diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index d3a68540d9b4..000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,50 +0,0 @@ -defaults: &defaults - docker : - - image: knqyf263/ci-trivy:latest - environment: - CGO_ENABLED: "0" - -jobs: - test: - <<: *defaults - steps: - - checkout - - run: - name: Test - command: go test ./... - release: - <<: *defaults - steps: - - checkout - - run: - name: Release - command: goreleaser --rm-dist - - run: - name: Clone trivy repository - command: git clone git@github.com:knqyf263/trivy-repo.git - - run: - name: Setup git settings - command: | - git config --global user.email "knqyf263@gmail.com" - git config --global user.name "Teppei Fukuda" - - run: - name: Create rpm repository - command: ci/deploy-rpm.sh - - run: - name: Import GPG key - command: echo -e "$GPG_KEY" | gpg --import - - run: - name: Create deb repository - command: ci/deploy-deb.sh - -workflows: - version: 2 - release: - jobs: - - test - - release: - filters: - branches: - ignore: /.*/ - tags: - only: /.*/ diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000000..9f28501b3999 --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ +--- +Language: Proto +BasedOnStyle: Google +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true diff --git a/.dockerignore b/.dockerignore index 51549fa5a6e6..610fbae488ba 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,6 @@ +.git +.github +.cache +.circleci +integration imgs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..1738163f9adf --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000000..46f4b5c85c25 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,22 @@ +# Global +* @knqyf263 + +# SBOM/Vulnerability scanning +pkg/dependency/ @knqyf263 @DmitriyLewen +pkg/fanal/ @knqyf263 @DmitriyLewen +pkg/sbom/ @knqyf263 @DmitriyLewen +pkg/scanner/ @knqyf263 @DmitriyLewen + +# Misconfiguration scanning +docs/docs/scanner/misconfiguration/ @simar7 @nikpivkin +docs/docs/target/aws.md @simar7 @nikpivkin +pkg/fanal/analyzer/config/ @simar7 @nikpivkin +pkg/cloud/ @simar7 @nikpivkin +pkg/iac/ @simar7 @nikpivkin + +# Helm chart +helm/trivy/ @chen-keinan + +# Kubernetes scanning +pkg/k8s/ @chen-keinan +docs/docs/target/kubernetes.md @chen-keinan diff --git a/.github/DISCUSSION_TEMPLATE/adopters.yml b/.github/DISCUSSION_TEMPLATE/adopters.yml new file mode 100644 index 000000000000..03008a4df4f0 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/adopters.yml @@ -0,0 +1,47 @@ +title: "" +labels: ["adopters"] +body: + - type: textarea + id: info + attributes: + label: "[Optional] How do you use Trivy?" + validations: + required: false + - type: textarea + id: info + attributes: + label: "[Optional] Can you provide us with a quote on your favourite part of Trivy? This may be used on the trivy.dev website, posted on Twitter (@AquaTrivy) or similar marketing material." + validations: + required: false + - type: checkboxes + attributes: + label: "[Optional] Which targets are you scanning with Trivy?" + options: + - label: "Container Image" + - label: "Filesystem" + - label: "Git Repository" + - label: "Virtual Machine Image" + - label: "Kubernetes" + - label: "AWS" + - label: "SBOM" + validations: + required: false + - type: checkboxes + attributes: + label: "[Optional] What kind of issues are scanning with Trivy?" + options: + - label: "Software Bill of Materials (SBOM)" + - label: "Known vulnerabilities (CVEs)" + - label: "IaC issues and misconfigurations" + - label: "Sensitive information and secrets" + - label: "Software licenses" + - type: markdown + attributes: + value: | + ## Get in touch + We are always looking for + * User feedback + * Collaboration with other companies and organisations + * Or just to have a chat with you about trivy. + If any of this interests you or your marketing team, please reach out at: oss@aquasec.com + We would love to hear from you! diff --git a/.github/DISCUSSION_TEMPLATE/bugs.yml b/.github/DISCUSSION_TEMPLATE/bugs.yml new file mode 100644 index 000000000000..711b85c88334 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/bugs.yml @@ -0,0 +1,124 @@ +labels: ["kind/bug"] +body: + - type: markdown + attributes: + value: | + #### Note + Feel free to raise a bug report if something doesn't work as expected. + Please ensure that you're not creating a duplicate report by searching the [issues](https://github.com/aquasecurity/trivy/issues)/[discussions](https://github.com/aquasecurity/trivy/discussions) beforehand. + If you see any false positives or false negatives, please file a ticket [here](https://github.com/aquasecurity/trivy/discussions/new?category=false-detection). + + **Do not open a GitHub issue, please.** Maintainers triage discussions and then create issues. + + Please also check [our contribution guidelines](https://aquasecurity.github.io/trivy/latest/community/contribute/discussion/). + - type: textarea + attributes: + label: Description + description: Briefly describe the problem you are having in a few paragraphs. + validations: + required: true + - type: textarea + attributes: + label: Desired Behavior + description: What did you expect to happen? + validations: + required: true + - type: textarea + attributes: + label: Actual Behavior + description: What happened instead? + validations: + required: true + - type: textarea + attributes: + label: Reproduction Steps + description: How do you trigger this bug? Please walk us through it step by step. + value: | + 1. + 2. + 3. + ... + render: bash + validations: + required: true + - type: dropdown + attributes: + label: Target + description: Which target are you scanning? It is equal to which subcommand you are using. + options: + - Container Image + - Filesystem + - Git Repository + - Virtual Machine Image + - Kubernetes + - AWS + - SBOM + validations: + required: false + - type: dropdown + attributes: + label: Scanner + description: Which scanner are you using? + options: + - Vulnerability + - Misconfiguration + - Secret + - License + validations: + required: false + - type: dropdown + attributes: + label: Output Format + description: Which output format are you using? + options: + - Table + - JSON + - Template + - SARIF + - CycloneDX + - SPDX + validations: + required: false + - type: dropdown + attributes: + label: Mode + description: Which mode are you using? Specify "Standalone" if you are not using `trivy server`. + options: + - Standalone + - Client/Server + validations: + required: false + - type: textarea + attributes: + label: Debug Output + description: Output of run with `--debug` + placeholder: "$ trivy --debug" + render: bash + validations: + required: true + - type: input + attributes: + label: Operating System + description: On what operating system are you running Trivy? + placeholder: "e.g. macOS Big Sur" + validations: + required: true + - type: textarea + attributes: + label: Version + description: Output of `trivy --version` + placeholder: "$ trivy --version" + render: bash + validations: + required: true + - type: checkboxes + attributes: + label: Checklist + description: Have you tried the following? + options: + - label: Run `trivy image --reset` + - label: Read [the troubleshooting](https://aquasecurity.github.io/trivy/latest/docs/references/troubleshooting/) + - type: markdown + attributes: + value: | + We would be happy if you could share how you are using Trivy [here](https://github.com/aquasecurity/trivy/discussions/new?category=adopters). \ No newline at end of file diff --git a/.github/DISCUSSION_TEMPLATE/documentation.yml b/.github/DISCUSSION_TEMPLATE/documentation.yml new file mode 100644 index 000000000000..5ef35e7efa1d --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/documentation.yml @@ -0,0 +1,28 @@ +labels: ["kind/documentation"] +body: + - type: markdown + attributes: + value: | + #### Note + Feel free to create a docs report if something doesn't work as expected or is unclear in the documentation. + Please ensure that you're not creating a duplicate report by searching the [issues](https://github.com/aquasecurity/trivy/issues)/[discussions](https://github.com/aquasecurity/trivy/discussions) beforehand. + + Please also check [our contribution guidelines](https://aquasecurity.github.io/trivy/latest/community/contribute/discussion/). + - type: textarea + attributes: + label: Description + description: Briefly describe the what has been unclear in the existing documentation + validations: + required: true + - type: textarea + attributes: + label: Link + description: Please provide a link to the current documentation or where you thought to find the information you were looking for + validations: + required: false + - type: textarea + attributes: + label: Suggestions + description: What would you like to have added or changed in the documentation? + validations: + required: true \ No newline at end of file diff --git a/.github/DISCUSSION_TEMPLATE/false-detection.yml b/.github/DISCUSSION_TEMPLATE/false-detection.yml new file mode 100644 index 000000000000..d56379032179 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/false-detection.yml @@ -0,0 +1,96 @@ +body: + - type: markdown + attributes: + value: | + #### Note + Feel free to raise a bug report if something doesn't work as expected. + Please ensure that you're not creating a duplicate report by searching the [issues](https://github.com/aquasecurity/trivy/issues)/[discussions](https://github.com/aquasecurity/trivy/discussions) beforehand. + + **Do not open a GitHub issue, please.** Maintainers triage discussions and then create issues. + + Please also check [our contribution guidelines](https://aquasecurity.github.io/trivy/latest/community/contribute/discussion/). + - type: input + attributes: + label: IDs + description: List the IDs of vulnerabilities, misconfigurations, secrets, or licenses that are either not detected or mistakenly detected. + placeholder: "e.g. CVE-2021-44228, CVE-2022-22965" + validations: + required: true + - type: textarea + attributes: + label: Description + description: Describe the false detection. + validations: + required: true + - type: textarea + attributes: + label: Reproduction Steps + description: How do you trigger this bug? Please walk us through it step by step. + value: | + 1. + 2. + 3. + ... + render: bash + validations: + required: true + - type: dropdown + attributes: + label: Target + description: Which target are you scanning? It is equal to which subcommand you are using. + options: + - Container Image + - Filesystem + - Git Repository + - Virtual Machine Image + - Kubernetes + - AWS + - SBOM + validations: + required: true + - type: dropdown + attributes: + label: Scanner + description: Which scanner are you using? + options: + - Vulnerability + - Misconfiguration + - Secret + - License + validations: + required: true + - type: input + attributes: + label: Target OS + description: What operating system are you scanning? Fill in this field if the scanning target is an operating system. + placeholder: "Example: Ubuntu 22.04" + validations: + required: false + - type: textarea + attributes: + label: Debug Output + description: Output of run with `--debug` + placeholder: "$ trivy --debug" + render: bash + validations: + required: true + - type: textarea + attributes: + label: Version + description: Output of `trivy --version` + placeholder: "$ trivy --version" + render: bash + validations: + required: true + - type: checkboxes + attributes: + label: Checklist + options: + - label: Read [the documentation regarding wrong detection](https://aquasecurity.github.io/trivy/dev/community/contribute/discussion/#false-detection) + - label: Ran Trivy with `-f json` that shows data sources and confirmed that the security advisory in data sources was correct + validations: + required: true + - type: markdown + attributes: + value: | + We would be happy if you could share how you are using Trivy [here](https://github.com/aquasecurity/trivy/discussions/new?category=adopters). \ No newline at end of file diff --git a/.github/DISCUSSION_TEMPLATE/ideas.yml b/.github/DISCUSSION_TEMPLATE/ideas.yml new file mode 100644 index 000000000000..c616af429b30 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/ideas.yml @@ -0,0 +1,47 @@ +labels: ["kind/feature"] +body: + - type: markdown + attributes: + value: | + #### Note + Feel free to share your idea. + Please ensure that you're not creating a duplicate ticket by searching the [issues](https://github.com/aquasecurity/trivy/issues)/[discussions](https://github.com/aquasecurity/trivy/discussions) beforehand. + + **Do not open a GitHub issue, please.** Maintainers triage discussions and then create issues. + + Please also check [our contribution guidelines](https://aquasecurity.github.io/trivy/latest/community/contribute/discussion/). + - type: textarea + attributes: + label: Description + description: Describe your idea. + validations: + required: true + - type: dropdown + attributes: + label: Target + description: Which target is your idea related to? + options: + - Container Image + - Filesystem + - Git Repository + - Virtual Machine Image + - Kubernetes + - AWS + - SBOM + validations: + required: false + - type: dropdown + attributes: + label: Scanner + description: Which scanner is your idea related to? + options: + - Vulnerability + - Misconfiguration + - Secret + - License + validations: + required: false + - type: markdown + attributes: + value: | + We would be happy if you could share how you are using Trivy [here](https://github.com/aquasecurity/trivy/discussions/new?category=adopters). diff --git a/.github/DISCUSSION_TEMPLATE/q-a.yml b/.github/DISCUSSION_TEMPLATE/q-a.yml new file mode 100644 index 000000000000..75ba80949a9b --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/q-a.yml @@ -0,0 +1,84 @@ +labels: ["triage/support"] +body: + - type: markdown + attributes: + value: | + #### Note + If you have any troubles/questions, feel free to ask. + Please ensure that you're not asking a duplicate question by searching the [issues](https://github.com/aquasecurity/trivy/issues)/[discussions](https://github.com/aquasecurity/trivy/discussions) beforehand. + + **Do not open a GitHub issue, please.** Maintainers triage discussions and then create issues. + + Please also check [our contribution guidelines](https://aquasecurity.github.io/trivy/latest/community/contribute/discussion/). + - type: textarea + attributes: + label: Question + description: What kind of problem are you facing? Or, what questions do you have? + validations: + required: true + - type: dropdown + attributes: + label: Target + description: Which target are you scanning? It is equal to which subcommand you are using. + options: + - Container Image + - Filesystem + - Git Repository + - Virtual Machine Image + - Kubernetes + - AWS + - SBOM + validations: + required: false + - type: dropdown + attributes: + label: Scanner + description: Which scanner are you using? + options: + - Vulnerability + - Misconfiguration + - Secret + - License + validations: + required: false + - type: dropdown + attributes: + label: Output Format + description: Which output format are you using? + options: + - Table + - JSON + - Template + - SARIF + - CycloneDX + - SPDX + validations: + required: false + - type: dropdown + attributes: + label: Mode + description: Which mode are you using? Specify "Standalone" if you are not using `trivy server`. + options: + - Standalone + - Client/Server + validations: + required: false + - type: input + attributes: + label: Operating System + description: What operating system are you using? + placeholder: "Example: macOS Big Sur" + validations: + required: false + - type: textarea + attributes: + label: Version + description: Output of `trivy --version` + placeholder: "$ trivy --version" + render: bash + validations: + required: false + - type: markdown + attributes: + value: | + We would be happy if you could share how you are using Trivy [here](https://github.com/aquasecurity/trivy/discussions/new?category=adopters. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..80284b7bd81d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,17 @@ +blank_issues_enabled: false +contact_links: + - name: Report a false detection + url: https://github.com/aquasecurity/trivy/discussions/new?category=false-detection + about: Report false positives/negatives + - name: Report a bug + url: https://github.com/aquasecurity/trivy/discussions/new?category=bugs + about: Report bugs + - name: Enhance documentation + url: https://github.com/aquasecurity/trivy/discussions/new?category=documentation + about: Make suggestions to the documentation + - name: Request a feature enhancement + url: https://github.com/aquasecurity/trivy/discussions/new?category=ideas + about: Share ideas for new features + - name: Ask the community for help + url: https://github.com/aquasecurity/trivy/discussions/new?category=q-a + about: Ask questions and discuss with other community members \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..a61a92b3cb11 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + - package-ecosystem: docker + directory: / + schedule: + interval: monthly + - package-ecosystem: gomod + open-pull-requests-limit: 10 + directory: / + schedule: + interval: monthly diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000000..890bf5a8c373 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +## Description + +## Related issues +- Close #XXX + +## Related PRs +- [ ] #XXX +- [ ] #YYY + +Remove this section if you don't have related PRs. + +## Checklist +- [ ] I've read the [guidelines for contributing](https://aquasecurity.github.io/trivy/latest/community/contribute/pr/) to this repository. +- [ ] I've followed the [conventions](https://aquasecurity.github.io/trivy/latest/community/contribute/pr/#title) in the PR title. +- [ ] I've added tests that prove my fix is effective or that my feature works. +- [ ] I've updated the [documentation](https://github.com/aquasecurity/trivy/blob/main/docs) with the relevant information (if needed). +- [ ] I've added usage information (if the PR introduces new options) +- [ ] I've included a "before" and "after" example to the description (if the PR is a user interface change). diff --git a/.github/workflows/auto-close-issue.yaml b/.github/workflows/auto-close-issue.yaml new file mode 100644 index 000000000000..6d6600ef8e9c --- /dev/null +++ b/.github/workflows/auto-close-issue.yaml @@ -0,0 +1,46 @@ +name: Auto-close issues + +on: + issues: + types: [opened] + +jobs: + close_issue: + runs-on: ubuntu-latest + steps: + - name: Close issue if user does not have write or admin permissions + uses: actions/github-script@v8 + with: + script: | + // Get the issue creator's username + const issueCreator = context.payload.issue.user.login; + + // Check the user's permissions for the repository + const repoPermissions = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: issueCreator + }); + + const permission = repoPermissions.data.permission; + + // If the user does not have write or admin permissions, leave a comment and close the issue + if (permission !== 'write' && permission !== 'admin') { + const commentBody = "Please see https://aquasecurity.github.io/trivy/latest/community/contribute/issue/"; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: commentBody + }); + + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + state: 'closed', + state_reason: 'not_planned' + }); + + console.log(`Issue #${context.payload.issue.number} closed because ${issueCreator} does not have sufficient permissions.`); + } \ No newline at end of file diff --git a/.github/workflows/auto-update-labels.yaml b/.github/workflows/auto-update-labels.yaml new file mode 100644 index 000000000000..fff97d2549a7 --- /dev/null +++ b/.github/workflows/auto-update-labels.yaml @@ -0,0 +1,30 @@ +name: Auto-update labels +on: + push: + paths: + - 'misc/triage/labels.yaml' + branches: + - main + +jobs: + deploy: + name: Auto-update labels + runs-on: ubuntu-latest + steps: + - name: Checkout main + uses: actions/checkout@v4.2.0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Install aqua tools + uses: aquaproj/aqua-installer@v4.0.4 + with: + aqua_version: v1.25.0 + + - name: update labels + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: mage label \ No newline at end of file diff --git a/.github/workflows/auto_merge.yml b/.github/workflows/auto_merge.yml new file mode 100644 index 000000000000..06409558b913 --- /dev/null +++ b/.github/workflows/auto_merge.yml @@ -0,0 +1,31 @@ +name: Auto Update and Merge + +on: + schedule: + - cron: '*/5 * * * *' # Run every 5 minutes + pull_request_target: + types: [assigned, unassigned, labeled, unlabeled, opened, edited, reopened, synchronize, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled] + branches: + - master # Replace 'target-branch' with your actual branch name + + +jobs: + auto-merge: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4.2.0 + with: + fetch-depth: 0 # Fetch all history for merging + ref: ${{ github.event.pull_request.head.ref }} # Checkout the PR's branch + + - name: Merge PR into master + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git checkout master + git pull origin master # Ensure master is up-to-date + git merge ${{ github.event.pull_request.head.ref }} --no-ff # Merge the PR's branch + + - name: Push changes + run: git push origin master diff --git a/.github/workflows/bypass-cla.yaml b/.github/workflows/bypass-cla.yaml new file mode 100644 index 000000000000..93818d08e41b --- /dev/null +++ b/.github/workflows/bypass-cla.yaml @@ -0,0 +1,12 @@ +# This workflow is used to bypass the required status checks in merge queue. +# cf. https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/troubleshooting-required-status-checks +name: CLA +on: + merge_group: + +jobs: + cla: + name: license/cla + runs-on: ubuntu-latest + steps: + - run: 'echo "No test required"' \ No newline at end of file diff --git a/.github/workflows/bypass-test.yaml b/.github/workflows/bypass-test.yaml new file mode 100644 index 000000000000..abf91aff45f6 --- /dev/null +++ b/.github/workflows/bypass-test.yaml @@ -0,0 +1,31 @@ +# This workflow is used to bypass the required status checks. +# cf. https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/troubleshooting-required-status-checks +name: Test +on: + push: + paths: + - '**.md' + - 'docs/**' + - 'mkdocs.yml' + - 'LICENSE' + pull_request: + paths: + - '**.md' + - 'docs/**' + - 'mkdocs.yml' + - 'LICENSE' +jobs: + test: + name: Test + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: [ubuntu-latest, windows-latest, macos-latest] + steps: + - run: 'echo "No test required"' + + integration: + name: Integration Test + runs-on: ubuntu-latest + steps: + - run: 'echo "No test required"' \ No newline at end of file diff --git a/.github/workflows/canary.yaml b/.github/workflows/canary.yaml new file mode 100644 index 000000000000..6a7f0a5fd164 --- /dev/null +++ b/.github/workflows/canary.yaml @@ -0,0 +1,60 @@ +name: Canary build +on: + push: + branches: + - 'main' + paths: + - '**.go' + - 'go.mod' + - 'Dockerfile.canary' + - '.github/workflows/canary.yaml' + workflow_dispatch: + +jobs: + build-binaries: + name: Build binaries + uses: ./.github/workflows/reusable-release.yaml + with: + goreleaser_config: goreleaser-canary.yml + goreleaser_options: '--snapshot --clean --timeout 60m' # will not release + secrets: inherit + + upload-binaries: + name: Upload binaries + needs: build-binaries # run this job after 'build-binaries' job completes + runs-on: ubuntu-latest + steps: + - name: Restore Trivy binaries from cache + uses: actions/cache@v5.0.3 + with: + path: dist/ + key: ${{ runner.os }}-bins-${{github.workflow}}-${{github.sha}} + + # Upload artifacts + - name: Upload artifacts (trivy_Linux-64bit) + uses: actions/upload-artifact@v6 + with: + name: trivy_Linux-64bit + path: dist/trivy_*_Linux-64bit.tar.gz + if-no-files-found: error + + - name: Upload artifacts (trivy_Linux-ARM64) + uses: actions/upload-artifact@v6 + with: + name: trivy_Linux-ARM64 + path: dist/trivy_*_Linux-ARM64.tar.gz + if-no-files-found: error + + - name: Upload artifacts (trivy_macOS-64bit) + uses: actions/upload-artifact@v6 + with: + name: trivy_macOS-64bit + path: dist/trivy_*_macOS-64bit.tar.gz + if-no-files-found: error + + - name: Upload artifacts (trivy_macOS-ARM64) + uses: actions/upload-artifact@v6 + with: + name: trivy_macOS-ARM64 + path: dist/trivy_*_macOS-ARM64.tar.gz + if-no-files-found: error \ No newline at end of file diff --git a/.github/workflows/mkdocs-dev.yaml b/.github/workflows/mkdocs-dev.yaml new file mode 100644 index 000000000000..849090230c5e --- /dev/null +++ b/.github/workflows/mkdocs-dev.yaml @@ -0,0 +1,34 @@ +name: Deploy the dev documentation +on: + push: + paths: + - 'docs/**' + - mkdocs.yml + branches: + - main +jobs: + deploy: + name: Deploy the dev documentation + runs-on: ubuntu-22.04 + steps: + - name: Checkout main + uses: actions/checkout@v4.2.0 + with: + fetch-depth: 0 + persist-credentials: true + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + pip install git+https://${GH_TOKEN}@github.com/squidfunk/mkdocs-material-insiders.git + pip install -r docs/build/requirements.txt + env: + GH_TOKEN: ${{ secrets.MKDOCS_AQUA_BOT }} + - name: Configure the git user + run: | + git config user.name "knqyf263" + git config user.email "knqyf263@gmail.com" + - name: Deploy the dev documents + run: mike deploy --push dev diff --git a/.github/workflows/mkdocs-latest.yaml b/.github/workflows/mkdocs-latest.yaml new file mode 100644 index 000000000000..bed98391b2ce --- /dev/null +++ b/.github/workflows/mkdocs-latest.yaml @@ -0,0 +1,42 @@ +name: Deploy the latest documentation +on: + workflow_dispatch: + inputs: + version: + description: Version to be deployed + required: true + push: + tags: + - "v*" +jobs: + deploy: + name: Deploy the latest documentation + runs-on: ubuntu-22.04 + steps: + - name: Checkout main + uses: actions/checkout@v4.2.0 + with: + fetch-depth: 0 + persist-credentials: true + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + pip install git+https://${GH_TOKEN}@github.com/squidfunk/mkdocs-material-insiders.git + pip install -r docs/build/requirements.txt + env: + GH_TOKEN: ${{ secrets.MKDOCS_AQUA_BOT }} + - name: Configure the git user + run: | + git config user.name "knqyf263" + git config user.email "knqyf263@gmail.com" + - name: Deploy the latest documents from new tag push + if: ${{ github.event.inputs.version == '' }} + run: | + VERSION=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g") + mike deploy --push --update-aliases ${VERSION%.*} latest + - name: Deploy the latest documents from manual trigger + if: ${{ github.event.inputs.version != '' }} + run: mike deploy --push --update-aliases ${{ github.event.inputs.version }} latest diff --git a/.github/workflows/publish-chart.yaml b/.github/workflows/publish-chart.yaml new file mode 100644 index 000000000000..a349772bb685 --- /dev/null +++ b/.github/workflows/publish-chart.yaml @@ -0,0 +1,87 @@ + +name: Publish Helm chart + +on: + workflow_dispatch: + pull_request: + branches: + - main + paths: + - 'helm/trivy/**' + push: + tags: + - "v*" +env: + HELM_REP: helm-charts + GH_OWNER: aquasecurity + CHART_DIR: helm/trivy + KIND_VERSION: "v0.14.0" + KIND_IMAGE: "kindest/node:v1.23.6@sha256:b1fa224cc6c7ff32455e0b1fd9cbfd3d3bc87ecaa8fcb06961ed1afb3db0f9ae" +jobs: + test-chart: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v4.2.0 + with: + fetch-depth: 0 + - name: Install Helm + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 + with: + version: v3.5.0 + - name: Set up python + uses: actions/setup-python@v5 + with: + python-version: 3.7 + - name: Setup Chart Linting + id: lint + uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f + - name: Setup Kubernetes cluster (KIND) + uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 + with: + version: ${{ env.KIND_VERSION }} + image: ${{ env.KIND_IMAGE }} + - name: Run chart-testing + run: ct lint-and-install --validate-maintainers=false --charts helm/trivy + - name: Run chart-testing (Ingress enabled) + run: | + sed -i -e '136s,false,'true',g' ./helm/trivy/values.yaml + ct lint-and-install --validate-maintainers=false --charts helm/trivy + + publish-chart: + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + needs: + - test-chart + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v4.2.0 + with: + fetch-depth: 0 + - name: Install chart-releaser + run: | + wget https://github.com/helm/chart-releaser/releases/download/v1.3.0/chart-releaser_1.3.0_linux_amd64.tar.gz + echo "baed2315a9bb799efb71d512c5198a2a3b8dcd139d7f22f878777cffcd649a37 chart-releaser_1.3.0_linux_amd64.tar.gz" | sha256sum -c - + tar xzvf chart-releaser_1.3.0_linux_amd64.tar.gz cr + - name: Package helm chart + run: | + ./cr package ${{ env.CHART_DIR }} + - name: Upload helm chart + # Failed with upload the same version: https://github.com/helm/chart-releaser/issues/101 + continue-on-error: true + run: | + ./cr upload -o ${{ env.GH_OWNER }} -r ${{ env.HELM_REP }} --token ${{ secrets.ORG_REPO_TOKEN }} -p .cr-release-packages + - name: Index helm chart + run: | + ./cr index -o ${{ env.GH_OWNER }} -r ${{ env.HELM_REP }} -c https://${{ env.GH_OWNER }}.github.io/${{ env.HELM_REP }}/ -i index.yaml + - name: Push index file + uses: dmnemec/copy_file_to_another_repo_action@c93037aa10fa8893de271f19978c980d0c1a9b37 #v1.1.1 + env: + API_TOKEN_GITHUB: ${{ secrets.ORG_REPO_TOKEN }} + with: + source_file: 'index.yaml' + destination_repo: '${{ env.GH_OWNER }}/${{ env.HELM_REP }}' + destination_folder: '.' + destination_branch: 'gh-pages' + user_email: aqua-bot@users.noreply.github.com + user_name: 'aqua-bot' diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 000000000000..2057d719913e --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,57 @@ +name: Release +on: + push: + tags: + - "v*" + +jobs: + release: + name: Release + uses: ./.github/workflows/reusable-release.yaml + with: + goreleaser_config: goreleaser.yml + goreleaser_options: '--clean --timeout 90m' + secrets: inherit + + deploy-packages: + name: Deploy rpm/dep packages + needs: release # run this job after 'release' job completes + runs-on: ubuntu-22.04 + steps: + - name: Checkout code + uses: actions/checkout@v4.2.0 + with: + fetch-depth: 0 + + - name: Restore Trivy binaries from cache + uses: actions/cache@v5.0.3 + with: + path: dist/ + key: ${{ runner.os }}-bins-${{github.workflow}}-${{github.sha}} + + - name: Install dependencies + run: | + sudo apt-get -y update + sudo apt-get -y install rpm reprepro createrepo-c distro-info + + - name: Checkout trivy-repo + uses: actions/checkout@v4.2.0 + with: + repository: ${{ github.repository_owner }}/trivy-repo + path: trivy-repo + fetch-depth: 0 + token: ${{ secrets.ORG_REPO_TOKEN }} + + - name: Setup git settings + run: | + git config --global user.email "knqyf263@gmail.com" + git config --global user.name "Teppei Fukuda" + + - name: Create rpm repository + run: ci/deploy-rpm.sh + + - name: Import GPG key + run: echo -e "${{ secrets.GPG_KEY }}" | gpg --import + + - name: Create deb repository + run: ci/deploy-deb.sh diff --git a/.github/workflows/reusable-release.yaml b/.github/workflows/reusable-release.yaml new file mode 100644 index 000000000000..6ef9eea74f11 --- /dev/null +++ b/.github/workflows/reusable-release.yaml @@ -0,0 +1,130 @@ +name: Reusable release +on: + workflow_call: + inputs: + goreleaser_config: + description: 'file path to GoReleaser config' + required: true + type: string + goreleaser_options: + description: 'GoReleaser options separated by spaces' + default: '' + required: false + type: string + +env: + GH_USER: "aqua-bot" + +jobs: + release: + name: Release + runs-on: ubuntu-latest + env: + DOCKER_CLI_EXPERIMENTAL: "enabled" + permissions: + id-token: write # For cosign + packages: write # For GHCR + contents: read # Not required for public repositories, but for clarity + steps: + - name: Maximize build space + uses: easimon/maximize-build-space@v10 + with: + root-reserve-mb: 32768 # The Go cache (`~/.cache/go-build` and `~/go/pkg`) requires a lot of storage space. + remove-android: 'true' + remove-docker-images: 'true' + remove-dotnet: 'true' + remove-haskell: 'true' + + - name: Cosign install + uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + + - name: Show available Docker Buildx platforms + run: echo ${{ steps.buildx.outputs.platforms }} + + - name: Login to docker.io registry + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to ghcr.io registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ env.GH_USER }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to ECR + uses: docker/login-action@v3 + with: + registry: public.ecr.aws + username: ${{ secrets.ECR_ACCESS_KEY_ID }} + password: ${{ secrets.ECR_SECRET_ACCESS_KEY }} + + - name: Checkout code + uses: actions/checkout@v4.2.0 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Generate SBOM + uses: CycloneDX/gh-gomod-generate-sbom@v2 + with: + args: mod -licenses -json -output bom.json + version: ^v1 + + - name: "save gpg key" + env: + GPG_KEY: ${{ secrets.GPG_KEY }} + run: | + echo "$GPG_KEY" > gpg.key + + - name: GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: v1.20.0 + args: release -f=${{ inputs.goreleaser_config}} ${{ inputs.goreleaser_options}} + env: + GITHUB_TOKEN: ${{ secrets.ORG_REPO_TOKEN }} + NFPM_DEFAULT_RPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + GPG_FILE: "gpg.key" + + - name: "remove gpg key" + run: | + rm gpg.key + + # Push images to registries (only for canary build) + # The custom Dockerfile.canary is necessary + # because GoReleaser Free doesn't support pushing images with the `--snapshot` flag. + - name: Build and push + if: ${{ inputs.goreleaser_config == 'goreleaser-canary.yml' }} + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64, linux/arm64 + file: ./Dockerfile.canary # path to Dockerfile + context: . + push: true + tags: | + aquasec/trivy:canary + ghcr.io/aquasecurity/trivy:canary + public.ecr.aws/aquasecurity/trivy:canary + + - name: Cache Trivy binaries + uses: actions/cache@v5.0.3 + with: + path: dist/ + # use 'github.sha' to create a unique cache folder for each run. + # use 'github.workflow' to create a unique cache folder if some runs have same commit sha. + # e.g. build and release runs + key: ${{ runner.os }}-bins-${{github.workflow}}-${{github.sha}} diff --git a/.github/workflows/roadmap.yaml b/.github/workflows/roadmap.yaml new file mode 100644 index 000000000000..96f6e7ae4fb8 --- /dev/null +++ b/.github/workflows/roadmap.yaml @@ -0,0 +1,79 @@ +name: Add issues to the roadmap project + +on: + issues: + types: + - labeled + +jobs: + add-issue-to-roadmap-project: + name: Add issue to the roadmap project + runs-on: ubuntu-latest + steps: + # 'kind/feature' AND 'priority/backlog' labels -> 'Backlog' column + - uses: actions/add-to-project@v1.0.2 # add new issue to project + with: + project-url: https://github.com/orgs/aquasecurity/projects/25 + github-token: ${{ secrets.ORG_PROJECT_TOKEN }} + labeled: kind/feature, priority/backlog + label-operator: AND + id: add-backlog-issue + - uses: titoportas/update-project-fields@v0.1.0 # change Priority(column) of added issue + if: ${{ steps.add-backlog-issue.outputs.itemId }} + with: + project-url: https://github.com/orgs/aquasecurity/projects/25 + github-token: ${{ secrets.ORG_PROJECT_TOKEN }} + item-id: ${{ steps.add-backlog-issue.outputs.itemId }} # Use the item-id output of the previous step + field-keys: Priority + field-values: Backlog + + # 'kind/feature' AND 'priority/important-longterm' labels -> 'Important (long-term)' column + - uses: actions/add-to-project@v1.0.2 # add new issue to project + with: + project-url: https://github.com/orgs/aquasecurity/projects/25 + github-token: ${{ secrets.ORG_PROJECT_TOKEN }} + labeled: kind/feature, priority/important-longterm + label-operator: AND + id: add-longterm-issue + - uses: titoportas/update-project-fields@v0.1.0 # change Priority(column) of added issue + if: ${{ steps.add-longterm-issue.outputs.itemId }} + with: + project-url: https://github.com/orgs/aquasecurity/projects/25 + github-token: ${{ secrets.ORG_PROJECT_TOKEN }} + item-id: ${{ steps.add-longterm-issue.outputs.itemId }} # Use the item-id output of the previous step + field-keys: Priority + field-values: Important (long-term) + + # 'kind/feature' AND 'priority/important-soon' labels -> 'Important (soon)' column + - uses: actions/add-to-project@v1.0.2 # add new issue to project + with: + project-url: https://github.com/orgs/aquasecurity/projects/25 + github-token: ${{ secrets.ORG_PROJECT_TOKEN }} + labeled: kind/feature, priority/important-soon + label-operator: AND + id: add-soon-issue + - uses: titoportas/update-project-fields@v0.1.0 # change Priority(column) of added issue + if: ${{ steps.add-soon-issue.outputs.itemId }} + with: + project-url: https://github.com/orgs/aquasecurity/projects/25 + github-token: ${{ secrets.ORG_PROJECT_TOKEN }} + item-id: ${{ steps.add-soon-issue.outputs.itemId }} # Use the item-id output of the previous step + field-keys: Priority + field-values: Important (soon) + + # 'kind/feature' AND 'priority/critical-urgent' labels -> 'Urgent' column + - uses: actions/add-to-project@v1.0.2 # add new issue to project + with: + project-url: https://github.com/orgs/aquasecurity/projects/25 + github-token: ${{ secrets.ORG_PROJECT_TOKEN }} + labeled: kind/feature, priority/critical-urgent + label-operator: AND + id: add-urgent-issue + - uses: titoportas/update-project-fields@v0.1.0 # change Priority(column) of added issue + if: ${{ steps.add-urgent-issue.outputs.itemId }} + with: + project-url: https://github.com/orgs/aquasecurity/projects/25 + github-token: ${{ secrets.ORG_PROJECT_TOKEN }} + item-id: ${{ steps.add-urgent-issue.outputs.itemId }} # Use the item-id output of the previous step + field-keys: Priority + field-values: Urgent \ No newline at end of file diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml new file mode 100644 index 000000000000..86c9e722501e --- /dev/null +++ b/.github/workflows/scan.yaml @@ -0,0 +1,23 @@ +name: Scan vulnerabilities +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +jobs: + build: + name: Scan Go vulnerabilities + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4.2.0 + + - name: Run Trivy vulnerability scanner and create GitHub issues + uses: knqyf263/trivy-issue-action@v0.0.6 + with: + assignee: knqyf263 + severity: CRITICAL + skip-dirs: integration,examples,pkg + label: kind/security + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml new file mode 100644 index 000000000000..0183c09f1d13 --- /dev/null +++ b/.github/workflows/semantic-pr.yaml @@ -0,0 +1,104 @@ +name: "Lint PR title" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: | + feat + fix + docs + style + refactor + perf + test + build + ci + chore + revert + BREAKING + + scopes: | + vuln + misconf + secret + license + + image + fs + repo + sbom + server + k8s + aws + vm + + alpine + wolfi + chainguard + redhat + alma + rocky + mariner + oracle + debian + ubuntu + amazon + suse + photon + distroless + windows + + ruby + php + python + nodejs + rust + dotnet + java + go + c + c\+\+ + elixir + dart + swift + bitnami + + os + lang + + kubernetes + dockerfile + terraform + cloudformation + + docker + podman + containerd + oci + + cli + flag + + cyclonedx + spdx + purl + vex + + helm + report + db + parser + deps diff --git a/.github/workflows/stale-issues.yaml b/.github/workflows/stale-issues.yaml new file mode 100644 index 000000000000..077f30ea8180 --- /dev/null +++ b/.github/workflows/stale-issues.yaml @@ -0,0 +1,19 @@ +name: "Stale PR's" +on: + schedule: + - cron: '0 0 * * *' +jobs: + stale: + timeout-minutes: 1 + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v10 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-pr-message: 'This PR is stale because it has been labeled with inactivity.' + exempt-pr-labels: 'lifecycle/active' + stale-pr-label: 'lifecycle/stale' + days-before-stale: 60 + days-before-issue-stale: '-1' + days-before-close: 20 + days-before-issue-close: '-1' diff --git a/.github/workflows/test-docs.yaml b/.github/workflows/test-docs.yaml new file mode 100644 index 000000000000..0492292c43f9 --- /dev/null +++ b/.github/workflows/test-docs.yaml @@ -0,0 +1,29 @@ +name: Test docs +on: + pull_request: + paths: + - 'docs/**' + - 'mkdocs.yml' +jobs: + build-documents: + name: Documentation Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.2.0 + with: + fetch-depth: 0 + persist-credentials: true + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + pip install -r docs/build/requirements.txt + - name: Configure the git user + run: | + git config user.name "knqyf263" + git config user.email "knqyf263@gmail.com" + - name: Deploy the dev documents + run: mike deploy test diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 000000000000..1f739578d754 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,219 @@ +name: Test +on: + pull_request: + paths-ignore: + - '**.md' + - 'docs/**' + - 'mkdocs.yml' + - 'LICENSE' + merge_group: +jobs: + test: + name: Test + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: [ubuntu-latest, windows-latest, macos-latest] + steps: + - name: Maximize build space + uses: easimon/maximize-build-space@v10 + with: + root-reserve-mb: 32768 # The golangci-lint uses a lot of space. + remove-android: "true" + remove-docker-images: "true" + remove-dotnet: "true" + remove-haskell: "true" + if: matrix.operating-system == 'ubuntu-latest' + + - uses: actions/checkout@v4.2.0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: go mod tidy + run: | + go mod tidy + if [ -n "$(git status --porcelain)" ]; then + echo "Run 'go mod tidy' and push it" + exit 1 + fi + if: matrix.operating-system == 'ubuntu-latest' + + - name: Lint + id: lint + uses: golangci/golangci-lint-action@v8.0.0 + with: + version: v1.54 + args: --deadline=30m --out-format=line-number + skip-cache: true # https://github.com/golangci/golangci-lint-action/issues/244#issuecomment-1052197778 + if: matrix.operating-system == 'ubuntu-latest' + + - name: Check if linter failed + run: | + echo "Linter failed, running 'mage lint:fix' might help to correct some errors" + exit 1 + if: ${{ failure() && steps.lint.conclusion == 'failure' }} + + - name: Install tools + uses: aquaproj/aqua-installer@v4.0.4 + with: + aqua_version: v1.25.0 + aqua_opts: "" + + - name: Check if CLI references are up-to-date + run: | + mage docs:generate + if [ -n "$(git status --porcelain)" ]; then + echo "Run 'mage docs:generate' and push it" + exit 1 + fi + if: matrix.operating-system == 'ubuntu-latest' + + - name: Run unit tests + run: mage test:unit + + integration: + name: Integration Test + runs-on: ubuntu-latest + steps: + - name: Check out code into the Go module directory + uses: actions/checkout@v4.2.0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Install tools + uses: aquaproj/aqua-installer@v4.0.4 + with: + aqua_version: v1.25.0 + + - name: Run integration tests + run: mage test:integration + + k8s-integration: + name: K8s Integration Test + runs-on: ubuntu-latest + steps: + - name: Maximize build space + uses: easimon/maximize-build-space@v10 + with: + root-reserve-mb: 32768 # The Go cache (`~/.cache/go-build` and `~/go/pkg`) requires a lot of storage space. + remove-android: "true" + remove-docker-images: "true" + remove-dotnet: "true" + remove-haskell: "true" + + - name: Check out code into the Go module directory + uses: actions/checkout@v4.2.0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Install tools + uses: aquaproj/aqua-installer@v4.0.4 + with: + aqua_version: v1.25.0 + + - name: Run k8s integration tests + run: mage test:k8s + + module-test: + name: Module Integration Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.2.0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Install tools + uses: aquaproj/aqua-installer@v4.0.4 + with: + aqua_version: v1.25.0 + + - name: Run module integration tests + shell: bash + run: | + mage test:module + + vm-test: + name: VM Integration Test + runs-on: ubuntu-latest + steps: + - name: Maximize build space + uses: easimon/maximize-build-space@v10 + with: + root-reserve-mb: 32768 # The Go cache (`~/.cache/go-build` and `~/go/pkg`) requires a lot of storage space. + remove-android: 'true' + remove-docker-images: 'true' + remove-dotnet: 'true' + remove-haskell: 'true' + + - name: Checkout + uses: actions/checkout@v4.2.0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - name: Install tools + uses: aquaproj/aqua-installer@v4.0.4 + with: + aqua_version: v1.25.0 + - name: Run vm integration tests + run: | + mage test:vm + + build-test: + name: Build Test + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: [ubuntu-latest, windows-latest, macos-latest] + env: + DOCKER_CLI_EXPERIMENTAL: "enabled" + steps: + - name: Maximize build space + uses: easimon/maximize-build-space@v10 + with: + root-reserve-mb: 32768 # The Go cache (`~/.cache/go-build` and `~/go/pkg`) requires a lot of storage space. + remove-android: 'true' + remove-docker-images: 'true' + remove-dotnet: 'true' + remove-haskell: 'true' + if: matrix.operating-system == 'ubuntu-latest' + + - name: Checkout + uses: actions/checkout@v4.2.0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Determine GoReleaser ID + id: goreleaser_id + shell: bash + run: | + if [ "${{ matrix.operating-system }}" == "windows-latest" ]; then + echo "id=--id build-windows" >> $GITHUB_OUTPUT + elif [ "${{ matrix.operating-system }}" == "macos-latest" ]; then + echo "id=--id build-macos --id build-bsd" >> $GITHUB_OUTPUT + else + echo "id=--id build-linux" >> $GITHUB_OUTPUT + fi + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: v1.20.0 + args: build --snapshot --clean --timeout 90m ${{ steps.goreleaser_id.outputs.id }} diff --git a/.gitignore b/.gitignore index 97cca67c6785..0ee064889504 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,31 @@ *.dll *.so *.dylib +/trivy +## chart release +.cr-release-packages # Test binary, build with `go test -c` *.test - # Output of the go coverage tool, specifically when used with LiteIDE *.out - .idea +.vscode +# Directory Cache Files +.DS_Store +thumbs.db +# test fixtures +coverage.txt +integration/testdata/fixtures/images +integration/testdata/fixtures/vm-images +# SBOMs generated during CI +/bom.json +# goreleaser output +dist +# WebAssembly +*.wasm +# Signing +gpg.key +cmd/trivy/trivy +# macOS system files +._* diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 000000000000..7be028f1e1ce --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,120 @@ +linters-settings: + errcheck: + check-type-assertions: true + check-blank: true + govet: + check-shadowing: false + gofmt: + simplify: false + revive: + ignore-generated-header: true + gocyclo: + min-complexity: 20 + dupl: + threshold: 100 + goconst: + min-len: 3 + min-occurrences: 3 + misspell: + locale: US + ignore-words: + - licence + - optimise + gosec: + excludes: + - G101 + - G114 + - G204 + - G402 + gci: + sections: + - standard + - default + - prefix(github.com/aquasecurity/) + - blank + - dot + gomodguard: + blocked: + modules: + - github.com/hashicorp/go-version: + recommendations: + - github.com/aquasecurity/go-version + reason: "`aquasecurity/go-version` is designed for our use-cases" + - github.com/Masterminds/semver: + recommendations: + - github.com/aquasecurity/go-version + reason: "`aquasecurity/go-version` is designed for our use-cases" + gocritic: + disabled-checks: + - appendAssign + - unnamedResult + - whyNoLint + - indexAlloc + - octalLiteral + - hugeParam + - rangeValCopy + - regexpSimplify + - sloppyReassign + - commentedOutCode + enabled-tags: + - diagnostic + - style + - performance + - experimental + - opinionated + settings: + ruleguard: + failOn: all + rules: '${configDir}/misc/lint/rules.go' + +linters: + disable-all: true + enable: + - unused + - ineffassign + - typecheck + - govet + - revive + - gosec + - unconvert + - goconst + - gocyclo + - gofmt + - misspell + - bodyclose + - gci + - gomodguard + - tenv + - gocritic + +run: + go: '1.21' + skip-files: + - ".*_mock.go$" + - ".*_test.go$" + - "integration/*" + - "examples/*" + skip-dirs: + - "pkg/iac/scanners/terraform/parser/funcs" # copies of Terraform functions + +issues: + exclude-rules: + - linters: + - gosec + text: "G304: Potential file inclusion" + - linters: + - gosec + text: "Deferring unsafe method" + - linters: + - errcheck + text: "Close` is not checked" + - linters: + - errcheck + text: "os.*` is not checked" + - linters: + - golint + text: "a blank import should be only in a main or test package" + exclude: + - "should have a package comment, unless it's in another file for this package" + exclude-use-default: false + max-same-issues: 0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000000..60cfd8b2c3dd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +See [Issues](https://aquasecurity.github.io/trivy/latest/community/contribute/issue/) and [Pull Requests](https://aquasecurity.github.io/trivy/latest/community/contribute/pr/) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2aa304aac953..40468c7653e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,5 @@ -FROM golang:1.12-alpine AS builder -ADD go.mod go.sum /app/ -WORKDIR /app/ -RUN apk --no-cache add git -RUN go mod download -ADD . /app/ -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o /trivy cmd/trivy/main.go - -FROM alpine:3.9 +FROM alpine:3.23.3 RUN apk --no-cache add ca-certificates git -COPY --from=builder /trivy /usr/local/bin/trivy -RUN chmod +x /usr/local/bin/trivy - -CMD ["trivy"] +COPY trivy /usr/local/bin/trivy +COPY contrib/*.tpl contrib/ +ENTRYPOINT ["trivy"] diff --git a/Dockerfile.canary b/Dockerfile.canary new file mode 100644 index 000000000000..3b8c2c00b84a --- /dev/null +++ b/Dockerfile.canary @@ -0,0 +1,11 @@ +FROM alpine:3.23.3 +RUN apk --no-cache add ca-certificates git + +# binaries were created with GoReleaser +# need to copy binaries from folder with correct architecture +# example architecture folder: dist/trivy_canary_build_linux_arm64/trivy +# GoReleaser adds _v* to the folder name, but only when GOARCH is amd64 +ARG TARGETARCH +COPY "dist/trivy_canary_build_linux_${TARGETARCH}*/trivy" /usr/local/bin/trivy +COPY contrib/*.tpl contrib/ +ENTRYPOINT ["trivy"] diff --git a/Dockerfile.protoc b/Dockerfile.protoc new file mode 100644 index 000000000000..534b08636d7c --- /dev/null +++ b/Dockerfile.protoc @@ -0,0 +1,20 @@ +FROM --platform=linux/amd64 golang:1.26 + +# Set environment variable for protoc +ENV PROTOC_ZIP=protoc-3.19.4-linux-x86_64.zip + +# Install unzip for protoc installation and clean up cache +RUN apt-get update && apt-get install -y unzip && rm -rf /var/lib/apt/lists/* + +# Download and install protoc +RUN curl --retry 5 -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/$PROTOC_ZIP \ + && unzip -o $PROTOC_ZIP -d /usr/local bin/protoc \ + && unzip -o $PROTOC_ZIP -d /usr/local 'include/*' \ + && rm -f $PROTOC_ZIP + +# Install Go tools +RUN go install github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.0 +RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.1 +RUN go install github.com/magefile/mage@v1.14.0 + +ENV TRIVY_PROTOC_CONTAINER=true diff --git a/LICENSE b/LICENSE index 7ce066ea3308..261eeb9e9f8b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2019 Teppei Fukuda - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 000000000000..3fe97bf7d4b0 --- /dev/null +++ b/NOTICE @@ -0,0 +1,4 @@ +Trivy +Copyright 2019-2020 Aqua Security Software Ltd. + +This product includes software developed by Aqua Security (https://aquasec.com). diff --git a/README.md b/README.md index d629d3ed4a8c..f02dd9c0cd9b 100644 --- a/README.md +++ b/README.md @@ -1,255 +1,150 @@ - +
+ -[![GitHub release](https://img.shields.io/github/release/knqyf263/trivy.svg)](https://github.com/knqyf263/trivy/releases/latest) -[![CircleCI](https://circleci.com/gh/knqyf263/trivy.svg?style=svg)](https://circleci.com/gh/knqyf263/trivy) -[![Go Report Card](https://goreportcard.com/badge/github.com/knqyf263/trivy)](https://goreportcard.com/report/github.com/knqyf263/trivy) -[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/knqyf263/trivy/blob/master/LICENSE) +[![GitHub Release][release-img]][release] +[![Test][test-img]][test] +[![Go Report Card][go-report-img]][go-report] +[![License: Apache-2.0][license-img]][license] +[![GitHub Downloads][github-downloads-img]][release] +![Docker Pulls][docker-pulls] -A Simple and Comprehensive Vulnerability Scanner for Containers, Compatible with CI +[📖 Documentation][docs] +
-# Abstract -`Trivy` is a simple and comprehensive vulnerability scanner for containers. -`Trivy` detects vulnerabilities of OS packages (Alpine, RHEL, CentOS, etc.) and application dependencies (Bundler, Composer, npm, etc.). -`Trivy` is easy to use. Just install the binary and you're ready to scan. It can be scanned just by specifying a container image name. +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. -It is considered to be used in CI. Before pushing to a container registry, you can scan your local container image easily. -See [here](#continuous-integration-ci) for details. +Targets (what Trivy can scan): +- Container Image +- Filesystem +- Git Repository (remote) +- Virtual Machine Image +- Kubernetes +- AWS -# Features -- Detect comprehensive vulnerabilities - - OS packages (Alpine, Red Hat Enterprise Linux, CentOS, Debian, Ubuntu) - - **Application dependencies** (Bundler, Composer, Pipenv, npm) -- Simple - - Specify only an image name -- Easy installation - - **No need for prerequirements** such as installation of DB, libraries, etc. - - `apt-get install`, `yum install` and `brew install` is possible (See [Installation](#installation)) -- High accuracy - - Especially Alpine -- **Compatible with CI** - - See [CI Example](#continuous-integration-ci) +Scanners (what Trivy can find there): +- OS packages and software dependencies in use (SBOM) +- Known vulnerabilities (CVEs) +- IaC issues and misconfigurations +- Sensitive information and secrets +- Software licenses -# Installation +Trivy supports most popular programming languages, operating systems, and platforms. For a complete list, see the [Scanning Coverage] page. -## RHEL/CentOS +To learn more, go to the [Trivy homepage][homepage] for feature highlights, or to the [Documentation site][docs] for detailed information. -Add repository setting to `/etc/yum.repos.d`. +## Quick Start -``` -$ sudo vim /etc/yum.repos.d/trivy.repo -[trivy] -name=Trivy repository -baseurl=https://knqyf263.github.io/trivy-repo/rpm/releases/$releasever/$basearch/ -gpgcheck=0 -enabled=1 -$ sudo yum -y update -$ sudo yum -y install trivy -``` +### Get Trivy -or - -``` -$ rpm -ivh https://github.com/knqyf263/trivy/releases/download/v0.0.3/trivy_0.0.3_Linux-64bit.rpm -``` +Trivy is available in most common distribution channels. The full list of installation options is available in the [Installation] page. Here are a few popular examples: -## Debian/Ubuntu +- `brew install trivy` +- `docker run aquasec/trivy` +- Download binary from +- See [Installation] for more -Replace `[CODE_NAME]` with your code name +Trivy is integrated with many popular platforms and applications. The complete list of integrations is available in the [Ecosystem] page. Here are a few popular examples: -CODE_NAME: wheezy, jessie, stretch, buster, trusty, xenial, bionic +- [GitHub Actions](https://github.com/aquasecurity/trivy-action) +- [Kubernetes operator](https://github.com/aquasecurity/trivy-operator) +- [VS Code plugin](https://github.com/aquasecurity/trivy-vscode-extension) +- See [Ecosystem] for more -``` -$ sudo apt-get install apt-transport-https gnupg -$ wget -qO - https://knqyf263.github.io/trivy-repo/deb/public.key | sudo apt-key add - -$ echo deb https://knqyf263.github.io/trivy-repo/deb [CODE_NAME] main | sudo tee -a /etc/apt/sources.list -$ sudo apt-get update -$ sudo apt-get install trivy -``` +### Canary builds +There are canary builds ([Docker Hub](https://hub.docker.com/r/aquasec/trivy/tags?page=1&name=canary), [GitHub](https://github.com/aquasecurity/trivy/pkgs/container/trivy/75776514?tag=canary), [ECR](https://gallery.ecr.aws/aquasecurity/trivy#canary) images and [binaries](https://github.com/aquasecurity/trivy/actions/workflows/canary.yaml)) as generated every push to main branch. -or +Please be aware: canary builds might have critical bugs, it's not recommended for use in production. -``` -$ sudo apt-get install rpm -$ wget https://github.com/knqyf263/trivy/releases/download/v0.0.3/trivy_0.0.3_Linux-64bit.deb -$ sudo dpkg -i trivy_0.0.3_Linux-64bit.deb -``` +### General usage -## Mac OS X / Homebrew -You can use homebrew on OS X. -``` -$ brew tap knqyf263/trivy -$ brew install knqyf263/trivy/trivy +```bash +trivy [--scanners ] ``` -## Binary (Including Windows) -Go to [the releases page](https://github.com/knqyf263/trivy/releases), find the version you want, and download the zip file. Unpack the zip file, and put the binary to somewhere you want (on UNIX-y systems, /usr/local/bin or the like). Make sure it has execution bits turned on. +Examples: -## From source - -```sh -$ go get -u github.com/knqyf263/trivy +```bash +trivy image python:3.4-alpine ``` -# Examples -## Continuous Integration (CI) -Scan your image built in Travis CI/CircleCI. The test will fail if a vulnerability is found. When you don't want to fail the test, specify `--exit-code 0` . - -**Note**: The first time take a while (faster by cache after the second time) -### Travis CI +
+Result -``` -$ cat .travis.yml -services: - - docker - -before_install: - - docker build -t trivy-ci-test:latest . - - wget https://github.com/knqyf263/trivy/releases/download/v0.0.3/trivy_0.0.3_Linux-64bit.tar.gz - - tar zxvf trivy_0.0.3_Linux-64bit.tar.gz -script: - - ./trivy --exit-code 1 --quiet trivy-ci-test:latest -cache: - directories: - - $HOME/.cache/trivy -``` +https://user-images.githubusercontent.com/1161307/171013513-95f18734-233d-45d3-aaf5-d6aec687db0e.mov -example: https://travis-ci.org/knqyf263/trivy-ci-test -repository: https://github.com/knqyf263/trivy-ci-test +
-### Circle CI - -``` -$ cat .circleci/config.yml -jobs: - build: - docker: - - image: docker:18.09-git - steps: - - checkout - - setup_remote_docker - - restore_cache: - key: vulnerability-db - - run: - name: Build image - command: docker build -t trivy-ci-test:latest . - - run: - name: Install trivy - command: | - wget https://github.com/knqyf263/trivy/releases/download/v0.0.4/trivy_0.0.4_Linux-64bit.tar.gz - tar zxvf trivy_0.0.4_Linux-64bit.tar.gz - mv trivy /usr/local/bin - - run: - name: Scan the local image with trivy - command: trivy --exit-code 1 --quiet trivy-ci-test:latest - - save_cache: - key: vulnerability-db - paths: - - $HOME/.cache/trivy -workflows: - version: 2 - release: - jobs: - - build +```bash +trivy fs --scanners vuln,secret,misconfig myproject/ ``` -example: https://circleci.com/gh/knqyf263/trivy-ci-test -repository: https://github.com/knqyf263/trivy-ci-test +
+Result -# Usage +https://user-images.githubusercontent.com/1161307/171013917-b1f37810-f434-465c-b01a-22de036bd9b3.mov -``` -NAME: - trivy - A simple and comprehensive vulnerability scanner for containers -USAGE: - trivy [options] image_name -VERSION: - 0.0.3 -OPTIONS: - --format value, -f value format (table, json) (default: "table") - --input value, -i value input file path instead of image name - --severity value, -s value severities of vulnerabilities to be displayed (comma separated) (default: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL") - --output value, -o value output file name - --exit-code value Exit code when vulnerabilities were found (default: 0) - --skip-update skip db update - --clean, -c clean all cache - --quiet, -q suppress progress bar - --ignore-unfixed display only fixed vulnerabilities - --refresh refresh DB (usually used after version update of trivy - --debug, -d debug mode - --help, -h show help - --version, -v print the version -``` +
-# Q&A -## Homebrew -### Error: Your macOS keychain GitHub credentials do not have sufficient scope! - -``` -$ brew tap knqyf263/trivy -Error: Your macOS keychain GitHub credentials do not have sufficient scope! -Scopes they need: none -Scopes they have: -Create a personal access token: - https://github.com/settings/tokens/new?scopes=gist,public_repo&description=Homebrew -echo 'export HOMEBREW_GITHUB_API_TOKEN=your_token_here' >> ~/.zshrc +```bash +trivy k8s --report summary cluster ``` -Try: -``` -$ printf "protocol=https\nhost=github.com\n" | git credential-osxkeychain erase -``` +
+Result -### Error: knqyf263/trivy/trivy 64 already installed +![k8s summary](docs/imgs/trivy-k8s.png) -``` -$ brew upgrade -... -Error: knqyf263/trivy/trivy 64 already installed -``` +
-Try: +## FAQ -``` -$ brew unlink trivy && brew uninstall trivy -($ rm -rf /usr/local/Cellar/trivy/64) -$ brew install knqyf263/trivy/trivy -``` +### How to pronounce the name "Trivy"? -## Others -### Detected version update of trivy. Please try again with --refresh option -Try again with `--refresh` option +`tri` is pronounced like **tri**gger, `vy` is pronounced like en**vy**. -``` -$ trivy --refresh alpine:3.9 -``` +## Want more? Check out Aqua -### Unknown error -Try again with `--clean` option - -``` -$ trivy --clean -``` +If you liked Trivy, you will love Aqua which builds on top of Trivy to provide even more enhanced capabilities for a complete security management offering. +You can find a high level comparison table specific to Trivy users [here](https://github.com/aquasecurity/resources/blob/main/trivy-aqua.md). +In addition check out the website for more information about our products and services. +If you'd like to contact Aqua or request a demo, please use this form: -# Contribute +## Community -1. fork a repository: github.com/knqyf263/trivy to github.com/you/repo -2. get original code: `go get github.com/knqyf263/trivy` -3. work on original code -4. add remote to your repo: git remote add myfork https://github.com/you/repo.git -5. push your changes: git push myfork -6. create a new Pull Request +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] +Join our [Slack community][slack] to stay up to date with community efforts. -- see [GitHub and Go: forking, pull requests, and go-getting](http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html) +Please ensure to abide by our [Code of Conduct][code-of-conduct] during all interactions. ----- +[test]: https://github.com/aquasecurity/trivy/actions/workflows/test.yaml +[test-img]: https://github.com/aquasecurity/trivy/actions/workflows/test.yaml/badge.svg +[go-report]: https://goreportcard.com/report/github.com/aquasecurity/trivy +[go-report-img]: https://goreportcard.com/badge/github.com/aquasecurity/trivy +[release]: https://github.com/aquasecurity/trivy/releases +[release-img]: https://img.shields.io/github/release/aquasecurity/trivy.svg?logo=github +[github-downloads-img]: https://img.shields.io/github/downloads/aquasecurity/trivy/total?logo=github +[docker-pulls]: https://img.shields.io/docker/pulls/aquasec/trivy?logo=docker&label=docker%20pulls%20%2F%20trivy +[license]: https://github.com/aquasecurity/trivy/blob/main/LICENSE +[license-img]: https://img.shields.io/badge/License-Apache%202.0-blue.svg +[homepage]: https://trivy.dev +[docs]: https://aquasecurity.github.io/trivy +[pronunciation]: #how-to-pronounce-the-name-trivy +[slack]: https://slack.aquasec.com +[code-of-conduct]: https://github.com/aquasecurity/community/blob/main/CODE_OF_CONDUCT.md -# Credits -Special thanks to [Tomoya Amachi](https://github.com/tomoyamachi) +[Installation]:https://aquasecurity.github.io/trivy/latest/getting-started/installation/ +[Ecosystem]: https://aquasecurity.github.io/trivy/latest/ecosystem/ +[Scanning Coverage]: https://aquasecurity.github.io/trivy/latest/docs/coverage/ -# License -MIT +[alpine]: https://ariadne.space/2021/06/08/the-vulnerability-remediation-lifecycle-of-alpine-containers/ +[rego]: https://www.openpolicyagent.org/docs/latest/#rego +[sigstore]: https://www.sigstore.dev/ -# Author -Teppei Fukuda (knqyf263) +[aquasec]: https://aquasec.com +[oss]: https://www.aquasec.com/products/open-source-projects/ +[discussions]: https://github.com/aquasecurity/trivy/discussions diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000000..e04c319dc302 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,10 @@ +# Security Policy + +## Supported Versions + +This is an open source project that is provided as-is without warrenty or liability. +As such no supportability commitment. The maintainers will do the best they can to address any report promptly and responsibly. + +## Reporting a Vulnerability + +Please use the "Private vulnerability reporting" feature in the GitHub repository (under the "Security" tab). diff --git a/aqua.yaml b/aqua.yaml new file mode 100644 index 000000000000..0ffe7fbe2b81 --- /dev/null +++ b/aqua.yaml @@ -0,0 +1,10 @@ +--- +# aqua - Declarative CLI Version Manager +# https://aquaproj.github.io/ +registries: +- type: standard + ref: v3.157.0 # renovate: depName=aquaproj/aqua-registry +packages: +- name: tinygo-org/tinygo@v0.29.0 +- name: WebAssembly/binaryen@version_112 +- name: magefile/mage@v1.14.0 diff --git a/brand/Trivy-OSS-Logo-Color-Horizontal-RGB.png b/brand/Trivy-OSS-Logo-Color-Horizontal-RGB.png new file mode 100644 index 000000000000..04ae7cd4e9ad Binary files /dev/null and b/brand/Trivy-OSS-Logo-Color-Horizontal-RGB.png differ diff --git a/brand/Trivy-OSS-Logo-Color-Horizontal-RGB.svg b/brand/Trivy-OSS-Logo-Color-Horizontal-RGB.svg new file mode 100644 index 000000000000..9cdd1b594dd9 --- /dev/null +++ b/brand/Trivy-OSS-Logo-Color-Horizontal-RGB.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/brand/Trivy-OSS-Logo-Color-Stacked-RGB.png b/brand/Trivy-OSS-Logo-Color-Stacked-RGB.png new file mode 100644 index 000000000000..de36d4fcd7d3 Binary files /dev/null and b/brand/Trivy-OSS-Logo-Color-Stacked-RGB.png differ diff --git a/brand/Trivy-OSS-Logo-Color-Stacked-RGB.svg b/brand/Trivy-OSS-Logo-Color-Stacked-RGB.svg new file mode 100644 index 000000000000..fad8b53fbab5 --- /dev/null +++ b/brand/Trivy-OSS-Logo-Color-Stacked-RGB.svg @@ -0,0 +1,3206 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + v82+dTLO1RDGbbAO/HPo4KImk2Xnuvyid29h9OyZ22RhLmhaMpe2sjFul4m5sNVjWbebLNv2ybJn +fX6RM5txvnU2jvfu7yTi2TJ8azXP415t1On1RJaGPYl+/+cR0BtpHtpFnYVeSFOw4xze9Rc7NO1L +3V1G43WjgSwB+48l3++Z3Kt1FPloI1DD3sSK+JNGDe8eR77f05h343zrPg54zq4hfPNEmYL3E+si +Dpjux/Cp0S5wdhkNYFy9dOq9E3hwtIcmCW2bQDs/0ydXy/St1S5pfutyxt1m28y4OuI7u4ZQrsfo +sc0xe210TJ7bLMMHN/Mw6vkeyrz+Mocm8y5rs2tdWSaPrc/wwXkZPLc6Bm+NnuFj+zByaXVW7csu +3ZnrsjZvZQLZaB1FPH9TeEfzNO55n0c+ewcx79YR13Vl9No8zd+bHUT5ZxdVHn4iTEJ7qfR7D10S +/hvDu18TGPdtCul8jeBctymk8zpgPJuJ9HtTmZi0iyoPbRrAN28zOPeLPAf9DqLeh8FLm3HAczbR +5aC99Bm+mUDBXaPNQlvp83sndYLtHkg9G+ewzt5B1LNvvnN+pg+unvGDq4s2D7sSqLc7JCnYDYoM +vHG+dbYMYNvsAtfLzjhweNG7uTOEbnTWrZdxLQYNfMOEL77FoIHfZWBmZWEwf2famTAbF2ZPbZuN ++2M8bawDN9OGxQSyjdkkxnFtwHTyyx1aWYwemzb7dpPxPraBk2HQwMu8LvAaDENWjiuWxeuKucjN +Yrxj2MA1QJjAx7y+sng2WUzfmlaGsI0rU9jGnTFsm2UO2bYub1wt/2FgXHsBA/dhGlzaxjq4qJWJ +uaDJY9k03yHrRpNl3Wgyl7VtB0532MA7WPji9AYNNhvqnC0trSosJgdoe2gXmLCYqJiqmLiqmKqY +sJi4qJio3momKiYrLSYurSqmm0qKiYqJiunniNVMYb2DBTY1uD62Cw1yMtRMVUwJonLy2Pb00LQO +7qayrjgwUWVpYU11YWltSeXQMVFlaW0xVWlt7aGRMa3VfeXIOW05HKiupqiwsLCyrqa6uqy0sqa6 +rLqs6KqsrKysrLaytKiytq6quKawqLq6uKawsqi2sriupq6ouK6otq6usKistKauurSyrKqourSs +rqimrKqorKy6tLS4qLq6sLI4bHFtZW1xaXFRdWVdWXFRcXFpWU1tbVFtbW1VUVlVXV1tYV1hdWld +TW1pWWVddV1tcbDa0urasrra0rLSssq6utLS4rqikhXktOVYqMq60sLSstLq6mrLsXA1tbVVpVWl +tcWlNVWldaW2tWXFRWd1gamqb11xUW1tbXFdVW1VbW1ldVVtWW1tVVVtcW1RXWlNWW1tTVltcU1t +WW1tbW1dSW1tUU1hbW1hbWFdVWVtZWVdaV1xdWFhdXVtTWlpbW1tbW1VWV1RVXFtYU1tbW1dXWVR +aVlZbVlxUU1tbW1hdWFVTW1pZV1hbVVpSU1lbUlNZWVtaWFtbV1taV1tbW1dSW1pTVFtVU1pdU11 +aXVpdU1xbWFxTWVpaXV1TWlpaW1RbU1paWlpaW1laXVVZWVpbW1NZWlpTWVhYWVhaW1pTWldaVlN +XVFVTV1paXVZWVm4srrK6urCutLaotLaupK60rLS0pqyyrKy0sqy0tri0sKqwpqy0tqy0tLK4qK6 +4sLi2rLi0sqqstrSotKi0srqyurqysrKyqKyyuKi6srK4uLKyqq62sqqmsrayrLCysqa0srSusra +yprSyurC4srq6prK4rqaysrayuLamsLK0prCyqLKysrK2rq66rLK0rLK6trK4rriysqq0uri6uLa +quLK2uLCwtqisrrCusLS2rqSwrLKwqrCypqqwtrKqsLCwqrCyqLC4kCFdcU1RYXVlcV1hYU1lcV1 +tbVVdVWV1VXFpdWFNbWlRaWVlTWldZXFZTWVFZF6WAJAGvgkAcs7KmPJPiZMQBWFbHIiAjnFNPSB +A9OLo4amN0cBmoc9YGwi9ljFLPRRQ/OwRwvORBysm+IdqZrhHB80xTdQNxV1hIJKEln0FBEFLhJK +FVuxSuwPb1niIalc+ZUxspgFjgOgrRsnQJZhHIshaUuoF04BfxReLs8k8LKtBpzpx4kHvTBxID7E +c/gOr+E2vIbj8Buew4NgiLiTEBM/QGSAN2By4nBaClBIPMEcjFeAZSEgxQaqBDyAMfGQDBMycdiB +RzUQzAeEoYapAHjQMDZWAoaIpQByaHiJJaVfkySVf7FGPC+XEIKvYKH/AiBF1nxxJa2T0Qem5yIP +UE3EHSE0wTpGeCrqIBWtHII4GjmE8VMRtrMTMQhppiGPF5eDsDcwDXvowDTUgcNTrMNVk4eUszsE +dPi1sY6ZzwBTuH5MpMODcQwNQUIaK/A7siw+IUECjoVkgE/9MHEoGiTOBEPEjbI8XOglhxfxH17E +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 +Lzi8h7eJOcAw9rEhcLzAdm6MhayPpXBoG2sRg+xhgJ1ZCERK52EPJZvfGy00vTpebHp3oND04pjB +KaaRYlN8Y5WTMcfnZiKOVM5EHKydiDRoeHIeAfz8NPLH6aEJXGYBTdQmh0DpKlqR0t/GWsQUjEN8 +BTl5pMDnJCzgUDRBnIbbcBgew124S5a/uLjkcB++w4F4Dc954TKccxdOw294EA4T5xDhCqkG5wW6 +8QDGMr1MMCn5ohABpVxy+LhkAD2ySQD8ZusTuc3EHp+aXh4lMg9zsNAE6xgBSgkk0dKFFbTPSSHi +S8MdMjALfczYROThuhnWwYrpvYFjU2zD9fNDE7lIJlhstvER7eF4i+w31qIVIqJ4ga9lTXEGPUQ8 +iOdwnawM1+EzvMdlhufwGU7DYbhMlnPOOc/yF84555xzzrkLh+EyvIbf8BqOw2P4DM/yFz7DbzgO +5+E+3IhHFUXxOLMIYmMqGjq9sZ2Wfk5sWAIyoeHZ2cQPfEAAZMckCHhHKQDooZFDGjEJYXVseneg +0Pz2IKH5zVEiE0yjReb3hgrN740Ym4g2TGyKb6xuJuJI5WTE8bm5yKNz0/FGp6bjDc1NyAI+NSNl +aIZ3YPELxiQuZCHnB/xbUfEEOUoch+NwGB7DXzgMl+Ez3IbfcBxew2F4DIfhMDzLXfgLf+E1PIbD +cBgOw2P4DKfhOpyHMy09vQjCfQGxhsGEXhM3ZPHQvniUFAPOBIWAW3BQ5RJoR/ipIbaQNc5xQW48 +ADFMUylzAVtMZTl1OXTO25yWVA6d1tZUVZXWltSWVhcXl5qV1hZbF5vUVppalxRbGxeWFFUXG5fU +mhbb2pWVmpoW2laXFVYVFduaWpZUWpVVl1QX1xqW1NpVFpdUlRUWW1rbldbV1hYTlhUeh6osrS2m +qqwsJrOnnAdob3dzXl1oaWprV1ZrXWpoVVJoV1tpUldYbVhSWVxUbVJYVFtqaGpVaUxzaFhWU1Qy +2h7aBT80LZsbGt0GJiyrDV5sa3ZqcHVyA6YEVVVpXVRUWFVqWVxqUlVaVF1SWVlXWFJobWhrUm1V +aVVrbVxVURc2yHt5XmZUVRu8opgeMCUw0zKjMoticsB0YYPMoUPdQCwnj21PD02roc5B20O7sFb3 +9fQ0bqDC/1D6JQgB+bNkbNZPIRZl/NZmGT83GkGIyTmsSWvvEOW1i0U5wZUA5VULFDnYY+7ciDQJ +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 +D5qxKhdwFiKOUEo8iBUaqHXX7xn8M4huthBo/CT+txWLjsVPlVYOmUy4hiySvqOdAK1QAzaGSlAK +0LK+shG1gxOGaO04Wkf7aFfE+N4H5w8PMjSJ8DCqGrimxGHh8refOczAAXlt9w6i9D8WpmftKIz3 +gaseXEWXnLo2a7NJEH3KY7LaIjS3oqFhbKoKdhuiWxkjHMo9hkaPRfRkalVaFphCb3hS9qPTc1UP +PoEIHXFBsNILEC3NtV+W+BRFIWOwIssM1oCgD50M0IrSXVbxR3qqHKN5irsUPBCESAu2M+ZYsJCL +nYZZX382grKeTR8VPwe1Cap5sZ4EV0N/vfKLW2az/319BQH1X9AcrfmkaJkjihiW7C6C0OYAioi6 +pYSkrvjdV6Bnk0csnhOD5B5KYweTCOCAVyZ9fY4FIsOu+k+YToyJhYXy9YpHSfUyIfpKGH2w8Nzw +t4Wg0+so867Ypye6kHDBGi56IbiMqGh1Dto8icYm8XIFLwFLL0Jv2uOcjiyS5O6NHKP1Qf2pMIL4 +imxfS1qnHGjhyvN2CUYZ6b+THDhO/srSBcrDop793RBOCSklm3e2wwA7ptdSwTd9tGHmS46l48ec +feBmoY/Wg3k2szqUVXjDhyo2pWQ/4saMMLZLCcM8CSiniWSwfvcvPH7OtolT6omFJJDxIdaEY/CM +kReskVFioGCyygNPhhw/H3m0AEA5oHaGt+ds6ODUU4xe+t3I01x/mYa6fLW1oiOX3XCbUGy/2WfM +a8t4ID0EPJ0aBS5uFxaqZnvXME9YQr0GRNJ51BltC8zQpXetBrQMOXTd6A4QjneTFn4CySCSmDgB +7AkAdApfMmFkYzkwNTUwLWVjZTAtNDEzNy05MWY0LTE3YzQxNWZlYjZlZWQ1OWI4NTBkLTU2MGUt +NDM2OC1iYTc1LWI4NjJlMGZiZDc0NCA2MzguMjUyMzcyM2RlMGI1MC1hZDYxLTQ3NWYtYjQxNi00 +OTc0MzBiYTFmODA5MjQ5YTM5NS00MGJiLTRiNWEtOTFkMy1iYzMzY2E0OWEwZTIzLjQ2MTI1NTAx +My4gQQXLISI8r/YSnv3mdJAGUK1mGlirkeZ5oLkj57eZqjkitM2pBN9OEOgGpA054K/AulA5gxCC +WMNY55qfmjGYhgfc2g3aBbImasdKejSmHzz45bdz9JjgAO58D+w6GTbu1AaONVeA5zenKTyYNaPx +30QE6gYWpgHyAwunYdB/c88RLoEI9OYbWEuP1vSDZxDRBTZcjABkO1IxODNlNDNmODQtNGZjZC00 +NDUyLThlM2YtOGRhMTdlY2I2MjM4MmVkYjg2NWQtODUzYy00OThhLWFmMzItYTY3NmUxZTM5ZmU2 +bWwxMF9TVkdGaWx0ZXINLyA6DS9YTUxOb2RlIDoNKGZ4bWxub2RlLW5vZGVuYW12YWx1MSAvSW50 +dHlwL0FycmF5ZVR1cmJ1bGVuYztjaGlsZHJlbi9yZXN1bHQodHVyYjJhdHRyaWJ1dGU7ICxzdGl0 +Y2hUaWxlcyhub1NiYXNlRnJlcXVlbmN5MC4wNW51bU9jdGF2MnRmZUNvbXBvc2l0b3BlcmF0b3Io +aW5pblNvdXJjZUdyYXBoaWN4MCV4aDEweXd3aWQpQUlfX2lkb2JqZWN0L0RlZiA7NGZyYWN0YWxO +b2lzNEdhdXNzaWFuQmx1MWIyZERldmlmZU9mZnNlb2RkZFNwZWN1bGFyTGlnaHRpbmdQb2ludEwt +NTAwLXotMnpzcGVjT3VzdXJmYWNldHlsbDp3aEV4cG9uZW50KDFDb25zdGFsaXRQYWlhcml0aG1l +dGtrNGszazMxMDEyMTJNZXJnTm9kLTIxNHk0QmV2ZWxTaGFkb3dNb3JwaG9sb2d5YWRpbGFyYWRp +dTEuYmJuYi1kbmIybjVEaXNwbGFjZW1lbnRNYXAoYm5zM3hDaGFubmVsU2VsZWNSeUFDb2xvck1h +dHJpNDQxbWFuaW1hY2N1bXUobm9uY2FsY00obGluZWFkNWZyb210bzV0b3Jlc3RhcmFsd2F5Zmls +bGZyZWV6ZU5kZGl0cmViZWcwczU1NG5jYzhjY2NjY2NjOGNjYzFjY2NuYigtNTQxQ29vbEJEXzY2 +ZXJFcm9kNjY0Xyg3cmVwZWF0RChpbmRlZmluc3BsaTFyZW1vdlIxIDE7MjAgMTU7MjAwIDIwMDsg +MTUgMjA7MSAxIGNuUGl4ZWxQbGF5NTAgNTsyMCAyMDtEaWZmdXNlNXllbGxvdztncmVlbjtibHVl +O2luZGlnbzt2aW9sZXQ7cmVkO29yYW5EaWF6aW11ZWxldjZkMTFsMXJlZDUwMTAxMDEyMm5yZWQ2 +ODgtMTM0MjAuMC4xNW50YTh4NTRkZG9uRmxvb2Zsb29kYmxhY2s7IG9wYWNpdHk6c0NuMzVuMW4w +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 +KIriQg1vjJaYSEY4RnS/m7Ng3iII1cY0EKRrfPvZNsSTaSmllFKmpJdFVYtKhA53wPG+Gt4HxgcD +CR5c883SaZw5y9mT5VfmmGsVfbqwzLNOem0V6+3KYtkv7XRNaScSlnkaTddkWfkx34z92pazulO2 +pdmp7GjnjJNWU8jldPkZSxqvV1Hep5ed2PGUfa3MkVbHLWe2hT4tNIUkvbJOoVxZpyGnnv1jnXVe +eqmVGM/s8kZqba2SejwaE3vXaYtO6VeJ71f5q+mqLnrn+63S/ZL3uiXV6/6kPe3EUya2g3lZj/2j +evxZYflTqKomq7o256b2XoktW/Gd/U5f0vfL/nUrKynaKr77Y49vZ/UR3eJPpPg//bPb9+rKjal5 +VvG7vxtXibviKv7nmeeMjy+O7ZOUZzWFxK6iexVxnq7Jijf2lXhOV+yrSnslnlX186eLVHsKiU39 +PQ6vy6h0eqySZrd1yq9IFOdZ8e3pyo+riO3Tl5Ri+ZPim/uraH+6LoyraC/Hw1PHTbFX0fO0cIWr +C1cVjjbaiON1eSPGUya2NV2TFW3MMedoJyvmmKtcxWzlzVWct4p2IsVsMTKw26zPlnFYRtebabVT +3uxO+yWtJq6+q3vhn0bT1Thj6Zb+t/TqsfSc35/OebNtLP3xvKhXcc7p8n8iTVmrOHuSOorHl3VZ +Fsm6rL6uD/Whpuu6PtR1fajJ+lAY94f6UFPpATQaYd5YpjhUdfZY5VmmlYvsrbDpNstY5nhUKc5w +1zrCKhddrMpAmVazgrIqgzLatLEBAAAAAJgXW9sz5o8KorWTdU3WdE3WZV3WZV3Vb73wvVfmLPPF +12TN+bJ9utrd3W2ersmq1FYs7bzf8k7XnS/vS4zxdE1WT5bTOV0t6WTVavOUFc+nVHa/P+mvybLV +V9Pl+PGUX00WxXlKd1yvt/Sruj1dGVdZq8m6tLaVdFaap6QkSavRhV3mimKLcTVZ18pL8knlxaPp +sibrPdJ7p7Uuf8r36qKzrVzhxtn7/+Ls3tht47f0W3a01s6fLTP4iZBVfESZgy8DQRha7MLSJXYu +kw+FwWw+1If6UB/qQ1H6oSYQ5kN9qBAM5/D4n+x52maNcNpF8fhQIyBmESMEIAClTcyj2VlkX1Qt +rq7NuHKR0woUmqHq2tDEoUwnINszlOGMXVFX1BurRqFLK1xzVnUdPahOO/lQFB4f6kMJQAAAAAAA +ACAAAXz7B3fsGRVUCD5UBOzjfKgP9flQU8niEX6ZA66TbSAM1DIjIGZR2UwhjM21P7f0/PN+9Wqt +7Env/adu7Y1P8z/1+1TiSe9tfOvjnl6lraJXi9/dXnn72r49r73xvk9/S2m9T2W1fael0l7P+T3b +eiOul1J3i/3Sz7Wf/pTVp/Xc9fbs2U17Zp8X12qxrBdbW78pro+dXvvVJbX3/tvZFF+Jn9L5Fkv3 +LDEJV6NrevVyuk+fQmI/kDZtM7m2GXu65kRyWj/3e71d68T9M/uc2X5H69UzndMznT/tvXNi/C6/ +Le739qb/j7O8/taVLenBLSaEubMdZ/fOE8lj2hszbXurOD/f3N3XZOHpwlW08ubK5toucTWRMMZg +4k8gzCTyiB7bejX5ZFuE0BN+l0/uXD4BPtQEQfq60UbnYtm+dlrpDRFFmejrQplG2F1U4mRNV74m +q3o1XS1vNVn2Voozlba6SFXa6NW9S+zSg07kdV9mToqh1944r8XUr1dxehX93p4sYvt0pxoe0SfM +I2aXtFJ3POutc9ZKa6WUzumKs06k7v/u/8a0ipf2/0Sad/ac7rO7aXfP6T7b+1ax53Sftq+ddc5a +MddOD1qlV28/0o+2shRj22m1VcRVpNm6nVWk1XRFryKttu2slFZbMVW6rZJW8VZ7I6VevYpexWrr +dMVaRVtVsU4WkVp7o432Tle0VbSVrS6WbSet9d0vrbezbTovxo/zzJM2rm3z/bdeJRVtmbbJQizj +HueMmEbrfU1WtNNVZa9I+atqurJHKy8pWo/5p0xsK/5XNv6M/5MV/6P9Kv7Hn9Kr+PPzV9NVTST7 +L/owK4aaKPusGbHHtpHWkTCUjb6sulOKHVtbxf5QbK+qLbZb7hXLjtknjocJg4k9jZpiQph4cf0q +PskjXlvF/yr6FIKJHjtXsX0CYZxeG9u7qtqreKvos3J6bc5Z5tyxPX/0OZHijT6nTOzRZOW+NE9X +vFX8+DPW6D2vyYruEX8Vc84df8aLM+6XVTVd+CdSdK/wTyGx26fSw5c56D5cF4YlTPh1HgSNSPs4 +IZymbZ78oaanLyu2+f83RFqJk20gjNNM7w/1oXC907OVPV1+G0v36cqSVqRYM7Yubaw/YR6Y9qUz +2s+R67jLJPrEfagP9aE+1If6UB/qQ32oD/WhPtSH+lAf6kN9qA/1oT7U9aE+1ET6UA== + + + dhJ5xI49cUyjyZoz93uWXaVPV09WfDpb1o+1Us/SRptb0mj/1tovf7LoR1yvjbOarsn6Wfms6leT +tsReXfeW1yt8q0iryYrzq0rrlPerKqftlncKVZfVY51VtD6F+nrua2XP6fKMbRVpvlN6vZJWsqel +Ocv80xWtrWgVcf5aRVtnldTWmek1WT9rlfTnYyqp/8yS+hTy2bllO56uOl3Rzr/SvaqwrFX0l/9V +zFWW7dUUqj+/sl7Nr65Xcn6m3bK/+mq64sVuZa7itJTK+tVT2VXEs4rUrVcTacI84ve1LmdujHG+ +Pf9OpSzUvs4Btoki0WUybR2IFIF650KJOA3FBZTW3E4npu3YvXtSt7azpXbavtctntTeiadsmqv9 +txZM9GvhxIl/+qRv2+KaHX+m9fHEt+mlXZ22nbnO+njethdbMDFnCydWn7Lz11rtt52PacY/L+2b +28p2i2nTvz2r7JgvvU5pV3cLJtLpFk6c1WmVtTa2mHb1Wf/Wvu4+s60X5zlxzh3xP/2nNec5L8a5 +c5724qeP8b//nHNiOnHG9s7+t/Nx35zfK223NnvGF3/FTtteW3Oe87N7tmBiWzgRV5/1LW7q8+I5 +G/ed2b77Y7futFJMJ/VafTq27d1en06f1H96U4ztW+r2c8bY3qZzugUTK7Vw4rfLvnninxVj/N9t +cVv889baleLs+XOtTWult9stmNif++nMFHf/zLjd/qyWUgsnWtq16fRM2/5tfzrvbDwrtVk2vbfe +ebPnjLFbMHG6/bdgol+3aExudQsmZmvnW0vvN/XZ+VqKvf6tVPatlXrPe/G704n/fn+mV96fb2+t +Fkyk3Tg3lR3v09r251tcZ57fjrsx7Ynxz87Z0nf77d72qfU67e23f5taLOm1YKLjOr9f4u5qb85y +0vbPnqe19FoLJuLsFk5s6nfi7qa1TveeOdu+bj3jiuvsrvSvu7ut1rPftxdjl3O+hRP/Z84S/33H +nzH26TRXCyba6xX3W9nx2re21r8430pttbRiOntaMPFSt3DipW/RmGR5vRu33/+2OMuO2R/f7p72 +7fTvbtnZwolOr//tf+9uOmtbnDH995yv7GinBRPxWzjxzq/z3TPO9ud3xbi76WfcTudnv9deLPu7 +ftv8XakFE/O0cOLMbtGYNM0X/+e/tmVXTCumj+9neu289+b7j//O6xlbx0IuC8EsanUsO77t7Lb9 +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 +vizkOqEuE2IUW+aZxAAQsjD0bFtp07mMyJbUJ1kIQQPGjNO2sNSNYnvnkoVCOO1BpnVgkRBNIwth +m3CUXVTjOLCQUhw9O45e6qxtyyzPtmXWpOC6AGhfR/HX3p+YcJRdOOOqEY6yKxxll799ztd12+bi +Sj8i7eNsmYPsihFCbcNKBWIIp1l83dZZYed5Pi8CZhYxdy4elrywhBFz59Izhl+mIJJKmiizQDDx +uoyYhWClEA67olcKM08Eo0j7QmLMnUtIDEvdSKjjRrINFLMQ2zpQi9EdFEkizJugtmFdSGqe6H6o +zgMfP/2qgfHLPMyLuXPpL3rEYp612lDbNBAWaiJNDCFkGzH7MqJ759KZeNoHIzDLdELY9mUhp/mH +eSCHfWFbdNxvFCZZGGIk+pyLjDAL+bAwhNPCMPMEQCNpoddl3PgnptRGiECzbfo/a5aTYggQwlF2 +kbow7EAirtRhspC2+YMRGOo2D+E0EaeBMs+HOXAQAX6YAwcRH4DUhWAp9AE5hgm3kSBgIZbJNo+E +gREOwDDzZBsI0z7MgWMhJsLCjJQ/1IcSKUw+1IfqCEfZJcqyDYNl38XrPiuAg50v2w== + + + Shoal4zH7R/qQ1mIPqwDhd+lsc8wLHQbdENVYyFQ9SNLq0kvrTDsE5vfkw/FdVqogV2GRrexZRoR +sfKhsojTOHg4nxDULkzGe8Z+//Hn3H0vru50Wvr3Z9fGTuv//WnBxPzutH3epvk/Z5xnfmpzvf3v +fumdGU9MPf98W7NTi6230582gjIDk+j0LZj49C2Y2D1ddrQ2f/aK39quf+vFbWe7bc+Xer2fM8WN +4UTan++0lebP17s67ew2V3otzZ5vvn7v9Wy929576dvG2C2uXbPtWXH9WbHPOjF26vQ6vfTWp+/v +Fo3Jf3vN1Pvmx30nnX5vV/wT34srdfd7c73Xgon3WjjxXptrtnP+12rtlB0xtj0f21qr2+t4Zm9c +72N7cbVgoleLxsSCifbfZVc6H/c//Zrnz2qtxfSppd237VswsUMyokcHLyoIZUw/iWAcIox4MJg4 +W/b/ndRx457X32K/E8t+fKvfeem81nrPSy2caPOs+N7auPbN8zadtzqV7i+70jnbUkx9ZotrbWzt +/VqnbZ94us244p85T2ybzp90Tnrn9aZv52O3cKK1Tn9i774Z5+9bndLaTi+emXqmt86m1WduW3G+ +NRVeRcWs8ColAj5BgESgQV6Ys0TAJyY0YmESIBFYmEQsTAKk0BJmOgyayUSHQec7DMIQUMF/mcnE +BGShHw/Mfzww35ExyJoihGEipCZCipCJW1gaHqL0oXgofShCIOZ7VzosDd+RRziZpeEzODglEEvD +h3jkOSIORIal4S0SMN8kfBIwLz3oSsC8e5AGRJNJoPFJTR7EhwZEIYEGRKMB4WQJD+Kjk96HwmcQ +NPgJyKJVOMqArbSx12c40CsBDRCxABGhUOCDfgJMboGD4ELTHgUucCYSyS6GjY1u8yQECUq2gYIG +KYAAivRJgoAAl4gHpcAgJQL8csVZgFloGJSCLYDZ2HRFtEnw+OgkAqgHkWqBEQqQEVEAgwgkAijw +RlEqKhjLCkmMRU2bDisDpADNgczHndjYWA1hZYAmQ2JjAzJgIW80JZFC5oN1bBwRDInvK11woqcm +EGhcPKuzqGgwoOwAi05jmFwChW3AAAQgk7+4WD41iTIkjrOoEv/h8fHAvDXdqyLAwgRBHBDEkVZJ +5IF5aVXS6rA0vCUnEaPvw/Hw8PDgZIn5fTg9iRh9n+/TIEGV4CNqqhJ8VCX4qErwUYMEBgx4cLKE +RgLmVpXgI0qCj9r76PCwKElHAo1bGAk0bjVoGLX3wfE+OBJdB6cywUBQRkDKCEgZASkjYNfB4cHh +weHRYMDlwSkjoMMI+JAxyFo+g6it0GIQtUVNFgZRWxMIBlE3TNPhg2VzOnywjJTBkfB4eDw8Hgyd +XEDR1gfLGqOBNXywrCs8SkWlOCYkBSKoqcKrmBwKRFwVPkE1XI6JidHACkRUFIiYKA2wDfLC/Ejw +URMgfYO8MKcSAZ9gcFhgUSBioqHCJwHSAA+DiECFT1AKEKiM5IXZYeDQmSKggueACjQeZiYTFAMj +YNYdBg6dBs1kgkJNVocKFShoJhOUApNGwpGgdMAOI0Gggtd4qCwE0ho1Rcj0fagJlKCgJlCCIeHj +qvAJCoUGdHlwalpo4IFJmAglIh4YCmoCJcLvLqDwvs8Hh5o4D0eCQk2kDA7FLWSOiJMlqEkDg8BE +KEHhdBYQHFFD6UNNFBMkQeGECCwNj9HAKEA8ouGIKNRU4QizHgCCOBKcMAHzAhgbjKFCM5mgbGYo +gsA5IBzCOSBcIZOJVEoeCdgGe6AmzyTQOEyQhdKJNEXDAuYexEfGg8FhgUeDhlFrGGRGRslQk8ZD +ZVEYKjgWGx0T8yOBpvqA0EBMDYhGJhOpMExfJ4FmUpMGxIFAPoD5jiHjwCRi1A8NEnCqABsGmAfv +uonjg2X9oJDgkTRkSFAKUBqJCkxJwXd6ouEhLBpKn04eMQCyaBWKqgS6wEPpQ9EUwwKKzuDgZImR +DKf6kKBkwA5DeSqTiVQw71kuD95ZQGgkJCjUVMCCUpkeWmkN1iDqDAJOlphsiCaeBESEglbBaGAU +TU1faSNBKUCaXBJcHhzjYEmqSKUmDA8NSAMFy5FKICTRAcshZ9iiMpLZqBQMkEgkOMCv4ZISASJ3 +kjV0LIDgNDVRcJMAKvgAeZLGgoK96LzptywofR8OdOMgIC00kJgamYgCqMDAIPJgLjC44KGiDxr6 +EHAGz8Vo085gkwAe+KDUhGDzOBQUbIGQF0Cp5ILSp0m4PLgDyKJVFCwYEgR8X8nDUukGDltUcsQn +UAoMVmTDVJCgHPyqDEvDJ3yfhXxNaUAjAyGxNDIQks2IBEUiGYhAfGQC1UL4FNFC5QkaRJkOEBkM +M6HBQuY5GRCNisPCRMQthgbeAKthMkxq0tDI5PessFAsRgPjLKoExRPg4EkwWGh4aKU1IChIC3ik +TL8EBI1IWCxIvk+1KAyST+cxRASIaIDh3ZS4cEozxjlG0uJY1UFdQuLy4BlICIgHnYBgTEYUUBDU +AxkRikkCu5CxYKBkhXR0gwQNLkeH90HgIcIPo9PYaGrCaEQ8OXd4GIyUMFou0JBNYhFaIPpsaFzd +dSCgGHA154EQCdD0x5AhQaGmrbNIUKpOaI46+SWkDA5F830SAgyifhCQ8WCQYHZ0KLRewoV/XRwT +c+NobBDYSKgqfMJAVbFQShsGUX/cSdYgoRaUPumhld5waDRd4INSE4GIUMGDPgtNUWDKRNjehabB +AQyi1rDQMKjKxgWG8NBKU5Mm7CIoGQ4UE4mG/0j30+Ahj3RwXvoaRDCIOiQAJ1AGPAamJspIxub7 +JBIBlQeQj4SJx1xATQEoIg9eAgWDXkBAeiIBgYYtYsQiAaSgqWnyEJIwyDoXIBaIA60hUAgwuRig +JoaOysDEhWTS0ZAviQaQju4goN8nUrnINhSsFnbHRcC5PGHJwdPZaIqAmmgF5Vx3Wni+TsqKAw0V +CQ8YG0aDHKDfR00gEI0Fzkakq+kEht4GIxQJ1PQwc+dqiQObkQQaDc9IgzqpPuaH0wB6l0HUno7K +CNUGLjBITQcUCnA642QYFgwEtG7T1FQgc0GpTNokZAio2DSCD3oPlBooKFiNAAIo1KQAwcTEgfIF +pe9juA3gJEFA94GgoxTUNBVcwGkbOBvadFAMIB8WJmBslZo2AkA6yiBqimVhoQikg4dW+oLAK/g+ +wADUpAFdHly0hQEmBzc0ZFiMQQIPCeo0DKg4CRwHFDhNTQsuF08GpWAzBjY2feGgcResaJMAWtmH +miweetMeH9SCTiw8iFQBNBSAQgCBFSFBTQ2WAhkfHQogAomAAxXQpkCRccCCtPfQoC6weEQ+Cj4Q +zqPRUQvwQcog4UhQSviakpoGPFgKPhGrYeGcQdSTSYEB04PiYNDKPhMPrXQF3hJ8HWCAjVMlKCAU +ChPPwWBFjmQyCRINn2lQIAE3FniuC7jwkNEVjM1+GwCiUWm4NJfJZWggb4ChlGnZTDh+wSH/4lFI +dJUFJ90XYIehJvphqbTnYEnqRlkIGkRtPVRWAQdLUjUaBIOkQzAGCQSGhUFSoApRJBsyRJEUmJdK +UiFDFIk1IkGRhB3RQLkcJaGmikVh2GAgAiHxVLiSXMgQRSIJ//sURjwqzd3Lg4csF8tm42NzINMH +EnyaDnqg20gDAiiwBJgrBlATgQsP4gKySHyflX0omnThke/Afd6ACwUbdw0ICISDGQ0ZAzgDOotK +BVdi0BMMODiukPCBcA8LUMGDVCNgw7EBgnGRJOCMrO+jvfGCQzc1VUAdExgOlqQKGF1T1g5lkGTM +g0FSXY6SVC5HSRZ4HIrk8rCAkCyYG0cjM2Bh0iDpy1HfzSxIEIg+EBJq6iQUoEgOMBCBkHQd0SCB +sCgMkofLUZLwZhYk3ceFg+RaFAZJWxQGCaVDGSQHQh8ICcbmISQJGiqV5B36gaKwgKJJBKoEpfLQ +Sr9FV5KMhwWEhJo8ESWKRI4WOEi8TIJKctFQqSQSMkSRKEwOUCSXzUNIQAW4SkIC//vAK8TXlA4h +FAKvsW1G+sJMzYO3wF84C+pfwI+CxUXCUrioWHRgiFOThUeDjAGVBgbM9Eg6FRkDlwVFU8J8WUbh +wUpQ8IIanBCoBHMohJGkRKFSiGZGABAAAAMTADCALB6SiYRyTc4yRNoDFAAEaVgyflBEMBUGQ5FI +JEZSFEdhEAVRyhhjEEIIoZoaMgBxawa8CYEjvoVnLJRKt/XDdNXk8wQtDUr6XX1ht8axYdrook9R +REoXSneDVelXMV0jlrGgdK343uA/FfYxUtGVV4BZf5VX0pkuzcABXXglJAhR3o+m36p0Rdev2xmN +K+OVNwMK5LWESYNmPecGx/DNGjeV0Wd0cT+AcSw7mvrzAFIcQAzV5HTe+QvR9B4DBdcXs+HSSUZZ +Q/b7TLc5y4R3xFTG4asEaIVTS9rVLEhRGVOy4kRey5WpMbhOKNIvGEZdtKcxNZXsbhZF0jiJc1rY +VLFTz7RhQqGvE+ddsSfcleqVTRuYz47txSPfrjAdwzx45PSKVqrQHLzJhDxQarL1UcbJXJk/bOuP +HPmALcj0VjPWi6QscJtFW3eWzWhnisSOEv2rJU09GzKvV0l9USv+XuShEarE3WWanSQ7EsAkCewi +estpCFoWsp9g/1jgngugI4qJQsjCqpRCGqiEBx2Rsmll3vV9XHZxYbAVHOATDafAR4SaKSZ0jxhY +3vB1FFuGcUPnDcjo4PVAHYeGcorbnD8JJLPfLnvULsjogTyPQDoZMlOjhItfJWXuXhPduMtZrRnl +ERT6UA3AZiNbnjcCzjMC2eHBtYsoCB4IoZRTE8HofnU4PIbn8EDNI/waKTGi7zSLFoq8vKhyEBD2 +EGtvXry99WCK/Xzk7YWyc5pTFkDNzpRirn7Che87eTClELl5yZYyvPM2sQw4zIErE62m2vjXUaxk +5p5uHpfhVr0Kv+C/FgC2M0ycls3UbbVNgcjGAldb/Zg92QZjgD/OQhBlvGweZajz0qS57grRks+q +OcwWL+ueobTsQQL6Vd6RiNI90Suv10oR7bNqnWZOTGVx+ZpBEu0Rnmv45nzRaPIMv6bf+aw92pAn +U/bPJSTzA7wTBjZTZmMHNhAZpSG82H1Smnr+dYThX4wOp8MWTgSMMzoERi3z2kqbeVPAR9AKChYN +4dr0LCcR6xu1uqKPvV10iZPudsqBibm/paQxhcW6IE+3wrRtSG1vvFjT3SlOYe4iFUB36dBAA9av +vbtSug4bPPeeVzJDijHeqbzh+G6HvKtL8wB2E/DMu8WiM3/PArlo3s1CbUUUt4TX3G4ylkNjWk4k +9ZTj7M1g9scKWiImvppp4u4njFKZGb71dLCNPFJIlthuWnnZV+QsFBGV12dn2akkF7EHb1Xg5pQr +dzDkAGM1Zyfv4hD521F3Jdr+ELQzJoTUHnO49kc4EijWQj5LJs/cG5zawCwat8vLJw== + + + 1QM4r0syvflYi9vYWfHtm/74GuySs3H9egDrAo1XYF1czcEGuwMlsYXd++lgH7clnXw9sOA5NIH0 +F7IJdjtVkadaqsW6dUOx7touPj0dfJ1gmQcTies/MmNpv3OKopvi1yNcTjgubExbIDHA8LlgWbOb +zsSCGFkUVaHraCRyxUw7pmz3x3T/KCXLLFQAtq/ggQ+sr+8dfe6a34UNYDmmfVz234+2FTq0ZnAO +GXErJKf4PDU8A06ndQOPp5pWYN9ZE/AHlkmLLdG/JsdNL7BDU173UiHYUBm8MXUZvKbqiRHEuayQ +/KhFDFrtJUXpPNl4XTAyd9iS6U/Uq67XZUW19GzsGiwhL1ch6k0Rroen0xHfpO3v31b4u+vV3Db2 +Veu76mV1+IBlXHiJbRMfkFKBkh34FcMVPoX2IWLVteSRhz1T6NlFxKuF9+cPpzagjbhIqWK6Ue8Y +Pj6Y8LAOREy4MCjse3yDT3YhxUIdNJ3NzpuMryGw2vMnEgmMlJbjG8oZSPzh6YuIKWM9dKDioqoQ +R7UbsPVaS56I8Kc7TB5fVJiFUAjuhkw8SVHHeMVJc52XlLd0fCQPRvBmdfVIPxbjdyXwBhmE8yVU +TLZnLsW9s5M2u9Rem9gPtqcGXY4UcjBFPRP+ZMEnjjJs27ipeQ4KCHcLLqOWjBQff426FddUO5L2 +X1/SCt0NIrheNRnmCmF77D7uh/wKZC/qqRiBA+DDqzNfyKK3go/t2Z5sO1xOz2jp5CFxlvqrkZmY +sx6dTi49BfRwXiukQzXO+N+LGN+lu+irVtPaW5FKFB7pcKrZz7r+hinMDwd/9x/5vqPzaty/ANi6 +KRGcjFeQkLjvVyiC9BcJe7aMLjmB4+Z1Ei+jTA64NB8CownJBoQxZFUYK6OlcStDaABuphEOcu/U ++wVuASmlfcEKzAgC0zHKjUhKrwjnGwyphSFkCfTaVZdxjLTwOeG+9bLyu9q1ZTcXpSL1uajo9S8r +rdWgRbSEm6uiNBOycXWRipeHzWtjFLeGlENxqtfRMh9eUN/iuxBiU/IrgWf502j7zZoBj9bYXXyO +POJu2/4vfHni533O+aP2biYI6IH6NUms88MCpwrl5HVXMuLNbOzMmvRqYIv8emGpAOjbqow+Qi1p +pOD1Q+I/gqhMNNOXOc30Bs/T7HBaaMo2L+JarunJmtMgawOqYd3HFZJUf0XTp7ogvnYQFVU5sJIt +G6WhOGZRaKh4S1n30uMkvw50yWF28LfmRUNxEKCbB+x8P0Bez3F9MIum7CeBhxfK4YU7LPJPgGfP +VHmWpe0TjCdoyGey61DdckzzMNlx1P/sW6VzcPrKfH7+iAZ7fBx5YfPAC0xv3v6CyN4eA0spLRG3 +Ibn3vb7VuzxeK41V6qCu5dzEwP2teETjrSUXe+XnqgVdT6aD99OWAa/ksP4hs3OM9AQgflJf88fK +CF8CsgREnQ9xmPnBNEkkWCDmXseGNEBjaUMYSRNxQ91PavQVNpGFma6LzLL2rFipXNBV3MTX+rYv +I4YNOfuCsWhcwmG0EsCXBn0Xd8VMIO0zusLxlNIAyMvpVHMZGHRZHiLUVu1WgiBqjpMhJjcSaCYT +S+mH8yF+NBjnUpZHphVZ1hNlbsVmleYW/JBNaiL9qsJlFJHejeVgqbtX7xREN0QEKM3j9djUXld5 +X82ZtH6ejO2wfQ3CnakGMk+NquHvSgAqrCQsVBakD9QZMBA2A2ZdGc9lZMOFK7ahjM2Fd5nP+aZh +vQF521Hov3V8xIIjBlm8YMytj/QotPyxLFBnBxS6Wj8/KHKRdXyGjupMmtX4Gq1jM+dH1qxYI4K1 +kWqMHrWQR/G5IhoQuuMfeKRdpfFV7ngFs8SUOXwulE5h7W5QI3Q+EnM5RH9ZSVw6aCQPs5NWGPVO +JLK/jsUi6Feps7Tr2ot7f+Jx3lr12otuA0nU48alTHNbcNIPLam3rn6zZQSpcS6OPN4v9oMMoGCD +9cuXIYsrPgHrElL0MiFqWZf+UClTDODUmuXo1kH4Fw+NdG8SZvDyXQ8QLC2kYU2oEXpKuX+1Hwow +XYTb2TPH4+FhuMfWPq09zM/lF3LehwbhR3DXhHn6nzyYlIBEn1O7lNLqy7WXmL6nIt5F7X1U7rHF +xw/hXYTEROV9JZx9dutoDsazLZW0xQksg+oYNspXjHEVr5pPfD/i9Niwiybacb+LirmsgkeKnxyD +Z+cfPzxJLPM6iQwyBDazKSxMCyjAJMCAbmsl4MD9AIzMw1wURGCOlKTZkoekqtXsJOeMtK8Brhx8 +QhTw8ReXeT4YxoMIWMt9VvKzBcBbiYp7gH2+w3EwxAuYRJWGz7h2BbDmR/Sto9FMopQf90xAmO5L +YYsp9QliPwkbzMZKAm14CyBDUeQFHYzpyGoHAWiQBeb9RDSsRBXEuvHs7MDwiEl1vKY+jO83tnGM +h75SM6iSFTfNPqGkbd6cNuDDcW9+er8EsHEM3YFF6Srrvcg47gLq/fshpwMYuC2Mt4HwlZoszr07 +TpbEpJ6XjcmNHWj9zCSnsirdBhmt7/pOepelYwgPAuK6vY+TfvUf6UsogmqaPWPoH4u/GqSwNP5u +AZsf/WRBwKfJrGv8GjGnmEG7Bw8/rek64xocART3FUpNUEDaR0Cv5yw1LgpAUFLB0wOHMWemSARN +4ZNM+TQlEnrE0gHvANzYDl4Yxgk42b04knz10rP4hfox1K3KzPYG6OGam6/wncoiSecW8RvpyYwp +kiKzaMuetiYC9t++JAyo8HWW9s0+YUIZ+sFzoGb8IickLBjCBFfZrw5xs6u3WbHeVEs2MM2AAeH2 +7XcdVpjeaXjBtXgmmR+zdd2IcxqchMlhzOAnFpOEm3LoZ/MrMzzjfm1iWw4P1m2DmG9dB2vrmgw4 +1LByMkgfXFr1Sb0WAx9NmCrmhHYlCI/KZN7bR4DsV7IffEEl5bDwmuUgeZ3iC4m4caE3owkKHCAI +f9ONa8ZmylzWA72LU+SPP+9/po651KgS0Pxf6YTuWbmV9rzks4IA0Y9Es2egNOzM5zCiKcvtlU5R +jYEr/qhQCpbOjHtqf8sMKTdvYhGqx6+YbPitrnX8zDb75dc4it3l0ErVfIS8bPE7dcS74m4jv6y8 +zqSBMsz2bgBlYAfzcK0DMDKL+xPuMYrdbuuXhKorpFodhKQhzkNPJX7Dv0igWOcxg98wHRp947LJ +dYhT/fBSZduN9II5Iqh0DLL9l2A/DcUMHPsJCKLDIKYRdJqbgNbo0oJUcvtAYHL8tBl8faMFAEAD +3wjGGRxgH5Y2YpFAG8l/yECmhfZj9cGEpir2PfFet1n18rU1RsDrE00egYTsJBJzTo3RuNOmO7fd +rFNAh4eVVD5NDYdoxKRTTaAsUpCEP3k4om7OEnGKJf5v6hxQN5mSXqDqiJYBrPwbKlUkC0vWsgEy +UHRb9JswpBDpVmsDb7nkDoAmT4apS5nfHH50pwakCCnYrZwDZPKT/p6RrXAPXzUGBDreEHFlNxQa +x6qBl9wn6QwVGeoJrVr6b8kxBXDn9woxkenOYUTwiqVXfe/ToVkkU8Xb/2QueN5+23cjYXWg9efr +kHEcFml/7BFSSW+JjjPxx27KKchytIH9r9IpK6nxu3OX5FJU64aRIZJStIodNjdwUo0UFset2jvw +DdnwS0c7VVARi3T3Tb+066jGhHN2GUEh2xN3oaHVv3on5ky29Ctcr9FczBPUUwHG9Bp7eHgZQ9Jc +2CiOS+IbkTwKAKI1JBKq9RvDEen9vAHn2QqgrgbD8lIGdynfB3KDJjAiFTZvodfIu/UkcAEqtjnc +wNhbWRPwYmFFIST4fZXmWNQcLmyobHWO8vO1Pt7zdevQFDH2jr7Y5YMi1rKoS5BrIzb4YtMiRGVM +1uwc+4RKCDECYzY6hHAtR//84C1HEhgwmDRBZIyouaA15wmtkBSE8nS9mbo/AmjcSSmR2nPXC1Hh +mdS0isSdUCxeQtkCaYKoL1ToDHjKz7UEiAX1jD6HzOXRcpqy/+G8xMN47gawYrtK4iqHhq9aFjHI +7WhwGtlPB9SmLiwlyy1ZdArpNTzDb49mAm1Z29mI4gqLLHxQe6Xs3b8UooeUCrnkFRocNvLXKa7j +qMI69pWFc1sGtYN3eWncN4QtA9qhtHg/S2yW+gsva7oJsHfANzwUyCNMcxVCDgGEiWYjLc5YVsxn +mQNdl220FZO2ywfrDFRx0mdQpklRsnB6ANbAR1qKST26DOziLap4JaTdvRGPUTCEeAXCxofu2akv +dNIOXz9iBgVRWySoXBn+JUgSorHIVtiivPFd6OzgGPm7AkdsRklr+JBNFh7s0wPDnKk0PDO4lkgC +tsxlgz/wKveJJaTSSgzk2spT3NgMAmhFVGzuN5uApPouwZHW1Yu3rRmfHUoz3lfS0MbYtnB2qDmk +7PesG9kkNt21wBfQvfMA2uwvDWCg2tFKqUw47irWSdb+uD6WB+cfCxGELek5ZNmULBAoY+BS1RxO +TEYlxKHKp4Ifynq285VrJ+/D51gdVtNkDFrseiaH6eboksyVhCU9qyE6CSzeDTZg9tFOgoWbXsCi +lbbNApDPBhrzrvrxqtcJQEQmXnuuLyd3lAjXixmv4WIDbJyYx6PQFIO+qlmx6jHyJFfY46E978TS +kvwPjOpjj2/nT6jyAKwMFkgsYaCzXgDzEBTwDCZ7TQYpNo4vZ6BO5l0RsQA5otQ1MpVtdQ/hQMcH +hORhoSRC5KS56WwkQdX5placPycrjcACJr360vuwkyB1/Cq/yGEOaxdt4Lc33Ny4zTGpA3jQVIro +vp/2h4jbkrLutMN5aEcMbQ5/kEZBfCd/7GJ09prx4fhoX+/sgYb6Jffbx+QDFUEYRgtNmddRWt9s +oW/yh7obObNoyXoPrzUbSlyZ0NJVaXFmUlqyiGgJ9Fw6eTPBUgU1uCgv4gtsvuOjMrcIOTGri+QJ +SrEgwJXJmW+qjsHEpkkDXEFWZPkkF2DPewMXwC8bqSc6vUO4XfqyFMolBwRN7L4FJ6PQbXbloGpY +Es053tJXhSO5vANOqBDSnKyDcZNjgRJYdiXNsCAytQvSnUq44BJX+IjN5RyGIj3yiYFjqdKxMaiO +yDvTQfnc98ex75MGSoXZcgC8v7Ixy90TUs9lHuppyLGDtA1ZEgg3gdg8rIFucCs22gQE2Xz2W2Q4 +6R9NPVAwM/cSKRikETWa5BHzxopnJP8A8yVzhy1YtJLJE6+Up4ocOULI4k2NKMzAAhkjq+R4SSKf +SJX4Wt2yc9JnpXIKgFLULRggrnSZrP0jFOIrAvLKnWKMUmXhY1aNqXmOGOIZuIkaY9rH0y0B/a0w +mKnTaetFzlvwU0NGCl7EZHXyVKtrElJEJs9aSZT3Gf0O3oi+SYvuxmrisLdsCa2WTLyTD54hBUYe +sysi3BlH8YVnK9o2vzLd9VGlCT+osvoR0bG0tIBSm0WiiwDu/nzPGMsj2DAdqUIWEA== + + + 6yneP7cua4qkFJnK1Z+KlthUssxJ8t6OtB23W55FTjg4PHY92aFU3Pf5ZnzCAfiRqhvDKjrdzWdH +KXsKslMEkF48TOoqu1st0YxbY+plwq0R7AXmXiWwcFCt6HqwcYLO70ZxR/G+RXkgrVcDQ1H5xsLG +lgeryx5JuMDmHJ2hQUw2kh6bv7dVdnPMhTGN2hxYT7fK/Fd8JsdWOTseHHgAQF+x0Bv4fdph01zP +GWpS51I6TiEQOcYamyFLlsKm5LhJVkFOok7yDaiO+l1TZUK3pKsgLc7g2hCmfrFJGDGvi9otgOVF +LMaNAw0k9rwXQYprF59a96NngNYhgzGVkDyRukcXZ2U/yVr757MsR6g+SPEihPhxVyH0PCj2QUyX +d6xEe3qj0Nq8OD4GEXlsOyf+ggf6MW9+KW+cYqaeXChdYIW6kIXRZtGQYLE40oQ1g9M2GVdbQsFH +adiluMxg37iC0yiU7w6jKN4syDqJAY0zNp9eKcMjNqn9sql+oiC+IyH7Xnx745p6UE3AGh7G2iGN +kjP1kAVKw3rUHU7QD161kNv2pLt5gE9S8uTX7ZVbMXoHnMnlScp4wHGGVcMsV3GO4MO3L62Sihtx +V1cgeuCK4IlcY7l8WMlFacXruqXrFvEiG219lxDaa1QyTVZHT+zCnmWBabEiy0gaQ0vpb9mW0kVN +zOAk7QqM8asQMIvWsZI7Vp7YQumPSQWOteTGnRRSz4/uGWWOEIQcoP+mAFWfWwsXzYDX+DYPoKdq ++ZXVAA3xFXGc7NArMaZnA7ZStmJt03a1+7sdssAu4/5EplmDgTOuBQMTeG4oqmvWjSx3bWJwkaci +KcCfeJLSKdzPPrF2pjQnW/oxDjw3kt545aIS0sTCQtTWtA6o/gNhPQ/7KRzt6AORSOr5yevVuAnP +UaEwK3ls9dFmijdd7WDBmu8hXqynqmmgALsWkMbCV/MpzfXf+lSKvl+kxOYT79x2Rc+WygQ668di +2LVCUz6t6+W4kmkxcSQ3j8xGx7ZxkOmEiacs6oJZ3k6WYfYJsdNVeonl8g2yWbq6FF7j0OQ6TmQ2 +CT8QvfkPZPFl1xBkkCJxJU5ap8ZUN45Iaf2IZei5Rgt8JltRSseir4z400ENsxnBiVO15bL2Xtkp +HHrOHpPE3/2QSxz0SCVp8kYHhIzEcN19DL/fIMa8GN4wKTeGt1uVTCkUC2KgfEN1gidJV6c8v1WS +Zq6rw+SK7o8Do3aeU2DaO6hLJc+12OeIxm70VOyUQxcQ9xnfTvei5lEo4EcCz8wYTbFmfy7iMKyP +4JF18J0G5tBwYSVAD6KKHX7wFMi8TbaqDN6IK+FTvPaAHHA5TNOiqnoFrwNSaJZUKtlo5RDynsRj +gBGllJALCfnikEtgPW250Ew5HSLgLXT3plZaC55CbmuSsAZvH5KGj5eitQAdJ06UDZ6Aluu8Zr0k +Lc6f5cIfFRBd+AHssIZpKGCMUTQtI8hL2nm5tG82+SjJVXugTizEDFibCpmcpkBNKho4Qbg1Ymf5 +B6gDGGLFMIR6IIELAA3lxxmYecsDJasJhosuBAUUfzUq7bFHs6QfZEjOMQYX+BTCryTNxAuCazTO +/hIWBtqUEviabHwBViUyEhiQf1QBW2J3aDg4NeEEayAeuMdDEX2sHt1IQzTLaXALi6P0bVmTzKrd +Q0Y8t+IKMO5dbTNQ+Upsi3gy7P+REcIVcXZKELo/iKsdQCjWiZJzAkAL8bTUixvZofyMh16Yxw79 +UM9NYIW4Q0j7QWSjffTUrW2IO+GBeX2XG8GoEUoGvyetEaY2ZZq+6/jQ6pTTCRt/2XutH6IlZdWT +Ne9HjUPMn8N5yDxyxu64H7i2O2sL/Mj3cGfh7Yw72OfnTzLGGQ9TF6IyVijdZ/gpXSBZkNUFRBa9 +q8lboVZZ38F8Hkn8wWBIIMRwvkxGcdjSiJk7vE+ByflvQNIeQ2c0mK7Flab3hAmdctoyb/V1WF83 +7C+sGJAsNWH+XNSP3wmUPw6wh7NIkl/401bLEU+o2QZSFwlt7goZ+JEs78qBfXSS7+LsYnZdRifq +qbvkxFjf/jCGyh9TM9OhXH/T29PZ/4ZE59t5Yuqwl7L+NyppdkFmTrJk/BrxnJYe5sSUJXFK17dS +cQrrTu22MOJxtonkKZ4ysixJBPA5QA3OMAhPOPcVsH4RiqbS/H58qnPMHbmMW5q0HD1Z5VJW1FD2 +x8ngSDOsIGjCt/YKaY7tx+OY3ycVbo2FUuDFcFIkUzLHuuZCkjm8JgTOqD1+rHoVcWQ6cbqmuy7D +EoQ3uFOAU+K2niDDA2Si/hWOFBDwkYkZh3OXXnBxFPvisFm14LdrqpZZZllRirJUUVieUKbD1VC4 +8eDtGAKPlguLAa6sPBr34bCm27ur/5aa5dLSQJ4+CPYQcwRwoupKcO69sB+3GA4B/Dy//DRsabHm +tvOkNJW5wJNgGv5YiDEnL5WK1dICUOyVB/FDPCAU5wFZjRC4JhuIxvDrKCJJco4c54obLPlQRV/M +k8V/0dcdGlgSJzxOUVhYOI44PBpgB7FGwQhS2mKlA4ouddqbsRk6Oj2GoAiJixe37qm2zZviZoyx +/5Vw00nIaUW0CLJ2WBu2CNsLftSKylwAIPgILvNNqoWXk4A/Pg0o/QcLmh48XJVwZGsoEmTol8A+ +WVmy22Ef75jteWdxBbaXAMj8JlT55scy8+MkOxzDqJ7+3eshz0WGNEC2zZO+2m7m8KEvFFVK/16H +4IFdLcwfAdG3JRV+/PHkB2NkOqbQknRVhIB07S6ktK8IqkqeTytYUSIRm8xnREEy4D3FyhYi8oYY +Gopzy5562EpcXYFzo+GTpGFxYeaCbzvodlCeTyiZkv3MCYyiD+VAR3C6jiiGTQQAOvk+Gdc8/w/U +3xjhwoZOfCRhkAFdPp7X6vmamkdxgXOiKuoyl+EGLiJcFnnRk6Zes/UTonA84ZKKyWxJLI4QIV/u +h1BTwU1AbdeyoI0XiAAmpcUXPdr2gmiG0qVPLY5TRdFdCCucCSfjtT0gdXKb8QhEPt6NWRlJgdxn +8X+U5rx8X+F2NLbeetZKgXox8HyWq16gEAT0n5Ut2wt7PrbhnxT3V0bbhEgYRr7I3NqxlJG60fWU +AFnOVEGgQ84uQPyO7sxjiRe9eaT72NSHwLxAx+QEZKs/8CU8pvfREu7cGQY8riZxYEMlty/StuWI +5QgNG2Gg23RiN5OIxhOtPTbqkiLF+HbkrfdgT9ZbEvYT0cClCYYaL1SW+nBsTiRfOHdjBkCb1s3E +y/BFoUwi+a6ksNXsVfR/AENmSNoGB9EeZtRQL+chVhRAAP1fJ4uRFdUQ1IhLj4dMf6SCQXTr9r0q +MUkZjA4XaIMlJi9whGsngaXhP5eGnGxfx6wt/X2wzV50HeLKrfvMiS2aCY1ftaIHr6SmraOBqCAP +PTRaUDgOvEmUGfBYniakrwQEIxdsNCwVxD8wnfQ1Iix2qllbcEb/5oyQW9+jgy54UudPFhvdWBxR +XT35mo60w7pbHAkhjFq7AM+EhHUcpJjMGQF3CXAMW0J3ba8A8biZZg/Z+L6m2zlr5Rky+ctHaixa +0n2t2oNcf/63wdmbCe2HH1Iu3Eex+5aot7asQR6ng8ippJCU7n7ifRG1nkPbfKIqF5+lNG5w0XKo +p1S5tvO0EYXkEDmdXSrAodXz8tVaiDWdsWQxyS0jhw2WyJbJpb8mH01nEf8dGvwXOTsy4aPApjXh +gJsRD/3tyrr58H2spLmghG3wyQIVUt6KHEf7CIBYZXUqGh9CYi+0HwTedZb0z0RM5YsSZKBv+azw +K6IB3l28+IZBkj7kYO3dj8xzHzbEZ/m84PoXNj0aIzEcsvFyCNctmav+qpsRHHa4hifp/BAEm3Tx +eL7oB1cOLUvdPzHdrsO9SEZ4HeXxWqzKIwqghfoFh5uaIyxLmAmCporCmBOAUuDRF7jK3ySuv9H6 +9EL6hAq8UjWHaCKiKwdsmJR4+KhsE4LRT0NgNaDsOioqXe/owomhcliPtho7IbcIA4PQrNYhN1dU +7+a1BO7/ZtUaFyQ2ytfCGOzkdOVmMd1Jn7nIE3mFiTYe8KjUui/I59lGeCowLSwOUD6PRJSocJ12 +Bi8sNRZonoeBy3UM4XgKpm7ZhHEOxu6+RUGOXGS4Q24hW/FDcLX8yPkvF2Qh0UZWLJ/cLLdvI5vQ +UgFbet/4XJsWJaB9QTdkpE8CXqXnSDtqW+0iKY2vNzGgGyV42h3yrkwG4Efw3WgHYoHWD8X9vXUI +hdr48tcrFF8soVUZ/8/JHx0jL808f+nMQExYEZHL2zRAQhw4FpjgpOYfar8JmqLYtpDNYXoWu6aY +hWDYkwc0MNyxB32j5OtP9SValC/ybdeNH+wftl27CATrutVdEZOqvCI9ljEpX+oDiRCdqquU2lQd +MKGo4aqhaiiVq2PJNg2blq7ELxVo6Uou6T2nwsYpwFYi6LyGlC1q49xluHWy/4A6mM8NHCehaNP3 +lmfgFn0RNQ5hOYliReGRzIsKOml+6W1HR9jth17Kf+itIHrKEWAk+T2n9CH0S9YnHW3T4q0tmYtq +1qa6amjOPaGLQVV/yrGwAcMxKWEjva0FZC0dY/zMjq5hfxoo06TsX5dYdlm18SfWqrBhWbMGpz/f +tJPsqpjqk2hD2ZnJeR60AQ9GuqzQS5L4hACBctykSM0EDbiw/7BfynM2DlyTAq5rqWnPghMvsQRr +jrJCRoEFNZB69smmiPzC81iCofD3ER+qzucpiAS5rTUOG7IZAC7O0l1DuKx93hJmKY0TYUWUUEpQ +bobspDjtaetQvXz0eUE3aRdULnFhshKVpZqlv7OSGHQ3mEaXpeQn097Gi35vBRQ0f0juJaaEWaoy +cf5HNGitWMJ3CXqJvBfBLJeel8nznYPZpvCKpbsXgwhDcYw342+fyx01ppjEmoUORwJFhbNJ/Luh +sVN0P94jZ5A7Nf3tA08dyHecLrhPxJy3kCIffJ1kuoRlzi9JYpMj+DyXJBudnXnd1DiKKiNlwsS1 +Dz0EFFXqFj8IgmMO0uMOIXTXAr0o8DnNiEOaG5BgNYNQEYj/IL3TAF4HwL9nzRYrsc1qm/QrRVic +mDx/yZPJlGAAv6xD+8rOqC4bibViYhkavnESzKydJZDDiMbt3o3MRij/gnTXgcjA5fwLhScKdeKb +awZ/haLyDBjnZysgUDRfH8OClfSPT0ddOksOB5pZLJSbo4WklDhJktNzpj1cnShOrHufYFTwhw6s +ySsCkgmAGVS20GY4iWf1JIY1CT05RDqPBDXuoVAvWQmARaJZ2k9VdkBrTOsxeqsuCQu9oK7+s5bh +wnQoFc5iJ1iIETXVBz96sSvl/ahOaFAOS9NRkeMOpXVB/GNWLgNuGS9KU/S8YwkNfPI+Mcqk2S1i +c1/hUxMF2EMjQhn7qmH825GdtIbHr34kUu8ObUJJT807ovG5/NWiHprdWzcqJnabHQ== + + + Wt+jJKT7IZQ2j84+tFXuqKMaqGPVCv2/TPKzDrDEPCKzoq9iTahx0m/qlB+6VvbLx7N2xqpiB12X +yXwo2gqgEAmqYS/aZhwKBQr/0mGBOeLIwKJIJCv2XeD/+0Dq9+o/CeYrJpDKo0V4l+Lp4RVurJvo +j2iJvUWtlntQPHy73LrFRKx2cAzptYVN/zHt3U2BXBrNP42OZQYrXVhLeCmtolIb5EevPr1MPndN +FZQsmLZh5q5v5N5oIP6Vcyw4PAfOIAr+uHlbaLjZBxbP4vS3hUiWgBTerhva2cTVCf1GQEc2qy2w +S66e3K1hM5D1Cob8GaYI+sMU/MZdMf01js6NerOzz2vn9Ymz8J916MW/8I2ydUsnYuGBJ7rGZfsA +xlwlaT+KeGP0zjMz/Dw5PRv00OOVG8UfuoO0bbGF/r9yPm9Ixew/dZVHuUPwMDRGbmND5zfeSeus +ZUpoWI0hPlJRo8kDUxITSimAafsLs9LR8z/L1V3wZk6VfreuQt/FpWxS7GZlKyWmTO9cc/NLt7OY +7youdYXG/UhryQx9fFOlE1l0niJ1bBCbCc1WbyaU9ly2XiiJ4FolCYw2T04RcPClorGPm9wGbaeW +m2cbHWMYhVQk7hL0Z1p9smzxG5FAd9JqRtueyswPzXBvkHjxKaXJ+IlA4dHV5618jtlB4coG2ZNw +myRdolBzsFSFjEBUbeVFAi4DoXk8cpTeGOf/ESGHd01tLSsEcrdFufFA9TLbGGsNWn/YWQ/p9yQf +h1rtaLpUuqAVQ0hyyTo9jaHE1qtRq6WIEWr8DEWGGBxGsnNeywMXYyFfCDpuYF2UPTWpqxhJC/p+ +vExz5SROB9A7Skbinb8KCY3k3vpFEsA2DvqQ+QHu92FKthmh1rXVlAaIFjtB+kaiDVs7qTJaG+xF +UcWwci5bFbzMMcy5v11GHG0BMxRjiNOPA7oBmDZ/efmQS6t9KpHaahlJQX7Z/2LSI+MWUD/Mb768 +mcb9b16gXP+doP5i0GgmA/EPPwJJJlPXLWuXmNxdimAHda+o48jlS2Ovhab7uPi/nY/TkbT8q8oe +EU/3SpoAm7WvTrxpgjOoYTTerRLVfCP0TsQQDlkucZZViDNwG7Y3GOQTT47yNuMZEFij7hDQgsje +E4OrYu1oVdoCg0TwM/jbXQG7wiNTGGhD1UIWkv2sIwgS7BRWWvEOYsujFyASSRDAQfapss5jBj4w +DX5gEDX4CjvglDclbfOULgNuowQ6SPNCDSWApQ4B9+f1BuDAT6g3xUAv+biaJTaWxdPnBRkfygW4 +357McBv4jiV22YoJ4yPyIB0P/YY1unQNd4+VSF5Q2VGxtHt6BVzHylqe+1qFAulzzWbPlDmVlgAP +Xqv2IFi+SMGkk+gkYN/RE480eZAcEKF1G8Jscdm3QHgzjy3HiE9iCDq5x1CyG9jAmE6zEYE6RhkH +9aVulWnQ8J2HCJoEdVIeR2SAIQgjyGLAyoAFKbBtWGIzQ49SpQTG4sYkibnNIfgUCI5xMsBdgVTO +saMAyJorjYD9Vp/29bCbBOlENwY0fexgiNL8uaDXR6k9ciLtr+yYGqkX8qqbas/TlVLf03I4d8BY +8RKp2ugd/ITGeAdQQOUvIwTA7nfpF+h3XIVF0xOH00IPRluapInTwnGDQ4zUTLfJllTcBfltIcP2 +o4Ko2pz3cEnriACzmAKt5Uznt5eQZDKZLiB2gFmswm0N0dxl+jMPUngdAT7k/Aa5u85j4C8Vd7g+ +o9AUAEilsJibAC2G78EOG3i8wRiebioUvxDeN6irrlHIHV70a6XXo31qo6gNQYmN+nDCUslV/BLA +nkBUswPjzOygUAalEbBjE7S5oJt5Tc0FhAF4Bz2Gs5rxxMzdZanjRjc2FkerWtXbug5pzYRoJw4N +Ahaka2S3zE5KM7hCYc8Ic2sIZ/6CK0W77Hha/NPNWpgIPA3f7+tydOiy0z+ONF51qHQbNMNybGoh +9pNeNUMWmj1mDOh11VH/bSM0I0ehc2QWhn+/FoZ3pCGkXVzlFrVZCVePygxwwnTELYRejaFGrKJ2 +8FJxFYKj4vHU+W3M7QoGeVSpE8IGppnu6i/qbX4WIcM90KcbyVC1QpqBC7wqo9w18m181LEgLS1k +nRFrWQlzbTVZerpSo0HqXzQFkiK+Q7QytAtEsxuuksnEdLaZ0SfQ6zGr/jArqoSMJSouEFJ+K2nE +m58CmYwCgtztk/LyEvsSUUw2uw4fbUopUn3o8ghSWWwB2VtaLqv29jAuN9MDwcD/HbEENmI3B939 +ObwHp+Jy14nQzBGx+1ighZ90j3BltrkKWPrb5nsWISZ00KJKla0fQ+OxRri8EClth6C7udJv91m9 +pBLXn8d5k0hrA7ka2sx8VhjugK+kYS2ojrY4iFQ6o+yneYzmmIQVEWX+L+FswSd4O9ktHfAMxLxz +M1QfUjqRvA7m3joqfsVtKTT0EtPlTN2ubKQbHI0p21047drRmEUwZHqKr5ZngymDFrvYcKoOOf0q +VMH2MZnCgSS37cfOcIsa4DDYlnedNUjFhqysr7MHeOVbeO6xsgVOGxun4iJDZYXGicLR+JKdn3YA +rtZLjGodw7mLVV98hgAL9zUme45Ysmj2QhR+8EeLymFvYuksJ9ruitVMweXYLMuSGcrBG10KtBRp +zCZYIw9lTYixJxWvq0g0G0GRRR5Ux5hUfWhMd2fB4h56CUXVQxQvAkMdu6532kCKpeOLfAHlJaPm +QULddxmLy1kkrq/1NAZhOOQRsQ3klqgSsIqCqO1R8uwTXq6N1Ppn7PqGMU6NA0+uRl/ZWwa85mAO ++Lk67kdICpQLNIhir3IO9NmxUyLpE4xCpglhb5GFbMEDxUDn7sWXZsWqdyh6u/08F8HSY8RLu/e9 +GE3t7erXhhCvSzw8mH75sJZP+GokzX34nLFBjPIaOuzMJ61EkaKvEDlgaWtwiywsZ0K+qt4zkl4f +WMUaFyRBBEI6+hwlOIzAt0F9s2uApVANM6wK0DFwtChjkIM5RllCwY8LhVRFKGgC7MnQM1vcxGeN +3KjO2aGQBV5xpx0rApNN6NImHnGalL50OZViRYbULPuz7fVGHFQjMQu96k0lMocyRkFeKkARaDS+ +7qtjkRtI6SW8X3WRqpkvCimQHaBgQb58Ru4i6CuSR3FJ26S/qwFjb9mUBrUceBXFdggmseDmH8Fo +Lzy3SyZJaD2OB73oGqW+R0q+VFYxuYjW2nSbD2yWk25MhYlTscnZCSckPGwj5081yNNZ5Lc8LhGs +l0fsRSS0AI2PB4GGmBlwJwi10IRFe9vq1VjiWYh8RcZz+F4mJ5F8sFRQXlExAm2m8VFhaPQIGxS2 +iRYhNTG3rcTjgqispBQZfjzEeycgBuAIssJ0CbmshuOElk2XKr6hsA/ozdx0EHFeNZRF5PJnYVvx +M5nmNcD0vOP1QgeX+FkaSdlS2O4GxFOa0oUwpsU8LeSGdtSFURz1SyBgXR6rjfJfZ3nF8fdwS8VY +xedN2TMaKAEGdiIeEK8VAUiIhlIAXhxCMnWvnrTpbwz9JAlmIzpIhqUiLimO9zIf+J4mycNVL/PB +KJCfQ11D3U038cYio3vga3khU1dlGhec/hV9sA7xoPxWbrSX4Tc94WpTTQhO3owafgEdGtwCyLPX +ijTyIvWIi7pwjD6fMQmlm25EYihILovPVxQXjFavAN+/r3VyK5cpgjLRbwKDMMlkQbHcw8+CXmba +VFPK9U27F6fuQCwNyzygmfpf1BGFRf1gIYycpeFUT1UC+EuAA6OOC3IqjBR3vtDHtHQGwZ4VwV9o +dfbCLPIWWfKkajyghg8403orhOrhCEOM4/o60peToRdtnQ0dr2h/rnNtd4MoMihl4lCh1eYa1ZQz +sl0R99AdZdKKPSrgNYG4SUnHvAC8VhXHy30NlRu/P6iBj6gnLGbEtU4GZznLFgiuplKVLdhMFiLq +jKLpauSciSznW66wxRn6NTcNZfPnAQ+E3wVPb4K/HUAHw9zaG11TzWDoH+hbUNSCeQsrIqBJWKNA +TJY0hrAG0gGNPPRgBg70hiwfIBPIzlfYiQLDzMUQLyGOv9QiU5dwNLb30QiehwMKZ0Z3ekhpC/U1 +QTK0Ytfsup7XfxfoHkvedL9ftdAVHq+V/36tG3q0k1h8jfbJEP+ifWQA5NOJ9vmTQdeyp8WJVNYb +j8wbJ+1K1w1RU2NmhIIL3fzz6aiGJSrhtLBFPNN/MbOmk3JyB4RGyR5E7Vc8sZVw2CsszOISnC+o +mQXo4w16XnS4COTnNYWL8fikdMjlNSKMmPWEpIVfRxzNi4JGW7KrAsQLZgGJcpOqqWOmbqlc+pcB +UGxoszhif7NsCVC2kL2KwmVm4Q0RU4uTxQI2bdegaPQd9t4K6LI9BmeDn1S1Y5/poeTHuwvyvg9/ +wh1ee26Y20Z//TXCr3UKMj0iU2ikBk1oDxhAuvpTBm6TNgSLCMbEKrosm513VYiPfXeBAXDuTYsk +T7ywOUzUdbIcceDsWAURA5D7tXWHGmD9mvrBcsAX8Y3spP3B6pkRwblELOQls5wKVYz0FZ+4vBBz +cBQ9WfMby+jColcPUuxZa21jTz/30CFXg9n/P4jpjwMlLvlhbgBniJkAgLHgRuSVvAQ7X66CZ0FU +dLuwJMBLx1hINXYYrSmghWeIwDjlSvMEa+T0RaWdwyAKv2vlxHMUCu/DteO/vE4h9RvqeN9sPbsl +QWSCyX93p2OdluKD0Avgmcrci4uXv8Wltd72PcELCtys9h2gLcV4Zj/tzFVh1iQ28OrRALPNUlBy +PPWtE2JwtXe8ajjkXBqc+SdICptqkmbDqHKTyxKJwG4hBz+CUj+1HoHr+4iBsj4mCY0mUYJcs0M7 +Iw3k1rqkBPmACgx4OQ31QAxEMZDtyAx4sQQykhujRZHeVoaubM7Wi/plEbQlkg7dIZ0F2j9YRwQg +8fmpf/9wPlih0YodhinGwM7D7Xui7uOz5D1YcP4NMjmZ8vglAQEHqQBv/QHKvXIk3OzLqQyRpuCX +9z+1Yyq2/4pwIjJZy1a3aemxocA/JzxxR+t+Du6A4KoH9xDyYSIrPI38KWPW1vkMkXeLtUKnTKj/ +esddE/5HVKT7Uhnt24WBejDTVxGX7hrQrD4ZgMNdrg/HssHglMD929JpSRdn0igj9Rh9mR9pjNV5 +MVa6cbVIY0eQZrt0c9fMr9SBy9Q/5aT8Z6Wdbuu3gj7ewNURFrH0V2KIdY0KI/3CSdQUqmpQj8zF +hB8eFqCrqMhQqbgDg99wDhXWydFDUEI4+s89W5LI99W73Ifwp3PkmqYDYT22QM3duvibZ2cr5eoI +uV6/x6xccnFgyae9JG4RrIlOATt8rqk8c37Kl9VK8UVT8/f7oCOmX23H+ysXnIFCAhhoIiqsjx8P +xEAXOXl2xeJBxAjWZBpRMPLavjek5LuyLDw1nSp0o19khI3xJMrwY9hPzyv6iiE07w== + + + T1FkDtRPhCFqKoF/anFRZTUvsahpaTDNMzkaDkbCtcAwITgmhUEn2eH6npcKz7MxCyZpjgkZpVtN +MBAKs0uFbnYEczUyOGrOiUBm0iIKIc4jxrX4GACRaGMDF0jZPpVB8rdr+7VDMALLqnBjOxDht4Un +7fwqyusHiZgxmB1QPZmlW7eWWJYmIsjK2IB+ulS6ZQkTB1uEJX0cqXwqF3xQz6BB7zJKaHxM1T6L +JPqiA2bbatQaohx7SVuM3KrgVWjWqSnvLw2zTlYQym236A43lKk7zVTNg+Y1nSlQxn0PNsTH8L1L +HiFioYsFxXgRR0zW1wOjSBqUDDi4rQdlcjDoGsf1hF3X43urGq9s2TQTmK+PDxKpPXSFQ5ThShxa +CkGR2JNQKdgBw0HZCYwGGHAtAkw0g691U7m6b1jVesbQ8OWku5XRx+wEZjwDRYJo+fTgCshp+EMa +VxAJOYSlngymqrHsWiEDWPQkRksi0HM1VlTnytUt+OHajVGaQE6YIbotv/3LDq6JLnxKKqUhbbXa +Dw5eWKv+Q2LNDZpPUemuCEr5RKpPSGbBrXkzmofJe6B2PZ+dyh44q1SwpsCAH3AOyAdCwKF4/aGO +PGvgeE6Dw2VP4idNpVnyTLWstq+3TJ3BFMcUzPYYwpQ/qCTRH0ivYvfJGNyvREuiZ7fj5PhAXt9G +VgreJEi/SOQfFbfQvkfFqF7n+a4VnlUAYif/3nPBEgkyXKMjyTav2GSYtMCszwRgF1YZfp0zhpgt +nk1Jg2yo0Df6dwgNA6jMhRYuo1+hzvNyYkLlsroIVYjkq+VBjWUACPe5Cie5VWyzhqkrqF/w8gBf +hkZcbrUCxU6rbvAcaKJ9YgIa7qjy7hZnYZAjfotapIPe/u/Oui70dELgQxJzwpw+qu7GXCXeoJn4 +qweuRYvsH5485iSceApPKp1i+Pv5FgDKSjb4EEffl/VrmHei8XmzyrVdS+IUiSw2+UvTdnm/EgQu +E0WwInEkA3iPDEqNEIxC835oX7v+F05JZbqrqn0mo66DxiLS4LvBa1YMVvy+PnGIqbf+buErZNO0 +Ek2W5MAICQLLEQAUePgRgQrrc/sUsEy+SmXXZNXpa6xHPh/ANy69i/+lrky2bN7zx2m4jL4EgWuT +VAe2MR7Bivz8cnn0d81zE4RrTox3AxkytJuff37qNgNnQfBHgJor8XEAMweHJlbnPJqp9mrppcFN +TKWaOEzLvjhw0+QIZwAzHw+xROQ0f8ZR5GfY3mLyiGWDyMoDr6l875xFNHfqmUOtAQwVb9oADlrg +ykpnBLbLzsAsIXUAqaMSCq8gWyHD3XkGZBwaScR39f9by2Rk2ikUGuf9z+YiCNOnJBW7MxA4wmaD +e56KOBoxR41/pJvRLTI8ob/EWL5u/8bjSqyehAAYyYExcKIXsYrasxV1F/2LE9lqtxTCYU/C1h6U +/NKOg6Y/gub/FXHbLNukQ8Yn2a7Ix34kzOe0YFcSgzG4+mj/1sqoe4WO0opnUMHoIVRie9nHGc+A +GAongfrEcYHSt1qIChkIMBFUctpM2dBJ0pkQ1PjPJmxbbujInoJsRfb9gtyHuDIohEq0OQO0QZ1t +D0MctdW2HveRvJNyjaCdArtxlrfSM+3+feXbKtlstPGNpcfoPMTF9xlxBPXk76JdvaXTUUxggeFf +chlh5Oz3hcu6rffcaOaATKNkeCQ1PBtRB9WPiG70ChqfLDs60Qid4bG8B5lSKoVh5TyiPyphB6Nl ++iTrU47jwWKF+6NzB8oaEnySyDBToiIkf4Kg6S2fBCh0H/i4iWk0P2ingEKdxti+oMv2LBDdnCbn +ADaoxBHNqt7j8LTrX8PtwHRocitAVDeni5rMiQwxyr1uxSMazilg0ko/wg5+aekZ1TPlyhKaCTmQ +aKCq5uOzYnXg7U4EfYjx7q5G6UBacnxYSUCRbrjTBUkEeHziyVmpgP14ouxy14LhrMRG8H9j/l0S +ekOlTcXIpElBksAsrigNkEE9XbJoxh3IOKd+4Fw7uW8/Q6xEhbSUrkYHXaHBvajqzgVhfFzpKJQO +ycvUkqAhKTgBZ6hHivTz9f29Y517o5dHVqMspXU8Zu8gzRsgywnVcq64RKn62i7jSkpjHWlrWldA +0w2zGXil2AcYrled4OHlAjMVsw/3O4hvgRNcZH+tgpaGLbZQHAXiDyozyHkv9dxm30WXDL6nMSLy +3FDCwn5hGHitbxBqXem8SqcvbWJR2GopNYbnUwsTOYFQvPJrfB5iJ0fgQV8/uL7fFDgWB6e29J82 +YwYoNZoS9gBtEaA+ymz5BE+3sbuDNTanirgau8L18U8bCQvhRHW/r2VwGYZFuR629g/jQ5ShffZu +8DnNybQQscEiRPYAKEHOQJtuFMKBV4TthgC+8kye7jHBgojyCCpvXkFCzdzjo1Pm01ox8cgrqXSE +7nDUFDt53KrKRlylgKavWnqB0bElKDV3ERBQNkJDQGEFMMfOJ92Fes62IjockJZAJUK/kTc9XH/Q +XSCSBrVaeHX4gNkpCvzSj8ifqx4a3D9JOuDWYy/iWIuyp7IpSJun1D07aOGYglNbKAg56wyf6JLQ +6RZp1gROq1ao7O6HKW0CYxW81Zjqx2o/hFLYSqZoTjKFfLpswiNT52XWpyXpDQT2RgqgDIB/1k2P +4Qp4xX1xQQOvT2a5Rnv/hPio/4QW+SccpqMojjSrXuf9AYVkvRdP0QBYKfff+q4hH4LC43KgkO4y +UIhzGhdlGQxsf3eNWsiyROYy4CNQYpx/O9Owk65nu2Dd18J7uyTBJ8XcxAQsq1dKmI57+m3IOfX3 +k0xrAuugemHlmis470Eull050Nr9Mz5/cxFQkjGJrZil2jLYxFW/N+dXCeVGUz4CkTmMmjFAxQPH +BsrrqmRExm9e+qLMDXhqy/i11PdbVrppi0a6JTmoan0LpEUJClFNYqfZ1tcldbCloeXjf6ZJQrWS +fRKfR2ZqbvZkZTYH44R1Pn4kMoWQa5WjHTO4BYvZA4egyNzNx70U0QSecQwBROSYGtIVmUp2vPBV +m+RgKlSkdH1lZtda00adcQuLJ9g9bGDXvz9IYfH/U6eAHH41bQPNia1L4PfxxyuHp3HLq5dFfSpy +et3EughxDAFGznuRt8yoEOxJ+Qd4/asXA63QnecAo4sAanjOI27WhZbnIXMK7A9nh58RVyMPrtX4 +QjGJKQUA6A5ssiINwkRxVuJM3cTOKBthqYmnr/9SIM+2OLlfQbcLmaOqTCpCatPJqQwzRDEGA0j/ +ZooN8ACH5l/raQoL+fPRR3QWbjevpNV2JgZaLBqWA42/OdZFbVM8tai/2nRZbjZ2YHrvd4npPML6 +Glou//FXIKcRmciwMxLiJlF216Wdubk/6RiYBxLzYlrClHF+C6HK0bZNjJ8opFhPHARvDvtd6iVQ +fzWB5QJMNu18Q6M0GYW31cQPV090psHKOfKTsFxCtecxFHMadmngihDc+IPWUyFzg0d7+fF+o1hS +opxfISgf2tRnTwUFCAl7L1ZnRdQWAVPcrecTvv7D+bnj1kf+8DP7gO8ZYOQi9sBIBZQUXJ48P8r0 +ndnGkHh0f1gC6ULrl+4hO3fb9+sHsRtapTyVEuetzyYM6MPpXmCbtm6naG2K1fZFICfzaWp2e2Op +k/+mS8XDjtehYQmuzWdA8sIQlx7YMOsuxGGrBvY2v1Y2QAoxutzFPQ3xc5gaDObPsUf8UkeWgprp +RvWC0bWEHEfY//m078OYusLUCaW7F7YhJtZgbbrcgaYjv7s8N92Y1JLT+nkZYFQZI+x2+udnfR9w +mE0cZbzH6VmqypezJiuRMtfNeC5HApJ9XkCZEjN2aTVOZgIkD2gamhi4BBIEYPRXriqupjjIg8aG +R9nSMF8gK9CEhxYPUU7BWqIw3BYxgLb9KJZWbqTfQC6IZnyoHXKIdjJsoWu+ybaMkmqZ2VV6ZaUN +S4o+oi1TBDVFwTTC4X1UgkWTpr8vhbCKsdWjVAq0FVFbCsDfJa9zYOeFfLO1mdk8HE6mwDMXdIwv +9TKZnPwRhZ9Yrt3dHA1oFrKQsPDj9TM/Qzt/CobmVYzcuRnNuh87J7fctCG19zvuEiCOt3GzBxZm +H8pAEyy1FlYTYHnUVgAcCjD58qiUHOkS12LDv2L8PJzwmzGx0I/DwhIGKoOhRY2ufUX9DqImYb9m +Xp3jY55xw+sCmV1ocvp0LuLFAIAWNW51Q2sHfYNu5TBdvNn4NJJRnRQMuM2THLiax0Zw3KPimQqo +m1PAFvWZ668dRbOtilW55CI9y7c9iUDVyC91BAEA1gj7HyOpTgGwk+SIS5H2jQiGOCky9uOfykp7 +fo2RmcFqda1XR41ERFTMxluxk1bpfqdp8Y/4mgg6R1XxJrccK5hFCCtNggFXMyceu5ReIOrCGE8r +DCC6eRs7tgHpgljQ4eYyYsAwQikTIi0f6tcxTPbc0eASwc2SksWKHq69CG4APnvbhwTB82LLkxVN ++UF1UJmI/aXfhfRSF3ohS4NDPWfUPWpLGwN1EEvInvKOUgjQ9FQ4bKcA6XzhhuxK+X2Pj0JQ0y89 +1J8WXtUUPCK3BkM4vnxUiEQjzTAbdih2R31ejrMh13ijCPic15ek5VZ90cbuq2nNPyw6JjQgPNQM +cllNeDsKd1BfO2CCiXMFqAeQAEggsBeYHoXXBCmgQcvSBup1W1RdLt2BoL+FxBPn/Q05Gk7oe4ei +EueBpKQwfvSPVaY25Z5GuMhP9vKLD2IfMaMRdnnxA5bdZ1HYiPhNo6zqx3+yDk5EwFQDa7JyMqzE +KWDzOj3GeQld/v1iNMhzOf5GDk97STi/z+L9nk0m9OgmOU2fSDIMPJRnZgBW1ECsF4FE3uD4wNJ0 +xiwOVoOxvgwctJm8D5444PXx9vWj6gFeokYPO1wO0og6CBHUJzDeghXyxVacRl2ais9Efaz6JlYo +A37RsJ9tJ9oqEW03evHz9s1fZKTc+AGDuxcFP/h951DUyNU+XZeizWhjw4JveDLqbItOVkJqLuBn +8+mzs9N3DOxIgwFFDKIcldxFBxGLAouurjYTgLByoxlaOa4l8guYJEo77pEi3/llHqUJcAVdHGdg +zAcbJ8EWe+BXrBwlRvdVnqN4psknjSds0SwikqtJE7ggs15ktH/zgkqdws8OgrBaJarWySkFtuhx +thKYVnHbth5hDGlfb1RUGnqKGj3KE7PYAoUOrG4/aqpeTgvcMmKRvFPYxR07hB1YUp1dpOh0nzLH +miQHUllxtgFwqsUNWfPGlEZugGjSZhvGpuJp9qxxJ0XNKVMaAaGZ156rM+FoSWImwSllqJFCho3U +GAuSGCjCbK/IdA2YKsWXI41tjBf9mSc1P5wpwRQX2ySVDIqeK/4PCoUsdYYbUMgxp4C3qIo+R0Hk +AtpVGFfxLbL9O2RD5ezMJ3EVCcYOBUNuc8KPXArd2EhpcuVTzwfdUDm8cPp61EmTKQ== + + + 0YekAdjm3qwBnMj32c8t4EuKQ/oRdD/MB7eaEDFePhPd3LNSDTXMaQjVZ7TPnvs4f0vleYgHHzk1 +bF891TdOcNhKtpLuYVJjmkakFgfiCij0eY3/iKUY0YyD5Eqb8rMBSJDE4c2GY3Hu4kwV0bq97Gq5 +OMvx+nyJe2GyC9skD51M8SDavliaRj8GQW5vhQXdOhaQBuDlYVuRjHkbuNcmRlsF3476jQTuwl+X +34i0EQ93rgfnR+yIg2Ez+7eHgRoU7B3LYVFmdQ8toADIf1UoTQhLAZ4i8k5WdtTZZ2X7A4iv/sTa +22ZJpnz5UJkf1WqbjB/+MOjsinFysjwhqxJBaNIAYtD8l+f3ZVOyyedkT7UjOAsizwV2Nx/s6nrC +eNE+uxnhV92n4iJJvxnjwseoeRXajQqnpVdTesL5WoKsJ7e4L1EyLAPvrnLZOhneqjMkTTvbrPIU +J0IV12ZZziDNkf4PACV3xvnnVv5vGP3wAaRPsTuGcESZYX5LGFI6sl6BuAFN5QKK9XqKK6AkkLVy +YN78L4VHCHIc4BnhwsP/YBivuPeuiIiUZBz2pzkYT+E1BOPlWAUmCvsJYwebwAucz5aRQjApBak0 +Luy6cD10Aa9kxSgdH6lPhgsRiJQ6Jo/Qh8IEXpkDEI61F1TJwKuzZTGBgwvOSS9bZtqylBEBi4OT +CoUeJN2yJ/Em0MmD0n1QEAgUI4hN+RKnyNFM8YCWLrBO/0mlKhAghcxj6FIfHUKjKiYM6qAuJ0VR +IBDxlmFAIwQNyxg50RfHlnW/kepsGfOWaeyHbtmWKcWWbZlumSWOaPZ1tkxu2cSIqlPLRmVT2cBg +mIrJo0FcpdMgrgJB4k1FNSY2FQ6DAQYiQMim8h5LU0G5XpuKgYAls6lURrxlG4oB1BAgJSGg1wen +oAFR5KGrIIhgbYlhBOoTTcoC6LInJSAKDNSWBahFqnNxkWhkKls2SUqdhwPP8oALJFPZsmzF2uAY +bVmHKNQcARfBh8hBgWDLCDCeLeswOfgIwTYenmgVglHwyaxYjElAdexGbhl7Rjqaij6WpoLhYMJV +OiQimIoGJ7nKBiGDqXw+mE0FQrWwqcA4lqayZaeRjqaCsnRcBWKEwVUYIYNhIPGmgvBYmsrnsTSV +acGAqUAUzUhLJTAg/yE4MAC1QYPGhQN246ELcbg69kQCI+hTqQIBggVwdeyWScQUXhPf0bi2zEAV ++lDMljGbRFBGNDAh0TFtWQCJjxIHEccs8YkwTCwjCKGQSwBu2ZapUFq6ZQ6ljuayEVVgoE5xPDBY +Bcx08nmpLBMfGAFJJLRlb1Pg2VwHHQeOh0RyJpwFhTE0QhGglSjUHIzXiPMZGKiUrFhzwYk44xAY +o4WZbCHdxr+IlPFJNSCYiAZQYN2nIcWRIhrQ6CD5ho4M2KjAFfHxYdiAJhBFCEeKSCVONiCF13hE +JAYHFuijrg2IoQKBYsT6EEKmD8n3+qAICjifDZTCoJTJQNALJAILoBeJa6F7IPGUBqh0ShdYntIK +j4urs2WxlK0nBsZoS0AmfxaULWNpRqmjUSWYLqSUbSPSFKA45p4Gloi3GEhmyw44NDZ7gVVQBR9W +wEinFQedjjKr1yN5DD4BlxQI0JYpwIsE0SVAQQSo4sEy8dnoXiafkdEGx+iDEuHpgpMPxogzEOho +LgIbJu08dLQ0YSsQeMv41aII0MhBqSTCEA2cClaBoFPgcbhcRhBEWiPO5wHFEYBbhlmRLB0oswOi +Vki3IoLtOERCH42UDEYgSOloAiaqH0OMRUQISyIixURKEkSgDvT6rEjKNHoYUSj4bAbKikRh8J6I +KVwmDG8ZrOI73YrUaSjjZKuEBHhYjJ6Fg2OEejC9YkglBQI0AputAhjFRAQoxEJEM4oJYAQ2TFrg +EQvdlnUCDJhWQFUnUAx1UgKCVXxHE0oJWBzAYCKTzUMfYg4fogEXRYBQBiANIAHTZkUKaYAbo0oE +yoq0ZQkHp4lPayKmKgcwHQ+JD90qDjrlSlhblzIeC0/A/CFsOiApBSYBhYyQx8PAJHVeYJIgmGED +gWAjPj5b9tBCoBglbFoCIo7ohIDDIMCwgYBgAE2gD4Qm0FxIZXAiiAcGIxg50WANvQlogOA3+ASl +8dClLPQ/nsyFiEsIDBuMAGJq6YWoYoNNRhhOuC2DsYsWvIuWXDathk5XcWKkW+ZQ4TBYZUXq9IJB +HmhGr06BpjJR/UoYGlQYh8FQHuQqBabhGGAwjIbBhr5SdEK3zDkOJUWJnJBU5HAiKYcLMFIEDkmB +xisQuAoqQMimEosFXCoUNOJXEh+JlYEJCCVmsfWAbzx0GQ6FkHVdBuBny1ACHoePRIeBCWg3UAIT +1a/gBCMG6wKMRhowI1IXHR/rgBoCsQQKGYNt2WVuwGAPBLCkCqwPkiKcEEj6sbSBZuIFwpWPhuZV +JiAiJDomTgvjxC8EQlIwCQkXHxssXXrA4mGgqDBAkw9se8XVy+TiaZ2oDEg4I6eHiIAAWyZyDIhi +4KN1mXg9QysihAIuQHh4JEYeEDa8hPoK7J2eirQgWf4+XlSMvP7h1bBAYCqQv302Ul4C7cGBUYEI +KS8BEAOISRFeCBZMCXfJSDjMDJIOSSEE1lvmrgtWVzG56EvoLAwMMFxoKgohsQiNCdRSiEIkPpeK +CRGQi0cdawIpMXQb54K1Hw8FqEkeIJt9mGBpGpM5HWi2DG6cpL6DWSBhjBbhQlWanS3TkIVjHbAV +bFUaObBwSCDkwMIdBxYOqWCrEkziwMKhEAXBQMsDNmA/+oVRsmxelo9SLGO0eVk+Ni/Lh6tiQjWh +mkjAGPGWnUycTGyZxgSilGkFaGkrQEsvrQAtLUmVSlIlCBXKwYZJQ5wNk4ZevmHSEIRqk/D0yXXa +JDxdceCcHDh9cp0cOJ7KJuFplBTHo/CvOdAKc6AJljPQp22FkgDOQDOK6OJJQBFpUDJ4y16xDE5I +CMCtVAjcJpCVCsFk2rJVpUS6igclpYPy6qB0UFql0WjLRhojiQYMR33olokuCjQFoMxaUilQZlMI +Uh3RRQlBiqFEYRtZpcAqBw0BLYEGjxQGGeNWYrMBsJWJIIEUsKkYSEABjIqAAkgMxtESR8dEN7KZ +MkiqIKSAWEWFuzIUn0zIM0iQFGgePg6yYmCSXQErlb5S0hCADRCBpJwGKQAD4yMPAx2XkHedDFhi +2qVkbMsuFYORk4NOHFF1+qtTymO1qUBQRKYSAaExsalQPIg2lS04ZRxYIqA+IVCUVgD2DDp5nYx8 +SBEZqU/CyJL6GGzEQgdzWazYQSAFam5Fcr3Qh77Qh/YLfWioQrVlKo7ihfHCyF4YFB0BHBsK2A+l +UGE2XCrCGIVClg9XhauiVFHqTGxelszL8qGsGYm1ugJQZh+3yYzEWnoyUeqJ0snElm1bVpIqdUpS +pU4JJYBjk/D0yXXaJDzdJ9eFI+FpiwPHc3JpTooS4WmFEgV/zYEUhVUoUbicVoHgcQYGTqugUPJQ +WqWOJiFk4lhnwIMSseloNsIEwmwCyZRKpdKm5KEhaUoBHC7XpqIz0hhxIozRujYVnRFX0SONTYXJ +pqLDJ6KThU1IJDohSGUoUGYJUrCQKGQBcTIbqqHuDjIpMG6ASRKjkU6cHkzSQsChBAdKAbEtW1Vs +iBh8E4AcpIBYBsZJLjwXTMj3lvGnMBUDkNeRYNkyCQuBUrtIATGMigzMQxzYMgG4nI0UmArjFRch +TsckHzwMcsEisGFSDxKGKlXSeERQs3FyEAGh8YggFFoCIgqFDiG0I6KIg9WBBmyEQkDpPhATJAtR +/Lj4iKYnqYB1rKrA4/DZstSpkmEUTcoCKMFXC3IRvBa6jEbr8YJCjKAGDGsB1C3EMrgAloAxWgRN +90IfGuuo2FBUCBwKjagQOFSxiXXENFQIzFWoENiighcObAVbwVZ74XxocwG7ZS+MUkdDMDGhClk+ +LB8vy4e6KlwhjFGmgzFaixaAMrtlHwnVyYSEimAk1tIdibX0ZKLEAWU2hHlQZi1dhCpChdlwYTZM +ym00mAtmw6QhjpZ2SapUkip1NFsgIJt1QZykViuShgnki4MhwEGzZZ2E6PSfAAlNiyUwUkgdKvGs +uEQF8tAxcJG1poeQ1GegI2EQBAkM1JgMwgPHBdMtaDrVuHzLXAdbFqJY2EA4UOA4lEgMDiwYMbIQ +wZI3gUbNOX0uTEgWIpTK9smIoAYGis0KfCASWAEabDDs4VLaE5hrobMUeBw+8SCloCkpg5NWbEYX +jwqkCghRjAo0HlEvoCwKUgYG/qH4EVBvmUMEwwYzrFwPnSoFRtCj04igQRqhk8JKPCu2fERQ+7mo +SBxWxpZt2cJU4CoFJCKYyocgZhrgcDbr8CERGmbQLayIU2rpCX8Yuk45IeuKCFDgLdtGHhFUidgo +LYHlI2ojBVYgSQYSH5+HjmEDoVmCpHSBwpEiwuimccsqnQPNEsQLDBN0PCSfFBkZugAQFak7lQ+G +XyGdEFAACvQ4FAVOOLZIiWwZieMZW1ZReXTLTiy2DLQQ49RjaSoNqoVN5V0MXCoPnOQqrw94EYih +0qIwrAWRzVpE2wKIpCoxdMptyAZ0GaztgAyYViBerFhg4LcM5jJB2OgQ+lKQIuhTl7Q0xUFF6gAH +S+pzsrwWulNm9aGuEYT/ELg0CnypSEUPDLZlW8YUb8u6gZNcpSDAQZMRIGRTsVy2rEXaBISiINKx +KJMB1AObZeIj0EDSdAHpUtqGUwfRSbg4SJ1igvCfzkLwkeIIbXbLtmzLRFu2ZVuGKnGkiAxQOAMD +hxFFw8eitOJ8NDoKGEAezcdCp2lRcD4jlR5R0cDHtAwZAaDeOCREUMnL4ITbsu5iN1vq1YduGYHI +xMAYbZlIiCvWffTiORqw0YYlR/Nj4hEChEghIwSrbFnGw1ucAsLZ7OudFjqHi1fq4zKAgSzMdKDp +0uTwjykTMQyCYfC2bMu27CJhXEbDtdmAiwNNWx5AjxNAOAUVaXQYOlTM1bGuCljHLnQuImjLSAEu +J51ooS6Yy5ZJdLQJSGMLwPk8lFwsgExeAiSfhs028RkRWVKfBNSGoSN5XD7UwdQbbCJJeBrBddrW +VjEY6RyY+mKkIvWJASH1eZAgjwc2MLbeMgFPAkPHBqiQPakm/nPihAhKCWmJWBWCgni4YHXqccHq +Ys+nJoJT4FDaMgQEBxbIYoRkISqdPISoBx0UFCckhpbAJDHAeITAOU2E1YBQbKgL4isOlJNcZcID +wVWeelwqnVdJU1G4WGgqHBWiEIkPBMjrPxIPEAlcsVxKHWB5SXxCHAMiNq04iA6icyD5bFmpRGOz +Fq+LCAj1sWHQlmBA0AJytSCTBI2HjoGkELJbdkGVTECez27Ww6FRWlUL1WrBSCceJhIF2UbLOgMA +McN3NBoUFCfQlv2K1OmWbdkLPeyFPnSDadhUTI+lqVhwkqtYTAWuErIxfUfTtQK0Mg== + + + mUwmk1nQdJrRiLEDhshSwWBgVBgYzJZhMDAYDExFxgXjdcQyLJYKjEmFd0CITHrJuGQcBjMZ4Xi2 +7JJxUW3ZxZKxZQ8lBQ4gvGULDaxN4ZGGyD2JAAEvw/QnDAIMtmUxhfXEOA0eji3bqBouUjAgYiKD +Tarzjf4IzArBRcwrlRSGwVAeSh7AsRgXFQgw2Kg7qagCWg2dZpykvsMhMtk8viOWwZqAkcyLAzOi +6lRBNEp7aTV0GhFKMGxZkTp9iIuEPnmCCpeIlVQcVBTYgqDySnWkbBsLKfAl4B0RIaY4GcBgoRYB +y2MEcaC5fJAsRBkHA5O0SvBEkBqkQkYogEQHBtsyTYfjNo0ORkHTMqBKnWJIpU5dIYzRxotEafeh +1DFkDkoa29DpVE8MFEQacm3e6Tsaz6njtUvodLr5GHA0RnvhcC4XD30VKgS+eKfTLSPAaMJ3pD+V +VA2l2Xmh75QzbQ5+yzKm72g2I7GWXkzf0bwVR0s5RhSe1gdllguJZfAG4sDCmVZDpyYLT9B5pGig +0uB6bSoKlo6rKBhI0UBFNNLRVCYsHVdxBgNMheCi4li7ZRZNSYNeVLBIHbJtFDRF1WHoBjyy9sRC +RWqSTBeypBFGg/YH6jXxUABqFsz4juY1JQWNsRVBz21hg7QQwE3Egg4mQ5pAeQ+A0umWvR4OLMwe +y4HS6eWzpVUNFNZToXQaurBIAUmC0ECCIaEPjEarAJaMbK+FDT5dYFakVVc02mOkg/JYuGM1RvRi +pFONdPo2BB7KPYx06h7N0dHpCSfMDmt0XHSoqqOT6Oh0yzYCGyblGjomCodJOSgoTiDnqBBYkWCY +I1ZV0alHgiiiZfmQSnI6SAVMyCSAYyeKHVSlABYTEA6BAUcn4iw2UBWdljwCDxXQRHxH0yMQC11f +pDpFKZkDnRHYMOlKpDrdPCizXUOqU1hDAMdKFMdcdhyIdAgtjNFmSKlOV5lPqtMtS6lIeFrzoMw+ +Cpq2hCDV0SjDdwzf0SjHVdoEhu9oWE+jpGHgCECVliEkYYA7JXgkLBCYCx2EBjg2Q3PQ6bRgQ5nN +oKA4gbYMEzudztcIa1QSYtlsgpq+o5ELPCJ1pBf6zcF36koI4NhM/E4v+53lPcrqO5p3QekUVvGd +DUmqo9mgoDiBTKqJkWZFQaHRUYEmEIZqgqTUMUFZFGj0h2KUGhGYpAKPWPcJ8AgxQNVDKSOkapEs +RCmhDXZMKCSPw4ckJtrFgFIbXLk8sHbLRBqcgpZwTha6hYaF/2yZCk0noEoKajFRlgXOxsLbWDiF +OAPtEdqyioMQiQ9OLa5CchINDBCygW/0KmkqFwFCNhWUCJRCyFo0GkgNgVr4T8qHtQCqfDAelxQu +ViwhengAbdnAy7RlvGVbViGwdFwlQaWydBzKY7WpNIkIprJlFRzE44mJiwfQSSOBQR+JUMdW3sZD +9zKckD1wDZj2ZMIpHUCZePDAwIm3bMu2LIXCUTQhEy3Plm3ZlsUG3gRSMEDpPhmai8HoNZxsQCjY +wgZC5XIScCJigR2NqECHtQAiWT2DAmIF1J94stAxcOTjggeUlnasCgRos2VbtmVb1oHJtizGsTQV +UoCDpvJA4k2lHUy4yoXrtalgSjwXPYERBOGhIjXBVnDSEpAl9dkyV0ECQzcgAmLthwOlpQ0HCiGL +CeAtxtDTcxbRnVRUW8YxgYdyRqtYgBKTBQ2GCTx0ouXZshFnyzQfK1RniikBk6RyTwSp8tkRAkrl +ZAPastCHcD4NG4cFEMNi/uPhXDYbYDPqWI+fLHQDPtodZRY+VGMptRhTkECQIVF0CF2QQJDZsorK +o1t2sXBCMAzXsRiAevPiyDioxEgajNEG4CABkTIkIysSiBPYMOlr4jubAo0AEhEgjMyDh4PimEMQ +RFxQlHyse69WRgwJobQsq1sGu1AhUEARAw3moiISuli2LDaIU00FrgKzWGgqCh6rTYXkJJrK52Kh +qTA8iDap0wNrAzau1GeBxBISyxNy7BehQ57+8uVDd/7pK1nfwuY0GT9ufFJZfvOEUq7UqS+fhK5x +VJ2uuu/x45PQt2OcMbLCpxDydHcZW9+ErslX/whZPfpGGKX83dXeuBBKltNZ+cHoGYn2MkqGMsbZ +Ekqp+vLN6PxRX/4/fNEjw7jtkV/v3Ngf4YQSrpTzJ6v+v33JkvVp9Njw++e6LkNllhvlbJaq+pGx +Wj3+f3PP80HZEO7PV/ngN+dy30r3CNVlhBK+9F9vXY8xSvi0o//r2VqU3d7wV7Z8CvellN0ZS5vT +O8r51KXCyS07csOp/xw2VH+GE+rvm7A5ld+/v+9nZySaR7j+sNXbZ2vHV8nqsCevnBwffIavkxe6 +lC8+ir2lO6e1jM87WZf3TeesRHt3+c1JnT2hhI9lZxwqzWXkpxM6c6sydB1NPEiEVqBUCnRaQSSs +TBGglG6cUnlCh/JdeWHvW6krV77KGGU3VNad71B/Nz6e86PPOZXjg+zaN9SeEDJDuRpnjD2hy4/d +yu6R2TOU1qoL+R/sKGNE7WYh7R3O2A2ldIbS45TMae+QPe67axz3J0PPWFr7t3wYf3Z0ORsy3Mku +37b/zzm1SzQYPUr4eDKnSY8af1mu5LftPN/hU4XaUE7duXK+Sue42lbbalttq221rbbVttpW22pb +bXC1+WpbbatttZXI1bbaVhvKoWy4M7a68kZl+PCZG0Zd7VZuX5cfW/17evx1Xp6uvW9/1PfOAKCt +7Faoj2GM6qh1KXnf1JhVaJN3J/z96FFj/O3XGUtz6TD6P4zfPne+nbK9+ckIeUqe/TEq3JixpM7V +7V3uKP977msZtYVRm6OMLjcuQ6gyQ33sGTkLSfdlGB/UyWmRYzfsuArjgytVzuao8fGMGUtzCOWr +nN4cfXtyGkfpc8Y3PWrc2ZIf5MlpLxk2/PeNuzvjS5+cJmWE3i8lxyykTYbR22OMkZ/65DQJZT98 +CTVqLxVuy9c/uadPjsvtPqM/5Dijv/WYsXh1SugMJ6e15FedT6rUWOHuLoxS8nOeANBi/N/XF6OM +Lt2h94SyBKUbI1SFcTlyfDNGVHyWq9rPT/vhQofTXfJO+VkJ1Fb5HEKI+kDJuXPK+GL0DAUbfuy4 +D3bJcrdsKB93e5T8orvmckap0vcffr+orjlk79cXJeR654wan5RSo1z55vTZHifU1maZhdTZY4yc +Nn1OlhJ63OextYazf/3jNr/uyGmyI8fWos7eCLlb+9flnJ2htP+X+gzl7BLNdXVljBAqwxfZZZxR +qjPzQ31Tsib5Y4RQNoyc9tAnXIX9Usa56+2zIS/8hs6O0qC2xgylU58ufKlTwoev4UfWGTlOGTkt +Qo2981vjfN0t+edkyWlSsnPGkitbSk7rlssNo5TQn0PW/Od63Ahbctqyz/hx4Wu/GVm66vLGqLxP +/vqMDdlX3/TvCLlhr+Q0/u3XXdfVN7d3ta221bbaVtuuia621bbaVttqK1ltLFTKastKHrXaVltW +stpe7nT4LWdcfTyhNuP/XH/9bneYhVz293q/nnDh6q5G1tf3EmYs9wnj8jPLfvCj7/zuhhBmoYzl +yxvbYUeFy/Hjw6zEpetsZblQPo9w9VtCGLXdbunb89/HLMQcpYz9qj8fwzh7HcKHzz9mLI1j+8PZ +HKVslfIbauTnHbW4sNkbwjgnbN05p8f5nmOG0l7K6XI+/yykff/uT5Ub5z7rPuzufr2RXXJDqNov +csxKNHaNkqVGX+/pszvChv7co8p2V4lS3WdtmYV4tpTsELb89zNyb/QoX6vMSn5bZqzYzDFK6fOx +jBK65J46H5wxQ0moz1dmJfH94fLKGfe9y5aLIwRiVZqAYLFkGafuMvT3C1FQQrlSderH1wyzkGzu +fggfK9Rmt3ZkVo/9fGas/tZldTjn6/icJpnhlPFB+Q3VPTZDlfp+/u87P3yRtlnotT+yP2aYsTQo +/1/G38n9cp/LbxllnBDyer9VyGlTof8+jw/l7pPyM1RH2Bv5vcKsZM74qpzW3LMhP9jws5TMrkYg +TTMTlIZZLetAaarpa6plHiaj1W7WGUBsy7asc9SHRuYCgR/FMQ+gAMIRMjFAE8jiacBGFhxPBEnk +oQODRTQzb7AyP27LeMu4vwMzNW+nW9a9jtt0nW66DZfBbIfZslSmC51KATEByJvdbDabt1Ex2LgQ +DGajYrBltiwkg+kqFhpYBTDRxXPyCnkGMhoeFiFMyHI6Gnah2TI+4LCgrGIY39H0KpbBb0KFwN3C +wdMo7UYUwLF7UUDQXAHGaEdODkhKFxFQSqXqPqClJhIp4KTk6QQEdMW8sGnkGQhpMBMBoFDAx2Gg +NWIesQxugUY2QqaJ2HFcpd0yBo3SbtmWEZBQm810JxiuUIxOSjKRsaASSImhQ6mAdaxrYnRSGEew +sF3MiKCLDtSBZss4gQ1TtmUpAWIZrMFQIXBmQ+ChL6F0EBrg2IRKGgcljQYQSMFAb1mGJqBwHMVd +OB+qMX1Ho3lZPtT0HU2mFeCy4mhp9y76Aka6ThmpYbRt25a1QDhcATgJiWkt2AlKrAUD0VogHK4W +CofWAgBHDCSDtUhEOGARHIfa7dgRujbkl691Rvi/sXc67Kje6tL3XbZDKN82s7cz9NjQ91cny/ke +MqfF9ehyzimXX0/XpC7P/ueQOa1jXKgQevR9ckJ1lzzlU9i83U/1tVaOHmNzS27V33+sr0WNHTfq +zpVv32X3707J0rW9v+Gbq1Fla2/zi9NhS1efu3JG2Mr+r6Vr7jAuy7iSuZfM0OGbMXZDqJB3Moxx +4/MoodSPrerMPZ9/3XfOdi1KX+ga488nnTn9lhr9uWN/x45vZ/wIH3zmNMkzSjk9vobLcOrc5n5T +OtTXqbIXyhkfnOoRyjlbJUuFv/wTxsgq+bUzT7ktV0KFM7rGciH0fe5zVXJ/jAy1o/z42HtC6I85 +wm+pvMoMuyPc1slQPskxTofPoWtQQpVwPm/5sxv6lJHhO+9Ch+/+HnpGovlC6S9fhFPyx5aQGW7/ +urs3ZHadUOWM/HhjT5frUvV5uwZ3FUr5euP25J+Qd8boHP91boQQus6Oc8aFU8LuXgkjbytsuM4b +oUK5+x6jbru26j64cKFG6VPlv+S4Dl+/u8Oo3hP25Ddjt+wpoerkKaPUj84+57ZUbcn/2i+Uv/O9 +M6ctVJ/Nvc6TOe2hfvuc0Lcnz+kO9X3/4VNnzvt9v+arMHbc3eUoo27/i/1a3Al9Ze9U+bajlDq3 +fR/sz0g09tgfV2YsV4f9DN9k5jRm2P2TV+G6v8jcL/ufX5ss4UspfbJ8kpUljFL7M5KPk9nfpz9n +LC3qQvbt5/i4+Xvh9vx+3mX4YDcv5P9/fXNfiz/93zpzWpzv7f6UPyPRvBUuy9f/fQjbmdPkQ/nw +Ic/u5+w95UYJ4WyVsecz/JctH3b/RzkVfpxwned2VF3XJKtqx4Xs8nGz83R9bVtGfw== + + + f9txSlYYX+NOuVPOnswZa65O+c+sj/c15qkN4Wv2fY2hd9T5UUJ1ufLxfkaiPc/nZSiXp8M4Xz9j +afC3+eN8f38el9M2eq+7u3wfI+zXh3Lu7zevzlf5ZvyXuqtSIacxvztU+E9K6D6ljJLj8rZ0TptS +Lvwp4VN+q6+TuyyE0HelQlX9qQpft4zdPFvOqc6rurzLHCWwE4aLnFNlc5zcy/KVw2m5BBCRcvLQ +6gBdmsPZhdsLodQnOZwWCYgJBwqr9QBj9PfaQpUR+urCfpF5Ql7efq3nTumQt5n7fg+beeFK/rna +Mepjhg1fc49xoa/zTo9vwtd8xsiQWbt5YT8JPyPRoIzzo3yeUN8u97P/SxhbOvTdhpMhu0eXUiVH +lq/btZULnTtGyfxmL6e933LKJ3m5ZUu5UK4672tfTltW3ajyfSFPdWWFcaW6Fj1K6ZzGc3pkqJE9 +I9G6G/KyQ24uatz3lW+yZyX80ne34/+D/xLWIvEw4TBhSjhgHCkOpwUAEVQBB8yDI8WBhEMERcQk +AQXGgQLhYTJqiaSm9IYqVriSH0uVIgEYYZ2QhEwMPfZGyfNBr4C1UExaIQ6nFXOJkJCMOBwRDiln +zFjOM36vZNa+HUpWhzFjbXSPK1/Ul3PnMzeL5a/kyeu6P6f2cpSvHhc2fN6nLxtCuC92cxp8yFK+ +z/f4/L2/+Un4zfJXxo06o8qOclHzWWPzShIS7XuqfIf//96rof5+cp8llAula/0+5+o/61zun675 +s8eP7T77zV3v9SyksbqMPmf0/47z++PK2P4/Z//81Y1R6tM4pULZDFVdRnaoHB+2+3wRukP/5Z8M +1ZV7PUMtw+fanDbnQyn1f8L5OCokkavKUkr9J58zlhb/uTr/Ttla/MnekyXU+KCynLtxP0aoG1dO +hc+9n53f40LfybM7C2mw406O29tz/k/VGeVvfPc3fWdrs6c2M09uTvP+5/f5sjvGqS4/Prnt06VO +2S1nnPoz7oPOUVfnbJc/5fSoC1tKuLKZJAtpK92f45tx6kd15pX8XjanMWy4H2eralCbl5nd44Ms +J6tkl6tTxrmucUIpo4Tcrj9f3XtKToOqP1++b9+F++7fEEbt+Kbsl66Rl5+79UUfE66cM8r1qU8b +/nqETzZLOL3ls0Z9y7B5RocwTn7uUvshnA/1sTvLGPXB+JzmHB9+bDjZ39kh5H0an9PihC13ftwY +I3d8CZ95KlTtfXI9Q2krXUKH7f36ODqnPavuW3cp31+ErpHlL5Qvuuq6fHb4ZMuo35OlXC5D+cdn +Zp7R5+PmZ+afkfmtM8+4zhL+Qwm/Y7fU6XMnx7lv25XZH8/238mqDeNrZk6TK1nqyuX/pwwzFHtU +CSX3cj6E+7ZlbBij5I1PMnvD+aJk2NPZGUJmafOdPW78X35TA5VYqMR0RYqiJ0GlFEJohmZAggYz +EggAUCAWDQmFo9k0Vhh8FIACazwgMjA6IiAaGo9FxOFgIA4HROIwjOIoCIJYlqQoskwSBwDIhb7Q +jv4WSi9lMkF1sLMU2b5GFqfBmOn1SDfb0EX2Y5krFq/Zi7aM7bG1hXkGuoFIXXuD6mm/h1yIlp7o +EBZgtCM+8TQJlZPRjkAeGNeTcgrlXaR7RzVZ2PaNNHgoEDNWhDyabLZZAt2176CE8BS65TQhkNfc +wM4kwVnF3+ijyKsBX0ujNNmgcYE6ItRHHB1GfquotPd91XHgWZG3PLwtXvhwv9kG9rNX53CpdVCW +EOcm5oisC9gKXnPMElOQ2PGZaiyCSFYKH+YubmuIeddSuMADmRJWHkQ1didZtu9mGWHWtxfkFZd0 +69CWWxYzbhCg9x9MXFvLdvlaeSX+awZQipsjYoj3jjLZbbQSX5lwuuG5UpAHIlbY7bT6uCWe/3IT +O1wx5b9e2Z62oRNMSmfX67YQKwiC2WBfEwKhF514KPcWkk1602ualTAuSVLNyWvKQmnKJDVyVhBy +Ywkr1ss2mY116trQEKmZoMuh8BsJGLTFB60ikrG2gi0pBP7niMJldbScxbY0AwTIpr49LG5Io6IV ++8+2zOwlJxykX+xs0dLrKGCXRlENYDyfzjak24L0zQW+s82fCXTB3muAK7JAIJgkdsWJ0nh+QF2s +s8gkFo50FMwvfbnsSq4tN0ShvMJUUl233V2l4pN9Jcu7IVXoNjpt/cFBPSaZbc0aOGendwmzzfAb +JEieWIdtWyMpJsvj+r5tnO22UYQ2gswf5hA6gPj9KKujecumQYeAA6SjV932ZhaEUBNLqwTdnfVI +y+bpUnSXuejQGdcs54NGjH8gPy9iq5HSHbFAFzW0Wk7n8IIaPxc11kXSo4cAnbo56Lp1ecY/N4C4 +f6imRVv+tq0SaIonwVFzh5pOUh2/Bz9QM9WOhjrDOA4axLnv4OQv/nJpZbiFyod8TBqz6eHWsPOJ +iG+RY1dLLHYORCy5YyuT9d9GFzcVoKWnF7uNty05Kn4bbPmooFRG/rU87kZq9KTVfKTUvLSEgidm +2+aebtuX2HGwkOEqSR0+LZ0ENeCAxOb8EXIOKkpKOGEXhrepjCvIsqW/zaRNl2oUSBQ4fwD4WpZe +ClXZQjjSUAnJU37wgWk7a1vTMmXxKAriajXXZBDaSonHqw8rCn4+KzutXXWZ+8uy+xAesxYLLPHg +8nx6mrUObmI/ClrOCjvxIALDsnCLpEu8ISQu1jvcjyMJiTHH9rbsW5YPo+fIpHSWotJfS7jqUZ30 +Sv+gE6F8GxyllU4CoH47Y5EUyl3f1s0ytfMfEJKcdujJE5X5dWEZ+B0D4rJmY1+kCERx3AEvhPwb +TGEO6rHbqxP7LaeutNgdJ2r9vROu5V3R8XjiRu69DWqziUknKaXBG8eWC2K+ztdhLWWtIzo2x7ou +jweEUUYiGdTow4Em5FgwZNH0X0SP2Ai6kFOxumd8lb70i/Ht9uG+JHJZtFWDkZmzQgQG0FIH0Rhg +uw5aNyrzIfLx0+U2ZXqd5M+4lMLgJ4QcoqZD/1KQqlDhUHiQCxauV9eqnzJUdE2Rm3m46alacJVs +xOXvm012MUaCqwqCZeA7BC5qQRFa7U399IpX0YTSx0l+rc9nKWQuER0l15USSiqgPznaSOvO3P5c +eQENg3F70LgvQ0CHxGmMFi4d9U8AVvI8qm7VTDpoATd0JMEbtbTOhVyGiBjNSpgRKLa6rQ4YeDhK +fndemQMlg9CmeeUdXiahUMtH2I2fw0ZG0oCwqix+y5M1C4IqnIS9+6a4OwJPXEGu07Cqg+toZlEG +6Jy5AAikBWM4J4pleNiVxLiPILCjEgsdhFQxkIkE50QbEYAMxYO9wUM+yV4CIW8PmICMgomxqVf+ +m73MkIYHTve6KWzlE2Md4xBxXC0qVQ2VJkAnuM9W5ziMxPpnCrvKwp6sRBk3aposeESsKImpyK+r +enZlTvhmXyoXEkSlO4We5cOwFokUoy1R5Q2hVLOEmn1h7F0OkYhIaMayETGUjRmDT4wwIm3Qrr4M +sMCYxjUeZ4/JeYR/1fFNUUfKI9kqrxdQyKmVJ9IEFRkL+bQjoajyBnFl9TAkYfJxsmhyaEKySbUF +ipwawcJPw9dxxJ/pkMdSSM4Mu+IifKAzvK1/Y/oIe9Yw2HmD7Jjl7lzPbPdzD9PUjvDKzSn23ook +fifgVgnU/gqJyHv2EXg+uaBQFi8zsfy79Gzn0hzibiC/xzSabzvi9tItbdJ8RLe7RUvRfUnTL7ck +mLAjR8zO7EON2FrZ41v1ehjWePSwrsUUsDtVSadJoegqwFysiC9F+c9Y5uw65ndzTuuhAdRjz+fL +VxCMZ7MiQGsH6zJ3hPGmQFLuOqMJH3Iogo6oGXiAf6ZtKuNayNIb+TYSoTMe7UcUw4YY7cNHk/4r +aDy0li1nepDArXZy5tbvzqjSrZEdBaYwG0vgPbwBoRgR0mItB3/48f0YRTFSUl+5nhpc9ZQgWuET +dEdSj1iNlSS/BBmNgs3Z0euZ/G3E0fPuti4kxPcmB/i0ZBQhDvlz2BD6NozwusHL1ef1+BRO2cSb +KEjESCg6UNYf/jwyuUUlM4PCDHFCdOAKgWWVJEsLWrhNcnk3cwS71ygWzPwliE+eJxwIjkGWiBJd +An5XM8bFG/GTaTQ3GhTgSruhtT8KRycIcfdDpjnpxeCdib4IVsTmF8IQDAXpar62D/G/Alslc6Ax +GmWdhyAONc772hxdIe/f8O4pjufFyGIydBgdNqAEDS45ESxTIawGTTo+oI7+Sc8TBVgUp5i50Zzs +VNL0xUB43ZyxTLF+qe2XHo1ijd0NDZppMacwLreG6qWmsgjSQkK6GNtFYG9xD+j25cWEANSPRbxW +qka56pWHZaBd0ViIX95j5Gys6MVZipGf8SOHEiiUtPQe/oHiP61EqIacMMCMxre0XyK9SbWQiatS +3HR6bBiuDyV0rGdDPbB1MgvjANe6fThZFVRWMLWkWw+piKL5pn0MEggqLHJgBLBPSVOOHlOxui48 +vpdN2KuxF5EVQ1iX3+hMtxPEGp1QBUS+rdN8I5J00UIEgAZRbXQQwacf9WEbkDnoc0dBfBk2Y38x +0WZicHaM90CU+WZt0l98g5reWpTj+tfdtFzYQiFrVwBsRCjaNFMTk9S0I1M2crzMqpUIrN+Z/bFU +ShnLrHSunYzCs5PN2ZIkg0Sz3+6xRzsonNxsncdMQ0MB01CWJn/FAO63GSGSeiY0tJK3A1syMtdx +dcQgx1AcKGsHj15E7tBHuyMgAuP2sgweo0wReWToiWbSdlJD9zD1NYaftWf5nwi7OJHYwsEMWOhE +1OB/Mw30q8ElSCvlXhN+4J7k8YMgWnrg5j10ks4TCgF7wLEGct5mo0m2Vv8ItSwC4T5eoSVXvfst +o2YuVSFzdrEJWdd9b0AJRG52D1VKpLf9aZN3TW1JXfZd3f/KY5Vn2F8l5y9UFvDoJ+08FJA0WVTS +qkqVZqB1yDMdBBl8IOnwFp97aoi9QhMD3yhTZLtBIYQ2NytRYRECigUDnN5o5fTTE+MF7bMX6Pwg +E0i+Md2pR9wa6c2XqmiW2qfo5Fh4Y/hJiaIbZfZc4RwUMLYyoCqy87da4KnkshYL4k6R2KeViJ8O +29ymD1BYUdRmNtiwC6xx8yp78/yVdDF9VSx7jXRQXh1L5650TV2H5WeuurG42rU5vrfOpDxODbBQ +BP5a7Zdua2mtEgY7Bvoqp11lGHw+bBW1rDpg2wo6ZScQHvPLT9gRqAThGRuzoepAQJpvHc1KK0kk +NLtDMBT4+CRS27pjaIharEIL0PfJtg4lmagWiBiek46WVIekfb5neuFixb76qKrsqL50Nuo7o7ww +ZQLaJix7Hjt6ID1IFuojl1BDroyKNwBOFQUS5eImATHeVtz8U/msUX766Ucu++Rbk0/mXsyUqpZO +xXGjSJ3DIbZlz3O2J+1hbiIOLNCISxsJpj8LhEKranykY1z4on1DXLP0uCajioycXQ== + + + DBSyUyZNYsfo+QB1scgMBMhXc6VYAJg0Nq4ikg/nxjEeTiG8MlMDSbVX5/TEQs9Rgtc9udTayvLV +Sob6AnmFEEuK3pw/K9FUFyU6EQIJ1DXR21tAFXBlcjpWrzC7Wn9gzlfW57N7JPdJjYms07kxG5QV +R9hwecxjrwKrbjBirnTt3SwDBNF4XMRi0bxPHL6VgrbxMwgoYOb4J6Ylhsks+Tb3vzfWN1IQhksz +zO/hHSTcD5iy2LUCVs+GMBbpbFYXKHVRUFJLHedP0y86h9DlfZtCL7fzUb1EDTV1KGPJKBkfb1tz +TGvCtUBQwHp4B8l2ts8nbY/S4ixNrbMG9Xl4/kXdAhi5WLbDwukjtOhesPkaxIDkBLq5DBkCPZ9L +6r3Kv0/ZYAuybrIRgS3SlAQeR0Jvds8NBeDI4JqOL08n2h3KamnRPeTwQ9uAnQy5CH/cTKc40GTx +GEFMewc9SXMhrokwqORCS7+ALMW/Ub4m4zBdZdduZopWu2rVOl4MWZiAi9+mAjbNOU8aXxuBj4US +XspWKNl7XjB3imtYWlFnj8El61tEoEVkMOPmNQ1LTqAsVFhOAWRM4Q+lGr0tehxXghGGKR7rzMPU +qgZy+o/PYGoMLru8PRO5rDxozRJoM01on2NEOy7/iAk6WemoyTL0noRmT0tNMsBEMmO0GmsvrX8r +z3ZUjdOPejFfZthlWs6JlQ+INzmc6oWaHxU8hgTBXqUVId61Xd5G/5NM3H4cUeOdQTzQ2512mPc6 +IKO6qjDUpvmvKNTS+05r7SO+0wwlyEpJscOXDcos4G7DZ94VktB1wrc8E1S9gh6nXc7QDHSleAKa +Mf1zZGimvSq3LpaZ+kNFSJvpn+TiGCHUyrBYHYqwPZ+7rnumVb412Rx59Wyvldk+22w07m3ELdWm +D2yCyky3n43RbTe6TpJHvN0XQLshwzaQDcmPtxuigPNAIbo3MV+p5U3MbS7rgW7yMQ1uxi22yX8l +w5MA0WE1Kj9yyNlpmcjApiUortnzeAG+SuXIMVjCG3VhTTQr3stIdPrVFtEB/RVe3/SML5ZkJdGM +z1A/rmpxHH5Ox74ngee92bDlml9rAty/3ZXD5VoizlHuY+SusYr/QVlxX/JKGfir/FoXE27w47KO +ToKNEQpUj4tKBBQ19I8zaX3lyZj9EyMhSnlMnqp3KXjsVZjNTVWtCauwVB2lmWPEMMBAFatW9tL6 +9jZU7gQMFwm5RsBzHC7DmC6i0Di4GOwOXHCdO7rpPcoNsp651iaZAX75fgW9i1tI87a+qfUaFIsx +h4FOpH/+c0OoIUcS38QDWzc6cBkwW3CG0E+VQIgfnDdg8OMu6FSx4sM+gCBu5S3brb8kFt4kT1Vl +lDRmhREzim2F5qpWOoMNf0hEymiYVFY0c2171iSx7KAD3sQc+gMauTI1YDQBV3jCpPcGDCcEUmFZ +QR2fQDm/MynlNhlap/jGZEX72MiX19dF/StFxTn9aVWbAGtAbjIBChR3NcKi6PbDdxFp6arCRgIV +wF//hTAEt2sCXCxOaVdrqbg2dikAIJSprM4qvnrIL7Mz9m4fktesaX2iaF8fXFzdIDJHSBeGhd2k +IfWFATN9cOE+uaT4ME1NwhARyS9OKmMuHpYpOBSrMcxV1MkXh/iJR+EIrQ3LlCOYht+L5eishpq5 +O6vbQss/E7iqCh3bDb+ndyyuYP/1q6qyT+OPaexY4Jk3D1mIyRDs10DG2jMgZWY4OHzKngFhobod +Iuz8G3duIlb/HjpKBd7q2oxIGg73Ht8xYjaSioDuRUL1yxaJw0AiPi/66P/5iyedppMFfVrmxQH+ +Q+gg9CE5g25kQwu/dDqYlG8gWIZWQAqjrbtWfcHQjjHFTfYjuuRTFjCI0ADwBYNSkxAYc5FowBJE +IPvn8m2ejh38O8S2fNFvQEpqXbwAzw/b/ASV2deOA4LCRoTGS0aQLvIRYtXITAPhHK8NYBDKg7Dz +/4jYKh/0C3qTqi6ADohpOaf7y19mAaXgUsLyJy654hys/WcXsklb2+KBJEMkFpNjUoUEt3Pv9CA0 +g9fW45UJZxV8OabogpmN2bmhIfjZDXwk5rsTQU8oOLlyCoFZu7QH+S3RWwQXZ1xEpQURHHKglw0C +xrYbadZPpNSmAq1GoJEwSl61DlOvRpqgRxwPQ/z78iQPwnXzNpZUxlQElRcpzFEwGRti5QRiXQEm +88K7bCqNJh0t1+cNZIBraNQe6N5KfORRe1zxIrR706lRMkT6OHbiTSnCEGn/oet59N/kZUJM7c18 +SGe1T+a8UMnDSqin/lor40L9B0rorFXRpk19QUp1nKUseweLWWbxDf2RzDW3zNWoxYxQBQ/C865V +gfAqZ4cET7HhLXBwa5bnhq4lgEHVkJIbsvSBG18bwNFKcHUNMcUbCsZKsFLRxuJqSCXeUL9WAjA/ +tveBGyvB9X5WYw5cdFWiv+jsHTHAX3k6BPTUR6qMHyti8M/+SHq6sTxVSpCH0FPPG1VHMUIyX2fS +264XGIqHSqFGd0uTAH9d3XrVhk20akU2PT/NmCpY9HGV4fLdmOpUbqj46wkrDtmUPvlqvfHHYvd3 +RUFF3jtVBwJLP4Gs27Is6mc+rPMbU9oRE7QE0QeuAhsZ9w8b7eLUUeQ6fGf9wf9b4BkpkoiT1lQZ +A3SyL/LNDxZlhzy0hZTSeoh5kTZIub44Z2QEkzZ4v+4xhYnwT3BiVCXSt1+HNAVx1mX7ZDxDEy4b +Cesb+Xwf5RW5Dt0A+im3MUDBjdfuKcipcMZNRzF38Ya+h09E9EYdBiSWn29Yrm+RGBuno1MDXruo +RIph1vy4Jso6EThKfKrU5shYHkVuAh+htL5icCZUbeHYxKDO2eHFXu/YILDb9w55FCwysrf7PcTg +pIAJqEzzjRKEDTAd9asrz8nYOtOsmm/HnG9wZA0e637DN5AEguEqUYbql6q5IQC+F2wKkUoasH40 +8vCffRKTpNrIRqOnzV/GmbKdykNWG+iSrXSw2RlShm0CDBVUEAAoShUyp0l6OphIFPv0AWRA4pO6 +Jj3UtTSj/m+vI7mfIAdjoBjb+d3o3q+Ap4S1Kd8AmmJXKzh6zMdwU4AltlujrzFgS+oDrr3O0o7Z ++wkUDhrCbBSAAuZNXUdCXCZZOmwvl22b7FfSlxtTXbbDITNSsu8jdr5km75qTmn3CEcQ16zOGqyI +G2S5Lgjial3EDf+ltSjIIFPVLck+FXF3JYjrXcTtVSLmTyFtEBDXOj3XwYq4XrH8Z5Fpwci8V8Tt +orAkFhlQGNngIi7An8Ussrswspq3uIi7sf+KWWSiMDKzRdyetWqjscgsYGSaiLghqyDpXNaVyyTI +uSZgZEss6lQUcSvaIhvdfRUjG04R1+xkXWSRZcHIvI9YfYtrAqNZSj7WhGsrIln2s3+6TtweMHtp +QCq7yjrgtzKKuBpBXIMqXoyMgIgL+sOrI5C9kqrOnI/zLAgCFIcLFE0D3hOwRzc/5v7nisdCNpSD +OKWyUfd9v4hQkATJJni1EJiSEGz6oa8qneguwb2SjImOWeeV6ya6j3Be798+verZ1thE/bYasJ6o +mHGbad3kcpQYB1KmVD6YB9aDqYBjOqxFi2o0DommbeljUTKUNaCNKmdWJI9aEd/csdssj7ehFweE +edD2EZu78e2LU7PXSOgdTvZ6o5Aig0OC1OrNgzifNyy6qRquMtFti09JRFVrVHFtuao1ydwSy+5z +LLWgwNekOrYV3oc1bpmzMZPg8EGKH6dvGyEAOY4+URgVpyJXS6n7N7MgoThmH0cvP909aX5o0oqC +yQYsDKJz8PLm7utzZjd8oT+gtz9OOjbCin2VbF+hh8jfR0cgbpm1NlCxx0y5225bA1j3gPevTu7u +t0szEi1Kqp+UczYjTd3DW6KUG3lU55t+tOitXnjHwK/fYrBLD5gHVzB56e6nwJsEnCs4V3cfW9eE +j0qiKJJwdXirTJzwEhweI2Hlky5ydyCeDJLd60gce+7GBodwt7XqfVQRiGcXXWFJblLDOt0No8Lb +LokC8xLaoF8CBrSNlj8qn39lo0cgz1z6O83aQduN6wdPO1DViEI7l6qdnmYTIIi798oa2jaeDN+a +EMI413CUoQeu4NG1rVIpHekLQHDqbKkIoqK29XNbVa5Fjba7elBuXK8y4FOMZOsnwMqTXU5ATWD/ +BLQcf8SS7SXOKhIj3HfUF94gtW2DuqlmrvbEO46IHp+Q6wmaqCI5/nbsL5afc7sZrf3oMn5cMQF/ +BXXNDOWEPbLCjwCcjRQxObeIiGGpDL0r4W6A/r/lVz3KuAQZ4IS893Wkuj4EJOuMrVwh+n1EWmQ/ +3otq32t+FCovjqbU8a0k1pOYl2KJW5M6aSoGeFYVki2gnXAM4tj2C7QEloIFKTxkuemyV3SPJmT5 +2zB6i62bWRIO6iIG7mmXcEUx3efJsMuSL3PRa9r0j2rxnqbjXbMkhL5EBcPsqlkJiLBtUI5ZDK/Q +QeXdC/YVwSyrPV8LHjBxeyQjxzumPywrODSoRlb6Qg50Bfstx8c4/l3HbzlyXDjshSJ9/USyh6Gv +p/59snmZrNd8L9lW8RBBYdd0k9Rrxi8BTuirfOlr+O43xhktvX1NeqypQfq6Pl8DCX1lb6Uvq+zN +6rE5sjXSMzR4ADck+M9Ir/IckZOTiVJql13GwLJpAJyDMofs4vtbTHXJq+6our8KE0WxGv1MzzNY +72HeX3YP7Os/k1/Z8NhgbVOsogQhAqsZxemEmw+QZ40cnI061GFyyx0klt/kVEXLlxJkYumdOteS +ibICikSY+t+qCu5PA0W1hyvsJYbRgmP2PxQNKVPK9kFe7ZoEK0hxzIDEkkwJn0ibBjbjwSmgh2he +Kf+GXevgmESf8chQPXjM487D5JCgvOvTIUDsF61Y0r/K0l6zICWT4EQTdYxsnTsR9eGOXH4YM7Hw +TKxDM4XLHqFWhMO5grY69ACQi8a25qgUVvs8hhCR0gmckL/eqt0BKSXyDLD0e4EdmhEDJnF9sY8G +1Lcq9LpcsNscNIjAuMW851x80o1ufwWk3VFpR0POC4Ms0aZPofpY92+Ta3cXjw8PniWsYpBM4EYu +04MCgKReJskyQoKrWTGADCwOMgOim8EwXnTn2gEF+5oHby8X4B7y3WqYCXVj9Mb6toPwzNUztNxt +n2VQ4yRiULEZr12e35GISY6GL8Ia1qksOQ7LfOA+Z0be6UtuWr0ajQG1ohRgvNYEVkXdmvHt8QTW +X+ib9aXlUtNstAOVT31idv71Bzb8Y2fp7PA77CToQRgfTI8a4lFdEfzFp0jrb5IwqfWMKc7R9Maa +IjVSuF+BbNldWmlXHuN16wl16Z5hzstzI4zTCr80iLGy9E/fkSnvyuYcSXXQ8uL6OcGkn2gPVPYr +n+YR7frAcuaMc1iK/H3kBECH/OzkGw83U3fDvHe6T34IN97LpdzFz6tFSaw1BXAG0g== + + + pAg1JIQPsGTrO5N5jS0ZVtew4Zq2wcWdX7HJ0w6nVreXpQ0yaalWTIB5WjYsMsvRB0QFN16wHInM +QBRrPEd7ddv1MYcq77EH4BXcqG+Q7ELzdY7N6nMUSlOfqYjRqbnKcYClwVmrxwqc5rcPqTTvGwTM +p2uGvQiw2PPnr7sDmvGPZetfS/Ehbjw23ktrQINSd2XrBl9UYlfGB4VNX635go71OY4Kcak3uPSv +X8bRh5SJ0PQn7ZjrQcP7Uh55iscMWcUoq4RVXhMDK6jLi1X5523SY6xm1nAZ1pcWL69IEQa3wpae +qUm9ugnR1ui+wOO1nh2Dh3V/oQnhTWkkxQGTO34natu0TVR5WnFzFd8IKej14NnXEOnXl2bSkVpU +JbgSE6FXoMVsr78P2pFQdLZwU47GUDSdKHDakATjDBSJpfuSRv40lP6Fshobqud7VrYUrt1Q+wvl +dW2oEVJpW6i+raFoSx7MheJbGyq+LtT1LpVCDRWyGjcLNZM2VCSkWC9U8NlQ9iHVbqGSbQ3Vmc0B +XahjY0N1vhyChep7DRVTqnJYqBYcjtOWwMR0JSEqeoFd3a0MYEhvAgOiBqbHZgy1l0YdNmz0YjfC +754aKZn2ZFG8Kt2fQvHF9H5uj7foHhNJ+Py8vKmFh5bZlK9EZsekZO7oX5Qyuh8EmGA2L0vjv25O +Qls6L2gQKWNjjNYMItkpv7R0o8uFmAqhm5p0GLD30HAAwUmdwRBxvp+jKJK512umfsJKfUXaMmUq +qU8x1BctcRkLXLhyjaTDGbc6TUN2Wc/CMBqrEOQlzqxWlRonacAavrIkC13amYMG2yLCaOCimj9D +mgSgKer1OFIzwUPLfHlDpVGoZJSuHdgMUauC4a54dFN18JncnAgPV7WXG5hNswh0GDqY8HunKS48 +c9FG0/tVOuG1hVVPxaiN7XoRRy39kolasp7xLueSvOSYDzl7RkyFVZiSWZDD3QHEWfThGZqJ9N0p +5tmsVnJ/RDxeNxDM9ZwQZftLC2EQGxT3iwdgAxbIHku/vjHH3O/MHIFxYTpH+zE0kL5nAOq6nQpp +QgL1CDRqQWmLqGOwa1uEPuYhMNovyBmZoEvJaFgMJIx3AOYtwvtS8NHEmdAv62ORMWUs0PG5cbN4 +vPaf6rgIALJQxFJjanRcZH2oh/rhj0KpmuUMo0oq/CaQeDRjVQ2Ap8I/xpoSgbh0gGBt2CN5segK +s2nLL8q7xgeEaipDlO6GSOc42gIOk4osU/MfSoJUlUNC852zgrEOfB3xmzmHjMFY7C2pqy7ADELO +zzdujGCKILeaOZYP9G2NE1HL0E83IFaz+xkUDnlNaQIUzLFyrQeHJe34x+4CeJ+DRC20pdNC2e6h +mBwP+rMl1lUTJ2gFHt/hvenYMBkVTZWAKhccbXNED8/tT5VSUSQdG7z2ff5RRiAO9orvMSWYgKot +NVK1m52yMomKQZ4iiTFd/xHk/oicq+FaLd8ERmsgEA4MKPv6sBOD+KWM8XDzFNE1RzgOMguuzC8Z +AnqhXDq/T6Ih3tWNUinWN+9wo/Rwic8tKk1a7bJbV9uumG8e8oT+1hebCPaVZBb9Sy4wzzgpRf5r +/tV2fNzUzDA5M4V3CthrBqR+LhHFHpuTOCGj08/M4uEFjVSTXG9gMAgc3U900jxgJJFIHp7jFW7p +QmC8UpJWmN/sFMQrKeQn1ROp3Eyk62pmVzzRVM7oL0TJNJaTZlYF90saUhdpaIsCecL0EJSj7DgT +kx3+Ej1MfIwhnGeF65biMsUExqKHe4EPWdkCU8lNBmZSD8I7o54aPQtI5qVFZYBD2IMqRkDs2/RR +AbhbpTk2CG/hlFvxsq9hGphuhjP/c4lAUzvjMf3rl+FdNZMZgp35fFpqCucYyTaItj0qyq5Y2nRg +srBO9MHvkN8Psmmww2q7Z2pOJZFJ5TIk2IBbwwaw3SVdw+9l0KqNieG1MQryQCmoIed1Brzvxwck +Tj6ApczCLYVr+FdljwU2V7mLLVFGJ1Jv4ywL4xMmFHd/DyPO90A0aQJeibSrtbyLtrtDLcObSGUm +id6WKtZa6Xt/1Hm51tfBBhYmcjnEfTXlIWOPo6lWI4WoGIob/9I+wAKWYiN57FJpxq0ctfuepGrm +H5r0txTLLQPF7PwwDKCAM0GV9xtLHcakbL6OmNTzjRpKTLBJKxbM9fiqgKLZdUCAthz480Uffk// +aC6PVLH/AZp5cI3t9481n0jwCq9m2tEsZK9aqdOSaZPDXlV6RA44KUw/m4+YVLfPD2KWMPQiVfTQ +gdbdg01oyUSzv39Ux1vFXxI20UqAFJ2XYu1zipqgtsYJUQ2dRG2RMcrBYoIVGbCO1JbwPW98qCLr +Pj2dHE7XIagAqxwsLXgZ6vxeCGLyuMRA74B443IcUB4a5yYzmFTXaa/2XTH8MyUDTSwVGT4F8JP7 +XaCJLxGQtRJU4rufx35FUdmtjs/aJvgsAVsFwIhhnRa5GVm6u2ephMX31j3ZY+BstpK72SbnB4PX +d054Y6C7v+HdXahpnC9j4h3hDcRHQYwUS3d3Jm4hWz4SOKW7Z8JbBTDjcJslMbG7reTUYMcSB/Ui +eF/kkiuuRnAlUe+aLfKTZ5e8DwjvC7ibht6zmkuBz1O4blARWg4n3a3Q0FaoPCU6yiJaFtV2pBKR +ktsClWu+S9q2dcAPltmMNmq7d/qDKfj6JW6r3w1I7kFwSsg1tQ3Hptmo1SvJnLbrg5vps0UCaGQ1 +kzqx+lKsaNo2yDcoHfNzvj5dKp1EZejp2AILQBy3J2VMXtt7FEASbodeL8XU9tOmfAHUIWh0/6Ol +bfxQrgdM/ldrUKbw2hbRUoRMk5lIUp5+VSI+4jakirRtRi9ZjkhILPjEqj/QwmvpS4/J3bvGwo/f +aXa7CGpxbPOXpublwD3gpJGoJ0MVQEN8DfU1GOLAu2I9gujFAr83LZJo0poxfEXtr0Xkvk+nOpP4 +I6vrjWaTZsS+pqTsdPw+JAWgM9PnAQtxEGFiFcjS7qgSgXdJOsAQBMaWO88yeBWqEdpaOu7ZHxxs +cZK28YbtRA2rNYcl60jvnLxjS+/3qX0nszKxsNoI2nqLJ9ZcZj2tbD7Eb6t8bJgAG0+exz6lmRyW +vYYfuDYqV8vzajMOc/VYdY/1+YMl15AhklKwKYR5qb8f1UYiZ4jnuCMrnzWRBfmTnqONFHq0HR4i +s5pr2GZkuTvCeL7xEUcVOvqZLIy72qRwadkxS1kCWeO9JcnU9vpFN2ww1iRro96bIazWNBOHzDp5 +GFzZjBry2pusVafopLrLgBfv19EPqiZ26UIo998Govbep4DcF1PyGg+eae7FvLW71uz5rRlfclqH +K7QYlHPpzwX6CxqfOQn3GYoN2fj6ITZxvbAyItpMbR073cpRZf88Hg0Oc+SbeeD+ZqlvekJKtkZl +rwmRsQ73NVrnV0naJnG37vW3SA+iMEDboYC8TIOSHBI+deVKCwu3YdN1dOo2sbpioxzMvp63P+m/ +vVm/MCxxpGL8y+vH49uJSuWpDgJ+uKWGVFzdZKWxaqwlgRIIAH/UueNTx6eaHYNq34iOdS0qbqx1 +RlJc6WEqYluDEwCEveGkcxiJ2NEoVtM3FpdLZZ+M3aJhpA7mbIKSsWrOHBVi38N9hpJnsrLsagqv +V0jZfgfuGNqzxdIO2tZrdWvSVVislautEGF7HVEzHZvhnrDcnrtKIaWzPfA4CbmVyHSA3R+8JVEY +xnok3zZtuE0yxrnIuzukfo62uhPPMmI41zudS6EuE4YyebsoykEx+skuuL7tZysMItgBoLVWIqdU +tjdxeLl3Gbk8AKhtbPlXS/5MF4b17E8XGMoRRrCBckdI25DBp5pLC4Ja7PrOSg+Cyha/Nfdtm65d +wn46VpsIl/KLMXFLmue+v4FMkSZRsExezwZHkR9nCmi8bgOxHpJzzxWzibFd2gIrIiuNjg62ymZn +bhVutBcBJl3rDgt22NeNnS4pREc95KjqtS7mLh+g4NoruiGQSJiHMvJsWpfvBjaA+37z1ae0bHZz +6MfWhYfK9WS1udynHvI6Oe3DkGAoMo4t65lUpWh+7K8/gSEV3FbhvmcCB/aQVEx2xFb9dziK8++q +x5SMCIPwONef2P9jumUzuzpqqifGEasHnTEzNkPwjDsPm1LlOzYWd3eOkQwwGPgMvBgTDrkWac1x +qC+LvINRhy9QcUoLyBDatj1H5Fp13/O62uwEPNfVyoH9g3MHMIf0pZrWRTonpg1aL/yHpM1H6znt +3nXgOpZW7gCo5NhjWAK+T8D4kZMsfH7Um2tY57YYXm2bOmA4ePLEzYJ+JP6djtDysmhTEqOxlo9I +WQSSTb2WN5vaqL65DHaUL3yEQNL1JU3V4OPy3JcksWz5RrpWmdJTkPtQ6HAw8nHUOXFfbBkYnLWZ +6rjOsI5gX2ygCPpUC02eB9PjlKK65fA0pQtxJSWz7XJfHqAzzrrEuF98u6aZFDXLq9twizfVq0ZF +js1WExj/smzp6HZwP3DHVQfCKBXUgTizi946rRI6X6uBUj5BsLbVD8NIllaJWy1P0xjjWJ0OOfZD +8YG4v2rl4G3vX/Tocc3pGQzOkRsOocOfBCk+zGDO3aStUjxTsdxojiC0hbVv6t7+yqIYe4k/FsvR +Cb5DRZq9LH6XVIUttOJs754ffInJ8X1dFxIeL/q5DvuHfV7ubszqM/LSGC/70nHBsPIOW74dBHt/ +YsZl6GY82rkDqh/KWMYhqWQHIu3c8r9XAZKKal1iSu6eMv7mpyNBZtP4rH0KtL2QKzOcKYPWFYAJ +tvhe+rd3Nd6I9v5C66cP4lB4XyHLtQ8hCdj3ka8lYxDEmMlh4q7H2kgeDBFPlRIFVHRBbGQMF1tV +oGmhMY1SPCV86xupB92QVEXFk8MKZMZkZUjEU/hVYaitAtjNFCGeZOiUnlo6WfGEWOXG3C9THXiV +4j/F05u/KpGlwxPTYasyqDGOPRTr4KmvoNwWnRcMIGn058/0Zi+oUdrl2l33i0PW7kSQhpM0bvvk ++hlAThunZkhQwUdxzVG7tC4ms5mnvmTN1WS5Uoru366as4WH/isJ79X8/8Bl2qzhZvW6NWxaKvp3 +gDqtVQENR0QhLltx3mpOkPt9zYXHTsfEb8W6t1ef+ZlEeq7pmOzkgFowXnl8OvyxAG6edYhphKyc +eh+JV336WdznQBsFccuz4xK8qibSvi4scHfXmpg/l0akWEtbzb9POSL9Rv5DmCQ4X6/rEEjFI67H +qTvTHlRkD4QzomoZyfOPKXNv6Y5syGol8kOXHOutwZLbohNU7F8W3pBlKCGPvHimeAuVrYibZM6x +NxKeDjNe4mXjm5NGQxc/mYY4c+kgeqVnE9nrm73TYNNKsuAUMeoJxfAlAWcQu0YVH4U0JpbwKmz7 +KH4mWnefIJ7PV48n5hRjNFxKH7FpxR7gi5Uzrj2epuniXSaOdaIr5hPh6+SXCIg8Vg== + + + rY0nxwhusR5PqEt+ExLGPvUXHk/eGfFSZ3Ck8YQ1PNO2izfQ/zjS93hKybhkRzSepNXDdZVEmxpP +mCNLy4CCpaJNX0UlQNJ60lctEotDAkjwJhhBAyczh+pssmEbNKEJcHlYh1YVC0Q2xOVhG3VhQB0A +mAu8C+6FxuJe8PaRgyZEYyO5zCFpPh5IVGhnl5lP6gP/C9tEnLKiIOF7X6m5ZIxte1spp9QfX7TC +Ces1Pnf/D/l1sSU2zrx5VE80fLkcD1nugOrDzLnd1pyunhus7uxZVQO7liJ11R/YgM+F6kkmYWSD +21CuSxx1mx5HJhaCJRuvj94hAgiGexx8k0G7dPk777B/KMnvlC5/Yi/05OsF4SbahqFVX13kkHNT +RlFJqV7GGwi/YHawtl0iFpH5V3HMMMWzKYw/vMHIB+GdzD2OflkoAJWKnG4QNyx1LXIFSNxsQN5e +3xXAuS0OOJgoMT8nbZtXSBEfxJs1vu4MqRd6hXSPT+srP1UEyoNPPUPazaKXW4KeiGcLor4Machj +KAfg9hduKhwRR/NtHcCD66xwJ0MAOq7bDAvnjGqERqYAKZRmcqqPhWuXEmbPTFrQF85RrVVhZ944 +TfJ0YboVdRuDCcXAgC8xygI/Mkv6aJSMZirOuuLWzwrakvEY3i1HPUHAsli71SlI4hy4JYlCBDwD +4k2hvxsDg3qeK+gruiKd+vdfzkHUUiZfuV4/2N+2TtnbM/H2cHnRl5zzXMCgVNihwHE1WW1BBMcl +9mZ5CTwomdF0t8hLcPJxDZQU8JDWhvrjNDr5ozSIDRA0iVuJbbsHK+7gl6kfaiuLPclw4kA5kwtL +kWORnG4evd767Wd+9r5CCut4QdK+QMlSiwrPhkoiGok69figDPySpuyPYNCeHxrJOTmxiss4qVa2 +HF556dxauw0fHuZj/pTT5b4naZx2GHo4q5Sg03UoF5CX6xEiwhCRwDpfGENQYFjOhy5+gKnKsIxE +JwlCGF1DAv9yBL3ag9IFV2CQsnrULfTvtEyYSAT+3QGYOu7ZGkXFGOJfhw3BOf3Lwidv3MzYmJuo +AcB1SlaKPPzrim4WIIoAu37VkUPITRwX5qaIZuxClsze3ETeP8gO/KtOcax1+dr2xvwsaPMMVk2H +QHPTKQ71WYx6iH9t6t9C+kDQvqcB/FtcSbDTqiIG/l27m5uynG34skSrSdaGMmaNf/0lQl//ilbU ++uz6hwi5Njb4V2J80CLV2cBljDhqMh+5F7LiX2OXT3ZxqJn+bdhFB5oDFCKNJgO6+sb0+NRRFKXx +caSUVf9muvhGLMwIUgwL+glKGVXxL5h5No1id56CDu3RLaIF/k2xHg73YiI+GsC/DcvyQXoasQ+q +56e4YM42h9J+DOSfrRgbsq7WgtwxMma2EDNQ1wzqZBo7ccZ6bJhN7xdqkdTEhSk++35pC5MRKTPN +2s4hwDUM4LQryAgxSrFC005xOgzqe2whMsbMxo+Kua/YGp+txSh2a5UzUU4dVKvOjQXtLLSvPjHv +qVy1Gjsouz6LiEoUaAWBBmBqhzMtdm6xhlJfZ2Mv4Q02iDI+NFLSmpOOmHTm6Lb2aC8Y2oIU7wf8 +Sgho9ipPSTIXVYhzCfeYJMs/X8dSFN5f361e4wxTLy3c57vQ4PkncSpzS91e/37gy72LlI/eJzqn +7rUhWCSrsygWeaRWbQwqKZx8tWFwfEpsIM3uHb3ry4PdC72QjmMhC5ko+sRwjrqWQnyUtCm97s/G +yb5Fd1+EG6Vw+yY5+U0glb4kBtLuME8xNAAVlbPzbc0bOWCJp8K0dcUwUWGguYc8eiEFAhwgXhLe +TLMoIc75/5qs4HD6tjpFpacQJfo+Liwho12YMpCUyj7Tn0GhQj3fKQdyCktKsW4BJSRVMMGXsgVJ +3YEtDhFeDw07rVDTfxyaMag3iL9W6r0KglrahV+9iVBvlYERWdoxM9SbVxPvmNTLLJ7TiSEXnilm +jYKAegmA7uO9OF/Q1HQJ9Z4DMAoBqRe4Eh138pZjPkApIbwCqZcI9vtDEMI21IvueTK25X4rMkGW +3Ipzvx4csOt9vZrZa+j6WfrnS1aYtt0y2CiMyQyhAnuuGBE9L2DZ6/8Io2IRILu0QzBT5LswmhGf +UVvHQ0Rr8oiW+jIsPWt+Vg0HDaRhOZlmZ1BaHeQ4oNMG11gm8gycMqL6/RH1Ayt7mWuqU1+MUlDj ++6whuJbxwYJ05l8gYpgBP4NQsYYYJuuKHCg5NRjGR6FNlX5bgy2bRTh2LGDKWdjX93bCFCQUaJE8 +U4ViDP+94r67ZITb0i4Vw5d0lGlQl8RYbPJFBo+W/CmhvQgMYuAUAx5lsDeliswOL++tK15ORFp1 +HyzQiSwm3LYTWmLwMll2SUleuns8SoKO1y9wPivYAYWDPfqsXphRux67rrChk+IFP6uRczPFxO08 +GCVhNcVTptq+XdzwH5RWlzeuhHssIofmPDgf0lxBv9v4TSGtw0bYf+Ndvccz13spgqvalJEBVAqK +q5ftDEUsqt6bLkAcjI2OmKpez1xGR+zGVKt6HbqpWEDE/Or9xjNz2JZhzD0VBQpvsgltUJH1C/+9 +DKYVUcQ0nb0eEqBZb87VSwK5kpG8CdBUvRwW5dW7dAYjdaN48zXNWLaCQvnqVZsxMhLHPqre5NC1 +PYqtvYy46sWeoX4Uy2GNQftiGuDue7n3rANWyKJv4lksT1uIqDgSqLCzKqQxdY1F0WuOtyMzbLX2 +FSr8aY9KfGNn8x9h9OPED3UFgA7GOMMvgH6JggYZN2QpfBwDu2aw54LG4muM8L/xwaDWpS1bQae7 +FbxNHrsJIrK5tQNvP6oQVbePByHYDRijUnzrl4jXdVMTWwZ75TtDAvoUESqU1qEtVAQknE2UTZRp +J3FS7tK5SeAk53qTjhcLLeByoLZi4AyVzLBcRq8dvnzBT2BRSYGMGAgpbjx0xBXgbpl51rjyg4ed +wKPQCu2PXFNOnJVDStWnPoALcEXY6eUrrgJwKy+ePCtUVLuD0NOwAcKVP6xBDYuY3SU4cPHclc4q +dGIZiHgimFadKV0Bv6Th0A0t+mk+MugIpiy7Ln4roVEM2E/1YCfGiCfVSMmUqxTBkZeQkUQnYG/i +3axqRHx3yTo/CGZjjUUqtNvWMMqFAAH031/8+Z3NQz03A4UkfknZrBOVJdTpE2wLEPA6zUKcPkpl +dkZbGCl/QyxNE0DKcPbuEm/nQovz4CyrxbtCfy2+3932v6YBnLVvONTAuzsiqWZJo0LLgRPglXdw +YGYPK+hfo40CR/Lbv7v97VXjF5SzeFfNTm6W18TDn6y+Hu+KzgLdRy7lONR+vGsr/HDwJQx/d4uW +WOR4l/RT1u/uESgweGUQZ847AapAif8OhISCTIB0UAEu8EvAZvIh6PiuKTE+ACiHqPVjKxcBA2eL +n84zYg0vRIQX+2u/3eTjJPkaRlAjFgyHYPmcb4OWSaOsOUtdVVGDLjH/RuOwkdZqrHJqg8OmmQat +FdSnH2vTW/eXnRky36WoP4PwSwqhvm03MdQitvGUwevZzjAPs4LXKiSlv7t1QJcUbt73PpfSoewu +qCgJy2CpCwttLfYOkiXZvEcd8vHehr0tGD7MnVdYc0SEJt7/PL9xJ0hRYe35YV2mADTW/Pcynblc +8GkMfd4x3I6tTHCAbO8QktSrRGN7XZ6jzLmDbpWM5byXzim2uvNTQdJWeMtksbM6vDEuhCKhAcwc +x3Cxl3gIC9sQtO9UKVvS23Q1iQO4a7YN/EP6rbLMesSEthLJA4v4t/PejuZvTCH8jrbTldZDabj4 +ll4/un4flpkMt437XUBK+w8d/h6h18nvsECgE94t7P/5sJPE7ry6kA9Vfl7seO8e4f/JK6WduZQB +77lcIZAJMe/9BjO3/JL8sD98QbhbNNmm5deKBHb9ewt9L3c2Sd0sfeySSI2iT6660lAVwB0NJb7h +Vb6W1lTJy4+4AF5mkEOcDfmCdiDgl3E2KZUAGGZDvEGSxwAvIzAoBXszLd1tuIoRX8MqqkElhkH2 +siH/JoKsUmqJ1Zle5hX0OIeERbxWSEJBsfLEDgSAko+WYYIyWtlAsm/tAeDQPdzr86817M48pOxM +gmuxc3jhzsAlhltDrxSQIr569VhhmZP0GckVD/NqpTq6NacJKro5/uia6KJhQD/6TMNmNWkwROqx +tEFlq2onGUd8sDT/tjxaHV0KinsYSWgc3ZtpEOSScLDo3pl1oWzdkfIi83uO5p+bWfz16D7nlMVR +R6UyR9dlV9gORR7ZGozVFz5/0LGU7lK078zXx4kjlnr0AiGR0gZuipSAmmL0ZcTRw0TmZKInWyqI +d+SKyRPGwY56pAHFCVN9r118YKGVpLgUR9gbTEWucsTCWZ0ZW1axgmWMjLqCdSjWOWw/n3e0thVz +kzxOqQrEfxS6o5KviiU/7U8XUr0YRM3LwLwOU8xckS2HIlzO6/sWiudqdmk0h4BNCpsjBrcJY+W9 +XgnueukCyw8XFp187C45PRIEODGS/4u4t0qEU3mpUoBq46l9yQrp5mYxOe97xYPju1MDVqrV7/bt +HyuCjd41qyM0joQJtC+gVtOgdXmV/Byo/+v20FAfuJ0ueA2bqjblyVyM5Ev6KmsdO2xdFm4TUBbW +LCgGH1VEQHDdhRgl1/fWBb7x5PuxiGqNJx0dMCqA7V1NXR1q7y9RP64OufGiIeL0SCCpjPZPrBrP +QHLxrgT3NtCJ/td5taZ4etsG0/AjudlSlAMcTjKqTHIR658Et9qE0QciAqj8ZFu/ynRL6yaQ1QMf +JjA8mC7FOiL2J9Zo+K3CSB5G0WjKmC7JPFb9pSWcihJMV9mZ1bg0 + + + diff --git a/brand/Trivy-OSS-Logo-White-Horizontal-RGB.png b/brand/Trivy-OSS-Logo-White-Horizontal-RGB.png new file mode 100644 index 000000000000..06587180294a Binary files /dev/null and b/brand/Trivy-OSS-Logo-White-Horizontal-RGB.png differ diff --git a/brand/Trivy-OSS-Logo-White-Horizontal-RGB.svg b/brand/Trivy-OSS-Logo-White-Horizontal-RGB.svg new file mode 100644 index 000000000000..b3183574dde0 --- /dev/null +++ b/brand/Trivy-OSS-Logo-White-Horizontal-RGB.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/brand/Trivy-OSS-Logo-White-Stacked-RGB.png b/brand/Trivy-OSS-Logo-White-Stacked-RGB.png new file mode 100644 index 000000000000..0ece65b88a0a Binary files /dev/null and b/brand/Trivy-OSS-Logo-White-Stacked-RGB.png differ diff --git a/brand/Trivy-OSS-Logo-White-Stacked-RGB.svg b/brand/Trivy-OSS-Logo-White-Stacked-RGB.svg new file mode 100644 index 000000000000..52def3a62ddc --- /dev/null +++ b/brand/Trivy-OSS-Logo-White-Stacked-RGB.svg @@ -0,0 +1,3179 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 +qt4aktqds6qqLW8s9lK9PkuNT9Lag/WVrsxIzq7VtW5ke0qRm/kpIuNhDZqYXGjf0aruJslrwymz +Exyjvcwnz+LYVO3oI/GLYmOE4FId7f/OzszkuuUQ1eEV3Szf1BnN/WTqxDIeq05nW+vkLCmZcsRH +iZl2PKK4Cm/nex3JJb9rKp3Oc1p7c1ValS8p9y7tPd2n8zqeUjquqiSXLgah0G4khT0b1iT2SCxd +7zSZ0qsy32Bl7H6GQzWasl/t0izVNIdGN3cOIZhHau5TelSZaYVVNg/VJVHdf5V582FV1pUObUfL +y73IamIQDAXrqxvRcm3ttPqRq+xGLZv0XW1Tdlem1Ch7V2ltrpX3dPFKTyQjMrP7zzpZLssibOnN +29RVk5p1dzV99xlZ0b9DovSk67OzNc5m3V67NSl/eXOFFzwsnNZY2n6W3RVrvb01OtWmPGsv0Uc2 +NjOyqskP1p/FI7IlkhnW8ExpLsm8B1a1vesZ4S9vabcrMrrCjk2h5WX+TndlxyKOueyq+ezr3Cyr +fuEvCyUnpy9eonOuEJ/To3Qjq2fdVbVgHsXLl0csDyw20z8jcR6t25Na8yFDI06PeqYrZ6ZaVVnP +eEIQWmIzIt9dTm08DjXnprs5O/zcp7CkDM/1qV7OPgz/z91OkrKcehY7dyvzkdhMrxJJXe3omLf0 +SHY0dkevH8qYj8OXzz6WtWtO0oumYu4M2mCNPyxEX1H9PIlXuvV61BFi1U09yVbSeqCn5iyHsVx3 +o03qujKZw4Tkujowx3U2Bznnl3M0Uj6OkIhM7MCjOd9L9KNw8l5yc9+VVBVPdVMdql1mTySURmJU +H6WdUc/SXJk1I3QgXW/3fIUOH1kzj04d3pVMiBAeq5uqvIdlDomPjh7aZNUI/ubjHesu55aZHVba +W8vjHYnWSbmjbYodxxplb4k4qjzsYFEqsYhplVb81KANni9/bz91PSP02Ky/MvpkV9urkMq+K7rj +dO9omv5aT3yNquhopHRTrPparfZaL6xK6arXKlWLdTobZ4YohaZIViMsp+taxaSksik+acgyeKfP +JUpO/Zx2ZFp3uzIx0e7nlDfvlIm211Wyks9d0bN1q44M0dR1ato8iY2d9EkyqTIhoSW+VT+oQlY2 +NaI3b5el8ezicz0pxee6e06j1zSGdCupU+HHaFj7U/Syp8z1vrGJyPLoLOuosHdCdy6SzCOT3qI5 +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 + + + qyGM22Ad+OfQwUVNJsvOdflF797C6Nkzt8nCXNC0ZC5tZWPcLhNzYavHsm43Wbbtk2XP+vwiZzbj +fOtsHO/d30nEs2X41mqex73aqNPriSwNexL9/s8joDfSPLSLOgu9kKZgxzm86y92aNqXuruMxutG +A1kC9h9Lvt8zuVfrKPLRRqCGvYkV8SeNGt49jny/pzHvxvnWfRzwnF1D+OaJMgXvJ9ZFHDDdj+FT +o13g7DIawLh66dR7J/DgaA9NEto2gXZ+pk+ululbq13S/NbljLvNtplxdcR3dg2hXI/RY5tj9tro +mDy3WYYPbuZh1PM9lHn9ZQ5N5l3WZte6skweW5/hg/MyeG51DN4aPcPH9mHk0uqs2pddujPXZW3e +ygSy0TqKeP6m8I7madzzPo989g5i3q0jruvK6LV5mr83O4jyzy6qPPxEmIT2Uun3Hrok/DeGd78m +MO7bFNL5GsG5blNI53XAeDYT6femMjFpF1Ue2jSAb95mcO4XeQ76HUS9D4OXNuOA52yiy0F76TN8 +M4GCu0abhbbS5/dO6gTbPZB6Ns5hnb2DqGfffOf8TB9cPeMHVxdtHnYlUG93SFKwGxQZeON862wZ +wLbZBa6XnXHg8KJ3c2cI3eisWy/jWgwa+IYJX3yLQQO/y8DMysJg/s60M2E2Lsye2jYb98d42lgH +bqYNiwlkG7NJjOPagOnklzu0shg9Nm327SbjfWwDJ8OggZd5XeA1GIasHFcsi9cVc5GbxXjHsIFr +gDCBj3l9ZfFsspi+Na0MYRtXprCNO2PYNsscsm1d3rha/sPAuPYCBu7DNLi0jXVwUSsTc0GTx7Jp +vkPWjSbLutFkLmvbDpzusIF3sPDF6Q0abDbUOVtaWlVYTA7Q9tAuMGExUTFVMXFVMVUxYTFxUTFR +vdVMVExWWkxcWlVMN5UUExUTFdPPEauZwnoHC2xqcH1sFxrkZKiZqpgSROXkse3poWkd3E1lXXFg +osrSwprqwtLaksqhY6LK0tpiqtLa2kMjY1qr+8qRc9pyOFBdTVFhYWFlXU11dVlpZU11WXVZ0VVZ +WVlZWW1laVFlbV1VcU1hUXV1cU1hZVFtZXFdTV1RcV1RbV1dYVFZaU1ddWllWVVRdWlZXVFNWVVR +WVl1aWlxUXV1YWVx2OLaytri0uKi6sq6suKi4uLSspra2qLa2tqqorKqurrawrrC6tK6mtrSssq6 +6rra4mC1pdW1ZXW1pWWlZZV1daWlxXVFJSvIacuxUJV1pYWlZaXV1dWWY+FqamurSqtKa4tLa6pK +60pta8sKi+4CU1XfuuKi2tra4rqq2qra2srqqtqy2tqqqtri2qK60pqy2tqastrimtqy2tra2rqS +2tqimsLa2sLawrqqytrKyrrSuuLqwsLq6tqa0tLa2tra2qqyuqKq4trCmtra2rq6yqLSsrLasuKi +mtra2sLqwqqa2tLKusLaqtKSmsrakprKytrSwtrautrSutra2rqS2tKaotqqmtLqmurS6tLqmuLa +wuKaytLS6uqa0tLS2qLamtLS0tLS2srS6qrKytLa2prK0tKaysLCyrrC0trSmtK60rKauqKqmrrS +0uqysrJwZXWV1dWFdaW1RaW1dSV1pWWlpTVllWVlpZVlpbXFpYVVhTVlpbVlpaWVxUV1xYXFtWXF +pZVVZbWlRaVFpZXVldXVlZWVlUVllcVF1ZWVxcWVlVV1tZVVNZW1lWWFlZU1pZWldZW1lTWlldWF +xZXV1TWVxXU1lZW1lcW1NYWVpTWFlUWVlZWVtXV11WWVpWWV1bWVxXXFlZVVpdXF1cW1VcWVtcWF +hbVFZXWFdYWltXUlhWWVhVWFlTVVhbWVVYWFhVWFlUWFxYEK64prigqrK4vrCgtrKovramur6qoq +q6uKS6sLa2pLi0orK2tK6yqLiymRelgCQBr4JAHLOypjyT4mTEAVhWxyIgI5xTT0gQPTi6OGpjdH +AZqHPWBsIvZYxSz0UUPzsEcLzkQcrJviHama4RwfNMU3UDcVdYSCShJZ9BQRBS4SShVbsUrsD29Z +4iGpXPmVMbKYBY4DoK0bJ0CWYRyLIWlLqBdOAX8UXi7PJPCyrQac6ceJB70wcSA+xHP4Dq/hNryG +4/AbnsODYIi4kxATP0BkgDdgcuJwWgpQSDzBHIxXgGUhIMUGqgQ8gDHxkAwTMnHYgUc1EMwHhKGG +qQB40DA2VgKGiKUAcmh4iSWlX5MklX+xRjwvlxCCr2Ch/wIgRdZ8cSWtk9EHpuciD1BNxB0hNME6 +Rngq6iAVrRyCOBo5hPFTEbazEzEIaaYhjxeXg7A3MA176MA01IHDU6zDVZOHlLM7BHT4tbGOmc8A +U7h+TKTDg3EMDUFCGivwO7IsPiFBAo6FZIBP/TBxKBokzgRDxI2yPFzoJYcX8R9exI84Ug8UpyE4 +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 +sSFwvMB2boyFrI+lcGgbaxGD7GGAnVkIRErnYQ8lm98bLTS9Ol5seneg0PTimMEpppFiU3xjlZMx +x+dmIo5UzkQcrJ2INGh4ch4B/Pw08sfpoQlcZgFN1CaHQOkqWpHS38ZaxBSMQ3wFOXmkwOckLOBQ +NEGchttwGB7DXbhLlr+4uORwH77DgXgNz3nhMpxzF07Db3gQDhPnEOEKqQbnBbrxAMYyvUwwKfmi +EAGlXHL4uGQAPbJJAPxm6xO5zcQen5peHiUyD3Ow0ATrGAFKCSTR0oUVtM9JIeJLwx0yMAt9zNhE +5OG6GdbBium9gWNTbMP180MTuUgmWGy28RHt4XiL7DfWohUioniBr2VNcQY9RDyI53CdrAzX4TO8 +x2WG5/AZTsNhuEyWc845z/IXzjnnnHPOuQuH4TK8ht/wGo7DY/gMz/IXPsNvOA7n4T7ciEcVRfE4 +swhiYyoaOr2xnZZ+TmxYAjKh4dnZxA98QABkxyQIeEcpAOihkUMaMQlhdWx6d6DQ/PYgofnNUSIT +TKNF5veGCs3vjRibiDZMbIpvrG4m4kjlZMTxubnIo3PT8UanpuMNzU3IAj41I2Vohndg8QvGJC5k +IecH/FtR8QQ5ShyH43AYHsNfOAyX4TPcht9wHF7DYXgMh+EwPMtd+At/4TU8hsNwGA7DY/gMp+E6 +nIczLT29CMJ9AbGGwYReEzdk8dC+eJQUA84EhYBbcFDlEmhH+KkhtpA1znFBbjwAMUxTKXMBW0xl +OXU5dM7bnJZUDp3W1lRVldaW1JZWFxeXmpXWFlsXm9RWmlqXFFsbF5YUVRcbl9SaFtvalZWamhba +VpcVVhUV25pallRalVWXVBfXGpbU2lUWl1SVFRZbWtuV1tXWFhOWFR6HqiytLaaqrCwms6ecB2hv +d3NeXWhpamtXVmtdamhVUmhXW2lSV1htWFJZXFRtUlhUW2poalVpTHNoWFZTVDLaHtoFPzQtmxsa +3QYmLKsNXmxrdmpwdXIDpgRVVWldVFRYVWpZXGpSVVpUXVJZWVdYUmhtaGtSbVVpVWttXFVRFzbI +e3leZlRVG7yimB4wJTDTMqMyi2JywHRhg8yhQ91ALCePbU8PTauhzkHbQ7uwVvf19DRuoML/UPol +CAH5s2Rs1k8hFmX81mYZPzcaQYjJOaxJa+8Q5bWLRTnBlQDlVQsUOdhj7tyINAktDEGlkUC/QJl/ +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 +TkKEEUqJB7GFBmrd1T2DfgaRzRYCjZ/E/7Zi0WPxU6GVA5MJ15BF0ne0G6BVasDFUEmVArSor2yi +dnDCFq2dR+toH+2KGL/0wdngQY4mEQ5GVQPXFDksXPn2sw8zOCDv7d7Bl//HyvSsGYXxkOCqE0fQ +JWfXZtAsCaJNeYyjtghPrWhoGltXwe646FbGCJdyj6nRYxE9Sa1K1wJT0FuflP3s9FxVg08goCMm +CHZ6AaIFc+2fJTJFUWQMVmSZwRqQ9FEnAxSjdNdV/G1PlWs0T3GXgicEIcKC7Yg5FlRysdMwy+vP +TlDWs+lbxc9hbYI2L9aT4Gror1d+c8ts9r+XryCo/gs1T2s+ES1zRBHPkt1BEJocQBFRYykhqRO/ +eyr0bPKIRXJikNxDaUTCEgEc8MKkr8+xQGzYVf8JsxNjsLBwvt7xKKleJkRfCaMPFp4b/oYQdDod +Yd4V//REFxIuWMNFLwSHERWtzkGTJ1HYJF6u4CVg6QX0pn6c05FF0ty9lWO0IKi/FUaQXZENasnr +lDMtXHneLtGoI/13kwPHyV9Zu4A8LOq7vxvCqSFlZPPOdhhwxnS1VHBNH22d+cKxdPyas0/cLvTR +92CezayHsgpv+FTFppbsR9yEEcbtUsIwTwDlLpEMzu3+hc3P2WrilD6xkAUyPsSKcAyeMfqCNTJK +DCyYrHjgqZHjdyOPBgCoB9TO8PaczQ5OcYrRT78be5rrLzNQl6++VnToshs2jvf9ZjnGjKaMJ1FD +4GrWaGFLuyZTNYd3A3vCMOpNQOzCo45oWyANcXpJaELLmNHVrzyrJIt1sZssp5PmEewJAHQKnzJh +ZGM5MDU1MC1lY2UwLTQxMzctOTFmNC0xN2M0MTVmZWI2ZWVkNTliODUwZC01NjBlLTQzNjgtYmE3 +NS1iODYyZTBmYmQ3NDQgNjM4LjI1MjM3MjNkZTBiNTAtYWQ2MS00NzVmLWI0MTYtNDk3NDMwYmEx +ZjgwOTI0OWEzOTUtNDBiYi00YjVhLTkxZDMtYmMzM2NhNDlhMGUyMy40NjEyNTUwMTMuIEEFyyEi +PK/2G5795nSQBlCtZhpYq5HmeaC5I+e3mao5IrTNqQTfThDoBqQNOeCvwLpQOYMQgljDWOean5ox +mIYH3NoN2gWyJmrHSno0ph88+OW3c/SY4ADufA/sOhk27tQGjjVXgOc3pyk8mDWj8d9EBOoGFqYB +8gMLp2HQf3PPES6BCPTmG1hLj9b0g9cP0QU2TIwARDuBMTgzZTQzZjg0LTRmY2QtNDQ1Mi04ZTNm +LThkYTE3ZWNiNjIzODJlZGI4NjVkLTg1M2MtNDk4YS1hZjMyLWE2NzZlMWUzOWZlNm1sMTBfU1ZH +RmlsdGVyDS8gOg0vWE1MTm9kZSA6DShmeG1sbm9kZS1ub2RlbmFtdmFsdTEgL0ludHR5cC9BcnJh +eWVUdXJidWxlbmM7Y2hpbGRyZW4vcmVzdWx0KHR1cmIyYXR0cmlidXRlOyAsc3RpdGNoVGlsZXMo +bm9TYmFzZUZyZXF1ZW5jeTAuMDVudW1PY3RhdjJ0ZmVDb21wb3NpdG9wZXJhdG9yKGluaW5Tb3Vy +Y2VHcmFwaGljeDAleGgxMHl3d2lkKUFJX19pZG9iamVjdC9EZWYgOzRmcmFjdGFsTm9pczRHYXVz +c2lhbkJsdTFiMmREZXZpZmVPZmZzZW9kZGRTcGVjdWxhckxpZ2h0aW5nUG9pbnRMLTUwMC16LTJ6 +c3BlY091c3VyZmFjZXR5bGw6RXhwb25lbnQoMUNvbnN0YWxpdFBhaWFyaXRobWV0a2s0azNrMzEw +MTIxMk1lcmdOb2QtMjE0eTRCZXZlbFNoYWRvd01vcnBob2xvZ3lhZGlsYXJhZGl1MS5iYm5iLWRu +YjJuNURpc3BsYWNlbWVudE1hcChibnMzeENoYW5uZWxTZWxlY1J5QUNvbG9yTWF0cmk0NDFtYW5p +bWFjY3VtdShub25jYWxjTShsaW5lYWQ1ZnJvbXRvNXRvcmVzdGFyYWx3YXlmaWxsZnJlZXplTmRk +aXRyZWJlZzBzNTU0bmNjOGNjY2NjY2M4Y2NjMWNjY25iKC01NDFDb29sQkRfNjZlckVyb2Q2NjRf +KDdyZXBlYXREKGluZGVmaW5zcGxpMXJlbW92UjEgMTsyMCAxNTsyMDAgMjAwOyAxNSAyMDsxIDEg +Y25QaXhlbFBsYXk1MCA1OzIwIDIwO0RpZmZ1c2U1eWVsbG93O2dyZWVuO2JsdWU7aW5kaWdvO3Zp +b2xldDtyZWQ7b3JhbkRpYXppbXVlbGV2NmQxMWwxcmVkNTAxMDEwMTIybnJlZDY4OC0xMzQyMC4w +LjE1bnRhOHg1NGRkb25GbG9vZmxvb2RibGFjazsgb3BhY2l0eTpzQ24zNW4xbjAxMDFHcmF5KDBP +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 +lWLD723rnSEnUzAhO5/X/n8zszVTw8iilFJKKVOmFqkfq/b/4Jg73lcD3wfDBxcJJKEPyzy45pul +0zhzlrMny6/MMdcq+nRhmWed9Noq1tuVxbJf2uma0k4kLPM0mq7JsvJjvhn7tS1ndadsS7NT2dHO +GSetppDL6fIzljRer6K8Ty87seMp+1qZI62OW85sC31aaApJemWdQrmyTkNOPfvHOuu89FIrMZ7Z +5Y3U2lol9Xg2Jvau0xad0q8S36/yV9NVXfTO91ul+yXvdUuq1/1Je9qJJ01sBvOyHjtI9fizwvKn +UFVNVnVtzk3tvRJbtuI7+52+pO+X/etWVlK0VXz3xx7fzuojusWfSPF/+me379WVO1PzrOJ3fzeu +EnfFVfzPM88ZH18c2ycpz2oaiV1F9yriPF2TFW/sK/GcrthXlfZKPKvq508XKfY0Epv6ex1ep1Hp +9FglzW7rlF+RKM6z4tvTlR9XEdunLynF8ifFN/dX0f50XRhX0d4Oh6eOm9pbYa+i52nhClcXrioc +bbQRx+vyRownTWxruiYr2phjztFOVswxV7mK2cqbqzhvFe1EitlmZGC3WZ8t4zCNrjfTaqe82Z32 +S1pNXH1X98I/jaarccbSLf1v6dVj6Tm/P53zZttY+uN5Ua/inNPl/0SaslZx9iR1E48v67IsknVZ +fV0f6kNN13V9qOv6UJP1oTDuD/WhppIDaDTCvLFMcajq7LHKs0wrF9lbYdNtlrHM8ahSnOGudYRV +LrpYlYEyrWYFZVUGZbRpYwMAAAAAMC+2tmfMHxFEayfrmqzpmqzLuqzLuqrfeuF7r8xZ5ouvyZrz +Zft0tbu72zxdk1WprVjaeb/lna6XGOPpmqyeLKdzulrSyarV5ikrnk+p7H5/0l+TZauvpsvx4ym/ +miyK85TuuF5v6Vd1e7oyrrJWk3VpbSvprDRPSUmSVqMLu8wVxRbjarKulZfkk8qLR9NlTdZ7pPdO +a13+lO/VRWdbucKNs/f/xdm9sdvGb+m37GitnT9bZvDzILP4iDIGXwaCMrTYhSVMpMBQPlQmw/lQ +H+pDfagPRemHmkCZD/WhRjK8w+N/sudpm1XCaVfl40OVgNhFfBCAAJQ4MY9mZ5F9UbW4ujbjykVO +K1BohqprQxOHMqWAbM9QhjN2RV1Rb6wahS6tcM1Z1XX0oDot5UNNfHyoDyUAAQAAAAAAgAAE8O0f +3LFnRFCFfKgH2Nf5UB/q86Gm0uUj/DIGHEW2gTBQ05SA2IWFM4UyNtf+3NLzz/vVq7WyJ733n7q1 +Nz7N/9TvU4knvbfxrY97epW2il4tfnd75e1r+/a89sb7Pv0tpfU+ldX2nZZKez3n92zrjbheSt0t +9ks/1376U1af1nPX27NnN+2ZfV5cq8WyXmxt/aa4PnZ67VeX1N77b2dTfCV+SudbLN2zxCRcja7p +1cvpPn0aie1A2jTOtc3Y0zUnktP6ud/r7Von7p/Z58z2O1qvnumcnun8ae+dE+N3+W1xv7c3/X+c +5fW3rmxJmUz8CZSZRB/RY3+y7UHoCT/MJ1NgPgE+1PRA+roRBwXGeoiiTPR1oWwj7DAs0cFtJpS5 +sx1n984T6WPaGzNte6s4P9/c3bavnVZ6x74mC08XrqKVN1c213aJq4mEsfXJmq58TVb1arpa3mqy +7K0UZyptdZGqtNGre5jYpQadyOs+zZw0Q6+9cV6LqV+v4vQq+r09XcT26U43PqJP2EfMLmml7njW +W+esldZKKZ3TFWedSN3/3f+NaRUv7f+JNO/sOd1nd9PuntN9tvetYs/pPm1fO+uctWaunR+0Sq/e +fqQfbWVpxrbTaquIq0izdTurSKvpil5FWm3bWSmttmaqdFslreKt9kZKvXoVvYrV1umKtYq2qmKd +LiK19kYb7Z2uaKtoK1tdLNtOWuu7X1pvZ9t0Xowf55knbVzb5vtvvUoq2jKNk4WYxj3OGTGN1vua +rGinq8pekfJX1XRlj1ZeUrQe80+a2Fb8r2z8Gf8nK/5H+1X8jz+lV/Hn56+mq5pI9l/0YVYMNVH2 +WTNij20jrSVhKBt9WXWnFDu2tor9odheVVtst9wrlh2zTx0OUyYTexo1zYQy8eL6VXzSR7y2iv9V +9GkkEz12rmL7BMo4vTa2d1W1V/FW0Wfl9Nqcs8y5Y3v+6HMixRt9TprYo8nKfWmernir+PFnrNF7 +XpMV3SP+KuacZfvLHH/GizPur6rpwj+RonuFfxqJ3T6VHL6MQffhujAsZcKv8xxsRNrXGeE0bfPk +DzU9fVmxzf/PIdJKnWwDYdxmen+oD4XrnZ6t7Ony21i6T1eWtCK5tLH+hH1g2pfOaD9HruMwlOgT +96E+1If6UB/qQ32oD/WhPtSH+lAf6kN9qA/1oT7Uh/pQ14f6UBPpQ9lJ9BE79tQxjQ== + + + JmvO3O9ZdpU+XT1Z8elsWT/WSj1LG21uSaP9W2u//MmiH3G9Ns5quibrZ+Wzql9N2hJ7dd1bXq/w +rSKtJivOryqtU96vqpy2W94pVF1Wj3VW0foU6uu5r5U9p8sztlWk+U7p9UpayZ6W5izzT1e0tqJV +xPlrFW2dVVJbZ6bXZP2sVdKfj6mk/jNL6lPIZ+eW7Xi66nRFO/9K96rCslbRX/5XMVdZtldTqP78 +yno1v7peyfmZdsv+6qvpihe7lbmK01Iq61dPZVcRzypSt15NpAn7iN/Xupy5Mcb59vw7lbJQ+zoG +GCeKRBjKtHUg0gPqFBhKxG0oUd59M87ftzqltZ1ePDP1TG+dTavP3LbifCulNbfTiWk7du+e1K3t +bKmdtu91iye1d+Ipm+Zq/+0lE/1eOnHinz7p27a4ZsefaX088W16aVenbWeusz6et+3Fl0zM+dKJ +1afs/LVW+23nY5rxz0v75ray3WLa9G/PKjvmS69T2tX9kol0+qUTZ3VaZa2NLaZdfda/ta+7z2zr +xXlOnHNH/E//ac15zotx7pynvfjpY/zvP+ecmE6csb2z/+183Dfn90rbrc2e8cVfsdO219ac5/zs +ni+Z2JdOxNVnfYub+rx4zsZ9Z7bv/titO60U00m9Vp+ObXu316fTJ/Wf3hRj+5a6/ZwxtrfpnH7J +xEovnfjtsm+e+GfFGP93W9wW/7y1dqU4e/5ca9Na6e32Syb25346M8XdPzNutz+rpfTSiZZelx27 +Np2eadu/7U/nnY1npTbLpvfWO2/2nDH2SyZOt/+XTPTrlw3KrX7JxGztfGvp/aY+O19Lsde/lcq+ +tVLveePF704n/vv9mV55f769tV4ykXbj3FR2vE9r259vcZ15fjvuxrQnxj87Z0vf7bd726fW67S3 +3/5tarGk95KJjuv8fom7q705y0nbP3ue1tJrL5mIs186sanfibub1jrde+Zs+7r1jCuus7vSv+7u +tlrPft9ejF3O+ZdO/J85S/z3HX/G2KfTXC+ZaK9X3NPKjte+tbX+xflWaqulFdPZ85KJl/qlEy/9 +ywYly+vduP3+t8VZdsz++Hb3tG+nf3fLzpdOdHr9b/97d9NZ2+KM6b/nfGVHOy+ZiP/SiXd+ne+e +cbY/vyvG3U0/43Y6P/u99mLZ3/Xb5u9KL5mY56UTZ/bLBqVpvvg//7Utu2JaMX18P9Nr570333/8 +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 +kpdtbBio2zoZUZZtmVF2YdqGRS3kMBuGNE22gTCOjIRhbFiou0ANxZeFHEWo04TYxJZ5KDGAgywM +PdtW4lBgSmRL6pQsdLABY8ZpW1jqRrGdApOFRjitQaZ5YJIRbSMLYZxwlF1U4zqwkFIcPTuOXuqs +bcssz7Zl1pzgugBoXzfx196fmXCUXTjjuhGOsiscZZe//c7XdRvn4ko/Iu3rbBmD7IoPQm3DSgTi +CKddfN3WWWHneT5PAmYXMVNgPCx5YSkjZgpMzxh+GYJIKmmi7CKE4nUaMRvBSiMcdkWvFGaeCEaR +9o3EmCkwIzEsdSWhjivJNlDMQmzrQC1Gd1AkiTBvgtqGdSGpeaL7oToPfPz0qwbGL/MwL2YKTH/R +IxbzrNWG2qaBsFATbWLoIOOI2acR3SkwFBNP22AEZhqKELZ9Wchp/mEeyGFf2Bcd95wEShaGGIl+ +B0MjzEY+LBzhtDDMPAHQSFroddh6rg8e3H+Lp6yOHTgIR9lF6sKwA4m4UpfJQtrmDUZgqNt8hNNE +nAbKPB/GgAEE+GEMGEB8AFIXgqXQB+QWKNxGcoCFmCbbPBIGQjAAw8yTbSBM+zAGjg== + + + hZgICzNS/lAfSpRA+VAfyiMcZZcoy7YMln0Yr/usAA5SfNlW2tjAaDxu/1Af6kL0YR0o/DCNfYZh +odugG6oaC4GqH1laTXpphWGf2PymfCiu00IN7DQ2Oo4t24CIlg+VRdymwdP5hKB2ZTTeM/b7jz/n +7ntxdafT0r8/uzZ2Wv/vz0sm5nen7fM2zf854zzzU5vr7X/3S+/MeGLq+efbmp1abL2d/rQRlBWg +RKd/ycSnf8nE7umyo7X5s1f81nb9Wy9uO9tte77U6/2cKe5MJ9L+fKetNH++3tVpZ7e50mtp9nzz +9XuvZ+vd9t5L3zbGbnHtmm3PiuvPin3WibFTp9fppbc+fX+/bFD+22um3jc/7jvp9Hu74p/4Xlyp +u9+b672XTLz30on32lyznfO/Voux7fnY1lrdXscze+N6H9uL6yUTvV42KJdMtP8uu9L5uP/p1zx/ +Vmstpk8t7b5t/5KJPaIRPTZ4EUFIY/qJJNPwoMSTycTZsv/vpI5p457X32K/E8t+fKvfeem81nrP +Sy+daPOs+N7auPbN8zadtzqV7i+70jnbUkx9ZotrbWzt/VqnbZ94us244p85T2ybzp90Tnrn9aZv +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 + + + diff --git a/brand/readme.md b/brand/readme.md new file mode 100644 index 000000000000..cf9460c0a0a5 --- /dev/null +++ b/brand/readme.md @@ -0,0 +1,2 @@ +This directory contains media assets, such as the Trivy logo. +Assets under this directory are provided under the Creative Commons - BY 4.0 License. For more details, see here: \ No newline at end of file diff --git a/ci/Dockerfile b/ci/Dockerfile deleted file mode 100644 index fba3d17e6888..000000000000 --- a/ci/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM bepsays/ci-goreleaser:1.12-2 - -RUN apt-get -y update \ - && apt-get -y install vim rpm reprepro createrepo \ - && wget https://dl.bintray.com/homebrew/mirror/berkeley-db-18.1.32.tar.gz \ - - # Berkeley DB - && tar zxvf berkeley-db-18.1.32.tar.gz \ - && cd db-18.1.32/build_unix \ - - # Linux - && ../dist/configure --prefix=/usr/local --host=x86_64-linux \ - && make \ - && make install \ - - # Darwin - && make clean \ - && ../dist/configure --prefix=/usr/local --host=x86_64-apple-darwin15 \ - && make \ - && make install diff --git a/ci/deploy-deb.sh b/ci/deploy-deb.sh index 9516c2467dc1..81cba977afa1 100755 --- a/ci/deploy-deb.sh +++ b/ci/deploy-deb.sh @@ -1,17 +1,24 @@ #!/bin/bash -RELEASES=(wheezy jessie stretch buster trusty xenial bionic) +DEBIAN_RELEASES=$(debian-distro-info --supported) +UBUNTU_RELEASES=$(sort -u <(ubuntu-distro-info --supported-esm) <(ubuntu-distro-info --supported)) cd trivy-repo/deb -for release in ${RELEASES[@]}; do - echo "Adding deb package to $release" +for release in ${DEBIAN_RELEASES[@]} ${UBUNTU_RELEASES[@]}; do + echo "Removing deb package of $release" reprepro -A i386 remove $release trivy reprepro -A amd64 remove $release trivy - reprepro includedeb $release ../../dist/*Linux-64bit.deb + reprepro -A arm64 remove $release trivy +done + +for release in ${DEBIAN_RELEASES[@]} ${UBUNTU_RELEASES[@]}; do + echo "Adding deb package to $release" reprepro includedeb $release ../../dist/*Linux-32bit.deb + reprepro includedeb $release ../../dist/*Linux-64bit.deb + reprepro includedeb $release ../../dist/*Linux-ARM64.deb done git add . git commit -m "Update deb packages" -git push origin master +git push origin main diff --git a/ci/deploy-rpm.sh b/ci/deploy-rpm.sh index 4a0c6f094e44..32a328dbbdee 100755 --- a/ci/deploy-rpm.sh +++ b/ci/deploy-rpm.sh @@ -1,20 +1,51 @@ -#!/bin/sh +#!/bin/bash -RPM_EL6=$(find dist/ -type f -name "*64bit.rpm" -printf "%f\n" | head -n1 | sed -e 's/_/-/g' -e 's/-Linux/.el6/' -e 's/-64bit/.x86_64/') -RPM_EL7=$(find dist/ -type f -name "*64bit.rpm" -printf "%f\n" | head -n1 | sed -e 's/_/-/g' -e 's/-Linux/.el7/' -e 's/-64bit/.x86_64/') +TRIVY_VERSION=$(find dist/ -type f -name "*64bit.rpm" -printf "%f\n" | head -n1 | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p') + +function create_common_rpm_repo () { + rpm_path=$1 + + ARCHES=("x86_64" "aarch64") + for arch in ${ARCHES[@]}; do + prefix=$arch + if [ "$arch" == "x86_64" ]; then + prefix="64bit" + elif [ "$arch" == "aarch64" ]; then + prefix="ARM64" + fi + + mkdir -p $rpm_path/$arch + cp ../dist/*${prefix}.rpm ${rpm_path}/$arch/ + createrepo_c -u https://github.com/aquasecurity/trivy/releases/download/ --location-prefix="v"$TRIVY_VERSION --update $rpm_path/$arch + rm ${rpm_path}/$arch/*${prefix}.rpm + done +} + +function create_rpm_repo () { + version=$1 + rpm_path=rpm/releases/${version}/x86_64 + + mkdir -p $rpm_path + cp ../dist/*64bit.rpm ${rpm_path}/ + + createrepo_c -u https://github.com/aquasecurity/trivy/releases/download/ --location-prefix="v"$TRIVY_VERSION --update $rpm_path + + rm ${rpm_path}/*64bit.rpm +} + +echo "Create RPM releases for Trivy v$TRIVY_VERSION" cd trivy-repo -mkdir -p rpm/releases/6/x86_64 -mkdir -p rpm/releases/7/x86_64 -cd rpm -cp ../../dist/*64bit.rpm releases/6/x86_64/${RPM_EL6} -cp ../../dist/*64bit.rpm releases/7/x86_64/${RPM_EL7} +echo "Processing common repository for RHEL/CentOS..." +create_common_rpm_repo rpm/releases -createrepo --update releases/6/x86_64/ -createrepo --update releases/7/x86_64/ +VERSIONS=(5 6 7 8 9) +for version in ${VERSIONS[@]}; do + echo "Processing RHEL/CentOS $version..." + create_rpm_repo $version +done git add . -git commit -m "Update rpm packages" -git push origin master - +git commit -m "Update rpm packages for Trivy v$TRIVY_VERSION" +git push origin main diff --git a/cmd/remic/main.go b/cmd/remic/main.go deleted file mode 100644 index b6c2c96b5b20..000000000000 --- a/cmd/remic/main.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "os" - "strings" - - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "github.com/knqyf263/trivy/pkg/remic" - "github.com/urfave/cli" - - "github.com/knqyf263/trivy/pkg/log" -) - -func main() { - cli.AppHelpTemplate = `NAME: - {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} -USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} -VERSION: - {{.Version}}{{end}}{{end}}{{if .Description}} -DESCRIPTION: - {{.Description}}{{end}}{{if len .Authors}} -AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} -OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}} -` - app := cli.NewApp() - app.Name = "remic" - app.Version = "0.0.1" - app.ArgsUsage = "file" - - app.Usage = "A simple and fast tool for detecting vulnerabilities in application dependencies" - - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "format, f", - Value: "table", - Usage: "format (table, json)", - }, - cli.StringFlag{ - Name: "severity, s", - Value: strings.Join(vulnerability.SeverityNames, ","), - Usage: "severity of vulnerabilities to be displayed", - }, - cli.StringFlag{ - Name: "output, o", - Usage: "output file name", - }, - cli.BoolFlag{ - Name: "debug, d", - Usage: "debug mode", - }, - } - - app.Action = func(c *cli.Context) error { - return remic.Run(c) - } - - err := app.Run(os.Args) - if err != nil { - log.Logger.Fatal(err) - } -} diff --git a/cmd/trivy/main.go b/cmd/trivy/main.go index 8162fd890273..e2c545975315 100644 --- a/cmd/trivy/main.go +++ b/cmd/trivy/main.go @@ -1,105 +1,39 @@ package main import ( - l "log" + "context" "os" - "strings" - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" + "golang.org/x/xerrors" - "github.com/urfave/cli" + "github.com/aquasecurity/trivy/pkg/commands" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/plugin" - "github.com/knqyf263/trivy/pkg" - "github.com/knqyf263/trivy/pkg/log" -) - -var ( - version = "dev" + _ "modernc.org/sqlite" // sqlite driver for RPM DB and Java DB ) func main() { - cli.AppHelpTemplate = `NAME: - {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} -USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} -VERSION: - {{.Version}}{{end}}{{end}}{{if .Description}} -DESCRIPTION: - {{.Description}}{{end}}{{if len .Authors}} -AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} -OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}} -` - app := cli.NewApp() - app.Name = "trivy" - app.Version = version - app.ArgsUsage = "image_name" - - app.Usage = "A simple and comprehensive vulnerability scanner for containers" - - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "format, f", - Value: "table", - Usage: "format (table, json)", - }, - cli.StringFlag{ - Name: "input, i", - Value: "", - Usage: "input file path instead of image name", - }, - cli.StringFlag{ - Name: "severity, s", - Value: strings.Join(vulnerability.SeverityNames, ","), - Usage: "severities of vulnerabilities to be displayed (comma separated)", - }, - cli.StringFlag{ - Name: "output, o", - Usage: "output file name", - }, - cli.IntFlag{ - Name: "exit-code", - Usage: "Exit code when vulnerabilities were found", - Value: 0, - }, - cli.BoolFlag{ - Name: "skip-update", - Usage: "skip db update", - }, - cli.BoolFlag{ - Name: "clean, c", - Usage: "clean all cache", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "suppress progress bar", - }, - cli.BoolFlag{ - Name: "ignore-unfixed", - Usage: "display only fixed vulnerabilities", - }, - cli.BoolFlag{ - Name: "refresh", - Usage: "refresh DB (usually used after version update of trivy)", - }, - cli.BoolFlag{ - Name: "debug, d", - Usage: "debug mode", - }, + if err := run(); err != nil { + log.Fatal(err) } +} - app.Action = func(c *cli.Context) error { - return pkg.Run(c) +func run() error { + // Trivy behaves as the specified plugin. + if runAsPlugin := os.Getenv("TRIVY_RUN_AS_PLUGIN"); runAsPlugin != "" { + if !plugin.IsPredefined(runAsPlugin) { + return xerrors.Errorf("unknown plugin: %s", runAsPlugin) + } + if err := plugin.RunWithURL(context.Background(), runAsPlugin, plugin.RunOptions{Args: os.Args[1:]}); err != nil { + return xerrors.Errorf("plugin error: %w", err) + } + return nil } - err := app.Run(os.Args) - if err != nil { - if log.Logger != nil { - log.Logger.Fatal(err) - } - l.Fatal(err) + app := commands.NewApp() + if err := app.Execute(); err != nil { + return err } + return nil } diff --git a/contrib/Trivy.gitlab-ci.yml b/contrib/Trivy.gitlab-ci.yml new file mode 100644 index 000000000000..66225b0dac06 --- /dev/null +++ b/contrib/Trivy.gitlab-ci.yml @@ -0,0 +1,29 @@ +Trivy_container_scanning: + stage: test + image: + name: alpine:3.11 + variables: + # Override the GIT_STRATEGY variable in your `.gitlab-ci.yml` file and set it to `fetch` if you want to provide a `clair-whitelist.yml` + # file. See https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#overriding-the-container-scanning-template + # for details + GIT_STRATEGY: none + IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" + allow_failure: true + before_script: + - export TRIVY_VERSION=${TRIVY_VERSION:-v0.19.2} + - apk add --no-cache curl docker-cli + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + - curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin ${TRIVY_VERSION} + - curl -sSL -o /tmp/trivy-gitlab.tpl https://github.com/aquasecurity/trivy/raw/${TRIVY_VERSION}/contrib/gitlab.tpl + script: + - trivy --exit-code 0 --cache-dir .trivycache/ --no-progress --format template --template "@/tmp/trivy-gitlab.tpl" -o gl-container-scanning-report.json $IMAGE + cache: + paths: + - .trivycache/ + artifacts: + reports: + container_scanning: gl-container-scanning-report.json + dependencies: [] + only: + refs: + - branches diff --git a/contrib/asff.tpl b/contrib/asff.tpl new file mode 100644 index 000000000000..d6833a1d7ff1 --- /dev/null +++ b/contrib/asff.tpl @@ -0,0 +1,161 @@ +{ + "Findings": [ + {{- $t_first := true -}} + {{- range . -}} + {{- $target := .Target -}} + {{- $image := .Target -}} + {{- if gt (len $image) 127 -}} + {{- $image = $image | regexFind ".{124}$" | printf "...%v" -}} + {{- end}} + {{- range .Vulnerabilities -}} + {{- if $t_first -}} + {{- $t_first = false -}} + {{- else -}} + , + {{- end -}} + {{- $severity := .Severity -}} + {{- if eq $severity "UNKNOWN" -}} + {{- $severity = "INFORMATIONAL" -}} + {{- end -}} + {{- $description := .Description -}} + {{- if gt (len $description ) 512 -}} + {{- $description = (substr 0 512 $description) | printf "%v .." -}} + {{- end}} + { + "SchemaVersion": "2018-10-08", + "Id": "{{ $target }}/{{ .VulnerabilityID }}", + "ProductArn": "arn:aws:securityhub:{{ env "AWS_REGION" }}::product/aquasecurity/aquasecurity", + "GeneratorId": "Trivy/{{ .VulnerabilityID }}", + "AwsAccountId": "{{ env "AWS_ACCOUNT_ID" }}", + "Types": [ "Software and Configuration Checks/Vulnerabilities/CVE" ], + "CreatedAt": "{{ now | date "2006-01-02T15:04:05.999999999Z07:00" }}", + "UpdatedAt": "{{ now | date "2006-01-02T15:04:05.999999999Z07:00" }}", + "Severity": { + "Label": "{{ $severity }}" + }, + "Title": "Trivy found a vulnerability to {{ .VulnerabilityID }} in container {{ $target }}, related to {{ .PkgName }}", + "Description": {{ escapeString $description | printf "%q" }}, + {{ if not (empty .PrimaryURL) -}} + "Remediation": { + "Recommendation": { + "Text": "More information on this vulnerability is provided in the hyperlink", + "Url": "{{ .PrimaryURL }}" + } + }, + {{ end -}} + "ProductFields": { "Product Name": "Trivy" }, + "Resources": [ + { + "Type": "Container", + "Id": "{{ $target }}", + "Partition": "aws", + "Region": "{{ env "AWS_REGION" }}", + "Details": { + "Container": { "ImageName": "{{ $image }}" }, + "Other": { + "CVE ID": "{{ .VulnerabilityID }}", + "CVE Title": {{ .Title | printf "%q" }}, + "PkgName": "{{ .PkgName }}", + "Installed Package": "{{ .InstalledVersion }}", + "Patched Package": "{{ .FixedVersion }}", + "NvdCvssScoreV3": "{{ (index .CVSS (sourceID "nvd")).V3Score }}", + "NvdCvssVectorV3": "{{ (index .CVSS (sourceID "nvd")).V3Vector }}", + "NvdCvssScoreV2": "{{ (index .CVSS (sourceID "nvd")).V2Score }}", + "NvdCvssVectorV2": "{{ (index .CVSS (sourceID "nvd")).V2Vector }}" + } + } + } + ], + "RecordState": "ACTIVE" + } + {{- end -}} + {{- range .Misconfigurations -}} + {{- if $t_first -}}{{- $t_first = false -}}{{- else -}},{{- end -}} + {{- $severity := .Severity -}} + {{- if eq $severity "UNKNOWN" -}} + {{- $severity = "INFORMATIONAL" -}} + {{- end -}} + {{- $description := .Description -}} + {{- if gt (len $description ) 512 -}} + {{- $description = (substr 0 512 $description) | printf "%v .." -}} + {{- end}} + { + "SchemaVersion": "2018-10-08", + "Id": "{{ $target }}/{{ .ID }}", + "ProductArn": "arn:aws:securityhub:{{ env "AWS_REGION" }}::product/aquasecurity/aquasecurity", + "GeneratorId": "Trivy/{{ .ID }}", + "AwsAccountId": "{{ env "AWS_ACCOUNT_ID" }}", + "Types": [ "Software and Configuration Checks" ], + "CreatedAt": "{{ now | date "2006-01-02T15:04:05.999999999Z07:00" }}", + "UpdatedAt": "{{ now | date "2006-01-02T15:04:05.999999999Z07:00" }}", + "Severity": { + "Label": "{{ $severity }}" + }, + "Title": "Trivy found a misconfiguration in {{ $target }}: {{ escapeString .Title }}", + "Description": {{ escapeString $description | printf "%q" }}, + "Remediation": { + "Recommendation": { + "Text": "{{ .Resolution }}", + "Url": "{{ .PrimaryURL }}" + } + }, + "ProductFields": { "Product Name": "Trivy" }, + "Resources": [ + { + "Type": "Other", + "Id": "{{ $target }}", + "Partition": "aws", + "Region": "{{ env "AWS_REGION" }}", + "Details": { + "Other": { + "Message": "{{ .Message }}", + "Filename": "{{ $target }}", + "StartLine": "{{ .CauseMetadata.StartLine }}", + "EndLine": "{{ .CauseMetadata.EndLine }}" + } + } + } + ], + "RecordState": "ACTIVE" + } + {{- end -}} + {{- range .Secrets -}} + {{- if $t_first -}}{{- $t_first = false -}}{{- else -}},{{- end -}} + {{- $severity := .Severity -}} + {{- if eq $severity "UNKNOWN" -}} + {{- $severity = "INFORMATIONAL" -}} + {{- end -}} + { + "SchemaVersion": "2018-10-08", + "Id": "{{ $target }}", + "ProductArn": "arn:aws:securityhub:{{ env "AWS_REGION" }}::product/aquasecurity/aquasecurity", + "GeneratorId": "Trivy", + "AwsAccountId": "{{ env "AWS_ACCOUNT_ID" }}", + "Types": [ "Sensitive Data Identifications" ], + "CreatedAt": "{{ now | date "2006-01-02T15:04:05.999999999Z07:00" }}", + "UpdatedAt": "{{ now | date "2006-01-02T15:04:05.999999999Z07:00" }}", + "Severity": { + "Label": "{{ $severity }}" + }, + "Title": "Trivy found a secret in {{ $target }}: {{ .Title }}", + "Description": "Trivy found a secret in {{ $target }}: {{ .Title }}", + "ProductFields": { "Product Name": "Trivy" }, + "Resources": [ + { + "Type": "Other", + "Id": "{{ $target }}", + "Partition": "aws", + "Region": "{{ env "AWS_REGION" }}", + "Details": { + "Other": { + "Filename": "{{ $target }}" + } + } + } + ], + "RecordState": "ACTIVE" + } + {{- end -}} + {{- end }} + ] +} diff --git a/contrib/example_policy/advanced.rego b/contrib/example_policy/advanced.rego new file mode 100644 index 000000000000..33e0a6232ea0 --- /dev/null +++ b/contrib/example_policy/advanced.rego @@ -0,0 +1,106 @@ +package trivy + +import data.lib.trivy + +default ignore = false + +nvd_v3_vector = v { + v := input.CVSS.nvd.V3Vector +} + +redhat_v3_vector = v { + v := input.CVSS.redhat.V3Vector +} + +# Ignore a vulnerability which requires high privilege +ignore { + nvd_cvss_vector := trivy.parse_cvss_vector_v3(nvd_v3_vector) + nvd_cvss_vector.PrivilegesRequired == "High" + + # Check against RedHat scores as well as NVD + redhat_cvss_vector := trivy.parse_cvss_vector_v3(redhat_v3_vector) + redhat_cvss_vector.PrivilegesRequired == "High" +} + +# Ignore a vulnerability which requires user interaction +ignore { + nvd_cvss_vector := trivy.parse_cvss_vector_v3(nvd_v3_vector) + nvd_cvss_vector.UserInteraction == "Required" + + # Check against RedHat scores as well as NVD + redhat_cvss_vector := trivy.parse_cvss_vector_v3(redhat_v3_vector) + redhat_cvss_vector.UserInteraction == "Required" +} + +ignore { + input.PkgName == "openssl" + + # Split CVSSv3 vector + nvd_cvss_vector := trivy.parse_cvss_vector_v3(nvd_v3_vector) + + # Evaluate Attack Vector + ignore_attack_vectors := {"Physical", "Local"} + nvd_cvss_vector.AttackVector == ignore_attack_vectors[_] +} + +ignore { + input.PkgName == "openssl" + + # Evaluate severity + input.Severity == {"LOW", "MEDIUM", "HIGH"}[_] + + # Evaluate CWE-ID + deny_cwe_ids := { + "CWE-119", # Improper Restriction of Operations within the Bounds of a Memory Buffer + "CWE-200", # Exposure of Sensitive Information to an Unauthorized Actor + } + + count({x | x := input.CweIDs[_]; x == deny_cwe_ids[_]}) == 0 +} + +ignore { + input.PkgName == "bash" + + # Split CVSSv3 vector + nvd_cvss_vector := trivy.parse_cvss_vector_v3(nvd_v3_vector) + + # Evaluate Attack Vector + ignore_attack_vectors := {"Physical", "Local", "Adjacent"} + nvd_cvss_vector.AttackVector == ignore_attack_vectors[_] + + # Evaluate severity + input.Severity == {"LOW", "MEDIUM", "HIGH"}[_] +} + +ignore { + input.PkgName == "django" + + # Split CVSSv3 vector + nvd_cvss_vector := trivy.parse_cvss_vector_v3(nvd_v3_vector) + + # Evaluate Attack Vector + ignore_attack_vectors := {"Physical", "Local"} + nvd_cvss_vector.AttackVector == ignore_attack_vectors[_] + + # Evaluate severity + input.Severity == {"LOW", "MEDIUM"}[_] + + # Evaluate CWE-ID + deny_cwe_ids := { + "CWE-89", # SQL Injection + "CWE-78", # OS Command Injection + } + + count({x | x := input.CweIDs[_]; x == deny_cwe_ids[_]}) == 0 +} + +ignore { + input.PkgName == "jquery" + + # Split CVSSv3 vector + nvd_cvss_vector := trivy.parse_cvss_vector_v3(nvd_v3_vector) + + # Evaluate CWE-ID + deny_cwe_ids := {"CWE-79"} # XSS + count({x | x := input.CweIDs[_]; x == deny_cwe_ids[_]}) == 0 +} diff --git a/contrib/example_policy/basic.rego b/contrib/example_policy/basic.rego new file mode 100644 index 000000000000..0cf9f0ea04e7 --- /dev/null +++ b/contrib/example_policy/basic.rego @@ -0,0 +1,76 @@ +package trivy + +import data.lib.trivy + +default ignore = false + +ignore_pkgs := {"bash", "bind-license", "rpm", "vim", "vim-minimal"} + +ignore_severities := {"LOW", "MEDIUM"} + +nvd_v3_vector = v { + v := input.CVSS.nvd.V3Vector +} + +redhat_v3_vector = v { + v := input.CVSS.redhat.V3Vector +} + +ignore { + input.PkgName == ignore_pkgs[_] +} + +ignore { + input.Severity == ignore_severities[_] +} + +# Ignore a vulnerability which is not remotely exploitable +ignore { + nvd_cvss_vector := trivy.parse_cvss_vector_v3(nvd_v3_vector) + nvd_cvss_vector.AttackVector != "Network" + + redhat_cvss_vector := trivy.parse_cvss_vector_v3(redhat_v3_vector) + redhat_cvss_vector.AttackVector != "Network" +} + +# Ignore a vulnerability which requires high privilege +ignore { + nvd_cvss_vector := trivy.parse_cvss_vector_v3(nvd_v3_vector) + nvd_cvss_vector.PrivilegesRequired == "High" + + redhat_cvss_vector := trivy.parse_cvss_vector_v3(redhat_v3_vector) + redhat_cvss_vector.PrivilegesRequired == "High" +} + +# Ignore a vulnerability which requires user interaction +ignore { + nvd_cvss_vector := trivy.parse_cvss_vector_v3(nvd_v3_vector) + nvd_cvss_vector.UserInteraction == "Required" + + redhat_cvss_vector := trivy.parse_cvss_vector_v3(redhat_v3_vector) + redhat_cvss_vector.UserInteraction == "Required" +} + +# Ignore CSRF +ignore { + # https://cwe.mitre.org/data/definitions/352.html + input.CweIDs[_] == "CWE-352" +} + +# Ignore a license +ignore { + input.PkgName == "alpine-baselayout" + input.Name == "GPL-2.0" +} + +# Ignore loose file license +ignore { + input.Name == "AGPL-3.0" + input.FilePath == "/usr/share/grafana/LICENSE" +} + +# Ignore secret +ignore { + input.RuleID == "aws-access-key-id" + input.Match == "AWS_ACCESS_KEY_ID=\"********************\"" +} diff --git a/contrib/gitlab-codequality.tpl b/contrib/gitlab-codequality.tpl new file mode 100644 index 000000000000..5beec257e078 --- /dev/null +++ b/contrib/gitlab-codequality.tpl @@ -0,0 +1,103 @@ +{{- /* Template based on https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types */ -}} +[ + {{- $t_first := true }} + {{- range . }} + {{- $target := .Target }} + {{- range .Vulnerabilities -}} + {{- if $t_first -}} + {{- $t_first = false -}} + {{ else -}} + , + {{- end }} + { + "type": "issue", + "check_name": "container_scanning", + "categories": [ "Security" ], + "description": {{ list .VulnerabilityID .PkgName .InstalledVersion .Title | join " - " | printf "%q" }}, + "fingerprint": "{{ list .VulnerabilityID .PkgName .InstalledVersion $target | join "" | sha1sum }}", + "content": {{ .Description | printf "%q" }}, + "severity": {{ if eq .Severity "LOW" -}} + "info" + {{- else if eq .Severity "MEDIUM" -}} + "minor" + {{- else if eq .Severity "HIGH" -}} + "major" + {{- else if eq .Severity "CRITICAL" -}} + "critical" + {{- else -}} + "info" + {{- end }}, + "location": { + "path": "{{ $target }}", + "lines": { + "begin": 0 + } + } + } + {{- end -}} + {{- range .Misconfigurations -}} + {{- if $t_first -}} + {{- $t_first = false -}} + {{ else -}} + , + {{- end }} + { + "type": "issue", + "check_name": "container_scanning", + "categories": [ "Security" ], + "description": {{ list "Misconfig" .ID .Title | join " - " | printf "%q" }}, + "fingerprint": "{{ list .ID .Title $target | join "" | sha1sum }}", + "content": {{ .Description | printf "%q" }}, + "severity": {{ if eq .Severity "LOW" -}} + "info" + {{- else if eq .Severity "MEDIUM" -}} + "minor" + {{- else if eq .Severity "HIGH" -}} + "major" + {{- else if eq .Severity "CRITICAL" -}} + "critical" + {{- else -}} + "info" + {{- end }}, + "location": { + "path": "{{ $target }}", + "lines": { + "begin": {{ .CauseMetadata.StartLine }} + } + } + } + {{- end -}} + {{- range .Secrets -}} + {{- if $t_first -}} + {{- $t_first = false -}} + {{ else -}} + , + {{- end }} + { + "type": "issue", + "check_name": "container_scanning", + "categories": [ "Security" ], + "description": {{ list "Secret" .RuleID .Title | join " - " | printf "%q" }}, + "fingerprint": "{{ list .RuleID .Title $target | join "" | sha1sum }}", + "content": {{ .Title | printf "%q" }}, + "severity": {{ if eq .Severity "LOW" -}} + "info" + {{- else if eq .Severity "MEDIUM" -}} + "minor" + {{- else if eq .Severity "HIGH" -}} + "major" + {{- else if eq .Severity "CRITICAL" -}} + "critical" + {{- else -}} + "info" + {{- end }}, + "location": { + "path": "{{ $target }}", + "lines": { + "begin": {{ .StartLine }} + } + } + } + {{- end -}} + {{- end }} +] diff --git a/contrib/gitlab.tpl b/contrib/gitlab.tpl new file mode 100644 index 000000000000..744c0c9394cb --- /dev/null +++ b/contrib/gitlab.tpl @@ -0,0 +1,105 @@ +{{- /* Template based on https://docs.gitlab.com/ee/user/application_security/container_scanning/#reports-json-format */ -}} +{ + "version": "15.0.7", + "scan": { + "analyzer": { + "id": "trivy", + "name": "Trivy", + "vendor": { + "name": "Aqua Security" + }, + "version": "{{ appVersion }}" + }, + "end_time": "{{ now | date "2006-01-02T15:04:05" }}", + "scanner": { + "id": "trivy", + "name": "Trivy", + "url": "https://github.com/aquasecurity/trivy/", + "vendor": { + "name": "Aqua Security" + }, + "version": "{{ appVersion }}" + }, + "start_time": "{{ now | date "2006-01-02T15:04:05" }}", + "status": "success", + "type": "container_scanning" + }, + "vulnerabilities": [ + {{- $t_first := true }} + {{- range . }} + {{- $target := .Target }} + {{- $image := $target | regexFind "[^\\s]+" }} + {{- range .Vulnerabilities -}} + {{- if $t_first -}} + {{- $t_first = false -}} + {{ else -}} + , + {{- end }} + { + "id": "{{ .VulnerabilityID }}", + "name": {{ .Title | printf "%q" }}, + "description": {{ .Description | printf "%q" }}, + "severity": {{ if eq .Severity "UNKNOWN" -}} + "Unknown" + {{- else if eq .Severity "LOW" -}} + "Low" + {{- else if eq .Severity "MEDIUM" -}} + "Medium" + {{- else if eq .Severity "HIGH" -}} + "High" + {{- else if eq .Severity "CRITICAL" -}} + "Critical" + {{- else -}} + "{{ .Severity }}" + {{- end }}, + "solution": {{ if .FixedVersion -}} + "Upgrade {{ .PkgName }} to {{ .FixedVersion }}" + {{- else -}} + "No solution provided" + {{- end }}, + "location": { + "dependency": { + "package": { + "name": "{{ .PkgName }}" + }, + "version": "{{ .InstalledVersion }}" + }, + {{- /* TODO: No mapping available - https://github.com/aquasecurity/trivy/issues/332 */}} + "operating_system": "Unknown", + "image": "{{ $image }}" + }, + "identifiers": [ + { + {{- /* TODO: Type not extractable - https://github.com/aquasecurity/trivy-db/pull/24 */}} + "type": "cve", + "name": "{{ .VulnerabilityID }}", + "value": "{{ .VulnerabilityID }}" + {{- /* cf. https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/e3d280d7f0862ca66a1555ea8b24016a004bb914/dist/container-scanning-report-format.json#L157-179 */}} + {{- if .PrimaryURL | regexMatch "^(https?|ftp)://.+" -}}, + "url": "{{ .PrimaryURL }}" + {{- end }} + } + ], + "links": [ + {{- $l_first := true -}} + {{- range .References -}} + {{- if $l_first -}} + {{- $l_first = false }} + {{- else -}} + , + {{- end -}} + {{- if . | regexMatch "^(https?|ftp)://.+" -}} + { + "url": "{{ . }}" + } + {{- else -}} + {{- $l_first = true }} + {{- end -}} + {{- end }} + ] + } + {{- end -}} + {{- end }} + ], + "remediations": [] +} diff --git a/contrib/html.tpl b/contrib/html.tpl new file mode 100644 index 000000000000..e92b1b1cf7f5 --- /dev/null +++ b/contrib/html.tpl @@ -0,0 +1,148 @@ + + + + +{{- if . }} + + {{- escapeXML ( index . 0 ).Target }} - Trivy Report - {{ now }} + + + +

{{- escapeXML ( index . 0 ).Target }} - Trivy Report - {{ now }}

+ + {{- range . }} + + {{- if (eq (len .Vulnerabilities) 0) }} + + {{- else }} + + + + + + + + + {{- range .Vulnerabilities }} + + + + + + + + + {{- end }} + {{- end }} + {{- if (eq (len .Misconfigurations ) 0) }} + + {{- else }} + + + + + + + + {{- range .Misconfigurations }} + + + + + + + + {{- end }} + {{- end }} + {{- end }} +
{{ .Type | toString | escapeXML }}
No Vulnerabilities found
PackageVulnerability IDSeverityInstalled VersionFixed VersionLinks
{{ escapeXML .PkgName }}{{ escapeXML .VulnerabilityID }}{{ escapeXML .Vulnerability.Severity }}{{ escapeXML .InstalledVersion }}{{ escapeXML .FixedVersion }}
No Misconfigurations found
TypeMisconf IDCheckSeverityMessage
{{ escapeXML .Type }}{{ escapeXML .ID }}{{ escapeXML .Title }}{{ escapeXML .Severity }}
+{{- else }} + + +

Trivy Returned Empty Report

+{{- end }} + + diff --git a/contrib/install.sh b/contrib/install.sh new file mode 100755 index 000000000000..8b1627c96418 --- /dev/null +++ b/contrib/install.sh @@ -0,0 +1,422 @@ +#!/bin/sh +set -e +# Code generated by godownloader on 2020-01-14T10:03:29Z. DO NOT EDIT. +# + +usage() { + this=$1 + cat </dev/null +} +echoerr() { + echo "$@" 1>&2 +} +log_prefix() { + echo "$0" +} +_logp=6 +log_set_priority() { + _logp="$1" +} +log_priority() { + if test -z "$1"; then + echo "$_logp" + return + fi + [ "$1" -le "$_logp" ] +} +log_tag() { + case $1 in + 0) echo "emerg" ;; + 1) echo "alert" ;; + 2) echo "crit" ;; + 3) echo "err" ;; + 4) echo "warning" ;; + 5) echo "notice" ;; + 6) echo "info" ;; + 7) echo "debug" ;; + *) echo "$1" ;; + esac +} +log_debug() { + log_priority 7 || return 0 + echo "$(log_prefix)" "$(log_tag 7)" "$@" +} +log_info() { + log_priority 6 || return 0 + echo "$(log_prefix)" "$(log_tag 6)" "$@" +} +log_err() { + log_priority 3 || return 0 + echoerr "$(log_prefix)" "$(log_tag 3)" "$@" +} +log_crit() { + log_priority 2 || return 0 + echoerr "$(log_prefix)" "$(log_tag 2)" "$@" +} +uname_os() { + os=$(uname -s | tr '[:upper:]' '[:lower:]') + case "$os" in + cygwin_nt*) os="windows" ;; + mingw*) os="windows" ;; + msys_nt*) os="windows" ;; + esac + echo "$os" +} +uname_arch() { + arch=$(uname -m) + case $arch in + x86_64) arch="amd64" ;; + x86) arch="386" ;; + i686) arch="386" ;; + i386) arch="386" ;; + ppc64le) arch="ppc64le" ;; + aarch64) arch="arm64" ;; + armv5*) arch="armv5" ;; + armv6*) arch="armv6" ;; + armv7*) arch="armv7" ;; + s390*) arch="s390x" ;; + esac + echo ${arch} +} +uname_os_check() { + os=$(uname_os) + case "$os" in + darwin) return 0 ;; + dragonfly) return 0 ;; + freebsd) return 0 ;; + linux) return 0 ;; + android) return 0 ;; + nacl) return 0 ;; + netbsd) return 0 ;; + openbsd) return 0 ;; + plan9) return 0 ;; + solaris) return 0 ;; + windows) return 0 ;; + esac + log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" + return 1 +} +uname_arch_check() { + arch=$(uname_arch) + case "$arch" in + 386) return 0 ;; + amd64) return 0 ;; + arm64) return 0 ;; + armv5) return 0 ;; + armv6) return 0 ;; + armv7) return 0 ;; + ppc64) return 0 ;; + ppc64le) return 0 ;; + mips) return 0 ;; + mipsle) return 0 ;; + mips64) return 0 ;; + mips64le) return 0 ;; + s390x) return 0 ;; + amd64p32) return 0 ;; + esac + log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" + return 1 +} +untar() { + tarball=$1 + case "${tarball}" in + *.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;; + *.tar) tar --no-same-owner -xf "${tarball}" ;; + *.zip) unzip "${tarball}" ;; + *) + log_err "untar unknown archive format for ${tarball}" + return 1 + ;; + esac +} +http_download_curl() { + local_file=$1 + source_url=$2 + header=$3 + if [ -z "$header" ]; then + code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url") + else + code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") + fi + if [ "$code" != "200" ]; then + log_debug "http_download_curl received HTTP status $code" + return 1 + fi + return 0 +} +http_download_wget() { + local_file=$1 + source_url=$2 + header=$3 + if [ -z "$header" ]; then + wget -q -O "$local_file" "$source_url" + else + wget -q --header "$header" -O "$local_file" "$source_url" + fi +} +http_download() { + log_debug "http_download $2" + if is_command curl; then + http_download_curl "$@" + return + elif is_command wget; then + http_download_wget "$@" + return + fi + log_crit "http_download unable to find wget or curl" + return 1 +} +http_copy() { + tmp=$(mktemp) + http_download "${tmp}" "$1" "$2" || return 1 + body=$(cat "$tmp") + rm -f "${tmp}" + echo "$body" +} +github_release() { + owner_repo=$1 + version=$2 + test -z "$version" && version="latest" + giturl="https://github.com/${owner_repo}/releases/${version}" + json=$(http_copy "$giturl" "Accept:application/json") + test -z "$json" && return 1 + version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') + test -z "$version" && return 1 + echo "$version" +} +hash_sha256() { + TARGET=${1:-/dev/stdin} + if is_command gsha256sum; then + hash=$(gsha256sum "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command sha256sum; then + hash=$(sha256sum "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command shasum; then + hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command openssl; then + hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f a + else + log_crit "hash_sha256 unable to find command to compute sha-256 hash" + return 1 + fi +} +hash_sha256_verify() { + TARGET=$1 + checksums=$2 + if [ -z "$checksums" ]; then + log_err "hash_sha256_verify checksum file not specified in arg2" + return 1 + fi + BASENAME=${TARGET##*/} + want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) + if [ -z "$want" ]; then + log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" + return 1 + fi + got=$(hash_sha256 "$TARGET") + if [ "$want" != "$got" ]; then + log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" + return 1 + fi +} +cat /dev/null < + +{{- range . -}} +{{- $failures := len .Vulnerabilities }} + + {{- if not (eq .Type "") }} + + + + {{- end -}} + {{ range .Vulnerabilities }} + + {{ escapeXML .Description }} + + {{- end }} + + +{{- if .MisconfSummary }} + +{{- else }} + +{{- end }} + {{- if not (eq .Type "") }} + + + + {{- end -}} + {{ range .Misconfigurations }} + + {{- if (eq .Status "FAIL") }} + {{ escapeXML .Description }} + {{- end }} + + {{- end }} + +{{- end }} + diff --git a/docs/build/Dockerfile b/docs/build/Dockerfile new file mode 100644 index 000000000000..8a90ffcc8fb2 --- /dev/null +++ b/docs/build/Dockerfile @@ -0,0 +1,10 @@ +FROM squidfunk/mkdocs-material:9.4.6 + +## If you want to see exactly the same version as is published to GitHub pages +## use a private image for insiders, which requires authentication. + +# docker login -u ${GITHUB_USERNAME} -p ${GITHUB_TOKEN} ghcr.io +# FROM ghcr.io/squidfunk/mkdocs-material-insiders + +COPY requirements.txt . +RUN pip install -r requirements.txt diff --git a/docs/build/requirements.txt b/docs/build/requirements.txt new file mode 100644 index 000000000000..bf20fa1e736d --- /dev/null +++ b/docs/build/requirements.txt @@ -0,0 +1,30 @@ +click==8.3.1 +csscompressor==0.9.5 +ghp-import==2.1.0 +htmlmin==0.1.12 +importlib-metadata==8.7.1 +Jinja2==3.1.6 +jsmin==3.0.1 +Markdown==3.10.2 +MarkupSafe==3.0.3 +mergedeep==1.3.4 +mike==2.1.4 +mkdocs==1.6.1 +mkdocs-macros-plugin==1.5.0 +mkdocs-material==9.7.5 +mkdocs-material-extensions==1.3.1 +mkdocs-minify-plugin==0.8.0 +mkdocs-redirects==1.2.2 +packaging==26.0 +Pygments==2.19.2 +pymdown-extensions==10.21 +pyparsing==3.3.2 +python-dateutil==2.9.0.post0 +PyYAML==6.0.3 +pyyaml-env-tag==1.1 +six==1.17.0 +termcolor==3.3.0 +verspec==0.1.0 +watchdog==6.0.0 +zipp==3.23.0 + diff --git a/docs/community/contribute/discussion.md b/docs/community/contribute/discussion.md new file mode 100644 index 000000000000..b7e1a2d7294d --- /dev/null +++ b/docs/community/contribute/discussion.md @@ -0,0 +1,49 @@ +# Discussions +Thank you for taking interest in contributing to Trivy! + +Trivy uses [GitHub Discussion](https://github.com/aquasecurity/trivy/discussions) for bug reports, feature requests, and questions. +If maintainers decide to accept a new feature or confirm that it is a bug, they will close the discussion and create a [GitHub Issue](https://github.com/aquasecurity/trivy/issues) associated with that discussion. + +- Feel free to open discussions for any reason. When you open a new discussion, you'll have to select a discussion category as described below. +- Please spend a small amount of time giving due diligence to the issue/discussion tracker. Your discussion might be a duplicate. If it is, please add your comment to the existing issue/discussion. +- Remember that users might search for your issue/discussion in the future, so please give it a meaningful title to help others. +- The issue should clearly explain the reason for opening, the proposal if you have any, and any relevant technical information. + +There are 4 categories: + +- 💡 [Ideas](https://github.com/aquasecurity/trivy/discussions/categories/ideas) + - Share ideas for new features +- 🔎 [False Detection](https://github.com/aquasecurity/trivy/discussions/categories/false-detection) + - Report false positives/negatives +- 🛠[Bugs](https://github.com/aquasecurity/trivy/discussions/categories/bugs) + - Report something that is not working as expected +- 🙠[Q&A](https://github.com/aquasecurity/trivy/discussions/categories/q-a) + - Ask the community for help + +!!! note + If you find any false positives or false negatives, please make sure to report them under the "False Detection" category, not "Bugs". + +## False detection +Trivy depends on [multiple data sources](https://aquasecurity.github.io/trivy/latest/docs/scanner/vulnerability/#data-sources). +Sometime these databases contain mistakes. + +If Trivy can't detect any CVE-IDs or shows false positive result, at first please follow the next steps: + +1. Run Trivy with `-f json` that shows data sources. +2. According to the shown data source, make sure that the security advisory in the data source is correct. + +If the data source is correct and Trivy shows wrong results, please raise an issue on Trivy. + +### GitHub Advisory Database +Visit [here](https://github.com/advisories) and search CVE-ID. + +If you find a problem, it'll be nice to fix it: [How to contribute to a GitHub security advisory](https://github.blog/2022-02-22-github-advisory-database-now-open-to-community-contributions/) + +### GitLab Advisory Database +Visit [here](https://advisories.gitlab.com/) and search CVE-ID. + +If you find a problem, it'll be nice to fix it: [Create an issue to GitLab Advisory Database](https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/issues/new) + +### Red Hat CVE Database +Visit [here](https://access.redhat.com/security/security-updates/?cwe=476#/cve) and search CVE-ID. + diff --git a/docs/community/contribute/issue.md b/docs/community/contribute/issue.md new file mode 100644 index 000000000000..c402f73b9cfc --- /dev/null +++ b/docs/community/contribute/issue.md @@ -0,0 +1,7 @@ +# Issues +Thank you for taking interest in contributing to Trivy! + +Trivy uses [GitHub Discussion](./discussion.md) for bug reports, feature requests, and questions. + +!!! warning + Issues created by non-maintainers will be immediately closed. \ No newline at end of file diff --git a/docs/community/contribute/pr.md b/docs/community/contribute/pr.md new file mode 100644 index 000000000000..2538cce3327f --- /dev/null +++ b/docs/community/contribute/pr.md @@ -0,0 +1,218 @@ +Thank you for taking interest in contributing to Trivy! + +1. Every Pull Request should have an associated bug or feature issue unless you are fixing a trivial documentation issue. +1. Please add the associated Issue link in the PR description. +1. Your PR is more likely to be accepted if it focuses on just one change. +1. There's no need to add or tag reviewers. +1. If a reviewer commented on your code or asked for changes, please remember to respond with comment. Do not mark discussion as resolved. It's up to reviewer to mark it resolved (in case if suggested fix addresses problem properly). PRs with unresolved issues should not be merged (even if the comment is unclear or requires no action from your side). +1. Please include a comment with the results before and after your change. +1. Your PR is more likely to be accepted if it includes tests (We have not historically been very strict about tests, but we would like to improve this!). +1. If your PR affects the user experience in some way, please update the README.md and the CLI help accordingly. + +## Development +Install the necessary tools for development by following their respective installation instructions. + +- [Go](https://go.dev/doc/install) +- [Mage](https://magefile.org/) + +### Build +After making changes to the Go source code, build the project with the following command: + +```shell +$ mage build +$ ./trivy -h +``` + +### Lint +You must pass the linter checks: + +```shell +$ mage lint:run +``` + +Additionally, you need to have run `go mod tidy`, so execute the following command as well: + +```shell +$ mage tidy +``` + +To autofix linters use the following command: +```shell +$ mage lint:fix +``` + +### Unit tests +Your PR must pass all the unit tests. You can test it as below. + +``` +$ mage test:unit +``` + +### Integration tests +Your PR must pass all the integration tests. You can test it as below. + +``` +$ mage test:integration +``` + +### Documentation +If you update CLI flags, you need to generate the CLI references. +The test will fail if they are not up-to-date. + +```shell +$ mage docs:generate +``` + +You can build the documents as below and view it at http://localhost:8000. + +``` +$ mage docs:serve +``` + +## Title +It is not that strict, but we use the title conventions in this repository. +Each commit message doesn't have to follow the conventions as long as it is clear and descriptive since it will be squashed and merged. + +### Format of the title + +``` +(): +``` + +The `type` and `scope` should always be lowercase as shown below. + +**Allowed `` values:** + +- **feat** for a new feature for the user, not a new feature for build script. Such commit will trigger a release bumping a MINOR version. +- **fix** for a bug fix for the user, not a fix to a build script. Such commit will trigger a release bumping a PATCH version. +- **perf** for performance improvements. Such commit will trigger a release bumping a PATCH version. +- **docs** for changes to the documentation. +- **style** for formatting changes, missing semicolons, etc. +- **refactor** for refactoring production code, e.g. renaming a variable. +- **test** for adding missing tests, refactoring tests; no production code change. +- **build** for updating build configuration, development tools or other changes irrelevant to the user. +- **chore** for updates that do not apply to the above, such as dependency updates. +- **ci** for changes to CI configuration files and scripts +- **revert** for revert to a previous commit + +**Allowed `` values:** + +checks: + +- vuln +- misconf +- secret +- license + +mode: + +- image +- fs +- repo +- sbom +- k8s +- server +- aws +- vm + +os: + +- alpine +- redhat +- alma +- rocky +- mariner +- oracle +- debian +- ubuntu +- amazon +- suse +- photon +- distroless + +language: + +- ruby +- php +- python +- nodejs +- rust +- dotnet +- java +- go +- elixir +- dart + +vuln: + +- os +- lang + +config: + +- kubernetes +- dockerfile +- terraform +- cloudformation + +container + +- docker +- podman +- containerd +- oci + +cli: + +- cli +- flag + +SBOM: + +- cyclonedx +- spdx +- purl + +others: + +- helm +- report +- db +- parser +- deps + +The `` can be empty (e.g. if the change is a global or difficult to assign to a single component), in which case the parentheses are omitted. + +### Example titles + +``` +feat(alma): add support for AlmaLinux +``` + +``` +fix(oracle): handle advisories with ksplice versions +``` + +``` +docs(misconf): add comparison with Conftest and TFsec +``` + +``` +chore(deps): bump go.uber.org/zap from 1.19.1 to 1.20.0 +``` + +**NOTE**: please do not use `chore(deps): update fanal` and something like that if you add new features or fix bugs in Trivy-related projects. +The PR title should describe what the PR adds or fixes even though it just updates the dependency in Trivy. + +## Commits + + +## Understand where your pull request belongs + +Trivy is composed of several repositories that work together: + +- [Trivy](https://github.com/aquasecurity/trivy) is the client-side, user-facing, command line tool. +- [vuln-list](https://github.com/aquasecurity/vuln-list) is a vulnerability database, aggregated from different sources, and normalized for easy consumption. Think of this as the "server" side of the trivy command line tool. **There should be no pull requests to this repo** +- [vuln-list-update](https://github.com/aquasecurity/vuln-list-update) is the code that maintains the vuln-list database. +- [trivy-db](https://github.com/aquasecurity/trivy-db) maintains the vulnerability database pulled by Trivy CLI. +- [go-dep-parser](https://github.com/aquasecurity/go-dep-parser) is a library for parsing lock files such as package-lock.json and Gemfile.lock. diff --git a/docs/community/maintainer/help-wanted.md b/docs/community/maintainer/help-wanted.md new file mode 100644 index 000000000000..41111731a9fc --- /dev/null +++ b/docs/community/maintainer/help-wanted.md @@ -0,0 +1,78 @@ +# Overview + +We use two labels [help wanted](#help-wanted) and [good first +issue](#good-first-issue) to identify issues that have been specially groomed +for new contributors. The `good first issue` label is a subset of `help wanted` +label, indicating that members have committed to providing extra assistance for +new contributors. All `good first issue` items also have the `help wanted` +label. + +## Help Wanted + +Items marked with the `help wanted` label need to ensure that they are: + +- **Low Barrier to Entry** + + It should be tractable for new contributors. Documentation on how that type of + change should be made should already exist. + +- **Clear Task** + + The task is agreed upon and does not require further discussions in the + community. Call out if that area of code is untested and requires new + fixtures. + + API / CLI behavior is decided and included in the OP issue, for example: "The + new command syntax is `trivy --format yaml IMAGE_NAME`"_ with + expected validations called out. + +- **Goldilocks priority** + + Not too high that a core contributor should do it, but not too low that it + isn't useful enough for a core contributor to spend time to review it, answer + questions, help get it into a release, etc. + +- **Up-To-Date** + + Often these issues become obsolete and have already been done, are no longer + desired, no longer make sense, have changed priority or difficulty , etc. + + +## Good First Issue + +Items marked with the `good first issue` label are intended for _first-time +contributors_. It indicates that members will keep an eye out for these pull +requests and shepherd it through our processes. + +These items need to ensure that they follow the guidelines for `help wanted` +labels (above) in addition to meeting the following criteria: + +- **No Barrier to Entry** + + The task is something that a new contributor can tackle without advanced + setup, or domain knowledge. + +- **Solution Explained** + + The recommended solution is clearly described in the issue. + +- **Provides Context** + + If background knowledge is required, this should be explicitly mentioned and a + list of suggested readings included. + +- **Gives Examples** + + Link to examples of similar implementations so new contributors have a + reference guide for their changes. + +- **Identifies Relevant Code** + + The relevant code and tests to be changed should be linked in the issue. + +- **Ready to Test** + + There should be existing tests that can be modified, or existing test cases + fit to be copied. If the area of code doesn't have tests, before labeling the + issue, add a test fixture. This prep often makes a great `help wanted` task! + diff --git a/docs/community/maintainer/triage.md b/docs/community/maintainer/triage.md new file mode 100644 index 000000000000..4d72fda693c9 --- /dev/null +++ b/docs/community/maintainer/triage.md @@ -0,0 +1,198 @@ +# Triage + +Triage is an important part of maintaining the health of the trivy repo. +A well organized repo allows maintainers to prioritize feature requests, fix bugs, and respond to users facing difficulty with the tool as quickly as possible. + +Triage includes: + +- Labeling issues +- Responding to issues +- Closing issues + +# Daily Triage +Daily triage has two goals: + +1. Responsiveness for new issues +1. Responsiveness when explicitly requested information was provided + +It covers: + +1. Issues without a `kind/` or `triage/` label +1. Issues without a `priority/` label +1. `triage/needs-information` issues which the user has followed up on, and now require a response. + +## Categorization + +The most important level of categorizing the issue is defining what type it is. +We typically want at least one of the following labels on every issue, and some issues may fall into multiple categories: + +- `triage/support` - The default for most incoming issues +- `kind/bug` - When it’s a bug or we aren’t delivering the best user experience + +Other possibilities: +- `kind/feature`- Identify new feature requests +- `kind/testing` - Update or fix unit/integration tests +- `kind/cleanup` - Cleaning up/refactoring the codebase +- `kind/documentation` - Updates or additions to trivy documentation + +If the issue is specific to a driver for OS packages or libraries: + +**co/[driver for OS packages]** + + - `co/alpine` + - `co/amazon` + - `co/debian` + - `co/oracle` + - `co/photon` + - `co/redhat` + - `co/suse` + - `co/ubuntu` + +**co/[driver for libraries of programming languages]** + + - `co/bundler` + - `co/cargo` + - `co/composer` + - `co/npm` + - `co/yarn` + - `co/pipenv` + - `co/poetry` + + +**Help wanted?** + +`Good First Issue` - bug has a proposed solution, can be implemented w/o further discussion. + +`Help wanted` - if the bug could use help from a contributor + + +## Prioritization +If the issue is not `triage/support`, it needs a priority label. + +`priority/critical-urgent` - someones top priority ASAP, such as security issue, user-visible bug, or build breakage. Rarely used. + +`priority/important-soon`: in time for the next two releases. It should be attached to a milestone. + +`priority/important-longterm`: 2-4 releases from now + +`priority/backlog`: agreed that this would be good to have, but no one is available at the moment. Consider tagging as `help wanted` + +`priority/awaiting-more-evidence`: may be useful, but there is not yet enough support. + + +# Weekly Triage + +Weekly triage has three goals: + +1. Catching up on unresponded issues +1. Reviewing and closing PR’s +1. Closing stale issues + + +## Post-Release Triage + +Post-release triage occurs after a major release (around every 4-6 weeks). +It focuses on: + +1. Closing bugs that have been resolved by the release +1. Reprioritizing bugs that have not been resolved by the release +1. Letting users know if we believe that there is still an issue + +This includes reviewing: + +1. Every issue that hasn’t been touched in the last 2 days +1. Re-evaluation of long-term issues +1. Re-evaluation of short-term issues + + +## Responding to Issues + +### Needs More Information +A sample response to ask for more info: + +> I don’t yet have a clear way to replicate this issue. Do you mind adding some additional details. Here is additional information that would be helpful: +> +> \* The exact `trivy` command line used +> +> \* The exact image you want to scan +> +> \* The full output of the `trivy` command, preferably with `--debug` for extra logging. +> +> +> Thank you for sharing your experience! + + +Then: Label with `triage/needs-information`. + +### Issue might be resolved +If you think a release may have resolved an issue, ask the author to see if their issue has been resolved: + +> Could you please check to see if trivy addresses this issue? We've made some changes with how this is handled, and improved the trivy logs output to help us debug tricky cases like this. + +Then: Label with `triage/needs-information`. + + +## Closing with Care + +Issues typically need to be closed for the following reasons: + +- The issue has been addressed +- The issue is a duplicate of an existing issue +- There has been a lack of information over a long period of time + +In any of these situations, we aim to be kind when closing the issue, and offer the author action items should they need to reopen their issue or still require a solution. + +Samples responses for these situations include: + +### Issue has been addressed + +>@author: I believe this issue is now addressed by trivy v1.0.0, as it . If you still see this issue with trivy v1.0 or higher, please reopen this issue. +> +>Thank you for reporting this issue! + +Then: Close the issue + +### Duplicate Issue + +>This issue appears to be a duplicate of #X, do you mind if we move the conversation there? +> +>This way we can centralize the content relating to the issue. If you feel that this issue is not in fact a duplicate, please re-open it. If you have additional information to share, please add it to the new issue. +> +>Thank you for reporting this! + +Then: Label with `triage/duplicate` and close the issue. + +### Lack of Information +If an issue hasn't been active for more than four weeks, and the author has been pinged at least once, then the issue can be closed. + +>Hey @author -- hopefully it's OK if I close this - there wasn't enough information to make it actionable, and some time has already passed. If you are able to provide additional details, you may reopen it at any point. +> +>Here is additional information that may be helpful to us: +> +>\* Whether the issue occurs with the latest trivy release +> +>\* The exact `trivy` command line used +> +>\* The exact image you want to scan +> +>\* The full output of the `trivy` command, preferably with `--debug` for extra logging. +> +> +>Thank you for sharing your experience! + +Then: Close the issue. + +## Help Wanted issues + +We use two labels [help wanted](https://github.com/aquasecurity/trivy/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) +and [good first issue](https://github.com/aquasecurity/trivy/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) +to identify issues that have been specially groomed for new contributors. + +We have specific [guidelines](/docs/community/maintainer/help-wanted.md) +for how to use these labels. If you see an issue that satisfies these +guidelines, you can add the `help wanted` label and the `good first issue` label. +Please note that adding the `good first issue` label must also +add the `help wanted` label. + +If an issue has these labels but does not satisfy the guidelines, please +ask for more details to be added to the issue or remove the labels. diff --git a/docs/community/principles.md b/docs/community/principles.md new file mode 100644 index 000000000000..8f369429ced5 --- /dev/null +++ b/docs/community/principles.md @@ -0,0 +1,53 @@ +# Trivy Project Principles +This document outlines the guiding principles and governance framework for the Trivy project. + +## Core Principles +Trivy is a security scanner focused on static analysis and designed with simplicity and security at its core. +All new proposals to the project must adhere to the following principles. + +### Static Analysis (No Runtime Required) +Trivy operates without requiring container or VM image startups, eliminating the need for Docker or similar runtimes, except for scanning images stored within a container runtime. +This approach enhances security and efficiency by minimizing dependencies. + +### External Dependency Free (Single Binary) +Operating as a single binary, Trivy is independent of external environments and avoids executing external OS commands or processes. +If specific functionality, like Maven's, is needed, Trivy opts for internal reimplementations or processing outputs of the tool without direct execution of external tools. + +This approach obviously requires more effort but significantly reduces security risks associated with executing OS commands and dependency errors due to external environment versions. +Simplifying the scanner's use by making it operational immediately upon binary download facilitates easier initiation of scans. + +### No Setup Required +Trivy must be ready to use immediately after installation. +It's unacceptable for Trivy not to function without setting up a database or writing configuration files by default. +Such setups should only be necessary for users requiring specific customizations. + +Security often isn't a top priority for many organizations and can be easily deferred. +Trivy aims to lower the barrier to entry by simplifying the setup process, making it easier for users to start securing their projects. + +### Security Focus +Trivy prioritizes the identification of security issues, excluding features unrelated to security, such as performance metrics or content listings of container images. +It can, however, produce and output intermediate representations like SBOMs for comprehensive security assessments. + +Trivy serves as a tool with opinions on security, used to warn users about potential issues. + +### Detecting Unintended States +Trivy is designed to detect unintended vulnerable states in projects, such as the use of vulnerable versions of dependencies or misconfigurations in Infrastructure as Code (IaC) that may unintentionally expose servers to the internet. +The focus is on identifying developer mistakes or undesirable states, not on detecting intentional attacks, such as malicious images and malware. + +## Out of Scope Features +Aqua Security offers a premium version with several features not available in the open-source Trivy project. +While detailed information can be found [here][trivy-aqua], it's beneficial to highlight specific functionalities frequently inquired about: + +### Runtime Security +As mentioned in [the Core Principles](#static-analysis-no-runtime-required), Trivy is a static analysis security scanner, making runtime security outside its scope. +Runtime security needs are addressed by [Tracee][tracee] or [the commercial version of Aqua Security](). + +### Intentional Attacks +As mentioned in [the Core Principles](#detecting-unintended-states), detection of intentional attacks, such as malware or malicious container images, is not covered by Trivy and is supported in [the commercial version][aqua]. + +### User Interface +Trivy primarily operates via CLI for displaying results, with a richer UI available in [the commercial version][aqua]. + +[trivy-aqua]: https://github.com/aquasecurity/resources/blob/main/trivy-aqua.md +[tracee]: https://github.com/aquasecurity/tracee +[aqua]: https://www.aquasec.com/ \ No newline at end of file diff --git a/docs/docs/advanced/air-gap.md b/docs/docs/advanced/air-gap.md new file mode 100644 index 000000000000..8793defbb5c5 --- /dev/null +++ b/docs/docs/advanced/air-gap.md @@ -0,0 +1,142 @@ +# Air-Gapped Environment + +Trivy can be used in air-gapped environments. Note that an allowlist is [here][allowlist]. + +## Air-Gapped Environment for vulnerabilities + +### Download the vulnerability database +At first, you need to download the vulnerability database for use in air-gapped environments. + +=== "Trivy" + + ``` + TRIVY_TEMP_DIR=$(mktemp -d) + trivy --cache-dir $TRIVY_TEMP_DIR image --download-db-only + tar -cf ./db.tar.gz -C $TRIVY_TEMP_DIR/db metadata.json trivy.db + rm -rf $TRIVY_TEMP_DIR + ``` + +=== "oras >= v0.13.0" + Please follow [oras installation instruction][oras]. + + Download `db.tar.gz`: + + ``` + $ oras pull ghcr.io/aquasecurity/trivy-db:2 + ``` + +=== "oras < v0.13.0" + Please follow [oras installation instruction][oras]. + + Download `db.tar.gz`: + + ``` + $ oras pull -a ghcr.io/aquasecurity/trivy-db:2 + ``` + +### Download the Java index database[^1] +Java users also need to download the Java index database for use in air-gapped environments. + +!!! note + You container image may contain JAR files even though you don't use Java directly. + In that case, you also need to download the Java index database. + +=== "Trivy" + + ``` + TRIVY_TEMP_DIR=$(mktemp -d) + trivy --cache-dir $TRIVY_TEMP_DIR image --download-java-db-only + tar -cf ./javadb.tar.gz -C $TRIVY_TEMP_DIR/java-db metadata.json trivy-java.db + rm -rf $TRIVY_TEMP_DIR + ``` +=== "oras >= v0.13.0" + Please follow [oras installation instruction][oras]. + + Download `javadb.tar.gz`: + + ``` + $ oras pull ghcr.io/aquasecurity/trivy-java-db:1 + ``` + +=== "oras < v0.13.0" + Please follow [oras installation instruction][oras]. + + Download `javadb.tar.gz`: + + ``` + $ oras pull -a ghcr.io/aquasecurity/trivy-java-db:1 + ``` + + +### Transfer the DB files into the air-gapped environment +The way of transfer depends on the environment. + +=== "Vulnerability db" + ``` + $ rsync -av -e ssh /path/to/db.tar.gz [user]@[host]:dst + ``` + +=== "Java index db[^1]" + ``` + $ rsync -av -e ssh /path/to/javadb.tar.gz [user]@[host]:dst + ``` + +### Put the DB files in Trivy's cache directory +You have to know where to put the DB files. The following command shows the default cache directory. + +``` +$ ssh user@host +$ trivy -h | grep cache + --cache-dir value cache directory (default: "/home/myuser/.cache/trivy") [$TRIVY_CACHE_DIR] +``` +=== "Vulnerability db" + Put the DB file in the cache directory + `/db`. + + ``` + $ mkdir -p /home/myuser/.cache/trivy/db + $ cd /home/myuser/.cache/trivy/db + $ tar xvf /path/to/db.tar.gz -C /home/myuser/.cache/trivy/db + x trivy.db + x metadata.json + $ rm /path/to/db.tar.gz + ``` + +=== "Java index db[^1]" + Put the DB file in the cache directory + `/java-db`. + + ``` + $ mkdir -p /home/myuser/.cache/trivy/java-db + $ cd /home/myuser/.cache/trivy/java-db + $ tar xvf /path/to/javadb.tar.gz -C /home/myuser/.cache/trivy/java-db + x trivy-java.db + x metadata.json + $ rm /path/to/javadb.tar.gz + ``` + + + +In an air-gapped environment it is your responsibility to update the Trivy databases on a regular basis, so that the scanner can detect recently-identified vulnerabilities. + +### Run Trivy with the specific flags. +In an air-gapped environment, you have to specify `--skip-db-update` and `--skip-java-db-update`[^1] so that Trivy doesn't attempt to download the latest database files. +In addition, if you want to scan `pom.xml` dependencies, you need to specify `--offline-scan` since Trivy tries to issue API requests for scanning Java applications by default. + +``` +$ trivy image --skip-db-update --skip-java-db-update --offline-scan alpine:3.12 +``` + +## Air-Gapped Environment for misconfigurations + +No special measures are required to detect misconfigurations in an air-gapped environment. + +### Run Trivy with `--skip-policy-update` option +In an air-gapped environment, specify `--skip-policy-update` so that Trivy doesn't attempt to download the latest misconfiguration policies. + +``` +$ trivy conf --skip-policy-update /path/to/conf +``` + +[allowlist]: ../references/troubleshooting.md +[oras]: https://oras.land/cli/ + +[^1]: This is only required to scan `jar` files. More information about `Java index db` [here](../coverage/language/java.md) diff --git a/docs/docs/advanced/container/embed-in-dockerfile.md b/docs/docs/advanced/container/embed-in-dockerfile.md new file mode 100644 index 000000000000..2c467b7ae999 --- /dev/null +++ b/docs/docs/advanced/container/embed-in-dockerfile.md @@ -0,0 +1,28 @@ +# Embed in Dockerfile + +Scan your image as part of the build process by embedding Trivy in the +Dockerfile. This approach can be used to update Dockerfiles currently using +Aqua’s [Microscanner][microscanner]. + +```bash +$ cat Dockerfile +FROM alpine:3.7 + +RUN apk add curl \ + && curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin \ + && trivy rootfs --exit-code 1 --no-progress / + +$ docker build -t vulnerable-image . +``` +Alternatively you can use Trivy in a multistage build. Thus avoiding the +insecure `curl | sh`. Also the image is not changed. +```bash +[...] +# Run vulnerability scan on build image +FROM build AS vulnscan +COPY --from=aquasec/trivy:latest /usr/local/bin/trivy /usr/local/bin/trivy +RUN trivy rootfs --exit-code 1 --no-progress / +[...] +``` + +[microscanner]: https://github.com/aquasecurity/microscanner diff --git a/docs/docs/advanced/container/unpacked-filesystem.md b/docs/docs/advanced/container/unpacked-filesystem.md new file mode 100644 index 000000000000..74232db9a26a --- /dev/null +++ b/docs/docs/advanced/container/unpacked-filesystem.md @@ -0,0 +1,116 @@ +# Unpacked Filesystem + +Scan an unpacked container image filesystem. + +In this case, Trivy works the same way when scanning containers + +```bash +$ docker export $(docker create alpine:3.10.2) | tar -C /tmp/rootfs -xvf - +$ trivy rootfs /tmp/rootfs +``` + +
+Result + +```bash +2021-03-08T05:22:26.378Z INFO Need to update DB +2021-03-08T05:22:26.380Z INFO Downloading DB... +20.37 MiB / 20.37 MiB [-------------------------------------------------------------------------------------------------------------------------------------] 100.00% 8.24 MiB p/s 2s +2021-03-08T05:22:30.134Z INFO Detecting Alpine vulnerabilities... + +/tmp/rootfs (alpine 3.10.2) +=========================== +Total: 20 (UNKNOWN: 0, LOW: 2, MEDIUM: 10, HIGH: 8, CRITICAL: 0) + ++--------------+------------------+----------+-------------------+---------------+---------------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++--------------+------------------+----------+-------------------+---------------+---------------------------------------+ +| libcrypto1.1 | CVE-2020-1967 | HIGH | 1.1.1c-r0 | 1.1.1g-r0 | openssl: Segmentation | +| | | | | | fault in SSL_check_chain | +| | | | | | causes denial of service | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-1967 | ++ +------------------+ + +---------------+---------------------------------------+ +| | CVE-2021-23839 | | | 1.1.1j-r0 | openssl: incorrect SSLv2 | +| | | | | | rollback protection | +| | | | | | -->avd.aquasec.com/nvd/cve-2021-23839 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2021-23840 | | | | openssl: integer | +| | | | | | overflow in CipherUpdate | +| | | | | | -->avd.aquasec.com/nvd/cve-2021-23840 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2021-23841 | | | | openssl: NULL pointer dereference | +| | | | | | in X509_issuer_and_serial_hash() | +| | | | | | -->avd.aquasec.com/nvd/cve-2021-23841 | ++ +------------------+----------+ +---------------+---------------------------------------+ +| | CVE-2019-1547 | MEDIUM | | 1.1.1d-r0 | openssl: side-channel weak | +| | | | | | encryption vulnerability | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-1547 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2019-1549 | | | | openssl: information | +| | | | | | disclosure in fork() | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-1549 | ++ +------------------+ + +---------------+---------------------------------------+ +| | CVE-2019-1551 | | | 1.1.1d-r2 | openssl: Integer overflow in RSAZ | +| | | | | | modular exponentiation on x86_64 | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-1551 | ++ +------------------+ + +---------------+---------------------------------------+ +| | CVE-2020-1971 | | | 1.1.1i-r0 | openssl: EDIPARTYNAME | +| | | | | | NULL pointer de-reference | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-1971 | ++ +------------------+----------+ +---------------+---------------------------------------+ +| | CVE-2019-1563 | LOW | | 1.1.1d-r0 | openssl: information | +| | | | | | disclosure in PKCS7_dataDecode | +| | | | | | and CMS_decrypt_set1_pkey | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-1563 | ++--------------+------------------+----------+ +---------------+---------------------------------------+ +| libssl1.1 | CVE-2020-1967 | HIGH | | 1.1.1g-r0 | openssl: Segmentation | +| | | | | | fault in SSL_check_chain | +| | | | | | causes denial of service | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-1967 | ++ +------------------+ + +---------------+---------------------------------------+ +| | CVE-2021-23839 | | | 1.1.1j-r0 | openssl: incorrect SSLv2 | +| | | | | | rollback protection | +| | | | | | -->avd.aquasec.com/nvd/cve-2021-23839 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2021-23840 | | | | openssl: integer | +| | | | | | overflow in CipherUpdate | +| | | | | | -->avd.aquasec.com/nvd/cve-2021-23840 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2021-23841 | | | | openssl: NULL pointer dereference | +| | | | | | in X509_issuer_and_serial_hash() | +| | | | | | -->avd.aquasec.com/nvd/cve-2021-23841 | ++ +------------------+----------+ +---------------+---------------------------------------+ +| | CVE-2019-1547 | MEDIUM | | 1.1.1d-r0 | openssl: side-channel weak | +| | | | | | encryption vulnerability | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-1547 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2019-1549 | | | | openssl: information | +| | | | | | disclosure in fork() | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-1549 | ++ +------------------+ + +---------------+---------------------------------------+ +| | CVE-2019-1551 | | | 1.1.1d-r2 | openssl: Integer overflow in RSAZ | +| | | | | | modular exponentiation on x86_64 | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-1551 | ++ +------------------+ + +---------------+---------------------------------------+ +| | CVE-2020-1971 | | | 1.1.1i-r0 | openssl: EDIPARTYNAME | +| | | | | | NULL pointer de-reference | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-1971 | ++ +------------------+----------+ +---------------+---------------------------------------+ +| | CVE-2019-1563 | LOW | | 1.1.1d-r0 | openssl: information | +| | | | | | disclosure in PKCS7_dataDecode | +| | | | | | and CMS_decrypt_set1_pkey | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-1563 | ++--------------+------------------+----------+-------------------+---------------+---------------------------------------+ +| musl | CVE-2020-28928 | MEDIUM | 1.1.22-r3 | 1.1.22-r4 | In musl libc through 1.2.1, | +| | | | | | wcsnrtombs mishandles particular | +| | | | | | combinations of destination buffer... | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-28928 | ++--------------+ + + + + + +| musl-utils | | | | | | +| | | | | | | +| | | | | | | +| | | | | | | ++--------------+------------------+----------+-------------------+---------------+---------------------------------------+ +``` + +
diff --git a/docs/docs/advanced/modules.md b/docs/docs/advanced/modules.md new file mode 100644 index 000000000000..f12d87b8df79 --- /dev/null +++ b/docs/docs/advanced/modules.md @@ -0,0 +1,358 @@ +# Modules + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy provides a module feature to allow others to extend the Trivy CLI without the need to change the Trivy code base. +It changes the behavior during scanning by WebAssembly. + +## Overview +Trivy modules are add-on tools that integrate seamlessly with Trivy. +They provide a way to extend the core feature set of Trivy, but without updating the Trivy binary. + +- They can be added and removed from a Trivy installation without impacting the core Trivy tool. +- They can be written in any programming language supporting WebAssembly. + - It supports only [TinyGo][tinygo] at the moment. + +You can write your own detection logic. + +- Evaluate complex vulnerability conditions like [Spring4Shell][spring4shell] +- Detect a shell script communicating with malicious domains +- Detect malicious python install script (setup.py) +- Even detect misconfigurations in WordPress setting +- etc. + +Then, you can update the scan result however you want. + +- Change a severity +- Remove a vulnerability +- Add a new vulnerability +- etc. + +Modules should be distributed in OCI registries like GitHub Container Registry. + +!!! warning + WebAssembly doesn't allow file access and network access by default. + Modules can read required files only, but cannot overwrite them. + WebAssembly is sandboxed and secure by design, but Trivy modules available in public are not audited for security. + You should install and run third-party modules at your own risk even though + +Under the hood Trivy leverages [wazero][wazero] to run WebAssembly modules without CGO. + +## Installing a Module +A module can be installed using the `trivy module install` command. +This command takes an url. It will download the module and install it in the module cache. + +Trivy adheres to the XDG specification, so the location depends on whether XDG_DATA_HOME is set. +Trivy will now search XDG_DATA_HOME for the location of the Trivy modules cache. +The preference order is as follows: + +- XDG_DATA_HOME if set and .trivy/plugins exists within the XDG_DATA_HOME dir +- $HOME/.trivy/plugins + +For example, to download the WebAssembly module, you can execute the following command: + +```bash +$ trivy module install ghcr.io/aquasecurity/trivy-module-spring4shell +``` + +## Using Modules +Once the module is installed, Trivy will load all available modules in the cache on the start of the next Trivy execution. +The modules may inject custom logic into scanning and change the result. +You can run Trivy as usual and modules are loaded automatically. + +You will see the log messages about WASM modules. + +```shell +$ trivy image ghcr.io/aquasecurity/trivy-test-images:spring4shell-jre8 +2022-06-12T12:57:13.210+0300 INFO Loading ghcr.io/aquasecurity/trivy-module-spring4shell/spring4shell.wasm... +2022-06-12T12:57:13.596+0300 INFO Registering WASM module: spring4shell@v1 +... +2022-06-12T12:57:14.865+0300 INFO Module spring4shell: Java Version: 8, Tomcat Version: 8.5.77 +2022-06-12T12:57:14.865+0300 INFO Module spring4shell: change CVE-2022-22965 severity from CRITICAL to LOW + +Java (jar) + +Total: 9 (UNKNOWN: 1, LOW: 3, MEDIUM: 2, HIGH: 3, CRITICAL: 0) + +┌──────────────────────────────────────────────────────────────┬─────────────────────┬──────────┬───────────────────┬────────────────────────┬────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├──────────────────────────────────────────────────────────────┼─────────────────────┼──────────┼───────────────────┼────────────────────────┼────────────────────────────────────────────────────────────┤ +│ org.springframework.boot:spring-boot (helloworld.war) │ CVE-2022-22965 │ LOW │ 2.6.3 │ 2.5.12, 2.6.6 │ spring-framework: RCE via Data Binding on JDK 9+ │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-22965 │ +├──────────────────────────────────────────────────────────────┼─────────────────────┼──────────┼───────────────────┼────────────────────────┼────────────────────────────────────────────────────────────┤ +...(snip)... +``` + +In the above example, the Spring4Shell module changed the severity from CRITICAL to LOW because the application doesn't satisfy one of conditions. + +## Uninstalling Modules +Specify a module repository with `trivy module uninstall` command. + +```bash +$ trivy module uninstall ghcr.io/aquasecurity/trivy-module-spring4shell +``` + +## Building Modules +It supports TinyGo only at the moment. + +### TinyGo +Trivy provides Go SDK including three interfaces. +Your own module needs to implement either or both `Analyzer` and `PostScanner` in addition to `Module`. + +```go +type Module interface { + Version() int + Name() string +} + +type Analyzer interface { + RequiredFiles() []string + Analyze(filePath string) (*serialize.AnalysisResult, error) +} + +type PostScanner interface { + PostScanSpec() serialize.PostScanSpec + PostScan(serialize.Results) (serialize.Results, error) +} +``` + +In the following tutorial, it creates a WordPress module that detects a WordPress version and a critical vulnerability accordingly. + +!!! tips + You can use logging functions such as `Debug` and `Info` for debugging. + See [examples](#examples) for the detail. + +#### Initialize your module +Replace the repository name with yours. + +``` +$ go mod init github.com/aquasecurity/trivy-module-wordpress +``` + +#### Module interface +`Version()` returns your module version and should be incremented after updates. +`Name()` returns your module name. + +```go +package main + +const ( + version = 1 + name = "wordpress-module" +) + +type WordpressModule struct{ + // Cannot define fields as modules can't keep state. +} + +func (WordpressModule) Version() int { + return version +} + +func (WordpressModule) Name() string { + return name +} +``` + +!!! info + A struct cannot have any fields. Each method invocation is performed in different states. + +#### Analyzer interface +If you implement the `Analyzer` interface, `Analyze` method is called when the file path is matched to file patterns returned by `RequiredFiles()`. +A file pattern must be a regular expression. The syntax detail is [here][regexp]. + +`Analyze` takes the matched file path, then the file can be opened by `os.Open()`. + +```go +const typeWPVersion = "wordpress-version" + +func (WordpressModule) RequiredFiles() []string { + return []string{ + `wp-includes\/version.php`, + } +} + +func (WordpressModule) Analyze(filePath string) (*serialize.AnalysisResult, error) { + f, err := os.Open(filePath) // e.g. filePath: /usr/src/wordpress/wp-includes/version.php + if err != nil { + return nil, err + } + defer f.Close() + + var wpVersion string + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + if !strings.HasPrefix(line, "$wp_version=") { + continue + } + + ss := strings.Split(line, "=") + if len(ss) != 2 { + return nil, fmt.Errorf("invalid wordpress version: %s", line) + } + + // NOTE: it is an example; you actually need to handle comments, etc + ss[1] = strings.TrimSpace(ss[1]) + wpVersion = strings.Trim(ss[1], `";`) + } + + if err = scanner.Err(); err != nil { + return nil, err + } + + return &serialize.AnalysisResult{ + CustomResources: []serialize.CustomResource{ + { + Type: typeWPVersion, + FilePath: filePath, + Data: wpVersion, + }, + }, + }, nil +} +``` + +!!! tips + Trivy caches analysis results according to the module version. + We'd recommend cleaning the cache or changing the module version every time you update `Analyzer`. + + +#### PostScanner interface +`PostScan` is called after scanning and takes the scan result as an argument from Trivy. +In post scanning, your module can perform one of three actions: + +- Insert + - Add a new security finding + - e.g. Add a new vulnerability and misconfiguration +- Update + - Update the detected vulnerability and misconfiguration + - e.g. Change a severity +- Delete + - Delete the detected vulnerability and misconfiguration + - e.g. Remove Spring4Shell because it is not actually affected. + +`PostScanSpec()` returns which action the module does. +If it is `Update` or `Delete`, it also needs to return IDs such as CVE-ID and misconfiguration ID, which your module wants to update or delete. + +`serialize.Results` contains the filtered results matching IDs you specified. +Also, it includes `CustomResources` with the values your `Analyze` returns, so you can modify the scan result according to the custom resources. + +```go +func (WordpressModule) PostScanSpec() serialize.PostScanSpec { + return serialize.PostScanSpec{ + Action: api.ActionInsert, // Add new vulnerabilities + } +} + +func (WordpressModule) PostScan(results serialize.Results) (serialize.Results, error) { + // e.g. results + // [ + // { + // "Target": "", + // "Class": "custom", + // "CustomResources": [ + // { + // "Type": "wordpress-version", + // "FilePath": "/usr/src/wordpress/wp-includes/version.php", + // "Layer": { + // "DiffID": "sha256:057649e61046e02c975b84557c03c6cca095b8c9accd3bd20eb4e432f7aec887" + // }, + // "Data": "5.7.1" + // } + // ] + // } + // ] + var wpVersion int + for _, result := range results { + if result.Class != types.ClassCustom { + continue + } + + for _, c := range result.CustomResources { + if c.Type != typeWPVersion { + continue + } + wpVersion = c.Data.(string) + wasm.Info(fmt.Sprintf("WordPress Version: %s", wpVersion)) + + ...snip... + + if affectedVersion.Check(ver) { + vulnerable = true + } + break + } + } + + if vulnerable { + // Add CVE-2020-36326 + results = append(results, serialize.Result{ + Target: wpPath, + Class: types.ClassLangPkg, + Type: "wordpress", + Vulnerabilities: []types.DetectedVulnerability { + { + VulnerabilityID: "CVE-2020-36326", + PkgName: "wordpress", + InstalledVersion: wpVersion, + FixedVersion: "5.7.2", + Vulnerability: dbTypes.Vulnerability{ + Title: "PHPMailer 6.1.8 through 6.4.0 allows object injection through Phar Deserialization via addAttachment with a UNC pathname.", + Severity: "CRITICAL", + }, + }, + }, + }) + } + return results, nil +} +``` + +The new vulnerability will be added to the scan results. +This example shows how the module inserts a new finding. +If you are interested in `Update`, you can see an example of [Spring4Shell][trivy-module-spring4shell]. + +In the `Delete` action, `PostScan` needs to return results you want to delete. +If `PostScan` returns an empty, Trivy will not delete anything. + +#### Build +Follow [the install guide][tinygo-installation] and install TinyGo. + +```bash +$ tinygo build -o wordpress.wasm -scheduler=none -target=wasi --no-debug wordpress.go +``` + +Put the built binary to the module directory that is under the home directory by default. + +```bash +$ mkdir -p ~/.trivy/modules +$ cp wordpress.wasm ~/.trivy/modules +``` + +## Distribute Your Module +You can distribute your own module in OCI registries. Please follow [the oras installation instruction][oras]. + +```bash +oras push ghcr.io/aquasecurity/trivy-module-wordpress:latest wordpress.wasm:application/vnd.module.wasm.content.layer.v1+wasm +Uploading 3daa3dac086b wordpress.wasm +Pushed ghcr.io/aquasecurity/trivy-module-wordpress:latest +Digest: sha256:6416d0199d66ce52ced19f01d75454b22692ff3aa7737e45f7a189880840424f +``` + +## Examples +- [Spring4Shell][trivy-module-spring4shell] +- [WordPress][trivy-module-wordpress] + +[regexp]: https://github.com/google/re2/wiki/Syntax + +[tinygo]: https://tinygo.org/ +[spring4shell]: https://blog.aquasec.com/zero-day-rce-vulnerability-spring4shell +[wazero]: https://github.com/tetratelabs/wazero + +[trivy-module-spring4shell]: https://github.com/aquasecurity/trivy/tree/main/examples/module/spring4shell +[trivy-module-wordpress]: https://github.com/aquasecurity/trivy-module-wordpress + +[tinygo-installation]: https://tinygo.org/getting-started/install/ +[oras]: https://oras.land/cli/ \ No newline at end of file diff --git a/docs/docs/advanced/plugins.md b/docs/docs/advanced/plugins.md new file mode 100644 index 000000000000..dfdfb31d8c0d --- /dev/null +++ b/docs/docs/advanced/plugins.md @@ -0,0 +1,236 @@ +# Plugins +Trivy provides a plugin feature to allow others to extend the Trivy CLI without the need to change the Trivycode base. +This plugin system was inspired by the plugin system used in [kubectl][kubectl], [Helm][helm], and [Conftest][conftest]. + +## Overview +Trivy plugins are add-on tools that integrate seamlessly with Trivy. +They provide a way to extend the core feature set of Trivy, but without requiring every new feature to be written in Go and added to the core tool. + +- They can be added and removed from a Trivy installation without impacting the core Trivy tool. +- They can be written in any programming language. +- They integrate with Trivy, and will show up in Trivy help and subcommands. + +!!! warning + Trivy plugins available in public are not audited for security. + You should install and run third-party plugins at your own risk, since they are arbitrary programs running on your machine. + + +## Installing a Plugin +A plugin can be installed using the `trivy plugin install` command. +This command takes a url and will download the plugin and install it in the plugin cache. + +Trivy adheres to the XDG specification, so the location depends on whether XDG_DATA_HOME is set. +Trivy will now search XDG_DATA_HOME for the location of the Trivy plugins cache. +The preference order is as follows: + +- XDG_DATA_HOME if set and .trivy/plugins exists within the XDG_DATA_HOME dir +- ~/.trivy/plugins + +Under the hood Trivy leverages [go-getter][go-getter] to download plugins. +This means the following protocols are supported for downloading plugins: + +- OCI Registries +- Local Files +- Git +- HTTP/HTTPS +- Mercurial +- Amazon S3 +- Google Cloud Storage + +For example, to download the Kubernetes Trivy plugin you can execute the following command: + +```bash +$ trivy plugin install github.com/aquasecurity/trivy-plugin-kubectl +``` +Also, Trivy plugin can be installed from a local archive: +```bash +$ trivy plugin install myplugin.tar.gz +``` + +## Using Plugins +Once the plugin is installed, Trivy will load all available plugins in the cache on the start of the next Trivy execution. +A plugin will be made in the Trivy CLI based on the plugin name. +To display all plugins, you can list them by `trivy --help` + +```bash +$ trivy --help +NAME: + trivy - A simple and comprehensive vulnerability scanner for containers + +USAGE: + trivy [global options] command [command options] target + +VERSION: + dev + +COMMANDS: + image, i scan an image + filesystem, fs scan local filesystem + repository, repo scan remote repository + client, c client mode + server, s server mode + plugin, p manage plugins + kubectl scan kubectl resources + help, h Shows a list of commands or help for one command +``` + +As shown above, `kubectl` subcommand exists in the `COMMANDS` section. +To call the kubectl plugin and scan existing Kubernetes deployments, you can execute the following command: + +``` +$ trivy kubectl deployment -- --ignore-unfixed --severity CRITICAL +``` + +Internally the kubectl plugin calls the kubectl binary to fetch information about that deployment and passes the using images to Trivy. +You can see the detail [here][trivy-plugin-kubectl]. + +If you want to omit even the subcommand, you can use `TRIVY_RUN_AS_PLUGIN` environment variable. + +```bash +$ TRIVY_RUN_AS_PLUGIN=kubectl trivy job your-job -- --format json +``` + +## Installing and Running Plugins on the fly +`trivy plugin run` installs a plugin and runs it on the fly. +If the plugin is already present in the cache, the installation is skipped. + +```bash +trivy plugin run github.com/aquasecurity/trivy-plugin-kubectl pod your-pod -- --exit-code 1 +``` + +## Uninstalling Plugins +Specify a plugin name with `trivy plugin uninstall` command. + +```bash +$ trivy plugin uninstall kubectl +``` + +## Building Plugins +Each plugin has a top-level directory, and then a plugin.yaml file. + +```bash +your-plugin/ + | + |- plugin.yaml + |- your-plugin.sh +``` + +In the example above, the plugin is contained inside of a directory named `your-plugin`. +It has two files: plugin.yaml (required) and an executable script, your-plugin.sh (optional). + +The core of a plugin is a simple YAML file named plugin.yaml. +Here is an example YAML of trivy-plugin-kubectl plugin that adds support for Kubernetes scanning. + +```yaml +name: "kubectl" +repository: github.com/aquasecurity/trivy-plugin-kubectl +version: "0.1.0" +usage: scan kubectl resources +description: |- + A Trivy plugin that scans the images of a kubernetes resource. + Usage: trivy kubectl TYPE[.VERSION][.GROUP] NAME +platforms: + - selector: # optional + os: darwin + arch: amd64 + uri: ./trivy-kubectl # where the execution file is (local file, http, git, etc.) + bin: ./trivy-kubectl # path to the execution file + - selector: # optional + os: linux + arch: amd64 + uri: https://github.com/aquasecurity/trivy-plugin-kubectl/releases/download/v0.1.0/trivy-kubectl.tar.gz + bin: ./trivy-kubectl +``` + +The `plugin.yaml` field should contain the following information: + +- name: The name of the plugin. This also determines how the plugin will be made available in the Trivy CLI. For example, if the plugin is named kubectl, you can call the plugin with `trivy kubectl`. (required) +- version: The version of the plugin. (required) +- usage: A short usage description. (required) +- description: A long description of the plugin. This is where you could provide a helpful documentation of your plugin. (required) +- platforms: (required) + - selector: The OS/Architecture specific variations of a execution file. (optional) + - os: OS information based on GOOS (linux, darwin, etc.) (optional) + - arch: The architecture information based on GOARCH (amd64, arm64, etc.) (optional) + - uri: Where the executable file is. Relative path from the root directory of the plugin or remote URL such as HTTP and S3. (required) + - bin: Which file to call when the plugin is executed. Relative path from the root directory of the plugin. (required) + +The following rules will apply in deciding which platform to select: + +- If both `os` and `arch` under `selector` match the current platform, search will stop and the platform will be used. +- If `selector` is not present, the platform will be used. +- If `os` matches and there is no more specific `arch` match, the platform will be used. +- If no `platform` match is found, Trivy will exit with an error. + +After determining platform, Trivy will download the execution file from `uri` and store it in the plugin cache. +When the plugin is called via Trivy CLI, `bin` command will be executed. + +The plugin is responsible for handling flags and arguments. Any arguments are passed to the plugin from the `trivy` command. + +A plugin should be archived `*.tar.gz`. + +```bash +$ tar -czvf myplugin.tar.gz plugin.yaml script.py +plugin.yaml +script.py + +$ trivy plugin install myplugin.tar.gz +2023-03-03T19:04:42.026+0600 INFO Installing the plugin from myplugin.tar.gz... +2023-03-03T19:04:42.026+0600 INFO Loading the plugin metadata... + +$ trivy myplugin +Hello from Trivy demo plugin! +``` + +## Plugin Types +Plugins are typically intended to be used as subcommands of Trivy, +but some plugins can be invoked as part of Trivy's built-in commands. +Currently, the following type of plugin is experimentally supported: + +- Output plugins + +### Output Plugins + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy supports "output plugins" which process Trivy's output, +such as by transforming the output format or sending it elsewhere. +For instance, in the case of image scanning, the output plugin can be called as follows: + +```shell +$ trivy image --format json --output plugin= [--output-plugin-arg ] +``` + +Since scan results are passed to the plugin via standard input, plugins must be capable of handling standard input. + +!!! warning + To avoid Trivy hanging, you need to read all data from `Stdin` before the plugin exits successfully or stops with an error. + +While the example passes JSON to the plugin, other formats like SBOM can also be passed (e.g., `--format cyclonedx`). + +If a plugin requires flags or other arguments, they can be passed using `--output-plugin-arg`. +This is directly forwarded as arguments to the plugin. +For example, `--output plugin=myplugin --output-plugin-arg "--foo --bar=baz"` translates to `myplugin --foo --bar=baz` in execution. + +An example of the output plugin is available [here](https://github.com/aquasecurity/trivy-output-plugin-count). +It can be used as below: + +```shell +# Install the plugin first +$ trivy plugin install github.com/aquasecurity/trivy-output-plugin-count + +# Call the output plugin in image scanning +$ trivy image --format json --output plugin=count --output-plugin-arg "--published-after 2023-10-01" debian:12 +``` + +## Example +- https://github.com/aquasecurity/trivy-plugin-kubectl +- https://github.com/aquasecurity/trivy-output-plugin-count + +[kubectl]: https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/ +[helm]: https://helm.sh/docs/topics/plugins/ +[conftest]: https://www.conftest.dev/plugins/ +[go-getter]: https://github.com/hashicorp/go-getter +[trivy-plugin-kubectl]: https://github.com/aquasecurity/trivy-plugin-kubectl + diff --git a/docs/docs/advanced/private-registries/acr.md b/docs/docs/advanced/private-registries/acr.md new file mode 100644 index 000000000000..df960697af3b --- /dev/null +++ b/docs/docs/advanced/private-registries/acr.md @@ -0,0 +1,27 @@ +# Requirements +None, Trivy uses Azure SDK for Go. You don't need to install `az` command. + +# Privileges +Service principal must have the `AcrPull` permissions. + +## Creation of a service principal +```bash +export SP_DATA=$(az ad sp create-for-rbac --name TrivyTest --role AcrPull --scope "/subscriptions//resourceGroups//providers/Microsoft.ContainerRegistry/registries/") +``` + +# Usage +```bash +# must set TRIVY_USERNAME empty char +export AZURE_CLIENT_ID=$(echo $SP_DATA | jq -r '.appId') +export AZURE_CLIENT_SECRET=$(echo $SP_DATA | jq -r '.password') +export AZURE_TENANT_ID=$(echo $SP_DATA | jq -r '.tenant') +``` + +# Testing +You can test credentials in the following manner. + +```bash +docker run -it --rm -v /tmp:/tmp \ + -e AZURE_CLIENT_ID -e AZURE_CLIENT_SECRET -e AZURE_TENANT_ID \ + aquasec/trivy image your_special_project.azurecr.io/your_special_image:your_special_tag +``` diff --git a/docs/docs/advanced/private-registries/docker-hub.md b/docs/docs/advanced/private-registries/docker-hub.md new file mode 100644 index 000000000000..f069fa43a01b --- /dev/null +++ b/docs/docs/advanced/private-registries/docker-hub.md @@ -0,0 +1,2 @@ +See [here](./index.md) for the detail. +You don't need to provide a credential when download from public repository. diff --git a/docs/docs/advanced/private-registries/ecr.md b/docs/docs/advanced/private-registries/ecr.md new file mode 100644 index 000000000000..b3e4b5b49c75 --- /dev/null +++ b/docs/docs/advanced/private-registries/ecr.md @@ -0,0 +1,35 @@ +Trivy uses AWS SDK. You don't need to install `aws` CLI tool. +You can use [AWS CLI's ENV Vars][env-var]. + +[env-var]: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html + +### AWS private registry permissions + +You may need to grant permissions to allow Trivy to pull images from private ECR. + +It depends on how you want to provide AWS Role to trivy. + +- [IAM Role Service account](https://github.com/aws/amazon-eks-pod-identity-webhook) +- [Kube2iam](https://github.com/jtblin/kube2iam) or [Kiam](https://github.com/uswitch/kiam) + +#### IAM Role Service account + +Add the AWS role in trivy's service account annotations: + +```yaml +trivy: + + serviceAccount: + annotations: {} + # eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/IAM_ROLE_NAME +``` + +#### Kube2iam or Kiam + +Add the AWS role to pod's annotations: + +```yaml +podAnnotations: {} + ## kube2iam/kiam annotation + # iam.amazonaws.com/role: arn:aws:iam::ACCOUNT_ID:role/IAM_ROLE_NAME +``` diff --git a/docs/docs/advanced/private-registries/gcr.md b/docs/docs/advanced/private-registries/gcr.md new file mode 100644 index 000000000000..ac87a4cc9429 --- /dev/null +++ b/docs/docs/advanced/private-registries/gcr.md @@ -0,0 +1,40 @@ +# Requirements +None, Trivy uses Google Cloud SDK. You don't need to install `gcloud` command. + +# Privileges +Credential file must have the `roles/storage.objectViewer` permissions. +More information can be found in [Google's documentation](https://cloud.google.com/container-registry/docs/access-control) + +## JSON File Format +The JSON file specified should have the following format provided by google's service account mechanisms: + +```json +{ + "type": "service_account", + "project_id": "your_special_project", + "private_key_id": "XXXXXXXXXXXXXXXXXXXXxx", + "private_key": "-----BEGIN PRIVATE KEY-----\nNONONONO\n-----END PRIVATE KEY-----\n", + "client_email": "somedude@your_special_project.iam.gserviceaccount.com", + "client_id": "1234567890", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/somedude%40your_special_project.iam.gserviceaccount.com" +} +``` + +# Usage +If you want to use target project's repository, you can set them via `GOOGLE_APPLICATION_CREDENTIALS`. +```bash +# must set TRIVY_USERNAME empty char +export GOOGLE_APPLICATION_CREDENTIALS=/path/to/credential.json +``` + +# Testing +You can test credentials in the following manner (assuming they are in `/tmp` on host machine). + +```bash +docker run -it --rm -v /tmp:/tmp\ + -e GOOGLE_APPLICATION_CREDENTIALS=/tmp/service_account.json\ + aquasec/trivy image gcr.io/your_special_project/your_special_image:your_special_tag +``` diff --git a/docs/docs/advanced/private-registries/index.md b/docs/docs/advanced/private-registries/index.md new file mode 100644 index 000000000000..7d5a594cf5b6 --- /dev/null +++ b/docs/docs/advanced/private-registries/index.md @@ -0,0 +1,49 @@ +Trivy can download images from a private registry without the need for installing Docker or any other 3rd party tools. +This makes it easy to run within a CI process. + +## Credential +To use Trivy with private images, simply install it and provide your credentials: + +```shell +$ TRIVY_USERNAME=YOUR_USERNAME TRIVY_PASSWORD=YOUR_PASSWORD trivy image YOUR_PRIVATE_IMAGE +``` + +Trivy also supports providing credentials through CLI flags: + +```shell +$ TRIVY_PASSWORD=YOUR_PASSWORD trivy image --username YOUR_USERNAME YOUR_PRIVATE_IMAGE +``` + +!!! warning + The CLI flag `--password` is available, but its use is not recommended for security reasons. + +You can also store your credentials in `trivy.yaml`. +For more information, please refer to [the documentation](../../references/configuration/config-file.md). + +It can handle multiple sets of credentials as well: + +```shell +$ export TRIVY_USERNAME=USERNAME1,USERNAME2 +$ export TRIVY_PASSWORD=PASSWORD1,PASSWORD2 +$ trivy image YOUR_PRIVATE_IMAGE +``` + +In the example above, Trivy attempts to use two pairs of credentials: + +- USERNAME1/PASSWORD1 +- USERNAME2/PASSWORD2 + +Please note that the number of usernames and passwords must be the same. + +## docker login +If you have Docker configured locally and have set up the credentials, Trivy can access them. + +```shell +$ docker login ghcr.io +Username: +Password: +$ trivy image ghcr.io/your/private_image +``` + +!!! note + `docker login` can be used with any container runtime, such as Podman. diff --git a/docs/docs/advanced/private-registries/self.md b/docs/docs/advanced/private-registries/self.md new file mode 100644 index 000000000000..8af48a4ff120 --- /dev/null +++ b/docs/docs/advanced/private-registries/self.md @@ -0,0 +1,9 @@ +BasicAuth server needs `TRIVY_USERNAME` and `TRIVY_PASSWORD`. + +```bash +export TRIVY_USERNAME={USERNAME} +export TRIVY_PASSWORD={PASSWORD} + +# if you want to use 80 port, use NonSSL +export TRIVY_NON_SSL=true +``` diff --git a/docs/docs/compliance/compliance.md b/docs/docs/compliance/compliance.md new file mode 100644 index 000000000000..1d54af24a69a --- /dev/null +++ b/docs/docs/compliance/compliance.md @@ -0,0 +1,70 @@ +# Compliance Reports + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy’s compliance flag lets you curate a specific set of checks into a report. In a typical Trivy scan, there are hundreds of different checks for many different components and configurations, but sometimes you already know which specific checks you are interested in. Often this would be an industry accepted set of checks such as CIS, or some vendor specific guideline, or your own organization policy that you want to comply with. These are all possible using the flexible compliance infrastructure that's built into Trivy. Compliance reports are defined as simple YAML documents that select checks to include in the report. + +## Usage + +Compliance report is currently supported in the following targets (trivy sub-commands): + +- `trivy image` +- `trivy aws` +- `trivy k8s` + +Add the `--compliance` flag to the command line, and set it's value to desired report. +For example: `trivy k8s cluster --compliance k8s-nsa` (see below for built-in and custom reports) + +### Options + +The following flags are compatible with `--compliance` flag and allows customizing it's output: + +| flag | effect | +|--------------------|--------------------------------------------------------------------------------------| +| `--report summary` | shows a summary of the results. for every control shows the number of failed checks. | +| `--report all` | shows fully detailed results. for every control shows where it failed and why. | +| `--format table` | shows results in textual table format (good for human readability). | +| `--format json` | shows results in json format (good for machine readability). | + +## Built-in compliance + +Trivy has a number of built-in compliance reports that you can asses right out of the box. +to specify a built-in compliance report, select it by ID like `trivy --compliance `. + +For the list of built-in compliance reports, please see the relevant section: + +- [Docker compliance](../target/container_image.md#compliance) +- [Kubernetes compliance](../target/kubernetes.md#compliance) +- [AWS compliance](../target/aws.md#compliance) + +## Custom compliance + +You can create your own custom compliance report. A compliance report is a simple YAML document in the following format: + +```yaml +spec: + id: "k8s-myreport" # report unique identifier. this should not container spaces. + title: "My custom Kubernetes report" # report title. Any one-line title. + description: "Describe your report" # description of the report. Any text. + relatedResources : + - https://some.url # useful references. URLs only. + version: "1.0" # spec version (string) + controls: + - name: "Non-root containers" # Name for the control (appears in the report as is). Any one-line name. + description: 'Check that container is not running as root' # Description (appears in the report as is). Any text. + id: "1.0" # control identifier (string) + checks: # list of existing Trivy checks that define the control + - id: AVD-KSV-0012 # check ID. Must start with `AVD-` or `CVE-` + severity: "MEDIUM" # Severity for the control (note that checks severity isn't used) + - name: "Immutable container file systems" + description: 'Check that container root file system is immutable' + id: "1.1" + checks: + - id: AVD-KSV-0014 + severity: "LOW" +``` + +The check id field (`controls[].checks[].id`) is referring to existing check by it's "AVD ID". This AVD ID is easily located in the check's source code metadata header, or by browsing [Aqua vulnerability DB](https://avd.aquasec.com/), specifically in the [Misconfigurations](https://avd.aquasec.com/misconfig/) and [Vulnerabilities](https://avd.aquasec.com/nvd) sections. + +Once you have a compliance spec, you can select it by file path: `trivy --compliance @` (note the `@` indicating file path instead of report id). diff --git a/docs/docs/configuration/cache.md b/docs/docs/configuration/cache.md new file mode 100644 index 000000000000..3fd74a393892 --- /dev/null +++ b/docs/docs/configuration/cache.md @@ -0,0 +1,77 @@ +# Cache +The cache directory includes + +- [Vulnerability Database][trivy-db][^1] +- [Java Index Database][trivy-java-db][^2] +- [Misconfiguration Policies][misconf-policies][^3] +- Cache of previous scans. + +The cache option is common to all scanners. + +## Clear Caches +The `--clear-cache` option removes caches. + +**The scan is not performed.** + +``` +$ trivy image --clear-cache +``` + +
+Result + +``` +2019-11-15T15:13:26.209+0200 INFO Reopening vulnerability DB +2019-11-15T15:13:26.209+0200 INFO Removing image caches... +``` + +
+ +## Cache Directory +Specify where the cache is stored with `--cache-dir`. + +``` +$ trivy --cache-dir /tmp/trivy/ image python:3.4-alpine3.9 +``` + +## Cache Backend +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy supports local filesystem and Redis as the cache backend. This option is useful especially for client/server mode. + +Two options: + +- `fs` + - the cache path can be specified by `--cache-dir` +- `redis://` + - `redis://[HOST]:[PORT]` + - TTL can be configured via `--cache-ttl` + +``` +$ trivy server --cache-backend redis://localhost:6379 +``` + +If you want to use TLS with Redis, you can enable it by specifying the `--redis-tls` flag. + +```shell +$ trivy server --cache-backend redis://localhost:6379 --redis-tls +``` + +Trivy also supports for connecting to Redis with your certificates. +You need to specify `--redis-ca` , `--redis-cert` , and `--redis-key` options. + +``` +$ trivy server --cache-backend redis://localhost:6379 \ + --redis-ca /path/to/ca-cert.pem \ + --redis-cert /path/to/cert.pem \ + --redis-key /path/to/key.pem +``` + +[trivy-db]: ./db.md#vulnerability-database +[trivy-java-db]: ./db.md#java-index-database +[misconf-policies]: ../scanner/misconfiguration/policy/builtin.md + +[^1]: Downloaded when scanning for vulnerabilities +[^2]: Downloaded when scanning `jar/war/par/ear` files +[^3]: Downloaded when scanning for misconfigurations \ No newline at end of file diff --git a/docs/docs/configuration/db.md b/docs/docs/configuration/db.md new file mode 100644 index 000000000000..1479a79183a1 --- /dev/null +++ b/docs/docs/configuration/db.md @@ -0,0 +1,85 @@ +# DB + +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | | +| Secret | | +| License | | + +The vulnerability database and the Java index database are needed only for vulnerability scanning. +See [here](../scanner/vulnerability.md) for the detail. + +## Vulnerability Database + +### Skip update of vulnerability DB +If you want to skip downloading the vulnerability database, use the `--skip-db-update` option. + +``` +$ trivy image --skip-db-update python:3.4-alpine3.9 +``` + +
+Result + +``` +2019-05-16T12:48:08.703+0900 INFO Detecting Alpine vulnerabilities... + +python:3.4-alpine3.9 (alpine 3.9.2) +=================================== +Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0) + ++---------+------------------+----------+-------------------+---------------+--------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++---------+------------------+----------+-------------------+---------------+--------------------------------+ +| openssl | CVE-2019-1543 | MEDIUM | 1.1.1a-r1 | 1.1.1b-r1 | openssl: ChaCha20-Poly1305 | +| | | | | | with long nonces | ++---------+------------------+----------+-------------------+---------------+--------------------------------+ +``` + +
+ +### Only download vulnerability database +You can also ask `Trivy` to simply retrieve the vulnerability database. +This is useful to initialize workers in Continuous Integration systems. + +``` +$ trivy image --download-db-only +``` + +### DB Repository +`Trivy` could also download the vulnerability database from an external OCI registry by using `--db-repository` option. + +``` +$ trivy image --db-repository registry.gitlab.com/gitlab-org/security-products/dependencies/trivy-db +``` + +!!!note + Trivy automatically adds the `trivy-db` schema version as a tag if the tag is not used: + + `trivy-db-registry:latest` => `trivy-db-registry:latest`, but `trivy-db-registry` => `trivy-db-registry:2`. + +## Java Index Database +The same options are also available for the Java index DB, which is used for scanning Java applications. +Skipping an update can be done by using the `--skip-java-db-update` option, while `--download-java-db-only` can be used to only download the Java index DB. + +!!! Note + In [Client/Server](../references/modes/client-server.md) mode, `Java index DB` is currently only used on the `client` side. + +Downloading the Java index DB from an external OCI registry can be done by using the `--java-db-repository` option. + +``` +$ trivy image --java-db-repository registry.gitlab.com/gitlab-org/security-products/dependencies/trivy-java-db --download-java-db-only +``` + +!!!note + Trivy automatically adds the `trivy-java-db` schema version as a tag if the tag is not used: + + `java-db-registry:latest` => `java-db-registry:latest`, but `java-db-registry` => `java-db-registry:1`. + +## Remove DBs +The `--reset` flag removes all caches and databases. + +``` +$ trivy image --reset +``` \ No newline at end of file diff --git a/docs/docs/configuration/filtering.md b/docs/docs/configuration/filtering.md new file mode 100644 index 000000000000..75e880af4f49 --- /dev/null +++ b/docs/docs/configuration/filtering.md @@ -0,0 +1,497 @@ +# Filtering +Trivy provides various methods for filtering the results. + +```mermaid +flowchart LR + Issues("Detected\nIssues") --> Severity + + subgraph Filtering + subgraph Prioritization + direction TB + Severity("By Severity") --> Status("By Status") + end + subgraph Suppression + Status --> Ignore("By Finding IDs") + Ignore --> Rego("By Rego") + Rego --> VEX("By VEX") + end + end + VEX --> Results +``` + +Similar to the functionality of filtering results, you can also limit the sub-targets for each scanner. +For information on these settings, please refer to the scanner-specific documentation ([vulnerability](../scanner/vulnerability.md) , [misconfiguration](../scanner/misconfiguration/index.md), etc.). + +## Prioritization +You can filter the results by + +- [Severity](#by-severity) +- [Status](#by-status) + +### By Severity + +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | ✓ | + +Use `--severity` option. + +```bash +$ trivy image --severity HIGH,CRITICAL ruby:2.4.0 +``` + +
+Result + +```bash +2019-05-16T01:51:46.255+0900 INFO Updating vulnerability database... +2019-05-16T01:51:49.213+0900 INFO Detecting Debian vulnerabilities... + +ruby:2.4.0 (debian 8.7) +======================= +Total: 1785 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1680, CRITICAL: 105) + ++-----------------------------+------------------+----------+---------------------------+----------------------------------+-------------------------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++-----------------------------+------------------+----------+---------------------------+----------------------------------+-------------------------------------------------+ +| apt | CVE-2019-3462 | CRITICAL | 1.0.9.8.3 | 1.0.9.8.5 | Incorrect sanitation of the | +| | | | | | 302 redirect field in HTTP | +| | | | | | transport method of... | ++-----------------------------+------------------+----------+---------------------------+----------------------------------+-------------------------------------------------+ +| bash | CVE-2019-9924 | HIGH | 4.3-11 | 4.3-11+deb8u2 | bash: BASH_CMD is writable in | +| | | | | | restricted bash shells | ++ +------------------+ + +----------------------------------+-------------------------------------------------+ +| | CVE-2016-7543 | | | 4.3-11+deb8u1 | bash: Specially crafted | +| | | | | | SHELLOPTS+PS4 variables allows | +| | | | | | command substitution | ++-----------------------------+------------------+ +---------------------------+----------------------------------+-------------------------------------------------+ +| binutils | CVE-2017-8421 | | 2.25-5 | | binutils: Memory exhaustion in | +| | | | | | objdump via a crafted PE file | ++ +------------------+ + +----------------------------------+-------------------------------------------------+ +| | CVE-2017-14930 | | | | binutils: Memory leak in | +| | | | | | decode_line_info | ++ +------------------+ + +----------------------------------+-------------------------------------------------+ +| | CVE-2017-7614 | | | | binutils: NULL | +| | | | | | pointer dereference in | +| | | | | | bfd_elf_final_link function | ++ +------------------+ + +----------------------------------+-------------------------------------------------+ +| | CVE-2014-9939 | | | | binutils: buffer overflow in | +| | | | | | ihex.c | ++ +------------------+ + +----------------------------------+-------------------------------------------------+ +| | CVE-2017-13716 | | | | binutils: Memory leak with the | +| | | | | | C++ symbol demangler routine | +| | | | | | in libiberty | ++ +------------------+ + +----------------------------------+-------------------------------------------------+ +| | CVE-2018-12699 | | | | binutils: heap-based buffer | +| | | | | | overflow in finish_stab in | +| | | | | | stabs.c | ++-----------------------------+------------------+ +---------------------------+----------------------------------+-------------------------------------------------+ +| bsdutils | CVE-2015-5224 | | 2.25.2-6 | | util-linux: File name | +| | | | | | collision due to incorrect | +| | | | | | mkstemp use | ++ +------------------+ + +----------------------------------+-------------------------------------------------+ +| | CVE-2016-2779 | | | | util-linux: runuser tty hijack | +| | | | | | via TIOCSTI ioctl | ++-----------------------------+------------------+----------+---------------------------+----------------------------------+-------------------------------------------------+ +``` + +
+ +```bash +trivy conf --severity HIGH,CRITICAL examples/misconf/mixed +``` + +
+Result + +```shell +2022-05-16T13:50:42.718+0100 INFO Detected config files: 3 + +Dockerfile (dockerfile) +======================= +Tests: 17 (SUCCESSES: 16, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (HIGH: 1, CRITICAL: 0) + +HIGH: Last USER command in Dockerfile should not be 'root' +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile. + +See https://avd.aquasec.com/misconfig/ds002 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Dockerfile:3 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 3 [ USER root +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + + +deployment.yaml (kubernetes) +============================ +Tests: 8 (SUCCESSES: 8, FAILURES: 0, EXCEPTIONS: 0) +Failures: 0 (HIGH: 0, CRITICAL: 0) + + +main.tf (terraform) +=================== +Tests: 1 (SUCCESSES: 0, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (HIGH: 0, CRITICAL: 1) + +CRITICAL: Classic resources should not be used. +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +AWS Classic resources run in a shared environment with infrastructure owned by other AWS customers. You should run +resources in a VPC instead. + +See https://avd.aquasec.com/misconfig/avd-aws-0081 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + main.tf:2-4 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 2 ┌ resource "aws_db_security_group" "sg" { + 3 │ + 4 â”” } +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +``` +
+ +### By Status + +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | | +| Secret | | +| License | | + +Trivy supports the following vulnerability statuses: + +- `unknown` +- `not_affected`: this package is not affected by this vulnerability on this platform +- `affected`: this package is affected by this vulnerability on this platform, but there is no patch released yet +- `fixed`: this vulnerability is fixed on this platform +- `under_investigation`: it is currently unknown whether or not this vulnerability affects this package on this platform, and it is under investigation +- `will_not_fix`: this package is affected by this vulnerability on this platform, but there is currently no intention to fix it (this would primarily be for flaws that are of Low or Moderate impact that pose no significant risk to customers) +- `fix_deferred`: this package is affected by this vulnerability on this platform, and may be fixed in the future +- `end_of_life`: this package has been identified to contain the impacted component, but analysis to determine whether it is affected or not by this vulnerability was not performed + +Note that vulnerabilities with the `unknown`, `not_affected` or `under_investigation` status are not detected. +These are only defined for comprehensiveness, and you will not have the opportunity to specify these statuses. + +Some statuses are supported in limited distributions. + +| OS | Fixed | Affected | Under Investigation | Will Not Fix | Fix Deferred | End of Life | +|:----------:|:-----:|:--------:|:-------------------:|:------------:|:------------:|:-----------:| +| Debian | ✓ | ✓ | | | ✓ | ✓ | +| RHEL | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Other OSes | ✓ | ✓ | | | | | + + +To ignore vulnerabilities with specific statuses, use the `--ignore-status ` option. + + +```bash +$ trivy image --ignore-status affected,fixed ruby:2.4.0 +``` + +
+Result + +``` +2019-05-16T12:50:14.786+0900 INFO Detecting Debian vulnerabilities... + +ruby:2.4.0 (debian 8.7) +======================= +Total: 527 (UNKNOWN: 0, LOW: 276, MEDIUM: 83, HIGH: 158, CRITICAL: 10) + +┌─────────────────────────────┬──────────────────┬──────────┬──────────────┬────────────────────────────┬───────────────┬──────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────────────────────────┼──────────────────┼──────────┼──────────────┼────────────────────────────┼───────────────┼──────────────────────────────────────────────────────────────┤ +│ binutils │ CVE-2014-9939 │ CRITICAL │ will_not_fix │ 2.25-5 │ │ binutils: buffer overflow in ihex.c │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2014-9939 │ +│ ├──────────────────┤ │ │ ├───────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2017-6969 │ │ │ │ │ binutils: Heap-based buffer over-read in readelf when │ +│ │ │ │ │ │ │ processing corrupt RL78 binaries │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2017-6969 │ +│ ├──────────────────┤ │ │ ├───────────────┼──────────────────────────────────────────────────────────────┤ +... +``` + +
+ +!!! tip + To skip all unfixed vulnerabilities, you can use the `--ignore-unfixed` flag . + It is a shorthand of `--ignore-status affected,will_not_fix,fix_deferred,end_of_life`. + It displays "fixed" vulnerabilities only. + +```bash +$ trivy image --ignore-unfixed ruby:2.4.0 +``` + +## Suppression +You can filter the results by + +- [Finding IDs](#by-finding-ids) +- [Rego](#by-rego) +- [Vulnerability Exploitability Exchange (VEX)](#by-vulnerability-exploitability-exchange-vex) + +To show the suppressed results, use the `--show-suppressed` flag. + +```bash +$ trivy image --vex debian11.csaf.vex --ignorefile .trivyignore.yaml --show-suppressed debian:11 +... + +Suppressed Vulnerabilities (Total: 9) + +┌───────────────┬───────────────┬──────────┬──────────────┬─────────────────────────────────────────────┬───────────────────┠+│ Library │ Vulnerability │ Severity │ Status │ Statement │ Source │ +├───────────────┼───────────────┼──────────┼──────────────┼─────────────────────────────────────────────┼───────────────────┤ +│ libdb5.3 │ CVE-2019-8457 │ CRITICAL │ not_affected │ vulnerable_code_not_in_execute_path │ CSAF VEX │ +├───────────────┼───────────────┼──────────┼──────────────┼─────────────────────────────────────────────┼───────────────────┤ +│ bsdutils │ CVE-2022-0563 │ LOW │ ignored │ Accept the risk │ .trivyignore.yaml │ +├───────────────┤ │ │ │ │ │ +│ libblkid1 │ │ │ │ │ │ +├───────────────┤ │ │ │ │ │ +│ libmount1 │ │ │ │ │ │ +├───────────────┤ │ │ │ │ │ +│ libsmartcols1 │ │ │ │ │ │ +├───────────────┤ │ │ │ │ │ +│ libuuid1 │ │ │ │ │ │ +├───────────────┤ │ │ │ │ │ +│ mount │ │ │ │ │ │ +├───────────────┼───────────────┤ │ ├─────────────────────────────────────────────┤ │ +│ tar │ CVE-2005-2541 │ │ │ The vulnerable configuration is not enabled │ │ +├───────────────┼───────────────┤ │ ├─────────────────────────────────────────────┤ │ +│ util-linux │ CVE-2022-0563 │ │ │ Accept the risk │ │ +└───────────────┴───────────────┴──────────┴──────────────┴─────────────────────────────────────────────┴───────────────────┘ +``` + +### By Finding IDs + +Trivy supports the [.trivyignore](#trivyignore) and [.trivyignore.yaml](#trivyignoreyaml) ignore files. + +#### .trivyignore + +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | | + + +```bash +$ cat .trivyignore +# Accept the risk +CVE-2018-14618 + +# Accept the risk until 2023-01-01 +CVE-2019-14697 exp:2023-01-01 + +# No impact in our settings +CVE-2019-1543 + +# Ignore misconfigurations +AVD-DS-0002 + +# Ignore secrets +generic-unwanted-rule +aws-account-id +``` + +```bash +$ trivy image python:3.4-alpine3.9 +``` + +
+Result + +```bash +2019-05-16T12:53:10.076+0900 INFO Updating vulnerability database... +2019-05-16T12:53:28.134+0900 INFO Detecting Alpine vulnerabilities... + +python:3.4-alpine3.9 (alpine 3.9.2) +=================================== +Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + +``` + +
+ +#### .trivyignore.yaml + +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | ✓ | + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +When the extension of the specified ignore file is either `.yml` or `.yaml`, Trivy will load the file as YAML. +For the `.trivyignore.yaml` file, you can set ignored IDs separately for `vulnerabilities`, `misconfigurations`, `secrets`, or `licenses`[^1]. + +Available fields: + +| Field | Required | Type | Description | +|------------|:--------:|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| id | ✓ | string | The identifier of the vulnerability, misconfiguration, secret, or license[^1]. | +| paths[^2] | | string array | The list of file paths to ignore. If `paths` is not set, the ignore finding is applied to all files. | +| purls | | string array | The list of PURLs to ignore packages. If `purls` is not set, the ignore finding is applied to all packages. This field is currently available only for vulnerabilities. | +| expired_at | | date (`yyyy-mm-dd`) | The expiration date of the ignore finding. If `expired_at` is not set, the ignore finding is always valid. | +| statement | | string | The reason for ignoring the finding. (This field is not used for filtering.) | + +```bash +$ cat .trivyignore.yaml +vulnerabilities: + - id: CVE-2022-40897 + paths: + - "usr/local/lib/python3.9/site-packages/setuptools-58.1.0.dist-info/METADATA" + statement: Accept the risk + - id: CVE-2023-2650 + - id: CVE-2023-3446 + - id: CVE-2023-3817 + purls: + - "pkg:deb/debian/libssl1.1" + - id: CVE-2023-29491 + expired_at: 2023-09-01 + +misconfigurations: + - id: AVD-DS-0001 + - id: AVD-DS-0002 + paths: + - "docs/Dockerfile" + statement: The image needs root privileges + +secrets: + - id: aws-access-key-id + - id: aws-secret-access-key + paths: + - "foo/bar/aws.secret" + +licenses: + - id: GPL-3.0 # License name is used as ID + paths: + - "usr/share/gcc/python/libstdcxx/v6/__init__.py" +``` + +Since this feature is experimental, you must explicitly specify the YAML file path using the `--ignorefile` flag. +Once this functionality is stable, the YAML file will be loaded automatically. + +```bash +$ trivy image --ignorefile ./.trivyignore.yaml python:3.9.16-alpine3.16 +``` + +
+Result + +```bash +2023-08-31T11:10:27.155+0600 INFO Vulnerability scanning is enabled +2023-08-31T11:10:27.155+0600 INFO Secret scanning is enabled +2023-08-31T11:10:27.155+0600 INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning +2023-08-31T11:10:27.155+0600 INFO Please see also https://aquasecurity.github.io/trivy/dev/docs/scanner/secret/#recommendation for faster secret detection +2023-08-31T11:10:29.164+0600 INFO Detected OS: alpine +2023-08-31T11:10:29.164+0600 INFO Detecting Alpine vulnerabilities... +2023-08-31T11:10:29.169+0600 INFO Number of language-specific files: 1 +2023-08-31T11:10:29.170+0600 INFO Detecting python-pkg vulnerabilities... + +python:3.9.16-alpine3.16 (alpine 3.16.5) +======================================== +Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + + +``` + +
+ +### By Rego + +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | ✓ | + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +[Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) is a policy language that allows you to express decision logic in a concise syntax. +Rego is part of the popular [Open Policy Agent (OPA)](https://www.openpolicyagent.org) CNCF project. +For advanced filtering, Trivy allows you to use Rego language to filter vulnerabilities. + +Use the `--ignore-policy` flag which takes a path to a Rego file that defines the filtering policy. +The Rego package name must be `trivy` and it must include a "rule" named `ignore` which determines if each individual scan result should be excluded (ignore=true) or not (ignore=false). +The `input` for the evaluation is each [DetectedVulnerability](https://github.com/aquasecurity/trivy/blob/00f2059e5d7bc2ca2e3e8b1562bdfede1ed570e3/pkg/types/vulnerability.go#L9) and [DetectedMisconfiguration](https://github.com/aquasecurity/trivy/blob/00f2059e5d7bc2ca2e3e8b1562bdfede1ed570e3/pkg/types/misconfiguration.go#L6). + +A practical way to observe the filtering policy input in your case, is to run a scan with the `--format json` option and look at the resulting structure: + +```bash +trivy image -f json centos:7 + +... + "Results": [ + { + "Target": "centos:7 (centos 7.9.2009)", + "Class": "os-pkgs", + "Type": "centos", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2015-5186", + "PkgID": "audit-libs@2.8.5-4.el7.x86_64", + "PkgName": "audit-libs", + "InstalledVersion": "2.8.5-4.el7", + "Layer": { + "Digest": "sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc", + "DiffID": "sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2015-5186", + "Title": "log terminal emulator escape sequences handling", + "Description": "Audit before 2.4.4 in Linux does not sanitize escape characters in filenames.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-20" + ], +... +``` + +Each individual Vulnerability, Misconfiguration, License and Secret (under `Results.Vulnerabilities`, `Results.Misconfigurations`, +`Results.Licenses`, `Results.Secrets`) is evaluated for exclusion or inclusion by the `ignore` rule. + +The following is a Rego ignore policy that filters out every vulnerability with a specific CWE ID (as seen in the JSON example above): + +```rego +package trivy + +default ignore = false + +ignore { + input.CweIDs[_] == "CWE-20" +} +``` + +```bash +trivy image --ignore-policy contrib/example_policy/basic.rego centos:7 +``` + +For more advanced use cases, there is a built-in Rego library with helper functions that you can import into your policy using: `import data.lib.trivy`. +More info about the helper functions are in the library [here](https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/pkg/result/module.go). + +You can find more example policies [here](https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/pkg/result/module.go) + +### By Vulnerability Exploitability Exchange (VEX) +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | | +| Secret | | +| License | | + +Please refer to the [VEX documentation](../supply-chain/vex.md) for the details. + + +[^1]: license name is used as id for `.trivyignore.yaml` files. +[^2]: This doesn't work for os package licenses (e.g. apk, dpkg, rpm). For projects which manage dependencies through a dependency file (e.g. go.mod, yarn.lock) `path` should point to that particular file. diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md new file mode 100644 index 000000000000..b70163954beb --- /dev/null +++ b/docs/docs/configuration/index.md @@ -0,0 +1,31 @@ +# Configuration +Trivy can be configured using the following ways. Each item takes precedence over the item below it: + +- CLI flags +- Environment variables +- Configuration file + +## CLI Flags +You can view the list of available flags using the `--help` option. +For more details, please refer to [the CLI reference](../references/configuration/cli/trivy.md). + +## Environment Variables +Trivy can be customized by environment variables. +The environment variable key is the flag name converted by the following procedure. + +- Add `TRIVY_` prefix +- Make it all uppercase +- Replace `-` with `_` + +For example, + +- `--debug` => `TRIVY_DEBUG` +- `--cache-dir` => `TRIVY_CACHE_DIR` + +``` +$ TRIVY_DEBUG=true TRIVY_SEVERITY=CRITICAL trivy image alpine:3.15 +``` + +## Configuration File +By default, Trivy reads the `trivy.yaml` file. +For more details, please refer to [the page](../references/configuration/config-file.md). diff --git a/docs/docs/configuration/others.md b/docs/docs/configuration/others.md new file mode 100644 index 000000000000..371350f07a96 --- /dev/null +++ b/docs/docs/configuration/others.md @@ -0,0 +1,119 @@ +# Others + +## Enable/Disable Scanners +You can enable/disable scanners with the `--scanners` flag. + +Supported values: + +- vuln +- misconfig +- secret +- license + +For example, container image scanning enables vulnerability and secret scanners by default. +If you don't need secret scanning, it can be disabled. + +``` shell +$ trivy image --scanners vuln alpine:3.15 +``` + +## Exit Code +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | ✓ | + +By default, `Trivy` exits with code 0 even when security issues are detected. +Use the `--exit-code` option if you want to exit with a non-zero exit code. + +``` +$ trivy image --exit-code 1 python:3.4-alpine3.9 +``` + +
+Result + +``` +2019-05-16T12:51:43.500+0900 INFO Updating vulnerability database... +2019-05-16T12:52:00.387+0900 INFO Detecting Alpine vulnerabilities... + +python:3.4-alpine3.9 (alpine 3.9.2) +=================================== +Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0) + ++---------+------------------+----------+-------------------+---------------+--------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++---------+------------------+----------+-------------------+---------------+--------------------------------+ +| openssl | CVE-2019-1543 | MEDIUM | 1.1.1a-r1 | 1.1.1b-r1 | openssl: ChaCha20-Poly1305 | +| | | | | | with long nonces | ++---------+------------------+----------+-------------------+---------------+--------------------------------+ +``` + +
+ +This option is useful for CI/CD. In the following example, the test will fail only when a critical vulnerability is found. + +``` +$ trivy image --exit-code 0 --severity MEDIUM,HIGH ruby:2.4.0 +$ trivy image --exit-code 1 --severity CRITICAL ruby:2.4.0 +``` + +## Exit on EOL +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | | +| Secret | | +| License | | + +Sometimes you may surprisingly get 0 vulnerabilities in an old image: + +- Enabling `--ignore-unfixed` option while all packages have no fixed versions. +- Scanning a rather outdated OS (e.g. Ubuntu 10.04). + +An OS at the end of service/life (EOL) usually gets into this situation, which is definitely full of vulnerabilities. +`--exit-on-eol` can fail scanning on EOL OS with a non-zero code. +This flag is available with the following targets. + +- Container images (`trivy image`) +- Virtual machine images (`trivy vm`) +- SBOM (`trivy sbom`) +- Root filesystem (`trivy rootfs`) + +``` +$ trivy image --exit-on-eol 1 alpine:3.10 +``` + +
+Result + +``` +2023-03-01T11:07:15.455+0200 INFO Vulnerability scanning is enabled +... +2023-03-01T11:07:17.938+0200 WARN This OS version is no longer supported by the distribution: alpine 3.10.9 +2023-03-01T11:07:17.938+0200 WARN The vulnerability detection may be insufficient because security updates are not provided + +alpine:3.10 (alpine 3.10.9) +=========================== +Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1) + +┌───────────┬────────────────┬──────────┬───────────────────┬───────────────┬─────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├───────────┼────────────────┼──────────┼───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤ +│ apk-tools │ CVE-2021-36159 │ CRITICAL │ 2.10.6-r0 │ 2.10.7-r0 │ libfetch before 2021-07-26, as used in apk-tools, xbps, and │ +│ │ │ │ │ │ other products, mishandles... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-36159 │ +└───────────┴────────────────┴──────────┴───────────────────┴───────────────┴─────────────────────────────────────────────────────────────┘ +2023-03-01T11:07:17.941+0200 ERROR Detected EOL OS: alpine 3.10.9 +``` + +
+ +This option is useful for CI/CD. +The following example will fail when a critical vulnerability is found or the OS is EOSL: + +``` +$ trivy image --exit-code 1 --exit-on-eol 1 --severity CRITICAL alpine:3.16.3 +``` diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md new file mode 100644 index 000000000000..117db88de866 --- /dev/null +++ b/docs/docs/configuration/reporting.md @@ -0,0 +1,451 @@ +# Reporting + +## Format +Trivy supports the following formats: + +- Table +- JSON +- [SARIF](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning) +- Template +- SBOM +- GitHub dependency snapshot + +### Table (Default) + +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | ✓ | + +``` +$ trivy image -f table golang:1.12-alpine +``` + +#### Show origins of vulnerable dependencies + +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | | +| Secret | | +| License | | + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Modern software development relies on the use of third-party libraries. +Third-party dependencies also depend on others so a list of dependencies can be represented as a dependency graph. +In some cases, vulnerable dependencies are not linked directly, and it requires analyses of the tree. +To make this task simpler Trivy can show a dependency origin tree with the `--dependency-tree` flag. +This flag is only available with the `--format table` flag. + +The following OS package managers are currently supported: + +| OS Package Managers | +|---------------------| +| apk | +| dpkg | +| rpm | + +The following languages are currently supported: + +| Language | File | +|----------|--------------------------------------------| +| Node.js | [package-lock.json][nodejs-package-lock] | +| | [pnpm-lock.yaml][pnpm-lock] | +| | [yarn.lock][yarn-lock] | +| .NET | [packages.lock.json][dotnet-packages-lock] | +| Python | [poetry.lock][poetry-lock] | +| Ruby | [Gemfile.lock][gemfile-lock] | +| Rust | [cargo-auditable binaries][cargo-binaries] | +| Go | [go.mod][go-mod] | +| PHP | [composer.lock][composer-lock] | +| Java | [pom.xml][pom-xml] | +| | [*gradle.lockfile][gradle-lockfile] | +| Dart | [pubspec.lock][pubspec-lock] | + +This tree is the reverse of the dependency graph. +However, if you want to resolve a vulnerability in a particular indirect dependency, the reversed tree is useful to know where that dependency comes from and identify which package you actually need to update. + +In table output, it looks like: + +```sh +$ trivy fs --severity HIGH,CRITICAL --dependency-tree /path/to/your_node_project + +package-lock.json (npm) +======================= +Total: 2 (HIGH: 1, CRITICAL: 1) + +┌──────────────────┬────────────────┬──────────┬───────────────────┬───────────────┬────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├──────────────────┼────────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤ +│ follow-redirects │ CVE-2022-0155 │ HIGH │ 1.14.6 │ 1.14.7 │ follow-redirects: Exposure of Private Personal Information │ +│ │ │ │ │ │ to an Unauthorized Actor │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-0155 │ +├──────────────────┼────────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤ +│ glob-parent │ CVE-2020-28469 │ CRITICAL │ 3.1.0 │ 5.1.2 │ nodejs-glob-parent: Regular expression denial of service │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-28469 │ +└──────────────────┴────────────────┴──────────┴───────────────────┴───────────────┴────────────────────────────────────────────────────────────┘ + +Dependency Origin Tree (Reversed) +================================= +package-lock.json +├── follow-redirects@1.14.6, (HIGH: 1, CRITICAL: 0) +│ └── axios@0.21.4 +└── glob-parent@3.1.0, (HIGH: 0, CRITICAL: 1) + └── chokidar@2.1.8 + └── watchpack-chokidar2@2.0.1 + └── watchpack@1.7.5 + └── webpack@4.46.0 + └── cra-append-sw@2.7.0 +``` + +Vulnerable dependencies are shown in the top level of the tree. +Lower levels show how those vulnerabilities are introduced. +In the example above **axios@0.21.4** included in the project directly depends on the vulnerable **follow-redirects@1.14.6**. +Also, **glob-parent@3.1.0** with some vulnerabilities is included through chain of dependencies that is added by **cra-append-sw@2.7.0**. + +Then, you can try to update **axios@0.21.4** and **cra-append-sw@2.7.0** to resolve vulnerabilities in **follow-redirects@1.14.6** and **glob-parent@3.1.0**. + +### JSON + +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | ✓ | + +``` +$ trivy image -f json -o results.json golang:1.12-alpine +``` + +
+Result + +``` +2019-05-16T01:46:31.777+0900 INFO Updating vulnerability database... +2019-05-16T01:47:03.007+0900 INFO Detecting Alpine vulnerabilities... +``` + +
+ +
+JSON + +``` +[ + { + "Target": "php-app/composer.lock", + "Vulnerabilities": null + }, + { + "Target": "node-app/package-lock.json", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2018-16487", + "PkgName": "lodash", + "InstalledVersion": "4.17.4", + "FixedVersion": "\u003e=4.17.11", + "Title": "lodash: Prototype pollution in utilities function", + "Description": "A prototype pollution vulnerability was found in lodash \u003c4.17.11 where the functions merge, mergeWith, and defaultsDeep can be tricked into adding or modifying properties of Object.prototype.", + "Severity": "HIGH", + "References": [ + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-16487", + ] + } + ] + }, + { + "Target": "trivy-ci-test (alpine 3.7.1)", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2018-16840", + "PkgName": "curl", + "InstalledVersion": "7.61.0-r0", + "FixedVersion": "7.61.1-r1", + "Title": "curl: Use-after-free when closing \"easy\" handle in Curl_close()", + "Description": "A heap use-after-free flaw was found in curl versions from 7.59.0 through 7.61.1 in the code related to closing an easy handle. ", + "Severity": "HIGH", + "References": [ + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-16840", + ] + }, + { + "VulnerabilityID": "CVE-2019-3822", + "PkgName": "curl", + "InstalledVersion": "7.61.0-r0", + "FixedVersion": "7.61.1-r2", + "Title": "curl: NTLMv2 type-3 header stack buffer overflow", + "Description": "libcurl versions from 7.36.0 to before 7.64.0 are vulnerable to a stack-based buffer overflow. ", + "Severity": "HIGH", + "References": [ + "https://curl.haxx.se/docs/CVE-2019-3822.html", + "https://lists.apache.org/thread.html/8338a0f605bdbb3a6098bb76f666a95fc2b2f53f37fa1ecc89f1146f@%3Cdevnull.infra.apache.org%3E" + ] + }, + { + "VulnerabilityID": "CVE-2018-16839", + "PkgName": "curl", + "InstalledVersion": "7.61.0-r0", + "FixedVersion": "7.61.1-r1", + "Title": "curl: Integer overflow leading to heap-based buffer overflow in Curl_sasl_create_plain_message()", + "Description": "Curl versions 7.33.0 through 7.61.1 are vulnerable to a buffer overrun in the SASL authentication code that may lead to denial of service.", + "Severity": "HIGH", + "References": [ + "https://github.com/curl/curl/commit/f3a24d7916b9173c69a3e0ee790102993833d6c5", + ] + }, + { + "VulnerabilityID": "CVE-2018-19486", + "PkgName": "git", + "InstalledVersion": "2.15.2-r0", + "FixedVersion": "2.15.3-r0", + "Title": "git: Improper handling of PATH allows for commands to be executed from the current directory", + "Description": "Git before 2.19.2 on Linux and UNIX executes commands from the current working directory (as if '.' were at the end of $PATH) in certain cases involving the run_command() API and run-command.c, because there was a dangerous change from execvp to execv during 2017.", + "Severity": "HIGH", + "References": [ + "https://usn.ubuntu.com/3829-1/", + ] + }, + { + "VulnerabilityID": "CVE-2018-17456", + "PkgName": "git", + "InstalledVersion": "2.15.2-r0", + "FixedVersion": "2.15.3-r0", + "Title": "git: arbitrary code execution via .gitmodules", + "Description": "Git before 2.14.5, 2.15.x before 2.15.3, 2.16.x before 2.16.5, 2.17.x before 2.17.2, 2.18.x before 2.18.1, and 2.19.x before 2.19.1 allows remote code execution during processing of a recursive \"git clone\" of a superproject if a .gitmodules file has a URL field beginning with a '-' character.", + "Severity": "HIGH", + "References": [ + "http://www.securitytracker.com/id/1041811", + ] + } + ] + }, + { + "Target": "python-app/Pipfile.lock", + "Vulnerabilities": null + }, + { + "Target": "ruby-app/Gemfile.lock", + "Vulnerabilities": null + }, + { + "Target": "rust-app/Cargo.lock", + "Vulnerabilities": null + } +] +``` + +
+ +`VulnerabilityID`, `PkgName`, `InstalledVersion`, and `Severity` in `Vulnerabilities` are always filled with values, but other fields might be empty. + +### SARIF +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | ✓ | + +[SARIF][sarif] can be generated with the `--format sarif` flag. + +``` +$ trivy image --format sarif -o report.sarif golang:1.12-alpine +``` + +This SARIF file can be uploaded to GitHub code scanning results, and there is a [Trivy GitHub Action][action] for automating this process. + +### GitHub dependency snapshot +Trivy supports the following packages. + +- [OS packages][os_packages] +- [Language-specific packages][language_packages] + +[GitHub dependency snapshots][github-sbom] can be generated with the `--format github` flag. + +``` +$ trivy image --format github -o report.gsbom alpine +``` + +This snapshot file can be [submitted][github-sbom-submit] to your GitHub repository. + +### Template + +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | ✓ | + +#### Custom Template + +{% raw %} +``` +$ trivy image --format template --template "{{ range . }} {{ .Target }} {{ end }}" golang:1.12-alpine +``` +{% endraw %} + +
+Result + +``` +2020-01-02T18:02:32.856+0100 INFO Detecting Alpine vulnerabilities... + golang:1.12-alpine (alpine 3.10.2) +``` +
+ +You can compute different figures within the template using [sprig][sprig] functions. +As an example you can summarize the different classes of issues: + + +{% raw %} +``` +$ trivy image --format template --template '{{- $critical := 0 }}{{- $high := 0 }}{{- range . }}{{- range .Vulnerabilities }}{{- if eq .Severity "CRITICAL" }}{{- $critical = add $critical 1 }}{{- end }}{{- if eq .Severity "HIGH" }}{{- $high = add $high 1 }}{{- end }}{{- end }}{{- end }}Critical: {{ $critical }}, High: {{ $high }}' golang:1.12-alpine +``` +{% endraw %} + +
+Result + +``` +Critical: 0, High: 2 +``` +
+ +For other features of sprig, see the official [sprig][sprig] documentation. + +#### Load templates from a file +You can load templates from a file prefixing the template path with an @. + +``` +$ trivy image --format template --template "@/path/to/template" golang:1.12-alpine +``` + +#### Default Templates + +If Trivy is installed using rpm then default templates can be found at `/usr/local/share/trivy/templates`. + +##### JUnit +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | | +| License | | + +In the following example using the template `junit.tpl` XML can be generated. +``` +$ trivy image --format template --template "@contrib/junit.tpl" -o junit-report.xml golang:1.12-alpine +``` + +##### ASFF +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | | + +Trivy also supports an [ASFF template for reporting findings to AWS Security Hub][asff] + +##### HTML +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | | +| License | | + +``` +$ trivy image --format template --template "@contrib/html.tpl" -o report.html golang:1.12-alpine +``` + +The following example shows use of default HTML template when Trivy is installed using rpm. + +``` +$ trivy image --format template --template "@/usr/local/share/trivy/templates/html.tpl" -o report.html golang:1.12-alpine +``` + +### SBOM +See [here](../supply-chain/sbom.md) for details. + +## Output +Trivy supports the following output destinations: + +- File +- Plugin + +### File +By specifying `--output `, you can output the results to a file. +Here is an example: + +``` +$ trivy image --format json --output result.json debian:12 +``` + +### Plugin +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Plugins capable of receiving Trivy's results via standard input, called "output plugin", can be seamlessly invoked using the `--output` flag. + +``` +$ trivy [--format ] --output plugin= [--output-plugin-arg ] +``` + +This is useful for cases where you want to convert the output into a custom format, or when you want to send the output somewhere. +For more details, please check [here](../advanced/plugins.md#output-plugins). + +## Converting +To generate multiple reports, you can generate the JSON report first and convert it to other formats with the `convert` subcommand. + +```shell +$ trivy image --format json -o result.json --list-all-pkgs debian:11 +$ trivy convert --format cyclonedx --output result.cdx result.json +``` + +!!! note + Please note that if you want to convert to a format that requires a list of packages, + such as SBOM, you need to add the `--list-all-pkgs` flag when outputting in JSON. + +[Filtering options](./filtering.md) such as `--severity` are also available with `convert`. + +```shell +# Output all severities in JSON +$ trivy image --format json -o result.json --list-all-pkgs debian:11 + +# Output only critical issues in table format +$ trivy convert --format table --severity CRITICAL result.json +``` + +!!! note + JSON reports from "trivy aws" and "trivy k8s" are not yet supported. + +[cargo-auditable]: https://github.com/rust-secure-code/cargo-auditable/ +[action]: https://github.com/aquasecurity/trivy-action +[asff]: ../../tutorials/integrations/aws-security-hub.md +[sarif]: https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/managing-results-from-code-scanning +[sprig]: http://masterminds.github.io/sprig/ +[github-sbom]: https://docs.github.com/en/rest/dependency-graph/dependency-submission?apiVersion=2022-11-28#about-dependency-submissions +[github-sbom-submit]: https://docs.github.com/en/rest/dependency-graph/dependency-submission?apiVersion=2022-11-28#create-a-snapshot-of-dependencies-for-a-repository + +[os_packages]: ../scanner/vulnerability.md#os-packages +[language_packages]: ../scanner/vulnerability.md#language-specific-packages + +[nodejs-package-lock]: ../coverage/language/nodejs.md#npm +[pnpm-lock]: ../coverage/language/nodejs.md#pnpm +[yarn-lock]: ../coverage/language/nodejs.md#yarn +[dotnet-packages-lock]: ../coverage/language/dotnet.md#packageslockjson +[poetry-lock]: ../coverage/language/python.md#poetry +[gemfile-lock]: ../coverage/language/ruby.md#bundler +[go-mod]: ../coverage/language/golang.md#go-modules +[composer-lock]: ../coverage/language/php.md#composer +[pom-xml]: ../coverage/language/java.md#pomxml +[gradle-lockfile]: ../coverage/language/java.md#gradlelock +[pubspec-lock]: ../coverage/language/dart.md#dart +[cargo-binaries]: ../coverage/language/rust.md#binaries \ No newline at end of file diff --git a/docs/docs/configuration/skipping.md b/docs/docs/configuration/skipping.md new file mode 100644 index 000000000000..7e228d696fff --- /dev/null +++ b/docs/docs/configuration/skipping.md @@ -0,0 +1,119 @@ +# Skipping Files and Directories + +This section details ways to specify the files and directories that Trivy should not scan. + +## Skip Files +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | ✓ | + +By default, Trivy traverses directories and searches for all necessary files for scanning. +You can skip files that you don't maintain using the `--skip-files` flag, or the equivalent Trivy YAML config option. + +Using the `--skip-files` flag: +```bash +$ trivy image --skip-files "/Gemfile.lock" --skip-files "/var/lib/gems/2.5.0/gems/http_parser.rb-0.6.0/Gemfile.lock" quay.io/fluentd_elasticsearch/fluentd:v2.9.0 +``` + +Using the Trivy YAML configuration: +```yaml +image: + skip-files: + - foo + - "testdata/*/bar" +``` + +It's possible to specify globs as part of the value. + +```bash +$ trivy image --skip-files "./testdata/*/bar" . +``` + +This will skip any file named `bar` in the subdirectories of testdata. + +```bash +$ trivy config --skip-files "./foo/**/*.tf" . +``` + +This will skip any files with the extension `.tf` in subdirectories of foo at any depth. + +## Skip Directories +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | ✓ | +| License | ✓ | + +By default, Trivy traverses directories and searches for all necessary files for scanning. +You can skip directories that you don't maintain using the `--skip-dirs` flag, or the equivalent Trivy YAML config option. + +Using the `--skip-dirs` flag: +```bash +$ trivy image --skip-dirs /var/lib/gems/2.5.0/gems/fluent-plugin-detect-exceptions-0.0.13 --skip-dirs "/var/lib/gems/2.5.0/gems/http_parser.rb-0.6.0" quay.io/fluentd_elasticsearch/fluentd:v2.9.0 +``` + +Using the Trivy YAML configuration: +```yaml +image: + skip-dirs: + - foo/bar/ + - "**/.terraform" +``` + +It's possible to specify globs as part of the value. + +```bash +$ trivy image --skip-dirs "./testdata/*" . +``` + +This will skip all subdirectories of the testdata directory. + +```bash +$ trivy config --skip-dirs "**/.terraform" . +``` + +This will skip subdirectories at any depth named `.terraform/`. (Note: this will match `./foo/.terraform` or +`./foo/bar/.terraform`, but not `./.terraform`.) + +!!! tip + Glob patterns work with any trivy subcommand (image, config, etc.) and can be specified to skip both directories (with `--skip-dirs`) and files (with `--skip-files`). + + +### Advanced globbing +Trivy also supports bash style [extended](https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Pattern-Matching) glob pattern matching. + +```bash +$ trivy image --skip-files "**/foo" image:tag +``` + +This will skip the file `foo` that happens to be nested under any parent(s). + +## File patterns +| Scanner | Supported | +|:----------------:|:---------:| +| Vulnerability | ✓ | +| Misconfiguration | ✓ | +| Secret | | +| License | ✓[^1] | + +When a directory is given as an input, Trivy will recursively look for and test all files based on file patterns. +The default file patterns are [here](../scanner/misconfiguration/custom/index.md). + +In addition to the default file patterns, the `--file-patterns` option takes regexp patterns to look for your files. +For example, it may be useful when your file name of Dockerfile doesn't match the default patterns. + +This can be repeated for specifying multiple file patterns. + +A file pattern contains the analyzer it is used for, and the pattern itself, joined by a semicolon. For example: +``` +--file-patterns "dockerfile:.*.docker" --file-patterns "kubernetes:*.tpl" --file-patterns "pip:requirements-.*\.txt" +``` + +The prefixes are listed [here](https://github.com/aquasecurity/trivy/tree/{{ git.commit }}/pkg/fanal/analyzer/const.go) + + +[^1]: Only work with the [license-full](../scanner/license.md) flag) \ No newline at end of file diff --git a/docs/docs/coverage/iac/azure-arm.md b/docs/docs/coverage/iac/azure-arm.md new file mode 100644 index 000000000000..609c6e0c3055 --- /dev/null +++ b/docs/docs/coverage/iac/azure-arm.md @@ -0,0 +1,33 @@ +# Azure ARM Template +Trivy supports the scanners listed in the table below. + +| Scanner | Supported | +| :----------------: | :-------: | +| [Misconfiguration] | ✓ | +| [Secret] | ✓ | + +It supports the following configurations: + +| Format | Supported | +| :----------: | :-------: | +| ARM template | ✓ | +| Bicep | ✓[^1] | + +To scan Bicep codes, you need to convert them into ARM templates first. + +``` +az bicep build -f main.bicep +or +bicep build main.bicep +``` + +## Misconfiguration +Trivy recursively searches directories and scans all found Azure ARM templates. + +## Secret +The secret scan is performed on plain text files, with no special treatment for Azure ARM templates. + +[Misconfiguration]: ../../scanner/misconfiguration/index.md +[Secret]: ../../scanner/secret.md + +[^1]: Bicep is not natively supported. It needs to be converted into Azure ARM templates. \ No newline at end of file diff --git a/docs/docs/coverage/iac/cloudformation.md b/docs/docs/coverage/iac/cloudformation.md new file mode 100644 index 000000000000..5665e1e77acd --- /dev/null +++ b/docs/docs/coverage/iac/cloudformation.md @@ -0,0 +1,35 @@ +# CloudFormation +Trivy supports the scanners listed in the table below. + +| Scanner | Supported | +|:------------------:|:---------:| +| [Misconfiguration] | ✓ | +| [Secret] | ✓ | + +It supports the following formats. + +| Format | Supported | +|:------:|:---------:| +| JSON | ✓ | +| YAML | ✓ | + +## Misconfiguration +Trivy recursively searches directories and scans all found CloudFormation files. +It evaluates properties, functions, and other elements within CloudFormation files to detect misconfigurations. + +### Value Overrides +You can provide `cf-params` with path to [CloudFormation Parameters] file to Trivy to scan your CloudFormation code with parameters. + +```bash +trivy conf --cf-params params.json ./infrastructure/cf +``` + +You can check a [CloudFormation Parameters Example] + +## Secret +The secret scan is performed on plain text files, with no special treatment for CloudFormation. + +[Misconfiguration]: ../../scanner/misconfiguration/index.md +[Secret]: ../../scanner/secret.md +[CloudFormation Parameters]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html +[CloudFormation Parameters Example]: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudformation/deploy.html#supported-json-syntax \ No newline at end of file diff --git a/docs/docs/coverage/iac/docker.md b/docs/docs/coverage/iac/docker.md new file mode 100644 index 000000000000..8b554e623021 --- /dev/null +++ b/docs/docs/coverage/iac/docker.md @@ -0,0 +1,24 @@ +# Docker +Trivy supports the scanners listed in the table below. + +| Scanner | Supported | +| :----------------: | :-------: | +| [Misconfiguration] | ✓ | +| [Secret] | ✓ | + +It supports the following configurations. + +| Config | Supported | +| :-----------: | :-------: | +| Dockerfile | ✓ | +| Containerfile | ✓ | +| Compose | - | + +## Misconfiguration +Trivy recursively searches directories and scans all found Docker files. + +## Secret +The secret scan is performed on plain text files, with no special treatment for Dockerfile. + +[Misconfiguration]: ../../scanner/misconfiguration/index.md +[Secret]: ../../scanner/secret.md \ No newline at end of file diff --git a/docs/docs/coverage/iac/helm.md b/docs/docs/coverage/iac/helm.md new file mode 100644 index 000000000000..4f9f87de8608 --- /dev/null +++ b/docs/docs/coverage/iac/helm.md @@ -0,0 +1,60 @@ +# Helm +Trivy supports two types of Helm scanning, templates and packaged charts. +The following scanners are supported. + +| Format | [Misconfiguration] | [Secret] | +| -------- | :----------------: | :------: | +| Template | ✓ | ✓ | +| Chart | ✓ | - | + +## Misconfiguration +Trivy recursively searches directories and scans all found Helm files. + +It evaluates variables, functions, and other elements within Helm templates and resolve the chart to Kubernetes manifests then run the Kubernetes checks. +See [here](../../scanner/misconfiguration/policy/builtin.md) for more details on the built-in policies. + +### Value overrides +There are a number of options for overriding values in Helm charts. +When override values are passed to the Helm scanner, the values will be used during the Manifest rendering process and will become part of the scanned artifact. + +#### Setting inline value overrides +Overrides can be set inline on the command line + +```bash +trivy conf --helm-set securityContext.runAsUser=0 ./charts/mySql +``` + +#### Setting value file overrides +Overrides can be in a file that has the key=value set. + +```yaml +# Example override file (overrides.yaml) + +securityContext: + runAsUser: 0 +``` + +```bash +trivy conf --helm-values overrides.yaml ./charts/mySql +``` + +#### Setting value as explicit string +the `--helm-set-string` is the same as `--helm-set` but explicitly retains the value as a string + +```bash +trivy config --helm-set-string name=false ./infrastructure/tf +``` + +#### Setting specific values from files +Specific override values can come from specific files + +```bash +trivy conf --helm-set-file environment=dev.values.yaml ./charts/mySql +``` + +## Secret +The secret scan is performed on plain text files, with no special treatment for Helm. +Secret scanning is not conducted on the contents of packaged Charts, such as tar or tar.gz. + +[Misconfiguration]: ../../scanner/misconfiguration/index.md +[Secret]: ../../scanner/secret.md \ No newline at end of file diff --git a/docs/docs/coverage/iac/index.md b/docs/docs/coverage/iac/index.md new file mode 100644 index 000000000000..168c3dd650fa --- /dev/null +++ b/docs/docs/coverage/iac/index.md @@ -0,0 +1,22 @@ +# Infrastructure as Code + +## Scanner +Trivy scans Infrastructure as Code (IaC) files for + +- [Misconfigurations][misconf] +- [Secrets][secret] + +## Supported configurations + +| Config type | File patterns | +|-------------------------------------|-----------------------------------------------| +| [Kubernetes](kubernetes.md) | \*.yml, \*.yaml, \*.json | +| [Docker](docker.md) | Dockerfile, Containerfile | +| [Terraform](terraform.md) | \*.tf, \*.tf.json, \*.tfvars | +| [Terraform Plan](terraform.md) | tfplan, \*.tfplan, \*.tfplan.json, \*.tf.json | +| [CloudFormation](cloudformation.md) | \*.yml, \*.yaml, \*.json | +| [Azure ARM Template](azure-arm.md) | \*.json | +| [Helm](helm.md) | \*.yaml, \*.tpl, \*.tar.gz, etc. | + +[misconf]: ../../scanner/misconfiguration/index.md +[secret]: ../../scanner/secret.md diff --git a/docs/docs/coverage/iac/kubernetes.md b/docs/docs/coverage/iac/kubernetes.md new file mode 100644 index 000000000000..61b5163438ca --- /dev/null +++ b/docs/docs/coverage/iac/kubernetes.md @@ -0,0 +1,31 @@ +# Kubernetes +Trivy supports the scanners listed in the table below. + +| Scanner | Supported | +| :----------------: | :-------: | +| [Misconfiguration] | ✓ | +| [Secret] | ✓ | + +In addition to raw YAML and JSON, it supports the following templates: + +| Template | Supported | +| :-------------: | :-------: | +| [Helm](helm.md) | ✓ | +| Kustomize | ✓[^1] | + +!!! note + Trivy does not support Kustomize overlays, so it scans files defined in the base. + Or, you can scan the output of `kustomize build`. + +## Misconfiguration +Trivy recursively searches directories and scans all found Kubernetes files. + +## Secret +The secret scan is performed on plain text files, with no special treatment for Kubernetes. +This means that Base64 encoded secrets are not scanned, and only secrets written in plain text are detected. + + +[Misconfiguration]: ../../scanner/misconfiguration/index.md +[Secret]: ../../scanner/secret.md + +[^1]: Kustomize is not natively supported. \ No newline at end of file diff --git a/docs/docs/coverage/iac/terraform.md b/docs/docs/coverage/iac/terraform.md new file mode 100644 index 000000000000..843126f54d3a --- /dev/null +++ b/docs/docs/coverage/iac/terraform.md @@ -0,0 +1,50 @@ +# Terraform +Trivy supports the scanners listed in the table below. + +| Scanner | Supported | +|:----------------:|:---------:| +| Misconfiguration | ✓ | +| Secret | ✓ | + +It supports the following formats: + +| Format | Supported | +|:-------------:|:---------:| +| JSON | ✓ | +| HCL | ✓ | +| Plan Snapshot | ✓ | +| Plan JSON | ✓ | + +Trivy can scan Terraform Plan files (snapshots) or their JSON representations. To create a Terraform Plan and scan it, run the following command: +```bash +terraform plan --out tfplan +trivy conf tfplan +``` + +To scan a Terraform Plan representation in JSON format, run the following command: +```bash +terraform show -json tfplan > tfplan.json +trivy conf tfplan.json +``` + +## Misconfiguration +Trivy recursively searches directories and scans all found Terraform files. +It also evaluates variables, imports, and other elements within Terraform files to detect misconfigurations. + +### Value Overrides +You can provide `tf-vars` files to Trivy to override default values specified in the Terraform HCL code. + +```bash +trivy conf --tf-vars dev.terraform.tfvars ./infrastructure/tf +``` + +### Exclude Downloaded Terraform Modules +By default, downloaded modules are also scanned. +If you don't want to scan them, you can use the `--tf-exclude-downloaded-modules` flag. + +```bash +trivy conf --tf-exclude-downloaded-modules ./configs +``` + +## Secret +The secret scan is performed on plain text files, with no special treatment for Terraform. \ No newline at end of file diff --git a/docs/docs/coverage/index.md b/docs/docs/coverage/index.md new file mode 100644 index 000000000000..41637ef5a60b --- /dev/null +++ b/docs/docs/coverage/index.md @@ -0,0 +1,9 @@ +# Scanning Coverage +Trivy can detect security issues in many different platforms, languages and configuration files. +This section gives a general overview of that coverage, and can help answer the frequently asked question "Does Trivy support X?". +For more detailed information about the specific platforms and languages, check the relevant documentation. + +- [OS Packages](os/index.md) +- [Language-specific Packages](language/index.md) +- [IaC files](iac/index.md) +- [Kubernetes clusters](./kubernetes.md) \ No newline at end of file diff --git a/docs/docs/coverage/kubernetes.md b/docs/docs/coverage/kubernetes.md new file mode 100644 index 000000000000..5f2b3a62fc5e --- /dev/null +++ b/docs/docs/coverage/kubernetes.md @@ -0,0 +1,24 @@ +# Kubernetes + +When scanning a Kubernetes cluster, Trivy differentiates between the following: + +1. Cluster infrastructure (e.g api-server, kubelet, addons) +1. Cluster configuration (e.g Roles, ClusterRoles). +1. Application workloads (e.g nginx, postgresql). + +Whenever Trivy scans either of these Kubernetes resources, the container image is scanned separately to the Kubernetes resource definition (the YAML manifest) that defines the resource. +When scanning any of the above, the container image is scanned separately to the Kubernetes resource definition (the YAML manifest) that defines the resource. + +Container image is scanned for: + +- Vulnerabilities +- Misconfigurations +- Exposed secrets + +Kubernetes resource definition is scanned for: + +- Vulnerabilities - partially supported through [KBOM scanning](#KBOM) +- Misconfigurations +- Exposed secrets + +To learn more, please see the [documentation for Kubernetes scanning](../target/kubernetes.md). diff --git a/docs/docs/coverage/language/c.md b/docs/docs/coverage/language/c.md new file mode 100644 index 000000000000..6efe1e87e62b --- /dev/null +++ b/docs/docs/coverage/language/c.md @@ -0,0 +1,23 @@ +# C/C++ + +Trivy supports [Conan][conan] C/C++ Package Manager. + +The following scanners are supported. + +| Package manager | SBOM | Vulnerability | License | +| --------------- | :---: | :-----------: | :-----: | +| Conan | ✓ | ✓ | - | + +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +| --------------- | -------------- | :---------------------: | :--------------: | :----------------------------------: | :------: | +| Conan | conan.lock[^1] | ✓ | Excluded | ✓ | ✓ | + +## Conan +In order to detect dependencies, Trivy searches for `conan.lock`[^1]. + +[conan]: https://docs.conan.io/1/index.html +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + +[^1]: `conan.lock` is default name. To scan a custom filename use [file-patterns](../../configuration/skipping.md#file-patterns) \ No newline at end of file diff --git a/docs/docs/coverage/language/dart.md b/docs/docs/coverage/language/dart.md new file mode 100644 index 000000000000..0ce6d1cc52b0 --- /dev/null +++ b/docs/docs/coverage/language/dart.md @@ -0,0 +1,31 @@ +# Dart + +Trivy supports [Dart][dart]. + +The following scanners are supported. + +| Package manager | SBOM | Vulnerability | License | +|-------------------------| :---: | :-----------: |:-------:| +| [Dart][dart-repository] | ✓ | ✓ | - | + +The following table provides an outline of the features Trivy offers. + + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|-------------------------|--------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| +| [Dart][dart-repository] | pubspec.lock | ✓ | Included | ✓ | - | + +## Dart +In order to detect dependencies, Trivy searches for `pubspec.lock`. + +Trivy marks indirect dependencies, but `pubspec.lock` file doesn't have options to separate root and dev transitive dependencies. +So Trivy includes all dependencies in report. + +To build `dependency tree` Trivy parses [cache directory][cache-directory]. Currently supported default directories and `PUB_CACHE` environment (absolute path only). +!!! note + Make sure the cache directory contains all the dependencies installed in your application. To download missing dependencies, use `dart pub get` command. + +[dart]: https://dart.dev/ +[dart-repository]: https://pub.dev/ +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[cache-directory]: https://dart.dev/tools/pub/glossary#system-cache diff --git a/docs/docs/coverage/language/dotnet.md b/docs/docs/coverage/language/dotnet.md new file mode 100644 index 000000000000..0a05454365e9 --- /dev/null +++ b/docs/docs/coverage/language/dotnet.md @@ -0,0 +1,51 @@ +# .NET + +Trivy supports `.NET core` and `NuGet` package managers. + +The following scanners are supported. + +| Artifact | SBOM | Vulnerability | License | +|-----------|:----:|:-------------:|:-------:| +| .Net Core | ✓ | ✓ | - | +| NuGet | ✓ | ✓ | ✓ | + +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|:---------------:|--------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| +| .Net Core | *.deps.json | ✓ | Excluded | - | ✓ | +| NuGet | packages.config | ✓ | Excluded | - | - | +| NuGet | *Packages.props | - | Excluded | - | - | +| NuGet | packages.lock.json | ✓ | Included | ✓ | ✓ | + +## *.deps.json +Trivy parses `*.deps.json` files. Trivy currently excludes dev dependencies from the report. + +## packages.config +Trivy only finds dependency names and versions from `packages.config` files. To build dependency graph, it is better to use `packages.lock.json` files. + +## *Packages.props +Trivy parses `*Packages.props` files. Both legacy `Packages.props` and modern `Directory.Packages.props` are supported. + +### license detection +`packages.config` files don't have information about the licenses used. +Trivy uses [*.nuspec][nuspec] files from [global packages folder][global-packages] to detect licenses. +!!! note + The `licenseUrl` field is [deprecated][license-url]. Trivy doesn't parse this field and only checks the [license] field (license `expression` type only). +Currently only the default path and `NUGET_PACKAGES` environment variable are supported. + +## packages.lock.json +Don't forgot to [enable][enable-lock] lock files in your project. + +!!! tip + Please make sure your lock file is up-to-date after modifying dependencies. + +### license detection +Same as [packages.config](#license-detection) + +[enable-lock]: https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#enabling-the-lock-file +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[nuspec]: https://learn.microsoft.com/en-us/nuget/reference/nuspec +[global-packages]: https://learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders +[license]: https://learn.microsoft.com/en-us/nuget/reference/nuspec#license +[license-url]: https://learn.microsoft.com/en-us/nuget/reference/nuspec#licenseurl diff --git a/docs/docs/coverage/language/elixir.md b/docs/docs/coverage/language/elixir.md new file mode 100644 index 000000000000..c447dc4ec1bd --- /dev/null +++ b/docs/docs/coverage/language/elixir.md @@ -0,0 +1,27 @@ +# Elixir + +Trivy supports [Hex][hex] repository for [Elixir][elixir]. + +The following scanners are supported. + +| Package manager | SBOM | Vulnerability | License | +|-----------------| :---: | :-----------: |:-------:| +| [hex][hex] | ✓ | ✓ | - | + +The following table provides an outline of the features Trivy offers. + + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|-----------------|--------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| +| [hex][hex] | mix.lock[^1] | ✓ | Excluded | - | ✓ | + +## Hex +In order to detect dependencies, Trivy searches for `mix.lock`[^1]. + +[Configure](https://hexdocs.pm/mix/Mix.Project.html#module-configuration) your project to use `mix.lock`[^1] file. + +[elixir]: https://elixir-lang.org/ +[hex]: https://hex.pm/ +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + +[^1]: `mix.lock` is default name. To scan a custom filename use [file-patterns](../../configuration/skipping.md#file-patterns) \ No newline at end of file diff --git a/docs/docs/coverage/language/golang.md b/docs/docs/coverage/language/golang.md new file mode 100644 index 000000000000..54bebfdc4185 --- /dev/null +++ b/docs/docs/coverage/language/golang.md @@ -0,0 +1,80 @@ +# Go + +## Features +Trivy supports two types of Go scanning, Go Modules and binaries built by Go. + +The following scanners are supported. + +| Artifact | SBOM | Vulnerability | License | +| -------- | :---: | :-----------: | :-----: | +| Modules | ✓ | ✓ | ✓[^2] | +| Binaries | ✓ | ✓ | - | + +The table below provides an outline of the features Trivy offers. + +| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | +|----------|:-----------:|:-----------------|:----------------------------------:| +| Modules | ✅ | Include | ✅[^2] | +| Binaries | ✅ | Exclude | - | + +!!! note + Trivy scans only dependencies of the Go project. + Let's say you scan the Docker binary, Trivy doesn't detect vulnerabilities of Docker itself. + Also, when you scan go.mod in Kubernetes, the Kubernetes vulnerabilities will not be found. + +### Go Modules +Depending on Go versions, the required files are different. + +| Version | Required files | Offline | +| ------- | :------------: | :-----: | +| \>=1.17 | go.mod | ✅ | +| <1.17 | go.mod, go.sum | ✅ | + +In Go 1.17+ projects, Trivy uses `go.mod` for direct/indirect dependencies. +On the other hand, it uses `go.mod` for direct dependencies and `go.sum` for indirect dependencies in Go 1.16 or less. + +Go 1.17+ holds actually needed indirect dependencies in `go.mod`, and it reduces false detection. +`go.sum` in Go 1.16 or less contains all indirect dependencies that are even not needed for compiling. +If you want to have better detection, please consider updating the Go version in your project. + +!!! note + The Go version doesn't mean your CLI version, but the Go version in your go.mod. + + ``` + module github.com/aquasecurity/trivy + + go 1.18 + + require ( + github.com/CycloneDX/cyclonedx-go v0.5.0 + ... + ) + ``` + + To update the Go version in your project, you need to run the following command. + + ``` + $ go mod tidy -go=1.18 + ``` + +To identify licenses and dependency relationships, you need to download modules to local cache beforehand, +such as `go mod download`, `go mod tidy`, etc. +Trivy traverses `$GOPATH/pkg/mod` and collects those extra information. + +### Go binaries +Trivy scans binaries built by Go. +If there is a Go binary in your container image, Trivy automatically finds and scans it. + +Also, you can scan your local binaries. + +``` +$ trivy rootfs ./your_binary +``` + +!!! note + It doesn't work with UPX-compressed binaries. + +[^1]: It doesn't require the Internet access. +[^2]: Need to download modules to local cache beforehand + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies \ No newline at end of file diff --git a/docs/docs/coverage/language/index.md b/docs/docs/coverage/language/index.md new file mode 100644 index 000000000000..470250216eea --- /dev/null +++ b/docs/docs/coverage/language/index.md @@ -0,0 +1,69 @@ +# Programming Language + +Trivy supports programming languages for + +- [SBOM][sbom] +- [Vulnerabilities][vuln] +- [Licenses][license] + +## Supported languages +The files analyzed vary depending on the target. +This is because Trivy primarily categorizes targets into two groups: + +- Pre-build +- Post-build + +If the target is a pre-build project, like a code repository, Trivy will analyze files used for building, such as lock files. +On the other hand, when the target is a post-build artifact, like a container image, Trivy will analyze installed package metadata like `.gemspec`, binary files, and so on. + +| Language | File | Image[^5] | Rootfs[^6] | Filesystem[^7] | Repository[^8] | +|----------------------|--------------------------------------------------------------------------------------------|:---------:|:----------:|:--------------:|:--------------:| +| [Ruby](ruby.md) | Gemfile.lock | - | - | ✅ | ✅ | +| | gemspec | ✅ | ✅ | - | - | +| [Python](python.md) | Pipfile.lock | - | - | ✅ | ✅ | +| | poetry.lock | - | - | ✅ | ✅ | +| | requirements.txt | - | - | ✅ | ✅ | +| | egg package[^1] | ✅ | ✅ | - | - | +| | wheel package[^2] | ✅ | ✅ | - | - | +| | conda package[^3] | ✅ | ✅ | - | - | +| [PHP](php.md) | composer.lock | ✅ | ✅ | ✅ | ✅ | +| [Node.js](nodejs.md) | package-lock.json | - | - | ✅ | ✅ | +| | yarn.lock | - | - | ✅ | ✅ | +| | pnpm-lock.yaml | - | - | ✅ | ✅ | +| | package.json | ✅ | ✅ | - | - | +| [.NET](dotnet.md) | packages.lock.json | ✅ | ✅ | ✅ | ✅ | +| | packages.config | ✅ | ✅ | ✅ | ✅ | +| | .deps.json | ✅ | ✅ | ✅ | ✅ | +| | *Packages.props[^11] | ✅ | ✅ | ✅ | ✅ | +| [Java](java.md) | JAR/WAR/PAR/EAR[^4] | ✅ | ✅ | - | - | +| | pom.xml | - | - | ✅ | ✅ | +| | *gradle.lockfile | - | - | ✅ | ✅ | +| [Go](golang.md) | Binaries built by Go | ✅ | ✅ | - | - | +| | go.mod | - | - | ✅ | ✅ | +| [Rust](rust.md) | Cargo.lock | ✅ | ✅ | ✅ | ✅ | +| | Binaries built with [cargo-auditable](https://github.com/rust-secure-code/cargo-auditable) | ✅ | ✅ | - | - | +| [C/C++](c.md) | conan.lock | - | - | ✅ | ✅ | +| [Elixir](elixir.md) | mix.lock[^10] | - | - | ✅ | ✅ | +| [Dart](dart.md) | pubspec.lock | - | - | ✅ | ✅ | +| [Swift](swift.md) | Podfile.lock | - | - | ✅ | ✅ | +| | Package.resolved | - | - | ✅ | ✅ | + +The path of these files does not matter. + +Example: [Dockerfile](https://github.com/aquasecurity/trivy-ci-test/blob/main/Dockerfile) + +[sbom]: ../../supply-chain/sbom.md +[vuln]: ../../scanner/vulnerability.md +[license]: ../../scanner/license.md + +[^1]: `*.egg-info`, `*.egg-info/PKG-INFO`, `*.egg` and `EGG-INFO/PKG-INFO` +[^2]: `.dist-info/META-DATA` +[^3]: `envs/*/conda-meta/*.json` +[^4]: `*.jar`, `*.war`, `*.par` and `*.ear` +[^5]: ✅ means "enabled" and `-` means "disabled" in the image scanning +[^6]: ✅ means "enabled" and `-` means "disabled" in the rootfs scanning +[^7]: ✅ means "enabled" and `-` means "disabled" in the filesystem scanning +[^8]: ✅ means "enabled" and `-` means "disabled" in the git repository scanning +[^9]: ✅ means that Trivy detects line numbers where each dependency is declared in the scanned file. Only supported in [json](../../configuration/reporting.md#json) and [sarif](../../configuration/reporting.md#sarif) formats. SARIF uses `startline == 1 and endline == 1` for unsupported file types +[^10]: To scan a filename other than the default filename use [file-patterns](../../configuration/skipping.md#file-patterns) +[^11]: `Directory.Packages.props` and legacy `Packages.props` file names are supported diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md new file mode 100644 index 000000000000..e2e97b46c61f --- /dev/null +++ b/docs/docs/coverage/language/java.md @@ -0,0 +1,95 @@ +# Java +Trivy supports three types of Java scanning: `JAR/WAR/PAR/EAR`, `pom.xml` and `*gradle.lockfile` files. + +Each artifact supports the following scanners: + +| Artifact | SBOM | Vulnerability | License | +|------------------|:----:|:-------------:|:-------:| +| JAR/WAR/PAR/EAR | ✓ | ✓ | - | +| pom.xml | ✓ | ✓ | ✓ | +| *gradle.lockfile | ✓ | ✓ | ✓ | + +The following table provides an outline of the features Trivy offers. + +| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:| +| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | +| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | +| *gradle.lockfile | - | Exclude | ✓ | ✓ | + +These may be enabled or disabled depending on the target. +See [here](./index.md) for the detail. + +## JAR/WAR/PAR/EAR +To find information about your JAR[^2] file, Trivy parses `pom.properties` and `MANIFEST.MF` files in your JAR[^2] file and takes required properties[^3]. + +If those files don't exist or don't contain enough information - Trivy will try to find this JAR[^2] file in [trivy-java-db](https://github.com/aquasecurity/trivy-java-db). +The Java DB will be automatically downloaded/updated when any JAR[^2] file is found. +It is stored in [the cache directory](../../configuration/cache.md#cache-directory). + +!!! warning "EXPERIMENTAL" + Finding JARs in `trivy-java-db` is an experimental function. + +Base JAR[^2] may contain inner JARs[^2] within itself. +To find information about these JARs[^2], the same logic is used as for the base JAR[^2]. + +`table` format only contains the name of root JAR[^2] . To get the full path to inner JARs[^2] use the `json` format. + +## pom.xml +Trivy parses your `pom.xml` file and tries to find files with dependencies from these local locations. + +- project directory[^4] +- relativePath field[^5] +- local repository directory[^6]. + +If your machine doesn't have the necessary files - Trivy tries to find the information about these dependencies in the [maven repository](https://repo.maven.apache.org/maven2/). + +!!! Note + Trivy only takes information about packages. We don't take a list of vulnerabilities for packages from the `maven repository`. + Information about data sources for Java you can see [here](../../scanner/vulnerability.md#data-sources-1). + +You can disable connecting to the maven repository with the `--offline-scan` flag. +The `--offline-scan` flag does not affect the Trivy database. +The vulnerability database will be downloaded anyway. + +!!! Warning + Trivy may skip some dependencies (that were not found on your local machine) when the `--offline-scan` flag is passed. + + +### maven-invoker-plugin +Typically, the integration tests directory (`**/[src|target]/it/*/pom.xml`) of [maven-invoker-plugin][maven-invoker-plugin] doesn't contain actual `pom.xml` files and should be skipped to avoid noise. + +Trivy marks dependencies from these files as the development dependencies and skip them by default. +If you need to show them, use the `--include-dev-deps` flag. + + +## Gradle.lock +`gradle.lock` files only contain information about used dependencies. + +!!!note + All necessary files are checked locally. Gradle file scanning doesn't require internet access. + +### Dependency-tree +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. +Trivy finds child dependencies from `*.pom` files in the cache[^8] directory. + +But there is no reliable way to determine direct dependencies (even using other files). +Therefore, we mark all dependencies as indirect to use logic to guess direct dependencies and build a dependency tree. + +### Licenses +Trity also can detect licenses for dependencies. + +Make sure that you have cache[^8] directory to find licenses from `*.pom` dependency files. + +[^1]: Uses maven repository to get information about dependencies. Internet access required. +[^2]: It means `*.jar`, `*.war`, `*.par` and `*.ear` file +[^3]: `ArtifactID`, `GroupID` and `Version` +[^4]: e.g. when parent pom.xml file has `../pom.xml` path +[^5]: When you use dependency path in `relativePath` field in pom.xml file +[^6]: `/Users//.m2/repository` (for Linux and Mac) and `C:/Users//.m2/repository` (for Windows) by default +[^7]: To avoid confusion, Trivy only finds locations for direct dependencies from the base pom.xml file. +[^8]: The supported directories are `$GRADLE_USER_HOME/caches` and `$HOME/.gradle/caches` (`%HOMEPATH%\.gradle\caches` for Windows). + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[maven-invoker-plugin]: https://maven.apache.org/plugins/maven-invoker-plugin/usage.html \ No newline at end of file diff --git a/docs/docs/coverage/language/nodejs.md b/docs/docs/coverage/language/nodejs.md new file mode 100644 index 000000000000..c371a1117178 --- /dev/null +++ b/docs/docs/coverage/language/nodejs.md @@ -0,0 +1,73 @@ +# Node.js + +Trivy supports four types of Node.js package managers: `npm`, `Yarn`, `pnpm` and `Bun`[^1]. + +The following scanners are supported. + +| Artifact | SBOM | Vulnerability | License | +|----------|:----:|:-------------:|:-------:| +| npm | ✓ | ✓ | ✓ | +| Yarn | ✓ | ✓ | ✓ | +| pnpm | ✓ | ✓ | - | +| Bun | ✓ | ✓ | ✓ | + +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|:---------------:|-------------------|:-----------------------:|:-----------------:|:------------------------------------:|:--------:| +| npm | package-lock.json | ✓ | [Excluded](#npm) | ✓ | ✓ | +| Yarn | yarn.lock | ✓ | [Excluded](#yarn) | ✓ | ✓ | +| pnpm | pnpm-lock.yaml | ✓ | Excluded | ✓ | - | +| Bun | yarn.lock | ✓ | [Excluded](#yarn) | ✓ | ✓ | + +In addition, Trivy scans installed packages with `package.json`. + +| File | Dependency graph | Position | License | +|--------------|:----------------:|:--------:|:-------:| +| package.json | - | - | ✅ | + +These may be enabled or disabled depending on the target. +See [here](./index.md) for the detail. + +## Package managers +Trivy parses your files generated by package managers in filesystem/repository scanning. + +!!! tip + Please make sure your lock file is up-to-date after modifying `package.json`. + +### npm +Trivy parses `package-lock.json`. +To identify licenses, you need to download dependencies to `node_modules` beforehand. +Trivy analyzes `node_modules` for licenses. + +By default, Trivy doesn't report development dependencies. Use the `--include-dev-deps` flag to include them. + +### Yarn +Trivy parses `yarn.lock`, which doesn't contain information about development dependencies. +Trivy also uses `package.json` file to handle [aliases](https://classic.yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add-alias). + +To exclude devDependencies and allow aliases, `package.json` also needs to be present next to `yarn.lock`. + +Trivy analyzes `.yarn` (Yarn 2+) or `node_modules` (Yarn Classic) folder next to the yarn.lock file to detect licenses. + +By default, Trivy doesn't report development dependencies. Use the `--include-dev-deps` flag to include them. + +### pnpm +Trivy parses `pnpm-lock.yaml`, then finds production dependencies and builds a [tree][dependency-graph] of dependencies with vulnerabilities. + +### Bun +Trivy supports scanning `yarn.lock` files generated by [Bun](https://bun.sh/docs/install/lockfile#how-do-i-inspect-bun-s-lockfile). You can use the command `bun install -y` to generate a Yarn-compatible `yarn.lock`. + +!!! note + `bun.lockb` is not supported. + +## Packages +Trivy parses the manifest files of installed packages in container image scanning and so on. + +### package.json +Trivy searches for `package.json` files under `node_modules` and identifies installed packages. +It only extracts package names, versions and licenses for those packages. + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + +[^1]: [yarn.lock](#bun) must be generated diff --git a/docs/docs/coverage/language/php.md b/docs/docs/coverage/language/php.md new file mode 100644 index 000000000000..6fa138c35290 --- /dev/null +++ b/docs/docs/coverage/language/php.md @@ -0,0 +1,26 @@ +# PHP + +Trivy supports [Composer][composer], which is a tool for dependency management in PHP. + +The following scanners are supported. + +| Package manager | SBOM | Vulnerability | License | +| --------------- | :---: | :-----------: | :-----: | +| Composer | ✓ | ✓ | ✓ | + +The following table provides an outline of the features Trivy offers. + + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|-----------------|---------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| +| Composer | composer.lock | ✓ | Excluded | ✓ | ✓ | + +## Composer +In order to detect dependencies, Trivy searches for `composer.lock`. + +Trivy also supports dependency trees; however, to display an accurate tree, it needs to know whether each package is a direct dependency of the project. +Since this information is not included in `composer.lock`, Trivy parses `composer.json`, which should be located next to `composer.lock`. +If you want to see the dependency tree, please ensure that `composer.json` is present. + +[composer]: https://getcomposer.org/ +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies \ No newline at end of file diff --git a/docs/docs/coverage/language/python.md b/docs/docs/coverage/language/python.md new file mode 100644 index 000000000000..eaed8f1d4b7d --- /dev/null +++ b/docs/docs/coverage/language/python.md @@ -0,0 +1,119 @@ +# Python + +Trivy supports three types of Python package managers: `pip`, `Pipenv` and `Poetry`. +The following scanners are supported for package managers. + +| Package manager | SBOM | Vulnerability | License | +| --------------- | :---: | :-----------: | :-----: | +| pip | ✓ | ✓ | - | +| Pipenv | ✓ | ✓ | - | +| Poetry | ✓ | ✓ | - | + +In addition, Trivy supports three formats of Python packages: `egg`, `wheel` and `conda`. +The following scanners are supported for Python packages. + +| Packaging | SBOM | Vulnerability | License | +| --------- | :---: | :-----------: | :-----: | +| Egg | ✓ | ✓ | ✓ | +| Wheel | ✓ | ✓ | ✓ | +| Conda | ✓ | - | - | + + +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| +| pip | requirements.txt | - | Include | - | - | +| Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | +| Poetry | poetry.lock | ✓ | Exclude | ✓ | | + + +| Packaging | Dependency graph | +| --------- | :--------------: | +| Egg | ✓ | +| Wheel | ✓ | + +These may be enabled or disabled depending on the target. +See [here](./index.md) for the detail. + +## Package managers +Trivy parses your files generated by package managers in filesystem/repository scanning. + +### pip +Trivy only parses [version specifiers](https://packaging.python.org/en/latest/specifications/version-specifiers/#id4) with `==` comparison operator and without `.*`. +To convert unsupported version specifiers - use the `pip freeze` command. + +```bash +$ cat requirements.txt +boto3~=1.24.60 +click>=8.0 +json-fix==0.5.* +$ pip install -r requirements.txt +... +$ pip freeze > requirements.txt +$ cat requirements.txt +boto3==1.24.96 +botocore==1.27.96 +click==8.1.7 +jmespath==1.0.1 +json-fix==0.5.2 +python-dateutil==2.8.2 +s3transfer==0.6.2 +setuptools==69.0.2 +six==1.16.0 +urllib3==1.26.18 +wheel==0.42.0 +``` + +`requirements.txt` files usually contain only the direct dependencies and not contain the transitive dependencies. +Therefore, Trivy scans only for the direct dependencies with `requirements.txt`. + +To detect transitive dependencies as well, you need to generate `requirements.txt` with `pip freeze`. + +```zsh +$ cat requirements.txt # it will only find `requests@2.28.2`. +requests==2.28.2 +$ pip install -r requirements.txt +... + +$ pip freeze > requirements.txt +$ cat requirements.txt # it will also find the transitive dependencies of `requests@2.28.2`. +certifi==2022.12.7 +charset-normalizer==3.1.0 +idna==3.4 +PyJWT==2.1.0 +requests==2.28.2 +urllib3==1.26.15 +``` + +`pip freeze` also helps to resolve [extras](https://packaging.python.org/en/latest/tutorials/installing-packages/#installing-extras)(optional) dependencies (like `package[extras]=0.0.0`). + +`requirements.txt` files don't contain information about dependencies used for development. +Trivy could detect vulnerabilities on the development packages, which not affect your production environment. + +License detection is not supported for `pip`. + +### Pipenv +Trivy parses `Pipfile.lock`. +`Pipfile.lock` files don't contain information about dependencies used for development. +Trivy could detect vulnerabilities on the development packages, which not affect your production environment. + +License detection is not supported for `Pipenv`. + +### Poetry +Trivy uses `poetry.lock` to identify dependencies and find vulnerabilities. +To build the correct dependency graph, `pyproject.toml` also needs to be present next to `poetry.lock`. + +License detection is not supported for `Poetry`. + +## Packaging +Trivy parses the manifest files of installed packages in container image scanning and so on. +See [here](https://packaging.python.org/en/latest/discussions/wheel-vs-egg/) for the detail. + +### Egg +Trivy looks for `*.egg-info`, `*.egg-info/PKG-INFO`, `*.egg` and `EGG-INFO/PKG-INFO` to identify Python packages. + +### Wheel +Trivy looks for `.dist-info/META-DATA` to identify Python packages. + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies diff --git a/docs/docs/coverage/language/ruby.md b/docs/docs/coverage/language/ruby.md new file mode 100644 index 000000000000..70dd1b2e8182 --- /dev/null +++ b/docs/docs/coverage/language/ruby.md @@ -0,0 +1,30 @@ +# Ruby + +Trivy supports [Bundler][bundler] and [RubyGems][rubygems]. +The following scanners are supported for Cargo. + +| Package manager | SBOM | Vulnerability | License | +|-----------------|:----:|:-------------:|:-------:| +| Bundler | ✓ | ✓ | - | +| RubyGems | ✓ | ✓ | ✓ | + + +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|-----------------|--------------|:-----------------------:|:-----------------|:------------------------------------:|:--------:| +| Bundler | Gemfile.lock | ✓ | Included | ✓ | ✓ | +| RubyGems | .gemspec | - | Included | - | - | + + +### Bundler +Trivy searches for `Gemfile.lock` to detect dependencies. + + +### RubyGems +`.gemspec` files doesn't contains transitive dependencies. You need to scan each `.gemspec` file separately. + +[bundler]: https://bundler.io +[rubygems]: https://rubygems.org/ +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + diff --git a/docs/docs/coverage/language/rust.md b/docs/docs/coverage/language/rust.md new file mode 100644 index 000000000000..ddcda798d4cb --- /dev/null +++ b/docs/docs/coverage/language/rust.md @@ -0,0 +1,44 @@ +# Rust + +Trivy supports [Cargo](https://doc.rust-lang.org/stable/cargo/), which is the Rust package manager. +The following scanners are supported for Cargo. + +| Package manager | SBOM | Vulnerability | License | +| --------------- | :---: | :-----------: | :-----: | +| Cargo | ✓ | ✓ | - | + +In addition, it supports binaries built with [cargo-auditable](https://github.com/rust-secure-code/cargo-auditable). + +| Artifact | SBOM | Vulnerability | License | +| -------- | :---: | :-----------: | :-----: | +| Binaries | ✓ | ✓ | - | + +## Features +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|-----------------|------------|:-----------------------:|:-----------------|:------------------------------------:|:--------:| +| Cargo | Cargo.lock | ✓ | Excluded[^1] | ✓ | ✓ | + + +| Artifact | Transitive dependencies | Dev dependencies | Dependency graph | Position | +| -------- | :---------------------: | :--------------- | :--------------: | :------: | +| Binaries | ✓ | Excluded | - | - | + + +### Cargo +Trivy searches for `Cargo.lock` to detect dependencies. + +Trivy also supports dependency trees; however, to display an accurate tree, it needs to know whether each package is a direct dependency of the project. +Since this information is not included in `Cargo.lock`, Trivy parses `Cargo.toml`, which should be located next to `Cargo.lock`. +If you want to see the dependency tree, please ensure that `Cargo.toml` is present. + +Scan `Cargo.lock` and `Cargo.toml` together also removes developer dependencies. + +### Binaries +Trivy scans binaries built with [cargo-auditable](https://github.com/rust-secure-code/cargo-auditable). +If such a binary exists, Trivy will identify it as being built with cargo-audit and scan it. + +[^1]: When you scan Cargo.lock and Cargo.toml together. + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies \ No newline at end of file diff --git a/docs/docs/coverage/language/swift.md b/docs/docs/coverage/language/swift.md new file mode 100644 index 000000000000..0245a06b44a3 --- /dev/null +++ b/docs/docs/coverage/language/swift.md @@ -0,0 +1,44 @@ +# Swift + +Trivy supports [CocoaPods][cocoapods] and [Swift][swift] package managers. + +The following scanners are supported. + +| Package manager | SBOM | Vulnerability | License | +|-----------------|:----:|:-------------:|:-------:| +| Swift | ✓ | ✓ | - | +| CocoaPods | ✓ | ✓ | - | + +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|:---------------:|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| +| Swift | Package.resolved | ✓ | Included | - | ✓ | +| CocoaPods | Podfile.lock | ✓ | Included | ✓ | - | + +These may be enabled or disabled depending on the target. +See [here](./index.md) for the detail. + +## Swift +Trivy parses [Package.resolved][package-resolved] file to find dependencies. +Don't forget to update (`swift package update` command) this file before scanning. + +## CocoaPods +CocoaPods uses package names in `PodFile.lock`, but [GitHub Advisory Database (GHSA)][ghsa] Trivy relies on uses Git URLs. +We parse [the CocoaPods Specs][cocoapods-specs] to match package names and links. + +!!! note "Limitation" + Since [GHSA][ghsa] holds only Git URLs, such as github.com/apple/swift-nio, + Trivy can't identify affected submodules, and detect all submodules maintained by the same URL. + For example, [SwiftNIOHTTP1][niohttp1] and [SwiftNIOWebSocket][niowebsocket] both are maintained under `github.com/apple/swift-nio`, + and Trivy detect CVE-2022-3215 for both of them, even though only [SwiftNIOHTTP1][niohttp1] is actually affected. + +[cocoapods]: https://cocoapods.org/ +[cocoapods-specs]: https://github.com/CocoaPods/Specs +[ghsa]: https://github.com/advisories?query=type%3Areviewed+ecosystem%3Aswift +[swift]: https://www.swift.org/package-manager/ +[package-resolved]: https://github.com/apple/swift-package-manager/blob/4a42f2519e3f7b8a731c5ed89b47ed577df8f86c/Documentation/Usage.md#resolving-versions-packageresolved-file +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + +[niohttp1]: https://cocoapods.org/pods/SwiftNIOHTTP1 +[niowebsocket]: https://cocoapods.org/pods/SwiftNIOWebSocket \ No newline at end of file diff --git a/docs/docs/coverage/os/alma.md b/docs/docs/coverage/os/alma.md new file mode 100644 index 000000000000..4c0f7dd39ed2 --- /dev/null +++ b/docs/docs/coverage/os/alma.md @@ -0,0 +1,73 @@ +# AlmaLinux +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +|:------------------------------------:|:---------:| +| Unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages that have been installed through package managers such as `dnf` and `yum`. + +## Vulnerability +AlmaLinux offers its own security advisories, and these are utilized when scanning AlmaLinux for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +### Fixed Version +When looking at fixed versions, it's crucial to consider the patches supplied by AlmaLinux. +For example, for CVE-2023-0464, the fixed version for AlmaLinux 9 is listed as `3.0.7-16.el9_2` in [their advisory][ALSA-2023:3722]. +Note that this is different from the upstream fixed version, which is `3.0.9`, `3.1.1`, and son on. +Typically, only the upstream information gets listed on [NVD], so it's important not to get confused. + +### Severity +Trivy calculates the severity of an issue based on the severity provided by AlmaLinux. +If the severity is not provided or defined yet by AlmaLinux, the severity from the NVD is taken into account. + +Using CVE-2023-0464 as an example, while it is rated as "High" in NVD, AlmaLinux has marked as ["moderate"][ALSA-2023:3722]. +As a result, Trivy will display it as "Medium". + +The table below is the mapping of AlmaLinux's severity to Trivy's severity levels. + +| AlmaLinux | Trivy | +| :-------: | :------: | +| Low | Low | +| Moderate | Medium | +| Important | High | +| Critical | Critical | + +### Status +Trivy supports the following [vulnerability statuses] for AlmaLinux. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | | +| Will Not Fix | | +| Fix Deferred | | +| End of Life | | + + +## License +Trivy identifies licenses by examining the metadata of RPM packages. + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + +[errata]: https://errata.almalinux.org/ + +[ALSA-2023:3722]: https://errata.almalinux.org/9/ALSA-2023-3722.html +[NVD]: https://nvd.nist.gov/vuln/detail/CVE-2023-0464 + +[vulnerability statuses]: ../../configuration/filtering.md#by-status \ No newline at end of file diff --git a/docs/docs/coverage/os/alpine.md b/docs/docs/coverage/os/alpine.md new file mode 100644 index 000000000000..baa2de10c3ce --- /dev/null +++ b/docs/docs/coverage/os/alpine.md @@ -0,0 +1,59 @@ +# Alpine Linux +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +|:------------------------------------:|:---------:| +| Unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages that have been installed through `apk`. + +## Vulnerability +Alpine Linux offers its own security advisories, and these are utilized when scanning Alpine for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +### Fixed Version +When looking at fixed versions, it's crucial to consider the patches supplied by Alpine. +For example, for CVE-2023-0464, the fixed version for Alpine Linux is listed as `3.1.0-r1` in [the secfixes][CVE-2023-0464]. +Note that this is different from the upstream fixed version, which is `3.1.1`. +Typically, only the upstream information gets listed on [NVD], so it's important not to get confused. + +### Severity +For Alpine vulnerabilities, the severity is determined using the values set by NVD. + +### Status +Trivy supports the following [vulnerability statuses] for Alpine. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | | +| Will Not Fix | | +| Fix Deferred | | +| End of Life | | + +## License +Trivy identifies licenses by examining the metadata of APK packages. + + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[secdb]: https://secdb.alpinelinux.org/ + +[CVE-2023-0464]: https://gitlab.alpinelinux.org/alpine/aports/-/blob/dad5b7380ab3be705951ce6fd2d7bba513d6a744/main/openssl/APKBUILD#L36-37 +[NVD]: https://nvd.nist.gov/vuln/detail/CVE-2023-0464 + +[vulnerability statuses]: ../../configuration/filtering.md#by-status \ No newline at end of file diff --git a/docs/docs/coverage/os/amazon.md b/docs/docs/coverage/os/amazon.md new file mode 100644 index 000000000000..4cbc8ad370b6 --- /dev/null +++ b/docs/docs/coverage/os/amazon.md @@ -0,0 +1,72 @@ +# Amazon Linux +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +|:------------------------------------:|:---------:| +| Unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages that have been installed through package managers such as `dnf` and `yum`. + +## Vulnerability +Amazon Linux offers its own security advisories, and these are utilized when scanning Amazon Linux for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +### Fixed Version +When looking at fixed versions, it's crucial to consider the patches supplied by Amazon. +For example, for CVE-2023-0464, the fixed version for Amazon Linux 2023 is listed as `3.0.8-1.amzn2023.0.2` in [ALAS2023-2023-181]. +Note that this is different from the upstream fixed version, which is `3.0.9`, `3.1.1`, and so on. +Typically, only the upstream information gets listed on [NVD], so it's important not to get confused. + +### Severity +Trivy determines vulnerability severity based on the severity metric provided by Amazon. +For example, the security patch for [CVE-2023-0464] in Amazon Linux 2023 is provided as [ALAS2023-2023-181]. +Its severity is rated as "Medium". +Thus, even though it's evaluated as "HIGH" in the NVD, Trivy displays it with a severity of "MEDIUM". + +The table below is the mapping of Amazon's severity to Trivy's severity levels. + +| Amazon | Trivy | +| :-------: | :------: | +| Low | Low | +| Medium | Medium | +| Important | High | +| Critical | Critical | + +### Status +Trivy supports the following [vulnerability statuses] for Amazon Linux. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | | +| Will Not Fix | | +| Fix Deferred | | +| End of Life | | + +## License +Trivy identifies licenses by examining the metadata of RPM packages. + + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[center]: https://alas.aws.amazon.com/ + +[CVE-2023-0464]: https://alas.aws.amazon.com/cve/html/CVE-2023-0464.html +[ALAS2023-2023-181]: https://alas.aws.amazon.com/AL2023/ALAS-2023-181.html +[NVD]: https://nvd.nist.gov/vuln/detail/CVE-2023-0464 + +[vulnerability statuses]: ../../configuration/filtering.md#by-status \ No newline at end of file diff --git a/docs/docs/coverage/os/bitnami.md b/docs/docs/coverage/os/bitnami.md new file mode 100644 index 000000000000..56cfb97a4601 --- /dev/null +++ b/docs/docs/coverage/os/bitnami.md @@ -0,0 +1,63 @@ +# Bitnami Images + +!!! warning "EXPERIMENTAL" + Scanning results may be inaccurate. + +While it is not an OS, this page describes the details of the [container images provided by Bitnami](https://github.com/bitnami/containers). +Bitnami images are based on [Debian](debian.md). +Please see [the Debian page](debian.md) for OS packages. + +Trivy supports the following scanners for Bitnami packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +| :----------------------------------: | :-------: | +| Unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | - | + +## SBOM +Trivy analyzes the SBOM information contained within the container images provided by Bitnami. +The SBOM files are located at `/opt/bitnami//.spdx-.spdx`. + +## Vulnerability +Since Bitnami has its [own vulnerability database][vulndb], it uses these for vulnerability detection of applications and packages distributed by Bitnami. + +!!! note + Trivy does not support vulnerability detection of independently compiled binaries, so even if you scan container images like `nginx:1.15.2`, vulnerabilities in Nginx cannot be detected. + This is because main applications like Nginx are [not installed by the package manager](https://github.com/nginxinc/docker-nginx/blob/321a13a966eeff945196ddd31a629dad2aa85eda/mainline/debian/Dockerfile). + However, in the case of Bitnami images, since these SBOMs are stored within the image, scanning `bitnami/nginx:1.15.2` allows for the detection of vulnerabilities in Nginx. + +### Fixed Version +Trivy refers to the [Bitnami database][vulndb]. Please note that these may differ from the upstream fixed versions. + +### Severity +Similar to Fixed versions, it follows Bitnami's vulnerability database. + +### Status +Trivy supports the following [vulnerability statuses] for Bitnami packages. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | | +| Will Not Fix | | +| Fix Deferred | | +| End of Life | | + + + +## License +If licenses are included in the SBOM distributed by Bitnami, they will be used for scanning. + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + +[vulndb]: https://github.com/bitnami/vulndb +[vulnerability statuses]: ../../configuration/filtering.md#by-status diff --git a/docs/docs/coverage/os/cbl-mariner.md b/docs/docs/coverage/os/cbl-mariner.md new file mode 100644 index 000000000000..0ca42bbb9993 --- /dev/null +++ b/docs/docs/coverage/os/cbl-mariner.md @@ -0,0 +1,66 @@ +# CBL-Mariner +Trivy supports the following scanners for OS packages. + +| Version | SBOM | Vulnerability | License | +| ---------------- | :---: | :-----------: | :-----: | +| 1.0 | ✔ | ✔ | ✔ | +| 1.0 (Distroless) | ✔ | ✔ | | +| 2.0 | ✔ | ✔ | ✔ | +| 2.0 (Distroless) | ✔ | ✔ | | + + +The following table provides an outline of the targets Trivy supports. + +| Version | Container image | Virtual machine | Arch | +| ------- | :-------------: | :-------------: | :----------: | +| 1.0 | ✔ | ✔ | amd64, arm64 | +| 2.0 | ✔ | ✔ | amd64, arm64 | + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +|:------------------------------------:|:---------:| +| Detect unfixed vulnerabilities | ✓ | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages that have been installed through package managers such as `dnf` and `yum`. + +## Vulnerability +CBL-Mariner offers its own security advisories, and these are utilized when scanning CBL-Mariner for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +### Fixed Version +Trivy takes fixed versions from [CBL-Mariner OVAL][oval]. + +### Severity +Trivy calculates the severity of an issue based on the severity provided in [CBL-Mariner OVAL][oval]. + +### Status +Trivy supports the following [vulnerability statuses] for CBL-Mariner. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | | +| Will Not Fix | | +| Fix Deferred | | +| End of Life | | + + +## License +Trivy identifies licenses by examining the metadata of RPM packages. + +!!! note + License detection is not supported for CBL-Mariner Distroless. + + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[cbl-mariner]: https://github.com/microsoft/CBL-Mariner + +[oval]: https://github.com/microsoft/CBL-MarinerVulnerabilityData/ + +[vulnerability statuses]: ../../configuration/filtering.md#by-status diff --git a/docs/docs/coverage/os/centos.md b/docs/docs/coverage/os/centos.md new file mode 100644 index 000000000000..be881ae26fb1 --- /dev/null +++ b/docs/docs/coverage/os/centos.md @@ -0,0 +1,38 @@ +# CentOS +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +| :-----------------------------------: | :-------: | +| Unfixed vulnerabilities | ✓ | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Same as [RHEL](rhel.md#sbom). + +## Vulnerability +CentOS does not provide straightforward machine-readable security advisories. +As a result, Trivy utilizes the security advisories from [Red Hat Enterprise Linux (RHEL)](rhel.md#vulnerability) for detecting vulnerabilities in CentOS. +This approach might lead to situations where, even though Trivy displays a fixed version, CentOS might not have the patch available yet. +Since patches released for RHEL often become available in CentOS after some time, it's usually just a matter of waiting. + +!!! note + The case for CentOS Stream, which is not supported by Trivy, is entirely different from CentOS. + +As Trivy relies on Red Hat's advisories, please refer to [Red Hat](rhel.md) for details regarding vulnerability severity and status. + + +## License +Same as [RHEL](rhel.md#license). + + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies \ No newline at end of file diff --git a/docs/docs/coverage/os/chainguard.md b/docs/docs/coverage/os/chainguard.md new file mode 100644 index 000000000000..41a6610aea3f --- /dev/null +++ b/docs/docs/coverage/os/chainguard.md @@ -0,0 +1,32 @@ +# Chainguard +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +| :-----------------------------------: | :-------: | +| Detect unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Same as [Alpine Linux](alpine.md#sbom). + +## Vulnerability +Chainguard offers its own security advisories, and these are utilized when scanning Chainguard for vulnerabilities. +Everything else is the same as [Alpine Linux](alpine.md#vulnerability). + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +## License +Same as [Alpine Linux](alpine.md#license). + + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[secdb]: https://packages.cgr.dev/chainguard/security.json \ No newline at end of file diff --git a/docs/docs/coverage/os/debian.md b/docs/docs/coverage/os/debian.md new file mode 100644 index 000000000000..a5e28e01e639 --- /dev/null +++ b/docs/docs/coverage/os/debian.md @@ -0,0 +1,74 @@ +# Debian +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +| :-----------------------------------: | :-------: | +| Unfixed vulnerabilities | ✓ | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages that have been installed through package managers such as `apt` and `dpkg`. +While there are some exceptions, like Go binaries and JAR files, it's important to note that binaries that have been custom-built using `make` or tools installed via `curl` are generally not detected. + +## Vulnerability +Debian offers its own security advisories, and these are utilized when scanning Debian for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +### Fixed Version +When looking at fixed versions, it's crucial to consider the patches supplied by Debian. +For example, for CVE-2023-3269, the fixed version for Debian 12 (bookworm) is listed as `6.1.37-1` in [the Security Tracker][CVE-2023-3269]. +This patch is provided in [DSA-5448-1]. +Note that this is different from the upstream fixed version, which is `6.5`. +Typically, only the upstream information gets listed on [NVD], so it's important not to get confused. + +### Severity +Trivy calculates the severity of an issue based on the 'Urgency' metric found in the Security Tracker. +If 'Urgency' isn't provided by Debian, the severity from the NVD is taken into account. + +Using CVE-2019-15052 as an example, while it is rated as "Critical" in NVD, Debian has marked its "Urgency" as ["Low"][CVE-2019-15052]. +As a result, Trivy will display it as "Low". + +### Status +Trivy supports the following [vulnerability statuses] for Debian. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | | +| Will Not Fix | | +| Fix Deferred | ✓ | +| End of Life | ✓ | + +## License +To identify the license of a package, Trivy checks the copyright file located at `/usr/share/doc/*/copyright`. + +However, this method has its limitations as the file isn't machine-readable, leading to situations where the license isn't detected. +In such scenarios, the `--license-full` flag can be passed. +It compares the contents of known licenses with the copyright file to discern the license in question. +Please be aware that using this flag can increase memory usage, so it's disabled by default for efficiency. + + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + +[debian-tracker]: https://security-tracker.debian.org/tracker/ +[debian-oval]: https://www.debian.org/security/oval/ + +[CVE-2023-3269]: https://security-tracker.debian.org/tracker/CVE-2023-3269 +[CVE-2019-15052]: https://security-tracker.debian.org/tracker/CVE-2019-15052 +[DSA-5448-1]: https://security-tracker.debian.org/tracker/DSA-5448-1 +[NVD]: https://nvd.nist.gov/vuln/detail/CVE-2023-3269 + +[vulnerability statuses]: ../../configuration/filtering.md#by-status \ No newline at end of file diff --git a/docs/docs/coverage/os/google-distroless.md b/docs/docs/coverage/os/google-distroless.md new file mode 100644 index 000000000000..ef5b3df917c7 --- /dev/null +++ b/docs/docs/coverage/os/google-distroless.md @@ -0,0 +1,34 @@ +# Google Distroless Images +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +| :----------------------------------: | :-------: | +| Unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages pre-installed in distroless images. + +## Vulnerability +Google Distroless is based on [Debian]; see there for details. + +## License +Google Distroless is based on [Debian]; see there for details. + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + +[metadata]: https://packages.vmware.com/photon/photon_cve_metadata/ + +[vulnerability statuses]: ../../configuration/filtering.md#by-status + +[Debian]: debian.md \ No newline at end of file diff --git a/docs/docs/coverage/os/index.md b/docs/docs/coverage/os/index.md new file mode 100644 index 000000000000..e04a452fc4d3 --- /dev/null +++ b/docs/docs/coverage/os/index.md @@ -0,0 +1,45 @@ +# OS + +## Scanner +Trivy supports operating systems for + +- [SBOM][sbom] +- [Vulnerabilities][vuln] +- [Licenses][license] + +## Supported OS + +| OS | Supported Versions | Package Managers | +|-----------------------------------------------|-------------------------------------|------------------| +| [Alpine Linux](alpine.md) | 2.2 - 2.7, 3.0 - 3.19, edge | apk | +| [Wolfi Linux](wolfi.md) | (n/a) | apk | +| [Chainguard](chainguard.md) | (n/a) | apk | +| [Red Hat Enterprise Linux](rhel.md) | 6, 7, 8 | dnf/yum/rpm | +| [CentOS](centos.md)[^1] | 6, 7, 8 | dnf/yum/rpm | +| [AlmaLinux](alma.md) | 8, 9 | dnf/yum/rpm | +| [Rocky Linux](rocky.md) | 8, 9 | dnf/yum/rpm | +| [Oracle Linux](oracle.md) | 5, 6, 7, 8 | dnf/yum/rpm | +| [CBL-Mariner](cbl-mariner.md) | 1.0, 2.0 | dnf/yum/rpm | +| [Amazon Linux](amazon.md) | 1, 2, 2023 | dnf/yum/rpm | +| [openSUSE Leap](suse.md) | 42, 15 | zypper/rpm | +| [SUSE Enterprise Linux](suse.md) | 11, 12, 15 | zypper/rpm | +| [Photon OS](photon.md) | 1.0, 2.0, 3.0, 4.0 | tndf/yum/rpm | +| [Debian GNU/Linux](debian.md) | 7, 8, 9, 10, 11, 12 | apt/dpkg | +| [Ubuntu](ubuntu.md) | All versions supported by Canonical | apt/dpkg | + +## Supported container images + +| Container image | Supported Versions | Package Managers | +|-----------------------------------------------|-------------------------------------|------------------| +| [Google Distroless](google-distroless.md)[^2] | Any | apt/dpkg | +| [Bitnami](bitnami.md) | Any | - | + +Each page gives more details. + +[^1]: CentOS Stream is not supported +[^2]: https://github.com/GoogleContainerTools/distroless + + +[sbom]: ../../supply-chain/sbom.md +[vuln]: ../../scanner/vulnerability.md +[license]: ../../scanner/license.md diff --git a/docs/docs/coverage/os/oracle.md b/docs/docs/coverage/os/oracle.md new file mode 100644 index 000000000000..3799918b9a31 --- /dev/null +++ b/docs/docs/coverage/os/oracle.md @@ -0,0 +1,70 @@ +# Oracle Linux +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +| :-----------------------------------: | :-------: | +| Unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages that have been installed through package managers such as `dnf` and `yum`. + +## Vulnerability +Oracle Linux offers its own security advisories, and these are utilized when scanning Oracle Linux for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +### Fixed Version +Trivy takes fixed versions from [Oracle security advisories][alerts]. + +### Severity +Trivy determines vulnerability severity based on the severity metric provided in [Oracle security advisories][alerts]. +For example, the security patch for [CVE-2023-0464][CVE-2023-0464] is provided as [ELSA-2023-2645][ELSA-2023-2645]. +Its severity is rated as "MODERATE". +Thus, even though it's evaluated as "HIGH" in the NVD, Trivy displays it with a severity of "MEDIUM". + +The table below is the mapping of Oracle's threat to Trivy's severity levels. + +| Oracle | Trivy | +| :-------: | :------: | +| Low | Low | +| Moderate | Medium | +| Important | High | +| Critical | Critical | + +### Status +Trivy supports the following [vulnerability statuses] for Oracle Linux. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | | +| Will Not Fix | | +| Fix Deferred | | +| End of Life | | + +## License +Trivy identifies licenses by examining the metadata of RPM packages. + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + +[oval]: https://linux.oracle.com/security/oval/ +[alerts]: https://www.oracle.com/security-alerts/ + +[CVE-2023-0464]: https://linux.oracle.com/cve/CVE-2023-0464.html +[ELSA-2023-2645]: https://linux.oracle.com/errata/ELSA-2023-2645.html +[NVD]: https://nvd.nist.gov/vuln/detail/CVE-2023-0464 + +[vulnerability statuses]: ../../configuration/filtering.md#by-status \ No newline at end of file diff --git a/docs/docs/coverage/os/photon.md b/docs/docs/coverage/os/photon.md new file mode 100644 index 000000000000..532dbc169241 --- /dev/null +++ b/docs/docs/coverage/os/photon.md @@ -0,0 +1,54 @@ +# Photon OS +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +|:------------------------------------:|:---------:| +| Unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages that have been installed through package managers such as `tdnf` and `yum`. + +## Vulnerability +Photon OS offers its own security advisories, and these are utilized when scanning Photon OS for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +### Fixed Version +Trivy takes fixed versions from [Photon CVE metadata][metadata]. + +### Severity +Trivy determines the severity of vulnerabilities based on the CVSSv3 score provided by Photon OS. +See [here](../../scanner/vulnerability.md#severity-selection) for the conversion table from CVSS score to severity. + +### Status +Trivy supports the following [vulnerability statuses] for Photon OS. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | | +| Will Not Fix | | +| Fix Deferred | | +| End of Life | | + +## License +Trivy identifies licenses by examining the metadata of RPM packages. + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies + +[metadata]: https://packages.vmware.com/photon/photon_cve_metadata/ + +[vulnerability statuses]: ../../configuration/filtering.md#by-status \ No newline at end of file diff --git a/docs/docs/coverage/os/rhel.md b/docs/docs/coverage/os/rhel.md new file mode 100644 index 000000000000..8300005a4968 --- /dev/null +++ b/docs/docs/coverage/os/rhel.md @@ -0,0 +1,84 @@ +# Red Hat Enterprise Linux +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +| :----------------------------------: | :-------: | +| Unfixed vulnerabilities | ✓ | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages that have been installed through package managers such as `dnf` and `yum`. + +## Vulnerability +Red Hat offers its own security advisories, and these are utilized when scanning Red Hat Enterprise Linux (RHEL) for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +### Fixed Version +When looking at fixed versions, it's crucial to consider the patches supplied by Red Hat. +For example, for CVE-2023-0464, the fixed version for RHEL 9 is listed as `3.0.7-16.el9_2` in [their advisory][CVE-2023-0464]. +This patch is provided in [RHSA-2023:3722]. +Note that this is different from the upstream fixed version, which is `3.0.9`, `3.1.1`, and so on. +Typically, only the upstream information gets listed on [NVD], so it's important not to get confused. + +### Severity +Trivy calculates the severity of a vulnerability based on the 'Impact' metric provided by Red Hat. +If the impact is not provided or defined yet by Red Hat, the severity from the NVD is taken into account. + +Using CVE-2023-0464 as an example, while it is rated as "HIGH" in NVD, Red Hat has marked its 'Impact' as ["Low"][CVE-2023-0464]. +As a result, Trivy will display it as "Low". + +The table below is the mapping of Red Hat's impact to Trivy's severity levels. + +| Red Hat | Trivy | +| :-------: | :------: | +| Low | Low | +| Moderate | Medium | +| Important | High | +| Critical | Critical | + +### Status +Trivy supports the following [vulnerability statuses] for RHEL. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | ✓ | +| Will Not Fix | ✓ | +| Fix Deferred | ✓ | +| End of Life | ✓ | + +When a vulnerability status is listed as "End of Life", it means a vulnerability with the impact level assigned to this CVE is no longer covered by its current support lifecycle phase. +The product has been identified to contain the impacted component, but analysis to determine whether it is affected or not by this vulnerability was not performed. +Red Hat advises that the product should be assumed to be affected. +Therefore, Trivy detects vulnerabilities with this status as "End of Life". + +On the other hand, for those marked "Under Investigation," the impact is unclear as they are still being examined, so Trivy does not detect them. Once the investigation is completed, the status should be updated. + +!!! abstract + Vulnerabilities with a status of "End of Life", where the presence or absence of impact is unclear, are detected by Trivy. However, those with a status of "Under Investigation" are not detected. + +## License +Trivy identifies licenses by examining the metadata of RPM packages. + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[oval]: https://www.redhat.com/security/data/oval/v2/ +[api]: https://www.redhat.com/security/data/metrics/ + +[CVE-2023-0464]: https://access.redhat.com/security/cve/cve-2023-0464 +[RHSA-2023:3722]: https://access.redhat.com/errata/RHSA-2023:3722 +[NVD]: https://nvd.nist.gov/vuln/detail/CVE-2023-0464 + +[vulnerability statuses]: ../../configuration/filtering.md#by-status diff --git a/docs/docs/coverage/os/rocky.md b/docs/docs/coverage/os/rocky.md new file mode 100644 index 000000000000..7e6dba7bb14e --- /dev/null +++ b/docs/docs/coverage/os/rocky.md @@ -0,0 +1,70 @@ +# Rocky Linux +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +|:------------------------------------:|:---------:| +| Unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages that have been installed through package managers such as `dnf` and `yum`. + +## Vulnerability +Rocky Linux offers its own security advisories, and these are utilized when scanning Rocky Linux for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +### Fixed Version +Trivy takes fixed versions from [Rocky Linux Errata][errata], not NVD or somewhere else. +See [here](../../scanner/vulnerability.md#data-source-selection) for more details. + +!!! architectures + There are cases when the vulnerability affects packages of not all architectures. + For example, vulnerable packages for [CVE-2023-0361](https://errata.rockylinux.org/RLSA-2023:1141) are only `aarch64` packages. + + Trivy only detects vulnerabilities for packages of your architecture. + +### Severity +Trivy calculates the severity of an issue based on the severity provided in [Rocky Linux Errata][errata]. + +The table below is the mapping of Rocky Linux's severity to Trivy's severity levels. + +| Rocky Linux | Trivy | +| :---------: | :------: | +| Low | Low | +| Moderate | Medium | +| Important | High | +| Critical | Critical | + +### Status +Trivy supports the following [vulnerability statuses] for Rocky Linux. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | | +| Will Not Fix | | +| Fix Deferred | | +| End of Life | | + + +## License +Trivy identifies licenses by examining the metadata of RPM packages. + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[updateinfo]: https://download.rockylinux.org/pub/rocky/ +[errata]: https://errata.rockylinux.org/ + +[vulnerability statuses]: ../../configuration/filtering.md#by-status \ No newline at end of file diff --git a/docs/docs/coverage/os/suse.md b/docs/docs/coverage/os/suse.md new file mode 100644 index 000000000000..6ff52de31c86 --- /dev/null +++ b/docs/docs/coverage/os/suse.md @@ -0,0 +1,40 @@ +# SUSE +Trivy supports the following distributions: + +- openSUSE Leap +- SUSE Enterprise Linux (SLE) + +Please see [here](index.md#supported-os) for supported versions. + +Trivy supports these scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +|:------------------------------------:|:---------:| +| Unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Trivy detects packages that have been installed through package managers such as `dnf` and `yum`. + +## Vulnerability +SUSE offers its [own security advisories][cvrf], and these are utilized when scanning openSUSE/SLE for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +## License +Trivy identifies licenses by examining the metadata of RPM packages. + + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[cvrf]: http://ftp.suse.com/pub/projects/security/cvrf/ + +[vulnerability statuses]: ../../configuration/filtering.md#by-status \ No newline at end of file diff --git a/docs/docs/coverage/os/ubuntu.md b/docs/docs/coverage/os/ubuntu.md new file mode 100644 index 000000000000..c922c988c63d --- /dev/null +++ b/docs/docs/coverage/os/ubuntu.md @@ -0,0 +1,65 @@ +# Ubuntu +Trivy supports these scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +Please see [here](index.md#supported-os) for supported versions. + +The following table provides an outline of the features Trivy offers. + +| Feature | Supported | +|:------------------------------------:|:---------:| +| Detect unfixed vulnerabilities | ✓ | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Same as [Debian](debian.md#sbom). + +## Vulnerability +Ubuntu offers its own security advisories, and these are utilized when scanning Ubuntu for vulnerabilities. + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +### Fixed Version +When looking at fixed versions, it's crucial to consider the patches supplied by Ubuntu. +As an illustration, for CVE-2023-3269, the fixed version for Ubuntu 23.04 (lunar) is listed as `6.2.0-26.26` in [the Security Tracker][CVE-2023-3269]. +It's essential to recognize that this differs from the upstream fixed version, which stands at `6.5`. +Typically, only the upstream information gets listed on [NVD][CVE-2023-3269 NVD], so it's important not to get confused. + +### Severity +Trivy calculates the severity of an issue based on the 'Priority' metric found in the Security Tracker. +If 'Priority' isn't provided by Ubuntu, the severity from the NVD is taken into account. + +Using CVE-2019-15052 as an example, while it is rated as ["Critical" in NVD][CVE-2019-15052 NVD], Ubuntu has marked its "Priority" as ["Medium"][CVE-2019-15052]. +As a result, Trivy will display it as "Medium". + +### Status +Trivy supports the following [vulnerability statuses] for Ubuntu. + +| Status | Supported | +| :-----------------: | :-------: | +| Fixed | ✓ | +| Affected | ✓ | +| Under Investigation | | +| Will Not Fix | | +| Fix Deferred | | +| End of Life | | + +## License +Same as [Debian](debian.md#license). + + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[Ubuntu CVE Tracker]: https://ubuntu.com/security/cve + +[CVE-2023-3269]: https://ubuntu.com/security/CVE-2023-3269 +[CVE-2019-15052]: https://ubuntu.com/security/CVE-2019-15052 +[CVE-2023-3269 NVD]: https://nvd.nist.gov/vuln/detail/CVE-2023-3269 +[CVE-2019-15052 NVD]: https://nvd.nist.gov/vuln/detail/CVE-2019-15052 + +[vulnerability statuses]: ../../configuration/filtering.md#by-status \ No newline at end of file diff --git a/docs/docs/coverage/os/wolfi.md b/docs/docs/coverage/os/wolfi.md new file mode 100644 index 000000000000..9099e89add8e --- /dev/null +++ b/docs/docs/coverage/os/wolfi.md @@ -0,0 +1,31 @@ +# Wolfi Linux +Trivy supports these scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | ✓ | +| License | ✓ | + +The table below outlines the features offered by Trivy. + +| Feature | Supported | +|:------------------------------------:|:---------:| +| Detect unfixed vulnerabilities | - | +| [Dependency graph][dependency-graph] | ✓ | + +## SBOM +Same as [Alpine Linux](alpine.md#sbom). + +## Vulnerability +Wolfi Linux offers its own security advisories, and these are utilized when scanning Wolfi for vulnerabilities. +Everything else is the same as [Alpine Linux](alpine.md#vulnerability). + +### Data Source +See [here](../../scanner/vulnerability.md#data-sources). + +## License +Same as [Alpine Linux](alpine.md#license). + +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[secdb]: https://packages.wolfi.dev/os/security.json \ No newline at end of file diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 000000000000..a475356b734e --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,5 @@ +# Docs + +In this section you can find the complete reference documentation for all the different features and settings that Trivy has to offer. + +👈 Please use the side-navigation on the left in order to browse the different topics. diff --git a/docs/docs/references/configuration/cli/trivy.md b/docs/docs/references/configuration/cli/trivy.md new file mode 100644 index 000000000000..f3c543a210f9 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy.md @@ -0,0 +1,60 @@ +## trivy + +Unified security scanner + +### Synopsis + +Scanner for vulnerabilities in container images, file systems, and Git repositories, as well as for configuration issues and hard-coded secrets + +``` +trivy [global flags] command [flags] target +``` + +### Examples + +``` + # Scan a container image + $ trivy image python:3.4-alpine + + # Scan a container image from a tar archive + $ trivy image --input ruby-3.1.tar + + # Scan local filesystem + $ trivy fs . + + # Run in server mode + $ trivy server +``` + +### Options + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + -f, --format string version format (json) + --generate-default-config write the default config to trivy-default.yaml + -h, --help help for trivy + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy aws](trivy_aws.md) - [EXPERIMENTAL] Scan AWS account +* [trivy config](trivy_config.md) - Scan config files for misconfigurations +* [trivy convert](trivy_convert.md) - Convert Trivy JSON report into a different format +* [trivy filesystem](trivy_filesystem.md) - Scan local filesystem +* [trivy image](trivy_image.md) - Scan a container image +* [trivy kubernetes](trivy_kubernetes.md) - [EXPERIMENTAL] Scan kubernetes cluster +* [trivy module](trivy_module.md) - Manage modules +* [trivy plugin](trivy_plugin.md) - Manage plugins +* [trivy repository](trivy_repository.md) - Scan a repository +* [trivy rootfs](trivy_rootfs.md) - Scan rootfs +* [trivy sbom](trivy_sbom.md) - Scan SBOM for vulnerabilities and licenses +* [trivy server](trivy_server.md) - Server mode +* [trivy version](trivy_version.md) - Print the version +* [trivy vm](trivy_vm.md) - [EXPERIMENTAL] Scan a virtual machine image + diff --git a/docs/docs/references/configuration/cli/trivy_aws.md b/docs/docs/references/configuration/cli/trivy_aws.md new file mode 100644 index 000000000000..b87bfce2bc30 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_aws.md @@ -0,0 +1,126 @@ +## trivy aws + +[EXPERIMENTAL] Scan AWS account + +### Synopsis + +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: + +- accessanalyzer +- api-gateway +- athena +- cloudfront +- cloudtrail +- cloudwatch +- codebuild +- documentdb +- dynamodb +- ec2 +- ecr +- ecs +- efs +- eks +- elasticache +- elasticsearch +- elb +- emr +- iam +- kinesis +- kms +- lambda +- mq +- msk +- neptune +- rds +- redshift +- s3 +- sns +- sqs +- ssm +- workspaces + + +``` +trivy aws [flags] +``` + +### Examples + +``` + # basic scanning + $ trivy aws --region us-east-1 + + # limit scan to a single service: + $ trivy aws --region us-east-1 --service s3 + + # limit scan to multiple services: + $ trivy aws --region us-east-1 --service s3 --service ec2 + + # force refresh of cache for fresh results + $ trivy aws --region us-east-1 --update-cache + +``` + +### Options + +``` + --account string The AWS account to scan. It's useful to specify this when reviewing cached results for multiple accounts. + --arn string The AWS ARN to show results for. Useful to filter results once a scan is cached. + --cf-params strings specify paths to override the CloudFormation parameters files + --compliance string compliance report to generate (aws-cis-1.2,aws-cis-1.4) + --config-data strings specify paths from which data for the Rego policies will be recursively loaded + --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --endpoint string AWS Endpoint override + --exit-code int specify exit code when any security issues are found + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + --helm-api-versions strings Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment) + --helm-kube-version string Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. + --helm-set strings specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-set-file strings specify Helm values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) + --helm-set-string strings specify Helm string values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-values strings specify paths to override the Helm values.yaml files + -h, --help help for aws + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-non-failures include successes and exceptions, available with '--scanners misconfig' + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --max-cache-age duration The maximum age of the cloud cache. Cached data will be requeried from the cloud provider if it is older than this. (default 24h0m0s) + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") + --policy-namespaces strings Rego namespaces + --region string AWS Region to scan + --report string specify a report format for the output (all,summary) (default "all") + --reset-policy-bundle remove policy bundle + --service strings Only scan AWS Service(s) specified with this flag. Can specify multiple services using --service A --service B etc. + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --skip-policy-update skip fetching rego policy updates + --skip-service strings Skip selected AWS Service(s) specified with this flag. Can specify multiple services using --skip-service A --skip-service B etc. + -t, --template string output template + --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules + --tf-vars strings specify paths to override the Terraform tfvars files + --trace enable more verbose trace output for custom queries + --update-cache Update the cache for the applicable cloud provider instead of using cached results. +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md new file mode 100644 index 000000000000..070257b4a896 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -0,0 +1,75 @@ +## trivy config + +Scan config files for misconfigurations + +``` +trivy config [flags] DIR +``` + +### Options + +``` + --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-ttl duration cache TTL when using redis as cache backend + --cf-params strings specify paths to override the CloudFormation parameters files + --clear-cache clear image caches without scanning + --compliance string compliance report to generate + --config-data strings specify paths from which data for the Rego policies will be recursively loaded + --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --enable-modules strings [EXPERIMENTAL] module names to enable + --exit-code int specify exit code when any security issues are found + --file-patterns strings specify config file patterns + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + --helm-api-versions strings Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment) + --helm-kube-version string Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. + --helm-set strings specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-set-file strings specify Helm values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) + --helm-set-string strings specify Helm string values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-values strings specify paths to override the Helm values.yaml files + -h, --help help for config + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-non-failures include successes and exceptions, available with '--scanners misconfig' + --k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0) + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) + --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") + --policy-namespaces strings Rego namespaces + --redis-ca string redis ca file location, if using redis as cache backend + --redis-cert string redis certificate file location, if using redis as cache backend + --redis-key string redis key file location, if using redis as cache backend + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --registry-token string registry token + --report string specify a compliance report format for the output (all,summary) (default "all") + --reset-policy-bundle remove policy bundle + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --skip-dirs strings specify the directories or glob patterns to skip + --skip-files strings specify the files or glob patterns to skip + --skip-policy-update skip fetching rego policy updates + -t, --template string output template + --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules + --tf-vars strings specify paths to override the Terraform tfvars files + --trace enable more verbose trace output for custom queries + --username strings username. Comma-separated usernames allowed. +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_convert.md b/docs/docs/references/configuration/cli/trivy_convert.md new file mode 100644 index 000000000000..e8fe589500ea --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_convert.md @@ -0,0 +1,54 @@ +## trivy convert + +Convert Trivy JSON report into a different format + +``` +trivy convert [flags] RESULT_JSON +``` + +### Examples + +``` + # report conversion + $ trivy image --format json --output result.json --list-all-pkgs debian:11 + $ trivy convert --format cyclonedx --output result.cdx result.json + +``` + +### Options + +``` + --compliance string compliance report to generate + --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --exit-code int specify exit code when any security issues are found + --exit-on-eol int exit with the specified code when the OS reaches end of service/life + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + -h, --help help for convert + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignorefile string specify .trivyignore file (default ".trivyignore") + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --report string specify a report format for the output (all,summary) (default "all") + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + -t, --template string output template +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md new file mode 100644 index 000000000000..7aeb72ca5970 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -0,0 +1,113 @@ +## trivy filesystem + +Scan local filesystem + +``` +trivy filesystem [flags] PATH +``` + +### Examples + +``` + # Scan a local project including language-specific files + $ trivy fs /path/to/your_project + + # Scan a single file + $ trivy fs ./trivy-ci-test/Pipfile.lock +``` + +### Options + +``` + --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-ttl duration cache TTL when using redis as cache backend + --cf-params strings specify paths to override the CloudFormation parameters files + --clear-cache clear image caches without scanning + --compliance string compliance report to generate + --config-data strings specify paths from which data for the Rego policies will be recursively loaded + --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --custom-headers strings custom headers in client mode + --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --download-db-only download/update vulnerability database but don't run a scan + --download-java-db-only download/update Java index database but don't run a scan + --enable-modules strings [EXPERIMENTAL] module names to enable + --exit-code int specify exit code when any security issues are found + --file-patterns strings specify config file patterns + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + --helm-api-versions strings Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment) + --helm-kube-version string Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. + --helm-set strings specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-set-file strings specify Helm values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) + --helm-set-string strings specify Helm string values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-values strings specify paths to override the Helm values.yaml files + -h, --help help for filesystem + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) + --ignore-unfixed display only fixed vulnerabilities + --ignored-licenses strings specify a list of license to ignore + --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-dev-deps include development dependencies in the report (supported: npm, yarn) + --include-non-failures include successes and exceptions, available with '--scanners misconfig' + --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --license-confidence-level float specify license classifier's confidence level (default 0.9) + --license-full eagerly look for licenses in source code headers and license files + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) + --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") + --no-progress suppress progress bar + --offline-scan do not issue API requests to identify dependencies + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) + --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") + --policy-namespaces strings Rego namespaces + --redis-ca string redis ca file location, if using redis as cache backend + --redis-cert string redis certificate file location, if using redis as cache backend + --redis-key string redis key file location, if using redis as cache backend + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --registry-token string registry token + --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --report string specify a compliance report format for the output (all,summary) (default "all") + --reset remove all caches and database + --reset-policy-bundle remove policy bundle + --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) + --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) + --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") + --server string server address in client mode + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-db-update skip updating vulnerability database + --skip-dirs strings specify the directories or glob patterns to skip + --skip-files strings specify the files or glob patterns to skip + --skip-java-db-update skip updating Java index database + --skip-policy-update skip fetching rego policy updates + -t, --template string output template + --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules + --tf-vars strings specify paths to override the Terraform tfvars files + --token string for authentication in client/server mode + --token-header string specify a header name for token in client/server mode (default "Trivy-Token") + --trace enable more verbose trace output for custom queries + --username strings username. Comma-separated usernames allowed. + --vex string [EXPERIMENTAL] file path to VEX + --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md new file mode 100644 index 000000000000..e5d91f31eb7c --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -0,0 +1,133 @@ +## trivy image + +Scan a container image + +``` +trivy image [flags] IMAGE_NAME +``` + +### Examples + +``` + # Scan a container image + $ trivy image python:3.4-alpine + + # Scan a container image from a tar archive + $ trivy image --input ruby-3.1.tar + + # Filter by severities + $ trivy image --severity HIGH,CRITICAL alpine:3.15 + + # Ignore unfixed/unpatched vulnerabilities + $ trivy image --ignore-unfixed alpine:3.15 + + # Scan a container image in client mode + $ trivy image --server http://127.0.0.1:4954 alpine:latest + + # Generate json result + $ trivy image --format json --output result.json alpine:3.15 + + # Generate a report in the CycloneDX format + $ trivy image --format cyclonedx --output result.cdx alpine:3.15 +``` + +### Options + +``` + --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-ttl duration cache TTL when using redis as cache backend + --clear-cache clear image caches without scanning + --compliance string compliance report to generate (docker-cis) + --config-data strings specify paths from which data for the Rego policies will be recursively loaded + --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --custom-headers strings custom headers in client mode + --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --docker-host string unix domain socket path to use for docker scanning + --download-db-only download/update vulnerability database but don't run a scan + --download-java-db-only download/update Java index database but don't run a scan + --enable-modules strings [EXPERIMENTAL] module names to enable + --exit-code int specify exit code when any security issues are found + --exit-on-eol int exit with the specified code when the OS reaches end of service/life + --file-patterns strings specify config file patterns + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + --helm-api-versions strings Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment) + --helm-kube-version string Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. + --helm-set strings specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-set-file strings specify Helm values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) + --helm-set-string strings specify Helm string values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-values strings specify paths to override the Helm values.yaml files + -h, --help help for image + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) + --ignore-unfixed display only fixed vulnerabilities + --ignored-licenses strings specify a list of license to ignore + --ignorefile string specify .trivyignore file (default ".trivyignore") + --image-config-scanners strings comma-separated list of what security issues to detect on container image configurations (misconfig,secret) + --image-src strings image source(s) to use, in priority order (docker,containerd,podman,remote) (default [docker,containerd,podman,remote]) + --include-non-failures include successes and exceptions, available with '--scanners misconfig' + --input string input file path instead of image name + --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --license-confidence-level float specify license classifier's confidence level (default 0.9) + --license-full eagerly look for licenses in source code headers and license files + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) + --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") + --no-progress suppress progress bar + --offline-scan do not issue API requests to identify dependencies + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) + --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --platform string set platform in the form os/arch if image is multi-platform capable + --podman-host string unix podman socket path to use for podman scanning + --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") + --policy-namespaces strings Rego namespaces + --redis-ca string redis ca file location, if using redis as cache backend + --redis-cert string redis certificate file location, if using redis as cache backend + --redis-key string redis key file location, if using redis as cache backend + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --registry-token string registry token + --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --removed-pkgs detect vulnerabilities of removed packages (only for Alpine) + --report string specify a format for the compliance report. (all,summary) (default "summary") + --reset remove all caches and database + --reset-policy-bundle remove policy bundle + --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) + --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) + --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") + --server string server address in client mode + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-db-update skip updating vulnerability database + --skip-dirs strings specify the directories or glob patterns to skip + --skip-files strings specify the files or glob patterns to skip + --skip-java-db-update skip updating Java index database + --skip-policy-update skip fetching rego policy updates + -t, --template string output template + --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules + --token string for authentication in client/server mode + --token-header string specify a header name for token in client/server mode (default "Trivy-Token") + --trace enable more verbose trace output for custom queries + --username strings username. Comma-separated usernames allowed. + --vex string [EXPERIMENTAL] file path to VEX + --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md new file mode 100644 index 000000000000..a1befafeb504 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -0,0 +1,123 @@ +## trivy kubernetes + +[EXPERIMENTAL] Scan kubernetes cluster + +``` +trivy kubernetes [flags] { cluster | all | specific resources like kubectl. eg: pods, pod/NAME } +``` + +### Examples + +``` + # cluster scanning + $ trivy k8s --report summary cluster + + # namespace scanning: + $ trivy k8s -n kube-system --report summary all + + # resources scanning: + $ trivy k8s --report=summary deploy + $ trivy k8s --namespace=kube-system --report=summary deploy,configmaps + + # resource scanning: + $ trivy k8s deployment/orion + +``` + +### Options + +``` + -A, --all-namespaces fetch resources from all cluster namespaces + --burst int specify the maximum burst for throttle (default 10) + --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-ttl duration cache TTL when using redis as cache backend + --clear-cache clear image caches without scanning + --compliance string compliance report to generate (k8s-nsa,k8s-cis,k8s-pss-baseline,k8s-pss-restricted) + --components strings specify which components to scan (workload,infra) (default [workload,infra]) + --config-data strings specify paths from which data for the Rego policies will be recursively loaded + --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --context string specify a context to scan + --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --download-db-only download/update vulnerability database but don't run a scan + --download-java-db-only download/update Java index database but don't run a scan + --exclude-nodes strings indicate the node labels that the node-collector job should exclude from scanning (example: kubernetes.io/arch:arm64,team:dev) + --exclude-owned exclude resources that have an owner reference + --exit-code int specify exit code when any security issues are found + --file-patterns strings specify config file patterns + -f, --format string format (table,json,cyclonedx) (default "table") + --helm-api-versions strings Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment) + --helm-kube-version string Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. + --helm-set strings specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-set-file strings specify Helm values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) + --helm-set-string strings specify Helm string values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-values strings specify paths to override the Helm values.yaml files + -h, --help help for kubernetes + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) + --ignore-unfixed display only fixed vulnerabilities + --ignorefile string specify .trivyignore file (default ".trivyignore") + --image-src strings image source(s) to use, in priority order (docker,containerd,podman,remote) (default [docker,containerd,podman,remote]) + --include-non-failures include successes and exceptions, available with '--scanners misconfig' + --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0) + --kubeconfig string specify the kubeconfig file path to use + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) + -n, --namespace string specify a namespace to scan + --no-progress suppress progress bar + --node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.0.9") + --node-collector-namespace string specify the namespace in which the node-collector job should be deployed (default "trivy-temp") + --offline-scan do not issue API requests to identify dependencies + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) + --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") + --policy-namespaces strings Rego namespaces + --qps float specify the maximum QPS to the master from this client (default 5) + --redis-ca string redis ca file location, if using redis as cache backend + --redis-cert string redis certificate file location, if using redis as cache backend + --redis-key string redis key file location, if using redis as cache backend + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --registry-token string registry token + --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --report string specify a report format for the output (all,summary) (default "all") + --reset remove all caches and database + --reset-policy-bundle remove policy bundle + --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) + --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,rbac) (default [vuln,misconfig,secret,rbac]) + --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-db-update skip updating vulnerability database + --skip-dirs strings specify the directories or glob patterns to skip + --skip-files strings specify the files or glob patterns to skip + --skip-java-db-update skip updating Java index database + --skip-policy-update skip fetching rego policy updates + -t, --template string output template + --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules + --tolerations strings specify node-collector job tolerations (example: key1=value1:NoExecute,key2=value2:NoSchedule) + --trace enable more verbose trace output for custom queries + --username strings username. Comma-separated usernames allowed. + --vex string [EXPERIMENTAL] file path to VEX + --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_module.md b/docs/docs/references/configuration/cli/trivy_module.md new file mode 100644 index 000000000000..136a090009b2 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_module.md @@ -0,0 +1,31 @@ +## trivy module + +Manage modules + +### Options + +``` + --enable-modules strings [EXPERIMENTAL] module names to enable + -h, --help help for module + --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner +* [trivy module install](trivy_module_install.md) - Install a module +* [trivy module uninstall](trivy_module_uninstall.md) - Uninstall a module + diff --git a/docs/docs/references/configuration/cli/trivy_module_install.md b/docs/docs/references/configuration/cli/trivy_module_install.md new file mode 100644 index 000000000000..400f37509930 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_module_install.md @@ -0,0 +1,33 @@ +## trivy module install + +Install a module + +``` +trivy module install [flags] REPOSITORY +``` + +### Options + +``` + -h, --help help for install +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --enable-modules strings [EXPERIMENTAL] module names to enable + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy module](trivy_module.md) - Manage modules + diff --git a/docs/docs/references/configuration/cli/trivy_module_uninstall.md b/docs/docs/references/configuration/cli/trivy_module_uninstall.md new file mode 100644 index 000000000000..7d78e180103b --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_module_uninstall.md @@ -0,0 +1,33 @@ +## trivy module uninstall + +Uninstall a module + +``` +trivy module uninstall [flags] REPOSITORY +``` + +### Options + +``` + -h, --help help for uninstall +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --enable-modules strings [EXPERIMENTAL] module names to enable + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy module](trivy_module.md) - Manage modules + diff --git a/docs/docs/references/configuration/cli/trivy_plugin.md b/docs/docs/references/configuration/cli/trivy_plugin.md new file mode 100644 index 000000000000..9f47212d17f5 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_plugin.md @@ -0,0 +1,33 @@ +## trivy plugin + +Manage plugins + +### Options + +``` + -h, --help help for plugin +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner +* [trivy plugin info](trivy_plugin_info.md) - Show information about the specified plugin +* [trivy plugin install](trivy_plugin_install.md) - Install a plugin +* [trivy plugin list](trivy_plugin_list.md) - List installed plugin +* [trivy plugin run](trivy_plugin_run.md) - Run a plugin on the fly +* [trivy plugin uninstall](trivy_plugin_uninstall.md) - Uninstall a plugin +* [trivy plugin update](trivy_plugin_update.md) - Update an existing plugin + diff --git a/docs/docs/references/configuration/cli/trivy_plugin_info.md b/docs/docs/references/configuration/cli/trivy_plugin_info.md new file mode 100644 index 000000000000..e6982ffc0367 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_plugin_info.md @@ -0,0 +1,31 @@ +## trivy plugin info + +Show information about the specified plugin + +``` +trivy plugin info PLUGIN_NAME +``` + +### Options + +``` + -h, --help help for info +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy plugin](trivy_plugin.md) - Manage plugins + diff --git a/docs/docs/references/configuration/cli/trivy_plugin_install.md b/docs/docs/references/configuration/cli/trivy_plugin_install.md new file mode 100644 index 000000000000..f92da9598326 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_plugin_install.md @@ -0,0 +1,31 @@ +## trivy plugin install + +Install a plugin + +``` +trivy plugin install URL | FILE_PATH +``` + +### Options + +``` + -h, --help help for install +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy plugin](trivy_plugin.md) - Manage plugins + diff --git a/docs/docs/references/configuration/cli/trivy_plugin_list.md b/docs/docs/references/configuration/cli/trivy_plugin_list.md new file mode 100644 index 000000000000..ea789ea7195c --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_plugin_list.md @@ -0,0 +1,31 @@ +## trivy plugin list + +List installed plugin + +``` +trivy plugin list +``` + +### Options + +``` + -h, --help help for list +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy plugin](trivy_plugin.md) - Manage plugins + diff --git a/docs/docs/references/configuration/cli/trivy_plugin_run.md b/docs/docs/references/configuration/cli/trivy_plugin_run.md new file mode 100644 index 000000000000..0dc7087a19d0 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_plugin_run.md @@ -0,0 +1,31 @@ +## trivy plugin run + +Run a plugin on the fly + +``` +trivy plugin run URL | FILE_PATH +``` + +### Options + +``` + -h, --help help for run +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy plugin](trivy_plugin.md) - Manage plugins + diff --git a/docs/docs/references/configuration/cli/trivy_plugin_uninstall.md b/docs/docs/references/configuration/cli/trivy_plugin_uninstall.md new file mode 100644 index 000000000000..69fbb1d5a1d9 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_plugin_uninstall.md @@ -0,0 +1,31 @@ +## trivy plugin uninstall + +Uninstall a plugin + +``` +trivy plugin uninstall PLUGIN_NAME +``` + +### Options + +``` + -h, --help help for uninstall +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy plugin](trivy_plugin.md) - Manage plugins + diff --git a/docs/docs/references/configuration/cli/trivy_plugin_update.md b/docs/docs/references/configuration/cli/trivy_plugin_update.md new file mode 100644 index 000000000000..532add27cfa3 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_plugin_update.md @@ -0,0 +1,31 @@ +## trivy plugin update + +Update an existing plugin + +``` +trivy plugin update PLUGIN_NAME +``` + +### Options + +``` + -h, --help help for update +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy plugin](trivy_plugin.md) - Manage plugins + diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md new file mode 100644 index 000000000000..fa4d13bbdeee --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -0,0 +1,113 @@ +## trivy repository + +Scan a repository + +``` +trivy repository [flags] (REPO_PATH | REPO_URL) +``` + +### Examples + +``` + # Scan your remote git repository + $ trivy repo https://github.com/knqyf263/trivy-ci-test + # Scan your local git repository + $ trivy repo /path/to/your/repository +``` + +### Options + +``` + --branch string pass the branch name to be scanned + --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-ttl duration cache TTL when using redis as cache backend + --cf-params strings specify paths to override the CloudFormation parameters files + --clear-cache clear image caches without scanning + --commit string pass the commit hash to be scanned + --config-data strings specify paths from which data for the Rego policies will be recursively loaded + --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --custom-headers strings custom headers in client mode + --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --download-db-only download/update vulnerability database but don't run a scan + --download-java-db-only download/update Java index database but don't run a scan + --enable-modules strings [EXPERIMENTAL] module names to enable + --exit-code int specify exit code when any security issues are found + --file-patterns strings specify config file patterns + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + --helm-api-versions strings Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment) + --helm-kube-version string Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. + --helm-set strings specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-set-file strings specify Helm values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) + --helm-set-string strings specify Helm string values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-values strings specify paths to override the Helm values.yaml files + -h, --help help for repository + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) + --ignore-unfixed display only fixed vulnerabilities + --ignored-licenses strings specify a list of license to ignore + --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-dev-deps include development dependencies in the report (supported: npm, yarn) + --include-non-failures include successes and exceptions, available with '--scanners misconfig' + --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --license-confidence-level float specify license classifier's confidence level (default 0.9) + --license-full eagerly look for licenses in source code headers and license files + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) + --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") + --no-progress suppress progress bar + --offline-scan do not issue API requests to identify dependencies + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) + --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") + --policy-namespaces strings Rego namespaces + --redis-ca string redis ca file location, if using redis as cache backend + --redis-cert string redis certificate file location, if using redis as cache backend + --redis-key string redis key file location, if using redis as cache backend + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --registry-token string registry token + --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --reset remove all caches and database + --reset-policy-bundle remove policy bundle + --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) + --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) + --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") + --server string server address in client mode + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-db-update skip updating vulnerability database + --skip-dirs strings specify the directories or glob patterns to skip + --skip-files strings specify the files or glob patterns to skip + --skip-java-db-update skip updating Java index database + --skip-policy-update skip fetching rego policy updates + --tag string pass the tag name to be scanned + -t, --template string output template + --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules + --tf-vars strings specify paths to override the Terraform tfvars files + --token string for authentication in client/server mode + --token-header string specify a header name for token in client/server mode (default "Trivy-Token") + --trace enable more verbose trace output for custom queries + --username strings username. Comma-separated usernames allowed. + --vex string [EXPERIMENTAL] file path to VEX + --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md new file mode 100644 index 000000000000..088b5deb6d88 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -0,0 +1,114 @@ +## trivy rootfs + +Scan rootfs + +``` +trivy rootfs [flags] ROOTDIR +``` + +### Examples + +``` + # Scan unpacked filesystem + $ docker export $(docker create alpine:3.10.2) | tar -C /tmp/rootfs -xvf - + $ trivy rootfs /tmp/rootfs + + # Scan from inside a container + $ docker run --rm -it alpine:3.11 + / # curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin + / # trivy rootfs / +``` + +### Options + +``` + --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-ttl duration cache TTL when using redis as cache backend + --cf-params strings specify paths to override the CloudFormation parameters files + --clear-cache clear image caches without scanning + --config-data strings specify paths from which data for the Rego policies will be recursively loaded + --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --custom-headers strings custom headers in client mode + --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --download-db-only download/update vulnerability database but don't run a scan + --download-java-db-only download/update Java index database but don't run a scan + --enable-modules strings [EXPERIMENTAL] module names to enable + --exit-code int specify exit code when any security issues are found + --exit-on-eol int exit with the specified code when the OS reaches end of service/life + --file-patterns strings specify config file patterns + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + --helm-api-versions strings Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment) + --helm-kube-version string Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. + --helm-set strings specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-set-file strings specify Helm values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) + --helm-set-string strings specify Helm string values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-values strings specify paths to override the Helm values.yaml files + -h, --help help for rootfs + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) + --ignore-unfixed display only fixed vulnerabilities + --ignored-licenses strings specify a list of license to ignore + --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-non-failures include successes and exceptions, available with '--scanners misconfig' + --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --license-confidence-level float specify license classifier's confidence level (default 0.9) + --license-full eagerly look for licenses in source code headers and license files + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) + --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") + --no-progress suppress progress bar + --offline-scan do not issue API requests to identify dependencies + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) + --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") + --policy-namespaces strings Rego namespaces + --redis-ca string redis ca file location, if using redis as cache backend + --redis-cert string redis certificate file location, if using redis as cache backend + --redis-key string redis key file location, if using redis as cache backend + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --registry-token string registry token + --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --reset remove all caches and database + --reset-policy-bundle remove policy bundle + --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) + --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) + --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") + --server string server address in client mode + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-db-update skip updating vulnerability database + --skip-dirs strings specify the directories or glob patterns to skip + --skip-files strings specify the files or glob patterns to skip + --skip-java-db-update skip updating Java index database + --skip-policy-update skip fetching rego policy updates + -t, --template string output template + --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules + --tf-vars strings specify paths to override the Terraform tfvars files + --token string for authentication in client/server mode + --token-header string specify a header name for token in client/server mode (default "Trivy-Token") + --trace enable more verbose trace output for custom queries + --username strings username. Comma-separated usernames allowed. + --vex string [EXPERIMENTAL] file path to VEX + --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md new file mode 100644 index 000000000000..5d941e9744ba --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -0,0 +1,85 @@ +## trivy sbom + +Scan SBOM for vulnerabilities and licenses + +``` +trivy sbom [flags] SBOM_PATH +``` + +### Examples + +``` + # Scan CycloneDX and show the result in tables + $ trivy sbom /path/to/report.cdx + + # Scan CycloneDX-type attestation and show the result in tables + $ trivy sbom /path/to/report.cdx.intoto.jsonl + +``` + +### Options + +``` + --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-ttl duration cache TTL when using redis as cache backend + --clear-cache clear image caches without scanning + --compliance string compliance report to generate + --custom-headers strings custom headers in client mode + --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --download-db-only download/update vulnerability database but don't run a scan + --download-java-db-only download/update Java index database but don't run a scan + --exit-code int specify exit code when any security issues are found + --exit-on-eol int exit with the specified code when the OS reaches end of service/life + --file-patterns strings specify config file patterns + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + -h, --help help for sbom + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) + --ignore-unfixed display only fixed vulnerabilities + --ignored-licenses strings specify a list of license to ignore + --ignorefile string specify .trivyignore file (default ".trivyignore") + --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --no-progress suppress progress bar + --offline-scan do not issue API requests to identify dependencies + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --redis-ca string redis ca file location, if using redis as cache backend + --redis-cert string redis certificate file location, if using redis as cache backend + --redis-key string redis key file location, if using redis as cache backend + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --reset remove all caches and database + --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) + --scanners strings comma-separated list of what security issues to detect (vuln,license) (default [vuln]) + --server string server address in client mode + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-db-update skip updating vulnerability database + --skip-dirs strings specify the directories or glob patterns to skip + --skip-files strings specify the files or glob patterns to skip + --skip-java-db-update skip updating Java index database + -t, --template string output template + --token string for authentication in client/server mode + --token-header string specify a header name for token in client/server mode (default "Trivy-Token") + --vex string [EXPERIMENTAL] file path to VEX + --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_server.md b/docs/docs/references/configuration/cli/trivy_server.md new file mode 100644 index 000000000000..d888034c34bf --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_server.md @@ -0,0 +1,62 @@ +## trivy server + +Server mode + +``` +trivy server [flags] +``` + +### Examples + +``` + # Run a server + $ trivy server + + # Listen on 0.0.0.0:10000 + $ trivy server --listen 0.0.0.0:10000 + +``` + +### Options + +``` + --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-ttl duration cache TTL when using redis as cache backend + --clear-cache clear image caches without scanning + --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --download-db-only download/update vulnerability database but don't run a scan + --enable-modules strings [EXPERIMENTAL] module names to enable + -h, --help help for server + --listen string listen address in server mode (default "localhost:4954") + --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") + --no-progress suppress progress bar + --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --redis-ca string redis ca file location, if using redis as cache backend + --redis-cert string redis certificate file location, if using redis as cache backend + --redis-key string redis key file location, if using redis as cache backend + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --registry-token string registry token + --reset remove all caches and database + --skip-db-update skip updating vulnerability database + --token string for authentication in client/server mode + --token-header string specify a header name for token in client/server mode (default "Trivy-Token") + --username strings username. Comma-separated usernames allowed. +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_version.md b/docs/docs/references/configuration/cli/trivy_version.md new file mode 100644 index 000000000000..70529c7445ce --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_version.md @@ -0,0 +1,32 @@ +## trivy version + +Print the version + +``` +trivy version [flags] +``` + +### Options + +``` + -f, --format string version format (json) + -h, --help help for version +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md new file mode 100644 index 000000000000..7b186a505794 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -0,0 +1,101 @@ +## trivy vm + +[EXPERIMENTAL] Scan a virtual machine image + +``` +trivy vm [flags] VM_IMAGE +``` + +### Examples + +``` + # Scan your AWS AMI + $ trivy vm --scanners vuln ami:${your_ami_id} + + # Scan your AWS EBS snapshot + $ trivy vm ebs:${your_ebs_snapshot_id} + +``` + +### Options + +``` + --aws-region string AWS region to scan + --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-ttl duration cache TTL when using redis as cache backend + --clear-cache clear image caches without scanning + --compliance string compliance report to generate + --custom-headers strings custom headers in client mode + --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --download-db-only download/update vulnerability database but don't run a scan + --download-java-db-only download/update Java index database but don't run a scan + --enable-modules strings [EXPERIMENTAL] module names to enable + --exit-code int specify exit code when any security issues are found + --exit-on-eol int exit with the specified code when the OS reaches end of service/life + --file-patterns strings specify config file patterns + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + --helm-api-versions strings Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment) + --helm-kube-version string Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. + --helm-set strings specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-set-file strings specify Helm values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) + --helm-set-string strings specify Helm string values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --helm-values strings specify paths to override the Helm values.yaml files + -h, --help help for vm + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) + --ignore-unfixed display only fixed vulnerabilities + --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-non-failures include successes and exceptions, available with '--scanners misconfig' + --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) + --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") + --no-progress suppress progress bar + --offline-scan do not issue API requests to identify dependencies + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) + --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") + --redis-ca string redis ca file location, if using redis as cache backend + --redis-cert string redis certificate file location, if using redis as cache backend + --redis-key string redis key file location, if using redis as cache backend + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --reset remove all caches and database + --reset-policy-bundle remove policy bundle + --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) + --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) + --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") + --server string server address in client mode + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-db-update skip updating vulnerability database + --skip-dirs strings specify the directories or glob patterns to skip + --skip-files strings specify the files or glob patterns to skip + --skip-java-db-update skip updating Java index database + -t, --template string output template + --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules + --token string for authentication in client/server mode + --token-header string specify a header name for token in client/server mode (default "Trivy-Token") + --vex string [EXPERIMENTAL] file path to VEX + --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md new file mode 100644 index 000000000000..f649d2a213b6 --- /dev/null +++ b/docs/docs/references/configuration/config-file.md @@ -0,0 +1,402 @@ +# Config file + +Trivy can be customized by tweaking a `trivy.yaml` file. +The config path can be overridden by the `--config` flag. + +An example is [here][example]. + +## Global Options + +```yaml +# Same as '--quiet' +# Default is false +quiet: false + +# Same as '--debug' +# Default is false +debug: false + +# Same as '--insecure' +# Default is false +insecure: false + +# Same as '--timeout' +# Default is '5m' +timeout: 10m + +# Same as '--cache-dir' +# Default is your system cache dir +cache: + dir: $HOME/.cache/trivy +``` + +## Report Options + +```yaml +# Same as '--format' +# Default is 'table' +format: table + +# Same as '--report' (available with 'trivy k8s') +# Default is all +report: all + +# Same as '--template' +# Default is empty +template: + +# Same as '--dependency-tree' +# Default is false +dependency-tree: false + +# Same as '--list-all-pkgs' +# Default is false +list-all-pkgs: false + +# Same as '--ignorefile' +# Default is '.trivyignore' +ignorefile: .trivyignore + +# Same as '--ignore-policy' +# Default is empty +ignore-policy: + +# Same as '--exit-code' +# Default is 0 +exit-code: 0 + +# Same as '--exit-on-eol' +# Default is 0 +exit-on-eol: 0 + +# Same as '--output' +# Default is empty (stdout) +output: + +# Same as '--severity' +# Default is all severities +severity: + - UNKNOWN + - LOW + - MEDIUM + - HIGH + - CRITICAL +``` + +## Scan Options +Available in client/server mode + +```yaml +scan: + # Same as '--file-patterns' + # Default is empty + file-patterns: + - + + # Same as '--skip-dirs' + # Default is empty + skip-dirs: + - usr/local/ + - etc/ + + # Same as '--skip-files' + # Default is empty + skip-files: + - package-dev.json + + # Same as '--offline-scan' + # Default is false + offline-scan: false + + # Same as '--scanners' + # Default depends on subcommand + scanners: + - vuln + - misconfig + - secret + - license +``` + +## Cache Options + +```yaml +cache: + # Same as '--cache-backend' + # Default is 'fs' + backend: 'fs' + + # Same as '--cache-ttl' + # Default is 0 (no ttl) + ttl: 0 + + # Redis options + redis: + # Same as '--redis-ca' + # Default is empty + ca: + + # Same as '--redis-cert' + # Default is empty + cert: + + # Same as '--redis-key' + # Default is empty + key: +``` + +## DB Options + +```yaml +db: + # Same as '--skip-db-update' + # Default is false + skip-update: false + + # Same as '--no-progress' + # Default is false + no-progress: false + + # Same as '--db-repository' + # Default is 'ghcr.io/aquasecurity/trivy-db' + repository: ghcr.io/aquasecurity/trivy-db + + # Same as '--java-db-repository' + # Default is 'ghcr.io/aquasecurity/trivy-java-db' + java-repository: ghcr.io/aquasecurity/trivy-java-db +``` + +## Registry Options + +```yaml +registry: + # Same as '--username' + # Default is empty + username: + + # Same as '--password' + # Default is empty + password: + + # Same as '--registry-token' + # Default is empty + registry-token: +``` + +## Image Options +Available with container image scanning + +```yaml +image: + # Same as '--input' (available with 'trivy image') + # Default is empty + input: + + # Same as '--removed-pkgs' + # Default is false + removed-pkgs: false + + # Same as '--platform' + # Default is empty + platform: + + docker: + # Same as '--docker-host' + # Default is empty + host: + + podman: + # Same as '--podman-host' + # Default is empty + host: +``` + +## Vulnerability Options +Available with vulnerability scanning + +```yaml +vulnerability: + # Same as '--vuln-type' + # Default is 'os,library' + type: + - os + - library + + # Same as '--ignore-unfixed' + # Default is false + ignore-unfixed: false +``` + +## Secret Options +Available with secret scanning + +```yaml +secret: + # Same as '--secret-config' + # Default is 'trivy-secret.yaml' + config: config/trivy/secret.yaml +``` + +## Rego Options + +```yaml +rego + # Same as '--trace' + # Default is false + trace: false + + # Same as '--config-policy' + # Default is empty + policy: + - policy/repository + - policy/custom + - policy/some-policy.rego + + # Same as '--config-data' + # Default is empty + data: + - data/ + + # Same as '--policy-namespaces' + # Default is empty + namespaces: + - opa.examples + - users +``` + +## Misconfiguration Options +Available with misconfiguration scanning + +```yaml +misconfiguration: + # Same as '--include-non-failures' + # Default is false + include-non-failures: false + + # Same as '--miconfig-scanners' + # Default is all scanners + scanners: + - dockerfile + - terraform + + # helm value override configurations + helm: + # set individual values + set: + - securityContext.runAsUser=10001 + + # set values with file + values: + - overrides.yaml + + # set specific values from specific files + set-file: + - image=dev-overrides.yaml + + # set as string and preserve type + set-string: + - name=true + + # Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. + api-versions: + - policy/v1/PodDisruptionBudget + - apps/v1/Deployment + + # Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. + kube-version: "v1.21.0" + + # terraform tfvars overrrides + terraform: + vars: + - dev-terraform.tfvars + - common-terraform.tfvars + + # Same as '--tf-exclude-downloaded-modules' + # Default is false + exclude-downloaded-modules: false +``` + +## Kubernetes Options +Available with Kubernetes scanning + +```yaml +kubernetes: + # Same as '--context' + # Default is empty + context: + + # Same as '--namespace' + # Default is empty + namespace: +``` + +## Repository Options +Available with git repository scanning (`trivy repo`) + +```yaml +repository: + # Same as '--branch' + # Default is empty + branch: + + # Same as '--commit' + # Default is empty + commit: + + # Same as '--tag' + # Default is empty + tag: +``` + +## Client/Server Options +Available in client/server mode + +```yaml +server: + # Same as '--server' (available in client mode) + # Default is empty + addr: http://localhost:4954 + + # Same as '--token' + # Default is empty + token: "something-secret" + + # Same as '--token-header' + # Default is 'Trivy-Token' + token-header: 'My-Token-Header' + + # Same as '--custom-headers' + # Default is empty + custom-headers: + - scanner: trivy + - x-api-token: xxx + + # Same as '--listen' (available in server mode) + # Default is 'localhost:4954' + listen: 0.0.0.0:10000 +``` + +## Cloud Options + +Available for cloud scanning (currently only `trivy aws`) + +```yaml +cloud: + # whether to force a cache update for every scan + update-cache: false + + # how old cached results can be before being invalidated + max-cache-age: 24h + + # aws-specific cloud settings + aws: + # the aws region to use + region: us-east-1 + + # the aws endpoint to use (not required for general use) + endpoint: https://my.custom.aws.endpoint + + # the aws account to use (this will be determined from your environment when not set) + account: 123456789012 +``` + +[example]: https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/examples/trivy-conf/trivy.yaml diff --git a/docs/docs/references/modes/client-server.md b/docs/docs/references/modes/client-server.md new file mode 100644 index 000000000000..4e812bd48ab3 --- /dev/null +++ b/docs/docs/references/modes/client-server.md @@ -0,0 +1,336 @@ +# Client/Server + +Trivy has client/server mode. Trivy server has vulnerability database and Trivy client doesn't have to download vulnerability database. It is useful if you want to scan images or files at multiple locations and do not want to download the database at every location. + +## Server +At first, you need to launch Trivy server. It downloads vulnerability database automatically and continue to fetch the latest DB in the background. +``` +$ trivy server --listen localhost:8080 +2019-12-12T15:17:06.551+0200 INFO Need to update DB +2019-12-12T15:17:56.706+0200 INFO Reopening DB... +2019-12-12T15:17:56.707+0200 INFO Listening localhost:8080... +``` + +If you want to accept a connection from outside, you have to specify `0.0.0.0` or your ip address, not `localhost`. + +``` +$ trivy server --listen 0.0.0.0:8080 +``` + +## Remote image scan +Then, specify the server address for `image` command. +``` +$ trivy image --server http://localhost:8080 alpine:3.10 +``` +**Note**: It's important to specify the protocol (http or https). + +
+Result + +``` +alpine:3.10 (alpine 3.10.2) +=========================== +Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 2, HIGH: 0, CRITICAL: 0) + ++---------+------------------+----------+-------------------+---------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | ++---------+------------------+----------+-------------------+---------------+ +| openssl | CVE-2019-1549 | MEDIUM | 1.1.1c-r0 | 1.1.1d-r0 | ++ +------------------+ + + + +| | CVE-2019-1563 | | | | ++ +------------------+----------+ + + +| | CVE-2019-1547 | LOW | | | ++---------+------------------+----------+-------------------+---------------+ +``` +
+ +## Remote scan of local filesystem +Also, there is a way to scan local file system: +```shell +$ trivy fs --server http://localhost:8080 --severity CRITICAL ./integration/testdata/fixtures/fs/pom/ +``` +**Note**: It's important to specify the protocol (http or https). +
+Result + +``` +pom.xml (pom) +============= +Total: 24 (CRITICAL: 24) + ++---------------------------------------------+------------------+----------+-------------------+--------------------------------+---------------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++---------------------------------------------+------------------+----------+-------------------+--------------------------------+---------------------------------------+ +| com.fasterxml.jackson.core:jackson-databind | CVE-2017-17485 | CRITICAL | 2.9.1 | 2.8.11, 2.9.4 | jackson-databind: Unsafe | +| | | | | | deserialization due to | +| | | | | | incomplete black list (incomplete | +| | | | | | fix for CVE-2017-15095)... | +| | | | | | -->avd.aquasec.com/nvd/cve-2017-17485 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2018-11307 | | | 2.7.9.4, 2.8.11.2, 2.9.6 | jackson-databind: Potential | +| | | | | | information exfiltration with | +| | | | | | default typing, serialization | +| | | | | | gadget from MyBatis | +| | | | | | -->avd.aquasec.com/nvd/cve-2018-11307 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2018-14718 | | | 2.6.7.2, 2.9.7 | jackson-databind: arbitrary code | +| | | | | | execution in slf4j-ext class | +| | | | | | -->avd.aquasec.com/nvd/cve-2018-14718 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2018-14719 | | | | jackson-databind: arbitrary | +| | | | | | code execution in blaze-ds-opt | +| | | | | | and blaze-ds-core classes | +| | | | | | -->avd.aquasec.com/nvd/cve-2018-14719 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2018-14720 | | | | jackson-databind: exfiltration/XXE | +| | | | | | in some JDK classes | +| | | | | | -->avd.aquasec.com/nvd/cve-2018-14720 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2018-14721 | | | | jackson-databind: server-side request | +| | | | | | forgery (SSRF) in axis2-jaxws class | +| | | | | | -->avd.aquasec.com/nvd/cve-2018-14721 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2018-19360 | | | 2.6.7.3, 2.7.9.5, 2.8.11.3, | jackson-databind: improper | +| | | | | 2.9.8 | polymorphic deserialization | +| | | | | | in axis2-transport-jms class | +| | | | | | -->avd.aquasec.com/nvd/cve-2018-19360 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2018-19361 | | | | jackson-databind: improper | +| | | | | | polymorphic deserialization | +| | | | | | in openjpa class | +| | | | | | -->avd.aquasec.com/nvd/cve-2018-19361 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2018-19362 | | | | jackson-databind: improper | +| | | | | | polymorphic deserialization | +| | | | | | in jboss-common-core class | +| | | | | | -->avd.aquasec.com/nvd/cve-2018-19362 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2018-7489 | | | 2.7.9.3, 2.8.11.1, 2.9.5 | jackson-databind: incomplete fix | +| | | | | | for CVE-2017-7525 permits unsafe | +| | | | | | serialization via c3p0 libraries | +| | | | | | -->avd.aquasec.com/nvd/cve-2018-7489 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2019-14379 | | | 2.7.9.6, 2.8.11.4, 2.9.9.2 | jackson-databind: default | +| | | | | | typing mishandling leading | +| | | | | | to remote code execution | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-14379 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2019-14540 | | | 2.9.10 | jackson-databind: | +| | | | | | Serialization gadgets in | +| | | | | | com.zaxxer.hikari.HikariConfig | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-14540 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2019-14892 | | | 2.6.7.3, 2.8.11.5, 2.9.10 | jackson-databind: Serialization | +| | | | | | gadgets in classes of the | +| | | | | | commons-configuration package | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-14892 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2019-14893 | | | 2.8.11.5, 2.9.10 | jackson-databind: | +| | | | | | Serialization gadgets in | +| | | | | | classes of the xalan package | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-14893 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2019-16335 | | | 2.9.10 | jackson-databind: | +| | | | | | Serialization gadgets in | +| | | | | | com.zaxxer.hikari.HikariDataSource | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-16335 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2019-16942 | | | 2.9.10.1 | jackson-databind: | +| | | | | | Serialization gadgets in | +| | | | | | org.apache.commons.dbcp.datasources.* | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-16942 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2019-16943 | | | | jackson-databind: | +| | | | | | Serialization gadgets in | +| | | | | | com.p6spy.engine.spy.P6DataSource | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-16943 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2019-17267 | | | 2.9.10 | jackson-databind: Serialization | +| | | | | | gadgets in classes of | +| | | | | | the ehcache package | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-17267 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2019-17531 | | | 2.9.10.1 | jackson-databind: | +| | | | | | Serialization gadgets in | +| | | | | | org.apache.log4j.receivers.db.* | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-17531 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2019-20330 | | | 2.8.11.5, 2.9.10.2 | jackson-databind: lacks | +| | | | | | certain net.sf.ehcache blocking | +| | | | | | -->avd.aquasec.com/nvd/cve-2019-20330 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2020-8840 | | | 2.7.9.7, 2.8.11.5, 2.9.10.3 | jackson-databind: Lacks certain | +| | | | | | xbean-reflect/JNDI blocking | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-8840 | ++ +------------------+ + +--------------------------------+---------------------------------------+ +| | CVE-2020-9546 | | | 2.7.9.7, 2.8.11.6, 2.9.10.4 | jackson-databind: Serialization | +| | | | | | gadgets in shaded-hikari-config | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-9546 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2020-9547 | | | | jackson-databind: Serialization | +| | | | | | gadgets in ibatis-sqlmap | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-9547 | ++ +------------------+ + + +---------------------------------------+ +| | CVE-2020-9548 | | | | jackson-databind: Serialization | +| | | | | | gadgets in anteros-core | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-9548 | ++---------------------------------------------+------------------+----------+-------------------+--------------------------------+---------------------------------------+ +``` +
+ +## Remote scan of root filesystem +Also, there is a way to scan root file system: +```shell +$ trivy rootfs --server http://localhost:8080 --severity CRITICAL /tmp/rootfs +``` +**Note**: It's important to specify the protocol (http or https). +
+Result + +``` +/tmp/rootfs (alpine 3.10.2) + +Total: 1 (CRITICAL: 1) + +┌───────────┬────────────────┬──────────┬───────────────────┬───────────────┬─────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├───────────┼────────────────┼──────────┼───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤ +│ apk-tools │ CVE-2021-36159 │ CRITICAL │ 2.10.4-r2 │ 2.10.7-r0 │ libfetch before 2021-07-26, as used in apk-tools, xbps, and │ +│ │ │ │ │ │ other products, mishandles... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-36159 │ +└───────────┴────────────────┴──────────┴───────────────────┴───────────────┴─────────────────────────────────────────────────────────────┘ +``` +
+ +## Remote scan of git repository +Also, there is a way to scan remote git repository: +```shell +$ trivy repo https://github.com/knqyf263/trivy-ci-test --server http://localhost:8080 +``` +**Note**: It's important to specify the protocol (http or https). +
+Result + +``` +Cargo.lock (cargo) +================== +Total: 5 (UNKNOWN: 0, LOW: 0, MEDIUM: 2, HIGH: 2, CRITICAL: 0) + +┌───────────┬─────────────────────┬──────────┬───────────────────┬───────────────┬─────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├───────────┼─────────────────────┼──────────┼───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤ +│ ammonia │ CVE-2019-15542 │ HIGH │ 1.9.0 │ 2.1.0 │ Uncontrolled recursion in ammonia │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-15542 │ +│ ├─────────────────────┼──────────┤ ├───────────────┼─────────────────────────────────────────────────────────────┤ +│ │ CVE-2021-38193 │ MEDIUM │ │ 2.1.3, 3.1.0 │ An issue was discovered in the ammonia crate before 3.1.0 │ +│ │ │ │ │ │ for Rust.... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-38193 │ +├───────────┼─────────────────────┤ ├───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤ +│ smallvec │ CVE-2019-15551 │ │ 0.6.9 │ 0.6.10 │ An issue was discovered in the smallvec crate before 0.6.10 │ +│ │ │ │ │ │ for Rust.... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-15551 │ +│ ├─────────────────────┼──────────┤ ├───────────────┼─────────────────────────────────────────────────────────────┤ +│ │ CVE-2018-25023 │ HIGH │ │ 0.6.13 │ An issue was discovered in the smallvec crate before 0.6.13 │ +│ │ │ │ │ │ for Rust.... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2018-25023 │ +│ ├─────────────────────┼──────────┤ │ ├─────────────────────────────────────────────────────────────┤ +│ │ GHSA-66p5-j55p-32r9 │ MEDIUM │ │ │ smallvec creates uninitialized value of any type │ +│ │ │ │ │ │ https://github.com/advisories/GHSA-66p5-j55p-32r9 │ +└───────────┴─────────────────────┴──────────┴───────────────────┴───────────────┴─────────────────────────────────────────────────────────────┘ + +Pipfile.lock (pipenv) +===================== +Total: 8 (UNKNOWN: 0, LOW: 0, MEDIUM: 6, HIGH: 2, CRITICAL: 0) + +┌─────────────────────┬────────────────┬──────────┬───────────────────┬────────────────────────┬──────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├─────────────────────┼────────────────┼──────────┼───────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ celery │ CVE-2021-23727 │ HIGH │ 4.3.0 │ 5.2.2 │ celery: stored command injection vulnerability may allow │ +│ │ │ │ │ │ privileges escalation │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-23727 │ +├─────────────────────┼────────────────┤ ├───────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ django │ CVE-2019-6975 │ │ 2.0.9 │ 1.11.19, 2.0.12, 2.1.7 │ python-django: memory exhaustion in │ +│ │ │ │ │ │ django.utils.numberformat.format() │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-6975 │ +│ ├────────────────┼──────────┤ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2019-3498 │ MEDIUM │ │ 1.11.18, 2.0.10, 2.1.5 │ python-django: Content spoofing via URL path in default 404 │ +│ │ │ │ │ │ page │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-3498 │ +│ ├────────────────┤ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2021-33203 │ │ │ 2.2.24, 3.1.12, 3.2.4 │ django: Potential directory traversal via ``admindocs`` │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-33203 │ +├─────────────────────┼────────────────┤ ├───────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ urllib3 │ CVE-2019-11324 │ │ 1.24.1 │ 1.24.2 │ python-urllib3: Certification mishandle when error should be │ +│ │ │ │ │ │ thrown │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-11324 │ +│ ├────────────────┤ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2021-33503 │ │ │ 1.26.5 │ python-urllib3: ReDoS in the parsing of authority part of │ +│ │ │ │ │ │ URL │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-33503 │ +│ ├────────────────┼──────────┤ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2019-11236 │ MEDIUM │ │ 1.24.3 │ python-urllib3: CRLF injection due to not encoding the │ +│ │ │ │ │ │ '\r\n' sequence leading to... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-11236 │ +│ ├────────────────┤ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2020-26137 │ │ │ 1.25.9 │ python-urllib3: CRLF injection via HTTP request method │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-26137 │ +└─────────────────────┴────────────────┴──────────┴───────────────────┴────────────────────────┴──────────────────────────────────────────────────────────────┘ +``` +
+ +## Authentication + +``` +$ trivy server --listen localhost:8080 --token dummy +``` + +``` +$ trivy image --server http://localhost:8080 --token dummy alpine:3.10 +``` + +## Endpoints + +### Health +Checks whether the Trivy server is running. Authentication is not required. + +Example request: +```bash +curl -s 0.0.0.0:8080/healthz +ok +``` + +Returns the `200 OK` status if the request was successful. +### Version + +Returns the version of the Trivy and all components (db, policy). Authentication is not required. + +Example request: +```bash +curl -s 0.0.0.0:8080/version | jq +{ + "Version": "dev", + "VulnerabilityDB": { + "Version": 2, + "NextUpdate": "2023-07-25T14:15:29.876639806Z", + "UpdatedAt": "2023-07-25T08:15:29.876640206Z", + "DownloadedAt": "2023-07-25T09:36:25.599004Z" + }, + "JavaDB": { + "Version": 1, + "NextUpdate": "2023-07-28T01:03:52.169192565Z", + "UpdatedAt": "2023-07-25T01:03:52.169192765Z", + "DownloadedAt": "2023-07-25T09:37:48.906152Z" + }, + "PolicyBundle": { + "Digest": "sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43", + "DownloadedAt": "2023-07-23T11:40:33.122462Z" + } +} +``` + +Returns the `200 OK` status if the request was successful. + +## Architecture + +![architecture](../../../imgs/client-server.png) + diff --git a/docs/docs/references/modes/standalone.md b/docs/docs/references/modes/standalone.md new file mode 100644 index 000000000000..c36776af9009 --- /dev/null +++ b/docs/docs/references/modes/standalone.md @@ -0,0 +1,16 @@ +# Standalone + +`trivy image`, `trivy filesystem`, and `trivy repo` works as standalone mode. + +## Image + +![standalone](../../../imgs/image.png) + +## Filesystem + +![fs](../../../imgs/fs.png) + +## Git Repository + +![repo](../../../imgs/repo.png) + diff --git a/docs/docs/references/troubleshooting.md b/docs/docs/references/troubleshooting.md new file mode 100644 index 000000000000..fcdb97bb8a61 --- /dev/null +++ b/docs/docs/references/troubleshooting.md @@ -0,0 +1,246 @@ +# Troubleshooting + +## Scan +### Timeout + +!!! error + ``` bash + $ trivy image ... + ... + analyze error: timeout: context deadline exceeded + ``` + +Your scan may time out. Java takes a particularly long time to scan. Try increasing the value of the ---timeout option such as `--timeout 15m`. + +### Unable to initialize an image scanner + +!!! error + ```bash + $ trivy image ... + ... + 2024-01-19T08:15:33.288Z FATAL image scan error: scan error: unable to initialize a scanner: unable to initialize an image scanner: 4 errors occurred: + * docker error: unable to inspect the image (ContainerImageName): Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? + * containerd error: containerd socket not found: /run/containerd/containerd.sock + * podman error: unable to initialize Podman client: no podman socket found: stat podman/podman.sock: no such file or directory + * remote error: GET https://index.docker.io/v2/ContainerImageName: MANIFEST_UNKNOWN: manifest unknown; unknown tag=0.1 + ``` + +It means Trivy is unable to find the container image in the following places: + +* Docker Engine +* containerd +* Podman +* A remote registry + +Please see error messages for details of each error. + +Common mistakes include the following, depending on where you are pulling images from: + +#### Common +- Typos in the image name + - Common mistake :) +- Forgetting to specify the registry + - By default, it is considered to be Docker Hub ( `index.docker.io` ). + +#### Docker Engine +- Incorrect Docker host + - If the Docker daemon's socket path is not `/var/run/docker.sock`, you need to specify the `--docker-host` flag or the `DOCKER_HOST` environment variable. + The same applies when using TCP; you must specify the correct host address. + +#### containerd +- Incorrect containerd address + - If you are using a non-default path, you need to specify the `CONTAINERD_ADDRESS` environment variable. + Please refer to [this documentation](../target/container_image.md#containerd). +- Incorrect namespace + - If you are using a non-default namespace, you need to specify the `CONTAINERD_NAMESPACE` environment variable. + Please refer to [this documentation](../target/container_image.md#containerd). + - +#### Podman +- Podman socket configuration + - You need to enable the Podman socket. Please refer to [this documentation](../target/container_image.md#podman). + +#### Container Registry +- Unauthenticated + - If you are using a private container registry, you need to authenticate. Please refer to [this documentation](../advanced/private-registries/index.md). +- Using a proxy + - If you are using a proxy within your network, you need to correctly set the `HTTP_PROXY`, `HTTPS_PROXY`, etc., environment variables. +- Use of a self-signed certificate in the registry + - Because certificate verification will fail, you need to either trust that certificate or use the `--insecure` flag (not recommended in production). + +### Certification + +!!! error + Error: x509: certificate signed by unknown authority + +`TRIVY_INSECURE` can be used to allow insecure connections to a container registry when using SSL. + +``` +$ TRIVY_INSECURE=true trivy image [YOUR_IMAGE] +``` + +### GitHub Rate limiting + +!!! error + ``` bash + $ trivy image ... + ... + API rate limit exceeded for xxx.xxx.xxx.xxx. + ``` + +Specify GITHUB_TOKEN for authentication +https://developer.github.com/v3/#rate-limiting + +``` +$ GITHUB_TOKEN=XXXXXXXXXX trivy alpine:3.10 +``` + +### Unable to open JAR files + +!!! error + ``` bash + $ trivy image ... + ... + failed to analyze file: failed to analyze usr/lib/jvm/java-1.8-openjdk/lib/tools.jar: unable to open usr/lib/jvm/java-1.8-openjdk/lib/tools.jar: failed to open: unable to read the file: stream error: stream ID 9; PROTOCOL_ERROR; received from peer + ``` + +Currently, we're investigating this issue. As a temporary mitigation, you may be able to avoid this issue by downloading the Java DB in advance. + +```shell +$ trivy image --download-java-db-only +2023-02-01T16:57:04.322+0900 INFO Downloading the Java DB... +$ trivy image [YOUR_JAVA_IMAGE] +``` + +### Running in parallel takes same time as series run +When running trivy on multiple images simultaneously, it will take same time as running trivy in series. +This is because of a limitation of boltdb. +> Bolt obtains a file lock on the data file so multiple processes cannot open the same database at the same time. Opening an already open Bolt database will cause it to hang until the other process closes it. + +Reference : [boltdb: Opening a database][boltdb]. + +[boltdb]: https://github.com/boltdb/bolt#opening-a-database + +### Multiple Trivy servers + +!!! error + ``` + $ trivy image --server http://xxx.com:xxxx test-image + ... + - twirp error internal: failed scan, test-image: failed to apply layers: layer cache missing: sha256:***** + ``` +To run multiple Trivy servers, you need to use Redis as the cache backend so that those servers can share the cache. +Follow [this instruction][redis-cache] to do so. + + +### Problems with `/tmp` on remote Git repository scans + +!!! error + FATAL repository scan error: scan error: unable to initialize a scanner: unable to initialize a filesystem scanner: git clone error: write /tmp/fanal-remote... + +Trivy clones remote Git repositories under the `/tmp` directory before scanning them. If `/tmp` doesn't work for you, you can change it by setting the `TMPDIR` environment variable. + +Try: + +``` +$ TMPDIR=/my/custom/path trivy repo ... +``` + +### Running out of space during image scans + +!!! error + ``` bash + image scan failed: + failed to copy the image: + write /tmp/fanal-3323732142: no space left on device + ``` + +Trivy uses the `/tmp` directory during image scan, if the image is large or `/tmp` is of insufficient size then the scan fails You can set the `TMPDIR` environment variable to use redirect trivy to use a directory with adequate storage. + +Try: + +``` +$ TMPDIR=/my/custom/path trivy image ... +``` + +## DB +### Old DB schema + +!!! error + --skip-update cannot be specified with the old DB schema. + +Trivy v0.23.0 or later requires Trivy DB v2. Please update your local database or follow [the instruction of air-gapped environment][air-gapped]. + +### Error downloading vulnerability DB + +!!! error + FATAL failed to download vulnerability DB + +If trivy is running behind corporate firewall, you have to add the following urls to your allowlist. + +- ghcr.io +- pkg-containers.githubusercontent.com + +### Denied + +!!! error + GET https://ghcr.io/token?scope=repository%3Aaquasecurity%2Ftrivy-db%3Apull&service=ghcr.io: DENIED: denied + +Your local GHCR (GitHub Container Registry) token might be expired. +Please remove the token and try downloading the DB again. + +```shell +docker logout ghcr.io +``` + + +## Homebrew +### Scope error +!!! error + Error: Your macOS keychain GitHub credentials do not have sufficient scope! + +``` +$ brew tap aquasecurity/trivy +Error: Your macOS keychain GitHub credentials do not have sufficient scope! +Scopes they need: none +Scopes they have: +Create a personal access token: +https://github.com/settings/tokens/new?scopes=gist,public_repo&description=Homebrew +echo 'export HOMEBREW_GITHUB_API_TOKEN=your_token_here' >> ~/.zshrc +``` + +Try: + +``` +$ printf "protocol=https\nhost=github.com\n" | git credential-osxkeychain erase +``` + +### Already installed +!!! error + Error: aquasecurity/trivy/trivy 64 already installed + +``` +$ brew upgrade +... +Error: aquasecurity/trivy/trivy 64 already installed +``` + +Try: + +``` +$ brew unlink trivy && brew uninstall trivy +($ rm -rf /usr/local/Cellar/trivy/64) +$ brew install aquasecurity/trivy/trivy +``` + + +## Others +### Unknown error + +Try again with `--reset` option: + +``` +$ trivy image --reset +``` + +[air-gapped]: ../advanced/air-gap.md +[redis-cache]: ../../vulnerability/examples/cache/#cache-backend diff --git a/docs/docs/scanner/license.md b/docs/docs/scanner/license.md new file mode 100644 index 000000000000..7472011af671 --- /dev/null +++ b/docs/docs/scanner/license.md @@ -0,0 +1,349 @@ +# License Scanning + +Trivy scans any container image for license files and offers an opinionated view on the risk associated with the license. + +License are classified using the [Google License Classification][google-license-classification] - + + - Forbidden + - Restricted + - Reciprocal + - Notice + - Permissive + - Unencumbered + - Unknown + +!!! tip + Licenses that Trivy fails to recognize are classified as UNKNOWN. + As those licenses may be in violation, it is recommended to check those unknown licenses as well. + +By default, Trivy scans licenses for packages installed by `apk`, `apt-get`, `dnf`, `npm`, `pip`, `gem`, etc. +Check out [the coverage document][coverage] for details. + +To enable extended license scanning, you can use `--license-full`. +In addition to package licenses, Trivy scans source code files, Markdown documents, text files and `LICENSE` documents to identify license usage within the image or filesystem. + +By default, Trivy only classifies licenses that are matched with a confidence level of 0.9 or more by the classifier. +To configure the confidence level, you can use `--license-confidence-level`. This enables us to classify licenses that might be matched with a lower confidence level by the classifer. + +!!! note + The full license scanning is expensive. It takes a while. + +| License scanning | Image | Rootfs | Filesystem | Repository | SBOM | +|:---------------------:|:-----:|:------:|:----------:|:----------:|:----:| +| Standard | ✅ | ✅ | ✅[^1][^2] | ✅[^1][^2] | ✅ | +| Full (--license-full) | ✅ | ✅ | ✅ | ✅ | - | + +License checking classifies the identified licenses and map the classification to severity. + +| Classification | Severity | +| -------------- | -------- | +| Forbidden | CRITICAL | +| Restricted | HIGH | +| Reciprocal | MEDIUM | +| Notice | LOW | +| Permissive | LOW | +| Unencumbered | LOW | +| Unknown | UNKNOWN | + +## Quick start +This section shows how to scan license in container image and filesystem. + +### Standard scanning +Specify an image name with `--scanners license`. + +``` shell +$ trivy image --scanners license --severity UNKNOWN,HIGH,CRITICAL alpine:3.15 +2022-07-13T17:28:39.526+0300 INFO License scanning is enabled + +OS Packages (license) +===================== +Total: 6 (UNKNOWN: 0, HIGH: 6, CRITICAL: 0) + +┌───────────────────┬─────────┬────────────────┬──────────┠+│ Package │ License │ Classification │ Severity │ +├───────────────────┼─────────┼────────────────┼──────────┤ +│ alpine-baselayout │ GPL-2.0 │ Restricted │ HIGH │ +├───────────────────┤ │ │ │ +│ apk-tools │ │ │ │ +├───────────────────┤ │ │ │ +│ busybox │ │ │ │ +├───────────────────┤ │ │ │ +│ musl-utils │ │ │ │ +├───────────────────┤ │ │ │ +│ scanelf │ │ │ │ +├───────────────────┤ │ │ │ +│ ssl_client │ │ │ │ +└───────────────────┴─────────┴────────────────┴──────────┘ +``` + +### Full scanning +Specify `--license-full` + +``` shell +$ trivy image --scanners license --severity UNKNOWN,HIGH,CRITICAL --license-full grafana/grafana +2022-07-13T17:48:40.905+0300 INFO Full license scanning is enabled + +OS Packages (license) +===================== +Total: 20 (UNKNOWN: 9, HIGH: 11, CRITICAL: 0) + +┌───────────────────┬───────────────────┬────────────────┬──────────┠+│ Package │ License │ Classification │ Severity │ +├───────────────────┼───────────────────┼────────────────┼──────────┤ +│ alpine-baselayout │ GPL-2.0 │ Restricted │ HIGH │ +├───────────────────┤ │ │ │ +│ apk-tools │ │ │ │ +├───────────────────┼───────────────────┤ │ │ +│ bash │ GPL-3.0 │ │ │ +├───────────────────┼───────────────────┼────────────────┼──────────┤ +│ keyutils-libs │ GPL-2.0 │ Restricted │ HIGH │ +│ ├───────────────────┼────────────────┼──────────┤ +│ │ LGPL-2.0-or-later │ Non Standard │ UNKNOWN │ +├───────────────────┼───────────────────┤ │ │ +│ libaio │ LGPL-2.1-or-later │ │ │ +├───────────────────┼───────────────────┼────────────────┼──────────┤ +│ libcom_err │ GPL-2.0 │ Restricted │ HIGH │ +│ ├───────────────────┼────────────────┼──────────┤ +│ │ LGPL-2.0-or-later │ Non Standard │ UNKNOWN │ +├───────────────────┼───────────────────┼────────────────┼──────────┤ +│ tzdata │ Public-Domain │ Non Standard │ UNKNOWN │ +└───────────────────┴───────────────────┴────────────────┴──────────┘ + +Loose File License(s) (license) +=============================== +Total: 6 (UNKNOWN: 4, HIGH: 0, CRITICAL: 2) + +┌────────────────┬──────────┬──────────────┬──────────────────────────────────────────────────────────────┠+│ Classification │ Severity │ License │ File Location │ +├────────────────┼──────────┼──────────────┼──────────────────────────────────────────────────────────────┤ +│ Forbidden │ CRITICAL │ AGPL-3.0 │ /usr/share/grafana/LICENSE │ +│ │ │ │ │ +│ │ │ │ │ +├────────────────┼──────────┼──────────────┼──────────────────────────────────────────────────────────────┤ +│ Non Standard │ UNKNOWN │ BSD-0-Clause │ /usr/share/grafana/public/build/5069.d6aae9dd11d49c741a80.j- │ +│ │ │ │ s.LICENSE.txt │ +│ │ │ ├──────────────────────────────────────────────────────────────┤ +│ │ │ │ /usr/share/grafana/public/build/6444.d6aae9dd11d49c741a80.j- │ +│ │ │ │ s.LICENSE.txt │ +│ │ │ ├──────────────────────────────────────────────────────────────┤ +│ │ │ │ /usr/share/grafana/public/build/7889.d6aae9dd11d49c741a80.j- │ +│ │ │ │ s.LICENSE.txt │ +│ │ │ ├──────────────────────────────────────────────────────────────┤ +│ │ │ │ /usr/share/grafana/public/build/canvasPanel.d6aae9dd11d49c7- │ +│ │ │ │ 41a80.js.LICENSE.txt │ +└────────────────┴──────────┴──────────────┴──────────────────────────────────────────────────────────────┘ +``` + +## Configuration + +Trivy has number of configuration flags for use with license scanning; + +### Ignored Licenses + +Trivy license scanning can ignore licenses that are identified to explicitly remove them from the results using the `--ignored-licenses` flag; + +```shell +$ trivy image --scanners license --ignored-licenses MPL-2.0,MIT --severity HIGH grafana/grafana:latest +2022-07-13T18:15:28.605Z INFO License scanning is enabled + +OS Packages (license) +===================== +Total: 2 (HIGH: 2, CRITICAL: 0) + +┌───────────────────┬─────────┬────────────────┬──────────┠+│ Package │ License │ Classification │ Severity │ +├───────────────────┼─────────┼────────────────┼──────────┤ +│ alpine-baselayout │ GPL-2.0 │ Restricted │ HIGH │ +├───────────────────┤ │ │ │ +│ ssl_client │ │ │ │ +└───────────────────┴─────────┴────────────────┴──────────┘ + +``` + +### Configuring Classifier Confidence Level +You can use the `--license-confidence-level` flag to adjust the confidence level between 0.0 to 1.0 (default 0.9). +For example, when you run the scanner with the default confidence level on [SPDX license list data](https://github.com/spdx/license-list-data/tree/main/text), it is able to detect only 258 licenses. + +```shell +$ trivy fs --scanners license --license-full +2023-04-18T10:05:13.601-0700 INFO Full license scanning is enabled + +Loose File License(s) (license) +=============================== +Total: 258 (UNKNOWN: 70, LOW: 90, MEDIUM: 18, HIGH: 58, CRITICAL: 22) +``` + +However, by configuring the confidence level to 0.8, the scanner is now able to detect 282 licenses. + +```shell +$ trivy fs --scanners license --license-full --license-confidence-level 0.8 +2023-04-18T10:21:39.637-0700 INFO Full license scanning is enabled + +Loose File License(s) (license) +=============================== +Total: 282 (UNKNOWN: 81, LOW: 97, MEDIUM: 24, HIGH: 58, CRITICAL: 22) +``` + +### Custom Classification +You can generate the default config by the `--generate-default-config` flag and customize the license classification. +For example, if you want to forbid only AGPL-3.0, you can leave it under `forbidden` and move other licenses to another classification. + +```shell +$ trivy image --generate-default-config +$ vim trivy.yaml +license: + forbidden: + - AGPL-3.0 + + restricted: + - AGPL-1.0 + - CC-BY-NC-1.0 + - CC-BY-NC-2.0 + - CC-BY-NC-2.5 + - CC-BY-NC-3.0 + - CC-BY-NC-4.0 + - CC-BY-NC-ND-1.0 + - CC-BY-NC-ND-2.0 + - CC-BY-NC-ND-2.5 + - CC-BY-NC-ND-3.0 + - CC-BY-NC-ND-4.0 + - CC-BY-NC-SA-1.0 + - CC-BY-NC-SA-2.0 + - CC-BY-NC-SA-2.5 + - CC-BY-NC-SA-3.0 + - CC-BY-NC-SA-4.0 + - Commons-Clause + - Facebook-2-Clause + - Facebook-3-Clause + - Facebook-Examples + - WTFPL + - BCL + - CC-BY-ND-1.0 + - CC-BY-ND-2.0 + - CC-BY-ND-2.5 + - CC-BY-ND-3.0 + - CC-BY-ND-4.0 + - CC-BY-SA-1.0 + - CC-BY-SA-2.0 + - CC-BY-SA-2.5 + - CC-BY-SA-3.0 + - CC-BY-SA-4.0 + - GPL-1.0 + - GPL-2.0 + - GPL-2.0-with-autoconf-exception + - GPL-2.0-with-bison-exception + - GPL-2.0-with-classpath-exception + - GPL-2.0-with-font-exception + - GPL-2.0-with-GCC-exception + - GPL-3.0 + - GPL-3.0-with-autoconf-exception + - GPL-3.0-with-GCC-exception + - LGPL-2.0 + - LGPL-2.1 + - LGPL-3.0 + - NPL-1.0 + - NPL-1.1 + - OSL-1.0 + - OSL-1.1 + - OSL-2.0 + - OSL-2.1 + - OSL-3.0 + - QPL-1.0 + - Sleepycat + + reciprocal: + - APSL-1.0 + - APSL-1.1 + - APSL-1.2 + - APSL-2.0 + - CDDL-1.0 + - CDDL-1.1 + - CPL-1.0 + - EPL-1.0 + - EPL-2.0 + - FreeImage + - IPL-1.0 + - MPL-1.0 + - MPL-1.1 + - MPL-2.0 + - Ruby + + notice: + - AFL-1.1 + - AFL-1.2 + - AFL-2.0 + - AFL-2.1 + - AFL-3.0 + - Apache-1.0 + - Apache-1.1 + - Apache-2.0 + - Artistic-1.0-cl8 + - Artistic-1.0-Perl + - Artistic-1.0 + - Artistic-2.0 + - BSL-1.0 + - BSD-2-Clause-FreeBSD + - BSD-2-Clause-NetBSD + - BSD-2-Clause + - BSD-3-Clause-Attribution + - BSD-3-Clause-Clear + - BSD-3-Clause-LBNL + - BSD-3-Clause + - BSD-4-Clause + - BSD-4-Clause-UC + - BSD-Protection + - CC-BY-1.0 + - CC-BY-2.0 + - CC-BY-2.5 + - CC-BY-3.0 + - CC-BY-4.0 + - FTL + - ISC + - ImageMagick + - Libpng + - Lil-1.0 + - Linux-OpenIB + - LPL-1.02 + - LPL-1.0 + - MS-PL + - MIT + - NCSA + - OpenSSL + - PHP-3.01 + - PHP-3.0 + - PIL + - Python-2.0 + - Python-2.0-complete + - PostgreSQL + - SGI-B-1.0 + - SGI-B-1.1 + - SGI-B-2.0 + - Unicode-DFS-2015 + - Unicode-DFS-2016 + - Unicode-TOU + - UPL-1.0 + - W3C-19980720 + - W3C-20150513 + - W3C + - X11 + - Xnet + - Zend-2.0 + - zlib-acknowledgement + - Zlib + - ZPL-1.1 + - ZPL-2.0 + - ZPL-2.1 + + unencumbered: + - CC0-1.0 + - Unlicense + - 0BSD + + permissive: [] +``` + +[^1]: See the list of supported language files [here](../coverage/language/index.md). +[^2]: Some lock files require additional files (e.g. files from the cache directory) to detect licenses. Check [coverage][coverage] for more information. + +[coverage]: ../coverage/index.md +[google-license-classification]: https://opensource.google/documentation/reference/thirdparty/licenses diff --git a/docs/docs/scanner/misconfiguration/custom/combine.md b/docs/docs/scanner/misconfiguration/custom/combine.md new file mode 100644 index 000000000000..0d7997bc0272 --- /dev/null +++ b/docs/docs/scanner/misconfiguration/custom/combine.md @@ -0,0 +1,44 @@ +# Combined input + +## Overview +Trivy usually scans each configuration file individually. +Sometimes it might be useful to compare values from different configuration files simultaneously. + +When `combine` is set to true, all config files under the specified directory are combined into one input data structure. + +!!! example + ``` + __rego_input__ := { + "combine": false, + } + ``` + +In "combine" mode, the `input` document becomes an array, where each element is an object with two fields: + +- `"path": "path/to/file"`: the relative file path of the respective file +- `"contents": ...`: the parsed content of the respective file + +Now you can ensure that duplicate values match across the entirety of your configuration files. + +## Return value +In "combine" mode, the `deny` entrypoint must return an object with two keys + +`filepath` (required) +: the relative file path of the file being evaluated + +`msg` (required) +: the message describing an issue + +!!! example + ``` + deny[res] { + resource := input[i].contents + ... some logic ... + + res := { + "filepath": input[i].path, + "msg": "something bad", + } + } + ``` + diff --git a/docs/docs/scanner/misconfiguration/custom/data.md b/docs/docs/scanner/misconfiguration/custom/data.md new file mode 100644 index 000000000000..6e858d86ed6f --- /dev/null +++ b/docs/docs/scanner/misconfiguration/custom/data.md @@ -0,0 +1,35 @@ +# Custom Data + +Custom policies may require additional data in order to determine an answer. + +For example, an allowed list of resources that can be created. +Instead of hardcoding this information inside your policy, Trivy allows passing paths to data files with the `--data` flag. + +Given the following yaml file: + +```bash +$ cd examples/misconf/custom-data +$ cat data/ports.yaml [~/src/github.com/aquasecurity/trivy/examples/misconf/custom-data] +services: + ports: + - "20" + - "20/tcp" + - "20/udp" + - "23" + - "23/tcp" +``` + +This can be imported into your policy: + +```rego +import data.services + +ports := services.ports +``` + +Then, you need to pass data paths through `--data` option. +Trivy recursively searches the specified paths for JSON (`*.json`) and YAML (`*.yaml`) files. + +```bash +$ trivy conf --policy ./policy --data data --namespaces user ./configs +``` \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/custom/debug.md b/docs/docs/scanner/misconfiguration/custom/debug.md new file mode 100644 index 000000000000..8ea0cc5e0e71 --- /dev/null +++ b/docs/docs/scanner/misconfiguration/custom/debug.md @@ -0,0 +1,304 @@ +# Debugging policies +When working on more complex queries (or when learning Rego), it's useful to see exactly how the policy is applied. +For this purpose you can use the `--trace` flag. +This will output a large trace from Open Policy Agent like the following: + +!!! tip + Only failed policies show traces. If you want to debug a passed policy, you need to make it fail on purpose. + +```shell +$ trivy conf --trace configs/ +2022-05-16T13:47:58.853+0100 INFO Detected config files: 1 + +Dockerfile (dockerfile) +======================= +Tests: 23 (SUCCESSES: 21, FAILURES: 2, EXCEPTIONS: 0) +Failures: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 1, CRITICAL: 0) + +MEDIUM: Specify a tag in the 'FROM' statement for image 'alpine' +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +When using a 'FROM' statement you should use a specific tag to avoid uncontrolled behavior when the image is updated. + +See https://avd.aquasec.com/misconfig/ds001 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Dockerfile:1 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 1 [ FROM alpine:latest +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + +HIGH: Last USER command in Dockerfile should not be 'root' +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile. + +See https://avd.aquasec.com/misconfig/ds002 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Dockerfile:3 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 3 [ USER root +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + + +ID: DS001 +File: Dockerfile +Namespace: builtin.dockerfile.DS001 +Query: data.builtin.dockerfile.DS001.deny +Message: Specify a tag in the 'FROM' statement for image 'alpine' +TRACE Enter data.builtin.dockerfile.DS001.deny = _ +TRACE | Eval data.builtin.dockerfile.DS001.deny = _ +TRACE | Index data.builtin.dockerfile.DS001.deny (matched 1 rule) +TRACE | Enter data.builtin.dockerfile.DS001.deny +TRACE | | Eval output = data.builtin.dockerfile.DS001.fail_latest[_] +TRACE | | Index data.builtin.dockerfile.DS001.fail_latest (matched 1 rule) +TRACE | | Enter data.builtin.dockerfile.DS001.fail_latest +TRACE | | | Eval output = data.builtin.dockerfile.DS001.image_tags[_] +TRACE | | | Index data.builtin.dockerfile.DS001.image_tags (matched 2 rules) +TRACE | | | Enter data.builtin.dockerfile.DS001.image_tags +TRACE | | | | Eval from = data.lib.docker.from[_] +TRACE | | | | Index data.lib.docker.from (matched 1 rule) +TRACE | | | | Enter data.lib.docker.from +TRACE | | | | | Eval instruction = input.stages[_][_] +TRACE | | | | | Eval instruction.Cmd = "from" +TRACE | | | | | Exit data.lib.docker.from +TRACE | | | | Redo data.lib.docker.from +TRACE | | | | | Redo instruction.Cmd = "from" +TRACE | | | | | Redo instruction = input.stages[_][_] +TRACE | | | | | Eval instruction.Cmd = "from" +TRACE | | | | | Fail instruction.Cmd = "from" +TRACE | | | | | Redo instruction = input.stages[_][_] +TRACE | | | | | Eval instruction.Cmd = "from" +TRACE | | | | | Fail instruction.Cmd = "from" +TRACE | | | | | Redo instruction = input.stages[_][_] +TRACE | | | | Eval name = from.Value[0] +TRACE | | | | Eval not startswith(name, "$") +TRACE | | | | Enter startswith(name, "$") +TRACE | | | | | Eval startswith(name, "$") +TRACE | | | | | Fail startswith(name, "$") +TRACE | | | | Eval data.builtin.dockerfile.DS001.parse_tag(name, __local505__) +TRACE | | | | Index data.builtin.dockerfile.DS001.parse_tag (matched 2 rules) +TRACE | | | | Enter data.builtin.dockerfile.DS001.parse_tag +TRACE | | | | | Eval split(name, ":", __local504__) +TRACE | | | | | Eval [img, tag] = __local504__ +TRACE | | | | | Exit data.builtin.dockerfile.DS001.parse_tag +TRACE | | | | Eval [img, tag] = __local505__ +TRACE | | | | Eval output = {"cmd": from, "img": img, "tag": tag} +TRACE | | | | Exit data.builtin.dockerfile.DS001.image_tags +TRACE | | | Redo data.builtin.dockerfile.DS001.image_tags +TRACE | | | | Redo output = {"cmd": from, "img": img, "tag": tag} +TRACE | | | | Redo [img, tag] = __local505__ +TRACE | | | | Redo data.builtin.dockerfile.DS001.parse_tag(name, __local505__) +TRACE | | | | Redo data.builtin.dockerfile.DS001.parse_tag +TRACE | | | | | Redo [img, tag] = __local504__ +TRACE | | | | | Redo split(name, ":", __local504__) +TRACE | | | | Enter data.builtin.dockerfile.DS001.parse_tag +TRACE | | | | | Eval tag = "latest" +TRACE | | | | | Eval not contains(img, ":") +TRACE | | | | | Enter contains(img, ":") +TRACE | | | | | | Eval contains(img, ":") +TRACE | | | | | | Exit contains(img, ":") +TRACE | | | | | Redo contains(img, ":") +TRACE | | | | | | Redo contains(img, ":") +TRACE | | | | | Fail not contains(img, ":") +TRACE | | | | | Redo tag = "latest" +TRACE | | | | Redo name = from.Value[0] +TRACE | | | | Redo from = data.lib.docker.from[_] +TRACE | | | Enter data.builtin.dockerfile.DS001.image_tags +TRACE | | | | Eval from = data.lib.docker.from[i] +TRACE | | | | Index data.lib.docker.from (matched 1 rule) +TRACE | | | | Eval name = from.Value[0] +TRACE | | | | Eval cmd_obj = input.stages[j][k] +TRACE | | | | Eval possibilities = {"arg", "env"} +TRACE | | | | Eval cmd_obj.Cmd = possibilities[l] +TRACE | | | | Fail cmd_obj.Cmd = possibilities[l] +TRACE | | | | Redo possibilities = {"arg", "env"} +TRACE | | | | Redo cmd_obj = input.stages[j][k] +TRACE | | | | Eval possibilities = {"arg", "env"} +TRACE | | | | Eval cmd_obj.Cmd = possibilities[l] +TRACE | | | | Fail cmd_obj.Cmd = possibilities[l] +TRACE | | | | Redo possibilities = {"arg", "env"} +TRACE | | | | Redo cmd_obj = input.stages[j][k] +TRACE | | | | Eval possibilities = {"arg", "env"} +TRACE | | | | Eval cmd_obj.Cmd = possibilities[l] +TRACE | | | | Fail cmd_obj.Cmd = possibilities[l] +TRACE | | | | Redo possibilities = {"arg", "env"} +TRACE | | | | Redo cmd_obj = input.stages[j][k] +TRACE | | | | Redo name = from.Value[0] +TRACE | | | | Redo from = data.lib.docker.from[i] +TRACE | | | Eval __local752__ = output.img +TRACE | | | Eval neq(__local752__, "scratch") +TRACE | | | Eval __local753__ = output.img +TRACE | | | Eval not data.builtin.dockerfile.DS001.is_alias(__local753__) +TRACE | | | Enter data.builtin.dockerfile.DS001.is_alias(__local753__) +TRACE | | | | Eval data.builtin.dockerfile.DS001.is_alias(__local753__) +TRACE | | | | Index data.builtin.dockerfile.DS001.is_alias (matched 1 rule, early exit) +TRACE | | | | Enter data.builtin.dockerfile.DS001.is_alias +TRACE | | | | | Eval img = data.builtin.dockerfile.DS001.get_aliases[_] +TRACE | | | | | Index data.builtin.dockerfile.DS001.get_aliases (matched 1 rule) +TRACE | | | | | Enter data.builtin.dockerfile.DS001.get_aliases +TRACE | | | | | | Eval from_cmd = data.lib.docker.from[_] +TRACE | | | | | | Index data.lib.docker.from (matched 1 rule) +TRACE | | | | | | Eval __local749__ = from_cmd.Value +TRACE | | | | | | Eval data.builtin.dockerfile.DS001.get_alias(__local749__, __local503__) +TRACE | | | | | | Index data.builtin.dockerfile.DS001.get_alias (matched 1 rule) +TRACE | | | | | | Enter data.builtin.dockerfile.DS001.get_alias +TRACE | | | | | | | Eval __local748__ = values[i] +TRACE | | | | | | | Eval lower(__local748__, __local501__) +TRACE | | | | | | | Eval "as" = __local501__ +TRACE | | | | | | | Fail "as" = __local501__ +TRACE | | | | | | | Redo lower(__local748__, __local501__) +TRACE | | | | | | | Redo __local748__ = values[i] +TRACE | | | | | | Fail data.builtin.dockerfile.DS001.get_alias(__local749__, __local503__) +TRACE | | | | | | Redo __local749__ = from_cmd.Value +TRACE | | | | | | Redo from_cmd = data.lib.docker.from[_] +TRACE | | | | | Fail img = data.builtin.dockerfile.DS001.get_aliases[_] +TRACE | | | | Fail data.builtin.dockerfile.DS001.is_alias(__local753__) +TRACE | | | Eval output.tag = "latest" +TRACE | | | Exit data.builtin.dockerfile.DS001.fail_latest +TRACE | | Redo data.builtin.dockerfile.DS001.fail_latest +TRACE | | | Redo output.tag = "latest" +TRACE | | | Redo __local753__ = output.img +TRACE | | | Redo neq(__local752__, "scratch") +TRACE | | | Redo __local752__ = output.img +TRACE | | | Redo output = data.builtin.dockerfile.DS001.image_tags[_] +TRACE | | Eval __local754__ = output.img +TRACE | | Eval sprintf("Specify a tag in the 'FROM' statement for image '%s'", [__local754__], __local509__) +TRACE | | Eval msg = __local509__ +TRACE | | Eval __local755__ = output.cmd +TRACE | | Eval data.lib.docker.result(msg, __local755__, __local510__) +TRACE | | Index data.lib.docker.result (matched 1 rule) +TRACE | | Enter data.lib.docker.result +TRACE | | | Eval object.get(cmd, "EndLine", 0, __local470__) +TRACE | | | Eval object.get(cmd, "Path", "", __local471__) +TRACE | | | Eval object.get(cmd, "StartLine", 0, __local472__) +TRACE | | | Eval result = {"endline": __local470__, "filepath": __local471__, "msg": msg, "startline": __local472__} +TRACE | | | Exit data.lib.docker.result +TRACE | | Eval res = __local510__ +TRACE | | Exit data.builtin.dockerfile.DS001.deny +TRACE | Redo data.builtin.dockerfile.DS001.deny +TRACE | | Redo res = __local510__ +TRACE | | Redo data.lib.docker.result(msg, __local755__, __local510__) +TRACE | | Redo data.lib.docker.result +TRACE | | | Redo result = {"endline": __local470__, "filepath": __local471__, "msg": msg, "startline": __local472__} +TRACE | | | Redo object.get(cmd, "StartLine", 0, __local472__) +TRACE | | | Redo object.get(cmd, "Path", "", __local471__) +TRACE | | | Redo object.get(cmd, "EndLine", 0, __local470__) +TRACE | | Redo __local755__ = output.cmd +TRACE | | Redo msg = __local509__ +TRACE | | Redo sprintf("Specify a tag in the 'FROM' statement for image '%s'", [__local754__], __local509__) +TRACE | | Redo __local754__ = output.img +TRACE | | Redo output = data.builtin.dockerfile.DS001.fail_latest[_] +TRACE | Exit data.builtin.dockerfile.DS001.deny = _ +TRACE Redo data.builtin.dockerfile.DS001.deny = _ +TRACE | Redo data.builtin.dockerfile.DS001.deny = _ +TRACE + + +ID: DS002 +File: Dockerfile +Namespace: builtin.dockerfile.DS002 +Query: data.builtin.dockerfile.DS002.deny +Message: Last USER command in Dockerfile should not be 'root' +TRACE Enter data.builtin.dockerfile.DS002.deny = _ +TRACE | Eval data.builtin.dockerfile.DS002.deny = _ +TRACE | Index data.builtin.dockerfile.DS002.deny (matched 2 rules) +TRACE | Enter data.builtin.dockerfile.DS002.deny +TRACE | | Eval data.builtin.dockerfile.DS002.fail_user_count +TRACE | | Index data.builtin.dockerfile.DS002.fail_user_count (matched 1 rule, early exit) +TRACE | | Enter data.builtin.dockerfile.DS002.fail_user_count +TRACE | | | Eval __local771__ = data.builtin.dockerfile.DS002.get_user +TRACE | | | Index data.builtin.dockerfile.DS002.get_user (matched 1 rule) +TRACE | | | Enter data.builtin.dockerfile.DS002.get_user +TRACE | | | | Eval user = data.lib.docker.user[_] +TRACE | | | | Index data.lib.docker.user (matched 1 rule) +TRACE | | | | Enter data.lib.docker.user +TRACE | | | | | Eval instruction = input.stages[_][_] +TRACE | | | | | Eval instruction.Cmd = "user" +TRACE | | | | | Fail instruction.Cmd = "user" +TRACE | | | | | Redo instruction = input.stages[_][_] +TRACE | | | | | Eval instruction.Cmd = "user" +TRACE | | | | | Exit data.lib.docker.user +TRACE | | | | Redo data.lib.docker.user +TRACE | | | | | Redo instruction.Cmd = "user" +TRACE | | | | | Redo instruction = input.stages[_][_] +TRACE | | | | | Eval instruction.Cmd = "user" +TRACE | | | | | Fail instruction.Cmd = "user" +TRACE | | | | | Redo instruction = input.stages[_][_] +TRACE | | | | Eval username = user.Value[_] +TRACE | | | | Exit data.builtin.dockerfile.DS002.get_user +TRACE | | | Redo data.builtin.dockerfile.DS002.get_user +TRACE | | | | Redo username = user.Value[_] +TRACE | | | | Redo user = data.lib.docker.user[_] +TRACE | | | Eval count(__local771__, __local536__) +TRACE | | | Eval lt(__local536__, 1) +TRACE | | | Fail lt(__local536__, 1) +TRACE | | | Redo count(__local771__, __local536__) +TRACE | | | Redo __local771__ = data.builtin.dockerfile.DS002.get_user +TRACE | | Fail data.builtin.dockerfile.DS002.fail_user_count +TRACE | Enter data.builtin.dockerfile.DS002.deny +TRACE | | Eval cmd = data.builtin.dockerfile.DS002.fail_last_user_root[_] +TRACE | | Index data.builtin.dockerfile.DS002.fail_last_user_root (matched 1 rule) +TRACE | | Enter data.builtin.dockerfile.DS002.fail_last_user_root +TRACE | | | Eval stage_users = data.lib.docker.stage_user[_] +TRACE | | | Index data.lib.docker.stage_user (matched 1 rule) +TRACE | | | Enter data.lib.docker.stage_user +TRACE | | | | Eval stage = input.stages[stage_name] +TRACE | | | | Eval users = [cmd | cmd = stage[_]; cmd.Cmd = "user"] +TRACE | | | | Enter cmd = stage[_]; cmd.Cmd = "user" +TRACE | | | | | Eval cmd = stage[_] +TRACE | | | | | Eval cmd.Cmd = "user" +TRACE | | | | | Fail cmd.Cmd = "user" +TRACE | | | | | Redo cmd = stage[_] +TRACE | | | | | Eval cmd.Cmd = "user" +TRACE | | | | | Exit cmd = stage[_]; cmd.Cmd = "user" +TRACE | | | | Redo cmd = stage[_]; cmd.Cmd = "user" +TRACE | | | | | Redo cmd.Cmd = "user" +TRACE | | | | | Redo cmd = stage[_] +TRACE | | | | | Eval cmd.Cmd = "user" +TRACE | | | | | Fail cmd.Cmd = "user" +TRACE | | | | | Redo cmd = stage[_] +TRACE | | | | Exit data.lib.docker.stage_user +TRACE | | | Redo data.lib.docker.stage_user +TRACE | | | | Redo users = [cmd | cmd = stage[_]; cmd.Cmd = "user"] +TRACE | | | | Redo stage = input.stages[stage_name] +TRACE | | | Eval count(stage_users, __local537__) +TRACE | | | Eval len = __local537__ +TRACE | | | Eval minus(len, 1, __local538__) +TRACE | | | Eval last = stage_users[__local538__] +TRACE | | | Eval user = last.Value[0] +TRACE | | | Eval user = "root" +TRACE | | | Exit data.builtin.dockerfile.DS002.fail_last_user_root +TRACE | | Redo data.builtin.dockerfile.DS002.fail_last_user_root +TRACE | | | Redo user = "root" +TRACE | | | Redo user = last.Value[0] +TRACE | | | Redo last = stage_users[__local538__] +TRACE | | | Redo minus(len, 1, __local538__) +TRACE | | | Redo len = __local537__ +TRACE | | | Redo count(stage_users, __local537__) +TRACE | | | Redo stage_users = data.lib.docker.stage_user[_] +TRACE | | Eval msg = "Last USER command in Dockerfile should not be 'root'" +TRACE | | Eval data.lib.docker.result(msg, cmd, __local540__) +TRACE | | Index data.lib.docker.result (matched 1 rule) +TRACE | | Enter data.lib.docker.result +TRACE | | | Eval object.get(cmd, "EndLine", 0, __local470__) +TRACE | | | Eval object.get(cmd, "Path", "", __local471__) +TRACE | | | Eval object.get(cmd, "StartLine", 0, __local472__) +TRACE | | | Eval result = {"endline": __local470__, "filepath": __local471__, "msg": msg, "startline": __local472__} +TRACE | | | Exit data.lib.docker.result +TRACE | | Eval res = __local540__ +TRACE | | Exit data.builtin.dockerfile.DS002.deny +TRACE | Redo data.builtin.dockerfile.DS002.deny +TRACE | | Redo res = __local540__ +TRACE | | Redo data.lib.docker.result(msg, cmd, __local540__) +TRACE | | Redo data.lib.docker.result +TRACE | | | Redo result = {"endline": __local470__, "filepath": __local471__, "msg": msg, "startline": __local472__} +TRACE | | | Redo object.get(cmd, "StartLine", 0, __local472__) +TRACE | | | Redo object.get(cmd, "Path", "", __local471__) +TRACE | | | Redo object.get(cmd, "EndLine", 0, __local470__) +TRACE | | Redo msg = "Last USER command in Dockerfile should not be 'root'" +TRACE | | Redo cmd = data.builtin.dockerfile.DS002.fail_last_user_root[_] +TRACE | Exit data.builtin.dockerfile.DS002.deny = _ +TRACE Redo data.builtin.dockerfile.DS002.deny = _ +TRACE | Redo data.builtin.dockerfile.DS002.deny = _ +TRACE +``` \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/custom/index.md b/docs/docs/scanner/misconfiguration/custom/index.md new file mode 100644 index 000000000000..b1c16219e8f8 --- /dev/null +++ b/docs/docs/scanner/misconfiguration/custom/index.md @@ -0,0 +1,204 @@ +# Custom Policies + +## Overview +You can write custom policies in [Rego][rego]. +Once you finish writing custom policies, you can pass the policy files or the directory where those policies are stored with `--policy` option. + +``` bash +trivy conf --policy /path/to/policy.rego --policy /path/to/custom_policies --namespaces user /path/to/config_dir +``` + +As for `--namespaces` option, the detail is described as below. + +### File formats +If a file name matches the following file patterns, Trivy will parse the file and pass it as input to your Rego policy. + +| File format | File pattern | +|---------------|-----------------------------------------------------------| +| JSON | `*.json` | +| YAML | `*.yaml` and `*.yml` | +| Dockerfile | `Dockerfile`, `Dockerfile.*`, and `*.Dockerfile` | +| Containerfile | `Containerfile`, `Containerfile.*`, and `*.Containerfile` | +| Terraform | `*.tf` and `*.tf.json` | + +### Configuration languages +In the above general file formats, Trivy automatically identifies the following types of configuration files: + +- CloudFormation (JSON/YAML) +- Kubernetes (JSON/YAML) +- Helm (YAML) +- Terraform Plan (JSON/Snapshot) + +This is useful for filtering inputs, as described below. + +## Rego format +A single package must contain only one policy. + +!!!example + ``` rego + # METADATA + # title: Deployment not allowed + # description: Deployments are not allowed because of some reasons. + # schemas: + # - input: schema["kubernetes"] + # custom: + # id: ID001 + # severity: LOW + # input: + # selector: + # - type: kubernetes + package user.kubernetes.ID001 + + deny[res] { + input.kind == "Deployment" + msg := sprintf("Found deployment '%s' but deployments are not allowed", [input.metadata.name]) + res := result.new(msg, input.kind) + } + ``` + +In this example, ID001 "Deployment not allowed" is defined under `user.kubernetes.ID001`. +If you add a new custom policy, it must be defined under a new package like `user.kubernetes.ID002`. + +### Policy structure + +`# METADATA` (optional) +: - SHOULD be defined for clarity since these values will be displayed in the scan results + - `custom.input` SHOULD be set to indicate the input type the policy should be applied to. See [list of available types](https://github.com/aquasecurity/defsec/blob/418759b4dc97af25f30f32e0bd365be7984003a1/pkg/types/sources.go) + +`package` (required) +: - MUST follow the Rego's [specification][package] + - MUST be unique per policy + - SHOULD include policy id for uniqueness + - MAY include the group name such as `kubernetes` for clarity + - Group name has no effect on policy evaluation + +`deny` (required) +: - SHOULD be `deny` or start with `deny_` + - Although `warn`, `warn_*`, `violation`, `violation_` also work for compatibility, `deny` is recommended as severity can be defined in `__rego_metadata__`. + - SHOULD return ONE OF: + - The result of a call to `result.new(msg, cause)`. The `msg` is a `string` describing the issue occurrence, and the `cause` is the property/object where the issue occurred. Providing this allows Trivy to ascertain line numbers and highlight code in the output. + - A `string` denoting the detected issue + - Although `object` with `msg` field is accepted, other fields are dropped and `string` is recommended if `result.new()` is not utilised. + - e.g. `{"msg": "deny message", "details": "something"}` + + +### Package +A package name must be unique per policy. + +!!!example + ``` rego + package user.kubernetes.ID001 + ``` + +By default, only `builtin.*` packages will be evaluated. +If you define custom packages, you have to specify the package prefix via `--namespaces` option. + +``` bash +trivy conf --policy /path/to/custom_policies --namespaces user /path/to/config_dir +``` + +In this case, `user.*` will be evaluated. +Any package prefixes such as `main` and `user` are allowed. + +### Metadata +Metadata helps enrich Trivy's scan results with useful information. + +The annotation format is described in the [OPA documentation](https://www.openpolicyagent.org/docs/latest/annotations/). + +Trivy supports extra fields in the `custom` section as described below. + +!!!example + ``` rego + # METADATA + # title: Deployment not allowed + # description: Deployments are not allowed because of some reasons. + # custom: + # id: ID001 + # severity: LOW + # input: + # selector: + # - type: kubernetes + ``` + +All fields are optional. The `schemas` field should be used to enable policy validation using a built-in schema. The +schema that will be used is based on the input document type. It is recommended to use this to ensure your policies are +correct and do not reference incorrect properties/values. + +| Field name | Allowed values | Default value | In table | In JSON | +|----------------------------|-------------------------------------------------------------------|:----------------------------:|:----------------:|:----------------:| +| title | Any characters | N/A | :material-check: | :material-check: | +| description | Any characters | | :material-close: | :material-check: | +| schemas.input | `schema["kubernetes"]`, `schema["dockerfile"]`, `schema["cloud"]` | (applied to all input types) | :material-close: | :material-close: | +| custom.id | Any characters | N/A | :material-check: | :material-check: | +| custom.severity | `LOW`, `MEDIUM`, `HIGH`, `CRITICAL` | UNKNOWN | :material-check: | :material-check: | +| custom.recommended_actions | Any characters | | :material-close: | :material-check: | +| custom.input.selector.type | Any item(s) in [this list][source-types] | | :material-close: | :material-check: | +| url | Any characters | | :material-close: | :material-check: | + + +Some fields are displayed in scan results. + +``` bash +k.yaml (kubernetes) +─────────────────── + +Tests: 32 (SUCCESSES: 31, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + +LOW: Found deployment 'my-deployment' but deployments are not allowed +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Deployments are not allowed because of some reasons. +──────────────────────────────────────────────────────────────────────── + k.yaml:1-2 +──────────────────────────────────────────────────────────────────────── + 1 ┌ apiVersion: v1 + 2 â”” kind: Deployment +──────────────────────────────────────────────────────────────────────── +``` + +### Input +You can specify input format via the `custom.input` annotation. + +!!!example + ``` rego + # METADATA + # custom: + # input: + # combine: false + # selector: + # - type: kubernetes + ``` + +`combine` (boolean) +: The details are [here](combine.md). + +`selector` (array) +: This option filters the input by file format or configuration language. + In the above example, Trivy passes only Kubernetes files to this policy. + Even if a Dockerfile exists in the specified directory, it will not be passed to the policy as input. + + Possible values for input types are: + + - `dockerfile` (Dockerfile) + - `kubernetes` (Kubernetes YAML/JSON) + - `rbac` (Kubernetes RBAC YAML/JSON) + - `cloud` (Cloud format, as defined by defsec - this is used for Terraform, CloudFormation, and Cloud/AWS scanning) + - `yaml` (Generic YAML) + - `json` (Generic JSON) + - `toml` (Generic TOML) + + When configuration languages such as Kubernetes are not identified, file formats such as JSON will be used as `type`. + When a configuration language is identified, it will overwrite `type`. + + !!! example + `pod.yaml` including Kubernetes Pod will be handled as `kubernetes`, not `yaml`. + `type` is overwritten by `kubernetes` from `yaml`. + + `type` accepts `kubernetes`, `dockerfile`, `cloudformation`, `terraform`, `terraformplan`, `json`, or `yaml`. + +### Schemas +See [here](schema.md) for the detail. + +[rego]: https://www.openpolicyagent.org/docs/latest/policy-language/ +[package]: https://www.openpolicyagent.org/docs/latest/policy-language/#packages +[source-types]: https://github.com/aquasecurity/defsec/blob/418759b4dc97af25f30f32e0bd365be7984003a1/pkg/types/sources.go diff --git a/docs/docs/scanner/misconfiguration/custom/schema.md b/docs/docs/scanner/misconfiguration/custom/schema.md new file mode 100644 index 000000000000..8791d1a22752 --- /dev/null +++ b/docs/docs/scanner/misconfiguration/custom/schema.md @@ -0,0 +1,92 @@ +# Input Schema + +## Overview +Policies can be defined with custom schemas that allow inputs to be verified against them. Adding a policy schema +enables Trivy to show more detailed error messages when an invalid input is encountered. + +In Trivy we have been able to define a schema for a [Dockerfile](https://github.com/aquasecurity/trivy-iac/blob/main/pkg/rego/schemas/dockerfile.json) +Without input schemas, a policy would be as follows: + +!!! example + ``` + # METADATA + package mypackage + + deny { + input.evil == "foo bar" + } + ``` + +If this policy is run against offending Dockerfile(s), there will not be any issues as the policy will fail to evaluate. +Although the policy's failure to evaluate is legitimate, this should not result in a positive result for the scan. + +For instance if we have a policy that checks for misconfigurations in a `Dockerfile`, we could define the +schema as such + +!!! example + ``` + # METADATA + # schemas: + # - input: schema["dockerfile"] + package mypackage + + deny { + input.evil == "foo bar" + } + ``` + +Here `input: schema["dockerfile"]` points to a schema that expects a valid `Dockerfile` as input. An example of this +can be found [here](https://github.com/aquasecurity/defsec/blob/master/pkg/rego/schemas/dockerfile.json) + +Now if this policy is evaluated against, a more descriptive error will be available to help fix the problem. + +```bash +1 error occurred: testpolicy.rego:8: rego_type_error: undefined ref: input.evil + input.evil + ^ + have: "evil" + want (one of): ["Stages"] +``` + +Currently, out of the box the following schemas are supported natively: + +1. [Docker](https://github.com/aquasecurity/trivy-iac/blob/main/pkg/rego/schemas/dockerfile.json) +2. [Kubernetes](https://github.com/aquasecurity/trivy-iac/blob/main/pkg/rego/schemas/kubernetes.json) +3. [Cloud](https://github.com/aquasecurity/trivy-iac/blob/main/pkg/rego/schemas/cloud.json) + + +## Custom Policies with Custom Schemas + +You can also bring a custom policy that defines one or more custom schema. + +!!! example + ``` + # METADATA + # schemas: + # - input: schema["fooschema"] + # - input: schema["barschema"] + package mypackage + + deny { + input.evil == "foo bar" + } + ``` + +The policies can be placed in a structure as follows + +!!! example + ``` + /Users/user/my-custom-policies + ├── my_policy.rego + └── schemas + └── fooschema.json + └── barschema.json + ``` + +To use such a policy with Trivy, use the `--config-policy` flag that points to the policy file or to the directory where the schemas and policies are contained. + +```bash +$ trivy --config-policy=/Users/user/my-custom-policies +``` + +For more details on how to define schemas within Rego policies, please see the [OPA guide](https://www.openpolicyagent.org/docs/latest/schemas/#schema-annotations) that describes it in more detail. \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/custom/selectors.md b/docs/docs/scanner/misconfiguration/custom/selectors.md new file mode 100644 index 000000000000..3def3d8e9a20 --- /dev/null +++ b/docs/docs/scanner/misconfiguration/custom/selectors.md @@ -0,0 +1,51 @@ +# Input Selectors + +## Overview +Sometimes you might want to limit a certain policy to only be run on certain resources. This can be +achieved with input selectors. + +## Use case +For instance, if you have a custom policy that you only want to be evaluated if a certain resource type is being scanned. +In such a case you could utilize input selectors to limit its evaluation on only those resources. + +!!! example + ``` + # METADATA + # title: "RDS Publicly Accessible" + # description: "Ensures RDS instances are not launched into the public cloud." + # custom: + # input: + # selector: + # - type: cloud + # subtypes: + # - provider: aws + # service: rds + package builtin.aws.rds.aws0999 + + deny[res] { + instance := input.aws.rds.instances[_] + instance.publicaccess.value + res := result.new("Instance has Public Access enabled", instance.publicaccess) + ``` + +Observe the following `subtypes` defined: +```yaml + # subtypes: + # - provider: aws + # service: rds +``` + +They will ensure that the policy is only run when the input to such a policy contains an `RDS` instance. + +## Enabling selectors and subtypes +Currently, the following are supported: + +| Selector | Subtype fields required | Example | +|--------------------------|-------------------------|---------------------------------| +| Cloud (AWS, Azure, etc.) | `provider`, `service` | `provider: aws`, `service: rds` | +| Kubernetes | | `type: kubernetes` | +| Dockerfile | | `type: dockerfile` | + + +## Default behaviour +If no subtypes or selectors are specified, the policy will be evaluated regardless of input. \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/custom/testing.md b/docs/docs/scanner/misconfiguration/custom/testing.md new file mode 100644 index 000000000000..e09df1a35bc0 --- /dev/null +++ b/docs/docs/scanner/misconfiguration/custom/testing.md @@ -0,0 +1,90 @@ +# Testing +It is highly recommended to write tests for your custom policies. + +## Rego testing +To help you verify the correctness of your custom policies, OPA gives you a framework that you can use to write tests for your policies. +By writing tests for your custom policies you can speed up the development process of new rules and reduce the amount of time it takes to modify rules as requirements evolve. + +For more details, see [Policy Testing][opa-testing]. + +!!! example + ``` + package user.dockerfile.ID002 + + test_add_denied { + r := deny with input as {"stages": {"alpine:3.13": [ + {"Cmd": "add", "Value": ["/target/resources.tar.gz", "resources.jar"]}, + {"Cmd": "add", "Value": ["/target/app.jar", "app.jar"]}, + ]}} + + count(r) == 1 + r[_] == "Consider using 'COPY /target/app.jar app.jar' command instead of 'ADD /target/app.jar app.jar'" + } + ``` + +To write tests for custom policies, you can refer to existing tests under [trivy-policies][trivy-policies]. + +## Go testing +[Fanal][fanal] which is a core library of Trivy can be imported as a Go library. +You can scan config files in Go and test your custom policies using Go's testing methods, such as [table-driven tests][table]. +This allows you to use the actual configuration file as input, making it easy to prepare test data and ensure that your custom policies work in practice. + +In particular, Dockerfile and HCL need to be converted to structural data as input, which may be different from the expected input format. + +!!! tip + We recommend writing OPA and Go tests both since they have different roles, like unit tests and integration tests. + +The following example stores allowed and denied configuration files in a directory. +`Successes` contains the result of successes, and `Failures` contains the result of failures. + +``` go +{ + name: "disallowed ports", + input: "configs/", + fields: fields{ + policyPaths: []string{"policy"}, + dataPaths: []string{"data"}, + namespaces: []string{"user"}, + }, + want: []types.Misconfiguration{ + { + FileType: types.Dockerfile, + FilePath: "Dockerfile.allowed", + Successes: types.MisconfResults{ + { + Namespace: "user.dockerfile.ID002", + PolicyMetadata: types.PolicyMetadata{ + ID: "ID002", + Type: "Docker Custom Check", + Title: "Disallowed ports exposed", + Severity: "HIGH", + }, + }, + }, + }, + { + FileType: types.Dockerfile, + FilePath: "Dockerfile.denied", + Failures: types.MisconfResults{ + { + Namespace: "user.dockerfile.ID002", + Message: "Port 23 should not be exposed", + PolicyMetadata: types.PolicyMetadata{ + ID: "ID002", + Type: "Docker Custom Check", + Title: "Disallowed ports exposed", + Severity: "HIGH", + }, + }, + }, + }, + }, +}, +``` + +`Dockerfile.allowed` has one successful result in `Successes`, while `Dockerfile.denied` has one failure result in `Failures`. + +[opa-testing]: https://www.openpolicyagent.org/docs/latest/policy-testing/ +[defsec]: https://github.com/aquasecurity/trivy-policies/tree/main +[table]: https://github.com/golang/go/wiki/TableDrivenTests +[fanal]: https://github.com/aquasecurity/fanal \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/index.md b/docs/docs/scanner/misconfiguration/index.md new file mode 100644 index 000000000000..b243d3e8dc17 --- /dev/null +++ b/docs/docs/scanner/misconfiguration/index.md @@ -0,0 +1,561 @@ +# Misconfiguration Scanning +Trivy provides built-in policies to detect configuration issues in popular Infrastructure as Code files, such as: Docker, Kubernetes, Terraform, CloudFormation, and more. +In addition to built-in policies, you can write your own custom policies, as you can see [here][custom]. + +## Quick start + +Simply specify a directory containing IaC files such as Terraform, CloudFormation, Azure ARM templates, Helm Charts and Dockerfile. + +```bash +$ trivy config [YOUR_IaC_DIRECTORY] +``` + + +!!! example + ``` + $ ls build/ + Dockerfile + $ trivy config ./build + 2022-05-16T13:29:29.952+0100 INFO Detected config files: 1 + + Dockerfile (dockerfile) + ======================= + Tests: 23 (SUCCESSES: 22, FAILURES: 1, EXCEPTIONS: 0) + Failures: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0) + + MEDIUM: Specify a tag in the 'FROM' statement for image 'alpine' + â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• + When using a 'FROM' statement you should use a specific tag to avoid uncontrolled behavior when the image is updated. + + See https://avd.aquasec.com/misconfig/ds001 + ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Dockerfile:1 + ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 1 [ FROM alpine:latest + ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + ``` + +You can also enable misconfiguration detection in container image, filesystem and git repository scanning via `--scanners misconfig`. + +```bash +$ trivy image --scanners misconfig IMAGE_NAME +``` + +```bash +$ trivy fs --scanners misconfig /path/to/dir +``` + +!!! note + Misconfiguration detection is not enabled by default in `image`, `fs` and `repo` subcommands. + +Unlike the `config` subcommand, `image`, `fs` and `repo` subcommands can also scan for vulnerabilities and secrets at the same time. +You can specify `--scanners vuln,misconfig,secret` to enable vulnerability and secret detection as well as misconfiguration detection. + + +!!! example + ``` bash + $ ls myapp/ + Dockerfile Pipfile.lock + $ trivy fs --scanners vuln,misconfig,secret --severity HIGH,CRITICAL myapp/ + 2022-05-16T13:42:21.440+0100 INFO Number of language-specific files: 1 + 2022-05-16T13:42:21.440+0100 INFO Detecting pipenv vulnerabilities... + 2022-05-16T13:42:21.440+0100 INFO Detected config files: 1 + + Pipfile.lock (pipenv) + ===================== + Total: 1 (HIGH: 1, CRITICAL: 0) + + ┌──────────┬────────────────┬──────────┬───────────────────┬───────────────┬───────────────────────────────────────────────────────────┠+ │ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ + ├──────────┼────────────────┼──────────┼───────────────────┼───────────────┼───────────────────────────────────────────────────────────┤ + │ httplib2 │ CVE-2021-21240 │ HIGH │ 0.12.1 │ 0.19.0 │ python-httplib2: Regular expression denial of service via │ + │ │ │ │ │ │ malicious header │ + │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-21240 │ + └──────────┴────────────────┴──────────┴───────────────────┴───────────────┴───────────────────────────────────────────────────────────┘ + + Dockerfile (dockerfile) + ======================= + Tests: 17 (SUCCESSES: 16, FAILURES: 1, EXCEPTIONS: 0) + Failures: 1 (HIGH: 1, CRITICAL: 0) + + HIGH: Last USER command in Dockerfile should not be 'root' + â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• + Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile. + + See https://avd.aquasec.com/misconfig/ds002 + ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Dockerfile:3 + ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 3 [ USER root + ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + ``` + +In the above example, Trivy detected vulnerabilities of Python dependencies and misconfigurations in Dockerfile. + +## Type detection +The specified directory can contain mixed types of IaC files. +Trivy automatically detects config types and applies relevant policies. + +For example, the following example holds IaC files for Terraform, CloudFormation, Kubernetes, Helm Charts, and Dockerfile in the same directory. + +``` bash +$ ls iac/ +Dockerfile deployment.yaml main.tf mysql-8.8.26.tar +$ trivy conf --severity HIGH,CRITICAL ./iac +``` + +
+Result + +``` +2022-06-06T11:01:21.142+0100 INFO Detected config files: 8 + +Dockerfile (dockerfile) + +Tests: 21 (SUCCESSES: 20, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (MEDIUM: 0, HIGH: 1, CRITICAL: 0) + +HIGH: Specify at least 1 USER command in Dockerfile with non-root user as argument +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile. + +See https://avd.aquasec.com/misconfig/ds002 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + + +deployment.yaml (kubernetes) + +Tests: 20 (SUCCESSES: 15, FAILURES: 5, EXCEPTIONS: 0) +Failures: 5 (MEDIUM: 4, HIGH: 1, CRITICAL: 0) + +MEDIUM: Container 'hello-kubernetes' of Deployment 'hello-kubernetes' should set 'securityContext.allowPrivilegeEscalation' to false +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node. + +See https://avd.aquasec.com/misconfig/ksv001 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + deployment.yaml:16-19 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 16 ┌ - name: hello-kubernetes + 17 │ image: hello-kubernetes:1.5 + 18 │ ports: + 19 â”” - containerPort: 8080 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + +HIGH: Deployment 'hello-kubernetes' should not specify '/var/run/docker.socker' in 'spec.template.volumes.hostPath.path' +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Mounting docker.sock from the host can give the container full root access to the host. + +See https://avd.aquasec.com/misconfig/ksv006 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + deployment.yaml:6-29 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 6 ┌ replicas: 3 + 7 │ selector: + 8 │ matchLabels: + 9 │ app: hello-kubernetes + 10 │ template: + 11 │ metadata: + 12 │ labels: + 13 │ app: hello-kubernetes + 14 â”” spec: + .. +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + +MEDIUM: Container 'hello-kubernetes' of Deployment 'hello-kubernetes' should set 'securityContext.runAsNonRoot' to true +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +'runAsNonRoot' forces the running image to run as a non-root user to ensure least privileges. + +See https://avd.aquasec.com/misconfig/ksv012 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + deployment.yaml:16-19 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 16 ┌ - name: hello-kubernetes + 17 │ image: hello-kubernetes:1.5 + 18 │ ports: + 19 â”” - containerPort: 8080 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + +MEDIUM: Deployment 'hello-kubernetes' should not set 'spec.template.volumes.hostPath' +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +HostPath volumes must be forbidden. + +See https://avd.aquasec.com/misconfig/ksv023 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + deployment.yaml:6-29 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 6 ┌ replicas: 3 + 7 │ selector: + 8 │ matchLabels: + 9 │ app: hello-kubernetes + 10 │ template: + 11 │ metadata: + 12 │ labels: + 13 │ app: hello-kubernetes + 14 â”” spec: + .. +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + +MEDIUM: Deployment 'hello-kubernetes' should set 'securityContext.sysctl' to the allowed values +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Sysctls can disable security mechanisms or affect all containers on a host, and should be disallowed except for an allowed 'safe' subset. A sysctl is considered safe if it is namespaced in the container or the Pod, and it is isolated from other Pods or processes on the same Node. + +See https://avd.aquasec.com/misconfig/ksv026 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + deployment.yaml:6-29 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 6 ┌ replicas: 3 + 7 │ selector: + 8 │ matchLabels: + 9 │ app: hello-kubernetes + 10 │ template: + 11 │ metadata: + 12 │ labels: + 13 │ app: hello-kubernetes + 14 â”” spec: + .. +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + + +mysql-8.8.26.tar:templates/primary/statefulset.yaml (helm) + +Tests: 20 (SUCCESSES: 18, FAILURES: 2, EXCEPTIONS: 0) +Failures: 2 (MEDIUM: 2, HIGH: 0, CRITICAL: 0) + +MEDIUM: Container 'mysql' of StatefulSet 'mysql' should set 'securityContext.allowPrivilegeEscalation' to false +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node. + +See https://avd.aquasec.com/misconfig/ksv001 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + mysql-8.8.26.tar:templates/primary/statefulset.yaml:56-130 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 56 ┌ - name: mysql + 57 │ image: docker.io/bitnami/mysql:8.0.28-debian-10-r23 + 58 │ imagePullPolicy: "IfNotPresent" + 59 │ securityContext: + 60 │ runAsUser: 1001 + 61 │ env: + 62 │ - name: BITNAMI_DEBUG + 63 │ value: "false" + 64 â”” - name: MYSQL_ROOT_PASSWORD + .. +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + +MEDIUM: Container 'mysql' of StatefulSet 'mysql' should set 'securityContext.runAsNonRoot' to true +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +'runAsNonRoot' forces the running image to run as a non-root user to ensure least privileges. + +See https://avd.aquasec.com/misconfig/ksv012 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + mysql-8.8.26.tar:templates/primary/statefulset.yaml:56-130 +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 56 ┌ - name: mysql + 57 │ image: docker.io/bitnami/mysql:8.0.28-debian-10-r23 + 58 │ imagePullPolicy: "IfNotPresent" + 59 │ securityContext: + 60 │ runAsUser: 1001 + 61 │ env: + 62 │ - name: BITNAMI_DEBUG + 63 │ value: "false" + 64 â”” - name: MYSQL_ROOT_PASSWORD + .. +─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + +``` + +
+ +You can see the config type next to each file name. + +!!! example +``` bash +Dockerfile (dockerfile) +======================= +Tests: 23 (SUCCESSES: 22, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (HIGH: 1, CRITICAL: 0) + +... + +deployment.yaml (kubernetes) +============================ +Tests: 28 (SUCCESSES: 15, FAILURES: 13, EXCEPTIONS: 0) +Failures: 13 (MEDIUM: 4, HIGH: 1, CRITICAL: 0) + +... + +main.tf (terraform) +=================== +Tests: 23 (SUCCESSES: 14, FAILURES: 9, EXCEPTIONS: 0) +Failures: 9 (HIGH: 6, CRITICAL: 1) + +... + +bucket.yaml (cloudformation) +============================ +Tests: 9 (SUCCESSES: 3, FAILURES: 6, EXCEPTIONS: 0) +Failures: 6 (UNKNOWN: 0, LOW: 0, MEDIUM: 2, HIGH: 4, CRITICAL: 0) + +... + +mysql-8.8.26.tar:templates/primary/statefulset.yaml (helm) +========================================================== +Tests: 20 (SUCCESSES: 18, FAILURES: 2, EXCEPTIONS: 0) +Failures: 2 (MEDIUM: 2, HIGH: 0, CRITICAL: 0) +``` + +## Configuration +This section describes misconfiguration-specific configuration. +Other common options are documented [here](../../configuration/index.md). + +### Enabling a subset of misconfiguration scanners +It's possible to only enable certain misconfiguration scanners if you prefer. +You can do so by passing the `--misconfig-scanners` option. +This flag takes a comma-separated list of configuration scanner types. + +```bash +trivy config --misconfig-scanners=terraform,dockerfile . +``` + +Will only scan for misconfigurations that pertain to Terraform and Dockerfiles. + +### Passing custom policies +You can pass policy files or directories including your custom policies through `--policy` option. +This can be repeated for specifying multiple files or directories. + +```bash +cd examplex/misconf/ +trivy conf --policy custom-policy/policy --policy combine/policy --policy policy.rego --namespaces user misconf/mixed +``` + +For more details, see [Custom Policies](./custom/index.md). + +!!! tip +You also need to specify `--namespaces` option. + +### Passing custom data +You can pass directories including your custom data through `--data` option. +This can be repeated for specifying multiple directories. + +```bash +cd examples/misconf/custom-data +trivy conf --policy ./policy --data ./data --namespaces user ./configs +``` + +For more details, see [Custom Data](./custom/data.md). + +### Passing namespaces +By default, Trivy evaluates policies defined in `builtin.*`. +If you want to evaluate custom policies in other packages, you have to specify package prefixes through `--namespaces` option. +This can be repeated for specifying multiple packages. + +``` bash +trivy conf --policy ./policy --namespaces main --namespaces user ./configs +``` + +### Private terraform registries +Trivy can download terraform code from private registries. +To pass credentials you must use the `TF_TOKEN_` environment variables. +You cannot use a `.terraformrc` or `terraform.rc` file, these are not supported by trivy yet. + +From the terraform [docs](https://developer.hashicorp.com/terraform/cli/config/config-file#environment-variable-credentials): + +> Environment variable names should have the prefix TF_TOKEN_ added to the domain name, with periods encoded as underscores. +> For example, the value of a variable named `TF_TOKEN_app_terraform_io` will be used as a bearer authorization token when the CLI makes service requests to the hostname `app.terraform.io`. +> +> You must convert domain names containing non-ASCII characters to their punycode equivalent with an ACE prefix. +> For example, token credentials for `例ãˆã°.com` must be set in a variable called `TF_TOKEN_xn--r8j3dr99h_com`. +> +> Hyphens are also valid within host names but usually invalid as variable names and may be encoded as double underscores. +> For example, you can set a token for the domain name café.fr as TF_TOKEN_xn--caf-dma_fr or TF_TOKEN_xn____caf__dma_fr. + +If multiple variables evaluate to the same hostname, Trivy will choose the environment variable name where the dashes have not been encoded as double underscores. + + +### Skipping resources by inline comments + +Trivy supports ignoring misconfigured resources by inline comments for Terraform and CloudFormation configuration files only. + +In cases where Trivy can detect comments of a specific format immediately adjacent to resource definitions, it is possible to ignore findings from a single source of resource definition (in contrast to `.trivyignore`, which has a directory-wide scope on all of the files scanned). The format for these comments is `trivy:ignore:` immediately following the format-specific line-comment [token](https://developer.hashicorp.com/terraform/language/syntax/configuration#comments). + +The ignore rule must contain one of the possible check IDs that can be found in its metadata: ID, short code or alias. The `id` from the metadata is not case-sensitive, so you can specify, for example, `AVD-AWS-0089` or `avd-aws-0089`. + +For example, to ignore a misconfiguration ID `AVD-GCP-0051` in a Terraform HCL file: + +```terraform +#trivy:ignore:AVD-GCP-0051 +resource "google_container_cluster" "example" { + name = var.cluster_name + location = var.region +} +``` + +You can add multiple ignores on the same comment line: +```terraform +#trivy:ignore:AVD-GCP-0051 trivy:ignore:AVD-GCP-0053 +resource "google_container_cluster" "example" { + name = var.cluster_name + location = var.region +} +``` + +You can also specify a long ID, which is formed as follows: `--`. + +As an example, consider the following check metadata: + +```yaml +# custom: + # id: AVD-AWS-0089 + # avd_id: AVD-AWS-0089 + # provider: aws + # service: s3 + # severity: LOW + # short_code: enable-logging +``` + +Long ID would look like the following: `aws-s3-enable-logging`. + +Example for CloudFromation: +```yaml +AWSTemplateFormatVersion: "2010-09-09" +Resources: +#trivy:ignore:* + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: test-bucket +``` + +#### Expiration Date + +You can specify the expiration date of the ignore rule in `yyyy-mm-dd` format. This is a useful feature when you want to make sure that an ignored issue is not forgotten and worth revisiting in the future. For example: +```tf +#trivy:ignore:aws-s3-enable-logging:exp:2024-03-10 +resource "aws_s3_bucket" "example" { + bucket = "test" +} +``` + +The `aws-s3-enable-logging` check will be ignored until `2024-03-10` until the ignore rule expires. + +#### Ignoring by attributes + +You can ignore a resource by its attribute value. This is useful when using the `for-each` meta-argument. For example: + +```tf +locals { + ports = ["3306", "5432"] +} + +#trivy:ignore:aws-ec2-no-public-ingress-sgr[from_port=3306] +resource "aws_security_group_rule" "example" { + for_each = toset(local.ports) + type = "ingress" + from_port = each.key + to_port = each.key + protocol = "TCP" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = aws_security_group.example.id + source_security_group_id = aws_security_group.example.id +} +``` + +The `aws-ec2-no-public-ingress-sgr` check will be ignored only for the `aws_security_group_rule` resource with port number `5432`. It is important to note that the ignore rule should not enclose the attribute value in quotes, despite the fact that the port is represented as a string. + +If you want to ignore multiple resources on different attributes, you can specify multiple ignore rules: + +```tf +#trivy:ignore:aws-ec2-no-public-ingress-sgr[from_port=3306] +#trivy:ignore:aws-ec2-no-public-ingress-sgr[from_port=5432] +``` + +You can also ignore a resource on multiple attributes: +```tf +locals { + rules = { + first = { + port = 1000 + type = "ingress" + }, + second = { + port = 1000 + type = "egress" + } + } +} + +#trivy:ignore:aws-ec2-no-public-ingress-sgr[from_port=1000,type=egress] +resource "aws_security_group_rule" "example" { + for_each = { for k, v in local.rules : k => v } + + type = each.value.type + from_port = each.value.port + to_port = each.value.port + protocol = "TCP" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = aws_security_group.example.id + source_security_group_id = aws_security_group.example.id +} +``` + +Checks can also be ignored by nested attributes, but certain restrictions apply: + +- You cannot access an individual block using indexes, for example when working with dynamic blocks. +- Special variables like [each](https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#the-each-object) and [count](https://developer.hashicorp.com/terraform/language/meta-arguments/count#the-count-object) cannot be accessed. + +```tf +#trivy:ignore:*[logging_config.prefix=myprefix] +resource "aws_cloudfront_distribution" "example" { + logging_config { + include_cookies = false + bucket = "mylogs.s3.amazonaws.com" + prefix = "myprefix" + } +} +``` + +#### Ignoring module issues + +Issues in third-party modules cannot be ignored using the method described above, because you may not have access to modify the module source code. In such a situation you can add ignore rules above the module block, for example: + +```tf +#trivy:ignore:aws-s3-enable-logging +module "s3_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + + bucket = "my-s3-bucket" +} +``` + +An example of ignoring checks for a specific bucket in a module: +```tf +locals { + bucket = ["test1", "test2"] +} + +#trivy:ignore:*[bucket=test1] +module "s3_bucket" { + for_each = toset(local.bucket) + source = "terraform-aws-modules/s3-bucket/aws" + bucket = each.value +} +``` + +#### Support for Wildcards + +You can use wildcards in the `ws` (workspace) and `ignore` sections of the ignore rules. + +```tf +# trivy:ignore:aws-s3-*:ws:dev-* +``` + +This example ignores all checks starting with `aws-s3-` for workspaces matching the pattern `dev-*`. + +[custom]: custom/index.md diff --git a/docs/docs/scanner/misconfiguration/policy/builtin.md b/docs/docs/scanner/misconfiguration/policy/builtin.md new file mode 100644 index 000000000000..aeaa48afbb27 --- /dev/null +++ b/docs/docs/scanner/misconfiguration/policy/builtin.md @@ -0,0 +1,24 @@ +# Built-in Policies + +## Policy Sources +Built-in policies are mainly written in [Rego][rego] and Go. +Those policies are managed under [trivy-policies repository][trivy-policies]. +See [here](../../../coverage/iac/index.md) for the list of supported config types. + +For suggestions or issues regarding policy content, please open an issue under the [trivy-policies][trivy-policies] repository. + +## Policy Distribution +Trivy policies are distributed as an OPA bundle on [GitHub Container Registry][ghcr] (GHCR). +When misconfiguration detection is enabled, Trivy pulls the OPA bundle from GHCR as an OCI artifact and stores it in the cache. +Those policies are then loaded into Trivy OPA engine and used for detecting misconfigurations. +If Trivy is unable to pull down newer policies, it will use the embedded set of policies as a fallback. This is also the case in air-gap environments where `--skip-policy-update` might be passed. + +## Update Interval +Trivy checks for updates to OPA bundle on GHCR every 24 hours and pulls it if there are any updates. + +[rego]: https://www.openpolicyagent.org/docs/latest/policy-language/ + +[kubernetes-policies]: https://github.com/aquasecurity/trivy-policies/tree/main/rules/kubernetes/policies +[docker-policies]: https://github.com/aquasecurity/trivy-policies/tree/main/rules/docker/policies +[trivy-policies]: https://github.com/aquasecurity/trivy-policies +[ghcr]: https://github.com/aquasecurity/trivy-policies/pkgs/container/trivy-policies \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/policy/exceptions.md b/docs/docs/scanner/misconfiguration/policy/exceptions.md new file mode 100644 index 000000000000..9d0e109fcdd5 --- /dev/null +++ b/docs/docs/scanner/misconfiguration/policy/exceptions.md @@ -0,0 +1,98 @@ +# Exceptions +Exceptions let you specify cases where you allow policy violations. +Trivy supports two types of exceptions. + +!!! info + Exceptions can be applied to built-in policies as well as custom policies. + +## Namespace-based exceptions +There are some cases where you need to disable built-in policies partially or fully. +Namespace-based exceptions lets you rough choose which individual packages to exempt. + +To use namespace-based exceptions, create a Rego rule with the name `exception` that returns the package names to exempt. +The `exception` rule must be defined under `namespace.exceptions`. +`data.namespaces` includes all package names. + + +!!! example + ``` rego + package namespace.exceptions + + import data.namespaces + + exception[ns] { + ns := data.namespaces[_] + startswith(ns, "builtin.kubernetes") + } + ``` + +This example exempts all built-in policies for Kubernetes. + +For more details, see [an example][ns-example]. + +## Rule-based exceptions +There are some cases where you need more flexibility and granularity in defining which cases to exempt. +Rule-based exceptions lets you granularly choose which individual rules to exempt, while also declaring under which conditions to exempt them. + +To use rule-based exceptions, create a Rego rule with the name `exception` that returns the rule name suffixes to exempt, prefixed by `deny_` (for example, returning `foo` will exempt `deny_foo`). +The rule can make any other assertion, for example, on the input or data documents. +This is useful to specify the exemption for a specific case. + +Note that if you specify the empty string, the exception will match all rules named `deny`. + +``` +exception[rules] { + # Logic + + rules = ["foo","bar"] +} +``` + +The above would provide an exception from `deny_foo` and `deny_bar`. + + +!!! example + ``` + package user.kubernetes.ID100 + + __rego_metadata := { + "id": "ID100", + "title": "Deployment not allowed", + "severity": "HIGH", + "type": "Kubernetes Custom Check", + } + + deny_deployment[msg] { + input.kind == "Deployment" + msg = sprintf("Found deployment '%s' but deployments are not allowed", [name]) + } + + exception[rules] { + input.kind == "Deployment" + input.metadata.name == "allow-deployment" + + rules := ["deployment"] + } + ``` + +If you want to apply rule-based exceptions to built-in policies, you have to define the exception under the same package. + +!!! example + ``` rego + package builtin.kubernetes.KSV012 + + exception[rules] { + input.metadata.name == "can-run-as-root" + rules := [""] + } + ``` + +This exception is applied to [KSV012][ksv012] in trivy-policies. +You can get the package names in the [trivy-policies repository][trivy-policies] or the JSON output from Trivy. + +For more details, see [an example][rule-example]. + +[ns-example]: https://github.com/aquasecurity/trivy/tree/{{ git.commit }}/examples/misconf/namespace-exception +[rule-example]: https://github.com/aquasecurity/trivy/tree/{{ git.commit }}/examples/misconf/rule-exception +[ksv012]: https://github.com/aquasecurity/trivy-policies/blob/main/rules/kubernetes/policies/pss/restricted/3_runs_as_root.rego +[trivy-policies]: https://github.com/aquasecurity/trivy-policies/ \ No newline at end of file diff --git a/docs/docs/scanner/secret.md b/docs/docs/scanner/secret.md new file mode 100644 index 000000000000..49d8c8488ece --- /dev/null +++ b/docs/docs/scanner/secret.md @@ -0,0 +1,315 @@ +# Secret Scanning + +Trivy scans any container image, filesystem and git repository to detect exposed secrets like passwords, api keys, and tokens. +Secret scanning is enabled by default. + +Trivy will scan every plaintext file, according to builtin rules or configuration. There are plenty of builtin rules: + +- AWS access key +- GCP service account +- GitHub personal access token +- GitLab personal access token +- Slack access token +- etc. + +You can see a full list of [built-in rules][builtin] and [built-in allow rules][builtin-allow]. + +!!! tip + If your secret is not detected properly, please make sure that your file including the secret is not in [the allowed paths][builtin-allow]. + You can disable allow rules via [disable-allow-rules](#disable-rules). + +## Quick start +This section shows how to scan secrets in container image and filesystem. Other subcommands should be the same. + +### Container image +Specify an image name. + +``` shell +$ trivy image myimage:1.0.0 +2022-04-21T18:56:44.099+0300 INFO Detected OS: alpine +2022-04-21T18:56:44.099+0300 INFO Detecting Alpine vulnerabilities... +2022-04-21T18:56:44.101+0300 INFO Number of language-specific files: 0 + +myimage:1.0.0 (alpine 3.15.0) +============================= +Total: 6 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 2) + ++--------------+------------------+----------+-------------------+---------------+---------------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++--------------+------------------+----------+-------------------+---------------+---------------------------------------+ +| busybox | CVE-2022-28391 | CRITICAL | 1.34.1-r3 | 1.34.1-r5 | CVE-2022-28391 affecting | +| | | | | | package busybox 1.35.0 | +| | | | | | -->avd.aquasec.com/nvd/cve-2022-28391 | ++--------------+------------------| |-------------------+---------------+---------------------------------------+ +| ssl_client | CVE-2022-28391 | | 1.34.1-r3 | 1.34.1-r5 | CVE-2022-28391 affecting | +| | | | | | package busybox 1.35.0 | +| | | | | | -->avd.aquasec.com/nvd/cve-2022-28391 | ++--------------+------------------+----------+-------------------+---------------+---------------------------------------+ + +app/secret.sh (secrets) +======================= +Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1) + ++----------+-------------------+----------+---------+--------------------------------+ +| CATEGORY | DESCRIPTION | SEVERITY | LINE NO | MATCH | ++----------+-------------------+----------+---------+--------------------------------+ +| AWS | AWS Access Key ID | CRITICAL | 10 | export AWS_ACCESS_KEY_ID=***** | ++----------+-------------------+----------+---------+--------------------------------+ +``` + + +!!! tip + Trivy tries to detect a base image and skip those layers for secret scanning. + A base image usually contains a lot of files and makes secret scanning much slower. + If a secret is not detected properly, you can see base layers with the `--debug` flag. + +### Filesystem + +``` shell +$ trivy fs /path/to/your_project +...(snip)... + +certs/key.pem (secrets) +======================== +Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0) + ++----------------------+------------------------+----------+---------+---------------------------------+ +| CATEGORY | DESCRIPTION | SEVERITY | LINE NO | MATCH | ++----------------------+------------------------+----------+---------+---------------------------------+ +| AsymmetricPrivateKey | Asymmetric Private Key | HIGH | 1 | -----BEGIN RSA PRIVATE KEY----- | ++----------------------+------------------------+----------+---------+---------------------------------+ +``` + + +!!! tip + Your project may have some secrets for testing. You can skip them with `--skip-dirs` or `--skip-files`. + We would recommend specifying these options so that the secret scanning can be faster if those files don't need to be scanned. + Also, you can specify paths to be allowed in a configuration file. See the detail [here](#configuration). + +## Configuration +This section describes secret-specific configuration. +Other common options are documented [here](../configuration/index.md). + +Trivy has a set of builtin rules for secret scanning, which can be extended or modified by a configuration file. +Trivy tries to load `trivy-secret.yaml` in the current directory by default. +If the file doesn't exist, only built-in rules are used. +You can customize the config file path via the `--secret-config` flag. + +!!! warning + Trivy uses [Golang regexp package](https://pkg.go.dev/regexp/syntax#hdr-Syntax). To use `^` and `$` as symbols of begin and end of line use multi-line mode -`(?m)`. + +### Custom Rules +Trivy allows defining custom rules. + +``` yaml +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + path: .*\.sh + keywords: + - secret + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret + allow-rules: + - id: skip-text + description: skip text files + path: .*\.txt +``` + +`id` (required) +: - Unique identifier for this rule. + +`category` (required) +: - String used for metadata and reporting purposes. + +`title` (required) +: - Short human-readable title of the rule. + +`severity` (required) +: - How critical this rule is. +- Allowed values: +- CRITICAL +- HIGH +- MEDIUM +- LOW + +`regex` (required) +: - Golang regular expression used to detect secrets. + +`path` (optional) +: - Golang regular expression used to match paths. + +`keywords` (optional, recommended) +: - Keywords are used for pre-regex check filtering. +- Rules that contain keywords will perform a quick string compare check to make sure the keyword(s) are in the content being scanned. +- Ideally these values should either be part of the identifier or unique strings specific to the rule's regex. +- It is recommended to define for better performance. + +`allow-rules` (optional) +: - Allow rules for a single rule to reduce false positives with known secrets. +- The details are below. + +### Allow Rules +If the detected secret is matched with the specified `regex`, then that secret will be skipped and not detected. +The same logic applies for `path`. + +`allow-rules` can be defined globally and per each rule. The fields are the same. + +``` yaml +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + allow-rules: + - id: skip-text + description: skip text files + path: .*\.txt +allow-rules: + - id: social-security-number + description: skip social security number + regex: 219-09-9999 +``` + + +`id` (required) +: - Unique identifier for this allow rule. + +`description` (optional) +: - Short human-readable description of this allow rule. + +`regex` (optional) +: - Golang regular expression used to allow detected secrets. +- `regex` or `path` must be specified. + +`path` (optional) +: - Golang regular expression used to allow matched paths. +- `regex` or `path` must be specified. + +### Enable Rules +Trivy provides plenty of out-of-box rules and allow rules, but you may not need all of them. +In that case, `enable-builtin-rules` will be helpful. +If you just need AWS secret detection, you can enable only relevant rules as shown below. +It specifies AWS-related rule IDs in `enable-builtin-rules`. +All other rules are disabled, so the scanning will be much faster. +We would strongly recommend using this option if you don't need all rules. + +You can see a full list of [built-in rule IDs][builtin] and [built-in allow rule IDs][builtin-allow]. + +``` yaml +enable-builtin-rules: + - aws-access-key-id + - aws-account-id + - aws-secret-access-key +``` + +### Disable Rules +Trivy offers built-in rules and allow rules, but you may want to disable some of them. +For example, you don't use Slack, so Slack doesn't have to be scanned. +You can specify the Slack rule IDs, `slack-access-token` and `slack-web-hook` in `disable-rules` so that those rules will be disabled for less false positives. + +You should specify either `enable-builtin-rules` or `disable-rules`. +If they both are specified, `disable-rules` takes precedence. +In case `github-pat` is specified in `enable-builtin-rules` and `disable-rules`, it will be disabled. + +In addition, there are some allow rules. +Markdown files are ignored by default, but you may want to scan markdown files as well. +You can disable the allow rule by adding `markdown` to `disable-allow-rules`. + +You can see a full list of [built-in rule IDs][builtin] and [built-in allow rule IDs][builtin-allow]. + +``` yaml +disable-rules: + - slack-access-token + - slack-web-hook +disable-allow-rules: + - markdown +``` + +## Recommendation +We would recommend specifying `--skip-dirs` for faster secret scanning. +In container image scanning, Trivy walks the file tree rooted `/` and scans all the files other than [built-in allowed paths][builtin-allow]. +It will take a while if your image contains a lot of files even though Trivy tries to avoid scanning layers from a base image. +If you want to make scanning faster, `--skip-dirs` and `--skip-files` helps so that Trivy will skip scanning those files and directories. +You can see more options [here](../configuration/others.md). + +`allow-rules` is also helpful. See the [allow-rules](#allow-rules) section. + +In addition, all the built-in rules are enabled by default, so it takes some time to scan all of them. +If you don't need all those rules, you can use `enable-builtin-rules` or `disable-rules` in the configuration file. +You should use `enable-builtin-rules` if you need only AWS secret detection, for example. +All rules are disabled except for the ones you specify, so it runs very fast. +On the other hand, you should use `disable-rules` if you just want to disable some built-in rules. +See the [enable-rules](#enable-rules) and [disable-rules](#disable-rules) sections for the detail. + +If you don't need secret scanning, you can disable it via the `--scanners` flag. + +```shell +$ trivy image --scanners vuln alpine:3.15 +``` + +## Example +`trivy-secret.yaml` in the working directory is loaded by default. + +``` yaml +$ cat trivy-secret.yaml +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] +allow-rules: + - id: social-security-number + description: skip social security number + regex: 219-09-9999 + - id: log-dir + description: skip log directory + path: ^\/var\/log\/ +disable-rules: + - slack-access-token + - slack-web-hook +disable-allow-rules: + - markdown + +# The following command automatically loads the above configuration. +$ trivy image YOUR_IMAGE +``` + +Also, you can customize the config file path via `--secret-config`. + +``` yaml +$ cat ./secret-config/trivy.yaml +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + allow-rules: + - id: skip-text + description: skip text files + path: .*\.txt +enable-builtin-rules: + - aws-access-key-id + - aws-account-id + - aws-secret-access-key +disable-allow-rules: + - usr-dirs + +# Pass the above config with `--secret-config`. +$ trivy fs --secret-config ./secret-config/trivy.yaml /path/to/your_project +``` + +## Credit +This feature is inspired by [gitleaks][gitleaks]. + +[builtin]: https://github.com/aquasecurity/trivy/blob/{{ git.tag }}/pkg/fanal/secret/builtin-rules.go +[builtin-allow]: https://github.com/aquasecurity/trivy/blob/{{ git.tag }}/pkg/fanal/secret/builtin-allow-rules.go +[gitleaks]: https://github.com/gitleaks/gitleaks + +[builtin]: https://github.com/aquasecurity/trivy/blob/main/pkg/fanal/secret/builtin-rules.go +[builtin-allow]: https://github.com/aquasecurity/trivy/blob/main/pkg/fanal/secret/builtin-allow-rules.go diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md new file mode 100644 index 000000000000..e18fecfcbf30 --- /dev/null +++ b/docs/docs/scanner/vulnerability.md @@ -0,0 +1,268 @@ +# Vulnerability Scanning +Trivy detects known vulnerabilities according to the versions of installed packages. + +The following packages are supported. + +- [OS packages](#os-packages) +- [Language-specific packages](#language-specific-packages) +- [Kubernetes components (control plane, node and addons)](#kubernetes) + +Trivy also detects known vulnerabilities in Kubernetes components using KBOM (Kubernetes bill of Material) scanning. To learn more, see the [documentation for Kubernetes scanning](../target/kubernetes.md#KBOM). + +## OS Packages +Trivy is capable of automatically detecting installed OS packages when scanning container images, VM images and running hosts. + +!!! note + Trivy doesn't support third-party/self-compiled packages/binaries, but official packages provided by vendors such as Red Hat and Debian. + +### Supported OS +See [here](../coverage/os/index.md#supported-os) for the supported OSes. + +### Data Sources + +| OS | Source | +| ------------- | ------------------------------------------------------------ | +| Arch Linux | [Vulnerable Issues][arch] | +| Alpine Linux | [secdb][alpine] | +| Wolfi Linux | [secdb][wolfi] | +| Chainguard | [secdb][chainguard] | +| Amazon Linux | [Amazon Linux Security Center][amazon] | +| Debian | [Security Bug Tracker][debian-tracker] / [OVAL][debian-oval] | +| Ubuntu | [Ubuntu CVE Tracker][ubuntu] | +| RHEL/CentOS | [OVAL][rhel-oval] / [Security Data][rhel-api] | +| AlmaLinux | [AlmaLinux Product Errata][alma] | +| Rocky Linux | [Rocky Linux UpdateInfo][rocky] | +| Oracle Linux | [OVAL][oracle] | +| CBL-Mariner | [OVAL][mariner] | +| OpenSUSE/SLES | [CVRF][suse] | +| Photon OS | [Photon Security Advisory][photon] | + +#### Data Source Selection +Trivy **only** consumes security advisories from the sources listed in the above table. + +As for packages installed from OS package managers (`dpkg`, `yum`, `apk`, etc.), Trivy uses the advisory database from the appropriate **OS vendor**. + +For example: for a python package installed from `yum` (Amazon linux), Trivy will only get advisories from [ALAS][amazon]. +But for a python package installed from another source (e.g. `pip`), Trivy will get advisories from the `GitLab` and `GitHub` databases. + +This advisory selection is essential to avoid getting false positives because OS vendors usually backport upstream fixes, and the fixed version can be different from the upstream fixed version. + +#### Severity Selection +The severity is taken from the selected data source since the severity from vendors is more accurate. +Using CVE-2023-0464 as an example, while it is [rated as "HIGH" in NVD][nvd-CVE-2023-0464], Red Hat has marked its 'Impact' as ["Low"][redhat-CVE-2023-0464]. +As a result, Trivy will display it as "Low". + +The severity depends on the compile option, the default configuration, etc. +NVD doesn't know how the vendor distributes the software. +Red Hat evaluates the severity more accurately. +That's why Trivy prefers vendor scores over NVD. + +If the data source does not provide a severity, the severity is determined based on the CVSS score as follows: + +| Base Score Range | Severity | +| ---------------- | -------- | +| 0.1-3.9 | Low | +| 4.0-6.9 | Medium | +| 7.0-8.9 | High | +| 9.0-10.0 | Critical | + +If the CVSS score is also not provided, it falls back to [NVD][nvd], and if NVD does not have severity, it will be UNKNOWN. + +### Unfixed Vulnerabilities +The unfixed/unfixable vulnerabilities mean that the patch has not yet been provided on their distribution. +To hide unfixed/unfixable vulnerabilities, you can use the `--ignore-unfixed` flag. + +## Language-specific Packages + +### Supported Languages +See [here](../coverage/language/index.md#supported-languages) for the supported languages. + +### Data Sources + +| Language | Source | Commercial Use | Delay[^1] | +|----------|-----------------------------------------------------|:--------------:|:---------:| +| PHP | [PHP Security Advisories Database][php] | ✅ | - | +| | [GitHub Advisory Database (Composer)][php-ghsa] | ✅ | - | +| Python | [GitHub Advisory Database (pip)][python-ghsa] | ✅ | - | +| | [Open Source Vulnerabilities (PyPI)][python-osv] | ✅ | - | +| Ruby | [Ruby Advisory Database][ruby] | ✅ | - | +| | [GitHub Advisory Database (RubyGems)][ruby-ghsa] | ✅ | - | +| Node.js | [Ecosystem Security Working Group][nodejs] | ✅ | - | +| | [GitHub Advisory Database (npm)][nodejs-ghsa] | ✅ | - | +| Java | [GitHub Advisory Database (Maven)][java-ghsa] | ✅ | - | +| Go | [GitHub Advisory Database (Go)][go-ghsa] | ✅ | - | +| Rust | [Open Source Vulnerabilities (crates.io)][rust-osv] | ✅ | - | +| .NET | [GitHub Advisory Database (NuGet)][dotnet-ghsa] | ✅ | - | +| C/C++ | [GitLab Advisories Community][gitlab] | ✅ | 1 month | +| Dart | [GitHub Advisory Database (Pub)][pub-ghsa] | ✅ | - | +| Elixir | [GitHub Advisory Database (Erlang)][erlang-ghsa] | ✅ | - | +| Swift | [GitHub Advisory Database (Swift)][swift-ghsa] | ✅ | - | + +[^1]: Intentional delay between vulnerability disclosure and registration in the DB + +## Kubernetes + +Trivy can detect vulnerabilities in Kubernetes clusters and components. + +### Data Sources + +| Vendor | Source | +| ------------- |---------------------------------------------| +| Kubernetes | [Kubernetes Official CVE feed][k8s-cve][^1] | + +[^1]: Some manual triage and correction has been made. + +## Database +Trivy downloads [the vulnerability database](https://github.com/aquasecurity/trivy-db) every 6 hours. +Trivy uses two types of databases for vulnerability detection: + +- Vulnerability Database +- Java Index Database + +This page provides detailed information about these databases. + +### Vulnerability Database +Trivy utilizes a database containing vulnerability information. +This database is built every six hours on [GitHub](https://github.com/aquasecurity/trivy-db) and is distributed via [GitHub Container registry (GHCR)](https://ghcr.io/aquasecurity/trivy-db). +The database is cached and updated as needed. +As Trivy updates the database automatically during execution, users don't need to be concerned about it. + +For CLI flags related to the database, please refer to [this page](../configuration/db.md). + +#### Private Hosting +If you host the database on your own OCI registry, you can specify a different repository with the `--db-repository` flag. +The default is `ghcr.io/aquasecurity/trivy-db`. + +```shell +$ trivy image --db-repository YOUR_REPO YOUR_IMAGE +``` + +If authentication is required, it can be configured in the same way as for private images. +Please refer to [the documentation](../advanced/private-registries/index.md) for more details. + +### Java Index Database +This database is only downloaded when scanning JAR files so that Trivy can identify the groupId, artifactId, and version of JAR files. +It is built once a day on [GitHub](https://github.com/aquasecurity/trivy-java-db) and distributed via [GitHub Container registry (GHCR)](https://ghcr.io/aquasecurity/trivy-java-db). +Like the vulnerability database, it is automatically downloaded and updated when needed, so users don't need to worry about it. + +#### Private Hosting +If you host the database on your own OCI registry, you can specify a different repository with the `--java-db-repository` flag. +The default is `ghcr.io/aquasecurity/trivy-java-db`. + +If authentication is required, you need to run `docker login YOUR_REGISTRY`. +Currently, specifying a username and password is not supported. + +## Configuration +This section describes vulnerability-specific configuration. +Other common options are documented [here](../configuration/index.md). + +### Enabling a subset of package types +It's possible to only enable certain package types if you prefer. +You can do so by passing the `--vuln-type` option. +This flag takes a comma-separated list of package types. + +Available values: + +- os + - Scan OS packages managed by the OS package manager (e.g. `dpkg`, `yum`, `apk`). +- library + - Scan language-specific packages (e.g. packages installed by `pip`, `npm`, or `gem`). + +```bash +$ trivy image --vuln-type os ruby:2.4.0 +``` + + +
+Result + +```bash +2019-05-22T19:36:50.530+0200 INFO Updating vulnerability database... +2019-05-22T19:36:51.681+0200 INFO Detecting Alpine vulnerabilities... +2019-05-22T19:36:51.685+0200 INFO Updating npm Security DB... +2019-05-22T19:36:52.389+0200 INFO Detecting npm vulnerabilities... +2019-05-22T19:36:52.390+0200 INFO Updating pipenv Security DB... +2019-05-22T19:36:53.406+0200 INFO Detecting pipenv vulnerabilities... + +ruby:2.4.0 (debian 8.7) +======================= +Total: 7 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 3, CRITICAL: 2) + ++---------+------------------+----------+-------------------+---------------+----------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++---------+------------------+----------+-------------------+---------------+----------------------------------+ +| curl | CVE-2018-14618 | CRITICAL | 7.61.0-r0 | 7.61.1-r0 | curl: NTLM password overflow | +| | | | | | via integer overflow | ++ +------------------+----------+ +---------------+----------------------------------+ +| | CVE-2018-16839 | HIGH | | 7.61.1-r1 | curl: Integer overflow leading | +| | | | | | to heap-based buffer overflow in | +| | | | | | Curl_sasl_create_plain_message() | ++---------+------------------+----------+-------------------+---------------+----------------------------------+ +| git | CVE-2018-17456 | HIGH | 2.15.2-r0 | 2.15.3-r0 | git: arbitrary code execution | +| | | | | | via .gitmodules | ++ +------------------+ + + +----------------------------------+ +| | CVE-2018-19486 | | | | git: Improper handling of | +| | | | | | PATH allows for commands to be | +| | | | | | executed from... | ++---------+------------------+----------+-------------------+---------------+----------------------------------+ +| libssh2 | CVE-2019-3855 | CRITICAL | 1.8.0-r2 | 1.8.1-r0 | libssh2: Integer overflow in | +| | | | | | transport read resulting in | +| | | | | | out of bounds write... | ++---------+------------------+----------+-------------------+---------------+----------------------------------+ +| sqlite | CVE-2018-20346 | MEDIUM | 3.21.0-r1 | 3.25.3-r0 | CVE-2018-20505 CVE-2018-20506 | +| | | | | | sqlite: Multiple flaws in | +| | | | | | sqlite which can be triggered | +| | | | | | via... | ++---------+------------------+----------+-------------------+---------------+----------------------------------+ +| tar | CVE-2018-20482 | LOW | 1.29-r1 | 1.31-r0 | tar: Infinite read loop in | +| | | | | | sparse_dump_region function in | +| | | | | | sparse.c | ++---------+------------------+----------+-------------------+---------------+----------------------------------+ +``` + +
+ +[^1]: https://github.com/GoogleContainerTools/distroless + +[nvd-CVE-2023-0464]: https://nvd.nist.gov/vuln/detail/CVE-2023-0464 +[redhat-CVE-2023-0464]: https://access.redhat.com/security/cve/cve-2023-0464 + +[arch]: https://security.archlinux.org/ +[alpine]: https://secdb.alpinelinux.org/ +[wolfi]: https://packages.wolfi.dev/os/security.json +[chainguard]: https://packages.cgr.dev/chainguard/security.json +[amazon]: https://alas.aws.amazon.com/ +[debian-tracker]: https://security-tracker.debian.org/tracker/ +[debian-oval]: https://www.debian.org/security/oval/ +[ubuntu]: https://ubuntu.com/security/cve +[rhel-oval]: https://www.redhat.com/security/data/oval/v2/ +[rhel-api]: https://www.redhat.com/security/data/metrics/ +[alma]: https://errata.almalinux.org/ +[rocky]: https://download.rockylinux.org/pub/rocky/ +[oracle]: https://linux.oracle.com/security/oval/ +[suse]: http://ftp.suse.com/pub/projects/security/cvrf/ +[photon]: https://packages.vmware.com/photon/photon_cve_metadata/ +[mariner]: https://github.com/microsoft/CBL-MarinerVulnerabilityData/ + +[php-ghsa]: https://github.com/advisories?query=ecosystem%3Acomposer +[python-ghsa]: https://github.com/advisories?query=ecosystem%3Apip +[ruby-ghsa]: https://github.com/advisories?query=ecosystem%3Arubygems +[nodejs-ghsa]: https://github.com/advisories?query=ecosystem%3Anpm +[java-ghsa]: https://github.com/advisories?query=ecosystem%3Amaven +[dotnet-ghsa]: https://github.com/advisories?query=ecosystem%3Anuget +[pub-ghsa]: https://github.com/advisories?query=ecosystem%3Apub +[erlang-ghsa]: https://github.com/advisories?query=ecosystem%3Aerlang +[go-ghsa]: https://github.com/advisories?query=ecosystem%3Ago +[swift-ghsa]: https://github.com/advisories?query=ecosystem%3Aswift + +[php]: https://github.com/FriendsOfPHP/security-advisories +[ruby]: https://github.com/rubysec/ruby-advisory-db +[nodejs]: https://github.com/nodejs/security-wg +[gitlab]: https://gitlab.com/gitlab-org/advisories-community + +[python-osv]: https://osv.dev/list?q=&ecosystem=PyPI +[rust-osv]: https://osv.dev/list?q=&ecosystem=crates.io + +[nvd]: https://nvd.nist.gov/vuln + +[k8s-cve]: https://kubernetes.io/docs/reference/issues-security/official-cve-feed/ diff --git a/docs/docs/supply-chain/attestation/rekor.md b/docs/docs/supply-chain/attestation/rekor.md new file mode 100644 index 000000000000..c23c70046c15 --- /dev/null +++ b/docs/docs/supply-chain/attestation/rekor.md @@ -0,0 +1,147 @@ +# Scan SBOM attestation in Rekor + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +## Container images +Trivy can retrieve SBOM attestation of the specified container image in the [Rekor][rekor] instance and scan it for vulnerabilities. + +### Prerequisites +1. SBOM attestation stored in Rekor + - See [the "Keyless signing" section][sbom-attest] if you want to upload your SBOM attestation to Rekor. + + +### Scanning +You need to pass `--sbom-sources rekor` so that Trivy will look for SBOM attestation in Rekor. + +!!! note + `--sbom-sources` can be used only with `trivy image` at the moment. + +```bash +$ trivy image --sbom-sources rekor otms61/alpine:3.7.3 [~/src/github.com/aquasecurity/trivy] +2022-09-16T17:37:13.258+0900 INFO Vulnerability scanning is enabled +2022-09-16T17:37:13.258+0900 INFO Secret scanning is enabled +2022-09-16T17:37:13.258+0900 INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning +2022-09-16T17:37:13.258+0900 INFO Please see also https://aquasecurity.github.io/trivy/dev/docs/secret/scanning/#recommendation for faster secret detection +2022-09-16T17:37:14.827+0900 INFO Detected SBOM format: cyclonedx-json +2022-09-16T17:37:14.901+0900 INFO Found SBOM (cyclonedx) attestation in Rekor +2022-09-16T17:37:14.903+0900 INFO Detected OS: alpine +2022-09-16T17:37:14.903+0900 INFO Detecting Alpine vulnerabilities... +2022-09-16T17:37:14.907+0900 INFO Number of language-specific files: 0 +2022-09-16T17:37:14.908+0900 WARN This OS version is no longer supported by the distribution: alpine 3.7.3 +2022-09-16T17:37:14.908+0900 WARN The vulnerability detection may be insufficient because security updates are not provided + +otms61/alpine:3.7.3 (alpine 3.7.3) +================================== +Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 2) + +┌────────────┬────────────────┬──────────┬───────────────────┬───────────────┬──────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├────────────┼────────────────┼──────────┼───────────────────┼───────────────┼──────────────────────────────────────────────────────────┤ +│ musl │ CVE-2019-14697 │ CRITICAL │ 1.1.18-r3 │ 1.1.18-r4 │ musl libc through 1.1.23 has an x87 floating-point stack │ +│ │ │ │ │ │ adjustment im ...... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-14697 │ +├────────────┤ │ │ │ │ │ +│ musl-utils │ │ │ │ │ │ +│ │ │ │ │ │ │ +│ │ │ │ │ │ │ +└────────────┴────────────────┴──────────┴───────────────────┴───────────────┴──────────────────────────────────────────────────────────┘ + +``` + +If you have your own Rekor instance, you can specify the URL via `--rekor-url`. + +```bash +$ trivy image --sbom-sources rekor --rekor-url https://my-rekor.dev otms61/alpine:3.7.3 +``` + +## Non-packaged binaries +Trivy can retrieve SBOM attestation of non-packaged binaries in the [Rekor][rekor] instance and scan it for vulnerabilities. + +### Prerequisites +1. SBOM attestation stored in Rekor + - See [the "Keyless signing" section][sbom-attest] if you want to upload your SBOM attestation to Rekor. + +Cosign currently does not support keyless signing for blob attestation, so use our plugin at the moment. +This example uses a cat clone [bat][bat] written in Rust. +You need to generate SBOM from lock files like `Cargo.lock` at first. + +```bash +$ git clone -b v0.20.0 https://github.com/sharkdp/bat +$ trivy fs --format cyclonedx --output bat.cdx ./bat/Cargo.lock +``` + +Then [our attestation plugin][plugin-attest] allows you to store the SBOM attestation linking to a `bat` binary in the Rekor instance. + +```bash +$ wget https://github.com/sharkdp/bat/releases/download/v0.20.0/bat-v0.20.0-x86_64-apple-darwin.tar.gz +$ tar xvf bat-v0.20.0-x86_64-apple-darwin.tar.gz +$ trivy plugin install github.com/aquasecurity/trivy-plugin-attest +$ trivy attest --predicate ./bat.cdx --type cyclonedx ./bat-v0.20.0-x86_64-apple-darwin/bat +``` + +!!! note + The public instance of the Rekor maintained by the Sigstore team limits the attestation size. + If you are using the public instance, please make sure that your SBOM is small enough. + To get more detail, please refer to the Rekor project's [documentation](https://github.com/sigstore/rekor#public-instance). + +### Scan a non-packaged binary +Trivy calculates the digest of the `bat` binary and searches for the SBOM attestation by the digest in Rekor. +If it is found, Trivy uses that for vulnerability scanning. + +```bash +$ trivy fs --sbom-sources rekor ./bat-v0.20.0-x86_64-apple-darwin/bat +2022-10-25T13:27:25.950+0300 INFO Found SBOM attestation in Rekor: bat +2022-10-25T13:27:25.993+0300 INFO Number of language-specific files: 1 +2022-10-25T13:27:25.993+0300 INFO Detecting cargo vulnerabilities... + +bat (cargo) +=========== +Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0) + +┌───────────┬───────────────────┬──────────┬───────────────────┬───────────────┬────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├───────────┼───────────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤ +│ regex │ CVE-2022-24713 │ HIGH │ 1.5.4 │ 1.5.5 │ Mozilla: Denial of Service via complex regular expressions │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-24713 │ +└───────────┴───────────────────┴──────────┴───────────────────┴───────────────┴────────────────────────────────────────────────────────────┘ +``` + +Also, it is applied to non-packaged binaries even in container images. + +```bash +$ trivy image --sbom-sources rekor --scanners vuln alpine-with-bat +2022-10-25T13:40:14.920+0300 INFO Vulnerability scanning is enabled +2022-10-25T13:40:18.047+0300 INFO Found SBOM attestation in Rekor: bat +2022-10-25T13:40:18.186+0300 INFO Detected OS: alpine +2022-10-25T13:40:18.186+0300 INFO Detecting Alpine vulnerabilities... +2022-10-25T13:40:18.199+0300 INFO Number of language-specific files: 1 +2022-10-25T13:40:18.199+0300 INFO Detecting cargo vulnerabilities... + +alpine-with-bat (alpine 3.15.6) +=============================== +Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + + +bat (cargo) +=========== +Total: 4 (UNKNOWN: 3, LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0) + +┌───────────┬───────────────────┬──────────┬───────────────────┬───────────────┬────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├───────────┼───────────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤ +│ regex │ CVE-2022-24713 │ HIGH │ 1.5.4 │ 1.5.5 │ Mozilla: Denial of Service via complex regular expressions │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-24713 │ +└───────────┴───────────────────┴──────────┴───────────────────┴───────────────┴────────────────────────────────────────────────────────────┘ +``` + + +!!! note + The `--sbom-sources rekor` flag slows down the scanning as it queries Rekor on the Internet for all non-packaged binaries. + +[rekor]: https://github.com/sigstore/rekor +[sbom-attest]: sbom.md#keyless-signing + +[plugin-attest]: https://github.com/aquasecurity/trivy-plugin-attest + +[bat]: https://github.com/sharkdp/bat \ No newline at end of file diff --git a/docs/docs/supply-chain/attestation/sbom.md b/docs/docs/supply-chain/attestation/sbom.md new file mode 100644 index 000000000000..5d2667d0e100 --- /dev/null +++ b/docs/docs/supply-chain/attestation/sbom.md @@ -0,0 +1,87 @@ +# SBOM attestation + +[Cosign](https://github.com/sigstore/cosign) supports generating and verifying [in-toto attestations](https://github.com/in-toto/attestation). This tool enables you to sign and verify SBOM attestation. +And, Trivy can take an SBOM attestation as input and scan for vulnerabilities + +!!! note + In the following examples, the `cosign` command will write an attestation to a target OCI registry, so you must have permission to write. + If you want to avoid writing an OCI registry and only want to see an attestation, add the `--no-upload` option to the `cosign` command. + +## Sign with a local key pair + +Cosign can generate key pairs and use them for signing and verification. After you run the following command, you will get a public and private key pair. Read more about [how to generate key pairs](https://docs.sigstore.dev/cosign/key-generation). + +```bash +$ cosign generate-key-pair +``` + +In the following example, Trivy generates an SBOM in the CycloneDX format, and then Cosign attaches an attestation of the SBOM to a container image with a local key pair. + +```bash +# The cyclonedx type is supported in Cosign v1.10.0 or later. +$ trivy image --format cyclonedx -o sbom.cdx.json +$ cosign attest --key /path/to/cosign.key --type cyclonedx --predicate sbom.cdx.json +``` + +Then, you can verify attestations on the image. + +```bash +$ cosign verify-attestation --key /path/to/cosign.pub --type cyclonedx +``` + +You can also create attestations of other formatted SBOM. + +```bash +# spdx +$ trivy image --format spdx -o sbom.spdx +$ cosign attest --key /path/to/cosign.key --type spdx --predicate sbom.spdx + +# spdx-json +$ trivy image --format spdx-json -o sbom.spdx.json +$ cosign attest --key /path/to/cosign.key --type spdx --predicate sbom.spdx.json +``` + +## Keyless signing + +You can use Cosign to sign without keys by authenticating with an OpenID Connect protocol supported by sigstore (Google, GitHub, or Microsoft). + +```bash +# The cyclonedx type is supported in Cosign v1.10.0 or later. +$ trivy image --format cyclonedx -o sbom.cdx.json +# The following command uploads SBOM attestation to the public Rekor instance. +$ COSIGN_EXPERIMENTAL=1 cosign attest --type cyclonedx --predicate sbom.cdx.json +``` + +You can verify attestations. +```bash +$ COSIGN_EXPERIMENTAL=1 cosign verify-attestation --type cyclonedx +``` + +## Scanning + +Trivy can take an SBOM attestation as input and scan for vulnerabilities. Currently, Trivy supports CycloneDX-type attestation. + +In the following example, Cosign can get an CycloneDX-type attestation and trivy scan it. +You must create CycloneDX-type attestation before trying the example. +To learn more about how to create an CycloneDX-Type attestation and attach it to an image, see the [Sign with a local key pair](#sign-with-a-local-key-pair) section. + +```bash +$ cosign verify-attestation --key /path/to/cosign.pub --type cyclonedx > sbom.cdx.intoto.jsonl +$ trivy sbom ./sbom.cdx.intoto.jsonl + +sbom.cdx.intoto.jsonl (alpine 3.7.3) +========================= +Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 2) + +┌────────────┬────────────────┬──────────┬───────────────────┬───────────────┬──────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├────────────┼────────────────┼──────────┼───────────────────┼───────────────┼──────────────────────────────────────────────────────────┤ +│ musl │ CVE-2019-14697 │ CRITICAL │ 1.1.18-r3 │ 1.1.18-r4 │ musl libc through 1.1.23 has an x87 floating-point stack │ +│ │ │ │ │ │ adjustment im ...... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-14697 │ +├────────────┤ │ │ │ │ │ +│ musl-utils │ │ │ │ │ │ +│ │ │ │ │ │ │ +│ │ │ │ │ │ │ +└────────────┴────────────────┴──────────┴───────────────────┴───────────────┴──────────────────────────────────────────────────────────┘ +``` diff --git a/docs/docs/supply-chain/attestation/vuln.md b/docs/docs/supply-chain/attestation/vuln.md new file mode 100644 index 000000000000..b1484387266a --- /dev/null +++ b/docs/docs/supply-chain/attestation/vuln.md @@ -0,0 +1,192 @@ +# Cosign Vulnerability Attestation + +## Generate Cosign Vulnerability Scan Record + +Trivy generates reports in the [Cosign vulnerability scan record format][vuln-attest-spec]. + +You can use the regular subcommands (like image, fs and rootfs) and specify `cosign-vuln` with the --format option. + +``` +$ trivy image --format cosign-vuln --output vuln.json alpine:3.10 +``` + +
+Result + +```json +{ + "invocation": { + "parameters": null, + "uri": "", + "event_id": "", + "builder.id": "" + }, + "scanner": { + "uri": "pkg:github/aquasecurity/trivy@v0.30.1-8-gf9cb8a28", + "version": "v0.30.1-8-gf9cb8a28", + "db": { + "uri": "", + "version": "" + }, + "result": { + "SchemaVersion": 2, + "CreatedAt": 1629894030, + "ArtifactName": "alpine:3.10", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "alpine", + "Name": "3.10.9", + "EOSL": true + }, + "ImageID": "sha256:e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a", + "DiffIDs": [ + "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635" + ], + "RepoTags": [ + "alpine:3.10" + ], + "RepoDigests": [ + "alpine@sha256:451eee8bedcb2f029756dc3e9d73bab0e7943c1ac55cff3a4861c52a0fdd3e98" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "fdb7e80e3339e8d0599282e606c907aa5881ee4c668a68136119e6dfac6ce3a4", + "created": "2021-04-14T19:20:05.338397761Z", + "docker_version": "19.03.12", + "history": [ + { + "created": "2021-04-14T19:20:04.987219124Z", + "created_by": "/bin/sh -c #(nop) ADD file:c5377eaa926bf412dd8d4a08b0a1f2399cfd708743533b0aa03b53d14cb4bb4e in / " + }, + { + "created": "2021-04-14T19:20:05.338397761Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635" + ] + }, + "config": { + "Cmd": [ + "/bin/sh" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:eb2080c455e94c22ae35b3aef9e078c492a00795412e026e4d6b41ef64bc7dd8" + } + } + }, + "Results": [ + { + "Target": "alpine:3.10 (alpine 3.10.9)", + "Class": "os-pkgs", + "Type": "alpine", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2021-36159", + "PkgName": "apk-tools", + "InstalledVersion": "2.10.6-r0", + "FixedVersion": "2.10.7-r0", + "Layer": { + "Digest": "sha256:396c31837116ac290458afcb928f68b6cc1c7bdd6963fc72f52f365a2a89c1b5", + "DiffID": "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-36159", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Description": "libfetch before 2021-07-26, as used in apk-tools, xbps, and other products, mishandles numeric strings for the FTP and HTTP protocols. The FTP passive mode implementation allows an out-of-bounds read because strtol is used to parse the relevant numbers into address bytes. It does not check if the line ends prematurely. If it does, the for-loop condition checks for the '\\0' terminator one byte too late.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-125" + ], + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", + "V2Score": 6.4, + "V3Score": 9.1 + } + }, + "References": [ + "https://github.com/freebsd/freebsd-src/commits/main/lib/libfetch", + "https://gitlab.alpinelinux.org/alpine/apk-tools/-/issues/10749", + "https://lists.apache.org/thread.html/r61db8e7dcb56dc000a5387a88f7a473bacec5ee01b9ff3f55308aacc@%3Cdev.kafka.apache.org%3E", + "https://lists.apache.org/thread.html/r61db8e7dcb56dc000a5387a88f7a473bacec5ee01b9ff3f55308aacc@%3Cusers.kafka.apache.org%3E", + "https://lists.apache.org/thread.html/rbf4ce74b0d1fa9810dec50ba3ace0caeea677af7c27a97111c06ccb7@%3Cdev.kafka.apache.org%3E", + "https://lists.apache.org/thread.html/rbf4ce74b0d1fa9810dec50ba3ace0caeea677af7c27a97111c06ccb7@%3Cusers.kafka.apache.org%3E" + ], + "PublishedDate": "2021-08-03T14:15:00Z", + "LastModifiedDate": "2021-10-18T12:19:00Z" + } + ] + } + ] + } + }, + "metadata": { + "scanStartedOn": "2022-07-24T17:14:04.864682+09:00", + "scanFinishedOn": "2022-07-24T17:14:04.864682+09:00" + } +} +``` + +
+ +## Create Cosign Vulnerability Attestation + +[Cosign](https://github.com/sigstore/cosign) supports generating and verifying [in-toto attestations](https://github.com/in-toto/attestation). This tool enables you to sign and verify Cosign vulnerability attestation. + +!!! note + In the following examples, the `cosign` command will write an attestation to a target OCI registry, so you must have permission to write. + If you want to avoid writing an OCI registry and only want to see an attestation, add the `--no-upload` option to the `cosign` command. + + +### Sign with a local key pair + +Cosign can generate key pairs and use them for signing and verification. After you run the following command, you will get a public and private key pair. Read more about [how to generate key pairs](https://docs.sigstore.dev/cosign/key-generation). + +```bash +$ cosign generate-key-pair +``` + +In the following example, Trivy generates a cosign vulnerability scan record, and then Cosign attaches an attestation of it to a container image with a local key pair. + +``` +$ trivy image --format cosign-vuln --output vuln.json +$ cosign attest --key /path/to/cosign.key --type vuln --predicate vuln.json +``` + +Then, you can verify attestations on the image. + +``` +$ cosign verify-attestation --key /path/to/cosign.pub --type vuln +``` + +### Keyless signing + +You can use Cosign to sign without keys by authenticating with an OpenID Connect protocol supported by sigstore (Google, GitHub, or Microsoft). + +``` +$ trivy image --format cosign-vuln -o vuln.json +$ cosign attest --type vuln --predicate vuln.json +``` +This will provide a certificate in the output section. + +You can verify attestations: + +``` +$ cosign verify-attestation --certificate=path-to-the-certificate --type vuln --certificate-identity Email-used-to-sign --certificate-oidc-issuer='the-issuer-used' +``` + +[vuln-attest-spec]: https://github.com/sigstore/cosign/blob/95b74db89941e8ec85e768f639efd4d948db06cd/specs/COSIGN_VULN_ATTESTATION_SPEC.md \ No newline at end of file diff --git a/docs/docs/supply-chain/sbom.md b/docs/docs/supply-chain/sbom.md new file mode 100644 index 000000000000..cb3a68c9d8f3 --- /dev/null +++ b/docs/docs/supply-chain/sbom.md @@ -0,0 +1,766 @@ +# SBOM + +## Generating + +Trivy can generate the following SBOM formats. + +- [CycloneDX](#cyclonedx) +- [SPDX](#spdx) + +### CLI commands +To generate SBOM, you can use the `--format` option for each subcommand such as `image`, `fs` and `vm`. + +``` +$ trivy image --format spdx-json --output result.json alpine:3.15 +``` + + +``` +$ trivy fs --format cyclonedx --output result.json /app/myproject +``` + +
+Result + +``` +{ + "bomFormat": "CycloneDX", + "specVersion": "1.3", + "serialNumber": "urn:uuid:2be5773d-7cd3-4b4b-90a5-e165474ddace", + "version": 1, + "metadata": { + "timestamp": "2022-02-22T15:11:40.270597Z", + "tools": [ + { + "vendor": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ], + "component": { + "bom-ref": "pkg:oci/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300?repository_url=index.docker.io%2Flibrary%2Falpine&arch=amd64", + "type": "container", + "name": "alpine:3.15", + "version": "", + "purl": "pkg:oci/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300?repository_url=index.docker.io%2Flibrary%2Falpine&arch=amd64", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:c059bfaa849c4d8e4aecaeb3a10c2d9b3d85f5165c66ad3a4d937758128c4d18" + }, + { + "name": "aquasecurity:trivy:RepoDigest", + "value": "alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "alpine:3.15" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:apk/alpine/alpine-baselayout@3.2.0-r18?distro=3.15.0", + "type": "library", + "name": "alpine-baselayout", + "version": "3.2.0-r18", + "licenses": [ + { + "expression": "GPL-2.0-only" + } + ], + "purl": "pkg:apk/alpine/alpine-baselayout@3.2.0-r18?distro=3.15.0", + "properties": [ + { + "name": "aquasecurity:trivy:SrcName", + "value": "alpine-baselayout" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.2.0-r18" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:59bf1c3509f33515622619af21ed55bbe26d24913cedbca106468a5fb37a50c3" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759" + } + ] + }, + ...(snip)... + { + "bom-ref": "pkg:apk/alpine/zlib@1.2.11-r3?distro=3.15.0", + "type": "library", + "name": "zlib", + "version": "1.2.11-r3", + "licenses": [ + { + "expression": "Zlib" + } + ], + "purl": "pkg:apk/alpine/zlib@1.2.11-r3?distro=3.15.0", + "properties": [ + { + "name": "aquasecurity:trivy:SrcName", + "value": "zlib" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.2.11-r3" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:59bf1c3509f33515622619af21ed55bbe26d24913cedbca106468a5fb37a50c3" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759" + } + ] + }, + { + "bom-ref": "3da6a469-964d-4b4e-b67d-e94ec7c88d37", + "type": "operating-system", + "name": "alpine", + "version": "3.15.0", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "alpine" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + } + ] + } + ], + "dependencies": [ + { + "ref": "3da6a469-964d-4b4e-b67d-e94ec7c88d37", + "dependsOn": [ + "pkg:apk/alpine/alpine-baselayout@3.2.0-r18?distro=3.15.0", + "pkg:apk/alpine/alpine-keys@2.4-r1?distro=3.15.0", + "pkg:apk/alpine/apk-tools@2.12.7-r3?distro=3.15.0", + "pkg:apk/alpine/busybox@1.34.1-r3?distro=3.15.0", + "pkg:apk/alpine/ca-certificates-bundle@20191127-r7?distro=3.15.0", + "pkg:apk/alpine/libc-utils@0.7.2-r3?distro=3.15.0", + "pkg:apk/alpine/libcrypto1.1@1.1.1l-r7?distro=3.15.0", + "pkg:apk/alpine/libretls@3.3.4-r2?distro=3.15.0", + "pkg:apk/alpine/libssl1.1@1.1.1l-r7?distro=3.15.0", + "pkg:apk/alpine/musl@1.2.2-r7?distro=3.15.0", + "pkg:apk/alpine/musl-utils@1.2.2-r7?distro=3.15.0", + "pkg:apk/alpine/scanelf@1.3.3-r0?distro=3.15.0", + "pkg:apk/alpine/ssl_client@1.34.1-r3?distro=3.15.0", + "pkg:apk/alpine/zlib@1.2.11-r3?distro=3.15.0" + ] + }, + { + "ref": "pkg:oci/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300?repository_url=index.docker.io%2Flibrary%2Falpine&arch=amd64", + "dependsOn": [ + "3da6a469-964d-4b4e-b67d-e94ec7c88d37" + ] + } + ] +} + +``` + +
+ +### Supported packages +Trivy supports the following packages. + +- [OS packages][os_packages] +- [Language-specific packages][language_packages] + + +### Formats +#### CycloneDX +Trivy can generate SBOM in the [CycloneDX][cyclonedx] format. +Note that XML format is not supported at the moment. + +You can use the regular subcommands (like `image`, `fs` and `rootfs`) and specify `cyclonedx` with the `--format` option. + +CycloneDX can represent either or both SBOM or BOV. + +- [Software Bill of Materials (SBOM)][sbom] +- [Bill of Vulnerabilities (BOV)][bov] + +By default, `--format cyclonedx` represents SBOM and doesn't include vulnerabilities in the CycloneDX output. + +``` +$ trivy image --format cyclonedx --output result.json alpine:3.15 +2022-07-19T07:47:27.624Z INFO "--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the CycloneDX report. +``` + +
+Result + +``` +$ cat result.json | jq . +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:2be5773d-7cd3-4b4b-90a5-e165474ddace", + "version": 1, + "metadata": { + "timestamp": "2022-02-22T15:11:40.270597Z", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "pkg:oci/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300?repository_url=index.docker.io%2Flibrary%2Falpine&arch=amd64", + "type": "container", + "name": "alpine:3.15", + "version": "", + "purl": "pkg:oci/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300?repository_url=index.docker.io%2Flibrary%2Falpine&arch=amd64", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:c059bfaa849c4d8e4aecaeb3a10c2d9b3d85f5165c66ad3a4d937758128c4d18" + }, + { + "name": "aquasecurity:trivy:RepoDigest", + "value": "alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "alpine:3.15" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:apk/alpine/alpine-baselayout@3.2.0-r18?distro=3.15.0", + "type": "library", + "name": "alpine-baselayout", + "version": "3.2.0-r18", + "licenses": [ + { + "expression": "GPL-2.0-only" + } + ], + "purl": "pkg:apk/alpine/alpine-baselayout@3.2.0-r18?distro=3.15.0", + "properties": [ + { + "name": "aquasecurity:trivy:SrcName", + "value": "alpine-baselayout" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.2.0-r18" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:59bf1c3509f33515622619af21ed55bbe26d24913cedbca106468a5fb37a50c3" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759" + } + ] + }, + ...(snip)... + { + "bom-ref": "pkg:apk/alpine/zlib@1.2.11-r3?distro=3.15.0", + "type": "library", + "name": "zlib", + "version": "1.2.11-r3", + "licenses": [ + { + "expression": "Zlib" + } + ], + "purl": "pkg:apk/alpine/zlib@1.2.11-r3?distro=3.15.0", + "properties": [ + { + "name": "aquasecurity:trivy:SrcName", + "value": "zlib" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.2.11-r3" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:59bf1c3509f33515622619af21ed55bbe26d24913cedbca106468a5fb37a50c3" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759" + } + ] + }, + { + "bom-ref": "3da6a469-964d-4b4e-b67d-e94ec7c88d37", + "type": "operating-system", + "name": "alpine", + "version": "3.15.0", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "alpine" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + } + ] + } + ], + "dependencies": [ + { + "ref": "3da6a469-964d-4b4e-b67d-e94ec7c88d37", + "dependsOn": [ + "pkg:apk/alpine/alpine-baselayout@3.2.0-r18?distro=3.15.0", + "pkg:apk/alpine/alpine-keys@2.4-r1?distro=3.15.0", + "pkg:apk/alpine/apk-tools@2.12.7-r3?distro=3.15.0", + "pkg:apk/alpine/busybox@1.34.1-r3?distro=3.15.0", + "pkg:apk/alpine/ca-certificates-bundle@20191127-r7?distro=3.15.0", + "pkg:apk/alpine/libc-utils@0.7.2-r3?distro=3.15.0", + "pkg:apk/alpine/libcrypto1.1@1.1.1l-r7?distro=3.15.0", + "pkg:apk/alpine/libretls@3.3.4-r2?distro=3.15.0", + "pkg:apk/alpine/libssl1.1@1.1.1l-r7?distro=3.15.0", + "pkg:apk/alpine/musl@1.2.2-r7?distro=3.15.0", + "pkg:apk/alpine/musl-utils@1.2.2-r7?distro=3.15.0", + "pkg:apk/alpine/scanelf@1.3.3-r0?distro=3.15.0", + "pkg:apk/alpine/ssl_client@1.34.1-r3?distro=3.15.0", + "pkg:apk/alpine/zlib@1.2.11-r3?distro=3.15.0" + ] + }, + { + "ref": "pkg:oci/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300?repository_url=index.docker.io%2Flibrary%2Falpine&arch=amd64", + "dependsOn": [ + "3da6a469-964d-4b4e-b67d-e94ec7c88d37" + ] + } + ], + "vulnerabilities": [ + { + "id": "CVE-2021-42386", + "source": { + "name": "alpine", + "url": "https://secdb.alpinelinux.org/" + }, + "ratings": [ + { + "source": { + "name": "nvd" + }, + "score": 7.2, + "severity": "high", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "nvd" + }, + "score": 6.5, + "severity": "medium", + "method": "CVSSv2", + "vector": "AV:N/AC:L/Au:S/C:P/I:P/A:P" + }, + { + "source": { + "name": "redhat" + }, + "score": 6.6, + "severity": "medium", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H" + } + ], + "cwes": [ + 416 + ], + "description": "A use-after-free in Busybox's awk applet leads to denial of service and possibly code execution when processing a crafted awk pattern in the nvalloc function", + "advisories": [ + { + "url": "https://access.redhat.com/security/cve/CVE-2021-42386" + }, + { + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42386" + } + ], + "published": "2021-11-15 21:15:00 +0000 UTC", + "updated": "2022-01-04 17:14:00 +0000 UTC", + "affects": [ + { + "ref": "pkg:apk/alpine/busybox@1.33.1-r3?distro=3.14.2" + }, + { + "ref": "pkg:apk/alpine/ssl_client@1.33.1-r3?distro=3.14.2" + } + ] + } + ] +} + +``` + +
+ +If you want to include vulnerabilities, you can enable vulnerability scanning via `--scanners vuln`. + +``` +$ trivy image --scanners vuln --format cyclonedx --output result.json alpine:3.15 +``` + +#### SPDX +Trivy can generate SBOM in the [SPDX][spdx] format. + +You can use the regular subcommands (like `image`, `fs` and `rootfs`) and specify `spdx` with the `--format` option. + +``` +$ trivy image --format spdx --output result.spdx alpine:3.15 +``` + +
+Result + +``` +$ cat result.spdx +SPDXVersion: SPDX-2.2 +DataLicense: CC0-1.0 +SPDXID: SPDXRef-DOCUMENT +DocumentName: alpine:3.15 +DocumentNamespace: https://aquasecurity.github.io/trivy/container_image/alpine:3.15-bebf6b19-a94c-4e2c-af44-065f63923f48 +Creator: Organization: aquasecurity +Creator: Tool: trivy-0.38.1 +Created: 2022-04-28T07:32:57.142806Z + +##### Package: zlib + +PackageName: zlib +SPDXID: SPDXRef-12bc938ac028a5e1 +PackageVersion: 1.2.12-r0 +FilesAnalyzed: false +PackageLicenseConcluded: Zlib +PackageLicenseDeclared: Zlib + +##### Package: apk-tools + +PackageName: apk-tools +SPDXID: SPDXRef-26c274652190d87f +PackageVersion: 2.12.7-r3 +FilesAnalyzed: false +PackageLicenseConcluded: GPL-2.0-only +PackageLicenseDeclared: GPL-2.0-only + +##### Package: libretls + +PackageName: libretls +SPDXID: SPDXRef-2b021966d19a8211 +PackageVersion: 3.3.4-r3 +FilesAnalyzed: false +PackageLicenseConcluded: ISC AND (BSD-3-Clause OR MIT) +PackageLicenseDeclared: ISC AND (BSD-3-Clause OR MIT) + +##### Package: busybox + +PackageName: busybox +SPDXID: SPDXRef-317ce3476703f20d +PackageVersion: 1.34.1-r5 +FilesAnalyzed: false +PackageLicenseConcluded: GPL-2.0-only +PackageLicenseDeclared: GPL-2.0-only + +##### Package: libcrypto1.1 + +PackageName: libcrypto1.1 +SPDXID: SPDXRef-34f407fb4dbd67f4 +PackageVersion: 1.1.1n-r0 +FilesAnalyzed: false +PackageLicenseConcluded: OpenSSL +PackageLicenseDeclared: OpenSSL + +##### Package: libc-utils + +PackageName: libc-utils +SPDXID: SPDXRef-4bbc1cb449d54083 +PackageVersion: 0.7.2-r3 +FilesAnalyzed: false +PackageLicenseConcluded: BSD-2-Clause AND BSD-3-Clause +PackageLicenseDeclared: BSD-2-Clause AND BSD-3-Clause + +##### Package: alpine-keys + +PackageName: alpine-keys +SPDXID: SPDXRef-a3bdd174be1456b6 +PackageVersion: 2.4-r1 +FilesAnalyzed: false +PackageLicenseConcluded: MIT +PackageLicenseDeclared: MIT + +##### Package: ca-certificates-bundle + +PackageName: ca-certificates-bundle +SPDXID: SPDXRef-ac6472ba26fb991c +PackageVersion: 20211220-r0 +FilesAnalyzed: false +PackageLicenseConcluded: MPL-2.0 AND MIT +PackageLicenseDeclared: MPL-2.0 AND MIT + +##### Package: libssl1.1 + +PackageName: libssl1.1 +SPDXID: SPDXRef-b2d1b1d70fe90f7d +PackageVersion: 1.1.1n-r0 +FilesAnalyzed: false +PackageLicenseConcluded: OpenSSL +PackageLicenseDeclared: OpenSSL + +##### Package: scanelf + +PackageName: scanelf +SPDXID: SPDXRef-c617077ba6649520 +PackageVersion: 1.3.3-r0 +FilesAnalyzed: false +PackageLicenseConcluded: GPL-2.0-only +PackageLicenseDeclared: GPL-2.0-only + +##### Package: musl + +PackageName: musl +SPDXID: SPDXRef-ca80b810029cde0e +PackageVersion: 1.2.2-r7 +FilesAnalyzed: false +PackageLicenseConcluded: MIT +PackageLicenseDeclared: MIT + +##### Package: alpine-baselayout + +PackageName: alpine-baselayout +SPDXID: SPDXRef-d782e64751ba9faa +PackageVersion: 3.2.0-r18 +FilesAnalyzed: false +PackageLicenseConcluded: GPL-2.0-only +PackageLicenseDeclared: GPL-2.0-only + +##### Package: musl-utils + +PackageName: musl-utils +SPDXID: SPDXRef-e5e8a237f6162e22 +PackageVersion: 1.2.2-r7 +FilesAnalyzed: false +PackageLicenseConcluded: MIT BSD GPL2+ +PackageLicenseDeclared: MIT BSD GPL2+ + +##### Package: ssl_client + +PackageName: ssl_client +SPDXID: SPDXRef-fdf0ce84f6337be4 +PackageVersion: 1.34.1-r5 +FilesAnalyzed: false +PackageLicenseConcluded: GPL-2.0-only +PackageLicenseDeclared: GPL-2.0-only +``` + +
+ +SPDX-JSON format is also supported by using `spdx-json` with the `--format` option. + +``` +$ trivy image --format spdx-json --output result.spdx.json alpine:3.15 +``` + +
+Result + +``` +$ cat result.spdx.json | jq . +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2022-04-28T08:16:55.328255Z", + "creators": [ + "Tool: trivy-0.38.1", + "Organization: aquasecurity" + ] + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "http://aquasecurity.github.io/trivy/container_image/alpine:3.15-d9549e3a-a4c5-4ee3-8bde-8c78d451fbe7", + "name": "alpine:3.15", + "packages": [ + { + "SPDXID": "SPDXRef-12bc938ac028a5e1", + "filesAnalyzed": false, + "licenseConcluded": "Zlib", + "licenseDeclared": "Zlib", + "name": "zlib", + "versionInfo": "1.2.12-r0" + }, + { + "SPDXID": "SPDXRef-26c274652190d87f", + "filesAnalyzed": false, + "licenseConcluded": "GPL-2.0-only", + "licenseDeclared": "GPL-2.0-only", + "name": "apk-tools", + "versionInfo": "2.12.7-r3" + }, + { + "SPDXID": "SPDXRef-2b021966d19a8211", + "filesAnalyzed": false, + "licenseConcluded": "ISC AND (BSD-3-Clause OR MIT)", + "licenseDeclared": "ISC AND (BSD-3-Clause OR MIT)", + "name": "libretls", + "versionInfo": "3.3.4-r3" + }, + { + "SPDXID": "SPDXRef-317ce3476703f20d", + "filesAnalyzed": false, + "licenseConcluded": "GPL-2.0-only", + "licenseDeclared": "GPL-2.0-only", + "name": "busybox", + "versionInfo": "1.34.1-r5" + }, + { + "SPDXID": "SPDXRef-34f407fb4dbd67f4", + "filesAnalyzed": false, + "licenseConcluded": "OpenSSL", + "licenseDeclared": "OpenSSL", + "name": "libcrypto1.1", + "versionInfo": "1.1.1n-r0" + }, + { + "SPDXID": "SPDXRef-4bbc1cb449d54083", + "filesAnalyzed": false, + "licenseConcluded": "BSD-2-Clause AND BSD-3-Clause", + "licenseDeclared": "BSD-2-Clause AND BSD-3-Clause", + "name": "libc-utils", + "versionInfo": "0.7.2-r3" + }, + { + "SPDXID": "SPDXRef-a3bdd174be1456b6", + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "name": "alpine-keys", + "versionInfo": "2.4-r1" + }, + { + "SPDXID": "SPDXRef-ac6472ba26fb991c", + "filesAnalyzed": false, + "licenseConcluded": "MPL-2.0 AND MIT", + "licenseDeclared": "MPL-2.0 AND MIT", + "name": "ca-certificates-bundle", + "versionInfo": "20211220-r0" + }, + { + "SPDXID": "SPDXRef-b2d1b1d70fe90f7d", + "filesAnalyzed": false, + "licenseConcluded": "OpenSSL", + "licenseDeclared": "OpenSSL", + "name": "libssl1.1", + "versionInfo": "1.1.1n-r0" + }, + { + "SPDXID": "SPDXRef-c617077ba6649520", + "filesAnalyzed": false, + "licenseConcluded": "GPL-2.0-only", + "licenseDeclared": "GPL-2.0-only", + "name": "scanelf", + "versionInfo": "1.3.3-r0" + }, + { + "SPDXID": "SPDXRef-ca80b810029cde0e", + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "name": "musl", + "versionInfo": "1.2.2-r7" + }, + { + "SPDXID": "SPDXRef-d782e64751ba9faa", + "filesAnalyzed": false, + "licenseConcluded": "GPL-2.0-only", + "licenseDeclared": "GPL-2.0-only", + "name": "alpine-baselayout", + "versionInfo": "3.2.0-r18" + }, + { + "SPDXID": "SPDXRef-e5e8a237f6162e22", + "filesAnalyzed": false, + "licenseConcluded": "MIT BSD GPL2+", + "licenseDeclared": "MIT BSD GPL2+", + "name": "musl-utils", + "versionInfo": "1.2.2-r7" + }, + { + "SPDXID": "SPDXRef-fdf0ce84f6337be4", + "filesAnalyzed": false, + "licenseConcluded": "GPL-2.0-only", + "licenseDeclared": "GPL-2.0-only", + "name": "ssl_client", + "versionInfo": "1.34.1-r5" + } + ], + "spdxVersion": "SPDX-2.2" +} +``` + +
+ +## Scanning +Trivy can take SBOM documents as input for scanning. +See [here](../target/sbom.md) for more details. + +Also, Trivy searches for SBOM files in container images. + +```bash +$ trivy image bitnami/elasticsearch:8.7.1 +``` + +For example, [Bitnami images](https://github.com/bitnami/containers) contain SBOM files in `/opt/bitnami` directory. +Trivy automatically detects the SBOM files and uses them for scanning. +It is enabled in the following targets. + +| Target | Enabled | +| :-------------: | :-----: | +| Container Image | ✓ | +| Filesystem | | +| Rootfs | ✓ | +| Git Repository | | +| VM Image | ✓ | +| Kubernetes | | +| AWS | | +| SBOM | | + + +[spdx]: https://spdx.dev/wp-content/uploads/sites/41/2020/08/SPDX-specification-2-2.pdf + +[cyclonedx]: https://cyclonedx.org/ +[sbom]: https://cyclonedx.org/capabilities/sbom/ +[bov]: https://cyclonedx.org/capabilities/bov/ + +[os_packages]: ../scanner/vulnerability.md#os-packages +[language_packages]: ../scanner/vulnerability.md#language-specific-packages diff --git a/docs/docs/supply-chain/vex.md b/docs/docs/supply-chain/vex.md new file mode 100644 index 000000000000..59f3c5b97353 --- /dev/null +++ b/docs/docs/supply-chain/vex.md @@ -0,0 +1,378 @@ +# Vulnerability Exploitability Exchange (VEX) + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy supports filtering detected vulnerabilities using [the Vulnerability Exploitability Exchange (VEX)](https://www.ntia.gov/files/ntia/publications/vex_one-page_summary.pdf), a standardized format for sharing and exchanging information about vulnerabilities. +By providing VEX during scanning, it is possible to filter vulnerabilities based on their status. +Currently, Trivy supports the following three formats: + +- [CycloneDX](https://cyclonedx.org/capabilities/vex/) +- [OpenVEX](https://github.com/openvex/spec) +- [CSAF](https://oasis-open.github.io/csaf-documentation/specification.html) + +This is still an experimental implementation, with only minimal functionality added. + +## CycloneDX +| Target | Supported | +|:---------------:|:---------:| +| Container Image | | +| Filesystem | | +| Code Repository | | +| VM Image | | +| Kubernetes | | +| SBOM | ✅ | + +There are [two VEX formats](https://cyclonedx.org/capabilities/vex/) for CycloneDX: + +- Independent BOM and VEX BOM +- BOM With Embedded VEX + +Trivy only supports the Independent BOM and VEX BOM format, so you need to provide a separate VEX file alongside the SBOM. +The input SBOM format must be in CycloneDX format. + +The following steps are required: + +1. Generate a CycloneDX SBOM +2. Create a VEX based on the SBOM generated in step 1 +3. Provide the VEX when scanning the CycloneDX SBOM + +### Generate the SBOM +You can generate a CycloneDX SBOM with Trivy as follows: + +```shell +$ trivy image --format cyclonedx --output debian11.sbom.cdx debian:11 +``` + +### Create the VEX +Next, create a VEX based on the generated SBOM. +Multiple vulnerability statuses can be defined under `vulnerabilities`. +Take a look at the example below. + +``` +$ cat < trivy.vex.cdx +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 1, + "vulnerabilities": [ + { + "id": "CVE-2020-8911", + "analysis": { + "state": "not_affected", + "justification": "code_not_reachable", + "response": ["will_not_fix", "update"], + "detail": "The vulnerable function is not called" + }, + "affects": [ + { + "ref": "urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:golang/github.com/aws/aws-sdk-go@1.44.234" + } + ] + } + ] +} +EOF +``` + +This is a VEX document in the CycloneDX format. +The vulnerability ID, such as a CVE-ID or GHSA-ID, should be placed in `vulnerabilities.id`. +When the `analysis.state` is set to `not_affected`, Trivy will not detect the vulnerability. + +BOM-Links must be placed in `affects.ref`. +The BOM-Link has the following syntax and consists of three elements: + +``` +urn:cdx:serialNumber/version#bom-ref +``` + +- serialNumber +- version +- bom-ref + +These values must be obtained from the CycloneDX SBOM. +Please note that while the serialNumber starts with `urn:uuid:`, the BOM-Link starts with `urn:cdx:`. + +The `bom-ref` must contain the BOM-Ref of the package affected by the vulnerability. +In the example above, since the Go package `github.com/aws/aws-sdk-go` is affected by CVE-2020-8911, it was necessary to specify the SBOM's BOM-Ref, `pkg:golang/github.com/aws/aws-sdk-go@1.44.234`. + +For more details on CycloneDX VEX and BOM-Link, please refer to the following links: + +- [CycloneDX VEX](https://cyclonedx.org/capabilities/vex/) +- [BOM-Link](https://cyclonedx.org/capabilities/bomlink/) +- [Examples](https://github.com/CycloneDX/bom-examples/tree/master) + +### Scan SBOM with VEX +Provide the VEX when scanning the CycloneDX SBOM. + +``` +$ trivy sbom trivy.sbom.cdx --vex trivy.vex.cdx +... +2023-04-13T12:55:44.838+0300 INFO Filtered out the detected vulnerability {"VEX format": "CycloneDX", "vulnerability-id": "CVE-2020-8911", "status": "not_affected", "justification": "code_not_reachable"} + +go.mod (gomod) +============== +Total: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + +┌───────────────────────────┬───────────────┬──────────┬───────────────────┬───────────────┬────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├───────────────────────────┼───────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤ +│ github.com/aws/aws-sdk-go │ CVE-2020-8912 │ LOW │ 1.44.234 │ │ aws-sdk-go: In-band key negotiation issue in AWS S3 Crypto │ +│ │ │ │ │ │ SDK for golang... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-8912 │ +└───────────────────────────┴───────────────┴──────────┴───────────────────┴───────────────┴────────────────────────────────────────────────────────────┘ +``` + +CVE-2020-8911 is no longer shown as it is filtered out according to the given CycloneDX VEX document. + +## OpenVEX +| Target | Supported | +|:---------------:|:---------:| +| Container Image | ✅ | +| Filesystem | ✅ | +| Code Repository | ✅ | +| VM Image | ✅ | +| Kubernetes | ✅ | +| SBOM | ✅ | + +Trivy also supports [OpenVEX][openvex] that is designed to be minimal, compliant, interoperable, and embeddable. +OpenVEX can be used in all Trivy targets, unlike CycloneDX VEX. + +The following steps are required: + +1. Create a VEX document +2. Provide the VEX when scanning your target + +### Create the VEX document +Please see also [the example](https://github.com/openvex/examples). +In Trivy, [the Package URL (PURL)][purl] is used as the product identifier. + +``` +$ cat < debian11.openvex +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-2e67563e128250cbcb3e98930df948dd053e43271d70dc50cfa22d57e03fe96f", + "author": "Aqua Security", + "timestamp": "2023-08-29T19:07:16.853479631-06:00", + "version": 1, + "statements": [ + { + "vulnerability": {"name": "CVE-2019-8457"}, + "products": [ + {"@id": "pkg:deb/debian/libdb5.3@5.3.28+dfsg1-0.8"} + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] +} +EOF +``` + +In the above example, PURLs, located in `packages.externalRefs.referenceLocator` in SPDX are used for the product identifier. + +!!! note + If a qualifier is specified in the PURL used as the product id in the VEX, the qualifier is compared. + Other qualifiers are ignored in the comparison. + `pkg:deb/debian/curl@7.50.3-1` in OpenVEX matches `pkg:deb/debian/curl@7.50.3-1?arch=i386`, + while `pkg:deb/debian/curl@7.50.3-1?arch=amd64` does not match `pkg:deb/debian/curl@7.50.3-1?arch=i386`. + +### Scan with VEX +Provide the VEX when scanning your target. + +``` +$ trivy image debian:11 --vex debian11.openvex +... +2023-04-26T17:56:05.358+0300 INFO Filtered out the detected vulnerability {"VEX format": "OpenVEX", "vulnerability-id": "CVE-2019-8457", "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path"} + +debian11.spdx.json (debian 11.6) +================================ +Total: 80 (UNKNOWN: 0, LOW: 58, MEDIUM: 6, HIGH: 16, CRITICAL: 0) +``` + +CVE-2019-8457 is no longer shown as it is filtered out according to the given OpenVEX document. + + +## CSAF +| Target | Supported | +|:---------------:|:---------:| +| Container Image | ✅ | +| Filesystem | ✅ | +| Code Repository | ✅ | +| VM Image | ✅ | +| Kubernetes | ✅ | +| SBOM | ✅ | + +Trivy also supports [CSAF][csaf] format for VEX. +Since CSAF aims to be SBOM format agnostic, both CycloneDX and SPDX formats are available for use as input SBOMs in Trivy. + +The following steps are required: + +1. Create a CSAF document +2. Provide the CSAF when scanning your target + + +### Create the CSAF document +Create a CSAF document in JSON format as follows: + +``` +$ cat < debian11.vex.csaf +{ + "document": { + "category": "csaf_vex", + "csaf_version": "2.0", + "notes": [ + { + "category": "summary", + "text": "Example Company VEX document. Unofficial content for demonstration purposes only.", + "title": "Author comment" + } + ], + "publisher": { + "category": "vendor", + "name": "Example Company ProductCERT", + "namespace": "https://psirt.example.com" + }, + "title": "AquaSecurity example VEX document", + "tracking": { + "current_release_date": "2024-01-01T11:00:00.000Z", + "generator": { + "date": "2024-01-01T11:00:00.000Z", + "engine": { + "name": "Secvisogram", + "version": "1.11.0" + } + }, + "id": "2024-EVD-UC-01-A-001", + "initial_release_date": "2024-01-01T11:00:00.000Z", + "revision_history": [ + { + "date": "2024-01-01T11:00:00.000Z", + "number": "1", + "summary": "Initial version." + } + ], + "status": "final", + "version": "1" + } + }, + "product_tree": { + "branches": [ + { + "branches": [ + { + "branches": [ + { + "category": "product_version", + "name": "5.3", + "product": { + "name": "Database Libraries 5.3", + "product_id": "LIBDB-5328", + "product_identification_helper": { + "purl": "pkg:deb/debian/libdb5.3@5.3.28%2Bdfsg1-0.8?arch=amd64\u0026distro=debian-11.8" + } + } + } + ], + "category": "product_name", + "name": "Database Libraries" + } + ], + "category": "vendor", + "name": "Debian" + } + ] + }, + "vulnerabilities": [ + { + "cve": "CVE-2019-8457", + "notes": [ + { + "category": "description", + "text": "SQLite3 from 3.6.0 to and including 3.27.2 is vulnerable to heap out-of-bound read in the rtreenode() function when handling invalid rtree tables.", + "title": "CVE description" + } + ], + "product_status": { + "known_not_affected": [ + "LIBDB-5328" + ] + }, + "threats": [ + { + "category": "impact", + "details": "Vulnerable code not in execute path.", + "product_ids": [ + "LIBDB-5328" + ] + } + ] + } + ] +} +EOF +``` + +### Scan with CSAF VEX +Provide the CSAF document when scanning your target. + +```console +$ trivy image debian:11 --vex debian11.vex.csaf +... +2024-01-02T10:28:26.704+0100 INFO Filtered out the detected vulnerability {"VEX format": "CSAF", "vulnerability-id": "CVE-2019-8457", "status": "not_affected"} + +debian11.spdx.json (debian 11.6) +================================ +Total: 80 (UNKNOWN: 0, LOW: 58, MEDIUM: 6, HIGH: 16, CRITICAL: 0) +``` + +CVE-2019-8457 is no longer shown as it is filtered out according to the given CSAF document. + +## Appendix +### PURL matching +In the context of VEX, Package URLs (PURLs) are utilized to identify specific software packages and their versions. +The PURL matching specification outlines how PURLs are interpreted for vulnerability exception processing, ensuring precise identification and broad coverage of software packages. + +!!! note + The following PURL matching rules are not formally defined within the current official PURL specification. + Instead, they represent [a community consensus][purl-matching] on how to interpret PURLs. + +Below are the key aspects of the PURL matching rules: + +#### Matching Without Version +A PURL without a specified version (e.g., `pkg:maven/com.google.guava/guava`) matches all versions of that package. +This rule simplifies the application of vulnerability exceptions to all versions of a package. + +**Example**: `pkg:maven/com.google.guava/guava` matches: + +- All versions of `guava`, such as `com.google.guava:guava:24.1.1`, `com.google.guava:guava:30.0`. + +#### Matching Without Qualifiers +A PURL without any qualifiers (e.g., `pkg:maven/com.google.guava/guava@24.1.1`) matches any variation of that package, irrespective of qualifiers. +This approach ensures broad matching capabilities, covering all architectural or platform-specific variations of a package version. + +**Example**: `pkg:maven/com.google.guava/guava@24.1.1` matches: + +- `pkg:maven/com.google.guava/guava@24.1.1?classifier=x86` +- `pkg:maven/com.google.guava/guava@24.1.1?type=pom` + +#### Matching With Specific Qualifiers +A PURL that includes specific qualifiers (e.g., `pkg:maven/com.google.guava/guava@24.1.1?classifier=x86`) matches only those package versions that include the same qualifiers. + +**Example**: `pkg:maven/com.google.guava/guava@24.1.1?classifier=x86` matches: + +- `pkg:maven/com.google.guava/guava@24.1.1?classifier=x86&type=dll` + - Extra qualifiers (e.g., `type=dll`) are ignored. + +does not match: + +- `pkg:maven/com.google.guava/guava@24.1.1` + - `classifier=x86` is missing. +- `pkg:maven/com.google.guava/guava@24.1.1?classifier=sources` + - `classifier` must have the same value. + + +[csaf]: https://oasis-open.github.io/csaf-documentation/specification.html +[openvex]: https://github.com/openvex/spec +[purl]: https://github.com/package-url/purl-spec +[purl-matching]: https://github.com/openvex/spec/issues/27 diff --git a/docs/docs/target/aws.md b/docs/docs/target/aws.md new file mode 100644 index 000000000000..78781646b118 --- /dev/null +++ b/docs/docs/target/aws.md @@ -0,0 +1,109 @@ +# Amazon Web Services + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +The Trivy AWS CLI allows you to scan your AWS account for misconfigurations. +You can either run the CLI locally or integrate it into your CI/CD pipeline. + +Whilst you can already scan the infrastructure-as-code that defines your AWS resources with `trivy config`, you can now scan your live AWS account(s) directly too. + +The included checks cover all of the aspects of the [AWS CIS 1.2](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-cis.html) automated benchmarks. + +Trivy uses the same [authentication methods](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) as the AWS CLI to configure and authenticate your access to the AWS platform. + +You will need permissions configured to read all AWS resources - we recommend using a group/role with the `ReadOnlyAccess` policy attached. + +Once you've scanned your account, you can run additional commands to filter the results without having to run the entire scan again - infrastructure information is cached locally per AWS account/region. + +Trivy currently supports the following scanning for AWS accounts. + +- Misconfigurations + +## CLI Commands + +Scan a full AWS account (all supported services): + +```shell +trivy aws --region us-east-1 +``` + +You can allow Trivy to determine the AWS region etc. by using the standard AWS configuration files and environment variables. The `--region` flag overrides these. + +![AWS Summary Report](../../imgs/trivy-aws.png) + +The summary view is the default when scanning multiple services. + +Scan a specific service: + +```shell +trivy aws --service s3 +``` + +Scan multiple services: + +```shell +# --service s3,ec2 works too +trivy aws --service s3 --service ec2 +``` + +Show results for a specific AWS resource: + +```shell +trivy aws --service s3 --arn arn:aws:s3:::example-bucket +``` + +All ARNs with detected issues will be displayed when showing results for their associated service. + +## Compliance +This section describes AWS specific compliance reports. +For an overview of Trivy's Compliance feature, including working with custom compliance, check out the [Compliance documentation](../compliance/compliance.md). + +### Built in reports + +the following reports are available out of the box: + +| Compliance | Name for command | More info | +|------------------------------------|------------------|------------------------------------------------------------------------------------------------------| +| AWS CIS Foundations Benchmark v1.2 | `aws-cis-1.2` | [link](https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf) | +| AWS CIS Foundations Benchmark v1.4 | `aws-cis-1.4` | [link](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls-1.4.0.html) | + +### Examples + +Scan a cloud account and generate a compliance summary report: + +``` +$ trivy aws --compliance= --report=summary +``` + +***Note*** : The `Issues` column represent the total number of failed checks for this control. + + +Get all of the detailed output for checks: + +``` +$ trivy aws --compliance= --report all +``` + +Report result in JSON format: + +``` +$ trivy aws --compliance= --report all --format json +``` + +## Cached Results + +By default, Trivy will cache a representation of each AWS service for 24 hours. +This means you can filter and view results for a service without having to wait for the entire scan to run again. +If you want to force the cache to be refreshed with the latest data, you can use `--update-cache`. +Or if you'd like to use cached data for a different timeframe, you can specify `--max-cache-age` (e.g. `--max-cache-age 2h`.). +Regardless of whether the cache is used or not, rules will be evaluated again with each run of `trivy aws`. + +## Custom Policies + +You can write custom policies for Trivy to evaluate against your AWS account. +These policies are written in [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/), the same language used by [Open Policy Agent](https://www.openpolicyagent.org/). +See the [Custom Policies](../scanner/misconfiguration/custom/index.md) page for more information on how to write custom policies. + +Custom policies in cloud scanning also support passing in custom data. This can be useful when you want to selectively enable/disable certain aspects of your cloud policies. +See the [Custom Data](../scanner/misconfiguration/custom/data.md) page for more information on how to provide custom data to custom policies. diff --git a/docs/docs/target/container_image.md b/docs/docs/target/container_image.md new file mode 100644 index 000000000000..2ce008922614 --- /dev/null +++ b/docs/docs/target/container_image.md @@ -0,0 +1,509 @@ +# Container Image + +Trivy supports two targets for container images. + +- Files inside container images +- Container image metadata + +## Files inside container images +Container images consist of files. +For instance, new files will be installed if you install a package. + +Trivy scans the files inside container images for + +- Vulnerabilities +- Misconfigurations +- Secrets +- Licenses + +By default, vulnerability and secret scanning are enabled, and you can configure that with `--scanners`. + +### Vulnerabilities +It is enabled by default. +You can simply specify your image name (and a tag). +It detects known vulnerabilities in your container image. +See [here](../scanner/vulnerability.md) for the detail. + +``` +$ trivy image [YOUR_IMAGE_NAME] +``` + +For example: + +``` +$ trivy image python:3.4-alpine +``` + +
+Result + +``` +2019-05-16T01:20:43.180+0900 INFO Updating vulnerability database... +2019-05-16T01:20:53.029+0900 INFO Detecting Alpine vulnerabilities... + +python:3.4-alpine3.9 (alpine 3.9.2) +=================================== +Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0) + ++---------+------------------+----------+-------------------+---------------+--------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++---------+------------------+----------+-------------------+---------------+--------------------------------+ +| openssl | CVE-2019-1543 | MEDIUM | 1.1.1a-r1 | 1.1.1b-r1 | openssl: ChaCha20-Poly1305 | +| | | | | | with long nonces | ++---------+------------------+----------+-------------------+---------------+--------------------------------+ +``` + +
+ +To enable only vulnerability scanning, you can specify `--scanners vuln`. + +```shell +$ trivy image --scanners vuln [YOUR_IMAGE_NAME] +``` + +### Misconfigurations +It is supported, but it is not useful in most cases. +As mentioned [here](../scanner/misconfiguration/index.md), Trivy mainly supports Infrastructure as Code (IaC) files for misconfigurations. +If your container image includes IaC files such as Kubernetes YAML files or Terraform files, you should enable this feature with `--scanners misconfig`. + +``` +$ trivy image --scanners misconfig [YOUR_IMAGE_NAME] +``` + +### Secrets +It is enabled by default. +See [here](../scanner/secret.md) for the detail. + +```shell +$ trivy image [YOUR_IMAGE_NAME] +``` + +### Licenses +It is disabled by default. +See [here](../scanner/license.md) for the detail. + +```shell +$ trivy image --scanners license [YOUR_IMAGE_NAME] +``` + +## Container image metadata +Container images have [configuration](https://github.com/opencontainers/image-spec/blob/2fb996805b3734779bf9a3a84dc9a9691ad7efdd/config.md). +`docker inspect` and `docker history` show the information according to the configuration. + +Trivy scans the configuration of container images for + +- Misconfigurations +- Secrets + +They are disabled by default. +You can enable them with `--image-config-scanners`. + +!!! tips + The configuration can be exported as the JSON file by `docker save`. + +### Misconfigurations +Trivy detects misconfigurations on the configuration of container images. +The image config is converted into Dockerfile and Trivy handles it as Dockerfile. +See [here](../scanner/misconfiguration/index.md) for the detail of Dockerfile scanning. + +It is disabled by default. +You can enable it with `--image-config-scanners config`. + +``` +$ trivy image --image-config-scanners config [YOUR_IMAGE_NAME] +``` + +
+Result + +``` +alpine:3.17 (dockerfile) +======================== +Tests: 24 (SUCCESSES: 21, FAILURES: 3, EXCEPTIONS: 0) +Failures: 3 (UNKNOWN: 0, LOW: 2, MEDIUM: 0, HIGH: 1, CRITICAL: 0) + +HIGH: Specify at least 1 USER command in Dockerfile with non-root user as argument +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile. + +See https://avd.aquasec.com/misconfig/ds002 +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + +LOW: Consider using 'COPY file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /' command instead of 'ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /' +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +You should use COPY instead of ADD unless you want to extract a tar file. Note that an ADD command will extract a tar file, which adds the risk of Zip-based vulnerabilities. Accordingly, it is advised to use a COPY command, which does not extract tar files. + +See https://avd.aquasec.com/misconfig/ds005 +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + alpine:3.17:1 +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 1 [ ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in / +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + +LOW: Add HEALTHCHECK instruction in your Dockerfile +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +You shoud add HEALTHCHECK instruction in your docker container images to perform the health check on running containers. + +See https://avd.aquasec.com/misconfig/ds026 +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +``` +
+ +!!! tip + You can see how each layer is created with `docker history`. + +### Secrets +Trivy detects secrets on the configuration of container images. +The image config is converted into JSON and Trivy scans the file for secrets. +It is especially useful for environment variables that are likely to have credentials by accident. +See [here](../scanner/secret.md) for the detail. + +```shell +$ trivy image --image-config-scanners secret [YOUR_IMAGE_NAME] +``` + +
+Result + +``` +vuln-image (alpine 3.17.1) +========================== +Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + + +vuln-image (secrets) +==================== +Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 2) + +CRITICAL: GitHub (github-pat) +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +GitHub Personal Access Token +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + test:16 +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 14 { + 15 "created": "2023-01-09T17:05:20Z", + 16 [ "created_by": "ENV secret=****************************************", + 17 "comment": "buildkit.dockerfile.v0", +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + +CRITICAL: GitHub (github-pat) +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +GitHub Personal Access Token +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + test:34 +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 32 "Env": [ + 33 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + 34 [ "secret=****************************************" + 35 ] +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + +``` + +
+ +!!! tip + You can see environment variables with `docker inspect`. + +## Supported + +Trivy will look for the specified image in a series of locations. By default, it +will first look in the local Docker Engine, then Containerd, Podman, and +finally container registry. + +This behavior can be modified with the `--image-src` flag. For example, the +command + +```bash +trivy image --image-src podman,containerd alpine:3.7.3 +``` + +Will first search in Podman. If the image is found there, it will be scanned +and the results returned. If the image is not found in Podman, then Trivy will +search in Containerd. If the image is not found there either, the scan will +fail and no more image sources will be searched. + +### Docker Engine +Trivy tries to looks for the specified image in your local Docker Engine. +It will be skipped if Docker Engine is not running locally. + +If your docker socket is not the default path, you can override it via `DOCKER_HOST`. + +### containerd + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy tries to looks for the specified image in your local [containerd](https://containerd.io/). +It will be skipped if containerd is not running locally. + +Specify your image name in containerd running locally. + +```bash +$ nerdctl images +REPOSITORY TAG IMAGE ID CREATED PLATFORM SIZE BLOB SIZE +aquasec/nginx latest 2bcabc23b454 3 hours ago linux/amd64 149.1 MiB 54.1 MiB +$ trivy image aquasec/nginx +``` + +If your containerd socket is not the default path (`//run/containerd/containerd.sock`), you can override it via `CONTAINERD_ADDRESS`. + +```bash +$ export CONTAINERD_ADDRESS=/run/k3s/containerd/containerd.sock +$ trivy image aquasec/nginx +``` + +If your scan targets are images in a namespace other than containerd's default namespace (`default`), you can override it via `CONTAINERD_NAMESPACE`. + +```bash +$ export CONTAINERD_NAMESPACE=k8s.io +$ trivy image aquasec/nginx +``` + +### Podman + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Scan your image in Podman (>=2.0) running locally. The remote Podman is not supported. +Before performing Trivy commands, you must enable the podman.sock systemd service on your machine. +For more details, see [here](https://github.com/containers/podman/blob/master/docs/tutorials/remote_client.md#enable-the-podman-service-on-the-server-machine). + + +```bash +$ systemctl --user enable --now podman.socket +``` + +Then, you can scan your image in Podman. + +```bash +$ cat Dockerfile +FROM alpine:3.12 +RUN apk add --no-cache bash +$ podman build -t test . +$ podman images +REPOSITORY TAG IMAGE ID CREATED SIZE +localhost/test latest efc372d4e0de About a minute ago 7.94 MB +$ trivy image test +``` + +### Container Registry +Trivy supports registries that comply with the following specifications. + +- [Docker Registry HTTP API V2](https://docs.docker.com/registry/spec/api/) +- [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec) + +You can configure credentials with `docker login`. +See [here](../advanced/private-registries/index.md) for the detail. + +### Tar Files +Trivy supports image tar files generated by the following tools. + +- [Docker Image Specification](https://github.com/moby/moby/tree/master/image/spec) + - [Moby Project](https://github.com/moby/moby/) + - [Buildah](https://github.com/containers/buildah) + - [Podman](https://github.com/containers/podman) + - [img](https://github.com/genuinetools/img) +- [Kaniko](https://github.com/GoogleContainerTools/kaniko) + +``` +$ docker pull ruby:3.1-alpine3.15 +$ docker save ruby:3.1-alpine3.15 -o ruby-3.1.tar +$ trivy image --input ruby-3.1.tar +``` + +
+Result + +``` +2022-02-03T10:08:19.127Z INFO Detected OS: alpine +2022-02-03T10:08:19.127Z WARN This OS version is not on the EOL list: alpine 3.15 +2022-02-03T10:08:19.127Z INFO Detecting Alpine vulnerabilities... +2022-02-03T10:08:19.127Z INFO Number of language-specific files: 2 +2022-02-03T10:08:19.127Z INFO Detecting gemspec vulnerabilities... +2022-02-03T10:08:19.128Z INFO Detecting node-pkg vulnerabilities... +2022-02-03T10:08:19.128Z WARN This OS version is no longer supported by the distribution: alpine 3.15.0 +2022-02-03T10:08:19.128Z WARN The vulnerability detection may be insufficient because security updates are not provided + +ruby-3.1.tar (alpine 3.15.0) +============================ +Total: 3 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 3, CRITICAL: 0) + ++----------+------------------+----------+-------------------+---------------+---------------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++----------+------------------+----------+-------------------+---------------+---------------------------------------+ +| gmp | CVE-2021-43618 | HIGH | 6.2.1-r0 | 6.2.1-r1 | gmp: Integer overflow and resultant | +| | | | | | buffer overflow via crafted input | +| | | | | | -->avd.aquasec.com/nvd/cve-2021-43618 | ++----------+ + + + + + +| gmp-dev | | | | | | +| | | | | | | +| | | | | | | ++----------+ + + + + + +| libgmpxx | | | | | | +| | | | | | | +| | | | | | | ++----------+------------------+----------+-------------------+---------------+---------------------------------------+ + +Node.js (node-pkg) +================== +Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + + +Ruby (gemspec) +============== +Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) +``` + +
+ +### OCI Layout +Trivy supports image directories compliant with [Open Container Image Layout Specification](https://github.com/opencontainers/image-spec/blob/master/spec.md). + +Buildah: + +``` +$ buildah push docker.io/library/alpine:3.11 oci:/path/to/alpine +$ trivy image --input /path/to/alpine +``` + +Skopeo: + +``` +$ skopeo copy docker-daemon:alpine:3.11 oci:/path/to/alpine +$ trivy image --input /path/to/alpine +``` + +Referencing specific images can be done by their tag or by their manifest digest: +``` +# Referenced by tag +$ trivy image --input /path/to/alpine:3.15 + +# Referenced by digest +$ trivy image --input /path/to/alpine@sha256:82389ea44e50c696aba18393b168a833929506f5b29b9d75eb817acceb6d54ba +``` + +## SBOM +Trivy supports the generation of Software Bill of Materials (SBOM) for container images and the search for SBOMs during vulnerability scanning. + +### Generation +Trivy can generate SBOM for container images. +See [here](../supply-chain/sbom.md) for the detail. + +### Discovery +Trivy can search for Software Bill of Materials (SBOMs) that reference container images. +If an SBOM is found, the vulnerability scan is performed using the SBOM instead of the container image. +By using the SBOM, you can perform a vulnerability scan more quickly, as it allows you to skip pulling the container image and analyzing its layers. + +To enable this functionality, you need to specify the `--sbom-sources` flag. +The following two sources are supported: + +- OCI Registry (`oci`) +- Rekor (`rekor`) + +Example: + +```bash +$ trivy image --sbom-sources oci ghcr.io/knqyf263/oci-referrers +2023-03-05T17:36:55.278+0200 INFO Vulnerability scanning is enabled +2023-03-05T17:36:58.103+0200 INFO Detected SBOM format: cyclonedx-json +2023-03-05T17:36:58.129+0200 INFO Found SBOM (cyclonedx) in the OCI referrers +... + +ghcr.io/knqyf263/oci-referrers (alpine 3.16.2) +============================================== +Total: 17 (UNKNOWN: 0, LOW: 0, MEDIUM: 5, HIGH: 9, CRITICAL: 3) +``` + +The OCI Registry utilizes the [Referrers API](https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers). +For more information about Rekor, please refer to [its documentation](../supply-chain/attestation/rekor.md). + +## Compliance + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +This section describes container image specific compliance reports. +For an overview of Trivy's Compliance feature, including working with custom compliance, check out the [Compliance documentation](../compliance/compliance.md). + +### Built in reports + +The following reports are available out of the box: + +| Compliance | Version | Name for command | More info | +|----------------------------------------|---------|------------------|---------------------------------------------------------------------------------------------| +| CIS Docker Community Edition Benchmark | 1.1.0 | `docker-cis` | [Link](https://www.aquasec.com/cloud-native-academy/docker-container/docker-cis-benchmark/) | + +### Examples + +Scan a container image configuration and generate a compliance summary report: + +``` +$ trivy image --compliance docker-cis [YOUR_IMAGE_NAME] +``` + +!!! note + The `Issues` column represent the total number of failed checks for this control. + +## Authentication +Please reference [this page](../advanced/private-registries/index.md). + +## Options +### Scan Image on a specific Architecture and OS +By default, Trivy loads an image on a "linux/amd64" machine. +To customise this, pass a `--platform` argument in the format OS/Architecture for the image: + +``` +$ trivy image --platform=os/architecture [YOUR_IMAGE_NAME] +``` + +For example: + +``` +$ trivy image --platform=linux/arm alpine:3.16.1 +``` + +
+Result + +``` +2022-10-25T21:00:50.972+0300 INFO Vulnerability scanning is enabled +2022-10-25T21:00:50.972+0300 INFO Secret scanning is enabled +2022-10-25T21:00:50.972+0300 INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning +2022-10-25T21:00:50.972+0300 INFO Please see also https://aquasecurity.github.io/trivy/dev/docs/secret/scanning/#recommendation for faster secret detection +2022-10-25T21:00:56.190+0300 INFO Detected OS: alpine +2022-10-25T21:00:56.190+0300 INFO Detecting Alpine vulnerabilities... +2022-10-25T21:00:56.191+0300 INFO Number of language-specific files: 0 + +alpine:3.16.1 (alpine 3.16.1) +============================= +Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1) + +┌─────────┬────────────────┬──────────┬───────────────────┬───────────────┬─────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├─────────┼────────────────┼──────────┼───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤ +│ zlib │ CVE-2022-37434 │ CRITICAL │ 1.2.12-r1 │ 1.2.12-r2 │ zlib: heap-based buffer over-read and overflow in inflate() │ +│ │ │ │ │ │ in inflate.c via a... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-37434 │ +└─────────┴────────────────┴──────────┴───────────────────┴───────────────┴─────────────────────────────────────────────────────────────┘ +``` + +
+ +### Configure Docker daemon socket to connect to. +You can configure Docker daemon socket with `DOCKER_HOST` or `--docker-host`. + +```shell +$ trivy image --docker-host tcp://127.0.0.1:2375 YOUR_IMAGE +``` + +### Configure Podman daemon socket to connect to. +You can configure Podman daemon socket with `--podman-host`. + +```shell +$ trivy image --podman-host /run/user/1000/podman/podman.sock YOUR_IMAGE +``` \ No newline at end of file diff --git a/docs/docs/target/filesystem.md b/docs/docs/target/filesystem.md new file mode 100644 index 000000000000..72e47f0e0c5e --- /dev/null +++ b/docs/docs/target/filesystem.md @@ -0,0 +1,93 @@ +# Filesystem + +Scan your local projects for + +- Vulnerabilities +- Misconfigurations +- Secrets +- Licenses + +By default, vulnerability and secret scanning are enabled, and you can configure that with `--scanners`. + +```bash +$ trivy fs /path/to/project +``` + +It's also possible to scan a single file. + +``` +$ trivy fs ~/src/github.com/aquasecurity/trivy-ci-test/Pipfile.lock +``` + +## Scanners +### Vulnerabilities +It is enabled by default. +Trivy will look for vulnerabilities based on lock files such as Gemfile.lock and package-lock.json. +See [here](../scanner/vulnerability.md) for the detail. + +``` +$ trivy fs ~/src/github.com/aquasecurity/trivy-ci-test +``` + +
+Result + +``` +2020-06-01T17:06:58.652+0300 WARN OS is not detected and vulnerabilities in OS packages are not detected. +2020-06-01T17:06:58.652+0300 INFO Detecting pipenv vulnerabilities... +2020-06-01T17:06:58.691+0300 INFO Detecting cargo vulnerabilities... + +Pipfile.lock +============ +Total: 10 (UNKNOWN: 2, LOW: 0, MEDIUM: 6, HIGH: 2, CRITICAL: 0) + ++---------------------+------------------+----------+-------------------+------------------------+------------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++---------------------+------------------+----------+-------------------+------------------------+------------------------------------+ +| django | CVE-2020-7471 | HIGH | 2.0.9 | 3.0.3, 2.2.10, 1.11.28 | django: potential | +| | | | | | SQL injection via | +| | | | | | StringAgg(delimiter) | ++ +------------------+----------+ +------------------------+------------------------------------+ +| | CVE-2019-19844 | MEDIUM | | 3.0.1, 2.2.9, 1.11.27 | Django: crafted email address | +| | | | | | allows account takeover | ++ +------------------+ + +------------------------+------------------------------------+ +| | CVE-2019-3498 | | | 2.1.5, 2.0.10, 1.11.18 | python-django: Content | +| | | | | | spoofing via URL path in | +| | | | | | default 404 page | ++ +------------------+ + +------------------------+------------------------------------+ +| | CVE-2019-6975 | | | 2.1.6, 2.0.11, 1.11.19 | python-django: | +| | | | | | memory exhaustion in | +| | | | | | django.utils.numberformat.format() | ++---------------------+------------------+----------+-------------------+------------------------+------------------------------------+ +... +``` + +
+ +### Misconfigurations +It is disabled by default and can be enabled with `--scanners misconfig`. +See [here](../scanner/misconfiguration/index.md) for the detail. + +```shell +$ trivy fs --scanners misconfig /path/to/project +``` + +### Secrets +It is enabled by default. +See [here](../scanner/secret.md) for the detail. + +```shell +$ trivy fs /path/to/project +``` + +### Licenses +It is disabled by default. +See [here](../scanner/license.md) for the detail. + +```shell +$ trivy fs --scanners license /path/to/project +``` + +## SBOM generation +Trivy can generate SBOM for local projects. +See [here](../supply-chain/sbom.md) for the detail. diff --git a/docs/docs/target/kubernetes.md b/docs/docs/target/kubernetes.md new file mode 100644 index 000000000000..88986b6d3eff --- /dev/null +++ b/docs/docs/target/kubernetes.md @@ -0,0 +1,401 @@ +# Kubernetes + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy can connect to your Kubernetes cluster and scan it for security issues using the `trivy k8s` command. This page covers the technical capabilities of Trivy Kubernetes scanning. +Trivy can also be installed *inside* your cluster as a Kubernetes Operator, and continuously scan it. For more about this, please see the [Trivy Operator](https://aquasecurity.github.io/trivy-operator/) project. + +When scanning a Kubernetes cluster, Trivy differentiates between the following: + +1. Cluster infrastructure (e.g api-server, kubelet, addons) +1. Cluster configuration (e.g Roles, ClusterRoles). +1. Application workloads (e.g nginx, postgresql). + +When scanning any of the above, the container image is scanned separately to the Kubernetes resource definition (the YAML manifest) that defines the resource. + +Container image is scanned for: + +- Vulnerabilities +- Misconfigurations +- Exposed secrets + +Kubernetes resource definition is scanned for: + +- Vulnerabilities (Open Source Libraries, Control Plane and Node Components) +- Misconfigurations +- Exposed secrets + +## Kubernetes target configurations + +Trivy follows the behavior of the `kubectl` tool as much as possible. + +### Scope + +The command expects an argument that selects the scope of the scan (similarly to how `kubectl` expects an argument after `kubectl get`). This argument can be: +1. A Kubernetes Kind. e.g `pod`, `deployment`, etc. +2. A Kubernetes Resource. e.g `pods/mypod`, etc. +3. `all`. Scan common workload kinds, as listed [here](https://github.com/aquasecurity/trivy-kubernetes/blob/bf8cc2a00d9772e0aa271f06d375b936152b54b1/pkg/k8s/k8s.go#L296:L314) +4. `cluster` scan the entire cluster including all namespaced resources and cluster level resources. + +Examples: + +``` +trivy k8s all +trivy k8s pods +trivy k8s deploy myapp +trivy k8s pod/mypod +trivy k8s pods,deploy +trivy k8s cluster +``` + +Note that the scope argument must appear last in the command line, after any other flag. + +### Cluster + +By default Trivy will look for a [`kubeconfig` configuration file in the default location](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/), and use the default cluster that is specified. +You can also specify a `kubeconfig` using the `--kubeconfig` flag: + +``` +trivy k8s --kubeconfig ~/.kube/config2 +``` + +### Namespace + +By default Trivy will scan all namespaces (following `kubectl` behavior). To specify a namespace use the `--namespace` flag: + +``` +trivy k8s --kubeconfig ~/.kube/config2 --namespace default +``` +### Node + +You can exclude specific nodes from the scan using the `--exclude-nodes` flag, which takes a label in the format `label-name:label-value` and excludes all matching nodes: + +``` +trivy k8s cluster --report summary --exclude-nodes kubernetes.io/arch:arm6 +``` + +## Control Plane and Node Components Vulnerability Scanning + +Trivy is capable of discovering Kubernetes control plane (apiserver, controller-manager and etc) and node components(kubelet, kube-proxy and etc), matching them against the [official Kubernetes vulnerability database feed](https://github.com/aquasecurity/vuln-list-k8s), and reporting any vulnerabilities it finds + + +``` +trivy k8s cluster --scanners vuln --report all + +NodeComponents/kind-control-plane (kubernetes) + +Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 2, CRITICAL: 0) + +┌────────────────┬────────────────┬──────────┬────────┬───────────────────┬──────────────────────────────────┬───────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├────────────────┼────────────────┼──────────┼────────┼───────────────────┼──────────────────────────────────┼───────────────────────────────────────────────────┤ +│ k8s.io/kubelet │ CVE-2023-2431 │ LOW │ fixed │ 1.21.1 │ 1.24.14, 1.25.10, 1.26.5, 1.27.2 │ Bypass of seccomp profile enforcement │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-2431 │ +│ ├────────────────┼──────────┤ │ ├──────────────────────────────────┼───────────────────────────────────────────────────┤ +│ │ CVE-2021-25741 │ HIGH │ │ │ 1.19.16, 1.20.11, 1.21.5, 1.22.1 │ Symlink exchange can allow host filesystem access │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-25741 │ +│ ├────────────────┤ │ │ ├──────────────────────────────────┼───────────────────────────────────────────────────┤ +│ │ CVE-2021-25749 │ │ │ │ 1.22.14, 1.23.11, 1.24.5 │ runAsNonRoot logic bypass for Windows containers │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-25749 │ +└────────────────┴────────────────┴──────────┴────────┴───────────────────┴──────────────────────────────────┴───────────────────────────────────────────────────┘ +``` + + +### Components types + +You can control what kinds of components are discovered using the `--components` flag: +- `--components infra` will discover only cluster infrastructure components. +- `--components workloads` will discover only application workloads. +- If the flag is omitted: infra, workloads, and RBAC are discovered. + +## Reporting and filtering + +Since scanning an entire cluster for any security issue can be overwhelming, By default Trivy summarizes the results in a simple "summary" view. +By scoping the scan on a specific resource, you can see the detailed report. +You can always choose the report granularity using the `--report summary`/`--report all` flag. + +Scan a full cluster and generate a simple summary report: + +``` +$ trivy k8s --report=summary cluster +``` + +![k8s Summary Report](../../imgs/trivy-k8s.png) + +Filter by severity: + +``` +trivy k8s --severity=CRITICAL --report=all cluster +``` + +Filter by scanners (Vulnerabilities, Secrets or Misconfigurations): + +``` +trivy k8s --scanners=secret --report=summary cluster +# or +trivy k8s --scanners=misconfig --report=summary cluster +``` + +The supported output formats are `table`, which is the default, and `json`. + +``` +trivy k8s --format json -o results.json cluster +``` + +
+Result + +```json +{ + "ClusterName": "minikube", + "Vulnerabilities": [ + { + "Namespace": "default", + "Kind": "Deployment", + "Name": "app", + "Results": [ + { + "Target": "ubuntu:latest (ubuntu 22.04)", + "Class": "os-pkgs", + "Type": "ubuntu", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2016-2781", + "PkgName": "coreutils", + "InstalledVersion": "8.32-4.1ubuntu1", + "Layer": { + "Digest": "sha256:125a6e411906fe6b0aaa50fc9d600bf6ff9bb11a8651727ce1ed482dc271c24c", + "DiffID": "sha256:e59fc94956120a6c7629f085027578e6357b48061d45714107e79f04a81a6f0c" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2016-2781", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "coreutils: Non-privileged session can escape to the parent session in chroot", + "Description": "chroot in GNU coreutils, when used with --userspec, allows local users to escape to the parent session via a crafted TIOCSTI ioctl call, which pushes characters to the terminal's input buffer.", + "Severity": "LOW", + "CweIDs": [ + "CWE-20" + ], + "VendorSeverity": { + "cbl-mariner": 2, + "nvd": 2, + "redhat": 2, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:N/I:P/A:N", + "V3Vector": "CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", + "V2Score": 2.1, + "V3Score": 6.5 + }, + "redhat": { + "V2Vector": "AV:L/AC:H/Au:N/C:C/I:C/A:C", + "V3Vector": "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H", + "V2Score": 6.2, + "V3Score": 8.6 + } + }, + "References": [ + "http://seclists.org/oss-sec/2016/q1/452", + "http://www.openwall.com/lists/oss-security/2016/02/28/2", + "http://www.openwall.com/lists/oss-security/2016/02/28/3", + "https://access.redhat.com/security/cve/CVE-2016-2781", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-2781", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://lore.kernel.org/patchwork/patch/793178/", + "https://nvd.nist.gov/vuln/detail/CVE-2016-2781" + ], + "PublishedDate": "2017-02-07T15:59:00Z", + "LastModifiedDate": "2021-02-25T17:15:00Z" + } + ] + } + ] + } + ], + "Misconfigurations": [ + { + "Namespace": "default", + "Kind": "Deployment", + "Name": "app", + "Results": [ + { + "Target": "Deployment/app", + "Class": "config", + "Type": "kubernetes", + "MisconfSummary": { + "Successes": 20, + "Failures": 19, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Kubernetes Security Check", + "ID": "KSV001", + "Title": "Process can elevate its own privileges", + "Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", + "Message": "Container 'app' of Deployment 'app' should set 'securityContext.allowPrivilegeEscalation' to false", + "Namespace": "builtin.kubernetes.KSV001", + "Query": "data.builtin.kubernetes.KSV001.deny", + "Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv001" + ], + "Status": "FAIL", + "Layer": {}, + "IacMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 121, + "EndLine": 133 + } + }, + { + "Type": "Kubernetes Security Check", + "ID": "KSV003", + "Title": "Default capabilities not dropped", + "Description": "The container should drop all default capabilities and add only those that are needed for its execution.", + "Message": "Container 'app' of Deployment 'app' should add 'ALL' to 'securityContext.capabilities.drop'", + "Namespace": "builtin.kubernetes.KSV003", + "Query": "data.builtin.kubernetes.KSV003.deny", + "Resolution": "Add 'ALL' to containers[].securityContext.capabilities.drop.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv003", + "References": [ + "https://kubesec.io/basics/containers-securitycontext-capabilities-drop-index-all/", + "https://avd.aquasec.com/misconfig/ksv003" + ], + "Status": "FAIL", + "Layer": {}, + "IacMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 121, + "EndLine": 133 + } + } + ] + } + ] + }, + { + "Namespace": "default", + "Kind": "ConfigMap", + "Name": "kube-root-ca.crt" + } + ] +} + +``` + +
+ +## Compliance +This section describes Kubernetes specific compliance reports. +For an overview of Trivy's Compliance feature, including working with custom compliance, check out the [Compliance documentation](../compliance/compliance.md). + +The following reports are available out of the box: + +| Compliance | Name for command | More info | +|----------------------------------------------|----------------------|---------------------------------------------------------------------------------------------------------------------| +| NSA, CISA Kubernetes Hardening Guidance v1.2 | `k8s-nsa` | [Link](https://media.defense.gov/2022/Aug/29/2003066362/-1/-1/0/CTR_KUBERNETES_HARDENING_GUIDANCE_1.2_20220829.PDF) | +| CIS Benchmark for Kubernetes v1.23 | `k8s-cis` | [Link](https://www.cisecurity.org/benchmark/kubernetes) | +| Pod Security Standards, Baseline | `k8s-pss-baseline` | [Link](https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline) | +| Pod Security Standards, Restricted | `k8s-pss-restricted` | [Link](https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted) | + +Examples: + +Scan the cluster for Kubernetes Pod Security Standards Baseline compliance: + +``` + +$ trivy k8s cluster --compliance=k8s-pss-baseline --report summary + +``` + +Get the detailed report for checks: + +``` + +$ trivy k8s cluster --compliance=k8s-cis --report all + +``` + +Get summary report in JSON format: + +``` + +$ trivy k8s cluster --compliance=k8s-cis --report summary --format json + +``` + +Get detailed report in JSON format: + +``` + +$ trivy k8s cluster --compliance=k8s-cis --report all --format json + +``` + +## KBOM + +KBOM, Kubernetes Bill of Materials, is a manifest of all the important components that make up your Kubernetes cluster – Control plane components, Node Components, and Addons, including their versions and images. Which “api-server†version are you currently running? Which flavor of "kubelet" is running on each node? What kind of etcd or storage are you currently using? And most importantly – are there any vulnerabilities known to affect these components? These are all questions that KBOM can help you answer. +For more background on KBOM, see [here](https://blog.aquasec.com/introducing-kbom-kubernetes-bill-of-materials). + +Trivy can generate KBOM in CycloneDX format: + +```sh + +$ trivy k8s cluster --format cyclonedx --output mykbom.cdx.json + +``` + +Trivy can also scan that generated KBOM (or any SBOM) for vulnerabilities: + +```sh + +$ trivy sbom mykbom.cdx.json + +``` + +
+Result + +```sh + +2023-09-28T22:52:25.707+0300 INFO Vulnerability scanning is enabled + 2023-09-28T22:52:25.707+0300 INFO Detected SBOM format: cyclonedx-json + 2023-09-28T22:52:25.717+0300 WARN No OS package is detected. Make sure you haven't deleted any files that contain information about the installed packages. + 2023-09-28T22:52:25.717+0300 WARN e.g. files under "/lib/apk/db/", "/var/lib/dpkg/" and "/var/lib/rpm" + 2023-09-28T22:52:25.717+0300 INFO Detected OS: debian gnu/linux + 2023-09-28T22:52:25.717+0300 WARN unsupported os : debian gnu/linux + 2023-09-28T22:52:25.717+0300 INFO Number of language-specific files: 3 + 2023-09-28T22:52:25.717+0300 INFO Detecting kubernetes vulnerabilities... + 2023-09-28T22:52:25.718+0300 INFO Detecting gobinary vulnerabilities... + Kubernetes (kubernetes) + Total: 2 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 1, CRITICAL: 0) + ┌────────────────┬────────────────┬──────────┬────────┬───────────────────┬─────────────────────────────────┬──────────────────────────────────────────────────┠+ │ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ + ├────────────────┼────────────────┼──────────┼────────┼───────────────────┼─────────────────────────────────┼──────────────────────────────────────────────────┤ + │ k8s.io/kubelet │ CVE-2021-25749 │ HIGH │ fixed │ 1.24.0 │ 1.22.14, 1.23.11, 1.24.5 │ runAsNonRoot logic bypass for Windows containers │ + │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-25749 │ + │ ├────────────────┼──────────┤ │ ├─────────────────────────────────┼──────────────────────────────────────────────────┤ + │ │ CVE-2023-2431 │ LOW │ │ │ 1.24.14, 1.25.9, 1.26.4, 1.27.1 │ Bypass of seccomp profile enforcement │ + │ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-2431 │ + └────────────────┴────────────────┴──────────┴────────┴───────────────────┴─────────────────────────────────┴──────────────────────────────────────────────────┘ +``` + +
+ +Find more in the [documentation for SBOM scanning](./sbom.md). + +Currently KBOM vulnerability matching works for plain Kubernetes distributions and does not work well for vendor variants, including some cloud managed distributions. diff --git a/docs/docs/target/repository.md b/docs/docs/target/repository.md new file mode 100644 index 000000000000..a4385825e33f --- /dev/null +++ b/docs/docs/target/repository.md @@ -0,0 +1,155 @@ +# Code Repository + +Scan your local or remote code repositories for + +- Vulnerabilities +- Misconfigurations +- Secrets +- Licenses + +By default, vulnerability and secret scanning are enabled, and you can configure that with `--scanners`. + +```bash +$ trivy repo (REPO_PATH | REPO_URL) +``` + +For example, you can scan a local repository as below. + +```bash +$ trivy repo ./ +``` + +It's also possible to scan a single file. + +``` +$ trivy repo ./trivy-ci-test/Pipfile.lock +``` + +To scan remote code repositories, you need to specify the URL. + +```bash +$ trivy repo https://github.com/aquasecurity/trivy-ci-test +``` + +## Rationale +`trivy repo` is designed to scan code repositories, and it is intended to be used for scanning local/remote repositories in your machine or in your CI environment. +Therefore, unlike container/VM image scanning, it targets lock files such as package-lock.json and does not target artifacts like JAR files, binary files, etc. +See [here](../scanner/vulnerability.md#language-specific-packages) for the detail. + +## Scanners +### Vulnerabilities +It is enabled by default. +Trivy will look for vulnerabilities based on lock files such as Gemfile.lock and package-lock.json. +See [here](../scanner/vulnerability.md) for the detail. + +``` +$ trivy repo ~/src/github.com/aquasecurity/trivy-ci-test +``` + +
+Result + +``` +2020-06-01T17:06:58.652+0300 WARN OS is not detected and vulnerabilities in OS packages are not detected. +2020-06-01T17:06:58.652+0300 INFO Detecting pipenv vulnerabilities... +2020-06-01T17:06:58.691+0300 INFO Detecting cargo vulnerabilities... + +Pipfile.lock +============ +Total: 10 (UNKNOWN: 2, LOW: 0, MEDIUM: 6, HIGH: 2, CRITICAL: 0) + ++---------------------+------------------+----------+-------------------+------------------------+------------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++---------------------+------------------+----------+-------------------+------------------------+------------------------------------+ +| django | CVE-2020-7471 | HIGH | 2.0.9 | 3.0.3, 2.2.10, 1.11.28 | django: potential | +| | | | | | SQL injection via | +| | | | | | StringAgg(delimiter) | ++ +------------------+----------+ +------------------------+------------------------------------+ +| | CVE-2019-19844 | MEDIUM | | 3.0.1, 2.2.9, 1.11.27 | Django: crafted email address | +| | | | | | allows account takeover | ++ +------------------+ + +------------------------+------------------------------------+ +| | CVE-2019-3498 | | | 2.1.5, 2.0.10, 1.11.18 | python-django: Content | +| | | | | | spoofing via URL path in | +| | | | | | default 404 page | ++ +------------------+ + +------------------------+------------------------------------+ +| | CVE-2019-6975 | | | 2.1.6, 2.0.11, 1.11.19 | python-django: | +| | | | | | memory exhaustion in | +| | | | | | django.utils.numberformat.format() | ++---------------------+------------------+----------+-------------------+------------------------+------------------------------------+ +... +``` + +
+ +### Misconfigurations +It is disabled by default and can be enabled with `--scanners misconfig`. +See [here](../scanner/misconfiguration/index.md) for the detail. + +```shell +$ trivy repo --scanners misconfig (REPO_PATH | REPO_URL) +``` + +### Secrets +It is enabled by default. +See [here](../scanner/secret.md) for the detail. + +```shell +$ trivy repo (REPO_PATH | REPO_URL) +``` + +### Licenses +It is disabled by default. +See [here](../scanner/license.md) for the detail. + +```shell +$ trivy repo --scanners license (REPO_PATH | REPO_URL) +``` + +## SBOM generation +Trivy can generate SBOM for code repositories. +See [here](../supply-chain/sbom.md) for the detail. + +## References +The following flags and environmental variables are available for remote git repositories. + +### Scanning a Branch + +Pass a `--branch` argument with a valid branch name on the remote repository provided: + +``` +$ trivy repo --branch +``` + +### Scanning upto a Commit + +Pass a `--commit` argument with a valid commit hash on the remote repository provided: + +``` +$ trivy repo --commit +``` + +### Scanning a Tag + +Pass a `--tag` argument with a valid tag on the remote repository provided: + +``` +$ trivy repo --tag +``` + +### Scanning Private Repositories +In order to scan private GitHub or GitLab repositories, the environment variable `GITHUB_TOKEN` or `GITLAB_TOKEN` must be set, respectively, with a valid token that has access to the private repository being scanned. + +The `GITHUB_TOKEN` environment variable will take precedence over `GITLAB_TOKEN`, so if a private GitLab repository will be scanned, then `GITHUB_TOKEN` must be unset. + +You can find how to generate your GitHub Token in the following [GitHub documentation.](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) + +For example: + +``` +$ export GITHUB_TOKEN="your_private_github_token" +$ trivy repo + +# or +$ export GITLAB_TOKEN="your_private_gitlab_token" +$ trivy repo +``` diff --git a/docs/docs/target/rootfs.md b/docs/docs/target/rootfs.md new file mode 100644 index 000000000000..1b6b7438b6a4 --- /dev/null +++ b/docs/docs/target/rootfs.md @@ -0,0 +1,15 @@ +# Rootfs +Rootfs scanning is for special use cases such as + +- Host machine +- [Root filesystem](../advanced/container/embed-in-dockerfile.md) +- [Unpacked filesystem](../advanced/container/unpacked-filesystem.md) + +```bash +$ trivy rootfs /path/to/rootfs +``` + +!!! note + Rootfs scanning works differently from the Filesystem scanning. + You should use `trivy fs` to scan your local projects in CI/CD. + See [here](../scanner/vulnerability.md) for the differences. diff --git a/docs/docs/target/sbom.md b/docs/docs/target/sbom.md new file mode 100644 index 000000000000..4ea50035df1c --- /dev/null +++ b/docs/docs/target/sbom.md @@ -0,0 +1,152 @@ +# SBOM scanning + +Trivy can take the following SBOM formats as an input and scan for vulnerabilities and licenses. + +- CycloneDX +- SPDX +- SPDX JSON +- CycloneDX-type attestation +- [KBOM](./kubernetes.md#KBOM) in CycloneDX format + +To scan SBOM, you can use the `sbom` subcommand and pass the path to the SBOM. +The input format is automatically detected. + +```bash + +$ trivy sbom /path/to/sbom_file + +``` + +By default, vulnerability scan in SBOM is executed. You can use `--scanners vuln,license` +command property to select also license scan, or `--scanners license` alone. + +!!! note + Passing SBOMs generated by tool other than Trivy may result in inaccurate detection + because Trivy relies on custom properties in SBOM for accurate scanning. + +## CycloneDX + +Trivy supports CycloneDX as an input. + +!!! note + CycloneDX XML is not supported at the moment. + +```bash +$ trivy sbom /path/to/cyclonedx.json +``` + +## SPDX + +Trivy supports the SPDX SBOM as an input. + +The following SPDX formats are supported: + +- Tag-value (`--format spdx`) +- JSON (`--format spdx-json`) + +```bash +$ trivy image --format spdx-json --output spdx.json alpine:3.16.0 +$ trivy sbom spdx.json +``` + +
+Result + +``` +2022-09-15T21:32:27.168+0300 INFO Vulnerability scanning is enabled +2022-09-15T21:32:27.169+0300 INFO Detected SBOM format: spdx-json +2022-09-15T21:32:27.210+0300 INFO Detected OS: alpine +2022-09-15T21:32:27.210+0300 INFO Detecting Alpine vulnerabilities... +2022-09-15T21:32:27.211+0300 INFO Number of language-specific files: 0 + +spdx.json (alpine 3.16.0) +========================= +Total: 5 (UNKNOWN: 0, LOW: 0, MEDIUM: 2, HIGH: 2, CRITICAL: 1) + +┌──────────────┬────────────────┬──────────┬───────────────────┬───────────────┬────────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├──────────────┼────────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤ +│ busybox │ CVE-2022-30065 │ HIGH │ 1.35.0-r13 │ 1.35.0-r15 │ busybox: A use-after-free in Busybox's awk applet leads to │ +│ │ │ │ │ │ denial of service... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-30065 │ +├──────────────┼────────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤ +│ libcrypto1.1 │ CVE-2022-2097 │ MEDIUM │ 1.1.1o-r0 │ 1.1.1q-r0 │ openssl: AES OCB fails to encrypt some bytes │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-2097 │ +├──────────────┤ │ │ │ │ │ +│ libssl1.1 │ │ │ │ │ │ +│ │ │ │ │ │ │ +├──────────────┼────────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤ +│ ssl_client │ CVE-2022-30065 │ HIGH │ 1.35.0-r13 │ 1.35.0-r15 │ busybox: A use-after-free in Busybox's awk applet leads to │ +│ │ │ │ │ │ denial of service... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-30065 │ +├──────────────┼────────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤ +│ zlib │ CVE-2022-37434 │ CRITICAL │ 1.2.12-r1 │ 1.2.12-r2 │ zlib: a heap-based buffer over-read or buffer overflow in │ +│ │ │ │ │ │ inflate in inflate.c... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-37434 │ +└──────────────┴────────────────┴──────────┴───────────────────┴───────────────┴────────────────────────────────────────────────────────────┘ +``` + +
+ +## SBOM attestation + +You can also scan an SBOM attestation. +In the following example, [Cosign](https://github.com/sigstore/cosign) gets an attestation and Trivy scans it. +You must create CycloneDX-type attestation before trying the example. +To learn more about how to create an CycloneDX-Type attestation and attach it to an image, see the [SBOM attestation page](../supply-chain/attestation/sbom.md#sign-with-a-local-key-pair). + +```bash +$ cosign verify-attestation --key /path/to/cosign.pub --type cyclonedx > sbom.cdx.intoto.jsonl +$ trivy sbom ./sbom.cdx.intoto.jsonl + +sbom.cdx.intoto.jsonl (alpine 3.7.3) +========================= +Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 2) + +┌────────────┬────────────────┬──────────┬───────────────────┬───────────────┬──────────────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ +├────────────┼────────────────┼──────────┼───────────────────┼───────────────┼──────────────────────────────────────────────────────────┤ +│ musl │ CVE-2019-14697 │ CRITICAL │ 1.1.18-r3 │ 1.1.18-r4 │ musl libc through 1.1.23 has an x87 floating-point stack │ +│ │ │ │ │ │ adjustment im ...... │ +│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-14697 │ +├────────────┤ │ │ │ │ │ +│ musl-utils │ │ │ │ │ │ +│ │ │ │ │ │ │ +│ │ │ │ │ │ │ +└────────────┴────────────────┴──────────┴───────────────────┴───────────────┴──────────────────────────────────────────────────────────┘ +``` + +## KBOM + +To read more about KBOM, see the [documentation for Kubernetes scanning](./kubernetes.md#KBOM). + +The supported Kubernetes distributions for core components vulnerability scanning are: + +- [Kubernetes upstream](https://github.com/kubernetes/kubernetes) +- [Rancher rke2](https://github.com/rancher/rke2) + +```sh + +$ trivy k8s --format cyclonedx cluster -o kbom.json +$ trivy sbom kbom.json +2023-09-28T22:52:25.707+0300 INFO Vulnerability scanning is enabled +2023-09-28T22:52:25.717+0300 INFO Number of language-specific files: 3 +2023-09-28T22:52:25.717+0300 INFO Detecting kubernetes vulnerabilities... +2023-09-28T22:52:25.718+0300 INFO Detecting gobinary vulnerabilities... + +Kubernetes (kubernetes) + +Total: 2 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 1, CRITICAL: 0) + + +┌────────────────┬────────────────┬──────────┬────────┬───────────────────┬────────────────────────────────┬──────────────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├────────────────┼────────────────┼──────────┼────────┼───────────────────┼────────────────────────────────┼──────────────────────────────────────────────────┤ +│ k8s.io/kubelet │ CVE-2021-25749 │ HIGH │ fixed │ 1.24.0 │ 1.22.14, 1.23.11, 1.24.5 │ runAsNonRoot logic bypass for Windows containers │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2021-25749 │ +│ ├────────────────┼──────────┤ │ ├────────────────────────────────┼──────────────────────────────────────────────────┤ +│ │ CVE-2023-2431 │ LOW │ │ │1.24.14, 1.25.9, 1.26.4, 1.27.1 │ Bypass of seccomp profile enforcement │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-2431 │ +└────────────────┴────────────────┴──────────┴────────┴───────────────────┴────────────────────────────────┴──────────────────────────────────────────────────┘ + +``` diff --git a/docs/docs/target/vm.md b/docs/docs/target/vm.md new file mode 100644 index 000000000000..b0dc23e9c507 --- /dev/null +++ b/docs/docs/target/vm.md @@ -0,0 +1,240 @@ +# Virtual Machine Image + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +To scan virtual machine (VM) images, you can use the `vm` subcommand. + +## Targets +The following targets are currently supported: + +- Local file +- AWS EC2 + - Amazon Machine Image (AMI) + - Amazon Elastic Block Store (EBS) Snapshot + +### Local file +Pass the path to your local VM image file. + +```bash +$ trivy vm --scanners 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 │ +├────────────────────────────┼────────────────┼──────────┤ ├───────────────────────────────┼──────────────────────────────────────────────────────────────┤ +... +``` + +
+ +### 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 --scanners vuln ami:ami-0123456789abcdefg +``` + +If you want to scan a AMI of non-default setting region, you can set any region via `--aws-region` option. + +```shell +$ trivy vm --aws-region ap-northeast-1 ami:ami-0123456789abcdefg +``` + + +#### 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 + +### Amazon 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 --scanners vuln ebs:snap-0123456789abcdefg +``` + + +If you want to scan an EBS Snapshot of non-default setting region, you can set any region via `--aws-region` option. + +```shell +$ trivy vm --aws-region ap-northeast-1 ebs:ebs-0123456789abcdefg +``` + +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 + +## Scanners +Trivy supports VM image scanning for + +- Vulnerabilities +- Misconfigurations +- Secrets +- Licenses + +### Vulnerabilities +It is enabled by default. +You can simply specify your VM image location. +It detects known vulnerabilities in your VM image. +See [here](../scanner/vulnerability.md) for the detail. + +``` +$ trivy vm [YOUR_VM_IMAGE] +``` + +### Misconfigurations +It is supported, but it is not useful in most cases. +As mentioned [here](../scanner/misconfiguration/index.md), Trivy mainly supports Infrastructure as Code (IaC) files for misconfigurations. +If your VM image includes IaC files such as Kubernetes YAML files or Terraform files, you should enable this feature with `--scanners misconfig`. + +``` +$ trivy vm --scanners misconfig [YOUR_VM_IMAGE] +``` + +### Secrets +It is enabled by default. +See [here](../scanner/secret.md) for the detail. + +```shell +$ trivy vm [YOUR_VM_IMAGE] +``` + +!!! tip + The scanning could be faster if you enable only vulnerability scanning (`--scanners vuln`) because Trivy tries to download only necessary blocks for vulnerability detection. + +### Licenses +It is disabled by default. +See [here](../scanner/license.md) for the detail. + +```shell +$ trivy vm --scanners license [YOUR_VM_IMAGE] +``` + +## SBOM generation +Trivy can generate SBOM for VM images. +See [here](../supply-chain/sbom.md) 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 | | + + +[vmdk]: https://www.vmware.com/app/vmdk/?src=vmdk +[ebsapi-elements]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-accessing-snapshot.html#ebsapi-elements +[coldsnap]: https://github.com/awslabs/coldsnap + diff --git a/docs/ecosystem/cicd.md b/docs/ecosystem/cicd.md new file mode 100644 index 000000000000..e840e1d40984 --- /dev/null +++ b/docs/ecosystem/cicd.md @@ -0,0 +1,89 @@ +# CI/CD Integrations + +## Azure DevOps (Official) +[Azure Devops](https://azure.microsoft.com/en-us/products/devops/#overview) is Microsoft Azure cloud native CI/CD service. + +Trivy has a "Azure Devops Pipelines Task" for Trivy, that lets you easily introduce security scanning into your workflow, with an integrated Azure Devops UI. + +👉 Get it at: + +## GitHub Actions +[GitHub Actions](https://github.com/features/actions) is GitHub's native CI/CD and job orchestration service. + +### trivy-action (Official) + +GitHub Action for integrating Trivy into your GitHub pipeline + +👉 Get it at: + +### trivy-action (Community) + +GitHub Action to scan vulnerability using Trivy. If vulnerabilities are found by Trivy, it creates a GitHub Issue. + +👉 Get it at: + +### trivy-github-issues (Community) + +In this action, Trivy scans the dependency files such as package-lock.json and go.sum in your repository, then create GitHub issues according to the result. + +👉 Get it at: + +## Buildkite Plugin (Community) + +The trivy buildkite plugin provides a convenient mechanism for running the open-source trivy static analysis tool on your project. + +👉 Get it at: https://github.com/equinixmetal-buildkite/trivy-buildkite-plugin + +## Dagger (Community) +[Dagger](https://dagger.io/) is CI/CD as code that runs anywhere. + +The Dagger module for Trivy provides functions for scanning container images from registries as well as Dagger Container objects from any Dagger SDK (e.g. Go, Python, Node.js, etc). + +👉 Get it at: + + +## Semaphore (Community) +[Semaphore](https://semaphoreci.com/) is a CI/CD service. + +You can use Trivy in Semaphore for scanning code, containers, infrastructure, and Kubernetes in Semaphore workflow. + +👉 Get it at: + +## CircleCI (Community) +[CircleCI](https://circleci.com/) is a CI/CD service. + +You can use the Trivy Orb for Circle CI to introduce security scanning into your workflow. + +👉 Get it at: +Source: + +## Woodpecker CI (Community) + +Example Trivy step in pipeline + +```yml +pipeline: + securitycheck: + image: aquasec/trivy:latest + commands: + # use any trivy command, if exit code is 0 woodpecker marks it as passed, else it assumes it failed + - trivy fs --exit-code 1 --skip-dirs web/ --skip-dirs docs/ --severity MEDIUM,HIGH,CRITICAL . +``` + +Woodpecker does use Trivy itself so you can [see it in use there](https://github.com/woodpecker-ci/woodpecker/pull/1163). + +## Concourse CI (Community) +[Concourse CI](https://concourse-ci.org/) is a CI/CD service. + +You can use Trivy Resource in Concourse for scanning containers and introducing security scanning into your workflow. +It has capabilities to fail the pipeline, create issues, alert communication channels (using respective resources) based on Trivy scan output. + +👉 Get it at: + + +## SecObserve GitHub actions and GitLab templates (Community) +[SecObserve GitHub actions and GitLab templates](https://github.com/MaibornWolff/secobserve_actions_templates) run various vulnerability scanners, providing uniform methods and parameters for launching the tools. + +The Trivy integration supports scanning Docker images and local filesystems for vulnerabilities as well as scanning IaC files for misconfigurations. + +👉 Get it at: diff --git a/docs/ecosystem/ide.md b/docs/ecosystem/ide.md new file mode 100644 index 000000000000..e179eb7883cd --- /dev/null +++ b/docs/ecosystem/ide.md @@ -0,0 +1,66 @@ +# IDE and developer tools Integrations + +## VSCode (Official) +[Visual Studio Code](https://code.visualstudio.com/) is an open source versatile code editor and development environment. + +👉 Get it at: + +## JetBrains (Official) +[JetBrains](https://jetbrains.com) makes IDEs such as Goland, Pycharm, IntelliJ, Webstorm, and more. + +The Trivy plugin for JetBrains IDEs lets you use Trivy right from your development environment. + +👉 Get it at: + +## Kubernetes Lens (Official) +[Kubernetes Lens](https://k8slens.dev/) is a management application for Kubernetes clusters. + +Trivy has an extension for Kubernetes Lens that lets you scan Kubernetes workloads and view the results in the Lens UI. + +👉 Get it at: + +## Vim (Community) +[Vim](https://www.vim.org/) is a terminal based text editor. + +Vim plugin for Trivy to install and run Trivy. + +👉 Get it at: + +## Docker Desktop (Community) +[Docker Desktop](https://www.docker.com/products/docker-desktop/) is an easy way to install [Docker]() container engine on your development machine, and manage it in a GUI . + +Trivy Docker Desktop extension for scanning container images for vulnerabilities and generating SBOMs + +👉 Get it at: + +## Rancher Desktop (Community) +[Rancher Desktop](https://rancherdesktop.io/) is an easy way to use containers and Kubernetes on your development machine, and manage it in a GUI. + +Trivy is natively integrated with Rancher, no installation is needed. More info in Rancher documentation: + +## LazyTrivy (Community) +A terminal native UI for Trivy + +👉 Get it at: + +## Trivy Vulnerability explorer (Community) + +Web application that allows to load a Trivy report in json format and displays the vulnerabilities of a single target in an interactive data table + +👉 Get it at: + +## Trivy pre-commit (Community) + +A trivy pre-commit hook that runs a `trivy fs` in your git repo before commiting, preventing you from commiting secrets in the first place. + +👉 Get it at: + +## AWS CDK + +[The AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/) is an open-source software development framework to define cloud infrastructure in code and provision it through AWS CloudFormation. + +### image-scanner-with-trivy (Community) + +A CDK Construct Library to scan an image with trivy in CDK codes. + +👉 Get it at: diff --git a/docs/ecosystem/index.md b/docs/ecosystem/index.md new file mode 100644 index 000000000000..f9141d9be50b --- /dev/null +++ b/docs/ecosystem/index.md @@ -0,0 +1,10 @@ +# Ecosystem +Trivy is integrated into many popular tools and applications, so that you can easily add security to your workflow. + +In this section you will find an aggregation of the different integrations. Integrations are listed as either "official" or "community". Official integrations are developed by the core Trivy team and supported by it. Community integrations are integrations developed by the community, and collected here for your convenience. For support or questions about community integrations, please contact the original developers. + +👈 Please use the side-navigation on the left in order to browse the different topics. + +## Add missing integration + +We are happy to showcase community integrations in this section. To suggest an addition simply make a Pull Request to add the missing integration. diff --git a/docs/ecosystem/prod.md b/docs/ecosystem/prod.md new file mode 100644 index 000000000000..8f9b8fb71be0 --- /dev/null +++ b/docs/ecosystem/prod.md @@ -0,0 +1,31 @@ +# Production and cloud Integrations + +## Kubernetes + +[Kubernetes](https://kubernetes.io/) is an open-source system for automating deployment, scaling, and management of containerized applications. + +### Trivy Operator (Official) + +Using the Trivy Operator you can install Trivy into a Kubernetes cluster so that it automatically and continuously scan your workloads and cluster for security issues. + +👉 Get it at: + +## Harbor (Official) +[Harbor](https://goharbor.io/) is an open source cloud native container and artifact registry. + +Trivy is natively integrated into Harbor, no installation is needed. More info in Harbor documentation: + +## Kyverno (Community) +[Kyverno](https://kyverno.io/) is a policy management tool for Kubernetes. + +You can use Kyverno to ensure and enforce that deployed workloads' images are scanned for vulnerabilities. + +👉 Get it at: + +## Zora (Community) + +[Zora](https://zora-docs.undistro.io/) is an open-source solution that scans Kubernetes clusters with multiple plugins at scheduled times. + +Trivy is integrated into Zora as a vulnerability scanner plugin. + +👉 Get it at: diff --git a/docs/ecosystem/reporting.md b/docs/ecosystem/reporting.md new file mode 100644 index 000000000000..8f599cf2a12c --- /dev/null +++ b/docs/ecosystem/reporting.md @@ -0,0 +1,32 @@ +# Reporting + +## DefectDojo (Community) +DefectDojo can parse Trivy JSON reports. The parser supports deduplication and auto-close features. + +👉 Get it at: + +## SecObserve (Community) +SecObserve can parse Trivy results as CycloneDX reports and provides an unified overview of vulnerabilities from different sources. Vulnerabilities can be evaluated with manual and rule based assessments. + +👉 Get it at: + +## Scan2html (Community) +A Trivy plugin that scans and outputs the results to an interactive html file. + +👉 Get it at: + +## SonarQube (Community) +A Trivy plugin that converts JSON report to SonarQube [generic issues format](https://docs.sonarqube.org/9.6/analyzing-source-code/importing-external-issues/generic-issue-import-format/). + +👉 Get it at: + +## Trivy-Streamlit (Community) +Trivy-Streamlit is a Streamlit application that allows you to quickly parse the results from a Trivy JSON report. + +👉 Get it at: + +## Trivy-Vulnerability-Explorer (Community) + +This project is a web application that allows to load a Trivy report in json format and displays the vulnerabilities of a single target in an interactive data table. + +👉 Get it at: diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md new file mode 100644 index 000000000000..3bd1e9fcb978 --- /dev/null +++ b/docs/getting-started/faq.md @@ -0,0 +1,25 @@ +## FAQ + +### How to pronounce the name "Trivy"? + +`tri` is pronounced like **tri**gger, `vy` is pronounced like en**vy**. + +### Does Trivy support X? + +Check out the [Scanning coverage page](../docs/coverage/index.md). + +### Is there a paid version of Trivy? + +If you liked Trivy, you will love Aqua which builds on top of Trivy to provide even more enhanced capabilities for a complete security management offering. +You can find a high level comparison table specific to Trivy users [here](https://github.com/aquasecurity/resources/blob/main/trivy-aqua.md). +In addition check out the website for more information about our products and services. +If you'd like to contact Aqua or request a demo, please use this form: + +### How to generate multiple reports? +See [here](../docs/configuration/reporting.md#converting). + +### How to run Trivy under air-gapped environment? +See [here](../docs/advanced/air-gap.md). + +### Why `trivy fs` and `trivy repo` does not scan JAR files for vulnerabilities? +See [here](../docs/target/repository.md#rationale). diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 000000000000..5b18edc30b4b --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,168 @@ +# Installing Trivy + +In this section you will find an aggregation of the different ways to install Trivy. installations are listed as either "official" or "community". Official integrations are developed by the core Trivy team and supported by it. Community integrations are integrations developed by the community, and collected here for your convenience. For support or questions about community integrations, please contact the original developers. + +## Install using Package Manager + +### RHEL/CentOS (Official) + +=== "Repository" + Add repository setting to `/etc/yum.repos.d`. + + ``` bash + RELEASE_VERSION=$(grep -Po '(?<=VERSION_ID=")[0-9]' /etc/os-release) + cat << EOF | sudo tee -a /etc/yum.repos.d/trivy.repo + [trivy] + name=Trivy repository + baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$RELEASE_VERSION/\$basearch/ + gpgcheck=1 + enabled=1 + gpgkey=https://aquasecurity.github.io/trivy-repo/rpm/public.key + EOF + sudo yum -y update + sudo yum -y install trivy + ``` + +=== "RPM" + + ``` bash + rpm -ivh https://github.com/aquasecurity/trivy/releases/download/{{ git.tag }}/trivy_{{ git.tag[1:] }}_Linux-64bit.rpm + ``` + +### Debian/Ubuntu (Official) + +=== "Repository" + Add repository setting to `/etc/apt/sources.list.d`. + + ``` bash + sudo apt-get install wget apt-transport-https gnupg lsb-release + wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null + echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list + sudo apt-get update + sudo apt-get install trivy + ``` + +=== "DEB" + + ``` bash + wget https://github.com/aquasecurity/trivy/releases/download/{{ git.tag }}/trivy_{{ git.tag[1:] }}_Linux-64bit.deb + sudo dpkg -i trivy_{{ git.tag[1:] }}_Linux-64bit.deb + ``` + +### Homebrew (Official) + +Homebrew for MacOS and Linux. + +```bash +brew install trivy +``` + +### Arch Linux (Community) + +Arch Linux Package Repository. + +```bash +pacman -S trivy +``` + +References: +- +- + + +### MacPorts (Community) + +[MacPorts](https://www.macports.org) for MacOS. + +```bash +sudo port install trivy +``` + +References: +- + +### Nix/NixOS (Community) + +Nix package manager for Linux and MacOS. + +=== "Command line" + +`nix-env --install -A nixpkgs.trivy` + +=== "Configuration" + +```nix + # your other config ... + environment.systemPackages = with pkgs; [ + # your other packages ... + trivy + ]; +``` + +=== "Home Manager" + +```nix + # your other config ... + home.packages = with pkgs; [ + # your other packages ... + trivy + ]; +``` + +References: +- + +### FreeBSD (Official) + +[Pkg](https://freebsd.org) for FreeBSD. + +```bash +pkg install trivy +``` + +## Install from GitHub Release (Official) + +### Download Binary + +1. Download the file for your operating system/architecture from [GitHub Release assets](https://github.com/aquasecurity/trivy/releases/tag/{{ git.tag }}) (`curl -LO https://url.to/trivy.tar.gz`). +2. Unpack the downloaded archive (`tar -xzf ./trivy.tar.gz`). +3. Put the binary somewhere in your `$PATH` (e.g `mv ./trivy /usr/local/bin/`). +4. Make sure the binary has execution bit turned on (`chmod +x ./trivy`). + +### Install Script + +The process above can be automated by the following script: + +```bash +curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin {{ git.tag }} +``` + +### Install from source + +```bash +git clone --depth 1 --branch {{ git.tag }} https://github.com/aquasecurity/trivy +cd trivy +go install ./cmd/trivy +``` + +## Use container image + +1. Pull Trivy image (`docker pull aquasec/trivy:{{ git.tag[1:] }}`) + 2. It is advisable to mount a consistent [cache dir](../docs/configuration/cache.md) on the host into the Trivy container. +3. For scanning container images with Trivy, mount `docker.sock` from the host into the Trivy container. + +Example: + +``` bash +docker run -v /var/run/docker.sock:/var/run/docker.sock -v $HOME/Library/Caches:/root/.cache/ aquasec/trivy:{{ git.tag[1:] }} image python:3.4-alpine +``` + +| Registry | Repository | Link | Supportability | +|--------------------------------------|-------------------------------------|-----------------------------------------------------------------------|----------------| +| Docker Hub | `docker.io/aquasec/trivy` | https://hub.docker.com/r/aquasec/trivy | Official | +| GitHub Container Registry (GHCR) | `ghcr.io/aquasecurity/trivy` | https://github.com/orgs/aquasecurity/packages/container/package/trivy | Official | +| AWS Elastic Container Registry (ECR) | `public.ecr.aws/aquasecurity/trivy` | https://gallery.ecr.aws/aquasecurity/trivy | Official | + +## Other Tools to use and deploy Trivy + +For additional tools and ways to install and use Trivy in different environments such as in IDE, Kubernetes or CI/CD, see [Ecosystem section](../ecosystem/index.md). diff --git a/docs/getting-started/signature-verification.md b/docs/getting-started/signature-verification.md new file mode 100644 index 000000000000..1443b4586c6f --- /dev/null +++ b/docs/getting-started/signature-verification.md @@ -0,0 +1,99 @@ +# Signature Verification + +## Verifying a Cosign signature +All binaries and container images are signed by [Cosign](https://github.com/sigstore/cosign). + +You need the following tool: + +- [Cosign](https://docs.sigstore.dev/cosign/installation/) + +### Verifying signed container images +1. Use the following command for keyless [verification](https://docs.sigstore.dev/cosign/verify/): + ```shell + cosign verify aquasec/trivy: \ + --certificate-identity-regexp 'https://github\.com/aquasecurity/trivy/\.github/workflows/.+' \ + --certificate-oidc-issuer "https://token.actions.githubusercontent.com" + ``` + +2. You should get the following output + ```shell + Verification for index.docker.io/aquasec/trivy:latest -- + The following checks were performed on each of these signatures: + - The cosign claims were validated + - Existence of the claims in the transparency log was verified offline + - The code-signing certificate was verified using trusted certificate authority certificates + + .... + ``` + +### Verifying signed binaries + +1. Download the required tarball, associated signature and certificate files +2. Use the following command for keyless verification: + ```shell + cosign verify-blob \ + --certificate \ + --signature \ + --certificate-identity-regexp 'https://github\.com/aquasecurity/trivy/\.github/workflows/.+' \ + --certificate-oidc-issuer "https://token.actions.githubusercontent.com" + ``` +3. You should get the following output + ``` + Verified OK + ``` + +For example: + +```shell +$ wget "https://github.com/aquasecurity/trivy/releases/download/v0.45.0/trivy_0.45.0_Linux-32bit.tar.gz" +$ wget "https://github.com/aquasecurity/trivy/releases/download/v0.45.0/trivy_0.45.0_Linux-32bit.tar.gz.pem" +$ wget "https://github.com/aquasecurity/trivy/releases/download/v0.45.0/trivy_0.45.0_Linux-32bit.tar.gz.sig" +$ cosign verify-blob trivy_0.45.0_Linux-32bit.tar.gz \ + --certificate trivy_0.45.0_Linux-32bit.tar.gz.pem \ + --signature trivy_0.45.0_Linux-32bit.tar.gz.sig \ + --certificate-identity-regexp 'https://github\.com/aquasecurity/trivy/\.github/workflows/.+' \ + --certificate-oidc-issuer "https://token.actions.githubusercontent.com" + +Vetified OK +``` + +## Verifying a GPG signature + +RPM and Deb packages are also signed by GPG. + +### Verifying RPM + +The public key downloaded [here](https://aquasecurity.github.io/trivy-repo/rpm/public.key). + +1. Download the public key + ```shell + curl https://aquasecurity.github.io/trivy-repo/rpm/public.key \ + --output pub.key + ``` +2. Import the key + ```shell + rpm --import pub.key + ``` +3. Verify that the key has been imported + ```shell + rpm -q --queryformat "%{SUMMARY}\n" $(rpm -q gpg-pubkey) + ``` + You should get the following output + ```shell + gpg(trivy) + ``` + +4. Download the required binary + ```shell + curl -L https://github.com/aquasecurity/trivy/releases/download//.rpm \ + --output trivy.rpm + ``` +5. Check the binary with the following command + ```shell + rpm -K trivy.rpm + ``` + You should get the following output + ```shell + trivy.rpm: digests signatures OK + ``` + diff --git a/docs/imgs/Security-Hub.jpeg b/docs/imgs/Security-Hub.jpeg new file mode 100644 index 000000000000..cece6bcc0c18 Binary files /dev/null and b/docs/imgs/Security-Hub.jpeg differ diff --git a/docs/imgs/argocd-ui.png b/docs/imgs/argocd-ui.png new file mode 100644 index 000000000000..f9e31a958abb Binary files /dev/null and b/docs/imgs/argocd-ui.png differ diff --git a/docs/imgs/client-server.png b/docs/imgs/client-server.png new file mode 100644 index 000000000000..fce67eefdf0b Binary files /dev/null and b/docs/imgs/client-server.png differ diff --git a/docs/imgs/docker-desktop.png b/docs/imgs/docker-desktop.png new file mode 100644 index 000000000000..3eafeb8e8941 Binary files /dev/null and b/docs/imgs/docker-desktop.png differ diff --git a/docs/imgs/excalidraw/client-server.excalidraw b/docs/imgs/excalidraw/client-server.excalidraw new file mode 100644 index 000000000000..ad67667a9c4a --- /dev/null +++ b/docs/imgs/excalidraw/client-server.excalidraw @@ -0,0 +1,1151 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "ribsikcWWSf8Aw4M6BOYJ", + "type": "rectangle", + "x": 458.6370544433594, + "y": 379.5105285644531, + "width": 169.21945190429688, + "height": 104.2457275390625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 698826281, + "version": 381, + "versionNonce": 413251305, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY", + "JinTKutXOSTzURP969rwo", + "nXkZQsrtjmNqPM6SmPxrc", + "Ljv7RJF8FjyOJX3vZ2ou8", + "o_BKODe9vjtHWQsJ8F3tD", + "zTrormMP-N-W6thSxlTgK", + "fBva4zCGT2vIFPpTWC-oZ" + ] + }, + { + "id": "GkrbG--OvBT9zJ-w8E5oQ", + "type": "ellipse", + "x": 427.04335021972656, + "y": 65.240966796875, + "width": 215.20677185058594, + "height": 152.14088439941406, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 995070601, + "version": 684, + "versionNonce": 78137383, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY" + ] + }, + { + "id": "KPLvdnBVoU3U5XGhCch-x", + "type": "text", + "x": 511.77996826171875, + "y": 407.1625671386719, + "width": 62, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 938497417, + "version": 253, + "versionNonce": 788328905, + "isDeleted": false, + "boundElementIds": null, + "text": "Trivy\nServer", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "Ry65W-Cczzy8M9JsouEgZ", + "type": "text", + "x": 488.6784362792969, + "y": 116.34368896484375, + "width": 89, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1676815783, + "version": 456, + "versionNonce": 14330695, + "isDeleted": false, + "boundElementIds": null, + "text": "GitHub\n(trivy-db)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "eJn9MVaNlcyj8-YPfrBSY", + "type": "arrow", + "x": 538.7528045696993, + "y": 220.23935960349465, + "width": 0.45618097890837817, + "height": 154.16252445127046, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1741763111, + "version": 1298, + "versionNonce": 267243689, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.45618097890837817, + 154.16252445127046 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "GkrbG--OvBT9zJ-w8E5oQ", + "focus": -0.03598869040285281, + "gap": 2.911871895302724 + }, + "endBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": -0.04563780983822428, + "gap": 5.10864450968802 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "2P02jU3j2eEc92lH0YSwm", + "type": "text", + "x": 575.9393615722656, + "y": 254.42640686035156, + "width": 327, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 501371753, + "version": 629, + "versionNonce": 185838183, + "isDeleted": false, + "boundElementIds": null, + "text": "1. Download Trivy DB\n(including vulnerability information)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "nXkZQsrtjmNqPM6SmPxrc", + "type": "arrow", + "x": 128.84802246093747, + "y": 395.4753877561888, + "width": 316.63877589590845, + "height": 0.1213064482017785, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 2075020231, + "version": 327, + "versionNonce": 1795630503, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 316.63877589590845, + 0.1213064482017785 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "2ZWTQQ2dQDWF8xj1BLdVG", + "focus": 1.483056059007069, + "gap": 12.799499511718778 + }, + "endBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": 0.46421771350547186, + "gap": 12.273425031825923 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "OPRFQTrsfmF5a7us-mxkI", + "type": "draw", + "x": -60.78019714355469, + "y": 464.1100280880928, + "width": 72.64572143554688, + "height": 72.18890380859375, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1541066697, + "version": 569, + "versionNonce": 1115805895, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -39.526153564453125, + 16.35540771484375 + ], + [ + -32.219512939453125, + 72.170166015625 + ], + [ + 30.40386962890625, + 72.18890380859375 + ], + [ + 33.11956787109375, + 32.5743408203125 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "QzKnYI7o5Yxg_7szebL6Z", + "type": "draw", + "x": -37.06263732910158, + "y": 508.8429382443428, + "width": 16.976165771484375, + "height": 16.45367431640625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 2144447815, + "version": 203, + "versionNonce": 28982057, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 6.905059814453125, + -16.45367431640625 + ], + [ + 16.976165771484375, + -5.1099853515625 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "qqbEan2u1uU3loTdTvQ4B", + "type": "text", + "x": -184.67498779296875, + "y": 548.9957397580147, + "width": 230, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1619652615, + "version": 833, + "versionNonce": 981018599, + "isDeleted": false, + "boundElementIds": null, + "text": "6. Analyze pulled layers", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "GgiFj7vEEZT-VPfKiX6oo", + "type": "rectangle", + "x": -48.80625915527344, + "y": 386.3571411073208, + "width": 169.21945190429688, + "height": 104.2457275390625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1005158727, + "version": 500, + "versionNonce": 1358533383, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY", + "JinTKutXOSTzURP969rwo", + "nXkZQsrtjmNqPM6SmPxrc", + "2tbWET6O9G38YibdIyLpy", + "FuK6iJ6YprzRoh4wg1UHf", + "o_BKODe9vjtHWQsJ8F3tD", + "fBva4zCGT2vIFPpTWC-oZ" + ] + }, + { + "id": "qQZYQjma-4h8rOrxn5yBo", + "type": "text", + "x": 4.3366546630859375, + "y": 414.00917968153954, + "width": 57, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1918851753, + "version": 381, + "versionNonce": 56771817, + "isDeleted": false, + "boundElementIds": null, + "text": "Trivy\nClient", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "vtOTfv319aihVmgTMMbQG", + "type": "ellipse", + "x": -95.58086395263672, + "y": 68.13672637939453, + "width": 249.63902282714844, + "height": 152.14088439941406, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1265127495, + "version": 929, + "versionNonce": 1316732873, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY", + "2tbWET6O9G38YibdIyLpy", + "FuK6iJ6YprzRoh4wg1UHf" + ] + }, + { + "id": "bz64cTcmJtjZotVar2MHr", + "type": "text", + "x": -73.4975357055664, + "y": 108.37564849853516, + "width": 202, + "height": 74, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 976812969, + "version": 670, + "versionNonce": 938982727, + "isDeleted": false, + "boundElementIds": null, + "text": "Container Registries\nor\nContainer Engines", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 67 + }, + { + "id": "Z5DzSXSTeTNfyo9GRr57B", + "type": "ellipse", + "x": 871.677619934082, + "y": 347.7999496459961, + "width": 215.20677185058594, + "height": 152.14088439941406, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1020418857, + "version": 836, + "versionNonce": 845340329, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY", + "Ljv7RJF8FjyOJX3vZ2ou8", + "zTrormMP-N-W6thSxlTgK" + ] + }, + { + "id": "SGILIw_oLMs1yuieBWcoP", + "type": "text", + "x": 898.8127059936523, + "y": 398.90267181396484, + "width": 158, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1953695719, + "version": 653, + "versionNonce": 1086225511, + "isDeleted": false, + "boundElementIds": [ + "zTrormMP-N-W6thSxlTgK" + ], + "text": "Cache Backend\n(Local or Redis)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "Ljv7RJF8FjyOJX3vZ2ou8", + "type": "arrow", + "x": 635.243173087202, + "y": 408.1721813855087, + "width": 237.82178502385182, + "height": 0.48828450950475144, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 744746601, + "version": 617, + "versionNonce": 230891913, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 237.82178502385182, + -0.48828450950475144 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": -0.4450219327551968, + "gap": 7.386666739545717 + }, + "endBinding": { + "elementId": "Z5DzSXSTeTNfyo9GRr57B", + "focus": 0.21564885268585596, + "gap": 1.0335001891177882 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "BlRXUB6fETT_zZD6O1fNL", + "type": "text", + "x": -163.05282592773438, + "y": 268.6500244140625, + "width": 124, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 609582183, + "version": 841, + "versionNonce": 2090882951, + "isDeleted": false, + "boundElementIds": null, + "text": "2. Download\n manifest", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "2tbWET6O9G38YibdIyLpy", + "type": "arrow", + "x": -12.186006749219864, + "y": 221.1919311337398, + "width": 0.45618097890837817, + "height": 154.16252445127046, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1740746569, + "version": 1437, + "versionNonce": 1574439017, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.45618097890837817, + 154.16252445127046 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "vtOTfv319aihVmgTMMbQG", + "focus": 0.3337009650548285, + "gap": 5.11281200236435 + }, + "endBinding": { + "elementId": "GgiFj7vEEZT-VPfKiX6oo", + "focus": -0.5585687247500966, + "gap": 11.002685522310514 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "2ZWTQQ2dQDWF8xj1BLdVG", + "type": "text", + "x": 141.64752197265625, + "y": 364.4148864746094, + "width": 294, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1932287977, + "version": 912, + "versionNonce": 299486887, + "isDeleted": false, + "boundElementIds": [ + "nXkZQsrtjmNqPM6SmPxrc" + ], + "text": "3. Ask missing layers in cache", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "pIwVlwytOx1ZJ4aOCvtRF", + "type": "text", + "x": 636.46630859375, + "y": 352.5987548828125, + "width": 246, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 973317671, + "version": 871, + "versionNonce": 1819087689, + "isDeleted": false, + "boundElementIds": null, + "text": "4. Return existing layers", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "FuK6iJ6YprzRoh4wg1UHf", + "type": "arrow", + "x": 75.37933138554581, + "y": 220.52838743256788, + "width": 0.45618097890837817, + "height": 154.16252445127046, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1148774855, + "version": 1487, + "versionNonce": 722952647, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.45618097890837817, + 154.16252445127046 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "vtOTfv319aihVmgTMMbQG", + "focus": -0.36784928325571326, + "gap": 5.485033392258245 + }, + "endBinding": { + "elementId": "GgiFj7vEEZT-VPfKiX6oo", + "focus": 0.4745035772454494, + "gap": 11.666229223482446 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "jgdyeMn1ZO33kPci2KoGL", + "type": "text", + "x": 94.91287231445312, + "y": 265.060302734375, + "width": 162, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1318802377, + "version": 872, + "versionNonce": 1037943337, + "isDeleted": false, + "boundElementIds": null, + "text": "5. Pull only\n missing layers", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "o_BKODe9vjtHWQsJ8F3tD", + "type": "arrow", + "x": 128.61206957157697, + "y": 440.13062341766545, + "width": 316.63877589590845, + "height": 0.1213064482017785, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 20958471, + "version": 415, + "versionNonce": 1769312487, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 316.63877589590845, + 0.1213064482017785 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "GgiFj7vEEZT-VPfKiX6oo", + "focus": 0.030966433153118762, + "gap": 8.19887682255353 + }, + "endBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": -0.16596748618058757, + "gap": 13.386208975873956 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "6DaFskX4dI8KM57tYJKwz", + "type": "text", + "x": 151.09302660282708, + "y": 409.41115607163283, + "width": 259, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 221555433, + "version": 1091, + "versionNonce": 1058379017, + "isDeleted": false, + "boundElementIds": null, + "text": "7. Send the analysis result", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "zTrormMP-N-W6thSxlTgK", + "type": "arrow", + "x": 639.0015349036082, + "y": 455.7986607407429, + "width": 233.13104569613938, + "height": 0.2533468001336132, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1122927111, + "version": 471, + "versionNonce": 377543687, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 233.13104569613938, + 0.2533468001336132 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": 0.4608119841777207, + "gap": 11.145028555951967 + }, + "endBinding": { + "elementId": "Z5DzSXSTeTNfyo9GRr57B", + "focus": -0.42458037983475105, + "gap": 8.237658674598492 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "2sst3d11OgZLJZ3vdmH15", + "type": "text", + "x": 709.851318359375, + "y": 424.4541015625, + "width": 82, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1801175527, + "version": 1132, + "versionNonce": 865078249, + "isDeleted": false, + "boundElementIds": null, + "text": "8. Store", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "QsQ0GcuViNzFPK5QPICis", + "type": "text", + "x": 408.48883056640625, + "y": 561.8040618896484, + "width": 254, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 773954023, + "version": 992, + "versionNonce": 419481159, + "isDeleted": false, + "boundElementIds": null, + "text": "9. Detect security issues", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "fBva4zCGT2vIFPpTWC-oZ", + "type": "arrow", + "x": 448.39657694205636, + "y": 484.4299201560043, + "width": 322.1210694778896, + "height": 0.5052042161862573, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 184790569, + "version": 677, + "versionNonce": 151709097, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -322.1210694778896, + -0.5052042161862573 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": -1.0131990709910756, + "gap": 10.240477501303019 + }, + "endBinding": { + "elementId": "GgiFj7vEEZT-VPfKiX6oo", + "focus": 0.8669472708483955, + "gap": 5.862314715143327 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "PALQlCWrOXdjqBkRKhS6D", + "type": "draw", + "x": 523.952392578125, + "y": 487.95794677734375, + "width": 72.486083984375, + "height": 62.78338623046875, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 273802089, + "version": 310, + "versionNonce": 230581607, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -23.2962646484375, + 36.8623046875 + ], + [ + -3.4830322265625, + 59.561767578125 + ], + [ + 29.205810546875, + 62.78338623046875 + ], + [ + 49.1898193359375, + 49.74090576171875 + ], + [ + 45.6522216796875, + 21.439453125 + ], + [ + 24.2618408203125, + 1.5714111328125 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "vP9i2PUtjapBX142LXX7m", + "type": "draw", + "x": 552.87939453125, + "y": 504.59832763671875, + "width": 16.5072021484375, + "height": 18.1295166015625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 786227753, + "version": 48, + "versionNonce": 2144487561, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.4810791015625, + -18.1295166015625 + ], + [ + 16.5072021484375, + -13.106201171875 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "2XUWMZRUNlhKGsJGNjK9k", + "type": "text", + "x": 154.5665283203125, + "y": 450.849365234375, + "width": 258, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 826013511, + "version": 1180, + "versionNonce": 1404772487, + "isDeleted": false, + "boundElementIds": null, + "text": "10. Return the scan result", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/docs/imgs/excalidraw/fs.excalidraw b/docs/imgs/excalidraw/fs.excalidraw new file mode 100644 index 000000000000..bd97566531aa --- /dev/null +++ b/docs/imgs/excalidraw/fs.excalidraw @@ -0,0 +1,397 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "ribsikcWWSf8Aw4M6BOYJ", + "type": "rectangle", + "x": 528.53466796875, + "y": 377.9640197753906, + "width": 169.21945190429688, + "height": 104.2457275390625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 698826281, + "version": 274, + "versionNonce": 942385065, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY", + "JinTKutXOSTzURP969rwo" + ] + }, + { + "id": "GkrbG--OvBT9zJ-w8E5oQ", + "type": "ellipse", + "x": 496.9409637451172, + "y": 63.6944580078125, + "width": 215.20677185058594, + "height": 152.14088439941406, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 995070601, + "version": 595, + "versionNonce": 1870840679, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY" + ] + }, + { + "id": "KPLvdnBVoU3U5XGhCch-x", + "type": "text", + "x": 585.4711608886719, + "y": 418.1110534667969, + "width": 48, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 938497417, + "version": 119, + "versionNonce": 1368050313, + "isDeleted": false, + "boundElementIds": null, + "text": "Trivy", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "Ry65W-Cczzy8M9JsouEgZ", + "type": "text", + "x": 558.5760498046875, + "y": 114.79718017578125, + "width": 89, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1676815783, + "version": 368, + "versionNonce": 2034482823, + "isDeleted": false, + "boundElementIds": null, + "text": "GitHub\n(trivy-db)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "eJn9MVaNlcyj8-YPfrBSY", + "type": "arrow", + "x": 608.6504180950899, + "y": 218.69285081443215, + "width": 0.45618097890837817, + "height": 154.16252445127046, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1741763111, + "version": 1069, + "versionNonce": 2093125993, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.45618097890837817, + 154.16252445127046 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "GkrbG--OvBT9zJ-w8E5oQ", + "focus": -0.035986229233252585, + "gap": 2.9120411440381986 + }, + "endBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": -0.04563780983822369, + "gap": 5.10864450968802 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "2P02jU3j2eEc92lH0YSwm", + "type": "text", + "x": 637.9371185302734, + "y": 248.28482055664062, + "width": 327, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 501371753, + "version": 360, + "versionNonce": 326653351, + "isDeleted": false, + "boundElementIds": null, + "text": "1. Download Trivy DB\n(including vulnerability information)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "zdNQvzJczyD9GSExNdKS1", + "type": "draw", + "x": 513.9153137207031, + "y": 461.14288330078125, + "width": 72.64572143554688, + "height": 72.18890380859375, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1270145927, + "version": 459, + "versionNonce": 1094761993, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -39.526153564453125, + 16.35540771484375 + ], + [ + -32.219512939453125, + 72.170166015625 + ], + [ + 30.40386962890625, + 72.18890380859375 + ], + [ + 33.11956787109375, + 32.5743408203125 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "YK7xYiSKb1RwCCobsRAXm", + "type": "text", + "x": 391.5774230957031, + "y": 541.0142517089844, + "width": 280, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 2027931817, + "version": 510, + "versionNonce": 1538345895, + "isDeleted": false, + "boundElementIds": null, + "text": "3. Traverse directories\nand look for necessary files", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "Z_DiM_TKY2bBa4Q5VEWgI", + "type": "text", + "x": 684.9671020507812, + "y": 621.1561279296875, + "width": 160, + "height": 25, + "angle": 0, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1365660617, + "version": 564, + "versionNonce": 2113117703, + "isDeleted": false, + "boundElementIds": null, + "text": "Local Filesystem", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "kqEATPhet5tYxzkEOFZng", + "type": "text", + "x": 571.0751342773438, + "y": 494.0994567871094, + "width": 254, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 130557095, + "version": 654, + "versionNonce": 2037434313, + "isDeleted": false, + "boundElementIds": null, + "text": "4. Detect security issues", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "K0XdIaMYVmUP2kGc8Oe1O", + "type": "rectangle", + "x": 360.9377136230469, + "y": 357.07373046875, + "width": 495.45401000976557, + "height": 297.8450317382812, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1367853545, + "version": 339, + "versionNonce": 314224297, + "isDeleted": false, + "boundElementIds": null + }, + { + "id": "_vOsyHICDRWn01MF3g7rB", + "type": "draw", + "x": 539.7380981445312, + "y": 501.50762939453125, + "width": 16.976165771484375, + "height": 16.45367431640625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1322966281, + "version": 82, + "versionNonce": 244034663, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 6.905059814453125, + -16.45367431640625 + ], + [ + 16.976165771484375, + -5.1099853515625 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/docs/imgs/excalidraw/image.excalidraw b/docs/imgs/excalidraw/image.excalidraw new file mode 100644 index 000000000000..005aa44462a7 --- /dev/null +++ b/docs/imgs/excalidraw/image.excalidraw @@ -0,0 +1,504 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "ribsikcWWSf8Aw4M6BOYJ", + "type": "rectangle", + "x": 528.53466796875, + "y": 359.7196350097656, + "width": 169.21945190429688, + "height": 104.2457275390625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 698826281, + "version": 235, + "versionNonce": 865905065, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY", + "JinTKutXOSTzURP969rwo" + ] + }, + { + "id": "GkrbG--OvBT9zJ-w8E5oQ", + "type": "ellipse", + "x": 382.4654998779297, + "y": 70.28388977050781, + "width": 215.20677185058594, + "height": 152.14088439941406, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 995070601, + "version": 442, + "versionNonce": 1305668297, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY" + ] + }, + { + "id": "KPLvdnBVoU3U5XGhCch-x", + "type": "text", + "x": 585.4711608886719, + "y": 399.8666687011719, + "width": 48, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 938497417, + "version": 82, + "versionNonce": 463601353, + "isDeleted": false, + "boundElementIds": null, + "text": "Trivy", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "Ry65W-Cczzy8M9JsouEgZ", + "type": "text", + "x": 445.5017395019531, + "y": 121.72871398925781, + "width": 89, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1676815783, + "version": 306, + "versionNonce": 1721298503, + "isDeleted": false, + "boundElementIds": null, + "text": "GitHub\n(trivy-db)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "eJn9MVaNlcyj8-YPfrBSY", + "type": "arrow", + "x": 497.981827043938, + "y": 226.98470679602065, + "width": 90.39040277767413, + "height": 127.08286800676547, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1741763111, + "version": 591, + "versionNonce": 1564262983, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 90.39040277767413, + 127.08286800676547 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "GkrbG--OvBT9zJ-w8E5oQ", + "focus": 0.41046776258752976, + "gap": 4.7596344319156 + }, + "endBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": 0.1341309277800711, + "gap": 5.652060206979513 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "2P02jU3j2eEc92lH0YSwm", + "type": "text", + "x": 181.22366333007812, + "y": 252.094970703125, + "width": 327, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 501371753, + "version": 281, + "versionNonce": 1175555431, + "isDeleted": false, + "boundElementIds": null, + "text": "1. Download Trivy DB\n(including vulnerability information)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "P3WUTj2Q9se-JE7t3AUeq", + "type": "ellipse", + "x": 635.5376052856445, + "y": 68.77783966064453, + "width": 286.11024475097656, + "height": 152.14088439941406, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1633931305, + "version": 684, + "versionNonce": 1011691465, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY", + "JinTKutXOSTzURP969rwo" + ] + }, + { + "id": "2q_e-oMWU2gv7ZEClixAx", + "type": "text", + "x": 680.7248458862305, + "y": 102.90502166748047, + "width": 186, + "height": 74, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 866083559, + "version": 676, + "versionNonce": 1669555559, + "isDeleted": false, + "boundElementIds": null, + "text": "Container Registry\nor\nContainer Engine", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 67 + }, + { + "id": "JinTKutXOSTzURP969rwo", + "type": "arrow", + "x": 725.3393330640303, + "y": 216.94283962355922, + "width": 69.9915382406898, + "height": 134.02688987715163, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1348821927, + "version": 938, + "versionNonce": 1580551495, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -69.9915382406898, + 134.02688987715163 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "P3WUTj2Q9se-JE7t3AUeq", + "focus": 0.10510087993199528, + "gap": 1.4587528984692284 + }, + "endBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": 0.0931277668871816, + "gap": 8.74990550905477 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "kHnEt-AjbEzMxB61VSDc2", + "type": "text", + "x": 723.4168395996094, + "y": 258.063232421875, + "width": 295, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1080814281, + "version": 613, + "versionNonce": 1064224615, + "isDeleted": false, + "boundElementIds": null, + "text": "2. Pull missing layers in cache", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "zdNQvzJczyD9GSExNdKS1", + "type": "draw", + "x": 524.4000854492188, + "y": 435.25982666015625, + "width": 72.64572143554688, + "height": 72.18890380859375, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1270145927, + "version": 331, + "versionNonce": 1090245193, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -39.526153564453125, + 16.35540771484375 + ], + [ + -32.219512939453125, + 72.170166015625 + ], + [ + 30.40386962890625, + 72.18890380859375 + ], + [ + 33.11956787109375, + 32.5743408203125 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "13A9Y6sL_9DQ1KskiX5Oj", + "type": "draw", + "x": 545.8597412109375, + "y": 483.20318603515625, + "width": 25.76263427734375, + "height": 16.2581787109375, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 788909033, + "version": 85, + "versionNonce": 991433415, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 13.24993896484375, + -16.2581787109375 + ], + [ + 25.76263427734375, + -5.01812744140625 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "YK7xYiSKb1RwCCobsRAXm", + "type": "text", + "x": 261.88555908203125, + "y": 379.65887451171875, + "width": 249, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 2027931817, + "version": 255, + "versionNonce": 809279785, + "isDeleted": false, + "boundElementIds": null, + "text": "3. Analyze layers &\nStore informatin in cache", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "Z_DiM_TKY2bBa4Q5VEWgI", + "type": "text", + "x": 404.58673095703125, + "y": 522.828125, + "width": 144, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1365660617, + "version": 327, + "versionNonce": 2016944615, + "isDeleted": false, + "boundElementIds": null, + "text": "4. Apply layers", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "kqEATPhet5tYxzkEOFZng", + "type": "text", + "x": 598.8711547851562, + "y": 480.22222900390625, + "width": 257, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 130557095, + "version": 539, + "versionNonce": 562048487, + "isDeleted": false, + "boundElementIds": null, + "text": "5. Detect security issues", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/docs/imgs/excalidraw/overview.excalidraw b/docs/imgs/excalidraw/overview.excalidraw new file mode 100644 index 000000000000..2762654df7f2 --- /dev/null +++ b/docs/imgs/excalidraw/overview.excalidraw @@ -0,0 +1,466 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 788, + "versionNonce": 555477386, + "isDeleted": false, + "id": "BkXuq_6BxgqZGZWc8oCtu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 599.653076171875, + "y": 734.7542114257812, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 1227.452155219184, + "height": 151.39703369140625, + "seed": 1632394695, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177570112, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 713, + "versionNonce": 44400470, + "isDeleted": false, + "id": "YQURTHNPSe05RPSlYRcok", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1118.2101508246528, + "y": 763.5906914605034, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 45, + "seed": 891391049, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177702292, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Trivy", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Trivy" + }, + { + "type": "text", + "version": 1191, + "versionNonce": 1166344150, + "isDeleted": false, + "id": "6dpF2EyZBtYgO6MrvGj0-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 875.3033447265625, + "y": 820.7327100965712, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 612, + "height": 36, + "seed": 687997545, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177705177, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Vulnerability/Misconfiguration/Secret Scanner", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Vulnerability/Misconfiguration/Secret Scanner" + }, + { + "type": "rectangle", + "version": 858, + "versionNonce": 1118008458, + "isDeleted": false, + "id": "cpnTMy7L2AUg9IDJppF4H", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 600.9835205078125, + "y": 635.5783640543619, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 335.3091227213542, + "height": 82.36856587727866, + "seed": 77164935, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177872265, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1077, + "versionNonce": 1122201878, + "isDeleted": false, + "id": "9-blmNVtLesthMSY_f60t", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 649.8531494140625, + "y": 660.223378499349, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 224, + "height": 36, + "seed": 860091815, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177872265, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Container Image", + "baseline": 25, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Container Image" + }, + { + "type": "rectangle", + "version": 1118, + "versionNonce": 1679315786, + "isDeleted": false, + "id": "gugZxhi7ThlcjWY_MFO7q", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 954.3485412597656, + "y": 635.849225362142, + "strokeColor": "#000000", + "backgroundColor": "#be4bdb", + "width": 409.35879516601574, + "height": 82.97188822428383, + "seed": 1232790121, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177872265, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1300, + "versionNonce": 1187044950, + "isDeleted": false, + "id": "K48gtpesBxIGJxLTnI2CB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1084.4311319986978, + "y": 660.9795074462891, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 139, + "height": 36, + "seed": 449264361, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177872265, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Filesystem", + "baseline": 25, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Filesystem" + }, + { + "type": "rectangle", + "version": 1204, + "versionNonce": 688085514, + "isDeleted": false, + "id": "La6f87LDZ0uEIZB947bXo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1375.0136108398438, + "y": 636.5654322306316, + "strokeColor": "#000000", + "backgroundColor": "#12b886", + "width": 452.76554361979186, + "height": 80.08313496907543, + "seed": 2005637801, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177872265, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1432, + "versionNonce": 1593746326, + "isDeleted": false, + "id": "aOgRPVQ81jhOfkvzjWTMF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1498.8465237087673, + "y": 658.0244835747612, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 201, + "height": 36, + "seed": 1284472935, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177872265, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Git Repository", + "baseline": 25, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Git Repository" + }, + { + "type": "rectangle", + "version": 2792, + "versionNonce": 183831882, + "isDeleted": false, + "id": "10WjipxoLx2zzSI91pXbR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 599.7894943723566, + "y": 905.6027750791251, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 344.482180700969, + "height": 83.67398764683533, + "seed": 1813731484, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177825759, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2771, + "versionNonce": 617525398, + "isDeleted": false, + "id": "M7Cngti6H0_kawKRN8yJ6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 963.2554264391833, + "y": 904.2447769132434, + "strokeColor": "#000000", + "backgroundColor": "#82c91e", + "width": 402.42137951281796, + "height": 86.03696372105414, + "seed": 1260603804, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177777585, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1057, + "versionNonce": 405881110, + "isDeleted": false, + "id": "Iq57wFRtO1a8AU0rT6lRD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1046.152429428344, + "y": 930.8676815998951, + "strokeColor": "#000000", + "backgroundColor": "#82c91e", + "width": 218, + "height": 36, + "seed": 1329695396, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177655817, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Misconfiguration", + "baseline": 25, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": null, + "originalText": "Misconfiguration" + }, + { + "type": "text", + "version": 883, + "versionNonce": 969949898, + "isDeleted": false, + "id": "_cm6xpfcL9Yv2XBK5MBZF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 681.3134368986982, + "y": 931.5212932384402, + "strokeColor": "#000000", + "backgroundColor": "#82c91e", + "width": 161, + "height": 36, + "seed": 807441828, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177624726, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Vulnerability", + "baseline": 25, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": null, + "originalText": "Vulnerability" + }, + { + "type": "rectangle", + "version": 2874, + "versionNonce": 1934391254, + "isDeleted": false, + "id": "Fq7meULupm1A9leboPlko", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1389.3043677318824, + "y": 903.8533384764222, + "strokeColor": "#000000", + "backgroundColor": "#4c6ef5", + "width": 437.15079032010976, + "height": 84.42746665074158, + "seed": 230693534, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177785481, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1121, + "versionNonce": 110517002, + "isDeleted": false, + "id": "OUGk8nZzvgcKUHhKUcQov", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1556.0451356485157, + "y": 930.8040952304675, + "strokeColor": "#000000", + "backgroundColor": "#82c91e", + "width": 91, + "height": 36, + "seed": 2044527454, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652177636085, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Secret", + "baseline": 25, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": null, + "originalText": "Secret" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/docs/imgs/excalidraw/repo.excalidraw b/docs/imgs/excalidraw/repo.excalidraw new file mode 100644 index 000000000000..72fcf1b47905 --- /dev/null +++ b/docs/imgs/excalidraw/repo.excalidraw @@ -0,0 +1,631 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "ribsikcWWSf8Aw4M6BOYJ", + "type": "rectangle", + "x": 458.6370544433594, + "y": 379.5105285644531, + "width": 169.21945190429688, + "height": 104.2457275390625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 698826281, + "version": 360, + "versionNonce": 899087049, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY", + "JinTKutXOSTzURP969rwo", + "nXkZQsrtjmNqPM6SmPxrc" + ] + }, + { + "id": "GkrbG--OvBT9zJ-w8E5oQ", + "type": "ellipse", + "x": 427.04335021972656, + "y": 65.240966796875, + "width": 215.20677185058594, + "height": 152.14088439941406, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 995070601, + "version": 679, + "versionNonce": 1333899847, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY" + ] + }, + { + "id": "KPLvdnBVoU3U5XGhCch-x", + "type": "text", + "x": 515.5735473632812, + "y": 419.6575622558594, + "width": 48, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 938497417, + "version": 202, + "versionNonce": 677296553, + "isDeleted": false, + "boundElementIds": null, + "text": "Trivy", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "Ry65W-Cczzy8M9JsouEgZ", + "type": "text", + "x": 488.6784362792969, + "y": 116.34368896484375, + "width": 89, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1676815783, + "version": 451, + "versionNonce": 490852711, + "isDeleted": false, + "boundElementIds": null, + "text": "GitHub\n(trivy-db)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "eJn9MVaNlcyj8-YPfrBSY", + "type": "arrow", + "x": 538.7528045696993, + "y": 220.23935960349465, + "width": 0.45618097890837817, + "height": 154.16252445127046, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1741763111, + "version": 1292, + "versionNonce": 764124297, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.45618097890837817, + 154.16252445127046 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "GkrbG--OvBT9zJ-w8E5oQ", + "focus": -0.03598869040285281, + "gap": 2.911871895302724 + }, + "endBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": -0.04563780983822428, + "gap": 5.10864450968802 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "2P02jU3j2eEc92lH0YSwm", + "type": "text", + "x": 190.61294555664062, + "y": 255.17030334472656, + "width": 327, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 501371753, + "version": 569, + "versionNonce": 2012286087, + "isDeleted": false, + "boundElementIds": null, + "text": "1. Download Trivy DB\n(including vulnerability information)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "YK7xYiSKb1RwCCobsRAXm", + "type": "text", + "x": 588.7474975585938, + "y": 488.1255798339844, + "width": 280, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 2027931817, + "version": 628, + "versionNonce": 30461609, + "isDeleted": false, + "boundElementIds": null, + "text": "3. Traverse directories\nand look for necessary files", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "Z_DiM_TKY2bBa4Q5VEWgI", + "type": "text", + "x": 877.162353515625, + "y": 618.5094604492188, + "width": 160, + "height": 25, + "angle": 0, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1365660617, + "version": 647, + "versionNonce": 760156423, + "isDeleted": false, + "boundElementIds": null, + "text": "Local Filesystem", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "K0XdIaMYVmUP2kGc8Oe1O", + "type": "rectangle", + "x": 360.9377136230469, + "y": 357.07373046875, + "width": 695.5669860839844, + "height": 297.8450317382812, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1367853545, + "version": 417, + "versionNonce": 1598611913, + "isDeleted": false, + "boundElementIds": null + }, + { + "id": "9cBakj4Z-FKXwYYdweyW1", + "type": "ellipse", + "x": 774.155647277832, + "y": 56.2083175778389, + "width": 215.20677185058594, + "height": 152.14088439941406, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#4c6ef5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1010956009, + "version": 673, + "versionNonce": 825999529, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY", + "vGa683rpZ9AztfvrVvEyD" + ] + }, + { + "id": "5y2AKGYkXhCldwQOecwl6", + "type": "text", + "x": 806.7732543945312, + "y": 106.18511658906937, + "width": 147, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1289267591, + "version": 83, + "versionNonce": 1127203721, + "isDeleted": false, + "boundElementIds": null, + "text": "Remote\nGit Repository", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "vGa683rpZ9AztfvrVvEyD", + "type": "arrow", + "x": 884.4609964246083, + "y": 220.86864013003574, + "width": 0.45618097890837817, + "height": 154.16252445127046, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1762737031, + "version": 1200, + "versionNonce": 457627015, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.45618097890837817, + 154.16252445127046 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "9cBakj4Z-FKXwYYdweyW1", + "focus": -0.022674122391029432, + "gap": 12.541597764893623 + }, + "endBinding": { + "elementId": "4_okkFmweGK_2DBTmRp4i", + "focus": 0.047202684587572305, + "gap": 3.295263653270979 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "4_okkFmweGK_2DBTmRp4i", + "type": "rectangle", + "x": 796.4703521728516, + "y": 378.3264282345772, + "width": 169.21945190429688, + "height": 104.2457275390625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#4c6ef5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 853231143, + "version": 387, + "versionNonce": 1096837737, + "isDeleted": false, + "boundElementIds": [ + "eJn9MVaNlcyj8-YPfrBSY", + "JinTKutXOSTzURP969rwo", + "vGa683rpZ9AztfvrVvEyD", + "nXkZQsrtjmNqPM6SmPxrc" + ] + }, + { + "id": "zvgwnUmSms_XcbMDespIP", + "type": "text", + "x": 847.181640625, + "y": 404.5675476193428, + "width": 72, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1769283399, + "version": 307, + "versionNonce": 1392544935, + "isDeleted": false, + "boundElementIds": null, + "text": "Git\nProject", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "f0kayc0oXZazxPJKizaM1", + "type": "text", + "x": 916.8968811035156, + "y": 253.85886842012405, + "width": 258, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 49135625, + "version": 649, + "versionNonce": 1150891337, + "isDeleted": false, + "boundElementIds": null, + "text": "2. Clone Git Repository\n to local temp directory", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43 + }, + { + "id": "nXkZQsrtjmNqPM6SmPxrc", + "type": "arrow", + "x": 640.9059448242188, + "y": 429.34260255098343, + "width": 146.4581298828125, + "height": 0.93572998046875, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 2075020231, + "version": 93, + "versionNonce": 1421238215, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 146.4581298828125, + -0.93572998046875 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "ribsikcWWSf8Aw4M6BOYJ", + "focus": -0.031650787945314215, + "gap": 13.0494384765625 + }, + "endBinding": { + "elementId": "4_okkFmweGK_2DBTmRp4i", + "focus": 0.050151997400131744, + "gap": 9.106277465820312 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "OPRFQTrsfmF5a7us-mxkI", + "type": "draw", + "x": 446.98912048339844, + "y": 467.28928834199905, + "width": 72.64572143554688, + "height": 72.18890380859375, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1541066697, + "version": 504, + "versionNonce": 1115999975, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -39.526153564453125, + 16.35540771484375 + ], + [ + -32.219512939453125, + 72.170166015625 + ], + [ + 30.40386962890625, + 72.18890380859375 + ], + [ + 33.11956787109375, + 32.5743408203125 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "QzKnYI7o5Yxg_7szebL6Z", + "type": "draw", + "x": 472.81190490722656, + "y": 507.65403443574905, + "width": 16.976165771484375, + "height": 16.45367431640625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 2144447815, + "version": 127, + "versionNonce": 807483145, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 6.905059814453125, + -16.45367431640625 + ], + [ + 16.976165771484375, + -5.1099853515625 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "qqbEan2u1uU3loTdTvQ4B", + "type": "text", + "x": 376.983642578125, + "y": 561.5254028439522, + "width": 254, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1619652615, + "version": 718, + "versionNonce": 1190681095, + "isDeleted": false, + "boundElementIds": null, + "text": "4. Detect security issues", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/docs/imgs/fs.png b/docs/imgs/fs.png new file mode 100644 index 000000000000..2a6acb83d058 Binary files /dev/null and b/docs/imgs/fs.png differ diff --git a/docs/imgs/gitlab-codequality.png b/docs/imgs/gitlab-codequality.png new file mode 100644 index 000000000000..b7069b8af7c1 Binary files /dev/null and b/docs/imgs/gitlab-codequality.png differ diff --git a/docs/imgs/image.png b/docs/imgs/image.png new file mode 100644 index 000000000000..e014f0c7b653 Binary files /dev/null and b/docs/imgs/image.png differ diff --git a/docs/imgs/logo-horizontal.svg b/docs/imgs/logo-horizontal.svg new file mode 100644 index 000000000000..9cdd1b594dd9 --- /dev/null +++ b/docs/imgs/logo-horizontal.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/imgs/logo-white.svg b/docs/imgs/logo-white.svg new file mode 100644 index 000000000000..f546d23b3358 --- /dev/null +++ b/docs/imgs/logo-white.svg @@ -0,0 +1,3124 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + o2fP3CYLc0HTkrm0lY1xu0zMha0ey7rdZNm2T5Y96/OLnNmM862zcbx3fycRz5bhW6t5Hvdqo06v +J7I07En0+z+PgN5I89Au6iz0QpqCHefwrr/YoWlf6u4yGq8bDWQJ2H8s+X7P5F6to8hHG4Ea9iZW +xJ80anj3OPL9nsa8G+db93HAc3YN4ZsnyhS8n1gXccB0P4ZPjXaBs8toAOPqpVPvncCDoz00SWjb +BNr5mT65WqZvrXZJ81uXM+4222bG1RHf2TWEcj1Gj22O2WujY/LcZhk+uJmHUc/3UOb1lzk0mXdZ +m13ryjJ5bH2GD87L4LnVMXhr9Awf24eRS6uzal926c5cl7V5KxPIRuso4vmbwjuap3HP+zzy2TuI +ebeOuK4ro9fmaf7e7CDKP7uo8vATYRLaS6Xfe+iS8N8Y3v2awLhvU0jnawTnuk0hndcB49lMpN+b +ysSkXVR5aNMAvnmbwblf5DnodxD1Pgxe2owDnrOJLgftpc/wzQQK7hptFtpKn987qRNs90Dq2TiH +dfYOop59853zM31w9YwfXF20ediVQL3dIUnBblBk4I3zrbNlANtmF7hedsaBw4vezZ0hdKOzbr2M +azFo4BsmfPEtBg38LgMzKwuD+TvTzoTZuDB7atts3B/jaWMduJk2LCaQbcwmMY5rA6aTX+7QymL0 +2LTZt5uM97ENnAyDBl7mdYHXYBiyclyxLF5XzEVuFuMdwwauAcIEPub1lcWzyWL61rQyhG1cmcI2 +7oxh2yxzyLZ1eeNq+Q8D49oLGLgP0+DSNtbBRa1MzAVNHsum+Q5ZN5os60aTuaxtO3C6wwbewcIX +pzdosNlQ52xpaVVhMTlA20O7wITFRMVUxcRVxVTFhMXERcVE9VYzUTFZaTFxaVUx3VRSTFRMVEw/ +R6xmCusdLLCpwfWxXWiQk6FmqmJKEJWTx7anh6Z1cDeVdcWBiSpLC2uqC0trSyqHjokqS2uLqUpr +aw+NjGmt7itHzmnL4UB1NUWFhYWVdTXV1WWllTXVZdVlRVdlZWVlZbWVpUWVtXVVxTWFRdXVxTWF +lUW1lcV1NXVFxXVFtXV1hUVlpTV11aWVZVVF1aVldUU1ZVVFZWXVpaXFRdXVhZXFYYtrK2uLS4uL +qivryoqLiotLy2pqa4tqa2urisqq6upqC+sKq0vrampLyyrrqutqi4PVllbXltXVlpaVllXW1ZWW +FtcVlawgpy3HQlXWlRaWlpVWV1dbjoWrqa2tKq0qrS0urakqrSu1rS0rLjqrC0xVfeuKi2pra4vr +qmqramsrq6tqy2prq6pqi2uL6kprympra8pqi2tqy2pra2vrSmpri2oKa2sLawvrqiprKyvrSuuK +qwsLq6tra0pLa2tra2uryuqKqoprC2tqa2vr6iqLSsvKasuKi2pqa2sLqwurampLK+sKa6tKS2oq +a0tqKitrSwtra+tqS+tqa2vrSmpLa4pqq2pKq2uqS6tLq2uKawuLaypLS6ura0pLS2uLamtKS0tL +S2srS6urKitLa2trKktLayoLCyvrCktrS2tK60rLauqKqmrqSkury8rKwpXVVVZXF9aV1haV1taV +1JWWlZbWlFWWlZVWlpXWFpcWVhXWlJXWlpWWVhYX1RUXFteWFZdWVpXVlhaVFpVWVldWV1dWVlYW +lVUWF1VXVhYXV1ZW1dVWVtVU1laWFVZW1pRWltZV1lbWlFZWFxZXVlfXVBbX1VRW1lYW19YUVpbW +FFYWVlZW1tbVVZdVlpZVVtdWFtcVV1ZWlVYXVxfXVhVX1hYXFtYWldUV1hWW1taVFJZVFlYVVtZU +FdZWVhUWFlYVVhYVFgcqrCuuKSqsriyuKyysqSyuq62tqquqrK4qLq0urKktLSqtrGKYplLmIvWw +BIA08EkClndUxpJ9TJiAKgrZ5EQEcopp6AMHphdHDU1vjgI0D3vA2ETssYpZ6KOG5mGPFpyJOFg3 +xTtSNcM5PmiKb6BuKuoIBZUksugpIgpcJJQqtmKV2B/essRDUrnyK2NkMQscB0BbN06ALMM4FkPS +llAvnAL+KLxcnkngZVsNONOPEw96YeJAfIjn8B1ew214DcfhNzyHB8EQcSchJn6AyABvwOTE4bQU +oJB4gjkYrwDLQkCKDVQJeABj4iEZJmTisAOPaiCYDwhDDVMB8KBhbKwEDBFLAeTQ8BJLSr8mSSr/ +Yo14Xi4hBF/BQv8FQIqs+eJKWiejD0zPRR6gmog7QmiCdYzwVNRBKlo5BHE0cgjjpyJsZydiENJM +Qx4vLgdhb2Aa9tCBaagDh6dYh6smDylndwjo8GtjHTOfAaZw/ZhIhwfjGBqChDRW4HdkWXxCggQc +C8kAn/ph4lA0SJwJhogbZXm40EsOL+I/vIgfcaQeKE5DcPCC2M2QJbQ38F+hAn8yasCNaBRwIc7E +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 +oCQNjTQGKKDGwTHUplKAo/rmQxQFZxKjtfVodPYNVRHDuD6UOTwAXRJZwGhp4Po9h4XVbx/+MDYH +ALfdu9+4/5GZnrSiMBQYXHduBF3M7Do6NEWCeFO+dNSWpqgV80xD7ypIj4tqyRjoUu7ANXodg6bZ +YG8XQxtngXzFdHpzKsAn0NBxFOQ1vQBhylz7zOSbojA2BlOy6GANJPqgkwGWUbrtKp6np4oarXIw +SUE8gpC9YNaIOdYHclFoGNP1ZyAodrNJpuK3qTZhmpfxSTD26e9dlnDLuPf/ia8Qrf7WNUFruosu +cER5x5K8JwhODqCNGLGUQJWG3ykKei73iMJwYsvqoXDWrIkHDmSa0lfjWJA27OL8kOxEHFgYmK// +8air3g2id4SBDBaIG95ECHoUJTDvjz+9DgkJY9YIRS8UgoiKfuZIyJNH2CQEV7AQsKQCvRc/znVk +kfTc7csxZBDUZYUhxK5oAWrx6xQwLVxF3q4SxCP9u8nBa+Qvut1HHrap7h8fwvSQLrK5ZDvsO2OK +Wiqmpo+9zizEMSMImjMhShf6tHtwSMwUh7Isb9tVxfaWLHH0wgjqdulhmHEAay+RLofs/r7mB6tq +4gQ+sdQFcjwEj3DcZ8ybgjV2JV4smKN4INfIkbyRBwAA7Ad8Z3gHZ38H/3CKQaZf3r3e9ZcYqPut +vFZ86LKrF074/WYWY05TxvO4IYhg1sijSbsMVDU7dil7wi7qDeDDNOwJAHQKqDJhZGM5MDU1MC1l +Y2UwLTQxMzctOTFmNC0xN2M0MTVmZWI2ZWVkNTliODUwZC01NjBlLTQzNjgtYmE3NS1iODYyZTBm +YmQ3NDQgNjM4LjI1MjM3MjNkZTBiNTAtYWQ2MS00NzVmLWI0MTYtNDk3NDMwYmExZjgwOTI0OWEz +OTUtNDBiYi00YjVhLTkxZDMtYmMzM2NhNDlhMGUyMy40NjEyNTUwMTMuIEEFyyEiPK/2b5795nSQ +BlCtZhpYq5HmeaC5I+e3mao5IrTNqQTfThDoBqQNOeCvwLpQOYMQgljDWOean5oxmIYH3NoN2gWy +JmrHSno0ph88+OW3c/SY4ADufA/sOhk27tQGjjVXgOc3pyk8mDWj8d9EBOoGFqYB8gMLp2HQf3PP +ES6BCPTmG1hLj9b0g5cK0QU2dIwAxDttMTgzZTQzZjg0LTRmY2QtNDQ1Mi04ZTNmLThkYTE3ZWNi +NjIzODJlZGI4NjVkLTg1M2MtNDk4YS1hZjMyLWE2NzZlMWUzOWZlNm1sMTBfU1ZHRmlsdGVyDS8g +Og0vWE1MTm9kZSA6DShmeG1sbm9kZS1ub2RlbmFtdmFsdTEgL0ludHR5cC9BcnJheWVUdXJidWxl +bmM7Y2hpbGRyZW4vcmVzdWx0KHR1cmIyYXR0cmlidXRlOyAsc3RpdGNoVGlsZXMobm9TYmFzZUZy +ZXF1ZW5jeTAuMDVudW1PY3RhdjJ0ZmVDb21wb3NpdG9wZXJhdG9yKGluaW5Tb3VyY2VHcmFwaGlj +eDAleGgxMHl3d2lkKUFJX19pZG9iamVjdC9EZWYgOzRmcmFjdGFsTm9pczRHYXVzc2lhbkJsdTFi +MmREZXZpZmVPZmZzZW9kZGRTcGVjdWxhckxpZ2h0aW5nUG9pbnRMLTUwMC16LTJ6c3BlY091c3Vy +ZmFjZXR5bGwtY29sb3I6d2hFeHBvbmVudCgxQ29uc3RhbGl0UGFpYXJpdGhtZXRrazRrM2szMTAx +MjEyTWVyZ05vZC0yMTR5NEJldmVsU2hhZG93TW9ycGhvbG9neWFkaWxhcmFkaXUxLmJibmItZG5i +Mm41RGlzcGxhY2VtZW50TWFwKGJuczN4Q2hhbm5lbFNlbGVjUnlBQ29sb3JNYXRyaTQ0MW1hbmlt +YWNjdW11KG5vbmNhbGNNKGxpbmVhZDVmcm9tdG81dG9yZXN0YXJhbHdheWZpbGxmcmVlemVOZGRp +dHJlYmVnMHM1NTRuY2M4Y2NjY2NjYzhjY2MxY2NjbmIoLTU0MUNvb2xCRF82NmVyRXJvZDY2NF8o +N3JlcGVhdEQoaW5kZWZpbnNwbGkxcmVtb3ZSMSAxOzIwIDE1OzIwMCAyMDA7IDE1IDIwOzEgMSBj +blBpeGVsUGxheTUwIDU7MjAgMjA7RGlmZnVzZTV5ZWxsb3c7Z3JlZW47Ymx1ZTtpbmRpZ287dmlv +bGV0O3JlZDtvcmFuRGlhemltdWVsZXY2ZDExbDFyZWQ1MDEwMTAxMjJucmVkNjg4LTEzNDIwLjAu +MTVudGE4eDU0ZGRvbkZsb29mbG9vZGJsYWNrOyBvcGFjaXR5OnNDbjM1bjFuMDEwMUdyYXkoME94 +LUNvbXBCbHVyVDFuZW50VHJhbnNmRnVuY3RhYmxlVjJGdW5jRy43IDBCMUNvbXBYZmVyRmlyZUEt +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 +yoYSYWyu/bml55/3q1drZU967z91a298mv+p36cST3pv41sf9/QqrRW9Wvzu9srb1/btee2N9336 +W0rrfSqr7TstlfZ6zu/Z1htxvZS6W+yXfq799KesPq3nrrdnz27aM/u8uFaLZb3Y2vpNcX3s9Nqv +Lqm999/OpvhK/JTOt1i6Z4lL2CJh062X0306FRK7ocRtG4ybsSlsUiWn9XO/19u1Ttw/s8+Z7Xe0 +Xj3TOT3T+dPeOyfG7/Lb4n5vb/r/OMvrf2HZljCY+FQIQ40+0WODNK5B6Am/CyhPXEACQBXlUPo8 +ksbE5XKII230eSKtE3oFKrHBLUaEubMdZ/fOVOkz7Y2Ztr1WnJ9v7m7b104rvWNj1IUpDFvRypst +m2u7xBZVwtg6dVFYxqirukVhLa9FXfZWijOV1sJKVdro1U2X2KcBvJHJ+zJzUgy99sZ5LaZ+3YrT +rej3NmUR26c77XyiU9knZpe0Unc8661z1kprpZROCouzUqXu/+7/xtSKl/Y/VZp39pzus7tpd8/p +Ptv7WrHndJ+2r511zlox184PrdKttx/pR2tZirHttForYivSbN1OK1KLwqJbkVbbdlZKq62YKt1W +Sa14q72RUq9uRbditZXCYrWitaxYKYtIrb3RRnspLForWstaF8u2k9b67pfW29k2nRfjx3nmSRvX +tvn+W6+Sjjht22hhlnGPc0ZMo/XGqCtaCrOyW6X8lkVh2aOVtxStx/xUJvYV/y0bf8Z/6or/0b4V +/+NP6Vb8+fktCrOokv0ffdkVw22kfdeM2GPbSOtIKNJIn1bdKcWOrbVivyi2WxYX2y/3imXH7NRG +A4XBxKZITTEiTLy4vhW/9InXWvHfik6FYKLHzlZsp0IYp9fG9rasditeK/q0nF6bc5Y5d2zPH31S +pXijTyoTm0RduS/NFBavFT/+jDV6T4y6onvEb8Wcs2x/mePPeHHG/ZZFYfipUnS38FMhsdupU8On +LfBAnReGJ0z4eSYGmtH2cUK6beM8GVRRT19WbPP/a4y2E0fjQlmnmd6gClTheqdnK5vC/DaW7hSW +JbVKLm2sT2UfTPvSGe3n2HndZRKd6kAVqAJVoApUgSpQBapAFagCVaAKVIEqUAWqQBWoAlWgCgNV +oIoqgSpLjT6xY1MbFIm65sz9nmVX6RTW1BWfzpb1Y63Us7TR5pY02r+19sunLvoR1w== + + + a+O0KIy6flo+rfrWpC2xW9e95XULXytSi7rifKvSOuV9q8ppu+WlRNVl9VinFa1Tor6e+1rZk8I8 +Y2tFmu+UXq+kluxpac4yP4VFay1qRZy/WtHWWSW1dWaKUdfPWiX9+ZhK6j+zpE6JfHZu2Y4prFJY +tPOvdLcsLKsV/eW/FbOVZbtFierPt6xb863rlpyfabfst96isHixW5mtOC2lsr71VLYV8bQidesW +VaKyT/y+1uXMjTHOt+df6qSF2+ctyDZxNLpMKM4LlRpQn7hUo05TeQH+siudsy3F1Ge2uNbG1t6v +ddr2iafbjCv+mfPEtun8Seekd15v+nY+dgsnWuv0J/bum3H+vtUpre304pmpZ3rrbFp95rYV51sp +rbmdTkzbsXv3pG5tZ0vttH2vWzypvRNP2TRX+28tmOjXwokT//RJ37bFNTv+TOvjiW/TS7s6bTtz +nfXxvG0vtmBizhZOrD5l56+12m87H9OMf17aN7eV7RbTpn97VtkxX3qd0q7uFkyk0y2cOKvTKmtt +bDHt6rP+rX3dfWZbL85z4pw74n/6T2vOc16Mc+c87cVPH+N//znnxHTijO2d/W/n4745v1fabm32 +jC/+ip22vbbmPOdn92zBxLZwIq4+61vc1OfFczbuO7N998du3WmlmE7qtfp0bNu7vT6dPqn/9KYY +27fU7eeMsb1N53QLJlZq4cRvl33zxD8rxvi/2+K2+OettSvF2fPnWpvWSm+3WzCxP/fTmSnu/plx +u/1ZLaUWTrT0uuzYten0TNv+bX8672w8K7VZNr233nmz54yxWzBxuv23YKJft3Qmt7oFE7O1862l +95v67HwtxV7/Vir71kq9540Xvzud+O/3Z3rl/fn21mrBRNqNc1PZ8T6tbX++xXXm+e24G9OeGP/s +nC19t9/ubZ9ar9Pefvu3qcWSXgsmOq7z+yXurvbmLCdt/+x5WkuvtWAizm7hxKZ+J+5uWut075mz +7evWM664zu5K/7q722o9+317MXY551s48X/mLPHfd/wZY59Oc7Vgor1ecU8rO1771tb6F+dbqa2W +VkxnTwsmXuoWTrz0LZ1Jlte7cfv9b4uz7Jj98e3uad9O/+6WnS2c6PT63/737qaztsUZ03/P+cqO +dlowEb+FE+/8Ot8942x/fleMu5t+xu10fvZ77cWyv+u3zd+VWjAxTwsnzuyWzqRpvvg//7Utu2Ja +MX18P9Nr570333/8d17P2HoWdlpIZlGrY9nxbWe37fdPHObSn9X2V3zxxDhrcFlLq308+9Z3iS1u +7LXTtI/zn91xtW87/tan9dKet9aaH7vtkdaJGE5nMqGyjwATEQZFBeATwtDCUOMyjFGnAeWJS0Vd +IAi5U0bSMh6hKdY4gMdx4qeFp4v511brjb9eSm21+D97z/k504pztt65PUuvFnv+KaXH9ogU2oKN +Avw0jqh9HknDxFGXmRhoovWOC22ZiROB2UVFW/CBIlFmCsDniYS+bFQpBwzmwdDAUzR5X2bWfwwm +eqay47/Fb6fsSwiSvPt1yrbCLkFp40oiYYYhEmYYEV9cb9uufnG22TruSy+VjbHX3JnWOy+mtlbZ +noa2YAszTuuEdJdMOUSGSRQQ40MkaZ0EoQtOhXKIDPXLOEBQ6y7AUaeZtMynhVop2zKdvSMSsklE +reR9Itetc+L6ld6mjefLjvT+u/+7rZ8ptbZttZjGenPbiaetU3aVPWXn+tUbv//tmbH/pXPKrthO +Kme19XFnenu254krtvPil/PzvD49z8fUXvr/9NavlzqutOIrP9N657Tu/dQ7R5rttfavtV7ppD6/ +5pvne32cKXX79e+3ndNvV6/WI0X0rxdfi6/PS7+zv51OnepoBaJ24jDulgyTLYANoPkCoCEy7ePc +wRvcdbp0UybBT+s+74q3QXi6mIw2sJsIN41KZAhFGomkidqCDYvctqlEBtOWSY+d13XeFp5EGgNN +haIEjDqNw4xGF522QKOBG5RDZDBFBNR6tdKzrfZK94gdLzhu23Dctgm306jzrvDLMCqREhAnkYoP +le+Ce23E+0KPaAMzzIgoCz1caAM3jkrstodY0RZsoUgrZQUqkcHDAT3EbgCvq6hQYO4Zp61RwyYU +g8eOHSuVjh0rN1biTyiGCcUACjNuA7Idq+cCNfiy0IZpXAhuHwY0cYkzVtlxzio70tgTimFCMbyL +gDl2jjXMuE3tOLs9+oRimFAyMlRKmeaJnyYSZhoWK9oppPM0IrgQZpcIePowI24TdRpGtCKXeaIv +E7liRQQUEZ08jTDjNlGGpljbJ5QVGSqj7uR1ogxNccZKZPi00XcRPy0k6zKLKAKePkykLrot5MRh +YM+1OohZGKUGiNRSDT6yQczCKo1V3ovr9JxlXz62Tq/XOp3df7GVTXXAk8gjgTLtu4QMNI2/bi+u +jnHXt0p0zqazJ52z6bxyLl3M7a6y4vvzZ7bqW9VfUgqT7Z+tKu3Tx7VaOd+K86WHstD6Mkwo0/iu +V85Y6Vt5RXfiv95PleqV9EXf1lrpjykRzVRWt7CFffTO+OIqvSTbLSzSx06VppxWxF8xiVGnStJa +iyp1pNhW6ZRoOoXlnKX9t+hL9+i00o/Z2pY2v6zTstOisLZWdiu/dR2r8n/K+/Hzv/TZEjt1ucSy +rSk90kxd9qzyvb39y67oidRM85SXxsf5ykrzuzWhLrez/+Wdcj52ianrfVbs1PXWmlb0SV1uTY9e +5fvMuGWmrvkuaZyz6ZTU5cdvizbO1FZqRZytl9RaS+W0Ys2dXb7lVGluqMtt/Sux5d8v34o00nov +9f+XN0q38sq39nJZY77RShqf2q+y5fXuOWX2SGt9Sl2R0n+ZpfWZ5f2uVVqqlPPFLuu9n+MGCaP1 +RlpffvQ7e94qH1RtcKAKVHGzX16n1sqbva+8L6c7fnldvlvTVpldTite2vPe+BbFRZxxfeoup+XX +nQYODmIsYBDgEPAsKhXdKUFHJNjgOMIByME/FiCCB6lmwGZD4+PiIjnAIV1fR3vjBIbuiqqEOCIu +GC5JHUDCpqwTVEGSs4+CxLpdJancrpKAmDgUCfaxcJCATI7TyRBImCxI+nbVdzMJEo8RyEFSURMH +GRSJgQQiDhKvHxYkDhdFQdJwu0oS3kyCxOsowCC5F0VB0hdFQUKZoAoSAyKQg+Ri8w6SAwsVS/IM +3UBBAJHoEkWFoFgeWum3aEuS+Vg4SCrK83CiSCQJhEFiyhywJAUWKpYEQoooEoSJAYrksnkHSSij +syQl8L8PxEKMTckQQqH4DrcZaQwzNQ/ejL9wQOpfQBCCxQXiQihQsfDAEK8oi48CDAKVBQXM9Egm +KjICYBYUzQnzaRmEBgtBwSAVghJBASIxQ5EISkbCBKkk9NngyA8PUcUKGaDpivUQ3sBoEpG5UCgA +gjkgLwE0f0BCgAESFazIijJ5KgSFocNS6Q0TMki4kY1BUkcgB4nmg8NBgkkgDBKPFFEk2MKWZOKD +w0FCv49BImI7Fso/ZAygb0GpNILJ6S8X9WPiOb6Lke4QRhTXNIB0KggkLPKKKkkkI33isAcCDdmm +MDc6MAvFglQIyqinANfbgFgDDJSsAYsnxswECRIZgoBHYdQQIBcRdCRTISiZDoVRL1hQKl0JcURQ +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 +bnGL2wRlixvNY3XcJ+Cg4xA+b+HmQYbHPWjILBwFlpkkIFAwWoIicDdBShV9ij1KYNw2FkCmb2Zn +Fj4qFcg0j4qUaUqeSmQi0ET1N4UvxqHi5n2Eoi+ekBYAllHqKB+haIZWJm6eS9y6ipD0he5GQP6g +aEbhw2ncs6lxMQsYtwsZpgSKKg+ibhmBAJZMkc0UZdXkMTJW+mJPIWoOxHowlYCAAE4ECiKVT8O3 +mQIFkUrcOFSm4zaRXDgzioeRDIDTIltFEHC0Twfh8QCrF/ipvDDCD+g9VKGWMVSMRUAiwIICCFFB +yFxIT6MJEYWHBlilfVLWirguTq0l8Y7b7AG1abAXAx3lgbvgTCRxuxP0UFnkcTOJQ8eJKEIL96Gv +jtNMHDrOhNCxIKhmIP+IhwuOOAaLywgoJjBuBQ2jPHeBcYEE3aj0SU7yQe7OSF7xOJELECZEwano +2UNWIbDwWD4hwSAyTNxoD4KHy6gncQvFbQPj4UAQS7Wl+HTc5EINgaNM7dOQEjYNngTfqKDfwJ4c +JxQ4AY09V/fp2GVjupFmnm7tnRJj+tYpvp2ttJduHEfu2dnnxNRzdJ70Uoff631/XowlzdRz0G5L +6cyfcct56cX+fbHLm6mXM8Zuq8tqaf2+706p57j0L+4scaUbafb3nbPOWan/xN0Y4/mU9rxva534 +bi9a532/tV6Z88V1dr1/c77veTp11IszfnrvxP82W/mzKf1L8ZyVdrazYluf0mlnrV4r9Smb1ov7 +ev0qba4ezlVeWh3f16+dNbe9M3emV+a2MMf1624fX4o9TGungs78GGP5dl73bF92xdVim6nTaq/X +mmetG8lx7Td+69R+y6b1Wktz55642pf/7k+r/M7Uf17sVmKvG8mR+yd+Sen1tjT7zyvzvdLOiy29 ++D3X7yzvnbZSO7H8n5dOSm29PeXbeS+mnbFjnGl/lfn/5um5c/2Zp8Xv8j693Y5xbc+BJ661f1aJ +56x/qcwU11np7c+0ZZ7Tc9h6s30qu+L/DLNN67V/pVOca5bT4v5toxLGMIYxjGEM4/9/rZRmb5lz +dXzN9LrtbOXNtXH3pU/xtLS/v97pluLG9Fp3K9/a++3U/lfpXe3snO/Es96J8b3YWultN5bjs89L +3W3bS6vb6Vh6W8fT7PZmbGdtaTHN3jV/bitr22xnpvVr04un2+3loO23dsY+b0tcq+fAndtefH9i +KrNbp3XmO92ppE0903wvxhvJ4V0/2695fn68wRz23eLqOSrOuWfGtG0YwxjGMIYxjGFkOWidk3a9 +tVbPcak3rVZaSx2eZa3Voz0rfVsl/WznnbNpnk2rz3/7+GKf2d/il2/r41vnzT5r5+vVSnwptZ/b +XjunU4e1uFba/bNKbKvnmJS+Xyr/r3ul1l3aWz0Hzdbalo7p9pq49mfPMt/qOWbfSt2z41onprht +04olxbctnflJtmK31HM4rvZS+fQ3GK5Z3lor/uzW0jCGMYxhDOMG8zAgLHBC8HQCR2FCQRhSAE8+ +P0/61H7Oful9pxbjOi9mOWr1mWudtF6ZZ3XM2hPn2p/nvT+rw+t0+nnep00xvtX652u/biSH46/Z +6dss86wbyfEZu81/u+uUs59e+jhntxLPp279ykqp58Buc/WXs1aH55ndZtzzNrX/T6e8tG4sB3bv +aTGltm+u9O/1jCduWmu1tVaca/VKPUe2M7vLptVDD+joLYPkoLhjmyA52tHV0Y6NIsMTum+YAdDi +FrePkiokKg+br6Sn0QAiixUnZLIh0EFKzDwOl4zC5wIBM5spzP9Xff9LL24ft58VMwvN96/j9snP +s3xfWz6LV6H4R4nbqfJxRqAA2sO+xS0Wi0WquIHouSgUFTcQK3FzVShfqmTAEph1JpmYjJOBVCQo +HDgUl18m5uyhi9sTrCS1kAZRMd0MaREvGVCb/0oEUgLklk7Ayv1BIDI9AQiPY8QIXqDJBXhCob4a +0OrQ5xMQA2UwAQEzlScf8mQgnI7CEAByAjQHkClBo6BFfAvEUHFCDPe7aIA8biYJkMctbpEPyeKV +L0Z5rsEz6hdDhIMgC5Dpq6VmE67B4Bn1zIuU/LsRF3yYIBF0cfMeqtAWt40AtIjvIFCbr1giFC0L +QAQcyMrCAUkQgCQMQAsByIxbRBfULpTkTS4V3YUqputkkooOVUxXaQWYhKtWf3LSMgDjw1hYWFwj +IXYKw0/3ESxbmQIOurh9C51R1QQsdK1fgNj4TA4sEz4MZYHwmbzL8hCC66SBfK8HE14zcFIqG4TV +hPKVuq87DRo3DYK4cRpKqg2B6HIA+pgOWFbB49CxsBoCPfMy0jxkfBw6Na7VwpRAjoKg9aMKmoVb +0JSoRVY1edBjpc8hQTJoZDLz0siA1UUTsXgmGRSICuA0eGynihEIdJnOLJEcBBYuMixAg0po8bgV +jAIw+rVggfD1xrWguRjUWk3rNkrgvUxAfmBAu0mjmNrnwoqIW9xKWeRxAq8LCqeJ0EKT0f1c7oFI +lp80oYObB5DpM0Xc5QEmHg/GDYa6jFrhIaPgH1z1klQbMwH8GVio0CB8JtVm/sIL9FBbbXQoHfvG +zSKZ6GbC6co0OCqUvpOE7KTRaD0ONAIX1OqIDC0O0gAx7x1OF3H7XGhE3DhUpuMWc4gbWKL9SbI6 +zgBVsnDyATLhEDzscbIK+1VoaGjkJMmP2+lidNJIwFgCNwUY0zd5gSzHaD0eHD0yBxqOISXqWkGq +cZtpZGwaVJEZEzQl0EPCaHWsscs7EE34nBsrfZE/eGlKJYduSgym51Op083MZnGL2zfIuE0DD3uc +QMBBFxHAZeEkk7hFOk8NgQGp/lRBs/AYzAv5DCUx+RwVSh+m4CTqAo2QqD9JpOK04lg8bnGLAdvH +wBNjjFvLYqURgJiL5mjNYjVaa2bhaFmsNFq1laMFgBXNYpu1XhermYLnkcIYxjCGMYzUUk+559ry +zsYYN33p+HP93E2r55yt/fk2f37anR2Xvn9feutTeZt6DvsUX2vdqXXQauek9O2teMpM/17vjGm/ +9c5X4vxeJ30qP0/P4dhW68D+/7Pi7jslxdbzvLcxpRb79May8ZwUY3mpnZnaOt8nfetV1reOv/Sz +ldS+20yt15butuektKd9+/ennLf6/SpvW0d1/65W5js9B3XvOTOduObcTzFu6pheefH0HPQ6tf/S +tt1eDr+X0mo7W5/S8/Qctimltsp3Sz3/xNlWLGfTz1nSacMYxjCG0Tt/rrSztDlfp7POfDF+em+t +lDq2NNfG+a/s23jWnp4tvhdfn//5Uge10u9tXOv1O6etMtufFv+dt6XPmWlXS/va7HLmWZ9aSXN1 +XNs9sbR1evn9vleH0/v9dOJ6r8R01kmzrLluL9wvK825273aW2XGudbcXmfNk9r8mN6bZfbpOWq9 +9doqKa355sbTw48tzpR+vtdpvpXSfK91iuul71bin//+ts6ab5Z04jvrzPhSl5POimlLt/Pev+7f +Ntfa3hLPusEy9UltxV/DGMYwhjGMYQyj9Upr27cV98SN86RtvU78tPu+xe4/vzO9ubo79nq7uk/P +9K+kd3q11knnrBXja6ltK23f7eXoWt9604kt/ffOLa3jtrbWia/LxjZTTOdfmfur9/uljoyxtdIf +T7c540ul+71uu+KJM64by1Hv/ezX0m6XmM72+/PdZe3ruDhfn5V2buuebXX8/Wkf53ylvf6Ob6We +rexst5fja7X4vu1J88a63bPO252v/Xd5sXW4rfVpT5+zylyn53N6nrXKia2j+r3uVV6LreM7+x/L +ii2u1NJ577Uwx2Oabb3U3eJ6uz+HMYxhDKNbb1y/731KvauVtuY77bxNcZ2O57X/+M7aN/djmat/ +255dHRN7Z+y4Tjnr9NxO2rZiarvtxPlWz1Ria7eX42Z887RPu9KN5ahYUjrbtvyL6XR7bZ31Pb+V +9VoHrvQ2tj9n5+rdbqec12Ls2O2d80r707uV2klzltbajdW5LW6v0i2+c9Zpr73WG2fs/+0v277n +sG27P+f+9p7u3/hajO2t0tr37sS5G+Ovk2b596+dFeNq781ZNn7Pkb1Wij1XSfF7OHvOs94859OM +r8y0rZ21b9swhjGMYQxjGMPY2Zv4rX2vO/aMvb78z3d6vvNi/NjaOzOV9rvp/faKc/0VbBYzTebj +i+W0/9Pfel8uLQ0BKGzEEFoT4GReLnZmfLm0XhYZqxqrhXC5XKweT3svldVz7e6mktLpObKDVjpd +Pp1exY7d7ctc7/Zy3Glp9af40o3lsO9f7ZV15nfcvvV+vvln7jyl289OJ6VT+s/Pt9v23F4Oime2 +tt5a75XXf1K3edqNRe/T+tdKr7TtbEprdXzf2S5xtjRbb9z3JaXVcTs7zt7Zr+fw2/MztdX6S+s5 +d3WKu/GlVv60tdp5pa3f1158L83W4vy1v3qOx7Va+//Vcb9rzk+pbdqY+qVT+qzby+HTXq+5/99z +5M//+eI76wZzOJ393fTp9X/bsymWlN4w0lnrRZGxClFgq9nqdLm0NFauhBYALkgCqxnFauPgtbqo +XWQU1GarWgJFhqd1ccLZ2llro9pp4zmz44sAYLBiL07IP+PP1z9bmZq1ahktzuXSomlcvF6ey+Vi +Ra/jjVVx9a6yYuq4dGb8U35vrFtn+6z2Su9qnVLL8nt9urvXpjPbe6f9jF+6rY5Mq2dq8ddvWemc +l2I53y2etumtVOJbK2EMYxjDGMYwhvEVRlgYwxjGMIYxjGEMYxjDCLu3MZ349XJMfKf7Y/yVzqbW +rzfGn2W/dcxbZ/0qr79j+7St4+JaZ/vseVtez+25/mzal1o5m+aeE2NK/85ccX08sw== + + + 3TgOWq3f+9felxj/xfWz96VyPp392Fa/1VJ7c+7pkl76uOnsrll62765c8+XuG21uD62j7dXZ1q7 +Ype3/sZyfHVLqefAX3PfbPGs0i+19HNn25Je6jm82scYy/vVzsfUc3iSB6jUNIWKJAc5hhRCaGhG +MGEDUxIIADAcFA1KpaPZuGYSfBQAAmM8IDgyQCQiGBociYYisUAcDAgFwmAwEIMoCMZRjKWwk8oG +AEh57IyZziDf254uN6vUSBKHzKieTq7CRL5+K73rPK93LK2fRlaPbfUMWOpCkzmdVkp3hD8nJjQ6 +bqFLZ0NkfSo5w1d9dBWYPl7ofsy51w1ycJdPSo/JdbwqcclMj3o4wRHTA/8hH3zWpgk5fw7DlldM +VzPHvQH/Z8rPt0mTA6TzX99qULG7AgS6fHFUOilg40i3kjIf3RXrCTV++Ta+Wm0HdTACani7jRSx +MP1Inn5VcduZ4IbqjlnSwnpiAwpH25lrMIRB2055FYWFS0glVsxmyc3ixAwfJYzkkGXGZyb+Cr9n +oNZI70WhkUxpF2Hlk5LuBiXI8mPCTWMgsU1xP6o7NXT6UJYFGecsHwmHmg03keDo+5+wMaS24cux +366skhCsLJVlMtATjV7CH53PFXAjKnzSwsqxRy+/9GDFl8fTL9RXK2pdY7R600Kpf7HTo4yUTnLo +0QM1nXAj9LhMJ9nnMdL5qhA48wZlsupmiPQbQ4Isg1fTGTPkhsYo6j/7L5ZsyeYbscqdOtS5Ch+U +B4GvjhLcLhdRqBsF5s9TZCiI+KhQCjA7sp66PgvywEetLGthY66GgytpR/g+Z4vhjEC666j1EW2S +HJskl0MA2lAvquzZAxU/bPpazxLD1maoNXD9mqq1hnqbTB27b/RijKJMe7dcwx6vGnatsZZO+UE5 +gTvxl5T22WCUOVSX6c3QeYa8uSvTn1WGR0eIk+NLd39hiAFQGOW14a0kfwdS2aM3ItPFQszU6YxH +KiMZtjrX6zRO2XJVbMP0NglVmoKzCjGRMkEtxuASnL56l/1XbgP/C9GoE9NrYHSOYiI4XC8H96NO +jYaRfP19etdFsHe8XD9thvVYQHUqy6DutJkOyyPSlxL9lgJI1l6a1G/qJ/3D05X29Z4Uu04mS2rT +hRVq2L2Ida7UdJEh6D0Vt85A7IRi86qZC2KtSSZHHFx7qulSPgaxXHO2/aLDTl8n1tba3eY4o4z8 +cmrP8kUg76bn96NYO0EUBxhw07dw1SI+xLAbmAsIvdZuK6qX66bSKRm62Q7Xgyqk7W+B7Hg5P9zA +zSBjaMf9/y4C2SVsg9qvctMZuIc9X1dEzEttNr6/HMiW6xz3/b88swOtG+6/LpeHPYQ5yG62wOIY +Z8EtbnSPHYy3afEQa6BcHkM33WLLUuHSE4JAalqg1l4vdvTZUy80PT58gVz64PRA17iuNFhvCyHV +Tw3qYXHTiTgd6fFO37uLrjIn0K9mEWsQzmTq79PJJZJTk13JHriHOkQubYk2hsxHlZK6sXrqBNzC +T1cPCL9XEwSsAwTBAKVvjkFotQVH783JSazFp5w8AuhNNoIA1NvN8YPMl1pIyDVd1jMzoQnC4lP5 +pITLgJwdx7BidoR5ea6WB1uPoujPb7ONy07R4abfUOaB6KurEbD0xBK5UiJTWTJ5iZeFPtQjpNYm +DC/qJHwlB/KTuCu0mvJ24K0MuSEWg1h91vXR/jC8Ag6kJhDqcl7VvGkw1JstMV6iPtyWTqyC4sWt +o/5gtBdW6bfORhf13Mxow64LcUXUf6B9Nre+1clIrn0zcmM6sO8N119HiHVco/rq4KD+N9NxGRsl +6ZAeV/SZ+xrJWH2zX0xbvF51U/SFhSi/VyZpwyOjfmKRIp+VNN15U7bqhxKADW3Ib+IGQLIVgJC3 +RLZMjtwYDbZ4Ee3lIS3Zm7hGjvfBmOP5c7GncZlr1nGxt0tFBlR0ESVAZE1RyFZWOsTKbusB3HNG +MUcAU1AOxCEiPbgNoKW6wkFB29I008hyRZY0wo63r3Bfjtls9HiFdGX5JzMCadXFqGtaUb5OQObg +AOgP5Ujrk6rgyKretRrtkdUN3t7XZYin/RzL3XuHFCPRD/NIiAD57UD4c0q8ruJtVy1p/c0Uh7nC +9bJOjRL1EYbprWyOkSQkvCeO5L7MnHSsNj3raxG2u0qwxdy1Nnyp2Pnksw5trlvToK8Wg/jeZWKx +jVAlHSZ9EyEivyBdVshX2rKMrrtxdCDln2T2IhZ+4iUT7SybkhpILLe7Y7291vp3L66y3+eIBQhR +Bl3O811pO6AInSkw7T9WCWdQqKKOUgY6I+KgTYmvTwL+TwMplpCt9ZEQTiPJRoKgzUdFktvG+oMr +gVW73c0chi3JGWlMKrofbOqt2MxV0DqnkQLtM/oxDMmWa0snlsp4n6T03LorYohtUnj6P6++lJAG +SkuQnwXR/uWD8HN9FFDeTr6i/QMMe4ikwoNMJm0kmggbrjjWeYzrBYBazuQ02JoHjsNftOo+4ZC1 +UfERMwnQji+ErIKE0TYI3Tex+rQX7L6UfZB3bN46PWOjDauFRCYW6Do2xloVy0Y+jqIPx+qRXT1Q +o2CCXGd+7ivNKyD1DRrWXUtRWCynTH0iEOi4t6egylSyw+p7+DMMtZ2V4nRjaKzsI6bsbbRwles/ +te9H8ZcqdNQUdoDKlva5frUYEtsoErX61O5/TTRUs7UnftlheU5VXfTpEgss7GQGX4Fpy2evj0Iz +OFp6rVCVLq8UqmogrFI18AIdkbt+A3b5oiADQF2j0AUdIR2SXEMvk0Nc7enwvMF1A8xPpGO81bhw +q2k/2toCbK1kLWDFU0v3FXdJi6pszEHrjfmsWnhzKpa3oo6xw6zRKWtrhvqQNU9hmSNjHe0vsVDv +lfpr4sNTCFPulUsFlhLJaD5Qn+ptmAd6R6dImHFTNJdnigulV+K4KzPIKqUsJ/qflMiJ1ESPQg7F +aNSosXJRUUwUJArQxSJZ/ossQJXDw6AIJxqYAoECf4Z/WoOqGx3ajiX52dNzyZNv7tTpDOvEilrL +dE6HJk52wZuKGkUBMdVVrP3sa1zIJ1IIQ+K6X2QC6cqwL4F1D6dOIbiTJr/OaJRNTaFoGVffnPHf ++m84ytWKdS1v5dMeRgV0laPuaSUeMkpS+Tp1GOGZsM+Qk3tpT4qC4WpDpVJVhOmGeiHRQuQzkGoo +XK6bzHCCaH5vCpEJeD/DdNZFQNCWKA3q6lHWFeUX4qnWmkG51i6chnPMnVtKp3YNNLiblZ6mIXBE +dvgWgK5op5ICSlNTA7HHEhVfCsyOQWVyXkuZeulV20aYGYaPCFWWLP9K/Gm0CgyaEM9DallOTRZT +29E6d9FK5e76tsp0NqVpxSqVzQhFPW4Nq/AlXb827R8xKrEPV99yJdW7mOwuSmmrtI/nr5Sk4sEx +Kp29GjKisxJMn0BlCJyAOaEaK0UGyV0AvhE7HT3Ql0xpd0OKy/5itKUmWfcGnZTHEfJ1LWDrHFSq +A3yC3vvykmMr5c8NMT1KeRBstjM4Vix6zDniJMNIjWC9zVKjf4RQEdSpE3axYwqYYmdBBlpvD0C2 +buap1MYMUcCFaqjJN5lro0jLFCSIIMCwXmSTMY6TOOUprtO11hObTCWGXgLh8Wk1hD4kANbpA3rc +tT3PQvGJMKz0hUE//pSTyZKaTKYtS0N0rFwta08w8hc0q8WfaRhoXBTPExqrx1pYOrio427sw7X+ +LE1qwXV1uazX5IyYpJPV5sJSHs9NHB86FNn4viySpORDGrWpid0TqwxqwFzaQOiruCAyQI5Gr2Mk +uIVkPmuLQF5IdUVb8ZojGq9qQhHl6rnNTk5j1X5amNalar8m2WZ0RgysuSOtrG/PKmfVhLwFK9bV +caKLQYnCVE4urLWcLpzQBbgwmotvGi59i8E+KU9V6NhHhKtojf3oM5ltWodaDllE66SkqZGacBhp +C8ztAsEjE3/PITErRyLaBRCOPWQ99R0H7uM+fdNAGQy9s4jzr1fWvaw71QAhYkOFtIq0eJa7d3kL +bY9/Ycb21YQWw76kO86izGpdme7XY6Z0i0VFeDGrvTaL/J6PJexrR2wV0ucw50++sDkL9KiUbiHc +CAoSNcgZGQmsRmG8kMnAnlnbQhQhB6tJJkKy8y9O7A4mzj7pUFTh9POi9DYkwrcQiFx18SDuceg4 +NZjEkCWmvxZYJJ84Ey5UjaB+IlDXggs3kOmusaSWs3FvW6Ezj7JF5YR8OVWY5p7pJ3TI1OWKhDyd +o/LNmfosP71pPJzgzoXbj0RV56bxTdlb5JfddbLuWCRf4jphLmw0XImjWuIf8U3wQysiNiGURTPf +YwTjvVN0L1BzcX/CaM+JbwWIwy8VsRBmOgMHZZeEsj1Fj0WQP/eTIiSfAb9kVkSrEk2P7wAks51j +IS8rtPqhgSRYJ3C8rNTA5hiLJCfLbCxuo5Ll59kxZkuyLfaNfcKqYSxaMPuC+XVXoF4hBoAqYJaa +1K9SuZChjKvRVAn5PaSa+MBzg3zcg2w53NjdoVvjWG2JMSdRxhZVu17K7XShSVE4SM8yuLOEcLBz +0JZ8UXR6RfiHWBBReHq6yGnJ0AtsabEyZCDMDTDyPEEkFFtnqeCpzaqn7mWcCfqWR44KRwLotVWE +NkkPEBeX5ddgHvwyWxpiJOX30JCIeUs+T5JEU1ZUJcX6hz7FWYl9BxcqFxuiTeOUIZq2kNWwDlkd +TYmmWHJaRDM7N+oD3LA5rRpsRy7VyDRSwV3it3LXsjoRFoIcTAXhvGtGSQy1rJl1lER4a9i6NWtx +Q98TwOlqSMINee7A3dOGCW4lWFdDynhDFrASDFa0wXINqcAbssxKsOYHSx+4lUlwU59V14HbNEr+ +9+e8Iwb4KU8ngJ56S7MCuyIGn3gepKcDy1NBCSJCT6Z5w+MXI2ScbSZ9Er3ANjw01TW6JpoEJPd1 +a+RFbCKpLLTJs1Vi8tqit6ks3WFjqkTAqcZNS+R9mJc0XgU15h89mQUrQIu8l8UD94xEIl+3WbOo +wnUA5sqn/DcTtPWj37qCHqt5rDLidDoh53sQuH47/ltejIxMmlNjq8xYk+xgZYKDbGQXZdpPSoU8 +1ChymMG3/vGZ6MiZDcjW7Xs2MVg/IEOVi86vI4CiLoJ1YWaJZygth0hkdCOP/yjHEOjQcacflY2B +ENr1dolGzjM3LjjKH8UbADnkAeG66nC8WPCggUN9JdBoaGynpLtGvHJCzFUz5Vrpd0J5lJizqc3t +Y3kDKWTCn7K3YpBPqMZ2bK6QvLJDrLxeziCw1vc651FJM0Lc/fJ2O1H5CMan+cIIIZl00frVgudk +us58Uc23w/mqylmDnmuO7w06IOh3JQrr/VJNJgjLe4Hmv/1Eg0BHI4n9s09jAttGNlGQtPl4hlK2 +phCy2ugo2bqBzbKLMgzKwVBlgwAYQGoBc1TpGQIi0RZlH0UICW00stMrR0tz5f/2hg== + + + k/vosS2zpkFq3+15v5MlJSzhfwPWFKtwgkOhHkN5/ZTYXq15Br0tqSA/LGuWFo33cykc1EWz0VwF +kJe6lp4uk0Ad9l6Xrf7sF8qXG5cv49GhFyIZPBE7B36baG1Os3s0D8TVrc50WcQ1WW6BCeKmE3FP +/6V3FeSAVHUYNCWKuG4EcVtGxJUPiHGsQjcaxDXYc01PEfcX6yUWGc8YmXJF3GcURswiC46R/S/i +dn1KySIjBUYW4rRE3JX7s7HIGIeR6S/iWm3V1o5Flh0ju4i43ao4o3PhVC6xkHO3YWTqsejsKuI2 +bJGJtmsrRhakiOvbyQqwyOLDyD4csZ0RjwVGVYV8jJJrq/kL9jJ/tiFuj0x7aSqXXZ3A5ldWIu4v +IG5dFW2MjK2Iq/nh9SiyV/NVZ+rH9RIiDMW1B0VVAj/F6aMbjkr/IcVPIavMIDul0rN9T34iNHtB +MiOvVkelJBWbTnC5WSeaaXAvQGKiw+i8VthEzbRHfP/2g1XPFOcmaodXg+SGilmjmBZ9XrUSYxLI +lEoG83c9OBGO6YM8WjRd4340fUMfCyRD2UJrVEmyI3jUisJz16hZ9saGXi0J86DbI9nsRiAtTniu +kQAwnKT1RmBFtoUEaRibBw2ZNwxYqZrRZaINi08fRNXYqLJu+cWa9NUSS2hNLMUXEDqpYHmB9+eI +W1DYmFk0fNDNx3FvI2SS4zj1KOTDqWIyEnXn4SyoFse+46jHXupJb5UmbSaw6cHC8iAHP8B09zX9 +4RivAgRU9sdJxeBZsa+S7WuopKpPy6xP7zFN0SmfDrkbDZ+orVKBN1sz7G4m00z7FgUHTsoizQCj +R3gzNceN/O6xTr+zikoH3jQm6reM7IYBs14FEzzdjRF4v3iuYI51dyjWTaiwJOKRhE+Dd4PEWRHB +8zES4Z40dXd/2uEg2RKOxNlzd+6xz92q+uVRxQlmt9HCYsekhjvdnf7C+39GgVQM7Um7o4/JNrSN +7B8VgvvlvMcI5oV4o1kio+2xLZbRakJDe/vVvoRm0wwrxHuFvrbvgLJv5UIglWslCo0CVzA6bSMW +WcfPAihPPaCKED1qmxNuz8q1sGj7Lw/K1+tVEeifSLaIA7wx2VsFFCbInIBU7Q+UbP3irMpSpsM5 +mqDzQeEhZZtnU03Lu1FlsKEnHkLJE0S1ivRJ7tionD1c3QzE/dCMBq/Y6QTAe806UdoeocMPhrPB +UJgcoY1wUyoTaSXcCrC6Q/ya0hqXGCe5k/cGjbDtQ4irzlTlCozfp0NFmuO9EHwvL6PQNnEovw7W +JYE0iXUUS5r96qT1NvEs0Uq2mj3heOfYohloIVhO1eLrkGVM2cu6LQRkeWwYhcW2l1n7grpgBK70 +Pbmi5n/huF7TUn5Mlf856nDNFgh9kSIYlnYz6BR5tkFJZjH0owPUoyPYqmBZVvBfkyNgsqJIaVs7 +9jMsKxJoUKVb+roXuoKbytFtHH7rSLgcmQUOJFNKX6uRLCP0RYGJTfYocPWacJKtZxwi8O6aN1Gv +Wf2STUJfkktfL3a/zOBoydrXFKVGKOmLzfnWIfSVtJW+nIDbpWPTI2ikJzx4sEdIs51RQXlekbPE +RCGgy07EwPI0gIuDn0P2kPsDTHWjVdcXur8tE+Vijc6g53mtd33uv4AecvpfjF4pRCxmbfetgloh +qGtG7HRC8wdIUH4Ok6NucZh8ewdx7jcbVTT+W4BM23qn+VqqlxUKkAhB/I1XcM82UBcOXAGoYkQI +zq/+oVxISQPbR+LVIj9YAfaY2BEL1yR81m2aNIwHDzDbU+mUf7Fd6zBNos8kgOfTLTNifRzzuOYw +CScoR/vpgIjlUCt28S/q2muIP0DpTDQUGCNVdid8TZrL5a8x8QjPZDI0a3DZqpIiHCQK2mCmB0C2 +aFycoypcbSMxhFRNJ+5CvuJX7fGlNLtnkO7vhYNlRgw/iVDFPnzQt53r9W7Bvhk0COS4BUdz7k7S +bbe/PNL2hdLuLeftLEuv6ZMgfawF2cRSdxsTc8eTJRQxmFdpIzcKTwDQuZd/ZEGW4GhQDCCDBYrM +AJ7NwJ4XPa52kNe+ttzbGwTue3431jNh/Bi9XF3vIOzhPsv8Q8tX0Kk/aBG77c8ct/ao/o6SnDQx +fNFp2JkZhDgspwMvN1O20ye2aVUVNHZiRRKC8VoBbK+6Bfx2eS7NX6iR9SV4qWmzaCfIp7UwO+rr +Lw35safu7AQZNgPooZUPM0UNYanGAP5iqUhr2UkYYz0TiXPvemPBpUbm7leQLft/NdqVEQe0noMu +NTDMYZ4b9jkt9qUBUCtLA/uOzGFXvuFIiuJqi+uQCSo/0bEq6VdKlEa0y4Bl34zTsxTDwMiJHQf5 +GTxuPNeD1L2+CN71xCZ64w1Wyv3zvFqBxlosAKpA+sVHbB5OaDS2OFhdNsA11QMXa72i5NOujVqd +f0obJLUUoJgwhVq2qpmlEg+IhDfeEQ7AyEB0Nd5Te3Xv+liYZfcY6vEKvvgG5bnQcsaxqWWODmfq +axYxnjWXhQ4wNDhj9Vg+07wiQyoZfYP/fCp32EsRi70r/ro4oGXnWHr9tYobcPEY1z++AW1w3JVD +G3zWY1duIwqbWl7zSY/1GScutdijTxYOLm3r/3X0acUjRP2A2jHXd5kpwlaMGR5iFIe8Kq80CSug +ykt/+bts0pdXs3ukDCvXxSuxX3pgilr8OALS/VthwZ55Ub1KbdDWMn2Bt7SqL+ax8f2FrMWbYqTE +ATNj/EhrW7gJVXkisfK86EFiL9Y9FMOzL+b068uUVcTkXzhTxU7VK1qwT4TfB14mFOkWntHoGIoM +JyKEak0wZkDRfyxfksJOQyl3ofrHhqJOzwr9zO2G2nShwqYNRUMqzIUyuzVUbcmTs1C1taEosYVa +7ZKbqaF2rsF3ofzThuIhFeGFIiIbKhdSRi5U89ZQydmcngvVHBsq85f9WCjgr6HIS1W1C+Udh6MU +CYzrKwnbey/om7uV2QPy+cAAmYGp2S6D9lIuw4ZxvVhe+KGuwTQTjRYFJGWrKVRrTB7BzbiLjiWR +lM4PyrvbPPTpTUGYgDAmJVWjUdjNANhhvkSzeKk2/g1wWg2geIlE5GYb43GNhbRDCanSztCZ71Qw +HdQQwlDlfOHwUqfEhuGz+ZIvfuSSNrRe/dFKjYP80socUN9WahgsYJl7X0CXjWztTGY5ESB7tF7y +BoZLCJIsZ/belSpNgxsaHOzpQt07k6VB20QoGK72VM5YMBF4i4I1R7QmGteEUt74aFhXhbdrB7xc +1AoQ3dgaDlVdcCY4JxIIoDaMGqgCWQSG6SwTNOZ0xwt8LRJD079SSkR7ANWmGZWxdJHOgby++qIW +hGe84zNIPrTgh6X7iOnVE5MLlqm5UZF4ih5EIwKRaudiHk+kldz1iGQIjYw6gJOjt7/KCDDagDSf +GBcw0ILMVvr1x3NMtlOqCAsv0znUj7DBeD0DFFF2KlrwbQVBGNsFJTIh1mAB0vGKS0Lo5J8uZzxL +l4JS8jdYNO8AwagIEyl4h8SZiEgWo8iYQVqgY8XgZpF57eMqchFgsFDQQGNeZFxkcpgodf+PYmmZ +5bBQJRB8EzJ4tGRUA2yhwnh3TYlkLh0xsDrokWwVXcaLtCUbZUz8ARERlEHR0gq/84zG2cOsShbp +Cu0UQSLKIbXyzvFhrKtfR5SIc7BgMCpSS/LDBbQmfHd5jYwXogjXq9nMQ3PDrfEhahnW6TbEauk9 +AoUh7xlNSOI5Vh71ICDS4vHYsQfvK0vUTFlkVaj6PbSR7tlfbWFdRPExrUDsh/9YDgy5x2YPoWIZ +BVQhcDS4I3q8XX/cGkV96zBbh+R8VPkIdbDi32P8KQHetlSC3W4OFGVSpAV5v4mxz/KxxP8IZ66G +xluVCYymgWDfGAH2vXKnbukfE0b85sl513RyHOQWXKm/pEl6yTE6P5lEQzx2BuhCl96yww0LwyXM +LWpitFrUbtG23XaKPFxW+kNQmlgZ65G4+1ddqP5xWoocYv4ZPvx5qe/DJPeEd7rZa8bBf4rtxh7L +k7+KsRrMzA/gyR5UE3pv3Rm+BuivnkTkMrJMKo9F8LKjcbFSDzPJvBdtduoVJcX4pDIiFdJpz10p +jMYTccwZHSJKnFU5Ka4qGFPSbN5IQzYUKPnTwOkcZ8c5V+7wr7Fh4t0VQndWks9SvMiXwJrYWgtS +kdU8v6CUTcKg0YOsM2JYg0/EZFZzZdVpy/7t4kdmX6J9qs5nq4K2EcQWImsTn/kaboHpJpw5tCUC +5NoM4vWvdB7exe7M0NbMny+IppCO8bZBUN6jgmnFkokBU9T7iX5SB/mtmU1GHZa4P5NAKtlMKqqa +YPOoGjap7S6CDr/HIFZtWMtrYwHkQVJQ18LrDOvvx8dUTj5pUuaxLUVE+B+0xxpSVLnHtUTvTzq9 +6Vkm2idsIS50D3vW7yFp0uQ6RBbRWnKh7VhALaFNFGkmud5KFSOt1LU/BK+X8zXFwEqPnKhxv/w8 +5OxxQKxV7kSV2uK6Kg1RV2Cp1Uhjl+pk3PJs91vgu5lNbNJc5FguFihm5adiQGxdgyolbmwfj0kl +vw636rmjGkrrsUnnHB4V9lWRobPrsMu2HIv0ReNVOC6XmAfzM79//IoipSq8CWs/v5CxrFJHOVNG +DjOq9OgZcIo8/ZAf6X23z31iljv0MqXo+UBrs4xN9Huimdo/uvZWui9JmGglQYpsS7EwOUV+oLZA +CVFxTKKayxihYanCiiy1A3xLCpg3PFWRlZWeThlOtxBUg1V+SQteGOpgL6ShgbjEF++AlOJyhFAe +xrrJ/O0/7bdX/O4f/5n3gaaGikyUAoTBfgfpxA8EZFkJatK7vMc+palsHY7PiCX4LKitgm3EaEwT +3Myd7pY3lYjAbZFF93CczfLg7h2p/JDt1cWF95V09ye8zY6arfNlTqUW3pq5tsTIybr7G34YmduR +IEB3nwXelTzDwWGWBKDu3tWOBokkDoqKYLbImFNcVeBKQhyvLDJgZ7c7Kglvidx9d++x5rLt53kI +BlKEftBTdx9oaFsoTywd5SW88drWUyKdcPu9jOfHVNtSbPoRRZuRUNsOoj9Ija/HcXv73YjPHg50 +QuTVtiFWZtPIXvH4tD1lN7O6FvH8CBwniovV1GLJr+3VEECpgpbzne7Si0hUSU6HCliAtnJ7LGN0 +bbeoAPu4vfh6taTtv5K3AqBDEMb+RpW2/VDmDJgcz9ajzGW1rbqlbJYmIJEknH7YkZTk9pyEtL0r +VqJgkpB8fNI+P3reWzK4x+bxo7HwI5dm0jkiGI6N5NJ0X17Sg7wbmXtS0QH09NdkxUPk6bwr5JE5 +GAuw8WrRg62pcdFXRCctGu59Mu3Mr0dGWW+pm9T+9zXJyNbh8xB9gM5Sn4eA4nDbxNCBJX9UHYLH +w3QAGYLZlnvP7pMaOVJtYdb1rHQBWyXRtr1he0XDEMphlTuS8yHr2A7ofaqrySw2WA== + + + WJoF7XjiiTuuWU8Bmw8FbZU4DRMM48kMx9U0c23ZWvPjnNWKH5hImwlXVQ+IPWD2h3K55lkRA4I9 +Y/Ni1P30ErHNEHpyx6y4qSK5uSerRysM9NDwGCIzkWsYdkTCHVF2vvGIg206vCQ78L7aUqm0wuOl +LIysiWhJM2p7d0P3M4ztzNoI6c0ACTXNBENmSx6OlGxG77w+JOvjKZJQd+HzomZ29LN6YgcXQsL+ +B7fUHlIU+P1i/tQshpkm2pi3ltd6kdwaGi+n/7hCC6LWpT9v6K+E+CzY32dCDnFb1o9b4srfyihK +szf82LmWY/L9QWkaUZtDBemB+5dCImULKZAaGUUT/mId9mtElK/itO2mbh+5b5llAjJAG0gBZaSG +QRzLnDp8gYUlhsPGs3TqybG6fqM8zb6x2x/3b4qcCRhZHJMyjrX1s/ft6anUuoOyHW7vkLKmbsoa +C8hqUVACYbujjlGdupxds9vO943Ir+tvvcOMM5ISSg8vMbZLTgCW/IYDy+E6YuNQLBDYWKaXSnIy +b0Tj6g7ujQRdY9VvN2oDHz8+M8EzTrPsYYWXKaS6v38shjanYu0F2hBaPT7p9ov1WLXr5oev4y3S +sSm4JZSb6F2lT+no8XknQVGJvUVgB8Hb42kYK45MadNjaEIZ507obkv5c/jXbessgcO5QOqcEers +SCjytytQDpLSjzWCS9R+AoQBvAEAR9EKVkoBq5FwL8fFQNsBUP7Gtjou+bNPGNe9P2VgxEcYuAYq +PCFxZAwe2g9aENi7r+9oU/R4DK+tuaS2Ze4ShtrYBI8E078YBRVJS8j7K5wU2SPBCvB6Dg6KoDlT +gJa63dZ6yc9940ibeDdp765IaRqFGOx/Mftiqz/QrkA1MQ93mJTDTjT21Eohrelhf1U3pIvNcu0w +3XhICcND+/PsBsuHGmyA+X6hpSkFNbvJ+vFS4HXBNUJtc22SQ/ZLTfs3JFaLXGdl3UhUqc0l+19O +EKcC+lU4mTMBax6yuyZ+ZTOHdpxMhScSfQJ5stZF/KPs8VhBYRCOCP2J3X/MLJuAq3SieiIdYxXX +2e3GBkOeAf6wCynssRvi7mWIvIPBSmTgOb44VFmkR8IhB8byDkAdTpYVlPo9Qyxte12Q67ouzetq +2Qk4rh3oh/0jcwcphkxCdaCKhEwMILT+qgvp9UMrOe2hdRAbC7h3kKq0T39Y+t4HB7DHyRc7P6zN +GmLeptNfWwsOXKonIcUscdniy9C4Ng451iVFDQ35+AL5QLLYa3kPUgfXV8jBx/PCqQlkpb6unBp8 +/Oa+ACPM1hnpADKlJ0BuT9Th+Ei7UQfAvVdl8EFCCqo0g2F/wSItCxHRqStMAAXTJJlEdZrD+Twj +xMVRZqz8vgQ3ZnTtEkMG5fvLZlK3MC9E8RZvUFeNe5xNpwmMYKNYSqMdDQvciZOmjNaTdXyd2bem +TguV0ashoJSqCGuAModhRtPCvLrlafHAIOSSM8cjPL4y949aeS7Q+xQ9+iRz2nrBuYINH9HhYd4U +O38w5yVNqhQfq8gxmkd4Wlglm7oDjbPIUa/i3f9wrRYYhworXqr49aGqClrxd+deIfsSc91dnBbu +wy9IfB32J/aZVNVYs8/4j8yY7svSguGRN7Z8MwjmcWKWQNDN2KjvBlQ/qbHcFKlkTc7MwPK/BQEC +CdWExZRhPeVX8tP/ILPJ+awFCNqulisAZ4CpderAxIt8r5HMuyJoRGHIofU3g3hL3nVDdkqHEEXn ++/igJdMIIvX6w3Csx2UkrwIRTxsRhf26YDgy7YsJVqBJaGzjKZ40bn1X6+H1JO1S8SSMAmmTLM5E +PD10VVCsVVBzM0YRT2SdtFLLqiqe5qscKPdblQORUwik4kndq2JA6RDK59CqRDWm20NPHbzbFcRt +AYaC6Z6E/pxMT/+CBEq5rl10v/jB2v0KygeSlt0+hj5rzGmBaoZyOvjCqAH+LnXHMj9/6vyowZrs +qYLRvXZV0Et40LmSxr3a/gdKpk273ISLrSEDy95/H6vTxBQQMCIJZNmS01ZbiISfX6TwdumYeK2Y +qb0KNv/iSM8HHROiHFAa42fHJ+4/1oHNM4gxXS2XU+cjdNRnD4t4Bq5RgGJ5NmBCp14jjdiSAxfB +WtPhc3MjxWBpi/d9dHRANzI/wpj78y1jJ41UzBJ6LPEZaaCirQlnJH3LlBILzpt7EzuSMIyViNQu +2Y6tIXVu6zZQCg4nvCmXAR6PLMGZxS00VPFn0lixt114OpK8GPDG0032hp5jAgtx5qRL7pUSJ5Fw +S/bExObYZIcvYh4Tit0mAUoQswkVu5A0ZvDwLCBYFIeBW3dyI7a2Jo+nYRR7GS4Fi9iu4lPjAsoZ +ah9PU3cxexOPd4sU248UxcTqCAweO5rGE1zBkW+PJ7Ul1zoJcZ/6zcfTv0a854yQNZ40DQ9ac/Fu +8X8nCR5PAcclm0/jaY4eriwJW9x4GhBZQgYULRWteBWxqCDQk0JaJICPBKpNnWAPPKe5hiriZE0o +aDANw/IIhtIrloloSM2wjR5h5BlA7gXuv9wL1eJH8Bb0BK1wxhZZRiJpPpuxKPTCi39P6igSC8sj +crMCS/jqrNQPMMZISVsJYepvSFpZhvWKzyK9J35p6xncmbwe1ZOJL7eWh1w6YN0wr15gaUZkz+1W +d/Os6sCuP5K6sB2Y5nMD86QjoU7Br7HXJdq5zYGRIYUgVOMVrHcNCMHLjGNzMiaUrlLiLdIPZva7 +3fI73ws3ec3F3FQFMNTjq7cQcsZ81LOWGly81f4XuhVru1/EwDH/GsdEBSybUh3EGwyhENDDnPjT +r38B6JEgWVWDb6KuX6yA45s95NsTdQX0bqsMjopJzDGkkXEMKQqDGAWNj5chaW2u0O7xv3/N5iry +TXDRypASZkvmDrITkdKCiC9DFHoMagOw5IVFC+doDr4tn8rgVhW2ZcjKoHWro3DuSI3RyPSQwlMM +3fsQfO2ylToTSoFOlTkqXJWO8sYrkieJ6YZVN4xMANdSvsTPLNhzWbquUX01U6nXFZN+ZqAWaGPk +bdmsCRyGhUOtTtmJ84EtWBUiyg3oMgW7biwD9ShUQe/DypD011Odgz5NsWxloH6Nu22dvr5nIt2w +ltWXsP4JP4MSWYf4c2eymgCihEtsTyA4X1BIgKbdi/zs5IZEoCwPD3VX149T7slrpkHchpjM+UkI +tXswYQdTTR10rbzeJIPEgR6fnLMibUByIhK61vpV13wIfyU/rJCCZOcCb6BqCZ7hLYnSJ1IV4INi +9EtvZn+xQaN5aCDo5EQAl7FVrVhw1OmlgyvtHx34lY/AUk6IfU8iOy0SHVdWhaCr7lBWx5dZCVF3 +E1GHdW4jhlB8WA6FLi/AFOBhOS5stizvrkkf//5CW89BiwhX5mKyervt+hd2EyYJwr9LAKbwPdt/ +VMyX+NcSITxJ/6L6bMHNn4q56dEArgZZqSnw70q6OZcocvr6ryMnSm669TA37Zix47DIqLnJCf8g +2eFfe3Cs4PIn25tIWRDMM91Nm53mJsc55FoMLBP/2qx/2/qARvu+Fvi3GiXBS6sKZfh3d93cRPds +E8kyTJMM92U4GP/SSoSZ/jWs0PmzN4Bycw158K/n+Egm1VaBqzdxpDLfbi80xb+SKB8ecYgc/Ru6 +i9RoDlpFGgUDukLExP7UMQql6XOktK1/XVx8WAuRkhSTPPrpVsaG8e8LMpvGjDt/tUJ76BZxg3+z +raere1ziA6Pxr02yqHtPuwhT91w1F/yszYndjzn7s1lsOevkssAYI7aZ3YoZsprRnQHGHkOxzjbM +Y+eH7i5q8jJF+n1ftpYsK8ZMibVTHICDAVDSlbIy5mlcoVhTzIfh0x6bopQxI+FRMbIVQ40Xz9IS +/Vjl5JRTYdSqS7EYMItxq8fNe8qiWgENyiLXMl6JwhUgMHNf7TSaFoVZrF7UV+2xF8eDTeIWH+co +aUekY9g7A96twdorA22ByxsZv/4n114loUBGhdXSXOLHY2kRCOWx1Mr0R3UbDCKvALNjhXtAkkbK +Cbk3bUm+QONfh0uYvZ7Re+/61AU8PNDBeyNkJIfUXrWCh8TJF3IxiasnBuH/9aN3W+Jg06E3QRYV +sgIlikZKnHZuKeqjpE3/1P3ZqOxb9HoRbgUUbjNJA28CsPwSaCCNpTzF3VBUVMjOt9q8AZeliwpL +nBTDGBWG8x6S/UKyPWBL8cJV842I0jBnGanZwYHOba0qykBDlDD9cQWFLPkzZdpSKi6lv9ogDD3f +mw5AFpbosW4BSsgwYAy+RAuiugNmOGRcD9V7WqWlOTnkHKiXqP+/1BsT1FVaF1/9K6HeQIlhp7RD +NdRrqQlDjNSrEI9XiWkcz6yuBh1AvTyCf/GejC9Ga5wH9cYMMFpL6u2/khqc4uYYvEEpC++Z1JvE +/jZCkOkM9T56nmhveQKKw5tHT8UzXu8Y7FJer0TCa9h5liXmgzW9bScCbAhiaoNQzZUrYlWdF5Lz +enwEin0FGJp20KIpvhbPzYjYqDXiEGk0HjFRvgyn50rOqh5GA2VbTpbZmavVoQ2O37ThnmXkz6j8 +J2rUHxE9sFMu80b1PMcodQvfM2BofBlDLIgEwgtkBbMbzvwURzfzibLIWUkoBKMeENo20h1rMHkz +kmDHWDA5C859H+oxBaGPFsIZpSzG9eAVE90FFLhlGVLxzaSbSI/skpapTb4heL7NnxnaqwgQ/4Xi +Ad/zvimN3bxieR98xXuDiKbue4BOWQpPtp3QEu/J+LICTFLg3ePlErd/u/i8WcEiFDKi8dmBlIxa +KnYxwCa2FHfqrBboZhpTtPOQCMn4FJ8x1YEegYZ3pixi3rgZbg0iKn3l4HxYc83NbmOVQhQftpL/ +wnb1SmVG6qU7VjUxI9PMFJKvXs4Gajmq3msuQDBIFB0/Vb2hu4ya6cZ0qXrluCkiobt+9W7smznW +ZZhejIquwgvHhDa9SNoKW19i1QqkYtJmb8+Jk9bbf/VyRc72MKFAl6o3xJm+euHO2KRuRJqvwRiL +A6iXV28qGaMzcSyg6hWZro0/JunLsKreBAz14DE4awwyLhQfdw/me89BwF6IvtwbYDnNQub0kWAG +O9tkGlPFWCSq5tgdGQFNa4OpsCZ7FOIbY4R/BGAf5xiq/Q/0VeMWfsHMJVo+yPSaFapx9O9apT3X +clPGRoSPx4eurUuxSwWZKhnwRn/sKojIzFIO3sRXIfhun5362I0qjMo065ec13WwJuYFG30/Q2Kd +FIEdygzRGWzqFlklsi5q2WOXgFMkZ4mcyznepGtqoXNyOeysmKGhOgRmlXFQhi9f+gS2IynvIwYT +xDUJvbjCKVpmPzWO8uDdIvA4Wjr7o2sgx4kJoLLi9QHrPH3Nhl++gATADPyi+KxYTJ0MQmzDpluu +XCwjFqxD7O5y4JJzVzRYgQbLSuQTsVx1pekC9qV3AhxaXNJ8ndAhFbP+XjwRREQxgA== + + + AKiDnSfjnFQ7p1MtUwRE5k5GzjsBo4h3D60B/O5qc362VRAVq1Joh60Z34WsCwheM/kzNzCDdEuP +Qqp86Qrr4HBKSOITQwSI4DrNkzk0SuURj34hUsaG+J4mNMsQ7N3VducOHYe9s8jFu9n/Wmq/u0p9 +Td98TtpwiIt3N0VSMkuj7JEDneGVMMCRxx4R7l+rpkRH8iG/u2H36qwXZIR4V2anSYk68bAhWWuP +d4PPApFHNswYbDLe9YAf6vlSFL+7WUpMRLwb4vTBu7tVFKRdmYJJ8q47Knj1v3eaUCCOQdGqwH/8 +gppmagjac1d7jBttKy186HCwivBQzuKnW86s4QmH8Ap/bb6beNiUrRkhFNKCgSFY5vOt2TJhKGtK +qasSadBYzr9pzBZpDQ1UTmbgUZrBXGsl+fQEWlrWPaUzr+G7zPSnEadN4de3TBMjO8SWBZh4q9gZ +WmBAwloNc/J3tw3o7srNt/lzKYuUjQQVlLUMFrCwMlCL3W2y1Md7PkJ+vjYk17Lzh2F+xU5HdP3E +//NMCneiGRUsmB9yyBSQyZqrL8OWy6X1xtD9jmFxbDWRCWS7iJAMMq2U7XUzjbJtAN2Cx1gMvTQz +sZWZn4SSBvdvaZYgKg== + + + diff --git a/docs/imgs/logo.png b/docs/imgs/logo.png new file mode 100644 index 000000000000..08943b5accaa Binary files /dev/null and b/docs/imgs/logo.png differ diff --git a/docs/imgs/overview.png b/docs/imgs/overview.png new file mode 100644 index 000000000000..777c32d939d2 Binary files /dev/null and b/docs/imgs/overview.png differ diff --git a/docs/imgs/repo.png b/docs/imgs/repo.png new file mode 100644 index 000000000000..80b80689d02f Binary files /dev/null and b/docs/imgs/repo.png differ diff --git a/docs/imgs/secret-demo.gif b/docs/imgs/secret-demo.gif new file mode 100644 index 000000000000..085606ccb563 Binary files /dev/null and b/docs/imgs/secret-demo.gif differ diff --git a/docs/imgs/trivy-aws.png b/docs/imgs/trivy-aws.png new file mode 100644 index 000000000000..5e748fea5757 Binary files /dev/null and b/docs/imgs/trivy-aws.png differ diff --git a/docs/imgs/trivy-k8s.png b/docs/imgs/trivy-k8s.png new file mode 100644 index 000000000000..e959d50c7039 Binary files /dev/null and b/docs/imgs/trivy-k8s.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000000..b72367364181 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,140 @@ +--- +hide: +- toc +--- +![logo](imgs/logo.png){ align=right } + +# Trivy Documentation + +👋 Welcome to Trivy Documentation! To help you get around, please notice the different sections at the top global menu: + +- You are currently in the [Getting Started] section where you can find general information and help with first steps. +- In the [Tutorials] section you can find step-by-step guides that help you accomplish specific tasks. +- In the [Docs] section you can find the complete reference documentation for all of the different features and settings that Trivy has to offer. +- In the [Ecosystem] section you can find how Trivy works together with other tools and applications that you might already use. +- In the [Contributing] section you can find technical developer documentation and contribution guidelines. + +# About Trivy + +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) +- Virtual Machine Image +- Kubernetes +- AWS + +Scanners (what Trivy can find there): + +- OS packages and software dependencies in use (SBOM) +- Known vulnerabilities (CVEs) +- IaC issues and misconfigurations +- Sensitive information and secrets +- Software licenses + +Trivy supports most popular programming languages, operating systems, and platforms. For a complete list, see the [Scanning Coverage] page. + +To learn more, go to the [Trivy homepage][homepage] for feature highlights, or to the [Documentation site][Docs] for detailed information. + +## Quick Start + +### Get Trivy + +Trivy is available in most common distribution channels. The complete list of installation options is available in the [Installation] page. Here are a few popular examples: + +- `brew install trivy` +- `docker run aquasec/trivy` +- Download binary from +- See [Installation] for more + +Trivy is integrated with many popular platforms and applications. The complete list of integrations is available in the [Ecosystem] page. Here are a few popular options examples: + +- [GitHub Actions](https://github.com/aquasecurity/trivy-action) +- [Kubernetes operator](https://github.com/aquasecurity/trivy-operator) +- [VS Code plugin](https://github.com/aquasecurity/trivy-vscode-extension) +- See [Ecosystem] for more + +### General usage + +```bash +trivy [--scanners ] +``` + +Examples: + +```bash +trivy image python:3.4-alpine +``` + +
+Result + +
+ +
Demo: Vulnerability Detection
+
+ +
+ +```bash +trivy fs --scanners vuln,secret,misconfig myproject/ +``` + +
+Result + +
+ +
Demo: Misconfiguration Detection
+
+ +
+ +```bash +trivy k8s --report summary cluster +``` + +
+Result + +
+ +
Demo: Secret Detection
+
+ +
+ +# Want more? Check out Aqua + +If you liked Trivy, you will love Aqua which builds on top of Trivy to provide even more enhanced capabilities for a complete security management offering. +You can find a high level comparison table specific to Trivy users [here](https://github.com/aquasecurity/resources/blob/main/trivy-aqua.md). +In addition check out the website for more information about our products and services. +If you'd like to contact Aqua or request a demo, please use this form: + +--- + +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/index.md +[Installation]: getting-started/installation.md +[pronunciation]: getting-started/faq.md#how-to-pronounce-the-name-trivy +[Scanning Coverage]: ./docs/coverage/index.md + +[aquasec]: https://aquasec.com +[oss]: https://www.aquasec.com/products/open-source-projects/ +[discussions]: https://github.com/aquasecurity/trivy/discussions + +[homepage]: https://trivy.dev +[Tutorials]: ./tutorials/overview +[Docs]: ./docs +[Getting Started]: ./ +[Contributing]: ./community/contribute/issue diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 000000000000..f3bc7637878b --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block outdated %} +You're not viewing the latest version. + + Click here to go to latest. + +{% endblock %} \ No newline at end of file diff --git a/docs/tutorials/additional-resources/cks.md b/docs/tutorials/additional-resources/cks.md new file mode 100644 index 000000000000..657899c3b977 --- /dev/null +++ b/docs/tutorials/additional-resources/cks.md @@ -0,0 +1,26 @@ +# CKS preparation resources + +The [Certified Kubernetes Security Specialist (CKS) Exam](https://training.linuxfoundation.org/certification/certified-kubernetes-security-specialist/) is offered by The Linux Foundation. It provides assurance that a CKS has the skills, knowledge, and competence on a broad range of best practices for securing container-based applications and Kubernetes platforms during build, deployment and runtime. CKA certification is required to sit for this exam. + +### Community Resources + +- [Trivy Video overview (short)][overview] +- [Example questions from the exam][exam] +- [More example questions][questions] +- [CKS exam study guide][study-guide] +- [Docker Image Vulnerabilities & Trivy Image Scanning Demo | K21Academy](https://youtu.be/gHz10UsEdys) + +### Aqua Security Blog posts to learn more + +- Supply chain security best [practices][supply-chain-best-practices] +- Supply chain [attacks][supply-chain-attacks] + +If you know of interesting resources, please start a PR to add those to the list. + +[overview]: https://youtu.be/2cjH6Zkieys +[exam]: https://jonathan18186.medium.com/certified-kubernetes-security-specialist-cks-preparation-part-7-supply-chain-security-9cf62c34cf6a +[questions]: https://github.com/kodekloudhub/certified-kubernetes-security-specialist-cks-course/blob/main/docs/06-Supply-Chain-Security/09-Scan-images-for-known-vulnerabilities-(Trivy).md +[study-guide]: https://devopscube.com/cks-exam-guide-tips/ + +[supply-chain-best-practices]: https://blog.aquasec.com/supply-chain-security-best-practices +[supply-chain-attacks]: https://blog.aquasec.com/supply-chain-threats-using-container-images diff --git a/docs/tutorials/additional-resources/community.md b/docs/tutorials/additional-resources/community.md new file mode 100644 index 000000000000..c1ab7241e4e5 --- /dev/null +++ b/docs/tutorials/additional-resources/community.md @@ -0,0 +1,38 @@ +# Community References +Below is a list of additional resources from the community. + +## Vulnerability Scanning + +- [Detecting Spring4Shell with Trivy and Grype](https://youtu.be/mOfBcpJWwSs) +- [Scan OS of your EC2 instances with Trivy](https://pabis.eu/blog/2023-05-01-Scan-Instances-With-Trivy.html) + +## CI/CD Pipelines + +- [How to use Tekton to set up a CI pipeline with OpenShift Pipelines](https://www.redhat.com/architect/cicd-pipeline-openshift-tekton) +- [Continuous Container Vulnerability Testing with Trivy](https://semaphoreci.com/blog/continuous-container-vulnerability-testing-with-trivy) +- [Getting Started With Trivy and Jenkins](https://youtu.be/MWe01VdwuMA) +- [How to use Tekton to set up a CI pipeline with OpenShift Pipelines](https://www.redhat.com/architect/cicd-pipeline-openshift-tekton) + +## Misconfiguration Scanning + +- [Identifying Misconfigurations in your Terraform](https://youtu.be/cps1V5fOHtE) +- [How to write custom policies for Trivy](https://blog.ediri.io/how-to-write-custom-policies-for-trivy) + +## SBOM, Attestation & related + +- [Attesting Image Scans With Kyverno](https://neonmirrors.net/post/2022-07/attesting-image-scans-kyverno/) + +## Trivy Kubernetes + +- [Using Trivy Kubernetes in OVHCloud documentation.](https://docs.ovh.com/gb/en/kubernetes/installing-trivy/) + +## Comparisons + +- [the vulnerability remediation lifecycle of Alpine containers](https://ariadne.space/2021/06/08/the-vulnerability-remediation-lifecycle-of-alpine-containers/) +- [Open Source CVE Scanner Round-Up: Clair vs Anchore vs Trivy](https://boxboat.com/2020/04/24/image-scanning-tech-compared/) +- [Docker Image Security: Static Analysis Tool Comparison – Anchore Engine vs Clair vs Trivy](https://www.a10o.net/devsecops/docker-image-security-static-analysis-tool-comparison-anchore-engine-vs-clair-vs-trivy/) + +### Evaluations + +- [Istio evaluating to use Trivy](https://github.com/istio/release-builder/pull/687#issuecomment-874938417) +- [Research Spike: evaluate Trivy for scanning running containers](https://gitlab.com/gitlab-org/gitlab/-/issues/270888) diff --git a/docs/tutorials/additional-resources/references.md b/docs/tutorials/additional-resources/references.md new file mode 100644 index 000000000000..fa45cc99d12c --- /dev/null +++ b/docs/tutorials/additional-resources/references.md @@ -0,0 +1,38 @@ +# Additional Resources and Tutorials +Below is a list of additional resources from Aqua Security. + +## Announcements + +- [Trivy Vulnerability Scanner Joins the Aqua Open-source Family](https://blog.aquasec.com/trivy-vulnerability-scanner-joins-aqua-family) +- [Trivy Image Vulnerability Scanner Now Under Apache 2.0 License](https://blog.aquasec.com/trivy-open-source-vulnerability-scanner-apache2.0-license) + +## Vulnerability Scanning + +- [Using Trivy to Discover Vulnerabilities in VS Code Projects](https://blog.aquasec.com/trivy-open-source-vulnerability-scanner-vs-code) +- [How does a vulnerability scanner identify packages?](https://youtu.be/PaMnzeHBa8M) +- [Handling Container Vulnerabilities with Open Policy Agent - Teppei Fukuda, Aqua Security](https://youtu.be/WKE2XNZ2zr4) + +## CI/CD Pipelines + +- [DevSecOps with Trivy and GitHub Actions](https://blog.aquasec.com/devsecops-with-trivy-github-actions) +- [Find Image Vulnerabilities Using GitHub and Aqua Security Trivy Action](https://blog.aquasec.com/github-vulnerability-scanner-trivy) + +## Misconfiguration Scanning + +- [Identifying Misconfigurations in your Terraform](https://youtu.be/cps1V5fOHtE) + +## Client/Server + +- [Using Trivy in client server mode](https://youtu.be/tNQ-VlahtYM) + +## Workshops + +- [Trivy Live Demo & Q&A](https://youtu.be/6Vw0QgJ-k5o) +- [First Steps to Full Lifecycle Security with Open Source Tools - Rory McCune & Anais Urlichs](https://youtu.be/nwJ0366rs6s) + + +## Older Resources + +- [Webinar: Trivy Open Source Scanner for Container Images – Just Download and Run!](https://youtu.be/XnYxX9uueoQ) +- [Kubernetes Security through GitOps Best Practices: ArgoCD and Starboard](https://youtu.be/YvMY8to9aHI) +- [Get started with Kubernetes Security and Starboard](https://youtu.be/QgctrpTpJec) diff --git a/docs/tutorials/integrations/aws-codepipeline.md b/docs/tutorials/integrations/aws-codepipeline.md new file mode 100644 index 000000000000..396339e1c382 --- /dev/null +++ b/docs/tutorials/integrations/aws-codepipeline.md @@ -0,0 +1,4 @@ +# AWS CodePipeline +See [this blog post][blog] for an example of using Trivy within AWS CodePipeline. + +[blog]: https://aws.amazon.com/blogs/containers/scanning-images-with-trivy-in-an-aws-codepipeline/ diff --git a/docs/tutorials/integrations/aws-security-hub.md b/docs/tutorials/integrations/aws-security-hub.md new file mode 100644 index 000000000000..6963eed57241 --- /dev/null +++ b/docs/tutorials/integrations/aws-security-hub.md @@ -0,0 +1,72 @@ +# AWS Security Hub + +![Amazon Security Hub](../../imgs/Security-Hub.jpeg){ width=50 } + +## Upload findings to Security Hub + +In the following example using the template `asff.tpl`, [ASFF][asff] file can be generated. + +``` +$ AWS_REGION=us-west-1 AWS_ACCOUNT_ID=123456789012 trivy image --format template --template "@contrib/asff.tpl" -o report.asff golang:1.12-alpine +``` + +ASFF template needs AWS_REGION and AWS_ACCOUNT_ID from environment variables. + +The Product [ARN][arn] field follows the pattern below to match what AWS requires for the [product resource type][resource-type]. + +{% raw %} +``` +"ProductArn": "arn:aws:securityhub:{{ env "AWS_REGION" }}::product/aquasecurity/aquasecurity", +``` +{% endraw %} + +In order to upload results you must first run [enable-import-findings-for-product][enable] like: + +``` +aws securityhub enable-import-findings-for-product --product-arn arn:aws:securityhub:::product/aquasecurity/aquasecurity +``` + +The findings are [formatted for the API][asff-syntax] with a key of `Findings` and a value of the array of findings. +In order to upload via the CLI the outer wrapping must be removed being left with only the array of findings. +The easiest way of doing this is with the [jq library][jq] using the command + +``` +cat report.asff | jq '.Findings' +``` + +Then, you can upload it with AWS CLI. + +``` +$ aws securityhub batch-import-findings --findings file://report.asff +``` + +### Note + +The [batch-import-findings][batch-import-findings] command limits the number of findings uploaded to 100 per request. +The best known workaround to this problem is using [jq][jq] to run the following command + +``` +jq '.[:100]' report.asff 1> short_report.asff +``` + +## Customize +You can customize [asff.tpl][asff.tpl] + +``` +$ export AWS_REGION=us-west-1 +$ export AWS_ACCOUNT_ID=123456789012 +$ trivy image --format template --template "@your-asff.tpl" -o report.asff golang:1.12-alpine +``` + +## Reference +[aws.amazon.com/blogs/security/how-to-build-ci-cd-pipeline-container-vulnerability-scanning-trivy-and-aws-security-hub/](https://aws.amazon.com/blogs/security/how-to-build-ci-cd-pipeline-container-vulnerability-scanning-trivy-and-aws-security-hub/) + +[asff]: https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-findings-format.html +[asff-syntax]: https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-findings-format-syntax.html +[arn]: https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html +[resource-type]: https://github.com/awsdocs/aws-security-hub-user-guide/blob/master/doc_source/securityhub-partner-providers.md#aqua-security--aqua-cloud-native-security-platform-sends-findings +[enable]: https://docs.aws.amazon.com/cli/latest/reference/securityhub/enable-import-findings-for-product.html +[batch-import-findings]: https://docs.aws.amazon.com/cli/latest/reference/securityhub/batch-import-findings.html#options +[asff.tpl]: https://github.com/aquasecurity/trivy/blob/main/contrib/asff.tpl + +[jq]: https://stedolan.github.io/jq/ \ No newline at end of file diff --git a/docs/tutorials/integrations/azure-devops.md b/docs/tutorials/integrations/azure-devops.md new file mode 100644 index 000000000000..1fbac0e848d7 --- /dev/null +++ b/docs/tutorials/integrations/azure-devops.md @@ -0,0 +1,22 @@ +# Azure Devops + +- Here is the [Azure DevOps Pipelines Task for Trivy][action] + +![trivy-azure](https://github.com/aquasecurity/trivy-azure-pipelines-task/blob/main/screenshot.png?raw=true) + +### [Use ImageCleaner to clean up stale images on your Azure Kubernetes Service cluster][azure2] + +It's common to use pipelines to build and deploy images on Azure Kubernetes Service (AKS) clusters. While great for image creation, this process often doesn't account for the stale images left behind and can lead to image bloat on cluster nodes. These images can present security issues as they may contain vulnerabilities. By cleaning these unreferenced images, you can remove an area of risk in your clusters. When done manually, this process can be time intensive, which ImageCleaner can mitigate via automatic image identification and removal. + + Vulnerability is determined based on a trivy scan, after which images with a LOW, MEDIUM, HIGH, or CRITICAL classification are flagged. An updated ImageList will be automatically generated by ImageCleaner based on a set time interval, and can also be supplied manually. +### [Microsoft Defender for container registries and Trivy][azure] + +This blog explains how to scan your Azure Container Registry-based container images with the integrated vulnerability scanner when they're built as part of your GitHub workflows. + +To set up the scanner, you'll need to enable Microsoft Defender for Containers and the CI/CD integration. When your CI/CD workflows push images to your registries, you can view registry scan results and a summary of CI/CD scan results. + +The findings of the CI/CD scans are an enrichment to the existing registry scan findings by Qualys. Defender for Cloud's CI/CD scanning is powered by Aqua Trivy + +[action]: https://github.com/aquasecurity/trivy-azure-pipelines-task +[azure]: https://docs.microsoft.com/en-us/azure/defender-for-cloud/defender-for-containers-cicd +[azure2]: https://docs.microsoft.com/en-us/azure/aks/image-cleaner?tabs=azure-cli diff --git a/docs/tutorials/integrations/bitbucket.md b/docs/tutorials/integrations/bitbucket.md new file mode 100644 index 000000000000..6b42855dcb38 --- /dev/null +++ b/docs/tutorials/integrations/bitbucket.md @@ -0,0 +1,5 @@ +# Bitbucket Pipelines + +See [trivy-pipe][trivy-pipe] for the details. + +[trivy-pipe]: https://github.com/aquasecurity/trivy-pipe diff --git a/docs/tutorials/integrations/circleci.md b/docs/tutorials/integrations/circleci.md new file mode 100644 index 000000000000..5f0152c2ac0f --- /dev/null +++ b/docs/tutorials/integrations/circleci.md @@ -0,0 +1,34 @@ +# CircleCI + +``` +$ cat .circleci/config.yml +jobs: + build: + docker: + - image: docker:stable-git + steps: + - checkout + - setup_remote_docker + - run: + name: Build image + command: docker build -t trivy-ci-test:${CIRCLE_SHA1} . + - run: + name: Install trivy + command: | + apk add --update-cache --upgrade curl + curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin + - run: + name: Scan the local image with trivy + command: trivy image --exit-code 0 --no-progress trivy-ci-test:${CIRCLE_SHA1} +workflows: + version: 2 + release: + jobs: + - build +``` + +[Example][example] +[Repository][repository] + +[example]: https://circleci.com/gh/aquasecurity/trivy-ci-test +[repository]: https://github.com/aquasecurity/trivy-ci-test diff --git a/docs/tutorials/integrations/github-actions.md b/docs/tutorials/integrations/github-actions.md new file mode 100644 index 000000000000..2dade1051a11 --- /dev/null +++ b/docs/tutorials/integrations/github-actions.md @@ -0,0 +1,9 @@ +# GitHub Actions + +- Here is the [Trivy GitHub Action][action] +- The Microsoft Azure team have written a [container-scan action][azure] that uses Trivy and Dockle +- For full control over the options specified to Trivy, this [blog post][blog] describes adding Trivy into your own GitHub action workflows + +[action]: https://github.com/aquasecurity/trivy-action +[azure]: https://github.com/Azure/container-scan +[blog]: https://blog.aquasec.com/devsecops-with-trivy-github-actions diff --git a/docs/tutorials/integrations/gitlab-ci.md b/docs/tutorials/integrations/gitlab-ci.md new file mode 100644 index 000000000000..d1afc9128e4d --- /dev/null +++ b/docs/tutorials/integrations/gitlab-ci.md @@ -0,0 +1,181 @@ +# GitLab CI + +GitLab 15.0 includes [free](https://gitlab.com/groups/gitlab-org/-/epics/2233) integration with Trivy. + +To [configure container scanning with Trivy in GitLab](https://docs.gitlab.com/ee/user/application_security/container_scanning/#configuration), simply include the CI template in your `.gitlab-ci.yml` file: + +```yaml +include: + - template: Security/Container-Scanning.gitlab-ci.yml +``` + +If you're a GitLab 14.x Ultimate customer, you can use the same configuration above. + +Alternatively, you can always use the example configurations below. + +```yaml +stages: + - test + +trivy: + stage: test + image: docker:stable + services: + - name: docker:dind + entrypoint: ["env", "-u", "DOCKER_HOST"] + command: ["dockerd-entrypoint.sh"] + variables: + DOCKER_HOST: tcp://docker:2375/ + DOCKER_DRIVER: overlay2 + # See https://github.com/docker-library/docker/pull/166 + DOCKER_TLS_CERTDIR: "" + IMAGE: trivy-ci-test:$CI_COMMIT_SHA + TRIVY_NO_PROGRESS: "true" + TRIVY_CACHE_DIR: ".trivycache/" + before_script: + - export TRIVY_VERSION=$(wget -qO - "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') + - echo $TRIVY_VERSION + - wget --no-verbose https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz -O - | tar -zxvf - + allow_failure: true + script: + # Build image + - docker build -t $IMAGE . + # Build report + - ./trivy image --exit-code 0 --format template --template "@/contrib/gitlab.tpl" -o gl-container-scanning-report.json $IMAGE + # Print report + - ./trivy image --exit-code 0 --severity HIGH $IMAGE + # Fail on severe vulnerabilities + - ./trivy image --exit-code 1 --severity CRITICAL $IMAGE + cache: + paths: + - .trivycache/ + # Enables https://docs.gitlab.com/ee/user/application_security/container_scanning/ (Container Scanning report is available on GitLab EE Ultimate or GitLab.com Gold) + artifacts: + reports: + container_scanning: gl-container-scanning-report.json +``` + +[Example][example] +[Repository][repository] + +### GitLab CI using Trivy container + +To scan a previously built image that has already been pushed into the +GitLab container registry the following CI job manifest can be used. +Note that `entrypoint` needs to be unset for the `script` section to work. +In case of a non-public GitLab project Trivy additionally needs to +authenticate to the registry to be able to pull your application image. +Finally, it is not necessary to clone the project repo as we only work +with the container image. + +```yaml +container_scanning: + image: + name: docker.io/aquasec/trivy:latest + entrypoint: [""] + variables: + # No need to clone the repo, we exclusively work on artifacts. See + # https://docs.gitlab.com/ee/ci/runners/configure_runners.html#git-strategy + GIT_STRATEGY: none + TRIVY_USERNAME: "$CI_REGISTRY_USER" + TRIVY_PASSWORD: "$CI_REGISTRY_PASSWORD" + TRIVY_AUTH_URL: "$CI_REGISTRY" + TRIVY_NO_PROGRESS: "true" + TRIVY_CACHE_DIR: ".trivycache/" + FULL_IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + script: + - trivy --version + # cache cleanup is needed when scanning images with the same tags, it does not remove the database + - time trivy image --clear-cache + # update vulnerabilities db + - time trivy image --download-db-only + # Builds report and puts it in the default workdir $CI_PROJECT_DIR, so `artifacts:` can take it from there + - time trivy image --exit-code 0 --format template --template "@/contrib/gitlab.tpl" + --output "$CI_PROJECT_DIR/gl-container-scanning-report.json" "$FULL_IMAGE_NAME" + # Prints full report + - time trivy image --exit-code 0 "$FULL_IMAGE_NAME" + # Fail on critical vulnerabilities + - time trivy image --exit-code 1 --severity CRITICAL "$FULL_IMAGE_NAME" + cache: + paths: + - .trivycache/ + # Enables https://docs.gitlab.com/ee/user/application_security/container_scanning/ (Container Scanning report is available on GitLab EE Ultimate or GitLab.com Gold) + artifacts: + when: always + reports: + container_scanning: gl-container-scanning-report.json + tags: + - docker-runner +``` + +[example]: https://gitlab.com/aquasecurity/trivy-ci-test/pipelines +[repository]: https://github.com/aquasecurity/trivy-ci-test + +### GitLab CI alternative template + +Depending on the edition of gitlab you have or your desired workflow, the +container scanning template may not meet your needs. As an addition to the +above container scanning template, a template for +[code climate](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html) +has been included. The key things to update from the above examples are +the `template` and `report` type. An updated example is below. + +```yaml +stages: + - test + +trivy: + stage: test + image: docker:stable + services: + - name: docker:dind + entrypoint: ["env", "-u", "DOCKER_HOST"] + command: ["dockerd-entrypoint.sh"] + variables: + DOCKER_HOST: tcp://docker:2375/ + DOCKER_DRIVER: overlay2 + # See https://github.com/docker-library/docker/pull/166 + DOCKER_TLS_CERTDIR: "" + IMAGE: trivy-ci-test:$CI_COMMIT_SHA + TRIVY_NO_PROGRESS: "true" + TRIVY_CACHE_DIR: ".trivycache/" + before_script: + - export TRIVY_VERSION=$(wget -qO - "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') + - echo $TRIVY_VERSION + - wget --no-verbose https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz -O - | tar -zxvf - + allow_failure: true + script: + # Build image + - docker build -t $IMAGE . + # Image report + - ./trivy image --exit-code 0 --format template --template "@/contrib/gitlab-codequality.tpl" -o gl-codeclimate-image.json $IMAGE + # Filesystem report + - ./trivy filesystem --scanners misconfig,vuln --exit-code 0 --format template --template "@/contrib/gitlab-codequality.tpl" -o gl-codeclimate-fs.json . + # Combine report + - apk update && apk add jq + - jq -s 'add' gl-codeclimate-image.json gl-codeclimate-fs.json > gl-codeclimate.json + cache: + paths: + - .trivycache/ + # Enables https://docs.gitlab.com/ee/user/application_security/container_scanning/ (Container Scanning report is available on GitLab EE Ultimate or GitLab.com Gold) + artifacts: + paths: + - gl-codeclimate.json + reports: + codequality: gl-codeclimate.json +``` + +Currently gitlab only supports a single code quality report. There is an +open [feature request](https://gitlab.com/gitlab-org/gitlab/-/issues/9014) +to support multiple reports. Until this has been implemented, if you +already have a code quality report in your pipeline, you can use +`jq` to combine reports. Depending on how you name your artifacts, it may +be necessary to rename the artifact if you want to reuse the name. To then +combine the previous artifact with the output of trivy, the following `jq` +command can be used, `jq -s 'add' prev-codeclimate.json trivy-codeclimate.json > gl-codeclimate.json`. + +### GitLab CI alternative template example report + +You'll be able to see a full report in the GitLab pipeline code quality UI, where filesystem vulnerabilities and misconfigurations include links to the flagged files and image vulnerabilities report the image/os or runtime/library that the vulnerability originates from instead. + +![codequality](../../imgs/gitlab-codequality.png) diff --git a/docs/tutorials/integrations/index.md b/docs/tutorials/integrations/index.md new file mode 100644 index 000000000000..64c6b993fc6a --- /dev/null +++ b/docs/tutorials/integrations/index.md @@ -0,0 +1,2 @@ +# Integrations +Scan your image automatically as part of your CI workflow, failing the workflow if a vulnerability is found. When you don't want to fail the test, specify `--exit-code 0`. diff --git a/docs/tutorials/integrations/travis-ci.md b/docs/tutorials/integrations/travis-ci.md new file mode 100644 index 000000000000..c9376a4234e7 --- /dev/null +++ b/docs/tutorials/integrations/travis-ci.md @@ -0,0 +1,29 @@ +# Travis CI + +``` +$ cat .travis.yml +services: + - docker + +env: + global: + - COMMIT=${TRAVIS_COMMIT::8} + +before_install: + - docker build -t trivy-ci-test:${COMMIT} . + - export VERSION=$(curl --silent "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') + - wget https://github.com/aquasecurity/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz + - tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz +script: + - ./trivy image --exit-code 0 --severity HIGH --no-progress trivy-ci-test:${COMMIT} + - ./trivy image --exit-code 1 --severity CRITICAL --no-progress trivy-ci-test:${COMMIT} +cache: + directories: + - $HOME/.cache/trivy +``` + +[Example][example] +[Repository][repository] + +[example]: https://travis-ci.org/aquasecurity/trivy-ci-test +[repository]: https://github.com/aquasecurity/trivy-ci-test diff --git a/docs/tutorials/kubernetes/cluster-scanning.md b/docs/tutorials/kubernetes/cluster-scanning.md new file mode 100644 index 000000000000..a0d097cd78a4 --- /dev/null +++ b/docs/tutorials/kubernetes/cluster-scanning.md @@ -0,0 +1,80 @@ +# Kubernetes Scanning Tutorial + +## Prerequisites + +To test the following commands yourself, make sure that you’re connected to a Kubernetes cluster. A simple kind, a Docker-Desktop or microk8s cluster will do. In our case, we’ll use a one-node kind cluster. + +Pro tip: The output of the commands will be even more interesting if you have some workloads running in your cluster. + +## Cluster Scanning + +Trivy K8s is great to get an overview of all the vulnerabilities and misconfiguration issues or to scan specific workloads that are running in your cluster. You would want to use the Trivy K8s command either on your own local cluster or in your CI/CD pipeline post deployments. + +The `trivy k8s` command is part of the Trivy CLI. + +With the following command, we can scan our entire Kubernetes cluster for vulnerabilities and get a summary of the scan: + +``` +trivy k8s --report=summary cluster +``` + +To get detailed information for all your resources, just replace ‘summary’ with ‘all’: + +``` +trivy k8s --report=all cluster +``` + +However, we recommend displaying all information only in case you scan a specific namespace or resource since you can get overwhelmed with additional details. + +Furthermore, we can specify the namespace that Trivy is supposed to scan to focus on specific resources in the scan result: + +``` +trivy k8s -n kube-system --report=summary cluster +``` + +Again, if you’d like to receive additional details, use the ‘--report=all’ flag: + +``` +trivy k8s -n kube-system --report=all cluster +``` + +Like with scanning for vulnerabilities, we can also filter in-cluster security issues by severity of the vulnerabilities: + +``` +trivy k8s --severity=CRITICAL --report=summary cluster +``` + +Note that you can use any of the Trivy flags on the Trivy K8s command. + +With the Trivy K8s command, you can also scan specific workloads that are running within your cluster, such as our deployment: + +``` +trivy k8s --namespace app --report=summary deployments/react-application +``` + +## Trivy Operator + +The Trivy K8s command is an imperative model to scan resources. We wouldn’t want to manually scan each resource across different environments. The larger the cluster and the more workloads are running in it, the more error-prone this process would become. With the Trivy Operator, we can automate the scanning process after the deployment. + +The Trivy Operator follows the Kubernetes Operator Model. Operators automate human actions, and the result of the task is saved as custom resource definitions (CRDs) within your cluster. + +This has several benefits: + +- Trivy Operator is installed CRDs in our cluster. As a result, all our resources, including our security scanner and its scan results, are Kubernetes resources. This makes it much easier to integrate the Trivy Operator directly into our existing processes, such as connecting Trivy with Prometheus, a monitoring system. + +- The Trivy Operator will automatically scan your resources every six hours. You can set up automatic alerting in case new critical security issues are discovered. + +- The CRDs can be both machine and human-readable depending on which applications consume the CRDs. This allows for more versatile applications of the Trivy operator. + + +There are several ways that you can install the Trivy Operator in your cluster. In this guide, we’re going to use the Helm installation based on the [following documentation.](../../docs/target/kubernetes.md#trivy-operator) + +Please follow the Trivy Operator documentation for further information on: + +- [Installation of the Trivy Operator](https://aquasecurity.github.io/trivy-operator/latest/getting-started/installation/) +- [Getting started guide](https://aquasecurity.github.io/trivy-operator/latest/getting-started/quick-start/) + + + + + diff --git a/docs/tutorials/kubernetes/gitops.md b/docs/tutorials/kubernetes/gitops.md new file mode 100644 index 000000000000..b5a30ff47450 --- /dev/null +++ b/docs/tutorials/kubernetes/gitops.md @@ -0,0 +1,131 @@ +# Installing the Trivy-Operator through GitOps + +This tutorial shows you how to install the Trivy Operator through GitOps platforms, namely ArgoCD and FluxCD. + +## ArgoCD + +Make sure to have [ArgoCD installed](https://argo-cd.readthedocs.io/en/stable/getting_started/) and running in your Kubernetes cluster. + +You can either deploy the Trivy Operator through the argocd CLI or by applying a Kubernetes manifest. + +ArgoCD command: +``` +> kubectl create ns trivy-system +> argocd app create trivy-operator --repo https://github.com/aquasecurity/trivy-operator --path deploy/helm --dest-server https://kubernetes.default.svc --dest-namespace trivy-system +``` +Note that this installation is directly related to our official Helm Chart. If you want to change any of the value, we'd suggest you to create a separate values.yaml file. + +Kubernetes manifest `trivy-operator.yaml`: +``` +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: trivy-operator + namespace: argocd +spec: + project: default + source: + chart: trivy-operator + repoURL: https://aquasecurity.github.io/helm-charts/ + targetRevision: 0.0.3 + helm: + values: | + trivy: + ignoreUnfixed: true + destination: + server: https://kubernetes.default.svc + namespace: trivy-system + syncPolicy: + automated: + prune: true + selfHeal: true +``` + +To apply the Kubernetes manifest, if you have the manifest locally, you can use the following command through kubectl: +``` +> kubectl apply -f trivy-operator.yaml + +application.argoproj.io/trivy-operator created +``` + +If you have the manifest in a Git repository, you can apply it to your cluster through the following command: +``` +> kubectl apply -n argocd -f https://raw.githubusercontent.com/AnaisUrlichs/argocd-starboard/main/starboard/argocd-starboard.yaml +``` +The latter command would allow you to make changes to the YAML manifest that ArgoCD would register automatically. + +Once deployed, you want to tell ArgoCD to sync the application from the actual state to the desired state: +``` +argocd app sync trivy-operator +``` + +Now you can see the deployment in the ArgoCD UI. Have a look at the ArgoCD documentation to know how to access the UI. + +![ArgoCD UI after deploying the Trivy Operator](../../imgs/argocd-ui.png) + +Note that ArgoCD is unable to show the Trivy CRDs as synced. + + +## FluxCD + +Make sure to have [FluxCD installed](https://fluxcd.io/docs/installation/#install-the-flux-cli) and running in your Kubernetes cluster. + +You can either deploy the Trivy Operator through the Flux CLI or by applying a Kubernetes manifest. + +Flux command: +``` +> kubectl create ns trivy-system +> flux create source helm trivy-operator --url https://aquasecurity.github.io/helm-charts --namespace trivy-system +> flux create helmrelease trivy-operator --chart trivy-operator + --source HelmRepository/trivy-operator + --chart-version 0.0.3 + --namespace trivy-system +``` + +Kubernetes manifest `trivy-operator.yaml`: +``` +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: trivy-operator + namespace: flux-system +spec: + interval: 60m + url: https://aquasecurity.github.io/helm-charts/ + +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: trivy-operator + namespace: trivy-system +spec: + chart: + spec: + chart: trivy-operator + sourceRef: + kind: HelmRepository + name: trivy-operator + namespace: flux-system + version: 0.10.1 + interval: 60m + values: + trivy: + ignoreUnfixed: true + install: + crds: CreateReplace + createNamespace: true +``` + +You can then apply the file to your Kubernetes cluster: +``` +kubectl apply -f trivy-operator.yaml +``` + +## After the installation + +After the install, you want to check that the Trivy operator is running in the trivy-system namespace: +``` +kubectl get deployment -n trivy-system +``` + diff --git a/docs/tutorials/kubernetes/kyverno.md b/docs/tutorials/kubernetes/kyverno.md new file mode 100644 index 000000000000..f2e9d12006a7 --- /dev/null +++ b/docs/tutorials/kubernetes/kyverno.md @@ -0,0 +1,99 @@ +# Attesting Image Scans With Kyverno + +This tutorial is based on the following blog post by Chip Zoller: [Attesting Image Scans With Kyverno](https://neonmirrors.net/post/2022-07/attesting-image-scans-kyverno/) + +This tutorial details + +- Verify the container image has an attestation with Kyverno + +### Prerequisites +1. A running Kubernetes cluster that kubectl is connected to +2. A Container image signed with Cosign and an attestation generated for a Trivy Vulnerability scan. + [Follow this tutorial for more information.][vuln-attestation] + +### Kyverno Policy to check attestation + +The following policy ensures that the attestation is no older than 168h: + +vuln-attestation.yaml + +{% raw %} + +```bash +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: check-vulnerabilities +spec: + validationFailureAction: Enforce + background: false + webhookTimeoutSeconds: 30 + failurePolicy: Fail + rules: + - name: checking-vulnerability-scan-not-older-than-one-hour + match: + any: + - resources: + kinds: + - Pod + verifyImages: + - imageReferences: + - "*" + attestations: + - type: https://cosign.sigstore.dev/attestation/vuln/v1 + conditions: + - all: + - key: "{{ time_since('','{{ metadata.scanFinishedOn }}', '') }}" + operator: LessThanOrEquals + value: "1h" + attestors: + - count: 1 + entries: + - keys: + publicKeys: |- + -----BEGIN PUBLIC KEY----- + abc + xyz + -----END PUBLIC KEY----- +``` + +{% endraw %} + +### Apply the policy to your Kubernetes cluster + +Ensure that you have Kyverno already deployed and running on your cluster -- for instance through he Kyverno Helm Chart. + +Next, apply the above policy: +``` +kubectl apply -f vuln-attestation.yaml +``` + +To ensure that the policy worked, we can deploy an example Kubernetes Pod with our container image: + +``` +kubectl run app-signed --image= docker.io/anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd  +``` +Note that the image is based on the [signing tutorial.][vuln-attestation] + +Once we apply the deployment, it should pass since our attestation is available: +``` +kubectl apply -f deployment.yaml -n app +deployment.apps/cns-website created +``` + +However, if we try to deploy any other container image, our deployment will fail. We can verify this by replacing the image referenced in the deployment with `docker.io/anaisurlichs/cns-website:0.0.5` and applying the deployment: +``` +kubectl run app-unsigned --image=docker.io/anaisurlichs/cns-website:0.1.1  + +Resource: "apps/v1, Resource=deployments", GroupVersionKind: "apps/v1, Kind=Deployment" +Name: "cns-website", Namespace: "app" +for: "deployment-two.yaml": admission webhook "mutate.kyverno.svc-fail" denied the request: + +resource Deployment/app/cns-website was blocked due to the following policies + +check-image: + autogen-check-image: | + failed to verify signature for docker.io/anaisurlichs/cns-website:0.0.5: .attestors[0].entries[0].keys: no matching signatures: +``` + +[vuln-attestation]: ../signing/vuln-attestation.md \ No newline at end of file diff --git a/docs/tutorials/misconfiguration/custom-checks.md b/docs/tutorials/misconfiguration/custom-checks.md new file mode 100644 index 000000000000..ecf8f5af1b5a --- /dev/null +++ b/docs/tutorials/misconfiguration/custom-checks.md @@ -0,0 +1,111 @@ +# Custom Checks with Rego + +Trivy can scan configuration files for common security issues (a.k.a IaC misconfiguration scanning). In addition to a comprehensive built in database of checks, you can add your own custom checks. Checks are written in [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) language and the full documentation for checks and customizing them is available [here](https://aquasecurity.github.io/trivy/latest/docs/scanner/misconfiguration/custom/). + +This tutorial will walk you through writing a custom check in Rego that checks for an issue in a Dockerfile. + +When you are writing a check, it's important to understand the input to the check. This will be the IaC file that you are scanning; for example, a Kubernetes YAML resource definition, or an AWS JSON CloudFormation, or in our case a Dockerfile. + +Since Rego is primarily tailored to query JSON objects, all incoming configuration files needs to be first converted to structured objects, which is available to the Rego code as the input variable. This is nothing that users have to do manually in Trivy. Instead, Rego makes it possible to pass in custom Schemas that detail how files are converted. Once Rego has access to a custom Schema, it will know in which format to access configuration files such as a Dockerfile. + +[Here you can find the schemas](https://github.com/aquasecurity/defsec/tree/master/pkg/rego/schemas) that define how different configuration files are converted to JSON by Trivy. +This tutorial will make use of the [dockerfile.json schema](https://github.com/aquasecurity/defsec/tree/master/pkg/rego/schemas). The schema will need to be parsed into your custom check. + +Users can also use the [Schema Explorer](https://aquasecurity.github.io/trivy-schemas/) to view the structure of the data provided to Rego. + +## Create a Rego file and Specify Trivy metadata + +First, create a new `.rego` file e.g. a `docker-check.rego` file: +``` +touch docker-check.rego +``` + +Next, we need to specify metadata about the check. This is information that helps Trivy load and process the check. + +``` +# METADATA +# title: Verify Image +# description: Verify Image is allowed to be used and in the right format +# schemas: +# - input: schema["dockerfile"] +# custom: +# id: ID001 +# severity: MEDIUM +# input: +# selector: +# - type: dockerfile +``` + +Important: The `METADATA` has to be defined on top of the file. + +More information on the different fields in the metadata can be found in the [Trivy documentation.](https://aquasecurity.github.io/trivy/latest/docs/scanner/misconfiguration/custom/) + +## Package and imports + +``` +package custom.dockerfile.ID001 + +import future.keywords.in +``` + +Every rego check has a package name. In our case, we will call it `custom.dockerfile.ID001` to avoid confusion between custom checks and built-in checks. The group name `dockerfile` has no effect on the package name. Note that each package has to contain only one check. However, we can pass multiple checks into our Trivy scan. +The first keyword of the package, in this case `custom`, will be reused in the `trivy` command as the `--namespace`. + +## Allowed data + +The check that we are setting up compares the container images used in the Dockerfile with a list of white-listed container images. Thus, we need to add the images that are allowed to be used in the Dockerfile to our check. In our case, we will store them in an array of arrays: + +``` +allowed_images := { + ["node:21-alpine3.19", "as", "build-deps"], + ["nginx:1.2"] +} +``` + +## Select the images that are used in the Dockerfile + +Next, we need to iterate over the different commands in our Dockerfile and identify the commands that provide the base container images: + +``` +deny[msg] { + input.Stages[m].Commands[l].Cmd == "from" + val := input.Stages[m].Commands[l].Value + not val in allowed_images + msg := sprintf("The container image '%s' used in the Dockerfile is not allowed", val) +} +``` + +Let's look at the check line by line: + +1. The rule should always be `deny` in the Trivy Rego checks +2. `input.Stages[m].Commands[l].Cmd` `input` allows us to access the different commands in the Dockerfile. We need to access the commands that use "FROM". Every command will be converted to lowercase. +3. `val := input.Stages[m].Commands[l].Value` accesses the value of the `FROM` command and stores it in `val` +4. `not val in allowed_images` checks whether val is not part of our allowed images list; this part of the check relies on the import statement +5. In case our check fails, the `msg` will be printed with the image name used in `val` + +Note that Rego + +* uses `AND` automatically to combine conditions in this check +* automatically iterates through the array of commands in the Dockefile and allowed images + +## Run the check in a Trivy misconfiguration scan + +Ensure that you have Trivy installed and run the following command: + +```bash +trivy fs --scanners misconf --policy ./docker-check.rego --namespaces custom ./Dockerfile +``` + +Please replace: + +* `./docker-check.rego` with the file path to your check +* `custom` should be replaced with your package name if different +* `./Dockerfile` is the path to the Dockerfile that should be scanned + +**Note**: If you define custom packages, you have to specify the package prefix via `--namespaces` option. In our case, we called the custom package `custom`. + +## Resources + +* [Rego provides a long list of courses](https://academy.styra.com/collections) that can be useful in writing more complex checks +* [The Rego documentation provides detailed information on the different types, iterations etc.](https://www.openpolicyagent.org/docs/latest/) +* Have a look at the [built-in checks](https://github.com/aquasecurity/trivy-policies/tree/main/checks) for Trivy for inspiration on how to write custom checks. \ No newline at end of file diff --git a/docs/tutorials/misconfiguration/terraform.md b/docs/tutorials/misconfiguration/terraform.md new file mode 100644 index 000000000000..8240e1ba53b2 --- /dev/null +++ b/docs/tutorials/misconfiguration/terraform.md @@ -0,0 +1,114 @@ +# Scanning Terraform files with Trivy + +This tutorial is focused on ways Trivy can scan Terraform IaC configuration files. + +A video tutorial on Terraform Misconfiguration scans can be found on the [Aqua Open Source YouTube account.](https://youtu.be/BWp5JLXkbBc) + +**A note to tfsec users** +We have been consolidating all of our scanning-related efforts in one place, and that is Trivy. You can read more on the decision in the [tfsec discussions.](https://github.com/aquasecurity/tfsec/discussions/1994) + +## Trivy Config Command + +Terraform configuration scanning is available as part of the `trivy config` command. This command scans all configuration files for misconfiguration issues. You can find the details within [misconfiguration scans in the Trivy documentation.](https://aquasecurity.github.io/trivy/latest/docs/scanner/misconfiguration/) + +Command structure: +``` +trivy config +``` + +The `trivy config` command can scan Terraform configuration, CloudFormation, Dockerfile, Kubernetes manifests, and Helm Charts for misconfiguration. Trivy will compare the configuration found in the file with a set of best practices. + +- If the configuration is following best practices, the check will pass, +- If the configuration does not define the resource of some configuration, Trivy will assume the default configuration for the resource creation is used. In this case, the check might fail. +- If the configuration that has been defined does not follow best practices, the check will fail. + +### Prerequisites +Install Trivy on your local machines. The documentation provides several [different installation options.](https://aquasecurity.github.io/trivy/latest/getting-started/installation/) +This tutorial will use this example [Terraform tutorial](https://github.com/Cloud-Native-Security/trivy-demo/tree/main/bad_iac/terraform) for terraform misconfiguration scanning with Trivy. + +Git clone the tutorial and cd into the directory: +``` +git clone git@github.com:Cloud-Native-Security/trivy-demo.git +cd bad_iac/terraform +``` +In this case, the folder only containes Terraform configuration files. However, you could scan a directory that contains several different configurations e.g. Kubernetes YAML manifests, Dockerfile, and Terraform. Trivy will then detect the different configuration files and apply the right rules automatically. + +## Different types of `trivy config` scans + +Below are several examples of how the trivy config scan can be used. + +General Terraform scan with trivy: +``` +trivy config +``` + +So if we are already in the directory that we want to scan: +``` +trivy config ./ +``` +### Specify the scan format +The `--format` flag changes the way that Trivy displays the scan result: + +JSON: +``` +trivy config -f json terraform-infra +``` + +Sarif: +``` +trivy config -f sarif terraform-infra +``` + +### Specifying the output location + +The `--output` flag specifies the file location in which the scan result should be saved: + +JSON: +``` +trivy config -f json -o example.json terraform-infra +``` + +Sarif: +``` +trivy config -f sarif -o example.sarif terraform-infra +``` + +### Filtering by severity + +If you are presented with lots and lots of misconfiguration across different files, you might want to filter or the misconfiguration with the highest severity: + +``` +trivy config --severity CRITICAL, MEDIUM terraform-infra +``` + +### Passing tf.tfvars files into `trivy config` scans + +You can pass terraform values to Trivy to override default values found in the Terraform HCL code. More information are provided [in the documentation.](https://aquasecurity.github.io/trivy/latest/docs/coverage/iac/terraform/#value-overrides) + +``` +trivy conf --tf-vars terraform.tfvars ./ +``` +### Custom Checks + +We have lots of examples in the [documentation](https://aquasecurity.github.io/trivy/latest/docs/scanner/misconfiguration/custom/) on how you can write and pass custom Rego policies into terraform misconfiguration scans. + +## Secret and vulnerability scans + +The `trivy config` command does not perform secrete and vulnerability checks out of the box. However, you can specify as part of your `trivy fs` scan that you would like to scan you terraform files for exposed secrets and misconfiguraction through the following flags: + +``` +trivy fs --scanners secret,misconfig ./ +``` + +The `trivy config` command is a sub-command of the `trivy fs` command. You can learn more about this command in the [documentation.](https://aquasecurity.github.io/trivy/latest/docs/target/filesystem/) + +## Scanning Terraform Plan files + +Instead of scanning your different Terraform resources individually, you could also scan your Terraform Plan file before it is deployed for misconfiguration. This will give you insights into any misconfiguration of your resources as they would become deployed. [Here](https://aquasecurity.github.io/trivy/latest/docs/coverage/iac/terraform/#terraform) is the link to the documentation. + +Note that you need to be able to create a terraform init and plan without any errors. + +## Using Trivy in your CI/CD pipeline +Similar to tfsec, Trivy can be used either on local developer machines or integrated into your CI/CD pipeline. There are several steps available for different pipelines, including GitHub Actions, Circle CI, GitLab, Travis and more in the tutorials section of the documentation: [https://aquasecurity.github.io/trivy/latest/tutorials/integrations/](https://aquasecurity.github.io/trivy/latest/tutorials/integrations/) + + \ No newline at end of file diff --git a/docs/tutorials/overview.md b/docs/tutorials/overview.md new file mode 100644 index 000000000000..4ee08539778c --- /dev/null +++ b/docs/tutorials/overview.md @@ -0,0 +1,18 @@ +# Tutorials + +In this section you can find step-by-step guides that help you accomplish specific tasks. + +👈 Please use the side-navigation on the left in order to browse the different topics. + +## Adding tutorials + +You are welcome to create tutorials and showcase them here. Tutorials can be either included in here as full articles, or included as external links under [external community resources][community-resources]. +Before sending PR, please first create an issue (of kind "Documentation") and describe the suggestion, if it's external link or article, and what category it's under. + +Guidelines: + +- Focus on a specific use case. Start by clearly describing the use case and when/who it is relevant for. +- Provide an end-to-end set of instructions. Make sure anyone can easily follow. +- Describe the expected outcome after each step. Include examples as much as possible. + +[community-resources]: additional-resources/community.md \ No newline at end of file diff --git a/docs/tutorials/shell/shell-completion.md b/docs/tutorials/shell/shell-completion.md new file mode 100644 index 000000000000..1f9f3ecb154d --- /dev/null +++ b/docs/tutorials/shell/shell-completion.md @@ -0,0 +1,66 @@ +# Enable shell completion + +Below is example steps to enable shell completion feature for `trivy` cli: + +### 1. Know your current shell + +```bash +$ echo $SHELL +/bin/zsh # For this example it is zsh, but will be vary depend on your $SHELL, maybe /bin/bash or /bin/fish +``` + +### 2. Run `completion` command to get sub-commands + +``` bash +$ trivy completion zsh -h +Generate the autocompletion script for the zsh shell. + +If shell completion is not already enabled in your environment you will need +to enable it. You can execute the following once: + + echo "autoload -U compinit; compinit" >> ~/.zshrc + +To load completions in your current shell session: + + source <(trivy completion zsh); compdef _trivy trivy + +To load completions for every new session, execute once: + +#### Linux: + + trivy completion zsh > "${fpath[1]}/_trivy" + +#### macOS: + + trivy completion zsh > $(brew --prefix)/share/zsh/site-functions/_trivy + +You will need to start a new shell for this setup to take effect. +``` + +### 3. Run the sub-commands following the instruction + +```bash +echo "autoload -U compinit; compinit" >> ~/.zshrc +source <(trivy completion zsh); compdef _trivy trivy +trivy completion zsh > "${fpath[1]}/_trivy" +``` + +### 4. Start a new shell and you can see the shell completion + +```bash +$ trivy [tab] +aws -- scan aws account +completion -- Generate the autocompletion script for the specified shell +config -- Scan config files for misconfigurations +filesystem -- Scan local filesystem +help -- Help about any command +image -- Scan a container image +kubernetes -- scan kubernetes cluster +module -- Manage modules +plugin -- Manage plugins +repository -- Scan a repository +rootfs -- Scan rootfs +sbom -- Scan SBOM for vulnerabilities +server -- Server mode +version -- Print the version +``` \ No newline at end of file diff --git a/docs/tutorials/signing/vuln-attestation.md b/docs/tutorials/signing/vuln-attestation.md new file mode 100644 index 000000000000..2e4b487306c6 --- /dev/null +++ b/docs/tutorials/signing/vuln-attestation.md @@ -0,0 +1,145 @@ +# Vulnerability Scan Record Attestation + +This tutorial details how to + +- Scan container images for vulnerabilities +- Generate an attestation, using Cosign, with and without generating a separate key pair + +#### Prerequisites + +1. [Trivy CLI](../../getting-started/installation.md) installed +2. [Cosign CLI](https://docs.sigstore.dev/system_config/installation/) installed +3. Ensure that you have access to a container image in a remote container registry that you own/within your account. In this tutorial, we will use DockerHub. + +## Scan Container Image for vulnerabilities + +Scan your container image for vulnerabilities and save the scan result to a scan.json file: +``` +trivy image --ignore-unfixed --format cosign-vuln --output scan.json DockerHubID/imagename:imagetag +``` + +For example: +``` +trivy image --ignore-unfixed --format cosign-vuln --output scan.json anaisurlichs/signed-example:0.1 +``` + +* `--ignore-unfixed`: Ensures only the vulnerabilities, which have a already a fix available, are displayed +* `--output scan.json`: The scan output is saved to a scan.json file instead of being displayed in the terminal. + +Note: Replace the container image with the container image that you want to scan. + +## Option 1: Signing and Generating an attestation without new key pair + +#### Signing + +Sign the container image: +``` +cosign sign DockerHubID/imagename@imageSHA +``` + +The `imageSHA` can be obtained through the following docker command: +``` +docker image ls --digests +``` +The SHA will be displayed next to the image name and tag. + +Note that it is better practice to sign the image SHA rather than the tag as the SHA will remain the same for the particular image that we have signed. + +For example: +``` +cosign sign docker.io/anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd +``` + +#### Attestation + +The following command generates an attestation for the vulnerability scan and uploads it to the container image used: +``` +cosign attest --predicate scan.json --type vuln docker.io/DockerHubID/imagename:imageSHA +``` + +For example: +``` +cosign attest --predicate scan.json --type vuln docker.io/anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd +``` + +Note: Replace the container image with the container image that you would like to scan. + +Next, Sigstore will ask you to verify with an account -- Microsoft, GitHub, or Google. + +Once done, the user will be provided with a certificate in the terminal where they ran the command. Example certificate: +``` +-----BEGIN CERTIFICATE----- +MIIC1TCCAlygAwIBAgIUfSXI7xTWSLq4nuygd8YPuhPZlEswCgYIKoZIzj0EAwMw +NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl +cm1lZGlhdGUwHhcNMjQwMTExMTMzODUzWhcNMjQwMTExMTM0ODUzWjAAMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAETcUNnK76mfo9G3j1c7NN6Vcn6yQPDX5rd3QB +unkHs1Uk59CWv3qm6sUyRNYaATs9zdHAZqLck8G4P/Pj7+GzCKOCAXswggF3MA4G +........ +-----END CERTIFICATE----- +``` + + +## Option 2: Signing and Generating an attestation with a new Cosign key pair + +To generate an attestation for the container image with a separate key pair, we can use Cosign to generate a new key pair: +``` +cosign generate-key-pair  +``` + +This will generate a `cosign.key` and a `cosign.pub` file. The `cosign.key` file is your private key that should be kept confidential as it is used to sign artefacts. However, the `cosign.pub` file contains the information of the corresponding public key. This key can be used by third parties to verify the attestation -- basically that this person who claims to have signed the attestation actually is the one who signed it. + +#### Signing + +Sign the container image: +``` +cosign sign --key cosign.key docker.io/anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd +``` + +#### Attestation + +To generate the attestation with the specific key pairs, run the following command: +``` +cosign attest --key cosign.key --type vuln --predicate scan.json docker.io/anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd  +``` + +## Verify the attestation + +### Option 1 -- No separate key pair + +If you have not generated a key pair but received a certificate after the container image was signed, use the following command to verify the attestation: + +``` +cosign verify-attestation --type vuln --certificate-identity Email-used-to-sign --certificate-oidc-issuer='the-issuer-used' docker.io/DockerHubID/imagename:imageSHA +``` + +For example, the command could be like this: +``` +cosign verify-attestation --type vuln --certificate-identity urlichsanais@gmail.com --certificate-oidc-issuer='https://github.com/login/oauth' anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd +``` + +### Option 2 -- Separate key pair + +If you have used a new cosign key pair, the attestation can be verified through the following command: +``` +cosign verify-attestation --key cosign.pub --type vuln anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd  +``` + +
+Output + +The output should look similar to the following: +``` +Verification for anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd -- +The following checks were performed on each of these signatures: + - The cosign claims were validated + - Existence of the claims in the transparency log was verified offline + - The signatures were verified against the specified public key +{"payloadType":"application/vnd.in-toto+json","payload": +``` +
+ +## More information + +See [here][vuln-attestation] for more details. + +[vuln-attestation]: ../../docs/supply-chain/attestation/vuln.md \ No newline at end of file diff --git a/examples/module/spring4shell/README.md b/examples/module/spring4shell/README.md new file mode 100644 index 000000000000..77b28697fce7 --- /dev/null +++ b/examples/module/spring4shell/README.md @@ -0,0 +1,40 @@ +# Spring4Shell module + +This module provides a more in-depth investigation of Spring4Shell detection. + +## Set up + +``` +$ tinygo build -o spring4shell.wasm -scheduler=none -target=wasi --no-debug spring4shell.go +$ mkdir -p ~/.trivy/modules +$ cp spring4shell.wasm ~/.trivy/modules +``` + +It is also available in [GHCR][trivy-module-spring4shell]. +You can install it via `trivy module install`. + +```bash +$ trivy module install ghcr.io/aquasecurity/trivy-module-spring4shell +2022-06-13T15:32:21.972+0300 INFO Installing the module from ghcr.io/aquasecurity/trivy-module-spring4shell... +``` + +## Run Trivy + +``` +$ trivy image spring-core-rce-jdk8:latest +2022-05-29T22:35:04.873+0300 INFO Loading spring4shell.wasm... +2022-05-29T22:35:05.348+0300 INFO Registering WASM module: spring4shell@v1 +2022-05-29T22:35:07.124+0300 INFO Module spring4shell: analyzing /app/tomcat/RELEASE-NOTES... +2022-05-29T22:35:07.139+0300 INFO Module spring4shell: analyzing /app/jdk9/release... +2022-05-29T22:37:04.636+0300 INFO Module spring4shell: analyzing /app/jdk9/release... +... +2022-05-29T22:37:08.917+0300 INFO Module spring4shell: Java Version: 8, Tomcat Version: 8.5.77 +2022-05-29T22:37:08.917+0300 INFO Module spring4shell: change CVE-2022-22965 severity from CRITICAL to LOW +``` + +In the above example, the Java version is 8 which is not affected by CVE-2022-22965, so this module changes the severity from CRITICAL to LOW. + +## Note +This module is also used for testing in Trivy. + +[trivy-module-spring4shell]: https://github.com/orgs/aquasecurity/packages/container/package/trivy-module-spring4shell \ No newline at end of file diff --git a/examples/module/spring4shell/spring4shell.go b/examples/module/spring4shell/spring4shell.go new file mode 100644 index 000000000000..8d7d18ab65dd --- /dev/null +++ b/examples/module/spring4shell/spring4shell.go @@ -0,0 +1,285 @@ +//go:generate tinygo build -o spring4shell.wasm -scheduler=none -target=wasi --no-debug spring4shell.go +//go:build tinygo.wasm + +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "regexp" + "strconv" + "strings" + + "github.com/aquasecurity/trivy/pkg/module/api" + "github.com/aquasecurity/trivy/pkg/module/serialize" + "github.com/aquasecurity/trivy/pkg/module/wasm" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + ModuleVersion = 1 + ModuleName = "spring4shell" + TypeJavaMajor = ModuleName + "/java-major-version" + TypeTomcatVersion = ModuleName + "/tomcat-version" +) + +var ( + tomcatVersionRegex = regexp.MustCompile(`Apache Tomcat Version ([\d.]+)`) +) + +// main is required for TinyGo to compile to Wasm. +func main() { + wasm.RegisterModule(Spring4Shell{}) +} + +type Spring4Shell struct { + // Cannot define fields as modules can't keep state. +} + +func (Spring4Shell) Version() int { + return ModuleVersion +} + +func (Spring4Shell) Name() string { + return ModuleName +} + +func (Spring4Shell) RequiredFiles() []string { + return []string{ + `\/openjdk-\d+\/release`, // For OpenJDK version + `\/jdk\d+\/release`, // For JDK version + `tomcat\/RELEASE-NOTES`, // For Tomcat version + } +} + +func (s Spring4Shell) Analyze(filePath string) (*serialize.AnalysisResult, error) { + wasm.Info(fmt.Sprintf("analyzing %s...", filePath)) + f, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer f.Close() + + switch { + case strings.HasSuffix(filePath, "/release"): + return s.parseJavaRelease(f, filePath) + case strings.HasSuffix(filePath, "/RELEASE-NOTES"): + return s.parseTomcatReleaseNotes(f, filePath) + } + + return nil, nil +} + +// Parse a jdk release file like "/usr/local/openjdk-11/release" +func (Spring4Shell) parseJavaRelease(f *os.File, filePath string) (*serialize.AnalysisResult, error) { + var javaVersion string + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + if !strings.HasPrefix(line, "JAVA_VERSION=") { + continue + } + + ss := strings.Split(line, "=") + if len(ss) != 2 { + return nil, fmt.Errorf("invalid java version: %s", line) + } + + javaVersion = strings.Trim(ss[1], `"`) + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return &serialize.AnalysisResult{ + CustomResources: []serialize.CustomResource{ + { + Type: TypeJavaMajor, + FilePath: filePath, + Data: javaVersion, + }, + }, + }, nil +} + +func (Spring4Shell) parseTomcatReleaseNotes(f *os.File, filePath string) (*serialize.AnalysisResult, error) { + b, err := io.ReadAll(f) + if err != nil { + return nil, err + } + + m := tomcatVersionRegex.FindStringSubmatch(string(b)) + if len(m) != 2 { + return nil, fmt.Errorf("unknown tomcat release notes format") + } + + return &serialize.AnalysisResult{ + CustomResources: []serialize.CustomResource{ + { + Type: TypeTomcatVersion, + FilePath: filePath, + Data: m[1], + }, + }, + }, nil +} + +func (Spring4Shell) PostScanSpec() serialize.PostScanSpec { + return serialize.PostScanSpec{ + Action: api.ActionUpdate, // Update severity + IDs: []string{"CVE-2022-22965"}, + } +} + +// PostScan takes results including custom resources and detected CVE-2022-22965. +// +// Example input: +// [ +// +// { +// "Target": "", +// "Class": "custom", +// "CustomResources": [ +// { +// "Type": "spring4shell/java-major-version", +// "FilePath": "/usr/local/openjdk-8/release", +// "Layer": { +// "Digest": "sha256:d7b564a873af313eb2dbcb1ed0d393c57543e3666bdedcbe5d75841d72b1f791", +// "DiffID": "sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1" +// }, +// "Data": "1.8.0_322" +// }, +// { +// "Type": "spring4shell/tomcat-version", +// "FilePath": "/usr/local/tomcat/RELEASE-NOTES", +// "Layer": { +// "Digest": "sha256:59c0978ccb117247fd40d936973c40df89195f60466118c5acc6a55f8ba29f06", +// "DiffID": "sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6" +// }, +// "Data": "8.5.77" +// } +// ] +// }, +// { +// "Target": "Java", +// "Class": "lang-pkgs", +// "Type": "jar", +// "Vulnerabilities": [ +// { +// "VulnerabilityID": "CVE-2022-22965", +// "PkgName": "org.springframework.boot:spring-boot", +// "PkgPath": "usr/local/tomcat/webapps/helloworld.war", +// "InstalledVersion": "2.6.3", +// "FixedVersion": "2.5.12, 2.6.6", +// "Layer": { +// "Digest": "sha256:cc44af318e91e6f9f9bf73793fa4f0639487613f46aa1f819b02b6e8fb5c6c07", +// "DiffID": "sha256:eb769943b91f10a0418f2fc3b4a4fde6c6293be60c37293fcc0fa319edaf27a5" +// }, +// "SeveritySource": "nvd", +// "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-22965", +// "DataSource": { +// "ID": "glad", +// "Name": "GitLab Advisory Database Community", +// "URL": "https://gitlab.com/gitlab-org/advisories-community" +// }, +// "Title": "spring-framework: RCE via Data Binding on JDK 9+", +// "Description": "A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.", +// "Severity": "CRITICAL", +// "CweIDs": [ +// "CWE-94" +// ], +// "VendorSeverity": { +// "ghsa": 4, +// "nvd": 4, +// "redhat": 3 +// }, +// "CVSS": { +// "ghsa": { +// "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", +// "V3Score": 9.8 +// }, +// "nvd": { +// "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", +// "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", +// "V2Score": 7.5, +// "V3Score": 9.8 +// }, +// "redhat": { +// "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", +// "V3Score": 8.1 +// } +// }, +// "References": [ +// "https://github.com/advisories/GHSA-36p3-wjmg-h94x" +// ], +// "PublishedDate": "2022-04-01T23:15:00Z", +// "LastModifiedDate": "2022-05-19T14:21:00Z" +// } +// ] +// } +// +// ] +func (Spring4Shell) PostScan(results serialize.Results) (serialize.Results, error) { + var javaMajorVersion int + var tomcatVersion string + for _, result := range results { + if result.Class != types.ClassCustom { + continue + } + + for _, c := range result.CustomResources { + if c.Type == TypeJavaMajor { + v := c.Data.(string) + ss := strings.Split(v, ".") + if len(ss) == 0 || len(ss) < 2 { + wasm.Warn("Invalid Java version: " + v) + continue + } + + ver := ss[0] + if ver == "1" { + ver = ss[1] + } + + var err error + javaMajorVersion, err = strconv.Atoi(ver) + if err != nil { + wasm.Warn("Invalid Java version: " + v) + continue + } + } else if c.Type == TypeTomcatVersion { + tomcatVersion = c.Data.(string) + } + } + } + + wasm.Info(fmt.Sprintf("Java Version: %d, Tomcat Version: %s", javaMajorVersion, tomcatVersion)) + + vulnerable := true + // TODO: version comparison + if tomcatVersion == "10.0.20" || tomcatVersion == "9.0.62" || tomcatVersion == "8.5.78" { + vulnerable = false + } else if javaMajorVersion <= 8 { + vulnerable = false + } + + for i, result := range results { + for j, vuln := range result.Vulnerabilities { + // Look up Spring4Shell + if vuln.VulnerabilityID != "CVE-2022-22965" { + continue + } + + // If it doesn't satisfy any of requirements, the severity should be changed to LOW. + if !strings.Contains(vuln.PkgPath, ".war") || !vulnerable { + wasm.Info(fmt.Sprintf("change %s CVE-2022-22965 severity from CRITICAL to LOW", vuln.PkgName)) + results[i].Vulnerabilities[j].Severity = "LOW" + } + } + } + + return results, nil +} diff --git a/examples/trivy-conf/trivy.yaml b/examples/trivy-conf/trivy.yaml new file mode 100644 index 000000000000..6b6fa490af23 --- /dev/null +++ b/examples/trivy-conf/trivy.yaml @@ -0,0 +1,24 @@ +timeout: 10m +format: json +dependency-tree: true +list-all-pkgs: true +exit-code: 1 +output: result.json +severity: + - HIGH + - CRITICAL +scan: + skip-dirs: + - /lib64 + - /lib + - /usr/lib + - /usr/include + + scanners: + - vuln + - secret +vulnerability: + type: + - os + - library + ignore-unfixed: true \ No newline at end of file diff --git a/go.mod b/go.mod index a8ffbf7d54bf..5a7a6d8399ae 100644 --- a/go.mod +++ b/go.mod @@ -1,41 +1,457 @@ -module github.com/knqyf263/trivy +module github.com/aquasecurity/trivy -go 1.12 +go 1.25.0 require ( - github.com/briandowns/spinner v0.0.0-20190319032542-ac46072a5a91 - github.com/emirpasic/gods v1.12.0 // indirect - github.com/etcd-io/bbolt v1.3.2 - github.com/fatih/color v1.7.0 - github.com/gliderlabs/ssh v0.1.3 // indirect - github.com/golang/protobuf v1.3.1 // indirect - github.com/knqyf263/fanal v0.0.0-20190507123206-ceab60083e70 - github.com/knqyf263/go-deb-version v0.0.0-20170509080151-9865fe14d09b - github.com/knqyf263/go-dep-parser v0.0.0-20190429154931-c377a5391790 - github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 - github.com/knqyf263/go-version v1.1.1 - github.com/mattn/go-colorable v0.1.1 // indirect - github.com/mattn/go-runewidth v0.0.4 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/olekukonko/tablewriter v0.0.1 - github.com/stretchr/testify v1.3.0 // indirect - github.com/urfave/cli v1.20.0 - github.com/xanzy/ssh-agent v0.2.1 // indirect - go.etcd.io/bbolt v1.3.2 // indirect - go.uber.org/atomic v1.3.2 // indirect - go.uber.org/multierr v1.1.0 // indirect - go.uber.org/zap v1.9.1 - golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 - golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 // indirect - golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 // indirect - golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 - gopkg.in/cheggaaa/pb.v1 v1.0.28 - gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect - gopkg.in/src-d/go-git-fixtures.v3 v3.4.0 // indirect - gopkg.in/src-d/go-git.v4 v4.10.0 - gopkg.in/yaml.v2 v2.2.2 + github.com/Azure/azure-sdk-for-go v68.0.0+incompatible + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 + github.com/BurntSushi/toml v1.5.0 + github.com/CycloneDX/cyclonedx-go v0.9.2 + github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible + github.com/Masterminds/sprig/v3 v3.3.0 + github.com/NYTimes/gziphandler v1.1.1 + github.com/alicebob/miniredis/v2 v2.35.0 + github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 + github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce + github.com/aquasecurity/go-npm-version v0.0.2 + github.com/aquasecurity/go-pep440-version v0.0.1 + github.com/aquasecurity/go-version v0.0.1 + github.com/aquasecurity/loading v0.0.5 + github.com/aquasecurity/table v1.11.0 + github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da + github.com/aquasecurity/tml v0.6.1 + github.com/aquasecurity/trivy-aws v0.15.1 + github.com/aquasecurity/trivy-db v0.0.0-20240910133327-7e0f4d2ed4c1 + github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 + github.com/aquasecurity/trivy-kubernetes v0.9.0 + github.com/aquasecurity/trivy-policies v0.10.0 + github.com/aws/aws-sdk-go-v2 v1.41.1 + github.com/aws/aws-sdk-go-v2/config v1.32.5 + github.com/aws/aws-sdk-go-v2/credentials v1.19.5 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.227.0 + github.com/aws/aws-sdk-go-v2/service/ecr v1.55.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.88.3 + github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 + github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c + github.com/bmatcuk/doublestar/v4 v4.8.1 + github.com/cenkalti/backoff v2.2.1+incompatible + github.com/cheggaaa/pb/v3 v3.1.7 + github.com/containerd/containerd v1.7.29 + github.com/csaf-poc/csaf_distribution/v3 v3.0.0 + github.com/docker/docker v28.5.2+incompatible + github.com/docker/go-connections v0.6.0 + github.com/fatih/color v1.18.0 + github.com/go-git/go-git/v5 v5.16.5 + github.com/go-openapi/runtime v0.29.2 + github.com/go-openapi/strfmt v0.25.0 + github.com/go-redis/redis/v8 v8.11.5 + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang/protobuf v1.5.4 + github.com/google/go-containerregistry v0.20.7 + github.com/google/licenseclassifier/v2 v2.0.0 + github.com/google/uuid v1.6.0 + github.com/google/wire v0.6.0 + github.com/hashicorp/go-getter v1.7.9 + github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/go-retryablehttp v0.7.8 + github.com/hashicorp/golang-lru/v2 v2.0.7 + github.com/in-toto/in-toto-golang v0.9.0 + github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f + github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 + github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075 + github.com/knqyf263/go-rpmdb v0.1.1 + github.com/knqyf263/nested v0.0.1 + github.com/kylelemons/godebug v1.1.0 + github.com/liamg/jfather v0.0.9 + github.com/magefile/mage v1.15.0 + github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee + github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 + github.com/masahiro331/go-ext4-filesystem v0.0.0-20240620024024-ca14e6327bbd + github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 + github.com/masahiro331/go-vmdk-parser v0.0.0-20221225061455-612096e4bbbd + github.com/masahiro331/go-xfs-filesystem v0.0.0-20231205045356-1b22259a6c44 + github.com/mattn/go-shellwords v1.0.12 + github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 + github.com/mitchellh/hashstructure/v2 v2.0.2 + github.com/mitchellh/mapstructure v1.5.0 + github.com/moby/buildkit v0.23.1 + github.com/open-policy-agent/opa v1.6.0 + github.com/opencontainers/go-digest v1.0.0 + github.com/opencontainers/image-spec v1.1.1 + github.com/openvex/go-vex v0.2.7 + github.com/owenrumney/go-sarif/v2 v2.3.3 + github.com/package-url/packageurl-go v0.1.3 + github.com/quasilyte/go-ruleguard/dsl v0.3.22 + github.com/samber/lo v1.51.0 + github.com/saracen/walker v0.1.4 + github.com/secure-systems-lab/go-securesystemslib v0.10.0 + github.com/sigstore/rekor v1.5.0 + github.com/sirupsen/logrus v1.9.3 + github.com/sosedoff/gitkit v0.4.0 + github.com/spdx/tools-golang v0.5.5 // v0.5.3 with necessary changes. Can be upgraded to version 0.5.4 after release. + github.com/spf13/cast v1.10.0 + github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 + github.com/spf13/viper v1.21.0 + github.com/stretchr/testify v1.11.1 + github.com/testcontainers/testcontainers-go v0.40.0 + github.com/testcontainers/testcontainers-go/modules/localstack v0.37.0 + github.com/tetratelabs/wazero v1.9.0 + github.com/twitchtv/twirp v8.1.3+incompatible + github.com/xeipuuv/gojsonschema v1.2.0 + github.com/xlab/treeprint v1.2.0 + go.etcd.io/bbolt v1.4.2 + go.uber.org/zap v1.27.1 + golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 + golang.org/x/mod v0.31.0 + golang.org/x/net v0.49.0 + golang.org/x/sync v0.19.0 + golang.org/x/term v0.39.0 + golang.org/x/text v0.33.0 + golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 + google.golang.org/protobuf v1.36.11 + gopkg.in/yaml.v3 v3.0.1 + k8s.io/api v0.34.0 + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 + modernc.org/sqlite v1.38.0 ) -replace github.com/genuinetools/reg => github.com/tomoyamachi/reg v0.16.2-0.20190418055600-c6010b917a55 +require ( + github.com/alecthomas/chroma v0.10.0 + github.com/antchfx/htmlquery v1.3.4 + github.com/apparentlymart/go-cidr v1.1.0 + github.com/aws/smithy-go v1.24.0 + github.com/hashicorp/go-uuid v1.0.3 + github.com/hashicorp/go-version v1.7.0 + github.com/hashicorp/hc-install v0.9.2 + github.com/hashicorp/hcl/v2 v2.24.0 + github.com/hashicorp/terraform-exec v0.23.0 + github.com/liamg/iamgo v0.0.9 + github.com/liamg/memoryfs v1.6.0 + github.com/mitchellh/go-homedir v1.1.0 + github.com/owenrumney/squealer v1.2.11 + github.com/zclconf/go-cty v1.16.3 + github.com/zclconf/go-cty-yaml v1.1.0 + golang.org/x/crypto v0.47.0 + helm.sh/helm/v3 v3.19.0 +) -replace github.com/olekukonko/tablewriter => github.com/knqyf263/tablewriter v0.0.2 +require ( + cel.dev/expr v0.25.1 // indirect + cloud.google.com/go v0.121.6 // indirect + cloud.google.com/go/auth v0.18.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + cloud.google.com/go/iam v1.5.3 // indirect + cloud.google.com/go/monitoring v1.24.3 // indirect + cloud.google.com/go/storage v1.56.0 // indirect + cyphar.com/go-pathrs v0.2.1 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect + github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.29 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect + github.com/Intevation/gval v1.3.0 // indirect + github.com/Intevation/jsonpath v0.2.1 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Microsoft/hcsshim v0.13.0 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/agnivade/levenshtein v1.2.1 // indirect + github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect + github.com/antchfx/xpath v1.3.3 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go v1.55.7 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 // indirect + github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.36.2 // indirect + github.com/aws/aws-sdk-go-v2/service/apigateway v1.28.1 // indirect + github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.24.7 // indirect + github.com/aws/aws-sdk-go-v2/service/athena v1.48.5 // indirect + github.com/aws/aws-sdk-go-v2/service/cloudfront v1.43.1 // indirect + github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.46.2 // indirect + github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.2 // indirect + github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.45.0 // indirect + github.com/aws/aws-sdk-go-v2/service/codebuild v1.49.2 // indirect + github.com/aws/aws-sdk-go-v2/service/docdb v1.39.6 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ecs v1.52.1 // indirect + github.com/aws/aws-sdk-go-v2/service/efs v1.34.1 // indirect + github.com/aws/aws-sdk-go-v2/service/eks v1.53.0 // indirect + github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.1 // indirect + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1 // indirect + github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.32.7 // indirect + github.com/aws/aws-sdk-go-v2/service/emr v1.47.1 // indirect + github.com/aws/aws-sdk-go-v2/service/iam v1.38.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 // indirect + github.com/aws/aws-sdk-go-v2/service/kafka v1.38.7 // indirect + github.com/aws/aws-sdk-go-v2/service/kinesis v1.32.7 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.49.1 // indirect + github.com/aws/aws-sdk-go-v2/service/lambda v1.69.1 // indirect + github.com/aws/aws-sdk-go-v2/service/mq v1.27.8 // indirect + github.com/aws/aws-sdk-go-v2/service/neptune v1.35.6 // indirect + github.com/aws/aws-sdk-go-v2/service/rds v1.92.0 // indirect + github.com/aws/aws-sdk-go-v2/service/redshift v1.52.2 // indirect + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.7 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sns v1.33.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect + github.com/aws/aws-sdk-go-v2/service/workspaces v1.50.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/briandowns/spinner v1.23.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/containerd/cgroups/v3 v3.0.5 // indirect + github.com/containerd/containerd/api v1.9.0 // indirect + github.com/containerd/continuity v0.4.5 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/fifo v1.1.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v1.0.0-rc.1 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect + github.com/containerd/ttrpc v1.2.7 // indirect + github.com/containerd/typeurl/v2 v2.2.3 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect + github.com/cyphar/filepath-securejoin v0.6.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/dlclark/regexp2 v1.11.0 // indirect + github.com/docker/cli v29.2.0+incompatible // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect + github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-ini/ini v1.67.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/analysis v0.24.1 // indirect + github.com/go-openapi/errors v0.22.6 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/loads v0.23.2 // indirect + github.com/go-openapi/spec v0.22.3 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect + github.com/go-openapi/validate v0.25.1 // indirect + github.com/go-test/deep v1.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/goccy/go-yaml v1.9.5 // indirect + github.com/gofrs/uuid v4.3.1+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.9 // indirect + github.com/googleapis/gax-go/v2 v2.16.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/gosuri/uitable v0.0.4 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/terraform-json v0.24.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.18.1 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect + github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect + github.com/moby/sys/mountinfo v0.7.2 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/signal v0.7.1 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/opencontainers/runtime-spec v1.2.1 // indirect + github.com/opencontainers/selinux v1.13.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rubenv/sql-migrate v1.8.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/shirou/gopsutil/v4 v4.25.6 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tchap/go-patricia/v2 v2.3.2 // indirect + github.com/tklauser/go-sysconf v0.3.13 // indirect + github.com/tklauser/numcpus v0.7.0 // indirect + github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect + github.com/ulikunitz/xz v0.5.14 // indirect + github.com/vbatts/tar-split v0.12.2 // indirect + github.com/vektah/gqlparser/v2 v2.5.28 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/yashtewari/glob-intersection v0.2.0 // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.mongodb.org/mongo-driver v1.17.6 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.40.0 // indirect + google.golang.org/api v0.260.0 // indirect + google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/grpc v1.79.3 // indirect + gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + k8s.io/apiextensions-apiserver v0.34.0 // indirect + k8s.io/apimachinery v0.34.0 // indirect + k8s.io/apiserver v0.34.0 // indirect + k8s.io/cli-runtime v0.34.0 // indirect + k8s.io/client-go v0.34.0 // indirect + k8s.io/component-base v0.34.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + k8s.io/kubectl v0.34.0 // indirect + modernc.org/libc v1.65.10 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + oras.land/oras-go/v2 v2.6.0 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/kustomize/api v0.20.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/go.sum b/go.sum index 878c37c47565..00d9e2822bc3 100644 --- a/go.sum +++ b/go.sum @@ -1,319 +1,3349 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= -cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= +cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= +cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= +cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= +cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= +cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= +cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= +cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M= +github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= +github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= +github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8= +github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GoogleCloudPlatform/docker-credential-gcr v1.5.0 h1:wykTgKwhVr2t2qs+xI020s6W5dt614QqCHV+7W9dg64= -github.com/GoogleCloudPlatform/docker-credential-gcr v1.5.0/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs= -github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo= +github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible h1:juIaKLLVhqzP55d8x4cSVgwyQv76Z55/fRv/UBr2KkQ= +github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= +github.com/Intevation/gval v1.3.0 h1:+Ze5sft5MmGbZrHj06NVUbcxCb67l9RaPTLMNr37mjw= +github.com/Intevation/gval v1.3.0/go.mod h1:xmGyGpP5be12EL0P12h+dqiYG8qn2j3PJxIgkoOHO5o= +github.com/Intevation/jsonpath v0.2.1 h1:rINNQJ0Pts5XTFEG+zamtdL7l9uuE1z0FBA+r55Sw+A= +github.com/Intevation/jsonpath v0.2.1/go.mod h1:WnZ8weMmwAx/fAO3SutjYFU+v7DFreNYnibV7CiaYIw= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= +github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/aws/aws-sdk-go v1.19.11 h1:tqaTGER6Byw3QvsjGW0p018U2UOqaJPeJuzoaF7jjoQ= -github.com/aws/aws-sdk-go v1.19.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= +github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/antchfx/htmlquery v1.3.4 h1:Isd0srPkni2iNTWCwVj/72t7uCphFeor5Q8nCzj1jdQ= +github.com/antchfx/htmlquery v1.3.4/go.mod h1:K9os0BwIEmLAvTqaNSua8tXLWRWZpocZIH73OzWQbwM= +github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs= +github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= +github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30xLN2sUZcMXl50hg+PJCIDdJgIvIbVcKqLJ/ZrtM= +github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8= +github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM= +github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s= +github.com/aquasecurity/go-mock-aws v0.0.0-20240523055201-a4152219967f h1:NRq3oUfkheKgoYPjNUApUtClKaBRcc6KzdcBHqZPrAM= +github.com/aquasecurity/go-mock-aws v0.0.0-20240523055201-a4152219967f/go.mod h1:95xczqqItx1yPSrYG2SQM2gi2lqoYG9i3pLsYKSTpgI= +github.com/aquasecurity/go-npm-version v0.0.2 h1:6sNIaeW4Hw8Xg51nPoD3VSo/5qmFSu0VL809iehEOvc= +github.com/aquasecurity/go-npm-version v0.0.2/go.mod h1:DXyKqRe2yb83peANMjQr8dGDkHanEgoFv8BOQdWlSUQ= +github.com/aquasecurity/go-pep440-version v0.0.1 h1:8VKKQtH2aV61+0hovZS3T//rUF+6GDn18paFTVS0h0M= +github.com/aquasecurity/go-pep440-version v0.0.1/go.mod h1:3naPe+Bp6wi3n4l5iBFCZgS0JG8vY6FT0H4NGhFJ+i4= +github.com/aquasecurity/go-version v0.0.0-20201107203531-5e48ac5d022a/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU= +github.com/aquasecurity/go-version v0.0.1 h1:4cNl516agK0TCn5F7mmYN+xVs1E3S45LkgZk3cbaW2E= +github.com/aquasecurity/go-version v0.0.1/go.mod h1:s1UU6/v2hctXcOa3OLwfj5d9yoXHa3ahf+ipSwEvGT0= +github.com/aquasecurity/loading v0.0.5 h1:2iq02sPSSMU+ULFPmk0v0lXnK/eZ2e0dRAj/Dl5TvuM= +github.com/aquasecurity/loading v0.0.5/go.mod h1:NSHeeq1JTDTFuXAe87q4yQ2DX57pXiaQMqq8Zm9HCJA= +github.com/aquasecurity/table v1.11.0 h1:SzgCAv7dZcv/gyAyzxorS6OgEk7w/WU5iT2pStIkpl4= +github.com/aquasecurity/table v1.11.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= +github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da h1:pj/adfN0Wbzc0H8YkI1nX5K92wOU5/1/1TRuuc0y5Nw= +github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da/go.mod h1:852lbQLpK2nCwlR4ZLYIccxYCfoQao6q9Nl6tjz54v8= +github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= +github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= +github.com/aquasecurity/trivy-aws v0.15.1 h1:XfX0/g7krHPjmE9NAJHxK6y+iMVXWUO2ew1kmlIkS7I= +github.com/aquasecurity/trivy-aws v0.15.1/go.mod h1:GNvfjLAWxiZ9WJvIiatJSTHLAr6H8O7FLMCKa10z3Kk= +github.com/aquasecurity/trivy-checks v1.10.0 h1:Q0FWsYy/uwvr/icRSOzNu55yDZ1ME8hZlpglNs62ZfE= +github.com/aquasecurity/trivy-checks v1.10.0/go.mod h1:/b633SOFNp8RjkxSq+FOg4SgxjklUp+BIQEyTWCnN1k= +github.com/aquasecurity/trivy-db v0.0.0-20240910133327-7e0f4d2ed4c1 h1:G0gnacAORRUqz2Tm5MqivSpldY2GZ74ijhJcMsae+sA= +github.com/aquasecurity/trivy-db v0.0.0-20240910133327-7e0f4d2ed4c1/go.mod h1:PYkSRx4dlgFATEt+okGwibvbxVEtqsOdH+vX/saACYE= +github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= +github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= +github.com/aquasecurity/trivy-kubernetes v0.9.0 h1:rp8RuXwKfFWUPR/ULksA2WpD0z6rslVkzLmPGQr61Wc= +github.com/aquasecurity/trivy-kubernetes v0.9.0/go.mod h1:/JrWYEBmG5R30MraUQxtjM2vo6fc9Dt7ANMxJKrDzTw= +github.com/aquasecurity/trivy-policies v0.10.0 h1:QONOsIFi6+WyB+7NGMBQeCgMFcRg6RV9dTBBpeOFDxU= +github.com/aquasecurity/trivy-policies v0.10.0/go.mod h1:7WU0GTUqtQxqQ+FV3JAy7lskQQZU6lp7Mz1i8GEapFw= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= +github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= +github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= +github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8= +github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82 h1:EO13QJTCD1Ig2IrQnoHTRrn981H9mB7afXsZ89WptI4= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82/go.mod h1:AGh1NCg0SH+uyJamiJA5tTQcql4MMRDXGRdMmCxCXzY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 h1:w9LnHqTq8MEdlnyhV4Bwfizd65lfNCNgdlNC6mM5paE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9/go.mod h1:LGEP6EK4nj+bwWNdrvX/FnDTFowdBNwcSPuZu/ouFys= +github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.36.2 h1:9WvCTutkgDExBamb9UZQ94oiCjJwXUhhtoBH3NIs0Iw= +github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.36.2/go.mod h1:9QmJU2Zam+wUZe8etjM4VY9NlC0WeMFLIvtUIOIko4U= +github.com/aws/aws-sdk-go-v2/service/apigateway v1.28.1 h1:XBVNkd4B5uGC6RgJ+/4x/ZUuk7/6f/hQ8cvGRSxltBI= +github.com/aws/aws-sdk-go-v2/service/apigateway v1.28.1/go.mod h1:4cynmnoMmeiroN7OvI8upcQbvJ3Rfzd3j5hOthYKQnk= +github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.24.7 h1:sDijLs7HLzhvxPWxODryESj4gMA8A8seQWhaUVZaPlE= +github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.24.7/go.mod h1:5AN33eZ7xq2pxsvMJGgXmQA30PQGHOGOaHZf8Q7AaFw= +github.com/aws/aws-sdk-go-v2/service/athena v1.48.5 h1:nfeCwaDRq4tXZUoBENWNydZQ7YeP3lPuWnAMCJSYcuE= +github.com/aws/aws-sdk-go-v2/service/athena v1.48.5/go.mod h1:27ljwDsnZvfrZKsLzWD4WFjI4OZutEFIjvVtYfj9gHc= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.43.1 h1:riUb1ppQ6Qs0+Yz0bttwlwPIl0AdBAcJdtuKLzsbaI4= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.43.1/go.mod h1:fXHLupAMPNGhRAW7e2kS0aoDY/KsQ9GHu80GSK70cRs= +github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.46.2 h1:DrN2vg75JseLCepYjMVav43e+v7+AhArtWlm2F0OJ6Y= +github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.46.2/go.mod h1:WcTfALKgqv+VCMRCLtG4155sAwcfdYhFADc/yDJgSlc= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.2 h1:+M/uY6CU2TjCyi9u8ZcowyguWvpifU7C4eQowdZeXBU= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.2/go.mod h1:URs8sqsyaxiAZkKP6tOEmhcs9j2ynFIomqOKY/CAHJc= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.45.0 h1:j9rGKWaYglZpf9KbJCQVM/L85Y4UdGMgK80A1OddR24= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.45.0/go.mod h1:LZafBHU62ByizrdhNLMnzWGsUX+abAW4q35PN+FOj+A= +github.com/aws/aws-sdk-go-v2/service/codebuild v1.49.2 h1:hQB9ib1JAhjLodnSczNfG5XDw1bWZU0wBwW0FjDs2/4= +github.com/aws/aws-sdk-go-v2/service/codebuild v1.49.2/go.mod h1:dOgg7MoOJYIvQkUkZRrKuO/3YdAbO3+fIJlICyDc3dc= +github.com/aws/aws-sdk-go-v2/service/docdb v1.39.6 h1:N2iwdECLOQuClhv3XdTgGSY75N7QHF5nDekPGoFpRyY= +github.com/aws/aws-sdk-go-v2/service/docdb v1.39.6/go.mod h1:pa9tid/NTUDjayagl5El0KVg9IiV3hJEKVxFmvyc+J8= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.2 h1:dTzxoKbznBEm2xscSQc4DXQ447j/IZRTCwhJxiDN3mg= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.2/go.mod h1:xDvUyIkwBwNtVZJdHEwAuhFly3mezwdEWkbJ5oNYwIw= +github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= +github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.227.0 h1:leicz3rwJmu7yfGrmKjWSV4lVIepp1msmWIlTcLSYLQ= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.227.0/go.mod h1:35jGWx7ECvCwTsApqicFYzZ7JFEnBc6oHUuOQ3xIS54= +github.com/aws/aws-sdk-go-v2/service/ecr v1.55.1 h1:B7f9R99lCF83XlolTg6d6Lvghyto+/VU83ZrneAVfK8= +github.com/aws/aws-sdk-go-v2/service/ecr v1.55.1/go.mod h1:cpYRXx5BkmS3mwWRKPbWSPKmyAUNL7aLWAPiiinwk/U= +github.com/aws/aws-sdk-go-v2/service/ecs v1.52.1 h1:85SGI/Db9I8PT2rvDLIRGxXdSzuyC4ZKDJwfzuv7WqQ= +github.com/aws/aws-sdk-go-v2/service/ecs v1.52.1/go.mod h1:Ghi1OWUv4+VMEULWiHsKH2gNA3KAcMoLWsvU0eRXvIA= +github.com/aws/aws-sdk-go-v2/service/efs v1.34.1 h1:y2BaF/VBEQM5Gi27ZOX1jSKRQLNifOfvegkqKKDPNEM= +github.com/aws/aws-sdk-go-v2/service/efs v1.34.1/go.mod h1:0c/j249PCFk5d/KHJsBIoCVdnZa9Or71J/fqC/n63BA= +github.com/aws/aws-sdk-go-v2/service/eks v1.53.0 h1:ACTxnLwL6YNmuYbxtp/VR3HGL9SWXU6VZkXPjWST9ZQ= +github.com/aws/aws-sdk-go-v2/service/eks v1.53.0/go.mod h1:ZzOjZXGGUQxOq+T3xmfPLKCZe4OaB5vm1LdGaC8IPn4= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.1 h1:Dch9DIcyrHf6OTEhgzz7wIFKrHZAWfcZ1BCAlZtwpYo= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.1/go.mod h1:gzplo968xB24VkeHC4wKfDbSERguKL2xKPL1Csd/mH4= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1 h1:L9Wt9zgtoYKIlaeFTy+EztGjL4oaXBBGtVXA+jaeYko= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1/go.mod h1:yxzLdxt7bVGvIOPYIKFtiaJCJnx2ChlIIvlhW4QgI6M= +github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.32.7 h1:LEo0xkW2yM3qVp8awRVG1lp1o/eR2o0CSR4h9CpB3us= +github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.32.7/go.mod h1:cMruo7iPDprwYjl8CruF4+ht8gZnMJ8nmbVtgEEBu8M= +github.com/aws/aws-sdk-go-v2/service/emr v1.47.1 h1:7vjckLqynZflJB13FUfp1oWOvmAhkHFlA6WsnWSOcj0= +github.com/aws/aws-sdk-go-v2/service/emr v1.47.1/go.mod h1:Bae8t4Qt9r5jnngv9coFhXIHvFBCzlplxsqzm2pOLTs= +github.com/aws/aws-sdk-go-v2/service/iam v1.38.2 h1:8iFKuRj/FJipy/aDZ2lbq0DYuEHdrxp0qVsdi+ZEwnE= +github.com/aws/aws-sdk-go-v2/service/iam v1.38.2/go.mod h1:UBe4z0VZnbXGp6xaCW1ulE9pndjfpsnrU206rWZcR0Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9 h1:by3nYZLR9l8bUH7kgaMU4dJgYFjyRdFEfORlDpPILB4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9/go.mod h1:IWjQYlqw4EX9jw2g3qnEPPWvCE6bS8fKzhMed1OK7c8= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 h1:nbmKXZzXPJn41CcD4HsHsGWqvKjLKz9kWu6XxvLmf1s= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6/go.mod h1:SJhcisfKfAawsdNQoZMBEjg+vyN2lH6rO6fP+T94z5Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 h1:wuZ5uW2uhJR63zwNlqWH2W4aL4ZjeJP3o92/W+odDY4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9/go.mod h1:/G58M2fGszCrOzvJUkDdY8O9kycodunH4VdT5oBAqls= +github.com/aws/aws-sdk-go-v2/service/kafka v1.38.7 h1:RjCwdphB+wBtT/qQw6e7c6HOZ7ab4WN22Uw8GSmKNb8= +github.com/aws/aws-sdk-go-v2/service/kafka v1.38.7/go.mod h1:6ezJjIOpnDf+Q/BJ2CIITrcdXSvfUS1zwjnEHHPa8oU= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.32.7 h1:QTtbqxI+i2gaWjcTwJZtm8/xEl9kiQXXbOatGabNuXA= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.32.7/go.mod h1:5aKZaOb2yfdeAOvfam0/6HoUXg01pN172bn7MqpM35c= +github.com/aws/aws-sdk-go-v2/service/kms v1.49.1 h1:U0asSZ3ifpuIehDPkRI2rxHbmFUMplDA2VeR9Uogrmw= +github.com/aws/aws-sdk-go-v2/service/kms v1.49.1/go.mod h1:NZo9WJqQ0sxQ1Yqu1IwCHQFQunTms2MlVgejg16S1rY= +github.com/aws/aws-sdk-go-v2/service/lambda v1.69.1 h1:q1NrvoJiz0rm9ayKOJ9wsMGmStK6rZSY36BDICMrcuY= +github.com/aws/aws-sdk-go-v2/service/lambda v1.69.1/go.mod h1:hDj7He9kbR9T5zugnS+T21l4z6do4SEGuno/BpJLpA0= +github.com/aws/aws-sdk-go-v2/service/mq v1.27.8 h1:xGcrYXOE7mCt14ToL/ZibXsBW0DhReBuollS35Ci7pQ= +github.com/aws/aws-sdk-go-v2/service/mq v1.27.8/go.mod h1:EjYQlgBAl1BVTGEpjQSTTn8q2IaBYmKZAMGorq+J8N8= +github.com/aws/aws-sdk-go-v2/service/neptune v1.35.6 h1:4XpR4uxdYixFnQecBQ4bj+OVTTIllaVLIdNZT9PVGXU= +github.com/aws/aws-sdk-go-v2/service/neptune v1.35.6/go.mod h1:JeJv7jf5G41lHVNiZ+24s7Frzg8k9xYCGborBe0FdNw= +github.com/aws/aws-sdk-go-v2/service/rds v1.92.0 h1:W0gUYAjO24u/M6tpR041wMHJWGzleOhxtCnNLImdrZs= +github.com/aws/aws-sdk-go-v2/service/rds v1.92.0/go.mod h1:ADD2uROOoEIXjbjDPEvDDZWnGmfKFYMddgKwG5RlBGw= +github.com/aws/aws-sdk-go-v2/service/redshift v1.52.2 h1:mUObSWeMlDwNEZEJAC/72AtnEwfjZqwTCli+up5Yj9A= +github.com/aws/aws-sdk-go-v2/service/redshift v1.52.2/go.mod h1:UydVhUJOB/DaCJWiaBkPlvuzvWVcUlgbS2Bxn33bcKI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.88.3 h1:P18I4ipbk+b/3dZNq5YYh+Hq6XC0vp5RWkLp1tJldDA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.88.3/go.mod h1:Rm3gw2Jov6e6kDuamDvyIlZJDMYk97VeCZ82wz/mVZ0= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.7 h1:Nyfbgei75bohfmZNxgN27i528dGYVzqWJGlAO6lzXy8= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.7/go.mod h1:FG4p/DciRxPgjA+BEOlwRHN0iA8hX2h9g5buSy3cTDA= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.7 h1:N3o8mXK6/MP24BtD9sb51omEO9J9cgPM3Ughc293dZc= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.7/go.mod h1:AAHZydTB8/V2zn3WNwjLXBK1RAcSEpDNmFfrmjvrJQg= +github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2 h1:mFLfxLZB/TVQwNJAYox4WaxpIu+dFVIcExrmRmRCOhw= +github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2/go.mod h1:GnvfTdlvcpD+or3oslHPOn4Mu6KaCwlCp+0p0oqWnrM= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= +github.com/aws/aws-sdk-go-v2/service/workspaces v1.50.2 h1:VfPamxDTVfbKWP3UPDt3iJ8msHkHfIvqZNUhLjtGD9Q= +github.com/aws/aws-sdk-go-v2/service/workspaces v1.50.2/go.mod h1:OX0I9k3wOSsRCrdqAUOVEDLHupani0n+UJ80TY0fIWc= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/briandowns/spinner v0.0.0-20190319032542-ac46072a5a91 h1:GMmnK0dvr0Sf0gx3DvTbln0c8DE07B7sPVD9dgHOqo4= -github.com/briandowns/spinner v0.0.0-20190319032542-ac46072a5a91/go.mod h1:hw/JEQBIE+c/BLI4aKM8UU8v+ZqrD3h7HC27kKt8JQU= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c h1:C4UZIaS+HAw+X6jGUsoP2ZbM99PuqhCttjomg1yhNAI= +github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= +github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= +github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= +github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= +github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= +github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/containerd/continuity v0.0.0-20180921161001-7f53d412b9eb h1:qSMRxG547z/BgQmyVyADxaMADQXVAD9uleP2sQeClbo= -github.com/containerd/continuity v0.0.0-20180921161001-7f53d412b9eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/coreos/clair v0.0.0-20180919182544-44ae4bc9590a h1:glxUtT0RlaVJU86kg78ygzfhwW6D+uj5H+aOK01QDgI= -github.com/coreos/clair v0.0.0-20180919182544-44ae4bc9590a/go.mod h1:uXhHPWAoRqw0jJc2f8RrPCwRhIo9otQ8OEWUFtpCiwA= -github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= -github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= +github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= +github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= +github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= +github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= +github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= +github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= +github.com/containerd/stargz-snapshotter/estargz v0.7.0/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw= +github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= +github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= +github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= +github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/csaf-poc/csaf_distribution/v3 v3.0.0 h1:ob9+Fmpff0YWgTP3dYaw7G2hKQ9cegh9l3zksc+q3sM= +github.com/csaf-poc/csaf_distribution/v3 v3.0.0/go.mod h1:uilCTiNKivq+6zrDvjtZaUeLk70oe21iwKivo6ILwlQ= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= +github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= -github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= -github.com/docker/cli v0.0.0-20180920165730-54c19e67f69c h1:QlAVcyoF7QQVN7zV+xYBjgwtRVlRU3WCTCpb2mcqQrM= -github.com/docker/cli v0.0.0-20180920165730-54c19e67f69c/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20180920194744-16128bbac47f h1:hYf+mPizfvpH6VgIxdntnOmQHd1F1mQUc1oG+j3Ol2g= -github.com/docker/distribution v0.0.0-20180920194744-16128bbac47f/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v0.0.0-20180924202107-a9c061deec0f h1:W4fbqg0JUwy6lLesoJaV/rE0fwAmtdtinMa64X1CEh0= -github.com/docker/docker v0.0.0-20180924202107-a9c061deec0f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-ce v0.0.0-20180924210327-f53bd8bb8e43 h1:gZ4lWixV821UVbYtr+oz1ZPCHkbtE+ivfmHyZRgyl2Y= -github.com/docker/docker-ce v0.0.0-20180924210327-f53bd8bb8e43/go.mod h1:l1FUGRYBvbjnZ8MS6A2xOji4aZFlY/Qmgz7p4oXH7ac= -github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g= -github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/go-connections v0.0.0-20180821093606-97c2040d34df/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y= +github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= +github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= +github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= +github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v23.0.0-rc.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 h1:yWHOI+vFjEsAakUTSrtqc/SAHrhSkmn48pqjidZX3QA= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/etcd-io/bbolt v1.3.2 h1:RLRQ0TKLX7DlBRXAJHvbmXL17Q3KNnTBtZ9B6Qo+/Y0= -github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fernet/fernet-go v0.0.0-20180830025343-9eac43b88a5e/go.mod h1:2H9hjfbpSMHwY503FclkV/lZTBh2YlOmLLSda12uL8c= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= +github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/genuinetools/pkg v0.0.0-20180910213200-1c141f661797/go.mod h1:XTcrCYlXPxnxL2UpnwuRn7tcaTn9HAhxFoFJucootk8= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/gliderlabs/ssh v0.1.3 h1:cBU46h1lYQk5f2Z+jZbewFKy+1zzE2aUX/ilcPDAm9M= -github.com/gliderlabs/ssh v0.1.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4= +github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= +github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM= +github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84= +github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo= +github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4= +github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY= +github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= +github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc= +github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= +github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= +github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-openapi/validate v0.25.1 h1:sSACUI6Jcnbo5IWqbYHgjibrhhmt3vR6lCzKZnmAgBw= +github.com/go-openapi/validate v0.25.1/go.mod h1:RMVyVFYte0gbSTaZ0N4KmTn6u/kClvAFp+mAVfS/DQc= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y= +github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= +github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= +github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZcN5ttKpZLfa/wSo5iLw= +github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I= +github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/licenseclassifier/v2 v2.0.0 h1:1Y57HHILNf4m0ABuMVb6xk4vAJYEUO0gDxNpog0pyeA= +github.com/google/licenseclassifier/v2 v2.0.0/go.mod h1:cOjbdH0kyC9R22sdQbYsFkto4NGCAc+ZSwbeThazEtM= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20250602020802-c6617b811d0e h1:FJta/0WsADCe1r9vQjdHbd3KuiLPu7Y9WlyLGwMUNyE= +github.com/google/pprof v0.0.0-20250602020802-c6617b811d0e/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= +github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.9 h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU= +github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= +github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.7.9 h1:G9gcjrDixz7glqJ+ll5IWvggSBR+R0B54DSRt4qfdC4= +github.com/hashicorp/go-getter v1.7.9/go.mod h1:dyFCmT1AQkDfOIt9NH8pw9XBDqNrIKJT5ylbpi7zPNE= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= +github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24= +github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/terraform-exec v0.23.0 h1:MUiBM1s0CNlRFsCLJuM5wXZrzA3MnPYEsiXmzATMW/I= +github.com/hashicorp/terraform-exec v0.23.0/go.mod h1:mA+qnx1R8eePycfwKkCRk3Wy65mwInvlpAeOwmA7vlY= +github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q= +github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= +github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= -github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/knqyf263/berkeleydb v0.0.0-20190501065933-fafe01fb9662/go.mod h1:bu1CcN4tUtoRcI/B/RFHhxMNKFHVq/c3SV+UTyduoXg= -github.com/knqyf263/fanal v0.0.0-20190506110705-2b5cb3000ff6 h1:iSztZNfwEPMN2CvUX1SxNEclRZn+rwRMdsnAegxRJk4= -github.com/knqyf263/fanal v0.0.0-20190506110705-2b5cb3000ff6/go.mod h1:OiuWIClssf5WzbMcR8lfspdBVaP+vRQndY4kHeFgrDw= -github.com/knqyf263/fanal v0.0.0-20190507123206-ceab60083e70 h1:L27WBZxk7N70WilG91kgvs0EnV+JVCoOTsNQa8tMBJs= -github.com/knqyf263/fanal v0.0.0-20190507123206-ceab60083e70/go.mod h1:OiuWIClssf5WzbMcR8lfspdBVaP+vRQndY4kHeFgrDw= -github.com/knqyf263/go-deb-version v0.0.0-20170509080151-9865fe14d09b h1:DiDMmSwuY27PJxA2Gs0+uI/bQ/ehKARaGXRdlp+wFis= -github.com/knqyf263/go-deb-version v0.0.0-20170509080151-9865fe14d09b/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao= -github.com/knqyf263/go-dep-parser v0.0.0-20190429154931-c377a5391790 h1:c02gG0yRNr25lcLOH+678SuuxxMUq36i48PQnmAweWk= -github.com/knqyf263/go-dep-parser v0.0.0-20190429154931-c377a5391790/go.mod h1:CtT+dtv38jSz5EYYCX21LgtVXP+J3soF2fzQT8lHCfY= -github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 h1:HDjRqotkViMNcGMGicb7cgxklx8OwnjtCBmyWEqrRvM= -github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0= -github.com/knqyf263/go-rpmdb v0.0.0-20190501070121-10a1c42a10dc/go.mod h1:MrSSvdMpTSymaQWk1yFr9sxFSyQmKMj6jkbvGrchBV8= -github.com/knqyf263/go-version v1.1.1 h1:+MpcBC9b7rk5ihag8Y/FLG8get1H2GjniwKQ+9DxI2o= -github.com/knqyf263/go-version v1.1.1/go.mod h1:0tBvHvOBSf5TqGNcY+/ih9o8qo3R16iZCpB9rP0D3VM= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= +github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= +github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 h1:PPPlUUqPP6fLudIK4n0l0VU4KT2cQGnheW9x8pNiCHI= +github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422/go.mod h1:ijAmSS4jErO6+KRzcK6ixsm3Vt96hMhJ+W+x+VmbrQA= +github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075 h1:aC6MEAs3PE3lWD7lqrJfDxHd6hcced9R4JTZu85cJwU= +github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0= +github.com/knqyf263/go-rpmdb v0.1.1 h1:oh68mTCvp1XzxdU7EfafcWzzfstUZAEa3MW0IJye584= +github.com/knqyf263/go-rpmdb v0.1.1/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww= github.com/knqyf263/nested v0.0.1 h1:Sv26CegUMhjt19zqbBKntjwESdxe5hxVPSk0+AKjdUc= github.com/knqyf263/nested v0.0.1/go.mod h1:zwhsIhMkBg90DTOJQvxPkKIypEHPYkgWHs4gybdlUmk= -github.com/knqyf263/tablewriter v0.0.2 h1:lGaBruL/oJt8FAlMVy9KU0oQ/6NXAJjvK7wBgZyc+Og= -github.com/knqyf263/tablewriter v0.0.2/go.mod h1:NDOJQAZxabBL3e13jQVktkvbr6bxXXPon8Lyh7fRPPc= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/liamg/iamgo v0.0.9 h1:tADGm3xVotyRJmuKKaH4+zsBn7LOcvgdpuF3WsSKW3c= +github.com/liamg/iamgo v0.0.9/go.mod h1:Kk6ZxBF/GQqG9nnaUjIi6jf+WXNpeOTyhwc6gnguaZQ= +github.com/liamg/jfather v0.0.9 h1:OneImpI305rzKWFO/FNntHtZPn0ozkOX70raEefnXlo= +github.com/liamg/jfather v0.0.9/go.mod h1:DLNvrUWF5uuDitNvC3Sr27Xj3uJ70TCq0V8n3wtipTc= +github.com/liamg/memoryfs v1.6.0 h1:jAFec2HI1PgMTem5gR7UT8zi9u4BfG5jorCRlLH06W8= +github.com/liamg/memoryfs v1.6.0/go.mod h1:z7mfqXFQS8eSeBBsFjYLlxYRMRyiPktytvYCYTb3BSk= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/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/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI= +github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +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/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +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.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee h1:cgm8mE25x5XXX2oyvJDlyJ72K+rDu/4ZCYce2worNb8= +github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee/go.mod h1:rojbW5tVhH1cuVYFKZS+QX+VGXK45JVsRO+jW92kkKM= +github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 h1:uQubA711SeYStvStohMLrdvRTTohdPHrEPFzerLcY9I= +github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323/go.mod h1:OdtzwqTtu49Gh5RFkNEU1SbcihIuVTtUipwHflqxckE= +github.com/masahiro331/go-ext4-filesystem v0.0.0-20240620024024-ca14e6327bbd h1:JEIW94K3spsvBI5Xb9PGhKSIza9/jxO1lF30tPCAJlA= +github.com/masahiro331/go-ext4-filesystem v0.0.0-20240620024024-ca14e6327bbd/go.mod h1:3XMMY1M486mWGTD13WPItg6FsgflQR72ZMAkd+gsyoQ= +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-20221225061455-612096e4bbbd h1:Y30EzvuoVp97b0unb/GOFXzBUKRXZXUN2e0wYmvC+ic= +github.com/masahiro331/go-vmdk-parser v0.0.0-20221225061455-612096e4bbbd/go.mod h1:5f7mCJGW9cJb8SDn3z8qodGxpMCOo8d/2nls/tiwRrw= +github.com/masahiro331/go-xfs-filesystem v0.0.0-20231205045356-1b22259a6c44 h1:VmSjn0UCyfXUNdePDr7uM/uZTnGSp+mKD5+cYkEoLx4= +github.com/masahiro331/go-xfs-filesystem v0.0.0-20231205045356-1b22259a6c44/go.mod h1:QKBZqdn6teT0LK3QhAf3K6xakItd1LonOShOEC44idQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 h1:TLygBUBxikNJJfLwgm+Qwdgq1FtfV8Uh7bcxRyTzK8s= +github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032/go.mod h1:vYT9HE7WCvL64iVeZylKmCsWKfE+JZ8105iuh2Trk8g= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/buildkit v0.23.1 h1:CZtFmPRF+IFG1C8QfPnktGO1Dzzt5JSwtQ5eDqIh+ag= +github.com/moby/buildkit v0.23.1/go.mod h1:keNXljNmKX1T0AtM0bMObc8OV6mA9cOuquVbPcRpU/Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= +github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2 h1:QhPf3A2AZW3tTGvHPg0TA+CR3oHbVLlXUhlghqISp1I= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/open-policy-agent/opa v1.6.0 h1:/S/cnNQJ2MUMNzizHPbisTWBHowmLkPrugY5jjkPlRQ= +github.com/open-policy-agent/opa v1.6.0/go.mod h1:zFmw4P+W62+CWGYRDDswfVYSCnPo6oYaktQnfIaRFC4= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= -github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= -github.com/peterhellberg/link v1.0.0 h1:mUWkiegowUXEcmlb+ybF75Q/8D2Y0BjZtR8cxoKhaQo= -github.com/peterhellberg/link v1.0.0/go.mod h1:gtSlOT4jmkY8P47hbTc8PTgiDDWpdPbFYl75keYyBB8= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= +github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= +github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= +github.com/openvex/go-vex v0.2.7 h1:/pN3bqvS4QOc6WkkL0hbKzJuAtsUD9vmvk9IZkzD3Zc= +github.com/openvex/go-vex v0.2.7/go.mod h1:ZyQC3NXl9jjS53JOpBG3LAUXySkW8IlJ/GIhsnf5D54= +github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= +github.com/owenrumney/go-sarif/v2 v2.3.3 h1:ubWDJcF5i3L/EIOER+ZyQ03IfplbSU1BLOE26uKQIIU= +github.com/owenrumney/go-sarif/v2 v2.3.3/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= +github.com/owenrumney/squealer v1.2.11 h1:vMudrj70VeOzY+t7Phz9Yo0wAgm4kXes9DcTLBVDqGY= +github.com/owenrumney/squealer v1.2.11/go.mod h1:8KOuitfOfmS/OtzgxQbxnnrbngAGopfgKB/BiGGpqGA= +github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= +github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.0.0-20180924113449-f69c853d21c1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f h1:BVwpUVJDADN2ufcGik7W992pyps0wZ888b/y9GXcLTU= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= +github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI= +github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= +github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI= +github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/saracen/walker v0.1.4 h1:/WCOt98GRkQ0KgL6hXJFBpoH21XY6iCD2N6LQWBFiaU= +github.com/saracen/walker v0.1.4/go.mod h1:2F+hfOidTHfXP2AmlKOqpO+yewf8fIvNUDBNJogpJbk= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14= +github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= +github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= +github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sigstore/rekor v1.5.0 h1:rL7SghHd5HLCtsCrxw0yQg+NczGvM75EjSPPWuGjaiQ= +github.com/sigstore/rekor v1.5.0/go.mod h1:D7JoVCUkxwQOpPDNYeu+CE8zeBC18Y5uDo6tF8s2rcQ= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= -github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sosedoff/gitkit v0.4.0 h1:opyQJ/h9xMRLsz2ca/2CRXtstePcpldiZN8DpLLF8Os= +github.com/sosedoff/gitkit v0.4.0/go.mod h1:V3EpGZ0nvCBhXerPsbDeqtyReNb48cwP9KtkUYTKT5I= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= +github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= +github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/tomoyamachi/reg v0.16.2-0.20190418055600-c6010b917a55 h1:O7Xl4zpk6zjYnwxUd7lubrx7xdzQ+PqfTgaxLE9nF+o= -github.com/tomoyamachi/reg v0.16.2-0.20190418055600-c6010b917a55/go.mod h1:12Fe9EIvK3dG/qWhNk5e9O96I8SGmCKLsJ8GsXUbk+Y= -github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM= +github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= +github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo= +github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= +github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= +github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= +github.com/testcontainers/testcontainers-go/modules/k3s v0.37.0 h1:lqwknybf56hBLi2YsKs01VLSUK8qXnIcG1FM/6/L5qI= +github.com/testcontainers/testcontainers-go/modules/k3s v0.37.0/go.mod h1:RIsXAxAUiaDNfsGsYcZB1TyDn2mqy52lO0HrGFts8cs= +github.com/testcontainers/testcontainers-go/modules/localstack v0.37.0 h1:nPuxUYseqS0eYJg7KDJd95PhoMhdpTnSNtkDLwWFngo= +github.com/testcontainers/testcontainers-go/modules/localstack v0.37.0/go.mod h1:Mw+N4qqJ5iWbg45yWsdLzICfeCEwvYNudfAHHFqCU8Q= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= +github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= +github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= +github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= +github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= +github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= +github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.14 h1:uv/0Bq533iFdnMHZdRBTOlaNMdb1+ZxXIlHDZHIHcvg= +github.com/ulikunitz/xz v0.5.14/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8= -github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= -github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= -go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= +github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/vektah/gqlparser/v2 v2.5.28 h1:bIulcl3LF69ba6EiZVGD88y4MkM+Jxrf3P2MX8xLRkY= +github.com/vektah/gqlparser/v2 v2.5.28/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= +github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk= +github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= +github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= +go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss= +go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180925072008-f04abc6bdfa7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180925112736-b09afc3d579e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998= -golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 h1:PPwnA7z1Pjf7XYaBP9GL1VAMZmcIWyFz7QCMSIIa3Bg= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +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= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4= +google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180924164928-221a8d4f7494/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= -gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek= -gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= -gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= -gopkg.in/src-d/go-git-fixtures.v3 v3.4.0 h1:KFpaNTUcLHLoP/OkdcRXR+MA5p55MhA41YVb7Wd8EfM= -gopkg.in/src-d/go-git-fixtures.v3 v3.4.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= -gopkg.in/src-d/go-git.v4 v4.10.0 h1:NWjTJTQnk8UpIGlssuefyDZ6JruEjo5s88vm88uASbw= -gopkg.in/src-d/go-git.v4 v4.10.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.1.0+incompatible h1:5USw7CrJBYKqjg9R7QlA6jzqZKEAtvW82aNmsxxGPxw= -gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= +helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= +k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= +k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= +k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= +k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/apiserver v0.34.0 h1:Z51fw1iGMqN7uJ1kEaynf2Aec1Y774PqU+FVWCFV3Jg= +k8s.io/apiserver v0.34.0/go.mod h1:52ti5YhxAvewmmpVRqlASvaqxt0gKJxvCeW7ZrwgazQ= +k8s.io/cli-runtime v0.34.0 h1:N2/rUlJg6TMEBgtQ3SDRJwa8XyKUizwjlOknT1mB2Cw= +k8s.io/cli-runtime v0.34.0/go.mod h1:t/skRecS73Piv+J+FmWIQA2N2/rDjdYSQzEE67LUUs8= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= +k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/component-base v0.34.0 h1:bS8Ua3zlJzapklsB1dZgjEJuJEeHjj8yTu1gxE2zQX8= +k8s.io/component-base v0.34.0/go.mod h1:RSCqUdvIjjrEm81epPcjQ/DS+49fADvGSCkIP3IC6vg= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/kubectl v0.34.0 h1:NcXz4TPTaUwhiX4LU+6r6udrlm0NsVnSkP3R9t0dmxs= +k8s.io/kubectl v0.34.0/go.mod h1:bmd0W5i+HuG7/p5sqicr0Li0rR2iIhXL0oUyLF3OjR4= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= +modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA= +modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc= +modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI= +modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= +oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= +sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= +sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= +sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/goreleaser-canary.yml b/goreleaser-canary.yml new file mode 100644 index 000000000000..da395ab53e9d --- /dev/null +++ b/goreleaser-canary.yml @@ -0,0 +1,39 @@ +project_name: trivy_canary_build +builds: + - + main: cmd/trivy/main.go + binary: trivy + ldflags: + - -s -w + - "-extldflags '-static'" + - -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}} + env: + - CGO_ENABLED=0 + goos: + - darwin + - linux + - windows + goarch: + - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 + +archives: + - + format: tar.gz + name_template: >- + {{ .ProjectName }}_{{ .Version }}_ + {{- if eq .Os "darwin" }}macOS + {{- else}}{{- title .Os }}{{ end }}- + {{- if eq .Arch "amd64" }}64bit + {{- else if eq .Arch "arm64" }}ARM64 + {{- else }}{{ .Arch }}{{ end }} + files: + - README.md + - LICENSE + - contrib/*.tpl + format_overrides: + - goos: windows + format: zip diff --git a/goreleaser.yml b/goreleaser.yml index f617ffb65ec1..6e24883428dd 100644 --- a/goreleaser.yml +++ b/goreleaser.yml @@ -1,81 +1,309 @@ project_name: trivy builds: - - main: cmd/trivy/main.go + - id: build-linux + main: cmd/trivy/main.go binary: trivy - ldflags: + ldflags: - -s -w - "-extldflags '-static'" - - -X main.version={{.Version}} + - -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}} env: - CGO_ENABLED=0 goos: - - darwin - linux - - windows - - freebsd - - openbsd goarch: - - amd64 - 386 - arm + - amd64 + - arm64 + - s390x + - ppc64le + goarm: + - 7 + - id: build-bsd + main: cmd/trivy/main.go + binary: trivy + ldflags: + - -s -w + - "-extldflags '-static'" + - -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}} + env: + - CGO_ENABLED=0 + goos: + - freebsd + goarch: + # modernc.org/sqlite doesn't support freebsd/arm64, etc. + - 386 + - amd64 + - id: build-macos + main: cmd/trivy/main.go + binary: trivy + ldflags: + - -s -w + - "-extldflags '-static'" + - -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}} + env: + - CGO_ENABLED=0 + goos: + - darwin + goarch: + - amd64 - arm64 goarm: - 7 + - id: build-windows + main: cmd/trivy/main.go + binary: trivy + ldflags: + - -s -w + - "-extldflags '-static'" + - -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}} + env: + - CGO_ENABLED=0 + goos: + - windows + goarch: + # modernc.org/sqlite doesn't support windows/386 and windows/arm, etc. + - amd64 + goarm: + - 7 + +release: + extra_files: + - glob: ./bom.json + discussion_category_name: Announcements + +nfpms: + - + formats: + - deb + - rpm + vendor: "aquasecurity" + homepage: "https://github.com/aquasecurity" + maintainer: "Teppei Fukuda " + description: "A Fast Vulnerability Scanner for Containers" + license: "Apache-2.0" + file_name_template: >- + {{ .ProjectName }}_{{ .Version }}_ + {{- if eq .Os "darwin" }}macOS + {{- else if eq .Os "openbsd" }}OpenBSD + {{- else if eq .Os "netbsd" }}NetBSD + {{- else if eq .Os "freebsd" }}FreeBSD + {{- else if eq .Os "dragonfly" }}DragonFlyBSD + {{- else}}{{- title .Os }}{{ end }}- + {{- if eq .Arch "amd64" }}64bit + {{- else if eq .Arch "386" }}32bit + {{- else if eq .Arch "arm" }}ARM + {{- else if eq .Arch "arm64" }}ARM64 + {{- else if eq .Arch "ppc64le" }}PPC64LE + {{- else }}{{ .Arch }}{{ end }} + contents: + - src: contrib/*.tpl + dst: /usr/local/share/trivy/templates + rpm: + signature: + key_file: '{{ .Env.GPG_FILE }}' + +archives: + - id: archive + format: tar.gz + name_template: >- + {{ .ProjectName }}_{{ .Version }}_ + {{- if eq .Os "darwin" }}macOS + {{- else if eq .Os "linux" }}Linux + {{- else if eq .Os "openbsd" }}OpenBSD + {{- else if eq .Os "netbsd" }}NetBSD + {{- else if eq .Os "freebsd" }}FreeBSD + {{- else if eq .Os "dragonfly" }}DragonFlyBSD + {{- else}}{{- .Os }}{{ end }}- + {{- if eq .Arch "amd64" }}64bit + {{- else if eq .Arch "386" }}32bit + {{- else if eq .Arch "arm" }}ARM + {{- else if eq .Arch "arm64" }}ARM64 + {{- else if eq .Arch "ppc64le" }}PPC64LE + {{- else }}{{ .Arch }}{{ end }} + files: + - README.md + - LICENSE + - contrib/*.tpl + format_overrides: + - goos: windows + format: zip + + +brews: + - + repository: + owner: aquasecurity + name: homebrew-trivy + homepage: "https://github.com/aquasecurity/trivy" + description: "Scanner for vulnerabilities in container images, file systems, and Git repositories, as well as for configuration issues" + test: | + system "#{bin}/trivy", "--version" + +dockers: + - image_templates: + - "docker.io/aquasec/trivy:{{ .Version }}-amd64" + - "docker.io/aquasec/trivy:latest-amd64" + - "ghcr.io/aquasecurity/trivy:{{ .Version }}-amd64" + - "ghcr.io/aquasecurity/trivy:latest-amd64" + - "public.ecr.aws/aquasecurity/trivy:latest-amd64" + - "public.ecr.aws/aquasecurity/trivy:{{ .Version }}-amd64" + use: buildx + goos: linux + goarch: amd64 + ids: + - build-linux + build_flag_templates: + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.description=A Fast Vulnerability Scanner for Containers" + - "--label=org.opencontainers.image.vendor=Aqua Security" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.source=https://github.com/aquasecurity/trivy" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.url=https://www.aquasec.com/products/trivy/" + - "--label=org.opencontainers.image.documentation=https://aquasecurity.github.io/trivy/v{{ .Version }}/" + - "--platform=linux/amd64" + extra_files: + - contrib/ + - image_templates: + - "docker.io/aquasec/trivy:{{ .Version }}-arm64" + - "docker.io/aquasec/trivy:latest-arm64" + - "ghcr.io/aquasecurity/trivy:{{ .Version }}-arm64" + - "ghcr.io/aquasecurity/trivy:latest-arm64" + - "public.ecr.aws/aquasecurity/trivy:latest-arm64" + - "public.ecr.aws/aquasecurity/trivy:{{ .Version }}-arm64" + use: buildx + goos: linux + goarch: arm64 + ids: + - build-linux + build_flag_templates: + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.description=A Fast Vulnerability Scanner for Containers" + - "--label=org.opencontainers.image.vendor=Aqua Security" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.source=https://github.com/aquasecurity/trivy" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.url=https://www.aquasec.com/products/trivy/" + - "--label=org.opencontainers.image.documentation=https://aquasecurity.github.io/trivy/v{{ .Version }}/" + - "--platform=linux/arm64" + extra_files: + - contrib/ + - image_templates: + - "docker.io/aquasec/trivy:{{ .Version }}-s390x" + - "docker.io/aquasec/trivy:latest-s390x" + - "ghcr.io/aquasecurity/trivy:{{ .Version }}-s390x" + - "ghcr.io/aquasecurity/trivy:latest-s390x" + - "public.ecr.aws/aquasecurity/trivy:latest-s390x" + - "public.ecr.aws/aquasecurity/trivy:{{ .Version }}-s390x" + use: buildx + goos: linux + goarch: s390x + ids: + - build-linux + build_flag_templates: + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.description=A Fast Vulnerability Scanner for Containers" + - "--label=org.opencontainers.image.vendor=Aqua Security" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.source=https://github.com/aquasecurity/trivy" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.url=https://www.aquasec.com/products/trivy/" + - "--label=org.opencontainers.image.documentation=https://aquasecurity.github.io/trivy/v{{ .Version }}/" + - "--platform=linux/s390x" + extra_files: + - contrib/ + - image_templates: + - "docker.io/aquasec/trivy:{{ .Version }}-ppc64le" + - "docker.io/aquasec/trivy:latest-ppc64le" + - "ghcr.io/aquasecurity/trivy:{{ .Version }}-ppc64le" + - "ghcr.io/aquasecurity/trivy:latest-ppc64le" + - "public.ecr.aws/aquasecurity/trivy:latest-ppc64le" + - "public.ecr.aws/aquasecurity/trivy:{{ .Version }}-ppc64le" + use: buildx + goos: linux + goarch: ppc64le + ids: + - build-linux + build_flag_templates: + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.description=A Fast Vulnerability Scanner for Containers" + - "--label=org.opencontainers.image.vendor=Aqua Security" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.source=https://github.com/aquasecurity/trivy" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.url=https://www.aquasec.com/products/trivy/" + - "--label=org.opencontainers.image.documentation=https://aquasecurity.github.io/trivy/v{{ .Version }}/" + - "--platform=linux/ppc64le" + extra_files: + - contrib/ -nfpm: - formats: - - deb - - rpm - dependencies: - - rpm - vendor: "knqyf263" - homepage: "https://github.com/knqyf263" - maintainer: "Teppei Fukuda " - description: "A Fast Vulnerability Scanner for Containers" - license: "MIT" - name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}" - replacements: - amd64: 64bit - 386: 32bit - arm: ARM - arm64: ARM64 - darwin: macOS - linux: Linux - windows: Windows - openbsd: OpenBSD - netbsd: NetBSD - freebsd: FreeBSD - dragonfly: DragonFlyBSD +docker_manifests: + - name_template: 'aquasec/trivy:{{ .Version }}' + image_templates: + - 'aquasec/trivy:{{ .Version }}-amd64' + - 'aquasec/trivy:{{ .Version }}-arm64' + - 'aquasec/trivy:{{ .Version }}-s390x' + - 'aquasec/trivy:{{ .Version }}-ppc64le' + - name_template: 'ghcr.io/aquasecurity/trivy:{{ .Version }}' + image_templates: + - 'ghcr.io/aquasecurity/trivy:{{ .Version }}-amd64' + - 'ghcr.io/aquasecurity/trivy:{{ .Version }}-arm64' + - 'ghcr.io/aquasecurity/trivy:{{ .Version }}-s390x' + - 'ghcr.io/aquasecurity/trivy:{{ .Version }}-ppc64le' + - name_template: 'public.ecr.aws/aquasecurity/trivy:{{ .Version }}' + image_templates: + - 'public.ecr.aws/aquasecurity/trivy:{{ .Version }}-amd64' + - 'public.ecr.aws/aquasecurity/trivy:{{ .Version }}-arm64' + - 'public.ecr.aws/aquasecurity/trivy:{{ .Version }}-s390x' + - 'public.ecr.aws/aquasecurity/trivy:{{ .Version }}-ppc64le' + - name_template: 'aquasec/trivy:latest' + image_templates: + - 'aquasec/trivy:{{ .Version }}-amd64' + - 'aquasec/trivy:{{ .Version }}-arm64' + - 'aquasec/trivy:{{ .Version }}-s390x' + - 'aquasec/trivy:{{ .Version }}-ppc64le' + - name_template: 'ghcr.io/aquasecurity/trivy:latest' + image_templates: + - 'ghcr.io/aquasecurity/trivy:{{ .Version }}-amd64' + - 'ghcr.io/aquasecurity/trivy:{{ .Version }}-arm64' + - 'ghcr.io/aquasecurity/trivy:{{ .Version }}-s390x' + - 'ghcr.io/aquasecurity/trivy:{{ .Version }}-ppc64le' + - name_template: 'public.ecr.aws/aquasecurity/trivy:latest' + image_templates: + - 'public.ecr.aws/aquasecurity/trivy:{{ .Version }}-amd64' + - 'public.ecr.aws/aquasecurity/trivy:{{ .Version }}-arm64' + - 'public.ecr.aws/aquasecurity/trivy:{{ .Version }}-s390x' + - 'public.ecr.aws/aquasecurity/trivy:{{ .Version }}-ppc64le' -archive: - format: tar.gz - format_overrides: - - goos: windows - format: zip - name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}" - replacements: - amd64: 64bit - 386: 32bit - arm: ARM - arm64: ARM64 - darwin: macOS - linux: Linux - windows: Windows - openbsd: OpenBSD - netbsd: NetBSD - freebsd: FreeBSD - dragonfly: DragonFlyBSD - files: - - README.md - - LICENSE +signs: +- cmd: cosign + env: + - COSIGN_EXPERIMENTAL=1 + signature: "${artifact}.sig" + certificate: "${artifact}.pem" + args: + - "sign-blob" + - "--oidc-issuer=https://token.actions.githubusercontent.com" + - "--output-certificate=${certificate}" + - "--output-signature=${signature}" + - "${artifact}" + - "--yes" + artifacts: all + output: true -brew: - github: - owner: knqyf263 - name: homebrew-trivy - dependencies: - - rpm - homepage: "https://github.com/knqyf263/trivy" - description: "" - test: | - system "#{bin}/program --version" +docker_signs: +- cmd: cosign + env: + - COSIGN_EXPERIMENTAL=1 + artifacts: manifests + output: true + args: + - 'sign' + - '${artifact}' + - '--yes' diff --git a/helm/trivy/.helmignore b/helm/trivy/.helmignore new file mode 100644 index 000000000000..50af03172541 --- /dev/null +++ b/helm/trivy/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/trivy/Chart.yaml b/helm/trivy/Chart.yaml new file mode 100644 index 000000000000..3fc94a4b4968 --- /dev/null +++ b/helm/trivy/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: trivy +version: 0.7.0 +appVersion: 0.37.2 +description: Trivy helm chart +keywords: + - scanner + - trivy + - vulnerability +sources: + - https://github.com/aquasecurity/trivy diff --git a/helm/trivy/README.md b/helm/trivy/README.md new file mode 100644 index 000000000000..5b4e2690bd57 --- /dev/null +++ b/helm/trivy/README.md @@ -0,0 +1,111 @@ +# Trivy Scanner + +Trivy vulnerability scanner standalone installation. + +## TL;DR; + +``` +$ helm install trivy . --namespace trivy --create-namespace +``` + +## Introduction + +This chart bootstraps a Trivy deployment on a [Kubernetes](http://kubernetes.io) cluster using the +[Helm](https://helm.sh) package manager. + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 3+ + +## Installing from the Aqua Chart Repository + +``` +helm repo add aquasecurity https://aquasecurity.github.io/helm-charts/ +helm repo update +helm search repo trivy +helm install my-trivy aquasecurity/trivy +``` + +## Installing the Chart + +To install the chart with the release name `my-release`: + +``` +$ helm install my-release . +``` + +The command deploys Trivy on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) +section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list`. + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +``` +$ helm delete my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +The following table lists the configurable parameters of the Trivy chart and their default values. + +| Parameter | Description | Default | +|---------------------------------------|-------------------------------------------------------------------------|----------------| +| `image.registry` | Image registry | `docker.io` | +| `image.repository` | Image name | `aquasec/trivy` | +| `image.tag` | Image tag | `{TAG_NAME}` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `image.pullSecret` | The name of an imagePullSecret used to pull trivy image from e.g. Docker Hub or a private registry | | +| `replicaCount` | Number of Trivy Pods to run | `1` | +| `trivy.debugMode` | The flag to enable or disable Trivy debug mode | `false` | +| `trivy.gitHubToken` | The GitHub access token to download Trivy DB. More info: https://github.com/aquasecurity/trivy#github-rate-limiting | | +| `trivy.registryUsername` | The username used to log in at dockerhub. More info: https://aquasecurity.github.io/trivy/dev/advanced/private-registries/docker-hub/ | | +| `trivy.registryPassword` | The password used to log in at dockerhub. More info: https://aquasecurity.github.io/trivy/dev/advanced/private-registries/docker-hub/ | | +| `trivy.registryCredentialsExistingSecret` | Name of Secret containing dockerhub credentials. Alternative to the 2 parameters above, has precedence if set. | | +| `trivy.serviceAccount.annotations` | Additional annotations to add to the Kubernetes service account resource | | +| `trivy.skipDBUpdate` | The flag to enable or disable Trivy DB downloads from GitHub | `false` | +| `trivy.dbRepository` | OCI repository to retrieve the trivy vulnerability database from | `ghcr.io/aquasecurity/trivy-db` | +| `trivy.cache.redis.enabled` | Enable Redis as caching backend | `false` | +| `trivy.cache.redis.url` | Specify redis connection url, e.g. redis://redis.redis.svc:6379 | `` | +| `trivy.cache.redis.ttl` | Specify redis TTL, e.g. 3600s or 24h | `` | +| `trivy.cache.redis.tls` | Enable Redis TLS with public certificates | `` | +| `trivy.serverToken` | The token to authenticate Trivy client with Trivy server | `` | +| `trivy.existingSecret` | existingSecret if an existing secret has been created outside the chart. Overrides gitHubToken, registryUsername, registryPassword, serverToken | `` | +| `trivy.podAnnotations` | Annotations for pods created by statefulset | `{}` | +| `trivy.extraEnvVars` | extraEnvVars to be set on the container | `{}` | +| `service.name` | If specified, the name used for the Trivy service | | +| `service.type` | Kubernetes service type | `ClusterIP` | +| `service.port` | Kubernetes service port | `4954` | +| `service.sessionAffinity` | Kubernetes service session affinity | `ClientIP` | +| `httpProxy` | The URL of the HTTP proxy server | | +| `httpsProxy` | The URL of the HTTPS proxy server | | +| `noProxy` | The URLs that the proxy settings do not apply to | | +| `nodeSelector` | Node labels for pod assignment | | +| `affinity` | Affinity settings for pod assignment | | +| `tolerations` | Tolerations for pod assignment | | +| `podAnnotations` | Annotations for pods created by statefulset | `{}` | + +The above parameters map to the env variables defined in [trivy](https://github.com/aquasecurity/trivy#configuration). + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. + +``` +$ helm install my-release . \ + --namespace my-namespace \ + --set "service.port=9090" \ + --set "trivy.vulnType=os\,library" +``` + +## Storage + +This chart uses a PersistentVolumeClaim to reduce the number of database downloads between POD restarts or updates. The storageclass should have the reclaim policy `Retain`. + +## Caching + +You can specify a Redis server as cache backend. This Redis server has to be already present. You can use the [bitnami chart](https://bitnami.com/stack/redis/helm). +More Information about the caching backends can be found [here](https://github.com/aquasecurity/trivy#specify-cache-backend). diff --git a/helm/trivy/templates/NOTES.txt b/helm/trivy/templates/NOTES.txt new file mode 100644 index 000000000000..443a853e1e47 --- /dev/null +++ b/helm/trivy/templates/NOTES.txt @@ -0,0 +1,2 @@ +You should be able to access Trivy server installation within +the cluster at http://{{ include "trivy.fullname" . }}.{{ .Release.Namespace }}:{{ .Values.service.port }} diff --git a/helm/trivy/templates/_helpers.tpl b/helm/trivy/templates/_helpers.tpl new file mode 100644 index 000000000000..28d83fcff0bd --- /dev/null +++ b/helm/trivy/templates/_helpers.tpl @@ -0,0 +1,55 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "trivy.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "trivy.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "trivy.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "trivy.labels" -}} +app.kubernetes.io/name: {{ include "trivy.name" . }} +helm.sh/chart: {{ include "trivy.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Return the proper imageRef as used by the container template spec. +*/}} +{{- define "trivy.imageRef" -}} +{{- $registryName := .Values.image.registry -}} +{{- $repositoryName := .Values.image.repository -}} +{{- $tag := .Values.image.tag | default .Chart.AppVersion | toString -}} +{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} diff --git a/helm/trivy/templates/configmap.yaml b/helm/trivy/templates/configmap.yaml new file mode 100644 index 000000000000..a4629144b073 --- /dev/null +++ b/helm/trivy/templates/configmap.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "trivy.fullname" . }} + labels: +{{ include "trivy.labels" . | indent 4 }} +data: + TRIVY_LISTEN: "0.0.0.0:{{ .Values.service.port }}" + TRIVY_CACHE_DIR: "/home/scanner/.cache/trivy" +{{- if .Values.trivy.cache.redis.enabled }} + TRIVY_CACHE_BACKEND: {{ .Values.trivy.cache.redis.url | quote }} + TRIVY_CACHE_TTL: {{ .Values.trivy.cache.redis.ttl | quote }} + TRIVY_REDIS_TLS: {{ .Values.trivy.cache.redis.tls | quote }} +{{- end }} + TRIVY_DEBUG: {{ .Values.trivy.debugMode | quote }} + TRIVY_SKIP_DB_UPDATE: {{ .Values.trivy.skipDBUpdate | quote }} + TRIVY_DB_REPOSITORY: {{ .Values.trivy.dbRepository | quote }} +{{- if .Values.httpProxy }} + HTTP_PROXY: {{ .Values.httpProxy | quote }} +{{- end }} +{{- if .Values.httpsProxy }} + HTTPS_PROXY: {{ .Values.httpsProxy | quote }} +{{- end }} +{{- if .Values.noProxy }} + NO_PROXY: {{ .Values.noProxy | quote }} +{{- end }} +{{- with .Values.trivy.extraEnvVars }} + {{- . | toYaml | nindent 2 }} +{{- end }} diff --git a/helm/trivy/templates/ingress.yaml b/helm/trivy/templates/ingress.yaml new file mode 100644 index 000000000000..fc4a59fc98c9 --- /dev/null +++ b/helm/trivy/templates/ingress.yaml @@ -0,0 +1,53 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "trivy.fullname" . -}} +{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} +apiVersion: networking.k8s.io/v1 +{{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" }} +apiVersion: networking.k8s.io/v1beta1 +{{- else }} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ include "trivy.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "trivy.labels" . | indent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if and (.Values.ingress.ingressClassName) (semverCompare ">= v1.18.0" .Capabilities.KubeVersion.Version) }} + ingressClassName: {{ .Values.ingress.ingressClassName }} +{{- end }} +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + - path: {{ $.Values.ingress.path }} + {{- if $.Capabilities.APIVersions.Has "networking.k8s.io/v1/Ingress" }} + pathType: {{ $.Values.ingress.pathType }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ $.Values.service.port -}} + {{- else }} + backend: + serviceName: {{ $fullName }} + servicePort: {{ $.Values.service.port -}} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/trivy/templates/podsecuritypolicy.yaml b/helm/trivy/templates/podsecuritypolicy.yaml new file mode 100644 index 000000000000..4539667771ea --- /dev/null +++ b/helm/trivy/templates/podsecuritypolicy.yaml @@ -0,0 +1,44 @@ +{{- if .Values.rbac.pspEnabled }} + {{- if .Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy" }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ include "trivy.fullname" . }} + {{- with .Values.rbac.pspAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: +{{ include "trivy.labels" . | indent 4 }} +spec: + privileged: false + allowPrivilegeEscalation: false + volumes: + - 'configMap' + - 'emptyDir' + - 'persistentVolumeClaim' + - 'secret' + - 'projected' + - 'downwardAPI' + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'MustRunAsNonRoot' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + readOnlyRootFilesystem: true + requiredDropCapabilities: + - ALL + {{- end }} +{{- end }} diff --git a/helm/trivy/templates/role.yaml b/helm/trivy/templates/role.yaml new file mode 100644 index 000000000000..461ec60fab57 --- /dev/null +++ b/helm/trivy/templates/role.yaml @@ -0,0 +1,18 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "trivy.fullname" . }} + labels: +{{ include "trivy.labels" . | indent 4 }} + namespace: {{ .Release.Namespace }} +{{- if .Values.rbac.pspEnabled }} + {{- if .Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy" }} +rules: +- apiGroups: ['policy'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: [{{ include "trivy.fullname" . }}] + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/trivy/templates/rolebinding.yaml b/helm/trivy/templates/rolebinding.yaml new file mode 100644 index 000000000000..8b35061b8c34 --- /dev/null +++ b/helm/trivy/templates/rolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "trivy.fullname" . }} + labels: +{{ include "trivy.labels" . | indent 4 }} + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "trivy.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "trivy.fullname" . }} +{{- end }} \ No newline at end of file diff --git a/helm/trivy/templates/secret.yaml b/helm/trivy/templates/secret.yaml new file mode 100644 index 000000000000..0aa79c8a7b71 --- /dev/null +++ b/helm/trivy/templates/secret.yaml @@ -0,0 +1,16 @@ +{{- if not .Values.trivy.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "trivy.fullname" . }} + labels: +{{ include "trivy.labels" . | indent 4 }} +type: Opaque +data: + GITHUB_TOKEN: {{ .Values.trivy.gitHubToken | default "" | b64enc | quote }} + TRIVY_TOKEN: {{ .Values.trivy.serverToken | default "" | b64enc | quote }} +{{- if not .Values.trivy.registryCredentialsExistingSecret }} + TRIVY_USERNAME: {{ .Values.trivy.registryUsername | default "" | b64enc | quote }} + TRIVY_PASSWORD: {{ .Values.trivy.registryPassword | default "" | b64enc | quote }} +{{- end -}} +{{- end }} diff --git a/helm/trivy/templates/service.yaml b/helm/trivy/templates/service.yaml new file mode 100644 index 000000000000..8744b2201424 --- /dev/null +++ b/helm/trivy/templates/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.service.name | default (include "trivy.fullname" .) }} + labels: +{{ include "trivy.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type | default "ClusterIP" }} + selector: + app.kubernetes.io/name: {{ include "trivy.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + ports: + - name: trivy-http + protocol: TCP + port: {{ .Values.service.port | default 4954 }} + targetPort: {{ .Values.service.port | default 4954 }} + sessionAffinity: {{ .Values.service.sessionAffinity | default "ClientIP" }} + diff --git a/helm/trivy/templates/serviceaccount.yaml b/helm/trivy/templates/serviceaccount.yaml new file mode 100644 index 000000000000..2b10e9069bd4 --- /dev/null +++ b/helm/trivy/templates/serviceaccount.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "trivy.fullname" . }} + labels: +{{ include "trivy.labels" . | indent 4 }} +{{- if (.Values.trivy.serviceAccount).annotations }} + annotations: +{{ toYaml .Values.trivy.serviceAccount.annotations | indent 4 }} +{{- end }} + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/helm/trivy/templates/statefulset.yaml b/helm/trivy/templates/statefulset.yaml new file mode 100644 index 000000000000..efb90274fe2c --- /dev/null +++ b/helm/trivy/templates/statefulset.yaml @@ -0,0 +1,136 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "trivy.fullname" . }} + labels: +{{ include "trivy.labels" . | indent 4 }} + {{- with .Values.trivy.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + podManagementPolicy: "Parallel" + serviceName: {{ include "trivy.fullname" . }} + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "trivy.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + {{- if .Values.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + spec: + resources: + requests: + storage: {{ .Values.persistence.size }} + accessModes: + - {{ .Values.persistence.accessMode }} + storageClassName: {{ .Values.persistence.storageClass }} + {{- end }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- . | toYaml | nindent 8 }} + {{- end }} + labels: + app.kubernetes.io/name: {{ include "trivy.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + {{- with .Values.trivy.labels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "trivy.fullname" . }} + automountServiceAccountToken: false + {{- if .Values.podSecurityContext }} + securityContext: +{{ toYaml .Values.podSecurityContext | indent 8 }} + {{- end }} + {{- if .Values.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.image.pullSecret }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: +{{ toYaml .Values.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: +{{ toYaml .Values.tolerations | indent 8 }} + {{- end }} + {{- if .Values.affinity }} + affinity: +{{ toYaml .Values.affinity | indent 8 }} + {{- end }} + containers: + - name: main + image: {{ template "trivy.imageRef" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.securityContext }} + securityContext: +{{ toYaml .Values.securityContext | indent 12 }} + {{- end }} + args: + - server + {{- if .Values.trivy.registryCredentialsExistingSecret }} + env: + - name: TRIVY_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.trivy.registryCredentialsExistingSecret }} + key: TRIVY_USERNAME + - name: TRIVY_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.trivy.registryCredentialsExistingSecret }} + key: TRIVY_PASSWORD + {{- end }} + envFrom: + - configMapRef: + name: {{ include "trivy.fullname" . }} + - secretRef: + {{- if not .Values.trivy.existingSecret }} + name: {{ include "trivy.fullname" . }} + {{- else }} + name: {{ .Values.trivy.existingSecret }} + {{- end }} + ports: + - name: trivy-http + containerPort: {{ .Values.service.port }} + livenessProbe: + httpGet: + scheme: HTTP + path: /healthz + port: trivy-http + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 10 + readinessProbe: + httpGet: + scheme: HTTP + path: /healthz + port: trivy-http + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + volumeMounts: + - mountPath: /tmp + name: tmp-data + readOnly: false + - mountPath: /home/scanner/.cache + name: data + readOnly: false + {{- if .Values.resources }} + resources: +{{ toYaml .Values.resources | indent 12 }} + {{- end }} + volumes: + - name: tmp-data + emptyDir: {} + {{- if not .Values.persistence.enabled }} + - name: data + emptyDir: {} + {{- end }} diff --git a/helm/trivy/values.yaml b/helm/trivy/values.yaml new file mode 100644 index 000000000000..a969f10b7899 --- /dev/null +++ b/helm/trivy/values.yaml @@ -0,0 +1,163 @@ +nameOverride: "" +fullnameOverride: "" + +image: + registry: docker.io + repository: aquasec/trivy + # tag is an override of the image tag, which is by default set by the + # appVersion field in Chart.yaml. + tag: "" + pullPolicy: IfNotPresent + pullSecret: "" + +replicaCount: 1 + +persistence: + enabled: true + storageClass: "" + accessMode: ReadWriteOnce + size: 5Gi + +resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1 + memory: 1Gi + +rbac: + create: true + pspEnabled: false + pspAnnotations: {} + +podSecurityContext: + runAsUser: 65534 + runAsNonRoot: true + fsGroup: 65534 + +securityContext: + privileged: false + readOnlyRootFilesystem: true + +## Node labels for pod assignment +## Ref: https://kubernetes.io/docs/user-guide/node-selection/ +nodeSelector: {} + +## Affinity settings for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ +affinity: {} + +## Tolerations for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +tolerations: [] + +## Annotations for pods created by statefulset +## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: {} + +trivy: + # debugMode the flag to enable Trivy debug mode + debugMode: false + # gitHubToken the GitHub access token to download Trivy DB + # + # Trivy DB contains vulnerability information from NVD, Red Hat, and many other upstream vulnerability databases. + # It is downloaded by Trivy from the GitHub release page https://github.com/aquasecurity/trivy-db/releases and cached + # in the local file system (`/home/scanner/.cache/trivy/db/trivy.db`). In addition, the database contains the update + # timestamp so Trivy can detect whether it should download a newer version from the Internet or use the cached one. + # Currently, the database is updated every 12 hours and published as a new release to GitHub. + # + # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough + # for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000 + # requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult + # https://developer.github.com/v3/#rate-limiting + # + # You can create a GitHub token by following the instructions in + # https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line + gitHubToken: "" + + # Docker registry credentials + # See also: https://aquasecurity.github.io/trivy/dev/advanced/private-registries/docker-hub/ + # + # Either + # Directly in this file + # + # TRIVY_USERNAME + registryUsername: "" + # TRIVY_PASSWORD + registryPassword: "" + # + # Or + # From an existing secret + # + # The secret must be Opaque and just contain "TRIVY_USERNAME: your_user" and "TRIVY_PASSWORD: your_password" as k/v pairs. + # NOTE: When this is set the previous parameters are ignored. + # + # registryCredentialsExistingSecret: name-of-existing-secret + # skipDBUpdate the flag to enable or disable Trivy DB downloads from GitHub + # + # You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues. + # If the flag is enabled you have to manually download the `trivy.db` file and mount it in the + # `/home/scanner/.cache/trivy/db/trivy.db` path (see `cacheDir`). + skipDBUpdate: false + # OCI repository to retrieve the trivy vulnerability database from + dbRepository: ghcr.io/aquasecurity/trivy-db + # Trivy supports filesystem and redis as caching backend + # https://github.com/aquasecurity/trivy#specify-cache-backend + # This location is only used for the cache, not the db storage: https://github.com/aquasecurity/trivy/issues/765#issue-756010345 + # + # In case you specify redis as backend, make sure you installed a redis server yourself, e.g. + # https://bitnami.com/stack/redis/helm + # + # In case redis is not enabled, the filesystem will be used + cache: + redis: + enabled: false + url: "" # e.g. redis://redis.redis.svc:6379 + ttl: "" # e.g 3600s, 24h + tls: false + serviceAccount: + annotations: {} + # eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/IAM_ROLE_NAME + # If you want to add custom labels to your statefulset and podTemplate + labels: {} + # serverToken is the token to authenticate Trivy client with Trivy server. + serverToken: "" + # existingSecret if an existing secret has been created outside the chart. + # Overrides gitHubToken, registryUsername, registryPassword, serverToken + existingSecret: "" + # extraEnvVars to be set on the container + extraEnvVars: {} + +service: + # If specified, the name used for the Trivy service. + name: + # type Kubernetes service type + type: ClusterIP + # port Kubernetes service port + port: 4954 + # sessionAffinity Kubernetes service session affinity + sessionAffinity: ClientIP + +ingress: + enabled: false + # From Kubernetes 1.18+ this field is supported in case your ingress controller supports it. When set, you do not need to add the ingress class as annotation. + ingressClassName: + annotations: {} + # kubernetes.io/ingress.class: nginx + hosts: + - host: trivy.example.com + path: "/" + # type is only needed for networking.k8s.io/v1 in k8s 1.19+ + pathType: Prefix + tls: [] + # - secretName: trivy-example-tls + # hosts: + # - trivy.example.com + +# httpProxy the URL of the HTTP proxy server +httpProxy: +# httpsProxy the URL of the HTTPS proxy server +httpsProxy: +# noProxy the URLs that the proxy settings do not apply to +noProxy: diff --git a/imgs/logo.png b/imgs/logo.png deleted file mode 100644 index b8d821ad6e2d..000000000000 Binary files a/imgs/logo.png and /dev/null differ diff --git a/integration/aws_cloud_test.go b/integration/aws_cloud_test.go new file mode 100644 index 000000000000..9a8cb8781474 --- /dev/null +++ b/integration/aws_cloud_test.go @@ -0,0 +1,78 @@ +//go:build integration + +package integration + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" + awscommands "github.com/aquasecurity/trivy/pkg/cloud/aws/commands" + "github.com/aquasecurity/trivy/pkg/flag" +) + +func TestAwsCommandRun(t *testing.T) { + tests := []struct { + name string + options flag.Options + envs map[string]string + wantErr string + }{ + { + name: "fail without region", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + }, + envs: map[string]string{ + "AWS_ACCESS_KEY_ID": "test", + "AWS_SECRET_ACCESS_KEY": "test", + }, + wantErr: "aws region is required", + }, + { + name: "fail without creds", + envs: map[string]string{ + "AWS_PROFILE": "non-existent-profile", + }, + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + }, + }, + wantErr: "non-existent-profile", + }, + } + + ctx := context.Background() + + localstackC, addr, err := testutil.SetupLocalStack(ctx, "2.2.0") + require.NoError(t, err) + defer localstackC.Terminate(ctx) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + tt.options.AWSOptions.Endpoint = addr + tt.options.GlobalOptions.Timeout = time.Minute + + for k, v := range tt.envs { + t.Setenv(k, v) + } + + err := awscommands.Run(context.Background(), tt.options) + + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } + assert.NoError(t, err) + }) + } + +} diff --git a/integration/client_server_test.go b/integration/client_server_test.go new file mode 100644 index 000000000000..f217021658ae --- /dev/null +++ b/integration/client_server_test.go @@ -0,0 +1,662 @@ +//go:build integration + +package integration + +import ( + "context" + "fmt" + "github.com/aquasecurity/trivy/pkg/types" + "os" + "path/filepath" + "strings" + "testing" + "time" + + dockercontainer "github.com/docker/docker/api/types/container" + "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + + "github.com/aquasecurity/trivy/pkg/report" +) + +type csArgs struct { + Command string + RemoteAddrOption string + Format types.Format + TemplatePath string + IgnoreUnfixed bool + Severity []string + IgnoreIDs []string + Input string + ClientToken string + ClientTokenHeader string + ListAllPackages bool + Target string + secretConfig string +} + +func TestClientServer(t *testing.T) { + tests := []struct { + name string + args csArgs + golden string + wantErr string + }{ + { + name: "alpine 3.9", + args: csArgs{ + Input: "testdata/fixtures/images/alpine-39.tar.gz", + }, + golden: "testdata/alpine-39.json.golden", + }, + { + name: "alpine 3.9 with high and critical severity", + args: csArgs{ + IgnoreUnfixed: true, + Severity: []string{ + "HIGH", + "CRITICAL", + }, + Input: "testdata/fixtures/images/alpine-39.tar.gz", + }, + golden: "testdata/alpine-39-high-critical.json.golden", + }, + { + name: "alpine 3.9 with .trivyignore", + args: csArgs{ + IgnoreUnfixed: false, + IgnoreIDs: []string{ + "CVE-2019-1549", + "CVE-2019-14697", + }, + Input: "testdata/fixtures/images/alpine-39.tar.gz", + }, + golden: "testdata/alpine-39-ignore-cveids.json.golden", + }, + { + name: "alpine 3.10", + args: csArgs{ + Input: "testdata/fixtures/images/alpine-310.tar.gz", + }, + golden: "testdata/alpine-310.json.golden", + }, + { + name: "alpine distroless", + args: csArgs{ + Input: "testdata/fixtures/images/alpine-distroless.tar.gz", + }, + golden: "testdata/alpine-distroless.json.golden", + }, + { + name: "debian buster/10", + args: csArgs{ + Input: "testdata/fixtures/images/debian-buster.tar.gz", + }, + golden: "testdata/debian-buster.json.golden", + }, + { + name: "debian buster/10 with --ignore-unfixed option", + args: csArgs{ + IgnoreUnfixed: true, + Input: "testdata/fixtures/images/debian-buster.tar.gz", + }, + golden: "testdata/debian-buster-ignore-unfixed.json.golden", + }, + { + name: "debian stretch/9", + args: csArgs{ + Input: "testdata/fixtures/images/debian-stretch.tar.gz", + }, + golden: "testdata/debian-stretch.json.golden", + }, + { + name: "ubuntu 18.04", + args: csArgs{ + Input: "testdata/fixtures/images/ubuntu-1804.tar.gz", + }, + golden: "testdata/ubuntu-1804.json.golden", + }, + { + name: "centos 7", + args: csArgs{ + Input: "testdata/fixtures/images/centos-7.tar.gz", + }, + golden: "testdata/centos-7.json.golden", + }, + { + name: "centos 7 with --ignore-unfixed option", + args: csArgs{ + IgnoreUnfixed: true, + Input: "testdata/fixtures/images/centos-7.tar.gz", + }, + golden: "testdata/centos-7-ignore-unfixed.json.golden", + }, + { + name: "centos 7 with medium severity", + args: csArgs{ + IgnoreUnfixed: true, + Severity: []string{"MEDIUM"}, + Input: "testdata/fixtures/images/centos-7.tar.gz", + }, + golden: "testdata/centos-7-medium.json.golden", + }, + { + name: "centos 6", + args: csArgs{ + Input: "testdata/fixtures/images/centos-6.tar.gz", + }, + golden: "testdata/centos-6.json.golden", + }, + { + name: "ubi 7", + args: csArgs{ + Input: "testdata/fixtures/images/ubi-7.tar.gz", + }, + golden: "testdata/ubi-7.json.golden", + }, + { + name: "almalinux 8", + args: csArgs{ + Input: "testdata/fixtures/images/almalinux-8.tar.gz", + }, + golden: "testdata/almalinux-8.json.golden", + }, + { + name: "rocky linux 8", + args: csArgs{ + Input: "testdata/fixtures/images/rockylinux-8.tar.gz", + }, + golden: "testdata/rockylinux-8.json.golden", + }, + { + name: "distroless base", + args: csArgs{ + Input: "testdata/fixtures/images/distroless-base.tar.gz", + }, + golden: "testdata/distroless-base.json.golden", + }, + { + name: "distroless python27", + args: csArgs{ + Input: "testdata/fixtures/images/distroless-python27.tar.gz", + }, + golden: "testdata/distroless-python27.json.golden", + }, + { + name: "amazon 1", + args: csArgs{ + Input: "testdata/fixtures/images/amazon-1.tar.gz", + }, + golden: "testdata/amazon-1.json.golden", + }, + { + name: "amazon 2", + args: csArgs{ + Input: "testdata/fixtures/images/amazon-2.tar.gz", + }, + golden: "testdata/amazon-2.json.golden", + }, + { + name: "oracle 8", + args: csArgs{ + Input: "testdata/fixtures/images/oraclelinux-8.tar.gz", + }, + golden: "testdata/oraclelinux-8.json.golden", + }, + { + name: "opensuse leap 15.1", + args: csArgs{ + Input: "testdata/fixtures/images/opensuse-leap-151.tar.gz", + }, + golden: "testdata/opensuse-leap-151.json.golden", + }, + { + name: "photon 3.0", + args: csArgs{ + Input: "testdata/fixtures/images/photon-30.tar.gz", + }, + golden: "testdata/photon-30.json.golden", + }, + { + name: "CBL-Mariner 1.0", + args: csArgs{ + Input: "testdata/fixtures/images/mariner-1.0.tar.gz", + }, + golden: "testdata/mariner-1.0.json.golden", + }, + { + name: "busybox with Cargo.lock", + args: csArgs{ + Input: "testdata/fixtures/images/busybox-with-lockfile.tar.gz", + }, + golden: "testdata/busybox-with-lockfile.json.golden", + }, + { + name: "scan pox.xml with repo command in client/server mode", + args: csArgs{ + Command: "repo", + RemoteAddrOption: "--server", + Target: "testdata/fixtures/repo/pom/", + }, + golden: "testdata/pom.json.golden", + }, + { + name: "scan package-lock.json with repo command in client/server mode", + args: csArgs{ + Command: "repo", + RemoteAddrOption: "--server", + Target: "testdata/fixtures/repo/npm/", + ListAllPackages: true, + }, + golden: "testdata/npm.json.golden", + }, + { + name: "scan sample.pem with repo command in client/server mode", + args: csArgs{ + Command: "repo", + RemoteAddrOption: "--server", + secretConfig: "testdata/fixtures/repo/secrets/trivy-secret.yaml", + Target: "testdata/fixtures/repo/secrets/", + }, + golden: "testdata/secrets.json.golden", + }, + { + name: "scan remote repository with repo command in client/server mode", + args: csArgs{ + Command: "repo", + RemoteAddrOption: "--server", + Target: "https://github.com/knqyf263/trivy-ci-test", + }, + golden: "testdata/test-repo.json.golden", + }, + } + + addr, cacheDir := setup(t, setupOptions{}) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + + if tt.args.secretConfig != "" { + osArgs = append(osArgs, "--secret-config", tt.args.secretConfig) + } + + runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{}) + }) + } +} + +func TestClientServerWithFormat(t *testing.T) { + tests := []struct { + name string + args csArgs + golden string + }{ + { + name: "alpine 3.10 with gitlab template", + args: csArgs{ + Format: "template", + TemplatePath: "@../contrib/gitlab.tpl", + Input: "testdata/fixtures/images/alpine-310.tar.gz", + }, + golden: "testdata/alpine-310.gitlab.golden", + }, + { + name: "alpine 3.10 with gitlab-codequality template", + args: csArgs{ + Format: "template", + TemplatePath: "@../contrib/gitlab-codequality.tpl", + Input: "testdata/fixtures/images/alpine-310.tar.gz", + }, + golden: "testdata/alpine-310.gitlab-codequality.golden", + }, + { + name: "alpine 3.10 with sarif format", + args: csArgs{ + Format: "sarif", + Input: "testdata/fixtures/images/alpine-310.tar.gz", + }, + golden: "testdata/alpine-310.sarif.golden", + }, + { + name: "alpine 3.10 with ASFF template", + args: csArgs{ + Format: "template", + TemplatePath: "@../contrib/asff.tpl", + Input: "testdata/fixtures/images/alpine-310.tar.gz", + }, + golden: "testdata/alpine-310.asff.golden", + }, + { + name: "scan secrets with ASFF template", + args: csArgs{ + Command: "repo", + RemoteAddrOption: "--server", + Format: "template", + TemplatePath: "@../contrib/asff.tpl", + Target: "testdata/fixtures/repo/secrets/", + }, + golden: "testdata/secrets.asff.golden", + }, + { + name: "alpine 3.10 with html template", + args: csArgs{ + Format: "template", + TemplatePath: "@../contrib/html.tpl", + Input: "testdata/fixtures/images/alpine-310.tar.gz", + }, + golden: "testdata/alpine-310.html.golden", + }, + { + name: "alpine 3.10 with junit template", + args: csArgs{ + Format: "template", + TemplatePath: "@../contrib/junit.tpl", + Input: "testdata/fixtures/images/alpine-310.tar.gz", + }, + golden: "testdata/alpine-310.junit.golden", + }, + { + name: "alpine 3.10 with github dependency snapshots format", + args: csArgs{ + Format: "github", + Input: "testdata/fixtures/images/alpine-310.tar.gz", + }, + golden: "testdata/alpine-310.gsbom.golden", + }, + } + + fakeTime := time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC) + report.CustomTemplateFuncMap = map[string]interface{}{ + "now": func() time.Time { + return fakeTime + }, + "date": func(format string, t time.Time) string { + return t.Format(format) + }, + } + + // For GitHub Dependency Snapshots + t.Setenv("GITHUB_REF", "/ref/feature-1") + t.Setenv("GITHUB_SHA", "39da54a1ff04120a31df8cbc94ce9ede251d21a3") + t.Setenv("GITHUB_JOB", "integration") + t.Setenv("GITHUB_RUN_ID", "1910764383") + t.Setenv("GITHUB_WORKFLOW", "workflow-name") + + t.Cleanup(func() { + report.CustomTemplateFuncMap = map[string]interface{}{} + }) + + addr, cacheDir := setup(t, setupOptions{}) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("AWS_REGION", "test-region") + t.Setenv("AWS_ACCOUNT_ID", "123456789012") + osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + + runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{}) + }) + } +} + +func TestClientServerWithCycloneDX(t *testing.T) { + tests := []struct { + name string + args csArgs + golden string + }{ + { + name: "fluentd with RubyGems with CycloneDX format", + args: csArgs{ + Format: "cyclonedx", + Input: "testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz", + }, + golden: "testdata/fluentd-multiple-lockfiles.cdx.json.golden", + }, + } + + addr, cacheDir := setup(t, setupOptions{}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + runTest(t, osArgs, tt.golden, "", types.FormatCycloneDX, runOptions{ + fakeUUID: "3ff14136-e09f-4df9-80ea-%012d", + }) + }) + } +} + +func TestClientServerWithToken(t *testing.T) { + tests := []struct { + name string + args csArgs + golden string + wantErr string + }{ + { + name: "alpine 3.9 with token", + args: csArgs{ + Input: "testdata/fixtures/images/alpine-39.tar.gz", + ClientToken: "token", + ClientTokenHeader: "Trivy-Token", + }, + golden: "testdata/alpine-39.json.golden", + }, + { + name: "invalid token", + args: csArgs{ + Input: "testdata/fixtures/images/distroless-base.tar.gz", + ClientToken: "invalidtoken", + ClientTokenHeader: "Trivy-Token", + }, + wantErr: "twirp error unauthenticated: invalid token", + }, + { + name: "invalid token header", + args: csArgs{ + Input: "testdata/fixtures/images/distroless-base.tar.gz", + ClientToken: "token", + ClientTokenHeader: "Unknown-Header", + }, + wantErr: "twirp error unauthenticated: invalid token", + }, + } + + serverToken := "token" + serverTokenHeader := "Trivy-Token" + addr, cacheDir := setup(t, setupOptions{ + token: serverToken, + tokenHeader: serverTokenHeader, + }) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{wantErr: tt.wantErr}) + }) + } +} + +func TestClientServerWithRedis(t *testing.T) { + // Set up a Redis container + ctx := context.Background() + // This test includes 2 checks + // redisC container will terminate after first check + redisC, addr := setupRedis(t, ctx) + + // Set up Trivy server + addr, cacheDir := setup(t, setupOptions{cacheBackend: addr}) + t.Cleanup(func() { os.RemoveAll(cacheDir) }) + + // Test parameters + testArgs := csArgs{ + Input: "testdata/fixtures/images/alpine-39.tar.gz", + } + golden := "testdata/alpine-39.json.golden" + + t.Run("alpine 3.9", func(t *testing.T) { + osArgs := setupClient(t, testArgs, addr, cacheDir, golden) + + // Run Trivy client + runTest(t, osArgs, golden, "", types.FormatJSON, runOptions{}) + }) + + // Terminate the Redis container + require.NoError(t, redisC.Terminate(ctx)) + + t.Run("sad path", func(t *testing.T) { + osArgs := setupClient(t, testArgs, addr, cacheDir, golden) + + // Run Trivy client + runTest(t, osArgs, "", "", types.FormatJSON, runOptions{ + wantErr: "unable to store cache", + }) + }) +} + +type setupOptions struct { + token string + tokenHeader string + cacheBackend string +} + +func setup(t *testing.T, options setupOptions) (string, string) { + t.Helper() + + // Set up testing DB + cacheDir := initDB(t) + + // Set a temp dir so that modules will not be loaded + t.Setenv("XDG_DATA_HOME", cacheDir) + + port, err := getFreePort() + assert.NoError(t, err) + addr := fmt.Sprintf("localhost:%d", port) + + go func() { + osArgs := setupServer(addr, options.token, options.tokenHeader, cacheDir, options.cacheBackend) + + // Run Trivy server + require.NoError(t, execute(osArgs)) + }() + + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + err = waitPort(ctx, addr) + assert.NoError(t, err) + + return addr, cacheDir +} + +func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []string { + osArgs := []string{ + "--cache-dir", + cacheDir, + "server", + "--skip-update", + "--listen", + addr, + } + if token != "" { + osArgs = append(osArgs, []string{ + "--token", + token, + "--token-header", + tokenHeader, + }...) + } + if cacheBackend != "" { + osArgs = append(osArgs, "--cache-backend", cacheBackend) + } + return osArgs +} + +func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden string) []string { + if c.Command == "" { + c.Command = "image" + } + if c.RemoteAddrOption == "" { + c.RemoteAddrOption = "--server" + } + t.Helper() + osArgs := []string{ + "--cache-dir", + cacheDir, + c.Command, + c.RemoteAddrOption, + "http://" + addr, + } + + if c.Format != "" { + osArgs = append(osArgs, "--format", string(c.Format)) + if c.TemplatePath != "" { + osArgs = append(osArgs, "--template", c.TemplatePath) + } + } else { + osArgs = append(osArgs, "--format", "json") + } + + if c.ListAllPackages { + osArgs = append(osArgs, "--list-all-pkgs") + } + + if c.IgnoreUnfixed { + osArgs = append(osArgs, "--ignore-unfixed") + } + if len(c.Severity) != 0 { + osArgs = append(osArgs, + "--severity", strings.Join(c.Severity, ","), + ) + } + + if len(c.IgnoreIDs) != 0 { + trivyIgnore := filepath.Join(t.TempDir(), ".trivyignore") + err := os.WriteFile(trivyIgnore, []byte(strings.Join(c.IgnoreIDs, "\n")), 0444) + require.NoError(t, err, "failed to write .trivyignore") + osArgs = append(osArgs, "--ignorefile", trivyIgnore) + } + if c.ClientToken != "" { + osArgs = append(osArgs, "--token", c.ClientToken, "--token-header", c.ClientTokenHeader) + } + if c.Input != "" { + osArgs = append(osArgs, "--input", c.Input) + } + + if c.Target != "" { + osArgs = append(osArgs, c.Target) + } + + return osArgs +} + +func setupRedis(t *testing.T, ctx context.Context) (testcontainers.Container, string) { + t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") + t.Helper() + imageName := "redis:5.0" + port := "6379/tcp" + req := testcontainers.ContainerRequest{ + Name: "redis", + Image: imageName, + ExposedPorts: []string{port}, + HostConfigModifier: func(hostConfig *dockercontainer.HostConfig) { + hostConfig.AutoRemove = true + }, + } + + redis, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + require.NoError(t, err) + + ip, err := redis.Host(ctx) + require.NoError(t, err) + + p, err := redis.MappedPort(ctx, nat.Port(port)) + require.NoError(t, err) + + addr := fmt.Sprintf("redis://%s:%s", ip, p.Port()) + return redis, addr +} diff --git a/integration/config_test.go b/integration/config_test.go new file mode 100644 index 000000000000..b7c58e8238d6 --- /dev/null +++ b/integration/config_test.go @@ -0,0 +1,230 @@ +//go:build integration + +package integration + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/types" +) + +// TestConfiguration tests the configuration of the CLI flags, environmental variables, and config file +func TestConfiguration(t *testing.T) { + type args struct { + input string + flags map[string]string + envs map[string]string + configFile string + } + type test struct { + name string + args args + golden string + wantErr string + } + + tests := []test{ + { + name: "skip files", + args: args{ + input: "testdata/fixtures/repo/gomod", + flags: map[string]string{ + "scanners": "vuln", + "skip-files": "path/to/dummy,testdata/fixtures/repo/gomod/submod2/go.mod", + }, + envs: map[string]string{ + "TRIVY_SCANNERS": "vuln", + "TRIVY_SKIP_FILES": "path/to/dummy,testdata/fixtures/repo/gomod/submod2/go.mod", + }, + configFile: `--- +scan: + scanners: + - vuln + skip-files: + - path/to/dummy + - testdata/fixtures/repo/gomod/submod2/go.mod +`, + }, + golden: "testdata/gomod-skip.json.golden", + }, + { + name: "dockerfile with custom file pattern", + args: args{ + input: "testdata/fixtures/repo/dockerfile_file_pattern", + flags: map[string]string{ + "scanners": "misconfig", + "file-patterns": "dockerfile:Customfile", + "namespaces": "testing", + }, + envs: map[string]string{ + "TRIVY_SCANNERS": "misconfig", + "TRIVY_FILE_PATTERNS": "dockerfile:Customfile", + "TRIVY_NAMESPACES": "testing", + }, + configFile: `--- +scan: + scanners: + - misconfig + file-patterns: + - dockerfile:Customfile +rego: + skip-policy-update: true + namespaces: + - testing +`, + }, + golden: "testdata/dockerfile_file_pattern.json.golden", + }, + { + name: "key alias", // "--scanners" vs "--security-checks" + args: args{ + input: "testdata/fixtures/repo/gomod", + flags: map[string]string{ + "security-checks": "vuln", + }, + envs: map[string]string{ + "TRIVY_SECURITY_CHECKS": "vuln", + }, + configFile: `--- +scan: + security-checks: + - vuln +`, + }, + golden: "testdata/gomod.json.golden", + }, + { + name: "value alias", // "--scanners vuln" vs "--scanners vulnerability" + args: args{ + input: "testdata/fixtures/repo/gomod", + flags: map[string]string{ + "scanners": "vulnerability", + }, + envs: map[string]string{ + "TRIVY_SCANNERS": "vulnerability", + }, + configFile: `--- +scan: + scanners: + - vulnerability +`, + }, + golden: "testdata/gomod.json.golden", + }, + { + name: "invalid value", + args: args{ + input: "testdata/fixtures/repo/gomod", + flags: map[string]string{ + "scanners": "vulnerability", + "severity": "CRITICAL,INVALID", + }, + envs: map[string]string{ + "TRIVY_SCANNERS": "vulnerability", + "TRIVY_SEVERITY": "CRITICAL,INVALID", + }, + configFile: `--- +scan: + scanners: + - vulnerability +severity: + - CRITICAL + - INVALID +`, + }, + wantErr: `invalid argument "[CRITICAL INVALID]" for "--severity" flag`, + }, + } + + // Set up testing DB + cacheDir := initDB(t) + + // Set a temp dir so that modules will not be loaded + t.Setenv("XDG_DATA_HOME", cacheDir) + + for _, tt := range tests { + command := "repo" + + t.Run(tt.name+" with CLI flags", func(t *testing.T) { + osArgs := []string{ + "--format", + "json", + "--cache-dir", + cacheDir, + "--skip-db-update", + "--skip-policy-update", + command, + tt.args.input, + } + for key, value := range tt.args.flags { + osArgs = append(osArgs, "--"+key, value) + } + + // Set up the output file + outputFile := filepath.Join(t.TempDir(), "output.json") + osArgs = append(osArgs, "--output", outputFile) + + runTest(t, osArgs, tt.golden, outputFile, types.FormatJSON, runOptions{ + wantErr: tt.wantErr, + }) + }) + + t.Run(tt.name+" with environmental variables", func(t *testing.T) { + // Set up the output file + outputFile := filepath.Join(t.TempDir(), "output.json") + + t.Setenv("TRIVY_OUTPUT", outputFile) + t.Setenv("TRIVY_FORMAT", "json") + t.Setenv("TRIVY_CACHE_DIR", cacheDir) + t.Setenv("TRIVY_SKIP_DB_UPDATE", "true") + t.Setenv("TRIVY_SKIP_POLICY_UPDATE", "true") + for key, value := range tt.args.envs { + t.Setenv(key, value) + } + + osArgs := []string{ + command, + tt.args.input, + } + + runTest(t, osArgs, tt.golden, outputFile, types.FormatJSON, runOptions{ + wantErr: tt.wantErr, + }) + }) + + t.Run(tt.name+" with config file", func(t *testing.T) { + // Set up the output file + outputFile := filepath.Join(t.TempDir(), "output.json") + + configFile := tt.args.configFile + configFile = configFile + fmt.Sprintf(` +format: json +output: %s +cache: + dir: %s +db: + skip-update: true +`, outputFile, cacheDir) + + configPath := filepath.Join(t.TempDir(), "trivy.yaml") + err := os.WriteFile(configPath, []byte(configFile), 0444) + require.NoError(t, err) + + osArgs := []string{ + command, + "--config", + configPath, + tt.args.input, + } + + runTest(t, osArgs, tt.golden, outputFile, types.FormatJSON, runOptions{ + wantErr: tt.wantErr, + }) + }) + } +} diff --git a/integration/data/auth_config/config.yml b/integration/data/auth_config/config.yml new file mode 100644 index 000000000000..a30db449750f --- /dev/null +++ b/integration/data/auth_config/config.yml @@ -0,0 +1,23 @@ +server: + addr: ":5001" + certificate: "/certs/cert.pem" + key: "/certs/key.pem" + +token: + issuer: "Trivy auth server" # Must match issuer in the Registry config. + expiration: 900 + +users: + # Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate. + "admin": + password: "$2y$05$LO.vzwpWC5LZGqThvEfznu8qhb5SGqvBSWY1J3yZ4AxtMRZ3kN5jC" # badmin + "test": + password: "$2y$05$WuwBasGDAgr.QCbGIjKJaep4dhxeai9gNZdmBnQXqpKly57oNutya" # 123 + +acl: + - match: {account: "admin"} + actions: ["*"] + comment: "Admin has full access to everything." + - match: {account: "test"} + actions: ["pull"] + comment: "User \"test\" can pull stuff." diff --git a/integration/data/certs/cert.pem b/integration/data/certs/cert.pem new file mode 100644 index 000000000000..921165489467 --- /dev/null +++ b/integration/data/certs/cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICwTCCAamgAwIBAgIJAP09YW8ChPlwMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV +BAoMB0FjbWUgQ28wIBcNMjEwNTEyMDQ0NzA1WhgPMjEwMDA0MTQwNDQ3MDVaMBIx +EDAOBgNVBAoMB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDNmKpDOzU8GK5Xb3GfeqU1kKQ0gBejGtqK5ydH8tlRoy2NKGvjJ95nhIxUXMKe +e345JFlzkCen5Ekvt70LT0O253z0FecfpaFilreIiu5J2YWWNtlruMhpjp4kYVMO +piKnujiNK9eAUcz++YeAmrog7QPBJBCgdu18xTy/yOW/Y414e1efvbRJZ4TaQb0Y +LgXRl1nlOLPPr5ew9pgnct7DxJVXpjXtgBxCsfcjH4kZGfc9zP0IKyODqaSCFRtj +eKH8gSpJCimBp3hpWvsSTHTRraOxAGXqhIYPhqRM83eB2QbeHnyk+YOn76pdMndb +vqAPksmTyHcgZShkhGcHKvbVAgMBAAGjGDAWMBQGA1UdEQQNMAuCCWxvY2FsaG9z +dDANBgkqhkiG9w0BAQsFAAOCAQEAHxXOTKGP1hl3J2jQrpha5LuYdMEbK1HFbPhV +042k0tBmfP3wRgx0o/WQhg4f5RswQRtipdUCmMZVOAoQfos8j9LFmIKwcsboEQe/ +Fvqq2+W/5TRhsKn/1OxvCZAEurazSygtm6hyiMGwKjJLfyzwjZx+Oopn3lqRUP36 +gLQQ57szoNZFKyPN2z2unXAuDG5wpG2InX8WJvlrhaiCHGUoxO8r0rVawm58bahM +uGPlVPCNdxl1h7K8aecKpm+7Wh8n06Nl/kOWBDFAXeI8IwrnIy1rAZLngvnjqL// +umjXKCBWya48ed9HMoOR2aruzseXc8k6cGXuBxYFtHissPvPPQ== +-----END CERTIFICATE----- diff --git a/integration/data/certs/key.pem b/integration/data/certs/key.pem new file mode 100644 index 000000000000..26b79f722008 --- /dev/null +++ b/integration/data/certs/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAzZiqQzs1PBiuV29xn3qlNZCkNIAXoxraiucnR/LZUaMtjShr +4yfeZ4SMVFzCnnt+OSRZc5Anp+RJL7e9C09Dtud89BXnH6WhYpa3iIruSdmFljbZ +a7jIaY6eJGFTDqYip7o4jSvXgFHM/vmHgJq6IO0DwSQQoHbtfMU8v8jlv2ONeHtX +n720SWeE2kG9GC4F0ZdZ5Tizz6+XsPaYJ3Lew8SVV6Y17YAcQrH3Ix+JGRn3Pcz9 +CCsjg6mkghUbY3ih/IEqSQopgad4aVr7Ekx00a2jsQBl6oSGD4akTPN3gdkG3h58 +pPmDp++qXTJ3W76gD5LJk8h3IGUoZIRnByr21QIDAQABAoIBAQCVN2ETjIxVgqA+ +K08u7Ses2b6jr/f31AybVasnx/S8EI+F7Llo003SmdvzeqNxvLVeqagWfKCbdM89 +R8B3zd6aiCYjTSZCzMZ1tGeePR83EB2paUOhsCocmnricpSChEeQrlJO+2vb4QLE +Z7xVtXazYPIhophCri4tKUWu+BLvNPez+TndaE5Xg77HLmu24rloZh6XhYDdFWd+ +u/eF+QiWy4/EoLUv2TLym8ivUws+r2G9yK57kcQCJw+BqlaRew7Ts0RHnam53OxV +T4dEHJxAfXO8jC1F5NCjoBO/+0HJqrMtD0NqWH9G+fEtakL7h5oeh6vYrSQfpZGC +V7MXojqdAoGBAOhwBi0erXOn4strtkGvSjJ6HVLwWfmm+rlfm23JGigYghYTSxBM +ESuwppt1QPXK5jfil89RqrvDqKG9BjXV4yWyaJlIRaYeJe8/TZa3e8WkLr0XaKGH +v1LTW+/uc73ihDJ/M2axmP4vjThCfqiG9aKXLCDM+DIgfdvIbkXUfPZjAoGBAOJw +Fc6D3z0r09F3/UgtADhQlbD2jzs6xdcqCu7af3527F6ePXXU8CTLS5jusAiDW5xH +ukQS/0ZM92UTUJxpQxgzHSWOImhcv3o45vzQ0C0pXSSaE+Pp8QYWaE/BdE+VoVOK +YGAfppZywPGnKYt4R5ho5XLwAL3rrH+2m7z51mdnAoGBANP5LbjCLF64Mb1f2pOm +f1zvPoTfyr5BSI/7n+yMJL2CNEhbie4v4MzeSeKmGPrO8grvK5EXIkQgGE5/6wT3 +rTI4tOltHo9zGRdJvMGBTXAd3b32diYxfQrU1BhIducph3PhyweRWTweM4SmJ4ob +ojGH+edj5ckZFo50CBTIxrmPAoGBAI8SpSSsfCRJiffjadzt2iK7AColT9DrzM+r +1+adlksQ1z7dmxXVqrqE3UpPHljyrrKrO40Bt9vyi6qIrrl1ZRhoS3VMPn9UgwO1 +6nU5dx/h7+FNnV23ljvzcotaP6R9dca0OzrhJMAQ18qYhY6DPDGXrcqWzNEzlPXJ +KtQXxBnnAoGAFQPvW/wDahrGcm1MBw83E0TgNJpoB10tz6R1dLdKVSHJUXMfxmij +Wj4MaF0JB0GWRRjutng+i7y7Tx+mUpu80qV8E9zAH7jGFnpqjw8A9zp5ftK00e7Y +shRlg+lhJhlvMA5QCYNzpYj+7EXJm7nzbhC6pMtBapT9a/MUPYFte38= +-----END RSA PRIVATE KEY----- diff --git a/integration/docker_engine_test.go b/integration/docker_engine_test.go new file mode 100644 index 000000000000..35122b51c90a --- /dev/null +++ b/integration/docker_engine_test.go @@ -0,0 +1,310 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/types" + "io" + "os" + "strings" + "testing" + + api "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDockerEngine(t *testing.T) { + if *update { + t.Skipf("This test doesn't update golden files") + } + tests := []struct { + name string + imageTag string + invalidImage bool + ignoreUnfixed bool + ignoreStatus []string + severity []string + ignoreIDs []string + input string + golden string + wantErr string + }{ + { + name: "alpine:3.9", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:alpine-39", + input: "testdata/fixtures/images/alpine-39.tar.gz", + golden: "testdata/alpine-39.json.golden", + }, + { + name: "alpine:3.9, with high and critical severity", + severity: []string{ + "HIGH", + "CRITICAL", + }, + imageTag: "ghcr.io/aquasecurity/trivy-test-images:alpine-39", + input: "testdata/fixtures/images/alpine-39.tar.gz", + golden: "testdata/alpine-39-high-critical.json.golden", + }, + { + name: "alpine:3.9, with .trivyignore", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:alpine-39", + ignoreIDs: []string{ + "CVE-2019-1549", + "CVE-2019-14697", + }, + input: "testdata/fixtures/images/alpine-39.tar.gz", + golden: "testdata/alpine-39-ignore-cveids.json.golden", + }, + { + name: "alpine:3.10", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", + input: "testdata/fixtures/images/alpine-310.tar.gz", + golden: "testdata/alpine-310.json.golden", + }, + { + name: "amazonlinux:1", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:amazon-1", + input: "testdata/fixtures/images/amazon-1.tar.gz", + golden: "testdata/amazon-1.json.golden", + }, + { + name: "amazonlinux:2", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:amazon-2", + input: "testdata/fixtures/images/amazon-2.tar.gz", + golden: "testdata/amazon-2.json.golden", + }, + { + name: "almalinux 8", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:almalinux-8", + input: "testdata/fixtures/images/almalinux-8.tar.gz", + golden: "testdata/almalinux-8.json.golden", + }, + { + name: "rocky linux 8", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:rockylinux-8", + input: "testdata/fixtures/images/rockylinux-8.tar.gz", + golden: "testdata/rockylinux-8.json.golden", + }, + { + name: "centos 6", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:centos-6", + input: "testdata/fixtures/images/centos-6.tar.gz", + golden: "testdata/centos-6.json.golden", + }, + { + name: "centos 7", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:centos-7", + input: "testdata/fixtures/images/centos-7.tar.gz", + golden: "testdata/centos-7.json.golden", + }, + { + name: "centos 7, with --ignore-unfixed option", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:centos-7", + ignoreUnfixed: true, + input: "testdata/fixtures/images/centos-7.tar.gz", + golden: "testdata/centos-7-ignore-unfixed.json.golden", + }, + { + name: "centos 7, with --ignore-status option", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:centos-7", + ignoreStatus: []string{"will_not_fix"}, + input: "testdata/fixtures/images/centos-7.tar.gz", + golden: "testdata/centos-7-ignore-unfixed.json.golden", + }, + { + name: "centos 7, with --ignore-unfixed option, with medium severity", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:centos-7", + ignoreUnfixed: true, + severity: []string{"MEDIUM"}, + input: "testdata/fixtures/images/centos-7.tar.gz", + golden: "testdata/centos-7-medium.json.golden", + }, + { + name: "registry.redhat.io/ubi7", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:ubi-7", + input: "testdata/fixtures/images/ubi-7.tar.gz", + golden: "testdata/ubi-7.json.golden", + }, + { + name: "debian buster/10", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:debian-buster", + input: "testdata/fixtures/images/debian-buster.tar.gz", + golden: "testdata/debian-buster.json.golden", + }, + { + name: "debian buster/10, with --ignore-unfixed option", + ignoreUnfixed: true, + imageTag: "ghcr.io/aquasecurity/trivy-test-images:debian-buster", + input: "testdata/fixtures/images/debian-buster.tar.gz", + golden: "testdata/debian-buster-ignore-unfixed.json.golden", + }, + { + name: "debian buster/10, with --ignore-status option", + ignoreStatus: []string{"affected"}, + imageTag: "ghcr.io/aquasecurity/trivy-test-images:debian-buster", + input: "testdata/fixtures/images/debian-buster.tar.gz", + golden: "testdata/debian-buster-ignore-unfixed.json.golden", + }, + { + name: "debian stretch/9", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:debian-stretch", + input: "testdata/fixtures/images/debian-stretch.tar.gz", + golden: "testdata/debian-stretch.json.golden", + }, + { + name: "distroless base", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:distroless-base", + input: "testdata/fixtures/images/distroless-base.tar.gz", + golden: "testdata/distroless-base.json.golden", + }, + { + name: "distroless python2.7", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:distroless-python27", + input: "testdata/fixtures/images/distroless-python27.tar.gz", + golden: "testdata/distroless-python27.json.golden", + }, + { + name: "oracle linux 8", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:oraclelinux-8", + input: "testdata/fixtures/images/oraclelinux-8.tar.gz", + golden: "testdata/oraclelinux-8.json.golden", + }, + { + name: "ubuntu 18.04", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:ubuntu-1804", + input: "testdata/fixtures/images/ubuntu-1804.tar.gz", + golden: "testdata/ubuntu-1804.json.golden", + }, + { + name: "ubuntu 18.04, with --ignore-unfixed option", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:ubuntu-1804", + ignoreUnfixed: true, + input: "testdata/fixtures/images/ubuntu-1804.tar.gz", + golden: "testdata/ubuntu-1804-ignore-unfixed.json.golden", + }, + { + name: "opensuse leap 15.1", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:opensuse-leap-151", + input: "testdata/fixtures/images/opensuse-leap-151.tar.gz", + golden: "testdata/opensuse-leap-151.json.golden", + }, + { + name: "photon 3.0", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:photon-30", + input: "testdata/fixtures/images/photon-30.tar.gz", + golden: "testdata/photon-30.json.golden", + }, + { + name: "CBL-Mariner 1.0", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:mariner-1.0", + input: "testdata/fixtures/images/mariner-1.0.tar.gz", + golden: "testdata/mariner-1.0.json.golden", + }, + { + name: "busybox with Cargo.lock", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:busybox-with-lockfile", + input: "testdata/fixtures/images/busybox-with-lockfile.tar.gz", + golden: "testdata/busybox-with-lockfile.json.golden", + }, + { + name: "sad path, invalid image", + invalidImage: true, + input: "badimage:latest", + wantErr: "unable to inspect the image (badimage:latest)", + }, + } + + // Set up testing DB + cacheDir := initDB(t) + + // Set a temp dir so that modules will not be loaded + t.Setenv("XDG_DATA_HOME", cacheDir) + + ctx := context.Background() + defer ctx.Done() + + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + require.NoError(t, err) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.invalidImage { + testfile, err := os.Open(tt.input) + require.NoError(t, err, tt.name) + + // ensure image doesnt already exists + _, _ = cli.ImageRemove(ctx, tt.input, api.ImageRemoveOptions{ + Force: true, + PruneChildren: true, + }) + + // load image into docker engine + res, err := cli.ImageLoad(ctx, testfile, true) + require.NoError(t, err, tt.name) + if _, err := io.Copy(io.Discard, res.Body); err != nil { + require.NoError(t, err, tt.name) + } + defer res.Body.Close() + + // tag our image to something unique + err = cli.ImageTag(ctx, tt.imageTag, tt.input) + require.NoError(t, err, tt.name) + + // cleanup + t.Cleanup(func() { + _, _ = cli.ImageRemove(ctx, tt.input, api.ImageRemoveOptions{ + Force: true, + PruneChildren: true, + }) + _, _ = cli.ImageRemove(ctx, tt.imageTag, api.ImageRemoveOptions{ + Force: true, + PruneChildren: true, + }) + }) + } + + osArgs := []string{ + "--cache-dir", + cacheDir, + "image", + "--skip-update", + "--format=json", + } + + if tt.ignoreUnfixed { + osArgs = append(osArgs, "--ignore-unfixed") + } + + if len(tt.ignoreStatus) != 0 { + osArgs = append(osArgs, + []string{ + "--ignore-status", + strings.Join(tt.ignoreStatus, ","), + }..., + ) + } + if len(tt.severity) != 0 { + osArgs = append(osArgs, + []string{ + "--severity", + strings.Join(tt.severity, ","), + }..., + ) + } + if len(tt.ignoreIDs) != 0 { + trivyIgnore := ".trivyignore" + err = os.WriteFile(trivyIgnore, []byte(strings.Join(tt.ignoreIDs, "\n")), 0444) + assert.NoError(t, err, "failed to write .trivyignore") + defer os.Remove(trivyIgnore) + } + osArgs = append(osArgs, tt.input) + + // Run Trivy + runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{wantErr: tt.wantErr}) + }) + } +} diff --git a/integration/integration_test.go b/integration/integration_test.go new file mode 100644 index 000000000000..43fe3ac8c820 --- /dev/null +++ b/integration/integration_test.go @@ -0,0 +1,309 @@ +//go:build integration || vm_integration || module_integration || k8s_integration + +package integration + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "io" + "net" + "os" + "path/filepath" + "sort" + "strings" + "testing" + "time" + + cdx "github.com/CycloneDX/cyclonedx-go" + "github.com/samber/lo" + spdxjson "github.com/spdx/tools-golang/json" + "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/spdxlib" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/xeipuuv/gojsonschema" + + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy-db/pkg/metadata" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/commands" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" + + _ "modernc.org/sqlite" +) + +var update = flag.Bool("update", false, "update golden files") + +const SPDXSchema = "https://raw.githubusercontent.com/spdx/spdx-spec/development/v%s/schemas/spdx-schema.json" + +func initDB(t *testing.T) string { + fixtureDir := filepath.Join("testdata", "fixtures", "db") + entries, err := os.ReadDir(fixtureDir) + require.NoError(t, err) + + var fixtures []string + for _, entry := range entries { + if entry.IsDir() { + continue + } + fixtures = append(fixtures, filepath.Join(fixtureDir, entry.Name())) + } + + cacheDir := dbtest.InitDB(t, fixtures) + defer db.Close() + + dbDir := filepath.Dir(db.Path(cacheDir)) + + metadataFile := filepath.Join(dbDir, "metadata.json") + f, err := os.Create(metadataFile) + require.NoError(t, err) + + err = json.NewEncoder(f).Encode(metadata.Metadata{ + Version: db.SchemaVersion, + NextUpdate: time.Now().Add(24 * time.Hour), + UpdatedAt: time.Now(), + }) + require.NoError(t, err) + + dbtest.InitJavaDB(t, cacheDir) + return cacheDir +} + +func getFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil +} + +func waitPort(ctx context.Context, addr string) error { + for { + conn, err := net.Dial("tcp", addr) + if err == nil && conn != nil { + return nil + } + select { + case <-ctx.Done(): + return err + default: + time.Sleep(1 * time.Second) + } + } +} + +func readReport(t *testing.T, filePath string) types.Report { + t.Helper() + + f, err := os.Open(filePath) + require.NoError(t, err, filePath) + defer f.Close() + + var report types.Report + err = json.NewDecoder(f).Decode(&report) + require.NoError(t, err, filePath) + + // We don't compare history because the nano-seconds in "created" don't match + report.Metadata.ImageConfig.History = nil + + // We don't compare repo tags because the archive doesn't support it + report.Metadata.RepoTags = nil + report.Metadata.RepoDigests = nil + + for i, result := range report.Results { + for j := range result.Vulnerabilities { + report.Results[i].Vulnerabilities[j].Layer.Digest = "" + } + + sort.Slice(result.CustomResources, func(i, j int) bool { + if result.CustomResources[i].Type != result.CustomResources[j].Type { + return result.CustomResources[i].Type < result.CustomResources[j].Type + } + return result.CustomResources[i].FilePath < result.CustomResources[j].FilePath + }) + } + + return report +} + +func readCycloneDX(t *testing.T, filePath string) *cdx.BOM { + f, err := os.Open(filePath) + require.NoError(t, err) + defer f.Close() + + bom := cdx.NewBOM() + decoder := cdx.NewBOMDecoder(f, cdx.BOMFileFormatJSON) + err = decoder.Decode(bom) + require.NoError(t, err) + + // Sort components + if bom.Components != nil { + sort.Slice(*bom.Components, func(i, j int) bool { + return (*bom.Components)[i].Name < (*bom.Components)[j].Name + }) + for i := range *bom.Components { + (*bom.Components)[i].BOMRef = "" + sort.Slice(*(*bom.Components)[i].Properties, func(ii, jj int) bool { + return (*(*bom.Components)[i].Properties)[ii].Name < (*(*bom.Components)[i].Properties)[jj].Name + }) + } + sort.Slice(*bom.Vulnerabilities, func(i, j int) bool { + return (*bom.Vulnerabilities)[i].ID < (*bom.Vulnerabilities)[j].ID + }) + } + + return bom +} + +func readSpdxJson(t *testing.T, filePath string) *spdx.Document { + f, err := os.Open(filePath) + require.NoError(t, err) + defer f.Close() + + bom, err := spdxjson.Read(f) + require.NoError(t, err) + + sort.Slice(bom.Relationships, func(i, j int) bool { + if bom.Relationships[i].RefA.ElementRefID != bom.Relationships[j].RefA.ElementRefID { + return bom.Relationships[i].RefA.ElementRefID < bom.Relationships[j].RefA.ElementRefID + } + return bom.Relationships[i].RefB.ElementRefID < bom.Relationships[j].RefB.ElementRefID + }) + + sort.Slice(bom.Files, func(i, j int) bool { + return bom.Files[i].FileSPDXIdentifier < bom.Files[j].FileSPDXIdentifier + }) + + // We don't compare values which change each time an SBOM is generated + bom.CreationInfo.Created = "" + bom.DocumentNamespace = "" + + return bom +} + +type runOptions struct { + wantErr string + override func(want, got *types.Report) + fakeUUID string +} + +// runTest runs Trivy with the given args and compares the output with the golden file. +// If outputFile is empty, the output file is created in a temporary directory. +// If update is true, the golden file is updated. +func runTest(t *testing.T, osArgs []string, wantFile, outputFile string, format types.Format, opts runOptions) { + if opts.fakeUUID != "" { + uuid.SetFakeUUID(t, opts.fakeUUID) + } + + if outputFile == "" { + // Set up the output file + outputFile = filepath.Join(t.TempDir(), "output.json") + if *update && opts.override == nil { + outputFile = wantFile + } + } + osArgs = append(osArgs, "--output", outputFile) + + // Run Trivy + err := execute(osArgs) + if opts.wantErr != "" { + require.ErrorContains(t, err, opts.wantErr) + return + } + require.NoError(t, err) + + // Compare want and got + switch format { + case types.FormatCycloneDX: + compareCycloneDX(t, wantFile, outputFile) + case types.FormatSPDXJSON: + compareSPDXJson(t, wantFile, outputFile) + case types.FormatJSON: + compareReports(t, wantFile, outputFile, opts.override) + case types.FormatTemplate, types.FormatSarif, types.FormatGitHub: + compareRawFiles(t, wantFile, outputFile) + default: + require.Fail(t, "invalid format", "format: %s", format) + } +} + +func execute(osArgs []string) error { + // viper.XXX() (e.g. viper.ReadInConfig()) affects the global state, so we need to reset it after each test. + defer viper.Reset() + + // Set a fake time + ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) + + // Setup CLI App + app := commands.NewApp() + app.SetOut(io.Discard) + app.SetArgs(osArgs) + + // Run Trivy + return app.ExecuteContext(ctx) +} + +func compareRawFiles(t *testing.T, wantFile, gotFile string) { + want, err := os.ReadFile(wantFile) + require.NoError(t, err) + got, err := os.ReadFile(gotFile) + require.NoError(t, err) + assert.EqualValues(t, string(want), string(got)) +} + +func compareReports(t *testing.T, wantFile, gotFile string, override func(want, got *types.Report)) { + want := readReport(t, wantFile) + got := readReport(t, gotFile) + if override != nil { + override(&want, &got) + } + assert.Equal(t, want, got) +} + +func compareCycloneDX(t *testing.T, wantFile, gotFile string) { + want := readCycloneDX(t, wantFile) + got := readCycloneDX(t, gotFile) + assert.Equal(t, want, got) + + // Validate CycloneDX output against the JSON schema + validateReport(t, got.JSONSchema, got) +} + +func compareSPDXJson(t *testing.T, wantFile, gotFile string) { + want := readSpdxJson(t, wantFile) + got := readSpdxJson(t, gotFile) + assert.Equal(t, want, got) + + SPDXVersion, ok := strings.CutPrefix(want.SPDXVersion, "SPDX-") + assert.True(t, ok) + + assert.NoError(t, spdxlib.ValidateDocument(got)) + + // Validate SPDX output against the JSON schema + validateReport(t, fmt.Sprintf(SPDXSchema, SPDXVersion), got) +} + +func validateReport(t *testing.T, schema string, report any) { + schemaLoader := gojsonschema.NewReferenceLoader(schema) + documentLoader := gojsonschema.NewGoLoader(report) + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + require.NoError(t, err) + + if valid := result.Valid(); !valid { + errs := lo.Map(result.Errors(), func(err gojsonschema.ResultError, _ int) string { + return err.String() + }) + assert.True(t, valid, strings.Join(errs, "\n")) + } +} diff --git a/integration/k8s_test.go b/integration/k8s_test.go new file mode 100644 index 000000000000..62a0bbd2d526 --- /dev/null +++ b/integration/k8s_test.go @@ -0,0 +1,161 @@ +//go:build k8s_integration + +package integration + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + cdx "github.com/CycloneDX/cyclonedx-go" + "github.com/aquasecurity/trivy/pkg/k8s/report" + "github.com/aquasecurity/trivy/pkg/types" + + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Note: the test required k8s (kind) cluster installed. +// "mage test:k8s" will run this test. + +func TestK8s(t *testing.T) { + // Set up testing DB + cacheDir := initDB(t) + t.Run("misconfig and vulnerability scan", func(t *testing.T) { + // Set up the output file + outputFile := filepath.Join(t.TempDir(), "output.json") + + osArgs := []string{ + "--cache-dir", + cacheDir, + "k8s", + "cluster", + "--report", + "summary", + "-q", + "--timeout", + "5m0s", + "--format", + "json", + "--components", + "workload", + "--context", + "kind-kind-test", + "--output", + outputFile, + } + + // Run Trivy + err := execute(osArgs) + require.NoError(t, err) + + var got report.ConsolidatedReport + f, err := os.Open(outputFile) + require.NoError(t, err) + defer f.Close() + + err = json.NewDecoder(f).Decode(&got) + require.NoError(t, err) + + // Flatten findings + results := lo.FlatMap(got.Findings, func(resource report.Resource, _ int) []types.Result { + return resource.Results + }) + + // Has vulnerabilities + assert.True(t, lo.SomeBy(results, func(r types.Result) bool { + return len(r.Vulnerabilities) > 0 + })) + + // Has misconfigurations + assert.True(t, lo.SomeBy(results, func(r types.Result) bool { + return len(r.Misconfigurations) > 0 + })) + }) + t.Run("kbom cycloneDx", func(t *testing.T) { + // Set up the output file + outputFile := filepath.Join(t.TempDir(), "output.json") + osArgs := []string{ + "k8s", + "cluster", + "--format", + "cyclonedx", + "-q", + "--context", + "kind-kind-test", + "--output", + outputFile, + } + + // Run Trivy + err := execute(osArgs) + require.NoError(t, err) + + var got *cdx.BOM + f, err := os.Open(outputFile) + require.NoError(t, err) + defer f.Close() + + err = json.NewDecoder(f).Decode(&got) + require.NoError(t, err) + + assert.Equal(t, got.Metadata.Component.Name, "k8s.io/kubernetes") + assert.Equal(t, got.Metadata.Component.Type, cdx.ComponentType("platform")) + + // Has components + assert.True(t, len(*got.Components) > 0) + + // Has dependecies + assert.True(t, lo.SomeBy(*got.Dependencies, func(r cdx.Dependency) bool { + return len(*r.Dependencies) > 0 + })) + + }) + + t.Run("specific resource scan", func(t *testing.T) { + // Set up the output file + outputFile := filepath.Join(t.TempDir(), "output.json") + + osArgs := []string{ + "k8s", + "-n", + "default", + "deployments/nginx-deployment", + "-q", + "--timeout", + "5m0s", + "--format", + "json", + "--components", + "workload", + "--context", + "kind-kind-test", + "--output", + outputFile, + } + + // Run Trivy + err := execute(osArgs) + require.NoError(t, err) + + var got report.Report + f, err := os.Open(outputFile) + require.NoError(t, err) + defer f.Close() + + err = json.NewDecoder(f).Decode(&got) + require.NoError(t, err) + + // Flatten findings + results := lo.FlatMap(got.Resources, func(resource report.Resource, _ int) []types.Result { + return resource.Results + }) + + // Has vulnerabilities + assert.True(t, lo.SomeBy(results, func(r types.Result) bool { + return len(r.Vulnerabilities) > 0 + })) + }) +} diff --git a/integration/module_test.go b/integration/module_test.go new file mode 100644 index 000000000000..16745da1c8fd --- /dev/null +++ b/integration/module_test.go @@ -0,0 +1,62 @@ +//go:build module_integration + +package integration + +import ( + "github.com/aquasecurity/trivy/pkg/types" + "path/filepath" + "testing" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/scanner/post" +) + +func TestModule(t *testing.T) { + tests := []struct { + name string + input string + golden string + }{ + { + name: "spring4shell jre 8, severity update", + input: "testdata/fixtures/images/spring4shell-jre8.tar.gz", + golden: "testdata/spring4shell-jre8.json.golden", + }, + { + name: "spring4shell jre 11, no severity update", + input: "testdata/fixtures/images/spring4shell-jre11.tar.gz", + golden: "testdata/spring4shell-jre11.json.golden", + }, + } + + // Set up testing DB + cacheDir := initDB(t) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osArgs := []string{ + "--cache-dir", + cacheDir, + "image", + "--ignore-unfixed", + "--format", + "json", + "--skip-db-update", + "--offline-scan", + "--quiet", + "--module-dir", + filepath.Join("../", "examples", "module", "spring4shell"), + "--input", + tt.input, + } + + t.Cleanup(func() { + analyzer.DeregisterAnalyzer("spring4shell") + post.DeregisterPostScanner("spring4shell") + }) + + // Run Trivy + runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{}) + }) + } +} diff --git a/integration/registry_test.go b/integration/registry_test.go new file mode 100644 index 000000000000..b62865667dc3 --- /dev/null +++ b/integration/registry_test.go @@ -0,0 +1,337 @@ +//go:build integration + +package integration + +import ( + "bytes" + "compress/gzip" + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "github.com/aquasecurity/trivy/pkg/types" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "testing" + + dockercontainer "github.com/docker/docker/api/types/container" + "github.com/docker/go-connections/nat" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + registryImage = "registry:2.7.0" + registryPort = "5443/tcp" + + authImage = "cesanta/docker_auth:1" + authPort = "5001/tcp" + authUsername = "admin" + authPassword = "badmin" +) + +func setupRegistry(ctx context.Context, baseDir string, authURL *url.URL) (testcontainers.Container, error) { + req := testcontainers.ContainerRequest{ + Name: "registry", + Image: registryImage, + ExposedPorts: []string{registryPort}, + Env: map[string]string{ + "REGISTRY_HTTP_ADDR": "0.0.0.0:5443", + "REGISTRY_HTTP_TLS_CERTIFICATE": "/certs/cert.pem", + "REGISTRY_HTTP_TLS_KEY": "/certs/key.pem", + "REGISTRY_AUTH": "token", + "REGISTRY_AUTH_TOKEN_REALM": fmt.Sprintf("%s/auth", authURL), + "REGISTRY_AUTH_TOKEN_SERVICE": "registry.docker.io", + "REGISTRY_AUTH_TOKEN_ISSUER": "Trivy auth server", + "REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE": "/certs/cert.pem", + "REGISTRY_AUTH_TOKEN_AUTOREDIRECT": "false", + }, + Mounts: testcontainers.Mounts( + testcontainers.BindMount(filepath.Join(baseDir, "data", "certs"), "/certs"), + ), + HostConfigModifier: func(hostConfig *dockercontainer.HostConfig) { + hostConfig.AutoRemove = true + }, + WaitingFor: wait.ForHTTP("v2").WithTLS(true).WithAllowInsecure(true). + WithStatusCodeMatcher(func(status int) bool { + return status == http.StatusUnauthorized + }), + } + + registryC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + return registryC, err +} + +func setupAuthServer(ctx context.Context, baseDir string) (testcontainers.Container, error) { + req := testcontainers.ContainerRequest{ + Name: "docker_auth", + Image: authImage, + ExposedPorts: []string{authPort}, + Mounts: testcontainers.Mounts( + testcontainers.BindMount(filepath.Join(baseDir, "data", "auth_config"), "/config"), + testcontainers.BindMount(filepath.Join(baseDir, "data", "certs"), "/certs"), + ), + HostConfigModifier: func(hostConfig *dockercontainer.HostConfig) { + hostConfig.AutoRemove = true + }, + Cmd: []string{"/config/config.yml"}, + } + + authC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + return authC, err +} + +func getURL(ctx context.Context, container testcontainers.Container, exposedPort nat.Port) (*url.URL, error) { + ip, err := container.Host(ctx) + if err != nil { + return nil, err + } + + port, err := container.MappedPort(ctx, exposedPort) + if err != nil { + return nil, err + } + + urlStr := fmt.Sprintf("https://%s:%s", ip, port.Port()) + return url.Parse(urlStr) +} + +type registryOption struct { + AuthURL *url.URL + Username string + Password string + RegistryToken bool +} + +func TestRegistry(t *testing.T) { + ctx := context.Background() + + baseDir, err := filepath.Abs(".") + require.NoError(t, err) + + // disable Reaper for auth server and registry containers + t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") + + // set up auth server + authC, err := setupAuthServer(ctx, baseDir) + require.NoError(t, err) + defer authC.Terminate(ctx) + + authURL, err := getURL(ctx, authC, authPort) + require.NoError(t, err) + + // set up registry + registryC, err := setupRegistry(ctx, baseDir, authURL) + require.NoError(t, err) + defer registryC.Terminate(ctx) + + registryURL, err := getURL(ctx, registryC, registryPort) + require.NoError(t, err) + + auth := &authn.Basic{ + Username: authUsername, + Password: authPassword, + } + + tests := []struct { + name string + imageName string + imageFile string + option registryOption + golden string + wantErr string + }{ + { + name: "happy path with username/password", + imageName: "alpine:3.10", + imageFile: "testdata/fixtures/images/alpine-310.tar.gz", + option: registryOption{ + AuthURL: authURL, + Username: authUsername, + Password: authPassword, + }, + golden: "testdata/alpine-310-registry.json.golden", + }, + { + name: "happy path with registry token", + imageName: "alpine:3.10", + imageFile: "testdata/fixtures/images/alpine-310.tar.gz", + option: registryOption{ + AuthURL: authURL, + Username: authUsername, + Password: authPassword, + RegistryToken: true, + }, + golden: "testdata/alpine-310-registry.json.golden", + }, + { + name: "sad path", + imageName: "alpine:3.10", + imageFile: "testdata/fixtures/images/alpine-310.tar.gz", + wantErr: "unexpected status code 401 Unauthorized: Auth failed", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + s := fmt.Sprintf("%s/%s", registryURL.Host, tc.imageName) + imageRef, err := name.ParseReference(s) + require.NoError(t, err) + + // Load a test image from the tar file, tag it and push to the test registry. + err = replicateImage(imageRef, tc.imageFile, auth) + require.NoError(t, err) + + osArgs, err := scan(t, imageRef, baseDir, tc.golden, tc.option) + + // Run Trivy + runTest(t, osArgs, tc.golden, "", types.FormatJSON, runOptions{ + wantErr: tc.wantErr, + override: func(_, got *types.Report) { + got.ArtifactName = tc.imageName + for i := range got.Results { + got.Results[i].Target = fmt.Sprintf("%s (alpine 3.10.2)", tc.imageName) + } + }, + }) + }) + } +} + +func scan(t *testing.T, imageRef name.Reference, baseDir, goldenFile string, opt registryOption) ([]string, error) { + // Set up testing DB + cacheDir := initDB(t) + + // Set a temp dir so that modules will not be loaded + t.Setenv("XDG_DATA_HOME", cacheDir) + + // Setup env + if err := setupEnv(t, imageRef, baseDir, opt); err != nil { + return nil, err + } + + osArgs := []string{ + "-q", + "--cache-dir", + cacheDir, + "image", + "--format", + "json", + "--skip-update", + imageRef.Name(), + } + + return osArgs, nil +} + +func setupEnv(t *testing.T, imageRef name.Reference, baseDir string, opt registryOption) error { + t.Setenv("TRIVY_INSECURE", "true") + + if opt.Username != "" && opt.Password != "" { + if opt.RegistryToken { + // Get a registry token in advance + token, err := requestRegistryToken(imageRef, baseDir, opt) + if err != nil { + return err + } + t.Setenv("TRIVY_REGISTRY_TOKEN", token) + } else { + t.Setenv("TRIVY_USERNAME", opt.Username) + t.Setenv("TRIVY_PASSWORD", opt.Password) + } + } + return nil +} + +func requestRegistryToken(imageRef name.Reference, baseDir string, opt registryOption) (string, error) { + // Create a CA certificate pool and add cert.pem to it + caCert, err := os.ReadFile(filepath.Join(baseDir, "data", "certs", "cert.pem")) + if err != nil { + return "", err + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + // Create a HTTPS client and supply the created CA pool + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: caCertPool, + }, + }, + } + + // Get a registry token + req, err := http.NewRequest("GET", fmt.Sprintf("%s/auth", opt.AuthURL), nil) + if err != nil { + return "", err + } + + // Set query parameters + values := req.URL.Query() + values.Set("service", "registry.docker.io") + values.Set("scope", imageRef.Scope("pull")) + req.URL.RawQuery = values.Encode() + + req.SetBasicAuth(opt.Username, opt.Password) + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + type res struct { + AccessToken string `json:"access_token"` + } + + var r res + if err = json.NewDecoder(resp.Body).Decode(&r); err != nil { + return "", err + } + + return r.AccessToken, nil +} + +// ReplicateImage tags the given imagePath and pushes it to the given dest registry. +func replicateImage(imageRef name.Reference, imagePath string, auth authn.Authenticator) error { + img, err := tarball.Image(func() (io.ReadCloser, error) { + b, err := os.ReadFile(imagePath) + if err != nil { + return nil, err + } + gr, err := gzip.NewReader(bytes.NewReader(b)) + if err != nil { + return nil, err + } + return io.NopCloser(gr), nil + }, nil) + if err != nil { + return err + } + + t := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + err = remote.Write(imageRef, img, remote.WithAuth(auth), remote.WithTransport(t)) + if err != nil { + return err + } + + return nil +} diff --git a/integration/repo_test.go b/integration/repo_test.go new file mode 100644 index 000000000000..ba11aa9ccb0f --- /dev/null +++ b/integration/repo_test.go @@ -0,0 +1,496 @@ +//go:build integration + +package integration + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "os" + "strings" + "testing" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +// TestRepository tests `trivy repo` with the local code repositories +func TestRepository(t *testing.T) { + type args struct { + scanner types.Scanner + ignoreIDs []string + policyPaths []string + namespaces []string + listAllPkgs bool + input string + secretConfig string + filePatterns []string + helmSet []string + helmValuesFile []string + skipFiles []string + skipDirs []string + command string + format types.Format + includeDevDeps bool + parallel int + } + tests := []struct { + name string + args args + golden string + override func(want, got *types.Report) + }{ + { + name: "gomod", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/gomod", + }, + golden: "testdata/gomod.json.golden", + }, + { + name: "gomod with skip files", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/gomod", + skipFiles: []string{"testdata/fixtures/repo/gomod/submod2/go.mod"}, + }, + golden: "testdata/gomod-skip.json.golden", + }, + { + name: "gomod with skip dirs", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/gomod", + skipDirs: []string{"testdata/fixtures/repo/gomod/submod2"}, + }, + golden: "testdata/gomod-skip.json.golden", + }, + { + name: "gomod in series", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/gomod", + parallel: 1, + }, + golden: "testdata/gomod.json.golden", + }, + { + name: "npm", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/npm", + listAllPkgs: true, + }, + golden: "testdata/npm.json.golden", + }, + { + name: "npm with dev deps", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/npm", + listAllPkgs: true, + includeDevDeps: true, + }, + golden: "testdata/npm-with-dev.json.golden", + }, + { + name: "yarn", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/yarn", + listAllPkgs: true, + }, + golden: "testdata/yarn.json.golden", + }, + { + name: "pnpm", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/pnpm", + }, + golden: "testdata/pnpm.json.golden", + }, + { + name: "pip", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/pip", + }, + golden: "testdata/pip.json.golden", + }, + { + name: "pipenv", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/pipenv", + }, + golden: "testdata/pipenv.json.golden", + }, + { + name: "poetry", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/poetry", + }, + golden: "testdata/poetry.json.golden", + }, + { + name: "pom", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/pom", + }, + golden: "testdata/pom.json.golden", + }, + { + name: "gradle", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/gradle", + }, + golden: "testdata/gradle.json.golden", + }, + { + name: "conan", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/conan", + }, + golden: "testdata/conan.json.golden", + }, + { + name: "nuget", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/nuget", + }, + golden: "testdata/nuget.json.golden", + }, + { + name: "dotnet", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/dotnet", + }, + golden: "testdata/dotnet.json.golden", + }, + { + name: "packages-props", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/packagesprops", + }, + golden: "testdata/packagesprops.json.golden", + }, + { + name: "swift", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/swift", + }, + golden: "testdata/swift.json.golden", + }, + { + name: "cocoapods", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/cocoapods", + }, + golden: "testdata/cocoapods.json.golden", + }, + { + name: "pubspec.lock", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/pubspec", + }, + golden: "testdata/pubspec.lock.json.golden", + }, + { + name: "mix.lock", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/mixlock", + }, + golden: "testdata/mix.lock.json.golden", + }, + { + name: "composer.lock", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/composer", + }, + golden: "testdata/composer.lock.json.golden", + }, + { + name: "dockerfile", + args: args{ + scanner: types.MisconfigScanner, + input: "testdata/fixtures/repo/dockerfile", + namespaces: []string{"testing"}, + }, + golden: "testdata/dockerfile.json.golden", + }, + { + name: "dockerfile with custom file pattern", + args: args{ + scanner: types.MisconfigScanner, + input: "testdata/fixtures/repo/dockerfile_file_pattern", + namespaces: []string{"testing"}, + filePatterns: []string{"dockerfile:Customfile"}, + }, + golden: "testdata/dockerfile_file_pattern.json.golden", + }, + { + name: "dockerfile with rule exception", + args: args{ + scanner: types.MisconfigScanner, + policyPaths: []string{"testdata/fixtures/repo/rule-exception/policy"}, + input: "testdata/fixtures/repo/rule-exception", + }, + golden: "testdata/dockerfile-rule-exception.json.golden", + }, + { + name: "dockerfile with namespace exception", + args: args{ + scanner: types.MisconfigScanner, + policyPaths: []string{"testdata/fixtures/repo/namespace-exception/policy"}, + input: "testdata/fixtures/repo/namespace-exception", + }, + golden: "testdata/dockerfile-namespace-exception.json.golden", + }, + { + name: "dockerfile with custom policies", + args: args{ + scanner: types.MisconfigScanner, + policyPaths: []string{"testdata/fixtures/repo/custom-policy/policy"}, + namespaces: []string{"user"}, + input: "testdata/fixtures/repo/custom-policy", + }, + golden: "testdata/dockerfile-custom-policies.json.golden", + }, + { + name: "tarball helm chart scanning with builtin policies", + args: args{ + scanner: types.MisconfigScanner, + input: "testdata/fixtures/repo/helm", + }, + golden: "testdata/helm.json.golden", + }, + { + name: "helm chart directory scanning with builtin policies", + args: args{ + scanner: types.MisconfigScanner, + input: "testdata/fixtures/repo/helm_testchart", + }, + golden: "testdata/helm_testchart.json.golden", + }, + { + name: "helm chart directory scanning with value overrides using set", + args: args{ + scanner: types.MisconfigScanner, + input: "testdata/fixtures/repo/helm_testchart", + helmSet: []string{"securityContext.runAsUser=0"}, + }, + golden: "testdata/helm_testchart.overridden.json.golden", + }, + { + name: "helm chart directory scanning with value overrides using value file", + args: args{ + scanner: types.MisconfigScanner, + input: "testdata/fixtures/repo/helm_testchart", + helmValuesFile: []string{"testdata/fixtures/repo/helm_values/values.yaml"}, + }, + golden: "testdata/helm_testchart.overridden.json.golden", + }, + { + name: "helm chart directory scanning with builtin policies and non string Chart name", + args: args{ + scanner: types.MisconfigScanner, + input: "testdata/fixtures/repo/helm_badname", + }, + golden: "testdata/helm_badname.json.golden", + }, + { + name: "secrets", + args: args{ + scanner: "vuln,secret", + input: "testdata/fixtures/repo/secrets", + secretConfig: "testdata/fixtures/repo/secrets/trivy-secret.yaml", + }, + golden: "testdata/secrets.json.golden", + }, + { + name: "conda generating CycloneDX SBOM", + args: args{ + command: "rootfs", + format: "cyclonedx", + input: "testdata/fixtures/repo/conda", + }, + golden: "testdata/conda-cyclonedx.json.golden", + }, + { + name: "pom.xml generating CycloneDX SBOM (with vulnerabilities)", + args: args{ + command: "fs", + scanner: types.VulnerabilityScanner, + format: "cyclonedx", + input: "testdata/fixtures/repo/pom", + }, + golden: "testdata/pom-cyclonedx.json.golden", + }, + { + name: "conda generating SPDX SBOM", + args: args{ + command: "rootfs", + format: "spdx-json", + input: "testdata/fixtures/repo/conda", + }, + golden: "testdata/conda-spdx.json.golden", + }, + { + name: "gomod with fs subcommand", + args: args{ + command: "fs", + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/gomod", + skipFiles: []string{"testdata/fixtures/repo/gomod/submod2/go.mod"}, + }, + golden: "testdata/gomod-skip.json.golden", + override: func(want, _ *types.Report) { + want.ArtifactType = ftypes.ArtifactFilesystem + }, + }, + { + name: "dockerfile with fs subcommand and an alias scanner", + args: args{ + command: "fs", + scanner: "config", // for backward compatibility + policyPaths: []string{"testdata/fixtures/repo/custom-policy/policy"}, + namespaces: []string{"user"}, + input: "testdata/fixtures/repo/custom-policy", + }, + golden: "testdata/dockerfile-custom-policies.json.golden", + override: func(want, got *types.Report) { + want.ArtifactType = ftypes.ArtifactFilesystem + }, + }, + } + + // Set up testing DB + cacheDir := initDB(t) + + // Set a temp dir so that modules will not be loaded + t.Setenv("XDG_DATA_HOME", cacheDir) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + command := "repo" + if tt.args.command != "" { + command = tt.args.command + } + + format := types.FormatJSON + if tt.args.format != "" { + format = tt.args.format + } + + osArgs := []string{ + "-q", + "--cache-dir", + cacheDir, + command, + "--skip-db-update", + "--skip-policy-update", + "--format", + string(format), + "--parallel", + fmt.Sprint(tt.args.parallel), + "--offline-scan", + tt.args.input, + } + + if tt.args.scanner != "" { + osArgs = append(osArgs, "--scanners", string(tt.args.scanner)) + } + + if len(tt.args.policyPaths) != 0 { + for _, policyPath := range tt.args.policyPaths { + osArgs = append(osArgs, "--config-policy", policyPath) + } + } + + if len(tt.args.namespaces) != 0 { + for _, namespace := range tt.args.namespaces { + osArgs = append(osArgs, "--policy-namespaces", namespace) + } + } + + if len(tt.args.ignoreIDs) != 0 { + trivyIgnore := ".trivyignore" + err := os.WriteFile(trivyIgnore, []byte(strings.Join(tt.args.ignoreIDs, "\n")), 0444) + assert.NoError(t, err, "failed to write .trivyignore") + defer os.Remove(trivyIgnore) + } + + if len(tt.args.filePatterns) != 0 { + for _, filePattern := range tt.args.filePatterns { + osArgs = append(osArgs, "--file-patterns", filePattern) + } + } + + if len(tt.args.helmSet) != 0 { + for _, helmSet := range tt.args.helmSet { + osArgs = append(osArgs, "--helm-set", helmSet) + } + } + + if len(tt.args.helmValuesFile) != 0 { + for _, helmValuesFile := range tt.args.helmValuesFile { + osArgs = append(osArgs, "--helm-values", helmValuesFile) + } + } + + if len(tt.args.skipFiles) != 0 { + for _, skipFile := range tt.args.skipFiles { + osArgs = append(osArgs, "--skip-files", skipFile) + } + } + + if len(tt.args.skipDirs) != 0 { + for _, skipDir := range tt.args.skipDirs { + osArgs = append(osArgs, "--skip-dirs", skipDir) + } + } + + if tt.args.listAllPkgs { + osArgs = append(osArgs, "--list-all-pkgs") + } + + if tt.args.includeDevDeps { + osArgs = append(osArgs, "--include-dev-deps") + } + + if tt.args.secretConfig != "" { + osArgs = append(osArgs, "--secret-config", tt.args.secretConfig) + } + + runTest(t, osArgs, tt.golden, "", format, runOptions{ + fakeUUID: "3ff14136-e09f-4df9-80ea-%012d", + override: tt.override, + }) + }) + } +} diff --git a/integration/sbom_test.go b/integration/sbom_test.go new file mode 100644 index 000000000000..65c99f9e9600 --- /dev/null +++ b/integration/sbom_test.go @@ -0,0 +1,250 @@ +//go:build integration + +package integration + +import ( + "path/filepath" + "testing" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestSBOM(t *testing.T) { + type args struct { + input string + format string + artifactType string + scanners string + } + tests := []struct { + name string + args args + golden string + override types.Report + }{ + { + name: "centos7 cyclonedx", + args: args{ + input: "testdata/fixtures/sbom/centos-7-cyclonedx.json", + format: "json", + artifactType: "cyclonedx", + }, + golden: "testdata/centos-7.json.golden", + override: types.Report{ + ArtifactName: "testdata/fixtures/sbom/centos-7-cyclonedx.json", + ArtifactType: ftypes.ArtifactType("cyclonedx"), + Results: types.Results{ + { + Target: "testdata/fixtures/sbom/centos-7-cyclonedx.json (centos 7.6.1810)", + Vulnerabilities: []types.DetectedVulnerability{ + { + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810", + }, + }, + { + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", + }, + }, + { + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", + }, + }, + }, + }, + }, + }, + }, + { + name: "fluentd-multiple-lockfiles cyclonedx", + args: args{ + input: "testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json", + format: "json", + artifactType: "cyclonedx", + }, + golden: "testdata/fluentd-multiple-lockfiles.json.golden", + }, + { + name: "minikube KBOM", + args: args{ + input: "testdata/fixtures/sbom/minikube-kbom.json", + format: "json", + artifactType: "cyclonedx", + }, + golden: "testdata/minikube-kbom.json.golden", + }, + { + name: "centos7 in in-toto attestation", + args: args{ + input: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl", + format: "json", + artifactType: "cyclonedx", + }, + golden: "testdata/centos-7.json.golden", + override: types.Report{ + ArtifactName: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl", + ArtifactType: ftypes.ArtifactType("cyclonedx"), + Results: types.Results{ + { + Target: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl (centos 7.6.1810)", + Vulnerabilities: []types.DetectedVulnerability{ + { + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810", + }, + }, + { + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", + }, + }, + { + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", + }, + }, + }, + }, + }, + }, + }, + { + name: "centos7 spdx tag-value", + args: args{ + input: "testdata/fixtures/sbom/centos-7-spdx.txt", + format: "json", + artifactType: "spdx", + }, + golden: "testdata/centos-7.json.golden", + override: types.Report{ + ArtifactName: "testdata/fixtures/sbom/centos-7-spdx.txt", + ArtifactType: ftypes.ArtifactType("spdx"), + Results: types.Results{ + { + Target: "testdata/fixtures/sbom/centos-7-spdx.txt (centos 7.6.1810)", + }, + }, + }, + }, + { + name: "centos7 spdx json", + args: args{ + input: "testdata/fixtures/sbom/centos-7-spdx.json", + format: "json", + artifactType: "spdx", + }, + golden: "testdata/centos-7.json.golden", + override: types.Report{ + ArtifactName: "testdata/fixtures/sbom/centos-7-spdx.json", + ArtifactType: ftypes.ArtifactType("spdx"), + Results: types.Results{ + { + Target: "testdata/fixtures/sbom/centos-7-spdx.json (centos 7.6.1810)", + }, + }, + }, + }, + { + name: "license check cyclonedx json", + args: args{ + input: "testdata/fixtures/sbom/license-cyclonedx.json", + format: "json", + artifactType: "cyclonedx", + scanners: "license", + }, + golden: "testdata/license-cyclonedx.json.golden", + }, + } + + // Set up testing DB + cacheDir := initDB(t) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + scanners := "vuln" + if tt.args.scanners != "" { + scanners = tt.args.scanners + } + + osArgs := []string{ + "--cache-dir", + cacheDir, + "sbom", + "-q", + "--skip-db-update", + "--format", + tt.args.format, + "--scanners", + scanners, + } + + // Set up the output file + outputFile := filepath.Join(t.TempDir(), "output.json") + if *update { + outputFile = tt.golden + } + + osArgs = append(osArgs, "--output", outputFile) + osArgs = append(osArgs, tt.args.input) + + // Run "trivy sbom" + err := execute(osArgs) + assert.NoError(t, err) + + // Compare want and got + switch tt.args.format { + case "json": + compareSBOMReports(t, tt.golden, outputFile, tt.override) + default: + require.Fail(t, "invalid format", "format: %s", tt.args.format) + } + }) + } +} + +// TODO(teppei): merge into compareReports +func compareSBOMReports(t *testing.T, wantFile, gotFile string, overrideWant types.Report) { + want := readReport(t, wantFile) + + if overrideWant.ArtifactName != "" { + want.ArtifactName = overrideWant.ArtifactName + } + if overrideWant.ArtifactType != "" { + want.ArtifactType = overrideWant.ArtifactType + } + want.Metadata.ImageID = "" + want.Metadata.ImageConfig = v1.ConfigFile{} + want.Metadata.DiffIDs = nil + for i, result := range want.Results { + for j := range result.Vulnerabilities { + want.Results[i].Vulnerabilities[j].Layer.DiffID = "" + } + } + + for i, result := range overrideWant.Results { + want.Results[i].Target = result.Target + for j, vuln := range result.Vulnerabilities { + if vuln.PkgIdentifier.PURL != nil { + want.Results[i].Vulnerabilities[j].PkgIdentifier.PURL = vuln.PkgIdentifier.PURL + } + if vuln.PkgIdentifier.BOMRef != "" { + want.Results[i].Vulnerabilities[j].PkgIdentifier.BOMRef = vuln.PkgIdentifier.BOMRef + } + } + } + + got := readReport(t, gotFile) + // when running on Windows FS + got.ArtifactName = filepath.ToSlash(filepath.Clean(got.ArtifactName)) + for i, result := range got.Results { + got.Results[i].Target = filepath.ToSlash(filepath.Clean(result.Target)) + } + assert.Equal(t, want, got) +} diff --git a/integration/standalone_tar_test.go b/integration/standalone_tar_test.go new file mode 100644 index 000000000000..67cd869ebf1a --- /dev/null +++ b/integration/standalone_tar_test.go @@ -0,0 +1,557 @@ +//go:build integration + +package integration + +import ( + "github.com/aquasecurity/trivy/pkg/types" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTar(t *testing.T) { + type args struct { + IgnoreUnfixed bool + Severity []string + IgnoreIDs []string + Format types.Format + Input string + SkipDirs []string + SkipFiles []string + } + tests := []struct { + name string + args args + golden string + }{ + { + name: "alpine 3.9", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/alpine-39.tar.gz", + }, + golden: "testdata/alpine-39.json.golden", + }, + { + name: "alpine 3.9 with skip dirs", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/alpine-39.tar.gz", + SkipDirs: []string{ + "/etc", + }, + }, + golden: "testdata/alpine-39-skip.json.golden", + }, + { + name: "alpine 3.9 with skip files", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/alpine-39.tar.gz", + SkipFiles: []string{ + "/etc", + "/etc/TZ", + "/etc/alpine-release", + "/etc/apk", + "/etc/apk/arch", + "/etc/apk/keys", + "/etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "/etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "/etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "/etc/apk/protected_paths.d", + "/etc/apk/repositories", + "/etc/apk/world", + "/etc/conf.d", + "/etc/crontabs", + "/etc/crontabs/root", + "/etc/fstab", + "/etc/group", + "/etc/hostname", + "/etc/hosts", + "/etc/init.d", + "/etc/inittab", + "/etc/issue", + "/etc/logrotate.d", + "/etc/logrotate.d/acpid", + "/etc/modprobe.d", + "/etc/modprobe.d/aliases.conf", + "/etc/modprobe.d/blacklist.conf", + "/etc/modprobe.d/i386.conf", + "/etc/modprobe.d/kms.conf", + "/etc/modules", + "/etc/modules-load.d", + "/etc/motd", + "/etc/mtab", + "/etc/network", + "/etc/network/if-down.d", + "/etc/network/if-post-down.d", + "/etc/network/if-post-up.d", + "/etc/network/if-pre-down.d", + "/etc/network/if-pre-up.d", + "/etc/network/if-up.d", + "/etc/network/if-up.d/dad", + "/etc/opt", + "/etc/os-release", + "/etc/passwd", + "/etc/periodic", + "/etc/periodic/15min", + "/etc/periodic/daily", + "/etc/periodic/hourly", + "/etc/periodic/monthly", + "/etc/periodic/weekly", + "/etc/profile", + "/etc/profile.d", + "/etc/profile.d/color_prompt", + "/etc/protocols", + "/etc/securetty", + "/etc/services", + "/etc/shadow", + "/etc/shells", + "/etc/ssl", + "/etc/ssl/cert.pem", + "/etc/ssl/certs", + "/etc/ssl/ct_log_list.cnf", + "/etc/ssl/ct_log_list.cnf.dist", + "/etc/ssl/misc", + "/etc/ssl/misc/CA.pl", + "/etc/ssl/misc/tsget", + "/etc/ssl/misc/tsget.pl", + "/etc/ssl/openssl.cnf", + "/etc/ssl/openssl.cnf.dist", + "/etc/ssl/private", + "/etc/sysctl.conf", + "/etc/sysctl.d", + "/etc/sysctl.d/00-alpine.conf", + "/etc/udhcpd.conf", + }, + }, + golden: "testdata/alpine-39-skip.json.golden", + }, + { + name: "alpine 3.9 with high and critical severity", + args: args{ + IgnoreUnfixed: true, + Severity: []string{ + "HIGH", + "CRITICAL", + }, + Format: types.FormatJSON, + Input: "testdata/fixtures/images/alpine-39.tar.gz", + }, + golden: "testdata/alpine-39-high-critical.json.golden", + }, + { + name: "alpine 3.9 with .trivyignore", + args: args{ + IgnoreUnfixed: false, + IgnoreIDs: []string{ + "CVE-2019-1549", + "CVE-2019-14697", + }, + Format: types.FormatJSON, + Input: "testdata/fixtures/images/alpine-39.tar.gz", + }, + golden: "testdata/alpine-39-ignore-cveids.json.golden", + }, + { + name: "alpine 3.10", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/alpine-310.tar.gz", + }, + golden: "testdata/alpine-310.json.golden", + }, + { + name: "alpine distroless", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/alpine-distroless.tar.gz", + }, + golden: "testdata/alpine-distroless.json.golden", + }, + { + name: "amazon linux 1", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/amazon-1.tar.gz", + }, + golden: "testdata/amazon-1.json.golden", + }, + { + name: "amazon linux 2", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/amazon-2.tar.gz", + }, + golden: "testdata/amazon-2.json.golden", + }, + { + name: "debian buster/10", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/debian-buster.tar.gz", + }, + golden: "testdata/debian-buster.json.golden", + }, + { + name: "debian buster/10 with --ignore-unfixed option", + args: args{ + IgnoreUnfixed: true, + Format: types.FormatJSON, + Input: "testdata/fixtures/images/debian-buster.tar.gz", + }, + golden: "testdata/debian-buster-ignore-unfixed.json.golden", + }, + { + name: "debian stretch/9", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/debian-stretch.tar.gz", + }, + golden: "testdata/debian-stretch.json.golden", + }, + { + name: "ubuntu 18.04", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/ubuntu-1804.tar.gz", + }, + golden: "testdata/ubuntu-1804.json.golden", + }, + { + name: "ubuntu 18.04 with --ignore-unfixed option", + args: args{ + IgnoreUnfixed: true, + Format: types.FormatJSON, + Input: "testdata/fixtures/images/ubuntu-1804.tar.gz", + }, + golden: "testdata/ubuntu-1804-ignore-unfixed.json.golden", + }, + { + name: "centos 7", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/centos-7.tar.gz", + }, + golden: "testdata/centos-7.json.golden", + }, + { + name: "centos 7with --ignore-unfixed option", + args: args{ + IgnoreUnfixed: true, + Format: types.FormatJSON, + Input: "testdata/fixtures/images/centos-7.tar.gz", + }, + golden: "testdata/centos-7-ignore-unfixed.json.golden", + }, + { + name: "centos 7 with medium severity", + args: args{ + IgnoreUnfixed: true, + Severity: []string{"MEDIUM"}, + Format: types.FormatJSON, + Input: "testdata/fixtures/images/centos-7.tar.gz", + }, + golden: "testdata/centos-7-medium.json.golden", + }, + { + name: "centos 6", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/centos-6.tar.gz", + }, + golden: "testdata/centos-6.json.golden", + }, + { + name: "ubi 7", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/ubi-7.tar.gz", + }, + golden: "testdata/ubi-7.json.golden", + }, + { + name: "almalinux 8", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/almalinux-8.tar.gz", + }, + golden: "testdata/almalinux-8.json.golden", + }, + { + name: "rocky linux 8", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/rockylinux-8.tar.gz", + }, + golden: "testdata/rockylinux-8.json.golden", + }, + { + name: "distroless base", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/distroless-base.tar.gz", + }, + golden: "testdata/distroless-base.json.golden", + }, + { + name: "distroless python27", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/distroless-python27.tar.gz", + }, + golden: "testdata/distroless-python27.json.golden", + }, + { + name: "oracle linux 8", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/oraclelinux-8.tar.gz", + }, + golden: "testdata/oraclelinux-8.json.golden", + }, + { + name: "opensuse leap 15.1", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/opensuse-leap-151.tar.gz", + }, + golden: "testdata/opensuse-leap-151.json.golden", + }, + { + name: "photon 3.0", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/photon-30.tar.gz", + }, + golden: "testdata/photon-30.json.golden", + }, + { + name: "CBL-Mariner 1.0", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/mariner-1.0.tar.gz", + }, + golden: "testdata/mariner-1.0.json.golden", + }, + { + name: "busybox with Cargo.lock integration", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/busybox-with-lockfile.tar.gz", + }, + golden: "testdata/busybox-with-lockfile.json.golden", + }, + { + name: "fluentd with RubyGems", + args: args{ + IgnoreUnfixed: true, + Format: types.FormatJSON, + Input: "testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz", + }, + golden: "testdata/fluentd-gems.json.golden", + }, + } + + // Set up testing DB + cacheDir := initDB(t) + + // Set a temp dir so that modules will not be loaded + t.Setenv("XDG_DATA_HOME", cacheDir) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osArgs := []string{ + "--cache-dir", + cacheDir, + "image", + "-q", + "--format", + string(tt.args.Format), + "--skip-update", + } + + if tt.args.IgnoreUnfixed { + osArgs = append(osArgs, "--ignore-unfixed") + } + if len(tt.args.Severity) != 0 { + osArgs = append(osArgs, "--severity", strings.Join(tt.args.Severity, ",")) + } + if len(tt.args.IgnoreIDs) != 0 { + trivyIgnore := ".trivyignore" + err := os.WriteFile(trivyIgnore, []byte(strings.Join(tt.args.IgnoreIDs, "\n")), 0444) + assert.NoError(t, err, "failed to write .trivyignore") + defer os.Remove(trivyIgnore) + } + if tt.args.Input != "" { + osArgs = append(osArgs, "--input", tt.args.Input) + } + + if len(tt.args.SkipFiles) != 0 { + for _, skipFile := range tt.args.SkipFiles { + osArgs = append(osArgs, "--skip-files", skipFile) + } + } + + if len(tt.args.SkipDirs) != 0 { + for _, skipDir := range tt.args.SkipDirs { + osArgs = append(osArgs, "--skip-dirs", skipDir) + } + } + + // Run Trivy + runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{}) + }) + } +} + +func TestTarWithEnv(t *testing.T) { + type args struct { + IgnoreUnfixed bool + Severity []string + Format string + Input string + SkipDirs []string + } + tests := []struct { + name string + testArgs args + golden string + }{ + { + name: "alpine 3.9 with skip dirs", + testArgs: args{ + Format: "json", + Input: "testdata/fixtures/images/alpine-39.tar.gz", + SkipDirs: []string{ + "/etc", + }, + }, + golden: "testdata/alpine-39-skip.json.golden", + }, + { + name: "alpine 3.9 with high and critical severity", + testArgs: args{ + IgnoreUnfixed: true, + Severity: []string{ + "HIGH", + "CRITICAL", + }, + Format: "json", + Input: "testdata/fixtures/images/alpine-39.tar.gz", + }, + golden: "testdata/alpine-39-high-critical.json.golden", + }, + { + name: "debian buster/10 with --ignore-unfixed option", + testArgs: args{ + IgnoreUnfixed: true, + Format: "json", + Input: "testdata/fixtures/images/debian-buster.tar.gz", + }, + golden: "testdata/debian-buster-ignore-unfixed.json.golden", + }, + } + + // Set up testing DB + cacheDir := initDB(t) + + // Set a temp dir so that modules will not be loaded + t.Setenv("XDG_DATA_HOME", cacheDir) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("TRIVY_FORMAT", tt.testArgs.Format) + t.Setenv("TRIVY_CACHE_DIR", cacheDir) + t.Setenv("TRIVY_QUIET", "true") + t.Setenv("TRIVY_SKIP_UPDATE", "true") + + if tt.testArgs.IgnoreUnfixed { + t.Setenv("TRIVY_IGNORE_UNFIXED", "true") + } + if len(tt.testArgs.Severity) != 0 { + t.Setenv("TRIVY_SEVERITY", strings.Join(tt.testArgs.Severity, ",")) + } + if tt.testArgs.Input != "" { + t.Setenv("TRIVY_INPUT", tt.testArgs.Input) + } + + if len(tt.testArgs.SkipDirs) != 0 { + t.Setenv("TRIVY_SKIP_DIRS", strings.Join(tt.testArgs.SkipDirs, ",")) + } + + // Run Trivy + runTest(t, []string{"image"}, tt.golden, "", types.FormatJSON, runOptions{}) + }) + } +} + +func TestTarWithConfigFile(t *testing.T) { + tests := []struct { + name string + input string + configFile string + golden string + }{ + { + name: "alpine 3.9 with high and critical severity", + input: "testdata/fixtures/images/alpine-39.tar.gz", + configFile: `quiet: true +format: json +severity: + - HIGH + - CRITICAL +vulnerability: + type: + - os +cache: + dir: /should/be/overwritten +`, + golden: "testdata/alpine-39-high-critical.json.golden", + }, + { + name: "debian buster/10 with --ignore-unfixed option", + input: "testdata/fixtures/images/debian-buster.tar.gz", + configFile: `quiet: true +format: json +vulnerability: + ignore-unfixed: true +cache: + dir: /should/be/overwritten +`, + golden: "testdata/debian-buster-ignore-unfixed.json.golden", + }, + } + + // Set up testing DB + cacheDir := initDB(t) + + // Set a temp dir so that modules will not be loaded + t.Setenv("XDG_DATA_HOME", cacheDir) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "trivy.yaml") + err := os.WriteFile(configPath, []byte(tt.configFile), 0600) + require.NoError(t, err) + + osArgs := []string{ + "--cache-dir", + cacheDir, + "image", + "--skip-db-update", + "--config", + configPath, + "--input", + tt.input, + } + + // Run Trivy + runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{}) + }) + } +} diff --git a/integration/testdata/almalinux-8.json.golden b/integration/testdata/almalinux-8.json.golden new file mode 100644 index 000000000000..409e02d6e9bd --- /dev/null +++ b/integration/testdata/almalinux-8.json.golden @@ -0,0 +1,141 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/almalinux-8.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "alma", + "Name": "8.5" + }, + "ImageID": "sha256:4ca63ce1d8a90da2ed4f2d5e93e8e9db2f32d0fabf0718a2edebbe0e70826622", + "DiffIDs": [ + "sha256:124d41c237c5e823577dda97e87cebaecce62d585c725d07e709ce410681de4d" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "a467f67a48d469e1975b7414f33f2cf87121d4cc59d2ee029ea58e6b81774769", + "created": "2021-11-13T12:10:27.09871973Z", + "docker_version": "20.10.7", + "history": [ + { + "created": "2021-11-13T12:10:26.29818864Z", + "created_by": "/bin/sh -c #(nop) ADD file:2e002305ccb9d8a4dcef52509c4c50b9a15e76c9c49ca6abda3e0d7091c63fa7 in / " + }, + { + "created": "2021-11-13T12:10:27.09871973Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:124d41c237c5e823577dda97e87cebaecce62d585c725d07e709ce410681de4d" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:d38d2eac03bc19e080df596d6148863a0f8293f3a277a7524f378da79a1feb0f" + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/almalinux-8.tar.gz (alma 8.5)", + "Class": "os-pkgs", + "Type": "alma", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2021-3712", + "PkgID": "openssl-libs@1.1.1k-4.el8.x86_64", + "PkgName": "openssl-libs", + "PkgIdentifier": { + "PURL": "pkg:rpm/alma/openssl-libs@1.1.1k-4.el8?arch=x86_64\u0026distro=alma-8.5\u0026epoch=1" + }, + "InstalledVersion": "1:1.1.1k-4.el8", + "FixedVersion": "1:1.1.1k-5.el8_5", + "Status": "fixed", + "Layer": { + "Digest": "sha256:a1f18d9dc5496c63197eb9a4f1d4bf5cc88c6a34f64f0fe11ea233070392ce48", + "DiffID": "sha256:124d41c237c5e823577dda97e87cebaecce62d585c725d07e709ce410681de4d" + }, + "SeveritySource": "alma", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-3712", + "DataSource": { + "ID": "alma", + "Name": "AlmaLinux Product Errata", + "URL": "https://errata.almalinux.org/" + }, + "Title": "openssl: Read buffer overruns processing ASN.1 strings", + "Description": "ASN.1 strings are represented internally within OpenSSL as an ASN1_STRING structure which contains a buffer holding the string data and a field holding the buffer length. This contrasts with normal C strings which are represented as a buffer for the string data which is terminated with a NUL (0) byte. Although not a strict requirement, ASN.1 strings that are parsed using OpenSSL's own \"d2i\" functions (and other similar parsing functions) as well as any string whose value has been set with the ASN1_STRING_set() function will additionally NUL terminate the byte array in the ASN1_STRING structure. However, it is possible for applications to directly construct valid ASN1_STRING structures which do not NUL terminate the byte array by directly setting the \"data\" and \"length\" fields in the ASN1_STRING array. This can also happen by using the ASN1_STRING_set0() function. Numerous OpenSSL functions that print ASN.1 data have been found to assume that the ASN1_STRING byte array will be NUL terminated, even though this is not guaranteed for strings that have been directly constructed. Where an application requests an ASN.1 structure to be printed, and where that ASN.1 structure contains ASN1_STRINGs that have been directly constructed by the application without NUL terminating the \"data\" field, then a read buffer overrun can occur. The same thing can also occur during name constraints processing of certificates (for example if a certificate has been directly constructed by the application instead of loading it via the OpenSSL parsing functions, and the certificate contains non NUL terminated ASN1_STRING structures). It can also occur in the X509_get1_email(), X509_REQ_get1_email() and X509_get1_ocsp() functions. If a malicious actor can cause an application to directly construct an ASN1_STRING and then process it through one of the affected OpenSSL functions then this issue could be hit. This might result in a crash (causing a Denial of Service attack). It could also result in the disclosure of private memory contents (such as private keys, or sensitive plaintext). Fixed in OpenSSL 1.1.1l (Affected 1.1.1-1.1.1k). Fixed in OpenSSL 1.0.2za (Affected 1.0.2-1.0.2y).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-125" + ], + "VendorSeverity": { + "alma": 2, + "amazon": 2, + "arch-linux": 3, + "cbl-mariner": 3, + "nvd": 3, + "oracle-oval": 2, + "photon": 3, + "redhat": 2, + "rocky": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", + "V2Score": 5.8, + "V3Score": 7.4 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", + "V3Score": 7.4 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2021/08/26/2", + "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2021-3712.json", + "https://access.redhat.com/security/cve/CVE-2021-3712", + "https://crates.io/crates/openssl-src", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3712", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=94d23fcff9b2a7a8368dfe52214d5c2569882c11", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=ccb0a11145ee72b042d10593a64eaf9e8a55ec12", + "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10366", + "https://linux.oracle.com/cve/CVE-2021-3712.html", + "https://linux.oracle.com/errata/ELSA-2022-9023.html", + "https://lists.apache.org/thread.html/r18995de860f0e63635f3008fd2a6aca82394249476d21691e7c59c9e@%3Cdev.tomcat.apache.org%3E", + "https://lists.apache.org/thread.html/rad5d9f83f0d11fb3f8bb148d179b8a9ad7c6a17f18d70e5805a713d1@%3Cdev.tomcat.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2021/09/msg00014.html", + "https://lists.debian.org/debian-lts-announce/2021/09/msg00021.html", + "https://nvd.nist.gov/vuln/detail/CVE-2021-3712", + "https://rustsec.org/advisories/RUSTSEC-2021-0098.html", + "https://security.netapp.com/advisory/ntap-20210827-0010/", + "https://ubuntu.com/security/notices/USN-5051-1", + "https://ubuntu.com/security/notices/USN-5051-2", + "https://ubuntu.com/security/notices/USN-5051-3", + "https://ubuntu.com/security/notices/USN-5051-4 (regression only in trusty/esm)", + "https://ubuntu.com/security/notices/USN-5088-1", + "https://www.debian.org/security/2021/dsa-4963", + "https://www.openssl.org/news/secadv/20210824.txt", + "https://www.oracle.com/security-alerts/cpuoct2021.html", + "https://www.tenable.com/security/tns-2021-16", + "https://www.tenable.com/security/tns-2022-02" + ], + "PublishedDate": "2021-08-24T15:15:00Z", + "LastModifiedDate": "2022-01-06T09:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/alpine-310-registry.json.golden b/integration/testdata/alpine-310-registry.json.golden new file mode 100644 index 000000000000..51100d633f53 --- /dev/null +++ b/integration/testdata/alpine-310-registry.json.golden @@ -0,0 +1,374 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "alpine:3.10", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "alpine", + "Name": "3.10.2", + "EOSL": true + }, + "ImageID": "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", + "DiffIDs": [ + "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + ], + "RepoTags": [ + "alpine:3.10" + ], + "RepoDigests": [ + "alpine@sha256:b1c5a500182b21d0bfa5a584a8526b56d8be316f89e87d951be04abed2446e60" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "0a80155a31551fcc1a36fccbbda79fcd3f0b1c7d270653d00310e6e2217c57e6", + "created": "2019-08-20T20:19:55.211423266Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-08-20T20:19:55.062606894Z", + "created_by": "/bin/sh -c #(nop) ADD file:fe64057fbb83dccb960efabbf1cd8777920ef279a7fa8dbca0a8801c651bdf7c in / " + }, + { + "created": "2019-08-20T20:19:55.211423266Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + ] + }, + "config": { + "Cmd": [ + "/bin/sh" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:06f4121dff4d0123ce11bd2e44f48da9ba9ddcd23ae376ea1f363f63ea0849b5", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "alpine:3.10 (alpine 3.10.2)", + "Class": "os-pkgs", + "Type": "alpine", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-1549", + "PkgID": "libcrypto1.1@1.1.1c-r0", + "PkgName": "libcrypto1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + }, + "InstalledVersion": "1.1.1c-r0", + "FixedVersion": "1.1.1d-r0", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1549", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: information disclosure in fork()", + "Description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-330" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-1549", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be", + "https://linux.oracle.com/cve/CVE-2019-1549.html", + "https://linux.oracle.com/errata/ELSA-2020-1840.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", + "https://seclists.org/bugtraq/2019/Oct/1", + "https://security.netapp.com/advisory/ntap-20190919-0002/", + "https://support.f5.com/csp/article/K44070243", + "https://support.f5.com/csp/article/K44070243?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://usn.ubuntu.com/4376-1/", + "https://www.debian.org/security/2019/dsa-4539", + "https://www.openssl.org/news/secadv/20190910.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + ], + "PublishedDate": "2019-09-10T17:15:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "libcrypto1.1@1.1.1c-r0", + "PkgName": "libcrypto1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + }, + "InstalledVersion": "1.1.1c-r0", + "FixedVersion": "1.1.1d-r2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1549", + "PkgID": "libssl1.1@1.1.1c-r0", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + }, + "InstalledVersion": "1.1.1c-r0", + "FixedVersion": "1.1.1d-r0", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1549", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: information disclosure in fork()", + "Description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-330" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-1549", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be", + "https://linux.oracle.com/cve/CVE-2019-1549.html", + "https://linux.oracle.com/errata/ELSA-2020-1840.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", + "https://seclists.org/bugtraq/2019/Oct/1", + "https://security.netapp.com/advisory/ntap-20190919-0002/", + "https://support.f5.com/csp/article/K44070243", + "https://support.f5.com/csp/article/K44070243?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://usn.ubuntu.com/4376-1/", + "https://www.debian.org/security/2019/dsa-4539", + "https://www.openssl.org/news/secadv/20190910.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + ], + "PublishedDate": "2019-09-10T17:15:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "libssl1.1@1.1.1c-r0", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + }, + "InstalledVersion": "1.1.1c-r0", + "FixedVersion": "1.1.1d-r2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/alpine-310.asff.golden b/integration/testdata/alpine-310.asff.golden new file mode 100644 index 000000000000..6164ba5f1140 --- /dev/null +++ b/integration/testdata/alpine-310.asff.golden @@ -0,0 +1,184 @@ +{ + "Findings": [ + { + "SchemaVersion": "2018-10-08", + "Id": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)/CVE-2019-1549", + "ProductArn": "arn:aws:securityhub:test-region::product/aquasecurity/aquasecurity", + "GeneratorId": "Trivy/CVE-2019-1549", + "AwsAccountId": "123456789012", + "Types": [ "Software and Configuration Checks/Vulnerabilities/CVE" ], + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "UpdatedAt": "2021-08-25T12:20:30.000000005Z", + "Severity": { + "Label": "MEDIUM" + }, + "Title": "Trivy found a vulnerability to CVE-2019-1549 in container testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2), related to libcrypto1.1", + "Description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an applicati ..", + "Remediation": { + "Recommendation": { + "Text": "More information on this vulnerability is provided in the hyperlink", + "Url": "https://avd.aquasec.com/nvd/cve-2019-1549" + } + }, + "ProductFields": { "Product Name": "Trivy" }, + "Resources": [ + { + "Type": "Container", + "Id": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)", + "Partition": "aws", + "Region": "test-region", + "Details": { + "Container": { "ImageName": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)" }, + "Other": { + "CVE ID": "CVE-2019-1549", + "CVE Title": "openssl: information disclosure in fork()", + "PkgName": "libcrypto1.1", + "Installed Package": "1.1.1c-r0", + "Patched Package": "1.1.1d-r0", + "NvdCvssScoreV3": "5.3", + "NvdCvssVectorV3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "NvdCvssScoreV2": "5", + "NvdCvssVectorV2": "AV:N/AC:L/Au:N/C:P/I:N/A:N" + } + } + } + ], + "RecordState": "ACTIVE" + }, + { + "SchemaVersion": "2018-10-08", + "Id": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)/CVE-2019-1551", + "ProductArn": "arn:aws:securityhub:test-region::product/aquasecurity/aquasecurity", + "GeneratorId": "Trivy/CVE-2019-1551", + "AwsAccountId": "123456789012", + "Types": [ "Software and Configuration Checks/Vulnerabilities/CVE" ], + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "UpdatedAt": "2021-08-25T12:20:30.000000005Z", + "Severity": { + "Label": "MEDIUM" + }, + "Title": "Trivy found a vulnerability to CVE-2019-1551 in container testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2), related to libcrypto1.1", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly us ..", + "Remediation": { + "Recommendation": { + "Text": "More information on this vulnerability is provided in the hyperlink", + "Url": "https://avd.aquasec.com/nvd/cve-2019-1551" + } + }, + "ProductFields": { "Product Name": "Trivy" }, + "Resources": [ + { + "Type": "Container", + "Id": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)", + "Partition": "aws", + "Region": "test-region", + "Details": { + "Container": { "ImageName": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)" }, + "Other": { + "CVE ID": "CVE-2019-1551", + "CVE Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "PkgName": "libcrypto1.1", + "Installed Package": "1.1.1c-r0", + "Patched Package": "1.1.1d-r2", + "NvdCvssScoreV3": "5.3", + "NvdCvssVectorV3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "NvdCvssScoreV2": "5", + "NvdCvssVectorV2": "AV:N/AC:L/Au:N/C:P/I:N/A:N" + } + } + } + ], + "RecordState": "ACTIVE" + }, + { + "SchemaVersion": "2018-10-08", + "Id": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)/CVE-2019-1549", + "ProductArn": "arn:aws:securityhub:test-region::product/aquasecurity/aquasecurity", + "GeneratorId": "Trivy/CVE-2019-1549", + "AwsAccountId": "123456789012", + "Types": [ "Software and Configuration Checks/Vulnerabilities/CVE" ], + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "UpdatedAt": "2021-08-25T12:20:30.000000005Z", + "Severity": { + "Label": "MEDIUM" + }, + "Title": "Trivy found a vulnerability to CVE-2019-1549 in container testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2), related to libssl1.1", + "Description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an applicati ..", + "Remediation": { + "Recommendation": { + "Text": "More information on this vulnerability is provided in the hyperlink", + "Url": "https://avd.aquasec.com/nvd/cve-2019-1549" + } + }, + "ProductFields": { "Product Name": "Trivy" }, + "Resources": [ + { + "Type": "Container", + "Id": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)", + "Partition": "aws", + "Region": "test-region", + "Details": { + "Container": { "ImageName": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)" }, + "Other": { + "CVE ID": "CVE-2019-1549", + "CVE Title": "openssl: information disclosure in fork()", + "PkgName": "libssl1.1", + "Installed Package": "1.1.1c-r0", + "Patched Package": "1.1.1d-r0", + "NvdCvssScoreV3": "5.3", + "NvdCvssVectorV3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "NvdCvssScoreV2": "5", + "NvdCvssVectorV2": "AV:N/AC:L/Au:N/C:P/I:N/A:N" + } + } + } + ], + "RecordState": "ACTIVE" + }, + { + "SchemaVersion": "2018-10-08", + "Id": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)/CVE-2019-1551", + "ProductArn": "arn:aws:securityhub:test-region::product/aquasecurity/aquasecurity", + "GeneratorId": "Trivy/CVE-2019-1551", + "AwsAccountId": "123456789012", + "Types": [ "Software and Configuration Checks/Vulnerabilities/CVE" ], + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "UpdatedAt": "2021-08-25T12:20:30.000000005Z", + "Severity": { + "Label": "MEDIUM" + }, + "Title": "Trivy found a vulnerability to CVE-2019-1551 in container testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2), related to libssl1.1", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly us ..", + "Remediation": { + "Recommendation": { + "Text": "More information on this vulnerability is provided in the hyperlink", + "Url": "https://avd.aquasec.com/nvd/cve-2019-1551" + } + }, + "ProductFields": { "Product Name": "Trivy" }, + "Resources": [ + { + "Type": "Container", + "Id": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)", + "Partition": "aws", + "Region": "test-region", + "Details": { + "Container": { "ImageName": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)" }, + "Other": { + "CVE ID": "CVE-2019-1551", + "CVE Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "PkgName": "libssl1.1", + "Installed Package": "1.1.1c-r0", + "Patched Package": "1.1.1d-r2", + "NvdCvssScoreV3": "5.3", + "NvdCvssVectorV3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "NvdCvssScoreV2": "5", + "NvdCvssVectorV2": "AV:N/AC:L/Au:N/C:P/I:N/A:N" + } + } + } + ], + "RecordState": "ACTIVE" + } + ] +} diff --git a/integration/testdata/alpine-310.gitlab-codequality.golden b/integration/testdata/alpine-310.gitlab-codequality.golden new file mode 100644 index 000000000000..1a400bfd44d3 --- /dev/null +++ b/integration/testdata/alpine-310.gitlab-codequality.golden @@ -0,0 +1,62 @@ +[ + { + "type": "issue", + "check_name": "container_scanning", + "categories": [ "Security" ], + "description": "CVE-2019-1549 - libcrypto1.1 - 1.1.1c-r0 - openssl: information disclosure in fork()", + "fingerprint": "aeda1fbbe0e7f685887445359f8078c98eafd6de", + "content": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "severity": "minor", + "location": { + "path": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)", + "lines": { + "begin": 0 + } + } + }, + { + "type": "issue", + "check_name": "container_scanning", + "categories": [ "Security" ], + "description": "CVE-2019-1551 - libcrypto1.1 - 1.1.1c-r0 - openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "fingerprint": "473af5b2ba6b728fa3f356551c4b07a3b64d4f2a", + "content": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "severity": "minor", + "location": { + "path": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)", + "lines": { + "begin": 0 + } + } + }, + { + "type": "issue", + "check_name": "container_scanning", + "categories": [ "Security" ], + "description": "CVE-2019-1549 - libssl1.1 - 1.1.1c-r0 - openssl: information disclosure in fork()", + "fingerprint": "45d39f7ecf688270aeab0da7fcad5c0cf5a57886", + "content": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "severity": "minor", + "location": { + "path": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)", + "lines": { + "begin": 0 + } + } + }, + { + "type": "issue", + "check_name": "container_scanning", + "categories": [ "Security" ], + "description": "CVE-2019-1551 - libssl1.1 - 1.1.1c-r0 - openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "fingerprint": "28d484eb3b3439c4991f14b3b1b26cc339eee128", + "content": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "severity": "minor", + "location": { + "path": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)", + "lines": { + "begin": 0 + } + } + } +] diff --git a/integration/testdata/alpine-310.gitlab.golden b/integration/testdata/alpine-310.gitlab.golden new file mode 100644 index 000000000000..ad769b31959e --- /dev/null +++ b/integration/testdata/alpine-310.gitlab.golden @@ -0,0 +1,337 @@ +{ + "version": "15.0.7", + "scan": { + "analyzer": { + "id": "trivy", + "name": "Trivy", + "vendor": { + "name": "Aqua Security" + }, + "version": "dev" + }, + "end_time": "2021-08-25T12:20:30", + "scanner": { + "id": "trivy", + "name": "Trivy", + "url": "https://github.com/aquasecurity/trivy/", + "vendor": { + "name": "Aqua Security" + }, + "version": "dev" + }, + "start_time": "2021-08-25T12:20:30", + "status": "success", + "type": "container_scanning" + }, + "vulnerabilities": [ + { + "id": "CVE-2019-1549", + "name": "openssl: information disclosure in fork()", + "description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "severity": "Medium", + "solution": "Upgrade libcrypto1.1 to 1.1.1d-r0", + "location": { + "dependency": { + "package": { + "name": "libcrypto1.1" + }, + "version": "1.1.1c-r0" + }, + "operating_system": "Unknown", + "image": "testdata/fixtures/images/alpine-310.tar.gz" + }, + "identifiers": [ + { + "type": "cve", + "name": "CVE-2019-1549", + "value": "CVE-2019-1549", + "url": "https://avd.aquasec.com/nvd/cve-2019-1549" + } + ], + "links": [{ + "url": "https://access.redhat.com/security/cve/CVE-2019-1549" + },{ + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549" + },{ + "url": "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be" + },{ + "url": "https://linux.oracle.com/cve/CVE-2019-1549.html" + },{ + "url": "https://linux.oracle.com/errata/ELSA-2020-1840.html" + },{ + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/" + },{ + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/" + },{ + "url": "https://seclists.org/bugtraq/2019/Oct/1" + },{ + "url": "https://security.netapp.com/advisory/ntap-20190919-0002/" + },{ + "url": "https://support.f5.com/csp/article/K44070243" + },{ + "url": "https://support.f5.com/csp/article/K44070243?utm_source=f5support&utm_medium=RSS" + },{ + "url": "https://ubuntu.com/security/notices/USN-4376-1" + },{ + "url": "https://usn.ubuntu.com/4376-1/" + },{ + "url": "https://www.debian.org/security/2019/dsa-4539" + },{ + "url": "https://www.openssl.org/news/secadv/20190910.txt" + },{ + "url": "https://www.oracle.com/security-alerts/cpuapr2020.html" + },{ + "url": "https://www.oracle.com/security-alerts/cpujan2020.html" + },{ + "url": "https://www.oracle.com/security-alerts/cpujul2020.html" + },{ + "url": "https://www.oracle.com/security-alerts/cpuoct2020.html" + },{ + "url": "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + } + ] + }, + { + "id": "CVE-2019-1551", + "name": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "severity": "Medium", + "solution": "Upgrade libcrypto1.1 to 1.1.1d-r2", + "location": { + "dependency": { + "package": { + "name": "libcrypto1.1" + }, + "version": "1.1.1c-r0" + }, + "operating_system": "Unknown", + "image": "testdata/fixtures/images/alpine-310.tar.gz" + }, + "identifiers": [ + { + "type": "cve", + "name": "CVE-2019-1551", + "value": "CVE-2019-1551", + "url": "https://avd.aquasec.com/nvd/cve-2019-1551" + } + ], + "links": [{ + "url": "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html" + },{ + "url": "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html" + },{ + "url": "https://access.redhat.com/security/cve/CVE-2019-1551" + },{ + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551" + },{ + "url": "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f" + },{ + "url": "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98" + },{ + "url": "https://github.com/openssl/openssl/pull/10575" + },{ + "url": "https://linux.oracle.com/cve/CVE-2019-1551.html" + },{ + "url": "https://linux.oracle.com/errata/ELSA-2020-4514.html" + },{ + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/" + },{ + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/" + },{ + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/" + },{ + "url": "https://seclists.org/bugtraq/2019/Dec/39" + },{ + "url": "https://seclists.org/bugtraq/2019/Dec/46" + },{ + "url": "https://security.gentoo.org/glsa/202004-10" + },{ + "url": "https://security.netapp.com/advisory/ntap-20191210-0001/" + },{ + "url": "https://ubuntu.com/security/notices/USN-4376-1" + },{ + "url": "https://ubuntu.com/security/notices/USN-4504-1" + },{ + "url": "https://usn.ubuntu.com/4376-1/" + },{ + "url": "https://usn.ubuntu.com/4504-1/" + },{ + "url": "https://www.debian.org/security/2019/dsa-4594" + },{ + "url": "https://www.debian.org/security/2021/dsa-4855" + },{ + "url": "https://www.openssl.org/news/secadv/20191206.txt" + },{ + "url": "https://www.oracle.com/security-alerts/cpuApr2021.html" + },{ + "url": "https://www.oracle.com/security-alerts/cpujan2021.html" + },{ + "url": "https://www.oracle.com/security-alerts/cpujul2020.html" + },{ + "url": "https://www.tenable.com/security/tns-2019-09" + },{ + "url": "https://www.tenable.com/security/tns-2020-03" + },{ + "url": "https://www.tenable.com/security/tns-2020-11" + },{ + "url": "https://www.tenable.com/security/tns-2021-10" + } + ] + }, + { + "id": "CVE-2019-1549", + "name": "openssl: information disclosure in fork()", + "description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "severity": "Medium", + "solution": "Upgrade libssl1.1 to 1.1.1d-r0", + "location": { + "dependency": { + "package": { + "name": "libssl1.1" + }, + "version": "1.1.1c-r0" + }, + "operating_system": "Unknown", + "image": "testdata/fixtures/images/alpine-310.tar.gz" + }, + "identifiers": [ + { + "type": "cve", + "name": "CVE-2019-1549", + "value": "CVE-2019-1549", + "url": "https://avd.aquasec.com/nvd/cve-2019-1549" + } + ], + "links": [{ + "url": "https://access.redhat.com/security/cve/CVE-2019-1549" + },{ + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549" + },{ + "url": "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be" + },{ + "url": "https://linux.oracle.com/cve/CVE-2019-1549.html" + },{ + "url": "https://linux.oracle.com/errata/ELSA-2020-1840.html" + },{ + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/" + },{ + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/" + },{ + "url": "https://seclists.org/bugtraq/2019/Oct/1" + },{ + "url": "https://security.netapp.com/advisory/ntap-20190919-0002/" + },{ + "url": "https://support.f5.com/csp/article/K44070243" + },{ + "url": "https://support.f5.com/csp/article/K44070243?utm_source=f5support&utm_medium=RSS" + },{ + "url": "https://ubuntu.com/security/notices/USN-4376-1" + },{ + "url": "https://usn.ubuntu.com/4376-1/" + },{ + "url": "https://www.debian.org/security/2019/dsa-4539" + },{ + "url": "https://www.openssl.org/news/secadv/20190910.txt" + },{ + "url": "https://www.oracle.com/security-alerts/cpuapr2020.html" + },{ + "url": "https://www.oracle.com/security-alerts/cpujan2020.html" + },{ + "url": "https://www.oracle.com/security-alerts/cpujul2020.html" + },{ + "url": "https://www.oracle.com/security-alerts/cpuoct2020.html" + },{ + "url": "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + } + ] + }, + { + "id": "CVE-2019-1551", + "name": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "severity": "Medium", + "solution": "Upgrade libssl1.1 to 1.1.1d-r2", + "location": { + "dependency": { + "package": { + "name": "libssl1.1" + }, + "version": "1.1.1c-r0" + }, + "operating_system": "Unknown", + "image": "testdata/fixtures/images/alpine-310.tar.gz" + }, + "identifiers": [ + { + "type": "cve", + "name": "CVE-2019-1551", + "value": "CVE-2019-1551", + "url": "https://avd.aquasec.com/nvd/cve-2019-1551" + } + ], + "links": [{ + "url": "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html" + },{ + "url": "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html" + },{ + "url": "https://access.redhat.com/security/cve/CVE-2019-1551" + },{ + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551" + },{ + "url": "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f" + },{ + "url": "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98" + },{ + "url": "https://github.com/openssl/openssl/pull/10575" + },{ + "url": "https://linux.oracle.com/cve/CVE-2019-1551.html" + },{ + "url": "https://linux.oracle.com/errata/ELSA-2020-4514.html" + },{ + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/" + },{ + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/" + },{ + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/" + },{ + "url": "https://seclists.org/bugtraq/2019/Dec/39" + },{ + "url": "https://seclists.org/bugtraq/2019/Dec/46" + },{ + "url": "https://security.gentoo.org/glsa/202004-10" + },{ + "url": "https://security.netapp.com/advisory/ntap-20191210-0001/" + },{ + "url": "https://ubuntu.com/security/notices/USN-4376-1" + },{ + "url": "https://ubuntu.com/security/notices/USN-4504-1" + },{ + "url": "https://usn.ubuntu.com/4376-1/" + },{ + "url": "https://usn.ubuntu.com/4504-1/" + },{ + "url": "https://www.debian.org/security/2019/dsa-4594" + },{ + "url": "https://www.debian.org/security/2021/dsa-4855" + },{ + "url": "https://www.openssl.org/news/secadv/20191206.txt" + },{ + "url": "https://www.oracle.com/security-alerts/cpuApr2021.html" + },{ + "url": "https://www.oracle.com/security-alerts/cpujan2021.html" + },{ + "url": "https://www.oracle.com/security-alerts/cpujul2020.html" + },{ + "url": "https://www.tenable.com/security/tns-2019-09" + },{ + "url": "https://www.tenable.com/security/tns-2020-03" + },{ + "url": "https://www.tenable.com/security/tns-2020-11" + },{ + "url": "https://www.tenable.com/security/tns-2021-10" + } + ] + } + ], + "remediations": [] +} diff --git a/integration/testdata/alpine-310.gsbom.golden b/integration/testdata/alpine-310.gsbom.golden new file mode 100644 index 000000000000..699b07520428 --- /dev/null +++ b/integration/testdata/alpine-310.gsbom.golden @@ -0,0 +1,135 @@ +{ + "version": 0, + "detector": { + "name": "trivy", + "version": "dev", + "url": "https://github.com/aquasecurity/trivy" + }, + "ref": "/ref/feature-1", + "sha": "39da54a1ff04120a31df8cbc94ce9ede251d21a3", + "job": { + "correlator": "workflow-name_integration", + "id": "1910764383" + }, + "scanned": "2021-08-25T12:20:30Z", + "manifests": { + "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)": { + "name": "alpine", + "resolved": { + "alpine-baselayout": { + "package_url": "pkg:apk/alpine/alpine-baselayout@3.1.2-r0?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "busybox@1.30.1-r2", + "musl@1.1.22-r3" + ], + "scope": "runtime" + }, + "alpine-keys": { + "package_url": "pkg:apk/alpine/alpine-keys@2.1-r2?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "scope": "runtime" + }, + "apk-tools": { + "package_url": "pkg:apk/alpine/apk-tools@2.10.4-r2?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "libcrypto1.1@1.1.1c-r0", + "libssl1.1@1.1.1c-r0", + "musl@1.1.22-r3", + "zlib@1.2.11-r1" + ], + "scope": "runtime" + }, + "busybox": { + "package_url": "pkg:apk/alpine/busybox@1.30.1-r2?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "musl@1.1.22-r3" + ], + "scope": "runtime" + }, + "ca-certificates-cacert": { + "package_url": "pkg:apk/alpine/ca-certificates-cacert@20190108-r0?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "scope": "runtime" + }, + "libc-utils": { + "package_url": "pkg:apk/alpine/libc-utils@0.7.1-r0?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "musl-utils@1.1.22-r3" + ], + "scope": "runtime" + }, + "libcrypto1.1": { + "package_url": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "musl@1.1.22-r3" + ], + "scope": "runtime" + }, + "libssl1.1": { + "package_url": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "libcrypto1.1@1.1.1c-r0", + "musl@1.1.22-r3" + ], + "scope": "runtime" + }, + "libtls-standalone": { + "package_url": "pkg:apk/alpine/libtls-standalone@2.9.1-r0?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "ca-certificates-cacert@20190108-r0", + "libcrypto1.1@1.1.1c-r0", + "libssl1.1@1.1.1c-r0", + "musl@1.1.22-r3" + ], + "scope": "runtime" + }, + "musl": { + "package_url": "pkg:apk/alpine/musl@1.1.22-r3?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "scope": "runtime" + }, + "musl-utils": { + "package_url": "pkg:apk/alpine/musl-utils@1.1.22-r3?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "musl@1.1.22-r3", + "scanelf@1.2.3-r0" + ], + "scope": "runtime" + }, + "scanelf": { + "package_url": "pkg:apk/alpine/scanelf@1.2.3-r0?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "musl@1.1.22-r3" + ], + "scope": "runtime" + }, + "ssl_client": { + "package_url": "pkg:apk/alpine/ssl_client@1.30.1-r2?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "libtls-standalone@2.9.1-r0", + "musl@1.1.22-r3" + ], + "scope": "runtime" + }, + "zlib": { + "package_url": "pkg:apk/alpine/zlib@1.2.11-r1?arch=x86_64\u0026distro=3.10.2", + "relationship": "direct", + "dependencies": [ + "musl@1.1.22-r3" + ], + "scope": "runtime" + } + } + } + } +} \ No newline at end of file diff --git a/integration/testdata/alpine-310.html.golden b/integration/testdata/alpine-310.html.golden new file mode 100644 index 000000000000..83840bd015d5 --- /dev/null +++ b/integration/testdata/alpine-310.html.golden @@ -0,0 +1,234 @@ + + + + + + testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2) - Trivy Report - 2021-08-25 12:20:30.000000005 +0000 UTC + + + +

testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2) - Trivy Report - 2021-08-25 12:20:30.000000005 +0000 UTC

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
alpine
PackageVulnerability IDSeverityInstalled VersionFixed VersionLinks
libcrypto1.1CVE-2019-1549MEDIUM1.1.1c-r01.1.1d-r0
libcrypto1.1CVE-2019-1551MEDIUM1.1.1c-r01.1.1d-r2
libssl1.1CVE-2019-1549MEDIUM1.1.1c-r01.1.1d-r0
libssl1.1CVE-2019-1551MEDIUM1.1.1c-r01.1.1d-r2
No Misconfigurations found
+ + diff --git a/integration/testdata/alpine-310.json.golden b/integration/testdata/alpine-310.json.golden new file mode 100644 index 000000000000..d6b4a7884027 --- /dev/null +++ b/integration/testdata/alpine-310.json.golden @@ -0,0 +1,368 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/alpine-310.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "alpine", + "Name": "3.10.2", + "EOSL": true + }, + "ImageID": "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", + "DiffIDs": [ + "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "0a80155a31551fcc1a36fccbbda79fcd3f0b1c7d270653d00310e6e2217c57e6", + "created": "2019-08-20T20:19:55.211423266Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-08-20T20:19:55.062606894Z", + "created_by": "/bin/sh -c #(nop) ADD file:fe64057fbb83dccb960efabbf1cd8777920ef279a7fa8dbca0a8801c651bdf7c in / " + }, + { + "created": "2019-08-20T20:19:55.211423266Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + ] + }, + "config": { + "Cmd": [ + "/bin/sh" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:06f4121dff4d0123ce11bd2e44f48da9ba9ddcd23ae376ea1f363f63ea0849b5", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)", + "Class": "os-pkgs", + "Type": "alpine", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-1549", + "PkgID": "libcrypto1.1@1.1.1c-r0", + "PkgName": "libcrypto1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + }, + "InstalledVersion": "1.1.1c-r0", + "FixedVersion": "1.1.1d-r0", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1549", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: information disclosure in fork()", + "Description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-330" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-1549", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be", + "https://linux.oracle.com/cve/CVE-2019-1549.html", + "https://linux.oracle.com/errata/ELSA-2020-1840.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", + "https://seclists.org/bugtraq/2019/Oct/1", + "https://security.netapp.com/advisory/ntap-20190919-0002/", + "https://support.f5.com/csp/article/K44070243", + "https://support.f5.com/csp/article/K44070243?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://usn.ubuntu.com/4376-1/", + "https://www.debian.org/security/2019/dsa-4539", + "https://www.openssl.org/news/secadv/20190910.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + ], + "PublishedDate": "2019-09-10T17:15:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "libcrypto1.1@1.1.1c-r0", + "PkgName": "libcrypto1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + }, + "InstalledVersion": "1.1.1c-r0", + "FixedVersion": "1.1.1d-r2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1549", + "PkgID": "libssl1.1@1.1.1c-r0", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + }, + "InstalledVersion": "1.1.1c-r0", + "FixedVersion": "1.1.1d-r0", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1549", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: information disclosure in fork()", + "Description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-330" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-1549", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be", + "https://linux.oracle.com/cve/CVE-2019-1549.html", + "https://linux.oracle.com/errata/ELSA-2020-1840.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", + "https://seclists.org/bugtraq/2019/Oct/1", + "https://security.netapp.com/advisory/ntap-20190919-0002/", + "https://support.f5.com/csp/article/K44070243", + "https://support.f5.com/csp/article/K44070243?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://usn.ubuntu.com/4376-1/", + "https://www.debian.org/security/2019/dsa-4539", + "https://www.openssl.org/news/secadv/20190910.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + ], + "PublishedDate": "2019-09-10T17:15:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "libssl1.1@1.1.1c-r0", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + }, + "InstalledVersion": "1.1.1c-r0", + "FixedVersion": "1.1.1d-r2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/alpine-310.junit.golden b/integration/testdata/alpine-310.junit.golden new file mode 100644 index 000000000000..b3f5aab40c3b --- /dev/null +++ b/integration/testdata/alpine-310.junit.golden @@ -0,0 +1,25 @@ + + + + + + + + OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c). + + + There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t). + + + OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c). + + + There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t). + + + + + + + + diff --git a/integration/testdata/alpine-310.sarif.golden b/integration/testdata/alpine-310.sarif.golden new file mode 100644 index 000000000000..535bd2d09f71 --- /dev/null +++ b/integration/testdata/alpine-310.sarif.golden @@ -0,0 +1,193 @@ +{ + "version": "2.1.0", + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "fullName": "Trivy Vulnerability Scanner", + "informationUri": "https://github.com/aquasecurity/trivy", + "name": "Trivy", + "rules": [ + { + "id": "CVE-2019-1549", + "name": "OsPackageVulnerability", + "shortDescription": { + "text": "openssl: information disclosure in fork()" + }, + "fullDescription": { + "text": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c)." + }, + "defaultConfiguration": { + "level": "warning" + }, + "helpUri": "https://avd.aquasec.com/nvd/cve-2019-1549", + "help": { + "text": "Vulnerability CVE-2019-1549\nSeverity: MEDIUM\nPackage: libssl1.1\nFixed Version: 1.1.1d-r0\nLink: [CVE-2019-1549](https://avd.aquasec.com/nvd/cve-2019-1549)\nOpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "markdown": "**Vulnerability CVE-2019-1549**\n| Severity | Package | Fixed Version | Link |\n| --- | --- | --- | --- |\n|MEDIUM|libssl1.1|1.1.1d-r0|[CVE-2019-1549](https://avd.aquasec.com/nvd/cve-2019-1549)|\n\nOpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c)." + }, + "properties": { + "precision": "very-high", + "security-severity": "5.3", + "tags": [ + "vulnerability", + "security", + "MEDIUM" + ] + } + }, + { + "id": "CVE-2019-1551", + "name": "OsPackageVulnerability", + "shortDescription": { + "text": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64" + }, + "fullDescription": { + "text": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t)." + }, + "defaultConfiguration": { + "level": "warning" + }, + "helpUri": "https://avd.aquasec.com/nvd/cve-2019-1551", + "help": { + "text": "Vulnerability CVE-2019-1551\nSeverity: MEDIUM\nPackage: libssl1.1\nFixed Version: 1.1.1d-r2\nLink: [CVE-2019-1551](https://avd.aquasec.com/nvd/cve-2019-1551)\nThere is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "markdown": "**Vulnerability CVE-2019-1551**\n| Severity | Package | Fixed Version | Link |\n| --- | --- | --- | --- |\n|MEDIUM|libssl1.1|1.1.1d-r2|[CVE-2019-1551](https://avd.aquasec.com/nvd/cve-2019-1551)|\n\nThere is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t)." + }, + "properties": { + "precision": "very-high", + "security-severity": "5.3", + "tags": [ + "vulnerability", + "security", + "MEDIUM" + ] + } + } + ], + "version": "dev" + } + }, + "results": [ + { + "ruleId": "CVE-2019-1549", + "ruleIndex": 0, + "level": "warning", + "message": { + "text": "Package: libcrypto1.1\nInstalled Version: 1.1.1c-r0\nVulnerability CVE-2019-1549\nSeverity: MEDIUM\nFixed Version: 1.1.1d-r0\nLink: [CVE-2019-1549](https://avd.aquasec.com/nvd/cve-2019-1549)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "testdata/fixtures/images/alpine-310.tar.gz", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1, + "startColumn": 1, + "endLine": 1, + "endColumn": 1 + } + }, + "message": { + "text": "testdata/fixtures/images/alpine-310.tar.gz: libcrypto1.1@1.1.1c-r0" + } + } + ] + }, + { + "ruleId": "CVE-2019-1551", + "ruleIndex": 1, + "level": "warning", + "message": { + "text": "Package: libcrypto1.1\nInstalled Version: 1.1.1c-r0\nVulnerability CVE-2019-1551\nSeverity: MEDIUM\nFixed Version: 1.1.1d-r2\nLink: [CVE-2019-1551](https://avd.aquasec.com/nvd/cve-2019-1551)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "testdata/fixtures/images/alpine-310.tar.gz", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1, + "startColumn": 1, + "endLine": 1, + "endColumn": 1 + } + }, + "message": { + "text": "testdata/fixtures/images/alpine-310.tar.gz: libcrypto1.1@1.1.1c-r0" + } + } + ] + }, + { + "ruleId": "CVE-2019-1549", + "ruleIndex": 0, + "level": "warning", + "message": { + "text": "Package: libssl1.1\nInstalled Version: 1.1.1c-r0\nVulnerability CVE-2019-1549\nSeverity: MEDIUM\nFixed Version: 1.1.1d-r0\nLink: [CVE-2019-1549](https://avd.aquasec.com/nvd/cve-2019-1549)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "testdata/fixtures/images/alpine-310.tar.gz", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1, + "startColumn": 1, + "endLine": 1, + "endColumn": 1 + } + }, + "message": { + "text": "testdata/fixtures/images/alpine-310.tar.gz: libssl1.1@1.1.1c-r0" + } + } + ] + }, + { + "ruleId": "CVE-2019-1551", + "ruleIndex": 1, + "level": "warning", + "message": { + "text": "Package: libssl1.1\nInstalled Version: 1.1.1c-r0\nVulnerability CVE-2019-1551\nSeverity: MEDIUM\nFixed Version: 1.1.1d-r2\nLink: [CVE-2019-1551](https://avd.aquasec.com/nvd/cve-2019-1551)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "testdata/fixtures/images/alpine-310.tar.gz", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1, + "startColumn": 1, + "endLine": 1, + "endColumn": 1 + } + }, + "message": { + "text": "testdata/fixtures/images/alpine-310.tar.gz: libssl1.1@1.1.1c-r0" + } + } + ] + } + ], + "columnKind": "utf16CodeUnits", + "originalUriBaseIds": { + "ROOTPATH": { + "uri": "file:///" + } + }, + "properties": { + "imageName": "testdata/fixtures/images/alpine-310.tar.gz", + "repoDigests": null, + "repoTags": null + } + } + ] +} \ No newline at end of file diff --git a/integration/testdata/alpine-39-high-critical.json.golden b/integration/testdata/alpine-39-high-critical.json.golden new file mode 100644 index 000000000000..73288f579caa --- /dev/null +++ b/integration/testdata/alpine-39-high-critical.json.golden @@ -0,0 +1,150 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "alpine", + "Name": "3.9.4", + "EOSL": true + }, + "ImageID": "sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1", + "DiffIDs": [ + "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "c10d36fa368a7ea673683682666758adf35efe98e10989505f4f566b5b18538f", + "created": "2019-05-11T00:07:03.510395965Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-05-11T00:07:03.358250803Z", + "created_by": "/bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6ae03397b99ea77f2e9ee901c5c59e59f76f93adbb4035913 in / " + }, + { + "created": "2019-05-11T00:07:03.510395965Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + ] + }, + "config": { + "Cmd": [ + "/bin/sh" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:09f2bbe58e774849d74dc1391c2e01731896c745c4aba1ecf69a283bdb4b537a", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/alpine-39.tar.gz (alpine 3.9.4)", + "Class": "os-pkgs", + "Type": "alpine", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-14697", + "PkgID": "musl@1.1.20-r4", + "PkgName": "musl", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/musl@1.1.20-r4?arch=x86_64\u0026distro=3.9.4" + }, + "InstalledVersion": "1.1.20-r4", + "FixedVersion": "1.1.20-r5", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-14697", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Description": "musl libc through 1.1.23 has an x87 floating-point stack adjustment imbalance, related to the math/i386/ directory. In some cases, use of this library could introduce out-of-bounds writes that are not present in an application's source code.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "nvd": 4 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2019/08/06/4", + "https://security.gentoo.org/glsa/202003-13", + "https://www.openwall.com/lists/musl/2019/08/06/1" + ], + "PublishedDate": "2019-08-06T16:15:00Z", + "LastModifiedDate": "2020-03-14T19:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-14697", + "PkgID": "musl-utils@1.1.20-r4", + "PkgName": "musl-utils", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/musl-utils@1.1.20-r4?arch=x86_64\u0026distro=3.9.4" + }, + "InstalledVersion": "1.1.20-r4", + "FixedVersion": "1.1.20-r5", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-14697", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Description": "musl libc through 1.1.23 has an x87 floating-point stack adjustment imbalance, related to the math/i386/ directory. In some cases, use of this library could introduce out-of-bounds writes that are not present in an application's source code.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "nvd": 4 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2019/08/06/4", + "https://security.gentoo.org/glsa/202003-13", + "https://www.openwall.com/lists/musl/2019/08/06/1" + ], + "PublishedDate": "2019-08-06T16:15:00Z", + "LastModifiedDate": "2020-03-14T19:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/alpine-39-ignore-cveids.json.golden b/integration/testdata/alpine-39-ignore-cveids.json.golden new file mode 100644 index 000000000000..f11198c2364e --- /dev/null +++ b/integration/testdata/alpine-39-ignore-cveids.json.golden @@ -0,0 +1,224 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "alpine", + "Name": "3.9.4", + "EOSL": true + }, + "ImageID": "sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1", + "DiffIDs": [ + "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "c10d36fa368a7ea673683682666758adf35efe98e10989505f4f566b5b18538f", + "created": "2019-05-11T00:07:03.510395965Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-05-11T00:07:03.358250803Z", + "created_by": "/bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6ae03397b99ea77f2e9ee901c5c59e59f76f93adbb4035913 in / " + }, + { + "created": "2019-05-11T00:07:03.510395965Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + ] + }, + "config": { + "Cmd": [ + "/bin/sh" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:09f2bbe58e774849d74dc1391c2e01731896c745c4aba1ecf69a283bdb4b537a", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/alpine-39.tar.gz (alpine 3.9.4)", + "Class": "os-pkgs", + "Type": "alpine", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "libcrypto1.1@1.1.1b-r1", + "PkgName": "libcrypto1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + }, + "InstalledVersion": "1.1.1b-r1", + "FixedVersion": "1.1.1d-r2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "libssl1.1@1.1.1b-r1", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + }, + "InstalledVersion": "1.1.1b-r1", + "FixedVersion": "1.1.1d-r2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/alpine-39-skip.json.golden b/integration/testdata/alpine-39-skip.json.golden new file mode 100644 index 000000000000..2584d251c0b7 --- /dev/null +++ b/integration/testdata/alpine-39-skip.json.golden @@ -0,0 +1,50 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "none", + "Name": "" + }, + "ImageID": "sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1", + "DiffIDs": [ + "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "c10d36fa368a7ea673683682666758adf35efe98e10989505f4f566b5b18538f", + "created": "2019-05-11T00:07:03.510395965Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-05-11T00:07:03.358250803Z", + "created_by": "/bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6ae03397b99ea77f2e9ee901c5c59e59f76f93adbb4035913 in / " + }, + { + "created": "2019-05-11T00:07:03.510395965Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + ] + }, + "config": { + "Cmd": [ + "/bin/sh" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:09f2bbe58e774849d74dc1391c2e01731896c745c4aba1ecf69a283bdb4b537a", + "ArgsEscaped": true + } + } + } +} diff --git a/integration/testdata/alpine-39.json.golden b/integration/testdata/alpine-39.json.golden new file mode 100644 index 000000000000..303f7a3c7277 --- /dev/null +++ b/integration/testdata/alpine-39.json.golden @@ -0,0 +1,458 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "alpine", + "Name": "3.9.4", + "EOSL": true + }, + "ImageID": "sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1", + "DiffIDs": [ + "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "c10d36fa368a7ea673683682666758adf35efe98e10989505f4f566b5b18538f", + "created": "2019-05-11T00:07:03.510395965Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-05-11T00:07:03.358250803Z", + "created_by": "/bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6ae03397b99ea77f2e9ee901c5c59e59f76f93adbb4035913 in / " + }, + { + "created": "2019-05-11T00:07:03.510395965Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + ] + }, + "config": { + "Cmd": [ + "/bin/sh" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:09f2bbe58e774849d74dc1391c2e01731896c745c4aba1ecf69a283bdb4b537a", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/alpine-39.tar.gz (alpine 3.9.4)", + "Class": "os-pkgs", + "Type": "alpine", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-1549", + "PkgID": "libcrypto1.1@1.1.1b-r1", + "PkgName": "libcrypto1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + }, + "InstalledVersion": "1.1.1b-r1", + "FixedVersion": "1.1.1d-r0", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1549", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: information disclosure in fork()", + "Description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-330" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-1549", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be", + "https://linux.oracle.com/cve/CVE-2019-1549.html", + "https://linux.oracle.com/errata/ELSA-2020-1840.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", + "https://seclists.org/bugtraq/2019/Oct/1", + "https://security.netapp.com/advisory/ntap-20190919-0002/", + "https://support.f5.com/csp/article/K44070243", + "https://support.f5.com/csp/article/K44070243?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://usn.ubuntu.com/4376-1/", + "https://www.debian.org/security/2019/dsa-4539", + "https://www.openssl.org/news/secadv/20190910.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + ], + "PublishedDate": "2019-09-10T17:15:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "libcrypto1.1@1.1.1b-r1", + "PkgName": "libcrypto1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + }, + "InstalledVersion": "1.1.1b-r1", + "FixedVersion": "1.1.1d-r2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1549", + "PkgID": "libssl1.1@1.1.1b-r1", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + }, + "InstalledVersion": "1.1.1b-r1", + "FixedVersion": "1.1.1d-r0", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1549", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: information disclosure in fork()", + "Description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-330" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-1549", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be", + "https://linux.oracle.com/cve/CVE-2019-1549.html", + "https://linux.oracle.com/errata/ELSA-2020-1840.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", + "https://seclists.org/bugtraq/2019/Oct/1", + "https://security.netapp.com/advisory/ntap-20190919-0002/", + "https://support.f5.com/csp/article/K44070243", + "https://support.f5.com/csp/article/K44070243?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://usn.ubuntu.com/4376-1/", + "https://www.debian.org/security/2019/dsa-4539", + "https://www.openssl.org/news/secadv/20190910.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + ], + "PublishedDate": "2019-09-10T17:15:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "libssl1.1@1.1.1b-r1", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + }, + "InstalledVersion": "1.1.1b-r1", + "FixedVersion": "1.1.1d-r2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + }, + { + "VulnerabilityID": "CVE-2019-14697", + "PkgID": "musl@1.1.20-r4", + "PkgName": "musl", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/musl@1.1.20-r4?arch=x86_64\u0026distro=3.9.4" + }, + "InstalledVersion": "1.1.20-r4", + "FixedVersion": "1.1.20-r5", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-14697", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Description": "musl libc through 1.1.23 has an x87 floating-point stack adjustment imbalance, related to the math/i386/ directory. In some cases, use of this library could introduce out-of-bounds writes that are not present in an application's source code.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "nvd": 4 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2019/08/06/4", + "https://security.gentoo.org/glsa/202003-13", + "https://www.openwall.com/lists/musl/2019/08/06/1" + ], + "PublishedDate": "2019-08-06T16:15:00Z", + "LastModifiedDate": "2020-03-14T19:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-14697", + "PkgID": "musl-utils@1.1.20-r4", + "PkgName": "musl-utils", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/musl-utils@1.1.20-r4?arch=x86_64\u0026distro=3.9.4" + }, + "InstalledVersion": "1.1.20-r4", + "FixedVersion": "1.1.20-r5", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-14697", + "DataSource": { + "ID": "alpine", + "Name": "Alpine Secdb", + "URL": "https://secdb.alpinelinux.org/" + }, + "Description": "musl libc through 1.1.23 has an x87 floating-point stack adjustment imbalance, related to the math/i386/ directory. In some cases, use of this library could introduce out-of-bounds writes that are not present in an application's source code.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "nvd": 4 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2019/08/06/4", + "https://security.gentoo.org/glsa/202003-13", + "https://www.openwall.com/lists/musl/2019/08/06/1" + ], + "PublishedDate": "2019-08-06T16:15:00Z", + "LastModifiedDate": "2020-03-14T19:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/alpine-distroless.json.golden b/integration/testdata/alpine-distroless.json.golden new file mode 100644 index 000000000000..8f79cba7610c --- /dev/null +++ b/integration/testdata/alpine-distroless.json.golden @@ -0,0 +1,90 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/alpine-distroless.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "alpine", + "Name": "3.16" + }, + "ImageID": "sha256:22848737c0d272ad5d7c7369d8ca830a62929e63e38edcb22085139a6ae0688d", + "DiffIDs": [ + "sha256:89da7cc836da4b53ab1ceb572576458c005e7e444b8bb79abda196668a2f0c92" + ], + "ImageConfig": { + "architecture": "amd64", + "author": "github.com/chainguard-dev/apko", + "created": "1970-01-01T00:00:00Z", + "history": [ + { + "author": "apko", + "created": "1970-01-01T00:00:00Z", + "created_by": "apko", + "comment": "This is an apko single-layer image" + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:89da7cc836da4b53ab1ceb572576458c005e7e444b8bb79abda196668a2f0c92" + ] + }, + "config": { + "Entrypoint": [ + "/usr/bin/git" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt" + ], + "User": "65532" + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/alpine-distroless.tar.gz (alpine 3.16)", + "Class": "os-pkgs", + "Type": "alpine", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-24765", + "PkgID": "git@2.35.1-r2", + "PkgName": "git", + "PkgIdentifier": { + "PURL": "pkg:apk/alpine/git@2.35.1-r2?arch=x86_64\u0026distro=3.16" + }, + "InstalledVersion": "2.35.1-r2", + "FixedVersion": "2.35.2-r0", + "Status": "fixed", + "Layer": { + "Digest": "sha256:6c6f69aa25501b090c54c62a9c17e978064c2f1328f67a7ef88c81ce5f2d7983", + "DiffID": "sha256:89da7cc836da4b53ab1ceb572576458c005e7e444b8bb79abda196668a2f0c92" + }, + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-24765", + "Title": "Git for Windows is a fork of Git containing Windows-specific patches. ...", + "Description": "Git for Windows is a fork of Git containing Windows-specific patches.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-427" + ], + "VendorSeverity": { + "ubuntu": 2 + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2022/04/12/7", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24765", + "https://git-scm.com/book/en/v2/Appendix-A%3A-Git-in-Other-Environments-Git-in-Bash", + "https://git-scm.com/docs/git#Documentation/git.txt-codeGITCEILINGDIRECTORIEScode", + "https://github.com/git-for-windows/git/security/advisories/GHSA-vw2c-22j4-2fh2", + "https://ubuntu.com/security/notices/USN-5376-1" + ], + "PublishedDate": "2022-04-12T18:15:00Z", + "LastModifiedDate": "2022-04-12T21:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/amazon-1.json.golden b/integration/testdata/amazon-1.json.golden new file mode 100644 index 000000000000..6be4b41ffe3b --- /dev/null +++ b/integration/testdata/amazon-1.json.golden @@ -0,0 +1,130 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/amazon-1.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "amazon", + "Name": "AMI release 2018.03" + }, + "ImageID": "sha256:961c4ee06269351d858969ea0426878675ed708d3a140246eabbc0bfc352bffa", + "DiffIDs": [ + "sha256:984fe1509738f6f00f34d9be7398b07ebeb8b98dda077ff6be2cdb87111b73cf" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "ef1b126795001e9b4bdc14a01180e4d8146282d279f53e05adfaa8195ecda20e", + "created": "2019-09-05T23:37:46.854286502Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-09-05T23:37:46.575366692Z", + "created_by": "/bin/sh -c #(nop) ADD file:45ed06ba8960dec70e01e809fe38df2718d4b16aa2b0f88835522d8366de71e3 in / " + }, + { + "created": "2019-09-05T23:37:46.854286502Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:984fe1509738f6f00f34d9be7398b07ebeb8b98dda077ff6be2cdb87111b73cf" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:8db654f611aca1693ac658bd981ee35e4b6517e6ef74fa608c4b3b3595a986c8", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/amazon-1.tar.gz (amazon AMI release 2018.03)", + "Class": "os-pkgs", + "Type": "amazon", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-5481", + "PkgID": "curl@7.61.1-11.91.amzn1.x86_64", + "PkgName": "curl", + "PkgIdentifier": { + "PURL": "pkg:rpm/amazon/curl@7.61.1-11.91.amzn1?arch=x86_64\u0026distro=amazon-AMI+release+2018.03" + }, + "InstalledVersion": "7.61.1-11.91.amzn1", + "FixedVersion": "7.61.1-12.93.amzn1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:105ff6bf468b1422ad7c47ea9d63eae82f875c93310cb8d34551951e754ef43b", + "DiffID": "sha256:984fe1509738f6f00f34d9be7398b07ebeb8b98dda077ff6be2cdb87111b73cf" + }, + "SeveritySource": "amazon", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5481", + "DataSource": { + "ID": "amazon", + "Name": "Amazon Linux Security Center", + "URL": "https://alas.aws.amazon.com/" + }, + "Title": "curl: double free due to subsequent call of realloc()", + "Description": "Double-free vulnerability in the FTP-kerberos code in cURL 7.52.0 to 7.65.3.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-415" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 2, + "nvd": 4, + "oracle-oval": 2, + "photon": 4, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "V3Score": 5.7 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00048.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00055.html", + "https://access.redhat.com/security/cve/CVE-2019-5481", + "https://curl.haxx.se/docs/CVE-2019-5481.html", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5481", + "https://linux.oracle.com/cve/CVE-2019-5481.html", + "https://linux.oracle.com/errata/ELSA-2020-1792.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6CI4QQ2RSZX4VCFM76SIWGKY6BY7UWIC/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RGDVKSLY5JUNJRLYRUA6CXGQ2LM63XC3/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UA7KDM2WPM5CJDDGOEGFV6SSGD2J7RNT/", + "https://seclists.org/bugtraq/2020/Feb/36", + "https://security.gentoo.org/glsa/202003-29", + "https://security.netapp.com/advisory/ntap-20191004-0003/", + "https://ubuntu.com/security/notices/USN-4129-1", + "https://www.debian.org/security/2020/dsa-4633", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html" + ], + "PublishedDate": "2019-09-16T19:15:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/amazon-2.json.golden b/integration/testdata/amazon-2.json.golden new file mode 100644 index 000000000000..1f20d8d37b68 --- /dev/null +++ b/integration/testdata/amazon-2.json.golden @@ -0,0 +1,203 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/amazon-2.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "amazon", + "Name": "2 (Karoo)" + }, + "ImageID": "sha256:b94321659aca6a89cb7650a5b864bc8ec4bf62c620b8f1a01530c2e90a88c391", + "DiffIDs": [ + "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "e020a5508b9f809b29659128692cd634e3d4fba3f2c13d2029d797317b5c3a56", + "created": "2019-05-23T22:20:00.121624838Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-05-23T22:19:59.161963646Z", + "created_by": "/bin/sh -c #(nop) ADD file:3cf811fe5073384ff1d5f405992ef7e5e452ad6d4a4cb873eee65007382f3a4a in / " + }, + { + "created": "2019-05-23T22:20:00.121624838Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:648b8b37f8b5087423bec7f4331271253f8aff63154761a67c22cd0c3ba2661b", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/amazon-2.tar.gz (amazon 2 (Karoo))", + "Class": "os-pkgs", + "Type": "amazon", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-5481", + "PkgID": "curl@7.61.1-9.amzn2.0.1.x86_64", + "PkgName": "curl", + "PkgIdentifier": { + "PURL": "pkg:rpm/amazon/curl@7.61.1-9.amzn2.0.1?arch=x86_64\u0026distro=amazon-2+%28Karoo%29" + }, + "InstalledVersion": "7.61.1-9.amzn2.0.1", + "FixedVersion": "7.61.1-12.amzn2.0.1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:72d97abdfae3b3c933ff41e39779cc72853d7bd9dc1e4800c5294d6715257799", + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + }, + "SeveritySource": "amazon", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5481", + "DataSource": { + "ID": "amazon", + "Name": "Amazon Linux Security Center", + "URL": "https://alas.aws.amazon.com/" + }, + "Title": "curl: double free due to subsequent call of realloc()", + "Description": "Double-free vulnerability in the FTP-kerberos code in cURL 7.52.0 to 7.65.3.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-415" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 2, + "nvd": 4, + "oracle-oval": 2, + "photon": 4, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "V3Score": 5.7 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00048.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00055.html", + "https://access.redhat.com/security/cve/CVE-2019-5481", + "https://curl.haxx.se/docs/CVE-2019-5481.html", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5481", + "https://linux.oracle.com/cve/CVE-2019-5481.html", + "https://linux.oracle.com/errata/ELSA-2020-1792.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6CI4QQ2RSZX4VCFM76SIWGKY6BY7UWIC/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RGDVKSLY5JUNJRLYRUA6CXGQ2LM63XC3/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UA7KDM2WPM5CJDDGOEGFV6SSGD2J7RNT/", + "https://seclists.org/bugtraq/2020/Feb/36", + "https://security.gentoo.org/glsa/202003-29", + "https://security.netapp.com/advisory/ntap-20191004-0003/", + "https://ubuntu.com/security/notices/USN-4129-1", + "https://www.debian.org/security/2020/dsa-4633", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html" + ], + "PublishedDate": "2019-09-16T19:15:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5436", + "PkgID": "curl@7.61.1-9.amzn2.0.1.x86_64", + "PkgName": "curl", + "PkgIdentifier": { + "PURL": "pkg:rpm/amazon/curl@7.61.1-9.amzn2.0.1?arch=x86_64\u0026distro=amazon-2+%28Karoo%29" + }, + "InstalledVersion": "7.61.1-9.amzn2.0.1", + "FixedVersion": "7.61.1-11.amzn2.0.2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:72d97abdfae3b3c933ff41e39779cc72853d7bd9dc1e4800c5294d6715257799", + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + }, + "SeveritySource": "amazon", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5436", + "DataSource": { + "ID": "amazon", + "Name": "Amazon Linux Security Center", + "URL": "https://alas.aws.amazon.com/" + }, + "Title": "curl: TFTP receive heap buffer overflow in tftp_receive_packet() function", + "Description": "A heap buffer overflow in the TFTP receiving code allows for DoS or arbitrary code execution in libcurl versions 7.19.4 through 7.64.1.", + "Severity": "LOW", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 1, + "arch-linux": 3, + "nvd": 3, + "oracle-oval": 2, + "photon": 3, + "redhat": 1, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00008.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00017.html", + "http://www.openwall.com/lists/oss-security/2019/09/11/6", + "https://access.redhat.com/security/cve/CVE-2019-5436", + "https://curl.haxx.se/docs/CVE-2019-5436.html", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5436", + "https://linux.oracle.com/cve/CVE-2019-5436.html", + "https://linux.oracle.com/errata/ELSA-2020-1792.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/SMG3V4VTX2SE3EW3HQTN3DDLQBTORQC2/", + "https://seclists.org/bugtraq/2020/Feb/36", + "https://security.gentoo.org/glsa/202003-29", + "https://security.netapp.com/advisory/ntap-20190606-0004/", + "https://support.f5.com/csp/article/K55133295", + "https://support.f5.com/csp/article/K55133295?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-3993-1", + "https://ubuntu.com/security/notices/USN-3993-2", + "https://www.debian.org/security/2020/dsa-4633", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + ], + "PublishedDate": "2019-05-28T19:29:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + } + ] + } + ] +} 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 000000000000..27cda6c2b6e7 --- /dev/null +++ b/integration/testdata/amazonlinux2-gp2-x86-vm.json.golden @@ -0,0 +1,78 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "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-38177", + "PkgID": "bind-export-libs@9.11.4-26.P2.amzn2.5.2.x86_64", + "PkgName": "bind-export-libs", + "PkgIdentifier": { + "PURL": "pkg:rpm/amazon/bind-export-libs@9.11.4-26.P2.amzn2.5.2?arch=x86_64\u0026distro=amazon-2+%28Karoo%29\u0026epoch=32" + }, + "InstalledVersion": "32:9.11.4-26.P2.amzn2.5.2", + "FixedVersion": "99:9.11.4-26.P2.amzn2.13", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-38177", + "DataSource": { + "ID": "amazon", + "Name": "Amazon Linux Security Center", + "URL": "https://alas.aws.amazon.com/" + }, + "Title": "bind: memory leak in ECDSA DNSSEC verification code", + "Description": "By spoofing the target resolver with responses that have a malformed ECDSA signature, an attacker can trigger a small memory leak. It is possible to gradually erode available memory to the point where named crashes for lack of resources.", + "Severity": "MEDIUM", + "VendorSeverity": { + "arch-linux": 2, + "nvd": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", + "V3Score": 7.5 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", + "V3Score": 7.5 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2022/09/21/3", + "https://access.redhat.com/errata/RHSA-2022:6763", + "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2022-38177.json", + "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2022-38178.json", + "https://access.redhat.com/security/cve/CVE-2022-38177" + ], + "PublishedDate": "2022-09-21T11:15:00Z", + "LastModifiedDate": "2022-09-21T11:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/busybox-with-lockfile.json.golden b/integration/testdata/busybox-with-lockfile.json.golden new file mode 100644 index 000000000000..520afe2de928 --- /dev/null +++ b/integration/testdata/busybox-with-lockfile.json.golden @@ -0,0 +1,150 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/busybox-with-lockfile.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "ImageID": "sha256:88702f6b6133bf06cc46af48437d0c0fc661239155548757c65916504a0e5eee", + "DiffIDs": [ + "sha256:797ac4999b67d8c38a596919efa5b7b6a4a8fd5814cb8564efa482c5d8403e6d", + "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f" + ], + "ImageConfig": { + "architecture": "amd64", + "created": "2022-06-07T04:24:40.230164Z", + "docker_version": "20.10.14", + "history": [ + { + "created": "2022-03-11T20:19:46.778911455Z", + "created_by": "/bin/sh -c #(nop) ADD file:39f6523fbc03f554a59461a34850d68c31cd5822e5a6fddf2d0ea198ed9a11c4 in / " + }, + { + "created": "2022-03-11T20:19:46.866228701Z", + "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]", + "empty_layer": true + }, + { + "created": "2022-06-07T04:24:40.230164Z", + "created_by": "/bin/sh -c #(nop) COPY file:343df0159abcc51b06b4e56bfd4c06d2003b88947ed93b0cec6214ae5985669e in . " + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:797ac4999b67d8c38a596919efa5b7b6a4a8fd5814cb8564efa482c5d8403e6d", + "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f" + ] + }, + "config": { + "Cmd": [ + "sh" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:2fb6fc2d97e10c79983aa10e013824cc7fc8bae50630e32159821197dda95fe3" + } + } + }, + "Results": [ + { + "Target": "Cargo.lock", + "Class": "lang-pkgs", + "Type": "cargo", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-15542", + "PkgID": "ammonia@1.9.0", + "PkgName": "ammonia", + "PkgIdentifier": { + "PURL": "pkg:cargo/ammonia@1.9.0" + }, + "InstalledVersion": "1.9.0", + "FixedVersion": "\u003e= 2.1.0", + "Status": "fixed", + "Layer": { + "Digest": "sha256:fd2e3bc9bccc9c677572a542d020998389de94f127ca2c252ae627fc7c241cee", + "DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-15542", + "DataSource": { + "Name": "RustSec Advisory Database", + "URL": "https://github.com/RustSec/advisory-db" + }, + "Title": "Uncontrolled recursion leads to abort in HTML serialization", + "Description": "An issue was discovered in the ammonia crate before 2.1.0 for Rust. There is uncontrolled recursion during HTML DOM tree serialization.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-674" + ], + "VendorSeverity": { + "nvd": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V2Score": 5, + "V3Score": 7.5 + } + }, + "References": [ + "https://crates.io/crates/ammonia", + "https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md#210", + "https://rustsec.org/advisories/RUSTSEC-2019-0001.html" + ], + "PublishedDate": "2019-08-26T18:15:00Z", + "LastModifiedDate": "2020-08-24T17:37:00Z" + }, + { + "VulnerabilityID": "CVE-2021-38193", + "PkgID": "ammonia@1.9.0", + "PkgName": "ammonia", + "PkgIdentifier": { + "PURL": "pkg:cargo/ammonia@1.9.0" + }, + "InstalledVersion": "1.9.0", + "FixedVersion": "\u003e= 3.1.0, \u003e= 2.1.3, \u003c 3.0.0", + "Status": "fixed", + "Layer": { + "Digest": "sha256:fd2e3bc9bccc9c677572a542d020998389de94f127ca2c252ae627fc7c241cee", + "DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-38193", + "DataSource": { + "Name": "RustSec Advisory Database", + "URL": "https://github.com/RustSec/advisory-db" + }, + "Title": "Incorrect handling of embedded SVG and MathML leads to mutation XSS", + "Description": "An issue was discovered in the ammonia crate before 3.1.0 for Rust. XSS can occur because the parsing differences for HTML, SVG, and MathML are mishandled, a similar issue to CVE-2020-26870.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-79" + ], + "VendorSeverity": { + "nvd": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "V2Score": 4.3, + "V3Score": 6.1 + } + }, + "References": [ + "https://crates.io/crates/ammonia", + "https://github.com/rust-ammonia/ammonia/pull/142", + "https://raw.githubusercontent.com/rustsec/advisory-db/main/crates/ammonia/RUSTSEC-2021-0074.md", + "https://rustsec.org/advisories/RUSTSEC-2021-0074.html" + ], + "PublishedDate": "2021-08-08T06:15:00Z", + "LastModifiedDate": "2021-08-16T16:37:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/centos-6.json.golden b/integration/testdata/centos-6.json.golden new file mode 100644 index 000000000000..c1791c58a486 --- /dev/null +++ b/integration/testdata/centos-6.json.golden @@ -0,0 +1,227 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/centos-6.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "centos", + "Name": "6.10", + "EOSL": true + }, + "ImageID": "sha256:5bf9684f472089d6d5cb636041d3d6dc748dbde39f1aefc374bbd367bd2aabbf", + "DiffIDs": [ + "sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + ], + "ImageConfig": { + "architecture": "amd64", + "author": "https://github.com/CentOS/sig-cloud-instance-images", + "container": "39f18700e994db8cbf9769e400afd93f3e9d0b00e06d44fe31f5e81513204d42", + "created": "2021-09-15T18:20:39.183628076Z", + "docker_version": "20.10.7", + "history": [ + { + "author": "https://github.com/CentOS/sig-cloud-instance-images", + "created": "2021-09-15T18:20:32.237976425Z", + "created_by": "/bin/sh -c #(nop) MAINTAINER https://github.com/CentOS/sig-cloud-instance-images", + "empty_layer": true + }, + { + "author": "https://github.com/CentOS/sig-cloud-instance-images", + "created": "2021-09-15T18:20:38.186052996Z", + "created_by": "/bin/sh -c #(nop) ADD file:0065316a41144e95bcb133567cc86816b8368a823cc067d741e06ded59849fd8 in / " + }, + { + "author": "https://github.com/CentOS/sig-cloud-instance-images", + "created": "2021-09-15T18:20:38.990977879Z", + "created_by": "/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20181006", + "empty_layer": true + }, + { + "author": "https://github.com/CentOS/sig-cloud-instance-images", + "created": "2021-09-15T18:20:39.183628076Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:60e456a7493aee808844ffd1e87bffc99b84bb095366bfd68b8843e0328f9e20", + "Labels": { + "org.label-schema.build-date": "20181006", + "org.label-schema.license": "GPLv2", + "org.label-schema.name": "CentOS Base Image", + "org.label-schema.schema-version": "1.0", + "org.label-schema.vendor": "CentOS" + } + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/centos-6.tar.gz (centos 6.10)", + "Class": "os-pkgs", + "Type": "centos", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-29573", + "PkgID": "glibc@2.12-1.212.el6.x86_64", + "PkgName": "glibc", + "PkgIdentifier": { + "PURL": "pkg:rpm/centos/glibc@2.12-1.212.el6?arch=x86_64\u0026distro=centos-6.10" + }, + "InstalledVersion": "2.12-1.212.el6", + "Status": "end_of_life", + "Layer": { + "Digest": "sha256:ff50d722b38227ec8f2bbf0cdbce428b66745077c173d8117d91376128fa532e", + "DiffID": "sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-29573", + "Title": "glibc: stack-based buffer overflow if the input to any of the printf family of functions is an 80-bit long double with a non-canonical bit pattern", + "Description": "sysdeps/i386/ldbl2mpn.c in the GNU C Library (aka glibc or libc6) before 2.23 on x86 targets has a stack-based buffer overflow if the input to any of the printf family of functions is an 80-bit long double with a non-canonical bit pattern, as seen when passing a \\x00\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x04 value to sprintf. NOTE: the issue does not affect glibc by default in 2016 or later (i.e., 2.23 or later) because of commits made in 2015 for inlining of C99 math functions through use of GCC built-ins. In other words, the reference to 2.23 is intentional despite the mention of \"Fixed for glibc 2.33\" in the 26649 reference.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 2, + "nvd": 3, + "oracle-oval": 2, + "photon": 3, + "redhat": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V2Score": 5, + "V3Score": 7.5 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2020-29573", + "https://linux.oracle.com/cve/CVE-2020-29573.html", + "https://linux.oracle.com/errata/ELSA-2021-0348.html", + "https://security.gentoo.org/glsa/202101-20", + "https://security.netapp.com/advisory/ntap-20210122-0004/", + "https://sourceware.org/bugzilla/show_bug.cgi?id=26649", + "https://sourceware.org/pipermail/libc-alpha/2020-September/117779.html" + ], + "PublishedDate": "2020-12-06T00:15:00Z", + "LastModifiedDate": "2021-01-26T18:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1559", + "VendorIDs": [ + "RHSA-2019:2471" + ], + "PkgID": "openssl@1.0.1e-57.el6.x86_64", + "PkgName": "openssl", + "PkgIdentifier": { + "PURL": "pkg:rpm/centos/openssl@1.0.1e-57.el6?arch=x86_64\u0026distro=centos-6.10" + }, + "InstalledVersion": "1.0.1e-57.el6", + "FixedVersion": "1.0.1e-58.el6_10", + "Status": "fixed", + "Layer": { + "Digest": "sha256:ff50d722b38227ec8f2bbf0cdbce428b66745077c173d8117d91376128fa532e", + "DiffID": "sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1559", + "Title": "openssl: 0-byte record padding oracle", + "Description": "If an application encounters a fatal protocol error and then calls SSL_shutdown() twice (once to send a close_notify, and once to receive one) then OpenSSL can respond differently to the calling application if a 0 byte record is received with invalid padding compared to if a 0 byte record is received with an invalid MAC. If the application then behaves differently based on that in a way that is detectable to the remote peer, then this amounts to a padding oracle that could be used to decrypt data. In order for this to be exploitable \"non-stitched\" ciphersuites must be in use. Stitched ciphersuites are optimised implementations of certain commonly used ciphersuites. Also the application must call SSL_shutdown() twice even if a protocol error has occurred (applications should not do this but some do anyway). Fixed in OpenSSL 1.0.2r (Affected 1.0.2-1.0.2q).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-203" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 2, + "nvd": 2, + "oracle-oval": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V2Score": 4.3, + "V3Score": 5.9 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 5.9 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-03/msg00041.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00019.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00046.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00047.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-05/msg00049.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00080.html", + "http://www.securityfocus.com/bid/107174", + "https://access.redhat.com/errata/RHSA-2019:2304", + "https://access.redhat.com/errata/RHSA-2019:2437", + "https://access.redhat.com/errata/RHSA-2019:2439", + "https://access.redhat.com/errata/RHSA-2019:2471", + "https://access.redhat.com/errata/RHSA-2019:3929", + "https://access.redhat.com/errata/RHSA-2019:3931", + "https://access.redhat.com/security/cve/CVE-2019-1559", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1559", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e9bbefbf0f24c57645e7ad6a5a71ae649d18ac8e", + "https://github.com/RUB-NDS/TLS-Padding-Oracles", + "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10282", + "https://linux.oracle.com/cve/CVE-2019-1559.html", + "https://linux.oracle.com/errata/ELSA-2019-2471.html", + "https://lists.debian.org/debian-lts-announce/2019/03/msg00003.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EWC42UXL5GHTU5G77VKBF6JYUUNGSHOM/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Y3IVFGSERAZLNJCK35TEM2R4726XIH3Z/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBEV5QGDRFUZDMNECFXUSN5FMYOZDE4V/", + "https://security.gentoo.org/glsa/201903-10", + "https://security.netapp.com/advisory/ntap-20190301-0001/", + "https://security.netapp.com/advisory/ntap-20190301-0002/", + "https://security.netapp.com/advisory/ntap-20190423-0002/", + "https://support.f5.com/csp/article/K18549143", + "https://support.f5.com/csp/article/K18549143?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-3899-1", + "https://ubuntu.com/security/notices/USN-4376-2", + "https://usn.ubuntu.com/3899-1/", + "https://usn.ubuntu.com/4376-2/", + "https://www.debian.org/security/2019/dsa-4400", + "https://www.openssl.org/news/secadv/20190226.txt", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.tenable.com/security/tns-2019-02", + "https://www.tenable.com/security/tns-2019-03" + ], + "PublishedDate": "2019-02-27T23:29:00Z", + "LastModifiedDate": "2021-01-20T15:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/centos-7-ignore-unfixed.json.golden b/integration/testdata/centos-7-ignore-unfixed.json.golden new file mode 100644 index 000000000000..ad8379a5d80e --- /dev/null +++ b/integration/testdata/centos-7-ignore-unfixed.json.golden @@ -0,0 +1,251 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/centos-7.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "centos", + "Name": "7.6.1810" + }, + "ImageID": "sha256:f1cb7c7d58b73eac859c395882eec49d50651244e342cd6c68a5c7809785f427", + "DiffIDs": [ + "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "cc6043a787f6d1c7ae3e121ebdf1c4478186336aa7274871780a0a7bcc3a061a", + "created": "2019-03-14T21:20:29.635970966Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-03-14T21:20:28.997703205Z", + "created_by": "/bin/sh -c #(nop) ADD file:54b004357379717dfb7ea6f024ca80ce762ea4b06647fcddc0f6697146551172 in / " + }, + { + "created": "2019-03-14T21:20:29.452720615Z", + "created_by": "/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20181204", + "empty_layer": true + }, + { + "created": "2019-03-14T21:20:29.635970966Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:698a0848ee35389ab7b98494bdc60f887c54ddb94fc2326a1fb4eff8895aff43", + "Labels": { + "org.label-schema.build-date": "20181204", + "org.label-schema.license": "GPLv2", + "org.label-schema.name": "CentOS Base Image", + "org.label-schema.schema-version": "1.0", + "org.label-schema.vendor": "CentOS" + }, + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/centos-7.tar.gz (centos 7.6.1810)", + "Class": "os-pkgs", + "Type": "centos", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-1559", + "VendorIDs": [ + "RHSA-2019:2304" + ], + "PkgID": "openssl-libs@1.0.2k-16.el7.x86_64", + "PkgName": "openssl-libs", + "PkgIdentifier": { + "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1" + }, + "InstalledVersion": "1:1.0.2k-16.el7", + "FixedVersion": "1:1.0.2k-19.el7", + "Status": "fixed", + "Layer": { + "Digest": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "DiffID": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1559", + "Title": "openssl: 0-byte record padding oracle", + "Description": "If an application encounters a fatal protocol error and then calls SSL_shutdown() twice (once to send a close_notify, and once to receive one) then OpenSSL can respond differently to the calling application if a 0 byte record is received with invalid padding compared to if a 0 byte record is received with an invalid MAC. If the application then behaves differently based on that in a way that is detectable to the remote peer, then this amounts to a padding oracle that could be used to decrypt data. In order for this to be exploitable \"non-stitched\" ciphersuites must be in use. Stitched ciphersuites are optimised implementations of certain commonly used ciphersuites. Also the application must call SSL_shutdown() twice even if a protocol error has occurred (applications should not do this but some do anyway). Fixed in OpenSSL 1.0.2r (Affected 1.0.2-1.0.2q).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-203" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 2, + "nvd": 2, + "oracle-oval": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V2Score": 4.3, + "V3Score": 5.9 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 5.9 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-03/msg00041.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00019.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00046.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00047.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-05/msg00049.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00080.html", + "http://www.securityfocus.com/bid/107174", + "https://access.redhat.com/errata/RHSA-2019:2304", + "https://access.redhat.com/errata/RHSA-2019:2437", + "https://access.redhat.com/errata/RHSA-2019:2439", + "https://access.redhat.com/errata/RHSA-2019:2471", + "https://access.redhat.com/errata/RHSA-2019:3929", + "https://access.redhat.com/errata/RHSA-2019:3931", + "https://access.redhat.com/security/cve/CVE-2019-1559", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1559", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e9bbefbf0f24c57645e7ad6a5a71ae649d18ac8e", + "https://github.com/RUB-NDS/TLS-Padding-Oracles", + "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10282", + "https://linux.oracle.com/cve/CVE-2019-1559.html", + "https://linux.oracle.com/errata/ELSA-2019-2471.html", + "https://lists.debian.org/debian-lts-announce/2019/03/msg00003.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EWC42UXL5GHTU5G77VKBF6JYUUNGSHOM/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Y3IVFGSERAZLNJCK35TEM2R4726XIH3Z/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBEV5QGDRFUZDMNECFXUSN5FMYOZDE4V/", + "https://security.gentoo.org/glsa/201903-10", + "https://security.netapp.com/advisory/ntap-20190301-0001/", + "https://security.netapp.com/advisory/ntap-20190301-0002/", + "https://security.netapp.com/advisory/ntap-20190423-0002/", + "https://support.f5.com/csp/article/K18549143", + "https://support.f5.com/csp/article/K18549143?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-3899-1", + "https://ubuntu.com/security/notices/USN-4376-2", + "https://usn.ubuntu.com/3899-1/", + "https://usn.ubuntu.com/4376-2/", + "https://www.debian.org/security/2019/dsa-4400", + "https://www.openssl.org/news/secadv/20190226.txt", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.tenable.com/security/tns-2019-02", + "https://www.tenable.com/security/tns-2019-03" + ], + "PublishedDate": "2019-02-27T23:29:00Z", + "LastModifiedDate": "2021-01-20T15:15:00Z" + }, + { + "VulnerabilityID": "CVE-2018-0734", + "VendorIDs": [ + "RHSA-2019:2304" + ], + "PkgID": "openssl-libs@1.0.2k-16.el7.x86_64", + "PkgName": "openssl-libs", + "PkgIdentifier": { + "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1" + }, + "InstalledVersion": "1:1.0.2k-16.el7", + "FixedVersion": "1:1.0.2k-19.el7", + "Status": "fixed", + "Layer": { + "Digest": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "DiffID": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2018-0734", + "Title": "openssl: timing side channel attack in the DSA signature algorithm", + "Description": "The OpenSSL DSA signature algorithm has been shown to be vulnerable to a timing side channel attack. An attacker could use variations in the signing algorithm to recover the private key. Fixed in OpenSSL 1.1.1a (Affected 1.1.1). Fixed in OpenSSL 1.1.0j (Affected 1.1.0-1.1.0i). Fixed in OpenSSL 1.0.2q (Affected 1.0.2-1.0.2p).", + "Severity": "LOW", + "CweIDs": [ + "CWE-327" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 1, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V2Score": 4.3, + "V3Score": 5.9 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 5.1 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00030.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-07/msg00056.html", + "http://www.securityfocus.com/bid/105758", + "https://access.redhat.com/errata/RHSA-2019:2304", + "https://access.redhat.com/errata/RHSA-2019:3700", + "https://access.redhat.com/errata/RHSA-2019:3932", + "https://access.redhat.com/errata/RHSA-2019:3933", + "https://access.redhat.com/errata/RHSA-2019:3935", + "https://access.redhat.com/security/cve/CVE-2018-0734", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0734", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=43e6a58d4991a451daf4891ff05a48735df871ac", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=8abfe72e8c1de1b95f50aa0d9134803b4d00070f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=ef11e19d1365eea2b1851e6f540a0bf365d303e7", + "https://linux.oracle.com/cve/CVE-2018-0734.html", + "https://linux.oracle.com/errata/ELSA-2019-3700.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EWC42UXL5GHTU5G77VKBF6JYUUNGSHOM/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Y3IVFGSERAZLNJCK35TEM2R4726XIH3Z/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBEV5QGDRFUZDMNECFXUSN5FMYOZDE4V/", + "https://nodejs.org/en/blog/vulnerability/november-2018-security-releases/", + "https://nvd.nist.gov/vuln/detail/CVE-2018-0734", + "https://security.netapp.com/advisory/ntap-20181105-0002/", + "https://security.netapp.com/advisory/ntap-20190118-0002/", + "https://security.netapp.com/advisory/ntap-20190423-0002/", + "https://ubuntu.com/security/notices/USN-3840-1", + "https://usn.ubuntu.com/3840-1/", + "https://www.debian.org/security/2018/dsa-4348", + "https://www.debian.org/security/2018/dsa-4355", + "https://www.openssl.org/news/secadv/20181030.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html", + "https://www.oracle.com/technetwork/security-advisory/cpujan2019-5072801.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.tenable.com/security/tns-2018-16", + "https://www.tenable.com/security/tns-2018-17" + ], + "PublishedDate": "2018-10-30T12:29:00Z", + "LastModifiedDate": "2020-08-24T17:37:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/centos-7-medium.json.golden b/integration/testdata/centos-7-medium.json.golden new file mode 100644 index 000000000000..ef4a44d2bbe5 --- /dev/null +++ b/integration/testdata/centos-7-medium.json.golden @@ -0,0 +1,164 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/centos-7.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "centos", + "Name": "7.6.1810" + }, + "ImageID": "sha256:f1cb7c7d58b73eac859c395882eec49d50651244e342cd6c68a5c7809785f427", + "DiffIDs": [ + "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "cc6043a787f6d1c7ae3e121ebdf1c4478186336aa7274871780a0a7bcc3a061a", + "created": "2019-03-14T21:20:29.635970966Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-03-14T21:20:28.997703205Z", + "created_by": "/bin/sh -c #(nop) ADD file:54b004357379717dfb7ea6f024ca80ce762ea4b06647fcddc0f6697146551172 in / " + }, + { + "created": "2019-03-14T21:20:29.452720615Z", + "created_by": "/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20181204", + "empty_layer": true + }, + { + "created": "2019-03-14T21:20:29.635970966Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:698a0848ee35389ab7b98494bdc60f887c54ddb94fc2326a1fb4eff8895aff43", + "Labels": { + "org.label-schema.build-date": "20181204", + "org.label-schema.license": "GPLv2", + "org.label-schema.name": "CentOS Base Image", + "org.label-schema.schema-version": "1.0", + "org.label-schema.vendor": "CentOS" + }, + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/centos-7.tar.gz (centos 7.6.1810)", + "Class": "os-pkgs", + "Type": "centos", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-1559", + "VendorIDs": [ + "RHSA-2019:2304" + ], + "PkgID": "openssl-libs@1.0.2k-16.el7.x86_64", + "PkgName": "openssl-libs", + "PkgIdentifier": { + "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1" + }, + "InstalledVersion": "1:1.0.2k-16.el7", + "FixedVersion": "1:1.0.2k-19.el7", + "Status": "fixed", + "Layer": { + "Digest": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "DiffID": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1559", + "Title": "openssl: 0-byte record padding oracle", + "Description": "If an application encounters a fatal protocol error and then calls SSL_shutdown() twice (once to send a close_notify, and once to receive one) then OpenSSL can respond differently to the calling application if a 0 byte record is received with invalid padding compared to if a 0 byte record is received with an invalid MAC. If the application then behaves differently based on that in a way that is detectable to the remote peer, then this amounts to a padding oracle that could be used to decrypt data. In order for this to be exploitable \"non-stitched\" ciphersuites must be in use. Stitched ciphersuites are optimised implementations of certain commonly used ciphersuites. Also the application must call SSL_shutdown() twice even if a protocol error has occurred (applications should not do this but some do anyway). Fixed in OpenSSL 1.0.2r (Affected 1.0.2-1.0.2q).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-203" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 2, + "nvd": 2, + "oracle-oval": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V2Score": 4.3, + "V3Score": 5.9 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 5.9 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-03/msg00041.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00019.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00046.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00047.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-05/msg00049.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00080.html", + "http://www.securityfocus.com/bid/107174", + "https://access.redhat.com/errata/RHSA-2019:2304", + "https://access.redhat.com/errata/RHSA-2019:2437", + "https://access.redhat.com/errata/RHSA-2019:2439", + "https://access.redhat.com/errata/RHSA-2019:2471", + "https://access.redhat.com/errata/RHSA-2019:3929", + "https://access.redhat.com/errata/RHSA-2019:3931", + "https://access.redhat.com/security/cve/CVE-2019-1559", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1559", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e9bbefbf0f24c57645e7ad6a5a71ae649d18ac8e", + "https://github.com/RUB-NDS/TLS-Padding-Oracles", + "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10282", + "https://linux.oracle.com/cve/CVE-2019-1559.html", + "https://linux.oracle.com/errata/ELSA-2019-2471.html", + "https://lists.debian.org/debian-lts-announce/2019/03/msg00003.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EWC42UXL5GHTU5G77VKBF6JYUUNGSHOM/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Y3IVFGSERAZLNJCK35TEM2R4726XIH3Z/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBEV5QGDRFUZDMNECFXUSN5FMYOZDE4V/", + "https://security.gentoo.org/glsa/201903-10", + "https://security.netapp.com/advisory/ntap-20190301-0001/", + "https://security.netapp.com/advisory/ntap-20190301-0002/", + "https://security.netapp.com/advisory/ntap-20190423-0002/", + "https://support.f5.com/csp/article/K18549143", + "https://support.f5.com/csp/article/K18549143?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-3899-1", + "https://ubuntu.com/security/notices/USN-4376-2", + "https://usn.ubuntu.com/3899-1/", + "https://usn.ubuntu.com/4376-2/", + "https://www.debian.org/security/2019/dsa-4400", + "https://www.openssl.org/news/secadv/20190226.txt", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.tenable.com/security/tns-2019-02", + "https://www.tenable.com/security/tns-2019-03" + ], + "PublishedDate": "2019-02-27T23:29:00Z", + "LastModifiedDate": "2021-01-20T15:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/centos-7.json.golden b/integration/testdata/centos-7.json.golden new file mode 100644 index 000000000000..55ea768c99a8 --- /dev/null +++ b/integration/testdata/centos-7.json.golden @@ -0,0 +1,308 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/centos-7.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "centos", + "Name": "7.6.1810" + }, + "ImageID": "sha256:f1cb7c7d58b73eac859c395882eec49d50651244e342cd6c68a5c7809785f427", + "DiffIDs": [ + "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "cc6043a787f6d1c7ae3e121ebdf1c4478186336aa7274871780a0a7bcc3a061a", + "created": "2019-03-14T21:20:29.635970966Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-03-14T21:20:28.997703205Z", + "created_by": "/bin/sh -c #(nop) ADD file:54b004357379717dfb7ea6f024ca80ce762ea4b06647fcddc0f6697146551172 in / " + }, + { + "created": "2019-03-14T21:20:29.452720615Z", + "created_by": "/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20181204", + "empty_layer": true + }, + { + "created": "2019-03-14T21:20:29.635970966Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:698a0848ee35389ab7b98494bdc60f887c54ddb94fc2326a1fb4eff8895aff43", + "Labels": { + "org.label-schema.build-date": "20181204", + "org.label-schema.license": "GPLv2", + "org.label-schema.name": "CentOS Base Image", + "org.label-schema.schema-version": "1.0", + "org.label-schema.vendor": "CentOS" + }, + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/centos-7.tar.gz (centos 7.6.1810)", + "Class": "os-pkgs", + "Type": "centos", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@4.2.46-31.el7.x86_64", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64\u0026distro=centos-7.6.1810" + }, + "InstalledVersion": "4.2.46-31.el7", + "Status": "will_not_fix", + "Layer": { + "Digest": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "DiffID": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276", + "Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped", + "Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "Severity": "LOW", + "CweIDs": [ + "CWE-273" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "nvd": 3, + "oracle-oval": 1, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.2, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html", + "https://access.redhat.com/security/cve/CVE-2019-18276", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276", + "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff", + "https://linux.oracle.com/cve/CVE-2019-18276.html", + "https://linux.oracle.com/errata/ELSA-2021-1679.html", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://nvd.nist.gov/vuln/detail/CVE-2019-18276", + "https://security.gentoo.org/glsa/202105-34", + "https://security.netapp.com/advisory/ntap-20200430-0003/", + "https://www.youtube.com/watch?v=-wGtxJ8opa8" + ], + "PublishedDate": "2019-11-28T01:15:00Z", + "LastModifiedDate": "2021-05-26T12:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1559", + "VendorIDs": [ + "RHSA-2019:2304" + ], + "PkgID": "openssl-libs@1.0.2k-16.el7.x86_64", + "PkgName": "openssl-libs", + "PkgIdentifier": { + "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1" + }, + "InstalledVersion": "1:1.0.2k-16.el7", + "FixedVersion": "1:1.0.2k-19.el7", + "Status": "fixed", + "Layer": { + "Digest": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "DiffID": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1559", + "Title": "openssl: 0-byte record padding oracle", + "Description": "If an application encounters a fatal protocol error and then calls SSL_shutdown() twice (once to send a close_notify, and once to receive one) then OpenSSL can respond differently to the calling application if a 0 byte record is received with invalid padding compared to if a 0 byte record is received with an invalid MAC. If the application then behaves differently based on that in a way that is detectable to the remote peer, then this amounts to a padding oracle that could be used to decrypt data. In order for this to be exploitable \"non-stitched\" ciphersuites must be in use. Stitched ciphersuites are optimised implementations of certain commonly used ciphersuites. Also the application must call SSL_shutdown() twice even if a protocol error has occurred (applications should not do this but some do anyway). Fixed in OpenSSL 1.0.2r (Affected 1.0.2-1.0.2q).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-203" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 2, + "nvd": 2, + "oracle-oval": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V2Score": 4.3, + "V3Score": 5.9 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 5.9 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-03/msg00041.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00019.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00046.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00047.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-05/msg00049.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00080.html", + "http://www.securityfocus.com/bid/107174", + "https://access.redhat.com/errata/RHSA-2019:2304", + "https://access.redhat.com/errata/RHSA-2019:2437", + "https://access.redhat.com/errata/RHSA-2019:2439", + "https://access.redhat.com/errata/RHSA-2019:2471", + "https://access.redhat.com/errata/RHSA-2019:3929", + "https://access.redhat.com/errata/RHSA-2019:3931", + "https://access.redhat.com/security/cve/CVE-2019-1559", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1559", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e9bbefbf0f24c57645e7ad6a5a71ae649d18ac8e", + "https://github.com/RUB-NDS/TLS-Padding-Oracles", + "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10282", + "https://linux.oracle.com/cve/CVE-2019-1559.html", + "https://linux.oracle.com/errata/ELSA-2019-2471.html", + "https://lists.debian.org/debian-lts-announce/2019/03/msg00003.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EWC42UXL5GHTU5G77VKBF6JYUUNGSHOM/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Y3IVFGSERAZLNJCK35TEM2R4726XIH3Z/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBEV5QGDRFUZDMNECFXUSN5FMYOZDE4V/", + "https://security.gentoo.org/glsa/201903-10", + "https://security.netapp.com/advisory/ntap-20190301-0001/", + "https://security.netapp.com/advisory/ntap-20190301-0002/", + "https://security.netapp.com/advisory/ntap-20190423-0002/", + "https://support.f5.com/csp/article/K18549143", + "https://support.f5.com/csp/article/K18549143?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-3899-1", + "https://ubuntu.com/security/notices/USN-4376-2", + "https://usn.ubuntu.com/3899-1/", + "https://usn.ubuntu.com/4376-2/", + "https://www.debian.org/security/2019/dsa-4400", + "https://www.openssl.org/news/secadv/20190226.txt", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.tenable.com/security/tns-2019-02", + "https://www.tenable.com/security/tns-2019-03" + ], + "PublishedDate": "2019-02-27T23:29:00Z", + "LastModifiedDate": "2021-01-20T15:15:00Z" + }, + { + "VulnerabilityID": "CVE-2018-0734", + "VendorIDs": [ + "RHSA-2019:2304" + ], + "PkgID": "openssl-libs@1.0.2k-16.el7.x86_64", + "PkgName": "openssl-libs", + "PkgIdentifier": { + "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1" + }, + "InstalledVersion": "1:1.0.2k-16.el7", + "FixedVersion": "1:1.0.2k-19.el7", + "Status": "fixed", + "Layer": { + "Digest": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "DiffID": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2018-0734", + "Title": "openssl: timing side channel attack in the DSA signature algorithm", + "Description": "The OpenSSL DSA signature algorithm has been shown to be vulnerable to a timing side channel attack. An attacker could use variations in the signing algorithm to recover the private key. Fixed in OpenSSL 1.1.1a (Affected 1.1.1). Fixed in OpenSSL 1.1.0j (Affected 1.1.0-1.1.0i). Fixed in OpenSSL 1.0.2q (Affected 1.0.2-1.0.2p).", + "Severity": "LOW", + "CweIDs": [ + "CWE-327" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 1, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V2Score": 4.3, + "V3Score": 5.9 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 5.1 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00030.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-07/msg00056.html", + "http://www.securityfocus.com/bid/105758", + "https://access.redhat.com/errata/RHSA-2019:2304", + "https://access.redhat.com/errata/RHSA-2019:3700", + "https://access.redhat.com/errata/RHSA-2019:3932", + "https://access.redhat.com/errata/RHSA-2019:3933", + "https://access.redhat.com/errata/RHSA-2019:3935", + "https://access.redhat.com/security/cve/CVE-2018-0734", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0734", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=43e6a58d4991a451daf4891ff05a48735df871ac", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=8abfe72e8c1de1b95f50aa0d9134803b4d00070f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=ef11e19d1365eea2b1851e6f540a0bf365d303e7", + "https://linux.oracle.com/cve/CVE-2018-0734.html", + "https://linux.oracle.com/errata/ELSA-2019-3700.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EWC42UXL5GHTU5G77VKBF6JYUUNGSHOM/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Y3IVFGSERAZLNJCK35TEM2R4726XIH3Z/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBEV5QGDRFUZDMNECFXUSN5FMYOZDE4V/", + "https://nodejs.org/en/blog/vulnerability/november-2018-security-releases/", + "https://nvd.nist.gov/vuln/detail/CVE-2018-0734", + "https://security.netapp.com/advisory/ntap-20181105-0002/", + "https://security.netapp.com/advisory/ntap-20190118-0002/", + "https://security.netapp.com/advisory/ntap-20190423-0002/", + "https://ubuntu.com/security/notices/USN-3840-1", + "https://usn.ubuntu.com/3840-1/", + "https://www.debian.org/security/2018/dsa-4348", + "https://www.debian.org/security/2018/dsa-4355", + "https://www.openssl.org/news/secadv/20181030.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html", + "https://www.oracle.com/technetwork/security-advisory/cpujan2019-5072801.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.tenable.com/security/tns-2018-16", + "https://www.tenable.com/security/tns-2018-17" + ], + "PublishedDate": "2018-10-30T12:29:00Z", + "LastModifiedDate": "2020-08-24T17:37:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/cocoapods.json.golden b/integration/testdata/cocoapods.json.golden new file mode 100644 index 000000000000..9553f0624286 --- /dev/null +++ b/integration/testdata/cocoapods.json.golden @@ -0,0 +1,71 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/cocoapods", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Podfile.lock", + "Class": "lang-pkgs", + "Type": "cocoapods", + "Packages": [ + { + "ID": "_NIODataStructures@2.41.0", + "Name": "_NIODataStructures", + "Identifier": { + "PURL": "pkg:cocoapods/_NIODataStructures@2.41.0" + }, + "Version": "2.41.0", + "Layer": {} + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-3215", + "PkgID": "_NIODataStructures@2.41.0", + "PkgName": "_NIODataStructures", + "PkgIdentifier": { + "PURL": "pkg:cocoapods/_NIODataStructures@2.41.0" + }, + "InstalledVersion": "2.41.0", + "FixedVersion": "2.29.1, 2.39.1, 2.42.0", + "Status": "fixed", + "Layer": {}, + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-3215", + "Title": "SwiftNIO vulnerable to Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')", + "Description": "`NIOHTTP1` and projects using it for generating HTTP responses, including SwiftNIO, can be subject to a HTTP Response Injection attack...", + "Severity": "MEDIUM", + "VendorSeverity": { + "ghsa": 2 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "V3Score": 5.3 + } + }, + "References": [ + "https://github.com/apple/swift-nio/security/advisories/GHSA-7fj7-39wj-c64f", + "https://nvd.nist.gov/vuln/detail/CVE-2022-3215", + "https://github.com/apple/swift-nio/commit/a16e2f54a25b2af217044e5168997009a505930f", + "https://github.com/advisories/GHSA-7fj7-39wj-c64f" + ], + "PublishedDate": "2023-06-07T16:01:53Z", + "LastModifiedDate": "2023-06-19T16:45:07Z" + } + ] + } + ] +} diff --git a/integration/testdata/composer.lock.json.golden b/integration/testdata/composer.lock.json.golden new file mode 100644 index 000000000000..a3bdf13f9ece --- /dev/null +++ b/integration/testdata/composer.lock.json.golden @@ -0,0 +1,109 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/composer", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "composer.lock", + "Class": "lang-pkgs", + "Type": "composer", + "Packages": [ + { + "ID": "guzzlehttp/guzzle@7.4.4", + "Name": "guzzlehttp/guzzle", + "Identifier": { + "PURL": "pkg:composer/guzzlehttp/guzzle@7.4.4" + }, + "Version": "7.4.4", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "guzzlehttp/psr7@1.8.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 9, + "EndLine": 129 + } + ] + }, + { + "ID": "guzzlehttp/psr7@1.8.3", + "Name": "guzzlehttp/psr7", + "Identifier": { + "PURL": "pkg:composer/guzzlehttp/psr7@1.8.3" + }, + "Version": "1.8.3", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 130, + "EndLine": 245 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-24775", + "PkgID": "guzzlehttp/psr7@1.8.3", + "PkgName": "guzzlehttp/psr7", + "PkgIdentifier": { + "PURL": "pkg:composer/guzzlehttp/psr7@1.8.3" + }, + "InstalledVersion": "1.8.3", + "FixedVersion": "1.8.4", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-24775", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Composer", + "URL": "https://github.com/advisories?query=type%%3Areviewed+ecosystem%%3Acomposer" + }, + "Title": "Improper Input Validation in guzzlehttp/psr7", + "Description": "### Impact\nIn proper header parsing. An attacker could sneak in a new line character and pass untrusted values. \n\n### Patches\nThe issue is patched in 1.8.4 and 2.1.1.\n\n### Workarounds\nThere are no known workarounds.\n", + "Severity": "HIGH", + "CweIDs": [ + "CWE-20" + ], + "VendorSeverity": { + "ghsa": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "V3Score": 7.5 + } + }, + "References": [ + "https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96", + "https://nvd.nist.gov/vuln/detail/CVE-2022-24775" + ], + "PublishedDate": "2022-03-25T19:26:33Z", + "LastModifiedDate": "2022-06-14T20:02:29Z" + } + ] + } + ] +} diff --git a/integration/testdata/conan.json.golden b/integration/testdata/conan.json.golden new file mode 100644 index 000000000000..ee60780c1d5f --- /dev/null +++ b/integration/testdata/conan.json.golden @@ -0,0 +1,164 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/conan", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "conan.lock", + "Class": "lang-pkgs", + "Type": "conan", + "Packages": [ + { + "ID": "bzip2/1.0.8", + "Name": "bzip2", + "Identifier": { + "PURL": "pkg:conan/bzip2@1.0.8" + }, + "Version": "1.0.8", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 37, + "EndLine": 43 + } + ] + }, + { + "ID": "expat/2.4.8", + "Name": "expat", + "Identifier": { + "PURL": "pkg:conan/expat@2.4.8" + }, + "Version": "2.4.8", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 51, + "EndLine": 57 + } + ] + }, + { + "ID": "openssl/1.1.1q", + "Name": "openssl", + "Identifier": { + "PURL": "pkg:conan/openssl@1.1.1q" + }, + "Version": "1.1.1q", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 65, + "EndLine": 71 + } + ] + }, + { + "ID": "pcre/8.43", + "Name": "pcre", + "Identifier": { + "PURL": "pkg:conan/pcre@8.43" + }, + "Version": "8.43", + "Indirect": true, + "DependsOn": [ + "bzip2/1.0.8", + "zlib/1.2.12" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 26, + "EndLine": 36 + } + ] + }, + { + "ID": "poco/1.9.4", + "Name": "poco", + "Identifier": { + "PURL": "pkg:conan/poco@1.9.4" + }, + "Version": "1.9.4", + "DependsOn": [ + "pcre/8.43", + "zlib/1.2.12", + "expat/2.4.8", + "sqlite3/3.39.2", + "openssl/1.1.1q" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 12, + "EndLine": 25 + } + ] + }, + { + "ID": "sqlite3/3.39.2", + "Name": "sqlite3", + "Identifier": { + "PURL": "pkg:conan/sqlite3@3.39.2" + }, + "Version": "3.39.2", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 58, + "EndLine": 64 + } + ] + }, + { + "ID": "zlib/1.2.12", + "Name": "zlib", + "Identifier": { + "PURL": "pkg:conan/zlib@1.2.12" + }, + "Version": "1.2.12", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 44, + "EndLine": 50 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-14155", + "PkgID": "pcre/8.43", + "PkgName": "pcre", + "PkgIdentifier": { + "PURL": "pkg:conan/pcre@8.43" + }, + "InstalledVersion": "8.43", + "FixedVersion": "8.45", + "Status": "fixed", + "Layer": {}, + "Severity": "UNKNOWN" + } + ] + } + ] +} diff --git a/integration/testdata/conda-cyclonedx.json.golden b/integration/testdata/conda-cyclonedx.json.golden new file mode 100644 index 000000000000..9640112cce12 --- /dev/null +++ b/integration/testdata/conda-cyclonedx.json.golden @@ -0,0 +1,99 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000004", + "version": 1, + "metadata": { + "timestamp": "2021-08-25T12:20:30+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000001", + "type": "application", + "name": "testdata/fixtures/repo/conda", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:conda/openssl@1.1.1q", + "type": "library", + "name": "openssl", + "version": "1.1.1q", + "licenses": [ + { + "license": { + "name": "OpenSSL" + } + } + ], + "purl": "pkg:conda/openssl@1.1.1q", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "miniconda3/envs/testenv/conda-meta/openssl-1.1.1q-h7f8727e_0.json" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "conda-pkg" + } + ] + }, + { + "bom-ref": "pkg:conda/pip@22.2.2", + "type": "library", + "name": "pip", + "version": "22.2.2", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:conda/pip@22.2.2", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "miniconda3/envs/testenv/conda-meta/pip-22.2.2-py38h06a4308_0.json" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "conda-pkg" + } + ] + } + ], + "dependencies": [ + { + "ref": "3ff14136-e09f-4df9-80ea-000000000001", + "dependsOn": [ + "pkg:conda/openssl@1.1.1q", + "pkg:conda/pip@22.2.2" + ] + }, + { + "ref": "pkg:conda/openssl@1.1.1q", + "dependsOn": [] + }, + { + "ref": "pkg:conda/pip@22.2.2", + "dependsOn": [] + } + ], + "vulnerabilities": [] +} diff --git a/integration/testdata/conda-spdx.json.golden b/integration/testdata/conda-spdx.json.golden new file mode 100644 index 000000000000..db81eb8abd13 --- /dev/null +++ b/integration/testdata/conda-spdx.json.golden @@ -0,0 +1,125 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "testdata/fixtures/repo/conda", + "documentNamespace": "http://aquasecurity.github.io/trivy/filesystem/testdata/fixtures/repo/conda-3ff14136-e09f-4df9-80ea-000000000004", + "creationInfo": { + "creators": [ + "Organization: aquasecurity", + "Tool: trivy-dev" + ], + "created": "2021-08-25T12:20:30Z" + }, + "packages": [ + { + "name": "openssl", + "SPDXID": "SPDXRef-Package-b8061a5279413d55", + "versionInfo": "1.1.1q", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "filesAnalyzed": true, + "packageVerificationCode": { + "packageVerificationCodeValue": "2a05c7ad4baa728c79bc1ee1adea9b837d0bf6c0" + }, + "licenseConcluded": "OpenSSL", + "licenseDeclared": "OpenSSL", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:conda/openssl@1.1.1q" + } + ], + "attributionTexts": [ + "PkgType: conda-pkg" + ], + "primaryPackagePurpose": "LIBRARY" + }, + { + "name": "pip", + "SPDXID": "SPDXRef-Package-84198b3828050c11", + "versionInfo": "22.2.2", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "filesAnalyzed": true, + "packageVerificationCode": { + "packageVerificationCodeValue": "8619446a1cd1118e82f1f984ce59116422a59151" + }, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:conda/pip@22.2.2" + } + ], + "attributionTexts": [ + "PkgType: conda-pkg" + ], + "primaryPackagePurpose": "LIBRARY" + }, + { + "name": "testdata/fixtures/repo/conda", + "SPDXID": "SPDXRef-Filesystem-2e2426fd0f2580ef", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "attributionTexts": [ + "SchemaVersion: 2" + ], + "primaryPackagePurpose": "SOURCE" + } + ], + "files": [ + { + "fileName": "miniconda3/envs/testenv/conda-meta/openssl-1.1.1q-h7f8727e_0.json", + "SPDXID": "SPDXRef-File-600e5e0110a84891", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "237db0da53131e4548cb1181337fa0f420299e1f" + } + ], + "copyrightText": "" + }, + { + "fileName": "miniconda3/envs/testenv/conda-meta/pip-22.2.2-py38h06a4308_0.json", + "SPDXID": "SPDXRef-File-7eb62e2a3edddc0a", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "a6a2db7668f1ad541d704369fc66c96a4415aa24" + } + ], + "copyrightText": "" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-Filesystem-2e2426fd0f2580ef", + "relationshipType": "DESCRIBES" + }, + { + "spdxElementId": "SPDXRef-Filesystem-2e2426fd0f2580ef", + "relatedSpdxElement": "SPDXRef-Package-84198b3828050c11", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Filesystem-2e2426fd0f2580ef", + "relatedSpdxElement": "SPDXRef-Package-b8061a5279413d55", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Package-84198b3828050c11", + "relatedSpdxElement": "SPDXRef-File-7eb62e2a3edddc0a", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Package-b8061a5279413d55", + "relatedSpdxElement": "SPDXRef-File-600e5e0110a84891", + "relationshipType": "CONTAINS" + } + ] +} \ No newline at end of file diff --git a/integration/testdata/debian-buster-ignore-unfixed.json.golden b/integration/testdata/debian-buster-ignore-unfixed.json.golden new file mode 100644 index 000000000000..0d387db18fbf --- /dev/null +++ b/integration/testdata/debian-buster-ignore-unfixed.json.golden @@ -0,0 +1,126 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/debian-buster.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "debian", + "Name": "10.1" + }, + "ImageID": "sha256:c2c03a296d2329a4f3ab72a7bf38b78a8a80108204d326b0139d6af700e152d1", + "DiffIDs": [ + "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "cbb6a20ddb7dedfeee41aeb21e9780f14afbb0f47a6b1ffa514a1822f45d0a51", + "created": "2019-09-11T23:21:51.562946709Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-09-11T23:21:51.123609274Z", + "created_by": "/bin/sh -c #(nop) ADD file:770e381defc5e4a0ba5df52265a96494b9f5d94309234cb3f7bc6b00e1d18f9a in / " + }, + { + "created": "2019-09-11T23:21:51.562946709Z", + "created_by": "/bin/sh -c #(nop) CMD [\"bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + ] + }, + "config": { + "Cmd": [ + "bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:5519bb349f72eef81944da56843c995b1b81ed67c8e7e48ac29dd6c543c1dd2d", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/debian-buster.tar.gz (debian 10.1)", + "Class": "os-pkgs", + "Type": "debian", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18224", + "VendorIDs": [ + "DSA-4613-1" + ], + "PkgID": "libidn2-0@2.0.5-1", + "PkgName": "libidn2-0", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.1" + }, + "InstalledVersion": "2.0.5-1", + "FixedVersion": "2.0.5-1+deb10u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:4a56a430b2bac33260d6449e162017e2b23076c6411a17b46db67f5b84dde2bd", + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18224", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "libidn2: heap-based buffer overflow in idn2_to_ascii_4i in lib/lookup.c", + "Description": "idn2_to_ascii_4i in lib/lookup.c in GNU libidn2 before 2.1.1 has a heap-based buffer overflow via a long domain string.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 4, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "V3Score": 5.6 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00008.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00009.html", + "https://access.redhat.com/security/cve/CVE-2019-18224", + "https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=12420", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18224", + "https://github.com/libidn/libidn2/commit/e4d1558aa2c1c04a05066ee8600f37603890ba8c", + "https://github.com/libidn/libidn2/compare/libidn2-2.1.0...libidn2-2.1.1", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JDQVQ2XPV5BTZUFINT7AFJSKNNBVURNJ/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MINU5RKDFE6TKAFY5DRFN3WSFDS4DYVS/", + "https://seclists.org/bugtraq/2020/Feb/4", + "https://security.gentoo.org/glsa/202003-63", + "https://ubuntu.com/security/notices/USN-4168-1", + "https://usn.ubuntu.com/4168-1/", + "https://www.debian.org/security/2020/dsa-4613" + ], + "PublishedDate": "2019-10-21T17:15:00Z", + "LastModifiedDate": "2019-10-29T19:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/debian-buster.json.golden b/integration/testdata/debian-buster.json.golden new file mode 100644 index 000000000000..739158b5fe6e --- /dev/null +++ b/integration/testdata/debian-buster.json.golden @@ -0,0 +1,189 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/debian-buster.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "debian", + "Name": "10.1" + }, + "ImageID": "sha256:c2c03a296d2329a4f3ab72a7bf38b78a8a80108204d326b0139d6af700e152d1", + "DiffIDs": [ + "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "cbb6a20ddb7dedfeee41aeb21e9780f14afbb0f47a6b1ffa514a1822f45d0a51", + "created": "2019-09-11T23:21:51.562946709Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-09-11T23:21:51.123609274Z", + "created_by": "/bin/sh -c #(nop) ADD file:770e381defc5e4a0ba5df52265a96494b9f5d94309234cb3f7bc6b00e1d18f9a in / " + }, + { + "created": "2019-09-11T23:21:51.562946709Z", + "created_by": "/bin/sh -c #(nop) CMD [\"bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + ] + }, + "config": { + "Cmd": [ + "bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:5519bb349f72eef81944da56843c995b1b81ed67c8e7e48ac29dd6c543c1dd2d", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/debian-buster.tar.gz (debian 10.1)", + "Class": "os-pkgs", + "Type": "debian", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@5.0-4", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/bash@5.0-4?arch=amd64\u0026distro=debian-10.1" + }, + "InstalledVersion": "5.0-4", + "Status": "affected", + "Layer": { + "Digest": "sha256:4a56a430b2bac33260d6449e162017e2b23076c6411a17b46db67f5b84dde2bd", + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + }, + "SeveritySource": "debian", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped", + "Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "Severity": "LOW", + "CweIDs": [ + "CWE-273" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "debian": 1, + "nvd": 3, + "oracle-oval": 1, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.2, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html", + "https://access.redhat.com/security/cve/CVE-2019-18276", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276", + "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff", + "https://linux.oracle.com/cve/CVE-2019-18276.html", + "https://linux.oracle.com/errata/ELSA-2021-1679.html", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://nvd.nist.gov/vuln/detail/CVE-2019-18276", + "https://security.gentoo.org/glsa/202105-34", + "https://security.netapp.com/advisory/ntap-20200430-0003/", + "https://www.youtube.com/watch?v=-wGtxJ8opa8" + ], + "PublishedDate": "2019-11-28T01:15:00Z", + "LastModifiedDate": "2021-05-26T12:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-18224", + "VendorIDs": [ + "DSA-4613-1" + ], + "PkgID": "libidn2-0@2.0.5-1", + "PkgName": "libidn2-0", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.1" + }, + "InstalledVersion": "2.0.5-1", + "FixedVersion": "2.0.5-1+deb10u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:4a56a430b2bac33260d6449e162017e2b23076c6411a17b46db67f5b84dde2bd", + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18224", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "libidn2: heap-based buffer overflow in idn2_to_ascii_4i in lib/lookup.c", + "Description": "idn2_to_ascii_4i in lib/lookup.c in GNU libidn2 before 2.1.1 has a heap-based buffer overflow via a long domain string.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 4, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "V3Score": 5.6 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00008.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00009.html", + "https://access.redhat.com/security/cve/CVE-2019-18224", + "https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=12420", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18224", + "https://github.com/libidn/libidn2/commit/e4d1558aa2c1c04a05066ee8600f37603890ba8c", + "https://github.com/libidn/libidn2/compare/libidn2-2.1.0...libidn2-2.1.1", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JDQVQ2XPV5BTZUFINT7AFJSKNNBVURNJ/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MINU5RKDFE6TKAFY5DRFN3WSFDS4DYVS/", + "https://seclists.org/bugtraq/2020/Feb/4", + "https://security.gentoo.org/glsa/202003-63", + "https://ubuntu.com/security/notices/USN-4168-1", + "https://usn.ubuntu.com/4168-1/", + "https://www.debian.org/security/2020/dsa-4613" + ], + "PublishedDate": "2019-10-21T17:15:00Z", + "LastModifiedDate": "2019-10-29T19:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/debian-stretch.json.golden b/integration/testdata/debian-stretch.json.golden new file mode 100644 index 000000000000..ed15dd42381f --- /dev/null +++ b/integration/testdata/debian-stretch.json.golden @@ -0,0 +1,414 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/debian-stretch.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "debian", + "Name": "9.9" + }, + "ImageID": "sha256:f26939cc87ef44a6fc554eedd0a976ab30b5bc2769d65d2e986b6c5f1fd4053d", + "DiffIDs": [ + "sha256:f73e7e79899a33b4b9b78da62efb71520844f8dd518f3c390e27bc3063bce307" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "957bc0b73d29f0e1030fec9c63f81d3e81baa610cffcc9c574b14fee6d1821ae", + "created": "2019-08-14T00:24:45.872523599Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-08-14T00:24:45.612796997Z", + "created_by": "/bin/sh -c #(nop) ADD file:b9b24bd862a79bf6c6e79daf6babca27245063eb52a2f72ffc4fc3494ddd3d48 in / " + }, + { + "created": "2019-08-14T00:24:45.872523599Z", + "created_by": "/bin/sh -c #(nop) CMD [\"bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:f73e7e79899a33b4b9b78da62efb71520844f8dd518f3c390e27bc3063bce307" + ] + }, + "config": { + "Cmd": [ + "bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:2ce0e924e5d43d66387e476478ce3c857b1eaae74b5c74693ed47b3502bbdc3e", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/debian-stretch.tar.gz (debian 9.9)", + "Class": "os-pkgs", + "Type": "debian", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@4.4-5", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/bash@4.4-5?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "4.4-5", + "Status": "end_of_life", + "Layer": { + "Digest": "sha256:9cc2ad81d40d54dcae7fa5e8e17d9c34e8bba3b7c2cc7e26fb22734608bda32e", + "DiffID": "sha256:f73e7e79899a33b4b9b78da62efb71520844f8dd518f3c390e27bc3063bce307" + }, + "SeveritySource": "debian", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped", + "Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "Severity": "LOW", + "CweIDs": [ + "CWE-273" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "debian": 1, + "nvd": 3, + "oracle-oval": 1, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.2, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html", + "https://access.redhat.com/security/cve/CVE-2019-18276", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276", + "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff", + "https://linux.oracle.com/cve/CVE-2019-18276.html", + "https://linux.oracle.com/errata/ELSA-2021-1679.html", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://nvd.nist.gov/vuln/detail/CVE-2019-18276", + "https://security.gentoo.org/glsa/202105-34", + "https://security.netapp.com/advisory/ntap-20200430-0003/", + "https://www.youtube.com/watch?v=-wGtxJ8opa8" + ], + "PublishedDate": "2019-11-28T01:15:00Z", + "LastModifiedDate": "2021-05-26T12:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "VendorIDs": [ + "DSA-4535-1" + ], + "PkgID": "e2fslibs@1.43.4-2", + "PkgName": "e2fslibs", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/e2fslibs@1.43.4-2?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.43.4-2", + "FixedVersion": "1.43.4-2+deb9u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9cc2ad81d40d54dcae7fa5e8e17d9c34e8bba3b7c2cc7e26fb22734608bda32e", + "DiffID": "sha256:f73e7e79899a33b4b9b78da62efb71520844f8dd518f3c390e27bc3063bce307" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "VendorIDs": [ + "DSA-4535-1" + ], + "PkgID": "e2fsprogs@1.43.4-2", + "PkgName": "e2fsprogs", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/e2fsprogs@1.43.4-2?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.43.4-2", + "FixedVersion": "1.43.4-2+deb9u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9cc2ad81d40d54dcae7fa5e8e17d9c34e8bba3b7c2cc7e26fb22734608bda32e", + "DiffID": "sha256:f73e7e79899a33b4b9b78da62efb71520844f8dd518f3c390e27bc3063bce307" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "VendorIDs": [ + "DSA-4535-1" + ], + "PkgID": "libcomerr2@1.43.4-2", + "PkgName": "libcomerr2", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/libcomerr2@1.43.4-2?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.43.4-2", + "FixedVersion": "1.43.4-2+deb9u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9cc2ad81d40d54dcae7fa5e8e17d9c34e8bba3b7c2cc7e26fb22734608bda32e", + "DiffID": "sha256:f73e7e79899a33b4b9b78da62efb71520844f8dd518f3c390e27bc3063bce307" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "VendorIDs": [ + "DSA-4535-1" + ], + "PkgID": "libss2@1.43.4-2", + "PkgName": "libss2", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/libss2@1.43.4-2?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.43.4-2", + "FixedVersion": "1.43.4-2+deb9u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:9cc2ad81d40d54dcae7fa5e8e17d9c34e8bba3b7c2cc7e26fb22734608bda32e", + "DiffID": "sha256:f73e7e79899a33b4b9b78da62efb71520844f8dd518f3c390e27bc3063bce307" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/distroless-base.json.golden b/integration/testdata/distroless-base.json.golden new file mode 100644 index 000000000000..0bd390a36f54 --- /dev/null +++ b/integration/testdata/distroless-base.json.golden @@ -0,0 +1,405 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/distroless-base.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "debian", + "Name": "9.9" + }, + "ImageID": "sha256:7f04a8d247173b1f2546d22913af637bbab4e7411e00ae6207da8d94c445750d", + "DiffIDs": [ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + ], + "ImageConfig": { + "architecture": "amd64", + "author": "Bazel", + "created": "1970-01-01T00:00:00Z", + "history": [ + { + "author": "Bazel", + "created": "1970-01-01T00:00:00Z", + "created_by": "bazel build ..." + }, + { + "author": "Bazel", + "created": "1970-01-01T00:00:00Z", + "created_by": "bazel build ..." + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + ] + }, + "config": { + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt" + ] + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/distroless-base.tar.gz (debian 9.9)", + "Class": "os-pkgs", + "Type": "debian", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "libssl1.1@1.1.0k-1~deb9u1", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.1.0k-1~deb9u1", + "Status": "affected", + "Layer": { + "Digest": "sha256:e005d777a298a3529b1c8cf890883359e050cc966089ce84fea4d17b111907db", + "DiffID": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1563", + "VendorIDs": [ + "DSA-4539-1" + ], + "PkgID": "libssl1.1@1.1.0k-1~deb9u1", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.1.0k-1~deb9u1", + "FixedVersion": "1.1.0l-1~deb9u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e005d777a298a3529b1c8cf890883359e050cc966089ce84fea4d17b111907db", + "DiffID": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1563", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "openssl: information disclosure in PKCS7_dataDecode and CMS_decrypt_set1_pkey", + "Description": "In situations where an attacker receives automated notification of the success or failure of a decryption attempt an attacker, after sending a very large number of messages to be decrypted, can recover a CMS/PKCS7 transported encryption key or decrypt any RSA encrypted message that was encrypted with the public RSA key, using a Bleichenbacher padding oracle attack. Applications are not affected if they use a certificate together with the private RSA key to the CMS_decrypt or PKCS7_decrypt functions to select the correct recipient info to decrypt. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c). Fixed in OpenSSL 1.1.0l (Affected 1.1.0-1.1.0k). Fixed in OpenSSL 1.0.2t (Affected 1.0.2-1.0.2s).", + "Severity": "LOW", + "CweIDs": [ + "CWE-327", + "CWE-203" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 1, + "oracle-oval": 2, + "photon": 1, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 4.3, + "V3Score": 3.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V3Score": 3.7 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00054.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00072.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-10/msg00012.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-10/msg00016.html", + "http://packetstormsecurity.com/files/154467/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1563", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1563", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=08229ad838c50f644d7e928e2eef147b4308ad64", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=631f94db0065c78181ca9ba5546ebc8bb3884b97", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e21f8cf78a125cd3c8c0d1a1a6c8bb0b901f893f", + "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10365", + "https://linux.oracle.com/cve/CVE-2019-1563.html", + "https://linux.oracle.com/errata/ELSA-2020-1840.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00026.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", + "https://seclists.org/bugtraq/2019/Oct/0", + "https://seclists.org/bugtraq/2019/Oct/1", + "https://seclists.org/bugtraq/2019/Sep/25", + "https://security.gentoo.org/glsa/201911-04", + "https://security.netapp.com/advisory/ntap-20190919-0002/", + "https://support.f5.com/csp/article/K97324400?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4376-2", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4376-2/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4539", + "https://www.debian.org/security/2019/dsa-4540", + "https://www.openssl.org/news/secadv/20190910.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.tenable.com/security/tns-2019-09" + ], + "PublishedDate": "2019-09-10T17:15:00Z", + "LastModifiedDate": "2021-07-31T08:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "openssl@1.1.0k-1~deb9u1", + "PkgName": "openssl", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.1.0k-1~deb9u1", + "Status": "affected", + "Layer": { + "Digest": "sha256:e005d777a298a3529b1c8cf890883359e050cc966089ce84fea4d17b111907db", + "DiffID": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1563", + "VendorIDs": [ + "DSA-4539-1" + ], + "PkgID": "openssl@1.1.0k-1~deb9u1", + "PkgName": "openssl", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.1.0k-1~deb9u1", + "FixedVersion": "1.1.0l-1~deb9u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e005d777a298a3529b1c8cf890883359e050cc966089ce84fea4d17b111907db", + "DiffID": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1563", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "openssl: information disclosure in PKCS7_dataDecode and CMS_decrypt_set1_pkey", + "Description": "In situations where an attacker receives automated notification of the success or failure of a decryption attempt an attacker, after sending a very large number of messages to be decrypted, can recover a CMS/PKCS7 transported encryption key or decrypt any RSA encrypted message that was encrypted with the public RSA key, using a Bleichenbacher padding oracle attack. Applications are not affected if they use a certificate together with the private RSA key to the CMS_decrypt or PKCS7_decrypt functions to select the correct recipient info to decrypt. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c). Fixed in OpenSSL 1.1.0l (Affected 1.1.0-1.1.0k). Fixed in OpenSSL 1.0.2t (Affected 1.0.2-1.0.2s).", + "Severity": "LOW", + "CweIDs": [ + "CWE-327", + "CWE-203" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 1, + "oracle-oval": 2, + "photon": 1, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 4.3, + "V3Score": 3.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V3Score": 3.7 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00054.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00072.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-10/msg00012.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-10/msg00016.html", + "http://packetstormsecurity.com/files/154467/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1563", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1563", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=08229ad838c50f644d7e928e2eef147b4308ad64", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=631f94db0065c78181ca9ba5546ebc8bb3884b97", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e21f8cf78a125cd3c8c0d1a1a6c8bb0b901f893f", + "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10365", + "https://linux.oracle.com/cve/CVE-2019-1563.html", + "https://linux.oracle.com/errata/ELSA-2020-1840.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00026.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", + "https://seclists.org/bugtraq/2019/Oct/0", + "https://seclists.org/bugtraq/2019/Oct/1", + "https://seclists.org/bugtraq/2019/Sep/25", + "https://security.gentoo.org/glsa/201911-04", + "https://security.netapp.com/advisory/ntap-20190919-0002/", + "https://support.f5.com/csp/article/K97324400?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4376-2", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4376-2/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4539", + "https://www.debian.org/security/2019/dsa-4540", + "https://www.openssl.org/news/secadv/20190910.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.tenable.com/security/tns-2019-09" + ], + "PublishedDate": "2019-09-10T17:15:00Z", + "LastModifiedDate": "2021-07-31T08:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/distroless-python27.json.golden b/integration/testdata/distroless-python27.json.golden new file mode 100644 index 000000000000..8c0657976b97 --- /dev/null +++ b/integration/testdata/distroless-python27.json.golden @@ -0,0 +1,422 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/distroless-python27.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "debian", + "Name": "9.9" + }, + "ImageID": "sha256:6fcac2cc8a710f21577b5bbd534e0bfc841c0cca569b57182ba19054696cddda", + "DiffIDs": [ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + "sha256:6189abe095d53c1c9f2bfc8f50128ee876b9a5d10f9eda1564e5f5357d6ffe61", + "sha256:e92caab8efcf25a24bea5213ab7e54d4a5f5f08644836bb2d296070b1ae1044e" + ], + "ImageConfig": { + "architecture": "amd64", + "author": "Bazel", + "created": "1970-01-01T00:00:00Z", + "history": [ + { + "author": "Bazel", + "created": "1970-01-01T00:00:00Z", + "created_by": "bazel build ..." + }, + { + "author": "Bazel", + "created": "1970-01-01T00:00:00Z", + "created_by": "bazel build ..." + }, + { + "author": "Bazel", + "created": "1970-01-01T00:00:00Z", + "created_by": "bazel build ..." + }, + { + "author": "Bazel", + "created": "1970-01-01T00:00:00Z", + "created_by": "bazel build ..." + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + "sha256:6189abe095d53c1c9f2bfc8f50128ee876b9a5d10f9eda1564e5f5357d6ffe61", + "sha256:e92caab8efcf25a24bea5213ab7e54d4a5f5f08644836bb2d296070b1ae1044e" + ] + }, + "config": { + "Entrypoint": [ + "/usr/bin/python2.7" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt" + ] + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/distroless-python27.tar.gz (debian 9.9)", + "Class": "os-pkgs", + "Type": "debian", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "libssl1.1@1.1.0k-1~deb9u1", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.1.0k-1~deb9u1", + "Status": "affected", + "Layer": { + "Digest": "sha256:e005d777a298a3529b1c8cf890883359e050cc966089ce84fea4d17b111907db", + "DiffID": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1563", + "VendorIDs": [ + "DSA-4539-1" + ], + "PkgID": "libssl1.1@1.1.0k-1~deb9u1", + "PkgName": "libssl1.1", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.1.0k-1~deb9u1", + "FixedVersion": "1.1.0l-1~deb9u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e005d777a298a3529b1c8cf890883359e050cc966089ce84fea4d17b111907db", + "DiffID": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1563", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "openssl: information disclosure in PKCS7_dataDecode and CMS_decrypt_set1_pkey", + "Description": "In situations where an attacker receives automated notification of the success or failure of a decryption attempt an attacker, after sending a very large number of messages to be decrypted, can recover a CMS/PKCS7 transported encryption key or decrypt any RSA encrypted message that was encrypted with the public RSA key, using a Bleichenbacher padding oracle attack. Applications are not affected if they use a certificate together with the private RSA key to the CMS_decrypt or PKCS7_decrypt functions to select the correct recipient info to decrypt. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c). Fixed in OpenSSL 1.1.0l (Affected 1.1.0-1.1.0k). Fixed in OpenSSL 1.0.2t (Affected 1.0.2-1.0.2s).", + "Severity": "LOW", + "CweIDs": [ + "CWE-327", + "CWE-203" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 1, + "oracle-oval": 2, + "photon": 1, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 4.3, + "V3Score": 3.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V3Score": 3.7 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00054.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00072.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-10/msg00012.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-10/msg00016.html", + "http://packetstormsecurity.com/files/154467/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1563", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1563", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=08229ad838c50f644d7e928e2eef147b4308ad64", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=631f94db0065c78181ca9ba5546ebc8bb3884b97", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e21f8cf78a125cd3c8c0d1a1a6c8bb0b901f893f", + "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10365", + "https://linux.oracle.com/cve/CVE-2019-1563.html", + "https://linux.oracle.com/errata/ELSA-2020-1840.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00026.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", + "https://seclists.org/bugtraq/2019/Oct/0", + "https://seclists.org/bugtraq/2019/Oct/1", + "https://seclists.org/bugtraq/2019/Sep/25", + "https://security.gentoo.org/glsa/201911-04", + "https://security.netapp.com/advisory/ntap-20190919-0002/", + "https://support.f5.com/csp/article/K97324400?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4376-2", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4376-2/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4539", + "https://www.debian.org/security/2019/dsa-4540", + "https://www.openssl.org/news/secadv/20190910.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.tenable.com/security/tns-2019-09" + ], + "PublishedDate": "2019-09-10T17:15:00Z", + "LastModifiedDate": "2021-07-31T08:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1551", + "PkgID": "openssl@1.1.0k-1~deb9u1", + "PkgName": "openssl", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.1.0k-1~deb9u1", + "Status": "affected", + "Layer": { + "Digest": "sha256:e005d777a298a3529b1c8cf890883359e050cc966089ce84fea4d17b111907db", + "DiffID": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", + "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-200" + ], + "VendorSeverity": { + "amazon": 1, + "nvd": 2, + "oracle-oval": 1, + "photon": 2, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 4.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1551", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", + "https://github.com/openssl/openssl/pull/10575", + "https://linux.oracle.com/cve/CVE-2019-1551.html", + "https://linux.oracle.com/errata/ELSA-2020-4514.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", + "https://seclists.org/bugtraq/2019/Dec/39", + "https://seclists.org/bugtraq/2019/Dec/46", + "https://security.gentoo.org/glsa/202004-10", + "https://security.netapp.com/advisory/ntap-20191210-0001/", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4594", + "https://www.debian.org/security/2021/dsa-4855", + "https://www.openssl.org/news/secadv/20191206.txt", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.tenable.com/security/tns-2019-09", + "https://www.tenable.com/security/tns-2020-03", + "https://www.tenable.com/security/tns-2020-11", + "https://www.tenable.com/security/tns-2021-10" + ], + "PublishedDate": "2019-12-06T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + }, + { + "VulnerabilityID": "CVE-2019-1563", + "VendorIDs": [ + "DSA-4539-1" + ], + "PkgID": "openssl@1.1.0k-1~deb9u1", + "PkgName": "openssl", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + }, + "InstalledVersion": "1.1.0k-1~deb9u1", + "FixedVersion": "1.1.0l-1~deb9u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e005d777a298a3529b1c8cf890883359e050cc966089ce84fea4d17b111907db", + "DiffID": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1563", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "openssl: information disclosure in PKCS7_dataDecode and CMS_decrypt_set1_pkey", + "Description": "In situations where an attacker receives automated notification of the success or failure of a decryption attempt an attacker, after sending a very large number of messages to be decrypted, can recover a CMS/PKCS7 transported encryption key or decrypt any RSA encrypted message that was encrypted with the public RSA key, using a Bleichenbacher padding oracle attack. Applications are not affected if they use a certificate together with the private RSA key to the CMS_decrypt or PKCS7_decrypt functions to select the correct recipient info to decrypt. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c). Fixed in OpenSSL 1.1.0l (Affected 1.1.0-1.1.0k). Fixed in OpenSSL 1.0.2t (Affected 1.0.2-1.0.2s).", + "Severity": "LOW", + "CweIDs": [ + "CWE-327", + "CWE-203" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 1, + "oracle-oval": 2, + "photon": 1, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V2Score": 4.3, + "V3Score": 3.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V3Score": 3.7 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00054.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00072.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-10/msg00012.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-10/msg00016.html", + "http://packetstormsecurity.com/files/154467/Slackware-Security-Advisory-openssl-Updates.html", + "https://access.redhat.com/security/cve/CVE-2019-1563", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1563", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=08229ad838c50f644d7e928e2eef147b4308ad64", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=631f94db0065c78181ca9ba5546ebc8bb3884b97", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e21f8cf78a125cd3c8c0d1a1a6c8bb0b901f893f", + "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10365", + "https://linux.oracle.com/cve/CVE-2019-1563.html", + "https://linux.oracle.com/errata/ELSA-2020-1840.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00026.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", + "https://seclists.org/bugtraq/2019/Oct/0", + "https://seclists.org/bugtraq/2019/Oct/1", + "https://seclists.org/bugtraq/2019/Sep/25", + "https://security.gentoo.org/glsa/201911-04", + "https://security.netapp.com/advisory/ntap-20190919-0002/", + "https://support.f5.com/csp/article/K97324400?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-4376-1", + "https://ubuntu.com/security/notices/USN-4376-2", + "https://ubuntu.com/security/notices/USN-4504-1", + "https://usn.ubuntu.com/4376-1/", + "https://usn.ubuntu.com/4376-2/", + "https://usn.ubuntu.com/4504-1/", + "https://www.debian.org/security/2019/dsa-4539", + "https://www.debian.org/security/2019/dsa-4540", + "https://www.openssl.org/news/secadv/20190910.txt", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.tenable.com/security/tns-2019-09" + ], + "PublishedDate": "2019-09-10T17:15:00Z", + "LastModifiedDate": "2021-07-31T08:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/dockerfile-custom-policies.json.golden b/integration/testdata/dockerfile-custom-policies.json.golden new file mode 100644 index 000000000000..438ecf5d7270 --- /dev/null +++ b/integration/testdata/dockerfile-custom-policies.json.golden @@ -0,0 +1,70 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/custom-policy", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Dockerfile", + "Class": "config", + "Type": "dockerfile", + "MisconfSummary": { + "Successes": 27, + "Failures": 2, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Dockerfile Security Check", + "ID": "N/A", + "Title": "N/A", + "Description": "Rego module: data.user.bar", + "Message": "something bad: bar", + "Namespace": "user.bar", + "Query": "data.user.bar.deny", + "Severity": "UNKNOWN", + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Generic", + "Service": "general", + "Code": { + "Lines": null + } + } + }, + { + "Type": "Dockerfile Security Check", + "ID": "N/A", + "Title": "N/A", + "Description": "Rego module: data.user.foo", + "Message": "something bad: foo", + "Namespace": "user.foo", + "Query": "data.user.foo.deny", + "Severity": "UNKNOWN", + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Generic", + "Service": "general", + "Code": { + "Lines": null + } + } + } + ] + } + ] +} diff --git a/integration/testdata/dockerfile-namespace-exception.json.golden b/integration/testdata/dockerfile-namespace-exception.json.golden new file mode 100644 index 000000000000..be3a6adfa8d6 --- /dev/null +++ b/integration/testdata/dockerfile-namespace-exception.json.golden @@ -0,0 +1,30 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/namespace-exception", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Dockerfile", + "Class": "config", + "Type": "dockerfile", + "MisconfSummary": { + "Successes": 0, + "Failures": 0, + "Exceptions": 27 + } + } + ] +} diff --git a/integration/testdata/dockerfile-rule-exception.json.golden b/integration/testdata/dockerfile-rule-exception.json.golden new file mode 100644 index 000000000000..eae4e1e32fd6 --- /dev/null +++ b/integration/testdata/dockerfile-rule-exception.json.golden @@ -0,0 +1,58 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/rule-exception", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Dockerfile", + "Class": "config", + "Type": "dockerfile", + "MisconfSummary": { + "Successes": 26, + "Failures": 1, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Dockerfile Security Check", + "ID": "DS002", + "AVDID": "AVD-DS-0002", + "Title": "Image user should not be 'root'", + "Description": "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", + "Message": "Specify at least 1 USER command in Dockerfile with non-root user as argument", + "Namespace": "builtin.dockerfile.DS002", + "Query": "data.builtin.dockerfile.DS002.deny", + "Resolution": "Add 'USER \u003cnon root user name\u003e' line to the Dockerfile", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ds002", + "References": [ + "https://docs.docker.com/develop/develop-images/dockerfile_best-practices/", + "https://avd.aquasec.com/misconfig/ds002" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Dockerfile", + "Service": "general", + "Code": { + "Lines": null + } + } + } + ] + } + ] +} diff --git a/integration/testdata/dockerfile.json.golden b/integration/testdata/dockerfile.json.golden new file mode 100644 index 000000000000..3f6dd1b5b201 --- /dev/null +++ b/integration/testdata/dockerfile.json.golden @@ -0,0 +1,58 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/dockerfile", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Dockerfile", + "Class": "config", + "Type": "dockerfile", + "MisconfSummary": { + "Successes": 26, + "Failures": 1, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Dockerfile Security Check", + "ID": "DS002", + "AVDID": "AVD-DS-0002", + "Title": "Image user should not be 'root'", + "Description": "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", + "Message": "Specify at least 1 USER command in Dockerfile with non-root user as argument", + "Namespace": "builtin.dockerfile.DS002", + "Query": "data.builtin.dockerfile.DS002.deny", + "Resolution": "Add 'USER \u003cnon root user name\u003e' line to the Dockerfile", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ds002", + "References": [ + "https://docs.docker.com/develop/develop-images/dockerfile_best-practices/", + "https://avd.aquasec.com/misconfig/ds002" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Dockerfile", + "Service": "general", + "Code": { + "Lines": null + } + } + } + ] + } + ] +} diff --git a/integration/testdata/dockerfile_file_pattern.json.golden b/integration/testdata/dockerfile_file_pattern.json.golden new file mode 100644 index 000000000000..fe0372ddc79c --- /dev/null +++ b/integration/testdata/dockerfile_file_pattern.json.golden @@ -0,0 +1,58 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/dockerfile_file_pattern", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Customfile", + "Class": "config", + "Type": "dockerfile", + "MisconfSummary": { + "Successes": 26, + "Failures": 1, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Dockerfile Security Check", + "ID": "DS002", + "AVDID": "AVD-DS-0002", + "Title": "Image user should not be 'root'", + "Description": "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", + "Message": "Specify at least 1 USER command in Dockerfile with non-root user as argument", + "Namespace": "builtin.dockerfile.DS002", + "Query": "data.builtin.dockerfile.DS002.deny", + "Resolution": "Add 'USER \u003cnon root user name\u003e' line to the Dockerfile", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ds002", + "References": [ + "https://docs.docker.com/develop/develop-images/dockerfile_best-practices/", + "https://avd.aquasec.com/misconfig/ds002" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Dockerfile", + "Service": "general", + "Code": { + "Lines": null + } + } + } + ] + } + ] +} diff --git a/integration/testdata/dotnet.json.golden b/integration/testdata/dotnet.json.golden new file mode 100644 index 000000000000..264b28d7534d --- /dev/null +++ b/integration/testdata/dotnet.json.golden @@ -0,0 +1,82 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/dotnet", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "datacollector.deps.json", + "Class": "lang-pkgs", + "Type": "dotnet-core", + "Packages": [ + { + "Name": "Newtonsoft.Json", + "Identifier": { + "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1" + }, + "Version": "9.0.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 8, + "EndLine": 14 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "GHSA-5crp-9r3c-p9vr", + "PkgName": "Newtonsoft.Json", + "PkgIdentifier": { + "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1" + }, + "InstalledVersion": "9.0.1", + "FixedVersion": "13.0.1", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://github.com/advisories/GHSA-5crp-9r3c-p9vr", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Nuget", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anuget" + }, + "Title": "Improper Handling of Exceptional Conditions in Newtonsoft.Json", + "Description": "Newtonsoft.Json prior to version 13.0.1 is vulnerable to Insecure Defaults due to improper handling of expressions with high nesting level that lead to StackOverFlow exception or high CPU and RAM usage.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-755" + ], + "VendorSeverity": { + "ghsa": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + } + }, + "References": [ + "https://alephsecurity.com/2018/10/22/StackOverflowException/", + "https://alephsecurity.com/vulns/aleph-2018004" + ], + "PublishedDate": "2022-06-22T15:08:47Z", + "LastModifiedDate": "2022-06-27T18:37:23Z" + } + ] + } + ] +} diff --git a/integration/testdata/fixtures/db/almalinux.yaml b/integration/testdata/fixtures/db/almalinux.yaml new file mode 100644 index 000000000000..e7e463ebe389 --- /dev/null +++ b/integration/testdata/fixtures/db/almalinux.yaml @@ -0,0 +1,10 @@ +- bucket: alma 8 + pairs: + - bucket: openssl-libs + pairs: + - key: CVE-2021-3450 + value: + FixedVersion: 1:1.1.1g-15.el8_3 + - key: CVE-2021-3712 + value: + FixedVersion: 1:1.1.1k-5.el8_5 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/alpine.yaml b/integration/testdata/fixtures/db/alpine.yaml new file mode 100644 index 000000000000..15e5308012a7 --- /dev/null +++ b/integration/testdata/fixtures/db/alpine.yaml @@ -0,0 +1,37 @@ +- bucket: alpine 3.9 + pairs: + - bucket: openssl + pairs: + - key: CVE-2019-1549 + value: + FixedVersion: 1.1.1d-r0 + - key: CVE-2019-1551 + value: + FixedVersion: 1.1.1d-r2 + - bucket: musl + pairs: + - key: CVE-2019-14697 + value: + FixedVersion: 1.1.20-r5 +- bucket: alpine 3.10 + pairs: + - bucket: openssl + pairs: + - key: CVE-2019-1549 + value: + FixedVersion: 1.1.1d-r0 + - key: CVE-2019-1551 + value: + FixedVersion: 1.1.1d-r2 + - bucket: musl + pairs: + - key: CVE-2019-14697 + value: + FixedVersion: 1.1.20-r5 +- bucket: alpine edge + pairs: + - bucket: git + pairs: + - key: CVE-2022-24765 + value: + FixedVersion: 2.35.2-r0 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/amazon.yaml b/integration/testdata/fixtures/db/amazon.yaml new file mode 100644 index 000000000000..b1d71e016546 --- /dev/null +++ b/integration/testdata/fixtures/db/amazon.yaml @@ -0,0 +1,25 @@ +- bucket: amazon linux 1 + pairs: + - bucket: curl + pairs: + - key: CVE-2019-5436 + value: + FixedVersion: 7.61.1-11.91.amzn1 + - key: CVE-2019-5481 + value: + FixedVersion: 7.61.1-12.93.amzn1 +- bucket: amazon linux 2 + pairs: + - bucket: curl + pairs: + - key: CVE-2019-5436 + value: + FixedVersion: 7.61.1-11.amzn2.0.2 + - key: CVE-2019-5481 + value: + FixedVersion: 7.61.1-12.amzn2.0.1 + - bucket: bind-export-libs + pairs: + - key: CVE-2022-38177 + value: + FixedVersion: 99:9.11.4-26.P2.amzn2.13 diff --git a/integration/testdata/fixtures/db/cocoapods.yaml b/integration/testdata/fixtures/db/cocoapods.yaml new file mode 100644 index 000000000000..3b218912e36a --- /dev/null +++ b/integration/testdata/fixtures/db/cocoapods.yaml @@ -0,0 +1,14 @@ +- bucket: "cocoapods::GitHub Security Advisory Cocoapods" + pairs: + - bucket: _NIODataStructures + pairs: + - key: CVE-2022-3215 + value: + PatchedVersions: + - "2.29.1" + - "2.39.1" + - "2.42.0" + VulnerableVersions: + - "< 2.29.1" + - ">= 2.39.0, < 2.39.1" + - ">= 2.41.0, < 2.42.0" \ No newline at end of file diff --git a/integration/testdata/fixtures/db/composer.yaml b/integration/testdata/fixtures/db/composer.yaml new file mode 100644 index 000000000000..638c73a838fb --- /dev/null +++ b/integration/testdata/fixtures/db/composer.yaml @@ -0,0 +1,10 @@ +- bucket: "composer::GitHub Security Advisory Composer" + pairs: + - bucket: guzzlehttp/psr7 + pairs: + - key: CVE-2022-24775 + value: + PatchedVersions: + - 1.8.4 + VulnerableVersions: + - < 1.8.4 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/conan.yaml b/integration/testdata/fixtures/db/conan.yaml new file mode 100644 index 000000000000..9d7089ee8f71 --- /dev/null +++ b/integration/testdata/fixtures/db/conan.yaml @@ -0,0 +1,10 @@ +- bucket: conan::GitLab Advisory Database Community + pairs: + - bucket: pcre + pairs: + - key: CVE-2020-14155 + value: + PatchedVersions: + - "8.45" + VulnerableVersions: + - "<8.44" diff --git a/integration/testdata/fixtures/db/cpe.yaml b/integration/testdata/fixtures/db/cpe.yaml new file mode 100644 index 000000000000..174e63ee286c --- /dev/null +++ b/integration/testdata/fixtures/db/cpe.yaml @@ -0,0 +1,21 @@ +- bucket: Red Hat CPE + pairs: + - bucket: repository + pairs: + - key: "rhel-6-server-rpms" + value: + - 857 + - key: "rhel-7-server-rpms" + value: + - 869 + - bucket: nvr + pairs: + - key: "ubi7-container-7.7-140-x86_64" + value: + - 869 + - bucket: cpe + pairs: + - key: "857" + value: "cpe:/o:redhat:enterprise_linux:6::server" + - key: "869" + value: "cpe:/o:redhat:enterprise_linux:7::server" diff --git a/integration/testdata/fixtures/db/data-source.yaml b/integration/testdata/fixtures/db/data-source.yaml new file mode 100644 index 000000000000..6a2570b1e977 --- /dev/null +++ b/integration/testdata/fixtures/db/data-source.yaml @@ -0,0 +1,151 @@ +- bucket: data-source + pairs: + - key: "composer::GitHub Security Advisory Composer" + value: + ID: "ghsa" + Name: "GitHub Security Advisory Composer" + URL: "https://github.com/advisories?query=type%%3Areviewed+ecosystem%%3Acomposer" + - key: "maven::GitHub Security Advisory Maven" + value: + ID: "ghsa" + Name: "GitHub Security Advisory Maven" + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven" + - key: "npm::GitHub Security Advisory Npm" + value: + ID: "ghsa" + Name: "GitHub Security Advisory Npm" + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + - key: "nuget::GitHub Security Advisory Nuget" + value: + ID: "ghsa" + Name: "GitHub Security Advisory Nuget" + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anuget" + - key: "pip::GitHub Security Advisory Pip" + value: + ID: "ghsa" + Name: "GitHub Security Advisory Pip" + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + - key: "rubygems::GitHub Security Advisory RubyGems" + value: + ID: "ghsa" + Name: "GitHub Security Advisory RubyGems" + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Arubygems" + - key: "pub::GitHub Security Advisory Pub" + value: + ID: "ghsa" + Name: "GitHub Security Advisory Pub" + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apub" + - key: "erlang::GitHub Security Advisory Erlang" + value: + ID: "ghsa" + Name: "GitHub Security Advisory Erlang" + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Aerlang" + - key: "go::GitHub Security Advisory Go" + value: + ID: "ghsa" + Name: "GitHub Security Advisory Go" + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + - key: Oracle Linux 8 + value: + ID: "oracle-oval" + Name: "Oracle Linux OVAL definitions" + URL: "https://linux.oracle.com/security/oval/" + - key: Photon OS 3.0 + value: + ID: "photon" + Name: "Photon OS CVE metadata" + URL: "https://packages.vmware.com/photon/photon_cve_metadata/" + - key: alma 8 + value: + ID: "alma" + Name: "AlmaLinux Product Errata" + URL: "https://errata.almalinux.org/" + - key: alpine 3.9 + value: + ID: "alpine" + Name: "Alpine Secdb" + URL: "https://secdb.alpinelinux.org/" + - key: alpine 3.10 + value: + ID: "alpine" + Name: "Alpine Secdb" + URL: "https://secdb.alpinelinux.org/" + - key: amazon linux 1 + value: + ID: "amazon" + Name: "Amazon Linux Security Center" + URL: "https://alas.aws.amazon.com/" + - key: amazon linux 2 + value: + ID: "amazon" + Name: "Amazon Linux Security Center" + URL: "https://alas.aws.amazon.com/" + - key: cargo::Open Source Vulnerability + value: + Name: "RustSec Advisory Database" + URL: "https://github.com/RustSec/advisory-db" + - key: debian 10 + value: + ID: "debian" + Name: "Debian Security Tracker" + URL: "https://salsa.debian.org/security-tracker-team/security-tracker" + - key: debian 9 + value: + ID: "debian" + Name: "Debian Security Tracker" + URL: "https://salsa.debian.org/security-tracker-team/security-tracker" + - key: maven::GitLab Advisory Database Community + value: + ID: "glad" + Name: "GitLab Advisory Database Community" + URL: "https://gitlab.com/gitlab-org/advisories-community" + - key: npm::nodejs-security-wg + value: + ID: "nodejs-security-wg" + Name: "Node.js Ecosystem Security Working Group" + URL: "https://github.com/nodejs/security-wg" + - key: openSUSE Leap 15.0 + value: + ID: "suse-cvrf" + Name: "SUSE CVRF" + URL: "https://ftp.suse.com/pub/projects/security/cvrf/" + - key: openSUSE Leap 15.1 + value: + ID: "suse-cvrf" + Name: "SUSE CVRF" + URL: "https://ftp.suse.com/pub/projects/security/cvrf/" + - key: composer::php-security-advisories + value: + ID: "php-security-advisories" + Name: "PHP Security Advisories Database" + URL: "https://github.com/FriendsOfPHP/security-advisories" + - key: pip::Open Source Vulnerability + value: + ID: "osv" + Name: "Python Packaging Advisory Database" + URL: "https://github.com/pypa/advisory-db" + - key: rocky 8 + value: + ID: "rocky" + Name: "Rocky Linux updateinfo" + URL: "https://download.rockylinux.org/pub/rocky/" + - key: rubygems::ruby-advisory-db + value: + ID: "ruby-advisory-db" + Name: "Ruby Advisory Database" + URL: "https://github.com/rubysec/ruby-advisory-db" + - key: ubuntu 18.04 + value: + ID: "ubuntu" + Name: "Ubuntu CVE Tracker" + URL: "https://git.launchpad.net/ubuntu-cve-tracker" + - key: CBL-Mariner 1.0 + value: + ID: "cbl-mariner" + Name: "CBL-Mariner Vulnerability Data" + URL: "https://github.com/microsoft/CBL-MarinerVulnerabilityData" + - key: k8s::Official Kubernetes CVE Feed + value: + ID: "k8s" + Name: "Official Kubernetes CVE Feed" + URL: "https://kubernetes.io/docs/reference/issues-security/official-cve-feed/index.json" diff --git a/integration/testdata/fixtures/db/debian.yaml b/integration/testdata/fixtures/db/debian.yaml new file mode 100644 index 000000000000..a357227473f8 --- /dev/null +++ b/integration/testdata/fixtures/db/debian.yaml @@ -0,0 +1,41 @@ +- bucket: debian 9 + pairs: + - bucket: bash + pairs: + - key: CVE-2019-18276 + value: + Severity: 1.0 + Status: 7 # changed for test + - bucket: openssl + pairs: + - key: CVE-2019-1551 + value: + State: postponed + - bucket: e2fsprogs + pairs: + - key: CVE-2019-5094 + value: + FixedVersion: 1.43.4-2+deb9u1 + VendorIDs: + - DSA-4535-1 + - bucket: openssl + pairs: + - key: CVE-2019-1563 + value: + FixedVersion: 1.1.0l-1~deb9u1 + VendorIDs: + - DSA-4539-1 +- bucket: debian 10 + pairs: + - bucket: bash + pairs: + - key: CVE-2019-18276 + value: + Severity: 1 + - bucket: libidn2 + pairs: + - key: CVE-2019-18224 + value: + FixedVersion: 2.0.5-1+deb10u1 + VendorIDs: + - DSA-4613-1 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/erlang.yaml b/integration/testdata/fixtures/db/erlang.yaml new file mode 100644 index 000000000000..a5fd04d19359 --- /dev/null +++ b/integration/testdata/fixtures/db/erlang.yaml @@ -0,0 +1,10 @@ +- bucket: erlang::GitHub Security Advisory Erlang + pairs: + - bucket: phoenix + pairs: + - key: CVE-2022-42975 + value: + PatchedVersions: + - 1.6.14 + VulnerableVersions: + - "<= 1.6.14" \ No newline at end of file diff --git a/integration/testdata/fixtures/db/go.yaml b/integration/testdata/fixtures/db/go.yaml new file mode 100644 index 000000000000..0b7696c28db6 --- /dev/null +++ b/integration/testdata/fixtures/db/go.yaml @@ -0,0 +1,26 @@ +- bucket: go::GitHub Security Advisory Go + pairs: + - bucket: golang.org/x/text + pairs: + - key: CVE-2021-38561 + value: + PatchedVersions: + - "0.3.7" + VulnerableVersions: + - ">= 0, < 0.3.7" + - bucket: github.com/docker/distribution + pairs: + - key: GMS-2022-20 + value: + PatchedVersions: + - "v2.8.0" + VulnerableVersions: + - "< v2.8.0" + - bucket: github.com/open-policy-agent/opa + pairs: + - key: CVE-2022-23628 + value: + PatchedVersions: + - "0.37.0" + VulnerableVersions: + - ">= 0.33.1, < 0.37.0" diff --git a/integration/testdata/fixtures/db/java.yaml b/integration/testdata/fixtures/db/java.yaml new file mode 100644 index 000000000000..0ff255077d46 --- /dev/null +++ b/integration/testdata/fixtures/db/java.yaml @@ -0,0 +1,28 @@ +- bucket: maven::GitHub Security Advisory Maven + pairs: + - bucket: com.fasterxml.jackson.core:jackson-databind + pairs: + - key: CVE-2020-9548 + value: + PatchedVersions: + - 2.9.10.4 + VulnerableVersions: + - ">= 2.0.0, <= 2.9.10.3" + - bucket: org.springframework:spring-beans + pairs: + - key: CVE-2022-22965 + value: + PatchedVersions: + - 5.3.18 + VulnerableVersions: + - ">= 5.3.0, < 5.3.18" +- bucket: maven::GitLab Advisory Database Community + pairs: + - bucket: com.fasterxml.jackson.core:jackson-databind + pairs: + - key: CVE-2021-20190 + value: + PatchedVersions: + - 2.9.10.7 + VulnerableVersions: + - "[2.9.0,2.9.10.7)" diff --git a/integration/testdata/fixtures/db/k8s.yaml b/integration/testdata/fixtures/db/k8s.yaml new file mode 100644 index 000000000000..4fa27ca07828 --- /dev/null +++ b/integration/testdata/fixtures/db/k8s.yaml @@ -0,0 +1,16 @@ +- bucket: "k8s::Official Kubernetes CVE Feed" + pairs: + - bucket: k8s.io/kubelet + pairs: + - key: CVE-2023-2431 + value: + PatchedVersions: + - 1.24.14 + - 1.25.9 + - 1.26.4 + - 1.27.1 + VulnerableVersions: + - "< 1.24.14" + - ">= 1.25.0, < 1.25.9" + - ">= 1.26.0, < 1.26.4" + - ">= 1.27.0, < 1.27.1" diff --git a/integration/testdata/fixtures/db/mariner.yaml b/integration/testdata/fixtures/db/mariner.yaml new file mode 100644 index 000000000000..9475840c50a3 --- /dev/null +++ b/integration/testdata/fixtures/db/mariner.yaml @@ -0,0 +1,8 @@ +- bucket: CBL-Mariner 1.0 + pairs: + - bucket: vim + pairs: + - key: CVE-2022-0158 + value: + FixedVersion: 0:8.2.4082-1.cm1 + - key: CVE-2022-0261 diff --git a/integration/testdata/fixtures/db/nodejs.yaml b/integration/testdata/fixtures/db/nodejs.yaml new file mode 100644 index 000000000000..af22671978c9 --- /dev/null +++ b/integration/testdata/fixtures/db/nodejs.yaml @@ -0,0 +1,24 @@ +- bucket: "npm::GitHub Security Advisory Npm" + pairs: + - bucket: jquery + pairs: + - key: CVE-2015-9251 + value: + PatchedVersions: + - 3.0.0 + VulnerableVersions: + - < 3.0.0 + - key: CVE-2019-11358 + value: + PatchedVersions: + - 3.4.0 + VulnerableVersions: + - < 3.4.0 + - bucket: lodash + pairs: + - key: CVE-2019-10744 + value: + PatchedVersions: + - 4.17.12 + VulnerableVersions: + - < 4.17.12 diff --git a/integration/testdata/fixtures/db/nuget.yaml b/integration/testdata/fixtures/db/nuget.yaml new file mode 100644 index 000000000000..304ef92e25be --- /dev/null +++ b/integration/testdata/fixtures/db/nuget.yaml @@ -0,0 +1,10 @@ +- bucket: "nuget::GitHub Security Advisory Nuget" + pairs: + - bucket: Newtonsoft.Json + pairs: + - key: GHSA-5crp-9r3c-p9vr + value: + PatchedVersions: + - 13.0.1 + VulnerableVersions: + - < 13.0.1 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/opensuse.yaml b/integration/testdata/fixtures/db/opensuse.yaml new file mode 100644 index 000000000000..0eb4bf5c351c --- /dev/null +++ b/integration/testdata/fixtures/db/opensuse.yaml @@ -0,0 +1,12 @@ +- bucket: "openSUSE Leap 15.1" + pairs: + - bucket: libopenssl1_1 + pairs: + - key: "openSUSE-SU-2020:0062-1" + value: + FixedVersion: 1.1.0i-lp151.8.6.1 + - bucket: openssl-1_1 + pairs: + - key: "openSUSE-SU-2020:0062-1" + value: + FixedVersion: 1.1.0i-lp151.8.6.1 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/oracle.yaml b/integration/testdata/fixtures/db/oracle.yaml new file mode 100644 index 000000000000..7cc73092d651 --- /dev/null +++ b/integration/testdata/fixtures/db/oracle.yaml @@ -0,0 +1,10 @@ +- bucket: Oracle Linux 8 + pairs: + - bucket: curl + pairs: + - key: CVE-2019-3823 + value: + FixedVersion: 7.61.1-11.el8 + - key: CVE-2019-5436 + value: + FixedVersion: 7.61.1-12.el8 diff --git a/integration/testdata/fixtures/db/photon.yaml b/integration/testdata/fixtures/db/photon.yaml new file mode 100644 index 000000000000..ccc2abfa2576 --- /dev/null +++ b/integration/testdata/fixtures/db/photon.yaml @@ -0,0 +1,12 @@ +- bucket: Photon OS 3.0 + pairs: + - bucket: bash + pairs: + - key: CVE-2019-18276 + value: + FixedVersion: 4.4.18-2.ph3 + - bucket: curl + pairs: + - key: CVE-2019-5481 + value: + FixedVersion: 7.61.1-5.ph3 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/pub.yaml b/integration/testdata/fixtures/db/pub.yaml new file mode 100644 index 000000000000..603bc82cf843 --- /dev/null +++ b/integration/testdata/fixtures/db/pub.yaml @@ -0,0 +1,10 @@ +- bucket: pub::GitHub Security Advisory Pub + pairs: + - bucket: http + pairs: + - key: CVE-2020-35669 + value: + PatchedVersions: + - 0.13.3 + VulnerableVersions: + - "<= 0.13.3" \ No newline at end of file diff --git a/integration/testdata/fixtures/db/python.yaml b/integration/testdata/fixtures/db/python.yaml new file mode 100644 index 000000000000..2d484feff17c --- /dev/null +++ b/integration/testdata/fixtures/db/python.yaml @@ -0,0 +1,16 @@ +- bucket: "pip::GitHub Security Advisory Pip" + pairs: + - bucket: werkzeug + pairs: + - key: CVE-2019-14806 + value: + PatchedVersions: + - 0.15.3 + VulnerableVersions: + - < 0.15.3 + - key: CVE-2020-28724 + value: + PatchedVersions: + - 0.11.6 + VulnerableVersions: + - < 0.11.6 diff --git a/integration/testdata/fixtures/db/redhat.yaml b/integration/testdata/fixtures/db/redhat.yaml new file mode 100644 index 000000000000..2d07860741b6 --- /dev/null +++ b/integration/testdata/fixtures/db/redhat.yaml @@ -0,0 +1,77 @@ +- bucket: Red Hat + pairs: + - bucket: bash + pairs: + - key: CVE-2019-18276 + value: + Entries: + - Affected: + - 596 + - 597 + - 598 + - 601 + - 602 + - 869 + - 870 + - 924 + Cves: + - Severity: 1.0 + Arches: + - x86_64 + Status: 5 + - bucket: openssl + pairs: + - key: RHSA-2019:2304 + value: + Entries: + - Affected: + - 859 + - 860 + - 862 + - 869 + Cves: + - ID: CVE-2018-0734 + Severity: 1.0 + - ID: CVE-2019-1559 + Severity: 2.0 + FixedVersion: 1:1.0.2k-19.el7 + - key: RHSA-2019:2471 + value: + Entries: + - Affected: + - 855 + - 857 + - 858 + - 924 + Cves: + - ID: CVE-2019-1559 + Severity: 2.0 + FixedVersion: 0:1.0.1e-58.el6_10 + - bucket: openssl-libs + pairs: + - key: RHSA-2019:2304 + value: + Entries: + - Affected: + - 859 + - 860 + - 862 + - 869 + Cves: + - ID: CVE-2018-0734 + Severity: 1.0 + - ID: CVE-2019-1559 + Severity: 2.0 + FixedVersion: 1:1.0.2k-19.el7 + - bucket: glibc + pairs: + - key: CVE-2020-29573 + value: + Entries: + - Affected: + - 596 + - 857 + - 858 + Cves: + - Severity: 2.0 + Status: 7 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/rockylinux.yaml b/integration/testdata/fixtures/db/rockylinux.yaml new file mode 100644 index 000000000000..7a18405eec90 --- /dev/null +++ b/integration/testdata/fixtures/db/rockylinux.yaml @@ -0,0 +1,13 @@ +- bucket: rocky 8 + pairs: + - bucket: openssl-libs + pairs: + - key: CVE-2021-3712 + value: + FixedVersion: 1:1.1.1k-5.el8_5 + Entries: + - FixedVersion: "1:1.1.1k-5.el8_5" + Arches: + - x86_64 + VendorIds: + - RLSA-2021:4647 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/ruby.yaml b/integration/testdata/fixtures/db/ruby.yaml new file mode 100644 index 000000000000..802c7b9b05fd --- /dev/null +++ b/integration/testdata/fixtures/db/ruby.yaml @@ -0,0 +1,37 @@ +- bucket: "rubygems::GitHub Security Advisory RubyGems" + pairs: + - bucket: activesupport + pairs: + - key: CVE-2015-3226 + value: + PatchedVersions: + - 3.2.22.5 + - 4.2.2 + - 4.1.11 + VulnerableVersions: + - ">= 3.0.0, <= 3.2.22.4" + - ">= 4.2.0, < 4.2.2" + - ">= 4.1.0, < 4.1.11" + - key: CVE-2015-3227 + value: + PatchedVersions: + - 4.2.2 + - 4.1.11 + VulnerableVersions: + - ">= 4.2.0, < 4.2.2" + - < 4.1.11 + - key: CVE-2020-8165 + value: + PatchedVersions: + - 6.0.3.1 + - 5.2.4.3 + VulnerableVersions: + - ">= 6.0.0, <= 6.0.3" + - ">= 5.0.0, <= 5.2.4.2" + - bucket: http_parser + pairs: + - key: CVE-2020-9999 + value: + VulnerableVersions: + - ">= 0" + diff --git a/integration/testdata/fixtures/db/rust.yaml b/integration/testdata/fixtures/db/rust.yaml new file mode 100644 index 000000000000..a6c80c4358e4 --- /dev/null +++ b/integration/testdata/fixtures/db/rust.yaml @@ -0,0 +1,13 @@ +- bucket: cargo::Open Source Vulnerability + pairs: + - bucket: ammonia + pairs: + - key: CVE-2019-15542 + value: + PatchedVersions: + - ">= 2.1.0" + - key: CVE-2021-38193 + value: + PatchedVersions: + - ">= 3.1.0" + - ">= 2.1.3, < 3.0.0" diff --git a/integration/testdata/fixtures/db/swift.yaml b/integration/testdata/fixtures/db/swift.yaml new file mode 100644 index 000000000000..f380da408a33 --- /dev/null +++ b/integration/testdata/fixtures/db/swift.yaml @@ -0,0 +1,14 @@ +- bucket: "swift::GitHub Security Advisory Swift" + pairs: + - bucket: github.com/apple/swift-nio + pairs: + - key: CVE-2022-3215 + value: + PatchedVersions: + - "2.29.1" + - "2.39.1" + - "2.42.0" + VulnerableVersions: + - "< 2.29.1" + - ">= 2.39.0, < 2.39.1" + - ">= 2.41.0, < 2.42.0" \ No newline at end of file diff --git a/integration/testdata/fixtures/db/ubuntu.yaml b/integration/testdata/fixtures/db/ubuntu.yaml new file mode 100644 index 000000000000..d0de918d0476 --- /dev/null +++ b/integration/testdata/fixtures/db/ubuntu.yaml @@ -0,0 +1,20 @@ +- bucket: ubuntu 18.04 + pairs: + - bucket: bash + pairs: + - key: CVE-2016-9401 + value: + FixedVersion: 4.4-5ubuntu1 + - key: CVE-2019-18276 + value: {} + - bucket: e2fsprogs + pairs: + - key: CVE-2019-5094 + value: + FixedVersion: 1.44.1-1ubuntu1.2 +- bucket: ubuntu 22.04 + pairs: + - bucket: bash + pairs: + - key: CVE-2022-3715 + value: {} \ No newline at end of file diff --git a/integration/testdata/fixtures/db/vulnerability.yaml b/integration/testdata/fixtures/db/vulnerability.yaml new file mode 100644 index 000000000000..1cc7882214be --- /dev/null +++ b/integration/testdata/fixtures/db/vulnerability.yaml @@ -0,0 +1,1393 @@ +- bucket: vulnerability + pairs: + - key: CVE-2022-38177 + value: + Title: "bind: memory leak in ECDSA DNSSEC verification code" + Description: "By spoofing the target resolver with responses that have a malformed ECDSA signature, an attacker can trigger a small memory leak. It is possible to gradually erode available memory to the point where named crashes for lack of resources." + Severity: HIGH + CVSS: + nvd: + V3Score: 7.5 + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N" + redhat: + V3Score: 7.5 + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N" + LastModifiedDate: "2022-09-21T11:15:00Z" + PublishedDate: "2022-09-21T11:15:00Z" + References: + - "http://www.openwall.com/lists/oss-security/2022/09/21/3" + - "https://access.redhat.com/errata/RHSA-2022:6763" + - "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2022-38177.json" + - "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2022-38178.json" + - "https://access.redhat.com/security/cve/CVE-2022-38177" + VendorSeverity: + arch-linux: 2 + nvd: 2 + redhat: 2 + ubuntu: 2 + - key: CVE-2022-3715 + value: + Title: a heap-buffer-overflow in valid_parameter_transform + Severity: LOW + Description: A flaw was found in the bash package, where a heap-buffer overflow can occur in valid parameter_transform. This issue may lead to memory problems. + CVSS: + nvd: + V3Score: 7.8 + V3Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H + redhat: + V3Score: 6.6 + V3Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H + CweIDs: + - CWE-787 + LastModifiedDate: 2023-02-24T18:38:00Z + PublishedDate: 2023-01-05T15:15:00Z + References: + - https://access.redhat.com/errata/RHSA-2023:0340 + - https://access.redhat.com/security/cve/CVE-2022-3715 + - https://bugzilla.redhat.com/2126720 + - https://bugzilla.redhat.com/show_bug.cgi?id=2126720 + VendorSeverity: + cbl-mariner: 3.0 + nvd: 3.0 + photon: 3.0 + redhat: 1.0 + ubuntu: 2.0 + - key: CVE-2016-9401 + value: + CVSS: + nvd: + V2Score: 2.1 + V2Vector: AV:L/AC:L/Au:N/C:N/I:N/A:P + V3Score: 5.5 + V3Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H + redhat: + V2Score: 1.9 + V2Vector: AV:L/AC:M/Au:N/C:N/I:N/A:P + V3Score: 3.3 + V3Vector: CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L + CweIDs: + - CWE-416 + Description: popd in bash might allow local users to bypass the restricted shell and cause a use-after-free via a crafted address. + LastModifiedDate: 2020-09-14T18:32:00Z + PublishedDate: 2017-01-23T21:59:00Z + References: + - http://rhn.redhat.com/errata/RHSA-2017-0725.html + - http://www.openwall.com/lists/oss-security/2016/11/17/5 + - http://www.openwall.com/lists/oss-security/2016/11/17/9 + - http://www.securityfocus.com/bid/94398 + - https://access.redhat.com/errata/RHSA-2017:1931 + - https://access.redhat.com/security/cve/CVE-2016-9401 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-9401 + - https://linux.oracle.com/cve/CVE-2016-9401.html + - https://linux.oracle.com/errata/ELSA-2017-1931.html + - https://lists.debian.org/debian-lts-announce/2019/03/msg00028.html + - https://security.gentoo.org/glsa/201701-02 + - https://ubuntu.com/security/notices/USN-3294-1 + Severity: MEDIUM + Title: "bash: popd controlled free" + VendorSeverity: + amazon: 2.0 + nvd: 2.0 + oracle-oval: 2.0 + photon: 2.0 + redhat: 1.0 + ubuntu: 1.0 + - key: CVE-2018-0734 + value: + CVSS: + nvd: + V2Score: 4.3 + V2Vector: AV:N/AC:M/Au:N/C:P/I:N/A:N + V3Score: 5.9 + V3Vector: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N + redhat: + V3Score: 5.1 + V3Vector: CVSS:3.0/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N + CweIDs: + - CWE-327 + Description: The OpenSSL DSA signature algorithm has been shown to be vulnerable to a timing side channel attack. An attacker could use variations in the signing algorithm to recover the private key. Fixed in OpenSSL 1.1.1a (Affected 1.1.1). Fixed in OpenSSL 1.1.0j (Affected 1.1.0-1.1.0i). Fixed in OpenSSL 1.0.2q (Affected 1.0.2-1.0.2p). + LastModifiedDate: 2020-08-24T17:37:00Z + PublishedDate: 2018-10-30T12:29:00Z + References: + - http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00030.html + - http://lists.opensuse.org/opensuse-security-announce/2019-07/msg00056.html + - http://www.securityfocus.com/bid/105758 + - https://access.redhat.com/errata/RHSA-2019:2304 + - https://access.redhat.com/errata/RHSA-2019:3700 + - https://access.redhat.com/errata/RHSA-2019:3932 + - https://access.redhat.com/errata/RHSA-2019:3933 + - https://access.redhat.com/errata/RHSA-2019:3935 + - https://access.redhat.com/security/cve/CVE-2018-0734 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0734 + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=43e6a58d4991a451daf4891ff05a48735df871ac + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=8abfe72e8c1de1b95f50aa0d9134803b4d00070f + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=ef11e19d1365eea2b1851e6f540a0bf365d303e7 + - https://linux.oracle.com/cve/CVE-2018-0734.html + - https://linux.oracle.com/errata/ELSA-2019-3700.html + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EWC42UXL5GHTU5G77VKBF6JYUUNGSHOM/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Y3IVFGSERAZLNJCK35TEM2R4726XIH3Z/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBEV5QGDRFUZDMNECFXUSN5FMYOZDE4V/ + - https://nodejs.org/en/blog/vulnerability/november-2018-security-releases/ + - https://nvd.nist.gov/vuln/detail/CVE-2018-0734 + - https://security.netapp.com/advisory/ntap-20181105-0002/ + - https://security.netapp.com/advisory/ntap-20190118-0002/ + - https://security.netapp.com/advisory/ntap-20190423-0002/ + - https://ubuntu.com/security/notices/USN-3840-1 + - https://usn.ubuntu.com/3840-1/ + - https://www.debian.org/security/2018/dsa-4348 + - https://www.debian.org/security/2018/dsa-4355 + - https://www.openssl.org/news/secadv/20181030.txt + - https://www.oracle.com/security-alerts/cpuapr2020.html + - https://www.oracle.com/security-alerts/cpujan2020.html + - https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html + - https://www.oracle.com/technetwork/security-advisory/cpujan2019-5072801.html + - https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html + - https://www.tenable.com/security/tns-2018-16 + - https://www.tenable.com/security/tns-2018-17 + Severity: MEDIUM + Title: "openssl: timing side channel attack in the DSA signature algorithm" + VendorSeverity: + amazon: 2.0 + arch-linux: 1.0 + cbl-mariner: 2.0 + nvd: 2.0 + oracle-oval: 1.0 + photon: 2.0 + redhat: 1.0 + ubuntu: 1.0 + - key: CVE-2019-10744 + value: + CVSS: + nvd: + V2Score: 6.4 + V2Vector: AV:N/AC:L/Au:N/C:N/I:P/A:P + V3Score: 9.1 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H + redhat: + V3Score: 9.1 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H + Description: Versions of lodash lower than 4.17.12 are vulnerable to Prototype Pollution. The function defaultsDeep could be tricked into adding or modifying properties of Object.prototype using a constructor payload. + LastModifiedDate: 2021-03-16T13:57:00Z + PublishedDate: 2019-07-26T00:15:00Z + References: + - https://access.redhat.com/errata/RHSA-2019:3024 + - https://access.redhat.com/security/cve/CVE-2019-10744 + - https://github.com/advisories/GHSA-jf85-cpcp-j695 + - https://github.com/lodash/lodash/pull/4336 + - https://nvd.nist.gov/vuln/detail/CVE-2019-10744 + - https://security.netapp.com/advisory/ntap-20191004-0005/ + - https://snyk.io/vuln/SNYK-JS-LODASH-450202 + - https://support.f5.com/csp/article/K47105354?utm_source=f5support&utm_medium=RSS + - https://www.npmjs.com/advisories/1065 + - https://www.oracle.com/security-alerts/cpujan2021.html + - https://www.oracle.com/security-alerts/cpuoct2020.html + Severity: CRITICAL + Title: "nodejs-lodash: prototype pollution in defaultsDeep function leading to modifying properties" + VendorSeverity: + ghsa: 4.0 + nvd: 4.0 + redhat: 3.0 + - key: CVE-2019-11358 + value: + CVSS: + nvd: + V2Score: 4.3 + V2Vector: AV:N/AC:M/Au:N/C:N/I:P/A:N + V3Score: 6.1 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N + redhat: + V3Score: 5.6 + V3Vector: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L + CweIDs: + - CWE-79 + Description: jQuery before 3.4.0, as used in Drupal, Backdrop CMS, and other products, mishandles jQuery.extend(true, {}, ...) because of Object.prototype pollution. If an unsanitized source object contained an enumerable __proto__ property, it could extend the native Object.prototype. + LastModifiedDate: 2021-10-20T11:15:00Z + PublishedDate: 2019-04-20T00:29:00Z + References: + - http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00006.html + - http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00025.html + - http://packetstormsecurity.com/files/152787/dotCMS-5.1.1-Vulnerable-Dependencies.html + - http://packetstormsecurity.com/files/153237/RetireJS-CORS-Issue-Script-Execution.html + - http://packetstormsecurity.com/files/156743/OctoberCMS-Insecure-Dependencies.html + - http://seclists.org/fulldisclosure/2019/May/10 + - http://seclists.org/fulldisclosure/2019/May/11 + - http://seclists.org/fulldisclosure/2019/May/13 + - http://www.openwall.com/lists/oss-security/2019/06/03/2 + - http://www.securityfocus.com/bid/108023 + - https://access.redhat.com/errata/RHBA-2019:1570 + - https://access.redhat.com/errata/RHSA-2019:1456 + - https://access.redhat.com/errata/RHSA-2019:2587 + - https://access.redhat.com/errata/RHSA-2019:3023 + - https://access.redhat.com/errata/RHSA-2019:3024 + - https://access.redhat.com/security/cve/CVE-2019-11358 + - https://backdropcms.org/security/backdrop-sa-core-2019-009 + - https://blog.jquery.com/2019/04/10/jquery-3-4-0-released/ + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11358 + - https://github.com/DanielRuf/snyk-js-jquery-174006?files=1 + - https://github.com/advisories/GHSA-6c3j-c64m-qhgq + - https://github.com/jquery/jquery/commit/753d591aea698e57d6db58c9f722cd0808619b1b + - https://github.com/jquery/jquery/pull/4333 + - "https://github.com/rails/jquery-rails/blob/master/CHANGELOG.md#434" + - https://hackerone.com/reports/454365 + - https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44601 + - https://linux.oracle.com/cve/CVE-2019-11358.html + - https://linux.oracle.com/errata/ELSA-2020-4847.html + - https://lists.apache.org/thread.html/08720ef215ee7ab3386c05a1a90a7d1c852bf0706f176a7816bf65fc@%3Ccommits.airflow.apache.org%3E + - https://lists.apache.org/thread.html/519eb0fd45642dcecd9ff74cb3e71c20a4753f7d82e2f07864b5108f@%3Cdev.drill.apache.org%3E + - https://lists.apache.org/thread.html/5928aa293e39d248266472210c50f176cac1535220f2486e6a7fa844@%3Ccommits.airflow.apache.org%3E + - https://lists.apache.org/thread.html/6097cdbd6f0a337bedd9bb5cc441b2d525ff002a96531de367e4259f@%3Ccommits.airflow.apache.org%3E + - https://lists.apache.org/thread.html/88fb0362fd40e5b605ea8149f63241537b8b6fb5bfa315391fc5cbb7@%3Ccommits.airflow.apache.org%3E + - https://lists.apache.org/thread.html/b0656d359c7d40ec9f39c8cc61bca66802ef9a2a12ee199f5b0c1442@%3Cdev.drill.apache.org%3E + - https://lists.apache.org/thread.html/b736d0784cf02f5a30fbb4c5902762a15ad6d47e17e2c5a17b7d6205@%3Ccommits.airflow.apache.org%3E + - https://lists.apache.org/thread.html/ba79cf1658741e9f146e4c59b50aee56656ea95d841d358d006c18b6@%3Ccommits.roller.apache.org%3E + - https://lists.apache.org/thread.html/bcce5a9c532b386c68dab2f6b3ce8b0cc9b950ec551766e76391caa3@%3Ccommits.nifi.apache.org%3E + - https://lists.apache.org/thread.html/f9bc3e55f4e28d1dcd1a69aae6d53e609a758e34d2869b4d798e13cc@%3Cissues.drill.apache.org%3E + - https://lists.apache.org/thread.html/r2041a75d3fc09dec55adfd95d598b38d22715303f65c997c054844c9@%3Cissues.flink.apache.org%3E + - https://lists.apache.org/thread.html/r2baacab6e0acb5a2092eb46ae04fd6c3e8277b4fd79b1ffb7f3254fa@%3Cissues.flink.apache.org%3E + - https://lists.apache.org/thread.html/r38f0d1aa3c923c22977fe7376508f030f22e22c1379fbb155bf29766@%3Cdev.syncope.apache.org%3E + - https://lists.apache.org/thread.html/r41b5bfe009c845f67d4f68948cc9419ac2d62e287804aafd72892b08@%3Cissues.flink.apache.org%3E + - https://lists.apache.org/thread.html/r7aac081cbddb6baa24b75e74abf0929bf309b176755a53e3ed810355@%3Cdev.flink.apache.org%3E + - https://lists.apache.org/thread.html/r7d64895cc4dff84d0becfc572b20c0e4bf9bfa7b10c6f5f73e783734@%3Cdev.storm.apache.org%3E + - https://lists.apache.org/thread.html/r7e8ebccb7c022e41295f6fdb7b971209b83702339f872ddd8cf8bf73@%3Cissues.flink.apache.org%3E + - https://lists.apache.org/thread.html/rac25da84ecdcd36f6de5ad0d255f4e967209bbbebddb285e231da37d@%3Cissues.flink.apache.org%3E + - https://lists.apache.org/thread.html/rca37935d661f4689cb4119f1b3b224413b22be161b678e6e6ce0c69b@%3Ccommits.nifi.apache.org%3E + - https://lists.debian.org/debian-lts-announce/2019/05/msg00006.html + - https://lists.debian.org/debian-lts-announce/2019/05/msg00029.html + - https://lists.debian.org/debian-lts-announce/2020/02/msg00024.html + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/4UOAZIFCSZ3ENEFOR5IXX6NFAD3HV7FA/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5IABSKTYZ5JUGL735UKGXL5YPRYOPUYI/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KYH3OAGR2RTCHRA5NOKX2TES7SNQMWGO/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QV3PKZC3PQCO3273HAT76PAQZFBEO4KP/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RLXRX23725JL366CNZGJZ7AQQB7LHQ6F/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WZW27UCJ5CYFL4KFFFMYMIBNMIU2ALG5/ + - https://nvd.nist.gov/vuln/detail/CVE-2019-11358 + - https://seclists.org/bugtraq/2019/Apr/32 + - https://seclists.org/bugtraq/2019/Jun/12 + - https://seclists.org/bugtraq/2019/May/18 + - https://security.netapp.com/advisory/ntap-20190919-0001/ + - https://snyk.io/vuln/SNYK-JS-JQUERY-174006 + - https://www.debian.org/security/2019/dsa-4434 + - https://www.debian.org/security/2019/dsa-4460 + - https://www.drupal.org/sa-core-2019-006 + - https://www.oracle.com//security-alerts/cpujul2021.html + - https://www.oracle.com/security-alerts/cpuApr2021.html + - https://www.oracle.com/security-alerts/cpuapr2020.html + - https://www.oracle.com/security-alerts/cpujan2020.html + - https://www.oracle.com/security-alerts/cpujan2021.html + - https://www.oracle.com/security-alerts/cpujul2020.html + - https://www.oracle.com/security-alerts/cpuoct2020.html + - https://www.oracle.com/security-alerts/cpuoct2021.html + - https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html + - https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html + - https://www.privacy-wise.com/mitigating-cve-2019-11358-in-old-versions-of-jquery/ + - https://www.synology.com/security/advisory/Synology_SA_19_19 + - https://www.tenable.com/security/tns-2019-08 + - https://www.tenable.com/security/tns-2020-02 + Severity: MEDIUM + Title: "jquery: Prototype pollution in object's prototype leading to denial of service, remote code execution, or property injection" + VendorSeverity: + alma: 2.0 + amazon: 2.0 + arch-linux: 2.0 + ghsa: 2.0 + nodejs-security-wg: 2.0 + nvd: 2.0 + oracle-oval: 2.0 + redhat: 2.0 + ruby-advisory-db: 2.0 + ubuntu: 1.0 + - key: CVE-2019-14697 + value: + CVSS: + nvd: + V2Score: 7.5 + V2Vector: AV:N/AC:L/Au:N/C:P/I:P/A:P + V3Score: 9.8 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + CweIDs: + - CWE-787 + Description: musl libc through 1.1.23 has an x87 floating-point stack adjustment imbalance, related to the math/i386/ directory. In some cases, use of this library could introduce out-of-bounds writes that are not present in an application's source code. + LastModifiedDate: 2020-03-14T19:15:00Z + PublishedDate: 2019-08-06T16:15:00Z + References: + - http://www.openwall.com/lists/oss-security/2019/08/06/4 + - https://security.gentoo.org/glsa/202003-13 + - https://www.openwall.com/lists/musl/2019/08/06/1 + Severity: CRITICAL + VendorSeverity: + nvd: 4.0 + - key: CVE-2019-14806 + value: + CVSS: + nvd: + V2Score: 5.0 + V2Vector: AV:N/AC:L/Au:N/C:P/I:N/A:N + V3Score: 7.5 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N + redhat: + V3Score: 7.5 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N + CweIDs: + - CWE-331 + Description: Pallets Werkzeug before 0.15.3, when used with Docker, has insufficient debugger PIN randomness because Docker containers share the same machine id. + LastModifiedDate: 2019-09-11T00:15:00Z + PublishedDate: 2019-08-09T15:15:00Z + References: + - http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00034.html + - http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00047.html + - https://access.redhat.com/security/cve/CVE-2019-14806 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-14806 + - https://github.com/advisories/GHSA-gq9m-qvpx-68hc + - "https://github.com/pallets/werkzeug/blob/7fef41b120327d3912fbe12fb64f1951496fcf3e/src/werkzeug/debug/__init__.py#L168" + - https://github.com/pallets/werkzeug/commit/00bc43b1672e662e5e3b8cecd79e67fc968fa246 + - https://nvd.nist.gov/vuln/detail/CVE-2019-14806 + - https://palletsprojects.com/blog/werkzeug-0-15-3-released/ + - https://ubuntu.com/security/notices/USN-4655-1 + Severity: HIGH + Title: "python-werkzeug: insufficient debugger PIN randomness vulnerability" + VendorSeverity: + ghsa: 3.0 + nvd: 3.0 + redhat: 2.0 + ubuntu: 1.0 + - key: CVE-2019-1549 + value: + CVSS: + nvd: + V2Score: 5.0 + V2Vector: AV:N/AC:L/Au:N/C:P/I:N/A:N + V3Score: 5.3 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N + redhat: + V3Score: 4.8 + V3Vector: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N + CweIDs: + - CWE-330 + Description: OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c). + LastModifiedDate: 2020-10-20T22:15:00Z + PublishedDate: 2019-09-10T17:15:00Z + References: + - https://access.redhat.com/security/cve/CVE-2019-1549 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549 + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be + - https://linux.oracle.com/cve/CVE-2019-1549.html + - https://linux.oracle.com/errata/ELSA-2020-1840.html + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/ + - https://seclists.org/bugtraq/2019/Oct/1 + - https://security.netapp.com/advisory/ntap-20190919-0002/ + - https://support.f5.com/csp/article/K44070243 + - https://support.f5.com/csp/article/K44070243?utm_source=f5support&utm_medium=RSS + - https://ubuntu.com/security/notices/USN-4376-1 + - https://usn.ubuntu.com/4376-1/ + - https://www.debian.org/security/2019/dsa-4539 + - https://www.openssl.org/news/secadv/20190910.txt + - https://www.oracle.com/security-alerts/cpuapr2020.html + - https://www.oracle.com/security-alerts/cpujan2020.html + - https://www.oracle.com/security-alerts/cpujul2020.html + - https://www.oracle.com/security-alerts/cpuoct2020.html + - https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html + Severity: MEDIUM + Title: "openssl: information disclosure in fork()" + VendorSeverity: + amazon: 2.0 + nvd: 2.0 + oracle-oval: 2.0 + photon: 2.0 + redhat: 1.0 + ubuntu: 1.0 + - key: CVE-2019-1551 + value: + CVSS: + nvd: + V2Score: 5.0 + V2Vector: AV:N/AC:L/Au:N/C:P/I:N/A:N + V3Score: 5.3 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N + redhat: + V3Score: 4.8 + V3Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N + CweIDs: + - CWE-200 + Description: There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t). + LastModifiedDate: 2021-07-21T11:39:00Z + PublishedDate: 2019-12-06T18:15:00Z + References: + - http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html + - http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html + - https://access.redhat.com/security/cve/CVE-2019-1551 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551 + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98 + - https://github.com/openssl/openssl/pull/10575 + - https://linux.oracle.com/cve/CVE-2019-1551.html + - https://linux.oracle.com/errata/ELSA-2020-4514.html + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/ + - https://seclists.org/bugtraq/2019/Dec/39 + - https://seclists.org/bugtraq/2019/Dec/46 + - https://security.gentoo.org/glsa/202004-10 + - https://security.netapp.com/advisory/ntap-20191210-0001/ + - https://ubuntu.com/security/notices/USN-4376-1 + - https://ubuntu.com/security/notices/USN-4504-1 + - https://usn.ubuntu.com/4376-1/ + - https://usn.ubuntu.com/4504-1/ + - https://www.debian.org/security/2019/dsa-4594 + - https://www.debian.org/security/2021/dsa-4855 + - https://www.openssl.org/news/secadv/20191206.txt + - https://www.oracle.com/security-alerts/cpuApr2021.html + - https://www.oracle.com/security-alerts/cpujan2021.html + - https://www.oracle.com/security-alerts/cpujul2020.html + - https://www.tenable.com/security/tns-2019-09 + - https://www.tenable.com/security/tns-2020-03 + - https://www.tenable.com/security/tns-2020-11 + - https://www.tenable.com/security/tns-2021-10 + Severity: MEDIUM + Title: "openssl: Integer overflow in RSAZ modular exponentiation on x86_64" + VendorSeverity: + amazon: 1.0 + nvd: 2.0 + oracle-oval: 1.0 + photon: 2.0 + redhat: 1.0 + ubuntu: 1.0 + - key: CVE-2019-15542 + value: + CVSS: + nvd: + V2Score: 5.0 + V2Vector: AV:N/AC:L/Au:N/C:N/I:N/A:P + V3Score: 7.5 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H + CweIDs: + - CWE-674 + Description: An issue was discovered in the ammonia crate before 2.1.0 for Rust. There is uncontrolled recursion during HTML DOM tree serialization. + LastModifiedDate: 2020-08-24T17:37:00Z + PublishedDate: 2019-08-26T18:15:00Z + References: + - https://crates.io/crates/ammonia + - "https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md#210" + - https://rustsec.org/advisories/RUSTSEC-2019-0001.html + Severity: HIGH + Title: Uncontrolled recursion leads to abort in HTML serialization + VendorSeverity: + nvd: 3.0 + - key: CVE-2019-1559 + value: + CVSS: + nvd: + V2Score: 4.3 + V2Vector: AV:N/AC:M/Au:N/C:P/I:N/A:N + V3Score: 5.9 + V3Vector: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N + redhat: + V3Score: 5.9 + V3Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N + CweIDs: + - CWE-203 + Description: If an application encounters a fatal protocol error and then calls SSL_shutdown() twice (once to send a close_notify, and once to receive one) then OpenSSL can respond differently to the calling application if a 0 byte record is received with invalid padding compared to if a 0 byte record is received with an invalid MAC. If the application then behaves differently based on that in a way that is detectable to the remote peer, then this amounts to a padding oracle that could be used to decrypt data. In order for this to be exploitable "non-stitched" ciphersuites must be in use. Stitched ciphersuites are optimised implementations of certain commonly used ciphersuites. Also the application must call SSL_shutdown() twice even if a protocol error has occurred (applications should not do this but some do anyway). Fixed in OpenSSL 1.0.2r (Affected 1.0.2-1.0.2q). + LastModifiedDate: 2021-01-20T15:15:00Z + PublishedDate: 2019-02-27T23:29:00Z + References: + - http://lists.opensuse.org/opensuse-security-announce/2019-03/msg00041.html + - http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00019.html + - http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00046.html + - http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00047.html + - http://lists.opensuse.org/opensuse-security-announce/2019-05/msg00049.html + - http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00080.html + - http://www.securityfocus.com/bid/107174 + - https://access.redhat.com/errata/RHSA-2019:2304 + - https://access.redhat.com/errata/RHSA-2019:2437 + - https://access.redhat.com/errata/RHSA-2019:2439 + - https://access.redhat.com/errata/RHSA-2019:2471 + - https://access.redhat.com/errata/RHSA-2019:3929 + - https://access.redhat.com/errata/RHSA-2019:3931 + - https://access.redhat.com/security/cve/CVE-2019-1559 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1559 + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e9bbefbf0f24c57645e7ad6a5a71ae649d18ac8e + - https://github.com/RUB-NDS/TLS-Padding-Oracles + - https://kc.mcafee.com/corporate/index?page=content&id=SB10282 + - https://linux.oracle.com/cve/CVE-2019-1559.html + - https://linux.oracle.com/errata/ELSA-2019-2471.html + - https://lists.debian.org/debian-lts-announce/2019/03/msg00003.html + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EWC42UXL5GHTU5G77VKBF6JYUUNGSHOM/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Y3IVFGSERAZLNJCK35TEM2R4726XIH3Z/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBEV5QGDRFUZDMNECFXUSN5FMYOZDE4V/ + - https://security.gentoo.org/glsa/201903-10 + - https://security.netapp.com/advisory/ntap-20190301-0001/ + - https://security.netapp.com/advisory/ntap-20190301-0002/ + - https://security.netapp.com/advisory/ntap-20190423-0002/ + - https://support.f5.com/csp/article/K18549143 + - https://support.f5.com/csp/article/K18549143?utm_source=f5support&utm_medium=RSS + - https://ubuntu.com/security/notices/USN-3899-1 + - https://ubuntu.com/security/notices/USN-4376-2 + - https://usn.ubuntu.com/3899-1/ + - https://usn.ubuntu.com/4376-2/ + - https://www.debian.org/security/2019/dsa-4400 + - https://www.openssl.org/news/secadv/20190226.txt + - https://www.oracle.com/security-alerts/cpujan2020.html + - https://www.oracle.com/security-alerts/cpujan2021.html + - https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html + - https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html + - https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html + - https://www.tenable.com/security/tns-2019-02 + - https://www.tenable.com/security/tns-2019-03 + Severity: MEDIUM + Title: "openssl: 0-byte record padding oracle" + VendorSeverity: + amazon: 2.0 + arch-linux: 2.0 + nvd: 2.0 + oracle-oval: 2.0 + redhat: 2.0 + ubuntu: 2.0 + - key: CVE-2019-1563 + value: + CVSS: + nvd: + V2Score: 4.3 + V2Vector: AV:N/AC:M/Au:N/C:P/I:N/A:N + V3Score: 3.7 + V3Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N + redhat: + V3Score: 3.7 + V3Vector: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N + CweIDs: + - CWE-327 + - CWE-203 + Description: In situations where an attacker receives automated notification of the success or failure of a decryption attempt an attacker, after sending a very large number of messages to be decrypted, can recover a CMS/PKCS7 transported encryption key or decrypt any RSA encrypted message that was encrypted with the public RSA key, using a Bleichenbacher padding oracle attack. Applications are not affected if they use a certificate together with the private RSA key to the CMS_decrypt or PKCS7_decrypt functions to select the correct recipient info to decrypt. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c). Fixed in OpenSSL 1.1.0l (Affected 1.1.0-1.1.0k). Fixed in OpenSSL 1.0.2t (Affected 1.0.2-1.0.2s). + LastModifiedDate: 2021-07-31T08:15:00Z + PublishedDate: 2019-09-10T17:15:00Z + References: + - http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00054.html + - http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00072.html + - http://lists.opensuse.org/opensuse-security-announce/2019-10/msg00012.html + - http://lists.opensuse.org/opensuse-security-announce/2019-10/msg00016.html + - http://packetstormsecurity.com/files/154467/Slackware-Security-Advisory-openssl-Updates.html + - https://access.redhat.com/security/cve/CVE-2019-1563 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1563 + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=08229ad838c50f644d7e928e2eef147b4308ad64 + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=631f94db0065c78181ca9ba5546ebc8bb3884b97 + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e21f8cf78a125cd3c8c0d1a1a6c8bb0b901f893f + - https://kc.mcafee.com/corporate/index?page=content&id=SB10365 + - https://linux.oracle.com/cve/CVE-2019-1563.html + - https://linux.oracle.com/errata/ELSA-2020-1840.html + - https://lists.debian.org/debian-lts-announce/2019/09/msg00026.html + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/ + - https://seclists.org/bugtraq/2019/Oct/0 + - https://seclists.org/bugtraq/2019/Oct/1 + - https://seclists.org/bugtraq/2019/Sep/25 + - https://security.gentoo.org/glsa/201911-04 + - https://security.netapp.com/advisory/ntap-20190919-0002/ + - https://support.f5.com/csp/article/K97324400?utm_source=f5support&utm_medium=RSS + - https://ubuntu.com/security/notices/USN-4376-1 + - https://ubuntu.com/security/notices/USN-4376-2 + - https://ubuntu.com/security/notices/USN-4504-1 + - https://usn.ubuntu.com/4376-1/ + - https://usn.ubuntu.com/4376-2/ + - https://usn.ubuntu.com/4504-1/ + - https://www.debian.org/security/2019/dsa-4539 + - https://www.debian.org/security/2019/dsa-4540 + - https://www.openssl.org/news/secadv/20190910.txt + - https://www.oracle.com/security-alerts/cpuapr2020.html + - https://www.oracle.com/security-alerts/cpujan2020.html + - https://www.oracle.com/security-alerts/cpujul2020.html + - https://www.oracle.com/security-alerts/cpuoct2020.html + - https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html + - https://www.tenable.com/security/tns-2019-09 + Severity: LOW + Title: "openssl: information disclosure in PKCS7_dataDecode and CMS_decrypt_set1_pkey" + VendorSeverity: + amazon: 2.0 + nvd: 1.0 + oracle-oval: 2.0 + photon: 1.0 + redhat: 1.0 + ubuntu: 1.0 + - key: CVE-2019-18224 + value: + CVSS: + nvd: + V2Score: 7.5 + V2Vector: AV:N/AC:L/Au:N/C:P/I:P/A:P + V3Score: 9.8 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + redhat: + V3Score: 5.6 + V3Vector: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L + CweIDs: + - CWE-787 + Description: idn2_to_ascii_4i in lib/lookup.c in GNU libidn2 before 2.1.1 has a heap-based buffer overflow via a long domain string. + LastModifiedDate: 2019-10-29T19:15:00Z + PublishedDate: 2019-10-21T17:15:00Z + References: + - http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00008.html + - http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00009.html + - https://access.redhat.com/security/cve/CVE-2019-18224 + - https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=12420 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18224 + - https://github.com/libidn/libidn2/commit/e4d1558aa2c1c04a05066ee8600f37603890ba8c + - https://github.com/libidn/libidn2/compare/libidn2-2.1.0...libidn2-2.1.1 + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JDQVQ2XPV5BTZUFINT7AFJSKNNBVURNJ/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MINU5RKDFE6TKAFY5DRFN3WSFDS4DYVS/ + - https://seclists.org/bugtraq/2020/Feb/4 + - https://security.gentoo.org/glsa/202003-63 + - https://ubuntu.com/security/notices/USN-4168-1 + - https://usn.ubuntu.com/4168-1/ + - https://www.debian.org/security/2020/dsa-4613 + Severity: CRITICAL + Title: "libidn2: heap-based buffer overflow in idn2_to_ascii_4i in lib/lookup.c" + VendorSeverity: + amazon: 2.0 + nvd: 4.0 + redhat: 2.0 + ubuntu: 2.0 + - key: CVE-2019-18276 + value: + CVSS: + nvd: + V2Score: 7.2 + V2Vector: AV:L/AC:L/Au:N/C:C/I:C/A:C + V3Score: 7.8 + V3Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H + redhat: + V3Score: 7.8 + V3Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H + CweIDs: + - CWE-273 + Description: An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support "saved UID" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use "enable -f" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected. + LastModifiedDate: 2021-05-26T12:15:00Z + PublishedDate: 2019-11-28T01:15:00Z + References: + - http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html + - https://access.redhat.com/security/cve/CVE-2019-18276 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276 + - https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff + - https://linux.oracle.com/cve/CVE-2019-18276.html + - https://linux.oracle.com/errata/ELSA-2021-1679.html + - https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E + - https://nvd.nist.gov/vuln/detail/CVE-2019-18276 + - https://security.gentoo.org/glsa/202105-34 + - https://security.netapp.com/advisory/ntap-20200430-0003/ + - https://www.youtube.com/watch?v=-wGtxJ8opa8 + Severity: HIGH + Title: "bash: when effective UID is not equal to its real UID the saved UID is not dropped" + VendorSeverity: + cbl-mariner: 3.0 + nvd: 3.0 + oracle-oval: 1.0 + photon: 3.0 + redhat: 1.0 + ubuntu: 1.0 + - key: CVE-2019-3823 + value: + CVSS: + nvd: + V2Score: 5.0 + V2Vector: AV:N/AC:L/Au:N/C:N/I:N/A:P + V3Score: 7.5 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H + redhat: + V3Score: 4.3 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L + CweIDs: + - CWE-125 + Description: libcurl versions from 7.34.0 to before 7.64.0 are vulnerable to a heap out-of-bounds read in the code handling the end-of-response for SMTP. If the buffer passed to `smtp_endofresp()` isn't NUL terminated and contains no character ending the parsed number, and `len` is set to 5, then the `strtol()` call reads beyond the allocated buffer. The read contents will not be returned to the caller. + LastModifiedDate: 2021-03-09T15:15:00Z + PublishedDate: 2019-02-06T20:29:00Z + References: + - http://www.securityfocus.com/bid/106950 + - https://access.redhat.com/errata/RHSA-2019:3701 + - https://access.redhat.com/security/cve/CVE-2019-3823 + - https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2019-3823 + - https://cert-portal.siemens.com/productcert/pdf/ssa-936080.pdf + - https://curl.haxx.se/docs/CVE-2019-3823.html + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-3823 + - https://linux.oracle.com/cve/CVE-2019-3823.html + - https://linux.oracle.com/errata/ELSA-2019-3701.html + - https://lists.apache.org/thread.html/8338a0f605bdbb3a6098bb76f666a95fc2b2f53f37fa1ecc89f1146f@%3Cdevnull.infra.apache.org%3E + - https://security.gentoo.org/glsa/201903-03 + - https://security.netapp.com/advisory/ntap-20190315-0001/ + - https://ubuntu.com/security/notices/USN-3882-1 + - https://usn.ubuntu.com/3882-1/ + - https://www.debian.org/security/2019/dsa-4386 + - https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html + - https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html + Severity: HIGH + Title: "curl: SMTP end-of-response out-of-bounds read" + VendorSeverity: + amazon: 2.0 + arch-linux: 3.0 + nvd: 3.0 + oracle-oval: 2.0 + photon: 3.0 + redhat: 1.0 + ubuntu: 1.0 + - key: CVE-2019-5094 + value: + CVSS: + nvd: + V2Score: 4.6 + V2Vector: AV:L/AC:L/Au:N/C:P/I:P/A:P + V3Score: 6.7 + V3Vector: CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H + redhat: + V3Score: 6.4 + V3Vector: CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H + CweIDs: + - CWE-787 + Description: An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability. + LastModifiedDate: 2021-01-11T19:21:00Z + PublishedDate: 2019-09-24T22:15:00Z + References: + - https://access.redhat.com/security/cve/CVE-2019-5094 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094 + - https://linux.oracle.com/cve/CVE-2019-5094.html + - https://linux.oracle.com/errata/ELSA-2020-4011.html + - https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/ + - https://nvd.nist.gov/vuln/detail/CVE-2019-5094 + - https://seclists.org/bugtraq/2019/Sep/58 + - https://security.gentoo.org/glsa/202003-05 + - https://security.netapp.com/advisory/ntap-20200115-0002/ + - https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887 + - https://ubuntu.com/security/notices/USN-4142-1 + - https://ubuntu.com/security/notices/USN-4142-2 + - https://usn.ubuntu.com/4142-1/ + - https://usn.ubuntu.com/4142-2/ + - https://www.debian.org/security/2019/dsa-4535 + Severity: MEDIUM + Title: "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write" + VendorSeverity: + amazon: 2.0 + cbl-mariner: 2.0 + nvd: 2.0 + oracle-oval: 2.0 + photon: 2.0 + redhat: 2.0 + ubuntu: 2.0 + - key: CVE-2019-5436 + value: + CVSS: + nvd: + V2Score: 4.6 + V2Vector: AV:L/AC:L/Au:N/C:P/I:P/A:P + V3Score: 7.8 + V3Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H + redhat: + V3Score: 7.0 + V3Vector: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H + CweIDs: + - CWE-787 + Description: A heap buffer overflow in the TFTP receiving code allows for DoS or arbitrary code execution in libcurl versions 7.19.4 through 7.64.1. + LastModifiedDate: 2020-10-20T22:15:00Z + PublishedDate: 2019-05-28T19:29:00Z + References: + - http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00008.html + - http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00017.html + - http://www.openwall.com/lists/oss-security/2019/09/11/6 + - https://access.redhat.com/security/cve/CVE-2019-5436 + - https://curl.haxx.se/docs/CVE-2019-5436.html + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5436 + - https://linux.oracle.com/cve/CVE-2019-5436.html + - https://linux.oracle.com/errata/ELSA-2020-1792.html + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/SMG3V4VTX2SE3EW3HQTN3DDLQBTORQC2/ + - https://seclists.org/bugtraq/2020/Feb/36 + - https://security.gentoo.org/glsa/202003-29 + - https://security.netapp.com/advisory/ntap-20190606-0004/ + - https://support.f5.com/csp/article/K55133295 + - https://support.f5.com/csp/article/K55133295?utm_source=f5support&utm_medium=RSS + - https://ubuntu.com/security/notices/USN-3993-1 + - https://ubuntu.com/security/notices/USN-3993-2 + - https://www.debian.org/security/2020/dsa-4633 + - https://www.oracle.com/security-alerts/cpuapr2020.html + - https://www.oracle.com/security-alerts/cpuoct2020.html + - https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html + Severity: HIGH + Title: "curl: TFTP receive heap buffer overflow in tftp_receive_packet() function" + VendorSeverity: + amazon: 1.0 + arch-linux: 3.0 + nvd: 3.0 + oracle-oval: 2.0 + photon: 3.0 + redhat: 1.0 + ubuntu: 2.0 + - key: CVE-2019-5481 + value: + CVSS: + nvd: + V2Score: 7.5 + V2Vector: AV:N/AC:L/Au:N/C:P/I:P/A:P + V3Score: 9.8 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + redhat: + V3Score: 5.7 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H + CweIDs: + - CWE-415 + Description: Double-free vulnerability in the FTP-kerberos code in cURL 7.52.0 to 7.65.3. + LastModifiedDate: 2020-10-20T22:15:00Z + PublishedDate: 2019-09-16T19:15:00Z + References: + - http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00048.html + - http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00055.html + - https://access.redhat.com/security/cve/CVE-2019-5481 + - https://curl.haxx.se/docs/CVE-2019-5481.html + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5481 + - https://linux.oracle.com/cve/CVE-2019-5481.html + - https://linux.oracle.com/errata/ELSA-2020-1792.html + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6CI4QQ2RSZX4VCFM76SIWGKY6BY7UWIC/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RGDVKSLY5JUNJRLYRUA6CXGQ2LM63XC3/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UA7KDM2WPM5CJDDGOEGFV6SSGD2J7RNT/ + - https://seclists.org/bugtraq/2020/Feb/36 + - https://security.gentoo.org/glsa/202003-29 + - https://security.netapp.com/advisory/ntap-20191004-0003/ + - https://ubuntu.com/security/notices/USN-4129-1 + - https://www.debian.org/security/2020/dsa-4633 + - https://www.oracle.com/security-alerts/cpuapr2020.html + - https://www.oracle.com/security-alerts/cpujan2020.html + - https://www.oracle.com/security-alerts/cpuoct2020.html + Severity: CRITICAL + Title: "curl: double free due to subsequent call of realloc()" + VendorSeverity: + amazon: 2.0 + arch-linux: 2.0 + nvd: 4.0 + oracle-oval: 2.0 + photon: 4.0 + redhat: 2.0 + ubuntu: 2.0 + - key: CVE-2020-28724 + value: + CVSS: + nvd: + V2Score: 5.8 + V2Vector: AV:N/AC:M/Au:N/C:P/I:P/A:N + V3Score: 6.1 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N + redhat: + V3Score: 5.4 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N + CweIDs: + - CWE-601 + Description: Open redirect vulnerability in werkzeug before 0.11.6 via a double slash in the URL. + LastModifiedDate: 2020-12-01T16:05:00Z + PublishedDate: 2020-11-18T15:15:00Z + References: + - https://access.redhat.com/security/cve/CVE-2020-28724 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-28724 + - https://github.com/advisories/GHSA-3p3h-qghp-hvh2 + - https://github.com/pallets/flask/issues/1639 + - https://github.com/pallets/werkzeug/issues/822 + - https://github.com/pallets/werkzeug/pull/890/files + - https://nvd.nist.gov/vuln/detail/CVE-2020-28724 + - https://ubuntu.com/security/notices/USN-4655-1 + Severity: MEDIUM + Title: "python-werkzeug: open redirect via double slash in the URL" + VendorSeverity: + ghsa: 2.0 + nvd: 2.0 + redhat: 2.0 + ubuntu: 2.0 + - key: CVE-2020-29573 + value: + CVSS: + nvd: + V2Score: 5.0 + V2Vector: AV:N/AC:L/Au:N/C:N/I:N/A:P + 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: 7.5 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H + CweIDs: + - CWE-787 + Description: "sysdeps/i386/ldbl2mpn.c in the GNU C Library (aka glibc or libc6) before 2.23 on x86 targets has a stack-based buffer overflow if the input to any of the printf family of functions is an 80-bit long double with a non-canonical bit pattern, as seen when passing a \\x00\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x04 value to sprintf. NOTE: the issue does not affect glibc by default in 2016 or later (i.e., 2.23 or later) because of commits made in 2015 for inlining of C99 math functions through use of GCC built-ins. In other words, the reference to 2.23 is intentional despite the mention of \"Fixed for glibc 2.33\" in the 26649 reference." + LastModifiedDate: 2021-01-26T18:15:00Z + PublishedDate: 2020-12-06T00:15:00Z + References: + - https://access.redhat.com/security/cve/CVE-2020-29573 + - https://linux.oracle.com/cve/CVE-2020-29573.html + - https://linux.oracle.com/errata/ELSA-2021-0348.html + - https://security.gentoo.org/glsa/202101-20 + - https://security.netapp.com/advisory/ntap-20210122-0004/ + - https://sourceware.org/bugzilla/show_bug.cgi?id=26649 + - https://sourceware.org/pipermail/libc-alpha/2020-September/117779.html + Severity: HIGH + Title: "glibc: stack-based buffer overflow if the input to any of the printf family of functions is an 80-bit long double with a non-canonical bit pattern" + VendorSeverity: + amazon: 2.0 + arch-linux: 2.0 + nvd: 3.0 + oracle-oval: 2.0 + photon: 3.0 + redhat: 2.0 + - key: CVE-2020-8165 + value: + CVSS: + nvd: + V2Score: 7.5 + V2Vector: AV:N/AC:L/Au:N/C:P/I:P/A:P + V3Score: 9.8 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + redhat: + V3Score: 9.8 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + CweIDs: + - CWE-502 + Description: A deserialization of untrusted data vulnerability exists in rails < 5.2.4.3, rails < 6.0.3.1 that can allow an attacker to unmarshal user-provided objects in MemCacheStore and RedisCacheStore potentially resulting in an RCE. + LastModifiedDate: 2020-10-17T12:15:00Z + PublishedDate: 2020-06-19T18:15:00Z + References: + - http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00031.html + - http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00034.html + - https://access.redhat.com/security/cve/CVE-2020-8165 + - https://github.com/advisories/GHSA-2p68-f74v-9wc6 + - https://github.com/rubysec/ruby-advisory-db/blob/master/gems/activesupport/CVE-2020-8165.yml + - "https://groups.google.com/forum/#!msg/rubyonrails-security/bv6fW4S0Y1c/KnkEqM7AAQAJ" + - "https://groups.google.com/forum/#!topic/rubyonrails-security/bv6fW4S0Y1c" + - https://groups.google.com/g/rubyonrails-security/c/bv6fW4S0Y1c + - https://hackerone.com/reports/413388 + - https://lists.debian.org/debian-lts-announce/2020/06/msg00022.html + - https://lists.debian.org/debian-lts-announce/2020/07/msg00013.html + - https://nvd.nist.gov/vuln/detail/CVE-2020-8165 + - https://weblog.rubyonrails.org/2020/5/18/Rails-5-2-4-3-and-6-0-3-1-have-been-released/ + - https://www.debian.org/security/2020/dsa-4766 + Severity: CRITICAL + Title: "rubygem-activesupport: potentially unintended unmarshalling of user-provided objects in MemCacheStore and RedisCacheStore" + VendorSeverity: + ghsa: 3.0 + nvd: 4.0 + redhat: 3.0 + - key: CVE-2020-9548 + value: + CVSS: + nvd: + V2Score: 6.8 + V2Vector: AV:N/AC:M/Au:N/C:P/I:P/A:P + V3Score: 9.8 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + redhat: + V3Score: 8.1 + V3Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H + CweIDs: + - CWE-502 + Description: FasterXML jackson-databind 2.x before 2.9.10.4 mishandles the interaction between serialization gadgets and typing, related to br.com.anteros.dbcp.AnterosDBCPConfig (aka anteros-core). + LastModifiedDate: 2021-12-02T21:23:00Z + PublishedDate: 2020-03-02T04:15:00Z + References: + - https://access.redhat.com/security/cve/CVE-2020-9548 + - https://github.com/FasterXML/jackson-databind/issues/2634 + - https://github.com/advisories/GHSA-p43x-xfjf-5jhr + - https://lists.apache.org/thread.html/r35d30db00440ef63b791c4b7f7acb036e14d4a23afa2a249cb66c0fd@%3Cissues.zookeeper.apache.org%3E + - https://lists.apache.org/thread.html/r9464a40d25c3ba1a55622db72f113eb494a889656962d098c70c5bb1@%3Cdev.zookeeper.apache.org%3E + - https://lists.apache.org/thread.html/r98c9b6e4c9e17792e2cd1ec3e4aa20b61a791939046d3f10888176bb@%3Cissues.zookeeper.apache.org%3E + - https://lists.apache.org/thread.html/rb6fecb5e96a6d61e175ff49f33f2713798dd05cf03067c169d195596@%3Cissues.zookeeper.apache.org%3E + - https://lists.apache.org/thread.html/rd5a4457be4623038c3989294429bc063eec433a2e55995d81591e2ca@%3Cissues.zookeeper.apache.org%3E + - https://lists.apache.org/thread.html/rdd49ab9565bec436a896bc00c4b9fc9dce1598e106c318524fbdfec6@%3Cissues.zookeeper.apache.org%3E + - https://lists.apache.org/thread.html/rdd4df698d5d8e635144d2994922bf0842e933809eae259521f3b5097@%3Cissues.zookeeper.apache.org%3E + - https://lists.apache.org/thread.html/rf1bbc0ea4a9f014cf94df9a12a6477d24a27f52741dbc87f2fd52ff2@%3Cissues.geode.apache.org%3E + - https://lists.debian.org/debian-lts-announce/2020/03/msg00008.html + - https://medium.com/@cowtowncoder/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062 + - https://nvd.nist.gov/vuln/detail/CVE-2020-9548 + - https://security.netapp.com/advisory/ntap-20200904-0006/ + - https://www.oracle.com/security-alerts/cpujan2021.html + - https://www.oracle.com/security-alerts/cpujul2020.html + - https://www.oracle.com/security-alerts/cpuoct2020.html + - https://www.oracle.com/security-alerts/cpuoct2021.html + Severity: CRITICAL + Title: "jackson-databind: Serialization gadgets in anteros-core" + VendorSeverity: + ghsa: 4.0 + nvd: 4.0 + redhat: 3.0 + - key: CVE-2021-20190 + value: + CVSS: + nvd: + V2Score: 8.3 + V2Vector: AV:N/AC:M/Au:N/C:P/I:P/A:C + V3Score: 8.1 + V3Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H + redhat: + V3Score: 8.1 + V3Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H + CweIDs: + - CWE-502 + Description: A flaw was found in jackson-databind before 2.9.10.7. FasterXML mishandles the interaction between serialization gadgets and typing. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability. + LastModifiedDate: 2021-07-20T23:15:00Z + PublishedDate: 2021-01-19T17:15:00Z + References: + - https://access.redhat.com/security/cve/CVE-2021-20190 + - https://bugzilla.redhat.com/show_bug.cgi?id=1916633 + - https://github.com/FasterXML/jackson-databind/commit/7dbf51bf78d157098074a20bd9da39bd48c18e4a + - https://github.com/FasterXML/jackson-databind/issues/2854 + - https://github.com/advisories/GHSA-5949-rw7g-wx7w + - https://lists.apache.org/thread.html/r380e9257bacb8551ee6fcf2c59890ae9477b2c78e553fa9ea08e9d9a@%3Ccommits.nifi.apache.org%3E + - https://lists.debian.org/debian-lts-announce/2021/04/msg00025.html + - https://nvd.nist.gov/vuln/detail/CVE-2021-20190 + - https://security.netapp.com/advisory/ntap-20210219-0008/ + Severity: HIGH + Title: "jackson-databind: mishandles the interaction between serialization gadgets and typing, related to javax.swing" + VendorSeverity: + ghsa: 3.0 + nvd: 3.0 + redhat: 3.0 + - key: CVE-2023-2431 + value: + Title: "Bypass of seccomp profile enforcement " + Description: "A security issue was discovered in Kubelet that allows pods to bypass the seccomp profile enforcement..." + Severity: LOW + VendorSeverity: + k8s: 1 + CVSS: + k8s: + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N" + V3Score: 3.4 + References: + - https://github.com/kubernetes/kubernetes/issues/118690 + - https://www.cve.org/cverecord?id=CVE-2023-2431 + - key: CVE-2021-3712 + value: + CVSS: + nvd: + V2Score: 5.8 + V2Vector: AV:N/AC:M/Au:N/C:P/I:N/A:P + V3Score: 7.4 + V3Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H + redhat: + V3Score: 7.4 + V3Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H + CweIDs: + - CWE-125 + Description: ASN.1 strings are represented internally within OpenSSL as an ASN1_STRING structure which contains a buffer holding the string data and a field holding the buffer length. This contrasts with normal C strings which are represented as a buffer for the string data which is terminated with a NUL (0) byte. Although not a strict requirement, ASN.1 strings that are parsed using OpenSSL's own "d2i" functions (and other similar parsing functions) as well as any string whose value has been set with the ASN1_STRING_set() function will additionally NUL terminate the byte array in the ASN1_STRING structure. However, it is possible for applications to directly construct valid ASN1_STRING structures which do not NUL terminate the byte array by directly setting the "data" and "length" fields in the ASN1_STRING array. This can also happen by using the ASN1_STRING_set0() function. Numerous OpenSSL functions that print ASN.1 data have been found to assume that the ASN1_STRING byte array will be NUL terminated, even though this is not guaranteed for strings that have been directly constructed. Where an application requests an ASN.1 structure to be printed, and where that ASN.1 structure contains ASN1_STRINGs that have been directly constructed by the application without NUL terminating the "data" field, then a read buffer overrun can occur. The same thing can also occur during name constraints processing of certificates (for example if a certificate has been directly constructed by the application instead of loading it via the OpenSSL parsing functions, and the certificate contains non NUL terminated ASN1_STRING structures). It can also occur in the X509_get1_email(), X509_REQ_get1_email() and X509_get1_ocsp() functions. If a malicious actor can cause an application to directly construct an ASN1_STRING and then process it through one of the affected OpenSSL functions then this issue could be hit. This might result in a crash (causing a Denial of Service attack). It could also result in the disclosure of private memory contents (such as private keys, or sensitive plaintext). Fixed in OpenSSL 1.1.1l (Affected 1.1.1-1.1.1k). Fixed in OpenSSL 1.0.2za (Affected 1.0.2-1.0.2y). + LastModifiedDate: 2022-01-06T09:15:00Z + PublishedDate: 2021-08-24T15:15:00Z + References: + - http://www.openwall.com/lists/oss-security/2021/08/26/2 + - https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2021-3712.json + - https://access.redhat.com/security/cve/CVE-2021-3712 + - https://crates.io/crates/openssl-src + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3712 + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=94d23fcff9b2a7a8368dfe52214d5c2569882c11 + - https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=ccb0a11145ee72b042d10593a64eaf9e8a55ec12 + - https://kc.mcafee.com/corporate/index?page=content&id=SB10366 + - https://linux.oracle.com/cve/CVE-2021-3712.html + - https://linux.oracle.com/errata/ELSA-2022-9023.html + - https://lists.apache.org/thread.html/r18995de860f0e63635f3008fd2a6aca82394249476d21691e7c59c9e@%3Cdev.tomcat.apache.org%3E + - https://lists.apache.org/thread.html/rad5d9f83f0d11fb3f8bb148d179b8a9ad7c6a17f18d70e5805a713d1@%3Cdev.tomcat.apache.org%3E + - https://lists.debian.org/debian-lts-announce/2021/09/msg00014.html + - https://lists.debian.org/debian-lts-announce/2021/09/msg00021.html + - https://nvd.nist.gov/vuln/detail/CVE-2021-3712 + - https://rustsec.org/advisories/RUSTSEC-2021-0098.html + - https://security.netapp.com/advisory/ntap-20210827-0010/ + - https://ubuntu.com/security/notices/USN-5051-1 + - https://ubuntu.com/security/notices/USN-5051-2 + - https://ubuntu.com/security/notices/USN-5051-3 + - https://ubuntu.com/security/notices/USN-5051-4 (regression only in trusty/esm) + - https://ubuntu.com/security/notices/USN-5088-1 + - https://www.debian.org/security/2021/dsa-4963 + - https://www.openssl.org/news/secadv/20210824.txt + - https://www.oracle.com/security-alerts/cpuoct2021.html + - https://www.tenable.com/security/tns-2021-16 + - https://www.tenable.com/security/tns-2022-02 + Severity: HIGH + Title: "openssl: Read buffer overruns processing ASN.1 strings" + VendorSeverity: + alma: 2.0 + amazon: 2.0 + arch-linux: 3.0 + cbl-mariner: 3.0 + nvd: 3.0 + oracle-oval: 2.0 + photon: 3.0 + redhat: 2.0 + rocky: 2.0 + ubuntu: 2.0 + - key: CVE-2021-38193 + value: + CVSS: + nvd: + V2Score: 4.3 + V2Vector: AV:N/AC:M/Au:N/C:N/I:P/A:N + V3Score: 6.1 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N + CweIDs: + - CWE-79 + Description: An issue was discovered in the ammonia crate before 3.1.0 for Rust. XSS can occur because the parsing differences for HTML, SVG, and MathML are mishandled, a similar issue to CVE-2020-26870. + LastModifiedDate: 2021-08-16T16:37:00Z + PublishedDate: 2021-08-08T06:15:00Z + References: + - https://crates.io/crates/ammonia + - https://github.com/rust-ammonia/ammonia/pull/142 + - https://raw.githubusercontent.com/rustsec/advisory-db/main/crates/ammonia/RUSTSEC-2021-0074.md + - https://rustsec.org/advisories/RUSTSEC-2021-0074.html + Severity: MEDIUM + Title: Incorrect handling of embedded SVG and MathML leads to mutation XSS + VendorSeverity: + nvd: 2.0 + - key: CVE-2022-0158 + value: + CVSS: + nvd: + V2Score: 4.3 + V2Vector: AV:N/AC:M/Au:N/C:P/I:N/A:N + V3Score: 3.3 + V3Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N + redhat: + V3Score: 3.3 + V3Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N + CweIDs: + - CWE-122 + Description: vim is vulnerable to Heap-based Buffer Overflow + LastModifiedDate: 2022-01-15T16:15:00Z + PublishedDate: 2022-01-10T16:15:00Z + References: + - http://www.openwall.com/lists/oss-security/2022/01/15/1 + - https://access.redhat.com/security/cve/CVE-2022-0158 + - https://github.com/vim/vim/commit/5f25c3855071bd7e26255c68bf458b1b5cf92f39 + - https://huntr.dev/bounties/ac5d7005-07c6-4a0a-b251-ba9cdbf6738b + - https://huntr.dev/bounties/ac5d7005-07c6-4a0a-b251-ba9cdbf6738b/ + - https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HD5S2FC2HF22A7XQXK2XXIR46EARVWIM/ + - https://nvd.nist.gov/vuln/detail/CVE-2022-0158 + Severity: LOW + Title: "vim: heap-based read buffer overflow in compile_get_env()" + VendorSeverity: + cbl-mariner: 1.0 + nvd: 1.0 + redhat: 1.0 + - key: CVE-2022-0261 + value: + CweIDs: + - CWE-122 + Description: Heap-based Buffer Overflow in GitHub repository vim/vim prior to 8.2. + LastModifiedDate: 2022-01-18T16:15:00Z + PublishedDate: 2022-01-18T16:15:00Z + References: + - https://github.com/vim/vim/commit/9f8c304c8a390ade133bac29963dc8e56ab14cbc + - https://huntr.dev/bounties/fa795954-8775-4f23-98c6-d4d4d3fe8a82 + - https://nvd.nist.gov/vuln/detail/CVE-2022-0261 + Severity: HIGH + Title: CVE-2022-0261 affecting package vim 8.2.4081 + VendorSeverity: + cbl-mariner: 3.0 + - key: openSUSE-SU-2020:0062-1 + value: + Description: "This update for openssl-1_1 fixes the following issues:\n\nSecurity issue fixed:\n\n- CVE-2019-1551: Fixed an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli (bsc#1158809). \n\nVarious FIPS related improvements were done:\n\n- FIPS: Backport SSH KDF to openssl (jsc#SLE-8789, bsc#1157775).\n- Port FIPS patches from SLE-12 (bsc#1158101).\n- Use SHA-2 in the RSA pairwise consistency check (bsc#1155346).\n\nThis update was imported from the SUSE:SLE-15-SP1:Update update project." + References: + - https://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html + - https://www.suse.com/support/security/rating/ + Severity: MEDIUM + Title: Security update for openssl-1_1 + VendorSeverity: + suse-cvrf: 2.0 + - key: CVE-2022-24765 + value: + Title: "Git for Windows is a fork of Git containing Windows-specific patches. ..." + Description: "Git for Windows is a fork of Git containing Windows-specific patches." + CweIDs: + - CWE-427 + References: + - http://www.openwall.com/lists/oss-security/2022/04/12/7 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24765 + - https://git-scm.com/book/en/v2/Appendix-A%3A-Git-in-Other-Environments-Git-in-Bash + - https://git-scm.com/docs/git#Documentation/git.txt-codeGITCEILINGDIRECTORIEScode + - https://github.com/git-for-windows/git/security/advisories/GHSA-vw2c-22j4-2fh2 + - https://ubuntu.com/security/notices/USN-5376-1 + Severity: MEDIUM + VendorSeverity: + ubuntu: 2 + LastModifiedDate: 2022-04-12T21:15:00Z + PublishedDate: 2022-04-12T18:15:00Z + - key: GMS-2022-20 + value: + Title: OCI Manifest Type Confusion Issue + Description: "### Impact\n\nSystems that rely on digest equivalence for image attestations may be vulnerable to type confusion." + Severity: UNKNOWN + References: + - https://github.com/advisories/GHSA-qq97-vm5h-rrhg + - https://github.com/distribution/distribution/commit/b59a6f827947f9e0e67df0cfb571046de4733586 + - https://github.com/distribution/distribution/security/advisories/GHSA-qq97-vm5h-rrhg + - https://github.com/opencontainers/image-spec/pull/411 + - key: CVE-2022-23628 + value: + Title: Incorrect Calculation + Description: "OPA is an open source, general-purpose policy engine. Under certain conditions, pretty-printing an abstract syntax tree (AST) that contains synthetic nodes could change the logic of some statements by reordering array literals. Example of policies impacted are those that parse and compare web paths. **All of these** three conditions have to be met to create an adverse effect: 1. An AST of Rego had to be **created programmatically** such that it ends up containing terms without a location (such as wildcard variables). 2. The AST had to be **pretty-printed** using the `github.com/open-policy-agent/opa/format` package. 3. The result of the pretty-printing had to be **parsed and evaluated again** via an OPA instance using the bundles, or the Golang packages. If any of these three conditions are not met, you are not affected. Notably, all three would be true if using **optimized bundles**, i.e. bundles created with `opa build -O=1` or higher. In that case, the optimizer would fulfil condition (1.), the result of that would be pretty-printed when writing the bundle to disk, fulfilling (2.). When the bundle was then used, we'd satisfy (3.). As a workaround users may disable optimization when creating bundles." + Severity: MEDIUM + CweIDs: + - CWE-682 + VendorSeverity: + nvd: 2 + CVSS: + nvd: + V2Vector: AV:N/AC:M/Au:N/C:N/I:P/A:N + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N + V2Score: 4.3 + V3Score: 5.3 + References: + - https://github.com/advisories/GHSA-hcw3-j74m-qc58 + - https://github.com/open-policy-agent/opa/commit/932e4ffc37a590ace79e9b75ca4340288c220239 + - https://github.com/open-policy-agent/opa/commit/bfd984ddf93ef2c4963a08d4fdadae0bcf1a3717 + - https://github.com/open-policy-agent/opa/pull/3851 + - https://github.com/open-policy-agent/opa/security/advisories/GHSA-hcw3-j74m-qc58 + - https://nvd.nist.gov/vuln/detail/CVE-2022-23628 + PublishedDate: '2022-02-09T22:15:00Z' + LastModifiedDate: '2022-02-17T02:37:00Z' + - key: CVE-2021-38561 + value: + Description: "Due to improper index calculation, an incorrectly formatted language tag can cause Parse\nto panic via an out of bounds read. If Parse is used to process untrusted user inputs,\nthis may be used as a vector for a denial of service attack.\n" + Severity: UNKNOWN + References: + - https://go-review.googlesource.com/c/text/+/340830 + - https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f + - https://pkg.go.dev/vuln/GO-2021-0113 + - key: GHSA-5crp-9r3c-p9vr + value: + Title: "Improper Handling of Exceptional Conditions in Newtonsoft.Json" + Description: "Newtonsoft.Json prior to version 13.0.1 is vulnerable to Insecure Defaults due to improper handling of expressions with high nesting level that lead to StackOverFlow exception or high CPU and RAM usage." + Severity: HIGH + VendorSeverity: + ghsa: 3 + CweIDs: + - CWE-755 + CVSS: + ghsa: + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + V3Score: 7.5 + References: + - https://alephsecurity.com/2018/10/22/StackOverflowException/ + - https://alephsecurity.com/vulns/aleph-2018004 + PublishedDate: "2022-06-22T15:08:47Z" + LastModifiedDate: "2022-06-27T18:37:23Z" + - key: CVE-2022-42975 + value: + Title: "Phoenix before 1.6.14 mishandles check_origin wildcarding" + Description: "socket/transport.ex in Phoenix before 1.6.14 mishandles check_origin wildcarding. NOTE: LiveView applications are unaffected by default because of the presence of a LiveView CSRF token." + Severity: HIGH + VendorSeverity: + ghsa: 3 + CVSS: + ghsa: + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N" + V3Score: 7.5 + References: + - https://nvd.nist.gov/vuln/detail/CVE-2022-42975 + - https://github.com/phoenixframework/phoenix/commit/6e7185b33a59e0b1d1c0b4223adf340a73e963ae + - https://hexdocs.pm/phoenix/1.6.14/changelog.html#1-6-14-2022-10-10 + - https://github.com/advisories/GHSA-p8f7-22gq-m7j9 + PublishedDate: "2022-10-17T12:00:27Z" + LastModifiedDate: "2022-10-18T18:01:44Z" + - key: CVE-2020-35669 + value: + Title: "http before 0.13.3 vulnerable to header injection" + Description: "An issue was discovered in the http package before 0.13.3 for Dart. If the attacker controls the HTTP method and the app is using Request directly, it's possible to achieve CRLF injection in an HTTP request via HTTP header injection. This issue has been addressed in commit abb2bb182 by validating request methods." + Severity: MEDIUM + VendorSeverity: + ghsa: 2 + CweIDs: + - CWE-74 + CVSS: + ghsa: + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + V3Score: 6.1 + References: + - https://nvd.nist.gov/vuln/detail/CVE-2020-35669 + - https://github.com/dart-lang/http/issues/511 + - https://github.com/dart-lang/http/blob/master/CHANGELOG.md#0133 + - https://github.com/dart-lang/http/pull/512 + - https://github.com/dart-lang/http/commit/abb2bb182fbd7f03aafd1f889b902d7b3bdb8769 + - https://pub.dev/packages/http/changelog#0133 + - https://github.com/advisories/GHSA-4rgh-jx4f-qfcq + PublishedDate: "2022-05-24T17:37:16Z" + LastModifiedDate: "2022-10-06T20:26:08Z" + - key: CVE-2022-3215 + value: + Title: "SwiftNIO vulnerable to Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')" + Description: "`NIOHTTP1` and projects using it for generating HTTP responses, including SwiftNIO, can be subject to a HTTP Response Injection attack..." + Severity: MEDIUM + VendorSeverity: + ghsa: 2 + CVSS: + ghsa: + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N" + V3Score: 5.3 + References: + - https://github.com/apple/swift-nio/security/advisories/GHSA-7fj7-39wj-c64f + - https://nvd.nist.gov/vuln/detail/CVE-2022-3215 + - https://github.com/apple/swift-nio/commit/a16e2f54a25b2af217044e5168997009a505930f + - https://github.com/advisories/GHSA-7fj7-39wj-c64f + PublishedDate: "2023-06-07T16:01:53Z" + LastModifiedDate: "2023-06-19T16:45:07Z" + - key: CVE-2022-24775 + value: + Title: "Improper Input Validation in guzzlehttp/psr7" + Description: "### Impact\nIn proper header parsing. An attacker could sneak in a new line character and pass untrusted values. \n\n### Patches\nThe issue is patched in 1.8.4 and 2.1.1.\n\n### Workarounds\nThere are no known workarounds.\n" + Severity: HIGH + VendorSeverity: + ghsa: 3 + CweIDs: + - CWE-20 + CVSS: + ghsa: + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N" + V3Score: 7.5 + References: + - https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96 + - https://nvd.nist.gov/vuln/detail/CVE-2022-24775 + PublishedDate: "2022-03-25T19:26:33Z" + LastModifiedDate: "2022-06-14T20:02:29Z" + - key: CVE-2022-22965 + value: + Title: "spring-framework: RCE via Data Binding on JDK 9+" + Description: "A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it." + Severity: CRITICAL + CweIDs: + - CWE-94 + VendorSeverity: + nvd: 4 + ghsa: 4 + redhat: 3 + CVSS: + ghsa: + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + V3Score: 9.8 + nvd: + V2Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P" + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + V2Score: 7.5 + V3Score: 9.8 + redhat: + V3Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H" + V3Score: 8.1 + References: + - "https://github.com/advisories/GHSA-36p3-wjmg-h94x", + PublishedDate: "2022-04-01T23:15:00Z" + LastModifiedDate: "2022-05-19T14:21:00Z" + - key: CVE-2020-14155 + value: + Title: "pcre: Integer overflow when parsing callout numeric arguments" + Description: "libpcre in PCRE before 8.44 allows an integer overflow via a large number after a (?C substring." + Severity: MEDIUM + CweIDs: + - CWE-190 + VendorSeverity: + alma: 1 + nvd: 2 + CVSS: + nvd: + V2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P" + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" + V2Score: 5 + V3Score: 5.3 + 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: + - "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" diff --git a/integration/testdata/fixtures/k8s/test_nginx.yaml b/integration/testdata/fixtures/k8s/test_nginx.yaml new file mode 100644 index 000000000000..c295e5f91af2 --- /dev/null +++ b/integration/testdata/fixtures/k8s/test_nginx.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/integration/testdata/fixtures/repo/cocoapods/Podfile.lock b/integration/testdata/fixtures/repo/cocoapods/Podfile.lock new file mode 100644 index 000000000000..905fe5fc5119 --- /dev/null +++ b/integration/testdata/fixtures/repo/cocoapods/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - _NIODataStructures (2.41.0) + +DEPENDENCIES: + - _NIODataStructures (= 2.41.0) + +SPEC REPOS: + trunk: + - _NIODataStructures + +SPEC CHECKSUMS: + _NIODataStructures: 3d45d8e70a1d17a15b1dc59d102c63dbc0525ffd + +PODFILE CHECKSUM: 2acff18c7f9246879b6a1a2d04e5decbc9410ef4 + +COCOAPODS: 1.12.1 \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/composer/composer.json b/integration/testdata/fixtures/repo/composer/composer.json new file mode 100644 index 000000000000..99e5b5f9aefd --- /dev/null +++ b/integration/testdata/fixtures/repo/composer/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "guzzlehttp/guzzle": "7.4.4" + } +} \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/composer/composer.lock b/integration/testdata/fixtures/repo/composer/composer.lock new file mode 100644 index 000000000000..16bd667e36b5 --- /dev/null +++ b/integration/testdata/fixtures/repo/composer/composer.lock @@ -0,0 +1,256 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "31bb67df1cc2bbbb0cd07dd1a0f516df", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "7.4.4", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "e3ff079b22820c2029d4c2a87796b6a0b8716ad8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/e3ff079b22820c2029d4c2a87796b6a0b8716ad8", + "reference": "e3ff079b22820c2029d4c2a87796b6a0b8716ad8", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/psr7": "^1.8.2 || ^2.1", + "php": "^7.2.5 || ^8.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.4.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-06-09T21:39:15+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.8.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", + "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-03-09T13:19:02+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/integration/testdata/fixtures/repo/conan/conan.lock b/integration/testdata/fixtures/repo/conan/conan.lock new file mode 100644 index 000000000000..3c066117ed59 --- /dev/null +++ b/integration/testdata/fixtures/repo/conan/conan.lock @@ -0,0 +1,77 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "options": "bzip2:build_executable=True\nbzip2:fPIC=True\nbzip2:shared=False\nexpat:char_type=char\nexpat:fPIC=True\nexpat:shared=False\nopenssl:386=False\nopenssl:enable_weak_ssl_ciphers=False\nopenssl:fPIC=True\nopenssl:no_aria=False\nopenssl:no_asm=False\nopenssl:no_async=False\nopenssl:no_bf=False\nopenssl:no_blake2=False\nopenssl:no_camellia=False\nopenssl:no_cast=False\nopenssl:no_chacha=False\nopenssl:no_cms=False\nopenssl:no_comp=False\nopenssl:no_ct=False\nopenssl:no_deprecated=False\nopenssl:no_des=False\nopenssl:no_dgram=False\nopenssl:no_dh=False\nopenssl:no_dsa=False\nopenssl:no_dso=False\nopenssl:no_ec=False\nopenssl:no_ecdh=False\nopenssl:no_ecdsa=False\nopenssl:no_engine=False\nopenssl:no_filenames=False\nopenssl:no_gost=False\nopenssl:no_hmac=False\nopenssl:no_idea=False\nopenssl:no_md4=False\nopenssl:no_md5=False\nopenssl:no_mdc2=False\nopenssl:no_ocsp=False\nopenssl:no_pinshared=False\nopenssl:no_rc2=False\nopenssl:no_rfc3779=False\nopenssl:no_rmd160=False\nopenssl:no_rsa=False\nopenssl:no_seed=False\nopenssl:no_sha=False\nopenssl:no_sm2=False\nopenssl:no_sm3=False\nopenssl:no_sm4=False\nopenssl:no_sock=False\nopenssl:no_srp=False\nopenssl:no_srtp=False\nopenssl:no_sse2=False\nopenssl:no_ssl=False\nopenssl:no_ssl3=False\nopenssl:no_stdio=False\nopenssl:no_tests=False\nopenssl:no_threads=False\nopenssl:no_tls1=False\nopenssl:no_ts=False\nopenssl:no_whirlpool=False\nopenssl:openssldir=None\nopenssl:shared=False\npcre:build_pcre_16=True\npcre:build_pcre_32=True\npcre:build_pcre_8=True\npcre:build_pcrecpp=False\npcre:build_pcregrep=True\npcre:fPIC=True\npcre:shared=False\npcre:with_bzip2=True\npcre:with_jit=False\npcre:with_stack_for_recursion=True\npcre:with_unicode_properties=True\npcre:with_utf=True\npcre:with_zlib=True\npoco:enable_active_record=deprecated\npoco:enable_apacheconnector=False\npoco:enable_cppparser=False\npoco:enable_crypto=True\npoco:enable_data=True\npoco:enable_data_odbc=False\npoco:enable_data_sqlite=True\npoco:enable_encodings=True\npoco:enable_fork=True\npoco:enable_json=True\npoco:enable_mongodb=True\npoco:enable_net=True\npoco:enable_netssl=True\npoco:enable_pagecompiler=False\npoco:enable_pagecompiler_file2page=False\npoco:enable_pdf=False\npoco:enable_pocodoc=False\npoco:enable_redis=True\npoco:enable_sevenzip=False\npoco:enable_util=True\npoco:enable_xml=True\npoco:enable_zip=True\npoco:fPIC=True\npoco:shared=False\nsqlite3:build_executable=True\nsqlite3:disable_gethostuuid=False\nsqlite3:enable_column_metadata=True\nsqlite3:enable_dbpage_vtab=False\nsqlite3:enable_dbstat_vtab=False\nsqlite3:enable_default_secure_delete=False\nsqlite3:enable_default_vfs=True\nsqlite3:enable_explain_comments=False\nsqlite3:enable_fts3=False\nsqlite3:enable_fts3_parenthesis=False\nsqlite3:enable_fts4=False\nsqlite3:enable_fts5=False\nsqlite3:enable_json1=False\nsqlite3:enable_math_functions=True\nsqlite3:enable_preupdate_hook=False\nsqlite3:enable_rtree=True\nsqlite3:enable_soundex=False\nsqlite3:enable_unlock_notify=True\nsqlite3:fPIC=True\nsqlite3:max_blob_size=None\nsqlite3:max_column=None\nsqlite3:max_variable_number=None\nsqlite3:omit_deprecated=False\nsqlite3:omit_load_extension=False\nsqlite3:shared=False\nsqlite3:threadsafe=1\nsqlite3:use_alloca=False\nzlib:fPIC=True\nzlib:shared=False", + "requires": [ + "1" + ], + "path": "../conanfile.txt", + "context": "host" + }, + "1": { + "ref": "poco/1.9.4", + "options": "enable_active_record=deprecated\nenable_apacheconnector=False\nenable_cppparser=False\nenable_crypto=True\nenable_data=True\nenable_data_odbc=False\nenable_data_sqlite=True\nenable_encodings=True\nenable_fork=True\nenable_json=True\nenable_mongodb=True\nenable_net=True\nenable_netssl=True\nenable_pagecompiler=False\nenable_pagecompiler_file2page=False\nenable_pdf=False\nenable_pocodoc=False\nenable_redis=True\nenable_sevenzip=False\nenable_util=True\nenable_xml=True\nenable_zip=True\nfPIC=True\nshared=False\nbzip2:build_executable=True\nbzip2:fPIC=True\nbzip2:shared=False\nexpat:char_type=char\nexpat:fPIC=True\nexpat:shared=False\nopenssl:386=False\nopenssl:enable_weak_ssl_ciphers=False\nopenssl:fPIC=True\nopenssl:no_aria=False\nopenssl:no_asm=False\nopenssl:no_async=False\nopenssl:no_bf=False\nopenssl:no_blake2=False\nopenssl:no_camellia=False\nopenssl:no_cast=False\nopenssl:no_chacha=False\nopenssl:no_cms=False\nopenssl:no_comp=False\nopenssl:no_ct=False\nopenssl:no_deprecated=False\nopenssl:no_des=False\nopenssl:no_dgram=False\nopenssl:no_dh=False\nopenssl:no_dsa=False\nopenssl:no_dso=False\nopenssl:no_ec=False\nopenssl:no_ecdh=False\nopenssl:no_ecdsa=False\nopenssl:no_engine=False\nopenssl:no_filenames=False\nopenssl:no_gost=False\nopenssl:no_hmac=False\nopenssl:no_idea=False\nopenssl:no_md4=False\nopenssl:no_md5=False\nopenssl:no_mdc2=False\nopenssl:no_ocsp=False\nopenssl:no_pinshared=False\nopenssl:no_rc2=False\nopenssl:no_rfc3779=False\nopenssl:no_rmd160=False\nopenssl:no_rsa=False\nopenssl:no_seed=False\nopenssl:no_sha=False\nopenssl:no_sm2=False\nopenssl:no_sm3=False\nopenssl:no_sm4=False\nopenssl:no_sock=False\nopenssl:no_srp=False\nopenssl:no_srtp=False\nopenssl:no_sse2=False\nopenssl:no_ssl=False\nopenssl:no_ssl3=False\nopenssl:no_stdio=False\nopenssl:no_tests=False\nopenssl:no_threads=False\nopenssl:no_tls1=False\nopenssl:no_ts=False\nopenssl:no_whirlpool=False\nopenssl:openssldir=None\nopenssl:shared=False\npcre:build_pcre_16=True\npcre:build_pcre_32=True\npcre:build_pcre_8=True\npcre:build_pcrecpp=False\npcre:build_pcregrep=True\npcre:fPIC=True\npcre:shared=False\npcre:with_bzip2=True\npcre:with_jit=False\npcre:with_stack_for_recursion=True\npcre:with_unicode_properties=True\npcre:with_utf=True\npcre:with_zlib=True\nsqlite3:build_executable=True\nsqlite3:disable_gethostuuid=False\nsqlite3:enable_column_metadata=True\nsqlite3:enable_dbpage_vtab=False\nsqlite3:enable_dbstat_vtab=False\nsqlite3:enable_default_secure_delete=False\nsqlite3:enable_default_vfs=True\nsqlite3:enable_explain_comments=False\nsqlite3:enable_fts3=False\nsqlite3:enable_fts3_parenthesis=False\nsqlite3:enable_fts4=False\nsqlite3:enable_fts5=False\nsqlite3:enable_json1=False\nsqlite3:enable_math_functions=True\nsqlite3:enable_preupdate_hook=False\nsqlite3:enable_rtree=True\nsqlite3:enable_soundex=False\nsqlite3:enable_unlock_notify=True\nsqlite3:fPIC=True\nsqlite3:max_blob_size=None\nsqlite3:max_column=None\nsqlite3:max_variable_number=None\nsqlite3:omit_deprecated=False\nsqlite3:omit_load_extension=False\nsqlite3:shared=False\nsqlite3:threadsafe=1\nsqlite3:use_alloca=False\nzlib:fPIC=True\nzlib:shared=False", + "package_id": "c3c2e0fbf9199382c510453d5fa86501149cf57a", + "prev": "0", + "requires": [ + "2", + "4", + "5", + "6", + "7" + ], + "context": "host" + }, + "2": { + "ref": "pcre/8.43", + "options": "build_pcre_16=True\nbuild_pcre_32=True\nbuild_pcre_8=True\nbuild_pcrecpp=False\nbuild_pcregrep=True\nfPIC=True\nshared=False\nwith_bzip2=True\nwith_jit=False\nwith_stack_for_recursion=True\nwith_unicode_properties=True\nwith_utf=True\nwith_zlib=True\nbzip2:build_executable=True\nbzip2:fPIC=True\nbzip2:shared=False\nzlib:fPIC=True\nzlib:shared=False", + "package_id": "fab187555fa87e54b51a5e8e8ff95b0f5855d00b", + "prev": "0", + "requires": [ + "3", + "4" + ], + "context": "host" + }, + "3": { + "ref": "bzip2/1.0.8", + "options": "build_executable=True\nfPIC=True\nshared=False", + "package_id": "3df6ebb8a308d309e882b21988fd9ea103560e16", + "prev": "0", + "context": "host" + }, + "4": { + "ref": "zlib/1.2.12", + "options": "fPIC=True\nshared=False", + "package_id": "76f87539fc90ff313e0b3182641a9bb558a717d2", + "prev": "0", + "context": "host" + }, + "5": { + "ref": "expat/2.4.8", + "options": "char_type=char\nfPIC=True\nshared=False", + "package_id": "b025735bb0d121754b0b4aaae6c02d3b9546c56f", + "prev": "0", + "context": "host" + }, + "6": { + "ref": "sqlite3/3.39.2", + "options": "build_executable=True\ndisable_gethostuuid=False\nenable_column_metadata=True\nenable_dbpage_vtab=False\nenable_dbstat_vtab=False\nenable_default_secure_delete=False\nenable_default_vfs=True\nenable_explain_comments=False\nenable_fts3=False\nenable_fts3_parenthesis=False\nenable_fts4=False\nenable_fts5=False\nenable_json1=False\nenable_math_functions=True\nenable_preupdate_hook=False\nenable_rtree=True\nenable_soundex=False\nenable_unlock_notify=True\nfPIC=True\nmax_blob_size=None\nmax_column=None\nmax_variable_number=None\nomit_deprecated=False\nomit_load_extension=False\nshared=False\nthreadsafe=1\nuse_alloca=False", + "package_id": "bc01b0a8d9a484b3b4226ef647e2ba7dd5b627ed", + "prev": "0", + "context": "host" + }, + "7": { + "ref": "openssl/1.1.1q", + "options": "386=False\nenable_weak_ssl_ciphers=False\nfPIC=True\nno_aria=False\nno_asm=False\nno_async=False\nno_bf=False\nno_blake2=False\nno_camellia=False\nno_cast=False\nno_chacha=False\nno_cms=False\nno_comp=False\nno_ct=False\nno_deprecated=False\nno_des=False\nno_dgram=False\nno_dh=False\nno_dsa=False\nno_dso=False\nno_ec=False\nno_ecdh=False\nno_ecdsa=False\nno_engine=False\nno_filenames=False\nno_gost=False\nno_hmac=False\nno_idea=False\nno_md4=False\nno_md5=False\nno_mdc2=False\nno_ocsp=False\nno_pinshared=False\nno_rc2=False\nno_rfc3779=False\nno_rmd160=False\nno_rsa=False\nno_seed=False\nno_sha=False\nno_sm2=False\nno_sm3=False\nno_sm4=False\nno_sock=False\nno_srp=False\nno_srtp=False\nno_sse2=False\nno_ssl=False\nno_ssl3=False\nno_stdio=False\nno_tests=False\nno_threads=False\nno_tls1=False\nno_ts=False\nno_whirlpool=False\nopenssldir=None\nshared=False", + "package_id": "76f87539fc90ff313e0b3182641a9bb558a717d2", + "prev": "0", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=5\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" +} \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/conda/miniconda3/envs/testenv/conda-meta/openssl-1.1.1q-h7f8727e_0.json b/integration/testdata/fixtures/repo/conda/miniconda3/envs/testenv/conda-meta/openssl-1.1.1q-h7f8727e_0.json new file mode 100644 index 000000000000..2cc3c34a1a81 --- /dev/null +++ b/integration/testdata/fixtures/repo/conda/miniconda3/envs/testenv/conda-meta/openssl-1.1.1q-h7f8727e_0.json @@ -0,0 +1,60 @@ +{ + "build": "h7f8727e_0", + "build_number": 0, + "channel": "https://repo.anaconda.com/pkgs/main/linux-64", + "constrains": [], + "depends": [ + "ca-certificates", + "libgcc-ng >=7.5.0" + ], + "extracted_package_dir": "/home/mmaitre/miniconda3/pkgs/openssl-1.1.1q-h7f8727e_0", + "features": "", + "files": [ + "bin/c_rehash", + "", + "ssl/openssl.cnf.dist" + ], + "fn": "openssl-1.1.1q-h7f8727e_0.conda", + "legacy_bz2_md5": "ad51928702694e3f6d25b7d4229c84e6", + "license": "OpenSSL", + "license_family": "Apache", + "link": { + "source": "/home/user/miniconda3/pkgs/openssl-1.1.1q-h7f8727e_0", + "type": 1 + }, + "md5": "2ac47797afee2ece8d339c18b095b8d8", + "name": "openssl", + "package_tarball_full_path": "/home/user/miniconda3/pkgs/openssl-1.1.1q-h7f8727e_0.conda", + "paths_data": { + "paths": [ + { + "_path": "bin/c_rehash", + "file_mode": "text", + "path_type": "hardlink", + "prefix_placeholder": "/opt/conda/conda-bld/openssl_1657551138854/_h_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_place", + "sha256": "04c9f4a5c91e20a24d14a36668b5bebe826d6087fb2337b68e33a4d485f5586f", + "sha256_in_prefix": "fc2a6b708cccb8ba90d20e54a9b07257fce009bf315a36f73d3e542dd8674921", + "size_in_bytes": 6991 + }, + { + "_path": "" + }, + { + "_path": "ssl/openssl.cnf.dist", + "path_type": "hardlink", + "sha256": "f10ba64917b4458fafc1e078c2eb9e6a7602e68fc98c2e9e6df5e1636ae27d6b", + "sha256_in_prefix": "f10ba64917b4458fafc1e078c2eb9e6a7602e68fc98c2e9e6df5e1636ae27d6b", + "size_in_bytes": 10909 + } + ], + "paths_version": 1 + }, + "requested_spec": "None", + "sha256": "49804293b87141523b2606836ece8e2aaa5202983698fd91e7c36bdb8c8a8de5", + "size": 2649280, + "subdir": "linux-64", + "timestamp": 1657551292835, + "track_features": "", + "url": "https://repo.anaconda.com/pkgs/main/linux-64/openssl-1.1.1q-h7f8727e_0.conda", + "version": "1.1.1q" +} \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/conda/miniconda3/envs/testenv/conda-meta/pip-22.2.2-py38h06a4308_0.json b/integration/testdata/fixtures/repo/conda/miniconda3/envs/testenv/conda-meta/pip-22.2.2-py38h06a4308_0.json new file mode 100644 index 000000000000..4788d70e21aa --- /dev/null +++ b/integration/testdata/fixtures/repo/conda/miniconda3/envs/testenv/conda-meta/pip-22.2.2-py38h06a4308_0.json @@ -0,0 +1,62 @@ +{ + "build": "py38h06a4308_0", + "build_number": 0, + "channel": "https://repo.anaconda.com/pkgs/main/linux-64", + "constrains": [], + "depends": [ + "python >=3.8,<3.9.0a0", + "setuptools", + "wheel" + ], + "extracted_package_dir": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0", + "features": "", + "files": [ + "bin/pip", + "", + "lib/python3.8/site-packages/pip/py.typed" + ], + "fn": "pip-22.2.2-py38h06a4308_0.conda", + "legacy_bz2_md5": "2ac9f1cfec65a1e4ef00cc0132ecd753", + "legacy_bz2_size": 2849993, + "license": "MIT", + "license_family": "MIT", + "link": { + "source": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0", + "type": 1 + }, + "md5": "ed3e0331e7c614b3148c9911e1fc15e3", + "name": "pip", + "package_tarball_full_path": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0.conda", + "paths_data": { + "paths": [ + { + "_path": "bin/pip", + "file_mode": "text", + "path_type": "hardlink", + "prefix_placeholder": "/opt/conda/conda-bld/pip_1664552683240/_h_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold", + "sha256": "1110c03ca2fb86e43e8b52a61cd9d7c722b2817fc893a613e1a947f5d7049008", + "sha256_in_prefix": "14ed5ba79d096035b83a469e863acbbaadd3ea6ca5f23f79eefa91fa857d3f19", + "size_in_bytes": 475 + }, + { + "_path": "" + }, + { + "_path": "lib/python3.8/site-packages/pip/py.typed", + "path_type": "hardlink", + "sha256": "10156fbcf4539ff788a73e5ee50ced48276b317ed0c1ded53fddd14a82256762", + "sha256_in_prefix": "10156fbcf4539ff788a73e5ee50ced48276b317ed0c1ded53fddd14a82256762", + "size_in_bytes": 286 + } + ], + "paths_version": 1 + }, + "requested_spec": "None", + "sha256": "3fb76b94cfa5ea9732bfb241b3d234ec0a5a48d16755c3c1ef3c94630f91eb26", + "size": 2417732, + "subdir": "linux-64", + "timestamp": 1664552878795, + "track_features": "", + "url": "https://repo.anaconda.com/pkgs/main/linux-64/pip-22.2.2-py38h06a4308_0.conda", + "version": "22.2.2" +} \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/custom-policy/Dockerfile b/integration/testdata/fixtures/repo/custom-policy/Dockerfile new file mode 100644 index 000000000000..e98bcba8e22b --- /dev/null +++ b/integration/testdata/fixtures/repo/custom-policy/Dockerfile @@ -0,0 +1,4 @@ +FROM alpine:3.13 + +USER nobody +HEALTHCHECK NONE diff --git a/integration/testdata/fixtures/repo/custom-policy/policy/bar.rego b/integration/testdata/fixtures/repo/custom-policy/policy/bar.rego new file mode 100644 index 000000000000..343ad688708e --- /dev/null +++ b/integration/testdata/fixtures/repo/custom-policy/policy/bar.rego @@ -0,0 +1,5 @@ +package user.bar + +deny[res] { + res := "something bad: bar" +} diff --git a/integration/testdata/fixtures/repo/custom-policy/policy/foo.rego b/integration/testdata/fixtures/repo/custom-policy/policy/foo.rego new file mode 100644 index 000000000000..a0ad07c5695d --- /dev/null +++ b/integration/testdata/fixtures/repo/custom-policy/policy/foo.rego @@ -0,0 +1,5 @@ +package user.foo + +deny[res] { + res := "something bad: foo" +} diff --git a/integration/testdata/fixtures/repo/dockerfile/Dockerfile b/integration/testdata/fixtures/repo/dockerfile/Dockerfile new file mode 100644 index 000000000000..7e9a47af8d3a --- /dev/null +++ b/integration/testdata/fixtures/repo/dockerfile/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine:3.13 + +HEALTHCHECK NONE \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/dockerfile_file_pattern/Customfile b/integration/testdata/fixtures/repo/dockerfile_file_pattern/Customfile new file mode 100644 index 000000000000..7e9a47af8d3a --- /dev/null +++ b/integration/testdata/fixtures/repo/dockerfile_file_pattern/Customfile @@ -0,0 +1,3 @@ +FROM alpine:3.13 + +HEALTHCHECK NONE \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/dotnet/datacollector.deps.json b/integration/testdata/fixtures/repo/dotnet/datacollector.deps.json new file mode 100644 index 000000000000..08699fee4a7a --- /dev/null +++ b/integration/testdata/fixtures/repo/dotnet/datacollector.deps.json @@ -0,0 +1,21 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v2.1", + "signature": "" + }, + "compilationOptions": {}, + "libraries": { + "Newtonsoft.Json/9.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==", + "path": "newtonsoft.json/9.0.1", + "hashPath": "newtonsoft.json.9.0.1.nupkg.sha512" + }, + "Microsoft.VisualStudio.TestPlatform.Common/17.2.0-release-20220408-11": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/gomod/go.mod b/integration/testdata/fixtures/repo/gomod/go.mod new file mode 100644 index 000000000000..fbd1cd9832d8 --- /dev/null +++ b/integration/testdata/fixtures/repo/gomod/go.mod @@ -0,0 +1,3 @@ +module github.com/testdata/testdata + +go 1.17 diff --git a/integration/testdata/fixtures/repo/gomod/go.sum b/integration/testdata/fixtures/repo/gomod/go.sum new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/integration/testdata/fixtures/repo/gomod/submod/go.mod b/integration/testdata/fixtures/repo/gomod/submod/go.mod new file mode 100644 index 000000000000..f1f3268e1fbc --- /dev/null +++ b/integration/testdata/fixtures/repo/gomod/submod/go.mod @@ -0,0 +1,7 @@ +module github.com/testdata/testdata/submod + +go 1.15 + +require ( + github.com/docker/distribution v2.7.1+incompatible +) diff --git a/integration/testdata/fixtures/repo/gomod/submod/go.sum b/integration/testdata/fixtures/repo/gomod/submod/go.sum new file mode 100644 index 000000000000..38efa1d61ad4 --- /dev/null +++ b/integration/testdata/fixtures/repo/gomod/submod/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/integration/testdata/fixtures/repo/gomod/submod2/go.mod b/integration/testdata/fixtures/repo/gomod/submod2/go.mod new file mode 100644 index 000000000000..08f06b0e7227 --- /dev/null +++ b/integration/testdata/fixtures/repo/gomod/submod2/go.mod @@ -0,0 +1,7 @@ +module github.com/testdata/testdata/submod2 + +go 1.15 + +require ( + github.com/davecgh/go-spew v1.1.0 +) diff --git a/integration/testdata/fixtures/repo/gomod/submod2/go.sum b/integration/testdata/fixtures/repo/gomod/submod2/go.sum new file mode 100644 index 000000000000..7e62d8411389 --- /dev/null +++ b/integration/testdata/fixtures/repo/gomod/submod2/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= diff --git a/integration/testdata/fixtures/repo/gradle/gradle.lockfile b/integration/testdata/fixtures/repo/gradle/gradle.lockfile new file mode 100644 index 000000000000..b6ba3d4b8b2e --- /dev/null +++ b/integration/testdata/fixtures/repo/gradle/gradle.lockfile @@ -0,0 +1,5 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +com.fasterxml.jackson.core:jackson-databind:2.9.1=compileClasspath, runtimeClasspath +empty= \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/helm/testchart.tar.gz b/integration/testdata/fixtures/repo/helm/testchart.tar.gz new file mode 100644 index 000000000000..e36b2b474f3e Binary files /dev/null and b/integration/testdata/fixtures/repo/helm/testchart.tar.gz differ diff --git a/integration/testdata/fixtures/repo/helm_badname/Chart.yaml b/integration/testdata/fixtures/repo/helm_badname/Chart.yaml new file mode 100644 index 000000000000..e840fbabf456 --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_badname/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: 1001 +version: 1.0.0 \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/helm_testchart/.helmignore b/integration/testdata/fixtures/repo/helm_testchart/.helmignore new file mode 100644 index 000000000000..0e8a0eb36f4c --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/integration/testdata/fixtures/repo/helm_testchart/Chart.yaml b/integration/testdata/fixtures/repo/helm_testchart/Chart.yaml new file mode 100644 index 000000000000..0ffb7d074a72 --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: testchart +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/integration/testdata/fixtures/repo/helm_testchart/templates/NOTES.txt b/integration/testdata/fixtures/repo/helm_testchart/templates/NOTES.txt new file mode 100644 index 000000000000..45e51670a862 --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "testchart.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "testchart.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "testchart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "testchart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/integration/testdata/fixtures/repo/helm_testchart/templates/_helpers.tpl b/integration/testdata/fixtures/repo/helm_testchart/templates/_helpers.tpl new file mode 100644 index 000000000000..4b0db05bf5f5 --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "testchart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "testchart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "testchart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "testchart.labels" -}} +helm.sh/chart: {{ include "testchart.chart" . }} +{{ include "testchart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "testchart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "testchart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "testchart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "testchart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/integration/testdata/fixtures/repo/helm_testchart/templates/deployment.yaml b/integration/testdata/fixtures/repo/helm_testchart/templates/deployment.yaml new file mode 100644 index 000000000000..cde22bc4fcc5 --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/templates/deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "testchart.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "testchart.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "testchart.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/integration/testdata/fixtures/repo/helm_testchart/templates/hpa.yaml b/integration/testdata/fixtures/repo/helm_testchart/templates/hpa.yaml new file mode 100644 index 000000000000..51734471d41d --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "testchart.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/integration/testdata/fixtures/repo/helm_testchart/templates/ingress.yaml b/integration/testdata/fixtures/repo/helm_testchart/templates/ingress.yaml new file mode 100644 index 000000000000..9732d2a24a14 --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "testchart.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "testchart.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/integration/testdata/fixtures/repo/helm_testchart/templates/service.yaml b/integration/testdata/fixtures/repo/helm_testchart/templates/service.yaml new file mode 100644 index 000000000000..86baf148215d --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "testchart.selectorLabels" . | nindent 4 }} diff --git a/integration/testdata/fixtures/repo/helm_testchart/templates/serviceaccount.yaml b/integration/testdata/fixtures/repo/helm_testchart/templates/serviceaccount.yaml new file mode 100644 index 000000000000..f728deb2a6bb --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "testchart.serviceAccountName" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/integration/testdata/fixtures/repo/helm_testchart/templates/tests/test-connection.yaml b/integration/testdata/fixtures/repo/helm_testchart/templates/tests/test-connection.yaml new file mode 100644 index 000000000000..a391ef1c462f --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "testchart.fullname" . }}-test-connection" + labels: + {{- include "testchart.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "testchart.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/integration/testdata/fixtures/repo/helm_testchart/values.yaml b/integration/testdata/fixtures/repo/helm_testchart/values.yaml new file mode 100644 index 000000000000..54dd329d3c6f --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_testchart/values.yaml @@ -0,0 +1,83 @@ +# Default values for testchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: nginx + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: + {} + # fsGroup: 2000 + +securityContext: + { + "capabilities": { + "drop": ["ALL"] + }, + "runAsUser": 10001, + "runAsNonRoot": true, + "runAsGroup": 10001, + "readOnlyRootFilesystem": true, + } + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: + {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 126Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/integration/testdata/fixtures/repo/helm_values/values.yaml b/integration/testdata/fixtures/repo/helm_values/values.yaml new file mode 100644 index 000000000000..041169482cbd --- /dev/null +++ b/integration/testdata/fixtures/repo/helm_values/values.yaml @@ -0,0 +1,2 @@ +securityContext: + runAsUser: 0 \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/mixlock/mix.lock b/integration/testdata/fixtures/repo/mixlock/mix.lock new file mode 100644 index 000000000000..a13ebcf57c21 --- /dev/null +++ b/integration/testdata/fixtures/repo/mixlock/mix.lock @@ -0,0 +1,12 @@ +%{ + "castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "phoenix": {:hex, :phoenix, "1.6.13", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"}, + "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.1", "a653e3d9d944aace0a064e4a13ad473ffa68f7bc4ca42dbf83cc1d464f1fb295", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "6c358e2cefc5f341c728914b867c556bbfd239fed9e881bac257d70cb2b8a6f6"}, + "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, + "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, +} \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/namespace-exception/Dockerfile b/integration/testdata/fixtures/repo/namespace-exception/Dockerfile new file mode 100644 index 000000000000..fa4b7580b688 --- /dev/null +++ b/integration/testdata/fixtures/repo/namespace-exception/Dockerfile @@ -0,0 +1,2 @@ +FROM alpine:3.13 +LABEL user.root="allow" diff --git a/integration/testdata/fixtures/repo/namespace-exception/policy/exception.rego b/integration/testdata/fixtures/repo/namespace-exception/policy/exception.rego new file mode 100644 index 000000000000..74abdccb1933 --- /dev/null +++ b/integration/testdata/fixtures/repo/namespace-exception/policy/exception.rego @@ -0,0 +1,8 @@ +package namespace.exceptions + +import data.namespaces + +exception[ns] { + ns := data.namespaces[_] + startswith(ns, "builtin") +} \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/npm/node_modules/jquery/package.json b/integration/testdata/fixtures/repo/npm/node_modules/jquery/package.json new file mode 100644 index 000000000000..db81fb8f6928 --- /dev/null +++ b/integration/testdata/fixtures/repo/npm/node_modules/jquery/package.json @@ -0,0 +1,113 @@ +{ + "name": "jquery", + "title": "jQuery", + "description": "JavaScript library for DOM operations", + "version": "3.3.9", + "main": "dist/jquery.js", + "homepage": "https://jquery.com", + "author": { + "name": "JS Foundation and other contributors", + "url": "https://github.com/jquery/jquery/blob/3.4.0/AUTHORS.txt" + }, + "repository": { + "type": "git", + "url": "https://github.com/jquery/jquery.git" + }, + "keywords": [ + "jquery", + "javascript", + "browser", + "library" + ], + "bugs": { + "url": "https://github.com/jquery/jquery/issues" + }, + "license": "MIT", + "dependencies": {}, + "devDependencies": { + "@babel/core": "7.3.3", + "@babel/plugin-transform-for-of": "7.2.0", + "commitplease": "3.2.0", + "core-js": "2.6.5", + "eslint-config-jquery": "1.0.1", + "grunt": "1.0.3", + "grunt-babel": "8.0.0", + "grunt-cli": "1.3.2", + "grunt-compare-size": "0.4.2", + "grunt-contrib-uglify": "3.4.0", + "grunt-contrib-watch": "1.1.0", + "grunt-eslint": "21.0.0", + "grunt-git-authors": "3.2.0", + "grunt-jsonlint": "1.1.0", + "grunt-karma": "3.0.1", + "grunt-newer": "1.3.0", + "grunt-npmcopy": "0.1.0", + "gzip-js": "0.3.2", + "husky": "1.3.1", + "insight": "0.10.1", + "jsdom": "13.2.0", + "karma": "4.0.1", + "karma-browserstack-launcher": "1.4.0", + "karma-chrome-launcher": "2.2.0", + "karma-firefox-launcher": "1.1.0", + "karma-ie-launcher": "1.0.0", + "karma-jsdom-launcher": "7.1.0", + "karma-qunit": "3.0.0", + "load-grunt-tasks": "4.0.0", + "native-promise-only": "0.8.1", + "promises-aplus-tests": "2.1.2", + "q": "1.5.1", + "qunit": "2.9.2", + "raw-body": "2.3.3", + "requirejs": "2.3.6", + "sinon": "2.3.7", + "sizzle": "2.3.4", + "strip-json-comments": "2.0.1", + "testswarm": "1.1.0", + "uglify-js": "3.4.7" + }, + "scripts": { + "build": "npm install && grunt", + "start": "grunt watch", + "test:browserless": "grunt && grunt test:slow", + "test:browser": "grunt && grunt karma:main", + "test": "grunt && grunt test:slow && grunt karma:main", + "jenkins": "npm run test:browserless" + }, + "commitplease": { + "nohook": true, + "components": [ + "Docs", + "Tests", + "Build", + "Support", + "Release", + "Core", + "Ajax", + "Attributes", + "Callbacks", + "CSS", + "Data", + "Deferred", + "Deprecated", + "Dimensions", + "Effects", + "Event", + "Manipulation", + "Offset", + "Queue", + "Selector", + "Serialize", + "Traversing", + "Wrap" + ], + "markerPattern": "^((clos|fix|resolv)(e[sd]|ing))|^(refs?)", + "ticketPattern": "^((Closes|Fixes) ([a-zA-Z]{2,}-)[0-9]+)|^(Refs? [^#])" + }, + "husky": { + "hooks": { + "commit-msg": "node node_modules/commitplease", + "pre-commit": "grunt lint:newer qunit_fixture" + } + } +} diff --git a/integration/testdata/fixtures/repo/npm/node_modules/promise/package.json b/integration/testdata/fixtures/repo/npm/node_modules/promise/package.json new file mode 100644 index 000000000000..6b6df7e49f1d --- /dev/null +++ b/integration/testdata/fixtures/repo/npm/node_modules/promise/package.json @@ -0,0 +1,35 @@ +{ + "name": "promise", + "version": "8.0.3", + "description": "Bare bones Promises/A+ implementation", + "main": "index.js", + "scripts": { + "prepublish": "node build", + "pretest": "node build", + "pretest-resolve": "node build", + "pretest-extensions": "node build", + "pretest-memory-leak": "node build", + "test": "mocha --bail --timeout 200 --slow 99999 -R dot && npm run test-memory-leak", + "test-resolve": "mocha test/resolver-tests.js --timeout 200 --slow 999999", + "test-extensions": "mocha test/extensions-tests.js --timeout 200 --slow 999999", + "test-memory-leak": "node --expose-gc test/memory-leak.js", + "coverage": "istanbul cover node_modules/mocha/bin/_mocha -- --bail --timeout 200 --slow 99999 -R dot" + }, + "repository": { + "type": "git", + "url": "https://github.com/then/promise.git" + }, + "author": "ForbesLindesay", + "license": "MIT", + "devDependencies": { + "acorn": "^1.0.1", + "better-assert": "*", + "istanbul": "^0.3.13", + "mocha": "*", + "promises-aplus-tests": "*", + "rimraf": "^2.3.2" + }, + "dependencies": { + "asap": "~2.0.6" + } +} \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/npm/node_modules/react-is/package.json b/integration/testdata/fixtures/repo/npm/node_modules/react-is/package.json new file mode 100644 index 000000000000..6e502546f89e --- /dev/null +++ b/integration/testdata/fixtures/repo/npm/node_modules/react-is/package.json @@ -0,0 +1,27 @@ +{ + "name": "react-is", + "version": "16.8.6", + "description": "Brand checking of React Elements.", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/facebook/react.git", + "directory": "packages/react-is" + }, + "keywords": [ + "react" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "homepage": "https://reactjs.org/", + "files": [ + "LICENSE", + "README.md", + "build-info.json", + "index.js", + "cjs/", + "umd/" + ] +} diff --git a/integration/testdata/fixtures/repo/npm/node_modules/react/package.json b/integration/testdata/fixtures/repo/npm/node_modules/react/package.json new file mode 100644 index 000000000000..fb0216747f2b --- /dev/null +++ b/integration/testdata/fixtures/repo/npm/node_modules/react/package.json @@ -0,0 +1,39 @@ +{ + "name": "react", + "description": "React is a JavaScript library for building user interfaces.", + "keywords": [ + "react" + ], + "version": "16.8.6", + "homepage": "https://reactjs.org/", + "bugs": "https://github.com/facebook/react/issues", + "license": "MIT", + "files": [ + "LICENSE", + "README.md", + "build-info.json", + "index.js", + "cjs/", + "umd/" + ], + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/facebook/react.git", + "directory": "packages/react" + }, + "engines": { + "node": ">=0.10.0" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.13.6" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + } +} diff --git a/integration/testdata/fixtures/repo/npm/node_modules/redux/package.json b/integration/testdata/fixtures/repo/npm/node_modules/redux/package.json new file mode 100644 index 000000000000..449e8fa08bcf --- /dev/null +++ b/integration/testdata/fixtures/repo/npm/node_modules/redux/package.json @@ -0,0 +1,105 @@ +{ + "name": "redux", + "version": "4.0.1", + "description": "Predictable state container for JavaScript apps", + "license": "MIT", + "homepage": "http://redux.js.org", + "repository": "github:reduxjs/redux", + "bugs": "https://github.com/reduxjs/redux/issues", + "keywords": [ + "redux", + "reducer", + "state", + "predictable", + "functional", + "immutable", + "hot", + "live", + "replay", + "flux", + "elm" + ], + "authors": [ + "Dan Abramov (https://github.com/gaearon)", + "Andrew Clark (https://github.com/acdlite)" + ], + "main": "lib/redux.js", + "unpkg": "dist/redux.js", + "module": "es/redux.js", + "typings": "./index.d.ts", + "files": [ + "dist", + "lib", + "es", + "src", + "index.d.ts" + ], + "scripts": { + "clean": "rimraf lib dist es coverage", + "format": "prettier --write \"{src,test}/**/*.{js,ts}\" index.d.ts", + "format:check": "prettier --list-different \"{src,test}/**/*.{js,ts}\" index.d.ts", + "lint": "eslint src test", + "pretest": "npm run build", + "test": "jest", + "test:watch": "npm test -- --watch", + "test:cov": "npm test -- --coverage", + "build": "rollup -c", + "prepare": "npm run clean && npm run format:check && npm run lint && npm test", + "examples:lint": "eslint examples", + "examples:test": "cross-env CI=true babel-node examples/testAll.js" + }, + "dependencies": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + }, + "devDependencies": { + "@babel/cli": "^7.0.0", + "@babel/core": "^7.0.0", + "@babel/node": "^7.0.0", + "@babel/plugin-external-helpers": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "@babel/preset-flow": "^7.0.0", + "@babel/register": "^7.0.0", + "babel-core": "^7.0.0-bridge.0", + "babel-eslint": "^9.0.0", + "babel-jest": "^23.6.0", + "cross-env": "^5.2.0", + "eslint": "^5.6.0", + "eslint-config-react-app": "^2.1.0", + "eslint-plugin-flowtype": "^2.50.1", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-jsx-a11y": "^6.1.1", + "eslint-plugin-react": "^7.11.1", + "glob": "^7.1.3", + "jest": "^23.6.0", + "prettier": "^1.14.3", + "rimraf": "^2.6.2", + "rollup": "^0.66.2", + "rollup-plugin-babel": "^4.0.1", + "rollup-plugin-node-resolve": "^3.4.0", + "rollup-plugin-replace": "^2.0.0", + "rollup-plugin-terser": "^3.0.0", + "rxjs": "^6.3.2", + "typescript": "^3.0.3", + "typings-tester": "^0.3.2" + }, + "npmName": "redux", + "npmFileMap": [ + { + "basePath": "/dist/", + "files": [ + "*.js" + ] + } + ], + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "jest": { + "testRegex": "(/test/.*\\.spec.js)$" + }, + "sideEffects": false +} diff --git a/integration/testdata/fixtures/repo/npm/node_modules/z-lock/package.json b/integration/testdata/fixtures/repo/npm/node_modules/z-lock/package.json new file mode 100644 index 000000000000..fd196fe2db6b --- /dev/null +++ b/integration/testdata/fixtures/repo/npm/node_modules/z-lock/package.json @@ -0,0 +1,26 @@ +{ + "name": "z-lock", + "version": "1.0.0", + "description": "This CommonJS module can create a simple \"lock\" that can be checked, locked/unlocked.", + "main": "lock.js", + "scripts": { + "test": "mocha test/*.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/ZeeCoder/z-lock.git" + }, + "keywords": [ + "lock" + ], + "author": "Hubert Viktor", + "license": "MIT", + "bugs": { + "url": "https://github.com/ZeeCoder/z-lock/issues" + }, + "homepage": "https://github.com/ZeeCoder/z-lock", + "devDependencies": { + "clone": "^1.0.2", + "mocha": "^2.2.5" + } +} diff --git a/integration/testdata/fixtures/repo/npm/package-lock.json b/integration/testdata/fixtures/repo/npm/package-lock.json new file mode 100644 index 000000000000..94d4563ff37b --- /dev/null +++ b/integration/testdata/fixtures/repo/npm/package-lock.json @@ -0,0 +1,98 @@ +{ + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "promise": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.3.tgz", + "integrity": "sha512-HeRDUL1RJiLhyA0/grn+PTShlBAcLuh/1BJGtrvjwbvRDCTLLMEz9rOGCV+R3vHY4MixIuoMEd9Yq/XvsTPcjw==", + "requires": { + "asap": "~2.0.6" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "react": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz", + "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.13.6" + } + }, + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" + }, + "redux": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz", + "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==", + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + }, + "scheduler": { + "version": "0.13.6", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz", + "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, + "z-lock": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/z-lock/-/z-lock-1.0.0.tgz", + "integrity": "sha512-TWoID7h5wphb4YHcY/tu9u7nZb6wtmBjqpRUYbQCemrhmJXL+7/Vblb6rs7ANnBInIt9Qccb7bXUCNGZpxekeA==", + "dev": true + } + } +} diff --git a/integration/testdata/fixtures/repo/nuget/packages.lock.json b/integration/testdata/fixtures/repo/nuget/packages.lock.json new file mode 100644 index 000000000000..7c06bd9d435f --- /dev/null +++ b/integration/testdata/fixtures/repo/nuget/packages.lock.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "dependencies": { + ".NETCoreApp,Version=v5.0": { + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[12.0.3, )", + "resolved": "12.0.3", + "contentHash": "6mgjfnRB4jKMlzHSl+VD+oUc1IebOZabkbyWj2RiTgWwYPPuaK1H97G1sHqGwPlS5npiF5Q0OrxN1wni2n5QWg==" + }, + "NuGet.Frameworks": { + "type": "Direct", + "requested": "[5.7.0, )", + "resolved": "5.7.0", + "contentHash": "7Q/wUoB3jCBcq9zoBOBGHFhe78C13jViPmvjvzTwthVV8DAjMfpXnqAYtgwdaRLJMkTXrtdLxfPBIFFhmlsnIQ==", + "dependencies": { + "Newtonsoft.Json": "12.0.3" + } + } + } + } +} diff --git a/integration/testdata/fixtures/repo/packagesprops/Directory.Packages.props b/integration/testdata/fixtures/repo/packagesprops/Directory.Packages.props new file mode 100644 index 000000000000..daa6f7ccdc4f --- /dev/null +++ b/integration/testdata/fixtures/repo/packagesprops/Directory.Packages.props @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/pip/requirements.txt b/integration/testdata/fixtures/repo/pip/requirements.txt new file mode 100644 index 000000000000..94b9f4a80b20 --- /dev/null +++ b/integration/testdata/fixtures/repo/pip/requirements.txt @@ -0,0 +1,8 @@ +click==8.3.1 +Flask==3.1.3 +itsdangerous==2.2.0 +Jinja2==3.1.6 +MarkupSafe>2.0.0 +Werkzeug==3.1.6 +oauth2-client==4.0.0 +python-gitlab==8.1.0 diff --git a/integration/testdata/fixtures/repo/pipenv/Pipfile.lock b/integration/testdata/fixtures/repo/pipenv/Pipfile.lock new file mode 100644 index 000000000000..c7e495b1e5f2 --- /dev/null +++ b/integration/testdata/fixtures/repo/pipenv/Pipfile.lock @@ -0,0 +1,29 @@ +{ + "_meta": { + "hash": { + "sha256": "06bf5e1462f5cf5abd8c226d9db597827c8fde5c6bbb0e9c87c2977720130c56" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.10" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "werkzeug": { + "hashes": [ + "sha256:1e0dedc2acb1f46827daa2e399c1485c8fa17c0d8e70b6b875b4e7f54bf408d2", + "sha256:b353856d37dec59d6511359f97f6a4b2468442e454bd1c98298ddce53cac1f04" + ], + "index": "pypi", + "version": "==0.11.1" + } + }, + "develop": {} +} diff --git a/integration/testdata/fixtures/repo/pnpm/pnpm-lock.yaml b/integration/testdata/fixtures/repo/pnpm/pnpm-lock.yaml new file mode 100644 index 000000000000..be652ed3a442 --- /dev/null +++ b/integration/testdata/fixtures/repo/pnpm/pnpm-lock.yaml @@ -0,0 +1,19 @@ +lockfileVersion: 5.4 + +specifiers: + jquery: 3.3.9 + lodash: 4.17.4 + +dependencies: + jquery: 3.3.9 + lodash: 4.17.4 + +packages: + + /jquery/3.3.9: + resolution: {integrity: sha512-ggRCXln9zEqv6OqAGXFEcshF5dSBvCkzj6Gm2gzuR5fWawaX8t7cxKVkkygKODrDAzKdoYw3l/e3pm3vlT4IbQ==} + dev: false + + /lodash/4.17.4: + resolution: {integrity: sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=} + dev: false diff --git a/integration/testdata/fixtures/repo/poetry/poetry.lock b/integration/testdata/fixtures/repo/poetry/poetry.lock new file mode 100644 index 000000000000..1e2f8f71e94c --- /dev/null +++ b/integration/testdata/fixtures/repo/poetry/poetry.lock @@ -0,0 +1,50 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "werkzeug" +version = "0.14" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "Werkzeug-0.14-py2.py3-none-any.whl", hash = "sha256:322b15deb0e503c3e96c267b676d47ca069edccbf6338549bea7916583822a55"}, + {file = "Werkzeug-0.14.tar.gz", hash = "sha256:4aea27a9513b056346e9c8b49107f4ee7927f7bcf0be63024ecee39d5b87e9ef"}, +] + +[package.extras] +dev = ["coverage", "pytest", "sphinx", "tox"] +termcolor = ["termcolor"] +watchdog = ["watchdog"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "7bf54e5dc4ab511438271b965af1def5798ef80c82c39a3cdfe9308fd7881ff1" \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/poetry/pyproject.toml b/integration/testdata/fixtures/repo/poetry/pyproject.toml new file mode 100644 index 000000000000..332d56ab7a96 --- /dev/null +++ b/integration/testdata/fixtures/repo/poetry/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "test" +version = "0.1.0" +description = "" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.9" +werkzeug = "0.14" +click = "8.1.3" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/integration/testdata/fixtures/repo/pom/pom.xml b/integration/testdata/fixtures/repo/pom/pom.xml new file mode 100644 index 000000000000..0f4214b5d254 --- /dev/null +++ b/integration/testdata/fixtures/repo/pom/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + com.example + log4shell + 1.0-SNAPSHOT + log4shell + war + + + UTF-8 + 1.8 + 1.8 + 5.7.1 + + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + + + com.fasterxml.jackson.core + jackson-databind + 2.9.1 + + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.3.1 + + + + + + \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/pubspec/pubspec.lock b/integration/testdata/fixtures/repo/pubspec/pubspec.lock new file mode 100644 index 000000000000..043c82efcea2 --- /dev/null +++ b/integration/testdata/fixtures/repo/pubspec/pubspec.lock @@ -0,0 +1,20 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + http: + dependency: "direct main" + description: + name: http + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.13.2" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.1" +sdks: + dart: ">=2.18.0 <3.0.0" + flutter: ">=3.3.0" \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/rule-exception/Dockerfile b/integration/testdata/fixtures/repo/rule-exception/Dockerfile new file mode 100644 index 000000000000..5bcf55456482 --- /dev/null +++ b/integration/testdata/fixtures/repo/rule-exception/Dockerfile @@ -0,0 +1,4 @@ +FROM alpine:3.13 +LABEL user.root="allow" + +HEALTHCHECK NONE \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/rule-exception/policy/exception.rego b/integration/testdata/fixtures/repo/rule-exception/policy/exception.rego new file mode 100644 index 000000000000..224f626021e4 --- /dev/null +++ b/integration/testdata/fixtures/repo/rule-exception/policy/exception.rego @@ -0,0 +1,15 @@ +package builtin.dockerfile.DS002 + +exception[rules] { + instruction := input.stages[_][_] + instruction.Cmd == "label" + + key := instruction.Value[i] + i % 2 == 0 + key == "user.root" + + value := instruction.Value[plus(i, 1)] + value == "\"allow\"" + + rules = [""] +} diff --git a/integration/testdata/fixtures/repo/secrets/deploy.sh b/integration/testdata/fixtures/repo/secrets/deploy.sh new file mode 100644 index 000000000000..f696ab73ac43 --- /dev/null +++ b/integration/testdata/fixtures/repo/secrets/deploy.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +export AWS_ACCESS_KEY_ID=AKIAABCDEFGHI1234567 + +export GITHUB_PAT=ghp_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ + +echo mysecret \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/secrets/trivy-secret.yaml b/integration/testdata/fixtures/repo/secrets/trivy-secret.yaml new file mode 100644 index 000000000000..8eb7d957e0ee --- /dev/null +++ b/integration/testdata/fixtures/repo/secrets/trivy-secret.yaml @@ -0,0 +1,8 @@ +rules: + - id: mysecret + category: Custom + title: My Secret + severity: HIGH + regex: mysecret +disable-rules: + - github-pat diff --git a/integration/testdata/fixtures/repo/swift/Package.resolved b/integration/testdata/fixtures/repo/swift/Package.resolved new file mode 100644 index 000000000000..f9ca89fb741c --- /dev/null +++ b/integration/testdata/fixtures/repo/swift/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "swift-atomics", + "repositoryURL": "https://github.com/apple/swift-atomics.git", + "state": { + "branch": null, + "revision": "6c89474e62719ddcc1e9614989fff2f68208fe10", + "version": "1.1.0" + } + }, + { + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio", + "state": { + "branch": null, + "revision": "ece5057615d1bee848341eceafdf04ca54d60177", + "version": "2.41.0" + } + } + ] + }, + "version": 1 +} diff --git a/integration/testdata/fixtures/repo/yarn/node_modules/jquery/package.json b/integration/testdata/fixtures/repo/yarn/node_modules/jquery/package.json new file mode 100644 index 000000000000..d50be28053ce --- /dev/null +++ b/integration/testdata/fixtures/repo/yarn/node_modules/jquery/package.json @@ -0,0 +1,118 @@ +{ + "name": "jquery", + "title": "jQuery", + "description": "JavaScript library for DOM operations", + "version": "3.2.1", + "main": "dist/jquery.js", + "homepage": "https://jquery.com", + "author": { + "name": "OpenJS Foundation and other contributors", + "url": "https://github.com/jquery/jquery/blob/3.7.0/AUTHORS.txt" + }, + "repository": { + "type": "git", + "url": "https://github.com/jquery/jquery.git" + }, + "keywords": [ + "jquery", + "javascript", + "browser", + "library" + ], + "bugs": { + "url": "https://github.com/jquery/jquery/issues" + }, + "license": "MIT", + "devDependencies": { + "@babel/core": "7.3.3", + "@babel/plugin-transform-for-of": "7.2.0", + "colors": "1.4.0", + "commitplease": "3.2.0", + "core-js": "2.6.5", + "eslint-config-jquery": "3.0.0", + "grunt": "1.5.3", + "grunt-babel": "8.0.0", + "grunt-cli": "1.4.3", + "grunt-compare-size": "0.4.2", + "grunt-contrib-uglify": "3.4.0", + "grunt-contrib-watch": "1.1.0", + "grunt-eslint": "22.0.0", + "grunt-git-authors": "3.2.0", + "grunt-jsonlint": "2.1.2", + "grunt-karma": "4.0.2", + "grunt-newer": "1.3.0", + "grunt-npmcopy": "0.2.0", + "gzip-js": "0.3.2", + "husky": "4.2.5", + "jsdom": "19.0.0", + "karma": "6.4.1", + "karma-browserstack-launcher": "1.6.0", + "karma-chrome-launcher": "3.1.1", + "karma-firefox-launcher": "2.1.2", + "karma-ie-launcher": "1.0.0", + "karma-jsdom-launcher": "12.0.0", + "karma-qunit": "4.1.2", + "karma-webkit-launcher": "2.1.0", + "load-grunt-tasks": "5.1.0", + "native-promise-only": "0.8.1", + "playwright-webkit": "1.30.0", + "promises-aplus-tests": "2.1.2", + "q": "1.5.1", + "qunit": "2.9.2", + "raw-body": "2.3.3", + "requirejs": "2.3.6", + "sinon": "2.3.7", + "strip-json-comments": "2.0.1", + "testswarm": "1.1.2", + "uglify-js": "3.4.7" + }, + "scripts": { + "build": "npm install && grunt", + "start": "grunt watch", + "test:browserless": "grunt && grunt test:slow", + "test:browser": "grunt && grunt karma:main", + "test:amd": "grunt && grunt karma:amd", + "test:no-deprecated": "grunt test:prepare && grunt custom:-deprecated && grunt karma:main", + "test:selector-native": "grunt test:prepare && grunt custom:-selector && grunt karma:main", + "test:slim": "grunt test:prepare && grunt custom:slim && grunt karma:main", + "test": "npm run test:slim && npm run test:no-deprecated && npm run test:selector-native && grunt && grunt test:slow && grunt karma:main && grunt karma:amd", + "jenkins": "npm run test:browserless" + }, + "commitplease": { + "nohook": true, + "components": [ + "Docs", + "Tests", + "Build", + "Support", + "Release", + "Core", + "Ajax", + "Attributes", + "Callbacks", + "CSS", + "Data", + "Deferred", + "Deprecated", + "Dimensions", + "Effects", + "Event", + "Manipulation", + "Offset", + "Queue", + "Selector", + "Serialize", + "Traversing", + "Wrap" + ], + "markerPattern": "^((clos|fix|resolv)(e[sd]|ing))|^(refs?)", + "ticketPattern": "^((Closes|Fixes) ([a-zA-Z]{2,}-)[0-9]+)|^(Refs? [^#])" + }, + "husky": { + "hooks": { + "commit-msg": "commitplease .git/COMMIT_EDITMSG", + "pre-commit": "grunt lint:newer qunit_fixture" + } + } + } + \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/yarn/package.json b/integration/testdata/fixtures/repo/yarn/package.json new file mode 100644 index 000000000000..7bf8044cdc36 --- /dev/null +++ b/integration/testdata/fixtures/repo/yarn/package.json @@ -0,0 +1,12 @@ +{ + "name": "integration", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "jquery": "^4.0.0" + }, + "devDependencies": { + "promise": "^8.3.0" + } +} diff --git a/integration/testdata/fixtures/repo/yarn/yarn.lock b/integration/testdata/fixtures/repo/yarn/yarn.lock new file mode 100644 index 000000000000..584449388a07 --- /dev/null +++ b/integration/testdata/fixtures/repo/yarn/yarn.lock @@ -0,0 +1,20 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +jquery@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== + +promise@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.0.3.tgz#f592e099c6cddc000d538ee7283bb190452b0bf6" + integrity sha512-HeRDUL1RJiLhyA0/grn+PTShlBAcLuh/1BJGtrvjwbvRDCTLLMEz9rOGCV+R3vHY4MixIuoMEd9Yq/XvsTPcjw== + dependencies: + asap "~2.0.6" diff --git a/integration/testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl b/integration/testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl new file mode 100644 index 000000000000..d7c6f4438d40 --- /dev/null +++ b/integration/testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl @@ -0,0 +1 @@ +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2N5Y2xvbmVkeC5vcmcvYm9tIiwic3ViamVjdCI6W3sibmFtZSI6ImluZGV4LmRvY2tlci5pby9saWJyYXJ5L2NlbnRvcyIsImRpZ2VzdCI6eyJzaGEyNTYiOiJiZTY1ZjQ4OGI3NzY0YWQzNjM4ZjIzNmI3YjUxNWIzNjc4MzY5YTUxMjRjNDdiOGQzMjkxNmQ2NDg3NDE4ZWE0In19XSwicHJlZGljYXRlIjp7ImJvbUZvcm1hdCI6IkN5Y2xvbmVEWCIsInNwZWNWZXJzaW9uIjoiMS40Iiwic2VyaWFsTnVtYmVyIjoidXJuOnV1aWQ6MTQ1NWMwMmQtNjRjYS00NTNlLWE1ZGYtZGRmYjcwYTdjODA0IiwidmVyc2lvbiI6MSwibWV0YWRhdGEiOnsidGltZXN0YW1wIjoiMjAyMi0wNi0xNFQxNTowODo0OCswMDowMCIsInRvb2xzIjpbeyJ2ZW5kb3IiOiJhcXVhc2VjdXJpdHkiLCJuYW1lIjoidHJpdnkiLCJ2ZXJzaW9uIjoiZGV2In1dLCJjb21wb25lbnQiOnsiYm9tLXJlZiI6ImQwZDQxZTMwLTk2NTAtNDg5ZC05NDhkLTQyNWZmMmVkNjNkMiIsInR5cGUiOiJjb250YWluZXIiLCJuYW1lIjoiaW50ZWdyYXRpb24vdGVzdGRhdGEvZml4dHVyZXMvaW1hZ2VzL2NlbnRvcy03LnRhci5neiIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U2NoZW1hVmVyc2lvbiIsInZhbHVlIjoiMiJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpJbWFnZUlEIiwidmFsdWUiOiJzaGEyNTY6ZjFjYjdjN2Q1OGI3M2VhYzg1OWMzOTU4ODJlZWM0OWQ1MDY1MTI0NGUzNDJjZDZjNjhhNWM3ODA5Nzg1ZjQyNyJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpEaWZmSUQiLCJ2YWx1ZSI6InNoYTI1Njo4OTE2OWQ4N2RiZTJiNzJiYTQyYmZiYjM1NzljOTU3MzIyYmFjYTI4ZTAzYTFlNTU4MDc2NTQyYTFjMWIyYjRhIn1dfX0sImNvbXBvbmVudHMiOlt7ImJvbS1yZWYiOiJwa2c6cnBtL2NlbnRvcy9iYXNoQDQuMi40Ni0zMS5lbDc/YXJjaD14ODZfNjQmZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsInR5cGUiOiJsaWJyYXJ5IiwibmFtZSI6ImJhc2giLCJ2ZXJzaW9uIjoiNC4yLjQ2LTMxLmVsNyIsImxpY2Vuc2VzIjpbeyJleHByZXNzaW9uIjoiR1BMdjMrIn1dLCJwdXJsIjoicGtnOnJwbS9jZW50b3MvYmFzaEA0LjIuNDYtMzEuZWw3P2FyY2g9eDg2XzY0JmRpc3Rybz1jZW50b3MtNy42LjE4MTAiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlBrZ0lEIiwidmFsdWUiOiJiYXNoQDQuMi40Ni0zMS5lbDcueDg2XzY0In0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY05hbWUiLCJ2YWx1ZSI6ImJhc2gifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjVmVyc2lvbiIsInZhbHVlIjoiNC4yLjQ2In0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY1JlbGVhc2UiLCJ2YWx1ZSI6IjMxLmVsNyJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpMYXllckRpZ2VzdCIsInZhbHVlIjoic2hhMjU2OmFjOTIwODIwN2FkYWFjM2E0OGU1NGE0ZGM2YjQ5YzY5ZTc4YzMwNzJkMmIzYWRkN2VmZGFiZjgxNGRiMjEzM2IifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWZmSUQiLCJ2YWx1ZSI6InNoYTI1Njo4OTE2OWQ4N2RiZTJiNzJiYTQyYmZiYjM1NzljOTU3MzIyYmFjYTI4ZTAzYTFlNTU4MDc2NTQyYTFjMWIyYjRhIn1dfSx7ImJvbS1yZWYiOiJwa2c6cnBtL2NlbnRvcy9vcGVuc3NsLWxpYnNAMS4wLjJrLTE2LmVsNz9hcmNoPXg4Nl82NCZlcG9jaD0xJmRpc3Rybz1jZW50b3MtNy42LjE4MTAiLCJ0eXBlIjoibGlicmFyeSIsIm5hbWUiOiJvcGVuc3NsLWxpYnMiLCJ2ZXJzaW9uIjoiMS4wLjJrLTE2LmVsNyIsImxpY2Vuc2VzIjpbeyJleHByZXNzaW9uIjoiT3BlblNTTCJ9XSwicHVybCI6InBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxLjAuMmstMTYuZWw3P2FyY2g9eDg2XzY0JmVwb2NoPTEmZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6UGtnSUQiLCJ2YWx1ZSI6Im9wZW5zc2wtbGlic0AxLjAuMmstMTYuZWw3Lng4Nl82NCJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNOYW1lIiwidmFsdWUiOiJvcGVuc3NsIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY1ZlcnNpb24iLCJ2YWx1ZSI6IjEuMC4yayJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNSZWxlYXNlIiwidmFsdWUiOiIxNi5lbDcifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjRXBvY2giLCJ2YWx1ZSI6IjEifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWdlc3QiLCJ2YWx1ZSI6InNoYTI1NjphYzkyMDgyMDdhZGFhYzNhNDhlNTRhNGRjNmI0OWM2OWU3OGMzMDcyZDJiM2FkZDdlZmRhYmY4MTRkYjIxMzNiIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ODkxNjlkODdkYmUyYjcyYmE0MmJmYmIzNTc5Yzk1NzMyMmJhY2EyOGUwM2ExZTU1ODA3NjU0MmExYzFiMmI0YSJ9XX0seyJib20tcmVmIjoiMDE3NWY3MzItZGY5ZC00YmI4LTlmNTYtODcwODk4ZTNmZjg5IiwidHlwZSI6Im9wZXJhdGluZy1zeXN0ZW0iLCJuYW1lIjoiY2VudG9zIiwidmVyc2lvbiI6IjcuNi4xODEwIiwicHJvcGVydGllcyI6W3sibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpUeXBlIiwidmFsdWUiOiJjZW50b3MifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6Q2xhc3MiLCJ2YWx1ZSI6Im9zLXBrZ3MifV19XSwiZGVwZW5kZW5jaWVzIjpbeyJyZWYiOiIwMTc1ZjczMi1kZjlkLTRiYjgtOWY1Ni04NzA4OThlM2ZmODkiLCJkZXBlbmRzT24iOlsicGtnOnJwbS9jZW50b3MvYmFzaEA0LjIuNDYtMzEuZWw3P2FyY2g9eDg2XzY0JmRpc3Rybz1jZW50b3MtNy42LjE4MTAiLCJwa2c6cnBtL2NlbnRvcy9vcGVuc3NsLWxpYnNAMS4wLjJrLTE2LmVsNz9hcmNoPXg4Nl82NCZlcG9jaD0xJmRpc3Rybz1jZW50b3MtNy42LjE4MTAiXX0seyJyZWYiOiJkMGQ0MWUzMC05NjUwLTQ4OWQtOTQ4ZC00MjVmZjJlZDYzZDIiLCJkZXBlbmRzT24iOlsiMDE3NWY3MzItZGY5ZC00YmI4LTlmNTYtODcwODk4ZTNmZjg5Il19XX19Cg==","signatures":[{"keyid":"","sig":"MEUCIQCtj78dipe+yzdlIsmwjn9QeaBTAPQacwIJAWfnrtp7FwIgcViOUgPA0WFYjimrIl7vbygdSpduM+ZzY3cqrDciH1U="}]} diff --git a/integration/testdata/fixtures/sbom/centos-7-cyclonedx.json b/integration/testdata/fixtures/sbom/centos-7-cyclonedx.json new file mode 100644 index 000000000000..801dea718089 --- /dev/null +++ b/integration/testdata/fixtures/sbom/centos-7-cyclonedx.json @@ -0,0 +1,152 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:1455c02d-64ca-453e-a5df-ddfb70a7c804", + "version": 1, + "metadata": { + "timestamp": "2022-06-14T15:08:48+00:00", + "tools": [ + { + "vendor": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ], + "component": { + "bom-ref": "d0d41e30-9650-489d-948d-425ff2ed63d2", + "type": "container", + "name": "integration/testdata/fixtures/images/centos-7.tar.gz", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:f1cb7c7d58b73eac859c395882eec49d50651244e342cd6c68a5c7809785f427" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810", + "type": "library", + "name": "bash", + "version": "4.2.46-31.el7", + "licenses": [ + { + "license": { + "name": "GPLv3+" + } + } + ], + "purl": "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "bash@4.2.46-31.el7.x86_64" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "bash" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "4.2.46" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "31.el7" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + } + ] + }, + { + "bom-ref": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", + "type": "library", + "name": "openssl-libs", + "version": "1.0.2k-16.el7", + "licenses": [ + { + "license": { + "name": "OpenSSL+" + } + } + ], + "purl": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "openssl-libs@1.0.2k-16.el7.x86_64" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "openssl" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.0.2k" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "16.el7" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "1" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + } + ] + }, + { + "bom-ref": "0175f732-df9d-4bb8-9f56-870898e3ff89", + "type": "operating-system", + "name": "centos", + "version": "7.6.1810", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "centos" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + } + ] + } + ], + "dependencies": [ + { + "ref": "0175f732-df9d-4bb8-9f56-870898e3ff89", + "dependsOn": [ + "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810", + "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810" + ] + }, + { + "ref": "d0d41e30-9650-489d-948d-425ff2ed63d2", + "dependsOn": [ + "0175f732-df9d-4bb8-9f56-870898e3ff89" + ] + } + ] +} \ No newline at end of file diff --git a/integration/testdata/fixtures/sbom/centos-7-spdx.json b/integration/testdata/fixtures/sbom/centos-7-spdx.json new file mode 100644 index 000000000000..ce13aa8afd8a --- /dev/null +++ b/integration/testdata/fixtures/sbom/centos-7-spdx.json @@ -0,0 +1,96 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2022-09-13T13:27:55.874784Z", + "creators": [ + "Tool: trivy-dev", + "Organization: aquasecurity" + ] + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "http://aquasecurity.github.io/trivy/container_image/integration/testdata/fixtures/images/centos-7.tar.gz-2906855d-5098-4a22-9a72-4f7099ea3d66", + "name": "integration/testdata/fixtures/images/centos-7.tar.gz", + "packages": [ + { + "SPDXID": "SPDXRef-ContainerImage-dd5cad897c6263", + "attributionTexts": [ + "SchemaVersion: 2", + "ImageID: sha256:f1cb7c7d58b73eac859c395882eec49d50651244e342cd6c68a5c7809785f427", + "DiffID: sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + ], + "filesAnalyzed": false, + "name": "integration/testdata/fixtures/images/centos-7.tar.gz" + }, + { + "SPDXID": "SPDXRef-OperatingSystem-2e91c856c499a371", + "filesAnalyzed": false, + "name": "centos", + "versionInfo": "7.6.1810" + }, + { + "SPDXID": "SPDXRef-Package-5a18334f22149877", + "attributionTexts": [ + "PkgID: bash@4.2.46-31.el7.x86_64", + "LayerDigest: sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "LayerDiffID: sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64\u0026distro=centos-7.6.1810", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "GPLv3+", + "licenseDeclared": "GPLv3+", + "name": "bash", + "sourceInfo": "built package from: bash 4.2.46-31.el7", + "versionInfo": "4.2.46" + }, + { + "SPDXID": "SPDXRef-Package-e16b1cbaa5186199", + "attributionTexts": [ + "PkgID: openssl-libs@1.0.2k-16.el7.x86_64", + "LayerDigest: sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "LayerDiffID: sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026epoch=1\u0026distro=centos-7.6.1810", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "OpenSSL", + "licenseDeclared": "OpenSSL", + "name": "openssl-libs", + "sourceInfo": "built package from: openssl-libs 1:1.0.2k-16.el7", + "versionInfo": "1:1.0.2k-19.el7" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-ContainerImage-dd5cad897c6263", + "relationshipType": "DESCRIBE", + "spdxElementId": "SPDXRef-DOCUMENT" + }, + { + "relatedSpdxElement": "SPDXRef-OperatingSystem-2e91c856c499a371", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-ContainerImage-dd5cad897c6263" + }, + { + "relatedSpdxElement": "SPDXRef-Package-5a18334f22149877", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-OperatingSystem-2e91c856c499a371" + }, + { + "relatedSpdxElement": "SPDXRef-Package-e16b1cbaa5186199", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-OperatingSystem-2e91c856c499a371" + } + ], + "spdxVersion": "SPDX-2.2" +} \ No newline at end of file diff --git a/integration/testdata/fixtures/sbom/centos-7-spdx.txt b/integration/testdata/fixtures/sbom/centos-7-spdx.txt new file mode 100644 index 000000000000..1177a9f4bdee --- /dev/null +++ b/integration/testdata/fixtures/sbom/centos-7-spdx.txt @@ -0,0 +1,59 @@ +SPDXVersion: SPDX-2.2 +DataLicense: CC0-1.0 +SPDXID: SPDXRef-DOCUMENT +DocumentName: integration/testdata/fixtures/images/centos-7.tar.gz +DocumentNamespace: http://aquasecurity.github.io/trivy/container_image/integration/testdata/fixtures/images/centos-7.tar.gz-6a2c050f-bc12-46dc-b2df-1f4e3e0b5e1d +Creator: Organization: aquasecurity +Creator: Tool: trivy-dev +Created: 2022-09-13T13:24:58.796907Z + +##### Package: integration/testdata/fixtures/images/centos-7.tar.gz + +PackageName: integration/testdata/fixtures/images/centos-7.tar.gz +SPDXID: SPDXRef-ContainerImage-dd5cad897c6263 +FilesAnalyzed: false +PackageAttributionText: SchemaVersion: 2 +PackageAttributionText: ImageID: sha256:f1cb7c7d58b73eac859c395882eec49d50651244e342cd6c68a5c7809785f427 +PackageAttributionText: DiffID: sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a + +##### Package: centos + +PackageName: centos +SPDXID: SPDXRef-OperatingSystem-2e91c856c499a371 +PackageVersion: 7.6.1810 +FilesAnalyzed: false + +##### Package: bash + +PackageName: bash +SPDXID: SPDXRef-Package-5a18334f22149877 +PackageVersion: 4.2.46 +FilesAnalyzed: false +PackageSourceInfo: built package from: bash 4.2.46-31.el7 +PackageLicenseConcluded: GPLv3+ +PackageLicenseDeclared: GPLv3+ +ExternalRef: PACKAGE-MANAGER purl pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810 +PackageAttributionText: PkgID: bash@4.2.46-31.el7.x86_64 +PackageAttributionText: LayerDigest: sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b +PackageAttributionText: LayerDiffID: sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a + +##### Package: openssl-libs + +PackageName: openssl-libs +SPDXID: SPDXRef-Package-e16b1cbaa5186199 +PackageVersion: 1.0.2k +FilesAnalyzed: false +PackageSourceInfo: built package from: openssl-libs 1:1.0.2k-16.el7 +PackageLicenseConcluded: OpenSSL +PackageLicenseDeclared: OpenSSL +ExternalRef: PACKAGE-MANAGER purl pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810 +PackageAttributionText: PkgID: openssl-libs@1.0.2k-16.el7.x86_64 +PackageAttributionText: LayerDigest: sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b +PackageAttributionText: LayerDiffID: sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a + +##### Relationships + +Relationship: SPDXRef-DOCUMENT DESCRIBE SPDXRef-ContainerImage-dd5cad897c6263 +Relationship: SPDXRef-ContainerImage-dd5cad897c6263 CONTAINS SPDXRef-OperatingSystem-2e91c856c499a371 +Relationship: SPDXRef-OperatingSystem-2e91c856c499a371 DEPENDS_ON SPDXRef-Package-5a18334f22149877 +Relationship: SPDXRef-OperatingSystem-2e91c856c499a371 DEPENDS_ON SPDXRef-Package-e16b1cbaa5186199 \ No newline at end of file diff --git a/integration/testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json b/integration/testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json new file mode 100644 index 000000000000..f5db904207d1 --- /dev/null +++ b/integration/testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json @@ -0,0 +1,171 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:31ee662c-480e-4f63-9765-23ea8afc754d", + "version": 1, + "metadata": { + "timestamp": "2022-06-14T15:10:14+00:00", + "tools": [ + { + "vendor": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ], + "component": { + "bom-ref": "95de56ee-980c-413d-8f68-6c674dc3e9d1", + "type": "container", + "name": "integration/testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:5a992077baba51b97f27591a10d54d2f2723dc9c81a3fe419e261023f2554933" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:02874b2b269dea8dde0f7edb4c9906904dfe38a09de1a214f20c650cfb15c60e" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:3752e1f6fd759c795c13aff2c93c081529366e27635ba6621e849b0f9cfc77f0" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:788c00e2cfc8f2a018ae4344ccf0b2c226ebd756d7effd1ce50eea1a4252cd89" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "type": "library", + "name": "bash", + "version": "5.0-4", + "purl": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:SrcName", + "value": "bash" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "5.0-4" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "type": "library", + "name": "libidn2-0", + "version": "2.0.5-1", + "purl": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:SrcName", + "value": "libidn2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.0.5-1" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + } + ] + }, + { + "bom-ref": "353f2470-9c8b-4647-9d0d-96d893838dc8", + "type": "operating-system", + "name": "debian", + "version": "10.2", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + } + ] + }, + { + "bom-ref": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", + "type": "library", + "name": "activesupport", + "version": "6.0.2.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/activesupport@6.0.2.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "gemspec" + } + ] + } + ], + "dependencies": [ + { + "ref": "353f2470-9c8b-4647-9d0d-96d893838dc8", + "dependsOn": [ + "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2" + ] + }, + { + "ref": "95de56ee-980c-413d-8f68-6c674dc3e9d1", + "dependsOn": [ + "353f2470-9c8b-4647-9d0d-96d893838dc8", + "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec" + ] + } + ] +} \ No newline at end of file diff --git a/integration/testdata/fixtures/sbom/license-cyclonedx.json b/integration/testdata/fixtures/sbom/license-cyclonedx.json new file mode 100644 index 000000000000..e8353ca609cc --- /dev/null +++ b/integration/testdata/fixtures/sbom/license-cyclonedx.json @@ -0,0 +1,125 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c09512e3-47e7-4eff-8f76-5d7ae72b26a5", + "version": 1, + "metadata": { + "timestamp": "2024-03-10T14:57:31+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "acc9d4aa-4158-4969-a497-637e114fde0c", + "type": "application", + "name": "C:/Users/bedla.czech/IdeaProjects/sbom-demo", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "eb56cd49-da98-4b08-bfc8-9880fb063cf1", + "type": "application", + "name": "pom.xml", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "pom" + } + ] + }, + { + "bom-ref": "pkg:maven/org.eclipse.sisu/org.eclipse.sisu.plexus@0.3.0.M1", + "type": "library", + "group": "org.eclipse.sisu", + "name": "org.eclipse.sisu.plexus", + "version": "0.3.0.M1", + "licenses": [ + { + "license": { + "name": "EPL-1.0" + } + } + ], + "purl": "pkg:maven/org.eclipse.sisu/org.eclipse.sisu.plexus@0.3.0.M1", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.0.M1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "pom" + } + ] + }, + { + "bom-ref": "pkg:maven/org.ow2.asm/asm@9.5", + "type": "library", + "group": "org.ow2.asm", + "name": "asm", + "version": "9.5", + "licenses": [ + { + "license": { + "name": "BSD-3-Clause" + } + } + ], + "purl": "pkg:maven/org.ow2.asm/asm@9.5", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "org.ow2.asm:asm:9.5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "pom" + } + ] + }, + { + "bom-ref": "pkg:maven/org.slf4j/slf4j-api@2.0.11", + "type": "library", + "group": "org.slf4j", + "name": "slf4j-api", + "version": "2.0.11", + "licenses": [ + { + "license": { + "name": "MIT License" + } + } + ], + "purl": "pkg:maven/org.slf4j/slf4j-api@2.0.11", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "org.slf4j:slf4j-api:2.0.11" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "pom" + } + ] + } + ], + "dependencies": [], + "vulnerabilities": [] +} diff --git a/integration/testdata/fixtures/sbom/minikube-kbom.json b/integration/testdata/fixtures/sbom/minikube-kbom.json new file mode 100644 index 000000000000..c1ee53d6c9c8 --- /dev/null +++ b/integration/testdata/fixtures/sbom/minikube-kbom.json @@ -0,0 +1,424 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:e2daaea6-d96f-4b84-960c-0d72c348cd23", + "version": 1, + "metadata": { + "timestamp": "2023-09-29T06:25:00+00:00", + "tools": [ + { + "vendor": "aquasecurity", + "name": "trivy", + "version": "0.45.1-15-g7bbd0d097" + } + ], + "component": { + "bom-ref": "pkg:k8s/k8s.io%2Fkubernetes@1.27.0", + "type": "platform", + "name": "k8s.io/kubernetes", + "version": "1.27.0", + "purl": "pkg:k8s/k8s.io%2Fkubernetes@1.27.0", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "cluster" + } + ] + } + }, + "components": [ + { + "bom-ref": "5262e708-f1a3-4fca-a1c3-0a8384f7f4a5", + "type": "operating-system", + "name": "ubuntu", + "version": "22.04.2", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "ubuntu" + } + ] + }, + { + "bom-ref": "a62abb1f-cb38-4fde-90f3-2bda3b87ddb2", + "type": "application", + "name": "node-core-components" + }, + { + "bom-ref": "a6350ac3-52f6-4c5f-a3e3-184b9a634bef", + "type": "platform", + "name": "minikube", + "properties": [ + { + "name": "aquasecurity:trivy:Architecture", + "value": "arm64" + }, + { + "name": "aquasecurity:trivy:HostName", + "value": "minikube" + }, + { + "name": "aquasecurity:trivy:KernelVersion", + "value": "5.15.49-linuxkit-pr" + }, + { + "name": "aquasecurity:trivy:NodeRole", + "value": "master" + }, + { + "name": "aquasecurity:trivy:OperatingSystem", + "value": "linux" + }, + { + "name": "aquasecurity:trivy:resource:Name", + "value": "minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "node" + } + ] + }, + { + "bom-ref": "b19a88a3-017d-4e70-a73a-75f48696ec0f", + "type": "application", + "name": "kube-dns", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "coredns-5d78c9869d-nd92n" + } + ] + }, + { + "bom-ref": "b1c502c9-3c6e-43af-822b-1cb55c6c6ff3", + "type": "application", + "name": "go.etcd.io/etcd/v3", + "version": "3.5.7-0", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "etcd-minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "controlPlane" + } + ] + }, + { + "bom-ref": "pkg:golang/docker@24.0.4", + "type": "application", + "name": "docker", + "version": "24.0.4", + "purl": "pkg:golang/docker@24.0.4", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "docker" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "node" + } + ] + }, + { + "bom-ref": "pkg:k8s/k8s.io%2Fapiserver@1.27.0", + "type": "application", + "name": "k8s.io/apiserver", + "version": "1.27.0", + "purl": "pkg:k8s/k8s.io%2Fapiserver@1.27.0", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "kube-apiserver-minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "controlPlane" + } + ] + }, + { + "bom-ref": "pkg:k8s/k8s.io%2Fcontroller-manager@1.27.0", + "type": "application", + "name": "k8s.io/controller-manager", + "version": "1.27.0", + "purl": "pkg:k8s/k8s.io%2Fcontroller-manager@1.27.0", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "kube-controller-manager-minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "controlPlane" + } + ] + }, + { + "bom-ref": "pkg:k8s/k8s.io%2Fkube-proxy@1.27.0", + "type": "application", + "name": "k8s.io/kube-proxy", + "version": "1.27.0", + "purl": "pkg:k8s/k8s.io%2Fkube-proxy@1.27.0", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "kube-proxy-4wftc" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "node" + } + ] + }, + { + "bom-ref": "pkg:k8s/k8s.io%2Fkube-scheduler@1.27.0", + "type": "application", + "name": "k8s.io/kube-scheduler", + "version": "1.27.0", + "purl": "pkg:k8s/k8s.io%2Fkube-scheduler@1.27.0", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "kube-scheduler-minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "controlPlane" + } + ] + }, + { + "bom-ref": "pkg:k8s/k8s.io%2Fkubelet@1.27.0", + "type": "application", + "name": "k8s.io/kubelet", + "version": "1.27.0", + "purl": "pkg:k8s/k8s.io%2Fkubelet@1.27.0", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "k8s.io/kubelet" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "node" + } + ] + }, + { + "bom-ref": "pkg:oci/coredns@sha256%3Aa0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e?repository_url=registry.k8s.io%2Fcoredns%2Fcoredns", + "type": "container", + "name": "registry.k8s.io/coredns/coredns", + "version": "sha256:a0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e", + "purl": "pkg:oci/coredns@sha256%3Aa0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e?repository_url=registry.k8s.io%2Fcoredns%2Fcoredns", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/coredns/coredns:1.10.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + }, + { + "bom-ref": "pkg:oci/etcd@sha256%3A51eae8381dcb1078289fa7b4f3df2630cdc18d09fb56f8e56b41c40e191d6c83?repository_url=registry.k8s.io%2Fetcd", + "type": "container", + "name": "registry.k8s.io/etcd", + "version": "sha256:51eae8381dcb1078289fa7b4f3df2630cdc18d09fb56f8e56b41c40e191d6c83", + "purl": "pkg:oci/etcd@sha256%3A51eae8381dcb1078289fa7b4f3df2630cdc18d09fb56f8e56b41c40e191d6c83?repository_url=registry.k8s.io%2Fetcd", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/etcd:3.5.7-0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + }, + { + "bom-ref": "pkg:oci/kube-apiserver@sha256%3A697cd88d94f7f2ef42144cb3072b016dcb2e9251f0e7d41a7fede557e555452d?repository_url=registry.k8s.io%2Fkube-apiserver", + "type": "container", + "name": "registry.k8s.io/kube-apiserver", + "version": "sha256:697cd88d94f7f2ef42144cb3072b016dcb2e9251f0e7d41a7fede557e555452d", + "purl": "pkg:oci/kube-apiserver@sha256%3A697cd88d94f7f2ef42144cb3072b016dcb2e9251f0e7d41a7fede557e555452d?repository_url=registry.k8s.io%2Fkube-apiserver", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/kube-apiserver:1.27.0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + }, + { + "bom-ref": "pkg:oci/kube-controller-manager@sha256%3A6286e500782ad6d0b37a1b8be57fc73f597dc931dfc73ff18ce534059803b265?repository_url=registry.k8s.io%2Fkube-controller-manager", + "type": "container", + "name": "registry.k8s.io/kube-controller-manager", + "version": "sha256:6286e500782ad6d0b37a1b8be57fc73f597dc931dfc73ff18ce534059803b265", + "purl": "pkg:oci/kube-controller-manager@sha256%3A6286e500782ad6d0b37a1b8be57fc73f597dc931dfc73ff18ce534059803b265?repository_url=registry.k8s.io%2Fkube-controller-manager", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/kube-controller-manager:1.27.0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + }, + { + "bom-ref": "pkg:oci/kube-proxy@sha256%3A4bcb707da9898d2625f5d4edc6d0c96519a24f16db914fc673aa8f97e41dbabf?repository_url=registry.k8s.io%2Fkube-proxy", + "type": "container", + "name": "registry.k8s.io/kube-proxy", + "version": "sha256:4bcb707da9898d2625f5d4edc6d0c96519a24f16db914fc673aa8f97e41dbabf", + "purl": "pkg:oci/kube-proxy@sha256%3A4bcb707da9898d2625f5d4edc6d0c96519a24f16db914fc673aa8f97e41dbabf?repository_url=registry.k8s.io%2Fkube-proxy", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/kube-proxy:1.27.0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + }, + { + "bom-ref": "pkg:oci/kube-scheduler@sha256%3A5897d7a97d23dce25cbf36fcd6e919180a8ef904bf5156583ffdb6a733ab04af?repository_url=registry.k8s.io%2Fkube-scheduler", + "type": "container", + "name": "registry.k8s.io/kube-scheduler", + "version": "sha256:5897d7a97d23dce25cbf36fcd6e919180a8ef904bf5156583ffdb6a733ab04af", + "purl": "pkg:oci/kube-scheduler@sha256%3A5897d7a97d23dce25cbf36fcd6e919180a8ef904bf5156583ffdb6a733ab04af?repository_url=registry.k8s.io%2Fkube-scheduler", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/kube-scheduler:1.27.0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + } + ], + "dependencies": [ + { + "ref": "5262e708-f1a3-4fca-a1c3-0a8384f7f4a5", + "dependsOn": [] + }, + { + "ref": "a62abb1f-cb38-4fde-90f3-2bda3b87ddb2", + "dependsOn": [ + "pkg:golang/docker@24.0.4", + "pkg:k8s/k8s.io%2Fkubelet@1.27.0" + ] + }, + { + "ref": "a6350ac3-52f6-4c5f-a3e3-184b9a634bef", + "dependsOn": [ + "5262e708-f1a3-4fca-a1c3-0a8384f7f4a5", + "a62abb1f-cb38-4fde-90f3-2bda3b87ddb2" + ] + }, + { + "ref": "b19a88a3-017d-4e70-a73a-75f48696ec0f", + "dependsOn": [ + "pkg:oci/coredns@sha256%3Aa0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e?repository_url=registry.k8s.io%2Fcoredns%2Fcoredns" + ] + }, + { + "ref": "b1c502c9-3c6e-43af-822b-1cb55c6c6ff3", + "dependsOn": [ + "pkg:oci/etcd@sha256%3A51eae8381dcb1078289fa7b4f3df2630cdc18d09fb56f8e56b41c40e191d6c83?repository_url=registry.k8s.io%2Fetcd" + ] + }, + { + "ref": "pkg:golang/docker@24.0.4", + "dependsOn": [] + }, + { + "ref": "pkg:k8s/k8s.io%2Fapiserver@1.27.0", + "dependsOn": [ + "pkg:oci/kube-apiserver@sha256%3A697cd88d94f7f2ef42144cb3072b016dcb2e9251f0e7d41a7fede557e555452d?repository_url=registry.k8s.io%2Fkube-apiserver" + ] + }, + { + "ref": "pkg:k8s/k8s.io%2Fcontroller-manager@1.27.0", + "dependsOn": [ + "pkg:oci/kube-controller-manager@sha256%3A6286e500782ad6d0b37a1b8be57fc73f597dc931dfc73ff18ce534059803b265?repository_url=registry.k8s.io%2Fkube-controller-manager" + ] + }, + { + "ref": "pkg:k8s/k8s.io%2Fkube-proxy@1.27.0", + "dependsOn": [ + "pkg:oci/kube-proxy@sha256%3A4bcb707da9898d2625f5d4edc6d0c96519a24f16db914fc673aa8f97e41dbabf?repository_url=registry.k8s.io%2Fkube-proxy" + ] + }, + { + "ref": "pkg:k8s/k8s.io%2Fkube-scheduler@1.27.0", + "dependsOn": [ + "pkg:oci/kube-scheduler@sha256%3A5897d7a97d23dce25cbf36fcd6e919180a8ef904bf5156583ffdb6a733ab04af?repository_url=registry.k8s.io%2Fkube-scheduler" + ] + }, + { + "ref": "pkg:k8s/k8s.io%2Fkubelet@1.27.0", + "dependsOn": [] + }, + { + "ref": "pkg:k8s/k8s.io%2Fkubernetes@1.27.0", + "dependsOn": [ + "a6350ac3-52f6-4c5f-a3e3-184b9a634bef", + "b19a88a3-017d-4e70-a73a-75f48696ec0f", + "b1c502c9-3c6e-43af-822b-1cb55c6c6ff3", + "pkg:k8s/k8s.io%2Fapiserver@1.27.0", + "pkg:k8s/k8s.io%2Fcontroller-manager@1.27.0", + "pkg:k8s/k8s.io%2Fkube-proxy@1.27.0", + "pkg:k8s/k8s.io%2Fkube-scheduler@1.27.0" + ] + }, + { + "ref": "pkg:oci/coredns@sha256%3Aa0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e?repository_url=registry.k8s.io%2Fcoredns%2Fcoredns", + "dependsOn": [] + }, + { + "ref": "pkg:oci/etcd@sha256%3A51eae8381dcb1078289fa7b4f3df2630cdc18d09fb56f8e56b41c40e191d6c83?repository_url=registry.k8s.io%2Fetcd", + "dependsOn": [] + }, + { + "ref": "pkg:oci/kube-apiserver@sha256%3A697cd88d94f7f2ef42144cb3072b016dcb2e9251f0e7d41a7fede557e555452d?repository_url=registry.k8s.io%2Fkube-apiserver", + "dependsOn": [] + }, + { + "ref": "pkg:oci/kube-controller-manager@sha256%3A6286e500782ad6d0b37a1b8be57fc73f597dc931dfc73ff18ce534059803b265?repository_url=registry.k8s.io%2Fkube-controller-manager", + "dependsOn": [] + }, + { + "ref": "pkg:oci/kube-proxy@sha256%3A4bcb707da9898d2625f5d4edc6d0c96519a24f16db914fc673aa8f97e41dbabf?repository_url=registry.k8s.io%2Fkube-proxy", + "dependsOn": [] + }, + { + "ref": "pkg:oci/kube-scheduler@sha256%3A5897d7a97d23dce25cbf36fcd6e919180a8ef904bf5156583ffdb6a733ab04af?repository_url=registry.k8s.io%2Fkube-scheduler", + "dependsOn": [] + } + ], + "vulnerabilities": [] +} diff --git a/integration/testdata/fluentd-gems.json.golden b/integration/testdata/fluentd-gems.json.golden new file mode 100644 index 000000000000..2072e0c4bf8d --- /dev/null +++ b/integration/testdata/fluentd-gems.json.golden @@ -0,0 +1,249 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "debian", + "Name": "10.2" + }, + "ImageID": "sha256:5a992077baba51b97f27591a10d54d2f2723dc9c81a3fe419e261023f2554933", + "DiffIDs": [ + "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f", + "sha256:02874b2b269dea8dde0f7edb4c9906904dfe38a09de1a214f20c650cfb15c60e", + "sha256:3752e1f6fd759c795c13aff2c93c081529366e27635ba6621e849b0f9cfc77f0", + "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9", + "sha256:788c00e2cfc8f2a018ae4344ccf0b2c226ebd756d7effd1ce50eea1a4252cd89", + "sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "232f3fc7ddffd71dc3ff52c6c0c3a5feea2f51acffd9b53850a8fc6f1a15319a", + "created": "2020-03-04T13:59:39.161374106Z", + "docker_version": "19.03.4", + "history": [ + { + "created": "2019-11-22T14:55:09.912242636Z", + "created_by": "/bin/sh -c #(nop) ADD file:bc8179c87c8dbb3d962bed1801f99e7c860ff03797cde6ad19b107d43b973ada in / " + }, + { + "created": "2019-11-22T14:55:10.253859615Z", + "created_by": "/bin/sh -c #(nop) CMD [\"bash\"]", + "empty_layer": true + }, + { + "created": "2020-03-04T13:58:17.973854594Z", + "created_by": "/bin/sh -c #(nop) ARG DEBIAN_FRONTEND=noninteractive", + "empty_layer": true + }, + { + "created": "2020-03-04T13:58:18.12120844Z", + "created_by": "/bin/sh -c #(nop) COPY file:4e7fdb1bc31a0f689d88f6af28d4f0352e89a2ac598c523e9637da3de75bfada in /tmp/install.sh " + }, + { + "created": "2020-03-04T13:58:18.26894021Z", + "created_by": "/bin/sh -c #(nop) COPY file:c03560fcb4f0aff4cecd93039c348ba4992564740c77e3d6049a44fe79ca44ab in /Gemfile " + }, + { + "created": "2020-03-04T13:59:37.96119583Z", + "created_by": "|1 DEBIAN_FRONTEND=noninteractive /bin/sh -c chmod +x /tmp/install.sh \u0026\u0026 /bin/bash -l -c /tmp/install.sh \u0026\u0026 rm /tmp/*" + }, + { + "created": "2020-03-04T13:59:38.583719926Z", + "created_by": "/bin/sh -c #(nop) COPY file:f742fdea941d5baccbf9a9c45ccc9cd943377f3c3e07da787a8d8d9f92a8b3d3 in /etc/fluent/fluent.conf " + }, + { + "created": "2020-03-04T13:59:38.72131564Z", + "created_by": "/bin/sh -c #(nop) COPY file:a9ce963551c165ec55bb4d982d96336caa97e8c70011eb4ca58927956bd08e2a in /run.sh " + }, + { + "created": "2020-03-04T13:59:38.844116271Z", + "created_by": "/bin/sh -c #(nop) EXPOSE 80", + "empty_layer": true + }, + { + "created": "2020-03-04T13:59:38.99446051Z", + "created_by": "/bin/sh -c #(nop) ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2", + "empty_layer": true + }, + { + "created": "2020-03-04T13:59:39.161374106Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/run.sh\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f", + "sha256:02874b2b269dea8dde0f7edb4c9906904dfe38a09de1a214f20c650cfb15c60e", + "sha256:3752e1f6fd759c795c13aff2c93c081529366e27635ba6621e849b0f9cfc77f0", + "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9", + "sha256:788c00e2cfc8f2a018ae4344ccf0b2c226ebd756d7effd1ce50eea1a4252cd89", + "sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065" + ] + }, + "config": { + "Cmd": [ + "/run.sh" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" + ], + "Image": "sha256:2a538358cddc4824e9eff1531e0c63ae5e3cda85d2984c647df9b1c816b9b86b", + "ExposedPorts": { + "80/tcp": {} + } + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz (debian 10.2)", + "Class": "os-pkgs", + "Type": "debian", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18224", + "VendorIDs": [ + "DSA-4613-1" + ], + "PkgID": "libidn2-0@2.0.5-1", + "PkgName": "libidn2-0", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.2" + }, + "InstalledVersion": "2.0.5-1", + "FixedVersion": "2.0.5-1+deb10u1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c", + "DiffID": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18224", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "libidn2: heap-based buffer overflow in idn2_to_ascii_4i in lib/lookup.c", + "Description": "idn2_to_ascii_4i in lib/lookup.c in GNU libidn2 before 2.1.1 has a heap-based buffer overflow via a long domain string.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 4, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "V3Score": 5.6 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00008.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00009.html", + "https://access.redhat.com/security/cve/CVE-2019-18224", + "https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=12420", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18224", + "https://github.com/libidn/libidn2/commit/e4d1558aa2c1c04a05066ee8600f37603890ba8c", + "https://github.com/libidn/libidn2/compare/libidn2-2.1.0...libidn2-2.1.1", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JDQVQ2XPV5BTZUFINT7AFJSKNNBVURNJ/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MINU5RKDFE6TKAFY5DRFN3WSFDS4DYVS/", + "https://seclists.org/bugtraq/2020/Feb/4", + "https://security.gentoo.org/glsa/202003-63", + "https://ubuntu.com/security/notices/USN-4168-1", + "https://usn.ubuntu.com/4168-1/", + "https://www.debian.org/security/2020/dsa-4613" + ], + "PublishedDate": "2019-10-21T17:15:00Z", + "LastModifiedDate": "2019-10-29T19:15:00Z" + } + ] + }, + { + "Target": "Ruby", + "Class": "lang-pkgs", + "Type": "gemspec", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-8165", + "PkgName": "activesupport", + "PkgPath": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", + "PkgIdentifier": { + "PURL": "pkg:gem/activesupport@6.0.2.1" + }, + "InstalledVersion": "6.0.2.1", + "FixedVersion": "6.0.3.1, 5.2.4.3", + "Status": "fixed", + "Layer": { + "Digest": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602", + "DiffID": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-8165", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory RubyGems", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Arubygems" + }, + "Title": "rubygem-activesupport: potentially unintended unmarshalling of user-provided objects in MemCacheStore and RedisCacheStore", + "Description": "A deserialization of untrusted data vulnerability exists in rails \u003c 5.2.4.3, rails \u003c 6.0.3.1 that can allow an attacker to unmarshal user-provided objects in MemCacheStore and RedisCacheStore potentially resulting in an RCE.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-502" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 4, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 9.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00031.html", + "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00034.html", + "https://access.redhat.com/security/cve/CVE-2020-8165", + "https://github.com/advisories/GHSA-2p68-f74v-9wc6", + "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/activesupport/CVE-2020-8165.yml", + "https://groups.google.com/forum/#!msg/rubyonrails-security/bv6fW4S0Y1c/KnkEqM7AAQAJ", + "https://groups.google.com/forum/#!topic/rubyonrails-security/bv6fW4S0Y1c", + "https://groups.google.com/g/rubyonrails-security/c/bv6fW4S0Y1c", + "https://hackerone.com/reports/413388", + "https://lists.debian.org/debian-lts-announce/2020/06/msg00022.html", + "https://lists.debian.org/debian-lts-announce/2020/07/msg00013.html", + "https://nvd.nist.gov/vuln/detail/CVE-2020-8165", + "https://weblog.rubyonrails.org/2020/5/18/Rails-5-2-4-3-and-6-0-3-1-have-been-released/", + "https://www.debian.org/security/2020/dsa-4766" + ], + "PublishedDate": "2020-06-19T18:15:00Z", + "LastModifiedDate": "2020-10-17T12:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden new file mode 100644 index 000000000000..934bda200639 --- /dev/null +++ b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden @@ -0,0 +1,8568 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000163", + "version": 1, + "metadata": { + "timestamp": "2021-08-25T12:20:30+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000001", + "type": "container", + "name": "testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz", + "properties": [ + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:02874b2b269dea8dde0f7edb4c9906904dfe38a09de1a214f20c650cfb15c60e" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:3752e1f6fd759c795c13aff2c93c081529366e27635ba6621e849b0f9cfc77f0" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:788c00e2cfc8f2a018ae4344ccf0b2c226ebd756d7effd1ce50eea1a4252cd89" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:5a992077baba51b97f27591a10d54d2f2723dc9c81a3fe419e261023f2554933" + }, + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000002", + "type": "operating-system", + "name": "debian", + "version": "10.2", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "debian" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/adduser@3.118?arch=all&distro=debian-10.2", + "type": "library", + "name": "adduser", + "version": "3.118", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/adduser@3.118?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "adduser@3.118" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "adduser" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.118" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/apt@1.8.2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "apt", + "version": "1.8.2", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/apt@1.8.2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "apt@1.8.2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "apt" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.8.2" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/base-files@10.3%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "base-files", + "version": "10.3+deb10u2", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/base-files@10.3%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "base-files@10.3+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "base-files" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "10.3+deb10u2" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/base-passwd@3.5.46?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "base-passwd", + "version": "3.5.46", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "PD" + } + } + ], + "purl": "pkg:deb/debian/base-passwd@3.5.46?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "base-passwd@3.5.46" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "base-passwd" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.5.46" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/bash@5.0-4?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "bash", + "version": "5.0-4", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/bash@5.0-4?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "bash@5.0-4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "bash" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "4" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "5.0" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/bsdutils@2.33.1-0.1?arch=amd64&distro=debian-10.2&epoch=1", + "type": "library", + "name": "bsdutils", + "version": "1:2.33.1-0.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "public-domain" + } + }, + { + "license": { + "name": "BSD-4-Clause" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/bsdutils@2.33.1-0.1?arch=amd64&distro=debian-10.2&epoch=1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "bsdutils@1:2.33.1-0.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "util-linux" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.33.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ca-certificates@20190110?arch=all&distro=debian-10.2", + "type": "library", + "name": "ca-certificates", + "version": "20190110", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "MPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/ca-certificates@20190110?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ca-certificates@20190110" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ca-certificates" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "20190110" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/coreutils@8.30-3?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "coreutils", + "version": "8.30-3", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/coreutils@8.30-3?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "coreutils@8.30-3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "coreutils" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "8.30" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/dash@0.5.10.2-5?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "dash", + "version": "0.5.10.2-5", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/dash@0.5.10.2-5?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "dash@0.5.10.2-5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "dash" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "5" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "0.5.10.2" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2", + "type": "library", + "name": "debconf", + "version": "1.5.71", + "licenses": [ + { + "license": { + "name": "BSD-2-Clause" + } + } + ], + "purl": "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "debconf@1.5.71" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "debconf" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.5.71" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/debian-archive-keyring@2019.1?arch=all&distro=debian-10.2", + "type": "library", + "name": "debian-archive-keyring", + "version": "2019.1", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/debian-archive-keyring@2019.1?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "debian-archive-keyring@2019.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "debian-archive-keyring" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2019.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/debianutils@4.8.6.1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "debianutils", + "version": "4.8.6.1", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/debianutils@4.8.6.1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "debianutils@4.8.6.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "debianutils" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "4.8.6.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/diffutils@3.7-3?arch=amd64&distro=debian-10.2&epoch=1", + "type": "library", + "name": "diffutils", + "version": "1:3.7-3", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GFDL" + } + } + ], + "purl": "pkg:deb/debian/diffutils@3.7-3?arch=amd64&distro=debian-10.2&epoch=1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "diffutils@1:3.7-3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "diffutils" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.7" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/dpkg@1.19.7?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "dpkg", + "version": "1.19.7", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "public-domain-s-s-d" + } + }, + { + "license": { + "name": "public-domain-md5" + } + } + ], + "purl": "pkg:deb/debian/dpkg@1.19.7?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "dpkg@1.19.7" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "dpkg" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.19.7" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/e2fsprogs@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "e2fsprogs", + "version": "1.44.5-1+deb10u2", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/e2fsprogs@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "e2fsprogs@1.44.5-1+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "e2fsprogs" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.44.5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/fdisk@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "fdisk", + "version": "2.33.1-0.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "public-domain" + } + }, + { + "license": { + "name": "BSD-4-Clause" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/fdisk@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "fdisk@2.33.1-0.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "util-linux" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.33.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/findutils@4.6.0%2Bgit%2B20190209-2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "findutils", + "version": "4.6.0+git+20190209-2", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GFDL-1.3" + } + } + ], + "purl": "pkg:deb/debian/findutils@4.6.0%2Bgit%2B20190209-2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "findutils@4.6.0+git+20190209-2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "findutils" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "4.6.0+git+20190209" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/gcc-8-base@8.3.0-6?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "gcc-8-base", + "version": "8.3.0-6", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GFDL-1.2" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "Artistic" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/gcc-8-base@8.3.0-6?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "gcc-8-base@8.3.0-6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "gcc-8" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "6" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "8.3.0" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/gpgv@2.2.12-1%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "gpgv", + "version": "2.2.12-1+deb10u1", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "permissive" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "Expat" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "RFC-Reference" + } + }, + { + "license": { + "name": "TinySCHEME" + } + }, + { + "license": { + "name": "CC0-1.0" + } + } + ], + "purl": "pkg:deb/debian/gpgv@2.2.12-1%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "gpgv@2.2.12-1+deb10u1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "gnupg2" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1+deb10u1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.2.12" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/grep@3.3-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "grep", + "version": "3.3-1", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/grep@3.3-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "grep@3.3-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "grep" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.3" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/gzip@1.9-3?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "gzip", + "version": "1.9-3", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/gzip@1.9-3?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "gzip@1.9-3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "gzip" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.9" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/hostname@3.21?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "hostname", + "version": "3.21", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/hostname@3.21?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "hostname@3.21" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "hostname" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.21" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/init-system-helpers@1.56%2Bnmu1?arch=all&distro=debian-10.2", + "type": "library", + "name": "init-system-helpers", + "version": "1.56+nmu1", + "licenses": [ + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/init-system-helpers@1.56%2Bnmu1?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "init-system-helpers@1.56+nmu1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "init-system-helpers" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.56+nmu1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libacl1@2.2.53-4?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libacl1", + "version": "2.2.53-4", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + } + ], + "purl": "pkg:deb/debian/libacl1@2.2.53-4?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libacl1@2.2.53-4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "acl" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "4" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.2.53" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libapt-pkg5.0@1.8.2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libapt-pkg5.0", + "version": "1.8.2", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/libapt-pkg5.0@1.8.2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libapt-pkg5.0@1.8.2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "apt" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.8.2" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libattr1@2.4.48-4?arch=amd64&distro=debian-10.2&epoch=1", + "type": "library", + "name": "libattr1", + "version": "1:2.4.48-4", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + } + ], + "purl": "pkg:deb/debian/libattr1@2.4.48-4?arch=amd64&distro=debian-10.2&epoch=1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libattr1@1:2.4.48-4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "attr" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "4" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.4.48" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libaudit-common@2.8.4-3?arch=all&distro=debian-10.2&epoch=1", + "type": "library", + "name": "libaudit-common", + "version": "1:2.8.4-3", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-1.0" + } + } + ], + "purl": "pkg:deb/debian/libaudit-common@2.8.4-3?arch=all&distro=debian-10.2&epoch=1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libaudit-common@1:2.8.4-3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "audit" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.8.4" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libaudit1@2.8.4-3?arch=amd64&distro=debian-10.2&epoch=1", + "type": "library", + "name": "libaudit1", + "version": "1:2.8.4-3", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-1.0" + } + } + ], + "purl": "pkg:deb/debian/libaudit1@2.8.4-3?arch=amd64&distro=debian-10.2&epoch=1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libaudit1@1:2.8.4-3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "audit" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.8.4" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libblkid1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libblkid1", + "version": "2.33.1-0.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "public-domain" + } + }, + { + "license": { + "name": "BSD-4-Clause" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libblkid1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libblkid1@2.33.1-0.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "util-linux" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.33.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libbz2-1.0@1.0.6-9.2~deb10u1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libbz2-1.0", + "version": "1.0.6-9.2~deb10u1", + "licenses": [ + { + "license": { + "name": "BSD-variant" + } + }, + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/libbz2-1.0@1.0.6-9.2~deb10u1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libbz2-1.0@1.0.6-9.2~deb10u1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "bzip2" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "9.2~deb10u1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.0.6" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libc-bin@2.28-10?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libc-bin", + "version": "2.28-10", + "licenses": [ + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/libc-bin@2.28-10?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libc-bin@2.28-10" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "glibc" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "10" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.28" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libc6", + "version": "2.28-10", + "licenses": [ + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libc6@2.28-10" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "glibc" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "10" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.28" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libcap-ng0@0.7.9-2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libcap-ng0", + "version": "0.7.9-2", + "licenses": [ + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libcap-ng0@0.7.9-2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libcap-ng0@0.7.9-2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libcap-ng" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "0.7.9" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libcom-err2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libcom-err2", + "version": "1.44.5-1+deb10u2", + "purl": "pkg:deb/debian/libcom-err2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libcom-err2@1.44.5-1+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "e2fsprogs" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.44.5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libdb5.3@5.3.28%2Bdfsg1-0.5?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libdb5.3", + "version": "5.3.28+dfsg1-0.5", + "purl": "pkg:deb/debian/libdb5.3@5.3.28%2Bdfsg1-0.5?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libdb5.3@5.3.28+dfsg1-0.5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "db5.3" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0.5" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "5.3.28+dfsg1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libdebconfclient0@0.249?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libdebconfclient0", + "version": "0.249", + "purl": "pkg:deb/debian/libdebconfclient0@0.249?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libdebconfclient0@0.249" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "cdebconf" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "0.249" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libext2fs2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libext2fs2", + "version": "1.44.5-1+deb10u2", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/libext2fs2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libext2fs2@1.44.5-1+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "e2fsprogs" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.44.5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libfdisk1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libfdisk1", + "version": "2.33.1-0.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "public-domain" + } + }, + { + "license": { + "name": "BSD-4-Clause" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libfdisk1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libfdisk1@2.33.1-0.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "util-linux" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.33.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libffi6@3.2.1-9?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libffi6", + "version": "3.2.1-9", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libffi6@3.2.1-9?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libffi6@3.2.1-9" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libffi" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "9" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.2.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libgcc1@8.3.0-6?arch=amd64&distro=debian-10.2&epoch=1", + "type": "library", + "name": "libgcc1", + "version": "1:8.3.0-6", + "purl": "pkg:deb/debian/libgcc1@8.3.0-6?arch=amd64&distro=debian-10.2&epoch=1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libgcc1@1:8.3.0-6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "gcc-8" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "6" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "8.3.0" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libgcrypt20@1.8.4-5?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libgcrypt20", + "version": "1.8.4-5", + "licenses": [ + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/libgcrypt20@1.8.4-5?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libgcrypt20@1.8.4-5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libgcrypt20" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "5" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.8.4" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libgdbm-compat4@1.18.1-4?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libgdbm-compat4", + "version": "1.18.1-4", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "GFDL-NIV-1.3+" + } + } + ], + "purl": "pkg:deb/debian/libgdbm-compat4@1.18.1-4?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libgdbm-compat4@1.18.1-4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "gdbm" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "4" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.18.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libgdbm6@1.18.1-4?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libgdbm6", + "version": "1.18.1-4", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "GFDL-NIV-1.3+" + } + } + ], + "purl": "pkg:deb/debian/libgdbm6@1.18.1-4?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libgdbm6@1.18.1-4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "gdbm" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "4" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.18.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libgmp10@6.1.2%2Bdfsg-4?arch=amd64&distro=debian-10.2&epoch=2", + "type": "library", + "name": "libgmp10", + "version": "2:6.1.2+dfsg-4", + "licenses": [ + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libgmp10@6.1.2%2Bdfsg-4?arch=amd64&distro=debian-10.2&epoch=2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libgmp10@2:6.1.2+dfsg-4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "2" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "gmp" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "4" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "6.1.2+dfsg" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libgnutls30@3.6.7-4?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libgnutls30", + "version": "3.6.7-4", + "licenses": [ + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GFDL-1.3" + } + }, + { + "license": { + "name": "CC0" + } + }, + { + "license": { + "name": "The MIT License" + } + }, + { + "license": { + "name": "LGPLv3+" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:deb/debian/libgnutls30@3.6.7-4?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libgnutls30@3.6.7-4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "gnutls28" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "4" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.6.7" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libgpg-error0@1.35-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libgpg-error0", + "version": "1.35-1", + "licenses": [ + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "g10-permissive" + } + }, + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libgpg-error0@1.35-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libgpg-error0@1.35-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libgpg-error" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.35" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libhogweed4@3.4.1-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libhogweed4", + "version": "3.4.1-1", + "purl": "pkg:deb/debian/libhogweed4@3.4.1-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libhogweed4@3.4.1-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "nettle" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.4.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libidn2-0", + "version": "2.0.5-1", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "Unicode" + } + } + ], + "purl": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libidn2-0@2.0.5-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libidn2" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.0.5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libjemalloc2@5.1.0-3?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libjemalloc2", + "version": "5.1.0-3", + "licenses": [ + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-2-Clause-Chemeris" + } + }, + { + "license": { + "name": "BSD-3-Clause-Google" + } + }, + { + "license": { + "name": "Expat" + } + }, + { + "license": { + "name": "BSD-3-Clause-Hiroshima-University" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + } + ], + "purl": "pkg:deb/debian/libjemalloc2@5.1.0-3?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libjemalloc2@5.1.0-3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "jemalloc" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "5.1.0" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/liblz4-1@1.8.3-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "liblz4-1", + "version": "1.8.3-1", + "licenses": [ + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/liblz4-1@1.8.3-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "liblz4-1@1.8.3-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "lz4" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.8.3" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/liblzma5@5.2.4-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "liblzma5", + "version": "5.2.4-1", + "licenses": [ + { + "license": { + "name": "PD" + } + }, + { + "license": { + "name": "probably-PD" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "permissive-fsf" + } + }, + { + "license": { + "name": "Autoconf" + } + }, + { + "license": { + "name": "permissive-nowarranty" + } + }, + { + "license": { + "name": "none" + } + }, + { + "license": { + "name": "config-h" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "noderivs" + } + }, + { + "license": { + "name": "PD-debian" + } + }, + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/liblzma5@5.2.4-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "liblzma5@5.2.4-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "xz-utils" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "5.2.4" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libmount1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libmount1", + "version": "2.33.1-0.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "public-domain" + } + }, + { + "license": { + "name": "BSD-4-Clause" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libmount1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libmount1@2.33.1-0.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "util-linux" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.33.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libncurses6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libncurses6", + "version": "6.1+20181013-2+deb10u2", + "purl": "pkg:deb/debian/libncurses6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libncurses6@6.1+20181013-2+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ncurses" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "6.1+20181013" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libncursesw6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libncursesw6", + "version": "6.1+20181013-2+deb10u2", + "purl": "pkg:deb/debian/libncursesw6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libncursesw6@6.1+20181013-2+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ncurses" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "6.1+20181013" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libnettle6@3.4.1-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libnettle6", + "version": "3.4.1-1", + "licenses": [ + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "other" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "GPL-2.0-with-autoconf-exception" + } + }, + { + "license": { + "name": "public-domain" + } + }, + { + "license": { + "name": "GAP" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libnettle6@3.4.1-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libnettle6@3.4.1-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "nettle" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.4.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libp11-kit0@0.23.15-2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libp11-kit0", + "version": "0.23.15-2", + "licenses": [ + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "permissive-like-automake-output" + } + }, + { + "license": { + "name": "ISC" + } + }, + { + "license": { + "name": "ISC+IBM" + } + }, + { + "license": { + "name": "same-as-rest-of-p11kit" + } + } + ], + "purl": "pkg:deb/debian/libp11-kit0@0.23.15-2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libp11-kit0@0.23.15-2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "p11-kit" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "0.23.15" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libpam-modules-bin@1.3.1-5?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libpam-modules-bin", + "version": "1.3.1-5", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libpam-modules-bin@1.3.1-5?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libpam-modules-bin@1.3.1-5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "pam" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "5" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.3.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libpam-modules@1.3.1-5?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libpam-modules", + "version": "1.3.1-5", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libpam-modules@1.3.1-5?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libpam-modules@1.3.1-5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "pam" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "5" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.3.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libpam-runtime@1.3.1-5?arch=all&distro=debian-10.2", + "type": "library", + "name": "libpam-runtime", + "version": "1.3.1-5", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libpam-runtime@1.3.1-5?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libpam-runtime@1.3.1-5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "pam" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "5" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.3.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libpam0g@1.3.1-5?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libpam0g", + "version": "1.3.1-5", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libpam0g@1.3.1-5?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libpam0g@1.3.1-5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "pam" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "5" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.3.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libpcre3@8.39-12?arch=amd64&distro=debian-10.2&epoch=2", + "type": "library", + "name": "libpcre3", + "version": "2:8.39-12", + "purl": "pkg:deb/debian/libpcre3@8.39-12?arch=amd64&distro=debian-10.2&epoch=2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libpcre3@2:8.39-12" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "2" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "pcre3" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "12" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "8.39" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libreadline7@7.0-5?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libreadline7", + "version": "7.0-5", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GFDL" + } + } + ], + "purl": "pkg:deb/debian/libreadline7@7.0-5?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libreadline7@7.0-5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "readline" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "5" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "7.0" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libruby2.5", + "version": "2.5.5-3+deb10u1", + "licenses": [ + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "Ruby" + } + }, + { + "license": { + "name": "Expat" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "SIL-1.1" + } + }, + { + "license": { + "name": "CC-BY-3.0-famfamfam" + } + }, + { + "license": { + "name": "PreserveNotice" + } + }, + { + "license": { + "name": "3C-BSD" + } + }, + { + "license": { + "name": "PublicDomain" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "AllPermissions" + } + }, + { + "license": { + "name": "PartialGplArtisticAndRuby" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "Artistic" + } + }, + { + "license": { + "name": "zlib/libpng" + } + }, + { + "license": { + "name": "GPL-1.0" + } + }, + { + "license": { + "name": "CC0" + } + }, + { + "license": { + "name": "Unicode" + } + }, + { + "license": { + "name": "Permissive" + } + } + ], + "purl": "pkg:deb/debian/libruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libruby2.5@2.5.5-3+deb10u1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ruby2.5" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3+deb10u1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.5.5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libseccomp2@2.3.3-4?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libseccomp2", + "version": "2.3.3-4", + "licenses": [ + { + "license": { + "name": "LGPL-2.1" + } + } + ], + "purl": "pkg:deb/debian/libseccomp2@2.3.3-4?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libseccomp2@2.3.3-4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libseccomp" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "4" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.3.3" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libselinux1@2.8-1%2Bb1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libselinux1", + "version": "2.8-1+b1", + "licenses": [ + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/libselinux1@2.8-1%2Bb1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libselinux1@2.8-1+b1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libselinux" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.8" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libsemanage-common@2.8-2?arch=all&distro=debian-10.2", + "type": "library", + "name": "libsemanage-common", + "version": "2.8-2", + "licenses": [ + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libsemanage-common@2.8-2?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libsemanage-common@2.8-2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libsemanage" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.8" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libsemanage1@2.8-2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libsemanage1", + "version": "2.8-2", + "licenses": [ + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libsemanage1@2.8-2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libsemanage1@2.8-2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libsemanage" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.8" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libsepol1@2.8-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libsepol1", + "version": "2.8-1", + "licenses": [ + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libsepol1@2.8-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libsepol1@2.8-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libsepol" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.8" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libsmartcols1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libsmartcols1", + "version": "2.33.1-0.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "public-domain" + } + }, + { + "license": { + "name": "BSD-4-Clause" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libsmartcols1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libsmartcols1@2.33.1-0.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "util-linux" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.33.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libss2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libss2", + "version": "1.44.5-1+deb10u2", + "purl": "pkg:deb/debian/libss2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libss2@1.44.5-1+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "e2fsprogs" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.44.5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libssl1.1@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libssl1.1", + "version": "1.1.1d-0+deb10u2", + "purl": "pkg:deb/debian/libssl1.1@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libssl1.1@1.1.1d-0+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "openssl" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.1.1d" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libstdc%2B%2B6@8.3.0-6?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libstdc++6", + "version": "8.3.0-6", + "purl": "pkg:deb/debian/libstdc%2B%2B6@8.3.0-6?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libstdc++6@8.3.0-6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "gcc-8" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "6" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "8.3.0" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libsystemd0@241-7~deb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libsystemd0", + "version": "241-7~deb10u2", + "licenses": [ + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "CC0-1.0" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "Expat" + } + }, + { + "license": { + "name": "public-domain" + } + } + ], + "purl": "pkg:deb/debian/libsystemd0@241-7~deb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libsystemd0@241-7~deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "systemd" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "7~deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "241" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libtasn1-6@4.13-3?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libtasn1-6", + "version": "4.13-3", + "licenses": [ + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GFDL-1.3" + } + } + ], + "purl": "pkg:deb/debian/libtasn1-6@4.13-3?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libtasn1-6@4.13-3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libtasn1-6" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "4.13" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libtinfo6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libtinfo6", + "version": "6.1+20181013-2+deb10u2", + "purl": "pkg:deb/debian/libtinfo6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libtinfo6@6.1+20181013-2+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ncurses" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "6.1+20181013" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libudev1@241-7~deb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libudev1", + "version": "241-7~deb10u2", + "licenses": [ + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "CC0-1.0" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "Expat" + } + }, + { + "license": { + "name": "public-domain" + } + } + ], + "purl": "pkg:deb/debian/libudev1@241-7~deb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libudev1@241-7~deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "systemd" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "7~deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "241" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libunistring2@0.9.10-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libunistring2", + "version": "0.9.10-1", + "licenses": [ + { + "license": { + "name": "LGPL-3.0" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "FreeSoftware" + } + }, + { + "license": { + "name": "GPL-2+ with distribution exception" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GFDL-1.2+" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "GFDL-1.2" + } + } + ], + "purl": "pkg:deb/debian/libunistring2@0.9.10-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libunistring2@0.9.10-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libunistring" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "0.9.10" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libuuid1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libuuid1", + "version": "2.33.1-0.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "public-domain" + } + }, + { + "license": { + "name": "BSD-4-Clause" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/libuuid1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libuuid1@2.33.1-0.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "util-linux" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.33.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libyaml-0-2@0.2.1-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libyaml-0-2", + "version": "0.2.1-1", + "licenses": [ + { + "license": { + "name": "Expat" + } + }, + { + "license": { + "name": "permissive" + } + } + ], + "purl": "pkg:deb/debian/libyaml-0-2@0.2.1-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libyaml-0-2@0.2.1-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libyaml" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "0.2.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libzstd1@1.3.8%2Bdfsg-3?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "libzstd1", + "version": "1.3.8+dfsg-3", + "licenses": [ + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "Zlib" + } + }, + { + "license": { + "name": "Expat" + } + } + ], + "purl": "pkg:deb/debian/libzstd1@1.3.8%2Bdfsg-3?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "libzstd1@1.3.8+dfsg-3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libzstd" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.3.8+dfsg" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/login@4.5-1.1?arch=amd64&distro=debian-10.2&epoch=1", + "type": "library", + "name": "login", + "version": "1:4.5-1.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/login@4.5-1.1?arch=amd64&distro=debian-10.2&epoch=1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "login@1:4.5-1.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "shadow" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "4.5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/mawk@1.3.3-17%2Bb3?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "mawk", + "version": "1.3.3-17+b3", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/mawk@1.3.3-17%2Bb3?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "mawk@1.3.3-17+b3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "mawk" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "17" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.3.3" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/mount@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "mount", + "version": "2.33.1-0.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "public-domain" + } + }, + { + "license": { + "name": "BSD-4-Clause" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/mount@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "mount@2.33.1-0.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "util-linux" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.33.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ncurses-base@6.1%2B20181013-2%2Bdeb10u2?arch=all&distro=debian-10.2", + "type": "library", + "name": "ncurses-base", + "version": "6.1+20181013-2+deb10u2", + "purl": "pkg:deb/debian/ncurses-base@6.1%2B20181013-2%2Bdeb10u2?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ncurses-base@6.1+20181013-2+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ncurses" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "6.1+20181013" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ncurses-bin@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "ncurses-bin", + "version": "6.1+20181013-2+deb10u2", + "purl": "pkg:deb/debian/ncurses-bin@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ncurses-bin@6.1+20181013-2+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ncurses" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "6.1+20181013" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/openssl@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "openssl", + "version": "1.1.1d-0+deb10u2", + "purl": "pkg:deb/debian/openssl@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "openssl@1.1.1d-0+deb10u2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "openssl" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0+deb10u2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.1.1d" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/passwd@4.5-1.1?arch=amd64&distro=debian-10.2&epoch=1", + "type": "library", + "name": "passwd", + "version": "1:4.5-1.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/passwd@4.5-1.1?arch=amd64&distro=debian-10.2&epoch=1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "passwd@1:4.5-1.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "shadow" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "4.5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/perl-base@5.28.1-6?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "perl-base", + "version": "5.28.1-6", + "purl": "pkg:deb/debian/perl-base@5.28.1-6?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "perl-base@5.28.1-6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "perl" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "6" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "5.28.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/rake@12.3.1-3?arch=all&distro=debian-10.2", + "type": "library", + "name": "rake", + "version": "12.3.1-3", + "licenses": [ + { + "license": { + "name": "Expat" + } + } + ], + "purl": "pkg:deb/debian/rake@12.3.1-3?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "rake@12.3.1-3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "rake" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "12.3.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/readline-common@7.0-5?arch=all&distro=debian-10.2", + "type": "library", + "name": "readline-common", + "version": "7.0-5", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GFDL" + } + } + ], + "purl": "pkg:deb/debian/readline-common@7.0-5?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "readline-common@7.0-5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "readline" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "5" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "7.0" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ruby-did-you-mean@1.2.1-1?arch=all&distro=debian-10.2", + "type": "library", + "name": "ruby-did-you-mean", + "version": "1.2.1-1", + "licenses": [ + { + "license": { + "name": "Expat" + } + } + ], + "purl": "pkg:deb/debian/ruby-did-you-mean@1.2.1-1?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ruby-did-you-mean@1.2.1-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ruby-did-you-mean" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.2.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ruby-minitest@5.11.3-1?arch=all&distro=debian-10.2", + "type": "library", + "name": "ruby-minitest", + "version": "5.11.3-1", + "licenses": [ + { + "license": { + "name": "Expat" + } + } + ], + "purl": "pkg:deb/debian/ruby-minitest@5.11.3-1?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ruby-minitest@5.11.3-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ruby-minitest" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "5.11.3" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ruby-net-telnet@0.1.1-2?arch=all&distro=debian-10.2", + "type": "library", + "name": "ruby-net-telnet", + "version": "0.1.1-2", + "licenses": [ + { + "license": { + "name": "Ruby" + } + } + ], + "purl": "pkg:deb/debian/ruby-net-telnet@0.1.1-2?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ruby-net-telnet@0.1.1-2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ruby-net-telnet" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "0.1.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ruby-power-assert@1.1.1-1?arch=all&distro=debian-10.2", + "type": "library", + "name": "ruby-power-assert", + "version": "1.1.1-1", + "licenses": [ + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "Ruby" + } + } + ], + "purl": "pkg:deb/debian/ruby-power-assert@1.1.1-1?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ruby-power-assert@1.1.1-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ruby-power-assert" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.1.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ruby-test-unit@3.2.8-1?arch=all&distro=debian-10.2", + "type": "library", + "name": "ruby-test-unit", + "version": "3.2.8-1", + "licenses": [ + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "Ruby" + } + }, + { + "license": { + "name": "PSF" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + } + ], + "purl": "pkg:deb/debian/ruby-test-unit@3.2.8-1?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ruby-test-unit@3.2.8-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ruby-test-unit" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "3.2.8" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ruby-xmlrpc@0.3.0-2?arch=all&distro=debian-10.2", + "type": "library", + "name": "ruby-xmlrpc", + "version": "0.3.0-2", + "licenses": [ + { + "license": { + "name": "Ruby" + } + } + ], + "purl": "pkg:deb/debian/ruby-xmlrpc@0.3.0-2?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ruby-xmlrpc@0.3.0-2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ruby-xmlrpc" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "0.3.0" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "ruby2.5", + "version": "2.5.5-3+deb10u1", + "licenses": [ + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "Ruby" + } + }, + { + "license": { + "name": "Expat" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "SIL-1.1" + } + }, + { + "license": { + "name": "CC-BY-3.0-famfamfam" + } + }, + { + "license": { + "name": "PreserveNotice" + } + }, + { + "license": { + "name": "3C-BSD" + } + }, + { + "license": { + "name": "PublicDomain" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "AllPermissions" + } + }, + { + "license": { + "name": "PartialGplArtisticAndRuby" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "Artistic" + } + }, + { + "license": { + "name": "zlib/libpng" + } + }, + { + "license": { + "name": "GPL-1.0" + } + }, + { + "license": { + "name": "CC0" + } + }, + { + "license": { + "name": "Unicode" + } + }, + { + "license": { + "name": "Permissive" + } + } + ], + "purl": "pkg:deb/debian/ruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ruby2.5@2.5.5-3+deb10u1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ruby2.5" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "3+deb10u1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.5.5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/ruby@2.5.1?arch=amd64&distro=debian-10.2&epoch=1", + "type": "library", + "name": "ruby", + "version": "1:2.5.1", + "licenses": [ + { + "license": { + "name": "RubyLicense" + } + }, + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/ruby@2.5.1?arch=amd64&distro=debian-10.2&epoch=1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "ruby@1:2.5.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "ruby-defaults" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.5.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/rubygems-integration@1.11?arch=all&distro=debian-10.2", + "type": "library", + "name": "rubygems-integration", + "version": "1.11", + "licenses": [ + { + "license": { + "name": "Expat" + } + } + ], + "purl": "pkg:deb/debian/rubygems-integration@1.11?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "rubygems-integration@1.11" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "rubygems-integration" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.11" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/sed@4.7-1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "sed", + "version": "4.7-1", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/sed@4.7-1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "sed@4.7-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "sed" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "4.7" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/sysvinit-utils@2.93-8?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "sysvinit-utils", + "version": "2.93-8", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/sysvinit-utils@2.93-8?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "sysvinit-utils@2.93-8" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "sysvinit" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "8" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.93" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/tar@1.30%2Bdfsg-6?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "tar", + "version": "1.30+dfsg-6", + "licenses": [ + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "GPL-2.0" + } + } + ], + "purl": "pkg:deb/debian/tar@1.30%2Bdfsg-6?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "tar@1.30+dfsg-6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "tar" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "6" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.30+dfsg" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/tzdata@2019c-0%2Bdeb10u1?arch=all&distro=debian-10.2", + "type": "library", + "name": "tzdata", + "version": "2019c-0+deb10u1", + "purl": "pkg:deb/debian/tzdata@2019c-0%2Bdeb10u1?arch=all&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "tzdata@2019c-0+deb10u1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "tzdata" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0+deb10u1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2019c" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/util-linux@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "type": "library", + "name": "util-linux", + "version": "2.33.1-0.1", + "licenses": [ + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "public-domain" + } + }, + { + "license": { + "name": "BSD-4-Clause" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "LGPL-2.0" + } + }, + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-3.0" + } + }, + { + "license": { + "name": "LGPL-3.0" + } + } + ], + "purl": "pkg:deb/debian/util-linux@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "util-linux@2.33.1-0.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "util-linux" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0.1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.33.1" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/zlib1g@1.2.11.dfsg-1?arch=amd64&distro=debian-10.2&epoch=1", + "type": "library", + "name": "zlib1g", + "version": "1:1.2.11.dfsg-1", + "licenses": [ + { + "license": { + "name": "Zlib" + } + } + ], + "purl": "pkg:deb/debian/zlib1g@1.2.11.dfsg-1?arch=amd64&distro=debian-10.2&epoch=1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "zlib1g@1:1.2.11.dfsg-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "zlib" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.2.11.dfsg" + } + ] + }, + { + "bom-ref": "pkg:gem/activesupport@6.0.2.1", + "type": "library", + "name": "activesupport", + "version": "6.0.2.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/activesupport@6.0.2.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/addressable@2.7.0", + "type": "library", + "name": "addressable", + "version": "2.7.0", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/addressable@2.7.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/addressable-2.7.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/concurrent-ruby@1.1.6", + "type": "library", + "name": "concurrent-ruby", + "version": "1.1.6", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/concurrent-ruby@1.1.6", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/concurrent-ruby-1.1.6.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/cool.io@1.6.0", + "type": "library", + "name": "cool.io", + "version": "1.6.0", + "purl": "pkg:gem/cool.io@1.6.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/cool.io-1.6.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/dig_rb@1.0.1", + "type": "library", + "name": "dig_rb", + "version": "1.0.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/dig_rb@1.0.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/dig_rb-1.0.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/domain_name@0.5.20190701", + "type": "library", + "name": "domain_name", + "version": "0.5.20190701", + "licenses": [ + { + "license": { + "name": "BSD-2-Clause" + } + }, + { + "license": { + "name": "BSD-3-Clause" + } + }, + { + "license": { + "name": "MPL-2.0" + } + } + ], + "purl": "pkg:gem/domain_name@0.5.20190701", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/domain_name-0.5.20190701.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/elasticsearch-api@7.5.0", + "type": "library", + "name": "elasticsearch-api", + "version": "7.5.0", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/elasticsearch-api@7.5.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/elasticsearch-api-7.5.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/elasticsearch-transport@7.5.0", + "type": "library", + "name": "elasticsearch-transport", + "version": "7.5.0", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/elasticsearch-transport@7.5.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/elasticsearch-transport-7.5.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/elasticsearch@7.5.0", + "type": "library", + "name": "elasticsearch", + "version": "7.5.0", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/elasticsearch@7.5.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/elasticsearch-7.5.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/excon@0.72.0", + "type": "library", + "name": "excon", + "version": "0.72.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/excon@0.72.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/excon-0.72.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/faraday@0.17.3", + "type": "library", + "name": "faraday", + "version": "0.17.3", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/faraday@0.17.3", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/faraday-0.17.3.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/ffi-compiler@1.0.1", + "type": "library", + "name": "ffi-compiler", + "version": "1.0.1", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/ffi-compiler@1.0.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/ffi-compiler-1.0.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/ffi@1.12.2", + "type": "library", + "name": "ffi", + "version": "1.12.2", + "licenses": [ + { + "license": { + "name": "BSD-3-Clause" + } + } + ], + "purl": "pkg:gem/ffi@1.12.2", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/ffi-1.12.2.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/fluent-plugin-concat@2.4.0", + "type": "library", + "name": "fluent-plugin-concat", + "version": "2.4.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/fluent-plugin-concat@2.4.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/fluent-plugin-concat-2.4.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/fluent-plugin-detect-exceptions@0.0.13", + "type": "library", + "name": "fluent-plugin-detect-exceptions", + "version": "0.0.13", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/fluent-plugin-detect-exceptions@0.0.13", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/fluent-plugin-detect-exceptions-0.0.13.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/fluent-plugin-elasticsearch@3.8.0", + "type": "library", + "name": "fluent-plugin-elasticsearch", + "version": "3.8.0", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/fluent-plugin-elasticsearch@3.8.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/fluent-plugin-elasticsearch-3.8.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/fluent-plugin-kubernetes_metadata_filter@2.4.1", + "type": "library", + "name": "fluent-plugin-kubernetes_metadata_filter", + "version": "2.4.1", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/fluent-plugin-kubernetes_metadata_filter@2.4.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/fluent-plugin-kubernetes_metadata_filter-2.4.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/fluent-plugin-multi-format-parser@1.0.0", + "type": "library", + "name": "fluent-plugin-multi-format-parser", + "version": "1.0.0", + "licenses": [ + { + "license": { + "name": "Apache License (2.0)" + } + } + ], + "purl": "pkg:gem/fluent-plugin-multi-format-parser@1.0.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/fluent-plugin-multi-format-parser-1.0.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/fluent-plugin-prometheus@1.7.0", + "type": "library", + "name": "fluent-plugin-prometheus", + "version": "1.7.0", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/fluent-plugin-prometheus@1.7.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/fluent-plugin-prometheus-1.7.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/fluent-plugin-systemd@1.0.2", + "type": "library", + "name": "fluent-plugin-systemd", + "version": "1.0.2", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/fluent-plugin-systemd@1.0.2", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/fluent-plugin-systemd-1.0.2.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/fluentd@1.8.0", + "type": "library", + "name": "fluentd", + "version": "1.8.0", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/fluentd@1.8.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/fluentd-1.8.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/http-accept@1.7.0", + "type": "library", + "name": "http-accept", + "version": "1.7.0", + "purl": "pkg:gem/http-accept@1.7.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/http-accept-1.7.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/http-cookie@1.0.3", + "type": "library", + "name": "http-cookie", + "version": "1.0.3", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/http-cookie@1.0.3", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/http-cookie-1.0.3.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/http-form_data@2.2.0", + "type": "library", + "name": "http-form_data", + "version": "2.2.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/http-form_data@2.2.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/http-form_data-2.2.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/http-parser@1.2.1", + "type": "library", + "name": "http-parser", + "version": "1.2.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/http-parser@1.2.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/http-parser-1.2.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/http@4.3.0", + "type": "library", + "name": "http", + "version": "4.3.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/http@4.3.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/http-4.3.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/http_parser.rb@0.6.0", + "type": "library", + "name": "http_parser.rb", + "version": "0.6.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/http_parser.rb@0.6.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/http_parser.rb-0.6.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/i18n@1.8.2", + "type": "library", + "name": "i18n", + "version": "1.8.2", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/i18n@1.8.2", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/i18n-1.8.2.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/kubeclient@4.6.0", + "type": "library", + "name": "kubeclient", + "version": "4.6.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/kubeclient@4.6.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/kubeclient-4.6.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/lru_redux@1.1.0", + "type": "library", + "name": "lru_redux", + "version": "1.1.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/lru_redux@1.1.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/lru_redux-1.1.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/mime-types-data@3.2019.1009", + "type": "library", + "name": "mime-types-data", + "version": "3.2019.1009", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/mime-types-data@3.2019.1009", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/mime-types-data-3.2019.1009.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/mime-types@3.3.1", + "type": "library", + "name": "mime-types", + "version": "3.3.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/mime-types@3.3.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/mime-types-3.3.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/minitest@5.14.0", + "type": "library", + "name": "minitest", + "version": "5.14.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/minitest@5.14.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/minitest-5.14.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/msgpack@1.3.3", + "type": "library", + "name": "msgpack", + "version": "1.3.3", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/msgpack@1.3.3", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/msgpack-1.3.3.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/multi_json@1.14.1", + "type": "library", + "name": "multi_json", + "version": "1.14.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/multi_json@1.14.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/multi_json-1.14.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/multipart-post@2.1.1", + "type": "library", + "name": "multipart-post", + "version": "2.1.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/multipart-post@2.1.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/multipart-post-2.1.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/netrc@0.11.0", + "type": "library", + "name": "netrc", + "version": "0.11.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/netrc@0.11.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/netrc-0.11.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/oj@3.10.0", + "type": "library", + "name": "oj", + "version": "3.10.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/oj@3.10.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/oj-3.10.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/prometheus-client@0.9.0", + "type": "library", + "name": "prometheus-client", + "version": "0.9.0", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/prometheus-client@0.9.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/prometheus-client-0.9.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/public_suffix@4.0.3", + "type": "library", + "name": "public_suffix", + "version": "4.0.3", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/public_suffix@4.0.3", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/public_suffix-4.0.3.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/quantile@0.2.1", + "type": "library", + "name": "quantile", + "version": "0.2.1", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/quantile@0.2.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/quantile-0.2.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/rake@13.0.1", + "type": "library", + "name": "rake", + "version": "13.0.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/rake@13.0.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/rake-13.0.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/recursive-open-struct@1.1.0", + "type": "library", + "name": "recursive-open-struct", + "version": "1.1.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/recursive-open-struct@1.1.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/recursive-open-struct-1.1.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/rest-client@2.1.0", + "type": "library", + "name": "rest-client", + "version": "2.1.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/rest-client@2.1.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/rest-client-2.1.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/serverengine@2.2.1", + "type": "library", + "name": "serverengine", + "version": "2.2.1", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/serverengine@2.2.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/serverengine-2.2.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/sigdump@0.2.4", + "type": "library", + "name": "sigdump", + "version": "0.2.4", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/sigdump@0.2.4", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/sigdump-0.2.4.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/strptime@0.2.3", + "type": "library", + "name": "strptime", + "version": "0.2.3", + "licenses": [ + { + "license": { + "name": "BSD-2-Clause" + } + } + ], + "purl": "pkg:gem/strptime@0.2.3", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/strptime-0.2.3.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/systemd-journal@1.3.3", + "type": "library", + "name": "systemd-journal", + "version": "1.3.3", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/systemd-journal@1.3.3", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/systemd-journal-1.3.3.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/thread_safe@0.3.6", + "type": "library", + "name": "thread_safe", + "version": "0.3.6", + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ], + "purl": "pkg:gem/thread_safe@0.3.6", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/thread_safe-0.3.6.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/tzinfo-data@1.2019.3", + "type": "library", + "name": "tzinfo-data", + "version": "1.2019.3", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/tzinfo-data@1.2019.3", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/tzinfo-data-1.2019.3.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/tzinfo@1.2.6", + "type": "library", + "name": "tzinfo", + "version": "1.2.6", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/tzinfo@1.2.6", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/tzinfo-1.2.6.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/unf@0.1.4", + "type": "library", + "name": "unf", + "version": "0.1.4", + "licenses": [ + { + "license": { + "name": "2-clause BSDL" + } + } + ], + "purl": "pkg:gem/unf@0.1.4", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/unf-0.1.4.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/unf_ext@0.0.7.6", + "type": "library", + "name": "unf_ext", + "version": "0.0.7.6", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/unf_ext@0.0.7.6", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/unf_ext-0.0.7.6.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/yajl-ruby@1.4.1", + "type": "library", + "name": "yajl-ruby", + "version": "1.4.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/yajl-ruby@1.4.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/yajl-ruby-1.4.1.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + }, + { + "bom-ref": "pkg:gem/zeitwerk@2.3.0", + "type": "library", + "name": "zeitwerk", + "version": "2.3.0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/zeitwerk@2.3.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/zeitwerk-2.3.0.gemspec" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + } + ], + "dependencies": [ + { + "ref": "3ff14136-e09f-4df9-80ea-000000000001", + "dependsOn": [ + "3ff14136-e09f-4df9-80ea-000000000002", + "pkg:gem/activesupport@6.0.2.1", + "pkg:gem/addressable@2.7.0", + "pkg:gem/concurrent-ruby@1.1.6", + "pkg:gem/cool.io@1.6.0", + "pkg:gem/dig_rb@1.0.1", + "pkg:gem/domain_name@0.5.20190701", + "pkg:gem/elasticsearch-api@7.5.0", + "pkg:gem/elasticsearch-transport@7.5.0", + "pkg:gem/elasticsearch@7.5.0", + "pkg:gem/excon@0.72.0", + "pkg:gem/faraday@0.17.3", + "pkg:gem/ffi-compiler@1.0.1", + "pkg:gem/ffi@1.12.2", + "pkg:gem/fluent-plugin-concat@2.4.0", + "pkg:gem/fluent-plugin-detect-exceptions@0.0.13", + "pkg:gem/fluent-plugin-elasticsearch@3.8.0", + "pkg:gem/fluent-plugin-kubernetes_metadata_filter@2.4.1", + "pkg:gem/fluent-plugin-multi-format-parser@1.0.0", + "pkg:gem/fluent-plugin-prometheus@1.7.0", + "pkg:gem/fluent-plugin-systemd@1.0.2", + "pkg:gem/fluentd@1.8.0", + "pkg:gem/http-accept@1.7.0", + "pkg:gem/http-cookie@1.0.3", + "pkg:gem/http-form_data@2.2.0", + "pkg:gem/http-parser@1.2.1", + "pkg:gem/http@4.3.0", + "pkg:gem/http_parser.rb@0.6.0", + "pkg:gem/i18n@1.8.2", + "pkg:gem/kubeclient@4.6.0", + "pkg:gem/lru_redux@1.1.0", + "pkg:gem/mime-types-data@3.2019.1009", + "pkg:gem/mime-types@3.3.1", + "pkg:gem/minitest@5.14.0", + "pkg:gem/msgpack@1.3.3", + "pkg:gem/multi_json@1.14.1", + "pkg:gem/multipart-post@2.1.1", + "pkg:gem/netrc@0.11.0", + "pkg:gem/oj@3.10.0", + "pkg:gem/prometheus-client@0.9.0", + "pkg:gem/public_suffix@4.0.3", + "pkg:gem/quantile@0.2.1", + "pkg:gem/rake@13.0.1", + "pkg:gem/recursive-open-struct@1.1.0", + "pkg:gem/rest-client@2.1.0", + "pkg:gem/serverengine@2.2.1", + "pkg:gem/sigdump@0.2.4", + "pkg:gem/strptime@0.2.3", + "pkg:gem/systemd-journal@1.3.3", + "pkg:gem/thread_safe@0.3.6", + "pkg:gem/tzinfo-data@1.2019.3", + "pkg:gem/tzinfo@1.2.6", + "pkg:gem/unf@0.1.4", + "pkg:gem/unf_ext@0.0.7.6", + "pkg:gem/yajl-ruby@1.4.1", + "pkg:gem/zeitwerk@2.3.0" + ] + }, + { + "ref": "3ff14136-e09f-4df9-80ea-000000000002", + "dependsOn": [ + "pkg:deb/debian/adduser@3.118?arch=all&distro=debian-10.2", + "pkg:deb/debian/apt@1.8.2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/base-files@10.3%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/base-passwd@3.5.46?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/bash@5.0-4?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/bsdutils@2.33.1-0.1?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/ca-certificates@20190110?arch=all&distro=debian-10.2", + "pkg:deb/debian/coreutils@8.30-3?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/dash@0.5.10.2-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2", + "pkg:deb/debian/debian-archive-keyring@2019.1?arch=all&distro=debian-10.2", + "pkg:deb/debian/debianutils@4.8.6.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/diffutils@3.7-3?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/dpkg@1.19.7?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/e2fsprogs@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/fdisk@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/findutils@4.6.0%2Bgit%2B20190209-2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/gcc-8-base@8.3.0-6?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/gpgv@2.2.12-1%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/grep@3.3-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/gzip@1.9-3?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/hostname@3.21?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/init-system-helpers@1.56%2Bnmu1?arch=all&distro=debian-10.2", + "pkg:deb/debian/libacl1@2.2.53-4?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libapt-pkg5.0@1.8.2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libattr1@2.4.48-4?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libaudit-common@2.8.4-3?arch=all&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libaudit1@2.8.4-3?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libblkid1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libbz2-1.0@1.0.6-9.2~deb10u1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libc-bin@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libcap-ng0@0.7.9-2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libcom-err2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libdb5.3@5.3.28%2Bdfsg1-0.5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libdebconfclient0@0.249?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libext2fs2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libfdisk1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libffi6@3.2.1-9?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgcc1@8.3.0-6?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libgcrypt20@1.8.4-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgdbm-compat4@1.18.1-4?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgdbm6@1.18.1-4?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgmp10@6.1.2%2Bdfsg-4?arch=amd64&distro=debian-10.2&epoch=2", + "pkg:deb/debian/libgnutls30@3.6.7-4?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgpg-error0@1.35-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libhogweed4@3.4.1-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libjemalloc2@5.1.0-3?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/liblz4-1@1.8.3-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/liblzma5@5.2.4-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libmount1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libncurses6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libncursesw6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libnettle6@3.4.1-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libp11-kit0@0.23.15-2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libpam-modules-bin@1.3.1-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libpam-modules@1.3.1-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libpam-runtime@1.3.1-5?arch=all&distro=debian-10.2", + "pkg:deb/debian/libpam0g@1.3.1-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libpcre3@8.39-12?arch=amd64&distro=debian-10.2&epoch=2", + "pkg:deb/debian/libreadline7@7.0-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libseccomp2@2.3.3-4?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libselinux1@2.8-1%2Bb1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libsemanage-common@2.8-2?arch=all&distro=debian-10.2", + "pkg:deb/debian/libsemanage1@2.8-2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libsepol1@2.8-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libsmartcols1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libss2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libssl1.1@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libstdc%2B%2B6@8.3.0-6?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libsystemd0@241-7~deb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libtasn1-6@4.13-3?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libtinfo6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libudev1@241-7~deb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libunistring2@0.9.10-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libuuid1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libyaml-0-2@0.2.1-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libzstd1@1.3.8%2Bdfsg-3?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/login@4.5-1.1?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/mawk@1.3.3-17%2Bb3?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/mount@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/ncurses-base@6.1%2B20181013-2%2Bdeb10u2?arch=all&distro=debian-10.2", + "pkg:deb/debian/ncurses-bin@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/openssl@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/passwd@4.5-1.1?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/perl-base@5.28.1-6?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/rake@12.3.1-3?arch=all&distro=debian-10.2", + "pkg:deb/debian/readline-common@7.0-5?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-did-you-mean@1.2.1-1?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-minitest@5.11.3-1?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-net-telnet@0.1.1-2?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-power-assert@1.1.1-1?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-test-unit@3.2.8-1?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-xmlrpc@0.3.0-2?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/ruby@2.5.1?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/rubygems-integration@1.11?arch=all&distro=debian-10.2", + "pkg:deb/debian/sed@4.7-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/sysvinit-utils@2.93-8?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/tar@1.30%2Bdfsg-6?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/tzdata@2019c-0%2Bdeb10u1?arch=all&distro=debian-10.2", + "pkg:deb/debian/util-linux@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/zlib1g@1.2.11.dfsg-1?arch=amd64&distro=debian-10.2&epoch=1" + ] + }, + { + "ref": "pkg:deb/debian/adduser@3.118?arch=all&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2", + "pkg:deb/debian/passwd@4.5-1.1?arch=amd64&distro=debian-10.2&epoch=1" + ] + }, + { + "ref": "pkg:deb/debian/apt@1.8.2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/adduser@3.118?arch=all&distro=debian-10.2", + "pkg:deb/debian/debian-archive-keyring@2019.1?arch=all&distro=debian-10.2", + "pkg:deb/debian/gpgv@2.2.12-1%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libapt-pkg5.0@1.8.2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgcc1@8.3.0-6?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libgnutls30@3.6.7-4?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libseccomp2@2.3.3-4?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libstdc%2B%2B6@8.3.0-6?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/base-files@10.3%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/base-passwd@3.5.46?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libdebconfclient0@0.249?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/bash@5.0-4?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/base-files@10.3%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/debianutils@4.8.6.1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/bsdutils@2.33.1-0.1?arch=amd64&distro=debian-10.2&epoch=1", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/ca-certificates@20190110?arch=all&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2", + "pkg:deb/debian/openssl@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/coreutils@8.30-3?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/dash@0.5.10.2-5?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2", + "pkg:deb/debian/debianutils@4.8.6.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/dpkg@1.19.7?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/debian-archive-keyring@2019.1?arch=all&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/debianutils@4.8.6.1?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/diffutils@3.7-3?arch=amd64&distro=debian-10.2&epoch=1", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/dpkg@1.19.7?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/tar@1.30%2Bdfsg-6?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/e2fsprogs@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/fdisk@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libfdisk1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libmount1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libncursesw6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libsmartcols1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libtinfo6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/findutils@4.6.0%2Bgit%2B20190209-2?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/gcc-8-base@8.3.0-6?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/gpgv@2.2.12-1%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libbz2-1.0@1.0.6-9.2~deb10u1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgcrypt20@1.8.4-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgpg-error0@1.35-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/zlib1g@1.2.11.dfsg-1?arch=amd64&distro=debian-10.2&epoch=1" + ] + }, + { + "ref": "pkg:deb/debian/grep@3.3-1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/dpkg@1.19.7?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/gzip@1.9-3?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/dpkg@1.19.7?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/hostname@3.21?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/init-system-helpers@1.56%2Bnmu1?arch=all&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/perl-base@5.28.1-6?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libacl1@2.2.53-4?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libattr1@2.4.48-4?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libapt-pkg5.0@1.8.2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libbz2-1.0@1.0.6-9.2~deb10u1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgcc1@8.3.0-6?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/liblz4-1@1.8.3-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/liblzma5@5.2.4-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libstdc%2B%2B6@8.3.0-6?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libsystemd0@241-7~deb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libudev1@241-7~deb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libzstd1@1.3.8%2Bdfsg-3?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/zlib1g@1.2.11.dfsg-1?arch=amd64&distro=debian-10.2&epoch=1" + ] + }, + { + "ref": "pkg:deb/debian/libattr1@2.4.48-4?arch=amd64&distro=debian-10.2&epoch=1", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libaudit-common@2.8.4-3?arch=all&distro=debian-10.2&epoch=1", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/libaudit1@2.8.4-3?arch=amd64&distro=debian-10.2&epoch=1", + "dependsOn": [ + "pkg:deb/debian/libaudit-common@2.8.4-3?arch=all&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libcap-ng0@0.7.9-2?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libblkid1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libuuid1@2.33.1-0.1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libbz2-1.0@1.0.6-9.2~deb10u1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libc-bin@2.28-10?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libgcc1@8.3.0-6?arch=amd64&distro=debian-10.2&epoch=1" + ] + }, + { + "ref": "pkg:deb/debian/libcap-ng0@0.7.9-2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libcom-err2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libdb5.3@5.3.28%2Bdfsg1-0.5?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libdebconfclient0@0.249?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libext2fs2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libfdisk1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libblkid1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libuuid1@2.33.1-0.1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libffi6@3.2.1-9?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libgcc1@8.3.0-6?arch=amd64&distro=debian-10.2&epoch=1", + "dependsOn": [ + "pkg:deb/debian/gcc-8-base@8.3.0-6?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libgcrypt20@1.8.4-5?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgpg-error0@1.35-1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libgdbm-compat4@1.18.1-4?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgdbm6@1.18.1-4?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libgdbm6@1.18.1-4?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libgmp10@6.1.2%2Bdfsg-4?arch=amd64&distro=debian-10.2&epoch=2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libgnutls30@3.6.7-4?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgmp10@6.1.2%2Bdfsg-4?arch=amd64&distro=debian-10.2&epoch=2", + "pkg:deb/debian/libhogweed4@3.4.1-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libnettle6@3.4.1-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libp11-kit0@0.23.15-2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libtasn1-6@4.13-3?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libunistring2@0.9.10-1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libgpg-error0@1.35-1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libhogweed4@3.4.1-1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgmp10@6.1.2%2Bdfsg-4?arch=amd64&distro=debian-10.2&epoch=2", + "pkg:deb/debian/libnettle6@3.4.1-1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libunistring2@0.9.10-1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libjemalloc2@5.1.0-3?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgcc1@8.3.0-6?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libstdc%2B%2B6@8.3.0-6?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/liblz4-1@1.8.3-1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/liblzma5@5.2.4-1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libmount1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libblkid1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libselinux1@2.8-1%2Bb1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libncurses6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libtinfo6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libncursesw6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libtinfo6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libnettle6@3.4.1-1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libp11-kit0@0.23.15-2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libffi6@3.2.1-9?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libpam-modules-bin@1.3.1-5?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libaudit1@2.8.4-3?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libpam0g@1.3.1-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libselinux1@2.8-1%2Bb1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libpam-modules@1.3.1-5?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/libpam-runtime@1.3.1-5?arch=all&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2", + "pkg:deb/debian/libpam-modules@1.3.1-5?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libpam0g@1.3.1-5?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2", + "pkg:deb/debian/libaudit1@2.8.4-3?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libpcre3@8.39-12?arch=amd64&distro=debian-10.2&epoch=2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libreadline7@7.0-5?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libtinfo6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/readline-common@7.0-5?arch=all&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libffi6@3.2.1-9?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgdbm-compat4@1.18.1-4?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgdbm6@1.18.1-4?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgmp10@6.1.2%2Bdfsg-4?arch=amd64&distro=debian-10.2&epoch=2", + "pkg:deb/debian/libncurses6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libreadline7@7.0-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libssl1.1@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libtinfo6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libyaml-0-2@0.2.1-1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/rake@12.3.1-3?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-did-you-mean@1.2.1-1?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-minitest@5.11.3-1?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-net-telnet@0.1.1-2?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-test-unit@3.2.8-1?arch=all&distro=debian-10.2", + "pkg:deb/debian/ruby-xmlrpc@0.3.0-2?arch=all&distro=debian-10.2", + "pkg:deb/debian/zlib1g@1.2.11.dfsg-1?arch=amd64&distro=debian-10.2&epoch=1" + ] + }, + { + "ref": "pkg:deb/debian/libseccomp2@2.3.3-4?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libselinux1@2.8-1%2Bb1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libpcre3@8.39-12?arch=amd64&distro=debian-10.2&epoch=2" + ] + }, + { + "ref": "pkg:deb/debian/libsemanage-common@2.8-2?arch=all&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/libsemanage1@2.8-2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libaudit1@2.8.4-3?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libbz2-1.0@1.0.6-9.2~deb10u1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libselinux1@2.8-1%2Bb1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libsemanage-common@2.8-2?arch=all&distro=debian-10.2", + "pkg:deb/debian/libsepol1@2.8-1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libsepol1@2.8-1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libsmartcols1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libss2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libcom-err2@1.44.5-1%2Bdeb10u2?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libssl1.1@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libstdc%2B%2B6@8.3.0-6?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/gcc-8-base@8.3.0-6?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgcc1@8.3.0-6?arch=amd64&distro=debian-10.2&epoch=1" + ] + }, + { + "ref": "pkg:deb/debian/libsystemd0@241-7~deb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/libtasn1-6@4.13-3?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libtinfo6@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libudev1@241-7~deb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libunistring2@0.9.10-1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libuuid1@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libyaml-0-2@0.2.1-1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/libzstd1@1.3.8%2Bdfsg-3?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/login@4.5-1.1?arch=amd64&distro=debian-10.2&epoch=1", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/mawk@1.3.3-17%2Bb3?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/mount@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/util-linux@2.33.1-0.1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/ncurses-base@6.1%2B20181013-2%2Bdeb10u2?arch=all&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/ncurses-bin@6.1%2B20181013-2%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/openssl@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libssl1.1@1.1.1d-0%2Bdeb10u2?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/passwd@4.5-1.1?arch=amd64&distro=debian-10.2&epoch=1", + "dependsOn": [ + "pkg:deb/debian/libaudit1@2.8.4-3?arch=amd64&distro=debian-10.2&epoch=1", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libpam-modules@1.3.1-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libpam0g@1.3.1-5?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libselinux1@2.8-1%2Bb1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libsemanage1@2.8-2?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/perl-base@5.28.1-6?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/rake@12.3.1-3?arch=all&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/ruby@2.5.1?arch=amd64&distro=debian-10.2&epoch=1" + ] + }, + { + "ref": "pkg:deb/debian/readline-common@7.0-5?arch=all&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/dpkg@1.19.7?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/ruby-did-you-mean@1.2.1-1?arch=all&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/ruby-minitest@5.11.3-1?arch=all&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/ruby-net-telnet@0.1.1-2?arch=all&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/ruby-power-assert@1.1.1-1?arch=all&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/ruby-test-unit@3.2.8-1?arch=all&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/ruby-power-assert@1.1.1-1?arch=all&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/ruby-xmlrpc@0.3.0-2?arch=all&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/ruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/libgmp10@6.1.2%2Bdfsg-4?arch=amd64&distro=debian-10.2&epoch=2", + "pkg:deb/debian/libruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/rubygems-integration@1.11?arch=all&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/ruby@2.5.1?arch=amd64&distro=debian-10.2&epoch=1", + "dependsOn": [ + "pkg:deb/debian/ruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/rubygems-integration@1.11?arch=all&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/ca-certificates@20190110?arch=all&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/sed@4.7-1?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/sysvinit-utils@2.93-8?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/init-system-helpers@1.56%2Bnmu1?arch=all&distro=debian-10.2", + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/util-linux@2.33.1-0.1?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/tar@1.30%2Bdfsg-6?arch=amd64&distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/tzdata@2019c-0%2Bdeb10u1?arch=all&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/debconf@1.5.71?arch=all&distro=debian-10.2" + ] + }, + { + "ref": "pkg:deb/debian/util-linux@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "dependsOn": [ + "pkg:deb/debian/fdisk@2.33.1-0.1?arch=amd64&distro=debian-10.2", + "pkg:deb/debian/login@4.5-1.1?arch=amd64&distro=debian-10.2&epoch=1" + ] + }, + { + "ref": "pkg:deb/debian/zlib1g@1.2.11.dfsg-1?arch=amd64&distro=debian-10.2&epoch=1", + "dependsOn": [ + "pkg:deb/debian/libc6@2.28-10?arch=amd64&distro=debian-10.2" + ] + }, + { + "ref": "pkg:gem/activesupport@6.0.2.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/addressable@2.7.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/concurrent-ruby@1.1.6", + "dependsOn": [] + }, + { + "ref": "pkg:gem/cool.io@1.6.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/dig_rb@1.0.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/domain_name@0.5.20190701", + "dependsOn": [] + }, + { + "ref": "pkg:gem/elasticsearch-api@7.5.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/elasticsearch-transport@7.5.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/elasticsearch@7.5.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/excon@0.72.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/faraday@0.17.3", + "dependsOn": [] + }, + { + "ref": "pkg:gem/ffi-compiler@1.0.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/ffi@1.12.2", + "dependsOn": [] + }, + { + "ref": "pkg:gem/fluent-plugin-concat@2.4.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/fluent-plugin-detect-exceptions@0.0.13", + "dependsOn": [] + }, + { + "ref": "pkg:gem/fluent-plugin-elasticsearch@3.8.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/fluent-plugin-kubernetes_metadata_filter@2.4.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/fluent-plugin-multi-format-parser@1.0.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/fluent-plugin-prometheus@1.7.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/fluent-plugin-systemd@1.0.2", + "dependsOn": [] + }, + { + "ref": "pkg:gem/fluentd@1.8.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/http-accept@1.7.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/http-cookie@1.0.3", + "dependsOn": [] + }, + { + "ref": "pkg:gem/http-form_data@2.2.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/http-parser@1.2.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/http@4.3.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/http_parser.rb@0.6.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/i18n@1.8.2", + "dependsOn": [] + }, + { + "ref": "pkg:gem/kubeclient@4.6.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/lru_redux@1.1.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/mime-types-data@3.2019.1009", + "dependsOn": [] + }, + { + "ref": "pkg:gem/mime-types@3.3.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/minitest@5.14.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/msgpack@1.3.3", + "dependsOn": [] + }, + { + "ref": "pkg:gem/multi_json@1.14.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/multipart-post@2.1.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/netrc@0.11.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/oj@3.10.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/prometheus-client@0.9.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/public_suffix@4.0.3", + "dependsOn": [] + }, + { + "ref": "pkg:gem/quantile@0.2.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/rake@13.0.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/recursive-open-struct@1.1.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/rest-client@2.1.0", + "dependsOn": [] + }, + { + "ref": "pkg:gem/serverengine@2.2.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/sigdump@0.2.4", + "dependsOn": [] + }, + { + "ref": "pkg:gem/strptime@0.2.3", + "dependsOn": [] + }, + { + "ref": "pkg:gem/systemd-journal@1.3.3", + "dependsOn": [] + }, + { + "ref": "pkg:gem/thread_safe@0.3.6", + "dependsOn": [] + }, + { + "ref": "pkg:gem/tzinfo-data@1.2019.3", + "dependsOn": [] + }, + { + "ref": "pkg:gem/tzinfo@1.2.6", + "dependsOn": [] + }, + { + "ref": "pkg:gem/unf@0.1.4", + "dependsOn": [] + }, + { + "ref": "pkg:gem/unf_ext@0.0.7.6", + "dependsOn": [] + }, + { + "ref": "pkg:gem/yajl-ruby@1.4.1", + "dependsOn": [] + }, + { + "ref": "pkg:gem/zeitwerk@2.3.0", + "dependsOn": [] + } + ], + "vulnerabilities": [] +} diff --git a/integration/testdata/fluentd-multiple-lockfiles.json.golden b/integration/testdata/fluentd-multiple-lockfiles.json.golden new file mode 100644 index 000000000000..701c0262753d --- /dev/null +++ b/integration/testdata/fluentd-multiple-lockfiles.json.golden @@ -0,0 +1,226 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json", + "ArtifactType": "cyclonedx", + "Metadata": { + "OS": { + "Family": "debian", + "Name": "10.2" + }, + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json (debian 10.2)", + "Class": "os-pkgs", + "Type": "debian", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@5.0-4", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "BOMRef": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2" + }, + "InstalledVersion": "5.0-4", + "Status": "affected", + "Layer": {}, + "SeveritySource": "debian", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped", + "Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "Severity": "LOW", + "CweIDs": [ + "CWE-273" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "debian": 1, + "nvd": 3, + "oracle-oval": 1, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.2, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html", + "https://access.redhat.com/security/cve/CVE-2019-18276", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276", + "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff", + "https://linux.oracle.com/cve/CVE-2019-18276.html", + "https://linux.oracle.com/errata/ELSA-2021-1679.html", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://nvd.nist.gov/vuln/detail/CVE-2019-18276", + "https://security.gentoo.org/glsa/202105-34", + "https://security.netapp.com/advisory/ntap-20200430-0003/", + "https://www.youtube.com/watch?v=-wGtxJ8opa8" + ], + "PublishedDate": "2019-11-28T01:15:00Z", + "LastModifiedDate": "2021-05-26T12:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-18224", + "VendorIDs": [ + "DSA-4613-1" + ], + "PkgID": "libidn2-0@2.0.5-1", + "PkgName": "libidn2-0", + "PkgIdentifier": { + "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "BOMRef": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2" + }, + "InstalledVersion": "2.0.5-1", + "FixedVersion": "2.0.5-1+deb10u1", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18224", + "DataSource": { + "ID": "debian", + "Name": "Debian Security Tracker", + "URL": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "Title": "libidn2: heap-based buffer overflow in idn2_to_ascii_4i in lib/lookup.c", + "Description": "idn2_to_ascii_4i in lib/lookup.c in GNU libidn2 before 2.1.1 has a heap-based buffer overflow via a long domain string.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "nvd": 4, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "V3Score": 5.6 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00008.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00009.html", + "https://access.redhat.com/security/cve/CVE-2019-18224", + "https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=12420", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18224", + "https://github.com/libidn/libidn2/commit/e4d1558aa2c1c04a05066ee8600f37603890ba8c", + "https://github.com/libidn/libidn2/compare/libidn2-2.1.0...libidn2-2.1.1", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JDQVQ2XPV5BTZUFINT7AFJSKNNBVURNJ/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MINU5RKDFE6TKAFY5DRFN3WSFDS4DYVS/", + "https://seclists.org/bugtraq/2020/Feb/4", + "https://security.gentoo.org/glsa/202003-63", + "https://ubuntu.com/security/notices/USN-4168-1", + "https://usn.ubuntu.com/4168-1/", + "https://www.debian.org/security/2020/dsa-4613" + ], + "PublishedDate": "2019-10-21T17:15:00Z", + "LastModifiedDate": "2019-10-29T19:15:00Z" + } + ] + }, + { + "Target": "Ruby", + "Class": "lang-pkgs", + "Type": "gemspec", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-8165", + "PkgID": "activesupport@6.0.2.1", + "PkgName": "activesupport", + "PkgPath": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", + "PkgIdentifier": { + "PURL": "pkg:gem/activesupport@6.0.2.1", + "BOMRef": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec" + }, + "InstalledVersion": "6.0.2.1", + "FixedVersion": "6.0.3.1, 5.2.4.3", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-8165", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory RubyGems", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Arubygems" + }, + "Title": "rubygem-activesupport: potentially unintended unmarshalling of user-provided objects in MemCacheStore and RedisCacheStore", + "Description": "A deserialization of untrusted data vulnerability exists in rails \u003c 5.2.4.3, rails \u003c 6.0.3.1 that can allow an attacker to unmarshal user-provided objects in MemCacheStore and RedisCacheStore potentially resulting in an RCE.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-502" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 4, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 9.8 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00031.html", + "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00034.html", + "https://access.redhat.com/security/cve/CVE-2020-8165", + "https://github.com/advisories/GHSA-2p68-f74v-9wc6", + "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/activesupport/CVE-2020-8165.yml", + "https://groups.google.com/forum/#!msg/rubyonrails-security/bv6fW4S0Y1c/KnkEqM7AAQAJ", + "https://groups.google.com/forum/#!topic/rubyonrails-security/bv6fW4S0Y1c", + "https://groups.google.com/g/rubyonrails-security/c/bv6fW4S0Y1c", + "https://hackerone.com/reports/413388", + "https://lists.debian.org/debian-lts-announce/2020/06/msg00022.html", + "https://lists.debian.org/debian-lts-announce/2020/07/msg00013.html", + "https://nvd.nist.gov/vuln/detail/CVE-2020-8165", + "https://weblog.rubyonrails.org/2020/5/18/Rails-5-2-4-3-and-6-0-3-1-have-been-released/", + "https://www.debian.org/security/2020/dsa-4766" + ], + "PublishedDate": "2020-06-19T18:15:00Z", + "LastModifiedDate": "2020-10-17T12:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/gomod-skip.json.golden b/integration/testdata/gomod-skip.json.golden new file mode 100644 index 000000000000..ce748cc19823 --- /dev/null +++ b/integration/testdata/gomod-skip.json.golden @@ -0,0 +1,157 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/gomod", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "go.mod", + "Class": "lang-pkgs", + "Type": "gomod", + "Vulnerabilities": [ + { + "VulnerabilityID": "GMS-2022-20", + "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", + "PkgName": "github.com/docker/distribution", + "PkgIdentifier": { + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible" + }, + "InstalledVersion": "2.7.1+incompatible", + "FixedVersion": "v2.8.0", + "Status": "fixed", + "Layer": {}, + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Title": "OCI Manifest Type Confusion Issue", + "Description": "### Impact\n\nSystems that rely on digest equivalence for image attestations may be vulnerable to type confusion.", + "Severity": "UNKNOWN", + "References": [ + "https://github.com/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/distribution/distribution/commit/b59a6f827947f9e0e67df0cfb571046de4733586", + "https://github.com/distribution/distribution/security/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/opencontainers/image-spec/pull/411" + ] + }, + { + "VulnerabilityID": "CVE-2022-23628", + "PkgID": "github.com/open-policy-agent/opa@v0.35.0", + "PkgName": "github.com/open-policy-agent/opa", + "PkgIdentifier": { + "PURL": "pkg:golang/github.com/open-policy-agent/opa@0.35.0" + }, + "InstalledVersion": "0.35.0", + "FixedVersion": "0.37.0", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-23628", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Title": "Incorrect Calculation", + "Description": "OPA is an open source, general-purpose policy engine. Under certain conditions, pretty-printing an abstract syntax tree (AST) that contains synthetic nodes could change the logic of some statements by reordering array literals. Example of policies impacted are those that parse and compare web paths. **All of these** three conditions have to be met to create an adverse effect: 1. An AST of Rego had to be **created programmatically** such that it ends up containing terms without a location (such as wildcard variables). 2. The AST had to be **pretty-printed** using the `github.com/open-policy-agent/opa/format` package. 3. The result of the pretty-printing had to be **parsed and evaluated again** via an OPA instance using the bundles, or the Golang packages. If any of these three conditions are not met, you are not affected. Notably, all three would be true if using **optimized bundles**, i.e. bundles created with `opa build -O=1` or higher. In that case, the optimizer would fulfil condition (1.), the result of that would be pretty-printed when writing the bundle to disk, fulfilling (2.). When the bundle was then used, we'd satisfy (3.). As a workaround users may disable optimization when creating bundles.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-682" + ], + "VendorSeverity": { + "nvd": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "V2Score": 4.3, + "V3Score": 5.3 + } + }, + "References": [ + "https://github.com/advisories/GHSA-hcw3-j74m-qc58", + "https://github.com/open-policy-agent/opa/commit/932e4ffc37a590ace79e9b75ca4340288c220239", + "https://github.com/open-policy-agent/opa/commit/bfd984ddf93ef2c4963a08d4fdadae0bcf1a3717", + "https://github.com/open-policy-agent/opa/pull/3851", + "https://github.com/open-policy-agent/opa/security/advisories/GHSA-hcw3-j74m-qc58", + "https://nvd.nist.gov/vuln/detail/CVE-2022-23628" + ], + "PublishedDate": "2022-02-09T22:15:00Z", + "LastModifiedDate": "2022-02-17T02:37:00Z" + }, + { + "VulnerabilityID": "CVE-2021-38561", + "PkgID": "golang.org/x/text@v0.3.6", + "PkgName": "golang.org/x/text", + "PkgIdentifier": { + "PURL": "pkg:golang/golang.org/x/text@0.3.6" + }, + "InstalledVersion": "0.3.6", + "FixedVersion": "0.3.7", + "Status": "fixed", + "Layer": {}, + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-38561", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Description": "Due to improper index calculation, an incorrectly formatted language tag can cause Parse\nto panic via an out of bounds read. If Parse is used to process untrusted user inputs,\nthis may be used as a vector for a denial of service attack.\n", + "Severity": "UNKNOWN", + "References": [ + "https://go-review.googlesource.com/c/text/+/340830", + "https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f", + "https://pkg.go.dev/vuln/GO-2021-0113" + ] + } + ] + }, + { + "Target": "submod/go.mod", + "Class": "lang-pkgs", + "Type": "gomod", + "Vulnerabilities": [ + { + "VulnerabilityID": "GMS-2022-20", + "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", + "PkgName": "github.com/docker/distribution", + "PkgIdentifier": { + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible" + }, + "InstalledVersion": "2.7.1+incompatible", + "FixedVersion": "v2.8.0", + "Status": "fixed", + "Layer": {}, + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Title": "OCI Manifest Type Confusion Issue", + "Description": "### Impact\n\nSystems that rely on digest equivalence for image attestations may be vulnerable to type confusion.", + "Severity": "UNKNOWN", + "References": [ + "https://github.com/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/distribution/distribution/commit/b59a6f827947f9e0e67df0cfb571046de4733586", + "https://github.com/distribution/distribution/security/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/opencontainers/image-spec/pull/411" + ] + } + ] + } + ] +} diff --git a/integration/testdata/gomod.json.golden b/integration/testdata/gomod.json.golden new file mode 100644 index 000000000000..5009b9d3bf81 --- /dev/null +++ b/integration/testdata/gomod.json.golden @@ -0,0 +1,190 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/gomod", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "go.mod", + "Class": "lang-pkgs", + "Type": "gomod", + "Vulnerabilities": [ + { + "VulnerabilityID": "GMS-2022-20", + "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", + "PkgName": "github.com/docker/distribution", + "PkgIdentifier": { + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible" + }, + "InstalledVersion": "2.7.1+incompatible", + "FixedVersion": "v2.8.0", + "Status": "fixed", + "Layer": {}, + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Title": "OCI Manifest Type Confusion Issue", + "Description": "### Impact\n\nSystems that rely on digest equivalence for image attestations may be vulnerable to type confusion.", + "Severity": "UNKNOWN", + "References": [ + "https://github.com/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/distribution/distribution/commit/b59a6f827947f9e0e67df0cfb571046de4733586", + "https://github.com/distribution/distribution/security/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/opencontainers/image-spec/pull/411" + ] + }, + { + "VulnerabilityID": "CVE-2022-23628", + "PkgID": "github.com/open-policy-agent/opa@v0.35.0", + "PkgName": "github.com/open-policy-agent/opa", + "PkgIdentifier": { + "PURL": "pkg:golang/github.com/open-policy-agent/opa@0.35.0" + }, + "InstalledVersion": "0.35.0", + "FixedVersion": "0.37.0", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-23628", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Title": "Incorrect Calculation", + "Description": "OPA is an open source, general-purpose policy engine. Under certain conditions, pretty-printing an abstract syntax tree (AST) that contains synthetic nodes could change the logic of some statements by reordering array literals. Example of policies impacted are those that parse and compare web paths. **All of these** three conditions have to be met to create an adverse effect: 1. An AST of Rego had to be **created programmatically** such that it ends up containing terms without a location (such as wildcard variables). 2. The AST had to be **pretty-printed** using the `github.com/open-policy-agent/opa/format` package. 3. The result of the pretty-printing had to be **parsed and evaluated again** via an OPA instance using the bundles, or the Golang packages. If any of these three conditions are not met, you are not affected. Notably, all three would be true if using **optimized bundles**, i.e. bundles created with `opa build -O=1` or higher. In that case, the optimizer would fulfil condition (1.), the result of that would be pretty-printed when writing the bundle to disk, fulfilling (2.). When the bundle was then used, we'd satisfy (3.). As a workaround users may disable optimization when creating bundles.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-682" + ], + "VendorSeverity": { + "nvd": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "V2Score": 4.3, + "V3Score": 5.3 + } + }, + "References": [ + "https://github.com/advisories/GHSA-hcw3-j74m-qc58", + "https://github.com/open-policy-agent/opa/commit/932e4ffc37a590ace79e9b75ca4340288c220239", + "https://github.com/open-policy-agent/opa/commit/bfd984ddf93ef2c4963a08d4fdadae0bcf1a3717", + "https://github.com/open-policy-agent/opa/pull/3851", + "https://github.com/open-policy-agent/opa/security/advisories/GHSA-hcw3-j74m-qc58", + "https://nvd.nist.gov/vuln/detail/CVE-2022-23628" + ], + "PublishedDate": "2022-02-09T22:15:00Z", + "LastModifiedDate": "2022-02-17T02:37:00Z" + }, + { + "VulnerabilityID": "CVE-2021-38561", + "PkgID": "golang.org/x/text@v0.3.6", + "PkgName": "golang.org/x/text", + "PkgIdentifier": { + "PURL": "pkg:golang/golang.org/x/text@0.3.6" + }, + "InstalledVersion": "0.3.6", + "FixedVersion": "0.3.7", + "Status": "fixed", + "Layer": {}, + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-38561", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Description": "Due to improper index calculation, an incorrectly formatted language tag can cause Parse\nto panic via an out of bounds read. If Parse is used to process untrusted user inputs,\nthis may be used as a vector for a denial of service attack.\n", + "Severity": "UNKNOWN", + "References": [ + "https://go-review.googlesource.com/c/text/+/340830", + "https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f", + "https://pkg.go.dev/vuln/GO-2021-0113" + ] + } + ] + }, + { + "Target": "submod/go.mod", + "Class": "lang-pkgs", + "Type": "gomod", + "Vulnerabilities": [ + { + "VulnerabilityID": "GMS-2022-20", + "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", + "PkgName": "github.com/docker/distribution", + "PkgIdentifier": { + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible" + }, + "InstalledVersion": "2.7.1+incompatible", + "FixedVersion": "v2.8.0", + "Status": "fixed", + "Layer": {}, + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Title": "OCI Manifest Type Confusion Issue", + "Description": "### Impact\n\nSystems that rely on digest equivalence for image attestations may be vulnerable to type confusion.", + "Severity": "UNKNOWN", + "References": [ + "https://github.com/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/distribution/distribution/commit/b59a6f827947f9e0e67df0cfb571046de4733586", + "https://github.com/distribution/distribution/security/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/opencontainers/image-spec/pull/411" + ] + } + ] + }, + { + "Target": "submod2/go.mod", + "Class": "lang-pkgs", + "Type": "gomod", + "Vulnerabilities": [ + { + "VulnerabilityID": "GMS-2022-20", + "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", + "PkgName": "github.com/docker/distribution", + "PkgIdentifier": { + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible" + }, + "InstalledVersion": "2.7.1+incompatible", + "FixedVersion": "v2.8.0", + "Status": "fixed", + "Layer": {}, + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Title": "OCI Manifest Type Confusion Issue", + "Description": "### Impact\n\nSystems that rely on digest equivalence for image attestations may be vulnerable to type confusion.", + "Severity": "UNKNOWN", + "References": [ + "https://github.com/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/distribution/distribution/commit/b59a6f827947f9e0e67df0cfb571046de4733586", + "https://github.com/distribution/distribution/security/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/opencontainers/image-spec/pull/411" + ] + } + ] + } + ] +} diff --git a/integration/testdata/gradle.json.golden b/integration/testdata/gradle.json.golden new file mode 100644 index 000000000000..86822d526e3b --- /dev/null +++ b/integration/testdata/gradle.json.golden @@ -0,0 +1,147 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/gradle", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "gradle.lockfile", + "Class": "lang-pkgs", + "Type": "gradle", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-9548", + "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", + "PkgName": "com.fasterxml.jackson.core:jackson-databind", + "PkgIdentifier": { + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + }, + "InstalledVersion": "2.9.1", + "FixedVersion": "2.9.10.4", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-9548", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Maven", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven" + }, + "Title": "jackson-databind: Serialization gadgets in anteros-core", + "Description": "FasterXML jackson-databind 2.x before 2.9.10.4 mishandles the interaction between serialization gadgets and typing, related to br.com.anteros.dbcp.AnterosDBCPConfig (aka anteros-core).", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-502" + ], + "VendorSeverity": { + "ghsa": 4, + "nvd": 4, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 6.8, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 8.1 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2020-9548", + "https://github.com/FasterXML/jackson-databind/issues/2634", + "https://github.com/advisories/GHSA-p43x-xfjf-5jhr", + "https://lists.apache.org/thread.html/r35d30db00440ef63b791c4b7f7acb036e14d4a23afa2a249cb66c0fd@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/r9464a40d25c3ba1a55622db72f113eb494a889656962d098c70c5bb1@%3Cdev.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/r98c9b6e4c9e17792e2cd1ec3e4aa20b61a791939046d3f10888176bb@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rb6fecb5e96a6d61e175ff49f33f2713798dd05cf03067c169d195596@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rd5a4457be4623038c3989294429bc063eec433a2e55995d81591e2ca@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rdd49ab9565bec436a896bc00c4b9fc9dce1598e106c318524fbdfec6@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rdd4df698d5d8e635144d2994922bf0842e933809eae259521f3b5097@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rf1bbc0ea4a9f014cf94df9a12a6477d24a27f52741dbc87f2fd52ff2@%3Cissues.geode.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2020/03/msg00008.html", + "https://medium.com/@cowtowncoder/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062", + "https://nvd.nist.gov/vuln/detail/CVE-2020-9548", + "https://security.netapp.com/advisory/ntap-20200904-0006/", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/security-alerts/cpuoct2021.html" + ], + "PublishedDate": "2020-03-02T04:15:00Z", + "LastModifiedDate": "2021-12-02T21:23:00Z" + }, + { + "VulnerabilityID": "CVE-2021-20190", + "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", + "PkgName": "com.fasterxml.jackson.core:jackson-databind", + "PkgIdentifier": { + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + }, + "InstalledVersion": "2.9.1", + "FixedVersion": "2.9.10.7", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-20190", + "DataSource": { + "ID": "glad", + "Name": "GitLab Advisory Database Community", + "URL": "https://gitlab.com/gitlab-org/advisories-community" + }, + "Title": "jackson-databind: mishandles the interaction between serialization gadgets and typing, related to javax.swing", + "Description": "A flaw was found in jackson-databind before 2.9.10.7. FasterXML mishandles the interaction between serialization gadgets and typing. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-502" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:P/A:C", + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 8.3, + "V3Score": 8.1 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 8.1 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2021-20190", + "https://bugzilla.redhat.com/show_bug.cgi?id=1916633", + "https://github.com/FasterXML/jackson-databind/commit/7dbf51bf78d157098074a20bd9da39bd48c18e4a", + "https://github.com/FasterXML/jackson-databind/issues/2854", + "https://github.com/advisories/GHSA-5949-rw7g-wx7w", + "https://lists.apache.org/thread.html/r380e9257bacb8551ee6fcf2c59890ae9477b2c78e553fa9ea08e9d9a@%3Ccommits.nifi.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2021/04/msg00025.html", + "https://nvd.nist.gov/vuln/detail/CVE-2021-20190", + "https://security.netapp.com/advisory/ntap-20210219-0008/" + ], + "PublishedDate": "2021-01-19T17:15:00Z", + "LastModifiedDate": "2021-07-20T23:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/helm.json.golden b/integration/testdata/helm.json.golden new file mode 100644 index 000000000000..c9721e205272 --- /dev/null +++ b/integration/testdata/helm.json.golden @@ -0,0 +1,912 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/helm", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "testchart.tar.gz:templates/pod.yaml", + "Class": "config", + "Type": "helm", + "MisconfSummary": { + "Successes": 125, + "Failures": 14, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Helm Security Check", + "ID": "KSV001", + "AVDID": "AVD-KSV-0001", + "Title": "Can elevate its own privileges", + "Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", + "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.allowPrivilegeEscalation' to false", + "Namespace": "builtin.kubernetes.KSV001", + "Query": "data.builtin.kubernetes.KSV001.deny", + "Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv001" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV003", + "AVDID": "AVD-KSV-0003", + "Title": "Default capabilities: some containers do not drop all", + "Description": "The container should drop all default capabilities and add only those that are needed for its execution.", + "Message": "Container 'nginx' of Deployment 'nginx-deployment' should add 'ALL' to 'securityContext.capabilities.drop'", + "Namespace": "builtin.kubernetes.KSV003", + "Query": "data.builtin.kubernetes.KSV003.deny", + "Resolution": "Add 'ALL' to containers[].securityContext.capabilities.drop.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv003", + "References": [ + "https://kubesec.io/basics/containers-securitycontext-capabilities-drop-index-all/", + "https://avd.aquasec.com/misconfig/ksv003" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV011", + "AVDID": "AVD-KSV-0011", + "Title": "CPU not limited", + "Description": "Enforcing CPU limits prevents DoS via resource exhaustion.", + "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'resources.limits.cpu'", + "Namespace": "builtin.kubernetes.KSV011", + "Query": "data.builtin.kubernetes.KSV011.deny", + "Resolution": "Set a limit value under 'containers[].resources.limits.cpu'.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv011", + "References": [ + "https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-resource-requests-and-limits", + "https://avd.aquasec.com/misconfig/ksv011" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV012", + "AVDID": "AVD-KSV-0012", + "Title": "Runs as root user", + "Description": "Force the running image to run as a non-root user to ensure least privileges.", + "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.runAsNonRoot' to true", + "Namespace": "builtin.kubernetes.KSV012", + "Query": "data.builtin.kubernetes.KSV012.deny", + "Resolution": "Set 'containers[].securityContext.runAsNonRoot' to true.", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv012", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv012" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV014", + "AVDID": "AVD-KSV-0014", + "Title": "Root file system is not read-only", + "Description": "An immutable root file system prevents applications from writing to their local disk. This can limit intrusions, as attackers will not be able to tamper with the file system or write foreign executables to disk.", + "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.readOnlyRootFilesystem' to true", + "Namespace": "builtin.kubernetes.KSV014", + "Query": "data.builtin.kubernetes.KSV014.deny", + "Resolution": "Change 'containers[].securityContext.readOnlyRootFilesystem' to 'true'.", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv014", + "References": [ + "https://kubesec.io/basics/containers-securitycontext-readonlyrootfilesystem-true/", + "https://avd.aquasec.com/misconfig/ksv014" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV015", + "AVDID": "AVD-KSV-0015", + "Title": "CPU requests not specified", + "Description": "When containers have resource requests specified, the scheduler can make better decisions about which nodes to place pods on, and how to deal with resource contention.", + "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'resources.requests.cpu'", + "Namespace": "builtin.kubernetes.KSV015", + "Query": "data.builtin.kubernetes.KSV015.deny", + "Resolution": "Set 'containers[].resources.requests.cpu'.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv015", + "References": [ + "https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-resource-requests-and-limits", + "https://avd.aquasec.com/misconfig/ksv015" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV016", + "AVDID": "AVD-KSV-0016", + "Title": "Memory requests not specified", + "Description": "When containers have memory requests specified, the scheduler can make better decisions about which nodes to place pods on, and how to deal with resource contention.", + "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'resources.requests.memory'", + "Namespace": "builtin.kubernetes.KSV016", + "Query": "data.builtin.kubernetes.KSV016.deny", + "Resolution": "Set 'containers[].resources.requests.memory'.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv016", + "References": [ + "https://kubesec.io/basics/containers-resources-limits-memory/", + "https://avd.aquasec.com/misconfig/ksv016" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV018", + "AVDID": "AVD-KSV-0018", + "Title": "Memory not limited", + "Description": "Enforcing memory limits prevents DoS via resource exhaustion.", + "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'resources.limits.memory'", + "Namespace": "builtin.kubernetes.KSV018", + "Query": "data.builtin.kubernetes.KSV018.deny", + "Resolution": "Set a limit value under 'containers[].resources.limits.memory'.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv018", + "References": [ + "https://kubesec.io/basics/containers-resources-limits-memory/", + "https://avd.aquasec.com/misconfig/ksv018" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV020", + "AVDID": "AVD-KSV-0020", + "Title": "Runs with UID \u003c= 10000", + "Description": "Force the container to run with user ID \u003e 10000 to avoid conflicts with the host’s user table.", + "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.runAsUser' \u003e 10000", + "Namespace": "builtin.kubernetes.KSV020", + "Query": "data.builtin.kubernetes.KSV020.deny", + "Resolution": "Set 'containers[].securityContext.runAsUser' to an integer \u003e 10000.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv020", + "References": [ + "https://kubesec.io/basics/containers-securitycontext-runasuser/", + "https://avd.aquasec.com/misconfig/ksv020" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV021", + "AVDID": "AVD-KSV-0021", + "Title": "Runs with GID \u003c= 10000", + "Description": "Force the container to run with group ID \u003e 10000 to avoid conflicts with the host’s user table.", + "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.runAsGroup' \u003e 10000", + "Namespace": "builtin.kubernetes.KSV021", + "Query": "data.builtin.kubernetes.KSV021.deny", + "Resolution": "Set 'containers[].securityContext.runAsGroup' to an integer \u003e 10000.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv021", + "References": [ + "https://kubesec.io/basics/containers-securitycontext-runasuser/", + "https://avd.aquasec.com/misconfig/ksv021" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV030", + "AVDID": "AVD-KSV-0030", + "Title": "Runtime/Default Seccomp profile not set", + "Description": "According to pod security standard 'Seccomp', the RuntimeDefault seccomp profile must be required, or allow specific additional profiles.", + "Message": "Either Pod or Container should set 'securityContext.seccompProfile.type' to 'RuntimeDefault'", + "Namespace": "builtin.kubernetes.KSV030", + "Query": "data.builtin.kubernetes.KSV030.deny", + "Resolution": "Set 'spec.securityContext.seccompProfile.type', 'spec.containers[*].securityContext.seccompProfile' and 'spec.initContainers[*].securityContext.seccompProfile' to 'RuntimeDefault' or undefined.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv030", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv030" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV104", + "AVDID": "AVD-KSV-0104", + "Title": "Seccomp policies disabled", + "Description": "A program inside the container can bypass Seccomp protection policies.", + "Message": "container \"nginx\" of deployment \"nginx-deployment\" in \"default\" namespace should specify a seccomp profile", + "Namespace": "builtin.kubernetes.KSV104", + "Query": "data.builtin.kubernetes.KSV104.deny", + "Resolution": "Specify seccomp either by annotation or by seccomp profile type having allowed values as per pod security standards", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv104", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline", + "https://avd.aquasec.com/misconfig/ksv104" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "Code": { + "Lines": null + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV106", + "AVDID": "AVD-KSV-0106", + "Title": "Container capabilities must only include NET_BIND_SERVICE", + "Description": "Containers must drop ALL capabilities, and are only permitted to add back the NET_BIND_SERVICE capability.", + "Message": "container should drop all", + "Namespace": "builtin.kubernetes.KSV106", + "Query": "data.builtin.kubernetes.KSV106.deny", + "Resolution": "Set 'spec.containers[*].securityContext.capabilities.drop' to 'ALL' and only add 'NET_BIND_SERVICE' to 'spec.containers[*].securityContext.capabilities.add'.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv106", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv106" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 19, + "EndLine": 22, + "Code": { + "Lines": [ + { + "Number": 19, + "Content": " - name: nginx", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: nginx", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 20, + "Content": " image: nginx:1.14.2", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mimage\u001b[0m: nginx:1.14.2", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 21, + "Content": " ports:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mports\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 22, + "Content": " - containerPort: 80", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mcontainerPort\u001b[0m: \u001b[38;5;37m80\u001b[0m", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV117", + "AVDID": "AVD-KSV-0117", + "Title": "Prevent binding to privileged ports", + "Description": "The ports which are lower than 1024 receive and transmit various sensitive and privileged data. Allowing containers to use them can bring serious implications.", + "Message": "deployment nginx-deployment in default namespace should not set spec.template.spec.containers.ports.containerPort to less than 1024", + "Namespace": "builtin.kubernetes.KSV117", + "Query": "data.builtin.kubernetes.KSV117.deny", + "Resolution": "Do not map the container ports to privileged host ports when starting a container.", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv117", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "https://avd.aquasec.com/misconfig/ksv117" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "Code": { + "Lines": null + } + } + } + ] + } + ] +} diff --git a/integration/testdata/helm_badname.json.golden b/integration/testdata/helm_badname.json.golden new file mode 100644 index 000000000000..081df6943447 --- /dev/null +++ b/integration/testdata/helm_badname.json.golden @@ -0,0 +1,18 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/helm_badname", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + } +} diff --git a/integration/testdata/helm_testchart.json.golden b/integration/testdata/helm_testchart.json.golden new file mode 100644 index 000000000000..ce6df6b17cee --- /dev/null +++ b/integration/testdata/helm_testchart.json.golden @@ -0,0 +1,360 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/helm_testchart", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "templates/deployment.yaml", + "Class": "config", + "Type": "helm", + "MisconfSummary": { + "Successes": 135, + "Failures": 4, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Helm Security Check", + "ID": "KSV001", + "AVDID": "AVD-KSV-0001", + "Title": "Can elevate its own privileges", + "Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", + "Message": "Container 'testchart' of Deployment 'testchart' should set 'securityContext.allowPrivilegeEscalation' to false", + "Namespace": "builtin.kubernetes.KSV001", + "Query": "data.builtin.kubernetes.KSV001.deny", + "Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv001" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 28, + "EndLine": 57, + "Code": { + "Lines": [ + { + "Number": 28, + "Content": " - name: testchart", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: testchart", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 29, + "Content": " securityContext:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33msecurityContext\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 30, + "Content": " capabilities:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mcapabilities\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 31, + "Content": " drop:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mdrop\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 32, + "Content": " - ALL", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - ALL", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 33, + "Content": " readOnlyRootFilesystem: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mreadOnlyRootFilesystem\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 34, + "Content": " runAsGroup: 10001", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsGroup\u001b[0m: \u001b[38;5;37m10001", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 35, + "Content": " runAsNonRoot: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsNonRoot\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 36, + "Content": " runAsUser: 10001", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m10001", + "FirstCause": false, + "LastCause": true + }, + { + "Number": 37, + "Content": "", + "IsCause": false, + "Annotation": "", + "Truncated": true, + "FirstCause": false, + "LastCause": false + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV030", + "AVDID": "AVD-KSV-0030", + "Title": "Runtime/Default Seccomp profile not set", + "Description": "According to pod security standard 'Seccomp', the RuntimeDefault seccomp profile must be required, or allow specific additional profiles.", + "Message": "Either Pod or Container should set 'securityContext.seccompProfile.type' to 'RuntimeDefault'", + "Namespace": "builtin.kubernetes.KSV030", + "Query": "data.builtin.kubernetes.KSV030.deny", + "Resolution": "Set 'spec.securityContext.seccompProfile.type', 'spec.containers[*].securityContext.seccompProfile' and 'spec.initContainers[*].securityContext.seccompProfile' to 'RuntimeDefault' or undefined.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv030", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv030" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 28, + "EndLine": 57, + "Code": { + "Lines": [ + { + "Number": 28, + "Content": " - name: testchart", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: testchart", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 29, + "Content": " securityContext:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33msecurityContext\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 30, + "Content": " capabilities:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mcapabilities\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 31, + "Content": " drop:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mdrop\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 32, + "Content": " - ALL", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - ALL", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 33, + "Content": " readOnlyRootFilesystem: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mreadOnlyRootFilesystem\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 34, + "Content": " runAsGroup: 10001", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsGroup\u001b[0m: \u001b[38;5;37m10001", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 35, + "Content": " runAsNonRoot: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsNonRoot\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 36, + "Content": " runAsUser: 10001", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m10001", + "FirstCause": false, + "LastCause": true + }, + { + "Number": 37, + "Content": "", + "IsCause": false, + "Annotation": "", + "Truncated": true, + "FirstCause": false, + "LastCause": false + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV104", + "AVDID": "AVD-KSV-0104", + "Title": "Seccomp policies disabled", + "Description": "A program inside the container can bypass Seccomp protection policies.", + "Message": "container \"testchart\" of deployment \"testchart\" in \"default\" namespace should specify a seccomp profile", + "Namespace": "builtin.kubernetes.KSV104", + "Query": "data.builtin.kubernetes.KSV104.deny", + "Resolution": "Specify seccomp either by annotation or by seccomp profile type having allowed values as per pod security standards", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv104", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline", + "https://avd.aquasec.com/misconfig/ksv104" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "Code": { + "Lines": null + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV117", + "AVDID": "AVD-KSV-0117", + "Title": "Prevent binding to privileged ports", + "Description": "The ports which are lower than 1024 receive and transmit various sensitive and privileged data. Allowing containers to use them can bring serious implications.", + "Message": "deployment testchart in default namespace should not set spec.template.spec.containers.ports.containerPort to less than 1024", + "Namespace": "builtin.kubernetes.KSV117", + "Query": "data.builtin.kubernetes.KSV117.deny", + "Resolution": "Do not map the container ports to privileged host ports when starting a container.", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv117", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "https://avd.aquasec.com/misconfig/ksv117" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "Code": { + "Lines": null + } + } + } + ] + }, + { + "Target": "templates/service.yaml", + "Class": "config", + "Type": "helm", + "MisconfSummary": { + "Successes": 106, + "Failures": 0, + "Exceptions": 0 + } + }, + { + "Target": "templates/serviceaccount.yaml", + "Class": "config", + "Type": "helm", + "MisconfSummary": { + "Successes": 105, + "Failures": 0, + "Exceptions": 0 + } + } + ] +} diff --git a/integration/testdata/helm_testchart.overridden.json.golden b/integration/testdata/helm_testchart.overridden.json.golden new file mode 100644 index 000000000000..573d789ef7b4 --- /dev/null +++ b/integration/testdata/helm_testchart.overridden.json.golden @@ -0,0 +1,587 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/helm_testchart", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "templates/deployment.yaml", + "Class": "config", + "Type": "helm", + "MisconfSummary": { + "Successes": 133, + "Failures": 6, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Helm Security Check", + "ID": "KSV001", + "AVDID": "AVD-KSV-0001", + "Title": "Can elevate its own privileges", + "Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", + "Message": "Container 'testchart' of Deployment 'testchart' should set 'securityContext.allowPrivilegeEscalation' to false", + "Namespace": "builtin.kubernetes.KSV001", + "Query": "data.builtin.kubernetes.KSV001.deny", + "Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv001" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 28, + "EndLine": 57, + "Code": { + "Lines": [ + { + "Number": 28, + "Content": " - name: testchart", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: testchart", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 29, + "Content": " securityContext:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33msecurityContext\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 30, + "Content": " capabilities:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mcapabilities\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 31, + "Content": " drop:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mdrop\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 32, + "Content": " - ALL", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - ALL", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 33, + "Content": " readOnlyRootFilesystem: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mreadOnlyRootFilesystem\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 34, + "Content": " runAsGroup: 10001", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsGroup\u001b[0m: \u001b[38;5;37m10001", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 35, + "Content": " runAsNonRoot: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsNonRoot\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 36, + "Content": " runAsUser: 0", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m0", + "FirstCause": false, + "LastCause": true + }, + { + "Number": 37, + "Content": "", + "IsCause": false, + "Annotation": "", + "Truncated": true, + "FirstCause": false, + "LastCause": false + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV020", + "AVDID": "AVD-KSV-0020", + "Title": "Runs with UID \u003c= 10000", + "Description": "Force the container to run with user ID \u003e 10000 to avoid conflicts with the host’s user table.", + "Message": "Container 'testchart' of Deployment 'testchart' should set 'securityContext.runAsUser' \u003e 10000", + "Namespace": "builtin.kubernetes.KSV020", + "Query": "data.builtin.kubernetes.KSV020.deny", + "Resolution": "Set 'containers[].securityContext.runAsUser' to an integer \u003e 10000.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv020", + "References": [ + "https://kubesec.io/basics/containers-securitycontext-runasuser/", + "https://avd.aquasec.com/misconfig/ksv020" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 28, + "EndLine": 57, + "Code": { + "Lines": [ + { + "Number": 28, + "Content": " - name: testchart", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: testchart", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 29, + "Content": " securityContext:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33msecurityContext\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 30, + "Content": " capabilities:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mcapabilities\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 31, + "Content": " drop:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mdrop\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 32, + "Content": " - ALL", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - ALL", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 33, + "Content": " readOnlyRootFilesystem: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mreadOnlyRootFilesystem\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 34, + "Content": " runAsGroup: 10001", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsGroup\u001b[0m: \u001b[38;5;37m10001", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 35, + "Content": " runAsNonRoot: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsNonRoot\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 36, + "Content": " runAsUser: 0", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m0", + "FirstCause": false, + "LastCause": true + }, + { + "Number": 37, + "Content": "", + "IsCause": false, + "Annotation": "", + "Truncated": true, + "FirstCause": false, + "LastCause": false + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV030", + "AVDID": "AVD-KSV-0030", + "Title": "Runtime/Default Seccomp profile not set", + "Description": "According to pod security standard 'Seccomp', the RuntimeDefault seccomp profile must be required, or allow specific additional profiles.", + "Message": "Either Pod or Container should set 'securityContext.seccompProfile.type' to 'RuntimeDefault'", + "Namespace": "builtin.kubernetes.KSV030", + "Query": "data.builtin.kubernetes.KSV030.deny", + "Resolution": "Set 'spec.securityContext.seccompProfile.type', 'spec.containers[*].securityContext.seccompProfile' and 'spec.initContainers[*].securityContext.seccompProfile' to 'RuntimeDefault' or undefined.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv030", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv030" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 28, + "EndLine": 57, + "Code": { + "Lines": [ + { + "Number": 28, + "Content": " - name: testchart", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - \u001b[38;5;33mname\u001b[0m: testchart", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 29, + "Content": " securityContext:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33msecurityContext\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 30, + "Content": " capabilities:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mcapabilities\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 31, + "Content": " drop:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mdrop\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 32, + "Content": " - ALL", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - ALL", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 33, + "Content": " readOnlyRootFilesystem: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mreadOnlyRootFilesystem\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 34, + "Content": " runAsGroup: 10001", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsGroup\u001b[0m: \u001b[38;5;37m10001", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 35, + "Content": " runAsNonRoot: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsNonRoot\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 36, + "Content": " runAsUser: 0", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m0", + "FirstCause": false, + "LastCause": true + }, + { + "Number": 37, + "Content": "", + "IsCause": false, + "Annotation": "", + "Truncated": true, + "FirstCause": false, + "LastCause": false + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV104", + "AVDID": "AVD-KSV-0104", + "Title": "Seccomp policies disabled", + "Description": "A program inside the container can bypass Seccomp protection policies.", + "Message": "container \"testchart\" of deployment \"testchart\" in \"default\" namespace should specify a seccomp profile", + "Namespace": "builtin.kubernetes.KSV104", + "Query": "data.builtin.kubernetes.KSV104.deny", + "Resolution": "Specify seccomp either by annotation or by seccomp profile type having allowed values as per pod security standards", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv104", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline", + "https://avd.aquasec.com/misconfig/ksv104" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "Code": { + "Lines": null + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV105", + "AVDID": "AVD-KSV-0105", + "Title": "Containers must not set runAsUser to 0", + "Description": "Containers should be forbidden from running with a root UID.", + "Message": "securityContext.runAsUser should be set to a value greater than 0", + "Namespace": "builtin.kubernetes.KSV105", + "Query": "data.builtin.kubernetes.KSV105.deny", + "Resolution": "Set 'securityContext.runAsUser' to a non-zero integer or leave undefined.", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv105", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv105" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 30, + "EndLine": 36, + "Code": { + "Lines": [ + { + "Number": 30, + "Content": " capabilities:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mcapabilities\u001b[0m:", + "FirstCause": true, + "LastCause": false + }, + { + "Number": 31, + "Content": " drop:", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mdrop\u001b[0m:", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 32, + "Content": " - ALL", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " - ALL", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 33, + "Content": " readOnlyRootFilesystem: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": " \u001b[38;5;33mreadOnlyRootFilesystem\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 34, + "Content": " runAsGroup: 10001", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsGroup\u001b[0m: \u001b[38;5;37m10001", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 35, + "Content": " runAsNonRoot: true", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsNonRoot\u001b[0m: \u001b[38;5;166mtrue", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 36, + "Content": " runAsUser: 0", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m0", + "FirstCause": false, + "LastCause": true + } + ] + } + } + }, + { + "Type": "Helm Security Check", + "ID": "KSV117", + "AVDID": "AVD-KSV-0117", + "Title": "Prevent binding to privileged ports", + "Description": "The ports which are lower than 1024 receive and transmit various sensitive and privileged data. Allowing containers to use them can bring serious implications.", + "Message": "deployment testchart in default namespace should not set spec.template.spec.containers.ports.containerPort to less than 1024", + "Namespace": "builtin.kubernetes.KSV117", + "Query": "data.builtin.kubernetes.KSV117.deny", + "Resolution": "Do not map the container ports to privileged host ports when starting a container.", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv117", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "https://avd.aquasec.com/misconfig/ksv117" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "Code": { + "Lines": null + } + } + } + ] + }, + { + "Target": "templates/service.yaml", + "Class": "config", + "Type": "helm", + "MisconfSummary": { + "Successes": 106, + "Failures": 0, + "Exceptions": 0 + } + }, + { + "Target": "templates/serviceaccount.yaml", + "Class": "config", + "Type": "helm", + "MisconfSummary": { + "Successes": 105, + "Failures": 0, + "Exceptions": 0 + } + } + ] +} diff --git a/integration/testdata/license-cyclonedx.json.golden b/integration/testdata/license-cyclonedx.json.golden new file mode 100644 index 000000000000..cf69da9756ed --- /dev/null +++ b/integration/testdata/license-cyclonedx.json.golden @@ -0,0 +1,65 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/sbom/license-cyclonedx.json", + "ArtifactType": "cyclonedx", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "OS Packages", + "Class": "license" + }, + { + "Target": "pom.xml", + "Class": "license" + }, + { + "Target": "Java", + "Class": "license", + "Licenses": [ + { + "Severity": "MEDIUM", + "Category": "reciprocal", + "PkgName": "org.eclipse.sisu:org.eclipse.sisu.plexus", + "FilePath": "", + "Name": "EPL-1.0", + "Confidence": 1, + "Link": "" + }, + { + "Severity": "LOW", + "Category": "notice", + "PkgName": "org.ow2.asm:asm", + "FilePath": "", + "Name": "BSD-3-Clause", + "Confidence": 1, + "Link": "" + }, + { + "Severity": "UNKNOWN", + "Category": "unknown", + "PkgName": "org.slf4j:slf4j-api", + "FilePath": "", + "Name": "MIT License", + "Confidence": 1, + "Link": "" + } + ] + }, + { + "Target": "Loose File License(s)", + "Class": "license-file" + } + ] +} diff --git a/integration/testdata/mariner-1.0.json.golden b/integration/testdata/mariner-1.0.json.golden new file mode 100644 index 000000000000..435dd5bdf7a6 --- /dev/null +++ b/integration/testdata/mariner-1.0.json.golden @@ -0,0 +1,135 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/mariner-1.0.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "cbl-mariner", + "Name": "1.0.20220122" + }, + "ImageID": "sha256:8cdcbf18341ed8afa5322e7b0077f8ef3f46896882c921df5f97c51b369f6767", + "DiffIDs": [ + "sha256:4266328c97a194b2ca52ec83bc05496596303f5e9b244ffa99cf84763a487804" + ], + "ImageConfig": { + "architecture": "amd64", + "created": "2022-01-27T01:19:38.526301656Z", + "docker_version": "20.10.12", + "history": [ + { + "created": "2022-01-27T01:19:38.526301656Z", + "comment": "Imported from -" + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:4266328c97a194b2ca52ec83bc05496596303f5e9b244ffa99cf84763a487804" + ] + }, + "config": {} + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/mariner-1.0.tar.gz (cbl-mariner 1.0.20220122)", + "Class": "os-pkgs", + "Type": "cbl-mariner", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-0261", + "PkgName": "vim", + "PkgIdentifier": { + "PURL": "pkg:cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64" + }, + "InstalledVersion": "8.2.4081-1.cm1", + "Status": "affected", + "Layer": { + "Digest": "sha256:3df36548ffbf2fa7319966e038058a3d2a922880009e535202546a6b250b9d57", + "DiffID": "sha256:4266328c97a194b2ca52ec83bc05496596303f5e9b244ffa99cf84763a487804" + }, + "SeveritySource": "cbl-mariner", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-0261", + "DataSource": { + "ID": "cbl-mariner", + "Name": "CBL-Mariner Vulnerability Data", + "URL": "https://github.com/microsoft/CBL-MarinerVulnerabilityData" + }, + "Title": "CVE-2022-0261 affecting package vim 8.2.4081", + "Description": "Heap-based Buffer Overflow in GitHub repository vim/vim prior to 8.2.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-122" + ], + "VendorSeverity": { + "cbl-mariner": 3 + }, + "References": [ + "https://github.com/vim/vim/commit/9f8c304c8a390ade133bac29963dc8e56ab14cbc", + "https://huntr.dev/bounties/fa795954-8775-4f23-98c6-d4d4d3fe8a82", + "https://nvd.nist.gov/vuln/detail/CVE-2022-0261" + ], + "PublishedDate": "2022-01-18T16:15:00Z", + "LastModifiedDate": "2022-01-18T16:15:00Z" + }, + { + "VulnerabilityID": "CVE-2022-0158", + "PkgName": "vim", + "PkgIdentifier": { + "PURL": "pkg:cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64" + }, + "InstalledVersion": "8.2.4081-1.cm1", + "FixedVersion": "8.2.4082-1.cm1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:3df36548ffbf2fa7319966e038058a3d2a922880009e535202546a6b250b9d57", + "DiffID": "sha256:4266328c97a194b2ca52ec83bc05496596303f5e9b244ffa99cf84763a487804" + }, + "SeveritySource": "cbl-mariner", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-0158", + "DataSource": { + "ID": "cbl-mariner", + "Name": "CBL-Mariner Vulnerability Data", + "URL": "https://github.com/microsoft/CBL-MarinerVulnerabilityData" + }, + "Title": "vim: heap-based read buffer overflow in compile_get_env()", + "Description": "vim is vulnerable to Heap-based Buffer Overflow", + "Severity": "LOW", + "CweIDs": [ + "CWE-122" + ], + "VendorSeverity": { + "cbl-mariner": 1, + "nvd": 1, + "redhat": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N", + "V2Score": 4.3, + "V3Score": 3.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N", + "V3Score": 3.3 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2022/01/15/1", + "https://access.redhat.com/security/cve/CVE-2022-0158", + "https://github.com/vim/vim/commit/5f25c3855071bd7e26255c68bf458b1b5cf92f39", + "https://huntr.dev/bounties/ac5d7005-07c6-4a0a-b251-ba9cdbf6738b", + "https://huntr.dev/bounties/ac5d7005-07c6-4a0a-b251-ba9cdbf6738b/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HD5S2FC2HF22A7XQXK2XXIR46EARVWIM/", + "https://nvd.nist.gov/vuln/detail/CVE-2022-0158" + ], + "PublishedDate": "2022-01-10T16:15:00Z", + "LastModifiedDate": "2022-01-15T16:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/minikube-kbom.json.golden b/integration/testdata/minikube-kbom.json.golden new file mode 100644 index 000000000000..fd5b6b24718f --- /dev/null +++ b/integration/testdata/minikube-kbom.json.golden @@ -0,0 +1,72 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/sbom/minikube-kbom.json", + "ArtifactType": "cyclonedx", + "Metadata": { + "OS": { + "Family": "ubuntu", + "Name": "22.04.2" + }, + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "testdata/fixtures/sbom/minikube-kbom.json (ubuntu 22.04.2)", + "Class": "os-pkgs", + "Type": "ubuntu" + }, + { + "Target": "Kubernetes", + "Class": "lang-pkgs", + "Type": "kubernetes", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2023-2431", + "PkgID": "k8s.io/kubelet@1.27.0", + "PkgName": "k8s.io/kubelet", + "PkgIdentifier": { + "PURL": "pkg:k8s/k8s.io%2Fkubelet@1.27.0", + "BOMRef": "pkg:k8s/k8s.io%2Fkubelet@1.27.0" + }, + "InstalledVersion": "1.27.0", + "FixedVersion": "1.24.14, 1.25.9, 1.26.4, 1.27.1", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "k8s", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2023-2431", + "DataSource": { + "ID": "k8s", + "Name": "Official Kubernetes CVE Feed", + "URL": "https://kubernetes.io/docs/reference/issues-security/official-cve-feed/index.json" + }, + "Title": "Bypass of seccomp profile enforcement ", + "Description": "A security issue was discovered in Kubelet that allows pods to bypass the seccomp profile enforcement...", + "Severity": "LOW", + "VendorSeverity": { + "k8s": 1 + }, + "CVSS": { + "k8s": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 3.4 + } + }, + "References": [ + "https://github.com/kubernetes/kubernetes/issues/118690", + "https://www.cve.org/cverecord?id=CVE-2023-2431" + ] + } + ] + } + ] +} diff --git a/integration/testdata/mix.lock.json.golden b/integration/testdata/mix.lock.json.golden new file mode 100644 index 000000000000..54445fbf9cfa --- /dev/null +++ b/integration/testdata/mix.lock.json.golden @@ -0,0 +1,218 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/mixlock", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "mix.lock", + "Class": "lang-pkgs", + "Type": "hex", + "Packages": [ + { + "ID": "castore@0.1.18", + "Name": "castore", + "Identifier": { + "PURL": "pkg:hex/castore@0.1.18" + }, + "Version": "0.1.18", + "Layer": {}, + "Locations": [ + { + "StartLine": 2, + "EndLine": 2 + } + ] + }, + { + "ID": "jason@1.4.0", + "Name": "jason", + "Identifier": { + "PURL": "pkg:hex/jason@1.4.0" + }, + "Version": "1.4.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 3, + "EndLine": 3 + } + ] + }, + { + "ID": "phoenix@1.6.13", + "Name": "phoenix", + "Identifier": { + "PURL": "pkg:hex/phoenix@1.6.13" + }, + "Version": "1.6.13", + "Layer": {}, + "Locations": [ + { + "StartLine": 4, + "EndLine": 4 + } + ] + }, + { + "ID": "phoenix_html@3.2.0", + "Name": "phoenix_html", + "Identifier": { + "PURL": "pkg:hex/phoenix_html@3.2.0" + }, + "Version": "3.2.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 5, + "EndLine": 5 + } + ] + }, + { + "ID": "phoenix_pubsub@2.1.1", + "Name": "phoenix_pubsub", + "Identifier": { + "PURL": "pkg:hex/phoenix_pubsub@2.1.1" + }, + "Version": "2.1.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 6, + "EndLine": 6 + } + ] + }, + { + "ID": "phoenix_template@1.0.0", + "Name": "phoenix_template", + "Identifier": { + "PURL": "pkg:hex/phoenix_template@1.0.0" + }, + "Version": "1.0.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 7, + "EndLine": 7 + } + ] + }, + { + "ID": "phoenix_view@2.0.1", + "Name": "phoenix_view", + "Identifier": { + "PURL": "pkg:hex/phoenix_view@2.0.1" + }, + "Version": "2.0.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 8, + "EndLine": 8 + } + ] + }, + { + "ID": "plug@1.14.0", + "Name": "plug", + "Identifier": { + "PURL": "pkg:hex/plug@1.14.0" + }, + "Version": "1.14.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 9, + "EndLine": 9 + } + ] + }, + { + "ID": "plug_crypto@1.2.3", + "Name": "plug_crypto", + "Identifier": { + "PURL": "pkg:hex/plug_crypto@1.2.3" + }, + "Version": "1.2.3", + "Layer": {}, + "Locations": [ + { + "StartLine": 10, + "EndLine": 10 + } + ] + }, + { + "ID": "telemetry@1.1.0", + "Name": "telemetry", + "Identifier": { + "PURL": "pkg:hex/telemetry@1.1.0" + }, + "Version": "1.1.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 11, + "EndLine": 11 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-42975", + "PkgID": "phoenix@1.6.13", + "PkgName": "phoenix", + "PkgIdentifier": { + "PURL": "pkg:hex/phoenix@1.6.13" + }, + "InstalledVersion": "1.6.13", + "FixedVersion": "1.6.14", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-42975", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Erlang", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Aerlang" + }, + "Title": "Phoenix before 1.6.14 mishandles check_origin wildcarding", + "Description": "socket/transport.ex in Phoenix before 1.6.14 mishandles check_origin wildcarding. NOTE: LiveView applications are unaffected by default because of the presence of a LiveView CSRF token.", + "Severity": "HIGH", + "VendorSeverity": { + "ghsa": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "V3Score": 7.5 + } + }, + "References": [ + "https://nvd.nist.gov/vuln/detail/CVE-2022-42975", + "https://github.com/phoenixframework/phoenix/commit/6e7185b33a59e0b1d1c0b4223adf340a73e963ae", + "https://hexdocs.pm/phoenix/1.6.14/changelog.html#1-6-14-2022-10-10", + "https://github.com/advisories/GHSA-p8f7-22gq-m7j9" + ], + "PublishedDate": "2022-10-17T12:00:27Z", + "LastModifiedDate": "2022-10-18T18:01:44Z" + } + ] + } + ] +} diff --git a/integration/testdata/npm-with-dev.json.golden b/integration/testdata/npm-with-dev.json.golden new file mode 100644 index 000000000000..a512e5247910 --- /dev/null +++ b/integration/testdata/npm-with-dev.json.golden @@ -0,0 +1,413 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/npm", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "package-lock.json", + "Class": "lang-pkgs", + "Type": "npm", + "Packages": [ + { + "ID": "asap@2.0.6", + "Name": "asap", + "Identifier": { + "PURL": "pkg:npm/asap@2.0.6" + }, + "Version": "2.0.6", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 6, + "EndLine": 10 + } + ] + }, + { + "ID": "jquery@3.3.9", + "Name": "jquery", + "Identifier": { + "PURL": "pkg:npm/jquery@3.3.9" + }, + "Version": "3.3.9", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 11, + "EndLine": 15 + } + ] + }, + { + "ID": "js-tokens@4.0.0", + "Name": "js-tokens", + "Identifier": { + "PURL": "pkg:npm/js-tokens@4.0.0" + }, + "Version": "4.0.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 16, + "EndLine": 20 + } + ] + }, + { + "ID": "loose-envify@1.4.0", + "Name": "loose-envify", + "Identifier": { + "PURL": "pkg:npm/loose-envify@1.4.0" + }, + "Version": "1.4.0", + "Indirect": true, + "DependsOn": [ + "js-tokens@4.0.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 21, + "EndLine": 28 + } + ] + }, + { + "ID": "object-assign@4.1.1", + "Name": "object-assign", + "Identifier": { + "PURL": "pkg:npm/object-assign@4.1.1" + }, + "Version": "4.1.1", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 29, + "EndLine": 33 + } + ] + }, + { + "ID": "promise@8.0.3", + "Name": "promise", + "Identifier": { + "PURL": "pkg:npm/promise@8.0.3" + }, + "Version": "8.0.3", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "DependsOn": [ + "asap@2.0.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 34, + "EndLine": 41 + } + ] + }, + { + "ID": "prop-types@15.7.2", + "Name": "prop-types", + "Identifier": { + "PURL": "pkg:npm/prop-types@15.7.2" + }, + "Version": "15.7.2", + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "react-is@16.8.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 42, + "EndLine": 51 + } + ] + }, + { + "ID": "react@16.8.6", + "Name": "react", + "Identifier": { + "PURL": "pkg:npm/react@16.8.6" + }, + "Version": "16.8.6", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "prop-types@15.7.2", + "scheduler@0.13.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 52, + "EndLine": 62 + } + ] + }, + { + "ID": "react-is@16.8.6", + "Name": "react-is", + "Identifier": { + "PURL": "pkg:npm/react-is@16.8.6" + }, + "Version": "16.8.6", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 63, + "EndLine": 67 + } + ] + }, + { + "ID": "redux@4.0.1", + "Name": "redux", + "Identifier": { + "PURL": "pkg:npm/redux@4.0.1" + }, + "Version": "4.0.1", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "symbol-observable@1.2.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 68, + "EndLine": 76 + } + ] + }, + { + "ID": "scheduler@0.13.6", + "Name": "scheduler", + "Identifier": { + "PURL": "pkg:npm/scheduler@0.13.6" + }, + "Version": "0.13.6", + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 77, + "EndLine": 85 + } + ] + }, + { + "ID": "symbol-observable@1.2.0", + "Name": "symbol-observable", + "Identifier": { + "PURL": "pkg:npm/symbol-observable@1.2.0" + }, + "Version": "1.2.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 86, + "EndLine": 90 + } + ] + }, + { + "ID": "z-lock@1.0.0", + "Name": "z-lock", + "Identifier": { + "PURL": "pkg:npm/z-lock@1.0.0" + }, + "Version": "1.0.0", + "Dev": true, + "Licenses": [ + "MIT" + ], + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 91, + "EndLine": 96 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-11358", + "PkgID": "jquery@3.3.9", + "PkgName": "jquery", + "PkgIdentifier": { + "PURL": "pkg:npm/jquery@3.3.9" + }, + "InstalledVersion": "3.3.9", + "FixedVersion": "3.4.0", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-11358", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "jquery: Prototype pollution in object's prototype leading to denial of service, remote code execution, or property injection", + "Description": "jQuery before 3.4.0, as used in Drupal, Backdrop CMS, and other products, mishandles jQuery.extend(true, {}, ...) because of Object.prototype pollution. If an unsanitized source object contained an enumerable __proto__ property, it could extend the native Object.prototype.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-79" + ], + "VendorSeverity": { + "alma": 2, + "amazon": 2, + "arch-linux": 2, + "ghsa": 2, + "nodejs-security-wg": 2, + "nvd": 2, + "oracle-oval": 2, + "redhat": 2, + "ruby-advisory-db": 2, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "V2Score": 4.3, + "V3Score": 6.1 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "V3Score": 5.6 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00006.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00025.html", + "http://packetstormsecurity.com/files/152787/dotCMS-5.1.1-Vulnerable-Dependencies.html", + "http://packetstormsecurity.com/files/153237/RetireJS-CORS-Issue-Script-Execution.html", + "http://packetstormsecurity.com/files/156743/OctoberCMS-Insecure-Dependencies.html", + "http://seclists.org/fulldisclosure/2019/May/10", + "http://seclists.org/fulldisclosure/2019/May/11", + "http://seclists.org/fulldisclosure/2019/May/13", + "http://www.openwall.com/lists/oss-security/2019/06/03/2", + "http://www.securityfocus.com/bid/108023", + "https://access.redhat.com/errata/RHBA-2019:1570", + "https://access.redhat.com/errata/RHSA-2019:1456", + "https://access.redhat.com/errata/RHSA-2019:2587", + "https://access.redhat.com/errata/RHSA-2019:3023", + "https://access.redhat.com/errata/RHSA-2019:3024", + "https://access.redhat.com/security/cve/CVE-2019-11358", + "https://backdropcms.org/security/backdrop-sa-core-2019-009", + "https://blog.jquery.com/2019/04/10/jquery-3-4-0-released/", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11358", + "https://github.com/DanielRuf/snyk-js-jquery-174006?files=1", + "https://github.com/advisories/GHSA-6c3j-c64m-qhgq", + "https://github.com/jquery/jquery/commit/753d591aea698e57d6db58c9f722cd0808619b1b", + "https://github.com/jquery/jquery/pull/4333", + "https://github.com/rails/jquery-rails/blob/master/CHANGELOG.md#434", + "https://hackerone.com/reports/454365", + "https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44601", + "https://linux.oracle.com/cve/CVE-2019-11358.html", + "https://linux.oracle.com/errata/ELSA-2020-4847.html", + "https://lists.apache.org/thread.html/08720ef215ee7ab3386c05a1a90a7d1c852bf0706f176a7816bf65fc@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/519eb0fd45642dcecd9ff74cb3e71c20a4753f7d82e2f07864b5108f@%3Cdev.drill.apache.org%3E", + "https://lists.apache.org/thread.html/5928aa293e39d248266472210c50f176cac1535220f2486e6a7fa844@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/6097cdbd6f0a337bedd9bb5cc441b2d525ff002a96531de367e4259f@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/88fb0362fd40e5b605ea8149f63241537b8b6fb5bfa315391fc5cbb7@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/b0656d359c7d40ec9f39c8cc61bca66802ef9a2a12ee199f5b0c1442@%3Cdev.drill.apache.org%3E", + "https://lists.apache.org/thread.html/b736d0784cf02f5a30fbb4c5902762a15ad6d47e17e2c5a17b7d6205@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/ba79cf1658741e9f146e4c59b50aee56656ea95d841d358d006c18b6@%3Ccommits.roller.apache.org%3E", + "https://lists.apache.org/thread.html/bcce5a9c532b386c68dab2f6b3ce8b0cc9b950ec551766e76391caa3@%3Ccommits.nifi.apache.org%3E", + "https://lists.apache.org/thread.html/f9bc3e55f4e28d1dcd1a69aae6d53e609a758e34d2869b4d798e13cc@%3Cissues.drill.apache.org%3E", + "https://lists.apache.org/thread.html/r2041a75d3fc09dec55adfd95d598b38d22715303f65c997c054844c9@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r2baacab6e0acb5a2092eb46ae04fd6c3e8277b4fd79b1ffb7f3254fa@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r38f0d1aa3c923c22977fe7376508f030f22e22c1379fbb155bf29766@%3Cdev.syncope.apache.org%3E", + "https://lists.apache.org/thread.html/r41b5bfe009c845f67d4f68948cc9419ac2d62e287804aafd72892b08@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r7aac081cbddb6baa24b75e74abf0929bf309b176755a53e3ed810355@%3Cdev.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r7d64895cc4dff84d0becfc572b20c0e4bf9bfa7b10c6f5f73e783734@%3Cdev.storm.apache.org%3E", + "https://lists.apache.org/thread.html/r7e8ebccb7c022e41295f6fdb7b971209b83702339f872ddd8cf8bf73@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/rac25da84ecdcd36f6de5ad0d255f4e967209bbbebddb285e231da37d@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/rca37935d661f4689cb4119f1b3b224413b22be161b678e6e6ce0c69b@%3Ccommits.nifi.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2019/05/msg00006.html", + "https://lists.debian.org/debian-lts-announce/2019/05/msg00029.html", + "https://lists.debian.org/debian-lts-announce/2020/02/msg00024.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/4UOAZIFCSZ3ENEFOR5IXX6NFAD3HV7FA/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5IABSKTYZ5JUGL735UKGXL5YPRYOPUYI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KYH3OAGR2RTCHRA5NOKX2TES7SNQMWGO/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QV3PKZC3PQCO3273HAT76PAQZFBEO4KP/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RLXRX23725JL366CNZGJZ7AQQB7LHQ6F/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WZW27UCJ5CYFL4KFFFMYMIBNMIU2ALG5/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-11358", + "https://seclists.org/bugtraq/2019/Apr/32", + "https://seclists.org/bugtraq/2019/Jun/12", + "https://seclists.org/bugtraq/2019/May/18", + "https://security.netapp.com/advisory/ntap-20190919-0001/", + "https://snyk.io/vuln/SNYK-JS-JQUERY-174006", + "https://www.debian.org/security/2019/dsa-4434", + "https://www.debian.org/security/2019/dsa-4460", + "https://www.drupal.org/sa-core-2019-006", + "https://www.oracle.com//security-alerts/cpujul2021.html", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/security-alerts/cpuoct2021.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.privacy-wise.com/mitigating-cve-2019-11358-in-old-versions-of-jquery/", + "https://www.synology.com/security/advisory/Synology_SA_19_19", + "https://www.tenable.com/security/tns-2019-08", + "https://www.tenable.com/security/tns-2020-02" + ], + "PublishedDate": "2019-04-20T00:29:00Z", + "LastModifiedDate": "2021-10-20T11:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/npm.json.golden b/integration/testdata/npm.json.golden new file mode 100644 index 000000000000..9bdb9ae37649 --- /dev/null +++ b/integration/testdata/npm.json.golden @@ -0,0 +1,393 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/npm", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "package-lock.json", + "Class": "lang-pkgs", + "Type": "npm", + "Packages": [ + { + "ID": "asap@2.0.6", + "Name": "asap", + "Identifier": { + "PURL": "pkg:npm/asap@2.0.6" + }, + "Version": "2.0.6", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 6, + "EndLine": 10 + } + ] + }, + { + "ID": "jquery@3.3.9", + "Name": "jquery", + "Identifier": { + "PURL": "pkg:npm/jquery@3.3.9" + }, + "Version": "3.3.9", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 11, + "EndLine": 15 + } + ] + }, + { + "ID": "js-tokens@4.0.0", + "Name": "js-tokens", + "Identifier": { + "PURL": "pkg:npm/js-tokens@4.0.0" + }, + "Version": "4.0.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 16, + "EndLine": 20 + } + ] + }, + { + "ID": "loose-envify@1.4.0", + "Name": "loose-envify", + "Identifier": { + "PURL": "pkg:npm/loose-envify@1.4.0" + }, + "Version": "1.4.0", + "Indirect": true, + "DependsOn": [ + "js-tokens@4.0.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 21, + "EndLine": 28 + } + ] + }, + { + "ID": "object-assign@4.1.1", + "Name": "object-assign", + "Identifier": { + "PURL": "pkg:npm/object-assign@4.1.1" + }, + "Version": "4.1.1", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 29, + "EndLine": 33 + } + ] + }, + { + "ID": "promise@8.0.3", + "Name": "promise", + "Identifier": { + "PURL": "pkg:npm/promise@8.0.3" + }, + "Version": "8.0.3", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "DependsOn": [ + "asap@2.0.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 34, + "EndLine": 41 + } + ] + }, + { + "ID": "prop-types@15.7.2", + "Name": "prop-types", + "Identifier": { + "PURL": "pkg:npm/prop-types@15.7.2" + }, + "Version": "15.7.2", + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "react-is@16.8.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 42, + "EndLine": 51 + } + ] + }, + { + "ID": "react@16.8.6", + "Name": "react", + "Identifier": { + "PURL": "pkg:npm/react@16.8.6" + }, + "Version": "16.8.6", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "prop-types@15.7.2", + "scheduler@0.13.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 52, + "EndLine": 62 + } + ] + }, + { + "ID": "react-is@16.8.6", + "Name": "react-is", + "Identifier": { + "PURL": "pkg:npm/react-is@16.8.6" + }, + "Version": "16.8.6", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 63, + "EndLine": 67 + } + ] + }, + { + "ID": "redux@4.0.1", + "Name": "redux", + "Identifier": { + "PURL": "pkg:npm/redux@4.0.1" + }, + "Version": "4.0.1", + "Licenses": [ + "MIT" + ], + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "symbol-observable@1.2.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 68, + "EndLine": 76 + } + ] + }, + { + "ID": "scheduler@0.13.6", + "Name": "scheduler", + "Identifier": { + "PURL": "pkg:npm/scheduler@0.13.6" + }, + "Version": "0.13.6", + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 77, + "EndLine": 85 + } + ] + }, + { + "ID": "symbol-observable@1.2.0", + "Name": "symbol-observable", + "Identifier": { + "PURL": "pkg:npm/symbol-observable@1.2.0" + }, + "Version": "1.2.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 86, + "EndLine": 90 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-11358", + "PkgID": "jquery@3.3.9", + "PkgName": "jquery", + "PkgIdentifier": { + "PURL": "pkg:npm/jquery@3.3.9" + }, + "InstalledVersion": "3.3.9", + "FixedVersion": "3.4.0", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-11358", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "jquery: Prototype pollution in object's prototype leading to denial of service, remote code execution, or property injection", + "Description": "jQuery before 3.4.0, as used in Drupal, Backdrop CMS, and other products, mishandles jQuery.extend(true, {}, ...) because of Object.prototype pollution. If an unsanitized source object contained an enumerable __proto__ property, it could extend the native Object.prototype.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-79" + ], + "VendorSeverity": { + "alma": 2, + "amazon": 2, + "arch-linux": 2, + "ghsa": 2, + "nodejs-security-wg": 2, + "nvd": 2, + "oracle-oval": 2, + "redhat": 2, + "ruby-advisory-db": 2, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "V2Score": 4.3, + "V3Score": 6.1 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "V3Score": 5.6 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00006.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00025.html", + "http://packetstormsecurity.com/files/152787/dotCMS-5.1.1-Vulnerable-Dependencies.html", + "http://packetstormsecurity.com/files/153237/RetireJS-CORS-Issue-Script-Execution.html", + "http://packetstormsecurity.com/files/156743/OctoberCMS-Insecure-Dependencies.html", + "http://seclists.org/fulldisclosure/2019/May/10", + "http://seclists.org/fulldisclosure/2019/May/11", + "http://seclists.org/fulldisclosure/2019/May/13", + "http://www.openwall.com/lists/oss-security/2019/06/03/2", + "http://www.securityfocus.com/bid/108023", + "https://access.redhat.com/errata/RHBA-2019:1570", + "https://access.redhat.com/errata/RHSA-2019:1456", + "https://access.redhat.com/errata/RHSA-2019:2587", + "https://access.redhat.com/errata/RHSA-2019:3023", + "https://access.redhat.com/errata/RHSA-2019:3024", + "https://access.redhat.com/security/cve/CVE-2019-11358", + "https://backdropcms.org/security/backdrop-sa-core-2019-009", + "https://blog.jquery.com/2019/04/10/jquery-3-4-0-released/", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11358", + "https://github.com/DanielRuf/snyk-js-jquery-174006?files=1", + "https://github.com/advisories/GHSA-6c3j-c64m-qhgq", + "https://github.com/jquery/jquery/commit/753d591aea698e57d6db58c9f722cd0808619b1b", + "https://github.com/jquery/jquery/pull/4333", + "https://github.com/rails/jquery-rails/blob/master/CHANGELOG.md#434", + "https://hackerone.com/reports/454365", + "https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44601", + "https://linux.oracle.com/cve/CVE-2019-11358.html", + "https://linux.oracle.com/errata/ELSA-2020-4847.html", + "https://lists.apache.org/thread.html/08720ef215ee7ab3386c05a1a90a7d1c852bf0706f176a7816bf65fc@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/519eb0fd45642dcecd9ff74cb3e71c20a4753f7d82e2f07864b5108f@%3Cdev.drill.apache.org%3E", + "https://lists.apache.org/thread.html/5928aa293e39d248266472210c50f176cac1535220f2486e6a7fa844@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/6097cdbd6f0a337bedd9bb5cc441b2d525ff002a96531de367e4259f@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/88fb0362fd40e5b605ea8149f63241537b8b6fb5bfa315391fc5cbb7@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/b0656d359c7d40ec9f39c8cc61bca66802ef9a2a12ee199f5b0c1442@%3Cdev.drill.apache.org%3E", + "https://lists.apache.org/thread.html/b736d0784cf02f5a30fbb4c5902762a15ad6d47e17e2c5a17b7d6205@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/ba79cf1658741e9f146e4c59b50aee56656ea95d841d358d006c18b6@%3Ccommits.roller.apache.org%3E", + "https://lists.apache.org/thread.html/bcce5a9c532b386c68dab2f6b3ce8b0cc9b950ec551766e76391caa3@%3Ccommits.nifi.apache.org%3E", + "https://lists.apache.org/thread.html/f9bc3e55f4e28d1dcd1a69aae6d53e609a758e34d2869b4d798e13cc@%3Cissues.drill.apache.org%3E", + "https://lists.apache.org/thread.html/r2041a75d3fc09dec55adfd95d598b38d22715303f65c997c054844c9@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r2baacab6e0acb5a2092eb46ae04fd6c3e8277b4fd79b1ffb7f3254fa@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r38f0d1aa3c923c22977fe7376508f030f22e22c1379fbb155bf29766@%3Cdev.syncope.apache.org%3E", + "https://lists.apache.org/thread.html/r41b5bfe009c845f67d4f68948cc9419ac2d62e287804aafd72892b08@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r7aac081cbddb6baa24b75e74abf0929bf309b176755a53e3ed810355@%3Cdev.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r7d64895cc4dff84d0becfc572b20c0e4bf9bfa7b10c6f5f73e783734@%3Cdev.storm.apache.org%3E", + "https://lists.apache.org/thread.html/r7e8ebccb7c022e41295f6fdb7b971209b83702339f872ddd8cf8bf73@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/rac25da84ecdcd36f6de5ad0d255f4e967209bbbebddb285e231da37d@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/rca37935d661f4689cb4119f1b3b224413b22be161b678e6e6ce0c69b@%3Ccommits.nifi.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2019/05/msg00006.html", + "https://lists.debian.org/debian-lts-announce/2019/05/msg00029.html", + "https://lists.debian.org/debian-lts-announce/2020/02/msg00024.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/4UOAZIFCSZ3ENEFOR5IXX6NFAD3HV7FA/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5IABSKTYZ5JUGL735UKGXL5YPRYOPUYI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KYH3OAGR2RTCHRA5NOKX2TES7SNQMWGO/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QV3PKZC3PQCO3273HAT76PAQZFBEO4KP/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RLXRX23725JL366CNZGJZ7AQQB7LHQ6F/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WZW27UCJ5CYFL4KFFFMYMIBNMIU2ALG5/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-11358", + "https://seclists.org/bugtraq/2019/Apr/32", + "https://seclists.org/bugtraq/2019/Jun/12", + "https://seclists.org/bugtraq/2019/May/18", + "https://security.netapp.com/advisory/ntap-20190919-0001/", + "https://snyk.io/vuln/SNYK-JS-JQUERY-174006", + "https://www.debian.org/security/2019/dsa-4434", + "https://www.debian.org/security/2019/dsa-4460", + "https://www.drupal.org/sa-core-2019-006", + "https://www.oracle.com//security-alerts/cpujul2021.html", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/security-alerts/cpuoct2021.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.privacy-wise.com/mitigating-cve-2019-11358-in-old-versions-of-jquery/", + "https://www.synology.com/security/advisory/Synology_SA_19_19", + "https://www.tenable.com/security/tns-2019-08", + "https://www.tenable.com/security/tns-2020-02" + ], + "PublishedDate": "2019-04-20T00:29:00Z", + "LastModifiedDate": "2021-10-20T11:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/nuget.json.golden b/integration/testdata/nuget.json.golden new file mode 100644 index 000000000000..064fb1e32d2f --- /dev/null +++ b/integration/testdata/nuget.json.golden @@ -0,0 +1,102 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/nuget", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "packages.lock.json", + "Class": "lang-pkgs", + "Type": "nuget", + "Packages": [ + { + "ID": "Newtonsoft.Json@12.0.3", + "Name": "Newtonsoft.Json", + "Identifier": { + "PURL": "pkg:nuget/Newtonsoft.Json@12.0.3" + }, + "Version": "12.0.3", + "Layer": {}, + "Locations": [ + { + "StartLine": 5, + "EndLine": 10 + } + ] + }, + { + "ID": "NuGet.Frameworks@5.7.0", + "Name": "NuGet.Frameworks", + "Identifier": { + "PURL": "pkg:nuget/NuGet.Frameworks@5.7.0" + }, + "Version": "5.7.0", + "DependsOn": [ + "Newtonsoft.Json@12.0.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 11, + "EndLine": 19 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "GHSA-5crp-9r3c-p9vr", + "PkgID": "Newtonsoft.Json@12.0.3", + "PkgName": "Newtonsoft.Json", + "PkgIdentifier": { + "PURL": "pkg:nuget/Newtonsoft.Json@12.0.3" + }, + "InstalledVersion": "12.0.3", + "FixedVersion": "13.0.1", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://github.com/advisories/GHSA-5crp-9r3c-p9vr", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Nuget", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anuget" + }, + "Title": "Improper Handling of Exceptional Conditions in Newtonsoft.Json", + "Description": "Newtonsoft.Json prior to version 13.0.1 is vulnerable to Insecure Defaults due to improper handling of expressions with high nesting level that lead to StackOverFlow exception or high CPU and RAM usage.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-755" + ], + "VendorSeverity": { + "ghsa": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + } + }, + "References": [ + "https://alephsecurity.com/2018/10/22/StackOverflowException/", + "https://alephsecurity.com/vulns/aleph-2018004" + ], + "PublishedDate": "2022-06-22T15:08:47Z", + "LastModifiedDate": "2022-06-27T18:37:23Z" + } + ] + } + ] +} diff --git a/integration/testdata/opensuse-leap-151.json.golden b/integration/testdata/opensuse-leap-151.json.golden new file mode 100644 index 000000000000..9ca650d3dbb3 --- /dev/null +++ b/integration/testdata/opensuse-leap-151.json.golden @@ -0,0 +1,131 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/opensuse-leap-151.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "opensuse.leap", + "Name": "15.1", + "EOSL": true + }, + "ImageID": "sha256:fef5ad254f6378f08071cfa2daaf05a1ce9857141c944b67a40742e63e65cecc", + "DiffIDs": [ + "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + ], + "ImageConfig": { + "architecture": "amd64", + "author": "Fabian Vogt \u003cfvogt@suse.com\u003e", + "created": "2019-11-05T15:54:41Z", + "history": [ + { + "created": "2019-11-05T15:54:41Z", + "created_by": "KIWI 9.17.16" + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Labels": { + "org.openbuildservice.disturl": "obs://build.opensuse.org/openSUSE:Leap:15.1:Images/images/740264e3294afe7ca32a3ea9deb863d2-opensuse-leap-image:docker", + "org.opencontainers.image.created": "2019-11-05T15:54:10.571514200Z", + "org.opencontainers.image.description": "Image containing a minimal environment for containers based on openSUSE Leap 15.1.", + "org.opencontainers.image.title": "openSUSE Leap 15.1 Base Container", + "org.opencontainers.image.url": "https://www.opensuse.org/", + "org.opencontainers.image.vendor": "openSUSE Project", + "org.opencontainers.image.version": "15.1.3.67", + "org.opensuse.base.created": "2019-11-05T15:54:10.571514200Z", + "org.opensuse.base.description": "Image containing a minimal environment for containers based on openSUSE Leap 15.1.", + "org.opensuse.base.disturl": "obs://build.opensuse.org/openSUSE:Leap:15.1:Images/images/740264e3294afe7ca32a3ea9deb863d2-opensuse-leap-image:docker", + "org.opensuse.base.reference": "registry.opensuse.org/opensuse/leap:15.1.3.67", + "org.opensuse.base.title": "openSUSE Leap 15.1 Base Container", + "org.opensuse.base.url": "https://www.opensuse.org/", + "org.opensuse.base.vendor": "openSUSE Project", + "org.opensuse.base.version": "15.1.3.67", + "org.opensuse.reference": "registry.opensuse.org/opensuse/leap:15.1.3.67" + } + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/opensuse-leap-151.tar.gz (opensuse.leap 15.1)", + "Class": "os-pkgs", + "Type": "opensuse.leap", + "Vulnerabilities": [ + { + "VulnerabilityID": "openSUSE-SU-2020:0062-1", + "PkgID": "libopenssl1_1@1.1.0i-lp151.8.3.1.x86_64", + "PkgName": "libopenssl1_1", + "PkgIdentifier": { + "PURL": "pkg:rpm/opensuse.leap/libopenssl1_1@1.1.0i-lp151.8.3.1?arch=x86_64\u0026distro=opensuse.leap-15.1" + }, + "InstalledVersion": "1.1.0i-lp151.8.3.1", + "FixedVersion": "1.1.0i-lp151.8.6.1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:5c5a844f54abd051851758624820ae6a08a9d6ddffddaebbb335601c32608fb3", + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + }, + "SeveritySource": "suse-cvrf", + "PrimaryURL": "https://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "DataSource": { + "ID": "suse-cvrf", + "Name": "SUSE CVRF", + "URL": "https://ftp.suse.com/pub/projects/security/cvrf/" + }, + "Title": "Security update for openssl-1_1", + "Description": "This update for openssl-1_1 fixes the following issues:\n\nSecurity issue fixed:\n\n- CVE-2019-1551: Fixed an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli (bsc#1158809). \n\nVarious FIPS related improvements were done:\n\n- FIPS: Backport SSH KDF to openssl (jsc#SLE-8789, bsc#1157775).\n- Port FIPS patches from SLE-12 (bsc#1158101).\n- Use SHA-2 in the RSA pairwise consistency check (bsc#1155346).\n\nThis update was imported from the SUSE:SLE-15-SP1:Update update project.", + "Severity": "MEDIUM", + "VendorSeverity": { + "suse-cvrf": 2 + }, + "References": [ + "https://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "https://www.suse.com/support/security/rating/" + ] + }, + { + "VulnerabilityID": "openSUSE-SU-2020:0062-1", + "PkgID": "openssl-1_1@1.1.0i-lp151.8.3.1.x86_64", + "PkgName": "openssl-1_1", + "PkgIdentifier": { + "PURL": "pkg:rpm/opensuse.leap/openssl-1_1@1.1.0i-lp151.8.3.1?arch=x86_64\u0026distro=opensuse.leap-15.1" + }, + "InstalledVersion": "1.1.0i-lp151.8.3.1", + "FixedVersion": "1.1.0i-lp151.8.6.1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:5c5a844f54abd051851758624820ae6a08a9d6ddffddaebbb335601c32608fb3", + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + }, + "SeveritySource": "suse-cvrf", + "PrimaryURL": "https://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "DataSource": { + "ID": "suse-cvrf", + "Name": "SUSE CVRF", + "URL": "https://ftp.suse.com/pub/projects/security/cvrf/" + }, + "Title": "Security update for openssl-1_1", + "Description": "This update for openssl-1_1 fixes the following issues:\n\nSecurity issue fixed:\n\n- CVE-2019-1551: Fixed an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli (bsc#1158809). \n\nVarious FIPS related improvements were done:\n\n- FIPS: Backport SSH KDF to openssl (jsc#SLE-8789, bsc#1157775).\n- Port FIPS patches from SLE-12 (bsc#1158101).\n- Use SHA-2 in the RSA pairwise consistency check (bsc#1155346).\n\nThis update was imported from the SUSE:SLE-15-SP1:Update update project.", + "Severity": "MEDIUM", + "VendorSeverity": { + "suse-cvrf": 2 + }, + "References": [ + "https://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", + "https://www.suse.com/support/security/rating/" + ] + } + ] + } + ] +} diff --git a/integration/testdata/oraclelinux-8.json.golden b/integration/testdata/oraclelinux-8.json.golden new file mode 100644 index 000000000000..6629e7fe0e55 --- /dev/null +++ b/integration/testdata/oraclelinux-8.json.golden @@ -0,0 +1,211 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/oraclelinux-8.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "oracle", + "Name": "8.0" + }, + "ImageID": "sha256:8988c7081e1f7b6c2928cbc4832b8a05968bb589d45d444ca1e3027c68f97f56", + "DiffIDs": [ + "sha256:91bac58a9ffae0dc2031e3f90d7bf04f66ccf019f180372152b0916d6e8a796f" + ], + "ImageConfig": { + "architecture": "amd64", + "author": "Oracle Linux Product Team \u003col-ovm-info_ww@oracle.com\u003e", + "container": "b5339bf682ea7c2a5b817144038646d7fa55d72e9943283bd0ff5e01eb8e5e40", + "created": "2019-10-15T21:23:26.082686371Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "author": "Oracle Linux Product Team \u003col-ovm-info_ww@oracle.com\u003e", + "created": "2018-08-30T21:49:27.028879762Z", + "created_by": "/bin/sh -c #(nop) MAINTAINER Oracle Linux Product Team \u003col-ovm-info_ww@oracle.com\u003e", + "empty_layer": true + }, + { + "author": "Oracle Linux Product Team \u003col-ovm-info_ww@oracle.com\u003e", + "created": "2019-10-15T21:23:25.715030578Z", + "created_by": "/bin/sh -c #(nop) ADD file:40a576906f00da132c935b4713c8012d0ac0422f507fc3239d647b0ed9930ed7 in / " + }, + { + "author": "Oracle Linux Product Team \u003col-ovm-info_ww@oracle.com\u003e", + "created": "2019-10-15T21:23:26.082686371Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:91bac58a9ffae0dc2031e3f90d7bf04f66ccf019f180372152b0916d6e8a796f" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:d2f0ba2a964f3d0b1935be99979b6930f8b989217ff6a5e6d4093e9df9baee11", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/oraclelinux-8.tar.gz (oracle 8.0)", + "Class": "os-pkgs", + "Type": "oracle", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-3823", + "PkgID": "curl@7.61.1-8.el8.x86_64", + "PkgName": "curl", + "PkgIdentifier": { + "PURL": "pkg:rpm/oracle/curl@7.61.1-8.el8?arch=x86_64\u0026distro=oracle-8.0" + }, + "InstalledVersion": "7.61.1-8.el8", + "FixedVersion": "7.61.1-11.el8", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e1b9aa33b064e76023cc29e9fac51bcebe62740c92ed38f09ba6205ddd9aa6f4", + "DiffID": "sha256:91bac58a9ffae0dc2031e3f90d7bf04f66ccf019f180372152b0916d6e8a796f" + }, + "SeveritySource": "oracle-oval", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-3823", + "DataSource": { + "ID": "oracle-oval", + "Name": "Oracle Linux OVAL definitions", + "URL": "https://linux.oracle.com/security/oval/" + }, + "Title": "curl: SMTP end-of-response out-of-bounds read", + "Description": "libcurl versions from 7.34.0 to before 7.64.0 are vulnerable to a heap out-of-bounds read in the code handling the end-of-response for SMTP. If the buffer passed to `smtp_endofresp()` isn't NUL terminated and contains no character ending the parsed number, and `len` is set to 5, then the `strtol()` call reads beyond the allocated buffer. The read contents will not be returned to the caller.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-125" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 3, + "nvd": 3, + "oracle-oval": 2, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V2Score": 5, + "V3Score": 7.5 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", + "V3Score": 4.3 + } + }, + "References": [ + "http://www.securityfocus.com/bid/106950", + "https://access.redhat.com/errata/RHSA-2019:3701", + "https://access.redhat.com/security/cve/CVE-2019-3823", + "https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2019-3823", + "https://cert-portal.siemens.com/productcert/pdf/ssa-936080.pdf", + "https://curl.haxx.se/docs/CVE-2019-3823.html", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-3823", + "https://linux.oracle.com/cve/CVE-2019-3823.html", + "https://linux.oracle.com/errata/ELSA-2019-3701.html", + "https://lists.apache.org/thread.html/8338a0f605bdbb3a6098bb76f666a95fc2b2f53f37fa1ecc89f1146f@%3Cdevnull.infra.apache.org%3E", + "https://security.gentoo.org/glsa/201903-03", + "https://security.netapp.com/advisory/ntap-20190315-0001/", + "https://ubuntu.com/security/notices/USN-3882-1", + "https://usn.ubuntu.com/3882-1/", + "https://www.debian.org/security/2019/dsa-4386", + "https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html" + ], + "PublishedDate": "2019-02-06T20:29:00Z", + "LastModifiedDate": "2021-03-09T15:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5436", + "PkgID": "curl@7.61.1-8.el8.x86_64", + "PkgName": "curl", + "PkgIdentifier": { + "PURL": "pkg:rpm/oracle/curl@7.61.1-8.el8?arch=x86_64\u0026distro=oracle-8.0" + }, + "InstalledVersion": "7.61.1-8.el8", + "FixedVersion": "7.61.1-12.el8", + "Status": "fixed", + "Layer": { + "Digest": "sha256:e1b9aa33b064e76023cc29e9fac51bcebe62740c92ed38f09ba6205ddd9aa6f4", + "DiffID": "sha256:91bac58a9ffae0dc2031e3f90d7bf04f66ccf019f180372152b0916d6e8a796f" + }, + "SeveritySource": "oracle-oval", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5436", + "DataSource": { + "ID": "oracle-oval", + "Name": "Oracle Linux OVAL definitions", + "URL": "https://linux.oracle.com/security/oval/" + }, + "Title": "curl: TFTP receive heap buffer overflow in tftp_receive_packet() function", + "Description": "A heap buffer overflow in the TFTP receiving code allows for DoS or arbitrary code execution in libcurl versions 7.19.4 through 7.64.1.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 1, + "arch-linux": 3, + "nvd": 3, + "oracle-oval": 2, + "photon": 3, + "redhat": 1, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00008.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00017.html", + "http://www.openwall.com/lists/oss-security/2019/09/11/6", + "https://access.redhat.com/security/cve/CVE-2019-5436", + "https://curl.haxx.se/docs/CVE-2019-5436.html", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5436", + "https://linux.oracle.com/cve/CVE-2019-5436.html", + "https://linux.oracle.com/errata/ELSA-2020-1792.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/SMG3V4VTX2SE3EW3HQTN3DDLQBTORQC2/", + "https://seclists.org/bugtraq/2020/Feb/36", + "https://security.gentoo.org/glsa/202003-29", + "https://security.netapp.com/advisory/ntap-20190606-0004/", + "https://support.f5.com/csp/article/K55133295", + "https://support.f5.com/csp/article/K55133295?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://ubuntu.com/security/notices/USN-3993-1", + "https://ubuntu.com/security/notices/USN-3993-2", + "https://www.debian.org/security/2020/dsa-4633", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + ], + "PublishedDate": "2019-05-28T19:29:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/packagesprops.json.golden b/integration/testdata/packagesprops.json.golden new file mode 100644 index 000000000000..5cce23a7c754 --- /dev/null +++ b/integration/testdata/packagesprops.json.golden @@ -0,0 +1,78 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/packagesprops", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Directory.Packages.props", + "Class": "lang-pkgs", + "Type": "packages-props", + "Packages": [ + { + "ID": "Newtonsoft.Json@9.0.1", + "Name": "Newtonsoft.Json", + "Identifier": { + "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1" + }, + "Version": "9.0.1", + "Layer": {} + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "GHSA-5crp-9r3c-p9vr", + "PkgID": "Newtonsoft.Json@9.0.1", + "PkgName": "Newtonsoft.Json", + "PkgIdentifier": { + "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1" + }, + "InstalledVersion": "9.0.1", + "FixedVersion": "13.0.1", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://github.com/advisories/GHSA-5crp-9r3c-p9vr", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Nuget", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anuget" + }, + "Title": "Improper Handling of Exceptional Conditions in Newtonsoft.Json", + "Description": "Newtonsoft.Json prior to version 13.0.1 is vulnerable to Insecure Defaults due to improper handling of expressions with high nesting level that lead to StackOverFlow exception or high CPU and RAM usage.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-755" + ], + "VendorSeverity": { + "ghsa": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + } + }, + "References": [ + "https://alephsecurity.com/2018/10/22/StackOverflowException/", + "https://alephsecurity.com/vulns/aleph-2018004" + ], + "PublishedDate": "2022-06-22T15:08:47Z", + "LastModifiedDate": "2022-06-27T18:37:23Z" + } + ] + } + ] +} diff --git a/integration/testdata/photon-30.json.golden b/integration/testdata/photon-30.json.golden new file mode 100644 index 000000000000..4e4c8d793faa --- /dev/null +++ b/integration/testdata/photon-30.json.golden @@ -0,0 +1,274 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/photon-30.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "photon", + "Name": "3.0" + }, + "ImageID": "sha256:5ccb5186b75cd13ff0d028f5b5b2bdf7ef7ca2b3d56eb2c6eb6c136077a6991a", + "DiffIDs": [ + "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "ed27e7f1fbd8ef9d3ea89947f682907e9a65a8e51bbe2e0eba60db6e69213848", + "created": "2019-08-23T22:26:32.857588774Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-08-23T22:26:32.53400229Z", + "created_by": "/bin/sh -c #(nop) ADD file:0d19c0b1adc18a00f073eeb1a9d6e5e4fdde392b20a3229ec0ef88642549b2df in / " + }, + { + "created": "2019-08-23T22:26:32.689364313Z", + "created_by": "/bin/sh -c #(nop) LABEL name=Photon OS x86_64/3.0 Base Image vendor=VMware build-date=20190823", + "empty_layer": true + }, + { + "created": "2019-08-23T22:26:32.857588774Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:e7cbb54381cebcd7eea8e391127352224e1d8268fd14bfa5c7dd53e507299f60", + "Labels": { + "build-date": "20190823", + "name": "Photon OS x86_64/3.0 Base Image", + "vendor": "VMware" + }, + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/photon-30.tar.gz (photon 3.0)", + "Class": "os-pkgs", + "Type": "photon", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@4.4.18-1.ph3.x86_64", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:rpm/photon/bash@4.4.18-1.ph3?arch=x86_64\u0026distro=photon-3.0" + }, + "InstalledVersion": "4.4.18-1.ph3", + "FixedVersion": "4.4.18-2.ph3", + "Status": "fixed", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + }, + "SeveritySource": "photon", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276", + "DataSource": { + "ID": "photon", + "Name": "Photon OS CVE metadata", + "URL": "https://packages.vmware.com/photon/photon_cve_metadata/" + }, + "Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped", + "Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-273" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "nvd": 3, + "oracle-oval": 1, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.2, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html", + "https://access.redhat.com/security/cve/CVE-2019-18276", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276", + "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff", + "https://linux.oracle.com/cve/CVE-2019-18276.html", + "https://linux.oracle.com/errata/ELSA-2021-1679.html", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://nvd.nist.gov/vuln/detail/CVE-2019-18276", + "https://security.gentoo.org/glsa/202105-34", + "https://security.netapp.com/advisory/ntap-20200430-0003/", + "https://www.youtube.com/watch?v=-wGtxJ8opa8" + ], + "PublishedDate": "2019-11-28T01:15:00Z", + "LastModifiedDate": "2021-05-26T12:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5481", + "PkgID": "curl@7.61.1-4.ph3.x86_64", + "PkgName": "curl", + "PkgIdentifier": { + "PURL": "pkg:rpm/photon/curl@7.61.1-4.ph3?arch=x86_64\u0026distro=photon-3.0" + }, + "InstalledVersion": "7.61.1-4.ph3", + "FixedVersion": "7.61.1-5.ph3", + "Status": "fixed", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + }, + "SeveritySource": "photon", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5481", + "DataSource": { + "ID": "photon", + "Name": "Photon OS CVE metadata", + "URL": "https://packages.vmware.com/photon/photon_cve_metadata/" + }, + "Title": "curl: double free due to subsequent call of realloc()", + "Description": "Double-free vulnerability in the FTP-kerberos code in cURL 7.52.0 to 7.65.3.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-415" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 2, + "nvd": 4, + "oracle-oval": 2, + "photon": 4, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "V3Score": 5.7 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00048.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00055.html", + "https://access.redhat.com/security/cve/CVE-2019-5481", + "https://curl.haxx.se/docs/CVE-2019-5481.html", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5481", + "https://linux.oracle.com/cve/CVE-2019-5481.html", + "https://linux.oracle.com/errata/ELSA-2020-1792.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6CI4QQ2RSZX4VCFM76SIWGKY6BY7UWIC/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RGDVKSLY5JUNJRLYRUA6CXGQ2LM63XC3/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UA7KDM2WPM5CJDDGOEGFV6SSGD2J7RNT/", + "https://seclists.org/bugtraq/2020/Feb/36", + "https://security.gentoo.org/glsa/202003-29", + "https://security.netapp.com/advisory/ntap-20191004-0003/", + "https://ubuntu.com/security/notices/USN-4129-1", + "https://www.debian.org/security/2020/dsa-4633", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html" + ], + "PublishedDate": "2019-09-16T19:15:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5481", + "PkgID": "curl-libs@7.61.1-4.ph3.x86_64", + "PkgName": "curl-libs", + "PkgIdentifier": { + "PURL": "pkg:rpm/photon/curl-libs@7.61.1-4.ph3?arch=x86_64\u0026distro=photon-3.0" + }, + "InstalledVersion": "7.61.1-4.ph3", + "FixedVersion": "7.61.1-5.ph3", + "Status": "fixed", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + }, + "SeveritySource": "photon", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5481", + "DataSource": { + "ID": "photon", + "Name": "Photon OS CVE metadata", + "URL": "https://packages.vmware.com/photon/photon_cve_metadata/" + }, + "Title": "curl: double free due to subsequent call of realloc()", + "Description": "Double-free vulnerability in the FTP-kerberos code in cURL 7.52.0 to 7.65.3.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-415" + ], + "VendorSeverity": { + "amazon": 2, + "arch-linux": 2, + "nvd": 4, + "oracle-oval": 2, + "photon": 4, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "V3Score": 5.7 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00048.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00055.html", + "https://access.redhat.com/security/cve/CVE-2019-5481", + "https://curl.haxx.se/docs/CVE-2019-5481.html", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5481", + "https://linux.oracle.com/cve/CVE-2019-5481.html", + "https://linux.oracle.com/errata/ELSA-2020-1792.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6CI4QQ2RSZX4VCFM76SIWGKY6BY7UWIC/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RGDVKSLY5JUNJRLYRUA6CXGQ2LM63XC3/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UA7KDM2WPM5CJDDGOEGFV6SSGD2J7RNT/", + "https://seclists.org/bugtraq/2020/Feb/36", + "https://security.gentoo.org/glsa/202003-29", + "https://security.netapp.com/advisory/ntap-20191004-0003/", + "https://ubuntu.com/security/notices/USN-4129-1", + "https://www.debian.org/security/2020/dsa-4633", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html" + ], + "PublishedDate": "2019-09-16T19:15:00Z", + "LastModifiedDate": "2020-10-20T22:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/pip.json.golden b/integration/testdata/pip.json.golden new file mode 100644 index 000000000000..29873d943d55 --- /dev/null +++ b/integration/testdata/pip.json.golden @@ -0,0 +1,195 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/pip", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "requirements.txt", + "Class": "lang-pkgs", + "Type": "pip", + "Packages": [ + { + "Name": "Flask", + "Identifier": { + "PURL": "pkg:pypi/flask@2.0.0" + }, + "Version": "2.0.0", + "Layer": {} + }, + { + "Name": "Jinja2", + "Identifier": { + "PURL": "pkg:pypi/jinja2@3.0.0" + }, + "Version": "3.0.0", + "Layer": {} + }, + { + "Name": "Werkzeug", + "Identifier": { + "PURL": "pkg:pypi/werkzeug@0.11" + }, + "Version": "0.11", + "Layer": {} + }, + { + "Name": "click", + "Identifier": { + "PURL": "pkg:pypi/click@8.0.0" + }, + "Version": "8.0.0", + "Layer": {} + }, + { + "Name": "itsdangerous", + "Identifier": { + "PURL": "pkg:pypi/itsdangerous@2.0.0" + }, + "Version": "2.0.0", + "Layer": {} + }, + { + "Name": "oauth2-client", + "Identifier": { + "PURL": "pkg:pypi/oauth2-client@4.0.0" + }, + "Version": "4.0.0", + "Layer": {} + }, + { + "Name": "python-gitlab", + "Identifier": { + "PURL": "pkg:pypi/python-gitlab@2.0.0" + }, + "Version": "2.0.0", + "Layer": {} + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-14806", + "PkgName": "Werkzeug", + "PkgIdentifier": { + "PURL": "pkg:pypi/werkzeug@0.11" + }, + "InstalledVersion": "0.11", + "FixedVersion": "0.15.3", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-14806", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Pip", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + }, + "Title": "python-werkzeug: insufficient debugger PIN randomness vulnerability", + "Description": "Pallets Werkzeug before 0.15.3, when used with Docker, has insufficient debugger PIN randomness because Docker containers share the same machine id.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-331" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3, + "redhat": 2, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V2Score": 5, + "V3Score": 7.5 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 7.5 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00034.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00047.html", + "https://access.redhat.com/security/cve/CVE-2019-14806", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-14806", + "https://github.com/advisories/GHSA-gq9m-qvpx-68hc", + "https://github.com/pallets/werkzeug/blob/7fef41b120327d3912fbe12fb64f1951496fcf3e/src/werkzeug/debug/__init__.py#L168", + "https://github.com/pallets/werkzeug/commit/00bc43b1672e662e5e3b8cecd79e67fc968fa246", + "https://nvd.nist.gov/vuln/detail/CVE-2019-14806", + "https://palletsprojects.com/blog/werkzeug-0-15-3-released/", + "https://ubuntu.com/security/notices/USN-4655-1" + ], + "PublishedDate": "2019-08-09T15:15:00Z", + "LastModifiedDate": "2019-09-11T00:15:00Z" + }, + { + "VulnerabilityID": "CVE-2020-28724", + "PkgName": "Werkzeug", + "PkgIdentifier": { + "PURL": "pkg:pypi/werkzeug@0.11" + }, + "InstalledVersion": "0.11", + "FixedVersion": "0.11.6", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-28724", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Pip", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + }, + "Title": "python-werkzeug: open redirect via double slash in the URL", + "Description": "Open redirect vulnerability in werkzeug before 0.11.6 via a double slash in the URL.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-601" + ], + "VendorSeverity": { + "ghsa": 2, + "nvd": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "V2Score": 5.8, + "V3Score": 6.1 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N", + "V3Score": 5.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2020-28724", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-28724", + "https://github.com/advisories/GHSA-3p3h-qghp-hvh2", + "https://github.com/pallets/flask/issues/1639", + "https://github.com/pallets/werkzeug/issues/822", + "https://github.com/pallets/werkzeug/pull/890/files", + "https://nvd.nist.gov/vuln/detail/CVE-2020-28724", + "https://ubuntu.com/security/notices/USN-4655-1" + ], + "PublishedDate": "2020-11-18T15:15:00Z", + "LastModifiedDate": "2020-12-01T16:05:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/pipenv.json.golden b/integration/testdata/pipenv.json.golden new file mode 100644 index 000000000000..e5076aa4571b --- /dev/null +++ b/integration/testdata/pipenv.json.golden @@ -0,0 +1,153 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/pipenv", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Pipfile.lock", + "Class": "lang-pkgs", + "Type": "pipenv", + "Packages": [ + { + "Name": "werkzeug", + "Identifier": { + "PURL": "pkg:pypi/werkzeug@0.11.1" + }, + "Version": "0.11.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 19, + "EndLine": 26 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-14806", + "PkgName": "werkzeug", + "PkgIdentifier": { + "PURL": "pkg:pypi/werkzeug@0.11.1" + }, + "InstalledVersion": "0.11.1", + "FixedVersion": "0.15.3", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-14806", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Pip", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + }, + "Title": "python-werkzeug: insufficient debugger PIN randomness vulnerability", + "Description": "Pallets Werkzeug before 0.15.3, when used with Docker, has insufficient debugger PIN randomness because Docker containers share the same machine id.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-331" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3, + "redhat": 2, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V2Score": 5, + "V3Score": 7.5 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 7.5 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00034.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00047.html", + "https://access.redhat.com/security/cve/CVE-2019-14806", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-14806", + "https://github.com/advisories/GHSA-gq9m-qvpx-68hc", + "https://github.com/pallets/werkzeug/blob/7fef41b120327d3912fbe12fb64f1951496fcf3e/src/werkzeug/debug/__init__.py#L168", + "https://github.com/pallets/werkzeug/commit/00bc43b1672e662e5e3b8cecd79e67fc968fa246", + "https://nvd.nist.gov/vuln/detail/CVE-2019-14806", + "https://palletsprojects.com/blog/werkzeug-0-15-3-released/", + "https://ubuntu.com/security/notices/USN-4655-1" + ], + "PublishedDate": "2019-08-09T15:15:00Z", + "LastModifiedDate": "2019-09-11T00:15:00Z" + }, + { + "VulnerabilityID": "CVE-2020-28724", + "PkgName": "werkzeug", + "PkgIdentifier": { + "PURL": "pkg:pypi/werkzeug@0.11.1" + }, + "InstalledVersion": "0.11.1", + "FixedVersion": "0.11.6", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-28724", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Pip", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + }, + "Title": "python-werkzeug: open redirect via double slash in the URL", + "Description": "Open redirect vulnerability in werkzeug before 0.11.6 via a double slash in the URL.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-601" + ], + "VendorSeverity": { + "ghsa": 2, + "nvd": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "V2Score": 5.8, + "V3Score": 6.1 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N", + "V3Score": 5.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2020-28724", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-28724", + "https://github.com/advisories/GHSA-3p3h-qghp-hvh2", + "https://github.com/pallets/flask/issues/1639", + "https://github.com/pallets/werkzeug/issues/822", + "https://github.com/pallets/werkzeug/pull/890/files", + "https://nvd.nist.gov/vuln/detail/CVE-2020-28724", + "https://ubuntu.com/security/notices/USN-4655-1" + ], + "PublishedDate": "2020-11-18T15:15:00Z", + "LastModifiedDate": "2020-12-01T16:05:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/pnpm.json.golden b/integration/testdata/pnpm.json.golden new file mode 100644 index 000000000000..305552bf1f2a --- /dev/null +++ b/integration/testdata/pnpm.json.golden @@ -0,0 +1,213 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/pnpm", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "pnpm-lock.yaml", + "Class": "lang-pkgs", + "Type": "pnpm", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-11358", + "PkgID": "jquery@3.3.9", + "PkgName": "jquery", + "PkgIdentifier": { + "PURL": "pkg:npm/jquery@3.3.9" + }, + "InstalledVersion": "3.3.9", + "FixedVersion": "3.4.0", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-11358", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "jquery: Prototype pollution in object's prototype leading to denial of service, remote code execution, or property injection", + "Description": "jQuery before 3.4.0, as used in Drupal, Backdrop CMS, and other products, mishandles jQuery.extend(true, {}, ...) because of Object.prototype pollution. If an unsanitized source object contained an enumerable __proto__ property, it could extend the native Object.prototype.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-79" + ], + "VendorSeverity": { + "alma": 2, + "amazon": 2, + "arch-linux": 2, + "ghsa": 2, + "nodejs-security-wg": 2, + "nvd": 2, + "oracle-oval": 2, + "redhat": 2, + "ruby-advisory-db": 2, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "V2Score": 4.3, + "V3Score": 6.1 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "V3Score": 5.6 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00006.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00025.html", + "http://packetstormsecurity.com/files/152787/dotCMS-5.1.1-Vulnerable-Dependencies.html", + "http://packetstormsecurity.com/files/153237/RetireJS-CORS-Issue-Script-Execution.html", + "http://packetstormsecurity.com/files/156743/OctoberCMS-Insecure-Dependencies.html", + "http://seclists.org/fulldisclosure/2019/May/10", + "http://seclists.org/fulldisclosure/2019/May/11", + "http://seclists.org/fulldisclosure/2019/May/13", + "http://www.openwall.com/lists/oss-security/2019/06/03/2", + "http://www.securityfocus.com/bid/108023", + "https://access.redhat.com/errata/RHBA-2019:1570", + "https://access.redhat.com/errata/RHSA-2019:1456", + "https://access.redhat.com/errata/RHSA-2019:2587", + "https://access.redhat.com/errata/RHSA-2019:3023", + "https://access.redhat.com/errata/RHSA-2019:3024", + "https://access.redhat.com/security/cve/CVE-2019-11358", + "https://backdropcms.org/security/backdrop-sa-core-2019-009", + "https://blog.jquery.com/2019/04/10/jquery-3-4-0-released/", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11358", + "https://github.com/DanielRuf/snyk-js-jquery-174006?files=1", + "https://github.com/advisories/GHSA-6c3j-c64m-qhgq", + "https://github.com/jquery/jquery/commit/753d591aea698e57d6db58c9f722cd0808619b1b", + "https://github.com/jquery/jquery/pull/4333", + "https://github.com/rails/jquery-rails/blob/master/CHANGELOG.md#434", + "https://hackerone.com/reports/454365", + "https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44601", + "https://linux.oracle.com/cve/CVE-2019-11358.html", + "https://linux.oracle.com/errata/ELSA-2020-4847.html", + "https://lists.apache.org/thread.html/08720ef215ee7ab3386c05a1a90a7d1c852bf0706f176a7816bf65fc@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/519eb0fd45642dcecd9ff74cb3e71c20a4753f7d82e2f07864b5108f@%3Cdev.drill.apache.org%3E", + "https://lists.apache.org/thread.html/5928aa293e39d248266472210c50f176cac1535220f2486e6a7fa844@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/6097cdbd6f0a337bedd9bb5cc441b2d525ff002a96531de367e4259f@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/88fb0362fd40e5b605ea8149f63241537b8b6fb5bfa315391fc5cbb7@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/b0656d359c7d40ec9f39c8cc61bca66802ef9a2a12ee199f5b0c1442@%3Cdev.drill.apache.org%3E", + "https://lists.apache.org/thread.html/b736d0784cf02f5a30fbb4c5902762a15ad6d47e17e2c5a17b7d6205@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/ba79cf1658741e9f146e4c59b50aee56656ea95d841d358d006c18b6@%3Ccommits.roller.apache.org%3E", + "https://lists.apache.org/thread.html/bcce5a9c532b386c68dab2f6b3ce8b0cc9b950ec551766e76391caa3@%3Ccommits.nifi.apache.org%3E", + "https://lists.apache.org/thread.html/f9bc3e55f4e28d1dcd1a69aae6d53e609a758e34d2869b4d798e13cc@%3Cissues.drill.apache.org%3E", + "https://lists.apache.org/thread.html/r2041a75d3fc09dec55adfd95d598b38d22715303f65c997c054844c9@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r2baacab6e0acb5a2092eb46ae04fd6c3e8277b4fd79b1ffb7f3254fa@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r38f0d1aa3c923c22977fe7376508f030f22e22c1379fbb155bf29766@%3Cdev.syncope.apache.org%3E", + "https://lists.apache.org/thread.html/r41b5bfe009c845f67d4f68948cc9419ac2d62e287804aafd72892b08@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r7aac081cbddb6baa24b75e74abf0929bf309b176755a53e3ed810355@%3Cdev.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r7d64895cc4dff84d0becfc572b20c0e4bf9bfa7b10c6f5f73e783734@%3Cdev.storm.apache.org%3E", + "https://lists.apache.org/thread.html/r7e8ebccb7c022e41295f6fdb7b971209b83702339f872ddd8cf8bf73@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/rac25da84ecdcd36f6de5ad0d255f4e967209bbbebddb285e231da37d@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/rca37935d661f4689cb4119f1b3b224413b22be161b678e6e6ce0c69b@%3Ccommits.nifi.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2019/05/msg00006.html", + "https://lists.debian.org/debian-lts-announce/2019/05/msg00029.html", + "https://lists.debian.org/debian-lts-announce/2020/02/msg00024.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/4UOAZIFCSZ3ENEFOR5IXX6NFAD3HV7FA/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5IABSKTYZ5JUGL735UKGXL5YPRYOPUYI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KYH3OAGR2RTCHRA5NOKX2TES7SNQMWGO/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QV3PKZC3PQCO3273HAT76PAQZFBEO4KP/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RLXRX23725JL366CNZGJZ7AQQB7LHQ6F/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WZW27UCJ5CYFL4KFFFMYMIBNMIU2ALG5/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-11358", + "https://seclists.org/bugtraq/2019/Apr/32", + "https://seclists.org/bugtraq/2019/Jun/12", + "https://seclists.org/bugtraq/2019/May/18", + "https://security.netapp.com/advisory/ntap-20190919-0001/", + "https://snyk.io/vuln/SNYK-JS-JQUERY-174006", + "https://www.debian.org/security/2019/dsa-4434", + "https://www.debian.org/security/2019/dsa-4460", + "https://www.drupal.org/sa-core-2019-006", + "https://www.oracle.com//security-alerts/cpujul2021.html", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/security-alerts/cpuoct2021.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.privacy-wise.com/mitigating-cve-2019-11358-in-old-versions-of-jquery/", + "https://www.synology.com/security/advisory/Synology_SA_19_19", + "https://www.tenable.com/security/tns-2019-08", + "https://www.tenable.com/security/tns-2020-02" + ], + "PublishedDate": "2019-04-20T00:29:00Z", + "LastModifiedDate": "2021-10-20T11:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-10744", + "PkgID": "lodash@4.17.4", + "PkgName": "lodash", + "PkgIdentifier": { + "PURL": "pkg:npm/lodash@4.17.4" + }, + "InstalledVersion": "4.17.4", + "FixedVersion": "4.17.12", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-10744", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "nodejs-lodash: prototype pollution in defaultsDeep function leading to modifying properties", + "Description": "Versions of lodash lower than 4.17.12 are vulnerable to Prototype Pollution. The function defaultsDeep could be tricked into adding or modifying properties of Object.prototype using a constructor payload.", + "Severity": "CRITICAL", + "VendorSeverity": { + "ghsa": 4, + "nvd": 4, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:N/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", + "V2Score": 6.4, + "V3Score": 9.1 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", + "V3Score": 9.1 + } + }, + "References": [ + "https://access.redhat.com/errata/RHSA-2019:3024", + "https://access.redhat.com/security/cve/CVE-2019-10744", + "https://github.com/advisories/GHSA-jf85-cpcp-j695", + "https://github.com/lodash/lodash/pull/4336", + "https://nvd.nist.gov/vuln/detail/CVE-2019-10744", + "https://security.netapp.com/advisory/ntap-20191004-0005/", + "https://snyk.io/vuln/SNYK-JS-LODASH-450202", + "https://support.f5.com/csp/article/K47105354?utm_source=f5support\u0026amp;utm_medium=RSS", + "https://www.npmjs.com/advisories/1065", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html" + ], + "PublishedDate": "2019-07-26T00:15:00Z", + "LastModifiedDate": "2021-03-16T13:57:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/poetry.json.golden b/integration/testdata/poetry.json.golden new file mode 100644 index 000000000000..394977d3ea02 --- /dev/null +++ b/integration/testdata/poetry.json.golden @@ -0,0 +1,117 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/poetry", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "poetry.lock", + "Class": "lang-pkgs", + "Type": "poetry", + "Packages": [ + { + "ID": "click@8.1.3", + "Name": "click", + "Identifier": { + "PURL": "pkg:pypi/click@8.1.3" + }, + "Version": "8.1.3", + "DependsOn": [ + "colorama@0.4.6" + ], + "Layer": {} + }, + { + "ID": "colorama@0.4.6", + "Name": "colorama", + "Identifier": { + "PURL": "pkg:pypi/colorama@0.4.6" + }, + "Version": "0.4.6", + "Indirect": true, + "Layer": {} + }, + { + "ID": "werkzeug@0.14", + "Name": "werkzeug", + "Identifier": { + "PURL": "pkg:pypi/werkzeug@0.14" + }, + "Version": "0.14", + "Layer": {} + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-14806", + "PkgID": "werkzeug@0.14", + "PkgName": "werkzeug", + "PkgIdentifier": { + "PURL": "pkg:pypi/werkzeug@0.14" + }, + "InstalledVersion": "0.14", + "FixedVersion": "0.15.3", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-14806", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Pip", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + }, + "Title": "python-werkzeug: insufficient debugger PIN randomness vulnerability", + "Description": "Pallets Werkzeug before 0.15.3, when used with Docker, has insufficient debugger PIN randomness because Docker containers share the same machine id.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-331" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3, + "redhat": 2, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V2Score": 5, + "V3Score": 7.5 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 7.5 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00034.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00047.html", + "https://access.redhat.com/security/cve/CVE-2019-14806", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-14806", + "https://github.com/advisories/GHSA-gq9m-qvpx-68hc", + "https://github.com/pallets/werkzeug/blob/7fef41b120327d3912fbe12fb64f1951496fcf3e/src/werkzeug/debug/__init__.py#L168", + "https://github.com/pallets/werkzeug/commit/00bc43b1672e662e5e3b8cecd79e67fc968fa246", + "https://nvd.nist.gov/vuln/detail/CVE-2019-14806", + "https://palletsprojects.com/blog/werkzeug-0-15-3-released/", + "https://ubuntu.com/security/notices/USN-4655-1" + ], + "PublishedDate": "2019-08-09T15:15:00Z", + "LastModifiedDate": "2019-09-11T00:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/pom-cyclonedx.json.golden b/integration/testdata/pom-cyclonedx.json.golden new file mode 100644 index 000000000000..27f54a71848b --- /dev/null +++ b/integration/testdata/pom-cyclonedx.json.golden @@ -0,0 +1,325 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000005", + "version": 1, + "metadata": { + "timestamp": "2021-08-25T12:20:30+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000001", + "type": "application", + "name": "testdata/fixtures/repo/pom", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000002", + "type": "application", + "name": "pom.xml", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "pom" + } + ] + }, + { + "bom-ref": "pkg:maven/com.example/log4shell@1.0-SNAPSHOT", + "type": "library", + "group": "com.example", + "name": "log4shell", + "version": "1.0-SNAPSHOT", + "purl": "pkg:maven/com.example/log4shell@1.0-SNAPSHOT", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "com.example:log4shell:1.0-SNAPSHOT" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "pom" + } + ] + }, + { + "bom-ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "type": "library", + "group": "com.fasterxml.jackson.core", + "name": "jackson-databind", + "version": "2.9.1", + "purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "com.fasterxml.jackson.core:jackson-databind:2.9.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "pom" + } + ] + } + ], + "dependencies": [ + { + "ref": "3ff14136-e09f-4df9-80ea-000000000001", + "dependsOn": [ + "3ff14136-e09f-4df9-80ea-000000000002" + ] + }, + { + "ref": "3ff14136-e09f-4df9-80ea-000000000002", + "dependsOn": [ + "pkg:maven/com.example/log4shell@1.0-SNAPSHOT", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + ] + }, + { + "ref": "pkg:maven/com.example/log4shell@1.0-SNAPSHOT", + "dependsOn": [ + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + ] + }, + { + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "dependsOn": [] + } + ], + "vulnerabilities": [ + { + "id": "CVE-2020-9548", + "source": { + "name": "ghsa", + "url": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven" + }, + "ratings": [ + { + "source": { + "name": "ghsa" + }, + "severity": "critical" + }, + { + "source": { + "name": "nvd" + }, + "score": 6.8, + "severity": "medium", + "method": "CVSSv2", + "vector": "AV:N/AC:M/Au:N/C:P/I:P/A:P" + }, + { + "source": { + "name": "nvd" + }, + "score": 9.8, + "severity": "critical", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "redhat" + }, + "score": 8.1, + "severity": "high", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H" + } + ], + "cwes": [ + 502 + ], + "description": "FasterXML jackson-databind 2.x before 2.9.10.4 mishandles the interaction between serialization gadgets and typing, related to br.com.anteros.dbcp.AnterosDBCPConfig (aka anteros-core).", + "recommendation": "Upgrade com.fasterxml.jackson.core:jackson-databind to version 2.9.10.4", + "advisories": [ + { + "url": "https://avd.aquasec.com/nvd/cve-2020-9548" + }, + { + "url": "https://access.redhat.com/security/cve/CVE-2020-9548" + }, + { + "url": "https://github.com/FasterXML/jackson-databind/issues/2634" + }, + { + "url": "https://github.com/advisories/GHSA-p43x-xfjf-5jhr" + }, + { + "url": "https://lists.apache.org/thread.html/r35d30db00440ef63b791c4b7f7acb036e14d4a23afa2a249cb66c0fd@%3Cissues.zookeeper.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/r9464a40d25c3ba1a55622db72f113eb494a889656962d098c70c5bb1@%3Cdev.zookeeper.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/r98c9b6e4c9e17792e2cd1ec3e4aa20b61a791939046d3f10888176bb@%3Cissues.zookeeper.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/rb6fecb5e96a6d61e175ff49f33f2713798dd05cf03067c169d195596@%3Cissues.zookeeper.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/rd5a4457be4623038c3989294429bc063eec433a2e55995d81591e2ca@%3Cissues.zookeeper.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/rdd49ab9565bec436a896bc00c4b9fc9dce1598e106c318524fbdfec6@%3Cissues.zookeeper.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/rdd4df698d5d8e635144d2994922bf0842e933809eae259521f3b5097@%3Cissues.zookeeper.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/rf1bbc0ea4a9f014cf94df9a12a6477d24a27f52741dbc87f2fd52ff2@%3Cissues.geode.apache.org%3E" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2020/03/msg00008.html" + }, + { + "url": "https://medium.com/@cowtowncoder/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-9548" + }, + { + "url": "https://security.netapp.com/advisory/ntap-20200904-0006/" + }, + { + "url": "https://www.oracle.com/security-alerts/cpujan2021.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpujul2020.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpuoct2020.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpuoct2021.html" + } + ], + "published": "2020-03-02T04:15:00+00:00", + "updated": "2021-12-02T21:23:00+00:00", + "affects": [ + { + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "versions": [ + { + "version": "2.9.1", + "status": "affected" + } + ] + } + ] + }, + { + "id": "CVE-2021-20190", + "source": { + "name": "glad", + "url": "https://gitlab.com/gitlab-org/advisories-community" + }, + "ratings": [ + { + "source": { + "name": "ghsa" + }, + "severity": "high" + }, + { + "source": { + "name": "nvd" + }, + "score": 8.3, + "severity": "high", + "method": "CVSSv2", + "vector": "AV:N/AC:M/Au:N/C:P/I:P/A:C" + }, + { + "source": { + "name": "nvd" + }, + "score": 8.1, + "severity": "high", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "redhat" + }, + "score": 8.1, + "severity": "high", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H" + } + ], + "cwes": [ + 502 + ], + "description": "A flaw was found in jackson-databind before 2.9.10.7. FasterXML mishandles the interaction between serialization gadgets and typing. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.", + "recommendation": "Upgrade com.fasterxml.jackson.core:jackson-databind to version 2.9.10.7", + "advisories": [ + { + "url": "https://avd.aquasec.com/nvd/cve-2021-20190" + }, + { + "url": "https://access.redhat.com/security/cve/CVE-2021-20190" + }, + { + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1916633" + }, + { + "url": "https://github.com/FasterXML/jackson-databind/commit/7dbf51bf78d157098074a20bd9da39bd48c18e4a" + }, + { + "url": "https://github.com/FasterXML/jackson-databind/issues/2854" + }, + { + "url": "https://github.com/advisories/GHSA-5949-rw7g-wx7w" + }, + { + "url": "https://lists.apache.org/thread.html/r380e9257bacb8551ee6fcf2c59890ae9477b2c78e553fa9ea08e9d9a@%3Ccommits.nifi.apache.org%3E" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2021/04/msg00025.html" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-20190" + }, + { + "url": "https://security.netapp.com/advisory/ntap-20210219-0008/" + } + ], + "published": "2021-01-19T17:15:00+00:00", + "updated": "2021-07-20T23:15:00+00:00", + "affects": [ + { + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "versions": [ + { + "version": "2.9.1", + "status": "affected" + } + ] + } + ] + } + ] +} diff --git a/integration/testdata/pom.json.golden b/integration/testdata/pom.json.golden new file mode 100644 index 000000000000..244817f1e4c7 --- /dev/null +++ b/integration/testdata/pom.json.golden @@ -0,0 +1,147 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/pom", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "pom.xml", + "Class": "lang-pkgs", + "Type": "pom", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-9548", + "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", + "PkgName": "com.fasterxml.jackson.core:jackson-databind", + "PkgIdentifier": { + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + }, + "InstalledVersion": "2.9.1", + "FixedVersion": "2.9.10.4", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-9548", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Maven", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven" + }, + "Title": "jackson-databind: Serialization gadgets in anteros-core", + "Description": "FasterXML jackson-databind 2.x before 2.9.10.4 mishandles the interaction between serialization gadgets and typing, related to br.com.anteros.dbcp.AnterosDBCPConfig (aka anteros-core).", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-502" + ], + "VendorSeverity": { + "ghsa": 4, + "nvd": 4, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 6.8, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 8.1 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2020-9548", + "https://github.com/FasterXML/jackson-databind/issues/2634", + "https://github.com/advisories/GHSA-p43x-xfjf-5jhr", + "https://lists.apache.org/thread.html/r35d30db00440ef63b791c4b7f7acb036e14d4a23afa2a249cb66c0fd@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/r9464a40d25c3ba1a55622db72f113eb494a889656962d098c70c5bb1@%3Cdev.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/r98c9b6e4c9e17792e2cd1ec3e4aa20b61a791939046d3f10888176bb@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rb6fecb5e96a6d61e175ff49f33f2713798dd05cf03067c169d195596@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rd5a4457be4623038c3989294429bc063eec433a2e55995d81591e2ca@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rdd49ab9565bec436a896bc00c4b9fc9dce1598e106c318524fbdfec6@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rdd4df698d5d8e635144d2994922bf0842e933809eae259521f3b5097@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rf1bbc0ea4a9f014cf94df9a12a6477d24a27f52741dbc87f2fd52ff2@%3Cissues.geode.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2020/03/msg00008.html", + "https://medium.com/@cowtowncoder/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062", + "https://nvd.nist.gov/vuln/detail/CVE-2020-9548", + "https://security.netapp.com/advisory/ntap-20200904-0006/", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/security-alerts/cpuoct2021.html" + ], + "PublishedDate": "2020-03-02T04:15:00Z", + "LastModifiedDate": "2021-12-02T21:23:00Z" + }, + { + "VulnerabilityID": "CVE-2021-20190", + "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", + "PkgName": "com.fasterxml.jackson.core:jackson-databind", + "PkgIdentifier": { + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + }, + "InstalledVersion": "2.9.1", + "FixedVersion": "2.9.10.7", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-20190", + "DataSource": { + "ID": "glad", + "Name": "GitLab Advisory Database Community", + "URL": "https://gitlab.com/gitlab-org/advisories-community" + }, + "Title": "jackson-databind: mishandles the interaction between serialization gadgets and typing, related to javax.swing", + "Description": "A flaw was found in jackson-databind before 2.9.10.7. FasterXML mishandles the interaction between serialization gadgets and typing. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-502" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:P/A:C", + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 8.3, + "V3Score": 8.1 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 8.1 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2021-20190", + "https://bugzilla.redhat.com/show_bug.cgi?id=1916633", + "https://github.com/FasterXML/jackson-databind/commit/7dbf51bf78d157098074a20bd9da39bd48c18e4a", + "https://github.com/FasterXML/jackson-databind/issues/2854", + "https://github.com/advisories/GHSA-5949-rw7g-wx7w", + "https://lists.apache.org/thread.html/r380e9257bacb8551ee6fcf2c59890ae9477b2c78e553fa9ea08e9d9a@%3Ccommits.nifi.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2021/04/msg00025.html", + "https://nvd.nist.gov/vuln/detail/CVE-2021-20190", + "https://security.netapp.com/advisory/ntap-20210219-0008/" + ], + "PublishedDate": "2021-01-19T17:15:00Z", + "LastModifiedDate": "2021-07-20T23:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/pubspec.lock.json.golden b/integration/testdata/pubspec.lock.json.golden new file mode 100644 index 000000000000..7a101cd02eca --- /dev/null +++ b/integration/testdata/pubspec.lock.json.golden @@ -0,0 +1,93 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/pubspec", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "pubspec.lock", + "Class": "lang-pkgs", + "Type": "pub", + "Packages": [ + { + "ID": "http@0.13.2", + "Name": "http", + "Identifier": { + "PURL": "pkg:pub/http@0.13.2" + }, + "Version": "0.13.2", + "Layer": {} + }, + { + "ID": "shelf@1.3.1", + "Name": "shelf", + "Identifier": { + "PURL": "pkg:pub/shelf@1.3.1" + }, + "Version": "1.3.1", + "Indirect": true, + "Layer": {} + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-35669", + "PkgID": "http@0.13.2", + "PkgName": "http", + "PkgIdentifier": { + "PURL": "pkg:pub/http@0.13.2" + }, + "InstalledVersion": "0.13.2", + "FixedVersion": "0.13.3", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-35669", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Pub", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apub" + }, + "Title": "http before 0.13.3 vulnerable to header injection", + "Description": "An issue was discovered in the http package before 0.13.3 for Dart. If the attacker controls the HTTP method and the app is using Request directly, it's possible to achieve CRLF injection in an HTTP request via HTTP header injection. This issue has been addressed in commit abb2bb182 by validating request methods.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-74" + ], + "VendorSeverity": { + "ghsa": 2 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "V3Score": 6.1 + } + }, + "References": [ + "https://nvd.nist.gov/vuln/detail/CVE-2020-35669", + "https://github.com/dart-lang/http/issues/511", + "https://github.com/dart-lang/http/blob/master/CHANGELOG.md#0133", + "https://github.com/dart-lang/http/pull/512", + "https://github.com/dart-lang/http/commit/abb2bb182fbd7f03aafd1f889b902d7b3bdb8769", + "https://pub.dev/packages/http/changelog#0133", + "https://github.com/advisories/GHSA-4rgh-jx4f-qfcq" + ], + "PublishedDate": "2022-05-24T17:37:16Z", + "LastModifiedDate": "2022-10-06T20:26:08Z" + } + ] + } + ] +} diff --git a/integration/testdata/rockylinux-8.json.golden b/integration/testdata/rockylinux-8.json.golden new file mode 100644 index 000000000000..6d8b3b7a29a8 --- /dev/null +++ b/integration/testdata/rockylinux-8.json.golden @@ -0,0 +1,141 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/rockylinux-8.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "rocky", + "Name": "8.5" + }, + "ImageID": "sha256:210996f98b856d7cd00496ddbe9412e73f1c714c95de09661e07b4e43648f9ab", + "DiffIDs": [ + "sha256:65dbea0a4b39709e0a2cc8624fd99478e9f302c0a5661d7676d6d3bd3cb6d181" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "16458df10693f731fae0492f791a5e4b725245c35cf28c7fca00982219d7bdf3", + "created": "2021-12-15T20:22:37.180885096Z", + "docker_version": "20.10.7", + "history": [ + { + "created": "2021-12-15T20:22:36.373826081Z", + "created_by": "/bin/sh -c #(nop) ADD file:790b4c6a174560d4701baf59e884e7d07f50f0e193e545d6d5ed1d7390979d1a in / " + }, + { + "created": "2021-12-15T20:22:37.180885096Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:65dbea0a4b39709e0a2cc8624fd99478e9f302c0a5661d7676d6d3bd3cb6d181" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:b3d7893772a2427ad53224d9db4c70be399de0a28c09804ac0c5cb203ab0244e" + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/rockylinux-8.tar.gz (rocky 8.5)", + "Class": "os-pkgs", + "Type": "rocky", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2021-3712", + "PkgID": "openssl-libs@1.1.1k-4.el8.x86_64", + "PkgName": "openssl-libs", + "PkgIdentifier": { + "PURL": "pkg:rpm/rocky/openssl-libs@1.1.1k-4.el8?arch=x86_64\u0026distro=rocky-8.5\u0026epoch=1" + }, + "InstalledVersion": "1:1.1.1k-4.el8", + "FixedVersion": "1:1.1.1k-5.el8_5", + "Status": "fixed", + "Layer": { + "Digest": "sha256:72a2451028f11c6927678e5f1bb8f35b4e723d3b342ec1a6980d7b5591cf81d6", + "DiffID": "sha256:65dbea0a4b39709e0a2cc8624fd99478e9f302c0a5661d7676d6d3bd3cb6d181" + }, + "SeveritySource": "rocky", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-3712", + "DataSource": { + "ID": "rocky", + "Name": "Rocky Linux updateinfo", + "URL": "https://download.rockylinux.org/pub/rocky/" + }, + "Title": "openssl: Read buffer overruns processing ASN.1 strings", + "Description": "ASN.1 strings are represented internally within OpenSSL as an ASN1_STRING structure which contains a buffer holding the string data and a field holding the buffer length. This contrasts with normal C strings which are represented as a buffer for the string data which is terminated with a NUL (0) byte. Although not a strict requirement, ASN.1 strings that are parsed using OpenSSL's own \"d2i\" functions (and other similar parsing functions) as well as any string whose value has been set with the ASN1_STRING_set() function will additionally NUL terminate the byte array in the ASN1_STRING structure. However, it is possible for applications to directly construct valid ASN1_STRING structures which do not NUL terminate the byte array by directly setting the \"data\" and \"length\" fields in the ASN1_STRING array. This can also happen by using the ASN1_STRING_set0() function. Numerous OpenSSL functions that print ASN.1 data have been found to assume that the ASN1_STRING byte array will be NUL terminated, even though this is not guaranteed for strings that have been directly constructed. Where an application requests an ASN.1 structure to be printed, and where that ASN.1 structure contains ASN1_STRINGs that have been directly constructed by the application without NUL terminating the \"data\" field, then a read buffer overrun can occur. The same thing can also occur during name constraints processing of certificates (for example if a certificate has been directly constructed by the application instead of loading it via the OpenSSL parsing functions, and the certificate contains non NUL terminated ASN1_STRING structures). It can also occur in the X509_get1_email(), X509_REQ_get1_email() and X509_get1_ocsp() functions. If a malicious actor can cause an application to directly construct an ASN1_STRING and then process it through one of the affected OpenSSL functions then this issue could be hit. This might result in a crash (causing a Denial of Service attack). It could also result in the disclosure of private memory contents (such as private keys, or sensitive plaintext). Fixed in OpenSSL 1.1.1l (Affected 1.1.1-1.1.1k). Fixed in OpenSSL 1.0.2za (Affected 1.0.2-1.0.2y).", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-125" + ], + "VendorSeverity": { + "alma": 2, + "amazon": 2, + "arch-linux": 3, + "cbl-mariner": 3, + "nvd": 3, + "oracle-oval": 2, + "photon": 3, + "redhat": 2, + "rocky": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:N/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", + "V2Score": 5.8, + "V3Score": 7.4 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", + "V3Score": 7.4 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2021/08/26/2", + "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2021-3712.json", + "https://access.redhat.com/security/cve/CVE-2021-3712", + "https://crates.io/crates/openssl-src", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3712", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=94d23fcff9b2a7a8368dfe52214d5c2569882c11", + "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=ccb0a11145ee72b042d10593a64eaf9e8a55ec12", + "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10366", + "https://linux.oracle.com/cve/CVE-2021-3712.html", + "https://linux.oracle.com/errata/ELSA-2022-9023.html", + "https://lists.apache.org/thread.html/r18995de860f0e63635f3008fd2a6aca82394249476d21691e7c59c9e@%3Cdev.tomcat.apache.org%3E", + "https://lists.apache.org/thread.html/rad5d9f83f0d11fb3f8bb148d179b8a9ad7c6a17f18d70e5805a713d1@%3Cdev.tomcat.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2021/09/msg00014.html", + "https://lists.debian.org/debian-lts-announce/2021/09/msg00021.html", + "https://nvd.nist.gov/vuln/detail/CVE-2021-3712", + "https://rustsec.org/advisories/RUSTSEC-2021-0098.html", + "https://security.netapp.com/advisory/ntap-20210827-0010/", + "https://ubuntu.com/security/notices/USN-5051-1", + "https://ubuntu.com/security/notices/USN-5051-2", + "https://ubuntu.com/security/notices/USN-5051-3", + "https://ubuntu.com/security/notices/USN-5051-4 (regression only in trusty/esm)", + "https://ubuntu.com/security/notices/USN-5088-1", + "https://www.debian.org/security/2021/dsa-4963", + "https://www.openssl.org/news/secadv/20210824.txt", + "https://www.oracle.com/security-alerts/cpuoct2021.html", + "https://www.tenable.com/security/tns-2021-16", + "https://www.tenable.com/security/tns-2022-02" + ], + "PublishedDate": "2021-08-24T15:15:00Z", + "LastModifiedDate": "2022-01-06T09:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/secrets.asff.golden b/integration/testdata/secrets.asff.golden new file mode 100644 index 000000000000..b3a7c45fadac --- /dev/null +++ b/integration/testdata/secrets.asff.golden @@ -0,0 +1,62 @@ +{ + "Findings": [{ + "SchemaVersion": "2018-10-08", + "Id": "deploy.sh", + "ProductArn": "arn:aws:securityhub:test-region::product/aquasecurity/aquasecurity", + "GeneratorId": "Trivy", + "AwsAccountId": "123456789012", + "Types": [ "Sensitive Data Identifications" ], + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "UpdatedAt": "2021-08-25T12:20:30.000000005Z", + "Severity": { + "Label": "CRITICAL" + }, + "Title": "Trivy found a secret in deploy.sh: AWS Access Key ID", + "Description": "Trivy found a secret in deploy.sh: AWS Access Key ID", + "ProductFields": { "Product Name": "Trivy" }, + "Resources": [ + { + "Type": "Other", + "Id": "deploy.sh", + "Partition": "aws", + "Region": "test-region", + "Details": { + "Other": { + "Filename": "deploy.sh" + } + } + } + ], + "RecordState": "ACTIVE" + },{ + "SchemaVersion": "2018-10-08", + "Id": "deploy.sh", + "ProductArn": "arn:aws:securityhub:test-region::product/aquasecurity/aquasecurity", + "GeneratorId": "Trivy", + "AwsAccountId": "123456789012", + "Types": [ "Sensitive Data Identifications" ], + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "UpdatedAt": "2021-08-25T12:20:30.000000005Z", + "Severity": { + "Label": "CRITICAL" + }, + "Title": "Trivy found a secret in deploy.sh: GitHub Personal Access Token", + "Description": "Trivy found a secret in deploy.sh: GitHub Personal Access Token", + "ProductFields": { "Product Name": "Trivy" }, + "Resources": [ + { + "Type": "Other", + "Id": "deploy.sh", + "Partition": "aws", + "Region": "test-region", + "Details": { + "Other": { + "Filename": "deploy.sh" + } + } + } + ], + "RecordState": "ACTIVE" + } + ] +} diff --git a/integration/testdata/secrets.json.golden b/integration/testdata/secrets.json.golden new file mode 100644 index 000000000000..15ab882e150c --- /dev/null +++ b/integration/testdata/secrets.json.golden @@ -0,0 +1,121 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/secrets", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "deploy.sh", + "Class": "secret", + "Secrets": [ + { + "RuleID": "aws-access-key-id", + "Category": "AWS", + "Severity": "CRITICAL", + "Title": "AWS Access Key ID", + "StartLine": 3, + "EndLine": 3, + "Code": { + "Lines": [ + { + "Number": 1, + "Content": "#!/bin/sh", + "IsCause": false, + "Annotation": "", + "Truncated": false, + "Highlighted": "#!/bin/sh", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 2, + "Content": "", + "IsCause": false, + "Annotation": "", + "Truncated": false, + "FirstCause": false, + "LastCause": false + }, + { + "Number": 3, + "Content": "export AWS_ACCESS_KEY_ID=********************", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "export AWS_ACCESS_KEY_ID=********************", + "FirstCause": true, + "LastCause": true + }, + { + "Number": 4, + "Content": "", + "IsCause": false, + "Annotation": "", + "Truncated": false, + "FirstCause": false, + "LastCause": false + } + ] + }, + "Match": "export AWS_ACCESS_KEY_ID=********************", + "Layer": {} + }, + { + "RuleID": "mysecret", + "Category": "Custom", + "Severity": "HIGH", + "Title": "My Secret", + "StartLine": 7, + "EndLine": 7, + "Code": { + "Lines": [ + { + "Number": 5, + "Content": "export GITHUB_PAT=ghp_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "IsCause": false, + "Annotation": "", + "Truncated": false, + "Highlighted": "export GITHUB_PAT=ghp_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "FirstCause": false, + "LastCause": false + }, + { + "Number": 6, + "Content": "", + "IsCause": false, + "Annotation": "", + "Truncated": false, + "FirstCause": false, + "LastCause": false + }, + { + "Number": 7, + "Content": "echo ********", + "IsCause": true, + "Annotation": "", + "Truncated": false, + "Highlighted": "echo ********", + "FirstCause": true, + "LastCause": true + } + ] + }, + "Match": "echo ********", + "Layer": {} + } + ] + } + ] +} diff --git a/integration/testdata/spring4shell-jre11.json.golden b/integration/testdata/spring4shell-jre11.json.golden new file mode 100644 index 000000000000..7db6bae72bfa --- /dev/null +++ b/integration/testdata/spring4shell-jre11.json.golden @@ -0,0 +1,276 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/spring4shell-jre11.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "debian", + "Name": "11.3" + }, + "ImageID": "sha256:ed8f0747d483b60657982f0ef1ba74482aed08795cf0eb774b00bc53022a8351", + "DiffIDs": [ + "sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce", + "sha256:1f6e409d1c59c8e06608a024b82d50490313abc3b2ff93730e43135d5be0cd72", + "sha256:1f0e278ace87a84577de56c99e5c05c6af6f8b582d1eb8dfd7de7be4cf215775", + "sha256:64272e9218cd019d57b84ac283aa35036cbd8c1dcface8c69f756088a0a13c45", + "sha256:8e6776c643c1db15d540016171fe04137ee2a26c7d0b18bfebdcbd31c6b0d8b3", + "sha256:0b201a611e5455d637c719d70eb5dd76fd4154bc4a5cf597d67ed2fb6647cc42", + "sha256:19da2426772aaa344a242e474fd7906d272fc8ded6eef5b4e461a4aa0725d7e5", + "sha256:1fdc094b0e85888d2204310083e3c09fff6a4daeecf22692aa6be5e8b4001f94", + "sha256:192960b65b1579403b36581de471fd2bd75a043b4743552f27ba16623f02c68f" + ], + "ImageConfig": { + "architecture": "amd64", + "created": "2022-06-07T03:41:13.228952Z", + "docker_version": "20.10.14", + "history": [ + { + "created": "2022-03-29T00:22:18.812238611Z", + "created_by": "/bin/sh -c #(nop) ADD file:966d3669b40f5fbaecee1ecbeb58debe19001076da5d94717080d55efbc25971 in / " + }, + { + "created": "2022-03-29T00:22:19.186561403Z", + "created_by": "/bin/sh -c #(nop) CMD [\"bash\"]", + "empty_layer": true + }, + { + "created": "2022-03-29T00:52:15.681202963Z", + "created_by": "/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates p11-kit \t; \trm -rf /var/lib/apt/lists/*" + }, + { + "created": "2022-03-29T00:55:28.571451389Z", + "created_by": "/bin/sh -c #(nop) ENV JAVA_HOME=/usr/local/openjdk-11", + "empty_layer": true + }, + { + "created": "2022-03-29T00:55:29.092016566Z", + "created_by": "/bin/sh -c { echo '#/bin/sh'; echo 'echo \"$JAVA_HOME\"'; } \u003e /usr/local/bin/docker-java-home \u0026\u0026 chmod +x /usr/local/bin/docker-java-home \u0026\u0026 [ \"$JAVA_HOME\" = \"$(docker-java-home)\" ] # backwards compatibility" + }, + { + "created": "2022-03-29T00:55:29.206969756Z", + "created_by": "/bin/sh -c #(nop) ENV PATH=/usr/local/openjdk-11/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "empty_layer": true + }, + { + "created": "2022-03-29T00:55:29.302995298Z", + "created_by": "/bin/sh -c #(nop) ENV LANG=C.UTF-8", + "empty_layer": true + }, + { + "created": "2022-03-29T00:55:29.392969112Z", + "created_by": "/bin/sh -c #(nop) ENV JAVA_VERSION=11.0.14.1", + "empty_layer": true + }, + { + "created": "2022-03-29T00:56:45.085797918Z", + "created_by": "/bin/sh -c set -eux; \t\tarch=\"$(dpkg --print-architecture)\"; \tcase \"$arch\" in \t\t'amd64') \t\t\tdownloadUrl='https://github.com/AdoptOpenJDK/openjdk11-upstream-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jre_x64_linux_11.0.14.1_1.tar.gz'; \t\t\t;; \t\t'arm64') \t\t\tdownloadUrl='https://github.com/AdoptOpenJDK/openjdk11-upstream-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jre_aarch64_linux_11.0.14.1_1.tar.gz'; \t\t\t;; \t\t*) echo \u003e\u00262 \"error: unsupported architecture: '$arch'\"; exit 1 ;; \tesac; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tdirmngr \t\tgnupg \t\twget \t; \trm -rf /var/lib/apt/lists/*; \t\twget --progress=dot:giga -O openjdk.tgz \"$downloadUrl\"; \twget --progress=dot:giga -O openjdk.tgz.asc \"$downloadUrl.sign\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \tgpg --batch --keyserver keyserver.ubuntu.com --recv-keys EAC843EBD3EFDB98CC772FADA5CD6035332FA671; \tgpg --batch --keyserver keyserver.ubuntu.com --keyserver-options no-self-sigs-only --recv-keys CA5F11C6CE22644D42C6AC4492EF8D39DC13168F; \tgpg --batch --list-sigs --keyid-format 0xLONG CA5F11C6CE22644D42C6AC4492EF8D39DC13168F \t\t| tee /dev/stderr \t\t| grep '0xA5CD6035332FA671' \t\t| grep 'Andrew Haley'; \tgpg --batch --verify openjdk.tgz.asc openjdk.tgz; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\"; \t\tmkdir -p \"$JAVA_HOME\"; \ttar --extract \t\t--file openjdk.tgz \t\t--directory \"$JAVA_HOME\" \t\t--strip-components 1 \t\t--no-same-owner \t; \trm openjdk.tgz*; \t\tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark \u003e /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \t\t{ \t\techo '#!/usr/bin/env bash'; \t\techo 'set -Eeuo pipefail'; \t\techo 'trust extract --overwrite --format=java-cacerts --filter=ca-anchors --purpose=server-auth \"$JAVA_HOME/lib/security/cacerts\"'; \t} \u003e /etc/ca-certificates/update.d/docker-openjdk; \tchmod +x /etc/ca-certificates/update.d/docker-openjdk; \t/etc/ca-certificates/update.d/docker-openjdk; \t\tfind \"$JAVA_HOME/lib\" -name '*.so' -exec dirname '{}' ';' | sort -u \u003e /etc/ld.so.conf.d/docker-openjdk.conf; \tldconfig; \t\tjava -Xshare:dump; \t\tjava --version" + }, + { + "created": "2022-03-30T05:16:56.493239413Z", + "created_by": "/bin/sh -c #(nop) ENV CATALINA_HOME=/usr/local/tomcat", + "empty_layer": true + }, + { + "created": "2022-03-30T05:16:56.592339446Z", + "created_by": "/bin/sh -c #(nop) ENV PATH=/usr/local/tomcat/bin:/usr/local/openjdk-11/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "empty_layer": true + }, + { + "created": "2022-03-30T05:16:57.135799132Z", + "created_by": "/bin/sh -c mkdir -p \"$CATALINA_HOME\"" + }, + { + "created": "2022-03-30T05:16:57.234962251Z", + "created_by": "/bin/sh -c #(nop) WORKDIR /usr/local/tomcat", + "empty_layer": true + }, + { + "created": "2022-03-30T05:16:57.332478398Z", + "created_by": "/bin/sh -c #(nop) ENV TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib", + "empty_layer": true + }, + { + "created": "2022-03-30T05:16:57.423152329Z", + "created_by": "/bin/sh -c #(nop) ENV LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib", + "empty_layer": true + }, + { + "created": "2022-03-30T05:38:59.455604207Z", + "created_by": "/bin/sh -c #(nop) ENV GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 5C3C5F3E314C866292F359A8F3AD5C94A67F707E 765908099ACF92702C7D949BFA0C35EA8AA299F1 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23", + "empty_layer": true + }, + { + "created": "2022-03-30T05:38:59.550766811Z", + "created_by": "/bin/sh -c #(nop) ENV TOMCAT_MAJOR=8", + "empty_layer": true + }, + { + "created": "2022-03-30T05:38:59.643674076Z", + "created_by": "/bin/sh -c #(nop) ENV TOMCAT_VERSION=8.5.77", + "empty_layer": true + }, + { + "created": "2022-03-30T05:38:59.744285526Z", + "created_by": "/bin/sh -c #(nop) ENV TOMCAT_SHA512=50f96584cbbbeeda92a3b573e7fe7e2c49e57ed4bc5246257dc1409abac0710b49fa7049a0dd9a3b8467bca2aa078ef608f49b676c1abf12529528ff71bb0260", + "empty_layer": true + }, + { + "created": "2022-03-30T05:39:00.204794279Z", + "created_by": "/bin/sh -c #(nop) COPY dir:92f3a0f303b55a048a73bf243c664f89aa86500eab95c7d20c2da44ed3fb434b in /usr/local/tomcat " + }, + { + "created": "2022-03-30T05:39:03.786979035Z", + "created_by": "/bin/sh -c set -eux; \tapt-get update; \txargs -rt apt-get install -y --no-install-recommends \u003c \"$TOMCAT_NATIVE_LIBDIR/.dependencies.txt\"; \trm -rf /var/lib/apt/lists/*" + }, + { + "created": "2022-03-30T05:39:05.151055599Z", + "created_by": "/bin/sh -c set -eux; \tnativeLines=\"$(catalina.sh configtest 2\u003e\u00261)\"; \tnativeLines=\"$(echo \"$nativeLines\" | grep 'Apache Tomcat Native')\"; \tnativeLines=\"$(echo \"$nativeLines\" | sort -u)\"; \tif ! echo \"$nativeLines\" | grep -E 'INFO: Loaded( APR based)? Apache Tomcat Native library' \u003e\u00262; then \t\techo \u003e\u00262 \"$nativeLines\"; \t\texit 1; \tfi" + }, + { + "created": "2022-03-30T05:39:05.243348189Z", + "created_by": "/bin/sh -c #(nop) EXPOSE 8080", + "empty_layer": true + }, + { + "created": "2022-03-30T05:39:05.342897424Z", + "created_by": "/bin/sh -c #(nop) CMD [\"catalina.sh\" \"run\"]", + "empty_layer": true + }, + { + "created": "2022-06-07T03:41:13.228952Z", + "created_by": "/bin/sh -c #(nop) COPY file:4a1136b54136f8775efe918c4cd6af1ad1e507b36a49286d4f2c6bde722d33f4 in /usr/local/tomcat/webapps/ " + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce", + "sha256:1f6e409d1c59c8e06608a024b82d50490313abc3b2ff93730e43135d5be0cd72", + "sha256:1f0e278ace87a84577de56c99e5c05c6af6f8b582d1eb8dfd7de7be4cf215775", + "sha256:64272e9218cd019d57b84ac283aa35036cbd8c1dcface8c69f756088a0a13c45", + "sha256:8e6776c643c1db15d540016171fe04137ee2a26c7d0b18bfebdcbd31c6b0d8b3", + "sha256:0b201a611e5455d637c719d70eb5dd76fd4154bc4a5cf597d67ed2fb6647cc42", + "sha256:19da2426772aaa344a242e474fd7906d272fc8ded6eef5b4e461a4aa0725d7e5", + "sha256:1fdc094b0e85888d2204310083e3c09fff6a4daeecf22692aa6be5e8b4001f94", + "sha256:192960b65b1579403b36581de471fd2bd75a043b4743552f27ba16623f02c68f" + ] + }, + "config": { + "Cmd": [ + "catalina.sh", + "run" + ], + "Env": [ + "PATH=/usr/local/tomcat/bin:/usr/local/openjdk-11/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "JAVA_HOME=/usr/local/openjdk-11", + "LANG=C.UTF-8", + "JAVA_VERSION=11.0.14.1", + "CATALINA_HOME=/usr/local/tomcat", + "TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib", + "LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib", + "GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 5C3C5F3E314C866292F359A8F3AD5C94A67F707E 765908099ACF92702C7D949BFA0C35EA8AA299F1 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23", + "TOMCAT_MAJOR=8", + "TOMCAT_VERSION=8.5.77", + "TOMCAT_SHA512=50f96584cbbbeeda92a3b573e7fe7e2c49e57ed4bc5246257dc1409abac0710b49fa7049a0dd9a3b8467bca2aa078ef608f49b676c1abf12529528ff71bb0260" + ], + "Image": "sha256:8ac2c9cef8f1bb48394c1b2ee81cc1d2096323a7a7cec4781d601eeaf7c32b03", + "WorkingDir": "/usr/local/tomcat", + "ExposedPorts": { + "8080/tcp": {} + } + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/spring4shell-jre11.tar.gz (debian 11.3)", + "Class": "os-pkgs", + "Type": "debian" + }, + { + "Target": "Java", + "Class": "lang-pkgs", + "Type": "jar", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-22965", + "PkgName": "org.springframework:spring-beans", + "PkgPath": "usr/local/tomcat/webapps/helloworld.war/WEB-INF/lib/spring-beans-5.3.15.jar", + "PkgIdentifier": { + "PURL": "pkg:maven/org.springframework/spring-beans@5.3.15" + }, + "InstalledVersion": "5.3.15", + "FixedVersion": "5.3.18", + "Status": "fixed", + "Layer": { + "Digest": "sha256:b47862f824700e0ea830e568e989fba777d8223c1f8321c6256b0c965b9f61ee", + "DiffID": "sha256:192960b65b1579403b36581de471fd2bd75a043b4743552f27ba16623f02c68f" + }, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-22965", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Maven", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven" + }, + "Title": "spring-framework: RCE via Data Binding on JDK 9+", + "Description": "A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-94" + ], + "VendorSeverity": { + "ghsa": 4, + "nvd": 4, + "redhat": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 9.8 + }, + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 8.1 + } + }, + "References": [ + "https://github.com/advisories/GHSA-36p3-wjmg-h94x" + ] + } + ] + }, + { + "Target": "", + "Class": "custom", + "CustomResources": [ + { + "Type": "spring4shell/java-major-version", + "FilePath": "/usr/local/openjdk-11/release", + "Layer": { + "Digest": "sha256:e94fd7d3bf7a9b78b61be8303cd35eb9da3f8d121cf572a3b8878cbf11e84818", + "DiffID": "sha256:64272e9218cd019d57b84ac283aa35036cbd8c1dcface8c69f756088a0a13c45" + }, + "Data": "11.0.14.1" + }, + { + "Type": "spring4shell/tomcat-version", + "FilePath": "/usr/local/tomcat/RELEASE-NOTES", + "Layer": { + "Digest": "sha256:ac3639dc6fd33e9eeead58a99c277cb06b8f69ba6a30fe7028e9677a67d94bd8", + "DiffID": "sha256:0b201a611e5455d637c719d70eb5dd76fd4154bc4a5cf597d67ed2fb6647cc42" + }, + "Data": "8.5.77" + } + ] + } + ] +} diff --git a/integration/testdata/spring4shell-jre8.json.golden b/integration/testdata/spring4shell-jre8.json.golden new file mode 100644 index 000000000000..e1a0e9ee0457 --- /dev/null +++ b/integration/testdata/spring4shell-jre8.json.golden @@ -0,0 +1,276 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/spring4shell-jre8.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "debian", + "Name": "11.3" + }, + "ImageID": "sha256:b88bc3d2f0b5aacf1d36efa498f427d923b01c854dac090acf5368c55ac04fda", + "DiffIDs": [ + "sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce", + "sha256:1f6e409d1c59c8e06608a024b82d50490313abc3b2ff93730e43135d5be0cd72", + "sha256:0e78b1e5673e8cc7c102fdda9e6b830b7dee2b29b178f34d25d9be59387e6950", + "sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1", + "sha256:053db4876c0df3df3294ee00e32e140b130ba33807d088750cb69b0e6fad158e", + "sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6", + "sha256:868d710aa4dc5fc4793508564fc45c991ed8d5f6ab3e4cf52bb856f29546f3d8", + "sha256:77b2d158369254d5055183f5483f8b6661170857b61768d1d95d18c2ec1714b6", + "sha256:eb769943b91f10a0418f2fc3b4a4fde6c6293be60c37293fcc0fa319edaf27a5" + ], + "ImageConfig": { + "architecture": "amd64", + "created": "2022-06-06T13:51:57.120019Z", + "docker_version": "20.10.14", + "history": [ + { + "created": "2022-03-29T00:22:18.812238611Z", + "created_by": "/bin/sh -c #(nop) ADD file:966d3669b40f5fbaecee1ecbeb58debe19001076da5d94717080d55efbc25971 in / " + }, + { + "created": "2022-03-29T00:22:19.186561403Z", + "created_by": "/bin/sh -c #(nop) CMD [\"bash\"]", + "empty_layer": true + }, + { + "created": "2022-03-29T00:52:15.681202963Z", + "created_by": "/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates p11-kit \t; \trm -rf /var/lib/apt/lists/*" + }, + { + "created": "2022-03-29T00:57:17.289858958Z", + "created_by": "/bin/sh -c #(nop) ENV JAVA_HOME=/usr/local/openjdk-8", + "empty_layer": true + }, + { + "created": "2022-03-29T00:57:17.842070347Z", + "created_by": "/bin/sh -c { echo '#/bin/sh'; echo 'echo \"$JAVA_HOME\"'; } \u003e /usr/local/bin/docker-java-home \u0026\u0026 chmod +x /usr/local/bin/docker-java-home \u0026\u0026 [ \"$JAVA_HOME\" = \"$(docker-java-home)\" ] # backwards compatibility" + }, + { + "created": "2022-03-29T00:57:17.930626834Z", + "created_by": "/bin/sh -c #(nop) ENV PATH=/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "empty_layer": true + }, + { + "created": "2022-03-29T00:57:18.024334574Z", + "created_by": "/bin/sh -c #(nop) ENV LANG=C.UTF-8", + "empty_layer": true + }, + { + "created": "2022-03-29T00:57:18.115954625Z", + "created_by": "/bin/sh -c #(nop) ENV JAVA_VERSION=8u322", + "empty_layer": true + }, + { + "created": "2022-03-29T00:58:15.033935002Z", + "created_by": "/bin/sh -c set -eux; \t\tarch=\"$(dpkg --print-architecture)\"; \tcase \"$arch\" in \t\t'amd64') \t\t\tdownloadUrl='https://github.com/AdoptOpenJDK/openjdk8-upstream-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jre_x64_linux_8u322b06.tar.gz'; \t\t\t;; \t\t'arm64') \t\t\tdownloadUrl='https://github.com/AdoptOpenJDK/openjdk8-upstream-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jre_aarch64_linux_8u322b06.tar.gz'; \t\t\t;; \t\t*) echo \u003e\u00262 \"error: unsupported architecture: '$arch'\"; exit 1 ;; \tesac; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tdirmngr \t\tgnupg \t\twget \t; \trm -rf /var/lib/apt/lists/*; \t\twget --progress=dot:giga -O openjdk.tgz \"$downloadUrl\"; \twget --progress=dot:giga -O openjdk.tgz.asc \"$downloadUrl.sign\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \tgpg --batch --keyserver keyserver.ubuntu.com --recv-keys EAC843EBD3EFDB98CC772FADA5CD6035332FA671; \tgpg --batch --keyserver keyserver.ubuntu.com --keyserver-options no-self-sigs-only --recv-keys CA5F11C6CE22644D42C6AC4492EF8D39DC13168F; \tgpg --batch --list-sigs --keyid-format 0xLONG CA5F11C6CE22644D42C6AC4492EF8D39DC13168F \t\t| tee /dev/stderr \t\t| grep '0xA5CD6035332FA671' \t\t| grep 'Andrew Haley'; \tgpg --batch --verify openjdk.tgz.asc openjdk.tgz; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\"; \t\tmkdir -p \"$JAVA_HOME\"; \ttar --extract \t\t--file openjdk.tgz \t\t--directory \"$JAVA_HOME\" \t\t--strip-components 1 \t\t--no-same-owner \t; \trm openjdk.tgz*; \t\tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark \u003e /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \t\t{ \t\techo '#!/usr/bin/env bash'; \t\techo 'set -Eeuo pipefail'; \t\techo 'trust extract --overwrite --format=java-cacerts --filter=ca-anchors --purpose=server-auth \"$JAVA_HOME/lib/security/cacerts\"'; \t} \u003e /etc/ca-certificates/update.d/docker-openjdk; \tchmod +x /etc/ca-certificates/update.d/docker-openjdk; \t/etc/ca-certificates/update.d/docker-openjdk; \t\tfind \"$JAVA_HOME/lib\" -name '*.so' -exec dirname '{}' ';' | sort -u \u003e /etc/ld.so.conf.d/docker-openjdk.conf; \tldconfig; \t\tjava -version" + }, + { + "created": "2022-03-30T05:24:58.62001394Z", + "created_by": "/bin/sh -c #(nop) ENV CATALINA_HOME=/usr/local/tomcat", + "empty_layer": true + }, + { + "created": "2022-03-30T05:24:58.713370816Z", + "created_by": "/bin/sh -c #(nop) ENV PATH=/usr/local/tomcat/bin:/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "empty_layer": true + }, + { + "created": "2022-03-30T05:24:59.23051474Z", + "created_by": "/bin/sh -c mkdir -p \"$CATALINA_HOME\"" + }, + { + "created": "2022-03-30T05:24:59.327279736Z", + "created_by": "/bin/sh -c #(nop) WORKDIR /usr/local/tomcat", + "empty_layer": true + }, + { + "created": "2022-03-30T05:24:59.421537441Z", + "created_by": "/bin/sh -c #(nop) ENV TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib", + "empty_layer": true + }, + { + "created": "2022-03-30T05:24:59.517366785Z", + "created_by": "/bin/sh -c #(nop) ENV LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib", + "empty_layer": true + }, + { + "created": "2022-03-30T05:42:09.694872721Z", + "created_by": "/bin/sh -c #(nop) ENV GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 5C3C5F3E314C866292F359A8F3AD5C94A67F707E 765908099ACF92702C7D949BFA0C35EA8AA299F1 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23", + "empty_layer": true + }, + { + "created": "2022-03-30T05:42:09.790546995Z", + "created_by": "/bin/sh -c #(nop) ENV TOMCAT_MAJOR=8", + "empty_layer": true + }, + { + "created": "2022-03-30T05:42:09.882411219Z", + "created_by": "/bin/sh -c #(nop) ENV TOMCAT_VERSION=8.5.77", + "empty_layer": true + }, + { + "created": "2022-03-30T05:42:09.972728447Z", + "created_by": "/bin/sh -c #(nop) ENV TOMCAT_SHA512=50f96584cbbbeeda92a3b573e7fe7e2c49e57ed4bc5246257dc1409abac0710b49fa7049a0dd9a3b8467bca2aa078ef608f49b676c1abf12529528ff71bb0260", + "empty_layer": true + }, + { + "created": "2022-03-30T05:42:10.425808771Z", + "created_by": "/bin/sh -c #(nop) COPY dir:4b2b2ad5d081ef9a92d92841b7b643214ae6e21bbb961e3197c3615dd08813ab in /usr/local/tomcat " + }, + { + "created": "2022-03-30T05:42:13.894391571Z", + "created_by": "/bin/sh -c set -eux; \tapt-get update; \txargs -rt apt-get install -y --no-install-recommends \u003c \"$TOMCAT_NATIVE_LIBDIR/.dependencies.txt\"; \trm -rf /var/lib/apt/lists/*" + }, + { + "created": "2022-03-30T05:42:15.098598192Z", + "created_by": "/bin/sh -c set -eux; \tnativeLines=\"$(catalina.sh configtest 2\u003e\u00261)\"; \tnativeLines=\"$(echo \"$nativeLines\" | grep 'Apache Tomcat Native')\"; \tnativeLines=\"$(echo \"$nativeLines\" | sort -u)\"; \tif ! echo \"$nativeLines\" | grep -E 'INFO: Loaded( APR based)? Apache Tomcat Native library' \u003e\u00262; then \t\techo \u003e\u00262 \"$nativeLines\"; \t\texit 1; \tfi" + }, + { + "created": "2022-03-30T05:42:15.18926788Z", + "created_by": "/bin/sh -c #(nop) EXPOSE 8080", + "empty_layer": true + }, + { + "created": "2022-03-30T05:42:15.283787651Z", + "created_by": "/bin/sh -c #(nop) CMD [\"catalina.sh\" \"run\"]", + "empty_layer": true + }, + { + "created": "2022-06-06T13:51:57.120019Z", + "created_by": "/bin/sh -c #(nop) COPY file:4a1136b54136f8775efe918c4cd6af1ad1e507b36a49286d4f2c6bde722d33f4 in /usr/local/tomcat/webapps/ " + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce", + "sha256:1f6e409d1c59c8e06608a024b82d50490313abc3b2ff93730e43135d5be0cd72", + "sha256:0e78b1e5673e8cc7c102fdda9e6b830b7dee2b29b178f34d25d9be59387e6950", + "sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1", + "sha256:053db4876c0df3df3294ee00e32e140b130ba33807d088750cb69b0e6fad158e", + "sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6", + "sha256:868d710aa4dc5fc4793508564fc45c991ed8d5f6ab3e4cf52bb856f29546f3d8", + "sha256:77b2d158369254d5055183f5483f8b6661170857b61768d1d95d18c2ec1714b6", + "sha256:eb769943b91f10a0418f2fc3b4a4fde6c6293be60c37293fcc0fa319edaf27a5" + ] + }, + "config": { + "Cmd": [ + "catalina.sh", + "run" + ], + "Env": [ + "PATH=/usr/local/tomcat/bin:/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "JAVA_HOME=/usr/local/openjdk-8", + "LANG=C.UTF-8", + "JAVA_VERSION=8u322", + "CATALINA_HOME=/usr/local/tomcat", + "TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib", + "LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib", + "GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 5C3C5F3E314C866292F359A8F3AD5C94A67F707E 765908099ACF92702C7D949BFA0C35EA8AA299F1 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23", + "TOMCAT_MAJOR=8", + "TOMCAT_VERSION=8.5.77", + "TOMCAT_SHA512=50f96584cbbbeeda92a3b573e7fe7e2c49e57ed4bc5246257dc1409abac0710b49fa7049a0dd9a3b8467bca2aa078ef608f49b676c1abf12529528ff71bb0260" + ], + "Image": "sha256:d69eef03ed55cfa0d799181c477762549e7ce94deb23193f04e6bec0c23d0dfd", + "WorkingDir": "/usr/local/tomcat", + "ExposedPorts": { + "8080/tcp": {} + } + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/spring4shell-jre8.tar.gz (debian 11.3)", + "Class": "os-pkgs", + "Type": "debian" + }, + { + "Target": "Java", + "Class": "lang-pkgs", + "Type": "jar", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-22965", + "PkgName": "org.springframework:spring-beans", + "PkgPath": "usr/local/tomcat/webapps/helloworld.war/WEB-INF/lib/spring-beans-5.3.15.jar", + "PkgIdentifier": { + "PURL": "pkg:maven/org.springframework/spring-beans@5.3.15" + }, + "InstalledVersion": "5.3.15", + "FixedVersion": "5.3.18", + "Status": "fixed", + "Layer": { + "Digest": "sha256:cc44af318e91e6f9f9bf73793fa4f0639487613f46aa1f819b02b6e8fb5c6c07", + "DiffID": "sha256:eb769943b91f10a0418f2fc3b4a4fde6c6293be60c37293fcc0fa319edaf27a5" + }, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-22965", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Maven", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven" + }, + "Title": "spring-framework: RCE via Data Binding on JDK 9+", + "Description": "A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.", + "Severity": "LOW", + "CweIDs": [ + "CWE-94" + ], + "VendorSeverity": { + "ghsa": 4, + "nvd": 4, + "redhat": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 9.8 + }, + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.5, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 8.1 + } + }, + "References": [ + "https://github.com/advisories/GHSA-36p3-wjmg-h94x" + ] + } + ] + }, + { + "Target": "", + "Class": "custom", + "CustomResources": [ + { + "Type": "spring4shell/java-major-version", + "FilePath": "/usr/local/openjdk-8/release", + "Layer": { + "Digest": "sha256:d7b564a873af313eb2dbcb1ed0d393c57543e3666bdedcbe5d75841d72b1f791", + "DiffID": "sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1" + }, + "Data": "1.8.0_322" + }, + { + "Type": "spring4shell/tomcat-version", + "FilePath": "/usr/local/tomcat/RELEASE-NOTES", + "Layer": { + "Digest": "sha256:59c0978ccb117247fd40d936973c40df89195f60466118c5acc6a55f8ba29f06", + "DiffID": "sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6" + }, + "Data": "8.5.77" + } + ] + } + ] +} diff --git a/integration/testdata/swift.json.golden b/integration/testdata/swift.json.golden new file mode 100644 index 000000000000..0a9d1ebffb21 --- /dev/null +++ b/integration/testdata/swift.json.golden @@ -0,0 +1,92 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/swift", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Package.resolved", + "Class": "lang-pkgs", + "Type": "swift", + "Packages": [ + { + "ID": "github.com/apple/swift-atomics@1.1.0", + "Name": "github.com/apple/swift-atomics", + "Identifier": { + "PURL": "pkg:swift/github.com/apple/swift-atomics@1.1.0" + }, + "Version": "1.1.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 4, + "EndLine": 12 + } + ] + }, + { + "ID": "github.com/apple/swift-nio@2.41.0", + "Name": "github.com/apple/swift-nio", + "Identifier": { + "PURL": "pkg:swift/github.com/apple/swift-nio@2.41.0" + }, + "Version": "2.41.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 13, + "EndLine": 21 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-3215", + "PkgID": "github.com/apple/swift-nio@2.41.0", + "PkgName": "github.com/apple/swift-nio", + "PkgIdentifier": { + "PURL": "pkg:swift/github.com/apple/swift-nio@2.41.0" + }, + "InstalledVersion": "2.41.0", + "FixedVersion": "2.29.1, 2.39.1, 2.42.0", + "Status": "fixed", + "Layer": {}, + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-3215", + "Title": "SwiftNIO vulnerable to Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')", + "Description": "`NIOHTTP1` and projects using it for generating HTTP responses, including SwiftNIO, can be subject to a HTTP Response Injection attack...", + "Severity": "MEDIUM", + "VendorSeverity": { + "ghsa": 2 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "V3Score": 5.3 + } + }, + "References": [ + "https://github.com/apple/swift-nio/security/advisories/GHSA-7fj7-39wj-c64f", + "https://nvd.nist.gov/vuln/detail/CVE-2022-3215", + "https://github.com/apple/swift-nio/commit/a16e2f54a25b2af217044e5168997009a505930f", + "https://github.com/advisories/GHSA-7fj7-39wj-c64f" + ], + "PublishedDate": "2023-06-07T16:01:53Z", + "LastModifiedDate": "2023-06-19T16:45:07Z" + } + ] + } + ] +} diff --git a/integration/testdata/test-repo.json.golden b/integration/testdata/test-repo.json.golden new file mode 100644 index 000000000000..e1ebd91418f7 --- /dev/null +++ b/integration/testdata/test-repo.json.golden @@ -0,0 +1,112 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "https://github.com/knqyf263/trivy-ci-test", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Cargo.lock", + "Class": "lang-pkgs", + "Type": "cargo", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-15542", + "PkgID": "ammonia@1.9.0", + "PkgName": "ammonia", + "PkgIdentifier": { + "PURL": "pkg:cargo/ammonia@1.9.0" + }, + "InstalledVersion": "1.9.0", + "FixedVersion": "\u003e= 2.1.0", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-15542", + "DataSource": { + "Name": "RustSec Advisory Database", + "URL": "https://github.com/RustSec/advisory-db" + }, + "Title": "Uncontrolled recursion leads to abort in HTML serialization", + "Description": "An issue was discovered in the ammonia crate before 2.1.0 for Rust. There is uncontrolled recursion during HTML DOM tree serialization.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-674" + ], + "VendorSeverity": { + "nvd": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V2Score": 5, + "V3Score": 7.5 + } + }, + "References": [ + "https://crates.io/crates/ammonia", + "https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md#210", + "https://rustsec.org/advisories/RUSTSEC-2019-0001.html" + ], + "PublishedDate": "2019-08-26T18:15:00Z", + "LastModifiedDate": "2020-08-24T17:37:00Z" + }, + { + "VulnerabilityID": "CVE-2021-38193", + "PkgID": "ammonia@1.9.0", + "PkgName": "ammonia", + "PkgIdentifier": { + "PURL": "pkg:cargo/ammonia@1.9.0" + }, + "InstalledVersion": "1.9.0", + "FixedVersion": "\u003e= 3.1.0, \u003e= 2.1.3, \u003c 3.0.0", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-38193", + "DataSource": { + "Name": "RustSec Advisory Database", + "URL": "https://github.com/RustSec/advisory-db" + }, + "Title": "Incorrect handling of embedded SVG and MathML leads to mutation XSS", + "Description": "An issue was discovered in the ammonia crate before 3.1.0 for Rust. XSS can occur because the parsing differences for HTML, SVG, and MathML are mishandled, a similar issue to CVE-2020-26870.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-79" + ], + "VendorSeverity": { + "nvd": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "V2Score": 4.3, + "V3Score": 6.1 + } + }, + "References": [ + "https://crates.io/crates/ammonia", + "https://github.com/rust-ammonia/ammonia/pull/142", + "https://raw.githubusercontent.com/rustsec/advisory-db/main/crates/ammonia/RUSTSEC-2021-0074.md", + "https://rustsec.org/advisories/RUSTSEC-2021-0074.html" + ], + "PublishedDate": "2021-08-08T06:15:00Z", + "LastModifiedDate": "2021-08-16T16:37:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/ubi-7.json.golden b/integration/testdata/ubi-7.json.golden new file mode 100644 index 000000000000..36062495c5a9 --- /dev/null +++ b/integration/testdata/ubi-7.json.golden @@ -0,0 +1,139 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/ubi-7.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "redhat", + "Name": "7.7" + }, + "ImageID": "sha256:6fecccc91c83e11ae4fede6793e9410841221d4779520c2b9e9fb7f7b3830264", + "DiffIDs": [ + "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac", + "sha256:ecb0311889b3478bc9b62660fa9391d5ebf8da4c6ae143cb33434873668f9e36" + ], + "ImageConfig": { + "architecture": "amd64", + "created": "2019-09-02T12:56:43.939095Z", + "docker_version": "1.13.1", + "history": [ + { + "created": "2019-09-02T12:56:36.440695936Z", + "comment": "Imported from -" + }, + { + "created": "2019-09-02T12:56:43.939095Z" + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac", + "sha256:ecb0311889b3478bc9b62660fa9391d5ebf8da4c6ae143cb33434873668f9e36" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "container=oci" + ], + "Hostname": "0da2e3774382", + "Image": "2e9103a7b91a7ffe333e9162ce98ea078263747527571655e93bd4d35ee278f0", + "Labels": { + "architecture": "x86_64", + "authoritative-source-url": "registry.access.redhat.com", + "build-date": "2019-09-02T12:56:18.824770", + "com.redhat.build-host": "cpt-1005.osbs.prod.upshift.rdu2.redhat.com", + "com.redhat.component": "ubi7-container", + "com.redhat.license_terms": "https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI", + "description": "The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly.", + "distribution-scope": "public", + "io.k8s.description": "The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly.", + "io.k8s.display-name": "Red Hat Universal Base Image 7", + "io.openshift.tags": "base rhel7", + "maintainer": "Red Hat, Inc.", + "name": "ubi7", + "release": "140", + "summary": "Provides the latest release of the Red Hat Universal Base Image 7.", + "url": "https://access.redhat.com/containers/#/registry.access.redhat.com/ubi7/images/7.7-140", + "vcs-ref": "4c80c8aa26e69950ab11b87789c8fb7665b1632d", + "vcs-type": "git", + "vendor": "Red Hat, Inc.", + "version": "7.7" + }, + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/ubi-7.tar.gz (redhat 7.7)", + "Class": "os-pkgs", + "Type": "redhat", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@4.2.46-33.el7.x86_64", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:rpm/redhat/bash@4.2.46-33.el7?arch=x86_64\u0026distro=redhat-7.7" + }, + "InstalledVersion": "4.2.46-33.el7", + "Status": "will_not_fix", + "Layer": { + "Digest": "sha256:7b1c937e0f6794db2535be6e4cb6d60a0b668ef78c2576611a3fb9c97a95ccdf", + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276", + "Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped", + "Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "Severity": "LOW", + "CweIDs": [ + "CWE-273" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "nvd": 3, + "oracle-oval": 1, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.2, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html", + "https://access.redhat.com/security/cve/CVE-2019-18276", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276", + "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff", + "https://linux.oracle.com/cve/CVE-2019-18276.html", + "https://linux.oracle.com/errata/ELSA-2021-1679.html", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://nvd.nist.gov/vuln/detail/CVE-2019-18276", + "https://security.gentoo.org/glsa/202105-34", + "https://security.netapp.com/advisory/ntap-20200430-0003/", + "https://www.youtube.com/watch?v=-wGtxJ8opa8" + ], + "PublishedDate": "2019-11-28T01:15:00Z", + "LastModifiedDate": "2021-05-26T12:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden b/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden new file mode 100644 index 000000000000..f2a38510385b --- /dev/null +++ b/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden @@ -0,0 +1,357 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/ubuntu-1804.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "ubuntu", + "Name": "18.04" + }, + "ImageID": "sha256:a2a15febcdf362f6115e801d37b5e60d6faaeedcb9896155e5fe9d754025be12", + "DiffIDs": [ + "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f", + "sha256:f7eae43028b334123c3a1d778f7bdf9783bbe651c8b15371df0120fd13ec35c5", + "sha256:7beb13bce073c21c9ee608acb13c7e851845245dc76ce81b418fdf580c45076b", + "sha256:122be11ab4a29e554786b4a1ec4764dd55656b59d6228a0a3de78eaf5c1f226c" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "41b694b9b42f9c5ef7fb40c24272927a727a6d6cb8120bb3eae5849ceb9bee77", + "created": "2019-08-15T07:28:14.830150536Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-08-15T07:28:12.433344678Z", + "created_by": "/bin/sh -c #(nop) ADD file:c477cb0e95c56b51e0b7353f3805165393689902b82a41bbe77dbef4b31667e1 in / " + }, + { + "created": "2019-08-15T07:28:13.20852008Z", + "created_by": "/bin/sh -c [ -z \"$(apt-get indextargets)\" ]" + }, + { + "created": "2019-08-15T07:28:13.964607567Z", + "created_by": "/bin/sh -c set -xe \t\t\u0026\u0026 echo '#!/bin/sh' \u003e /usr/sbin/policy-rc.d \t\u0026\u0026 echo 'exit 101' \u003e\u003e /usr/sbin/policy-rc.d \t\u0026\u0026 chmod +x /usr/sbin/policy-rc.d \t\t\u0026\u0026 dpkg-divert --local --rename --add /sbin/initctl \t\u0026\u0026 cp -a /usr/sbin/policy-rc.d /sbin/initctl \t\u0026\u0026 sed -i 's/^exit.*/exit 0/' /sbin/initctl \t\t\u0026\u0026 echo 'force-unsafe-io' \u003e /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \t\t\u0026\u0026 echo 'DPkg::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\"; };' \u003e /etc/apt/apt.conf.d/docker-clean \t\u0026\u0026 echo 'APT::Update::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\"; };' \u003e\u003e /etc/apt/apt.conf.d/docker-clean \t\u0026\u0026 echo 'Dir::Cache::pkgcache \"\"; Dir::Cache::srcpkgcache \"\";' \u003e\u003e /etc/apt/apt.conf.d/docker-clean \t\t\u0026\u0026 echo 'Acquire::Languages \"none\";' \u003e /etc/apt/apt.conf.d/docker-no-languages \t\t\u0026\u0026 echo 'Acquire::GzipIndexes \"true\"; Acquire::CompressionTypes::Order:: \"gz\";' \u003e /etc/apt/apt.conf.d/docker-gzip-indexes \t\t\u0026\u0026 echo 'Apt::AutoRemove::SuggestsImportant \"false\";' \u003e /etc/apt/apt.conf.d/docker-autoremove-suggests" + }, + { + "created": "2019-08-15T07:28:14.64282638Z", + "created_by": "/bin/sh -c mkdir -p /run/systemd \u0026\u0026 echo 'docker' \u003e /run/systemd/container" + }, + { + "created": "2019-08-15T07:28:14.830150536Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f", + "sha256:f7eae43028b334123c3a1d778f7bdf9783bbe651c8b15371df0120fd13ec35c5", + "sha256:7beb13bce073c21c9ee608acb13c7e851845245dc76ce81b418fdf580c45076b", + "sha256:122be11ab4a29e554786b4a1ec4764dd55656b59d6228a0a3de78eaf5c1f226c" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:bcbe079849fdbb50b3eb04798547e046bdbc82020b8b780d767cf29f7e60b396", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/ubuntu-1804.tar.gz (ubuntu 18.04)", + "Class": "os-pkgs", + "Type": "ubuntu", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-5094", + "PkgID": "e2fsprogs@1.44.1-1ubuntu1.1", + "PkgName": "e2fsprogs", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/e2fsprogs@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + }, + "InstalledVersion": "1.44.1-1ubuntu1.1", + "FixedVersion": "1.44.1-1ubuntu1.2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "PkgID": "libcom-err2@1.44.1-1ubuntu1.1", + "PkgName": "libcom-err2", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/libcom-err2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + }, + "InstalledVersion": "1.44.1-1ubuntu1.1", + "FixedVersion": "1.44.1-1ubuntu1.2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "PkgID": "libext2fs2@1.44.1-1ubuntu1.1", + "PkgName": "libext2fs2", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/libext2fs2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + }, + "InstalledVersion": "1.44.1-1ubuntu1.1", + "FixedVersion": "1.44.1-1ubuntu1.2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "PkgID": "libss2@1.44.1-1ubuntu1.1", + "PkgName": "libss2", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/libss2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + }, + "InstalledVersion": "1.44.1-1ubuntu1.1", + "FixedVersion": "1.44.1-1ubuntu1.2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/ubuntu-1804.json.golden b/integration/testdata/ubuntu-1804.json.golden new file mode 100644 index 000000000000..5fc21dba6a6e --- /dev/null +++ b/integration/testdata/ubuntu-1804.json.golden @@ -0,0 +1,419 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/ubuntu-1804.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "ubuntu", + "Name": "18.04" + }, + "ImageID": "sha256:a2a15febcdf362f6115e801d37b5e60d6faaeedcb9896155e5fe9d754025be12", + "DiffIDs": [ + "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f", + "sha256:f7eae43028b334123c3a1d778f7bdf9783bbe651c8b15371df0120fd13ec35c5", + "sha256:7beb13bce073c21c9ee608acb13c7e851845245dc76ce81b418fdf580c45076b", + "sha256:122be11ab4a29e554786b4a1ec4764dd55656b59d6228a0a3de78eaf5c1f226c" + ], + "ImageConfig": { + "architecture": "amd64", + "container": "41b694b9b42f9c5ef7fb40c24272927a727a6d6cb8120bb3eae5849ceb9bee77", + "created": "2019-08-15T07:28:14.830150536Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2019-08-15T07:28:12.433344678Z", + "created_by": "/bin/sh -c #(nop) ADD file:c477cb0e95c56b51e0b7353f3805165393689902b82a41bbe77dbef4b31667e1 in / " + }, + { + "created": "2019-08-15T07:28:13.20852008Z", + "created_by": "/bin/sh -c [ -z \"$(apt-get indextargets)\" ]" + }, + { + "created": "2019-08-15T07:28:13.964607567Z", + "created_by": "/bin/sh -c set -xe \t\t\u0026\u0026 echo '#!/bin/sh' \u003e /usr/sbin/policy-rc.d \t\u0026\u0026 echo 'exit 101' \u003e\u003e /usr/sbin/policy-rc.d \t\u0026\u0026 chmod +x /usr/sbin/policy-rc.d \t\t\u0026\u0026 dpkg-divert --local --rename --add /sbin/initctl \t\u0026\u0026 cp -a /usr/sbin/policy-rc.d /sbin/initctl \t\u0026\u0026 sed -i 's/^exit.*/exit 0/' /sbin/initctl \t\t\u0026\u0026 echo 'force-unsafe-io' \u003e /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \t\t\u0026\u0026 echo 'DPkg::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\"; };' \u003e /etc/apt/apt.conf.d/docker-clean \t\u0026\u0026 echo 'APT::Update::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\"; };' \u003e\u003e /etc/apt/apt.conf.d/docker-clean \t\u0026\u0026 echo 'Dir::Cache::pkgcache \"\"; Dir::Cache::srcpkgcache \"\";' \u003e\u003e /etc/apt/apt.conf.d/docker-clean \t\t\u0026\u0026 echo 'Acquire::Languages \"none\";' \u003e /etc/apt/apt.conf.d/docker-no-languages \t\t\u0026\u0026 echo 'Acquire::GzipIndexes \"true\"; Acquire::CompressionTypes::Order:: \"gz\";' \u003e /etc/apt/apt.conf.d/docker-gzip-indexes \t\t\u0026\u0026 echo 'Apt::AutoRemove::SuggestsImportant \"false\";' \u003e /etc/apt/apt.conf.d/docker-autoremove-suggests" + }, + { + "created": "2019-08-15T07:28:14.64282638Z", + "created_by": "/bin/sh -c mkdir -p /run/systemd \u0026\u0026 echo 'docker' \u003e /run/systemd/container" + }, + { + "created": "2019-08-15T07:28:14.830150536Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f", + "sha256:f7eae43028b334123c3a1d778f7bdf9783bbe651c8b15371df0120fd13ec35c5", + "sha256:7beb13bce073c21c9ee608acb13c7e851845245dc76ce81b418fdf580c45076b", + "sha256:122be11ab4a29e554786b4a1ec4764dd55656b59d6228a0a3de78eaf5c1f226c" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Image": "sha256:bcbe079849fdbb50b3eb04798547e046bdbc82020b8b780d767cf29f7e60b396", + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/ubuntu-1804.tar.gz (ubuntu 18.04)", + "Class": "os-pkgs", + "Type": "ubuntu", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@4.4.18-2ubuntu1.2", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/bash@4.4.18-2ubuntu1.2?arch=amd64\u0026distro=ubuntu-18.04" + }, + "InstalledVersion": "4.4.18-2ubuntu1.2", + "Status": "affected", + "Layer": { + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped", + "Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "Severity": "LOW", + "CweIDs": [ + "CWE-273" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "nvd": 3, + "oracle-oval": 1, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.2, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html", + "https://access.redhat.com/security/cve/CVE-2019-18276", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276", + "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff", + "https://linux.oracle.com/cve/CVE-2019-18276.html", + "https://linux.oracle.com/errata/ELSA-2021-1679.html", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://nvd.nist.gov/vuln/detail/CVE-2019-18276", + "https://security.gentoo.org/glsa/202105-34", + "https://security.netapp.com/advisory/ntap-20200430-0003/", + "https://www.youtube.com/watch?v=-wGtxJ8opa8" + ], + "PublishedDate": "2019-11-28T01:15:00Z", + "LastModifiedDate": "2021-05-26T12:15:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "PkgID": "e2fsprogs@1.44.1-1ubuntu1.1", + "PkgName": "e2fsprogs", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/e2fsprogs@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + }, + "InstalledVersion": "1.44.1-1ubuntu1.1", + "FixedVersion": "1.44.1-1ubuntu1.2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "PkgID": "libcom-err2@1.44.1-1ubuntu1.1", + "PkgName": "libcom-err2", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/libcom-err2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + }, + "InstalledVersion": "1.44.1-1ubuntu1.1", + "FixedVersion": "1.44.1-1ubuntu1.2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "PkgID": "libext2fs2@1.44.1-1ubuntu1.1", + "PkgName": "libext2fs2", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/libext2fs2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + }, + "InstalledVersion": "1.44.1-1ubuntu1.1", + "FixedVersion": "1.44.1-1ubuntu1.2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21:00Z" + }, + { + "VulnerabilityID": "CVE-2019-5094", + "PkgID": "libss2@1.44.1-1ubuntu1.1", + "PkgName": "libss2", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/libss2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + }, + "InstalledVersion": "1.44.1-1ubuntu1.1", + "FixedVersion": "1.44.1-1ubuntu1.2", + "Status": "fixed", + "Layer": { + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-5094", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "e2fsprogs: Crafted ext4 partition leads to out-of-bounds write", + "Description": "An exploitable code execution vulnerability exists in the quota file functionality of E2fsprogs 1.45.3. A specially crafted ext4 partition can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "amazon": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 4.6, + "V3Score": 6.7 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 6.4 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2019-5094", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5094", + "https://linux.oracle.com/cve/CVE-2019-5094.html", + "https://linux.oracle.com/errata/ELSA-2020-4011.html", + "https://lists.debian.org/debian-lts-announce/2019/09/msg00029.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2AKETJ6BREDUHRWQTV35SPGG5C6H7KSI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6DOBCYQKCTTWXBLMUPJ5TX3FY7JNCOKY/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-5094", + "https://seclists.org/bugtraq/2019/Sep/58", + "https://security.gentoo.org/glsa/202003-05", + "https://security.netapp.com/advisory/ntap-20200115-0002/", + "https://talosintelligence.com/vulnerability_reports/TALOS-2019-0887", + "https://ubuntu.com/security/notices/USN-4142-1", + "https://ubuntu.com/security/notices/USN-4142-2", + "https://usn.ubuntu.com/4142-1/", + "https://usn.ubuntu.com/4142-2/", + "https://www.debian.org/security/2019/dsa-4535" + ], + "PublishedDate": "2019-09-24T22:15:00Z", + "LastModifiedDate": "2021-01-11T19:21: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 000000000000..0c62f5aab4e2 --- /dev/null +++ b/integration/testdata/ubuntu-gp2-x86-vm.json.golden @@ -0,0 +1,75 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "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-3715", + "PkgID": "bash@5.1-6ubuntu1", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/bash@5.1-6ubuntu1?arch=amd64\u0026distro=ubuntu-22.04" + }, + "InstalledVersion": "5.1-6ubuntu1", + "Status": "affected", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-3715", + "Title": "a heap-buffer-overflow in valid_parameter_transform", + "Description": "A flaw was found in the bash package, where a heap-buffer overflow can occur in valid parameter_transform. This issue may lead to memory problems.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-787" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "nvd": 3, + "photon": 3, + "redhat": 1, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H", + "V3Score": 6.6 + } + }, + "References": [ + "https://access.redhat.com/errata/RHSA-2023:0340", + "https://access.redhat.com/security/cve/CVE-2022-3715", + "https://bugzilla.redhat.com/2126720", + "https://bugzilla.redhat.com/show_bug.cgi?id=2126720" + ], + "PublishedDate": "2023-01-05T15:15:00Z", + "LastModifiedDate": "2023-02-24T18:38:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/yarn.json.golden b/integration/testdata/yarn.json.golden new file mode 100644 index 000000000000..1e5cd3da24b3 --- /dev/null +++ b/integration/testdata/yarn.json.golden @@ -0,0 +1,179 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/yarn", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "yarn.lock", + "Class": "lang-pkgs", + "Type": "yarn", + "Packages": [ + { + "ID": "jquery@3.2.1", + "Name": "jquery", + "Identifier": { + "PURL": "pkg:npm/jquery@3.2.1" + }, + "Version": "3.2.1", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 10, + "EndLine": 13 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-11358", + "PkgID": "jquery@3.2.1", + "PkgName": "jquery", + "PkgIdentifier": { + "PURL": "pkg:npm/jquery@3.2.1" + }, + "InstalledVersion": "3.2.1", + "FixedVersion": "3.4.0", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-11358", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "jquery: Prototype pollution in object's prototype leading to denial of service, remote code execution, or property injection", + "Description": "jQuery before 3.4.0, as used in Drupal, Backdrop CMS, and other products, mishandles jQuery.extend(true, {}, ...) because of Object.prototype pollution. If an unsanitized source object contained an enumerable __proto__ property, it could extend the native Object.prototype.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-79" + ], + "VendorSeverity": { + "alma": 2, + "amazon": 2, + "arch-linux": 2, + "ghsa": 2, + "nodejs-security-wg": 2, + "nvd": 2, + "oracle-oval": 2, + "redhat": 2, + "ruby-advisory-db": 2, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "V2Score": 4.3, + "V3Score": 6.1 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "V3Score": 5.6 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00006.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00025.html", + "http://packetstormsecurity.com/files/152787/dotCMS-5.1.1-Vulnerable-Dependencies.html", + "http://packetstormsecurity.com/files/153237/RetireJS-CORS-Issue-Script-Execution.html", + "http://packetstormsecurity.com/files/156743/OctoberCMS-Insecure-Dependencies.html", + "http://seclists.org/fulldisclosure/2019/May/10", + "http://seclists.org/fulldisclosure/2019/May/11", + "http://seclists.org/fulldisclosure/2019/May/13", + "http://www.openwall.com/lists/oss-security/2019/06/03/2", + "http://www.securityfocus.com/bid/108023", + "https://access.redhat.com/errata/RHBA-2019:1570", + "https://access.redhat.com/errata/RHSA-2019:1456", + "https://access.redhat.com/errata/RHSA-2019:2587", + "https://access.redhat.com/errata/RHSA-2019:3023", + "https://access.redhat.com/errata/RHSA-2019:3024", + "https://access.redhat.com/security/cve/CVE-2019-11358", + "https://backdropcms.org/security/backdrop-sa-core-2019-009", + "https://blog.jquery.com/2019/04/10/jquery-3-4-0-released/", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11358", + "https://github.com/DanielRuf/snyk-js-jquery-174006?files=1", + "https://github.com/advisories/GHSA-6c3j-c64m-qhgq", + "https://github.com/jquery/jquery/commit/753d591aea698e57d6db58c9f722cd0808619b1b", + "https://github.com/jquery/jquery/pull/4333", + "https://github.com/rails/jquery-rails/blob/master/CHANGELOG.md#434", + "https://hackerone.com/reports/454365", + "https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44601", + "https://linux.oracle.com/cve/CVE-2019-11358.html", + "https://linux.oracle.com/errata/ELSA-2020-4847.html", + "https://lists.apache.org/thread.html/08720ef215ee7ab3386c05a1a90a7d1c852bf0706f176a7816bf65fc@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/519eb0fd45642dcecd9ff74cb3e71c20a4753f7d82e2f07864b5108f@%3Cdev.drill.apache.org%3E", + "https://lists.apache.org/thread.html/5928aa293e39d248266472210c50f176cac1535220f2486e6a7fa844@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/6097cdbd6f0a337bedd9bb5cc441b2d525ff002a96531de367e4259f@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/88fb0362fd40e5b605ea8149f63241537b8b6fb5bfa315391fc5cbb7@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/b0656d359c7d40ec9f39c8cc61bca66802ef9a2a12ee199f5b0c1442@%3Cdev.drill.apache.org%3E", + "https://lists.apache.org/thread.html/b736d0784cf02f5a30fbb4c5902762a15ad6d47e17e2c5a17b7d6205@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/ba79cf1658741e9f146e4c59b50aee56656ea95d841d358d006c18b6@%3Ccommits.roller.apache.org%3E", + "https://lists.apache.org/thread.html/bcce5a9c532b386c68dab2f6b3ce8b0cc9b950ec551766e76391caa3@%3Ccommits.nifi.apache.org%3E", + "https://lists.apache.org/thread.html/f9bc3e55f4e28d1dcd1a69aae6d53e609a758e34d2869b4d798e13cc@%3Cissues.drill.apache.org%3E", + "https://lists.apache.org/thread.html/r2041a75d3fc09dec55adfd95d598b38d22715303f65c997c054844c9@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r2baacab6e0acb5a2092eb46ae04fd6c3e8277b4fd79b1ffb7f3254fa@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r38f0d1aa3c923c22977fe7376508f030f22e22c1379fbb155bf29766@%3Cdev.syncope.apache.org%3E", + "https://lists.apache.org/thread.html/r41b5bfe009c845f67d4f68948cc9419ac2d62e287804aafd72892b08@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r7aac081cbddb6baa24b75e74abf0929bf309b176755a53e3ed810355@%3Cdev.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r7d64895cc4dff84d0becfc572b20c0e4bf9bfa7b10c6f5f73e783734@%3Cdev.storm.apache.org%3E", + "https://lists.apache.org/thread.html/r7e8ebccb7c022e41295f6fdb7b971209b83702339f872ddd8cf8bf73@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/rac25da84ecdcd36f6de5ad0d255f4e967209bbbebddb285e231da37d@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/rca37935d661f4689cb4119f1b3b224413b22be161b678e6e6ce0c69b@%3Ccommits.nifi.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2019/05/msg00006.html", + "https://lists.debian.org/debian-lts-announce/2019/05/msg00029.html", + "https://lists.debian.org/debian-lts-announce/2020/02/msg00024.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/4UOAZIFCSZ3ENEFOR5IXX6NFAD3HV7FA/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5IABSKTYZ5JUGL735UKGXL5YPRYOPUYI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KYH3OAGR2RTCHRA5NOKX2TES7SNQMWGO/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QV3PKZC3PQCO3273HAT76PAQZFBEO4KP/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RLXRX23725JL366CNZGJZ7AQQB7LHQ6F/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WZW27UCJ5CYFL4KFFFMYMIBNMIU2ALG5/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-11358", + "https://seclists.org/bugtraq/2019/Apr/32", + "https://seclists.org/bugtraq/2019/Jun/12", + "https://seclists.org/bugtraq/2019/May/18", + "https://security.netapp.com/advisory/ntap-20190919-0001/", + "https://snyk.io/vuln/SNYK-JS-JQUERY-174006", + "https://www.debian.org/security/2019/dsa-4434", + "https://www.debian.org/security/2019/dsa-4460", + "https://www.drupal.org/sa-core-2019-006", + "https://www.oracle.com//security-alerts/cpujul2021.html", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/security-alerts/cpuoct2021.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.privacy-wise.com/mitigating-cve-2019-11358-in-old-versions-of-jquery/", + "https://www.synology.com/security/advisory/Synology_SA_19_19", + "https://www.tenable.com/security/tns-2019-08", + "https://www.tenable.com/security/tns-2020-02" + ], + "PublishedDate": "2019-04-20T00:29:00Z", + "LastModifiedDate": "2021-10-20T11:15:00Z" + } + ] + } + ] +} diff --git a/integration/vm_test.go b/integration/vm_test.go new file mode 100644 index 000000000000..7ccc85f03994 --- /dev/null +++ b/integration/vm_test.go @@ -0,0 +1,101 @@ +//go:build vm_integration + +package integration + +import ( + "path/filepath" + "strings" + "testing" + + "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) + + const imageFile = "disk.img" + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osArgs := []string{ + "--cache-dir", + cacheDir, + "vm", + "--scanners", + "vuln", + "-q", + "--skip-db-update", + "--format", + tt.args.format, + } + + // Decompress the gzipped image file + imagePath := filepath.Join(t.TempDir(), imageFile) + testutil.DecompressSparseGzip(t, tt.args.input, imagePath) + + osArgs = append(osArgs, imagePath) + + // Run "trivy vm" + runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{ + override: func(_, got *types.Report) { + got.ArtifactName = "disk.img" + for i := range got.Results { + lastIndex := strings.LastIndex(got.Results[i].Target, "/") + got.Results[i].Target = got.Results[i].Target[lastIndex+1:] + } + }, + }) + }) + } +} diff --git a/internal/testutil/error.go b/internal/testutil/error.go new file mode 100644 index 000000000000..3a24df91c102 --- /dev/null +++ b/internal/testutil/error.go @@ -0,0 +1,11 @@ +package testutil + +import ( + "runtime" + + "github.com/samber/lo" +) + +var ErrNotExist string = lo.Ternary(runtime.GOOS == "windows", + "The system cannot find the file specified.", + "no such file or directory") diff --git a/internal/testutil/gzip.go b/internal/testutil/gzip.go new file mode 100644 index 000000000000..6f417e326ab6 --- /dev/null +++ b/internal/testutil/gzip.go @@ -0,0 +1,77 @@ +package testutil + +import ( + "bytes" + "compress/gzip" + "io" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + max = int64(10) << 30 // 10GB + blockSize = 4096 +) + +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) +} + +// DecompressSparseGzip decompresses a sparse gzip file for virtual machine image. +func DecompressSparseGzip(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) + + buf := make([]byte, blockSize) + var size int + var written int64 + for { + n, err := gr.Read(buf) + if n == 0 && err != nil { + if err == io.EOF { + break + } + require.NoError(t, err) + } + + size += n + err = w.Truncate(int64(size)) + require.NoError(t, err) + + if !bytes.Equal(buf[:n], make([]byte, n)) { + wn, err := w.WriteAt(buf[:n], int64(size-n)) + if err != nil { + if err == io.EOF { + break + } + require.NoError(t, err) + } + written += int64(wn) + if written > max { + require.Fail(t, "written size exceeds max") + } + } + } +} diff --git a/internal/testutil/localstack.go b/internal/testutil/localstack.go new file mode 100644 index 000000000000..71eaf5a3fcf9 --- /dev/null +++ b/internal/testutil/localstack.go @@ -0,0 +1,51 @@ +package testutil + +import ( + "context" + "fmt" + "os" + + dockercontainer "github.com/docker/docker/api/types/container" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/localstack" +) + +func SetupLocalStack(ctx context.Context, version string) (*localstack.LocalStackContainer, string, error) { + + if err := os.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true"); err != nil { + return nil, "", err + } + + container, err := localstack.RunContainer(ctx, testcontainers.CustomizeRequest( + testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "localstack/localstack:" + version, + HostConfigModifier: func(hostConfig *dockercontainer.HostConfig) { + hostConfig.AutoRemove = true + }, + }, + }, + )) + if err != nil { + return nil, "", err + } + + p, err := container.MappedPort(ctx, "4566/tcp") + if err != nil { + return nil, "", err + } + + provider, err := testcontainers.NewDockerProvider() + if err != nil { + return nil, "", err + } + defer provider.Close() + + host, err := provider.DaemonHost(ctx) + if err != nil { + return nil, "", err + } + + return container, fmt.Sprintf("http://%s:%d", host, p.Int()), nil + +} diff --git a/internal/testutil/util.go b/internal/testutil/util.go new file mode 100644 index 000000000000..f16b95acaae3 --- /dev/null +++ b/internal/testutil/util.go @@ -0,0 +1,114 @@ +package testutil + +import ( + "encoding/json" + "io/fs" + "path/filepath" + "strings" + "testing" + + "github.com/liamg/memoryfs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scan" +) + +func AssertRuleFound(t *testing.T, ruleID string, results scan.Results, message string, args ...interface{}) { + found := ruleIDInResults(ruleID, results.GetFailed()) + assert.True(t, found, append([]interface{}{message}, args...)...) + for _, result := range results.GetFailed() { + if result.Rule().LongID() == ruleID { + m := result.Metadata() + meta := &m + for meta != nil { + assert.NotNil(t, meta.Range(), 0) + assert.Greater(t, meta.Range().GetStartLine(), 0) + assert.Greater(t, meta.Range().GetEndLine(), 0) + meta = meta.Parent() + } + } + } +} + +func AssertRuleNotFound(t *testing.T, ruleID string, results scan.Results, message string, args ...interface{}) { + found := ruleIDInResults(ruleID, results.GetFailed()) + assert.False(t, found, append([]interface{}{message}, args...)...) +} + +func ruleIDInResults(ruleID string, results scan.Results) bool { + for _, res := range results { + if res.Rule().LongID() == ruleID { + return true + } + } + return false +} + +func CreateFS(t *testing.T, files map[string]string) fs.FS { + memfs := memoryfs.New() + for name, content := range files { + name := strings.TrimPrefix(name, "/") + err := memfs.MkdirAll(filepath.Dir(name), 0o700) + require.NoError(t, err) + err = memfs.WriteFile(name, []byte(content), 0o644) + require.NoError(t, err) + } + return memfs +} + +func AssertDefsecEqual(t *testing.T, expected, actual interface{}) { + expectedJson, err := json.MarshalIndent(expected, "", "\t") + require.NoError(t, err) + actualJson, err := json.MarshalIndent(actual, "", "\t") + require.NoError(t, err) + + if expectedJson[0] == '[' { + var expectedSlice []map[string]interface{} + require.NoError(t, json.Unmarshal(expectedJson, &expectedSlice)) + var actualSlice []map[string]interface{} + require.NoError(t, json.Unmarshal(actualJson, &actualSlice)) + expectedSlice = purgeMetadataSlice(expectedSlice) + actualSlice = purgeMetadataSlice(actualSlice) + assert.Equal(t, expectedSlice, actualSlice, "defsec adapted and expected values do not match") + } else { + var expectedMap map[string]interface{} + require.NoError(t, json.Unmarshal(expectedJson, &expectedMap)) + var actualMap map[string]interface{} + require.NoError(t, json.Unmarshal(actualJson, &actualMap)) + expectedMap = purgeMetadata(expectedMap) + actualMap = purgeMetadata(actualMap) + assert.Equal(t, expectedMap, actualMap, "defsec adapted and expected values do not match") + } +} + +func purgeMetadata(input map[string]interface{}) map[string]interface{} { + for k, v := range input { + if k == "metadata" || k == "Metadata" { + delete(input, k) + continue + } + if v, ok := v.(map[string]interface{}); ok { + input[k] = purgeMetadata(v) + } + if v, ok := v.([]interface{}); ok { + if len(v) > 0 { + if _, ok := v[0].(map[string]interface{}); ok { + maps := make([]map[string]interface{}, len(v)) + for i := range v { + maps[i] = v[i].(map[string]interface{}) + } + input[k] = purgeMetadataSlice(maps) + } + } + } + } + return input +} + +func purgeMetadataSlice(input []map[string]interface{}) []map[string]interface{} { + for i := range input { + input[i] = purgeMetadata(input[i]) + } + return input +} diff --git a/magefiles/cloud_actions.go b/magefiles/cloud_actions.go new file mode 100644 index 000000000000..045586c8837a --- /dev/null +++ b/magefiles/cloud_actions.go @@ -0,0 +1,271 @@ +//go:build mage_cloudactions + +package main + +import ( + "bufio" + "context" + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "github.com/antchfx/htmlquery" + "github.com/aquasecurity/trivy/pkg/log" + "golang.org/x/net/html" + "golang.org/x/sync/errgroup" +) + +const ( + serviceAuthURL = "https://docs.aws.amazon.com/service-authorization/latest/reference/" + serviceActionReferencesURL = "https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html" + targetFile = "pkg/iac/providers/aws/iam/actions.go" + defaultParallel = 10 +) + +func parseServiceURLs(doc *html.Node) ([]string, error) { + nodes, err := htmlquery.QueryAll(doc, `//div[@class="highlights"]/ul/li/a/@href`) + if err != nil { + return nil, fmt.Errorf("failed to search nodes: %w\n", err) + } + + res := make([]string, 0, len(nodes)) + + for _, node := range nodes { + // AWS Account Management + if node.FirstChild != nil { + res = append(res, serviceAuthURL+node.FirstChild.Data[2:]) + } + } + + return res, nil +} + +func parseActions(url string) ([]string, error) { + + doc, err := htmlquery.LoadURL(url) + if err != nil { + return nil, err + } + + servicePrefix, err := parseServicePrefix(doc) + if err != nil { + return nil, err + } + + actions, err := parseServiceActions(doc) + if err != nil { + return nil, err + } + + res := make([]string, 0, len(actions)) + + for _, act := range actions { + res = append(res, servicePrefix+":"+act) + } + + fmt.Printf("Parsing of %q actions is completed\n", servicePrefix) + + return res, nil +} + +func parseServiceActions(doc *html.Node) ([]string, error) { + table, err := htmlquery.Query(doc, `//div[@class="table-container"]/div/table/tbody`) + if table == nil { + return nil, errors.New("actions table not found") + } + if err != nil { + return nil, fmt.Errorf("failed to query tables: %w\n", err) + } + + var actions []string + + var f func(*html.Node) + f = func(n *html.Node) { + for _, tr := range findSubtags(n, "tr") { + var action string + for k, td := range findSubtags(tr, "td") { + // first column - action + if k == 0 { + if a := findSubtag(td, "a"); a != nil && a.FirstChild != nil { + action = a.FirstChild.Data + } + + // fourth column - resource type + // If the column is empty, then the action does not support resource-level permissions + // and you must specify all resources ("*") in your policy + } else if action != "" && k == 3 && td.FirstChild == nil { + actions = append(actions, action) + } + } + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(table) + + return actions, err +} + +func findSubtag(n *html.Node, tagName string) *html.Node { + for c := n.FirstChild; c != nil; c = c.NextSibling { + if c.Type == html.ElementNode && c.Data == tagName { + return c + } + } + + return nil +} + +func findSubtags(n *html.Node, tagName string) []*html.Node { + var result []*html.Node + for c := n.FirstChild; c != nil; c = c.NextSibling { + if c.Type == html.ElementNode && c.Data == tagName { + result = append(result, c) + } + } + return result +} + +func parseServicePrefix(doc *html.Node) (string, error) { + nodes, err := htmlquery.QueryAll(doc, `//div[@id="main-col-body"]/p/descendant-or-self::*/text()`) + if err != nil { + return "", fmt.Errorf("failed to query paragraph: %w\n", err) + } + + var sb strings.Builder + for _, node := range nodes { + sb.WriteString(node.Data) + } + + p := sb.String() + sb.Reset() + + idx := strings.Index(p, "service prefix: ") + if idx == -1 { + return "", fmt.Errorf("failed extract service prefix from text: %s\n", p) + } + idx += len("service prefix: ") + + if len(p)-1 <= idx { + return "", fmt.Errorf("failed to parse service prefix from text: %s\n", p) + } + + var parsed bool + for _, r := range p[idx:] { + if r == ')' { + parsed = true + break + } + sb.WriteRune(r) + } + + if !parsed { + return "", fmt.Errorf("failed to parse service prefix from text: %s\n", p) + } + + return sb.String(), nil +} + +func generateFile(path string, actions []string) error { + + f, err := os.Create(path) + if err != nil { + return fmt.Errorf("failed to create file: %w\n", err) + } + defer f.Close() + + w := bufio.NewWriter(f) + _, _ = w.WriteString( + `// Code generated by mage genallowedactions DO NOT EDIT. + +package iam + +var allowedActionsForResourceWildcardsMap = map[string]struct{}{ +`, + ) + + for _, action := range actions { + _, _ = w.WriteString("\t\"" + action + "\": {},\n") + } + _, _ = w.WriteString("}") + + return w.Flush() +} + +func main() { + if err := GenAllowedActions(); err != nil { + log.Fatal(err) + } +} + +// GenAllowedActions generates the list of valid actions for wildcard support +func GenAllowedActions() error { + log.Logger.Info("Start parsing actions") + startTime := time.Now() + defer func() { + log.Logger.Infof("Parsing is completed. Duration %fs\n", time.Since(startTime).Seconds()) + }() + + doc, err := htmlquery.LoadURL(serviceActionReferencesURL) + if err != nil { + return fmt.Errorf("failed to retrieve action references: %w\n", err) + } + urls, err := parseServiceURLs(doc) + if err != nil { + return err + } + + g, ctx := errgroup.WithContext(context.TODO()) + g.SetLimit(defaultParallel) + + // actions may be the same for services of different versions, + // e.g. Elastic Load Balancing and Elastic Load Balancing V2 + actionsSet := make(map[string]struct{}) + + var mu sync.Mutex + + for _, url := range urls { + url := url + if ctx.Err() != nil { + break + } + g.Go(func() error { + serviceActions, err := parseActions(url) + if err != nil { + return fmt.Errorf("failed to parse actions from %q: %w\n", url, err) + } + + mu.Lock() + for _, act := range serviceActions { + actionsSet[act] = struct{}{} + } + mu.Unlock() + + return nil + }) + } + + if err := g.Wait(); err != nil { + return err + } + + actions := make([]string, 0, len(actionsSet)) + + for act := range actionsSet { + actions = append(actions, act) + } + + sort.Strings(actions) + + path := filepath.FromSlash(targetFile) + if err := generateFile(path, actions); err != nil { + return fmt.Errorf("failed to generate file: %w\n", err) + } + return nil +} diff --git a/magefiles/cloud_actions_test.go b/magefiles/cloud_actions_test.go new file mode 100644 index 000000000000..8b558062f96f --- /dev/null +++ b/magefiles/cloud_actions_test.go @@ -0,0 +1,100 @@ +//go:build mage_cloudactions + +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/antchfx/htmlquery" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseActionTableURLs(t *testing.T) { + + doc, err := htmlquery.LoadDoc(filepath.Join("testdata", "reference_policies_actions-resources-contextkeys.html")) + require.NoError(t, err) + + urls, err := parseServiceURLs(doc) + require.NoError(t, err) + + expected := []string{ + "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsaccountmanagement.html", + "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsactivate.html", + "https://docs.aws.amazon.com/service-authorization/latest/reference/list_alexaforbusiness.html", + "https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonmediaimport.html", + "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsamplify.html", + "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsamplifyadmin.html", + "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsamplifyuibuilder.html", + } + assert.Equal(t, expected, urls) +} + +func TestParseServicePrefix(t *testing.T) { + + doc, err := htmlquery.LoadDoc(filepath.Join("testdata", "list_amazoncloudwatch.html")) + require.NoError(t, err) + + servicePrefix, err := parseServicePrefix(doc) + require.NoError(t, err) + + assert.Equal(t, "cloudwatch", servicePrefix) +} + +func TestParseActionsFromTable(t *testing.T) { + + doc, err := htmlquery.LoadDoc(filepath.Join("testdata", "list_amazoncloudwatch.html")) + require.NoError(t, err) + + actions, err := parseServiceActions(doc) + require.NoError(t, err) + + expected := []string{ + "DeleteAnomalyDetector", + "DescribeAlarmsForMetric", + "DescribeAnomalyDetectors", + "DescribeInsightRules", + "GetMetricData", + "GetMetricStatistics", + "GetMetricWidgetImage", + "Link", + "ListDashboards", + "ListManagedInsightRules", + "ListMetricStreams", + "ListMetrics", + "PutAnomalyDetector", + "PutManagedInsightRules", + "PutMetricData", + } + + assert.Equal(t, expected, actions) +} + +func TestGenerateFile(t *testing.T) { + tmpDir := t.TempDir() + + actions := []string{ + "account:DisableRegion", + "account:EnableRegion", + "account:ListRegions", + } + path := filepath.Join(tmpDir, "test.go") + require.NoError(t, generateFile(path, actions)) + + expected := `// Code generated by mage genallowedactions DO NOT EDIT. + +package iam + +var allowedActionsForResourceWildcardsMap = map[string]struct{}{ + "account:DisableRegion": {}, + "account:EnableRegion": {}, + "account:ListRegions": {}, +}` + + b, err := os.ReadFile(path) + require.NoError(t, err) + + assert.Equal(t, expected, string(b)) +} diff --git a/magefiles/docs.go b/magefiles/docs.go new file mode 100644 index 000000000000..b69e813690af --- /dev/null +++ b/magefiles/docs.go @@ -0,0 +1,29 @@ +//go:build mage_docs + +package main + +import ( + "os" + + "github.com/spf13/cobra/doc" + + "github.com/aquasecurity/trivy/pkg/commands" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" +) + +// Generate CLI references +func main() { + // Set a dummy path for the documents + flag.CacheDirFlag.Default = "/path/to/cache" + flag.ModuleDirFlag.Default = "$HOME/.trivy/modules" + + // Set a dummy path not to load plugins + os.Setenv("XDG_DATA_HOME", os.TempDir()) + + cmd := commands.NewApp() + cmd.DisableAutoGenTag = true + if err := doc.GenMarkdownTree(cmd, "./docs/docs/references/configuration/cli"); err != nil { + log.Fatal(err) + } +} diff --git a/magefiles/fixture.go b/magefiles/fixture.go new file mode 100644 index 000000000000..0ed9ae8d4217 --- /dev/null +++ b/magefiles/fixture.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/google/go-containerregistry/pkg/crane" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/magefile/mage/sh" +) + +func fixtureContainerImages() error { + const ( + testImages = "ghcr.io/aquasecurity/trivy-test-images" + dir = "integration/testdata/fixtures/images/" + ) + if err := os.MkdirAll(dir, 0750); err != nil { + return err + } + tags, err := crane.ListTags(testImages) + if err != nil { + return err + } + for _, tag := range tags { + fileName := tag + ".tar.gz" + filePath := filepath.Join(dir, fileName) + if exists(filePath) { + continue + } + fmt.Printf("Downloading %s...\n", tag) + imgName := fmt.Sprintf("%s:%s", testImages, tag) + img, err := crane.Pull(imgName) + if err != nil { + return err + } + tarPath := strings.TrimSuffix(filePath, ".gz") + if err = crane.Save(img, imgName, tarPath); err != nil { + return err + } + if err = sh.Run("gzip", tarPath); err != nil { + return err + } + } + return nil +} + +func fixtureVMImages() error { + const ( + testVMImages = "ghcr.io/aquasecurity/trivy-test-vm-images" + titleAnnotation = "org.opencontainers.image.title" + dir = "integration/testdata/fixtures/vm-images/" + ) + if err := os.MkdirAll(dir, 0750); err != nil { + return err + } + tags, err := crane.ListTags(testVMImages) + if err != nil { + return err + } + for _, tag := range tags { + img, err := crane.Pull(fmt.Sprintf("%s:%s", testVMImages, tag)) + if err != nil { + return err + } + + manifest, err := img.Manifest() + if err != nil { + return err + } + + layers, err := img.Layers() + if err != nil { + return err + } + + for i, layer := range layers { + fileName, ok := manifest.Layers[i].Annotations[titleAnnotation] + if !ok { + continue + } + filePath := filepath.Join(dir, fileName) + if exists(filePath) { + return nil + } + fmt.Printf("Downloading %s...\n", fileName) + if err = saveLayer(layer, filePath); err != nil { + return err + } + } + } + return nil +} + +func saveLayer(layer v1.Layer, filePath string) error { + f, err := os.Create(filePath) + if err != nil { + return err + } + defer f.Close() + + c, err := layer.Compressed() + if err != nil { + return err + } + if _, err = io.Copy(f, c); err != nil { + return err + } + return nil +} diff --git a/magefiles/magefile.go b/magefiles/magefile.go new file mode 100644 index 000000000000..fbb612b9d855 --- /dev/null +++ b/magefiles/magefile.go @@ -0,0 +1,435 @@ +package main + +import ( + "context" + "errors" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/magefile/mage/mg" + "github.com/magefile/mage/sh" + "github.com/magefile/mage/target" +) + +var ( + GOPATH = os.Getenv("GOPATH") + GOBIN = filepath.Join(GOPATH, "bin") + + ENV = map[string]string{ + "CGO_ENABLED": "0", + } +) + +func version() (string, error) { + if ver, err := sh.Output("git", "describe", "--tags", "--always"); err != nil { + return "", err + } else { + // Strips the v prefix from the tag + return strings.TrimPrefix(ver, "v"), nil + } +} + +func buildLdflags() (string, error) { + ver, err := version() + if err != nil { + return "", err + } + return fmt.Sprintf("-s -w -X=github.com/aquasecurity/trivy/pkg/version.ver=%s", ver), nil +} + +type Tool mg.Namespace + +// Aqua installs aqua if not installed +func (Tool) Aqua() error { + if exists(filepath.Join(GOBIN, "aqua")) { + return nil + } + return sh.Run("go", "install", "github.com/aquaproj/aqua/v2/cmd/aqua@v2.2.1") +} + +// Wire installs wire if not installed +func (Tool) Wire() error { + if installed("wire") { + return nil + } + return sh.Run("go", "install", "github.com/google/wire/cmd/wire@v0.5.0") +} + +// GolangciLint installs golangci-lint +func (Tool) GolangciLint() error { + const version = "v1.54.2" + if exists(filepath.Join(GOBIN, "golangci-lint")) { + return nil + } + command := fmt.Sprintf("curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b %s %s", GOBIN, version) + return sh.Run("bash", "-c", command) +} + +// Labeler installs labeler +func (Tool) Labeler() error { + if exists(filepath.Join(GOBIN, "labeler")) { + return nil + } + return sh.Run("go", "install", "github.com/knqyf263/labeler@latest") +} + +// Kind installs kind cluster +func (Tool) Kind() error { + return sh.RunWithV(ENV, "go", "install", "sigs.k8s.io/kind@v0.19.0") +} + +// Goyacc installs goyacc +func (Tool) Goyacc() error { + if exists(filepath.Join(GOBIN, "goyacc")) { + return nil + } + return sh.Run("go", "install", "golang.org/x/tools/cmd/goyacc@v0.7.0") +} + +// Mockery installs mockery +func (Tool) Mockery() error { + if exists(filepath.Join(GOBIN, "mockery")) { + return nil + } + return sh.Run("go", "install", "github.com/knqyf263/mockery/cmd/mockery@latest") +} + +// Wire generates the wire_gen.go file for each package +func Wire() error { + mg.Deps(Tool{}.Wire) + return sh.RunV("wire", "gen", "./pkg/commands/...", "./pkg/rpc/...", "./pkg/k8s/...") +} + +// Mock generates mocks +func Mock(dir string) error { + mg.Deps(Tool{}.Mockery) + mockeryArgs := []string{ + "-all", + "-inpkg", + "-case=snake", + "-dir", + dir, + } + return sh.RunV("mockery", mockeryArgs...) +} + +// Protoc parses PROTO_FILES and generates the Go code for client/server mode +func Protoc() error { + // It is called in the protoc container + if _, ok := os.LookupEnv("TRIVY_PROTOC_CONTAINER"); ok { + protoFiles, err := findProtoFiles() + if err != nil { + return err + } + for _, file := range protoFiles { + // Check if the generated Go file is up-to-date + dst := strings.TrimSuffix(file, ".proto") + ".pb.go" + if updated, err := target.Path(dst, file); err != nil { + return err + } else if !updated { + continue + } + + // Generate + if err = sh.RunV("protoc", "--twirp_out", ".", "--twirp_opt", "paths=source_relative", + "--go_out", ".", "--go_opt", "paths=source_relative", file); err != nil { + return err + } + } + return nil + } + + // It is called on the host + if err := sh.RunV("bash", "-c", "docker build -t trivy-protoc - < Dockerfile.protoc"); err != nil { + return err + } + return sh.Run("docker", "run", "--rm", "-it", "--platform", "linux/x86_64", "-v", "${PWD}:/app", "-w", "/app", "trivy-protoc", "mage", "protoc") +} + +// Yacc generates parser +func Yacc() error { + mg.Deps(Tool{}.Goyacc) + return sh.Run("go", "generate", "./pkg/licensing/expression/...") +} + +type Test mg.Namespace + +// FixtureContainerImages downloads and extracts required images +func (Test) FixtureContainerImages() error { + return fixtureContainerImages() +} + +// FixtureVMImages downloads and extracts required VM images +func (Test) FixtureVMImages() error { + return fixtureVMImages() +} + +// FixtureTerraformPlanSnapshots generates Terraform Plan files in test folders +func (Test) FixtureTerraformPlanSnapshots() error { + return fixtureTerraformPlanSnapshots(context.TODO()) +} + +// GenerateModules compiles WASM modules for unit tests +func (Test) GenerateModules() error { + pattern := filepath.Join("pkg", "module", "testdata", "*", "*.go") + if err := compileWasmModules(pattern); err != nil { + return err + } + return nil +} + +// GenerateExampleModules compiles example Wasm modules for integration tests +func (Test) GenerateExampleModules() error { + pattern := filepath.Join("examples", "module", "*", "*.go") + if err := compileWasmModules(pattern); err != nil { + return err + } + return nil +} + +// UpdateGolden updates golden files for integration tests +func (Test) UpdateGolden() error { + return sh.RunWithV(ENV, "go", "test", "-tags=integration", "./integration/...", "./pkg/fanal/test/integration/...", "-update") +} + +func compileWasmModules(pattern string) error { + goFiles, err := filepath.Glob(pattern) + if err != nil { + return err + } + + for _, src := range goFiles { + // e.g. examples/module/spring4shell/spring4shell.go + // => examples/module/spring4shell/spring4shell.wasm + dst := strings.TrimSuffix(src, ".go") + ".wasm" + if updated, err := target.Path(dst, src); err != nil { + return err + } else if !updated { + continue + } + // Check if TinyGo is installed + if !installed("tinygo") { + return errors.New("need to install TinyGo, follow https://tinygo.org/getting-started/install/") + } + if err = sh.Run("go", "generate", src); err != nil { + return err + } + } + return nil +} + +// Unit runs unit tests +func (t Test) Unit() error { + mg.Deps(t.GenerateModules) + return sh.RunWithV(ENV, "go", "test", "-v", "-short", "-coverprofile=coverage.txt", "-covermode=atomic", "./...") +} + +// Integration runs integration tests +func (t Test) Integration() error { + mg.Deps(t.FixtureContainerImages) + return sh.RunWithV(ENV, "go", "test", "-timeout", "15m", "-v", "-tags=integration", "./integration/...", "./pkg/fanal/test/integration/...") +} + +// K8s runs k8s integration tests +func (t Test) K8s() error { + mg.Deps(Tool{}.Kind) + + err := sh.RunWithV(ENV, "kind", "create", "cluster", "--name", "kind-test") + if err != nil { + return err + } + defer func() { + _ = sh.RunWithV(ENV, "kind", "delete", "cluster", "--name", "kind-test") + }() + err = sh.RunWithV(ENV, "kubectl", "apply", "-f", "./integration/testdata/fixtures/k8s/test_nginx.yaml") + if err != nil { + return err + } + return sh.RunWithV(ENV, "go", "test", "-v", "-tags=k8s_integration", "./integration/...") +} + +// Module runs Wasm integration tests +func (t Test) Module() error { + mg.Deps(t.FixtureContainerImages, t.GenerateExampleModules) + return sh.RunWithV(ENV, "go", "test", "-v", "-tags=module_integration", "./integration/...") +} + +// UpdateModuleGolden updates golden files for Wasm integration tests +func (t Test) UpdateModuleGolden() error { + mg.Deps(t.FixtureContainerImages, t.GenerateExampleModules) + return sh.RunWithV(ENV, "go", "test", "-v", "-tags=module_integration", "./integration/...", "-update") +} + +// VM runs VM integration tests +func (t Test) VM() error { + mg.Deps(t.FixtureVMImages) + return sh.RunWithV(ENV, "go", "test", "-v", "-tags=vm_integration", "./integration/...") +} + +// UpdateVMGolden updates golden files for integration tests +func (t Test) UpdateVMGolden() error { + mg.Deps(t.FixtureVMImages) + return sh.RunWithV(ENV, "go", "test", "-v", "-tags=vm_integration", "./integration/...", "-update") +} + +type Lint mg.Namespace + +// Run runs linters +func (Lint) Run() error { + mg.Deps(Tool{}.GolangciLint) + return sh.RunV("golangci-lint", "run", "--timeout", "5m") +} + +// Fix auto fixes linters +func (Lint) Fix() error { + mg.Deps(Tool{}.GolangciLint) + return sh.RunV("golangci-lint", "run", "--timeout", "5m", "--fix") +} + +// Fmt formats Go code and proto files +func Fmt() error { + // Check if clang-format is installed + if !installed("clang-format") { + return errors.New("need to install clang-format") + } + + // Format proto files + protoFiles, err := findProtoFiles() + if err != nil { + return err + } + for _, file := range protoFiles { + if err = sh.Run("clang-format", "-i", file); err != nil { + return err + } + } + + // Format Go code + return sh.Run("go", "fmt", "./...") +} + +// Tidy makes sure go.mod matches the source code in the module +func Tidy() error { + return sh.RunV("go", "mod", "tidy") +} + +// Build builds Trivy +func Build() error { + if updated, err := target.Dir("trivy", "pkg", "cmd"); err != nil { + return err + } else if !updated { + return nil + } + + ldflags, err := buildLdflags() + if err != nil { + return err + } + wd, err := os.Getwd() + if err != nil { + return err + } + return sh.RunWith(ENV, "go", "build", "-ldflags", ldflags, filepath.Join(wd, "cmd", "trivy")) +} + +// Install installs Trivy +func Install() error { + ldflags, err := buildLdflags() + if err != nil { + return err + } + wd, err := os.Getwd() + if err != nil { + return err + } + return sh.RunWith(ENV, "go", "install", "-ldflags", ldflags, filepath.Join(wd, "cmd", "trivy")) +} + +// Clean cleans up the fixtures +func Clean() error { + fixtureDir := filepath.Join("integration", "testdata", "fixtures") + paths := []string{ + filepath.Join(fixtureDir, "images"), + filepath.Join(fixtureDir, "vm-images"), + } + for _, p := range paths { + if err := sh.Rm(p); err != nil { + return err + } + } + return nil +} + +// Label updates labels +func Label() error { + mg.Deps(Tool{}.Labeler) + return sh.RunV("labeler", "apply", "misc/triage/labels.yaml", "-l", "5") +} + +type Docs mg.Namespace + +// Serve launches MkDocs development server to preview the documentation page +func (Docs) Serve() error { + const ( + mkdocsImage = "aquasec/mkdocs-material:dev" + mkdocsPort = "8000" + ) + if err := sh.Run("docker", "build", "-t", mkdocsImage, "-f", "docs/build/Dockerfile", "docs/build"); err != nil { + return err + } + return sh.Run("docker", "run", "--name", "mkdocs-serve", "--rm", "-v", "${PWD}:/docs", "-p", mkdocsPort+":8000", mkdocsImage) +} + +// Generate generates CLI references +func (Docs) Generate() error { + return sh.RunWith(ENV, "go", "run", "-tags=mage_docs", "./magefiles") +} + +func findProtoFiles() ([]string, error) { + var files []string + err := filepath.WalkDir("rpc", func(path string, d fs.DirEntry, err error) error { + switch { + case err != nil: + return err + case d.IsDir(): + return nil + case filepath.Ext(path) == ".proto": + files = append(files, path) + } + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} + +func exists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} + +func installed(cmd string) bool { + _, err := exec.LookPath(cmd) + return err == nil +} + +type Schema mg.Namespace + +func (Schema) Generate() error { + return sh.RunWith(ENV, "go", "run", "-tags=mage_schema", "./magefiles", "--", "generate") +} + +func (Schema) Verify() error { + return sh.RunWith(ENV, "go", "run", "-tags=mage_schema", "./magefiles", "--", "verify") +} + +type CloudActions mg.Namespace + +func (CloudActions) Generate() error { + return sh.RunWith(ENV, "go", "run", "-tags=mage_cloudactions", "./magefiles") +} diff --git a/magefiles/schema.go b/magefiles/schema.go new file mode 100644 index 000000000000..77d98d75d535 --- /dev/null +++ b/magefiles/schema.go @@ -0,0 +1,72 @@ +//go:build mage_schema + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + "os" + + "github.com/aquasecurity/trivy/pkg/iac/rego/schemas" +) + +const ( + schemaPath = "pkg/iac/rego/schemas/cloud.json" +) + +func main() { + if len(os.Args) < 3 { + log.Fatalf("invalid schema command args: %s", os.Args) + } + + switch os.Args[2] { + case "generate": + if err := GenSchema(); err != nil { + log.Fatalf(err.Error()) + } + log.Println("schema generated") + case "verify": + if err := VerifySchema(); err != nil { + log.Fatalf(err.Error()) + } + log.Println("schema valid") + } +} + +// GenSchema generates the Trivy IaC schema +func GenSchema() error { + schema, err := schemas.Build() + if err != nil { + return err + } + data, err := json.MarshalIndent(schema, "", " ") + if err != nil { + return err + } + if err := os.WriteFile(schemaPath, data, 0600); err != nil { + return err + } + return nil +} + +// VerifySchema verifies a generated schema for validity +func VerifySchema() error { + schema, err := schemas.Build() + if err != nil { + return err + } + data, err := json.MarshalIndent(schema, "", " ") + if err != nil { + return err + } + existing, err := os.ReadFile(schemaPath) + if err != nil { + return err + } + if !bytes.Equal(data, existing) { + return fmt.Errorf("schema is out of date:\n\nplease run 'mage schema:generate' and commit the changes\n") + } + return nil +} diff --git a/magefiles/terraformplan.go b/magefiles/terraformplan.go new file mode 100644 index 000000000000..de3d9016b6ed --- /dev/null +++ b/magefiles/terraformplan.go @@ -0,0 +1,140 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "path/filepath" + "strings" + + hversion "github.com/hashicorp/go-version" //nolint:gomodguard // hc-install uses hashicorp/go-version + "github.com/hashicorp/hc-install/product" + "github.com/hashicorp/hc-install/releases" + "github.com/hashicorp/terraform-exec/tfexec" + "golang.org/x/sync/errgroup" + + "github.com/aquasecurity/trivy/internal/testutil" +) + +const ( + terraformVersion = "1.7.3" + terraformParallelLimit = 5 + + tfplanFile = "tfplan" +) + +func fixtureTerraformPlanSnapshots(ctx context.Context) error { + localstackC, addr, err := testutil.SetupLocalStack(ctx, "3.1.0") + if err != nil { + return err + } + defer localstackC.Terminate(ctx) + + envs := []struct { + key string + val string + }{ + {"AWS_DEFAULT_REGION", "us-east-1"}, + {"AWS_ACCESS_KEY_ID", "test"}, + {"AWS_SECRET_ACCESS_KEY", "test"}, + {"AWS_ENDPOINT_URL", addr}, + } + + for _, env := range envs { + if err := os.Setenv(env.key, env.val); err != nil { + return err + } + } + + dirs := []string{ + "pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots", + "pkg/iac/scanners/terraformplan/snapshot/testdata", + } + + var workingDirs []string + + for _, dir := range dirs { + entries, err := os.ReadDir(filepath.FromSlash(dir)) + if err != nil { + return err + } + + for _, entry := range entries { + workingDirs = append(workingDirs, filepath.Join(dir, entry.Name())) + } + } + + installer := &releases.ExactVersion{ + Product: product.Terraform, + Version: hversion.Must(hversion.NewVersion(terraformVersion)), + } + + execPath, err := installer.Install(ctx) + if err != nil { + return fmt.Errorf("failed to install Terraform: %w", err) + } + + g, ctx := errgroup.WithContext(ctx) + g.SetLimit(terraformParallelLimit) + + for _, workingDir := range workingDirs { + workingDir := workingDir + g.Go(func() error { + if err := os.Remove(tfplanFile); err != nil && !errors.Is(err, os.ErrNotExist) { + return err + } + + if err := generatePlan(ctx, execPath, workingDir); err != nil { + return fmt.Errorf("failed to generate Terraform Plan: %w", err) + } + + return nil + }) + } + + return g.Wait() +} + +func generatePlan(ctx context.Context, execPath, workingDir string) error { + if err := cleanup(workingDir); err != nil { + return err + } + defer cleanup(workingDir) + + tf, err := tfexec.NewTerraform(workingDir, execPath) + if err != nil { + return fmt.Errorf("failed to run Terraform: %w", err) + } + + prefix := fmt.Sprintf("tfplan:%s:", filepath.Base(workingDir)) + tf.SetLogger(log.New(os.Stdout, prefix, log.LstdFlags)) + + if err = tf.Init(ctx, tfexec.Upgrade(true)); err != nil { + return fmt.Errorf("failed to run Init cmd: %w", err) + } + + if _, err := tf.Plan(ctx, tfexec.Out(tfplanFile)); err != nil { + return fmt.Errorf("failed to run Plan cmd: %w", err) + } + + return nil +} + +func cleanup(workingDir string) error { + entries, err := os.ReadDir(workingDir) + if err != nil { + return err + } + + for _, entry := range entries { + if entry.Name() == "terraform.tfstate" || strings.HasPrefix(entry.Name(), ".terraform") { + path := filepath.Join(workingDir, entry.Name()) + if err := os.RemoveAll(path); err != nil && !errors.Is(err, os.ErrNotExist) { + return err + } + } + } + return nil +} diff --git a/magefiles/testdata/list_amazoncloudwatch.html b/magefiles/testdata/list_amazoncloudwatch.html new file mode 100644 index 000000000000..10c20f2f8159 --- /dev/null +++ b/magefiles/testdata/list_amazoncloudwatch.html @@ -0,0 +1,958 @@ + + Actions, resources, and condition keys for Amazon CloudWatch - Service Authorization Reference
Actions, resources, and condition keys for Amazon CloudWatch - Service Authorization Reference

Actions, resources, and condition keys for Amazon CloudWatch

Amazon CloudWatch (service prefix: cloudwatch) provides the following service-specific resources, actions, and condition context keys for use in IAM permission policies.

References:

+ + + +
+

Actions defined by Amazon CloudWatch

+

You can specify the following actions in the Action element of an IAM policy statement. Use policies to grant permissions to perform an operation in AWS. When you use an action in a policy, you usually allow or deny access to the API operation or CLI command with the same name. However, in some cases, a single action controls access to more than one operation. Alternatively, some operations require several different actions.

+

The Resource types column of the Actions table indicates whether each action supports resource-level permissions. If there is no value for this column, you must specify all resources ("*") to which the policy applies in the Resource element of your policy statement. If the column includes a resource type, then you can specify an ARN of that type in a statement with that action. If the action has one or more required resources, the caller must have permission to use the action with those resources. Required resources are indicated in the table with an asterisk (*). If you limit resource access with the Resource element in an IAM policy, you must include an ARN or pattern for each required resource type. Some actions support multiple resource types. If the resource type is optional (not indicated as required), then you can choose to use one of the optional resource types.

+

The Condition keys column of the Actions table includes keys that you can specify in a policy statement's Condition element. For more information on the condition keys that are associated with resources for the service, see the Condition keys column of the Resource types table.

+
Note

Resource condition keys are listed in the Resource types table. You can find a link to the resource type that applies to an action in the Resource types (*required) column of the Actions table. The resource type in the Resource types table includes the Condition keys column, which are the resource condition keys that apply to an action in the Actions table.

+ +

For details about the columns in the following table, see Actions table.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ActionsDescriptionAccess levelResource types (*required)Condition keysDependent actions
+ + DeleteAlarms + Grants permission to delete a collection of alarmsWrite +

+ alarm* +

+
+ + DeleteAnomalyDetector + Grants permission to delete the specified anomaly detection model from your accountWrite
+ + DeleteDashboards + Grants permission to delete all CloudWatch dashboards that you specifyWrite +

+ dashboard* +

+
+ + DeleteInsightRules + Grants permission to delete a collection of insight rulesWrite +

+ insight-rule* +

+
+ + DeleteMetricStream + Grants permission to delete the CloudWatch metric stream that you specifyWrite +

+ metric-stream* +

+
+ + DescribeAlarmHistory + Grants permission to retrieve the history for the specified alarmRead +

+ alarm* +

+
+ + DescribeAlarms + Grants permission to describe all alarms, currently owned by the user's accountRead +

+ alarm* +

+
+ + DescribeAlarmsForMetric + Grants permission to describe all alarms configured on the specified metric, currently owned by the user's accountRead
+ + DescribeAnomalyDetectors + Grants permission to list the anomaly detection models that you have created in your accountRead
+ + DescribeInsightRules + Grants permission to describe all insight rules, currently owned by the user's accountRead
+ + DisableAlarmActions + Grants permission to disable actions for a collection of alarmsWrite +

+ alarm* +

+
+ + DisableInsightRules + Grants permission to disable a collection of insight rulesWrite +

+ insight-rule* +

+
+ + EnableAlarmActions + Grants permission to enable actions for a collection of alarmsWrite +

+ alarm* +

+
+ + EnableInsightRules + Grants permission to enable a collection of insight rulesWrite +

+ insight-rule* +

+
+ + GetDashboard + Grants permission to display the details of the CloudWatch dashboard you specifyRead +

+ dashboard* +

+
+ + GetInsightRuleReport + Grants permission to return the top-N report of unique contributors over a time range for a given insight ruleRead +

+ insight-rule* +

+
+ + GetMetricData + Grants permission to retrieve batch amounts of CloudWatch metric data and perform metric math on retrieved dataRead
+ + GetMetricStatistics + Grants permission to retrieve statistics for the specified metricRead
+ + GetMetricStream + Grants permission to return the details of a CloudWatch metric streamRead +

+ metric-stream* +

+
+ + GetMetricWidgetImage + Grants permission to retrieve snapshots of metric widgetsRead
Grants permission to share CloudWatch resources with a monitoring accountWrite
+ + ListDashboards + Grants permission to return a list of all CloudWatch dashboards in your accountList
+ + ListManagedInsightRules + Grants permission to list available managed Insight Rules for a given Resource ARNRead +

+ aws:RequestTag/${TagKey} +

+

+ aws:TagKeys +

+

+ cloudwatch:requestManagedResourceARNs +

+
+ + ListMetricStreams + Grants permission to return a list of all CloudWatch metric streams in your accountList
+ + ListMetrics + Grants permission to retrieve a list of valid metrics stored for the AWS account ownerList
+ + ListTagsForResource + Grants permission to list tags for an Amazon CloudWatch resourceList +

+ alarm +

+
+

+ insight-rule +

+
+

+ SCENARIO: + CloudWatch-Alarm +

+
+

+ alarm* +

+
+

+ SCENARIO: + CloudWatch-InsightRule +

+
+

+ insight-rule* +

+
+ + PutAnomalyDetector + Grants permission to create or update an anomaly detection model for a CloudWatch metricWrite
+ + PutCompositeAlarm + Grants permission to create or update a composite alarmWrite +

+ alarm* +

+
+

+ aws:RequestTag/${TagKey} +

+

+ aws:TagKeys +

+

+ cloudwatch:AlarmActions +

+
+ + PutDashboard + Grants permission to create a CloudWatch dashboard, or update an existing dashboard if it already existsWrite +

+ dashboard* +

+
+ + PutInsightRule + Grants permission to create a new insight rule or replace an existing insight ruleWrite +

+ insight-rule* +

+
+

+ aws:RequestTag/${TagKey} +

+

+ aws:TagKeys +

+

+ cloudwatch:requestInsightRuleLogGroups +

+
+ + PutManagedInsightRules + Grants permission to create managed Insight RulesWrite +

+ aws:RequestTag/${TagKey} +

+

+ aws:TagKeys +

+

+ cloudwatch:requestManagedResourceARNs +

+
+ + PutMetricAlarm + Grants permission to create or update an alarm and associates it with the specified Amazon CloudWatch metricWrite +

+ alarm* +

+
+

+ aws:RequestTag/${TagKey} +

+

+ aws:TagKeys +

+

+ cloudwatch:AlarmActions +

+
+ + PutMetricData + Grants permission to publish metric data points to Amazon CloudWatchWrite +

+ cloudwatch:namespace +

+
+ + PutMetricStream + Grants permission to create a CloudWatch metric stream, or update an existing metric stream if it already existsWrite +

+ metric-stream* +

+
+

+ aws:RequestTag/${TagKey} +

+

+ aws:TagKeys +

+
+ + SetAlarmState + Grants permission to temporarily set the state of an alarm for testing purposesWrite +

+ alarm* +

+
+ + StartMetricStreams + Grants permission to start all CloudWatch metric streams that you specifyWrite +

+ metric-stream* +

+
+ + StopMetricStreams + Grants permission to stop all CloudWatch metric streams that you specifyWrite +

+ metric-stream* +

+
+ + TagResource + Grants permission to add tags to an Amazon CloudWatch resourceTagging +

+ alarm +

+
+

+ insight-rule +

+
+

+ aws:TagKeys +

+

+ aws:RequestTag/${TagKey} +

+
+

+ SCENARIO: + CloudWatch-Alarm +

+
+

+ alarm* +

+
+

+ SCENARIO: + CloudWatch-InsightRule +

+
+

+ insight-rule* +

+
+ + UntagResource + Grants permission to remove a tag from an Amazon CloudWatch resourceTagging +

+ alarm +

+
+

+ insight-rule +

+
+

+ aws:TagKeys +

+
+

+ SCENARIO: + CloudWatch-Alarm +

+
+

+ alarm* +

+
+

+ SCENARIO: + CloudWatch-InsightRule +

+
+

+ insight-rule* +

+
+ +

Resource types defined by Amazon CloudWatch

+ +

The following resource types are defined by this service and can be used in the Resource element of IAM permission policy statements. Each action in the Actions table identifies the resource types that can be specified with that action. A resource type can also define which condition keys you can include in a policy. These keys are displayed in the last column of the Resource types table. For details about the columns in the following table, see Resource types table.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Resource typesARNCondition keys
+ + alarm + + arn:${Partition}:cloudwatch:${Region}:${Account}:alarm:${AlarmName} + +

+ aws:ResourceTag/${TagKey} +

+
+ + dashboard + + arn:${Partition}:cloudwatch::${Account}:dashboard/${DashboardName} +
+ + insight-rule + + arn:${Partition}:cloudwatch:${Region}:${Account}:insight-rule/${InsightRuleName} + +

+ aws:ResourceTag/${TagKey} +

+
+ + metric-stream + + arn:${Partition}:cloudwatch:${Region}:${Account}:metric-stream/${MetricStreamName} + +

+ aws:ResourceTag/${TagKey} +

+
+ +

Condition keys for Amazon CloudWatch

+ +

Amazon CloudWatch defines the following condition keys that can be used in the Condition element of an IAM policy. You can use these keys to further refine the conditions under which the policy statement applies. For details about the columns in the following table, see Condition keys table.

+ +

To view the global condition keys that are available to all services, see Available global condition keys.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Condition keysDescriptionType
+ + aws:RequestTag/${TagKey} + Filters actions based on the allowed set of values for each of the tagsString
+ + aws:ResourceTag/${TagKey} + Filters actions based on tag-value associated with the resourceString
+ + aws:TagKeys + Filters actions based on the presence of mandatory tags in the requestArrayOfString
+ + cloudwatch:AlarmActions + Filters actions based on defined alarm actionsArrayOfString
+ + cloudwatch:namespace + Filters actions based on the presence of optional namespace valuesString
+ + cloudwatch:requestInsightRuleLogGroups + Filters actions based on the Log Groups specified in an Insight RuleArrayOfString
+ + cloudwatch:requestManagedResourceARNs + Filters access by the Resource ARNs specified in a managed Insight RuleArrayOfARN
+
\ No newline at end of file diff --git a/magefiles/testdata/reference_policies_actions-resources-contextkeys.html b/magefiles/testdata/reference_policies_actions-resources-contextkeys.html new file mode 100644 index 000000000000..053322c7f6bc --- /dev/null +++ b/magefiles/testdata/reference_policies_actions-resources-contextkeys.html @@ -0,0 +1,26 @@ + diff --git a/misc/eol/data/debian.csv b/misc/eol/data/debian.csv new file mode 100644 index 000000000000..d87461000db3 --- /dev/null +++ b/misc/eol/data/debian.csv @@ -0,0 +1,19 @@ +1.1,Buzz,buzz,1993-08-16,1996-06-17,1997-06-05 +1.2,Rex,rex,1996-06-17,1996-12-12,1998-06-05 +1.3,Bo,bo,1996-12-12,1997-06-05,1999-03-09 +2.0,Hamm,hamm,1997-06-05,1998-07-24,2000-03-09 +2.1,Slink,slink,1998-07-24,1999-03-09,2000-10-30 +2.2,Potato,potato,1999-03-09,2000-08-15,2003-07-30 +3.0,Woody,woody,2000-08-15,2002-07-19,2006-06-30 +3.1,Sarge,sarge,2002-07-19,2005-06-06,2008-03-30 +4.0,Etch,etch,2005-06-06,2007-04-08,2010-02-15 +5.0,Lenny,lenny,2007-04-08,2009-02-14,2012-02-06 +6.0,Squeeze,squeeze,2009-02-14,2011-02-06,2014-05-31 +7,Wheezy,wheezy,2011-02-06,2013-05-04,2016-04-26 +8,Jessie,jessie,2013-05-04,2015-04-25,2018-06-06 +9,Stretch,stretch,2015-04-25,2017-06-17 +10,Buster,buster,2017-06-17 +11,Bullseye,bullseye,2019-08-01 +12,Bookworm,bookworm,2021-08-01 +,Sid,sid,1993-08-16 +,Experimental,experimental,1993-08-16 diff --git a/misc/eol/data/ubuntu.csv b/misc/eol/data/ubuntu.csv new file mode 100644 index 000000000000..bd9e81a95bbc --- /dev/null +++ b/misc/eol/data/ubuntu.csv @@ -0,0 +1,33 @@ +4.10,Warty Warthog,warty,2004-03-05,2004-10-20,2006-04-30 +5.04,Hoary Hedgehog,hoary,2004-10-20,2005-04-08,2006-10-31 +5.10,Breezy Badger,breezy,2005-04-08,2005-10-12,2007-04-13 +6.06 LTS,Dapper Drake,dapper,2005-10-12,2006-06-01,2009-07-14,2011-06-01 +6.10,Edgy Eft,edgy,2006-06-01,2006-10-26,2008-04-25 +7.04,Feisty Fawn,feisty,2006-10-26,2007-04-19,2008-10-19 +7.10,Gutsy Gibbon,gutsy,2007-04-19,2007-10-18,2009-04-18 +8.04 LTS,Hardy Heron,hardy,2007-10-18,2008-04-24,2011-05-12,2013-05-09 +8.10,Intrepid Ibex,intrepid,2008-04-24,2008-10-30,2010-04-30 +9.04,Jaunty Jackalope,jaunty,2008-10-30,2009-04-23,2010-10-23 +9.10,Karmic Koala,karmic,2009-04-23,2009-10-29,2011-04-29 +10.04 LTS,Lucid Lynx,lucid,2009-10-29,2010-04-29,2013-05-09,2015-04-29 +10.10,Maverick Meerkat,maverick,2010-04-29,2010-10-10,2012-04-10 +11.04,Natty Narwhal,natty,2010-10-10,2011-04-28,2012-10-28 +11.10,Oneiric Ocelot,oneiric,2011-04-28,2011-10-13,2013-05-09 +12.04 LTS,Precise Pangolin,precise,2011-10-13,2012-04-26,2017-04-26,2017-04-26,2019-04-26 +12.10,Quantal Quetzal,quantal,2012-04-26,2012-10-18,2014-05-16 +13.04,Raring Ringtail,raring,2012-10-18,2013-04-25,2014-01-27 +13.10,Saucy Salamander,saucy,2013-04-25,2013-10-17,2014-07-17 +14.04 LTS,Trusty Tahr,trusty,2013-10-17,2014-04-17,2019-04-25,2019-04-25,2022-04-25 +14.10,Utopic Unicorn,utopic,2014-04-17,2014-10-23,2015-07-23 +15.04,Vivid Vervet,vivid,2014-10-23,2015-04-23,2016-01-23 +15.10,Wily Werewolf,wily,2015-04-23,2015-10-22,2016-07-22 +16.04 LTS,Xenial Xerus,xenial,2015-10-22,2016-04-21,2021-04-21,2021-04-21,2024-04-21 +16.10,Yakkety Yak,yakkety,2016-04-21,2016-10-13,2017-07-20 +17.04,Zesty Zapus,zesty,2016-10-13,2017-04-13,2018-01-13 +17.10,Artful Aardvark,artful,2017-04-13,2017-10-19,2018-07-19 +18.04 LTS,Bionic Beaver,bionic,2017-10-19,2018-04-26,2023-04-26,2023-04-26,2028-04-26 +18.10,Cosmic Cuttlefish,cosmic,2018-04-26,2018-10-18,2019-07-18 +19.04,Disco Dingo,disco,2018-10-18,2019-04-18,2020-01-18 +19.10,Eoan Ermine,eoan,2019-04-18,2019-10-17,2020-07-17 +20.04 LTS,Focal Fossa,focal,2020-04-23,2025-04-23,2030-04-23 +22.04 LTS,Jammy Jellyfish,jammy,2022-04-21,2027-04-21,2032-04-21 diff --git a/misc/eol/main.go b/misc/eol/main.go new file mode 100644 index 000000000000..9cb4719b0acb --- /dev/null +++ b/misc/eol/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + "time" +) + +// This script displays EOL dates +func main() { + fmt.Println("Debian") + debianEOL() + + fmt.Println("\nUbuntu") + ubuntuEOL() +} + +func debianEOL() { + f, err := os.Open("data/debian.csv") + if err != nil { + panic(err) + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Split(line, ",") + + if len(fields) < 6 && fields[0] != "" { + fmt.Printf("\"%s\": time.Date(3000, 1, 1, 23, 59, 59, 0, time.UTC),\n", fields[0]) + } else if len(fields) == 6 { + eol, _ := time.Parse("2006-1-2", fields[5]) // nolint: errcheck + fmt.Printf("\"%s\": time.Date(%d, %d, %d, 23, 59, 59, 0, time.UTC),\n", fields[0], eol.Year(), eol.Month(), eol.Day()) + } + } +} + +func ubuntuEOL() { + f, err := os.Open("data/ubuntu.csv") + if err != nil { + panic(err) + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Split(line, ",") + eol, _ := time.Parse("2006-1-2", fields[len(fields)-1]) // nolint: errcheck + fmt.Printf("\"%s\": time.Date(%d, %d, %d, 23, 59, 59, 0, time.UTC),\n", strings.Fields(fields[0])[0], eol.Year(), eol.Month(), eol.Day()) + } +} diff --git a/misc/lint/rules.go b/misc/lint/rules.go new file mode 100644 index 000000000000..e477175a244f --- /dev/null +++ b/misc/lint/rules.go @@ -0,0 +1,22 @@ +//go:build ruleguard + +package gorules + +import "github.com/quasilyte/go-ruleguard/dsl" + +// cf. https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices +func declareEmptySlices(m dsl.Matcher) { + m.Match( + `$name := []$t{}`, + `$name := make([]$t, 0)`, + ). + Suggest(`var $name []$t`). + Report(`replace '$$' with 'var $name []$t'`) +} + +// cf. https://github.com/uber-go/guide/blob/master/style.md#initializing-maps +func initializeMaps(m dsl.Matcher) { + m.Match(`map[$key]$value{}`). + Suggest(`make(map[$key]$value)`). + Report(`replace '$$' with 'make(map[$key]$value)`) +} diff --git a/misc/triage/labels.yaml b/misc/triage/labels.yaml new file mode 100644 index 000000000000..e2babc4cab12 --- /dev/null +++ b/misc/triage/labels.yaml @@ -0,0 +1,136 @@ +--- +repo: aquasecurity/trivy +labels: + +# kind +- name: kind/breaking + color: e11d21 + description: Categorizes issue or PR as related to breaking compatibility. + from: kind/api-change +- name: kind/bug + color: e11d21 + from: bug + description: Categorizes issue or PR as related to a bug. +- name: kind/security-advisory + color: e11d21 + description: Categorizes issue or PR as related to security advisories. +- name: kind/cleanup + color: c7def8 + description: Categorizes issue or PR as related to cleaning up code, process, or technical debt. +- name: kind/deprecation + color: e11d21 + from: enhancement + description: Categorizes issue or PR as related to a feature/enhancement marked for deprecation. +- name: kind/documentation + color: c7def8 + description: Categorizes issue or PR as related to documentation. +- name: kind/feature + color: c7def8 + description: Categorizes issue or PR as related to a new feature. +- name: kind/testing + color: c7def8 + description: Categorizes issue or PR as related to a unit/integration test. +- name: kind/security + color: f4dddc + description: Categorizes issue or PR as related to Trivy's own security or internal vulnerabilities. +- name: kind/integration + color: f4dddc + description: Categorizes issue or PR as related to a third party integration of Trivy. + +# lifecycle for the stale bot +- name: lifecycle/frozen + color: d3e2f0 + description: Indicates that an issue or PR should not be auto-closed due to staleness. +- name: lifecycle/stale + color: "795548" + description: Denotes an issue or PR has remained open with no activity and will be auto-closed. +- name: lifecycle/active + color: "1d76db" + description: Indicates that an issue or PR is actively being worked on by a contributor. + +# priority +- name: priority/critical-urgent + color: e11d21 + description: Highest priority. Must be actively worked on as someone's top priority right now. +- name: priority/important-soon + color: eb6420 + description: Must be staffed and worked on either currently, or very soon, ideally in time for the next release. +- name: priority/important-longterm + color: eb6420 + description: Important over the long term, but may not be staffed and/or may need multiple releases to complete. +- name: priority/backlog + color: fbca04 + description: Higher priority than priority/awaiting-more-evidence. +- name: priority/awaiting-more-evidence + color: fef2c0 + description: Lowest priority. Possibly useful, but not yet enough support to actually get it done. + +# triage +- name: triage/discuss + color: faff84 + description: Items for discussion +- name: triage/duplicate + color: d455d0 + from: duplicate + description: Indicates an issue is a duplicate of other open issue. +- name: triage/needs-information + color: d455d0 + description: Indicates an issue needs more information in order to work on it. +- name: triage/not-reproducible + color: d455d0 + description: Indicates an issue can not be reproduced as described. +- name: triage/obsolete + color: faff84 + description: Bugs that no longer occur in the latest stable release +- name: triage/support + color: d455d0 + from: question + description: Indicates an issue that is a support question. +- name: triage/unresolved + color: d455d0 + description: Indicates an issue that can not or will not be resolved. + +# scanning +- name: scan/vulnerability + color: d9ead3 + description: Issues relating to vulnerability scanning +- name: scan/misconfiguration + color: d9ead3 + description: Issues relating to misconfiguration scanning +- name: scan/secret + color: d9ead3 + description: Issues relating to secret scanning +- name: scan/sbom + color: d9ead3 + description: Issues relating to SBOM +- name: scan/license + color: d9ead3 + description: Issues relating to license scanning + +# target +- name: target/container-image + color: 0ebdb0 + description: Issues relating to container image scanning +- name: target/filesystem + color: 0ebdb0 + description: Issues relating to filesystem scanning +- name: target/repository + color: 0ebdb0 + description: Issues relating to VCS repository scanning +- name: target/kubernetes + color: 0ebdb0 + description: Issues relating to kubernetes cluster scanning +- name: target/cloud + color: 0ebdb0 + description: Issues relating to cloud account scanning +- name: target/vm + color: 0ebdb0 + description: Issues relating to virtual machine scanning + +# others +- name: good first issue + color: 7057ff + description: Denotes an issue ready for a new contributor, according to the "help wanted" guidelines. +- name: help wanted + color: 006b75 + description: Denotes an issue that needs help from a contributor. Must meet "help wanted" guidelines. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000000..f85fd7a8f209 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,240 @@ +site_name: Trivy +site_url: https://aquasecurity.github.io/trivy/ +site_description: A Simple and Comprehensive Vulnerability Scanner for Containers and other Artifacts, Suitable for CI +docs_dir: docs/ +repo_name: GitHub +repo_url: https://github.com/aquasecurity/trivy +edit_uri: "blob/main/docs/" + +nav: + - Getting Started: + - Overview: index.md + - Installation: getting-started/installation.md + - Signature Verification: getting-started/signature-verification.md + - FAQ: getting-started/faq.md + - Tutorials: + - Overview: tutorials/overview.md + - CI/CD: + - Overview: tutorials/integrations/index.md + - GitHub Actions: tutorials/integrations/github-actions.md + - CircleCI: tutorials/integrations/circleci.md + - Travis CI: tutorials/integrations/travis-ci.md + - GitLab CI: tutorials/integrations/gitlab-ci.md + - Bitbucket Pipelines: tutorials/integrations/bitbucket.md + - AWS CodePipeline: tutorials/integrations/aws-codepipeline.md + - AWS Security Hub: tutorials/integrations/aws-security-hub.md + - Azure: tutorials/integrations/azure-devops.md + - Kubernetes: + - Cluster Scanning: tutorials/kubernetes/cluster-scanning.md + - Kyverno: tutorials/kubernetes/kyverno.md + - GitOps: tutorials/kubernetes/gitops.md + - Misconfiguration: + - Terraform scanning: tutorials/misconfiguration/terraform.md + - Custom Checks with Rego: tutorials/misconfiguration/custom-checks.md + - Signing: + - Vulnerability Scan Record Attestation: tutorials/signing/vuln-attestation.md + - Shell: + - Completion: tutorials/shell/shell-completion.md + - Additional Resources: + - Additional Resources: tutorials/additional-resources/references.md + - Community References: tutorials/additional-resources/community.md + - CKS Reference: tutorials/additional-resources/cks.md + - Docs: + - Overview: docs/index.md + - Target: + - Container Image: docs/target/container_image.md + - Filesystem: docs/target/filesystem.md + - Rootfs: docs/target/rootfs.md + - Code Repository: docs/target/repository.md + - Virtual Machine Image: docs/target/vm.md + - Kubernetes: docs/target/kubernetes.md + - AWS: docs/target/aws.md + - SBOM: docs/target/sbom.md + - Scanner: + - Vulnerability: docs/scanner/vulnerability.md + - Misconfiguration: + - Overview: docs/scanner/misconfiguration/index.md + - Policy: + - Built-in Policies: docs/scanner/misconfiguration/policy/builtin.md + - Exceptions: docs/scanner/misconfiguration/policy/exceptions.md + - Custom Policies: + - Overview: docs/scanner/misconfiguration/custom/index.md + - Data: docs/scanner/misconfiguration/custom/data.md + - Combine: docs/scanner/misconfiguration/custom/combine.md + - Selectors: docs/scanner/misconfiguration/custom/selectors.md + - Schemas: docs/scanner/misconfiguration/custom/schema.md + - Testing: docs/scanner/misconfiguration/custom/testing.md + - Debugging Policies: docs/scanner/misconfiguration/custom/debug.md + - Secret: docs/scanner/secret.md + - License: docs/scanner/license.md + - Coverage: + - Overview: docs/coverage/index.md + - OS: + - Overview: docs/coverage/os/index.md + - AlmaLinux: docs/coverage/os/alma.md + - Alpine Linux: docs/coverage/os/alpine.md + - Amazon Linux: docs/coverage/os/amazon.md + - CBL-Mariner: docs/coverage/os/cbl-mariner.md + - CentOS: docs/coverage/os/centos.md + - Chainguard: docs/coverage/os/chainguard.md + - Debian: docs/coverage/os/debian.md + - Oracle Linux: docs/coverage/os/oracle.md + - Photon OS: docs/coverage/os/photon.md + - Red Hat: docs/coverage/os/rhel.md + - Rocky Linux: docs/coverage/os/rocky.md + - SUSE: docs/coverage/os/suse.md + - Ubuntu: docs/coverage/os/ubuntu.md + - Wolfi: docs/coverage/os/wolfi.md + - Google Distroless (Images): docs/coverage/os/google-distroless.md + - Bitnami (Images): docs/coverage/os/bitnami.md + - Language: + - Overview: docs/coverage/language/index.md + - C/C++: docs/coverage/language/c.md + - Dart: docs/coverage/language/dart.md + - .NET: docs/coverage/language/dotnet.md + - Elixir: docs/coverage/language/elixir.md + - Go: docs/coverage/language/golang.md + - Java: docs/coverage/language/java.md + - Node.js: docs/coverage/language/nodejs.md + - PHP: docs/coverage/language/php.md + - Python: docs/coverage/language/python.md + - Ruby: docs/coverage/language/ruby.md + - Rust: docs/coverage/language/rust.md + - Swift: docs/coverage/language/swift.md + - IaC: + - Overview: docs/coverage/iac/index.md + - Azure ARM Template: docs/coverage/iac/azure-arm.md + - CloudFormation: docs/coverage/iac/cloudformation.md + - Docker: docs/coverage/iac/docker.md + - Helm: docs/coverage/iac/helm.md + - Kubernetes: docs/coverage/iac/kubernetes.md + - Terraform: docs/coverage/iac/terraform.md + - Kubernetes: docs/coverage/kubernetes.md + - Configuration: + - Overview: docs/configuration/index.md + - Filtering: docs/configuration/filtering.md + - Skipping Files: docs/configuration/skipping.md + - Reporting: docs/configuration/reporting.md + - Cache: docs/configuration/cache.md + - DB: docs/configuration/db.md + - Others: docs/configuration/others.md + - Supply Chain: + - SBOM: docs/supply-chain/sbom.md + - Attestation: + - SBOM: docs/supply-chain/attestation/sbom.md + - Cosign Vulnerability Scan Record: docs/supply-chain/attestation/vuln.md + - SBOM Attestation in Rekor: docs/supply-chain/attestation/rekor.md + - VEX: docs/supply-chain/vex.md + - Compliance: + - Reports: docs/compliance/compliance.md + - Advanced: + - Modules: docs/advanced/modules.md + - Plugins: docs/advanced/plugins.md + - Air-Gapped Environment: docs/advanced/air-gap.md + - Container Image: + - Embed in Dockerfile: docs/advanced/container/embed-in-dockerfile.md + - Unpacked container image filesystem: docs/advanced/container/unpacked-filesystem.md + - Private Docker Registries: + - Overview: docs/advanced/private-registries/index.md + - Docker Hub: docs/advanced/private-registries/docker-hub.md + - AWS ECR (Elastic Container Registry): docs/advanced/private-registries/ecr.md + - GCR (Google Container Registry): docs/advanced/private-registries/gcr.md + - ACR (Azure Container Registry): docs/advanced/private-registries/acr.md + - Self-Hosted: docs/advanced/private-registries/self.md + - References: + - Configuration: + - CLI: + - Overview: docs/references/configuration/cli/trivy.md + - AWS: docs/references/configuration/cli/trivy_aws.md + - Config: docs/references/configuration/cli/trivy_config.md + - Convert: docs/references/configuration/cli/trivy_convert.md + - Filesystem: docs/references/configuration/cli/trivy_filesystem.md + - Image: docs/references/configuration/cli/trivy_image.md + - Kubernetes: docs/references/configuration/cli/trivy_kubernetes.md + - Module: docs/references/configuration/cli/trivy_module.md + - Module Install: docs/references/configuration/cli/trivy_module_install.md + - Module Uninstall: docs/references/configuration/cli/trivy_module_uninstall.md + - Plugin: docs/references/configuration/cli/trivy_plugin.md + - Plugin Info: docs/references/configuration/cli/trivy_plugin_info.md + - Plugin Install: docs/references/configuration/cli/trivy_plugin_install.md + - Plugin List: docs/references/configuration/cli/trivy_plugin_list.md + - Plugin Run: docs/references/configuration/cli/trivy_plugin_run.md + - Plugin Uninstall: docs/references/configuration/cli/trivy_plugin_uninstall.md + - Plugin Update: docs/references/configuration/cli/trivy_plugin_update.md + - Repository: docs/references/configuration/cli/trivy_repository.md + - Rootfs: docs/references/configuration/cli/trivy_rootfs.md + - SBOM: docs/references/configuration/cli/trivy_sbom.md + - Server: docs/references/configuration/cli/trivy_server.md + - Version: docs/references/configuration/cli/trivy_version.md + - VM: docs/references/configuration/cli/trivy_vm.md + - Config file: docs/references/configuration/config-file.md + - Modes: + - Standalone: docs/references/modes/standalone.md + - Client/Server: docs/references/modes/client-server.md + - Troubleshooting: docs/references/troubleshooting.md + - Ecosystem: + - Overview: ecosystem/index.md + - CI/CD: ecosystem/cicd.md + - IDE and Dev tools: ecosystem/ide.md + - Production and Clouds: ecosystem/prod.md + - Reporting: ecosystem/reporting.md + - Contributing: + - Principles: community/principles.md + - How to contribute: + - Issues: community/contribute/issue.md + - Discussions: community/contribute/discussion.md + - Pull Requests: community/contribute/pr.md + - Maintainer: + - Help Wanted: community/maintainer/help-wanted.md + - Triage: community/maintainer/triage.md +theme: + name: material + custom_dir: docs/overrides + language: "en" + logo: imgs/logo-white.svg + features: + - navigation.tabs + - navigation.tabs.sticky + - navigation.sections + - navigation.footer + - content.action.edit + - content.tabs.link + - content.code.annotate + - content.code.copy + +markdown_extensions: + - pymdownx.highlight + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - admonition + - footnotes + - attr_list + - pymdownx.tabbed: + alternate_style: true + - def_list + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + +extra: + generator: false + version: + method: mike + provider: mike + default: latest + social: + - icon: fontawesome/brands/twitter + link: https://twitter.com/AquaTrivy + - icon: fontawesome/brands/github + link: https://github.com/aquasecurity/trivy + - icon: fontawesome/brands/slack + link: https://github.com/aquasecurity/trivy + +plugins: + - search + - macros + diff --git a/pkg/attestation/attestation.go b/pkg/attestation/attestation.go new file mode 100644 index 000000000000..8f4cb8ca54aa --- /dev/null +++ b/pkg/attestation/attestation.go @@ -0,0 +1,44 @@ +package attestation + +import ( + "bytes" + "encoding/base64" + "encoding/json" + + "github.com/in-toto/in-toto-golang/in_toto" + "github.com/secure-systems-lab/go-securesystemslib/dsse" + "golang.org/x/xerrors" +) + +// CosignPredicate specifies the format of the Custom Predicate. +// Cosign uses this structure when creating an SBOM attestation. +// cf. https://github.com/sigstore/cosign/blob/e0547cff64f98585a837a524ff77ff6b47ff5609/pkg/cosign/attestation/attestation.go#L39-L43 +type CosignPredicate struct { + Data interface{} +} + +// Statement holds in-toto statement headers and the predicate. +type Statement in_toto.Statement + +func (s *Statement) UnmarshalJSON(b []byte) error { + var envelope dsse.Envelope + err := json.NewDecoder(bytes.NewReader(b)).Decode(&envelope) + if err != nil { + return xerrors.Errorf("failed to decode as a dsse envelope: %w", err) + } + if envelope.PayloadType != in_toto.PayloadType { + return xerrors.Errorf("invalid attestation payload type: %s", envelope.PayloadType) + } + + decoded, err := base64.StdEncoding.DecodeString(envelope.Payload) + if err != nil { + return xerrors.Errorf("failed to decode attestation payload: %w", err) + } + + statement := (*in_toto.Statement)(s) + if err = json.NewDecoder(bytes.NewReader(decoded)).Decode(statement); err != nil { + return xerrors.Errorf("failed to decode attestation payload as in-toto statement: %w", err) + } + + return nil +} diff --git a/pkg/attestation/attestation_test.go b/pkg/attestation/attestation_test.go new file mode 100644 index 000000000000..763878f4ae41 --- /dev/null +++ b/pkg/attestation/attestation_test.go @@ -0,0 +1,55 @@ +package attestation_test + +import ( + "encoding/json" + "os" + "testing" + + "github.com/in-toto/in-toto-golang/in_toto" + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/attestation" +) + +func TestStatement_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + inputFile string + want attestation.Statement + }{ + { + name: "happy path", + inputFile: "testdata/attestation.json", + want: attestation.Statement{ + StatementHeader: in_toto.StatementHeader{ + Type: "https://in-toto.io/Statement/v0.1", + PredicateType: "cosign.sigstore.dev/attestation/v1", + Subject: []in_toto.Subject{ + { + Name: "ghcr.io/aquasecurity/trivy-test-images", + Digest: slsa.DigestSet{ + "sha256": "72c42ed48c3a2db31b7dafe17d275b634664a708d901ec9fd57b1529280f01fb", + }, + }, + }, + }, + Predicate: &attestation.CosignPredicate{ + Data: "foo\n", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + got := attestation.Statement{Predicate: &attestation.CosignPredicate{}} + err = json.NewDecoder(f).Decode(&got) + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/attestation/sbom/rekor.go b/pkg/attestation/sbom/rekor.go new file mode 100644 index 000000000000..7336def6b511 --- /dev/null +++ b/pkg/attestation/sbom/rekor.go @@ -0,0 +1,94 @@ +package sbom + +import ( + "bytes" + "context" + "encoding/json" + "errors" + + "github.com/in-toto/in-toto-golang/in_toto" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/attestation" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/rekor" +) + +var ErrNoSBOMAttestation = xerrors.New("no SBOM attestation found") + +type Rekor struct { + client *rekor.Client +} + +func NewRekor(url string) (Rekor, error) { + c, err := rekor.NewClient(url) + if err != nil { + return Rekor{}, xerrors.Errorf("rekor client error: %w", err) + } + return Rekor{ + client: c, + }, nil +} + +func (r *Rekor) RetrieveSBOM(ctx context.Context, digest string) ([]byte, error) { + entryIDs, err := r.client.Search(ctx, digest) + if err != nil { + return nil, xerrors.Errorf("failed to search rekor records: %w", err) + } else if len(entryIDs) == 0 { + return nil, ErrNoSBOMAttestation + } + + log.Logger.Debugf("Found matching Rekor entries: %s", entryIDs) + + for _, ids := range lo.Chunk[rekor.EntryID](entryIDs, rekor.MaxGetEntriesLimit) { + entries, err := r.client.GetEntries(ctx, ids) + if err != nil { + return nil, xerrors.Errorf("failed to get entries: %w", err) + } + + for _, entry := range entries { + ref, err := r.inspectRecord(entry) + if errors.Is(err, ErrNoSBOMAttestation) { + continue + } else if err != nil { + return nil, xerrors.Errorf("rekor record inspection error: %w", err) + } + return ref, nil + } + } + return nil, ErrNoSBOMAttestation +} + +func (r *Rekor) inspectRecord(entry rekor.Entry) ([]byte, error) { + // TODO: Trivy SBOM should take precedence + raw, err := r.parseStatement(entry) + if err != nil { + return nil, err + } + return raw, nil +} + +func (r *Rekor) parseStatement(entry rekor.Entry) (json.RawMessage, error) { + // Skip base64-encoded attestation + if bytes.HasPrefix(entry.Statement, []byte(`eyJ`)) { + return nil, ErrNoSBOMAttestation + } + + // Parse statement of in-toto attestation + var raw json.RawMessage + statement := &in_toto.Statement{ + Predicate: &attestation.CosignPredicate{ + Data: &raw, // Extract CycloneDX or SPDX + }, + } + if err := json.Unmarshal(entry.Statement, &statement); err != nil { + return nil, xerrors.Errorf("attestation parse error: %w", err) + } + + // TODO: add support for SPDX + if statement.PredicateType != in_toto.PredicateCycloneDX { + return nil, xerrors.Errorf("unsupported predicate type %s: %w", statement.PredicateType, ErrNoSBOMAttestation) + } + return raw, nil +} diff --git a/pkg/attestation/sbom/rekor_test.go b/pkg/attestation/sbom/rekor_test.go new file mode 100644 index 000000000000..db97d781568c --- /dev/null +++ b/pkg/attestation/sbom/rekor_test.go @@ -0,0 +1,53 @@ +package sbom_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/attestation/sbom" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/rekortest" +) + +func TestRekor_RetrieveSBOM(t *testing.T) { + tests := []struct { + name string + digest string + want string + wantErr string + }{ + { + name: "happy path", + digest: "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", + want: `{"bomFormat":"CycloneDX","specVersion":"1.5","version":2}`, + }, + { + name: "404", + digest: "sha256:unknown", + wantErr: "failed to search", + }, + } + + require.NoError(t, log.InitLogger(false, true)) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := rekortest.NewServer(t) + defer ts.Close() + + // Set the testing URL + rc, err := sbom.NewRekor(ts.URL()) + require.NoError(t, err) + + got, err := rc.RetrieveSBOM(context.Background(), tt.digest) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err, tt.name) + assert.Equal(t, tt.want, string(got)) + }) + } +} diff --git a/pkg/attestation/testdata/attestation.json b/pkg/attestation/testdata/attestation.json new file mode 100644 index 000000000000..2e9dbf326a24 --- /dev/null +++ b/pkg/attestation/testdata/attestation.json @@ -0,0 +1 @@ +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vYXF1YXNlY3VyaXR5L3RyaXZ5LXRlc3QtaW1hZ2VzIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjcyYzQyZWQ0OGMzYTJkYjMxYjdkYWZlMTdkMjc1YjYzNDY2NGE3MDhkOTAxZWM5ZmQ1N2IxNTI5MjgwZjAxZmIifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImZvb1xuIiwiVGltZXN0YW1wIjoiMjAyMi0wOC0wM1QxMzowODoyN1oifX0=","signatures":[{"keyid":"","sig":"MEUCIQClJhJ2mS78MWy4L32wxd+8gPXYwpvyn0nmuY9r5t8iiAIgHKKoIJbKAKQ8i/bgN76ocuGhwUMdbgqpgKF0yFfPfGI="}]} diff --git a/pkg/cache/nop.go b/pkg/cache/nop.go new file mode 100644 index 000000000000..6b52e91108c6 --- /dev/null +++ b/pkg/cache/nop.go @@ -0,0 +1,16 @@ +package cache + +import "github.com/aquasecurity/trivy/pkg/fanal/cache" + +func NopCache(ac cache.ArtifactCache) cache.Cache { + return nopCache{ArtifactCache: ac} +} + +type nopCache struct { + cache.ArtifactCache + cache.LocalArtifactCache +} + +func (nopCache) Close() error { + return nil +} diff --git a/pkg/cache/remote.go b/pkg/cache/remote.go new file mode 100644 index 000000000000..5900bf7a3b91 --- /dev/null +++ b/pkg/cache/remote.go @@ -0,0 +1,90 @@ +package cache + +import ( + "context" + "crypto/tls" + "net/http" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/rpc" + "github.com/aquasecurity/trivy/pkg/rpc/client" + rpcCache "github.com/aquasecurity/trivy/rpc/cache" +) + +// RemoteCache implements remote cache +type RemoteCache struct { + ctx context.Context // for custom header + client rpcCache.Cache +} + +// NewRemoteCache is the factory method for RemoteCache +func NewRemoteCache(url string, customHeaders http.Header, insecure bool) cache.ArtifactCache { + ctx := client.WithCustomHeaders(context.Background(), customHeaders) + + httpClient := &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: insecure, + }, + }, + } + c := rpcCache.NewCacheProtobufClient(url, httpClient) + return &RemoteCache{ctx: ctx, client: c} +} + +// PutArtifact sends artifact to remote client +func (c RemoteCache) PutArtifact(imageID string, artifactInfo types.ArtifactInfo) error { + err := rpc.Retry(func() error { + var err error + _, err = c.client.PutArtifact(c.ctx, rpc.ConvertToRPCArtifactInfo(imageID, artifactInfo)) + return err + }) + if err != nil { + return xerrors.Errorf("unable to store cache on the server: %w", err) + } + return nil +} + +// PutBlob sends blobInfo to remote client +func (c RemoteCache) PutBlob(diffID string, blobInfo types.BlobInfo) error { + err := rpc.Retry(func() error { + var err error + _, err = c.client.PutBlob(c.ctx, rpc.ConvertToRPCPutBlobRequest(diffID, blobInfo)) + return err + }) + if err != nil { + return xerrors.Errorf("unable to store cache on the server: %w", err) + } + return nil +} + +// MissingBlobs fetches missing blobs from RemoteCache +func (c RemoteCache) MissingBlobs(imageID string, layerIDs []string) (bool, []string, error) { + var layers *rpcCache.MissingBlobsResponse + err := rpc.Retry(func() error { + var err error + layers, err = c.client.MissingBlobs(c.ctx, rpc.ConvertToMissingBlobsRequest(imageID, layerIDs)) + return err + }) + if err != nil { + return false, nil, xerrors.Errorf("unable to fetch missing layers: %w", err) + } + return layers.MissingArtifact, layers.MissingBlobIds, nil +} + +// DeleteBlobs removes blobs by IDs from RemoteCache +func (c RemoteCache) DeleteBlobs(blobIDs []string) error { + err := rpc.Retry(func() error { + var err error + _, err = c.client.DeleteBlobs(c.ctx, rpc.ConvertToDeleteBlobsRequest(blobIDs)) + return err + }) + if err != nil { + return xerrors.Errorf("unable to delete blobs on the server: %w", err) + } + return nil +} diff --git a/pkg/cache/remote_test.go b/pkg/cache/remote_test.go new file mode 100644 index 000000000000..e396b71ae4ef --- /dev/null +++ b/pkg/cache/remote_test.go @@ -0,0 +1,345 @@ +package cache_test + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + google_protobuf "github.com/golang/protobuf/ptypes/empty" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/twitchtv/twirp" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/cache" + fcache "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" + rpcCache "github.com/aquasecurity/trivy/rpc/cache" + rpcScanner "github.com/aquasecurity/trivy/rpc/scanner" +) + +type mockCacheServer struct { + cache fcache.Cache +} + +func (s *mockCacheServer) PutArtifact(_ context.Context, in *rpcCache.PutArtifactRequest) (*google_protobuf.Empty, error) { + if strings.Contains(in.ArtifactId, "invalid") { + return &google_protobuf.Empty{}, xerrors.New("invalid image ID") + } + return &google_protobuf.Empty{}, nil +} + +func (s *mockCacheServer) PutBlob(_ context.Context, in *rpcCache.PutBlobRequest) (*google_protobuf.Empty, error) { + if strings.Contains(in.DiffId, "invalid") { + return &google_protobuf.Empty{}, xerrors.New("invalid layer ID") + } + return &google_protobuf.Empty{}, nil +} + +func (s *mockCacheServer) MissingBlobs(_ context.Context, in *rpcCache.MissingBlobsRequest) (*rpcCache.MissingBlobsResponse, error) { + var layerIDs []string + for _, layerID := range in.BlobIds[:len(in.BlobIds)-1] { + if strings.Contains(layerID, "invalid") { + return nil, xerrors.New("invalid layer ID") + } + layerIDs = append(layerIDs, layerID) + } + return &rpcCache.MissingBlobsResponse{MissingArtifact: true, MissingBlobIds: layerIDs}, nil +} + +func (s *mockCacheServer) DeleteBlobs(_ context.Context, in *rpcCache.DeleteBlobsRequest) (*google_protobuf.Empty, error) { + for _, blobId := range in.GetBlobIds() { + if strings.Contains(blobId, "invalid") { + return &google_protobuf.Empty{}, xerrors.New("invalid layer ID") + } + } + return &google_protobuf.Empty{}, nil +} + +func withToken(base http.Handler, token, tokenHeader string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if token != "" && token != r.Header.Get(tokenHeader) { + rpcScanner.WriteError(w, twirp.NewError(twirp.Unauthenticated, "invalid token")) + return + } + base.ServeHTTP(w, r) + }) +} + +func TestRemoteCache_PutArtifact(t *testing.T) { + mux := http.NewServeMux() + layerHandler := rpcCache.NewCacheServer(new(mockCacheServer), nil) + mux.Handle(rpcCache.CachePathPrefix, withToken(layerHandler, "valid-token", "Trivy-Token")) + ts := httptest.NewServer(mux) + + type args struct { + imageID string + imageInfo types.ArtifactInfo + customHeaders http.Header + } + tests := []struct { + name string + args args + wantErr string + }{ + { + name: "happy path", + args: args{ + imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + imageInfo: types.ArtifactInfo{ + SchemaVersion: 1, + Architecture: "amd64", + Created: time.Time{}, + DockerVersion: "18.06", + OS: "linux", + HistoryPackages: []types.Package{ + { + Name: "musl", + Version: "1.2.3", + }, + }, + }, + customHeaders: http.Header{ + "Trivy-Token": []string{"valid-token"}, + }, + }, + }, + { + name: "sad path", + args: args{ + imageID: "sha256:invalid", + imageInfo: types.ArtifactInfo{ + SchemaVersion: 1, + Architecture: "amd64", + Created: time.Time{}, + DockerVersion: "18.06", + OS: "linux", + HistoryPackages: []types.Package{ + { + Name: "musl", + Version: "1.2.3", + }, + }, + }, + customHeaders: http.Header{ + "Trivy-Token": []string{"valid-token"}, + }, + }, + wantErr: "twirp error internal", + }, + { + name: "sad path: invalid token", + args: args{ + imageID: "sha256:invalid", + customHeaders: http.Header{ + "Trivy-Token": []string{"invalid-token"}, + }, + }, + wantErr: "twirp error unauthenticated", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) + err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo) + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } else { + assert.NoError(t, err, tt.name) + } + }) + } +} + +func TestRemoteCache_PutBlob(t *testing.T) { + mux := http.NewServeMux() + layerHandler := rpcCache.NewCacheServer(new(mockCacheServer), nil) + mux.Handle(rpcCache.CachePathPrefix, withToken(layerHandler, "valid-token", "Trivy-Token")) + ts := httptest.NewServer(mux) + + type args struct { + diffID string + layerInfo types.BlobInfo + customHeaders http.Header + } + tests := []struct { + name string + args args + wantErr string + }{ + { + name: "happy path", + args: args{ + diffID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + customHeaders: http.Header{ + "Trivy-Token": []string{"valid-token"}, + }, + }, + }, + { + name: "sad path", + args: args{ + diffID: "sha256:invalid", + customHeaders: http.Header{ + "Trivy-Token": []string{"valid-token"}, + }, + }, + wantErr: "twirp error internal", + }, + { + name: "sad path: invalid token", + args: args{ + diffID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + customHeaders: http.Header{ + "Trivy-Token": []string{"invalid-token"}, + }, + }, + wantErr: "twirp error unauthenticated", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) + err := c.PutBlob(tt.args.diffID, tt.args.layerInfo) + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } else { + assert.NoError(t, err, tt.name) + } + }) + } +} + +func TestRemoteCache_MissingBlobs(t *testing.T) { + mux := http.NewServeMux() + layerHandler := rpcCache.NewCacheServer(new(mockCacheServer), nil) + mux.Handle(rpcCache.CachePathPrefix, withToken(layerHandler, "valid-token", "Trivy-Token")) + ts := httptest.NewServer(mux) + + type args struct { + imageID string + layerIDs []string + customHeaders http.Header + } + tests := []struct { + name string + args args + wantMissingImage bool + wantMissingLayerIDs []string + wantErr string + }{ + { + name: "happy path", + args: args{ + imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + layerIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + customHeaders: http.Header{ + "Trivy-Token": []string{"valid-token"}, + }, + }, + wantMissingImage: true, + wantMissingLayerIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + { + name: "sad path", + args: args{ + imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + layerIDs: []string{ + "sha256:invalid", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + customHeaders: http.Header{ + "Trivy-Token": []string{"valid-token"}, + }, + }, + wantErr: "twirp error internal", + }, + { + name: "sad path with invalid token", + args: args{ + imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + layerIDs: []string{ + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + customHeaders: http.Header{ + "Trivy-Token": []string{"invalid-token"}, + }, + }, + wantErr: "twirp error unauthenticated", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) + gotMissingImage, gotMissingLayerIDs, err := c.MissingBlobs(tt.args.imageID, tt.args.layerIDs) + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } else { + require.NoError(t, err, tt.name) + } + + assert.Equal(t, tt.wantMissingImage, gotMissingImage) + assert.Equal(t, tt.wantMissingLayerIDs, gotMissingLayerIDs) + }) + } +} + +func TestRemoteCache_PutArtifactInsecure(t *testing.T) { + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + defer ts.Close() + + type args struct { + imageID string + imageInfo types.ArtifactInfo + insecure bool + } + tests := []struct { + name string + args args + wantErr string + }{ + { + name: "happy path", + args: args{ + imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + imageInfo: types.ArtifactInfo{}, + insecure: true, + }, + }, + { + name: "sad path", + args: args{ + imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + imageInfo: types.ArtifactInfo{}, + insecure: false, + }, + wantErr: "failed to do request", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewRemoteCache(ts.URL, nil, tt.args.insecure) + err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err, tt.name) + }) + } +} diff --git a/pkg/clock/clock.go b/pkg/clock/clock.go new file mode 100644 index 000000000000..91f6a5212bd2 --- /dev/null +++ b/pkg/clock/clock.go @@ -0,0 +1,33 @@ +package clock + +import ( + "context" + "time" + + "k8s.io/utils/clock" + clocktesting "k8s.io/utils/clock/testing" +) + +// clockKey is the context key for clock. It is unexported to prevent collisions with context keys defined in +// other packages. +type clockKey struct{} + +// With returns a new context with the given time. +func With(ctx context.Context, t time.Time) context.Context { + c := clocktesting.NewFakeClock(t) + return context.WithValue(ctx, clockKey{}, c) +} + +// Now returns the current time. +func Now(ctx context.Context) time.Time { + return Clock(ctx).Now() +} + +// Clock returns the clock from the context. +func Clock(ctx context.Context) clock.Clock { + t, ok := ctx.Value(clockKey{}).(clock.Clock) + if !ok { + return clock.RealClock{} + } + return t +} diff --git a/pkg/cloud/aws/cache/cache.go b/pkg/cloud/aws/cache/cache.go new file mode 100644 index 000000000000..660cb24b443b --- /dev/null +++ b/pkg/cloud/aws/cache/cache.go @@ -0,0 +1,132 @@ +package cache + +import ( + "encoding/json" + "fmt" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/aquasecurity/trivy/pkg/iac/state" +) + +type Cache struct { + path string + accountID string + region string + maxAge time.Duration +} + +const SchemaVersion = 2 + +type CacheData struct { + SchemaVersion int `json:"schema_version"` + State *state.State `json:"state"` + Services map[string]ServiceMetadata `json:"service_metadata"` + Updated time.Time `json:"updated"` +} + +type ServiceMetadata struct { + Name string `json:"name"` + Updated time.Time `json:"updated"` +} + +var ErrCacheNotFound = fmt.Errorf("cache record not found") +var ErrCacheIncompatible = fmt.Errorf("cache record used incomatible schema") +var ErrCacheExpired = fmt.Errorf("cache record expired") + +func New(cacheDir string, maxCacheAge time.Duration, accountID, region string) *Cache { + return &Cache{ + path: path.Join(cacheDir, "cloud", "aws", accountID, strings.ToLower(region), "data.json"), + accountID: accountID, + region: region, + maxAge: maxCacheAge, + } +} + +func (c *Cache) load() (*CacheData, error) { + + m, err := os.Open(c.path) + if err != nil { + return nil, ErrCacheNotFound + } + defer func() { _ = m.Close() }() + + var data CacheData + if err := json.NewDecoder(m).Decode(&data); err != nil { + return nil, err + } + + if data.SchemaVersion != SchemaVersion { + return nil, ErrCacheIncompatible + } + + if time.Since(data.Updated) > c.maxAge { + return nil, ErrCacheExpired + } + + return &data, nil +} + +func (c *Cache) ListServices(required []string) (included, missing []string) { + + data, err := c.load() + if err != nil { + return nil, required + } + + for _, service := range required { + metadata, ok := data.Services[service] + if !ok { + missing = append(missing, service) + continue + } + if time.Since(metadata.Updated) > c.maxAge { + missing = append(missing, service) + continue + } + included = append(included, service) + } + + return included, missing +} + +func (c *Cache) LoadState() (*state.State, error) { + data, err := c.load() + if err != nil { + return nil, err + } + return data.State, nil +} + +func (c *Cache) AddServices(s *state.State, includedServices []string) error { + data := &CacheData{ + SchemaVersion: SchemaVersion, + State: s, + Services: make(map[string]ServiceMetadata), + Updated: time.Now(), + } + + if previous, err := c.load(); err == nil { + data.Services = previous.Services + } + + for _, service := range includedServices { + data.Services[service] = ServiceMetadata{ + Name: service, + Updated: time.Now(), + } + } + + if err := os.MkdirAll(filepath.Dir(c.path), 0700); err != nil { + return err + } + f, err := os.Create(c.path) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + return json.NewEncoder(f).Encode(data) +} diff --git a/pkg/cloud/aws/commands/run.go b/pkg/cloud/aws/commands/run.go new file mode 100644 index 000000000000..23406aeafda5 --- /dev/null +++ b/pkg/cloud/aws/commands/run.go @@ -0,0 +1,172 @@ +package commands + +import ( + "context" + "errors" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/sts" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-aws/pkg/errs" + awsScanner "github.com/aquasecurity/trivy-aws/pkg/scanner" + "github.com/aquasecurity/trivy/pkg/cloud" + "github.com/aquasecurity/trivy/pkg/cloud/aws/config" + "github.com/aquasecurity/trivy/pkg/cloud/aws/scanner" + "github.com/aquasecurity/trivy/pkg/cloud/report" + "github.com/aquasecurity/trivy/pkg/commands/operation" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" +) + +var allSupportedServicesFunc = awsScanner.AllSupportedServices + +func getAccountIDAndRegion(ctx context.Context, region, endpoint string) (string, string, error) { + log.Logger.Debug("Looking for AWS credentials provider...") + + cfg, err := config.LoadDefaultAWSConfig(ctx, region, endpoint) + if err != nil { + return "", "", err + } + + svc := sts.NewFromConfig(cfg) + + log.Logger.Debug("Looking up AWS caller identity...") + result, err := svc.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{}) + if err != nil { + return "", "", xerrors.Errorf("failed to discover AWS caller identity: %w", err) + } + if result.Account == nil { + return "", "", xerrors.Errorf("missing account id for aws account") + } + log.Logger.Debugf("Verified AWS credentials for account %s!", *result.Account) + return *result.Account, cfg.Region, nil +} + +func validateServicesInput(services, skipServices []string) error { + for _, s := range services { + for _, ss := range skipServices { + if s == ss { + return xerrors.Errorf("service: %s specified to both skip and include", s) + } + } + } + return nil +} + +func processOptions(ctx context.Context, opt *flag.Options) error { + if err := validateServicesInput(opt.Services, opt.SkipServices); err != nil { + return err + } + + // support comma separated services too + var splitServices []string + for _, service := range opt.Services { + splitServices = append(splitServices, strings.Split(service, ",")...) + } + opt.Services = splitServices + + var splitSkipServices []string + for _, skipService := range opt.SkipServices { + splitSkipServices = append(splitSkipServices, strings.Split(skipService, ",")...) + } + opt.SkipServices = splitSkipServices + + if len(opt.Services) != 1 && opt.ARN != "" { + return xerrors.Errorf("you must specify the single --service which the --arn relates to") + } + + if opt.Account == "" || opt.Region == "" { + var err error + opt.Account, opt.Region, err = getAccountIDAndRegion(ctx, opt.Region, opt.Endpoint) + if err != nil { + return err + } + } + + err := filterServices(opt) + if err != nil { + return err + } + + log.Logger.Debug("scanning services: ", opt.Services) + return nil +} + +func filterServices(opt *flag.Options) error { + switch { + case len(opt.Services) == 0 && len(opt.SkipServices) == 0: + log.Logger.Debug("No service(s) specified, scanning all services...") + opt.Services = allSupportedServicesFunc() + case len(opt.SkipServices) > 0: + log.Logger.Debug("excluding services: ", opt.SkipServices) + for _, s := range allSupportedServicesFunc() { + if slices.Contains(opt.SkipServices, s) { + continue + } + if !slices.Contains(opt.Services, s) { + opt.Services = append(opt.Services, s) + } + } + case len(opt.Services) > 0: + log.Logger.Debugf("Specific services were requested: [%s]...", strings.Join(opt.Services, ", ")) + for _, service := range opt.Services { + var found bool + supported := allSupportedServicesFunc() + for _, allowed := range supported { + if allowed == service { + found = true + break + } + } + if !found { + return xerrors.Errorf("service '%s' is not currently supported - supported services are: %s", service, strings.Join(supported, ", ")) + } + } + } + return nil +} + +func Run(ctx context.Context, opt flag.Options) error { + ctx, cancel := context.WithTimeout(ctx, opt.GlobalOptions.Timeout) + defer cancel() + + var err error + defer func() { + if errors.Is(err, context.DeadlineExceeded) { + log.Logger.Warn("Increase --timeout value") + } + }() + + if err := processOptions(ctx, &opt); err != nil { + return err + } + + results, cached, err := scanner.NewScanner().Scan(ctx, opt) + if err != nil { + var aerr errs.AdapterError + if errors.As(err, &aerr) { + for _, e := range aerr.Errors() { + log.Logger.Warnf("Adapter error: %s", e) + } + } else { + return xerrors.Errorf("aws scan error: %w", err) + } + } + + log.Logger.Debug("Writing report to output...") + + res := results.GetFailed() + if opt.MisconfOptions.IncludeNonFailures { + res = results + } + + r := report.New(cloud.ProviderAWS, opt.Account, opt.Region, res, opt.Services) + if err := report.Write(ctx, r, opt, cached); err != nil { + return xerrors.Errorf("unable to write results: %w", err) + } + + operation.Exit(opt, r.Failed()) + return nil +} diff --git a/pkg/cloud/aws/commands/run_test.go b/pkg/cloud/aws/commands/run_test.go new file mode 100644 index 000000000000..fe25bf20098d --- /dev/null +++ b/pkg/cloud/aws/commands/run_test.go @@ -0,0 +1,1285 @@ +package commands + +import ( + "bytes" + "context" + "os" + "path/filepath" + "testing" + "time" + + "github.com/aquasecurity/trivy/pkg/clock" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/compliance/spec" + "github.com/aquasecurity/trivy/pkg/flag" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +const expectedS3ScanResult = `{ + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "12345678", + "ArtifactType": "aws_account", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "arn:aws:s3:::examplebucket", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 1, + "Failures": 8, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "ID": "AVD-AWS-0086", + "AVDID": "AVD-AWS-0086", + "Title": "S3 Access block should block public ACL", + "Description": "S3 buckets should block public ACLs on buckets and any objects they contain. By blocking, PUTs with fail if the object has any public ACL a.", + "Message": "No public access block so not blocking public acls", + "Resolution": "Enable blocking any PUT calls with a public ACL specified", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0086", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0086" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0087", + "AVDID": "AVD-AWS-0087", + "Title": "S3 Access block should block public policy", + "Description": "S3 bucket policy should have block public policy to prevent users from putting a policy that enable public access.", + "Message": "No public access block so not blocking public policies", + "Resolution": "Prevent policies that allow public access being PUT", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0087", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0087" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0088", + "AVDID": "AVD-AWS-0088", + "Title": "Unencrypted S3 bucket.", + "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.", + "Message": "Bucket does not have encryption enabled", + "Resolution": "Configure bucket encryption", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0088" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0090", + "AVDID": "AVD-AWS-0090", + "Title": "S3 Data should be versioned", + "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket. \nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets. \nWith versioning you can recover more easily from both unintended user actions and application failures.", + "Message": "Bucket does not have versioning enabled", + "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0090" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0132", + "AVDID": "AVD-AWS-0132", + "Title": "S3 encryption should use Customer Managed Keys", + "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", + "Message": "Bucket does not encrypt data with a customer managed key.", + "Resolution": "Enable encryption using customer managed keys", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0132" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0091", + "AVDID": "AVD-AWS-0091", + "Title": "S3 Access Block should Ignore Public Acl", + "Description": "S3 buckets should ignore public ACLs on buckets and any objects they contain. By ignoring rather than blocking, PUT calls with public ACLs will still be applied but the ACL will be ignored.", + "Message": "No public access block so not ignoring public acls", + "Resolution": "Enable ignoring the application of public ACLs in PUT calls", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0091", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0091" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0092", + "AVDID": "AVD-AWS-0092", + "Title": "S3 Buckets not publicly accessible through ACL.", + "Description": "Buckets should not have ACLs that allow public access", + "Resolution": "Don't use canned ACLs or switch to private acl", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0092", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0092" + ], + "Status": "PASS", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0093", + "AVDID": "AVD-AWS-0093", + "Title": "S3 Access block should restrict public bucket to limit access", + "Description": "S3 buckets should restrict public policies for the bucket. By enabling, the restrict_public_buckets, only the bucket owner and AWS Services can access if it has a public policy.", + "Message": "No public access block so not restricting public buckets", + "Resolution": "Limit the access to public buckets to only the owner or AWS Services (eg; CloudFront)", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0093", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0093" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0094", + "AVDID": "AVD-AWS-0094", + "Title": "S3 buckets should each define an aws_s3_bucket_public_access_block", + "Description": "The \"block public access\" settings in S3 override individual policies that apply to a given bucket, meaning that all public access can be controlled in one central types for that bucket. It is therefore good practice to define these settings for each bucket in order to clearly define the public access that can be allowed for it.", + "Message": "Bucket does not have a corresponding public access block.", + "Resolution": "Define a aws_s3_bucket_public_access_block for the given bucket to control public access policies", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0094", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0094" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + } + ] + } + ] +} +` + +const expectedS3ScanResultWithExceptions = `{ + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "12345678", + "ArtifactType": "aws_account", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "arn:aws:s3:::examplebucket", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 0, + "Failures": 1, + "Exceptions": 8 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "ID": "AVD-AWS-0094", + "AVDID": "AVD-AWS-0094", + "Title": "S3 buckets should each define an aws_s3_bucket_public_access_block", + "Description": "The \"block public access\" settings in S3 override individual policies that apply to a given bucket, meaning that all public access can be controlled in one central types for that bucket. It is therefore good practice to define these settings for each bucket in order to clearly define the public access that can be allowed for it.", + "Message": "Bucket does not have a corresponding public access block.", + "Resolution": "Define a aws_s3_bucket_public_access_block for the given bucket to control public access policies", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0094", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0094" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + } + ] + } + ] +} +` + +const expectedCustomScanResult = `{ + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "12345678", + "ArtifactType": "aws_account", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 0, + "Failures": 1, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "Title": "Bad input data", + "Description": "Just failing rule with input data", + "Message": "Rego policy resulted in DENY", + "Namespace": "user.whatever", + "Query": "deny", + "Severity": "LOW", + "References": [ + "" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "cloud", + "Service": "s3", + "Code": { + "Lines": null + } + } + } + ] + }, + { + "Target": "arn:aws:s3:::examplebucket", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 1, + "Failures": 8, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "ID": "AVD-AWS-0086", + "AVDID": "AVD-AWS-0086", + "Title": "S3 Access block should block public ACL", + "Description": "S3 buckets should block public ACLs on buckets and any objects they contain. By blocking, PUTs with fail if the object has any public ACL a.", + "Message": "No public access block so not blocking public acls", + "Resolution": "Enable blocking any PUT calls with a public ACL specified", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0086", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0086" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0087", + "AVDID": "AVD-AWS-0087", + "Title": "S3 Access block should block public policy", + "Description": "S3 bucket policy should have block public policy to prevent users from putting a policy that enable public access.", + "Message": "No public access block so not blocking public policies", + "Resolution": "Prevent policies that allow public access being PUT", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0087", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0087" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0088", + "AVDID": "AVD-AWS-0088", + "Title": "Unencrypted S3 bucket.", + "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.", + "Message": "Bucket does not have encryption enabled", + "Resolution": "Configure bucket encryption", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0088" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0090", + "AVDID": "AVD-AWS-0090", + "Title": "S3 Data should be versioned", + "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket. \nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets. \nWith versioning you can recover more easily from both unintended user actions and application failures.", + "Message": "Bucket does not have versioning enabled", + "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0090" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0132", + "AVDID": "AVD-AWS-0132", + "Title": "S3 encryption should use Customer Managed Keys", + "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", + "Message": "Bucket does not encrypt data with a customer managed key.", + "Resolution": "Enable encryption using customer managed keys", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0132" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0091", + "AVDID": "AVD-AWS-0091", + "Title": "S3 Access Block should Ignore Public Acl", + "Description": "S3 buckets should ignore public ACLs on buckets and any objects they contain. By ignoring rather than blocking, PUT calls with public ACLs will still be applied but the ACL will be ignored.", + "Message": "No public access block so not ignoring public acls", + "Resolution": "Enable ignoring the application of public ACLs in PUT calls", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0091", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0091" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0092", + "AVDID": "AVD-AWS-0092", + "Title": "S3 Buckets not publicly accessible through ACL.", + "Description": "Buckets should not have ACLs that allow public access", + "Resolution": "Don't use canned ACLs or switch to private acl", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0092", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0092" + ], + "Status": "PASS", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0093", + "AVDID": "AVD-AWS-0093", + "Title": "S3 Access block should restrict public bucket to limit access", + "Description": "S3 buckets should restrict public policies for the bucket. By enabling, the restrict_public_buckets, only the bucket owner and AWS Services can access if it has a public policy.", + "Message": "No public access block so not restricting public buckets", + "Resolution": "Limit the access to public buckets to only the owner or AWS Services (eg; CloudFront)", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0093", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0093" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0094", + "AVDID": "AVD-AWS-0094", + "Title": "S3 buckets should each define an aws_s3_bucket_public_access_block", + "Description": "The \"block public access\" settings in S3 override individual policies that apply to a given bucket, meaning that all public access can be controlled in one central types for that bucket. It is therefore good practice to define these settings for each bucket in order to clearly define the public access that can be allowed for it.", + "Message": "Bucket does not have a corresponding public access block.", + "Resolution": "Define a aws_s3_bucket_public_access_block for the given bucket to control public access policies", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0094", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0094" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + } + ] + } + ] +} +` + +const expectedS3AndCloudTrailResult = `{ + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "123456789", + "ArtifactType": "aws_account", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 1, + "Failures": 3, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "ID": "AVD-AWS-0014", + "AVDID": "AVD-AWS-0014", + "Title": "Cloudtrail should be enabled in all regions regardless of where your AWS resources are generally homed", + "Description": "When creating Cloudtrail in the AWS Management Console the trail is configured by default to be multi-region, this isn't the case with the Terraform resource. Cloudtrail should cover the full AWS account to ensure you can track changes in regions you are not actively operting in.", + "Resolution": "Enable Cloudtrail in all regions", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0014", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0014" + ], + "Status": "PASS", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "Provider": "aws", + "Service": "cloudtrail", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0015", + "AVDID": "AVD-AWS-0015", + "Title": "Cloudtrail should be encrypted at rest to secure access to sensitive trail data", + "Description": "Cloudtrail logs should be encrypted at rest to secure the sensitive data. Cloudtrail logs record all activity that occurs in the the account through API calls and would be one of the first places to look when reacting to a breach.", + "Message": "Trail is not encrypted.", + "Resolution": "Enable encryption at rest", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0015", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0015" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "Provider": "aws", + "Service": "cloudtrail", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0016", + "AVDID": "AVD-AWS-0016", + "Title": "Cloudtrail log validation should be enabled to prevent tampering of log data", + "Description": "Log validation should be activated on Cloudtrail logs to prevent the tampering of the underlying data in the S3 bucket. It is feasible that a rogue actor compromising an AWS account might want to modify the log data to remove trace of their actions.", + "Message": "Trail does not have log validation enabled.", + "Resolution": "Turn on log validation for Cloudtrail", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0016", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0016" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "Provider": "aws", + "Service": "cloudtrail", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0162", + "AVDID": "AVD-AWS-0162", + "Title": "CloudTrail logs should be stored in S3 and also sent to CloudWatch Logs", + "Description": "CloudTrail is a web service that records AWS API calls made in a given account. The recorded information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the AWS service.\n\nCloudTrail uses Amazon S3 for log file storage and delivery, so log files are stored durably. In addition to capturing CloudTrail logs in a specified Amazon S3 bucket for long-term analysis, you can perform real-time analysis by configuring CloudTrail to send logs to CloudWatch Logs.\n\nFor a trail that is enabled in all Regions in an account, CloudTrail sends log files from all those Regions to a CloudWatch Logs log group.", + "Message": "Trail does not have CloudWatch logging configured", + "Resolution": "Enable logging to CloudWatch", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0162", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0162" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "Provider": "aws", + "Service": "cloudtrail", + "Code": { + "Lines": null + } + } + } + ] + }, + { + "Target": "arn:aws:s3:::examplebucket", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 1, + "Failures": 8, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "ID": "AVD-AWS-0086", + "AVDID": "AVD-AWS-0086", + "Title": "S3 Access block should block public ACL", + "Description": "S3 buckets should block public ACLs on buckets and any objects they contain. By blocking, PUTs with fail if the object has any public ACL a.", + "Message": "No public access block so not blocking public acls", + "Resolution": "Enable blocking any PUT calls with a public ACL specified", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0086", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0086" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0087", + "AVDID": "AVD-AWS-0087", + "Title": "S3 Access block should block public policy", + "Description": "S3 bucket policy should have block public policy to prevent users from putting a policy that enable public access.", + "Message": "No public access block so not blocking public policies", + "Resolution": "Prevent policies that allow public access being PUT", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0087", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0087" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0088", + "AVDID": "AVD-AWS-0088", + "Title": "Unencrypted S3 bucket.", + "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.", + "Message": "Bucket does not have encryption enabled", + "Resolution": "Configure bucket encryption", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0088" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0090", + "AVDID": "AVD-AWS-0090", + "Title": "S3 Data should be versioned", + "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket. \nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets. \nWith versioning you can recover more easily from both unintended user actions and application failures.", + "Message": "Bucket does not have versioning enabled", + "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0090" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0132", + "AVDID": "AVD-AWS-0132", + "Title": "S3 encryption should use Customer Managed Keys", + "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", + "Message": "Bucket does not encrypt data with a customer managed key.", + "Resolution": "Enable encryption using customer managed keys", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0132" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0091", + "AVDID": "AVD-AWS-0091", + "Title": "S3 Access Block should Ignore Public Acl", + "Description": "S3 buckets should ignore public ACLs on buckets and any objects they contain. By ignoring rather than blocking, PUT calls with public ACLs will still be applied but the ACL will be ignored.", + "Message": "No public access block so not ignoring public acls", + "Resolution": "Enable ignoring the application of public ACLs in PUT calls", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0091", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0091" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0092", + "AVDID": "AVD-AWS-0092", + "Title": "S3 Buckets not publicly accessible through ACL.", + "Description": "Buckets should not have ACLs that allow public access", + "Resolution": "Don't use canned ACLs or switch to private acl", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0092", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0092" + ], + "Status": "PASS", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0093", + "AVDID": "AVD-AWS-0093", + "Title": "S3 Access block should restrict public bucket to limit access", + "Description": "S3 buckets should restrict public policies for the bucket. By enabling, the restrict_public_buckets, only the bucket owner and AWS Services can access if it has a public policy.", + "Message": "No public access block so not restricting public buckets", + "Resolution": "Limit the access to public buckets to only the owner or AWS Services (eg; CloudFront)", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0093", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0093" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0094", + "AVDID": "AVD-AWS-0094", + "Title": "S3 buckets should each define an aws_s3_bucket_public_access_block", + "Description": "The \"block public access\" settings in S3 override individual policies that apply to a given bucket, meaning that all public access can be controlled in one central types for that bucket. It is therefore good practice to define these settings for each bucket in order to clearly define the public access that can be allowed for it.", + "Message": "Bucket does not have a corresponding public access block.", + "Resolution": "Define a aws_s3_bucket_public_access_block for the given bucket to control public access policies", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0094", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0094" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + } + ] + } + ] +} +` + +func Test_Run(t *testing.T) { + regoDir := t.TempDir() + + tests := []struct { + name string + options flag.Options + want string + expectErr bool + cacheContent string + regoPolicy string + allServices []string + inputData string + ignoreFile string + }{ + { + name: "succeed with cached infra", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + Account: "12345678", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3onlycache.json", + allServices: []string{"s3"}, + want: expectedS3ScanResult, + }, + { + name: "custom rego rule with passed results", + options: flag.Options{ + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + Account: "12345678", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + RegoOptions: flag.RegoOptions{ + Trace: true, + PolicyPaths: []string{ + filepath.Join(regoDir, "policies"), + }, + PolicyNamespaces: []string{ + "user", + }, + DataPaths: []string{ + filepath.Join(regoDir, "data"), + }, + SkipPolicyUpdate: true, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + regoPolicy: `# METADATA +# title: Bad input data +# description: Just failing rule with input data +# scope: package +# schemas: +# - input: schema["input"] +# custom: +# severity: LOW +# service: s3 +# input: +# selector: +# - type: cloud +package user.whatever +import data.settings.DS123.foo + +deny { + foo == true +} +`, + inputData: `{ + "settings": { + "DS123": { + "foo": true + } + } +}`, + cacheContent: filepath.Join("testdata", "s3onlycache.json"), + allServices: []string{"s3"}, + want: expectedCustomScanResult, + }, + { + name: "compliance report summary", + options: flag.Options{ + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + Account: "12345678", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + ReportOptions: flag.ReportOptions{ + Compliance: spec.ComplianceSpec{ + Spec: iacTypes.Spec{ + ID: "@testdata/example-spec.yaml", + Title: "my-custom-spec", + Description: "My fancy spec", + Version: "1.2", + Controls: []iacTypes.Control{ + { + ID: "1.1", + Name: "Unencrypted S3 bucket", + Description: "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-AWS-0088"}, + }, + Severity: "HIGH", + }, + }, + }, + }, + Format: "table", + ReportFormat: "summary", + }, + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + }, + cacheContent: "testdata/s3onlycache.json", + allServices: []string{"s3"}, + want: ` +Summary Report for compliance: my-custom-spec +┌─────┬──────────┬───────────────────────┬────────┬────────┠+│ ID │ Severity │ Control Name │ Status │ Issues │ +├─────┼──────────┼───────────────────────┼────────┼────────┤ +│ 1.1 │ HIGH │ Unencrypted S3 bucket │ FAIL │ 1 │ +└─────┴──────────┴───────────────────────┴────────┴────────┘ +`, + }, + { + name: "scan an unsupported service", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Account: "123456789", + Services: []string{"theultimateservice"}, + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3onlycache.json", + expectErr: true, + }, + { + name: "scan every service", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Account: "123456789", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3andcloudtrailcache.json", + allServices: []string{ + "s3", + "cloudtrail", + }, + want: expectedS3AndCloudTrailResult, + }, + { + name: "skip certain services and include specific services", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + SkipServices: []string{"cloudtrail"}, + Account: "123456789", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3andcloudtrailcache.json", + allServices: []string{ + "s3", + "cloudtrail", + }, + // we skip cloudtrail but still expect results from it as it is cached + want: expectedS3AndCloudTrailResult, + }, + { + name: "only skip certain services but scan the rest", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + SkipServices: []string{ + "cloudtrail", + "iam", + }, + Account: "12345678", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + allServices: []string{ + "s3", + "cloudtrail", + "iam", + }, + cacheContent: "testdata/s3onlycache.json", + want: expectedS3ScanResult, + }, + { + name: "fail - service specified to both include and exclude", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + SkipServices: []string{"s3"}, + Account: "123456789", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3andcloudtrailcache.json", + expectErr: true, + }, + { + name: "ignore findings with .trivyignore", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + Account: "12345678", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3onlycache.json", + allServices: []string{"s3"}, + ignoreFile: "testdata/.trivyignore", + want: expectedS3ScanResultWithExceptions, + }, + } + + ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.allServices != nil { + oldAllSupportedServicesFunc := allSupportedServicesFunc + allSupportedServicesFunc = func() []string { + return test.allServices + } + defer func() { + allSupportedServicesFunc = oldAllSupportedServicesFunc + }() + } + + output := bytes.NewBuffer(nil) + test.options.SetOutputWriter(output) + test.options.Debug = true + test.options.GlobalOptions.Timeout = time.Minute + if test.options.Format == "" { + test.options.Format = "json" + } + test.options.Severities = []dbTypes.Severity{ + dbTypes.SeverityUnknown, + dbTypes.SeverityLow, + dbTypes.SeverityMedium, + dbTypes.SeverityHigh, + dbTypes.SeverityCritical, + } + + if test.regoPolicy != "" { + require.NoError(t, os.MkdirAll(filepath.Join(regoDir, "policies"), 0755)) + require.NoError(t, os.WriteFile(filepath.Join(regoDir, "policies", "user.rego"), []byte(test.regoPolicy), 0644)) + } + + if test.inputData != "" { + require.NoError(t, os.MkdirAll(filepath.Join(regoDir, "data"), 0755)) + require.NoError(t, os.WriteFile(filepath.Join(regoDir, "data", "data.json"), []byte(test.inputData), 0644)) + } + + if test.cacheContent != "" { + cacheRoot := t.TempDir() + test.options.CacheDir = cacheRoot + cacheFile := filepath.Join(cacheRoot, "cloud", "aws", test.options.Account, test.options.Region, "data.json") + require.NoError(t, os.MkdirAll(filepath.Dir(cacheFile), 0700)) + + cacheData, err := os.ReadFile(test.cacheContent) + require.NoError(t, err, test.name) + + require.NoError(t, os.WriteFile(cacheFile, cacheData, 0600)) + } + + if test.ignoreFile != "" { + test.options.ReportOptions.IgnoreFile = test.ignoreFile + } + + err := Run(ctx, test.options) + if test.expectErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, test.want, output.String()) + }) + } +} diff --git a/pkg/cloud/aws/commands/testdata/.trivyignore b/pkg/cloud/aws/commands/testdata/.trivyignore new file mode 100644 index 000000000000..44ef395ee173 --- /dev/null +++ b/pkg/cloud/aws/commands/testdata/.trivyignore @@ -0,0 +1,8 @@ +AVD-AWS-0086 +AVD-AWS-0087 +AVD-AWS-0088 +AVD-AWS-0090 +AVD-AWS-0132 +AVD-AWS-0091 +AVD-AWS-0092 +AVD-AWS-0093 \ No newline at end of file diff --git a/pkg/cloud/aws/commands/testdata/example-spec.yaml b/pkg/cloud/aws/commands/testdata/example-spec.yaml new file mode 100644 index 000000000000..19fbf0a3bf31 --- /dev/null +++ b/pkg/cloud/aws/commands/testdata/example-spec.yaml @@ -0,0 +1,13 @@ +spec: + id: "0001" + title: my-custom-spec + description: My fancy spec + version: "1.2" + controls: + - id: "1.1" + name: Unencrypted S3 bucket + description: |- + S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised. + checks: + - id: AVD-AWS-0088 + severity: HIGH \ No newline at end of file diff --git a/pkg/cloud/aws/commands/testdata/s3andcloudtrailcache.json b/pkg/cloud/aws/commands/testdata/s3andcloudtrailcache.json new file mode 100644 index 000000000000..f9cfd2abcec2 --- /dev/null +++ b/pkg/cloud/aws/commands/testdata/s3andcloudtrailcache.json @@ -0,0 +1,420 @@ +{ + "schema_version": 2, + "state": { + "AWS": { + "S3": { + "Buckets": [{ + "Metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "Name": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": "examplebucket" + }, + "PublicAccessBlock": null, + "BucketPolicies": null, + "Encryption": { + "Metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "Enabled": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": false + }, + "Algorithm": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": "" + }, + "KMSKeyId": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": "" + } + }, + "Versioning": { + "Metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "Enabled": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": false + }, + "MFADelete": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": false + } + }, + "Logging": { + "Metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "Enabled": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": false + }, + "TargetBucket": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": "" + } + }, + "ACL": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": "private" + } + }] + }, + "CloudTrail": { + "Trails": [{ + "Metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "unresolvable": false + }, + "Name": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "unresolvable": false + }, + "value": "management-events" + }, + "EnableLogFileValidation": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "unresolvable": false + }, + "value": false + }, + "IsMultiRegion": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "unresolvable": false + }, + "value": true + }, + "KMSKeyID": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "unresolvable": false + }, + "value": "" + }, + "CloudWatchLogsLogGroupArn": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "unresolvable": false + }, + "value": "" + }, + "IsLogging": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "unresolvable": false + }, + "value": true + }, + "BucketName": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "unresolvable": false + }, + "value": "aws-cloudtrail-logs-12345678-d0a47f2f" + }, + "EventSelectors": null + }] + } + } + + }, + "service_metadata": { + "s3": { + "name": "s3", + "updated": "2022-10-04T14:08:36.659817426+01:00" + }, + "cloudtrail": { + "name": "cloudtrail", + "updated": "2022-10-04T14:08:36.659817426+01:00" + } + }, + "updated": "2022-10-04T14:08:36.659817426+01:00" +} diff --git a/pkg/cloud/aws/commands/testdata/s3onlycache.json b/pkg/cloud/aws/commands/testdata/s3onlycache.json new file mode 100644 index 000000000000..43a015aa9ca9 --- /dev/null +++ b/pkg/cloud/aws/commands/testdata/s3onlycache.json @@ -0,0 +1,261 @@ +{ + "schema_version": 2, + "state": { + "AWS": { + "S3": { + "Buckets": [{ + "Metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "Name": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": "examplebucket" + }, + "PublicAccessBlock": null, + "BucketPolicies": null, + "Encryption": { + "Metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "Enabled": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": false + }, + "Algorithm": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": "" + }, + "KMSKeyId": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": "" + } + }, + "Versioning": { + "Metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "Enabled": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": false + }, + "MFADelete": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": false + } + }, + "Logging": { + "Metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "Enabled": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": false + }, + "TargetBucket": { + "metadata": { + "default": true, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": "" + } + }, + "ACL": { + "metadata": { + "default": false, + "explicit": false, + "managed": true, + "parent": null, + "range": { + "endLine": 0, + "filename": "arn:aws:s3:::examplebucket", + "fsKey": "", + "isLogicalSource": false, + "sourcePrefix": "remote", + "startLine": 0 + }, + "ref": "arn:aws:s3:::examplebucket", + "unresolvable": false + }, + "value": "private" + } + }] + } + } + }, + "service_metadata": { + "s3": { + "name": "s3", + "updated": "2022-10-04T14:08:36.659817426+01:00" + } + }, + "updated": "2022-10-04T14:08:36.659817426+01:00" +} diff --git a/pkg/cloud/aws/config/config.go b/pkg/cloud/aws/config/config.go new file mode 100644 index 000000000000..a2c9be5916bf --- /dev/null +++ b/pkg/cloud/aws/config/config.go @@ -0,0 +1,47 @@ +package config + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "golang.org/x/xerrors" +) + +func EndpointResolver(endpoint string) aws.EndpointResolverWithOptionsFunc { + return aws.EndpointResolverWithOptionsFunc(func(_, reg string, options ...interface{}) (aws.Endpoint, error) { + return aws.Endpoint{ + PartitionID: "aws", + URL: endpoint, + SigningRegion: reg, + Source: aws.EndpointSourceCustom, + }, nil + }) +} + +func MakeAWSOptions(region, endpoint string) []func(*awsconfig.LoadOptions) error { + var options []func(*awsconfig.LoadOptions) error + + if region != "" { + options = append(options, awsconfig.WithRegion(region)) + } + + if endpoint != "" { + options = append(options, awsconfig.WithEndpointResolverWithOptions(EndpointResolver(endpoint))) + } + + return options +} + +func LoadDefaultAWSConfig(ctx context.Context, region, endpoint string) (aws.Config, error) { + cfg, err := awsconfig.LoadDefaultConfig(ctx, MakeAWSOptions(region, endpoint)...) + if err != nil { + return aws.Config{}, xerrors.Errorf("aws config load error: %w", err) + } + + if cfg.Region == "" { + return aws.Config{}, xerrors.New("aws region is required") + } + + return cfg, nil +} diff --git a/pkg/cloud/aws/scanner/progress.go b/pkg/cloud/aws/scanner/progress.go new file mode 100644 index 000000000000..a313dd482c6c --- /dev/null +++ b/pkg/cloud/aws/scanner/progress.go @@ -0,0 +1,83 @@ +package scanner + +import ( + "fmt" + "io" + "os" + + "github.com/aquasecurity/loading/pkg/bar" +) + +type progressTracker struct { + serviceBar *bar.Bar + serviceTotal int + serviceCurrent int + isTTY bool + debugWriter io.Writer +} + +func newProgressTracker(w io.Writer) *progressTracker { + var isTTY bool + if stat, err := os.Stdout.Stat(); err == nil { + isTTY = stat.Mode()&os.ModeCharDevice == os.ModeCharDevice + } + return &progressTracker{ + isTTY: isTTY, + debugWriter: w, + } +} + +func (m *progressTracker) Finish() { + if !m.isTTY || m.serviceBar == nil { + return + } + m.serviceBar.Finish() +} + +func (m *progressTracker) IncrementResource() { + if !m.isTTY { + return + } + m.serviceBar.Increment() +} + +func (m *progressTracker) SetTotalResources(i int) { + if !m.isTTY { + return + } + m.serviceBar.SetTotal(i) +} + +func (m *progressTracker) SetTotalServices(i int) { + m.serviceTotal = i +} + +func (m *progressTracker) SetServiceLabel(label string) { + if !m.isTTY { + return + } + m.serviceBar.SetLabel("└╴" + label) + m.serviceBar.SetCurrent(0) +} + +func (m *progressTracker) FinishService() { + if !m.isTTY { + return + } + m.serviceCurrent++ + m.serviceBar.Finish() +} + +func (m *progressTracker) StartService(name string) { + if !m.isTTY { + return + } + + fmt.Fprintf(m.debugWriter, "[%d/%d] Scanning %s...\n", m.serviceCurrent+1, m.serviceTotal, name) + m.serviceBar = bar.New( + bar.OptionHideOnFinish(true), + bar.OptionWithAutoComplete(false), + bar.OptionWithRenderFunc(bar.RenderColoured(0xff, 0x66, 0x00)), + ) + m.SetServiceLabel("Initializing...") +} diff --git a/pkg/cloud/aws/scanner/scanner.go b/pkg/cloud/aws/scanner/scanner.go new file mode 100644 index 000000000000..84b5cf6c640e --- /dev/null +++ b/pkg/cloud/aws/scanner/scanner.go @@ -0,0 +1,172 @@ +package scanner + +import ( + "context" + "fmt" + "io/fs" + + "golang.org/x/xerrors" + + aws "github.com/aquasecurity/trivy-aws/pkg/scanner" + "github.com/aquasecurity/trivy/pkg/cloud/aws/cache" + "github.com/aquasecurity/trivy/pkg/commands/operation" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/state" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +type AWSScanner struct { +} + +func NewScanner() *AWSScanner { + return &AWSScanner{} +} + +func (s *AWSScanner) Scan(ctx context.Context, option flag.Options) (scan.Results, bool, error) { + + awsCache := cache.New(option.CacheDir, option.MaxCacheAge, option.Account, option.Region) + included, missing := awsCache.ListServices(option.Services) + + prefixedLogger := &log.PrefixedLogger{Name: "aws"} + + var scannerOpts []options.ScannerOption + if !option.NoProgress { + tracker := newProgressTracker(prefixedLogger) + defer tracker.Finish() + scannerOpts = append(scannerOpts, aws.ScannerWithProgressTracker(tracker)) + } + + if len(missing) > 0 { + scannerOpts = append(scannerOpts, aws.ScannerWithAWSServices(missing...)) + } + + if option.Debug { + scannerOpts = append(scannerOpts, options.ScannerWithDebug(prefixedLogger)) + } + + if option.Trace { + scannerOpts = append(scannerOpts, options.ScannerWithTrace(prefixedLogger)) + } + + if option.Region != "" { + scannerOpts = append( + scannerOpts, + aws.ScannerWithAWSRegion(option.Region), + ) + } + + if option.Endpoint != "" { + scannerOpts = append( + scannerOpts, + aws.ScannerWithAWSEndpoint(option.Endpoint), + ) + } + + var policyPaths []string + var downloadedPolicyPaths []string + var err error + downloadedPolicyPaths, err = operation.InitBuiltinPolicies(context.Background(), option.CacheDir, option.Quiet, option.SkipPolicyUpdate, option.MisconfOptions.PolicyBundleRepository, option.RegistryOpts()) + if err != nil { + if !option.SkipPolicyUpdate { + log.Logger.Errorf("Falling back to embedded policies: %s", err) + } + } else { + log.Logger.Debug("Policies successfully loaded from disk") + policyPaths = append(policyPaths, downloadedPolicyPaths...) + scannerOpts = append(scannerOpts, + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithEmbeddedLibraries(false)) + } + + var policyFS fs.FS + policyFS, policyPaths, err = misconf.CreatePolicyFS(append(policyPaths, option.RegoOptions.PolicyPaths...)) + if err != nil { + return nil, false, xerrors.Errorf("unable to create policyfs: %w", err) + } + + scannerOpts = append(scannerOpts, + options.ScannerWithPolicyFilesystem(policyFS), + options.ScannerWithPolicyDirs(policyPaths...), + ) + + dataFS, dataPaths, err := misconf.CreateDataFS(option.RegoOptions.DataPaths) + if err != nil { + log.Logger.Errorf("Could not load config data: %s", err) + } + scannerOpts = append(scannerOpts, + options.ScannerWithDataDirs(dataPaths...), + options.ScannerWithDataFilesystem(dataFS), + ) + + scannerOpts = addPolicyNamespaces(option.RegoOptions.PolicyNamespaces, scannerOpts) + + if option.Compliance.Spec.ID != "" { + scannerOpts = append(scannerOpts, options.ScannerWithSpec(option.Compliance.Spec.ID)) + } else { + scannerOpts = append(scannerOpts, options.ScannerWithFrameworks( + framework.Default, + framework.CIS_AWS_1_2)) + } + + scanner := aws.New(scannerOpts...) + + var freshState *state.State + if len(missing) > 0 || option.CloudOptions.UpdateCache { + var err error + freshState, err = scanner.CreateState(ctx) + if err != nil { + return nil, false, err + } + } + + fullState, err := createState(freshState, awsCache) + if err != nil { + return nil, false, err + } + + if fullState == nil { + return nil, false, fmt.Errorf("no resultant state found") + } + + if err := awsCache.AddServices(fullState, missing); err != nil { + return nil, false, err + } + + defsecResults, err := scanner.Scan(ctx, fullState) + if err != nil { + return nil, false, err + } + + return defsecResults, len(included) > 0, nil +} + +func createState(freshState *state.State, awsCache *cache.Cache) (*state.State, error) { + var fullState *state.State + if previousState, err := awsCache.LoadState(); err == nil { + if freshState != nil { + fullState, err = previousState.Merge(freshState) + if err != nil { + return nil, err + } + } else { + fullState = previousState + } + } else { + fullState = freshState + } + return fullState, nil +} + +func addPolicyNamespaces(namespaces []string, scannerOpts []options.ScannerOption) []options.ScannerOption { + if len(namespaces) > 0 { + scannerOpts = append( + scannerOpts, + options.ScannerWithPolicyNamespaces(namespaces...), + ) + } + return scannerOpts +} diff --git a/pkg/cloud/provider.go b/pkg/cloud/provider.go new file mode 100644 index 000000000000..f495e15e2095 --- /dev/null +++ b/pkg/cloud/provider.go @@ -0,0 +1,5 @@ +package cloud + +const ( + ProviderAWS = "AWS" +) diff --git a/pkg/cloud/report/convert.go b/pkg/cloud/report/convert.go new file mode 100644 index 000000000000..2f6917d18553 --- /dev/null +++ b/pkg/cloud/report/convert.go @@ -0,0 +1,106 @@ +package report + +import ( + "fmt" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws/arn" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/types" +) + +func ConvertResults(results scan.Results, provider string, scoped []string) map[string]ResultsAtTime { + convertedResults := make(map[string]ResultsAtTime) + resultsByServiceAndARN := make(map[string]map[string]scan.Results) + for _, result := range results { + + service := result.Rule().Service + resource := result.Flatten().Resource + if service == "" || service == "general" { + if parsed, err := arn.Parse(resource); err == nil { + service = parsed.Service + } + } + + existingService, ok := resultsByServiceAndARN[service] + if !ok { + existingService = make(map[string]scan.Results) + } + + existingService[resource] = append(existingService[resource], result) + resultsByServiceAndARN[service] = existingService + } + // ensure we have entries for all scoped services, even if there are no results + for _, service := range scoped { + if _, ok := resultsByServiceAndARN[service]; !ok { + resultsByServiceAndARN[service] = nil + } + } + for service, arnResults := range resultsByServiceAndARN { + + var convertedArnResults []types.Result + + for arn, serviceResults := range arnResults { + + arnResult := types.Result{ + Target: arn, + Class: types.ClassConfig, + Type: ftypes.Cloud, + } + + for _, result := range serviceResults { + + var primaryURL string + + // empty namespace implies a go rule from defsec, "builtin" refers to a built-in rego rule + // this ensures we don't generate bad links for custom policies + if result.RegoNamespace() == "" || strings.HasPrefix(result.RegoNamespace(), "builtin.") { + primaryURL = fmt.Sprintf("https://avd.aquasec.com/misconfig/%s", strings.ToLower(result.Rule().AVDID)) + } + + status := types.MisconfStatusFailure + switch result.Status() { + case scan.StatusPassed: + status = types.MisconfStatusPassed + case scan.StatusIgnored: + status = types.MisconfStatusException + } + + flat := result.Flatten() + + arnResult.Misconfigurations = append(arnResult.Misconfigurations, types.DetectedMisconfiguration{ + Type: provider, + ID: result.Rule().AVDID, + AVDID: result.Rule().AVDID, + Title: result.Rule().Summary, + Description: strings.TrimSpace(result.Rule().Explanation), + Message: strings.TrimSpace(result.Description()), + Namespace: result.RegoNamespace(), + Query: result.RegoRule(), + Resolution: result.Rule().Resolution, + Severity: string(result.Severity()), + PrimaryURL: primaryURL, + References: []string{primaryURL}, + Status: status, + CauseMetadata: ftypes.CauseMetadata{ + Resource: flat.Resource, + Provider: string(flat.RuleProvider), + Service: service, + StartLine: flat.Location.StartLine, + EndLine: flat.Location.EndLine, + }, + }) + } + + convertedArnResults = append(convertedArnResults, arnResult) + } + convertedResults[service] = ResultsAtTime{ + Results: convertedArnResults, + CreationTime: time.Now(), + } + } + return convertedResults +} diff --git a/pkg/cloud/report/convert_test.go b/pkg/cloud/report/convert_test.go new file mode 100644 index 000000000000..b8a0b728c53f --- /dev/null +++ b/pkg/cloud/report/convert_test.go @@ -0,0 +1,242 @@ +package report + +import ( + "sort" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/stretchr/testify/assert" + + fanaltypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/iac/scan" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +func Test_ResultConversion(t *testing.T) { + + tests := []struct { + name string + results scan.Results + provider string + scoped []string + expected map[string]ResultsAtTime + }{ + { + name: "no results", + results: scan.Results{}, + provider: "AWS", + expected: make(map[string]ResultsAtTime), + }, + { + name: "no results, multiple scoped services", + results: scan.Results{}, + provider: "AWS", + scoped: []string{"s3", "ec2"}, + expected: map[string]ResultsAtTime{ + "s3": {}, + "ec2": {}, + }, + }, + { + name: "multiple results", + results: func() scan.Results { + + baseRule := scan.Rule{ + AVDID: "AVD-AWS-9999", + Aliases: []string{"AWS999"}, + ShortCode: "no-bad-stuff", + Summary: "Do not use bad stuff", + Explanation: "Bad stuff is... bad", + Impact: "Bad things", + Resolution: "Remove bad stuff", + Provider: "AWS", + Severity: "HIGH", + } + + var s3Results scan.Results + s3Results.Add( + "something failed", + iacTypes.NewRemoteMetadata((arn.ARN{ + Partition: "aws", + Service: "s3", + Region: "us-east-1", + AccountID: "1234567890", + Resource: "bucket1", + }).String()), + ) + s3Results.Add( + "something else failed", + iacTypes.NewRemoteMetadata((arn.ARN{ + Partition: "aws", + Service: "s3", + Region: "us-east-1", + AccountID: "1234567890", + Resource: "bucket2", + }).String()), + ) + s3Results.Add( + "something else failed again", + iacTypes.NewRemoteMetadata((arn.ARN{ + Partition: "aws", + Service: "s3", + Region: "us-east-1", + AccountID: "1234567890", + Resource: "bucket2", + }).String()), + ) + baseRule.Service = "s3" + s3Results.SetRule(baseRule) + var ec2Results scan.Results + ec2Results.Add( + "instance is bad", + iacTypes.NewRemoteMetadata((arn.ARN{ + Partition: "aws", + Service: "ec2", + Region: "us-east-1", + AccountID: "1234567890", + Resource: "instance1", + }).String()), + ) + baseRule.Service = "ec2" + ec2Results.SetRule(baseRule) + return append(s3Results, ec2Results...) + }(), + provider: "AWS", + expected: map[string]ResultsAtTime{ + "s3": { + Results: types.Results{ + { + Target: "arn:aws:s3:us-east-1:1234567890:bucket1", + Class: "config", + Type: "cloud", + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "AWS", + ID: "AVD-AWS-9999", + AVDID: "AVD-AWS-9999", + Title: "Do not use bad stuff", + Description: "Bad stuff is... bad", + Message: "something failed", + Resolution: "Remove bad stuff", + Severity: "HIGH", + PrimaryURL: "https://avd.aquasec.com/misconfig/avd-aws-9999", + References: []string{ + "https://avd.aquasec.com/misconfig/avd-aws-9999", + }, + Status: "FAIL", + CauseMetadata: fanaltypes.CauseMetadata{ + Resource: "arn:aws:s3:us-east-1:1234567890:bucket1", + Provider: "AWS", + Service: "s3", + StartLine: 0, + EndLine: 0, + Code: fanaltypes.Code{}, + }, + }, + }, + }, + { + Target: "arn:aws:s3:us-east-1:1234567890:bucket2", + Class: "config", + Type: "cloud", + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "AWS", + ID: "AVD-AWS-9999", + AVDID: "AVD-AWS-9999", + Title: "Do not use bad stuff", + Description: "Bad stuff is... bad", + Message: "something else failed", + Resolution: "Remove bad stuff", + Severity: "HIGH", + PrimaryURL: "https://avd.aquasec.com/misconfig/avd-aws-9999", + References: []string{ + "https://avd.aquasec.com/misconfig/avd-aws-9999", + }, + Status: "FAIL", + CauseMetadata: fanaltypes.CauseMetadata{ + Resource: "arn:aws:s3:us-east-1:1234567890:bucket2", + Provider: "AWS", + Service: "s3", + }, + }, + { + Type: "AWS", + ID: "AVD-AWS-9999", + AVDID: "AVD-AWS-9999", + Title: "Do not use bad stuff", + Description: "Bad stuff is... bad", + Message: "something else failed again", + Resolution: "Remove bad stuff", + Severity: "HIGH", + PrimaryURL: "https://avd.aquasec.com/misconfig/avd-aws-9999", + References: []string{ + "https://avd.aquasec.com/misconfig/avd-aws-9999", + }, + Status: "FAIL", + CauseMetadata: fanaltypes.CauseMetadata{ + Resource: "arn:aws:s3:us-east-1:1234567890:bucket2", + Provider: "AWS", + Service: "s3", + }, + }, + }, + }, + }, + }, + "ec2": { + Results: types.Results{ + { + Target: "arn:aws:ec2:us-east-1:1234567890:instance1", + Class: "config", + Type: "cloud", + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "AWS", + ID: "AVD-AWS-9999", + AVDID: "AVD-AWS-9999", + Title: "Do not use bad stuff", + Description: "Bad stuff is... bad", + Message: "instance is bad", + Resolution: "Remove bad stuff", + Severity: "HIGH", + PrimaryURL: "https://avd.aquasec.com/misconfig/avd-aws-9999", + References: []string{ + "https://avd.aquasec.com/misconfig/avd-aws-9999", + }, + Status: "FAIL", + CauseMetadata: fanaltypes.CauseMetadata{ + Resource: "arn:aws:ec2:us-east-1:1234567890:instance1", + Provider: "AWS", + Service: "ec2", + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + converted := ConvertResults(test.results, test.provider, test.scoped) + assertConvertedResultsMatch(t, test.expected, converted) + }) + } + +} + +func assertConvertedResultsMatch(t *testing.T, expected, actual map[string]ResultsAtTime) { + assert.Equal(t, len(expected), len(actual)) + for service, resultsAtTime := range expected { + _, ok := actual[service] + assert.True(t, ok) + sort.Slice(actual[service].Results, func(i, j int) bool { + return actual[service].Results[i].Target < actual[service].Results[j].Target + }) + assert.ElementsMatch(t, resultsAtTime.Results, actual[service].Results) + } +} diff --git a/pkg/cloud/report/report.go b/pkg/cloud/report/report.go new file mode 100644 index 000000000000..2b2f8f3f17ea --- /dev/null +++ b/pkg/cloud/report/report.go @@ -0,0 +1,160 @@ +package report + +import ( + "context" + "io" + "os" + "sort" + "time" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/tml" + "github.com/aquasecurity/trivy/pkg/clock" + cr "github.com/aquasecurity/trivy/pkg/compliance/report" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/iac/scan" + pkgReport "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/result" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + tableFormat = "table" +) + +// Report represents an AWS scan report +type Report struct { + Provider string + AccountID string + Region string + Results map[string]ResultsAtTime + ServicesInScope []string +} + +type ResultsAtTime struct { + Results types.Results + CreationTime time.Time +} + +func New(provider, accountID, region string, defsecResults scan.Results, scopedServices []string) *Report { + return &Report{ + Provider: provider, + AccountID: accountID, + Results: ConvertResults(defsecResults, provider, scopedServices), + ServicesInScope: scopedServices, + Region: region, + } +} + +// Failed returns whether the aws report includes any "failed" results +func (r *Report) Failed() bool { + for _, set := range r.Results { + if set.Results.Failed() { + return true + } + } + return false +} + +// Write writes the results in the give format +func Write(ctx context.Context, rep *Report, opt flag.Options, fromCache bool) error { + output, cleanup, err := opt.OutputWriter(ctx) + if err != nil { + return xerrors.Errorf("failed to create output file: %w", err) + } + defer cleanup() + + if opt.Compliance.Spec.ID != "" { + return writeCompliance(ctx, rep, opt, output) + } + + ignoreConf, err := result.ParseIgnoreFile(ctx, opt.IgnoreFile) + if err != nil { + return xerrors.Errorf("%s error: %w", opt.IgnoreFile, err) + } + + var filtered []types.Result + + // filter results + for _, resultsAtTime := range rep.Results { + for _, res := range resultsAtTime.Results { + resCopy := res + if err := result.FilterResult(ctx, &resCopy, ignoreConf, opt.FilterOpts()); err != nil { + return err + } + sort.Slice(resCopy.Misconfigurations, func(i, j int) bool { + return resCopy.Misconfigurations[i].CauseMetadata.Resource < resCopy.Misconfigurations[j].CauseMetadata.Resource + }) + filtered = append(filtered, resCopy) + } + } + sort.Slice(filtered, func(i, j int) bool { + return filtered[i].Target < filtered[j].Target + }) + + base := types.Report{ + CreatedAt: clock.Now(ctx), + ArtifactName: rep.AccountID, + ArtifactType: ftypes.ArtifactAWSAccount, + Results: filtered, + } + + switch opt.Format { + case tableFormat: + + // ensure color/formatting is disabled for pipes/non-pty + var useANSI bool + if output == os.Stdout { + if o, err := os.Stdout.Stat(); err == nil { + useANSI = (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice + } + } + if !useANSI { + tml.DisableFormatting() + } + + switch { + case len(opt.Services) == 1 && opt.ARN == "": + if err := writeResourceTable(rep, filtered, output, opt.Services[0]); err != nil { + return err + } + case len(opt.Services) == 1 && opt.ARN != "": + if err := writeResultsForARN(rep, filtered, output, opt.Services[0], opt.ARN, opt.Severities); err != nil { + return err + } + default: + if err := writeServiceTable(rep, filtered, output); err != nil { + return err + } + } + + // render cache info + if fromCache { + _ = tml.Fprintf(output, "\nThis scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache.\n") + } + + return nil + default: + return pkgReport.Write(ctx, base, opt) + } +} + +func writeCompliance(ctx context.Context, rep *Report, opt flag.Options, output io.Writer) error { + var crr []types.Results + for _, r := range rep.Results { + crr = append(crr, r.Results) + } + + complianceReport, err := cr.BuildComplianceReport(crr, opt.Compliance) + if err != nil { + return xerrors.Errorf("compliance report build error: %w", err) + } + + return cr.Write(ctx, complianceReport, cr.Option{ + Format: opt.Format, + Report: opt.ReportFormat, + Output: output, + }) +} diff --git a/pkg/cloud/report/resource.go b/pkg/cloud/report/resource.go new file mode 100644 index 000000000000..79b1b8cc2e94 --- /dev/null +++ b/pkg/cloud/report/resource.go @@ -0,0 +1,88 @@ +package report + +import ( + "fmt" + "io" + "sort" + "strconv" + + "golang.org/x/term" + + "github.com/aquasecurity/table" + "github.com/aquasecurity/tml" + pkgReport "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" +) + +type sortableRow struct { + name string + counts map[string]int +} + +func writeResourceTable(report *Report, results types.Results, output io.Writer, service string) error { + + termWidth, _, err := term.GetSize(0) + if err != nil { + termWidth = 80 + } + maxWidth := termWidth - 48 + if maxWidth < 20 { + maxWidth = 20 + } + + t := table.New(output) + t.SetColumnMaxWidth(maxWidth) + t.SetHeaders("Resource", "Misconfigurations") + t.AddHeaders("Resource", "Critical", "High", "Medium", "Low", "Unknown") + t.SetHeaderVerticalAlignment(table.AlignBottom) + t.SetHeaderAlignment(table.AlignLeft, table.AlignCenter, table.AlignCenter, table.AlignCenter, table.AlignCenter, table.AlignCenter) + t.SetAlignment(table.AlignLeft, table.AlignRight, table.AlignRight, table.AlignRight, table.AlignRight, table.AlignRight) + t.SetRowLines(false) + t.SetAutoMergeHeaders(true) + t.SetHeaderColSpans(0, 1, 5) + + // map resource -> severity -> count + grouped := make(map[string]map[string]int) + for _, result := range results { + for _, misconfiguration := range result.Misconfigurations { + if misconfiguration.CauseMetadata.Service != service { + continue + } + if _, ok := grouped[misconfiguration.CauseMetadata.Resource]; !ok { + grouped[misconfiguration.CauseMetadata.Resource] = make(map[string]int) + } + grouped[misconfiguration.CauseMetadata.Resource][misconfiguration.Severity]++ + } + } + + var sortable []sortableRow + for resource, severityCounts := range grouped { + sortable = append(sortable, sortableRow{ + name: resource, + counts: severityCounts, + }) + } + sort.Slice(sortable, func(i, j int) bool { return sortable[i].name < sortable[j].name }) + for _, row := range sortable { + t.AddRow( + row.name, + pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["CRITICAL"]), "CRITICAL"), + pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["HIGH"]), "HIGH"), + pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["MEDIUM"]), "MEDIUM"), + pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["LOW"]), "LOW"), + pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["UNKNOWN"]), "UNKNOWN"), + ) + } + + // render scan title + _ = tml.Fprintf(output, "\nResource Summary for Service '%s' (%s Account %s)\n", service, report.Provider, report.AccountID) + + // render table + if len(sortable) > 0 { + t.Render() + } else { + _, _ = fmt.Fprint(output, "\nNo problems detected.\n") + } + + return nil +} diff --git a/pkg/cloud/report/resource_test.go b/pkg/cloud/report/resource_test.go new file mode 100644 index 000000000000..3f909b8d3b3f --- /dev/null +++ b/pkg/cloud/report/resource_test.go @@ -0,0 +1,124 @@ +package report + +import ( + "bytes" + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/flag" +) + +func Test_ResourceReport(t *testing.T) { + tests := []struct { + name string + options flag.Options + fromCache bool + expected string + }{ + { + name: "simple table output", + options: flag.Options{ + ReportOptions: flag.ReportOptions{ + Format: tableFormat, + Severities: []types.Severity{ + types.SeverityLow, + types.SeverityMedium, + types.SeverityHigh, + types.SeverityCritical, + }, + }, + AWSOptions: flag.AWSOptions{ + Services: []string{"s3"}, + }, + }, + fromCache: false, + expected: ` +Resource Summary for Service 's3' (AWS Account ) +┌─────────────────────────────────────────┬──────────────────────────────────────────┠+│ │ Misconfigurations │ +│ ├──────────┬──────┬────────┬─────┬─────────┤ +│ Resource │ Critical │ High │ Medium │ Low │ Unknown │ +├─────────────────────────────────────────┼──────────┼──────┼────────┼─────┼─────────┤ +│ arn:aws:s3:us-east-1:1234567890:bucket1 │ 0 │ 1 │ 0 │ 0 │ 0 │ +│ arn:aws:s3:us-east-1:1234567890:bucket2 │ 0 │ 2 │ 0 │ 0 │ 0 │ +└─────────────────────────────────────────┴──────────┴──────┴────────┴─────┴─────────┘ +`, + }, + { + name: "results from cache", + options: flag.Options{ + ReportOptions: flag.ReportOptions{ + Format: tableFormat, + Severities: []types.Severity{ + types.SeverityLow, + types.SeverityMedium, + types.SeverityHigh, + types.SeverityCritical, + }, + }, + AWSOptions: flag.AWSOptions{ + Services: []string{"s3"}, + }, + }, + fromCache: true, + expected: ` +Resource Summary for Service 's3' (AWS Account ) +┌─────────────────────────────────────────┬──────────────────────────────────────────┠+│ │ Misconfigurations │ +│ ├──────────┬──────┬────────┬─────┬─────────┤ +│ Resource │ Critical │ High │ Medium │ Low │ Unknown │ +├─────────────────────────────────────────┼──────────┼──────┼────────┼─────┼─────────┤ +│ arn:aws:s3:us-east-1:1234567890:bucket1 │ 0 │ 1 │ 0 │ 0 │ 0 │ +│ arn:aws:s3:us-east-1:1234567890:bucket2 │ 0 │ 2 │ 0 │ 0 │ 0 │ +└─────────────────────────────────────────┴──────────┴──────┴────────┴─────┴─────────┘ + +This scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache. +`, + }, + { + name: "no problems", + options: flag.Options{ + ReportOptions: flag.ReportOptions{ + Format: tableFormat, + Severities: []types.Severity{ + types.SeverityLow, + }, + }, + AWSOptions: flag.AWSOptions{ + Services: []string{"s3"}, + }, + }, + fromCache: false, + expected: ` +Resource Summary for Service 's3' (AWS Account ) + +No problems detected. +`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + report := New( + "AWS", + tt.options.AWSOptions.Account, + tt.options.AWSOptions.Region, + createTestResults(), + tt.options.AWSOptions.Services, + ) + + output := bytes.NewBuffer(nil) + tt.options.SetOutputWriter(output) + require.NoError(t, Write(context.Background(), report, tt.options, tt.fromCache)) + + assert.Equal(t, "AWS", report.Provider) + assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID) + assert.Equal(t, tt.options.AWSOptions.Region, report.Region) + assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope) + assert.Equal(t, tt.expected, output.String()) + }) + } +} diff --git a/pkg/cloud/report/result.go b/pkg/cloud/report/result.go new file mode 100644 index 000000000000..103be8a40afc --- /dev/null +++ b/pkg/cloud/report/result.go @@ -0,0 +1,35 @@ +package report + +import ( + "fmt" + "io" + + "github.com/aquasecurity/tml" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + renderer "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" +) + +func writeResultsForARN(report *Report, results types.Results, output io.Writer, service, arn string, severities []dbTypes.Severity) error { + + // render scan title + _ = tml.Fprintf(output, "\nResults for '%s' (%s Account %s)\n\n", arn, report.Provider, report.AccountID) + + for _, result := range results { + var filtered []types.DetectedMisconfiguration + for _, misconfiguration := range result.Misconfigurations { + if arn != "" && misconfiguration.CauseMetadata.Resource != arn { + continue + } + if service != "" && misconfiguration.CauseMetadata.Service != service { + continue + } + filtered = append(filtered, misconfiguration) + } + if len(filtered) > 0 { + _, _ = fmt.Fprint(output, renderer.NewMisconfigRenderer(result, severities, false, false, true).Render()) + } + } + + return nil +} diff --git a/pkg/cloud/report/result_test.go b/pkg/cloud/report/result_test.go new file mode 100644 index 000000000000..6afc67305c4f --- /dev/null +++ b/pkg/cloud/report/result_test.go @@ -0,0 +1,83 @@ +package report + +import ( + "bytes" + "context" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/flag" +) + +func Test_ARNReport(t *testing.T) { + tests := []struct { + name string + options flag.Options + fromCache bool + expected string + }{ + { + name: "simple output", + options: flag.Options{ + ReportOptions: flag.ReportOptions{ + Format: tableFormat, + Severities: []types.Severity{ + types.SeverityLow, + types.SeverityMedium, + types.SeverityHigh, + types.SeverityCritical, + }, + }, + AWSOptions: flag.AWSOptions{ + Services: []string{"s3"}, + ARN: "arn:aws:s3:us-east-1:1234567890:bucket1", + Account: "1234567890", + }, + }, + fromCache: false, + expected: ` +Results for 'arn:aws:s3:us-east-1:1234567890:bucket1' (AWS Account 1234567890) + + +arn:aws:s3:us-east-1:1234567890:bucket1 (cloud) + +Tests: 1 (SUCCESSES: 0, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0) + +HIGH: something failed +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Bad stuff is... bad + +See https://avd.aquasec.com/misconfig/avd-aws-9999 +──────────────────────────────────────── + + +`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + report := New( + "AWS", + tt.options.AWSOptions.Account, + tt.options.AWSOptions.Region, + createTestResults(), + tt.options.AWSOptions.Services, + ) + + output := bytes.NewBuffer(nil) + tt.options.SetOutputWriter(output) + require.NoError(t, Write(context.Background(), report, tt.options, tt.fromCache)) + + assert.Equal(t, "AWS", report.Provider) + assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID) + assert.Equal(t, tt.options.AWSOptions.Region, report.Region) + assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope) + assert.Equal(t, tt.expected, strings.ReplaceAll(output.String(), "\r\n", "\n")) + }) + } +} diff --git a/pkg/cloud/report/service.go b/pkg/cloud/report/service.go new file mode 100644 index 000000000000..e25d8ea393f9 --- /dev/null +++ b/pkg/cloud/report/service.go @@ -0,0 +1,85 @@ +package report + +import ( + "fmt" + "io" + "sort" + "strconv" + "time" + + "github.com/aquasecurity/table" + "github.com/aquasecurity/tml" + pkgReport "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" +) + +func writeServiceTable(report *Report, results types.Results, output io.Writer) error { + + t := table.New(output) + + t.SetHeaders("Service", "Misconfigurations", "Last Scanned") + t.AddHeaders("Service", "Critical", "High", "Medium", "Low", "Unknown", "Last Scanned") + t.SetRowLines(false) + t.SetHeaderVerticalAlignment(table.AlignBottom) + t.SetHeaderAlignment(table.AlignLeft, table.AlignCenter, table.AlignCenter, table.AlignCenter, table.AlignCenter, table.AlignCenter, table.AlignLeft) + t.SetAlignment(table.AlignLeft, table.AlignRight, table.AlignRight, table.AlignRight, table.AlignRight, table.AlignRight, table.AlignLeft) + t.SetAutoMergeHeaders(true) + t.SetHeaderColSpans(0, 1, 5, 1) + + // map service -> severity -> count + grouped := make(map[string]map[string]int) + // set zero counts for all services + for _, service := range report.ServicesInScope { + grouped[service] = make(map[string]int) + } + for _, result := range results { + for _, misconfiguration := range result.Misconfigurations { + service := misconfiguration.CauseMetadata.Service + if _, ok := grouped[service]; !ok { + grouped[service] = make(map[string]int) + } + grouped[service][misconfiguration.Severity]++ + } + } + + var sortable []sortableRow + for service, severityCounts := range grouped { + sortable = append(sortable, sortableRow{ + name: service, + counts: severityCounts, + }) + } + sort.Slice(sortable, func(i, j int) bool { return sortable[i].name < sortable[j].name }) + for _, row := range sortable { + var lastScanned string + scanAgo := time.Since(report.Results[row.name].CreationTime).Truncate(time.Minute) + switch { + case scanAgo.Hours() >= 48: + lastScanned = fmt.Sprintf("%d days ago", int(scanAgo.Hours()/24)) + case scanAgo.Hours() > 1: + lastScanned = fmt.Sprintf("%d hours ago", int(scanAgo.Hours())) + case scanAgo.Minutes() > 1: + lastScanned = fmt.Sprintf("%d minutes ago", int(scanAgo.Minutes())) + default: + lastScanned = "just now" + } + + t.AddRow( + row.name, + pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["CRITICAL"]), "CRITICAL"), + pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["HIGH"]), "HIGH"), + pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["MEDIUM"]), "MEDIUM"), + pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["LOW"]), "LOW"), + pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["UNKNOWN"]), "UNKNOWN"), + lastScanned, + ) + } + + // render scan title + _ = tml.Fprintf(output, "\nScan Overview for %s Account %s\n", report.Provider, report.AccountID) + + // render table + t.Render() + + return nil +} diff --git a/pkg/cloud/report/service_test.go b/pkg/cloud/report/service_test.go new file mode 100644 index 000000000000..12c998e23913 --- /dev/null +++ b/pkg/cloud/report/service_test.go @@ -0,0 +1,421 @@ +package report + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/aquasecurity/trivy/pkg/clock" + + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/iac/scan" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Test_ServiceReport(t *testing.T) { + tests := []struct { + name string + options flag.Options + fromCache bool + expected string + }{ + { + name: "simple table output", + options: flag.Options{ + ReportOptions: flag.ReportOptions{ + Format: tableFormat, + Severities: []types.Severity{ + types.SeverityLow, + types.SeverityMedium, + types.SeverityHigh, + types.SeverityCritical, + }, + }, + }, + fromCache: false, + expected: ` +Scan Overview for AWS Account +┌─────────┬──────────────────────────────────────────────────┬──────────────┠+│ │ Misconfigurations │ │ +│ ├──────────┬──────────────┬────────┬─────┬─────────┤ │ +│ Service │ Critical │ High │ Medium │ Low │ Unknown │ Last Scanned │ +├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤ +│ ec2 │ 0 │ 1 │ 0 │ 0 │ 0 │ just now │ +│ s3 │ 0 │ 3 │ 0 │ 0 │ 0 │ just now │ +└─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘ +`, + }, + { + name: "results from cache", + options: flag.Options{ + ReportOptions: flag.ReportOptions{ + Format: tableFormat, + Severities: []types.Severity{ + types.SeverityLow, + types.SeverityMedium, + types.SeverityHigh, + types.SeverityCritical, + }, + }, + }, + fromCache: true, + expected: ` +Scan Overview for AWS Account +┌─────────┬──────────────────────────────────────────────────┬──────────────┠+│ │ Misconfigurations │ │ +│ ├──────────┬──────────────┬────────┬─────┬─────────┤ │ +│ Service │ Critical │ High │ Medium │ Low │ Unknown │ Last Scanned │ +├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤ +│ ec2 │ 0 │ 1 │ 0 │ 0 │ 0 │ just now │ +│ s3 │ 0 │ 3 │ 0 │ 0 │ 0 │ just now │ +└─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘ + +This scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache. +`, + }, + { + name: "filter severities", + options: flag.Options{ + ReportOptions: flag.ReportOptions{ + Format: tableFormat, + Severities: []types.Severity{ + types.SeverityMedium, + }, + }, + AWSOptions: flag.AWSOptions{ + Services: []string{ + "s3", + "ec2", + }, + }, + }, + fromCache: false, + expected: ` +Scan Overview for AWS Account +┌─────────┬──────────────────────────────────────────────────┬──────────────┠+│ │ Misconfigurations │ │ +│ ├──────────┬──────────────┬────────┬─────┬─────────┤ │ +│ Service │ Critical │ High │ Medium │ Low │ Unknown │ Last Scanned │ +├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤ +│ ec2 │ 0 │ 0 │ 0 │ 0 │ 0 │ just now │ +│ s3 │ 0 │ 0 │ 0 │ 0 │ 0 │ just now │ +└─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘ +`, + }, + { + name: "scoped services without results", + options: flag.Options{ + ReportOptions: flag.ReportOptions{ + Format: tableFormat, + Severities: []types.Severity{ + types.SeverityLow, + types.SeverityMedium, + types.SeverityHigh, + types.SeverityCritical, + }, + }, + AWSOptions: flag.AWSOptions{ + Services: []string{ + "ec2", + "s3", + "iam", + }, + }, + }, + fromCache: false, + expected: ` +Scan Overview for AWS Account +┌─────────┬──────────────────────────────────────────────────┬──────────────┠+│ │ Misconfigurations │ │ +│ ├──────────┬──────────────┬────────┬─────┬─────────┤ │ +│ Service │ Critical │ High │ Medium │ Low │ Unknown │ Last Scanned │ +├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤ +│ ec2 │ 0 │ 1 │ 0 │ 0 │ 0 │ just now │ +│ iam │ 0 │ 0 │ 0 │ 0 │ 0 │ just now │ +│ s3 │ 0 │ 3 │ 0 │ 0 │ 0 │ just now │ +└─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘ +`, + }, + { + name: "json output", + options: flag.Options{ + ReportOptions: flag.ReportOptions{ + Format: "json", + Severities: []types.Severity{ + types.SeverityLow, + types.SeverityMedium, + types.SeverityHigh, + types.SeverityCritical, + }, + }, + }, + fromCache: false, + expected: `{ + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactType": "aws_account", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "arn:aws:ec2:us-east-1:1234567890:instance1", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 0, + "Failures": 1, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "ID": "AVD-AWS-9999", + "AVDID": "AVD-AWS-9999", + "Title": "Do not use bad stuff", + "Description": "Bad stuff is... bad", + "Message": "instance is bad", + "Resolution": "Remove bad stuff", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-9999" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:ec2:us-east-1:1234567890:instance1", + "Provider": "AWS", + "Service": "ec2", + "Code": { + "Lines": null + } + } + } + ] + }, + { + "Target": "arn:aws:s3:us-east-1:1234567890:bucket1", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 0, + "Failures": 1, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "ID": "AVD-AWS-9999", + "AVDID": "AVD-AWS-9999", + "Title": "Do not use bad stuff", + "Description": "Bad stuff is... bad", + "Message": "something failed", + "Resolution": "Remove bad stuff", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-9999" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:us-east-1:1234567890:bucket1", + "Provider": "AWS", + "Service": "s3", + "Code": { + "Lines": null + } + } + } + ] + }, + { + "Target": "arn:aws:s3:us-east-1:1234567890:bucket2", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 0, + "Failures": 2, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "ID": "AVD-AWS-9999", + "AVDID": "AVD-AWS-9999", + "Title": "Do not use bad stuff", + "Description": "Bad stuff is... bad", + "Message": "something else failed", + "Resolution": "Remove bad stuff", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-9999" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:us-east-1:1234567890:bucket2", + "Provider": "AWS", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-9999", + "AVDID": "AVD-AWS-9999", + "Title": "Do not use bad stuff", + "Description": "Bad stuff is... bad", + "Message": "something else failed again", + "Resolution": "Remove bad stuff", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-9999" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:us-east-1:1234567890:bucket2", + "Provider": "AWS", + "Service": "s3", + "Code": { + "Lines": null + } + } + } + ] + }, + { + "Target": "arn:aws:s3:us-east-1:1234567890:bucket3", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 1, + "Failures": 0, + "Exceptions": 0 + } + } + ] +}`, + }, + } + ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + report := New( + "AWS", + tt.options.AWSOptions.Account, + tt.options.AWSOptions.Region, + createTestResults(), + tt.options.AWSOptions.Services, + ) + + output := bytes.NewBuffer(nil) + tt.options.SetOutputWriter(output) + require.NoError(t, Write(ctx, report, tt.options, tt.fromCache)) + + assert.Equal(t, "AWS", report.Provider) + assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID) + assert.Equal(t, tt.options.AWSOptions.Region, report.Region) + assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope) + + if tt.options.Format == "json" { + // json output can be formatted/ordered differently - we just care that the data matches + assert.JSONEq(t, tt.expected, output.String()) + } else { + assert.Equal(t, tt.expected, output.String()) + } + }) + } +} + +func createTestResults() scan.Results { + + baseRule := scan.Rule{ + AVDID: "AVD-AWS-9999", + Aliases: []string{"AWS999"}, + ShortCode: "no-bad-stuff", + Summary: "Do not use bad stuff", + Explanation: "Bad stuff is... bad", + Impact: "Bad things", + Resolution: "Remove bad stuff", + Provider: "AWS", + Severity: "HIGH", + } + + var s3Results scan.Results + s3Results.Add( + "something failed", + iacTypes.NewRemoteMetadata((arn.ARN{ + Partition: "aws", + Service: "s3", + Region: "us-east-1", + AccountID: "1234567890", + Resource: "bucket1", + }).String()), + ) + s3Results.Add( + "something else failed", + iacTypes.NewRemoteMetadata((arn.ARN{ + Partition: "aws", + Service: "s3", + Region: "us-east-1", + AccountID: "1234567890", + Resource: "bucket2", + }).String()), + ) + s3Results.Add( + "something else failed again", + iacTypes.NewRemoteMetadata((arn.ARN{ + Partition: "aws", + Service: "s3", + Region: "us-east-1", + AccountID: "1234567890", + Resource: "bucket2", + }).String()), + ) + s3Results.AddPassed( + iacTypes.NewRemoteMetadata((arn.ARN{ + Partition: "aws", + Service: "s3", + Region: "us-east-1", + AccountID: "1234567890", + Resource: "bucket3", + }).String()), + ) + baseRule.Service = "s3" + s3Results.SetRule(baseRule) + var ec2Results scan.Results + ec2Results.Add( + "instance is bad", + iacTypes.NewRemoteMetadata((arn.ARN{ + Partition: "aws", + Service: "ec2", + Region: "us-east-1", + AccountID: "1234567890", + Resource: "instance1", + }).String()), + ) + baseRule.Service = "ec2" + ec2Results.SetRule(baseRule) + return append(s3Results, ec2Results...) +} diff --git a/pkg/commands/app.go b/pkg/commands/app.go new file mode 100644 index 000000000000..41d1d2ff645d --- /dev/null +++ b/pkg/commands/app.go @@ -0,0 +1,1267 @@ +package commands + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + "sort" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/xerrors" + + awsScanner "github.com/aquasecurity/trivy-aws/pkg/scanner" + awscommands "github.com/aquasecurity/trivy/pkg/cloud/aws/commands" + "github.com/aquasecurity/trivy/pkg/commands/artifact" + "github.com/aquasecurity/trivy/pkg/commands/convert" + "github.com/aquasecurity/trivy/pkg/commands/server" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/flag" + k8scommands "github.com/aquasecurity/trivy/pkg/k8s/commands" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/module" + "github.com/aquasecurity/trivy/pkg/plugin" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/version" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" +) + +const ( + usageTemplate = `Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: +{{.Example}}{{end}}{{if .HasAvailableSubCommands}} + +Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + +%s + +Global Flags: +{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} + +Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +` + + groupScanning = "scanning" + groupManagement = "management" + groupUtility = "utility" + groupPlugin = "plugin" +) + +// NewApp is the factory method to return Trivy CLI +func NewApp() *cobra.Command { + globalFlags := flag.NewGlobalFlagGroup() + rootCmd := NewRootCommand(globalFlags) + rootCmd.AddGroup( + &cobra.Group{ + ID: groupScanning, + Title: "Scanning Commands", + }, + &cobra.Group{ + ID: groupManagement, + Title: "Management Commands", + }, + &cobra.Group{ + ID: groupUtility, + Title: "Utility Commands", + }, + ) + rootCmd.SetCompletionCommandGroupID(groupUtility) + rootCmd.SetHelpCommandGroupID(groupUtility) + rootCmd.AddCommand( + NewImageCommand(globalFlags), + NewFilesystemCommand(globalFlags), + NewRootfsCommand(globalFlags), + NewRepositoryCommand(globalFlags), + NewClientCommand(globalFlags), + NewServerCommand(globalFlags), + NewConfigCommand(globalFlags), + NewConvertCommand(globalFlags), + NewPluginCommand(), + NewModuleCommand(globalFlags), + NewKubernetesCommand(globalFlags), + NewSBOMCommand(globalFlags), + NewVersionCommand(globalFlags), + NewAWSCommand(globalFlags), + NewVMCommand(globalFlags), + ) + + if plugins := loadPluginCommands(); len(plugins) > 0 { + rootCmd.AddGroup(&cobra.Group{ + ID: groupPlugin, + Title: "Plugin Commands", + }) + rootCmd.AddCommand(plugins...) + } + + return rootCmd +} + +func loadPluginCommands() []*cobra.Command { + var commands []*cobra.Command + plugins, err := plugin.LoadAll() + if err != nil { + log.Logger.Debugf("no plugins were loaded") + return nil + } + for _, p := range plugins { + p := p + cmd := &cobra.Command{ + Use: fmt.Sprintf("%s [flags]", p.Name), + Short: p.Usage, + GroupID: groupPlugin, + RunE: func(cmd *cobra.Command, args []string) error { + if err = p.Run(cmd.Context(), plugin.RunOptions{Args: args}); err != nil { + return xerrors.Errorf("plugin error: %w", err) + } + return nil + }, + DisableFlagParsing: true, + } + commands = append(commands, cmd) + } + return commands +} + +func initConfig(configFile string) error { + // Read from config + viper.SetConfigFile(configFile) + viper.SetConfigType("yaml") + if err := viper.ReadInConfig(); err != nil { + if errors.Is(err, os.ErrNotExist) { + log.Logger.Debugf("config file %q not found", configFile) + return nil + } + return xerrors.Errorf("config file %q loading error: %s", configFile, err) + } + log.Logger.Infof("Loaded %s", configFile) + return nil +} + +func NewRootCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + var versionFormat string + cmd := &cobra.Command{ + Use: "trivy [global flags] command [flags] target", + Short: "Unified security scanner", + Long: "Scanner for vulnerabilities in container images, file systems, and Git repositories, as well as for configuration issues and hard-coded secrets", + Example: ` # Scan a container image + $ trivy image python:3.4-alpine + + # Scan a container image from a tar archive + $ trivy image --input ruby-3.1.tar + + # Scan local filesystem + $ trivy fs . + + # Run in server mode + $ trivy server`, + Args: cobra.NoArgs, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // Set the Trivy version here so that we can override version printer. + cmd.Version = version.AppVersion() + + // viper.BindPFlag cannot be called in init(). + // cf. https://github.com/spf13/cobra/issues/875 + // https://github.com/spf13/viper/issues/233 + if err := globalFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + + // The config path is needed for config initialization. + // It needs to be obtained before ToOptions(). + configPath := viper.GetString(flag.ConfigFileFlag.ConfigName) + + // Configure environment variables and config file + // It cannot be called in init() because it must be called after viper.BindPFlags. + if err := initConfig(configPath); err != nil { + return err + } + + globalOptions, err := globalFlags.ToOptions() + if err != nil { + return err + } + + // Initialize logger + if err := log.InitLogger(globalOptions.Debug, globalOptions.Quiet); err != nil { + return err + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + globalOptions, err := globalFlags.ToOptions() + if err != nil { + return err + } + + if globalOptions.ShowVersion { + // Customize version output + return showVersion(globalOptions.CacheDir, versionFormat, cmd.OutOrStdout()) + } else { + return cmd.Help() + } + }, + } + + // Add version format flag, only json is supported + cmd.Flags().StringVarP(&versionFormat, flag.FormatFlag.Name, flag.FormatFlag.Shorthand, "", "version format (json)") + + globalFlags.AddFlags(cmd) + + return cmd +} + +func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + scanFlagGroup := flag.NewScanFlagGroup() + scanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + + reportFlagGroup := flag.NewReportFlagGroup() + report := flag.ReportFormatFlag.Clone() + report.Default = "summary" // override the default value as the summary is preferred for the compliance report + report.Usage = "specify a format for the compliance report." // "--report" works only with "--compliance" + reportFlagGroup.ReportFormat = report + + compliance := flag.ComplianceFlag.Clone() + compliance.Values = []string{types.ComplianceDockerCIS} + reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. + + misconfFlagGroup := flag.NewMisconfFlagGroup() + misconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' + misconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' + + imageFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CacheFlagGroup: flag.NewCacheFlagGroup(), + DBFlagGroup: flag.NewDBFlagGroup(), + ImageFlagGroup: flag.NewImageFlagGroup(), // container image specific + LicenseFlagGroup: flag.NewLicenseFlagGroup(), + MisconfFlagGroup: misconfFlagGroup, + ModuleFlagGroup: flag.NewModuleFlagGroup(), + RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode + RegistryFlagGroup: flag.NewRegistryFlagGroup(), + RegoFlagGroup: flag.NewRegoFlagGroup(), + ReportFlagGroup: reportFlagGroup, + ScanFlagGroup: scanFlagGroup, + SecretFlagGroup: flag.NewSecretFlagGroup(), + VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + } + + cmd := &cobra.Command{ + Use: "image [flags] IMAGE_NAME", + Aliases: []string{"i"}, + GroupID: groupScanning, + Short: "Scan a container image", + Example: ` # Scan a container image + $ trivy image python:3.4-alpine + + # Scan a container image from a tar archive + $ trivy image --input ruby-3.1.tar + + # Filter by severities + $ trivy image --severity HIGH,CRITICAL alpine:3.15 + + # Ignore unfixed/unpatched vulnerabilities + $ trivy image --ignore-unfixed alpine:3.15 + + # Scan a container image in client mode + $ trivy image --server http://127.0.0.1:4954 alpine:latest + + # Generate json result + $ trivy image --format json --output result.json alpine:3.15 + + # Generate a report in the CycloneDX format + $ trivy image --format cyclonedx --output result.cdx alpine:3.15`, + + // 'Args' cannot be used since it is called before PreRunE and viper is not configured yet. + // cmd.Args -> cannot validate args here + // cmd.PreRunE -> configure viper && validate args + // cmd.RunE -> run the command + PreRunE: func(cmd *cobra.Command, args []string) error { + // viper.BindPFlag cannot be called in init(), so it is called in PreRunE. + // cf. https://github.com/spf13/cobra/issues/875 + // https://github.com/spf13/viper/issues/233 + if err := imageFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + return validateArgs(cmd, args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + options, err := imageFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + return artifact.Run(cmd.Context(), options, artifact.TargetContainerImage) + }, + SilenceErrors: true, + SilenceUsage: true, + } + + imageFlags.AddFlags(cmd) + cmd.SetFlagErrorFunc(flagErrorFunc) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, imageFlags.Usages(cmd))) + + return cmd +} + +func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + reportFlagGroup := flag.NewReportFlagGroup() + reportFormat := flag.ReportFormatFlag.Clone() + reportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports + reportFlagGroup.ReportFormat = reportFormat + reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + + fsFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CacheFlagGroup: flag.NewCacheFlagGroup(), + DBFlagGroup: flag.NewDBFlagGroup(), + LicenseFlagGroup: flag.NewLicenseFlagGroup(), + MisconfFlagGroup: flag.NewMisconfFlagGroup(), + ModuleFlagGroup: flag.NewModuleFlagGroup(), + RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode + RegistryFlagGroup: flag.NewRegistryFlagGroup(), + RegoFlagGroup: flag.NewRegoFlagGroup(), + ReportFlagGroup: reportFlagGroup, + ScanFlagGroup: flag.NewScanFlagGroup(), + SecretFlagGroup: flag.NewSecretFlagGroup(), + VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + } + + cmd := &cobra.Command{ + Use: "filesystem [flags] PATH", + Aliases: []string{"fs"}, + GroupID: groupScanning, + Short: "Scan local filesystem", + Example: ` # Scan a local project including language-specific files + $ trivy fs /path/to/your_project + + # Scan a single file + $ trivy fs ./trivy-ci-test/Pipfile.lock`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := fsFlags.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 := fsFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + options, err := fsFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + return artifact.Run(cmd.Context(), options, artifact.TargetFilesystem) + }, + SilenceErrors: true, + SilenceUsage: true, + } + + cmd.SetFlagErrorFunc(flagErrorFunc) + fsFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, fsFlags.Usages(cmd))) + + return cmd +} + +func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + rootfsFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CacheFlagGroup: flag.NewCacheFlagGroup(), + DBFlagGroup: flag.NewDBFlagGroup(), + LicenseFlagGroup: flag.NewLicenseFlagGroup(), + MisconfFlagGroup: flag.NewMisconfFlagGroup(), + ModuleFlagGroup: flag.NewModuleFlagGroup(), + RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode + RegistryFlagGroup: flag.NewRegistryFlagGroup(), + RegoFlagGroup: flag.NewRegoFlagGroup(), + ReportFlagGroup: flag.NewReportFlagGroup(), + ScanFlagGroup: flag.NewScanFlagGroup(), + SecretFlagGroup: flag.NewSecretFlagGroup(), + VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + } + rootfsFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary + rootfsFlags.ReportFlagGroup.Compliance = nil // disable '--compliance' + rootfsFlags.ReportFlagGroup.ReportFormat = nil // disable '--report' + rootfsFlags.ScanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + + cmd := &cobra.Command{ + Use: "rootfs [flags] ROOTDIR", + Short: "Scan rootfs", + GroupID: groupScanning, + Example: ` # Scan unpacked filesystem + $ docker export $(docker create alpine:3.10.2) | tar -C /tmp/rootfs -xvf - + $ trivy rootfs /tmp/rootfs + + # Scan from inside a container + $ docker run --rm -it alpine:3.11 + / # curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin + / # trivy rootfs /`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := rootfsFlags.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 := rootfsFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + options, err := rootfsFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + return artifact.Run(cmd.Context(), options, artifact.TargetRootfs) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + rootfsFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, rootfsFlags.Usages(cmd))) + + return cmd +} + +func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + repoFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CacheFlagGroup: flag.NewCacheFlagGroup(), + DBFlagGroup: flag.NewDBFlagGroup(), + LicenseFlagGroup: flag.NewLicenseFlagGroup(), + MisconfFlagGroup: flag.NewMisconfFlagGroup(), + ModuleFlagGroup: flag.NewModuleFlagGroup(), + RegistryFlagGroup: flag.NewRegistryFlagGroup(), + RegoFlagGroup: flag.NewRegoFlagGroup(), + RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode + ReportFlagGroup: flag.NewReportFlagGroup(), + ScanFlagGroup: flag.NewScanFlagGroup(), + SecretFlagGroup: flag.NewSecretFlagGroup(), + VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + RepoFlagGroup: flag.NewRepoFlagGroup(), + } + repoFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary + repoFlags.ReportFlagGroup.Compliance = nil // disable '--compliance' + repoFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + + cmd := &cobra.Command{ + Use: "repository [flags] (REPO_PATH | REPO_URL)", + Aliases: []string{"repo"}, + GroupID: groupScanning, + Short: "Scan a repository", + Example: ` # Scan your remote git repository + $ trivy repo https://github.com/knqyf263/trivy-ci-test + # Scan your local git repository + $ trivy repo /path/to/your/repository`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := repoFlags.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 := repoFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + options, err := repoFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + return artifact.Run(cmd.Context(), options, artifact.TargetRepository) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + repoFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, repoFlags.Usages(cmd))) + + return cmd +} + +func NewConvertCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + convertFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + ScanFlagGroup: &flag.ScanFlagGroup{}, + ReportFlagGroup: flag.NewReportFlagGroup(), + } + cmd := &cobra.Command{ + Use: "convert [flags] RESULT_JSON", + Aliases: []string{"conv"}, + GroupID: groupUtility, + Short: "Convert Trivy JSON report into a different format", + Example: ` # report conversion + $ trivy image --format json --output result.json --list-all-pkgs debian:11 + $ trivy convert --format cyclonedx --output result.cdx result.json +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := convertFlags.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 := convertFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + opts, err := convertFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + + return convert.Run(cmd.Context(), opts) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + convertFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, convertFlags.Usages(cmd))) + + return cmd +} + +// NewClientCommand returns the 'client' subcommand that is deprecated +func NewClientCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + remoteFlags := flag.NewClientFlags() + remoteAddr := flag.Flag[string]{ + Name: "remote", + ConfigName: "server.addr", + Shorthand: "", + Default: "http://localhost:4954", + Usage: "server address", + } + remoteFlags.ServerAddr = &remoteAddr // disable '--server' and enable '--remote' instead. + + clientFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CacheFlagGroup: flag.NewCacheFlagGroup(), + DBFlagGroup: flag.NewDBFlagGroup(), + MisconfFlagGroup: flag.NewMisconfFlagGroup(), + RegistryFlagGroup: flag.NewRegistryFlagGroup(), + RegoFlagGroup: flag.NewRegoFlagGroup(), + RemoteFlagGroup: remoteFlags, + ReportFlagGroup: flag.NewReportFlagGroup(), + ScanFlagGroup: flag.NewScanFlagGroup(), + VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + } + + cmd := &cobra.Command{ + Use: "client [flags] IMAGE_NAME", + Aliases: []string{"c"}, + Hidden: true, // 'client' command is deprecated + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := clientFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + return validateArgs(cmd, args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + log.Logger.Warn("'client' subcommand is deprecated now. See https://github.com/aquasecurity/trivy/discussions/2119") + + if err := clientFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + options, err := clientFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + return artifact.Run(cmd.Context(), options, artifact.TargetContainerImage) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + clientFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, clientFlags.Usages(cmd))) + + return cmd +} + +func NewServerCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + serverFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CacheFlagGroup: flag.NewCacheFlagGroup(), + DBFlagGroup: flag.NewDBFlagGroup(), + ModuleFlagGroup: flag.NewModuleFlagGroup(), + RemoteFlagGroup: flag.NewServerFlags(), + RegistryFlagGroup: flag.NewRegistryFlagGroup(), + } + + // java-db only works on client side. + serverFlags.DBFlagGroup.DownloadJavaDBOnly = nil // disable '--download-java-db-only' + serverFlags.DBFlagGroup.SkipJavaDBUpdate = nil // disable '--skip-java-db-update' + serverFlags.DBFlagGroup.JavaDBRepository = nil // disable '--java-db-repository' + + cmd := &cobra.Command{ + Use: "server [flags]", + Aliases: []string{"s"}, + GroupID: groupUtility, + Short: "Server mode", + Example: ` # Run a server + $ trivy server + + # Listen on 0.0.0.0:10000 + $ trivy server --listen 0.0.0.0:10000 +`, + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + if err := serverFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + options, err := serverFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + return server.Run(cmd.Context(), options) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + serverFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, serverFlags.Usages(cmd))) + + return cmd +} + +func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + reportFlagGroup := flag.NewReportFlagGroup() + reportFlagGroup.DependencyTree = nil // disable '--dependency-tree' + reportFlagGroup.ListAllPkgs = nil // disable '--list-all-pkgs' + reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + reportFlagGroup.ShowSuppressed = nil // disable '--show-suppressed' + reportFormat := flag.ReportFormatFlag.Clone() + reportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports + reportFlagGroup.ReportFormat = reportFormat + + scanFlags := &flag.ScanFlagGroup{ + // Enable only '--skip-dirs' and '--skip-files' and disable other flags + SkipDirs: flag.SkipDirsFlag.Clone(), + SkipFiles: flag.SkipFilesFlag.Clone(), + FilePatterns: flag.FilePatternsFlag.Clone(), + } + + configFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CacheFlagGroup: flag.NewCacheFlagGroup(), + MisconfFlagGroup: flag.NewMisconfFlagGroup(), + ModuleFlagGroup: flag.NewModuleFlagGroup(), + RegistryFlagGroup: flag.NewRegistryFlagGroup(), + RegoFlagGroup: flag.NewRegoFlagGroup(), + K8sFlagGroup: &flag.K8sFlagGroup{ + // disable unneeded flags + K8sVersion: flag.K8sVersionFlag.Clone(), + }, + ReportFlagGroup: reportFlagGroup, + ScanFlagGroup: scanFlags, + } + + cmd := &cobra.Command{ + Use: "config [flags] DIR", + Aliases: []string{"conf"}, + GroupID: groupScanning, + Short: "Scan config files for misconfigurations", + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := configFlags.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 := configFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + options, err := configFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + + // Disable OS and language analyzers + options.DisabledAnalyzers = append(analyzer.TypeOSes, analyzer.TypeLanguages...) + + // Scan only for misconfigurations + options.Scanners = types.Scanners{types.MisconfigScanner} + + return artifact.Run(cmd.Context(), options, artifact.TargetFilesystem) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + configFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, configFlags.Usages(cmd))) + + return cmd +} + +func NewPluginCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "plugin subcommand", + Aliases: []string{"p"}, + GroupID: groupManagement, + Short: "Manage plugins", + SilenceErrors: true, + SilenceUsage: true, + } + cmd.AddCommand( + &cobra.Command{ + Use: "install URL | FILE_PATH", + Aliases: []string{"i"}, + Short: "Install a plugin", + SilenceErrors: true, + DisableFlagsInUseLine: true, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if _, err := plugin.Install(cmd.Context(), args[0], true); err != nil { + return xerrors.Errorf("plugin install error: %w", err) + } + return nil + }, + }, + &cobra.Command{ + Use: "uninstall PLUGIN_NAME", + Aliases: []string{"u"}, + SilenceErrors: true, + DisableFlagsInUseLine: true, + Short: "Uninstall a plugin", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + if err := plugin.Uninstall(args[0]); err != nil { + return xerrors.Errorf("plugin uninstall error: %w", err) + } + return nil + }, + }, + &cobra.Command{ + Use: "list", + Aliases: []string{"l"}, + SilenceErrors: true, + DisableFlagsInUseLine: true, + Short: "List installed plugin", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + info, err := plugin.List() + if err != nil { + return xerrors.Errorf("plugin list display error: %w", err) + } + if _, err := fmt.Fprint(os.Stdout, info); err != nil { + return xerrors.Errorf("print error: %w", err) + } + return nil + }, + }, + &cobra.Command{ + Use: "info PLUGIN_NAME", + Short: "Show information about the specified plugin", + SilenceErrors: true, + DisableFlagsInUseLine: true, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + info, err := plugin.Information(args[0]) + if err != nil { + return xerrors.Errorf("plugin information display error: %w", err) + } + if _, err := fmt.Fprint(os.Stdout, info); err != nil { + return xerrors.Errorf("print error: %w", err) + } + return nil + }, + }, + &cobra.Command{ + Use: "run URL | FILE_PATH", + Aliases: []string{"r"}, + SilenceErrors: true, + DisableFlagsInUseLine: true, + Short: "Run a plugin on the fly", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return plugin.RunWithURL(cmd.Context(), args[0], plugin.RunOptions{Args: args[1:]}) + }, + }, + &cobra.Command{ + Use: "update PLUGIN_NAME", + Short: "Update an existing plugin", + SilenceErrors: true, + DisableFlagsInUseLine: true, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + if err := plugin.Update(args[0]); err != nil { + return xerrors.Errorf("plugin update error: %w", err) + } + return nil + }, + }, + ) + cmd.SetFlagErrorFunc(flagErrorFunc) + return cmd +} + +func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + moduleFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + ModuleFlagGroup: flag.NewModuleFlagGroup(), + } + + cmd := &cobra.Command{ + Use: "module subcommand", + Aliases: []string{"m"}, + GroupID: groupManagement, + Short: "Manage modules", + SilenceErrors: true, + SilenceUsage: true, + } + + // Add subcommands + cmd.AddCommand( + &cobra.Command{ + Use: "install [flags] REPOSITORY", + Aliases: []string{"i"}, + Short: "Install a module", + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := moduleFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return cmd.Help() + } + + repo := args[0] + opts, err := moduleFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + return module.Install(cmd.Context(), opts.ModuleDir, repo, opts.Quiet, opts.RegistryOpts()) + }, + }, + &cobra.Command{ + Use: "uninstall [flags] REPOSITORY", + Aliases: []string{"u"}, + Short: "Uninstall a module", + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := moduleFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return cmd.Help() + } + + repo := args[0] + opts, err := moduleFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + return module.Uninstall(cmd.Context(), opts.ModuleDir, repo) + }, + }, + ) + moduleFlags.AddFlags(cmd) + cmd.SetFlagErrorFunc(flagErrorFunc) + return cmd +} + +func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + scanFlags := flag.NewScanFlagGroup() + scanners := flag.ScannersFlag.Clone() + // overwrite the default scanners + scanners.Values = xstrings.ToStringSlice(types.Scanners{ + types.VulnerabilityScanner, + types.MisconfigScanner, + types.SecretScanner, + types.RBACScanner, + }) + scanners.Default = scanners.Values + scanFlags.Scanners = scanners + scanFlags.IncludeDevDeps = nil // disable '--include-dev-deps' + + // required only SourceFlag + imageFlags := &flag.ImageFlagGroup{ImageSources: flag.SourceFlag.Clone()} + + reportFlagGroup := flag.NewReportFlagGroup() + compliance := flag.ComplianceFlag.Clone() + compliance.Values = []string{ + types.ComplianceK8sNsa, + types.ComplianceK8sCIS, + types.ComplianceK8sPSSBaseline, + types.ComplianceK8sPSSRestricted, + } + reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. + reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + + formatFlag := flag.FormatFlag.Clone() + formatFlag.Values = xstrings.ToStringSlice([]types.Format{ + types.FormatTable, + types.FormatJSON, + types.FormatCycloneDX, + }) + reportFlagGroup.Format = formatFlag + + misconfFlagGroup := flag.NewMisconfFlagGroup() + misconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' + misconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' + + k8sFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CacheFlagGroup: flag.NewCacheFlagGroup(), + DBFlagGroup: flag.NewDBFlagGroup(), + ImageFlagGroup: imageFlags, + K8sFlagGroup: flag.NewK8sFlagGroup(), // kubernetes-specific flags + MisconfFlagGroup: misconfFlagGroup, + RegoFlagGroup: flag.NewRegoFlagGroup(), + ReportFlagGroup: reportFlagGroup, + ScanFlagGroup: scanFlags, + SecretFlagGroup: flag.NewSecretFlagGroup(), + RegistryFlagGroup: flag.NewRegistryFlagGroup(), + VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + } + cmd := &cobra.Command{ + Use: "kubernetes [flags] { cluster | all | specific resources like kubectl. eg: pods, pod/NAME }", + Aliases: []string{"k8s"}, + GroupID: groupScanning, + Short: "[EXPERIMENTAL] Scan kubernetes cluster", + Example: ` # cluster scanning + $ trivy k8s --report summary cluster + + # namespace scanning: + $ trivy k8s -n kube-system --report summary all + + # resources scanning: + $ trivy k8s --report=summary deploy + $ trivy k8s --namespace=kube-system --report=summary deploy,configmaps + + # resource scanning: + $ trivy k8s deployment/orion +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := k8sFlags.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 := k8sFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + opts, err := k8sFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + + return k8scommands.Run(cmd.Context(), args, opts) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + k8sFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, k8sFlags.Usages(cmd))) + + return cmd +} + +func NewAWSCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + reportFlagGroup := flag.NewReportFlagGroup() + compliance := flag.ComplianceFlag + compliance.Values = []string{ + types.ComplianceAWSCIS12, + types.ComplianceAWSCIS14, + } + reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand. + reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + reportFlagGroup.ShowSuppressed = nil // disable '--show-suppressed' + + awsFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + AWSFlagGroup: flag.NewAWSFlagGroup(), + CloudFlagGroup: flag.NewCloudFlagGroup(), + MisconfFlagGroup: flag.NewMisconfFlagGroup(), + RegoFlagGroup: flag.NewRegoFlagGroup(), + ReportFlagGroup: reportFlagGroup, + } + + services := awsScanner.AllSupportedServices() + sort.Strings(services) + + cmd := &cobra.Command{ + Use: "aws [flags]", + Aliases: []string{}, + GroupID: groupScanning, + Args: cobra.ExactArgs(0), + 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: + +- %s +`, strings.Join(services, "\n- ")), + Example: ` # basic scanning + $ trivy aws --region us-east-1 + + # limit scan to a single service: + $ trivy aws --region us-east-1 --service s3 + + # limit scan to multiple services: + $ trivy aws --region us-east-1 --service s3 --service ec2 + + # force refresh of cache for fresh results + $ trivy aws --region us-east-1 --update-cache +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := awsFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + opts, err := awsFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + if opts.Timeout < time.Hour { + opts.Timeout = time.Hour + log.Logger.Debug("Timeout is set to less than 1 hour - upgrading to 1 hour for this command.") + } + return awscommands.Run(cmd.Context(), opts) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + awsFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, awsFlags.Usages(cmd))) + + return cmd +} + +func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + vmFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CacheFlagGroup: flag.NewCacheFlagGroup(), + DBFlagGroup: flag.NewDBFlagGroup(), + MisconfFlagGroup: flag.NewMisconfFlagGroup(), + ModuleFlagGroup: flag.NewModuleFlagGroup(), + RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode + ReportFlagGroup: flag.NewReportFlagGroup(), + ScanFlagGroup: flag.NewScanFlagGroup(), + SecretFlagGroup: flag.NewSecretFlagGroup(), + VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + AWSFlagGroup: &flag.AWSFlagGroup{ + Region: &flag.Flag[string]{ + Name: "aws-region", + ConfigName: "aws.region", + Usage: "AWS region to scan", + }, + }, + } + vmFlags.ReportFlagGroup.ReportFormat = nil // disable '--report' + vmFlags.ScanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + vmFlags.MisconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' + vmFlags.MisconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' + + cmd := &cobra.Command{ + Use: "vm [flags] VM_IMAGE", + Aliases: []string{}, + GroupID: groupScanning, + Short: "[EXPERIMENTAL] Scan a virtual machine image", + Example: ` # Scan your AWS AMI + $ trivy vm --scanners 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(args) + 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' + reportFlagGroup.ReportFormat = nil // TODO: support --report summary + + scanners := flag.ScannersFlag.Clone() + scanners.Values = xstrings.ToStringSlice(types.Scanners{ + types.VulnerabilityScanner, + types.LicenseScanner, + }) + scanners.Default = xstrings.ToStringSlice(types.Scanners{ + types.VulnerabilityScanner, + }) + scanFlagGroup := flag.NewScanFlagGroup() + scanFlagGroup.Scanners = scanners // allow only 'vuln' and 'license' options for '--scanners' + scanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + scanFlagGroup.Parallel = nil // disable '--parallel' + + licenseFlagGroup := flag.NewLicenseFlagGroup() + // License full-scan and confidence-level are for file content only + licenseFlagGroup.LicenseFull = nil + licenseFlagGroup.LicenseConfidenceLevel = nil + + sbomFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CacheFlagGroup: flag.NewCacheFlagGroup(), + DBFlagGroup: flag.NewDBFlagGroup(), + RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode + ReportFlagGroup: reportFlagGroup, + ScanFlagGroup: scanFlagGroup, + SBOMFlagGroup: flag.NewSBOMFlagGroup(), + VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + LicenseFlagGroup: licenseFlagGroup, + } + + cmd := &cobra.Command{ + Use: "sbom [flags] SBOM_PATH", + Short: "Scan SBOM for vulnerabilities and licenses", + GroupID: groupScanning, + Example: ` # Scan CycloneDX and show the result in tables + $ trivy sbom /path/to/report.cdx + + # Scan CycloneDX-type attestation and show the result in tables + $ trivy sbom /path/to/report.cdx.intoto.jsonl +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := sbomFlags.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 := sbomFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + options, err := sbomFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + + return artifact.Run(cmd.Context(), options, artifact.TargetSBOM) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + sbomFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, sbomFlags.Usages(cmd))) + + return cmd +} + +func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + var versionFormat string + cmd := &cobra.Command{ + Use: "version [flags]", + Short: "Print the version", + GroupID: groupUtility, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + options, err := globalFlags.ToOptions() + if err != nil { + return err + } + return showVersion(options.CacheDir, versionFormat, cmd.OutOrStdout()) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + + // Add version format flag, only json is supported + cmd.Flags().StringVarP(&versionFormat, flag.FormatFlag.Name, flag.FormatFlag.Shorthand, "", "version format (json)") + + return cmd +} + +func showVersion(cacheDir, outputFormat string, w io.Writer) error { + versionInfo := version.NewVersionInfo(cacheDir) + switch outputFormat { + case "json": + if err := json.NewEncoder(w).Encode(versionInfo); err != nil { + return xerrors.Errorf("json encode error: %w", err) + } + default: + fmt.Fprint(w, versionInfo.String()) + } + return nil +} + +func validateArgs(cmd *cobra.Command, args []string) error { + // '--clear-cache', '--download-db-only', '--download-java-db-only', '--reset' and '--generate-default-config' don't conduct the subsequent scanning + if viper.GetBool(flag.ClearCacheFlag.ConfigName) || viper.GetBool(flag.DownloadDBOnlyFlag.ConfigName) || + viper.GetBool(flag.ResetFlag.ConfigName) || viper.GetBool(flag.GenerateDefaultConfigFlag.ConfigName) || + viper.GetBool(flag.DownloadJavaDBOnlyFlag.ConfigName) || viper.GetBool(flag.ResetPolicyBundleFlag.ConfigName) { + return nil + } + + if len(args) == 0 && viper.GetString(flag.InputFlag.ConfigName) == "" { + if err := cmd.Help(); err != nil { + return err + } + + if f := cmd.Flags().Lookup(flag.InputFlag.ConfigName); f != nil { + return xerrors.New(`Require at least 1 argument or --input option`) + } + return xerrors.New(`Require at least 1 argument`) + } else if cmd.Name() != "kubernetes" && len(args) > 1 { + if err := cmd.Help(); err != nil { + return err + } + return xerrors.New(`multiple targets cannot be specified`) + } + + return nil +} + +// show help on using the command when an invalid flag is encountered +func flagErrorFunc(command *cobra.Command, err error) error { + if err := command.Help(); err != nil { + return err + } + command.Println() // add empty line after list of flags + return err +} diff --git a/pkg/commands/app_test.go b/pkg/commands/app_test.go new file mode 100644 index 000000000000..0a4651d9e63d --- /dev/null +++ b/pkg/commands/app_test.go @@ -0,0 +1,299 @@ +package commands + +import ( + "bytes" + "io" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/types" +) + +func Test_showVersion(t *testing.T) { + type args struct { + cacheDir string + outputFormat string + version string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "happy path, table output", + args: args{ + outputFormat: "table", + version: "dev", + cacheDir: "testdata", + }, + want: `Version: dev +Vulnerability DB: + Version: 2 + UpdatedAt: 2022-03-02 06:07:07.99504083 +0000 UTC + NextUpdate: 2022-03-02 12:07:07.99504023 +0000 UTC + DownloadedAt: 2022-03-02 10:03:38.383312 +0000 UTC +Java DB: + Version: 1 + UpdatedAt: 2023-03-14 00:47:02.774253754 +0000 UTC + NextUpdate: 2023-03-17 00:47:02.774253254 +0000 UTC + DownloadedAt: 2023-03-14 03:04:55.058541039 +0000 UTC +Policy Bundle: + Digest: sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd + DownloadedAt: 2023-03-02 01:06:08.191725 +0000 UTC +`, + }, + { + name: "sad path, bogus cache dir", + args: args{ + outputFormat: "json", + version: "dev", + cacheDir: "/foo/bar/bogus", + }, + want: `{"Version":"dev"} +`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := new(bytes.Buffer) + showVersion(tt.args.cacheDir, tt.args.outputFormat, got) + assert.Equal(t, tt.want, got.String(), tt.name) + }) + } +} + +// Check flag and command for print version +func TestPrintVersion(t *testing.T) { + tableOutput := `Version: dev +Vulnerability DB: + Version: 2 + UpdatedAt: 2022-03-02 06:07:07.99504083 +0000 UTC + NextUpdate: 2022-03-02 12:07:07.99504023 +0000 UTC + DownloadedAt: 2022-03-02 10:03:38.383312 +0000 UTC +Java DB: + Version: 1 + UpdatedAt: 2023-03-14 00:47:02.774253754 +0000 UTC + NextUpdate: 2023-03-17 00:47:02.774253254 +0000 UTC + DownloadedAt: 2023-03-14 03:04:55.058541039 +0000 UTC +Policy Bundle: + Digest: sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd + DownloadedAt: 2023-03-02 01:06:08.191725 +0000 UTC +` + jsonOutput := `{"Version":"dev","VulnerabilityDB":{"Version":2,"NextUpdate":"2022-03-02T12:07:07.99504023Z","UpdatedAt":"2022-03-02T06:07:07.99504083Z","DownloadedAt":"2022-03-02T10:03:38.383312Z"},"JavaDB":{"Version":1,"NextUpdate":"2023-03-17T00:47:02.774253254Z","UpdatedAt":"2023-03-14T00:47:02.774253754Z","DownloadedAt":"2023-03-14T03:04:55.058541039Z"},"PolicyBundle":{"Digest":"sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd","DownloadedAt":"2023-03-02T01:06:08.191725Z"}} +` + tests := []struct { + name string + arguments []string // 1st argument is path to trivy binaries + want string + }{ + { + name: "happy path. '-v' flag is used", + arguments: []string{ + "-v", + "--cache-dir", + "testdata", + }, + want: tableOutput, + }, + { + name: "happy path. '-version' flag is used", + arguments: []string{ + "--version", + "--cache-dir", + "testdata", + }, + want: tableOutput, + }, + { + name: "happy path. 'version' command is used", + arguments: []string{ + "--cache-dir", + "testdata", + "version", + }, + want: tableOutput, + }, + { + name: "happy path. 'version', '--format json' flags are used", + arguments: []string{ + "--cache-dir", + "testdata", + "version", + "--format", + "json", + }, + want: jsonOutput, + }, + { + name: "happy path. '-v', '--format json' flags are used", + arguments: []string{ + "--cache-dir", + "testdata", + "-v", + "--format", + "json", + }, + want: jsonOutput, + }, + { + name: "happy path. '--version', '--format json' flags are used", + arguments: []string{ + "--cache-dir", + "testdata", + "--version", + "--format", + "json", + }, + want: jsonOutput, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := new(bytes.Buffer) + app := NewApp() + app.SetOut(got) + app.SetArgs(test.arguments) + + err := app.Execute() + require.NoError(t, err) + assert.Equal(t, test.want, got.String()) + }) + } +} + +func TestFlags(t *testing.T) { + type want struct { + format types.Format + severities []dbTypes.Severity + } + tests := []struct { + name string + arguments []string // 1st argument is path to trivy binaries + want want + wantErr string + }{ + { + name: "happy path", + arguments: []string{ + "test", + }, + want: want{ + format: types.FormatTable, + severities: []dbTypes.Severity{ + dbTypes.SeverityUnknown, + dbTypes.SeverityLow, + dbTypes.SeverityMedium, + dbTypes.SeverityHigh, + dbTypes.SeverityCritical, + }, + }, + }, + { + name: "happy path with comma-separated severities", + arguments: []string{ + "test", + "--severity", + "LOW,MEDIUM", + }, + want: want{ + format: types.FormatTable, + severities: []dbTypes.Severity{ + dbTypes.SeverityLow, + dbTypes.SeverityMedium, + }, + }, + }, + { + name: "happy path with repeated severities", + arguments: []string{ + "test", + "--severity", + "LOW", + "--severity", + "HIGH", + }, + want: want{ + format: types.FormatTable, + severities: []dbTypes.Severity{ + dbTypes.SeverityLow, + dbTypes.SeverityHigh, + }, + }, + }, + { + name: "happy path with json", + arguments: []string{ + "test", + "--format", + "json", + "--severity", + "CRITICAL", + }, + want: want{ + format: types.FormatJSON, + severities: []dbTypes.Severity{ + dbTypes.SeverityCritical, + }, + }, + }, + { + name: "invalid format", + arguments: []string{ + "test", + "--format", + "foo", + }, + wantErr: `invalid argument "foo" for "--format" flag`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + globalFlags := flag.NewGlobalFlagGroup() + rootCmd := NewRootCommand(globalFlags) + rootCmd.SetErr(io.Discard) + rootCmd.SetOut(io.Discard) + + flags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + ReportFlagGroup: flag.NewReportFlagGroup(), + } + cmd := &cobra.Command{ + Use: "test", + RunE: func(cmd *cobra.Command, args []string) error { + // Bind + if err := flags.Bind(cmd); err != nil { + return err + } + + options, err := flags.ToOptions(args) + if err != nil { + return err + } + + assert.Equal(t, tt.want.format, options.Format) + assert.Equal(t, tt.want.severities, options.Severities) + return nil + }, + } + flags.AddFlags(cmd) + rootCmd.AddCommand(cmd) + + rootCmd.SetArgs(tt.arguments) + + err := rootCmd.Execute() + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + }) + } +} diff --git a/pkg/commands/artifact/inject.go b/pkg/commands/artifact/inject.go new file mode 100644 index 000000000000..19d6e8565f02 --- /dev/null +++ b/pkg/commands/artifact/inject.go @@ -0,0 +1,114 @@ +//go:build wireinject +// +build wireinject + +package artifact + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/fanal/artifact/vm" + + "github.com/google/wire" + + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/rpc/client" + "github.com/aquasecurity/trivy/pkg/scanner" +) + +////////////// +// Standalone +////////////// + +// initializeImageScanner is for container image scanning in standalone mode +// e.g. dockerd, container registry, podman, etc. +func initializeImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, + localArtifactCache cache.LocalArtifactCache, imageOpt types.ImageOptions, artifactOption artifact.Option) ( + scanner.Scanner, func(), error) { + wire.Build(scanner.StandaloneDockerSet) + return scanner.Scanner{}, nil, nil +} + +// initializeArchiveScanner is for container image archive scanning in standalone mode +// e.g. docker save -o alpine.tar alpine:3.15 +func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, + localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) { + wire.Build(scanner.StandaloneArchiveSet) + return scanner.Scanner{}, nil +} + +// initializeFilesystemScanner is for filesystem scanning in standalone mode +func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, + localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + wire.Build(scanner.StandaloneFilesystemSet) + return scanner.Scanner{}, nil, nil +} + +func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, + localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + wire.Build(scanner.StandaloneRepositorySet) + return scanner.Scanner{}, nil, nil +} + +func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, + localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + wire.Build(scanner.StandaloneSBOMSet) + return scanner.Scanner{}, nil, nil +} + +func initializeVMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, + localArtifactCache cache.LocalArtifactCache, walker vm.Walker, artifactOption artifact.Option) ( + scanner.Scanner, func(), error) { + wire.Build(scanner.StandaloneVMSet) + return scanner.Scanner{}, nil, nil +} + +///////////////// +// Client/Server +///////////////// + +// initializeRemoteImageScanner is for container image scanning in client/server mode +// e.g. dockerd, container registry, podman, etc. +func initializeRemoteImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, + remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) ( + scanner.Scanner, func(), error) { + wire.Build(scanner.RemoteDockerSet) + return scanner.Scanner{}, nil, nil +} + +// initializeRemoteArchiveScanner is for container image archive scanning in client/server mode +// e.g. docker save -o alpine.tar alpine:3.15 +func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, + remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, error) { + wire.Build(scanner.RemoteArchiveSet) + return scanner.Scanner{}, nil +} + +// initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode +func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, + remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + wire.Build(scanner.RemoteFilesystemSet) + return scanner.Scanner{}, nil, nil +} + +// initializeRemoteRepositoryScanner is for repository scanning in client/server mode +func initializeRemoteRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, + remoteScanOptions client.ScannerOption, artifactOption artifact.Option) ( + scanner.Scanner, func(), error) { + wire.Build(scanner.RemoteRepositorySet) + return scanner.Scanner{}, nil, nil +} + +// initializeRemoteSBOMScanner is for sbom scanning in client/server mode +func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, + remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + 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, + walker vm.Walker, 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 new file mode 100644 index 000000000000..c54f0fe2fe75 --- /dev/null +++ b/pkg/commands/artifact/run.go @@ -0,0 +1,732 @@ +package artifact + +import ( + "context" + "errors" + "fmt" + + "github.com/hashicorp/go-multierror" + "github.com/samber/lo" + "github.com/spf13/viper" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/go-version/pkg/semver" + "github.com/aquasecurity/trivy-db/pkg/db" + tcache "github.com/aquasecurity/trivy/pkg/cache" + "github.com/aquasecurity/trivy/pkg/commands/operation" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/javadb" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/module" + "github.com/aquasecurity/trivy/pkg/policy" + pkgReport "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/result" + "github.com/aquasecurity/trivy/pkg/rpc/client" + "github.com/aquasecurity/trivy/pkg/scanner" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +// TargetKind represents what kind of artifact Trivy scans +type TargetKind string + +const ( + TargetContainerImage TargetKind = "image" + TargetFilesystem TargetKind = "fs" + TargetRootfs TargetKind = "rootfs" + TargetRepository TargetKind = "repo" + TargetImageArchive TargetKind = "archive" + TargetSBOM TargetKind = "sbom" + TargetVM TargetKind = "vm" + + devVersion = "dev" +) + +var ( + defaultPolicyNamespaces = []string{ + "appshield", + "defsec", + "builtin", + } + SkipScan = errors.New("skip subsequent processes") +) + +// InitializeScanner defines the initialize function signature of scanner +type InitializeScanner func(context.Context, ScannerConfig) (scanner.Scanner, func(), error) + +type ScannerConfig struct { + // e.g. image name and file path + Target string + + // Cache + ArtifactCache cache.ArtifactCache + LocalArtifactCache cache.LocalArtifactCache + + // Client/Server options + ServerOption client.ScannerOption + + // Artifact options + ArtifactOption artifact.Option +} + +type Runner interface { + // ScanImage scans an image + ScanImage(ctx context.Context, opts flag.Options) (types.Report, error) + // ScanFilesystem scans a filesystem + ScanFilesystem(ctx context.Context, opts flag.Options) (types.Report, error) + // ScanRootfs scans rootfs + ScanRootfs(ctx context.Context, opts flag.Options) (types.Report, error) + // ScanRepository scans repository + 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 + Report(ctx context.Context, opts flag.Options, report types.Report) error + // Close closes runner + Close(ctx context.Context) error +} + +type runner struct { + cache cache.Cache + dbOpen bool + + // WASM modules + module *module.Manager +} + +type runnerOption func(*runner) + +// WithCacheClient takes a custom cache implementation +// It is useful when Trivy is imported as a library. +func WithCacheClient(c cache.Cache) runnerOption { + return func(r *runner) { + r.cache = c + } +} + +// NewRunner initializes Runner that provides scanning functionalities. +// It is possible to return SkipScan and it must be handled by caller. +func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...runnerOption) (Runner, error) { + r := &runner{} + for _, opt := range opts { + opt(r) + } + + if err := r.initCache(cliOptions); err != nil { + return nil, xerrors.Errorf("cache error: %w", err) + } + + // Update the vulnerability database if needed. + if err := r.initDB(ctx, cliOptions); err != nil { + return nil, xerrors.Errorf("DB error: %w", err) + } + + // Initialize WASM modules + m, err := module.NewManager(ctx, module.Options{ + Dir: cliOptions.ModuleDir, + EnabledModules: cliOptions.EnabledModules, + }) + if err != nil { + return nil, xerrors.Errorf("WASM module error: %w", err) + } + m.Register() + r.module = m + + return r, nil +} + +// Close closes everything +func (r *runner) Close(ctx context.Context) error { + var errs error + if err := r.cache.Close(); err != nil { + errs = multierror.Append(errs, err) + } + + if r.dbOpen { + if err := db.Close(); err != nil { + errs = multierror.Append(errs, err) + } + } + + if err := r.module.Close(ctx); err != nil { + errs = multierror.Append(errs, err) + } + return errs +} + +func (r *runner) ScanImage(ctx context.Context, opts flag.Options) (types.Report, error) { + // Disable the lock file scanning + opts.DisabledAnalyzers = analyzer.TypeLockfiles + + var s InitializeScanner + switch { + case opts.Input != "" && opts.ServerAddr == "": + // Scan image tarball in standalone mode + s = archiveStandaloneScanner + case opts.Input != "" && opts.ServerAddr != "": + // Scan image tarball in client/server mode + s = archiveRemoteScanner + case opts.Input == "" && opts.ServerAddr == "": + // Scan container image in standalone mode + s = imageStandaloneScanner + case opts.Input == "" && opts.ServerAddr != "": + // Scan container image in client/server mode + s = imageRemoteScanner + } + + return r.scanArtifact(ctx, opts, s) +} + +func (r *runner) ScanFilesystem(ctx context.Context, opts flag.Options) (types.Report, error) { + // Disable scanning of individual package and SBOM files + opts.DisabledAnalyzers = append(opts.DisabledAnalyzers, analyzer.TypeIndividualPkgs...) + opts.DisabledAnalyzers = append(opts.DisabledAnalyzers, analyzer.TypeSBOM) + + return r.scanFS(ctx, opts) +} + +func (r *runner) ScanRootfs(ctx context.Context, opts flag.Options) (types.Report, error) { + // Disable the lock file scanning + opts.DisabledAnalyzers = append(opts.DisabledAnalyzers, analyzer.TypeLockfiles...) + + return r.scanFS(ctx, opts) +} + +func (r *runner) scanFS(ctx context.Context, opts flag.Options) (types.Report, error) { + var s InitializeScanner + if opts.ServerAddr == "" { + // Scan filesystem in standalone mode + s = filesystemStandaloneScanner + } else { + // Scan filesystem in client/server mode + s = filesystemRemoteScanner + } + + return r.scanArtifact(ctx, opts, s) +} + +func (r *runner) ScanRepository(ctx context.Context, opts flag.Options) (types.Report, error) { + // Do not scan OS packages + opts.VulnType = []string{types.VulnTypeLibrary} + + // Disable the OS analyzers, individual package analyzers and SBOM analyzer + opts.DisabledAnalyzers = append(analyzer.TypeIndividualPkgs, analyzer.TypeOSes...) + opts.DisabledAnalyzers = append(opts.DisabledAnalyzers, analyzer.TypeSBOM) + + var s InitializeScanner + if opts.ServerAddr == "" { + // Scan repository in standalone mode + s = repositoryStandaloneScanner + } else { + // Scan repository in client/server mode + s = repositoryRemoteScanner + } + return r.scanArtifact(ctx, opts, s) +} + +func (r *runner) ScanSBOM(ctx context.Context, opts flag.Options) (types.Report, error) { + var s InitializeScanner + if opts.ServerAddr == "" { + // Scan cycloneDX in standalone mode + s = sbomStandaloneScanner + } else { + // Scan cycloneDX in client/server mode + s = sbomRemoteScanner + } + + 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 { + return types.Report{}, xerrors.Errorf("scan error: %w", err) + } + + return report, nil +} + +func (r *runner) Filter(ctx context.Context, opts flag.Options, report types.Report) (types.Report, error) { + // Filter results + if err := result.Filter(ctx, report, opts.FilterOpts()); err != nil { + return types.Report{}, xerrors.Errorf("filtering error: %w", err) + } + return report, nil +} + +func (r *runner) Report(ctx context.Context, opts flag.Options, report types.Report) error { + if err := pkgReport.Write(ctx, report, opts); err != nil { + return xerrors.Errorf("unable to write results: %w", err) + } + + return nil +} + +func (r *runner) initDB(ctx context.Context, opts flag.Options) error { + if err := r.initJavaDB(opts); err != nil { + return err + } + + // When scanning config files or running as client mode, it doesn't need to download the vulnerability database. + if opts.ServerAddr != "" || !opts.Scanners.Enabled(types.VulnerabilityScanner) { + return nil + } + + // download the database file + noProgress := opts.Quiet || opts.NoProgress + if err := operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, noProgress, opts.SkipDBUpdate, opts.RegistryOpts()); err != nil { + return err + } + + if opts.DownloadDBOnly { + return SkipScan + } + + if err := db.Init(opts.CacheDir); err != nil { + return xerrors.Errorf("error in vulnerability DB initialize: %w", err) + } + r.dbOpen = true + + return nil +} + +func (r *runner) initJavaDB(opts flag.Options) error { + // When running as server mode, it doesn't need to download the Java database. + if opts.Listen != "" { + return nil + } + + // If vulnerability scanning and SBOM generation are disabled, it doesn't need to download the Java database. + if !opts.Scanners.Enabled(types.VulnerabilityScanner) && + !slices.Contains(types.SupportedSBOMFormats, opts.Format) { + return nil + } + + // Update the Java DB + noProgress := opts.Quiet || opts.NoProgress + javadb.Init(opts.CacheDir, opts.JavaDBRepository, opts.SkipJavaDBUpdate, noProgress, opts.RegistryOpts()) + if opts.DownloadJavaDBOnly { + if err := javadb.Update(); err != nil { + return xerrors.Errorf("Java DB error: %w", err) + } + return SkipScan + } + + return nil +} + +func (r *runner) initCache(opts flag.Options) error { + // Skip initializing cache when custom cache is passed + if r.cache != nil { + return nil + } + + // client/server mode + if opts.ServerAddr != "" { + remoteCache := tcache.NewRemoteCache(opts.ServerAddr, opts.CustomHeaders, opts.Insecure) + r.cache = tcache.NopCache(remoteCache) + return nil + } + + // standalone mode + fsutils.SetCacheDir(opts.CacheDir) + cacheClient, err := operation.NewCache(opts.CacheOptions) + if err != nil { + return xerrors.Errorf("unable to initialize the cache: %w", err) + } + log.Logger.Debugf("cache dir: %s", fsutils.CacheDir()) + + if opts.Reset { + defer cacheClient.Close() + if err = cacheClient.Reset(); err != nil { + return xerrors.Errorf("cache reset error: %w", err) + } + return SkipScan + } + + if opts.ResetPolicyBundle { + c, err := policy.NewClient(fsutils.CacheDir(), true, opts.MisconfOptions.PolicyBundleRepository) + if err != nil { + return xerrors.Errorf("failed to instantiate policy client: %w", err) + } + if err := c.Clear(); err != nil { + return xerrors.Errorf("failed to remove the cache: %w", err) + } + return SkipScan + } + + if opts.ClearCache { + defer cacheClient.Close() + if err = cacheClient.ClearArtifacts(); err != nil { + return xerrors.Errorf("cache clear error: %w", err) + } + return SkipScan + } + + r.cache = cacheClient + return nil +} + +// Run performs artifact scanning +func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err error) { + ctx, cancel := context.WithTimeout(ctx, opts.Timeout) + defer cancel() + + defer func() { + if errors.Is(err, context.DeadlineExceeded) { + log.Logger.Warn("Increase --timeout value") + } + }() + + if opts.GenerateDefaultConfig { + log.Logger.Info("Writing the default config to trivy-default.yaml...") + return viper.SafeWriteConfigAs("trivy-default.yaml") + } + + r, err := NewRunner(ctx, opts) + if err != nil { + if errors.Is(err, SkipScan) { + return nil + } + return xerrors.Errorf("init error: %w", err) + } + defer r.Close(ctx) + + var report types.Report + switch targetKind { + case TargetContainerImage, TargetImageArchive: + if report, err = r.ScanImage(ctx, opts); err != nil { + return xerrors.Errorf("image scan error: %w", err) + } + case TargetFilesystem: + if report, err = r.ScanFilesystem(ctx, opts); err != nil { + return xerrors.Errorf("filesystem scan error: %w", err) + } + case TargetRootfs: + if report, err = r.ScanRootfs(ctx, opts); err != nil { + return xerrors.Errorf("rootfs scan error: %w", err) + } + case TargetRepository: + if report, err = r.ScanRepository(ctx, opts); err != nil { + return xerrors.Errorf("repository scan error: %w", err) + } + case TargetSBOM: + 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) + if err != nil { + return xerrors.Errorf("filter error: %w", err) + } + + if err = r.Report(ctx, opts, report); err != nil { + return xerrors.Errorf("report error: %w", err) + } + + operation.ExitOnEOL(opts, report.Metadata) + operation.Exit(opts, report.Results.Failed()) + + return nil +} + +func disabledAnalyzers(opts flag.Options) []analyzer.Type { + // Specified analyzers to be disabled depending on scanning modes + // e.g. The 'image' subcommand should disable the lock file scanning. + analyzers := opts.DisabledAnalyzers + + // It doesn't analyze apk commands by default. + if !opts.ScanRemovedPkgs { + analyzers = append(analyzers, analyzer.TypeApkCommand) + } + + // Do not analyze programming language packages when not running in 'library' + if !slices.Contains(opts.VulnType, types.VulnTypeLibrary) { + analyzers = append(analyzers, analyzer.TypeLanguages...) + } + + // Do not perform secret scanning when it is not specified. + if !opts.Scanners.Enabled(types.SecretScanner) { + analyzers = append(analyzers, analyzer.TypeSecret) + } + + // Filter only enabled misconfiguration scanners + ma, err := filterMisconfigAnalyzers(opts.MisconfigScanners, analyzer.TypeConfigFiles) + if err != nil { + log.Logger.Errorf("Invalid misconfig scanners specified: %s defaulting to use all misconfig scanners", opts.MisconfigScanners) + } else { + analyzers = append(analyzers, ma...) + } + + // Do not perform misconfiguration scanning when it is not specified. + if !opts.Scanners.AnyEnabled(types.MisconfigScanner, types.RBACScanner) { + analyzers = append(analyzers, analyzer.TypeConfigFiles...) + } + + // Scanning file headers and license files is expensive. + // It is performed only when '--scanners license' and '--license-full' are specified together. + if !opts.Scanners.Enabled(types.LicenseScanner) || !opts.LicenseFull { + analyzers = append(analyzers, analyzer.TypeLicenseFile) + } + + // Parsing jar files requires Java-db client + // But we don't create client if vulnerability analysis is disabled and SBOM format is not used + // We need to disable jar analyzer to avoid errors + // TODO disable all languages that don't contain license information for this case + if !opts.Scanners.Enabled(types.VulnerabilityScanner) && !slices.Contains(types.SupportedSBOMFormats, opts.Format) { + analyzers = append(analyzers, analyzer.TypeJar) + } + + // Do not perform misconfiguration scanning on container image config + // when it is not specified. + if !opts.ImageConfigScanners.Enabled(types.MisconfigScanner) { + analyzers = append(analyzers, analyzer.TypeHistoryDockerfile) + } + + // Skip executable file analysis if Rekor isn't a specified SBOM source. + if !slices.Contains(opts.SBOMSources, types.SBOMSourceRekor) { + analyzers = append(analyzers, analyzer.TypeExecutable) + } + + return analyzers +} + +func filterMisconfigAnalyzers(included, all []analyzer.Type) ([]analyzer.Type, error) { + _, missing := lo.Difference(all, included) + if len(missing) > 0 { + return nil, xerrors.Errorf("invalid misconfiguration scanner specified %s valid scanners: %s", missing, all) + } + + log.Logger.Debugf("Enabling misconfiguration scanners: %s", included) + return lo.Without(all, included...), nil +} + +func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfig, types.ScanOptions, error) { + target := opts.Target + if opts.Input != "" { + target = opts.Input + } + + if opts.Compliance.Spec.ID != "" { + // set scanners types by spec + scanners, err := opts.Compliance.Scanners() + if err != nil { + return ScannerConfig{}, types.ScanOptions{}, xerrors.Errorf("scanner error: %w", err) + } + + opts.Scanners = scanners + opts.ImageConfigScanners = nil + // TODO: define image-config-scanners in the spec + if opts.Compliance.Spec.ID == "docker-cis" { + opts.Scanners = types.Scanners{types.VulnerabilityScanner} + opts.ImageConfigScanners = types.Scanners{ + types.MisconfigScanner, + types.SecretScanner, + } + } + } + + scanOptions := types.ScanOptions{ + VulnType: opts.VulnType, + Scanners: opts.Scanners, + ImageConfigScanners: opts.ImageConfigScanners, // this is valid only for 'image' subcommand + ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand + ListAllPackages: opts.ListAllPkgs, + LicenseCategories: opts.LicenseCategories, + FilePatterns: opts.FilePatterns, + IncludeDevDeps: opts.IncludeDevDeps, + } + + if len(opts.ImageConfigScanners) != 0 { + log.Logger.Infof("Container image config scanners: %q", opts.ImageConfigScanners) + } + + if opts.Scanners.Enabled(types.VulnerabilityScanner) { + log.Logger.Info("Vulnerability scanning is enabled") + log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType) + } + + // ScannerOption is filled only when config scanning is enabled. + var configScannerOptions misconf.ScannerOption + if opts.Scanners.Enabled(types.MisconfigScanner) || opts.ImageConfigScanners.Enabled(types.MisconfigScanner) { + log.Logger.Info("Misconfiguration scanning is enabled") + + var downloadedPolicyPaths []string + var disableEmbedded bool + downloadedPolicyPaths, err := operation.InitBuiltinPolicies(context.Background(), opts.CacheDir, opts.Quiet, opts.SkipPolicyUpdate, opts.MisconfOptions.PolicyBundleRepository, opts.RegistryOpts()) + if err != nil { + if !opts.SkipPolicyUpdate { + log.Logger.Errorf("Falling back to embedded policies: %s", err) + } + } else { + log.Logger.Debug("Policies successfully loaded from disk") + disableEmbedded = true + } + configScannerOptions = misconf.ScannerOption{ + Debug: opts.Debug, + Trace: opts.Trace, + Namespaces: append(opts.PolicyNamespaces, defaultPolicyNamespaces...), + PolicyPaths: append(opts.PolicyPaths, downloadedPolicyPaths...), + DataPaths: opts.DataPaths, + HelmValues: opts.HelmValues, + HelmValueFiles: opts.HelmValueFiles, + HelmFileValues: opts.HelmFileValues, + HelmStringValues: opts.HelmStringValues, + HelmAPIVersions: opts.HelmAPIVersions, + HelmKubeVersion: opts.HelmKubeVersion, + TerraformTFVars: opts.TerraformTFVars, + CloudFormationParamVars: opts.CloudFormationParamVars, + K8sVersion: opts.K8sVersion, + DisableEmbeddedPolicies: disableEmbedded, + DisableEmbeddedLibraries: disableEmbedded, + TfExcludeDownloaded: opts.TfExcludeDownloaded, + } + } + + // Do not load config file for secret scanning + if opts.Scanners.Enabled(types.SecretScanner) { + ver := canonicalVersion(opts.AppVersion) + log.Logger.Info("Secret scanning is enabled") + log.Logger.Info("If your scanning is slow, please try '--scanners vuln' to disable secret scanning") + log.Logger.Infof("Please see also https://aquasecurity.github.io/trivy/%s/docs/scanner/secret/#recommendation for faster secret detection", ver) + } else { + opts.SecretConfigPath = "" + } + + if opts.Scanners.Enabled(types.LicenseScanner) { + if opts.LicenseFull { + log.Logger.Info("Full license scanning is enabled") + } else { + log.Logger.Info("License scanning is enabled") + } + } + + // SPDX needs to calculate digests for package files + var fileChecksum bool + if opts.Format == types.FormatSPDXJSON || opts.Format == types.FormatSPDX { + fileChecksum = true + } + + return ScannerConfig{ + Target: target, + ArtifactCache: cacheClient, + LocalArtifactCache: cacheClient, + ServerOption: client.ScannerOption{ + RemoteURL: opts.ServerAddr, + CustomHeaders: opts.CustomHeaders, + Insecure: opts.Insecure, + }, + ArtifactOption: artifact.Option{ + DisabledAnalyzers: disabledAnalyzers(opts), + SkipFiles: opts.SkipFiles, + SkipDirs: opts.SkipDirs, + FilePatterns: opts.FilePatterns, + Offline: opts.OfflineScan, + NoProgress: opts.NoProgress || opts.Quiet, + Insecure: opts.Insecure, + RepoBranch: opts.RepoBranch, + RepoCommit: opts.RepoCommit, + RepoTag: opts.RepoTag, + SBOMSources: opts.SBOMSources, + RekorURL: opts.RekorURL, + //Platform: opts.Platform, + Parallel: opts.Parallel, + AWSRegion: opts.Region, + AWSEndpoint: opts.Endpoint, + FileChecksum: fileChecksum, + + // For image scanning + ImageOption: ftypes.ImageOptions{ + RegistryOptions: opts.RegistryOpts(), + DockerOptions: ftypes.DockerOptions{ + Host: opts.DockerHost, + }, + PodmanOptions: ftypes.PodmanOptions{ + Host: opts.PodmanHost, + }, + ImageSources: opts.ImageSources, + }, + + // For misconfiguration scanning + MisconfScannerOption: configScannerOptions, + + // For secret scanning + SecretScannerOption: analyzer.SecretScannerOption{ + ConfigPath: opts.SecretConfigPath, + }, + + // For license scanning + LicenseScannerOption: analyzer.LicenseScannerOption{ + Full: opts.LicenseFull, + ClassifierConfidenceLevel: opts.LicenseConfidenceLevel, + }, + }, + }, scanOptions, nil +} + +func scan(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner, cacheClient cache.Cache) ( + types.Report, error) { + scannerConfig, scanOptions, err := initScannerConfig(opts, cacheClient) + if err != nil { + return types.Report{}, err + } + s, cleanup, err := initializeScanner(ctx, scannerConfig) + if err != nil { + return types.Report{}, xerrors.Errorf("unable to initialize a scanner: %w", err) + } + defer cleanup() + + report, err := s.ScanArtifact(ctx, scanOptions) + if err != nil { + return types.Report{}, xerrors.Errorf("scan failed: %w", err) + } + return report, nil +} + +func canonicalVersion(ver string) string { + if ver == devVersion { + return ver + } + v, err := semver.Parse(ver) + if err != nil { + return devVersion + } + // Replace pre-release with "dev" + // e.g. v0.34.0-beta1+snapshot-1 + if v.IsPreRelease() || v.Metadata() != "" { + return devVersion + } + // Add "v" prefix and cut a patch number, "0.34.0" => "v0.34" for the url + return fmt.Sprintf("v%d.%d", v.Major(), v.Minor()) +} diff --git a/pkg/commands/artifact/run_test.go b/pkg/commands/artifact/run_test.go new file mode 100644 index 000000000000..02d35a53d44b --- /dev/null +++ b/pkg/commands/artifact/run_test.go @@ -0,0 +1,48 @@ +package artifact + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCanonicalVersion(t *testing.T) { + tests := []struct { + title string + input string + want string + }{ + { + title: "good way", + input: "0.34.0", + want: "v0.34", + }, + { + title: "version with v - isn't right semver version", + input: "v0.34.0", + want: devVersion, + }, + { + title: "dev version", + input: devVersion, + want: devVersion, + }, + { + title: "pre-release", + input: "v0.34.0-beta1+snapshot-1", + want: devVersion, + }, + { + title: "no version", + input: "", + want: devVersion, + }, + } + + for _, test := range tests { + t.Run(test.title, func(t *testing.T) { + got := canonicalVersion(test.input) + require.Equal(t, test.want, got) + }) + } +} diff --git a/pkg/commands/artifact/scanner.go b/pkg/commands/artifact/scanner.go new file mode 100644 index 000000000000..5685572a10c4 --- /dev/null +++ b/pkg/commands/artifact/scanner.go @@ -0,0 +1,132 @@ +package artifact + +import ( + "context" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/walker" + "github.com/aquasecurity/trivy/pkg/scanner" +) + +// imageStandaloneScanner initializes a container image scanner in standalone mode +// $ trivy image alpine:3.15 +func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeImageScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, + conf.ArtifactOption.ImageOption, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize an image scanner: %w", err) + } + return s, cleanup, nil +} + +// archiveStandaloneScanner initializes an image archive scanner in standalone mode +// $ trivy image --input alpine.tar +func archiveStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + s, err := initializeArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err) + } + return s, func() {}, nil +} + +// imageRemoteScanner initializes a container image scanner in client/server mode +// $ trivy image --server localhost:4954 alpine:3.15 +func imageRemoteScanner(ctx context.Context, conf ScannerConfig) ( + scanner.Scanner, func(), error) { + s, cleanup, err := initializeRemoteImageScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, + conf.ArtifactOption.ImageOption, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize a remote image scanner: %w", err) + } + return s, cleanup, nil +} + +// archiveRemoteScanner initializes an image archive scanner in client/server mode +// $ trivy image --server localhost:4954 --input alpine.tar +func archiveRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + // Scan tar file + s, err := initializeRemoteArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the remote archive scanner: %w", err) + } + return s, func() {}, nil +} + +// filesystemStandaloneScanner initializes a filesystem scanner in standalone mode +func filesystemStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err) + } + return s, cleanup, nil +} + +// filesystemRemoteScanner initializes a filesystem scanner in client/server mode +func filesystemRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote filesystem scanner: %w", err) + } + return s, cleanup, nil +} + +// repositoryStandaloneScanner initializes a repository scanner in standalone mode +func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a repository scanner: %w", err) + } + return s, cleanup, nil +} + +// repositoryRemoteScanner initializes a repository scanner in client/server mode +func repositoryRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeRemoteRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, + conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote repository scanner: %w", err) + } + return s, cleanup, nil +} + +// sbomStandaloneScanner initializes a SBOM scanner in standalone mode +func sbomStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err) + } + return s, cleanup, nil +} + +// sbomRemoteScanner initializes a SBOM scanner in client/server mode +func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeRemoteSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote cycloneDX scanner: %w", err) + } + return s, cleanup, nil +} + +// vmStandaloneScanner initializes a VM scanner in standalone mode +func vmStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + // TODO: The walker should be initialized in initializeVMScanner after https://github.com/aquasecurity/trivy/pull/5180 + w := walker.NewVM(conf.ArtifactOption.SkipFiles, conf.ArtifactOption.SkipDirs) + s, cleanup, err := initializeVMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, + w, 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) { + // TODO: The walker should be initialized in initializeVMScanner after https://github.com/aquasecurity/trivy/pull/5180 + w := walker.NewVM(conf.ArtifactOption.SkipFiles, conf.ArtifactOption.SkipDirs) + s, cleanup, err := initializeRemoteVMScanner(ctx, conf.Target, conf.ArtifactCache, w, conf.ServerOption, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote vm scanner: %w", err) + } + return s, cleanup, nil +} diff --git a/pkg/commands/artifact/wire_gen.go b/pkg/commands/artifact/wire_gen.go new file mode 100644 index 000000000000..e91ae3d5e8c8 --- /dev/null +++ b/pkg/commands/artifact/wire_gen.go @@ -0,0 +1,235 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package artifact + +import ( + "context" + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/fanal/applier" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + image2 "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" + local2 "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" + "github.com/aquasecurity/trivy/pkg/fanal/artifact/repo" + "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" + "github.com/aquasecurity/trivy/pkg/rpc/client" + "github.com/aquasecurity/trivy/pkg/scanner" + "github.com/aquasecurity/trivy/pkg/scanner/langpkg" + "github.com/aquasecurity/trivy/pkg/scanner/local" + "github.com/aquasecurity/trivy/pkg/scanner/ospkg" + "github.com/aquasecurity/trivy/pkg/vulnerability" +) + +// Injectors from inject.go: + +// initializeImageScanner is for container image scanning in standalone mode +// e.g. dockerd, container registry, podman, etc. +func initializeImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, imageOpt types.ImageOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + applierApplier := applier.NewApplier(localArtifactCache) + ospkgScanner := ospkg.NewScanner() + langpkgScanner := langpkg.NewScanner() + config := db.Config{} + client := vulnerability.NewClient(config) + localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) + typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, imageOpt) + if err != nil { + return scanner.Scanner{}, nil, err + } + artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) + if err != nil { + cleanup() + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) + return scannerScanner, func() { + cleanup() + }, nil +} + +// initializeArchiveScanner is for container image archive scanning in standalone mode +// e.g. docker save -o alpine.tar alpine:3.15 +func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) { + applierApplier := applier.NewApplier(localArtifactCache) + ospkgScanner := ospkg.NewScanner() + langpkgScanner := langpkg.NewScanner() + config := db.Config{} + client := vulnerability.NewClient(config) + localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) + typesImage, err := image.NewArchiveImage(filePath) + if err != nil { + return scanner.Scanner{}, err + } + artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) + if err != nil { + return scanner.Scanner{}, err + } + scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) + return scannerScanner, nil +} + +// initializeFilesystemScanner is for filesystem scanning in standalone mode +func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + applierApplier := applier.NewApplier(localArtifactCache) + ospkgScanner := ospkg.NewScanner() + langpkgScanner := langpkg.NewScanner() + config := db.Config{} + client := vulnerability.NewClient(config) + localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) + artifactArtifact, err := local2.NewArtifact(path, artifactCache, artifactOption) + if err != nil { + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) + return scannerScanner, func() { + }, nil +} + +func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + applierApplier := applier.NewApplier(localArtifactCache) + ospkgScanner := ospkg.NewScanner() + langpkgScanner := langpkg.NewScanner() + config := db.Config{} + client := vulnerability.NewClient(config) + localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) + artifactArtifact, cleanup, err := repo.NewArtifact(url, artifactCache, artifactOption) + if err != nil { + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) + return scannerScanner, func() { + cleanup() + }, nil +} + +func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + applierApplier := applier.NewApplier(localArtifactCache) + ospkgScanner := ospkg.NewScanner() + langpkgScanner := langpkg.NewScanner() + config := db.Config{} + client := vulnerability.NewClient(config) + localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) + artifactArtifact, err := sbom.NewArtifact(filePath, artifactCache, artifactOption) + if err != nil { + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) + return scannerScanner, func() { + }, nil +} + +func initializeVMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, walker vm.Walker, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + applierApplier := applier.NewApplier(localArtifactCache) + ospkgScanner := ospkg.NewScanner() + langpkgScanner := langpkg.NewScanner() + config := db.Config{} + client := vulnerability.NewClient(config) + localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) + artifactArtifact, err := vm.NewArtifact(filePath, artifactCache, walker, artifactOption) + if err != nil { + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) + return scannerScanner, func() { + }, nil +} + +// initializeRemoteImageScanner is for container image scanning in client/server mode +// e.g. dockerd, container registry, podman, etc. +func initializeRemoteImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue + clientScanner := client.NewScanner(remoteScanOptions, v...) + typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, imageOpt) + if err != nil { + return scanner.Scanner{}, nil, err + } + artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) + if err != nil { + cleanup() + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact) + return scannerScanner, func() { + cleanup() + }, nil +} + +var ( + _wireValue = []client.Option(nil) +) + +// initializeRemoteArchiveScanner is for container image archive scanning in client/server mode +// e.g. docker save -o alpine.tar alpine:3.15 +func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, error) { + v := _wireValue + clientScanner := client.NewScanner(remoteScanOptions, v...) + typesImage, err := image.NewArchiveImage(filePath) + if err != nil { + return scanner.Scanner{}, err + } + artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) + if err != nil { + return scanner.Scanner{}, err + } + scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact) + return scannerScanner, nil +} + +// initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode +func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue + clientScanner := client.NewScanner(remoteScanOptions, v...) + artifactArtifact, err := local2.NewArtifact(path, artifactCache, artifactOption) + if err != nil { + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact) + return scannerScanner, func() { + }, nil +} + +// initializeRemoteRepositoryScanner is for repository scanning in client/server mode +func initializeRemoteRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue + clientScanner := client.NewScanner(remoteScanOptions, v...) + artifactArtifact, cleanup, err := repo.NewArtifact(url, artifactCache, artifactOption) + if err != nil { + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact) + return scannerScanner, func() { + cleanup() + }, nil +} + +// initializeRemoteSBOMScanner is for sbom scanning in client/server mode +func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue + clientScanner := client.NewScanner(remoteScanOptions, v...) + artifactArtifact, err := sbom.NewArtifact(path, artifactCache, artifactOption) + if err != nil { + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact) + return scannerScanner, func() { + }, nil +} + +// initializeRemoteVMScanner is for vm scanning in client/server mode +func initializeRemoteVMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, walker vm.Walker, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue + clientScanner := client.NewScanner(remoteScanOptions, v...) + artifactArtifact, err := vm.NewArtifact(path, artifactCache, walker, artifactOption) + if err != nil { + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact) + return scannerScanner, func() { + }, nil +} diff --git a/pkg/commands/convert/run.go b/pkg/commands/convert/run.go new file mode 100644 index 000000000000..9045e54bfa3d --- /dev/null +++ b/pkg/commands/convert/run.go @@ -0,0 +1,51 @@ +package convert + +import ( + "context" + "encoding/json" + "os" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/commands/operation" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/result" + "github.com/aquasecurity/trivy/pkg/types" +) + +func Run(ctx context.Context, opts flag.Options) (err error) { + ctx, cancel := context.WithTimeout(ctx, opts.Timeout) + defer cancel() + + f, err := os.Open(opts.Target) + if err != nil { + return xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + var r types.Report + if err = json.NewDecoder(f).Decode(&r); err != nil { + return xerrors.Errorf("json decode error: %w", err) + } + + // "convert" supports JSON results produced by Trivy scanning other than AWS and Kubernetes + if r.ArtifactName == "" && r.ArtifactType == "" { + return xerrors.New("AWS and Kubernetes scanning reports are not yet supported") + } + + if err = result.Filter(ctx, r, opts.FilterOpts()); err != nil { + return xerrors.Errorf("unable to filter results: %w", err) + } + + log.Logger.Debug("Writing report to output...") + if err = report.Write(ctx, r, opts); err != nil { + return xerrors.Errorf("unable to write results: %w", err) + } + + operation.ExitOnEOL(opts, r.Metadata) + operation.Exit(opts, r.Results.Failed()) + + return nil +} diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go new file mode 100644 index 000000000000..8f8561a7c290 --- /dev/null +++ b/pkg/commands/operation/operation.go @@ -0,0 +1,219 @@ +package operation + +import ( + "context" + "crypto/tls" + "crypto/x509" + "os" + "strings" + "sync" + + "github.com/go-redis/redis/v8" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/wire" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/metadata" + "github.com/aquasecurity/trivy/pkg/db" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/policy" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +var mu sync.Mutex + +// SuperSet binds cache dependencies +var SuperSet = wire.NewSet( + cache.NewFSCache, + wire.Bind(new(cache.LocalArtifactCache), new(cache.FSCache)), + NewCache, +) + +// Cache implements the local cache +type Cache struct { + cache.Cache +} + +// NewCache is the factory method for Cache +func NewCache(c flag.CacheOptions) (Cache, error) { + if strings.HasPrefix(c.CacheBackend, "redis://") { + log.Logger.Infof("Redis cache: %s", c.CacheBackendMasked()) + options, err := redis.ParseURL(c.CacheBackend) + if err != nil { + return Cache{}, err + } + + if !lo.IsEmpty(c.RedisOptions) { + caCert, cert, err := GetTLSConfig(c.RedisCACert, c.RedisCert, c.RedisKey) + if err != nil { + return Cache{}, err + } + + options.TLSConfig = &tls.Config{ + RootCAs: caCert, + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS12, + } + } else if c.RedisTLS { + options.TLSConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + } + } + + redisCache := cache.NewRedisCache(options, c.CacheTTL) + return Cache{Cache: redisCache}, nil + } + + if c.CacheTTL != 0 { + log.Logger.Warn("'--cache-ttl' is only available with Redis cache backend") + } + + // standalone mode + fsCache, err := cache.NewFSCache(fsutils.CacheDir()) + if err != nil { + return Cache{}, xerrors.Errorf("unable to initialize fs cache: %w", err) + } + return Cache{Cache: fsCache}, nil +} + +// Reset resets the cache +func (c Cache) Reset() (err error) { + if err := c.ClearDB(); err != nil { + return xerrors.Errorf("failed to clear the database: %w", err) + } + if err := c.ClearArtifacts(); err != nil { + return xerrors.Errorf("failed to clear the artifact cache: %w", err) + } + return nil +} + +// ClearDB clears the DB cache +func (c Cache) ClearDB() (err error) { + log.Logger.Info("Removing DB file...") + if err = os.RemoveAll(fsutils.CacheDir()); err != nil { + return xerrors.Errorf("failed to remove the directory (%s) : %w", fsutils.CacheDir(), err) + } + return nil +} + +// ClearArtifacts clears the artifact cache +func (c Cache) ClearArtifacts() error { + log.Logger.Info("Removing artifact caches...") + if err := c.Clear(); err != nil { + return xerrors.Errorf("failed to remove the cache: %w", err) + } + return nil +} + +// DownloadDB downloads the DB +func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository name.Reference, quiet, skipUpdate bool, + opt ftypes.RegistryOptions) error { + mu.Lock() + defer mu.Unlock() + + client := db.NewClient(cacheDir, quiet, db.WithDBRepository(dbRepository)) + needsUpdate, err := client.NeedsUpdate(appVersion, skipUpdate) + if err != nil { + return xerrors.Errorf("database error: %w", err) + } + + if needsUpdate { + log.Logger.Info("Need to update DB") + log.Logger.Infof("DB Repository: %s", dbRepository) + log.Logger.Info("Downloading DB...") + if err = client.Download(ctx, cacheDir, opt); err != nil { + return xerrors.Errorf("failed to download vulnerability DB: %w", err) + } + } + + // for debug + if err = showDBInfo(cacheDir); err != nil { + return xerrors.Errorf("failed to show database info: %w", err) + } + return nil +} + +func showDBInfo(cacheDir string) error { + m := metadata.NewClient(cacheDir) + meta, err := m.Get() + if err != nil { + return xerrors.Errorf("something wrong with DB: %w", err) + } + log.Logger.Debugf("DB Schema: %d, UpdatedAt: %s, NextUpdate: %s, DownloadedAt: %s", + meta.Version, meta.UpdatedAt, meta.NextUpdate, meta.DownloadedAt) + return nil +} + +// InitBuiltinPolicies downloads the built-in policies and loads them +func InitBuiltinPolicies(ctx context.Context, cacheDir string, quiet, skipUpdate bool, policyBundleRepository string, registryOpts ftypes.RegistryOptions) ([]string, error) { + mu.Lock() + defer mu.Unlock() + + client, err := policy.NewClient(cacheDir, quiet, policyBundleRepository) + if err != nil { + return nil, xerrors.Errorf("policy client error: %w", err) + } + + needsUpdate := false + if !skipUpdate { + needsUpdate, err = client.NeedsUpdate(ctx, registryOpts) + if err != nil { + return nil, xerrors.Errorf("unable to check if built-in policies need to be updated: %w", err) + } + } + + if needsUpdate { + log.Logger.Info("Need to update the built-in policies") + log.Logger.Info("Downloading the built-in policies...") + if err = client.DownloadBuiltinPolicies(ctx, registryOpts); err != nil { + return nil, xerrors.Errorf("failed to download built-in policies: %w", err) + } + } + + policyPaths, err := client.LoadBuiltinPolicies() + if err != nil { + if skipUpdate { + msg := "No downloadable policies were loaded as --skip-policy-update is enabled" + log.Logger.Info(msg) + return nil, xerrors.Errorf(msg) + } + return nil, xerrors.Errorf("policy load error: %w", err) + } + return policyPaths, nil +} + +// GetTLSConfig gets tls config from CA, Cert and Key file +func GetTLSConfig(caCertPath, certPath, keyPath string) (*x509.CertPool, tls.Certificate, error) { + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, tls.Certificate{}, err + } + + caCert, err := os.ReadFile(caCertPath) + if err != nil { + return nil, tls.Certificate{}, err + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + return caCertPool, cert, nil +} + +func Exit(opts flag.Options, failedResults bool) { + if opts.ExitCode != 0 && failedResults { + os.Exit(opts.ExitCode) + } +} + +func ExitOnEOL(opts flag.Options, m types.Metadata) { + if opts.ExitOnEOL != 0 && m.OS != nil && m.OS.Eosl { + log.Logger.Errorf("Detected EOL OS: %s %s", m.OS.Family, m.OS.Name) + os.Exit(opts.ExitOnEOL) + } +} diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go new file mode 100644 index 000000000000..03b8f144170a --- /dev/null +++ b/pkg/commands/server/run.go @@ -0,0 +1,63 @@ +package server + +import ( + "context" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/commands/operation" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/module" + rpcServer "github.com/aquasecurity/trivy/pkg/rpc/server" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +// Run runs the scan +func Run(ctx context.Context, opts flag.Options) (err error) { + if err = log.InitLogger(opts.Debug, opts.Quiet); err != nil { + return xerrors.Errorf("failed to initialize a logger: %w", err) + } + + // configure cache dir + fsutils.SetCacheDir(opts.CacheDir) + cache, err := operation.NewCache(opts.CacheOptions) + if err != nil { + return xerrors.Errorf("server cache error: %w", err) + } + defer cache.Close() + log.Logger.Debugf("cache dir: %s", fsutils.CacheDir()) + + if opts.Reset { + return cache.ClearDB() + } + + // download the database file + if err = operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, + true, opts.SkipDBUpdate, opts.RegistryOpts()); err != nil { + return err + } + + if opts.DownloadDBOnly { + return nil + } + + if err = db.Init(opts.CacheDir); err != nil { + return xerrors.Errorf("error in vulnerability DB initialize: %w", err) + } + + // Initialize WASM modules + m, err := module.NewManager(ctx, module.Options{ + Dir: opts.ModuleDir, + EnabledModules: opts.EnabledModules, + }) + if err != nil { + return xerrors.Errorf("WASM module error: %w", err) + } + m.Register() + + server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader, + opts.DBRepository, opts.RegistryOpts()) + return server.ListenAndServe(ctx, cache, opts.SkipDBUpdate) +} diff --git a/pkg/commands/testdata/db/metadata.json b/pkg/commands/testdata/db/metadata.json new file mode 100644 index 000000000000..4710ea229b3d --- /dev/null +++ b/pkg/commands/testdata/db/metadata.json @@ -0,0 +1,7 @@ +{ + "Version": 2, + "NextUpdate": "2022-03-02T12:07:07.99504023Z", + "UpdatedAt": "2022-03-02T06:07:07.99504083Z", + "DownloadedAt": "2022-03-02T10:03:38.383312Z" +} + diff --git a/pkg/commands/testdata/java-db/metadata.json b/pkg/commands/testdata/java-db/metadata.json new file mode 100644 index 000000000000..67ccfdf8b3be --- /dev/null +++ b/pkg/commands/testdata/java-db/metadata.json @@ -0,0 +1 @@ +{"Version":1,"NextUpdate":"2023-03-17T00:47:02.774253254Z","UpdatedAt":"2023-03-14T00:47:02.774253754Z","DownloadedAt":"2023-03-14T03:04:55.058541039Z"} diff --git a/pkg/commands/testdata/policy/metadata.json b/pkg/commands/testdata/policy/metadata.json new file mode 100644 index 000000000000..7dfb4dd56692 --- /dev/null +++ b/pkg/commands/testdata/policy/metadata.json @@ -0,0 +1 @@ +{"Digest":"sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd","DownloadedAt":"2023-03-01T17:06:08.191725-08:00"} \ No newline at end of file diff --git a/pkg/compliance/report/json.go b/pkg/compliance/report/json.go new file mode 100644 index 000000000000..8d58f25106b5 --- /dev/null +++ b/pkg/compliance/report/json.go @@ -0,0 +1,41 @@ +package report + +import ( + "encoding/json" + "fmt" + "io" + + "golang.org/x/xerrors" +) + +type JSONWriter struct { + Output io.Writer + Report string +} + +// Write writes the results in JSON format +func (jw JSONWriter) Write(report *ComplianceReport) error { + var output []byte + var err error + + var v interface{} + switch jw.Report { + case allReport: + v = report + case summaryReport: + v = BuildSummary(report) + default: + return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, jw.Report) + } + + output, err = json.MarshalIndent(v, "", " ") + if err != nil { + return xerrors.Errorf("failed to marshal json: %w", err) + } + + if _, err = fmt.Fprintln(jw.Output, string(output)); err != nil { + return xerrors.Errorf("failed to write json: %w", err) + } + + return nil +} diff --git a/pkg/compliance/report/json_test.go b/pkg/compliance/report/json_test.go new file mode 100644 index 000000000000..31680b4ff513 --- /dev/null +++ b/pkg/compliance/report/json_test.go @@ -0,0 +1,91 @@ +package report_test + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/compliance/report" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestJSONWriter_Write(t *testing.T) { + input := &report.ComplianceReport{ + ID: "1234", + Title: "NSA", + RelatedResources: []string{"https://example.com"}, + Results: []*report.ControlCheckResult{ + { + ID: "1.0", + Name: "Non-root containers", + Severity: "MEDIUM", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-KSV012", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + }, + { + ID: "1.1", + Name: "Immutable container file systems", + Severity: "LOW", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-KSV013", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + }, + }, + } + + tests := []struct { + name string + reportType string + input *report.ComplianceReport + want string + }{ + { + name: "build summary json output report", + reportType: "summary", + input: input, + want: filepath.Join("testdata", "summary.json"), + }, + { + name: "build full json output report", + reportType: "all", + input: input, + want: filepath.Join("testdata", "all.json"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf := new(bytes.Buffer) + tr := report.JSONWriter{ + Report: tt.reportType, + Output: buf, + } + err := tr.Write(tt.input) + require.NoError(t, err) + + want, err := os.ReadFile(tt.want) + require.NoError(t, err) + + assert.JSONEq(t, string(want), buf.String()) + }) + } +} diff --git a/pkg/compliance/report/report.go b/pkg/compliance/report/report.go new file mode 100644 index 000000000000..d4b31668ed6d --- /dev/null +++ b/pkg/compliance/report/report.go @@ -0,0 +1,137 @@ +package report + +import ( + "context" + "io" + + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/compliance/spec" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + allReport = "all" + summaryReport = "summary" +) + +type Option struct { + Format types.Format + Report string + Output io.Writer + Severities []dbTypes.Severity + ColumnHeading []string +} + +// ComplianceReport represents a kubernetes scan report +type ComplianceReport struct { + ID string + Title string + Description string + Version string + RelatedResources []string + Results []*ControlCheckResult +} + +type ControlCheckResult struct { + ID string + Name string + Description string + DefaultStatus iacTypes.ControlStatus `json:",omitempty"` + Severity string + Results types.Results +} + +// SummaryReport represents a kubernetes scan report with consolidated findings +type SummaryReport struct { + SchemaVersion int `json:",omitempty"` + ID string + Title string + SummaryControls []ControlCheckSummary `json:",omitempty"` +} + +type ControlCheckSummary struct { + ID string + Name string + Severity string + TotalFail *int `json:",omitempty"` +} + +// Writer defines the result write operation +type Writer interface { + Write(ComplianceReport) error +} + +// Write writes the results in the given format +func Write(ctx context.Context, report *ComplianceReport, option Option) error { + switch option.Format { + case types.FormatJSON: + jwriter := JSONWriter{ + Output: option.Output, + Report: option.Report, + } + return jwriter.Write(report) + case types.FormatTable: + if !report.empty() { + complianceWriter := &TableWriter{ + Output: option.Output, + Report: option.Report, + Severities: option.Severities, + } + err := complianceWriter.Write(ctx, report) + if err != nil { + return err + } + } + return nil + default: + return xerrors.Errorf(`unknown format %q. Use "json" or "table"`, option.Format) + } +} + +func (r ComplianceReport) empty() bool { + return len(r.Results) == 0 +} + +// buildControlCheckResults create compliance results data +func buildControlCheckResults(checksMap map[string]types.Results, controls []iacTypes.Control) []*ControlCheckResult { + var complianceResults []*ControlCheckResult + for _, control := range controls { + var results types.Results + for _, c := range control.Checks { + results = append(results, checksMap[c.ID]...) + } + complianceResults = append(complianceResults, &ControlCheckResult{ + Name: control.Name, + ID: control.ID, + Description: control.Description, + Severity: string(control.Severity), + DefaultStatus: control.DefaultStatus, + Results: results, + }) + } + return complianceResults +} + +// buildComplianceReportResults create compliance results data +func buildComplianceReportResults(checksMap map[string]types.Results, s iacTypes.Spec) *ComplianceReport { + controlCheckResult := buildControlCheckResults(checksMap, s.Controls) + return &ComplianceReport{ + ID: s.ID, + Title: s.Title, + Description: s.Description, + Version: s.Version, + RelatedResources: s.RelatedResources, + Results: controlCheckResult, + } +} + +func BuildComplianceReport(scanResults []types.Results, cs spec.ComplianceSpec) (*ComplianceReport, error) { + // aggregate checks by ID + aggregateChecksByID := spec.AggregateAllChecksBySpecID(scanResults, cs) + + // build compliance report results + return buildComplianceReportResults(aggregateChecksByID, cs.Spec), nil +} diff --git a/pkg/compliance/report/report_test.go b/pkg/compliance/report/report_test.go new file mode 100644 index 000000000000..e673bd50a24d --- /dev/null +++ b/pkg/compliance/report/report_test.go @@ -0,0 +1,243 @@ +package report_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/compliance/report" + "github.com/aquasecurity/trivy/pkg/compliance/spec" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestBuildComplianceReport(t *testing.T) { + type args struct { + scanResults []types.Results + cs spec.ComplianceSpec + } + tests := []struct { + name string + args args + want *report.ComplianceReport + wantErr assert.ErrorAssertionFunc + }{ + { + name: "happy", + args: args{ + scanResults: []types.Results{ + { + { + Target: "Deployment/metrics-server", + Class: types.ClassConfig, + Type: ftypes.Kubernetes, + MisconfSummary: &types.MisconfSummary{ + Successes: 1, + Failures: 0, + Exceptions: 0, + }, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Kubernetes Security Check", + ID: "KSV001", + AVDID: "AVD-KSV-0001", + Title: "Process can elevate its own privileges", + Description: "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", + Message: "Container 'metrics-server' of Deployment 'metrics-server' should set 'securityContext.allowPrivilegeEscalation' to false", + Namespace: "builtin.kubernetes.KSV001", + Query: "data.builtin.kubernetes.KSV001.deny", + Resolution: "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.", + Severity: dbTypes.SeverityMedium.String(), + PrimaryURL: "https://avd.aquasec.com/misconfig/ksv001", + References: []string{ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv001", + }, + Status: types.MisconfStatusPassed, + }, + { + Type: "Kubernetes Security Check", + ID: "KSV002", + AVDID: "AVD-KSV-9999", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + { + { + Target: "rancher/metrics-server:v0.3.6 (debian 9.9)", + Class: types.ClassOSPkg, + Type: "debian", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "DLA-2424-1", + VendorIDs: []string{"DLA-2424-1"}, + PkgName: "tzdata", + InstalledVersion: "2019a-0+deb9u1", + FixedVersion: "2020d-0+deb9u1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Debian, + Name: "Debian Security Tracker", + URL: "https://salsa.debian.org/security-tracker-team/security-tracker", + }, + Vulnerability: dbTypes.Vulnerability{ + Title: "tzdata - new upstream version", + Severity: dbTypes.SeverityUnknown.String(), + }, + }, + }, + }, + }, + }, + cs: spec.ComplianceSpec{ + Spec: iacTypes.Spec{ + ID: "1234", + Title: "NSA", + Description: "National Security Agency - Kubernetes Hardening Guidance", + Version: "1.0", + RelatedResources: []string{ + "https://example.com", + }, + Controls: []iacTypes.Control{ + { + ID: "1.0", + Name: "Non-root containers", + Description: "Check that container is not running as root", + Severity: "MEDIUM", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-KSV-0001"}, + }, + }, + { + ID: "1.1", + Name: "Immutable container file systems", + Description: "Check that container root file system is immutable", + Severity: "LOW", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-KSV-0002"}, + }, + }, + { + ID: "1.2", + Name: "tzdata - new upstream version", + Description: "Bad tzdata package", + Severity: "CRITICAL", + Checks: []iacTypes.SpecCheck{ + {ID: "DLA-2424-1"}, + }, + }, + }, + }, + }, + }, + want: &report.ComplianceReport{ + ID: "1234", + Title: "NSA", + Description: "National Security Agency - Kubernetes Hardening Guidance", + Version: "1.0", + RelatedResources: []string{ + "https://example.com", + }, + Results: []*report.ControlCheckResult{ + { + ID: "1.0", + Name: "Non-root containers", + Description: "Check that container is not running as root", + Severity: "MEDIUM", + Results: types.Results{ + { + Target: "Deployment/metrics-server", + Class: types.ClassConfig, + Type: ftypes.Kubernetes, + MisconfSummary: &types.MisconfSummary{ + Successes: 1, + Failures: 0, + Exceptions: 0, + }, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Kubernetes Security Check", + ID: "KSV001", + AVDID: "AVD-KSV-0001", + Title: "Process can elevate its own privileges", + Description: "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", + Message: "Container 'metrics-server' of Deployment 'metrics-server' should set 'securityContext.allowPrivilegeEscalation' to false", + Namespace: "builtin.kubernetes.KSV001", + Query: "data.builtin.kubernetes.KSV001.deny", + Resolution: "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.", + Severity: dbTypes.SeverityMedium.String(), + PrimaryURL: "https://avd.aquasec.com/misconfig/ksv001", + References: []string{ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv001", + }, + Status: types.MisconfStatusPassed, + }, + }, + }, + }, + }, + { + ID: "1.1", + Name: "Immutable container file systems", + Description: "Check that container root file system is immutable", + Severity: "LOW", + Results: nil, + }, + { + ID: "1.2", + Name: "tzdata - new upstream version", + Description: "Bad tzdata package", + Severity: "CRITICAL", + Results: types.Results{ + { + Target: "rancher/metrics-server:v0.3.6 (debian 9.9)", + Class: types.ClassOSPkg, + Type: "debian", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "DLA-2424-1", + VendorIDs: []string{"DLA-2424-1"}, + PkgName: "tzdata", + InstalledVersion: "2019a-0+deb9u1", + FixedVersion: "2020d-0+deb9u1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Debian, + Name: "Debian Security Tracker", + URL: "https://salsa.debian.org/security-tracker-team/security-tracker", + }, + Vulnerability: dbTypes.Vulnerability{ + Title: "tzdata - new upstream version", + Severity: dbTypes.SeverityUnknown.String(), + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := report.BuildComplianceReport(tt.args.scanResults, tt.args.cs) + if !tt.wantErr(t, err, fmt.Sprintf("BuildComplianceReport(%v, %v)", tt.args.scanResults, tt.args.cs)) { + return + } + assert.Equalf(t, tt.want, got, "BuildComplianceReport(%v, %v)", tt.args.scanResults, tt.args.cs) + }) + } +} diff --git a/pkg/compliance/report/summary.go b/pkg/compliance/report/summary.go new file mode 100644 index 000000000000..0262249e7214 --- /dev/null +++ b/pkg/compliance/report/summary.go @@ -0,0 +1,111 @@ +package report + +import ( + "fmt" + "io" + "strconv" + "strings" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/table" +) + +func BuildSummary(cr *ComplianceReport) *SummaryReport { + var ccma []ControlCheckSummary + for _, control := range cr.Results { + ccm := ControlCheckSummary{ + ID: control.ID, + Name: control.Name, + Severity: control.Severity, + } + if !strings.Contains(control.Name, "Manual") { + ccm.TotalFail = lo.ToPtr(len(control.Results)) + } + ccma = append(ccma, ccm) + } + return &SummaryReport{ + ID: cr.ID, + Title: cr.Title, + SummaryControls: ccma, + } +} + +type SummaryWriter struct { + Output io.Writer +} + +func NewSummaryWriter(output io.Writer) SummaryWriter { + return SummaryWriter{ + Output: output, + } +} + +// Write writes the results in a summarized table format +func (s SummaryWriter) Write(report *ComplianceReport) error { + if _, err := fmt.Fprintln(s.Output); err != nil { + return xerrors.Errorf("failed to write summary report: %w", err) + } + + if _, err := fmt.Fprintf(s.Output, "Summary Report for compliance: %s\n", report.Title); err != nil { + return xerrors.Errorf("failed to write summary report: %w", err) + } + sr := BuildSummary(report) + t := table.New(s.Output) + t.SetRowLines(false) + configureHeader(t, s.columns()) + + for _, summaryControl := range sr.SummaryControls { + rowParts := s.generateSummary(summaryControl) + t.AddRow(rowParts...) + } + + t.Render() + + return nil +} + +func (s SummaryWriter) columns() []string { + return []string{ + ControlIDColumn, + SeverityColumn, + ControlNameColumn, + StatusColumn, + IssuesColumn, + } +} + +func (s SummaryWriter) generateSummary(summaryControls ControlCheckSummary) []string { + // "-" means manual checks + numOfIssues := "-" + status := "-" + if summaryControls.TotalFail != nil { + if *summaryControls.TotalFail == 0 { + status = "PASS" + } else { + status = "FAIL" + } + numOfIssues = strconv.Itoa(*summaryControls.TotalFail) + } + return []string{ + summaryControls.ID, + summaryControls.Severity, + summaryControls.Name, + status, + numOfIssues, + } +} + +func configureHeader(t *table.Table, columnHeading []string) { + headerAlignment := []table.Alignment{ + table.AlignLeft, + table.AlignCenter, + table.AlignLeft, + table.AlignCenter, + table.AlignCenter, + } + t.SetHeaders(columnHeading...) + t.SetAlignment(headerAlignment...) + t.SetAutoMergeHeaders(true) +} diff --git a/pkg/compliance/report/summary_test.go b/pkg/compliance/report/summary_test.go new file mode 100644 index 000000000000..1dbf862d59ba --- /dev/null +++ b/pkg/compliance/report/summary_test.go @@ -0,0 +1,167 @@ +package report_test + +import ( + "testing" + + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/compliance/report" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestBuildSummary(t *testing.T) { + tests := []struct { + name string + reportType string + input *report.ComplianceReport + want *report.SummaryReport + }{ + { + name: "build report summary config only", + reportType: "summary", + input: &report.ComplianceReport{ + ID: "1234", + Title: "NSA", + RelatedResources: []string{"https://example.com"}, + Results: []*report.ControlCheckResult{ + { + ID: "1.0", + Name: "Non-root containers", + Severity: "MEDIUM", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-KSV012", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + }, + { + ID: "1.1", + Name: "Immutable container file systems", + Severity: "LOW", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-KSV013", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + }, + }, + }, + want: &report.SummaryReport{ + SchemaVersion: 0, + ID: "1234", + Title: "NSA", + SummaryControls: []report.ControlCheckSummary{ + { + ID: "1.0", + Name: "Non-root containers", + Severity: "MEDIUM", + TotalFail: lo.ToPtr(1), + }, + { + ID: "1.1", + Name: "Immutable container file systems", + Severity: "LOW", + TotalFail: lo.ToPtr(1), + }, + }, + }, + }, + { + name: "build full json output report", + reportType: "all", + input: &report.ComplianceReport{ + ID: "1234", + Title: "NSA", + RelatedResources: []string{"https://example.com"}, + Results: []*report.ControlCheckResult{ + { + ID: "1.0", + Name: "Non-root containers", + Severity: "MEDIUM", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-KSV012", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + }, + { + ID: "1.1", + Name: "Immutable container file systems", + Severity: "LOW", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-KSV013", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + }, + { + ID: "1.2", + Name: "tzdata - new upstream version", + Severity: "LOW", + Results: types.Results{ + { + Vulnerabilities: []types.DetectedVulnerability{ + {VulnerabilityID: "CVE-9999-0001"}, + {VulnerabilityID: "CVE-9999-0002"}, + }, + }, + }, + }, + }, + }, + want: &report.SummaryReport{ + SchemaVersion: 0, + ID: "1234", + Title: "NSA", + SummaryControls: []report.ControlCheckSummary{ + { + ID: "1.0", + Name: "Non-root containers", + Severity: "MEDIUM", + TotalFail: lo.ToPtr(1), + }, + { + ID: "1.1", + Name: "Immutable container file systems", + Severity: "LOW", + TotalFail: lo.ToPtr(1), + }, + { + ID: "1.2", + Name: "tzdata - new upstream version", + Severity: "LOW", + TotalFail: lo.ToPtr(1), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := report.BuildSummary(tt.input) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/compliance/report/table.go b/pkg/compliance/report/table.go new file mode 100644 index 000000000000..f82571c9b718 --- /dev/null +++ b/pkg/compliance/report/table.go @@ -0,0 +1,51 @@ +package report + +import ( + "context" + "io" + + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + pkgReport "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" +) + +type TableWriter struct { + Report string + Output io.Writer + Severities []dbTypes.Severity + ColumnHeading []string +} + +const ( + ControlIDColumn = "ID" + SeverityColumn = "Severity" + ControlNameColumn = "Control Name" + StatusColumn = "Status" + IssuesColumn = "Issues" +) + +func (tw TableWriter) Write(ctx context.Context, report *ComplianceReport) error { + switch tw.Report { + case allReport: + t := pkgReport.Writer{ + Output: tw.Output, + Severities: tw.Severities, + } + for _, cr := range report.Results { + r := types.Report{Results: cr.Results} + err := t.Write(ctx, r) + if err != nil { + return err + } + } + case summaryReport: + writer := NewSummaryWriter(tw.Output) + return writer.Write(report) + default: + return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, tw.Report) + } + + return nil +} diff --git a/pkg/compliance/report/table_test.go b/pkg/compliance/report/table_test.go new file mode 100644 index 000000000000..839909a94359 --- /dev/null +++ b/pkg/compliance/report/table_test.go @@ -0,0 +1,87 @@ +package report_test + +import ( + "bytes" + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/compliance/report" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestTableWriter_Write(t *testing.T) { + + tests := []struct { + name string + reportType string + input *report.ComplianceReport + want string + }{ + { + name: "build summary table", + reportType: "summary", + input: &report.ComplianceReport{ + ID: "1234", + Title: "NSA", + RelatedResources: []string{"https://example.com"}, + Results: []*report.ControlCheckResult{ + { + ID: "1.0", + Name: "Non-root containers", + Severity: "MEDIUM", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-KSV012", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + }, + { + ID: "1.1", + Name: "Immutable container file systems", + Severity: "LOW", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-KSV013", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + }, + }, + }, + want: filepath.Join("testdata", "table_summary.txt"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf := new(bytes.Buffer) + tr := report.TableWriter{ + Report: tt.reportType, + Output: buf, + } + err := tr.Write(context.Background(), tt.input) + require.NoError(t, err) + + want, err := os.ReadFile(tt.want) + want = bytes.ReplaceAll(want, []byte("\r"), []byte("")) + + require.NoError(t, err) + + assert.Equal(t, string(want), buf.String()) + }) + } +} diff --git a/pkg/compliance/report/testdata/all.json b/pkg/compliance/report/testdata/all.json new file mode 100644 index 000000000000..459de9998d5c --- /dev/null +++ b/pkg/compliance/report/testdata/all.json @@ -0,0 +1,57 @@ +{ + "ID": "1234", + "Title": "NSA", + "Description": "", + "Version": "", + "RelatedResources": [ + "https://example.com" + ], + "Results": [ + { + "ID": "1.0", + "Name": "Non-root containers", + "Description": "", + "Severity": "MEDIUM", + "Results": [ + { + "Target": "", + "Misconfigurations": [ + { + "AVDID": "AVD-KSV012", + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Code": { + "Lines": null + } + } + } + ] + } + ] + }, + { + "ID": "1.1", + "Name": "Immutable container file systems", + "Description": "", + "Severity": "LOW", + "Results": [ + { + "Target": "", + "Misconfigurations": [ + { + "AVDID": "AVD-KSV013", + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Code": { + "Lines": null + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/compliance/report/testdata/summary.json b/pkg/compliance/report/testdata/summary.json new file mode 100644 index 000000000000..2ee48756272b --- /dev/null +++ b/pkg/compliance/report/testdata/summary.json @@ -0,0 +1,18 @@ +{ + "ID": "1234", + "Title": "NSA", + "SummaryControls": [ + { + "ID": "1.0", + "Name": "Non-root containers", + "Severity": "MEDIUM", + "TotalFail": 1 + }, + { + "ID": "1.1", + "Name": "Immutable container file systems", + "Severity": "LOW", + "TotalFail": 1 + } + ] +} \ No newline at end of file diff --git a/pkg/compliance/report/testdata/table_all.txt b/pkg/compliance/report/testdata/table_all.txt new file mode 100644 index 000000000000..8a789b2ceaa5 --- /dev/null +++ b/pkg/compliance/report/testdata/table_all.txt @@ -0,0 +1,52 @@ + +Deployment/metrics-server (kubernetes) +====================================== +Tests: 1 (SUCCESSES: 1, FAILURES: 0, EXCEPTIONS: 0) +Failures: 0 () + +MEDIUM: Container 'metrics-server' of Deployment 'metrics-server' should set 'securityContext.allowPrivilegeEscalation' to false +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node. + +See https://avd.aquasec.com/misconfig/ksv001 +──────────────────────────────────────── + Deployment/metrics-server:132-140 +──────────────────────────────────────── + 132 ┌ - image: rancher/metrics-server:v0.3.6 + 133 │ imagePullPolicy: IfNotPresent + 134 │ name: metrics-server + 135 │ resources: {} + 136 │ terminationMessagePath: /dev/termination-log + 137 │ terminationMessagePolicy: File + 138 │ volumeMounts: + 139 │ - mountPath: /tmp + 140 â”” name: tmp-dir +──────────────────────────────────────── + + + +Deployment/metrics-server (kubernetes) +====================================== +Tests: 1 (SUCCESSES: 1, FAILURES: 0, EXCEPTIONS: 0) +Failures: 0 () + +LOW: Container 'metrics-server' of Deployment 'metrics-server' should add 'ALL' to 'securityContext.capabilities.drop' +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +The container should drop all default capabilities and add only those that are needed for its execution. + +See https://avd.aquasec.com/misconfig/ksv003 +──────────────────────────────────────── + Deployment/metrics-server:132-140 +──────────────────────────────────────── + 132 ┌ - image: rancher/metrics-server:v0.3.6 + 133 │ imagePullPolicy: IfNotPresent + 134 │ name: metrics-server + 135 │ resources: {} + 136 │ terminationMessagePath: /dev/termination-log + 137 │ terminationMessagePolicy: File + 138 │ volumeMounts: + 139 │ - mountPath: /tmp + 140 â”” name: tmp-dir +──────────────────────────────────────── + + diff --git a/pkg/compliance/report/testdata/table_summary.txt b/pkg/compliance/report/testdata/table_summary.txt new file mode 100644 index 000000000000..5600ae6a8cc2 --- /dev/null +++ b/pkg/compliance/report/testdata/table_summary.txt @@ -0,0 +1,8 @@ + +Summary Report for compliance: NSA +┌─────┬──────────┬──────────────────────────────────┬────────┬────────┠+│ ID │ Severity │ Control Name │ Status │ Issues │ +├─────┼──────────┼──────────────────────────────────┼────────┼────────┤ +│ 1.0 │ MEDIUM │ Non-root containers │ FAIL │ 1 │ +│ 1.1 │ LOW │ Immutable container file systems │ FAIL │ 1 │ +└─────┴──────────┴──────────────────────────────────┴────────┴────────┘ diff --git a/pkg/compliance/spec/compliance.go b/pkg/compliance/spec/compliance.go new file mode 100644 index 000000000000..0f219dca7c60 --- /dev/null +++ b/pkg/compliance/spec/compliance.go @@ -0,0 +1,93 @@ +package spec + +import ( + "fmt" + "os" + "strings" + + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + sp "github.com/aquasecurity/trivy-policies/pkg/spec" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +type Severity string + +// ComplianceSpec represent the compliance specification +type ComplianceSpec struct { + Spec iacTypes.Spec `yaml:"spec"` +} + +const ( + FailStatus iacTypes.ControlStatus = "FAIL" + PassStatus iacTypes.ControlStatus = "PASS" + WarnStatus iacTypes.ControlStatus = "WARN" +) + +// Scanners reads spec control and determines the scanners by check ID prefix +func (cs *ComplianceSpec) Scanners() (types.Scanners, error) { + scannerTypes := make(map[types.Scanner]struct{}) + for _, control := range cs.Spec.Controls { + for _, check := range control.Checks { + scannerType := scannerByCheckID(check.ID) + if scannerType == types.UnknownScanner { + return nil, xerrors.Errorf("unsupported check ID: %s", check.ID) + } + scannerTypes[scannerType] = struct{}{} + } + } + return maps.Keys(scannerTypes), nil +} + +// CheckIDs return list of compliance check IDs +func (cs *ComplianceSpec) CheckIDs() map[types.Scanner][]string { + checkIDsMap := make(map[types.Scanner][]string) + for _, control := range cs.Spec.Controls { + for _, check := range control.Checks { + scannerType := scannerByCheckID(check.ID) + checkIDsMap[scannerType] = append(checkIDsMap[scannerType], check.ID) + } + } + return checkIDsMap +} + +func scannerByCheckID(checkID string) types.Scanner { + checkID = strings.ToLower(checkID) + switch { + case strings.HasPrefix(checkID, "cve-") || strings.HasPrefix(checkID, "dla-"): + return types.VulnerabilityScanner + case strings.HasPrefix(checkID, "avd-"): + return types.MisconfigScanner + case strings.HasPrefix(checkID, "vuln-"): // custom id for filtering vulnerabilities by severity + return types.VulnerabilityScanner + case strings.HasPrefix(checkID, "secret-"): // custom id for filtering secrets by severity + return types.SecretScanner + default: + return types.UnknownScanner + } +} + +// GetComplianceSpec accepct compliance flag name/path and return builtin or file system loaded spec +func GetComplianceSpec(specNameOrPath string) (ComplianceSpec, error) { + var b []byte + var err error + if strings.HasPrefix(specNameOrPath, "@") { + b, err = os.ReadFile(strings.TrimPrefix(specNameOrPath, "@")) + if err != nil { + return ComplianceSpec{}, fmt.Errorf("error retrieving compliance spec from path: %w", err) + } + } else { + // TODO: GetSpecByName() should return []byte + b = []byte(sp.NewSpecLoader().GetSpecByName(specNameOrPath)) + } + + var complianceSpec ComplianceSpec + if err = yaml.Unmarshal(b, &complianceSpec); err != nil { + return ComplianceSpec{}, xerrors.Errorf("spec yaml decode error: %w", err) + } + return complianceSpec, nil + +} diff --git a/pkg/compliance/spec/compliance_test.go b/pkg/compliance/spec/compliance_test.go new file mode 100644 index 000000000000..a4ee4961973e --- /dev/null +++ b/pkg/compliance/spec/compliance_test.go @@ -0,0 +1,241 @@ +package spec_test + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/compliance/spec" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestComplianceSpec_Scanners(t *testing.T) { + tests := []struct { + name string + spec iacTypes.Spec + want types.Scanners + wantErr assert.ErrorAssertionFunc + }{ + { + name: "get config scanner type by check id prefix", + spec: iacTypes.Spec{ + ID: "1234", + Title: "NSA", + Description: "National Security Agency - Kubernetes Hardening Guidance", + RelatedResources: []string{ + "https://example.com", + }, + Version: "1.0", + Controls: []iacTypes.Control{ + { + Name: "Non-root containers", + Description: "Check that container is not running as root", + ID: "1.0", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-KSV012"}, + }, + }, + { + Name: "Check that encryption resource has been set", + Description: "Control checks whether encryption resource has been set", + ID: "1.1", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-1.2.31"}, + {ID: "AVD-1.2.32"}, + }, + }, + }, + }, + want: []types.Scanner{types.MisconfigScanner}, + wantErr: assert.NoError, + }, + { + name: "get config and vuln scanners types by check id prefix", + spec: iacTypes.Spec{ + ID: "1234", + Title: "NSA", + Description: "National Security Agency - Kubernetes Hardening Guidance", + RelatedResources: []string{ + "https://example.com", + }, + Version: "1.0", + Controls: []iacTypes.Control{ + { + Name: "Non-root containers", + Description: "Check that container is not running as root", + ID: "1.0", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-KSV012"}, + }, + }, + { + Name: "Check that encryption resource has been set", + Description: "Control checks whether encryption resource has been set", + ID: "1.1", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-1.2.31"}, + {ID: "AVD-1.2.32"}, + }, + }, + { + Name: "Ensure no critical vulnerabilities", + Description: "Control checks whether critical vulnerabilities are not found", + ID: "7.0", + Checks: []iacTypes.SpecCheck{ + {ID: "CVE-9999-9999"}, + }, + }, + }, + }, + want: []types.Scanner{ + types.MisconfigScanner, + types.VulnerabilityScanner, + }, + wantErr: assert.NoError, + }, + { + name: "unknown prefix", + spec: iacTypes.Spec{ + ID: "1234", + Title: "NSA", + Description: "National Security Agency - Kubernetes Hardening Guidance", + RelatedResources: []string{ + "https://example.com", + }, + Version: "1.0", + Controls: []iacTypes.Control{ + { + Name: "Unknown", + ID: "1.0", + Checks: []iacTypes.SpecCheck{ + {ID: "UNKNOWN-001"}, + }, + }, + }, + }, + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cs := &spec.ComplianceSpec{ + Spec: tt.spec, + } + got, err := cs.Scanners() + if !tt.wantErr(t, err, "Scanners()") { + return + } + sort.Slice(got, func(i, j int) bool { + return got[i] < got[j] + }) // for consistency + assert.Equalf(t, tt.want, got, "Scanners()") + }) + } +} + +func TestComplianceSpec_CheckIDs(t *testing.T) { + tests := []struct { + name string + spec iacTypes.Spec + want map[types.Scanner][]string + }{ + { + name: "get config scanner type by check id prefix", + spec: iacTypes.Spec{ + ID: "1234", + Title: "NSA", + Description: "National Security Agency - Kubernetes Hardening Guidance", + RelatedResources: []string{ + "https://example.com", + }, + Version: "1.0", + Controls: []iacTypes.Control{ + { + Name: "Non-root containers", + Description: "Check that container is not running as root", + ID: "1.0", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-KSV012"}, + }, + }, + { + Name: "Check that encryption resource has been set", + Description: "Control checks whether encryption resource has been set", + ID: "1.1", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-1.2.31"}, + {ID: "AVD-1.2.32"}, + }, + }, + }, + }, + want: map[types.Scanner][]string{ + types.MisconfigScanner: { + "AVD-KSV012", + "AVD-1.2.31", + "AVD-1.2.32", + }, + }, + }, + { + name: "get config and vuln scanners types by check id prefix", + spec: iacTypes.Spec{ + ID: "1234", + Title: "NSA", + Description: "National Security Agency - Kubernetes Hardening Guidance", + RelatedResources: []string{ + "https://example.com", + }, + Version: "1.0", + Controls: []iacTypes.Control{ + { + Name: "Non-root containers", + Description: "Check that container is not running as root", + ID: "1.0", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-KSV012"}, + }, + }, + { + Name: "Check that encryption resource has been set", + Description: "Control checks whether encryption resource has been set", + ID: "1.1", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-1.2.31"}, + {ID: "AVD-1.2.32"}, + }, + }, + { + Name: "Ensure no critical vulnerabilities", + Description: "Control checks whether critical vulnerabilities are not found", + ID: "7.0", + Checks: []iacTypes.SpecCheck{ + {ID: "CVE-9999-9999"}, + }, + }, + }, + }, + want: map[types.Scanner][]string{ + types.MisconfigScanner: { + "AVD-KSV012", + "AVD-1.2.31", + "AVD-1.2.32", + }, + types.VulnerabilityScanner: { + "CVE-9999-9999", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cs := &spec.ComplianceSpec{ + Spec: tt.spec, + } + got := cs.CheckIDs() + assert.Equalf(t, tt.want, got, "CheckIDs()") + }) + } +} diff --git a/pkg/compliance/spec/custom.go b/pkg/compliance/spec/custom.go new file mode 100644 index 000000000000..8e7ed6679340 --- /dev/null +++ b/pkg/compliance/spec/custom.go @@ -0,0 +1,74 @@ +package spec + +import ( + "github.com/samber/lo" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +// We might be going to rewrite these functions in Rego, +// but we'll keep them for now until we need flexibility. +var customIDs = map[string]func(types.Result) types.Result{ + "VULN-CRITICAL": filterCriticalVulns, + "VULN-HIGH": filterHighVulns, + "SECRET-CRITICAL": filterCriticalSecrets, + "SECRET-HIGH": filterHighSecrets, +} + +func mapCustomIDsToFilteredResults(result types.Result, checkIDs map[types.Scanner][]string, + mapCheckByID map[string]types.Results) { + for _, ids := range checkIDs { + for _, id := range ids { + filterFunc, ok := customIDs[id] + if !ok { + continue + } + filtered := filterFunc(result) + if filtered.IsEmpty() { + continue + } + mapCheckByID[id] = types.Results{filtered} + } + } +} + +func filterCriticalVulns(result types.Result) types.Result { + return filterVulns(result, dbTypes.SeverityCritical) +} + +func filterHighVulns(result types.Result) types.Result { + return filterVulns(result, dbTypes.SeverityHigh) +} + +func filterVulns(result types.Result, severity dbTypes.Severity) types.Result { + filtered := lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { + return vuln.Severity == severity.String() + }) + return types.Result{ + Target: result.Target, + Class: result.Class, + Type: result.Type, + Vulnerabilities: filtered, + } +} + +func filterCriticalSecrets(result types.Result) types.Result { + return filterSecrets(result, dbTypes.SeverityCritical) +} + +func filterHighSecrets(result types.Result) types.Result { + return filterSecrets(result, dbTypes.SeverityHigh) +} + +func filterSecrets(result types.Result, severity dbTypes.Severity) types.Result { + filtered := lo.Filter(result.Secrets, func(secret types.DetectedSecret, _ int) bool { + return secret.Severity == severity.String() + }) + return types.Result{ + Target: result.Target, + Class: result.Class, + Type: result.Type, + Secrets: filtered, + } +} diff --git a/pkg/compliance/spec/mapper.go b/pkg/compliance/spec/mapper.go new file mode 100644 index 000000000000..2efa488bff54 --- /dev/null +++ b/pkg/compliance/spec/mapper.go @@ -0,0 +1,71 @@ +package spec + +import ( + "golang.org/x/exp/slices" + + "github.com/aquasecurity/trivy/pkg/types" +) + +// MapSpecCheckIDToFilteredResults map spec check id to filtered scan results +func MapSpecCheckIDToFilteredResults(result types.Result, checkIDs map[types.Scanner][]string) map[string]types.Results { + mapCheckByID := make(map[string]types.Results) + for _, vuln := range result.Vulnerabilities { + // Skip irrelevant check IDs + if !slices.Contains(checkIDs[types.VulnerabilityScanner], vuln.VulnerabilityID) { + continue + } + mapCheckByID[vuln.VulnerabilityID] = append(mapCheckByID[vuln.VulnerabilityID], types.Result{ + Target: result.Target, + Class: result.Class, + Type: result.Type, + Vulnerabilities: []types.DetectedVulnerability{vuln}, + }) + } + for _, m := range result.Misconfigurations { + // Skip irrelevant check IDs + if !slices.Contains(checkIDs[types.MisconfigScanner], m.AVDID) { + continue + } + + mapCheckByID[m.AVDID] = append(mapCheckByID[m.AVDID], types.Result{ + Target: result.Target, + Class: result.Class, + Type: result.Type, + MisconfSummary: misconfigSummary(m), + Misconfigurations: []types.DetectedMisconfiguration{m}, + }) + } + + // Evaluate custom IDs + mapCustomIDsToFilteredResults(result, checkIDs, mapCheckByID) + + return mapCheckByID +} + +func misconfigSummary(misconfig types.DetectedMisconfiguration) *types.MisconfSummary { + rms := types.MisconfSummary{} + switch misconfig.Status { + case types.MisconfStatusPassed: + rms.Successes = 1 + case types.MisconfStatusFailure: + rms.Failures = 1 + case types.MisconfStatusException: + rms.Exceptions = 1 + } + return &rms +} + +// AggregateAllChecksBySpecID aggregates all scan results and map it to spec ids +func AggregateAllChecksBySpecID(multiResults []types.Results, cs ComplianceSpec) map[string]types.Results { + checkIDs := cs.CheckIDs() + complianceArr := make(map[string]types.Results, 0) + for _, resResult := range multiResults { + for _, result := range resResult { + m := MapSpecCheckIDToFilteredResults(result, checkIDs) + for id, checks := range m { + complianceArr[id] = append(complianceArr[id], checks...) + } + } + } + return complianceArr +} diff --git a/pkg/compliance/spec/mapper_test.go b/pkg/compliance/spec/mapper_test.go new file mode 100644 index 000000000000..62050d27ec47 --- /dev/null +++ b/pkg/compliance/spec/mapper_test.go @@ -0,0 +1,165 @@ +package spec_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/compliance/spec" + "github.com/aquasecurity/trivy/pkg/fanal/secret" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestMapSpecCheckIDToFilteredResults(t *testing.T) { + checkIDs := map[types.Scanner][]string{ + types.MisconfigScanner: { + "AVD-KSV012", + "AVD-1.2.31", + "AVD-1.2.32", + }, + types.VulnerabilityScanner: { + "CVE-9999-9999", + "VULN-CRITICAL", + }, + types.SecretScanner: { + "SECRET-CRITICAL", + }, + } + tests := []struct { + name string + checkIDs map[types.Scanner][]string + result types.Result + want map[string]types.Results + }{ + { + name: "misconfiguration", + checkIDs: checkIDs, + result: types.Result{ + Target: "target", + Class: types.ClassConfig, + Type: ftypes.Kubernetes, + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-KSV012", + Status: types.MisconfStatusFailure, + }, + { + AVDID: "AVD-KSV013", + Status: types.MisconfStatusFailure, + }, + { + AVDID: "AVD-1.2.31", + Status: types.MisconfStatusFailure, + }, + }, + }, + want: map[string]types.Results{ + "AVD-KSV012": { + { + Target: "target", + Class: types.ClassConfig, + Type: ftypes.Kubernetes, + MisconfSummary: &types.MisconfSummary{ + Successes: 0, + Failures: 1, + Exceptions: 0, + }, + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-KSV012", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + "AVD-1.2.31": { + { + Target: "target", + Class: types.ClassConfig, + Type: ftypes.Kubernetes, + MisconfSummary: &types.MisconfSummary{ + Successes: 0, + Failures: 1, + Exceptions: 0, + }, + Misconfigurations: []types.DetectedMisconfiguration{ + { + AVDID: "AVD-1.2.31", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + }, + }, + { + name: "secret", + checkIDs: checkIDs, + result: types.Result{ + Target: "target", + Class: types.ClassSecret, + Secrets: []types.DetectedSecret{ + { + RuleID: "aws-access-key-id", + Category: secret.CategoryAWS, + Severity: "CRITICAL", + Title: "AWS Access Key ID", + Code: ftypes.Code{ + Lines: []ftypes.Line{ + { + Number: 2, + Content: "AWS_ACCESS_KEY_ID=*****", + }, + }, + }, + }, + { + RuleID: "aws-account-id", + Category: secret.CategoryAWS, + Severity: "HIGH", + Title: "AWS Account ID", + Code: ftypes.Code{ + Lines: []ftypes.Line{ + { + Number: 1, + Content: "AWS_ACCOUNT_ID=*****", + }, + }, + }, + }, + }, + }, + want: map[string]types.Results{ + "SECRET-CRITICAL": { + { + Target: "target", + Class: types.ClassSecret, + Secrets: []types.DetectedSecret{ + { + RuleID: "aws-access-key-id", + Category: secret.CategoryAWS, + Severity: "CRITICAL", + Title: "AWS Access Key ID", + Code: ftypes.Code{ + Lines: []ftypes.Line{ + { + Number: 2, + Content: "AWS_ACCESS_KEY_ID=*****", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := spec.MapSpecCheckIDToFilteredResults(tt.result, tt.checkIDs) + assert.Equalf(t, tt.want, got, "MapSpecCheckIDToFilteredResults()") + }) + } +} diff --git a/pkg/db/db.go b/pkg/db/db.go index 9b32fceb8bbe..9ecb281b064e 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -1,144 +1,212 @@ package db import ( - "encoding/json" - "os" - "path/filepath" - - "github.com/knqyf263/trivy/pkg/log" + "context" + "errors" + "fmt" + "time" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote/transport" "golang.org/x/xerrors" + "k8s.io/utils/clock" - "github.com/knqyf263/trivy/pkg/utils" + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy-db/pkg/metadata" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/oci" +) - bolt "github.com/etcd-io/bbolt" +const ( + SchemaVersion = db.SchemaVersion + dbMediaType = "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip" ) var ( - db *bolt.DB - dbDir = filepath.Join(utils.CacheDir(), "db") + DefaultRepository = fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-db", db.SchemaVersion) + defaultRepository, _ = name.NewTag(DefaultRepository) ) -func Init() (err error) { - if err = os.MkdirAll(dbDir, 0700); err != nil { - return xerrors.Errorf("failed to mkdir: %w", err) +// Operation defines the DB operations +type Operation interface { + NeedsUpdate(cliVersion string, skip bool) (need bool, err error) + Download(ctx context.Context, dst string, opt types.RegistryOptions) (err error) +} + +type options struct { + artifact *oci.Artifact + clock clock.Clock + dbRepository name.Reference +} + +// Option is a functional option +type Option func(*options) + +// WithOCIArtifact takes an OCI artifact +func WithOCIArtifact(art *oci.Artifact) Option { + return func(opts *options) { + opts.artifact = art } +} - dbPath := filepath.Join(dbDir, "trivy.db") - log.Logger.Debugf("db path: %s", dbPath) - db, err = bolt.Open(dbPath, 0600, nil) - if err != nil { - return xerrors.Errorf("failed to open db: %w", err) +// WithDBRepository takes a dbRepository +func WithDBRepository(dbRepository name.Reference) Option { + return func(opts *options) { + opts.dbRepository = dbRepository } - return nil } -func Reset() error { - if err := os.RemoveAll(dbDir); err != nil { - return xerrors.Errorf("failed to reset DB: %w", err) +// WithClock takes a clock +func WithClock(c clock.Clock) Option { + return func(opts *options) { + opts.clock = c } - return nil } -func GetVersion() string { - var version string - value, err := Get("trivy", "metadata", "version") - if err != nil { - return "" +// Client implements DB operations +type Client struct { + *options + + cacheDir string + metadata metadata.Client + quiet bool +} + +// NewClient is the factory method for DB client +func NewClient(cacheDir string, quiet bool, opts ...Option) *Client { + o := &options{ + clock: clock.RealClock{}, + dbRepository: defaultRepository, } - if err = json.Unmarshal(value, &version); err != nil { - return "" + + for _, opt := range opts { + opt(o) } - return version -} -func SetVersion(version string) error { - err := Update("trivy", "metadata", "version", version) - if err != nil { - return xerrors.Errorf("failed to save DB version: %w", err) + return &Client{ + options: o, + cacheDir: cacheDir, + metadata: metadata.NewClient(cacheDir), + quiet: quiet, } - return nil } -func Update(rootBucket, nestedBucket, key string, value interface{}) error { - err := db.Update(func(tx *bolt.Tx) error { - return PutNestedBucket(tx, rootBucket, nestedBucket, key, value) - }) +// NeedsUpdate check is DB needs update +func (c *Client) NeedsUpdate(cliVersion string, skip bool) (bool, error) { + meta, err := c.metadata.Get() if err != nil { - return xerrors.Errorf("error in db update: %w", err) + log.Logger.Debugf("There is no valid metadata file: %s", err) + if skip { + log.Logger.Error("The first run cannot skip downloading DB") + return false, xerrors.New("--skip-update cannot be specified on the first run") + } + meta = metadata.Metadata{Version: db.SchemaVersion} + } + + if db.SchemaVersion < meta.Version { + log.Logger.Errorf("Trivy version (%s) is old. Update to the latest version.", cliVersion) + return false, xerrors.Errorf("the version of DB schema doesn't match. Local DB: %d, Expected: %d", + meta.Version, db.SchemaVersion) + } + + if skip { + log.Logger.Debug("Skipping DB update...") + if err = c.validate(meta); err != nil { + return false, xerrors.Errorf("validate error: %w", err) + } + return false, nil + } + + if db.SchemaVersion != meta.Version { + log.Logger.Debugf("The local DB schema version (%d) does not match with supported version schema (%d).", meta.Version, db.SchemaVersion) + return true, nil } - return err + + return !c.isNewDB(meta), nil } -func PutNestedBucket(tx *bolt.Tx, rootBucket, nestedBucket, key string, value interface{}) error { - root, err := tx.CreateBucketIfNotExists([]byte(rootBucket)) - if err != nil { - return xerrors.Errorf("failed to create a bucket: %w", err) +func (c *Client) validate(meta metadata.Metadata) error { + if db.SchemaVersion != meta.Version { + log.Logger.Error("The local DB has an old schema version which is not supported by the current version of Trivy CLI. DB needs to be updated.") + return xerrors.Errorf("--skip-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", + meta.Version, db.SchemaVersion) } - return Put(root, nestedBucket, key, value) + return nil } -func Put(root *bolt.Bucket, nestedBucket, key string, value interface{}) error { - nested, err := root.CreateBucketIfNotExists([]byte(nestedBucket)) - if err != nil { - return xerrors.Errorf("failed to create a bucket: %w", err) + +func (c *Client) isNewDB(meta metadata.Metadata) bool { + if c.clock.Now().Before(meta.NextUpdate) { + log.Logger.Debug("DB update was skipped because the local DB is the latest") + return true } - v, err := json.Marshal(value) - if err != nil { - return xerrors.Errorf("failed to unmarshal JSON: %w", err) + + if c.clock.Now().Before(meta.DownloadedAt.Add(time.Hour)) { + log.Logger.Debug("DB update was skipped because the local DB was downloaded during the last hour") + return true } - return nested.Put([]byte(key), v) + return false } -func BatchUpdate(fn func(tx *bolt.Tx) error) error { - err := db.Batch(func(tx *bolt.Tx) error { - return fn(tx) - }) + +// Download downloads the DB file +func (c *Client) Download(ctx context.Context, dst string, opt types.RegistryOptions) error { + // Remove the metadata file under the cache directory before downloading DB + if err := c.metadata.Delete(); err != nil { + log.Logger.Debug("no metadata file") + } + + art, err := c.initOCIArtifact(opt) if err != nil { - return xerrors.Errorf("error in batch update: %w", err) + return xerrors.Errorf("OCI artifact error: %w", err) + } + + if err = art.Download(ctx, db.Dir(dst), oci.DownloadOption{MediaType: dbMediaType}); err != nil { + return xerrors.Errorf("database download error: %w", err) + } + + if err = c.updateDownloadedAt(dst); err != nil { + return xerrors.Errorf("failed to update downloaded_at: %w", err) } return nil } -func Get(rootBucket, nestedBucket, key string) (value []byte, err error) { - err = db.View(func(tx *bolt.Tx) error { - root := tx.Bucket([]byte(rootBucket)) - if root == nil { - return nil - } - nested := root.Bucket([]byte(nestedBucket)) - if nested == nil { - return nil - } - value = nested.Get([]byte(key)) - return nil - }) +func (c *Client) updateDownloadedAt(dst string) error { + log.Logger.Debug("Updating database metadata...") + + // We have to initialize a metadata client here + // since the destination may be different from the cache directory. + client := metadata.NewClient(dst) + meta, err := client.Get() if err != nil { - return nil, xerrors.Errorf("failed to get data from db: %w", err) + return xerrors.Errorf("unable to get metadata: %w", err) } - return value, nil + + meta.DownloadedAt = c.clock.Now().UTC() + if err = client.Update(meta); err != nil { + return xerrors.Errorf("failed to update metadata: %w", err) + } + + return nil } -func ForEach(rootBucket, nestedBucket string) (value map[string][]byte, err error) { - value = map[string][]byte{} - err = db.View(func(tx *bolt.Tx) error { - root := tx.Bucket([]byte(rootBucket)) - if root == nil { - return nil - } - nested := root.Bucket([]byte(nestedBucket)) - if nested == nil { - return nil - } - err := nested.ForEach(func(k, v []byte) error { - value[string(k)] = v - return nil - }) - if err != nil { - return xerrors.Errorf("error in db foreach: %w", err) - } - return nil - }) +func (c *Client) initOCIArtifact(opt types.RegistryOptions) (*oci.Artifact, error) { + if c.artifact != nil { + return c.artifact, nil + } + + art, err := oci.NewArtifact(c.dbRepository.String(), c.quiet, opt) if err != nil { - return nil, xerrors.Errorf("failed to get all key/value in the specified bucket: %w", err) + var terr *transport.Error + if errors.As(err, &terr) { + for _, diagnostic := range terr.Errors { + // For better user experience + if diagnostic.Code == transport.DeniedErrorCode || diagnostic.Code == transport.UnauthorizedErrorCode { + log.Logger.Warn("See https://aquasecurity.github.io/trivy/latest/docs/references/troubleshooting/#db") + break + } + } + } + return nil, xerrors.Errorf("OCI artifact error: %w", err) } - return value, nil + return art, nil } diff --git a/pkg/db/db_test.go b/pkg/db/db_test.go new file mode 100644 index 000000000000..ef15370879a6 --- /dev/null +++ b/pkg/db/db_test.go @@ -0,0 +1,244 @@ +package db_test + +import ( + "context" + "fmt" + "testing" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" + fakei "github.com/google/go-containerregistry/pkg/v1/fake" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/utils/clock" + clocktesting "k8s.io/utils/clock/testing" + + tdb "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy-db/pkg/metadata" + "github.com/aquasecurity/trivy/pkg/db" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/oci" +) + +const mediaType = "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip" + +type fakeLayer struct { + v1.Layer +} + +func (f fakeLayer) MediaType() (types.MediaType, error) { + return mediaType, nil +} + +func newFakeLayer(t *testing.T, input string) v1.Layer { + layer, err := tarball.LayerFromFile(input) + require.NoError(t, err) + + return fakeLayer{layer} +} + +func TestClient_NeedsUpdate(t *testing.T) { + timeNextUpdateDay1 := time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC) + timeNextUpdateDay2 := time.Date(2019, 10, 2, 0, 0, 0, 0, time.UTC) + + tests := []struct { + name string + skip bool + clock clock.Clock + metadata metadata.Metadata + want bool + wantErr string + }{ + { + name: "happy path", + clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + metadata: metadata.Metadata{ + Version: tdb.SchemaVersion, + NextUpdate: timeNextUpdateDay1, + }, + want: true, + }, + { + name: "happy path for first run", + clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + metadata: metadata.Metadata{}, + want: true, + }, + { + name: "happy path with old schema version", + clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + metadata: metadata.Metadata{ + Version: 0, + NextUpdate: timeNextUpdateDay1, + }, + want: true, + }, + { + name: "happy path with --skip-update", + clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + metadata: metadata.Metadata{ + Version: tdb.SchemaVersion, + NextUpdate: timeNextUpdateDay1, + }, + skip: true, + want: false, + }, + { + name: "skip downloading DB", + clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + metadata: metadata.Metadata{ + Version: tdb.SchemaVersion, + NextUpdate: timeNextUpdateDay2, + }, + want: false, + }, + { + name: "newer schema version", + clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + metadata: metadata.Metadata{ + Version: tdb.SchemaVersion + 1, + NextUpdate: timeNextUpdateDay2, + }, + wantErr: fmt.Sprintf("the version of DB schema doesn't match. Local DB: %d, Expected: %d", + tdb.SchemaVersion+1, tdb.SchemaVersion), + }, + { + name: "--skip-update on the first run", + clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + metadata: metadata.Metadata{}, + skip: true, + wantErr: "--skip-update cannot be specified on the first run", + }, + { + name: "--skip-update with different schema version", + clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + metadata: metadata.Metadata{ + Version: 0, + NextUpdate: timeNextUpdateDay1, + }, + skip: true, + wantErr: fmt.Sprintf("--skip-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", + 0, tdb.SchemaVersion), + }, + { + name: "happy with old DownloadedAt", + clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + metadata: metadata.Metadata{ + Version: tdb.SchemaVersion, + NextUpdate: timeNextUpdateDay1, + DownloadedAt: time.Date(2019, 9, 30, 22, 30, 0, 0, time.UTC), + }, + want: true, + }, + { + name: "skip downloading DB with recent DownloadedAt", + clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + metadata: metadata.Metadata{ + Version: tdb.SchemaVersion, + NextUpdate: timeNextUpdateDay1, + DownloadedAt: time.Date(2019, 9, 30, 23, 30, 0, 0, time.UTC), + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cacheDir := t.TempDir() + if tt.metadata != (metadata.Metadata{}) { + meta := metadata.NewClient(cacheDir) + err := meta.Update(tt.metadata) + require.NoError(t, err) + } + + client := db.NewClient(cacheDir, true, db.WithClock(tt.clock)) + needsUpdate, err := client.NeedsUpdate("test", tt.skip) + + switch { + case tt.wantErr != "": + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + default: + assert.NoError(t, err, tt.name) + } + + assert.Equal(t, tt.want, needsUpdate) + }) + } +} + +func TestClient_Download(t *testing.T) { + timeDownloadedAt := clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)) + + tests := []struct { + name string + input string + want metadata.Metadata + wantErr string + }{ + { + name: "happy path", + input: "testdata/db.tar.gz", + want: metadata.Metadata{ + Version: 1, + NextUpdate: time.Date(3000, 1, 1, 18, 5, 43, 198355188, time.UTC), + UpdatedAt: time.Date(3000, 1, 1, 12, 5, 43, 198355588, time.UTC), + DownloadedAt: time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC), + }, + }, + { + name: "invalid gzip", + input: "testdata/trivy.db", + wantErr: "unexpected EOF", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cacheDir := t.TempDir() + + // Mock image + img := new(fakei.FakeImage) + img.LayersReturns([]v1.Layer{newFakeLayer(t, tt.input)}, nil) + img.ManifestReturns(&v1.Manifest{ + Layers: []v1.Descriptor{ + { + MediaType: "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip", + Size: 100, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "aec482bc254b5dd025d3eaf5bb35997d3dba783e394e8f91d5a415963151bfb8", + }, + Annotations: map[string]string{ + "org.opencontainers.image.title": "db.tar.gz", + }, + }, + }, + }, nil) + + // Mock OCI artifact + opt := ftypes.RegistryOptions{ + Insecure: false, + } + art, err := oci.NewArtifact("db", true, opt, oci.WithImage(img)) + require.NoError(t, err) + + client := db.NewClient(cacheDir, true, db.WithOCIArtifact(art), db.WithClock(timeDownloadedAt)) + err = client.Download(context.Background(), cacheDir, opt) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + + meta := metadata.NewClient(cacheDir) + got, err := meta.Get() + require.NoError(t, err) + + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/db/mock_operation.go b/pkg/db/mock_operation.go new file mode 100644 index 000000000000..b5a879fb5afc --- /dev/null +++ b/pkg/db/mock_operation.go @@ -0,0 +1,126 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package db + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// MockOperation is an autogenerated mock type for the Operation type +type MockOperation struct { + mock.Mock +} + +type OperationDownloadArgs struct { + Ctx context.Context + CtxAnything bool + Dst string + DstAnything bool +} + +type OperationDownloadReturns struct { + Err error +} + +type OperationDownloadExpectation struct { + Args OperationDownloadArgs + Returns OperationDownloadReturns +} + +func (_m *MockOperation) ApplyDownloadExpectation(e OperationDownloadExpectation) { + var args []interface{} + if e.Args.CtxAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.Ctx) + } + if e.Args.DstAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.Dst) + } + _m.On("Download", args...).Return(e.Returns.Err) +} + +func (_m *MockOperation) ApplyDownloadExpectations(expectations []OperationDownloadExpectation) { + for _, e := range expectations { + _m.ApplyDownloadExpectation(e) + } +} + +// Download provides a mock function with given fields: ctx, dst +func (_m *MockOperation) Download(ctx context.Context, dst string, opt types.RegistryOptions) error { + ret := _m.Called(ctx, dst, opt) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, dst) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type OperationNeedsUpdateArgs struct { + CliVersion string + CliVersionAnything bool + Skip bool + SkipAnything bool +} + +type OperationNeedsUpdateReturns struct { + Need bool + Err error +} + +type OperationNeedsUpdateExpectation struct { + Args OperationNeedsUpdateArgs + Returns OperationNeedsUpdateReturns +} + +func (_m *MockOperation) ApplyNeedsUpdateExpectation(e OperationNeedsUpdateExpectation) { + var args []interface{} + if e.Args.CliVersionAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.CliVersion) + } + if e.Args.SkipAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.Skip) + } + _m.On("NeedsUpdate", args...).Return(e.Returns.Need, e.Returns.Err) +} + +func (_m *MockOperation) ApplyNeedsUpdateExpectations(expectations []OperationNeedsUpdateExpectation) { + for _, e := range expectations { + _m.ApplyNeedsUpdateExpectation(e) + } +} + +// NeedsUpdate provides a mock function with given fields: cliVersion, skip +func (_m *MockOperation) NeedsUpdate(cliVersion string, skip bool) (bool, error) { + ret := _m.Called(cliVersion, skip) + + var r0 bool + if rf, ok := ret.Get(0).(func(string, bool) bool); ok { + r0 = rf(cliVersion, skip) + } else { + r0 = ret.Get(0).(bool) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, bool) error); ok { + r1 = rf(cliVersion, skip) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/db/testdata/db.tar.gz b/pkg/db/testdata/db.tar.gz new file mode 100644 index 000000000000..3580e70a1104 Binary files /dev/null and b/pkg/db/testdata/db.tar.gz differ diff --git a/pkg/db/testdata/trivy.db b/pkg/db/testdata/trivy.db new file mode 100644 index 000000000000..30d74d258442 --- /dev/null +++ b/pkg/db/testdata/trivy.db @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/pkg/dbtest/db.go b/pkg/dbtest/db.go new file mode 100644 index 000000000000..9ef89fadba99 --- /dev/null +++ b/pkg/dbtest/db.go @@ -0,0 +1,57 @@ +package dbtest + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + + fixtures "github.com/aquasecurity/bolt-fixtures" + "github.com/aquasecurity/trivy-db/pkg/db" + jdb "github.com/aquasecurity/trivy-java-db/pkg/db" +) + +// InitDB initializes testing database. +func InitDB(t *testing.T, fixtureFiles []string) string { + // Create a temp dir + dir := t.TempDir() + + dbPath := db.Path(dir) + dbDir := filepath.Dir(dbPath) + err := os.MkdirAll(dbDir, 0700) + require.NoError(t, err) + + // Load testdata into BoltDB + loader, err := fixtures.New(dbPath, fixtureFiles) + require.NoError(t, err) + require.NoError(t, loader.Load()) + require.NoError(t, loader.Close()) + + // Initialize DB + require.NoError(t, db.Init(dir)) + + return dir +} + +func Close() error { + return db.Close() +} + +func InitJavaDB(t *testing.T, cacheDir string) { + dbDir := filepath.Join(cacheDir, "java-db") + javaDB, err := jdb.New(dbDir) + require.NoError(t, err) + err = javaDB.Init() + require.NoError(t, err) + + meta := jdb.Metadata{ + Version: jdb.SchemaVersion, + NextUpdate: time.Now().Add(24 * time.Hour), + UpdatedAt: time.Now(), + } + metac := jdb.NewMetadata(dbDir) + err = metac.Update(meta) + require.NoError(t, err) +} diff --git a/pkg/dependency/id.go b/pkg/dependency/id.go new file mode 100644 index 000000000000..d40289cedc6a --- /dev/null +++ b/pkg/dependency/id.go @@ -0,0 +1,32 @@ +package dependency + +import ( + "strings" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// ID returns a unique ID for the given library. +// The package ID is used to construct the dependency graph. +// The separator is different for each language type. +func ID(ltype types.LangType, name, version string) string { + if version == "" { + return name + } + + sep := "@" + switch ltype { + case types.Conan: + sep = "/" + case types.GoModule, types.GoBinary: + // Return a module ID according the Go way. + // Format: @v + // e.g. github.com/aquasecurity/go-dep-parser@v0.0.0-20230130190635-5e31092b0621 + if !strings.HasPrefix(version, "v") { + version = "v" + version + } + case types.Jar, types.Pom, types.Gradle: + sep = ":" + } + return name + sep + version +} diff --git a/pkg/dependency/id_test.go b/pkg/dependency/id_test.go new file mode 100644 index 000000000000..173d71c3d726 --- /dev/null +++ b/pkg/dependency/id_test.go @@ -0,0 +1,73 @@ +package dependency_test + +import ( + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestID(t *testing.T) { + type args struct { + ltype types.LangType + name string + version string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "conan", + args: args{ + ltype: types.Conan, + name: "test", + version: "1.0.0", + }, + want: "test/1.0.0", + }, + { + name: "go module", + args: args{ + ltype: types.GoModule, + name: "test", + version: "1.0.0", + }, + want: "test@v1.0.0", + }, + { + name: "gradle", + args: args{ + ltype: types.Gradle, + name: "test", + version: "1.0.0", + }, + want: "test:1.0.0", + }, + { + name: "pip", + args: args{ + ltype: types.Pip, + name: "test", + version: "1.0.0", + }, + want: "test@1.0.0", + }, + { + name: "no version", + args: args{ + ltype: types.Pom, + name: "test", + version: "", + }, + want: "test", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := dependency.ID(tt.args.ltype, tt.args.name, tt.args.version) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/c/conan/parse.go b/pkg/dependency/parser/c/conan/parse.go new file mode 100644 index 000000000000..78e3bdc09636 --- /dev/null +++ b/pkg/dependency/parser/c/conan/parse.go @@ -0,0 +1,134 @@ +package conan + +import ( + "io" + "strings" + + "github.com/liamg/jfather" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type LockFile struct { + GraphLock GraphLock `json:"graph_lock"` +} + +type GraphLock struct { + Nodes map[string]Node `json:"nodes"` +} + +type Node struct { + Ref string `json:"ref"` + Requires []string `json:"requires"` + StartLine int + EndLine int +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var lock LockFile + input, err := io.ReadAll(r) + if err != nil { + return nil, nil, xerrors.Errorf("failed to read canon lock file: %w", err) + } + if err := jfather.Unmarshal(input, &lock); err != nil { + return nil, nil, xerrors.Errorf("failed to decode canon lock file: %w", err) + } + + // Get a list of direct dependencies + var directDeps []string + if root, ok := lock.GraphLock.Nodes["0"]; ok { + directDeps = root.Requires + } + + // Parse packages + parsed := make(map[string]types.Library) + for i, node := range lock.GraphLock.Nodes { + if node.Ref == "" { + continue + } + lib, err := parseRef(node) + if err != nil { + log.Logger.Debug(err) + continue + } + + // Determine if the package is a direct dependency or not + direct := slices.Contains(directDeps, i) + lib.Indirect = !direct + + parsed[i] = lib + } + + // Parse dependency graph + var libs []types.Library + var deps []types.Dependency + for i, node := range lock.GraphLock.Nodes { + lib, ok := parsed[i] + if !ok { + continue + } + + var childDeps []string + for _, req := range node.Requires { + if child, ok := parsed[req]; ok { + childDeps = append(childDeps, child.ID) + } + } + if len(childDeps) != 0 { + deps = append(deps, types.Dependency{ + ID: lib.ID, + DependsOn: childDeps, + }) + } + + libs = append(libs, lib) + } + return libs, deps, nil +} + +func parseRef(node Node) (types.Library, error) { + // full ref format: package/version@user/channel#rrev:package_id#prev + // various examples: + // 'pkga/0.1@user/testing' + // 'pkgb/0.1.0' + // 'pkgc/system' + // 'pkgd/0.1.0#7dcb50c43a5a50d984c2e8fa5898bf18' + ss := strings.Split(strings.Split(strings.Split(node.Ref, "@")[0], "#")[0], "/") + if len(ss) != 2 { + return types.Library{}, xerrors.Errorf("Unable to determine conan dependency: %q", node.Ref) + } + return types.Library{ + ID: dependency.ID(ftypes.Conan, ss[0], ss[1]), + Name: ss[0], + Version: ss[1], + Locations: []types.Location{ + { + StartLine: node.StartLine, + EndLine: node.EndLine, + }, + }, + }, nil +} + +// UnmarshalJSONWithMetadata needed to detect start and end lines of deps +func (n *Node) UnmarshalJSONWithMetadata(node jfather.Node) error { + if err := node.Decode(&n); err != nil { + return err + } + // Decode func will overwrite line numbers if we save them first + n.StartLine = node.Range().Start.Line + n.EndLine = node.Range().End.Line + return nil +} diff --git a/pkg/dependency/parser/c/conan/parse_test.go b/pkg/dependency/parser/c/conan/parse_test.go new file mode 100644 index 000000000000..a22ec90afc1b --- /dev/null +++ b/pkg/dependency/parser/c/conan/parse_test.go @@ -0,0 +1,139 @@ +package conan_test + +import ( + "os" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/c/conan" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + inputFile string // Test input file + wantLibs []types.Library + wantDeps []types.Dependency + }{ + { + name: "happy path", + inputFile: "testdata/happy.lock", + wantLibs: []types.Library{ + { + ID: "pkga/0.0.1", + Name: "pkga", + Version: "0.0.1", + Locations: []types.Location{ + { + StartLine: 13, + EndLine: 22, + }, + }, + }, + { + ID: "pkgb/system", + Name: "pkgb", + Version: "system", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 23, + EndLine: 29, + }, + }, + }, + { + ID: "pkgc/0.1.1", + Name: "pkgc", + Version: "0.1.1", + Locations: []types.Location{ + { + StartLine: 30, + EndLine: 35, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "pkga/0.0.1", + DependsOn: []string{ + "pkgb/system", + }, + }, + }, + }, + { + name: "happy path. lock file with revisions support", + inputFile: "testdata/happy2.lock", + wantLibs: []types.Library{ + { + ID: "openssl/3.0.3", + Name: "openssl", + Version: "3.0.3", + Locations: []types.Location{ + { + StartLine: 12, + EndLine: 22, + }, + }, + }, + { + ID: "zlib/1.2.12", + Name: "zlib", + Version: "1.2.12", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 23, + EndLine: 30, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "openssl/3.0.3", + DependsOn: []string{ + "zlib/1.2.12", + }, + }, + }, + }, + { + name: "happy path. lock file without dependencies", + inputFile: "testdata/empty.lock", + }, + { + name: "sad path. wrong ref format", + inputFile: "testdata/sad.lock", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + gotLibs, gotDeps, err := conan.NewParser().Parse(f) + require.NoError(t, err) + + sort.Slice(gotLibs, func(i, j int) bool { + ret := strings.Compare(gotLibs[i].Name, gotLibs[j].Name) + if ret != 0 { + return ret < 0 + } + return gotLibs[i].Version < gotLibs[j].Version + }) + + assert.Equal(t, tt.wantLibs, gotLibs) + assert.Equal(t, tt.wantDeps, gotDeps) + }) + } +} diff --git a/pkg/dependency/parser/c/conan/testdata/empty.lock b/pkg/dependency/parser/c/conan/testdata/empty.lock new file mode 100644 index 000000000000..13708b45b920 --- /dev/null +++ b/pkg/dependency/parser/c/conan/testdata/empty.lock @@ -0,0 +1,14 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "options": "", + "path": "conanfile.txt", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=9\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" +} \ No newline at end of file diff --git a/pkg/dependency/parser/c/conan/testdata/happy.lock b/pkg/dependency/parser/c/conan/testdata/happy.lock new file mode 100644 index 000000000000..764f490c07e4 --- /dev/null +++ b/pkg/dependency/parser/c/conan/testdata/happy.lock @@ -0,0 +1,41 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "options": "glew:fPIC=True\nglew:shared=False\nglew:with_egl=False", + "requires": [ + "1", + "3" + ], + "path": "conanfile.txt", + "context": "host" + }, + "1": { + "ref": "pkga/0.0.1", + "options": "fPIC=True\nshared=False\nwith_egl=False", + "package_id": "", + "prev": "0", + "requires": [ + "2" + ], + "context": "host" + }, + "2": { + "ref": "pkgb/system", + "options": "", + "package_id": "", + "prev": "0", + "context": "host" + }, + "3": { + "ref": "pkgc/0.1.1@user/testing", + "options": "shared=False", + "path": "conanfile.txt", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=9\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" +} \ No newline at end of file diff --git a/pkg/dependency/parser/c/conan/testdata/happy2.lock b/pkg/dependency/parser/c/conan/testdata/happy2.lock new file mode 100644 index 000000000000..33ba5dbf7e67 --- /dev/null +++ b/pkg/dependency/parser/c/conan/testdata/happy2.lock @@ -0,0 +1,36 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "options": "openssl:386=False\nopenssl:enable_weak_ssl_ciphers=False\nopenssl:fPIC=True\nopenssl:no_aria=False\nopenssl:no_asm=False\nopenssl:no_async=False\nopenssl:no_bf=False\nopenssl:no_blake2=False\nopenssl:no_camellia=False\nopenssl:no_cast=False\nopenssl:no_chacha=False\nopenssl:no_cms=False\nopenssl:no_comp=False\nopenssl:no_ct=False\nopenssl:no_deprecated=False\nopenssl:no_des=False\nopenssl:no_dgram=False\nopenssl:no_dh=False\nopenssl:no_dsa=False\nopenssl:no_dso=False\nopenssl:no_ec=False\nopenssl:no_ecdh=False\nopenssl:no_ecdsa=False\nopenssl:no_engine=False\nopenssl:no_filenames=False\nopenssl:no_fips=False\nopenssl:no_gost=False\nopenssl:no_idea=False\nopenssl:no_legacy=False\nopenssl:no_md2=True\nopenssl:no_md4=False\nopenssl:no_mdc2=False\nopenssl:no_module=False\nopenssl:no_ocsp=False\nopenssl:no_pinshared=False\nopenssl:no_rc2=False\nopenssl:no_rc4=False\nopenssl:no_rc5=False\nopenssl:no_rfc3779=False\nopenssl:no_rmd160=False\nopenssl:no_seed=False\nopenssl:no_sm2=False\nopenssl:no_sm3=False\nopenssl:no_sm4=False\nopenssl:no_sock=False\nopenssl:no_srp=False\nopenssl:no_srtp=False\nopenssl:no_sse2=False\nopenssl:no_ssl=False\nopenssl:no_ssl3=False\nopenssl:no_stdio=False\nopenssl:no_threads=False\nopenssl:no_tls1=False\nopenssl:no_ts=False\nopenssl:no_whirlpool=False\nopenssl:no_zlib=False\nopenssl:openssldir=None\nopenssl:shared=False\nzlib:fPIC=True\nzlib:shared=False", + "requires": [ + "1" + ], + "path": "conanfile.txt", + "context": "host" + }, + "1": { + "ref": "openssl/3.0.3#288ab73765e69844899535609ee0dfe4", + "options": "386=False\nenable_weak_ssl_ciphers=False\nfPIC=True\nno_aria=False\nno_asm=False\nno_async=False\nno_bf=False\nno_blake2=False\nno_camellia=False\nno_cast=False\nno_chacha=False\nno_cms=False\nno_comp=False\nno_ct=False\nno_deprecated=False\nno_des=False\nno_dgram=False\nno_dh=False\nno_dsa=False\nno_dso=False\nno_ec=False\nno_ecdh=False\nno_ecdsa=False\nno_engine=False\nno_filenames=False\nno_fips=False\nno_gost=False\nno_idea=False\nno_legacy=False\nno_md2=True\nno_md4=False\nno_mdc2=False\nno_module=False\nno_ocsp=False\nno_pinshared=False\nno_rc2=False\nno_rc4=False\nno_rc5=False\nno_rfc3779=False\nno_rmd160=False\nno_seed=False\nno_sm2=False\nno_sm3=False\nno_sm4=False\nno_sock=False\nno_srp=False\nno_srtp=False\nno_sse2=False\nno_ssl=False\nno_ssl3=False\nno_stdio=False\nno_threads=False\nno_tls1=False\nno_ts=False\nno_whirlpool=False\nno_zlib=False\nopenssldir=None\nshared=False\nzlib:fPIC=True\nzlib:shared=False", + "package_id": "772675ace4978e609416253f5f352cadffbfbb53", + "prev": "8f9ca9645535191ea41a1c349e18523f", + "modified": true, + "requires": [ + "2" + ], + "context": "host" + }, + "2": { + "ref": "zlib/1.2.12#b76db676bd992afa93dd18a675323942", + "options": "fPIC=True\nshared=False", + "package_id": "2a19826344ff00be1c04403f2f8e7008ed3a7cc6", + "prev": "88731f6a10f12adde3a66722c1327ada", + "modified": true, + "context": "host" + } + }, + "revisions_enabled": true + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++11\ncompiler.version=12\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" +} \ No newline at end of file diff --git a/pkg/dependency/parser/c/conan/testdata/sad.lock b/pkg/dependency/parser/c/conan/testdata/sad.lock new file mode 100644 index 000000000000..6c951e0ca405 --- /dev/null +++ b/pkg/dependency/parser/c/conan/testdata/sad.lock @@ -0,0 +1,24 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "options": "", + "requires": [ + "1" + ], + "path": "conanfile.txt", + "context": "host" + }, + "1": { + "ref": "wrong/ref/format", + "options": "", + "package_id": "", + "prev": "0", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=9\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" +} \ No newline at end of file diff --git a/pkg/dependency/parser/conda/meta/parse.go b/pkg/dependency/parser/conda/meta/parse.go new file mode 100644 index 000000000000..30d344bfdfa3 --- /dev/null +++ b/pkg/dependency/parser/conda/meta/parse.go @@ -0,0 +1,45 @@ +package meta + +import ( + "encoding/json" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type packageJSON struct { + Name string `json:"name"` + Version string `json:"version"` + License string `json:"license"` +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +// Parse parses Anaconda (a.k.a. conda) environment metadata. +// e.g. /envs//conda-meta/.json +// For details see https://conda.io/projects/conda/en/latest/user-guide/concepts/environments.html +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var data packageJSON + err := json.NewDecoder(r).Decode(&data) + if err != nil { + return nil, nil, xerrors.Errorf("JSON decode error: %w", err) + } + + if data.Name == "" || data.Version == "" { + return nil, nil, xerrors.Errorf("unable to parse conda package") + } + + return []types.Library{ + { + Name: data.Name, + Version: data.Version, + License: data.License, // can be empty + }, + }, nil, nil +} diff --git a/pkg/dependency/parser/conda/meta/parse_test.go b/pkg/dependency/parser/conda/meta/parse_test.go new file mode 100644 index 000000000000..8bde5e184f0e --- /dev/null +++ b/pkg/dependency/parser/conda/meta/parse_test.go @@ -0,0 +1,59 @@ +package meta_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/meta" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + input string + want []types.Library + wantErr string + }{ + { + name: "_libgcc_mutex", + input: "testdata/_libgcc_mutex-0.1-main.json", + want: []types.Library{{Name: "_libgcc_mutex", Version: "0.1"}}, + }, + { + name: "libgomp", + input: "testdata/libgomp-11.2.0-h1234567_1.json", + want: []types.Library{{Name: "libgomp", Version: "11.2.0", License: "GPL-3.0-only WITH GCC-exception-3.1"}}, + }, + { + name: "invalid_json", + input: "testdata/invalid_json.json", + wantErr: "JSON decode error: invalid character", + }, + { + name: "invalid_package", + input: "testdata/invalid_package.json", + wantErr: "unable to parse conda package", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.input) + require.NoError(t, err) + defer f.Close() + + got, _, err := meta.NewParser().Parse(f) + + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/conda/meta/testdata/README.md b/pkg/dependency/parser/conda/meta/testdata/README.md new file mode 100644 index 000000000000..023eab2caee9 --- /dev/null +++ b/pkg/dependency/parser/conda/meta/testdata/README.md @@ -0,0 +1,14 @@ +To recreate the test files: +- Start a miniconda container: +```bash +docker run --name miniconda --rm -it continuumio/miniconda3@sha256:58b1c7df8d69655ffec017ede784a075e3c2e9feff0fc50ef65300fc75aa45ae bash +``` +- In the container, initialize a conda environment: +```bash +conda create --yes -n test-dep-parser python=3.9.12 +``` +- Export conda package definitions out of the container: +```bash +docker cp miniconda:/opt/conda/envs/test-dep-parser/conda-meta/_libgcc_mutex-0.1-main.json . +docker cp miniconda:/opt/conda/envs/test-dep-parser/conda-meta/libgomp-11.2.0-h1234567_1.json . +``` diff --git a/pkg/dependency/parser/conda/meta/testdata/_libgcc_mutex-0.1-main.json b/pkg/dependency/parser/conda/meta/testdata/_libgcc_mutex-0.1-main.json new file mode 100644 index 000000000000..507261770721 --- /dev/null +++ b/pkg/dependency/parser/conda/meta/testdata/_libgcc_mutex-0.1-main.json @@ -0,0 +1,32 @@ +{ + "build": "main", + "build_number": 0, + "channel": "https://repo.anaconda.com/pkgs/main/linux-64", + "constrains": [], + "depends": [], + "extracted_package_dir": "/opt/conda/pkgs/_libgcc_mutex-0.1-main", + "features": "", + "files": [], + "fn": "_libgcc_mutex-0.1-main.conda", + "legacy_bz2_md5": "013d3f22cd3b87f71224bd936765bcad", + "legacy_bz2_size": 3121, + "link": { + "source": "/opt/conda/pkgs/_libgcc_mutex-0.1-main", + "type": 1 + }, + "md5": "c3473ff8bdb3d124ed5ff11ec380d6f9", + "name": "_libgcc_mutex", + "package_tarball_full_path": "/opt/conda/pkgs/_libgcc_mutex-0.1-main.conda", + "paths_data": { + "paths": [], + "paths_version": 1 + }, + "requested_spec": "None", + "sha256": "476626712f60e5ef0fe04c354727152b1ee5285d57ccd3575c7be930122bd051", + "size": 3473, + "subdir": "linux-64", + "timestamp": 1562011674792, + "track_features": "", + "url": "https://repo.anaconda.com/pkgs/main/linux-64/_libgcc_mutex-0.1-main.conda", + "version": "0.1" +} \ No newline at end of file diff --git a/pkg/dependency/parser/conda/meta/testdata/invalid_json.json b/pkg/dependency/parser/conda/meta/testdata/invalid_json.json new file mode 100644 index 000000000000..1cc7687e5f40 --- /dev/null +++ b/pkg/dependency/parser/conda/meta/testdata/invalid_json.json @@ -0,0 +1 @@ +Invalid JSON \ No newline at end of file diff --git a/pkg/dependency/parser/conda/meta/testdata/invalid_package.json b/pkg/dependency/parser/conda/meta/testdata/invalid_package.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/pkg/dependency/parser/conda/meta/testdata/invalid_package.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pkg/dependency/parser/conda/meta/testdata/libgomp-11.2.0-h1234567_1.json b/pkg/dependency/parser/conda/meta/testdata/libgomp-11.2.0-h1234567_1.json new file mode 100644 index 000000000000..7a13ea8b0386 --- /dev/null +++ b/pkg/dependency/parser/conda/meta/testdata/libgomp-11.2.0-h1234567_1.json @@ -0,0 +1,60 @@ +{ + "build": "h1234567_1", + "build_number": 1, + "channel": "https://repo.anaconda.com/pkgs/main/linux-64", + "constrains": [], + "depends": [ + "_libgcc_mutex 0.1 main" + ], + "extracted_package_dir": "/opt/conda/pkgs/libgomp-11.2.0-h1234567_1", + "features": "", + "files": [ + "lib/libgomp.so", + "lib/libgomp.so.1.0.0", + "share/licenses/gcc-libs/RUNTIME.LIBRARY.EXCEPTION.gomp_copy" + ], + "fn": "libgomp-11.2.0-h1234567_1.conda", + "legacy_bz2_md5": "3080c2813e6e1d0f8a100a38b5b3456d", + "legacy_bz2_size": 573231, + "license": "GPL-3.0-only WITH GCC-exception-3.1", + "link": { + "source": "/opt/conda/pkgs/libgomp-11.2.0-h1234567_1", + "type": 1 + }, + "md5": "b372c0eea9b60732fdae4b817a63c8cd", + "name": "libgomp", + "package_tarball_full_path": "/opt/conda/pkgs/libgomp-11.2.0-h1234567_1.conda", + "paths_data": { + "paths": [ + { + "_path": "lib/libgomp.so", + "path_type": "softlink", + "sha256": "e3b68c5f37afb7b70bd12273b69706ab33a397714e8336910f0e47f8f1cf6854", + "size_in_bytes": 1265616 + }, + { + "_path": "lib/libgomp.so.1.0.0", + "path_type": "hardlink", + "sha256": "e3b68c5f37afb7b70bd12273b69706ab33a397714e8336910f0e47f8f1cf6854", + "sha256_in_prefix": "e3b68c5f37afb7b70bd12273b69706ab33a397714e8336910f0e47f8f1cf6854", + "size_in_bytes": 1265616 + }, + { + "_path": "share/licenses/gcc-libs/RUNTIME.LIBRARY.EXCEPTION.gomp_copy", + "path_type": "hardlink", + "sha256": "9d6b43ce4d8de0c878bf16b54d8e7a10d9bd42b75178153e3af6a815bdc90f74", + "sha256_in_prefix": "9d6b43ce4d8de0c878bf16b54d8e7a10d9bd42b75178153e3af6a815bdc90f74", + "size_in_bytes": 3324 + } + ], + "paths_version": 1 + }, + "requested_spec": "None", + "sha256": "a1c6e599df45e116af81c36ec4c9efb1793fa3a0b854dd90dd6c8813cd476e90", + "size": 485145, + "subdir": "linux-64", + "timestamp": 1654090775721, + "track_features": "", + "url": "https://repo.anaconda.com/pkgs/main/linux-64/libgomp-11.2.0-h1234567_1.conda", + "version": "11.2.0" +} \ No newline at end of file diff --git a/pkg/dependency/parser/dart/pub/parse.go b/pkg/dependency/parser/dart/pub/parse.go new file mode 100644 index 000000000000..efd8d9aa5d41 --- /dev/null +++ b/pkg/dependency/parser/dart/pub/parse.go @@ -0,0 +1,56 @@ +package pub + +import ( + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +const ( + idFormat = "%s@%s" + transitiveDep = "transitive" +) + +// Parser is a parser for pubspec.lock +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +type lock struct { + Packages map[string]Dep `yaml:"packages"` +} + +type Dep struct { + Dependency string `yaml:"dependency"` + Version string `yaml:"version"` +} + +func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + l := &lock{} + if err := yaml.NewDecoder(r).Decode(&l); err != nil { + return nil, nil, xerrors.Errorf("failed to decode pubspec.lock: %w", err) + } + var libs []types.Library + for name, dep := range l.Packages { + // We would like to exclude dev dependencies, but we cannot identify + // which indirect dependencies were introduced by dev dependencies + // as there are 3 dependency types, "direct main", "direct dev" and "transitive". + // It will be confusing if we exclude direct dev dependencies and include transitive dev dependencies. + // We decided to keep all dev dependencies until Pub will add support for "transitive main" and "transitive dev". + lib := types.Library{ + ID: dependency.ID(ftypes.Pub, name, dep.Version), + Name: name, + Version: dep.Version, + Indirect: dep.Dependency == transitiveDep, + } + libs = append(libs, lib) + } + + return libs, nil, nil +} diff --git a/pkg/dependency/parser/dart/pub/parse_test.go b/pkg/dependency/parser/dart/pub/parse_test.go new file mode 100644 index 000000000000..ef62ab971658 --- /dev/null +++ b/pkg/dependency/parser/dart/pub/parse_test.go @@ -0,0 +1,76 @@ +package pub_test + +import ( + "fmt" + "os" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/dart/pub" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + inputFile string + want []types.Library + wantErr assert.ErrorAssertionFunc + }{ + { + name: "happy path", + inputFile: "testdata/happy.lock", + want: []types.Library{ + { + ID: "crypto@3.0.2", + Name: "crypto", + Version: "3.0.2", + }, + { + ID: "flutter_test@0.0.0", + Name: "flutter_test", + Version: "0.0.0", + }, + { + ID: "uuid@3.0.6", + Name: "uuid", + Version: "3.0.6", + Indirect: true, + }, + }, + wantErr: assert.NoError, + }, + { + name: "empty path", + inputFile: "testdata/empty.lock", + wantErr: assert.NoError, + }, + { + name: "broken yaml", + inputFile: "testdata/broken.lock", + wantErr: assert.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + gotLibs, _, err := pub.NewParser().Parse(f) + if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.inputFile)) { + return + } + + sort.Slice(gotLibs, func(i, j int) bool { + return gotLibs[i].ID < gotLibs[j].ID + }) + + assert.Equal(t, tt.want, gotLibs) + }) + } +} diff --git a/pkg/dependency/parser/dart/pub/testdata/broken.lock b/pkg/dependency/parser/dart/pub/testdata/broken.lock new file mode 100644 index 000000000000..d86c7d42138e --- /dev/null +++ b/pkg/dependency/parser/dart/pub/testdata/broken.lock @@ -0,0 +1 @@ +[this is not yaml] \ No newline at end of file diff --git a/pkg/dependency/parser/dart/pub/testdata/empty.lock b/pkg/dependency/parser/dart/pub/testdata/empty.lock new file mode 100644 index 000000000000..eb4cea8c2089 --- /dev/null +++ b/pkg/dependency/parser/dart/pub/testdata/empty.lock @@ -0,0 +1,6 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: +sdks: + dart: ">=2.18.0 <3.0.0" + flutter: ">=3.3.0" \ No newline at end of file diff --git a/pkg/dependency/parser/dart/pub/testdata/happy.lock b/pkg/dependency/parser/dart/pub/testdata/happy.lock new file mode 100644 index 000000000000..3a37840aa3bb --- /dev/null +++ b/pkg/dependency/parser/dart/pub/testdata/happy.lock @@ -0,0 +1,25 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + crypto: + dependency: "direct main" + description: + name: crypto + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.6" +sdks: + dart: ">=2.18.0 <3.0.0" + flutter: ">=3.3.0" \ No newline at end of file diff --git a/pkg/dependency/parser/dotnet/core_deps/parse.go b/pkg/dependency/parser/dotnet/core_deps/parse.go new file mode 100644 index 000000000000..c4bf533a87df --- /dev/null +++ b/pkg/dependency/parser/dotnet/core_deps/parse.go @@ -0,0 +1,79 @@ +package core_deps + +import ( + "io" + "strings" + + "github.com/liamg/jfather" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var depsFile dotNetDependencies + + input, err := io.ReadAll(r) + if err != nil { + return nil, nil, xerrors.Errorf("read error: %w", err) + } + if err := jfather.Unmarshal(input, &depsFile); err != nil { + return nil, nil, xerrors.Errorf("failed to decode .deps.json file: %w", err) + } + + var libraries []types.Library + for nameVer, lib := range depsFile.Libraries { + if !strings.EqualFold(lib.Type, "package") { + continue + } + + split := strings.Split(nameVer, "/") + if len(split) != 2 { + // Invalid name + log.Logger.Warnf("Cannot parse .NET library version from: %s", nameVer) + continue + } + + libraries = append(libraries, types.Library{ + Name: split[0], + Version: split[1], + Locations: []types.Location{ + { + StartLine: lib.StartLine, + EndLine: lib.EndLine, + }, + }, + }) + } + + return libraries, nil, nil +} + +type dotNetDependencies struct { + Libraries map[string]dotNetLibrary `json:"libraries"` +} + +type dotNetLibrary struct { + Type string `json:"type"` + StartLine int + EndLine int +} + +// UnmarshalJSONWithMetadata needed to detect start and end lines of deps +func (t *dotNetLibrary) UnmarshalJSONWithMetadata(node jfather.Node) error { + if err := node.Decode(&t); err != nil { + return err + } + // Decode func will overwrite line numbers if we save them first + t.StartLine = node.Range().Start.Line + t.EndLine = node.Range().End.Line + return nil +} diff --git a/pkg/dependency/parser/dotnet/core_deps/parse_test.go b/pkg/dependency/parser/dotnet/core_deps/parse_test.go new file mode 100644 index 000000000000..839cf9ed97ba --- /dev/null +++ b/pkg/dependency/parser/dotnet/core_deps/parse_test.go @@ -0,0 +1,70 @@ +package core_deps + +import ( + "os" + "path" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + vectors := []struct { + file string // Test input file + want []types.Library + wantErr string + }{ + { + file: "testdata/ExampleApp1.deps.json", + want: []types.Library{ + {Name: "Newtonsoft.Json", Version: "13.0.1", Locations: []types.Location{{StartLine: 33, EndLine: 39}}}, + }, + }, + { + file: "testdata/NoLibraries.deps.json", + want: nil, + }, + { + file: "testdata/InvalidJson.deps.json", + wantErr: "failed to decode .deps.json file: EOF", + }, + } + + for _, tt := range vectors { + t.Run(path.Base(tt.file), func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + + got, _, err := NewParser().Parse(f) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + } else { + require.NoError(t, err) + + sort.Slice(got, func(i, j int) bool { + ret := strings.Compare(got[i].Name, got[j].Name) + if ret == 0 { + return got[i].Version < got[j].Version + } + return ret < 0 + }) + + sort.Slice(tt.want, func(i, j int) bool { + ret := strings.Compare(tt.want[i].Name, tt.want[j].Name) + if ret == 0 { + return tt.want[i].Version < tt.want[j].Version + } + return ret < 0 + }) + + assert.Equal(t, tt.want, got) + } + }) + } +} diff --git a/pkg/dependency/parser/dotnet/core_deps/testdata/ExampleApp1.deps.json b/pkg/dependency/parser/dotnet/core_deps/testdata/ExampleApp1.deps.json new file mode 100644 index 000000000000..d2fbdc825ac5 --- /dev/null +++ b/pkg/dependency/parser/dotnet/core_deps/testdata/ExampleApp1.deps.json @@ -0,0 +1,41 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v5.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v5.0": { + "ExampleApp1/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.1" + }, + "runtime": { + "ExampleApp1.dll": {} + } + }, + "Newtonsoft.Json/13.0.1": { + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.1.25517" + } + } + } + } + }, + "libraries": { + "ExampleApp1/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Newtonsoft.Json/13.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==", + "path": "newtonsoft.json/13.0.1", + "hashPath": "newtonsoft.json.13.0.1.nupkg.sha512" + } + } + } \ No newline at end of file diff --git a/pkg/dependency/parser/dotnet/core_deps/testdata/InvalidJson.deps.json b/pkg/dependency/parser/dotnet/core_deps/testdata/InvalidJson.deps.json new file mode 100644 index 000000000000..300197b6fecb --- /dev/null +++ b/pkg/dependency/parser/dotnet/core_deps/testdata/InvalidJson.deps.json @@ -0,0 +1,12 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v5.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v5.0": { + "ExampleApp1/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.1" + }, \ No newline at end of file diff --git a/pkg/dependency/parser/dotnet/core_deps/testdata/NoLibraries.deps.json b/pkg/dependency/parser/dotnet/core_deps/testdata/NoLibraries.deps.json new file mode 100644 index 000000000000..ebd10345378c --- /dev/null +++ b/pkg/dependency/parser/dotnet/core_deps/testdata/NoLibraries.deps.json @@ -0,0 +1,27 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v5.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v5.0": { + "ExampleApp1/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.1" + }, + "runtime": { + "ExampleApp1.dll": {} + } + }, + "Newtonsoft.Json/13.0.1": { + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.1.25517" + } + } + } + } + } +} diff --git a/pkg/dependency/parser/frameworks/wordpress/parse.go b/pkg/dependency/parser/frameworks/wordpress/parse.go new file mode 100644 index 000000000000..61e00ded81cc --- /dev/null +++ b/pkg/dependency/parser/frameworks/wordpress/parse.go @@ -0,0 +1,78 @@ +package wordpress + +import ( + "bufio" + "io" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func Parse(r io.Reader) (lib types.Library, err error) { + + // If wordpress file, open file and + // find line with content + // $wp_version = ''; + + var version string + isComment := false + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + + // Remove comment + commentIndex := strings.Index(line, "//") + if commentIndex != -1 { + line = line[:commentIndex] + } + + line = strings.TrimSpace(line) + + // Handle comment + switch { + case strings.HasPrefix(line, "/*"): + isComment = true + continue + case isComment && strings.HasSuffix(line, "*/"): + isComment = false + continue + case isComment: + continue + } + + // It might include $wp_version_something + if !strings.HasPrefix(line, "$wp_version") { + continue + } + + ss := strings.Split(line, "=") + if len(ss) != 2 || strings.TrimSpace(ss[0]) != "$wp_version" { + continue + } + + // Each variable must end with ";". + end := strings.Index(ss[1], ";") + if end == -1 { + continue + } + + // Remove ";" and white space. + version = strings.TrimSpace(ss[1][:end]) + + // Remove single and double quotes. + version = strings.Trim(version, `'"`) + + break + } + + if err = scanner.Err(); err != nil || version == "" { + return types.Library{}, xerrors.New("version.php could not be parsed") + } + + return types.Library{ + Name: "wordpress", + Version: version, + }, nil +} diff --git a/pkg/dependency/parser/frameworks/wordpress/parse_test.go b/pkg/dependency/parser/frameworks/wordpress/parse_test.go new file mode 100644 index 000000000000..623ae06b87c7 --- /dev/null +++ b/pkg/dependency/parser/frameworks/wordpress/parse_test.go @@ -0,0 +1,49 @@ +package wordpress + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParseWordPress(t *testing.T) { + tests := []struct { + file string // Test input file + want types.Library + wantErr string + }{ + { + file: "testdata/version.php", + want: types.Library{ + Name: "wordpress", + Version: "4.9.4-alpha", + }, + }, + { + file: "testdata/versionFail.php", + wantErr: "version.php could not be parsed", + }, + } + + for _, tt := range tests { + t.Run(path.Base(tt.file), func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + + got, err := Parse(f) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/frameworks/wordpress/testdata/version.php b/pkg/dependency/parser/frameworks/wordpress/testdata/version.php new file mode 100644 index 000000000000..dd0bf2c1ab0c --- /dev/null +++ b/pkg/dependency/parser/frameworks/wordpress/testdata/version.php @@ -0,0 +1,50 @@ + github.com/user/pkg + VCSUrlGoPkgInRegexWithUser = regexp.MustCompile(`^gopkg\.in/([^/]+)/([^.]+)\..*$`) + + // gopkg.in without user segment + // Example: gopkg.in/pkg.v3 -> github.com/go-pkg/pkg + VCSUrlGoPkgInRegexWithoutUser = regexp.MustCompile(`^gopkg\.in/([^.]+)\..*$`) +) + +type Parser struct { + replace bool // 'replace' represents if the 'replace' directive should be taken into account. +} + +func NewParser(replace bool) types.Parser { + return &Parser{ + replace: replace, + } +} + +func (p *Parser) GetExternalRefs(path string) []types.ExternalRef { + if url := resolveVCSUrl(path); url != "" { + return []types.ExternalRef{ + { + Type: types.RefVCS, + URL: url, + }, + } + } + + return nil +} + +func resolveVCSUrl(modulePath string) string { + switch { + case strings.HasPrefix(modulePath, "github.com/"): + return "https://" + VCSUrlMajorVersionSuffixRegex.ReplaceAllString(modulePath, "") + case VCSUrlGoPkgInRegexWithUser.MatchString(modulePath): + return "https://" + VCSUrlGoPkgInRegexWithUser.ReplaceAllString(modulePath, "github.com/$1/$2") + case VCSUrlGoPkgInRegexWithoutUser.MatchString(modulePath): + return "https://" + VCSUrlGoPkgInRegexWithoutUser.ReplaceAllString(modulePath, "github.com/go-$1/$1") + } + + return "" +} + +// Parse parses a go.mod file +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + libs := make(map[string]types.Library) + + goModData, err := io.ReadAll(r) + if err != nil { + return nil, nil, xerrors.Errorf("file read error: %w", err) + } + + modFileParsed, err := modfile.Parse("go.mod", goModData, nil) + if err != nil { + return nil, nil, xerrors.Errorf("go.mod parse error: %w", err) + } + + skipIndirect := true + if modFileParsed.Go != nil { // Old go.mod file may not include the go version. Go version for these files is less than 1.17 + skipIndirect = lessThan117(modFileParsed.Go.Version) + } + + for _, require := range modFileParsed.Require { + // Skip indirect dependencies less than Go 1.17 + if skipIndirect && require.Indirect { + continue + } + libs[require.Mod.Path] = types.Library{ + ID: packageID(require.Mod.Path, require.Mod.Version[1:]), + Name: require.Mod.Path, + Version: require.Mod.Version[1:], + Indirect: require.Indirect, + ExternalReferences: p.GetExternalRefs(require.Mod.Path), + } + } + + // No need to evaluate the 'replace' directive for indirect dependencies + if p.replace { + for _, rep := range modFileParsed.Replace { + // Check if replaced path is actually in our libs. + old, ok := libs[rep.Old.Path] + if !ok { + continue + } + + // If the replace directive has a version on the left side, make sure it matches the version that was imported. + if rep.Old.Version != "" && old.Version != rep.Old.Version[1:] { + continue + } + + // Only support replace directive with version on the right side. + // Directive without version is a local path. + if rep.New.Version == "" { + // Delete old lib, since it's a local path now. + delete(libs, rep.Old.Path) + continue + } + + // Delete old lib, in case the path has changed. + delete(libs, rep.Old.Path) + + // Add replaced library to library register. + libs[rep.New.Path] = types.Library{ + ID: packageID(rep.New.Path, rep.New.Version[1:]), + Name: rep.New.Path, + Version: rep.New.Version[1:], + Indirect: old.Indirect, + ExternalReferences: p.GetExternalRefs(rep.New.Path), + } + } + } + + return maps.Values(libs), nil, nil +} + +// Check if the Go version is less than 1.17 +func lessThan117(ver string) bool { + ss := strings.Split(ver, ".") + if len(ss) != 2 { + return false + } + major, err := strconv.Atoi(ss[0]) + if err != nil { + return false + } + minor, err := strconv.Atoi(ss[1]) + if err != nil { + return false + } + + return major <= 1 && minor < 17 +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.GoModule, name, version) +} diff --git a/pkg/dependency/parser/golang/mod/parse_test.go b/pkg/dependency/parser/golang/mod/parse_test.go new file mode 100644 index 000000000000..6372785df058 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/parse_test.go @@ -0,0 +1,101 @@ +package mod + +import ( + "os" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + file string + replace bool + want []types.Library + }{ + { + name: "normal", + file: "testdata/normal/go.mod", + replace: true, + want: GoModNormal, + }, + { + name: "without go version", + file: "testdata/no-go-version/gomod", + replace: true, + want: GoModNoGoVersion, + }, + { + name: "replace", + file: "testdata/replaced/go.mod", + replace: true, + want: GoModReplaced, + }, + { + name: "no replace", + file: "testdata/replaced/go.mod", + replace: false, + want: GoModUnreplaced, + }, + { + name: "replace with version", + file: "testdata/replaced-with-version/go.mod", + replace: true, + want: GoModReplacedWithVersion, + }, + { + name: "replaced with version mismatch", + file: "testdata/replaced-with-version-mismatch/go.mod", + replace: true, + want: GoModReplacedWithVersionMismatch, + }, + { + name: "replaced with local path", + file: "testdata/replaced-with-local-path/go.mod", + replace: true, + want: GoModReplacedWithLocalPath, + }, + { + name: "replaced with local path and version", + file: "testdata/replaced-with-local-path-and-version/go.mod", + replace: true, + want: GoModReplacedWithLocalPathAndVersion, + }, + { + name: "replaced with local path and version, mismatch", + file: "testdata/replaced-with-local-path-and-version-mismatch/go.mod", + replace: true, + want: GoModReplacedWithLocalPathAndVersionMismatch, + }, + { + name: "go 1.16", + file: "testdata/go116/go.mod", + replace: true, + want: GoMod116, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + + got, _, err := NewParser(tt.replace).Parse(f) + require.NoError(t, err) + + sort.Slice(got, func(i, j int) bool { + return got[i].Name < got[j].Name + }) + sort.Slice(tt.want, func(i, j int) bool { + return tt.want[i].Name < tt.want[j].Name + }) + + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/golang/mod/parse_testcase.go b/pkg/dependency/parser/golang/mod/parse_testcase.go new file mode 100644 index 000000000000..35a396e53c1c --- /dev/null +++ b/pkg/dependency/parser/golang/mod/parse_testcase.go @@ -0,0 +1,261 @@ +package mod + +import "github.com/aquasecurity/trivy/pkg/dependency/types" + +var ( + // execute go mod tidy in normal folder + GoModNormal = []types.Library{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Indirect: true, + }, + { + ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", + Name: "gopkg.in/yaml.v3", + Version: "3.0.0-20210107192922-496545a6307b", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/go-yaml/yaml", + }, + }, + }, + } + + // execute go mod tidy in replaced folder + GoModReplaced = []types.Library{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20220406074731-71021a481237", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Indirect: true, + }, + } + + // execute go mod tidy in replaced folder + GoModUnreplaced = []types.Library{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211110174639-8257534ffed3", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211110174639-8257534ffed3", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Indirect: true, + }, + } + + // execute go mod tidy in replaced-with-version folder + GoModReplacedWithVersion = []types.Library{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20220406074731-71021a481237", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Indirect: true, + }, + } + + // execute go mod tidy in replaced-with-version-mismatch folder + GoModReplacedWithVersionMismatch = []types.Library{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Indirect: true, + }, + { + ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", + Name: "gopkg.in/yaml.v3", + Version: "3.0.0-20210107192922-496545a6307b", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/go-yaml/yaml", + }, + }, + }, + } + + // execute go mod tidy in replaced-with-local-path folder + GoModReplacedWithLocalPath = []types.Library{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + { + ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", + Name: "gopkg.in/yaml.v3", + Version: "3.0.0-20210107192922-496545a6307b", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/go-yaml/yaml", + }, + }, + }, + } + + // execute go mod tidy in replaced-with-local-path-and-version folder + GoModReplacedWithLocalPathAndVersion = []types.Library{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + { + ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", + Name: "gopkg.in/yaml.v3", + Version: "3.0.0-20210107192922-496545a6307b", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/go-yaml/yaml", + }, + }, + }, + } + + // execute go mod tidy in replaced-with-local-path-and-version-mismatch folder + GoModReplacedWithLocalPathAndVersionMismatch = []types.Library{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Indirect: true, + }, + { + ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", + Name: "gopkg.in/yaml.v3", + Version: "3.0.0-20210107192922-496545a6307b", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/go-yaml/yaml", + }, + }, + }, + } + + // execute go mod tidy in go116 folder + GoMod116 = []types.Library{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + } + + // execute go mod tidy in no-go-version folder + GoModNoGoVersion = []types.Library{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + } +) diff --git a/pkg/dependency/parser/golang/mod/testdata/go116/go.mod b/pkg/dependency/parser/golang/mod/testdata/go116/go.mod new file mode 100644 index 000000000000..d86af50288e8 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/go116/go.mod @@ -0,0 +1,7 @@ +module github.com/org/repo + +go 1.16 + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff + +require gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/pkg/dependency/parser/golang/mod/testdata/go116/go.sum b/pkg/dependency/parser/golang/mod/testdata/go116/go.sum new file mode 100644 index 000000000000..032bb4727cfe --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/go116/go.sum @@ -0,0 +1,62 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff h1:JCKEV3TgUNh9fn+8hXyIdsF9yErA0rUbCkgt2flRKt4= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff/go.mod h1:8fJ//Ob6/03lxbn4xa1F+G/giVtiVLxnZNpBp5xOxNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/pkg/dependency/parser/golang/mod/testdata/go116/main.go b/pkg/dependency/parser/golang/mod/testdata/go116/main.go new file mode 100644 index 000000000000..0b51fb861f32 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/go116/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" +) + +func main() { + if _, err := mod.Parse(nil); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/dependency/parser/golang/mod/testdata/no-go-version/gomod b/pkg/dependency/parser/golang/mod/testdata/no-go-version/gomod new file mode 100644 index 000000000000..a95a8f7eba91 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/no-go-version/gomod @@ -0,0 +1,3 @@ +module github.com/org/repo + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff \ No newline at end of file diff --git a/pkg/dependency/parser/golang/mod/testdata/no-go-version/main.go b/pkg/dependency/parser/golang/mod/testdata/no-go-version/main.go new file mode 100644 index 000000000000..0b51fb861f32 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/no-go-version/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" +) + +func main() { + if _, err := mod.Parse(nil); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/dependency/parser/golang/mod/testdata/normal/go.mod b/pkg/dependency/parser/golang/mod/testdata/normal/go.mod new file mode 100644 index 000000000000..9d48b25e0079 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/normal/go.mod @@ -0,0 +1,10 @@ +module github.com/org/repo + +go 1.17 + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff + +require ( + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/pkg/dependency/parser/golang/mod/testdata/normal/go.sum b/pkg/dependency/parser/golang/mod/testdata/normal/go.sum new file mode 100644 index 000000000000..032bb4727cfe --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/normal/go.sum @@ -0,0 +1,62 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff h1:JCKEV3TgUNh9fn+8hXyIdsF9yErA0rUbCkgt2flRKt4= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff/go.mod h1:8fJ//Ob6/03lxbn4xa1F+G/giVtiVLxnZNpBp5xOxNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/pkg/dependency/parser/golang/mod/testdata/normal/main.go b/pkg/dependency/parser/golang/mod/testdata/normal/main.go new file mode 100644 index 000000000000..0b51fb861f32 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/normal/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" +) + +func main() { + if _, err := mod.Parse(nil); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/go.mod b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/go.mod new file mode 100644 index 000000000000..7df6b938efc9 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/go.mod @@ -0,0 +1,12 @@ +module github.com/org/repo + +go 1.17 + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff + +require ( + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) + +replace golang.org/x/xerrors v0.0.1 => ./xerrors diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/go.sum b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/go.sum new file mode 100644 index 000000000000..8a219a39d474 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/go.sum @@ -0,0 +1,61 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff h1:JCKEV3TgUNh9fn+8hXyIdsF9yErA0rUbCkgt2flRKt4= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff/go.mod h1:8fJ//Ob6/03lxbn4xa1F+G/giVtiVLxnZNpBp5xOxNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/main.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/main.go new file mode 100644 index 000000000000..0b51fb861f32 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" +) + +func main() { + if _, err := mod.Parse(nil); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/xerrors/go.mod b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/xerrors/go.mod new file mode 100644 index 000000000000..a795158073f9 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/xerrors/go.mod @@ -0,0 +1,3 @@ +module golang.org/x/xerrors + +go 1.12 diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/xerrors/xerrors.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/xerrors/xerrors.go new file mode 100644 index 000000000000..c92105140956 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/xerrors/xerrors.go @@ -0,0 +1,5 @@ +package xerrors + +func Errorf(format string, a ...interface{}) error { + return nil +} diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/go.mod b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/go.mod new file mode 100644 index 000000000000..49ac483441af --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/go.mod @@ -0,0 +1,12 @@ +module github.com/org/repo + +go 1.17 + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff + +require ( + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) + +replace golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 => ./xerrors diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/go.sum b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/go.sum new file mode 100644 index 000000000000..3d1d7c0e3913 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/go.sum @@ -0,0 +1,60 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff h1:JCKEV3TgUNh9fn+8hXyIdsF9yErA0rUbCkgt2flRKt4= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff/go.mod h1:8fJ//Ob6/03lxbn4xa1F+G/giVtiVLxnZNpBp5xOxNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/main.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/main.go new file mode 100644 index 000000000000..0b51fb861f32 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" +) + +func main() { + if _, err := mod.Parse(nil); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/xerrors/go.mod b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/xerrors/go.mod new file mode 100644 index 000000000000..a795158073f9 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/xerrors/go.mod @@ -0,0 +1,3 @@ +module golang.org/x/xerrors + +go 1.12 diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/xerrors/xerrors.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/xerrors/xerrors.go new file mode 100644 index 000000000000..c92105140956 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/xerrors/xerrors.go @@ -0,0 +1,5 @@ +package xerrors + +func Errorf(format string, a ...interface{}) error { + return nil +} diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/go.mod b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/go.mod new file mode 100644 index 000000000000..50f5c43a18dc --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/go.mod @@ -0,0 +1,12 @@ +module github.com/org/repo + +go 1.17 + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff + +require ( + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) + +replace golang.org/x/xerrors => ./xerrors diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/go.sum b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/go.sum new file mode 100644 index 000000000000..444e67c69532 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/go.sum @@ -0,0 +1,59 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff h1:JCKEV3TgUNh9fn+8hXyIdsF9yErA0rUbCkgt2flRKt4= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff/go.mod h1:8fJ//Ob6/03lxbn4xa1F+G/giVtiVLxnZNpBp5xOxNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/main.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/main.go new file mode 100644 index 000000000000..0b51fb861f32 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" +) + +func main() { + if _, err := mod.Parse(nil); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/xerrors/go.mod b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/xerrors/go.mod new file mode 100644 index 000000000000..a795158073f9 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/xerrors/go.mod @@ -0,0 +1,3 @@ +module golang.org/x/xerrors + +go 1.12 diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/xerrors/xerrors.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/xerrors/xerrors.go new file mode 100644 index 000000000000..c92105140956 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/xerrors/xerrors.go @@ -0,0 +1,5 @@ +package xerrors + +func Errorf(format string, a ...interface{}) error { + return nil +} diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-version-mismatch/go.mod b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version-mismatch/go.mod new file mode 100644 index 000000000000..0f2f71faba63 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version-mismatch/go.mod @@ -0,0 +1,12 @@ +module github.com/org/repo + +go 1.17 + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff + +require ( + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) + +replace github.com/aquasecurity/go-dep-parser v0.0.1 => github.com/aquasecurity/go-dep-parser v0.0.0-20220406074731-71021a481237 diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-version-mismatch/go.sum b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version-mismatch/go.sum new file mode 100644 index 000000000000..032bb4727cfe --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version-mismatch/go.sum @@ -0,0 +1,62 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff h1:JCKEV3TgUNh9fn+8hXyIdsF9yErA0rUbCkgt2flRKt4= +github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff/go.mod h1:8fJ//Ob6/03lxbn4xa1F+G/giVtiVLxnZNpBp5xOxNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-version-mismatch/main.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version-mismatch/main.go new file mode 100644 index 000000000000..0b51fb861f32 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version-mismatch/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" +) + +func main() { + if _, err := mod.Parse(nil); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-version/go.mod b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version/go.mod new file mode 100644 index 000000000000..e02dc649e6dc --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version/go.mod @@ -0,0 +1,9 @@ +module github.com/org/repo + +go 1.17 + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211110174639-8257534ffed3 + +require golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + +replace github.com/aquasecurity/go-dep-parser v0.0.0-20211110174639-8257534ffed3 => github.com/aquasecurity/go-dep-parser v0.0.0-20220406074731-71021a481237 diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-version/go.sum b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version/go.sum new file mode 100644 index 000000000000..b75b2debc906 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version/go.sum @@ -0,0 +1,61 @@ +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aquasecurity/go-dep-parser v0.0.0-20220406074731-71021a481237 h1:FX5MaNimz5xK6LYbp+mI23i2m6OmoKaHAEgRVehLDs8= +github.com/aquasecurity/go-dep-parser v0.0.0-20220406074731-71021a481237/go.mod h1:MewgJXyrz9PgCHh8zunRNY4BY72ltNYWeTYAt1paaLc= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-version/main.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version/main.go new file mode 100644 index 000000000000..0b51fb861f32 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-version/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" +) + +func main() { + if _, err := mod.Parse(nil); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced/go.mod b/pkg/dependency/parser/golang/mod/testdata/replaced/go.mod new file mode 100644 index 000000000000..cc7e47974be2 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced/go.mod @@ -0,0 +1,9 @@ +module github.com/org/repo + +go 1.17 + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211110174639-8257534ffed3 + +require golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + +replace github.com/aquasecurity/go-dep-parser => github.com/aquasecurity/go-dep-parser v0.0.0-20220406074731-71021a481237 diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced/go.sum b/pkg/dependency/parser/golang/mod/testdata/replaced/go.sum new file mode 100644 index 000000000000..b75b2debc906 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced/go.sum @@ -0,0 +1,61 @@ +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aquasecurity/go-dep-parser v0.0.0-20220406074731-71021a481237 h1:FX5MaNimz5xK6LYbp+mI23i2m6OmoKaHAEgRVehLDs8= +github.com/aquasecurity/go-dep-parser v0.0.0-20220406074731-71021a481237/go.mod h1:MewgJXyrz9PgCHh8zunRNY4BY72ltNYWeTYAt1paaLc= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced/main.go b/pkg/dependency/parser/golang/mod/testdata/replaced/main.go new file mode 100644 index 000000000000..0b51fb861f32 --- /dev/null +++ b/pkg/dependency/parser/golang/mod/testdata/replaced/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" +) + +func main() { + if _, err := mod.Parse(nil); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/dependency/parser/golang/sum/parse.go b/pkg/dependency/parser/golang/sum/parse.go new file mode 100644 index 000000000000..e06b474c0a97 --- /dev/null +++ b/pkg/dependency/parser/golang/sum/parse.go @@ -0,0 +1,51 @@ +package sum + +import ( + "bufio" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +// Parse parses a go.sum file +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var libs []types.Library + uniqueLibs := make(map[string]string) + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + s := strings.Fields(line) + if len(s) < 2 { + continue + } + + // go.sum records and sorts all non-major versions + // with the latest version as last entry + uniqueLibs[s[0]] = strings.TrimSuffix(strings.TrimPrefix(s[1], "v"), "/go.mod") + } + if err := scanner.Err(); err != nil { + return nil, nil, xerrors.Errorf("scan error: %w", err) + } + + for k, v := range uniqueLibs { + libs = append(libs, types.Library{ + ID: dependency.ID(ftypes.GoModule, k, v), + Name: k, + Version: v, + }) + } + + return libs, nil, nil +} diff --git a/pkg/dependency/parser/golang/sum/parse_test.go b/pkg/dependency/parser/golang/sum/parse_test.go new file mode 100644 index 000000000000..3888743cdf85 --- /dev/null +++ b/pkg/dependency/parser/golang/sum/parse_test.go @@ -0,0 +1,61 @@ +package sum + +import ( + "os" + "path" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + vectors := []struct { + file string + want []types.Library + }{ + { + file: "testdata/gomod_normal.sum", + want: GoModNormal, + }, + { + file: "testdata/gomod_emptyline.sum", + want: GoModEmptyLine, + }, + { + file: "testdata/gomod_many.sum", + want: GoModMany, + }, + { + file: "testdata/gomod_trivy.sum", + want: GoModTrivy, + }, + } + + for _, v := range vectors { + t.Run(path.Base(v.file), func(t *testing.T) { + f, err := os.Open(v.file) + require.NoError(t, err) + defer f.Close() + + got, _, err := NewParser().Parse(f) + require.NoError(t, err) + + for i := range got { + got[i].ID = "" // Not compare IDs, tested in mod.TestModuleID() + } + + sort.Slice(got, func(i, j int) bool { + return got[i].Name < got[j].Name + }) + sort.Slice(v.want, func(i, j int) bool { + return v.want[i].Name < v.want[j].Name + }) + + assert.Equal(t, v.want, got) + }) + } +} diff --git a/pkg/dependency/parser/golang/sum/parse_testcase.go b/pkg/dependency/parser/golang/sum/parse_testcase.go new file mode 100644 index 000000000000..70d4972c6f76 --- /dev/null +++ b/pkg/dependency/parser/golang/sum/parse_testcase.go @@ -0,0 +1,394 @@ +package sum + +import "github.com/aquasecurity/trivy/pkg/dependency/types" + +var ( + // docker run --name gomod --rm -it golang:1.15 bash + // export USER=gomod + // mkdir repo + // cd repo + // go mod init github.com/org/repo + // go get golang.org/x/xerrors + // go list -m all | awk 'NR>1 {sub(/^v/, "", $2); printf("{\""$1"\", \""$2"\", },\n")}' + GoModNormal = []types.Library{ + {Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1"}, + } + + // https://github.com/uudashr/gopkgs/blob/616744904701ef01d868da4b66aad0e6856c361d/v2/go.sum + GoModEmptyLine = []types.Library{ + {Name: "github.com/karrick/godirwalk", Version: "1.12.0"}, + {Name: "github.com/pkg/errors", Version: "0.8.1"}, + } + + // docker run --name gomod --rm -it golang:1.15 bash + // export USER=gomod + // mkdir repo + // cd repo + // go mod init github.com/org/repo + // go get golang.org/x/xerrors + // go get github.com/urfave/cli + // go get github.com/stretchr/testify + // go get github.com/BurntSushi/toml + // go list -m all | awk 'NR>1 {sub(/^v/, "", $2); printf("{\""$1"\", \""$2"\", },\n")}' + GoModMany = []types.Library{ + {Name: "github.com/BurntSushi/toml", Version: "0.3.1"}, + {Name: "github.com/cpuguy83/go-md2man/v2", Version: "2.0.0-20190314233015-f79a8a8ca69d"}, + {Name: "github.com/davecgh/go-spew", Version: "1.1.0"}, + {Name: "github.com/pmezard/go-difflib", Version: "1.0.0"}, + {Name: "github.com/russross/blackfriday/v2", Version: "2.0.1"}, + {Name: "github.com/shurcooL/sanitized_anchor_name", Version: "1.0.0"}, + {Name: "github.com/stretchr/objx", Version: "0.1.0"}, + {Name: "github.com/stretchr/testify", Version: "1.7.0"}, + {Name: "github.com/urfave/cli", Version: "1.22.5"}, + {Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1"}, + {Name: "gopkg.in/check.v1", Version: "0.0.0-20161208181325-20d25e280405"}, + {Name: "gopkg.in/yaml.v2", Version: "2.2.2"}, + {Name: "gopkg.in/yaml.v3", Version: "3.0.0-20200313102051-9f266ea9e77c"}, + } + + // docker run --name gomod --rm -it golang:1.15 bash + // export USER=gomod + // mkdir repo + // cd repo + // go mod init github.com/org/repo + // go get github.com/aquasecurity/trivy + // go list -m all | awk 'NR>1 {sub(/^v/, "", $2); printf("{\""$1"\", \""$2"\", },\n")}' + GoModTrivy = []types.Library{ + {Name: "cloud.google.com/go", Version: "0.65.0"}, + {Name: "cloud.google.com/go/bigquery", Version: "1.8.0"}, + {Name: "cloud.google.com/go/datastore", Version: "1.1.0"}, + {Name: "cloud.google.com/go/pubsub", Version: "1.3.1"}, + {Name: "cloud.google.com/go/storage", Version: "1.10.0"}, + {Name: "dmitri.shuralyov.com/gpu/mtl", Version: "0.0.0-20190408044501-666a987793e9"}, + {Name: "github.com/Azure/azure-sdk-for-go", Version: "38.0.0+incompatible"}, + {Name: "github.com/Azure/go-ansiterm", Version: "0.0.0-20170929234023-d6e3b3328b78"}, + {Name: "github.com/Azure/go-autorest/autorest", Version: "0.9.3"}, + {Name: "github.com/Azure/go-autorest/autorest/adal", Version: "0.8.1"}, + {Name: "github.com/Azure/go-autorest/autorest/date", Version: "0.2.0"}, + {Name: "github.com/Azure/go-autorest/autorest/mocks", Version: "0.3.0"}, + {Name: "github.com/Azure/go-autorest/autorest/to", Version: "0.3.0"}, + {Name: "github.com/Azure/go-autorest/autorest/validation", Version: "0.1.0"}, + {Name: "github.com/Azure/go-autorest/logger", Version: "0.1.0"}, + {Name: "github.com/Azure/go-autorest/tracing", Version: "0.5.0"}, + {Name: "github.com/BurntSushi/toml", Version: "0.3.1"}, + {Name: "github.com/BurntSushi/xgb", Version: "0.0.0-20160522181843-27f122750802"}, + {Name: "github.com/GoogleCloudPlatform/docker-credential-gcr", Version: "1.5.0"}, + {Name: "github.com/GoogleCloudPlatform/k8s-cloud-provider", Version: "0.0.0-20190822182118-27a4ced34534"}, + {Name: "github.com/Microsoft/go-winio", Version: "0.4.15-0.20190919025122-fc70bd9a86b5"}, + {Name: "github.com/Microsoft/hcsshim", Version: "0.8.6"}, + {Name: "github.com/NYTimes/gziphandler", Version: "0.0.0-20170623195520-56545f4a5d46"}, + {Name: "github.com/OneOfOne/xxhash", Version: "1.2.7"}, + {Name: "github.com/PuerkitoBio/purell", Version: "1.1.1"}, + {Name: "github.com/PuerkitoBio/urlesc", Version: "0.0.0-20170810143723-de5bf2ad4578"}, + {Name: "github.com/VividCortex/ewma", Version: "1.1.1"}, + {Name: "github.com/alcortesm/tgz", Version: "0.0.0-20161220082320-9c5fe88206d7"}, + {Name: "github.com/alecthomas/template", Version: "0.0.0-20160405071501-a0175ee3bccc"}, + {Name: "github.com/alecthomas/units", Version: "0.0.0-20151022065526-2efee857e7cf"}, + {Name: "github.com/alicebob/gopher-json", Version: "0.0.0-20200520072559-a9ecdc9d1d3a"}, + {Name: "github.com/alicebob/miniredis/v2", Version: "2.14.1"}, + {Name: "github.com/anmitsu/go-shlex", Version: "0.0.0-20161002113705-648efa622239"}, + {Name: "github.com/aquasecurity/bolt-fixtures", Version: "0.0.0-20200903104109-d34e7f983986"}, + {Name: "github.com/aquasecurity/fanal", Version: "0.0.0-20210119051230-28c249da7cfd"}, + {Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20201028043324-889d4a92b8e0"}, + {Name: "github.com/aquasecurity/go-gem-version", Version: "0.0.0-20201115065557-8eed6fe000ce"}, + {Name: "github.com/aquasecurity/go-npm-version", Version: "0.0.0-20201110091526-0b796d180798"}, + {Name: "github.com/aquasecurity/go-pep440-version", Version: "0.0.0-20210121094942-22b2f8951d46"}, + {Name: "github.com/aquasecurity/go-version", Version: "0.0.0-20210121072130-637058cfe492"}, + {Name: "github.com/aquasecurity/testdocker", Version: "0.0.0-20210106133225-0b17fe083674"}, + {Name: "github.com/aquasecurity/trivy", Version: "0.16.0"}, + {Name: "github.com/aquasecurity/trivy-db", Version: "0.0.0-20210105160501-c5bf4e153277"}, + {Name: "github.com/aquasecurity/vuln-list-update", Version: "0.0.0-20191016075347-3d158c2bf9a2"}, + {Name: "github.com/araddon/dateparse", Version: "0.0.0-20190426192744-0d74ffceef83"}, + {Name: "github.com/armon/consul-api", Version: "0.0.0-20180202201655-eb2c6b5be1b6"}, + {Name: "github.com/armon/go-socks5", Version: "0.0.0-20160902184237-e75332964ef5"}, + {Name: "github.com/aws/aws-sdk-go", Version: "1.27.1"}, + {Name: "github.com/beorn7/perks", Version: "1.0.0"}, + {Name: "github.com/bgentry/speakeasy", Version: "0.1.0"}, + {Name: "github.com/blang/semver", Version: "3.5.0+incompatible"}, + {Name: "github.com/briandowns/spinner", Version: "1.12.0"}, + {Name: "github.com/caarlos0/env/v6", Version: "6.0.0"}, + {Name: "github.com/cenkalti/backoff", Version: "2.2.1+incompatible"}, + {Name: "github.com/census-instrumentation/opencensus-proto", Version: "0.2.1"}, + {Name: "github.com/cespare/xxhash/v2", Version: "2.1.1"}, + {Name: "github.com/cheggaaa/pb/v3", Version: "3.0.3"}, + {Name: "github.com/chzyer/logex", Version: "1.1.10"}, + {Name: "github.com/chzyer/readline", Version: "0.0.0-20180603132655-2972be24d48e"}, + {Name: "github.com/chzyer/test", Version: "0.0.0-20180213035817-a1ea475d72b1"}, + {Name: "github.com/client9/misspell", Version: "0.3.4"}, + {Name: "github.com/cncf/udpa/go", Version: "0.0.0-20191209042840-269d4d468f6f"}, + {Name: "github.com/cockroachdb/datadriven", Version: "0.0.0-20190809214429-80d97fb3cbaa"}, + {Name: "github.com/containerd/containerd", Version: "1.3.3"}, + {Name: "github.com/containerd/continuity", Version: "0.0.0-20190426062206-aaeac12a7ffc"}, + {Name: "github.com/coreos/etcd", Version: "3.3.10+incompatible"}, + {Name: "github.com/coreos/go-etcd", Version: "2.0.0+incompatible"}, + {Name: "github.com/coreos/go-oidc", Version: "2.1.0+incompatible"}, + {Name: "github.com/coreos/go-semver", Version: "0.3.0"}, + {Name: "github.com/coreos/go-systemd", Version: "0.0.0-20190321100706-95778dfbb74e"}, + {Name: "github.com/coreos/pkg", Version: "0.0.0-20180108230652-97fdf19511ea"}, + {Name: "github.com/cpuguy83/go-md2man", Version: "1.0.10"}, + {Name: "github.com/cpuguy83/go-md2man/v2", Version: "2.0.0"}, + {Name: "github.com/creack/pty", Version: "1.1.9"}, + {Name: "github.com/davecgh/go-spew", Version: "1.1.1"}, + {Name: "github.com/deckarep/golang-set", Version: "1.7.1"}, + {Name: "github.com/dgrijalva/jwt-go", Version: "3.2.0+incompatible"}, + {Name: "github.com/dgryski/go-rendezvous", Version: "0.0.0-20200823014737-9f7001d12a5f"}, + {Name: "github.com/dnaeon/go-vcr", Version: "1.0.1"}, + {Name: "github.com/docker/cli", Version: "0.0.0-20191017083524-a8ff7f821017"}, + {Name: "github.com/docker/distribution", Version: "2.7.1+incompatible"}, + {Name: "github.com/docker/docker", Version: "1.4.2-0.20190924003213-a8608b5b67c7"}, + {Name: "github.com/docker/docker-credential-helpers", Version: "0.6.3"}, + {Name: "github.com/docker/go-connections", Version: "0.4.0"}, + {Name: "github.com/docker/go-units", Version: "0.4.0"}, + {Name: "github.com/docker/spdystream", Version: "0.0.0-20160310174837-449fdfce4d96"}, + {Name: "github.com/dustin/go-humanize", Version: "1.0.0"}, + {Name: "github.com/elazarl/goproxy", Version: "0.0.0-20200809112317-0581fc3aee2d"}, + {Name: "github.com/elazarl/goproxy/ext", Version: "0.0.0-20200809112317-0581fc3aee2d"}, + {Name: "github.com/emicklei/go-restful", Version: "2.9.5+incompatible"}, + {Name: "github.com/emirpasic/gods", Version: "1.12.0"}, + {Name: "github.com/envoyproxy/go-control-plane", Version: "0.9.4"}, + {Name: "github.com/envoyproxy/protoc-gen-validate", Version: "0.1.0"}, + {Name: "github.com/evanphx/json-patch", Version: "4.2.0+incompatible"}, + {Name: "github.com/fatih/color", Version: "1.10.0"}, + {Name: "github.com/flynn/go-shlex", Version: "0.0.0-20150515145356-3f9db97f8568"}, + {Name: "github.com/fsnotify/fsnotify", Version: "1.4.9"}, + {Name: "github.com/ghodss/yaml", Version: "1.0.0"}, + {Name: "github.com/gin-contrib/sse", Version: "0.1.0"}, + {Name: "github.com/gin-gonic/gin", Version: "1.5.0"}, + {Name: "github.com/gliderlabs/ssh", Version: "0.2.2"}, + {Name: "github.com/go-git/gcfg", Version: "1.5.0"}, + {Name: "github.com/go-git/go-billy/v5", Version: "5.0.0"}, + {Name: "github.com/go-git/go-git-fixtures/v4", Version: "4.0.1"}, + {Name: "github.com/go-git/go-git/v5", Version: "5.0.0"}, + {Name: "github.com/go-gl/glfw", Version: "0.0.0-20190409004039-e6da0acd62b1"}, + {Name: "github.com/go-gl/glfw/v3.3/glfw", Version: "0.0.0-20200222043503-6f7a984d4dc4"}, + {Name: "github.com/go-kit/kit", Version: "0.8.0"}, + {Name: "github.com/go-logfmt/logfmt", Version: "0.3.0"}, + {Name: "github.com/go-logr/logr", Version: "0.1.0"}, + {Name: "github.com/go-openapi/jsonpointer", Version: "0.19.3"}, + {Name: "github.com/go-openapi/jsonreference", Version: "0.19.3"}, + {Name: "github.com/go-openapi/spec", Version: "0.19.3"}, + {Name: "github.com/go-openapi/swag", Version: "0.19.5"}, + {Name: "github.com/go-playground/locales", Version: "0.13.0"}, + {Name: "github.com/go-playground/universal-translator", Version: "0.17.0"}, + {Name: "github.com/go-redis/redis", Version: "6.15.7+incompatible"}, + {Name: "github.com/go-redis/redis/v8", Version: "8.4.0"}, + {Name: "github.com/go-restruct/restruct", Version: "0.0.0-20191227155143-5734170a48a1"}, + {Name: "github.com/go-sql-driver/mysql", Version: "1.5.0"}, + {Name: "github.com/go-stack/stack", Version: "1.8.0"}, + {Name: "github.com/gobwas/glob", Version: "0.2.3"}, + {Name: "github.com/goccy/go-yaml", Version: "1.8.2"}, + {Name: "github.com/gogo/protobuf", Version: "1.3.1"}, + {Name: "github.com/golang/glog", Version: "0.0.0-20160126235308-23def4e6c14b"}, + {Name: "github.com/golang/groupcache", Version: "0.0.0-20200121045136-8c9f03a8e57e"}, + {Name: "github.com/golang/mock", Version: "1.4.4"}, + {Name: "github.com/golang/protobuf", Version: "1.4.2"}, + {Name: "github.com/google/btree", Version: "1.0.0"}, + {Name: "github.com/google/go-cmp", Version: "0.5.3"}, + {Name: "github.com/google/go-containerregistry", Version: "0.0.0-20200331213917-3d03ed9b1ca2"}, + {Name: "github.com/google/go-github/v28", Version: "28.1.1"}, + {Name: "github.com/google/go-querystring", Version: "1.0.0"}, + {Name: "github.com/google/gofuzz", Version: "1.0.0"}, + {Name: "github.com/google/martian", Version: "2.1.0+incompatible"}, + {Name: "github.com/google/martian/v3", Version: "3.0.0"}, + {Name: "github.com/google/pprof", Version: "0.0.0-20200708004538-1a94d8640e99"}, + {Name: "github.com/google/renameio", Version: "0.1.0"}, + {Name: "github.com/google/subcommands", Version: "1.0.1"}, + {Name: "github.com/google/uuid", Version: "1.1.1"}, + {Name: "github.com/google/wire", Version: "0.3.0"}, + {Name: "github.com/googleapis/gax-go/v2", Version: "2.0.5"}, + {Name: "github.com/googleapis/gnostic", Version: "0.2.2"}, + {Name: "github.com/gophercloud/gophercloud", Version: "0.1.0"}, + {Name: "github.com/gopherjs/gopherjs", Version: "0.0.0-20200217142428-fce0ec30dd00"}, + {Name: "github.com/gorilla/context", Version: "1.1.1"}, + {Name: "github.com/gorilla/mux", Version: "1.7.4"}, + {Name: "github.com/gorilla/websocket", Version: "1.4.0"}, + {Name: "github.com/gregjones/httpcache", Version: "0.0.0-20180305231024-9cad4c3443a7"}, + {Name: "github.com/grpc-ecosystem/go-grpc-middleware", Version: "1.0.1-0.20190118093823-f849b5445de4"}, + {Name: "github.com/grpc-ecosystem/go-grpc-prometheus", Version: "1.2.0"}, + {Name: "github.com/grpc-ecosystem/grpc-gateway", Version: "1.9.5"}, + {Name: "github.com/hashicorp/errwrap", Version: "1.0.0"}, + {Name: "github.com/hashicorp/go-multierror", Version: "1.1.0"}, + {Name: "github.com/hashicorp/go-version", Version: "1.2.1"}, + {Name: "github.com/hashicorp/golang-lru", Version: "0.5.3"}, + {Name: "github.com/hashicorp/hcl", Version: "1.0.0"}, + {Name: "github.com/hpcloud/tail", Version: "1.0.0"}, + {Name: "github.com/ianlancetaylor/demangle", Version: "0.0.0-20181102032728-5e5cf60278f6"}, + {Name: "github.com/imdario/mergo", Version: "0.3.5"}, + {Name: "github.com/inconshreveable/mousetrap", Version: "1.0.0"}, + {Name: "github.com/jbenet/go-context", Version: "0.0.0-20150711004518-d14ea06fba99"}, + {Name: "github.com/jessevdk/go-flags", Version: "1.4.0"}, + {Name: "github.com/jmespath/go-jmespath", Version: "0.0.0-20180206201540-c2b33e8439af"}, + {Name: "github.com/joefitzgerald/rainbow-reporter", Version: "0.1.0"}, + {Name: "github.com/jonboulle/clockwork", Version: "0.1.0"}, + {Name: "github.com/json-iterator/go", Version: "1.1.8"}, + {Name: "github.com/jstemmer/go-junit-report", Version: "0.9.1"}, + {Name: "github.com/jtolds/gls", Version: "4.20.0+incompatible"}, + {Name: "github.com/julienschmidt/httprouter", Version: "1.2.0"}, + {Name: "github.com/kevinburke/ssh_config", Version: "0.0.0-20190725054713-01f96b0aa0cd"}, + {Name: "github.com/kisielk/errcheck", Version: "1.2.0"}, + {Name: "github.com/kisielk/gotool", Version: "1.0.0"}, + {Name: "github.com/knqyf263/go-apk-version", Version: "0.0.0-20200609155635-041fdbb8563f"}, + {Name: "github.com/knqyf263/go-deb-version", Version: "0.0.0-20190517075300-09fca494f03d"}, + {Name: "github.com/knqyf263/go-rpm-version", Version: "0.0.0-20170716094938-74609b86c936"}, + {Name: "github.com/knqyf263/go-rpmdb", Version: "0.0.0-20201215100354-a9e3110d8ee1"}, + {Name: "github.com/knqyf263/nested", Version: "0.0.1"}, + {Name: "github.com/konsorten/go-windows-terminal-sequences", Version: "1.0.2"}, + {Name: "github.com/kr/logfmt", Version: "0.0.0-20140226030751-b84e30acd515"}, + {Name: "github.com/kr/pretty", Version: "0.1.0"}, + {Name: "github.com/kr/pty", Version: "1.1.5"}, + {Name: "github.com/kr/text", Version: "0.2.0"}, + {Name: "github.com/kylelemons/godebug", Version: "1.1.0"}, + {Name: "github.com/leodido/go-urn", Version: "1.2.0"}, + {Name: "github.com/magiconair/properties", Version: "1.8.0"}, + {Name: "github.com/mailru/easyjson", Version: "0.7.0"}, + {Name: "github.com/mattn/go-colorable", Version: "0.1.8"}, + {Name: "github.com/mattn/go-isatty", Version: "0.0.12"}, + {Name: "github.com/mattn/go-jsonpointer", Version: "0.0.0-20180225143300-37667080efed"}, + {Name: "github.com/mattn/go-runewidth", Version: "0.0.9"}, + {Name: "github.com/matttproud/golang_protobuf_extensions", Version: "1.0.1"}, + {Name: "github.com/maxbrunsfeld/counterfeiter/v6", Version: "6.2.2"}, + {Name: "github.com/mitchellh/go-homedir", Version: "1.1.0"}, + {Name: "github.com/mitchellh/mapstructure", Version: "1.1.2"}, + {Name: "github.com/modern-go/concurrent", Version: "0.0.0-20180306012644-bacd9c7ef1dd"}, + {Name: "github.com/modern-go/reflect2", Version: "1.0.1"}, + {Name: "github.com/morikuni/aec", Version: "1.0.0"}, + {Name: "github.com/munnerz/goautoneg", Version: "0.0.0-20191010083416-a7dc8b61c822"}, + {Name: "github.com/mwitkow/go-conntrack", Version: "0.0.0-20161129095857-cc309e4a2223"}, + {Name: "github.com/mxk/go-flowrate", Version: "0.0.0-20140419014527-cca7078d478f"}, + {Name: "github.com/niemeyer/pretty", Version: "0.0.0-20200227124842-a10e7caefd8e"}, + {Name: "github.com/nxadm/tail", Version: "1.4.4"}, + {Name: "github.com/olekukonko/tablewriter", Version: "0.0.2-0.20190607075207-195002e6e56a"}, + {Name: "github.com/onsi/ginkgo", Version: "1.14.2"}, + {Name: "github.com/onsi/gomega", Version: "1.10.3"}, + {Name: "github.com/open-policy-agent/opa", Version: "0.21.1"}, + {Name: "github.com/opencontainers/go-digest", Version: "1.0.0-rc1"}, + {Name: "github.com/opencontainers/image-spec", Version: "1.0.2-0.20190823105129-775207bd45b6"}, + {Name: "github.com/opencontainers/runc", Version: "0.1.1"}, + {Name: "github.com/parnurzeal/gorequest", Version: "0.2.16"}, + {Name: "github.com/pelletier/go-toml", Version: "1.2.0"}, + {Name: "github.com/peterbourgon/diskv", Version: "2.0.1+incompatible"}, + {Name: "github.com/peterh/liner", Version: "0.0.0-20170211195444-bf27d3ba8e1d"}, + {Name: "github.com/pkg/errors", Version: "0.9.1"}, + {Name: "github.com/pmezard/go-difflib", Version: "1.0.0"}, + {Name: "github.com/pquerna/cachecontrol", Version: "0.0.0-20171018203845-0dec1b30a021"}, + {Name: "github.com/prometheus/client_golang", Version: "1.0.0"}, + {Name: "github.com/prometheus/client_model", Version: "0.0.0-20190812154241-14fe0d1b01d4"}, + {Name: "github.com/prometheus/common", Version: "0.4.1"}, + {Name: "github.com/prometheus/procfs", Version: "0.0.2"}, + {Name: "github.com/rcrowley/go-metrics", Version: "0.0.0-20181016184325-3113b8401b8a"}, + {Name: "github.com/remyoudompheng/bigfft", Version: "0.0.0-20170806203942-52369c62f446"}, + {Name: "github.com/rogpeppe/fastuuid", Version: "0.0.0-20150106093220-6724a57986af"}, + {Name: "github.com/rogpeppe/go-charset", Version: "0.0.0-20180617210344-2471d30d28b4"}, + {Name: "github.com/rogpeppe/go-internal", Version: "1.3.0"}, + {Name: "github.com/rubiojr/go-vhd", Version: "0.0.0-20160810183302-0bfd3b39853c"}, + {Name: "github.com/russross/blackfriday", Version: "1.5.2"}, + {Name: "github.com/russross/blackfriday/v2", Version: "2.0.1"}, + {Name: "github.com/saracen/walker", Version: "0.0.0-20191201085201-324a081bae7e"}, + {Name: "github.com/satori/go.uuid", Version: "1.2.0"}, + {Name: "github.com/sclevine/spec", Version: "1.2.0"}, + {Name: "github.com/sergi/go-diff", Version: "1.1.0"}, + {Name: "github.com/shurcooL/sanitized_anchor_name", Version: "1.0.0"}, + {Name: "github.com/simplereach/timeutils", Version: "1.2.0"}, + {Name: "github.com/sirupsen/logrus", Version: "1.5.0"}, + {Name: "github.com/smartystreets/assertions", Version: "1.2.0"}, + {Name: "github.com/smartystreets/goconvey", Version: "1.6.4"}, + {Name: "github.com/soheilhy/cmux", Version: "0.1.4"}, + {Name: "github.com/sosedoff/gitkit", Version: "0.2.0"}, + {Name: "github.com/spf13/afero", Version: "1.2.2"}, + {Name: "github.com/spf13/cast", Version: "1.3.0"}, + {Name: "github.com/spf13/cobra", Version: "0.0.5"}, + {Name: "github.com/spf13/jwalterweatherman", Version: "1.0.0"}, + {Name: "github.com/spf13/pflag", Version: "1.0.5"}, + {Name: "github.com/spf13/viper", Version: "1.3.2"}, + {Name: "github.com/stretchr/objx", Version: "0.3.0"}, + {Name: "github.com/stretchr/testify", Version: "1.6.1"}, + {Name: "github.com/testcontainers/testcontainers-go", Version: "0.3.1"}, + {Name: "github.com/tmc/grpc-websocket-proxy", Version: "0.0.0-20170815181823-89b8d40f7ca8"}, + {Name: "github.com/twitchtv/twirp", Version: "5.10.1+incompatible"}, + {Name: "github.com/ugorji/go", Version: "1.1.7"}, + {Name: "github.com/ugorji/go/codec", Version: "1.1.7"}, + {Name: "github.com/urfave/cli", Version: "1.22.5"}, + {Name: "github.com/urfave/cli/v2", Version: "2.3.0"}, + {Name: "github.com/vdemeester/k8s-pkg-credentialprovider", Version: "1.17.4"}, + {Name: "github.com/vmware/govmomi", Version: "0.20.3"}, + {Name: "github.com/xanzy/ssh-agent", Version: "0.2.1"}, + {Name: "github.com/xiang90/probing", Version: "0.0.0-20190116061207-43a291ad63a2"}, + {Name: "github.com/xordataexchange/crypt", Version: "0.0.3-0.20170626215501-b2862e3d0a77"}, + {Name: "github.com/yashtewari/glob-intersection", Version: "0.0.0-20180916065949-5c77d914dd0b"}, + {Name: "github.com/yuin/goldmark", Version: "1.1.32"}, + {Name: "github.com/yuin/gopher-lua", Version: "0.0.0-20191220021717-ab39c6098bdb"}, + {Name: "go.etcd.io/bbolt", Version: "1.3.5"}, + {Name: "go.etcd.io/etcd", Version: "0.0.0-20191023171146-3cf2f69b5738"}, + {Name: "go.opencensus.io", Version: "0.22.4"}, + {Name: "go.opentelemetry.io/otel", Version: "0.14.0"}, + {Name: "go.uber.org/atomic", Version: "1.5.1"}, + {Name: "go.uber.org/multierr", Version: "1.4.0"}, + {Name: "go.uber.org/tools", Version: "0.0.0-20190618225709-2cfd321de3ee"}, + {Name: "go.uber.org/zap", Version: "1.13.0"}, + {Name: "golang.org/x/crypto", Version: "0.0.0-20201002170205-7f63de1d35b0"}, + {Name: "golang.org/x/exp", Version: "0.0.0-20200224162631-6cc2880d07d6"}, + {Name: "golang.org/x/image", Version: "0.0.0-20190802002840-cff245a6509b"}, + {Name: "golang.org/x/lint", Version: "0.0.0-20200302205851-738671d3881b"}, + {Name: "golang.org/x/mobile", Version: "0.0.0-20190719004257-d2bd2a29d028"}, + {Name: "golang.org/x/mod", Version: "0.3.0"}, + {Name: "golang.org/x/net", Version: "0.0.0-20201006153459-a7d1128ccaa0"}, + {Name: "golang.org/x/oauth2", Version: "0.0.0-20201208152858-08078c50e5b5"}, + {Name: "golang.org/x/sync", Version: "0.0.0-20200625203802-6e8e738ad208"}, + {Name: "golang.org/x/sys", Version: "0.0.0-20201006155630-ac719f4daadf"}, + {Name: "golang.org/x/text", Version: "0.3.3"}, + {Name: "golang.org/x/time", Version: "0.0.0-20191024005414-555d28b269f0"}, + {Name: "golang.org/x/tools", Version: "0.0.0-20200825202427-b303f430e36d"}, + {Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1"}, + {Name: "gonum.org/v1/gonum", Version: "0.0.0-20190331200053-3d26580ed485"}, + {Name: "gonum.org/v1/netlib", Version: "0.0.0-20190331212654-76723241ea4e"}, + {Name: "google.golang.org/api", Version: "0.30.0"}, + {Name: "google.golang.org/appengine", Version: "1.6.6"}, + {Name: "google.golang.org/genproto", Version: "0.0.0-20200825200019-8632dd797987"}, + {Name: "google.golang.org/grpc", Version: "1.31.0"}, + {Name: "google.golang.org/protobuf", Version: "1.25.0"}, + {Name: "gopkg.in/alecthomas/kingpin.v2", Version: "2.2.6"}, + {Name: "gopkg.in/check.v1", Version: "1.0.0-20200902074654-038fdea0a05b"}, + {Name: "gopkg.in/cheggaaa/pb.v1", Version: "1.0.28"}, + {Name: "gopkg.in/errgo.v2", Version: "2.1.0"}, + {Name: "gopkg.in/fsnotify.v1", Version: "1.4.7"}, + {Name: "gopkg.in/gcfg.v1", Version: "1.2.0"}, + {Name: "gopkg.in/go-playground/assert.v1", Version: "1.2.1"}, + {Name: "gopkg.in/go-playground/validator.v9", Version: "9.31.0"}, + {Name: "gopkg.in/inf.v0", Version: "0.9.1"}, + {Name: "gopkg.in/mgo.v2", Version: "2.0.0-20180705113604-9856a29383ce"}, + {Name: "gopkg.in/natefinch/lumberjack.v2", Version: "2.0.0"}, + {Name: "gopkg.in/resty.v1", Version: "1.12.0"}, + {Name: "gopkg.in/square/go-jose.v2", Version: "2.2.2"}, + {Name: "gopkg.in/tomb.v1", Version: "1.0.0-20141024135613-dd632973f1e7"}, + {Name: "gopkg.in/warnings.v0", Version: "0.1.2"}, + {Name: "gopkg.in/yaml.v2", Version: "2.4.0"}, + {Name: "gopkg.in/yaml.v3", Version: "3.0.0-20200615113413-eeeca48fe776"}, + {Name: "gotest.tools", Version: "2.2.0+incompatible"}, + {Name: "honnef.co/go/tools", Version: "0.0.1-2020.1.4"}, + {Name: "k8s.io/api", Version: "0.17.4"}, + {Name: "k8s.io/apimachinery", Version: "0.17.4"}, + {Name: "k8s.io/apiserver", Version: "0.17.4"}, + {Name: "k8s.io/client-go", Version: "0.17.4"}, + {Name: "k8s.io/cloud-provider", Version: "0.17.4"}, + {Name: "k8s.io/code-generator", Version: "0.17.2"}, + {Name: "k8s.io/component-base", Version: "0.17.4"}, + {Name: "k8s.io/csi-translation-lib", Version: "0.17.4"}, + {Name: "k8s.io/gengo", Version: "0.0.0-20190822140433-26a664648505"}, + {Name: "k8s.io/klog", Version: "1.0.0"}, + {Name: "k8s.io/klog/v2", Version: "2.0.0"}, + {Name: "k8s.io/kube-openapi", Version: "0.0.0-20191107075043-30be4d16710a"}, + {Name: "k8s.io/legacy-cloud-providers", Version: "0.17.4"}, + {Name: "k8s.io/utils", Version: "0.0.0-20201110183641-67b214c5f920"}, + {Name: "modernc.org/cc", Version: "1.0.0"}, + {Name: "modernc.org/golex", Version: "1.0.0"}, + {Name: "modernc.org/mathutil", Version: "1.0.0"}, + {Name: "modernc.org/strutil", Version: "1.0.0"}, + {Name: "modernc.org/xc", Version: "1.0.0"}, + {Name: "moul.io/http2curl", Version: "1.0.0"}, + {Name: "rsc.io/binaryregexp", Version: "0.2.0"}, + {Name: "rsc.io/quote/v3", Version: "3.1.0"}, + {Name: "rsc.io/sampler", Version: "1.3.0"}, + {Name: "sigs.k8s.io/structured-merge-diff", Version: "1.0.1-0.20191108220359-b1b620dd3f06"}, + {Name: "sigs.k8s.io/yaml", Version: "1.1.0"}, + } +) diff --git a/pkg/dependency/parser/golang/sum/testdata/gomod_emptyline.sum b/pkg/dependency/parser/golang/sum/testdata/gomod_emptyline.sum new file mode 100644 index 000000000000..a135140b3ec3 --- /dev/null +++ b/pkg/dependency/parser/golang/sum/testdata/gomod_emptyline.sum @@ -0,0 +1,5 @@ +github.com/karrick/godirwalk v1.12.0 h1:nkS4xxsjiZMvVlazd0mFyiwD4BR9f3m6LXGhM2TUx3Y= +github.com/karrick/godirwalk v1.12.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= + diff --git a/pkg/dependency/parser/golang/sum/testdata/gomod_many.sum b/pkg/dependency/parser/golang/sum/testdata/gomod_many.sum new file mode 100644 index 000000000000..f7e224b946b4 --- /dev/null +++ b/pkg/dependency/parser/golang/sum/testdata/gomod_many.sum @@ -0,0 +1,24 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= +github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/dependency/parser/golang/sum/testdata/gomod_normal.sum b/pkg/dependency/parser/golang/sum/testdata/gomod_normal.sum new file mode 100644 index 000000000000..1ff365f579a0 --- /dev/null +++ b/pkg/dependency/parser/golang/sum/testdata/gomod_normal.sum @@ -0,0 +1,2 @@ +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/dependency/parser/golang/sum/testdata/gomod_trivy.sum b/pkg/dependency/parser/golang/sum/testdata/gomod_trivy.sum new file mode 100644 index 000000000000..3bb9ba16c633 --- /dev/null +++ b/pkg/dependency/parser/golang/sum/testdata/gomod_trivy.sum @@ -0,0 +1,869 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleCloudPlatform/docker-credential-gcr v1.5.0/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs= +github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.7/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8= +github.com/aquasecurity/fanal v0.0.0-20210119051230-28c249da7cfd/go.mod h1:kur6SaohYhsjQLzijAdtn+X8rkTtwxawE51WyVCXLKk= +github.com/aquasecurity/go-dep-parser v0.0.0-20201028043324-889d4a92b8e0/go.mod h1:X42mTIRhgPalSm81Om2kD+3ydeunbC8TZtZj1bvgRo8= +github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s= +github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798/go.mod h1:hxbJZtKlO4P8sZ9nztizR6XLoE33O+BkPmuYQ4ACyz0= +github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46/go.mod h1:olhPNdiiAAMiSujemd1O/sc6GcyePr23f/6uGKtthNg= +github.com/aquasecurity/go-version v0.0.0-20201107203531-5e48ac5d022a/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU= +github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU= +github.com/aquasecurity/testdocker v0.0.0-20210106133225-0b17fe083674/go.mod h1:psfu0MVaiTDLpNxCoNsTeILSKY2EICBwv345f3M+Ffs= +github.com/aquasecurity/trivy v0.16.0 h1:lyzqYGQ2TYxcjFwKBw6aU3PwHLvc0+6VTech+0/LnqQ= +github.com/aquasecurity/trivy v0.16.0/go.mod h1:SLXDW/zKJWE7XhMEGGfkFGABNvz6b8BAbNjuXztJlj0= +github.com/aquasecurity/trivy-db v0.0.0-20210105160501-c5bf4e153277/go.mod h1:N7CWA/vjVw78GWAdCJGhFQVqNGEA4e47a6eIWm+C/Bc= +github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2/go.mod h1:6NhOP0CjZJL27bZZcaHECtzWdwDDm2g6yCY0QgXEGQQ= +github.com/araddon/dateparse v0.0.0-20190426192744-0d74ffceef83/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/briandowns/spinner v1.12.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= +github.com/caarlos0/env/v6 v6.0.0/go.mod h1:+wdyOmtjoZIW2GJOc2OYa5NoOFuWD/bIpWqm30NgtRk= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb/v3 v3.0.3/go.mod h1:Pp35CDuiEpHa/ZLGCtBbM6CBwMstv1bJlG884V+73Yc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190506211059-b20a14b54661/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/elazarl/goproxy/ext v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M= +github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y= +github.com/goccy/go-yaml v1.8.2/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v0.0.0-20181025225059-d3de96c4c28e/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-containerregistry v0.0.0-20200331213917-3d03ed9b1ca2/go.mod h1:pD1UFYs7MCAx+ZLShBdttcaOSbyc8F9Na/9IZLNwJeA= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v0.0.0-20181024020800-521ea7b17d02/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= +github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao= +github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0= +github.com/knqyf263/go-rpmdb v0.0.0-20201215100354-a9e3110d8ee1/go.mod h1:RDPNeIkU5NWXtt0OMEoILyxwUC/DyXeRtK295wpqSi0= +github.com/knqyf263/nested v0.0.1/go.mod h1:zwhsIhMkBg90DTOJQvxPkKIypEHPYkgWHs4gybdlUmk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-jsonpointer v0.0.0-20180225143300-37667080efed/go.mod h1:SDJ4hurDYyQ9/7nc+eCYtXqdufgK4Cq9TJlwPklqEYA= +github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2-0.20190607075207-195002e6e56a/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/open-policy-agent/opa v0.21.1/go.mod h1:cZaTfhxsj7QdIiUI0U9aBtOLLTqVNe+XE60+9kZKLHw= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20181025174421-f30f42803563/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/simplereach/timeutils v1.2.0/go.mod h1:VVbQDfN/FHRZa1LSqcwo4kNZ62OOyqLLGQKYB3pB0Q8= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sosedoff/gitkit v0.2.0/go.mod h1:A+o6ZazfVJwetlcHz3ah6th66XcBdsyzLo+aBt/AsK4= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v0.0.0-20181024212040-082b515c9490/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/testcontainers/testcontainers-go v0.3.1/go.mod h1:br7bkzIukhPSIjy07Ma3OuXjjFvl2jm7CDU0LQNsqLw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/twitchtv/twirp v5.10.1+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= +github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181023182221-1baf3a9d7d67/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201006155630-ac719f4daadf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA= +k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= +k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I= +k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc= +k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KNK9U= +k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE= +k8s.io/csi-translation-lib v0.17.4/go.mod h1:CsxmjwxEI0tTNMzffIAcgR9lX4wOh6AKHdxQrT7L0oo= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/pkg/dependency/parser/gradle/lockfile/parse.go b/pkg/dependency/parser/gradle/lockfile/parse.go new file mode 100644 index 000000000000..6d466570d2ff --- /dev/null +++ b/pkg/dependency/parser/gradle/lockfile/parse.go @@ -0,0 +1,57 @@ +package lockfile + +import ( + "bufio" + "strings" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var libs []types.Library + scanner := bufio.NewScanner(r) + var lineNum int + for scanner.Scan() { + lineNum++ + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, "#") { // skip comments + continue + } + + // dependency format: group:artifact:version=classPaths + dep := strings.Split(line, ":") + if len(dep) != 3 { // skip the last line with lists of empty configurations + continue + } + + name := strings.Join(dep[:2], ":") + version := strings.Split(dep[2], "=")[0] // remove classPaths + libs = append(libs, types.Library{ + ID: dependency.ID(ftypes.Gradle, name, version), + Name: name, + Version: version, + Locations: []types.Location{ + { + StartLine: lineNum, + EndLine: lineNum, + }, + }, + // There is no reliable way to determine direct dependencies (even using other files). + // Therefore, we mark all dependencies as Indirect. + // This is necessary to try to guess direct dependencies and build a dependency tree. + Indirect: true, + }) + + } + return utils.UniqueLibraries(libs), nil, nil +} diff --git a/pkg/dependency/parser/gradle/lockfile/parse_test.go b/pkg/dependency/parser/gradle/lockfile/parse_test.go new file mode 100644 index 000000000000..49cc7fe1c3a3 --- /dev/null +++ b/pkg/dependency/parser/gradle/lockfile/parse_test.go @@ -0,0 +1,89 @@ +package lockfile + +import ( + "os" + "sort" + "strings" + "testing" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/stretchr/testify/assert" +) + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + inputFile string + want []types.Library + }{ + { + name: "happy path", + inputFile: "testdata/happy.lockfile", + want: []types.Library{ + { + ID: "cglib:cglib-nodep:2.1.2", + Name: "cglib:cglib-nodep", + Version: "2.1.2", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + { + ID: "org.springframework:spring-asm:3.1.3.RELEASE", + Name: "org.springframework:spring-asm", + Version: "3.1.3.RELEASE", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + ID: "org.springframework:spring-beans:5.0.5.RELEASE", + Name: "org.springframework:spring-beans", + Version: "5.0.5.RELEASE", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 6, + EndLine: 6, + }, + }, + }, + }, + }, + { + name: "empty", + inputFile: "testdata/empty.lockfile", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parser := NewParser() + f, err := os.Open(tt.inputFile) + assert.NoError(t, err) + + libs, _, _ := parser.Parse(f) + sortLibs(libs) + assert.Equal(t, tt.want, libs) + }) + } +} + +func sortLibs(libs []types.Library) { + sort.Slice(libs, func(i, j int) bool { + ret := strings.Compare(libs[i].Name, libs[j].Name) + if ret == 0 { + return libs[i].Version < libs[j].Version + } + return ret < 0 + }) +} diff --git a/pkg/dependency/parser/gradle/lockfile/testdata/empty.lockfile b/pkg/dependency/parser/gradle/lockfile/testdata/empty.lockfile new file mode 100644 index 000000000000..77cdc75caaca --- /dev/null +++ b/pkg/dependency/parser/gradle/lockfile/testdata/empty.lockfile @@ -0,0 +1,4 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +empty=incomingCatalog \ No newline at end of file diff --git a/pkg/dependency/parser/gradle/lockfile/testdata/happy.lockfile b/pkg/dependency/parser/gradle/lockfile/testdata/happy.lockfile new file mode 100644 index 000000000000..d2b164ee42c5 --- /dev/null +++ b/pkg/dependency/parser/gradle/lockfile/testdata/happy.lockfile @@ -0,0 +1,8 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +cglib:cglib-nodep:2.1.2=testRuntimeClasspath,classpath + org.springframework:spring-asm:3.1.3.RELEASE=classpath +org.springframework:spring-beans:5.0.5.RELEASE=compileClasspath, runtimeClasspath + # io.grpc:grpc-api:1.21.1=classpath +empty= \ No newline at end of file diff --git a/pkg/dependency/parser/hex/mix/parse.go b/pkg/dependency/parser/hex/mix/parse.go new file mode 100644 index 000000000000..edc43fd284c6 --- /dev/null +++ b/pkg/dependency/parser/hex/mix/parse.go @@ -0,0 +1,67 @@ +package mix + +import ( + "bufio" + "strings" + "unicode" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +// Parser is a parser for mix.lock +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var libs []types.Library + scanner := bufio.NewScanner(r) + var lineNumber int // It is used to save dependency location + for scanner.Scan() { + lineNumber++ + line := strings.TrimSpace(scanner.Text()) + name, body, ok := strings.Cut(line, ":") + if !ok { + // skip 1st and last lines + continue + } + name = strings.Trim(name, `"`) + + // dependency format: + // "": {<:hex|:git>, :, "", "", [:mix], [], hexpm", ""}, + ss := strings.FieldsFunc(body, func(r rune) bool { + return unicode.IsSpace(r) || r == ',' + }) + if len(ss) < 8 { // In the case where array is empty: s == 8, in other cases s > 8 + // git repository doesn't have dependency version + // skip these dependencies + if !strings.Contains(ss[0], ":git") { + log.Logger.Warnf("Cannot parse dependency: %s", line) + } else { + log.Logger.Debugf("Skip git dependencies: %s", name) + } + continue + } + version := strings.Trim(ss[2], `"`) + libs = append(libs, types.Library{ + ID: dependency.ID(ftypes.Hex, name, version), + Name: name, + Version: version, + Locations: []types.Location{ + { + StartLine: lineNumber, + EndLine: lineNumber, + }, + }, + }) + + } + return utils.UniqueLibraries(libs), nil, nil +} diff --git a/pkg/dependency/parser/hex/mix/parse_test.go b/pkg/dependency/parser/hex/mix/parse_test.go new file mode 100644 index 000000000000..ab3d929dd96f --- /dev/null +++ b/pkg/dependency/parser/hex/mix/parse_test.go @@ -0,0 +1,77 @@ +package mix + +import ( + "os" + "sort" + "strings" + "testing" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/stretchr/testify/assert" +) + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + inputFile string + want []types.Library + }{ + { + name: "happy path", + inputFile: "testdata/happy.mix.lock", + want: []types.Library{ + { + ID: "bunt@0.2.0", + Name: "bunt", + Version: "0.2.0", + Locations: []types.Location{{StartLine: 2, EndLine: 2}}, + }, + { + ID: "credo@1.6.6", + Name: "credo", + Version: "1.6.6", + Locations: []types.Location{{StartLine: 3, EndLine: 3}}, + }, + { + ID: "file_system@0.2.10", + Name: "file_system", + Version: "0.2.10", + Locations: []types.Location{{StartLine: 4, EndLine: 4}}, + }, + { + ID: "jason@1.3.0", + Name: "jason", + Version: "1.3.0", + Locations: []types.Location{{StartLine: 5, EndLine: 5}}, + }, + }, + }, + { + name: "empty", + inputFile: "testdata/empty.mix.lock", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parser := NewParser() + f, err := os.Open(tt.inputFile) + assert.NoError(t, err) + + libs, _, _ := parser.Parse(f) + sortLibs(libs) + assert.Equal(t, tt.want, libs) + }) + } +} + +func sortLibs(libs []types.Library) { + sort.Slice(libs, func(i, j int) bool { + ret := strings.Compare(libs[i].Name, libs[j].Name) + if ret == 0 { + return libs[i].Version < libs[j].Version + } + return ret < 0 + }) +} diff --git a/pkg/dependency/parser/hex/mix/testdata/empty.mix.lock b/pkg/dependency/parser/hex/mix/testdata/empty.mix.lock new file mode 100644 index 000000000000..15081287722e --- /dev/null +++ b/pkg/dependency/parser/hex/mix/testdata/empty.mix.lock @@ -0,0 +1,2 @@ +%{ +} \ No newline at end of file diff --git a/pkg/dependency/parser/hex/mix/testdata/happy.mix.lock b/pkg/dependency/parser/hex/mix/testdata/happy.mix.lock new file mode 100644 index 000000000000..7ab92fc60614 --- /dev/null +++ b/pkg/dependency/parser/hex/mix/testdata/happy.mix.lock @@ -0,0 +1,7 @@ +%{ + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "credo": {:hex, :credo, "1.6.6", "f51f8d45db1af3b2e2f7bee3e6d3c871737bda4a91bff00c5eec276517d1a19c", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "625520ce0984ee0f9f1f198165cd46fa73c1e59a17ebc520038b8fce056a5bdc"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, + "esaml": {:git, "https://github.com/firezone/esaml.git", "4294a3ac5262582144e117c10a1537287b6c1fe8", []}, +} \ No newline at end of file diff --git a/pkg/dependency/parser/java/jar/parse.go b/pkg/dependency/parser/java/jar/parse.go new file mode 100644 index 000000000000..d5f1f6df0a4d --- /dev/null +++ b/pkg/dependency/parser/java/jar/parse.go @@ -0,0 +1,444 @@ +package jar + +import ( + "archive/zip" + "bufio" + "crypto/sha1" // nolint:gosec + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "regexp" + "strings" + + "github.com/samber/lo" + "go.uber.org/zap" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +var ( + jarFileRegEx = regexp.MustCompile(`^([a-zA-Z0-9\._-]*[^-*])-(\d\S*(?:-SNAPSHOT)?).jar$`) +) + +type Client interface { + Exists(groupID, artifactID string) (bool, error) + SearchBySHA1(sha1 string) (Properties, error) + SearchByArtifactID(artifactID, version string) (string, error) +} + +type Parser struct { + rootFilePath string + offline bool + size int64 + + client Client +} + +type Option func(*Parser) + +func WithFilePath(filePath string) Option { + return func(p *Parser) { + p.rootFilePath = filePath + } +} + +func WithOffline(offline bool) Option { + return func(p *Parser) { + p.offline = offline + } +} + +func WithSize(size int64) Option { + return func(p *Parser) { + p.size = size + } +} + +func NewParser(c Client, opts ...Option) types.Parser { + p := &Parser{ + client: c, + } + + for _, opt := range opts { + opt(p) + } + + return p +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + libs, deps, err := p.parseArtifact(p.rootFilePath, p.size, r) + if err != nil { + return nil, nil, xerrors.Errorf("unable to parse %s: %w", p.rootFilePath, err) + } + return removeLibraryDuplicates(libs), deps, nil +} + +func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + log.Logger.Debugw("Parsing Java artifacts...", zap.String("file", filePath)) + + // Try to extract artifactId and version from the file name + // e.g. spring-core-5.3.4-SNAPSHOT.jar => sprint-core, 5.3.4-SNAPSHOT + fileName := filepath.Base(filePath) + fileProps := parseFileName(filePath) + + libs, m, foundPomProps, err := p.traverseZip(filePath, size, r, fileProps) + if err != nil { + return nil, nil, xerrors.Errorf("zip error: %w", err) + } + + // If pom.properties is found, it should be preferred than MANIFEST.MF. + if foundPomProps { + return libs, nil, nil + } + + manifestProps := m.properties(filePath) + if p.offline { + // In offline mode, we will not check if the artifact information is correct. + if !manifestProps.Valid() { + log.Logger.Debugw("Unable to identify POM in offline mode", zap.String("file", fileName)) + return libs, nil, nil + } + return append(libs, manifestProps.Library()), nil, nil + } + + if manifestProps.Valid() { + // Even if MANIFEST.MF is found, the groupId and artifactId might not be valid. + // We have to make sure that the artifact exists actually. + if ok, _ := p.client.Exists(manifestProps.GroupID, manifestProps.ArtifactID); ok { + // If groupId and artifactId are valid, they will be returned. + return append(libs, manifestProps.Library()), nil, nil + } + } + + // If groupId and artifactId are not found, call Maven Central's search API with SHA-1 digest. + props, err := p.searchBySHA1(r, filePath) + if err == nil { + return append(libs, props.Library()), nil, nil + } else if !errors.Is(err, ArtifactNotFoundErr) { + return nil, nil, xerrors.Errorf("failed to search by SHA1: %w", err) + } + + log.Logger.Debugw("No such POM in the central repositories", zap.String("file", fileName)) + + // Return when artifactId or version from the file name are empty + if fileProps.ArtifactID == "" || fileProps.Version == "" { + return libs, nil, nil + } + + // Try to search groupId by artifactId via sonatype API + // When some artifacts have the same groupIds, it might result in false detection. + fileProps.GroupID, err = p.client.SearchByArtifactID(fileProps.ArtifactID, fileProps.Version) + if err == nil { + log.Logger.Debugw("POM was determined in a heuristic way", zap.String("file", fileName), + zap.String("artifact", fileProps.String())) + libs = append(libs, fileProps.Library()) + } else if !errors.Is(err, ArtifactNotFoundErr) { + return nil, nil, xerrors.Errorf("failed to search by artifact id: %w", err) + } + + return libs, nil, nil +} + +func (p *Parser) traverseZip(filePath string, size int64, r xio.ReadSeekerAt, fileProps Properties) ( + []types.Library, manifest, bool, error) { + var libs []types.Library + var m manifest + var foundPomProps bool + + zr, err := zip.NewReader(r, size) + if err != nil { + return nil, manifest{}, false, xerrors.Errorf("zip error: %w", err) + } + + for _, fileInJar := range zr.File { + switch { + case filepath.Base(fileInJar.Name) == "pom.properties": + props, err := parsePomProperties(fileInJar, filePath) + if err != nil { + return nil, manifest{}, false, xerrors.Errorf("failed to parse %s: %w", fileInJar.Name, err) + } + // Validation of props to avoid getting libs with empty Name/Version + if props.Valid() { + libs = append(libs, props.Library()) + + // Check if the pom.properties is for the original JAR/WAR/EAR + if fileProps.ArtifactID == props.ArtifactID && fileProps.Version == props.Version { + foundPomProps = true + } + } + case filepath.Base(fileInJar.Name) == "MANIFEST.MF": + m, err = parseManifest(fileInJar) + if err != nil { + return nil, manifest{}, false, xerrors.Errorf("failed to parse MANIFEST.MF: %w", err) + } + case isArtifact(fileInJar.Name): + innerLibs, _, err := p.parseInnerJar(fileInJar, filePath) // TODO process inner deps + if err != nil { + log.Logger.Debugf("Failed to parse %s: %s", fileInJar.Name, err) + continue + } + libs = append(libs, innerLibs...) + } + } + return libs, m, foundPomProps, nil +} + +func (p *Parser) parseInnerJar(zf *zip.File, rootPath string) ([]types.Library, []types.Dependency, error) { + fr, err := zf.Open() + if err != nil { + return nil, nil, xerrors.Errorf("unable to open %s: %w", zf.Name, err) + } + + f, err := os.CreateTemp("", "inner") + if err != nil { + return nil, nil, xerrors.Errorf("unable to create a temp file: %w", err) + } + defer func() { + _ = f.Close() + _ = os.Remove(f.Name()) + }() + + // Copy the file content to the temp file + if n, err := io.CopyN(f, fr, int64(zf.UncompressedSize64)); err != nil { + return nil, nil, xerrors.Errorf("file copy error: %w", err) + } else if n != int64(zf.UncompressedSize64) { + return nil, nil, xerrors.Errorf("file copy size error: %w", err) + } + + // build full path to inner jar + fullPath := path.Join(rootPath, zf.Name) // nolint:gosec + if !strings.HasPrefix(fullPath, path.Clean(rootPath)) { + return nil, nil, nil // zip slip + } + + // Parse jar/war/ear recursively + innerLibs, innerDeps, err := p.parseArtifact(fullPath, int64(zf.UncompressedSize64), f) + if err != nil { + return nil, nil, xerrors.Errorf("failed to parse %s: %w", zf.Name, err) + } + + return innerLibs, innerDeps, nil +} + +func (p *Parser) searchBySHA1(r io.ReadSeeker, filePath string) (Properties, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return Properties{}, xerrors.Errorf("file seek error: %w", err) + } + + h := sha1.New() // nolint:gosec + if _, err := io.Copy(h, r); err != nil { + return Properties{}, xerrors.Errorf("unable to calculate SHA-1: %w", err) + } + s := hex.EncodeToString(h.Sum(nil)) + prop, err := p.client.SearchBySHA1(s) + if err != nil { + return Properties{}, err + } + prop.FilePath = filePath + return prop, nil +} + +func isArtifact(name string) bool { + ext := filepath.Ext(name) + if ext == ".jar" || ext == ".ear" || ext == ".war" { + return true + } + return false +} + +func parseFileName(filePath string) Properties { + fileName := filepath.Base(filePath) + packageVersion := jarFileRegEx.FindStringSubmatch(fileName) + if len(packageVersion) != 3 { + return Properties{} + } + + return Properties{ + ArtifactID: packageVersion[1], + Version: packageVersion[2], + FilePath: filePath, + } +} + +func parsePomProperties(f *zip.File, filePath string) (Properties, error) { + file, err := f.Open() + if err != nil { + return Properties{}, xerrors.Errorf("unable to open pom.properties: %w", err) + } + defer file.Close() + + p := Properties{ + FilePath: filePath, + } + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + switch { + case strings.HasPrefix(line, "groupId="): + p.GroupID = strings.TrimPrefix(line, "groupId=") + case strings.HasPrefix(line, "artifactId="): + p.ArtifactID = strings.TrimPrefix(line, "artifactId=") + case strings.HasPrefix(line, "version="): + p.Version = strings.TrimPrefix(line, "version=") + } + } + + if err = scanner.Err(); err != nil { + return Properties{}, xerrors.Errorf("scan error: %w", err) + } + return p, nil +} + +type manifest struct { + implementationVersion string + implementationTitle string + implementationVendor string + implementationVendorId string + specificationTitle string + specificationVersion string + specificationVendor string + bundleName string + bundleVersion string + bundleSymbolicName string +} + +func parseManifest(f *zip.File) (manifest, error) { + file, err := f.Open() + if err != nil { + return manifest{}, xerrors.Errorf("unable to open MANIFEST.MF: %w", err) + } + defer file.Close() + + var m manifest + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + + // Skip variables. e.g. Bundle-Name: %bundleName + ss := strings.Fields(line) + if len(ss) <= 1 || (len(ss) > 1 && strings.HasPrefix(ss[1], "%")) { + continue + } + + // It is not determined which fields are present in each application. + // In some cases, none of them are included, in which case they cannot be detected. + switch { + case strings.HasPrefix(line, "Implementation-Version:"): + m.implementationVersion = strings.TrimPrefix(line, "Implementation-Version:") + case strings.HasPrefix(line, "Implementation-Title:"): + m.implementationTitle = strings.TrimPrefix(line, "Implementation-Title:") + case strings.HasPrefix(line, "Implementation-Vendor:"): + m.implementationVendor = strings.TrimPrefix(line, "Implementation-Vendor:") + case strings.HasPrefix(line, "Implementation-Vendor-Id:"): + m.implementationVendorId = strings.TrimPrefix(line, "Implementation-Vendor-Id:") + case strings.HasPrefix(line, "Specification-Version:"): + m.specificationVersion = strings.TrimPrefix(line, "Specification-Version:") + case strings.HasPrefix(line, "Specification-Title:"): + m.specificationTitle = strings.TrimPrefix(line, "Specification-Title:") + case strings.HasPrefix(line, "Specification-Vendor:"): + m.specificationVendor = strings.TrimPrefix(line, "Specification-Vendor:") + case strings.HasPrefix(line, "Bundle-Version:"): + m.bundleVersion = strings.TrimPrefix(line, "Bundle-Version:") + case strings.HasPrefix(line, "Bundle-Name:"): + m.bundleName = strings.TrimPrefix(line, "Bundle-Name:") + case strings.HasPrefix(line, "Bundle-SymbolicName:"): + m.bundleSymbolicName = strings.TrimPrefix(line, "Bundle-SymbolicName:") + } + } + + if err = scanner.Err(); err != nil { + return manifest{}, xerrors.Errorf("scan error: %w", err) + } + return m, nil +} + +func (m manifest) properties(filePath string) Properties { + groupID, err := m.determineGroupID() + if err != nil { + return Properties{} + } + + artifactID, err := m.determineArtifactID() + if err != nil { + return Properties{} + } + + version, err := m.determineVersion() + if err != nil { + return Properties{} + } + + return Properties{ + GroupID: groupID, + ArtifactID: artifactID, + Version: version, + FilePath: filePath, + } +} + +func (m manifest) determineGroupID() (string, error) { + var groupID string + switch { + case m.implementationVendorId != "": + groupID = m.implementationVendorId + case m.bundleSymbolicName != "": + groupID = m.bundleSymbolicName + + // e.g. "com.fasterxml.jackson.core.jackson-databind" => "com.fasterxml.jackson.core" + idx := strings.LastIndex(m.bundleSymbolicName, ".") + if idx > 0 { + groupID = m.bundleSymbolicName[:idx] + } + case m.implementationVendor != "": + groupID = m.implementationVendor + case m.specificationVendor != "": + groupID = m.specificationVendor + default: + return "", xerrors.New("no groupID found") + } + return strings.TrimSpace(groupID), nil +} + +func (m manifest) determineArtifactID() (string, error) { + var artifactID string + switch { + case m.implementationTitle != "": + artifactID = m.implementationTitle + case m.specificationTitle != "": + artifactID = m.specificationTitle + case m.bundleName != "": + artifactID = m.bundleName + default: + return "", xerrors.New("no artifactID found") + } + return strings.TrimSpace(artifactID), nil +} + +func (m manifest) determineVersion() (string, error) { + var version string + switch { + case m.implementationVersion != "": + version = m.implementationVersion + case m.specificationVersion != "": + version = m.specificationVersion + case m.bundleVersion != "": + version = m.bundleVersion + default: + return "", xerrors.New("no version found") + } + return strings.TrimSpace(version), nil +} + +func removeLibraryDuplicates(libs []types.Library) []types.Library { + return lo.UniqBy(libs, func(lib types.Library) string { + return fmt.Sprintf("%s::%s::%s", lib.Name, lib.Version, lib.FilePath) + }) +} diff --git a/pkg/dependency/parser/java/jar/parse_test.go b/pkg/dependency/parser/java/jar/parse_test.go new file mode 100644 index 000000000000..88125a5a34b1 --- /dev/null +++ b/pkg/dependency/parser/java/jar/parse_test.go @@ -0,0 +1,332 @@ +package jar_test + +import ( + "encoding/json" + "github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar/sonatype" + "net/http" + "net/http/httptest" + "os" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +var ( + // cd testdata/testimage/maven && docker build -t test . + // docker run --rm --name test -it test bash + // mvn dependency:list + // mvn dependency:tree -Dscope=compile -Dscope=runtime | awk '/:tree/,/BUILD SUCCESS/' | awk 'NR > 1 { print }' | head -n -2 | awk '{print $NF}' | awk -F":" '{printf("{\""$1":"$2"\", \""$4 "\", \"\"},\n")}' + // paths filled in manually + wantMaven = []types.Library{ + { + Name: "com.example:web-app", + Version: "1.0-SNAPSHOT", + FilePath: "testdata/maven.war", + }, + { + Name: "com.fasterxml.jackson.core:jackson-databind", + Version: "2.9.10.6", + FilePath: "testdata/maven.war/WEB-INF/lib/jackson-databind-2.9.10.6.jar", + }, + { + Name: "com.fasterxml.jackson.core:jackson-annotations", + Version: "2.9.10", + FilePath: "testdata/maven.war/WEB-INF/lib/jackson-annotations-2.9.10.jar", + }, + { + Name: "com.fasterxml.jackson.core:jackson-core", + Version: "2.9.10", + FilePath: "testdata/maven.war/WEB-INF/lib/jackson-core-2.9.10.jar", + }, + { + Name: "com.cronutils:cron-utils", + Version: "9.1.2", + FilePath: "testdata/maven.war/WEB-INF/lib/cron-utils-9.1.2.jar", + }, + { + Name: "org.slf4j:slf4j-api", + Version: "1.7.30", + FilePath: "testdata/maven.war/WEB-INF/lib/slf4j-api-1.7.30.jar", + }, + { + Name: "org.glassfish:javax.el", + Version: "3.0.0", + FilePath: "testdata/maven.war/WEB-INF/lib/javax.el-3.0.0.jar", + }, + { + Name: "org.apache.commons:commons-lang3", + Version: "3.11", + FilePath: "testdata/maven.war/WEB-INF/lib/commons-lang3-3.11.jar", + }, + } + + // cd testdata/testimage/gradle && docker build -t test . + // docker run --rm --name test -it test bash + // gradle app:dependencies --configuration implementation | grep "[+\]---" | cut -d" " -f2 | awk -F":" '{printf("{\""$1":"$2"\", \""$3"\", \"\"},\n")}' + // paths filled in manually + wantGradle = []types.Library{ + { + Name: "commons-dbcp:commons-dbcp", + Version: "1.4", + FilePath: "testdata/gradle.war/WEB-INF/lib/commons-dbcp-1.4.jar", + }, + { + Name: "commons-pool:commons-pool", + Version: "1.6", + FilePath: "testdata/gradle.war/WEB-INF/lib/commons-pool-1.6.jar", + }, + { + Name: "log4j:log4j", + Version: "1.2.17", + FilePath: "testdata/gradle.war/WEB-INF/lib/log4j-1.2.17.jar", + }, + { + Name: "org.apache.commons:commons-compress", + Version: "1.19", + FilePath: "testdata/gradle.war/WEB-INF/lib/commons-compress-1.19.jar", + }, + } + + // manually created + wantSHA1 = []types.Library{ + { + Name: "org.springframework:spring-core", + Version: "5.3.3", + FilePath: "testdata/test.jar", + }, + } + + // offline + wantOffline = []types.Library{ + { + Name: "org.springframework:Spring Framework", + Version: "2.5.6.SEC03", + FilePath: "testdata/test.jar", + }, + } + + // manually created + wantHeuristic = []types.Library{ + { + Name: "com.example:heuristic", + Version: "1.0.0-SNAPSHOT", + FilePath: "testdata/heuristic-1.0.0-SNAPSHOT.jar", + }, + } + + // manually created + wantFatjar = []types.Library{ + { + Name: "com.google.guava:failureaccess", + Version: "1.0.1", + FilePath: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar", + }, + { + Name: "com.google.guava:guava", + Version: "29.0-jre", + FilePath: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar", + }, + { + Name: "com.google.guava:listenablefuture", + Version: "9999.0-empty-to-avoid-conflict-with-guava", + FilePath: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar", + }, + { + Name: "com.google.j2objc:j2objc-annotations", + Version: "1.3", + FilePath: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar", + }, + { + Name: "org.apache.hadoop.thirdparty:hadoop-shaded-guava", + Version: "1.1.0-SNAPSHOT", + FilePath: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar", + }, + } + + // manually created + wantNestedJar = []types.Library{ + { + Name: "test:nested", + Version: "0.0.1", + FilePath: "testdata/nested.jar", + }, + { + Name: "test:nested2", + Version: "0.0.2", + FilePath: "testdata/nested.jar/META-INF/jars/nested2.jar", + }, + { + Name: "test:nested3", + Version: "0.0.3", + FilePath: "testdata/nested.jar/META-INF/jars/nested2.jar/META-INF/jars/nested3.jar", + }, + } + + // manually created + wantDuplicatesJar = []types.Library{ + { + Name: "io.quarkus.gizmo:gizmo", + Version: "1.1.1.Final", + FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar", + }, + { + Name: "log4j:log4j", + Version: "1.2.16", + FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar/jars/log4j-1.2.16.jar", + }, + { + Name: "log4j:log4j", + Version: "1.2.17", + FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar/jars/log4j-1.2.17.jar", + }, + } +) + +type apiResponse struct { + Response response `json:"response"` +} + +type response struct { + NumFound int `json:"numFound"` + Docs []doc `json:"docs"` +} + +type doc struct { + ID string `json:"id"` + GroupID string `json:"g"` + ArtifactID string `json:"a"` + Version string `json:"v"` + P string `json:"p"` + VersionCount int `json:versionCount` +} + +func TestParse(t *testing.T) { + vectors := []struct { + name string + file string // Test input file + offline bool + want []types.Library + }{ + { + name: "maven", + file: "testdata/maven.war", + want: wantMaven, + }, + { + name: "gradle", + file: "testdata/gradle.war", + want: wantGradle, + }, + { + name: "nested jars", + file: "testdata/nested.jar", + want: wantNestedJar, + }, + { + name: "sha1 search", + file: "testdata/test.jar", + want: wantSHA1, + }, + { + name: "offline", + file: "testdata/test.jar", + offline: true, + want: wantOffline, + }, + { + name: "artifactId search", + file: "testdata/heuristic-1.0.0-SNAPSHOT.jar", + want: wantHeuristic, + }, + { + name: "fat jar", + file: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar", + want: wantFatjar, + }, + { + name: "duplicate libraries", + file: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar", + want: wantDuplicatesJar, + }, + } + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + res := apiResponse{ + Response: response{ + NumFound: 1, + }, + } + + switch { + case strings.Contains(r.URL.Query().Get("q"), "springframework"): + res.Response.NumFound = 0 + case strings.Contains(r.URL.Query().Get("q"), "c666f5bc47eb64ed3bbd13505a26f58be71f33f0"): + res.Response.Docs = []doc{ + { + ID: "org.springframework.spring-core", + GroupID: "org.springframework", + ArtifactID: "spring-core", + Version: "5.3.3", + }, + } + case strings.Contains(r.URL.Query().Get("q"), "Gizmo"): + res.Response.NumFound = 0 + case strings.Contains(r.URL.Query().Get("q"), "85d30c06026afd9f5be26da3194d4698c447a904"): + res.Response.Docs = []doc{ + { + ID: "io.quarkus.gizmo.gizmo", + GroupID: "io.quarkus.gizmo", + ArtifactID: "gizmo", + Version: "1.1.1.Final", + }, + } + case strings.Contains(r.URL.Query().Get("q"), "heuristic"): + res.Response.Docs = []doc{ + { + ID: "org.springframework.heuristic", + GroupID: "org.springframework", + ArtifactID: "heuristic", + VersionCount: 10, + }, + { + ID: "com.example.heuristic", + GroupID: "com.example", + ArtifactID: "heuristic", + VersionCount: 100, + }, + } + } + _ = json.NewEncoder(w).Encode(res) + })) + + for _, v := range vectors { + t.Run(v.name, func(t *testing.T) { + f, err := os.Open(v.file) + require.NoError(t, err) + + stat, err := f.Stat() + require.NoError(t, err) + + c := sonatype.New(sonatype.WithURL(ts.URL), sonatype.WithHTTPClient(ts.Client())) + p := jar.NewParser(c, jar.WithFilePath(v.file), jar.WithOffline(v.offline), jar.WithSize(stat.Size())) + + got, _, err := p.Parse(f) + require.NoError(t, err) + + sort.Slice(got, func(i, j int) bool { + return got[i].Name < got[j].Name + }) + sort.Slice(v.want, func(i, j int) bool { + return v.want[i].Name < v.want[j].Name + }) + + assert.Equal(t, v.want, got) + }) + } +} diff --git a/pkg/dependency/parser/java/jar/sonatype/log.go b/pkg/dependency/parser/java/jar/sonatype/log.go new file mode 100644 index 000000000000..9d4ef0b7db87 --- /dev/null +++ b/pkg/dependency/parser/java/jar/sonatype/log.go @@ -0,0 +1,32 @@ +package sonatype + +import "github.com/aquasecurity/trivy/pkg/log" + +// logger implements LeveledLogger +// https://github.com/hashicorp/go-retryablehttp/blob/991b9d0a42d13014e3689dd49a94c02be01f4237/client.go#L285-L290 +type logger struct{} + +func (logger) Error(msg string, keysAndValues ...interface{}) { + // Use Debugw to suppress errors on failure + if msg == "request failed" { + log.Logger.Debugw(msg, keysAndValues...) + return + } + log.Logger.Errorw(msg, keysAndValues) +} + +func (logger) Info(msg string, keysAndValues ...interface{}) { + log.Logger.Infow(msg, keysAndValues...) +} + +func (logger) Debug(msg string, keysAndValues ...interface{}) { + // This message is displayed too much + if msg == "performing request" { + return + } + log.Logger.Debugw(msg, keysAndValues...) +} + +func (logger) Warn(msg string, keysAndValues ...interface{}) { + log.Logger.Warnw(msg, keysAndValues...) +} diff --git a/pkg/dependency/parser/java/jar/sonatype/sonatype.go b/pkg/dependency/parser/java/jar/sonatype/sonatype.go new file mode 100644 index 000000000000..63cae20b670f --- /dev/null +++ b/pkg/dependency/parser/java/jar/sonatype/sonatype.go @@ -0,0 +1,196 @@ +package sonatype + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "sort" + "time" + + "github.com/hashicorp/go-retryablehttp" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar" +) + +const ( + baseURL = "https://search.maven.org/solrsearch/select" + idQuery = `g:"%s" AND a:"%s"` + artifactIdQuery = `a:"%s" AND p:"jar"` + sha1Query = `1:"%s"` +) + +type apiResponse struct { + Response struct { + NumFound int `json:"numFound"` + Docs []struct { + ID string `json:"id"` + GroupID string `json:"g"` + ArtifactID string `json:"a"` + Version string `json:"v"` + P string `json:"p"` + VersionCount int `json:"versionCount"` + } `json:"docs"` + } `json:"response"` +} + +type Sonatype struct { + baseURL string + httpClient *http.Client +} + +type Option func(*Sonatype) + +func WithURL(url string) Option { + return func(p *Sonatype) { + p.baseURL = url + } +} + +func WithHTTPClient(client *http.Client) Option { + return func(p *Sonatype) { + p.httpClient = client + } +} + +func New(opts ...Option) Sonatype { + // for HTTP retry + retryClient := retryablehttp.NewClient() + retryClient.Logger = logger{} + retryClient.RetryWaitMin = 20 * time.Second + retryClient.RetryWaitMax = 5 * time.Minute + retryClient.RetryMax = 5 + client := retryClient.StandardClient() + + // attempt to read the maven central api url from os environment, if it's + // not set use the default + mavenURL, ok := os.LookupEnv("MAVEN_CENTRAL_URL") + if !ok { + mavenURL = baseURL + } + + s := Sonatype{ + baseURL: mavenURL, + httpClient: client, + } + + for _, opt := range opts { + opt(&s) + } + + return s +} + +func (s Sonatype) Exists(groupID, artifactID string) (bool, error) { + req, err := http.NewRequest(http.MethodGet, s.baseURL, http.NoBody) + if err != nil { + return false, xerrors.Errorf("unable to initialize HTTP client: %w", err) + } + + q := req.URL.Query() + q.Set("q", fmt.Sprintf(idQuery, groupID, artifactID)) + q.Set("rows", "1") + req.URL.RawQuery = q.Encode() + + resp, err := s.httpClient.Do(req) + if err != nil { + return false, xerrors.Errorf("http error: %w", err) + } + defer resp.Body.Close() + + var res apiResponse + if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { + return false, xerrors.Errorf("json decode error: %w", err) + } + return res.Response.NumFound > 0, nil +} + +func (s Sonatype) SearchBySHA1(sha1 string) (jar.Properties, error) { + + req, err := http.NewRequest(http.MethodGet, s.baseURL, http.NoBody) + if err != nil { + return jar.Properties{}, xerrors.Errorf("unable to initialize HTTP client: %w", err) + } + + q := req.URL.Query() + q.Set("q", fmt.Sprintf(sha1Query, sha1)) + q.Set("rows", "1") + q.Set("wt", "json") + req.URL.RawQuery = q.Encode() + + resp, err := s.httpClient.Do(req) + if err != nil { + return jar.Properties{}, xerrors.Errorf("sha1 search error: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return jar.Properties{}, xerrors.Errorf("status %s from %s", resp.Status, req.URL.String()) + } + + var res apiResponse + if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { + return jar.Properties{}, xerrors.Errorf("json decode error: %w", err) + } + + if len(res.Response.Docs) == 0 { + return jar.Properties{}, xerrors.Errorf("digest %s: %w", sha1, jar.ArtifactNotFoundErr) + } + + // Some artifacts might have the same SHA-1 digests. + // e.g. "javax.servlet:jstl" and "jstl:jstl" + docs := res.Response.Docs + sort.Slice(docs, func(i, j int) bool { + return docs[i].ID < docs[j].ID + }) + d := docs[0] + + return jar.Properties{ + GroupID: d.GroupID, + ArtifactID: d.ArtifactID, + Version: d.Version, + }, nil +} + +func (s Sonatype) SearchByArtifactID(artifactID, _ string) (string, error) { + req, err := http.NewRequest(http.MethodGet, s.baseURL, http.NoBody) + if err != nil { + return "", xerrors.Errorf("unable to initialize HTTP client: %w", err) + } + + q := req.URL.Query() + q.Set("q", fmt.Sprintf(artifactIdQuery, artifactID)) + q.Set("rows", "20") + q.Set("wt", "json") + req.URL.RawQuery = q.Encode() + + resp, err := s.httpClient.Do(req) + if err != nil { + return "", xerrors.Errorf("artifactID search error: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", xerrors.Errorf("status %s from %s", resp.Status, req.URL.String()) + } + + var res apiResponse + if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { + return "", xerrors.Errorf("json decode error: %w", err) + } + + if len(res.Response.Docs) == 0 { + return "", xerrors.Errorf("artifactID %s: %w", artifactID, jar.ArtifactNotFoundErr) + } + + // Some artifacts might have the same artifactId. + // e.g. "javax.servlet:jstl" and "jstl:jstl" + docs := res.Response.Docs + sort.Slice(docs, func(i, j int) bool { + return docs[i].VersionCount > docs[j].VersionCount + }) + d := docs[0] + + return d.GroupID, nil +} diff --git a/pkg/dependency/parser/java/jar/testdata/gradle.war b/pkg/dependency/parser/java/jar/testdata/gradle.war new file mode 100644 index 000000000000..3564bcee40f0 Binary files /dev/null and b/pkg/dependency/parser/java/jar/testdata/gradle.war differ diff --git a/pkg/dependency/parser/java/jar/testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar b/pkg/dependency/parser/java/jar/testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar new file mode 100644 index 000000000000..793c4845e727 Binary files /dev/null and b/pkg/dependency/parser/java/jar/testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar differ diff --git a/pkg/dependency/parser/java/jar/testdata/heuristic-1.0.0-SNAPSHOT.jar b/pkg/dependency/parser/java/jar/testdata/heuristic-1.0.0-SNAPSHOT.jar new file mode 100644 index 000000000000..1922bc51ab3a Binary files /dev/null and b/pkg/dependency/parser/java/jar/testdata/heuristic-1.0.0-SNAPSHOT.jar differ diff --git a/pkg/dependency/parser/java/jar/testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar b/pkg/dependency/parser/java/jar/testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar new file mode 100644 index 000000000000..080b15ba5e40 Binary files /dev/null and b/pkg/dependency/parser/java/jar/testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar differ diff --git a/pkg/dependency/parser/java/jar/testdata/maven.war b/pkg/dependency/parser/java/jar/testdata/maven.war new file mode 100644 index 000000000000..787d517174b7 Binary files /dev/null and b/pkg/dependency/parser/java/jar/testdata/maven.war differ diff --git a/pkg/dependency/parser/java/jar/testdata/nested.jar b/pkg/dependency/parser/java/jar/testdata/nested.jar new file mode 100644 index 000000000000..8d0c14fe52dc Binary files /dev/null and b/pkg/dependency/parser/java/jar/testdata/nested.jar differ diff --git a/pkg/dependency/parser/java/jar/testdata/test.jar b/pkg/dependency/parser/java/jar/testdata/test.jar new file mode 100644 index 000000000000..c48cc32ee7b2 Binary files /dev/null and b/pkg/dependency/parser/java/jar/testdata/test.jar differ diff --git a/pkg/dependency/parser/java/jar/testdata/testimage/gradle/Dockerfile b/pkg/dependency/parser/java/jar/testdata/testimage/gradle/Dockerfile new file mode 100644 index 000000000000..cbc82c322976 --- /dev/null +++ b/pkg/dependency/parser/java/jar/testdata/testimage/gradle/Dockerfile @@ -0,0 +1,4 @@ +FROM gradle:6.8.1-jdk +RUN gradle init --type java-application +COPY build.gradle app/ +RUN gradle war diff --git a/pkg/dependency/parser/java/jar/testdata/testimage/gradle/build.gradle b/pkg/dependency/parser/java/jar/testdata/testimage/gradle/build.gradle new file mode 100644 index 000000000000..9ea187a36d53 --- /dev/null +++ b/pkg/dependency/parser/java/jar/testdata/testimage/gradle/build.gradle @@ -0,0 +1,25 @@ +plugins { + id 'application' + id 'war' +} + +repositories { + // Use JCenter for resolving dependencies. + jcenter() +} + +dependencies { + // Use JUnit test framework. + testImplementation 'junit:junit:4.13' + + implementation 'commons-dbcp:commons-dbcp:1.4' + implementation 'commons-pool:commons-pool:1.6' + implementation 'log4j:log4j:1.2.17' + implementation 'org.apache.commons:commons-compress:1.19' +} + +application { + // Define the main class for the application. + mainClass = 'gradle.App' +} + diff --git a/pkg/dependency/parser/java/jar/testdata/testimage/maven/Dockerfile b/pkg/dependency/parser/java/jar/testdata/testimage/maven/Dockerfile new file mode 100644 index 000000000000..3f327544b293 --- /dev/null +++ b/pkg/dependency/parser/java/jar/testdata/testimage/maven/Dockerfile @@ -0,0 +1,8 @@ +FROM maven:3.6.3-jdk-11 + +RUN mvn archetype:generate -DgroupId=com.example -DartifactId=web-app -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false +WORKDIR /web-app +COPY pom.xml . +RUN mvn clean install && mvn package + +CMD ["mvn", "dependency:tree"] diff --git a/pkg/dependency/parser/java/jar/testdata/testimage/maven/pom.xml b/pkg/dependency/parser/java/jar/testdata/testimage/maven/pom.xml new file mode 100644 index 000000000000..e7ffc52b67cb --- /dev/null +++ b/pkg/dependency/parser/java/jar/testdata/testimage/maven/pom.xml @@ -0,0 +1,32 @@ + + 4.0.0 + com.example + web-app + war + 1.0-SNAPSHOT + web-app Maven Webapp + http://maven.apache.org + + + junit + junit + 3.8.1 + test + + + com.fasterxml.jackson.core + jackson-databind + 2.9.10.6 + + + com.cronutils + cron-utils + 9.1.2 + + + + web-app + + + diff --git a/pkg/dependency/parser/java/jar/types.go b/pkg/dependency/parser/java/jar/types.go new file mode 100644 index 000000000000..ddd378b778b7 --- /dev/null +++ b/pkg/dependency/parser/java/jar/types.go @@ -0,0 +1,34 @@ +package jar + +import ( + "fmt" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +var ArtifactNotFoundErr = xerrors.New("no artifact found") + +type Properties struct { + GroupID string + ArtifactID string + Version string + FilePath string // path to file containing these props +} + +func (p Properties) Library() types.Library { + return types.Library{ + Name: fmt.Sprintf("%s:%s", p.GroupID, p.ArtifactID), + Version: p.Version, + FilePath: p.FilePath, + } +} + +func (p Properties) Valid() bool { + return p.GroupID != "" && p.ArtifactID != "" && p.Version != "" +} + +func (p Properties) String() string { + return fmt.Sprintf("%s:%s:%s", p.GroupID, p.ArtifactID, p.Version) +} diff --git a/pkg/dependency/parser/java/pom/artifact.go b/pkg/dependency/parser/java/pom/artifact.go new file mode 100644 index 000000000000..7cbab3b5b651 --- /dev/null +++ b/pkg/dependency/parser/java/pom/artifact.go @@ -0,0 +1,162 @@ +package pom + +import ( + "fmt" + "os" + "regexp" + "strings" + + "github.com/samber/lo" + "golang.org/x/exp/slices" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +var ( + varRegexp = regexp.MustCompile(`\${(\S+?)}`) +) + +type artifact struct { + GroupID string + ArtifactID string + Version version + Licenses []string + + Exclusions map[string]struct{} + + Module bool + Root bool + Direct bool + + Locations types.Locations +} + +func newArtifact(groupID, artifactID, version string, licenses []string, props map[string]string) artifact { + return artifact{ + GroupID: evaluateVariable(groupID, props, nil), + ArtifactID: evaluateVariable(artifactID, props, nil), + Version: newVersion(evaluateVariable(version, props, nil)), + Licenses: licenses, + } +} + +func (a artifact) IsEmpty() bool { + return a.GroupID == "" || a.ArtifactID == "" || a.Version.String() == "" +} + +func (a artifact) Equal(o artifact) bool { + return a.GroupID == o.GroupID || a.ArtifactID == o.ArtifactID || a.Version.String() == o.Version.String() +} + +func (a artifact) JoinLicenses() string { + return strings.Join(a.Licenses, ", ") +} + +func (a artifact) ToPOMLicenses() pomLicenses { + return pomLicenses{ + License: lo.Map(a.Licenses, func(lic string, _ int) pomLicense { + return pomLicense{Name: lic} + }), + } +} + +func (a artifact) Inherit(parent artifact) artifact { + // inherited from a parent + if a.GroupID == "" { + a.GroupID = parent.GroupID + } + + if len(a.Licenses) == 0 { + a.Licenses = parent.Licenses + } + + if a.Version.String() == "" { + a.Version = parent.Version + } + return a +} + +func (a artifact) Name() string { + return fmt.Sprintf("%s:%s", a.GroupID, a.ArtifactID) +} + +func (a artifact) String() string { + return fmt.Sprintf("%s:%s", a.Name(), a.Version) +} + +type version struct { + ver string + hard bool +} + +// Only soft and hard requirements for the specified version are supported at the moment. +func newVersion(s string) version { + var hard bool + if strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]") { + s = strings.Trim(s, "[]") + hard = true + } + + // TODO: Other requirements are not supported + if strings.ContainsAny(s, ",()[]") { + s = "" + } + + return version{ + ver: s, + hard: hard, + } +} + +func (v1 version) shouldOverride(v2 version) bool { + if !v1.hard && v2.hard { + return true + } + return false +} + +func (v1 version) String() string { + return v1.ver +} + +func evaluateVariable(s string, props map[string]string, seenProps []string) string { + if props == nil { + props = make(map[string]string) + } + + for _, m := range varRegexp.FindAllStringSubmatch(s, -1) { + var newValue string + + // env.X: https://maven.apache.org/pom.html#Properties + // e.g. env.PATH + if strings.HasPrefix(m[1], "env.") { + newValue = os.Getenv(strings.TrimPrefix(m[1], "env.")) + } else { + // might include another property. + // e.g. ${skipTests} + ss, ok := props[m[1]] + if ok { + // search for looped properties + if slices.Contains(seenProps, ss) { + printLoopedPropertiesStack(m[0], seenProps) + return "" + } + seenProps = append(seenProps, ss) // save evaluated props to check if we get this prop again + newValue = evaluateVariable(ss, props, seenProps) + seenProps = []string{} // clear props if we returned from recursive. Required for correct work with 2 same props like ${foo}-${foo} + } + + } + s = strings.ReplaceAll(s, m[0], newValue) + } + return s +} + +func printLoopedPropertiesStack(env string, usedProps []string) { + var s string + for _, prop := range usedProps { + s += fmt.Sprintf("%s -> ", prop) + } + log.Logger.Warnf("Lopped properties were detected: %s%s", s, env) +} diff --git a/pkg/dependency/parser/java/pom/artifact_test.go b/pkg/dependency/parser/java/pom/artifact_test.go new file mode 100644 index 000000000000..79715dbcca7c --- /dev/null +++ b/pkg/dependency/parser/java/pom/artifact_test.go @@ -0,0 +1,99 @@ +package pom + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_evaluateVariable(t *testing.T) { + type args struct { + s string + props map[string]string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "happy path", + args: args{ + s: "${java.version}", + props: map[string]string{ + "java.version": "1.7", + }, + }, + want: "1.7", + }, + { + name: "two variables", + args: args{ + s: "${foo.name}-${bar.name}", + props: map[string]string{ + "foo.name": "aaa", + "bar.name": "bbb", + }, + }, + want: "aaa-bbb", + }, + { + name: "looped variables", + args: args{ + s: "${foo.name}", + props: map[string]string{ + "foo.name": "${bar.name}", + "bar.name": "${foo.name}", + }, + }, + want: "", + }, + { + name: "same variables", + args: args{ + s: "${foo.name}-${foo.name}", + props: map[string]string{ + "foo.name": "aaa", + }, + }, + want: "aaa-aaa", + }, + { + name: "nested variables", + args: args{ + s: "${jackson.version.core}", + props: map[string]string{ + "jackson.version": "2.12.1", + "jackson.version.core": "${jackson.version}", + }, + }, + want: "2.12.1", + }, + { + name: "environmental variable", + args: args{ + s: "${env.TEST_GO_DEP_PARSER}", + }, + want: "1.2.3", + }, + { + name: "no variable", + args: args{ + s: "1.12", + }, + want: "1.12", + }, + } + + envName := "TEST_GO_DEP_PARSER" + os.Setenv(envName, "1.2.3") + defer os.Unsetenv(envName) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := evaluateVariable(tt.args.s, tt.args.props, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/java/pom/cache.go b/pkg/dependency/parser/java/pom/cache.go new file mode 100644 index 000000000000..af4586e095fa --- /dev/null +++ b/pkg/dependency/parser/java/pom/cache.go @@ -0,0 +1,21 @@ +package pom + +import "fmt" + +type pomCache map[string]*analysisResult + +func newPOMCache() pomCache { + return pomCache{} +} + +func (c pomCache) put(art artifact, result analysisResult) { + c[c.key(art)] = &result +} + +func (c pomCache) get(art artifact) *analysisResult { + return c[c.key(art)] +} + +func (c pomCache) key(art artifact) string { + return fmt.Sprintf("%s:%s", art.Name(), art.Version) +} diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go new file mode 100644 index 000000000000..955f8cfd9e33 --- /dev/null +++ b/pkg/dependency/parser/java/pom/parse.go @@ -0,0 +1,701 @@ +package pom + +import ( + "encoding/xml" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "sort" + "strings" + + multierror "github.com/hashicorp/go-multierror" + "github.com/samber/lo" + "go.uber.org/zap" + "golang.org/x/net/html/charset" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +const ( + centralURL = "https://repo.maven.apache.org/maven2/" +) + +type options struct { + offline bool + remoteRepos []string +} + +type option func(*options) + +func WithOffline(offline bool) option { + return func(opts *options) { + opts.offline = offline + } +} + +func WithRemoteRepos(repos []string) option { + return func(opts *options) { + opts.remoteRepos = repos + } +} + +type parser struct { + rootPath string + cache pomCache + localRepository string + remoteRepositories []string + offline bool + servers []Server +} + +func NewParser(filePath string, opts ...option) types.Parser { + o := &options{ + offline: false, + remoteRepos: []string{centralURL}, + } + + for _, opt := range opts { + opt(o) + } + + s := readSettings() + localRepository := s.LocalRepository + if localRepository == "" { + homeDir, _ := os.UserHomeDir() + localRepository = filepath.Join(homeDir, ".m2", "repository") + } + + return &parser{ + rootPath: filepath.Clean(filePath), + cache: newPOMCache(), + localRepository: localRepository, + remoteRepositories: o.remoteRepos, + offline: o.offline, + servers: s.Servers, + } +} + +func (p *parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + content, err := parsePom(r) + if err != nil { + return nil, nil, xerrors.Errorf("failed to parse POM: %w", err) + } + + root := &pom{ + filePath: p.rootPath, + content: content, + } + + // Analyze root POM + result, err := p.analyze(root, analysisOptions{lineNumber: true}) + if err != nil { + return nil, nil, xerrors.Errorf("analyze error (%s): %w", p.rootPath, err) + } + + // Cache root POM + p.cache.put(result.artifact, result) + + return p.parseRoot(root.artifact(), make(map[string]struct{})) +} + +func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]types.Library, []types.Dependency, error) { + // Prepare a queue for dependencies + queue := newArtifactQueue() + + // Enqueue root POM + root.Root = true + root.Module = false + queue.enqueue(root) + + var ( + libs []types.Library + deps []types.Dependency + rootDepManagement []pomDependency + uniqArtifacts = make(map[string]artifact) + uniqDeps = make(map[string][]string) + ) + + // Iterate direct and transitive dependencies + for !queue.IsEmpty() { + art := queue.dequeue() + + // Modules should be handled separately so that they can have independent dependencies. + // It means multi-module allows for duplicate dependencies. + if art.Module { + if _, ok := uniqModules[art.String()]; ok { + continue + } + uniqModules[art.String()] = struct{}{} + + moduleLibs, moduleDeps, err := p.parseRoot(art, uniqModules) + if err != nil { + return nil, nil, err + } + + libs = append(libs, moduleLibs...) + if moduleDeps != nil { + deps = append(deps, moduleDeps...) + } + continue + } + + // For soft requirements, skip dependency resolution that has already been resolved. + if uniqueArt, ok := uniqArtifacts[art.Name()]; ok { + if !uniqueArt.Version.shouldOverride(art.Version) { + continue + } + // mark artifact as Direct, if saved artifact is Direct + // take a look `hard requirement for the specified version` test + if uniqueArt.Direct { + art.Direct = true + } + // We don't need to overwrite dependency location for hard links + if uniqueArt.Locations != nil { + art.Locations = uniqueArt.Locations + } + } + + result, err := p.resolve(art, rootDepManagement) + if err != nil { + return nil, nil, xerrors.Errorf("resolve error (%s): %w", art, err) + } + + if art.Root { + // Managed dependencies in the root POM affect transitive dependencies + rootDepManagement = p.resolveDepManagement(result.properties, result.dependencyManagement) + + // mark root artifact and its dependencies as Direct + art.Direct = true + result.dependencies = lo.Map(result.dependencies, func(dep artifact, _ int) artifact { + dep.Direct = true + return dep + }) + } + + // Parse, cache, and enqueue modules. + for _, relativePath := range result.modules { + moduleArtifact, err := p.parseModule(result.filePath, relativePath) + if err != nil { + log.Logger.Debugf("Unable to parse %q module: %s", result.filePath, err) + continue + } + + queue.enqueue(moduleArtifact) + } + + // Resolve transitive dependencies later + queue.enqueue(result.dependencies...) + + // Offline mode may be missing some fields. + if !art.IsEmpty() { + // Override the version + uniqArtifacts[art.Name()] = artifact{ + Version: art.Version, + Licenses: result.artifact.Licenses, + Direct: art.Direct, + Root: art.Root, + Locations: art.Locations, + } + + // save only dependency names + // version will be determined later + dependsOn := lo.Map(result.dependencies, func(a artifact, _ int) string { + return a.Name() + }) + uniqDeps[packageID(art.Name(), art.Version.String())] = dependsOn + } + } + + // Convert to []types.Library and []types.Dependency + for name, art := range uniqArtifacts { + lib := types.Library{ + ID: packageID(name, art.Version.String()), + Name: name, + Version: art.Version.String(), + License: art.JoinLicenses(), + Indirect: !art.Direct, + Locations: art.Locations, + } + libs = append(libs, lib) + + // Convert dependency names into dependency IDs + dependsOn := lo.FilterMap(uniqDeps[lib.ID], func(dependOnName string, _ int) (string, bool) { + ver := depVersion(dependOnName, uniqArtifacts) + return packageID(dependOnName, ver), ver != "" + }) + + sort.Strings(dependsOn) + if len(dependsOn) > 0 { + deps = append(deps, types.Dependency{ + ID: lib.ID, + DependsOn: dependsOn, + }) + } + } + + sort.Sort(types.Libraries(libs)) + sort.Sort(types.Dependencies(deps)) + + return libs, deps, nil +} + +// depVersion finds dependency in uniqArtifacts and return its version +func depVersion(depName string, uniqArtifacts map[string]artifact) string { + if art, ok := uniqArtifacts[depName]; ok { + return art.Version.String() + } + return "" +} + +func (p *parser) parseModule(currentPath, relativePath string) (artifact, error) { + // modulePath: "root/" + "module/" => "root/module" + module, err := p.openRelativePom(currentPath, relativePath) + if err != nil { + return artifact{}, xerrors.Errorf("unable to open the relative path: %w", err) + } + + result, err := p.analyze(module, analysisOptions{}) + if err != nil { + return artifact{}, xerrors.Errorf("analyze error: %w", err) + } + + moduleArtifact := module.artifact() + moduleArtifact.Module = true + + p.cache.put(moduleArtifact, result) + + return moduleArtifact, nil +} + +func (p *parser) resolve(art artifact, rootDepManagement []pomDependency) (analysisResult, error) { + // If the artifact is found in cache, it is returned. + if result := p.cache.get(art); result != nil { + return *result, nil + } + + log.Logger.Debugf("Resolving %s:%s:%s...", art.GroupID, art.ArtifactID, art.Version) + pomContent, err := p.tryRepository(art.GroupID, art.ArtifactID, art.Version.String()) + if err != nil { + log.Logger.Debug(err) + } + result, err := p.analyze(pomContent, analysisOptions{ + exclusions: art.Exclusions, + depManagement: rootDepManagement, + }) + if err != nil { + return analysisResult{}, xerrors.Errorf("analyze error: %w", err) + } + + p.cache.put(art, result) + return result, nil +} + +type analysisResult struct { + filePath string + artifact artifact + dependencies []artifact + dependencyManagement []pomDependency // Keep the order of dependencies in 'dependencyManagement' + properties map[string]string + modules []string +} + +type analysisOptions struct { + exclusions map[string]struct{} + depManagement []pomDependency // from the root POM + lineNumber bool // Save line numbers +} + +func (p *parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error) { + if pom == nil || pom.content == nil { + return analysisResult{}, nil + } + + // Update remoteRepositories + p.remoteRepositories = utils.UniqueStrings(append(pom.repositories(p.servers), p.remoteRepositories...)) + + // Parent + parent, err := p.parseParent(pom.filePath, pom.content.Parent) + if err != nil { + return analysisResult{}, xerrors.Errorf("parent error: %w", err) + } + + // Inherit values/properties from parent + pom.inherit(parent) + + // Generate properties + props := pom.properties() + + // dependencyManagements have the next priority: + // 1. Managed dependencies from this POM + // 2. Managed dependencies from parent of this POM + depManagement := p.mergeDependencyManagements(pom.content.DependencyManagement.Dependencies.Dependency, + parent.dependencyManagement) + + // Merge dependencies. Child dependencies must be preferred than parent dependencies. + // Parents don't have to resolve dependencies. + deps := p.parseDependencies(pom.content.Dependencies.Dependency, props, depManagement, opts) + deps = p.mergeDependencies(parent.dependencies, deps, opts.exclusions) + + return analysisResult{ + filePath: pom.filePath, + artifact: pom.artifact(), + dependencies: deps, + dependencyManagement: depManagement, + properties: props, + modules: pom.content.Modules.Module, + }, nil +} + +func (p *parser) mergeDependencyManagements(depManagements ...[]pomDependency) []pomDependency { + uniq := make(map[string]struct{}) + var depManagement []pomDependency + // The preceding argument takes precedence. + for _, dm := range depManagements { + for _, dep := range dm { + if _, ok := uniq[dep.Name()]; ok { + continue + } + depManagement = append(depManagement, dep) + uniq[dep.Name()] = struct{}{} + } + } + return depManagement +} + +func (p *parser) parseDependencies(deps []pomDependency, props map[string]string, depManagement []pomDependency, + opts analysisOptions) []artifact { + // Imported POMs often have no dependencies, so dependencyManagement resolution can be skipped. + if len(deps) == 0 { + return nil + } + + // Resolve dependencyManagement + depManagement = p.resolveDepManagement(props, depManagement) + + rootDepManagement := opts.depManagement + var dependencies []artifact + for _, d := range deps { + // Resolve dependencies + d = d.Resolve(props, depManagement, rootDepManagement) + + if (d.Scope != "" && d.Scope != "compile" && d.Scope != "runtime") || d.Optional { + continue + } + + dependencies = append(dependencies, d.ToArtifact(opts)) + } + return dependencies +} + +func (p *parser) resolveDepManagement(props map[string]string, depManagement []pomDependency) []pomDependency { + var newDepManagement, imports []pomDependency + for _, dep := range depManagement { + // cf. https://howtodoinjava.com/maven/maven-dependency-scopes/#import + if dep.Scope == "import" { + imports = append(imports, dep) + } else { + // Evaluate variables + newDepManagement = append(newDepManagement, dep.Resolve(props, nil, nil)) + } + } + + // Managed dependencies with a scope of "import" should be processed after other managed dependencies. + // cf. https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#importing-dependencies + for _, imp := range imports { + art := newArtifact(imp.GroupID, imp.ArtifactID, imp.Version, nil, props) + result, err := p.resolve(art, nil) + if err != nil { + continue + } + + // We need to recursively check all nested depManagements, + // so that we don't miss dependencies on nested depManagements with `Import` scope. + newProps := utils.MergeMaps(props, result.properties) + result.dependencyManagement = p.resolveDepManagement(newProps, result.dependencyManagement) + for k, dd := range result.dependencyManagement { + // Evaluate variables and overwrite dependencyManagement + result.dependencyManagement[k] = dd.Resolve(newProps, nil, nil) + } + newDepManagement = p.mergeDependencyManagements(newDepManagement, result.dependencyManagement) + } + return newDepManagement +} + +func (p *parser) mergeDependencies(parent, child []artifact, exclusions map[string]struct{}) []artifact { + var deps []artifact + unique := make(map[string]struct{}) + + for _, d := range append(child, parent...) { + if excludeDep(exclusions, d) { + continue + } + if _, ok := unique[d.Name()]; ok { + continue + } + unique[d.Name()] = struct{}{} + deps = append(deps, d) + } + + return deps +} + +func excludeDep(exclusions map[string]struct{}, art artifact) bool { + if _, ok := exclusions[art.Name()]; ok { + return true + } + // Maven can use "*" in GroupID and ArtifactID fields to exclude dependencies + // https://maven.apache.org/pom.html#exclusions + for exlusion := range exclusions { + // exclusion format - ":" + e := strings.Split(exlusion, ":") + if (e[0] == art.GroupID || e[0] == "*") && (e[1] == art.ArtifactID || e[1] == "*") { + return true + } + } + return false +} + +func (p *parser) parseParent(currentPath string, parent pomParent) (analysisResult, error) { + // Pass nil properties so that variables in are not evaluated. + target := newArtifact(parent.GroupId, parent.ArtifactId, parent.Version, nil, nil) + // if version is property (e.g. ${revision}) - we still need to parse this pom + if target.IsEmpty() && !isProperty(parent.Version) { + return analysisResult{}, nil + } + log.Logger.Debugf("Start parent: %s", target.String()) + defer func() { + log.Logger.Debugf("Exit parent: %s", target.String()) + }() + + // If the artifact is found in cache, it is returned. + if result := p.cache.get(target); result != nil { + return *result, nil + } + + parentPOM, err := p.retrieveParent(currentPath, parent.RelativePath, target) + if err != nil { + log.Logger.Debugf("parent POM not found: %s", err) + } + + result, err := p.analyze(parentPOM, analysisOptions{}) + if err != nil { + return analysisResult{}, xerrors.Errorf("analyze error: %w", err) + } + + p.cache.put(target, result) + + return result, nil +} + +func (p *parser) retrieveParent(currentPath, relativePath string, target artifact) (*pom, error) { + var errs error + + // Try relativePath + if relativePath != "" { + pom, err := p.tryRelativePath(target, currentPath, relativePath) + if err != nil { + errs = multierror.Append(errs, err) + } else { + return pom, nil + } + } + + // If not found, search the parent director + pom, err := p.tryRelativePath(target, currentPath, "../pom.xml") + if err != nil { + errs = multierror.Append(errs, err) + } else { + return pom, nil + } + + // If not found, search local/remote remoteRepositories + pom, err = p.tryRepository(target.GroupID, target.ArtifactID, target.Version.String()) + if err != nil { + errs = multierror.Append(errs, err) + } else { + return pom, nil + } + + // Reaching here means the POM wasn't found + return nil, errs +} + +func (p *parser) tryRelativePath(parentArtifact artifact, currentPath, relativePath string) (*pom, error) { + pom, err := p.openRelativePom(currentPath, relativePath) + if err != nil { + return nil, err + } + + // To avoid an infinite loop or parsing the wrong parent when using relatedPath or `../pom.xml`, + // we need to compare GAV of `parentArtifact` (`parent` tag from base pom) and GAV of pom from `relativePath`. + // See `compare ArtifactIDs for base and parent pom's` test for example. + // But GroupID can be inherited from parent (`p.analyze` function is required to get the GroupID). + // Version can contain a property (`p.analyze` function is required to get the GroupID). + // So we can only match ArtifactID's. + if pom.artifact().ArtifactID != parentArtifact.ArtifactID { + return nil, xerrors.New("'parent.relativePath' points at wrong local POM") + } + result, err := p.analyze(pom, analysisOptions{}) + if err != nil { + return nil, xerrors.Errorf("analyze error: %w", err) + } + + if !parentArtifact.Equal(result.artifact) { + return nil, xerrors.New("'parent.relativePath' points at wrong local POM") + } + + return pom, nil +} + +func (p *parser) openRelativePom(currentPath, relativePath string) (*pom, error) { + // e.g. child/pom.xml => child/ + dir := filepath.Dir(currentPath) + + // e.g. child + ../parent => parent/ + filePath := filepath.Join(dir, relativePath) + + isDir, err := isDirectory(filePath) + if err != nil { + return nil, err + } else if isDir { + // e.g. parent/ => parent/pom.xml + filePath = filepath.Join(filePath, "pom.xml") + } + + pom, err := p.openPom(filePath) + if err != nil { + return nil, xerrors.Errorf("failed to open %s: %w", filePath, err) + } + return pom, nil +} + +func (p *parser) openPom(filePath string) (*pom, error) { + f, err := os.Open(filePath) + if err != nil { + return nil, xerrors.Errorf("file open error (%s): %w", filePath, err) + } + + content, err := parsePom(f) + if err != nil { + return nil, xerrors.Errorf("failed to parse the local POM: %w", err) + } + return &pom{ + filePath: filePath, + content: content, + }, nil +} +func (p *parser) tryRepository(groupID, artifactID, version string) (*pom, error) { + if version == "" { + return nil, xerrors.Errorf("Version missing for %s:%s", groupID, artifactID) + } + + // Generate a proper path to the pom.xml + // e.g. com.fasterxml.jackson.core, jackson-annotations, 2.10.0 + // => com/fasterxml/jackson/core/jackson-annotations/2.10.0/jackson-annotations-2.10.0.pom + paths := strings.Split(groupID, ".") + paths = append(paths, artifactID, version, fmt.Sprintf("%s-%s.pom", artifactID, version)) + + // Search local remoteRepositories + loaded, err := p.loadPOMFromLocalRepository(paths) + if err == nil { + return loaded, nil + } + + // Search remote remoteRepositories + loaded, err = p.fetchPOMFromRemoteRepositories(paths) + if err == nil { + return loaded, nil + } + + return nil, xerrors.Errorf("%s:%s:%s was not found in local/remote repositories", groupID, artifactID, version) +} + +func (p *parser) loadPOMFromLocalRepository(paths []string) (*pom, error) { + paths = append([]string{p.localRepository}, paths...) + localPath := filepath.Join(paths...) + + return p.openPom(localPath) +} + +func (p *parser) fetchPOMFromRemoteRepositories(paths []string) (*pom, error) { + // Do not try fetching pom.xml from remote repositories in offline mode + if p.offline { + log.Logger.Debug("Fetching the remote pom.xml is skipped") + return nil, xerrors.New("offline mode") + } + + // try all remoteRepositories + for _, repo := range p.remoteRepositories { + fetched, err := fetchPOMFromRemoteRepository(repo, paths) + if err != nil { + return nil, xerrors.Errorf("fetch repository error: %w", err) + } else if fetched == nil { + continue + } + return fetched, nil + } + return nil, xerrors.Errorf("the POM was not found in remote remoteRepositories") +} + +func fetchPOMFromRemoteRepository(repo string, paths []string) (*pom, error) { + repoURL, err := url.Parse(repo) + if err != nil { + log.Logger.Errorw("URL parse error", zap.String("repo", repo)) + return nil, nil + } + + paths = append([]string{repoURL.Path}, paths...) + repoURL.Path = path.Join(paths...) + + client := &http.Client{} + req, err := http.NewRequest("GET", repoURL.String(), http.NoBody) + if err != nil { + log.Logger.Debugf("Request failed for %s%s", repoURL.Host, repoURL.Path) + return nil, nil + } + if repoURL.User != nil { + password, _ := repoURL.User.Password() + req.SetBasicAuth(repoURL.User.Username(), password) + } + + resp, err := client.Do(req) + if err != nil || resp.StatusCode != http.StatusOK { + log.Logger.Debugf("Failed to fetch from %s%s", repoURL.Host, repoURL.Path) + return nil, nil + } + defer resp.Body.Close() + + content, err := parsePom(resp.Body) + if err != nil { + return nil, xerrors.Errorf("failed to parse the remote POM: %w", err) + } + + return &pom{ + filePath: "", // from remote repositories + content: content, + }, nil +} + +func parsePom(r io.Reader) (*pomXML, error) { + parsed := &pomXML{} + decoder := xml.NewDecoder(r) + decoder.CharsetReader = charset.NewReaderLabel + if err := decoder.Decode(parsed); err != nil { + return nil, xerrors.Errorf("xml decode error: %w", err) + } + return parsed, nil +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.Pom, name, version) +} diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go new file mode 100644 index 000000000000..b73e40511507 --- /dev/null +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -0,0 +1,1312 @@ +package pom_test + +import ( + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/java/pom" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestPom_Parse(t *testing.T) { + tests := []struct { + name string + inputFile string + local bool + offline bool + want []types.Library + wantDeps []types.Dependency + wantErr string + }{ + { + name: "local repository", + inputFile: filepath.Join("testdata", "happy", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:happy:1.0.0", + Name: "com.example:happy", + Version: "1.0.0", + License: "BSD-3-Clause", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 32, + EndLine: 36, + }, + }, + }, + { + ID: "org.example:example-runtime:1.0.0", + Name: "org.example:example-runtime", + Version: "1.0.0", + Locations: types.Locations{ + { + StartLine: 37, + EndLine: 42, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:happy:1.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + "org.example:example-runtime:1.0.0", + }, + }, + }, + }, + { + name: "remote repository", + inputFile: filepath.Join("testdata", "happy", "pom.xml"), + local: false, + want: []types.Library{ + { + ID: "com.example:happy:1.0.0", + Name: "com.example:happy", + Version: "1.0.0", + License: "BSD-3-Clause", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 32, + EndLine: 36, + }, + }, + }, + { + ID: "org.example:example-runtime:1.0.0", + Name: "org.example:example-runtime", + Version: "1.0.0", + Locations: types.Locations{ + { + StartLine: 37, + EndLine: 42, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:happy:1.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + "org.example:example-runtime:1.0.0", + }, + }, + }, + }, + { + name: "offline mode", + inputFile: filepath.Join("testdata", "offline", "pom.xml"), + local: false, + offline: true, + want: []types.Library{ + { + ID: "org.example:example-offline:2.3.4", + Name: "org.example:example-offline", + Version: "2.3.4", + Locations: types.Locations{ + { + StartLine: 17, + EndLine: 21, + }, + }, + }, + }, + }, + { + name: "inherit parent properties", + inputFile: filepath.Join("testdata", "parent-properties", "child", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:child:1.0.0", + Name: "com.example:child", + Version: "1.0.0", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 33, + EndLine: 37, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:child:1.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + }, + }, + { + name: "inherit project properties from parent", + inputFile: filepath.Join("testdata", "project-version-from-parent", "child", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:child:2.0.0", + Name: "com.example:child", + Version: "2.0.0", + }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 18, + EndLine: 22, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:child:2.0.0", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, + }, + }, + { + name: "inherit properties in parent depManagement with import scope", + inputFile: filepath.Join("testdata", "inherit-props", "base", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:test:0.0.1-SNAPSHOT", + Name: "com.example:test", + Version: "0.0.1-SNAPSHOT", + }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 18, + EndLine: 21, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:test:0.0.1-SNAPSHOT", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, + }, + }, + { + name: "dependencyManagement prefers child properties", + inputFile: filepath.Join("testdata", "parent-child-properties", "child", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:child:1.0.0", + Name: "com.example:child", + Version: "1.0.0", + }, + { + ID: "org.example:example-api:4.0.0", + Name: "org.example:example-api", + Version: "4.0.0", + Indirect: true, + }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Locations: types.Locations{ + { + StartLine: 22, + EndLine: 26, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:child:1.0.0", + DependsOn: []string{ + "org.example:example-dependency:1.2.3", + }, + }, + { + ID: "org.example:example-dependency:1.2.3", + DependsOn: []string{ + "org.example:example-api:4.0.0", + }, + }, + }, + }, + { + name: "inherit parent dependencies", + inputFile: filepath.Join("testdata", "parent-dependencies", "child", "pom.xml"), + local: false, + want: []types.Library{ + { + ID: "com.example:child:1.0.0-SNAPSHOT", + Name: "com.example:child", + Version: "1.0.0-SNAPSHOT", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:child:1.0.0-SNAPSHOT", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + }, + }, + { + name: "inherit parent dependencyManagement", + inputFile: filepath.Join("testdata", "parent-dependency-management", "child", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:child:3.0.0", + Name: "com.example:child", + Version: "3.0.0", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 26, + EndLine: 29, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:child:3.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + }, + }, + { + name: "transitive parents", + inputFile: filepath.Join("testdata", "transitive-parents", "base", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:base:4.0.0", + Name: "com.example:base", + Version: "4.0.0", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Indirect: true, + }, + { + ID: "org.example:example-child:2.0.0", + Name: "org.example:example-child", + Version: "2.0.0", + License: "Apache 2.0", + Locations: types.Locations{ + { + StartLine: 28, + EndLine: 32, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:base:4.0.0", + DependsOn: []string{ + "org.example:example-child:2.0.0", + }, + }, + { + ID: "org.example:example-child:2.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + }, + }, + { + name: "parent relativePath", + inputFile: filepath.Join("testdata", "parent-relative-path", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:child:1.0.0", + Name: "com.example:child", + Version: "1.0.0", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 26, + EndLine: 30, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:child:1.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + }, + }, + { + name: "parent version in property", + inputFile: filepath.Join("testdata", "parent-version-is-property", "child", "pom.xml"), + local: false, + want: []types.Library{ + { + ID: "com.example:child:1.0.0-SNAPSHOT", + Name: "com.example:child", + Version: "1.0.0-SNAPSHOT", + }, + { + ID: "org.example:example-api:1.1.1", + Name: "org.example:example-api", + Version: "1.1.1", + Locations: types.Locations{ + { + StartLine: 19, + EndLine: 22, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:child:1.0.0-SNAPSHOT", + DependsOn: []string{ + "org.example:example-api:1.1.1", + }, + }, + }, + }, + { + name: "parent in a remote repository", + inputFile: filepath.Join("testdata", "parent-remote-repository", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "org.example:child:1.0.0", + Name: "org.example:child", + Version: "1.0.0", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 25, + EndLine: 29, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "org.example:child:1.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + }, + }, + { + // mvn dependency:tree + // [INFO] com.example:soft:jar:1.0.0 + // [INFO] +- org.example:example-api:jar:1.7.30:compile + // [INFO] \- org.example:example-dependency:jar:1.2.3:compile + // Save DependsOn for each library - https://github.com/aquasecurity/go-dep-parser/pull/243#discussion_r1303904548 + name: "soft requirement", + inputFile: filepath.Join("testdata", "soft-requirement", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:soft:1.0.0", + Name: "com.example:soft", + Version: "1.0.0", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 32, + EndLine: 36, + }, + }, + }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Locations: types.Locations{ + { + StartLine: 37, + EndLine: 41, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:soft:1.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + "org.example:example-dependency:1.2.3", + }, + }, + { + ID: "org.example:example-dependency:1.2.3", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + }, + }, + { + // mvn dependency:tree + // [INFO] com.example:soft-transitive:jar:1.0.0 + // [INFO] +- org.example:example-dependency:jar:1.2.3:compile + // [INFO] | \- org.example:example-api:jar:2.0.0:compile + // [INFO] \- org.example:example-dependency2:jar:2.3.4:compile + // Save DependsOn for each library - https://github.com/aquasecurity/go-dep-parser/pull/243#discussion_r1303904548 + name: "soft requirement with transitive dependencies", + inputFile: filepath.Join("testdata", "soft-requirement-with-transitive-dependencies", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:soft-transitive:1.0.0", + Name: "com.example:soft-transitive", + Version: "1.0.0", + }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Indirect: true, + }, + { + ID: "org.example:example-dependency2:2.3.4", + Name: "org.example:example-dependency2", + Version: "2.3.4", + Locations: types.Locations{ + { + StartLine: 18, + EndLine: 22, + }, + }, + }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Locations: types.Locations{ + { + StartLine: 13, + EndLine: 17, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:soft-transitive:1.0.0", + DependsOn: []string{ + "org.example:example-dependency2:2.3.4", + "org.example:example-dependency:1.2.3", + }, + }, + { + ID: "org.example:example-dependency2:2.3.4", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, + { + ID: "org.example:example-dependency:1.2.3", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, + }, + }, + { + // mvn dependency:tree + //[INFO] com.example:hard:jar:1.0.0 + //[INFO] +- org.example:example-nested:jar:3.3.4:compile + //[INFO] \- org.example:example-dependency:jar:1.2.3:compile + //[INFO] \- org.example:example-api:jar:2.0.0:compile + // Save DependsOn for each library - https://github.com/aquasecurity/go-dep-parser/pull/243#discussion_r1303904548 + name: "hard requirement for the specified version", + inputFile: filepath.Join("testdata", "hard-requirement", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:hard:1.0.0", + Name: "com.example:hard", + Version: "1.0.0", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Indirect: true, + }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Locations: types.Locations{ + { + StartLine: 33, + EndLine: 37, + }, + }, + }, + { + ID: "org.example:example-nested:3.3.4", + Name: "org.example:example-nested", + Version: "3.3.4", + Locations: types.Locations{ + { + StartLine: 28, + EndLine: 32, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:hard:1.0.0", + DependsOn: []string{ + "org.example:example-dependency:1.2.3", + "org.example:example-nested:3.3.4", + }, + }, + { + ID: "org.example:example-dependency:1.2.3", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, + { + ID: "org.example:example-nested:3.3.4", + DependsOn: []string{ + "org.example:example-dependency:1.2.3", + }, + }, + }, + }, + { + name: "version requirement", + inputFile: filepath.Join("testdata", "version-requirement", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:hard:1.0.0", + Name: "com.example:hard", + Version: "1.0.0", + License: "Apache 2.0", + }, + }, + }, + { + name: "import dependencyManagement", + inputFile: filepath.Join("testdata", "import-dependency-management", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:import:2.0.0", + Name: "com.example:import", + Version: "2.0.0", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 34, + EndLine: 37, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:import:2.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + }, + }, + { + name: "import multiple dependencyManagement", + inputFile: filepath.Join("testdata", "import-dependency-management-multiple", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:import:2.0.0", + Name: "com.example:import", + Version: "2.0.0", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 42, + EndLine: 45, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:import:2.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + }, + }, + { + name: "exclusions", + inputFile: filepath.Join("testdata", "exclusions", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:exclusions:3.0.0", + Name: "com.example:exclusions", + Version: "3.0.0", + }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Indirect: true, + }, + { + ID: "org.example:example-nested:3.3.3", + Name: "org.example:example-nested", + Version: "3.3.3", + Locations: types.Locations{ + { + StartLine: 14, + EndLine: 28, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:exclusions:3.0.0", + DependsOn: []string{ + "org.example:example-nested:3.3.3", + }, + }, + { + ID: "org.example:example-nested:3.3.3", + DependsOn: []string{ + "org.example:example-dependency:1.2.3", + }, + }, + }, + }, + { + name: "exclusions in child", + inputFile: filepath.Join("testdata", "exclusions-in-child", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:example:1.0.0", + Name: "com.example:example", + Version: "1.0.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + Indirect: true, + License: "The Apache Software License, Version 2.0", + }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Indirect: true, + }, + { + ID: "org.example:example-exclusions:4.0.0", + Name: "org.example:example-exclusions", + Version: "4.0.0", + Locations: types.Locations{ + { + StartLine: 10, + EndLine: 14, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:example:1.0.0", + DependsOn: []string{ + "org.example:example-exclusions:4.0.0", + }, + }, + { + ID: "org.example:example-exclusions:4.0.0", + DependsOn: []string{ + "org.example:example-api:1.7.30", + "org.example:example-dependency:1.2.3", + }, + }, + }, + }, + { + name: "exclusions with wildcards", + inputFile: filepath.Join("testdata", "wildcard-exclusions", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:wildcard-exclusions:4.0.0", + Name: "com.example:wildcard-exclusions", + Version: "4.0.0", + }, + { + ID: "org.example:example-dependency2:2.3.4", + Name: "org.example:example-dependency2", + Version: "2.3.4", + Locations: types.Locations{ + { + StartLine: 25, + EndLine: 35, + }, + }, + }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Locations: types.Locations{ + { + StartLine: 14, + EndLine: 24, + }, + }, + }, + { + ID: "org.example:example-nested:3.3.3", + Name: "org.example:example-nested", + Version: "3.3.3", + Locations: types.Locations{ + { + StartLine: 36, + EndLine: 46, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:wildcard-exclusions:4.0.0", + DependsOn: []string{ + "org.example:example-dependency2:2.3.4", + "org.example:example-dependency:1.2.3", + "org.example:example-nested:3.3.3", + }, + }, + }, + }, + { + name: "multi module", + inputFile: filepath.Join("testdata", "multi-module", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:aggregation:1.0.0", + Name: "com.example:aggregation", + Version: "1.0.0", + License: "Apache 2.0", + }, + { + ID: "com.example:module:1.1.1", + Name: "com.example:module", + Version: "1.1.1", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Indirect: true, + }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + }, + }, + // maven doesn't include modules in dep tree of root pom + // for modules uses separate graph: + // âžœ mvn dependency:tree + // [INFO] --------------------------------[ jar ]--------------------------------- + // [INFO] + // [INFO] --- dependency:3.6.0:tree (default-cli) @ module --- + // [INFO] com.example:module:jar:1.1.1 + // [INFO] \- org.example:example-dependency:jar:1.2.3:compile + // [INFO] \- org.example:example-api:jar:2.0.0:compile + // [INFO] + // [INFO] ----------------------< com.example:aggregation >----------------------- + // [INFO] Building aggregation 1.0.0 [2/2] + // [INFO] from pom.xml + // [INFO] --------------------------------[ pom ]--------------------------------- + // [INFO] + // [INFO] --- dependency:3.6.0:tree (default-cli) @ aggregation --- + // [INFO] com.example:aggregation:pom:1.0.0 + wantDeps: []types.Dependency{ + { + ID: "com.example:module:1.1.1", + DependsOn: []string{ + "org.example:example-dependency:1.2.3", + }, + }, + { + ID: "org.example:example-dependency:1.2.3", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, + }, + }, + { + name: "Infinity loop for modules", + inputFile: filepath.Join("testdata", "modules-infinity-loop", "pom.xml"), + local: true, + want: []types.Library{ + // as module + { + ID: "org.example:module-1:2.0.0", + Name: "org.example:module-1", + Version: "2.0.0", + }, + // as dependency + { + ID: "org.example:module-1:2.0.0", + Name: "org.example:module-1", + Version: "2.0.0", + }, + { + ID: "org.example:module-2:3.0.0", + Name: "org.example:module-2", + Version: "3.0.0", + }, + { + ID: "org.example:root:1.0.0", + Name: "org.example:root", + Version: "1.0.0", + }, + }, + wantDeps: []types.Dependency{ + { + ID: "org.example:module-2:3.0.0", + DependsOn: []string{ + "org.example:module-1:2.0.0", + }, + }, + }, + }, + { + name: "multi module soft requirement", + inputFile: filepath.Join("testdata", "multi-module-soft-requirement", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:aggregation:1.0.0", + Name: "com.example:aggregation", + Version: "1.0.0", + }, + { + ID: "com.example:module1:1.1.1", + Name: "com.example:module1", + Version: "1.1.1", + }, + { + ID: "com.example:module2:1.1.1", + Name: "com.example:module2", + Version: "1.1.1", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:module1:1.1.1", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + { + ID: "com.example:module2:1.1.1", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, + }, + }, + { + name: "overwrite artifact version from dependencyManagement in the root POM", + inputFile: filepath.Join("testdata", "root-pom-dep-management", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:root-pom-dep-management:1.0.0", + Name: "com.example:root-pom-dep-management", + Version: "1.0.0", + }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Indirect: true, + }, + // dependency version is taken from `com.example:root-pom-dep-management` from dependencyManagement + // not from `com.example:example-nested` from `com.example:example-nested` + { + ID: "org.example:example-dependency:1.2.4", + Name: "org.example:example-dependency", + Version: "1.2.4", + Indirect: true, + }, + { + ID: "org.example:example-nested:3.3.3", + Name: "org.example:example-nested", + Version: "3.3.3", + Locations: types.Locations{ + { + StartLine: 20, + EndLine: 24, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:root-pom-dep-management:1.0.0", + DependsOn: []string{ + "org.example:example-nested:3.3.3", + }, + }, + { + ID: "org.example:example-dependency:1.2.4", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, + { + ID: "org.example:example-nested:3.3.3", + DependsOn: []string{ + "org.example:example-dependency:1.2.4", + }, + }, + }, + }, + { + name: "transitive dependencyManagement should not be inherited", + inputFile: filepath.Join("testdata", "transitive-dependency-management", "pom.xml"), + local: true, + want: []types.Library{ + // Managed dependencies (org.example:example-api:1.7.30) in org.example:example-dependency-management3 + // should not affect dependencies of example-dependency (org.example:example-api:2.0.0) + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Indirect: true, + }, + { + ID: "org.example:example-dependency-management3:1.1.1", + Name: "org.example:example-dependency-management3", + Version: "1.1.1", + Locations: types.Locations{ + { + StartLine: 14, + EndLine: 18, + }, + }, + }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Indirect: true, + }, + { + ID: "org.example:transitive-dependency-management:2.0.0", + Name: "org.example:transitive-dependency-management", + Version: "2.0.0", + }, + }, + wantDeps: []types.Dependency{ + { + ID: "org.example:example-dependency-management3:1.1.1", + DependsOn: []string{ + "org.example:example-dependency:1.2.3", + }, + }, + { + ID: "org.example:example-dependency:1.2.3", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, + { + ID: "org.example:transitive-dependency-management:2.0.0", + DependsOn: []string{ + "org.example:example-dependency-management3:1.1.1", + }, + }, + }, + }, + { + name: "parent not found", + inputFile: filepath.Join("testdata", "not-found-parent", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:no-parent:1.0-SNAPSHOT", + Name: "com.example:no-parent", + Version: "1.0-SNAPSHOT", + License: "Apache 2.0", + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 27, + EndLine: 31, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:no-parent:1.0-SNAPSHOT", + DependsOn: []string{ + "org.example:example-api:1.7.30", + }, + }, + }, + }, + { + name: "dependency not found", + inputFile: filepath.Join("testdata", "not-found-dependency", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:not-found-dependency:1.0.0", + Name: "com.example:not-found-dependency", + Version: "1.0.0", + License: "Apache 2.0", + }, + { + ID: "org.example:example-not-found:999", + Name: "org.example:example-not-found", + Version: "999", + Locations: types.Locations{ + { + StartLine: 21, + EndLine: 25, + }, + }, + }, + }, + wantDeps: []types.Dependency{ + { + ID: "com.example:not-found-dependency:1.0.0", + DependsOn: []string{ + "org.example:example-not-found:999", + }, + }, + }, + }, + { + name: "module not found - unable to parse module", + inputFile: filepath.Join("testdata", "not-found-module", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:aggregation:1.0.0", + Name: "com.example:aggregation", + Version: "1.0.0", + License: "Apache 2.0", + }, + }, + }, + { + name: "multiply licenses", + inputFile: filepath.Join("testdata", "multiply-licenses", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:multiply-licenses:1.0.0", + Name: "com.example:multiply-licenses", + Version: "1.0.0", + License: "MIT, Apache 2.0", + }, + }, + }, + { + name: "inherit parent license", + inputFile: filepath.Join("testdata", "inherit-license", "module", "submodule", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example.app:submodule:1.0.0", + Name: "com.example.app:submodule", + Version: "1.0.0", + License: "Apache-2.0", + }, + }, + }, + { + name: "compare ArtifactIDs for base and parent pom's", + inputFile: filepath.Join("testdata", "no-parent-infinity-loop", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:child:1.0.0", + Name: "com.example:child", + Version: "1.0.0", + License: "The Apache Software License, Version 2.0", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + var remoteRepos []string + if tt.local { + // for local repository + t.Setenv("MAVEN_HOME", "testdata/settings/global") + } else { + // for remote repository + h := http.FileServer(http.Dir(filepath.Join("testdata", "repository"))) + ts := httptest.NewServer(h) + remoteRepos = []string{ts.URL} + } + + p := pom.NewParser(tt.inputFile, pom.WithRemoteRepos(remoteRepos), pom.WithOffline(tt.offline)) + + gotLibs, gotDeps, err := p.Parse(f) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + + assert.Equal(t, tt.want, gotLibs) + assert.Equal(t, tt.wantDeps, gotDeps) + }) + } +} diff --git a/pkg/dependency/parser/java/pom/pom.go b/pkg/dependency/parser/java/pom/pom.go new file mode 100644 index 000000000000..8b610cc5925b --- /dev/null +++ b/pkg/dependency/parser/java/pom/pom.go @@ -0,0 +1,377 @@ +package pom + +import ( + "encoding/xml" + "fmt" + "io" + "maps" + "net/url" + "reflect" + "strings" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +type pom struct { + filePath string + content *pomXML +} + +func (p *pom) inherit(result analysisResult) { + // Merge properties + p.content.Properties = utils.MergeMaps(result.properties, p.content.Properties) + + art := p.artifact().Inherit(result.artifact) + + p.content.GroupId = art.GroupID + p.content.ArtifactId = art.ArtifactID + p.content.Licenses = art.ToPOMLicenses() + + if isProperty(art.Version.String()) { + p.content.Version = evaluateVariable(art.Version.String(), p.content.Properties, nil) + } else { + p.content.Version = art.Version.String() + } +} + +func (p pom) properties() properties { + props := p.content.Properties + return utils.MergeMaps(props, p.projectProperties()) +} + +func (p pom) projectProperties() map[string]string { + val := reflect.ValueOf(p.content).Elem() + props := p.listProperties(val) + + // "version" and "groupId" elements could be inherited from parent. + // https://maven.apache.org/pom.html#inheritance + props["groupId"] = p.content.GroupId + props["version"] = p.content.Version + + // https://maven.apache.org/pom.html#properties + projectProperties := make(map[string]string) + for k, v := range props { + if strings.HasPrefix(k, "project.") { + continue + } + + // e.g. ${project.groupId} + key := fmt.Sprintf("project.%s", k) + projectProperties[key] = v + + // It is deprecated, but still available. + // e.g. ${groupId} + projectProperties[k] = v + } + + return projectProperties +} + +func (p pom) listProperties(val reflect.Value) map[string]string { + props := make(map[string]string) + for i := 0; i < val.NumField(); i++ { + f := val.Type().Field(i) + + tag, ok := f.Tag.Lookup("xml") + if !ok || strings.Contains(tag, ",") { + // e.g. ",chardata" + continue + } + + switch f.Type.Kind() { + case reflect.Slice: + continue + case reflect.Map: + m := val.Field(i) + for _, e := range m.MapKeys() { + v := m.MapIndex(e) + props[e.String()] = v.String() + } + case reflect.Struct: + nestedProps := p.listProperties(val.Field(i)) + for k, v := range nestedProps { + key := fmt.Sprintf("%s.%s", tag, k) + props[key] = v + } + default: + props[tag] = val.Field(i).String() + } + } + return props +} + +func (p pom) artifact() artifact { + return newArtifact(p.content.GroupId, p.content.ArtifactId, p.content.Version, p.licenses(), p.content.Properties) +} + +func (p pom) licenses() []string { + return lo.FilterMap(p.content.Licenses.License, func(lic pomLicense, _ int) (string, bool) { + return lic.Name, lic.Name != "" + }) +} + +func (p pom) repositories(servers []Server) []string { + var urls []string + for _, rep := range p.content.Repositories.Repository { + // Add only enabled repositories + if rep.Releases.Enabled == "false" && rep.Snapshots.Enabled == "false" { + continue + } + + repoURL, err := url.Parse(rep.URL) + if err != nil { + log.Logger.Debugf("Unable to parse remote repository url: %s", err) + continue + } + + // Get the credentials from settings.xml based on matching server id + // with the repository id from pom.xml and use it for accessing the repository url + for _, server := range servers { + if rep.ID == server.ID && server.Username != "" && server.Password != "" { + repoURL.User = url.UserPassword(server.Username, server.Password) + break + } + } + + log.Logger.Debugf("Adding repository %s: %s", rep.ID, rep.URL) + urls = append(urls, repoURL.String()) + } + return urls +} + +type pomXML struct { + Parent pomParent `xml:"parent"` + GroupId string `xml:"groupId"` + ArtifactId string `xml:"artifactId"` + Version string `xml:"version"` + Licenses pomLicenses `xml:"licenses"` + Modules struct { + Text string `xml:",chardata"` + Module []string `xml:"module"` + } `xml:"modules"` + Properties properties `xml:"properties"` + DependencyManagement struct { + Text string `xml:",chardata"` + Dependencies pomDependencies `xml:"dependencies"` + } `xml:"dependencyManagement"` + Dependencies pomDependencies `xml:"dependencies"` + Repositories pomRepositories `xml:"repositories"` +} + +type pomParent struct { + GroupId string `xml:"groupId"` + ArtifactId string `xml:"artifactId"` + Version string `xml:"version"` + RelativePath string `xml:"relativePath"` +} + +type pomLicenses struct { + Text string `xml:",chardata"` + License []pomLicense `xml:"license"` +} + +type pomLicense struct { + Name string `xml:"name"` +} + +type pomDependencies struct { + Text string `xml:",chardata"` + Dependency []pomDependency `xml:"dependency"` +} + +type pomDependency struct { + Text string `xml:",chardata"` + GroupID string `xml:"groupId"` + ArtifactID string `xml:"artifactId"` + Version string `xml:"version"` + Scope string `xml:"scope"` + Optional bool `xml:"optional"` + Exclusions pomExclusions `xml:"exclusions"` + StartLine int + EndLine int +} + +type pomExclusions struct { + Text string `xml:",chardata"` + Exclusion []pomExclusion `xml:"exclusion"` +} + +// ref. https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html +type pomExclusion struct { + GroupID string `xml:"groupId"` + ArtifactID string `xml:"artifactId"` +} + +func (d pomDependency) Name() string { + return fmt.Sprintf("%s:%s", d.GroupID, d.ArtifactID) +} + +// Resolve evaluates variables in the dependency and inherit some fields from dependencyManagement to the dependency. +func (d pomDependency) Resolve(props map[string]string, depManagement, rootDepManagement []pomDependency) pomDependency { + // Evaluate variables + dep := pomDependency{ + Text: d.Text, + GroupID: evaluateVariable(d.GroupID, props, nil), + ArtifactID: evaluateVariable(d.ArtifactID, props, nil), + Version: evaluateVariable(d.Version, props, nil), + Scope: evaluateVariable(d.Scope, props, nil), + Optional: d.Optional, + Exclusions: d.Exclusions, + StartLine: d.StartLine, + EndLine: d.EndLine, + } + + // If this dependency is managed in the root POM, + // we need to overwrite fields according to the managed dependency. + if managed, found := findDep(d.Name(), rootDepManagement); found { // dependencyManagement from the root POM + if managed.Version != "" { + dep.Version = evaluateVariable(managed.Version, props, nil) + } + if managed.Scope != "" { + dep.Scope = evaluateVariable(managed.Scope, props, nil) + } + if managed.Optional { + dep.Optional = managed.Optional + } + if len(managed.Exclusions.Exclusion) != 0 { + dep.Exclusions = managed.Exclusions + } + return dep + } + + // Inherit version, scope and optional from dependencyManagement if empty + if managed, found := findDep(d.Name(), depManagement); found { // dependencyManagement from parent + if dep.Version == "" { + dep.Version = evaluateVariable(managed.Version, props, nil) + } + if dep.Scope == "" { + dep.Scope = evaluateVariable(managed.Scope, props, nil) + } + // TODO: need to check the behavior + if !dep.Optional { + dep.Optional = managed.Optional + } + if len(dep.Exclusions.Exclusion) == 0 { + dep.Exclusions = managed.Exclusions + } + } + return dep +} + +// ToArtifact converts dependency to artifact. +// It should be called after calling Resolve() so that variables can be evaluated. +func (d pomDependency) ToArtifact(opts analysisOptions) artifact { + // To avoid shadow adding exclusions to top pom's, + // we need to initialize a new map for each new artifact + // See `exclusions in child` test for more information + exclusions := make(map[string]struct{}) + if opts.exclusions != nil { + exclusions = maps.Clone(opts.exclusions) + } + for _, e := range d.Exclusions.Exclusion { + exclusions[fmt.Sprintf("%s:%s", e.GroupID, e.ArtifactID)] = struct{}{} + } + + var locations types.Locations + if opts.lineNumber { + locations = types.Locations{ + { + StartLine: d.StartLine, + EndLine: d.EndLine, + }, + } + } + + return artifact{ + GroupID: d.GroupID, + ArtifactID: d.ArtifactID, + Version: newVersion(d.Version), + Exclusions: exclusions, + Locations: locations, + } +} + +type properties map[string]string + +type property struct { + XMLName xml.Name + Value string `xml:",chardata"` +} + +func (props *properties) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error { + *props = properties{} + for { + var p property + err := d.Decode(&p) + if err == io.EOF { + break + } else if err != nil { + return xerrors.Errorf("XML decode error: %w", err) + } + + (*props)[p.XMLName.Local] = p.Value + } + return nil +} + +func (deps *pomDependencies) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error { + for { + token, err := d.Token() + if err == io.EOF { + break + } else if err != nil { + return xerrors.Errorf("XML decode error: %w", err) + } + + t, ok := token.(xml.StartElement) + if !ok { + continue + } + + if t.Name.Local == "dependency" { + var dep pomDependency + dep.StartLine, _ = d.InputPos() // tag starts + + // Decode the element + err = d.DecodeElement(&dep, &t) + if err != nil { + return xerrors.Errorf("Error decoding dependency: %w", err) + } + + dep.EndLine, _ = d.InputPos() // tag ends + deps.Dependency = append(deps.Dependency, dep) + } + } + return nil +} + +func findDep(name string, depManagement []pomDependency) (pomDependency, bool) { + return lo.Find(depManagement, func(item pomDependency) bool { + return item.Name() == name + }) +} + +type pomRepositories struct { + Text string `xml:",chardata"` + Repository []pomRepository `xml:"repository"` +} + +type pomRepository struct { + Text string `xml:",chardata"` + ID string `xml:"id"` + Name string `xml:"name"` + URL string `xml:"url"` + Releases struct { + Text string `xml:",chardata"` + Enabled string `xml:"enabled"` + } `xml:"releases"` + Snapshots struct { + Text string `xml:",chardata"` + Enabled string `xml:"enabled"` + } `xml:"snapshots"` +} diff --git a/pkg/dependency/parser/java/pom/queue.go b/pkg/dependency/parser/java/pom/queue.go new file mode 100644 index 000000000000..a28658564f74 --- /dev/null +++ b/pkg/dependency/parser/java/pom/queue.go @@ -0,0 +1,32 @@ +package pom + +import "sync" + +// artifactQueue the queue of Items +type artifactQueue struct { + items []artifact + lock sync.RWMutex +} + +func newArtifactQueue() *artifactQueue { + return &artifactQueue{} +} + +func (s *artifactQueue) enqueue(items ...artifact) { + s.lock.Lock() + s.items = append(s.items, items...) + s.lock.Unlock() +} + +func (s *artifactQueue) dequeue() artifact { + s.lock.Lock() + item := s.items[0] + s.items = s.items[1:] + s.lock.Unlock() + return item +} + +// IsEmpty returns true if the queue is empty +func (s *artifactQueue) IsEmpty() bool { + return len(s.items) == 0 +} diff --git a/pkg/dependency/parser/java/pom/settings.go b/pkg/dependency/parser/java/pom/settings.go new file mode 100644 index 000000000000..42153155706a --- /dev/null +++ b/pkg/dependency/parser/java/pom/settings.go @@ -0,0 +1,79 @@ +package pom + +import ( + "encoding/xml" + "os" + "path/filepath" + + "golang.org/x/net/html/charset" +) + +type Server struct { + ID string `xml:"id"` + Username string `xml:"username"` + Password string `xml:"password"` +} + +type settings struct { + LocalRepository string `xml:"localRepository"` + Servers []Server `xml:"servers>server"` +} + +// serverFound checks that servers already contain server. +// Maven compares servers by ID only. +func serverFound(servers []Server, id string) bool { + for _, server := range servers { + if server.ID == id { + return true + } + } + return false +} + +func readSettings() settings { + s := settings{} + + userSettingsPath := filepath.Join(os.Getenv("HOME"), ".m2", "settings.xml") + userSettings, err := openSettings(userSettingsPath) + if err == nil { + s = userSettings + } + + // Some package managers use this path by default + mavenHome := "/usr/share/maven" + if mHome := os.Getenv("MAVEN_HOME"); mHome != "" { + mavenHome = mHome + } + globalSettingsPath := filepath.Join(mavenHome, "conf", "settings.xml") + globalSettings, err := openSettings(globalSettingsPath) + if err == nil { + // We need to merge global and user settings. User settings being dominant. + // https://maven.apache.org/settings.html#quick-overview + if s.LocalRepository == "" { + s.LocalRepository = globalSettings.LocalRepository + } + // Maven checks user servers first, then global servers + for _, server := range globalSettings.Servers { + if !serverFound(s.Servers, server.ID) { + s.Servers = append(s.Servers, server) + } + } + } + + return s +} + +func openSettings(filePath string) (settings, error) { + f, err := os.Open(filePath) + if err != nil { + return settings{}, err + } + + s := settings{} + decoder := xml.NewDecoder(f) + decoder.CharsetReader = charset.NewReaderLabel + if err = decoder.Decode(&s); err != nil { + return settings{}, err + } + return s, nil +} diff --git a/pkg/dependency/parser/java/pom/settings_test.go b/pkg/dependency/parser/java/pom/settings_test.go new file mode 100644 index 000000000000..0d4941a18f97 --- /dev/null +++ b/pkg/dependency/parser/java/pom/settings_test.go @@ -0,0 +1,136 @@ +package pom + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_ReadSettings(t *testing.T) { + tests := []struct { + name string + envs map[string]string + wantSettings settings + }{ + { + name: "happy path with only global settings", + envs: map[string]string{ + "HOME": "", + "MAVEN_HOME": filepath.Join("testdata", "settings", "global"), + }, + wantSettings: settings{ + LocalRepository: "testdata/repository", + Servers: []Server{ + { + ID: "global-server", + }, + { + ID: "server-with-credentials", + Username: "test-user", + Password: "test-password-from-global", + }, + { + ID: "server-with-name-only", + Username: "test-user-only", + }, + }, + }, + }, + { + name: "happy path with only user settings", + envs: map[string]string{ + "HOME": filepath.Join("testdata", "settings", "user"), + "MAVEN_HOME": "NOT_EXISTING_PATH", + }, + wantSettings: settings{ + LocalRepository: "testdata/user/repository", + Servers: []Server{ + { + ID: "user-server", + }, + { + ID: "server-with-credentials", + Username: "test-user", + Password: "test-password", + }, + { + ID: "server-with-name-only", + Username: "test-user-only", + }, + }, + }, + }, + { + // $ mvn help:effective-settings + //[INFO] ------------------< org.apache.maven:standalone-pom >------------------- + //[INFO] --- maven-help-plugin:3.4.0:effective-settings (default-cli) @ standalone-pom --- + //Effective user-specific configuration settings: + // + // + // + // /root/testdata/user/repository + // + // + // user-server + // + // + // test-user + // *** + // server-with-credentials + // + // + // test-user-only + // server-with-name-only + // + // + // global-server + // + // + // + name: "happy path with global and user settings", + envs: map[string]string{ + "HOME": filepath.Join("testdata", "settings", "user"), + "MAVEN_HOME": filepath.Join("testdata", "settings", "global"), + }, + wantSettings: settings{ + LocalRepository: "testdata/user/repository", + Servers: []Server{ + { + ID: "user-server", + }, + { + ID: "server-with-credentials", + Username: "test-user", + Password: "test-password", + }, + { + ID: "server-with-name-only", + Username: "test-user-only", + }, + { + ID: "global-server", + }, + }, + }, + }, + { + name: "without settings", + envs: map[string]string{ + "HOME": "", + "MAVEN_HOME": "NOT_EXISTING_PATH", + }, + wantSettings: settings{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for env, settingsDir := range tt.envs { + t.Setenv(env, settingsDir) + } + + gotSettings := readSettings() + require.Equal(t, tt.wantSettings, gotSettings) + }) + } +} diff --git a/pkg/dependency/parser/java/pom/testdata/exclusions-in-child/pom.xml b/pkg/dependency/parser/java/pom/testdata/exclusions-in-child/pom.xml new file mode 100644 index 000000000000..5ff82cec31a8 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/exclusions-in-child/pom.xml @@ -0,0 +1,17 @@ + + 4.0.0 + + com.example + example + 1.0.0 + + + + org.example + example-exclusions + 4.0.0 + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/exclusions/pom.xml b/pkg/dependency/parser/java/pom/testdata/exclusions/pom.xml new file mode 100644 index 000000000000..54e1ff11b47f --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/exclusions/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + com.example + exclusions + 3.0.0 + + pom + exclusions + Exclusions + + + + org.example + example-nested + 3.3.3 + + + org.example + example-api + + + org.example + example-api-common + + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/happy/pom.xml b/pkg/dependency/parser/java/pom/testdata/happy/pom.xml new file mode 100644 index 000000000000..1f3c9697a17d --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/happy/pom.xml @@ -0,0 +1,50 @@ + + 4.0.0 + + com.example + happy + 1.0.0 + + happy + Example + + + + BSD-3-Clause + https://opensource.org/licenses/BSD-3-Clause + repo + + + + + + knqyf263 + https://github.com/knqyf263 + + + + + 1.7.30 + + + + + org.example + example-api + ${api.version} + + + org.example + example-runtime + 1.0.0 + runtime + + + org.example + example-provided + 999 + provided + + + diff --git a/pkg/dependency/parser/java/pom/testdata/hard-requirement/pom.xml b/pkg/dependency/parser/java/pom/testdata/hard-requirement/pom.xml new file mode 100644 index 000000000000..179cce4dde68 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/hard-requirement/pom.xml @@ -0,0 +1,39 @@ + + 4.0.0 + + com.example + hard + 1.0.0 + + soft + Example + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + knqyf263 + https://github.com/knqyf263 + + + + + + org.example + example-nested + 3.3.4 + + + org.example + example-dependency + 1.2.4 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/import-dependency-management-multiple/pom.xml b/pkg/dependency/parser/java/pom/testdata/import-dependency-management-multiple/pom.xml new file mode 100644 index 000000000000..c474b500cefa --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/import-dependency-management-multiple/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + + com.example + import + 2.0.0 + + pom + import + Import dependencyManagement + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + + org.example + example-dependency-management + 2.2.2 + import + pom + + + + org.example + example-dependency-management2 + 1.1.1 + import + pom + + + + + + + org.example + example-api + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/import-dependency-management/pom.xml b/pkg/dependency/parser/java/pom/testdata/import-dependency-management/pom.xml new file mode 100644 index 000000000000..f26a79f3678c --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/import-dependency-management/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + + com.example + import + 2.0.0 + + pom + import + Import dependencyManagement + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + + org.example + example-dependency-management + 3.3.3 + import + pom + + + + + + + org.example + example-api + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/inherit-license/module/pom.xml b/pkg/dependency/parser/java/pom/testdata/inherit-license/module/pom.xml new file mode 100644 index 000000000000..26acf4b82e92 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/inherit-license/module/pom.xml @@ -0,0 +1,10 @@ + + 4.0.0 + + com.example.app + my-app + 1.0.0 + + pom + module + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/inherit-license/module/submodule/pom.xml b/pkg/dependency/parser/java/pom/testdata/inherit-license/module/submodule/pom.xml new file mode 100644 index 000000000000..a904d152f110 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/inherit-license/module/submodule/pom.xml @@ -0,0 +1,10 @@ + + 4.0.0 + + com.example.app + module + 1.0.0 + + + submodule + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/inherit-license/pom.xml b/pkg/dependency/parser/java/pom/testdata/inherit-license/pom.xml new file mode 100644 index 000000000000..2c080d502ff7 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/inherit-license/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + pom + com.example.app + my-app + 1.0.0 + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0 + repo + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/inherit-props/base/pom.xml b/pkg/dependency/parser/java/pom/testdata/inherit-props/base/pom.xml new file mode 100644 index 000000000000..02422500889b --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/inherit-props/base/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + com.example + parent + 1.0.0 + + com.example + test + 0.0.1-SNAPSHOT + Spring Boot project + + 2.0.0 + + + + org.example + example-api + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/inherit-props/pom.xml b/pkg/dependency/parser/java/pom/testdata/inherit-props/pom.xml new file mode 100644 index 000000000000..32437873d2ae --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/inherit-props/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + + com.example + parent + 1.0.0 + + pom + parent + Parent + + + 1.0.0 + + + + + + org.example + example-bom + ${bom.version} + pom + import + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/modules-infinity-loop/module-1/module-2/pom.xml b/pkg/dependency/parser/java/pom/testdata/modules-infinity-loop/module-1/module-2/pom.xml new file mode 100644 index 000000000000..37f39009ce97 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/modules-infinity-loop/module-1/module-2/pom.xml @@ -0,0 +1,16 @@ + + 4.0.0 + + module-2 + org.example + 3.0.0 + + + + org.example + module-1 + 2.0.0 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/modules-infinity-loop/module-1/pom.xml b/pkg/dependency/parser/java/pom/testdata/modules-infinity-loop/module-1/pom.xml new file mode 100644 index 000000000000..9952a80dc685 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/modules-infinity-loop/module-1/pom.xml @@ -0,0 +1,12 @@ + + 4.0.0 + + module-1 + org.example + 2.0.0 + + + module-2 + + diff --git a/pkg/dependency/parser/java/pom/testdata/modules-infinity-loop/pom.xml b/pkg/dependency/parser/java/pom/testdata/modules-infinity-loop/pom.xml new file mode 100644 index 000000000000..372fefd3fce9 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/modules-infinity-loop/pom.xml @@ -0,0 +1,13 @@ + + 4.0.0 + + root + org.example + 1.0.0 + + + module-1 + module-2 + + diff --git a/pkg/dependency/parser/java/pom/testdata/multi-module-soft-requirement/module1/pom.xml b/pkg/dependency/parser/java/pom/testdata/multi-module-soft-requirement/module1/pom.xml new file mode 100644 index 000000000000..78ba97541db4 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/multi-module-soft-requirement/module1/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + com.example + module1 + 1.1.1 + + module1 + Module 1 + + + 1.7.30 + + + + + org.example + example-api + ${api.version} + + + diff --git a/pkg/dependency/parser/java/pom/testdata/multi-module-soft-requirement/module2/pom.xml b/pkg/dependency/parser/java/pom/testdata/multi-module-soft-requirement/module2/pom.xml new file mode 100644 index 000000000000..a2a1373f5fe8 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/multi-module-soft-requirement/module2/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + com.example + module2 + 1.1.1 + + module2 + Module 2 + + + 2.0.0 + + + + + org.example + example-api + ${api.version} + + + diff --git a/pkg/dependency/parser/java/pom/testdata/multi-module-soft-requirement/pom.xml b/pkg/dependency/parser/java/pom/testdata/multi-module-soft-requirement/pom.xml new file mode 100644 index 000000000000..c0b0789bfbed --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/multi-module-soft-requirement/pom.xml @@ -0,0 +1,18 @@ + + 4.0.0 + + com.example + aggregation + 1.0.0 + + pom + aggregation + Aggregation + + + module1 + module2 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/multi-module/module/pom.xml b/pkg/dependency/parser/java/pom/testdata/multi-module/module/pom.xml new file mode 100644 index 000000000000..ea72b3e29878 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/multi-module/module/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + com.example + module + 1.1.1 + + module + Module + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + org.example + example-dependency + 1.2.3 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/multi-module/pom.xml b/pkg/dependency/parser/java/pom/testdata/multi-module/pom.xml new file mode 100644 index 000000000000..398eb1f3850f --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/multi-module/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + com.example + aggregation + 1.0.0 + + pom + aggregation + Aggregation + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + module + + + diff --git a/pkg/dependency/parser/java/pom/testdata/multiply-licenses/pom.xml b/pkg/dependency/parser/java/pom/testdata/multiply-licenses/pom.xml new file mode 100644 index 000000000000..1f9609e670a9 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/multiply-licenses/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + com.example + multiply-licenses + 1.0.0 + + multiply-licenses + Example + + + + MIT + All source code is under the MIT license. + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + diff --git a/pkg/dependency/parser/java/pom/testdata/no-parent-infinity-loop/parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/no-parent-infinity-loop/parent/pom.xml new file mode 100644 index 000000000000..0af9c80ea494 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/no-parent-infinity-loop/parent/pom.xml @@ -0,0 +1,19 @@ + + 4.0.0 + + com.example + parent + 1.0.0 + + pom + parent + Parent + + + org.example + example-api + 1.7.30 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/no-parent-infinity-loop/pom.xml b/pkg/dependency/parser/java/pom/testdata/no-parent-infinity-loop/pom.xml new file mode 100644 index 000000000000..f2c9bfb7f078 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/no-parent-infinity-loop/pom.xml @@ -0,0 +1,17 @@ + + 4.0.0 + + child + + child + Child + + + com.example + parent + 1.0.0 + ./parent + + + diff --git a/pkg/dependency/parser/java/pom/testdata/not-found-dependency/pom.xml b/pkg/dependency/parser/java/pom/testdata/not-found-dependency/pom.xml new file mode 100644 index 000000000000..e1a6ff726502 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/not-found-dependency/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + com.example + not-found-dependency + 1.0.0 + + sad + Sad path + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + org.example + example-not-found + 999 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/not-found-module/pom.xml b/pkg/dependency/parser/java/pom/testdata/not-found-module/pom.xml new file mode 100644 index 000000000000..398eb1f3850f --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/not-found-module/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + com.example + aggregation + 1.0.0 + + pom + aggregation + Aggregation + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + module + + + diff --git a/pkg/dependency/parser/java/pom/testdata/not-found-parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/not-found-parent/pom.xml new file mode 100644 index 000000000000..bde412e53455 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/not-found-parent/pom.xml @@ -0,0 +1,33 @@ + + 4.0.0 + + com.example + no-parent + 1.0-SNAPSHOT + + no-parent + Parent not found + + + com.example + parent + 1.0.0 + + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + org.example + example-api + 1.7.30 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/offline/pom.xml b/pkg/dependency/parser/java/pom/testdata/offline/pom.xml new file mode 100644 index 000000000000..837d5837a884 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/offline/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + + child + + child + Child + + + com.example + parent + ${revision}-${changelist} + + + + + org.example + example-offline + 2.3.4 + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-child-properties/child/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-child-properties/child/pom.xml new file mode 100644 index 000000000000..455ce51acf35 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-child-properties/child/pom.xml @@ -0,0 +1,28 @@ + + 4.0.0 + + child + + child + Child + + + com.example + parent + 1.0.0 + ../parent + + + + 4.0.0 + + + + + org.example + example-dependency + 1.2.3 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-child-properties/parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-child-properties/parent/pom.xml new file mode 100644 index 000000000000..5ec3c450e1a2 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-child-properties/parent/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + + com.example + top-parent + 1.0.0 + ../top-parent + + + + com.example + parent + 1.0.0 + + pom + parent + Parent + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-child-properties/top-parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-child-properties/top-parent/pom.xml new file mode 100644 index 000000000000..e8208fb1bd9b --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-child-properties/top-parent/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + + com.example + top-parent + 1.0.0 + + pom + top-parent + Parent + + + 3.0.0 + + + + + + org.example + example-bom + ${bom.version} + pom + import + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-dependencies/child/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-dependencies/child/pom.xml new file mode 100644 index 000000000000..25a07f9f3644 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-dependencies/child/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + + child + + child + Child + + + com.example + parent + ${revision}-${changelist} + + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-dependencies/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-dependencies/pom.xml new file mode 100644 index 000000000000..2e7ebf581e66 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-dependencies/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + + com.example + parent + ${revision}${changelist} + + pom + parent + Parent + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + 1.0.0 + -SNAPSHOT + 1.7.30 + + + + + org.example + example-api + ${api.version} + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-dependency-management/child/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-dependency-management/child/pom.xml new file mode 100644 index 000000000000..cbb3ea6f83ef --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-dependency-management/child/pom.xml @@ -0,0 +1,32 @@ + + 4.0.0 + + child + 3.0.0 + + child + Child + + + com.example + parent + 2.0.0 + + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + org.example + example-api + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-dependency-management/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-dependency-management/pom.xml new file mode 100644 index 000000000000..a6fb717a8dbf --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-dependency-management/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + + com.example + parent + 2.0.0 + + pom + parent + Parent + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + 1.7.30 + + + + + + org.example + example-api + ${api.version} + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-properties/child/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-properties/child/pom.xml new file mode 100644 index 000000000000..145d77f5da14 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-properties/child/pom.xml @@ -0,0 +1,39 @@ + + 4.0.0 + + child + + child + Child + + + com.example + parent + 1.0.0 + + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + + + + org.example + example-api + ${api.version} + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-properties/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-properties/pom.xml new file mode 100644 index 000000000000..42ffe5a2a2be --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-properties/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + + com.example + parent + 1.0.0 + + pom + parent + Parent + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + knqyf263 + https://github.com/knqyf263 + + + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + + + 1.7.30 + + + + + org.example + example-api + 2.2.2 + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-relative-path/parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-relative-path/parent/pom.xml new file mode 100644 index 000000000000..e62b4cf06eb7 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-relative-path/parent/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + + com.example + parent + 1.0.0 + + pom + parent + Parent + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + knqyf263 + https://github.com/knqyf263 + + + + + 1.7.30 + + + + + org.example + example-api + ${api.version} + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-relative-path/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-relative-path/pom.xml new file mode 100644 index 000000000000..ac95e2178777 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-relative-path/pom.xml @@ -0,0 +1,32 @@ + + 4.0.0 + + child + + child + Child + + + com.example + parent + 1.0.0 + ./parent + + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + org.example + example-api + ${api.version} + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-remote-repository/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-remote-repository/pom.xml new file mode 100644 index 000000000000..18033a7bc7b9 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-remote-repository/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + child + + child + Child + + + org.example + example-parent + 1.0.0 + + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + org.example + example-api + 1.7.30 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-version-is-property/child/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-version-is-property/child/pom.xml new file mode 100644 index 000000000000..7581c228ef10 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-version-is-property/child/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + child + + child + ${revision} + Child + + + com.example + parent + ${revision} + ../pom.xml + + + + + org.example + example-api + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/parent-version-is-property/pom.xml b/pkg/dependency/parser/java/pom/testdata/parent-version-is-property/pom.xml new file mode 100644 index 000000000000..5848d053447f --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/parent-version-is-property/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + com.example + parent + ${revision} + + pom + parent + Parent + + + 1.0.0-SNAPSHOT + + + + + + org.example + example-api + 1.1.1 + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/project-version-from-parent/child/pom.xml b/pkg/dependency/parser/java/pom/testdata/project-version-from-parent/child/pom.xml new file mode 100644 index 000000000000..96ebac536822 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/project-version-from-parent/child/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + + child + + child + Child + + + com.example + parent + 2.0.0 + ../parent + + + + + org.example + example-api + ${project.version} + + + diff --git a/pkg/dependency/parser/java/pom/testdata/project-version-from-parent/parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/project-version-from-parent/parent/pom.xml new file mode 100644 index 000000000000..0b9266b87df0 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/project-version-from-parent/parent/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + + com.example + top-parent + 3.0.0 + ../top-parent + + + + com.example + parent + 2.0.0 + + pom + parent + Parent + + diff --git a/pkg/dependency/parser/java/pom/testdata/project-version-from-parent/top-parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/project-version-from-parent/top-parent/pom.xml new file mode 100644 index 000000000000..cd26c583bf09 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/project-version-from-parent/top-parent/pom.xml @@ -0,0 +1,13 @@ + + 4.0.0 + + com.example + top-parent + 3.0.0 + + pom + top-parent + Parent + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-api/1.7.30/example-api-1.7.30.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-api/1.7.30/example-api-1.7.30.pom new file mode 100644 index 000000000000..6d0cb6692de2 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-api/1.7.30/example-api-1.7.30.pom @@ -0,0 +1,23 @@ + + + + 4.0.0 + + org.example + example-api + 1.7.30 + + jar + Example API Module + The example API + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-api/2.0.0/example-api-2.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-api/2.0.0/example-api-2.0.0.pom new file mode 100644 index 000000000000..283d1e0fd4dd --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-api/2.0.0/example-api-2.0.0.pom @@ -0,0 +1,23 @@ + + + + 4.0.0 + + org.example + example-api + 2.0.0 + + jar + Example API Module + The example API + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/1.0.0/example-bom-1.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/1.0.0/example-bom-1.0.0.pom new file mode 100644 index 000000000000..8b11960b0e2a --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/1.0.0/example-bom-1.0.0.pom @@ -0,0 +1,23 @@ + + 4.0.0 + + org.example + example-bom + 1.0.0 + + pom + example-bom + Example Bom + + + + + org.example + example-api + 1.7.30 + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/2.0.0/example-bom-2.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/2.0.0/example-bom-2.0.0.pom new file mode 100644 index 000000000000..dfec17c9f838 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/2.0.0/example-bom-2.0.0.pom @@ -0,0 +1,23 @@ + + 4.0.0 + + org.example + example-bom + 2.0.0 + + pom + example-bom + Example Bom + + + + + org.example + example-api + 2.0.0 + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/3.0.0/example-bom-3.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/3.0.0/example-bom-3.0.0.pom new file mode 100644 index 000000000000..cd63e109b570 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/3.0.0/example-bom-3.0.0.pom @@ -0,0 +1,27 @@ + + 4.0.0 + + org.example + example-bom + 3.0.0 + + pom + example-bom + Example Bom + + + 3.0.0 + + + + + + org.example + example-api + ${api.version} + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/4.0.0/example-bom-4.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/4.0.0/example-bom-4.0.0.pom new file mode 100644 index 000000000000..1dda07117915 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-bom/4.0.0/example-bom-4.0.0.pom @@ -0,0 +1,27 @@ + + 4.0.0 + + org.example + example-bom + 4.0.0 + + pom + example-bom + Example Bom + + + 4.0.0 + + + + + + org.example + example-api + ${api.version} + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-child-parent/2.0.0/example-child-parent-2.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-child-parent/2.0.0/example-child-parent-2.0.0.pom new file mode 100644 index 000000000000..451b2e90eb89 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-child-parent/2.0.0/example-child-parent-2.0.0.pom @@ -0,0 +1,23 @@ + + 4.0.0 + + org.example + example-child-parent + 2.0.0 + + pom + example-child-parent + Child-Parent + + + + + org.example + example-api + 2.7.30 + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-child/2.0.0/example-child-2.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-child/2.0.0/example-child-2.0.0.pom new file mode 100644 index 000000000000..3be0f6d65d93 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-child/2.0.0/example-child-2.0.0.pom @@ -0,0 +1,34 @@ + + 4.0.0 + + org.example + example-child + 2.0.0 + + pom + example-child + example-child-parent + + + org.example + example-child-parent + 2.0.0 + + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + org.example + example-api + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management/2.2.2/example-dependency-management-2.2.2.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management/2.2.2/example-dependency-management-2.2.2.pom new file mode 100644 index 000000000000..e0d2488bfcdc --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management/2.2.2/example-dependency-management-2.2.2.pom @@ -0,0 +1,25 @@ + + + + 4.0.0 + + org.example + example-dependency-management + 2.2.2 + + pom + Example API Dependency Management + The example API + + + + + org.example + example-api + 1.7.30 + + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management/3.3.3/example-dependency-management-3.3.3.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management/3.3.3/example-dependency-management-3.3.3.pom new file mode 100644 index 000000000000..000e70f101e2 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management/3.3.3/example-dependency-management-3.3.3.pom @@ -0,0 +1,39 @@ + + + + 4.0.0 + + org.example + example-dependency-management + 3.3.3 + + pom + Example API Dependency Management + The example API + + + 1.1.1 + + + + + + org.example + example-dependency-management2 + ${project.managed.version} + import + pom + + + + + + + org.example + example-dependency + 1.2.3 + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management2/1.1.1/example-dependency-management2-1.1.1.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management2/1.1.1/example-dependency-management2-1.1.1.pom new file mode 100644 index 000000000000..58cdbf8cf7a6 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management2/1.1.1/example-dependency-management2-1.1.1.pom @@ -0,0 +1,29 @@ + + + + 4.0.0 + + org.example + example-dependency-management2 + 1.1.1 + + pom + Example API Dependency Management + The example API + + + 1.7.30 + + + + + + org.example + example-api + ${project.managed.version} + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management3/1.1.1/example-dependency-management3-1.1.1.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management3/1.1.1/example-dependency-management3-1.1.1.pom new file mode 100644 index 000000000000..fa82d974ea15 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency-management3/1.1.1/example-dependency-management3-1.1.1.pom @@ -0,0 +1,34 @@ + + + + 4.0.0 + + org.example + example-dependency-management3 + 1.1.1 + + pom + Example API Dependency Management + The example API + + + + + + org.example + example-api + 1.7.30 + + + + + + + org.example + example-dependency + 1.2.3 + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.3/example-dependency-1.2.3.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.3/example-dependency-1.2.3.pom new file mode 100644 index 000000000000..4eacd92abcd3 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.3/example-dependency-1.2.3.pom @@ -0,0 +1,23 @@ + + + + 4.0.0 + + org.example + example-dependency + 1.2.3 + + jar + Example API Dependency + The example API + + + + org.example + example-api + 2.0.0 + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.4/example-dependency-1.2.4.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.4/example-dependency-1.2.4.pom new file mode 100644 index 000000000000..17a8717d2cec --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.4/example-dependency-1.2.4.pom @@ -0,0 +1,23 @@ + + + + 4.0.0 + + org.example + example-dependency + 1.2.3 + + jar + Example API Dependency + The example API + + + + org.example + example-api + [2.0.0] + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency2/2.3.4/example-dependency2-2.3.4.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency2/2.3.4/example-dependency2-2.3.4.pom new file mode 100644 index 000000000000..e2b9395f05d2 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency2/2.3.4/example-dependency2-2.3.4.pom @@ -0,0 +1,23 @@ + + + + 4.0.0 + + org.example + example-dependency2 + 2.3.4 + + jar + Example API Dependency + The example API + + + + org.example + example-api + 1.7.30 + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-exclusions/4.0.0/example-exclusions-4.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-exclusions/4.0.0/example-exclusions-4.0.0.pom new file mode 100644 index 000000000000..7e02d67c6b1d --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-exclusions/4.0.0/example-exclusions-4.0.0.pom @@ -0,0 +1,28 @@ + + 4.0.0 + + org.example + example-exclusions + 4.0.0 + + + + org.example + example-dependency + 1.2.3 + + + org.example + example-api + + + + + org.example + example-api + 1.7.30 + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested/3.3.3/example-nested-3.3.3.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested/3.3.3/example-nested-3.3.3.pom new file mode 100644 index 000000000000..1d618e0ca020 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested/3.3.3/example-nested-3.3.3.pom @@ -0,0 +1,23 @@ + + + + 4.0.0 + + org.example + example-nested + 3.3.3 + + jar + Example API Dependency + The example API + + + + org.example + example-dependency + 1.2.3 + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested/3.3.4/example-nested-3.3.4.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested/3.3.4/example-nested-3.3.4.pom new file mode 100644 index 000000000000..691a8400c381 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested/3.3.4/example-nested-3.3.4.pom @@ -0,0 +1,23 @@ + + + + 4.0.0 + + org.example + example-nested + 3.3.4 + + jar + Example API Dependency + The example API + + + + org.example + example-dependency + [1.2.3] + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-parent/1.0.0/example-parent-1.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-parent/1.0.0/example-parent-1.0.0.pom new file mode 100644 index 000000000000..3a4d8749e168 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-parent/1.0.0/example-parent-1.0.0.pom @@ -0,0 +1,14 @@ + + + + 4.0.0 + + org.example + example-parent + 1.0.0 + + pom + Example API Parent + The example API + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/root-pom-dep-management/pom.xml b/pkg/dependency/parser/java/pom/testdata/root-pom-dep-management/pom.xml new file mode 100644 index 000000000000..c5cfa4e56871 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/root-pom-dep-management/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + com.example + root-pom-dep-management + 1.0.0 + + + + + org.example + example-dependency + 1.2.4 + + + + + + + org.example + example-nested + 3.3.3 + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/settings/global/conf/settings.xml b/pkg/dependency/parser/java/pom/testdata/settings/global/conf/settings.xml new file mode 100644 index 000000000000..791406988b03 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/settings/global/conf/settings.xml @@ -0,0 +1,21 @@ + + + testdata/repository + + + + global-server + + + server-with-credentials + test-user + test-password-from-global + + + server-with-name-only + test-user-only + + + diff --git a/pkg/dependency/parser/java/pom/testdata/settings/user/.m2/settings.xml b/pkg/dependency/parser/java/pom/testdata/settings/user/.m2/settings.xml new file mode 100755 index 000000000000..aa720b72c82d --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/settings/user/.m2/settings.xml @@ -0,0 +1,21 @@ + + + testdata/user/repository + + + + user-server + + + server-with-credentials + test-user + test-password + + + server-with-name-only + test-user-only + + + diff --git a/pkg/dependency/parser/java/pom/testdata/soft-requirement-with-transitive-dependencies/pom.xml b/pkg/dependency/parser/java/pom/testdata/soft-requirement-with-transitive-dependencies/pom.xml new file mode 100644 index 000000000000..76d62b2e8174 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/soft-requirement-with-transitive-dependencies/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + + com.example + soft-transitive + 1.0.0 + + soft-transitive + Example + + + + org.example + example-dependency + 1.2.3 + + + org.example + example-dependency2 + 2.3.4 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/soft-requirement/pom.xml b/pkg/dependency/parser/java/pom/testdata/soft-requirement/pom.xml new file mode 100644 index 000000000000..661b10b66657 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/soft-requirement/pom.xml @@ -0,0 +1,43 @@ + + 4.0.0 + + com.example + soft + 1.0.0 + + soft + Example + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + knqyf263 + https://github.com/knqyf263 + + + + + 1.7.30 + + + + + org.example + example-api + ${api.version} + + + org.example + example-dependency + 1.2.3 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/transitive-dependency-management/pom.xml b/pkg/dependency/parser/java/pom/testdata/transitive-dependency-management/pom.xml new file mode 100644 index 000000000000..4e3c0972b7fe --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/transitive-dependency-management/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + org.example + transitive-dependency-management + 2.0.0 + + jar + Example + Example + + + + org.example + example-dependency-management3 + 1.1.1 + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/transitive-parents/base/pom.xml b/pkg/dependency/parser/java/pom/testdata/transitive-parents/base/pom.xml new file mode 100644 index 000000000000..5662c0c9e8de --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/transitive-parents/base/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + + com.example + base + 4.0.0 + + base + Base + + + com.example + parent + 3.0.0 + ../parent + + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + org.example + example-child + 2.0.0 + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/transitive-parents/pom.xml b/pkg/dependency/parser/java/pom/testdata/transitive-parents/pom.xml new file mode 100644 index 000000000000..2c0de4a24541 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/transitive-parents/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + com.example + parent + 3.0.0 + + pom + parent + Parent + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + + org.example + example-api + 1.7.30 + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/version-requirement/pom.xml b/pkg/dependency/parser/java/pom/testdata/version-requirement/pom.xml new file mode 100644 index 000000000000..5ac8c1087670 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/version-requirement/pom.xml @@ -0,0 +1,34 @@ + + 4.0.0 + + com.example + hard + 1.0.0 + + soft + Example + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + knqyf263 + https://github.com/knqyf263 + + + + + + org.example + example-api + (,1.0] + + + diff --git a/pkg/dependency/parser/java/pom/testdata/wildcard-exclusions/pom.xml b/pkg/dependency/parser/java/pom/testdata/wildcard-exclusions/pom.xml new file mode 100644 index 000000000000..fc6447b57fa7 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/wildcard-exclusions/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + + com.example + wildcard-exclusions + 4.0.0 + + pom + wildcard-exclusions + Exclusions with wildcards + + + + org.example + example-dependency + 1.2.3 + + + * + example-api + + + + + org.example + example-dependency2 + 2.3.4 + + + * + * + + + + + org.example + example-nested + 3.3.3 + + + org.example + * + + + + + diff --git a/pkg/dependency/parser/java/pom/utils.go b/pkg/dependency/parser/java/pom/utils.go new file mode 100644 index 000000000000..fb42673515f2 --- /dev/null +++ b/pkg/dependency/parser/java/pom/utils.go @@ -0,0 +1,21 @@ +package pom + +import ( + "os" + "strings" +) + +func isDirectory(path string) (bool, error) { + fileInfo, err := os.Stat(path) + if err != nil { + return false, err + } + return fileInfo.IsDir(), err +} + +func isProperty(version string) bool { + if version != "" && strings.HasPrefix(version, "${") && strings.HasSuffix(version, "}") { + return true + } + return false +} diff --git a/pkg/dependency/parser/julia/manifest/naive_pkg_parser.go b/pkg/dependency/parser/julia/manifest/naive_pkg_parser.go new file mode 100644 index 000000000000..21f899ca177a --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/naive_pkg_parser.go @@ -0,0 +1,69 @@ +package julia + +import ( + "bufio" + "io" + "strings" +) + +type pkgPosition struct { + start int + end int +} +type minPkg struct { + uuid string + version string + position pkgPosition +} + +func (pkg *minPkg) setEndPositionIfEmpty(n int) { + if pkg.position.end == 0 { + pkg.position.end = n + } +} + +type naivePkgParser struct { + r io.Reader +} + +func (parser *naivePkgParser) parse() map[string]pkgPosition { + var currentPkg minPkg = minPkg{} + var idx = make(map[string]pkgPosition, 0) + + scanner := bufio.NewScanner(parser.r) + lineNum := 1 + for scanner.Scan() { + line := scanner.Text() + switch { + case strings.HasPrefix(strings.TrimSpace(line), "["): + if currentPkg.uuid != "" { + currentPkg.setEndPositionIfEmpty(lineNum - 1) + idx[currentPkg.uuid] = currentPkg.position + } + currentPkg = minPkg{} + currentPkg.position.start = lineNum + + case strings.HasPrefix(strings.TrimSpace(line), "uuid ="): + currentPkg.uuid = propertyValue(line) + case strings.HasPrefix(strings.TrimSpace(line), "version ="): + currentPkg.version = propertyValue(line) + case strings.TrimSpace(line) == "": + currentPkg.setEndPositionIfEmpty(lineNum - 1) + } + + lineNum++ + } + // add last item + if currentPkg.uuid != "" { + currentPkg.setEndPositionIfEmpty(lineNum - 1) + idx[currentPkg.uuid] = currentPkg.position + } + return idx +} +func propertyValue(line string) string { + parts := strings.Split(line, "=") + if len(parts) == 2 { + return strings.Trim(parts[1], ` "`) + } + return "" +} diff --git a/pkg/dependency/parser/julia/manifest/parse.go b/pkg/dependency/parser/julia/manifest/parse.go new file mode 100644 index 000000000000..685a54074629 --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/parse.go @@ -0,0 +1,166 @@ +package julia + +import ( + "io" + "sort" + + "github.com/BurntSushi/toml" + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type primitiveManifest struct { + JuliaVersion string `toml:"julia_version"` + ManifestFormat string `toml:"manifest_format"` + Dependencies map[string][]primitiveDependency `toml:"deps"` // e.g. [[deps.Foo]] +} + +type primitiveDependency struct { + Dependencies toml.Primitive `toml:"deps"` // by name. e.g. deps = ["Foo"] or [deps.Foo.deps] + UUID string `toml:"uuid"` + Version string `toml:"version"` // not specified for stdlib packages, which are of the Julia version + DependsOn []string `toml:"-"` // list of dependent UUID's. +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var oldDeps map[string][]primitiveDependency + var primMan primitiveManifest + var manMetadata toml.MetaData + decoder := toml.NewDecoder(r) + // Try to read the old Manifest format. If that fails, try the new format. + if _, err := decoder.Decode(&oldDeps); err != nil { + if _, err = r.Seek(0, io.SeekStart); err != nil { + return nil, nil, xerrors.Errorf("seek error: %w", err) + } + if manMetadata, err = decoder.Decode(&primMan); err != nil { + return nil, nil, xerrors.Errorf("decode error: %w", err) + } + } + + // We can't know the Julia version on an old manifest. + // All newer manifests include a manifest version and a julia version. + if primMan.ManifestFormat == "" { + primMan = primitiveManifest{ + JuliaVersion: "unknown", + Dependencies: oldDeps, + } + } + + man, err := decodeManifest(&primMan, &manMetadata) + if err != nil { + return nil, nil, xerrors.Errorf("unable to decode manifest dependencies: %w", err) + } + + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, nil, xerrors.Errorf("seek error: %w", err) + } + + // naive parser to get line numbers + pkgParser := naivePkgParser{r: r} + lineNumIdx := pkgParser.parse() + + var libs []types.Library + var deps []types.Dependency + for name, manifestDeps := range man.Dependencies { + for _, manifestDep := range manifestDeps { + version := depVersion(manifestDep.Version, man.JuliaVersion) + pkgID := manifestDep.UUID + lib := types.Library{ + ID: pkgID, + Name: name, + Version: version, + } + if pos, ok := lineNumIdx[manifestDep.UUID]; ok { + lib.Locations = []types.Location{ + { + StartLine: pos.start, + EndLine: pos.end, + }, + } + } + + libs = append(libs, lib) + + if len(manifestDep.DependsOn) > 0 { + deps = append(deps, types.Dependency{ + ID: pkgID, + DependsOn: manifestDep.DependsOn, + }) + } + } + } + sort.Sort(types.Libraries(libs)) + sort.Sort(types.Dependencies(deps)) + return libs, deps, nil +} + +// Returns the effective version of the `dep`. +// stdlib packages do not have a version in the manifest because they are packaged with julia itself +func depVersion(depVersion, juliaVersion string) string { + if depVersion == "" { + return juliaVersion + } + return depVersion +} + +// Decodes a primitive manifest using the metadata from parse time. +func decodeManifest(man *primitiveManifest, metadata *toml.MetaData) (*primitiveManifest, error) { + // Decode each dependency into the new manifest + for depName, primDeps := range man.Dependencies { + var newPrimDeps []primitiveDependency + for _, primDep := range primDeps { + newPrimDep, err := decodeDependency(man, primDep, metadata) + if err != nil { + return nil, err + } + newPrimDeps = append(newPrimDeps, newPrimDep) + } + man.Dependencies[depName] = newPrimDeps + } + + return man, nil +} + +// Decodes a primitive dependency using the metadata from parse time. +func decodeDependency(man *primitiveManifest, dep primitiveDependency, metadata *toml.MetaData) (primitiveDependency, error) { + // Try to decode as []string first where the manifest looks like deps = ["A", "B"] + var possibleDeps []string + err := metadata.PrimitiveDecode(dep.Dependencies, &possibleDeps) + if err == nil { + var possibleUuids []string + for _, depName := range possibleDeps { + primDep := man.Dependencies[depName] + if len(primDep) > 1 { + return primitiveDependency{}, xerrors.Errorf("Dependency %q has invalid format (parsed multiple deps): %s", depName, primDep) + } + possibleUuids = append(possibleUuids, primDep[0].UUID) + } + sort.Strings(possibleUuids) + dep.DependsOn = possibleUuids + return dep, nil + } + + // The other possibility is a map where the manifest looks like + // [deps.A.deps] + // B = "..." + var possibleDepsMap map[string]string + err = metadata.PrimitiveDecode(dep.Dependencies, &possibleDepsMap) + if err == nil { + possibleUuids := maps.Values(possibleDepsMap) + sort.Strings(possibleUuids) + dep.DependsOn = possibleUuids + return dep, nil + } + + // We don't know what the shape of the data is -- i.e. an invalid manifest + return primitiveDependency{}, err +} diff --git a/pkg/dependency/parser/julia/manifest/parse_test.go b/pkg/dependency/parser/julia/manifest/parse_test.go new file mode 100644 index 000000000000..6eacf5a76133 --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/parse_test.go @@ -0,0 +1,75 @@ +package julia + +import ( + "os" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + file string // Test input file + want []types.Library + wantDeps []types.Dependency + }{ + { + name: "Manifest v1.6", + file: "testdata/primary/Manifest_v1.6.toml", + want: juliaV1_6Libs, + wantDeps: juliaV1_6Deps, + }, + { + name: "Manifest v1.8", + file: "testdata/primary/Manifest_v1.8.toml", + want: juliaV1_8Libs, + wantDeps: juliaV1_8Deps, + }, + { + name: "no deps v1.6", + file: "testdata/no_deps_v1.6/Manifest.toml", + want: nil, + wantDeps: nil, + }, + { + name: "no deps v1.9", + file: "testdata/no_deps_v1.9/Manifest.toml", + want: nil, + wantDeps: nil, + }, + { + name: "dep extensions v1.9", + file: "testdata/dep_ext_v1.9/Manifest.toml", + want: juliaV1_9DepExtLibs, + wantDeps: nil, + }, + { + name: "shadowed dep v1.9", + file: "testdata/shadowed_dep_v1.9/Manifest.toml", + want: juliaV1_9ShadowedDepLibs, + wantDeps: juliaV1_9ShadowedDepDeps, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + + gotLibs, gotDeps, err := NewParser().Parse(f) + require.NoError(t, err) + + sort.Sort(types.Libraries(tt.want)) + assert.Equal(t, tt.want, gotLibs) + if tt.wantDeps != nil { + sort.Sort(types.Dependencies(tt.wantDeps)) + assert.Equal(t, tt.wantDeps, gotDeps) + } + }) + } +} diff --git a/pkg/dependency/parser/julia/manifest/parse_testcase.go b/pkg/dependency/parser/julia/manifest/parse_testcase.go new file mode 100644 index 000000000000..3055cc8d15a8 --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/parse_testcase.go @@ -0,0 +1,77 @@ +package julia + +import "github.com/aquasecurity/trivy/pkg/dependency/types" + +var ( + juliaV1_6Libs = []types.Library{ + {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "unknown", Locations: []types.Location{{StartLine: 3, EndLine: 5}}}, + {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []types.Location{{StartLine: 7, EndLine: 11}}}, + {ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "unknown", Locations: []types.Location{{StartLine: 13, EndLine: 14}}}, + {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.4.2", Locations: []types.Location{{StartLine: 16, EndLine: 20}}}, + {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "unknown", Locations: []types.Location{{StartLine: 22, EndLine: 24}}}, + {ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "unknown", Locations: []types.Location{{StartLine: 26, EndLine: 27}}}, + } + + juliaV1_6Deps = []types.Dependency{ + {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}}, + {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", DependsOn: []string{ + "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", + "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", + "a63ad114-7e13-5084-954f-fe012c677804", + "ade2ca70-3891-5945-98fb-dc099432e06a", + }}, + {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", DependsOn: []string{"ade2ca70-3891-5945-98fb-dc099432e06a"}}, + {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", DependsOn: []string{"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"}}, + } + + juliaV1_8Libs = []types.Library{ + {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "1.8.5", Locations: []types.Location{{StartLine: 7, EndLine: 9}}}, + {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []types.Location{{StartLine: 11, EndLine: 15}}}, + {ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "1.8.5", Locations: []types.Location{{StartLine: 17, EndLine: 18}}}, + {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.5.10", Locations: []types.Location{{StartLine: 20, EndLine: 24}}}, + {ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a", Name: "PrecompileTools", Version: "1.1.1", Locations: []types.Location{{StartLine: 26, EndLine: 30}}}, + {ID: "21216c6a-2e73-6563-6e65-726566657250", Name: "Preferences", Version: "1.4.0", Locations: []types.Location{{StartLine: 32, EndLine: 36}}}, + {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "1.8.5", Locations: []types.Location{{StartLine: 38, EndLine: 40}}}, + {ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c", Name: "Random", Version: "1.8.5", Locations: []types.Location{{StartLine: 42, EndLine: 44}}}, + {ID: "ea8e919c-243c-51af-8825-aaa63cd721ce", Name: "SHA", Version: "0.7.0", Locations: []types.Location{{StartLine: 46, EndLine: 48}}}, + {ID: "9e88b42a-f829-5b0c-bbe9-9e923198166b", Name: "Serialization", Version: "1.8.5", Locations: []types.Location{{StartLine: 50, EndLine: 51}}}, + {ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76", Name: "TOML", Version: "1.0.0", Locations: []types.Location{{StartLine: 53, EndLine: 56}}}, + {ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", Name: "UUIDs", Version: "1.8.5", Locations: []types.Location{{StartLine: 58, EndLine: 60}}}, + {ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "1.8.5", Locations: []types.Location{{StartLine: 62, EndLine: 63}}}, + } + + juliaV1_8Deps = []types.Dependency{ + {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}}, + {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", DependsOn: []string{ + "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", + "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", + "a63ad114-7e13-5084-954f-fe012c677804", + "ade2ca70-3891-5945-98fb-dc099432e06a", + }}, + {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", DependsOn: []string{ + "ade2ca70-3891-5945-98fb-dc099432e06a", + "aea7be01-6a6a-4083-8856-8a6e6704d82a", + "cf7118a7-6976-5b1a-9a39-7adc72f591a4", + }}, + {ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a", DependsOn: []string{"21216c6a-2e73-6563-6e65-726566657250"}}, + {ID: "21216c6a-2e73-6563-6e65-726566657250", DependsOn: []string{"fa267f1f-6049-4f14-aa54-33bafae1ed76"}}, + {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", DependsOn: []string{"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"}}, + {ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c", DependsOn: []string{"9e88b42a-f829-5b0c-bbe9-9e923198166b", "ea8e919c-243c-51af-8825-aaa63cd721ce"}}, + {ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76", DependsOn: []string{"ade2ca70-3891-5945-98fb-dc099432e06a"}}, + {ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", DependsOn: []string{"9a3f8284-a2c9-5f02-9a11-845980a1fd5c", "ea8e919c-243c-51af-8825-aaa63cd721ce"}}, + } + + juliaV1_9DepExtLibs = []types.Library{ + {ID: "621f4979-c628-5d54-868e-fcf4e3e8185c", Name: "AbstractFFTs", Version: "1.3.1", Locations: []types.Location{{StartLine: 7, EndLine: 10}}}, + } + + juliaV1_9ShadowedDepLibs = []types.Library{ + {ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", Name: "A", Version: "1.9.0", Locations: []types.Location{{StartLine: 7, EndLine: 8}}}, + {ID: "f41f7b98-334e-11e9-1257-49272045fb24", Name: "B", Version: "1.9.0", Locations: []types.Location{{StartLine: 13, EndLine: 14}}}, + {ID: "edca9bc6-334e-11e9-3554-9595dbb4349c", Name: "B", Version: "1.9.0", Locations: []types.Location{{StartLine: 15, EndLine: 16}}}, + } + + juliaV1_9ShadowedDepDeps = []types.Dependency{ + {ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", DependsOn: []string{"f41f7b98-334e-11e9-1257-49272045fb24"}}, + } +) diff --git a/pkg/dependency/parser/julia/manifest/testdata/dep_ext_v1.9/Manifest.toml b/pkg/dependency/parser/julia/manifest/testdata/dep_ext_v1.9/Manifest.toml new file mode 100644 index 000000000000..c3b22a724c15 --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/testdata/dep_ext_v1.9/Manifest.toml @@ -0,0 +1,16 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc" + +[[deps.AbstractFFTs]] +git-tree-sha1 = "16b6dbc4cf7caee4e1e75c49485ec67b667098a0" +uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c" +version = "1.3.1" + + [deps.AbstractFFTs.extensions] + AbstractFFTsChainRulesCoreExt = "ChainRulesCore" + + [deps.AbstractFFTs.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" diff --git a/pkg/dependency/parser/julia/manifest/testdata/no_deps_v1.6/Manifest.toml b/pkg/dependency/parser/julia/manifest/testdata/no_deps_v1.6/Manifest.toml new file mode 100644 index 000000000000..f45eecff031f --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/testdata/no_deps_v1.6/Manifest.toml @@ -0,0 +1,2 @@ +# This file is machine-generated - editing it directly is not advised + diff --git a/pkg/dependency/parser/julia/manifest/testdata/no_deps_v1.9/Manifest.toml b/pkg/dependency/parser/julia/manifest/testdata/no_deps_v1.9/Manifest.toml new file mode 100644 index 000000000000..9ed5ab94bdf9 --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/testdata/no_deps_v1.9/Manifest.toml @@ -0,0 +1,7 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "da39a3ee5e6b4b0d3255bfef95601890afd80709" + +[deps] diff --git a/pkg/dependency/parser/julia/manifest/testdata/primary/Manifest_v1.6.toml b/pkg/dependency/parser/julia/manifest/testdata/primary/Manifest_v1.6.toml new file mode 100644 index 000000000000..6883d0041576 --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/testdata/primary/Manifest_v1.6.toml @@ -0,0 +1,27 @@ +# This file is machine-generated - editing it directly is not advised + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[Parsers]] +deps = ["Dates"] +git-tree-sha1 = "6c01a9b494f6d2a9fc180a08b182fcb06f0958a0" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.4.2" + +[[Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/pkg/dependency/parser/julia/manifest/testdata/primary/Manifest_v1.8.toml b/pkg/dependency/parser/julia/manifest/testdata/primary/Manifest_v1.8.toml new file mode 100644 index 000000000000..6b7c988ca956 --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/testdata/primary/Manifest_v1.8.toml @@ -0,0 +1,63 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.8.5" +manifest_format = "2.0" +project_hash = "f65b9de676a27ce78ee011db6d477b3d44d1a7c5" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "a5aef8d4a6e8d81f171b2bd4be5265b01384c74c" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.5.10" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "259e206946c293698122f63e2b513a7c99a244e8" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.1.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "7eb1686b4f04b82f96ed7a4ea5890a4f0c7a09f1" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.0" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.Random]] +deps = ["SHA", "Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.0" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/pkg/dependency/parser/julia/manifest/testdata/shadowed_dep_v1.9/Manifest.toml b/pkg/dependency/parser/julia/manifest/testdata/shadowed_dep_v1.9/Manifest.toml new file mode 100644 index 000000000000..dd4ea00b943d --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/testdata/shadowed_dep_v1.9/Manifest.toml @@ -0,0 +1,16 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc" + +[[deps.A]] +uuid = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60" + + [deps.A.deps] + B = "f41f7b98-334e-11e9-1257-49272045fb24" + +[[deps.B]] +uuid = "f41f7b98-334e-11e9-1257-49272045fb24" +[[deps.B]] +uuid = "edca9bc6-334e-11e9-3554-9595dbb4349c" diff --git a/pkg/dependency/parser/nodejs/npm/parse.go b/pkg/dependency/parser/nodejs/npm/parse.go new file mode 100644 index 000000000000..b74cfa5ce2f5 --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/parse.go @@ -0,0 +1,411 @@ +package npm + +import ( + "fmt" + "io" + "path" + "slices" + "sort" + "strings" + + "github.com/liamg/jfather" + "github.com/samber/lo" + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +const nodeModulesDir = "node_modules" + +type LockFile struct { + Dependencies map[string]Dependency `json:"dependencies"` + Packages map[string]Package `json:"packages"` + LockfileVersion int `json:"lockfileVersion"` +} +type Dependency struct { + Version string `json:"version"` + Dev bool `json:"dev"` + Dependencies map[string]Dependency `json:"dependencies"` + Requires map[string]string `json:"requires"` + Resolved string `json:"resolved"` + StartLine int + EndLine int +} + +type Package struct { + Name string `json:"name"` + Version string `json:"version"` + Dependencies map[string]string `json:"dependencies"` + OptionalDependencies map[string]string `json:"optionalDependencies"` + DevDependencies map[string]string `json:"devDependencies"` + Resolved string `json:"resolved"` + Dev bool `json:"dev"` + Link bool `json:"link"` + Workspaces []string `json:"workspaces"` + StartLine int + EndLine int +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var lockFile LockFile + input, err := io.ReadAll(r) + if err != nil { + return nil, nil, xerrors.Errorf("read error: %w", err) + } + if err := jfather.Unmarshal(input, &lockFile); err != nil { + return nil, nil, xerrors.Errorf("decode error: %w", err) + } + + var libs []types.Library + var deps []types.Dependency + if lockFile.LockfileVersion == 1 { + libs, deps = p.parseV1(lockFile.Dependencies, make(map[string]string)) + } else { + libs, deps = p.parseV2(lockFile.Packages) + } + + return utils.UniqueLibraries(libs), uniqueDeps(deps), nil +} + +func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types.Dependency) { + libs := make(map[string]types.Library, len(packages)-1) + var deps []types.Dependency + + // Resolve links first + // https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#packages + resolveLinks(packages) + + directDeps := make(map[string]struct{}) + for name, version := range lo.Assign(packages[""].Dependencies, packages[""].OptionalDependencies, packages[""].DevDependencies) { + pkgPath := joinPaths(nodeModulesDir, name) + if _, ok := packages[pkgPath]; !ok { + log.Logger.Debugf("Unable to find the direct dependency: '%s@%s'", name, version) + continue + } + // Store the package paths of direct dependencies + // e.g. node_modules/body-parser + directDeps[pkgPath] = struct{}{} + } + + for pkgPath, pkg := range packages { + if !strings.HasPrefix(pkgPath, "node_modules") { + continue + } + + // pkg.Name exists when package name != folder name + pkgName := pkg.Name + if pkgName == "" { + pkgName = pkgNameFromPath(pkgPath) + } + + pkgID := packageID(pkgName, pkg.Version) + location := types.Location{ + StartLine: pkg.StartLine, + EndLine: pkg.EndLine, + } + + var ref types.ExternalRef + if pkg.Resolved != "" { + ref = types.ExternalRef{ + Type: types.RefOther, + URL: pkg.Resolved, + } + } + + pkgIndirect := isIndirectLib(pkgPath, directDeps) + + // There are cases when similar libraries use same dependencies + // we need to add location for each these dependencies + if savedLib, ok := libs[pkgID]; ok { + savedLib.Dev = savedLib.Dev && pkg.Dev + savedLib.Indirect = savedLib.Indirect && pkgIndirect + + if ref.URL != "" && !slices.Contains(savedLib.ExternalReferences, ref) { + savedLib.ExternalReferences = append(savedLib.ExternalReferences, ref) + sortExternalReferences(savedLib.ExternalReferences) + } + + savedLib.Locations = append(savedLib.Locations, location) + sort.Sort(savedLib.Locations) + + libs[pkgID] = savedLib + continue + } + + lib := types.Library{ + ID: pkgID, + Name: pkgName, + Version: pkg.Version, + Indirect: pkgIndirect, + Dev: pkg.Dev, + ExternalReferences: lo.Ternary(ref.URL != "", []types.ExternalRef{ref}, nil), + Locations: []types.Location{location}, + } + libs[pkgID] = lib + + // npm builds graph using optional deps. e.g.: + // └─┬ watchpack@1.7.5 + // ├─┬ chokidar@3.5.3 - optional dependency + // │ └── glob-parent@5.1. + dependencies := lo.Assign(pkg.Dependencies, pkg.OptionalDependencies) + dependsOn := make([]string, 0, len(dependencies)) + for depName, depVersion := range dependencies { + depID, err := findDependsOn(pkgPath, depName, packages) + if err != nil { + log.Logger.Warnf("Cannot resolve the version: '%s@%s'", depName, depVersion) + continue + } + dependsOn = append(dependsOn, depID) + } + + if len(dependsOn) > 0 { + deps = append(deps, types.Dependency{ + ID: lib.ID, + DependsOn: dependsOn, + }) + } + + } + + return maps.Values(libs), deps +} + +// for local package npm uses links. e.g.: +// function/func1 -> target of package +// node_modules/func1 -> link to target +// see `package-lock_v3_with_workspace.json` to better understanding +func resolveLinks(packages map[string]Package) { + links := lo.PickBy(packages, func(_ string, pkg Package) bool { + return pkg.Link + }) + // Early return + if len(links) == 0 { + return + } + + rootPkg := packages[""] + if rootPkg.Dependencies == nil { + rootPkg.Dependencies = make(map[string]string) + } + + workspaces := rootPkg.Workspaces + for pkgPath, pkg := range packages { + for linkPath, link := range links { + if !strings.HasPrefix(pkgPath, link.Resolved) { + continue + } + // The target doesn't have the "resolved" field, so we need to copy it from the link. + if pkg.Resolved == "" { + pkg.Resolved = link.Resolved + } + + // Resolve the link package so all packages are located under "node_modules". + resolvedPath := strings.ReplaceAll(pkgPath, link.Resolved, linkPath) + packages[resolvedPath] = pkg + + // Delete the target package + delete(packages, pkgPath) + + if isWorkspace(pkgPath, workspaces) { + rootPkg.Dependencies[pkgNameFromPath(linkPath)] = pkg.Version + } + break + } + } + packages[""] = rootPkg +} + +func isWorkspace(pkgPath string, workspaces []string) bool { + for _, workspace := range workspaces { + if match, err := path.Match(workspace, pkgPath); err != nil { + log.Logger.Debugf("unable to parse workspace %q for %s", workspace, pkgPath) + } else if match { + return true + } + } + return false +} + +func findDependsOn(pkgPath, depName string, packages map[string]Package) (string, error) { + depPath := joinPaths(pkgPath, nodeModulesDir) + paths := strings.Split(depPath, "/") + // Try to resolve the version with the nearest directory + // e.g. for pkgPath == `node_modules/body-parser/node_modules/debug`, depName == `ms`: + // - "node_modules/body-parser/node_modules/debug/node_modules/ms" + // - "node_modules/body-parser/node_modules/ms" + // - "node_modules/ms" + for i := len(paths) - 1; i >= 0; i-- { + if paths[i] != nodeModulesDir { + continue + } + modulePath := joinPaths(paths[:i+1]...) + modulePath = joinPaths(modulePath, depName) + + if dep, ok := packages[modulePath]; ok { + return packageID(depName, dep.Version), nil + } + } + + // It should not reach here. + return "", xerrors.Errorf("can't find dependsOn for %s", depName) +} + +func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string]string) ([]types.Library, []types.Dependency) { + // Update package name and version mapping. + for pkgName, dep := range dependencies { + // Overwrite the existing package version so that the nested version can take precedence. + versions[pkgName] = dep.Version + } + + var libs []types.Library + var deps []types.Dependency + for pkgName, dep := range dependencies { + lib := types.Library{ + ID: packageID(pkgName, dep.Version), + Name: pkgName, + Version: dep.Version, + Dev: dep.Dev, + Indirect: true, // lockfile v1 schema doesn't have information about Direct dependencies + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: dep.Resolved, + }, + }, + Locations: []types.Location{ + { + StartLine: dep.StartLine, + EndLine: dep.EndLine, + }, + }, + } + libs = append(libs, lib) + + dependsOn := make([]string, 0, len(dep.Requires)) + for libName, requiredVer := range dep.Requires { + // Try to resolve the version with nested dependencies first + if resolvedDep, ok := dep.Dependencies[libName]; ok { + libID := packageID(libName, resolvedDep.Version) + dependsOn = append(dependsOn, libID) + continue + } + + // Try to resolve the version with the higher level dependencies + if ver, ok := versions[libName]; ok { + dependsOn = append(dependsOn, packageID(libName, ver)) + continue + } + + // It should not reach here. + log.Logger.Warnf("Cannot resolve the version: %s@%s", libName, requiredVer) + } + + if len(dependsOn) > 0 { + deps = append(deps, types.Dependency{ + ID: packageID(lib.Name, lib.Version), + DependsOn: dependsOn, + }) + } + + if dep.Dependencies != nil { + // Recursion + childLibs, childDeps := p.parseV1(dep.Dependencies, maps.Clone(versions)) + libs = append(libs, childLibs...) + deps = append(deps, childDeps...) + } + } + + return libs, deps +} + +func uniqueDeps(deps []types.Dependency) []types.Dependency { + var uniqDeps []types.Dependency + unique := make(map[string]struct{}) + + for _, dep := range deps { + sort.Strings(dep.DependsOn) + depKey := fmt.Sprintf("%s:%s", dep.ID, strings.Join(dep.DependsOn, ",")) + if _, ok := unique[depKey]; !ok { + unique[depKey] = struct{}{} + uniqDeps = append(uniqDeps, dep) + } + } + + sort.Sort(types.Dependencies(uniqDeps)) + return uniqDeps +} + +func isIndirectLib(pkgPath string, directDeps map[string]struct{}) bool { + // A project can contain 2 different versions of the same dependency. + // e.g. `node_modules/string-width/node_modules/strip-ansi` and `node_modules/string-ansi` + // direct dependencies always have root path (`node_modules/`) + if _, ok := directDeps[pkgPath]; ok { + return false + } + return true +} + +func pkgNameFromPath(pkgPath string) string { + // lock file contains path to dependency in `node_modules`. e.g.: + // node_modules/string-width + // node_modules/string-width/node_modules/strip-ansi + // we renamed to `node_modules` directory prefixes `workspace` when resolving Links + // node_modules/function1 + // node_modules/nested_func/node_modules/debug + if index := strings.LastIndex(pkgPath, nodeModulesDir); index != -1 { + return pkgPath[index+len(nodeModulesDir)+1:] + } + log.Logger.Warnf("npm %q package path doesn't have `node_modules` prefix", pkgPath) + return pkgPath +} + +func joinPaths(paths ...string) string { + return strings.Join(paths, "/") +} + +// UnmarshalJSONWithMetadata needed to detect start and end lines of deps for v1 +func (t *Dependency) UnmarshalJSONWithMetadata(node jfather.Node) error { + if err := node.Decode(&t); err != nil { + return err + } + // Decode func will overwrite line numbers if we save them first + t.StartLine = node.Range().Start.Line + t.EndLine = node.Range().End.Line + return nil +} + +// UnmarshalJSONWithMetadata needed to detect start and end lines of deps for v2 or newer +func (t *Package) UnmarshalJSONWithMetadata(node jfather.Node) error { + if err := node.Decode(&t); err != nil { + return err + } + // Decode func will overwrite line numbers if we save them first + t.StartLine = node.Range().Start.Line + t.EndLine = node.Range().End.Line + return nil +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.Npm, name, version) +} + +func sortExternalReferences(refs []types.ExternalRef) { + sort.Slice(refs, func(i, j int) bool { + if refs[i].Type != refs[j].Type { + return refs[i].Type < refs[j].Type + } + return refs[i].URL < refs[j].URL + }) +} diff --git a/pkg/dependency/parser/nodejs/npm/parse_test.go b/pkg/dependency/parser/nodejs/npm/parse_test.go new file mode 100644 index 000000000000..786fe643dfde --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/parse_test.go @@ -0,0 +1,72 @@ +package npm + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + file string // Test input file + want []types.Library + wantDeps []types.Dependency + }{ + { + name: "lock version v1", + file: "testdata/package-lock_v1.json", + want: npmV1Libs, + wantDeps: npmDeps, + }, + { + name: "lock version v2", + file: "testdata/package-lock_v2.json", + want: npmV2Libs, + wantDeps: npmDeps, + }, + { + name: "lock version v3", + file: "testdata/package-lock_v3.json", + want: npmV2Libs, + wantDeps: npmDeps, + }, + { + name: "lock version v3 with workspace", + file: "testdata/package-lock_v3_with_workspace.json", + want: npmV3WithWorkspaceLibs, + wantDeps: npmV3WithWorkspaceDeps, + }, + { + name: "lock file v3 contains same dev and non-dev dependencies", + file: "testdata/package-lock_v3_with-same-dev-and-non-dev.json", + want: npmV3WithSameDevAndNonDevLibs, + wantDeps: npmV3WithSameDevAndNonDevDeps, + }, + { + name: "lock version v3 with workspace and without direct deps field", + file: "testdata/package-lock_v3_without_root_deps_field.json", + want: npmV3WithoutRootDepsField, + wantDeps: npmV3WithoutRootDepsFieldDeps, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + + got, deps, err := NewParser().Parse(f) + require.NoError(t, err) + + assert.Equal(t, tt.want, got) + if tt.wantDeps != nil { + assert.Equal(t, tt.wantDeps, deps) + } + }) + } +} diff --git a/pkg/dependency/parser/nodejs/npm/parse_testcase.go b/pkg/dependency/parser/nodejs/npm/parse_testcase.go new file mode 100644 index 000000000000..29b9e63d8f1c --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/parse_testcase.go @@ -0,0 +1,1604 @@ +package npm + +import "github.com/aquasecurity/trivy/pkg/dependency/types" + +var ( + // docker run --name node --rm -it node@sha256:51dd437f31812df71108b81385e2945071ec813d5815fa3403855669c8f3432b sh + // mkdir node_v1 && cd node_v1 + // npm init --force + // npm install --save finalhandler@1.1.1 body-parser@1.18.3 ms@1.0.0 @babel/helper-string-parser@7.19.4 + // npm install --save-dev debug@2.5.2 + // npm install --save-optional promise + // npm i --lockfile-version 1 + // libraries are filled manually + + npmV1Libs = []types.Library{ + { + ID: "@babel/helper-string-parser@7.19.4", + Name: "@babel/helper-string-parser", + Version: "7.19.4", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 7, + EndLine: 11, + }, + }, + }, + { + ID: "asap@2.0.6", + Name: "asap", + Version: "2.0.6", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 12, + EndLine: 17, + }, + }, + }, + { + ID: "body-parser@1.18.3", + Name: "body-parser", + Version: "1.18.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 18, + EndLine: 49, + }, + }, + }, + { + ID: "bytes@3.0.0", + Name: "bytes", + Version: "3.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 50, + EndLine: 54, + }, + }, + }, + { + ID: "content-type@1.0.5", + Name: "content-type", + Version: "1.0.5", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 55, + EndLine: 59, + }, + }, + }, + { + ID: "debug@2.5.2", + Name: "debug", + Version: "2.5.2", + Dev: true, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 60, + EndLine: 76, + }, + }, + }, + { + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 35, + EndLine: 42, + }, + { + StartLine: 111, + EndLine: 118, + }, + }, + }, + { + ID: "depd@1.1.2", + Name: "depd", + Version: "1.1.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 77, + EndLine: 81, + }, + }, + }, + { + ID: "ee-first@1.1.1", + Name: "ee-first", + Version: "1.1.1", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 82, + EndLine: 86, + }, + }, + }, + { + ID: "encodeurl@1.0.2", + Name: "encodeurl", + Version: "1.0.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 87, + EndLine: 91, + }, + }, + }, + { + ID: "escape-html@1.0.3", + Name: "escape-html", + Version: "1.0.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 92, + EndLine: 96, + }, + }, + }, + { + ID: "finalhandler@1.1.1", + Name: "finalhandler", + Version: "1.1.1", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 97, + EndLine: 125, + }, + }, + }, + { + ID: "http-errors@1.6.3", + Name: "http-errors", + Version: "1.6.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 126, + EndLine: 136, + }, + }, + }, + { + ID: "iconv-lite@0.4.23", + Name: "iconv-lite", + Version: "0.4.23", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 137, + EndLine: 144, + }, + }, + }, + { + ID: "inherits@2.0.3", + Name: "inherits", + Version: "2.0.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 145, + EndLine: 149, + }, + }, + }, + { + ID: "media-typer@0.3.0", + Name: "media-typer", + Version: "0.3.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 150, + EndLine: 154, + }, + }, + }, + { + ID: "mime-db@1.52.0", + Name: "mime-db", + Version: "1.52.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 155, + EndLine: 159, + }, + }, + }, + { + ID: "mime-types@2.1.35", + Name: "mime-types", + Version: "2.1.35", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 160, + EndLine: 167, + }, + }, + }, + { + ID: "ms@0.7.2", + Name: "ms", + Version: "0.7.2", + Dev: true, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 69, + EndLine: 74, + }, + }, + }, + { + ID: "ms@1.0.0", + Name: "ms", + Version: "1.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 168, + EndLine: 172, + }, + }, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 43, + EndLine: 47, + }, + { + StartLine: 119, + EndLine: 123, + }, + }, + }, + { + ID: "on-finished@2.3.0", + Name: "on-finished", + Version: "2.3.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 173, + EndLine: 180, + }, + }, + }, + { + ID: "parseurl@1.3.3", + Name: "parseurl", + Version: "1.3.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 181, + EndLine: 185, + }, + }, + }, + { + ID: "promise@8.3.0", + Name: "promise", + Version: "8.3.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 186, + EndLine: 194, + }, + }, + }, + { + ID: "qs@6.5.2", + Name: "qs", + Version: "6.5.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 195, + EndLine: 199, + }, + }, + }, + { + ID: "raw-body@2.3.3", + Name: "raw-body", + Version: "2.3.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 200, + EndLine: 210, + }, + }, + }, + { + ID: "safer-buffer@2.1.2", + Name: "safer-buffer", + Version: "2.1.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 211, + EndLine: 215, + }, + }, + }, + { + ID: "setprototypeof@1.1.0", + Name: "setprototypeof", + Version: "1.1.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 216, + EndLine: 220, + }, + }, + }, + { + ID: "statuses@1.4.0", + Name: "statuses", + Version: "1.4.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 221, + EndLine: 225, + }, + }, + }, + { + ID: "type-is@1.6.18", + Name: "type-is", + Version: "1.6.18", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 226, + EndLine: 234, + }, + }, + }, + { + ID: "unpipe@1.0.0", + Name: "unpipe", + Version: "1.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 235, + EndLine: 239, + }, + }, + }, + } + + // dependencies are filled manually + npmDeps = []types.Dependency{ + { + ID: "body-parser@1.18.3", + DependsOn: []string{ + "bytes@3.0.0", + "content-type@1.0.5", + "debug@2.6.9", + "depd@1.1.2", + "http-errors@1.6.3", + "iconv-lite@0.4.23", + "on-finished@2.3.0", + "qs@6.5.2", + "raw-body@2.3.3", + "type-is@1.6.18", + }, + }, + { + ID: "debug@2.5.2", + DependsOn: []string{"ms@0.7.2"}, + }, + { + ID: "debug@2.6.9", + DependsOn: []string{"ms@2.0.0"}, + }, + { + ID: "finalhandler@1.1.1", + DependsOn: []string{ + "debug@2.6.9", + "encodeurl@1.0.2", + "escape-html@1.0.3", + "on-finished@2.3.0", + "parseurl@1.3.3", + "statuses@1.4.0", + "unpipe@1.0.0", + }, + }, + { + ID: "http-errors@1.6.3", + DependsOn: []string{ + "depd@1.1.2", + "inherits@2.0.3", + "setprototypeof@1.1.0", + "statuses@1.4.0", + }, + }, + { + ID: "iconv-lite@0.4.23", + DependsOn: []string{"safer-buffer@2.1.2"}, + }, + { + ID: "mime-types@2.1.35", + DependsOn: []string{"mime-db@1.52.0"}, + }, + { + ID: "on-finished@2.3.0", + DependsOn: []string{"ee-first@1.1.1"}, + }, + { + ID: "promise@8.3.0", + DependsOn: []string{"asap@2.0.6"}, + }, + { + ID: "raw-body@2.3.3", + DependsOn: []string{ + "bytes@3.0.0", + "http-errors@1.6.3", + "iconv-lite@0.4.23", + "unpipe@1.0.0", + }, + }, + { + ID: "type-is@1.6.18", + DependsOn: []string{ + "media-typer@0.3.0", + "mime-types@2.1.35", + }, + }, + } + + // ... and + // npm i --lockfile-version 2 + // same as npmV1Libs but change `Indirect` field to false for `body-parser@1.18.3`, `finalhandler@1.1.1`, `@babel/helper-string-parser@7.19.4`, `promise@8.3.0` and `ms@1.0.0` libraries. + // also need to get locations from `packages` struct + // --- lockfile version 3 --- + // npm i --lockfile-version 3 + // same as npmV2Libs. + npmV2Libs = []types.Library{ + { + ID: "@babel/helper-string-parser@7.19.4", + Name: "@babel/helper-string-parser", + Version: "7.19.4", + Dev: false, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 24, + EndLine: 31, + }, + }, + }, + { + ID: "asap@2.0.6", + Name: "asap", + Version: "2.0.6", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 32, + EndLine: 37, + }, + }, + }, + { + ID: "body-parser@1.18.3", + Name: "body-parser", + Version: "1.18.3", + Dev: false, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 38, + EndLine: 57, + }, + }, + }, + { + ID: "bytes@3.0.0", + Name: "bytes", + Version: "3.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 71, + EndLine: 78, + }, + }, + }, + { + ID: "content-type@1.0.5", + Name: "content-type", + Version: "1.0.5", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 79, + EndLine: 86, + }, + }, + }, + { + ID: "debug@2.5.2", + Name: "debug", + Version: "2.5.2", + Dev: true, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 87, + EndLine: 95, + }, + }, + }, + { + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 58, + EndLine: 65, + }, + { + StartLine: 145, + EndLine: 152, + }, + }, + }, + { + ID: "depd@1.1.2", + Name: "depd", + Version: "1.1.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 102, + EndLine: 109, + }, + }, + }, + { + ID: "ee-first@1.1.1", + Name: "ee-first", + Version: "1.1.1", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 110, + EndLine: 114, + }, + }, + }, + { + ID: "encodeurl@1.0.2", + Name: "encodeurl", + Version: "1.0.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 115, + EndLine: 122, + }, + }, + }, + { + ID: "escape-html@1.0.3", + Name: "escape-html", + Version: "1.0.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 123, + EndLine: 127, + }, + }, + }, + { + ID: "finalhandler@1.1.1", + Name: "finalhandler", + Version: "1.1.1", + Dev: false, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 128, + EndLine: 144, + }, + }, + }, + { + ID: "http-errors@1.6.3", + Name: "http-errors", + Version: "1.6.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 158, + EndLine: 171, + }, + }, + }, + { + ID: "iconv-lite@0.4.23", + Name: "iconv-lite", + Version: "0.4.23", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 172, + EndLine: 182, + }, + }, + }, + { + ID: "inherits@2.0.3", + Name: "inherits", + Version: "2.0.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 183, + EndLine: 187, + }, + }, + }, + { + ID: "media-typer@0.3.0", + Name: "media-typer", + Version: "0.3.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 188, + EndLine: 195, + }, + }, + }, + { + ID: "mime-db@1.52.0", + Name: "mime-db", + Version: "1.52.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 196, + EndLine: 203, + }, + }, + }, + { + ID: "mime-types@2.1.35", + Name: "mime-types", + Version: "2.1.35", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 204, + EndLine: 214, + }, + }, + }, + { + ID: "ms@0.7.2", + Name: "ms", + Version: "0.7.2", + Dev: true, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 96, + EndLine: 101, + }, + }, + }, + { + ID: "ms@1.0.0", + Name: "ms", + Version: "1.0.0", + Dev: false, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 215, + EndLine: 219, + }, + }, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 66, + EndLine: 70, + }, + { + StartLine: 153, + EndLine: 157, + }, + }, + }, + { + ID: "on-finished@2.3.0", + Name: "on-finished", + Version: "2.3.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 220, + EndLine: 230, + }, + }, + }, + { + ID: "parseurl@1.3.3", + Name: "parseurl", + Version: "1.3.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 231, + EndLine: 238, + }, + }, + }, + { + ID: "promise@8.3.0", + Name: "promise", + Version: "8.3.0", + Dev: false, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 239, + EndLine: 247, + }, + }, + }, + { + ID: "qs@6.5.2", + Name: "qs", + Version: "6.5.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 248, + EndLine: 255, + }, + }, + }, + { + ID: "raw-body@2.3.3", + Name: "raw-body", + Version: "2.3.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 256, + EndLine: 269, + }, + }, + }, + { + ID: "safer-buffer@2.1.2", + Name: "safer-buffer", + Version: "2.1.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 270, + EndLine: 274, + }, + }, + }, + { + ID: "setprototypeof@1.1.0", + Name: "setprototypeof", + Version: "1.1.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 275, + EndLine: 279, + }, + }, + }, + { + ID: "statuses@1.4.0", + Name: "statuses", + Version: "1.4.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 280, + EndLine: 287, + }, + }, + }, + { + ID: "type-is@1.6.18", + Name: "type-is", + Version: "1.6.18", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 288, + EndLine: 299, + }, + }, + }, + { + ID: "unpipe@1.0.0", + Name: "unpipe", + Version: "1.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 300, + EndLine: 307, + }, + }, + }, + } + + // docker run --name node --rm -it node@sha256:51dd437f31812df71108b81385e2945071ec813d5815fa3403855669c8f3432b sh + // mkdir node_v3_with_workspace && cd node_v3_with_workspace + // npm init --force + // npm init -w ./functions/func1 !!! use `function1` name for package + // grep -v "version" ./functions/func1/package.json > tmpfile && mv tmpfile ./functions/func1/package.json + // npm init -w ./functions/nested_func --force + // npm install --save debug@2.5.2 + // sed -i 's/\^/=/g' package.json + // npm install --save debug@2.6.9 -w nested_func + // npm install nested_func -w function1 + // grep -v "functions/func1" ./package.json > tmpfile && mv tmpfile ./package.json + // sed -i 's/functions\/nested_func/functions\/*/g' package.json + // npm update + // libraries are filled manually + npmV3WithWorkspaceLibs = []types.Library{ + { + ID: "debug@2.5.2", + Name: "debug", + Version: "2.5.2", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 39, + EndLine: 46, + }, + }, + }, + { + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 31, + EndLine: 38, + }, + }, + }, + { + ID: "function1", + Name: "function1", + Version: "", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "functions/func1", + }, + }, + Locations: []types.Location{ + { + StartLine: 18, + EndLine: 23, + }, + }, + }, + { + ID: "ms@0.7.2", + Name: "ms", + Version: "0.7.2", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 47, + EndLine: 51, + }, + }, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 56, + EndLine: 60, + }, + }, + }, + { + ID: "nested_func@1.0.0", + Name: "nested_func", + Version: "1.0.0", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "functions/nested_func", + }, + }, + Locations: []types.Location{ + { + StartLine: 24, + EndLine: 30, + }, + }, + }, + } + + npmV3WithWorkspaceDeps = []types.Dependency{ + { + ID: "debug@2.5.2", + DependsOn: []string{"ms@0.7.2"}, + }, + { + ID: "debug@2.6.9", + DependsOn: []string{"ms@2.0.0"}, + }, + { + ID: "function1", + DependsOn: []string{"nested_func@1.0.0"}, + }, + { + ID: "nested_func@1.0.0", + DependsOn: []string{"debug@2.6.9"}, + }, + } + + // docker run --name node --rm -it node@sha256:51dd437f31812df71108b81385e2945071ec813d5815fa3403855669c8f3432b sh + // mkdir node_v3_without_direct_deps && cd node_v3_without_direct_deps + // npm init --force + // npm init -w ./functions/func1 --force + // npm install --save debug@2.6.9 -w func1 + // libraries are filled manually + npmV3WithoutRootDepsField = []types.Library{ + { + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 29, + }, + }, + }, + { + ID: "func1@1.0.0", + Name: "func1", + Version: "1.0.0", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "functions/func1", + }, + }, + Locations: []types.Location{ + { + StartLine: 15, + EndLine: 21, + }, + }, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 34, + EndLine: 38, + }, + }, + }, + } + + npmV3WithoutRootDepsFieldDeps = []types.Dependency{ + { + ID: "debug@2.6.9", + DependsOn: []string{"ms@2.0.0"}, + }, + { + ID: "func1@1.0.0", + DependsOn: []string{"debug@2.6.9"}, + }, + } + + npmV3WithSameDevAndNonDevLibs = []types.Library{ + { + ID: "fsevents@1.2.9", + Name: "fsevents", + Version: "1.2.9", + Dev: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 18, + EndLine: 37, + }, + }, + }, + { + ID: "minimist@0.0.8", + Name: "minimist", + Version: "0.0.8", + Indirect: false, + Dev: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 38, + EndLine: 43, + }, + { + StartLine: 68, + EndLine: 72, + }, + }, + }, + { + ID: "mkdirp@0.5.1", + Name: "mkdirp", + Version: "0.5.1", + Indirect: true, + Dev: true, + Locations: []types.Location{ + { + StartLine: 44, + EndLine: 55, + }, + }, + }, + { + ID: "node-pre-gyp@0.12.0", + Name: "node-pre-gyp", + Version: "0.12.0", + Indirect: true, + Dev: true, + Locations: []types.Location{ + { + StartLine: 56, + EndLine: 67, + }, + }, + }, + } + + npmV3WithSameDevAndNonDevDeps = []types.Dependency{ + { + ID: "fsevents@1.2.9", + DependsOn: []string{"node-pre-gyp@0.12.0"}, + }, + { + ID: "mkdirp@0.5.1", + DependsOn: []string{"minimist@0.0.8"}, + }, + { + ID: "node-pre-gyp@0.12.0", + DependsOn: []string{"mkdirp@0.5.1"}, + }, + } +) diff --git a/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v1.json b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v1.json new file mode 100644 index 000000000000..17958d1b90d7 --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v1.json @@ -0,0 +1,241 @@ +{ + "name": "node_v1", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "optional": true + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha512-YQyoqQG3sO8iCmf8+hyVpgHHOv0/hCEFiS4zTGUwTA1HjAFX66wRcNQrVCeJq9pgESMRvUAOvSil5MJlmccuKQ==", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "debug": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + "integrity": "sha512-iHrIBaTK1JzBz5WvitFmZGaTCO/mHiU3NKi8UKjh7rU2JboIbVMZU7pFSCpvc2NxfkrvyaQ5zfdNRJnft/TcoQ==", + "dev": true, + "requires": { + "ms": "0.7.2" + }, + "dependencies": { + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha512-5NnE67nQSQDJHVahPJna1PQ/zCXMnQop3yUCxjKPNzCxuyPSKWTQ/5Gu5CZmjetwGLWRA+PzeF5thlbOdbQldA==", + "dev": true + } + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", + "integrity": "sha512-85ytwCiGUnD84ui6ULG1KBFMaZgHW3jg5KPr9jt+ZPYt75+XK+JGbYddGrBQ+RSHXOhekCnCZwJywBoFvFl0kw==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "optional": true, + "requires": { + "asap": "~2.0.6" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + } + } +} \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v2.json b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v2.json new file mode 100644 index 000000000000..f983f461593c --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v2.json @@ -0,0 +1,544 @@ +{ + "name": "node_v1", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "node_v1", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "body-parser": "^1.18.3", + "finalhandler": "^1.1.1", + "ms": "^1.0.0" + }, + "devDependencies": { + "debug": "^2.5.2" + }, + "optionalDependencies": { + "promise": "^8.3.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "optional": true + }, + "node_modules/body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha512-YQyoqQG3sO8iCmf8+hyVpgHHOv0/hCEFiS4zTGUwTA1HjAFX66wRcNQrVCeJq9pgESMRvUAOvSil5MJlmccuKQ==", + "dependencies": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/debug": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + "integrity": "sha512-iHrIBaTK1JzBz5WvitFmZGaTCO/mHiU3NKi8UKjh7rU2JboIbVMZU7pFSCpvc2NxfkrvyaQ5zfdNRJnft/TcoQ==", + "dev": true, + "dependencies": { + "ms": "0.7.2" + } + }, + "node_modules/debug/node_modules/ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha512-5NnE67nQSQDJHVahPJna1PQ/zCXMnQop3yUCxjKPNzCxuyPSKWTQ/5Gu5CZmjetwGLWRA+PzeF5thlbOdbQldA==", + "dev": true + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", + "integrity": "sha512-85ytwCiGUnD84ui6ULG1KBFMaZgHW3jg5KPr9jt+ZPYt75+XK+JGbYddGrBQ+RSHXOhekCnCZwJywBoFvFl0kw==" + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "optional": true, + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dependencies": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + } + }, + "dependencies": { + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "optional": true + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha512-YQyoqQG3sO8iCmf8+hyVpgHHOv0/hCEFiS4zTGUwTA1HjAFX66wRcNQrVCeJq9pgESMRvUAOvSil5MJlmccuKQ==", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "debug": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + "integrity": "sha512-iHrIBaTK1JzBz5WvitFmZGaTCO/mHiU3NKi8UKjh7rU2JboIbVMZU7pFSCpvc2NxfkrvyaQ5zfdNRJnft/TcoQ==", + "dev": true, + "requires": { + "ms": "0.7.2" + }, + "dependencies": { + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha512-5NnE67nQSQDJHVahPJna1PQ/zCXMnQop3yUCxjKPNzCxuyPSKWTQ/5Gu5CZmjetwGLWRA+PzeF5thlbOdbQldA==", + "dev": true + } + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", + "integrity": "sha512-85ytwCiGUnD84ui6ULG1KBFMaZgHW3jg5KPr9jt+ZPYt75+XK+JGbYddGrBQ+RSHXOhekCnCZwJywBoFvFl0kw==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "optional": true, + "requires": { + "asap": "~2.0.6" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + } + } +} \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3.json b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3.json new file mode 100644 index 000000000000..6db46f4f7267 --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3.json @@ -0,0 +1,309 @@ +{ + "name": "node_v1", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node_v1", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "body-parser": "^1.18.3", + "finalhandler": "^1.1.1", + "ms": "^1.0.0" + }, + "devDependencies": { + "debug": "^2.5.2" + }, + "optionalDependencies": { + "promise": "^8.3.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "optional": true + }, + "node_modules/body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha512-YQyoqQG3sO8iCmf8+hyVpgHHOv0/hCEFiS4zTGUwTA1HjAFX66wRcNQrVCeJq9pgESMRvUAOvSil5MJlmccuKQ==", + "dependencies": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/debug": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + "integrity": "sha512-iHrIBaTK1JzBz5WvitFmZGaTCO/mHiU3NKi8UKjh7rU2JboIbVMZU7pFSCpvc2NxfkrvyaQ5zfdNRJnft/TcoQ==", + "dev": true, + "dependencies": { + "ms": "0.7.2" + } + }, + "node_modules/debug/node_modules/ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha512-5NnE67nQSQDJHVahPJna1PQ/zCXMnQop3yUCxjKPNzCxuyPSKWTQ/5Gu5CZmjetwGLWRA+PzeF5thlbOdbQldA==", + "dev": true + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", + "integrity": "sha512-85ytwCiGUnD84ui6ULG1KBFMaZgHW3jg5KPr9jt+ZPYt75+XK+JGbYddGrBQ+RSHXOhekCnCZwJywBoFvFl0kw==" + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "optional": true, + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dependencies": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + } + } +} \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_with-same-dev-and-non-dev.json b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_with-same-dev-and-non-dev.json new file mode 100644 index 000000000000..4fe518b82bcd --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_with-same-dev-and-non-dev.json @@ -0,0 +1,74 @@ +{ + "name": "5139", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "5139", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "minimist": "^0.0.8" + }, + "devDependencies": { + "fsevents": "^1.2.9" + } + }, + "node_modules/fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "bundleDependencies": [ + "node-pre-gyp" + ], + "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", + "dev": true, + "hasInstallScript": true, + "os": [ + "darwin" + ], + "dependencies": { + "node-pre-gyp": "^0.12.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/fsevents/node_modules/minimist": { + "version": "0.0.8", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/fsevents/node_modules/mkdirp": { + "version": "0.5.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/fsevents/node_modules/node-pre-gyp": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "mkdirp": "^0.5.1" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==" + } + } +} diff --git a/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_with_workspace.json b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_with_workspace.json new file mode 100644 index 000000000000..2d765d5d7fd1 --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_with_workspace.json @@ -0,0 +1,66 @@ +{ + "name": "node_v3_with_workspace", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node_v3_with_workspace", + "version": "1.0.0", + "license": "ISC", + "workspaces": [ + "functions/*" + ], + "dependencies": { + "debug": "=2.5.2" + } + }, + "functions/func1": { + "license": "ISC", + "dependencies": { + "nested_func": "^1.0.0" + } + }, + "functions/nested_func": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "debug": "^2.6.9" + } + }, + "functions/nested_func/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debug": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + "integrity": "sha512-iHrIBaTK1JzBz5WvitFmZGaTCO/mHiU3NKi8UKjh7rU2JboIbVMZU7pFSCpvc2NxfkrvyaQ5zfdNRJnft/TcoQ==", + "dependencies": { + "ms": "0.7.2" + } + }, + "node_modules/debug/node_modules/ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha512-5NnE67nQSQDJHVahPJna1PQ/zCXMnQop3yUCxjKPNzCxuyPSKWTQ/5Gu5CZmjetwGLWRA+PzeF5thlbOdbQldA==" + }, + "node_modules/function1": { + "resolved": "functions/func1", + "link": true + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/nested_func": { + "resolved": "functions/nested_func", + "link": true + } + } +} diff --git a/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_without_root_deps_field.json b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_without_root_deps_field.json new file mode 100644 index 000000000000..49cc58f4bb4b --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_without_root_deps_field.json @@ -0,0 +1,40 @@ +{ + "name": "node_v3_without_direct_deps", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node_v3_without_direct_deps", + "version": "1.0.0", + "license": "ISC", + "workspaces": [ + "functions/func1" + ] + }, + "functions/func1": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "debug": "^2.6.9" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/func1": { + "resolved": "functions/func1", + "link": true + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } +} \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/packagejson/parse.go b/pkg/dependency/parser/nodejs/packagejson/parse.go new file mode 100644 index 000000000000..19a53679f2d0 --- /dev/null +++ b/pkg/dependency/parser/nodejs/packagejson/parse.go @@ -0,0 +1,116 @@ +package packagejson + +import ( + "encoding/json" + "io" + "regexp" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var nameRegexp = regexp.MustCompile(`^(@[A-Za-z0-9-._]+/)?[A-Za-z0-9-._]+$`) + +type packageJSON struct { + Name string `json:"name"` + Version string `json:"version"` + License interface{} `json:"license"` + Dependencies map[string]string `json:"dependencies"` + OptionalDependencies map[string]string `json:"optionalDependencies"` + DevDependencies map[string]string `json:"devDependencies"` + Workspaces any `json:"workspaces"` +} + +type Package struct { + types.Library + Dependencies map[string]string + OptionalDependencies map[string]string + DevDependencies map[string]string + Workspaces []string +} + +type Parser struct{} + +func NewParser() *Parser { + return &Parser{} +} + +func (p *Parser) Parse(r io.Reader) (Package, error) { + var pkgJSON packageJSON + if err := json.NewDecoder(r).Decode(&pkgJSON); err != nil { + return Package{}, xerrors.Errorf("JSON decode error: %w", err) + } + + if !IsValidName(pkgJSON.Name) { + return Package{}, xerrors.Errorf("Name can only contain URL-friendly characters") + } + + var id string + // Name and version fields are optional + // https://docs.npmjs.com/cli/v9/configuring-npm/package-json#name + if pkgJSON.Name != "" && pkgJSON.Version != "" { + id = dependency.ID(ftypes.NodePkg, pkgJSON.Name, pkgJSON.Version) + } + + return Package{ + Library: types.Library{ + ID: id, + Name: pkgJSON.Name, + Version: pkgJSON.Version, + License: parseLicense(pkgJSON.License), + }, + Dependencies: pkgJSON.Dependencies, + OptionalDependencies: pkgJSON.OptionalDependencies, + DevDependencies: pkgJSON.DevDependencies, + Workspaces: parseWorkspaces(pkgJSON.Workspaces), + }, nil +} + +func parseLicense(val interface{}) string { + // the license isn't always a string, check for legacy struct if not string + switch v := val.(type) { + case string: + return v + case map[string]interface{}: + if license, ok := v["type"]; ok { + return license.(string) + } + } + return "" +} + +// parseWorkspaces returns slice of workspaces +func parseWorkspaces(val any) []string { + // Workspaces support 2 types - https://github.com/SchemaStore/schemastore/blob/d9516961f8a5b0e65a457808070147b5a866f60b/src/schemas/json/package.json#L777 + switch ws := val.(type) { + // Workspace as object (map[string][]string) + // e.g. "workspaces": {"packages": ["packages/*", "plugins/*"]}, + case map[string]interface{}: + // Take only workspaces for `packages` - https://classic.yarnpkg.com/blog/2018/02/15/nohoist/ + if pkgsWorkspaces, ok := ws["packages"]; ok { + return lo.Map(pkgsWorkspaces.([]interface{}), func(workspace interface{}, _ int) string { + return workspace.(string) + }) + } + // Workspace as string array + // e.g. "workspaces": ["packages/*", "backend"], + case []interface{}: + return lo.Map(ws, func(workspace interface{}, _ int) string { + return workspace.(string) + }) + } + return nil +} + +func IsValidName(name string) bool { + // Name is optional field + // https://docs.npmjs.com/cli/v9/configuring-npm/package-json#name + if name == "" { + return true + } + return nameRegexp.MatchString(name) +} diff --git a/pkg/dependency/parser/nodejs/packagejson/parse_test.go b/pkg/dependency/parser/nodejs/packagejson/parse_test.go new file mode 100644 index 000000000000..97a0027d22ef --- /dev/null +++ b/pkg/dependency/parser/nodejs/packagejson/parse_test.go @@ -0,0 +1,185 @@ +package packagejson_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + vectors := []struct { + name string + inputFile string + want packagejson.Package + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/package.json", + + // docker run --name composer --rm -it node:12-alpine sh + // npm init --force + // npm install --save promise jquery + // npm ls | grep -E -o "\S+@\S+" | awk -F@ 'NR>0 {printf("{\""$1"\", \""$2"\"},\n")}' + want: packagejson.Package{ + Library: types.Library{ + ID: "bootstrap@5.0.2", + Name: "bootstrap", + Version: "5.0.2", + License: "MIT", + }, + Dependencies: map[string]string{ + "js-tokens": "^4.0.0", + }, + OptionalDependencies: map[string]string{ + "colors": "^1.4.0", + }, + DevDependencies: map[string]string{ + "@babel/cli": "^7.14.5", + "@babel/core": "^7.14.6", + }, + Workspaces: []string{ + "packages/*", + "backend", + }, + }, + }, + { + name: "happy path - legacy license", + inputFile: "testdata/legacy_package.json", + want: packagejson.Package{ + Library: types.Library{ + ID: "angular@4.1.2", + Name: "angular", + Version: "4.1.2", + License: "ISC", + }, + Dependencies: map[string]string{}, + DevDependencies: map[string]string{ + "@babel/cli": "^7.14.5", + "@babel/core": "^7.14.6", + }, + }, + }, + { + name: "happy path - version doesn't exist", + inputFile: "testdata/without_version_package.json", + want: packagejson.Package{ + Library: types.Library{ + ID: "", + Name: "angular", + }, + }, + }, + { + name: "happy path - workspace as struct", + inputFile: "testdata/workspace_as_map_package.json", + want: packagejson.Package{ + Library: types.Library{ + ID: "example@1.0.0", + Name: "example", + Version: "1.0.0", + }, + Workspaces: []string{ + "packages/*", + }, + }, + }, + { + name: "invalid package name", + inputFile: "testdata/invalid_name.json", + wantErr: "Name can only contain URL-friendly characters", + }, + { + name: "sad path", + inputFile: "testdata/invalid_package.json", + + // docker run --name composer --rm -it node:12-alpine sh + // npm init --force + // npm install --save promise jquery + // npm ls | grep -E -o "\S+@\S+" | awk -F@ 'NR>0 {printf("{\""$1"\", \""$2"\"},\n")}' + wantErr: "JSON decode error", + }, + { + name: "without name and version", + inputFile: "testdata/without_name_and_version_package.json", + want: packagejson.Package{ + Library: types.Library{ + License: "MIT", + }, + }, + }, + } + + for _, v := range vectors { + t.Run(v.name, func(t *testing.T) { + f, err := os.Open(v.inputFile) + require.NoError(t, err) + defer f.Close() + + got, err := packagejson.NewParser().Parse(f) + if v.wantErr != "" { + assert.ErrorContains(t, err, v.wantErr) + return + } + + require.NoError(t, err) + assert.Equal(t, v.want, got) + }) + } +} + +func TestIsValidName(t *testing.T) { + tests := []struct { + name string + want bool + }{ + { + name: "", + want: true, + }, + { + name: "test_package", + want: true, + }, + { + name: "test.package", + want: true, + }, + { + name: "test-package", + want: true, + }, + { + name: "@test/package", + want: true, + }, + { + name: "test@package", + want: false, + }, { + name: "test?package", + want: false, + }, + { + name: "test/package", + want: false, + }, + { + name: "package/", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + valid := packagejson.IsValidName(tt.name) + require.Equal(t, tt.want, valid) + }) + } +} diff --git a/pkg/dependency/parser/nodejs/packagejson/testdata/invalid_name.json b/pkg/dependency/parser/nodejs/packagejson/testdata/invalid_name.json new file mode 100644 index 000000000000..f97f91d2c852 --- /dev/null +++ b/pkg/dependency/parser/nodejs/packagejson/testdata/invalid_name.json @@ -0,0 +1,11 @@ +{ + "name": "@invalid/packageName/", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/packagejson/testdata/invalid_package.json b/pkg/dependency/parser/nodejs/packagejson/testdata/invalid_package.json new file mode 100644 index 000000000000..d2b086ecf43a --- /dev/null +++ b/pkg/dependency/parser/nodejs/packagejson/testdata/invalid_package.json @@ -0,0 +1,3 @@ +{ + "name" "bootstrap" +} \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/packagejson/testdata/legacy_package.json b/pkg/dependency/parser/nodejs/packagejson/testdata/legacy_package.json new file mode 100644 index 000000000000..6176081f1760 --- /dev/null +++ b/pkg/dependency/parser/nodejs/packagejson/testdata/legacy_package.json @@ -0,0 +1,136 @@ + +{ + "name": "angular", + "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", + "version": "4.1.2", + "config": { + "version_short": "5.0" + }, + "keywords": [ + "css", + "sass", + "mobile-first", + "responsive", + "front-end", + "framework", + "web" + ], + "homepage": "https://getbootstrap.com/", + "author": "The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)", + "contributors": [ + "Twitter, Inc." + ], + "scripts": { + "start": "npm-run-all --parallel watch docs-serve", + "bundlewatch": "bundlewatch --config .bundlewatch.config.json", + "css": "npm-run-all css-compile css-prefix css-rtl css-minify", + "css-compile": "sass --style expanded --source-map --embed-sources --no-error-css scss/:dist/css/", + "css-rtl": "cross-env NODE_ENV=RTL postcss --config build/postcss.config.js --dir \"dist/css\" --ext \".rtl.css\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.rtl.css\"", + "css-lint": "npm-run-all --aggregate-output --continue-on-error --parallel css-lint-*", + "css-lint-stylelint": "stylelint \"**/*.{css,scss}\" --cache --cache-location .cache/.stylelintcache --rd", + "css-lint-vars": "fusv scss/ site/assets/scss/", + "css-minify": "npm-run-all --aggregate-output --parallel css-minify-*", + "css-minify-main": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*rtl*.css\"", + "css-minify-rtl": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*rtl.css\" \"!dist/css/*.min.css\"", + "css-prefix": "npm-run-all --aggregate-output --parallel css-prefix-*", + "css-prefix-main": "postcss --config build/postcss.config.js --replace \"dist/css/*.css\" \"!dist/css/*.rtl*.css\" \"!dist/css/*.min.css\"", + "css-prefix-examples": "postcss --config build/postcss.config.js --replace \"site/content/**/*.css\"", + "css-prefix-examples-rtl": "cross-env-shell NODE_ENV=RTL postcss --config build/postcss.config.js --dir \"site/content/docs/$npm_package_config_version_short/examples/\" --ext \".rtl.css\" --base \"site/content/docs/$npm_package_config_version_short/examples/\" \"site/content/docs/$npm_package_config_version_short/examples/{blog,carousel,dashboard,cheatsheet}/*.css\" \"!site/content/docs/$npm_package_config_version_short/examples/{blog,carousel,dashboard,cheatsheet}/*.rtl.css\"", + "js": "npm-run-all js-compile js-minify", + "js-compile": "npm-run-all --aggregate-output --parallel js-compile-*", + "js-compile-standalone": "rollup --environment BUNDLE:false --config build/rollup.config.js --sourcemap", + "js-compile-standalone-esm": "rollup --environment ESM:true,BUNDLE:false --config build/rollup.config.js --sourcemap", + "js-compile-bundle": "rollup --environment BUNDLE:true --config build/rollup.config.js --sourcemap", + "js-compile-plugins": "node build/build-plugins.js", + "js-lint": "eslint --cache --cache-location .cache/.eslintcache --report-unused-disable-directives ", + "js-minify": "npm-run-all --aggregate-output --parallel js-minify-*", + "js-minify-standalone": "terser --compress passes=2 --mangle --comments \"/^!/\" --source-map \"content=dist/js/bootstrap.js.map,includeSources,url=bootstrap.min.js.map\" --output dist/js/bootstrap.min.js dist/js/bootstrap.js", + "js-minify-standalone-esm": "terser --compress passes=2 --mangle --comments \"/^!/\" --source-map \"content=dist/js/bootstrap.esm.js.map,includeSources,url=bootstrap.esm.min.js.map\" --output dist/js/bootstrap.esm.min.js dist/js/bootstrap.esm.js", + "js-minify-bundle": "terser --compress passes=2 --mangle --comments \"/^!/\" --source-map \"content=dist/js/bootstrap.bundle.js.map,includeSources,url=bootstrap.bundle.min.js.map\" --output dist/js/bootstrap.bundle.min.js dist/js/bootstrap.bundle.js", + "js-test": "npm-run-all --aggregate-output --parallel js-test-karma js-test-jquery js-test-integration-*", + "js-debug": "cross-env DEBUG=true npm run js-test-karma", + "js-test-karma": "karma start js/tests/karma.conf.js", + "js-test-integration-bundle": "rollup --config js/tests/integration/rollup.bundle.js", + "js-test-integration-modularity": "rollup --config js/tests/integration/rollup.bundle-modularity.js", + "js-test-cloud": "cross-env BROWSERSTACK=true npm run js-test-karma", + "js-test-jquery": "cross-env JQUERY=true npm run js-test-karma", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel js-lint css-lint lockfile-lint", + "docs": "npm-run-all docs-build docs-lint", + "docs-build": "hugo --cleanDestinationDir", + "docs-compile": "npm run docs-build", + "docs-linkinator": "linkinator _site --recurse --skip \"^(?!http://localhost)\" --verbosity error", + "docs-vnu": "node build/vnu-jar.js", + "docs-lint": "npm-run-all --aggregate-output --parallel docs-vnu docs-linkinator", + "docs-serve": "hugo server --port 9001 --disableFastRender", + "docs-serve-only": "npx sirv-cli _site --port 9001", + "lockfile-lint": "lockfile-lint --allowed-hosts npm --allowed-schemes https: --empty-hostname false --type npm --path package-lock.json", + "update-deps": "ncu -u -x karma-browserstack-launcher,terser && npm update && echo Manually update site/assets/js/vendor", + "release": "npm-run-all dist release-sri docs-build release-zip*", + "release-sri": "node build/generate-sri.js", + "release-version": "node build/change-version.js", + "release-zip": "cross-env-shell \"rm -rf bootstrap-$npm_package_version-dist && cp -r dist/ bootstrap-$npm_package_version-dist && zip -r9 bootstrap-$npm_package_version-dist.zip bootstrap-$npm_package_version-dist && rm -rf bootstrap-$npm_package_version-dist\"", + "release-zip-examples": "node build/zip-examples.js", + "dist": "npm-run-all --aggregate-output --parallel css js", + "test": "npm-run-all lint dist js-test docs-build docs-lint", + "netlify": "cross-env-shell HUGO_BASEURL=$DEPLOY_PRIME_URL npm-run-all dist release-sri docs-build", + "watch": "npm-run-all --parallel watch-*", + "watch-css-main": "nodemon --watch scss/ --ext scss --exec \"npm-run-all css-lint css-compile css-prefix\"", + "watch-css-dist": "nodemon --watch dist/css/ --ext css --ignore \"dist/css/*.rtl.*\" --exec \"npm run css-rtl\"", + "watch-css-docs": "nodemon --watch site/assets/scss/ --ext scss --exec \"npm run css-lint\"", + "watch-js-main": "nodemon --watch js/src/ --ext js --exec \"npm-run-all js-lint js-compile\"", + "watch-js-docs": "nodemon --watch site/assets/js/ --ext js --exec \"npm run js-lint\"" + }, + "style": "dist/css/bootstrap.css", + "sass": "scss/bootstrap.scss", + "main": "dist/js/bootstrap.js", + "module": "dist/js/bootstrap.esm.js", + "repository": { + "type": "git", + "url": "git+https://github.com/twbs/bootstrap.git" + }, + "bugs": { + "url": "https://github.com/twbs/bootstrap/issues" + }, + "license": { + "type" : "ISC", + "url" : "https://opensource.org/licenses/ISC" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + }, + "dependencies": {}, + "peerDependencies": { + "@popperjs/core": "^2.9.2" + }, + "devDependencies": { + "@babel/cli": "^7.14.5", + "@babel/core": "^7.14.6" + }, + "files": [ + "dist/{css,js}/*.{css,js,map}", + "js/{src,dist}/**/*.{js,map}", + "scss/**/*.scss" + ], + "hugo-bin": { + "buildTags": "extended" + }, + "jspm": { + "registry": "npm", + "main": "js/bootstrap", + "directories": { + "lib": "dist" + }, + "shim": { + "js/bootstrap": { + "deps": [ + "@popperjs/core" + ] + } + }, + "dependencies": {}, + "peerDependencies": { + "@popperjs/core": "^2.9.2" + } + } +} diff --git a/pkg/dependency/parser/nodejs/packagejson/testdata/package.json b/pkg/dependency/parser/nodejs/packagejson/testdata/package.json new file mode 100644 index 000000000000..9e1e0858d4cc --- /dev/null +++ b/pkg/dependency/parser/nodejs/packagejson/testdata/package.json @@ -0,0 +1,142 @@ + +{ + "name": "bootstrap", + "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", + "version": "5.0.2", + "config": { + "version_short": "5.0" + }, + "keywords": [ + "css", + "sass", + "mobile-first", + "responsive", + "front-end", + "framework", + "web" + ], + "workspaces": [ + "packages/*", + "backend" + ], + "homepage": "https://getbootstrap.com/", + "author": "The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)", + "contributors": [ + "Twitter, Inc." + ], + "scripts": { + "start": "npm-run-all --parallel watch docs-serve", + "bundlewatch": "bundlewatch --config .bundlewatch.config.json", + "css": "npm-run-all css-compile css-prefix css-rtl css-minify", + "css-compile": "sass --style expanded --source-map --embed-sources --no-error-css scss/:dist/css/", + "css-rtl": "cross-env NODE_ENV=RTL postcss --config build/postcss.config.js --dir \"dist/css\" --ext \".rtl.css\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.rtl.css\"", + "css-lint": "npm-run-all --aggregate-output --continue-on-error --parallel css-lint-*", + "css-lint-stylelint": "stylelint \"**/*.{css,scss}\" --cache --cache-location .cache/.stylelintcache --rd", + "css-lint-vars": "fusv scss/ site/assets/scss/", + "css-minify": "npm-run-all --aggregate-output --parallel css-minify-*", + "css-minify-main": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*rtl*.css\"", + "css-minify-rtl": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*rtl.css\" \"!dist/css/*.min.css\"", + "css-prefix": "npm-run-all --aggregate-output --parallel css-prefix-*", + "css-prefix-main": "postcss --config build/postcss.config.js --replace \"dist/css/*.css\" \"!dist/css/*.rtl*.css\" \"!dist/css/*.min.css\"", + "css-prefix-examples": "postcss --config build/postcss.config.js --replace \"site/content/**/*.css\"", + "css-prefix-examples-rtl": "cross-env-shell NODE_ENV=RTL postcss --config build/postcss.config.js --dir \"site/content/docs/$npm_package_config_version_short/examples/\" --ext \".rtl.css\" --base \"site/content/docs/$npm_package_config_version_short/examples/\" \"site/content/docs/$npm_package_config_version_short/examples/{blog,carousel,dashboard,cheatsheet}/*.css\" \"!site/content/docs/$npm_package_config_version_short/examples/{blog,carousel,dashboard,cheatsheet}/*.rtl.css\"", + "js": "npm-run-all js-compile js-minify", + "js-compile": "npm-run-all --aggregate-output --parallel js-compile-*", + "js-compile-standalone": "rollup --environment BUNDLE:false --config build/rollup.config.js --sourcemap", + "js-compile-standalone-esm": "rollup --environment ESM:true,BUNDLE:false --config build/rollup.config.js --sourcemap", + "js-compile-bundle": "rollup --environment BUNDLE:true --config build/rollup.config.js --sourcemap", + "js-compile-plugins": "node build/build-plugins.js", + "js-lint": "eslint --cache --cache-location .cache/.eslintcache --report-unused-disable-directives ", + "js-minify": "npm-run-all --aggregate-output --parallel js-minify-*", + "js-minify-standalone": "terser --compress passes=2 --mangle --comments \"/^!/\" --source-map \"content=dist/js/bootstrap.js.map,includeSources,url=bootstrap.min.js.map\" --output dist/js/bootstrap.min.js dist/js/bootstrap.js", + "js-minify-standalone-esm": "terser --compress passes=2 --mangle --comments \"/^!/\" --source-map \"content=dist/js/bootstrap.esm.js.map,includeSources,url=bootstrap.esm.min.js.map\" --output dist/js/bootstrap.esm.min.js dist/js/bootstrap.esm.js", + "js-minify-bundle": "terser --compress passes=2 --mangle --comments \"/^!/\" --source-map \"content=dist/js/bootstrap.bundle.js.map,includeSources,url=bootstrap.bundle.min.js.map\" --output dist/js/bootstrap.bundle.min.js dist/js/bootstrap.bundle.js", + "js-test": "npm-run-all --aggregate-output --parallel js-test-karma js-test-jquery js-test-integration-*", + "js-debug": "cross-env DEBUG=true npm run js-test-karma", + "js-test-karma": "karma start js/tests/karma.conf.js", + "js-test-integration-bundle": "rollup --config js/tests/integration/rollup.bundle.js", + "js-test-integration-modularity": "rollup --config js/tests/integration/rollup.bundle-modularity.js", + "js-test-cloud": "cross-env BROWSERSTACK=true npm run js-test-karma", + "js-test-jquery": "cross-env JQUERY=true npm run js-test-karma", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel js-lint css-lint lockfile-lint", + "docs": "npm-run-all docs-build docs-lint", + "docs-build": "hugo --cleanDestinationDir", + "docs-compile": "npm run docs-build", + "docs-linkinator": "linkinator _site --recurse --skip \"^(?!http://localhost)\" --verbosity error", + "docs-vnu": "node build/vnu-jar.js", + "docs-lint": "npm-run-all --aggregate-output --parallel docs-vnu docs-linkinator", + "docs-serve": "hugo server --port 9001 --disableFastRender", + "docs-serve-only": "npx sirv-cli _site --port 9001", + "lockfile-lint": "lockfile-lint --allowed-hosts npm --allowed-schemes https: --empty-hostname false --type npm --path package-lock.json", + "update-deps": "ncu -u -x karma-browserstack-launcher,terser && npm update && echo Manually update site/assets/js/vendor", + "release": "npm-run-all dist release-sri docs-build release-zip*", + "release-sri": "node build/generate-sri.js", + "release-version": "node build/change-version.js", + "release-zip": "cross-env-shell \"rm -rf bootstrap-$npm_package_version-dist && cp -r dist/ bootstrap-$npm_package_version-dist && zip -r9 bootstrap-$npm_package_version-dist.zip bootstrap-$npm_package_version-dist && rm -rf bootstrap-$npm_package_version-dist\"", + "release-zip-examples": "node build/zip-examples.js", + "dist": "npm-run-all --aggregate-output --parallel css js", + "test": "npm-run-all lint dist js-test docs-build docs-lint", + "netlify": "cross-env-shell HUGO_BASEURL=$DEPLOY_PRIME_URL npm-run-all dist release-sri docs-build", + "watch": "npm-run-all --parallel watch-*", + "watch-css-main": "nodemon --watch scss/ --ext scss --exec \"npm-run-all css-lint css-compile css-prefix\"", + "watch-css-dist": "nodemon --watch dist/css/ --ext css --ignore \"dist/css/*.rtl.*\" --exec \"npm run css-rtl\"", + "watch-css-docs": "nodemon --watch site/assets/scss/ --ext scss --exec \"npm run css-lint\"", + "watch-js-main": "nodemon --watch js/src/ --ext js --exec \"npm-run-all js-lint js-compile\"", + "watch-js-docs": "nodemon --watch site/assets/js/ --ext js --exec \"npm run js-lint\"" + }, + "style": "dist/css/bootstrap.css", + "sass": "scss/bootstrap.scss", + "main": "dist/js/bootstrap.js", + "module": "dist/js/bootstrap.esm.js", + "repository": { + "type": "git", + "url": "git+https://github.com/twbs/bootstrap.git" + }, + "bugs": { + "url": "https://github.com/twbs/bootstrap/issues" + }, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + }, + "dependencies": { + "js-tokens": "^4.0.0" + }, + "peerDependencies": { + "@popperjs/core": "^2.9.2" + }, + "optionalDependencies": { + "colors": "^1.4.0" + }, + "devDependencies": { + "@babel/cli": "^7.14.5", + "@babel/core": "^7.14.6" + }, + "files": [ + "dist/{css,js}/*.{css,js,map}", + "js/{src,dist}/**/*.{js,map}", + "scss/**/*.scss" + ], + "hugo-bin": { + "buildTags": "extended" + }, + "jspm": { + "registry": "npm", + "main": "js/bootstrap", + "directories": { + "lib": "dist" + }, + "shim": { + "js/bootstrap": { + "deps": [ + "@popperjs/core" + ] + } + }, + "dependencies": {}, + "peerDependencies": { + "@popperjs/core": "^2.9.2" + } + } +} diff --git a/pkg/dependency/parser/nodejs/packagejson/testdata/without_name_and_version_package.json b/pkg/dependency/parser/nodejs/packagejson/testdata/without_name_and_version_package.json new file mode 100644 index 000000000000..8f75dc6ee963 --- /dev/null +++ b/pkg/dependency/parser/nodejs/packagejson/testdata/without_name_and_version_package.json @@ -0,0 +1,3 @@ +{ + "license": "MIT" +} diff --git a/pkg/dependency/parser/nodejs/packagejson/testdata/without_version_package.json b/pkg/dependency/parser/nodejs/packagejson/testdata/without_version_package.json new file mode 100644 index 000000000000..2764f1f6f15d --- /dev/null +++ b/pkg/dependency/parser/nodejs/packagejson/testdata/without_version_package.json @@ -0,0 +1,4 @@ + +{ + "name": "angular" +} diff --git a/pkg/dependency/parser/nodejs/packagejson/testdata/workspace_as_map_package.json b/pkg/dependency/parser/nodejs/packagejson/testdata/workspace_as_map_package.json new file mode 100644 index 000000000000..21a198e8bc6e --- /dev/null +++ b/pkg/dependency/parser/nodejs/packagejson/testdata/workspace_as_map_package.json @@ -0,0 +1,8 @@ +{ + "name": "example", + "version": "1.0.0", + "workspaces": { + "packages": ["packages/*"], + "nohoist": ["**/react-native", "**/react-native/**"] + } +} diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go new file mode 100644 index 000000000000..9e93be6a89c1 --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -0,0 +1,182 @@ +package pnpm + +import ( + "fmt" + "strconv" + "strings" + + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/go-version/pkg/semver" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type PackageResolution struct { + Tarball string `yaml:"tarball,omitempty"` +} + +type PackageInfo struct { + Resolution PackageResolution `yaml:"resolution"` + Dependencies map[string]string `yaml:"dependencies,omitempty"` + DevDependencies map[string]string `yaml:"devDependencies,omitempty"` + IsDev bool `yaml:"dev,omitempty"` + Name string `yaml:"name,omitempty"` + Version string `yaml:"version,omitempty"` +} + +type LockFile struct { + LockfileVersion any `yaml:"lockfileVersion"` + Dependencies map[string]any `yaml:"dependencies,omitempty"` + DevDependencies map[string]any `yaml:"devDependencies,omitempty"` + Packages map[string]PackageInfo `yaml:"packages,omitempty"` +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var lockFile LockFile + if err := yaml.NewDecoder(r).Decode(&lockFile); err != nil { + return nil, nil, xerrors.Errorf("decode error: %w", err) + } + + lockVer := parseLockfileVersion(lockFile) + if lockVer < 0 { + return nil, nil, nil + } + + libs, deps := p.parse(lockVer, lockFile) + + return libs, deps, nil +} + +func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]types.Library, []types.Dependency) { + var libs []types.Library + var deps []types.Dependency + + // Dependency path is a path to a dependency with a specific set of resolved subdependencies. + // cf. https://github.com/pnpm/spec/blob/ad27a225f81d9215becadfa540ef05fa4ad6dd60/dependency-path.md + for depPath, info := range lockFile.Packages { + if info.IsDev { + continue + } + + // Dependency name may be present in dependencyPath or Name field. Same for Version. + // e.g. packages installed from local directory or tarball + // cf. https://github.com/pnpm/spec/blob/274ff02de23376ad59773a9f25ecfedd03a41f64/lockfile/6.0.md#packagesdependencypathname + name := info.Name + version := info.Version + + if name == "" { + name, version = parsePackage(depPath, lockVer) + } + pkgID := packageID(name, version) + + dependencies := make([]string, 0, len(info.Dependencies)) + for depName, depVer := range info.Dependencies { + dependencies = append(dependencies, packageID(depName, depVer)) + } + + libs = append(libs, types.Library{ + ID: pkgID, + Name: name, + Version: version, + Indirect: isIndirectLib(name, lockFile.Dependencies), + }) + + if len(dependencies) > 0 { + deps = append(deps, types.Dependency{ + ID: pkgID, + DependsOn: dependencies, + }) + } + } + + return libs, deps +} + +func parseLockfileVersion(lockFile LockFile) float64 { + switch v := lockFile.LockfileVersion.(type) { + // v5 + case float64: + return v + // v6+ + case string: + if lockVer, err := strconv.ParseFloat(v, 64); err != nil { + log.Logger.Debugf("Unable to convert the lock file version to float: %s", err) + return -1 + } else { + return lockVer + } + default: + log.Logger.Debugf("Unknown type for the lock file version: %s", lockFile.LockfileVersion) + return -1 + } +} + +func isIndirectLib(name string, directDeps map[string]interface{}) bool { + _, ok := directDeps[name] + return !ok +} + +// cf. https://github.com/pnpm/pnpm/blob/ce61f8d3c29eee46cee38d56ced45aea8a439a53/packages/dependency-path/src/index.ts#L112-L163 +func parsePackage(depPath string, lockFileVersion float64) (string, string) { + // The version separator is different between v5 and v6+. + versionSep := "@" + if lockFileVersion < 6 { + versionSep = "/" + } + return parseDepPath(depPath, versionSep) +} + +func parseDepPath(depPath, versionSep string) (string, string) { + // Skip registry + // e.g. + // - "registry.npmjs.org/lodash/4.17.10" => "lodash/4.17.10" + // - "registry.npmjs.org/@babel/generator/7.21.9" => "@babel/generator/7.21.9" + // - "/lodash/4.17.10" => "lodash/4.17.10" + _, depPath, _ = strings.Cut(depPath, "/") + + // Parse scope + // e.g. + // - v5: "@babel/generator/7.21.9" => {"babel", "generator/7.21.9"} + // - v6+: "@babel/helper-annotate-as-pure@7.18.6" => "{"babel", "helper-annotate-as-pure@7.18.6"} + var scope string + if strings.HasPrefix(depPath, "@") { + scope, depPath, _ = strings.Cut(depPath, "/") + } + + // Parse package name + // e.g. + // - v5: "generator/7.21.9" => {"generator", "7.21.9"} + // - v6+: "helper-annotate-as-pure@7.18.6" => {"helper-annotate-as-pure", "7.18.6"} + var name, version string + name, version, _ = strings.Cut(depPath, versionSep) + if scope != "" { + name = fmt.Sprintf("%s/%s", scope, name) + } + // Trim peer deps + // e.g. + // - v5: "7.21.5_@babel+core@7.21.8" => "7.21.5" + // - v6+: "7.21.5(@babel/core@7.20.7)" => "7.21.5" + if idx := strings.IndexAny(version, "_("); idx != -1 { + version = version[:idx] + } + if _, err := semver.Parse(version); err != nil { + log.Logger.Debugf("Skip %q package. %q doesn't match semver: %s", depPath, version, err) + return "", "" + } + return name, version +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.Pnpm, name, version) +} diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_test.go b/pkg/dependency/parser/nodejs/pnpm/parse_test.go new file mode 100644 index 000000000000..19851a2c21c0 --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/parse_test.go @@ -0,0 +1,224 @@ +package pnpm + +import ( + "os" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + file string // Test input file + want []types.Library + wantDeps []types.Dependency + }{ + { + name: "normal", + file: "testdata/pnpm-lock_normal.yaml", + want: pnpmNormal, + wantDeps: pnpmNormalDeps, + }, + { + name: "with dev deps", + file: "testdata/pnpm-lock_with_dev.yaml", + want: pnpmWithDev, + wantDeps: pnpmWithDevDeps, + }, + { + name: "many", + file: "testdata/pnpm-lock_many.yaml", + want: pnpmMany, + wantDeps: pnpmManyDeps, + }, + { + name: "archives", + file: "testdata/pnpm-lock_archives.yaml", + want: pnpmArchives, + wantDeps: pnpmArchivesDeps, + }, + { + name: "v6", + file: "testdata/pnpm-lock_v6.yaml", + want: pnpmV6, + wantDeps: pnpmV6Deps, + }, + { + name: "v6 with dev deps", + file: "testdata/pnpm-lock_v6_with_dev.yaml", + want: pnpmV6WithDev, + wantDeps: pnpmV6WithDevDeps, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + + got, deps, err := NewParser().Parse(f) + require.NoError(t, err) + + sortLibs(got) + sortLibs(tt.want) + + assert.Equal(t, tt.want, got) + if tt.wantDeps != nil { + sortDeps(deps) + sortDeps(tt.wantDeps) + assert.Equal(t, tt.wantDeps, deps) + } + }) + } +} + +func sortDeps(deps []types.Dependency) { + sort.Slice(deps, func(i, j int) bool { + return strings.Compare(deps[i].ID, deps[j].ID) < 0 + }) + + for i := range deps { + sort.Strings(deps[i].DependsOn) + } +} + +func sortLibs(libs []types.Library) { + sort.Slice(libs, func(i, j int) bool { + ret := strings.Compare(libs[i].Name, libs[j].Name) + if ret == 0 { + return libs[i].Version < libs[j].Version + } + return ret < 0 + }) +} + +func Test_parsePackage(t *testing.T) { + tests := []struct { + name string + lockFileVer float64 + pkg string + wantName string + wantVersion string + }{ + { + name: "v5 - relative path", + lockFileVer: 5.0, + pkg: "/lodash/4.17.10", + wantName: "lodash", + wantVersion: "4.17.10", + }, + { + name: "v5 - registry", + lockFileVer: 5.0, + pkg: "registry.npmjs.org/lodash/4.17.10", + wantName: "lodash", + wantVersion: "4.17.10", + }, + { + name: "v5 - relative path with slash", + lockFileVer: 5.0, + pkg: "/@babel/generator/7.21.9", + wantName: "@babel/generator", + wantVersion: "7.21.9", + }, + { + name: "v5 - registry path with slash", + lockFileVer: 5.0, + pkg: "registry.npmjs.org/@babel/generator/7.21.9", + wantName: "@babel/generator", + wantVersion: "7.21.9", + }, + { + name: "v5 - relative path with slash and peer deps", + lockFileVer: 5.0, + pkg: "/@babel/helper-compilation-targets/7.21.5_@babel+core@7.21.8", + wantName: "@babel/helper-compilation-targets", + wantVersion: "7.21.5", + }, + { + name: "v5 - relative path with underline and peer deps", + lockFileVer: 5.0, + pkg: "/lodash._baseclone/4.5.7_@babel+core@7.21.8", + wantName: "lodash._baseclone", + wantVersion: "4.5.7", + }, + { + name: "v5 - registry with slash and peer deps", + lockFileVer: 5.0, + pkg: "registry.npmjs.org/@babel/helper-compilation-targets/7.21.5_@babel+core@7.21.8", + wantName: "@babel/helper-compilation-targets", + wantVersion: "7.21.5", + }, + { + name: "v5 - relative path with wrong version", + lockFileVer: 5.0, + pkg: "/lodash/4-wrong", + wantName: "", + wantVersion: "", + }, + { + name: "v6 - relative path", + lockFileVer: 6.0, + pkg: "/update-browserslist-db@1.0.11", + wantName: "update-browserslist-db", + wantVersion: "1.0.11", + }, + { + name: "v6 - registry", + lockFileVer: 6.0, + pkg: "registry.npmjs.org/lodash@4.17.10", + wantName: "lodash", + wantVersion: "4.17.10", + }, + { + name: "v6 - relative path with slash", + lockFileVer: 6.0, + pkg: "/@babel/helper-annotate-as-pure@7.18.6", + wantName: "@babel/helper-annotate-as-pure", + wantVersion: "7.18.6", + }, + { + name: "v6 - registry with slash", + lockFileVer: 6.0, + pkg: "registry.npmjs.org/@babel/helper-annotate-as-pure@7.18.6", + wantName: "@babel/helper-annotate-as-pure", + wantVersion: "7.18.6", + }, + { + name: "v6 - relative path with slash and peer deps", + lockFileVer: 6.0, + pkg: "/@babel/helper-compilation-targets@7.21.5(@babel/core@7.20.7)", + wantName: "@babel/helper-compilation-targets", + wantVersion: "7.21.5", + }, + { + name: "v6 - registry with slash and peer deps", + lockFileVer: 6.0, + pkg: "registry.npmjs.org/@babel/helper-compilation-targets@7.21.5(@babel/core@7.20.7)", + wantName: "@babel/helper-compilation-targets", + wantVersion: "7.21.5", + }, + { + name: "v6 - relative path with wrong version", + lockFileVer: 6.0, + pkg: "/lodash@4-wrong", + wantName: "", + wantVersion: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotName, gotVersion := parsePackage(tt.pkg, tt.lockFileVer) + assert.Equal(t, tt.wantName, gotName) + assert.Equal(t, tt.wantVersion, gotVersion) + }) + + } +} diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go new file mode 100644 index 000000000000..2b776cd0a9c8 --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go @@ -0,0 +1,220 @@ +package pnpm + +import "github.com/aquasecurity/trivy/pkg/dependency/types" + +var ( + // docker run --name node --rm -it node:16-alpine sh + // npm install -g pnpm + // pnpm add promise jquery + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: true},\n")}' | sort -u + pnpmNormal = []types.Library{ + {ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Indirect: true}, + {ID: "jquery@3.6.0", Name: "jquery", Version: "3.6.0", Indirect: false}, + {ID: "promise@8.1.0", Name: "promise", Version: "8.1.0", Indirect: false}, + } + pnpmNormalDeps = []types.Dependency{ + { + ID: "promise@8.1.0", + DependsOn: []string{"asap@2.0.6"}, + }, + } + + // docker run --name node --rm -it node:16-alpine sh + // npm install -g pnpm + // pnpm add react redux + // pnpm add -D mocha + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: true},\n")}' | sort -u + pnpmWithDev = []types.Library{ + {ID: "@babel/runtime@7.18.3", Name: "@babel/runtime", Version: "7.18.3", Indirect: true}, + {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Indirect: true}, + {ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Indirect: true}, + {ID: "react@18.1.0", Name: "react", Version: "18.1.0", Indirect: false}, + {ID: "redux@4.2.0", Name: "redux", Version: "4.2.0", Indirect: false}, + {ID: "regenerator-runtime@0.13.9", Name: "regenerator-runtime", Version: "0.13.9", Indirect: true}, + } + pnpmWithDevDeps = []types.Dependency{ + { + ID: "@babel/runtime@7.18.3", + DependsOn: []string{"regenerator-runtime@0.13.9"}, + }, + { + ID: "loose-envify@1.4.0", + DependsOn: []string{"js-tokens@4.0.0"}, + }, + { + ID: "react@18.1.0", + DependsOn: []string{"loose-envify@1.4.0"}, + }, + { + ID: "redux@4.2.0", + DependsOn: []string{"@babel/runtime@7.18.3"}, + }, + } + + // docker run --name node --rm -it node:16-alpine sh + // npm install -g pnpm + // pnpm add react redux lodash request chalk commander + // pnpm add -D mocha + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: true},\n")}' | sort -u + pnpmMany = []types.Library{ + {ID: "@babel/runtime@7.18.3", Name: "@babel/runtime", Version: "7.18.3", Indirect: true}, + {ID: "ajv@6.12.6", Name: "ajv", Version: "6.12.6", Indirect: true}, + {ID: "asn1@0.2.6", Name: "asn1", Version: "0.2.6", Indirect: true}, + {ID: "assert-plus@1.0.0", Name: "assert-plus", Version: "1.0.0", Indirect: true}, + {ID: "asynckit@0.4.0", Name: "asynckit", Version: "0.4.0", Indirect: true}, + {ID: "aws-sign2@0.7.0", Name: "aws-sign2", Version: "0.7.0", Indirect: true}, + {ID: "aws4@1.11.0", Name: "aws4", Version: "1.11.0", Indirect: true}, + {ID: "bcrypt-pbkdf@1.0.2", Name: "bcrypt-pbkdf", Version: "1.0.2", Indirect: true}, + {ID: "caseless@0.12.0", Name: "caseless", Version: "0.12.0", Indirect: true}, + {ID: "chalk@5.0.1", Name: "chalk", Version: "5.0.1", Indirect: false}, + {ID: "combined-stream@1.0.8", Name: "combined-stream", Version: "1.0.8", Indirect: true}, + {ID: "commander@9.3.0", Name: "commander", Version: "9.3.0", Indirect: false}, + {ID: "core-util-is@1.0.2", Name: "core-util-is", Version: "1.0.2", Indirect: true}, + {ID: "dashdash@1.14.1", Name: "dashdash", Version: "1.14.1", Indirect: true}, + {ID: "delayed-stream@1.0.0", Name: "delayed-stream", Version: "1.0.0", Indirect: true}, + {ID: "ecc-jsbn@0.1.2", Name: "ecc-jsbn", Version: "0.1.2", Indirect: true}, + {ID: "extend@3.0.2", Name: "extend", Version: "3.0.2", Indirect: true}, + {ID: "extsprintf@1.3.0", Name: "extsprintf", Version: "1.3.0", Indirect: true}, + {ID: "fast-deep-equal@3.1.3", Name: "fast-deep-equal", Version: "3.1.3", Indirect: true}, + {ID: "fast-json-stable-stringify@2.1.0", Name: "fast-json-stable-stringify", Version: "2.1.0", Indirect: true}, + {ID: "forever-agent@0.6.1", Name: "forever-agent", Version: "0.6.1", Indirect: true}, + {ID: "form-data@2.3.3", Name: "form-data", Version: "2.3.3", Indirect: true}, + {ID: "getpass@0.1.7", Name: "getpass", Version: "0.1.7", Indirect: true}, + {ID: "har-schema@2.0.0", Name: "har-schema", Version: "2.0.0", Indirect: true}, + {ID: "har-validator@5.1.5", Name: "har-validator", Version: "5.1.5", Indirect: true}, + {ID: "http-signature@1.2.0", Name: "http-signature", Version: "1.2.0", Indirect: true}, + {ID: "is-typedarray@1.0.0", Name: "is-typedarray", Version: "1.0.0", Indirect: true}, + {ID: "isstream@0.1.2", Name: "isstream", Version: "0.1.2", Indirect: true}, + {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Indirect: true}, + {ID: "jsbn@0.1.1", Name: "jsbn", Version: "0.1.1", Indirect: true}, + {ID: "json-schema-traverse@0.4.1", Name: "json-schema-traverse", Version: "0.4.1", Indirect: true}, + {ID: "json-schema@0.4.0", Name: "json-schema", Version: "0.4.0", Indirect: true}, + {ID: "json-stringify-safe@5.0.1", Name: "json-stringify-safe", Version: "5.0.1", Indirect: true}, + {ID: "jsprim@1.4.2", Name: "jsprim", Version: "1.4.2", Indirect: true}, + {ID: "lodash@4.17.21", Name: "lodash", Version: "4.17.21", Indirect: false}, + {ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Indirect: true}, + {ID: "mime-db@1.52.0", Name: "mime-db", Version: "1.52.0", Indirect: true}, + {ID: "mime-types@2.1.35", Name: "mime-types", Version: "2.1.35", Indirect: true}, + {ID: "oauth-sign@0.9.0", Name: "oauth-sign", Version: "0.9.0", Indirect: true}, + {ID: "performance-now@2.1.0", Name: "performance-now", Version: "2.1.0", Indirect: true}, + {ID: "psl@1.8.0", Name: "psl", Version: "1.8.0", Indirect: true}, + {ID: "punycode@2.1.1", Name: "punycode", Version: "2.1.1", Indirect: true}, + {ID: "qs@6.5.3", Name: "qs", Version: "6.5.3", Indirect: true}, + {ID: "react@18.1.0", Name: "react", Version: "18.1.0", Indirect: false}, + {ID: "redux@4.2.0", Name: "redux", Version: "4.2.0", Indirect: false}, + {ID: "regenerator-runtime@0.13.9", Name: "regenerator-runtime", Version: "0.13.9", Indirect: true}, + {ID: "request@2.88.2", Name: "request", Version: "2.88.2", Indirect: false}, + {ID: "safe-buffer@5.2.1", Name: "safe-buffer", Version: "5.2.1", Indirect: true}, + {ID: "safer-buffer@2.1.2", Name: "safer-buffer", Version: "2.1.2", Indirect: true}, + {ID: "sshpk@1.17.0", Name: "sshpk", Version: "1.17.0", Indirect: true}, + {ID: "tough-cookie@2.5.0", Name: "tough-cookie", Version: "2.5.0", Indirect: true}, + {ID: "tunnel-agent@0.6.0", Name: "tunnel-agent", Version: "0.6.0", Indirect: true}, + {ID: "tweetnacl@0.14.5", Name: "tweetnacl", Version: "0.14.5", Indirect: true}, + {ID: "uri-js@4.4.1", Name: "uri-js", Version: "4.4.1", Indirect: true}, + {ID: "uuid@3.4.0", Name: "uuid", Version: "3.4.0", Indirect: true}, + {ID: "verror@1.10.0", Name: "verror", Version: "1.10.0", Indirect: true}, + } + pnpmManyDeps = []types.Dependency{ + {ID: "@babel/runtime@7.18.3", DependsOn: []string{"regenerator-runtime@0.13.9"}}, + {ID: "ajv@6.12.6", DependsOn: []string{"fast-deep-equal@3.1.3", "fast-json-stable-stringify@2.1.0", "json-schema-traverse@0.4.1", "uri-js@4.4.1"}}, + {ID: "asn1@0.2.6", DependsOn: []string{"safer-buffer@2.1.2"}}, + {ID: "bcrypt-pbkdf@1.0.2", DependsOn: []string{"tweetnacl@0.14.5"}}, + {ID: "combined-stream@1.0.8", DependsOn: []string{"delayed-stream@1.0.0"}}, + {ID: "dashdash@1.14.1", DependsOn: []string{"assert-plus@1.0.0"}}, + {ID: "ecc-jsbn@0.1.2", DependsOn: []string{"jsbn@0.1.1", "safer-buffer@2.1.2"}}, + {ID: "form-data@2.3.3", DependsOn: []string{"asynckit@0.4.0", "combined-stream@1.0.8", "mime-types@2.1.35"}}, + {ID: "getpass@0.1.7", DependsOn: []string{"assert-plus@1.0.0"}}, + {ID: "har-validator@5.1.5", DependsOn: []string{"ajv@6.12.6", "har-schema@2.0.0"}}, + {ID: "http-signature@1.2.0", DependsOn: []string{"assert-plus@1.0.0", "jsprim@1.4.2", "sshpk@1.17.0"}}, + {ID: "jsprim@1.4.2", DependsOn: []string{"assert-plus@1.0.0", "extsprintf@1.3.0", "json-schema@0.4.0", "verror@1.10.0"}}, + {ID: "loose-envify@1.4.0", DependsOn: []string{"js-tokens@4.0.0"}}, + {ID: "mime-types@2.1.35", DependsOn: []string{"mime-db@1.52.0"}}, + {ID: "react@18.1.0", DependsOn: []string{"loose-envify@1.4.0"}}, + {ID: "redux@4.2.0", DependsOn: []string{"@babel/runtime@7.18.3"}}, + {ID: "request@2.88.2", DependsOn: []string{"aws-sign2@0.7.0", "aws4@1.11.0", "caseless@0.12.0", "combined-stream@1.0.8", "extend@3.0.2", "forever-agent@0.6.1", "form-data@2.3.3", "har-validator@5.1.5", "http-signature@1.2.0", "is-typedarray@1.0.0", "isstream@0.1.2", "json-stringify-safe@5.0.1", "mime-types@2.1.35", "oauth-sign@0.9.0", "performance-now@2.1.0", "qs@6.5.3", "safe-buffer@5.2.1", "tough-cookie@2.5.0", "tunnel-agent@0.6.0", "uuid@3.4.0"}}, + {ID: "sshpk@1.17.0", DependsOn: []string{"asn1@0.2.6", "assert-plus@1.0.0", "bcrypt-pbkdf@1.0.2", "dashdash@1.14.1", "ecc-jsbn@0.1.2", "getpass@0.1.7", "jsbn@0.1.1", "safer-buffer@2.1.2", "tweetnacl@0.14.5"}}, + {ID: "tough-cookie@2.5.0", DependsOn: []string{"psl@1.8.0", "punycode@2.1.1"}}, + {ID: "tunnel-agent@0.6.0", DependsOn: []string{"safe-buffer@5.2.1"}}, + {ID: "uri-js@4.4.1", DependsOn: []string{"punycode@2.1.1"}}, + {ID: "verror@1.10.0", DependsOn: []string{"assert-plus@1.0.0", "core-util-is@1.0.2", "extsprintf@1.3.0"}}, + } + + // docker run --name node --rm -it node@sha256:710a2c192ca426e03e4f3ec1869e5c29db855eb6969b74e6c50fd270ffccd3f1 sh + // npm install -g pnpm@8.5.1 + // mkdir /temp && cd /temp + // npm install lodash@4.17.21 + // cd ./node_modules/lodash/ + // npm pack + // mkdir -p /app/foo/bar && cd /app + // cp /temp/node_modules/lodash/lodash-4.17.21.tgz /app/foo/bar/lodash.tgz + // npm init -y + // npm install ./foo/bar/lodash.tgz + // mkdir package1 && cd package1 + // npm init -y + // npm install asynckit@0.4.0 + // cd .. + // npm install ./package1 + // pnpm update + // pnpm add https://github.com/debug-js/debug/tarball/4.3.4 + // pnpm add https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5 + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: false},\n")}' | sort -u + // manually update `Indirect` fields + pnpmArchives = []types.Library{ + {ID: "asynckit@0.4.0", Name: "asynckit", Version: "0.4.0", Indirect: true}, + {ID: "debug@4.3.4", Name: "debug", Version: "4.3.4", Indirect: false}, + {ID: "is-negative@2.0.1", Name: "is-negative", Version: "2.0.1", Indirect: false}, + {ID: "lodash@4.17.21", Name: "lodash", Version: "4.17.21", Indirect: false}, + {ID: "ms@2.1.2", Name: "ms", Version: "2.1.2", Indirect: true}, + {ID: "package1@1.0.0", Name: "package1", Version: "1.0.0", Indirect: false}, + } + + pnpmArchivesDeps = []types.Dependency{ + { + ID: "debug@4.3.4", + DependsOn: []string{"ms@2.1.2"}, + }, + { + ID: "package1@1.0.0", + DependsOn: []string{"asynckit@0.4.0"}, + }, + } + + // docker run --name node --rm -it node@sha256:710a2c192ca426e03e4f3ec1869e5c29db855eb6969b74e6c50fd270ffccd3f1 sh + // npm install -g pnpm@8.5.1 + // pnpm add promise@8.1.0 jquery@3.6.0 + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: true},\n")}' | sort -u + pnpmV6 = pnpmNormal + pnpmV6Deps = pnpmNormalDeps + + // docker run --name node --rm -it node@sha256:710a2c192ca426e03e4f3ec1869e5c29db855eb6969b74e6c50fd270ffccd3f1 sh + // npm install -g pnpm@8.5.1 + // pnpm add react@18.1.0 redux@4.2.0 + // pnpm add -D mocha@10.0.0 + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: true},\n")}' | sort -u + pnpmV6WithDev = []types.Library{ + {ID: "@babel/runtime@7.22.3", Name: "@babel/runtime", Version: "7.22.3", Indirect: true}, + {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Indirect: true}, + {ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Indirect: true}, + {ID: "react@18.1.0", Name: "react", Version: "18.1.0", Indirect: false}, + {ID: "redux@4.2.0", Name: "redux", Version: "4.2.0", Indirect: false}, + {ID: "regenerator-runtime@0.13.11", Name: "regenerator-runtime", Version: "0.13.11", Indirect: true}, + } + pnpmV6WithDevDeps = []types.Dependency{ + { + ID: "@babel/runtime@7.22.3", + DependsOn: []string{"regenerator-runtime@0.13.11"}, + }, + { + ID: "loose-envify@1.4.0", + DependsOn: []string{"js-tokens@4.0.0"}, + }, + { + ID: "react@18.1.0", + DependsOn: []string{"loose-envify@1.4.0"}, + }, + { + ID: "redux@4.2.0", + DependsOn: []string{"@babel/runtime@7.22.3"}, + }, + } +) diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_archives.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_archives.yaml new file mode 100644 index 000000000000..7d7ae2fc1d8a --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_archives.yaml @@ -0,0 +1,60 @@ +lockfileVersion: '6.0' + +dependencies: + debug: + specifier: https://github.com/debug-js/debug/tarball/4.3.4 + version: '@github.com/debug-js/debug/tarball/4.3.4' + is-negative: + specifier: https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5 + version: '@codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5' + lodash: + specifier: file:foo/bar/lodash.tgz + version: file:foo/bar/lodash.tgz + package1: + specifier: file:package1 + version: file:package1 + +packages: + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: false + + '@codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5': + resolution: {tarball: https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5} + name: is-negative + version: 2.0.1 + engines: {node: '>=0.10.0'} + dev: false + + '@github.com/debug-js/debug/tarball/4.3.4': + resolution: {tarball: https://github.com/debug-js/debug/tarball/4.3.4} + name: debug + version: 4.3.4 + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: false + + file:foo/bar/lodash.tgz: + resolution: {integrity: sha512-fPftOkGbplay6FszUHWPJ8wV7liS+n2gB/UVN0Wv4G71KJWx+8trhGYsbekWqz6TTzkKur67bAdSIIccmKIyLA==, tarball: file:foo/bar/lodash.tgz} + name: lodash + version: 4.17.21 + dev: false + + file:package1: + resolution: {directory: package1, type: directory} + name: package1 + version: 1.0.0 + dependencies: + asynckit: 0.4.0 + dev: false diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_many.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_many.yaml new file mode 100644 index 000000000000..22ef76912aff --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_many.yaml @@ -0,0 +1,868 @@ +lockfileVersion: 5.4 + +specifiers: + chalk: ^5.0.1 + commander: ^9.3.0 + lodash: ^4.17.21 + mocha: ^10.0.0 + react: ^18.1.0 + redux: ^4.2.0 + request: ^2.88.2 + +dependencies: + chalk: 5.0.1 + commander: 9.3.0 + lodash: 4.17.21 + react: 18.1.0 + redux: 4.2.0 + request: 2.88.2 + +devDependencies: + mocha: 10.0.0 + +packages: + + /@babel/runtime/7.18.3: + resolution: {integrity: sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.9 + dev: false + + /@ungap/promise-all-settled/1.1.2: + resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} + dev: true + + /ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: false + + /ansi-colors/4.1.1: + resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + engines: {node: '>=6'} + dev: true + + /ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /anymatch/3.1.2: + resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /asn1/0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + dependencies: + safer-buffer: 2.1.2 + dev: false + + /assert-plus/1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + dev: false + + /asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /aws-sign2/0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + dev: false + + /aws4/1.11.0: + resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==} + dev: false + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /bcrypt-pbkdf/1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + dependencies: + tweetnacl: 0.14.5 + dev: false + + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /browser-stdout/1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + dev: true + + /camelcase/6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + + /caseless/0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + dev: false + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chalk/5.0.1: + resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false + + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.2 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /cliui/7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + + /commander/9.3.0: + resolution: {integrity: sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==} + engines: {node: ^12.20.0 || >=14} + dev: false + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /core-util-is/1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + dev: false + + /dashdash/1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + dependencies: + assert-plus: 1.0.0 + dev: false + + /debug/4.3.4_supports-color@8.1.1: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + supports-color: 8.1.1 + dev: true + + /decamelize/4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + dev: true + + /delayed-stream/1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /diff/5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + dev: true + + /ecc-jsbn/0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + dev: false + + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /extend/3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false + + /extsprintf/1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + dev: false + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: false + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: false + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /find-up/5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat/5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + dev: true + + /forever-agent/0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + dev: false + + /form-data/2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /get-caller-file/2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /getpass/0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + dependencies: + assert-plus: 1.0.0 + dev: false + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob/7.2.0: + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /har-schema/2.0.0: + resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} + engines: {node: '>=4'} + dev: false + + /har-validator/5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + dev: false + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /he/1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: true + + /http-signature/1.2.0: + resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} + engines: {node: '>=0.8', npm: '>=1.3.7'} + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.2 + sshpk: 1.17.0 + dev: false + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-plain-obj/2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + dev: true + + /is-typedarray/1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + dev: false + + /is-unicode-supported/0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /isstream/0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + dev: false + + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: false + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /jsbn/0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + dev: false + + /json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: false + + /json-schema/0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + dev: false + + /json-stringify-safe/5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: false + + /jsprim/1.4.2: + resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} + engines: {node: '>=0.6.0'} + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + dev: false + + /locate-path/6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + + /log-symbols/4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + + /loose-envify/1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: false + + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch/5.0.1: + resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /mocha/10.0.0: + resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==} + engines: {node: '>= 14.0.0'} + hasBin: true + dependencies: + '@ungap/promise-all-settled': 1.1.2 + ansi-colors: 4.1.1 + browser-stdout: 1.3.1 + chokidar: 3.5.3 + debug: 4.3.4_supports-color@8.1.1 + diff: 5.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 7.2.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.0.1 + ms: 2.1.3 + nanoid: 3.3.3 + serialize-javascript: 6.0.0 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.2.1 + yargs: 16.2.0 + yargs-parser: 20.2.4 + yargs-unparser: 2.0.0 + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /ms/2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + + /nanoid/3.3.3: + resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /oauth-sign/0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + dev: false + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /p-limit/3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-locate/5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /path-exists/4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /performance-now/2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + dev: false + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /psl/1.8.0: + resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==} + dev: false + + /punycode/2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} + dev: false + + /qs/6.5.3: + resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} + engines: {node: '>=0.6'} + dev: false + + /randombytes/2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /react/18.1.0: + resolution: {integrity: sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /redux/4.2.0: + resolution: {integrity: sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==} + dependencies: + '@babel/runtime': 7.18.3 + dev: false + + /regenerator-runtime/0.13.9: + resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} + dev: false + + /request/2.88.2: + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + dependencies: + aws-sign2: 0.7.0 + aws4: 1.11.0 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.3 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.4.0 + dev: false + + /require-directory/2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + /safer-buffer/2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + + /serialize-javascript/6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + dependencies: + randombytes: 2.1.0 + dev: true + + /sshpk/1.17.0: + resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + dev: false + + /string-width/4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-color/8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + dev: true + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /tough-cookie/2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + dependencies: + psl: 1.8.0 + punycode: 2.1.1 + dev: false + + /tunnel-agent/0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /tweetnacl/0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + dev: false + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.1.1 + dev: false + + /uuid/3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + dev: false + + /verror/1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + dev: false + + /workerpool/6.2.1: + resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} + dev: true + + /wrap-ansi/7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /y18n/5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yargs-parser/20.2.4: + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} + dev: true + + /yargs-unparser/2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + dev: true + + /yargs/16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.4 + dev: true + + /yocto-queue/0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_normal.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_normal.yaml new file mode 100644 index 000000000000..f76c340a9660 --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_normal.yaml @@ -0,0 +1,25 @@ +lockfileVersion: 5.3 + +specifiers: + jquery: ^3.6.0 + promise: ^8.1.0 + +dependencies: + jquery: 3.6.0 + promise: 8.1.0 + +packages: + + /asap/2.0.6: + resolution: {integrity: sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=} + dev: false + + /jquery/3.6.0: + resolution: {integrity: sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==} + dev: false + + /promise/8.1.0: + resolution: {integrity: sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==} + dependencies: + asap: 2.0.6 + dev: false \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v6.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v6.yaml new file mode 100644 index 000000000000..c4d0d524896a --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v6.yaml @@ -0,0 +1,25 @@ +lockfileVersion: '6.0' + +dependencies: + jquery: + specifier: 3.6.0 + version: 3.6.0 + promise: + specifier: 8.1.0 + version: 8.1.0 + +packages: + + /asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + dev: false + + /jquery@3.6.0: + resolution: {integrity: sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==} + dev: false + + /promise@8.1.0: + resolution: {integrity: sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==} + dependencies: + asap: 2.0.6 + dev: false diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v6_with_dev.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v6_with_dev.yaml new file mode 100644 index 000000000000..119bb9817bcf --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v6_with_dev.yaml @@ -0,0 +1,563 @@ +lockfileVersion: '6.0' + +dependencies: + react: + specifier: 18.1.0 + version: 18.1.0 + redux: + specifier: 4.2.0 + version: 4.2.0 + +devDependencies: + mocha: + specifier: ^10.0.0 + version: 10.0.0 + +packages: + + /@babel/runtime@7.22.3: + resolution: {integrity: sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + dev: false + + /@ungap/promise-all-settled@1.1.2: + resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} + dev: true + + /ansi-colors@4.1.1: + resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + engines: {node: '>=6'} + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + dev: true + + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /debug@4.3.4(supports-color@8.1.1): + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + supports-color: 8.1.1 + dev: true + + /decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + dev: true + + /diff@5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.0: + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + dev: true + + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: false + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: false + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch@5.0.1: + resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /mocha@10.0.0: + resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==} + engines: {node: '>= 14.0.0'} + hasBin: true + dependencies: + '@ungap/promise-all-settled': 1.1.2 + ansi-colors: 4.1.1 + browser-stdout: 1.3.1 + chokidar: 3.5.3 + debug: 4.3.4(supports-color@8.1.1) + diff: 5.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 7.2.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.0.1 + ms: 2.1.3 + nanoid: 3.3.3 + serialize-javascript: 6.0.0 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.2.1 + yargs: 16.2.0 + yargs-parser: 20.2.4 + yargs-unparser: 2.0.0 + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + + /nanoid@3.3.3: + resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /react@18.1.0: + resolution: {integrity: sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /redux@4.2.0: + resolution: {integrity: sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==} + dependencies: + '@babel/runtime': 7.22.3 + dev: false + + /regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: false + + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /serialize-javascript@6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + dependencies: + randombytes: 2.1.0 + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /workerpool@6.2.1: + resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yargs-parser@20.2.4: + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} + dev: true + + /yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + dev: true + + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.4 + dev: true + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_with_dev.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_with_dev.yaml new file mode 100644 index 000000000000..de07fb897473 --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_with_dev.yaml @@ -0,0 +1,562 @@ +lockfileVersion: 5.4 + +specifiers: + mocha: ^10.0.0 + react: ^18.1.0 + redux: ^4.2.0 + +dependencies: + react: 18.1.0 + redux: 4.2.0 + +devDependencies: + mocha: 10.0.0 + +packages: + + /@babel/runtime/7.18.3: + resolution: {integrity: sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.9 + dev: false + + /@ungap/promise-all-settled/1.1.2: + resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} + dev: true + + /ansi-colors/4.1.1: + resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + engines: {node: '>=6'} + dev: true + + /ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /anymatch/3.1.2: + resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /browser-stdout/1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + dev: true + + /camelcase/6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.2 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /cliui/7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /debug/4.3.4_supports-color@8.1.1: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + supports-color: 8.1.1 + dev: true + + /decamelize/4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + dev: true + + /diff/5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + dev: true + + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /find-up/5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat/5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + dev: true + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /get-caller-file/2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob/7.2.0: + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /he/1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: true + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-plain-obj/2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + dev: true + + /is-unicode-supported/0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: false + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /locate-path/6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /log-symbols/4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + + /loose-envify/1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: false + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch/5.0.1: + resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /mocha/10.0.0: + resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==} + engines: {node: '>= 14.0.0'} + hasBin: true + dependencies: + '@ungap/promise-all-settled': 1.1.2 + ansi-colors: 4.1.1 + browser-stdout: 1.3.1 + chokidar: 3.5.3 + debug: 4.3.4_supports-color@8.1.1 + diff: 5.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 7.2.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.0.1 + ms: 2.1.3 + nanoid: 3.3.3 + serialize-javascript: 6.0.0 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.2.1 + yargs: 16.2.0 + yargs-parser: 20.2.4 + yargs-unparser: 2.0.0 + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /ms/2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + + /nanoid/3.3.3: + resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /p-limit/3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-locate/5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /path-exists/4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /randombytes/2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /react/18.1.0: + resolution: {integrity: sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /redux/4.2.0: + resolution: {integrity: sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==} + dependencies: + '@babel/runtime': 7.18.3 + dev: false + + /regenerator-runtime/0.13.9: + resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} + dev: false + + /require-directory/2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /serialize-javascript/6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + dependencies: + randombytes: 2.1.0 + dev: true + + /string-width/4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-color/8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + dev: true + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /workerpool/6.2.1: + resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} + dev: true + + /wrap-ansi/7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /y18n/5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yargs-parser/20.2.4: + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} + dev: true + + /yargs-unparser/2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + dev: true + + /yargs/16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.4 + dev: true + + /yocto-queue/0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true diff --git a/pkg/dependency/parser/nodejs/yarn/parse.go b/pkg/dependency/parser/nodejs/yarn/parse.go new file mode 100644 index 000000000000..9b8394eb57c8 --- /dev/null +++ b/pkg/dependency/parser/nodejs/yarn/parse.go @@ -0,0 +1,321 @@ +package yarn + +import ( + "bufio" + "bytes" + "io" + "regexp" + "strings" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +var ( + yarnPatternRegexp = regexp.MustCompile(`^\s?\\?"?(?P\S+?)@(?:(?P\S+?):)?(?P.+?)\\?"?:?$`) + yarnVersionRegexp = regexp.MustCompile(`^"?version:?"?\s+"?(?P[^"]+)"?`) + yarnDependencyRegexp = regexp.MustCompile(`\s{4,}"?(?P.+?)"?:?\s"?(?:(?P\S+?):)?(?P[^"]+)"?`) +) + +type LockFile struct { + Dependencies map[string]Dependency +} + +type Library struct { + Patterns []string + Name string + Version string + Location types.Location +} +type Dependency struct { + Pattern string + Name string +} + +type LineScanner struct { + *bufio.Scanner + lineCount int +} + +func NewLineScanner(r io.Reader) *LineScanner { + return &LineScanner{ + Scanner: bufio.NewScanner(r), + } +} + +func (s *LineScanner) Scan() bool { + scan := s.Scanner.Scan() + if scan { + s.lineCount++ + } + return scan +} + +func (s *LineScanner) LineNum(prevNum int) int { + return prevNum + s.lineCount - 1 +} + +func parsePattern(target string) (packagename, protocol, version string, err error) { + capture := yarnPatternRegexp.FindStringSubmatch(target) + if len(capture) < 3 { + return "", "", "", xerrors.New("not package format") + } + for i, group := range yarnPatternRegexp.SubexpNames() { + switch group { + case "package": + packagename = capture[i] + case "protocol": + protocol = capture[i] + case "version": + version = capture[i] + } + } + return +} + +func parsePackagePatterns(target string) (packagename, protocol string, patterns []string, err error) { + patternsSplit := strings.Split(target, ", ") + packagename, protocol, _, err = parsePattern(patternsSplit[0]) + if err != nil { + return "", "", nil, err + } + patterns = lo.Map(patternsSplit, func(pattern string, _ int) string { + _, _, version, _ := parsePattern(pattern) + return packageID(packagename, version) + }) + return +} + +func getVersion(target string) (version string, err error) { + capture := yarnVersionRegexp.FindStringSubmatch(target) + if len(capture) < 2 { + return "", xerrors.Errorf("failed to parse version: '%s", target) + } + return capture[len(capture)-1], nil +} + +func getDependency(target string) (name, version string, err error) { + capture := yarnDependencyRegexp.FindStringSubmatch(target) + if len(capture) < 3 { + return "", "", xerrors.New("not dependency") + } + if !validProtocol(capture[2]) { + return "", "", nil + } + return capture[1], capture[3], nil +} + +func validProtocol(protocol string) bool { + switch protocol { + // only scan npm packages + case "npm", "": + return true + } + return false +} + +func ignoreProtocol(protocol string) bool { + switch protocol { + case "workspace", "patch", "file", "link", "portal", "github", "git", "git+ssh", "git+http", "git+https", "git+file": + return true + } + return false +} + +func parseResults(patternIDs map[string]string, dependsOn map[string][]string) (deps []types.Dependency) { + // find dependencies by patterns + for libID, depPatterns := range dependsOn { + depIDs := lo.Map(depPatterns, func(pattern string, index int) string { + return patternIDs[pattern] + }) + deps = append(deps, types.Dependency{ + ID: libID, + DependsOn: depIDs, + }) + } + return deps +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func scanBlocks(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if i := bytes.Index(data, []byte("\n\n")); i >= 0 { + // We have a full newline-terminated line. + return i + 2, data[0:i], nil + } else if i := bytes.Index(data, []byte("\r\n\r\n")); i >= 0 { + return i + 4, data[0:i], nil + } + + // If we're at EOF, we have a final, non-terminated line. Return it. + if atEOF { + return len(data), data, nil + } + // Request more data. + return 0, nil, nil +} + +func parseBlock(block []byte, lineNum int) (lib Library, deps []string, newLine int, err error) { + var ( + emptyLines int // lib can start with empty lines first + skipBlock bool + ) + + scanner := NewLineScanner(bytes.NewReader(block)) + for scanner.Scan() { + line := scanner.Text() + + if line == "" { + emptyLines++ + continue + } + + if line[0] == '#' || skipBlock { + continue + } + + // Skip this block + if strings.HasPrefix(line, "__metadata") { + skipBlock = true + continue + } + + line = strings.TrimPrefix(strings.TrimSpace(line), "\"") + + switch { + case strings.HasPrefix(line, "version"): + if lib.Version, err = getVersion(line); err != nil { + skipBlock = true + } + continue + case strings.HasPrefix(line, "dependencies:"): + // start dependencies block + deps = parseDependencies(scanner) + continue + } + + // try parse package patterns + if name, protocol, patterns, patternErr := parsePackagePatterns(line); patternErr == nil { + if patterns == nil || !validProtocol(protocol) { + skipBlock = true + if !ignoreProtocol(protocol) { + // we need to calculate the last line of the block in order to correctly determine the line numbers of the next blocks + // store the error. we will handle it later + err = xerrors.Errorf("unknown protocol: '%s', line: %s", protocol, line) + continue + } + continue + } else { + lib.Patterns = patterns + lib.Name = name + continue + } + } + } + + // in case an unsupported protocol is detected + // show warning and continue parsing + if err != nil { + log.Logger.Warnf("Yarn protocol error: %s", err) + return Library{}, nil, scanner.LineNum(lineNum), nil + } + + lib.Location = types.Location{ + StartLine: lineNum + emptyLines, + EndLine: scanner.LineNum(lineNum), + } + + if scanErr := scanner.Err(); scanErr != nil { + err = scanErr + } + + return lib, deps, scanner.LineNum(lineNum), err +} + +func parseDependencies(scanner *LineScanner) (deps []string) { + for scanner.Scan() { + line := scanner.Text() + if dep, err := parseDependency(line); err != nil { + // finished dependencies block + return deps + } else { + deps = append(deps, dep) + } + } + + return +} + +func parseDependency(line string) (string, error) { + if name, version, err := getDependency(line); err != nil { + return "", err + } else { + return packageID(name, version), nil + } +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + lineNumber := 1 + var libs []types.Library + + // patternIDs holds mapping between patterns and library IDs + // e.g. ajv@^6.5.5 => ajv@6.10.0 + patternIDs := make(map[string]string) + + scanner := bufio.NewScanner(r) + scanner.Split(scanBlocks) + dependsOn := make(map[string][]string) + for scanner.Scan() { + block := scanner.Bytes() + lib, deps, newLine, err := parseBlock(block, lineNumber) + lineNumber = newLine + 2 + if err != nil { + return nil, nil, err + } else if lib.Name == "" { + continue + } + + libID := packageID(lib.Name, lib.Version) + libs = append(libs, types.Library{ + ID: libID, + Name: lib.Name, + Version: lib.Version, + Locations: []types.Location{lib.Location}, + }) + + for _, pattern := range lib.Patterns { + // e.g. + // combined-stream@^1.0.6 => combined-stream@1.0.8 + // combined-stream@~1.0.6 => combined-stream@1.0.8 + patternIDs[pattern] = libID + if len(deps) > 0 { + dependsOn[libID] = deps + } + } + } + + if err := scanner.Err(); err != nil { + return nil, nil, xerrors.Errorf("failed to scan yarn.lock, got scanner error: %s", err.Error()) + } + + // Replace dependency patterns with library IDs + // e.g. ajv@^6.5.5 => ajv@6.10.0 + deps := parseResults(patternIDs, dependsOn) + return libs, deps, nil +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.Yarn, name, version) +} diff --git a/pkg/dependency/parser/nodejs/yarn/parse_test.go b/pkg/dependency/parser/nodejs/yarn/parse_test.go new file mode 100644 index 000000000000..90ce497ed9d8 --- /dev/null +++ b/pkg/dependency/parser/nodejs/yarn/parse_test.go @@ -0,0 +1,348 @@ +package yarn + +import ( + "os" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParsePattern(t *testing.T) { + vectors := []struct { + name string + target string + expectName string + expectProtocol string + expactVersion string + occurErr bool + }{ + { + name: "normal", + target: `asn1@~0.2.3:`, + expectName: "asn1", + expactVersion: "~0.2.3", + }, + { + name: "normal with protocol", + target: `asn1@npm:~0.2.3:`, + expectName: "asn1", + expectProtocol: "npm", + expactVersion: "~0.2.3", + }, + { + name: "scope", + target: `@babel/code-frame@^7.0.0:`, + expectName: "@babel/code-frame", + expactVersion: "^7.0.0", + }, + { + name: "scope with protocol", + target: `@babel/code-frame@npm:^7.0.0:`, + expectName: "@babel/code-frame", + expectProtocol: "npm", + expactVersion: "^7.0.0", + }, + { + name: "scope with protocol and quotes", + target: `"@babel/code-frame@npm:^7.0.0":`, + expectName: "@babel/code-frame", + expectProtocol: "npm", + expactVersion: "^7.0.0", + }, + { + name: "unusual version", + target: `grunt-contrib-cssmin@3.0.*:`, + expectName: "grunt-contrib-cssmin", + expactVersion: "3.0.*", + }, + { + name: "conditional version", + target: `"js-tokens@^3.0.0 || ^4.0.0":`, + expectName: "js-tokens", + expactVersion: "^3.0.0 || ^4.0.0", + }, + { + target: "grunt-contrib-uglify-es@gruntjs/grunt-contrib-uglify#harmony:", + expectName: "grunt-contrib-uglify-es", + expactVersion: "gruntjs/grunt-contrib-uglify#harmony", + }, + { + target: `"jquery@git+https://xxxx:x-oauth-basic@github.com/tomoyamachi/jquery":`, + expectName: "jquery", + expectProtocol: "git+https", + expactVersion: "//xxxx:x-oauth-basic@github.com/tomoyamachi/jquery", + }, + { + target: `normal line`, + occurErr: true, + }, + } + + for _, v := range vectors { + gotName, gotProtocol, gotVersion, err := parsePattern(v.target) + + if v.occurErr != (err != nil) { + t.Errorf("expect error %t but err is %s", v.occurErr, err) + continue + } + + if gotName != v.expectName { + t.Errorf("name mismatch: got %s, want %s, target :%s", gotName, v.expectName, v.target) + } + + if gotProtocol != v.expectProtocol { + t.Errorf("protocol mismatch: got %s, want %s, target :%s", gotProtocol, v.expectProtocol, v.target) + } + + if gotVersion != v.expactVersion { + t.Errorf("version mismatch: got %s, want %s, target :%s", gotVersion, v.expactVersion, v.target) + } + } +} + +func TestParsePackagePatterns(t *testing.T) { + vectors := []struct { + name string + target string + expectName string + expectProtocol string + expactPatterns []string + occurErr bool + }{ + { + name: "normal", + target: `asn1@~0.2.3:`, + expectName: "asn1", + expactPatterns: []string{ + "asn1@~0.2.3", + }, + }, + { + name: "normal with quotes", + target: `"asn1@~0.2.3":`, + expectName: "asn1", + expactPatterns: []string{ + "asn1@~0.2.3", + }, + }, + { + name: "normal with protocol", + target: `asn1@npm:~0.2.3:`, + expectName: "asn1", + expectProtocol: "npm", + expactPatterns: []string{ + "asn1@~0.2.3", + }, + }, + { + name: "multiple patterns", + target: `loose-envify@^1.1.0, loose-envify@^1.4.0:`, + expectName: "loose-envify", + expactPatterns: []string{ + "loose-envify@^1.1.0", + "loose-envify@^1.4.0", + }, + }, + { + name: "multiple patterns v2", + target: `"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0":`, + expectName: "loose-envify", + expectProtocol: "npm", + expactPatterns: []string{ + "loose-envify@^1.1.0", + "loose-envify@^1.4.0", + }, + }, + { + target: `normal line`, + occurErr: true, + }, + } + + for _, v := range vectors { + gotName, gotProtocol, gotPatterns, err := parsePackagePatterns(v.target) + + if v.occurErr != (err != nil) { + t.Errorf("expect error %t but err is %s", v.occurErr, err) + continue + } + + if gotName != v.expectName { + t.Errorf("name mismatch: got %s, want %s, target: %s", gotName, v.expectName, v.target) + } + + if gotProtocol != v.expectProtocol { + t.Errorf("protocol mismatch: got %s, want %s, target: %s", gotProtocol, v.expectProtocol, v.target) + } + + sort.Strings(gotPatterns) + sort.Strings(v.expactPatterns) + + assert.Equal(t, v.expactPatterns, gotPatterns) + } +} + +func TestGetDependency(t *testing.T) { + vectors := []struct { + name string + target string + expectName string + expactVersion string + occurErr bool + }{ + { + name: "normal", + target: ` chalk "^2.0.1"`, + expectName: "chalk", + expactVersion: "^2.0.1", + }, + { + name: "range", + target: ` js-tokens "^3.0.0 || ^4.0.0"`, + expectName: "js-tokens", + expactVersion: "^3.0.0 || ^4.0.0", + }, + { + name: "normal v2", + target: ` depd: ~1.1.2`, + expectName: "depd", + expactVersion: "~1.1.2", + }, + { + name: "range version v2", + target: ` statuses: ">= 1.5.0 < 2"`, + expectName: "statuses", + expactVersion: ">= 1.5.0 < 2", + }, + { + name: "name with scope", + target: ` "@types/color-name": ^1.1.1`, + expectName: "@types/color-name", + expactVersion: "^1.1.1", + }, + { + name: "version with protocol", + target: ` ms: "npm:2.1.2"`, + expectName: "ms", + expactVersion: "2.1.2", + }, + } + + for _, v := range vectors { + gotName, gotVersion, err := getDependency(v.target) + + if v.occurErr != (err != nil) { + t.Errorf("expect error %t but err is %s", v.occurErr, err) + continue + } + + if gotName != v.expectName { + t.Errorf("name mismatch: got %s, want %s, target: %s", gotName, v.expectName, v.target) + } + + if gotVersion != v.expactVersion { + t.Errorf("version mismatch: got %s, want %s, target: %s", gotVersion, v.expactVersion, v.target) + } + } +} + +func TestParse(t *testing.T) { + tests := []struct { + name string + file string // Test input file + want []types.Library + wantDeps []types.Dependency + }{ + { + name: "happy", + file: "testdata/yarn_happy.lock", + want: yarnHappy, + wantDeps: yarnHappyDeps, + }, + { + file: "testdata/yarn_with_npm.lock", + want: yarnWithNpm, + }, + { + name: "happy v2", + file: "testdata/yarn_v2_happy.lock", + want: yarnV2Happy, + wantDeps: yarnV2HappyDeps, + }, + { + name: "yarn with local dependency", + file: "testdata/yarn_with_local.lock", + want: yarnWithLocal, + wantDeps: yarnWithLocalDeps, + }, + { + name: "yarn v2 with protocols in dependency section", + file: "testdata/yarn_v2_deps_with_protocol.lock", + want: yarnV2DepsWithProtocol, + wantDeps: yarnV2DepsWithProtocolDeps, + }, + { + name: "yarn with git dependency", + file: "testdata/yarn_with_git.lock", + }, + { + name: "yarn file with bad protocol", + file: "testdata/yarn_with_bad_protocol.lock", + want: yarnBadProtocol, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + + got, deps, err := NewParser().Parse(f) + require.NoError(t, err) + + sortLibs(got) + sortLibs(tt.want) + + assert.Equal(t, tt.want, got) + if tt.wantDeps != nil { + sortDeps(deps) + sortDeps(tt.wantDeps) + assert.Equal(t, tt.wantDeps, deps) + } + }) + } +} + +func sortDeps(deps []types.Dependency) { + sort.Slice(deps, func(i, j int) bool { + return strings.Compare(deps[i].ID, deps[j].ID) < 0 + }) + + for i := range deps { + sort.Strings(deps[i].DependsOn) + } +} + +func sortLibs(libs []types.Library) { + sort.Slice(libs, func(i, j int) bool { + ret := strings.Compare(libs[i].Name, libs[j].Name) + if ret == 0 { + return libs[i].Version < libs[j].Version + } + return ret < 0 + }) + for _, lib := range libs { + sortLocations(lib.Locations) + } +} + +func sortLocations(locs []types.Location) { + sort.Slice(locs, func(i, j int) bool { + return locs[i].StartLine < locs[j].StartLine + }) +} diff --git a/pkg/dependency/parser/nodejs/yarn/parse_testcase.go b/pkg/dependency/parser/nodejs/yarn/parse_testcase.go new file mode 100644 index 000000000000..e9d48c246f29 --- /dev/null +++ b/pkg/dependency/parser/nodejs/yarn/parse_testcase.go @@ -0,0 +1,160 @@ +package yarn + +import "github.com/aquasecurity/trivy/pkg/dependency/types" + +var ( + yarnHappy = []types.Library{ + {ID: "@babel/helper-regex@7.4.4", Name: "@babel/helper-regex", Version: "7.4.4", Locations: []types.Location{{StartLine: 4, EndLine: 9}}}, + {ID: "ansi-regex@2.1.1", Name: "ansi-regex", Version: "2.1.1", Locations: []types.Location{{StartLine: 11, EndLine: 14}}}, + {ID: "ansi-regex@3.0.0", Name: "ansi-regex", Version: "3.0.0", Locations: []types.Location{{StartLine: 16, EndLine: 19}}}, + {ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Locations: []types.Location{{StartLine: 21, EndLine: 24}}}, + {ID: "inherits@2.0.3", Name: "inherits", Version: "2.0.3", Locations: []types.Location{{StartLine: 26, EndLine: 29}}}, + {ID: "is-fullwidth-code-point@2.0.0", Name: "is-fullwidth-code-point", Version: "2.0.0", Locations: []types.Location{{StartLine: 31, EndLine: 34}}}, + {ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []types.Location{{StartLine: 41, EndLine: 44}}}, + {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Locations: []types.Location{{StartLine: 36, EndLine: 39}}}, + {ID: "lodash@4.17.11", Name: "lodash", Version: "4.17.11", Locations: []types.Location{{StartLine: 46, EndLine: 49}}}, + {ID: "promise@8.0.3", Name: "promise", Version: "8.0.3", Locations: []types.Location{{StartLine: 51, EndLine: 56}}}, + {ID: "safe-buffer@5.1.2", Name: "safe-buffer", Version: "5.1.2", Locations: []types.Location{{StartLine: 58, EndLine: 61}}}, + {ID: "safer-buffer@2.1.2", Name: "safer-buffer", Version: "2.1.2", Locations: []types.Location{{StartLine: 63, EndLine: 66}}}, + {ID: "statuses@1.5.0", Name: "statuses", Version: "1.5.0", Locations: []types.Location{{StartLine: 68, EndLine: 71}}}, + {ID: "string-width@2.1.1", Name: "string-width", Version: "2.1.1", Locations: []types.Location{{StartLine: 73, EndLine: 79}}}, + {ID: "strip-ansi@3.0.1", Name: "strip-ansi", Version: "3.0.1", Locations: []types.Location{{StartLine: 82, EndLine: 87}}}, + {ID: "strip-ansi@4.0.0", Name: "strip-ansi", Version: "4.0.0", Locations: []types.Location{{StartLine: 89, EndLine: 94}}}, + {ID: "whatwg-fetch@3.0.0", Name: "whatwg-fetch", Version: "3.0.0", Locations: []types.Location{{StartLine: 96, EndLine: 99}}}, + {ID: "wide-align@1.1.3", Name: "wide-align", Version: "1.1.3", Locations: []types.Location{{StartLine: 101, EndLine: 106}}}, + } + + yarnHappyDeps = []types.Dependency{ + { + ID: "@babel/helper-regex@7.4.4", + DependsOn: []string{ + "lodash@4.17.11", + }, + }, + { + ID: "promise@8.0.3", + DependsOn: []string{ + "asap@2.0.6", + }, + }, + { + ID: "string-width@2.1.1", + DependsOn: []string{ + "is-fullwidth-code-point@2.0.0", + "strip-ansi@4.0.0", + }, + }, + { + ID: "strip-ansi@3.0.1", + DependsOn: []string{ + "ansi-regex@2.1.1", + }, + }, + { + ID: "strip-ansi@4.0.0", + DependsOn: []string{ + "ansi-regex@3.0.0", + }, + }, + { + ID: "wide-align@1.1.3", + DependsOn: []string{ + "string-width@2.1.1", + }, + }, + } + + yarnV2Happy = []types.Library{ + {ID: "@types/color-name@1.1.1", Name: "@types/color-name", Version: "1.1.1", Locations: []types.Location{{StartLine: 8, EndLine: 13}}}, + {ID: "abbrev@1.1.1", Name: "abbrev", Version: "1.1.1", Locations: []types.Location{{StartLine: 15, EndLine: 20}}}, + {ID: "ansi-styles@3.2.1", Name: "ansi-styles", Version: "3.2.1", Locations: []types.Location{{StartLine: 22, EndLine: 29}}}, + {ID: "ansi-styles@4.2.1", Name: "ansi-styles", Version: "4.2.1", Locations: []types.Location{{StartLine: 31, EndLine: 39}}}, + {ID: "assert-plus@1.0.0", Name: "assert-plus", Version: "1.0.0", Locations: []types.Location{{StartLine: 41, EndLine: 46}}}, + {ID: "async@3.2.0", Name: "async", Version: "3.2.0", Locations: []types.Location{{StartLine: 48, EndLine: 53}}}, + {ID: "color-convert@1.9.3", Name: "color-convert", Version: "1.9.3", Locations: []types.Location{{StartLine: 63, EndLine: 70}}}, + {ID: "color-convert@2.0.1", Name: "color-convert", Version: "2.0.1", Locations: []types.Location{{StartLine: 72, EndLine: 79}}}, + {ID: "color-name@1.1.3", Name: "color-name", Version: "1.1.3", Locations: []types.Location{{StartLine: 81, EndLine: 86}}}, + {ID: "color-name@1.1.4", Name: "color-name", Version: "1.1.4", Locations: []types.Location{{StartLine: 88, EndLine: 93}}}, + {ID: "ipaddr.js@1.9.1", Name: "ipaddr.js", Version: "1.9.1", Locations: []types.Location{{StartLine: 104, EndLine: 109}}}, + {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Locations: []types.Location{{StartLine: 111, EndLine: 116}}}, + {ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Locations: []types.Location{{StartLine: 118, EndLine: 127}}}, + {ID: "node-gyp@7.1.0", Name: "node-gyp", Version: "7.1.0", Locations: []types.Location{{StartLine: 129, EndLine: 136}}}, + {ID: "once@1.4.0", Name: "once", Version: "1.4.0", Locations: []types.Location{{StartLine: 138, EndLine: 145}}}, + {ID: "wrappy@1.0.2", Name: "wrappy", Version: "1.0.2", Locations: []types.Location{{StartLine: 147, EndLine: 152}}}, + } + + yarnV2HappyDeps = []types.Dependency{ + { + ID: "ansi-styles@3.2.1", + DependsOn: []string{ + "color-convert@1.9.3", + }, + }, + { + ID: "ansi-styles@4.2.1", + DependsOn: []string{ + "@types/color-name@1.1.1", + "color-convert@2.0.1", + }, + }, + { + ID: "color-convert@1.9.3", + DependsOn: []string{ + "color-name@1.1.3", + }, + }, + { + ID: "color-convert@2.0.1", + DependsOn: []string{ + "color-name@1.1.4", + }, + }, + { + ID: "loose-envify@1.4.0", + DependsOn: []string{ + "js-tokens@4.0.0", + }, + }, + { + ID: "once@1.4.0", + DependsOn: []string{ + "wrappy@1.0.2", + }, + }, + } + + yarnWithLocal = []types.Library{ + {ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Locations: []types.Location{{StartLine: 5, EndLine: 8}}}, + {ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []types.Location{{StartLine: 10, EndLine: 13}}}, + {ID: "promise@8.0.3", Name: "promise", Version: "8.0.3", Locations: []types.Location{{StartLine: 15, EndLine: 20}}}, + } + + yarnWithLocalDeps = []types.Dependency{ + { + ID: "promise@8.0.3", + DependsOn: []string{ + "asap@2.0.6", + }, + }, + } + + yarnWithNpm = []types.Library{ + {ID: "jquery@3.6.0", Name: "jquery", Version: "3.6.0", Locations: []types.Location{{StartLine: 1, EndLine: 4}}}, + } + + yarnBadProtocol = []types.Library{ + {ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []types.Location{{StartLine: 4, EndLine: 7}}}, + } + + yarnV2DepsWithProtocol = []types.Library{ + {ID: "debug@4.3.4", Name: "debug", Version: "4.3.4", Locations: []types.Location{{StartLine: 16, EndLine: 26}}}, + {ID: "ms@2.1.2", Name: "ms", Version: "2.1.2", Locations: []types.Location{{StartLine: 28, EndLine: 33}}}, + } + + yarnV2DepsWithProtocolDeps = []types.Dependency{ + { + ID: "debug@4.3.4", + DependsOn: []string{"ms@2.1.2"}, + }, + } +) diff --git a/pkg/dependency/parser/nodejs/yarn/testdata/yarn_happy.lock b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_happy.lock new file mode 100644 index 000000000000..582df0e3c377 --- /dev/null +++ b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_happy.lock @@ -0,0 +1,106 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.4.4.tgz#a47e02bc91fb259d2e6727c2a30013e3ac13c4a2" + integrity sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q== + dependencies: + lodash "^4.17.11" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +inherits@2, inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jquery@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== + +lodash@4.17.11, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +promise@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.0.3.tgz#f592e099c6cddc000d538ee7283bb190452b0bf6" + integrity sha512-HeRDUL1RJiLhyA0/grn+PTShlBAcLuh/1BJGtrvjwbvRDCTLLMEz9rOGCV+R3vHY4MixIuoMEd9Yq/XvsTPcjw== + dependencies: + asap "~2.0.6" + +safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +whatwg-fetch@>=0.10.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== + +wide-align@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/yarn/testdata/yarn_v2_deps_with_protocol.lock b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_v2_deps_with_protocol.lock new file mode 100644 index 000000000000..b42fd4f3c5e3 --- /dev/null +++ b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_v2_deps_with_protocol.lock @@ -0,0 +1,33 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"app@workspace:.": + version: 0.0.0-use.local + resolution: "app@workspace:." + dependencies: + debug: "npm:4.3.4" + languageName: unknown + linkType: soft + +"debug@npm:4.3.4": + version: 4.3.4 + resolution: "debug@npm:4.3.4" + dependencies: + ms: "npm:2.1.2" + peerDependenciesMeta: + supports-color: + optional: true + checksum: cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736 + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc + languageName: node + linkType: hard diff --git a/pkg/dependency/parser/nodejs/yarn/testdata/yarn_v2_happy.lock b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_v2_happy.lock new file mode 100644 index 000000000000..d693d3085670 --- /dev/null +++ b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_v2_happy.lock @@ -0,0 +1,152 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 4 + cacheKey: 6 + +"@types/color-name@npm:^1.1.1": + version: 1.1.1 + resolution: "@types/color-name@npm:1.1.1" + checksum: 8311db94a9c4ecd247763b81e783ee49d87678b4ce6a7ee502e2bd5cea242b7357804a04855db009f713174bc654cc0c01c7303d40d757e5d710f5ac0368500f + languageName: node + linkType: hard + +"abbrev@npm:1": + version: 1.1.1 + resolution: "abbrev@npm:1.1.1" + checksum: 9f9236a3cc7f56c167be3aa81c77fcab2e08dfb8047b7861b91440f20b299b9442255856bdbe9d408d7e96a0b64a36e1c27384251126962490b4eee841b533e0 + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.0": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: ^1.9.0 + checksum: 456e1c23d9277512a47718da75e7fbb0a5ee215ef893c2f76d6b3efe8fceabc861121b80b0362146f5f995d21a1633f05a19bbf6283ae66ac11dc3b9c0bed779 + languageName: node + linkType: hard + +"ansi-styles@npm:^4.1.0": + version: 4.2.1 + resolution: "ansi-styles@npm:4.2.1" + dependencies: + "@types/color-name": ^1.1.1 + color-convert: ^2.0.1 + checksum: c8c007d5dab7b4fea064c9ea318114e1f6fc714bb382d061ac09e66bc83c8f3ce12bb6354be01598722c14a5d710af280b7614d269354f80d2535946aefa82f4 + languageName: node + linkType: hard + +"assert-plus@npm:1.0.0, assert-plus@npm:^1.0.0": + version: 1.0.0 + resolution: "assert-plus@npm:1.0.0" + checksum: 1bda24f67343ccb75a7eee31179c92cf9f79bd6f6bc24101b0ce1495ef979376dd9b0f9b9064812bba564cdade5fbf851ed76b4a44b5e141d49cdaee6ffed6b2 + languageName: node + linkType: hard + +"async@npm:^3.2.0": + version: 3.2.0 + resolution: "async@npm:3.2.0" + checksum: 5c7913c08496877a9896dc6670d3a6c64f02d350e74b9e9191194959c473414a0732539ebdfec0fd2f34c20f439714773a30c20e0e68eb27bd8ee5ec9d8ff5ba + languageName: node + linkType: hard + +"code@workspace:.": + version: 0.0.0-use.local + resolution: "code@workspace:." + dependencies: + async: ^3.2.0 + languageName: unknown + linkType: soft + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: 1.1.3 + checksum: 5f244daa3d1fe1f216d48878c550465067d15268688308554e613b7640a068f96588096d51f0b98b68f15d6ff6bb8ad24e172582ac8c0ad43fa4d3da60fd1b79 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: ~1.1.4 + checksum: 3d5d8a011a43012ca11b6d739049ecf2055d95582fd16ec44bf1e685eb0baa5cc652002be8a1dc92b429c8d87418287d0528266a7595cb1ad8a7f4f1d3049df2 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: d8b91bb90aefc05b6ff568cf8889566dcc6269824df6f3c9b8ca842b18d7f4d089c07dc166808d33f22092d4a79167aa56a96a5ff0d21efab548bf44614db43b + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 3e1c9a4dee12eada307436f61614dd11fe300469db2b83f80c8b7a7cd8a1015f0f18dd13403f018927b249003777ff60baba4a03c65f12e6bddc0dfd9642021f + languageName: node + linkType: hard + +"fsevents@patch:fsevents@~2.1.2#builtin": + version: 2.1.3 + resolution: "fsevents@patch:fsevents@npm%3A2.1.3#builtin::version=2.1.3&hash=87eb42" + dependencies: + node-gyp: latest + checksum: d8ae862048fc127cdbd00d02b2feb7c25946c3ce4cbc44e958134be87e239577a16dafafa1c270d010b8624e1b1e0372e23f7865c55c6f83e83fc9f68b0a30d2 + languageName: node + linkType: hard + +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: de15bc7e63973d960abc43c9fbbf19589c726774f59d157d1b29382a1e86ae87c68cbd8b5c78a1712a87fc4fcd91e10762c7671950c66a1a19040ff4fd2f9c9b + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 1fc4e4667ac2d972aba65148b9cbf9c17566b2394d3504238d8492bbd3e68f496c657eab06b26b40b17db5cac0a34d153a12130e2d2d2bb6dc2cdc8a4764eb1b + languageName: node + linkType: hard + +"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: ^3.0.0 || ^4.0.0 + bin: + loose-envify: cli.js + checksum: 5c3b47bbe5f597a3889fb001a3a98aaea2a3fafa48089c19034de1e0121bf57dbee609d184478514d74d5c5a7e9cfa3d846343455e5123b060040d46c39e91dc + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 7.1.0 + resolution: "node-gyp@npm:7.1.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 78518a89047fdacb14c41586ce038584e21993f5c7ad31834c78cf06de0514fe4ef84a9034461695a10667bc81ee9ad8bc7d725cf951d4dfe1c0c175d763da59 + languageName: node + linkType: hard + +"once@npm:^1.3.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: 1 + checksum: 57afc246536cf6494437f982b26475f22bee860f8b77ce8eb1543f42a8bffe04b2c66ddfea9a16cb25ccb80943f8ee4fc639367ef97b7a6a4f2672eb573963f5 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 519fcda0fcdf0c16327be2de9d98646742307bc830277e8868529fcf7566f2b330a6453c233e0cdcb767d5838dd61a90984a02ecc983bcddebea5ad0833bbf98 + languageName: node + linkType: hard \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_bad_protocol.lock b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_bad_protocol.lock new file mode 100644 index 000000000000..0dbcb99dde9b --- /dev/null +++ b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_bad_protocol.lock @@ -0,0 +1,7 @@ +asap@unsupported:~2.0.6: + version "2.0.6" + +jquery@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_git.lock b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_git.lock new file mode 100644 index 000000000000..171f64ed8b16 --- /dev/null +++ b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_git.lock @@ -0,0 +1,7 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"mapbox-gl-feature-drag-and-drop@git+https://github.com/jrd/mapbox-gl-feature-drag-and-drop.git": + version "1.1.1" + resolved "git+https://github.com/jrd/mapbox-gl-feature-drag-and-drop.git#538b1126e0d15210e91b4a34ffc223b4fec1ef87" \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_local.lock b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_local.lock new file mode 100644 index 000000000000..e1df9a7fa7d1 --- /dev/null +++ b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_local.lock @@ -0,0 +1,24 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +jquery@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== + +promise@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.0.3.tgz#f592e099c6cddc000d538ee7283bb190452b0bf6" + integrity sha512-HeRDUL1RJiLhyA0/grn+PTShlBAcLuh/1BJGtrvjwbvRDCTLLMEz9rOGCV+R3vHY4MixIuoMEd9Yq/XvsTPcjw== + dependencies: + asap "~2.0.6" + +"local-package@file:vendor/local-package.tar.gz": + version "0.0.1" + resolved "file:vendor/local-package.tar.gz#f0b5161c241e8c94acc6241c49e3ab6806e6d4d7" \ No newline at end of file diff --git a/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_npm.lock b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_npm.lock new file mode 100644 index 000000000000..936ea2a6ab40 --- /dev/null +++ b/pkg/dependency/parser/nodejs/yarn/testdata/yarn_with_npm.lock @@ -0,0 +1,4 @@ +"jquery@^3.6.0": + "integrity" "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + "resolved" "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz" + "version" "3.6.0" \ No newline at end of file diff --git a/pkg/dependency/parser/nuget/config/parse.go b/pkg/dependency/parser/nuget/config/parse.go new file mode 100644 index 000000000000..6a43d30ddf94 --- /dev/null +++ b/pkg/dependency/parser/nuget/config/parse.go @@ -0,0 +1,53 @@ +package config + +import ( + "encoding/xml" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type cfgPackageReference struct { + XMLName xml.Name `xml:"package"` + TargetFramework string `xml:"targetFramework,attr"` + Version string `xml:"version,attr"` + DevDependency bool `xml:"developmentDependency,attr"` + ID string `xml:"id,attr"` +} + +type config struct { + XMLName xml.Name `xml:"packages"` + Packages []cfgPackageReference `xml:"package"` +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var cfgData config + if err := xml.NewDecoder(r).Decode(&cfgData); err != nil { + return nil, nil, xerrors.Errorf("failed to decode .config file: %w", err) + } + + var libs []types.Library + for _, pkg := range cfgData.Packages { + if pkg.ID == "" || pkg.DevDependency { + continue + } + + lib := types.Library{ + Name: pkg.ID, + Version: pkg.Version, + } + + libs = append(libs, lib) + } + + return utils.UniqueLibraries(libs), nil, nil +} diff --git a/pkg/dependency/parser/nuget/config/parse_test.go b/pkg/dependency/parser/nuget/config/parse_test.go new file mode 100644 index 000000000000..864246bc9c44 --- /dev/null +++ b/pkg/dependency/parser/nuget/config/parse_test.go @@ -0,0 +1,58 @@ +package config_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/config" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string // Test input file + inputFile string + want []types.Library + wantErr string + }{ + { + name: "Config", + inputFile: "testdata/packages.config", + want: []types.Library{ + {Name: "Newtonsoft.Json", Version: "6.0.4"}, + {Name: "Microsoft.AspNet.WebApi", Version: "5.2.2"}, + }, + }, + { + name: "with development dependency", + inputFile: "testdata/dev_dependency.config", + want: []types.Library{ + {Name: "Newtonsoft.Json", Version: "8.0.3"}, + }, + }, + { + name: "sad path", + inputFile: "testdata/malformed_xml.config", + wantErr: "failed to decode .config file: XML syntax error on line 5: unexpected EOF", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + + got, _, err := config.NewParser().Parse(f) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + assert.NoError(t, err) + assert.ElementsMatch(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/nuget/config/testdata/dev_dependency.config b/pkg/dependency/parser/nuget/config/testdata/dev_dependency.config new file mode 100644 index 000000000000..49c08076e17a --- /dev/null +++ b/pkg/dependency/parser/nuget/config/testdata/dev_dependency.config @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/dependency/parser/nuget/config/testdata/malformed_xml.config b/pkg/dependency/parser/nuget/config/testdata/malformed_xml.config new file mode 100644 index 000000000000..6fdc75174fe9 --- /dev/null +++ b/pkg/dependency/parser/nuget/config/testdata/malformed_xml.config @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/dependency/parser/nuget/config/testdata/packages.config b/pkg/dependency/parser/nuget/config/testdata/packages.config new file mode 100644 index 000000000000..51ea35d31a83 --- /dev/null +++ b/pkg/dependency/parser/nuget/config/testdata/packages.config @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/dependency/parser/nuget/lock/parse.go b/pkg/dependency/parser/nuget/lock/parse.go new file mode 100644 index 000000000000..30205362db8a --- /dev/null +++ b/pkg/dependency/parser/nuget/lock/parse.go @@ -0,0 +1,113 @@ +package lock + +import ( + "io" + + "github.com/liamg/jfather" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type LockFile struct { + Version int `json:"version"` + Targets map[string]Dependencies `json:"dependencies"` +} + +type Dependencies map[string]Dependency + +type Dependency struct { + Type string `json:"type"` + Resolved string `json:"resolved"` + StartLine int + EndLine int + Dependencies map[string]string `json:"dependencies,omitempty"` +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var lockFile LockFile + input, err := io.ReadAll(r) + if err != nil { + return nil, nil, xerrors.Errorf("failed to read packages.lock.json: %w", err) + } + if err := jfather.Unmarshal(input, &lockFile); err != nil { + return nil, nil, xerrors.Errorf("failed to decode packages.lock.json: %w", err) + } + + var libs []types.Library + depsMap := make(map[string][]string) + for _, targetContent := range lockFile.Targets { + for packageName, packageContent := range targetContent { + // If package type is "project", it is the actual project, and we skip it. + if packageContent.Type == "Project" { + continue + } + + depId := packageID(packageName, packageContent.Resolved) + + lib := types.Library{ + ID: depId, + Name: packageName, + Version: packageContent.Resolved, + Indirect: packageContent.Type != "Direct", + Locations: []types.Location{ + { + StartLine: packageContent.StartLine, + EndLine: packageContent.EndLine, + }, + }, + } + libs = append(libs, lib) + + var dependsOn []string + + for depName := range packageContent.Dependencies { + dependsOn = append(dependsOn, packageID(depName, targetContent[depName].Resolved)) + } + + if savedDependsOn, ok := depsMap[depId]; ok { + dependsOn = utils.UniqueStrings(append(dependsOn, savedDependsOn...)) + } + + if len(dependsOn) > 0 { + depsMap[depId] = dependsOn + } + } + } + + var deps []types.Dependency + for depId, dependsOn := range depsMap { + dep := types.Dependency{ + ID: depId, + DependsOn: dependsOn, + } + deps = append(deps, dep) + } + + return utils.UniqueLibraries(libs), deps, nil +} + +// UnmarshalJSONWithMetadata needed to detect start and end lines of deps +func (t *Dependency) UnmarshalJSONWithMetadata(node jfather.Node) error { + if err := node.Decode(&t); err != nil { + return err + } + // Decode func will overwrite line numbers if we save them first + t.StartLine = node.Range().Start.Line + t.EndLine = node.Range().End.Line + return nil +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.NuGet, name, version) +} diff --git a/pkg/dependency/parser/nuget/lock/parse_test.go b/pkg/dependency/parser/nuget/lock/parse_test.go new file mode 100644 index 000000000000..04ddb22244df --- /dev/null +++ b/pkg/dependency/parser/nuget/lock/parse_test.go @@ -0,0 +1,87 @@ +package lock + +import ( + "os" + "path" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + vectors := []struct { + file string // Test input file + want []types.Library + wantDeps []types.Dependency + }{ + { + file: "testdata/packages_lock_simple.json", + want: nuGetSimple, + wantDeps: nuGetSimpleDeps, + }, + { + file: "testdata/packages_lock_subdependencies.json", + want: nuGetSubDependencies, + wantDeps: nuGetSubDependenciesDeps, + }, + { + file: "testdata/packages_lock_multi.json", + want: nuGetMultiTarget, + wantDeps: nuGetMultiTargetDeps, + }, + { + file: "testdata/packages_lock_legacy.json", + want: nuGetLegacy, + wantDeps: nuGetLegacyDeps, + }, + } + + for _, v := range vectors { + t.Run(path.Base(v.file), func(t *testing.T) { + f, err := os.Open(v.file) + require.NoError(t, err) + + got, deps, err := NewParser().Parse(f) + require.NoError(t, err) + + sort.Slice(got, func(i, j int) bool { + ret := strings.Compare(got[i].Name, got[j].Name) + if ret == 0 { + return got[i].Version < got[j].Version + } + return ret < 0 + }) + + sort.Slice(v.want, func(i, j int) bool { + ret := strings.Compare(v.want[i].Name, v.want[j].Name) + if ret == 0 { + return v.want[i].Version < v.want[j].Version + } + return ret < 0 + }) + + assert.Equal(t, v.want, got) + + if v.wantDeps != nil { + sortDeps(deps) + sortDeps(v.wantDeps) + assert.Equal(t, v.wantDeps, deps) + } + }) + } +} + +func sortDeps(deps []types.Dependency) { + sort.Slice(deps, func(i, j int) bool { + return strings.Compare(deps[i].ID, deps[j].ID) < 0 + }) + + for i := range deps { + sort.Strings(deps[i].DependsOn) + } +} diff --git a/pkg/dependency/parser/nuget/lock/parse_testcase.go b/pkg/dependency/parser/nuget/lock/parse_testcase.go new file mode 100644 index 000000000000..699db9f8c037 --- /dev/null +++ b/pkg/dependency/parser/nuget/lock/parse_testcase.go @@ -0,0 +1,145 @@ +package lock + +import "github.com/aquasecurity/trivy/pkg/dependency/types" + +var ( + // docker run --rm -i -t mcr.microsoft.com/dotnet/sdk:latest + // apt -y update && apt -y install jq + // cd /usr/local/src + // dotnet new mvc + // dotnet add package Newtonsoft.Json + // dotnet add package NuGet.Frameworks + // dotnet restore --use-lock-file + // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' + nuGetSimple = []types.Library{ + {ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", Indirect: false, Locations: []types.Location{{StartLine: 5, EndLine: 10}}}, + {ID: "NuGet.Frameworks@5.7.0", Name: "NuGet.Frameworks", Version: "5.7.0", Indirect: false, Locations: []types.Location{{StartLine: 11, EndLine: 16}}}, + } + nuGetSimpleDeps []types.Dependency + + // docker run --rm -i -t mcr.microsoft.com/dotnet/sdk:latest + // apt -y update && apt -y install jq + // cd /usr/local/src + // dotnet new webapi + // dotnet add package Newtonsoft.Json + // dotnet add package NuGet.Frameworks + // dotnet restore --use-lock-file + // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' + nuGetSubDependencies = []types.Library{ + {ID: "Microsoft.Extensions.ApiDescription.Server@3.0.0", Name: "Microsoft.Extensions.ApiDescription.Server", Version: "3.0.0", Indirect: true, Locations: []types.Location{{StartLine: 29, EndLine: 33}}}, + {ID: "Microsoft.OpenApi@1.1.4", Name: "Microsoft.OpenApi", Version: "1.1.4", Indirect: true, Locations: []types.Location{{StartLine: 34, EndLine: 38}}}, + {ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", Indirect: false, Locations: []types.Location{{StartLine: 5, EndLine: 10}}}, + {ID: "NuGet.Frameworks@5.7.0", Name: "NuGet.Frameworks", Version: "5.7.0", Indirect: false, Locations: []types.Location{{StartLine: 11, EndLine: 16}}}, + {ID: "Swashbuckle.AspNetCore@5.5.1", Name: "Swashbuckle.AspNetCore", Version: "5.5.1", Indirect: false, Locations: []types.Location{{StartLine: 17, EndLine: 28}}}, + {ID: "Swashbuckle.AspNetCore.Swagger@5.5.1", Name: "Swashbuckle.AspNetCore.Swagger", Version: "5.5.1", Indirect: true, Locations: []types.Location{{StartLine: 39, EndLine: 46}}}, + {ID: "Swashbuckle.AspNetCore.SwaggerGen@5.5.1", Name: "Swashbuckle.AspNetCore.SwaggerGen", Version: "5.5.1", Indirect: true, Locations: []types.Location{{StartLine: 47, EndLine: 54}}}, + {ID: "Swashbuckle.AspNetCore.SwaggerUI@5.5.1", Name: "Swashbuckle.AspNetCore.SwaggerUI", Version: "5.5.1", Indirect: true, Locations: []types.Location{{StartLine: 55, EndLine: 59}}}, + } + nuGetSubDependenciesDeps = []types.Dependency{ + {ID: "Swashbuckle.AspNetCore.Swagger@5.5.1", DependsOn: []string{"Microsoft.OpenApi@1.1.4"}}, + {ID: "Swashbuckle.AspNetCore.SwaggerGen@5.5.1", DependsOn: []string{"Swashbuckle.AspNetCore.Swagger@5.5.1"}}, + {ID: "Swashbuckle.AspNetCore@5.5.1", DependsOn: []string{"Microsoft.Extensions.ApiDescription.Server@3.0.0", "Swashbuckle.AspNetCore.Swagger@5.5.1", "Swashbuckle.AspNetCore.SwaggerGen@5.5.1", "Swashbuckle.AspNetCore.SwaggerUI@5.5.1"}}} + + // mcr.microsoft.com/dotnet/sdk:latest + // apt -y update && apt -y install jq + // cd /usr/local/src + // dotnet new console + // dotnet add package Newtonsoft.Json + // dotnet add package AWSSDK.Core + // dotnet restore --use-lock-file + // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' + nuGetLegacy = []types.Library{ + {ID: "AWSSDK.Core@3.5.1.30", Name: "AWSSDK.Core", Version: "3.5.1.30", Indirect: false, Locations: []types.Location{{StartLine: 5, EndLine: 10}}}, + {ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", Indirect: false, Locations: []types.Location{{StartLine: 11, EndLine: 16}}}, + } + nuGetLegacyDeps []types.Dependency + + // docker run --rm -i -t mcr.microsoft.com/dotnet/sdk:latest + // apt -y update && apt -y install jq + // cd /usr/local/src + // dotnet new classlib -f net5.0 + // sed -i 's~TargetFramework>net5.0net4.0;netstandard2.0;netstandard1.0;net35;net2.0 + + + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/nuget/packagesprops/testdata/no_item_group.props b/pkg/dependency/parser/nuget/packagesprops/testdata/no_item_group.props new file mode 100644 index 000000000000..aa3b28138667 --- /dev/null +++ b/pkg/dependency/parser/nuget/packagesprops/testdata/no_item_group.props @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/nuget/packagesprops/testdata/no_project.props b/pkg/dependency/parser/nuget/packagesprops/testdata/no_project.props new file mode 100644 index 000000000000..041498a6cc24 --- /dev/null +++ b/pkg/dependency/parser/nuget/packagesprops/testdata/no_project.props @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/pkg/dependency/parser/nuget/packagesprops/testdata/packages.props b/pkg/dependency/parser/nuget/packagesprops/testdata/packages.props new file mode 100644 index 000000000000..7ec6d02a1083 --- /dev/null +++ b/pkg/dependency/parser/nuget/packagesprops/testdata/packages.props @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/nuget/packagesprops/testdata/several_item_groups b/pkg/dependency/parser/nuget/packagesprops/testdata/several_item_groups new file mode 100644 index 000000000000..1575af4ae724 --- /dev/null +++ b/pkg/dependency/parser/nuget/packagesprops/testdata/several_item_groups @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/nuget/packagesprops/testdata/variables_and_empty b/pkg/dependency/parser/nuget/packagesprops/testdata/variables_and_empty new file mode 100644 index 000000000000..d801b1f723f1 --- /dev/null +++ b/pkg/dependency/parser/nuget/packagesprops/testdata/variables_and_empty @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/php/composer/parse.go b/pkg/dependency/parser/php/composer/parse.go new file mode 100644 index 000000000000..1fbf4316db9a --- /dev/null +++ b/pkg/dependency/parser/php/composer/parse.go @@ -0,0 +1,113 @@ +package composer + +import ( + "io" + "sort" + "strings" + + "github.com/liamg/jfather" + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type lockFile struct { + Packages []packageInfo `json:"packages"` +} +type packageInfo struct { + Name string `json:"name"` + Version string `json:"version"` + Require map[string]string `json:"require"` + License []string `json:"license"` + StartLine int + EndLine int +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var lockFile lockFile + input, err := io.ReadAll(r) + if err != nil { + return nil, nil, xerrors.Errorf("read error: %w", err) + } + if err = jfather.Unmarshal(input, &lockFile); err != nil { + return nil, nil, xerrors.Errorf("decode error: %w", err) + } + + libs := make(map[string]types.Library) + foundDeps := make(map[string][]string) + for _, pkg := range lockFile.Packages { + lib := types.Library{ + ID: dependency.ID(ftypes.Composer, pkg.Name, pkg.Version), + Name: pkg.Name, + Version: pkg.Version, + Indirect: false, // composer.lock file doesn't have info about Direct/Indirect deps. Will think that all dependencies are Direct + License: strings.Join(pkg.License, ", "), + Locations: []types.Location{ + { + StartLine: pkg.StartLine, + EndLine: pkg.EndLine, + }, + }, + } + libs[lib.Name] = lib + + var dependsOn []string + for depName := range pkg.Require { + // Require field includes required php version, skip this + // Also skip PHP extensions + if depName == "php" || strings.HasPrefix(depName, "ext") { + continue + } + dependsOn = append(dependsOn, depName) // field uses range of versions, so later we will fill in the versions from the libraries + } + if len(dependsOn) > 0 { + foundDeps[lib.ID] = dependsOn + } + } + + // fill deps versions + var deps []types.Dependency + for libID, depsOn := range foundDeps { + var dependsOn []string + for _, depName := range depsOn { + if lib, ok := libs[depName]; ok { + dependsOn = append(dependsOn, lib.ID) + continue + } + log.Logger.Debugf("unable to find version of %s", depName) + } + sort.Strings(dependsOn) + deps = append(deps, types.Dependency{ + ID: libID, + DependsOn: dependsOn, + }) + } + + libSlice := maps.Values(libs) + sort.Sort(types.Libraries(libSlice)) + sort.Sort(types.Dependencies(deps)) + + return libSlice, deps, nil +} + +// UnmarshalJSONWithMetadata needed to detect start and end lines of deps +func (t *packageInfo) UnmarshalJSONWithMetadata(node jfather.Node) error { + if err := node.Decode(&t); err != nil { + return err + } + // Decode func will overwrite line numbers if we save them first + t.StartLine = node.Range().Start.Line + t.EndLine = node.Range().End.Line + return nil +} diff --git a/pkg/dependency/parser/php/composer/parse_test.go b/pkg/dependency/parser/php/composer/parse_test.go new file mode 100644 index 000000000000..8c80899bc4ba --- /dev/null +++ b/pkg/dependency/parser/php/composer/parse_test.go @@ -0,0 +1,171 @@ +package composer + +import ( + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "os" + "testing" +) + +var ( + // docker run --name composer --rm -it composer@sha256:082ed124b68e7e880721772a6bf22ad809e3bc87db8bbee9f0ec7127bb21ccad bash + // apk add jq + // composer require guzzlehttp/guzzle:6.5.8 + // composer require pear/log:1.13.3 --dev + // composer show -i --no-dev -f json | jq --sort-keys -rc '.installed[] | "{ID: \"\(.name)@\(.version)\", Name: \"\(.name)\", Version: \"\(.version)\", License: \"MIT\", Locations: []types.Location{{StartLine: , EndLine: }}},"' + // locations are filled manually + composerLibs = []types.Library{ + { + ID: "guzzlehttp/guzzle@6.5.8", + Name: "guzzlehttp/guzzle", + Version: "6.5.8", + License: "MIT", + Locations: []types.Location{ + { + StartLine: 9, + EndLine: 123, + }, + }, + }, + { + ID: "guzzlehttp/promises@1.5.2", + Name: "guzzlehttp/promises", + Version: "1.5.2", + License: "MIT", + Locations: []types.Location{ + { + StartLine: 124, + EndLine: 207, + }, + }, + }, + { + ID: "guzzlehttp/psr7@1.9.0", + Name: "guzzlehttp/psr7", + Version: "1.9.0", + License: "MIT", + Locations: []types.Location{ + { + StartLine: 208, + EndLine: 317, + }, + }, + }, + { + ID: "psr/http-message@1.0.1", + Name: "psr/http-message", + Version: "1.0.1", + License: "MIT", + Locations: []types.Location{ + { + StartLine: 318, + EndLine: 370, + }, + }, + }, + { + ID: "ralouphie/getallheaders@3.0.3", + Name: "ralouphie/getallheaders", + Version: "3.0.3", + License: "MIT", + Locations: []types.Location{ + { + StartLine: 371, + EndLine: 414, + }, + }, + }, + { + ID: "symfony/polyfill-intl-idn@v1.27.0", + Name: "symfony/polyfill-intl-idn", + Version: "v1.27.0", + License: "MIT", + Locations: []types.Location{ + { + StartLine: 415, + EndLine: 501, + }, + }, + }, + { + ID: "symfony/polyfill-intl-normalizer@v1.27.0", + Name: "symfony/polyfill-intl-normalizer", + Version: "v1.27.0", + License: "MIT", + Locations: []types.Location{ + { + StartLine: 502, + EndLine: 585, + }, + }, + }, + { + ID: "symfony/polyfill-php72@v1.27.0", + Name: "symfony/polyfill-php72", + Version: "v1.27.0", + License: "MIT", + Locations: []types.Location{ + { + StartLine: 586, + EndLine: 661, + }, + }, + }, + } + // dependencies are filled manually + composerDeps = []types.Dependency{ + { + ID: "guzzlehttp/guzzle@6.5.8", + DependsOn: []string{ + "guzzlehttp/promises@1.5.2", + "guzzlehttp/psr7@1.9.0", + "symfony/polyfill-intl-idn@v1.27.0", + }, + }, + { + ID: "guzzlehttp/psr7@1.9.0", + DependsOn: []string{ + "psr/http-message@1.0.1", + "ralouphie/getallheaders@3.0.3", + }, + }, + { + ID: "symfony/polyfill-intl-idn@v1.27.0", + DependsOn: []string{ + "symfony/polyfill-intl-normalizer@v1.27.0", + "symfony/polyfill-php72@v1.27.0", + }, + }, + } +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + file string + wantLibs []types.Library + wantDeps []types.Dependency + }{ + { + name: "happy path", + file: "testdata/composer_happy.lock", + wantLibs: composerLibs, + wantDeps: composerDeps, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + defer f.Close() + + gotLibs, gotDeps, err := NewParser().Parse(f) + require.NoError(t, err) + + assert.Equal(t, tt.wantLibs, gotLibs) + assert.Equal(t, tt.wantDeps, gotDeps) + }) + } +} diff --git a/pkg/dependency/parser/php/composer/testdata/composer_happy.lock b/pkg/dependency/parser/php/composer/testdata/composer_happy.lock new file mode 100644 index 000000000000..2987ab8d58d9 --- /dev/null +++ b/pkg/dependency/parser/php/composer/testdata/composer_happy.lock @@ -0,0 +1,792 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "99c95f97c9ab9a0045cd1f3b910a5b77", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "6.5.8", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a52f0440530b54fa079ce76e8c5d196a42cad981", + "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.9", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.17" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/6.5.8" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-06-20T22:16:07+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "b94b2807d85443f9719887892882d0329d1e2598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", + "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2022-08-28T14:55:35+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", + "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.9.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-06-20T21:43:03+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", + "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", + "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + } + ], + "packages-dev": [ + { + "name": "pear/log", + "version": "1.13.3", + "source": { + "type": "git", + "url": "https://github.com/pear/Log.git", + "reference": "21af0be11669194d72d88b5ee9d5f176dc75d9a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Log/zipball/21af0be11669194d72d88b5ee9d5f176dc75d9a3", + "reference": "21af0be11669194d72d88b5ee9d5f176dc75d9a3", + "shasum": "" + }, + "require": { + "pear/pear_exception": "1.0.1 || 1.0.2", + "php": ">5.2" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "pear/db": "Install optionally via your project's composer.json" + }, + "type": "library", + "autoload": { + "psr-0": { + "Log": "./" + }, + "exclude-from-classmap": [ + "/examples/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jon Parise", + "email": "jon@php.net", + "homepage": "http://www.indelible.org", + "role": "Developer" + } + ], + "description": "PEAR Logging Framework", + "homepage": "http://pear.github.io/Log/", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/pear/Log/issues", + "source": "https://github.com/pear/Log" + }, + "time": "2021-05-04T23:51:30+00:00" + }, + { + "name": "pear/pear_exception", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/pear/PEAR_Exception.git", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "<9" + }, + "type": "class", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "PEAR/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "." + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Helgi Thormar", + "email": "dufuz@php.net" + }, + { + "name": "Greg Beaver", + "email": "cellog@php.net" + } + ], + "description": "The PEAR Exception base class.", + "homepage": "https://github.com/pear/PEAR_Exception", + "keywords": [ + "exception" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception", + "source": "https://github.com/pear/PEAR_Exception" + }, + "time": "2021-03-21T15:43:46+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} \ No newline at end of file diff --git a/pkg/dependency/parser/python/packaging/parse.go b/pkg/dependency/parser/python/packaging/parse.go new file mode 100644 index 000000000000..41514872fbb7 --- /dev/null +++ b/pkg/dependency/parser/python/packaging/parse.go @@ -0,0 +1,87 @@ +package packaging + +import ( + "bufio" + "errors" + "io" + "net/textproto" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +// Parse parses egg and wheel metadata. +// e.g. .egg-info/PKG-INFO and dist-info/METADATA +func (*Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + rd := textproto.NewReader(bufio.NewReader(r)) + h, err := rd.ReadMIMEHeader() + if e := textproto.ProtocolError(""); errors.As(err, &e) { + // A MIME header may contain bytes in the key or value outside the set allowed by RFC 7230. + // cf. https://cs.opensource.google/go/go/+/a6642e67e16b9d769a0c08e486ba08408064df19 + // However, our required key/value could have been correctly parsed, + // so we continue with the subsequent process. + log.Logger.Debugf("MIME protocol error: %s", err) + } else if err != nil && err != io.EOF { + return nil, nil, xerrors.Errorf("read MIME error: %w", err) + } + + name, version := h.Get("name"), h.Get("version") + if name == "" || version == "" { + return nil, nil, xerrors.New("name or version is empty") + } + + // "License-Expression" takes precedence in accordance with https://peps.python.org/pep-0639/#deprecate-license-field + // Although keep in mind that pep-0639 is still in draft. + var license string + if le := h.Get("License-Expression"); le != "" { + license = le + } else { + // Get possible multiple occurrences of licenses from "Classifier: License" field + // When present it should define the license whereas "License" would define any additional exceptions or modifications + // ref. https://packaging.python.org/en/latest/specifications/core-metadata/#license + var licenses []string + for _, classifier := range h.Values("Classifier") { + if strings.HasPrefix(classifier, "License :: ") { + values := strings.Split(classifier, " :: ") + licenseName := values[len(values)-1] + // According to the classifier list https://pypi.org/classifiers/ there is one classifier which seems more like a grouping + // It has no specific license definition (Classifier: License :: OSI Approved) - it is skipped + if licenseName != "OSI Approved" { + licenses = append(licenses, licenseName) + } + } + } + license = strings.Join(licenses, ", ") + + if l := h.Get("License"); l != "" { + if len(licenses) != 0 { + log.Logger.Infof("License acquired from METADATA classifiers may be subject to additional terms for [%s:%s]", name, version) + } else { + license = l + } + } + + } + + if license == "" && h.Get("License-File") != "" { + license = "file://" + h.Get("License-File") + } + + return []types.Library{ + { + Name: name, + Version: version, + License: license, + }, + }, nil, nil +} diff --git a/pkg/dependency/parser/python/packaging/parse_test.go b/pkg/dependency/parser/python/packaging/parse_test.go new file mode 100644 index 000000000000..ee08c5bdca82 --- /dev/null +++ b/pkg/dependency/parser/python/packaging/parse_test.go @@ -0,0 +1,147 @@ +package packaging_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/packaging" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + input string + want []types.Library + wantErr bool + }{ + // listing dependencies based on METADATA/PKG-INFO files + // docker run --name pipenv --rm -it python:3.7-alpine /bin/sh + // pip install pipenv + // find / -wholename "*(dist-info/METADATA|.egg-info/PKG-INFO)" | xargs -I {} sh -c 'cat {} | grep -e "^Name:" -e "^Version:" -e "^License:"' | tee METADATAS + // cat METADATAS | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{for(i=1;i<=NF;i=i+3){printf "\{\""$i"\", \""$(i+1)"\", \""$(i+2)"\"\}\n"}}' + + { + name: "egg PKG-INFO", + input: "testdata/setuptools-51.3.3-py3.8.egg-info.PKG-INFO", + + // docker run --name python --rm -it python:3.9-alpine sh + // apk add py3-setuptools + // cd /usr/lib/python3.9/site-packages/setuptools-52.0.0-py3.9.egg-info/ + // cat PKG-INFO | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | \ + // tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}' + want: []types.Library{{Name: "setuptools", Version: "51.3.3", License: "UNKNOWN"}}, + }, + { + name: "egg PKG-INFO with description containing non-RFC 7230 bytes", + input: "testdata/unidecode-egg-info.PKG-INFO", + want: []types.Library{ + { + Name: "Unidecode", + Version: "0.4.1", + License: "UNKNOWN", + }, + }, + }, + { + name: "egg-info", + input: "testdata/distlib-0.3.1-py3.9.egg-info", + + // docker run --name python --rm -it python:3.9-alpine sh + // apk add py3-distlib + // cd /usr/lib/python3.9/site-packages/ + // cat distlib-0.3.1-py3.9.egg-info | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | \ + // tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}' + want: []types.Library{{Name: "distlib", Version: "0.3.1", License: "Python license"}}, + }, + { + name: "wheel METADATA", + input: "testdata/simple-0.1.0.METADATA", + + // finding relevant metadata files for tests + // mkdir dist-infos + // find / -wholename "*dist-info/METADATA" | rev | cut -d '/' -f2- | rev | xargs -I % cp -r % dist-infos/ + // find dist-infos/ | grep -v METADATA | xargs rm -R + + // for single METADATA file with known name + // cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}' + want: []types.Library{{Name: "simple", Version: "0.1.0", License: ""}}, + }, + { + name: "wheel METADATA", + + // for single METADATA file with known name + // cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}' + input: "testdata/distlib-0.3.1.METADATA", + want: []types.Library{{Name: "distlib", Version: "0.3.1", License: "Python Software Foundation License"}}, + }, + { + name: "wheel METADATA", + // Input defines "Classifier: License" but it ends at "OSI Approved" which doesn't define any specific license, thus "License" field is added to results + input: "testdata/asyncssh-2.14.2.METADATA", + + want: []types.Library{{Name: "asyncssh", Version: "2.14.2", License: "Eclipse Public License v2.0"}}, + }, + { + name: "wheel METADATA", + // Input defines multiple "Classifier: License" + input: "testdata/pyphen-0.14.0.METADATA", + + want: []types.Library{ + {Name: "pyphen", Version: "0.14.0", License: "GNU General Public License v2 or later (GPLv2+), GNU Lesser General Public License v2 or later (LGPLv2+), Mozilla Public License 1.1 (MPL 1.1)"}, + }, + }, + { + name: "invalid", + input: "testdata/invalid.json", + wantErr: true, + }, + { + name: "with License-Expression field", + input: "testdata/iniconfig-2.0.0.METADATA", + want: []types.Library{ + { + Name: "iniconfig", + Version: "2.0.0", + License: "MIT", + }, + }, + }, + { + name: "with an empty license field but with license in Classifier", + input: "testdata/zipp-3.12.1.METADATA", + want: []types.Library{ + { + Name: "zipp", + Version: "3.12.1", + License: "MIT License", + }, + }, + }, + { + name: "without licenses, but with a license file (a license in Classifier was removed)", + input: "testdata/networkx-3.0.METADATA", + want: []types.Library{ + { + Name: "networkx", + Version: "3.0", + License: "file://LICENSE.txt", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.input) + require.NoError(t, err) + + got, _, err := packaging.NewParser().Parse(f) + require.Equal(t, tt.wantErr, err != nil) + + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/python/packaging/testdata/asyncssh-2.14.2.METADATA b/pkg/dependency/parser/python/packaging/testdata/asyncssh-2.14.2.METADATA new file mode 100644 index 000000000000..4896c3f45c2e --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/asyncssh-2.14.2.METADATA @@ -0,0 +1,46 @@ +Metadata-Version: 2.1 +Name: asyncssh +Version: 2.14.2 +Summary: AsyncSSH: Asynchronous SSHv2 client and server library +Home-page: http://asyncssh.timeheart.net +Author: Ron Frederick +Author-email: ronf@timeheart.net +License: Eclipse Public License v2.0 +Project-URL: Documentation, https://asyncssh.readthedocs.io +Project-URL: Source, https://github.com/ronf/asyncssh +Project-URL: Tracker, https://github.com/ronf/asyncssh/issues +Platform: Any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Internet +Classifier: Topic :: Security :: Cryptography +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Networking +Requires-Python: >= 3.6 +License-File: LICENSE +Requires-Dist: cryptography (>=39.0) +Requires-Dist: typing-extensions (>=3.6) +Provides-Extra: bcrypt +Requires-Dist: bcrypt (>=3.1.3) ; extra == 'bcrypt' +Provides-Extra: fido2 +Requires-Dist: fido2 (>=0.9.2) ; extra == 'fido2' +Provides-Extra: gssapi +Requires-Dist: gssapi (>=1.2.0) ; extra == 'gssapi' +Provides-Extra: libnacl +Requires-Dist: libnacl (>=1.4.2) ; extra == 'libnacl' +Provides-Extra: pkcs11 +Requires-Dist: python-pkcs11 (>=0.7.0) ; extra == 'pkcs11' +Provides-Extra: pyopenssl +Requires-Dist: pyOpenSSL (>=23.0.0) ; extra == 'pyopenssl' +Provides-Extra: pywin32 +Requires-Dist: pywin32 (>=227) ; extra == 'pywin32' diff --git a/pkg/dependency/parser/python/packaging/testdata/distlib-0.3.1-py3.9.egg-info b/pkg/dependency/parser/python/packaging/testdata/distlib-0.3.1-py3.9.egg-info new file mode 100644 index 000000000000..95c76f43e0dd --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/distlib-0.3.1-py3.9.egg-info @@ -0,0 +1,30 @@ +Metadata-Version: 1.1 +Name: distlib +Version: 0.3.1 +Summary: Distribution utilities +Home-page: https://bitbucket.org/pypa/distlib +Author: Vinay Sajip +Author-email: vinay_sajip@red-dove.com +License: Python license +Download-URL: https://bitbucket.org/pypa/distlib/downloads/distlib-0.3.1.zip +Description: Low-level components of distutils2/packaging, augmented with higher-level APIs for making packaging easier. + +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Topic :: Software Development +Classifier: Topic :: Utilities diff --git a/pkg/dependency/parser/python/packaging/testdata/distlib-0.3.1.METADATA b/pkg/dependency/parser/python/packaging/testdata/distlib-0.3.1.METADATA new file mode 100644 index 000000000000..54f5f6497f32 --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/distlib-0.3.1.METADATA @@ -0,0 +1,24 @@ +Metadata-Version: 1.1 +Name: distlib +Version: 0.3.1 +Summary: Distribution utilities +Description: Low-level components of distutils2/packaging, augmented with higher-level APIs for making packaging easier. +Home-page: https://bitbucket.org/pypa/distlib +Author: Vinay Sajip +Author-email: vinay_sajip@red-dove.com +License: Python license +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Download-URL: https://bitbucket.org/pypa/distlib/downloads/distlib-0.3.1.zip diff --git a/pkg/dependency/parser/python/packaging/testdata/iniconfig-2.0.0.METADATA b/pkg/dependency/parser/python/packaging/testdata/iniconfig-2.0.0.METADATA new file mode 100644 index 000000000000..3ea1e01cb025 --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/iniconfig-2.0.0.METADATA @@ -0,0 +1,80 @@ +Metadata-Version: 2.1 +Name: iniconfig +Version: 2.0.0 +Summary: brain-dead simple config-ini parsing +Project-URL: Homepage, https://github.com/pytest-dev/iniconfig +Author-email: Ronny Pfannschmidt , Holger Krekel +License-Expression: MIT +License-File: LICENSE +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst + +iniconfig: brain-dead simple parsing of ini files +======================================================= + +iniconfig is a small and simple INI-file parser module +having a unique set of features: + +* maintains order of sections and entries +* supports multi-line values with or without line-continuations +* supports "#" comments everywhere +* raises errors with proper line-numbers +* no bells and whistles like automatic substitutions +* iniconfig raises an Error if two sections have the same name. + +If you encounter issues or have feature wishes please report them to: + + https://github.com/RonnyPfannschmidt/iniconfig/issues + +Basic Example +=================================== + +If you have an ini file like this: + +.. code-block:: ini + + # content of example.ini + [section1] # comment + name1=value1 # comment + name1b=value1,value2 # comment + + [section2] + name2= + line1 + line2 + +then you can do: + +.. code-block:: pycon + + >>> import iniconfig + >>> ini = iniconfig.IniConfig("example.ini") + >>> ini['section1']['name1'] # raises KeyError if not exists + 'value1' + >>> ini.get('section1', 'name1b', [], lambda x: x.split(",")) + ['value1', 'value2'] + >>> ini.get('section1', 'notexist', [], lambda x: x.split(",")) + [] + >>> [x.name for x in list(ini)] + ['section1', 'section2'] + >>> list(list(ini)[0].items()) + [('name1', 'value1'), ('name1b', 'value1,value2')] + >>> 'section1' in ini + True + >>> 'inexistendsection' in ini + False diff --git a/pkg/dependency/parser/python/packaging/testdata/invalid.json b/pkg/dependency/parser/python/packaging/testdata/invalid.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/invalid.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pkg/dependency/parser/python/packaging/testdata/networkx-3.0.METADATA b/pkg/dependency/parser/python/packaging/testdata/networkx-3.0.METADATA new file mode 100644 index 000000000000..ad434a2057fc --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/networkx-3.0.METADATA @@ -0,0 +1,132 @@ +Metadata-Version: 2.1 +Name: networkx +Version: 3.0 +Summary: Python package for creating and manipulating graphs and networks +Home-page: https://networkx.org/ +Author: Aric Hagberg +Author-email: hagberg@lanl.gov +Maintainer: NetworkX Developers +Maintainer-email: networkx-discuss@googlegroups.com +Project-URL: Bug Tracker, https://github.com/networkx/networkx/issues +Project-URL: Documentation, https://networkx.org/documentation/stable/ +Project-URL: Source Code, https://github.com/networkx/networkx +Keywords: Networks,Graph Theory,Mathematics,network,graph,discrete mathematics,math +Platform: Linux +Platform: Mac OSX +Platform: Windows +Platform: Unix +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Science/Research +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Scientific/Engineering :: Bio-Informatics +Classifier: Topic :: Scientific/Engineering :: Information Analysis +Classifier: Topic :: Scientific/Engineering :: Mathematics +Classifier: Topic :: Scientific/Engineering :: Physics +Requires-Python: >=3.8 +License-File: LICENSE.txt +Provides-Extra: default +Requires-Dist: numpy (>=1.20) ; extra == 'default' +Requires-Dist: scipy (>=1.8) ; extra == 'default' +Requires-Dist: matplotlib (>=3.4) ; extra == 'default' +Requires-Dist: pandas (>=1.3) ; extra == 'default' +Provides-Extra: developer +Requires-Dist: pre-commit (>=2.20) ; extra == 'developer' +Requires-Dist: mypy (>=0.991) ; extra == 'developer' +Provides-Extra: doc +Requires-Dist: sphinx (==5.2.3) ; extra == 'doc' +Requires-Dist: pydata-sphinx-theme (>=0.11) ; extra == 'doc' +Requires-Dist: sphinx-gallery (>=0.11) ; extra == 'doc' +Requires-Dist: numpydoc (>=1.5) ; extra == 'doc' +Requires-Dist: pillow (>=9.2) ; extra == 'doc' +Requires-Dist: nb2plots (>=0.6) ; extra == 'doc' +Requires-Dist: texext (>=0.6.7) ; extra == 'doc' +Provides-Extra: extra +Requires-Dist: lxml (>=4.6) ; extra == 'extra' +Requires-Dist: pygraphviz (>=1.10) ; extra == 'extra' +Requires-Dist: pydot (>=1.4.2) ; extra == 'extra' +Requires-Dist: sympy (>=1.10) ; extra == 'extra' +Provides-Extra: test +Requires-Dist: pytest (>=7.2) ; extra == 'test' +Requires-Dist: pytest-cov (>=4.0) ; extra == 'test' +Requires-Dist: codecov (>=2.1) ; extra == 'test' + +NetworkX +======== + +.. image:: https://github.com/networkx/networkx/workflows/test/badge.svg?tag=networkx-3.0 + :target: https://github.com/networkx/networkx/actions?query=branch%3Anetworkx-3.0 + +.. image:: https://codecov.io/gh/networkx/networkx/branch/main/graph/badge.svg + :target: https://app.codecov.io/gh/networkx/networkx/branch/main + +.. image:: https://img.shields.io/github/labels/networkx/networkx/Good%20First%20Issue?color=green&label=Contribute%20&style=flat-square + :target: https://github.com/networkx/networkx/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22 + + +NetworkX is a Python package for the creation, manipulation, +and study of the structure, dynamics, and functions +of complex networks. + +- **Website (including documentation):** https://networkx.org +- **Mailing list:** https://groups.google.com/forum/#!forum/networkx-discuss +- **Source:** https://github.com/networkx/networkx +- **Bug reports:** https://github.com/networkx/networkx/issues +- **Report a security vulnerability:** https://tidelift.com/security +- **Tutorial:** https://networkx.org/documentation/latest/tutorial.html +- **GitHub Discussions:** https://github.com/networkx/networkx/discussions + +Simple example +-------------- + +Find the shortest path between two nodes in an undirected graph: + +.. code:: pycon + + >>> import networkx as nx + >>> G = nx.Graph() + >>> G.add_edge("A", "B", weight=4) + >>> G.add_edge("B", "D", weight=2) + >>> G.add_edge("A", "C", weight=3) + >>> G.add_edge("C", "D", weight=4) + >>> nx.shortest_path(G, "A", "D", weight="weight") + ['A', 'B', 'D'] + +Install +------- + +Install the latest version of NetworkX:: + + $ pip install networkx + +Install with all optional dependencies:: + + $ pip install networkx[all] + +For additional details, please see `INSTALL.rst`. + +Bugs +---- + +Please report any bugs that you find `here `_. +Or, even better, fork the repository on `GitHub `_ +and create a pull request (PR). We welcome all changes, big or small, and we +will help you make the PR if you are new to `git` (just ask on the issue and/or +see `CONTRIBUTING.rst`). + +License +------- + +Released under the 3-Clause BSD license (see `LICENSE.txt`):: + + Copyright (C) 2004-2023 NetworkX Developers + Aric Hagberg + Dan Schult + Pieter Swart diff --git a/pkg/dependency/parser/python/packaging/testdata/pyphen-0.14.0.METADATA b/pkg/dependency/parser/python/packaging/testdata/pyphen-0.14.0.METADATA new file mode 100644 index 000000000000..9a84dcd15884 --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/pyphen-0.14.0.METADATA @@ -0,0 +1,38 @@ +Metadata-Version: 2.1 +Name: pyphen +Version: 0.14.0 +Summary: Pure Python module to hyphenate text +Keywords: hyphenation +Author-email: Guillaume Ayoub +Maintainer-email: CourtBouillon +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) +Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) +Classifier: License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1) +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Text Processing +Classifier: Topic :: Text Processing :: Linguistic +Requires-Dist: sphinx ; extra == "doc" +Requires-Dist: sphinx_rtd_theme ; extra == "doc" +Requires-Dist: pytest ; extra == "test" +Requires-Dist: isort ; extra == "test" +Requires-Dist: flake8 ; extra == "test" +Project-URL: Changelog, https://github.com/Kozea/Pyphen/releases +Project-URL: Code, https://github.com/Kozea/Pyphen +Project-URL: Documentation, https://pyphen.org/ +Project-URL: Donation, https://opencollective.com/courtbouillon +Project-URL: Homepage, https://www.courtbouillon.org/pyphen +Project-URL: Issues, https://github.com/Kozea/Pyphen/issues +Provides-Extra: doc +Provides-Extra: test diff --git a/pkg/dependency/parser/python/packaging/testdata/setuptools-51.3.3-py3.8.egg-info.PKG-INFO b/pkg/dependency/parser/python/packaging/testdata/setuptools-51.3.3-py3.8.egg-info.PKG-INFO new file mode 100644 index 000000000000..f5341300a798 --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/setuptools-51.3.3-py3.8.egg-info.PKG-INFO @@ -0,0 +1,87 @@ +Metadata-Version: 2.1 +Name: setuptools +Version: 51.3.3 +Summary: Easily download, build, install, upgrade, and uninstall Python packages +Home-page: https://github.com/pypa/setuptools +Author: Python Packaging Authority +Author-email: distutils-sig@python.org +License: UNKNOWN +Project-URL: Documentation, https://setuptools.readthedocs.io/ +Description: .. image:: https://img.shields.io/pypi/v/setuptools.svg + :target: `PyPI link`_ + + .. image:: https://img.shields.io/pypi/pyversions/setuptools.svg + :target: `PyPI link`_ + + .. _PyPI link: https://pypi.org/project/setuptools + + .. image:: https://github.com/pypa/setuptools/workflows/tests/badge.svg + :target: https://github.com/pypa/setuptools/actions?query=workflow%3A%22tests%22 + :alt: tests + + .. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: Black + + .. image:: https://img.shields.io/readthedocs/setuptools/latest.svg + :target: https://setuptools.readthedocs.io + + .. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white + :target: https://codecov.io/gh/pypa/setuptools + + .. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat + :target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme + + See the `Installation Instructions + `_ in the Python Packaging + User's Guide for instructions on installing, upgrading, and uninstalling + Setuptools. + + Questions and comments should be directed to the `distutils-sig + mailing list `_. + Bug reports and especially tested patches may be + submitted directly to the `bug tracker + `_. + + + Code of Conduct + =============== + + Everyone interacting in the setuptools project's codebases, issue trackers, + chat rooms, and mailing lists is expected to follow the + `PSF Code of Conduct `_. + + + For Enterprise + ============== + + Available as part of the Tidelift Subscription. + + Setuptools and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. + + `Learn more `_. + + + Security Contact + ================ + + To report a security vulnerability, please use the + `Tidelift security contact `_. + Tidelift will coordinate the fix and disclosure. + +Keywords: CPAN PyPI distutils eggs package management +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Requires-Python: >=3.6 +Provides-Extra: testing +Provides-Extra: docs +Provides-Extra: ssl +Provides-Extra: certs \ No newline at end of file diff --git a/pkg/dependency/parser/python/packaging/testdata/simple-0.1.0.METADATA b/pkg/dependency/parser/python/packaging/testdata/simple-0.1.0.METADATA new file mode 100644 index 000000000000..a92f79bf844d --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/simple-0.1.0.METADATA @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: simple +Version: 0.1.0 diff --git a/pkg/dependency/parser/python/packaging/testdata/unidecode-egg-info.PKG-INFO b/pkg/dependency/parser/python/packaging/testdata/unidecode-egg-info.PKG-INFO new file mode 100644 index 000000000000..80e0762c5173 --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/unidecode-egg-info.PKG-INFO @@ -0,0 +1,47 @@ +Metadata-Version: 1.1 +Name: Unidecode +Version: 0.4.1 +Summary: US-ASCII transliterations of Unicode text +Home-page: http://www.tablix.org/~avian/blog/archives/2009/01/unicode_transliteration_in_python/ +Author: Tomaz Solc +Author-email: tomaz.solc@tablix.org +License: UNKNOWN +Description: + Unidecode + ========= + + ASCII transliterations of Unicode text + + Example Use + ----------- + + :: + + from unidecode import unidecode + print unidecode(u"北亰") + + # That prints: Bei Jing + + Description + ----------- + + It often happens that you have non-Roman text data in Unicode, but + you can't display it -- usually because you're trying to show it + to a user via an application that doesn't support Unicode, or + because the fonts you need aren't accessible. You could represent + the Unicode characters as "???????" or " BA A0q0...", but + that's nearly useless to the user who actually wants to read what + the text says. + + What Unidecode provides is a function, 'unidecode(...)' that + takes Unicode data and tries to represent it in ASCII characters + (i.e., the universally displayable characters between 0x00 and 0x7F). + The representation is almost always an attempt at *transliteration* + -- i.e., conveying, in Roman letters, the pronunciation expressed by + the text in some other writing system. (See the example above) + + This is a Python port of Text::Unidecode Perl module by + Sean M. Burke . + +Platform: UNKNOWN +Provides: unidecode diff --git a/pkg/dependency/parser/python/packaging/testdata/zipp-3.12.1.METADATA b/pkg/dependency/parser/python/packaging/testdata/zipp-3.12.1.METADATA new file mode 100644 index 000000000000..142b8d071568 --- /dev/null +++ b/pkg/dependency/parser/python/packaging/testdata/zipp-3.12.1.METADATA @@ -0,0 +1,106 @@ +Metadata-Version: 2.1 +Name: zipp +Version: 3.12.1 +Summary: Backport of pathlib-compatible object wrapper for zip files +Home-page: https://github.com/jaraco/zipp +Author: Jason R. Coombs +Author-email: jaraco@jaraco.com +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Requires-Python: >=3.7 +License-File: LICENSE +Provides-Extra: docs +Requires-Dist: sphinx (>=3.5) ; extra == 'docs' +Requires-Dist: jaraco.packaging (>=9) ; extra == 'docs' +Requires-Dist: rst.linker (>=1.9) ; extra == 'docs' +Requires-Dist: furo ; extra == 'docs' +Requires-Dist: sphinx-lint ; extra == 'docs' +Requires-Dist: jaraco.tidelift (>=1.4) ; extra == 'docs' +Provides-Extra: testing +Requires-Dist: pytest (>=6) ; extra == 'testing' +Requires-Dist: pytest-checkdocs (>=2.4) ; extra == 'testing' +Requires-Dist: flake8 (<5) ; extra == 'testing' +Requires-Dist: pytest-cov ; extra == 'testing' +Requires-Dist: pytest-enabler (>=1.3) ; extra == 'testing' +Requires-Dist: jaraco.itertools ; extra == 'testing' +Requires-Dist: func-timeout ; extra == 'testing' +Requires-Dist: jaraco.functools ; extra == 'testing' +Requires-Dist: more-itertools ; extra == 'testing' +Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == 'testing' +Requires-Dist: pytest-mypy (>=0.9.1) ; (platform_python_implementation != "PyPy") and extra == 'testing' +Requires-Dist: pytest-flake8 ; (python_version < "3.12") and extra == 'testing' + +.. image:: https://img.shields.io/pypi/v/zipp.svg + :target: https://pypi.org/project/zipp + +.. image:: https://img.shields.io/pypi/pyversions/zipp.svg + +.. image:: https://github.com/jaraco/zipp/workflows/tests/badge.svg + :target: https://github.com/jaraco/zipp/actions?query=workflow%3A%22tests%22 + :alt: tests + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: Black + +.. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest +.. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest + +.. image:: https://img.shields.io/badge/skeleton-2023-informational + :target: https://blog.jaraco.com/skeleton + +.. image:: https://tidelift.com/badges/package/pypi/zipp + :target: https://tidelift.com/subscription/pkg/pypi-zipp?utm_source=pypi-zipp&utm_medium=readme + + +A pathlib-compatible Zipfile object wrapper. Official backport of the standard library +`Path object `_. + + +Compatibility +============= + +New features are introduced in this third-party library and later merged +into CPython. The following table indicates which versions of this library +were contributed to different versions in the standard library: + +.. list-table:: + :header-rows: 1 + + * - zipp + - stdlib + * - 3.9 + - 3.12 + * - 3.5 + - 3.11 + * - 3.2 + - 3.10 + * - 3.3 ?? + - 3.9 + * - 1.0 + - 3.8 + + +Usage +===== + +Use ``zipp.Path`` in place of ``zipfile.Path`` on any Python. + +For Enterprise +============== + +Available as part of the Tidelift Subscription. + +This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. + +`Learn more `_. + +Security Contact +================ + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure. \ No newline at end of file diff --git a/pkg/dependency/parser/python/pip/parse.go b/pkg/dependency/parser/python/pip/parse.go new file mode 100644 index 000000000000..4d4f893d63c0 --- /dev/null +++ b/pkg/dependency/parser/python/pip/parse.go @@ -0,0 +1,77 @@ +package pip + +import ( + "bufio" + "strings" + "unicode" + + "golang.org/x/text/encoding" + u "golang.org/x/text/encoding/unicode" + "golang.org/x/text/transform" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +const ( + commentMarker string = "#" + endColon string = ";" + hashMarker string = "--" + startExtras string = "[" + endExtras string = "]" +) + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + // `requirements.txt` can use byte order marks (BOM) + // e.g. on Windows `requirements.txt` can use UTF-16LE with BOM + // We need to override them to avoid the file being read incorrectly + var transformer = u.BOMOverride(encoding.Nop.NewDecoder()) + decodedReader := transform.NewReader(r, transformer) + + scanner := bufio.NewScanner(decodedReader) + var libs []types.Library + for scanner.Scan() { + line := scanner.Text() + line = strings.ReplaceAll(line, " ", "") + line = strings.ReplaceAll(line, `\`, "") + line = removeExtras(line) + line = rStripByKey(line, commentMarker) + line = rStripByKey(line, endColon) + line = rStripByKey(line, hashMarker) + s := strings.Split(line, "==") + if len(s) != 2 { + continue + } + libs = append(libs, types.Library{ + Name: s[0], + Version: s[1], + }) + } + if err := scanner.Err(); err != nil { + return nil, nil, xerrors.Errorf("scan error: %w", err) + } + return libs, nil, nil +} + +func rStripByKey(line, key string) string { + if pos := strings.Index(line, key); pos >= 0 { + line = strings.TrimRightFunc((line)[:pos], unicode.IsSpace) + } + return line +} + +func removeExtras(line string) string { + startIndex := strings.Index(line, startExtras) + endIndex := strings.Index(line, endExtras) + 1 + if startIndex != -1 && endIndex != -1 { + line = line[:startIndex] + line[endIndex:] + } + return line +} diff --git a/pkg/dependency/parser/python/pip/parse_test.go b/pkg/dependency/parser/python/pip/parse_test.go new file mode 100644 index 000000000000..a3a183f94a8e --- /dev/null +++ b/pkg/dependency/parser/python/pip/parse_test.go @@ -0,0 +1,68 @@ +package pip + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + vectors := []struct { + file string + want []types.Library + }{ + { + file: "testdata/requirements_flask.txt", + want: requirementsFlask, + }, + { + file: "testdata/requirements_comments.txt", + want: requirementsComments, + }, + { + file: "testdata/requirements_spaces.txt", + want: requirementsSpaces, + }, + { + file: "testdata/requirements_no_version.txt", + want: requirementsNoVersion, + }, + { + file: "testdata/requirements_operator.txt", + want: requirementsOperator, + }, + { + file: "testdata/requirements_hash.txt", + want: requirementsHash, + }, + { + file: "testdata/requirements_hyphens.txt", + want: requirementsHyphens, + }, + { + file: "testdata/requirement_exstras.txt", + want: requirementsExtras, + }, + { + file: "testdata/requirements_utf16le.txt", + want: requirementsUtf16le, + }, + } + + for _, v := range vectors { + t.Run(path.Base(v.file), func(t *testing.T) { + f, err := os.Open(v.file) + require.NoError(t, err) + + got, _, err := NewParser().Parse(f) + require.NoError(t, err) + + assert.Equal(t, v.want, got) + }) + } +} diff --git a/pkg/dependency/parser/python/pip/parse_testcase.go b/pkg/dependency/parser/python/pip/parse_testcase.go new file mode 100644 index 000000000000..45642d47f2fa --- /dev/null +++ b/pkg/dependency/parser/python/pip/parse_testcase.go @@ -0,0 +1,56 @@ +package pip + +import "github.com/aquasecurity/trivy/pkg/dependency/types" + +var ( + requirementsFlask = []types.Library{ + {Name: "click", Version: "8.0.0"}, + {Name: "Flask", Version: "2.0.0"}, + {Name: "itsdangerous", Version: "2.0.0"}, + {Name: "Jinja2", Version: "3.0.0"}, + {Name: "MarkupSafe", Version: "2.0.0"}, + {Name: "Werkzeug", Version: "2.0.0"}, + } + + requirementsComments = []types.Library{ + {Name: "click", Version: "8.0.0"}, + {Name: "Flask", Version: "2.0.0"}, + {Name: "Jinja2", Version: "3.0.0"}, + {Name: "MarkupSafe", Version: "2.0.0"}, + } + + requirementsSpaces = []types.Library{ + {Name: "click", Version: "8.0.0"}, + {Name: "Flask", Version: "2.0.0"}, + {Name: "itsdangerous", Version: "2.0.0"}, + {Name: "Jinja2", Version: "3.0.0"}, + } + + requirementsNoVersion = []types.Library{ + {Name: "Flask", Version: "2.0.0"}, + } + + requirementsOperator = []types.Library{ + {Name: "Django", Version: "2.3.4"}, + {Name: "SomeProject", Version: "5.4"}, + } + + requirementsHash = []types.Library{ + {Name: "FooProject", Version: "1.2"}, + {Name: "Jinja2", Version: "3.0.0"}, + } + + requirementsHyphens = []types.Library{ + {Name: "oauth2-client", Version: "4.0.0"}, + {Name: "python-gitlab", Version: "2.0.0"}, + } + + requirementsExtras = []types.Library{ + {Name: "pyjwt", Version: "2.1.0"}, + {Name: "celery", Version: "4.4.7"}, + } + + requirementsUtf16le = []types.Library{ + {Name: "attrs", Version: "20.3.0"}, + } +) diff --git a/pkg/dependency/parser/python/pip/testdata/requirement_exstras.txt b/pkg/dependency/parser/python/pip/testdata/requirement_exstras.txt new file mode 100644 index 000000000000..9ff057e40f57 --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirement_exstras.txt @@ -0,0 +1,2 @@ +pyjwt[crypto]==2.1.0 +celery[redis, pytest]==4.4.7 \ No newline at end of file diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_comments.txt b/pkg/dependency/parser/python/pip/testdata/requirements_comments.txt new file mode 100644 index 000000000000..e0d29e00085f --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirements_comments.txt @@ -0,0 +1,8 @@ +# foo==8.0.0 +#bar==8.0.0 +#comment +click==8.0.0 +Flask==2.0.0 #comment +Jinja2==3.0.0#comment +MarkupSafe==2.0.0 # comment + diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_flask.txt b/pkg/dependency/parser/python/pip/testdata/requirements_flask.txt new file mode 100644 index 000000000000..3369a13760c5 --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirements_flask.txt @@ -0,0 +1,6 @@ +click==8.0.0 +Flask==2.0.0 +itsdangerous==2.0.0 +Jinja2==3.0.0 +MarkupSafe==2.0.0 +Werkzeug==2.0.0 diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_hash.txt b/pkg/dependency/parser/python/pip/testdata/requirements_hash.txt new file mode 100644 index 000000000000..08c31dee996c --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirements_hash.txt @@ -0,0 +1,6 @@ +FooProject == 1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 \ + --hash=sha256:486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7 + +Jinja2 == 3.0.0 \ + --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 \ + --hash=sha256:486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7 \ No newline at end of file diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_hyphens.txt b/pkg/dependency/parser/python/pip/testdata/requirements_hyphens.txt new file mode 100644 index 000000000000..92963565d02f --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirements_hyphens.txt @@ -0,0 +1,2 @@ +oauth2-client==4.0.0 +python-gitlab==2.0.0 \ No newline at end of file diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_no_version.txt b/pkg/dependency/parser/python/pip/testdata/requirements_no_version.txt new file mode 100644 index 000000000000..26e30e0000aa --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirements_no_version.txt @@ -0,0 +1,2 @@ +Flask==2.0.0 +pandas diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_operator.txt b/pkg/dependency/parser/python/pip/testdata/requirements_operator.txt new file mode 100644 index 000000000000..5b5ee8a73b30 --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirements_operator.txt @@ -0,0 +1,7 @@ +keyring >= 4.1.1 # Minimum version 4.1.1 +coverage != 3.5 # Version Exclusion. Anything except version 3.5 +Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.* +Django == 2.3.4 +SomeProject ==5.4 ; python_version < '3.8' +numpyNew; sys_platform == 'win32' +numpy >= 3.4.1; sys_platform == 'win32' \ No newline at end of file diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_spaces.txt b/pkg/dependency/parser/python/pip/testdata/requirements_spaces.txt new file mode 100644 index 000000000000..6636231e5e21 --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirements_spaces.txt @@ -0,0 +1,5 @@ +click == 8.0.0 +Flask ==2.0.0 +itsdangerous== 2.0.0 + +Jinja2 == 3.0.0 # comment diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_utf16le.txt b/pkg/dependency/parser/python/pip/testdata/requirements_utf16le.txt new file mode 100644 index 000000000000..2fa914006dd3 Binary files /dev/null and b/pkg/dependency/parser/python/pip/testdata/requirements_utf16le.txt differ diff --git a/pkg/dependency/parser/python/pipenv/parse.go b/pkg/dependency/parser/python/pipenv/parse.go new file mode 100644 index 000000000000..70332764195e --- /dev/null +++ b/pkg/dependency/parser/python/pipenv/parse.go @@ -0,0 +1,64 @@ +package pipenv + +import ( + "io" + "strings" + + "github.com/liamg/jfather" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type lockFile struct { + Default map[string]dependency `json:"default"` +} +type dependency struct { + Version string `json:"version"` + StartLine int + EndLine int +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var lockFile lockFile + input, err := io.ReadAll(r) + if err != nil { + return nil, nil, xerrors.Errorf("failed to read packages.lock.json: %w", err) + } + if err := jfather.Unmarshal(input, &lockFile); err != nil { + return nil, nil, xerrors.Errorf("failed to decode Pipenv.lock: %w", err) + } + + var libs []types.Library + for pkgName, dependency := range lockFile.Default { + libs = append(libs, types.Library{ + Name: pkgName, + Version: strings.TrimLeft(dependency.Version, "="), + Locations: []types.Location{ + { + StartLine: dependency.StartLine, + EndLine: dependency.EndLine, + }, + }, + }) + } + return libs, nil, nil +} + +// UnmarshalJSONWithMetadata needed to detect start and end lines of deps +func (t *dependency) UnmarshalJSONWithMetadata(node jfather.Node) error { + if err := node.Decode(&t); err != nil { + return err + } + // Decode func will overwrite line numbers if we save them first + t.StartLine = node.Range().Start.Line + t.EndLine = node.Range().End.Line + return nil +} diff --git a/pkg/dependency/parser/python/pipenv/parse_test.go b/pkg/dependency/parser/python/pipenv/parse_test.go new file mode 100644 index 000000000000..03fbe573ee7b --- /dev/null +++ b/pkg/dependency/parser/python/pipenv/parse_test.go @@ -0,0 +1,62 @@ +package pipenv + +import ( + "os" + "path" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + vectors := []struct { + file string // Test input file + want []types.Library + }{ + { + file: "testdata/Pipfile_normal.lock", + want: pipenvNormal, + }, + { + file: "testdata/Pipfile_django.lock", + want: pipenvDjango, + }, + { + file: "testdata/Pipfile_many.lock", + want: pipenvMany, + }, + } + + for _, v := range vectors { + t.Run(path.Base(v.file), func(t *testing.T) { + f, err := os.Open(v.file) + require.NoError(t, err) + + got, _, err := NewParser().Parse(f) + require.NoError(t, err) + + sort.Slice(got, func(i, j int) bool { + ret := strings.Compare(got[i].Name, got[j].Name) + if ret == 0 { + return got[i].Version < got[j].Version + } + return ret < 0 + }) + + sort.Slice(v.want, func(i, j int) bool { + ret := strings.Compare(v.want[i].Name, v.want[j].Name) + if ret == 0 { + return v.want[i].Version < v.want[j].Version + } + return ret < 0 + }) + + assert.Equal(t, v.want, got) + }) + } +} diff --git a/pkg/dependency/parser/python/pipenv/parse_testcase.go b/pkg/dependency/parser/python/pipenv/parse_testcase.go new file mode 100644 index 000000000000..6a611944d3eb --- /dev/null +++ b/pkg/dependency/parser/python/pipenv/parse_testcase.go @@ -0,0 +1,78 @@ +package pipenv + +import "github.com/aquasecurity/trivy/pkg/dependency/types" + +var ( + // docker run --name pipenv --rm -it python:3.9-alpine sh + // apk add jq + // mkdir app && cd /app + // pip install pipenv + // pipenv install requests pyyaml + // pipenv graph --json | jq -rc '.[] | "{\"\(.package.package_name | ascii_downcase)\", \"\(.package.installed_version)\", \"\"},"' + // graph doesn't contain information about location of dependency in lock file. + // add locations manually + pipenvNormal = []types.Library{ + {Name: "urllib3", Version: "1.24.2", Locations: []types.Location{{StartLine: 65, EndLine: 71}}}, + {Name: "requests", Version: "2.21.0", Locations: []types.Location{{StartLine: 57, EndLine: 64}}}, + {Name: "pyyaml", Version: "5.1", Locations: []types.Location{{StartLine: 40, EndLine: 56}}}, + {Name: "idna", Version: "2.8", Locations: []types.Location{{StartLine: 33, EndLine: 39}}}, + {Name: "chardet", Version: "3.0.4", Locations: []types.Location{{StartLine: 26, EndLine: 32}}}, + {Name: "certifi", Version: "2019.3.9", Locations: []types.Location{{StartLine: 19, EndLine: 25}}}, + } + + // docker run --name pipenv --rm -it python:3.9-alpine bash + // apk add jq + // mkdir app && cd /app + // pip install pipenv + // pipenv install requests pyyaml django djangorestframework + // pipenv graph --json | jq -rc '.[] | "{\"\(.package.package_name | ascii_downcase)\", \"\(.package.installed_version)\", \"\"},"' + // graph doesn't contain information about location of dependency in lock file. + // add locations manually + pipenvDjango = []types.Library{ + {Name: "urllib3", Version: "1.24.2", Locations: []types.Location{{StartLine: 95, EndLine: 101}}}, + {Name: "sqlparse", Version: "0.3.0", Locations: []types.Location{{StartLine: 88, EndLine: 94}}}, + {Name: "requests", Version: "2.21.0", Locations: []types.Location{{StartLine: 80, EndLine: 87}}}, + {Name: "pyyaml", Version: "5.1", Locations: []types.Location{{StartLine: 63, EndLine: 79}}}, + {Name: "pytz", Version: "2019.1", Locations: []types.Location{{StartLine: 56, EndLine: 62}}}, + {Name: "idna", Version: "2.8", Locations: []types.Location{{StartLine: 49, EndLine: 55}}}, + {Name: "djangorestframework", Version: "3.9.3", Locations: []types.Location{{StartLine: 41, EndLine: 48}}}, + {Name: "django", Version: "2.2", Locations: []types.Location{{StartLine: 33, EndLine: 40}}}, + {Name: "chardet", Version: "3.0.4", Locations: []types.Location{{StartLine: 26, EndLine: 32}}}, + {Name: "certifi", Version: "2019.3.9", Locations: []types.Location{{StartLine: 19, EndLine: 25}}}, + } + + // docker run --name pipenv --rm -it python:3.9-alpine bash + // apk add jq + // mkdir app && cd /app + // pip install pipenv + // pipenv install requests pyyaml django djangorestframework six botocore python-dateutil simplejson setuptools pyasn1 awscli jinja2 + // pipenv graph --json | jq -rc '.[] | "{\"\(.package.package_name | ascii_downcase)\", \"\(.package.installed_version)\", \"\"},"' + // graph doesn't contain information about location of dependency in lock file. + // add locations manually + pipenvMany = []types.Library{ + {Name: "urllib3", Version: "1.24.2", Locations: []types.Location{{StartLine: 237, EndLine: 244}}}, + {Name: "sqlparse", Version: "0.3.0", Locations: []types.Location{{StartLine: 230, EndLine: 236}}}, + {Name: "six", Version: "1.12.0", Locations: []types.Location{{StartLine: 222, EndLine: 229}}}, + {Name: "simplejson", Version: "3.16.0", Locations: []types.Location{{StartLine: 204, EndLine: 221}}}, + {Name: "s3transfer", Version: "0.2.0", Locations: []types.Location{{StartLine: 197, EndLine: 203}}}, + {Name: "rsa", Version: "3.4.2", Locations: []types.Location{{StartLine: 190, EndLine: 196}}}, + {Name: "requests", Version: "2.21.0", Locations: []types.Location{{StartLine: 182, EndLine: 189}}}, + {Name: "pyyaml", Version: "3.13", Locations: []types.Location{{StartLine: 165, EndLine: 181}}}, + {Name: "pytz", Version: "2019.1", Locations: []types.Location{{StartLine: 158, EndLine: 164}}}, + {Name: "python-dateutil", Version: "2.8.0", Locations: []types.Location{{StartLine: 150, EndLine: 157}}}, + {Name: "pyasn1", Version: "0.4.5", Locations: []types.Location{{StartLine: 142, EndLine: 149}}}, + {Name: "markupsafe", Version: "1.1.1", Locations: []types.Location{{StartLine: 109, EndLine: 141}}}, + {Name: "jmespath", Version: "0.9.4", Locations: []types.Location{{StartLine: 102, EndLine: 108}}}, + {Name: "jinja2", Version: "2.10.1", Locations: []types.Location{{StartLine: 94, EndLine: 101}}}, + {Name: "idna", Version: "2.8", Locations: []types.Location{{StartLine: 87, EndLine: 93}}}, + {Name: "framework", Version: "0.1.0", Locations: []types.Location{{StartLine: 80, EndLine: 86}}}, + {Name: "docutils", Version: "0.14", Locations: []types.Location{{StartLine: 72, EndLine: 79}}}, + {Name: "djangorestframework", Version: "3.9.3", Locations: []types.Location{{StartLine: 64, EndLine: 71}}}, + {Name: "django", Version: "2.2", Locations: []types.Location{{StartLine: 56, EndLine: 63}}}, + {Name: "colorama", Version: "0.3.9", Locations: []types.Location{{StartLine: 49, EndLine: 55}}}, + {Name: "chardet", Version: "3.0.4", Locations: []types.Location{{StartLine: 42, EndLine: 48}}}, + {Name: "certifi", Version: "2019.3.9", Locations: []types.Location{{StartLine: 35, EndLine: 41}}}, + {Name: "botocore", Version: "1.12.137", Locations: []types.Location{{StartLine: 27, EndLine: 34}}}, + {Name: "awscli", Version: "1.16.147", Locations: []types.Location{{StartLine: 19, EndLine: 26}}}, + } +) diff --git a/pkg/dependency/parser/python/pipenv/testdata/Pipfile_django.lock b/pkg/dependency/parser/python/pipenv/testdata/Pipfile_django.lock new file mode 100644 index 000000000000..bc520cd11534 --- /dev/null +++ b/pkg/dependency/parser/python/pipenv/testdata/Pipfile_django.lock @@ -0,0 +1,104 @@ +{ + "_meta": { + "hash": { + "sha256": "3072b1f87376eaf80e623fe275f79051cae6232f7e458a281bf2723853149d91" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" + ], + "version": "==2019.3.9" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "django": { + "hashes": [ + "sha256:7c3543e4fb070d14e10926189a7fcf42ba919263b7473dceaefce34d54e8a119", + "sha256:a2814bffd1f007805b19194eb0b9a331933b82bd5da1c3ba3d7b7ba16e06dc4b" + ], + "index": "pypi", + "version": "==2.2" + }, + "djangorestframework": { + "hashes": [ + "sha256:1d22971a5fc98becdbbad9710ca2a9148dd339f6cbea4c3ddbed2cb84bab94e1", + "sha256:2884763160b997073ff1e937bd820a69d23978902a3ccd0ba53a217e196239f0" + ], + "index": "pypi", + "version": "==3.9.3" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "pytz": { + "hashes": [ + "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", + "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + ], + "version": "==2019.1" + }, + "pyyaml": { + "hashes": [ + "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", + "sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", + "sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", + "sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", + "sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", + "sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", + "sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", + "sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", + "sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", + "sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", + "sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19" + ], + "index": "pypi", + "version": "==5.1" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "index": "pypi", + "version": "==2.21.0" + }, + "sqlparse": { + "hashes": [ + "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", + "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + ], + "version": "==0.3.0" + }, + "urllib3": { + "hashes": [ + "sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0", + "sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3" + ], + "version": "==1.24.2" + } + }, + "develop": {} +} diff --git a/pkg/dependency/parser/python/pipenv/testdata/Pipfile_many.lock b/pkg/dependency/parser/python/pipenv/testdata/Pipfile_many.lock new file mode 100644 index 000000000000..38605f921ab7 --- /dev/null +++ b/pkg/dependency/parser/python/pipenv/testdata/Pipfile_many.lock @@ -0,0 +1,247 @@ +{ + "_meta": { + "hash": { + "sha256": "6a0046c55fa1d91553ca88f7098f5e382f4954eaa7d48ce15d3f9135f4d6d5fe" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "awscli": { + "hashes": [ + "sha256:2ed95e925c5d867ac02086a0721cdf19bae581b6b6dffd1d5cfc4b1c54b2a21b", + "sha256:3c53a97d817798287e5468d5dd57acc3b9a8b02f495a32f80582f6b323294c20" + ], + "index": "pypi", + "version": "==1.16.147" + }, + "botocore": { + "hashes": [ + "sha256:0d95794f6b1239c75e2c5f966221bcd4b68020fddb5676f757531eedbb612ed8", + "sha256:3213cf48cf2ceee10fc3b93221f2cd1c38521cca7584f547d5c086213cc60f35" + ], + "index": "pypi", + "version": "==1.12.137" + }, + "certifi": { + "hashes": [ + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" + ], + "version": "==2019.3.9" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "colorama": { + "hashes": [ + "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", + "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" + ], + "version": "==0.3.9" + }, + "django": { + "hashes": [ + "sha256:7c3543e4fb070d14e10926189a7fcf42ba919263b7473dceaefce34d54e8a119", + "sha256:a2814bffd1f007805b19194eb0b9a331933b82bd5da1c3ba3d7b7ba16e06dc4b" + ], + "index": "pypi", + "version": "==2.2" + }, + "djangorestframework": { + "hashes": [ + "sha256:1d22971a5fc98becdbbad9710ca2a9148dd339f6cbea4c3ddbed2cb84bab94e1", + "sha256:2884763160b997073ff1e937bd820a69d23978902a3ccd0ba53a217e196239f0" + ], + "index": "pypi", + "version": "==3.9.3" + }, + "docutils": { + "hashes": [ + "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", + "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", + "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + ], + "version": "==0.14" + }, + "framework": { + "hashes": [ + "sha256:a988297ade5c38bc58f4afc02b1dc075ad0f6dae2cbd1be6342067aaaeee532d" + ], + "index": "pypi", + "version": "==0.1.0" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "jinja2": { + "hashes": [ + "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", + "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" + ], + "index": "pypi", + "version": "==2.10.1" + }, + "jmespath": { + "hashes": [ + "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6", + "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c" + ], + "version": "==0.9.4" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + ], + "version": "==1.1.1" + }, + "pyasn1": { + "hashes": [ + "sha256:da2420fe13a9452d8ae97a0e478adde1dee153b11ba832a95b223a2ba01c10f7", + "sha256:da6b43a8c9ae93bc80e2739efb38cc776ba74a886e3e9318d65fe81a8b8a2c6e" + ], + "index": "pypi", + "version": "==0.4.5" + }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "index": "pypi", + "version": "==2.8.0" + }, + "pytz": { + "hashes": [ + "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", + "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + ], + "version": "==2019.1" + }, + "pyyaml": { + "hashes": [ + "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", + "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", + "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", + "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", + "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", + "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", + "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", + "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", + "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", + "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", + "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" + ], + "index": "pypi", + "version": "==3.13" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "index": "pypi", + "version": "==2.21.0" + }, + "rsa": { + "hashes": [ + "sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5", + "sha256:43f682fea81c452c98d09fc316aae12de6d30c4b5c84226642cf8f8fd1c93abd" + ], + "version": "==3.4.2" + }, + "s3transfer": { + "hashes": [ + "sha256:7b9ad3213bff7d357f888e0fab5101b56fa1a0548ee77d121c3a3dbfbef4cb2e", + "sha256:f23d5cb7d862b104401d9021fc82e5fa0e0cf57b7660a1331425aab0c691d021" + ], + "version": "==0.2.0" + }, + "simplejson": { + "hashes": [ + "sha256:067a7177ddfa32e1483ba5169ebea1bc2ea27f224853211ca669325648ca5642", + "sha256:2fc546e6af49fb45b93bbe878dea4c48edc34083729c0abd09981fe55bdf7f91", + "sha256:354fa32b02885e6dae925f1b5bbf842c333c1e11ea5453ddd67309dc31fdb40a", + "sha256:37e685986cf6f8144607f90340cff72d36acf654f3653a6c47b84c5c38d00df7", + "sha256:3af610ee72efbe644e19d5eaad575c73fb83026192114e5f6719f4901097fce2", + "sha256:3b919fc9cf508f13b929a9b274c40786036b31ad28657819b3b9ba44ba651f50", + "sha256:3dd289368bbd064974d9a5961101f080e939cbe051e6689a193c99fb6e9ac89b", + "sha256:6c3258ffff58712818a233b9737fe4be943d306c40cf63d14ddc82ba563f483a", + "sha256:75e3f0b12c28945c08f54350d91e624f8dd580ab74fd4f1bbea54bc6b0165610", + "sha256:b1f329139ba647a9548aa05fb95d046b4a677643070dc2afc05fa2e975d09ca5", + "sha256:ee9625fc8ee164902dfbb0ff932b26df112da9f871c32f0f9c1bcf20c350fe2a", + "sha256:fb2530b53c28f0d4d84990e945c2ebb470edb469d63e389bf02ff409012fe7c5" + ], + "index": "pypi", + "version": "==3.16.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "index": "pypi", + "version": "==1.12.0" + }, + "sqlparse": { + "hashes": [ + "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", + "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + ], + "version": "==0.3.0" + }, + "urllib3": { + "hashes": [ + "sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0", + "sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3" + ], + "markers": "python_version >= '3.4'", + "version": "==1.24.2" + } + }, + "develop": {} +} diff --git a/pkg/dependency/parser/python/pipenv/testdata/Pipfile_normal.lock b/pkg/dependency/parser/python/pipenv/testdata/Pipfile_normal.lock new file mode 100644 index 000000000000..7f2f7a683dfc --- /dev/null +++ b/pkg/dependency/parser/python/pipenv/testdata/Pipfile_normal.lock @@ -0,0 +1,74 @@ +{ + "_meta": { + "hash": { + "sha256": "5d17fda061a57afd47f9dbf5f74aa3803c28e73fa8fa247425ba76549bc30804" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" + ], + "version": "==2019.3.9" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "pyyaml": { + "hashes": [ + "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", + "sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", + "sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", + "sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", + "sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", + "sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", + "sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", + "sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", + "sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", + "sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", + "sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19" + ], + "index": "pypi", + "version": "==5.1" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "index": "pypi", + "version": "==2.21.0" + }, + "urllib3": { + "hashes": [ + "sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0", + "sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3" + ], + "version": "==1.24.2" + } + }, + "develop": {} +} diff --git a/pkg/dependency/parser/python/poetry/parse.go b/pkg/dependency/parser/python/poetry/parse.go new file mode 100644 index 000000000000..e476b8c18d93 --- /dev/null +++ b/pkg/dependency/parser/python/poetry/parse.go @@ -0,0 +1,160 @@ +package poetry + +import ( + "sort" + "strings" + + "github.com/BurntSushi/toml" + "golang.org/x/xerrors" + + version "github.com/aquasecurity/go-pep440-version" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type Lockfile struct { + Packages []struct { + Category string `toml:"category"` + Description string `toml:"description"` + Marker string `toml:"marker,omitempty"` + Name string `toml:"name"` + Optional bool `toml:"optional"` + PythonVersions string `toml:"python-versions"` + Version string `toml:"version"` + Dependencies map[string]interface{} `toml:"dependencies"` + Metadata interface{} + } `toml:"package"` +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var lockfile Lockfile + if _, err := toml.NewDecoder(r).Decode(&lockfile); err != nil { + return nil, nil, xerrors.Errorf("failed to decode poetry.lock: %w", err) + } + + // Keep all installed versions + libVersions := parseVersions(lockfile) + + var libs []types.Library + var deps []types.Dependency + for _, pkg := range lockfile.Packages { + if pkg.Category == "dev" { + continue + } + + pkgID := packageID(pkg.Name, pkg.Version) + libs = append(libs, types.Library{ + ID: pkgID, + Name: pkg.Name, + Version: pkg.Version, + }) + + dependsOn := parseDependencies(pkg.Dependencies, libVersions) + if len(dependsOn) != 0 { + deps = append(deps, types.Dependency{ + ID: pkgID, + DependsOn: dependsOn, + }) + } + } + return libs, deps, nil +} + +// parseVersions stores all installed versions of libraries for use in dependsOn +// as the dependencies of libraries use version range. +func parseVersions(lockfile Lockfile) map[string][]string { + libVersions := make(map[string][]string) + for _, pkg := range lockfile.Packages { + if pkg.Category == "dev" { + continue + } + if vers, ok := libVersions[pkg.Name]; ok { + libVersions[pkg.Name] = append(vers, pkg.Version) + } else { + libVersions[pkg.Name] = []string{pkg.Version} + } + } + return libVersions +} + +func parseDependencies(deps map[string]any, libVersions map[string][]string) []string { + var dependsOn []string + for name, versRange := range deps { + if dep, err := parseDependency(name, versRange, libVersions); err != nil { + log.Logger.Debugf("failed to parse poetry dependency: %s", err) + } else if dep != "" { + dependsOn = append(dependsOn, dep) + } + } + sort.Slice(dependsOn, func(i, j int) bool { + return dependsOn[i] < dependsOn[j] + }) + return dependsOn +} + +func parseDependency(name string, versRange any, libVersions map[string][]string) (string, error) { + name = normalizePkgName(name) + vers, ok := libVersions[name] + if !ok { + return "", xerrors.Errorf("no version found for %q", name) + } + + for _, ver := range vers { + var vRange string + + switch r := versRange.(type) { + case string: + vRange = r + case map[string]interface{}: + for k, v := range r { + if k == "version" { + vRange = v.(string) + } + } + } + + if matched, err := matchVersion(ver, vRange); err != nil { + return "", xerrors.Errorf("failed to match version for %s: %w", name, err) + } else if matched { + return packageID(name, ver), nil + } + } + return "", xerrors.Errorf("no matched version found for %q", name) +} + +// matchVersion checks if the package version satisfies the given constraint. +func matchVersion(currentVersion, constraint string) (bool, error) { + v, err := version.Parse(currentVersion) + if err != nil { + return false, xerrors.Errorf("python version error (%s): %s", currentVersion, err) + } + + c, err := version.NewSpecifiers(constraint, version.WithPreRelease(true)) + if err != nil { + return false, xerrors.Errorf("python constraint error (%s): %s", constraint, err) + } + + return c.Check(v), nil +} + +func normalizePkgName(name string) string { + // The package names don't use `_`, `.` or upper case, but dependency names can contain them. + // We need to normalize those names. + name = strings.ToLower(name) // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L819 + name = strings.ReplaceAll(name, "_", "-") // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L50 + name = strings.ReplaceAll(name, ".", "-") // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L816 + return name +} + +func packageID(name, ver string) string { + return dependency.ID(ftypes.Poetry, name, ver) +} diff --git a/pkg/dependency/parser/python/poetry/parse_test.go b/pkg/dependency/parser/python/poetry/parse_test.go new file mode 100644 index 000000000000..c02999a8eff8 --- /dev/null +++ b/pkg/dependency/parser/python/poetry/parse_test.go @@ -0,0 +1,129 @@ +package poetry + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + file string + wantLibs []types.Library + wantDeps []types.Dependency + wantErr assert.ErrorAssertionFunc + }{ + { + name: "normal", + file: "testdata/poetry_normal.lock", + wantLibs: poetryNormal, + wantErr: assert.NoError, + }, + { + name: "many", + file: "testdata/poetry_many.lock", + wantLibs: poetryMany, + wantDeps: poetryManyDeps, + wantErr: assert.NoError, + }, + { + name: "flask", + file: "testdata/poetry_flask.lock", + wantLibs: poetryFlask, + wantDeps: poetryFlaskDeps, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + defer f.Close() + + p := &Parser{} + gotLibs, gotDeps, err := p.Parse(f) + if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.file)) { + return + } + assert.Equalf(t, tt.wantLibs, gotLibs, "Parse(%v)", tt.file) + assert.Equalf(t, tt.wantDeps, gotDeps, "Parse(%v)", tt.file) + }) + } +} + +func TestParseDependency(t *testing.T) { + tests := []struct { + name string + packageName string + versionRange interface{} + libsVersions map[string][]string + want string + wantErr string + }{ + { + name: "handle package name", + packageName: "Test_project.Name", + versionRange: "*", + libsVersions: map[string][]string{ + "test-project-name": {"1.0.0"}, + }, + want: "test-project-name@1.0.0", + }, + { + name: "version range as string", + packageName: "test", + versionRange: ">=1.0.0", + libsVersions: map[string][]string{ + "test": {"2.0.0"}, + }, + want: "test@2.0.0", + }, + { + name: "version range == *", + packageName: "test", + versionRange: "*", + libsVersions: map[string][]string{ + "test": {"3.0.0"}, + }, + want: "test@3.0.0", + }, + { + name: "version range as json", + packageName: "test", + versionRange: map[string]interface{}{ + "version": ">=4.8.3", + "markers": "python_version < \"3.8\"", + }, + libsVersions: map[string][]string{ + "test": {"5.0.0"}, + }, + want: "test@5.0.0", + }, + { + name: "libsVersions doesn't contain required version", + packageName: "test", + versionRange: ">=1.0.0", + libsVersions: map[string][]string{}, + wantErr: "no version found", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseDependency(tt.packageName, tt.versionRange, tt.libsVersions) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/python/poetry/parse_testcase.go b/pkg/dependency/parser/python/poetry/parse_testcase.go new file mode 100644 index 000000000000..c6511c0bd089 --- /dev/null +++ b/pkg/dependency/parser/python/poetry/parse_testcase.go @@ -0,0 +1,135 @@ +package poetry + +import "github.com/aquasecurity/trivy/pkg/dependency/types" + +var ( + // docker run --name pipenv --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh + // apk add curl + // curl -sSL https://install.python-poetry.org | python3 - + // export PATH=/root/.local/bin:$PATH + // poetry new normal && cd normal + // poetry add pypi@2.1 + // poetry show -a | awk '{gsub(/\(!\)/, ""); printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\"},\n") }' + poetryNormal = []types.Library{ + {ID: "pypi@2.1", Name: "pypi", Version: "2.1"}, + } + + // docker run --name pipenv --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh + // apk add curl + // curl -sSL https://install.python-poetry.org | python3 - + // export PATH=/root/.local/bin:$PATH + // poetry new many && cd many + // curl -o poetry.lock https://raw.githubusercontent.com/python-poetry/poetry/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock + // curl -o pyproject.toml https://raw.githubusercontent.com/python-poetry/poetry/c8945eb110aeda611cc6721565d7ad0c657d453a/pyproject.toml + // poetry show -a | awk '{gsub(/\(!\)/, ""); printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\"},\n") }' + // `--no-dev` flag uncorrected returns deps. Then need to remove `dev` deps manually + // list of dev deps - cat poetry.lock | grep 'category = "dev"' -B 3 + poetryMany = []types.Library{ + {ID: "attrs@22.2.0", Name: "attrs", Version: "22.2.0"}, + {ID: "backports-cached-property@1.0.2", Name: "backports-cached-property", Version: "1.0.2"}, + {ID: "build@0.10.0", Name: "build", Version: "0.10.0"}, + {ID: "cachecontrol@0.12.11", Name: "cachecontrol", Version: "0.12.11"}, + {ID: "certifi@2022.12.7", Name: "certifi", Version: "2022.12.7"}, + {ID: "cffi@1.15.1", Name: "cffi", Version: "1.15.1"}, + {ID: "charset-normalizer@3.0.1", Name: "charset-normalizer", Version: "3.0.1"}, + {ID: "cleo@2.0.1", Name: "cleo", Version: "2.0.1"}, + {ID: "colorama@0.4.6", Name: "colorama", Version: "0.4.6"}, + {ID: "crashtest@0.4.1", Name: "crashtest", Version: "0.4.1"}, + {ID: "cryptography@39.0.0", Name: "cryptography", Version: "39.0.0"}, + {ID: "distlib@0.3.6", Name: "distlib", Version: "0.3.6"}, + {ID: "dulwich@0.21.2", Name: "dulwich", Version: "0.21.2"}, + {ID: "filelock@3.9.0", Name: "filelock", Version: "3.9.0"}, + {ID: "html5lib@1.1", Name: "html5lib", Version: "1.1"}, + {ID: "idna@3.4", Name: "idna", Version: "3.4"}, + {ID: "importlib-metadata@6.0.0", Name: "importlib-metadata", Version: "6.0.0"}, + {ID: "importlib-resources@5.10.2", Name: "importlib-resources", Version: "5.10.2"}, + {ID: "installer@0.6.0", Name: "installer", Version: "0.6.0"}, + {ID: "jaraco-classes@3.2.3", Name: "jaraco-classes", Version: "3.2.3"}, + {ID: "jeepney@0.8.0", Name: "jeepney", Version: "0.8.0"}, + {ID: "jsonschema@4.17.3", Name: "jsonschema", Version: "4.17.3"}, + {ID: "keyring@23.13.1", Name: "keyring", Version: "23.13.1"}, + {ID: "lockfile@0.12.2", Name: "lockfile", Version: "0.12.2"}, + {ID: "more-itertools@9.0.0", Name: "more-itertools", Version: "9.0.0"}, + {ID: "msgpack@1.0.4", Name: "msgpack", Version: "1.0.4"}, + {ID: "packaging@23.0", Name: "packaging", Version: "23.0"}, + {ID: "pexpect@4.8.0", Name: "pexpect", Version: "4.8.0"}, + {ID: "pkginfo@1.9.6", Name: "pkginfo", Version: "1.9.6"}, + {ID: "pkgutil-resolve-name@1.3.10", Name: "pkgutil-resolve-name", Version: "1.3.10"}, + {ID: "platformdirs@2.6.2", Name: "platformdirs", Version: "2.6.2"}, + {ID: "poetry-core@1.5.0", Name: "poetry-core", Version: "1.5.0"}, + {ID: "poetry-plugin-export@1.3.0", Name: "poetry-plugin-export", Version: "1.3.0"}, + {ID: "ptyprocess@0.7.0", Name: "ptyprocess", Version: "0.7.0"}, + {ID: "pycparser@2.21", Name: "pycparser", Version: "2.21"}, + {ID: "pyproject-hooks@1.0.0", Name: "pyproject-hooks", Version: "1.0.0"}, + {ID: "pyrsistent@0.19.3", Name: "pyrsistent", Version: "0.19.3"}, + {ID: "pywin32-ctypes@0.2.0", Name: "pywin32-ctypes", Version: "0.2.0"}, + {ID: "rapidfuzz@2.13.7", Name: "rapidfuzz", Version: "2.13.7"}, + {ID: "requests@2.28.2", Name: "requests", Version: "2.28.2"}, + {ID: "requests-toolbelt@0.10.1", Name: "requests-toolbelt", Version: "0.10.1"}, + {ID: "secretstorage@3.3.3", Name: "secretstorage", Version: "3.3.3"}, + {ID: "shellingham@1.5.0.post1", Name: "shellingham", Version: "1.5.0.post1"}, + {ID: "six@1.16.0", Name: "six", Version: "1.16.0"}, + {ID: "tomli@2.0.1", Name: "tomli", Version: "2.0.1"}, + {ID: "tomlkit@0.11.6", Name: "tomlkit", Version: "0.11.6"}, + {ID: "trove-classifiers@2023.1.20", Name: "trove-classifiers", Version: "2023.1.20"}, + {ID: "typing-extensions@4.4.0", Name: "typing-extensions", Version: "4.4.0"}, + {ID: "urllib3@1.26.14", Name: "urllib3", Version: "1.26.14"}, + {ID: "virtualenv@20.16.5", Name: "virtualenv", Version: "20.16.5"}, + {ID: "virtualenv@20.17.1", Name: "virtualenv", Version: "20.17.1"}, + {ID: "webencodings@0.5.1", Name: "webencodings", Version: "0.5.1"}, + {ID: "xattr@0.10.1", Name: "xattr", Version: "0.10.1"}, + {ID: "zipp@3.12.0", Name: "zipp", Version: "3.12.0"}, + } + + // cat poetry.lock | grep "\[package.dependencies\]" -B 3 -A 8 - it might help to complete this slice + poetryManyDeps = []types.Dependency{ + {ID: "build@0.10.0", DependsOn: []string{"colorama@0.4.6", "importlib-metadata@6.0.0", "packaging@23.0", "pyproject-hooks@1.0.0", "tomli@2.0.1"}}, + {ID: "cachecontrol@0.12.11", DependsOn: []string{"lockfile@0.12.2", "msgpack@1.0.4", "requests@2.28.2"}}, + {ID: "cffi@1.15.1", DependsOn: []string{"pycparser@2.21"}}, + {ID: "cleo@2.0.1", DependsOn: []string{"crashtest@0.4.1", "rapidfuzz@2.13.7"}}, + {ID: "cryptography@39.0.0", DependsOn: []string{"cffi@1.15.1"}}, + {ID: "dulwich@0.21.2", DependsOn: []string{"typing-extensions@4.4.0", "urllib3@1.26.14"}}, + {ID: "html5lib@1.1", DependsOn: []string{"six@1.16.0", "webencodings@0.5.1"}}, + {ID: "importlib-metadata@6.0.0", DependsOn: []string{"typing-extensions@4.4.0", "zipp@3.12.0"}}, + {ID: "importlib-resources@5.10.2", DependsOn: []string{"zipp@3.12.0"}}, + {ID: "jaraco-classes@3.2.3", DependsOn: []string{"more-itertools@9.0.0"}}, + {ID: "jsonschema@4.17.3", DependsOn: []string{"attrs@22.2.0", "importlib-metadata@6.0.0", "importlib-resources@5.10.2", "pkgutil-resolve-name@1.3.10", "pyrsistent@0.19.3", "typing-extensions@4.4.0"}}, + {ID: "keyring@23.13.1", DependsOn: []string{"importlib-metadata@6.0.0", "importlib-resources@5.10.2", "jaraco-classes@3.2.3", "jeepney@0.8.0", "pywin32-ctypes@0.2.0", "secretstorage@3.3.3"}}, + {ID: "pexpect@4.8.0", DependsOn: []string{"ptyprocess@0.7.0"}}, + {ID: "platformdirs@2.6.2", DependsOn: []string{"typing-extensions@4.4.0"}}, + {ID: "poetry-core@1.5.0", DependsOn: []string{"importlib-metadata@6.0.0"}}, + {ID: "poetry-plugin-export@1.3.0", DependsOn: []string{"poetry-core@1.5.0"}}, + {ID: "pyproject-hooks@1.0.0", DependsOn: []string{"tomli@2.0.1"}}, + {ID: "requests@2.28.2", DependsOn: []string{"certifi@2022.12.7", "charset-normalizer@3.0.1", "idna@3.4", "urllib3@1.26.14"}}, + {ID: "requests-toolbelt@0.10.1", DependsOn: []string{"requests@2.28.2"}}, + {ID: "secretstorage@3.3.3", DependsOn: []string{"cryptography@39.0.0", "jeepney@0.8.0"}}, + {ID: "virtualenv@20.16.5", DependsOn: []string{"distlib@0.3.6", "filelock@3.9.0", "platformdirs@2.6.2"}}, + {ID: "virtualenv@20.17.1", DependsOn: []string{"distlib@0.3.6", "filelock@3.9.0", "importlib-metadata@6.0.0", "platformdirs@2.6.2"}}, + {ID: "xattr@0.10.1", DependsOn: []string{"cffi@1.15.1"}}, + } + + // docker run --name pipenv --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh + // apk add curl + // curl -sSL https://install.python-poetry.org | python3 - + // export PATH=/root/.local/bin:$PATH + // poetry new web && cd web + // poetry add flask@1.0.3 + // poetry show -a | awk '{gsub(/\(!\)/, ""); printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\"},\n") }' + poetryFlask = []types.Library{ + {ID: "click@8.1.3", Name: "click", Version: "8.1.3"}, + {ID: "colorama@0.4.6", Name: "colorama", Version: "0.4.6"}, + {ID: "flask@1.0.3", Name: "flask", Version: "1.0.3"}, + {ID: "itsdangerous@2.1.2", Name: "itsdangerous", Version: "2.1.2"}, + {ID: "jinja2@3.1.2", Name: "jinja2", Version: "3.1.2"}, + {ID: "markupsafe@2.1.2", Name: "markupsafe", Version: "2.1.2"}, + {ID: "werkzeug@2.2.3", Name: "werkzeug", Version: "2.2.3"}, + } + + // cat poetry.lock | grep "\[package.dependencies\]" -B 3 -A 8 - it might help to complete this slice + poetryFlaskDeps = []types.Dependency{ + {ID: "click@8.1.3", DependsOn: []string{"colorama@0.4.6"}}, + {ID: "flask@1.0.3", DependsOn: []string{"click@8.1.3", "itsdangerous@2.1.2", "jinja2@3.1.2", "werkzeug@2.2.3"}}, + {ID: "jinja2@3.1.2", DependsOn: []string{"markupsafe@2.1.2"}}, + {ID: "werkzeug@2.2.3", DependsOn: []string{"markupsafe@2.1.2"}}, + } +) diff --git a/pkg/dependency/parser/python/poetry/testdata/poetry_flask.lock b/pkg/dependency/parser/python/poetry/testdata/poetry_flask.lock new file mode 100644 index 000000000000..5c28fa3e56fc --- /dev/null +++ b/pkg/dependency/parser/python/poetry/testdata/poetry_flask.lock @@ -0,0 +1,164 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "flask" +version = "1.0.3" +description = "A simple framework for building complex web applications." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "Flask-1.0.3-py2.py3-none-any.whl", hash = "sha256:e7d32475d1de5facaa55e3958bc4ec66d3762076b074296aa50ef8fdc5b9df61"}, + {file = "Flask-1.0.3.tar.gz", hash = "sha256:ad7c6d841e64296b962296c2c2dabc6543752985727af86a975072dea984b6f3"}, +] + +[package.dependencies] +click = ">=5.1" +itsdangerous = ">=0.24" +Jinja2 = ">=2.10" +Werkzeug = ">=0.14" + +[package.extras] +dev = ["coverage", "pallets-sphinx-themes", "pytest (>=3)", "sphinx", "sphinxcontrib-log-cabinet", "tox"] +docs = ["pallets-sphinx-themes", "sphinx", "sphinxcontrib-log-cabinet"] +dotenv = ["python-dotenv"] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.2" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, +] + +[[package]] +name = "werkzeug" +version = "2.2.3" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"}, + {file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "c84861cc8679600635c65a32b5079dbfdf0c615c25a7db3d94c23156df8c56e9" \ No newline at end of file diff --git a/pkg/dependency/parser/python/poetry/testdata/poetry_many.lock b/pkg/dependency/parser/python/poetry/testdata/poetry_many.lock new file mode 100644 index 000000000000..ced2f1975c42 --- /dev/null +++ b/pkg/dependency/parser/python/poetry/testdata/poetry_many.lock @@ -0,0 +1,1984 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "backports-cached-property" +version = "1.0.2" +description = "cached_property() - computed once per instance, cached as attribute" +category = "main" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "backports.cached-property-1.0.2.tar.gz", hash = "sha256:9306f9eed6ec55fd156ace6bc1094e2c86fae5fb2bf07b6a9c00745c656e75dd"}, + {file = "backports.cached_property-1.0.2-py3-none-any.whl", hash = "sha256:baeb28e1cd619a3c9ab8941431fe34e8490861fb998c6c4590693d50171db0cc"}, +] + +[[package]] +name = "build" +version = "0.10.0" +description = "A simple, correct Python build frontend" +category = "main" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "build-0.10.0-py3-none-any.whl", hash = "sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171"}, + {file = "build-0.10.0.tar.gz", hash = "sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "os_name == \"nt\""} +importlib-metadata = {version = ">=0.22", markers = "python_version < \"3.8\""} +packaging = ">=19.0" +pyproject_hooks = "*" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2021.08.31)", "sphinx (>=4.0,<5.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)"] +test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "toml (>=0.10.0)", "wheel (>=0.36.0)"] +typing = ["importlib-metadata (>=5.1)", "mypy (==0.991)", "tomli", "typing-extensions (>=3.7.4.3)"] +virtualenv = ["virtualenv (>=20.0.35)"] + +[[package]] +name = "cachecontrol" +version = "0.12.11" +description = "httplib2 caching for requests" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "CacheControl-0.12.11-py2.py3-none-any.whl", hash = "sha256:2c75d6a8938cb1933c75c50184549ad42728a27e9f6b92fd677c3151aa72555b"}, + {file = "CacheControl-0.12.11.tar.gz", hash = "sha256:a5b9fcc986b184db101aa280b42ecdcdfc524892596f606858e0b7a8b4d9e144"}, +] + +[package.dependencies] +lockfile = {version = ">=0.9", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2" +requests = "*" + +[package.extras] +filecache = ["lockfile (>=0.9)"] +redis = ["redis (>=2.10.5)"] + +[[package]] +name = "cachy" +version = "0.3.0" +description = "Cachy provides a simple yet effective caching library." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"}, + {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, +] + +[package.extras] +memcached = ["python-memcached (>=1.59,<2.0)"] +msgpack = ["msgpack-python (>=0.5,<0.6)"] +redis = ["redis (>=3.3.6,<4.0.0)"] + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.0.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, + {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, +] + +[[package]] +name = "cleo" +version = "2.0.1" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "cleo-2.0.1-py3-none-any.whl", hash = "sha256:6eb133670a3ed1f3b052d53789017b6e50fca66d1287e6e6696285f4cb8ea448"}, + {file = "cleo-2.0.1.tar.gz", hash = "sha256:eb4b2e1f3063c11085cebe489a6e9124163c226575a3c3be69b2e51af4a15ec5"}, +] + +[package.dependencies] +crashtest = ">=0.4.1,<0.5.0" +rapidfuzz = ">=2.2.0,<3.0.0" + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.1.0" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"}, + {file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c"}, + {file = "coverage-7.1.0-cp310-cp310-win32.whl", hash = "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352"}, + {file = "coverage-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038"}, + {file = "coverage-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040"}, + {file = "coverage-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e"}, + {file = "coverage-7.1.0-cp311-cp311-win32.whl", hash = "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7"}, + {file = "coverage-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c"}, + {file = "coverage-7.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3"}, + {file = "coverage-7.1.0-cp37-cp37m-win32.whl", hash = "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73"}, + {file = "coverage-7.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5"}, + {file = "coverage-7.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06"}, + {file = "coverage-7.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0"}, + {file = "coverage-7.1.0-cp38-cp38-win32.whl", hash = "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab"}, + {file = "coverage-7.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c"}, + {file = "coverage-7.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6"}, + {file = "coverage-7.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c"}, + {file = "coverage-7.1.0-cp39-cp39-win32.whl", hash = "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4"}, + {file = "coverage-7.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3"}, + {file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"}, + {file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "crashtest" +version = "0.4.1" +description = "Manage Python errors with ease" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, + {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, +] + +[[package]] +name = "cryptography" +version = "39.0.0" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288"}, + {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717"}, + {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df"}, + {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1"}, + {file = "cryptography-39.0.0-cp36-abi3-win32.whl", hash = "sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de"}, + {file = "cryptography-39.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39"}, + {file = "cryptography-39.0.0.tar.gz", hash = "sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "ruff"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] + +[[package]] +name = "deepdiff" +version = "6.2.2" +description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "deepdiff-6.2.2-py3-none-any.whl", hash = "sha256:dea62316741f86c1d8e946f47c4c21386788457c898a495a5e6b0ccdcd76d9b6"}, + {file = "deepdiff-6.2.2.tar.gz", hash = "sha256:d04d997a68bf8bea01f8a97395877314ef5c2131d8f57bba2295f3adda725282"}, +] + +[package.dependencies] +ordered-set = ">=4.0.2,<4.2.0" + +[package.extras] +cli = ["click (==8.1.3)", "pyyaml (==6.0)"] + +[[package]] +name = "deepdiff" +version = "6.2.3" +description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "deepdiff-6.2.3-py3-none-any.whl", hash = "sha256:d83b06e043447d6770860a635abecb46e849b0494c43ced2ecafda7628c7ce72"}, + {file = "deepdiff-6.2.3.tar.gz", hash = "sha256:a02aaa8171351eba675cff5f795ec7a90987f86ad5449553308d4e18df57dc3d"}, +] + +[package.dependencies] +ordered-set = ">=4.0.2,<4.2.0" +orjson = "*" + +[package.extras] +cli = ["click (==8.1.3)", "pyyaml (==6.0)"] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] + +[[package]] +name = "dulwich" +version = "0.21.2" +description = "Python Git Library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dulwich-0.21.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:42c459742bb1802a7686b90f15b7e6f0475ab2a6a8b60b1b01fe8309d6182962"}, + {file = "dulwich-0.21.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:60db3151c7b21d4d30cb39f92a324f6b4296c4226077a9845a297b28062566ac"}, + {file = "dulwich-0.21.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7eacdee555ad5b24774e633b9976b0fd3721f87c4583c0d5644f66427a005276"}, + {file = "dulwich-0.21.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78bf7d82e0fb65a89b6f1cfe88bb46664056f852bed945d07b47b1d56fc76334"}, + {file = "dulwich-0.21.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578e1adec5105643a9c77875e6e6f02627da58a549fab4730584c7bacf0555f9"}, + {file = "dulwich-0.21.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ea866332f6ed48738e62d2ba57fbad0f0c172191730e1ca61a460ae6603ff3d"}, + {file = "dulwich-0.21.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aaf539fb5d0dcdeab11b0f09f63e6e6851023b1fcdd92d66c51e3c1c45f60843"}, + {file = "dulwich-0.21.2-cp310-cp310-win32.whl", hash = "sha256:86203171f36d5524baa2d04c8b491fd83e2e840b1b3c587535604309b1022ffd"}, + {file = "dulwich-0.21.2-cp310-cp310-win_amd64.whl", hash = "sha256:d6dac3a7453a2de1c1f910794cf7147571771998d2325fea21abd3878a7e91ae"}, + {file = "dulwich-0.21.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09c90e5b4fe9adb0fc2989b67f7eee37a4ad205e2e32550cec7d0af388ffe7ab"}, + {file = "dulwich-0.21.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:37ea1dd93df7e8d8202be4a114633db832b2839793e3a43ab57adf5f5d9c1715"}, + {file = "dulwich-0.21.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b94140249d09976aa60dda88964695903b03956a1b3b37d0ced7f0ca27c249ce"}, + {file = "dulwich-0.21.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4f7c53fe5910b68cc750b1ffff009fde808df5c3ae2906d2102d1d4290bbd6"}, + {file = "dulwich-0.21.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ea27192f2c07a84765b06202dbb467ae0da6c93e7824e4e5022ca214e01f7c"}, + {file = "dulwich-0.21.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:acfc3650b8f67306afbc15dae22e9507fab8a0bdf5986c711c047a134a127321"}, + {file = "dulwich-0.21.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0448f4ae5753c08c4ab5e090a89f4d30974847bd41e352854b9a30c0dca00971"}, + {file = "dulwich-0.21.2-cp311-cp311-win32.whl", hash = "sha256:9eae7e44086b2e3bab3f74bfb28d69a6ae52d2fdc83e2d64191485063baf5102"}, + {file = "dulwich-0.21.2-cp311-cp311-win_amd64.whl", hash = "sha256:f3d364192572ade997e40dcc618fedb29176f89685ba07fae2a23d18811a848f"}, + {file = "dulwich-0.21.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2ae925e9501f26e7e925fd9141f606be1cdff164c2a6a0d93e2c68980ce00f9a"}, + {file = "dulwich-0.21.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9752c4eb4625209f61f8315b089709fb26dc4a593dade714e14d4c275d3658a5"}, + {file = "dulwich-0.21.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6380ffbb6048eb7bef3ed24ac684fb515061a3e881df12e8f8542854829215"}, + {file = "dulwich-0.21.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e3291ecffdeb4c1ae5453d698ee9d5ec9149e2c31c69e2056214e7a150422921"}, + {file = "dulwich-0.21.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1b26f54a735543468609d8120271cb0353d56bbbcb594daef38a880feb86a5d6"}, + {file = "dulwich-0.21.2-cp37-cp37m-win32.whl", hash = "sha256:bc8429e417ff5bce7f622beaa8dbdd660093b80a8d6ccb0040149cdf38c61c45"}, + {file = "dulwich-0.21.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7808c558589bea127e991a2a82a8ce3a317bed84d509ed54d46ee5d6324957c3"}, + {file = "dulwich-0.21.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:34e59d348461c81553ca520c3f95c85d62cf6baaa297abe5e80cea0d404bab82"}, + {file = "dulwich-0.21.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fddeed845ad99743cce8b43683b12fe2e164e920baa0c3baff6094b7d6a1831b"}, + {file = "dulwich-0.21.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4c0deea0a9447539db8e41ada9813353b1f572d2a56a7c2eb73178759eefc06"}, + {file = "dulwich-0.21.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e04411a8f2f6ad8dd3b18cc464e3cf996813e832b910d0b41c2e8f0aa4b2860"}, + {file = "dulwich-0.21.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8121fac495a79b946dfb2612ef48c644d5dd9c55278cc023f1337aa4c1dddbe9"}, + {file = "dulwich-0.21.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:06b5ea5a31372446375bb30304d8d3ce90795200f148df470faf41fa66061dc6"}, + {file = "dulwich-0.21.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a68393f843518818fd98e34191820ae92e5c09a0ae2c9cac6f474d69b3a243ab"}, + {file = "dulwich-0.21.2-cp38-cp38-win32.whl", hash = "sha256:48d047e8945348830e576170ef9e5f6f1dbf81567331db54caa3c4d67c93770b"}, + {file = "dulwich-0.21.2-cp38-cp38-win_amd64.whl", hash = "sha256:3023933c1bf35e149f74d94e42944fce9903c8daea6560cd0657da75f5653e80"}, + {file = "dulwich-0.21.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a911565cec4f38689d4888298d5cdd3c41151f15e8c2ca0238432a1194d55741"}, + {file = "dulwich-0.21.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c80b558cc5d9fbd0bc8609d5100b6b57d7a2f164ad1a2caf158179629fc0ef40"}, + {file = "dulwich-0.21.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adcc225256f63e44dc8d81f66bd0bfdf1d1b161ed399a0923a434937adf64e54"}, + {file = "dulwich-0.21.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b96f96c02490181297fc5431d71679d448bf2b3c5aee579e546d1779a36567d"}, + {file = "dulwich-0.21.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb8a812d32400b12a0b70d603edd2b2fce4f5b0867062e2089f81db2e770c6e7"}, + {file = "dulwich-0.21.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:82873d5248dc9bfe2aae4a4bf8518633c4e12144cf6ecba56f2996abc8e4c53e"}, + {file = "dulwich-0.21.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e35981b025483f56a615a1c6440a235c675ec65cea8575b2260b636136197fa0"}, + {file = "dulwich-0.21.2-cp39-cp39-win32.whl", hash = "sha256:72ea83a8e6bb5e9f0f86bae262e2ff867d15073895030ac042a652cf64e52d44"}, + {file = "dulwich-0.21.2-cp39-cp39-win_amd64.whl", hash = "sha256:b13fe5509bc4a365788999ea1915ac2ab59617301f7c6bbb06b25cd5f0725f60"}, + {file = "dulwich-0.21.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2efa033a4477cc1f6f0056b39a970eb9ac19a10f1a51683a590a1068c5c3c084"}, + {file = "dulwich-0.21.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6fe1563d1efd3d6c4fcfda210d313d449e50ba5371500dc68a0fd3afb0a6ec3"}, + {file = "dulwich-0.21.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2a774c06d10700e66610bc021050bee6b81ebce141b2e695b5448e2e70466df"}, + {file = "dulwich-0.21.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ae82a1f0c9abe9a3719ebc611cbc2a60fcb9b02a567f750280ff5f783e21abfb"}, + {file = "dulwich-0.21.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:86e83c9b713323d47796b3b95ddf95f491ffaa07050ed6145ac2f3249b67bd06"}, + {file = "dulwich-0.21.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24a7de7e5ec0889f674a4975b61d5e5a455313cb6d32bddb0981f42c0edd789"}, + {file = "dulwich-0.21.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc265def2b50a10bfdd9539a5d5d6a313e45493c1e20303f4c18ba045b1d555b"}, + {file = "dulwich-0.21.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7097a5a6e06d9339c52b636ea90f93b498441ab962075bc21279396c25e1ff7b"}, + {file = "dulwich-0.21.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c7b698ef8bf9ee7de13a282dc6f196ffcfd679c36434059f6c9e0be67b4c51a0"}, + {file = "dulwich-0.21.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:929b720a80c758f29b48ad9b25d3b16e2276767fadfbcdb1d910b9565f4dcc82"}, + {file = "dulwich-0.21.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7385eae5cc0b4777c5021d6b556eca9a0ebfc8e767329ebf5d55c45f931dbd3"}, + {file = "dulwich-0.21.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7efd37d32e924f97beb3ee4a8cf7fb00210db954f7c971d4c67970d8bbe9508b"}, + {file = "dulwich-0.21.2.tar.gz", hash = "sha256:d865ae7fd9497d64ce345a6784ff1775b01317fba9632ef9d2dfd7978f1b0d4f"}, +] + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version <= \"3.7\""} +urllib3 = ">=1.25" + +[package.extras] +fastimport = ["fastimport"] +https = ["urllib3 (>=1.24.1)"] +paramiko = ["paramiko"] +pgp = ["gpg"] + +[[package]] +name = "exceptiongroup" +version = "1.1.0" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, + {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "1.9.0" +description = "execnet: rapid multi-Python deployment" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] + +[package.extras] +testing = ["pre-commit"] + +[[package]] +name = "filelock" +version = "3.9.0" +description = "A platform independent file lock." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, + {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, +] + +[package.extras] +docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["chardet (>=2.2)", "genshi", "lxml"] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +name = "httpretty" +version = "1.1.4" +description = "HTTP client mock for Python" +category = "dev" +optional = false +python-versions = ">=3" +files = [ + {file = "httpretty-1.1.4.tar.gz", hash = "sha256:20de0e5dd5a18292d36d928cc3d6e52f8b2ac73daec40d41eb62dee154933b68"}, +] + +[[package]] +name = "identify" +version = "2.5.17" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "identify-2.5.17-py2.py3-none-any.whl", hash = "sha256:7d526dd1283555aafcc91539acc061d8f6f59adb0a7bba462735b0a318bff7ed"}, + {file = "identify-2.5.17.tar.gz", hash = "sha256:93cc61a861052de9d4c541a7acb7e3dcc9c11b398a2144f6e52ae5285f5f4f06"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.0.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, + {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "importlib-resources" +version = "5.10.2" +description = "Read resources from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_resources-5.10.2-py3-none-any.whl", hash = "sha256:7d543798b0beca10b6a01ac7cafda9f822c54db9e8376a6bf57e0cbd74d486b6"}, + {file = "importlib_resources-5.10.2.tar.gz", hash = "sha256:e4a96c8cc0339647ff9a5e0550d9f276fc5a01ffa276012b58ec108cfd7b8484"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "installer" +version = "0.6.0" +description = "A library for installing Python wheels." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "installer-0.6.0-py3-none-any.whl", hash = "sha256:ae7c62d1d6158b5c096419102ad0d01fdccebf857e784cee57f94165635fe038"}, + {file = "installer-0.6.0.tar.gz", hash = "sha256:f3bd36cd261b440a88a1190b1becca0578fee90b4b62decc796932fdd5ae8839"}, +] + +[[package]] +name = "jaraco-classes" +version = "3.2.3" +description = "Utility functions for Python class constructs" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, + {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + +[[package]] +name = "jsonschema" +version = "4.17.3" +description = "An implementation of JSON Schema validation for Python" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, + {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, +] + +[package.dependencies] +attrs = ">=17.4.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} +pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "keyring" +version = "23.13.1" +description = "Store and access your passwords safely." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, + {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +importlib-resources = {version = "*", markers = "python_version < \"3.9\""} +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +completion = ["shtab"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "lockfile" +version = "0.12.2" +description = "Platform-independent file locking module" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, + {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, +] + +[[package]] +name = "more-itertools" +version = "9.0.0" +description = "More routines for operating on iterables, beyond itertools" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"}, + {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"}, +] + +[[package]] +name = "msgpack" +version = "1.0.4" +description = "MessagePack serializer" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, + {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"}, + {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db"}, + {file = "msgpack-1.0.4-cp310-cp310-win32.whl", hash = "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef"}, + {file = "msgpack-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075"}, + {file = "msgpack-1.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae"}, + {file = "msgpack-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6"}, + {file = "msgpack-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661"}, + {file = "msgpack-1.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236"}, + {file = "msgpack-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44"}, + {file = "msgpack-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243"}, + {file = "msgpack-1.0.4-cp38-cp38-win32.whl", hash = "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2"}, + {file = "msgpack-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae"}, + {file = "msgpack-1.0.4-cp39-cp39-win32.whl", hash = "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c"}, + {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, + {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, +] + +[[package]] +name = "mypy" +version = "1.0.0" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0626db16705ab9f7fa6c249c017c887baf20738ce7f9129da162bb3075fc1af"}, + {file = "mypy-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ace23f6bb4aec4604b86c4843276e8fa548d667dbbd0cb83a3ae14b18b2db6c"}, + {file = "mypy-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87edfaf344c9401942883fad030909116aa77b0fa7e6e8e1c5407e14549afe9a"}, + {file = "mypy-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0ab090d9240d6b4e99e1fa998c2d0aa5b29fc0fb06bd30e7ad6183c95fa07593"}, + {file = "mypy-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:7cc2c01dfc5a3cbddfa6c13f530ef3b95292f926329929001d45e124342cd6b7"}, + {file = "mypy-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14d776869a3e6c89c17eb943100f7868f677703c8a4e00b3803918f86aafbc52"}, + {file = "mypy-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb2782a036d9eb6b5a6efcdda0986774bf798beef86a62da86cb73e2a10b423d"}, + {file = "mypy-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cfca124f0ac6707747544c127880893ad72a656e136adc935c8600740b21ff5"}, + {file = "mypy-1.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8845125d0b7c57838a10fd8925b0f5f709d0e08568ce587cc862aacce453e3dd"}, + {file = "mypy-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b1b9e1ed40544ef486fa8ac022232ccc57109f379611633ede8e71630d07d2"}, + {file = "mypy-1.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c7cf862aef988b5fbaa17764ad1d21b4831436701c7d2b653156a9497d92c83c"}, + {file = "mypy-1.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd187d92b6939617f1168a4fe68f68add749902c010e66fe574c165c742ed88"}, + {file = "mypy-1.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4e5175026618c178dfba6188228b845b64131034ab3ba52acaffa8f6c361f805"}, + {file = "mypy-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2f6ac8c87e046dc18c7d1d7f6653a66787a4555085b056fe2d599f1f1a2a2d21"}, + {file = "mypy-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7306edca1c6f1b5fa0bc9aa645e6ac8393014fa82d0fa180d0ebc990ebe15964"}, + {file = "mypy-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3cfad08f16a9c6611e6143485a93de0e1e13f48cfb90bcad7d5fde1c0cec3d36"}, + {file = "mypy-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67cced7f15654710386e5c10b96608f1ee3d5c94ca1da5a2aad5889793a824c1"}, + {file = "mypy-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a86b794e8a56ada65c573183756eac8ac5b8d3d59daf9d5ebd72ecdbb7867a43"}, + {file = "mypy-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:50979d5efff8d4135d9db293c6cb2c42260e70fb010cbc697b1311a4d7a39ddb"}, + {file = "mypy-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ae4c7a99e5153496243146a3baf33b9beff714464ca386b5f62daad601d87af"}, + {file = "mypy-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e398652d005a198a7f3c132426b33c6b85d98aa7dc852137a2a3be8890c4072"}, + {file = "mypy-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be78077064d016bc1b639c2cbcc5be945b47b4261a4f4b7d8923f6c69c5c9457"}, + {file = "mypy-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92024447a339400ea00ac228369cd242e988dd775640755fa4ac0c126e49bb74"}, + {file = "mypy-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:fe523fcbd52c05040c7bee370d66fee8373c5972171e4fbc323153433198592d"}, + {file = "mypy-1.0.0-py3-none-any.whl", hash = "sha256:2efa963bdddb27cb4a0d42545cd137a8d2b883bd181bbc4525b568ef6eca258f"}, + {file = "mypy-1.0.0.tar.gz", hash = "sha256:f34495079c8d9da05b183f9f7daec2878280c2ad7cc81da686ef0b484cea2ecf"}, +] + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "ordered-set" +version = "4.1.0" +description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"}, + {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"}, +] + +[package.extras] +dev = ["black", "mypy", "pytest"] + +[[package]] +name = "orjson" +version = "3.8.5" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "orjson-3.8.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:143639b9898b094883481fac37733231da1c2ae3aec78a1dd8d3b58c9c9fceef"}, + {file = "orjson-3.8.5-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:31f43e63e0d94784c55e86bd376df3f80b574bea8c0bc5ecd8041009fa8ec78a"}, + {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c802ea6d4a0d40f096aceb5e7ef0a26c23d276cb9334e1cadcf256bb090b6426"}, + {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf298b55b371c2772420c5ace4d47b0a3ea1253667e20ded3c363160fd0575f6"}, + {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68cb4a8501a463771d55bb22fc72795ec7e21d71ab083e000a2c3b651b6fb2af"}, + {file = "orjson-3.8.5-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:4f1427952b3bd92bfb63a61b7ffc33a9f54ec6de296fa8d924cbeba089866acb"}, + {file = "orjson-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c0a9f329468c8eb000742455b83546849bcd69495d6baa6e171c7ee8600a47bd"}, + {file = "orjson-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6535d527aa1e4a757a6ce9b61f3dd74edc762e7d2c6991643aae7c560c8440bd"}, + {file = "orjson-3.8.5-cp310-none-win_amd64.whl", hash = "sha256:2eee64c028adf6378dd714c8debc96d5b92b6bb4862debb65ca868e59bac6c63"}, + {file = "orjson-3.8.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:f5745ff473dd5c6718bf8c8d5bc183f638b4f3e03c7163ffcda4d4ef453f42ff"}, + {file = "orjson-3.8.5-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:544f1240b295083697027a5093ec66763218ff16f03521d5020e7a436d2e417b"}, + {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85c9c6bab97a831e7741089057347d99901b4db2451a076ca8adedc7d96297f"}, + {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bae7347764e7be6dada980fd071e865544c98317ab61af575c9cc5e1dc7e3fe"}, + {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c67f6f6e9d26a06b63126112a7bc8d8529df048d31df2a257a8484b76adf3e5d"}, + {file = "orjson-3.8.5-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:758238364142fcbeca34c968beefc0875ffa10aa2f797c82f51cfb1d22d0934e"}, + {file = "orjson-3.8.5-cp311-none-win_amd64.whl", hash = "sha256:cc7579240fb88a626956a6cb4a181a11b62afbc409ce239a7b866568a2412fa2"}, + {file = "orjson-3.8.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:79aa3e47cbbd4eedbbde4f988f766d6cf38ccb51d52cfabfeb6b8d1b58654d25"}, + {file = "orjson-3.8.5-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:2544cd0d089faa862f5a39f508ee667419e3f9e11f119a6b1505cfce0eb26601"}, + {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2be0025ca7e460bcacb250aba8ce0239be62957d58cf34045834cc9302611d3"}, + {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b57bf72902d818506906e49c677a791f90dbd7f0997d60b14bc6c1ce4ce4cf9"}, + {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ae9832a11c6a9efa8c14224e5caf6e35046efd781de14e59eb69ab4e561cf3"}, + {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:0e28330cc6d51741cad0edd1b57caf6c5531aff30afe41402acde0a03246b8ed"}, + {file = "orjson-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:155954d725627b5480e6cc1ca488afb4fa685099a4ace5f5bf21a182fabf6706"}, + {file = "orjson-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ece1b6ef9312df5d5274ca6786e613b7da7de816356e36bcad9ea8a73d15ab71"}, + {file = "orjson-3.8.5-cp37-none-win_amd64.whl", hash = "sha256:6f58d1f0702332496bc1e2d267c7326c851991b62cf6395370d59c47f9890007"}, + {file = "orjson-3.8.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:933f4ab98362f46a59a6d0535986e1f0cae2f6b42435e24a55922b4bc872af0c"}, + {file = "orjson-3.8.5-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:47a7ca236b25a138a74b2cb5169adcdc5b2b8abdf661de438ba65967a2cde9dc"}, + {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b573ca942c626fcf8a86be4f180b86b2498b18ae180f37b4180c2aced5808710"}, + {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a9bab11611d5452efe4ae5315f5eb806f66104c08a089fb84c648d2e8e00f106"}, + {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee2f5f6476617d01ca166266d70fd5605d3397a41f067022ce04a2e1ced4c8d"}, + {file = "orjson-3.8.5-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:ec0b0b6cd0b84f03537f22b719aca705b876c54ab5cf3471d551c9644127284f"}, + {file = "orjson-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:df3287dc304c8c4556dc85c4ab89eb333307759c1863f95e72e555c0cfce3e01"}, + {file = "orjson-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:09f40add3c2d208e20f8bf185df38f992bf5092202d2d30eced8f6959963f1d5"}, + {file = "orjson-3.8.5-cp38-none-win_amd64.whl", hash = "sha256:232ec1df0d708f74e0dd1fccac1e9a7008cd120d48fe695e8f0c9d80771da430"}, + {file = "orjson-3.8.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:8fba3e7aede3e88a01e94e6fe63d4580162b212e6da27ae85af50a1787e41416"}, + {file = "orjson-3.8.5-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:85e22c358cab170c8604e9edfffcc45dd7b0027ce57ed6bcacb556e8bfbbb704"}, + {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeab1d8247507a75926adf3ca995c74e91f5db1f168815bf3e774f992ba52b50"}, + {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:daaaef15a41e9e8cadc7677cefe00065ae10bce914eefe8da1cd26b3d063970b"}, + {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ccc9f52cf46bd353c6ae1153eaf9d18257ddc110d135198b0cd8718474685ce"}, + {file = "orjson-3.8.5-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:d48c182c7ff4ea0787806de8a2f9298ca44fd0068ecd5f23a4b2d8e03c745cb6"}, + {file = "orjson-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1848e3b4cc09cc82a67262ae56e2a772b0548bb5a6f9dcaee10dcaaf0a5177b7"}, + {file = "orjson-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38480031bc8add58effe802291e4abf7042ef72ae1a4302efe9a36c8f8bfbfcc"}, + {file = "orjson-3.8.5-cp39-none-win_amd64.whl", hash = "sha256:0e9a1c2e649cbaed410c882cedc8f3b993d8f1426d9327f31762d3f46fe7cc88"}, + {file = "orjson-3.8.5.tar.gz", hash = "sha256:77a3b2bd0c4ef7723ea09081e3329dac568a62463aed127c1501441b07ffc64b"}, +] + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, +] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pkginfo" +version = "1.9.6" +description = "Query metadata from sdists / bdists / installed packages." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, + {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov"] + +[[package]] +name = "pkgutil-resolve-name" +version = "1.3.10" +description = "Resolve a name to an object." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] + +[[package]] +name = "platformdirs" +version = "2.6.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poetry-core" +version = "1.5.0" +description = "Poetry PEP 517 Build Backend" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "poetry_core-1.5.0-py3-none-any.whl", hash = "sha256:e216b70f013c47b82a72540d34347632c5bfe59fd54f5fe5d51f6a68b19aaf84"}, + {file = "poetry_core-1.5.0.tar.gz", hash = "sha256:253521bb7104e1df81f64d7b49ea1825057c91fa156d7d0bd752fefdad6f8c7a"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} + +[[package]] +name = "poetry-plugin-export" +version = "1.3.0" +description = "Poetry plugin to export the dependencies to various formats" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "poetry_plugin_export-1.3.0-py3-none-any.whl", hash = "sha256:6e5919bf84afcb08cdd419a03f909f490d8671f00633a3c6df8ba09b0820dc2f"}, + {file = "poetry_plugin_export-1.3.0.tar.gz", hash = "sha256:61ae5ec1db233aba947a48e1ce54c6ff66afd0e1c87195d6bce64c73a5ae658c"}, +] + +[package.dependencies] +poetry = ">=1.3.0,<2.0.0" +poetry-core = ">=1.3.0,<2.0.0" + +[[package]] +name = "pre-commit" +version = "2.21.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "psutil" +version = "5.9.4" +description = "Cross-platform lib for process and system monitoring in Python." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, + {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, + {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, + {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, + {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, + {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, + {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, + {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pyproject-hooks" +version = "1.0.0" +description = "Wrappers to call pyproject.toml-based build backend hooks." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, + {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, +] + +[package.dependencies] +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "pyrsistent" +version = "0.19.3" +description = "Persistent/Functional/Immutable data structures" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, + {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, + {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, + {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, + {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, + {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, +] + +[[package]] +name = "pytest" +version = "7.2.1" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, + {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-github-actions-annotate-failures" +version = "0.1.8" +description = "pytest plugin to annotate failed tests with a workflow command for GitHub Actions" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +files = [ + {file = "pytest-github-actions-annotate-failures-0.1.8.tar.gz", hash = "sha256:2d6e6cb5f8d0aae4a27a20cc4e20fabd3199a121c57f44bc48fe28e372e0be23"}, + {file = "pytest_github_actions_annotate_failures-0.1.8-py2.py3-none-any.whl", hash = "sha256:6a882ff21672fa79deae8d917eb965a6bde2b25191e7632e1adfc23ffac008ab"}, +] + +[package.dependencies] +pytest = ">=4.0.0" + +[[package]] +name = "pytest-mock" +version = "3.10.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, + {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "pytest-randomly" +version = "3.12.0" +description = "Pytest plugin to randomly order tests and control random.seed." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"}, + {file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +pytest = "*" + +[[package]] +name = "pytest-xdist" +version = "3.1.0" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-xdist-3.1.0.tar.gz", hash = "sha256:40fdb8f3544921c5dfcd486ac080ce22870e71d82ced6d2e78fa97c2addd480c"}, + {file = "pytest_xdist-3.1.0-py3-none-any.whl", hash = "sha256:70a76f191d8a1d2d6be69fc440cdf85f3e4c03c08b520fd5dc5d338d6cf07d89"}, +] + +[package.dependencies] +execnet = ">=1.1" +psutil = {version = ">=3.0", optional = true, markers = "extra == \"psutil\""} +pytest = ">=6.2.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.0" +description = "" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, + {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, +] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "rapidfuzz" +version = "2.13.7" +description = "rapid fuzzy string matching" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rapidfuzz-2.13.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b75dd0928ce8e216f88660ab3d5c5ffe990f4dd682fd1709dba29d5dafdde6de"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:24d3fea10680d085fd0a4d76e581bfb2b1074e66e78fd5964d4559e1fcd2a2d4"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8109e0324d21993d5b2d111742bf5958f3516bf8c59f297c5d1cc25a2342eb66"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f705652360d520c2de52bee11100c92f59b3e3daca308ebb150cbc58aecdad"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7496e8779905b02abc0ab4ba2a848e802ab99a6e20756ffc967a0de4900bd3da"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24eb6b843492bdc63c79ee4b2f104059b7a2201fef17f25177f585d3be03405a"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:467c1505362823a5af12b10234cb1c4771ccf124c00e3fc9a43696512bd52293"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53dcae85956853b787c27c1cb06f18bb450e22cf57a4ad3444cf03b8ff31724a"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46b9b8aa09998bc48dd800854e8d9b74bc534d7922c1d6e1bbf783e7fa6ac29c"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1fbad8fb28d98980f5bff33c7842efef0315d42f0cd59082108482a7e6b61410"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:43fb8cb030f888c3f076d40d428ed5eb4331f5dd6cf1796cfa39c67bf0f0fc1e"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b6bad92de071cbffa2acd4239c1779f66851b60ffbbda0e4f4e8a2e9b17e7eef"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d00df2e4a81ffa56a6b1ec4d2bc29afdcb7f565e0b8cd3092fece2290c4c7a79"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-win32.whl", hash = "sha256:2c836f0f2d33d4614c3fbaf9a1eb5407c0fe23f8876f47fd15b90f78daa64c34"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-win_amd64.whl", hash = "sha256:c36fd260084bb636b9400bb92016c6bd81fd80e59ed47f2466f85eda1fc9f782"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b34e8c0e492949ecdd5da46a1cfc856a342e2f0389b379b1a45a3cdcd3176a6e"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:875d51b3497439a72e2d76183e1cb5468f3f979ab2ddfc1d1f7dde3b1ecfb42f"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae33a72336059213996fe4baca4e0e4860913905c2efb7c991eab33b95a98a0a"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5585189b3d90d81ccd62d4f18530d5ac8972021f0aaaa1ffc6af387ff1dce75"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42085d4b154a8232767de8296ac39c8af5bccee6b823b0507de35f51c9cbc2d7"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:585206112c294e335d84de5d5f179c0f932837752d7420e3de21db7fdc476278"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f891b98f8bc6c9d521785816085e9657212621e93f223917fb8e32f318b2957e"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08590905a95ccfa43f4df353dcc5d28c15d70664299c64abcad8721d89adce4f"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b5dd713a1734574c2850c566ac4286594bacbc2d60b9170b795bee4b68656625"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:988f8f6abfba7ee79449f8b50687c174733b079521c3cc121d65ad2d38831846"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b3210869161a864f3831635bb13d24f4708c0aa7208ef5baac1ac4d46e9b4208"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f6fe570e20e293eb50491ae14ddeef71a6a7e5f59d7e791393ffa99b13f1f8c2"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6120f2995f5154057454c5de99d86b4ef3b38397899b5da1265467e8980b2f60"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-win32.whl", hash = "sha256:b20141fa6cee041917801de0bab503447196d372d4c7ee9a03721b0a8edf5337"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-win_amd64.whl", hash = "sha256:ec55a81ac2b0f41b8d6fb29aad16e55417036c7563bad5568686931aa4ff08f7"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d005e058d86f2a968a8d28ca6f2052fab1f124a39035aa0523261d6baf21e1f"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe59a0c21a032024edb0c8e43f5dee5623fef0b65a1e3c1281836d9ce199af3b"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfc04f7647c29fb48da7a04082c34cdb16f878d3c6d098d62d5715c0ad3000c"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68a89bb06d5a331511961f4d3fa7606f8e21237467ba9997cae6f67a1c2c2b9e"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:effe182767d102cb65dfbbf74192237dbd22d4191928d59415aa7d7c861d8c88"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25b4cedf2aa19fb7212894ce5f5219010cce611b60350e9a0a4d492122e7b351"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3a9bd02e1679c0fd2ecf69b72d0652dbe2a9844eaf04a36ddf4adfbd70010e95"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5e2b3d020219baa75f82a4e24b7c8adcb598c62f0e54e763c39361a9e5bad510"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:cf62dacb3f9234f3fddd74e178e6d25c68f2067fde765f1d95f87b1381248f58"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:fa263135b892686e11d5b84f6a1892523123a00b7e5882eff4fbdabb38667347"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa4c598ed77f74ec973247ca776341200b0f93ec3883e34c222907ce72cb92a4"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-win32.whl", hash = "sha256:c2523f8180ebd9796c18d809e9a19075a1060b1a170fde3799e83db940c1b6d5"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-win_amd64.whl", hash = "sha256:5ada0a14c67452358c1ee52ad14b80517a87b944897aaec3e875279371a9cb96"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ca8a23097c1f50e0fdb4de9e427537ca122a18df2eead06ed39c3a0bef6d9d3a"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9be02162af0376d64b840f2fc8ee3366794fc149f1e06d095a6a1d42447d97c5"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af4f7c3c904ca709493eb66ca9080b44190c38e9ecb3b48b96d38825d5672559"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f50d1227e6e2a0e3ae1fb1c9a2e1c59577d3051af72c7cab2bcc430cb5e18da"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c71d9d512b76f05fa00282227c2ae884abb60e09f08b5ca3132b7e7431ac7f0d"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b52ac2626945cd21a2487aeefed794c14ee31514c8ae69b7599170418211e6f6"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca00fafd2756bc9649bf80f1cf72c647dce38635f0695d7ce804bc0f759aa756"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d248a109699ce9992304e79c1f8735c82cc4c1386cd8e27027329c0549f248a2"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c88adbcb933f6b8612f6c593384bf824e562bb35fc8a0f55fac690ab5b3486e5"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c8601a66fbfc0052bb7860d2eacd303fcde3c14e87fdde409eceff516d659e77"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:27be9c63215d302ede7d654142a2e21f0d34ea6acba512a4ae4cfd52bbaa5b59"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3dcffe1f3cbda0dc32133a2ae2255526561ca594f15f9644384549037b355245"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8450d15f7765482e86ef9be2ad1a05683cd826f59ad236ef7b9fb606464a56aa"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-win32.whl", hash = "sha256:460853983ab88f873173e27cc601c5276d469388e6ad6e08c4fd57b2a86f1064"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-win_amd64.whl", hash = "sha256:424f82c35dbe4f83bdc3b490d7d696a1dc6423b3d911460f5493b7ffae999fd2"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c3fbe449d869ea4d0909fc9d862007fb39a584fb0b73349a6aab336f0d90eaed"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:16080c05a63d6042643ae9b6cfec1aefd3e61cef53d0abe0df3069b9d4b72077"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dbcf5371ea704759fcce772c66a07647751d1f5dbdec7818331c9b31ae996c77"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:114810491efb25464016fd554fdf1e20d390309cecef62587494fc474d4b926f"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99a84ab9ac9a823e7e93b4414f86344052a5f3e23b23aa365cda01393ad895bd"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81642a24798851b118f82884205fc1bd9ff70b655c04018c467824b6ecc1fabc"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3741cb0bf9794783028e8b0cf23dab917fa5e37a6093b94c4c2f805f8e36b9f"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:759a3361711586a29bc753d3d1bdb862983bd9b9f37fbd7f6216c24f7c972554"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1333fb3d603d6b1040e365dca4892ba72c7e896df77a54eae27dc07db90906e3"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:916bc2e6cf492c77ad6deb7bcd088f0ce9c607aaeabc543edeb703e1fbc43e31"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:23524635840500ce6f4d25005c9529a97621689c85d2f727c52eed1782839a6a"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ebe303cd9839af69dd1f7942acaa80b1ba90bacef2e7ded9347fbed4f1654672"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fe56659ccadbee97908132135de4b875543353351e0c92e736b7c57aee298b5a"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-win32.whl", hash = "sha256:3f11a7eff7bc6301cd6a5d43f309e22a815af07e1f08eeb2182892fca04c86cb"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-win_amd64.whl", hash = "sha256:e8914dad106dacb0775718e54bf15e528055c4e92fb2677842996f2d52da5069"}, + {file = "rapidfuzz-2.13.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f7930adf84301797c3f09c94b9c5a9ed90a9e8b8ed19b41d2384937e0f9f5bd"}, + {file = "rapidfuzz-2.13.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c31022d9970177f6affc6d5dd757ed22e44a10890212032fabab903fdee3bfe7"}, + {file = "rapidfuzz-2.13.7-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f42b82f268689f429def9ecfb86fa65ceea0eaf3fed408b570fe113311bf5ce7"}, + {file = "rapidfuzz-2.13.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b477b43ced896301665183a5e0faec0f5aea2373005648da8bdcb3c4b73f280"}, + {file = "rapidfuzz-2.13.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d63def9bbc6b35aef4d76dc740301a4185867e8870cbb8719ec9de672212fca8"}, + {file = "rapidfuzz-2.13.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c66546e30addb04a16cd864f10f5821272a1bfe6462ee5605613b4f1cb6f7b48"}, + {file = "rapidfuzz-2.13.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f799d1d6c33d81e983d3682571cc7d993ae7ff772c19b3aabb767039c33f6d1e"}, + {file = "rapidfuzz-2.13.7-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d82f20c0060ffdaadaf642b88ab0aa52365b56dffae812e188e5bdb998043588"}, + {file = "rapidfuzz-2.13.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042644133244bfa7b20de635d500eb9f46af7097f3d90b1724f94866f17cb55e"}, + {file = "rapidfuzz-2.13.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75c45dcd595f8178412367e302fd022860ea025dc4a78b197b35428081ed33d5"}, + {file = "rapidfuzz-2.13.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3d8b081988d0a49c486e4e845a547565fee7c6e7ad8be57ff29c3d7c14c6894c"}, + {file = "rapidfuzz-2.13.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16ffad751f43ab61001187b3fb4a9447ec2d1aedeff7c5bac86d3b95f9980cc3"}, + {file = "rapidfuzz-2.13.7-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:020858dd89b60ce38811cd6e37875c4c3c8d7fcd8bc20a0ad2ed1f464b34dc4e"}, + {file = "rapidfuzz-2.13.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cda1e2f66bb4ba7261a0f4c2d052d5d909798fca557cbff68f8a79a87d66a18f"}, + {file = "rapidfuzz-2.13.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b6389c50d8d214c9cd11a77f6d501529cb23279a9c9cafe519a3a4b503b5f72a"}, + {file = "rapidfuzz-2.13.7.tar.gz", hash = "sha256:8d3e252d4127c79b4d7c2ae47271636cbaca905c8bb46d80c7930ab906cf4b5c"}, +] + +[package.extras] +full = ["numpy"] + +[[package]] +name = "requests" +version = "2.28.2" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, + {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-toolbelt" +version = "0.10.1" +description = "A utility belt for advanced users of python-requests" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-0.10.1.tar.gz", hash = "sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d"}, + {file = "requests_toolbelt-0.10.1-py2.py3-none-any.whl", hash = "sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "setuptools" +version = "67.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-67.0.0-py3-none-any.whl", hash = "sha256:9d790961ba6219e9ff7d9557622d2fe136816a264dd01d5997cfc057d804853d"}, + {file = "setuptools-67.0.0.tar.gz", hash = "sha256:883131c5b6efa70b9101c7ef30b2b7b780a4283d5fc1616383cdf22c83cbefe6"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shellingham" +version = "1.5.0.post1" +description = "Tool to Detect Surrounding Shell" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.0.post1-py2.py3-none-any.whl", hash = "sha256:368bf8c00754fd4f55afb7bbb86e272df77e4dc76ac29dbcbb81a59e9fc15744"}, + {file = "shellingham-1.5.0.post1.tar.gz", hash = "sha256:823bc5fb5c34d60f285b624e7264f4dda254bc803a3774a147bf99c0e3004a28"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.11.6" +description = "Style preserving TOML library" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, + {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, +] + +[[package]] +name = "trove-classifiers" +version = "2023.1.20" +description = "Canonical source for classifiers on PyPI (pypi.org)." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "trove-classifiers-2023.1.20.tar.gz", hash = "sha256:ed3fd4e1d2ea77ca9ed379380582566e6003a54d70a3e5521a9b2a7896609362"}, + {file = "trove_classifiers-2023.1.20-py3-none-any.whl", hash = "sha256:e4fb91e744a1b8a4e416226dbd4c3a58717295f54608e412cee526287d5d14d0"}, +] + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] + +[[package]] +name = "types-html5lib" +version = "1.1.11.11" +description = "Typing stubs for html5lib" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-html5lib-1.1.11.11.tar.gz", hash = "sha256:8cf7fb57dcaf3e612806e9bc25ae366ff7ca71a3418ae829f5b1a9c52cbb4960"}, + {file = "types_html5lib-1.1.11.11-py3-none-any.whl", hash = "sha256:7456a07a4d162bb8c42c2a088c60cca7c63d06cf2c409a8de39536a6cdbbccc2"}, +] + +[[package]] +name = "types-jsonschema" +version = "4.17.0.3" +description = "Typing stubs for jsonschema" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-jsonschema-4.17.0.3.tar.gz", hash = "sha256:746aa466ffed9a1acc7bdbd0ac0b5e068f00be2ee008c1d1e14b0944a8c8b24b"}, + {file = "types_jsonschema-4.17.0.3-py3-none-any.whl", hash = "sha256:c8d5b26b7c8da6a48d7fb1ce029b97e0ff6e74db3727efb968c69f39ad013685"}, +] + +[[package]] +name = "types-requests" +version = "2.28.11.8" +description = "Typing stubs for requests" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-requests-2.28.11.8.tar.gz", hash = "sha256:e67424525f84adfbeab7268a159d3c633862dafae15c5b19547ce1b55954f0a3"}, + {file = "types_requests-2.28.11.8-py3-none-any.whl", hash = "sha256:61960554baca0008ae7e2db2bd3b322ca9a144d3e80ce270f5fb640817e40994"}, +] + +[package.dependencies] +types-urllib3 = "<1.27" + +[[package]] +name = "types-urllib3" +version = "1.26.25.4" +description = "Typing stubs for urllib3" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-urllib3-1.26.25.4.tar.gz", hash = "sha256:eec5556428eec862b1ac578fb69aab3877995a99ffec9e5a12cf7fbd0cc9daee"}, + {file = "types_urllib3-1.26.25.4-py3-none-any.whl", hash = "sha256:ed6b9e8a8be488796f72306889a06a3fc3cb1aa99af02ab8afb50144d7317e49"}, +] + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] + +[[package]] +name = "urllib3" +version = "1.26.14" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, + {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.16.5" +description = "Virtual Python Environment builder" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"}, + {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"}, +] + +[package.dependencies] +distlib = ">=0.3.5,<1" +filelock = ">=3.4.1,<4" +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "virtualenv" +version = "20.17.1" +description = "Virtual Python Environment builder" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, + {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, +] + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.4.1,<4" +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "xattr" +version = "0.10.1" +description = "Python wrapper for extended filesystem attributes" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "xattr-0.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:16a660a883e703b311d1bbbcafc74fa877585ec081cd96e8dd9302c028408ab1"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1e2973e72faa87ca29d61c23b58c3c89fe102d1b68e091848b0e21a104123503"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:13279fe8f7982e3cdb0e088d5cb340ce9cbe5ef92504b1fd80a0d3591d662f68"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1dc9b9f580ef4b8ac5e2c04c16b4d5086a611889ac14ecb2e7e87170623a0b75"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:485539262c2b1f5acd6b6ea56e0da2bc281a51f74335c351ea609c23d82c9a79"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:295b3ab335fcd06ca0a9114439b34120968732e3f5e9d16f456d5ec4fa47a0a2"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a126eb38e14a2f273d584a692fe36cff760395bf7fc061ef059224efdb4eb62c"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:b0e919c24f5b74428afa91507b15e7d2ef63aba98e704ad13d33bed1288dca81"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e31d062cfe1aaeab6ba3db6bd255f012d105271018e647645941d6609376af18"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:209fb84c09b41c2e4cf16dd2f481bb4a6e2e81f659a47a60091b9bcb2e388840"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c4120090dac33eddffc27e487f9c8f16b29ff3f3f8bcb2251b2c6c3f974ca1e1"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e739d624491267ec5bb740f4eada93491de429d38d2fcdfb97b25efe1288eca"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2677d40b95636f3482bdaf64ed9138fb4d8376fb7933f434614744780e46e42d"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40039f1532c4456fd0f4c54e9d4e01eb8201248c321c6c6856262d87e9a99593"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:148466e5bb168aba98f80850cf976e931469a3c6eb11e9880d9f6f8b1e66bd06"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0aedf55b116beb6427e6f7958ccd80a8cbc80e82f87a4cd975ccb61a8d27b2ee"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3024a9ff157247c8190dd0eb54db4a64277f21361b2f756319d9d3cf20e475f"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f1be6e733e9698f645dbb98565bb8df9b75e80e15a21eb52787d7d96800e823b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7880c8a54c18bc091a4ce0adc5c6d81da1c748aec2fe7ac586d204d6ec7eca5b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:89c93b42c3ba8aedbc29da759f152731196c2492a2154371c0aae3ef8ba8301b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b905e808df61b677eb972f915f8a751960284358b520d0601c8cbc476ba2df6"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ef954d0655f93a34d07d0cc7e02765ec779ff0b59dc898ee08c6326ad614d5"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:199b20301b6acc9022661412346714ce764d322068ef387c4de38062474db76c"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec0956a8ab0f0d3f9011ba480f1e1271b703d11542375ef73eb8695a6bd4b78b"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffcb57ca1be338d69edad93cf59aac7c6bb4dbb92fd7bf8d456c69ea42f7e6d2"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f0563196ee54756fe2047627d316977dc77d11acd7a07970336e1a711e934db"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc354f086f926a1c7f04886f97880fed1a26d20e3bc338d0d965fd161dbdb8ab"}, + {file = "xattr-0.10.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c0cd2d02ef2fb45ecf2b0da066a58472d54682c6d4f0452dfe7ae2f3a76a42ea"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49626096ddd72dcc1654aadd84b103577d8424f26524a48d199847b5d55612d0"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceaa26bef8fcb17eb59d92a7481c2d15d20211e217772fb43c08c859b01afc6a"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c014c371391f28f8cd27d73ea59f42b30772cd640b5a2538ad4f440fd9190b"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:46c32cd605673606b9388a313b0050ee7877a0640d7561eea243ace4fa2cc5a6"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:772b22c4ff791fe5816a7c2a1c9fcba83f9ab9bea138eb44d4d70f34676232b4"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:183ad611a2d70b5a3f5f7aadef0fcef604ea33dcf508228765fd4ddac2c7321d"}, + {file = "xattr-0.10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8068df3ebdfa9411e58d5ae4a05d807ec5994645bb01af66ec9f6da718b65c5b"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bc40570155beb85e963ae45300a530223d9822edfdf09991b880e69625ba38a"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:436e1aaf23c07e15bed63115f1712d2097e207214fc6bcde147c1efede37e2c5"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7298455ccf3a922d403339781b10299b858bb5ec76435445f2da46fb768e31a5"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:986c2305c6c1a08f78611eb38ef9f1f47682774ce954efb5a4f3715e8da00d5f"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5dc6099e76e33fa3082a905fe59df766b196534c705cf7a2e3ad9bed2b8a180e"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:042ad818cda6013162c0bfd3816f6b74b7700e73c908cde6768da824686885f8"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d4c306828a45b41b76ca17adc26ac3dc00a80e01a5ba85d71df2a3e948828f2"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a606280b0c9071ef52572434ecd3648407b20df3d27af02c6592e84486b05894"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5b49d591cf34cda2079fd7a5cb2a7a1519f54dc2e62abe3e0720036f6ed41a85"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8705ac6791426559c1a5c2b88bb2f0e83dc5616a09b4500899bfff6a929302"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5ea974930e876bc5c146f54ac0f85bb39b7b5de2b6fc63f90364712ae368ebe"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f55a2dd73a12a1ae5113c5d9cd4b4ab6bf7950f4d76d0a1a0c0c4264d50da61d"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:475c38da0d3614cc5564467c4efece1e38bd0705a4dbecf8deeb0564a86fb010"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:925284a4a28e369459b2b7481ea22840eed3e0573a4a4c06b6b0614ecd27d0a7"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa32f1b45fed9122bed911de0fcc654da349e1f04fa4a9c8ef9b53e1cc98b91e"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c5d3d0e728bace64b74c475eb4da6148cd172b2d23021a1dcd055d92f17619ac"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8faaacf311e2b5cc67c030c999167a78a9906073e6abf08eaa8cf05b0416515c"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6b8d5ca452674e1a96e246a3d2db5f477aecbc7c945c73f890f56323e75203"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3725746a6502f40f72ef27e0c7bfc31052a239503ff3eefa807d6b02a249be22"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:789bd406d1aad6735e97b20c6d6a1701e1c0661136be9be862e6a04564da771f"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9a7a807ab538210ff8532220d8fc5e2d51c212681f63dbd4e7ede32543b070f"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3e5825b5fc99ecdd493b0cc09ec35391e7a451394fdf623a88b24726011c950d"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80638d1ce7189dc52f26c234cee3522f060fadab6a8bc3562fe0ddcbe11ba5a4"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3ff0dbe4a6ce2ce065c6de08f415bcb270ecfd7bf1655a633ddeac695ce8b250"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5267e5f9435c840d2674194150b511bef929fa7d3bc942a4a75b9eddef18d8d8"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b27dfc13b193cb290d5d9e62f806bb9a99b00cd73bb6370d556116ad7bb5dc12"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:636ebdde0277bce4d12d2ef2550885804834418fee0eb456b69be928e604ecc4"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d60c27922ec80310b45574351f71e0dd3a139c5295e8f8b19d19c0010196544f"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b34df5aad035d0343bd740a95ca30db99b776e2630dca9cc1ba8e682c9cc25ea"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24a7c04ff666d0fe905dfee0a84bc899d624aeb6dccd1ea86b5c347f15c20c1"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3878e1aff8eca64badad8f6d896cb98c52984b1e9cd9668a3ab70294d1ef92d"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4abef557028c551d59cf2fb3bf63f2a0c89f00d77e54c1c15282ecdd56943496"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0e14bd5965d3db173d6983abdc1241c22219385c22df8b0eb8f1846c15ce1fee"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9be588a4b6043b03777d50654c6079af3da60cc37527dbb80d36ec98842b1e"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bc4ae264aa679aacf964abf3ea88e147eb4a22aea6af8c6d03ebdebd64cfd6"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:827b5a97673b9997067fde383a7f7dc67342403093b94ea3c24ae0f4f1fec649"}, + {file = "xattr-0.10.1.tar.gz", hash = "sha256:c12e7d81ffaa0605b3ac8c22c2994a8e18a9cf1c59287a1b7722a2289c952ec5"}, +] + +[package.dependencies] +cffi = ">=1.0" + +[[package]] +name = "zipp" +version = "3.12.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.12.0-py3-none-any.whl", hash = "sha256:9eb0a4c5feab9b08871db0d672745b53450d7f26992fd1e4653aa43345e97b86"}, + {file = "zipp-3.12.0.tar.gz", hash = "sha256:73efd63936398aac78fd92b6f4865190119d6c91b531532e798977ea8dd402eb"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.7" +content-hash = "b200bd8e190a3e7410d91b29289e29149a8aa29fa633e344aed929ff16d7cd11" \ No newline at end of file diff --git a/pkg/dependency/parser/python/poetry/testdata/poetry_normal.lock b/pkg/dependency/parser/python/poetry/testdata/poetry_normal.lock new file mode 100644 index 000000000000..0f814daa20a9 --- /dev/null +++ b/pkg/dependency/parser/python/poetry/testdata/poetry_normal.lock @@ -0,0 +1,17 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "pypi" +version = "2.1" +description = "PyPI is the Python Package Index at http://pypi.org/" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pypi-2.1.tar.gz", hash = "sha256:f014c87f871c2143ff613d9d13a59cc8ab8309b048754c27da8c58b21de2c10d"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "8fe1a16a0f2018499bedde14bd5c44aa7d9540b90c1fe0d53610a62f22e4f612" diff --git a/pkg/dependency/parser/python/pyproject/pyproject.go b/pkg/dependency/parser/python/pyproject/pyproject.go new file mode 100644 index 000000000000..9a1532027a0c --- /dev/null +++ b/pkg/dependency/parser/python/pyproject/pyproject.go @@ -0,0 +1,37 @@ +package pyproject + +import ( + "io" + + "github.com/BurntSushi/toml" + "golang.org/x/xerrors" +) + +type PyProject struct { + Tool Tool `toml:"tool"` +} + +type Tool struct { + Poetry Poetry `toml:"poetry"` +} + +type Poetry struct { + Dependencies map[string]interface{} `toml:"dependencies"` +} + +// Parser parses pyproject.toml defined in PEP518. +// https://peps.python.org/pep-0518/ +type Parser struct { +} + +func NewParser() *Parser { + return &Parser{} +} + +func (p *Parser) Parse(r io.Reader) (map[string]interface{}, error) { + var conf PyProject + if _, err := toml.NewDecoder(r).Decode(&conf); err != nil { + return nil, xerrors.Errorf("toml decode error: %w", err) + } + return conf.Tool.Poetry.Dependencies, nil +} diff --git a/pkg/dependency/parser/python/pyproject/pyproject_test.go b/pkg/dependency/parser/python/pyproject/pyproject_test.go new file mode 100644 index 000000000000..ac72bad81211 --- /dev/null +++ b/pkg/dependency/parser/python/pyproject/pyproject_test.go @@ -0,0 +1,63 @@ +package pyproject_test + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/pyproject" +) + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + file string + want map[string]interface{} + wantErr assert.ErrorAssertionFunc + }{ + { + name: "happy path", + file: "testdata/happy.toml", + want: map[string]interface{}{ + "flask": "^1.0", + "python": "^3.9", + "requests": map[string]interface{}{ + "version": "2.28.1", + "optional": true, + }, + "virtualenv": []interface{}{ + map[string]interface{}{ + "version": "^20.4.3,!=20.4.5,!=20.4.6", + }, + map[string]interface{}{ + "version": "<20.16.6", + "markers": "sys_platform == 'win32' and python_version == '3.9'", + }, + }, + }, + wantErr: assert.NoError, + }, + { + name: "sad path", + file: "testdata/sad.toml", + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + defer f.Close() + + p := &pyproject.Parser{} + got, err := p.Parse(f) + if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.file)) { + return + } + assert.Equalf(t, tt.want, got, "Parse(%v)", tt.file) + }) + } +} diff --git a/pkg/dependency/parser/python/pyproject/testdata/happy.toml b/pkg/dependency/parser/python/pyproject/testdata/happy.toml new file mode 100644 index 000000000000..2cfd27e2acb2 --- /dev/null +++ b/pkg/dependency/parser/python/pyproject/testdata/happy.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "example" +version = "0.1.0" +description = "My Hello World Example" + +[tool.poetry.dependencies] +python = "^3.9" +flask = "^1.0" +requests = {version = "2.28.1", optional = true} +virtualenv = [ + { version = "^20.4.3,!=20.4.5,!=20.4.6" }, + { version = "<20.16.6", markers = "sys_platform == 'win32' and python_version == '3.9'" }, +] + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/pkg/dependency/parser/python/pyproject/testdata/sad.toml b/pkg/dependency/parser/python/pyproject/testdata/sad.toml new file mode 100644 index 000000000000..8e2f0bef135b --- /dev/null +++ b/pkg/dependency/parser/python/pyproject/testdata/sad.toml @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/pkg/dependency/parser/ruby/bundler/parse.go b/pkg/dependency/parser/ruby/bundler/parse.go new file mode 100644 index 000000000000..e8cd538e0da6 --- /dev/null +++ b/pkg/dependency/parser/ruby/bundler/parse.go @@ -0,0 +1,146 @@ +package bundler + +import ( + "bufio" + "sort" + "strings" + + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + libs := make(map[string]types.Library) + var dependsOn, directDeps []string + var deps []types.Dependency + var pkgID string + + lineNum := 1 + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + + // Parse dependencies + if countLeadingSpace(line) == 4 { + if len(dependsOn) > 0 { + deps = append(deps, types.Dependency{ + ID: pkgID, + DependsOn: dependsOn, + }) + } + dependsOn = make([]string, 0) // re-initialize + line = strings.TrimSpace(line) + s := strings.Fields(line) + if len(s) != 2 { + continue + } + version := strings.Trim(s[1], "()") // drop parentheses + version = strings.SplitN(version, "-", 2)[0] // drop platform (e.g. 1.13.6-x86_64-linux => 1.13.6) + name := s[0] + pkgID = packageID(name, version) + libs[name] = types.Library{ + ID: pkgID, + Name: name, + Version: version, + Indirect: true, + Locations: []types.Location{ + { + StartLine: lineNum, + EndLine: lineNum, + }, + }, + } + } + // Parse dependency graph + if countLeadingSpace(line) == 6 { + line = strings.TrimSpace(line) + s := strings.Fields(line) + dependsOn = append(dependsOn, s[0]) // store name only for now + } + lineNum++ + + // Parse direct dependencies + if line == "DEPENDENCIES" { + directDeps = parseDirectDeps(scanner) + } + } + // append last dependency (if any) + if len(dependsOn) > 0 { + deps = append(deps, types.Dependency{ + ID: pkgID, + DependsOn: dependsOn, + }) + } + + // Identify which are direct dependencies + for _, d := range directDeps { + if l, ok := libs[d]; ok { + l.Indirect = false + libs[d] = l + } + } + + for i, dep := range deps { + dependsOn = make([]string, 0) + for _, pkgName := range dep.DependsOn { + if lib, ok := libs[pkgName]; ok { + dependsOn = append(dependsOn, packageID(pkgName, lib.Version)) + } + } + deps[i].DependsOn = dependsOn + } + if err := scanner.Err(); err != nil { + return nil, nil, xerrors.Errorf("scan error: %w", err) + } + + libSlice := maps.Values(libs) + sort.Slice(libSlice, func(i, j int) bool { + return libSlice[i].Name < libSlice[j].Name + }) + return libSlice, deps, nil +} + +func countLeadingSpace(line string) int { + i := 0 + for _, runeValue := range line { + if runeValue == ' ' { + i++ + } else { + break + } + } + return i +} + +// Parse "DEPENDENCIES" +func parseDirectDeps(scanner *bufio.Scanner) []string { + var deps []string + for scanner.Scan() { + line := scanner.Text() + if countLeadingSpace(line) != 2 { + // Reach another section + break + } + ss := strings.Fields(line) + if len(ss) == 0 { + continue + } + deps = append(deps, ss[0]) + } + return deps +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.Bundler, name, version) +} diff --git a/pkg/dependency/parser/ruby/bundler/parse_test.go b/pkg/dependency/parser/ruby/bundler/parse_test.go new file mode 100644 index 000000000000..6b2b27dd5e3a --- /dev/null +++ b/pkg/dependency/parser/ruby/bundler/parse_test.go @@ -0,0 +1,198 @@ +package bundler_test + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/ruby/bundler" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +var ( + NormalLibs = []types.Library{ + { + ID: "coderay@1.1.2", + Name: "coderay", + Version: "1.1.2", + Indirect: true, + Locations: []types.Location{{StartLine: 4, EndLine: 4}}, + }, + { + ID: "concurrent-ruby@1.1.5", + Name: "concurrent-ruby", + Version: "1.1.5", + Indirect: true, + Locations: []types.Location{{StartLine: 5, EndLine: 5}}, + }, + { + ID: "dotenv@2.7.2", + Name: "dotenv", + Version: "2.7.2", + Locations: []types.Location{{StartLine: 6, EndLine: 6}}, + }, + { + ID: "faker@1.9.3", + Name: "faker", + Version: "1.9.3", + Locations: []types.Location{{StartLine: 7, EndLine: 7}}, + }, + { + ID: "i18n@1.6.0", + Name: "i18n", + Version: "1.6.0", + Indirect: true, + Locations: []types.Location{{StartLine: 9, EndLine: 9}}, + }, + { + ID: "method_source@0.9.2", + Name: "method_source", + Version: "0.9.2", + Indirect: true, + Locations: []types.Location{{StartLine: 11, EndLine: 11}}, + }, + { + ID: "pry@0.12.2", + Name: "pry", + Version: "0.12.2", + Locations: []types.Location{{StartLine: 12, EndLine: 12}}, + }, + } + NormalDeps = []types.Dependency{ + { + ID: "faker@1.9.3", + DependsOn: []string{"i18n@1.6.0"}, + }, + { + ID: "i18n@1.6.0", + DependsOn: []string{"concurrent-ruby@1.1.5"}, + }, + { + ID: "pry@0.12.2", + DependsOn: []string{ + "coderay@1.1.2", + "method_source@0.9.2", + }, + }, + } + Bundler2Libs = []types.Library{ + { + ID: "coderay@1.1.3", + Name: "coderay", + Version: "1.1.3", + Indirect: true, + Locations: []types.Location{{StartLine: 4, EndLine: 4}}, + }, + { + ID: "concurrent-ruby@1.1.10", + Name: "concurrent-ruby", + Version: "1.1.10", + Indirect: true, + Locations: []types.Location{{StartLine: 5, EndLine: 5}}, + }, + { + ID: "dotenv@2.7.6", + Name: "dotenv", + Version: "2.7.6", + Locations: []types.Location{{StartLine: 6, EndLine: 6}}, + }, + { + ID: "faker@2.21.0", + Name: "faker", + Version: "2.21.0", + Locations: []types.Location{{StartLine: 7, EndLine: 7}}, + }, + { + ID: "i18n@1.10.0", + Name: "i18n", + Version: "1.10.0", + Indirect: true, + Locations: []types.Location{{StartLine: 9, EndLine: 9}}, + }, + { + ID: "json@2.6.2", + Name: "json", + Version: "2.6.2", + Locations: []types.Location{{StartLine: 11, EndLine: 11}}, + }, + { + ID: "method_source@1.0.0", + Name: "method_source", + Version: "1.0.0", + Indirect: true, + Locations: []types.Location{{StartLine: 12, EndLine: 12}}, + }, + { + ID: "pry@0.14.1", + Name: "pry", + Version: "0.14.1", + Locations: []types.Location{{StartLine: 13, EndLine: 13}}, + }, + } + Bundler2Deps = []types.Dependency{ + { + ID: "faker@2.21.0", + DependsOn: []string{"i18n@1.10.0"}, + }, + { + ID: "i18n@1.10.0", + DependsOn: []string{"concurrent-ruby@1.1.10"}, + }, + { + ID: "pry@0.14.1", + DependsOn: []string{ + "coderay@1.1.3", + "method_source@1.0.0", + }, + }, + } +) + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + file string + wantLibs []types.Library + wantDeps []types.Dependency + wantErr assert.ErrorAssertionFunc + }{ + { + name: "normal", + file: "testdata/Gemfile_normal.lock", + wantLibs: NormalLibs, + wantDeps: NormalDeps, + wantErr: assert.NoError, + }, + { + name: "bundler2", + file: "testdata/Gemfile_bundler2.lock", + wantLibs: Bundler2Libs, + wantDeps: Bundler2Deps, + wantErr: assert.NoError, + }, + { + name: "malformed", + file: "testdata/Gemfile_malformed.lock", + wantLibs: []types.Library{}, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + defer f.Close() + + p := &bundler.Parser{} + gotLibs, gotDeps, err := p.Parse(f) + if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.file)) { + return + } + assert.Equalf(t, tt.wantLibs, gotLibs, "Parse(%v)", tt.file) + assert.Equalf(t, tt.wantDeps, gotDeps, "Parse(%v)", tt.file) + }) + } +} diff --git a/pkg/dependency/parser/ruby/bundler/testdata/Gemfile_bundler2.lock b/pkg/dependency/parser/ruby/bundler/testdata/Gemfile_bundler2.lock new file mode 100644 index 000000000000..55c0d8370cf3 --- /dev/null +++ b/pkg/dependency/parser/ruby/bundler/testdata/Gemfile_bundler2.lock @@ -0,0 +1,27 @@ +GEM + remote: https://rubygems.org/ + specs: + coderay (1.1.3) + concurrent-ruby (1.1.10) + dotenv (2.7.6) + faker (2.21.0) + i18n (>= 1.8.11, < 2) + i18n (1.10.0) + concurrent-ruby (~> 1.0) + json (2.6.2) + method_source (1.0.0) + pry (0.14.1) + coderay (~> 1.1) + method_source (~> 1.0) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + dotenv (~> 2.7) + faker (~> 2.21) + json (~> 2.6) + pry (~> 0.14.1) + +BUNDLED WITH + 2.3.7 \ No newline at end of file diff --git a/pkg/dependency/parser/ruby/bundler/testdata/Gemfile_malformed.lock b/pkg/dependency/parser/ruby/bundler/testdata/Gemfile_malformed.lock new file mode 100644 index 000000000000..eca77f35437e --- /dev/null +++ b/pkg/dependency/parser/ruby/bundler/testdata/Gemfile_malformed.lock @@ -0,0 +1 @@ +malformed \ No newline at end of file diff --git a/pkg/dependency/parser/ruby/bundler/testdata/Gemfile_normal.lock b/pkg/dependency/parser/ruby/bundler/testdata/Gemfile_normal.lock new file mode 100644 index 000000000000..d213f9f62322 --- /dev/null +++ b/pkg/dependency/parser/ruby/bundler/testdata/Gemfile_normal.lock @@ -0,0 +1,25 @@ +GEM + remote: https://rubygems.org/ + specs: + coderay (1.1.2) + concurrent-ruby (1.1.5) + dotenv (2.7.2) + faker (1.9.3) + i18n (>= 0.7) + i18n (1.6.0) + concurrent-ruby (~> 1.0) + method_source (0.9.2) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + +PLATFORMS + ruby + +DEPENDENCIES + dotenv (~> 2.7) + faker (~> 1.9) + pry (~> 0.12.2) + +BUNDLED WITH + 1.17.2 diff --git a/pkg/dependency/parser/ruby/gemspec/parse.go b/pkg/dependency/parser/ruby/gemspec/parse.go new file mode 100644 index 000000000000..e458f6bacd0e --- /dev/null +++ b/pkg/dependency/parser/ruby/gemspec/parse.go @@ -0,0 +1,135 @@ +package gemspec + +import ( + "bufio" + "fmt" + "regexp" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +const specNewStr = "Gem::Specification.new" + +var ( + // Capture the variable name + // e.g. Gem::Specification.new do |s| + // => s + newVarRegexp = regexp.MustCompile(`\|(?P.*)\|`) + + // Capture the value of "name" + // e.g. s.name = "async".freeze + // => "async".freeze + nameRegexp = regexp.MustCompile(`\.name\s*=\s*(?P\S+)`) + + // Capture the value of "version" + // e.g. s.version = "1.2.3" + // => "1.2.3" + versionRegexp = regexp.MustCompile(`\.version\s*=\s*(?P\S+)`) + + // Capture the value of "license" + // e.g. s.license = "MIT" + // => "MIT" + licenseRegexp = regexp.MustCompile(`\.license\s*=\s*(?P\S+)`) + + // Capture the value of "licenses" + // e.g. s.license = ["MIT".freeze, "BSDL".freeze] + // => "MIT".freeze, "BSDL".freeze + licensesRegexp = regexp.MustCompile(`\.licenses\s*=\s*\[(?P.+)\]`) +) + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) (libs []types.Library, deps []types.Dependency, err error) { + var newVar, name, version, license string + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.Contains(line, specNewStr) { + newVar = findSubString(newVarRegexp, line, "var") + } + + if newVar == "" { + continue + } + + // Capture name, version, license, and licenses + switch { + case strings.HasPrefix(line, fmt.Sprintf("%s.name", newVar)): + // https://guides.rubygems.org/specification-reference/#name + name = findSubString(nameRegexp, line, "name") + name = trim(name) + case strings.HasPrefix(line, fmt.Sprintf("%s.version", newVar)): + // https://guides.rubygems.org/specification-reference/#version + version = findSubString(versionRegexp, line, "version") + version = trim(version) + case strings.HasPrefix(line, fmt.Sprintf("%s.licenses", newVar)): + // https://guides.rubygems.org/specification-reference/#licenses= + license = findSubString(licensesRegexp, line, "licenses") + license = parseLicenses(license) + case strings.HasPrefix(line, fmt.Sprintf("%s.license", newVar)): + // https://guides.rubygems.org/specification-reference/#license= + license = findSubString(licenseRegexp, line, "license") + license = trim(license) + } + + // No need to iterate the loop anymore + if name != "" && version != "" && license != "" { + break + } + } + if err := scanner.Err(); err != nil { + return nil, nil, xerrors.Errorf("failed to parse gemspec: %w", err) + } + + if name == "" || version == "" { + return nil, nil, xerrors.New("failed to parse gemspec") + } + + return []types.Library{ + { + Name: name, + Version: version, + License: license, + }, + }, nil, nil +} + +func findSubString(re *regexp.Regexp, line, name string) string { + m := re.FindStringSubmatch(line) + if m == nil { + return "" + } + return m[re.SubexpIndex(name)] +} + +// Trim single quotes, double quotes and ".freeze" +// e.g. "async".freeze => async +func trim(s string) string { + s = strings.TrimSpace(s) + s = strings.TrimSuffix(s, ".freeze") + return strings.Trim(s, `'"`) +} + +func parseLicenses(s string) string { + // e.g. `"Ruby".freeze, "BSDL".freeze` + // => {"\"Ruby\".freeze", "\"BSDL\".freeze"} + ss := strings.Split(s, ",") + + // e.g. {"\"Ruby\".freeze", "\"BSDL\".freeze"} + // => {"Ruby", "BSDL"} + var licenses []string + for _, l := range ss { + licenses = append(licenses, trim(l)) + } + + return strings.Join(licenses, ", ") +} diff --git a/pkg/dependency/parser/ruby/gemspec/parse_test.go b/pkg/dependency/parser/ruby/gemspec/parse_test.go new file mode 100644 index 000000000000..586c0ee6b941 --- /dev/null +++ b/pkg/dependency/parser/ruby/gemspec/parse_test.go @@ -0,0 +1,81 @@ +package gemspec_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/ruby/gemspec" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + inputFile string + want []types.Library + wantErr string + }{ + { + name: "happy", + inputFile: "testdata/normal00.gemspec", + want: []types.Library{{ + Name: "rake", + Version: "13.0.3", + License: "MIT", + }}, + }, + { + name: "another variable name", + inputFile: "testdata/normal01.gemspec", + want: []types.Library{{ + Name: "async", + Version: "1.25.0", + }}, + }, + { + name: "license", + inputFile: "testdata/license.gemspec", + want: []types.Library{{ + Name: "async", + Version: "1.25.0", + License: "MIT", + }}, + }, + { + name: "multiple licenses", + inputFile: "testdata/multiple_licenses.gemspec", + want: []types.Library{{ + Name: "test-unit", + Version: "3.3.7", + License: "Ruby, BSDL, PSFL", + }}, + }, + { + name: "malformed variable name", + inputFile: "testdata/malformed00.gemspec", + wantErr: "failed to parse gemspec", + }, + { + name: "missing version", + inputFile: "testdata/malformed01.gemspec", + wantErr: "failed to parse gemspec", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + + got, _, err := gemspec.NewParser().Parse(f) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/ruby/gemspec/testdata/license.gemspec b/pkg/dependency/parser/ruby/gemspec/testdata/license.gemspec new file mode 100644 index 000000000000..ef7dbe77c65a --- /dev/null +++ b/pkg/dependency/parser/ruby/gemspec/testdata/license.gemspec @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- +# ... REDACTED ... + +Gem::Specification.new do |spec| + spec.name = "async".freeze + spec.version = "1.25.0" + spec.license = "MIT" + + # ... REDACTED ... +end diff --git a/pkg/dependency/parser/ruby/gemspec/testdata/malformed00.gemspec b/pkg/dependency/parser/ruby/gemspec/testdata/malformed00.gemspec new file mode 100644 index 000000000000..49b890354fb5 --- /dev/null +++ b/pkg/dependency/parser/ruby/gemspec/testdata/malformed00.gemspec @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- +# ... REDACTED ... + +# Wrong attribute value assignment. +Gem::Specification.new do |spec| + s.name = "async".freeze + s.version = "1.25.0" + + # ... REDACTED ... +end diff --git a/pkg/dependency/parser/ruby/gemspec/testdata/malformed01.gemspec b/pkg/dependency/parser/ruby/gemspec/testdata/malformed01.gemspec new file mode 100644 index 000000000000..253ba4888881 --- /dev/null +++ b/pkg/dependency/parser/ruby/gemspec/testdata/malformed01.gemspec @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- +# ... REDACTED ... + +# Missing version attribute. + +Gem::Specification.new do |s| + s.name = "async".freeze + + # ... REDACTED ... +end diff --git a/pkg/dependency/parser/ruby/gemspec/testdata/multiple_licenses.gemspec b/pkg/dependency/parser/ruby/gemspec/testdata/multiple_licenses.gemspec new file mode 100644 index 000000000000..999ab6129df8 --- /dev/null +++ b/pkg/dependency/parser/ruby/gemspec/testdata/multiple_licenses.gemspec @@ -0,0 +1,41 @@ +# -*- encoding: utf-8 -*- +# stub: test-unit 3.3.7 ruby lib + +Gem::Specification.new do |s| + s.name = "test-unit".freeze + s.version = "3.3.7" + + s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= + s.metadata = { "source_code_uri" => "https://github.com/test-unit/test-unit" } if s.respond_to? :metadata= + s.require_paths = ["lib".freeze] + s.authors = ["Kouhei Sutou".freeze, "Haruka Yoshihara".freeze] + s.date = "2020-11-18" + s.description = "test-unit (Test::Unit) is unit testing framework for Ruby, based on xUnit\nprinciples. These were originally designed by Kent Beck, creator of extreme\nprogramming software development methodology, for Smalltalk's SUnit. It allows\nwriting tests, checking results and automated testing in Ruby.\n".freeze + s.email = ["kou@cozmixng.org".freeze, "yoshihara@clear-code.com".freeze] + s.homepage = "http://test-unit.github.io/".freeze + s.licenses = ["Ruby".freeze,"BSDL".freeze, "PSFL".freeze] + s.rubygems_version = "3.2.22".freeze + s.summary = "An xUnit family unit testing framework for Ruby.".freeze + + s.installed_by_version = "3.2.22" if s.respond_to? :installed_by_version + + if s.respond_to? :specification_version then + s.specification_version = 4 + end + + if s.respond_to? :add_runtime_dependency then + s.add_runtime_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + else + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + end +end diff --git a/pkg/dependency/parser/ruby/gemspec/testdata/normal00.gemspec b/pkg/dependency/parser/ruby/gemspec/testdata/normal00.gemspec new file mode 100644 index 000000000000..afb9e85b0aa2 --- /dev/null +++ b/pkg/dependency/parser/ruby/gemspec/testdata/normal00.gemspec @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +# stub: rake 13.0.3 ruby lib + +Gem::Specification.new do |s| + s.name = "rake".freeze + s.version = "13.0.3" + + s.required_rubygems_version = Gem::Requirement.new(">= 1.3.2".freeze) if s.respond_to? :required_rubygems_version= + s.metadata = { "bug_tracker_uri" => "https://github.com/ruby/rake/issues", "changelog_uri" => "https://github.com/ruby/rake/blob/v13.0.3/History.rdoc", "documentation_uri" => "https://ruby.github.io/rake", "source_code_uri" => "https://github.com/ruby/rake/tree/v13.0.3" } if s.respond_to? :metadata= + s.require_paths = ["lib".freeze] + s.authors = ["Hiroshi SHIBATA".freeze, "Eric Hodel".freeze, "Jim Weirich".freeze] + s.bindir = "exe".freeze + s.date = "2020-12-21" + s.description = "Rake is a Make-like program implemented in Ruby. Tasks and dependencies are\nspecified in standard Ruby syntax.\nRake has the following features:\n * Rakefiles (rake's version of Makefiles) are completely defined in standard Ruby syntax.\n No XML files to edit. No quirky Makefile syntax to worry about (is that a tab or a space?)\n * Users can specify tasks with prerequisites.\n * Rake supports rule patterns to synthesize implicit tasks.\n * Flexible FileLists that act like arrays but know about manipulating file names and paths.\n * Supports parallel execution of tasks.\n".freeze + s.email = ["hsbt@ruby-lang.org".freeze, "drbrain@segment7.net".freeze, "".freeze] + s.executables = ["rake".freeze] + s.files = ["exe/rake".freeze] + s.homepage = "https://github.com/ruby/rake".freeze + s.licenses = ["MIT".freeze] + s.rdoc_options = ["--main".freeze, "README.rdoc".freeze] + s.required_ruby_version = Gem::Requirement.new(">= 2.2".freeze) + s.rubygems_version = "3.2.22".freeze + s.summary = "Rake is a Make-like program implemented in Ruby".freeze + + s.installed_by_version = "3.2.22" if s.respond_to? :installed_by_version +end diff --git a/pkg/dependency/parser/ruby/gemspec/testdata/normal01.gemspec b/pkg/dependency/parser/ruby/gemspec/testdata/normal01.gemspec new file mode 100644 index 000000000000..ae964c425713 --- /dev/null +++ b/pkg/dependency/parser/ruby/gemspec/testdata/normal01.gemspec @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- +# ... REDACTED ... + +Gem::Specification.new do |spec| + spec.name = "async".freeze # comment + spec.version = "1.25.0" + spec.licenses = "MIT" # invalid + + # ... REDACTED ... +end diff --git a/pkg/dependency/parser/rust/binary/parse.go b/pkg/dependency/parser/rust/binary/parse.go new file mode 100644 index 000000000000..5ddb2cf20be6 --- /dev/null +++ b/pkg/dependency/parser/rust/binary/parse.go @@ -0,0 +1,80 @@ +// Detects dependencies from Rust binaries built with https://github.com/rust-secure-code/cargo-auditable +package binary + +import ( + rustaudit "github.com/microsoft/go-rustaudit" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +var ( + ErrUnrecognizedExe = xerrors.New("unrecognized executable format") + ErrNonRustBinary = xerrors.New("non Rust auditable binary") +) + +// convertError detects rustaudit.ErrUnknownFileFormat and convert to +// ErrUnrecognizedExe and convert rustaudit.ErrNoRustDepInfo to ErrNonRustBinary +func convertError(err error) error { + if err == rustaudit.ErrUnknownFileFormat { + return ErrUnrecognizedExe + } + if err == rustaudit.ErrNoRustDepInfo { + return ErrNonRustBinary + } + + return err +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +// Parse scans files to try to report Rust crates and version injected into Rust binaries +// via https://github.com/rust-secure-code/cargo-auditable +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + info, err := rustaudit.GetDependencyInfo(r) + if err != nil { + return nil, nil, convertError(err) + } + + var libs []types.Library + var deps []types.Dependency + for _, pkg := range info.Packages { + if pkg.Kind != rustaudit.Runtime { + continue + } + pkgID := packageID(pkg.Name, pkg.Version) + libs = append(libs, types.Library{ + ID: pkgID, + Name: pkg.Name, + Version: pkg.Version, + Indirect: !pkg.Root, + }) + + var childDeps []string + for _, dep_idx := range pkg.Dependencies { + dep := info.Packages[dep_idx] + if dep.Kind == rustaudit.Runtime { + childDeps = append(childDeps, packageID(dep.Name, dep.Version)) + } + } + if len(childDeps) > 0 { + deps = append(deps, types.Dependency{ + ID: pkgID, + DependsOn: childDeps, + }) + } + } + + return libs, deps, nil +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.RustBinary, name, version) +} diff --git a/pkg/dependency/parser/rust/binary/parse_test.go b/pkg/dependency/parser/rust/binary/parse_test.go new file mode 100644 index 000000000000..63b0a3a705d1 --- /dev/null +++ b/pkg/dependency/parser/rust/binary/parse_test.go @@ -0,0 +1,90 @@ +package binary_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/rust/binary" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +// Test binaries generated from cargo-auditable test fixture +// https://github.com/rust-secure-code/cargo-auditable/tree/6b77151/cargo-auditable/tests/fixtures/workspace +var ( + libs = []types.Library{ + { + ID: "crate_with_features@0.1.0", + Name: "crate_with_features", + Version: "0.1.0", + Indirect: false, + }, + { + ID: "library_crate@0.1.0", + Name: "library_crate", + Version: "0.1.0", + Indirect: true, + }, + } + + deps = []types.Dependency{ + { + ID: "crate_with_features@0.1.0", + DependsOn: []string{"library_crate@0.1.0"}, + }, + } +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + inputFile string + want []types.Library + wantDeps []types.Dependency + wantErr string + }{ + { + name: "ELF", + inputFile: "testdata/test.elf", + want: libs, + wantDeps: deps, + }, + { + name: "PE", + inputFile: "testdata/test.exe", + want: libs, + wantDeps: deps, + }, + { + name: "Mach-O", + inputFile: "testdata/test.macho", + want: libs, + wantDeps: deps, + }, + { + name: "sad path", + inputFile: "testdata/dummy", + wantErr: "unrecognized executable format", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + got, gotDeps, err := binary.NewParser().Parse(f) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + assert.Equal(t, tt.wantDeps, gotDeps) + }) + } +} diff --git a/pkg/dependency/parser/rust/binary/testdata/dummy b/pkg/dependency/parser/rust/binary/testdata/dummy new file mode 100644 index 000000000000..26bf640459d3 --- /dev/null +++ b/pkg/dependency/parser/rust/binary/testdata/dummy @@ -0,0 +1 @@ +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA \ No newline at end of file diff --git a/pkg/dependency/parser/rust/binary/testdata/test.elf b/pkg/dependency/parser/rust/binary/testdata/test.elf new file mode 100644 index 000000000000..cadfee87b008 Binary files /dev/null and b/pkg/dependency/parser/rust/binary/testdata/test.elf differ diff --git a/pkg/dependency/parser/rust/binary/testdata/test.exe b/pkg/dependency/parser/rust/binary/testdata/test.exe new file mode 100644 index 000000000000..6a0019bd40a0 Binary files /dev/null and b/pkg/dependency/parser/rust/binary/testdata/test.exe differ diff --git a/pkg/dependency/parser/rust/binary/testdata/test.macho b/pkg/dependency/parser/rust/binary/testdata/test.macho new file mode 100644 index 000000000000..09271be2a41e Binary files /dev/null and b/pkg/dependency/parser/rust/binary/testdata/test.macho differ diff --git a/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go b/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go new file mode 100644 index 000000000000..775dae13477f --- /dev/null +++ b/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go @@ -0,0 +1,72 @@ +package cargo + +import ( + "bufio" + "fmt" + "io" + "strings" +) + +type pkgPosition struct { + start int + end int +} +type minPkg struct { + name string + version string + position pkgPosition +} + +func (pkg *minPkg) setEndPositionIfEmpty(n int) { + if pkg.position.end == 0 { + pkg.position.end = n + } +} + +type naivePkgParser struct { + r io.Reader +} + +func (parser *naivePkgParser) parse() map[string]pkgPosition { + var currentPkg minPkg = minPkg{} + var idx = make(map[string]pkgPosition, 0) + + scanner := bufio.NewScanner(parser.r) + lineNum := 1 + for scanner.Scan() { + line := scanner.Text() + switch { + case strings.HasPrefix(strings.TrimSpace(line), "["): + if currentPkg.name != "" { + pkgId := packageID(currentPkg.name, currentPkg.version) + currentPkg.setEndPositionIfEmpty(lineNum - 1) + idx[pkgId] = currentPkg.position + } + currentPkg = minPkg{} + currentPkg.position.start = lineNum + + case strings.HasPrefix(strings.TrimSpace(line), "name ="): + currentPkg.name = propertyValue(line) + case strings.HasPrefix(strings.TrimSpace(line), "version ="): + currentPkg.version = propertyValue(line) + case strings.TrimSpace(line) == "": + currentPkg.setEndPositionIfEmpty(lineNum - 1) + } + + lineNum++ + } + // add last item + if currentPkg.name != "" { + pkgId := fmt.Sprintf("%s@%s", currentPkg.name, currentPkg.version) + currentPkg.setEndPositionIfEmpty(lineNum - 1) + idx[pkgId] = currentPkg.position + } + return idx +} +func propertyValue(line string) string { + parts := strings.Split(line, "=") + if len(parts) == 2 { + return strings.Trim(parts[1], ` "`) + } + return "" +} diff --git a/pkg/dependency/parser/rust/cargo/parse.go b/pkg/dependency/parser/rust/cargo/parse.go new file mode 100644 index 000000000000..282e25152d04 --- /dev/null +++ b/pkg/dependency/parser/rust/cargo/parse.go @@ -0,0 +1,128 @@ +package cargo + +import ( + "io" + "sort" + "strings" + + "github.com/BurntSushi/toml" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type cargoPkg struct { + Name string `toml:"name"` + Version string `toml:"version"` + Source string `toml:"source,omitempty"` + Dependencies []string `toml:"dependencies,omitempty"` +} +type Lockfile struct { + Packages []cargoPkg `toml:"package"` +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var lockfile Lockfile + decoder := toml.NewDecoder(r) + if _, err := decoder.Decode(&lockfile); err != nil { + return nil, nil, xerrors.Errorf("decode error: %w", err) + } + + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, nil, xerrors.Errorf("seek error: %w", err) + } + + // naive parser to get line numbers by package from lock file + pkgParser := naivePkgParser{r: r} + lineNumIdx := pkgParser.parse() + + // We need to get version for unique dependencies for lockfile v3 from lockfile.Packages + pkgs := lo.SliceToMap(lockfile.Packages, func(pkg cargoPkg) (string, cargoPkg) { + return pkg.Name, pkg + }) + + var libs []types.Library + var deps []types.Dependency + for _, pkg := range lockfile.Packages { + pkgID := packageID(pkg.Name, pkg.Version) + lib := types.Library{ + ID: pkgID, + Name: pkg.Name, + Version: pkg.Version, + } + if pos, ok := lineNumIdx[pkgID]; ok { + lib.Locations = []types.Location{ + { + StartLine: pos.start, + EndLine: pos.end, + }, + } + } + + libs = append(libs, lib) + dep := parseDependencies(pkgID, pkg, pkgs) + if dep != nil { + deps = append(deps, *dep) + } + } + sort.Sort(types.Libraries(libs)) + sort.Sort(types.Dependencies(deps)) + return libs, deps, nil +} +func parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]cargoPkg) *types.Dependency { + var dependOn []string + + for _, pkgDep := range pkg.Dependencies { + /* + Dependency entries look like: + old Cargo.lock - https://github.com/rust-lang/cargo/blob/46bac2dc448ab12fe0f182bee8d35cc804d9a6af/tests/testsuite/lockfile_compat.rs#L48-L50 + "unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" + new Cargo.lock -https://github.com/rust-lang/cargo/blob/46bac2dc448ab12fe0f182bee8d35cc804d9a6af/tests/testsuite/lockfile_compat.rs#L39-L41 + "unsafe-any" - if lock file contains only 1 version of dependency + "unsafe-any 0.4.2" if lock file contains more than 1 version of dependency + */ + fields := strings.Fields(pkgDep) + switch len(fields) { + // unique dependency in new lock file + case 1: + name := fields[0] + version, ok := pkgs[name] + if !ok { + log.Logger.Debugf("can't find version for %s", name) + continue + } + dependOn = append(dependOn, packageID(name, version.Version)) + // 2: non-unique dependency in new lock file + // 3: old lock file + case 2, 3: + dependOn = append(dependOn, packageID(fields[0], fields[1])) + default: + log.Logger.Debugf("wrong dependency format for %s", pkgDep) + continue + } + } + if len(dependOn) > 0 { + sort.Strings(dependOn) + return &types.Dependency{ + ID: pkgId, + DependsOn: dependOn, + } + } else { + return nil + } +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.Cargo, name, version) +} diff --git a/pkg/dependency/parser/rust/cargo/parse_test.go b/pkg/dependency/parser/rust/cargo/parse_test.go new file mode 100644 index 000000000000..903cf0190a7d --- /dev/null +++ b/pkg/dependency/parser/rust/cargo/parse_test.go @@ -0,0 +1,152 @@ +package cargo + +import ( + "fmt" + "os" + "path" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +var ( + cargoNormalLibs = []types.Library{ + {ID: "normal@0.1.0", Name: "normal", Version: "0.1.0", Locations: []types.Location{{StartLine: 8, EndLine: 13}}}, + {ID: "libc@0.2.54", Name: "libc", Version: "0.2.54", Locations: []types.Location{{StartLine: 3, EndLine: 6}}}, + {ID: "typemap@0.3.3", Name: "typemap", Version: "0.3.3", Locations: []types.Location{{StartLine: 20, EndLine: 26}}}, + {ID: "url@1.7.2", Name: "url", Version: "1.7.2", Locations: []types.Location{{StartLine: 43, EndLine: 51}}}, + {ID: "unsafe-any@0.4.2", Name: "unsafe-any", Version: "0.4.2", Locations: []types.Location{{StartLine: 15, EndLine: 18}}}, + {ID: "matches@0.1.8", Name: "matches", Version: "0.1.8", Locations: []types.Location{{StartLine: 33, EndLine: 36}}}, + {ID: "idna@0.1.5", Name: "idna", Version: "0.1.5", Locations: []types.Location{{StartLine: 28, EndLine: 31}}}, + {ID: "percent-encoding@1.0.1", Name: "percent-encoding", Version: "1.0.1", Locations: []types.Location{{StartLine: 38, EndLine: 41}}}, + } + cargoNormalDeps = []types.Dependency{ + { + ID: "normal@0.1.0", + DependsOn: []string{"libc@0.2.54"}}, + { + ID: "typemap@0.3.3", + DependsOn: []string{"unsafe-any@0.4.2"}, + }, + { + ID: "url@1.7.2", + DependsOn: []string{"idna@0.1.5", "matches@0.1.8", "percent-encoding@1.0.1"}, + }, + } + cargoMixedLibs = []types.Library{ + {ID: "normal@0.1.0", Name: "normal", Version: "0.1.0", Locations: []types.Location{{StartLine: 17, EndLine: 22}}}, + {ID: "libc@0.2.54", Name: "libc", Version: "0.2.54", Locations: []types.Location{{StartLine: 3, EndLine: 6}}}, + {ID: "typemap@0.3.3", Name: "typemap", Version: "0.3.3", Locations: []types.Location{{StartLine: 55, EndLine: 61}}}, + {ID: "url@1.7.2", Name: "url", Version: "1.7.2", Locations: []types.Location{{StartLine: 26, EndLine: 34}}}, + {ID: "unsafe-any@0.4.2", Name: "unsafe-any", Version: "0.4.2", Locations: []types.Location{{StartLine: 9, EndLine: 12}}}, + {ID: "matches@0.1.8", Name: "matches", Version: "0.1.8", Locations: []types.Location{{StartLine: 41, EndLine: 44}}}, + {ID: "idna@0.1.5", Name: "idna", Version: "0.1.5", Locations: []types.Location{{StartLine: 36, EndLine: 39}}}, + {ID: "percent-encoding@1.0.1", Name: "percent-encoding", Version: "1.0.1", Locations: []types.Location{{StartLine: 46, EndLine: 49}}}, + } + + cargoV3Libs = []types.Library{ + {ID: "aho-corasick@0.7.20", Name: "aho-corasick", Version: "0.7.20", Locations: []types.Location{{StartLine: 5, EndLine: 12}}}, + {ID: "app@0.1.0", Name: "app", Version: "0.1.0", Locations: []types.Location{{StartLine: 14, EndLine: 21}}}, + {ID: "libc@0.2.140", Name: "libc", Version: "0.2.140", Locations: []types.Location{{StartLine: 23, EndLine: 27}}}, + {ID: "memchr@1.0.2", Name: "memchr", Version: "1.0.2", Locations: []types.Location{{StartLine: 29, EndLine: 36}}}, + {ID: "memchr@2.5.0", Name: "memchr", Version: "2.5.0", Locations: []types.Location{{StartLine: 38, EndLine: 42}}}, + {ID: "regex@1.7.3", Name: "regex", Version: "1.7.3", Locations: []types.Location{{StartLine: 44, EndLine: 53}}}, + {ID: "regex-syntax@0.5.6", Name: "regex-syntax", Version: "0.5.6", Locations: []types.Location{{StartLine: 55, EndLine: 62}}}, + {ID: "regex-syntax@0.6.29", Name: "regex-syntax", Version: "0.6.29", Locations: []types.Location{{StartLine: 64, EndLine: 68}}}, + {ID: "ucd-util@0.1.10", Name: "ucd-util", Version: "0.1.10", Locations: []types.Location{{StartLine: 70, EndLine: 74}}}, + } + + cargoV3Deps = []types.Dependency{ + { + ID: "aho-corasick@0.7.20", + DependsOn: []string{"memchr@2.5.0"}}, + { + ID: "app@0.1.0", + DependsOn: []string{"memchr@1.0.2", "regex-syntax@0.5.6", "regex@1.7.3"}, + }, + { + ID: "memchr@1.0.2", + DependsOn: []string{"libc@0.2.140"}, + }, + { + ID: "regex@1.7.3", + DependsOn: []string{"aho-corasick@0.7.20", "memchr@2.5.0", "regex-syntax@0.6.29"}, + }, + { + ID: "regex-syntax@0.5.6", + DependsOn: []string{"ucd-util@0.1.10"}, + }, + } +) + +func TestParse(t *testing.T) { + vectors := []struct { + file string // Test input file + wantLibs []types.Library + wantDeps []types.Dependency + wantErr assert.ErrorAssertionFunc + }{ + { + file: "testdata/cargo_normal.lock", + wantLibs: cargoNormalLibs, + wantDeps: cargoNormalDeps, + wantErr: assert.NoError, + }, + { + file: "testdata/cargo_mixed.lock", + wantLibs: cargoMixedLibs, + wantDeps: cargoNormalDeps, + wantErr: assert.NoError, + }, + { + file: "testdata/cargo_v3.lock", + wantLibs: cargoV3Libs, + wantDeps: cargoV3Deps, + wantErr: assert.NoError, + }, + { + file: "testdata/cargo_invalid.lock", + wantErr: assert.Error, + }, + } + + for _, v := range vectors { + t.Run(path.Base(v.file), func(t *testing.T) { + f, err := os.Open(v.file) + require.NoError(t, err) + + gotLibs, gotDeps, err := NewParser().Parse(f) + + if !v.wantErr(t, err, fmt.Sprintf("Parse(%v)", v.file)) { + return + } + + if err != nil { + return + } + + sortLibs(v.wantLibs) + sortDeps(v.wantDeps) + + assert.Equalf(t, v.wantLibs, gotLibs, "Parse libraries(%v)", v.file) + assert.Equalf(t, v.wantDeps, gotDeps, "Parse dependencies(%v)", v.file) + }) + } +} + +func sortLibs(libs []types.Library) { + sort.Slice(libs, func(i, j int) bool { + return strings.Compare(libs[i].ID, libs[j].ID) < 0 + }) +} + +func sortDeps(deps []types.Dependency) { + sort.Slice(deps, func(i, j int) bool { + return strings.Compare(deps[i].ID, deps[j].ID) < 0 + }) +} diff --git a/pkg/dependency/parser/rust/cargo/testdata/cargo_invalid.lock b/pkg/dependency/parser/rust/cargo/testdata/cargo_invalid.lock new file mode 100644 index 000000000000..a0690b395f18 --- /dev/null +++ b/pkg/dependency/parser/rust/cargo/testdata/cargo_invalid.lock @@ -0,0 +1,34 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "libc" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "normal" +version = +dependencies = [ + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c6785aa7dd976f5fbf3b71cfd9cd49d7f783c1ff565a858d71031c6c313aa5c6" diff --git a/pkg/dependency/parser/rust/cargo/testdata/cargo_mixed.lock b/pkg/dependency/parser/rust/cargo/testdata/cargo_mixed.lock new file mode 100644 index 000000000000..2c0cc23176d1 --- /dev/null +++ b/pkg/dependency/parser/rust/cargo/testdata/cargo_mixed.lock @@ -0,0 +1,61 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "libc" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + + +[[package]] +name = "unsafe-any" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + + + + +[[package]] +name = "normal" +version = "0.1.0" +dependencies = [ + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", +] + +# Extra comments + +[[package]] +name = "url" +version = """1.7.2""" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + + +[metadata] +"checksum libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c6785aa7dd976f5fbf3b71cfd9cd49d7f783c1ff565a858d71031c6c313aa5c6" + +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] diff --git a/pkg/dependency/parser/rust/cargo/testdata/cargo_normal.lock b/pkg/dependency/parser/rust/cargo/testdata/cargo_normal.lock new file mode 100644 index 000000000000..76ecc0abe5bc --- /dev/null +++ b/pkg/dependency/parser/rust/cargo/testdata/cargo_normal.lock @@ -0,0 +1,54 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "libc" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "normal" +version = "0.1.0" +dependencies = [ + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unsafe-any" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c6785aa7dd976f5fbf3b71cfd9cd49d7f783c1ff565a858d71031c6c313aa5c6" diff --git a/pkg/dependency/parser/rust/cargo/testdata/cargo_v3.lock b/pkg/dependency/parser/rust/cargo/testdata/cargo_v3.lock new file mode 100644 index 000000000000..a366dbdea056 --- /dev/null +++ b/pkg/dependency/parser/rust/cargo/testdata/cargo_v3.lock @@ -0,0 +1,74 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr 2.5.0", +] + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "memchr 1.0.2", + "regex", + "regex-syntax 0.5.6", +] + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr 2.5.0", + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-syntax" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +dependencies = [ + "ucd-util", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "ucd-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" \ No newline at end of file diff --git a/pkg/dependency/parser/swift/cocoapods/parse.go b/pkg/dependency/parser/swift/cocoapods/parse.go new file mode 100644 index 000000000000..7b2a580fd74c --- /dev/null +++ b/pkg/dependency/parser/swift/cocoapods/parse.go @@ -0,0 +1,114 @@ +package cocoapods + +import ( + "sort" + "strings" + + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +type lockFile struct { + Pods []any `yaml:"PODS"` // pod can be string or map[string]interface{} +} + +func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + lock := &lockFile{} + decoder := yaml.NewDecoder(r) + if err := decoder.Decode(&lock); err != nil { + return nil, nil, xerrors.Errorf("failed to decode cocoapods lock file: %s", err.Error()) + } + + parsedDeps := make(map[string]types.Library) // dependency name => Library + directDeps := make(map[string][]string) // dependency name => slice of child dependency names + for _, pod := range lock.Pods { + switch p := pod.(type) { + case string: // dependency with version number + lib, err := parseDep(p) + if err != nil { + log.Logger.Debug(err) + continue + } + parsedDeps[lib.Name] = lib + case map[string]interface{}: // dependency with its child dependencies + for dep, childDeps := range p { + lib, err := parseDep(dep) + if err != nil { + log.Logger.Debug(err) + continue + } + parsedDeps[lib.Name] = lib + + children, ok := childDeps.([]interface{}) + if !ok { + return nil, nil, xerrors.Errorf("invalid value of cocoapods direct dependency: %q", childDeps) + } + + for _, childDep := range children { + s, ok := childDep.(string) + if !ok { + return nil, nil, xerrors.Errorf("must be string: %q", childDep) + } + directDeps[lib.Name] = append(directDeps[lib.Name], strings.Fields(s)[0]) + } + } + } + } + + var deps []types.Dependency + for dep, childDeps := range directDeps { + var dependsOn []string + // find versions for child dependencies + for _, childDep := range childDeps { + dependsOn = append(dependsOn, packageID(childDep, parsedDeps[childDep].Version)) + } + deps = append(deps, types.Dependency{ + ID: parsedDeps[dep].ID, + DependsOn: dependsOn, + }) + } + + sort.Sort(types.Dependencies(deps)) + return utils.UniqueLibraries(maps.Values(parsedDeps)), deps, nil +} + +func parseDep(dep string) (types.Library, error) { + // dep example: + // 'AppCenter (4.2.0)' + // direct dep examples: + // 'AppCenter/Core' + // 'AppCenter/Analytics (= 4.2.0)' + // 'AppCenter/Analytics (-> 4.2.0)' + ss := strings.Split(dep, " (") + if len(ss) != 2 { + return types.Library{}, xerrors.Errorf("Unable to determine cocoapods dependency: %q", dep) + } + + name := ss[0] + version := strings.Trim(strings.TrimSpace(ss[1]), "()") + lib := types.Library{ + ID: packageID(name, version), + Name: name, + Version: version, + } + + return lib, nil +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.Cocoapods, name, version) +} diff --git a/pkg/dependency/parser/swift/cocoapods/parse_test.go b/pkg/dependency/parser/swift/cocoapods/parse_test.go new file mode 100644 index 000000000000..3a0823338713 --- /dev/null +++ b/pkg/dependency/parser/swift/cocoapods/parse_test.go @@ -0,0 +1,95 @@ +package cocoapods_test + +import ( + "os" + "testing" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/swift/cocoapods" + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + inputFile string // Test input file + wantLibs []types.Library + wantDeps []types.Dependency + }{ + { + name: "happy path", + inputFile: "testdata/happy.lock", + wantLibs: []types.Library{ + { + ID: "AppCenter/Analytics@4.2.0", + Name: "AppCenter/Analytics", + Version: "4.2.0", + }, + { + ID: "AppCenter/Core@4.2.0", + Name: "AppCenter/Core", + Version: "4.2.0", + }, + { + ID: "AppCenter/Crashes@4.2.0", + Name: "AppCenter/Crashes", + Version: "4.2.0", + }, + { + ID: "AppCenter@4.2.0", + Name: "AppCenter", + Version: "4.2.0", + }, + { + ID: "KeychainAccess@4.2.1", + Name: "KeychainAccess", + Version: "4.2.1", + }, + }, + wantDeps: []types.Dependency{ + { + ID: "AppCenter/Analytics@4.2.0", + DependsOn: []string{ + "AppCenter/Core@4.2.0", + }, + }, + { + ID: "AppCenter/Crashes@4.2.0", + DependsOn: []string{ + "AppCenter/Core@4.2.0", + }, + }, + { + ID: "AppCenter@4.2.0", + DependsOn: []string{ + "AppCenter/Analytics@4.2.0", + "AppCenter/Crashes@4.2.0", + }, + }, + }, + }, + { + name: "happy path. lock file without dependencies", + inputFile: "testdata/empty.lock", + }, + { + name: "sad path. wrong dep format", + inputFile: "testdata/sad.lock", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + gotLibs, gotDeps, err := cocoapods.NewParser().Parse(f) + require.NoError(t, err) + + assert.Equal(t, tt.wantLibs, gotLibs) + assert.Equal(t, tt.wantDeps, gotDeps) + }) + } +} diff --git a/pkg/dependency/parser/swift/cocoapods/testdata/empty.lock b/pkg/dependency/parser/swift/cocoapods/testdata/empty.lock new file mode 100644 index 000000000000..142864b62d23 --- /dev/null +++ b/pkg/dependency/parser/swift/cocoapods/testdata/empty.lock @@ -0,0 +1 @@ +COCOAPODS: 1.11.2 \ No newline at end of file diff --git a/pkg/dependency/parser/swift/cocoapods/testdata/happy.lock b/pkg/dependency/parser/swift/cocoapods/testdata/happy.lock new file mode 100644 index 000000000000..65600c35476c --- /dev/null +++ b/pkg/dependency/parser/swift/cocoapods/testdata/happy.lock @@ -0,0 +1,12 @@ +PODS: + - AppCenter (4.2.0): + - AppCenter/Analytics (= 4.2.0) + - AppCenter/Crashes (= 4.2.0) + - AppCenter/Analytics (4.2.0): + - AppCenter/Core + - AppCenter/Core (4.2.0) + - AppCenter/Crashes (4.2.0): + - AppCenter/Core + - KeychainAccess (4.2.1) + +COCOAPODS: 1.11.2 \ No newline at end of file diff --git a/pkg/dependency/parser/swift/cocoapods/testdata/sad.lock b/pkg/dependency/parser/swift/cocoapods/testdata/sad.lock new file mode 100644 index 000000000000..b0be368257b0 --- /dev/null +++ b/pkg/dependency/parser/swift/cocoapods/testdata/sad.lock @@ -0,0 +1,4 @@ +PODS: + - AppCenter = 4.2.0 + +COCOAPODS: 1.11.2 \ No newline at end of file diff --git a/pkg/dependency/parser/swift/swift/parse.go b/pkg/dependency/parser/swift/swift/parse.go new file mode 100644 index 000000000000..daeb8d3ef243 --- /dev/null +++ b/pkg/dependency/parser/swift/swift/parse.go @@ -0,0 +1,94 @@ +package swift + +import ( + "io" + "sort" + "strings" + + "github.com/liamg/jfather" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +// Parser is a parser for Package.resolved files +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var lockFile LockFile + input, err := io.ReadAll(r) + if err != nil { + return nil, nil, xerrors.Errorf("read error: %w", err) + } + if err := jfather.Unmarshal(input, &lockFile); err != nil { + return nil, nil, xerrors.Errorf("decode error: %w", err) + } + + var libs types.Libraries + pins := lockFile.Object.Pins + if lockFile.Version > 1 { + pins = lockFile.Pins + } + for _, pin := range pins { + name := libraryName(pin, lockFile.Version) + + // Skip packages for which we cannot resolve the version + if pin.State.Version == "" && pin.State.Branch == "" { + log.Logger.Warnf("Unable to resolve %q. Both the version and branch fields are empty.", name) + continue + } + + // A Pin can be resolved using `branch` without `version`. + // e.g. https://github.com/element-hq/element-ios/blob/6a9bcc88ea37147efba8f0a7bcf3ec187f4a4011/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved#L84-L92 + version := lo.Ternary(pin.State.Version != "", pin.State.Version, pin.State.Branch) + + libs = append(libs, types.Library{ + ID: dependency.ID(ftypes.Swift, name, version), + Name: name, + Version: version, + Locations: []types.Location{ + { + StartLine: pin.StartLine, + EndLine: pin.EndLine, + }, + }, + }) + } + sort.Sort(libs) + return libs, nil, nil +} + +func libraryName(pin Pin, lockVersion int) string { + // Package.resolved v1 uses `RepositoryURL` + // v2 uses `Location` + name := pin.RepositoryURL + if lockVersion > 1 { + name = pin.Location + } + // Swift uses `https://github.com//.git format + // `.git` suffix can be omitted (take a look happy test) + // Remove `https://` and `.git` to fit the same format + name = strings.TrimPrefix(name, "https://") + name = strings.TrimSuffix(name, ".git") + return name +} + +// UnmarshalJSONWithMetadata needed to detect start and end lines of deps for v1 +func (p *Pin) UnmarshalJSONWithMetadata(node jfather.Node) error { + if err := node.Decode(&p); err != nil { + return err + } + // Decode func will overwrite line numbers if we save them first + p.StartLine = node.Range().Start.Line + p.EndLine = node.Range().End.Line + return nil +} diff --git a/pkg/dependency/parser/swift/swift/parse_test.go b/pkg/dependency/parser/swift/swift/parse_test.go new file mode 100644 index 000000000000..b1d3d127a85a --- /dev/null +++ b/pkg/dependency/parser/swift/swift/parse_test.go @@ -0,0 +1,100 @@ +package swift + +import ( + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + inputFile string + want []types.Library + }{ + // docker run -it --rm swift@sha256:3c62ac97506ecf19ca15e4db57d7930e6a71559b23b19aa57e13d380133a54db + // mkdir app && cd app + // swift package init + // ## add new deps: ## + // sed -i 's/"1.0.0")/"1.0.0")\n.package(url: "https:\/\/github.com\/ReactiveCocoa\/ReactiveSwift", from: "7.0.0"),\n.package(url: "https:\/\/github.com\/Quick\/Nimble", .exact("9.2.1"))/' Package.swift + // swift package update + { + name: "happy path v1", + inputFile: "testdata/happy-v1-Package.resolved", + want: []types.Library{ + { + ID: "github.com/Quick/Nimble@9.2.1", + Name: "github.com/Quick/Nimble", + Version: "9.2.1", + Locations: []types.Location{{StartLine: 4, EndLine: 12}}, + }, + { + ID: "github.com/ReactiveCocoa/ReactiveSwift@7.1.1", + Name: "github.com/ReactiveCocoa/ReactiveSwift", + Version: "7.1.1", + Locations: []types.Location{{StartLine: 13, EndLine: 21}}, + }, + }, + }, + { + name: "happy path v2", + inputFile: "testdata/happy-v2-Package.resolved", + want: []types.Library{ + { + ID: "github.com/Quick/Nimble@9.2.1", + Name: "github.com/Quick/Nimble", + Version: "9.2.1", + Locations: []types.Location{{StartLine: 21, EndLine: 29}}, + }, + { + ID: "github.com/Quick/Quick@7.2.0", + Name: "github.com/Quick/Quick", + Version: "7.2.0", + Locations: []types.Location{{StartLine: 30, EndLine: 38}}, + }, + { + ID: "github.com/ReactiveCocoa/ReactiveSwift@7.1.1", + Name: "github.com/ReactiveCocoa/ReactiveSwift", + Version: "7.1.1", + Locations: []types.Location{{StartLine: 39, EndLine: 47}}, + }, + { + ID: "github.com/element-hq/swift-ogg@0.0.1", + Name: "github.com/element-hq/swift-ogg", + Version: "0.0.1", + Locations: []types.Location{{StartLine: 48, EndLine: 56}}, + }, + { + ID: "github.com/mattgallagher/CwlCatchException@2.1.2", + Name: "github.com/mattgallagher/CwlCatchException", + Version: "2.1.2", + Locations: []types.Location{{StartLine: 3, EndLine: 11}}, + }, + { + ID: "github.com/mattgallagher/CwlPreconditionTesting@2.1.2", + Name: "github.com/mattgallagher/CwlPreconditionTesting", + Version: "2.1.2", + Locations: []types.Location{{StartLine: 12, EndLine: 20}}, + }, + }, + }, + { + name: "empty", + inputFile: "testdata/empty-Package.resolved", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parser := NewParser() + f, err := os.Open(tt.inputFile) + assert.NoError(t, err) + + libs, _, err := parser.Parse(f) + assert.NoError(t, err) + assert.Equal(t, tt.want, libs) + }) + } +} diff --git a/pkg/dependency/parser/swift/swift/testdata/empty-Package.resolved b/pkg/dependency/parser/swift/swift/testdata/empty-Package.resolved new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/pkg/dependency/parser/swift/swift/testdata/empty-Package.resolved @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pkg/dependency/parser/swift/swift/testdata/happy-v1-Package.resolved b/pkg/dependency/parser/swift/swift/testdata/happy-v1-Package.resolved new file mode 100644 index 000000000000..cb15135d4208 --- /dev/null +++ b/pkg/dependency/parser/swift/swift/testdata/happy-v1-Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "Nimble", + "repositoryURL": "https://github.com/Quick/Nimble.git", + "state": { + "branch": null, + "revision": "c93f16c25af5770f0d3e6af27c9634640946b068", + "version": "9.2.1" + } + }, + { + "package": "ReactiveSwift", + "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift", + "state": { + "branch": null, + "revision": "40c465af19b993344e84355c00669ba2022ca3cd", + "version": "7.1.1" + } + } + ] + }, + "version": 1 +} \ No newline at end of file diff --git a/pkg/dependency/parser/swift/swift/testdata/happy-v2-Package.resolved b/pkg/dependency/parser/swift/swift/testdata/happy-v2-Package.resolved new file mode 100644 index 000000000000..d3a189b88337 --- /dev/null +++ b/pkg/dependency/parser/swift/swift/testdata/happy-v2-Package.resolved @@ -0,0 +1,59 @@ +{ + "pins" : [ + { + "identity" : "cwlcatchexception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlCatchException.git", + "state" : { + "revision" : "3b123999de19bf04905bc1dfdb76f817b0f2cc00", + "version" : "2.1.2" + } + }, + { + "identity" : "cwlpreconditiontesting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state" : { + "revision" : "a23ded2c91df9156628a6996ab4f347526f17b6b", + "version" : "2.1.2" + } + }, + { + "identity" : "nimble", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Nimble.git", + "state" : { + "revision" : "c93f16c25af5770f0d3e6af27c9634640946b068", + "version" : "9.2.1" + } + }, + { + "identity" : "quick", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Quick.git", + "state" : { + "revision" : "494eff9ad74a37047782b0d5d8d84c7ff49a60e4", + "version" : "7.2.0" + } + }, + { + "identity" : "reactiveswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveCocoa/ReactiveSwift", + "state" : { + "revision" : "40c465af19b993344e84355c00669ba2022ca3cd", + "version" : "7.1.1" + } + }, + { + "identity" : "swift-ogg", + "kind" : "remoteSourceControl", + "location" : "https://github.com/element-hq/swift-ogg", + "state" : { + "branch" : "0.0.1", + "revision" : "e9a9e7601da662fd8b97d93781ff5c60b4becf88" + } + } + ], + "version" : 2 +} diff --git a/pkg/dependency/parser/swift/swift/types.go b/pkg/dependency/parser/swift/swift/types.go new file mode 100644 index 000000000000..a196b7d6628a --- /dev/null +++ b/pkg/dependency/parser/swift/swift/types.go @@ -0,0 +1,26 @@ +package swift + +type LockFile struct { + Object Object `json:"object"` + Pins []Pin `json:"pins"` + Version int `json:"version"` +} + +type Object struct { + Pins []Pin `json:"pins"` +} + +type Pin struct { + Package string `json:"package"` + RepositoryURL string `json:"repositoryURL"` // Package.revision v1 + Location string `json:"location"` // Package.revision v2 + State State `json:"state"` + StartLine int + EndLine int +} + +type State struct { + Branch string `json:"branch"` + Revision string `json:"revision"` + Version string `json:"version"` +} diff --git a/pkg/dependency/parser/utils/utils.go b/pkg/dependency/parser/utils/utils.go new file mode 100644 index 000000000000..e89bc4ca3b65 --- /dev/null +++ b/pkg/dependency/parser/utils/utils.go @@ -0,0 +1,67 @@ +package utils + +import ( + "fmt" + "sort" + + "golang.org/x/exp/maps" + + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func UniqueStrings(ss []string) []string { + var results []string + uniq := make(map[string]struct{}) + for _, s := range ss { + if _, ok := uniq[s]; ok { + continue + } + results = append(results, s) + uniq[s] = struct{}{} + } + return results +} + +func UniqueLibraries(libs []types.Library) []types.Library { + if len(libs) == 0 { + return nil + } + unique := make(map[string]types.Library) + for _, lib := range libs { + identifier := fmt.Sprintf("%s@%s", lib.Name, lib.Version) + if l, ok := unique[identifier]; !ok { + unique[identifier] = lib + } else { + // There are times when we get 2 same libraries as root and dev dependencies. + // https://github.com/aquasecurity/trivy/issues/5532 + // In these cases, we need to mark the dependency as a root dependency. + if !lib.Dev { + l.Dev = lib.Dev + unique[identifier] = l + } + + if len(lib.Locations) > 0 { + // merge locations + l.Locations = append(l.Locations, lib.Locations...) + sort.Sort(l.Locations) + unique[identifier] = l + } + } + } + libSlice := maps.Values(unique) + sort.Sort(types.Libraries(libSlice)) + + return libSlice +} + +func MergeMaps(parent, child map[string]string) map[string]string { + if parent == nil { + return child + } + // Clone parent map to avoid shadow overwrite + newParent := maps.Clone(parent) + for k, v := range child { + newParent[k] = v + } + return newParent +} diff --git a/pkg/dependency/parser/utils/utils_test.go b/pkg/dependency/parser/utils/utils_test.go new file mode 100644 index 000000000000..ca8c8ab67568 --- /dev/null +++ b/pkg/dependency/parser/utils/utils_test.go @@ -0,0 +1,117 @@ +package utils + +import ( + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/stretchr/testify/require" + "testing" +) + +func TestUniqueLibraries(t *testing.T) { + tests := []struct { + name string + libs []types.Library + wantLibs []types.Library + }{ + { + name: "happy path merge locations", + libs: []types.Library{ + { + ID: "asn1@0.2.6", + Name: "asn1", + Version: "0.2.6", + Locations: []types.Location{ + { + StartLine: 10, + EndLine: 14, + }, + }, + }, + { + ID: "asn1@0.2.6", + Name: "asn1", + Version: "0.2.6", + Locations: []types.Location{ + { + StartLine: 24, + EndLine: 30, + }, + }, + }, + }, + wantLibs: []types.Library{ + { + ID: "asn1@0.2.6", + Name: "asn1", + Version: "0.2.6", + Locations: []types.Location{ + { + StartLine: 10, + EndLine: 14, + }, + { + StartLine: 24, + EndLine: 30, + }, + }, + }, + }, + }, + { + name: "happy path Dev and Root deps", + libs: []types.Library{ + { + ID: "asn1@0.2.6", + Name: "asn1", + Version: "0.2.6", + Dev: true, + }, + { + ID: "asn1@0.2.6", + Name: "asn1", + Version: "0.2.6", + Dev: false, + }, + }, + wantLibs: []types.Library{ + { + ID: "asn1@0.2.6", + Name: "asn1", + Version: "0.2.6", + Dev: false, + }, + }, + }, + { + name: "happy path Root and Dev deps", + libs: []types.Library{ + { + ID: "asn1@0.2.6", + Name: "asn1", + Version: "0.2.6", + Dev: false, + }, + { + ID: "asn1@0.2.6", + Name: "asn1", + Version: "0.2.6", + Dev: true, + }, + }, + wantLibs: []types.Library{ + { + ID: "asn1@0.2.6", + Name: "asn1", + Version: "0.2.6", + Dev: false, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotLibs := UniqueLibraries(tt.libs) + require.Equal(t, tt.wantLibs, gotLibs) + }) + } +} diff --git a/pkg/dependency/types/types.go b/pkg/dependency/types/types.go new file mode 100644 index 000000000000..b55d2c7df6ee --- /dev/null +++ b/pkg/dependency/types/types.go @@ -0,0 +1,74 @@ +package types + +import ( + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type Library struct { + ID string `json:",omitempty"` + Name string + Version string + Dev bool + Indirect bool `json:",omitempty"` + License string `json:",omitempty"` + ExternalReferences []ExternalRef `json:",omitempty"` + Locations Locations `json:",omitempty"` + FilePath string `json:",omitempty"` // Required to show nested jars +} + +type Libraries []Library + +func (libs Libraries) Len() int { return len(libs) } +func (libs Libraries) Less(i, j int) bool { + if libs[i].ID != libs[j].ID { // ID could be empty + return libs[i].ID < libs[j].ID + } else if libs[i].Name != libs[j].Name { // Name could be the same + return libs[i].Name < libs[j].Name + } + return libs[i].Version < libs[j].Version +} +func (libs Libraries) Swap(i, j int) { libs[i], libs[j] = libs[j], libs[i] } + +// Location in lock file +type Location struct { + StartLine int `json:",omitempty"` + EndLine int `json:",omitempty"` +} + +type Locations []Location + +func (locs Locations) Len() int { return len(locs) } +func (locs Locations) Less(i, j int) bool { + return locs[i].StartLine < locs[j].StartLine +} +func (locs Locations) Swap(i, j int) { locs[i], locs[j] = locs[j], locs[i] } + +type ExternalRef struct { + Type RefType + URL string +} + +type Dependency struct { + ID string + DependsOn []string +} + +type Dependencies []Dependency + +func (deps Dependencies) Len() int { return len(deps) } +func (deps Dependencies) Less(i, j int) bool { + return deps[i].ID < deps[j].ID +} +func (deps Dependencies) Swap(i, j int) { deps[i], deps[j] = deps[j], deps[i] } + +type Parser interface { + // Parse parses the dependency file + Parse(r xio.ReadSeekerAt) ([]Library, []Dependency, error) +} + +type RefType string + +const ( + RefVCS RefType = "vcs" + RefOther RefType = "other" +) diff --git a/pkg/detector/library/compare/bitnami/compare.go b/pkg/detector/library/compare/bitnami/compare.go new file mode 100644 index 000000000000..f3f62a7194d1 --- /dev/null +++ b/pkg/detector/library/compare/bitnami/compare.go @@ -0,0 +1,32 @@ +package bitnami + +import ( + version "github.com/bitnami/go-version/pkg/version" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" +) + +// Comparer represents a comparer for Bitnami +type Comparer struct{} + +// IsVulnerable checks if the package version is vulnerable to the advisory. +func (n Comparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool { + return compare.IsVulnerable(ver, advisory, n.matchVersion) +} + +// matchVersion checks if the package version satisfies the given constraint. +func (n Comparer) matchVersion(currentVersion, constraint string) (bool, error) { + v, err := version.Parse(currentVersion) + if err != nil { + return false, xerrors.Errorf("bitnami version error (%s): %s", currentVersion, err) + } + + c, err := version.NewConstraints(constraint) + if err != nil { + return false, xerrors.Errorf("bitnami constraint error (%s): %s", constraint, err) + } + + return c.Check(v), nil +} diff --git a/pkg/detector/library/compare/bitnami/compare_test.go b/pkg/detector/library/compare/bitnami/compare_test.go new file mode 100644 index 000000000000..fda9c5e5f0e0 --- /dev/null +++ b/pkg/detector/library/compare/bitnami/compare_test.go @@ -0,0 +1,141 @@ +package bitnami_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/bitnami" +) + +func TestBitnamiComparer_IsVulnerable(t *testing.T) { + type args struct { + currentVersion string + advisory types.Advisory + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "not vulnerable", + args: args{ + currentVersion: "1.2.3", + advisory: types.Advisory{ + VulnerableVersions: []string{"<1.2.3"}, + }, + }, + want: false, + }, + { + name: "vulnerable", + args: args{ + currentVersion: "1.2.3", + advisory: types.Advisory{ + VulnerableVersions: []string{"<=1.2.3"}, + }, + }, + want: true, + }, + { + name: "patched", + args: args{ + currentVersion: "1.2.3", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.2.3"}, + }, + }, + want: false, + }, + { + name: "unaffected", + args: args{ + currentVersion: "1.2.3", + advisory: types.Advisory{ + UnaffectedVersions: []string{"=1.2.3"}, + }, + }, + want: false, + }, + { + name: "vulnerable based on patched & unaffected versions", + args: args{ + currentVersion: "1.2.3", + advisory: types.Advisory{ + UnaffectedVersions: []string{"=1.2.0"}, + PatchedVersions: []string{">=1.2.4"}, + }, + }, + want: true, + }, + { + name: "patched with revision on current version", + args: args{ + currentVersion: "1.2.3-1", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.2.3"}, + }, + }, + want: false, + }, + { + name: "vulnerable with revision on current version", + args: args{ + currentVersion: "1.2.3-1", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.2.4"}, + }, + }, + want: true, + }, + { + name: "patched with revision on patch", + args: args{ + currentVersion: "1.2.4", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.2.3-1"}, + }, + }, + want: false, + }, + { + name: "vulnerable with revision on patch", + args: args{ + currentVersion: "1.2.3", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.2.3-1"}, + }, + }, + want: true, + }, + { + name: "patched with revisions on both current and patch", + args: args{ + currentVersion: "1.2.4-2", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.2.3-1"}, + }, + }, + want: false, + }, + { + name: "vulnerable with revision on both current and patch", + args: args{ + currentVersion: "1.2.3-0", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.2.3-1"}, + }, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := bitnami.Comparer{} + got := b.IsVulnerable(tt.args.currentVersion, tt.args.advisory) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/library/compare/compare.go b/pkg/detector/library/compare/compare.go new file mode 100644 index 000000000000..60829deb8141 --- /dev/null +++ b/pkg/detector/library/compare/compare.go @@ -0,0 +1,78 @@ +package compare + +import ( + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/go-version/pkg/version" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +// Comparer is an interface for version comparison +type Comparer interface { + IsVulnerable(currentVersion string, advisory dbTypes.Advisory) bool +} + +type matchVersion func(currentVersion, constraint string) (bool, error) + +// IsVulnerable checks if the package version is vulnerable to the advisory. +func IsVulnerable(pkgVer string, advisory dbTypes.Advisory, match matchVersion) bool { + // If one of vulnerable/patched versions is empty, we should detect it anyway. + for _, v := range append(advisory.VulnerableVersions, advisory.PatchedVersions...) { + if v == "" { + return true + } + } + var matched bool + var err error + + if len(advisory.VulnerableVersions) != 0 { + matched, err = match(pkgVer, strings.Join(advisory.VulnerableVersions, " || ")) + if err != nil { + log.Logger.Warn(err) + return false + } else if !matched { + // the version is not vulnerable + return false + } + } + + secureVersions := append(advisory.PatchedVersions, advisory.UnaffectedVersions...) + if len(secureVersions) == 0 { + // the version matches vulnerable versions and patched/unaffected versions are not provided + // or all values are empty + return matched + } + + matched, err = match(pkgVer, strings.Join(secureVersions, " || ")) + if err != nil { + log.Logger.Warn(err) + return false + } + return !matched +} + +// GenericComparer represents a comparer for semver-like versioning +type GenericComparer struct{} + +// IsVulnerable checks if the package version is vulnerable to the advisory. +func (v GenericComparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool { + return IsVulnerable(ver, advisory, v.matchVersion) +} + +// matchVersion checks if the package version satisfies the given constraint. +func (v GenericComparer) matchVersion(currentVersion, constraint string) (bool, error) { + ver, err := version.Parse(currentVersion) + if err != nil { + return false, xerrors.Errorf("version error (%s): %s", currentVersion, err) + } + + c, err := version.NewConstraints(constraint) + if err != nil { + return false, xerrors.Errorf("constraint error (%s): %s", currentVersion, err) + } + + return c.Check(ver), nil +} diff --git a/pkg/detector/library/compare/compare_test.go b/pkg/detector/library/compare/compare_test.go new file mode 100644 index 000000000000..3c1502e8fdda --- /dev/null +++ b/pkg/detector/library/compare/compare_test.go @@ -0,0 +1,116 @@ +package compare_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" +) + +func TestGenericComparer_IsVulnerable(t *testing.T) { + type args struct { + ver string + advisory types.Advisory + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "happy path", + args: args{ + ver: "1.2.3", + advisory: types.Advisory{ + VulnerableVersions: []string{"<=1.0"}, + PatchedVersions: []string{">=1.1"}, + }, + }, + }, + { + name: "no patch", + args: args{ + ver: "1.2.3", + advisory: types.Advisory{ + VulnerableVersions: []string{"<=99.999.99999"}, + PatchedVersions: []string{"<0.0.0"}, + }, + }, + want: true, + }, + { + name: "pre-release", + args: args{ + ver: "1.2.2-alpha", + advisory: types.Advisory{ + VulnerableVersions: []string{"<=1.2.2"}, + PatchedVersions: []string{">=1.2.2"}, + }, + }, + want: true, + }, + { + name: "multiple constraints", + args: args{ + ver: "2.0.0", + advisory: types.Advisory{ + VulnerableVersions: []string{">=1.7.0 <1.7.16", ">=1.8.0 <1.8.8", ">=2.0.0 <2.0.8", ">=3.0.0-beta.1 <3.0.0-beta.7"}, + PatchedVersions: []string{">=3.0.0-beta.7", ">=2.0.8 <3.0.0-beta.1", ">=1.8.8 <2.0.0", ">=1.7.16 <1.8.0"}, + }, + }, + want: true, + }, + { + name: "invalid version", + args: args{ + ver: "1.2..4", + advisory: types.Advisory{ + VulnerableVersions: []string{"<1.0.0"}, + }, + }, + want: false, + }, + { + name: "improper constraint", + args: args{ + ver: "1.2.3", + advisory: types.Advisory{ + VulnerableVersions: []string{"*"}, + PatchedVersions: nil, + }, + }, + want: false, + }, + { + name: "empty patched version", + args: args{ + ver: "1.2.3", + advisory: types.Advisory{ + VulnerableVersions: []string{"<=99.999.99999"}, + PatchedVersions: []string{""}, + }, + }, + want: true, + }, + { + name: "empty vulnerable & patched version", + args: args{ + ver: "1.2.3", + advisory: types.Advisory{ + VulnerableVersions: []string{""}, + PatchedVersions: []string{""}, + }, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := compare.GenericComparer{} + got := v.IsVulnerable(tt.args.ver, tt.args.advisory) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/library/compare/maven/compare.go b/pkg/detector/library/compare/maven/compare.go new file mode 100644 index 000000000000..d36156dce956 --- /dev/null +++ b/pkg/detector/library/compare/maven/compare.go @@ -0,0 +1,32 @@ +package maven + +import ( + version "github.com/masahiro331/go-mvn-version" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" +) + +// Comparer represents a comparer for maven +type Comparer struct{} + +// IsVulnerable checks if the package version is vulnerable to the advisory. +func (n Comparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool { + return compare.IsVulnerable(ver, advisory, n.matchVersion) +} + +// matchVersion checks if the package version satisfies the given constraint. +func (n Comparer) matchVersion(currentVersion, constraint string) (bool, error) { + v, err := version.NewVersion(currentVersion) + if err != nil { + return false, xerrors.Errorf("maven version error (%s): %s", currentVersion, err) + } + + c, err := version.NewComparer(constraint) + if err != nil { + return false, xerrors.Errorf("maven constraint error (%s): %s", constraint, err) + } + + return c.Check(v), nil +} diff --git a/pkg/detector/library/compare/maven/compare_test.go b/pkg/detector/library/compare/maven/compare_test.go new file mode 100644 index 000000000000..3335ca978b30 --- /dev/null +++ b/pkg/detector/library/compare/maven/compare_test.go @@ -0,0 +1,117 @@ +package maven_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/maven" +) + +func TestComparer_IsVulnerable(t *testing.T) { + type args struct { + currentVersion string + advisory dbTypes.Advisory + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "happy path", + args: args{ + currentVersion: "1.0.0", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<=1.0"}, + PatchedVersions: []string{">=1.1"}, + }, + }, + want: true, + }, + { + name: "final release", + args: args{ + currentVersion: "1.2.3.final", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<1.2.3"}, + PatchedVersions: []string{"1.2.3"}, + }, + }, + want: false, + }, + { + name: "pre-release", + args: args{ + currentVersion: "1.2.3-a1", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<1.2.3"}, + PatchedVersions: []string{">=1.2.3"}, + }, + }, + want: true, + }, + { + name: "multiple constraints", + args: args{ + currentVersion: "2.0.0", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{">=1.7.0 <1.7.16", ">=1.8.0 <1.8.8", ">=2.0.0 <2.0.8", ">=3.0.0-beta.1 <3.0.0-beta.7"}, + PatchedVersions: []string{">=3.0.0-beta.7", ">=2.0.8 <3.0.0-beta.1", ">=1.8.8 <2.0.0", ">=1.7.16 <1.8.0"}, + }, + }, + want: true, + }, + { + name: "version requirements", + args: args{ + currentVersion: "1.2.3", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"(,1.2.3]"}, + PatchedVersions: []string{"1.2.4"}, + }, + }, + want: true, + }, + { + name: "version soft requirements happy", + args: args{ + currentVersion: "1.2.3", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"1.2.3"}, + PatchedVersions: []string{"1.2.4"}, + }, + }, + want: true, + }, + { + name: "version soft requirements", + args: args{ + currentVersion: "1.2.3", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"1.2.2"}, + PatchedVersions: []string{"1.2.4"}, + }, + }, + want: false, + }, + { + name: "invalid constraint", + args: args{ + currentVersion: "1.2.3", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{`<1.0\.0`}, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := maven.Comparer{} + got := c.IsVulnerable(tt.args.currentVersion, tt.args.advisory) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/library/compare/npm/compare.go b/pkg/detector/library/compare/npm/compare.go new file mode 100644 index 000000000000..13e14b3c19af --- /dev/null +++ b/pkg/detector/library/compare/npm/compare.go @@ -0,0 +1,32 @@ +package npm + +import ( + "golang.org/x/xerrors" + + npm "github.com/aquasecurity/go-npm-version/pkg" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" +) + +// Comparer represents a comparer for npm +type Comparer struct{} + +// IsVulnerable checks if the package version is vulnerable to the advisory. +func (n Comparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool { + return compare.IsVulnerable(ver, advisory, n.MatchVersion) +} + +// MatchVersion checks if the package version satisfies the given constraint. +func (n Comparer) MatchVersion(currentVersion, constraint string) (bool, error) { + v, err := npm.NewVersion(currentVersion) + if err != nil { + return false, xerrors.Errorf("npm version error (%s): %s", currentVersion, err) + } + + c, err := npm.NewConstraints(constraint) + if err != nil { + return false, xerrors.Errorf("npm constraint error (%s): %s", constraint, err) + } + + return c.Check(v), nil +} diff --git a/pkg/detector/library/compare/npm/compare_test.go b/pkg/detector/library/compare/npm/compare_test.go new file mode 100644 index 000000000000..1d232e051d7c --- /dev/null +++ b/pkg/detector/library/compare/npm/compare_test.go @@ -0,0 +1,138 @@ +package npm_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/npm" +) + +func TestNpmComparer_IsVulnerable(t *testing.T) { + type args struct { + currentVersion string + advisory dbTypes.Advisory + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "happy path", + args: args{ + currentVersion: "1.0.0", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<=1.0"}, + PatchedVersions: []string{">=1.1"}, + }, + }, + want: true, + }, + { + name: "no patch", + args: args{ + currentVersion: "1.2.3", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<=99.999.99999"}, + PatchedVersions: []string{"<0.0.0"}, + }, + }, + want: true, + }, + { + name: "no patch with wildcard", + args: args{ + currentVersion: "1.2.3", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"*"}, + PatchedVersions: nil, + }, + }, + want: true, + }, + { + name: "pre-release", + args: args{ + currentVersion: "1.2.3-alpha", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<=1.2.2"}, + PatchedVersions: []string{">=1.2.2"}, + }, + }, + want: false, + }, + { + name: "multiple constraints", + args: args{ + currentVersion: "2.0.0", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{">=1.7.0 <1.7.16", ">=1.8.0 <1.8.8", ">=2.0.0 <2.0.8", ">=3.0.0-beta.1 <3.0.0-beta.7"}, + PatchedVersions: []string{">=3.0.0-beta.7", ">=2.0.8 <3.0.0-beta.1", ">=1.8.8 <2.0.0", ">=1.7.16 <1.8.0"}, + }, + }, + want: true, + }, + { + name: "x", + args: args{ + currentVersion: "2.0.1", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"2.0.x", "2.1.x"}, + PatchedVersions: []string{">=2.2.x"}, + }, + }, + want: true, + }, + { + name: "exact versions", + args: args{ + currentVersion: "2.1.0-M1", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"2.1.0-M1", "2.1.0-M2"}, + PatchedVersions: []string{">=2.1.0"}, + }, + }, + want: true, + }, + { + name: "caret", + args: args{ + currentVersion: "2.0.18", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<2.0.18", "<3.0.16"}, + PatchedVersions: []string{"^2.0.18", "^3.0.16"}, + }, + }, + want: false, + }, + { + name: "invalid version", + args: args{ + currentVersion: "1.2..4", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<1.0.0"}, + }, + }, + want: false, + }, + { + name: "invalid constraint", + args: args{ + currentVersion: "1.2.4", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"!1.0.0"}, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := npm.Comparer{} + got := c.IsVulnerable(tt.args.currentVersion, tt.args.advisory) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/library/compare/pep440/compare.go b/pkg/detector/library/compare/pep440/compare.go new file mode 100644 index 000000000000..8c88518a9d2f --- /dev/null +++ b/pkg/detector/library/compare/pep440/compare.go @@ -0,0 +1,32 @@ +package pep440 + +import ( + "golang.org/x/xerrors" + + version "github.com/aquasecurity/go-pep440-version" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" +) + +// Comparer represents a comparer for PEP 440 +type Comparer struct{} + +// IsVulnerable checks if the package version is vulnerable to the advisory. +func (n Comparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool { + return compare.IsVulnerable(ver, advisory, n.matchVersion) +} + +// matchVersion checks if the package version satisfies the given constraint. +func (n Comparer) matchVersion(currentVersion, constraint string) (bool, error) { + v, err := version.Parse(currentVersion) + if err != nil { + return false, xerrors.Errorf("python version error (%s): %s", currentVersion, err) + } + + c, err := version.NewSpecifiers(constraint, version.WithPreRelease(true)) + if err != nil { + return false, xerrors.Errorf("python constraint error (%s): %s", constraint, err) + } + + return c.Check(v), nil +} diff --git a/pkg/detector/library/compare/pep440/compare_test.go b/pkg/detector/library/compare/pep440/compare_test.go new file mode 100644 index 000000000000..7ab251e417b0 --- /dev/null +++ b/pkg/detector/library/compare/pep440/compare_test.go @@ -0,0 +1,116 @@ +package pep440_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/pep440" +) + +func TestPep440Comparer_IsVulnerable(t *testing.T) { + type args struct { + currentVersion string + advisory dbTypes.Advisory + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "happy path", + args: args{ + currentVersion: "1.0.0", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<=1.0"}, + PatchedVersions: []string{">=1.1"}, + }, + }, + want: true, + }, + { + name: "no patch", + args: args{ + currentVersion: "1.2.3", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<=99.999.99999"}, + PatchedVersions: []string{"<0.0.0"}, + }, + }, + want: true, + }, + { + name: "no patch with wildcard", + args: args{ + currentVersion: "1.2.3", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"*"}, + PatchedVersions: nil, + }, + }, + want: true, + }, + { + name: "pre-release", + args: args{ + currentVersion: "1.2.3a1", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<=1.2.2"}, + PatchedVersions: []string{">=1.2.2"}, + }, + }, + want: false, + }, + { + name: "multiple constraints", + args: args{ + currentVersion: "2.0.0", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{">=1.7.0 <1.7.16", ">=1.8.0 <1.8.8", ">=2.0.0 <2.0.8", ">=3.0.0b1 <3.0.0b7"}, + PatchedVersions: []string{">=3.0.0b7", ">=2.0.8 <3.0.0b1", ">=1.8.8 <2.0.0", ">=1.7.16 <1.8.0"}, + }, + }, + want: true, + }, + { + name: "exact versions", + args: args{ + currentVersion: "2.1.0.post1", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"2.1.0.post1", "2.0.0"}, + PatchedVersions: []string{">=2.1.1"}, + }, + }, + want: true, + }, + { + name: "invalid version", + args: args{ + currentVersion: "1.2..4", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<1.0.0"}, + }, + }, + want: false, + }, + { + name: "invalid constraint", + args: args{ + currentVersion: "1.2.4", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"!1.0.0"}, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pep440.Comparer{} + got := c.IsVulnerable(tt.args.currentVersion, tt.args.advisory) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/library/compare/rubygems/compare.go b/pkg/detector/library/compare/rubygems/compare.go new file mode 100644 index 000000000000..ccb84cb8f83e --- /dev/null +++ b/pkg/detector/library/compare/rubygems/compare.go @@ -0,0 +1,32 @@ +package rubygems + +import ( + "golang.org/x/xerrors" + + "github.com/aquasecurity/go-gem-version" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" +) + +// Comparer represents a comparer for RubyGems +type Comparer struct{} + +// IsVulnerable checks if the package version is vulnerable to the advisory. +func (r Comparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool { + return compare.IsVulnerable(ver, advisory, r.matchVersion) +} + +// matchVersion checks if the package version satisfies the given constraint. +func (r Comparer) matchVersion(currentVersion, constraint string) (bool, error) { + v, err := gem.NewVersion(currentVersion) + if err != nil { + return false, xerrors.Errorf("RubyGems version error (%s): %s", currentVersion, err) + } + + c, err := gem.NewConstraints(constraint) + if err != nil { + return false, xerrors.Errorf("RubyGems constraint error (%s): %s", constraint, err) + } + + return c.Check(v), nil +} diff --git a/pkg/detector/library/compare/rubygems/compare_test.go b/pkg/detector/library/compare/rubygems/compare_test.go new file mode 100644 index 000000000000..53dbde266352 --- /dev/null +++ b/pkg/detector/library/compare/rubygems/compare_test.go @@ -0,0 +1,102 @@ +package rubygems_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/rubygems" +) + +func TestRubyGemsComparer_IsVulnerable(t *testing.T) { + type args struct { + currentVersion string + advisory types.Advisory + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "happy path", + args: args{ + currentVersion: "1.2.3", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.2.0"}, + }, + }, + want: false, + }, + { + name: "pre-release", + args: args{ + currentVersion: "1.2.3.a", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.2.3"}, + }, + }, + want: true, + }, + { + name: "pre-release without dot", + args: args{ + currentVersion: "4.1a", + advisory: types.Advisory{ + UnaffectedVersions: []string{"< 4.2b1"}, + }, + }, + want: false, + }, + { + // https://github.com/aquasecurity/trivy/issues/108 + name: "hyphen", + args: args{ + currentVersion: "1.9.25-x86-mingw32", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.9.24"}, + }, + }, + want: false, + }, + { + // https://github.com/aquasecurity/trivy/issues/108 + name: "pessimistic", + args: args{ + currentVersion: "1.8.6-java", + advisory: types.Advisory{ + PatchedVersions: []string{"~> 1.5.5", "~> 1.6.8", ">= 1.7.7"}, + }, + }, + want: false, + }, + { + name: "invalid version", + args: args{ + currentVersion: "1.2..4", + advisory: types.Advisory{ + PatchedVersions: []string{">=1.2.3"}, + }, + }, + want: false, + }, + { + name: "invalid constraint", + args: args{ + currentVersion: "1.2.4", + advisory: types.Advisory{ + PatchedVersions: []string{"!1.2.0"}, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := rubygems.Comparer{} + got := r.IsVulnerable(tt.args.currentVersion, tt.args.advisory) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/library/detect.go b/pkg/detector/library/detect.go new file mode 100644 index 000000000000..3a7af4a06f54 --- /dev/null +++ b/pkg/detector/library/detect.go @@ -0,0 +1,42 @@ +package library + +import ( + "golang.org/x/xerrors" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +// Detect scans and returns vulnerabilities of library +func Detect(libType ftypes.LangType, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + driver, ok := NewDriver(libType) + if !ok { + return nil, nil + } + + vulns, err := detect(driver, pkgs) + if err != nil { + return nil, xerrors.Errorf("failed to scan %s vulnerabilities: %w", driver.Type(), err) + } + + return vulns, nil +} + +func detect(driver Driver, libs []ftypes.Package) ([]types.DetectedVulnerability, error) { + var vulnerabilities []types.DetectedVulnerability + for _, lib := range libs { + vulns, err := driver.DetectVulnerabilities(lib.ID, lib.Name, lib.Version) + if err != nil { + return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", driver.Type(), err) + } + + for i := range vulns { + vulns[i].Layer = lib.Layer + vulns[i].PkgPath = lib.FilePath + vulns[i].PkgIdentifier = lib.Identifier + } + vulnerabilities = append(vulnerabilities, vulns...) + } + + return vulnerabilities, nil +} diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go new file mode 100644 index 000000000000..b2f5b6babc38 --- /dev/null +++ b/pkg/detector/library/driver.go @@ -0,0 +1,159 @@ +package library + +import ( + "fmt" + "strings" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/bitnami" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/maven" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/npm" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/pep440" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/rubygems" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +// NewDriver returns a driver according to the library type +func NewDriver(libType ftypes.LangType) (Driver, bool) { + var ecosystem dbTypes.Ecosystem + var comparer compare.Comparer + + switch libType { + case ftypes.Bundler, ftypes.GemSpec: + ecosystem = vulnerability.RubyGems + comparer = rubygems.Comparer{} + case ftypes.RustBinary, ftypes.Cargo: + ecosystem = vulnerability.Cargo + comparer = compare.GenericComparer{} + case ftypes.Composer: + ecosystem = vulnerability.Composer + comparer = compare.GenericComparer{} + case ftypes.GoBinary, ftypes.GoModule: + ecosystem = vulnerability.Go + comparer = compare.GenericComparer{} + case ftypes.Jar, ftypes.Pom, ftypes.Gradle: + ecosystem = vulnerability.Maven + comparer = maven.Comparer{} + case ftypes.Npm, ftypes.Yarn, ftypes.Pnpm, ftypes.NodePkg, ftypes.JavaScript: + ecosystem = vulnerability.Npm + comparer = npm.Comparer{} + case ftypes.NuGet, ftypes.DotNetCore, ftypes.PackagesProps: + ecosystem = vulnerability.NuGet + comparer = compare.GenericComparer{} + case ftypes.Pipenv, ftypes.Poetry, ftypes.Pip, ftypes.PythonPkg: + ecosystem = vulnerability.Pip + comparer = pep440.Comparer{} + case ftypes.Pub: + ecosystem = vulnerability.Pub + comparer = compare.GenericComparer{} + case ftypes.Hex: + ecosystem = vulnerability.Erlang + comparer = compare.GenericComparer{} + case ftypes.Conan: + ecosystem = vulnerability.Conan + // Only semver can be used for version ranges + // https://docs.conan.io/en/latest/versioning/version_ranges.html + comparer = compare.GenericComparer{} + case ftypes.Swift: + // Swift uses semver + // https://www.swift.org/package-manager/#importing-dependencies + ecosystem = vulnerability.Swift + comparer = compare.GenericComparer{} + case ftypes.Cocoapods: + // CocoaPods uses RubyGems version specifiers + // https://guides.cocoapods.org/making/making-a-cocoapod.html#cocoapods-versioning-specifics + ecosystem = vulnerability.Cocoapods + comparer = rubygems.Comparer{} + case ftypes.CondaPkg: + log.Logger.Warn("Conda package is supported for SBOM, not for vulnerability scanning") + return Driver{}, false + case ftypes.Bitnami: + ecosystem = vulnerability.Bitnami + comparer = bitnami.Comparer{} + case ftypes.K8sUpstream: + ecosystem = vulnerability.Kubernetes + comparer = compare.GenericComparer{} + default: + log.Logger.Warnf("The %q library type is not supported for vulnerability scanning", libType) + return Driver{}, false + } + return Driver{ + ecosystem: ecosystem, + comparer: comparer, + dbc: db.Config{}, + }, true +} + +// Driver represents security advisories for each programming language +type Driver struct { + ecosystem dbTypes.Ecosystem + comparer compare.Comparer + dbc db.Config +} + +// Type returns the driver ecosystem +func (d *Driver) Type() string { + return string(d.ecosystem) +} + +// DetectVulnerabilities scans buckets with the prefix according to the ecosystem. +// If "ecosystem" is pip, it looks for buckets with "pip::" and gets security advisories from those buckets. +// It allows us to add a new data source with the ecosystem prefix (e.g. pip::new-data-source) +// and detect vulnerabilities without specifying a specific bucket name. +func (d *Driver) DetectVulnerabilities(pkgID, pkgName, pkgVer string) ([]types.DetectedVulnerability, error) { + // e.g. "pip::", "npm::" + prefix := fmt.Sprintf("%s::", d.ecosystem) + advisories, err := d.dbc.GetAdvisories(prefix, vulnerability.NormalizePkgName(d.ecosystem, pkgName)) + if err != nil { + return nil, xerrors.Errorf("failed to get %s advisories: %w", d.ecosystem, err) + } + + var vulns []types.DetectedVulnerability + for _, adv := range advisories { + if !d.comparer.IsVulnerable(pkgVer, adv) { + continue + } + + vuln := types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkgID, + PkgName: pkgName, + InstalledVersion: pkgVer, + FixedVersion: createFixedVersions(adv), + DataSource: adv.DataSource, + } + vulns = append(vulns, vuln) + } + + return vulns, nil +} + +func createFixedVersions(advisory dbTypes.Advisory) string { + if len(advisory.PatchedVersions) != 0 { + return joinFixedVersions(advisory.PatchedVersions) + } + + var fixedVersions []string + for _, version := range advisory.VulnerableVersions { + for _, s := range strings.Split(version, ",") { + s = strings.TrimSpace(s) + if !strings.HasPrefix(s, "<=") && strings.HasPrefix(s, "<") { + s = strings.TrimPrefix(s, "<") + fixedVersions = append(fixedVersions, strings.TrimSpace(s)) + } + } + } + return joinFixedVersions(fixedVersions) +} + +func joinFixedVersions(fixedVersions []string) string { + return strings.Join(lo.Uniq(fixedVersions), ", ") +} diff --git a/pkg/detector/library/driver_test.go b/pkg/detector/library/driver_test.go new file mode 100644 index 000000000000..b7b94153c6a1 --- /dev/null +++ b/pkg/detector/library/driver_test.go @@ -0,0 +1,207 @@ +package library_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/library" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestDriver_Detect(t *testing.T) { + type args struct { + pkgName string + pkgVer string + } + tests := []struct { + name string + fixtures []string + libType ftypes.LangType + args args + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/php.yaml", + "testdata/fixtures/data-source.yaml", + }, + libType: ftypes.Composer, + args: args{ + pkgName: "symfony/symfony", + pkgVer: "4.2.6", + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-10909", + PkgName: "symfony/symfony", + InstalledVersion: "4.2.6", + FixedVersion: "4.2.7", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.GLAD, + Name: "GitLab Advisory Database Community", + URL: "https://gitlab.com/gitlab-org/advisories-community", + }, + }, + }, + }, + { + name: "case-sensitive go package", + fixtures: []string{ + "testdata/fixtures/go.yaml", + "testdata/fixtures/data-source.yaml", + }, + libType: ftypes.GoModule, + args: args{ + pkgName: "github.com/Masterminds/vcs", + pkgVer: "v1.13.1", + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-21235", + PkgName: "github.com/Masterminds/vcs", + InstalledVersion: "v1.13.1", + FixedVersion: "v1.13.2", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.GLAD, + Name: "GitLab Advisory Database Community", + URL: "https://gitlab.com/gitlab-org/advisories-community", + }, + }, + }, + }, + { + name: "non-prefixed buckets", + fixtures: []string{"testdata/fixtures/php-without-prefix.yaml"}, + libType: ftypes.Composer, + args: args{ + pkgName: "symfony/symfony", + pkgVer: "4.2.6", + }, + want: nil, + }, + { + name: "no patched versions in the advisory", + fixtures: []string{ + "testdata/fixtures/php.yaml", + "testdata/fixtures/data-source.yaml", + }, + libType: ftypes.Composer, + args: args{ + pkgName: "symfony/symfony", + pkgVer: "4.4.6", + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-5275", + PkgName: "symfony/symfony", + InstalledVersion: "4.4.6", + FixedVersion: "4.4.7", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.PhpSecurityAdvisories, + Name: "PHP Security Advisories Database", + URL: "https://github.com/FriendsOfPHP/security-advisories", + }, + }, + }, + }, + { + name: "no vulnerable versions in the advisory", + fixtures: []string{ + "testdata/fixtures/ruby.yaml", + "testdata/fixtures/data-source.yaml", + }, + libType: ftypes.Bundler, + args: args{ + pkgName: "activesupport", + pkgVer: "4.1.1", + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2015-3226", + PkgName: "activesupport", + InstalledVersion: "4.1.1", + FixedVersion: ">= 4.2.2, ~> 4.1.11", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.RubySec, + Name: "Ruby Advisory Database", + URL: "https://github.com/rubysec/ruby-advisory-db", + }, + }, + }, + }, + { + name: "no vulnerability", + fixtures: []string{"testdata/fixtures/php.yaml"}, + libType: ftypes.Composer, + args: args{ + pkgName: "symfony/symfony", + pkgVer: "4.4.7", + }, + }, + { + name: "malformed JSON", + fixtures: []string{"testdata/fixtures/invalid-type.yaml"}, + libType: ftypes.Composer, + args: args{ + pkgName: "symfony/symfony", + pkgVer: "5.1.5", + }, + wantErr: "failed to unmarshal advisory JSON", + }, + { + name: "duplicated version in advisory", + fixtures: []string{ + "testdata/fixtures/pip.yaml", + "testdata/fixtures/data-source.yaml", + }, + libType: ftypes.PythonPkg, + args: args{ + pkgName: "Django", + pkgVer: "4.2.1", + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2023-36053", + PkgName: "Django", + InstalledVersion: "4.2.1", + FixedVersion: "4.2.3", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.GHSA, + Name: "GitHub Security Advisory Pip", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize DB + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + driver, ok := library.NewDriver(tt.libType) + require.True(t, ok) + + got, err := driver.DetectVulnerabilities("", tt.args.pkgName, tt.args.pkgVer) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + // Compare + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/library/testdata/fixtures/data-source.yaml b/pkg/detector/library/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..eeb4a57e9637 --- /dev/null +++ b/pkg/detector/library/testdata/fixtures/data-source.yaml @@ -0,0 +1,27 @@ +- bucket: data-source + pairs: + - key: go::GitLab Advisory Database Community + value: + ID: "glad" + Name: "GitLab Advisory Database Community" + URL: "https://gitlab.com/gitlab-org/advisories-community" + - key: composer::GitLab Advisory Database Community + value: + ID: "glad" + Name: "GitLab Advisory Database Community" + URL: "https://gitlab.com/gitlab-org/advisories-community" + - key: composer::php-security-advisories + value: + ID: "php-security-advisories" + Name: "PHP Security Advisories Database" + URL: "https://github.com/FriendsOfPHP/security-advisories" + - key: rubygems::ruby-advisory-db + value: + ID: "ruby-advisory-db" + Name: "Ruby Advisory Database" + URL: "https://github.com/rubysec/ruby-advisory-db" + - key: "pip::GitHub Security Advisory Pip" + value: + ID: "ghsa" + Name: "GitHub Security Advisory Pip" + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" diff --git a/pkg/detector/library/testdata/fixtures/go.yaml b/pkg/detector/library/testdata/fixtures/go.yaml new file mode 100644 index 000000000000..3d48dc9e9d70 --- /dev/null +++ b/pkg/detector/library/testdata/fixtures/go.yaml @@ -0,0 +1,10 @@ +- bucket: "go::GitLab Advisory Database Community" + pairs: + - bucket: github.com/Masterminds/vcs + pairs: + - key: CVE-2022-21235 + value: + PatchedVersions: + - v1.13.2 + VulnerableVersions: + - "= 4.2.0, < 4.2.7" +- bucket: php-security-advisories + pairs: + - bucket: symfony/symfony + pairs: + - key: CVE-2020-5275 + value: + VulnerableVersions: + - ">= 4.4.0, < 4.4.7" \ No newline at end of file diff --git a/pkg/detector/library/testdata/fixtures/php.yaml b/pkg/detector/library/testdata/fixtures/php.yaml new file mode 100644 index 000000000000..468b9872f523 --- /dev/null +++ b/pkg/detector/library/testdata/fixtures/php.yaml @@ -0,0 +1,18 @@ +- bucket: "composer::GitLab Advisory Database Community" + pairs: + - bucket: symfony/symfony + pairs: + - key: CVE-2019-10909 + value: + PatchedVersions: + - 4.2.7 + VulnerableVersions: + - ">= 4.2.0, < 4.2.7" +- bucket: "composer::php-security-advisories" + pairs: + - bucket: symfony/symfony + pairs: + - key: CVE-2020-5275 + value: + VulnerableVersions: + - ">= 4.4.0, < 4.4.7" \ No newline at end of file diff --git a/pkg/detector/library/testdata/fixtures/pip.yaml b/pkg/detector/library/testdata/fixtures/pip.yaml new file mode 100644 index 000000000000..f39357e16fb6 --- /dev/null +++ b/pkg/detector/library/testdata/fixtures/pip.yaml @@ -0,0 +1,18 @@ +- bucket: "pip::GitHub Security Advisory Pip" + pairs: + - bucket: Django + pairs: + - key: CVE-2023-36053 + value: + PatchedVersions: + - 4.2.3 + VulnerableVersions: + - < 4.2.3 + - bucket: django + pairs: + - key: CVE-2023-36053 + value: + PatchedVersions: + - 4.2.3 + VulnerableVersions: + - < 4.2.3 diff --git a/pkg/detector/library/testdata/fixtures/ruby.yaml b/pkg/detector/library/testdata/fixtures/ruby.yaml new file mode 100644 index 000000000000..c3305ed7ad5f --- /dev/null +++ b/pkg/detector/library/testdata/fixtures/ruby.yaml @@ -0,0 +1,11 @@ +- bucket: "rubygems::ruby-advisory-db" + pairs: + - bucket: activesupport + pairs: + - key: CVE-2015-3226 + value: + PatchedVersions: + - ">= 4.2.2" + - "~> 4.1.11" + UnaffectedVersions: + - "< 4.1.0" \ No newline at end of file diff --git a/pkg/detector/ospkg/alma/alma.go b/pkg/detector/ospkg/alma/alma.go new file mode 100644 index 000000000000..67465f04a1b0 --- /dev/null +++ b/pkg/detector/ospkg/alma/alma.go @@ -0,0 +1,105 @@ +package alma + +import ( + "context" + "strings" + "time" + + version "github.com/knqyf263/go-rpm-version" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/alma" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + eolDates = map[string]time.Time{ + // Source: + // https://endoflife.date/almalinux + "8": time.Date(2029, 3, 1, 23, 59, 59, 0, time.UTC), + "9": time.Date(2032, 5, 31, 23, 59, 59, 0, time.UTC), + } +) + +// Scanner implements the AlmaLinux scanner +type Scanner struct { + vs *alma.VulnSrc +} + +// NewScanner is the factory method for Scanner +func NewScanner() *Scanner { + return &Scanner{ + vs: alma.NewVulnSrc(), + } +} + +// Detect vulnerabilities in package using AlmaLinux scanner +func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting AlmaLinux vulnerabilities...") + + osVer = osver.Major(osVer) + log.Logger.Debugf("AlmaLinux: os version: %s", osVer) + log.Logger.Debugf("AlmaLinux: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + var skipPkgs []string + for _, pkg := range pkgs { + if strings.Contains(pkg.Release, ".module_el") && pkg.Modularitylabel == "" { + skipPkgs = append(skipPkgs, pkg.Name) + continue + } + pkgName := addModularNamespace(pkg.Name, pkg.Modularitylabel) + advisories, err := s.vs.Get(osVer, pkgName) + if err != nil { + return nil, xerrors.Errorf("failed to get AlmaLinux advisories: %w", err) + } + + installed := utils.FormatVersion(pkg) + installedVersion := version.NewVersion(installed) + for _, adv := range advisories { + fixedVersion := version.NewVersion(adv.FixedVersion) + if installedVersion.LessThan(fixedVersion) { + vuln := types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: installed, + FixedVersion: fixedVersion.String(), + PkgIdentifier: pkg.Identifier, + Layer: pkg.Layer, + DataSource: adv.DataSource, + Custom: adv.Custom, + } + vulns = append(vulns, vuln) + } + } + } + if len(skipPkgs) > 0 { + log.Logger.Infof("Skipped detection of these packages: %q because modular packages cannot be detected correctly due to a bug in AlmaLinux. See also: https://bugs.almalinux.org/view.php?id=173", skipPkgs) + } + + return vulns, nil +} + +// IsSupportedVersion checks if the version is supported. +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + return osver.Supported(ctx, eolDates, osFamily, osver.Major(osVer)) +} + +func addModularNamespace(name, label string) string { + // e.g. npm, nodejs:12:8030020201124152102:229f0a1c => nodejs:12::npm + var count int + for i, r := range label { + if r == ':' { + count++ + } + if count == 2 { + return label[:i] + "::" + name + } + } + return name +} diff --git a/pkg/detector/ospkg/alma/alma_test.go b/pkg/detector/ospkg/alma/alma_test.go new file mode 100644 index 000000000000..bd70079fd189 --- /dev/null +++ b/pkg/detector/ospkg/alma/alma_test.go @@ -0,0 +1,224 @@ +package alma_test + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/clock" + "testing" + "time" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/alma" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/alma.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "8.4", + pkgs: []ftypes.Package{ + { + Name: "python3-libs", + Epoch: 0, + Version: "3.6.8", + Release: "36.el8.alma", + Arch: "x86_64", + SrcName: "python3", + SrcEpoch: 0, + SrcVersion: "3.6.8", + SrcRelease: "36.el8.alma", + Modularitylabel: "", + Licenses: []string{"Python"}, + Layer: ftypes.Layer{}, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "python3-libs", + VulnerabilityID: "CVE-2020-26116", + InstalledVersion: "3.6.8-36.el8.alma", + FixedVersion: "3.6.8-37.el8.alma", + Layer: ftypes.Layer{}, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Alma, + Name: "AlmaLinux Product Errata", + URL: "https://errata.almalinux.org/", + }, + }, + }, + }, + { + name: "skip modular package", + fixtures: []string{ + "testdata/fixtures/modular.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "8.4", + pkgs: []ftypes.Package{ + { + Name: "nginx", + Epoch: 1, + Version: "1.14.1", + Release: "8.module_el8.3.0+2165+af250afe.alma", + Arch: "x86_64", + SrcName: "nginx", + SrcEpoch: 1, + SrcVersion: "1.14.1", + SrcRelease: "8.module_el8.3.0+2165+af250afe.alma", + Modularitylabel: "", // ref: https://bugs.almalinux.org/view.php?id=173 , https://github.com/aquasecurity/trivy/issues/2342#issuecomment-1158459628 + Licenses: []string{"BSD"}, + Layer: ftypes.Layer{}, + }, + }, + }, + want: nil, + }, + { + name: "modular package", + fixtures: []string{ + "testdata/fixtures/modular.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "8.6", + pkgs: []ftypes.Package{ + { + Name: "httpd", + Epoch: 0, + Version: "2.4.37", + Release: "46.module_el8.6.0+2872+fe0ff7aa.1.alma", + Arch: "x86_64", + SrcName: "httpd", + SrcEpoch: 0, + SrcVersion: "2.4.37", + SrcRelease: "46.module_el8.6.0+2872+fe0ff7aa.1.alma", + Modularitylabel: "httpd:2.4:8060020220510105858:9edba152", + Licenses: []string{"ASL 2.0"}, + Layer: ftypes.Layer{}, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "httpd", + VulnerabilityID: "CVE-2020-35452", + InstalledVersion: "2.4.37-46.module_el8.6.0+2872+fe0ff7aa.1.alma", + FixedVersion: "2.4.37-47.module_el8.6.0+2872+fe0ff7aa.1.alma", + Layer: ftypes.Layer{}, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Alma, + Name: "AlmaLinux Product Errata", + URL: "https://errata.almalinux.org/", + }, + }, + }, + }, + { + name: "Get returns an error", + fixtures: []string{ + "testdata/fixtures/invalid.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "8.4", + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.5-12", + SrcName: "jq", + SrcVersion: "1.5-12", + }, + }, + }, + wantErr: "failed to get AlmaLinux advisories", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := alma.NewScanner() + got, err := s.Detect(tt.args.osVer, nil, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestScanner_IsSupportedVersion(t *testing.T) { + type args struct { + osFamily ftypes.OSType + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool + }{ + { + name: "alma 8.4", + now: time.Date(2019, 3, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "alma", + osVer: "8.4", + }, + want: true, + }, + { + name: "alma 8.4 with EOL", + now: time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC), + args: args{ + osFamily: "alma", + osVer: "8.4", + }, + want: false, + }, + { + name: "latest", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "alma", + osVer: "999", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(context.Background(), tt.now) + s := alma.NewScanner() + got := s.IsSupportedVersion(ctx, tt.args.osFamily, tt.args.osVer) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/alma/testdata/fixtures/alma.yaml b/pkg/detector/ospkg/alma/testdata/fixtures/alma.yaml new file mode 100644 index 000000000000..af3043c75faa --- /dev/null +++ b/pkg/detector/ospkg/alma/testdata/fixtures/alma.yaml @@ -0,0 +1,10 @@ +- bucket: alma 8 + pairs: + - bucket: python3-libs + pairs: + - key: CVE-2019-16935 + value: + FixedVersion: "3.6.8-31.el8.alma" + - key: CVE-2020-26116 + value: + FixedVersion: "3.6.8-37.el8.alma" \ No newline at end of file diff --git a/pkg/detector/ospkg/alma/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/alma/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..46e77ad435cb --- /dev/null +++ b/pkg/detector/ospkg/alma/testdata/fixtures/data-source.yaml @@ -0,0 +1,7 @@ +- bucket: data-source + pairs: + - key: alma 8 + value: + ID: "alma" + Name: "AlmaLinux Product Errata" + URL: "https://errata.almalinux.org/" \ No newline at end of file diff --git a/pkg/detector/ospkg/alma/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/alma/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..a9cbeec8e062 --- /dev/null +++ b/pkg/detector/ospkg/alma/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: alma 8 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar \ No newline at end of file diff --git a/pkg/detector/ospkg/alma/testdata/fixtures/modular.yaml b/pkg/detector/ospkg/alma/testdata/fixtures/modular.yaml new file mode 100644 index 000000000000..14a9112e9d54 --- /dev/null +++ b/pkg/detector/ospkg/alma/testdata/fixtures/modular.yaml @@ -0,0 +1,12 @@ +- bucket: alma 8 + pairs: + - bucket: nginx:1.14::nginx + pairs: + - key: CVE-2019-9511 + value: + FixedVersion: "1:1.14.1-9.module_el8.3.0+2165+af250afe.alma" + - bucket: httpd:2.4::httpd + pairs: + - key: CVE-2020-35452 + value: + FixedVersion: "2.4.37-47.module_el8.6.0+2872+fe0ff7aa.1.alma" \ No newline at end of file diff --git a/pkg/detector/ospkg/alpine/alpine.go b/pkg/detector/ospkg/alpine/alpine.go new file mode 100644 index 000000000000..4be5cf128431 --- /dev/null +++ b/pkg/detector/ospkg/alpine/alpine.go @@ -0,0 +1,169 @@ +package alpine + +import ( + "context" + "strings" + "time" + + version "github.com/knqyf263/go-apk-version" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/alpine" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + eolDates = map[string]time.Time{ + "2.0": time.Date(2012, 4, 1, 23, 59, 59, 0, time.UTC), + "2.1": time.Date(2012, 11, 1, 23, 59, 59, 0, time.UTC), + "2.2": time.Date(2013, 5, 1, 23, 59, 59, 0, time.UTC), + "2.3": time.Date(2013, 11, 1, 23, 59, 59, 0, time.UTC), + "2.4": time.Date(2014, 5, 1, 23, 59, 59, 0, time.UTC), + "2.5": time.Date(2014, 11, 1, 23, 59, 59, 0, time.UTC), + "2.6": time.Date(2015, 5, 1, 23, 59, 59, 0, time.UTC), + "2.7": time.Date(2015, 11, 1, 23, 59, 59, 0, time.UTC), + "3.0": time.Date(2016, 5, 1, 23, 59, 59, 0, time.UTC), + "3.1": time.Date(2016, 11, 1, 23, 59, 59, 0, time.UTC), + "3.2": time.Date(2017, 5, 1, 23, 59, 59, 0, time.UTC), + "3.3": time.Date(2017, 11, 1, 23, 59, 59, 0, time.UTC), + "3.4": time.Date(2018, 5, 1, 23, 59, 59, 0, time.UTC), + "3.5": time.Date(2018, 11, 1, 23, 59, 59, 0, time.UTC), + "3.6": time.Date(2019, 5, 1, 23, 59, 59, 0, time.UTC), + "3.7": time.Date(2019, 11, 1, 23, 59, 59, 0, time.UTC), + "3.8": time.Date(2020, 5, 1, 23, 59, 59, 0, time.UTC), + "3.9": time.Date(2020, 11, 1, 23, 59, 59, 0, time.UTC), + "3.10": time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC), + "3.11": time.Date(2021, 11, 1, 23, 59, 59, 0, time.UTC), + "3.12": time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC), + "3.13": time.Date(2022, 11, 1, 23, 59, 59, 0, time.UTC), + "3.14": time.Date(2023, 5, 1, 23, 59, 59, 0, time.UTC), + "3.15": time.Date(2023, 11, 1, 23, 59, 59, 0, time.UTC), + "3.16": time.Date(2024, 5, 23, 23, 59, 59, 0, time.UTC), + "3.17": time.Date(2024, 11, 22, 23, 59, 59, 0, time.UTC), + "3.18": time.Date(2025, 5, 9, 23, 59, 59, 0, time.UTC), + "3.19": time.Date(2025, 11, 1, 23, 59, 59, 0, time.UTC), + "edge": time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), + } +) + +// Scanner implements the Alpine scanner +type Scanner struct { + vs alpine.VulnSrc +} + +// NewScanner is the factory method for Scanner +func NewScanner() *Scanner { + return &Scanner{ + vs: alpine.NewVulnSrc(), + } +} + +// Detect vulnerabilities in package using Alpine scanner +func (s *Scanner) Detect(osVer string, repo *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting Alpine vulnerabilities...") + osVer = osver.Minor(osVer) + repoRelease := s.repoRelease(repo) + + log.Logger.Debugf("alpine: os version: %s", osVer) + log.Logger.Debugf("alpine: package repository: %s", repoRelease) + log.Logger.Debugf("alpine: the number of packages: %d", len(pkgs)) + + stream := osVer + if repoRelease != "" && osVer != repoRelease { + // Prefer the repository release. Use OS version only when the repository is not detected. + stream = repoRelease + if repoRelease != "edge" { // TODO: we should detect the current edge version. + log.Logger.Warnf("Mixing Alpine versions is unsupported, OS: '%s', repository: '%s'", osVer, repoRelease) + } + } + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + srcName := pkg.SrcName + if srcName == "" { + srcName = pkg.Name + } + advisories, err := s.vs.Get(stream, srcName) + if err != nil { + return nil, xerrors.Errorf("failed to get alpine advisories: %w", err) + } + + sourceVersion, err := version.NewVersion(utils.FormatSrcVersion(pkg)) + if err != nil { + log.Logger.Debugf("failed to parse Alpine Linux installed package version: %s", err) + continue + } + + for _, adv := range advisories { + if !s.isVulnerable(sourceVersion, adv) { + continue + } + vulns = append(vulns, types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: utils.FormatVersion(pkg), + FixedVersion: adv.FixedVersion, + Layer: pkg.Layer, + PkgIdentifier: pkg.Identifier, + Custom: adv.Custom, + DataSource: adv.DataSource, + }) + } + } + return vulns, nil +} + +func (s *Scanner) isVulnerable(installedVersion version.Version, adv dbTypes.Advisory) bool { + // This logic is for unfixed vulnerabilities, but Trivy DB doesn't have advisories for unfixed vulnerabilities for now + // because Alpine just provides potentially vulnerable packages. It will cause a lot of false positives. + // This is for Aqua commercial products. + if adv.AffectedVersion != "" { + // AffectedVersion means which version introduced this vulnerability. + affectedVersion, err := version.NewVersion(adv.AffectedVersion) + if err != nil { + log.Logger.Debugf("failed to parse Alpine Linux affected package version: %s", err) + return false + } + if affectedVersion.GreaterThan(installedVersion) { + return false + } + } + + // This logic is also for unfixed vulnerabilities. + if adv.FixedVersion == "" { + // It means the unfixed vulnerability + return true + } + + // Compare versions for fixed vulnerabilities + fixedVersion, err := version.NewVersion(adv.FixedVersion) + if err != nil { + log.Logger.Debugf("failed to parse Alpine Linux fixed version: %s", err) + return false + } + + // It means the fixed vulnerability + return installedVersion.LessThan(fixedVersion) +} + +// IsSupportedVersion checks if the version is supported. +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + return osver.Supported(ctx, eolDates, osFamily, osver.Minor(osVer)) +} + +func (s *Scanner) repoRelease(repo *ftypes.Repository) string { + if repo == nil { + return "" + } + release := repo.Release + if strings.Count(release, ".") > 1 { + release = release[:strings.LastIndex(release, ".")] + } + return release +} diff --git a/pkg/detector/ospkg/alpine/alpine_test.go b/pkg/detector/ospkg/alpine/alpine_test.go new file mode 100644 index 000000000000..f420cf5576ab --- /dev/null +++ b/pkg/detector/ospkg/alpine/alpine_test.go @@ -0,0 +1,335 @@ +package alpine_test + +import ( + "context" + "sort" + "testing" + "time" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/alpine" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + repo *ftypes.Repository + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/alpine.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "3.10.2", + pkgs: []ftypes.Package{ + { + Name: "ansible", + Version: "2.6.4", + SrcName: "ansible", + SrcVersion: "2.6.4", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + { + Name: "invalid", + Version: "invalid", // skipped + SrcName: "invalid", + SrcVersion: "invalid", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "ansible", + VulnerabilityID: "CVE-2019-10217", + InstalledVersion: "2.6.4", + FixedVersion: "2.8.4-r0", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Alpine, + Name: "Alpine Secdb", + URL: "https://secdb.alpinelinux.org/", + }, + }, + { + PkgName: "ansible", + VulnerabilityID: "CVE-2021-20191", + InstalledVersion: "2.6.4", + FixedVersion: "", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Alpine, + Name: "Alpine Secdb", + URL: "https://secdb.alpinelinux.org/", + }, + }, + }, + }, + { + name: "contain rc", + fixtures: []string{ + "testdata/fixtures/alpine.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "3.10", + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcName: "jq", + SrcVersion: "1.6-r0", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "jq", + VulnerabilityID: "CVE-2020-1234", + InstalledVersion: "1.6-r0", + FixedVersion: "1.6-r1", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Alpine, + Name: "Alpine Secdb", + URL: "https://secdb.alpinelinux.org/", + }, + }, + }, + }, + { + name: "contain pre", + fixtures: []string{ + "testdata/fixtures/alpine.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "3.10", + pkgs: []ftypes.Package{ + { + Name: "test", + Version: "0.1.0_alpha", + SrcName: "test-src", + SrcVersion: "0.1.0_alpha", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2030-0002", + PkgName: "test", + InstalledVersion: "0.1.0_alpha", + FixedVersion: "0.1.0_alpha2", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Alpine, + Name: "Alpine Secdb", + URL: "https://secdb.alpinelinux.org/", + }, + }, + }, + }, + { + name: "repository is newer than OS version", + fixtures: []string{ + "testdata/fixtures/alpine.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "3.9.3", + repo: &ftypes.Repository{ + Family: ftypes.Alpine, + Release: "3.10", + }, + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcName: "jq", + SrcVersion: "1.6-r0", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "jq", + VulnerabilityID: "CVE-2020-1234", + InstalledVersion: "1.6-r0", + FixedVersion: "1.6-r1", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Alpine, + Name: "Alpine Secdb", + URL: "https://secdb.alpinelinux.org/", + }, + }, + }, + }, + { + name: "Get returns an error", + fixtures: []string{ + "testdata/fixtures/invalid.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "3.10.2", + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcName: "jq", + SrcVersion: "1.6-r0", + }, + }, + }, + wantErr: "failed to get alpine advisories", + }, + { + name: "No src name", + fixtures: []string{ + "testdata/fixtures/alpine.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "3.9.3", + repo: &ftypes.Repository{ + Family: ftypes.Alpine, + Release: "3.10", + }, + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcVersion: "1.6-r0", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "jq", + VulnerabilityID: "CVE-2020-1234", + InstalledVersion: "1.6-r0", + FixedVersion: "1.6-r1", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Alpine, + Name: "Alpine Secdb", + URL: "https://secdb.alpinelinux.org/", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := alpine.NewScanner() + got, err := s.Detect(tt.args.osVer, tt.args.repo, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + sort.Slice(got, func(i, j int) bool { + return got[i].VulnerabilityID < got[j].VulnerabilityID + }) + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestScanner_IsSupportedVersion(t *testing.T) { + type args struct { + osFamily ftypes.OSType + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool + }{ + { + name: "alpine 3.6", + now: time.Date(2019, 3, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "alpine", + osVer: "3.6", + }, + want: true, + }, + { + name: "alpine 3.6 with EOL", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "alpine", + osVer: "3.6.5", + }, + want: false, + }, + { + name: "alpine 3.9", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "alpine", + osVer: "3.9.0", + }, + want: true, + }, + { + name: "alpine 3.10", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "alpine", + osVer: "3.10", + }, + want: true, + }, + { + name: "unknown", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "alpine", + osVer: "unknown", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(context.Background(), tt.now) + s := alpine.NewScanner() + got := s.IsSupportedVersion(ctx, tt.args.osFamily, tt.args.osVer) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/alpine/testdata/fixtures/alpine.yaml b/pkg/detector/ospkg/alpine/testdata/fixtures/alpine.yaml new file mode 100644 index 000000000000..810578368790 --- /dev/null +++ b/pkg/detector/ospkg/alpine/testdata/fixtures/alpine.yaml @@ -0,0 +1,39 @@ +- bucket: alpine 3.10 + pairs: + - bucket: ansible + pairs: + - key: CVE-2018-10875 + value: + FixedVersion: "2.6.3-r0" + - key: CVE-2019-10217 + value: + FixedVersion: "2.8.4-r0" + - key: CVE-2020-1740 + value: + FixedVersion: "" + AffectedVersion: "2.6.5" + - key: CVE-2021-20191 + value: + FixedVersion: "" + - key: CVE-2019-INVALID + value: + FixedVersion: "invalid" + - bucket: jq + pairs: + - key: CVE-2016-4074 + value: + FixedVersion: "1.6_rc1-r0" + - key: CVE-2019-9999 + value: + FixedVersion: "1.6_rc2" + - key: CVE-2020-1234 + value: + FixedVersion: "1.6-r1" + - bucket: test-src + pairs: + - key: CVE-2030-0001 + value: + FixedVersion: "0.1.0_alpha_pre2" + - key: CVE-2030-0002 + value: + FixedVersion: "0.1.0_alpha2" diff --git a/pkg/detector/ospkg/alpine/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/alpine/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..ca781d2ee96d --- /dev/null +++ b/pkg/detector/ospkg/alpine/testdata/fixtures/data-source.yaml @@ -0,0 +1,7 @@ +- bucket: data-source + pairs: + - key: alpine 3.10 + value: + ID: "alpine" + Name: "Alpine Secdb" + URL: "https://secdb.alpinelinux.org/" \ No newline at end of file diff --git a/pkg/detector/ospkg/alpine/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/alpine/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..0250feaf958b --- /dev/null +++ b/pkg/detector/ospkg/alpine/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: alpine 3.10 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/amazon/amazon.go b/pkg/detector/ospkg/amazon/amazon.go new file mode 100644 index 000000000000..0c5a35f13a8f --- /dev/null +++ b/pkg/detector/ospkg/amazon/amazon.go @@ -0,0 +1,109 @@ +package amazon + +import ( + "context" + "strings" + "time" + + version "github.com/knqyf263/go-deb-version" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/amazon" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + eolDates = map[string]time.Time{ + // https://aws.amazon.com/jp/blogs/aws/update-on-amazon-linux-ami-end-of-life/ + "1": time.Date(2023, 12, 31, 23, 59, 59, 0, time.UTC), + // https://aws.amazon.com/amazon-linux-2/faqs/?nc1=h_ls + "2": time.Date(2025, 6, 30, 23, 59, 59, 0, time.UTC), + // Amazon Linux 2022 was renamed to 2023. AL2022 is not currently supported. + "2023": time.Date(2028, 3, 15, 23, 59, 59, 0, time.UTC), + } +) + +// Scanner to scan amazon vulnerabilities +type Scanner struct { + ac amazon.VulnSrc +} + +// NewScanner is the factory method to return Amazon scanner +func NewScanner() *Scanner { + return &Scanner{ + ac: amazon.NewVulnSrc(), + } +} + +// Detect scans the packages using amazon scanner +func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting Amazon Linux vulnerabilities...") + + osVer = strings.Fields(osVer)[0] + // The format `2023.xxx.xxxx` can be used. + osVer = osver.Major(osVer) + if osVer != "2" && osVer != "2022" && osVer != "2023" { + osVer = "1" + } + log.Logger.Debugf("amazon: os version: %s", osVer) + log.Logger.Debugf("amazon: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + advisories, err := s.ac.Get(osVer, pkg.Name) + if err != nil { + return nil, xerrors.Errorf("failed to get amazon advisories: %w", err) + } + + installed := utils.FormatVersion(pkg) + if installed == "" { + continue + } + + installedVersion, err := version.NewVersion(installed) + if err != nil { + log.Logger.Debugf("failed to parse Amazon Linux installed package version: %s", err) + continue + } + + for _, adv := range advisories { + fixedVersion, err := version.NewVersion(adv.FixedVersion) + if err != nil { + log.Logger.Debugf("failed to parse Amazon Linux package version: %s", err) + continue + } + + if installedVersion.LessThan(fixedVersion) { + vuln := types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: installed, + FixedVersion: adv.FixedVersion, + PkgIdentifier: pkg.Identifier, + Layer: pkg.Layer, + Custom: adv.Custom, + DataSource: adv.DataSource, + } + vulns = append(vulns, vuln) + } + } + } + return vulns, nil +} + +// IsSupportedVersion checks if the version is supported. +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + osVer = strings.Fields(osVer)[0] + // The format `2023.xxx.xxxx` can be used. + osVer = osver.Major(osVer) + if osVer != "2" && osVer != "2022" && osVer != "2023" { + osVer = "1" + } + + return osver.Supported(ctx, eolDates, osFamily, osVer) +} diff --git a/pkg/detector/ospkg/amazon/amazon_test.go b/pkg/detector/ospkg/amazon/amazon_test.go new file mode 100644 index 000000000000..c9c5a3c65840 --- /dev/null +++ b/pkg/detector/ospkg/amazon/amazon_test.go @@ -0,0 +1,257 @@ +package amazon_test + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/clock" + "testing" + "time" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/amazon" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "amazon linux 1", + fixtures: []string{ + "testdata/fixtures/amazon.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "1.2", + pkgs: []ftypes.Package{ + { + Name: "bind", + Epoch: 32, + Version: "9.8.2", + Release: "0.68.rc1.85.amzn1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "bind", + VulnerabilityID: "CVE-2020-8625", + InstalledVersion: "32:9.8.2-0.68.rc1.85.amzn1", + FixedVersion: "32:9.8.2-0.68.rc1.86.amzn1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Amazon, + Name: "Amazon Linux Security Center", + URL: "https://alas.aws.amazon.com/", + }, + }, + }, + }, + { + name: "amazon linux 2", + fixtures: []string{ + "testdata/fixtures/amazon.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "2", + pkgs: []ftypes.Package{ + { + Name: "bash", + Version: "4.2.45", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "bash", + VulnerabilityID: "CVE-2019-9924", + InstalledVersion: "4.2.45", + FixedVersion: "4.2.46-34.amzn2", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Amazon, + Name: "Amazon Linux Security Center", + URL: "https://alas.aws.amazon.com/", + }, + }, + }, + }, + { + name: "amazon linux 2023", + fixtures: []string{ + "testdata/fixtures/amazon.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "2023.3.20240304", + pkgs: []ftypes.Package{ + { + Name: "protobuf", + Version: "3.14.0-7.amzn2023.0.3", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "protobuf", + VulnerabilityID: "CVE-2022-1941", + InstalledVersion: "3.14.0-7.amzn2023.0.3", + FixedVersion: "3.19.6-1.amzn2023.0.1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Amazon, + Name: "Amazon Linux Security Center", + URL: "https://alas.aws.amazon.com/", + }, + }, + }, + }, + { + name: "empty version", + fixtures: []string{ + "testdata/fixtures/amazon.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "2", + pkgs: []ftypes.Package{ + { + Name: "bash", + }, + }, + }, + }, + { + name: "Get returns an error", + fixtures: []string{ + "testdata/fixtures/invalid.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "1", + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcName: "jq", + SrcVersion: "1.6-r0", + }, + }, + }, + wantErr: "failed to get amazon advisories", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := amazon.NewScanner() + got, err := s.Detect(tt.args.osVer, nil, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestScanner_IsSupportedVersion(t *testing.T) { + type args struct { + osFamily ftypes.OSType + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool + }{ + { + name: "amazon linux 1", + now: time.Date(2022, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "amazon", + osVer: "1", + }, + want: true, + }, + { + name: "amazon linux 1 EOL", + now: time.Date(2024, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "amazon", + osVer: "1", + }, + want: false, + }, + { + name: "amazon linux 2", + now: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC), + args: args{ + osFamily: "amazon", + osVer: "2", + }, + want: true, + }, + { + name: "amazon linux 2022", + now: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC), + args: args{ + osFamily: "amazon", + osVer: "2022", + }, + want: true, + }, + { + name: "amazon linux 2023", + now: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC), + args: args{ + osFamily: "amazon", + osVer: "2023", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(context.Background(), tt.now) + s := amazon.NewScanner() + got := s.IsSupportedVersion(ctx, tt.args.osFamily, tt.args.osVer) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/amazon/testdata/fixtures/amazon.yaml b/pkg/detector/ospkg/amazon/testdata/fixtures/amazon.yaml new file mode 100644 index 000000000000..fec6ad03d60b --- /dev/null +++ b/pkg/detector/ospkg/amazon/testdata/fixtures/amazon.yaml @@ -0,0 +1,28 @@ +- bucket: amazon linux 1 + pairs: + - bucket: bind + pairs: + - key: CVE-2020-8625 + value: + FixedVersion: "32:9.8.2-0.68.rc1.86.amzn1" +- bucket: amazon linux 2 + pairs: + - bucket: bash + pairs: + - key: CVE-2019-9924 + value: + FixedVersion: "4.2.46-34.amzn2" +- bucket: amazon linux 2022 + pairs: + - bucket: log4j + pairs: + - key: CVE-2021-44228 + value: + FixedVersion: "2.15.0-1.amzn2022.0.1" +- bucket: amazon linux 2023 + pairs: + - bucket: protobuf + pairs: + - key: CVE-2022-1941 + value: + FixedVersion: "3.19.6-1.amzn2023.0.1" \ No newline at end of file diff --git a/pkg/detector/ospkg/amazon/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/amazon/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..6c1d3e2c339a --- /dev/null +++ b/pkg/detector/ospkg/amazon/testdata/fixtures/data-source.yaml @@ -0,0 +1,22 @@ +- bucket: data-source + pairs: + - key: amazon linux 1 + value: + ID: "amazon" + Name: "Amazon Linux Security Center" + URL: "https://alas.aws.amazon.com/" + - key: amazon linux 2 + value: + ID: "amazon" + Name: "Amazon Linux Security Center" + URL: "https://alas.aws.amazon.com/" + - key: amazon linux 2022 + value: + ID: "amazon" + Name: "Amazon Linux Security Center" + URL: "https://alas.aws.amazon.com/" + - key: amazon linux 2023 + value: + ID: "amazon" + Name: "Amazon Linux Security Center" + URL: "https://alas.aws.amazon.com/" \ No newline at end of file diff --git a/pkg/detector/ospkg/amazon/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/amazon/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..073f4391cf87 --- /dev/null +++ b/pkg/detector/ospkg/amazon/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: amazon linux 1 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar \ No newline at end of file diff --git a/pkg/detector/ospkg/chainguard/chainguard.go b/pkg/detector/ospkg/chainguard/chainguard.go new file mode 100644 index 000000000000..bba9642c481c --- /dev/null +++ b/pkg/detector/ospkg/chainguard/chainguard.go @@ -0,0 +1,91 @@ +package chainguard + +import ( + "context" + + version "github.com/knqyf263/go-apk-version" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/chainguard" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +// Scanner implements the Chainguard scanner +type Scanner struct { + vs chainguard.VulnSrc +} + +// NewScanner is the factory method for Scanner +func NewScanner() *Scanner { + return &Scanner{ + vs: chainguard.NewVulnSrc(), + } +} + +// Detect vulnerabilities in package using Chainguard scanner +func (s *Scanner) Detect(_ string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting Chainguard vulnerabilities...") + + log.Logger.Debugf("chainguard: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + srcName := pkg.SrcName + if srcName == "" { + srcName = pkg.Name + } + advisories, err := s.vs.Get("", srcName) + if err != nil { + return nil, xerrors.Errorf("failed to get Chainguard advisories: %w", err) + } + + installed := utils.FormatVersion(pkg) + installedVersion, err := version.NewVersion(installed) + if err != nil { + log.Logger.Debugf("failed to parse Chainguard installed package version: %s", err) + continue + } + + for _, adv := range advisories { + if !s.isVulnerable(installedVersion, adv) { + continue + } + vulns = append(vulns, types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: installed, + FixedVersion: adv.FixedVersion, + Layer: pkg.Layer, + PkgIdentifier: pkg.Identifier, + Custom: adv.Custom, + DataSource: adv.DataSource, + }) + } + } + return vulns, nil +} + +func (s *Scanner) isVulnerable(installedVersion version.Version, adv dbTypes.Advisory) bool { + // Compare versions for fixed vulnerabilities + fixedVersion, err := version.NewVersion(adv.FixedVersion) + if err != nil { + log.Logger.Debugf("failed to parse Chainguard fixed version: %s", err) + return false + } + + // It means the fixed vulnerability + return installedVersion.LessThan(fixedVersion) +} + +// IsSupportedVersion checks if the version is supported. +func (s *Scanner) IsSupportedVersion(_ context.Context, _ ftypes.OSType, _ string) bool { + // Chainguard doesn't have versions, so there is no case where a given input yields a + // result of an unsupported Chainguard version. + + return true +} diff --git a/pkg/detector/ospkg/chainguard/chainguard_test.go b/pkg/detector/ospkg/chainguard/chainguard_test.go new file mode 100644 index 000000000000..446693ce2170 --- /dev/null +++ b/pkg/detector/ospkg/chainguard/chainguard_test.go @@ -0,0 +1,211 @@ +package chainguard_test + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/chainguard" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + repo *ftypes.Repository + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/chainguard.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + pkgs: []ftypes.Package{ + { + Name: "ansible", + Version: "2.6.4", + SrcName: "ansible", + SrcVersion: "2.6.4", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + { + Name: "invalid", + Version: "invalid", // skipped + SrcName: "invalid", + SrcVersion: "invalid", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "ansible", + VulnerabilityID: "CVE-2019-10217", + InstalledVersion: "2.6.4", + FixedVersion: "2.8.4-r0", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Chainguard, + Name: "Chainguard Secdb", + URL: "https://packages.cgr.dev/chainguard/security.json", + }, + }, + }, + }, + { + name: "contain rc", + fixtures: []string{ + "testdata/fixtures/chainguard.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcName: "jq", + SrcVersion: "1.6-r0", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "jq", + VulnerabilityID: "CVE-2020-1234", + InstalledVersion: "1.6-r0", + FixedVersion: "1.6-r1", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Chainguard, + Name: "Chainguard Secdb", + URL: "https://packages.cgr.dev/chainguard/security.json", + }, + }, + }, + }, + { + name: "contain pre", + fixtures: []string{ + "testdata/fixtures/chainguard.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + pkgs: []ftypes.Package{ + { + Name: "test", + Version: "0.1.0_alpha", + SrcName: "test-src", + SrcVersion: "0.1.0_alpha", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2030-0002", + PkgName: "test", + InstalledVersion: "0.1.0_alpha", + FixedVersion: "0.1.0_alpha2", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Chainguard, + Name: "Chainguard Secdb", + URL: "https://packages.cgr.dev/chainguard/security.json", + }, + }, + }, + }, + { + name: "Get returns an error", + fixtures: []string{ + "testdata/fixtures/invalid.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcName: "jq", + SrcVersion: "1.6-r0", + }, + }, + }, + wantErr: "failed to get Chainguard advisories", + }, + { + name: "No src name", + fixtures: []string{ + "testdata/fixtures/chainguard.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + repo: &ftypes.Repository{ + Family: ftypes.Chainguard, + Release: "3.10", + }, + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcVersion: "1.6-r0", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "jq", + VulnerabilityID: "CVE-2020-1234", + InstalledVersion: "1.6-r0", + FixedVersion: "1.6-r1", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Chainguard, + Name: "Chainguard Secdb", + URL: "https://packages.cgr.dev/chainguard/security.json", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := chainguard.NewScanner() + got, err := s.Detect("", tt.args.repo, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + sort.Slice(got, func(i, j int) bool { + return got[i].VulnerabilityID < got[j].VulnerabilityID + }) + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/chainguard/testdata/fixtures/chainguard.yaml b/pkg/detector/ospkg/chainguard/testdata/fixtures/chainguard.yaml new file mode 100644 index 000000000000..c6015908d392 --- /dev/null +++ b/pkg/detector/ospkg/chainguard/testdata/fixtures/chainguard.yaml @@ -0,0 +1,39 @@ +- bucket: chainguard + pairs: + - bucket: ansible + pairs: + - key: CVE-2018-10875 + value: + FixedVersion: "2.6.3-r0" + - key: CVE-2019-10217 + value: + FixedVersion: "2.8.4-r0" + - key: CVE-2020-1740 + value: + FixedVersion: "" + AffectedVersion: "2.6.5" + - key: CVE-2021-20191 + value: + FixedVersion: "" + - key: CVE-2019-INVALID + value: + FixedVersion: "invalid" + - bucket: jq + pairs: + - key: CVE-2016-4074 + value: + FixedVersion: "1.6_rc1-r0" + - key: CVE-2019-9999 + value: + FixedVersion: "1.6_rc2" + - key: CVE-2020-1234 + value: + FixedVersion: "1.6-r1" + - bucket: test-src + pairs: + - key: CVE-2030-0001 + value: + FixedVersion: "0.1.0_alpha_pre2" + - key: CVE-2030-0002 + value: + FixedVersion: "0.1.0_alpha2" diff --git a/pkg/detector/ospkg/chainguard/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/chainguard/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..b29a5f0eb011 --- /dev/null +++ b/pkg/detector/ospkg/chainguard/testdata/fixtures/data-source.yaml @@ -0,0 +1,7 @@ +- bucket: data-source + pairs: + - key: chainguard + value: + ID: "chainguard" + Name: "Chainguard Secdb" + URL: "https://packages.cgr.dev/chainguard/security.json" \ No newline at end of file diff --git a/pkg/detector/ospkg/chainguard/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/chainguard/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..b2fd595eb9b7 --- /dev/null +++ b/pkg/detector/ospkg/chainguard/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: chainguard + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/debian/debian.go b/pkg/detector/ospkg/debian/debian.go new file mode 100644 index 000000000000..c350f6d2c281 --- /dev/null +++ b/pkg/detector/ospkg/debian/debian.go @@ -0,0 +1,124 @@ +package debian + +import ( + "context" + "time" + + version "github.com/knqyf263/go-deb-version" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/debian" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + eolDates = map[string]time.Time{ + "1.1": time.Date(1997, 6, 5, 23, 59, 59, 0, time.UTC), + "1.2": time.Date(1998, 6, 5, 23, 59, 59, 0, time.UTC), + "1.3": time.Date(1999, 3, 9, 23, 59, 59, 0, time.UTC), + "2.0": time.Date(2000, 3, 9, 23, 59, 59, 0, time.UTC), + "2.1": time.Date(2000, 10, 30, 23, 59, 59, 0, time.UTC), + "2.2": time.Date(2003, 7, 30, 23, 59, 59, 0, time.UTC), + "3.0": time.Date(2006, 6, 30, 23, 59, 59, 0, time.UTC), + "3.1": time.Date(2008, 3, 30, 23, 59, 59, 0, time.UTC), + "4.0": time.Date(2010, 2, 15, 23, 59, 59, 0, time.UTC), + "5.0": time.Date(2012, 2, 6, 23, 59, 59, 0, time.UTC), + // LTS + "6.0": time.Date(2016, 2, 29, 23, 59, 59, 0, time.UTC), + "7": time.Date(2018, 5, 31, 23, 59, 59, 0, time.UTC), + "8": time.Date(2020, 6, 30, 23, 59, 59, 0, time.UTC), + "9": time.Date(2022, 6, 30, 23, 59, 59, 0, time.UTC), + "10": time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC), + "11": time.Date(2026, 8, 14, 23, 59, 59, 0, time.UTC), + "12": time.Date(2028, 6, 10, 23, 59, 59, 0, time.UTC), + "13": time.Date(3000, 1, 1, 23, 59, 59, 0, time.UTC), + } +) + +// Scanner implements the Debian scanner +type Scanner struct { + vs debian.VulnSrc +} + +// NewScanner is the factory method to return Scanner +func NewScanner() *Scanner { + return &Scanner{ + vs: debian.NewVulnSrc(), + } +} + +// Detect scans and return vulnerabilities using Debian scanner +func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting Debian vulnerabilities...") + + osVer = osver.Major(osVer) + log.Logger.Debugf("debian: os version: %s", osVer) + log.Logger.Debugf("debian: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + sourceVersion, err := version.NewVersion(utils.FormatSrcVersion(pkg)) + if err != nil { + log.Logger.Debugf("Debian installed package version error: %s", err) + continue + } + + advisories, err := s.vs.Get(osVer, pkg.SrcName) + if err != nil { + return nil, xerrors.Errorf("failed to get debian advisories: %w", err) + } + + for _, adv := range advisories { + vuln := types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + VendorIDs: adv.VendorIDs, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: utils.FormatVersion(pkg), + FixedVersion: adv.FixedVersion, + PkgIdentifier: pkg.Identifier, + Status: adv.Status, + Layer: pkg.Layer, + Custom: adv.Custom, + DataSource: adv.DataSource, + } + + if adv.Severity != dbTypes.SeverityUnknown { + // Package-specific severity + vuln.SeveritySource = vulnerability.Debian + vuln.Vulnerability = dbTypes.Vulnerability{ + Severity: adv.Severity.String(), + } + } + + // It means unfixed vulnerability. We don't have to compare versions. + if adv.FixedVersion == "" { + vulns = append(vulns, vuln) + continue + } + + var fixedVersion version.Version + fixedVersion, err = version.NewVersion(adv.FixedVersion) + if err != nil { + log.Logger.Debugf("Debian advisory package version error: %s", err) + continue + } + + if sourceVersion.LessThan(fixedVersion) { + vulns = append(vulns, vuln) + } + } + } + return vulns, nil +} + +// IsSupportedVersion checks if the version is supported. +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + return osver.Supported(ctx, eolDates, osFamily, osver.Major(osVer)) +} diff --git a/pkg/detector/ospkg/debian/debian_test.go b/pkg/detector/ospkg/debian/debian_test.go new file mode 100644 index 000000000000..8c22a386a74d --- /dev/null +++ b/pkg/detector/ospkg/debian/debian_test.go @@ -0,0 +1,181 @@ +package debian_test + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/clock" + "sort" + "testing" + "time" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/debian" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/debian.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "9.1", + pkgs: []ftypes.Package{ + { + Name: "htpasswd", + Version: "2.4.24", + SrcName: "apache2", + SrcVersion: "2.4.24", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "htpasswd", + VulnerabilityID: "CVE-2020-11985", + VendorIDs: []string{"DSA-4884-1"}, + InstalledVersion: "2.4.24", + FixedVersion: "2.4.25-1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Debian, + Name: "Debian Security Tracker", + URL: "https://salsa.debian.org/security-tracker-team/security-tracker", + }, + }, + { + PkgName: "htpasswd", + VulnerabilityID: "CVE-2021-31618", + InstalledVersion: "2.4.24", + Status: dbTypes.StatusWillNotFix, + SeveritySource: vulnerability.Debian, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityMedium.String(), + }, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Debian, + Name: "Debian Security Tracker", + URL: "https://salsa.debian.org/security-tracker-team/security-tracker", + }, + }, + }, + }, + { + name: "invalid bucket", + fixtures: []string{ + "testdata/fixtures/invalid.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "9.1", + pkgs: []ftypes.Package{ + { + Name: "htpasswd", + Version: "2.4.24", + SrcName: "apache2", + SrcVersion: "2.4.24", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + wantErr: "failed to unmarshal advisory JSON", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := debian.NewScanner() + got, err := s.Detect(tt.args.osVer, nil, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + sort.Slice(got, func(i, j int) bool { + return got[i].VulnerabilityID < got[j].VulnerabilityID + }) + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestScanner_IsSupportedVersion(t *testing.T) { + type args struct { + osFamily ftypes.OSType + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool + }{ + { + name: "debian 7", + now: time.Date(2018, 3, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "debian", + osVer: "7", + }, + want: true, + }, + { + name: "debian 8 EOL", + now: time.Date(2020, 7, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "debian", + osVer: "8.2", + }, + want: false, + }, + { + name: "latest", + now: time.Date(2020, 7, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "debian", + osVer: "999", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(context.Background(), tt.now) + s := debian.NewScanner() + got := s.IsSupportedVersion(ctx, tt.args.osFamily, tt.args.osVer) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/debian/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/debian/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..dbce04f64048 --- /dev/null +++ b/pkg/detector/ospkg/debian/testdata/fixtures/data-source.yaml @@ -0,0 +1,7 @@ +- bucket: data-source + pairs: + - key: debian 9 + value: + ID: "debian" + Name: "Debian Security Tracker" + URL: "https://salsa.debian.org/security-tracker-team/security-tracker" \ No newline at end of file diff --git a/pkg/detector/ospkg/debian/testdata/fixtures/debian.yaml b/pkg/detector/ospkg/debian/testdata/fixtures/debian.yaml new file mode 100644 index 000000000000..903f7e46e54b --- /dev/null +++ b/pkg/detector/ospkg/debian/testdata/fixtures/debian.yaml @@ -0,0 +1,17 @@ +- bucket: debian 9 + pairs: + - bucket: apache2 + pairs: + - key: CVE-2012-3499 + value: + FixedVersion: "2.2.22-13" + - key: CVE-2020-11985 + value: + FixedVersion: "2.4.25-1" + VendorIDs: + - DSA-4884-1 + - key: CVE-2021-31618 + value: + FixedVersion: "" + Severity: 2 + Status: 5 diff --git a/pkg/detector/ospkg/debian/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/debian/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..32ff0c70ab1c --- /dev/null +++ b/pkg/detector/ospkg/debian/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: debian 9 + pairs: + - bucket: apache2 + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar \ No newline at end of file diff --git a/pkg/detector/ospkg/detect.go b/pkg/detector/ospkg/detect.go new file mode 100644 index 000000000000..32ed2ff9c5ab --- /dev/null +++ b/pkg/detector/ospkg/detect.go @@ -0,0 +1,91 @@ +package ospkg + +import ( + "context" + "time" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/detector/ospkg/alma" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/alpine" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/amazon" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/chainguard" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/debian" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/mariner" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/oracle" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/photon" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/rocky" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/suse" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/ubuntu" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/wolfi" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + // ErrUnsupportedOS defines error for unsupported OS + ErrUnsupportedOS = xerrors.New("unsupported os") + + drivers = map[ftypes.OSType]Driver{ + ftypes.Alpine: alpine.NewScanner(), + ftypes.Alma: alma.NewScanner(), + ftypes.Amazon: amazon.NewScanner(), + ftypes.CBLMariner: mariner.NewScanner(), + ftypes.Debian: debian.NewScanner(), + ftypes.Ubuntu: ubuntu.NewScanner(), + ftypes.RedHat: redhat.NewScanner(), + ftypes.CentOS: redhat.NewScanner(), + ftypes.Rocky: rocky.NewScanner(), + ftypes.Oracle: oracle.NewScanner(), + ftypes.OpenSUSELeap: suse.NewScanner(suse.OpenSUSE), + ftypes.SLES: suse.NewScanner(suse.SUSEEnterpriseLinux), + ftypes.Photon: photon.NewScanner(), + ftypes.Wolfi: wolfi.NewScanner(), + ftypes.Chainguard: chainguard.NewScanner(), + } +) + +// RegisterDriver is defined for extensibility and not supposed to be used in Trivy. +func RegisterDriver(name ftypes.OSType, driver Driver) { + drivers[name] = driver +} + +// Driver defines operations for OS package scan +type Driver interface { + Detect(string, *ftypes.Repository, []ftypes.Package) ([]types.DetectedVulnerability, error) + IsSupportedVersion(context.Context, ftypes.OSType, string) bool +} + +// Detect detects the vulnerabilities +func Detect(ctx context.Context, _, osFamily ftypes.OSType, osName string, repo *ftypes.Repository, _ time.Time, pkgs []ftypes.Package) ([]types.DetectedVulnerability, bool, error) { + driver, err := newDriver(osFamily) + if err != nil { + return nil, false, ErrUnsupportedOS + } + + eosl := !driver.IsSupportedVersion(ctx, osFamily, osName) + + // Package `gpg-pubkey` doesn't use the correct version. + // We don't need to find vulnerabilities for this package. + filteredPkgs := lo.Filter(pkgs, func(pkg ftypes.Package, index int) bool { + return pkg.Name != "gpg-pubkey" + }) + vulns, err := driver.Detect(osName, repo, filteredPkgs) + if err != nil { + return nil, false, xerrors.Errorf("failed detection: %w", err) + } + + return vulns, eosl, nil +} + +func newDriver(osFamily ftypes.OSType) (Driver, error) { + if driver, ok := drivers[osFamily]; ok { + return driver, nil + } + + log.Logger.Warnf("unsupported os : %s", osFamily) + return nil, ErrUnsupportedOS +} diff --git a/pkg/detector/ospkg/mariner/mariner.go b/pkg/detector/ospkg/mariner/mariner.go new file mode 100644 index 000000000000..6e1054151518 --- /dev/null +++ b/pkg/detector/ospkg/mariner/mariner.go @@ -0,0 +1,81 @@ +package mariner + +import ( + "context" + + version "github.com/knqyf263/go-rpm-version" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/mariner" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +// Scanner implements the CBL-Mariner scanner +type Scanner struct { + vs mariner.VulnSrc +} + +// NewScanner is the factory method for Scanner +func NewScanner() *Scanner { + return &Scanner{ + vs: mariner.NewVulnSrc(), + } +} + +// Detect vulnerabilities in package using CBL-Mariner scanner +func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting CBL-Mariner vulnerabilities...") + + // e.g. 1.0.20210127 + osVer = osver.Minor(osVer) + + log.Logger.Debugf("CBL-Mariner: os version: %s", osVer) + log.Logger.Debugf("CBL-Mariner: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + // CBL Mariner OVAL contains source package names only. + advisories, err := s.vs.Get(osVer, pkg.SrcName) + if err != nil { + return nil, xerrors.Errorf("failed to get CBL-Mariner advisories: %w", err) + } + + sourceVersion := version.NewVersion(utils.FormatSrcVersion(pkg)) + + for _, adv := range advisories { + vuln := types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgName: pkg.Name, + InstalledVersion: utils.FormatVersion(pkg), + PkgIdentifier: pkg.Identifier, + Layer: pkg.Layer, + DataSource: adv.DataSource, + } + + // Unpatched vulnerabilities + if adv.FixedVersion == "" { + vulns = append(vulns, vuln) + continue + } + + // Patched vulnerabilities + fixedVersion := version.NewVersion(adv.FixedVersion) + if sourceVersion.LessThan(fixedVersion) { + vuln.FixedVersion = fixedVersion.String() + vulns = append(vulns, vuln) + } + } + } + + return vulns, nil +} + +// IsSupportedVersion checks if the version is supported. +func (s *Scanner) IsSupportedVersion(_ context.Context, _ ftypes.OSType, _ string) bool { + // EOL is not in public at the moment. + return true +} diff --git a/pkg/detector/ospkg/mariner/mariner_test.go b/pkg/detector/ospkg/mariner/mariner_test.go new file mode 100644 index 000000000000..262f211d8401 --- /dev/null +++ b/pkg/detector/ospkg/mariner/mariner_test.go @@ -0,0 +1,147 @@ +package mariner_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/mariner" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path 1.0 SrcName and Name are different", + fixtures: []string{ + "testdata/fixtures/mariner.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "1.0", + pkgs: []ftypes.Package{ + { + Name: "bind-utils", + Epoch: 0, + Version: "9.16.14", + Release: "1.cm1", + Arch: "aarch64", + SrcName: "bind", + SrcEpoch: 0, + SrcVersion: "9.16.14", + SrcRelease: "1.cm1", + Licenses: []string{"ISC"}, + Layer: ftypes.Layer{}, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "bind-utils", + VulnerabilityID: "CVE-2019-6470", + InstalledVersion: "9.16.14-1.cm1", + FixedVersion: "9.16.15-1.cm1", + Layer: ftypes.Layer{}, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.CBLMariner, + Name: "CBL-Mariner Vulnerability Data", + URL: "https://github.com/microsoft/CBL-MarinerVulnerabilityData", + }, + }, + }, + }, + { + name: "happy path 2.0", + fixtures: []string{ + "testdata/fixtures/mariner.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "2.0", + pkgs: []ftypes.Package{ + { + Name: "vim", + Epoch: 0, + Version: "8.2.4081", + Release: "1.cm1", + Arch: "aarch64", + SrcName: "vim", + SrcEpoch: 0, + SrcVersion: "8.2.4081", + SrcRelease: "1.cm1", + Licenses: []string{"Vim"}, + Layer: ftypes.Layer{}, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "vim", + VulnerabilityID: "CVE-2022-0261", + InstalledVersion: "8.2.4081-1.cm1", + Layer: ftypes.Layer{}, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.CBLMariner, + Name: "CBL-Mariner Vulnerability Data", + URL: "https://github.com/microsoft/CBL-MarinerVulnerabilityData", + }, + }, + }, + }, + { + name: "broken advisory", + fixtures: []string{"testdata/fixtures/invalid.yaml", "testdata/fixtures/data-source.yaml"}, + args: args{ + osVer: "1.0", + pkgs: []ftypes.Package{ + { + Name: "bind-utils", + Epoch: 0, + Version: "9.16.14", + Release: "1.cm1", + Arch: "aarch64", + SrcName: "bind", + SrcEpoch: 0, + SrcVersion: "9.16.14", + SrcRelease: "1.cm1", + Licenses: []string{"ISC"}, + Layer: ftypes.Layer{}, + }, + }, + }, + wantErr: "failed to get CBL-Mariner advisories", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := mariner.NewScanner() + got, err := s.Detect(tt.args.osVer, nil, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/mariner/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/mariner/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..57ce67b2ecd8 --- /dev/null +++ b/pkg/detector/ospkg/mariner/testdata/fixtures/data-source.yaml @@ -0,0 +1,14 @@ +- bucket: data-source + pairs: + - key: CBL-Mariner 1.0 + value: + ID: "cbl-mariner" + Name: "CBL-Mariner Vulnerability Data" + URL: "https://github.com/microsoft/CBL-MarinerVulnerabilityData" +- bucket: data-source + pairs: + - key: CBL-Mariner 2.0 + value: + ID: "cbl-mariner" + Name: "CBL-Mariner Vulnerability Data" + URL: "https://github.com/microsoft/CBL-MarinerVulnerabilityData" diff --git a/pkg/detector/ospkg/mariner/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/mariner/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..84273b060928 --- /dev/null +++ b/pkg/detector/ospkg/mariner/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: CBL-Mariner 1.0 + pairs: + - bucket: bind + pairs: + - key: CVE-2021-25219 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/mariner/testdata/fixtures/mariner.yaml b/pkg/detector/ospkg/mariner/testdata/fixtures/mariner.yaml new file mode 100644 index 000000000000..7f044d1a8b1a --- /dev/null +++ b/pkg/detector/ospkg/mariner/testdata/fixtures/mariner.yaml @@ -0,0 +1,16 @@ +- bucket: CBL-Mariner 1.0 + pairs: + - bucket: bind + pairs: + - key: CVE-2020-8618 + value: + FixedVersion: 0:9.16.3-2.cm1 + - key: CVE-2019-6470 + value: + FixedVersion: 0:9.16.15-1.cm1 + +- bucket: CBL-Mariner 2.0 + pairs: + - bucket: vim + pairs: + - key: CVE-2022-0261 diff --git a/pkg/detector/ospkg/oracle/oracle.go b/pkg/detector/ospkg/oracle/oracle.go new file mode 100644 index 000000000000..186e0d2734dc --- /dev/null +++ b/pkg/detector/ospkg/oracle/oracle.go @@ -0,0 +1,104 @@ +package oracle + +import ( + "context" + "strings" + "time" + + version "github.com/knqyf263/go-rpm-version" + "golang.org/x/xerrors" + + oracleoval "github.com/aquasecurity/trivy-db/pkg/vulnsrc/oracle-oval" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + eolDates = map[string]time.Time{ + // Source: + // https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf + // https://community.oracle.com/docs/DOC-917964 + "3": time.Date(2011, 12, 31, 23, 59, 59, 0, time.UTC), + "4": time.Date(2013, 12, 31, 23, 59, 59, 0, time.UTC), + "5": time.Date(2017, 12, 31, 23, 59, 59, 0, time.UTC), + "6": time.Date(2021, 3, 21, 23, 59, 59, 0, time.UTC), + "7": time.Date(2024, 7, 23, 23, 59, 59, 0, time.UTC), + "8": time.Date(2029, 7, 18, 23, 59, 59, 0, time.UTC), + "9": time.Date(2032, 7, 18, 23, 59, 59, 0, time.UTC), + } +) + +// Scanner implements oracle vulnerability scanner +type Scanner struct { + vs *oracleoval.VulnSrc +} + +// NewScanner is the factory method to return oracle vulnerabilities +func NewScanner() *Scanner { + return &Scanner{ + vs: oracleoval.NewVulnSrc(), + } +} + +func extractKsplice(v string) string { + subs := strings.Split(strings.ToLower(v), ".") + for _, s := range subs { + if strings.HasPrefix(s, "ksplice") { + return s + } + } + return "" +} + +// Detect scans and return vulnerability in Oracle scanner +func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting Oracle Linux vulnerabilities...") + + osVer = osver.Major(osVer) + log.Logger.Debugf("Oracle Linux: os version: %s", osVer) + log.Logger.Debugf("Oracle Linux: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + advisories, err := s.vs.Get(osVer, pkg.Name) + if err != nil { + return nil, xerrors.Errorf("failed to get Oracle Linux advisory: %w", err) + } + + installed := utils.FormatVersion(pkg) + installedVersion := version.NewVersion(installed) + for _, adv := range advisories { + // when one of them doesn't have ksplice, we'll also skip it + // extract kspliceX and compare it with kspliceY in advisories + // if kspliceX and kspliceY are different, we will skip the advisory + if extractKsplice(adv.FixedVersion) != extractKsplice(pkg.Release) { + continue + } + + fixedVersion := version.NewVersion(adv.FixedVersion) + vuln := types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: installed, + PkgIdentifier: pkg.Identifier, + Layer: pkg.Layer, + Custom: adv.Custom, + DataSource: adv.DataSource, + } + if installedVersion.LessThan(fixedVersion) { + vuln.FixedVersion = adv.FixedVersion + vulns = append(vulns, vuln) + } + } + } + return vulns, nil +} + +// IsSupportedVersion checks if the version is supported. +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + return osver.Supported(ctx, eolDates, osFamily, osver.Major(osVer)) +} diff --git a/pkg/detector/ospkg/oracle/oracle_test.go b/pkg/detector/ospkg/oracle/oracle_test.go new file mode 100644 index 000000000000..40c8f26f2d9a --- /dev/null +++ b/pkg/detector/ospkg/oracle/oracle_test.go @@ -0,0 +1,265 @@ +package oracle + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/clock" + "testing" + "time" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScanner_IsSupportedVersion(t *testing.T) { + tests := map[string]struct { + now time.Time + osFamily ftypes.OSType + osVersion string + expected bool + }{ + "oracle3": { + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + osFamily: "oracle", + osVersion: "3", + expected: false, + }, + "oracle4": { + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + osFamily: "oracle", + osVersion: "4", + expected: false, + }, + "oracle5": { + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + osFamily: "oracle", + osVersion: "5", + expected: false, + }, + "oracle6": { + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + osFamily: "oracle", + osVersion: "6", + expected: true, + }, + "oracle7": { + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + osFamily: "oracle", + osVersion: "7", + expected: true, + }, + "oracle7.6": { + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + osFamily: "oracle", + osVersion: "7.6", + expected: true, + }, + "oracle8": { + now: time.Date(2029, 7, 18, 23, 59, 58, 59, time.UTC), + osFamily: "oracle", + osVersion: "8", + expected: true, + }, + "oracle8-same-time": { + now: time.Date(2029, 7, 18, 23, 59, 59, 0, time.UTC), + osFamily: "oracle", + osVersion: "8", + expected: false, + }, + "latest": { + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + osFamily: "oracle", + osVersion: "latest", + expected: true, + }, + } + + for testName, tt := range tests { + s := NewScanner() + t.Run(testName, func(t *testing.T) { + ctx := clock.With(context.Background(), tt.now) + actual := s.IsSupportedVersion(ctx, tt.osFamily, tt.osVersion) + if actual != tt.expected { + t.Errorf("[%s] got %v, want %v", testName, actual, tt.expected) + } + }) + } + +} + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "detected", + fixtures: []string{ + "testdata/fixtures/oracle7.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "7", + pkgs: []ftypes.Package{ + { + Name: "curl", + Version: "7.29.0", + Release: "59.0.1.el7", + Arch: "x86_64", + SrcName: "curl", + SrcVersion: "7.29.0", + SrcRelease: "59.0.1.el7", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-8177", + PkgName: "curl", + InstalledVersion: "7.29.0-59.0.1.el7", + FixedVersion: "7.29.0-59.0.1.el7_9.1", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.OracleOVAL, + Name: "Oracle Linux OVAL definitions", + URL: "https://linux.oracle.com/security/oval/", + }, + }, + }, + }, + { + name: "without ksplice", + fixtures: []string{ + "testdata/fixtures/oracle7.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "7", + pkgs: []ftypes.Package{ + { + Name: "glibc", + Version: "2.17", + Release: "317.0.1.el7", + Arch: "x86_64", + SrcName: "glibc", + SrcVersion: "2.17", + SrcRelease: "317.0.1.el7", + }, + }, + }, + want: nil, + }, + { + name: "the installed version has ksplice2", + fixtures: []string{ + "testdata/fixtures/oracle7.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "7", + pkgs: []ftypes.Package{ + { + Name: "glibc", + Epoch: 2, + Version: "2.28", + Release: "151.0.1.ksplice2.el8", + Arch: "x86_64", + SrcEpoch: 2, + SrcName: "glibc", + SrcVersion: "2.28", + SrcRelease: "151.0.1.ksplice2.el8", + }, + }, + }, + want: nil, + }, + { + name: "with ksplice", + fixtures: []string{ + "testdata/fixtures/oracle7.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "7", + pkgs: []ftypes.Package{ + { + Name: "glibc", + Epoch: 2, + Version: "2.17", + Release: "156.ksplice1.el7", + Arch: "x86_64", + SrcEpoch: 2, + SrcName: "glibc", + SrcVersion: "2.17", + SrcRelease: "156.ksplice1.el7", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2017-1000364", + PkgName: "glibc", + InstalledVersion: "2:2.17-156.ksplice1.el7", + FixedVersion: "2:2.17-157.ksplice1.el7_3.4", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.OracleOVAL, + Name: "Oracle Linux OVAL definitions", + URL: "https://linux.oracle.com/security/oval/", + }, + }, + }, + }, + { + name: "malformed", + fixtures: []string{ + "testdata/fixtures/invalid-type.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "7", + pkgs: []ftypes.Package{ + { + Name: "curl", + Version: "7.29.0", + Release: "59.0.1.el7", + Arch: "x86_64", + SrcName: "curl", + SrcVersion: "7.29.0", + SrcRelease: "59.0.1.el7", + }, + }, + }, + wantErr: "failed to unmarshal advisory JSON", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := NewScanner() + got, err := s.Detect(tt.args.osVer, nil, tt.args.pkgs) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/oracle/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/oracle/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..996b4c870c39 --- /dev/null +++ b/pkg/detector/ospkg/oracle/testdata/fixtures/data-source.yaml @@ -0,0 +1,7 @@ +- bucket: data-source + pairs: + - key: Oracle Linux 7 + value: + ID: "oracle-oval" + Name: "Oracle Linux OVAL definitions" + URL: "https://linux.oracle.com/security/oval/" \ No newline at end of file diff --git a/pkg/detector/ospkg/oracle/testdata/fixtures/invalid-type.yaml b/pkg/detector/ospkg/oracle/testdata/fixtures/invalid-type.yaml new file mode 100644 index 000000000000..9b6ca8c40e3c --- /dev/null +++ b/pkg/detector/ospkg/oracle/testdata/fixtures/invalid-type.yaml @@ -0,0 +1,9 @@ +- bucket: Oracle Linux 7 + pairs: + - bucket: curl + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/oracle/testdata/fixtures/oracle7.yaml b/pkg/detector/ospkg/oracle/testdata/fixtures/oracle7.yaml new file mode 100644 index 000000000000..47c9931d8f1c --- /dev/null +++ b/pkg/detector/ospkg/oracle/testdata/fixtures/oracle7.yaml @@ -0,0 +1,12 @@ +- bucket: Oracle Linux 7 + pairs: + - bucket: curl + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: "7.29.0-59.0.1.el7_9.1" + - bucket: glibc + pairs: + - key: CVE-2017-1000364 + value: + FixedVersion: "2:2.17-157.ksplice1.el7_3.4" diff --git a/pkg/detector/ospkg/photon/photon.go b/pkg/detector/ospkg/photon/photon.go new file mode 100644 index 000000000000..b6c00f3c24f8 --- /dev/null +++ b/pkg/detector/ospkg/photon/photon.go @@ -0,0 +1,80 @@ +package photon + +import ( + "context" + "time" + + version "github.com/knqyf263/go-rpm-version" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/photon" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + eolDates = map[string]time.Time{ + "1.0": time.Date(2022, 2, 28, 23, 59, 59, 0, time.UTC), + "2.0": time.Date(2022, 12, 31, 23, 59, 59, 0, time.UTC), + // The following versions don't have the EOL dates yet. + // See https://blogs.vmware.com/vsphere/2022/01/photon-1-x-end-of-support-announcement.html + "3.0": time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC), + "4.0": time.Date(2025, 12, 31, 23, 59, 59, 0, time.UTC), + } +) + +// Scanner implements the Photon scanner +type Scanner struct { + vs photon.VulnSrc +} + +// NewScanner is the factory method for Scanner +func NewScanner() *Scanner { + return &Scanner{ + vs: photon.NewVulnSrc(), + } +} + +// Detect scans and returns vulnerabilities using photon scanner +func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting Photon Linux vulnerabilities...") + log.Logger.Debugf("Photon Linux: os version: %s", osVer) + log.Logger.Debugf("Photon Linux: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + advisories, err := s.vs.Get(osVer, pkg.SrcName) + if err != nil { + return nil, xerrors.Errorf("failed to get Photon Linux advisory: %w", err) + } + + installed := utils.FormatVersion(pkg) + installedVersion := version.NewVersion(installed) + for _, adv := range advisories { + fixedVersion := version.NewVersion(adv.FixedVersion) + vuln := types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: installed, + PkgIdentifier: pkg.Identifier, + Layer: pkg.Layer, + Custom: adv.Custom, + DataSource: adv.DataSource, + } + if installedVersion.LessThan(fixedVersion) { + vuln.FixedVersion = adv.FixedVersion + vulns = append(vulns, vuln) + } + } + } + return vulns, nil +} + +// IsSupportedVersion checks if the version is supported. +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + return osver.Supported(ctx, eolDates, osFamily, osVer) +} diff --git a/pkg/detector/ospkg/photon/photon_test.go b/pkg/detector/ospkg/photon/photon_test.go new file mode 100644 index 000000000000..b81b0fd30d6d --- /dev/null +++ b/pkg/detector/ospkg/photon/photon_test.go @@ -0,0 +1,156 @@ +package photon_test + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/clock" + "testing" + "time" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/photon" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/photon.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "1.0", + pkgs: []ftypes.Package{ + { + Name: "PyYAML", + Version: "3.12", + Release: "4.ph1", + SrcName: "PyYAML", + SrcVersion: "3.12", + SrcRelease: "4.ph1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "PyYAML", + VulnerabilityID: "CVE-2020-1747", + InstalledVersion: "3.12-4.ph1", + FixedVersion: "3.12-5.ph1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Photon, + Name: "Photon OS CVE metadata", + URL: "https://packages.vmware.com/photon/photon_cve_metadata/", + }, + }, + }, + }, + { + name: "invalid bucket", + fixtures: []string{ + "testdata/fixtures/invalid.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "1.0", + pkgs: []ftypes.Package{ + { + Name: "PyYAML", + Version: "3.12", + SrcName: "PyYAML", + SrcVersion: "3.12", + }, + }, + }, + wantErr: "failed to get Photon advisories", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := photon.NewScanner() + got, err := s.Detect(tt.args.osVer, nil, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestScanner_IsSupportedVersion(t *testing.T) { + type args struct { + osFamily ftypes.OSType + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool + }{ + { + name: "photon 1.0", + now: time.Date(2022, 1, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "photon", + osVer: "1.0", + }, + want: true, + }, + { + name: "photon 1.0 EOL", + now: time.Date(2022, 3, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "photon", + osVer: "1.0", + }, + want: false, + }, + { + name: "latest", + now: time.Date(2022, 1, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "photon", + osVer: "999.0", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(context.Background(), tt.now) + s := photon.NewScanner() + got := s.IsSupportedVersion(ctx, tt.args.osFamily, tt.args.osVer) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/photon/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/photon/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..b21fda699325 --- /dev/null +++ b/pkg/detector/ospkg/photon/testdata/fixtures/data-source.yaml @@ -0,0 +1,7 @@ +- bucket: data-source + pairs: + - key: Photon OS 1.0 + value: + ID: "photon" + Name: "Photon OS CVE metadata" + URL: "https://packages.vmware.com/photon/photon_cve_metadata/" \ No newline at end of file diff --git a/pkg/detector/ospkg/photon/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/photon/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..43af3dde0b04 --- /dev/null +++ b/pkg/detector/ospkg/photon/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: Photon OS 1.0 + pairs: + - bucket: PyYAML + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar \ No newline at end of file diff --git a/pkg/detector/ospkg/photon/testdata/fixtures/photon.yaml b/pkg/detector/ospkg/photon/testdata/fixtures/photon.yaml new file mode 100644 index 000000000000..665dd8331965 --- /dev/null +++ b/pkg/detector/ospkg/photon/testdata/fixtures/photon.yaml @@ -0,0 +1,10 @@ +- bucket: Photon OS 1.0 + pairs: + - bucket: PyYAML + pairs: + - key: CVE-2017-18342 + value: + FixedVersion: "3.12-3.ph1" + - key: CVE-2020-1747 + value: + FixedVersion: "3.12-5.ph1" diff --git a/pkg/detector/ospkg/redhat/redhat.go b/pkg/detector/ospkg/redhat/redhat.go new file mode 100644 index 000000000000..fe7581dbf481 --- /dev/null +++ b/pkg/detector/ospkg/redhat/redhat.go @@ -0,0 +1,220 @@ +package redhat + +import ( + "context" + "fmt" + "sort" + "strings" + "time" + + version "github.com/knqyf263/go-rpm-version" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + ustrings "github.com/aquasecurity/trivy-db/pkg/utils/strings" + redhat "github.com/aquasecurity/trivy-db/pkg/vulnsrc/redhat-oval" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + defaultContentSets = map[string][]string{ + "6": { + "rhel-6-server-rpms", + "rhel-6-server-extras-rpms", + }, + "7": { + "rhel-7-server-rpms", + "rhel-7-server-extras-rpms", + }, + "8": { + "rhel-8-for-x86_64-baseos-rpms", + "rhel-8-for-x86_64-appstream-rpms", + }, + "9": { + "rhel-9-for-x86_64-baseos-rpms", + "rhel-9-for-x86_64-appstream-rpms", + }, + } + redhatEOLDates = map[string]time.Time{ + "4": time.Date(2017, 5, 31, 23, 59, 59, 0, time.UTC), + "5": time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC), + "6": time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC), + // N/A + "7": time.Date(3000, 1, 1, 23, 59, 59, 0, time.UTC), + "8": time.Date(3000, 1, 1, 23, 59, 59, 0, time.UTC), + "9": time.Date(3000, 1, 1, 23, 59, 59, 0, time.UTC), + } + centosEOLDates = map[string]time.Time{ + "3": time.Date(2010, 10, 31, 23, 59, 59, 0, time.UTC), + "4": time.Date(2012, 2, 29, 23, 59, 59, 0, time.UTC), + "5": time.Date(2017, 3, 31, 23, 59, 59, 0, time.UTC), + "6": time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC), + "7": time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC), + "8": time.Date(2021, 12, 31, 23, 59, 59, 0, time.UTC), + } + excludedVendorsSuffix = []string{ + ".remi", + } +) + +// Scanner implements the RedHat scanner +type Scanner struct { + vs redhat.VulnSrc +} + +// NewScanner is the factory method for Scanner +func NewScanner() *Scanner { + return &Scanner{ + vs: redhat.NewVulnSrc(), + } +} + +// Detect scans and returns redhat vulnerabilities +func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting RHEL/CentOS vulnerabilities...") + + osVer = osver.Major(osVer) + log.Logger.Debugf("Red Hat: os version: %s", osVer) + log.Logger.Debugf("Red Hat: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + if !isFromSupportedVendor(pkg) { + log.Logger.Debugf("Skipping %s: unsupported vendor", pkg.Name) + continue + } + + detectedVulns, err := s.detect(osVer, pkg) + if err != nil { + return nil, xerrors.Errorf("redhat vulnerability detection error: %w", err) + } + vulns = append(vulns, detectedVulns...) + } + return vulns, nil +} + +func (s *Scanner) detect(osVer string, pkg ftypes.Package) ([]types.DetectedVulnerability, error) { + // For Red Hat OVAL v2 containing only binary package names + pkgName := addModularNamespace(pkg.Name, pkg.Modularitylabel) + + var contentSets []string + var nvr string + if pkg.BuildInfo == nil { + contentSets = defaultContentSets[osVer] + } else { + contentSets = pkg.BuildInfo.ContentSets + nvr = fmt.Sprintf("%s-%s", pkg.BuildInfo.Nvr, pkg.BuildInfo.Arch) + } + + advisories, err := s.vs.Get(pkgName, contentSets, []string{nvr}) + if err != nil { + return nil, xerrors.Errorf("failed to get Red Hat advisories: %w", err) + } + + installed := utils.FormatVersion(pkg) + installedVersion := version.NewVersion(installed) + + uniqVulns := make(map[string]types.DetectedVulnerability) + for _, adv := range advisories { + // if Arches for advisory is empty or pkg.Arch is "noarch", then any Arches are affected + if len(adv.Arches) != 0 && pkg.Arch != "noarch" { + if !slices.Contains(adv.Arches, pkg.Arch) { + continue + } + } + + vulnID := adv.VulnerabilityID + vuln := types.DetectedVulnerability{ + VulnerabilityID: vulnID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: utils.FormatVersion(pkg), + PkgIdentifier: pkg.Identifier, + Status: adv.Status, + Layer: pkg.Layer, + SeveritySource: vulnerability.RedHat, + Vulnerability: dbTypes.Vulnerability{ + Severity: adv.Severity.String(), + }, + Custom: adv.Custom, + } + + // unpatched vulnerabilities + if adv.FixedVersion == "" { + // Red Hat may contain several advisories for the same vulnerability (RHSA advisories). + // To avoid overwriting the fixed version by mistake, we should skip unpatched vulnerabilities if they were added earlier + if _, ok := uniqVulns[vulnID]; !ok { + uniqVulns[vulnID] = vuln + } + continue + } + + // patched vulnerabilities + fixedVersion := version.NewVersion(adv.FixedVersion) + if installedVersion.LessThan(fixedVersion) { + vuln.VendorIDs = adv.VendorIDs + vuln.FixedVersion = fixedVersion.String() + + if v, ok := uniqVulns[vulnID]; ok { + // In case two advisories resolve the same CVE-ID. + // e.g. The first fix might be incomplete. + v.VendorIDs = ustrings.Unique(append(v.VendorIDs, vuln.VendorIDs...)) + + // The newer fixed version should be taken. + if version.NewVersion(v.FixedVersion).LessThan(fixedVersion) { + v.FixedVersion = vuln.FixedVersion + } + uniqVulns[vulnID] = v + } else { + uniqVulns[vulnID] = vuln + } + } + } + + vulns := maps.Values(uniqVulns) + sort.Slice(vulns, func(i, j int) bool { + return vulns[i].VulnerabilityID < vulns[j].VulnerabilityID + }) + + return vulns, nil +} + +// IsSupportedVersion checks is OSFamily can be scanned with Redhat scanner +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + osVer = osver.Major(osVer) + if osFamily == ftypes.CentOS { + return osver.Supported(ctx, centosEOLDates, osFamily, osVer) + } + + return osver.Supported(ctx, redhatEOLDates, osFamily, osVer) +} + +func isFromSupportedVendor(pkg ftypes.Package) bool { + for _, suffix := range excludedVendorsSuffix { + if strings.HasSuffix(pkg.Release, suffix) { + return false + } + } + return true +} + +func addModularNamespace(name, label string) string { + // e.g. npm, nodejs:12:8030020201124152102:229f0a1c => nodejs:12::npm + var count int + for i, r := range label { + if r == ':' { + count++ + } + if count == 2 { + return label[:i] + "::" + name + } + } + return name +} diff --git a/pkg/detector/ospkg/redhat/redhat_test.go b/pkg/detector/ospkg/redhat/redhat_test.go new file mode 100644 index 000000000000..3910b87f9cac --- /dev/null +++ b/pkg/detector/ospkg/redhat/redhat_test.go @@ -0,0 +1,444 @@ +package redhat_test + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/clock" + "os" + "testing" + "time" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMain(m *testing.M) { + log.InitLogger(false, false) + os.Exit(m.Run()) +} + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + fixtures []string + args args + want []types.DetectedVulnerability + wantErr bool + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/redhat.yaml", + "testdata/fixtures/cpe.yaml", + }, + args: args{ + osVer: "7.6", + pkgs: []ftypes.Package{ + { + Name: "vim-minimal", + Version: "7.4.160", + Release: "5.el7", + Epoch: 2, + Arch: "x86_64", + SrcName: "vim", + SrcVersion: "7.4.160", + SrcRelease: "5.el7", + SrcEpoch: 2, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + BuildInfo: &ftypes.BuildInfo{ + ContentSets: []string{"rhel-7-server-rpms"}, + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2017-5953", + PkgName: "vim-minimal", + InstalledVersion: "2:7.4.160-5.el7", + Status: dbTypes.StatusWillNotFix, + SeveritySource: vulnerability.RedHat, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityLow.String(), + }, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + { + VulnerabilityID: "CVE-2019-12735", + VendorIDs: []string{"RHSA-2019:1619"}, + PkgName: "vim-minimal", + InstalledVersion: "2:7.4.160-5.el7", + FixedVersion: "2:7.4.160-6.el7_6", + SeveritySource: vulnerability.RedHat, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + { + name: "happy path: multiple RHSA-IDs", + fixtures: []string{ + "testdata/fixtures/redhat.yaml", + "testdata/fixtures/cpe.yaml", + }, + args: args{ + osVer: "7.5", + pkgs: []ftypes.Package{ + { + Name: "nss", + Version: "3.36.0", + Release: "7.1.el7_6", + Epoch: 0, + Arch: "x86_64", + SrcName: "nss", + SrcVersion: "3.36.0", + SrcRelease: "7.4.160", + SrcEpoch: 0, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + BuildInfo: &ftypes.BuildInfo{ + ContentSets: []string{"rhel-7-server-rpms"}, + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-17007", + VendorIDs: []string{"RHSA-2021:0876"}, + PkgName: "nss", + InstalledVersion: "3.36.0-7.1.el7_6", + FixedVersion: "3.36.0-9.el7_6", + SeveritySource: vulnerability.RedHat, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityMedium.String(), + }, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + { + VulnerabilityID: "CVE-2020-12403", + VendorIDs: []string{ + "RHSA-2021:0538", + "RHSA-2021:0876", + }, + PkgName: "nss", + InstalledVersion: "3.36.0-7.1.el7_6", + FixedVersion: "3.53.1-17.el7_3", + SeveritySource: vulnerability.RedHat, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + { + name: "happy path: package without architecture", + fixtures: []string{ + "testdata/fixtures/redhat.yaml", + "testdata/fixtures/cpe.yaml", + }, + args: args{ + osVer: "7.6", + pkgs: []ftypes.Package{ + { + Name: "kernel-headers", + Version: "3.10.0-1127.19", + Release: "1.el7", + Epoch: 0, + Arch: "noarch", + SrcName: "kernel-headers", + SrcVersion: "3.10.0-1127.19", + SrcRelease: "1.el7", + SrcEpoch: 0, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + BuildInfo: &ftypes.BuildInfo{ + ContentSets: []string{"rhel-7-server-rpms"}, + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2016-5195", + VendorIDs: []string{"RHSA-2017:0372"}, + PkgName: "kernel-headers", + InstalledVersion: "3.10.0-1127.19-1.el7", + FixedVersion: "4.5.0-15.2.1.el7", + SeveritySource: vulnerability.RedHat, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + { + name: "happy path: advisories have different arches", + fixtures: []string{ + "testdata/fixtures/redhat.yaml", + "testdata/fixtures/cpe.yaml", + }, + args: args{ + osVer: "7.6", + pkgs: []ftypes.Package{ + { + Name: "kernel-headers", + Version: "3.10.0-326.36", + Release: "3.el7", + Epoch: 0, + Arch: "x86_64", + SrcName: "kernel-headers", + SrcVersion: "3.10.0-326.36", + SrcRelease: "3.el7", + SrcEpoch: 0, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + BuildInfo: &ftypes.BuildInfo{ + ContentSets: []string{"rhel-7-server-rpms"}, + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2016-5195", + VendorIDs: []string{"RHSA-2016:2098"}, + PkgName: "kernel-headers", + InstalledVersion: "3.10.0-326.36-3.el7", + FixedVersion: "3.10.0-327.36.3.el7", + SeveritySource: vulnerability.RedHat, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + { + name: "no build info", + fixtures: []string{ + "testdata/fixtures/redhat.yaml", + "testdata/fixtures/cpe.yaml", + }, + args: args{ + osVer: "8.3", + pkgs: []ftypes.Package{ + { + Name: "vim-minimal", + Version: "7.4.160", + Release: "5.el8", + Epoch: 2, + Arch: "x86_64", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-12735", + VendorIDs: []string{"RHSA-2019:1619"}, + PkgName: "vim-minimal", + InstalledVersion: "2:7.4.160-5.el8", + FixedVersion: "2:7.4.160-7.el8_7", + SeveritySource: vulnerability.RedHat, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityMedium.String(), + }, + }, + }, + }, + { + name: "modular packages", + fixtures: []string{ + "testdata/fixtures/redhat.yaml", + "testdata/fixtures/cpe.yaml", + }, + args: args{ + osVer: "8.3", + pkgs: []ftypes.Package{ + { + Name: "php", + Version: "7.2.10", + Release: "1.module_el8.2.0+313+b04d0a66", + Arch: "x86_64", + SrcName: "php", + SrcVersion: "7.2.10", + SrcRelease: "1.module_el8.2.0+313+b04d0a66", + Modularitylabel: "php:7.2:8020020200507003613:2c7ca891", + Layer: ftypes.Layer{ + DiffID: "sha256:3e968ecc016e1b9aa19023798229bf2d25c813d1bf092533f38b056aff820524", + }, + BuildInfo: &ftypes.BuildInfo{ + Nvr: "ubi8-init-container-8.0-7", + Arch: "x86_64", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-11043", + VendorIDs: []string{"RHSA-2020:0322"}, + PkgName: "php", + InstalledVersion: "7.2.10-1.module_el8.2.0+313+b04d0a66", + FixedVersion: "7.2.11-1.1.module+el8.0.0+4664+17bd8d65", + SeveritySource: vulnerability.RedHat, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + Layer: ftypes.Layer{ + DiffID: "sha256:3e968ecc016e1b9aa19023798229bf2d25c813d1bf092533f38b056aff820524", + }, + }, + }, + }, + { + name: "packages from remi repository are skipped", + args: args{ + osVer: "7.6", + pkgs: []ftypes.Package{ + { + Name: "php", + Version: "7.3.23", + Release: "1.el7.remi", + Arch: "x86_64", + BuildInfo: &ftypes.BuildInfo{ + ContentSets: []string{"rhel-7-server-rpms"}, + }, + }, + }, + }, + want: []types.DetectedVulnerability(nil), + }, + { + name: "broken value", + fixtures: []string{ + "testdata/fixtures/invalid-type.yaml", + "testdata/fixtures/cpe.yaml", + }, + args: args{ + osVer: "7", + pkgs: []ftypes.Package{ + { + Name: "nss", + Version: "3.36.0", + Release: "7.1.el7_6", + Arch: "x86_64", + BuildInfo: &ftypes.BuildInfo{ + ContentSets: []string{"rhel-7-server-rpms"}, + }, + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dbtest.InitDB(t, tt.fixtures) + defer func() { _ = dbtest.Close() }() + + s := redhat.NewScanner() + got, err := s.Detect(tt.args.osVer, nil, tt.args.pkgs) + require.Equal(t, tt.wantErr, err != nil, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestScanner_IsSupportedVersion(t *testing.T) { + type args struct { + osFamily ftypes.OSType + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool + }{ + { + name: "centos 6", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "centos", + osVer: "6.8", + }, + want: true, + }, + { + name: "centos 6 EOL", + now: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC), + args: args{ + osFamily: "centos", + osVer: "6.7", + }, + want: false, + }, + { + name: "two dots", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "centos", + osVer: "8.0.1", + }, + want: true, + }, + { + name: "rhel 8", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "redhat", + osVer: "8.0", + }, + want: true, + }, + { + name: "latest", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "redhat", + osVer: "999.0", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(context.Background(), tt.now) + s := redhat.NewScanner() + got := s.IsSupportedVersion(ctx, tt.args.osFamily, tt.args.osVer) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/redhat/testdata/fixtures/cpe.yaml b/pkg/detector/ospkg/redhat/testdata/fixtures/cpe.yaml new file mode 100644 index 000000000000..806e6ea62523 --- /dev/null +++ b/pkg/detector/ospkg/redhat/testdata/fixtures/cpe.yaml @@ -0,0 +1,32 @@ +- bucket: Red Hat CPE + pairs: + - bucket: repository + pairs: + - key: "rhel-8-for-x86_64-baseos-rpms" + value: + - 2 + - 4 + - key: "3scale-amp-2-rpms-for-rhel-8-x86_64-debug-rpms" + value: + - 4 + - key: "rhel-7-server-rpms" + value: + - 0 + - bucket: nvr + pairs: + - key: "ubi8-init-container-8.0-7-x86_64" + value: + - 2 + - 3 + - bucket: cpe + pairs: + - key: "0" + value: "cpe:/o:redhat:enterprise_linux:7::server" + - key: "1" + value: "cpe:/o:redhat:enterprise_linux:7::client" + - key: "2" + value: "cpe:/a:redhat:enterprise_linux:8" + - key: "3" + value: "cpe:/a:redhat:enterprise_linux:8::appstream" + - key: "4" + value: "cpe:/o:redhat:enterprise_linux:8::baseos" diff --git a/pkg/detector/ospkg/redhat/testdata/fixtures/invalid-type.yaml b/pkg/detector/ospkg/redhat/testdata/fixtures/invalid-type.yaml new file mode 100644 index 000000000000..1c9ed1eeaa2f --- /dev/null +++ b/pkg/detector/ospkg/redhat/testdata/fixtures/invalid-type.yaml @@ -0,0 +1,7 @@ +- bucket: Red Hat + pairs: + - bucket: nss + pairs: + - key: RHSA-2021:0538 + value: + Entries: broken \ No newline at end of file diff --git a/pkg/detector/ospkg/redhat/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/redhat/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..095230aa7d7a --- /dev/null +++ b/pkg/detector/ospkg/redhat/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: Red Hat Enterprise Linux 6 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/redhat/testdata/fixtures/redhat.yaml b/pkg/detector/ospkg/redhat/testdata/fixtures/redhat.yaml new file mode 100644 index 000000000000..c3ba48072111 --- /dev/null +++ b/pkg/detector/ospkg/redhat/testdata/fixtures/redhat.yaml @@ -0,0 +1,107 @@ +- bucket: Red Hat + pairs: + - bucket: vim-minimal + pairs: + - key: RHSA-2019:1619 + value: + Entries: + - FixedVersion: 2:7.4.160-6.el7_6 + Affected: + - 0 + - 1 + Cves: + - ID: CVE-2019-12735 + Severity: 3 + - FixedVersion: 2:7.4.160-7.el8_7 + Affected: + - 2 + - 3 + Cves: + - ID: CVE-2019-12735 + Severity: 2 + - key: CVE-2017-5953 + value: + Entries: + - FixedVersion: "" + Status: 5 + Affected: + - 0 + - 1 + Cves: + - Severity: 1 + - bucket: nss + pairs: + - key: RHSA-2021:0538 + value: + Entries: + - FixedVersion: 0:3.53.1-17.el7_3 + Affected: + - 0 + - 1 + Cves: + - ID: CVE-2020-12403 + Severity: 3 + - key: RHSA-2021:0876 + value: + Entries: + - FixedVersion: 0:3.36.0-9.el7_6 + Affected: + - 0 + - 1 + Cves: + - ID: CVE-2019-17007 + Severity: 2 + - ID: CVE-2020-12403 + Severity: 3 + - bucket: "php:7.2::php" + pairs: + - key: RHSA-2020:0322 + value: + Entries: + - FixedVersion: "0:7.2.11-1.1.module+el8.0.0+4664+17bd8d65" + Affected: + - 2 + - 3 + Cves: + - ID: CVE-2019-11043 + Severity: 4 + - bucket: php + pairs: + - key: CVE-2006-4023 + value: + Entries: + - FixedVersion: "" + Affected: + - 0 + - 1 + Cves: + - Severity: 1 + - bucket: kernel-headers + pairs: + - key: RHSA-2016:2098 + value: + Entries: + - FixedVersion: 0:3.10.0-327.36.3.el7 + Affected: + - 0 + - 1 + Cves: + - ID: CVE-2016-5195 + Severity: 3 + Arches: + - ppc64 + - ppc64le + - s390x + - x86_64 + - key: RHSA-2017:0372 + value: + Entries: + - FixedVersion: 0:4.5.0-15.2.1.el7 + Affected: + - 0 + - 1 + Cves: + - ID: CVE-2016-5195 + Severity: 3 + Arches: + - aarch64 \ No newline at end of file diff --git a/pkg/detector/ospkg/rocky/rocky.go b/pkg/detector/ospkg/rocky/rocky.go new file mode 100644 index 000000000000..49aaa4d0a543 --- /dev/null +++ b/pkg/detector/ospkg/rocky/rocky.go @@ -0,0 +1,105 @@ +package rocky + +import ( + "context" + "time" + + version "github.com/knqyf263/go-rpm-version" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/rocky" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + eolDates = map[string]time.Time{ + // Source: + // https://endoflife.date/rocky-linux + "8": time.Date(2029, 5, 31, 23, 59, 59, 0, time.UTC), + "9": time.Date(2032, 5, 31, 23, 59, 59, 0, time.UTC), + } +) + +// Scanner implements the Rocky Linux scanner +type Scanner struct { + vs *rocky.VulnSrc +} + +// NewScanner is the factory method for Scanner +func NewScanner() *Scanner { + return &Scanner{ + vs: rocky.NewVulnSrc(), + } +} + +// Detect vulnerabilities in package using Rocky Linux scanner +func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting Rocky Linux vulnerabilities...") + + osVer = osver.Major(osVer) + log.Logger.Debugf("Rocky Linux: os version: %s", osVer) + log.Logger.Debugf("Rocky Linux: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + var skipPkgs []string + for _, pkg := range pkgs { + if pkg.Modularitylabel != "" { + skipPkgs = append(skipPkgs, pkg.Name) + continue + } + pkgName := addModularNamespace(pkg.Name, pkg.Modularitylabel) + advisories, err := s.vs.Get(osVer, pkgName, pkg.Arch) + if err != nil { + return nil, xerrors.Errorf("failed to get Rocky Linux advisories: %w", err) + } + + installed := utils.FormatVersion(pkg) + installedVersion := version.NewVersion(installed) + + for _, adv := range advisories { + fixedVersion := version.NewVersion(adv.FixedVersion) + if installedVersion.LessThan(fixedVersion) { + vuln := types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: installed, + FixedVersion: fixedVersion.String(), + PkgIdentifier: pkg.Identifier, + Layer: pkg.Layer, + DataSource: adv.DataSource, + Custom: adv.Custom, + } + vulns = append(vulns, vuln) + } + } + } + if len(skipPkgs) > 0 { + log.Logger.Infof("Skipped detection of these packages: %q because modular packages cannot be detected correctly due to a bug in Rocky Linux Errata. See also: https://forums.rockylinux.org/t/some-errata-missing-in-comparison-with-rhel-and-almalinux/3843", skipPkgs) + } + + return vulns, nil +} + +// IsSupportedVersion checks if the version is supported. +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + return osver.Supported(ctx, eolDates, osFamily, osver.Major(osVer)) +} + +func addModularNamespace(name, label string) string { + // e.g. npm, nodejs:12:8030020201124152102:229f0a1c => nodejs:12::npm + var count int + for i, r := range label { + if r == ':' { + count++ + } + if count == 2 { + return label[:i] + "::" + name + } + } + return name +} diff --git a/pkg/detector/ospkg/rocky/rocky_test.go b/pkg/detector/ospkg/rocky/rocky_test.go new file mode 100644 index 000000000000..dddba1df850a --- /dev/null +++ b/pkg/detector/ospkg/rocky/rocky_test.go @@ -0,0 +1,184 @@ +package rocky_test + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/clock" + "testing" + "time" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/rocky" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/rocky.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "8.5", + pkgs: []ftypes.Package{ + { + Name: "bpftool", + Epoch: 0, + Version: "4.18.0", + Release: "348.el8.0.3", + Arch: "aarch64", + SrcName: "kernel", + SrcEpoch: 0, + SrcVersion: "4.18.0", + SrcRelease: "348.el8.0.3", + Modularitylabel: "", + Licenses: []string{"GPLv2"}, + Layer: ftypes.Layer{}, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "bpftool", + VulnerabilityID: "CVE-2021-20317", + InstalledVersion: "4.18.0-348.el8.0.3", + FixedVersion: "5.18.0-348.2.1.el8_5", + Layer: ftypes.Layer{}, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Rocky, + Name: "Rocky Linux updateinfo", + URL: "https://download.rockylinux.org/pub/rocky/", + }, + }, + }, + }, + { + name: "skip modular package", + fixtures: []string{ + "testdata/fixtures/modular.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "8.5", + pkgs: []ftypes.Package{ + { + Name: "nginx", + Epoch: 1, + Version: "1.16.1", + Release: "2.module+el8.4.0+543+efbf198b.0", + Arch: "x86_64", + SrcName: "nginx", + SrcEpoch: 1, + SrcVersion: "1.16.1", + SrcRelease: "2.module+el8.4.0+543+efbf198b.0", + Modularitylabel: "nginx:1.16:8040020210610090125:9f9e2e7e", + Licenses: []string{"BSD"}, + Layer: ftypes.Layer{}, + }, + }, + }, + want: nil, + }, + { + name: "Get returns an error", + fixtures: []string{ + "testdata/fixtures/invalid.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "8.5", + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.5-12", + SrcName: "jq", + SrcVersion: "1.5-12", + }, + }, + }, + wantErr: "failed to get Rocky Linux advisories", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := rocky.NewScanner() + got, err := s.Detect(tt.args.osVer, nil, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestScanner_IsSupportedVersion(t *testing.T) { + type args struct { + osFamily ftypes.OSType + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool + }{ + { + name: "rocky 8.5", + now: time.Date(2019, 3, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "rocky", + osVer: "8.5", + }, + want: true, + }, + { + name: "rocky 8.5 with EOL", + now: time.Date(2029, 6, 1, 0, 0, 0, 0, time.UTC), + args: args{ + osFamily: "rocky", + osVer: "8.5", + }, + want: false, + }, + { + name: "latest", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "rocky", + osVer: "999.0", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(context.Background(), tt.now) + s := rocky.NewScanner() + got := s.IsSupportedVersion(ctx, tt.args.osFamily, tt.args.osVer) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/rocky/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/rocky/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..67713f60e158 --- /dev/null +++ b/pkg/detector/ospkg/rocky/testdata/fixtures/data-source.yaml @@ -0,0 +1,7 @@ +- bucket: data-source + pairs: + - key: rocky 8 + value: + ID: "rocky" + Name: "Rocky Linux updateinfo" + URL: "https://download.rockylinux.org/pub/rocky/" \ No newline at end of file diff --git a/pkg/detector/ospkg/rocky/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/rocky/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..883c443e8520 --- /dev/null +++ b/pkg/detector/ospkg/rocky/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: rocky 8 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar \ No newline at end of file diff --git a/pkg/detector/ospkg/rocky/testdata/fixtures/modular.yaml b/pkg/detector/ospkg/rocky/testdata/fixtures/modular.yaml new file mode 100644 index 000000000000..32858f14d62f --- /dev/null +++ b/pkg/detector/ospkg/rocky/testdata/fixtures/modular.yaml @@ -0,0 +1,7 @@ +- bucket: rocky 8 + pairs: + - bucket: nginx:1.16::nginx # actual: bucket of modular package is not created. ref: https://github.com/aquasecurity/trivy-db/pull/154 + pairs: + - key: CVE-2021-23017 + value: + FixedVersion: "1:1.16.1-2.module+el8.4.0+543+efbf198b.1" \ No newline at end of file diff --git a/pkg/detector/ospkg/rocky/testdata/fixtures/rocky.yaml b/pkg/detector/ospkg/rocky/testdata/fixtures/rocky.yaml new file mode 100644 index 000000000000..3956f0a1632d --- /dev/null +++ b/pkg/detector/ospkg/rocky/testdata/fixtures/rocky.yaml @@ -0,0 +1,18 @@ +- bucket: rocky 8 + pairs: + - bucket: bpftool + pairs: + - key: CVE-2021-20317 + value: + FixedVersion: "4.18.0-348.2.1.el8_5" + Entries: + - FixedVersion: "4.18.0-348.2.1.el8_5" + Arches: + - "x86_64" + VendorIDs: + - "RLSA-2021:4647" + - FixedVersion: "5.18.0-348.2.1.el8_5" + Arches: + - "aarch64" + VendorIDs: + - "RLSA-2021:4647" \ No newline at end of file diff --git a/pkg/detector/ospkg/suse/suse.go b/pkg/detector/ospkg/suse/suse.go new file mode 100644 index 000000000000..617161df87aa --- /dev/null +++ b/pkg/detector/ospkg/suse/suse.go @@ -0,0 +1,133 @@ +package suse + +import ( + "context" + "time" + + version "github.com/knqyf263/go-rpm-version" + "golang.org/x/xerrors" + + susecvrf "github.com/aquasecurity/trivy-db/pkg/vulnsrc/suse-cvrf" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + slesEolDates = map[string]time.Time{ + // Source: https://www.suse.com/lifecycle/ + "10": time.Date(2007, 12, 31, 23, 59, 59, 0, time.UTC), + "10.1": time.Date(2008, 11, 30, 23, 59, 59, 0, time.UTC), + "10.2": time.Date(2010, 4, 11, 23, 59, 59, 0, time.UTC), + "10.3": time.Date(2011, 10, 11, 23, 59, 59, 0, time.UTC), + "10.4": time.Date(2013, 7, 31, 23, 59, 59, 0, time.UTC), + "11": time.Date(2010, 12, 31, 23, 59, 59, 0, time.UTC), + "11.1": time.Date(2012, 8, 31, 23, 59, 59, 0, time.UTC), + "11.2": time.Date(2014, 1, 31, 23, 59, 59, 0, time.UTC), + "11.3": time.Date(2016, 1, 31, 23, 59, 59, 0, time.UTC), + "11.4": time.Date(2019, 3, 31, 23, 59, 59, 0, time.UTC), + "12": time.Date(2016, 6, 30, 23, 59, 59, 0, time.UTC), + "12.1": time.Date(2017, 5, 31, 23, 59, 59, 0, time.UTC), + "12.2": time.Date(2018, 3, 31, 23, 59, 59, 0, time.UTC), + "12.3": time.Date(2019, 1, 30, 23, 59, 59, 0, time.UTC), + "12.4": time.Date(2020, 6, 30, 23, 59, 59, 0, time.UTC), + "12.5": time.Date(2024, 10, 31, 23, 59, 59, 0, time.UTC), + "15": time.Date(2019, 12, 31, 23, 59, 59, 0, time.UTC), + "15.1": time.Date(2021, 1, 31, 23, 59, 59, 0, time.UTC), + "15.2": time.Date(2021, 12, 31, 23, 59, 59, 0, time.UTC), + "15.3": time.Date(2022, 12, 31, 23, 59, 59, 0, time.UTC), + "15.4": time.Date(2023, 12, 31, 23, 59, 59, 0, time.UTC), + "15.5": time.Date(2028, 12, 31, 23, 59, 59, 0, time.UTC), + // 6 months after SLES 15 SP7 release + // "15.6": time.Date(2028, 12, 31, 23, 59, 59, 0, time.UTC), + } + + opensuseEolDates = map[string]time.Time{ + // Source: https://en.opensuse.org/Lifetime + "42.1": time.Date(2017, 5, 17, 23, 59, 59, 0, time.UTC), + "42.2": time.Date(2018, 1, 26, 23, 59, 59, 0, time.UTC), + "42.3": time.Date(2019, 6, 30, 23, 59, 59, 0, time.UTC), + "15.0": time.Date(2019, 12, 3, 23, 59, 59, 0, time.UTC), + "15.1": time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC), + "15.2": time.Date(2021, 11, 30, 23, 59, 59, 0, time.UTC), + "15.3": time.Date(2022, 11, 30, 23, 59, 59, 0, time.UTC), + "15.4": time.Date(2023, 11, 30, 23, 59, 59, 0, time.UTC), + "15.5": time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), + } +) + +// Type defines SUSE type +type Type int + +const ( + // SUSEEnterpriseLinux is Linux Enterprise version + SUSEEnterpriseLinux Type = iota + // OpenSUSE for open versions + OpenSUSE +) + +// Scanner implements the SUSE scanner +type Scanner struct { + vs susecvrf.VulnSrc +} + +// NewScanner is the factory method for Scanner +func NewScanner(t Type) *Scanner { + switch t { + case SUSEEnterpriseLinux: + return &Scanner{ + vs: susecvrf.NewVulnSrc(susecvrf.SUSEEnterpriseLinux), + } + case OpenSUSE: + return &Scanner{ + vs: susecvrf.NewVulnSrc(susecvrf.OpenSUSE), + } + } + return nil +} + +// Detect scans and returns the vulnerabilities +func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting SUSE vulnerabilities...") + log.Logger.Debugf("SUSE: os version: %s", osVer) + log.Logger.Debugf("SUSE: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + advisories, err := s.vs.Get(osVer, pkg.Name) + if err != nil { + return nil, xerrors.Errorf("failed to get SUSE advisory: %w", err) + } + + installed := utils.FormatVersion(pkg) + installedVersion := version.NewVersion(installed) + for _, adv := range advisories { + fixedVersion := version.NewVersion(adv.FixedVersion) + vuln := types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: installed, + PkgIdentifier: pkg.Identifier, + Layer: pkg.Layer, + Custom: adv.Custom, + DataSource: adv.DataSource, + } + if installedVersion.LessThan(fixedVersion) { + vuln.FixedVersion = adv.FixedVersion + vulns = append(vulns, vuln) + } + } + } + return vulns, nil +} + +// IsSupportedVersion checks if OSFamily can be scanned using SUSE scanner +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + if osFamily == ftypes.SLES { + return osver.Supported(ctx, slesEolDates, osFamily, osVer) + } + return osver.Supported(ctx, opensuseEolDates, osFamily, osVer) +} diff --git a/pkg/detector/ospkg/suse/suse_test.go b/pkg/detector/ospkg/suse/suse_test.go new file mode 100644 index 000000000000..7766863e2ec4 --- /dev/null +++ b/pkg/detector/ospkg/suse/suse_test.go @@ -0,0 +1,162 @@ +package suse_test + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/clock" + "testing" + "time" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/suse" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + distribution suse.Type + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/suse.yaml", + "testdata/fixtures/data-source.yaml", + }, + distribution: suse.OpenSUSE, + args: args{ + osVer: "15.3", + pkgs: []ftypes.Package{ + { + Name: "postgresql", + Version: "13", + Release: "4.6.6", + SrcName: "postgresql", + SrcVersion: "13", + SrcRelease: "4.6.6", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "postgresql", + VulnerabilityID: "SUSE-SU-2021:0175-1", + InstalledVersion: "13-4.6.6", + FixedVersion: "13-4.6.7", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.SuseCVRF, + Name: "SUSE CVRF", + URL: "https://ftp.suse.com/pub/projects/security/cvrf/", + }, + }, + }, + }, + { + name: "broken bucket", + fixtures: []string{ + "testdata/fixtures/invalid.yaml", + "testdata/fixtures/data-source.yaml", + }, + distribution: suse.SUSEEnterpriseLinux, + args: args{ + osVer: "15.3", + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcName: "jq", + SrcVersion: "1.6-r0", + }, + }, + }, + wantErr: "failed to get SUSE advisories", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := suse.NewScanner(tt.distribution) + got, err := s.Detect(tt.args.osVer, nil, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestScanner_IsSupportedVersion(t *testing.T) { + type args struct { + osFamily ftypes.OSType + osVer string + } + tests := []struct { + name string + now time.Time + distribution suse.Type + args args + want bool + }{ + { + name: "opensuse.leap42.3", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "opensuse.leap", + osVer: "42.3", + }, + distribution: suse.OpenSUSE, + want: true, + }, + { + name: "sles12.3", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "suse linux enterprise server", + osVer: "12.3", + }, + distribution: suse.SUSEEnterpriseLinux, + want: false, + }, + { + name: "latest", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "opensuse.leap", + osVer: "999.0", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(context.Background(), tt.now) + s := suse.NewScanner(tt.distribution) + got := s.IsSupportedVersion(ctx, tt.args.osFamily, tt.args.osVer) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..13eb48d0a0ea --- /dev/null +++ b/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml @@ -0,0 +1,12 @@ +- bucket: data-source + pairs: + - key: openSUSE Leap 15.3 + value: + ID: "suse-cvrf" + Name: "SUSE CVRF" + URL: "https://ftp.suse.com/pub/projects/security/cvrf/" + - key: SUSE Linux Enterprise 15.3 + value: + ID: "suse-cvrf" + Name: "SUSE CVRF" + URL: "https://ftp.suse.com/pub/projects/security/cvrf/" \ No newline at end of file diff --git a/pkg/detector/ospkg/suse/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/suse/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..9a2e176c166c --- /dev/null +++ b/pkg/detector/ospkg/suse/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: SUSE Linux Enterprise 15.3 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/suse/testdata/fixtures/suse.yaml b/pkg/detector/ospkg/suse/testdata/fixtures/suse.yaml new file mode 100644 index 000000000000..6a17594af66c --- /dev/null +++ b/pkg/detector/ospkg/suse/testdata/fixtures/suse.yaml @@ -0,0 +1,10 @@ +- bucket: openSUSE Leap 15.3 + pairs: + - bucket: postgresql + pairs: + - key: SUSE-SU-2021:0175-1 + value: + FixedVersion: "13-4.6.7" + - key: CVE-2021-0001 + value: + FixedVersion: "" diff --git a/pkg/detector/ospkg/ubuntu/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/ubuntu/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..b5c8f895fd21 --- /dev/null +++ b/pkg/detector/ospkg/ubuntu/testdata/fixtures/data-source.yaml @@ -0,0 +1,12 @@ +- bucket: data-source + pairs: + - key: ubuntu 20.04 + value: + ID: "ubuntu" + Name: "Ubuntu CVE Tracker" + URL: "https://git.launchpad.net/ubuntu-cve-tracker" + - key: ubuntu 21.04 + value: + ID: "ubuntu" + Name: "Ubuntu CVE Tracker" + URL: "https://git.launchpad.net/ubuntu-cve-tracker" \ No newline at end of file diff --git a/pkg/detector/ospkg/ubuntu/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/ubuntu/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..29fcd05cbb99 --- /dev/null +++ b/pkg/detector/ospkg/ubuntu/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: ubuntu 21.04 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/ubuntu/testdata/fixtures/ubuntu.yaml b/pkg/detector/ospkg/ubuntu/testdata/fixtures/ubuntu.yaml new file mode 100644 index 000000000000..bcb207cd8e26 --- /dev/null +++ b/pkg/detector/ospkg/ubuntu/testdata/fixtures/ubuntu.yaml @@ -0,0 +1,20 @@ +- bucket: ubuntu 19.04 + pairs: + - bucket: wpa + pairs: + - key: CVE-2019-9243 + value: + FixedVersion: "" +- bucket: ubuntu 20.04 + pairs: + - bucket: wpa + pairs: + - key: CVE-2021-27803 + value: + FixedVersion: "2:2.9-1ubuntu4.3" + - key: CVE-2019-9243 + value: + FixedVersion: "" + - key: CVE-2016-4476 + value: + FixedVersion: "2.4-0ubuntu10" diff --git a/pkg/detector/ospkg/ubuntu/ubuntu.go b/pkg/detector/ospkg/ubuntu/ubuntu.go new file mode 100644 index 000000000000..46948806a64d --- /dev/null +++ b/pkg/detector/ospkg/ubuntu/ubuntu.go @@ -0,0 +1,151 @@ +package ubuntu + +import ( + "context" + "strings" + "time" + + version "github.com/knqyf263/go-deb-version" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ubuntu" + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + eolDates = map[string]time.Time{ + "4.10": time.Date(2006, 4, 30, 23, 59, 59, 0, time.UTC), + "5.04": time.Date(2006, 10, 31, 23, 59, 59, 0, time.UTC), + "5.10": time.Date(2007, 4, 13, 23, 59, 59, 0, time.UTC), + "6.06": time.Date(2011, 6, 1, 23, 59, 59, 0, time.UTC), + "6.10": time.Date(2008, 4, 25, 23, 59, 59, 0, time.UTC), + "7.04": time.Date(2008, 10, 19, 23, 59, 59, 0, time.UTC), + "7.10": time.Date(2009, 4, 18, 23, 59, 59, 0, time.UTC), + "8.04": time.Date(2013, 5, 9, 23, 59, 59, 0, time.UTC), + "8.10": time.Date(2010, 4, 30, 23, 59, 59, 0, time.UTC), + "9.04": time.Date(2010, 10, 23, 23, 59, 59, 0, time.UTC), + "9.10": time.Date(2011, 4, 29, 23, 59, 59, 0, time.UTC), + "10.04": time.Date(2015, 4, 29, 23, 59, 59, 0, time.UTC), + "10.10": time.Date(2012, 4, 10, 23, 59, 59, 0, time.UTC), + "11.04": time.Date(2012, 10, 28, 23, 59, 59, 0, time.UTC), + "11.10": time.Date(2013, 5, 9, 23, 59, 59, 0, time.UTC), + "12.04": time.Date(2019, 4, 26, 23, 59, 59, 0, time.UTC), + "12.04-ESM": time.Date(2019, 4, 28, 23, 59, 59, 0, time.UTC), + "12.10": time.Date(2014, 5, 16, 23, 59, 59, 0, time.UTC), + "13.04": time.Date(2014, 1, 27, 23, 59, 59, 0, time.UTC), + "13.10": time.Date(2014, 7, 17, 23, 59, 59, 0, time.UTC), + "14.04": time.Date(2022, 4, 25, 23, 59, 59, 0, time.UTC), + "14.04-ESM": time.Date(2024, 4, 25, 23, 59, 59, 0, time.UTC), + "14.10": time.Date(2015, 7, 23, 23, 59, 59, 0, time.UTC), + "15.04": time.Date(2016, 1, 23, 23, 59, 59, 0, time.UTC), + "15.10": time.Date(2016, 7, 22, 23, 59, 59, 0, time.UTC), + "16.04": time.Date(2021, 4, 21, 23, 59, 59, 0, time.UTC), + "16.04-ESM": time.Date(2026, 4, 29, 23, 59, 59, 0, time.UTC), + "16.10": time.Date(2017, 7, 20, 23, 59, 59, 0, time.UTC), + "17.04": time.Date(2018, 1, 13, 23, 59, 59, 0, time.UTC), + "17.10": time.Date(2018, 7, 19, 23, 59, 59, 0, time.UTC), + "18.04": time.Date(2023, 5, 31, 23, 59, 59, 0, time.UTC), + "18.04-ESM": time.Date(2028, 3, 31, 23, 59, 59, 0, time.UTC), + "18.10": time.Date(2019, 7, 18, 23, 59, 59, 0, time.UTC), + "19.04": time.Date(2020, 1, 18, 23, 59, 59, 0, time.UTC), + "19.10": time.Date(2020, 7, 17, 23, 59, 59, 0, time.UTC), + "20.04": time.Date(2025, 4, 23, 23, 59, 59, 0, time.UTC), + "20.10": time.Date(2021, 7, 22, 23, 59, 59, 0, time.UTC), + "21.04": time.Date(2022, 1, 20, 23, 59, 59, 0, time.UTC), + "21.10": time.Date(2022, 7, 14, 23, 59, 59, 0, time.UTC), + "22.04": time.Date(2027, 4, 23, 23, 59, 59, 0, time.UTC), + "22.10": time.Date(2023, 7, 20, 23, 59, 59, 0, time.UTC), + "23.04": time.Date(2024, 1, 20, 23, 59, 59, 0, time.UTC), + } +) + +// Scanner implements the Ubuntu scanner +type Scanner struct { + vs ubuntu.VulnSrc +} + +// NewScanner is the factory method for Scanner +func NewScanner() *Scanner { + return &Scanner{ + vs: ubuntu.NewVulnSrc(), + } +} + +// Detect scans and returns the vulnerabilities +func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting Ubuntu vulnerabilities...") + log.Logger.Debugf("ubuntu: os version: %s", osVer) + log.Logger.Debugf("ubuntu: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + osVer = s.versionFromEolDates(osVer) + advisories, err := s.vs.Get(osVer, pkg.SrcName) + if err != nil { + return nil, xerrors.Errorf("failed to get Ubuntu advisories: %w", err) + } + + sourceVersion, err := version.NewVersion(utils.FormatSrcVersion(pkg)) + if err != nil { + log.Logger.Debugf("failed to parse Ubuntu installed package version: %w", err) + continue + } + + for _, adv := range advisories { + vuln := types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: utils.FormatVersion(pkg), + FixedVersion: adv.FixedVersion, + PkgIdentifier: pkg.Identifier, + Layer: pkg.Layer, + Custom: adv.Custom, + DataSource: adv.DataSource, + } + + if adv.FixedVersion == "" { + vulns = append(vulns, vuln) + continue + } + + fixedVersion, err := version.NewVersion(adv.FixedVersion) + if err != nil { + log.Logger.Debugf("failed to parse Ubuntu package version: %w", err) + continue + } + + if sourceVersion.LessThan(fixedVersion) { + vulns = append(vulns, vuln) + } + } + } + return vulns, nil +} + +// IsSupportedVersion checks is OSFamily can be scanned using Ubuntu scanner +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + return osver.Supported(ctx, eolDates, osFamily, osVer) +} + +// versionFromEolDates checks if actual (not ESM) version is not outdated +func (s *Scanner) versionFromEolDates(osVer string) string { + if _, ok := eolDates[osVer]; ok { + return osVer + } + + // if base version (not ESM) is still actual + // we need to use this version + // e.g. Ubuntu doesn't have vulnerabilities for `18.04-ESM`, because `18.04` is not outdated + // then we need to get vulnerabilities for `18.04` + // if `18.04` is outdated - we need to use `18.04-ESM` (we will return error until we add `18.04-ESM` to eolDates) + ver := strings.TrimRight(osVer, "-ESM") + if eol, ok := eolDates[ver]; ok && time.Now().Before(eol) { // TODO: time.Now() should be replaced with clock.Now() + return ver + } + return osVer +} diff --git a/pkg/detector/ospkg/ubuntu/ubuntu_test.go b/pkg/detector/ospkg/ubuntu/ubuntu_test.go new file mode 100644 index 000000000000..a2218e211b77 --- /dev/null +++ b/pkg/detector/ospkg/ubuntu/ubuntu_test.go @@ -0,0 +1,262 @@ +package ubuntu_test + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/clock" + "sort" + "testing" + "time" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/ubuntu" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/ubuntu.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "20.04", + pkgs: []ftypes.Package{ + { + Name: "wpa", + Version: "2.9", + SrcName: "wpa", + SrcVersion: "2.9", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "wpa", + VulnerabilityID: "CVE-2019-9243", + InstalledVersion: "2.9", + FixedVersion: "", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Ubuntu, + Name: "Ubuntu CVE Tracker", + URL: "https://git.launchpad.net/ubuntu-cve-tracker", + }, + }, + { + PkgName: "wpa", + VulnerabilityID: "CVE-2021-27803", + InstalledVersion: "2.9", + FixedVersion: "2:2.9-1ubuntu4.3", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Ubuntu, + Name: "Ubuntu CVE Tracker", + URL: "https://git.launchpad.net/ubuntu-cve-tracker", + }, + }, + }, + }, + { + name: "ubuntu 20.04-ESM. 20.04 is not outdated", + fixtures: []string{ + "testdata/fixtures/ubuntu.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "20.04-ESM", + pkgs: []ftypes.Package{ + { + Name: "wpa", + Version: "2.9", + SrcName: "wpa", + SrcVersion: "2.9", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "wpa", + VulnerabilityID: "CVE-2019-9243", + InstalledVersion: "2.9", + FixedVersion: "", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Ubuntu, + Name: "Ubuntu CVE Tracker", + URL: "https://git.launchpad.net/ubuntu-cve-tracker", + }, + }, + { + PkgName: "wpa", + VulnerabilityID: "CVE-2021-27803", + InstalledVersion: "2.9", + FixedVersion: "2:2.9-1ubuntu4.3", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Ubuntu, + Name: "Ubuntu CVE Tracker", + URL: "https://git.launchpad.net/ubuntu-cve-tracker", + }, + }, + }, + }, + { + name: "ubuntu 19.04-ESM, 19.04 is outdated", // Use 19.04-ESM for testing, although it doesn't exist + fixtures: []string{ + "testdata/fixtures/ubuntu.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "19.04-ESM", + pkgs: []ftypes.Package{ + { + Name: "wpa", + Version: "2.9", + SrcName: "wpa", + SrcVersion: "2.9", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + }, + { + name: "broken bucket", + fixtures: []string{ + "testdata/fixtures/invalid.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + osVer: "21.04", + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcName: "jq", + SrcVersion: "1.6-r0", + }, + }, + }, + wantErr: "failed to get Ubuntu advisories", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := ubuntu.NewScanner() + got, err := s.Detect(tt.args.osVer, nil, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + sort.Slice(got, func(i, j int) bool { + return got[i].VulnerabilityID < got[j].VulnerabilityID + }) + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestScanner_IsSupportedVersion(t *testing.T) { + type args struct { + osFamily ftypes.OSType + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool + }{ + { + name: "ubuntu 12.04 eol ends", + now: time.Date(2019, 3, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "ubuntu", + osVer: "12.04", + }, + want: true, + }, + { + name: "ubuntu12.04", + now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "ubuntu", + osVer: "12.04", + }, + want: false, + }, + { + name: "ubuntu 18.04 ESM. 18.04 is not outdated", + now: time.Date(2022, 4, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "ubuntu", + osVer: "18.04-ESM", + }, + want: true, + }, + { + name: "ubuntu 18.04 ESM. 18.04 is outdated", + now: time.Date(2030, 4, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "ubuntu", + osVer: "18.04-ESM", + }, + want: false, + }, + { + name: "latest", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "ubuntu", + osVer: "99.04", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(context.Background(), tt.now) + s := ubuntu.NewScanner() + got := s.IsSupportedVersion(ctx, tt.args.osFamily, tt.args.osVer) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/ospkg/version/version.go b/pkg/detector/ospkg/version/version.go new file mode 100644 index 000000000000..dc47ffd88409 --- /dev/null +++ b/pkg/detector/ospkg/version/version.go @@ -0,0 +1,38 @@ +package version + +import ( + "context" + "strings" + "time" + + "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +// Major returns the major version +// e.g. 8.1 => 8 +func Major(osVer string) string { + osVer, _, _ = strings.Cut(osVer, ".") + return osVer +} + +// Minor returns the major and minor version +// e.g. 3.17.2 => 3.17 +func Minor(osVer string) string { + major, s, ok := strings.Cut(osVer, ".") + if !ok { + return osVer + } + minor, _, _ := strings.Cut(s, ".") + return major + "." + minor +} + +func Supported(ctx context.Context, eolDates map[string]time.Time, osFamily ftypes.OSType, osVer string) bool { + eol, ok := eolDates[osVer] + if !ok { + log.Logger.Warnf("This OS version is not on the EOL list: %s %s", osFamily, osVer) + return true // can be the latest version + } + return clock.Now(ctx).Before(eol) +} diff --git a/pkg/detector/ospkg/wolfi/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/wolfi/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..476d6744851d --- /dev/null +++ b/pkg/detector/ospkg/wolfi/testdata/fixtures/data-source.yaml @@ -0,0 +1,7 @@ +- bucket: data-source + pairs: + - key: wolfi + value: + ID: "wolfi" + Name: "Wolfi Secdb" + URL: "https://packages.wolfi.dev/os/security.json" \ No newline at end of file diff --git a/pkg/detector/ospkg/wolfi/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/wolfi/testdata/fixtures/invalid.yaml new file mode 100644 index 000000000000..683c60727f2d --- /dev/null +++ b/pkg/detector/ospkg/wolfi/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: wolfi + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/wolfi/testdata/fixtures/wolfi.yaml b/pkg/detector/ospkg/wolfi/testdata/fixtures/wolfi.yaml new file mode 100644 index 000000000000..5df51800a08d --- /dev/null +++ b/pkg/detector/ospkg/wolfi/testdata/fixtures/wolfi.yaml @@ -0,0 +1,39 @@ +- bucket: wolfi + pairs: + - bucket: ansible + pairs: + - key: CVE-2018-10875 + value: + FixedVersion: "2.6.3-r0" + - key: CVE-2019-10217 + value: + FixedVersion: "2.8.4-r0" + - key: CVE-2020-1740 + value: + FixedVersion: "" + AffectedVersion: "2.6.5" + - key: CVE-2021-20191 + value: + FixedVersion: "" + - key: CVE-2019-INVALID + value: + FixedVersion: "invalid" + - bucket: jq + pairs: + - key: CVE-2016-4074 + value: + FixedVersion: "1.6_rc1-r0" + - key: CVE-2019-9999 + value: + FixedVersion: "1.6_rc2" + - key: CVE-2020-1234 + value: + FixedVersion: "1.6-r1" + - bucket: test-src + pairs: + - key: CVE-2030-0001 + value: + FixedVersion: "0.1.0_alpha_pre2" + - key: CVE-2030-0002 + value: + FixedVersion: "0.1.0_alpha2" diff --git a/pkg/detector/ospkg/wolfi/wolfi.go b/pkg/detector/ospkg/wolfi/wolfi.go new file mode 100644 index 000000000000..9757fc6aa637 --- /dev/null +++ b/pkg/detector/ospkg/wolfi/wolfi.go @@ -0,0 +1,91 @@ +package wolfi + +import ( + "context" + + version "github.com/knqyf263/go-apk-version" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/wolfi" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +// Scanner implements the Wolfi scanner +type Scanner struct { + vs wolfi.VulnSrc +} + +// NewScanner is the factory method for Scanner +func NewScanner() *Scanner { + return &Scanner{ + vs: wolfi.NewVulnSrc(), + } +} + +// Detect vulnerabilities in package using Wolfi scanner +func (s *Scanner) Detect(_ string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.Logger.Info("Detecting Wolfi vulnerabilities...") + + log.Logger.Debugf("wolfi: the number of packages: %d", len(pkgs)) + + var vulns []types.DetectedVulnerability + for _, pkg := range pkgs { + srcName := pkg.SrcName + if srcName == "" { + srcName = pkg.Name + } + advisories, err := s.vs.Get("", srcName) + if err != nil { + return nil, xerrors.Errorf("failed to get Wolfi advisories: %w", err) + } + + installed := utils.FormatVersion(pkg) + installedVersion, err := version.NewVersion(installed) + if err != nil { + log.Logger.Debugf("failed to parse Wolfi Linux installed package version: %s", err) + continue + } + + for _, adv := range advisories { + if !s.isVulnerable(installedVersion, adv) { + continue + } + vulns = append(vulns, types.DetectedVulnerability{ + VulnerabilityID: adv.VulnerabilityID, + PkgID: pkg.ID, + PkgName: pkg.Name, + InstalledVersion: installed, + FixedVersion: adv.FixedVersion, + Layer: pkg.Layer, + PkgIdentifier: pkg.Identifier, + Custom: adv.Custom, + DataSource: adv.DataSource, + }) + } + } + return vulns, nil +} + +func (s *Scanner) isVulnerable(installedVersion version.Version, adv dbTypes.Advisory) bool { + // Compare versions for fixed vulnerabilities + fixedVersion, err := version.NewVersion(adv.FixedVersion) + if err != nil { + log.Logger.Debugf("failed to parse Wolfi Linux fixed version: %s", err) + return false + } + + // It means the fixed vulnerability + return installedVersion.LessThan(fixedVersion) +} + +// IsSupportedVersion checks if the version is supported. +func (s *Scanner) IsSupportedVersion(_ context.Context, _ ftypes.OSType, _ string) bool { + // Wolfi doesn't have versions, so there is no case where a given input yields a + // result of an unsupported Wolfi version. + + return true +} diff --git a/pkg/detector/ospkg/wolfi/wolfi_test.go b/pkg/detector/ospkg/wolfi/wolfi_test.go new file mode 100644 index 000000000000..78c1e4818c31 --- /dev/null +++ b/pkg/detector/ospkg/wolfi/wolfi_test.go @@ -0,0 +1,211 @@ +package wolfi_test + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/wolfi" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestScanner_Detect(t *testing.T) { + type args struct { + repo *ftypes.Repository + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{ + "testdata/fixtures/wolfi.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + pkgs: []ftypes.Package{ + { + Name: "ansible", + Version: "2.6.4", + SrcName: "ansible", + SrcVersion: "2.6.4", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + { + Name: "invalid", + Version: "invalid", // skipped + SrcName: "invalid", + SrcVersion: "invalid", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "ansible", + VulnerabilityID: "CVE-2019-10217", + InstalledVersion: "2.6.4", + FixedVersion: "2.8.4-r0", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Wolfi, + Name: "Wolfi Secdb", + URL: "https://packages.wolfi.dev/os/security.json", + }, + }, + }, + }, + { + name: "contain rc", + fixtures: []string{ + "testdata/fixtures/wolfi.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcName: "jq", + SrcVersion: "1.6-r0", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "jq", + VulnerabilityID: "CVE-2020-1234", + InstalledVersion: "1.6-r0", + FixedVersion: "1.6-r1", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Wolfi, + Name: "Wolfi Secdb", + URL: "https://packages.wolfi.dev/os/security.json", + }, + }, + }, + }, + { + name: "contain pre", + fixtures: []string{ + "testdata/fixtures/wolfi.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + pkgs: []ftypes.Package{ + { + Name: "test", + Version: "0.1.0_alpha", + SrcName: "test-src", + SrcVersion: "0.1.0_alpha", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2030-0002", + PkgName: "test", + InstalledVersion: "0.1.0_alpha", + FixedVersion: "0.1.0_alpha2", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Wolfi, + Name: "Wolfi Secdb", + URL: "https://packages.wolfi.dev/os/security.json", + }, + }, + }, + }, + { + name: "Get returns an error", + fixtures: []string{ + "testdata/fixtures/invalid.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcName: "jq", + SrcVersion: "1.6-r0", + }, + }, + }, + wantErr: "failed to get Wolfi advisories", + }, + { + name: "No src name", + fixtures: []string{ + "testdata/fixtures/wolfi.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + repo: &ftypes.Repository{ + Family: ftypes.Wolfi, + Release: "3.10", + }, + pkgs: []ftypes.Package{ + { + Name: "jq", + Version: "1.6-r0", + SrcVersion: "1.6-r0", + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "jq", + VulnerabilityID: "CVE-2020-1234", + InstalledVersion: "1.6-r0", + FixedVersion: "1.6-r1", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.Wolfi, + Name: "Wolfi Secdb", + URL: "https://packages.wolfi.dev/os/security.json", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + s := wolfi.NewScanner() + got, err := s.Detect("", tt.args.repo, tt.args.pkgs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + sort.Slice(got, func(i, j int) bool { + return got[i].VulnerabilityID < got[j].VulnerabilityID + }) + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/digest/digest.go b/pkg/digest/digest.go new file mode 100644 index 000000000000..5d9fa2ef1c68 --- /dev/null +++ b/pkg/digest/digest.go @@ -0,0 +1,84 @@ +package digest + +import ( + "crypto/sha1" // nolint + "crypto/sha256" + "fmt" + "hash" + "io" + "strings" + + "golang.org/x/xerrors" +) + +type Algorithm string + +func (a Algorithm) String() string { + return string(a) +} + +// supported digest types +const ( + SHA1 Algorithm = "sha1" // sha1 with hex encoding (lower case only) + SHA256 Algorithm = "sha256" // sha256 with hex encoding (lower case only) + MD5 Algorithm = "md5" // md5 with hex encoding (lower case only) +) + +// Digest allows simple protection of hex formatted digest strings, prefixed by their algorithm. +// +// The following is an example of the contents of Digest types: +// +// sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc +type Digest string + +// NewDigest returns a Digest from alg and a hash.Hash object. +func NewDigest(alg Algorithm, h hash.Hash) Digest { + return Digest(fmt.Sprintf("%s:%x", alg, h.Sum(nil))) +} + +// NewDigestFromString returns a Digest from alg and a string. +func NewDigestFromString(alg Algorithm, h string) Digest { + return Digest(fmt.Sprintf("%s:%s", alg, h)) +} + +func (d Digest) Algorithm() Algorithm { + return Algorithm(d[:d.sepIndex()]) +} + +func (d Digest) Encoded() string { + return string(d[d.sepIndex()+1:]) +} + +func (d Digest) String() string { + return string(d) +} + +func (d Digest) sepIndex() int { + i := strings.Index(string(d), ":") + if i < 0 { + i = 0 + } + return i +} + +func CalcSHA1(r io.ReadSeeker) (Digest, error) { + defer r.Seek(0, io.SeekStart) + + h := sha1.New() // nolint + if _, err := io.Copy(h, r); err != nil { + return "", xerrors.Errorf("unable to calculate sha1 digest: %w", err) + } + + return NewDigest(SHA1, h), nil +} + +func CalcSHA256(r io.ReadSeeker) (Digest, error) { + defer r.Seek(0, io.SeekStart) + + h := sha256.New() + if _, err := io.Copy(h, r); err != nil { + return "", xerrors.Errorf("unable to calculate sha256 digest: %w", err) + } + + return NewDigest(SHA256, h), nil +} diff --git a/pkg/downloader/download.go b/pkg/downloader/download.go new file mode 100644 index 000000000000..0c9388248a6d --- /dev/null +++ b/pkg/downloader/download.go @@ -0,0 +1,60 @@ +package downloader + +import ( + "context" + "os" + + getter "github.com/hashicorp/go-getter" + "golang.org/x/exp/maps" + "golang.org/x/xerrors" +) + +// DownloadToTempDir downloads the configured source to a temp dir. +func DownloadToTempDir(ctx context.Context, url string) (string, error) { + tempDir, err := os.MkdirTemp("", "trivy-plugin") + if err != nil { + return "", xerrors.Errorf("failed to create a temp dir: %w", err) + } + + pwd, err := os.Getwd() + if err != nil { + return "", xerrors.Errorf("unable to get the current dir: %w", err) + } + + if err = Download(ctx, url, tempDir, pwd); err != nil { + return "", xerrors.Errorf("download error: %w", err) + } + + return tempDir, nil +} + +// Download downloads the configured source to the destination. +func Download(ctx context.Context, src, dst, pwd string) error { + // go-getter doesn't allow the dst directory already exists if the src is directory. + _ = os.RemoveAll(dst) + + var opts []getter.ClientOption + + // Clone the global map so that it will not be accessed concurrently. + getters := maps.Clone(getter.Getters) + + // Overwrite the file getter so that a file will be copied + getters["file"] = &getter.FileGetter{Copy: true} + + // Build the client + client := &getter.Client{ + Ctx: ctx, + Src: src, + Dst: dst, + Pwd: pwd, + Getters: getters, + Mode: getter.ClientModeAny, + Options: opts, + } + + if err := client.Get(); err != nil { + return xerrors.Errorf("failed to download: %w", err) + } + + return nil +} diff --git a/pkg/extrafs/extrafs.go b/pkg/extrafs/extrafs.go new file mode 100644 index 000000000000..e3956c193bbe --- /dev/null +++ b/pkg/extrafs/extrafs.go @@ -0,0 +1,54 @@ +package extrafs + +import ( + "io/fs" + "os" + "path/filepath" +) + +/* + Go does not currently support symlinks in io/fs. + We work around this by wrapping the fs.FS returned by os.DirFS with our own type which bolts on the ReadLinkFS +*/ + +type OSFS interface { + fs.FS + fs.StatFS +} + +type ReadLinkFS interface { + ResolveSymlink(name, dir string) (string, error) +} + +type FS interface { + OSFS + ReadLinkFS +} + +type filesystem struct { + root string + underlying OSFS +} + +func OSDir(path string) FS { + return &filesystem{ + root: path, + underlying: os.DirFS(path).(OSFS), + } +} + +func (f *filesystem) Open(name string) (fs.File, error) { + return f.underlying.Open(name) +} + +func (f *filesystem) Stat(name string) (fs.FileInfo, error) { + return f.underlying.Stat(name) +} + +func (f *filesystem) ResolveSymlink(name, dir string) (string, error) { + link, err := os.Readlink(filepath.Join(f.root, dir, name)) + if err == nil { + return filepath.Join(dir, link), nil + } + return name, nil +} diff --git a/pkg/fanal/analyzer/all/import.go b/pkg/fanal/analyzer/all/import.go new file mode 100644 index 000000000000..443ebc6bdfb8 --- /dev/null +++ b/pkg/fanal/analyzer/all/import.go @@ -0,0 +1,51 @@ +package all + +import ( + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/buildinfo" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/executable" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/imgconf/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/imgconf/dockerfile" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/imgconf/secret" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/c/conan" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/conda/meta" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dart/pub" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/deps" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/nuget" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/packagesprops" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/elixir/mix" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/golang/binary" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/golang/mod" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/gradle" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/jar" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/pom" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/npm" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/pkg" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/pnpm" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/yarn" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/php/composer" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/packaging" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/pip" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/pipenv" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/poetry" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/bundler" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/gemspec" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/rust/binary" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/rust/cargo" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/swift/cocoapods" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/swift/swift" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/licensing" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/amazonlinux" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/debian" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/mariner" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/redhatbase" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/release" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/ubuntu" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/dpkg" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/rpm" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/repo/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/sbom" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" +) diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go new file mode 100644 index 000000000000..c5f55fd5fa15 --- /dev/null +++ b/pkg/fanal/analyzer/analyzer.go @@ -0,0 +1,515 @@ +package analyzer + +import ( + "context" + "errors" + "io/fs" + "os" + "regexp" + "sort" + "strings" + "sync" + + "github.com/samber/lo" + "golang.org/x/exp/slices" + "golang.org/x/sync/semaphore" + "golang.org/x/xerrors" + + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/log" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/misconf" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +var ( + analyzers = make(map[Type]analyzer) + postAnalyzers = make(map[Type]postAnalyzerInitialize) + + // ErrUnknownOS occurs when unknown OS is analyzed. + ErrUnknownOS = xerrors.New("unknown OS") + // ErrPkgAnalysis occurs when the analysis of packages is failed. + ErrPkgAnalysis = xerrors.New("failed to analyze packages") + // ErrNoPkgsDetected occurs when the required files for an OS package manager are not detected + ErrNoPkgsDetected = xerrors.New("no packages detected") +) + +////////////////////// +// Analyzer options // +////////////////////// + +// AnalyzerOptions is used to initialize analyzers +type AnalyzerOptions struct { + Group Group + Parallel int + FilePatterns []string + DisabledAnalyzers []Type + MisconfScannerOption misconf.ScannerOption + SecretScannerOption SecretScannerOption + LicenseScannerOption LicenseScannerOption +} + +type SecretScannerOption struct { + ConfigPath string +} + +type LicenseScannerOption struct { + // Use license classifier to get better results though the classification is expensive. + Full bool + ClassifierConfidenceLevel float64 +} + +//////////////// +// Interfaces // +//////////////// + +// Initializer represents analyzers that need to take parameters from users +type Initializer interface { + Init(AnalyzerOptions) error +} + +type analyzer interface { + Type() Type + Version() int + Analyze(ctx context.Context, input AnalysisInput) (*AnalysisResult, error) + Required(filePath string, info os.FileInfo) bool +} + +type PostAnalyzer interface { + Type() Type + Version() int + PostAnalyze(ctx context.Context, input PostAnalysisInput) (*AnalysisResult, error) + Required(filePath string, info os.FileInfo) bool +} + +//////////////////// +// Analyzer group // +//////////////////// + +type Group string + +const GroupBuiltin Group = "builtin" + +func RegisterAnalyzer(analyzer analyzer) { + if _, ok := analyzers[analyzer.Type()]; ok { + log.Logger.Fatalf("analyzer %s is registered twice", analyzer.Type()) + } + analyzers[analyzer.Type()] = analyzer +} + +type postAnalyzerInitialize func(options AnalyzerOptions) (PostAnalyzer, error) + +func RegisterPostAnalyzer(t Type, initializer postAnalyzerInitialize) { + if _, ok := postAnalyzers[t]; ok { + log.Logger.Fatalf("analyzer %s is registered twice", t) + } + postAnalyzers[t] = initializer +} + +// DeregisterAnalyzer is mainly for testing +func DeregisterAnalyzer(t Type) { + delete(analyzers, t) +} + +// CustomGroup returns a group name for custom analyzers +// This is mainly intended to be used in Aqua products. +type CustomGroup interface { + Group() Group +} + +type Opener func() (xio.ReadSeekCloserAt, error) + +type AnalyzerGroup struct { + analyzers []analyzer + postAnalyzers []PostAnalyzer + filePatterns map[Type][]*regexp.Regexp +} + +/////////////////////////// +// Analyzer input/output // +/////////////////////////// + +type AnalysisInput struct { + Dir string + FilePath string + Info os.FileInfo + Content xio.ReadSeekerAt + + Options AnalysisOptions +} + +type PostAnalysisInput struct { + FS fs.FS + Options AnalysisOptions +} + +type AnalysisOptions struct { + Offline bool + FileChecksum bool +} + +type AnalysisResult struct { + m sync.Mutex + OS types.OS + Repository *types.Repository + PackageInfos []types.PackageInfo + Applications []types.Application + Misconfigurations []types.Misconfiguration + Secrets []types.Secret + Licenses []types.LicenseFile + SystemInstalledFiles []string // A list of files installed by OS package manager + + // Digests contains SHA-256 digests of unpackaged files + // used to search for SBOM attestation. + Digests map[string]string + + // For Red Hat + BuildInfo *types.BuildInfo + + // CustomResources hold analysis results from custom analyzers. + // It is for extensibility and not used in OSS. + CustomResources []types.CustomResource +} + +func NewAnalysisResult() *AnalysisResult { + result := new(AnalysisResult) + return result +} + +func (r *AnalysisResult) isEmpty() bool { + return lo.IsEmpty(r.OS) && r.Repository == nil && len(r.PackageInfos) == 0 && len(r.Applications) == 0 && + len(r.Misconfigurations) == 0 && len(r.Secrets) == 0 && len(r.Licenses) == 0 && len(r.SystemInstalledFiles) == 0 && + r.BuildInfo == nil && len(r.Digests) == 0 && len(r.CustomResources) == 0 +} + +func (r *AnalysisResult) Sort() { + // OS packages + sort.Slice(r.PackageInfos, func(i, j int) bool { + return r.PackageInfos[i].FilePath < r.PackageInfos[j].FilePath + }) + + for _, pi := range r.PackageInfos { + sort.Sort(pi.Packages) + } + + // Language-specific packages + sort.Slice(r.Applications, func(i, j int) bool { + if r.Applications[i].FilePath != r.Applications[j].FilePath { + return r.Applications[i].FilePath < r.Applications[j].FilePath + } + return r.Applications[i].Type < r.Applications[j].Type + }) + + for _, app := range r.Applications { + sort.Sort(app.Libraries) + } + + // Custom resources + sort.Slice(r.CustomResources, func(i, j int) bool { + return r.CustomResources[i].FilePath < r.CustomResources[j].FilePath + }) + + // Misconfigurations + sort.Slice(r.Misconfigurations, func(i, j int) bool { + return r.Misconfigurations[i].FilePath < r.Misconfigurations[j].FilePath + }) + + // Secrets + sort.Slice(r.Secrets, func(i, j int) bool { + return r.Secrets[i].FilePath < r.Secrets[j].FilePath + }) + for _, sec := range r.Secrets { + sort.Slice(sec.Findings, func(i, j int) bool { + if sec.Findings[i].RuleID != sec.Findings[j].RuleID { + return sec.Findings[i].RuleID < sec.Findings[j].RuleID + } + return sec.Findings[i].StartLine < sec.Findings[j].StartLine + }) + } + + // License files + sort.Slice(r.Licenses, func(i, j int) bool { + if r.Licenses[i].Type == r.Licenses[j].Type { + if r.Licenses[i].FilePath == r.Licenses[j].FilePath { + return r.Licenses[i].Layer.DiffID < r.Licenses[j].Layer.DiffID + } else { + return r.Licenses[i].FilePath < r.Licenses[j].FilePath + } + } + + return r.Licenses[i].Type < r.Licenses[j].Type + }) +} + +func (r *AnalysisResult) Merge(newResult *AnalysisResult) { + if newResult == nil || newResult.isEmpty() { + return + } + + // this struct is accessed by multiple goroutines + r.m.Lock() + defer r.m.Unlock() + + r.OS.Merge(newResult.OS) + + if newResult.Repository != nil { + r.Repository = newResult.Repository + } + + if len(newResult.PackageInfos) > 0 { + r.PackageInfos = append(r.PackageInfos, newResult.PackageInfos...) + } + + if len(newResult.Applications) > 0 { + r.Applications = append(r.Applications, newResult.Applications...) + } + + // Merge SHA-256 digests of unpackaged files + if newResult.Digests != nil { + r.Digests = lo.Assign(r.Digests, newResult.Digests) + } + + r.Misconfigurations = append(r.Misconfigurations, newResult.Misconfigurations...) + r.Secrets = append(r.Secrets, newResult.Secrets...) + r.Licenses = append(r.Licenses, newResult.Licenses...) + r.SystemInstalledFiles = append(r.SystemInstalledFiles, newResult.SystemInstalledFiles...) + + if newResult.BuildInfo != nil { + if r.BuildInfo == nil { + r.BuildInfo = newResult.BuildInfo + } else { + // We don't need to merge build info here + // because there is theoretically only one file about build info in each layer. + if newResult.BuildInfo.Nvr != "" || newResult.BuildInfo.Arch != "" { + r.BuildInfo.Nvr = newResult.BuildInfo.Nvr + r.BuildInfo.Arch = newResult.BuildInfo.Arch + } + if len(newResult.BuildInfo.ContentSets) > 0 { + r.BuildInfo.ContentSets = newResult.BuildInfo.ContentSets + } + } + } + + r.CustomResources = append(r.CustomResources, newResult.CustomResources...) +} + +func belongToGroup(groupName Group, analyzerType Type, disabledAnalyzers []Type, analyzer any) bool { + if slices.Contains(disabledAnalyzers, analyzerType) { + return false + } + + analyzerGroupName := GroupBuiltin + if cg, ok := analyzer.(CustomGroup); ok { + analyzerGroupName = cg.Group() + } + if analyzerGroupName != groupName { + return false + } + + return true +} + +const separator = ":" + +func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { + groupName := opt.Group + if groupName == "" { + groupName = GroupBuiltin + } + + group := AnalyzerGroup{ + filePatterns: make(map[Type][]*regexp.Regexp), + } + for _, p := range opt.FilePatterns { + // e.g. "dockerfile:my_dockerfile_*" + s := strings.SplitN(p, separator, 2) + if len(s) != 2 { + return group, xerrors.Errorf("invalid file pattern (%s) expected format: \"fileType:regexPattern\" e.g. \"dockerfile:my_dockerfile_*\"", p) + } + + fileType, pattern := s[0], s[1] + r, err := regexp.Compile(pattern) + if err != nil { + return group, xerrors.Errorf("invalid file regexp (%s): %w", p, err) + } + + if _, ok := group.filePatterns[Type(fileType)]; !ok { + group.filePatterns[Type(fileType)] = []*regexp.Regexp{} + } + + group.filePatterns[Type(fileType)] = append(group.filePatterns[Type(fileType)], r) + } + + for analyzerType, a := range analyzers { + if !belongToGroup(groupName, analyzerType, opt.DisabledAnalyzers, a) { + continue + } + // Initialize only scanners that have Init() + if ini, ok := a.(Initializer); ok { + if err := ini.Init(opt); err != nil { + return AnalyzerGroup{}, xerrors.Errorf("analyzer initialization error: %w", err) + } + } + group.analyzers = append(group.analyzers, a) + } + + for analyzerType, init := range postAnalyzers { + a, err := init(opt) + if err != nil { + return AnalyzerGroup{}, xerrors.Errorf("post-analyzer init error: %w", err) + } + if !belongToGroup(groupName, analyzerType, opt.DisabledAnalyzers, a) { + continue + } + group.postAnalyzers = append(group.postAnalyzers, a) + } + + return group, nil +} + +type Versions struct { + Analyzers map[string]int + PostAnalyzers map[string]int +} + +// AnalyzerVersions returns analyzer version identifier used for cache keys. +func (ag AnalyzerGroup) AnalyzerVersions() Versions { + analyzerVersions := make(map[string]int) + for _, a := range ag.analyzers { + analyzerVersions[string(a.Type())] = a.Version() + } + postAnalyzerVersions := make(map[string]int) + for _, a := range ag.postAnalyzers { + postAnalyzerVersions[string(a.Type())] = a.Version() + } + return Versions{ + Analyzers: analyzerVersions, + PostAnalyzers: postAnalyzerVersions, + } +} + +// AnalyzeFile determines which files are required by the analyzers based on the file name and attributes, +// and passes only those files to the analyzer for analysis. +// This function may be called concurrently and must be thread-safe. +func (ag AnalyzerGroup) AnalyzeFile(ctx context.Context, wg *sync.WaitGroup, limit *semaphore.Weighted, result *AnalysisResult, + dir, filePath string, info os.FileInfo, opener Opener, disabled []Type, opts AnalysisOptions) error { + if info.IsDir() { + return nil + } + + // filepath extracted from tar file doesn't have the prefix "/" + cleanPath := strings.TrimLeft(filePath, "/") + + for _, a := range ag.analyzers { + // Skip disabled analyzers + if slices.Contains(disabled, a.Type()) { + continue + } + + if !ag.filePatternMatch(a.Type(), cleanPath) && !a.Required(cleanPath, info) { + continue + } + rc, err := opener() + if errors.Is(err, fs.ErrPermission) { + log.Logger.Debugf("Permission error: %s", filePath) + break + } else if err != nil { + return xerrors.Errorf("unable to open %s: %w", filePath, err) + } + + if err = limit.Acquire(ctx, 1); err != nil { + return xerrors.Errorf("semaphore acquire: %w", err) + } + wg.Add(1) + + go func(a analyzer, rc xio.ReadSeekCloserAt) { + defer limit.Release(1) + defer wg.Done() + defer rc.Close() + + ret, err := a.Analyze(ctx, AnalysisInput{ + Dir: dir, + FilePath: filePath, + Info: info, + Content: rc, + Options: opts, + }) + if err != nil && !errors.Is(err, fos.AnalyzeOSError) { + log.Logger.Debugf("Analysis error: %s", err) + return + } + result.Merge(ret) + }(a, rc) + } + + return nil +} + +// RequiredPostAnalyzers returns a list of analyzer types that require the given file. +func (ag AnalyzerGroup) RequiredPostAnalyzers(filePath string, info os.FileInfo) []Type { + if info.IsDir() { + return nil + } + var postAnalyzerTypes []Type + for _, a := range ag.postAnalyzers { + if ag.filePatternMatch(a.Type(), filePath) || a.Required(filePath, info) { + postAnalyzerTypes = append(postAnalyzerTypes, a.Type()) + } + } + return postAnalyzerTypes +} + +// PostAnalyze passes a virtual filesystem containing only required files +// and passes it to the respective post-analyzer. +// The obtained results are merged into the "result". +// This function may be called concurrently and must be thread-safe. +func (ag AnalyzerGroup) PostAnalyze(ctx context.Context, compositeFS *CompositeFS, result *AnalysisResult, opts AnalysisOptions) error { + for _, a := range ag.postAnalyzers { + fsys, ok := compositeFS.Get(a.Type()) + if !ok { + continue + } + + skippedFiles := result.SystemInstalledFiles + for _, app := range result.Applications { + skippedFiles = append(skippedFiles, app.FilePath) + for _, lib := range app.Libraries { + // The analysis result could contain packages listed in SBOM. + // The files of those packages don't have to be analyzed. + // This is especially helpful for expensive post-analyzers such as the JAR analyzer. + if lib.FilePath != "" { + skippedFiles = append(skippedFiles, lib.FilePath) + } + } + } + + filteredFS, err := fsys.Filter(skippedFiles) + if err != nil { + return xerrors.Errorf("unable to filter filesystem: %w", err) + } + + res, err := a.PostAnalyze(ctx, PostAnalysisInput{ + FS: filteredFS, + Options: opts, + }) + if err != nil { + return xerrors.Errorf("post analysis error: %w", err) + } + result.Merge(res) + } + return nil +} + +// PostAnalyzerFS returns a composite filesystem that contains multiple filesystems for each post-analyzer +func (ag AnalyzerGroup) PostAnalyzerFS() (*CompositeFS, error) { + return NewCompositeFS(ag) +} + +func (ag AnalyzerGroup) filePatternMatch(analyzerType Type, filePath string) bool { + for _, pattern := range ag.filePatterns[analyzerType] { + if pattern.MatchString(filePath) { + return true + } + } + return false +} diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go new file mode 100644 index 000000000000..8fee82acf600 --- /dev/null +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -0,0 +1,691 @@ +package analyzer_test + +import ( + "context" + "fmt" + "github.com/google/go-containerregistry/pkg/name" + "os" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sync/semaphore" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/javadb" + "github.com/aquasecurity/trivy/pkg/mapfs" + xio "github.com/aquasecurity/trivy/pkg/x/io" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/imgconf/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/jar" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/poetry" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/bundler" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/ubuntu" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/repo/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/handler/all" + _ "modernc.org/sqlite" +) + +func TestAnalysisResult_Merge(t *testing.T) { + type fields struct { + m sync.Mutex + OS types.OS + PackageInfos []types.PackageInfo + Applications []types.Application + } + type args struct { + new *analyzer.AnalysisResult + } + tests := []struct { + name string + fields fields + args args + want analyzer.AnalysisResult + }{ + { + name: "happy path", + fields: fields{ + OS: types.OS{ + Family: types.Debian, + Name: "9.8", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/libc", + Packages: types.Packages{ + { + Name: "libc", + Version: "1.2.3", + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: "bundler", + FilePath: "app/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "rails", + Version: "5.0.0", + }, + }, + }, + }, + }, + args: args{ + new: &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/openssl", + Packages: types.Packages{ + { + Name: "openssl", + Version: "1.1.1", + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: "bundler", + FilePath: "app2/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "nokogiri", + Version: "1.0.0", + }, + }, + }, + }, + }, + }, + want: analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Debian, + Name: "9.8", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/libc", + Packages: types.Packages{ + { + Name: "libc", + Version: "1.2.3", + }, + }, + }, + { + FilePath: "var/lib/dpkg/status.d/openssl", + Packages: types.Packages{ + { + Name: "openssl", + Version: "1.1.1", + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: "bundler", + FilePath: "app/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "rails", + Version: "5.0.0", + }, + }, + }, + { + Type: "bundler", + FilePath: "app2/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "nokogiri", + Version: "1.0.0", + }, + }, + }, + }, + }, + }, + { + name: "redhat must be replaced with oracle", + fields: fields{ + OS: types.OS{ + Family: types.RedHat, // this must be overwritten + Name: "8.0", + }, + }, + args: args{ + new: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Oracle, + Name: "8.0", + }, + }, + }, + want: analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Oracle, + Name: "8.0", + }, + }, + }, + { + name: "debian must be replaced with ubuntu", + fields: fields{ + OS: types.OS{ + Family: types.Debian, // this must be overwritten + Name: "9.0", + }, + }, + args: args{ + new: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Ubuntu, + Name: "18.04", + }, + }, + }, + want: analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Ubuntu, + Name: "18.04", + }, + }, + }, + { + name: "merge extended flag", + fields: fields{ + // This must be overwritten + OS: types.OS{ + Family: types.Ubuntu, + Name: "16.04", + }, + }, + args: args{ + new: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Ubuntu, + Extended: true, + }, + }, + }, + want: analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Ubuntu, + Name: "16.04", + Extended: true, + }, + }, + }, + { + name: "alpine OS needs to be extended with apk repositories", + fields: fields{ + OS: types.OS{ + Family: types.Alpine, + Name: "3.15.3", + }, + }, + args: args{ + new: &analyzer.AnalysisResult{ + Repository: &types.Repository{ + Family: types.Alpine, + Release: "edge", + }, + }, + }, + want: analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Alpine, + Name: "3.15.3", + }, + Repository: &types.Repository{ + Family: types.Alpine, + Release: "edge", + }, + }, + }, + { + name: "alpine must not be replaced with oracle", + fields: fields{ + OS: types.OS{ + Family: types.Alpine, // this must not be overwritten + Name: "3.11", + }, + }, + args: args{ + new: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Oracle, + Name: "8.0", + }, + }, + }, + want: analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Alpine, // this must not be overwritten + Name: "3.11", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := analyzer.AnalysisResult{ + OS: tt.fields.OS, + PackageInfos: tt.fields.PackageInfos, + Applications: tt.fields.Applications, + } + r.Merge(tt.args.new) + assert.Equal(t, tt.want, r) + }) + } +} + +func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { + type args struct { + filePath string + testFilePath string + disabledAnalyzers []analyzer.Type + filePatterns []string + } + tests := []struct { + name string + args args + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path with os analyzer", + args: args{ + filePath: "/etc/alpine-release", + testFilePath: "testdata/etc/alpine-release", + }, + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: "alpine", + Name: "3.11.6", + }, + }, + }, + { + name: "happy path with disabled os analyzer", + args: args{ + filePath: "/etc/alpine-release", + testFilePath: "testdata/etc/alpine-release", + disabledAnalyzers: []analyzer.Type{analyzer.TypeAlpine}, + }, + want: &analyzer.AnalysisResult{}, + }, + { + name: "happy path with package analyzer", + args: args{ + filePath: "/lib/apk/db/installed", + testFilePath: "testdata/lib/apk/db/installed", + }, + want: &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "/lib/apk/db/installed", + Packages: types.Packages{ + { + ID: "musl@1.1.24-r2", + Name: "musl", + Version: "1.1.24-r2", + SrcName: "musl", + SrcVersion: "1.1.24-r2", + Licenses: []string{"MIT"}, + Arch: "x86_64", + Digest: "sha1:cb2316a189ebee5282c4a9bd98794cc2477a74c6", + InstalledFiles: []string{ + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1", + }, + }, + }, + }, + }, + SystemInstalledFiles: []string{ + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1", + }, + }, + }, + { + name: "happy path with disabled package analyzer", + args: args{ + filePath: "/lib/apk/db/installed", + testFilePath: "testdata/lib/apk/db/installed", + disabledAnalyzers: []analyzer.Type{analyzer.TypeApk}, + }, + want: &analyzer.AnalysisResult{}, + }, + { + name: "happy path with library analyzer", + args: args{ + filePath: "/app/Gemfile.lock", + testFilePath: "testdata/app/Gemfile.lock", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: "bundler", + FilePath: "/app/Gemfile.lock", + Libraries: types.Packages{ + { + ID: "actioncable@5.2.3", + Name: "actioncable", + Version: "5.2.3", + Indirect: false, + DependsOn: []string{ + "actionpack@5.2.3", + }, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + { + ID: "actionpack@5.2.3", + Name: "actionpack", + Version: "5.2.3", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 6, + EndLine: 6, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with invalid os information", + args: args{ + filePath: "/etc/lsb-release", + testFilePath: "testdata/etc/hostname", + }, + want: &analyzer.AnalysisResult{}, + }, + { + name: "happy path with a directory", + args: args{ + filePath: "/etc/lsb-release", + testFilePath: "testdata/etc", + }, + want: &analyzer.AnalysisResult{}, + }, + { + name: "happy path with library analyzer file pattern regex", + args: args{ + filePath: "/app/Gemfile-dev.lock", + testFilePath: "testdata/app/Gemfile.lock", + filePatterns: []string{"bundler:Gemfile(-.*)?\\.lock"}, + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: "bundler", + FilePath: "/app/Gemfile-dev.lock", + Libraries: types.Packages{ + { + ID: "actioncable@5.2.3", + Name: "actioncable", + Version: "5.2.3", + Indirect: false, + DependsOn: []string{ + "actionpack@5.2.3", + }, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + { + ID: "actionpack@5.2.3", + Name: "actionpack", + Version: "5.2.3", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 6, + EndLine: 6, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "ignore permission error", + args: args{ + filePath: "/etc/alpine-release", + testFilePath: "testdata/no-permission", + }, + want: &analyzer.AnalysisResult{}, + }, + { + name: "sad path with opener error", + args: args{ + filePath: "/lib/apk/db/installed", + testFilePath: "testdata/error", + }, + wantErr: "unable to open /lib/apk/db/installed", + }, + { + name: "sad path with broken file pattern regex", + args: args{ + filePath: "/app/Gemfile-dev.lock", + testFilePath: "testdata/app/Gemfile.lock", + filePatterns: []string{"bundler:Gemfile(-.*?\\.lock"}, + }, + wantErr: "error parsing regexp", + }, + { + name: "sad path with broken file pattern", + args: args{ + filePath: "/app/Gemfile-dev.lock", + testFilePath: "testdata/app/Gemfile.lock", + filePatterns: []string{"Gemfile(-.*)?\\.lock"}, + }, + wantErr: "invalid file pattern", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var wg sync.WaitGroup + limit := semaphore.NewWeighted(3) + + got := new(analyzer.AnalysisResult) + a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{ + FilePatterns: tt.args.filePatterns, + DisabledAnalyzers: tt.args.disabledAnalyzers, + }) + if err != nil && tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + + info, err := os.Stat(tt.args.testFilePath) + require.NoError(t, err) + + ctx := context.Background() + err = a.AnalyzeFile(ctx, &wg, limit, got, "", tt.args.filePath, info, + func() (xio.ReadSeekCloserAt, error) { + if tt.args.testFilePath == "testdata/error" { + return nil, xerrors.New("error") + } else if tt.args.testFilePath == "testdata/no-permission" { + os.Chmod(tt.args.testFilePath, 0000) + t.Cleanup(func() { + os.Chmod(tt.args.testFilePath, 0644) + }) + } + return os.Open(tt.args.testFilePath) + }, + nil, analyzer.AnalysisOptions{}, + ) + + wg.Wait() + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestAnalyzerGroup_PostAnalyze(t *testing.T) { + tests := []struct { + name string + dir string + analyzerType analyzer.Type + want *analyzer.AnalysisResult + }{ + { + name: "jars with invalid jar", + dir: "testdata/post-apps/jar/", + analyzerType: analyzer.TypeJar, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Jar, + FilePath: "testdata/post-apps/jar/jackson-annotations-2.15.0-rc2.jar", + Libraries: types.Packages{ + { + Name: "com.fasterxml.jackson.core:jackson-annotations", + Version: "2.15.0-rc2", + FilePath: "testdata/post-apps/jar/jackson-annotations-2.15.0-rc2.jar", + }, + }, + }, + }, + }, + }, + { + name: "poetry files with invalid file", + dir: "testdata/post-apps/poetry/", + analyzerType: analyzer.TypePoetry, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Poetry, + FilePath: "testdata/post-apps/poetry/happy/poetry.lock", + Libraries: types.Packages{ + { + ID: "certifi@2022.12.7", + Name: "certifi", + Version: "2022.12.7", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + // Create a virtual filesystem + composite, err := analyzer.NewCompositeFS(analyzer.AnalyzerGroup{}) + require.NoError(t, err) + + mfs := mapfs.New() + require.NoError(t, mfs.CopyFilesUnder(tt.dir)) + composite.Set(tt.analyzerType, mfs) + + if tt.analyzerType == analyzer.TypeJar { + // init java-trivy-db with skip update + repo, err := name.NewTag(javadb.DefaultRepository) + require.NoError(t, err) + javadb.Init("./language/java/jar/testdata", repo, true, false, types.RegistryOptions{Insecure: false}) + } + + ctx := context.Background() + got := new(analyzer.AnalysisResult) + err = a.PostAnalyze(ctx, composite, got, analyzer.AnalysisOptions{}) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestAnalyzerGroup_AnalyzerVersions(t *testing.T) { + tests := []struct { + name string + disabled []analyzer.Type + want analyzer.Versions + }{ + { + name: "happy path", + disabled: []analyzer.Type{}, + want: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "apk-repo": 1, + "apk": 2, + "bundler": 1, + "ubuntu": 1, + "ubuntu-esm": 1, + }, + PostAnalyzers: map[string]int{ + "jar": 1, + "poetry": 1, + }, + }, + }, + { + name: "disable analyzers", + disabled: []analyzer.Type{ + analyzer.TypeAlpine, + analyzer.TypeApkRepo, + analyzer.TypeUbuntu, + analyzer.TypeUbuntuESM, + analyzer.TypeJar, + }, + want: analyzer.Versions{ + Analyzers: map[string]int{ + "apk": 2, + "bundler": 1, + }, + PostAnalyzers: map[string]int{ + "poetry": 1, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{ + DisabledAnalyzers: tt.disabled, + }) + require.NoError(t, err) + got := a.AnalyzerVersions() + fmt.Printf("%v\n", got) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/buildinfo/content_manifest.go b/pkg/fanal/analyzer/buildinfo/content_manifest.go new file mode 100644 index 000000000000..1c99a9783ebe --- /dev/null +++ b/pkg/fanal/analyzer/buildinfo/content_manifest.go @@ -0,0 +1,55 @@ +package buildinfo + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&contentManifestAnalyzer{}) +} + +const contentManifestAnalyzerVersion = 1 + +type contentManifest struct { + ContentSets []string `json:"content_sets"` +} + +// For Red Hat products +type contentManifestAnalyzer struct{} + +func (a contentManifestAnalyzer) Analyze(_ context.Context, target analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + var manifest contentManifest + if err := json.NewDecoder(target.Content).Decode(&manifest); err != nil { + return nil, xerrors.Errorf("invalid content manifest: %w", err) + } + + return &analyzer.AnalysisResult{ + BuildInfo: &types.BuildInfo{ + ContentSets: manifest.ContentSets, + }, + }, nil +} + +func (a contentManifestAnalyzer) Required(filePath string, _ os.FileInfo) bool { + dir, file := filepath.Split(filepath.ToSlash(filePath)) + if dir != "root/buildinfo/content_manifests/" { + return false + } + return filepath.Ext(file) == ".json" +} + +func (a contentManifestAnalyzer) Type() analyzer.Type { + return analyzer.TypeRedHatContentManifestType +} + +func (a contentManifestAnalyzer) Version() int { + return contentManifestAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/buildinfo/content_manifest_test.go b/pkg/fanal/analyzer/buildinfo/content_manifest_test.go new file mode 100644 index 000000000000..c7d8b880f5a1 --- /dev/null +++ b/pkg/fanal/analyzer/buildinfo/content_manifest_test.go @@ -0,0 +1,88 @@ +package buildinfo + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_contentManifestAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + input string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + input: "testdata/content_manifests/ubi8-minimal-container-8.5-218.json", + want: &analyzer.AnalysisResult{ + BuildInfo: &types.BuildInfo{ + ContentSets: []string{ + "rhel-8-for-x86_64-baseos-rpms", + "rhel-8-for-x86_64-appstream-rpms", + }, + }, + }, + }, + { + name: "broken json", + input: "testdata/content_manifests/broken.json", + wantErr: "invalid content manifest", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.input) + require.NoError(t, err) + defer f.Close() + + a := contentManifestAnalyzer{} + got, err := a.Analyze(context.Background(), analyzer.AnalysisInput{ + FilePath: tt.input, + Content: f, + }) + + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_contentManifestAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path", + filePath: "root/buildinfo/content_manifests/nodejs-12-container-1-66.json", + want: true, + }, + { + name: "sad path", + filePath: "root/buildinfo/content_manifests/nodejs-12-container-1-66.xml", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := contentManifestAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/buildinfo/dockerfile.go b/pkg/fanal/analyzer/buildinfo/dockerfile.go new file mode 100644 index 000000000000..b14198aa3dd8 --- /dev/null +++ b/pkg/fanal/analyzer/buildinfo/dockerfile.go @@ -0,0 +1,136 @@ +package buildinfo + +import ( + "context" + "os" + "path/filepath" + "strings" + + "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/moby/buildkit/frontend/dockerfile/parser" + "github.com/moby/buildkit/frontend/dockerfile/shell" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&dockerfileAnalyzer{}) +} + +const dockerfileAnalyzerVersion = 1 + +// For Red Hat products +type dockerfileAnalyzer struct{} + +func (a dockerfileAnalyzer) Analyze(_ context.Context, target analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + // ported from https://github.com/moby/buildkit/blob/b33357bcd2e3319b0323037c900c13b45a228df1/frontend/dockerfile/dockerfile2llb/convert.go#L73 + dockerfile, err := parser.Parse(target.Content) + if err != nil { + return nil, xerrors.Errorf("dockerfile parse error: %w", err) + } + + stages, metaArgs, err := instructions.Parse(dockerfile.AST) + if err != nil { + return nil, xerrors.Errorf("instruction parse error: %w", err) + } + + var args []instructions.KeyValuePairOptional + for _, cmd := range metaArgs { + for _, metaArg := range cmd.Args { + args = append(args, setKVValue(metaArg, nil)) + } + } + + shlex := shell.NewLex(dockerfile.EscapeToken) + env := metaArgsToMap(args) + + var component, arch string + for _, st := range stages { + for _, cmd := range st.Commands { + switch c := cmd.(type) { + case *instructions.EnvCommand: + for _, kvp := range c.Env { + env[kvp.Key] = kvp.Value + } + case *instructions.LabelCommand: + for _, kvp := range c.Labels { + key, err := shlex.ProcessWordWithMap(kvp.Key, env) + if err != nil { + return nil, xerrors.Errorf("unable to evaluate the label '%s': %w", kvp.Key, err) + } + + key = strings.ToLower(key) + if key == "com.redhat.component" || key == "bzcomponent" { + component, err = shlex.ProcessWordWithMap(kvp.Value, env) + } else if key == "architecture" { + arch, err = shlex.ProcessWordWithMap(kvp.Value, env) + } + + if err != nil { + return nil, xerrors.Errorf("failed to process the label '%s': %w", key, err) + } + } + } + } + } + + if component == "" { + return nil, xerrors.New("no component found") + } else if arch == "" { + return nil, xerrors.New("no arch found") + } + + return &analyzer.AnalysisResult{ + BuildInfo: &types.BuildInfo{ + Nvr: component + "-" + parseVersion(target.FilePath), + Arch: arch, + }, + }, nil +} + +func (a dockerfileAnalyzer) Required(filePath string, _ os.FileInfo) bool { + dir, file := filepath.Split(filepath.ToSlash(filePath)) + if dir != "root/buildinfo/" { + return false + } + return strings.HasPrefix(file, "Dockerfile") +} + +func (a dockerfileAnalyzer) Type() analyzer.Type { + return analyzer.TypeRedHatDockerfileType +} + +func (a dockerfileAnalyzer) Version() int { + return dockerfileAnalyzerVersion +} + +// parseVersion parses version from a file name +func parseVersion(nvr string) string { + releaseIndex := strings.LastIndex(nvr, "-") + if releaseIndex < 0 { + return "" + } + versionIndex := strings.LastIndex(nvr[:releaseIndex], "-") + version := nvr[versionIndex+1:] + return version +} + +// https://github.com/moby/buildkit/blob/b33357bcd2e3319b0323037c900c13b45a228df1/frontend/dockerfile/dockerfile2llb/convert.go#L474-L482 +func metaArgsToMap(metaArgs []instructions.KeyValuePairOptional) map[string]string { + m := make(map[string]string) + + for _, arg := range metaArgs { + m[arg.Key] = arg.ValueString() + } + + return m +} + +func setKVValue(kvpo instructions.KeyValuePairOptional, values map[string]string) instructions.KeyValuePairOptional { + if v, ok := values[kvpo.Key]; ok { + kvpo.Value = &v + } + return kvpo +} diff --git a/pkg/fanal/analyzer/buildinfo/dockerfile_test.go b/pkg/fanal/analyzer/buildinfo/dockerfile_test.go new file mode 100644 index 000000000000..2cecfdaa5388 --- /dev/null +++ b/pkg/fanal/analyzer/buildinfo/dockerfile_test.go @@ -0,0 +1,95 @@ +package buildinfo + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_dockerfileAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "com.redhat.component", + inputFile: "testdata/dockerfile/Dockerfile-ubi8-8.3-227", + want: &analyzer.AnalysisResult{ + BuildInfo: &types.BuildInfo{ + Nvr: "ubi8-container-8.3-227", + Arch: "x86_64", + }, + }, + }, + { + name: "BZcomponent", + inputFile: "testdata/dockerfile/Dockerfile-jboss-base-7-base-1.1-3", + want: &analyzer.AnalysisResult{ + BuildInfo: &types.BuildInfo{ + Nvr: "jboss-base-7-docker-1.1-3", + Arch: "x86_64", + }, + }, + }, + { + name: "missing architecture", + inputFile: "testdata/dockerfile/Dockerfile.sad", + wantErr: "no arch found", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := dockerfileAnalyzer{} + got, err := a.Analyze(context.Background(), analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + if tt.wantErr != "" { + require.Error(t, err) + assert.Equal(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_dockerfileAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path", + filePath: "root/buildinfo/Dockerfile-ubi8-8.3-227", + want: true, + }, + { + name: "sad path", + filePath: "app/Dockerfile-ubi8-8.3-227", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := dockerfileAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/buildinfo/testdata/content_manifests/broken.json b/pkg/fanal/analyzer/buildinfo/testdata/content_manifests/broken.json new file mode 100644 index 000000000000..86a410dd1d33 --- /dev/null +++ b/pkg/fanal/analyzer/buildinfo/testdata/content_manifests/broken.json @@ -0,0 +1 @@ +broken \ No newline at end of file diff --git a/pkg/fanal/analyzer/buildinfo/testdata/content_manifests/ubi8-minimal-container-8.5-218.json b/pkg/fanal/analyzer/buildinfo/testdata/content_manifests/ubi8-minimal-container-8.5-218.json new file mode 100644 index 000000000000..8653f337011c --- /dev/null +++ b/pkg/fanal/analyzer/buildinfo/testdata/content_manifests/ubi8-minimal-container-8.5-218.json @@ -0,0 +1,12 @@ +{ + "metadata": { + "icm_version": 1, + "icm_spec": "https://raw.githubusercontent.com/containerbuildsystem/atomic-reactor/master/atomic_reactor/schemas/content_manifest.json", + "image_layer_index": 1 + }, + "content_sets": [ + "rhel-8-for-x86_64-baseos-rpms", + "rhel-8-for-x86_64-appstream-rpms" + ], + "image_contents": [] +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/buildinfo/testdata/dockerfile/Dockerfile-jboss-base-7-base-1.1-3 b/pkg/fanal/analyzer/buildinfo/testdata/dockerfile/Dockerfile-jboss-base-7-base-1.1-3 new file mode 100644 index 000000000000..4f9f92911b21 --- /dev/null +++ b/pkg/fanal/analyzer/buildinfo/testdata/dockerfile/Dockerfile-jboss-base-7-base-1.1-3 @@ -0,0 +1,35 @@ +# registry.redhat.io/openshift3/metrics-cassandra:3.1.0 +FROM 82ad5fa11820c2889c60f7f748d67aab04400700c581843db0d1e68735327443 +MAINTAINER JBoss Cloud Enablement Feedback + +ENV base jboss-base + +LABEL BZComponent="${base}-7-docker" \ + Architecture="x86_64" \ + Name="jboss-base-7/base" \ + Version="1.1" \ + Release="3" + +# Explicitly set the $HOME env variable so it can be referenced in Dockerfiles +ENV HOME /home/jboss + +ADD jboss.repo /etc/yum.repos.d/jboss.repo + +# Install unzip and tar package which is required to unpack product distributions +# Cleanup the YUM metadata +RUN yum -y --disablerepo \* --enablerepo=jboss install yum-utils unzip tar && \ + yum clean all + +RUN rm /etc/yum.repos.d/jboss.repo + +# Create a user and group used to launch processes +# We use the ID 185 fot the group as well as for the user. +# This ID is registered static ID for the JBoss EAP product +# on RHEL which makes it safe to use. +RUN groupadd -r jboss -g 185 && useradd -u 185 -r -g jboss -m -d /home/jboss -s /sbin/nologin -c "JBoss user" jboss + +# Set the working directory to jboss' user home directory +WORKDIR /home/jboss + +# Specify the user which should be used to execute all commands below +USER jboss diff --git a/pkg/fanal/analyzer/buildinfo/testdata/dockerfile/Dockerfile-ubi8-8.3-227 b/pkg/fanal/analyzer/buildinfo/testdata/dockerfile/Dockerfile-ubi8-8.3-227 new file mode 100644 index 000000000000..910ae14e0577 --- /dev/null +++ b/pkg/fanal/analyzer/buildinfo/testdata/dockerfile/Dockerfile-ubi8-8.3-227 @@ -0,0 +1,30 @@ +FROM sha256:4224eead35ea350b4b9d4ac67550e92efb9a50d3855cb3381469fe4c7e3f2053 + +LABEL maintainer="Red Hat, Inc." + +LABEL com.redhat.component="ubi8-container" \ + name="ubi8" \ + version="8.3" + +#label for EULA +LABEL com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" + +#labels for container catalog +LABEL summary="Provides the latest release of Red Hat Universal Base Image 8." +LABEL description="The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly." +LABEL io.k8s.display-name="Red Hat Universal Base Image 8" +LABEL io.openshift.expose-services="" +LABEL io.openshift.tags="base rhel8" + +ENV container oci +ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ENV arch x86_64 + +CMD ["/bin/bash"] + +RUN rm -rf /var/log/* +#rhbz 1609043 +RUN mkdir -p /var/log/rhsm + +ADD ubi8-container-8.3-227.json /root/buildinfo/content_manifests/ubi8-container-8.3-227.json +LABEL "release"="227" "distribution-scope"="public" "vendor"="Red Hat, Inc." "build-date"="2020-12-10T01:59:40.343735" "architecture"=$arch "vcs-type"="git" "vcs-ref"="3652f52021079930cba3bf90d27d9f191b18115b" "com.redhat.build-host"="cpt-1002.osbs.prod.upshift.rdu2.redhat.com" "io.k8s.description"="The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly." "url"="https://access.redhat.com/containers/#/registry.access.redhat.com/ubi8/images/8.3-227" diff --git a/pkg/fanal/analyzer/buildinfo/testdata/dockerfile/Dockerfile.sad b/pkg/fanal/analyzer/buildinfo/testdata/dockerfile/Dockerfile.sad new file mode 100644 index 000000000000..96e596df5c6c --- /dev/null +++ b/pkg/fanal/analyzer/buildinfo/testdata/dockerfile/Dockerfile.sad @@ -0,0 +1,28 @@ +FROM sha256:4224eead35ea350b4b9d4ac67550e92efb9a50d3855cb3381469fe4c7e3f2053 + +LABEL maintainer="Red Hat, Inc." + +LABEL com.redhat.component="ubi8-container" \ + name="ubi8" \ + version="8.3" + +#label for EULA +LABEL com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" + +#labels for container catalog +LABEL summary="Provides the latest release of Red Hat Universal Base Image 8." +LABEL description="The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly." +LABEL io.k8s.display-name="Red Hat Universal Base Image 8" +LABEL io.openshift.expose-services="" +LABEL io.openshift.tags="base rhel8" + +ENV container oci +ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +CMD ["/bin/bash"] + +RUN rm -rf /var/log/* +#rhbz 1609043 +RUN mkdir -p /var/log/rhsm + +ADD ubi8-container-8.3-227.json /root/buildinfo/content_manifests/ubi8-container-8.3-227.json diff --git a/pkg/fanal/analyzer/config/all/import.go b/pkg/fanal/analyzer/config/all/import.go new file mode 100644 index 000000000000..b171ab5e8a7f --- /dev/null +++ b/pkg/fanal/analyzer/config/all/import.go @@ -0,0 +1,12 @@ +package all + +import ( + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/azurearm" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/cloudformation" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/dockerfile" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/helm" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/k8s" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraform" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraformplan/json" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraformplan/snapshot" +) diff --git a/pkg/fanal/analyzer/config/azurearm/azurearm.go b/pkg/fanal/analyzer/config/azurearm/azurearm.go new file mode 100644 index 000000000000..3c0c4b9828f1 --- /dev/null +++ b/pkg/fanal/analyzer/config/azurearm/azurearm.go @@ -0,0 +1,38 @@ +package azurearm + +import ( + "os" + "path/filepath" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +const ( + version = 1 + analyzerType = analyzer.TypeAzureARM +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newAzureARMConfigAnalyzer) +} + +// azureARMConfigAnalyzer is an analyzer for detecting misconfigurations in Azure ARM templates. +// It embeds config.Analyzer so it can implement analyzer.PostAnalyzer. +type azureARMConfigAnalyzer struct { + *config.Analyzer +} + +func newAzureARMConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, misconf.NewAzureARMScanner, opts) + if err != nil { + return nil, err + } + return &azureARMConfigAnalyzer{Analyzer: a}, nil +} + +// Required overrides config.Analyzer.Required() and check if the given file is JSON. +func (a *azureARMConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Ext(filePath) == ".json" +} diff --git a/pkg/fanal/analyzer/config/azurearm/azurearm_test.go b/pkg/fanal/analyzer/config/azurearm/azurearm_test.go new file mode 100644 index 000000000000..5546f492504e --- /dev/null +++ b/pkg/fanal/analyzer/config/azurearm/azurearm_test.go @@ -0,0 +1,36 @@ +package azurearm + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" +) + +func Test_azureARMConfigAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "json", + filePath: "test.json", + want: true, + }, + { + name: "yaml", + filePath: "test.yaml", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newAzureARMConfigAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + assert.Equal(t, tt.want, a.Required(tt.filePath, nil)) + }) + } +} diff --git a/pkg/fanal/analyzer/config/cloudformation/cloudformation.go b/pkg/fanal/analyzer/config/cloudformation/cloudformation.go new file mode 100644 index 000000000000..06f7e458a859 --- /dev/null +++ b/pkg/fanal/analyzer/config/cloudformation/cloudformation.go @@ -0,0 +1,30 @@ +package cloudformation + +import ( + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +const ( + analyzerType = analyzer.TypeCloudFormation + version = 1 +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newCloudFormationConfigAnalyzer) +} + +// cloudFormationConfigAnalyzer is an analyzer for detecting misconfigurations in CloudFormation files. +// It embeds config.Analyzer so it can implement analyzer.PostAnalyzer. +type cloudFormationConfigAnalyzer struct { + *config.Analyzer +} + +func newCloudFormationConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, misconf.NewCloudFormationScanner, opts) + if err != nil { + return nil, err + } + return &cloudFormationConfigAnalyzer{Analyzer: a}, nil +} diff --git a/pkg/fanal/analyzer/config/config.go b/pkg/fanal/analyzer/config/config.go new file mode 100644 index 000000000000..b5f3569d0ab4 --- /dev/null +++ b/pkg/fanal/analyzer/config/config.go @@ -0,0 +1,65 @@ +package config + +import ( + "context" + "os" + "path/filepath" + + "golang.org/x/xerrors" + "k8s.io/utils/strings/slices" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +var ( + _ analyzer.PostAnalyzer = (*Analyzer)(nil) + + requiredExts = []string{".json", ".yaml", ".yml", ".tfvars"} +) + +// Analyzer represents an analyzer for config files, +// which is embedded into each config analyzer such as Kubernetes. +type Analyzer struct { + typ analyzer.Type + version int + scanner *misconf.Scanner +} + +type NewScanner func([]string, misconf.ScannerOption) (*misconf.Scanner, error) + +func NewAnalyzer(t analyzer.Type, version int, newScanner NewScanner, opts analyzer.AnalyzerOptions) (*Analyzer, error) { + s, err := newScanner(opts.FilePatterns, opts.MisconfScannerOption) + if err != nil { + return nil, xerrors.Errorf("%s scanner init error: %w", t, err) + } + return &Analyzer{ + typ: t, + version: version, + scanner: s, + }, nil +} + +// PostAnalyze performs configuration analysis on the input filesystem and detect misconfigurations. +func (a *Analyzer) PostAnalyze(ctx context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + misconfs, err := a.scanner.Scan(ctx, input.FS) + if err != nil { + return nil, xerrors.Errorf("%s scan error: %w", a.typ, err) + } + return &analyzer.AnalysisResult{Misconfigurations: misconfs}, nil +} + +// Required checks if the given file path has one of the required file extensions. +func (a *Analyzer) Required(filePath string, _ os.FileInfo) bool { + return slices.Contains(requiredExts, filepath.Ext(filePath)) +} + +// Type returns the analyzer type of the current Analyzer instance. +func (a *Analyzer) Type() analyzer.Type { + return a.typ +} + +// Version returns the version of the current Analyzer instance. +func (a *Analyzer) Version() int { + return a.version +} diff --git a/pkg/fanal/analyzer/config/config_test.go b/pkg/fanal/analyzer/config/config_test.go new file mode 100644 index 000000000000..147b1f4d3201 --- /dev/null +++ b/pkg/fanal/analyzer/config/config_test.go @@ -0,0 +1,106 @@ +package config_test + +import ( + "context" + "os" + "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/types" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +func TestAnalyzer_PostAnalyze(t *testing.T) { + type fields struct { + typ analyzer.Type + newScanner config.NewScanner + opts analyzer.AnalyzerOptions + } + tests := []struct { + name string + fields fields + dir string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "dockerfile", + fields: fields{ + typ: analyzer.TypeDockerfile, + newScanner: misconf.NewDockerfileScanner, + opts: analyzer.AnalyzerOptions{ + MisconfScannerOption: misconf.ScannerOption{ + Namespaces: []string{"user"}, + PolicyPaths: []string{"testdata/rego"}, + DisableEmbeddedPolicies: true, + }, + }, + }, + dir: "testdata/src", + want: &analyzer.AnalysisResult{ + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.Dockerfile, + FilePath: "Dockerfile", + Successes: types.MisconfResults{ + types.MisconfResult{ + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Dockerfile Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + { + name: "non-existent dir", + fields: fields{ + typ: analyzer.TypeDockerfile, + newScanner: misconf.NewDockerfileScanner, + opts: analyzer.AnalyzerOptions{ + MisconfScannerOption: misconf.ScannerOption{ + Namespaces: []string{"user"}, + PolicyPaths: []string{"testdata/rego"}, + DisableEmbeddedPolicies: true, + }, + }, + }, + dir: "testdata/non-existent", + wantErr: testutil.ErrNotExist, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := config.NewAnalyzer(tt.fields.typ, 0, tt.fields.newScanner, tt.fields.opts) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/config/dockerfile/docker.go b/pkg/fanal/analyzer/config/dockerfile/docker.go new file mode 100644 index 000000000000..353cef4eb62a --- /dev/null +++ b/pkg/fanal/analyzer/config/dockerfile/docker.go @@ -0,0 +1,54 @@ +package dockerfile + +import ( + "os" + "path/filepath" + "strings" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +const ( + version = 1 + analyzerType = analyzer.TypeDockerfile +) + +var requiredFiles = []string{"Dockerfile", "Containerfile"} + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newDockerfileConfigAnalyzer) +} + +// dockerConfigAnalyzer is an analyzer for detecting misconfigurations in Dockerfiles. +// It embeds config.Analyzer so it can implement analyzer.PostAnalyzer. +type dockerConfigAnalyzer struct { + *config.Analyzer +} + +func newDockerfileConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, misconf.NewDockerfileScanner, opts) + if err != nil { + return nil, err + } + return &dockerConfigAnalyzer{Analyzer: a}, nil +} + +// Required does a case-insensitive check for filePath and returns true if +// filePath equals/startsWith/hasExtension requiredFiles +// It overrides config.Analyzer.Required(). +func (a *dockerConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { + base := filepath.Base(filePath) + ext := filepath.Ext(base) + for _, file := range requiredFiles { + if strings.EqualFold(base, file+ext) { + return true + } + if strings.EqualFold(ext, "."+file) { + return true + } + } + + return false +} diff --git a/pkg/fanal/analyzer/config/dockerfile/docker_test.go b/pkg/fanal/analyzer/config/dockerfile/docker_test.go new file mode 100644 index 000000000000..1a791be8bff1 --- /dev/null +++ b/pkg/fanal/analyzer/config/dockerfile/docker_test.go @@ -0,0 +1,73 @@ +package dockerfile + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_dockerConfigAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "dockerfile", + filePath: "dockerfile", + want: true, + }, + { + name: "Dockerfile", + filePath: "Dockerfile", + want: true, + }, + { + name: "Dockerfile with ext", + filePath: "Dockerfile.build", + want: true, + }, + { + name: "dockerfile as ext", + filePath: "build.dockerfile", + want: true, + }, + { + name: "Dockerfile in dir", + filePath: "docker/Dockerfile", + want: true, + }, + { + name: "Dockerfile as prefix", + filePath: "Dockerfilebuild", + want: false, + }, + { + name: "Dockerfile as suffix", + filePath: "buildDockerfile", + want: false, + }, + { + name: "Dockerfile as prefix with ext", + filePath: "Dockerfilebuild.sh", + want: false, + }, + { + name: "Dockerfile as suffix with ext", + filePath: "buildDockerfile.sh", + want: false, + }, + { + name: "json", + filePath: "deployment.json", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := dockerConfigAnalyzer{} + got := s.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/config/helm/helm.go b/pkg/fanal/analyzer/config/helm/helm.go new file mode 100644 index 000000000000..14ea6aff63de --- /dev/null +++ b/pkg/fanal/analyzer/config/helm/helm.go @@ -0,0 +1,60 @@ +package helm + +import ( + "os" + "path/filepath" + "strings" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +const ( + analyzerType = analyzer.TypeHelm + version = 1 + maxTarSize = 209_715_200 // 200MB +) + +var acceptedExts = []string{".tpl", ".json", ".yml", ".yaml", ".tar", ".tgz", ".tar.gz"} + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newHelmConfigAnalyzer) +} + +// helmConfigAnalyzer is an analyzer for detecting misconfigurations in Helm charts. +// It embeds config.Analyzer so it can implement analyzer.PostAnalyzer. +type helmConfigAnalyzer struct { + *config.Analyzer +} + +func newHelmConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, misconf.NewHelmScanner, opts) + if err != nil { + return nil, err + } + return &helmConfigAnalyzer{Analyzer: a}, nil +} + +// Required overrides config.Analyzer.Required() and checks if the given file is a Helm chart. +func (*helmConfigAnalyzer) Required(filePath string, info os.FileInfo) bool { + if info.Size() > maxTarSize { + // tarball is too big to be Helm chart - move on + return false + } + + for _, acceptable := range acceptedExts { + if strings.HasSuffix(strings.ToLower(filePath), acceptable) { + return true + } + } + + name := filepath.Base(filePath) + for _, acceptable := range []string{"Chart.yaml", ".helmignore"} { + if strings.EqualFold(name, acceptable) { + return true + } + } + + return false +} diff --git a/pkg/fanal/analyzer/config/helm/helm_test.go b/pkg/fanal/analyzer/config/helm/helm_test.go new file mode 100644 index 000000000000..982020769f50 --- /dev/null +++ b/pkg/fanal/analyzer/config/helm/helm_test.go @@ -0,0 +1,75 @@ +package helm + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_helmConfigAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "yaml", + filePath: "Chart.yaml", + want: true, + }, + { + name: "yaml - shorthand", + filePath: "templates/deployment.yml", + want: true, + }, + { + name: "tpl", + filePath: "templates/_helpers.tpl", + want: true, + }, + { + name: "json", + filePath: "values.json", + want: true, + }, + { + name: "NOTES.txt", + filePath: "templates/NOTES.txt", + want: false, + }, + { + name: ".helmignore", + filePath: ".helmignore", + want: true, + }, + { + name: "testchart.tgz", + filePath: "testchart.tgz", + want: true, + }, + { + name: "testchart.tar.gz", + filePath: "testchart.tar.gz", + want: true, + }, + { + name: "nope.tgz", + filePath: "nope.tgz", + want: true, // it's a tarball after all + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := helmConfigAnalyzer{} + + // Create a dummy file info + info, err := os.Stat("./helm_test.go") + require.NoError(t, err) + + got := s.Required(tt.filePath, info) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/config/k8s/k8s.go b/pkg/fanal/analyzer/config/k8s/k8s.go new file mode 100644 index 000000000000..6f3a58af16b5 --- /dev/null +++ b/pkg/fanal/analyzer/config/k8s/k8s.go @@ -0,0 +1,30 @@ +package k8s + +import ( + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +const ( + analyzerType = analyzer.TypeKubernetes + version = 1 +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newKubernetesConfigAnalyzer) +} + +// kubernetesConfigAnalyzer is an analyzer for detecting misconfigurations in Kubernetes config files. +// It embeds config.Analyzer so it can implement analyzer.PostAnalyzer. +type kubernetesConfigAnalyzer struct { + *config.Analyzer +} + +func newKubernetesConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, misconf.NewKubernetesScanner, opts) + if err != nil { + return nil, err + } + return &kubernetesConfigAnalyzer{Analyzer: a}, nil +} diff --git a/pkg/fanal/analyzer/config/terraform/terraform.go b/pkg/fanal/analyzer/config/terraform/terraform.go new file mode 100644 index 000000000000..363d35de87fe --- /dev/null +++ b/pkg/fanal/analyzer/config/terraform/terraform.go @@ -0,0 +1,38 @@ +package terraform + +import ( + "os" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +const ( + analyzerType = analyzer.TypeTerraform + version = 1 +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newTerraformConfigAnalyzer) +} + +// terraformConfigAnalyzer is an analyzer for detecting misconfigurations in Terraform files. +// It embeds config.Analyzer so it can implement analyzer.PostAnalyzer. +type terraformConfigAnalyzer struct { + *config.Analyzer +} + +func newTerraformConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformScanner, opts) + if err != nil { + return nil, err + } + return &terraformConfigAnalyzer{Analyzer: a}, nil +} + +// Required overrides config.Analyzer.Required() and checks if the given file is a Terraform file. +func (*terraformConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return detection.IsTerraformFile(filePath) +} diff --git a/pkg/fanal/analyzer/config/terraform/terraform_test.go b/pkg/fanal/analyzer/config/terraform/terraform_test.go new file mode 100644 index 000000000000..9096ee062a5e --- /dev/null +++ b/pkg/fanal/analyzer/config/terraform/terraform_test.go @@ -0,0 +1,53 @@ +package terraform + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfigAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "tf", + filePath: "/path/to/main.tf", + want: true, + }, + { + name: "tf.json", + filePath: "/path/to/main.tf.json", + want: true, + }, + { + name: "tfvars", + filePath: "/path/to/some.tfvars", + want: true, + }, + { + name: "json", + filePath: "/path/to/some.json", + want: false, + }, + { + name: "hcl", + filePath: "/path/to/main.hcl", + want: false, + }, + { + name: "yaml", + filePath: "deployment.yaml", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := terraformConfigAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/config/terraformplan/json/json.go b/pkg/fanal/analyzer/config/terraformplan/json/json.go new file mode 100644 index 000000000000..5272f0f990f9 --- /dev/null +++ b/pkg/fanal/analyzer/config/terraformplan/json/json.go @@ -0,0 +1,44 @@ +package terraformplan + +import ( + "os" + "path/filepath" + + "k8s.io/utils/strings/slices" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +const ( + analyzerType = analyzer.TypeTerraformPlanJSON + version = 1 +) + +var requiredExts = []string{ + ".json", +} + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newTerraformPlanJSONConfigAnalyzer) +} + +// terraformPlanConfigAnalyzer is an analyzer for detecting misconfigurations in Terraform Plan files in JSON format. +// It embeds config.Analyzer so it can implement analyzer.PostAnalyzer. +type terraformPlanConfigAnalyzer struct { + *config.Analyzer +} + +func newTerraformPlanJSONConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformPlanJSONScanner, opts) + if err != nil { + return nil, err + } + return &terraformPlanConfigAnalyzer{Analyzer: a}, nil +} + +// Required overrides config.Analyzer.Required() and checks if the given file is a Terraform Plan file in JSON format. +func (*terraformPlanConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return slices.Contains(requiredExts, filepath.Ext(filePath)) +} diff --git a/pkg/fanal/analyzer/config/terraformplan/json/json_test.go b/pkg/fanal/analyzer/config/terraformplan/json/json_test.go new file mode 100644 index 000000000000..0e0ec3d2e9fe --- /dev/null +++ b/pkg/fanal/analyzer/config/terraformplan/json/json_test.go @@ -0,0 +1,38 @@ +package terraformplan + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfigAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path", + filePath: "/path/to/tfplan.json", + want: true, + }, + { + name: "hcl", + filePath: "/path/to/main.hcl", + want: false, + }, + { + name: "yaml", + filePath: "deployment.yaml", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := terraformPlanConfigAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go new file mode 100644 index 000000000000..0597c137d96c --- /dev/null +++ b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go @@ -0,0 +1,37 @@ +package terraformplan + +import ( + "os" + "path/filepath" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +const ( + analyzerType = analyzer.TypeTerraformPlanSnapshot + version = 1 +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newTerraformPlanSnapshotConfigAnalyzer) +} + +// terraformPlanConfigAnalyzer is an analyzer for detecting misconfigurations in Terraform Plan files in snapshot format. +// It embeds config.Analyzer so it can implement analyzer.PostAnalyzer. +type terraformPlanConfigAnalyzer struct { + *config.Analyzer +} + +func newTerraformPlanSnapshotConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformPlanSnapshotScanner, opts) + if err != nil { + return nil, err + } + return &terraformPlanConfigAnalyzer{Analyzer: a}, nil +} + +func (*terraformPlanConfigAnalyzer) Required(filePath string, fi os.FileInfo) bool { + return filepath.Ext(filePath) == ".tfplan" || filepath.Base(filePath) == "tfplan" +} diff --git a/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot_test.go b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot_test.go new file mode 100644 index 000000000000..22bd69e5f357 --- /dev/null +++ b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot_test.go @@ -0,0 +1,38 @@ +package terraformplan + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfigAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "tfplan as extension", + filePath: "/path/to/test.tfplan", + want: true, + }, + { + name: "without extension", + filePath: "/path/to/tfplan", + want: true, + }, + { + name: "bad path", + filePath: "/path/to/mytfplan.txt", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := terraformPlanConfigAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/config/testdata/rego/policy.rego b/pkg/fanal/analyzer/config/testdata/rego/policy.rego new file mode 100644 index 000000000000..ab4daa223c8b --- /dev/null +++ b/pkg/fanal/analyzer/config/testdata/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", object.get(metadata, "StartLine", 0)), + "endline": object.get(metadata, "endline", object.get(metadata, "EndLine", 0)), + "filepath": object.get(metadata, "filepath", object.get(metadata, "Path", "")), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + cmd := input.stages[_][_] + res := result("No commands allowed!", cmd) +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/config/testdata/src/Dockerfile b/pkg/fanal/analyzer/config/testdata/src/Dockerfile new file mode 100644 index 000000000000..6d4b4c49ea2e --- /dev/null +++ b/pkg/fanal/analyzer/config/testdata/src/Dockerfile @@ -0,0 +1 @@ +FROM ubuntu \ No newline at end of file diff --git a/pkg/fanal/analyzer/config_analyzer.go b/pkg/fanal/analyzer/config_analyzer.go new file mode 100644 index 000000000000..651b936f1ac7 --- /dev/null +++ b/pkg/fanal/analyzer/config_analyzer.go @@ -0,0 +1,124 @@ +package analyzer + +import ( + "context" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +var configAnalyzerConstructors = make(map[Type]configAnalyzerConstructor) + +type configAnalyzerConstructor func(ConfigAnalyzerOptions) (ConfigAnalyzer, error) + +// RegisterConfigAnalyzer adds a constructor of config analyzer +func RegisterConfigAnalyzer(t Type, init configAnalyzerConstructor) { + configAnalyzerConstructors[t] = init +} + +// DeregisterConfigAnalyzer is mainly for testing +func DeregisterConfigAnalyzer(t Type) { + delete(configAnalyzerConstructors, t) +} + +// ConfigAnalyzer defines an interface for analyzer of container image config +type ConfigAnalyzer interface { + Type() Type + Version() int + Analyze(ctx context.Context, input ConfigAnalysisInput) (*ConfigAnalysisResult, error) + Required(osFound types.OS) bool +} + +// ConfigAnalyzerOptions is used to initialize config analyzers +type ConfigAnalyzerOptions struct { + FilePatterns []string + DisabledAnalyzers []Type + MisconfScannerOption misconf.ScannerOption + SecretScannerOption SecretScannerOption +} + +type ConfigAnalysisInput struct { + OS types.OS + Config *v1.ConfigFile +} + +type ConfigAnalysisResult struct { + Misconfiguration *types.Misconfiguration + Secret *types.Secret + HistoryPackages types.Packages +} + +func (r *ConfigAnalysisResult) Merge(newResult *ConfigAnalysisResult) { + if newResult == nil { + return + } + if newResult.Misconfiguration != nil { + r.Misconfiguration = newResult.Misconfiguration + } + if newResult.Secret != nil { + r.Secret = newResult.Secret + } + if newResult.HistoryPackages != nil { + r.HistoryPackages = newResult.HistoryPackages + } +} + +type ConfigAnalyzerGroup struct { + configAnalyzers []ConfigAnalyzer +} + +func NewConfigAnalyzerGroup(opts ConfigAnalyzerOptions) (ConfigAnalyzerGroup, error) { + var g ConfigAnalyzerGroup + for t, newConfigAnalyzer := range configAnalyzerConstructors { + // Skip the handler if it is disabled + if slices.Contains(opts.DisabledAnalyzers, t) { + continue + } + a, err := newConfigAnalyzer(opts) + if err != nil { + return ConfigAnalyzerGroup{}, xerrors.Errorf("config analyzer %s initialize error: %w", t, err) + } + + g.configAnalyzers = append(g.configAnalyzers, a) + } + + return g, nil +} + +// AnalyzerVersions returns analyzer version identifier used for cache keys. +func (ag *ConfigAnalyzerGroup) AnalyzerVersions() Versions { + versions := make(map[string]int) + for _, ca := range ag.configAnalyzers { + versions[string(ca.Type())] = ca.Version() + } + return Versions{ + Analyzers: versions, + } +} + +func (ag *ConfigAnalyzerGroup) AnalyzeImageConfig(ctx context.Context, targetOS types.OS, config *v1.ConfigFile) *ConfigAnalysisResult { + input := ConfigAnalysisInput{ + OS: targetOS, + Config: config, + } + result := new(ConfigAnalysisResult) + for _, a := range ag.configAnalyzers { + if !a.Required(targetOS) { + continue + } + + r, err := a.Analyze(ctx, input) + if err != nil { + log.Logger.Debugf("Image config analysis error: %s", err) + continue + } + + result.Merge(r) + } + return result +} diff --git a/pkg/fanal/analyzer/config_analyzer_test.go b/pkg/fanal/analyzer/config_analyzer_test.go new file mode 100644 index 000000000000..454ec680c83d --- /dev/null +++ b/pkg/fanal/analyzer/config_analyzer_test.go @@ -0,0 +1,164 @@ +package analyzer_test + +import ( + "context" + "errors" + "os" + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type mockConfigAnalyzer struct{} + +func newMockConfigAnalyzer(_ analyzer.ConfigAnalyzerOptions) (analyzer.ConfigAnalyzer, error) { + return mockConfigAnalyzer{}, nil +} + +func (mockConfigAnalyzer) Required(targetOS types.OS) bool { + return targetOS.Family == "alpine" +} + +func (mockConfigAnalyzer) Analyze(_ context.Context, input analyzer.ConfigAnalysisInput) (*analyzer.ConfigAnalysisResult, error) { + if input.Config == nil { + return nil, errors.New("error") + } + return &analyzer.ConfigAnalysisResult{ + HistoryPackages: types.Packages{ + { + Name: "musl", + Version: "1.1.24-r2", + }, + }, + }, nil +} + +func (mockConfigAnalyzer) Type() analyzer.Type { + return analyzer.Type("test") +} + +func (mockConfigAnalyzer) Version() int { + return 1 +} + +func TestMain(m *testing.M) { + mock := mockConfigAnalyzer{} + analyzer.RegisterConfigAnalyzer(mock.Type(), newMockConfigAnalyzer) + defer analyzer.DeregisterConfigAnalyzer(mock.Type()) + os.Exit(m.Run()) +} + +func TestAnalyzeConfig(t *testing.T) { + type args struct { + targetOS types.OS + config *v1.ConfigFile + disabledAnalyzers []analyzer.Type + filePatterns []string + } + tests := []struct { + name string + args args + want *analyzer.ConfigAnalysisResult + }{ + { + name: "happy path", + args: args{ + targetOS: types.OS{ + Family: "alpine", + Name: "3.11.6", + }, + config: &v1.ConfigFile{ + OS: "linux", + }, + }, + want: &analyzer.ConfigAnalysisResult{ + HistoryPackages: []types.Package{ + { + Name: "musl", + Version: "1.1.24-r2", + }, + }, + }, + }, + { + name: "non-target OS", + args: args{ + targetOS: types.OS{ + Family: "debian", + Name: "9.2", + }, + config: &v1.ConfigFile{ + OS: "linux", + }, + }, + want: &analyzer.ConfigAnalysisResult{}, + }, + { + name: "Analyze returns an error", + args: args{ + targetOS: types.OS{ + Family: "alpine", + Name: "3.11.6", + }, + }, + want: &analyzer.ConfigAnalysisResult{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := analyzer.NewConfigAnalyzerGroup(analyzer.ConfigAnalyzerOptions{ + FilePatterns: tt.args.filePatterns, + DisabledAnalyzers: tt.args.disabledAnalyzers, + }) + require.NoError(t, err) + got := a.AnalyzeImageConfig(context.Background(), tt.args.targetOS, tt.args.config) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestConfigAnalyzerGroup_AnalyzerVersions(t *testing.T) { + tests := []struct { + name string + disabled []analyzer.Type + want analyzer.Versions + }{ + { + name: "happy path", + disabled: []analyzer.Type{}, + want: analyzer.Versions{ + Analyzers: map[string]int{ + "apk-command": 1, + "test": 1, + }, + }, + }, + { + name: "disable analyzers", + disabled: []analyzer.Type{ + analyzer.TypeAlpine, + analyzer.TypeApkCommand, + }, + want: analyzer.Versions{ + Analyzers: map[string]int{ + "test": 1, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := analyzer.NewConfigAnalyzerGroup(analyzer.ConfigAnalyzerOptions{ + DisabledAnalyzers: tt.disabled, + }) + require.NoError(t, err) + got := a.AnalyzerVersions() + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go new file mode 100644 index 000000000000..29ed8027118f --- /dev/null +++ b/pkg/fanal/analyzer/const.go @@ -0,0 +1,235 @@ +package analyzer + +import ( + "github.com/aquasecurity/trivy/pkg/iac/detection" +) + +type Type string + +const ( + // ====== + // OS + // ====== + TypeOSRelease Type = "os-release" + TypeAlpine Type = "alpine" + TypeAmazon Type = "amazon" + TypeCBLMariner Type = "cbl-mariner" + TypeDebian Type = "debian" + TypePhoton Type = "photon" + TypeCentOS Type = "centos" + TypeRocky Type = "rocky" + TypeAlma Type = "alma" + TypeFedora Type = "fedora" + TypeOracle Type = "oracle" + TypeRedHatBase Type = "redhat" + TypeSUSE Type = "suse" + TypeUbuntu Type = "ubuntu" + TypeUbuntuESM Type = "ubuntu-esm" + + // OS Package + TypeApk Type = "apk" + TypeDpkg Type = "dpkg" + TypeDpkgLicense Type = "dpkg-license" // For analyzing licenses + TypeRpm Type = "rpm" + TypeRpmqa Type = "rpmqa" + + // OS Package Repository + TypeApkRepo Type = "apk-repo" + + // ============================ + // Programming Language Package + // ============================ + + // Ruby + TypeBundler Type = "bundler" + TypeGemSpec Type = "gemspec" + + // Rust + TypeRustBinary Type = "rustbinary" + TypeCargo Type = "cargo" + + // PHP + TypeComposer Type = "composer" + + // Java + TypeJar Type = "jar" + TypePom Type = "pom" + TypeGradleLock Type = "gradle-lockfile" + + // Node.js + TypeNpmPkgLock Type = "npm" + TypeNodePkg Type = "node-pkg" + TypeYarn Type = "yarn" + TypePnpm Type = "pnpm" + + // .NET + TypeNuget Type = "nuget" + TypeDotNetCore Type = "dotnet-core" + TypePackagesProps Type = "packages-props" + + // Conda + TypeCondaPkg Type = "conda-pkg" + + // Python + TypePythonPkg Type = "python-pkg" + TypePip Type = "pip" + TypePipenv Type = "pipenv" + TypePoetry Type = "poetry" + + // Go + TypeGoBinary Type = "gobinary" + TypeGoMod Type = "gomod" + + // C/C++ + TypeConanLock Type = "conan-lock" + + // Elixir + TypeMixLock Type = "mix-lock" + + // Swift + TypeSwift Type = "swift" + TypeCocoaPods Type = "cocoapods" + + // Dart + TypePubSpecLock Type = "pubspec-lock" + + // ============ + // Non-packaged + // ============ + TypeExecutable Type = "executable" + TypeSBOM Type = "sbom" + + // ============ + // Image Config + // ============ + TypeApkCommand Type = "apk-command" + TypeHistoryDockerfile Type = "history-dockerfile" + TypeImageConfigSecret Type = "image-config-secret" + + // ================= + // Structured Config + // ================= + TypeAzureARM Type = Type(detection.FileTypeAzureARM) + TypeCloudFormation Type = Type(detection.FileTypeCloudFormation) + TypeDockerfile Type = Type(detection.FileTypeDockerfile) + TypeHelm Type = Type(detection.FileTypeHelm) + TypeKubernetes Type = Type(detection.FileTypeKubernetes) + TypeTerraform Type = Type(detection.FileTypeTerraform) + TypeTerraformPlanJSON Type = Type(detection.FileTypeTerraformPlanJSON) + TypeTerraformPlanSnapshot Type = Type(detection.FileTypeTerraformPlanSnapshot) + + // ======== + // License + // ======== + TypeLicenseFile Type = "license-file" + + // ======== + // Secrets + // ======== + TypeSecret Type = "secret" + + // ======= + // Red Hat + // ======= + TypeRedHatContentManifestType Type = "redhat-content-manifest" + TypeRedHatDockerfileType Type = "redhat-dockerfile" +) + +var ( + // TypeOSes has all OS-related analyzers + TypeOSes = []Type{ + TypeOSRelease, + TypeAlpine, + TypeAmazon, + TypeCBLMariner, + TypeDebian, + TypePhoton, + TypeCentOS, + TypeRocky, + TypeAlma, + TypeFedora, + TypeOracle, + TypeRedHatBase, + TypeSUSE, + TypeUbuntu, + TypeApk, + TypeDpkg, + TypeDpkgLicense, + TypeRpm, + TypeRpmqa, + TypeApkRepo, + } + + // TypeLanguages has all language analyzers + TypeLanguages = []Type{ + TypeBundler, + TypeGemSpec, + TypeCargo, + TypeComposer, + TypeJar, + TypePom, + TypeGradleLock, + TypeNpmPkgLock, + TypeNodePkg, + TypeYarn, + TypePnpm, + TypeNuget, + TypeDotNetCore, + TypePackagesProps, + TypeCondaPkg, + TypePythonPkg, + TypePip, + TypePipenv, + TypePoetry, + TypeGoBinary, + TypeGoMod, + TypeRustBinary, + TypeConanLock, + TypeCocoaPods, + TypeSwift, + TypePubSpecLock, + TypeMixLock, + } + + // TypeLockfiles has all lock file analyzers + TypeLockfiles = []Type{ + TypeBundler, + TypeNpmPkgLock, + TypeYarn, + TypePnpm, + TypePip, + TypePipenv, + TypePoetry, + TypeGoMod, + TypePom, + TypeConanLock, + TypeGradleLock, + TypeCocoaPods, + TypeSwift, + TypePubSpecLock, + TypeMixLock, + } + + // TypeIndividualPkgs has all analyzers for individual packages + TypeIndividualPkgs = []Type{ + TypeGemSpec, + TypeNodePkg, + TypeCondaPkg, + TypePythonPkg, + TypeGoBinary, + TypeJar, + TypeRustBinary, + } + + // TypeConfigFiles has all config file analyzers + TypeConfigFiles = []Type{ + TypeAzureARM, + TypeCloudFormation, + TypeDockerfile, + TypeHelm, + TypeKubernetes, + TypeTerraform, + TypeTerraformPlanJSON, + TypeTerraformPlanSnapshot, + } +) diff --git a/pkg/fanal/analyzer/executable/executable.go b/pkg/fanal/analyzer/executable/executable.go new file mode 100644 index 000000000000..b484cb0fde3a --- /dev/null +++ b/pkg/fanal/analyzer/executable/executable.go @@ -0,0 +1,53 @@ +package executable + +import ( + "context" + "os" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +func init() { + analyzer.RegisterAnalyzer(&executableAnalyzer{}) +} + +const version = 1 + +// executableAnalyzer calculates SHA-256 for each binary not managed by package managers (called unpackaged binaries) +// so that it can search for SBOM attestation in post-handler. +type executableAnalyzer struct{} + +func (a executableAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + // Skip non-binaries + isBinary, err := utils.IsBinary(input.Content, input.Info.Size()) + if !isBinary || err != nil { + return nil, nil + } + + dig, err := digest.CalcSHA256(input.Content) + if err != nil { + return nil, xerrors.Errorf("sha256 error: %w", err) + } + + return &analyzer.AnalysisResult{ + Digests: map[string]string{ + input.FilePath: dig.String(), + }, + }, nil +} + +func (a executableAnalyzer) Required(_ string, fileInfo os.FileInfo) bool { + return utils.IsExecutable(fileInfo) +} + +func (a executableAnalyzer) Type() analyzer.Type { + return analyzer.TypeExecutable +} + +func (a executableAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/executable/executable_test.go b/pkg/fanal/analyzer/executable/executable_test.go new file mode 100644 index 000000000000..709836a0804e --- /dev/null +++ b/pkg/fanal/analyzer/executable/executable_test.go @@ -0,0 +1,53 @@ +package executable + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" +) + +func Test_executableAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + filePath string + want *analyzer.AnalysisResult + }{ + { + name: "binary", + filePath: "testdata/binary", + want: &analyzer.AnalysisResult{ + Digests: map[string]string{ + "testdata/binary": "sha256:9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a", + }, + }, + }, + { + name: "text", + filePath: "testdata/hello.txt", + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.filePath) + require.NoError(t, err) + defer f.Close() + + stat, err := f.Stat() + require.NoError(t, err) + + a := executableAnalyzer{} + got, err := a.Analyze(context.Background(), analyzer.AnalysisInput{ + FilePath: tt.filePath, + Content: f, + Info: stat, + }) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/executable/testdata/binary b/pkg/fanal/analyzer/executable/testdata/binary new file mode 100644 index 000000000000..82090ee2cbea --- /dev/null +++ b/pkg/fanal/analyzer/executable/testdata/binary @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkg/fanal/analyzer/executable/testdata/hello.txt b/pkg/fanal/analyzer/executable/testdata/hello.txt new file mode 100644 index 000000000000..ce013625030b --- /dev/null +++ b/pkg/fanal/analyzer/executable/testdata/hello.txt @@ -0,0 +1 @@ +hello diff --git a/pkg/fanal/analyzer/fs.go b/pkg/fanal/analyzer/fs.go new file mode 100644 index 000000000000..28880b6b0339 --- /dev/null +++ b/pkg/fanal/analyzer/fs.go @@ -0,0 +1,103 @@ +package analyzer + +import ( + "errors" + "io" + "io/fs" + "os" + "path" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/mapfs" + "github.com/aquasecurity/trivy/pkg/x/sync" +) + +// CompositeFS contains multiple filesystems for post-analyzers +type CompositeFS struct { + group AnalyzerGroup + dir string + files *sync.Map[Type, *mapfs.FS] +} + +func NewCompositeFS(group AnalyzerGroup) (*CompositeFS, error) { + tmpDir, err := os.MkdirTemp("", "analyzer-fs-*") + if err != nil { + return nil, xerrors.Errorf("unable to create temporary directory: %w", err) + } + + return &CompositeFS{ + group: group, + dir: tmpDir, + files: new(sync.Map[Type, *mapfs.FS]), + }, nil +} + +// CopyFileToTemp takes a file path and information, opens the file, copies its contents to a temporary file +func (c *CompositeFS) CopyFileToTemp(opener Opener, info os.FileInfo) (string, error) { + // Create a temporary file to which the file in the layer will be copied + // so that all the files will not be loaded into memory + f, err := os.CreateTemp(c.dir, "file-*") + if err != nil { + return "", xerrors.Errorf("create temp error: %w", err) + } + defer f.Close() + + // Open a file in the layer + r, err := opener() + if err != nil { + return "", xerrors.Errorf("file open error: %w", err) + } + defer r.Close() + + // Copy file content into the temporary file + if _, err = io.Copy(f, r); err != nil { + return "", xerrors.Errorf("copy error: %w", err) + } + + // Use 0600 instead of file permissions to avoid errors when a file uses incorrect permissions (e.g. 0044). + if err = os.Chmod(f.Name(), 0600); err != nil { + return "", xerrors.Errorf("chmod error: %w", err) + } + + return f.Name(), nil +} + +// CreateLink creates a link in the virtual filesystem that corresponds to a real file. +// The linked virtual file will have the same path as the real file path provided. +func (c *CompositeFS) CreateLink(analyzerTypes []Type, rootDir, virtualPath, realPath string) error { + // Create fs.FS for each post-analyzer that wants to analyze the current file + for _, t := range analyzerTypes { + // Since filesystem scanning may require access outside the specified path, (e.g. Terraform modules) + // it allows "../" access with "WithUnderlyingRoot". + var opts []mapfs.Option + if rootDir != "" { + opts = append(opts, mapfs.WithUnderlyingRoot(rootDir)) + } + mfs, _ := c.files.LoadOrStore(t, mapfs.New(opts...)) + if d := path.Dir(virtualPath); d != "." { + if err := mfs.MkdirAll(d, os.ModePerm); err != nil && !errors.Is(err, fs.ErrExist) { + return xerrors.Errorf("mapfs mkdir error: %w", err) + } + } + if err := mfs.WriteFile(virtualPath, realPath); err != nil { + return xerrors.Errorf("mapfs write error: %w", err) + } + } + return nil +} + +// Set sets the fs.FS for the specified post-analyzer +func (c *CompositeFS) Set(t Type, mfs *mapfs.FS) { + c.files.Store(t, mfs) +} + +// Get returns the fs.FS for the specified post-analyzer +func (c *CompositeFS) Get(t Type) (*mapfs.FS, bool) { + return c.files.Load(t) +} + +// Cleanup removes the temporary directory +func (c *CompositeFS) Cleanup() error { + return os.RemoveAll(c.dir) +} diff --git a/pkg/fanal/analyzer/imgconf/apk/apk.go b/pkg/fanal/analyzer/imgconf/apk/apk.go new file mode 100644 index 000000000000..43f6cc278023 --- /dev/null +++ b/pkg/fanal/analyzer/imgconf/apk/apk.go @@ -0,0 +1,292 @@ +package apk + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + builtinos "os" + "sort" + "strings" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +const ( + envApkIndexArchiveURL = "FANAL_APK_INDEX_ARCHIVE_URL" + analyzerVersion = 1 +) + +var defaultApkIndexArchiveURL = "https://raw.githubusercontent." + + "com/knqyf263/apkIndex-archive/master/alpine/v%s/main/x86_64/history.json" + +func init() { + analyzer.RegisterConfigAnalyzer(analyzer.TypeApkCommand, newAlpineCmdAnalyzer) +} + +type alpineCmdAnalyzer struct { + apkIndexArchiveURL string +} + +func newAlpineCmdAnalyzer(_ analyzer.ConfigAnalyzerOptions) (analyzer.ConfigAnalyzer, error) { + apkIndexArchiveURL := defaultApkIndexArchiveURL + if builtinos.Getenv(envApkIndexArchiveURL) != "" { + apkIndexArchiveURL = builtinos.Getenv(envApkIndexArchiveURL) + } + return alpineCmdAnalyzer{apkIndexArchiveURL: apkIndexArchiveURL}, nil +} + +type apkIndex struct { + Package map[string]archive + Provide provide +} + +type archive struct { + Origin string + Versions version + Dependencies []string + Provides []string +} + +type provide struct { + SO map[string]apk // package which provides the shared object + Package map[string]apk // package which provides the package +} + +type apk struct { + Package string + Versions version +} + +type version map[string]int + +func (a alpineCmdAnalyzer) Analyze(_ context.Context, input analyzer.ConfigAnalysisInput) (*analyzer.ConfigAnalysisResult, error) { + if input.Config == nil { + return nil, nil + } + var apkIndexArchive *apkIndex + var err error + if apkIndexArchive, err = a.fetchApkIndexArchive(input.OS); err != nil { + log.Println(err) + return nil, xerrors.Errorf("failed to fetch apk index archive: %w", err) + } + + pkgs := a.parseConfig(apkIndexArchive, input.Config) + if len(pkgs) == 0 { + return nil, nil + } + + return &analyzer.ConfigAnalysisResult{ + HistoryPackages: pkgs, + }, nil +} +func (a alpineCmdAnalyzer) fetchApkIndexArchive(targetOS types.OS) (*apkIndex, error) { + // 3.9.3 => 3.9 + osVer := targetOS.Name + if strings.Count(osVer, ".") > 1 { + osVer = osVer[:strings.LastIndex(osVer, ".")] + } + + url := fmt.Sprintf(a.apkIndexArchiveURL, osVer) + var reader io.Reader + if strings.HasPrefix(url, "file://") { + var err error + reader, err = builtinos.Open(strings.TrimPrefix(url, "file://")) + if err != nil { + return nil, xerrors.Errorf("failed to read APKINDEX archive file: %w", err) + } + } else { + // nolint + resp, err := http.Get(url) + if err != nil { + return nil, xerrors.Errorf("failed to fetch APKINDEX archive: %w", err) + } + defer resp.Body.Close() + reader = resp.Body + } + apkIndexArchive := &apkIndex{} + if err := json.NewDecoder(reader).Decode(apkIndexArchive); err != nil { + return nil, xerrors.Errorf("failed to decode APKINDEX JSON: %w", err) + } + + return apkIndexArchive, nil +} + +func (a alpineCmdAnalyzer) parseConfig(apkIndexArchive *apkIndex, config *v1.ConfigFile) (packages []types.Package) { + envs := make(map[string]string) + for _, env := range config.Config.Env { + index := strings.Index(env, "=") + envs["$"+env[:index]] = env[index+1:] + } + + uniqPkgs := make(map[string]types.Package) + for _, history := range config.History { + pkgs := a.parseCommand(history.CreatedBy, envs) + pkgs = a.resolveDependencies(apkIndexArchive, pkgs) + results := a.guessVersion(apkIndexArchive, pkgs, history.Created.Time) + for _, result := range results { + uniqPkgs[result.Name] = result + } + } + + return maps.Values(uniqPkgs) +} + +func (a alpineCmdAnalyzer) parseCommand(command string, envs map[string]string) (pkgs []string) { + if strings.Contains(command, "#(nop)") { + return nil + } + + command = strings.TrimPrefix(command, "/bin/sh -c") + var commands []string + for _, cmd := range strings.Split(command, "&&") { + for _, c := range strings.Split(cmd, ";") { + commands = append(commands, strings.TrimSpace(c)) + } + } + for _, cmd := range commands { + if !strings.HasPrefix(cmd, "apk") { + continue + } + + var add bool + for _, field := range strings.Fields(cmd) { + switch { + case strings.HasPrefix(field, "-") || strings.HasPrefix(field, "."): + continue + case field == "add": + add = true + case add: + if strings.HasPrefix(field, "$") { + pkgs = append(pkgs, strings.Fields(envs[field])...) + continue + } + pkgs = append(pkgs, field) + } + } + } + return pkgs +} +func (a alpineCmdAnalyzer) resolveDependencies(apkIndexArchive *apkIndex, originalPkgs []string) (pkgs []string) { + uniqPkgs := make(map[string]struct{}) + for _, pkgName := range originalPkgs { + if _, ok := uniqPkgs[pkgName]; ok { + continue + } + + seenPkgs := make(map[string]struct{}) + for _, p := range a.resolveDependency(apkIndexArchive, pkgName, seenPkgs) { + uniqPkgs[p] = struct{}{} + } + } + for pkg := range uniqPkgs { + pkgs = append(pkgs, pkg) + } + return pkgs +} + +func (a alpineCmdAnalyzer) resolveDependency(apkIndexArchive *apkIndex, pkgName string, + seenPkgs map[string]struct{}) (pkgNames []string) { + pkg, ok := apkIndexArchive.Package[pkgName] + if !ok { + return nil + } + if _, ok = seenPkgs[pkgName]; ok { + return nil + } + seenPkgs[pkgName] = struct{}{} + + pkgNames = append(pkgNames, pkgName) + for _, dependency := range pkg.Dependencies { + // sqlite-libs=3.26.0-r3 => sqlite-libs + dependency, _, _ = strings.Cut(dependency, "=") + + if strings.HasPrefix(dependency, "so:") { + soProvidePkg := apkIndexArchive.Provide.SO[dependency[3:]].Package + pkgNames = append(pkgNames, a.resolveDependency(apkIndexArchive, soProvidePkg, seenPkgs)...) + continue + } else if strings.HasPrefix(dependency, "pc:") || strings.HasPrefix(dependency, "cmd:") { + continue + } + pkgProvidePkg, ok := apkIndexArchive.Provide.Package[dependency] + if ok { + pkgNames = append(pkgNames, a.resolveDependency(apkIndexArchive, pkgProvidePkg.Package, seenPkgs)...) + continue + } + pkgNames = append(pkgNames, a.resolveDependency(apkIndexArchive, dependency, seenPkgs)...) + } + return pkgNames +} + +type historyVersion struct { + Version string + BuiltAt int +} + +func (a alpineCmdAnalyzer) guessVersion(apkIndexArchive *apkIndex, originalPkgs []string, + createdAt time.Time) (pkgs []types.Package) { + for _, pkg := range originalPkgs { + archive, ok := apkIndexArchive.Package[pkg] + if !ok { + continue + } + + var historyVersions []historyVersion + for version, builtAt := range archive.Versions { + historyVersions = append(historyVersions, historyVersion{ + Version: version, + BuiltAt: builtAt, + }) + } + sort.Slice(historyVersions, func(i, j int) bool { + return historyVersions[i].BuiltAt < historyVersions[j].BuiltAt + }) + + createdUnix := int(createdAt.Unix()) + var candidateVersion string + for _, historyVersion := range historyVersions { + if historyVersion.BuiltAt <= createdUnix { + candidateVersion = historyVersion.Version + } else if createdUnix < historyVersion.BuiltAt { + break + } + } + if candidateVersion == "" { + continue + } + + pkgs = append(pkgs, types.Package{ + Name: pkg, + Version: candidateVersion, + }) + + // Add origin package name + if archive.Origin != "" && archive.Origin != pkg { + pkgs = append(pkgs, types.Package{ + Name: archive.Origin, + Version: candidateVersion, + }) + } + } + return pkgs +} + +func (a alpineCmdAnalyzer) Required(targetOS types.OS) bool { + return targetOS.Family == types.Alpine +} + +func (a alpineCmdAnalyzer) Type() analyzer.Type { + return analyzer.TypeApkCommand +} + +func (a alpineCmdAnalyzer) Version() int { + return analyzerVersion +} diff --git a/pkg/fanal/analyzer/imgconf/apk/apk_test.go b/pkg/fanal/analyzer/imgconf/apk/apk_test.go new file mode 100644 index 000000000000..8f3856e2fde4 --- /dev/null +++ b/pkg/fanal/analyzer/imgconf/apk/apk_test.go @@ -0,0 +1,1379 @@ +package apk + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "reflect" + "sort" + "testing" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/kylelemons/godebug/pretty" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var ( + oldAlpineConfig = &v1.ConfigFile{ + Architecture: "amd64", + Container: "f5b08762ace1af069127a337579acd51c415b919d736e6615b453a3c6fbf260d", + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 21, 28, 53, 798628678, time.UTC), + }, + DockerVersion: "17.06.2-ce", + History: []v1.History{ + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 11, 22, 19, 38, 885299940, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ADD file:49f9e47e678d868d5b023482aa8dded71276a241a665c4f8b55ca77269321b34 in / ", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 11, 22, 19, 39, 58628442, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 12, 1, 26, 59, 951316015, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV PHPIZE_DEPS=autoconf \t\tdpkg-dev dpkg \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkgconf \t\tre2c", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 12, 1, 27, 1, 470388635, time.UTC), + }, + CreatedBy: "/bin/sh -c apk add --no-cache --virtual .persistent-deps \t\tca-certificates \t\tcurl \t\ttar \t\txz \t\tlibressl", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 12, 1, 27, 2, 432381785, time.UTC), + }, + CreatedBy: "/bin/sh -c set -x \t&& addgroup -g 82 -S www-data \t&& adduser -u 82 -D -S -G www-data www-data", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 12, 1, 27, 2, 715120309, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_INI_DIR=/usr/local/etc/php", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 12, 1, 27, 3, 655421341, time.UTC), + }, + CreatedBy: "/bin/sh -c mkdir -p $PHP_INI_DIR/conf.d", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 12, 1, 27, 3, 931799562, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 12, 1, 27, 4, 210945499, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 12, 1, 27, 4, 523116501, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.September, 12, 1, 27, 4, 795176159, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV GPG_KEYS=1729F83938DA44E27BA0F4D3DBDB397470D12172 B1B44D8F021E4E2D6021E995DC9FF8D3EE5AF27F", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 19, 2, 18, 415761689, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_VERSION=7.2.11", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 19, 2, 18, 599097853, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_URL=https://secure.php.net/get/php-7.2.11.tar.xz/from/this/mirror PHP_ASC_URL=https://secure.php.net/get/php-7.2.11.tar.xz.asc/from/this/mirror", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 19, 2, 18, 782890412, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_SHA256=da1a705c0bc46410e330fc6baa967666c8cd2985378fb9707c01a8e33b01d985 PHP_MD5=", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 19, 2, 22, 795846753, time.UTC), + }, + CreatedBy: "/bin/sh -c set -xe; \t\tapk add --no-cache --virtual .fetch-deps \t\tgnupg \t\twget \t; \t\tmkdir -p /usr/src; \tcd /usr/src; \t\twget -O php.tar.xz \"$PHP_URL\"; \t\tif [ -n \"$PHP_SHA256\" ]; then \t\techo \"$PHP_SHA256 *php.tar.xz\" | sha256sum -c -; \tfi; \tif [ -n \"$PHP_MD5\" ]; then \t\techo \"$PHP_MD5 *php.tar.xz\" | md5sum -c -; \tfi; \t\tif [ -n \"$PHP_ASC_URL\" ]; then \t\twget -O php.tar.xz.asc \"$PHP_ASC_URL\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \t\tfor key in $GPG_KEYS; do \t\t\tgpg --keyserver ha.pool.sks-keyservers.net --recv-keys \"$key\"; \t\tdone; \t\tgpg --batch --verify php.tar.xz.asc php.tar.xz; \t\tcommand -v gpgconf > /dev/null && gpgconf --kill all; \t\trm -rf \"$GNUPGHOME\"; \tfi; \t\tapk del .fetch-deps", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 19, 2, 23, 71406376, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) COPY file:207c686e3fed4f71f8a7b245d8dcae9c9048d276a326d82b553c12a90af0c0ca in /usr/local/bin/ ", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 19, 7, 13, 93396680, time.UTC), + }, + CreatedBy: "/bin/sh -c set -xe \t&& apk add --no-cache --virtual .build-deps \t\t$PHPIZE_DEPS \t\tcoreutils \t\tcurl-dev \t\tlibedit-dev \t\tlibressl-dev \t\tlibsodium-dev \t\tlibxml2-dev \t\tsqlite-dev \t\t&& export CFLAGS=\"$PHP_CFLAGS\" \t\tCPPFLAGS=\"$PHP_CPPFLAGS\" \t\tLDFLAGS=\"$PHP_LDFLAGS\" \t&& docker-php-source extract \t&& cd /usr/src/php \t&& gnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\" \t&& ./configure \t\t--build=\"$gnuArch\" \t\t--with-config-file-path=\"$PHP_INI_DIR\" \t\t--with-config-file-scan-dir=\"$PHP_INI_DIR/conf.d\" \t\t\t\t--enable-option-checking=fatal \t\t\t\t--with-mhash \t\t\t\t--enable-ftp \t\t--enable-mbstring \t\t--enable-mysqlnd \t\t--with-sodium=shared \t\t\t\t--with-curl \t\t--with-libedit \t\t--with-openssl \t\t--with-zlib \t\t\t\t$(test \"$gnuArch\" = 's390x-linux-gnu' && echo '--without-pcre-jit') \t\t\t\t$PHP_EXTRA_CONFIGURE_ARGS \t&& make -j \"$(nproc)\" \t&& make install \t&& { find /usr/local/bin /usr/local/sbin -type f -perm +0111 -exec strip --strip-all '{}' + || true; } \t&& make clean \t\t&& cp -v php.ini-* \"$PHP_INI_DIR/\" \t\t&& cd / \t&& docker-php-source delete \t\t&& runDeps=\"$( \t\tscanelf --needed --nobanner --format '%n#p' --recursive /usr/local \t\t\t| tr ',' '\\n' \t\t\t| sort -u \t\t\t| awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' \t)\" \t&& apk add --no-cache --virtual .php-rundeps $runDeps \t\t&& apk del .build-deps \t\t&& pecl update-channels \t&& rm -rf /tmp/pear ~/.pearrc", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 19, 7, 13, 722586262, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) COPY multi:2cdcedabcf5a3b9ae610fab7848e94bc2f64b4d85710d55fd6f79e44dacf73d8 in /usr/local/bin/ ", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 19, 7, 14, 618087104, time.UTC), + }, + CreatedBy: "/bin/sh -c docker-php-ext-enable sodium", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 19, 7, 14, 826981756, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENTRYPOINT [\"docker-php-entrypoint\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 19, 7, 15, 10831572, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) CMD [\"php\" \"-a\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 21, 28, 21, 919735971, time.UTC), + }, + CreatedBy: "/bin/sh -c apk --no-cache add git subversion openssh mercurial tini bash patch", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 21, 28, 22, 611763893, time.UTC), + }, + CreatedBy: "/bin/sh -c echo \"memory_limit=-1\" > \"$PHP_INI_DIR/conf.d/memory-limit.ini\" && echo \"date.timezone=${PHP_TIMEZONE:-UTC}\" > \"$PHP_INI_DIR/conf.d/date_timezone.ini\"", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 21, 28, 50, 224278478, time.UTC), + }, + CreatedBy: "/bin/sh -c apk add --no-cache --virtual .build-deps zlib-dev && docker-php-ext-install zip && runDeps=\"$( scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions | tr ',' '\\n' | sort -u | awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' )\" && apk add --virtual .composer-phpext-rundeps $runDeps && apk del .build-deps", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 21, 28, 50, 503010161, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_ALLOW_SUPERUSER=1", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 21, 28, 50, 775378559, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_HOME=/tmp", + EmptyLayer: true, + }, + { + Created: v1.Time{ + time.Date(2018, time.October, 15, 21, 28, 51, 35012363, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_VERSION=1.7.2", + EmptyLayer: true, + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 21, 28, 52, 491402624, time.UTC), + }, + CreatedBy: "/bin/sh -c curl --silent --fail --location --retry 3 --output /tmp/installer.php --url https://raw.githubusercontent.com/composer/getcomposer.org/b107d959a5924af895807021fcef4ffec5a76aa9/web/installer && php -r \" \\$signature = '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061'; \\$hash = hash('SHA384', file_get_contents('/tmp/installer.php')); if (!hash_equals(\\$signature, \\$hash)) { unlink('/tmp/installer.php'); echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; exit(1); }\" && php /tmp/installer.php --no-ansi --install-dir=/usr/bin --filename=composer --version=${COMPOSER_VERSION} && composer --ansi --version --no-interaction && rm -rf /tmp/* /tmp/.htaccess", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 21, 28, 52, 948859545, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) COPY file:295943a303e8f27de4302b6aa3687bce4b1d1392335efaaab9ecd37bec5ab4c5 in /docker-entrypoint.sh ", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 21, 28, 53, 295399872, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) WORKDIR /app", + }, + { + Created: v1.Time{ + Time: time.Date(2018, time.October, 15, 21, 28, 53, 582920705, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) ENTRYPOINT [\"/bin/sh\" \"/docker-entrypoint.sh\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{ + time.Date(2018, time.October, 15, 21, 28, 53, 798628678, time.UTC), + }, + CreatedBy: "/bin/sh -c #(nop) CMD [\"composer\"]", + EmptyLayer: true, + }, + }, + OS: "linux", + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + { + Algorithm: "sha256", + Hex: "0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + { + Algorithm: "sha256", + Hex: "9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + { + Algorithm: "sha256", + Hex: "dc00fbef458ad3204bbb548e2d766813f593d857b845a940a0de76aed94c94d1", + }, + { + Algorithm: "sha256", + Hex: "5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", + }, + { + Algorithm: "sha256", + Hex: "9bdb2c849099a99c8ab35f6fd7469c623635e8f4479a0a5a3df61e22bae509f6", + }, + { + Algorithm: "sha256", + Hex: "6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013", + }, + { + Algorithm: "sha256", + Hex: "83abef706f5ae199af65d1c13d737d0eb36219f0d18e36c6d8ff06159df39a63", + }, + { + Algorithm: "sha256", + Hex: "c03283c257abd289a30b4f5e9e1345da0e9bfdc6ca398ee7e8fac6d2c1456227", + }, + { + Algorithm: "sha256", + Hex: "2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4", + }, + { + Algorithm: "sha256", + Hex: "82c59ac8ee582542648e634ca5aff9a464c68ff8a054f105a58689fb52209e34", + }, + { + Algorithm: "sha256", + Hex: "2f4a5c9187c249834ebc28783bd3c65bdcbacaa8baa6620ddaa27846dd3ef708", + }, + { + Algorithm: "sha256", + Hex: "6ca56f561e677ae06c3bc87a70792642d671a4416becb9a101577c1a6e090e36", + }, + { + Algorithm: "sha256", + Hex: "154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + }, + { + Algorithm: "sha256", + Hex: "b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + }, + Config: v1.Config{ + Cmd: []string{"composer"}, + Entrypoint: []string{ + "/bin/sh", + "/docker-entrypoint.sh", + }, + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "PHPIZE_DEPS=autoconf \t\tdpkg-dev dpkg \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkgconf \t\tre2c", + "PHP_INI_DIR=/usr/local/etc/php", + "PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2", + "PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2", + "PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie", + "GPG_KEYS=1729F83938DA44E27BA0F4D3DBDB397470D12172 B1B44D8F021E4E2D6021E995DC9FF8D3EE5AF27F", + "PHP_VERSION=7.2.11", + "PHP_URL=https://secure.php.net/get/php-7.2.11.tar.xz/from/this/mirror", + "PHP_ASC_URL=https://secure.php.net/get/php-7.2.11.tar.xz.asc/from/this/mirror", + "PHP_SHA256=da1a705c0bc46410e330fc6baa967666c8cd2985378fb9707c01a8e33b01d985", + "PHP_MD5=", + "COMPOSER_ALLOW_SUPERUSER=1", + "COMPOSER_HOME=/tmp", + "COMPOSER_VERSION=1.7.2", + }, + Image: "sha256:ad8c55ed62ca1f439bd600c7251de347926ca901ab7f52a93d8fba743ef397c6", + WorkingDir: "/app", + ArgsEscaped: true, + }, + } + + alpineConfig = &v1.ConfigFile{ + Architecture: "amd64", + Container: "47d9d33b3d5abb0316dba1a0bfcbc12a6fa88d98ad30170c41d30718003de82e", + Created: v1.Time{Time: time.Date(2019, time.May, 11, 5, 10, 20, 331457195, time.UTC)}, + DockerVersion: "18.06.1-ce", + History: []v1.History{ + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 0, 7, 3, 358250803, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6ae03397b99ea77f2e9ee901c5c59e59f76f93adbb4035913 in / ", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 0, 7, 3, 510395965, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 43, 80069360, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHPIZE_DEPS=autoconf \t\tdpkg-dev dpkg \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkgconf \t\tre2c", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 44, 655269947, time.UTC)}, + CreatedBy: "/bin/sh -c apk add --no-cache \t\tca-certificates \t\tcurl \t\ttar \t\txz \t\topenssl", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 45, 787769041, time.UTC)}, + CreatedBy: "/bin/sh -c set -x \t&& addgroup -g 82 -S www-data \t&& adduser -u 82 -D -S -G www-data www-data", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 46, 47800659, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_INI_DIR=/usr/local/etc/php", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 47, 131691293, time.UTC)}, + CreatedBy: "/bin/sh -c set -eux; \tmkdir -p \"$PHP_INI_DIR/conf.d\"; \t[ ! -d /var/www/html ]; \tmkdir -p /var/www/html; \tchown www-data:www-data /var/www/html; \tchmod 777 /var/www/html", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 47, 360137598, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 47, 624002469, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 47, 823552655, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 48, 90975339, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV GPG_KEYS=CBAF69F173A0FEA4B537F470D66C9593118BCCB6 F38252826ACD957EF380D39F2F7956BC5DA04B5D", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 48, 311134986, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_VERSION=7.3.5", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 48, 546724822, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_URL=https://www.php.net/get/php-7.3.5.tar.xz/from/this/mirror PHP_ASC_URL=https://www.php.net/get/php-7.3.5.tar.xz.asc/from/this/mirror", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 48, 787069773, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_SHA256=e1011838a46fd4a195c8453b333916622d7ff5bce4aca2d9d99afac142db2472 PHP_MD5=", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 54, 588915046, time.UTC)}, + CreatedBy: "/bin/sh -c set -xe; \t\tapk add --no-cache --virtual .fetch-deps \t\tgnupg \t\twget \t; \t\tmkdir -p /usr/src; \tcd /usr/src; \t\twget -O php.tar.xz \"$PHP_URL\"; \t\tif [ -n \"$PHP_SHA256\" ]; then \t\techo \"$PHP_SHA256 *php.tar.xz\" | sha256sum -c -; \tfi; \tif [ -n \"$PHP_MD5\" ]; then \t\techo \"$PHP_MD5 *php.tar.xz\" | md5sum -c -; \tfi; \t\tif [ -n \"$PHP_ASC_URL\" ]; then \t\twget -O php.tar.xz.asc \"$PHP_ASC_URL\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \t\tfor key in $GPG_KEYS; do \t\t\tgpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys \"$key\"; \t\tdone; \t\tgpg --batch --verify php.tar.xz.asc php.tar.xz; \t\tcommand -v gpgconf > /dev/null && gpgconf --kill all; \t\trm -rf \"$GNUPGHOME\"; \tfi; \t\tapk del --no-network .fetch-deps", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 4, 54, 868883630, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) COPY file:ce57c04b70896f77cc11eb2766417d8a1240fcffe5bba92179ec78c458844110 in /usr/local/bin/ ", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 12, 28, 585346378, time.UTC)}, + CreatedBy: "/bin/sh -c set -xe \t&& apk add --no-cache --virtual .build-deps \t\t$PHPIZE_DEPS \t\targon2-dev \t\tcoreutils \t\tcurl-dev \t\tlibedit-dev \t\tlibsodium-dev \t\tlibxml2-dev \t\topenssl-dev \t\tsqlite-dev \t\t&& export CFLAGS=\"$PHP_CFLAGS\" \t\tCPPFLAGS=\"$PHP_CPPFLAGS\" \t\tLDFLAGS=\"$PHP_LDFLAGS\" \t&& docker-php-source extract \t&& cd /usr/src/php \t&& gnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\" \t&& ./configure \t\t--build=\"$gnuArch\" \t\t--with-config-file-path=\"$PHP_INI_DIR\" \t\t--with-config-file-scan-dir=\"$PHP_INI_DIR/conf.d\" \t\t\t\t--enable-option-checking=fatal \t\t\t\t--with-mhash \t\t\t\t--enable-ftp \t\t--enable-mbstring \t\t--enable-mysqlnd \t\t--with-password-argon2 \t\t--with-sodium=shared \t\t\t\t--with-curl \t\t--with-libedit \t\t--with-openssl \t\t--with-zlib \t\t\t\t$(test \"$gnuArch\" = 's390x-linux-gnu' && echo '--without-pcre-jit') \t\t\t\t$PHP_EXTRA_CONFIGURE_ARGS \t&& make -j \"$(nproc)\" \t&& find -type f -name '*.a' -delete \t&& make install \t&& { find /usr/local/bin /usr/local/sbin -type f -perm +0111 -exec strip --strip-all '{}' + || true; } \t&& make clean \t\t&& cp -v php.ini-* \"$PHP_INI_DIR/\" \t\t&& cd / \t&& docker-php-source delete \t\t&& runDeps=\"$( \t\tscanelf --needed --nobanner --format '%n#p' --recursive /usr/local \t\t\t| tr ',' '\\n' \t\t\t| sort -u \t\t\t| awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' \t)\" \t&& apk add --no-cache $runDeps \t\t&& apk del --no-network .build-deps \t\t&& pecl update-channels \t&& rm -rf /tmp/pear ~/.pearrc", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 12, 29, 98563791, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) COPY multi:03970f7b3773444b9f7f244f89d3ceeb4253ac6599f0ba0a4c0306c5bf7d1b9b in /usr/local/bin/ ", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 12, 30, 99974579, time.UTC)}, + CreatedBy: "/bin/sh -c docker-php-ext-enable sodium", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 12, 30, 266754534, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENTRYPOINT [\"docker-php-entrypoint\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 3, 12, 30, 414982715, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"php\" \"-a\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 5, 10, 12, 574223281, time.UTC)}, + CreatedBy: "/bin/sh -c apk add --no-cache --virtual .composer-rundeps git subversion openssh mercurial tini bash patch make zip unzip coreutils && apk add --no-cache --virtual .build-deps zlib-dev libzip-dev && docker-php-ext-configure zip --with-libzip && docker-php-ext-install -j$(getconf _NPROCESSORS_ONLN) zip opcache && runDeps=\"$( scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions | tr ',' '\\n' | sort -u | awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' )\" && apk add --no-cache --virtual .composer-phpext-rundeps $runDeps && apk del .build-deps && printf \"# composer php cli ini settings\\ndate.timezone=UTC\\nmemory_limit=-1\\nopcache.enable_cli=1\\n\" > $PHP_INI_DIR/php-cli.ini", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 5, 10, 12, 831274473, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_ALLOW_SUPERUSER=1", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 5, 10, 13, 3330711, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_HOME=/tmp", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 5, 10, 18, 503381656, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_VERSION=1.7.3", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 5, 10, 19, 619504049, time.UTC)}, + CreatedBy: "/bin/sh -c curl --silent --fail --location --retry 3 --output /tmp/installer.php --url https://raw.githubusercontent.com/composer/getcomposer.org/cb19f2aa3aeaa2006c0cd69a7ef011eb31463067/web/installer && php -r \" \\$signature = '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5'; \\$hash = hash('sha384', file_get_contents('/tmp/installer.php')); if (!hash_equals(\\$signature, \\$hash)) { unlink('/tmp/installer.php'); echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; exit(1); }\" && php /tmp/installer.php --no-ansi --install-dir=/usr/bin --filename=composer --version=${COMPOSER_VERSION} && composer --ansi --version --no-interaction && rm -f /tmp/installer.php", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 5, 10, 19, 803213107, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) COPY file:0bcb2d1c76549e38469db832f5bcfcb4c538b26748a9d4246cc64f35a23280d0 in /docker-entrypoint.sh ", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 5, 10, 19, 987396089, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) WORKDIR /app", + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 5, 10, 20, 159217819, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENTRYPOINT [\"/bin/sh\" \"/docker-entrypoint.sh\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, time.May, 11, 5, 10, 20, 331457195, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"composer\"]", + EmptyLayer: true, + }, + }, + OS: "linux", + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81", + }, + { + Algorithm: "sha256", + Hex: "3575e617b5f4845d72ac357ea1712be9037c1f73e8893fa4a5b887be964f8f59", + }, + { + Algorithm: "sha256", + Hex: "414e112bbb2c35bef0e76708e87a68b521a011a1941fe6d062e30da800c69d1f", + }, + { + Algorithm: "sha256", + Hex: "21f626200b4c7decb2150402d3b801a886ef9dab022d11478eb3240b2a1bb175", + }, + { + Algorithm: "sha256", + Hex: "64a9089492da43bf6f8f3b3b45aafee7d71f1dfd6464477e27b43b4dbe1da341", + }, + { + Algorithm: "sha256", + Hex: "c60e74b6df1608ee7a080978a9f5eddce48dd4d7366b65a5ec00c6e96deabfae", + }, + { + Algorithm: "sha256", + Hex: "489ab25ac6f9d77b5868493bfccc72bcbfaa85d8f393cdd21f3a6cb6e0256c15", + }, + { + Algorithm: "sha256", + Hex: "5a8c7d3402d369f0f5838b74da5c2bd3eaa64c6bbd8d8e11d7ec0affb074c276", + }, + { + Algorithm: "sha256", + Hex: "fe6bde799f85946dbed35f5f614532d68a9f8b62f3f42ae9164740c3d0a6296a", + }, + { + Algorithm: "sha256", + Hex: "40dd29f574f814717669b34efc4ae527a3af0829a2cccb9ec4f077a8cb2766cc", + }, + { + Algorithm: "sha256", + Hex: "0d5d3c0e6691d3c6d24dc782de33d64d490226c503414da0df93b8f605f93da5", + }, + { + Algorithm: "sha256", + Hex: "41467c77644ee108b8ef3e89db7f235ebb720ed4a4041bf746d7342193e6bc7d", + }, + { + Algorithm: "sha256", + Hex: "6a64ec219cdeecfe63aac5b7f43fb3cb6651c6b1a02ebbde6deeabf8a7e3b345", + }, + }, + }, + Config: v1.Config{ + Cmd: []string{"composer"}, + Entrypoint: []string{ + "/bin/sh", + "/docker-entrypoint.sh", + }, + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "PHPIZE_DEPS=autoconf \t\tdpkg-dev dpkg \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkgconf \t\tre2c", + "PHP_INI_DIR=/usr/local/etc/php", + "PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2", + "PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2", + "PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie", + "GPG_KEYS=CBAF69F173A0FEA4B537F470D66C9593118BCCB6 F38252826ACD957EF380D39F2F7956BC5DA04B5D", + "PHP_VERSION=7.3.5", + "PHP_URL=https://www.php.net/get/php-7.3.5.tar.xz/from/this/mirror", + "PHP_ASC_URL=https://www.php.net/get/php-7.3.5.tar.xz.asc/from/this/mirror", + "PHP_SHA256=e1011838a46fd4a195c8453b333916622d7ff5bce4aca2d9d99afac142db2472", + "PHP_MD5=", + "COMPOSER_ALLOW_SUPERUSER=1", + "COMPOSER_HOME=/tmp", + "COMPOSER_VERSION=1.7.3", + }, + Image: "sha256:45a1f30c00e614b0d90bb2a24affba0a304ff27660ad4717987fefe067cadec8", + WorkingDir: "/app", + ArgsEscaped: true, + }, + } + + wantPkgs = []types.Package{ + { + Name: "acl", + Version: "2.2.52-r5", + }, + { + Name: "apr", + Version: "1.6.5-r0", + }, + { + Name: "apr-util", + Version: "1.6.1-r5", + }, + { + Name: "argon2", + Version: "20171227-r1", + }, + { + Name: "argon2-dev", + Version: "20171227-r1", + }, + { + Name: "argon2-libs", + Version: "20171227-r1", + }, + { + Name: "attr", + Version: "2.4.47-r7", + }, + { + Name: "autoconf", + Version: "2.69-r2", + }, + { + Name: "bash", + Version: "4.4.19-r1", + }, + { + Name: "binutils", + Version: "2.31.1-r2", + }, + { + Name: "busybox", + Version: "1.29.3-r10", + }, + { + Name: "bzip2", + Version: "1.0.6-r6", + }, + { + Name: "ca-certificates", + Version: "20190108-r0", + }, + { + Name: "coreutils", + Version: "8.30-r0", + }, + { + Name: "curl", + Version: "7.64.0-r1", + }, + { + Name: "curl-dev", + Version: "7.64.0-r1", + }, + { + Name: "cyrus-sasl", + Version: "2.1.27-r1", + }, + { + Name: "db", + Version: "5.3.28-r1", + }, + { + Name: "dpkg", + Version: "1.19.2-r0", + }, + { + Name: "dpkg-dev", + Version: "1.19.2-r0", + }, + { + Name: "expat", + Version: "2.2.6-r0", + }, + { + Name: "file", + Version: "5.36-r0", + }, + { + Name: "g++", + Version: "8.3.0-r0", + }, + { + Name: "gcc", + Version: "8.3.0-r0", + }, + { + Name: "gdbm", + Version: "1.13-r1", + }, + { + Name: "git", + Version: "2.20.1-r0", + }, + { + Name: "gmp", + Version: "6.1.2-r1", + }, + { + Name: "gnupg", + Version: "2.2.12-r0", + }, + { + Name: "gnutls", + Version: "3.6.7-r0", + }, + { + Name: "isl", + Version: "0.18-r0", + }, + { + Name: "libacl", + Version: "2.2.52-r5", + }, + { + Name: "libassuan", + Version: "2.5.1-r0", + }, + { + Name: "libatomic", + Version: "8.3.0-r0", + }, + { + Name: "libattr", + Version: "2.4.47-r7", + }, + { + Name: "libbz2", + Version: "1.0.6-r6", + }, + { + Name: "libc-dev", + Version: "0.7.1-r0", + }, + { + Name: "libcap", + Version: "2.26-r0", + }, + { + Name: "libcrypto1.1", + Version: "1.1.1b-r1", + }, + { + Name: "libcurl", + Version: "7.64.0-r1", + }, + { + Name: "libedit", + Version: "20181209.3.1-r0", + }, + { + Name: "libedit-dev", + Version: "20181209.3.1-r0", + }, + { + Name: "libffi", + Version: "3.2.1-r6", + }, + { + Name: "libgcc", + Version: "8.3.0-r0", + }, + { + Name: "libgcrypt", + Version: "1.8.4-r0", + }, + { + Name: "libgomp", + Version: "8.3.0-r0", + }, + { + Name: "libgpg-error", + Version: "1.33-r0", + }, + { + Name: "libksba", + Version: "1.3.5-r0", + }, + { + Name: "libldap", + Version: "2.4.47-r2", + }, + { + Name: "libmagic", + Version: "5.36-r0", + }, + { + Name: "libsasl", + Version: "2.1.27-r1", + }, + { + Name: "libsodium", + Version: "1.0.16-r0", + }, + { + Name: "libsodium-dev", + Version: "1.0.16-r0", + }, + { + Name: "libssh2", + Version: "1.8.2-r0", + }, + { + Name: "libssh2-dev", + Version: "1.8.2-r0", + }, + { + Name: "libssl1.1", + Version: "1.1.1b-r1", + }, + { + Name: "libstdc++", + Version: "8.3.0-r0", + }, + { + Name: "libtasn1", + Version: "4.13-r0", + }, + { + Name: "libunistring", + Version: "0.9.10-r0", + }, + { + Name: "libuuid", + Version: "2.33-r0", + }, + { + Name: "libxml2", + Version: "2.9.9-r1", + }, + { + Name: "libxml2-dev", + Version: "2.9.9-r1", + }, + { + Name: "lz4", + Version: "1.8.3-r2", + }, + { + Name: "lz4-libs", + Version: "1.8.3-r2", + }, + { + Name: "m4", + Version: "1.4.18-r1", + }, + { + Name: "make", + Version: "4.2.1-r2", + }, + { + Name: "mercurial", + Version: "4.9.1-r0", + }, + { + Name: "mpc1", + Version: "1.0.3-r1", + }, + { + Name: "mpfr3", + Version: "3.1.5-r1", + }, + { + Name: "musl", + Version: "1.1.20-r4", + }, + { + Name: "musl-dev", + Version: "1.1.20-r4", + }, + { + Name: "ncurses", + Version: "6.1_p20190105-r0", + }, + { + Name: "ncurses-dev", + Version: "6.1_p20190105-r0", + }, + { + Name: "ncurses-libs", + Version: "6.1_p20190105-r0", + }, + { + Name: "ncurses-terminfo", + Version: "6.1_p20190105-r0", + }, + { + Name: "ncurses-terminfo-base", + Version: "6.1_p20190105-r0", + }, + { + Name: "nettle", + Version: "3.4.1-r0", + }, + { + Name: "nghttp2", + Version: "1.35.1-r0", + }, + { + Name: "nghttp2-dev", + Version: "1.35.1-r0", + }, + { + Name: "nghttp2-libs", + Version: "1.35.1-r0", + }, + { + Name: "npth", + Version: "1.6-r0", + }, + { + Name: "openldap", + Version: "2.4.47-r2", + }, + { + Name: "openssh", + Version: "7.9_p1-r5", + }, + { + Name: "openssh-client", + Version: "7.9_p1-r5", + }, + { + Name: "openssh-keygen", + Version: "7.9_p1-r5", + }, + { + Name: "openssh-server", + Version: "7.9_p1-r5", + }, + { + Name: "openssh-server-common", + Version: "7.9_p1-r5", + }, + { + Name: "openssh-sftp-server", + Version: "7.9_p1-r5", + }, + { + Name: "openssl", + Version: "1.1.1b-r1", + }, + { + Name: "openssl-dev", + Version: "1.1.1b-r1", + }, + { + Name: "p11-kit", + Version: "0.23.14-r0", + }, + { + Name: "patch", + Version: "2.7.6-r4", + }, + { + Name: "pcre2", + Version: "10.32-r1", + }, + { + Name: "perl", + Version: "5.26.3-r0", + }, + { + Name: "pinentry", + Version: "1.1.0-r0", + }, + { + Name: "pkgconf", + Version: "1.6.0-r0", + }, + { + Name: "python2", + Version: "2.7.16-r1", + }, + { + Name: "re2c", + Version: "1.1.1-r0", + }, + { + Name: "readline", + Version: "7.0.003-r1", + }, + { + Name: "serf", + Version: "1.3.9-r5", + }, + { + Name: "sqlite", + Version: "3.26.0-r3", + }, + { + Name: "sqlite-dev", + Version: "3.26.0-r3", + }, + { + Name: "sqlite-libs", + Version: "3.26.0-r3", + }, + { + Name: "subversion", + Version: "1.11.1-r0", + }, + { + Name: "subversion-libs", + Version: "1.11.1-r0", + }, + { + Name: "tar", + Version: "1.32-r0", + }, + { + Name: "unzip", + Version: "6.0-r4", + }, + { + Name: "util-linux", + Version: "2.33-r0", + }, + { + Name: "wget", + Version: "1.20.3-r0", + }, + { + Name: "xz", + Version: "5.2.4-r0", + }, + { + Name: "xz-libs", + Version: "5.2.4-r0", + }, + { + Name: "zip", + Version: "3.0-r7", + }, + { + Name: "zlib", + Version: "1.2.11-r1", + }, + { + Name: "zlib-dev", + Version: "1.2.11-r1", + }, + } +) + +func TestAnalyze(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + content, err := os.ReadFile("testdata/history_v3.9.json") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + res.WriteHeader(http.StatusOK) + res.Write(content) + return + })) + defer testServer.Close() + + type args struct { + targetOS types.OS + config *v1.ConfigFile + } + var tests = map[string]struct { + args args + apkIndexArchivePath string + want types.Packages + }{ + "old": { + args: args{ + targetOS: types.OS{ + Family: "alpine", + Name: "3.9.1", + }, + config: oldAlpineConfig, + }, + apkIndexArchivePath: "file://testdata/history_v%s.json", + want: nil, + }, + "new": { + args: args{ + targetOS: types.OS{ + Family: "alpine", + Name: "3.9.1", + }, + config: alpineConfig, + }, + apkIndexArchivePath: "file://testdata/history_v%s.json", + want: wantPkgs, + }, + "https": { + args: args{ + targetOS: types.OS{ + Family: "alpine", + Name: "3.9.1", + }, + config: alpineConfig, + }, + apkIndexArchivePath: testServer.URL + "/%v", + want: wantPkgs, + }, + } + for testName, v := range tests { + t.Run(testName, func(t *testing.T) { + t.Setenv(envApkIndexArchiveURL, v.apkIndexArchivePath) + a, err := newAlpineCmdAnalyzer(analyzer.ConfigAnalyzerOptions{}) + require.NoError(t, err) + result, err := a.Analyze(context.Background(), analyzer.ConfigAnalysisInput{ + OS: v.args.targetOS, + Config: v.args.config, + }) + require.NoError(t, err) + + got := lo.FromPtr(result) + sort.Sort(got.HistoryPackages) + assert.Equal(t, v.want, got.HistoryPackages) + }) + } +} + +func TestParseCommand(t *testing.T) { + var tests = map[string]struct { + command string + envs map[string]string + expected []string + }{ + "no package": { + command: "/bin/sh -c #(nop) ADD file:49f9e47e678d868d5b023482aa8dded71276a241a665c4f8b55ca77269321b34 in / ", + envs: nil, + expected: nil, + }, + "no-cache": { + command: "/bin/sh -c apk add --no-cache --virtual .persistent-deps \t\tca-certificates \t\tcurl \t\ttar \t\txz \t\tlibressl", + envs: nil, + expected: []string{ + "ca-certificates", + "curl", + "tar", + "xz", + "libressl", + }, + }, + // TODO: support $runDeps + "joined by &&": { + command: `/bin/sh -c apk add --no-cache --virtual .build-deps zlib-dev && docker-php-ext-install zip && runDeps=\"$( scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions | tr ',' '\\n' | sort -u | awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' )\" && apk add --virtual .composer-phpext-rundeps $runDeps && apk del .build-deps`, + envs: nil, + expected: []string{"zlib-dev"}, + }, + "joined by ;": { + command: "/bin/sh -c set -xe; \t\tapk add --no-cache --virtual .fetch-deps \t\tgnupg \t\twget \t; \t\tmkdir -p /usr/src; \tcd /usr/src; \t\twget -O php.tar.xz \"$PHP_URL\"; \t\tif [ -n \"$PHP_SHA256\" ]; then \t\techo \"$PHP_SHA256 *php.tar.xz\" | sha256sum -c -; \tfi; \tif [ -n \"$PHP_MD5\" ]; then \t\techo \"$PHP_MD5 *php.tar.xz\" | md5sum -c -; \tfi; \t\tif [ -n \"$PHP_ASC_URL\" ]; then \t\twget -O php.tar.xz.asc \"$PHP_ASC_URL\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \t\tfor key in $GPG_KEYS; do \t\t\tgpg --keyserver ha.pool.sks-keyservers.net --recv-keys \"$key\"; \t\tdone; \t\tgpg --batch --verify php.tar.xz.asc php.tar.xz; \t\tcommand -v gpgconf > /dev/null && gpgconf --kill all; \t\trm -rf \"$GNUPGHOME\"; \tfi; \t\tapk del .fetch-deps", + envs: nil, + expected: []string{ + "gnupg", + "wget", + }, + }, + "ENV": { + command: "/bin/sh -c set -xe \t&& apk add --no-cache --virtual .build-deps \t\t$PHPIZE_DEPS \t\tcoreutils \t\tcurl-dev \t\tlibedit-dev \t\tlibressl-dev \t\tlibsodium-dev \t\tlibxml2-dev \t\tsqlite-dev", + envs: map[string]string{ + "$PHPIZE_DEPS": "autoconf \t\tdpkg-dev dpkg \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkgconf \t\tre2c", + }, + expected: []string{ + "autoconf", + "dpkg-dev", + "dpkg", + "file", + "g++", + "gcc", + "libc-dev", + "make", + "pkgconf", + "re2c", + "coreutils", + "curl-dev", + "libedit-dev", + "libressl-dev", + "libsodium-dev", + "libxml2-dev", + "sqlite-dev", + }, + }, + } + analyzer := alpineCmdAnalyzer{} + for testName, v := range tests { + actual := analyzer.parseCommand(v.command, v.envs) + assert.Equal(t, v.expected, actual, "[%s]\n%s", testName, pretty.Compare(v.expected, actual)) + } +} + +func TestResolveDependency(t *testing.T) { + var tests = map[string]struct { + pkgName string + apkIndexArchivePath string + expected map[string]struct{} + }{ + "low": { + pkgName: "libblkid", + apkIndexArchivePath: "testdata/history_v3.9.json", + expected: map[string]struct{}{ + "libblkid": {}, + "libuuid": {}, + "musl": {}, + }, + }, + "medium": { + pkgName: "libgcab", + apkIndexArchivePath: "testdata/history_v3.9.json", + expected: map[string]struct{}{ + "busybox": {}, + "libblkid": {}, + "libuuid": {}, + "musl": {}, + "libmount": {}, + "pcre": {}, + "glib": {}, + "libgcab": {}, + "libintl": {}, + "zlib": {}, + "libffi": {}, + }, + }, + "high": { + pkgName: "postgresql", + apkIndexArchivePath: "testdata/history_v3.9.json", + expected: map[string]struct{}{ + "busybox": {}, + "ncurses-terminfo-base": {}, + "ncurses-terminfo": {}, + "libedit": {}, + "db": {}, + "libsasl": {}, + "libldap": {}, + "libpq": {}, + "postgresql-client": {}, + "tzdata": {}, + "libxml2": {}, + "postgresql": {}, + "musl": {}, + "libcrypto1.1": {}, + "libssl1.1": {}, + "ncurses-libs": {}, + "zlib": {}, + }, + }, + "package alias": { + pkgName: "sqlite-dev", + apkIndexArchivePath: "testdata/history_v3.9.json", + expected: map[string]struct{}{ + "sqlite-dev": {}, + "sqlite-libs": {}, + "pkgconf": {}, // pkgconfig => pkgconf + "musl": {}, + }, + }, + "circular dependencies": { + pkgName: "nodejs", + apkIndexArchivePath: "testdata/history_v3.7.json", + expected: map[string]struct{}{ + "busybox": {}, + "c-ares": {}, + "ca-certificates": {}, + "http-parser": {}, + "libcrypto1.0": {}, + "libgcc": {}, + "libressl2.6-libcrypto": {}, + "libssl1.0": {}, + "libstdc++": {}, + "libuv": {}, + "musl": {}, + "nodejs": {}, + "nodejs-npm": {}, + "zlib": {}, + }, + }, + } + analyzer := alpineCmdAnalyzer{} + for testName, v := range tests { + f, err := os.Open(v.apkIndexArchivePath) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + apkIndexArchive := &apkIndex{} + if err = json.NewDecoder(f).Decode(&apkIndexArchive); err != nil { + t.Fatalf("unexpected error: %s", err) + } + circularDependencyCheck := map[string]struct{}{} + pkgs := analyzer.resolveDependency(apkIndexArchive, v.pkgName, circularDependencyCheck) + actual := map[string]struct{}{} + for _, pkg := range pkgs { + actual[pkg] = struct{}{} + } + if !reflect.DeepEqual(v.expected, actual) { + t.Errorf("[%s]\n%s", testName, pretty.Compare(v.expected, actual)) + } + } +} + +func TestGuessVersion(t *testing.T) { + var tests = map[string]struct { + apkIndexArchive *apkIndex + pkgs []string + createdAt time.Time + expected []types.Package + }{ + "normal": { + apkIndexArchive: &apkIndex{ + Package: map[string]archive{ + "busybox": { + Versions: map[string]int{ + "1.24.2-r0": 100, + "1.24.2-r1": 200, + "1.24.2-r2": 300, + }, + }, + }, + }, + pkgs: []string{"busybox"}, + createdAt: time.Unix(200, 0), + expected: []types.Package{ + { + Name: "busybox", + Version: "1.24.2-r1", + }, + }, + }, + "unmatched version": { + apkIndexArchive: &apkIndex{ + Package: map[string]archive{ + "busybox": { + Versions: map[string]int{ + "1.24.2-r0": 100, + "1.24.2-r1": 200, + "1.24.2-r2": 300, + }, + }, + }, + }, + pkgs: []string{"busybox"}, + createdAt: time.Unix(50, 0), + expected: nil, + }, + "unmatched package": { + apkIndexArchive: &apkIndex{ + Package: map[string]archive{ + "busybox": { + Versions: map[string]int{ + "1.24.2-r0": 100, + "1.24.2-r1": 200, + "1.24.2-r2": 300, + }, + }, + }, + }, + pkgs: []string{ + "busybox", + "openssl", + }, + createdAt: time.Unix(200, 0), + expected: []types.Package{ + { + Name: "busybox", + Version: "1.24.2-r1", + }, + }, + }, + "origin": { + apkIndexArchive: &apkIndex{ + Package: map[string]archive{ + "sqlite-dev": { + Versions: map[string]int{ + "3.26.0-r0": 100, + "3.26.0-r1": 200, + "3.26.0-r2": 300, + "3.26.0-r3": 400, + }, + Origin: "sqlite", + }, + }, + }, + pkgs: []string{"sqlite-dev"}, + createdAt: time.Unix(500, 0), + expected: []types.Package{ + { + Name: "sqlite-dev", + Version: "3.26.0-r3", + }, + { + Name: "sqlite", + Version: "3.26.0-r3", + }, + }, + }, + } + analyzer := alpineCmdAnalyzer{} + for testName, v := range tests { + actual := analyzer.guessVersion(v.apkIndexArchive, v.pkgs, v.createdAt) + if !reflect.DeepEqual(v.expected, actual) { + t.Errorf("[%s]\n%s", testName, pretty.Compare(v.expected, actual)) + } + } +} diff --git a/pkg/fanal/analyzer/imgconf/apk/testdata/history_v3.7.json b/pkg/fanal/analyzer/imgconf/apk/testdata/history_v3.7.json new file mode 100644 index 000000000000..7995b3babf98 --- /dev/null +++ b/pkg/fanal/analyzer/imgconf/apk/testdata/history_v3.7.json @@ -0,0 +1,78106 @@ +{ + "package": { + "sox-doc": { + "versions": { + "14.4.2-r0": 1510074687 + }, + "origin": "sox" + }, + "alsa-utils-dbg": { + "versions": { + "1.1.4-r0": 1509468852 + }, + "origin": "alsa-utils", + "dependencies": [ + "dialog" + ] + }, + "perl-xml-sax-base": { + "versions": { + "1.09-r0": 1509475899 + }, + "origin": "perl-xml-sax-base", + "dependencies": [ + "perl" + ] + }, + "flite": { + "versions": { + "2.0.0-r0": 1509481030 + }, + "origin": "flite", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libflite.so.1=2.0.0", + "so:libflite_cmu_grapheme_lang.so.1=2.0.0", + "so:libflite_cmu_grapheme_lex.so.1=2.0.0", + "so:libflite_cmu_indic_lang.so.1=2.0.0", + "so:libflite_cmu_indic_lex.so.1=2.0.0", + "so:libflite_cmu_time_awb.so.1=2.0.0", + "so:libflite_cmu_us_awb.so.1=2.0.0", + "so:libflite_cmu_us_kal.so.1=2.0.0", + "so:libflite_cmu_us_kal16.so.1=2.0.0", + "so:libflite_cmu_us_rms.so.1=2.0.0", + "so:libflite_cmu_us_slt.so.1=2.0.0", + "so:libflite_cmulex.so.1=2.0.0", + "so:libflite_usenglish.so.1=2.0.0", + "cmd:flite", + "cmd:flite_cmu_time_awb", + "cmd:flite_cmu_us_awb", + "cmd:flite_cmu_us_kal", + "cmd:flite_cmu_us_kal16", + "cmd:flite_cmu_us_rms", + "cmd:flite_cmu_us_slt", + "cmd:flite_time" + ] + }, + "py3-twitter": { + "versions": { + "3.1-r2": 1509552771 + }, + "origin": "py-twitter", + "dependencies": [ + "py3-future", + "py3-requests", + "py3-requests-oauthlib", + "python3" + ] + }, + "kamailio-sqlite": { + "versions": { + "5.0.7-r0": 1532960874 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "kamailio-db", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0", + "so:libsrdb1.so.1" + ] + }, + "swig": { + "versions": { + "3.0.12-r1": 1509468194 + }, + "origin": "swig", + "dependencies": [ + "guile", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpcre.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:ccache-swig", + "cmd:swig" + ] + }, + "py-chardet": { + "versions": { + "3.0.4-r0": 1509481686 + }, + "origin": "py-chardet" + }, + "openldap-mqtt": { + "versions": { + "2.4.45-r3": 1510258132, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900515 + }, + "origin": "openldap", + "dependencies": [ + "openldap", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libmosquitto.so.1" + ] + }, + "giblib-doc": { + "versions": { + "1.2.4-r8": 1509470528 + }, + "origin": "giblib" + }, + "perl-test-manifest-doc": { + "versions": { + "2.02-r0": 1509471891 + }, + "origin": "perl-test-manifest" + }, + "xf86-video-s3": { + "versions": { + "0.6.5-r8": 1510074849 + }, + "origin": "xf86-video-s3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "cryptsetup-libs": { + "versions": { + "1.7.5-r1": 1510261295 + }, + "origin": "cryptsetup", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libdevmapper.so.1.02", + "so:libuuid.so.1" + ], + "provides": [ + "so:libcryptsetup.so.4=4.7.0" + ] + }, + "protobuf-vim": { + "versions": { + "3.4.1-r1": 1510846093 + }, + "origin": "protobuf" + }, + "lua5.3-posix": { + "versions": { + "33.4.0-r0": 1509468347 + }, + "origin": "lua-posix", + "dependencies": [ + "lua5.3-bitlib<26", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-archive-zip": { + "versions": { + "1.59-r0": 1509483548 + }, + "origin": "perl-archive-zip", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:crc32" + ] + }, + "txt2man": { + "versions": { + "1.6.0-r0": 1509475656 + }, + "origin": "txt2man", + "dependencies": [ + "gawk" + ], + "provides": [ + "cmd:bookman", + "cmd:src2man", + "cmd:txt2man" + ] + }, + "freshclam": { + "versions": { + "0.100.3-r0": 1555508238 + }, + "origin": "clamav", + "dependencies": [ + "logrotate", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libclamav.so.7", + "so:libcrypto.so.42", + "so:libz.so.1" + ], + "provides": [ + "cmd:freshclam" + ] + }, + "enchant-doc": { + "versions": { + "1.6.0-r12": 1509477682 + }, + "origin": "enchant" + }, + "perl-log-any-doc": { + "versions": { + "1.049-r0": 1509470556 + }, + "origin": "perl-log-any" + }, + "hwdata-usb": { + "versions": { + "0.305-r0": 1509468678 + }, + "origin": "hwdata" + }, + "libldap": { + "versions": { + "2.4.45-r3": 1510258131, + "2.4.46-r0": 1565073937, + "2.4.48-r0": 1566900514 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libsasl2.so.3", + "so:libssl.so.44" + ], + "provides": [ + "so:liblber-2.4.so.2=2.10.11", + "so:libldap-2.4.so.2=2.10.11", + "so:libldap_r-2.4.so.2=2.10.11" + ] + }, + "f2fs-tools-dev": { + "versions": { + "1.6.1-r0": 1509483523 + }, + "origin": "f2fs-tools", + "dependencies": [ + "f2fs-tools-libs=1.6.1-r0" + ] + }, + "libxscrnsaver-dev": { + "versions": { + "1.2.2-r1": 1509480773 + }, + "origin": "libxscrnsaver", + "dependencies": [ + "scrnsaverproto", + "libxext-dev", + "libx11-dev", + "libxscrnsaver=1.2.2-r1", + "pc:scrnsaverproto", + "pc:x11", + "pc:xext", + "pkgconfig" + ], + "provides": [ + "pc:xscrnsaver=1.2.2" + ] + }, + "lua5.3-cqueues": { + "versions": { + "20171014-r0": 1510619111 + }, + "origin": "lua-cqueues", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "samba-dc": { + "versions": { + "4.7.6-r3": 1555491788 + }, + "origin": "samba", + "dependencies": [ + "samba-common=4.7.6-r3", + "samba-server=4.7.6-r3", + "samba-winbind=4.7.6-r3", + "py-samba=4.7.6-r3", + "so:libMESSAGING-samba4.so", + "so:libasn1util-samba4.so", + "so:libauth-samba4.so", + "so:libauth4-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-ldap-samba4.so", + "so:libcliauth-samba4.so", + "so:libcluster-samba4.so", + "so:libcmdline-credentials-samba4.so", + "so:libcom_err.so.2", + "so:libcommon-auth-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdsdb-module-samba4.so", + "so:libevents-samba4.so", + "so:libflag-mapping-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr.so.0", + "so:libnetif-samba4.so", + "so:libpopt.so.0", + "so:libposix-eadb-samba4.so", + "so:libprocess-model-samba4.so", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libsecrets3-samba4.so", + "so:libservice-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-base-samba4.so", + "so:libsmbd-shim-samba4.so", + "so:libtalloc.so.2", + "so:libtdb-wrap-samba4.so", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtime-basic-samba4.so" + ], + "provides": [ + "so:libsmbpasswdparser-samba4.so=0", + "cmd:samba", + "cmd:samba-tool", + "cmd:samba_dnsupdate", + "cmd:samba_kcc", + "cmd:samba_spnupdate", + "cmd:samba_upgradedns" + ] + }, + "upower": { + "versions": { + "0.99.6-r0": 1510068214 + }, + "origin": "upower", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libupower-glib.so.3=3.0.1", + "cmd:upower" + ] + }, + "xinput": { + "versions": { + "1.6.2-r0": 1509490860 + }, + "origin": "xinput", + "dependencies": [ + "so:libX11.so.6", + "so:libXi.so.6", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xinput" + ] + }, + "py2-httplib2": { + "versions": { + "0.10.3-r0": 1509476699 + }, + "origin": "py-httplib2", + "dependencies": [ + "python2" + ] + }, + "makedepend-doc": { + "versions": { + "1.0.5-r1": 1509466284 + }, + "origin": "makedepend" + }, + "sems": { + "versions": { + "1.6.0-r6": 1510260897 + }, + "origin": "sems", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libevent_pthreads-2.1.so.6", + "so:libgcc_s.so.1", + "so:libsamplerate.so.0", + "so:libspandsp.so.2", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:sems", + "cmd:sems-get-callproperties", + "cmd:sems-list-active-calls", + "cmd:sems-list-calls", + "cmd:sems-list-finished-calls", + "cmd:sems-logfile-callextract", + "cmd:sems-sbc-get-activeprofile", + "cmd:sems-sbc-get-regex-map-names", + "cmd:sems-sbc-list-profiles", + "cmd:sems-sbc-load-profile", + "cmd:sems-sbc-reload-profile", + "cmd:sems-sbc-reload-profiles", + "cmd:sems-sbc-set-activeprofile", + "cmd:sems-sbc-set-regex-map", + "cmd:sems-stats" + ] + }, + "zfs-utils-py": { + "versions": { + "0.7.3-r0": 1509490074 + }, + "origin": "zfs", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:arc_summary.py", + "cmd:arcstat.py", + "cmd:dbufstat.py" + ] + }, + "dahdi-linux": { + "versions": { + "2.11.1-r0": 1509476068 + }, + "origin": "dahdi-linux" + }, + "libnet-doc": { + "versions": { + "1.1.6-r2": 1509481429 + }, + "origin": "libnet" + }, + "weechat": { + "versions": { + "1.9.1-r1": 1509530223 + }, + "origin": "weechat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libgcrypt.so.20", + "so:libgnutls.so.30", + "so:libncursesw.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:weechat", + "cmd:weechat-curses" + ] + }, + "libmspack-dev": { + "versions": { + "0.8_alpha-r0": 1543321986 + }, + "origin": "libmspack", + "dependencies": [ + "libmspack=0.8_alpha-r0", + "pkgconfig" + ] + }, + "ssmtp-doc": { + "versions": { + "2.64-r12": 1510260822 + }, + "origin": "ssmtp" + }, + "abiword-plugin-sdw": { + "versions": { + "3.0.2-r1": 1510073368 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "xf86-video-glint": { + "versions": { + "1.2.9-r0": 1510073704 + }, + "origin": "xf86-video-glint", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "xcb-util-keysyms": { + "versions": { + "0.4.0-r1": 1509473915 + }, + "origin": "xcb-util-keysyms", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-keysyms.so.1=1.0.0" + ] + }, + "recordmydesktop-doc": { + "versions": { + "0.3.8.1-r2": 1510075368 + }, + "origin": "recordmydesktop" + }, + "xf86-input-synaptics-doc": { + "versions": { + "1.9.0-r1": 1510073649 + }, + "origin": "xf86-input-synaptics" + }, + "gtkglext": { + "versions": { + "1.2.0-r11": 1510075507 + }, + "origin": "gtkglext", + "dependencies": [ + "so:libGL.so.1", + "so:libGLU.so.1", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangox-1.0.so.0" + ], + "provides": [ + "so:libgdkglext-x11-1.0.so.0=0.0.0", + "so:libgtkglext-x11-1.0.so.0=0.0.0" + ] + }, + "py2-itsdangerous": { + "versions": { + "0.24-r3": 1509551858 + }, + "origin": "py-itsdangerous", + "dependencies": [ + "python2" + ] + }, + "libass": { + "versions": { + "0.13.7-r0": 1509480356 + }, + "origin": "libass", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libfribidi.so.0" + ], + "provides": [ + "so:libass.so.9=9.0.1" + ] + }, + "xfce4-mixer": { + "versions": { + "4.11.0-r2": 1510069972 + }, + "origin": "xfce4-mixer", + "dependencies": [ + "hicolor-icon-theme", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdbus-glib-1.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstaudio-0.10.so.0", + "so:libgstinterfaces-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libkeybinder.so.0", + "so:libunique-1.0.so.0", + "so:libxfce4panel-1.0.so.4", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:xfce4-mixer" + ] + }, + "perl-class-returnvalue": { + "versions": { + "0.55-r1": 1510564545 + }, + "origin": "perl-class-returnvalue", + "dependencies": [ + "perl", + "perl-devel-stacktrace" + ] + }, + "py2-libxml2": { + "versions": { + "2.9.8-r1": 1540398579 + }, + "origin": "libxml2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libxml2.so.2" + ] + }, + "dpaste": { + "versions": { + "0.6-r0": 1509477739 + }, + "origin": "sprunge", + "dependencies": [ + "curl" + ], + "provides": [ + "cmd:dpaste" + ] + }, + "py3-purl": { + "versions": { + "1.3.1-r0": 1509483542 + }, + "origin": "py-purl", + "dependencies": [ + "py3-six", + "python3" + ] + }, + "perl-data-ical": { + "versions": { + "0.21-r1": 1510588973 + }, + "origin": "perl-data-ical", + "dependencies": [ + "perl-test-nowarnings", + "perl-test-warn", + "perl-text-vfile-asdata", + "perl-class-returnvalue", + "perl-test-longstring", + "perl-class-accessor", + "perl-file-slurp" + ] + }, + "uwsgi-lua": { + "versions": { + "2.0.17-r0": 1522154656 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:liblua-5.2.so.0" + ] + }, + "perl-path-class-doc": { + "versions": { + "0.37-r0": 1509480564 + }, + "origin": "perl-path-class" + }, + "unbound-migrate": { + "versions": { + "1.6.7-r1": 1510588259 + }, + "origin": "unbound", + "dependencies": [ + "dnssec-root" + ], + "provides": [ + "cmd:migrate-dnscache-to-unbound" + ] + }, + "nrpe": { + "versions": { + "2.15-r7": 1510259028 + }, + "origin": "nrpe", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "cmd:nrpe" + ] + }, + "xev-doc": { + "versions": { + "1.2.2-r0": 1509491060 + }, + "origin": "xev" + }, + "the_silver_searcher-zsh-completion": { + "versions": { + "2.1.0-r2": 1510831170 + }, + "origin": "the_silver_searcher" + }, + "dhclient": { + "versions": { + "4.3.5-r0": 1509496513 + }, + "origin": "dhcp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "cmd:dhclient", + "cmd:dhclient-script" + ] + }, + "acf-lvm2": { + "versions": { + "0.7.0-r2": 1510072195 + }, + "origin": "acf-lvm2", + "dependencies": [ + "acf-core", + "lvm2" + ] + }, + "clutter": { + "versions": { + "1.26.2-r1": 1510073009 + }, + "origin": "clutter", + "dependencies": [ + "so:libX11.so.6", + "so:libXcomposite.so.1", + "so:libXdamage.so.1", + "so:libXi.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo-gobject.so.2", + "so:libcairo.so.2", + "so:libcogl-pango.so.20", + "so:libcogl-path.so.20", + "so:libcogl.so.20", + "so:libfontconfig.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjson-glib-1.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpangoft2-1.0.so.0" + ], + "provides": [ + "so:libclutter-1.0.so.0=0.2600.2" + ] + }, + "asterisk-doc": { + "versions": { + "15.6.1-r0": 1537795340, + "15.6.2-r0": 1568705002 + }, + "origin": "asterisk" + }, + "wpa_supplicant": { + "versions": { + "2.6-r9": 1534860111, + "2.6-r10": 1559719897, + "2.6-r11": 1568726389 + }, + "origin": "wpa_supplicant", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libdbus-1.so.3", + "so:libnl-3.so.200", + "so:libnl-genl-3.so.200", + "so:libpcsclite.so.1", + "so:libssl.so.44" + ], + "provides": [ + "cmd:eapol_test", + "cmd:wpa_cli", + "cmd:wpa_passphrase", + "cmd:wpa_supplicant" + ] + }, + "pacman": { + "versions": { + "5.0.2-r1": 1510588357 + }, + "origin": "pacman", + "dependencies": [ + "so:libarchive.so.13", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libgpgme.so.11", + "so:libintl.so.8" + ], + "provides": [ + "so:libalpm.so.10=10.0.2", + "cmd:cleanupdelta", + "cmd:makepkg", + "cmd:makepkg-template", + "cmd:pacman", + "cmd:pacman-db-upgrade", + "cmd:pacman-key", + "cmd:pacman-optimize", + "cmd:pacsort", + "cmd:pactree", + "cmd:pkgdelta", + "cmd:repo-add", + "cmd:repo-elephant", + "cmd:repo-remove", + "cmd:testpkg", + "cmd:vercmp" + ] + }, + "gconf-dev": { + "versions": { + "3.2.6-r1": 1510928342 + }, + "origin": "gconf", + "dependencies": [ + "libxml2-dev", + "gtk+3.0-dev", + "polkit-dev", + "orbit2-dev", + "gconf=3.2.6-r1", + "pc:ORBit-2.0", + "pc:gio-2.0", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gconf-2.0=3.2.6" + ] + }, + "eggdbus-doc": { + "versions": { + "0.6-r5": 1509469812 + }, + "origin": "eggdbus" + }, + "xmodmap": { + "versions": { + "1.0.9-r2": 1509473722 + }, + "origin": "xmodmap", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xmodmap" + ] + }, + "subversion-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503664 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "libxfce4ui-lang": { + "versions": { + "4.12.1-r3": 1510067994 + }, + "origin": "libxfce4ui" + }, + "rxvt-unicode-doc": { + "versions": { + "9.22-r2": 1509476789 + }, + "origin": "rxvt-unicode" + }, + "libxfce4util": { + "versions": { + "4.12.1-r4": 1509468547 + }, + "origin": "libxfce4util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libxfce4util.so.7=7.0.0", + "cmd:xfce4-kiosk-query" + ] + }, + "gperf-doc": { + "versions": { + "3.1-r1": 1509462059 + }, + "origin": "gperf" + }, + "abiword-plugin-gdict": { + "versions": { + "3.0.2-r1": 1510073360 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0" + ] + }, + "haserl-doc": { + "versions": { + "0.9.35-r1": 1509468292 + }, + "origin": "haserl" + }, + "py2-vobject": { + "versions": { + "0.9.5-r2": 1510832515 + }, + "origin": "py-vobject", + "dependencies": [ + "python2", + "py2-icu", + "py2-dateutil" + ] + }, + "python2-tests": { + "versions": { + "2.7.15-r2": 1534944322 + }, + "origin": "python2", + "provides": [ + "python-tests=2.7.15-r2" + ] + }, + "xhost": { + "versions": { + "1.0.7-r1": 1509483059 + }, + "origin": "xhost", + "dependencies": [ + "so:libX11.so.6", + "so:libXmuu.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xhost" + ] + }, + "seabios-bin": { + "versions": { + "1.10.3-r0": 1510068884 + }, + "origin": "seabios", + "dependencies": [ + "seabios-bin=1.10.3-r0", + "seavgabios-bin=1.10.3-r0" + ] + }, + "perl-protocol-websocket-doc": { + "versions": { + "0.20-r0": 1509474790 + }, + "origin": "perl-protocol-websocket" + }, + "python2-dev": { + "versions": { + "2.7.15-r2": 1534944322 + }, + "origin": "python2", + "dependencies": [ + "pkgconfig", + "python2=2.7.15-r2" + ], + "provides": [ + "python-dev=2.7.15-r2", + "pc:python-2.7=2.7", + "pc:python2=2.7", + "pc:python=2.7", + "cmd:python-config", + "cmd:python2-config", + "cmd:python2.7-config" + ] + }, + "dovecot-fts-lucene": { + "versions": { + "2.2.36.3-r0": 1554102391, + "2.2.36.4-r0": 1567075099 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot", + "so:libc.musl-x86_64.so.1", + "so:libclucene-core.so.1", + "so:libclucene-shared.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:lib21_fts_lucene_plugin.so=0" + ] + }, + "xdg-utils-doc": { + "versions": { + "1.1.2-r0": 1510842481 + }, + "origin": "xdg-utils" + }, + "xf86-input-libinput-doc": { + "versions": { + "0.26.0-r0": 1510303212 + }, + "origin": "xf86-input-libinput" + }, + "pptpd": { + "versions": { + "1.4.0-r1": 1509485779 + }, + "origin": "pptpd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bcrelay", + "cmd:pptpctrl", + "cmd:pptpd" + ] + }, + "nagios-plugins-ifoperstatus": { + "versions": { + "2.2.1-r3": 1510288495 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins" + ] + }, + "nagios-plugins-openrc": { + "versions": { + "2.2.1-r3": 1510288499 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "openrc", + "sudo" + ] + }, + "py2-virtualenv": { + "versions": { + "15.1.0-r0": 1512030542 + }, + "origin": "py-virtualenv", + "dependencies": [ + "py2-pip", + "python2" + ], + "provides": [ + "cmd:virtualenv" + ] + }, + "shared-mime-info-lang": { + "versions": { + "1.9-r0": 1509464467 + }, + "origin": "shared-mime-info" + }, + "lame-dev": { + "versions": { + "3.100-r0": 1510088509 + }, + "origin": "lame", + "dependencies": [ + "lame=3.100-r0" + ] + }, + "libexecinfo": { + "versions": { + "1.1-r0": 1509491294 + }, + "origin": "libexecinfo", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libexecinfo.so.1=1" + ] + }, + "rtorrent-doc": { + "versions": { + "0.9.6-r2": 1509492667 + }, + "origin": "rtorrent" + }, + "py-gobject3": { + "versions": { + "3.24.1-r2": 1509481893 + }, + "origin": "py-gobject3" + }, + "libechonest-dev": { + "versions": { + "2.3.1-r0": 1510075923 + }, + "origin": "libechonest", + "dependencies": [ + "qjson-dev", + "qt-dev", + "libechonest=2.3.1-r0", + "pc:QJson", + "pc:QtCore", + "pc:QtNetwork", + "pkgconfig" + ], + "provides": [ + "pc:libechonest=2.3.1" + ] + }, + "herbstluftwm-zsh-completion": { + "versions": { + "0.7.0-r1": 1509551853 + }, + "origin": "herbstluftwm" + }, + "kamailio-db": { + "versions": { + "5.0.7-r0": 1532960874 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libsrdb1.so.1", + "so:libsrdb2.so.1", + "so:libsrutils.so.1", + "so:libtrie.so.1" + ] + }, + "lua-lzmq": { + "versions": { + "0.4.4-r0": 1509475920 + }, + "origin": "lua-lzmq" + }, + "boost-filesystem": { + "versions": { + "1.62.0-r5": 1509465874 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_system-mt.so.1.62.0", + "so:libboost_system.so.1.62.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_filesystem-mt.so.1.62.0=1.62.0", + "so:libboost_filesystem.so.1.62.0=1.62.0" + ] + }, + "pm-utils-dev": { + "versions": { + "1.4.1-r0": 1509491018 + }, + "origin": "pm-utils", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:pm-utils=1.4.1" + ] + }, + "zeromq": { + "versions": { + "4.2.5-r0": 1549279390, + "4.2.5-r1": 1563908217 + }, + "origin": "zeromq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libzmq.so.5" + ], + "provides": [ + "cmd:curve_keygen" + ] + }, + "lua5.1-libs": { + "versions": { + "5.1.5-r3": 1509462458 + }, + "origin": "lua5.1", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblua.so.5=5.1.4" + ] + }, + "farstream0.1": { + "versions": { + "0.1.2-r2": 1510933114 + }, + "origin": "farstream0.1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstnetbuffer-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgstrtp-0.10.so.0", + "so:libnice.so.10" + ], + "provides": [ + "so:libfarstream-0.1.so.0=0.0.1" + ] + }, + "mqtt-exec": { + "versions": { + "0.4-r1": 1531919011 + }, + "origin": "mqtt-exec", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ], + "provides": [ + "cmd:mqtt-exec" + ] + }, + "perl-test-refcount": { + "versions": { + "0.08-r0": 1509477781 + }, + "origin": "perl-test-refcount" + }, + "libxp-doc": { + "versions": { + "1.0.3-r0": 1509494901 + }, + "origin": "libxp" + }, + "acf-alpine-baselayout": { + "versions": { + "0.13.2-r0": 1530045059 + }, + "origin": "acf-alpine-baselayout", + "dependencies": [ + "acf-core", + "lua-json4", + "lua-posix" + ] + }, + "xorg-server-xephyr": { + "versions": { + "1.19.5-r1": 1540838460 + }, + "origin": "xorg-server", + "dependencies": [ + "so:libGL.so.1", + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.0.0", + "so:libepoxy.so.0", + "so:libpixman-1.so.0", + "so:libudev.so.1", + "so:libxcb-icccm.so.4", + "so:libxcb-image.so.0", + "so:libxcb-keysyms.so.1", + "so:libxcb-randr.so.0", + "so:libxcb-render-util.so.0", + "so:libxcb-render.so.0", + "so:libxcb-shape.so.0", + "so:libxcb-shm.so.0", + "so:libxcb-util.so.1", + "so:libxcb-xkb.so.1", + "so:libxcb-xv.so.0", + "so:libxcb.so.1", + "so:libxshmfence.so.1" + ], + "provides": [ + "cmd:Xephyr" + ] + }, + "qemu-cris": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-cris" + ] + }, + "docbook-xml": { + "versions": { + "4.5-r5": 1509459373 + }, + "origin": "docbook-xml", + "dependencies": [ + "libxml2-utils", + "/bin/sh" + ] + }, + "ruby-net-telnet": { + "versions": { + "2.4.6-r0": 1557166822 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ] + }, + "abcde-doc": { + "versions": { + "2.8.1-r1": 1509551899 + }, + "origin": "abcde" + }, + "perl-parse-syslog": { + "versions": { + "1.10-r2": 1509479748 + }, + "origin": "perl-parse-syslog", + "dependencies": [ + "perl" + ] + }, + "libatasmart-doc": { + "versions": { + "0.19-r1": 1509482228 + }, + "origin": "libatasmart" + }, + "samba-heimdal-libs": { + "versions": { + "4.7.6-r3": 1555491787 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2" + ], + "provides": [ + "so:libasn1-samba4.so.8=8.0.0", + "so:libgssapi-samba4.so.2=2.0.0", + "so:libhcrypto-samba4.so.5=5.0.1", + "so:libheimbase-samba4.so.1=1.0.0", + "so:libheimntlm-samba4.so.1=1.0.1", + "so:libhx509-samba4.so.5=5.0.0", + "so:libkrb5-samba4.so.26=26.0.0", + "so:libroken-samba4.so.19=19.0.1", + "so:libwind-samba4.so.0=0.0.0" + ] + }, + "libmpeg2": { + "versions": { + "0.5.1-r7": 1509473558 + }, + "origin": "libmpeg2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmpeg2.so.0=0.1.0", + "so:libmpeg2convert.so.0=0.0.0", + "cmd:corrupt_mpeg2", + "cmd:extract_mpeg2" + ] + }, + "libcroco": { + "versions": { + "0.6.12-r0": 1509467188, + "0.6.12-r1": 1563788970 + }, + "origin": "libcroco", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libcroco-0.6.so.3=3.0.1", + "cmd:csslint-0.6" + ] + }, + "aspell-ru": { + "versions": { + "0.99f7-r1": 1509472871 + }, + "origin": "aspell-ru" + }, + "lxterminal": { + "versions": { + "0.3.0-r2": 1510071921 + }, + "origin": "lxterminal", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libvte.so.9" + ], + "provides": [ + "cmd:lxterminal" + ] + }, + "figlet-doc": { + "versions": { + "2.2.5-r0": 1509480743 + }, + "origin": "figlet" + }, + "libguess": { + "versions": { + "1.2-r0": 1509494184 + }, + "origin": "libguess", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libguess.so.1=1.0.0" + ] + }, + "xfce4-power-manager-lang": { + "versions": { + "1.6.0-r2": 1510068245 + }, + "origin": "xfce4-power-manager", + "dependencies": [ + "polkit" + ] + }, + "perl-test-longstring-doc": { + "versions": { + "0.15-r0": 1509470425 + }, + "origin": "perl-test-longstring" + }, + "py3-mock": { + "versions": { + "2.0.0-r3": 1509493400 + }, + "origin": "py-mock", + "dependencies": [ + "py3-pbr", + "py3-six", + "python3" + ] + }, + "py-gobject": { + "versions": { + "2.28.6-r6": 1509471051 + }, + "origin": "py-gobject", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgthread-2.0.so.0" + ], + "provides": [ + "so:libpyglib-2.0-python.so.0=0.0.0", + "cmd:pygobject-codegen-2.0" + ] + }, + "py2-cparser": { + "versions": { + "2.18-r0": 1509483348 + }, + "origin": "py-cparser", + "dependencies": [ + "python2" + ] + }, + "mailx-doc": { + "versions": { + "8.1.1-r1": 1509494503 + }, + "origin": "mailx" + }, + "py2-jwt": { + "versions": { + "1.5.0-r1": 1509552755 + }, + "origin": "py-jwt", + "dependencies": [ + "python2" + ] + }, + "syslog-ng": { + "versions": { + "3.9.1-r3": 1510259404 + }, + "origin": "syslog-ng", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libevtlog.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libhiredis.so.0.13", + "so:libpcre.so.1", + "so:libpython2.7.so.1.0", + "so:libssl.so.44" + ], + "provides": [ + "so:libsyslog-ng-3.9.so.0=0.0.0", + "cmd:dqtool", + "cmd:loggen", + "cmd:pdbtool", + "cmd:syslog-ng", + "cmd:syslog-ng-ctl", + "cmd:update-patterndb" + ] + }, + "xproto-doc": { + "versions": { + "7.0.31-r1": 1509461805 + }, + "origin": "xproto" + }, + "sems-voicebox": { + "versions": { + "1.6.0-r6": 1510260897 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "perl-json": { + "versions": { + "2.97000-r0": 1511989778 + }, + "origin": "perl-json" + }, + "boost-serialization": { + "versions": { + "1.62.0-r5": 1509465879 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_serialization-mt.so.1.62.0=1.62.0", + "so:libboost_serialization.so.1.62.0=1.62.0" + ] + }, + "gvfs-fuse": { + "versions": { + "1.34.1-r0": 1511430258, + "1.34.1-r1": 1563787224 + }, + "origin": "gvfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfuse.so.2", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgvfscommon.so" + ] + }, + "mesa-gbm": { + "versions": { + "17.2.4-r1": 1510741947 + }, + "origin": "mesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libexpat.so.1", + "so:libwayland-client.so.0", + "so:libwayland-server.so.0" + ], + "provides": [ + "so:libgbm.so.1=1.0.0" + ] + }, + "dejagnu-doc": { + "versions": { + "1.6-r1": 1509491945 + }, + "origin": "dejagnu" + }, + "perl-data-uuid-doc": { + "versions": { + "1.221-r1": 1509477323 + }, + "origin": "perl-data-uuid" + }, + "lua5.3-rex-pcre": { + "versions": { + "2.9.0-r0": 1510619366 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ] + }, + "lighttpd-dbg": { + "versions": { + "1.4.48-r0": 1511925915 + }, + "origin": "lighttpd" + }, + "zlib": { + "versions": { + "1.2.11-r1": 1509456391 + }, + "origin": "zlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libz.so.1=1.2.11" + ] + }, + "perl-control-x10-doc": { + "versions": { + "2.09-r1": 1509491053 + }, + "origin": "perl-control-x10" + }, + "openldap-overlay-memberof": { + "versions": { + "2.4.45-r3": 1510258134, + "2.4.46-r0": 1565073940, + "2.4.48-r0": 1566900517 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "apache2-mod-wsgi": { + "versions": { + "4.5.22-r0": 1511871607 + }, + "origin": "apache2-mod-wsgi", + "dependencies": [ + "apache2", + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "py-hgtools": { + "versions": { + "6.5.2-r0": 1509490584 + }, + "origin": "py-hgtools" + }, + "dbus-libs": { + "versions": { + "1.10.24-r0": 1509465101, + "1.10.28-r0": 1560765707 + }, + "origin": "dbus", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdbus-1.so.3=3.14.16" + ] + }, + "libvpx-utils": { + "versions": { + "1.6.1-r0": 1509480734 + }, + "origin": "libvpx", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libvpx.so.4" + ], + "provides": [ + "cmd:vpxdec", + "cmd:vpxenc" + ] + }, + "libmodplug-dev": { + "versions": { + "0.8.9.0-r0": 1509489448 + }, + "origin": "libmodplug", + "dependencies": [ + "libmodplug=0.8.9.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmodplug=0.8.9.0" + ] + }, + "libedit-doc": { + "versions": { + "20170329.3.1-r3": 1509461725 + }, + "origin": "libedit" + }, + "consolekit2-dev": { + "versions": { + "1.2.0-r2": 1510067529 + }, + "origin": "consolekit2", + "dependencies": [ + "consolekit2=1.2.0-r2", + "pc:dbus-1", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:ck-connector=1.2.0", + "pc:libconsolekit=1.2.0" + ] + }, + "abiword": { + "versions": { + "3.0.2-r1": 1510073371 + }, + "origin": "abiword", + "dependencies": [ + "so:libX11.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libenchant.so.1", + "so:libfontconfig.so.1", + "so:libfribidi.so.0", + "so:libgcc_s.so.1", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgoffice-0.10.so.10", + "so:libgsf-1.so.114", + "so:libgtk-3.so.0", + "so:libjpeg.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpng16.so.16", + "so:librsvg-2.so.2", + "so:libstdc++.so.6", + "so:libwv-1.2.so.4", + "so:libxml2.so.2", + "so:libxslt.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libabiword-3.0.so=0", + "cmd:abiword" + ] + }, + "libtool": { + "versions": { + "2.4.6-r4": 1509456826 + }, + "origin": "libtool", + "dependencies": [ + "bash", + "libltdl" + ], + "provides": [ + "cmd:libtool", + "cmd:libtoolize" + ] + }, + "tiff": { + "versions": { + "4.0.10-r0": 1544169734, + "4.0.10-r1": 1566982294, + "4.0.10-r2": 1572815888 + }, + "origin": "tiff", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8", + "so:libz.so.1" + ], + "provides": [ + "so:libtiff.so.5=5.4.0" + ] + }, + "nginx-mod-http-set-misc": { + "versions": { + "1.12.2-r4": 1542814448 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "nginx-mod-devel-kit", + "so:libc.musl-x86_64.so.1" + ] + }, + "openntpd": { + "versions": { + "6.2_p3-r0": 1510618938 + }, + "origin": "openntpd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtls.so.16" + ], + "provides": [ + "cmd:ntpctl", + "cmd:ntpd" + ] + }, + "librsync-dev": { + "versions": { + "2.0.0-r1": 1509472924 + }, + "origin": "librsync", + "dependencies": [ + "librsync=2.0.0-r1" + ] + }, + "quagga-dbg": { + "versions": { + "1.2.4-r0": 1519133995 + }, + "origin": "quagga", + "dependencies": [ + "iproute2" + ] + }, + "unbound-dbg": { + "versions": { + "1.6.7-r1": 1510588258 + }, + "origin": "unbound", + "dependencies": [ + "dnssec-root" + ] + }, + "py2-jinja2": { + "versions": { + "2.9.6-r0": 1509476511 + }, + "origin": "py-jinja2", + "dependencies": [ + "py2-markupsafe", + "python2" + ] + }, + "audacious-plugins-lang": { + "versions": { + "3.9-r0": 1510288540 + }, + "origin": "audacious-plugins", + "dependencies": [ + "audacious" + ] + }, + "lua-json4": { + "versions": { + "1.0.0-r2": 1509468372 + }, + "origin": "lua-json4" + }, + "mariadb-test": { + "versions": { + "10.1.38-r1": 1551187991, + "10.1.40-r0": 1560354888, + "10.1.41-r0": 1565163112 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:my_safe_process", + "cmd:mysql-test", + "cmd:mysql_client_test" + ] + }, + "py3-requests": { + "versions": { + "2.18.4-r0": 1509483041 + }, + "origin": "py-requests", + "dependencies": [ + "py3-chardet", + "py3-idna", + "py3-certifi", + "py3-urllib3", + "python3" + ] + }, + "eudev-doc": { + "versions": { + "3.2.4-r1": 1509466081 + }, + "origin": "eudev" + }, + "libmcrypt-dev": { + "versions": { + "2.5.8-r7": 1509493845 + }, + "origin": "libmcrypt", + "dependencies": [ + "libmcrypt=2.5.8-r7" + ], + "provides": [ + "cmd:libmcrypt-config" + ] + }, + "tinyxml": { + "versions": { + "2.6.2-r1": 1509496005 + }, + "origin": "tinyxml", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libtinyxml.so.0=0.2.6.2" + ] + }, + "font-bh-100dpi": { + "versions": { + "1.0.3-r0": 1509491623 + }, + "origin": "font-bh-100dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "libxau-doc": { + "versions": { + "1.0.8-r2": 1509461811 + }, + "origin": "libxau" + }, + "a52dec-dev": { + "versions": { + "0.7.4-r6": 1510072174 + }, + "origin": "a52dec", + "dependencies": [ + "a52dec=0.7.4-r6" + ] + }, + "perl-data-guid": { + "versions": { + "0.049-r0": 1509479285 + }, + "origin": "perl-data-guid", + "dependencies": [ + "perl-sub-install", + "perl-sub-exporter", + "perl-data-uuid" + ] + }, + "perl-test2-plugin-nowarnings": { + "versions": { + "0.06-r0": 1510588886 + }, + "origin": "perl-test2-plugin-nowarnings", + "dependencies": [ + "perl-ipc-run3", + "perl-test2-suite", + "perl-test-simple" + ] + }, + "bigreqsproto-doc": { + "versions": { + "1.1.2-r3": 1509470423 + }, + "origin": "bigreqsproto" + }, + "lua5.2-dbi-mysql": { + "versions": { + "0.6-r1": 1511483402 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "libaio-dev": { + "versions": { + "0.3.110-r1": 1509471437 + }, + "origin": "libaio", + "dependencies": [ + "libaio=0.3.110-r1" + ] + }, + "py3-asn1-modules": { + "versions": { + "0.2.1-r0": 1511889298 + }, + "origin": "py-asn1-modules", + "dependencies": [ + "py3-openssl", + "py3-asn1" + ] + }, + "libunique3-dev": { + "versions": { + "3.0.2-r0": 1510069893 + }, + "origin": "libunique3", + "dependencies": [ + "libunique3=3.0.2-r0", + "pc:gtk+-3.0", + "pkgconfig" + ], + "provides": [ + "pc:unique-3.0=3.0.2" + ] + }, + "qv4l2": { + "versions": { + "1.12.5-r1": 1510072043 + }, + "origin": "v4l-utils", + "dependencies": [ + "so:libGL.so.1", + "so:libQtCore.so.4", + "so:libQtGui.so.4", + "so:libQtOpenGL.so.4", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libv4l2.so.0", + "so:libv4lconvert.so.0" + ], + "provides": [ + "cmd:qv4l2" + ] + }, + "znc-modpython": { + "versions": { + "1.7.1-r0": 1531900839, + "1.7.1-r1": 1565877330 + }, + "origin": "znc", + "dependencies": [ + "znc", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpython3.6m.so.1.0", + "so:libstdc++.so.6" + ] + }, + "rtpproxy-doc": { + "versions": { + "2.0.0-r4": 1509492566 + }, + "origin": "rtpproxy" + }, + "libassuan-doc": { + "versions": { + "2.4.4-r0": 1512029943 + }, + "origin": "libassuan" + }, + "libsamplerate": { + "versions": { + "0.1.9-r0": 1509473266 + }, + "origin": "libsamplerate", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsamplerate.so.0=0.1.8", + "cmd:sndfile-resample" + ] + }, + "lua5.1-rex-pcre": { + "versions": { + "2.9.0-r0": 1510619366 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ] + }, + "gnokii-smsd": { + "versions": { + "0.6.31-r6": 1510069849 + }, + "origin": "gnokii", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnokii.so.7", + "so:libintl.so.8" + ], + "provides": [ + "cmd:smsd" + ] + }, + "perl-libwww-doc": { + "versions": { + "6.29-r0": 1510038531 + }, + "origin": "perl-libwww" + }, + "libjpeg": { + "versions": { + "8-r6": 1509469919 + }, + "origin": "jpeg", + "dependencies": [ + "libjpeg-turbo" + ] + }, + "perl-term-readkey-doc": { + "versions": { + "2.37-r1": 1509494715 + }, + "origin": "perl-term-readkey" + }, + "x264-libs": { + "versions": { + "20170930-r0": 1509473602 + }, + "origin": "x264", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libx264.so.148=148" + ] + }, + "wireless-tools": { + "versions": { + "30_pre9-r0": 1509479803 + }, + "origin": "wireless-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ifrename", + "cmd:iwconfig", + "cmd:iwevent", + "cmd:iwgetid", + "cmd:iwlist", + "cmd:iwpriv", + "cmd:iwspy" + ] + }, + "rhash-libs": { + "versions": { + "1.3.5-r1": 1509461595 + }, + "origin": "rhash", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:librhash.so.0=0" + ] + }, + "libmowgli": { + "versions": { + "2.0.0-r0": 1509491365 + }, + "origin": "libmowgli", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmowgli-2.so.0=0.0.0" + ] + }, + "diffutils-doc": { + "versions": { + "3.6-r0": 1509459510 + }, + "origin": "diffutils" + }, + "openldap": { + "versions": { + "2.4.45-r3": 1510258136, + "2.4.46-r0": 1565073941, + "2.4.48-r0": 1566900518 + }, + "origin": "openldap", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2", + "so:libltdl.so.7", + "so:libsasl2.so.3", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:slapacl", + "cmd:slapadd", + "cmd:slapauth", + "cmd:slapcat", + "cmd:slapd", + "cmd:slapdn", + "cmd:slapindex", + "cmd:slappasswd", + "cmd:slapschema", + "cmd:slaptest" + ] + }, + "java-common": { + "versions": { + "0.1-r0": 1509482167 + }, + "origin": "java-common", + "dependencies": [ + "/bin/sh" + ] + }, + "perl-fcgi": { + "versions": { + "0.78-r1": 1509474656 + }, + "origin": "perl-fcgi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-db": { + "versions": { + "0.55-r1": 1509477765 + }, + "origin": "perl-db", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so" + ] + }, + "py3-curl": { + "versions": { + "7.43.0-r4": 1510288123 + }, + "origin": "py-curl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libpython3.6m.so.1.0" + ] + }, + "perl-javascript-minifier-doc": { + "versions": { + "1.14-r0": 1509482419 + }, + "origin": "perl-javascript-minifier" + }, + "netcat-openbsd-doc": { + "versions": { + "1.130-r1": 1509481636 + }, + "origin": "netcat-openbsd" + }, + "fprobe-ulog-doc": { + "versions": { + "1.2-r3": 1509496377 + }, + "origin": "fprobe-ulog" + }, + "libexif-dev": { + "versions": { + "0.6.21-r2": 1539006610 + }, + "origin": "libexif", + "dependencies": [ + "libexif=0.6.21-r2", + "pkgconfig" + ], + "provides": [ + "pc:libexif=0.6.21" + ] + }, + "py2-oauth2client": { + "versions": { + "4.1.2-r1": 1509551798 + }, + "origin": "py-oauth2client", + "dependencies": [ + "py2asn1", + "py2httplib2", + "py2asn1-modules", + "py2rsa", + "py2six", + "python2" + ] + }, + "libsodium": { + "versions": { + "1.0.15-r0": 1509474812 + }, + "origin": "libsodium", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsodium.so.23=23.0.0" + ] + }, + "indent": { + "versions": { + "2.2.11-r1": 1509496390 + }, + "origin": "indent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8" + ], + "provides": [ + "cmd:indent", + "cmd:texinfo2man" + ] + }, + "gtk+-dev": { + "versions": { + "2.24.31-r0": 1512030427 + }, + "origin": "gtk+", + "dependencies": [ + "gtk+2.0-dev" + ] + }, + "py3-redis": { + "versions": { + "2.10.5-r1": 1509552807 + }, + "origin": "py-redis", + "dependencies": [ + "python3" + ] + }, + "nagios-plugins-nt": { + "versions": { + "2.2.1-r3": 1510288497 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-twisted": { + "versions": { + "17.1.0-r0": 1509493298 + }, + "origin": "py-twisted", + "dependencies": [ + "py-crypto", + "py-zope-interface", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:cftp", + "cmd:ckeygen", + "cmd:conch", + "cmd:mailmail", + "cmd:pyhtmlizer", + "cmd:tkconch", + "cmd:trial", + "cmd:twist", + "cmd:twistd" + ] + }, + "lua-soap": { + "versions": { + "3.0-r0": 1509494668 + }, + "origin": "lua-soap", + "dependencies": [ + "lua-expat", + "lua-socket" + ] + }, + "pidgin-sipe-lang": { + "versions": { + "1.22.1-r0": 1510931371 + }, + "origin": "pidgin-sipe" + }, + "graphviz-graphs": { + "versions": { + "2.40.1-r0": 1510066920 + }, + "origin": "graphviz" + }, + "nginx-mod-devel-kit": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "acf-squid": { + "versions": { + "0.11.0-r2": 1510076156 + }, + "origin": "acf-squid", + "dependencies": [ + "acf-core", + "squid" + ] + }, + "py-pep8": { + "versions": { + "1.7.0-r0": 1509492633 + }, + "origin": "py-pep8", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:pep8" + ] + }, + "obex-data-server": { + "versions": { + "0.4.6-r4": 1510071752 + }, + "origin": "obex-data-server", + "dependencies": [ + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1", + "so:libdbus-glib-1.so.2", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libopenobex.so.2", + "so:libusb-0.1.so.4" + ], + "provides": [ + "cmd:obex-data-server" + ] + }, + "lua-cmsgpack": { + "versions": { + "0.4.0-r0": 1509493359 + }, + "origin": "lua-cmsgpack", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.3-bitlib": { + "versions": { + "5.3.0-r0": 1509468310 + }, + "origin": "lua-bitlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "logtail": { + "versions": { + "3.21-r0": 1509490917 + }, + "origin": "logtail", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:logtail" + ] + }, + "boost-python": { + "versions": { + "1.62.0-r5": 1509465878 + }, + "origin": "boost", + "dependencies": [ + "python", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "so:libboost_python-mt.so.1.62.0=1.62.0", + "so:libboost_python.so.1.62.0=1.62.0" + ] + }, + "weechat-lua": { + "versions": { + "1.9.1-r1": 1509530223 + }, + "origin": "weechat", + "dependencies": [ + "weechat", + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ] + }, + "sysklogd-doc": { + "versions": { + "1.5.1-r1": 1511795688 + }, + "origin": "sysklogd" + }, + "xorg-server-xnest": { + "versions": { + "1.19.5-r1": 1540838460 + }, + "origin": "xorg-server", + "dependencies": [ + "so:libX11.so.6", + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXext.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.0.0", + "so:libpixman-1.so.0" + ], + "provides": [ + "cmd:Xnest" + ] + }, + "libgnome-doc": { + "versions": { + "2.32.1-r7": 1510931516 + }, + "origin": "libgnome" + }, + "gtk-xfce-engine": { + "versions": { + "3.2.0-r2": 1510920446 + }, + "origin": "gtk-xfce-engine" + }, + "postgresql-dev": { + "versions": { + "10.7-r0": 1554274195, + "10.8-r0": 1557668011, + "10.9-r0": 1562225611, + "10.10-r0": 1565683434 + }, + "origin": "postgresql", + "dependencies": [ + "libressl-dev", + "libpq=10.10-r0", + "pkgconfig", + "postgresql-libs=10.10-r0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "pc:libecpg=10.10", + "pc:libecpg_compat=10.10", + "pc:libpgtypes=10.10", + "pc:libpq=10.10", + "cmd:ecpg", + "cmd:pg_config" + ] + }, + "gnumeric-dev": { + "versions": { + "1.12.36-r0": 1511455615 + }, + "origin": "gnumeric", + "dependencies": [ + "gnumeric=1.12.36-r0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:gtk+-3.0", + "pc:libgoffice-0.10", + "pc:libgsf-1", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libspreadsheet-1.12=1.12.36" + ] + }, + "guile-doc": { + "versions": { + "2.0.14-r0": 1509468174 + }, + "origin": "guile" + }, + "x265-dev": { + "versions": { + "2.5-r0": 1509482548 + }, + "origin": "x265", + "dependencies": [ + "pkgconfig", + "x265=2.5-r0" + ], + "provides": [ + "pc:x265=2.5" + ] + }, + "wv-doc": { + "versions": { + "1.2.9-r3": 1509483982 + }, + "origin": "wv" + }, + "pptpd-doc": { + "versions": { + "1.4.0-r1": 1509485776 + }, + "origin": "pptpd" + }, + "duply": { + "versions": { + "2.0.3-r0": 1510588387 + }, + "origin": "duply", + "dependencies": [ + "duplicity", + "bash" + ], + "provides": [ + "cmd:duply" + ] + }, + "xmlto-doc": { + "versions": { + "0.0.28-r2": 1509461795 + }, + "origin": "xmlto" + }, + "libvirt-xen": { + "versions": { + "3.9.0-r1": 1510088352, + "5.5.0-r0": 1562165540 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-daemon=5.5.0-r0", + "libvirt-common-drivers=5.5.0-r0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libvirt.so.0", + "so:libxenlight.so.4.9", + "so:libxenstore.so.3.0", + "so:libxentoollog.so.1", + "so:libxlutil.so.4.9", + "so:libxml2.so.2" + ] + }, + "dri3proto-doc": { + "versions": { + "1.0-r2": 1509466244 + }, + "origin": "dri3proto" + }, + "backuppc": { + "versions": { + "3.3.2-r0": 1509492572 + }, + "origin": "backuppc", + "dependencies": [ + "perl", + "busybox", + "rsync", + "perl-archive-zip", + "perl-io-compress", + "perl-libwww", + "perl-file-rsync", + "samba-client" + ] + }, + "libsigc++": { + "versions": { + "2.10.0-r1": 1509472940 + }, + "origin": "libsigc++", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libsigc-2.0.so.0=0.0.0" + ] + }, + "bdftopcf-doc": { + "versions": { + "1.0.5-r1": 1509470411 + }, + "origin": "bdftopcf" + }, + "joe": { + "versions": { + "4.5-r0": 1509494570 + }, + "origin": "joe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:jmacs", + "cmd:joe", + "cmd:jpico", + "cmd:jstar", + "cmd:rjoe" + ] + }, + "rest-doc": { + "versions": { + "0.8.1-r0": 1509492761 + }, + "origin": "rest" + }, + "opensp-lang": { + "versions": { + "1.5.2-r0": 1509488685 + }, + "origin": "opensp" + }, + "libxpm": { + "versions": { + "3.5.12-r0": 1509469832 + }, + "origin": "libxpm", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXpm.so.4=4.11.0", + "cmd:cxpm", + "cmd:sxpm" + ] + }, + "perl-test-script": { + "versions": { + "1.23-r0": 1510588920 + }, + "origin": "perl-test-script", + "dependencies": [ + "perl-ipc-run3", + "perl-probe-perl", + "perl-test-simple" + ] + }, + "perl-file-remove-doc": { + "versions": { + "1.57-r0": 1510352929 + }, + "origin": "perl-file-remove" + }, + "perl-crypt-rijndael": { + "versions": { + "1.11-r4": 1509471904 + }, + "origin": "perl-crypt-rijndael", + "dependencies": [ + "perl-test-manifest", + "so:libc.musl-x86_64.so.1" + ] + }, + "xfce4-screenshooter": { + "versions": { + "1.8.2-r1": 1510074738 + }, + "origin": "xfce4-screenshooter", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libexo-1.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libsoup-2.4.so.1", + "so:libxfce4panel-1.0.so.4", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:xfce4-screenshooter" + ] + }, + "iniparser-dev": { + "versions": { + "4.0-r1": 1509486557 + }, + "origin": "iniparser", + "dependencies": [ + "iniparser=4.0-r1" + ] + }, + "grub-doc": { + "versions": { + "2.02-r3": 1509495739 + }, + "origin": "grub" + }, + "perl-html-formattext-withlinks-andtables-doc": { + "versions": { + "0.07-r0": 1509481579 + }, + "origin": "perl-html-formattext-withlinks-andtables" + }, + "lua5.2-posixtz": { + "versions": { + "0.5-r1": 1509479783 + }, + "origin": "lua-posixtz", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "s6-dns-dev": { + "versions": { + "2.2.0.1-r0": 1509480281 + }, + "origin": "s6-dns", + "dependencies": [ + "s6-dns=2.2.0.1-r0" + ] + }, + "gsm-dev": { + "versions": { + "1.0.16-r0": 1509473211 + }, + "origin": "gsm", + "dependencies": [ + "gsm=1.0.16-r0" + ] + }, + "perl-html-rewriteattributes-doc": { + "versions": { + "0.05-r1": 1510564566 + }, + "origin": "perl-html-rewriteattributes" + }, + "lua5.1-discount": { + "versions": { + "1.2.10.1-r4": 1509479788 + }, + "origin": "lua-discount", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libconfig++": { + "versions": { + "1.5-r3": 1509706433 + }, + "origin": "libconfig", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libconfig++.so.9=9.2.0" + ] + }, + "perl-http-body-doc": { + "versions": { + "1.17-r0": 1509481767 + }, + "origin": "perl-http-body" + }, + "gtk+2.0-doc": { + "versions": { + "2.24.31-r0": 1510066781 + }, + "origin": "gtk+2.0" + }, + "grub": { + "versions": { + "2.02-r3": 1509495739 + }, + "origin": "grub", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02", + "so:libfreetype.so.6", + "so:liblzma.so.5" + ], + "provides": [ + "cmd:grub-bios-setup", + "cmd:grub-editenv", + "cmd:grub-file", + "cmd:grub-fstest", + "cmd:grub-glue-efi", + "cmd:grub-install", + "cmd:grub-kbdcomp", + "cmd:grub-macbless", + "cmd:grub-menulst2cfg", + "cmd:grub-mkconfig", + "cmd:grub-mkfont", + "cmd:grub-mkimage", + "cmd:grub-mklayout", + "cmd:grub-mknetdir", + "cmd:grub-mkpasswd-pbkdf2", + "cmd:grub-mkrelpath", + "cmd:grub-mkrescue", + "cmd:grub-mkstandalone", + "cmd:grub-ofpathname", + "cmd:grub-probe", + "cmd:grub-reboot", + "cmd:grub-render-label", + "cmd:grub-script-check", + "cmd:grub-set-default", + "cmd:grub-sparc64-setup", + "cmd:grub-syslinux2cfg" + ] + }, + "xl2tpd": { + "versions": { + "1.3.10-r0": 1509490847 + }, + "origin": "xl2tpd", + "dependencies": [ + "ppp-l2tp", + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:pfc", + "cmd:xl2tpd", + "cmd:xl2tpd-control" + ] + }, + "py-tdb": { + "versions": { + "1.3.15-r0": 1509486504 + }, + "origin": "tdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libtdb.so.1" + ] + }, + "pigz-doc": { + "versions": { + "2.3.4-r2": 1509494286 + }, + "origin": "pigz" + }, + "open-iscsi": { + "versions": { + "2.0.874-r0": 1509494139 + }, + "origin": "open-iscsi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libisns.so.0", + "so:libmount.so.1" + ], + "provides": [ + "cmd:iscsi-iname", + "cmd:iscsi_discovery", + "cmd:iscsiadm", + "cmd:iscsid", + "cmd:iscsistart", + "cmd:iscsiuio" + ] + }, + "acf-dnscache": { + "versions": { + "0.6.0-r2": 1510074047 + }, + "origin": "acf-dnscache", + "dependencies": [ + "acf-core", + "lua-posix", + "dnscache" + ] + }, + "ldb": { + "versions": { + "1.3.0-r1": 1534939012 + }, + "origin": "ldb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent.so.0" + ], + "provides": [ + "so:libldb.so.1=1.3.0" + ] + }, + "xmlrpc-c-client++": { + "versions": { + "1.39.11-r0": 1509482035 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libxmlrpc++.so.8", + "so:libxmlrpc.so.3", + "so:libxmlrpc_client.so.3", + "so:libxmlrpc_packetsocket.so.8", + "so:libxmlrpc_util++.so.8", + "so:libxmlrpc_util.so.3" + ], + "provides": [ + "so:libxmlrpc_client++.so.8=8.39" + ] + }, + "mercurial-zsh-completion": { + "versions": { + "4.5.2-r0": 1532937171, + "5.4.2-r1": 1522503664, + "4.5.2-r1": 1563792023 + }, + "origin": "mercurial", + "dependencies": [ + "zsh" + ] + }, + "pixman-dev": { + "versions": { + "0.34.0-r3": 1509545247 + }, + "origin": "pixman", + "dependencies": [ + "pixman=0.34.0-r3", + "pkgconfig" + ], + "provides": [ + "pc:pixman-1=0.34.0" + ] + }, + "kamailio-unixodbc": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2", + "so:libsrdb1.so.1" + ] + }, + "bison": { + "versions": { + "3.0.4-r0": 1509456687 + }, + "origin": "bison", + "dependencies": [ + "m4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bison", + "cmd:yacc" + ] + }, + "squid-lang-sl": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865669 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-html-formattext-withlinks-doc": { + "versions": { + "0.15-r0": 1509481572 + }, + "origin": "perl-html-formattext-withlinks" + }, + "bwm-ng-doc": { + "versions": { + "0.6.1-r3": 1509475294 + }, + "origin": "bwm-ng" + }, + "lua-posixtz": { + "versions": { + "0.5-r1": 1509479785 + }, + "origin": "lua-posixtz" + }, + "libxtst-doc": { + "versions": { + "1.2.3-r1": 1509466038 + }, + "origin": "libxtst" + }, + "libverto-dev": { + "versions": { + "0.3.0-r0": 1509469586 + }, + "origin": "libverto", + "dependencies": [ + "libverto-glib=0.3.0-r0", + "libverto-libev=0.3.0-r0", + "libverto-libevent=0.3.0-r0", + "libverto=0.3.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libverto-glib=0.3.0", + "pc:libverto-libev=0.3.0", + "pc:libverto-libevent=0.3.0", + "pc:libverto=0.3.0" + ] + }, + "perl-www-robotrules-doc": { + "versions": { + "6.02-r1": 1509464407 + }, + "origin": "perl-www-robotrules" + }, + "nagios-plugins-uptime": { + "versions": { + "2.2.1-r3": 1510288498 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "hexchat-lang": { + "versions": { + "2.12.4-r1": 1510260816 + }, + "origin": "hexchat" + }, + "libcrystalhd-dev": { + "versions": { + "20130708-r2": 1509493202 + }, + "origin": "libcrystalhd", + "dependencies": [ + "libcrystalhd=20130708-r2" + ] + }, + "gtkglext-dev": { + "versions": { + "1.2.0-r11": 1510075506 + }, + "origin": "gtkglext", + "dependencies": [ + "gtk+2.0-dev", + "mesa-dev", + "libice-dev", + "libxxf86vm-dev", + "libxi-dev", + "libx11-dev", + "libxt-dev", + "glu-dev", + "pangox-compat-dev", + "gtkglext=1.2.0-r11", + "pc:gdk-2.0", + "pc:gmodule-2.0", + "pc:gtk+-2.0", + "pc:pango", + "pc:pangox", + "pkgconfig" + ], + "provides": [ + "pc:gdkglext-1.0=1.2.0", + "pc:gdkglext-x11-1.0=1.2.0", + "pc:gtkglext-1.0=1.2.0", + "pc:gtkglext-x11-1.0=1.2.0" + ] + }, + "atk-doc": { + "versions": { + "2.26.1-r1": 1509464672 + }, + "origin": "atk" + }, + "perl-file-sharedir-install-doc": { + "versions": { + "0.11-r0": 1511889856 + }, + "origin": "perl-file-sharedir-install" + }, + "py3-flask": { + "versions": { + "0.12.2-r1": 1509551869 + }, + "origin": "py-flask", + "dependencies": [ + "py3-click", + "py3-itsdangerous", + "py3-jinja2", + "py3-werkzeug", + "python3" + ], + "provides": [ + "cmd:flask" + ] + }, + "xfce4-battery-plugin": { + "versions": { + "1.0.5-r2": 1510072895 + }, + "origin": "xfce4-battery-plugin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libxfce4panel-1.0.so.4", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7" + ] + }, + "docbook2x": { + "versions": { + "0.8.8-r6": 1509489153 + }, + "origin": "docbook2x", + "dependencies": [ + "texinfo", + "openjade", + "docbook-xml", + "docbook-xsl", + "perl-xml-sax", + "libxslt", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:db2x_manxml", + "cmd:db2x_texixml", + "cmd:db2x_xsltproc", + "cmd:docbook2x-man", + "cmd:docbook2x-texi", + "cmd:sgml2xml-isoent", + "cmd:utf8trans" + ] + }, + "libproxy": { + "versions": { + "0.4.15-r0": 1509468258 + }, + "origin": "libproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libproxy.so.1=1.0.0" + ] + }, + "xfce4-appfinder": { + "versions": { + "4.12.0-r0": 1510074536 + }, + "origin": "xfce4-appfinder", + "dependencies": [ + "hicolor-icon-theme", + "so:libc.musl-x86_64.so.1", + "so:libgarcon-1.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:xfce4-appfinder", + "cmd:xfrun4" + ] + }, + "orbit2-dev": { + "versions": { + "2.14.19-r3": 1510928299 + }, + "origin": "orbit2", + "dependencies": [ + "glib-dev", + "libidl-dev", + "orbit2=2.14.19-r3", + "pc:glib-2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pc:libIDL-2.0", + "pkgconfig" + ], + "provides": [ + "pc:ORBit-2.0=2.14.19", + "pc:ORBit-CosNaming-2.0=2.14.19", + "pc:ORBit-idl-2.0=2.14.19", + "pc:ORBit-imodule-2.0=2.14.19", + "cmd:orbit2-config" + ] + }, + "perl-carp-clan-doc": { + "versions": { + "6.06-r1": 1509473079 + }, + "origin": "perl-carp-clan" + }, + "mii-tool": { + "versions": { + "1.60_git20140218-r1": 1509492734 + }, + "origin": "net-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mii-tool" + ] + }, + "iaxmodem-doc": { + "versions": { + "1.3.0-r0": 1509494109 + }, + "origin": "iaxmodem" + }, + "openresolv": { + "versions": { + "3.9.0-r0": 1509476551 + }, + "origin": "openresolv", + "provides": [ + "cmd:resolvconf" + ] + }, + "kamailio-jsdt": { + "versions": { + "5.0.7-r0": 1532960877 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1" + ] + }, + "b43-fwcutter": { + "versions": { + "019-r0": 1509495309 + }, + "origin": "b43-fwcutter", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:b43-fwcutter" + ] + }, + "perl-http-date-doc": { + "versions": { + "6.02-r1": 1509464355 + }, + "origin": "perl-http-date" + }, + "avahi-tools": { + "versions": { + "0.6.32-r4": 1509465447, + "0.6.32-r5": 1563345619 + }, + "origin": "avahi", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libintl.so.8" + ], + "provides": [ + "cmd:avahi-browse", + "cmd:avahi-browse-domains", + "cmd:avahi-publish", + "cmd:avahi-publish-address", + "cmd:avahi-publish-service", + "cmd:avahi-resolve", + "cmd:avahi-resolve-address", + "cmd:avahi-resolve-host-name", + "cmd:avahi-set-host-name" + ] + }, + "bridge-utils": { + "versions": { + "1.6-r0": 1509491171 + }, + "origin": "bridge-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:brctl" + ] + }, + "thunar-vcs-plugin-svn": { + "versions": { + "0.1.4-r6": 1510314591 + }, + "origin": "thunar-vcs-plugin", + "dependencies": [ + "so:libapr-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libsvn_client-1.so.0", + "so:libsvn_fs-1.so.0", + "so:libsvn_subr-1.so.0", + "so:libsvn_wc-1.so.0", + "so:libxfce4util.so.7" + ] + }, + "perl-sub-install-doc": { + "versions": { + "0.928-r0": 1509473962 + }, + "origin": "perl-sub-install" + }, + "db-doc": { + "versions": { + "5.3.28-r0": 1509469314 + }, + "origin": "db" + }, + "perl-html-mason-psgihandler": { + "versions": { + "0.53-r0": 1510588962 + }, + "origin": "perl-html-mason-psgihandler", + "dependencies": [ + "perl-cgi-psgi", + "perl-html-mason" + ] + }, + "librrd-th": { + "versions": { + "1.5.6-r3": 1512296349 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpng16.so.16", + "so:libxml2.so.2" + ], + "provides": [ + "so:librrd_th.so.4=4.3.5" + ] + }, + "gdl-doc": { + "versions": { + "3.22.0-r0": 1510074987 + }, + "origin": "gdl" + }, + "py2-paramiko": { + "versions": { + "2.4.2-r0": 1551364326 + }, + "origin": "py-paramiko", + "dependencies": [ + "py2-asn1", + "py2-cryptography", + "py2-bcrypt", + "py2-pynacl", + "python2" + ] + }, + "xf86-video-fbdev-doc": { + "versions": { + "0.4.4-r5": 1510069870 + }, + "origin": "xf86-video-fbdev" + }, + "libxv": { + "versions": { + "1.0.11-r1": 1509467171 + }, + "origin": "libxv", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXv.so.1=1.0.0" + ] + }, + "bdftopcf": { + "versions": { + "1.0.5-r1": 1509470412 + }, + "origin": "bdftopcf", + "dependencies": [ + "so:libXfont.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bdftopcf" + ] + }, + "libxshmfence": { + "versions": { + "1.2-r2": 1509466255 + }, + "origin": "libxshmfence", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libxshmfence.so.1=1.0.0" + ] + }, + "yasm-dev": { + "versions": { + "1.3.0-r1": 1509473588 + }, + "origin": "yasm" + }, + "perl-capture-tiny": { + "versions": { + "0.46-r0": 1509485667 + }, + "origin": "perl-capture-tiny" + }, + "xfdesktop": { + "versions": { + "4.12.4-r0": 1510074490 + }, + "origin": "xfdesktop", + "dependencies": [ + "hicolor-icon-theme", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libexo-1.so.0", + "so:libgarcon-1.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libnotify.so.4", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libthunarx-2.so.0", + "so:libwnck-1.so.22", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:xfdesktop", + "cmd:xfdesktop-settings" + ] + }, + "imap-dev": { + "versions": { + "2007f-r7": 1510258915 + }, + "origin": "imap", + "dependencies": [ + "c-client=2007f-r7" + ] + }, + "xf86-video-chips": { + "versions": { + "1.2.7-r0": 1510074064 + }, + "origin": "xf86-video-chips", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libpcap-dev": { + "versions": { + "1.8.1-r1": 1509475964 + }, + "origin": "libpcap", + "dependencies": [ + "libpcap=1.8.1-r1" + ], + "provides": [ + "cmd:pcap-config" + ] + }, + "perl-file-rsync-doc": { + "versions": { + "0.74-r1": 1509473504 + }, + "origin": "perl-file-rsync" + }, + "opus-doc": { + "versions": { + "1.2.1-r1": 1509468917 + }, + "origin": "opus" + }, + "gvfs-mtp": { + "versions": { + "1.34.1-r0": 1511430258, + "1.34.1-r1": 1563787224 + }, + "origin": "gvfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libgvfscommon.so", + "so:libgvfsdaemon.so", + "so:libintl.so.8", + "so:libmtp.so.9", + "so:libusb-1.0.so.0" + ] + }, + "unbound-libs": { + "versions": { + "1.6.7-r1": 1510588258 + }, + "origin": "unbound", + "dependencies": [ + "dnssec-root", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libevent-2.1.so.6", + "so:libssl.so.44" + ], + "provides": [ + "so:libunbound.so.2=2.5.6" + ] + }, + "libqrencode-dev": { + "versions": { + "4.0.0-r0": 1509482399 + }, + "origin": "libqrencode", + "dependencies": [ + "libqrencode=4.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libqrencode=4.0.0" + ] + }, + "cyrus-sasl-gs2": { + "versions": { + "2.1.26-r11": 1510258044 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgssapi.so.3" + ] + }, + "alsa-lib-dbg": { + "versions": { + "1.1.4.1-r2": 1509468675 + }, + "origin": "alsa-lib" + }, + "lua5.1-gversion.lua": { + "versions": { + "0.2.0-r1": 1509475305 + }, + "origin": "lua-gversion", + "dependencies": [ + "lua5.1" + ] + }, + "lua-imlib2": { + "versions": { + "0.1-r1": 1509495907 + }, + "origin": "lua-imlib2", + "dependencies": [ + "so:libImlib2.so.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-exception-class-doc": { + "versions": { + "1.43-r0": 1509470597 + }, + "origin": "perl-exception-class" + }, + "libnfs": { + "versions": { + "2.0.0-r0": 1509493089 + }, + "origin": "libnfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnfs.so.11=11.0.0", + "cmd:nfs-cat", + "cmd:nfs-cp", + "cmd:nfs-ls" + ] + }, + "acf-db": { + "versions": { + "0.2.1-r2": 1510068617 + }, + "origin": "acf-db", + "dependencies": [ + "acf-core", + "acf-db-lib", + "acf-db-lib=0.2.1-r2" + ] + }, + "abiword-plugin-openxml": { + "versions": { + "3.0.2-r1": 1510073365 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "openvpn-doc": { + "versions": { + "2.4.4-r1": 1510259338 + }, + "origin": "openvpn" + }, + "device-mapper-libs": { + "versions": { + "2.02.175-r0": 1509462402 + }, + "origin": "lvm2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdevmapper.so.1.02=1.02" + ] + }, + "perl-data-page-pageset": { + "versions": { + "1.02-r1": 1509493853 + }, + "origin": "perl-data-page-pageset", + "dependencies": [ + "perl-data-page", + "perl-test-exception", + "perl-class-accessor" + ] + }, + "spice-glib": { + "versions": { + "0.34-r1": 1510260978 + }, + "origin": "spice-gtk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcelt051.so.0", + "so:libcrypto.so.42", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstapp-1.0.so.0", + "so:libgstaudio-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgstvideo-1.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:liblz4.so.1", + "so:libphodav-2.0.so.0", + "so:libpixman-1.so.0", + "so:libsasl2.so.3", + "so:libsoup-2.4.so.1", + "so:libssl.so.44", + "so:libusb-1.0.so.0", + "so:libusbredirhost.so.1", + "so:libusbredirparser.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libspice-client-glib-2.0.so.8=8.6.0", + "so:libspice-controller.so.0=0.0.0" + ] + }, + "npth": { + "versions": { + "1.5-r1": 1509472897 + }, + "origin": "npth", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnpth.so.0=0.1.1" + ] + }, + "kamailio-redis": { + "versions": { + "5.0.7-r0": 1532960877 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.13" + ] + }, + "attr-dev": { + "versions": { + "2.4.47-r6": 1509457000 + }, + "origin": "attr", + "dependencies": [ + "libattr=2.4.47-r6" + ] + }, + "freeradius-client": { + "versions": { + "1.1.7-r1": 1509488300 + }, + "origin": "freeradius-client", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfreeradius-client.so.2=2.0.0", + "cmd:login.radius", + "cmd:radacct", + "cmd:radembedded", + "cmd:radexample", + "cmd:radiusclient", + "cmd:radlogin", + "cmd:radstatus" + ] + }, + "uwsgi-graylog2": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ] + }, + "perl-net-smtp-tls-butmaintained": { + "versions": { + "0.24-r0": 1509494940 + }, + "origin": "perl-net-smtp-tls-butmaintained", + "dependencies": [ + "perl-net-ssleay", + "perl-io-socket-ssl", + "perl-digest-hmac" + ] + }, + "perl-gdtextutil-doc": { + "versions": { + "0.86-r0": 1509495188 + }, + "origin": "perl-gdtextutil" + }, + "smem-doc": { + "versions": { + "1.4-r1": 1509492706 + }, + "origin": "smem" + }, + "libiptcdata-doc": { + "versions": { + "1.0.4-r2": 1509496042 + }, + "origin": "libiptcdata" + }, + "thin-provisioning-tools-doc": { + "versions": { + "0.7.1-r0": 1509491569 + }, + "origin": "thin-provisioning-tools" + }, + "boost": { + "versions": { + "1.62.0-r5": 1509465883 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_filesystem-mt.so.1.62.0", + "so:libboost_filesystem.so.1.62.0", + "so:libboost_regex-mt.so.1.62.0", + "so:libboost_regex.so.1.62.0", + "so:libboost_system-mt.so.1.62.0", + "so:libboost_system.so.1.62.0", + "so:libboost_thread-mt.so.1.62.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libboost_atomic-mt.so.1.62.0=1.62.0", + "so:libboost_chrono-mt.so.1.62.0=1.62.0", + "so:libboost_chrono.so.1.62.0=1.62.0", + "so:libboost_container-mt.so.1.62.0=1.62.0", + "so:libboost_container.so.1.62.0=1.62.0", + "so:libboost_context-mt.so.1.62.0=1.62.0", + "so:libboost_coroutine-mt.so.1.62.0=1.62.0", + "so:libboost_coroutine.so.1.62.0=1.62.0", + "so:libboost_locale-mt.so.1.62.0=1.62.0", + "so:libboost_log-mt.so.1.62.0=1.62.0", + "so:libboost_log.so.1.62.0=1.62.0", + "so:libboost_log_setup-mt.so.1.62.0=1.62.0", + "so:libboost_log_setup.so.1.62.0=1.62.0", + "so:libboost_timer-mt.so.1.62.0=1.62.0", + "so:libboost_timer.so.1.62.0=1.62.0", + "so:libboost_type_erasure-mt.so.1.62.0=1.62.0", + "so:libboost_type_erasure.so.1.62.0=1.62.0", + "cmd:bcp", + "cmd:bjam", + "cmd:pyste.py" + ] + }, + "xe-guest-utilities": { + "versions": { + "6.1.0-r2": 1509492111 + }, + "origin": "xe-guest-utilities", + "provides": [ + "cmd:xe-daemon", + "cmd:xe-linux-distribution", + "cmd:xe-update-guest-attrs", + "cmd:xenstore", + "cmd:xenstore-chmod", + "cmd:xenstore-exists", + "cmd:xenstore-list", + "cmd:xenstore-ls", + "cmd:xenstore-read", + "cmd:xenstore-rm", + "cmd:xenstore-write" + ] + }, + "qt-assistant": { + "versions": { + "4.8.7-r8": 1510287208 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtGui.so.4", + "so:libQtHelp.so.4", + "so:libQtNetwork.so.4", + "so:libQtSql.so.4", + "so:libQtWebKit.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:assistant" + ] + }, + "py2-asn1-modules": { + "versions": { + "0.2.1-r0": 1511889299 + }, + "origin": "py-asn1-modules", + "dependencies": [ + "py2-openssl", + "py2-asn1" + ] + }, + "lua5.1-sqlite": { + "versions": { + "0.9.4-r2": 1509482115 + }, + "origin": "lua-sqlite", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "audacious-doc": { + "versions": { + "3.9-r0": 1510072524 + }, + "origin": "audacious" + }, + "apache2-ssl": { + "versions": { + "2.4.39-r0": 1554306882, + "2.4.41-r0": 1566292328 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "libressl", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "jemalloc": { + "versions": { + "5.0.1-r0": 1509482444 + }, + "origin": "jemalloc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libjemalloc.so.2=2" + ] + }, + "qt-odbc": { + "versions": { + "4.8.7-r8": 1510287208 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtSql.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libodbc.so.2", + "so:libstdc++.so.6" + ] + }, + "swig-doc": { + "versions": { + "3.0.12-r1": 1509468194 + }, + "origin": "swig" + }, + "py-flask-script": { + "versions": { + "2.0.5-r1": 1509552748 + }, + "origin": "py-flask-script", + "dependencies": [ + "python2", + "py-flask" + ] + }, + "apg": { + "versions": { + "2.2.3-r3": 1509489481 + }, + "origin": "apg", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:apg", + "cmd:apgbfm" + ] + }, + "openssh": { + "versions": { + "7.5_p1-r10": 1551712288 + }, + "origin": "openssh", + "dependencies": [ + "openssh-client", + "openssh-sftp-server", + "openssh-server", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "tdb-libs": { + "versions": { + "1.3.15-r0": 1509486504 + }, + "origin": "tdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libtdb.so.1=1.3.15" + ] + }, + "perl-locale-maketext-lexicon-doc": { + "versions": { + "1.00-r0": 1509494303 + }, + "origin": "perl-locale-maketext-lexicon" + }, + "libxt-dev": { + "versions": { + "1.1.5-r1": 1509466276 + }, + "origin": "libxt", + "dependencies": [ + "libsm-dev", + "libxt=1.1.5-r1", + "pc:ice", + "pc:sm", + "pc:x11", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xt=1.1.5" + ] + }, + "perl-io-html": { + "versions": { + "1.001-r1": 1509464368 + }, + "origin": "perl-io-html" + }, + "perl-net-smtp-ssl-doc": { + "versions": { + "1.04-r0": 1509494478 + }, + "origin": "perl-net-smtp-ssl" + }, + "paxctl": { + "versions": { + "0.9-r0": 1509495226 + }, + "origin": "paxctl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:paxctl" + ] + }, + "xtrans-doc": { + "versions": { + "1.3.5-r1": 1509461953 + }, + "origin": "xtrans" + }, + "aspell-uk": { + "versions": { + "1.4.0-r1": 1509494404 + }, + "origin": "aspell-uk" + }, + "libbonobo": { + "versions": { + "2.32.1-r6": 1510931484 + }, + "origin": "libbonobo", + "dependencies": [ + "so:libORBit-2.so.0", + "so:libORBitCosNaming-2.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libintl.so.8", + "so:libxml2.so.2" + ], + "provides": [ + "so:libbonobo-2.so.0=0.0.0", + "so:libbonobo-activation.so.4=4.0.0", + "cmd:activation-client", + "cmd:bonobo-activation-sysconf", + "cmd:bonobo-slay" + ] + }, + "perl-mailtools-doc": { + "versions": { + "2.19-r0": 1511989744 + }, + "origin": "perl-mailtools" + }, + "py-mock": { + "versions": { + "2.0.0-r3": 1509493402 + }, + "origin": "py-mock", + "dependencies": [ + "py-pbr", + "py-six" + ] + }, + "glib-dbg": { + "versions": { + "2.54.2-r0": 1509911132, + "2.54.2-r1": 1560764732 + }, + "origin": "glib" + }, + "libgcrypt-doc": { + "versions": { + "1.8.3-r0": 1529408085, + "1.8.3-r1": 1564327775, + "1.8.3-r2": 1574265351 + }, + "origin": "libgcrypt" + }, + "libffi-doc": { + "versions": { + "3.2.1-r4": 1509458692 + }, + "origin": "libffi" + }, + "linux-firmware": { + "versions": { + "20171121-r0": 1511250555 + }, + "origin": "linux-firmware" + }, + "parole-dev": { + "versions": { + "0.9.2-r1": 1510075084 + }, + "origin": "parole", + "dependencies": [ + "libxfce4ui-dev", + "libxfce4util-dev", + "intltool", + "gstreamer-dev", + "bash", + "gst-plugins-base-dev", + "libsm-dev", + "taglib-dev", + "libnotify-dev" + ] + }, + "elinks": { + "versions": { + "0.13-r4": 1510261265 + }, + "origin": "elinks", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libexpat.so.1", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:elinks" + ] + }, + "mariadb-doc": { + "versions": { + "10.1.38-r1": 1551187988, + "10.1.40-r0": 1560354885, + "10.1.41-r0": 1565163109 + }, + "origin": "mariadb" + }, + "ttf-dejavu": { + "versions": { + "2.37-r0": 1509482655 + }, + "origin": "ttf-dejavu", + "dependencies": [ + "fontconfig", + "encodings", + "mkfontdir", + "mkfontscale" + ] + }, + "perl-test-warn": { + "versions": { + "0.32-r0": 1509470538 + }, + "origin": "perl-test-warn", + "dependencies": [ + "perl-sub-uplevel" + ] + }, + "mpt-status-doc": { + "versions": { + "1.2.0-r0": 1509488649 + }, + "origin": "mpt-status" + }, + "gnutls-doc": { + "versions": { + "3.6.1-r0": 1509465348 + }, + "origin": "gnutls" + }, + "ttf-freefont": { + "versions": { + "20120503-r0": 1509491022 + }, + "origin": "ttf-freefont", + "dependencies": [ + "fontconfig", + "encodings", + "mkfontdir", + "mkfontscale" + ] + }, + "perl-data-optlist": { + "versions": { + "0.110-r0": 1509473976 + }, + "origin": "perl-data-optlist", + "dependencies": [ + "perl", + "perl-params-util", + "perl-sub-install" + ] + }, + "elfutils-libelf": { + "versions": { + "0.168-r1": 1509477711 + }, + "origin": "elfutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libelf.so.1=0" + ] + }, + "screen": { + "versions": { + "4.6.2-r0": 1509492558 + }, + "origin": "screen", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:screen", + "cmd:screen-4.6.2" + ] + }, + "libiec61883": { + "versions": { + "1.2.0-r1": 1509470083 + }, + "origin": "libiec61883", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libraw1394.so.11" + ], + "provides": [ + "so:libiec61883.so.0=0.1.1" + ] + }, + "ruby-dev": { + "versions": { + "2.4.6-r0": 1557166822 + }, + "origin": "ruby", + "dependencies": [ + "gmp-dev", + "pkgconfig", + "ruby-libs=2.4.6-r0" + ], + "provides": [ + "pc:ruby-2.4=2.4.0" + ] + }, + "bacula": { + "versions": { + "9.0.5-r3": 1512992127 + }, + "origin": "bacula", + "dependencies": [ + "/bin/sh", + "so:libbac-9.0.5.so", + "so:libbaccfg-9.0.5.so", + "so:libbacfind-9.0.5.so", + "so:libbacsd-9.0.5.so", + "so:libbacsql-9.0.5.so", + "so:libc.musl-x86_64.so.1", + "so:liblzo2.so.2", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:bacula-dir", + "cmd:bacula-sd", + "cmd:bbconsjson", + "cmd:bcopy", + "cmd:bdirjson", + "cmd:bextract", + "cmd:bfdjson", + "cmd:bls", + "cmd:bregex", + "cmd:bscan", + "cmd:bsdjson", + "cmd:bsmtp", + "cmd:btape", + "cmd:bwild", + "cmd:dbcheck" + ] + }, + "imagemagick-c++": { + "versions": { + "7.0.7.11-r1": 1510748307 + }, + "origin": "imagemagick", + "dependencies": [ + "so:libMagickCore-7.Q16HDRI.so.4", + "so:libMagickWand-7.Q16HDRI.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libMagick++-7.Q16HDRI.so.3=3.0.0" + ] + }, + "pjsua": { + "versions": { + "2.5.5-r3": 1510258881 + }, + "origin": "pjproject", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpj.so.2", + "so:libpjlib-util.so.2", + "so:libpjmedia.so.2", + "so:libpjsip-simple.so.2", + "so:libpjsip.so.2", + "so:libpjsua.so.2" + ], + "provides": [ + "cmd:pjsua" + ] + }, + "libraw": { + "versions": { + "0.18.6-r0": 1514450639 + }, + "origin": "libraw", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgomp.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libraw.so.16=16.0.0", + "so:libraw_r.so.16=16.0.0", + "cmd:4channels", + "cmd:dcraw_emu", + "cmd:dcraw_half", + "cmd:half_mt", + "cmd:mem_image", + "cmd:multirender_test", + "cmd:postprocessing_benchmark", + "cmd:raw-identify", + "cmd:simple_dcraw", + "cmd:unprocessed_raw" + ] + }, + "xgamma": { + "versions": { + "1.0.6-r0": 1509481750 + }, + "origin": "xgamma", + "dependencies": [ + "so:libX11.so.6", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xgamma" + ] + }, + "py3-alabaster": { + "versions": { + "0.7.10-r0": 1509476479 + }, + "origin": "py-alabaster", + "dependencies": [ + "python3" + ] + }, + "lua-lustache": { + "versions": { + "1.3.1-r1": 1509472928 + }, + "origin": "lua-lustache" + }, + "iperf-doc": { + "versions": { + "2.0.9-r1": 1509482124 + }, + "origin": "iperf" + }, + "perl-gdgraph": { + "versions": { + "1.54-r0": 1509496365 + }, + "origin": "perl-gdgraph", + "dependencies": [ + "perl-gdtextutil", + "perl-gd" + ] + }, + "libpq": { + "versions": { + "10.7-r0": 1554274198, + "10.8-r0": 1557668015, + "10.9-r0": 1562225614, + "10.10-r0": 1565683438 + }, + "origin": "postgresql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libldap_r-2.4.so.2", + "so:libssl.so.44" + ], + "provides": [ + "so:libpq.so.5=5.10" + ] + }, + "py-paramiko-demos": { + "versions": { + "2.4.2-r0": 1551364324 + }, + "origin": "py-paramiko", + "dependencies": [ + "py-asn1", + "py-cryptography", + "py-bcrypt", + "py-pynacl" + ] + }, + "py-tornado": { + "versions": { + "4.5.2-r1": 1509551890 + }, + "origin": "py-tornado" + }, + "xinit-doc": { + "versions": { + "1.3.4-r1": 1509473740 + }, + "origin": "xinit" + }, + "gc-doc": { + "versions": { + "7.6.0-r1": 1509467220 + }, + "origin": "gc" + }, + "libpng": { + "versions": { + "1.6.37-r0": 1557132258 + }, + "origin": "libpng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libpng16.so.16=16.37.0" + ] + }, + "curl-dbg": { + "versions": { + "7.61.1-r2": 1551780208, + "7.61.1-r3": 1568722562 + }, + "origin": "curl", + "dependencies": [ + "ca-certificates" + ] + }, + "sipsak-dbg": { + "versions": { + "0.9.6-r9": 1510260059 + }, + "origin": "sipsak" + }, + "kbd-misc": { + "versions": { + "2.0.4-r2": 1510922530 + }, + "origin": "kbd" + }, + "dnssec-root": { + "versions": { + "20100715-r3": 1510588189 + }, + "origin": "dnssec-root" + }, + "shorewall": { + "versions": { + "5.1.8-r0": 1509481282 + }, + "origin": "shorewall", + "dependencies": [ + "shorewall-core", + "perl", + "iptables", + "iproute2" + ] + }, + "perl-class-accessor": { + "versions": { + "0.34-r0": 1509474098 + }, + "origin": "perl-class-accessor", + "dependencies": [ + "perl" + ] + }, + "herbstluftwm-bash-completion": { + "versions": { + "0.7.0-r1": 1509551853 + }, + "origin": "herbstluftwm" + }, + "xfce4-session-doc": { + "versions": { + "4.12.1-r2": 1510074574 + }, + "origin": "xfce4-session" + }, + "pngcrush": { + "versions": { + "1.8.13-r0": 1509482684 + }, + "origin": "pngcrush", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpng16.so.16", + "so:libz.so.1" + ], + "provides": [ + "cmd:pngcrush" + ] + }, + "lua5.1-ldap": { + "versions": { + "1.2.3-r5": 1509485733 + }, + "origin": "lua-ldap", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ] + }, + "libarchive-doc": { + "versions": { + "3.3.2-r2": 1510258000, + "3.3.3-r0": 1566312165, + "3.3.3-r1": 1572677952 + }, + "origin": "libarchive" + }, + "libburn": { + "versions": { + "1.4.8-r0": 1509462200 + }, + "origin": "libburn", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libburn.so.4=4.101.0", + "cmd:cdrskin" + ] + }, + "clamav-lib": { + "versions": { + "0.100.3-r0": 1555508237 + }, + "origin": "clamav", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libclammspack.so.0", + "so:libcrypto.so.42", + "so:libpcre.so.1", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:libclamav.so.7=7.1.2" + ] + }, + "lua-xctrl": { + "versions": { + "2015.04.10-r2": 1509475304 + }, + "origin": "lua-xctrl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "apr-util-dbd_pgsql": { + "versions": { + "1.6.1-r1": 1510285059 + }, + "origin": "apr-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "xen-doc": { + "versions": { + "4.9.4-r0": 1551280495, + "4.9.4-r1": 1558103152 + }, + "origin": "xen" + }, + "libglade-doc": { + "versions": { + "2.6.4-r14": 1510071791 + }, + "origin": "libglade" + }, + "speexdsp-doc": { + "versions": { + "1.2_rc3-r4": 1509473232 + }, + "origin": "speexdsp" + }, + "lua-feedparser-common": { + "versions": { + "0.71-r0": 1509485573 + }, + "origin": "lua-feedparser" + }, + "libnftnl": { + "versions": { + "1.0.8-r1": 1509909171 + }, + "origin": "libnftnl" + }, + "cogl-dev": { + "versions": { + "1.22.2-r0": 1510072960 + }, + "origin": "cogl", + "dependencies": [ + "glib-dev", + "mesa-dev", + "libdrm-dev", + "libxdamage-dev", + "libxcomposite-dev", + "libxrandr-dev", + "gdk-pixbuf-dev", + "pango-dev", + "cairo-dev", + "gobject-introspection-dev", + "libxext-dev", + "eudev-dev", + "cogl=1.22.2-r0", + "pc:cairo>=1.10", + "pc:egl", + "pc:gbm", + "pc:gdk-pixbuf-2.0>=2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:libdrm", + "pc:x11", + "pc:xcomposite>=0.4", + "pc:xdamage", + "pc:xext", + "pc:xfixes>=3", + "pc:xrandr>=1.2", + "pkgconfig" + ], + "provides": [ + "pc:cogl-1.0=1.22.2", + "pc:cogl-2.0-experimental=2.0.0", + "pc:cogl-gl-1.0=1.22.2", + "pc:cogl-gles2-1.0=1.22.2", + "pc:cogl-gles2-2.0-experimental=2.0.0", + "pc:cogl-pango-1.0=1.22.2", + "pc:cogl-pango-2.0-experimental=1.22.2", + "pc:cogl-path-1.0=1.22.2", + "pc:cogl-path-2.0-experimental=1.22.2" + ] + }, + "freeswitch-sounds-fr-fr-sibylle-8000": { + "versions": { + "0.1.3-r0": 1509482708 + }, + "origin": "freeswitch-sounds-fr-fr-sibylle-8000" + }, + "freeswitch-dbg": { + "versions": { + "1.6.19-r0": 1510585356 + }, + "origin": "freeswitch" + }, + "openvswitch-doc": { + "versions": { + "2.8.1-r2": 1512030502 + }, + "origin": "openvswitch" + }, + "py2-pbr": { + "versions": { + "3.1.1-r0": 1509480855 + }, + "origin": "py-pbr", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:pbr" + ] + }, + "protobuf-c-dev": { + "versions": { + "1.3.0-r3": 1510846239 + }, + "origin": "protobuf-c", + "dependencies": [ + "pkgconfig", + "protobuf-c=1.3.0-r3" + ], + "provides": [ + "pc:libprotobuf-c=1.3.0" + ] + }, + "libxml2-dbg": { + "versions": { + "2.9.8-r1": 1540398579 + }, + "origin": "libxml2" + }, + "goaccess": { + "versions": { + "1.2-r1": 1509480167 + }, + "origin": "goaccess", + "dependencies": [ + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:goaccess" + ] + }, + "dosfstools": { + "versions": { + "4.1-r1": 1509495036 + }, + "origin": "dosfstools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dosfsck", + "cmd:dosfslabel", + "cmd:fatlabel", + "cmd:fsck.fat", + "cmd:fsck.msdos", + "cmd:fsck.vfat", + "cmd:mkdosfs", + "cmd:mkfs.fat", + "cmd:mkfs.msdos", + "cmd:mkfs.vfat" + ] + }, + "py3-imagesize": { + "versions": { + "0.7.1-r2": 1509474092 + }, + "origin": "py-imagesize", + "dependencies": [ + "python3" + ] + }, + "lua5.3-sql-odbc": { + "versions": { + "2.3.5-r1": 1509488833 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ] + }, + "printproto-doc": { + "versions": { + "1.0.5-r2": 1509494090 + }, + "origin": "printproto" + }, + "libsrtp-dev": { + "versions": { + "1.5.4-r0": 1509473355 + }, + "origin": "libsrtp", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:libsrtp=1.5.4" + ] + }, + "nikto": { + "versions": { + "2.1.5-r2": 1509489224 + }, + "origin": "nikto", + "dependencies": [ + "perl", + "nmap", + "libressl" + ], + "provides": [ + "cmd:nikto.pl" + ] + }, + "cciss_vol_status-doc": { + "versions": { + "1.12-r0": 1509493251 + }, + "origin": "cciss_vol_status" + }, + "gcr": { + "versions": { + "3.20.0-r1": 1510073693 + }, + "origin": "gcr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgcrypt.so.20", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libp11-kit.so.0", + "so:libpango-1.0.so.0" + ], + "provides": [ + "so:libgck-1.so.0=0.0.0", + "so:libgcr-base-3.so.1=1.0.0", + "so:libgcr-ui-3.so.1=1.0.0", + "cmd:gcr-viewer" + ] + }, + "lz4-libs": { + "versions": { + "1.8.0-r1": 1509461405 + }, + "origin": "lz4", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblz4.so.1=1.8.0" + ] + }, + "speex-dev": { + "versions": { + "1.2.0-r0": 1509473244 + }, + "origin": "speex", + "dependencies": [ + "pkgconfig", + "speex=1.2.0-r0" + ], + "provides": [ + "pc:speex=1.2.0" + ] + }, + "gradm-doc": { + "versions": { + "3.1.201607172312-r0": 1509494154 + }, + "origin": "gradm" + }, + "xsetroot-doc": { + "versions": { + "1.1.1-r1": 1509490722 + }, + "origin": "xsetroot" + }, + "acf-openldap": { + "versions": { + "1.0.1-r4": 1510072538 + }, + "origin": "acf-openldap", + "dependencies": [ + "acf-core", + "openldap", + "openldap-back-bdb" + ] + }, + "perl-www-robotrules": { + "versions": { + "6.02-r1": 1509464408 + }, + "origin": "perl-www-robotrules", + "dependencies": [ + "perl", + "perl-uri" + ] + }, + "ulogd-mysql": { + "versions": { + "2.0.5-r4": 1509480395 + }, + "origin": "ulogd", + "dependencies": [ + "ulogd", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "gst-plugins-base0.10-lang": { + "versions": { + "0.10.36-r3": 1510068368 + }, + "origin": "gst-plugins-base0.10" + }, + "tango-icon-theme": { + "versions": { + "0.8.90-r4": 1510365697 + }, + "origin": "tango-icon-theme" + }, + "qemu-x86_64": { + "versions": { + "2.10.1-r3": 1519746244 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-x86_64" + ] + }, + "clamav-libunrar": { + "versions": { + "0.100.3-r0": 1555508237 + }, + "origin": "clamav", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libclamunrar.so.7=7.1.2", + "so:libclamunrar_iface.so.7=7.1.2" + ] + }, + "perl-getopt-long-doc": { + "versions": { + "2.50-r0": 1509491104 + }, + "origin": "perl-getopt-long" + }, + "dhcpcd-doc": { + "versions": { + "6.11.5-r1": 1509494692 + }, + "origin": "dhcpcd" + }, + "py-backports_abc": { + "versions": { + "0.4-r3": 1509552782 + }, + "origin": "py-backports_abc" + }, + "dovecot-pigeonhole-plugin-ldap": { + "versions": { + "2.2.36.3-r0": 1554102387, + "2.2.36.4-r0": 1567075098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot-pigeonhole-plugin", + "dovecot-ldap", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ] + }, + "py3-flask-oauthlib": { + "versions": { + "0.9.3-r2": 1509552815 + }, + "origin": "py-flask-oauthlib", + "dependencies": [ + "py3-flask", + "py3-requests-oauthlib", + "python3" + ] + }, + "sems-early_announce": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "perl-test-without-module-doc": { + "versions": { + "0.20-r0": 1509481284 + }, + "origin": "perl-test-without-module" + }, + "libsndfile-dev": { + "versions": { + "1.0.28-r4": 1548491633, + "1.0.28-r5": 1563345857 + }, + "origin": "libsndfile", + "dependencies": [ + "flac-dev", + "libvorbis-dev", + "libogg-dev", + "libsndfile=1.0.28-r5", + "pkgconfig" + ], + "provides": [ + "pc:sndfile=1.0.28" + ] + }, + "xsetroot": { + "versions": { + "1.1.1-r1": 1509490722 + }, + "origin": "xsetroot", + "dependencies": [ + "so:libX11.so.6", + "so:libXcursor.so.1", + "so:libXmuu.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xsetroot" + ] + }, + "nagios-plugins-dhcp": { + "versions": { + "2.2.1-r3": 1510288494 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "yajl-tools": { + "versions": { + "2.1.0-r0": 1509475316 + }, + "origin": "yajl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:json_reformat", + "cmd:json_verify" + ] + }, + "libxvmc-doc": { + "versions": { + "1.0.10-r1": 1509467179 + }, + "origin": "libxvmc" + }, + "py-configshell": { + "versions": { + "1.1_p19-r2": 1509493963 + }, + "origin": "py-configshell", + "dependencies": [ + "python2", + "py-six", + "py-urwid", + "py-parsing" + ] + }, + "libgcrypt-dev": { + "versions": { + "1.8.3-r0": 1529408084, + "1.8.3-r1": 1564327775, + "1.8.3-r2": 1574265351 + }, + "origin": "libgcrypt", + "dependencies": [ + "libgpg-error-dev", + "libgcrypt=1.8.3-r2" + ], + "provides": [ + "cmd:libgcrypt-config" + ] + }, + "perl-convert-tnef-doc": { + "versions": { + "0.18-r0": 1509470831 + }, + "origin": "perl-convert-tnef" + }, + "perl-stream-buffered-doc": { + "versions": { + "0.02-r1": 1510564489 + }, + "origin": "perl-stream-buffered" + }, + "libgsasl-dev": { + "versions": { + "1.8.0-r2": 1509493437 + }, + "origin": "libgsasl", + "dependencies": [ + "pkgconfig", + "libgsasl=1.8.0-r2" + ], + "provides": [ + "pc:libgsasl=1.8.0" + ] + }, + "lcms2-doc": { + "versions": { + "2.8-r1": 1509464952 + }, + "origin": "lcms2" + }, + "distcc": { + "versions": { + "3.1-r12": 1509493045 + }, + "origin": "distcc", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:distcc", + "cmd:distccd", + "cmd:distccmon-text", + "cmd:lsdistcc" + ] + }, + "py-gobject-doc": { + "versions": { + "2.28.6-r6": 1509471051 + }, + "origin": "py-gobject" + }, + "squid-lang-et": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865667 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-importer": { + "versions": { + "0.024-r0": 1509481615 + }, + "origin": "perl-importer", + "dependencies": [ + "perl" + ] + }, + "libgss-doc": { + "versions": { + "0.1.5-r1": 1509493415 + }, + "origin": "libgss" + }, + "transmission-cli": { + "versions": { + "2.92-r8": 1510932986 + }, + "origin": "transmission", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libevent-2.1.so.6", + "so:libintl.so.8", + "so:libz.so.1" + ], + "provides": [ + "cmd:transmission-cli", + "cmd:transmission-create", + "cmd:transmission-edit", + "cmd:transmission-remote", + "cmd:transmission-show" + ] + }, + "gnupg": { + "versions": { + "2.2.3-r1": 1528897438 + }, + "origin": "gnupg", + "dependencies": [ + "pinentry", + "so:libassuan.so.0", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libgnutls.so.30", + "so:libgpg-error.so.0", + "so:libksba.so.8", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libnpth.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:addgnupghome", + "cmd:applygnupgdefaults", + "cmd:dirmngr", + "cmd:dirmngr-client", + "cmd:gpg", + "cmd:gpg-agent", + "cmd:gpg-connect-agent", + "cmd:gpg2", + "cmd:gpgconf", + "cmd:gpgparsemail", + "cmd:gpgscm", + "cmd:gpgsm", + "cmd:gpgtar", + "cmd:gpgv", + "cmd:gpgv2", + "cmd:kbxutil", + "cmd:watchgnupg" + ] + }, + "kamailio-ims": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libkamailio_ims.so.0", + "so:libsrdb1.so.1", + "so:libsrutils.so.1", + "so:libxml2.so.2" + ] + }, + "py2-gflags": { + "versions": { + "3.1.1-r1": 1509551806 + }, + "origin": "py-gflags", + "dependencies": [ + "python2" + ] + }, + "hunspell-pt": { + "versions": { + "20170814-r0": 1511168470 + }, + "origin": "hunspell-pt" + }, + "uwsgi-transformation_offload": { + "versions": { + "2.0.17-r0": 1522154660 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "tcl": { + "versions": { + "8.6.7-r0": 1509460298 + }, + "origin": "tcl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libtcl8.6.so=0", + "cmd:tclsh", + "cmd:tclsh8.6" + ] + }, + "perl-file-sharedir": { + "versions": { + "1.104-r0": 1510564486 + }, + "origin": "perl-file-sharedir", + "dependencies": [ + "perl", + "perl-class-inspector" + ] + }, + "perl-digest-md5": { + "versions": { + "2.55-r2": 1509482652 + }, + "origin": "perl-digest-md5", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-email-date-format": { + "versions": { + "1.005-r0": 1509489452 + }, + "origin": "perl-email-date-format" + }, + "mdadm-udev": { + "versions": { + "4.0-r0": 1509476488 + }, + "origin": "mdadm" + }, + "bluez-meshctl": { + "versions": { + "5.47-r3": 1510069788 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0", + "so:libjson-c.so.2", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:meshctl" + ] + }, + "orc-dev": { + "versions": { + "0.4.27-r0": 1509469955 + }, + "origin": "orc", + "dependencies": [ + "orc=0.4.27-r0", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:liborc-0.4.so.0", + "so:liborc-test-0.4.so.0" + ], + "provides": [ + "pc:orc-0.4=0.4.27", + "cmd:orc-bugreport" + ] + }, + "perl-class-returnvalue-doc": { + "versions": { + "0.55-r1": 1510564545 + }, + "origin": "perl-class-returnvalue" + }, + "lxc-dev": { + "versions": { + "2.1.1-r3": 1533578832 + }, + "origin": "lxc", + "dependencies": [ + "libcap-dev", + "lxc-libs=2.1.1-r3", + "pkgconfig" + ], + "provides": [ + "pc:lxc=2.1.1", + "cmd:lxc-update-config" + ] + }, + "libtxc_dxtn": { + "versions": { + "1.0.1-r5": 1510075567 + }, + "origin": "libtxc_dxtn", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libtxc_dxtn.so=0" + ] + }, + "perl-xml-sax-base-doc": { + "versions": { + "1.09-r0": 1509475899 + }, + "origin": "perl-xml-sax-base" + }, + "py3-jwt": { + "versions": { + "1.5.0-r1": 1509552755 + }, + "origin": "py-jwt", + "dependencies": [ + "python3" + ] + }, + "perl-data-optlist-doc": { + "versions": { + "0.110-r0": 1509473973 + }, + "origin": "perl-data-optlist" + }, + "xcalib": { + "versions": { + "0.8-r0": 1509494572 + }, + "origin": "xcalib", + "dependencies": [ + "so:libX11.so.6", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xcalib" + ] + }, + "lcms2-dev": { + "versions": { + "2.8-r1": 1509464951 + }, + "origin": "lcms2", + "dependencies": [ + "libjpeg-turbo-dev", + "tiff-dev", + "zlib-dev", + "lcms2=2.8-r1", + "pkgconfig" + ], + "provides": [ + "pc:lcms2=2.8" + ] + }, + "binutils-libs": { + "versions": { + "2.30-r1": 1527754981, + "2.30-r2": 1565788943 + }, + "origin": "binutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libbfd-2.30.so=0", + "so:libopcodes-2.30.so=0" + ] + }, + "mesa-dri-swrast": { + "versions": { + "17.2.4-r1": 1510741947 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "mpg123-doc": { + "versions": { + "1.25.7-r0": 1509707456 + }, + "origin": "mpg123" + }, + "libshout": { + "versions": { + "2.4.1-r1": 1510068384 + }, + "origin": "libshout", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libspeex.so.1", + "so:libtheora.so.0", + "so:libvorbis.so.0" + ], + "provides": [ + "so:libshout.so.3=3.2.0" + ] + }, + "libstdc++": { + "versions": { + "6.4.0-r5": 1509458072 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libstdc++.so.6=6.0.22" + ] + }, + "perl-ipc-run3": { + "versions": { + "0.048-r0": 1509481629 + }, + "origin": "perl-ipc-run3", + "dependencies": [ + "perl" + ] + }, + "bluez-cups": { + "versions": { + "5.47-r3": 1510069787 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0" + ] + }, + "ebtables-doc": { + "versions": { + "2.0.10.4-r2": 1509491244 + }, + "origin": "ebtables" + }, + "lua5.2-ldap": { + "versions": { + "1.2.3-r5": 1509485733 + }, + "origin": "lua-ldap", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ] + }, + "py-asn1-modules": { + "versions": { + "0.2.1-r0": 1511889299 + }, + "origin": "py-asn1-modules" + }, + "lua-sql-mysql": { + "versions": { + "2.3.5-r1": 1509488833 + }, + "origin": "lua-sql" + }, + "py3-ecdsa": { + "versions": { + "0.13-r5": 1509494955 + }, + "origin": "py-ecdsa", + "dependencies": [ + "py3-crypto" + ] + }, + "libfetch": { + "versions": { + "2.33-r2": 1509490729 + }, + "origin": "libfetch", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfetch.so=0" + ] + }, + "perl-sub-uplevel": { + "versions": { + "0.2800-r0": 1509470532 + }, + "origin": "perl-sub-uplevel", + "dependencies": [ + "perl" + ] + }, + "perl-html-format": { + "versions": { + "2.11-r0": 1509481566 + }, + "origin": "perl-html-format", + "dependencies": [ + "perl-html-tree", + "perl-font-afm" + ] + }, + "freeswitch-sample-config": { + "versions": { + "1.6.19-r0": 1510585357 + }, + "origin": "freeswitch", + "dependencies": [ + "freeswitch-timezones" + ] + }, + "gobject-introspection-dev": { + "versions": { + "1.54.1-r0": 1509464659 + }, + "origin": "gobject-introspection", + "dependencies": [ + "python2", + "glib-dev", + "libffi-dev", + "cairo-dev", + "libtool", + "gobject-introspection=1.54.1-r0", + "pc:glib-2.0", + "pc:gmodule-2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:libffi", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libgio-2.0.so.0", + "so:libgirepository-1.0.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "pc:gobject-introspection-1.0=1.54.1", + "pc:gobject-introspection-no-export-1.0=1.54.1", + "cmd:g-ir-annotation-tool", + "cmd:g-ir-compiler", + "cmd:g-ir-generate", + "cmd:g-ir-inspect", + "cmd:g-ir-scanner" + ] + }, + "gdk-pixbuf-lang": { + "versions": { + "2.36.10-r0": 1509465514 + }, + "origin": "gdk-pixbuf", + "dependencies": [ + "shared-mime-info" + ] + }, + "py-rsa": { + "versions": { + "3.4.2-r2": 1509551794 + }, + "origin": "py-rsa", + "dependencies": [ + "py-asn1" + ] + }, + "unrar": { + "versions": { + "5.5.8-r0": 1509468421 + }, + "origin": "unrar", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:unrar" + ] + }, + "halberd": { + "versions": { + "0.2.4-r1": 1509483053 + }, + "origin": "halberd", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:halberd" + ] + }, + "squid-lang-sr": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865669 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "nagios-plugins-procs": { + "versions": { + "2.2.1-r3": 1510288497 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-xml-simple": { + "versions": { + "2.24-r0": 1509477314 + }, + "origin": "perl-xml-simple", + "dependencies": [ + "perl-xml-parser", + "perl" + ] + }, + "perl-html-formattext-withlinks": { + "versions": { + "0.15-r0": 1509481574 + }, + "origin": "perl-html-formattext-withlinks", + "dependencies": [ + "perl-html-format", + "perl-uri", + "perl-html-tree" + ] + }, + "gc-dev": { + "versions": { + "7.6.0-r1": 1509467220 + }, + "origin": "gc", + "dependencies": [ + "gc=7.6.0-r1", + "libgc++=7.6.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:bdw-gc=7.6.0" + ] + }, + "perl-xml-parser-doc": { + "versions": { + "2.44-r4": 1509464445 + }, + "origin": "perl-xml-parser" + }, + "parole": { + "versions": { + "0.9.2-r1": 1510075086 + }, + "origin": "parole", + "dependencies": [ + "gst-plugins-good", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstaudio-1.0.so.0", + "so:libgstpbutils-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgsttag-1.0.so.0", + "so:libgstvideo-1.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libnotify.so.4", + "so:libtag_c.so.0", + "so:libxfce4ui-2.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:parole" + ] + }, + "s6-doc": { + "versions": { + "2.6.1.1-r0": 1510364940 + }, + "origin": "s6" + }, + "pangox-compat-dev": { + "versions": { + "0.0.2-r0": 1509482459 + }, + "origin": "pangox-compat", + "dependencies": [ + "pango-dev", + "libice-dev", + "pangox-compat=0.0.2-r0", + "pc:pango", + "pkgconfig" + ], + "provides": [ + "pc:pangox=1.30" + ] + }, + "libressl2.6-libssl": { + "versions": { + "2.6.5-r0": 1529043883 + }, + "origin": "libressl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ], + "provides": [ + "so:libssl.so.44=44.0.1" + ] + }, + "mosquitto": { + "versions": { + "1.4.15-r0": 1520176488, + "1.4.15-r2": 1563346324 + }, + "origin": "mosquitto", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libuuid.so.1", + "so:libwebsockets.so.12" + ], + "provides": [ + "cmd:mosquitto", + "cmd:mosquitto_passwd" + ] + }, + "libfdisk": { + "versions": { + "2.31.1-r0": 1541506294 + }, + "origin": "util-linux", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libfdisk.so.1=1.1.0" + ] + }, + "py3-funcsigs": { + "versions": { + "1.0.2-r1": 1509485907 + }, + "origin": "py-funcsigs", + "dependencies": [ + "python3" + ] + }, + "perl-list-someutils-doc": { + "versions": { + "0.53-r0": 1509474586 + }, + "origin": "perl-list-someutils" + }, + "kamailio-dbg": { + "versions": { + "5.0.7-r0": 1532960874 + }, + "origin": "kamailio" + }, + "libusb-compat": { + "versions": { + "0.1.5-r3": 1509473938 + }, + "origin": "libusb-compat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libusb-0.1.so.4=4.4.4" + ] + }, + "lzip-doc": { + "versions": { + "1.19-r1": 1509459870 + }, + "origin": "lzip" + }, + "wavpack-doc": { + "versions": { + "5.1.0-r3": 1548940101, + "5.1.0-r4": 1566809779 + }, + "origin": "wavpack" + }, + "perl-try-tiny-doc": { + "versions": { + "0.22-r1": 1509464413 + }, + "origin": "perl-try-tiny" + }, + "perl-html-parser-doc": { + "versions": { + "3.72-r2": 1509464394 + }, + "origin": "perl-html-parser" + }, + "attr-doc": { + "versions": { + "2.4.47-r6": 1509457001 + }, + "origin": "attr" + }, + "cifs-utils-dev": { + "versions": { + "6.7-r1": 1509475350 + }, + "origin": "cifs-utils" + }, + "perl-module-versions-report-doc": { + "versions": { + "1.06-r0": 1509495045 + }, + "origin": "perl-module-versions-report" + }, + "libguess-dev": { + "versions": { + "1.2-r0": 1509494181 + }, + "origin": "libguess", + "dependencies": [ + "libguess=1.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libguess=1.2" + ] + }, + "lua5.3-stringy": { + "versions": { + "0.4.0-r5": 1509480103 + }, + "origin": "lua-stringy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "pgtcl": { + "versions": { + "2.1.0-r0": 1509489163 + }, + "origin": "pgtcl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "perl-xml-rss-doc": { + "versions": { + "1.59-r1": 1511389028 + }, + "origin": "perl-xml-rss" + }, + "asciidoc-doc": { + "versions": { + "8.6.10-r0": 1509461782 + }, + "origin": "asciidoc" + }, + "libvirt-common-drivers": { + "versions": { + "3.9.0-r1": 1510088352, + "5.5.0-r0": 1562165541 + }, + "origin": "libvirt", + "dependencies": [ + "lvm2", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libnetcf.so.1", + "so:libpcap.so.1", + "so:libpciaccess.so.0", + "so:libudev.so.1", + "so:libvirt.so.0", + "so:libxml2.so.2" + ] + }, + "libssh2": { + "versions": { + "1.8.2-r0": 1553682025, + "1.9.0-r0": 1571235284, + "1.9.0-r1": 1571996777 + }, + "origin": "libssh2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libz.so.1" + ], + "provides": [ + "so:libssh2.so.1=1.0.1" + ] + }, + "perl-role-basic": { + "versions": { + "0.13-r0": 1509468428 + }, + "origin": "perl-role-basic" + }, + "haveged": { + "versions": { + "1.9.2-r0": 1511893694 + }, + "origin": "haveged", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhavege.so.1=1.1.0", + "cmd:haveged" + ] + }, + "perl-test-deep-doc": { + "versions": { + "1.126-r0": 1509468479 + }, + "origin": "perl-test-deep" + }, + "perl-want-doc": { + "versions": { + "0.29-r1": 1509477485 + }, + "origin": "perl-want" + }, + "perl-text-template": { + "versions": { + "1.46-r0": 1509491534 + }, + "origin": "perl-text-template", + "dependencies": [ + "perl" + ] + }, + "perl-proc-wait3": { + "versions": { + "0.05-r1": 1509482407 + }, + "origin": "perl-proc-wait3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "keyutils-libs": { + "versions": { + "1.5.10-r0": 1509469695 + }, + "origin": "keyutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libkeyutils.so.1=1.6" + ] + }, + "samba-test": { + "versions": { + "4.7.6-r3": 1555491789 + }, + "origin": "samba", + "dependencies": [ + "so:libMESSAGING-SEND-samba4.so", + "so:libMESSAGING-samba4.so", + "so:libaddns-samba4.so", + "so:libasn1-samba4.so.8", + "so:libasn1util-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-ldap-samba4.so", + "so:libcli-nbt-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcliauth-samba4.so", + "so:libcluster-samba4.so", + "so:libcmdline-credentials-samba4.so", + "so:libcom_err.so.2", + "so:libcommon-auth-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libdcerpc.so.0", + "so:libdnsserver-common-samba4.so", + "so:libdsdb-module-samba4.so", + "so:libevents-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgnutls.so.30", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:liblibsmb-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libnetapi.so.0", + "so:libnetif-samba4.so", + "so:libpopt.so.0", + "so:libregistry-samba4.so", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-net-samba4.so", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libshares-samba4.so", + "so:libsmbclient-raw-samba4.so", + "so:libsmbclient.so.0", + "so:libsmbconf.so.0", + "so:libsmbpasswdparser-samba4.so", + "so:libsys-rw-samba4.so", + "so:libtalloc.so.2", + "so:libtdb-wrap-samba4.so", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtime-basic-samba4.so", + "so:libutil-reg-samba4.so", + "so:libutil-tdb-samba4.so", + "so:libwbclient.so.0", + "so:libwinbind-client-samba4.so" + ], + "provides": [ + "so:libdlz-bind9-for-torture-samba4.so=0", + "so:libtorture-samba4.so=0", + "cmd:gentest", + "cmd:locktest", + "cmd:masktest", + "cmd:ndrdump", + "cmd:smbtorture" + ] + }, + "freeswitch-sangoma": { + "versions": { + "1.6.19-r0": 1510585357 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreeswitch.so.1", + "so:libsngtc_node.so" + ] + }, + "open-iscsi-doc": { + "versions": { + "2.0.874-r0": 1509494138 + }, + "origin": "open-iscsi" + }, + "alsa-lib": { + "versions": { + "1.1.4.1-r2": 1509468675 + }, + "origin": "alsa-lib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libasound.so.2=2.0.0", + "cmd:aserver" + ] + }, + "gconf": { + "versions": { + "3.2.6-r1": 1510928343 + }, + "origin": "gconf", + "dependencies": [ + "so:libORBit-2.so.0", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libintl.so.8", + "so:libpolkit-gobject-1.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgconf-2.so.4=4.1.5", + "cmd:gconf-merge-tree", + "cmd:gconftool-2", + "cmd:gsettings-data-convert", + "cmd:gsettings-schema-convert" + ] + }, + "ircservices": { + "versions": { + "5.1.24-r4": 1509481224 + }, + "origin": "ircservices", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ircservices", + "cmd:ircservices-chk", + "cmd:ircservices-convert-db" + ] + }, + "faac-doc": { + "versions": { + "1.28-r11": 1509470017 + }, + "origin": "faac" + }, + "xrdb": { + "versions": { + "1.1.0-r2": 1509473736 + }, + "origin": "xrdb", + "dependencies": [ + "mcpp", + "so:libX11.so.6", + "so:libXmuu.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xrdb" + ] + }, + "mariadb-dev": { + "versions": { + "10.1.38-r1": 1551187990, + "10.1.40-r0": 1560354886, + "10.1.41-r0": 1565163110 + }, + "origin": "mariadb", + "dependencies": [ + "libressl-dev", + "zlib-dev", + "mariadb-client-libs=10.1.41-r0", + "mariadb-libs=10.1.41-r0" + ], + "provides": [ + "mysql-dev=10.1.41-r0", + "cmd:mysql_config" + ] + }, + "qemu-system-sh4eb": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-sh4eb" + ] + }, + "ircii-doc": { + "versions": { + "20111115-r4": 1509492740 + }, + "origin": "ircii" + }, + "perl-mime-tools": { + "versions": { + "5.509-r1": 1509469901 + }, + "origin": "perl-mime-tools", + "dependencies": [ + "perl", + "perl-io-stringy", + "perl-mailtools", + "perl-convert-binhex" + ] + }, + "tinyproxy-doc": { + "versions": { + "1.8.4-r3": 1509491455 + }, + "origin": "tinyproxy" + }, + "fish-tools": { + "versions": { + "2.6.0-r2": 1511486201 + }, + "origin": "fish", + "dependencies": [ + "fish", + "python" + ] + }, + "perl-convert-binhex": { + "versions": { + "1.125-r0": 1509469888 + }, + "origin": "perl-convert-binhex", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:binhex.pl", + "cmd:debinhex.pl" + ] + }, + "sudo": { + "versions": { + "1.8.21_p2-r1": 1509459675 + }, + "origin": "sudo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:group_file.so=0", + "so:libsudo_noexec.so=0", + "so:libsudo_util.so.0=0.0.0", + "so:sudoers.so=0", + "so:system_group.so=0", + "cmd:sudo", + "cmd:sudoedit", + "cmd:sudoreplay", + "cmd:visudo" + ] + }, + "xmlrpc-c-doc": { + "versions": { + "1.39.11-r0": 1509482031 + }, + "origin": "xmlrpc-c" + }, + "openvpn-auth-pam": { + "versions": { + "2.4.4-r1": 1510259339 + }, + "origin": "openvpn", + "dependencies": [ + "iproute2", + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0" + ] + }, + "aspell-dev": { + "versions": { + "0.60.6.1-r12": 1509472867, + "0.60.6.1-r13": 1572541141 + }, + "origin": "aspell", + "dependencies": [ + "aspell-utils", + "aspell-libs=0.60.6.1-r13" + ], + "provides": [ + "cmd:pspell-config" + ] + }, + "sems-conference": { + "versions": { + "1.6.0-r6": 1510260895 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "avahi-ui-gtk3": { + "versions": { + "0.6.31-r7": 1510076323 + }, + "origin": "avahi-ui", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-glib.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libgdk-3.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libavahi-ui-gtk3.so.0=0.1.4" + ] + }, + "xset": { + "versions": { + "1.2.3-r1": 1509481585 + }, + "origin": "xset", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXmuu.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xset" + ] + }, + "vanessa_logger": { + "versions": { + "0.0.10-r0": 1509481344 + }, + "origin": "vanessa_logger", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libvanessa_logger.so.0=0.0.5", + "cmd:vanessa_logger_sample" + ] + }, + "libmpeg2-doc": { + "versions": { + "0.5.1-r7": 1509473556 + }, + "origin": "libmpeg2" + }, + "mcookie": { + "versions": { + "2.31.1-r0": 1541506294 + }, + "origin": "util-linux", + "dependencies": [ + "findmnt", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mcookie" + ] + }, + "antiword": { + "versions": { + "0.37-r3": 1509476493 + }, + "origin": "antiword", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:antiword" + ] + }, + "e2fsprogs": { + "versions": { + "1.43.7-r0": 1509469275, + "1.43.7-r1": 1571322245 + }, + "origin": "e2fsprogs", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libe2p.so.2", + "so:libext2fs.so.2", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:e2fsck", + "cmd:fsck.ext2", + "cmd:fsck.ext3", + "cmd:fsck.ext4", + "cmd:mke2fs", + "cmd:mkfs.ext2", + "cmd:mkfs.ext3", + "cmd:mkfs.ext4" + ] + }, + "libwebsockets": { + "versions": { + "2.4.0-r1": 1510258055 + }, + "origin": "libwebsockets", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:libwebsockets.so.12=12" + ] + }, + "perl-mail-clamav": { + "versions": { + "0.29-r12": 1509489512 + }, + "origin": "perl-mail-clamav", + "dependencies": [ + "perl", + "clamav", + "so:libc.musl-x86_64.so.1", + "so:libclamav.so.7" + ] + }, + "iwlwifi-1000-ucode": { + "versions": { + "39.31.5.1-r0": 1509483630 + }, + "origin": "iwlwifi-1000-ucode" + }, + "libmemcached-libs": { + "versions": { + "1.0.18-r2": 1520366184 + }, + "origin": "libmemcached", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libsasl2.so.3", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libhashkit.so.2=2.0.0", + "so:libmemcached.so.11=11.0.0", + "so:libmemcachedprotocol.so.0=0.0.0", + "so:libmemcachedutil.so.2=2.0.0" + ] + }, + "perl-test-mocktime": { + "versions": { + "0.15-r0": 1509475511 + }, + "origin": "perl-test-mocktime" + }, + "libev-doc": { + "versions": { + "4.24-r0": 1509469569 + }, + "origin": "libev" + }, + "graphviz-doc": { + "versions": { + "2.40.1-r0": 1510066917 + }, + "origin": "graphviz" + }, + "bluez-firmware": { + "versions": { + "1.2-r0": 1509492592 + }, + "origin": "bluez-firmware" + }, + "spice-gtk-doc": { + "versions": { + "0.34-r1": 1510260978 + }, + "origin": "spice-gtk" + }, + "irqbalance-doc": { + "versions": { + "1.2.0-r1": 1509492255 + }, + "origin": "irqbalance" + }, + "minizip": { + "versions": { + "1.2.11-r0": 1509495062 + }, + "origin": "minizip", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libminizip.so.1=1.0.0" + ] + }, + "xfsprogs-doc": { + "versions": { + "4.14.0-r1": 1528279973 + }, + "origin": "xfsprogs" + }, + "iscsi-scst": { + "versions": { + "2.2.1-r2": 1509482428 + }, + "origin": "iscsi-scst", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:iscsi-scst-adm", + "cmd:iscsi-scstd" + ] + }, + "asterisk-pgsql": { + "versions": { + "15.6.1-r0": 1537795340, + "15.6.2-r0": 1568705002 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "ebtables": { + "versions": { + "2.0.10.4-r2": 1509491245 + }, + "origin": "ebtables", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libebt_802_3.so=0", + "so:libebt_among.so=0", + "so:libebt_arp.so=0", + "so:libebt_arpreply.so=0", + "so:libebt_ip.so=0", + "so:libebt_ip6.so=0", + "so:libebt_limit.so=0", + "so:libebt_log.so=0", + "so:libebt_mark.so=0", + "so:libebt_mark_m.so=0", + "so:libebt_nat.so=0", + "so:libebt_nflog.so=0", + "so:libebt_pkttype.so=0", + "so:libebt_redirect.so=0", + "so:libebt_standard.so=0", + "so:libebt_stp.so=0", + "so:libebt_ulog.so=0", + "so:libebt_vlan.so=0", + "so:libebtable_broute.so=0", + "so:libebtable_filter.so=0", + "so:libebtable_nat.so=0", + "so:libebtc.so=0", + "cmd:ebtables", + "cmd:ebtables-restore", + "cmd:ebtables-save" + ] + }, + "apr-dev": { + "versions": { + "1.6.3-r0": 1509476182 + }, + "origin": "apr", + "dependencies": [ + "apr", + "util-linux-dev", + "bash", + "pkgconfig" + ], + "provides": [ + "pc:apr-1=1.6.3", + "cmd:apr-1-config" + ] + }, + "py-django-haystack": { + "versions": { + "2.5.0-r0": 1509477805 + }, + "origin": "py-django-haystack", + "dependencies": [ + "py-django" + ] + }, + "netcf-dev": { + "versions": { + "0.2.8-r4": 1509491200 + }, + "origin": "netcf", + "dependencies": [ + "netcf-libs=0.2.8-r4", + "pkgconfig" + ], + "provides": [ + "pc:netcf=0.2.8" + ] + }, + "dahdi-linux-dev": { + "versions": { + "2.11.1-r0": 1509476067 + }, + "origin": "dahdi-linux" + }, + "cpufreqd-doc": { + "versions": { + "2.4.2-r4": 1509492586 + }, + "origin": "cpufreqd" + }, + "ldb-tools": { + "versions": { + "1.3.0-r1": 1534939012 + }, + "origin": "ldb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libldb.so.1", + "so:libpopt.so.0", + "so:libtalloc.so.2" + ], + "provides": [ + "so:libldb-cmdline.so=0", + "cmd:ldbadd", + "cmd:ldbdel", + "cmd:ldbedit", + "cmd:ldbmodify", + "cmd:ldbrename", + "cmd:ldbsearch" + ] + }, + "jfsutils-doc": { + "versions": { + "1.1.15-r1": 1509482133 + }, + "origin": "jfsutils" + }, + "dbus-dev": { + "versions": { + "1.10.24-r0": 1509465101, + "1.10.28-r0": 1560765707 + }, + "origin": "dbus", + "dependencies": [ + "util-linux-dev", + "dbus-libs=1.10.28-r0", + "pkgconfig" + ], + "provides": [ + "pc:dbus-1=1.10.28" + ] + }, + "libcap": { + "versions": { + "2.25-r1": 1509459572 + }, + "origin": "libcap", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcap.so.2=2.25", + "cmd:capsh", + "cmd:getcap", + "cmd:getpcaps", + "cmd:setcap" + ] + }, + "perl-test-nowarnings-doc": { + "versions": { + "1.04-r1": 1509468472 + }, + "origin": "perl-test-nowarnings" + }, + "pcre-doc": { + "versions": { + "8.41-r1": 1509464277 + }, + "origin": "pcre" + }, + "vblade-doc": { + "versions": { + "23-r0": 1509493237 + }, + "origin": "vblade" + }, + "lua5.3-dbi-sqlite3": { + "versions": { + "0.6-r1": 1511483403 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "openldap-overlay-syncprov": { + "versions": { + "2.4.45-r3": 1510258135, + "2.4.46-r0": 1565073941, + "2.4.48-r0": 1566900518 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "gpgme-doc": { + "versions": { + "1.9.0-r1": 1510588163 + }, + "origin": "gpgme" + }, + "libao-dev": { + "versions": { + "1.2.0-r2": 1543926072 + }, + "origin": "libao", + "dependencies": [ + "libao=1.2.0-r2", + "pkgconfig" + ], + "provides": [ + "pc:ao=1.2.0" + ] + }, + "xf86-video-nouveau": { + "versions": { + "1.0.15-r0": 1510074882 + }, + "origin": "xf86-video-nouveau", + "dependencies": [ + "mesa-dri-nouveau", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_nouveau.so.2", + "so:libudev.so.1" + ] + }, + "kamailio-utils": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libsrdb1.so.1", + "so:libxml2.so.2" + ] + }, + "libspf2-tools": { + "versions": { + "1.2.10-r2": 1509491332 + }, + "origin": "libspf2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libspf2.so.2" + ], + "provides": [ + "cmd:spfd", + "cmd:spfquery" + ] + }, + "resourceproto-doc": { + "versions": { + "1.2.0-r2": 1509468557 + }, + "origin": "resourceproto" + }, + "kamailio-outbound": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "xscreensaver-extras": { + "versions": { + "5.36-r0": 1510074354 + }, + "origin": "xscreensaver", + "dependencies": [ + "bc", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXmu.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgdk_pixbuf_xlib-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libjpeg.so.8" + ] + }, + "email-doc": { + "versions": { + "3.1.4-r4": 1510260519 + }, + "origin": "email" + }, + "live-media": { + "versions": { + "2017.10.28-r0": 1510046892 + }, + "origin": "live-media", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libBasicUsageEnvironment.so.1=1.0.0", + "so:libUsageEnvironment.so.3=3.1.0", + "so:libgroupsock.so.8=8.1.1", + "so:libliveMedia.so.61=61.0.0" + ] + }, + "netcf": { + "versions": { + "0.2.8-r4": 1509491202 + }, + "origin": "netcf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetcf.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:ncftool" + ] + }, + "libsodium-dev": { + "versions": { + "1.0.15-r0": 1509474812 + }, + "origin": "libsodium", + "dependencies": [ + "libsodium=1.0.15-r0", + "pkgconfig" + ], + "provides": [ + "pc:libsodium=1.0.15" + ] + }, + "imap": { + "versions": { + "2007f-r7": 1510258915 + }, + "origin": "imap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "cmd:imapd", + "cmd:ipop2d", + "cmd:ipop3d" + ] + }, + "gnats": { + "versions": { + "4.2.0-r3": 1509496000 + }, + "origin": "gnats", + "dependencies": [ + "postfix", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:edit-pr", + "cmd:getclose", + "cmd:install-sid", + "cmd:query-pr", + "cmd:send-pr" + ] + }, + "open-isns-dev": { + "versions": { + "0.97-r2": 1510259303 + }, + "origin": "open-isns", + "dependencies": [ + "open-isns-lib=0.97-r2" + ] + }, + "kamailio-presence": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libsrdb1.so.1", + "so:libsrutils.so.1", + "so:libxml2.so.2" + ] + }, + "mkfontscale-doc": { + "versions": { + "1.1.2-r1": 1509469865 + }, + "origin": "mkfontscale" + }, + "acf-lib-lua5.1": { + "versions": { + "0.10.1-r0": 1510068294 + }, + "origin": "acf-lib" + }, + "freeswitch-dev": { + "versions": { + "1.6.19-r0": 1510585356 + }, + "origin": "freeswitch", + "dependencies": [ + "freeswitch-freetdm=1.6.19-r0", + "freeswitch=1.6.19-r0", + "pkgconfig" + ], + "provides": [ + "pc:freeswitch=1.6.19", + "pc:freetdm=0.1" + ] + }, + "perl-datetime-format-w3cdtf": { + "versions": { + "0.07-r0": 1510859374 + }, + "origin": "perl-datetime-format-w3cdtf", + "dependencies": [ + "perl", + "perl-datetime" + ] + }, + "xfce4-settings": { + "versions": { + "4.12.1-r0": 1510303246 + }, + "origin": "xfce4-settings", + "dependencies": [ + "so:libX11.so.6", + "so:libXcursor.so.1", + "so:libXi.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libexo-1.so.0", + "so:libfontconfig.so.1", + "so:libgarcon-1.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libnotify.so.4", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libupower-glib.so.3", + "so:libxfce4kbd-private-2.so.0", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2", + "so:libxklavier.so.16" + ], + "provides": [ + "cmd:xfce4-accessibility-settings", + "cmd:xfce4-appearance-settings", + "cmd:xfce4-display-settings", + "cmd:xfce4-keyboard-settings", + "cmd:xfce4-mime-settings", + "cmd:xfce4-mouse-settings", + "cmd:xfce4-settings-editor", + "cmd:xfce4-settings-manager", + "cmd:xfsettingsd" + ] + }, + "lua5.3-json4": { + "versions": { + "1.0.0-r2": 1509468370 + }, + "origin": "lua-json4" + }, + "lua5.3-cjson": { + "versions": { + "2.1.0-r7": 1509462474 + }, + "origin": "lua-cjson", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "haserl-lua5.1": { + "versions": { + "0.9.35-r1": 1509468298 + }, + "origin": "haserl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ], + "provides": [ + "cmd:haserl-lua5.1" + ] + }, + "aumix": { + "versions": { + "2.9.1-r6": 1509476722 + }, + "origin": "aumix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:aumix", + "cmd:mute", + "cmd:xaumix" + ] + }, + "py2-phonenumbers": { + "versions": { + "8.8.6-r0": 1511018972 + }, + "origin": "py-phonenumbers", + "dependencies": [ + "python2" + ] + }, + "py3-ply": { + "versions": { + "3.11-r0": 1556865249 + }, + "origin": "py3-ply", + "dependencies": [ + "python3" + ] + }, + "userspace-rcu-doc": { + "versions": { + "0.10.0-r0": 1509479772 + }, + "origin": "userspace-rcu" + }, + "yajl-dev": { + "versions": { + "2.1.0-r0": 1509475309 + }, + "origin": "yajl", + "dependencies": [ + "pkgconfig", + "yajl=2.1.0-r0" + ], + "provides": [ + "pc:yajl=2.1.0" + ] + }, + "xfwm4-themes": { + "versions": { + "4.10.0-r0": 1510074462 + }, + "origin": "xfwm4-themes", + "dependencies": [ + "xfwm4" + ] + }, + "garcon": { + "versions": { + "0.6.1-r1": 1510068108 + }, + "origin": "garcon", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-3.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libxfce4ui-1.so.0", + "so:libxfce4ui-2.so.0", + "so:libxfce4util.so.7" + ], + "provides": [ + "so:libgarcon-1.so.0=0.0.0", + "so:libgarcon-gtk2-1.so.0=0.0.0", + "so:libgarcon-gtk3-1.so.0=0.0.0" + ] + }, + "libxfce4ui-doc": { + "versions": { + "4.12.1-r3": 1510067993 + }, + "origin": "libxfce4ui" + }, + "libice-dev": { + "versions": { + "1.0.9-r2": 1509465974 + }, + "origin": "libice", + "dependencies": [ + "libice=1.0.9-r2", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:ice=1.0.9" + ] + }, + "libraw1394-doc": { + "versions": { + "2.1.2-r0": 1509470073 + }, + "origin": "libraw1394" + }, + "inotify-tools-dev": { + "versions": { + "3.14-r2": 1509475672 + }, + "origin": "inotify-tools", + "dependencies": [ + "inotify-tools=3.14-r2" + ] + }, + "lua5.3-filesystem": { + "versions": { + "1.7.0.2-r0": 1511049451 + }, + "origin": "lua-filesystem", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "sipsak": { + "versions": { + "0.9.6-r9": 1510260059 + }, + "origin": "sipsak", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libcrypto.so.42" + ], + "provides": [ + "cmd:sipsak" + ] + }, + "nano-doc": { + "versions": { + "2.9.1-r0": 1512032032 + }, + "origin": "nano" + }, + "iftop-doc": { + "versions": { + "0.17-r7": 1509489487 + }, + "origin": "iftop" + }, + "pciutils-dev": { + "versions": { + "3.5.6-r0": 1511165900 + }, + "origin": "pciutils", + "dependencies": [ + "pciutils-libs=3.5.6-r0", + "pkgconfig" + ], + "provides": [ + "pc:libpci=3.5.6" + ] + }, + "wayland-libs-client": { + "versions": { + "1.14.0-r2": 1510066936 + }, + "origin": "wayland", + "dependencies": [ + "wayland-libs-client", + "wayland-libs-cursor", + "wayland-libs-server", + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6" + ], + "provides": [ + "so:libwayland-client.so.0=0.3.0" + ] + }, + "py-phonenumbers": { + "versions": { + "8.8.6-r0": 1511018982 + }, + "origin": "py-phonenumbers" + }, + "perl-module-refresh-doc": { + "versions": { + "0.17-r1": 1510564499 + }, + "origin": "perl-module-refresh" + }, + "xvidcore-dev": { + "versions": { + "1.3.4-r0": 1509480928 + }, + "origin": "xvidcore", + "dependencies": [ + "xvidcore=1.3.4-r0" + ] + }, + "iceauth": { + "versions": { + "1.0.7-r0": 1509482662 + }, + "origin": "iceauth", + "dependencies": [ + "so:libICE.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:iceauth" + ] + }, + "xcb-util-renderutil": { + "versions": { + "0.3.9-r1": 1509473920 + }, + "origin": "xcb-util-renderutil", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-render.so.0", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-render-util.so.0=0.0.0" + ] + }, + "libverto-libev": { + "versions": { + "0.3.0-r0": 1509469586 + }, + "origin": "libverto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libev.so.4", + "so:libverto.so.1" + ], + "provides": [ + "so:libverto-libev.so.1=1.0.0" + ] + }, + "mtu": { + "versions": { + "1.5-r1": 1509474487 + }, + "origin": "pingu", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mtu" + ] + }, + "perl-extutils-helpers": { + "versions": { + "0.026-r0": 1509474753 + }, + "origin": "perl-extutils-helpers" + }, + "socat": { + "versions": { + "1.7.3.2-r3": 1510260539 + }, + "origin": "socat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libreadline.so.7", + "so:libssl.so.44" + ], + "provides": [ + "cmd:filan", + "cmd:procan", + "cmd:socat" + ] + }, + "nagios-plugins-dummy": { + "versions": { + "2.2.1-r3": 1510288495 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-io-doc": { + "versions": { + "1.25-r4": 1509483995 + }, + "origin": "perl-io" + }, + "alpine-base": { + "versions": { + "3.7.3-r0": 1551903503 + }, + "origin": "alpine-base", + "dependencies": [ + "alpine-baselayout", + "alpine-conf", + "apk-tools", + "busybox", + "busybox-suid", + "busybox-initscripts", + "openrc", + "libc-utils", + "alpine-keys" + ] + }, + "gigolo-doc": { + "versions": { + "0.4.2-r0": 1510076413 + }, + "origin": "gigolo" + }, + "aumix-doc": { + "versions": { + "2.9.1-r6": 1509476721 + }, + "origin": "aumix" + }, + "libjpeg-turbo-dev": { + "versions": { + "1.5.3-r2": 1537879742, + "1.5.3-r3": 1563792633 + }, + "origin": "libjpeg-turbo", + "dependencies": [ + "libjpeg-turbo=1.5.3-r3", + "pkgconfig" + ], + "provides": [ + "pc:libjpeg=1.5.3", + "pc:libturbojpeg=1.5.3" + ] + }, + "nettle": { + "versions": { + "3.3-r0": 1509465116 + }, + "origin": "nettle", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10" + ], + "provides": [ + "so:libhogweed.so.4=4.3", + "so:libnettle.so.6=6.3" + ] + }, + "drill": { + "versions": { + "1.6.17-r6": 1510258937 + }, + "origin": "ldns", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libldns.so.1" + ], + "provides": [ + "cmd:drill" + ] + }, + "krb5-libs": { + "versions": { + "1.15.4-r0": 1548491443 + }, + "origin": "krb5", + "dependencies": [ + "krb5-conf", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libcrypto.so.42", + "so:libkeyutils.so.1", + "so:libssl.so.44", + "so:libverto.so.1" + ], + "provides": [ + "so:libgssapi_krb5.so.2=2.2", + "so:libgssrpc.so.4=4.2", + "so:libk5crypto.so.3=3.1", + "so:libkadm5clnt_mit.so.11=11.0", + "so:libkadm5srv_mit.so.11=11.0", + "so:libkdb5.so.8=8.0", + "so:libkrad.so.0=0.0", + "so:libkrb5.so.3=3.3", + "so:libkrb5support.so.0=0.1" + ] + }, + "perl-module-build-doc": { + "versions": { + "0.4224-r0": 1509459456 + }, + "origin": "perl-module-build" + }, + "python3-wininst": { + "versions": { + "3.6.8-r0": 1555928809, + "3.6.9-r0": 1571233995, + "3.6.9-r1": 1571314639 + }, + "origin": "python3" + }, + "libshout-dev": { + "versions": { + "2.4.1-r1": 1510068381 + }, + "origin": "libshout", + "dependencies": [ + "speex-dev", + "libshout=2.4.1-r1", + "pc:ogg", + "pc:speex", + "pc:theora", + "pc:vorbis", + "pkgconfig" + ], + "provides": [ + "pc:shout=2.4.1" + ] + }, + "perl-locale-maketext-fuzzy-doc": { + "versions": { + "0.11-r1": 1510848491 + }, + "origin": "perl-locale-maketext-fuzzy" + }, + "lua5.1-posixtz": { + "versions": { + "0.5-r1": 1509479783 + }, + "origin": "lua-posixtz", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-mail-spamassassin": { + "versions": { + "3.4.1-r8": 1509517413, + "3.4.1-r2": 1509517006 + }, + "origin": "perl-mail-spamassassin", + "dependencies": [ + "perl-mail-dkim", + "perl-netaddr-ip", + "perl-digest-sha1", + "perl-html-parser", + "perl-net-dns", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sa-awl", + "cmd:sa-check_spamd", + "cmd:sa-compile", + "cmd:sa-learn", + "cmd:sa-update", + "cmd:spamassassin", + "cmd:spamc", + "cmd:spamd" + ] + }, + "boost-wave": { + "versions": { + "1.62.0-r5": 1509465882 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_system-mt.so.1.62.0", + "so:libboost_thread-mt.so.1.62.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_wave-mt.so.1.62.0=1.62.0" + ] + }, + "mdadm-misc": { + "versions": { + "4.0-r0": 1509476489 + }, + "origin": "mdadm", + "dependencies": [ + "mdadm", + "bash" + ], + "provides": [ + "cmd:handle-mdadm-events", + "cmd:mdcheck" + ] + }, + "cgit": { + "versions": { + "1.1-r3": 1533395015 + }, + "origin": "cgit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:liblua-5.3.so.0", + "so:libz.so.1" + ] + }, + "py2-singledispatch": { + "versions": { + "3.4.0.3-r1": 1509552750 + }, + "origin": "py2-singledispatch", + "dependencies": [ + "python2" + ], + "provides": [ + "py-singledispatch" + ] + }, + "openjade-libs": { + "versions": { + "1.3.2-r5": 1509488726 + }, + "origin": "openjade", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libogrove.so.0=0.0.1", + "so:libospgrove.so.0=0.0.1", + "so:libostyle.so.0=0.0.1" + ] + }, + "nss-dev": { + "versions": { + "3.34.1-r0": 1511890215 + }, + "origin": "nss", + "dependencies": [ + "nss", + "nspr-dev", + "pc:nspr>=3.34.1", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libnspr4.so", + "so:libnssutil3.so", + "so:libplc4.so", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgtest1.so.34=34", + "so:libnsssysinit.so=34", + "pc:mozilla-nss=3.34.1", + "pc:nss-softokn=3.34.1", + "pc:nss-util=3.34.1", + "pc:nss=3.34.1", + "cmd:nss-config" + ] + }, + "perl-sub-exporter-progressive": { + "versions": { + "0.001013-r0": 1509473996 + }, + "origin": "perl-sub-exporter-progressive" + }, + "libxdg-basedir-dev": { + "versions": { + "1.2.0-r0": 1509494298 + }, + "origin": "libxdg-basedir", + "dependencies": [ + "libxdg-basedir=1.2.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libxdg-basedir=1.2.0" + ] + }, + "sdl_mixer": { + "versions": { + "1.2.12-r1": 1510075316 + }, + "origin": "sdl_mixer", + "dependencies": [ + "so:libSDL-1.2.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL_mixer-1.2.so.0=0.12.0" + ] + }, + "speex-tools": { + "versions": { + "1.2.0-r0": 1509473247 + }, + "origin": "speex", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libspeex.so.1", + "so:libspeexdsp.so.1" + ], + "provides": [ + "cmd:speexdec", + "cmd:speexenc" + ] + }, + "ruby-bigdecimal": { + "versions": { + "2.4.6-r0": 1557166822 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ] + }, + "libx11": { + "versions": { + "1.6.6-r0": 1538999955 + }, + "origin": "libx11", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libX11-xcb.so.1=1.0.0", + "so:libX11.so.6=6.3.0" + ] + }, + "perl-gdgraph-doc": { + "versions": { + "1.54-r0": 1509496362 + }, + "origin": "perl-gdgraph" + }, + "avahi-dev": { + "versions": { + "0.6.32-r4": 1509465445, + "0.6.32-r5": 1563345619 + }, + "origin": "avahi", + "dependencies": [ + "gdbm-dev", + "avahi-compat-howl=0.6.32-r5", + "avahi-compat-libdns_sd=0.6.32-r5", + "avahi-glib=0.6.32-r5", + "avahi-libs=0.6.32-r5", + "avahi=0.6.32-r5", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:avahi-client=0.6.32", + "pc:avahi-compat-howl=0.9.8", + "pc:avahi-compat-libdns_sd=0.6.32", + "pc:avahi-core=0.6.32", + "pc:avahi-glib=0.6.32", + "pc:avahi-gobject=0.6.32", + "pc:howl=0.9.8", + "pc:libdns_sd=0.6.32" + ] + }, + "gnome-vfs-lang": { + "versions": { + "2.24.4-r6": 1510931439 + }, + "origin": "gnome-vfs", + "dependencies": [ + "gnome-mime-data" + ] + }, + "libmatroska": { + "versions": { + "1.4.8-r0": 1509479839 + }, + "origin": "libmatroska", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libebml.so.4", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libmatroska.so.6=6.0.0" + ] + }, + "collectd-lua": { + "versions": { + "5.7.2-r0": 1510069653 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ] + }, + "gsm-tools": { + "versions": { + "1.0.16-r0": 1509473212 + }, + "origin": "gsm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgsm.so.1" + ], + "provides": [ + "cmd:tcat", + "cmd:toast", + "cmd:untoast" + ] + }, + "darkice": { + "versions": { + "1.3-r0": 1509470067 + }, + "origin": "darkice", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libfaac.so.0", + "so:libgcc_s.so.1", + "so:libmp3lame.so.0", + "so:libogg.so.0", + "so:libstdc++.so.6", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2" + ], + "provides": [ + "cmd:darkice" + ] + }, + "sysfsutils": { + "versions": { + "2.1.0-r8": 1509475366 + }, + "origin": "sysfsutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsysfs.so.2=2.0.1", + "cmd:dlist_test", + "cmd:get_device", + "cmd:get_driver", + "cmd:get_module", + "cmd:systool" + ] + }, + "librevenge-dev": { + "versions": { + "0.0.4-r0": 1509495165 + }, + "origin": "librevenge", + "dependencies": [ + "boost-dev", + "zlib-dev", + "cppunit-dev", + "librevenge=0.0.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:librevenge-0.0=0.0.4", + "pc:librevenge-generators-0.0=0.0.4", + "pc:librevenge-stream-0.0=0.0.4" + ] + }, + "freeradius-rest": { + "versions": { + "3.0.15-r4": 1556202797, + "3.0.15-r5": 1566310605 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.15-r5", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libjson-c.so.2" + ], + "provides": [ + "so:rlm_rest.so=0" + ] + }, + "postfix-policyd-spf-perl": { + "versions": { + "2.007-r2": 1509494151 + }, + "origin": "postfix-policyd-spf-perl", + "dependencies": [ + "perl", + "perl-mail-spf", + "perl-netaddr-ip" + ], + "provides": [ + "cmd:postfix-policyd-spf-perl" + ] + }, + "libmicrohttpd": { + "versions": { + "0.9.57-r0": 1512032047 + }, + "origin": "libmicrohttpd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30" + ], + "provides": [ + "so:libmicrohttpd.so.12=12.45.0" + ] + }, + "perl-net-snmp": { + "versions": { + "6.0.1-r2": 1509471907 + }, + "origin": "perl-net-snmp", + "dependencies": [ + "perl-crypt-des", + "perl-crypt-rijndael", + "perl-digest-sha1" + ], + "provides": [ + "cmd:snmpkey" + ] + }, + "boost-random": { + "versions": { + "1.62.0-r5": 1509465878 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_system-mt.so.1.62.0", + "so:libboost_system.so.1.62.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_random-mt.so.1.62.0=1.62.0", + "so:libboost_random.so.1.62.0=1.62.0" + ] + }, + "libmnl-dev": { + "versions": { + "1.0.4-r0": 1509469217 + }, + "origin": "libmnl", + "dependencies": [ + "linux-headers", + "libmnl=1.0.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmnl=1.0.4" + ] + }, + "xgnokii": { + "versions": { + "0.6.31-r6": 1510069847 + }, + "origin": "gnokii", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgnokii.so.7", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:xgnokii" + ] + }, + "ninja-doc": { + "versions": { + "1.8.2-r0": 1509475826 + }, + "origin": "ninja" + }, + "rtpproxy": { + "versions": { + "2.0.0-r4": 1509492569 + }, + "origin": "rtpproxy", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:rtpproxy" + ] + }, + "xf86-input-keyboard": { + "versions": { + "1.9.0-r0": 1510074947 + }, + "origin": "xf86-input-keyboard", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "xrefresh-doc": { + "versions": { + "1.0.5-r0": 1509494698 + }, + "origin": "xrefresh" + }, + "cups": { + "versions": { + "2.2.10-r0": 1549287810, + "2.2.12-r0": 1566207598 + }, + "origin": "cups", + "dependencies": [ + "cups-client", + "poppler-utils", + "libressl", + "dbus", + "/bin/sh", + "cups-client=2.2.12-r0", + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2", + "so:libcupsimage.so.2", + "so:libdbus-1.so.3", + "so:libgcc_s.so.1", + "so:libpaper.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:cupsd", + "cmd:cupsfilter" + ] + }, + "distcc-pump": { + "versions": { + "3.1-r12": 1509493044 + }, + "origin": "distcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:pump" + ] + }, + "perl-digest-md5-doc": { + "versions": { + "2.55-r2": 1509482651 + }, + "origin": "perl-digest-md5" + }, + "font-bh-type1": { + "versions": { + "1.0.3-r0": 1509494001 + }, + "origin": "font-bh-type1", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "weechat-ruby": { + "versions": { + "1.9.1-r1": 1509530223 + }, + "origin": "weechat", + "dependencies": [ + "weechat", + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.4" + ] + }, + "goffice": { + "versions": { + "0.10.36-r0": 1511455403 + }, + "origin": "goffice", + "dependencies": [ + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:librsvg-2.so.2", + "so:libxml2.so.2", + "so:libxslt.so.1" + ], + "provides": [ + "so:libgoffice-0.10.so.10=10.0.36" + ] + }, + "py2-mako": { + "versions": { + "1.0.7-r0": 1509468232 + }, + "origin": "py-mako", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:mako-render" + ] + }, + "libssh2-doc": { + "versions": { + "1.8.2-r0": 1553682025, + "1.9.0-r0": 1571235284, + "1.9.0-r1": 1571996777 + }, + "origin": "libssh2" + }, + "py-zope-interface": { + "versions": { + "4.3.2-r1": 1509493285 + }, + "origin": "py-zope-interface" + }, + "libvdpau-doc": { + "versions": { + "1.1.1-r1": 1509467160 + }, + "origin": "libvdpau" + }, + "tinydns": { + "versions": { + "1.05-r47": 1509488767 + }, + "origin": "djbdns", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tinydns", + "cmd:tinydns-conf", + "cmd:tinydns-data", + "cmd:tinydns-edit", + "cmd:tinydns-get" + ] + }, + "perl-socket-doc": { + "versions": { + "2.024-r2": 1509484006 + }, + "origin": "perl-socket" + }, + "nghttp2": { + "versions": { + "1.28.0-r0": 1511922922, + "1.39.2-r0": 1568186892 + }, + "origin": "nghttp2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libcrypto.so.42", + "so:libev.so.4", + "so:libgcc_s.so.1", + "so:libnghttp2.so.14", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:h2load", + "cmd:nghttp", + "cmd:nghttpd", + "cmd:nghttpx" + ] + }, + "sdl_image-dev": { + "versions": { + "1.2.12-r3": 1510067913, + "1.2.12-r4": 1574265384 + }, + "origin": "sdl_image", + "dependencies": [ + "pc:sdl>=1.2.10", + "pkgconfig", + "sdl_image=1.2.12-r4" + ], + "provides": [ + "pc:SDL_image=1.2.12" + ] + }, + "dwm": { + "versions": { + "6.1-r2": 1509494032 + }, + "origin": "dwm", + "dependencies": [ + "dmenu", + "st", + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1" + ], + "provides": [ + "cmd:dwm" + ] + }, + "xf86dgaproto": { + "versions": { + "2.1-r4": 1509473781 + }, + "origin": "xf86dgaproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xf86dgaproto=2.1" + ] + }, + "device-mapper-event-libs": { + "versions": { + "2.02.175-r0": 1509462402 + }, + "origin": "lvm2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02" + ], + "provides": [ + "so:libdevmapper-event.so.1.02=1.02" + ] + }, + "recordmydesktop": { + "versions": { + "0.3.8.1-r2": 1510075370 + }, + "origin": "recordmydesktop", + "dependencies": [ + "so:libX11.so.6", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libtheora.so.0", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:recordmydesktop" + ] + }, + "mosquitto-doc": { + "versions": { + "1.4.15-r0": 1520176488, + "1.4.15-r2": 1563346323 + }, + "origin": "mosquitto" + }, + "cciss_vol_status": { + "versions": { + "1.12-r0": 1509493252 + }, + "origin": "cciss_vol_status", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cciss_vol_status" + ] + }, + "makekit": { + "versions": { + "0.2-r0": 1509494663 + }, + "origin": "makekit", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:makekit", + "cmd:mkdash" + ] + }, + "libmpeg2-dev": { + "versions": { + "0.5.1-r7": 1509473556 + }, + "origin": "libmpeg2", + "dependencies": [ + "libmpeg2=0.5.1-r7", + "pkgconfig" + ], + "provides": [ + "pc:libmpeg2=0.5.1", + "pc:libmpeg2convert=0.5.1" + ] + }, + "rsnapshot": { + "versions": { + "1.4.2-r0": 1509480276 + }, + "origin": "rsnapshot", + "dependencies": [ + "perl", + "rsync", + "openssh-client" + ], + "provides": [ + "cmd:rsnapshot", + "cmd:rsnapshot-diff" + ] + }, + "perl-params-classify": { + "versions": { + "0.015-r0": 1509470455 + }, + "origin": "perl-params-classify", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libedit-dev": { + "versions": { + "20170329.3.1-r3": 1509461724 + }, + "origin": "libedit", + "dependencies": [ + "ncurses-dev", + "libedit=20170329.3.1-r3", + "pkgconfig" + ], + "provides": [ + "pc:libedit=3.1" + ] + }, + "file": { + "versions": { + "5.32-r0": 1509461605, + "5.32-r1": 1563340161, + "5.32-r2": 1572348414 + }, + "origin": "file", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmagic.so.1" + ], + "provides": [ + "cmd:file" + ] + }, + "xfce4-screenshooter-lang": { + "versions": { + "1.8.2-r1": 1510074733 + }, + "origin": "xfce4-screenshooter" + }, + "libxxf86misc": { + "versions": { + "1.0.3-r1": 1509473661 + }, + "origin": "libxxf86misc", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXxf86misc.so.1=1.1.0" + ] + }, + "cyrus-sasl-doc": { + "versions": { + "2.1.26-r11": 1510258044 + }, + "origin": "cyrus-sasl" + }, + "libetpan-dev": { + "versions": { + "1.8-r2": 1509721604 + }, + "origin": "libetpan", + "dependencies": [ + "cyrus-sasl-dev", + "db-dev", + "libetpan=1.8-r2" + ], + "provides": [ + "cmd:libetpan-config" + ] + }, + "radvd-doc": { + "versions": { + "2.16-r0": 1509493025 + }, + "origin": "radvd" + }, + "perl-list-moreutils": { + "versions": { + "0.419-r1": 1509474021 + }, + "origin": "perl-list-moreutils", + "dependencies": [ + "perl-exporter-tiny" + ] + }, + "perl-html-tagset-doc": { + "versions": { + "3.20-r1": 1509464386 + }, + "origin": "perl-html-tagset" + }, + "libdrm-dev": { + "versions": { + "2.4.88-r0": 1510649882 + }, + "origin": "libdrm", + "dependencies": [ + "linux-headers", + "libdrm=2.4.88-r0", + "pkgconfig" + ], + "provides": [ + "pc:libdrm=2.4.88", + "pc:libdrm_amdgpu=2.4.88", + "pc:libdrm_freedreno=2.4.88", + "pc:libdrm_intel=2.4.88", + "pc:libdrm_nouveau=2.4.88", + "pc:libdrm_radeon=2.4.88", + "pc:libkms=1.0.0" + ] + }, + "perl-test-tcp-doc": { + "versions": { + "2.14-r0": 1509481784 + }, + "origin": "perl-test-tcp" + }, + "nginx-mod-http-geoip": { + "versions": { + "1.12.2-r4": 1542814446 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.2-doc": { + "versions": { + "5.2.4-r4": 1509459694 + }, + "origin": "lua5.2" + }, + "charybdis-doc": { + "versions": { + "3.5.5-r3": 1510260651 + }, + "origin": "charybdis" + }, + "perl-symbol-global-name": { + "versions": { + "0.05-r1": 1510564552 + }, + "origin": "perl-symbol-global-name" + }, + "perl-test-pod-doc": { + "versions": { + "1.51-r1": 1509461791 + }, + "origin": "perl-test-pod" + }, + "tinyxml-dev": { + "versions": { + "2.6.2-r1": 1509496004 + }, + "origin": "tinyxml", + "dependencies": [ + "tinyxml=2.6.2-r1" + ] + }, + "openvswitch-dev": { + "versions": { + "2.8.1-r2": 1512030506 + }, + "origin": "openvswitch", + "dependencies": [ + "libressl-dev", + "pkgconfig" + ], + "provides": [ + "pc:libofproto=2.8.1", + "pc:libopenvswitch=2.8.1", + "pc:libovsdb=2.8.1", + "pc:libsflow=2.8.1" + ] + }, + "asciidoc": { + "versions": { + "8.6.10-r0": 1509461782 + }, + "origin": "asciidoc", + "dependencies": [ + "python2", + "libxml2-utils", + "docbook-xsl" + ], + "provides": [ + "cmd:a2x", + "cmd:a2x.py", + "cmd:asciidoc", + "cmd:asciidoc.py" + ] + }, + "swish-e-doc": { + "versions": { + "2.4.7-r8": 1509483302 + }, + "origin": "swish-e" + }, + "perl-gdtextutil": { + "versions": { + "0.86-r0": 1509495191 + }, + "origin": "perl-gdtextutil", + "dependencies": [ + "perl-gd" + ] + }, + "thunar-volman-lang": { + "versions": { + "0.8.1-r2": 1510075201 + }, + "origin": "thunar-volman" + }, + "dansguardian": { + "versions": { + "2.12.0.3-r4": 1509494015 + }, + "origin": "dansguardian", + "dependencies": [ + "logrotate", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpcreposix.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:dansguardian" + ] + }, + "openldap-passwd-pbkdf2": { + "versions": { + "2.4.45-r3": 1510258132, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900515 + }, + "origin": "openldap", + "dependencies": [ + "openldap", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "libxcb-dev": { + "versions": { + "1.12-r1": 1509461874 + }, + "origin": "libxcb", + "dependencies": [ + "libxau-dev", + "xcb-proto", + "libxcb=1.12-r1", + "pc:pthread-stubs", + "pc:xau>=0.99.2", + "pc:xdmcp", + "pkgconfig" + ], + "provides": [ + "pc:xcb-composite=1.12", + "pc:xcb-damage=1.12", + "pc:xcb-dpms=1.12", + "pc:xcb-dri2=1.12", + "pc:xcb-dri3=1.12", + "pc:xcb-glx=1.12", + "pc:xcb-present=1.12", + "pc:xcb-randr=1.12", + "pc:xcb-record=1.12", + "pc:xcb-render=1.12", + "pc:xcb-res=1.12", + "pc:xcb-screensaver=1.12", + "pc:xcb-shape=1.12", + "pc:xcb-shm=1.12", + "pc:xcb-sync=1.12", + "pc:xcb-xf86dri=1.12", + "pc:xcb-xfixes=1.12", + "pc:xcb-xinerama=1.12", + "pc:xcb-xinput=1.12", + "pc:xcb-xkb=1.12", + "pc:xcb-xtest=1.12", + "pc:xcb-xv=1.12", + "pc:xcb-xvmc=1.12", + "pc:xcb=1.12" + ] + }, + "xclip": { + "versions": { + "0.13-r0": 1509495879 + }, + "origin": "xclip", + "dependencies": [ + "so:libX11.so.6", + "so:libXmu.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xclip", + "cmd:xclip-copyfile", + "cmd:xclip-cutfile", + "cmd:xclip-pastefile" + ] + }, + "qt-x11": { + "versions": { + "4.8.7-r8": 1510287209 + }, + "origin": "qt", + "dependencies": [ + "hicolor-icon-theme", + "so:libGL.so.1", + "so:libICE.so.6", + "so:libQtCore.so.4", + "so:libQtDBus.so.4", + "so:libQtNetwork.so.4", + "so:libQtScript.so.4", + "so:libQtSql.so.4", + "so:libQtXml.so.4", + "so:libQtXmlPatterns.so.4", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libjpeg.so.8", + "so:libmng.so.2", + "so:libpng16.so.16", + "so:libstdc++.so.6", + "so:libtiff.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libQt3Support.so.4=4.8.7", + "so:libQtCLucene.so.4=4.8.7", + "so:libQtDeclarative.so.4=4.8.7", + "so:libQtDesigner.so.4=4.8.7", + "so:libQtDesignerComponents.so.4=4.8.7", + "so:libQtGui.so.4=4.8.7", + "so:libQtHelp.so.4=4.8.7", + "so:libQtMultimedia.so.4=4.8.7", + "so:libQtOpenGL.so.4=4.8.7", + "so:libQtScriptTools.so.4=4.8.7", + "so:libQtSvg.so.4=4.8.7", + "cmd:qdbusviewer", + "cmd:qmlplugindump", + "cmd:qmlviewer" + ] + }, + "qpdf-doc": { + "versions": { + "7.0.0-r0": 1509482355 + }, + "origin": "qpdf" + }, + "gtk-engines-lang": { + "versions": { + "2.21.0-r2": 1510071720 + }, + "origin": "gtk-engines", + "dependencies": [ + "gtk-engines-clearlooks", + "gtk-engines-crux", + "gtk-engines-industrial", + "gtk-engines-mist", + "gtk-engines-redmond", + "gtk-engines-thinice" + ] + }, + "enca": { + "versions": { + "1.19-r1": 1509480335 + }, + "origin": "enca", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libenca.so.0=0.5.1", + "cmd:enca", + "cmd:enconv" + ] + }, + "py-flup": { + "versions": { + "1.0.2-r0": 1509489931 + }, + "origin": "py-flup", + "dependencies": [ + "python2" + ] + }, + "xfce4-skel": { + "versions": { + "4.12.0-r1": 1510920458 + }, + "origin": "xfce4", + "dependencies": [ + "exo>=0.10.3", + "garcon>=0.4.0", + "gtk-xfce-engine>=3.2.0", + "libxfce4ui>=4.12.0", + "libxfce4util>=4.12.0", + "thunar>=1.6.6", + "ttf-dejavu", + "xfce4-appfinder>=4.12.0", + "xfce4-mixer", + "xfce4-panel>=4.12.0", + "xfce4-power-manager>=1.4.3", + "xfce4-session>=4.12.0", + "xfce4-settings>=4.12.0", + "xfce4-terminal", + "xfconf>=4.12.0", + "xfdesktop>=4.12.0", + "xfwm4>=4.12.0" + ] + }, + "ninja": { + "versions": { + "1.8.2-r0": 1509475826 + }, + "origin": "ninja", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:ninja" + ] + }, + "openldap-back-shell": { + "versions": { + "2.4.45-r3": 1510258133, + "2.4.46-r0": 1565073939, + "2.4.48-r0": 1566900516 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "sfcapd": { + "versions": { + "1.6.15-r0": 1509494471, + "1.6.15-r1": 1574265426 + }, + "origin": "nfdump", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnfdump-1.6.15.so" + ], + "provides": [ + "cmd:sfcapd" + ] + }, + "py-pygments-doc": { + "versions": { + "2.2.0-r0": 1509493371 + }, + "origin": "py-pygments" + }, + "perl-net-libidn-doc": { + "versions": { + "0.12-r3": 1509468971 + }, + "origin": "perl-net-libidn" + }, + "jwm-lang": { + "versions": { + "2.3.7-r0": 1510075431 + }, + "origin": "jwm" + }, + "py-fuse": { + "versions": { + "0.2.1-r1": 1509493774 + }, + "origin": "py-fuse", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libfuse.so.2", + "so:libpython2.7.so.1.0" + ] + }, + "perl-inline-c-doc": { + "versions": { + "0.76-r0": 1509480892 + }, + "origin": "perl-inline-c" + }, + "openvpn-auth-ldap": { + "versions": { + "2.0.3-r6": 1509492316 + }, + "origin": "openvpn-auth-ldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libobjc.so.4" + ], + "provides": [ + "so:openvpn-auth-ldap.so=0" + ] + }, + "libcddb": { + "versions": { + "1.3.2-r2": 1509473612 + }, + "origin": "libcddb", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcddb.so.2=2.2.3", + "cmd:cddb_query" + ] + }, + "xfce4-terminal": { + "versions": { + "0.6.3-r1": 1510074621 + }, + "origin": "xfce4-terminal", + "dependencies": [ + "startup-notification", + "hicolor-icon-theme", + "so:libX11.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libvte.so.9", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7" + ], + "provides": [ + "cmd:xfce4-terminal" + ] + }, + "uvncrepeater": { + "versions": { + "014-r6": 1509491342 + }, + "origin": "uvncrepeater", + "dependencies": [ + "openrc>=0.6", + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:repeater" + ] + }, + "openldap-overlay-proxycache": { + "versions": { + "2.4.45-r3": 1510258135, + "2.4.46-r0": 1565073940, + "2.4.48-r0": 1566900517 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "mesa-gles": { + "versions": { + "17.2.4-r1": 1510741947 + }, + "origin": "mesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglapi.so.0" + ], + "provides": [ + "so:libGLESv1_CM.so.1=1.1.0", + "so:libGLESv2.so.2=2.0.0" + ] + }, + "perl-email-address-list-doc": { + "versions": { + "0.05-r2": 1510564503 + }, + "origin": "perl-email-address-list" + }, + "lua-mosquitto": { + "versions": { + "0.2-r1": 1509496428 + }, + "origin": "lua-mosquitto" + }, + "aspell-doc": { + "versions": { + "0.60.6.1-r12": 1509472868, + "0.60.6.1-r13": 1572541141 + }, + "origin": "aspell" + }, + "acf-skins": { + "versions": { + "0.6.0-r1": 1510068296 + }, + "origin": "acf-skins" + }, + "xfce4-whiskermenu-plugin": { + "versions": { + "1.7.3-r0": 1510073069 + }, + "origin": "xfce4-whiskermenu-plugin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libexo-1.so.0", + "so:libgarcon-1.so.0", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libstdc++.so.6", + "so:libxfce4panel-1.0.so.4", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7" + ], + "provides": [ + "cmd:xfce4-popup-whiskermenu" + ] + }, + "protobuf-dev": { + "versions": { + "3.4.1-r1": 1510846093 + }, + "origin": "protobuf", + "dependencies": [ + "zlib-dev", + "pkgconfig", + "protobuf=3.4.1-r1" + ], + "provides": [ + "pc:protobuf-lite=3.4.1", + "pc:protobuf=3.4.1" + ] + }, + "tlsdate-doc": { + "versions": { + "0.0.13-r5": 1510259209 + }, + "origin": "tlsdate" + }, + "spawn-fcgi": { + "versions": { + "1.6.4-r3": 1509552746 + }, + "origin": "spawn-fcgi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:spawn-fcgi" + ] + }, + "perl-module-build-tiny": { + "versions": { + "0.039-r0": 1509474782 + }, + "origin": "perl-module-build-tiny", + "dependencies": [ + "perl-extutils-installpaths", + "perl-extutils-config", + "perl-extutils-helpers", + "perl-test-harness" + ] + }, + "dahdi-linux-hardened-dev": { + "versions": { + "4.9.65-r1": 1511798670 + }, + "origin": "dahdi-linux-hardened", + "dependencies": [ + "dahdi-linux-dev" + ] + }, + "lua-penlight-shared": { + "versions": { + "1.5.4-r0": 1509485590 + }, + "origin": "lua-penlight" + }, + "lua-alt-getopt": { + "versions": { + "0.7-r8": 1509480081 + }, + "origin": "lua-alt-getopt" + }, + "asterisk-sample-config": { + "versions": { + "15.6.1-r0": 1537795342, + "15.6.2-r0": 1568705004 + }, + "origin": "asterisk" + }, + "lua5.1-lyaml": { + "versions": { + "6.1.3-r1": 1509473484 + }, + "origin": "lua-lyaml", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libyaml-0.so.2" + ] + }, + "heimdal-libs": { + "versions": { + "7.4.0-r2": 1514545901, + "7.4.0-r3": 1559659765, + "7.4.0-r4": 1562862336 + }, + "origin": "heimdal", + "dependencies": [ + "krb5-conf", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libdb-5.3.so", + "so:libreadline.so.7", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:libasn1.so.8=8.0.0", + "so:libgssapi.so.3=3.0.0", + "so:libhcrypto.so.4=4.1.0", + "so:libhdb.so.9=9.2.0", + "so:libheimbase.so.1=1.0.0", + "so:libheimntlm.so.0=0.1.0", + "so:libhx509.so.5=5.0.0", + "so:libkadm5clnt.so.7=7.0.1", + "so:libkadm5srv.so.8=8.0.1", + "so:libkafs.so.0=0.5.1", + "so:libkdc.so.2=2.0.0", + "so:libkrb5.so.26=26.0.0", + "so:libotp.so.0=0.1.5", + "so:libroken.so.18=18.1.0", + "so:libsl.so.0=0.2.1", + "so:libwind.so.0=0.0.0", + "so:windc.so.0=0.0.0", + "cmd:digest-service", + "cmd:kdigest", + "cmd:string2key", + "cmd:verify_krb5_conf" + ] + }, + "vala-doc": { + "versions": { + "0.36.4-r0": 1509480647 + }, + "origin": "vala" + }, + "samba-client-libs": { + "versions": { + "4.7.6-r3": 1555491787 + }, + "origin": "samba", + "dependencies": [ + "so:libaddns-samba4.so", + "so:libasn1util-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-nbt-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcliauth-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libinterfaces-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsmb-transport-samba4.so", + "so:libsocket-blocking-samba4.so", + "so:libsys-rw-samba4.so", + "so:libtalloc.so.2", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-reg-samba4.so" + ], + "provides": [ + "so:libcli-ldap-samba4.so=0", + "so:libcmdline-credentials-samba4.so=0", + "so:libdcerpc.so.0=0.0.1", + "so:libdsdb-garbage-collect-tombstones-samba4.so=0", + "so:libevents-samba4.so=0", + "so:libhttp-samba4.so=0", + "so:libnetif-samba4.so=0", + "so:libregistry-samba4.so=0", + "so:libsmbclient-raw-samba4.so=0" + ] + }, + "postgresql-libs": { + "versions": { + "10.7-r0": 1554274198, + "10.8-r0": 1557668015, + "10.9-r0": 1562225614, + "10.10-r0": 1565683438 + }, + "origin": "postgresql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "so:libecpg.so.6=6.10", + "so:libecpg_compat.so.3=3.10", + "so:libpgtypes.so.3=3.10" + ] + }, + "py-google-api-python-client": { + "versions": { + "1.4.2-r1": 1509551801 + }, + "origin": "py-google-api-python-client", + "dependencies": [ + "python2", + "py-httplib2", + "py-oauth2client", + "py-uritemplate", + "py-six" + ] + }, + "libwnck-lang": { + "versions": { + "2.31.0-r5": 1510068129 + }, + "origin": "libwnck" + }, + "freeradius-client-dev": { + "versions": { + "1.1.7-r1": 1509488299 + }, + "origin": "freeradius-client", + "dependencies": [ + "freeradius-client=1.1.7-r1" + ] + }, + "xdm": { + "versions": { + "1.1.11-r5": 1509480595 + }, + "origin": "xdm", + "dependencies": [ + "sessreg", + "so:libX11.so.6", + "so:libXau.so.6", + "so:libXaw.so.7", + "so:libXdmcp.so.6", + "so:libXext.so.6", + "so:libXmu.so.6", + "so:libXpm.so.4", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0" + ], + "provides": [ + "cmd:xdm" + ] + }, + "net-snmp-libs": { + "versions": { + "5.7.3-r10": 1510259616 + }, + "origin": "net-snmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ], + "provides": [ + "so:libnetsnmp.so.30=30.0.3" + ] + }, + "mdocml-soelim": { + "versions": { + "1.14.3-r0": 1509459639 + }, + "origin": "mdocml", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:soelim" + ] + }, + "valgrind": { + "versions": { + "3.12.0-r1": 1509494841 + }, + "origin": "valgrind", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:callgrind_annotate", + "cmd:callgrind_control", + "cmd:cg_annotate", + "cmd:cg_diff", + "cmd:cg_merge", + "cmd:ms_print", + "cmd:valgrind", + "cmd:valgrind-di-server", + "cmd:valgrind-listener", + "cmd:vgdb" + ] + }, + "perl-test-needs": { + "versions": { + "0.002005-r0": 1509481592 + }, + "origin": "perl-test-needs" + }, + "xmlrpc-c-client": { + "versions": { + "1.39.11-r0": 1509482033 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libxmlrpc.so.3", + "so:libxmlrpc_util.so.3" + ], + "provides": [ + "so:libxmlrpc_client.so.3=3.39" + ] + }, + "tk-doc": { + "versions": { + "8.6.6-r1": 1509462114 + }, + "origin": "tk" + }, + "squid-lang-ms": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865669 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "py-gtk-demo": { + "versions": { + "2.24.0-r14": 1510071866 + }, + "origin": "py-gtk", + "dependencies": [ + "py-gtk" + ], + "provides": [ + "cmd:pygtk-demo" + ] + }, + "luajit": { + "versions": { + "2.1.0_beta3-r0": 1509474651 + }, + "origin": "luajit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libluajit-5.1.so.2=2.1.0", + "cmd:luajit", + "cmd:luajit-2.1.0-beta3" + ] + }, + "libnotify-doc": { + "versions": { + "0.7.7-r0": 1510068016 + }, + "origin": "libnotify" + }, + "openipmi-lanserv": { + "versions": { + "2.0.22-r1": 1510260044 + }, + "origin": "openipmi", + "dependencies": [ + "so:libOpenIPMIposix.so.0", + "so:libOpenIPMIutils.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpopt.so.0" + ], + "provides": [ + "so:libIPMIlanserv.so.0=0.0.1", + "cmd:ipmi_sim", + "cmd:ipmilan", + "cmd:sdrcomp" + ] + }, + "shorewall6-doc": { + "versions": { + "5.1.8-r0": 1509489476 + }, + "origin": "shorewall6" + }, + "ttf-linux-libertine": { + "versions": { + "5.3.0-r0": 1509469876 + }, + "origin": "ttf-linux-libertine", + "dependencies": [ + "fontconfig", + "encodings", + "mkfontdir", + "mkfontscale" + ] + }, + "acf-clamsmtp": { + "versions": { + "0.6.0-r2": 1510075831 + }, + "origin": "acf-clamsmtp", + "dependencies": [ + "acf-core", + "clamsmtp" + ] + }, + "xf86-video-sunleo-doc": { + "versions": { + "1.2.2-r0": 1510074826 + }, + "origin": "xf86-video-sunleo" + }, + "vanessa_socket-dev": { + "versions": { + "0.0.13-r0": 1511165896 + }, + "origin": "vanessa_socket", + "dependencies": [ + "popt-dev", + "vanessa_logger-dev", + "pkgconfig", + "vanessa_socket=0.0.13-r0" + ], + "provides": [ + "pc:vanessa-socket=0.0.13" + ] + }, + "musl-dev": { + "versions": { + "1.1.18-r3": 1518031143, + "1.1.18-r4": 1565163296 + }, + "origin": "musl", + "dependencies": [ + "!uclibc-dev", + "musl=1.1.18-r4" + ] + }, + "postgresql": { + "versions": { + "10.7-r0": 1554274200, + "10.8-r0": 1557668017, + "10.9-r0": 1562225615, + "10.10-r0": 1565683440 + }, + "origin": "postgresql", + "dependencies": [ + "postgresql-client", + "tzdata", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libldap-2.4.so.2", + "so:libpq.so.5", + "so:libssl.so.44", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:initdb", + "cmd:pg_archivecleanup", + "cmd:pg_controldata", + "cmd:pg_ctl", + "cmd:pg_resetwal", + "cmd:pg_rewind", + "cmd:pg_test_fsync", + "cmd:pg_test_timing", + "cmd:pg_upgrade", + "cmd:pg_waldump", + "cmd:pgbench", + "cmd:postgres", + "cmd:postmaster" + ] + }, + "xautolock-doc": { + "versions": { + "2.2-r3": 1510072540 + }, + "origin": "xautolock" + }, + "libxt": { + "versions": { + "1.1.5-r1": 1509466278 + }, + "origin": "libxt", + "dependencies": [ + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXt.so.6=6.0.0" + ] + }, + "xf86-input-evdev": { + "versions": { + "2.10.5-r0": 1510074957 + }, + "origin": "xf86-input-evdev", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libevdev.so.2", + "so:libmtdev.so.1", + "so:libudev.so.1" + ] + }, + "linux-pam-dev": { + "versions": { + "1.3.0-r0": 1509473196 + }, + "origin": "linux-pam", + "dependencies": [ + "gettext-dev", + "linux-pam=1.3.0-r0" + ] + }, + "xf86-video-i740-doc": { + "versions": { + "1.3.6-r0": 1510074897 + }, + "origin": "xf86-video-i740" + }, + "vlan": { + "versions": { + "2.2-r0": 1529583317 + }, + "origin": "vlan" + }, + "py-unidecode": { + "versions": { + "0.04.21-r0": 1509493258 + }, + "origin": "py-unidecode", + "dependencies": [ + "py3-unidecode", + "py3-unidecode=0.04.21-r0" + ] + }, + "nrpe-plugin": { + "versions": { + "2.15-r7": 1510259028 + }, + "origin": "nrpe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libssl.so.44" + ] + }, + "py2-lxml": { + "versions": { + "4.1.1-r0": 1510088208 + }, + "origin": "py-lxml", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexslt.so.0", + "so:libpython2.7.so.1.0", + "so:libxml2.so.2", + "so:libxslt.so.1" + ] + }, + "gst-plugins-bad0.10": { + "versions": { + "0.10.23-r7": 1510287376 + }, + "origin": "gst-plugins-bad0.10", + "dependencies": [ + "so:libSDL-1.2.so.0", + "so:libX11.so.6", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcrypto.so.42", + "so:libdc1394.so.22", + "so:libfaac.so.0", + "so:libfaad.so.2", + "so:libflite.so.1", + "so:libflite_cmu_us_kal.so.1", + "so:libflite_cmulex.so.1", + "so:libflite_usenglish.so.1", + "so:libgcc_s.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsm.so.1", + "so:libgstapp-0.10.so.0", + "so:libgstaudio-0.10.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstcontroller-0.10.so.0", + "so:libgstfft-0.10.so.0", + "so:libgstinterfaces-0.10.so.0", + "so:libgstpbutils-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgstrtp-0.10.so.0", + "so:libgstsdp-0.10.so.0", + "so:libgsttag-0.10.so.0", + "so:libgstvideo-0.10.so.0", + "so:libintl.so.8", + "so:libjasper.so.4", + "so:libmms.so.0", + "so:liborc-0.4.so.0", + "so:libpng16.so.16", + "so:librsvg-2.so.2", + "so:libstdc++.so.6", + "so:libvpx.so.4", + "so:libxvidcore.so.4" + ], + "provides": [ + "so:libgstbasecamerabinsrc-0.10.so.23=23.0.0", + "so:libgstbasevideo-0.10.so.23=23.0.0", + "so:libgstcodecparsers-0.10.so.23=23.0.0", + "so:libgstphotography-0.10.so.23=23.0.0", + "so:libgstsignalprocessor-0.10.so.23=23.0.0" + ] + }, + "sed": { + "versions": { + "4.4-r1": 1509462308 + }, + "origin": "sed", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sed" + ] + }, + "lua5.1-lub": { + "versions": { + "1.1.0-r1": 1509475703 + }, + "origin": "lua-lub", + "dependencies": [ + "lua5.1", + "lua5.1-filesystem" + ] + }, + "pangomm-dev": { + "versions": { + "2.40.1-r0": 1509473003 + }, + "origin": "pangomm", + "dependencies": [ + "pango-dev", + "glibmm-dev", + "cairomm-dev", + "pangomm=2.40.1-r0", + "pc:cairomm-1.0>=1.2.2", + "pc:glibmm-2.4>=2.48.0", + "pc:pangocairo>=1.38.0", + "pkgconfig" + ], + "provides": [ + "pc:pangomm-1.4=2.40.1" + ] + }, + "libdvbcsa": { + "versions": { + "1.1.0-r0": 1509488846 + }, + "origin": "libdvbcsa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdvbcsa.so.1=1.0.1" + ] + }, + "json-glib": { + "versions": { + "1.2.8-r0": 1509480681 + }, + "origin": "json-glib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libjson-glib-1.0.so.0=0.200.8" + ] + }, + "py2-bluez": { + "versions": { + "0.22-r1": 1510075575 + }, + "origin": "py-bluez", + "dependencies": [ + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "lua-aports": { + "versions": { + "0.7.0-r0": 1509462496 + }, + "origin": "lua-aports", + "dependencies": [ + "lua5.2-cjson", + "lua5.2-filesystem", + "lua5.2-optarg", + "lua5.2" + ], + "provides": [ + "cmd:ap", + "cmd:buildrepo" + ] + }, + "recode-doc": { + "versions": { + "3.6-r1": 1509485761 + }, + "origin": "recode" + }, + "logcheck": { + "versions": { + "1.3.18-r0": 1509468631 + }, + "origin": "logcheck", + "dependencies": [ + "lockfile-progs", + "/bin/sh" + ], + "provides": [ + "cmd:logcheck", + "cmd:logcheck-test", + "cmd:logtail", + "cmd:logtail2" + ] + }, + "libsigc++-dev": { + "versions": { + "2.10.0-r1": 1509472940 + }, + "origin": "libsigc++", + "dependencies": [ + "libsigc++=2.10.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:sigc++-2.0=2.10.0" + ] + }, + "perl-term-table": { + "versions": { + "0.012-r0": 1509631261 + }, + "origin": "perl-term-table", + "dependencies": [ + "perl-importer" + ] + }, + "uwsgi-router_expires": { + "versions": { + "2.0.17-r0": 1522154657 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libnl3-dev": { + "versions": { + "3.2.28-r1": 1509475886 + }, + "origin": "libnl3", + "dependencies": [ + "libnl3-cli=3.2.28-r1", + "libnl3=3.2.28-r1", + "pkgconfig" + ], + "provides": [ + "pc:libnl-3.0=3.2.28", + "pc:libnl-cli-3.0=3.2.28", + "pc:libnl-genl-3.0=3.2.28", + "pc:libnl-idiag-3.0=3.2.28", + "pc:libnl-nf-3.0=3.2.28", + "pc:libnl-route-3.0=3.2.28", + "pc:libnl-xfrm-3.0=3.2.28" + ] + }, + "flex-libs": { + "versions": { + "2.6.4-r1": 1509456704 + }, + "origin": "flex", + "dependencies": [ + "m4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfl.so.2=2.0.0" + ] + }, + "py-pgen": { + "versions": { + "2.7.10-r0": 1509484067 + }, + "origin": "py-pgen", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pgen" + ] + }, + "partimage-doc": { + "versions": { + "0.6.9-r4": 1510260489 + }, + "origin": "partimage" + }, + "libunique-dev": { + "versions": { + "1.1.6-r6": 1510069913 + }, + "origin": "libunique", + "dependencies": [ + "libunique=1.1.6-r6", + "pc:gtk+-2.0", + "pkgconfig" + ], + "provides": [ + "pc:unique-1.0=1.1.6" + ] + }, + "qextserialport": { + "versions": { + "1.2_rc1-r0": 1510075401 + }, + "origin": "qextserialport", + "dependencies": [ + "so:libQtCore.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libqextserialport.so.1=1.2.0" + ] + }, + "lua5.1-evdev": { + "versions": { + "2.2.1-r1": 1509488870 + }, + "origin": "lua-evdev", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "fprobe": { + "versions": { + "1.1-r7": 1509481826 + }, + "origin": "fprobe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:fprobe" + ] + }, + "darkhttpd-openrc": { + "versions": { + "1.12-r2": 1526305370 + }, + "origin": "darkhttpd" + }, + "st-dbg": { + "versions": { + "0.7-r1": 1509494024 + }, + "origin": "st", + "dependencies": [ + "ncurses-terminfo" + ] + }, + "rtapd": { + "versions": { + "1.7-r6": 1509491640 + }, + "origin": "rtapd", + "dependencies": [ + "rtnppd", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "cmd:rtapd", + "cmd:vsnppd" + ] + }, + "libbluray-dev": { + "versions": { + "1.0.0-r0": 1509488856 + }, + "origin": "libbluray", + "dependencies": [ + "libbluray=1.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libbluray=1.0.0" + ] + }, + "perl-xml-parser": { + "versions": { + "2.44-r4": 1509464446 + }, + "origin": "perl-xml-parser", + "dependencies": [ + "perl-libwww", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ] + }, + "perl-net-telnet-doc": { + "versions": { + "3.04-r0": 1509477768 + }, + "origin": "perl-net-telnet" + }, + "perl-exporter-tiny": { + "versions": { + "1.000000-r0": 1509468499 + }, + "origin": "perl-exporter-tiny" + }, + "lua5.1-feedparser": { + "versions": { + "0.71-r0": 1509485576 + }, + "origin": "lua-feedparser", + "dependencies": [ + "lua5.1-expat", + "lua-feedparser-common", + "lua-feedparser-common=0.71-r0" + ] + }, + "su-exec": { + "versions": { + "0.2-r0": 1509483490 + }, + "origin": "su-exec", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:su-exec" + ] + }, + "sshpass": { + "versions": { + "1.06-r0": 1509494949 + }, + "origin": "sshpass", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sshpass" + ] + }, + "xf86-video-qxl": { + "versions": { + "0.1.5-r3": 1510073032 + }, + "origin": "xf86-video-qxl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "ristretto": { + "versions": { + "0.8.2-r0": 1510072650 + }, + "origin": "ristretto", + "dependencies": [ + "desktop-file-utils", + "hicolor-icon-theme", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdbus-glib-1.so.2", + "so:libexif.so.12", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:ristretto" + ] + }, + "cppunit-dev": { + "versions": { + "1.14.0-r0": 1509481255 + }, + "origin": "cppunit", + "dependencies": [ + "cppunit=1.14.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:cppunit=1.14.0" + ] + }, + "py-urwid": { + "versions": { + "1.3.1-r2": 1509493957 + }, + "origin": "py-urwid" + }, + "dpkg-doc": { + "versions": { + "1.18.24-r0": 1509494230 + }, + "origin": "dpkg" + }, + "icu-dev": { + "versions": { + "59.1-r1": 1509464843 + }, + "origin": "icu", + "dependencies": [ + "icu-libs=59.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:icu-i18n=59.1", + "pc:icu-io=59.1", + "pc:icu-uc=59.1", + "cmd:icu-config" + ] + }, + "gigolo": { + "versions": { + "0.4.2-r0": 1510076414 + }, + "origin": "gigolo", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:gigolo" + ] + }, + "py-sphinxcontrib-websupport": { + "versions": { + "1.0.1-r3": 1509493320 + }, + "origin": "py-sphinxcontrib-websupport" + }, + "perl-universal-require-doc": { + "versions": { + "0.13-r0": 1509469904 + }, + "origin": "perl-universal-require" + }, + "nginx-mod-http-fancyindex": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "boost-unit_test_framework": { + "versions": { + "1.62.0-r5": 1509465881 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_unit_test_framework-mt.so.1.62.0=1.62.0", + "so:libboost_unit_test_framework.so.1.62.0=1.62.0" + ] + }, + "scstadmin": { + "versions": { + "2.2.0-r2": 1509492547 + }, + "origin": "scstadmin", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:scstadmin" + ] + }, + "libxfont2": { + "versions": { + "2.0.3-r0": 1511975683 + }, + "origin": "libxfont2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfontenc.so.1", + "so:libfreetype.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libXfont2.so.2=2.0.0" + ] + }, + "qemu-i386": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-i386" + ] + }, + "cunit-dev": { + "versions": { + "2.1.3-r1": 1509466104 + }, + "origin": "cunit", + "dependencies": [ + "cunit=2.1.3-r1", + "pkgconfig" + ] + }, + "libvorbis-doc": { + "versions": { + "1.3.6-r2": 1549615971 + }, + "origin": "libvorbis" + }, + "motif": { + "versions": { + "2.3.4-r2": 1509495442 + }, + "origin": "motif", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1" + ], + "provides": [ + "so:libMrm.so.4=4.0.4", + "so:libUil.so.4=4.0.4", + "so:libXm.so.4=4.0.4", + "cmd:uil" + ] + }, + "lua-feedparser": { + "versions": { + "0.71-r0": 1509485586 + }, + "origin": "lua-feedparser" + }, + "spl-hardened-dev": { + "versions": { + "4.9.65-r1": 1511798817 + }, + "origin": "spl-hardened", + "dependencies": [ + "linux-hardened-dev=4.9.65-r1" + ] + }, + "exo-doc": { + "versions": { + "0.11.5-r0": 1510068072 + }, + "origin": "exo" + }, + "lua-uuid": { + "versions": { + "2012.05-r1": 1509489738 + }, + "origin": "lua-uuid", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ] + }, + "libcanberra-gtk3": { + "versions": { + "0.30-r1": 1510071976 + }, + "origin": "libcanberra", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcanberra.so.0", + "so:libgdk-3.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0" + ], + "provides": [ + "so:libcanberra-gtk3.so.0=0.1.9", + "cmd:canberra-gtk-play" + ] + }, + "lua5.2-curl": { + "versions": { + "0.3.7-r1": 1509493017 + }, + "origin": "lua-curl", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "help2man-doc": { + "versions": { + "1.47.5-r0": 1509456689 + }, + "origin": "help2man" + }, + "sysklogd": { + "versions": { + "1.5.1-r1": 1511795688 + }, + "origin": "sysklogd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:klogd", + "cmd:syslogd" + ] + }, + "perl-test-nowarnings": { + "versions": { + "1.04-r1": 1509468472 + }, + "origin": "perl-test-nowarnings", + "dependencies": [ + "perl", + "perl-test-simple" + ] + }, + "perl-file-copy-recursive": { + "versions": { + "0.38-r1": 1510855671 + }, + "origin": "perl-file-copy-recursive" + }, + "at-spi2-atk-dev": { + "versions": { + "2.26.1-r0": 1509466060 + }, + "origin": "at-spi2-atk", + "dependencies": [ + "glib-dev", + "dbus-dev", + "atk-dev", + "at-spi2-core-dev", + "at-spi2-atk=2.26.1-r0", + "pc:atspi-2", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:atk-bridge-2.0=2.26.1" + ] + }, + "perl-io-async": { + "versions": { + "0.69-r0": 1509480542 + }, + "origin": "perl-io-async", + "dependencies": [ + "perl-struct-dumb", + "perl-future" + ] + }, + "xf86-video-xgixp": { + "versions": { + "1.8.1-r8": 1510069923 + }, + "origin": "xf86-video-xgixp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libcdio-doc": { + "versions": { + "0.94-r0": 1509473626 + }, + "origin": "libcdio" + }, + "check-doc": { + "versions": { + "0.12.0-r1": 1509461855 + }, + "origin": "check" + }, + "squid-lang-da": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865667 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "freeswitch-flite": { + "versions": { + "1.6.19-r0": 1510585357 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libflite.so.1", + "so:libflite_cmu_us_awb.so.1", + "so:libflite_cmu_us_kal.so.1", + "so:libflite_cmu_us_kal16.so.1", + "so:libflite_cmu_us_rms.so.1", + "so:libflite_cmu_us_slt.so.1", + "so:libflite_usenglish.so.1", + "so:libfreeswitch.so.1" + ] + }, + "bacula-libs": { + "versions": { + "9.0.5-r3": 1512992127 + }, + "origin": "bacula", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:bpipe-fd.so=0", + "so:libbac-9.0.5.so=0", + "so:libbaccfg-9.0.5.so=0", + "so:libbacfind-9.0.5.so=0", + "so:libbacsd-9.0.5.so=0", + "so:libbacsql-9.0.5.so=0" + ] + }, + "unrar-doc": { + "versions": { + "5.5.8-r0": 1509468420 + }, + "origin": "unrar" + }, + "py3-pygments": { + "versions": { + "2.2.0-r0": 1509493370 + }, + "origin": "py-pygments", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:pygmentize-3" + ] + }, + "libdvbpsi-dev": { + "versions": { + "1.3.1-r0": 1511168477 + }, + "origin": "libdvbpsi", + "dependencies": [ + "libdvbpsi=1.3.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libdvbpsi=1.3.1" + ] + }, + "alpine-mirrors": { + "versions": { + "3.5.6-r0": 1511354854 + }, + "origin": "alpine-mirrors" + }, + "perl-crypt-rijndael-doc": { + "versions": { + "1.11-r4": 1509471903 + }, + "origin": "perl-crypt-rijndael" + }, + "wxgtk2.8-dev": { + "versions": { + "2.8.12.1-r4": 1510928414 + }, + "origin": "wxgtk2.8", + "dependencies": [ + "gtk+2.0-dev", + "zlib-dev", + "tiff-dev", + "libpng-dev", + "libjpeg-turbo-dev", + "expat-dev", + "libsm-dev", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libwx_baseu-2.8.so.0", + "so:libwx_baseu_xml-2.8.so.0", + "wxgtk2.8-base=2.8.12.1-r4", + "wxgtk2.8-media=2.8.12.1-r4", + "wxgtk2.8=2.8.12.1-r4" + ], + "provides": [ + "cmd:wxrc", + "cmd:wxrc-2.8" + ] + }, + "cyrus-sasl-ntlm": { + "versions": { + "2.1.26-r11": 1510258044 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "py3-unidecode": { + "versions": { + "0.04.21-r0": 1509493257 + }, + "origin": "py-unidecode", + "dependencies": [ + "py3-unidecode", + "python3" + ], + "provides": [ + "cmd:unidecode-3" + ] + }, + "abiword-plugin-mswrite": { + "versions": { + "3.0.2-r1": 1510073364 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "gvfs-smb": { + "versions": { + "1.34.1-r0": 1511430258, + "1.34.1-r1": 1563787224 + }, + "origin": "gvfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgvfscommon.so", + "so:libgvfsdaemon.so", + "so:libintl.so.8", + "so:libsmbclient.so.0" + ] + }, + "avahi-libs": { + "versions": { + "0.6.32-r4": 1509465448, + "0.6.32-r5": 1563345619 + }, + "origin": "avahi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libintl.so.8" + ], + "provides": [ + "so:libavahi-client.so.3=3.2.9", + "so:libavahi-common.so.3=3.5.3" + ] + }, + "libiptcdata": { + "versions": { + "1.0.4-r2": 1509496044 + }, + "origin": "libiptcdata", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libiptcdata.so.0=0.3.3", + "cmd:iptc" + ] + }, + "p7zip-doc": { + "versions": { + "16.02-r3": 1533742283 + }, + "origin": "p7zip" + }, + "trac": { + "versions": { + "1.2.2-r0": 1509491441 + }, + "origin": "trac", + "dependencies": [ + "python2", + "py-setuptools", + "py-genshi", + "/bin/sh" + ], + "provides": [ + "cmd:trac-admin", + "cmd:tracd" + ] + }, + "glew": { + "versions": { + "2.1.0-r0": 1510068629 + }, + "origin": "glew", + "dependencies": [ + "so:libGL.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libGLEW.so.2.1=2.1.0" + ] + }, + "collectd-libs": { + "versions": { + "5.7.2-r0": 1510069642 + }, + "origin": "collectd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20" + ], + "provides": [ + "so:libcollectdclient.so.1=1.0.0" + ] + }, + "acpi": { + "versions": { + "1.7-r2": 1509787633 + }, + "origin": "acpi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:acpi" + ] + }, + "py-templayer-doc": { + "versions": { + "1.5.1-r3": 1509489478 + }, + "origin": "py-templayer", + "dependencies": [ + "python2" + ] + }, + "perl-type-tiny": { + "versions": { + "1.000006-r0": 1509468523 + }, + "origin": "perl-type-tiny", + "dependencies": [ + "perl-exporter-tiny" + ] + }, + "rtpproxy-debug": { + "versions": { + "2.0.0-r4": 1509492567 + }, + "origin": "rtpproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:rtpproxy_debug" + ] + }, + "perl-mime-base64": { + "versions": { + "3.15-r4": 1509491805 + }, + "origin": "perl-mime-base64", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "clamav-scanner": { + "versions": { + "0.100.3-r0": 1555508238 + }, + "origin": "clamav", + "dependencies": [ + "freshclam", + "so:libc.musl-x86_64.so.1", + "so:libclamav.so.7", + "so:libcrypto.so.42", + "so:libz.so.1" + ], + "provides": [ + "cmd:clambc", + "cmd:clamscan", + "cmd:sigtool" + ] + }, + "xfce4-notes-plugin-lang": { + "versions": { + "1.8.1-r0": 1510074768 + }, + "origin": "xfce4-notes-plugin" + }, + "mosh-doc": { + "versions": { + "1.3.2-r3": 1510846214 + }, + "origin": "mosh" + }, + "ntop-doc": { + "versions": { + "5.0.1-r11": 1510314558 + }, + "origin": "ntop" + }, + "quvi-doc": { + "versions": { + "0.9.5-r4": 1509495084 + }, + "origin": "quvi" + }, + "xrdp-dev": { + "versions": { + "0.9.2-r3": 1510260212 + }, + "origin": "xrdp", + "dependencies": [ + "pkgconfig", + "xrdp=0.9.2-r3" + ], + "provides": [ + "pc:libpainter=0.1.1", + "pc:rfxcodec=0.1.1", + "pc:xrdp=0.9.2" + ] + }, + "a52dec-doc": { + "versions": { + "0.7.4-r6": 1510072175 + }, + "origin": "a52dec" + }, + "py2-curl": { + "versions": { + "7.43.0-r4": 1510288123 + }, + "origin": "py-curl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libpython2.7.so.1.0" + ] + }, + "libiec61883-doc": { + "versions": { + "1.2.0-r1": 1509470082 + }, + "origin": "libiec61883" + }, + "wv": { + "versions": { + "1.2.9-r3": 1509483983 + }, + "origin": "wv", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libwv-1.2.so.4=4.0.5", + "cmd:wvAbw", + "cmd:wvCleanLatex", + "cmd:wvConvert", + "cmd:wvDVI", + "cmd:wvDocBook", + "cmd:wvHtml", + "cmd:wvLatex", + "cmd:wvMime", + "cmd:wvPDF", + "cmd:wvPS", + "cmd:wvRTF", + "cmd:wvSummary", + "cmd:wvText", + "cmd:wvVersion", + "cmd:wvWare", + "cmd:wvWml" + ] + }, + "nspr-dev": { + "versions": { + "4.17-r0": 1509479384 + }, + "origin": "nspr", + "dependencies": [ + "nspr", + "pkgconfig" + ], + "provides": [ + "pc:nspr=4.17.0", + "cmd:nspr-config" + ] + }, + "py-gdbm": { + "versions": { + "2.7.15-r2": 1534944323 + }, + "origin": "python2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "python-gdbm=2.7.15-r2" + ] + }, + "lua5.2-augeas": { + "versions": { + "0.1.2-r3": 1509473122 + }, + "origin": "lua-augeas", + "dependencies": [ + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1" + ] + }, + "bacula-pgsql": { + "versions": { + "9.0.5-r3": 1512992127 + }, + "origin": "bacula", + "dependencies": [ + "bacula", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "so:libbaccats-postgresql-9.0.5.so=0" + ] + }, + "ipset-doc": { + "versions": { + "6.34-r1": 1537515181 + }, + "origin": "ipset" + }, + "libshout-doc": { + "versions": { + "2.4.1-r1": 1510068379 + }, + "origin": "libshout" + }, + "py3-openssl": { + "versions": { + "17.5.0-r0": 1548491340 + }, + "origin": "py-openssl", + "dependencies": [ + "py3-cryptography", + "py3-six", + "python3" + ] + }, + "mutt-doc": { + "versions": { + "1.10.1-r0": 1532446898 + }, + "origin": "mutt" + }, + "nss-static": { + "versions": { + "3.34.1-r0": 1511890215 + }, + "origin": "nss" + }, + "clang-analyzer": { + "versions": { + "5.0.0-r0": 1510683247 + }, + "origin": "clang", + "dependencies": [ + "clang=5.0.0-r0", + "perl", + "python2" + ], + "provides": [ + "cmd:scan-build", + "cmd:scan-view" + ] + }, + "squid-lang-fa": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865667 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-test-sharedfork-doc": { + "versions": { + "0.35-r0": 1509481778 + }, + "origin": "perl-test-sharedfork" + }, + "dhcp": { + "versions": { + "4.3.5-r0": 1509496514 + }, + "origin": "dhcp", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "cmd:dhcpd", + "cmd:omshell" + ] + }, + "perl-dbd-sqlite-dev": { + "versions": { + "1.54-r1": 1509477495 + }, + "origin": "perl-dbd-sqlite" + }, + "libxdg-basedir": { + "versions": { + "1.2.0-r0": 1509494299 + }, + "origin": "libxdg-basedir", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libxdg-basedir.so.1=1.2.0" + ] + }, + "openldap-back-sql": { + "versions": { + "2.4.45-r3": 1510258133, + "2.4.46-r0": 1565073939, + "2.4.48-r0": 1566900516 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2", + "so:libodbc.so.2" + ] + }, + "qt-tds": { + "versions": { + "4.8.7-r8": 1510287209 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtSql.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libsybdb.so.5" + ] + }, + "json-c": { + "versions": { + "0.12.1-r1": 1509466321 + }, + "origin": "json-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libjson-c.so.2=2.0.2" + ] + }, + "qemu-lang": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu" + }, + "oniguruma-dev": { + "versions": { + "6.6.1-r0": 1509470649 + }, + "origin": "oniguruma", + "dependencies": [ + "oniguruma=6.6.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:oniguruma=6.6.1", + "cmd:onig-config" + ] + }, + "djbdns-doc": { + "versions": { + "1.05-r47": 1509488768 + }, + "origin": "djbdns" + }, + "glade-doc": { + "versions": { + "3.20.1-r0": 1510073638 + }, + "origin": "glade" + }, + "atk-lang": { + "versions": { + "2.26.1-r1": 1509464672 + }, + "origin": "atk" + }, + "gtk+3.0-lang": { + "versions": { + "3.22.21-r0": 1510067779 + }, + "origin": "gtk+3.0", + "dependencies": [ + "shared-mime-info", + "gtk-update-icon-cache" + ] + }, + "jwhois-doc": { + "versions": { + "4.0-r4": 1509493813 + }, + "origin": "jwhois" + }, + "ntop": { + "versions": { + "5.0.1-r11": 1510314558 + }, + "origin": "ntop", + "dependencies": [ + "/bin/sh", + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgdbm.so.4", + "so:libpcap.so.1", + "so:libpython2.7.so.1.0", + "so:librrd_th.so.4", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:libnetflowPlugin-5.0.1.so=0", + "so:libntop-5.0.1.so=0", + "so:libntopreport-5.0.1.so=0", + "so:librrdPlugin-5.0.1.so=0", + "so:libsflowPlugin-5.0.1.so=0", + "cmd:ntop", + "cmd:ntop-update-geoip-db" + ] + }, + "gstreamer0.10-tools": { + "versions": { + "0.10.36-r2": 1509470983 + }, + "origin": "gstreamer0.10", + "dependencies": [ + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-0.10.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:gst-feedback", + "cmd:gst-feedback-0.10", + "cmd:gst-inspect", + "cmd:gst-inspect-0.10", + "cmd:gst-launch", + "cmd:gst-launch-0.10", + "cmd:gst-typefind", + "cmd:gst-typefind-0.10", + "cmd:gst-xmlinspect", + "cmd:gst-xmlinspect-0.10", + "cmd:gst-xmllaunch", + "cmd:gst-xmllaunch-0.10" + ] + }, + "libspf2-dev": { + "versions": { + "1.2.10-r2": 1509491330 + }, + "origin": "libspf2", + "dependencies": [ + "libspf2=1.2.10-r2" + ] + }, + "libsexy-dev": { + "versions": { + "0.1.11-r7": 1510072497 + }, + "origin": "libsexy", + "dependencies": [ + "libsexy=0.1.11-r7", + "pkgconfig" + ], + "provides": [ + "pc:libsexy=0.1.11" + ] + }, + "net-snmp-perl": { + "versions": { + "5.7.3-r10": 1510259617 + }, + "origin": "net-snmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.30", + "so:libnetsnmpagent.so.30", + "so:libnetsnmptrapd.so.30" + ], + "provides": [ + "cmd:mib2c", + "cmd:mib2c-update", + "cmd:net-snmp-cert", + "cmd:snmp-bridge-mib", + "cmd:traptoemail" + ] + }, + "xfce4-cpufreq-plugin-lang": { + "versions": { + "1.1.3-r2": 1510074004 + }, + "origin": "xfce4-cpufreq-plugin" + }, + "qemu-sparc64": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-sparc64" + ] + }, + "websocket++": { + "versions": { + "0.7.0-r0": 1509491258 + }, + "origin": "websocket++" + }, + "nss": { + "versions": { + "3.34.1-r0": 1511890216 + }, + "origin": "nss", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libnspr4.so", + "so:libplc4.so", + "so:libplds4.so", + "so:libsqlite3.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libfreebl3.so=34", + "so:libgtestutil.so.34=34", + "so:libnss3.so=34", + "so:libnssckbi.so=34", + "so:libnssdbm3.so=34", + "so:libnssutil3.so=34", + "so:libsmime3.so=34", + "so:libsoftokn3.so=34", + "so:libssl3.so=34" + ] + }, + "orage-doc": { + "versions": { + "4.12.1-r1": 1510073565 + }, + "origin": "orage" + }, + "openssh-keysign": { + "versions": { + "7.5_p1-r10": 1551712288 + }, + "origin": "openssh", + "dependencies": [ + "openssh-client", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libz.so.1" + ] + }, + "ppp-radius": { + "versions": { + "2.4.7-r5": 1509480128 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "nettle-dev": { + "versions": { + "3.3-r0": 1509465113 + }, + "origin": "nettle", + "dependencies": [ + "gmp-dev", + "nettle=3.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:hogweed=3.3", + "pc:nettle=3.3" + ] + }, + "gst-libav": { + "versions": { + "1.12.3-r0": 1510072163 + }, + "origin": "gst-libav", + "dependencies": [ + "so:libavcodec.so.57", + "so:libavfilter.so.6", + "so:libavformat.so.57", + "so:libavutil.so.55", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstaudio-1.0.so.0", + "so:libgstbase-1.0.so.0", + "so:libgstpbutils-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgstvideo-1.0.so.0" + ] + }, + "liboil-dev": { + "versions": { + "0.3.17-r5": 1509471014 + }, + "origin": "liboil", + "dependencies": [ + "liboil=0.3.17-r5", + "pkgconfig" + ], + "provides": [ + "pc:liboil-0.3=0.3.17" + ] + }, + "thunar-doc": { + "versions": { + "1.6.12-r0": 1510072625 + }, + "origin": "thunar" + }, + "libgpg-error-dev": { + "versions": { + "1.27-r1": 1509459211 + }, + "origin": "libgpg-error", + "dependencies": [ + "libgpg-error=1.27-r1" + ], + "provides": [ + "cmd:gpg-error-config" + ] + }, + "halberd-doc": { + "versions": { + "0.2.4-r1": 1509483052 + }, + "origin": "halberd" + }, + "wxgtk2.8-base": { + "versions": { + "2.8.12.1-r4": 1510928424 + }, + "origin": "wxgtk2.8", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libwx_baseu-2.8.so.0=0.8.0", + "so:libwx_baseu_net-2.8.so.0=0.8.0", + "so:libwx_baseu_xml-2.8.so.0=0.8.0" + ] + }, + "phodav-lang": { + "versions": { + "2.2-r0": 1509492231 + }, + "origin": "phodav" + }, + "perl-io-multiplex": { + "versions": { + "1.16-r0": 1509479741 + }, + "origin": "perl-io-multiplex", + "dependencies": [ + "perl" + ] + }, + "asterisk-curl": { + "versions": { + "15.6.1-r0": 1537795343, + "15.6.2-r0": 1568705005 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "libatomic": { + "versions": { + "6.4.0-r5": 1509458071 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libatomic.so.1=1.2.0" + ] + }, + "perl-tree-dag_node-doc": { + "versions": { + "1.29-r0": 1509494494 + }, + "origin": "perl-tree-dag_node" + }, + "wipe": { + "versions": { + "2.3.1-r0": 1509496532 + }, + "origin": "wipe", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:wipe" + ] + }, + "at-spi2-core-dbg": { + "versions": { + "2.26.2-r0": 1509466052 + }, + "origin": "at-spi2-core" + }, + "perl-text-password-pronounceable-doc": { + "versions": { + "0.30-r1": 1510564569 + }, + "origin": "perl-text-password-pronounceable" + }, + "chrony-doc": { + "versions": { + "3.2-r2": 1518706488 + }, + "origin": "chrony" + }, + "gucharmap-lang": { + "versions": { + "3.18.2-r0": 1510074174 + }, + "origin": "gucharmap" + }, + "cairomm-dev": { + "versions": { + "1.12.2-r0": 1509472990 + }, + "origin": "cairomm", + "dependencies": [ + "libsigc++-dev", + "cairomm=1.12.2-r0", + "pc:cairo-ft", + "pc:cairo-pdf", + "pc:cairo-png", + "pc:cairo-ps", + "pc:cairo-svg", + "pc:cairo-xlib", + "pc:cairo-xlib-xrender", + "pc:cairo>=1.10.0", + "pc:sigc++-2.0>=2.5.1", + "pkgconfig" + ], + "provides": [ + "pc:cairomm-1.0=1.12.2", + "pc:cairomm-ft-1.0=1.12.2", + "pc:cairomm-pdf-1.0=1.12.2", + "pc:cairomm-png-1.0=1.12.2", + "pc:cairomm-ps-1.0=1.12.2", + "pc:cairomm-svg-1.0=1.12.2", + "pc:cairomm-xlib-1.0=1.12.2", + "pc:cairomm-xlib-xrender-1.0=1.12.2" + ] + }, + "mpeg2dec": { + "versions": { + "0.5.1-r7": 1509473557 + }, + "origin": "libmpeg2", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libc.musl-x86_64.so.1", + "so:libmpeg2.so.0", + "so:libmpeg2convert.so.0" + ], + "provides": [ + "cmd:mpeg2dec" + ] + }, + "task": { + "versions": { + "2.5.1-r0": 1509491757 + }, + "origin": "task", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libstdc++.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:task" + ] + }, + "py-openssl": { + "versions": { + "17.5.0-r0": 1548491341 + }, + "origin": "py-openssl", + "dependencies": [ + "py-cryptography", + "py-six" + ] + }, + "tree-doc": { + "versions": { + "1.7.0-r1": 1509489739 + }, + "origin": "tree" + }, + "perl-convert-tnef": { + "versions": { + "0.18-r0": 1509470833 + }, + "origin": "perl-convert-tnef", + "dependencies": [ + "perl" + ] + }, + "lua5.1-cqueues": { + "versions": { + "20171014-r0": 1510619110 + }, + "origin": "lua-cqueues", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "sfic-doc": { + "versions": { + "0.1.7-r6": 1509492420 + }, + "origin": "sfic" + }, + "py3-wtforms": { + "versions": { + "2.1-r0": 1509476540 + }, + "origin": "py-wtforms", + "dependencies": [ + "python3" + ] + }, + "sparsehash-doc": { + "versions": { + "2.0.3-r0": 1509489125 + }, + "origin": "sparsehash" + }, + "perl-regexp-common-doc": { + "versions": { + "2016010801-r0": 1509485697 + }, + "origin": "perl-regexp-common" + }, + "perl-io-tty-doc": { + "versions": { + "1.12-r4": 1509483927 + }, + "origin": "perl-io-tty" + }, + "mp3splt-gtk-doc": { + "versions": { + "0.9.2-r2": 1510070622 + }, + "origin": "mp3splt-gtk" + }, + "gtksourceview2-doc": { + "versions": { + "2.10.5-r0": 1510067575 + }, + "origin": "gtksourceview2" + }, + "collectd-utils": { + "versions": { + "5.7.2-r0": 1510069644 + }, + "origin": "collectd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcollectdclient.so.1" + ], + "provides": [ + "cmd:collectd-nagios", + "cmd:collectd-tg", + "cmd:collectdctl" + ] + }, + "xcb-util-wm": { + "versions": { + "0.4.1-r1": 1509473928 + }, + "origin": "xcb-util-wm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-ewmh.so.2=2.0.0", + "so:libxcb-icccm.so.4=4.0.0" + ] + }, + "freeradius-unixodbc": { + "versions": { + "3.0.15-r4": 1556202796, + "3.0.15-r5": 1566310604 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.15-r5", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ], + "provides": [ + "freeradius3-unixodbc=3.0.15-r5", + "so:rlm_sql_unixodbc.so=0" + ] + }, + "gd-dev": { + "versions": { + "2.2.5-r3": 1554728246 + }, + "origin": "gd", + "dependencies": [ + "gd", + "perl", + "libgd=2.2.5-r3", + "pkgconfig" + ], + "provides": [ + "pc:gdlib=2.2.5", + "cmd:bdftogd", + "cmd:gdlib-config" + ] + }, + "perl-lwp-protocol-https": { + "versions": { + "6.06-r1": 1509495026 + }, + "origin": "perl-lwp-protocol-https", + "dependencies": [ + "perl-libwww", + "perl-io-socket-ssl", + "perl-mozilla-ca", + "perl-net-http", + "perl-net-ssleay" + ] + }, + "sessreg": { + "versions": { + "1.1.1-r0": 1509480574 + }, + "origin": "sessreg", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sessreg" + ] + }, + "quagga": { + "versions": { + "1.2.4-r0": 1519133995 + }, + "origin": "quagga", + "dependencies": [ + "iproute2", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libnetsnmp.so.30", + "so:libnetsnmpagent.so.30", + "so:libreadline.so.7" + ], + "provides": [ + "quagga-nhrp=1.2.4", + "so:libfpm_pb.so.0=0.0.0", + "so:libospf.so.0=0.0.0", + "so:libospfapiclient.so.0=0.0.0", + "so:libquagga_pb.so.0=0.0.0", + "so:libzebra.so.1=1.0.0", + "cmd:bgp_btoa", + "cmd:bgpd", + "cmd:isisd", + "cmd:nhrpd", + "cmd:ospf6d", + "cmd:ospfclient", + "cmd:ospfd", + "cmd:pimd", + "cmd:ripd", + "cmd:ripngd", + "cmd:test_igmpv3_join", + "cmd:vtysh", + "cmd:watchquagga", + "cmd:zebra" + ] + }, + "libxp": { + "versions": { + "1.0.3-r0": 1509494902 + }, + "origin": "libxp", + "dependencies": [ + "so:libX11.so.6", + "so:libXau.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXp.so.6=6.2.0" + ] + }, + "spice-gtk-dev": { + "versions": { + "0.34-r1": 1510260978 + }, + "origin": "spice-gtk", + "dependencies": [ + "gtk+3.0-dev", + "celt051-dev", + "polkit-dev", + "libxrandr-dev", + "libressl-dev", + "cyrus-sasl-dev", + "gst-plugins-base-dev", + "libjpeg-turbo-dev", + "zlib-dev", + "acl-dev", + "lz4-dev", + "pc:glib-2.0", + "pc:gtk+-3.0>=3.12", + "pc:openssl", + "pc:pixman-1>=0.17.7", + "pc:spice-protocol", + "pkgconfig", + "spice-glib=0.34-r1", + "spice-gtk=0.34-r1" + ], + "provides": [ + "pc:spice-client-glib-2.0=0.34", + "pc:spice-client-gtk-3.0=0.34", + "pc:spice-controller=0.34" + ] + }, + "weechat-aspell": { + "versions": { + "1.9.1-r1": 1509530222 + }, + "origin": "weechat", + "dependencies": [ + "weechat", + "so:libaspell.so.15", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-file-slurp": { + "versions": { + "9999.19-r0": 1509474124 + }, + "origin": "perl-file-slurp" + }, + "xfburn": { + "versions": { + "0.5.4-r0": 1510074795 + }, + "origin": "xfburn", + "dependencies": [ + "desktop-file-utils", + "hicolor-icon-theme", + "so:libburn.so.4", + "so:libc.musl-x86_64.so.1", + "so:libexo-1.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libisofs.so.6", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7" + ], + "provides": [ + "cmd:xfburn" + ] + }, + "perl-dbi": { + "versions": { + "1.637-r0": 1509477478 + }, + "origin": "perl-dbi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dbilogstrip", + "cmd:dbiprof", + "cmd:dbiproxy" + ] + }, + "lxsession-doc": { + "versions": { + "0.5.3-r0": 1510073918 + }, + "origin": "lxsession" + }, + "gst-plugins-base": { + "versions": { + "1.12.3-r0": 1510070598 + }, + "origin": "gst-plugins-base", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcdda_interface.so.0", + "so:libcdda_paranoia.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-1.0.so.0", + "so:libgstnet-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libintl.so.8", + "so:libogg.so.0", + "so:libopus.so.0", + "so:liborc-0.4.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libtheoradec.so.1", + "so:libtheoraenc.so.1", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libgstallocators-1.0.so.0=0.1203.0", + "so:libgstapp-1.0.so.0=0.1203.0", + "so:libgstaudio-1.0.so.0=0.1203.0", + "so:libgstfft-1.0.so.0=0.1203.0", + "so:libgstpbutils-1.0.so.0=0.1203.0", + "so:libgstriff-1.0.so.0=0.1203.0", + "so:libgstrtp-1.0.so.0=0.1203.0", + "so:libgstrtsp-1.0.so.0=0.1203.0", + "so:libgstsdp-1.0.so.0=0.1203.0", + "so:libgsttag-1.0.so.0=0.1203.0", + "so:libgstvideo-1.0.so.0=0.1203.0", + "cmd:gst-device-monitor-1.0", + "cmd:gst-discoverer-1.0", + "cmd:gst-play-1.0" + ] + }, + "xextproto-doc": { + "versions": { + "7.3.0-r2": 1509461946 + }, + "origin": "xextproto" + }, + "perl-date-format": { + "versions": { + "2.30-r0": 1510564547 + }, + "origin": "perl-date-format", + "dependencies": [ + "perl" + ] + }, + "perl-sub-name": { + "versions": { + "0.21-r1": 1509474063 + }, + "origin": "perl-sub-name", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py3-vobject": { + "versions": { + "0.9.5-r2": 1510832515 + }, + "origin": "py-vobject", + "dependencies": [ + "python3", + "py3-icu", + "py3-dateutil" + ] + }, + "krb5-server-ldap": { + "versions": { + "1.15.4-r0": 1548491443 + }, + "origin": "krb5", + "dependencies": [ + "krb5-conf", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libgssrpc.so.4", + "so:libkadm5srv_mit.so.11", + "so:libkdb5.so.8", + "so:libkrb5.so.3", + "so:libkrb5support.so.0", + "so:libldap-2.4.so.2" + ], + "provides": [ + "so:libkdb_ldap.so.1=1.0" + ] + }, + "zeromq-dev": { + "versions": { + "4.2.5-r0": 1549279389, + "4.2.5-r1": 1563908216 + }, + "origin": "zeromq", + "dependencies": [ + "libzmq=4.2.5-r1", + "pkgconfig" + ], + "provides": [ + "pc:libzmq=4.2.5" + ] + }, + "lua5.3-feedparser": { + "versions": { + "0.71-r0": 1509485583 + }, + "origin": "lua-feedparser", + "dependencies": [ + "lua5.3-expat", + "lua-feedparser-common", + "lua-feedparser-common=0.71-r0" + ] + }, + "lua-sec": { + "versions": { + "0.6-r3": 1510260656 + }, + "origin": "lua-sec" + }, + "sdl2_ttf-dev": { + "versions": { + "2.0.14-r1": 1510310960 + }, + "origin": "sdl2_ttf", + "dependencies": [ + "freetype-dev", + "sdl2-dev", + "pc:sdl2>=2.0.0", + "pkgconfig", + "sdl2_ttf=2.0.14-r1" + ], + "provides": [ + "pc:SDL2_ttf=2.0.14" + ] + }, + "glade3-dev": { + "versions": { + "3.8.5-r4": 1510067963 + }, + "origin": "glade3", + "dependencies": [ + "glade3=3.8.5-r4", + "pc:gtk+-2.0>=2.14.0", + "pc:libxml-2.0>=2.4.0", + "pkgconfig" + ], + "provides": [ + "pc:gladeui-1.0=3.8.5" + ] + }, + "fltk": { + "versions": { + "1.3.4-r0": 1510072239 + }, + "origin": "fltk", + "dependencies": [ + "so:libGL.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXft.so.2", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libfltk.so.1.3=1.3", + "so:libfltk_forms.so.1.3=1.3", + "so:libfltk_gl.so.1.3=1.3", + "so:libfltk_images.so.1.3=1.3" + ] + }, + "ncftp-bookmarks": { + "versions": { + "3.2.6-r1": 1509472912 + }, + "origin": "ncftp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncftp.so.3", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:ncftpbookmarks" + ] + }, + "nfdump": { + "versions": { + "1.6.15-r0": 1509494473, + "1.6.15-r1": 1574265426 + }, + "origin": "nfdump", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnfdump-1.6.15.so=0", + "cmd:nfanon", + "cmd:nfcapd", + "cmd:nfdump", + "cmd:nfexpire", + "cmd:nfreplay" + ] + }, + "libxkbfile-dev": { + "versions": { + "1.0.9-r2": 1509473691 + }, + "origin": "libxkbfile", + "dependencies": [ + "libxkbfile=1.0.9-r2", + "pc:kbproto", + "pc:x11", + "pkgconfig" + ], + "provides": [ + "pc:xkbfile=1.0.9" + ] + }, + "p11-kit-trust": { + "versions": { + "0.23.2-r2": 1509465179 + }, + "origin": "p11-kit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtasn1.so.6" + ] + }, + "uwsgi-router_metrics": { + "versions": { + "2.0.17-r0": 1522154657 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "mousepad-lang": { + "versions": { + "0.4.0-r2": 1510073843 + }, + "origin": "mousepad", + "dependencies": [ + "desktop-file-utils" + ] + }, + "glibmm-dev": { + "versions": { + "2.50.1-r0": 1509472979 + }, + "origin": "glibmm", + "dependencies": [ + "libsigc++-dev", + "glibmm=2.50.1-r0", + "pc:gio-2.0", + "pc:gobject-2.0", + "pc:sigc++-2.0", + "pkgconfig" + ], + "provides": [ + "pc:giomm-2.4=2.50.1", + "pc:glibmm-2.4=2.50.1" + ] + }, + "iasl": { + "versions": { + "20170303-r0": 1510068816 + }, + "origin": "acpica", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:iasl" + ] + }, + "py2-redis": { + "versions": { + "2.10.5-r1": 1509552806 + }, + "origin": "py-redis", + "dependencies": [ + "python2" + ] + }, + "make-doc": { + "versions": { + "4.2.1-r0": 1509458174 + }, + "origin": "make" + }, + "perl-dbix-dbschema-doc": { + "versions": { + "0.39-r0": 1509477489 + }, + "origin": "perl-dbix-dbschema" + }, + "json-glib-doc": { + "versions": { + "1.2.8-r0": 1509480681 + }, + "origin": "json-glib" + }, + "spice-gtk": { + "versions": { + "0.34-r1": 1510260978 + }, + "origin": "spice-gtk", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libepoxy.so.0", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libspice-client-glib-2.0.so.8" + ], + "provides": [ + "so:libspice-client-gtk-3.0.so.5=5.0.0" + ] + }, + "parallel": { + "versions": { + "20171122-r0": 1511894075 + }, + "origin": "parallel", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:env_parallel", + "cmd:env_parallel.ash", + "cmd:env_parallel.bash", + "cmd:env_parallel.csh", + "cmd:env_parallel.dash", + "cmd:env_parallel.fish", + "cmd:env_parallel.ksh", + "cmd:env_parallel.pdksh", + "cmd:env_parallel.sh", + "cmd:env_parallel.tcsh", + "cmd:env_parallel.zsh", + "cmd:niceload", + "cmd:parallel", + "cmd:parcat", + "cmd:sem", + "cmd:sql" + ] + }, + "uwsgi-cache": { + "versions": { + "2.0.17-r0": 1522154653 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "liblockfile-doc": { + "versions": { + "1.14-r0": 1511990622 + }, + "origin": "liblockfile" + }, + "acf-chrony": { + "versions": { + "0.8.0-r2": 1510074744 + }, + "origin": "acf-chrony", + "dependencies": [ + "acf-core", + "lua-posix", + "chrony" + ] + }, + "squid-lang-hu": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "lua-cjson": { + "versions": { + "2.1.0-r7": 1509462475 + }, + "origin": "lua-cjson" + }, + "json-glib-lang": { + "versions": { + "1.2.8-r0": 1509480681 + }, + "origin": "json-glib" + }, + "apache2-utils": { + "versions": { + "2.4.39-r0": 1554306882, + "2.4.41-r0": 1566292328 + }, + "origin": "apache2", + "dependencies": [ + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "cmd:ab", + "cmd:checkgid", + "cmd:dbmmanage", + "cmd:htcacheclean", + "cmd:htdbm", + "cmd:htdigest", + "cmd:htpasswd", + "cmd:httxt2dbm", + "cmd:logresolve", + "cmd:rotatelogs" + ] + }, + "openjade-doc": { + "versions": { + "1.3.2-r5": 1509488728 + }, + "origin": "openjade" + }, + "xf86-video-apm-doc": { + "versions": { + "1.2.5-r8": 1510070494 + }, + "origin": "xf86-video-apm" + }, + "perl-convert-binhex-doc": { + "versions": { + "1.125-r0": 1509469886 + }, + "origin": "perl-convert-binhex" + }, + "libfprint": { + "versions": { + "0.7.0-r0": 1509486009 + }, + "origin": "libfprint", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libnss3.so", + "so:libpixman-1.so.0", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libfprint.so.0=0.0.0" + ] + }, + "zfs-vanilla-dev": { + "versions": { + "4.9.161-r0": 1551780615, + "4.9.182-r0": 1560866659 + }, + "origin": "zfs-vanilla", + "dependencies": [ + "glib-dev", + "e2fsprogs-dev", + "util-linux-dev", + "libtirpc-dev", + "linux-vanilla-dev=4.9.182-r0", + "spl-vanilla-dev" + ] + }, + "sipcalc-doc": { + "versions": { + "1.1.6-r0": 1509492398 + }, + "origin": "sipcalc" + }, + "xfce4-panel": { + "versions": { + "4.12.1-r1": 1510068175 + }, + "origin": "xfce4-panel", + "dependencies": [ + "hicolor-icon-theme", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libexo-1.so.0", + "so:libgarcon-1.so.0", + "so:libgarcon-gtk2-1.so.0", + "so:libgdk-3.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libwnck-1.so.22", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "so:libxfce4panel-1.0.so.4=4.0.0", + "so:libxfce4panel-2.0.so.4=4.0.0", + "cmd:xfce4-panel", + "cmd:xfce4-popup-applicationsmenu", + "cmd:xfce4-popup-directorymenu", + "cmd:xfce4-popup-windowmenu" + ] + }, + "postfix-sqlite": { + "versions": { + "3.2.4-r1": 1510285340 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:postfix-sqlite.so=0" + ] + }, + "perl-net-telnet": { + "versions": { + "3.04-r0": 1509477772 + }, + "origin": "perl-net-telnet" + }, + "perl-module-metadata-doc": { + "versions": { + "1.000033-r0": 1509468434 + }, + "origin": "perl-module-metadata" + }, + "qemu-system-ppc": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-ppc" + ] + }, + "perl-probe-perl-doc": { + "versions": { + "0.03-r0": 1509482463 + }, + "origin": "perl-probe-perl" + }, + "lua-penlight-doc": { + "versions": { + "1.5.4-r0": 1509485590 + }, + "origin": "lua-penlight" + }, + "audacious-lang": { + "versions": { + "3.9-r0": 1510072525 + }, + "origin": "audacious" + }, + "perl-db-doc": { + "versions": { + "0.55-r1": 1509477765 + }, + "origin": "perl-db" + }, + "kamailio-json": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libjson-c.so.2", + "so:libsrutils.so.1" + ] + }, + "rsnapshot-doc": { + "versions": { + "1.4.2-r0": 1509480273 + }, + "origin": "rsnapshot" + }, + "postfix-mysql": { + "versions": { + "3.2.4-r1": 1510285340 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ], + "provides": [ + "so:postfix-mysql.so=0" + ] + }, + "nagios-plugins-disk": { + "versions": { + "2.2.1-r3": 1510288494 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-git-svn": { + "versions": { + "2.15.3-r0": 1540401292, + "2.15.4-r0": 1576015189 + }, + "origin": "git", + "dependencies": [ + "git=2.15.4-r0" + ] + }, + "rsyslog": { + "versions": { + "8.31.0-r0": 1512031983, + "8.31.0-r1": 1571767020 + }, + "origin": "rsyslog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libestr.so.0", + "so:libfastjson.so.4", + "so:libgcrypt.so.20", + "so:liblogging-stdlog.so.0", + "so:libnet.so.1", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:rsyslogd" + ] + }, + "libatasmart-dev": { + "versions": { + "0.19-r1": 1509482228 + }, + "origin": "libatasmart", + "dependencies": [ + "eudev-dev", + "libatasmart=0.19-r1", + "pkgconfig" + ], + "provides": [ + "pc:libatasmart=0.19" + ] + }, + "xbitmaps": { + "versions": { + "1.1.1-r2": 1509490716 + }, + "origin": "xbitmaps", + "dependencies": [ + "util-macros" + ] + }, + "sdl2_mixer-dev": { + "versions": { + "2.0.2-r0": 1511859191 + }, + "origin": "sdl2_mixer", + "dependencies": [ + "pc:sdl2>=2.0.7", + "pkgconfig", + "sdl2_mixer=2.0.2-r0" + ], + "provides": [ + "pc:SDL2_mixer=2.0.2" + ] + }, + "orc": { + "versions": { + "0.4.27-r0": 1509469955 + }, + "origin": "orc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liborc-0.4.so.0=0.27.0", + "so:liborc-test-0.4.so.0=0.27.0" + ] + }, + "qemu-sparc32plus": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-sparc32plus" + ] + }, + "libnetfilter_log": { + "versions": { + "1.0.1-r2": 1509480369 + }, + "origin": "libnetfilter_log", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnfnetlink.so.0" + ], + "provides": [ + "so:libnetfilter_log.so.1=1.1.0", + "so:libnetfilter_log_libipulog.so.1=1.0.0" + ] + }, + "lua5.1-doc": { + "versions": { + "5.1.5-r3": 1509462457 + }, + "origin": "lua5.1" + }, + "libnice": { + "versions": { + "0.1.14-r2": 1510931344 + }, + "origin": "libnice", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libnice.so.10=10.7.0", + "cmd:sdp-example", + "cmd:simple-example", + "cmd:stunbdc", + "cmd:stund", + "cmd:threaded-example" + ] + }, + "libavc1394-dev": { + "versions": { + "0.5.4-r1": 1509489637 + }, + "origin": "libavc1394", + "dependencies": [ + "libraw1394-dev", + "libavc1394=0.5.4-r1", + "pc:libraw1394", + "pkgconfig" + ], + "provides": [ + "pc:libavc1394=0.5.4" + ] + }, + "re2c": { + "versions": { + "1.0.2-r0": 1509475772 + }, + "origin": "re2c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:re2c" + ] + }, + "c-ares": { + "versions": { + "1.13.0-r0": 1509461299 + }, + "origin": "c-ares", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcares.so.2=2.2.0" + ] + }, + "perl-html-quoted": { + "versions": { + "0.04-r0": 1511989791 + }, + "origin": "perl-html-quoted", + "dependencies": [ + "perl", + "perl-html-parser" + ] + }, + "vte-lang": { + "versions": { + "0.28.2-r13": 1510071902 + }, + "origin": "vte" + }, + "apache2-mod-wsgi-doc": { + "versions": { + "4.5.22-r0": 1511871607 + }, + "origin": "apache2-mod-wsgi" + }, + "xfontsel": { + "versions": { + "1.0.5-r1": 1509481103 + }, + "origin": "xfontsel", + "dependencies": [ + "so:libX11.so.6", + "so:libXaw.so.7", + "so:libXmu.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xfontsel" + ] + }, + "mosh-server": { + "versions": { + "1.3.2-r3": 1510846214 + }, + "origin": "mosh", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libprotobuf.so.14", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:mosh-server" + ] + }, + "gummiboot": { + "versions": { + "48.1-r0": 1509483314 + }, + "origin": "gummiboot", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:gummiboot" + ] + }, + "speex": { + "versions": { + "1.2.0-r0": 1509473248 + }, + "origin": "speex", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libspeex.so.1=1.5.1" + ] + }, + "perl-javascript-minifier": { + "versions": { + "1.14-r0": 1509482423 + }, + "origin": "perl-javascript-minifier" + }, + "tinc-doc": { + "versions": { + "1.0.35-r0": 1549268950 + }, + "origin": "tinc" + }, + "scrot": { + "versions": { + "0.8.13-r0": 1509481275 + }, + "origin": "scrot", + "dependencies": [ + "so:libImlib2.so.1", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgiblib.so.1" + ], + "provides": [ + "cmd:scrot" + ] + }, + "font-dec-misc": { + "versions": { + "1.0.3-r0": 1509491348 + }, + "origin": "font-dec-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "kamailio-tls": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4" + ] + }, + "atk-dev": { + "versions": { + "2.26.1-r1": 1509464672 + }, + "origin": "atk", + "dependencies": [ + "atk=2.26.1-r1", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:atk=2.26.1" + ] + }, + "atf-dev": { + "versions": { + "0.21-r1": 1509459730 + }, + "origin": "atf", + "dependencies": [ + "atf=0.21-r1", + "pkgconfig" + ], + "provides": [ + "pc:atf-c++=0.21", + "pc:atf-c=0.21", + "pc:atf-sh=0.21" + ] + }, + "lz4": { + "versions": { + "1.8.0-r1": 1509461406 + }, + "origin": "lz4", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:lz4", + "cmd:lz4c", + "cmd:lz4cat", + "cmd:unlz4" + ] + }, + "freeradius-eap": { + "versions": { + "3.0.15-r4": 1556202797, + "3.0.15-r5": 1566310604 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.15-r5", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libfreeradius-eap.so", + "so:libfreeradius-radius.so", + "so:libfreeradius-server.so", + "so:libssl.so.44", + "so:libtalloc.so.2" + ], + "provides": [ + "freeradius3-eap=3.0.15-r5", + "so:rlm_eap.so=0", + "so:rlm_eap_fast.so=0", + "so:rlm_eap_gtc.so=0", + "so:rlm_eap_leap.so=0", + "so:rlm_eap_md5.so=0", + "so:rlm_eap_mschapv2.so=0", + "so:rlm_eap_peap.so=0", + "so:rlm_eap_pwd.so=0", + "so:rlm_eap_sim.so=0", + "so:rlm_eap_tls.so=0", + "so:rlm_eap_ttls.so=0", + "cmd:radeapclient" + ] + }, + "glib-networking": { + "versions": { + "2.54.1-r0": 1509477723 + }, + "origin": "glib-networking", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libp11-kit.so.0", + "so:libproxy.so.1" + ] + }, + "py3-psycopg2": { + "versions": { + "2.7.3.2-r0": 1510619592 + }, + "origin": "py-psycopg2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5", + "so:libpython3.6m.so.1.0" + ] + }, + "pciutils-libs": { + "versions": { + "3.5.6-r0": 1511165900 + }, + "origin": "pciutils", + "dependencies": [ + "hwdata-pci", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpci.so.3=3.5.6" + ] + }, + "lua-doc": { + "versions": { + "5.1.5-r4": 1509471877 + }, + "origin": "lua", + "dependencies": [ + "lua5.2-doc" + ] + }, + "iwlwifi-6000-ucode-doc": { + "versions": { + "9.221.4.1-r0": 1509495918 + }, + "origin": "iwlwifi-6000-ucode" + }, + "font-adobe-100dpi": { + "versions": { + "1.0.3-r0": 1509470417 + }, + "origin": "font-adobe-100dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "libfastjson-dev": { + "versions": { + "0.99.7-r0": 1509486266 + }, + "origin": "libfastjson", + "dependencies": [ + "libfastjson=0.99.7-r0", + "pkgconfig" + ], + "provides": [ + "pc:libfastjson=0.99.7" + ] + }, + "perl-text-csv": { + "versions": { + "1.20-r1": 1509488639 + }, + "origin": "perl-text-csv", + "dependencies": [ + "perl" + ] + }, + "lua-graphviz": { + "versions": { + "2.40.1-r0": 1510066918 + }, + "origin": "graphviz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcgraph.so.6", + "so:libgvc.so.6", + "so:liblua-5.2.so.0" + ] + }, + "libxcomposite": { + "versions": { + "0.4.4-r1": 1509465984 + }, + "origin": "libxcomposite", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXcomposite.so.1=1.0.0" + ] + }, + "haproxy-doc": { + "versions": { + "1.7.9-r1": 1510261284 + }, + "origin": "haproxy" + }, + "libgc++": { + "versions": { + "7.6.0-r1": 1509467221 + }, + "origin": "gc", + "dependencies": [ + "so:libgc.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgccpp.so.1=1.0.3" + ] + }, + "avahi-compat-howl": { + "versions": { + "0.6.32-r4": 1509465448, + "0.6.32-r5": 1563345619 + }, + "origin": "avahi", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhowl.so.0=0.0.0" + ] + }, + "libgnat": { + "versions": { + "6.4.0-r5": 1509458080 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libgnarl-6.so=0", + "so:libgnat-6.so=0" + ] + }, + "xfconf": { + "versions": { + "4.12.1-r2": 1510067934 + }, + "origin": "xfconf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libxfce4util.so.7" + ], + "provides": [ + "so:libxfconf-0.so.2=2.0.0", + "cmd:xfconf-query" + ] + }, + "uwsgi-stats_pusher_socket": { + "versions": { + "2.0.17-r0": 1522154659 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "kbd-legacy": { + "versions": { + "2.0.4-r2": 1510922529 + }, + "origin": "kbd" + }, + "perl-params-util": { + "versions": { + "1.07-r5": 1509473959 + }, + "origin": "perl-params-util", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-test-without-module": { + "versions": { + "0.20-r0": 1509481285 + }, + "origin": "perl-test-without-module" + }, + "glu-dev": { + "versions": { + "9.0.0-r3": 1510067878 + }, + "origin": "glu", + "dependencies": [ + "mesa-dev", + "glu=9.0.0-r3", + "pc:gl", + "pkgconfig" + ], + "provides": [ + "pc:glu=9.0.0" + ] + }, + "the_silver_searcher-doc": { + "versions": { + "2.1.0-r2": 1510831170 + }, + "origin": "the_silver_searcher" + }, + "cdparanoia-doc": { + "versions": { + "10.2-r7": 1509471029 + }, + "origin": "cdparanoia" + }, + "abiword-plugin-hrtext": { + "versions": { + "3.0.2-r1": 1510073361 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "sfdisk": { + "versions": { + "2.31.1-r0": 1541506294 + }, + "origin": "util-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfdisk.so.1", + "so:libncursesw.so.6", + "so:libsmartcols.so.1" + ], + "provides": [ + "cmd:sfdisk" + ] + }, + "samba-common-tools": { + "versions": { + "4.7.6-r3": 1555491786 + }, + "origin": "samba", + "dependencies": [ + "so:libCHARSET3-samba4.so", + "so:libaddns-samba4.so", + "so:libads-samba4.so", + "so:libauth-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-smb-common-samba4.so", + "so:libcli-spoolss-samba4.so", + "so:libcliauth-samba4.so", + "so:libcom_err.so.2", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libflag-mapping-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libgssapi-samba4.so.2", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldap-2.4.so.2", + "so:liblibcli-lsa3-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmsrpc3-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libnetapi.so.0", + "so:libpopt-samba3-samba4.so", + "so:libpopt.so.0", + "so:libprinting-migrate-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-modules-samba4.so", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb.so.0", + "so:libsecrets3-samba4.so", + "so:libserver-id-db-samba4.so", + "so:libserver-role-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-base-samba4.so", + "so:libsmbd-shim-samba4.so", + "so:libsmbldap.so.2", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtime-basic-samba4.so", + "so:libtrusts-util-samba4.so", + "so:libutil-reg-samba4.so", + "so:libutil-tdb-samba4.so", + "so:libwbclient.so.0" + ], + "provides": [ + "so:libgpo-samba4.so=0", + "so:libnet-keytab-samba4.so=0", + "cmd:net", + "cmd:pdbedit", + "cmd:profiles", + "cmd:smbcontrol", + "cmd:smbpasswd", + "cmd:testparm" + ] + }, + "perl-test2-suite-doc": { + "versions": { + "0.000083-r0": 1510564541 + }, + "origin": "perl-test2-suite" + }, + "collectd-redis": { + "versions": { + "5.7.2-r0": 1510069651 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.13" + ] + }, + "opensmtpd-doc": { + "versions": { + "6.0.2p1-r7": 1510259021 + }, + "origin": "opensmtpd" + }, + "glew-dev": { + "versions": { + "2.1.0-r0": 1510068629 + }, + "origin": "glew", + "dependencies": [ + "libxmu-dev", + "libxi-dev", + "mesa-dev", + "glu-dev", + "glew=2.1.0-r0", + "pc:glu", + "pkgconfig" + ], + "provides": [ + "pc:glew=2.1.0" + ] + }, + "lxdm-lang": { + "versions": { + "0.5.3-r1": 1510070032 + }, + "origin": "lxdm", + "dependencies": [ + "bash" + ] + }, + "sems-pin_collect": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "sems-ivr" + ] + }, + "qemu-system-or1k": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-or1k" + ] + }, + "libebml-dev": { + "versions": { + "1.3.5-r0": 1509479829, + "1.3.5-r1": 1564417272 + }, + "origin": "libebml", + "dependencies": [ + "libebml=1.3.5-r1", + "pkgconfig" + ], + "provides": [ + "pc:libebml=1.3.5" + ] + }, + "libressl": { + "versions": { + "2.6.5-r0": 1529043917 + }, + "origin": "libressl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libtls.so.16" + ], + "provides": [ + "cmd:ocspcheck", + "cmd:openssl" + ] + }, + "ffmpeg-doc": { + "versions": { + "3.4-r1": 1510072147 + }, + "origin": "ffmpeg" + }, + "task-doc": { + "versions": { + "2.5.1-r0": 1509491754 + }, + "origin": "task" + }, + "truecrypt": { + "versions": { + "7.1a-r3": 1510928472 + }, + "origin": "truecrypt", + "dependencies": [ + "device-mapper", + "so:libc.musl-x86_64.so.1", + "so:libfuse.so.2", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libwx_baseu-2.8.so.0", + "so:libwx_gtk2u_adv-2.8.so.0", + "so:libwx_gtk2u_core-2.8.so.0" + ], + "provides": [ + "cmd:truecrypt" + ] + }, + "perl-test-file-sharedir": { + "versions": { + "1.001002-r0": 1510855690 + }, + "origin": "perl-test-file-sharedir", + "dependencies": [ + "perl-class-tiny", + "perl-file-sharedir", + "perl-file-copy-recursive", + "perl-path-tiny", + "perl-scope-guard" + ] + }, + "lua5.1-md5": { + "versions": { + "1.2-r3": 1509468352 + }, + "origin": "lua-md5", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "terminus-font-doc": { + "versions": { + "4.46-r0": 1509491616 + }, + "origin": "terminus-font" + }, + "squid-lang-sv": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865670 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "fcgi++": { + "versions": { + "2.4.0-r8": 1510330998 + }, + "origin": "fcgi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfcgi.so.0", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libfcgi++.so.0=0.0.0" + ] + }, + "ngrep-dbg": { + "versions": { + "1.45-r4": 1509489071 + }, + "origin": "ngrep" + }, + "boost-wserialization": { + "versions": { + "1.62.0-r5": 1509465882 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_serialization-mt.so.1.62.0", + "so:libboost_serialization.so.1.62.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_wserialization-mt.so.1.62.0=1.62.0", + "so:libboost_wserialization.so.1.62.0=1.62.0" + ] + }, + "squid-lang-ja": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "xf86-video-openchrome-doc": { + "versions": { + "0.6.0-r1": 1510073043 + }, + "origin": "xf86-video-openchrome" + }, + "font-screen-cyrillic": { + "versions": { + "1.0.4-r0": 1509495009 + }, + "origin": "font-screen-cyrillic", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "freeswitch-pgsql": { + "versions": { + "1.6.19-r0": 1510585357 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreeswitch.so.1", + "so:libpq.so.5" + ] + }, + "arpwatch-ethercodes": { + "versions": { + "2.1a15-r16": 1510075585 + }, + "origin": "arpwatch" + }, + "libnetfilter_cthelper": { + "versions": { + "1.0.0-r0": 1509469239 + }, + "origin": "libnetfilter_cthelper", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0" + ], + "provides": [ + "so:libnetfilter_cthelper.so.0=0.0.0" + ] + }, + "ferm": { + "versions": { + "2.4.1-r0": 1509481745 + }, + "origin": "ferm", + "dependencies": [ + "perl", + "iptables" + ], + "provides": [ + "cmd:ferm", + "cmd:import-ferm" + ] + }, + "cvechecker-doc": { + "versions": { + "3.8-r1": 1510072447 + }, + "origin": "cvechecker" + }, + "libtheora-doc": { + "versions": { + "1.1.1-r13": 1510068272 + }, + "origin": "libtheora" + }, + "perl-http-daemon": { + "versions": { + "6.01-r1": 1509464384 + }, + "origin": "perl-http-daemon", + "dependencies": [ + "perl", + "perl-http-date", + "perl-http-message" + ] + }, + "py2-bcrypt": { + "versions": { + "3.1.4-r0": 1509474481 + }, + "origin": "py-bcrypt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "py-redis": { + "versions": { + "2.10.5-r1": 1509552807 + }, + "origin": "py-redis" + }, + "alsa-lib-dev": { + "versions": { + "1.1.4.1-r2": 1509468674 + }, + "origin": "alsa-lib", + "dependencies": [ + "alsa-lib=1.1.4.1-r2", + "pkgconfig" + ], + "provides": [ + "pc:alsa=1.1.4.1" + ] + }, + "mercurial-vim": { + "versions": { + "4.5.2-r0": 1532937170, + "4.5.2-r1": 1563792023 + }, + "origin": "mercurial" + }, + "py2-six": { + "versions": { + "1.11.0-r0": 1509465546 + }, + "origin": "py-six", + "dependencies": [ + "python2" + ] + }, + "perl-date-extract": { + "versions": { + "0.06-r1": 1510861954 + }, + "origin": "perl-date-extract", + "dependencies": [ + "perl-datetime-format-natural", + "perl-class-data-inheritable", + "perl-test-mocktime" + ] + }, + "harfbuzz": { + "versions": { + "1.6.3-r0": 1509464873 + }, + "origin": "harfbuzz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libglib-2.0.so.0", + "so:libgraphite2.so.3" + ], + "provides": [ + "so:libharfbuzz.so.0=0.10600.3" + ] + }, + "rgb-doc": { + "versions": { + "1.0.6-r0": 1509486278 + }, + "origin": "rgb" + }, + "perl-module-util-doc": { + "versions": { + "1.09-r0": 1509485616 + }, + "origin": "perl-module-util" + }, + "freeswitch-sounds-music-8000": { + "versions": { + "1.0.8-r1": 1509490954 + }, + "origin": "freeswitch-sounds-music-8000" + }, + "findutils": { + "versions": { + "4.6.0-r0": 1509481488 + }, + "origin": "findutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:find", + "cmd:locate", + "cmd:updatedb", + "cmd:xargs" + ] + }, + "setxkbmap": { + "versions": { + "1.3.1-r0": 1509496554 + }, + "origin": "setxkbmap", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libxkbfile.so.1" + ], + "provides": [ + "cmd:setxkbmap" + ] + }, + "pwgen-doc": { + "versions": { + "2.08-r0": 1509491100 + }, + "origin": "pwgen" + }, + "liboil-doc": { + "versions": { + "0.3.17-r5": 1509471015 + }, + "origin": "liboil" + }, + "cairo": { + "versions": { + "1.14.10-r0": 1509464621 + }, + "origin": "cairo", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libxcb-render.so.0", + "so:libxcb-shm.so.0", + "so:libxcb.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libcairo-script-interpreter.so.2=2.11400.10", + "so:libcairo.so.2=2.11400.10" + ] + }, + "acf-tinydns": { + "versions": { + "0.11.0-r2": 1510075935 + }, + "origin": "acf-tinydns", + "dependencies": [ + "acf-core", + "tinydns" + ] + }, + "less": { + "versions": { + "520-r0": 1509496059 + }, + "origin": "less", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:less", + "cmd:lessecho", + "cmd:lesskey" + ] + }, + "email2trac": { + "versions": { + "2.5.0-r1": 1509492118 + }, + "origin": "email2trac", + "dependencies": [ + "trac", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:delete_spam", + "cmd:email2trac", + "cmd:run_email2trac" + ] + }, + "spamassassin-compiler": { + "versions": { + "3.4.1-r8": 1509517412 + }, + "origin": "spamassassin", + "dependencies": [ + "re2c", + "gcc", + "perl-dev", + "perl-mail-spamassassin" + ], + "provides": [ + "cmd:sa-compile" + ] + }, + "alpine-conf": { + "versions": { + "3.7.0-r1": 1520242790 + }, + "origin": "alpine-conf", + "dependencies": [ + "openrc>0.13", + "busybox>=1.26.1-r3", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:lbu", + "cmd:lbu_commit", + "cmd:lbu_exclude", + "cmd:lbu_include", + "cmd:lbu_status", + "cmd:lbu_update", + "cmd:setup-acf", + "cmd:setup-alpine", + "cmd:setup-apkcache", + "cmd:setup-apkrepos", + "cmd:setup-bootable", + "cmd:setup-disk", + "cmd:setup-dns", + "cmd:setup-gparted-desktop", + "cmd:setup-hostname", + "cmd:setup-interfaces", + "cmd:setup-keymap", + "cmd:setup-lbu", + "cmd:setup-mta", + "cmd:setup-ntp", + "cmd:setup-proxy", + "cmd:setup-sshd", + "cmd:setup-timezone", + "cmd:setup-xen-dom0", + "cmd:setup-xorg-base", + "cmd:uniso", + "cmd:update-conf", + "cmd:update-kernel" + ] + }, + "perl-dbd-odbc": { + "versions": { + "1.56-r1": 1509493770 + }, + "origin": "perl-dbd-odbc", + "dependencies": [ + "perl", + "perl-dbi", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ] + }, + "finch": { + "versions": { + "2.12.0-r2": 1510069756 + }, + "origin": "pidgin", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libpanelw.so.6", + "so:libpurple.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgnt.so.0=0.8.10", + "cmd:finch" + ] + }, + "xdm-doc": { + "versions": { + "1.1.11-r5": 1509480595 + }, + "origin": "xdm" + }, + "uwsgi-logfile": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "boost-prg_exec_monitor": { + "versions": { + "1.62.0-r5": 1509465876 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_prg_exec_monitor-mt.so.1.62.0=1.62.0", + "so:libboost_prg_exec_monitor.so.1.62.0=1.62.0" + ] + }, + "abiword-plugin-paint": { + "versions": { + "3.0.2-r1": 1510073366 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "libxxf86vm-doc": { + "versions": { + "1.1.4-r1": 1509466237 + }, + "origin": "libxxf86vm" + }, + "lua5.2-inspect": { + "versions": { + "3.1.0-r1": 1509491026 + }, + "origin": "lua-inspect", + "dependencies": [ + "lua5.2" + ] + }, + "perl-cgi-doc": { + "versions": { + "4.36-r0": 1509470550 + }, + "origin": "perl-cgi" + }, + "openssh-server-pam": { + "versions": { + "7.5_p1-r10": 1551712288 + }, + "origin": "openssh", + "dependencies": [ + "openssh-keygen", + "openssh-server-common", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpam.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:sshd" + ] + }, + "usbredir-server": { + "versions": { + "0.7.1-r0": 1509707469 + }, + "origin": "usbredir", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirhost.so.1" + ], + "provides": [ + "cmd:usbredirserver" + ] + }, + "perl-mozilla-ca-doc": { + "versions": { + "20160104-r0": 1509494407 + }, + "origin": "perl-mozilla-ca" + }, + "nagios-plugins-http": { + "versions": { + "2.2.1-r3": 1510288495 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "tftp-hpa-doc": { + "versions": { + "5.2-r2": 1509469060 + }, + "origin": "tftp-hpa" + }, + "mesa-dri-intel": { + "versions": { + "17.2.4-r1": 1510741948 + }, + "origin": "mesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_intel.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "lockfile-progs": { + "versions": { + "0.1.17-r0": 1509468625 + }, + "origin": "lockfile-progs", + "dependencies": [ + "liblockfile", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:lockfile-check", + "cmd:lockfile-create", + "cmd:lockfile-remove", + "cmd:lockfile-touch", + "cmd:mail-lock", + "cmd:mail-touchlock", + "cmd:mail-unlock" + ] + }, + "abi-compliance-checker": { + "versions": { + "2.2-r0": 1510075237 + }, + "origin": "abi-compliance-checker", + "dependencies": [ + "perl", + "build-base" + ], + "provides": [ + "cmd:abi-compliance-checker" + ] + }, + "pingu": { + "versions": { + "1.5-r1": 1509474488 + }, + "origin": "pingu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libev.so.4" + ], + "provides": [ + "cmd:pingu", + "cmd:pinguctl" + ] + }, + "lua5.2-b64": { + "versions": { + "0.1-r1": 1509475856 + }, + "origin": "lua5.2-b64", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "talloc": { + "versions": { + "2.1.10-r0": 1509466375 + }, + "origin": "talloc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libtalloc.so.2=2.1.10" + ] + }, + "aria2-bash-completion": { + "versions": { + "1.33.1-r1": 1548941587 + }, + "origin": "aria2", + "dependencies": [ + "ca-certificates" + ] + }, + "lua5.2-ossl": { + "versions": { + "20171028-r1": 1510260842 + }, + "origin": "lua-ossl", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "uwsgi-router_basicauth": { + "versions": { + "2.0.17-r0": 1522154657 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-gv": { + "versions": { + "2.40.1-r0": 1510066918 + }, + "origin": "graphviz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcgraph.so.6", + "so:libgcc_s.so.1", + "so:libgvc.so.6", + "so:libstdc++.so.6" + ] + }, + "lua5.1-inspect": { + "versions": { + "3.1.0-r1": 1509491025 + }, + "origin": "lua-inspect", + "dependencies": [ + "lua5.1" + ] + }, + "wayland-libs-cursor": { + "versions": { + "1.14.0-r2": 1510066936 + }, + "origin": "wayland", + "dependencies": [ + "wayland-libs-client", + "wayland-libs-cursor", + "wayland-libs-server", + "so:libc.musl-x86_64.so.1", + "so:libwayland-client.so.0" + ], + "provides": [ + "so:libwayland-cursor.so.0=0.0.0" + ] + }, + "opusfile": { + "versions": { + "0.10-r0": 1512032061 + }, + "origin": "opusfile", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libogg.so.0", + "so:libopus.so.0", + "so:libssl.so.44" + ], + "provides": [ + "so:libopusfile.so.0=0.4.3", + "so:libopusurl.so.0=0.4.3" + ] + }, + "opusfile-doc": { + "versions": { + "0.10-r0": 1512032061 + }, + "origin": "opusfile" + }, + "gmp-doc": { + "versions": { + "6.1.2-r1": 1509456916 + }, + "origin": "gmp" + }, + "perl-io-tty": { + "versions": { + "1.12-r4": 1509483928 + }, + "origin": "perl-io-tty", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "git-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503663 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "libgnomekbd-lang": { + "versions": { + "3.6.0-r0": 1510933018 + }, + "origin": "libgnomekbd" + }, + "cups-filters-libs": { + "versions": { + "1.17.9-r0": 1510075884 + }, + "origin": "cups-filters", + "dependencies": [ + "poppler-utils", + "bc", + "ttf-freefont", + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2", + "so:libdbus-1.so.3", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libtiff.so.5" + ], + "provides": [ + "so:libcupsfilters.so.1=1.0.0", + "so:libfontembed.so.1=1.0.0" + ] + }, + "xfce4-xkb-plugin-lang": { + "versions": { + "0.7.1-r0": 1510074126 + }, + "origin": "xfce4-xkb-plugin" + }, + "libltdl": { + "versions": { + "2.4.6-r4": 1509456826 + }, + "origin": "libtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libltdl.so.7=7.3.1" + ] + }, + "dialog-doc": { + "versions": { + "1.3.20170509-r0": 1509468657 + }, + "origin": "dialog" + }, + "sdl_mixer-dev": { + "versions": { + "1.2.12-r1": 1510075315 + }, + "origin": "sdl_mixer", + "dependencies": [ + "pc:sdl>=1.2.10", + "pkgconfig", + "sdl_mixer=1.2.12-r1" + ], + "provides": [ + "pc:SDL_mixer=1.2.12" + ] + }, + "libmaa": { + "versions": { + "1.3.2-r0": 1509489138 + }, + "origin": "libmaa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmaa.so.3=3.0.0" + ] + }, + "gtk+2.0-lang": { + "versions": { + "2.24.31-r0": 1510066781 + }, + "origin": "gtk+2.0", + "dependencies": [ + "shared-mime-info", + "gtk-update-icon-cache" + ] + }, + "imake": { + "versions": { + "1.0.7-r1": 1509476556 + }, + "origin": "imake", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ccmakedep", + "cmd:cleanlinks", + "cmd:imake", + "cmd:makeg", + "cmd:mergelib", + "cmd:mkdirhier", + "cmd:mkhtmlindex", + "cmd:revpath", + "cmd:xmkmf" + ] + }, + "perl-proc-wait3-doc": { + "versions": { + "0.05-r1": 1509482406 + }, + "origin": "perl-proc-wait3" + }, + "libxxf86dga": { + "versions": { + "1.1.4-r1": 1509473787 + }, + "origin": "libxxf86dga", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXxf86dga.so.1=1.0.0" + ] + }, + "tftp-hpa": { + "versions": { + "5.2-r2": 1509469065 + }, + "origin": "tftp-hpa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:in.tftpd", + "cmd:tftp" + ] + }, + "xfce4-xkb-plugin": { + "versions": { + "0.7.1-r0": 1510074131 + }, + "origin": "xfce4-xkb-plugin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgarcon-1.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:librsvg-2.so.2", + "so:libwnck-1.so.22", + "so:libxfce4panel-1.0.so.4", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxklavier.so.16" + ] + }, + "bind-tools": { + "versions": { + "9.11.6_p1-r1": 1556872768, + "9.11.8-r0": 1561323229 + }, + "origin": "bind", + "dependencies": [ + "so:libbind9.so.161", + "so:libc.musl-x86_64.so.1", + "so:libdns.so.1106", + "so:libirs.so.161", + "so:libisc.so.1100", + "so:libisccfg.so.163", + "so:liblwres.so.161" + ], + "provides": [ + "cmd:delv", + "cmd:dig", + "cmd:dnssec-dsfromkey", + "cmd:dnssec-importkey", + "cmd:dnssec-keyfromlabel", + "cmd:dnssec-keygen", + "cmd:dnssec-revoke", + "cmd:dnssec-settime", + "cmd:dnssec-signzone", + "cmd:dnssec-verify", + "cmd:host", + "cmd:nslookup", + "cmd:nsupdate" + ] + }, + "igmpproxy": { + "versions": { + "0.1-r5": 1509489518 + }, + "origin": "igmpproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:igmpproxy" + ] + }, + "libevdev": { + "versions": { + "1.5.7-r1": 1509475753 + }, + "origin": "libevdev", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libevdev.so.2=2.1.19", + "cmd:libevdev-tweak-device", + "cmd:mouse-dpi-tool", + "cmd:touchpad-edge-detector" + ] + }, + "jemalloc-dev": { + "versions": { + "5.0.1-r0": 1509482444 + }, + "origin": "jemalloc", + "dependencies": [ + "jemalloc=5.0.1-r0", + "pkgconfig" + ] + }, + "libnih-doc": { + "versions": { + "1.0.3-r4": 1509473673 + }, + "origin": "libnih" + }, + "apg-doc": { + "versions": { + "2.2.3-r3": 1509489481 + }, + "origin": "apg" + }, + "uriparser": { + "versions": { + "0.8.4-r0": 1509494964 + }, + "origin": "uriparser", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liburiparser.so.1=1.0.20", + "cmd:uriparse" + ] + }, + "lua5.1-augeas": { + "versions": { + "0.1.2-r3": 1509473119 + }, + "origin": "lua-augeas", + "dependencies": [ + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1" + ] + }, + "libdvbpsi": { + "versions": { + "1.3.1-r0": 1511168477 + }, + "origin": "libdvbpsi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdvbpsi.so.10=10.0.0" + ] + }, + "uwsgi-dummy": { + "versions": { + "2.0.17-r0": 1522154654 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "taskd-pki": { + "versions": { + "1.1.0-r4": 1509491727 + }, + "origin": "taskd", + "dependencies": [ + "taskd", + "gnutls-utils" + ] + }, + "ncurses-terminfo": { + "versions": { + "6.0_p20171125-r1": 1534862982 + }, + "origin": "ncurses", + "dependencies": [ + "ncurses-terminfo-base", + "ncurses-terminfo-base=6.0_p20171125-r1" + ] + }, + "netcat-openbsd": { + "versions": { + "1.130-r1": 1509481636 + }, + "origin": "netcat-openbsd", + "dependencies": [ + "so:libbsd.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:nc" + ] + }, + "krb5-dev": { + "versions": { + "1.15.4-r0": 1548491443 + }, + "origin": "krb5", + "dependencies": [ + "e2fsprogs-dev", + "krb5-libs=1.15.4-r0", + "krb5-server-ldap=1.15.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:gssrpc=1.15.4", + "pc:kadm-client=1.15.4", + "pc:kadm-server=1.15.4", + "pc:kdb=1.15.4", + "pc:krb5-gssapi=1.15.4", + "pc:krb5=1.15.4", + "pc:mit-krb5-gssapi=1.15.4", + "pc:mit-krb5=1.15.4", + "cmd:krb5-config" + ] + }, + "pangomm": { + "versions": { + "2.40.1-r0": 1509473003 + }, + "origin": "pangomm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairomm-1.0.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libglibmm-2.4.so.1", + "so:libgobject-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libpangomm-1.4.so.1=1.0.30" + ] + }, + "nagios-plugins-ssh": { + "versions": { + "2.2.1-r3": 1510288498 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "zfs-scripts": { + "versions": { + "0.7.3-r0": 1509490074 + }, + "origin": "zfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libzfs.so.2" + ] + }, + "perl-file-slurp-doc": { + "versions": { + "9999.19-r0": 1509474123 + }, + "origin": "perl-file-slurp" + }, + "pmacct": { + "versions": { + "1.6.2-r0": 1509494257 + }, + "origin": "pmacct", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjansson.so.4", + "so:libmysqlclient.so.18", + "so:libpcap.so.1", + "so:libpq.so.5", + "so:libsqlite3.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:nfacctd", + "cmd:pmacct", + "cmd:pmacctd", + "cmd:pmbgpd", + "cmd:pmbmpd", + "cmd:pmtelemetryd", + "cmd:sfacctd" + ] + }, + "qemu-gtk": { + "versions": { + "2.10.1-r3": 1519746244 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libX11.so.6", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libsnappy.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-x86_64-gtk" + ] + }, + "umurmur-doc": { + "versions": { + "0.2.17-r1": 1510846249 + }, + "origin": "umurmur" + }, + "byacc": { + "versions": { + "20170709-r0": 1510310594 + }, + "origin": "byacc", + "dependencies": [ + "!bison", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:yacc" + ] + }, + "qemu-system-mips": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-mips" + ] + }, + "libmaxminddb": { + "versions": { + "1.3.1-r0": 1511893707 + }, + "origin": "libmaxminddb", + "dependencies": [ + "curl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmaxminddb.so.0=0.0.7", + "cmd:mmdblookup" + ] + }, + "xf86driproto": { + "versions": { + "2.1.1-r3": 1509473931 + }, + "origin": "xf86driproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xf86driproto=2.1.1" + ] + }, + "qemu-ppc": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-ppc" + ] + }, + "openldap-back-sock": { + "versions": { + "2.4.45-r3": 1510258133, + "2.4.46-r0": 1565073939, + "2.4.48-r0": 1566900516 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "perl-test-tcp": { + "versions": { + "2.14-r0": 1509481787 + }, + "origin": "perl-test-tcp", + "dependencies": [ + "perl-test-sharedfork" + ] + }, + "ed": { + "versions": { + "1.14.2-r2": 1509465946 + }, + "origin": "ed", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ed", + "cmd:red" + ] + }, + "perl-server-starter-doc": { + "versions": { + "0.15-r1": 1510564664 + }, + "origin": "perl-server-starter" + }, + "perl-eval-closure": { + "versions": { + "0.14-r0": 1509481599 + }, + "origin": "perl-eval-closure", + "dependencies": [ + "perl-try-tiny" + ] + }, + "libmp3splt": { + "versions": { + "0.9.2-r0": 1509475939 + }, + "origin": "libmp3splt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libid3tag.so.0", + "so:libltdl.so.7", + "so:libmad.so.0", + "so:libogg.so.0", + "so:libpcre.so.1", + "so:libvorbis.so.0", + "so:libvorbisfile.so.3" + ], + "provides": [ + "so:libmp3splt.so.0=0.0.9" + ] + }, + "libhistory": { + "versions": { + "7.0.003-r0": 1509456789 + }, + "origin": "readline", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhistory.so.7=7.0" + ] + }, + "tarsnap-doc": { + "versions": { + "1.0.39-r2": 1510258955 + }, + "origin": "tarsnap" + }, + "libpaper": { + "versions": { + "1.1.24-r3": 1509465362 + }, + "origin": "libpaper", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpaper.so.1=1.1.2", + "cmd:paperconf", + "cmd:paperconfig" + ] + }, + "glproto": { + "versions": { + "1.4.17-r2": 1509466241 + }, + "origin": "glproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:glproto=1.4.17" + ] + }, + "cairomm-doc": { + "versions": { + "1.12.2-r0": 1509472990 + }, + "origin": "cairomm" + }, + "perl-cps": { + "versions": { + "0.18-r0": 1509477800 + }, + "origin": "perl-cps", + "dependencies": [ + "perl-future" + ] + }, + "ncurses-libs": { + "versions": { + "6.0_p20171125-r1": 1534862983 + }, + "origin": "ncurses", + "dependencies": [ + "ncurses-terminfo-base", + "ncurses-terminfo=6.0_p20171125-r1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "ncurses-widec-libs=6.0_p20171125-r1", + "so:libformw.so.6=6.0", + "so:libmenuw.so.6=6.0", + "so:libncursesw.so.6=6.0", + "so:libpanelw.so.6=6.0" + ] + }, + "perl-module-install": { + "versions": { + "1.18-r0": 1510352967 + }, + "origin": "perl-module-install" + }, + "openrc-dev": { + "versions": { + "0.24.1-r4": 1511887834 + }, + "origin": "openrc", + "dependencies": [ + "openrc=0.24.1-r4", + "pkgconfig" + ], + "provides": [ + "pc:einfo=0.24.1", + "pc:openrc=0.24.1" + ] + }, + "iw-doc": { + "versions": { + "4.9-r0": 1509494921 + }, + "origin": "iw" + }, + "libcurl": { + "versions": { + "7.61.1-r2": 1551780214, + "7.61.1-r3": 1568722567 + }, + "origin": "curl", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssh2.so.1", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:libcurl.so.4=4.5.0" + ] + }, + "perl-socket-getaddrinfo": { + "versions": { + "0.20-r2": 1509488747 + }, + "origin": "perl-socket-getaddrinfo", + "dependencies": [ + "perl-extutils-cchecker" + ], + "provides": [ + "cmd:getaddrinfo", + "cmd:getnameinfo" + ] + }, + "mosquitto-libs++": { + "versions": { + "1.4.15-r0": 1520176488, + "1.4.15-r2": 1563346323 + }, + "origin": "mosquitto", + "dependencies": [ + "so:libmosquitto.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libmosquittopp.so.1=1" + ] + }, + "perl-lwp-mediatypes-doc": { + "versions": { + "6.02-r1": 1509464357 + }, + "origin": "perl-lwp-mediatypes" + }, + "gst-plugins-good": { + "versions": { + "1.12.3-r0": 1510075045 + }, + "origin": "gst-plugins-good", + "dependencies": [ + "so:libFLAC.so.8", + "so:libX11.so.6", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libavc1394.so.0", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdv.so.4", + "so:libgcc_s.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstallocators-1.0.so.0", + "so:libgstaudio-1.0.so.0", + "so:libgstbase-1.0.so.0", + "so:libgstfft-1.0.so.0", + "so:libgstnet-1.0.so.0", + "so:libgstpbutils-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgstriff-1.0.so.0", + "so:libgstrtp-1.0.so.0", + "so:libgstrtsp-1.0.so.0", + "so:libgstsdp-1.0.so.0", + "so:libgsttag-1.0.so.0", + "so:libgstvideo-1.0.so.0", + "so:libgudev-1.0.so.0", + "so:libiec61883.so.0", + "so:libintl.so.8", + "so:libjack.so.0", + "so:libjpeg.so.8", + "so:liborc-0.4.so.0", + "so:libpng16.so.16", + "so:libraw1394.so.11", + "so:librom1394.so.0", + "so:libshout.so.3", + "so:libsoup-2.4.so.1", + "so:libspeex.so.1", + "so:libstdc++.so.6", + "so:libtag.so.1", + "so:libv4l2.so.0", + "so:libvpx.so.4", + "so:libwavpack.so.1", + "so:libz.so.1" + ] + }, + "py-bcrypt": { + "versions": { + "3.1.4-r0": 1509474481 + }, + "origin": "py-bcrypt" + }, + "ppp-minconn": { + "versions": { + "2.4.7-r5": 1509480139 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua-pty": { + "versions": { + "1.2.1-r1": 1509496424 + }, + "origin": "lua-pty" + }, + "encodings": { + "versions": { + "1.0.4-r1": 1509469869 + }, + "origin": "encodings" + }, + "abiword-plugin-babelfish": { + "versions": { + "3.0.2-r1": 1510073356 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "liboping": { + "versions": { + "1.10.0-r0": 1509482706 + }, + "origin": "liboping", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "so:liboping.so.0=0.3.0", + "cmd:noping", + "cmd:oping" + ] + }, + "uwsgi-emperor_pg": { + "versions": { + "2.0.17-r0": 1522154654 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "perl-text-wikiformat-doc": { + "versions": { + "0.81-r0": 1509482360 + }, + "origin": "perl-text-wikiformat" + }, + "libice": { + "versions": { + "1.0.9-r2": 1509465974 + }, + "origin": "libice", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libICE.so.6=6.3.0" + ] + }, + "pstree-doc": { + "versions": { + "2.39-r0": 1509493760 + }, + "origin": "pstree" + }, + "postgresql-plpython2": { + "versions": { + "10.7-r0": 1554274199, + "10.8-r0": 1557668015, + "10.9-r0": 1562225614, + "10.10-r0": 1565683439 + }, + "origin": "postgresql", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "pinentry-gtk": { + "versions": { + "1.0.0-r0": 1510587943 + }, + "origin": "pinentry", + "dependencies": [ + "/bin/sh", + "so:libassuan.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgpg-error.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:pinentry-gtk-2" + ] + }, + "libxp-dev": { + "versions": { + "1.0.3-r0": 1509494901 + }, + "origin": "libxp", + "dependencies": [ + "libx11-dev", + "libxext-dev", + "libxau-dev", + "printproto", + "libxp=1.0.3-r0", + "pc:printproto", + "pc:x11", + "pc:xau", + "pc:xext", + "pkgconfig" + ], + "provides": [ + "pc:xp=1.0.3" + ] + }, + "orbit2-doc": { + "versions": { + "2.14.19-r3": 1510928302 + }, + "origin": "orbit2" + }, + "asterisk-cdr-mysql": { + "versions": { + "15.6.1-r0": 1537795343, + "15.6.2-r0": 1568705005 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "neon-dev": { + "versions": { + "0.30.2-r2": 1510285201 + }, + "origin": "neon", + "dependencies": [ + "expat-dev", + "libressl-dev", + "zlib-dev", + "neon=0.30.2-r2", + "pkgconfig" + ], + "provides": [ + "pc:neon=0.30.2", + "cmd:neon-config" + ] + }, + "perl-yaml-syck-doc": { + "versions": { + "1.30-r1": 1509461787 + }, + "origin": "perl-yaml-syck" + }, + "unifont-doc": { + "versions": { + "9.0.06-r0": 1509491373 + }, + "origin": "unifont" + }, + "sems-gsm": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libgsm.so.1" + ] + }, + "pcsc-lite": { + "versions": { + "1.8.22-r0": 1509474498 + }, + "origin": "pcsc-lite", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ], + "provides": [ + "cmd:pcsc-spy", + "cmd:pcscd" + ] + }, + "multipath-tools": { + "versions": { + "0.7.4-r0": 1510816244 + }, + "origin": "multipath-tools", + "dependencies": [ + "eudev", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02", + "so:libjson-c.so.2", + "so:libreadline.so.7", + "so:libudev.so.1", + "so:liburcu.so.6" + ], + "provides": [ + "so:libdmmp.so.0.1.0=0.1.0", + "so:libmpathcmd.so.0=0", + "so:libmpathpersist.so.0=0", + "so:libmultipath.so.0=0", + "cmd:kpartx", + "cmd:mpathpersist", + "cmd:multipath", + "cmd:multipathd" + ] + }, + "scons": { + "versions": { + "3.0.0-r1": 1510136398 + }, + "origin": "scons", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:scons", + "cmd:scons-3.0.0", + "cmd:scons-configure-cache", + "cmd:scons-configure-cache-3.0.0", + "cmd:scons-time", + "cmd:scons-time-3.0.0", + "cmd:sconsign", + "cmd:sconsign-3.0.0" + ] + }, + "nginx-mod-http-upstream-fair": { + "versions": { + "1.12.2-r4": 1542814448 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.3-curl": { + "versions": { + "0.3.7-r1": 1509493018 + }, + "origin": "lua-curl", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "libxfixes": { + "versions": { + "5.0.3-r1": 1509464685 + }, + "origin": "libxfixes", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXfixes.so.3=3.1.0" + ] + }, + "gtksourceview2-dev": { + "versions": { + "2.10.5-r0": 1510067575 + }, + "origin": "gtksourceview2", + "dependencies": [ + "gtk+2.0-dev", + "libxml2-dev", + "gtksourceview2=2.10.5-r0", + "pc:gtk+-2.0>=2.12.0", + "pc:libxml-2.0>=2.5.0", + "pkgconfig" + ], + "provides": [ + "pc:gtksourceview-2.0=2.10.5" + ] + }, + "libxvmc-dev": { + "versions": { + "1.0.10-r1": 1509467179 + }, + "origin": "libxvmc", + "dependencies": [ + "libxext-dev", + "libxvmc=1.0.10-r1", + "pc:videoproto", + "pc:x11", + "pc:xext", + "pc:xproto", + "pc:xv", + "pkgconfig" + ], + "provides": [ + "pc:xvmc=1.0.10" + ] + }, + "libsm-dev": { + "versions": { + "1.2.2-r1": 1509466262 + }, + "origin": "libsm", + "dependencies": [ + "libsm=1.2.2-r1", + "pc:ice", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:sm=1.2.2" + ] + }, + "lua-lub": { + "versions": { + "1.1.0-r1": 1509475705 + }, + "origin": "lua-lub" + }, + "libelf": { + "versions": { + "0.8.13-r3": 1509468229 + }, + "origin": "libelf", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libelf.so.0=0" + ] + }, + "iproute2": { + "versions": { + "4.13.0-r0": 1509481070 + }, + "origin": "iproute2", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libelf.so.0", + "so:libxtables.so.12" + ], + "provides": [ + "cmd:bridge", + "cmd:ctstat", + "cmd:genl", + "cmd:ifcfg", + "cmd:ifstat", + "cmd:ip", + "cmd:lnstat", + "cmd:nstat", + "cmd:routef", + "cmd:routel", + "cmd:rtacct", + "cmd:rtmon", + "cmd:rtpr", + "cmd:rtstat", + "cmd:ss", + "cmd:tc" + ] + }, + "dwm-doc": { + "versions": { + "6.1-r2": 1509494031 + }, + "origin": "dwm" + }, + "gtksourceview2-lang": { + "versions": { + "2.10.5-r0": 1510067576 + }, + "origin": "gtksourceview2" + }, + "libcom_err": { + "versions": { + "1.43.7-r0": 1509469274, + "1.43.7-r1": 1571322243 + }, + "origin": "e2fsprogs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcom_err.so.2=2.1" + ] + }, + "openssh-keygen": { + "versions": { + "7.5_p1-r10": 1551712287 + }, + "origin": "openssh", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ], + "provides": [ + "cmd:ssh-keygen" + ] + }, + "openbox-kde": { + "versions": { + "3.6.1-r1": 1510073886 + }, + "origin": "openbox", + "provides": [ + "cmd:openbox-kde-session" + ] + }, + "libxmu": { + "versions": { + "1.1.2-r1": 1509473711 + }, + "origin": "libxmu", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXmu.so.6=6.2.0", + "so:libXmuu.so.1=1.0.0" + ] + }, + "xf86-video-sis-doc": { + "versions": { + "0.10.9-r0": 1510075907 + }, + "origin": "xf86-video-sis" + }, + "libgnomekbd": { + "versions": { + "3.6.0-r0": 1510933020 + }, + "origin": "libgnomekbd", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libxklavier.so.16" + ], + "provides": [ + "so:libgnomekbd.so.8=8.0.0", + "so:libgnomekbdui.so.8=8.0.0", + "cmd:gkbd-keyboard-display" + ] + }, + "perl-dbd-pg": { + "versions": { + "3.7.0-r0": 1509491419 + }, + "origin": "perl-dbd-pg", + "dependencies": [ + "perl-dbi", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "samba-libnss-winbind": { + "versions": { + "4.7.6-r3": 1555491787 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libwbclient.so.0", + "so:libwinbind-client-samba4.so" + ], + "provides": [ + "so:libnss_winbind.so.2=2", + "so:libnss_wins.so.2=2" + ] + }, + "cvs-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503663 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "py-asn1": { + "versions": { + "0.4.2-r0": 1511889292 + }, + "origin": "py-asn1" + }, + "smem": { + "versions": { + "1.4-r1": 1509492708 + }, + "origin": "smem", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:smem" + ] + }, + "py2-babel": { + "versions": { + "2.5.1-r0": 1509490095 + }, + "origin": "py-babel", + "dependencies": [ + "py2-tz", + "python2" + ], + "provides": [ + "cmd:pybabel" + ] + }, + "lua5.2-discount": { + "versions": { + "1.2.10.1-r4": 1509479788 + }, + "origin": "lua-discount", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "fftw-dev": { + "versions": { + "3.3.6p2-r0": 1509468829 + }, + "origin": "fftw", + "dependencies": [ + "fftw-double-libs=3.3.6p2-r0", + "fftw-long-double-libs=3.3.6p2-r0", + "fftw-single-libs=3.3.6p2-r0", + "pkgconfig" + ] + }, + "collectd-nginx": { + "versions": { + "5.7.2-r0": 1510069645 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "py-cliapp": { + "versions": { + "1.20150829-r1": 1509552707 + }, + "origin": "py-cliapp", + "dependencies": [ + "python2" + ] + }, + "libxscrnsaver": { + "versions": { + "1.2.2-r1": 1509480774 + }, + "origin": "libxscrnsaver", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXss.so.1=1.0.0" + ] + }, + "font-util-dev": { + "versions": { + "1.3.1-r2": 1509470386 + }, + "origin": "font-util", + "dependencies": [ + "font-util", + "pkgconfig" + ], + "provides": [ + "pc:fontutil=1.3.1" + ] + }, + "subversion": { + "versions": { + "1.9.7-r0": 1510314505, + "1.9.12-r0": 1565086192 + }, + "origin": "subversion", + "dependencies": [ + "/bin/sh", + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libsasl2.so.3", + "so:libsvn_client-1.so.0", + "so:libsvn_delta-1.so.0", + "so:libsvn_diff-1.so.0", + "so:libsvn_fs-1.so.0", + "so:libsvn_fs_fs-1.so.0", + "so:libsvn_ra-1.so.0", + "so:libsvn_ra_svn-1.so.0", + "so:libsvn_repos-1.so.0", + "so:libsvn_subr-1.so.0", + "so:libsvn_wc-1.so.0" + ], + "provides": [ + "cmd:svn", + "cmd:svnadmin", + "cmd:svnbench", + "cmd:svndumpfilter", + "cmd:svnfsfs", + "cmd:svnlook", + "cmd:svnmucc", + "cmd:svnrdump", + "cmd:svnserve", + "cmd:svnsync", + "cmd:svnversion" + ] + }, + "perl-clone-doc": { + "versions": { + "0.39-r1": 1509477481 + }, + "origin": "perl-clone" + }, + "perl-net-async-http-doc": { + "versions": { + "0.40-r0": 1509480555 + }, + "origin": "perl-net-async-http" + }, + "openpgm": { + "versions": { + "5.2.122-r1": 1509475722 + }, + "origin": "openpgm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpgm-5.2.so.0=0.0.122" + ] + }, + "sems-dsm": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "meson": { + "versions": { + "0.43.0-r0": 1509475829 + }, + "origin": "meson", + "dependencies": [ + "python3", + "ninja" + ], + "provides": [ + "cmd:meson", + "cmd:mesonconf", + "cmd:mesonintrospect", + "cmd:mesontest", + "cmd:wraptool" + ] + }, + "rdiff-backup": { + "versions": { + "1.2.8-r2": 1509483273 + }, + "origin": "rdiff-backup", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:librsync.so.2" + ], + "provides": [ + "cmd:rdiff-backup", + "cmd:rdiff-backup-statistics" + ] + }, + "fetchmailconf": { + "versions": { + "6.3.26-r12": 1510261418 + }, + "origin": "fetchmail", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:fetchmailconf" + ] + }, + "mariadb-client-libs": { + "versions": { + "10.1.38-r1": 1551187990, + "10.1.40-r0": 1560354887, + "10.1.41-r0": 1565163111 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:libmysqlclient.so.18=18.0.0" + ] + }, + "ack-doc": { + "versions": { + "2.18-r0": 1510073016 + }, + "origin": "ack" + }, + "s6-linux-init": { + "versions": { + "0.3.1.0-r0": 1509492623 + }, + "origin": "s6-linux-init", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libskarnet.so.2.6" + ], + "provides": [ + "cmd:s6-halt", + "cmd:s6-linux-init-maker", + "cmd:s6-poweroff", + "cmd:s6-reboot" + ] + }, + "imagemagick-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503664 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "hdparm-doc": { + "versions": { + "9.52-r0": 1509479800 + }, + "origin": "hdparm" + }, + "itstool": { + "versions": { + "2.0.4-r3": 1511265097 + }, + "origin": "itstool", + "dependencies": [ + "py3-libxml2", + "python3" + ], + "provides": [ + "cmd:itstool" + ] + }, + "termrec-dev": { + "versions": { + "0.17-r1": 1509491604 + }, + "origin": "termrec", + "dependencies": [ + "termrec=0.17-r1" + ] + }, + "py3-bcrypt": { + "versions": { + "3.1.4-r0": 1509474481 + }, + "origin": "py-bcrypt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "perl-yaml-libyaml": { + "versions": { + "0.65-r2": 1509494322 + }, + "origin": "perl-yaml-libyaml", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "perl-yaml-xs=0.65-r2" + ] + }, + "font-util-doc": { + "versions": { + "1.3.1-r2": 1509470387 + }, + "origin": "font-util" + }, + "perl-regexp-common-net-cidr": { + "versions": { + "0.02-r1": 1510564550 + }, + "origin": "perl-regexp-common-net-cidr", + "dependencies": [ + "perl", + "perl-regexp-common" + ] + }, + "loudmouth": { + "versions": { + "1.5.3-r0": 1509490879 + }, + "origin": "loudmouth", + "dependencies": [ + "so:libasyncns.so.0", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libgobject-2.0.so.0", + "so:libidn.so.11", + "so:libintl.so.8" + ], + "provides": [ + "so:libloudmouth-1.so.0=0.1.0" + ] + }, + "ddate-doc": { + "versions": { + "0.2.2-r0": 1509492724 + }, + "origin": "ddate" + }, + "perl-test-pod": { + "versions": { + "1.51-r1": 1509461792 + }, + "origin": "perl-test-pod" + }, + "libmtp-dev": { + "versions": { + "1.1.14-r0": 1509481648 + }, + "origin": "libmtp", + "dependencies": [ + "libusb-compat-dev", + "libmtp=1.1.14-r0", + "pc:libusb-1.0", + "pkgconfig" + ], + "provides": [ + "pc:libmtp=1.1.14" + ] + }, + "perl-gd-doc": { + "versions": { + "2.67-r0": 1511805778 + }, + "origin": "perl-gd" + }, + "bitlbee-otr": { + "versions": { + "3.5.1-r1": 1510259355 + }, + "origin": "bitlbee", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libotr.so.5" + ] + }, + "thunar-dev": { + "versions": { + "1.6.12-r0": 1510072625 + }, + "origin": "thunar", + "dependencies": [ + "gtk+2.0-dev", + "glib-dev", + "exo-dev", + "pc:gio-2.0", + "pc:gtk+-2.0", + "pkgconfig", + "thunar=1.6.12-r0" + ], + "provides": [ + "pc:thunarx-2=1.6.12" + ] + }, + "xvinfo": { + "versions": { + "1.1.3-r0": 1509492435 + }, + "origin": "xvinfo", + "dependencies": [ + "so:libX11.so.6", + "so:libXv.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xvinfo" + ] + }, + "perl-cgi-fast-doc": { + "versions": { + "2.13-r0": 1511894072 + }, + "origin": "perl-cgi-fast" + }, + "gnome-doc-utils-lang": { + "versions": { + "0.20.10-r2": 1509627733 + }, + "origin": "gnome-doc-utils", + "dependencies": [ + "python2", + "docbook-xml", + "rarian", + "py2-libxml2", + "libxslt" + ] + }, + "perl-module-metadata": { + "versions": { + "1.000033-r0": 1509468436 + }, + "origin": "perl-module-metadata" + }, + "cracklib-words": { + "versions": { + "20080507-r2": 1509486317 + }, + "origin": "cracklib-words" + }, + "mlmmj-doc": { + "versions": { + "1.3.0-r0": 1509492628 + }, + "origin": "mlmmj" + }, + "krb5": { + "versions": { + "1.15.4-r0": 1548491443 + }, + "origin": "krb5", + "dependencies": [ + "krb5-conf", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libgssapi_krb5.so.2", + "so:libk5crypto.so.3", + "so:libkadm5clnt_mit.so.11", + "so:libkadm5srv_mit.so.11", + "so:libkdb5.so.8", + "so:libkrb5.so.3", + "so:libkrb5support.so.0", + "so:libss.so.2" + ], + "provides": [ + "cmd:gss-client", + "cmd:k5srvutil", + "cmd:kadmin", + "cmd:kdestroy", + "cmd:kinit", + "cmd:klist", + "cmd:kpasswd", + "cmd:ksu", + "cmd:kswitch", + "cmd:ktutil", + "cmd:kvno", + "cmd:sim_client", + "cmd:uuclient" + ] + }, + "xkbcomp-doc": { + "versions": { + "1.4.0-r2": 1509473697 + }, + "origin": "xkbcomp" + }, + "libfontenc": { + "versions": { + "1.1.3-r2": 1509469860 + }, + "origin": "libfontenc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libfontenc.so.1=1.0.0" + ] + }, + "texinfo": { + "versions": { + "6.5-r0": 1509456625 + }, + "origin": "texinfo", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:info", + "cmd:install-info", + "cmd:makeinfo", + "cmd:pdftexi2dvi", + "cmd:pod2texi", + "cmd:texi2any", + "cmd:texi2dvi", + "cmd:texi2pdf", + "cmd:texindex" + ] + }, + "ip6tables": { + "versions": { + "1.6.1-r1": 1509469980 + }, + "origin": "iptables", + "dependencies": [ + "iptables", + "iptables=1.6.1-r1", + "so:libc.musl-x86_64.so.1", + "so:libxtables.so.12" + ] + }, + "perl-json-maybexs": { + "versions": { + "1.003009-r0": 1510352918 + }, + "origin": "perl-json-maybexs", + "dependencies": [ + "perl-cpanel-json-xs" + ] + }, + "font-micro-misc": { + "versions": { + "1.0.3-r0": 1509494115 + }, + "origin": "font-micro-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "clutter-dev": { + "versions": { + "1.26.2-r1": 1510073008 + }, + "origin": "clutter", + "dependencies": [ + "gdk-pixbuf-dev", + "json-glib-dev", + "atk-dev", + "pango-dev", + "mesa-dev", + "libxcomposite-dev", + "libxi-dev", + "cairo-dev", + "cogl-dev", + "clutter=1.26.2-r1", + "pc:atk", + "pc:atk>=2.5.3", + "pc:cairo-gobject>=1.14.0", + "pc:cogl-1.0>=1.21.2", + "pc:cogl-pango-1.0", + "pc:cogl-path-1.0", + "pc:gio-2.0>=2.44.0", + "pc:json-glib-1.0>=0.12.0", + "pc:pangocairo>=1.30", + "pc:pangoft2", + "pc:x11", + "pc:xcomposite>=0.4", + "pc:xdamage", + "pc:xext", + "pc:xi", + "pkgconfig" + ], + "provides": [ + "pc:cally-1.0=1.26.2", + "pc:clutter-1.0=1.26.2", + "pc:clutter-cogl-1.0=1.26.2", + "pc:clutter-egl-1.0=1.26.2", + "pc:clutter-glx-1.0=1.26.2", + "pc:clutter-x11-1.0=1.26.2" + ] + }, + "xdriinfo": { + "versions": { + "1.0.5-r0": 1510074965 + }, + "origin": "xdriinfo", + "dependencies": [ + "so:libGL.so.1", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xdriinfo" + ] + }, + "py-libxml2": { + "versions": { + "2.9.8-r1": 1540398579 + }, + "origin": "libxml2" + }, + "bash-doc": { + "versions": { + "4.4.19-r1": 1518031135 + }, + "origin": "bash" + }, + "collectd-dev": { + "versions": { + "5.7.2-r0": 1510069641 + }, + "origin": "collectd", + "dependencies": [ + "collectd-libs=5.7.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcollectdclient=5.7.2" + ] + }, + "postgresql-plpython2-contrib": { + "versions": { + "10.7-r0": 1554274199, + "10.8-r0": 1557668016, + "10.9-r0": 1562225614, + "10.10-r0": 1565683439 + }, + "origin": "postgresql", + "dependencies": [ + "postgresql-plpython2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "znc-modperl": { + "versions": { + "1.7.1-r0": 1531900839, + "1.7.1-r1": 1565877330 + }, + "origin": "znc", + "dependencies": [ + "znc", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libperl.so", + "so:libstdc++.so.6" + ] + }, + "uwsgi-alarm_curl": { + "versions": { + "2.0.17-r0": 1522154653 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "pacman-lang": { + "versions": { + "5.0.2-r1": 1510588357 + }, + "origin": "pacman" + }, + "acf-postfix": { + "versions": { + "0.10.0-r2": 1510076433 + }, + "origin": "acf-postfix", + "dependencies": [ + "acf-core", + "postfix" + ] + }, + "pllua": { + "versions": { + "1.1.0-r0": 1510075359 + }, + "origin": "pllua", + "dependencies": [ + "postgresql", + "so:libc.musl-x86_64.so.1", + "so:libluajit-5.1.so.2" + ] + }, + "gperf": { + "versions": { + "3.1-r1": 1509462059 + }, + "origin": "gperf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:gperf" + ] + }, + "apk-tools-static": { + "versions": { + "2.10.1-r0": 1536582152 + }, + "origin": "apk-tools", + "provides": [ + "cmd:apk.static" + ] + }, + "joe-doc": { + "versions": { + "4.5-r0": 1509494570 + }, + "origin": "joe" + }, + "consolekit2-doc": { + "versions": { + "1.2.0-r2": 1510067530 + }, + "origin": "consolekit2" + }, + "libnetfilter_log-dev": { + "versions": { + "1.0.1-r2": 1509480367 + }, + "origin": "libnetfilter_log", + "dependencies": [ + "linux-headers", + "libnetfilter_log=1.0.1-r2", + "pc:libnfnetlink", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_log=1.0.1" + ] + }, + "libdvdcss-dev": { + "versions": { + "1.4.0-r1": 1509480288 + }, + "origin": "libdvdcss", + "dependencies": [ + "libdvdcss=1.4.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:libdvdcss=1.4.0" + ] + }, + "videoproto-doc": { + "versions": { + "2.3.3-r1": 1509467164 + }, + "origin": "videoproto" + }, + "py2-crypto": { + "versions": { + "2.6.1-r2": 1509483003 + }, + "origin": "py-crypto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libpython2.7.so.1.0" + ] + }, + "s6": { + "versions": { + "2.6.1.1-r0": 1510364940 + }, + "origin": "s6", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libexecline.so.2.3", + "so:libskarnet.so.2.6" + ], + "provides": [ + "so:libs6.so.2.6=2.6.1.1", + "cmd:s6-accessrules-cdb-from-fs", + "cmd:s6-accessrules-fs-from-cdb", + "cmd:s6-applyuidgid", + "cmd:s6-cleanfifodir", + "cmd:s6-connlimit", + "cmd:s6-envdir", + "cmd:s6-envuidgid", + "cmd:s6-fdholder-daemon", + "cmd:s6-fdholder-delete", + "cmd:s6-fdholder-deletec", + "cmd:s6-fdholder-getdump", + "cmd:s6-fdholder-getdumpc", + "cmd:s6-fdholder-list", + "cmd:s6-fdholder-listc", + "cmd:s6-fdholder-retrieve", + "cmd:s6-fdholder-retrievec", + "cmd:s6-fdholder-setdump", + "cmd:s6-fdholder-setdumpc", + "cmd:s6-fdholder-store", + "cmd:s6-fdholder-storec", + "cmd:s6-fdholder-transferdump", + "cmd:s6-fdholder-transferdumpc", + "cmd:s6-fdholderd", + "cmd:s6-fghack", + "cmd:s6-ftrig-listen", + "cmd:s6-ftrig-listen1", + "cmd:s6-ftrig-notify", + "cmd:s6-ftrig-wait", + "cmd:s6-ftrigrd", + "cmd:s6-ioconnect", + "cmd:s6-ipcclient", + "cmd:s6-ipcserver", + "cmd:s6-ipcserver-access", + "cmd:s6-ipcserver-socketbinder", + "cmd:s6-ipcserverd", + "cmd:s6-log", + "cmd:s6-mkfifodir", + "cmd:s6-notifyoncheck", + "cmd:s6-setlock", + "cmd:s6-setsid", + "cmd:s6-setuidgid", + "cmd:s6-softlimit", + "cmd:s6-sudo", + "cmd:s6-sudoc", + "cmd:s6-sudod", + "cmd:s6-supervise", + "cmd:s6-svc", + "cmd:s6-svlisten", + "cmd:s6-svlisten1", + "cmd:s6-svok", + "cmd:s6-svscan", + "cmd:s6-svscanctl", + "cmd:s6-svstat", + "cmd:s6-svwait", + "cmd:s6-tai64n", + "cmd:s6-tai64nlocal", + "cmd:s6lockd", + "cmd:ucspilogd" + ] + }, + "perl-net-ip-doc": { + "versions": { + "1.26-r0": 1509469033 + }, + "origin": "perl-net-ip" + }, + "xfce4-vala": { + "versions": { + "4.10.3-r7": 1510070641 + }, + "origin": "xfce4-vala", + "dependencies": [ + "exo-dev", + "libxfce4util-dev", + "libxfce4ui-dev", + "garcon-dev", + "xfce4-panel-dev", + "xfconf-dev", + "vala" + ] + }, + "libxi-doc": { + "versions": { + "1.7.9-r1": 1509466011 + }, + "origin": "libxi" + }, + "samba-server-libs": { + "versions": { + "4.7.6-r3": 1555491788 + }, + "origin": "samba", + "dependencies": [ + "so:libMESSAGING-SEND-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libldap-2.4.so.2", + "so:libmessages-dgm-samba4.so", + "so:libmessages-util-samba4.so", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsecrets3-samba4.so", + "so:libserver-id-db-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-base-samba4.so", + "so:libsmbldap.so.2", + "so:libsmbldaphelper-samba4.so", + "so:libtalloc-report-samba4.so", + "so:libtalloc.so.2", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-tdb-samba4.so" + ], + "provides": [ + "so:libMESSAGING-samba4.so=0", + "so:libdcerpc-samba4.so=0", + "so:libidmap-samba4.so=0", + "so:libnon-posix-acls-samba4.so=0", + "so:libnss-info-samba4.so=0" + ] + }, + "apcupsd": { + "versions": { + "3.14.14-r0": 1509495263 + }, + "origin": "apcupsd", + "dependencies": [ + "util-linux", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "cmd:apcaccess", + "cmd:apctest", + "cmd:apcupsd", + "cmd:smtp" + ] + }, + "dovecot-sqlite": { + "versions": { + "2.2.36.3-r0": 1554102390, + "2.2.36.4-r0": 1567075098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot-sql", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:libdriver_sqlite.so=0" + ] + }, + "perl-config-grammar": { + "versions": { + "1.12-r0": 1509483936 + }, + "origin": "perl-config-grammar" + }, + "faac": { + "versions": { + "1.28-r11": 1509470018 + }, + "origin": "faac", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libfaac.so.0=0.0.0", + "cmd:faac" + ] + }, + "perl-test-needs-doc": { + "versions": { + "0.002005-r0": 1509481590 + }, + "origin": "perl-test-needs" + }, + "libinput": { + "versions": { + "1.9.2-r0": 1511276853 + }, + "origin": "libinput", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libevdev.so.2", + "so:libinput.so.10", + "so:libudev.so.1" + ], + "provides": [ + "cmd:libinput", + "cmd:libinput-debug-events", + "cmd:libinput-list-devices" + ] + }, + "uwsgi-python3": { + "versions": { + "2.0.17-r0": 1522154661 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "clamav": { + "versions": { + "0.100.3-r0": 1555508238 + }, + "origin": "clamav", + "dependencies": [ + "clamav-scanner", + "clamav-daemon", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libclammspack.so.0=0.1.0" + ] + }, + "zfs-dracut": { + "versions": { + "0.7.3-r0": 1509490074 + }, + "origin": "zfs" + }, + "xtables-addons": { + "versions": { + "2.11-r1": 1509469988 + }, + "origin": "xtables-addons", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxtables.so.12" + ], + "provides": [ + "so:libxt_ACCOUNT_cl.so.0=0.0.0", + "cmd:iptaccount" + ] + }, + "lua5.3-ldbus": { + "versions": { + "20150430-r2": 1509492577 + }, + "origin": "lua-ldbus", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3" + ] + }, + "glamor-egl-dev": { + "versions": { + "0.6.0-r3": 1510068308 + }, + "origin": "glamor-egl", + "dependencies": [ + "mesa-dev", + "glamor-egl=0.6.0-r3", + "pkgconfig" + ], + "provides": [ + "pc:glamor-egl=0.6.0", + "pc:glamor=0.6.0" + ] + }, + "libcdio-paranoia-dev": { + "versions": { + "0.94_p1-r0": 1509473650 + }, + "origin": "libcdio-paranoia", + "dependencies": [ + "libcdio-paranoia=0.94_p1-r0", + "pc:libcdio", + "pkgconfig" + ] + }, + "which": { + "versions": { + "2.21-r1": 1509485572 + }, + "origin": "which", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:which" + ] + }, + "perl-want": { + "versions": { + "0.29-r1": 1509477486 + }, + "origin": "perl-want", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "iso-codes-dev": { + "versions": { + "3.75-r0": 1509466021 + }, + "origin": "iso-codes", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:iso-codes=3.75" + ] + }, + "acf-freeswitch-vmail": { + "versions": { + "0.6.2-r1": 1510585378 + }, + "origin": "acf-freeswitch-vmail", + "dependencies": [ + "acf-core", + "lua-sql-sqlite3", + "lua-socket", + "freeswitch", + "/bin/sh" + ] + }, + "perl-datetime-timezone": { + "versions": { + "2.15-r0": 1511251871 + }, + "origin": "perl-datetime-timezone", + "dependencies": [ + "perl-class-singleton", + "perl-params-validationcompiler", + "perl-namespace-autoclean", + "perl-try-tiny", + "perl-module-runtime", + "perl-specio" + ] + }, + "libnjb-dev": { + "versions": { + "2.2.7-r4": 1509481678 + }, + "origin": "libnjb", + "dependencies": [ + "libusb-compat-dev", + "libnjb=2.2.7-r4", + "pkgconfig" + ], + "provides": [ + "pc:libnjb=2.2.7" + ] + }, + "nagios-plugins-radius": { + "versions": { + "2.2.1-r3": 1510288497 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libfreeradius-client.so.2" + ] + }, + "rpcbind-doc": { + "versions": { + "0.2.4-r0": 1509488291 + }, + "origin": "rpcbind" + }, + "exiftool": { + "versions": { + "10.55-r0": 1509495133 + }, + "origin": "perl-image-exiftool", + "dependencies": [ + "perl-image-exiftool" + ], + "provides": [ + "cmd:exiftool" + ] + }, + "font-cronyx-cyrillic": { + "versions": { + "1.0.3-r0": 1509496607 + }, + "origin": "font-cronyx-cyrillic", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "py-ttystatus": { + "versions": { + "0.23-r1": 1509552708 + }, + "origin": "py-ttystatus", + "dependencies": [ + "python2" + ] + }, + "linux-vanilla-dev": { + "versions": { + "4.9.161-r0": 1551779586, + "4.9.182-r0": 1560866037 + }, + "origin": "linux-vanilla", + "dependencies": [ + "gmp-dev", + "bash", + "perl", + "so:libc.musl-x86_64.so.1", + "so:libelf.so.1" + ] + }, + "libvirt-dev": { + "versions": { + "3.9.0-r1": 1510088351, + "5.5.0-r0": 1562165539 + }, + "origin": "libvirt", + "dependencies": [ + "libtirpc-dev", + "libvirt-client=5.5.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libvirt-admin=5.5.0", + "pc:libvirt-lxc=5.5.0", + "pc:libvirt-qemu=5.5.0", + "pc:libvirt=5.5.0" + ] + }, + "ntfs-3g-libs": { + "versions": { + "2017.3.23-r1": 1509489438 + }, + "origin": "ntfs-3g", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libntfs-3g.so.88=88.0.0" + ] + }, + "squid-lang-id": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "flashcache-utils": { + "versions": { + "3.1.3-r0": 1509495925 + }, + "origin": "flashcache-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:flashcache_create", + "cmd:flashcache_destroy", + "cmd:flashcache_load", + "cmd:flashcache_setioctl", + "cmd:get_agsize" + ] + }, + "perl-net-cidr-lite": { + "versions": { + "0.21-r2": 1509496072 + }, + "origin": "perl-net-cidr-lite", + "dependencies": [ + "perl" + ] + }, + "lxsession-lang": { + "versions": { + "0.5.3-r0": 1510073919 + }, + "origin": "lxsession" + }, + "ncurses-terminfo-base": { + "versions": { + "6.0_p20171125-r1": 1534862982 + }, + "origin": "ncurses" + }, + "xf86-video-intel": { + "versions": { + "2.99.917_git20170325-r0": 1510076035 + }, + "origin": "xf86-video-intel", + "dependencies": [ + "mesa-dri-intel", + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libXv.so.1", + "so:libXvMC.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_intel.so.1", + "so:libpciaccess.so.0", + "so:libpixman-1.so.0", + "so:libudev.so.1", + "so:libxcb-dri2.so.0", + "so:libxcb-util.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libI810XvMC.so.1=1.0.0", + "so:libIntelXvMC.so.1=1.0.0" + ] + }, + "gummiboot-doc": { + "versions": { + "48.1-r0": 1509483313 + }, + "origin": "gummiboot" + }, + "uwsgi-rrdtool": { + "versions": { + "2.0.17-r0": 1522154658 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "rarian-dev": { + "versions": { + "0.8.1-r6": 1509465523 + }, + "origin": "rarian", + "dependencies": [ + "pkgconfig", + "rarian=0.8.1-r6" + ], + "provides": [ + "pc:rarian=0.8.1", + "cmd:rarian-sk-config", + "cmd:scrollkeeper-config" + ] + }, + "net-snmp-tools": { + "versions": { + "5.7.3-r10": 1510259617 + }, + "origin": "net-snmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libnetsnmp.so.30", + "so:libnetsnmpagent.so.30" + ], + "provides": [ + "cmd:agentxtrap", + "cmd:encode_keychange", + "cmd:net-snmp-create-v3-user", + "cmd:snmpbulkget", + "cmd:snmpbulkwalk", + "cmd:snmpconf", + "cmd:snmpdelta", + "cmd:snmpdf", + "cmd:snmpget", + "cmd:snmpgetnext", + "cmd:snmpinform", + "cmd:snmpnetstat", + "cmd:snmpset", + "cmd:snmpstatus", + "cmd:snmptable", + "cmd:snmptest", + "cmd:snmptranslate", + "cmd:snmptrap", + "cmd:snmpusm", + "cmd:snmpvacm", + "cmd:snmpwalk" + ] + }, + "py-nose": { + "versions": { + "1.3.7-r2": 1509552700 + }, + "origin": "py-nose" + }, + "icon-naming-utils": { + "versions": { + "0.8.90-r2": 1509477317 + }, + "origin": "icon-naming-utils", + "dependencies": [ + "perl-xml-simple", + "pkgconfig" + ], + "provides": [ + "pc:icon-naming-utils=0.8.90" + ] + }, + "xfsprogs": { + "versions": { + "4.14.0-r1": 1528279973 + }, + "origin": "xfsprogs", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:fsck.xfs", + "cmd:mkfs.xfs", + "cmd:xfs_repair" + ] + }, + "mailx": { + "versions": { + "8.1.1-r1": 1509494506 + }, + "origin": "mailx", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mail" + ] + }, + "xcb-util-renderutil-dev": { + "versions": { + "0.3.9-r1": 1509473920 + }, + "origin": "xcb-util-renderutil", + "dependencies": [ + "xcb-util-dev", + "pc:xcb-render", + "pkgconfig", + "xcb-util-renderutil=0.3.9-r1" + ], + "provides": [ + "pc:xcb-renderutil=0.3.9" + ] + }, + "libcddb-dev": { + "versions": { + "1.3.2-r2": 1509473611 + }, + "origin": "libcddb", + "dependencies": [ + "libcddb=1.3.2-r2", + "pkgconfig" + ], + "provides": [ + "pc:libcddb=1.3.2" + ] + }, + "curl": { + "versions": { + "7.61.1-r2": 1551780214, + "7.61.1-r3": 1568722567 + }, + "origin": "curl", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libz.so.1" + ], + "provides": [ + "cmd:curl" + ] + }, + "slang-dev": { + "versions": { + "2.3.1a-r0": 1509476135 + }, + "origin": "slang", + "dependencies": [ + "pkgconfig", + "slang=2.3.1a-r0" + ], + "provides": [ + "pc:slang=2.3.1" + ] + }, + "xf86-video-amdgpu-doc": { + "versions": { + "1.4.0-r0": 1510075099 + }, + "origin": "xf86-video-amdgpu" + }, + "ssh-getkey-ldap": { + "versions": { + "0.1.2-r0": 1509491919 + }, + "origin": "ssh-getkey-ldap", + "dependencies": [ + "lua", + "lua-ldap", + "/bin/sh" + ], + "provides": [ + "cmd:ssh-getkey-ldap" + ] + }, + "perl-datetime": { + "versions": { + "1.44-r0": 1510859303 + }, + "origin": "perl-datetime", + "dependencies": [ + "perl-datetime-locale", + "perl-try-tiny", + "perl-dist-checkconflicts", + "perl-params-validationcompiler", + "perl-datetime-timezone", + "perl-namespace-autoclean", + "perl-specio", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-dbd-odbc-doc": { + "versions": { + "1.56-r1": 1509493769 + }, + "origin": "perl-dbd-odbc" + }, + "perl-authen-sasl": { + "versions": { + "2.16-r1": 1510352971 + }, + "origin": "perl-authen-sasl", + "dependencies": [ + "perl", + "perl-digest-hmac" + ] + }, + "wget": { + "versions": { + "1.20.3-r0": 1554724053 + }, + "origin": "wget", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "cmd:wget" + ] + }, + "postgresql-plpython3-contrib": { + "versions": { + "10.7-r0": 1554274200, + "10.8-r0": 1557668016, + "10.9-r0": 1562225615, + "10.10-r0": 1565683439 + }, + "origin": "postgresql", + "dependencies": [ + "postgresql-plpython3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "thunar-archive-plugin": { + "versions": { + "0.3.1-r1": 1510075231 + }, + "origin": "thunar-archive-plugin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libthunarx-2.so.0" + ] + }, + "directfb-doc": { + "versions": { + "1.7.7-r1": 1509488975 + }, + "origin": "directfb" + }, + "libxres": { + "versions": { + "1.2.0-r0": 1509468563 + }, + "origin": "libxres", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXRes.so.1=1.0.0" + ] + }, + "xauth-doc": { + "versions": { + "1.0.10-r1": 1509473717 + }, + "origin": "xauth" + }, + "lua-rex": { + "versions": { + "2.9.0-r0": 1510619366 + }, + "origin": "lua-rex", + "dependencies": [ + "lua-rex-pcre", + "lua-rex-posix" + ] + }, + "abiword-plugin-urldict": { + "versions": { + "3.0.2-r1": 1510073369 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "perl-data-ical-doc": { + "versions": { + "0.21-r1": 1510588973 + }, + "origin": "perl-data-ical" + }, + "py2-chardet": { + "versions": { + "3.0.4-r0": 1509481684 + }, + "origin": "py-chardet", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:chardetect" + ] + }, + "nagios-plugins-mrtg": { + "versions": { + "2.2.1-r3": 1510288496 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "jq-doc": { + "versions": { + "1.5-r5": 1525102934 + }, + "origin": "jq" + }, + "lua-stdlib-doc": { + "versions": { + "41.2.0-r0": 1509495239 + }, + "origin": "lua-stdlib" + }, + "libcap-ng-utils": { + "versions": { + "0.7.8-r1": 1509461311 + }, + "origin": "libcap-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0" + ], + "provides": [ + "cmd:captest", + "cmd:filecap", + "cmd:netcap", + "cmd:pscap" + ] + }, + "perl-scope-upper-doc": { + "versions": { + "0.30-r0": 1509952234 + }, + "origin": "perl-scope-upper" + }, + "squid-lang-sk": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865669 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "apache2-proxy-html": { + "versions": { + "2.4.39-r0": 1554306881, + "2.4.41-r0": 1566292328 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2" + ] + }, + "py-urllib3": { + "versions": { + "1.22-r0": 1509483033 + }, + "origin": "py-urllib3" + }, + "dvgrab-doc": { + "versions": { + "3.5-r3": 1510075828 + }, + "origin": "dvgrab" + }, + "py-mako": { + "versions": { + "1.0.7-r0": 1509468234 + }, + "origin": "py-mako" + }, + "perl": { + "versions": { + "5.26.3-r0": 1543940998 + }, + "origin": "perl", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libperl.so=0", + "cmd:perl", + "cmd:perl5.26.3", + "cmd:perldoc", + "cmd:pod2html", + "cmd:pod2man", + "cmd:pod2text", + "cmd:pod2usage", + "cmd:podchecker", + "cmd:podselect" + ] + }, + "rrdcollect-doc": { + "versions": { + "0.2.10-r1": 1509492720 + }, + "origin": "rrdcollect" + }, + "libva": { + "versions": { + "1.8.2-r0": 1510071998 + }, + "origin": "libva", + "dependencies": [ + "so:libGL.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2" + ], + "provides": [ + "so:libva-drm.so.1=1.4000.0", + "so:libva-egl.so.1=1.4000.0", + "so:libva-glx.so.1=1.4000.0", + "so:libva-tpi.so.1=1.4000.0", + "so:libva-x11.so.1=1.4000.0", + "so:libva.so.1=1.4000.0" + ] + }, + "abiword-plugin-opml": { + "versions": { + "3.0.2-r1": 1510073365 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "wayland-dev": { + "versions": { + "1.14.0-r2": 1510066935 + }, + "origin": "wayland", + "dependencies": [ + "libffi-dev", + "expat-dev", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libxml2.so.2", + "wayland-libs-client=1.14.0-r2", + "wayland-libs-cursor=1.14.0-r2", + "wayland-libs-server=1.14.0-r2" + ], + "provides": [ + "pc:wayland-client=1.14.0", + "pc:wayland-cursor=1.14.0", + "pc:wayland-scanner=1.14.0", + "pc:wayland-server=1.14.0", + "cmd:wayland-scanner" + ] + }, + "rest-dev": { + "versions": { + "0.8.1-r0": 1509492761 + }, + "origin": "rest", + "dependencies": [ + "libsoup-dev", + "gobject-introspection-dev", + "pc:glib-2.0", + "pc:libsoup-2.4", + "pc:libxml-2.0", + "pkgconfig", + "rest=0.8.1-r0" + ], + "provides": [ + "pc:rest-0.7=0.8.1", + "pc:rest-extras-0.7=0.8.1" + ] + }, + "liblockfile-dev": { + "versions": { + "1.14-r0": 1511990622 + }, + "origin": "liblockfile" + }, + "libbluray": { + "versions": { + "1.0.0-r0": 1509488856 + }, + "origin": "libbluray", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libbluray.so.2=2.0.0", + "cmd:bd_info" + ] + }, + "mupdf-tools": { + "versions": { + "1.13.0-r0": 1533746007 + }, + "origin": "mupdf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmupdf.so.0", + "so:libmupdfthird.so.0" + ], + "provides": [ + "cmd:mjsgen", + "cmd:mujstest", + "cmd:muraster", + "cmd:mutool" + ] + }, + "freeswitch-snmp": { + "versions": { + "1.6.19-r0": 1510585357 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreeswitch.so.1", + "so:libnetsnmp.so.30", + "so:libnetsnmpagent.so.30" + ] + }, + "gtk-engines-clearlooks": { + "versions": { + "2.21.0-r2": 1510071722 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "gvfs-gphoto2": { + "versions": { + "1.34.1-r0": 1511430258, + "1.34.1-r1": 1563787224 + }, + "origin": "gvfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgphoto2.so.6", + "so:libgphoto2_port.so.12", + "so:libgudev-1.0.so.0", + "so:libgvfscommon.so", + "so:libgvfsdaemon.so", + "so:libintl.so.8" + ] + }, + "cmph-dev": { + "versions": { + "2.0-r1": 1509482577 + }, + "origin": "cmph", + "dependencies": [ + "libcmph=2.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:cmph=2.0" + ] + }, + "clamsmtp": { + "versions": { + "1.10-r15": 1509468381 + }, + "origin": "clamsmtp", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:clamsmtpd" + ] + }, + "py-asn1crypto": { + "versions": { + "0.23.0-r0": 1509476391 + }, + "origin": "py-asn1crypto" + }, + "freetype-dev": { + "versions": { + "2.8.1-r4": 1549555370 + }, + "origin": "freetype", + "dependencies": [ + "freetype=2.8.1-r4", + "pc:libpng", + "pc:zlib", + "pkgconfig" + ], + "provides": [ + "pc:freetype2=21.0.15", + "cmd:freetype-config" + ] + }, + "spandsp": { + "versions": { + "0.0.6-r0": 1509481507 + }, + "origin": "spandsp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtiff.so.5" + ], + "provides": [ + "so:libspandsp.so.2=2.0.0" + ] + }, + "opensp-doc": { + "versions": { + "1.5.2-r0": 1509488683 + }, + "origin": "opensp" + }, + "dosfstools-doc": { + "versions": { + "4.1-r1": 1509495035 + }, + "origin": "dosfstools" + }, + "libssh2-dev": { + "versions": { + "1.8.2-r0": 1553682023, + "1.9.0-r0": 1571235283, + "1.9.0-r1": 1571996776 + }, + "origin": "libssh2", + "dependencies": [ + "libssh2=1.9.0-r1", + "pc:libcrypto", + "pc:libssl", + "pc:zlib", + "pkgconfig" + ], + "provides": [ + "pc:libssh2=1.9.0" + ] + }, + "phodav-dev": { + "versions": { + "2.2-r0": 1509492229 + }, + "origin": "phodav", + "dependencies": [ + "pc:libsoup-2.4", + "phodav=2.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libphodav-2.0=2.2" + ] + }, + "xscreensaver-gl-extras": { + "versions": { + "5.36-r0": 1510074351 + }, + "origin": "xscreensaver", + "dependencies": [ + "bc", + "so:libGL.so.1", + "so:libGLU.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXmu.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgdk_pixbuf_xlib-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "cmd:xscreensaver-gl-helper" + ] + }, + "mesa": { + "versions": { + "17.2.4-r1": 1510741949 + }, + "origin": "mesa" + }, + "spawn-fcgi-doc": { + "versions": { + "1.6.4-r3": 1509552746 + }, + "origin": "spawn-fcgi" + }, + "py2-munkres": { + "versions": { + "1.0.12-r0": 1509468388 + }, + "origin": "py-munkres", + "dependencies": [ + "python2" + ] + }, + "freetype": { + "versions": { + "2.8.1-r4": 1549555370 + }, + "origin": "freetype", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libpng16.so.16", + "so:libz.so.1" + ], + "provides": [ + "so:libfreetype.so.6=6.15.0" + ] + }, + "acf-apk-tools": { + "versions": { + "0.11.0-r1": 1510073922 + }, + "origin": "acf-apk-tools", + "dependencies": [ + "acf-core", + "lua-posix", + "apk-tools" + ] + }, + "lua5.2-lustache": { + "versions": { + "1.3.1-r1": 1509472927 + }, + "origin": "lua-lustache", + "dependencies": [ + "lua-lustache-common", + "lua5.2", + "lua-lustache-common=1.3.1-r1" + ] + }, + "freeglut-dev": { + "versions": { + "3.0.0-r0": 1510071760 + }, + "origin": "freeglut", + "dependencies": [ + "mesa-dev", + "libx11-dev", + "libice-dev", + "libxxf86vm-dev", + "libxi-dev", + "glu-dev", + "freeglut=3.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:freeglut=3.0.0" + ] + }, + "p7zip": { + "versions": { + "16.02-r3": 1533742283 + }, + "origin": "p7zip", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:7z", + "cmd:7za", + "cmd:7zr", + "cmd:p7zip" + ] + }, + "quazip": { + "versions": { + "0.7.3-r0": 1510076335 + }, + "origin": "quazip", + "dependencies": [ + "so:libQtCore.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libquazip.so.1=1.0.0" + ] + }, + "kamailio-mysql": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "kamailio-db", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18", + "so:libsrdb1.so.1", + "so:libsrdb2.so.1" + ] + }, + "kamailio-doc": { + "versions": { + "5.0.7-r0": 1532960867 + }, + "origin": "kamailio" + }, + "perl-html-mason": { + "versions": { + "1.58-r0": 1509470629 + }, + "origin": "perl-html-mason", + "dependencies": [ + "perl-cgi", + "perl-cache-cache", + "perl-log-any", + "perl-html-parser", + "perl-class-container", + "perl-params-validate", + "perl-exception-class" + ], + "provides": [ + "cmd:convert0.6.README", + "cmd:convert0.6.pl", + "cmd:convert0.8.README", + "cmd:convert0.8.pl", + "cmd:mason.pl" + ] + }, + "lua-microlight": { + "versions": { + "1.1.1-r2": 1509480790 + }, + "origin": "lua-microlight" + }, + "xfce4-screenshooter-doc": { + "versions": { + "1.8.2-r1": 1510074729 + }, + "origin": "xfce4-screenshooter" + }, + "abiword-plugin-passepartout": { + "versions": { + "3.0.2-r1": 1510073366 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "lksctp-tools": { + "versions": { + "1.0.17-r0": 1509492330 + }, + "origin": "lksctp-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsctp.so.1=1.0.17", + "cmd:checksctp", + "cmd:sctp_darn", + "cmd:sctp_status", + "cmd:sctp_test", + "cmd:withsctp" + ] + }, + "lua5.2-sql-odbc": { + "versions": { + "2.3.5-r1": 1509488831 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ] + }, + "py3-pbr": { + "versions": { + "3.1.1-r0": 1509480860 + }, + "origin": "py-pbr", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:pbr" + ] + }, + "check": { + "versions": { + "0.12.0-r1": 1509461855 + }, + "origin": "check", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcheck.so.0=0.0.0", + "cmd:checkmk" + ] + }, + "py2-uritemplate.py": { + "versions": { + "3.0.0-r0": 1509486307 + }, + "origin": "py-uritemplate", + "dependencies": [ + "python2" + ] + }, + "lsyncd-doc": { + "versions": { + "2.2.2-r0": 1509493062 + }, + "origin": "lsyncd" + }, + "libisoburn": { + "versions": { + "1.4.8-r0": 1509462218 + }, + "origin": "libisoburn", + "dependencies": [ + "so:libburn.so.4", + "so:libc.musl-x86_64.so.1", + "so:libedit.so.0", + "so:libisofs.so.6" + ], + "provides": [ + "so:libisoburn.so.1=1.105.0" + ] + }, + "uwsgi-gevent3": { + "versions": { + "2.0.17-r0": 1522154661 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "squid-lang-nl": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865669 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "libcdio-paranoia": { + "versions": { + "0.94_p1-r0": 1509473651 + }, + "origin": "libcdio-paranoia", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcdio.so.16" + ], + "provides": [ + "so:libcdio_cdda.so.2=2.0.0", + "so:libcdio_paranoia.so.2=2.0.0", + "cmd:cd-paranoia" + ] + }, + "s6-linux-utils": { + "versions": { + "2.4.0.1-r0": 1509468269 + }, + "origin": "s6-linux-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libskarnet.so.2.6" + ], + "provides": [ + "cmd:s6-chroot", + "cmd:s6-devd", + "cmd:s6-fillurandompool", + "cmd:s6-freeramdisk", + "cmd:s6-hostname", + "cmd:s6-logwatch", + "cmd:s6-mount", + "cmd:s6-pivotchroot", + "cmd:s6-ps", + "cmd:s6-swapoff", + "cmd:s6-swapon", + "cmd:s6-uevent-listener", + "cmd:s6-uevent-spawner", + "cmd:s6-umount" + ] + }, + "perl-mail-spf-doc": { + "versions": { + "2.9.0-r2": 1509491249 + }, + "origin": "perl-mail-spf" + }, + "squid-lang-ro": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865669 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "py2-twitter": { + "versions": { + "3.1-r2": 1509552772 + }, + "origin": "py-twitter", + "dependencies": [ + "py2-future", + "py2-requests", + "py2-requests-oauthlib", + "python2" + ] + }, + "lua5.2-stdlib": { + "versions": { + "41.2.0-r0": 1509495244 + }, + "origin": "lua-stdlib" + }, + "libxt-doc": { + "versions": { + "1.1.5-r1": 1509466278 + }, + "origin": "libxt" + }, + "grub-bios": { + "versions": { + "2.02-r3": 1509495739 + }, + "origin": "grub", + "dependencies": [ + "grub" + ] + }, + "squark-dbg": { + "versions": { + "0.6.1-r1": 1509492120 + }, + "origin": "squark", + "dependencies": [ + "haserl" + ] + }, + "awstats-doc": { + "versions": { + "7.6-r2": 1515159956 + }, + "origin": "awstats" + }, + "lua-mqtt-publish": { + "versions": { + "0.1-r0": 1509462507 + }, + "origin": "lua-mqtt-publish" + }, + "font-util": { + "versions": { + "1.3.1-r2": 1509470387 + }, + "origin": "font-util", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bdftruncate", + "cmd:ucs2any" + ] + }, + "thin-provisioning-tools": { + "versions": { + "0.7.1-r0": 1509491570 + }, + "origin": "thin-provisioning-tools", + "dependencies": [ + "expat", + "boost", + "libaio", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:cache_check", + "cmd:cache_dump", + "cmd:cache_metadata_size", + "cmd:cache_repair", + "cmd:cache_restore", + "cmd:cache_writeback", + "cmd:era_check", + "cmd:era_dump", + "cmd:era_invalidate", + "cmd:era_restore", + "cmd:pdata_tools", + "cmd:thin_check", + "cmd:thin_delta", + "cmd:thin_dump", + "cmd:thin_ls", + "cmd:thin_metadata_size", + "cmd:thin_repair", + "cmd:thin_restore", + "cmd:thin_rmap" + ] + }, + "xrdb-doc": { + "versions": { + "1.1.0-r2": 1509473735 + }, + "origin": "xrdb" + }, + "perl-mro-compat": { + "versions": { + "0.13-r0": 1509481603 + }, + "origin": "perl-mro-compat" + }, + "jack-dbus": { + "versions": { + "1.9.10-r5": 1509473319 + }, + "origin": "jack", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libexpat.so.1", + "so:libjackserver.so.0" + ], + "provides": [ + "cmd:jackdbus" + ] + }, + "freeradius-mysql": { + "versions": { + "3.0.15-r4": 1556202796, + "3.0.15-r5": 1566310603 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius-sql=3.0.15-r5", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ], + "provides": [ + "freeradius3-mysql=3.0.15-r5", + "so:rlm_sql_mysql.so=0" + ] + }, + "tdb": { + "versions": { + "1.3.15-r0": 1509486505 + }, + "origin": "tdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtdb.so.1" + ], + "provides": [ + "cmd:tdbbackup", + "cmd:tdbdump", + "cmd:tdbrestore", + "cmd:tdbtool" + ] + }, + "libdbi": { + "versions": { + "0.9.0-r0": 1509474455 + }, + "origin": "libdbi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdbi.so.1=1.1.0" + ] + }, + "libgit2-dev": { + "versions": { + "0.25.1-r4": 1510288231 + }, + "origin": "libgit2", + "dependencies": [ + "curl-dev", + "libssh2-dev", + "libgit2=0.25.1-r4", + "pc:openssl", + "pc:zlib", + "pkgconfig" + ], + "provides": [ + "pc:libgit2=0.25.1" + ] + }, + "qemu": { + "versions": { + "2.10.1-r3": 1519746244 + }, + "origin": "qemu", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0", + "so:libcap.so.2", + "so:libglib-2.0.so.0" + ], + "provides": [ + "cmd:ivshmem-client", + "cmd:ivshmem-server", + "cmd:qemu-nios2", + "cmd:virtfs-proxy-helper" + ] + }, + "keybinder-dev": { + "versions": { + "0.3.0-r1": 1510069940 + }, + "origin": "keybinder", + "dependencies": [ + "gtk+2.0-dev", + "libxext-dev", + "keybinder=0.3.0-r1", + "pc:gtk+-2.0", + "pkgconfig" + ], + "provides": [ + "pc:keybinder=0.3.0" + ] + }, + "dovecot-pigeonhole-plugin": { + "versions": { + "2.2.36.3-r0": 1554102388, + "2.2.36.4-r0": 1567075098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot", + "so:libc.musl-x86_64.so.1", + "so:libdovecot-lda.so.0", + "so:libdovecot-login.so.0", + "so:libdovecot-storage.so.0", + "so:libdovecot.so.0" + ], + "provides": [ + "so:lib90_sieve_plugin.so=0", + "so:lib95_imap_sieve_plugin.so=0", + "so:libdovecot-sieve.so.0=0.0.0", + "cmd:sieve-dump", + "cmd:sieve-filter", + "cmd:sieve-test", + "cmd:sievec" + ] + }, + "vala": { + "versions": { + "0.36.4-r0": 1509480648 + }, + "origin": "vala", + "dependencies": [ + "glib-dev", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libvala-0.36.so.0=0.0.0", + "pc:libvala-0.36=0.36.4", + "pc:vapigen-0.36=0.36.4", + "pc:vapigen=0.36.4", + "cmd:vala", + "cmd:vala-0.36", + "cmd:vala-gen-introspect", + "cmd:vala-gen-introspect-0.36", + "cmd:valac", + "cmd:valac-0.36", + "cmd:vapicheck", + "cmd:vapicheck-0.36", + "cmd:vapigen", + "cmd:vapigen-0.36" + ] + }, + "usbredir-doc": { + "versions": { + "0.7.1-r0": 1509707469 + }, + "origin": "usbredir" + }, + "lua5.3-expat": { + "versions": { + "1.3.0-r2": 1509483370 + }, + "origin": "lua-expat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ] + }, + "wipe-doc": { + "versions": { + "2.3.1-r0": 1509496530 + }, + "origin": "wipe" + }, + "nodejs": { + "versions": { + "8.9.3-r1": 1522616959 + }, + "origin": "nodejs", + "dependencies": [ + "ca-certificates", + "nodejs-npm=8.9.3-r1", + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libcrypto.so.1.0.0", + "so:libgcc_s.so.1", + "so:libhttp_parser.so.2.7.1", + "so:libssl.so.1.0.0", + "so:libstdc++.so.6", + "so:libuv.so.1", + "so:libz.so.1" + ], + "provides": [ + "nodejs-lts=8.9.3", + "cmd:node" + ] + }, + "celt051-dev": { + "versions": { + "0.5.1.3-r0": 1509475636 + }, + "origin": "celt051", + "dependencies": [ + "celt051=0.5.1.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:celt051=0.5.1.3" + ] + }, + "libmicrohttpd-doc": { + "versions": { + "0.9.57-r0": 1512032047 + }, + "origin": "libmicrohttpd" + }, + "pdnsd": { + "versions": { + "1.2.9a-r5": 1509492602 + }, + "origin": "pdnsd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pdnsd", + "cmd:pdnsd-ctl" + ] + }, + "xf86-video-openchrome": { + "versions": { + "0.6.0-r1": 1510073044 + }, + "origin": "xf86-video-openchrome", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libXvMC.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2" + ], + "provides": [ + "so:libchromeXvMC.so.1=1.0.0", + "so:libchromeXvMCPro.so.1=1.0.0" + ] + }, + "libxdmcp": { + "versions": { + "1.1.2-r4": 1509461845 + }, + "origin": "libxdmcp", + "dependencies": [ + "so:libbsd.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXdmcp.so.6=6.0.0" + ] + }, + "perl-test-leaktrace": { + "versions": { + "0.15-r3": 1509474017 + }, + "origin": "perl-test-leaktrace", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "cdrkit-doc": { + "versions": { + "1.1.11-r2": 1509482677 + }, + "origin": "cdrkit" + }, + "openpgm-dev": { + "versions": { + "5.2.122-r1": 1509475721 + }, + "origin": "openpgm", + "dependencies": [ + "openpgm=5.2.122-r1", + "pkgconfig" + ], + "provides": [ + "pc:openpgm-5.2=5.2.122" + ] + }, + "libvncserver-dev": { + "versions": { + "0.9.11-r2": 1533743207, + "0.9.11-r3": 1572818864 + }, + "origin": "libvncserver", + "dependencies": [ + "libgcrypt-dev", + "libjpeg-turbo-dev", + "gnutls-dev", + "libpng-dev", + "libice-dev", + "libx11-dev", + "libxdamage-dev", + "libxext-dev", + "libxfixes-dev", + "libxi-dev", + "libxinerama-dev", + "libxrandr-dev", + "libxtst-dev", + "libvncserver=0.9.11-r3", + "pc:zlib", + "pkgconfig" + ], + "provides": [ + "pc:libvncclient=0.9.11", + "pc:libvncserver=0.9.11", + "cmd:libvncserver-config" + ] + }, + "sqlite": { + "versions": { + "3.25.3-r0": 1548491335, + "3.25.3-r1": 1563951530, + "3.25.3-r2": 1571590443 + }, + "origin": "sqlite", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:sqlite3" + ] + }, + "py-django": { + "versions": { + "1.11.20-r0": 1551365039, + "1.11.21-r0": 1561497065, + "1.11.22-r0": 1563783120, + "1.11.23-r0": 1565087802 + }, + "origin": "py-django", + "dependencies": [ + "py3-django=1.11.23-r0" + ] + }, + "py-gnome-bonobo": { + "versions": { + "2.28.1-r5": 1510933055 + }, + "origin": "py-gnome", + "dependencies": [ + "py-gtk", + "py-gnome-gnomecanvas" + ] + }, + "py2-mccabe": { + "versions": { + "0.6.1-r1": 1511661234 + }, + "origin": "py-mccabe", + "dependencies": [ + "python2" + ] + }, + "rhash-dev": { + "versions": { + "1.3.5-r1": 1509461595 + }, + "origin": "rhash", + "dependencies": [ + "rhash-libs=1.3.5-r1" + ] + }, + "hexchat-python": { + "versions": { + "2.12.4-r1": 1510260816 + }, + "origin": "hexchat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libpython2.7.so.1.0" + ] + }, + "coreutils": { + "versions": { + "8.28-r0": 1509462378 + }, + "origin": "coreutils", + "dependencies": [ + "/bin/sh", + "so:libacl.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:[", + "cmd:b2sum", + "cmd:base32", + "cmd:base64", + "cmd:basename", + "cmd:cat", + "cmd:chcon", + "cmd:chgrp", + "cmd:chmod", + "cmd:chown", + "cmd:chroot", + "cmd:cksum", + "cmd:comm", + "cmd:cp", + "cmd:csplit", + "cmd:cut", + "cmd:date", + "cmd:dd", + "cmd:df", + "cmd:dir", + "cmd:dircolors", + "cmd:dirname", + "cmd:du", + "cmd:echo", + "cmd:env", + "cmd:expand", + "cmd:expr", + "cmd:factor", + "cmd:false", + "cmd:fmt", + "cmd:fold", + "cmd:groups", + "cmd:head", + "cmd:hostid", + "cmd:id", + "cmd:install", + "cmd:join", + "cmd:link", + "cmd:ln", + "cmd:logname", + "cmd:ls", + "cmd:md5sum", + "cmd:mkdir", + "cmd:mkfifo", + "cmd:mknod", + "cmd:mktemp", + "cmd:mv", + "cmd:nice", + "cmd:nl", + "cmd:nohup", + "cmd:nproc", + "cmd:numfmt", + "cmd:od", + "cmd:paste", + "cmd:pathchk", + "cmd:pinky", + "cmd:pr", + "cmd:printenv", + "cmd:printf", + "cmd:ptx", + "cmd:pwd", + "cmd:readlink", + "cmd:realpath", + "cmd:rm", + "cmd:rmdir", + "cmd:runcon", + "cmd:seq", + "cmd:sha1sum", + "cmd:sha224sum", + "cmd:sha256sum", + "cmd:sha384sum", + "cmd:sha512sum", + "cmd:shred", + "cmd:shuf", + "cmd:sleep", + "cmd:sort", + "cmd:split", + "cmd:stat", + "cmd:stdbuf", + "cmd:stty", + "cmd:sum", + "cmd:sync", + "cmd:tac", + "cmd:tail", + "cmd:tee", + "cmd:test", + "cmd:timeout", + "cmd:touch", + "cmd:tr", + "cmd:true", + "cmd:truncate", + "cmd:tsort", + "cmd:tty", + "cmd:uname", + "cmd:unexpand", + "cmd:uniq", + "cmd:unlink", + "cmd:users", + "cmd:vdir", + "cmd:wc", + "cmd:who", + "cmd:whoami", + "cmd:yes" + ] + }, + "dovecot-sql": { + "versions": { + "2.2.36.3-r0": 1554102389, + "2.2.36.4-r0": 1567075098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot", + "so:libc.musl-x86_64.so.1", + "so:libdovecot.so.0" + ], + "provides": [ + "so:libdovecot-sql.so.0=0.0.0" + ] + }, + "rabbitmq-c-doc": { + "versions": { + "0.8.0-r3": 1510260051 + }, + "origin": "rabbitmq-c" + }, + "damageproto-doc": { + "versions": { + "1.2.1-r3": 1509464676 + }, + "origin": "damageproto" + }, + "py3-mako": { + "versions": { + "1.0.7-r0": 1509468233 + }, + "origin": "py-mako", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:mako-render" + ] + }, + "umix": { + "versions": { + "1.0.2-r7": 1509480174 + }, + "origin": "umix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:umix" + ] + }, + "nfs-utils": { + "versions": { + "2.1.1-r4": 1509492107 + }, + "origin": "nfs-utils", + "dependencies": [ + "rpcbind", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcom_err.so.2", + "so:libdevmapper.so.1.02", + "so:libevent-2.1.so.6", + "so:libgssapi_krb5.so.2", + "so:libkeyutils.so.1", + "so:libkrb5.so.3", + "so:libmount.so.1", + "so:libnfsidmap.so.0", + "so:libsqlite3.so.0", + "so:libtirpc.so.3" + ], + "provides": [ + "cmd:blkmapd", + "cmd:exportfs", + "cmd:mount.nfs", + "cmd:mount.nfs4", + "cmd:mountstats", + "cmd:nfsdcltrack", + "cmd:nfsidmap", + "cmd:nfsiostat", + "cmd:nfsstat", + "cmd:osd_login", + "cmd:rpc.gssd", + "cmd:rpc.idmapd", + "cmd:rpc.mountd", + "cmd:rpc.nfsd", + "cmd:rpc.statd", + "cmd:rpcdebug", + "cmd:showmount", + "cmd:sm-notify", + "cmd:start-statd", + "cmd:umount.nfs", + "cmd:umount.nfs4" + ] + }, + "xf86-video-glint-doc": { + "versions": { + "1.2.9-r0": 1510073703 + }, + "origin": "xf86-video-glint" + }, + "nodejs-npm": { + "versions": { + "8.9.3-r1": 1522616958 + }, + "origin": "nodejs", + "dependencies": [ + "nodejs" + ], + "provides": [ + "nodejs-current-npm", + "cmd:npm" + ] + }, + "openjpeg-tools": { + "versions": { + "2.3.0-r2": 1552584602 + }, + "origin": "openjpeg", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblcms2.so.2", + "so:libopenjp2.so.7", + "so:libpng16.so.16", + "so:libtiff.so.5" + ], + "provides": [ + "cmd:opj_compress", + "cmd:opj_decompress", + "cmd:opj_dump" + ] + }, + "perl-datetime-format-mail-doc": { + "versions": { + "0.403-r0": 1511386193 + }, + "origin": "perl-datetime-format-mail" + }, + "cdrkit": { + "versions": { + "1.1.11-r2": 1509482679 + }, + "origin": "cdrkit", + "dependencies": [ + "file", + "bzip2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:cdda2mp3", + "cmd:cdda2ogg", + "cmd:cdda2wav", + "cmd:cdrecord", + "cmd:devdump", + "cmd:dirsplit", + "cmd:genisoimage", + "cmd:icedax", + "cmd:isodebug", + "cmd:isodump", + "cmd:isoinfo", + "cmd:isovfy", + "cmd:mkhybrid", + "cmd:mkisofs", + "cmd:netscsid", + "cmd:pitchplay", + "cmd:readcd", + "cmd:readmult", + "cmd:readom", + "cmd:wodim" + ] + }, + "udisks2": { + "versions": { + "2.6.5-r0": 1510075153 + }, + "origin": "udisks2", + "dependencies": [ + "so:libacl.so.1", + "so:libatasmart.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libpolkit-agent-1.so.0", + "so:libpolkit-gobject-1.so.0", + "so:libudisks2.so.0" + ], + "provides": [ + "cmd:udisksctl", + "cmd:umount.udisks2" + ] + }, + "lua5.2-posix": { + "versions": { + "33.4.0-r0": 1509468345 + }, + "origin": "lua-posix", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "patchwork-doc": { + "versions": { + "1.1.3-r0": 1509474237, + "1.1.3-r1": 1562223418 + }, + "origin": "patchwork" + }, + "qt-doc": { + "versions": { + "4.8.7-r8": 1510287208 + }, + "origin": "qt" + }, + "py3-markupsafe": { + "versions": { + "1.0-r0": 1509476743 + }, + "origin": "py-markupsafe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "py-django-contact-form": { + "versions": { + "1.4.2-r0": 1509489456 + }, + "origin": "py-django-contact-form", + "dependencies": [ + "py-django" + ] + }, + "xdg-utils": { + "versions": { + "1.1.2-r0": 1510842482 + }, + "origin": "xdg-utils", + "dependencies": [ + "xset", + "xprop" + ], + "provides": [ + "cmd:xdg-desktop-icon", + "cmd:xdg-desktop-menu", + "cmd:xdg-email", + "cmd:xdg-icon-resource", + "cmd:xdg-mime", + "cmd:xdg-open", + "cmd:xdg-screensaver", + "cmd:xdg-settings" + ] + }, + "opensp-dev": { + "versions": { + "1.5.2-r0": 1509488681 + }, + "origin": "opensp", + "dependencies": [ + "opensp=1.5.2-r0" + ] + }, + "openssh-server-common": { + "versions": { + "7.5_p1-r10": 1551712288 + }, + "origin": "openssh" + }, + "dvgrab": { + "versions": { + "3.5-r3": 1510075829 + }, + "origin": "dvgrab", + "dependencies": [ + "so:libavc1394.so.0", + "so:libc.musl-x86_64.so.1", + "so:libdv.so.4", + "so:libgcc_s.so.1", + "so:libiec61883.so.0", + "so:libjpeg.so.8", + "so:libraw1394.so.11", + "so:librom1394.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:dvgrab" + ] + }, + "samba-pidl": { + "versions": { + "4.7.6-r3": 1555491789 + }, + "origin": "samba", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:pidl" + ] + }, + "s6-rc-doc": { + "versions": { + "0.2.1.2-r0": 1509492619 + }, + "origin": "s6-rc" + }, + "libsmartcols": { + "versions": { + "2.31.1-r0": 1541506294 + }, + "origin": "util-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsmartcols.so.1=1.1.0" + ] + }, + "ack": { + "versions": { + "2.18-r0": 1510073016 + }, + "origin": "ack", + "dependencies": [ + "perl-file-next", + "perl" + ], + "provides": [ + "cmd:ack" + ] + }, + "squid-lang-hy": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "smartmontools": { + "versions": { + "6.6-r0": 1510573358 + }, + "origin": "smartmontools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:smartctl", + "cmd:smartd", + "cmd:update-smart-drivedb" + ] + }, + "kbproto": { + "versions": { + "1.0.7-r2": 1509461961 + }, + "origin": "kbproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:kbproto=1.0.7" + ] + }, + "libxxf86misc-dev": { + "versions": { + "1.0.3-r1": 1509473661 + }, + "origin": "libxxf86misc", + "dependencies": [ + "libxxf86misc=1.0.3-r1", + "pc:x11", + "pc:xext", + "pc:xf86miscproto", + "pkgconfig" + ], + "provides": [ + "pc:xxf86misc=1.0.3" + ] + }, + "uwsgi-carbon": { + "versions": { + "2.0.17-r0": 1522154653 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libmms-dev": { + "versions": { + "0.6.4-r0": 1509480979 + }, + "origin": "libmms", + "dependencies": [ + "libmms=0.6.4-r0", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libmms=0.6.4" + ] + }, + "gitolite": { + "versions": { + "3.6.11-r0": 1548491616 + }, + "origin": "gitolite", + "dependencies": [ + "git", + "perl", + "/bin/sh" + ] + }, + "py3-gflags": { + "versions": { + "3.1.1-r1": 1509551804 + }, + "origin": "py-gflags", + "dependencies": [ + "python3" + ] + }, + "py-pylast": { + "versions": { + "1.9.0-r0": 1509491031 + }, + "origin": "py-pylast" + }, + "perl-test-mockrandom": { + "versions": { + "1.01-r1": 1510855674 + }, + "origin": "perl-test-mockrandom" + }, + "lua-sql-odbc": { + "versions": { + "2.3.5-r1": 1509488835 + }, + "origin": "lua-sql" + }, + "ifupdown": { + "versions": { + "0.7.53.1-r1": 1509494558 + }, + "origin": "ifupdown", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ifdown", + "cmd:ifquery", + "cmd:ifup" + ] + }, + "perl-sub-info-doc": { + "versions": { + "0.002-r0": 1509481619 + }, + "origin": "perl-sub-info" + }, + "dkimproxy-doc": { + "versions": { + "1.4.1-r5": 1509517849 + }, + "origin": "dkimproxy" + }, + "zlib-dev": { + "versions": { + "1.2.11-r1": 1509456390 + }, + "origin": "zlib", + "dependencies": [ + "pkgconfig", + "zlib=1.2.11-r1" + ], + "provides": [ + "pc:zlib=1.2.11" + ] + }, + "bacula-mysql": { + "versions": { + "9.0.5-r3": 1512992127 + }, + "origin": "bacula", + "dependencies": [ + "bacula", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ], + "provides": [ + "so:libbaccats-mysql-9.0.5.so=0" + ] + }, + "libnl3-cli": { + "versions": { + "3.2.28-r1": 1509475887 + }, + "origin": "libnl3", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnl-3.so.200", + "so:libnl-genl-3.so.200", + "so:libnl-idiag-3.so.200", + "so:libnl-nf-3.so.200", + "so:libnl-route-3.so.200" + ], + "provides": [ + "so:libnl-cli-3.so.200=200.23.0", + "cmd:genl-ctrl-list", + "cmd:idiag-socket-details", + "cmd:nf-ct-add", + "cmd:nf-ct-list", + "cmd:nf-exp-add", + "cmd:nf-exp-delete", + "cmd:nf-exp-list", + "cmd:nf-log", + "cmd:nf-monitor", + "cmd:nf-queue", + "cmd:nl-addr-add", + "cmd:nl-addr-delete", + "cmd:nl-addr-list", + "cmd:nl-class-add", + "cmd:nl-class-delete", + "cmd:nl-class-list", + "cmd:nl-classid-lookup", + "cmd:nl-cls-add", + "cmd:nl-cls-delete", + "cmd:nl-cls-list", + "cmd:nl-fib-lookup", + "cmd:nl-link-enslave", + "cmd:nl-link-ifindex2name", + "cmd:nl-link-list", + "cmd:nl-link-name2ifindex", + "cmd:nl-link-release", + "cmd:nl-link-set", + "cmd:nl-link-stats", + "cmd:nl-list-caches", + "cmd:nl-list-sockets", + "cmd:nl-monitor", + "cmd:nl-neigh-add", + "cmd:nl-neigh-delete", + "cmd:nl-neigh-list", + "cmd:nl-neightbl-list", + "cmd:nl-pktloc-lookup", + "cmd:nl-qdisc-add", + "cmd:nl-qdisc-delete", + "cmd:nl-qdisc-list", + "cmd:nl-route-add", + "cmd:nl-route-delete", + "cmd:nl-route-get", + "cmd:nl-route-list", + "cmd:nl-rule-list", + "cmd:nl-tctree-list", + "cmd:nl-util-addr" + ] + }, + "asterisk-mobile": { + "versions": { + "15.6.1-r0": 1537795342, + "15.6.2-r0": 1568705004 + }, + "origin": "asterisk", + "dependencies": [ + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "xfce4-terminal-lang": { + "versions": { + "0.6.3-r1": 1510074617 + }, + "origin": "xfce4-terminal", + "dependencies": [ + "startup-notification", + "hicolor-icon-theme" + ] + }, + "nano-syntax": { + "versions": { + "2.9.1-r0": 1512032032 + }, + "origin": "nano" + }, + "xfce4-wavelan-plugin": { + "versions": { + "0.6.0-r0": 1510072790 + }, + "origin": "xfce4-wavelan-plugin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-3.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libxfce4panel-2.0.so.4", + "so:libxfce4ui-2.so.0", + "so:libxfce4util.so.7" + ] + }, + "rsync": { + "versions": { + "3.1.3-r0": 1521547982 + }, + "origin": "rsync", + "dependencies": [ + "so:libacl.so.1", + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:rsync" + ] + }, + "mcpp": { + "versions": { + "2.7.2-r1": 1509473731 + }, + "origin": "mcpp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmcpp.so.0" + ], + "provides": [ + "cmd:mcpp" + ] + }, + "squid-lang-pl": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865669 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "libvdpau-dev": { + "versions": { + "1.1.1-r1": 1509467159 + }, + "origin": "libvdpau", + "dependencies": [ + "libvdpau=1.1.1-r1", + "pc:x11", + "pkgconfig" + ], + "provides": [ + "pc:vdpau=1.1.1" + ] + }, + "font-bh-75dpi": { + "versions": { + "1.0.3-r0": 1509496603 + }, + "origin": "font-bh-75dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "uwsgi-stats_pusher_file": { + "versions": { + "2.0.17-r0": 1522154659 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "imagemagick": { + "versions": { + "7.0.7.11-r1": 1510748307 + }, + "origin": "imagemagick", + "dependencies": [ + "so:libMagickCore-7.Q16HDRI.so.4", + "so:libMagickWand-7.Q16HDRI.so.4", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgomp.so.1", + "so:libgs.so.9", + "so:libjpeg.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpng16.so.16", + "so:librsvg-2.so.2", + "so:libtiff.so.5", + "so:libwebp.so.7", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:animate", + "cmd:compare", + "cmd:composite", + "cmd:conjure", + "cmd:convert", + "cmd:display", + "cmd:identify", + "cmd:import", + "cmd:magick", + "cmd:magick-script", + "cmd:mogrify", + "cmd:montage", + "cmd:stream" + ] + }, + "libxi": { + "versions": { + "1.7.9-r1": 1509466011 + }, + "origin": "libxi", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXi.so.6=6.1.0" + ] + }, + "squid-lang-cs": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865667 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "openobex": { + "versions": { + "1.7.2-r1": 1510071736 + }, + "origin": "openobex", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libopenobex.so.2=1.7.2", + "cmd:obex-check-device" + ] + }, + "rtmpdump": { + "versions": { + "2.4_git20160909-r3": 1510259117 + }, + "origin": "rtmpdump", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librtmp.so.1" + ], + "provides": [ + "cmd:rtmpdump", + "cmd:rtmpgw", + "cmd:rtmpsrv", + "cmd:rtmpsuck" + ] + }, + "perl-digest-sha1-doc": { + "versions": { + "2.13-r9": 1509468920 + }, + "origin": "perl-digest-sha1" + }, + "xcb-util": { + "versions": { + "0.4.0-r1": 1509464530 + }, + "origin": "xcb-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-util.so.1=1.0.0" + ] + }, + "ix": { + "versions": { + "0.6-r0": 1509477743 + }, + "origin": "sprunge", + "dependencies": [ + "curl" + ], + "provides": [ + "cmd:ix" + ] + }, + "perl-io-socket-ssl": { + "versions": { + "2.048-r1": 1509474731 + }, + "origin": "perl-io-socket-ssl", + "dependencies": [ + "ca-certificates", + "perl-net-libidn", + "perl-net-ssleay" + ] + }, + "py-docutils": { + "versions": { + "0.13.1-r0": 1509486248 + }, + "origin": "py-docutils", + "dependencies": [ + "py3-docutils", + "py3-docutils=0.13.1-r0" + ] + }, + "xbacklight": { + "versions": { + "1.2.1-r0": 1509491142 + }, + "origin": "xbacklight", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-randr.so.0", + "so:libxcb-util.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "cmd:xbacklight" + ] + }, + "xfce4-mixer-doc": { + "versions": { + "4.11.0-r2": 1510069969 + }, + "origin": "xfce4-mixer" + }, + "perl-net-dns-resolver-mock-doc": { + "versions": { + "1.20170814-r0": 1509469053 + }, + "origin": "perl-net-dns-resolver-mock" + }, + "py2-requests-oauthlib": { + "versions": { + "0.8.0-r1": 1509552764 + }, + "origin": "py-requests-oauthlib", + "dependencies": [ + "py2-oauthlib", + "py2-requests", + "python2" + ] + }, + "libwnck": { + "versions": { + "2.31.0-r5": 1510068129 + }, + "origin": "libwnck", + "dependencies": [ + "so:libX11.so.6", + "so:libXRes.so.1", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libstartup-notification-1.so.0" + ], + "provides": [ + "so:libwnck-1.so.22=22.4.0" + ] + }, + "asterisk-dahdi": { + "versions": { + "15.6.1-r0": 1537795343, + "15.6.2-r0": 1568705005 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpri.so.1.4", + "so:libtonezone.so.2" + ] + }, + "libxcb": { + "versions": { + "1.12-r1": 1509461940 + }, + "origin": "libxcb", + "dependencies": [ + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libxcb-composite.so.0=0.0.0", + "so:libxcb-damage.so.0=0.0.0", + "so:libxcb-dpms.so.0=0.0.0", + "so:libxcb-dri2.so.0=0.0.0", + "so:libxcb-dri3.so.0=0.0.0", + "so:libxcb-glx.so.0=0.0.0", + "so:libxcb-present.so.0=0.0.0", + "so:libxcb-randr.so.0=0.1.0", + "so:libxcb-record.so.0=0.0.0", + "so:libxcb-render.so.0=0.0.0", + "so:libxcb-res.so.0=0.0.0", + "so:libxcb-screensaver.so.0=0.0.0", + "so:libxcb-shape.so.0=0.0.0", + "so:libxcb-shm.so.0=0.0.0", + "so:libxcb-sync.so.1=1.0.0", + "so:libxcb-xf86dri.so.0=0.0.0", + "so:libxcb-xfixes.so.0=0.0.0", + "so:libxcb-xinerama.so.0=0.0.0", + "so:libxcb-xinput.so.0=0.1.0", + "so:libxcb-xkb.so.1=1.0.0", + "so:libxcb-xtest.so.0=0.0.0", + "so:libxcb-xv.so.0=0.0.0", + "so:libxcb-xvmc.so.0=0.0.0", + "so:libxcb.so.1=1.1.0" + ] + }, + "libunistring-dev": { + "versions": { + "0.9.7-r0": 1509458997 + }, + "origin": "libunistring", + "dependencies": [ + "libunistring=0.9.7-r0" + ] + }, + "py2-psycopg2": { + "versions": { + "2.7.3.2-r0": 1510619591 + }, + "origin": "py-psycopg2", + "dependencies": [ + "py-egenix-mx-base", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5", + "so:libpython2.7.so.1.0" + ] + }, + "freeradius-radclient": { + "versions": { + "3.0.15-r4": 1556202796, + "3.0.15-r5": 1566310604 + }, + "origin": "freeradius", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreeradius-radius.so", + "so:libtalloc.so.2" + ], + "provides": [ + "freeradius3-radclient=3.0.15-r5", + "cmd:radclient" + ] + }, + "py-virtualenv": { + "versions": { + "15.1.0-r0": 1512030542 + }, + "origin": "py-virtualenv" + }, + "lxterminal-lang": { + "versions": { + "0.3.0-r2": 1510071921 + }, + "origin": "lxterminal" + }, + "gst-plugins-base0.10": { + "versions": { + "0.10.36-r3": 1510068368 + }, + "origin": "gst-plugins-base0.10", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstcontroller-0.10.so.0", + "so:libgstdataprotocol-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libintl.so.8", + "so:libogg.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libtheoradec.so.1", + "so:libtheoraenc.so.1", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libgstapp-0.10.so.0=0.25.0", + "so:libgstaudio-0.10.so.0=0.25.0", + "so:libgstcdda-0.10.so.0=0.25.0", + "so:libgstfft-0.10.so.0=0.25.0", + "so:libgstinterfaces-0.10.so.0=0.25.0", + "so:libgstnetbuffer-0.10.so.0=0.25.0", + "so:libgstpbutils-0.10.so.0=0.25.0", + "so:libgstriff-0.10.so.0=0.25.0", + "so:libgstrtp-0.10.so.0=0.25.0", + "so:libgstrtsp-0.10.so.0=0.25.0", + "so:libgstsdp-0.10.so.0=0.25.0", + "so:libgsttag-0.10.so.0=0.25.0", + "so:libgstvideo-0.10.so.0=0.25.0", + "cmd:gst-discoverer-0.10", + "cmd:gst-visualise-0.10" + ] + }, + "pmacct-doc": { + "versions": { + "1.6.2-r0": 1509494257 + }, + "origin": "pmacct" + }, + "perl-date-calc-doc": { + "versions": { + "6.4-r1": 1509485624 + }, + "origin": "perl-date-calc" + }, + "lua5.2-filesystem": { + "versions": { + "1.7.0.2-r0": 1511049451 + }, + "origin": "lua-filesystem", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "libresample": { + "versions": { + "0.1.3-r1": 1509481513 + }, + "origin": "libresample" + }, + "perl-mail-dkim-doc": { + "versions": { + "0.44-r0": 1509516995 + }, + "origin": "perl-mail-dkim" + }, + "py3-parsing": { + "versions": { + "2.2.0-r0": 1509475644 + }, + "origin": "py-parsing", + "dependencies": [ + "python3" + ] + }, + "py3-lockfile": { + "versions": { + "0.12.2-r0": 1511197215 + }, + "origin": "py-lockfile", + "dependencies": [ + "python3" + ] + }, + "fts-dev": { + "versions": { + "1.2.7-r0": 1509481205 + }, + "origin": "fts", + "dependencies": [ + "fts=1.2.7-r0", + "pkgconfig" + ], + "provides": [ + "pc:libfts=1.2.7", + "pc:musl-fts=1.2.7" + ] + }, + "perl-class-data-inheritable-doc": { + "versions": { + "0.08-r0": 1509470593 + }, + "origin": "perl-class-data-inheritable" + }, + "lutok-dev": { + "versions": { + "0.4-r1": 1509459744 + }, + "origin": "lutok", + "dependencies": [ + "lua5.2-dev", + "lutok=0.4-r1", + "pkgconfig" + ], + "provides": [ + "pc:lutok=0.4" + ] + }, + "util-linux-doc": { + "versions": { + "2.31.1-r0": 1541506293 + }, + "origin": "util-linux" + }, + "apr-util": { + "versions": { + "1.6.1-r1": 1510285059 + }, + "origin": "apr-util", + "dependencies": [ + "so:libapr-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libexpat.so.1" + ], + "provides": [ + "so:libaprutil-1.so.0=0.6.1" + ] + }, + "py2-parsing": { + "versions": { + "2.2.0-r0": 1509475642 + }, + "origin": "py-parsing", + "dependencies": [ + "python2" + ] + }, + "py-django-extra-views": { + "versions": { + "0.6.4-r0": 1509493857 + }, + "origin": "py-django-extra-views", + "dependencies": [ + "py-django" + ] + }, + "libxinerama": { + "versions": { + "1.1.3-r1": 1509468244 + }, + "origin": "libxinerama", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXinerama.so.1=1.0.0" + ] + }, + "iproute2-bash-completion": { + "versions": { + "4.13.0-r0": 1509481070 + }, + "origin": "iproute2" + }, + "cmph": { + "versions": { + "2.0-r1": 1509482578 + }, + "origin": "cmph", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcmph.so.0" + ], + "provides": [ + "cmd:cmph" + ] + }, + "postfix-pcre": { + "versions": { + "3.2.4-r1": 1510285340 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ], + "provides": [ + "so:postfix-pcre.so=0" + ] + }, + "libacl": { + "versions": { + "2.2.52-r3": 1509459587 + }, + "origin": "acl", + "dependencies": [ + "so:libattr.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libacl.so.1=1.1.0" + ] + }, + "dovecot-dev": { + "versions": { + "2.2.36.3-r0": 1554102386, + "2.2.36.4-r0": 1567075097 + }, + "origin": "dovecot" + }, + "gsl": { + "versions": { + "2.4-r0": 1509493183 + }, + "origin": "gsl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgsl.so.23=23.0.0", + "so:libgslcblas.so.0=0.0.0", + "cmd:gsl-histogram", + "cmd:gsl-randist" + ] + }, + "jq-dev": { + "versions": { + "1.5-r5": 1525102934 + }, + "origin": "jq", + "dependencies": [ + "jq=1.5-r5" + ] + }, + "mc": { + "versions": { + "4.8.20-r0": 1511871582 + }, + "origin": "mc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libssh2.so.1" + ], + "provides": [ + "cmd:mc", + "cmd:mcdiff", + "cmd:mcedit", + "cmd:mcview" + ] + }, + "goaccess-doc": { + "versions": { + "1.2-r1": 1509480167 + }, + "origin": "goaccess" + }, + "openldap-back-meta": { + "versions": { + "2.4.45-r3": 1510258133, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900515 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "gnuchess-doc": { + "versions": { + "6.2.5-r0": 1509496082 + }, + "origin": "gnuchess" + }, + "perl-mime-types": { + "versions": { + "2.14-r0": 1511188636 + }, + "origin": "perl-mime-types" + }, + "collectd-mqtt": { + "versions": { + "5.7.2-r0": 1510069654 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ] + }, + "perl-extutils-cchecker": { + "versions": { + "0.10-r0": 1511889284 + }, + "origin": "perl-extutils-cchecker", + "dependencies": [ + "perl-test-exception" + ] + }, + "glfw": { + "versions": { + "3.2.1-r2": 1510068481 + }, + "origin": "glfw", + "dependencies": [ + "so:libX11.so.6", + "so:libXcursor.so.1", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libglfw.so.3=3.2" + ] + }, + "lua-openrc": { + "versions": { + "0.2-r2": 1509492205 + }, + "origin": "lua-openrc" + }, + "varnish-dbg": { + "versions": { + "5.2.1-r0": 1511265090 + }, + "origin": "varnish", + "dependencies": [ + "gcc", + "libc-dev", + "libgcc" + ] + }, + "logrotate-doc": { + "versions": { + "3.13.0-r0": 1509473542 + }, + "origin": "logrotate" + }, + "gnokii-smsd-mysql": { + "versions": { + "0.6.31-r6": 1510069852 + }, + "origin": "gnokii", + "dependencies": [ + "gnokii-smsd", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "nagios-plugins-load": { + "versions": { + "2.2.1-r3": 1510288496 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "ipset": { + "versions": { + "6.34-r1": 1537515181 + }, + "origin": "ipset", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0" + ], + "provides": [ + "so:libipset.so.3=3.7.0", + "cmd:ipset" + ] + }, + "qemu-hppa": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-hppa" + ] + }, + "libx11-dev": { + "versions": { + "1.6.6-r0": 1538999940 + }, + "origin": "libx11", + "dependencies": [ + "libxcb-dev", + "xextproto", + "xf86bigfontproto-dev", + "xtrans", + "inputproto", + "libx11=1.6.6-r0", + "pc:kbproto", + "pc:xcb", + "pc:xcb>=1.11.1", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:x11-xcb=1.6.6", + "pc:x11=1.6.6" + ] + }, + "samba-winbind": { + "versions": { + "4.7.6-r3": 1555491787 + }, + "origin": "samba", + "dependencies": [ + "samba-common=4.7.6-r3", + "so:libMESSAGING-samba4.so", + "so:libads-samba4.so", + "so:libasn1util-samba4.so", + "so:libauth-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-ldap-common-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcliauth-samba4.so", + "so:libcom_err.so.2", + "so:libcommon-auth-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libdcerpc-samba4.so", + "so:libflag-mapping-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libidmap-samba4.so", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldap-2.4.so.2", + "so:liblibcli-lsa3-samba4.so", + "so:liblibcli-netlogon3-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmsrpc3-samba4.so", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libnpa-tstream-samba4.so", + "so:libnss-info-samba4.so", + "so:libpopt-samba3-samba4.so", + "so:libpopt.so.0", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb-common-samba4.so", + "so:libsecrets3-samba4.so", + "so:libsmb-transport-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-shim-samba4.so", + "so:libsmbldap.so.2", + "so:libsocket-blocking-samba4.so", + "so:libsys-rw-samba4.so", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtime-basic-samba4.so", + "so:libtrusts-util-samba4.so", + "so:libutil-tdb-samba4.so" + ], + "provides": [ + "cmd:winbindd" + ] + }, + "font-mutt-misc": { + "versions": { + "1.0.3-r0": 1509496611 + }, + "origin": "font-mutt-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "oidentd-doc": { + "versions": { + "2.0.8-r5": 1509483894 + }, + "origin": "oidentd" + }, + "libmagic": { + "versions": { + "5.32-r0": 1509461605, + "5.32-r1": 1563340161, + "5.32-r2": 1572348414 + }, + "origin": "file", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmagic.so.1=1.0.0" + ] + }, + "db-c++": { + "versions": { + "5.3.28-r0": 1509469316 + }, + "origin": "db", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libdb_cxx-5.3.so=0" + ] + }, + "policyd-spf-fs": { + "versions": { + "23-r3": 1509491339 + }, + "origin": "policyd-spf-fs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libspf2.so.2" + ], + "provides": [ + "cmd:policyd-spf-fs" + ] + }, + "lockfile-progs-doc": { + "versions": { + "0.1.17-r0": 1509468621 + }, + "origin": "lockfile-progs" + }, + "libxau": { + "versions": { + "1.0.8-r2": 1509461812 + }, + "origin": "libxau", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXau.so.6=6.0.0" + ] + }, + "readline-doc": { + "versions": { + "7.0.003-r0": 1509456789 + }, + "origin": "readline" + }, + "perl-module-refresh": { + "versions": { + "0.17-r1": 1510564499 + }, + "origin": "perl-module-refresh" + }, + "linux-pam-doc": { + "versions": { + "1.3.0-r0": 1509473197 + }, + "origin": "linux-pam" + }, + "adwaita-icon-theme": { + "versions": { + "3.26.0-r0": 1510076149 + }, + "origin": "adwaita-icon-theme" + }, + "sox-dev": { + "versions": { + "14.4.2-r0": 1510074686 + }, + "origin": "sox", + "dependencies": [ + "pkgconfig", + "sox=14.4.2-r0" + ], + "provides": [ + "pc:sox=14.4.2" + ] + }, + "libraw1394-dev": { + "versions": { + "2.1.2-r0": 1509470072 + }, + "origin": "libraw1394", + "dependencies": [ + "libraw1394=2.1.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libraw1394=2.1.2" + ] + }, + "ilbc": { + "versions": { + "0.0.1-r0": 1510584810 + }, + "origin": "ilbc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libilbc.so.0=0.0.2" + ] + }, + "recode": { + "versions": { + "3.6-r1": 1509485764 + }, + "origin": "recode", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:librecode.so.0=0.0.0", + "cmd:recode" + ] + }, + "hunspell-doc": { + "versions": { + "1.6.2-r1": 1509477668 + }, + "origin": "hunspell" + }, + "perl-ipc-sharelite-doc": { + "versions": { + "0.17-r2": 1509469072 + }, + "origin": "perl-ipc-sharelite" + }, + "yasm-doc": { + "versions": { + "1.3.0-r1": 1509473588 + }, + "origin": "yasm" + }, + "umix-doc": { + "versions": { + "1.0.2-r7": 1509480173 + }, + "origin": "umix" + }, + "parted-doc": { + "versions": { + "3.2-r6": 1509482206 + }, + "origin": "parted" + }, + "postgresql-bdr": { + "versions": { + "9.4.10_p1-r3": 1510259287 + }, + "origin": "postgresql-bdr", + "dependencies": [ + "bash", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "postgresql", + "so:postgresql-bdr:libecpg.so.6=6.6", + "so:postgresql-bdr:libecpg_compat.so.3=3.6", + "so:postgresql-bdr:libpgtypes.so.3=3.5", + "cmd:createlang", + "cmd:droplang", + "cmd:ecpg", + "cmd:initdb", + "cmd:pg_config", + "cmd:pg_controldata", + "cmd:pg_ctl", + "cmd:pg_receivexlog", + "cmd:pg_resetxlog", + "cmd:postgres", + "cmd:postmaster" + ] + }, + "djbdns-common": { + "versions": { + "1.05-r47": 1509488767 + }, + "origin": "djbdns", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dnsip", + "cmd:dnsqr" + ] + }, + "perl-http-date": { + "versions": { + "6.02-r1": 1509464355 + }, + "origin": "perl-http-date", + "dependencies": [ + "perl" + ] + }, + "py2-simplejson": { + "versions": { + "3.8.2-r2": 1509494750 + }, + "origin": "py-simplejson", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "open-isns-doc": { + "versions": { + "0.97-r2": 1510259303 + }, + "origin": "open-isns" + }, + "py3-cairo-dev": { + "versions": { + "1.10.0-r0": 1509481861 + }, + "origin": "py3-cairo", + "dependencies": [ + "pc:cairo", + "pkgconfig" + ], + "provides": [ + "pc:py3cairo=1.10.0" + ] + }, + "perl-json-doc": { + "versions": { + "2.97000-r0": 1511989778 + }, + "origin": "perl-json" + }, + "boost-iostreams": { + "versions": { + "1.62.0-r5": 1509465875 + }, + "origin": "boost", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libboost_iostreams-mt.so.1.62.0=1.62.0", + "so:libboost_iostreams.so.1.62.0=1.62.0" + ] + }, + "lua5.3": { + "versions": { + "5.3.5-r2": 1557163184 + }, + "origin": "lua5.3", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblinenoise.so.0", + "so:liblua-5.3.so.0" + ], + "provides": [ + "cmd:lua5.3", + "cmd:luac5.3" + ] + }, + "udisks": { + "versions": { + "1.0.5-r3": 1510073404 + }, + "origin": "udisks", + "dependencies": [ + "so:libatasmart.so.4", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libdevmapper.so.1.02", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libparted.so.2", + "so:libpolkit-gobject-1.so.0", + "so:libsgutils2.so.2", + "so:libudev.so.1" + ], + "provides": [ + "cmd:udisks", + "cmd:udisks-tcp-bridge", + "cmd:umount.udisks" + ] + }, + "libmikmod-dev": { + "versions": { + "3.3.11.1-r0": 1509481761 + }, + "origin": "libmikmod", + "dependencies": [ + "libmikmod=3.3.11.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmikmod=3.3.11", + "cmd:libmikmod-config" + ] + }, + "perl-class-singleton": { + "versions": { + "1.5-r0": 1509485598 + }, + "origin": "perl-class-singleton" + }, + "xbanish-doc": { + "versions": { + "1.5-r0": 1509489791 + }, + "origin": "xbanish" + }, + "perl-device-serialport-doc": { + "versions": { + "1.04-r10": 1509489402 + }, + "origin": "perl-device-serialport" + }, + "perl-ipc-sharelite": { + "versions": { + "0.17-r2": 1509469073 + }, + "origin": "perl-ipc-sharelite", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.1-posix": { + "versions": { + "33.4.0-r0": 1509468343 + }, + "origin": "lua-posix", + "dependencies": [ + "lua5.1-bitlib<26", + "so:libc.musl-x86_64.so.1" + ] + }, + "gtkglext-doc": { + "versions": { + "1.2.0-r11": 1510075507 + }, + "origin": "gtkglext" + }, + "xf86-video-savage-doc": { + "versions": { + "2.3.9-r0": 1510076049 + }, + "origin": "xf86-video-savage" + }, + "jfsutils": { + "versions": { + "1.1.15-r1": 1509482135 + }, + "origin": "jfsutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:fsck.jfs", + "cmd:jfs_debugfs", + "cmd:jfs_fsck", + "cmd:jfs_fscklog", + "cmd:jfs_logdump", + "cmd:jfs_mkfs", + "cmd:jfs_tune", + "cmd:mkfs.jfs" + ] + }, + "ctags-doc": { + "versions": { + "5.8-r5": 1509491649 + }, + "origin": "ctags" + }, + "dropbear-openrc": { + "versions": { + "2018.76-r2": 1537439117 + }, + "origin": "dropbear" + }, + "pax-utils": { + "versions": { + "1.2.2-r1": 1509459679 + }, + "origin": "pax-utils", + "dependencies": [ + "scanelf", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2" + ], + "provides": [ + "cmd:dumpelf", + "cmd:pspax", + "cmd:scanmacho", + "cmd:symtree" + ] + }, + "asterisk-srtp": { + "versions": { + "15.6.1-r0": 1537795343, + "15.6.2-r0": 1568705005 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "geeqie": { + "versions": { + "1.3-r1": 1510075967 + }, + "origin": "geeqie", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libexiv2.so.14", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:liblua.so.5", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libstdc++.so.6", + "so:libtiff.so.5" + ], + "provides": [ + "cmd:geeqie" + ] + }, + "socat-doc": { + "versions": { + "1.7.3.2-r3": 1510260539 + }, + "origin": "socat" + }, + "lua5.3-struct": { + "versions": { + "0.2-r2": 1509494933 + }, + "origin": "lua-struct", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "tolua++": { + "versions": { + "1.0.93-r2": 1509479805 + }, + "origin": "tolua++", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ], + "provides": [ + "cmd:tolua++" + ] + }, + "libgudev": { + "versions": { + "230-r2": 1509470859 + }, + "origin": "libgudev", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libudev.so.1" + ], + "provides": [ + "so:libgudev-1.0.so.0=0.2.0" + ] + }, + "openldap-overlay-retcode": { + "versions": { + "2.4.45-r3": 1510258135, + "2.4.46-r0": 1565073941, + "2.4.48-r0": 1566900518 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "snappy-dev": { + "versions": { + "1.1.4-r2": 1509483342 + }, + "origin": "snappy", + "dependencies": [ + "pkgconfig", + "snappy=1.1.4-r2" + ], + "provides": [ + "pc:snappy=1.1.4" + ] + }, + "libpaper-doc": { + "versions": { + "1.1.24-r3": 1509465360 + }, + "origin": "libpaper" + }, + "popt-doc": { + "versions": { + "1.16-r7": 1509462411 + }, + "origin": "popt" + }, + "collectd-postgresql": { + "versions": { + "5.7.2-r0": 1510069646 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "perl-net-dns": { + "versions": { + "1.13-r0": 1509469031 + }, + "origin": "perl-net-dns", + "dependencies": [ + "perl" + ] + }, + "gettext-dev": { + "versions": { + "0.19.8.1-r1": 1509459196 + }, + "origin": "gettext", + "dependencies": [ + "gettext-asprintf=0.19.8.1-r1", + "gettext-libs=0.19.8.1-r1", + "gettext=0.19.8.1-r1", + "libintl=0.19.8.1-r1" + ] + }, + "gtk2-xfce-engine": { + "versions": { + "3.2.0-r2": 1510920446 + }, + "origin": "gtk-xfce-engine", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "pianobar": { + "versions": { + "2017.08.30-r0": 1510074140 + }, + "origin": "pianobar", + "dependencies": [ + "so:libao.so.4", + "so:libavcodec.so.57", + "so:libavfilter.so.6", + "so:libavformat.so.57", + "so:libavutil.so.55", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libgcrypt.so.20", + "so:libjson-c.so.2" + ], + "provides": [ + "cmd:pianobar" + ] + }, + "audit-dev": { + "versions": { + "2.7.7-r1": 1509491478 + }, + "origin": "audit", + "dependencies": [ + "linux-headers", + "audit-libs=2.7.7-r1" + ] + }, + "glib-networking-lang": { + "versions": { + "2.54.1-r0": 1509477723 + }, + "origin": "glib-networking", + "dependencies": [ + "ca-certificates" + ] + }, + "bluez-hid2hci": { + "versions": { + "5.47-r3": 1510069787 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ] + }, + "s6-linux-init-dev": { + "versions": { + "0.3.1.0-r0": 1509492622 + }, + "origin": "s6-linux-init" + }, + "perl-path-class": { + "versions": { + "0.37-r0": 1509480566 + }, + "origin": "perl-path-class" + }, + "hicolor-icon-theme": { + "versions": { + "0.17-r0": 1509465966 + }, + "origin": "hicolor-icon-theme" + }, + "wayland": { + "versions": { + "1.14.0-r2": 1510066937 + }, + "origin": "wayland", + "dependencies": [ + "wayland-libs-client", + "wayland-libs-cursor", + "wayland-libs-server" + ] + }, + "python2": { + "versions": { + "2.7.15-r2": 1534944323 + }, + "origin": "python2", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libexpat.so.1", + "so:libffi.so.6", + "so:libgdbm_compat.so.4", + "so:libncursesw.so.6", + "so:libpanelw.so.6", + "so:libreadline.so.7", + "so:libsqlite3.so.0", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "python=2.7.15-r2", + "so:libpython2.7.so.1.0=1.0", + "cmd:idle", + "cmd:pydoc", + "cmd:python", + "cmd:python2", + "cmd:python2.7", + "cmd:smtpd.py" + ] + }, + "perl-capture-tiny-doc": { + "versions": { + "0.46-r0": 1509485666 + }, + "origin": "perl-capture-tiny" + }, + "perl-text-soundex": { + "versions": { + "3.05-r0": 1509470837 + }, + "origin": "perl-text-soundex", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-sphinx": { + "versions": { + "1.6.5-r0": 1509480687 + }, + "origin": "py-sphinx", + "dependencies": [ + "make", + "py2-docutils", + "py2-jinja2", + "py2-pygments", + "py2-six", + "py2-sphinx_rtd_theme", + "py2-alabaster<0.8", + "py2-babel", + "py2-snowballstemmer", + "py2-imagesize", + "py2-requests", + "py2-sphinxcontrib-websupport", + "py2-typing", + "python2" + ], + "provides": [ + "cmd:sphinx-apidoc-2", + "cmd:sphinx-autogen-2", + "cmd:sphinx-build-2", + "cmd:sphinx-quickstart-2" + ] + }, + "libattr": { + "versions": { + "2.4.47-r6": 1509457001 + }, + "origin": "attr", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libattr.so.1=1.1.0" + ] + }, + "qemu-system-microblaze": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-microblaze" + ] + }, + "libunwind-doc": { + "versions": { + "1.2.1-r1": 1509493800 + }, + "origin": "libunwind" + }, + "libksba-doc": { + "versions": { + "1.3.5-r0": 1509472891 + }, + "origin": "libksba" + }, + "lua-dbi": { + "versions": { + "0.6-r1": 1511483403 + }, + "origin": "lua-dbi" + }, + "libuuid": { + "versions": { + "2.31.1-r0": 1541506294 + }, + "origin": "util-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libuuid.so.1=1.3.0" + ] + }, + "qemu-system-mipsel": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-mipsel" + ] + }, + "lua-hashids": { + "versions": { + "1.0.6-r1": 1509480766 + }, + "origin": "lua-hashids" + }, + "libgpg-error-doc": { + "versions": { + "1.27-r1": 1509459212 + }, + "origin": "libgpg-error" + }, + "perl-cache-simple-timedexpiry": { + "versions": { + "0.27-r1": 1510564524 + }, + "origin": "perl-cache-simple-timedexpiry", + "dependencies": [ + "perl" + ] + }, + "libatomic_ops-dev": { + "versions": { + "7.4.8-r0": 1509878778 + }, + "origin": "libatomic_ops", + "dependencies": [ + "libatomic_ops=7.4.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:atomic_ops=7.4.8" + ] + }, + "rest": { + "versions": { + "0.8.1-r0": 1509492761 + }, + "origin": "rest", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libsoup-2.4.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "so:librest-0.7.so.0=0.0.0", + "so:librest-extras-0.7.so.0=0.0.0" + ] + }, + "elinks-doc": { + "versions": { + "0.13-r4": 1510261265 + }, + "origin": "elinks" + }, + "tmux": { + "versions": { + "2.6-r0": 1509469998 + }, + "origin": "tmux", + "dependencies": [ + "ncurses-terminfo", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:tmux" + ] + }, + "pacman-doc": { + "versions": { + "5.0.2-r1": 1510588357 + }, + "origin": "pacman" + }, + "libxxf86misc-doc": { + "versions": { + "1.0.3-r1": 1509473661 + }, + "origin": "libxxf86misc" + }, + "clang-libs": { + "versions": { + "5.0.0-r0": 1510683247 + }, + "origin": "clang", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libclang.so.5.0=5.0" + ] + }, + "libdvdcss": { + "versions": { + "1.4.0-r1": 1509480289 + }, + "origin": "libdvdcss", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdvdcss.so.2=2.2.0" + ] + }, + "libevdev-doc": { + "versions": { + "1.5.7-r1": 1509475753 + }, + "origin": "libevdev" + }, + "bluez-deprecated": { + "versions": { + "5.47-r3": 1510069787 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libreadline.so.7", + "so:libudev.so.1" + ], + "provides": [ + "cmd:ciptool", + "cmd:gatttool", + "cmd:hciattach", + "cmd:hciconfig", + "cmd:hcidump", + "cmd:hcitool", + "cmd:rfcomm", + "cmd:sdptool" + ] + }, + "squashfs-tools": { + "versions": { + "4.3-r4": 1511196129 + }, + "origin": "squashfs-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblz4.so.1", + "so:liblzma.so.5", + "so:liblzo2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:mksquashfs", + "cmd:unsquashfs" + ] + }, + "mailcap": { + "versions": { + "2.1.48-r0": 1509474793 + }, + "origin": "mailcap" + }, + "bmd-tools": { + "versions": { + "1.0.2-r0": 1527191317 + }, + "origin": "bmd-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "cmd:bmd-extractfw", + "cmd:bmd-streamer" + ] + }, + "lang": { + "versions": { + "0.1-r0": 1509480915 + }, + "origin": "lang" + }, + "acf-freeswitch": { + "versions": { + "0.8.0-r2": 1510585376 + }, + "origin": "acf-freeswitch", + "dependencies": [ + "acf-core", + "freeswitch", + "lua-xml" + ] + }, + "slibtool": { + "versions": { + "0.5.17-r1": 1510843235 + }, + "origin": "slibtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:clibtool", + "cmd:clibtool-shared", + "cmd:clibtool-static", + "cmd:dlibtool", + "cmd:dlibtool-shared", + "cmd:dlibtool-static", + "cmd:slibtool", + "cmd:slibtool-shared", + "cmd:slibtool-static" + ] + }, + "libdaemon": { + "versions": { + "0.14-r2": 1509465393 + }, + "origin": "libdaemon", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdaemon.so.0=0.5.0" + ] + }, + "libgnome": { + "versions": { + "2.32.1-r7": 1510931516 + }, + "origin": "libgnome", + "dependencies": [ + "/bin/sh", + "so:libORBit-2.so.0", + "so:libbonobo-2.so.0", + "so:libbonobo-activation.so.4", + "so:libc.musl-x86_64.so.1", + "so:libcanberra.so.0", + "so:libgconf-2.so.4", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnomevfs-2.so.0", + "so:libgobject-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libintl.so.8", + "so:libpopt.so.0" + ], + "provides": [ + "so:libgnome-2.so.0=0.3200.1", + "cmd:gnome-open" + ] + }, + "ghostscript-gtk": { + "versions": { + "9.26-r2": 1554362768, + "9.26-r3": 1565700439, + "9.26-r4": 1571234493 + }, + "origin": "ghostscript", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgs.so.9", + "so:libgtk-3.so.0" + ], + "provides": [ + "cmd:gsx" + ] + }, + "perl-net-openssh-doc": { + "versions": { + "0.74-r0": 1509473012 + }, + "origin": "perl-net-openssh" + }, + "perl-xml-namespacesupport": { + "versions": { + "1.12-r0": 1509475902 + }, + "origin": "perl-xml-namespacesupport", + "dependencies": [ + "perl" + ] + }, + "cmake-doc": { + "versions": { + "3.9.5-r0": 1509907172 + }, + "origin": "cmake" + }, + "varnish": { + "versions": { + "5.2.1-r0": 1511265090 + }, + "origin": "varnish", + "dependencies": [ + "gcc", + "libc-dev", + "libgcc", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libedit.so.0", + "so:libexecinfo.so.1", + "so:libncursesw.so.6", + "so:libpcre.so.1", + "so:libvarnishapi.so.1" + ], + "provides": [ + "cmd:varnishadm", + "cmd:varnishd", + "cmd:varnishhist", + "cmd:varnishlog", + "cmd:varnishncsa", + "cmd:varnishstat", + "cmd:varnishtest", + "cmd:varnishtop" + ] + }, + "jsoncpp": { + "versions": { + "1.8.1-r1": 1510311447 + }, + "origin": "jsoncpp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libjsoncpp.so.0=0.0.0" + ] + }, + "eggdrop-gseen": { + "versions": { + "1.6.21-r2": 1509491904 + }, + "origin": "eggdrop", + "dependencies": [ + "tcl", + "so:libc.musl-x86_64.so.1", + "so:libtcl8.6.so" + ] + }, + "pinentry-doc": { + "versions": { + "1.0.0-r0": 1510587941 + }, + "origin": "pinentry" + }, + "lua5.2-lyaml": { + "versions": { + "6.1.3-r1": 1509473485 + }, + "origin": "lua-lyaml", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libyaml-0.so.2" + ] + }, + "perl-package-stash-xs-doc": { + "versions": { + "0.28-r4": 1509474037 + }, + "origin": "perl-package-stash-xs" + }, + "py3-cryptography": { + "versions": { + "2.0.3-r1": 1510288190, + "2.1.4-r0": 1559543208 + }, + "origin": "py-cryptography", + "dependencies": [ + "py3-cffi", + "py3-idna", + "py3-asn1crypto", + "py3-six", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpython3.6m.so.1.0", + "so:libssl.so.44" + ] + }, + "libarchive": { + "versions": { + "3.3.2-r2": 1510258000, + "3.3.3-r0": 1566312165, + "3.3.3-r1": 1572677952 + }, + "origin": "libarchive", + "dependencies": [ + "so:libacl.so.1", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:liblz4.so.1", + "so:liblzma.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libarchive.so.13=13.3.3" + ] + }, + "lzo-doc": { + "versions": { + "2.10-r2": 1509462188 + }, + "origin": "lzo" + }, + "vim-doc": { + "versions": { + "8.0.1359-r0": 1512029967, + "8.0.1359-r1": 1559759710, + "8.0.1359-r2": 1561188677 + }, + "origin": "vim" + }, + "brlaser-doc": { + "versions": { + "3-r0": 1510075894 + }, + "origin": "brlaser" + }, + "xf86-input-mouse-dev": { + "versions": { + "1.9.2-r0": 1510074097 + }, + "origin": "xf86-input-mouse", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xorg-mouse=1.9.2" + ] + }, + "avahi-ui-dev": { + "versions": { + "0.6.31-r7": 1510076322 + }, + "origin": "avahi-ui", + "dependencies": [ + "gtk+3.0-dev", + "gtk+2.0-dev", + "gdbm-dev", + "avahi-dev", + "avahi-ui-gtk3=0.6.31-r7", + "avahi-ui=0.6.31-r7", + "pc:avahi-client", + "pc:avahi-glib", + "pc:gtk+-2.0", + "pc:gtk+-3.0", + "pkgconfig" + ], + "provides": [ + "pc:avahi-ui-gtk3=0.6.31", + "pc:avahi-ui=0.6.31" + ] + }, + "libxfixes-doc": { + "versions": { + "5.0.3-r1": 1509464684 + }, + "origin": "libxfixes" + }, + "libxcomposite-dev": { + "versions": { + "0.4.4-r1": 1509465984 + }, + "origin": "libxcomposite", + "dependencies": [ + "libxext-dev", + "libxcomposite=0.4.4-r1", + "pc:compositeproto>=0.4", + "pc:x11", + "pc:xfixes", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xcomposite=0.4.4" + ] + }, + "dnsmasq-dnssec": { + "versions": { + "2.78-r3": 1540537793 + }, + "origin": "dnsmasq", + "dependencies": [ + "!dnsmasq", + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libhogweed.so.4", + "so:libnettle.so.6" + ], + "provides": [ + "cmd:dnsmasq" + ] + }, + "perl-hash-multivalue": { + "versions": { + "0.16-r0": 1511871381 + }, + "origin": "perl-hash-multivalue", + "dependencies": [ + "perl" + ] + }, + "lua5.3-rex-posix": { + "versions": { + "2.9.0-r0": 1510619366 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "bind-doc": { + "versions": { + "9.11.6_p1-r1": 1556872767, + "9.11.8-r0": 1561323229 + }, + "origin": "bind" + }, + "xdpyinfo": { + "versions": { + "1.3.2-r0": 1509491068 + }, + "origin": "xdpyinfo", + "dependencies": [ + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXi.so.6", + "so:libXtst.so.6", + "so:libc.musl-x86_64.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "cmd:xdpyinfo" + ] + }, + "installkernel": { + "versions": { + "3.5-r0": 1509477809 + }, + "origin": "installkernel", + "provides": [ + "cmd:installkernel" + ] + }, + "eudev": { + "versions": { + "3.2.4-r1": 1509466081 + }, + "origin": "eudev", + "dependencies": [ + "udev-init-scripts", + "eudev-libs=3.2.4-r1", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libkmod.so.2" + ], + "provides": [ + "udev=176", + "cmd:setup-udev", + "cmd:udevadm", + "cmd:udevd" + ] + }, + "libpcre2-16": { + "versions": { + "10.30-r0": 1509461774 + }, + "origin": "pcre2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre2-16.so.0=0.6.0" + ] + }, + "font-bitstream-75dpi": { + "versions": { + "1.0.3-r0": 1509480923 + }, + "origin": "font-bitstream-75dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "rxvt-unicode-terminfo": { + "versions": { + "9.22-r2": 1509476789 + }, + "origin": "rxvt-unicode", + "dependencies": [ + "rxvt-unicode-terminfo" + ] + }, + "gphoto2-lang": { + "versions": { + "2.5.14-r0": 1509493216 + }, + "origin": "gphoto2" + }, + "gvfs-lang": { + "versions": { + "1.34.1-r0": 1511430257, + "1.34.1-r1": 1563787223 + }, + "origin": "gvfs" + }, + "git-cvs": { + "versions": { + "2.15.3-r0": 1540401293, + "2.15.4-r0": 1576015190 + }, + "origin": "git", + "dependencies": [ + "perl", + "perl-git=2.15.4-r0", + "cvs", + "perl-dbd-sqlite" + ], + "provides": [ + "cmd:git-cvsserver" + ] + }, + "ulogd-doc": { + "versions": { + "2.0.5-r4": 1509480398 + }, + "origin": "ulogd" + }, + "perl-dist-checkconflicts": { + "versions": { + "0.11-r0": 1509474029 + }, + "origin": "perl-dist-checkconflicts", + "dependencies": [ + "perl-list-moreutils", + "perl-module-runtime" + ] + }, + "libxres-doc": { + "versions": { + "1.2.0-r0": 1509468563 + }, + "origin": "libxres" + }, + "gst-plugins-bad-doc": { + "versions": { + "1.12.3-r2": 1512039893 + }, + "origin": "gst-plugins-bad" + }, + "xtables-addons-doc": { + "versions": { + "2.11-r1": 1509469987 + }, + "origin": "xtables-addons" + }, + "libc6-compat": { + "versions": { + "1.1.18-r3": 1518031143, + "1.1.18-r4": 1565163296 + }, + "origin": "musl", + "dependencies": [ + "musl=1.1.18-r4" + ] + }, + "py3-babel": { + "versions": { + "2.5.1-r0": 1509490097 + }, + "origin": "py-babel", + "dependencies": [ + "py3-tz", + "python3" + ], + "provides": [ + "cmd:pybabel" + ] + }, + "openbox-gnome": { + "versions": { + "3.6.1-r1": 1510073885 + }, + "origin": "openbox", + "dependencies": [ + "so:libX11.so.6", + "so:libXau.so.6", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "cmd:gdm-control", + "cmd:gnome-panel-control", + "cmd:openbox-gnome-session" + ] + }, + "libffi": { + "versions": { + "3.2.1-r4": 1509458693 + }, + "origin": "libffi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libffi.so.6=6.0.4" + ] + }, + "m4": { + "versions": { + "1.4.18-r0": 1509456654 + }, + "origin": "m4", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:m4" + ] + }, + "lua5.3-sircbot": { + "versions": { + "0.4-r1": 1509492359 + }, + "origin": "sircbot", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "abiword-plugin-openwriter": { + "versions": { + "3.0.2-r1": 1510073365 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "lua5.1-dbi-mysql": { + "versions": { + "0.6-r1": 1511483402 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "libetpan-doc": { + "versions": { + "1.8-r2": 1509721604 + }, + "origin": "libetpan" + }, + "argp-standalone": { + "versions": { + "1.3-r2": 1509477695 + }, + "origin": "argp-standalone" + }, + "graphviz": { + "versions": { + "2.40.1-r0": 1510066920 + }, + "origin": "graphviz", + "dependencies": [ + "/bin/sh", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libltdl.so.7", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpangoft2-1.0.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libcdt.so.5=5.0.0", + "so:libcgraph.so.6=6.0.0", + "so:libgvc.so.6=6.0.0", + "so:libgvplugin_core.so.6=6.0.0", + "so:libgvplugin_dot_layout.so.6=6.0.0", + "so:libgvplugin_neato_layout.so.6=6.0.0", + "so:libgvplugin_pango.so.6=6.0.0", + "so:libgvplugin_xlib.so.6=6.0.0", + "so:libgvpr.so.2=2.0.0", + "so:liblab_gamut.so.1=1.0.0", + "so:libpathplan.so.4=4.0.0", + "so:libxdot.so.4=4.0.0", + "cmd:acyclic", + "cmd:bcomps", + "cmd:ccomps", + "cmd:circo", + "cmd:cluster", + "cmd:dijkstra", + "cmd:dot", + "cmd:dot2gxl", + "cmd:dot_builtins", + "cmd:dotty", + "cmd:edgepaint", + "cmd:fdp", + "cmd:gc", + "cmd:gml2gv", + "cmd:graphml2gv", + "cmd:gv2gml", + "cmd:gv2gxl", + "cmd:gvcolor", + "cmd:gvgen", + "cmd:gvmap", + "cmd:gvmap.sh", + "cmd:gvpack", + "cmd:gvpr", + "cmd:gxl2dot", + "cmd:gxl2gv", + "cmd:lneato", + "cmd:mm2gv", + "cmd:neato", + "cmd:nop", + "cmd:osage", + "cmd:patchwork", + "cmd:prune", + "cmd:sccmap", + "cmd:sfdp", + "cmd:tred", + "cmd:twopi", + "cmd:unflatten", + "cmd:vimdot" + ] + }, + "fontconfig-doc": { + "versions": { + "2.12.6-r0": 1509462083 + }, + "origin": "fontconfig" + }, + "dahdi-tools-doc": { + "versions": { + "2.11.1-r0": 1509476155 + }, + "origin": "dahdi-tools" + }, + "acf-nsd": { + "versions": { + "0.0.1-r0": 1510073459 + }, + "origin": "acf-nsd", + "dependencies": [ + "acf-core", + "nsd" + ] + }, + "libvncserver": { + "versions": { + "0.9.11-r2": 1533743207, + "0.9.11-r3": 1572818864 + }, + "origin": "libvncserver", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libz.so.1" + ], + "provides": [ + "so:libvncclient.so.1=1.0.0", + "so:libvncserver.so.1=1.0.0" + ] + }, + "lua5.1-pty": { + "versions": { + "1.2.1-r1": 1509496423 + }, + "origin": "lua-pty", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "py3-newt": { + "versions": { + "0.52.20-r0": 1509476145 + }, + "origin": "newt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnewt.so.0.52" + ] + }, + "mtools-doc": { + "versions": { + "4.0.18-r2": 1509462431 + }, + "origin": "mtools" + }, + "lksctp-tools-dev": { + "versions": { + "1.0.17-r0": 1509492326 + }, + "origin": "lksctp-tools", + "dependencies": [ + "lksctp-tools=1.0.17-r0", + "pkgconfig" + ], + "provides": [ + "pc:libsctp=1.0.17" + ] + }, + "xf86vidmodeproto": { + "versions": { + "2.3.1-r3": 1509466231 + }, + "origin": "xf86vidmodeproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xf86vidmodeproto=2.3.1" + ] + }, + "lua5.3-sec": { + "versions": { + "0.6-r3": 1510260656 + }, + "origin": "lua-sec", + "dependencies": [ + "lua5.3-socket", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "exo-lang": { + "versions": { + "0.11.5-r0": 1510068072 + }, + "origin": "exo", + "dependencies": [ + "hicolor-icon-theme" + ] + }, + "transmission-lang": { + "versions": { + "2.92-r8": 1510932986 + }, + "origin": "transmission" + }, + "mpt-status": { + "versions": { + "1.2.0-r0": 1509488650 + }, + "origin": "mpt-status", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mpt-status" + ] + }, + "perl-class-accessor-doc": { + "versions": { + "0.34-r0": 1509474097 + }, + "origin": "perl-class-accessor" + }, + "nfs-utils-dbg": { + "versions": { + "2.1.1-r4": 1509492107 + }, + "origin": "nfs-utils", + "dependencies": [ + "rpcbind" + ] + }, + "lua5.3-crypto": { + "versions": { + "0.3.2-r5": 1510261401 + }, + "origin": "lua-crypto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "uwsgi-emperor_zeromq": { + "versions": { + "2.0.17-r0": 1522154654 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libzmq.so.5" + ] + }, + "sdl2_image-dev": { + "versions": { + "2.0.2-r1": 1528280868, + "2.0.5-r0": 1568803593 + }, + "origin": "sdl2_image", + "dependencies": [ + "pc:sdl2>=2.0.8", + "pkgconfig", + "sdl2_image=2.0.5-r0" + ], + "provides": [ + "pc:SDL2_image=2.0.5" + ] + }, + "uwsgi-transformation_chunked": { + "versions": { + "2.0.17-r0": 1522154660 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libxcursor-dev": { + "versions": { + "1.1.15-r0": 1511975691 + }, + "origin": "libxcursor", + "dependencies": [ + "libxcursor=1.1.15-r0", + "pc:x11", + "pc:xfixes", + "pc:xproto", + "pc:xrender", + "pkgconfig" + ], + "provides": [ + "pc:xcursor=1.1.15" + ] + }, + "py-dbus": { + "versions": { + "1.2.0-r2": 1509465384 + }, + "origin": "py-dbus", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libglib-2.0.so.0" + ] + }, + "libxvmc": { + "versions": { + "1.0.10-r1": 1509467179 + }, + "origin": "libxvmc", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXvMC.so.1=1.0.0", + "so:libXvMCW.so.1=1.0.0" + ] + }, + "libxslt-doc": { + "versions": { + "1.1.31-r1": 1555487793, + "1.1.31-r2": 1572541164 + }, + "origin": "libxslt" + }, + "expect-doc": { + "versions": { + "5.45-r4": 1509489557 + }, + "origin": "expect" + }, + "gvfs-afp": { + "versions": { + "1.34.1-r0": 1511430257, + "1.34.1-r1": 1563787223 + }, + "origin": "gvfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgvfscommon.so", + "so:libgvfsdaemon.so", + "so:libintl.so.8" + ] + }, + "haproxy-systemd-wrapper": { + "versions": { + "1.7.9-r1": 1510261284 + }, + "origin": "haproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:haproxy-systemd-wrapper" + ] + }, + "nginx-mod-http-image-filter": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3" + ] + }, + "xproto": { + "versions": { + "7.0.31-r1": 1509461806 + }, + "origin": "xproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xproto=7.0.31" + ] + }, + "font-cursor-misc": { + "versions": { + "1.0.3-r1": 1509473684 + }, + "origin": "font-cursor-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig", + "util-macros" + ] + }, + "atf-doc": { + "versions": { + "0.21-r1": 1509459730 + }, + "origin": "atf" + }, + "glibmm-doc": { + "versions": { + "2.50.1-r0": 1509472979 + }, + "origin": "glibmm" + }, + "imake-doc": { + "versions": { + "1.0.7-r1": 1509476556 + }, + "origin": "imake" + }, + "lua5.3-doc": { + "versions": { + "5.3.5-r2": 1557163184 + }, + "origin": "lua5.3" + }, + "setup-box-doc": { + "versions": { + "1.0.1-r0": 1509490944 + }, + "origin": "setup-box", + "dependencies": [ + "jq" + ] + }, + "libical-dev": { + "versions": { + "2.0.0-r2": 1509468578 + }, + "origin": "libical", + "dependencies": [ + "libical=2.0.0-r2", + "pkgconfig" + ], + "provides": [ + "pc:libical=2.0" + ] + }, + "abcde": { + "versions": { + "2.8.1-r1": 1509551899 + }, + "origin": "abcde", + "dependencies": [ + "bash", + "cd-discid", + "py-eyed3" + ], + "provides": [ + "cmd:abcde", + "cmd:abcde-musicbrainz-tool", + "cmd:cddb-tool" + ] + }, + "perl-module-install-doc": { + "versions": { + "1.18-r0": 1510352967 + }, + "origin": "perl-module-install" + }, + "libedit": { + "versions": { + "20170329.3.1-r3": 1509461725 + }, + "origin": "libedit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "so:libedit.so.0=0.0.56" + ] + }, + "startup-notification": { + "versions": { + "0.12-r3": 1509468554 + }, + "origin": "startup-notification", + "dependencies": [ + "so:libX11-xcb.so.1", + "so:libc.musl-x86_64.so.1", + "so:libxcb-util.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libstartup-notification-1.so.0=0.0.0" + ] + }, + "hwdata-pnp": { + "versions": { + "0.305-r0": 1509468678 + }, + "origin": "hwdata" + }, + "perl-error-doc": { + "versions": { + "0.17025-r0": 1509461798 + }, + "origin": "perl-error" + }, + "mariadb-common": { + "versions": { + "10.1.38-r1": 1551187990, + "10.1.40-r0": 1560354886, + "10.1.41-r0": 1565163111 + }, + "origin": "mariadb", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "hylafax": { + "versions": { + "6.0.6-r4": 1539067243 + }, + "origin": "hylafax", + "dependencies": [ + "ghostscript", + "bash", + "tiff-tools", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libtiff.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libhylafax-6.0.so.6=6", + "cmd:choptest", + "cmd:cqtest", + "cmd:dialtest", + "cmd:edit-faxcover", + "cmd:faxabort", + "cmd:faxaddmodem", + "cmd:faxadduser", + "cmd:faxalter", + "cmd:faxanswer", + "cmd:faxconfig", + "cmd:faxcover", + "cmd:faxcron", + "cmd:faxdeluser", + "cmd:faxgetty", + "cmd:faxinfo", + "cmd:faxlock", + "cmd:faxmail", + "cmd:faxmodem", + "cmd:faxmsg", + "cmd:faxq", + "cmd:faxqclean", + "cmd:faxquit", + "cmd:faxrm", + "cmd:faxsend", + "cmd:faxsetup", + "cmd:faxsetup.bsdi", + "cmd:faxsetup.irix", + "cmd:faxsetup.linux", + "cmd:faxstat", + "cmd:faxstate", + "cmd:faxwatch", + "cmd:hfaxd", + "cmd:hylafax", + "cmd:lockname", + "cmd:ondelay", + "cmd:pagesend", + "cmd:probemodem", + "cmd:recvstats", + "cmd:sendfax", + "cmd:sendpage", + "cmd:tagtest", + "cmd:textfmt", + "cmd:tiffcheck", + "cmd:tsitest", + "cmd:typetest", + "cmd:xferfaxstats" + ] + }, + "lttng-ust-doc": { + "versions": { + "2.10.0-r1": 1509492304 + }, + "origin": "lttng-ust" + }, + "perl-cpanel-json-xs": { + "versions": { + "3.0239-r0": 1510352915 + }, + "origin": "perl-cpanel-json-xs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cpanel_json_xs" + ] + }, + "iputils": { + "versions": { + "20121221-r8": 1510259681 + }, + "origin": "iputils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.42" + ], + "provides": [ + "cmd:arping", + "cmd:clockdiff", + "cmd:ipg", + "cmd:ping", + "cmd:ping6", + "cmd:rarpd", + "cmd:rdisc", + "cmd:tftpd", + "cmd:tracepath", + "cmd:tracepath6", + "cmd:traceroute6" + ] + }, + "kamailio-memcached": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libmemcached.so.11" + ] + }, + "libproxy-dev": { + "versions": { + "0.4.15-r0": 1509468257 + }, + "origin": "libproxy", + "dependencies": [ + "zlib-dev", + "libproxy=0.4.15-r0", + "pkgconfig" + ], + "provides": [ + "pc:libproxy-1.0=0.4.15" + ] + }, + "flac": { + "versions": { + "1.3.2-r0": 1509473288 + }, + "origin": "flac", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libogg.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libFLAC++.so.6=6.3.0", + "so:libFLAC.so.8=8.3.0", + "cmd:flac", + "cmd:metaflac" + ] + }, + "cd-discid-doc": { + "versions": { + "1.4-r2": 1509551854 + }, + "origin": "cd-discid" + }, + "gsl-dev": { + "versions": { + "2.4-r0": 1509493183 + }, + "origin": "gsl", + "dependencies": [ + "gsl=2.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:gsl=2.4", + "cmd:gsl-config" + ] + }, + "py-paramiko-doc": { + "versions": { + "2.4.2-r0": 1551364324 + }, + "origin": "py-paramiko", + "dependencies": [ + "py-asn1", + "py-cryptography", + "py-bcrypt", + "py-pynacl" + ] + }, + "inputproto": { + "versions": { + "2.3.2-r1": 1509461957 + }, + "origin": "inputproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:inputproto=2.3.2" + ] + }, + "eggdbus-dev": { + "versions": { + "0.6-r5": 1509469812 + }, + "origin": "eggdbus", + "dependencies": [ + "eggdbus=0.6-r5", + "pc:gio-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:eggdbus-1=0.6" + ] + }, + "ethtool": { + "versions": { + "4.13-r0": 1509492545 + }, + "origin": "ethtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ethtool" + ] + }, + "rtmpdump-dev": { + "versions": { + "2.4_git20160909-r3": 1510259117 + }, + "origin": "rtmpdump", + "dependencies": [ + "zlib-dev", + "libressl-dev", + "librtmp=2.4_git20160909-r3", + "pc:libcrypto", + "pc:libssl", + "pkgconfig" + ], + "provides": [ + "pc:librtmp=v2.4" + ] + }, + "openrc-doc": { + "versions": { + "0.24.1-r4": 1511887834 + }, + "origin": "openrc" + }, + "lynx": { + "versions": { + "2.8.8_p2-r6": 1510842477 + }, + "origin": "lynx", + "dependencies": [ + "gzip", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:lynx" + ] + }, + "collectd-write_http": { + "versions": { + "5.7.2-r0": 1510069644 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libyajl.so.2" + ] + }, + "cd-discid": { + "versions": { + "1.4-r2": 1509551855 + }, + "origin": "cd-discid", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cd-discid" + ] + }, + "perl-file-temp-doc": { + "versions": { + "0.2304-r0": 1509495372 + }, + "origin": "perl-file-temp" + }, + "uwsgi-cheaper_backlog2": { + "versions": { + "2.0.17-r0": 1522154653 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "aoetools": { + "versions": { + "37-r0": 1509477686 + }, + "origin": "aoetools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:aoe-discover", + "cmd:aoe-flush", + "cmd:aoe-interfaces", + "cmd:aoe-mkdevs", + "cmd:aoe-mkshelf", + "cmd:aoe-revalidate", + "cmd:aoe-sancheck", + "cmd:aoe-stat", + "cmd:aoe-version", + "cmd:aoecfg", + "cmd:aoeping", + "cmd:coraid-update" + ] + }, + "nagios-plugins-dig": { + "versions": { + "2.2.1-r3": 1510288494 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "bind-tools", + "so:libc.musl-x86_64.so.1" + ] + }, + "libpcap": { + "versions": { + "1.8.1-r1": 1509475964 + }, + "origin": "libpcap", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcap.so.1=1.8.1" + ] + }, + "xfce4-cpugraph-plugin": { + "versions": { + "1.0.5-r1": 1510074087 + }, + "origin": "xfce4-cpugraph-plugin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libxfce4panel-1.0.so.4", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7" + ] + }, + "sdl2": { + "versions": { + "2.0.7-r3": 1511364667, + "2.0.10-r0": 1564135866 + }, + "origin": "sdl2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL2-2.0.so.0=0.10.0" + ] + }, + "lua-bitlib": { + "versions": { + "5.3.0-r0": 1509468313 + }, + "origin": "lua-bitlib" + }, + "acf-freeradius3": { + "versions": { + "0.3.1-r0": 1510072745 + }, + "origin": "acf-freeradius3", + "dependencies": [ + "acf-core", + "freeradius>3" + ] + }, + "acf-core": { + "versions": { + "0.21.1-r0": 1510068297 + }, + "origin": "acf-core", + "dependencies": [ + "acf-jquery", + "acf-lib", + "acf-skins", + "haserl-lua5.2", + "lua5.2", + "lua5.2-posix", + "lua5.2-md5", + "lua5.2-json4", + "lua5.2-subprocess", + "/bin/sh" + ], + "provides": [ + "cmd:acf-cli", + "cmd:acfpasswd" + ] + }, + "rlog-dev": { + "versions": { + "1.4-r4": 1509475979 + }, + "origin": "rlog", + "dependencies": [ + "pkgconfig", + "rlog=1.4-r4" + ], + "provides": [ + "pc:librlog=1.4" + ] + }, + "qemu-mips": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mips" + ] + }, + "lua5.1-sircbot": { + "versions": { + "0.4-r1": 1509492353 + }, + "origin": "sircbot", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py-future": { + "versions": { + "0.15.2-r4": 1509491798 + }, + "origin": "py-future" + }, + "libisoburn-doc": { + "versions": { + "1.4.8-r0": 1509462217 + }, + "origin": "libisoburn" + }, + "dtach-doc": { + "versions": { + "0.9-r0": 1509475624 + }, + "origin": "dtach" + }, + "collectd-apache": { + "versions": { + "5.7.2-r0": 1510069645 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "nginx-mod-http-headers-more": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "xl2tpd-doc": { + "versions": { + "1.3.10-r0": 1509490846 + }, + "origin": "xl2tpd" + }, + "py3-paramiko": { + "versions": { + "2.4.2-r0": 1551364325 + }, + "origin": "py-paramiko", + "dependencies": [ + "py3-asn1", + "py3-cryptography", + "py3-bcrypt", + "py3-pynacl", + "python3" + ] + }, + "perl-lwp-protocol-https-doc": { + "versions": { + "6.06-r1": 1509495025 + }, + "origin": "perl-lwp-protocol-https" + }, + "perl-extutils-installpaths": { + "versions": { + "0.011-r0": 1509474747 + }, + "origin": "perl-extutils-installpaths", + "dependencies": [ + "perl-extutils-config" + ] + }, + "libwebp-doc": { + "versions": { + "0.6.0-r1": 1509473038 + }, + "origin": "libwebp" + }, + "iwlwifi-5150-ucode": { + "versions": { + "8.24.2.2-r1": 1509475699 + }, + "origin": "iwlwifi-5150-ucode" + }, + "nagios-plugins-snmp": { + "versions": { + "2.2.1-r3": 1510288498 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "net-snmp-tools", + "so:libc.musl-x86_64.so.1" + ] + }, + "xdelta3": { + "versions": { + "3.0.11-r0": 1509491097 + }, + "origin": "xdelta3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xdelta3" + ] + }, + "nsd": { + "versions": { + "4.1.16-r1": 1510260999 + }, + "origin": "nsd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libevent-2.1.so.6", + "so:libssl.so.44" + ], + "provides": [ + "cmd:nsd", + "cmd:nsd-checkconf", + "cmd:nsd-checkzone", + "cmd:nsd-control", + "cmd:nsd-control-setup" + ] + }, + "orc-compiler": { + "versions": { + "0.4.27-r0": 1509469955 + }, + "origin": "orc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liborc-0.4.so.0" + ], + "provides": [ + "cmd:orcc" + ] + }, + "protobuf-c": { + "versions": { + "1.3.0-r3": 1510846239 + }, + "origin": "protobuf-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libprotobuf.so.14", + "so:libprotoc.so.14", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libprotobuf-c.so.1=1.0.0", + "cmd:protoc-c", + "cmd:protoc-gen-c" + ] + }, + "nload-doc": { + "versions": { + "0.7.4-r3": 1509496066 + }, + "origin": "nload" + }, + "gstreamer0.10-lang": { + "versions": { + "0.10.36-r2": 1509470984 + }, + "origin": "gstreamer0.10" + }, + "postfix-doc": { + "versions": { + "3.2.4-r1": 1510285340 + }, + "origin": "postfix" + }, + "irqbalance": { + "versions": { + "1.2.0-r1": 1509492256 + }, + "origin": "irqbalance", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:irqbalance", + "cmd:irqbalance-ui" + ] + }, + "perl-test-without-doc": { + "versions": { + "0.10-r0": 1509496582 + }, + "origin": "perl-test-without" + }, + "py-curl": { + "versions": { + "7.43.0-r4": 1510288123 + }, + "origin": "py-curl" + }, + "newt-doc": { + "versions": { + "0.52.20-r0": 1509476145 + }, + "origin": "newt" + }, + "lua5.1-mqtt-publish": { + "versions": { + "0.1-r0": 1509462497 + }, + "origin": "lua-mqtt-publish", + "dependencies": [ + "lua5.1-mosquitto" + ] + }, + "py-mysqldb": { + "versions": { + "1.2.5-r0": 1509492445 + }, + "origin": "py-mysqldb", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18", + "so:libpython2.7.so.1.0" + ] + }, + "perl-text-template-doc": { + "versions": { + "1.46-r0": 1509491527 + }, + "origin": "perl-text-template" + }, + "pcmciautils": { + "versions": { + "018-r1": 1509482232 + }, + "origin": "pcmciautils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:lspcmcia", + "cmd:pccardctl" + ] + }, + "perl-inline": { + "versions": { + "0.80-r0": 1509489231 + }, + "origin": "perl-inline", + "dependencies": [ + "perl" + ] + }, + "perl-encode-locale": { + "versions": { + "1.05-r1": 1509464360 + }, + "origin": "perl-encode-locale", + "dependencies": [ + "perl" + ] + }, + "nginx-doc": { + "versions": { + "1.12.2-r4": 1542814446 + }, + "origin": "nginx" + }, + "dnstop-doc": { + "versions": { + "20140915-r3": 1509485931 + }, + "origin": "dnstop" + }, + "perl-namespace-clean": { + "versions": { + "0.27-r0": 1509474076 + }, + "origin": "perl-namespace-clean", + "dependencies": [ + "perl-package-stash", + "perl-sub-name", + "perl-sub-identify", + "perl-b-hooks-endofscope" + ] + }, + "xfce4-volumed": { + "versions": { + "0.1.13-r1": 1510072212 + }, + "origin": "xfce4-volumed", + "dependencies": [ + "xfce4-mixer", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstaudio-0.10.so.0", + "so:libgstinterfaces-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libkeybinder.so.0", + "so:libnotify.so.4", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:xfce4-volumed" + ] + }, + "perl-extutils-cchecker-doc": { + "versions": { + "0.10-r0": 1511889284 + }, + "origin": "perl-extutils-cchecker" + }, + "transmission-daemon": { + "versions": { + "2.92-r8": 1510932986 + }, + "origin": "transmission", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libevent-2.1.so.6", + "so:libintl.so.8", + "so:libz.so.1" + ], + "provides": [ + "cmd:transmission-daemon" + ] + }, + "qca-dev": { + "versions": { + "2.1.3-r4": 1510288320 + }, + "origin": "qca", + "dependencies": [ + "qt-dev", + "pc:QtCore", + "pkgconfig", + "qca=2.1.3-r4" + ], + "provides": [ + "pc:qca2=2.1.3" + ] + }, + "knock": { + "versions": { + "0.7-r1": 1509480798 + }, + "origin": "knock", + "dependencies": [ + "iptables", + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:knock", + "cmd:knockd" + ] + }, + "libnice-gstreamer": { + "versions": { + "0.1.14-r2": 1510931344 + }, + "origin": "libnice", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libnice.so.10" + ] + }, + "perl-encode-utils": { + "versions": { + "2.93-r0": 1511889458 + }, + "origin": "perl-encode", + "dependencies": [ + "perl-encode", + "perl" + ], + "provides": [ + "perl-encode-piconv", + "cmd:encguess", + "cmd:piconv" + ] + }, + "libxml2-dev": { + "versions": { + "2.9.8-r1": 1540398579 + }, + "origin": "libxml2", + "dependencies": [ + "zlib-dev", + "libxml2=2.9.8-r1", + "pkgconfig" + ], + "provides": [ + "pc:libxml-2.0=2.9.8", + "cmd:xml2-config" + ] + }, + "lua-sec-doc": { + "versions": { + "0.6-r3": 1510260656 + }, + "origin": "lua-sec" + }, + "lftp": { + "versions": { + "4.8.3-r1": 1510259189 + }, + "origin": "lftp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libncursesw.so.6", + "so:libreadline.so.7", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:cmd-mirror.so=0", + "so:cmd-sleep.so=0", + "so:cmd-torrent.so=0", + "so:liblftp-jobs.so.0=0.0.0", + "so:liblftp-network.so=0", + "so:liblftp-pty.so=0", + "so:liblftp-tasks.so.0=0.0.0", + "so:proto-file.so=0", + "so:proto-fish.so=0", + "so:proto-ftp.so=0", + "so:proto-http.so=0", + "so:proto-sftp.so=0", + "cmd:lftp", + "cmd:lftpget" + ] + }, + "slony1": { + "versions": { + "2.2.6-r0": 1509491940 + }, + "origin": "slony1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "cmd:slon", + "cmd:slonik", + "cmd:slony_logshipper" + ] + }, + "perl-text-autoformat-doc": { + "versions": { + "1.669004-r0": 1509475841 + }, + "origin": "perl-text-autoformat" + }, + "lua5.2-penlight": { + "versions": { + "1.5.4-r0": 1509485591 + }, + "origin": "lua-penlight", + "dependencies": [ + "lua-penlight-shared", + "lua5.2-filesystem", + "lua-penlight-shared=1.5.4-r0" + ] + }, + "linux-headers": { + "versions": { + "4.4.6-r2": 1509457104 + }, + "origin": "linux-headers" + }, + "strace": { + "versions": { + "4.19-r0": 1509477631 + }, + "origin": "strace", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:strace", + "cmd:strace-graph", + "cmd:strace-log-merge" + ] + }, + "x264": { + "versions": { + "20170930-r0": 1509473602 + }, + "origin": "x264", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:x264" + ] + }, + "valgrind-dev": { + "versions": { + "3.12.0-r1": 1509494839 + }, + "origin": "valgrind", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:valgrind=3.12.0" + ] + }, + "uwsgi-spooler": { + "versions": { + "2.0.17-r0": 1522154659 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libsexy-doc": { + "versions": { + "0.1.11-r7": 1510072498 + }, + "origin": "libsexy" + }, + "hostapd-doc": { + "versions": { + "2.6-r3": 1510261213, + "2.6-r4": 1559723195, + "2.6-r5": 1559724985, + "2.6-r6": 1568724303 + }, + "origin": "hostapd" + }, + "perl-net-rblclient-doc": { + "versions": { + "0.5-r2": 1509479744 + }, + "origin": "perl-net-rblclient" + }, + "xen-dev": { + "versions": { + "4.9.4-r0": 1551280495, + "4.9.4-r1": 1558103152 + }, + "origin": "xen", + "dependencies": [ + "libressl-dev", + "python2-dev", + "e2fsprogs-dev", + "gettext", + "zlib-dev", + "ncurses-dev", + "dev86", + "texinfo", + "perl", + "pciutils-dev", + "glib-dev", + "yajl-dev", + "libnl3-dev", + "spice-dev", + "gnutls-dev", + "curl-dev", + "libaio-dev", + "lzo-dev", + "xz-dev", + "util-linux-dev", + "e2fsprogs-dev", + "linux-headers", + "argp-standalone", + "perl-dev", + "xen-libs=4.9.4-r1" + ] + }, + "rrsync": { + "versions": { + "3.1.3-r0": 1521547982 + }, + "origin": "rsync", + "dependencies": [ + "rsync", + "perl" + ], + "provides": [ + "cmd:rrsync" + ] + }, + "libcanberra-gtk2": { + "versions": { + "0.30-r1": 1510071976 + }, + "origin": "libcanberra", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcanberra.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ], + "provides": [ + "so:libcanberra-gtk.so.0=0.1.9" + ] + }, + "uwsgi-fastrouter": { + "versions": { + "2.0.17-r0": 1522154654 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "uwsgi-corerouter", + "so:libc.musl-x86_64.so.1" + ] + }, + "nfprofile": { + "versions": { + "1.6.15-r0": 1509494469, + "1.6.15-r1": 1574265426 + }, + "origin": "nfdump", + "dependencies": [ + "nfdump", + "so:libc.musl-x86_64.so.1", + "so:libnfdump-1.6.15.so", + "so:librrd.so.4" + ], + "provides": [ + "cmd:nfprofile" + ] + }, + "perl-package-anon": { + "versions": { + "0.05-r4": 1509489338 + }, + "origin": "perl-package-anon", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "bluez-dev": { + "versions": { + "5.47-r3": 1510069786 + }, + "origin": "bluez", + "dependencies": [ + "bluez-libs=5.47-r3", + "pkgconfig" + ], + "provides": [ + "pc:bluez=5.47" + ] + }, + "qemu-system-unicore32": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-unicore32" + ] + }, + "nagios-plugins-rpc": { + "versions": { + "2.2.1-r3": 1510288497 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins" + ] + }, + "perl-b-hooks-endofscope": { + "versions": { + "0.21-r0": 1509474011 + }, + "origin": "perl-b-hooks-endofscope", + "dependencies": [ + "perl-sub-exporter-progressive", + "perl-module-runtime", + "perl-module-implementation", + "perl-variable-magic" + ] + }, + "acf-dhcp": { + "versions": { + "0.9.1-r0": 1510076340 + }, + "origin": "acf-dhcp", + "dependencies": [ + "acf-core", + "dhcp" + ] + }, + "perl-image-exiftool": { + "versions": { + "10.55-r0": 1509495133 + }, + "origin": "perl-image-exiftool", + "dependencies": [ + "perl" + ] + }, + "perl-file-rsync": { + "versions": { + "0.74-r1": 1509473505 + }, + "origin": "perl-file-rsync", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "binutils": { + "versions": { + "2.30-r1": 1527754982, + "2.30-r2": 1565788944 + }, + "origin": "binutils", + "dependencies": [ + "so:libbfd-2.30.so", + "so:libc.musl-x86_64.so.1", + "so:libopcodes-2.30.so", + "so:libz.so.1" + ], + "provides": [ + "cmd:addr2line", + "cmd:ar", + "cmd:as", + "cmd:c++filt", + "cmd:dwp", + "cmd:elfedit", + "cmd:gprof", + "cmd:ld", + "cmd:ld.bfd", + "cmd:nm", + "cmd:objcopy", + "cmd:objdump", + "cmd:ranlib", + "cmd:readelf", + "cmd:size", + "cmd:strings", + "cmd:strip" + ] + }, + "fail2ban": { + "versions": { + "0.10.1-r0": 1509473544 + }, + "origin": "fail2ban", + "dependencies": [ + "python2", + "iptables", + "ip6tables", + "logrotate" + ], + "provides": [ + "cmd:fail2ban-client", + "cmd:fail2ban-python", + "cmd:fail2ban-regex", + "cmd:fail2ban-server", + "cmd:fail2ban-testcases" + ] + }, + "libgnomekbd-dev": { + "versions": { + "3.6.0-r0": 1510933015 + }, + "origin": "libgnomekbd", + "dependencies": [ + "gtk+3.0-dev", + "gconf-dev", + "libxklavier-dev", + "libxml2-dev", + "gobject-introspection-dev", + "libxcursor-dev", + "libxcomposite-dev", + "libxi-dev", + "libxau-dev", + "libxdmcp-dev", + "libxext-dev", + "libxcb-dev", + "libgnomekbd=3.6.0-r0", + "pc:gdk-3.0>=2.91.7", + "pc:gmodule-export-2.0", + "pc:libxklavier>=5.2", + "pkgconfig" + ], + "provides": [ + "pc:libgnomekbd=3.6.0", + "pc:libgnomekbdui=3.6.0" + ] + }, + "zfs-libs": { + "versions": { + "0.7.3-r0": 1509490074 + }, + "origin": "zfs", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libtirpc.so.3", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libnvpair.so.1=1.0.1", + "so:libuutil.so.1=1.0.1", + "so:libzfs.so.2=2.0.0", + "so:libzfs_core.so.1=1.0.0", + "so:libzpool.so.2=2.0.0" + ] + }, + "libgnome-lang": { + "versions": { + "2.32.1-r7": 1510931516 + }, + "origin": "libgnome" + }, + "ser2net-doc": { + "versions": { + "3.4-r1": 1509492427 + }, + "origin": "ser2net" + }, + "perl-sub-install": { + "versions": { + "0.928-r0": 1509473967 + }, + "origin": "perl-sub-install", + "dependencies": [ + "perl" + ] + }, + "py-dbus-dev": { + "versions": { + "1.2.0-r2": 1509465380 + }, + "origin": "py-dbus", + "dependencies": [ + "py-dbus", + "pc:dbus-1>=1.0", + "pkgconfig" + ], + "provides": [ + "pc:dbus-python=1.2.0" + ] + }, + "xf86-input-mouse-doc": { + "versions": { + "1.9.2-r0": 1510074096 + }, + "origin": "xf86-input-mouse" + }, + "s6-dns-doc": { + "versions": { + "2.2.0.1-r0": 1509480281 + }, + "origin": "s6-dns" + }, + "nettle-utils": { + "versions": { + "3.3-r0": 1509465115 + }, + "origin": "nettle", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libhogweed.so.4", + "so:libnettle.so.6" + ], + "provides": [ + "cmd:nettle-hash", + "cmd:nettle-lfib-stream", + "cmd:nettle-pbkdf2", + "cmd:pkcs1-conv", + "cmd:sexp-conv" + ] + }, + "xmlrpc-c-dev": { + "versions": { + "1.39.11-r0": 1509482028 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "libxml2-dev", + "xmlrpc-c++=1.39.11-r0", + "xmlrpc-c-abyss=1.39.11-r0", + "xmlrpc-c-client++=1.39.11-r0", + "xmlrpc-c-client=1.39.11-r0", + "xmlrpc-c=1.39.11-r0" + ], + "provides": [ + "cmd:xmlrpc-c-config" + ] + }, + "perl-boolean-doc": { + "versions": { + "0.46-r0": 1509485610 + }, + "origin": "perl-boolean" + }, + "libmicrohttpd-dev": { + "versions": { + "0.9.57-r0": 1512032047 + }, + "origin": "libmicrohttpd", + "dependencies": [ + "libmicrohttpd=0.9.57-r0", + "pc:gnutls", + "pkgconfig" + ], + "provides": [ + "pc:libmicrohttpd=0.9.57" + ] + }, + "at-spi2-core-dev": { + "versions": { + "2.26.2-r0": 1509466052 + }, + "origin": "at-spi2-core", + "dependencies": [ + "dbus-dev", + "glib-dev", + "libxtst-dev", + "at-spi2-core=2.26.2-r0", + "pc:dbus-1", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:atspi-2=2.26.2" + ] + }, + "lua-discount": { + "versions": { + "1.2.10.1-r4": 1509479789 + }, + "origin": "lua-discount" + }, + "perl-class-container-doc": { + "versions": { + "0.12-r1": 1509470590 + }, + "origin": "perl-class-container" + }, + "ncurses5-widec-libs": { + "versions": { + "5.9-r1": 1509483663 + }, + "origin": "ncurses5", + "dependencies": [ + "ncurses-terminfo-base", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libformw.so.5=5.9", + "so:libmenuw.so.5=5.9", + "so:libncursesw.so.5=5.9", + "so:libpanelw.so.5=5.9" + ] + }, + "ppp-daemon": { + "versions": { + "2.4.7-r5": 1509480141 + }, + "origin": "ppp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:poff", + "cmd:pon", + "cmd:pppd", + "cmd:pppdump", + "cmd:pppstats" + ] + }, + "qemu-sh4": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-sh4" + ] + }, + "perl-list-someutils": { + "versions": { + "0.53-r0": 1509474588 + }, + "origin": "perl-list-someutils", + "dependencies": [ + "perl-module-implementation", + "perl-list-someutils-xs" + ] + }, + "libmikmod": { + "versions": { + "3.3.11.1-r0": 1509481761 + }, + "origin": "libmikmod", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmikmod.so.3=3.3.0" + ] + }, + "bacula-doc": { + "versions": { + "9.0.5-r3": 1512992127 + }, + "origin": "bacula" + }, + "acl-dev": { + "versions": { + "2.2.52-r3": 1509459586 + }, + "origin": "acl", + "dependencies": [ + "libacl=2.2.52-r3" + ] + }, + "squid-doc": { + "versions": { + "3.5.27-r0": 1519824030, + "3.5.27-r1": 1562865666 + }, + "origin": "squid" + }, + "py3-jinja2": { + "versions": { + "2.9.6-r0": 1509476513 + }, + "origin": "py-jinja2", + "dependencies": [ + "py3-markupsafe", + "python3" + ] + }, + "poppler-qt4-dev": { + "versions": { + "0.56.0-r0": 1510071692 + }, + "origin": "poppler-qt4", + "dependencies": [ + "jpeg-dev", + "cairo-dev", + "libxml2-dev", + "fontconfig-dev", + "qt-dev", + "poppler-dev>=0.56.0", + "lcms2-dev", + "openjpeg-dev", + "pkgconfig", + "poppler-qt4=0.56.0-r0" + ], + "provides": [ + "pc:poppler-qt4=0.56.0" + ] + }, + "heimdal-dev": { + "versions": { + "7.4.0-r2": 1514545901, + "7.4.0-r3": 1559659765, + "7.4.0-r4": 1562862336 + }, + "origin": "heimdal", + "dependencies": [ + "libressl-dev", + "e2fsprogs-dev", + "db-dev", + "heimdal-libs=7.4.0-r4", + "pkgconfig" + ], + "provides": [ + "pc:heimdal-gssapi=7.4.0", + "pc:heimdal-kadm-client=7.4.0", + "pc:heimdal-kadm-server=7.4.0", + "pc:heimdal-krb5=7.4.0", + "pc:kadm-client=7.4.0", + "pc:kadm-server=7.4.0", + "pc:kafs=7.4.0", + "pc:krb5-gssapi=7.4.0", + "pc:krb5=7.4.0", + "cmd:krb5-config" + ] + }, + "which-doc": { + "versions": { + "2.21-r1": 1509485571 + }, + "origin": "which" + }, + "mesa-libwayland-egl": { + "versions": { + "17.2.4-r1": 1510741947 + }, + "origin": "mesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libwayland-egl.so.1=1.0.0" + ] + }, + "libbz2": { + "versions": { + "1.0.6-r6": 1509456387, + "1.0.6-r7": 1562268494 + }, + "origin": "bzip2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libbz2.so.1=1.0.6" + ] + }, + "qemu-guest-agent": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "cmd:qemu-ga" + ] + }, + "xfce4-whiskermenu-plugin-doc": { + "versions": { + "1.7.3-r0": 1510073068 + }, + "origin": "xfce4-whiskermenu-plugin" + }, + "openbox-dev": { + "versions": { + "3.6.1-r1": 1510073884 + }, + "origin": "openbox", + "dependencies": [ + "libxcursor-dev", + "libxrandr-dev", + "libxinerama-dev", + "startup-notification-dev", + "openbox-libs=3.6.1-r1", + "pc:glib-2.0", + "pc:imlib2", + "pc:librsvg-2.0", + "pc:libxml-2.0", + "pc:pangoxft", + "pc:xft", + "pkgconfig" + ], + "provides": [ + "pc:obrender-3.5=3.6", + "pc:obt-3.5=3.6" + ] + }, + "perl-crypt-openssl-rsa-doc": { + "versions": { + "0.28-r9": 1510261379 + }, + "origin": "perl-crypt-openssl-rsa" + }, + "libass-dev": { + "versions": { + "0.13.7-r0": 1509480355 + }, + "origin": "libass", + "dependencies": [ + "enca-dev", + "fontconfig-dev", + "fribidi-dev", + "freetype-dev", + "libass=0.13.7-r0", + "pc:fontconfig>=2.10.92", + "pc:freetype2>=9.10.3", + "pc:fribidi>=0.19.0", + "pkgconfig" + ], + "provides": [ + "pc:libass=0.13.7" + ] + }, + "lm_sensors-dev": { + "versions": { + "3.4.0-r4": 1509475442 + }, + "origin": "lm_sensors", + "dependencies": [ + "lm_sensors=3.4.0-r4" + ] + }, + "kamailio-carrierroute": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libsrdb1.so.1", + "so:libtrie.so.1" + ] + }, + "perl-parse-recdescent-doc": { + "versions": { + "1.967015-r0": 1509489503 + }, + "origin": "perl-parse-recdescent" + }, + "sems-g722": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libspandsp.so.2" + ] + }, + "libnih-dev": { + "versions": { + "1.0.3-r4": 1509473672 + }, + "origin": "libnih", + "dependencies": [ + "dbus-dev", + "expat-dev", + "libnih=1.0.3-r4", + "pkgconfig" + ], + "provides": [ + "pc:libnih-dbus=1.0.3", + "pc:libnih=1.0.3" + ] + }, + "py2-certifi": { + "versions": { + "2017.7.27.1-r1": 1509551895 + }, + "origin": "py-certifi", + "dependencies": [ + "python2" + ] + }, + "libsrtp": { + "versions": { + "1.5.4-r0": 1509473356 + }, + "origin": "libsrtp" + }, + "fprobe-ulog": { + "versions": { + "1.2-r3": 1509496379 + }, + "origin": "fprobe-ulog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetfilter_log.so.1", + "so:libnfnetlink.so.0" + ], + "provides": [ + "cmd:fprobe-ulog" + ] + }, + "perl-astro-suntime": { + "versions": { + "0.06-r0": 1509493236 + }, + "origin": "perl-astro-suntime", + "dependencies": [ + "perl" + ] + }, + "nginx-mod-mail": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "py3-chardet": { + "versions": { + "3.0.4-r0": 1509481686 + }, + "origin": "py-chardet", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:chardetect" + ] + }, + "musl-dbg": { + "versions": { + "1.1.18-r3": 1518031143, + "1.1.18-r4": 1565163296 + }, + "origin": "musl" + }, + "audacious-dev": { + "versions": { + "3.9-r0": 1510072524 + }, + "origin": "audacious", + "dependencies": [ + "gtk+2.0-dev", + "dbus-glib-dev", + "audacious=3.9-r0", + "pkgconfig" + ], + "provides": [ + "pc:audacious=3.9" + ] + }, + "lua5.2-maxminddb": { + "versions": { + "0.1-r1": 1509496571 + }, + "origin": "lua-maxminddb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmaxminddb.so.0" + ] + }, + "xfce4-xkb-plugin-doc": { + "versions": { + "0.7.1-r0": 1510074121 + }, + "origin": "xfce4-xkb-plugin" + }, + "dpkg-dev": { + "versions": { + "1.18.24-r0": 1509494230 + }, + "origin": "dpkg", + "dependencies": [ + "perl", + "pkgconfig" + ], + "provides": [ + "pc:libdpkg=1.18.24", + "cmd:dpkg-architecture", + "cmd:dpkg-buildpackage", + "cmd:dpkg-checkbuilddeps", + "cmd:dpkg-distaddfile", + "cmd:dpkg-genchanges", + "cmd:dpkg-gencontrol", + "cmd:dpkg-gensymbols", + "cmd:dpkg-name", + "cmd:dpkg-scanpackages", + "cmd:dpkg-scansources", + "cmd:dpkg-shlibdeps", + "cmd:dpkg-source", + "cmd:dpkg-vendor" + ] + }, + "putty": { + "versions": { + "0.71-r1": 1554726628 + }, + "origin": "putty", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:plink", + "cmd:pscp", + "cmd:psftp", + "cmd:puttygen" + ] + }, + "ttf-liberation": { + "versions": { + "2.00.1-r1": 1509491425 + }, + "origin": "ttf-liberation", + "dependencies": [ + "fontconfig" + ] + }, + "gptfdisk-doc": { + "versions": { + "1.0.3-r0": 1509495223 + }, + "origin": "gptfdisk" + }, + "gtk-engines-dev": { + "versions": { + "2.21.0-r2": 1510071721 + }, + "origin": "gtk-engines", + "dependencies": [ + "pc:gtk+-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gtk-engines-2=2.21.0" + ] + }, + "fftw-single-libs": { + "versions": { + "3.3.6p2-r0": 1509468830 + }, + "origin": "fftw", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfftw3f.so.3=3.5.6", + "so:libfftw3f_threads.so.3=3.5.6" + ] + }, + "libmowgli-dev": { + "versions": { + "2.0.0-r0": 1509491362 + }, + "origin": "libmowgli", + "dependencies": [ + "libmowgli=2.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmowgli-2=2.0.0" + ] + }, + "galculator": { + "versions": { + "2.1.4-r0": 1510075355 + }, + "origin": "galculator", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0" + ], + "provides": [ + "cmd:galculator" + ] + }, + "altermime-doc": { + "versions": { + "0.3.11-r0": 1509481329 + }, + "origin": "altermime" + }, + "xcb-util-dev": { + "versions": { + "0.4.0-r1": 1509464530 + }, + "origin": "xcb-util", + "dependencies": [ + "libxcb-dev", + "util-macros", + "pkgconfig", + "xcb-util=0.4.0-r1" + ], + "provides": [ + "pc:xcb-atom=0.4.0", + "pc:xcb-aux=0.4.0", + "pc:xcb-event=0.4.0", + "pc:xcb-util=0.4.0" + ] + }, + "encfs": { + "versions": { + "1.8.1-r7": 1510258715 + }, + "origin": "encfs", + "dependencies": [ + "fuse", + "so:libboost_serialization-mt.so.1.62.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libfuse.so.2", + "so:libgcc_s.so.1", + "so:librlog.so.5", + "so:libssl.so.44", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libencfs.so.6=6.0.2", + "cmd:encfs", + "cmd:encfsctl", + "cmd:encfssh" + ] + }, + "py-libmount": { + "versions": { + "2.31.1-r0": 1541506295 + }, + "origin": "util-linux", + "dependencies": [ + "findmnt", + "so:libc.musl-x86_64.so.1", + "so:libmount.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "libxmu-dev": { + "versions": { + "1.1.2-r1": 1509473711 + }, + "origin": "libxmu", + "dependencies": [ + "util-linux-dev", + "libxmu=1.1.2-r1", + "pc:x11", + "pc:xext", + "pc:xproto", + "pc:xt", + "pkgconfig" + ], + "provides": [ + "pc:xmu=1.1.2", + "pc:xmuu=1.1.2" + ] + }, + "bridge": { + "versions": { + "1.5-r3": 1509480112 + }, + "origin": "bridge" + }, + "atkmm-doc": { + "versions": { + "2.24.2-r0": 1509483360 + }, + "origin": "atkmm" + }, + "cairo-tools": { + "versions": { + "1.14.10-r0": 1509464621 + }, + "origin": "cairo", + "dependencies": [ + "so:libX11.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo-script-interpreter.so.2", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libpixman-1.so.0", + "so:libxcb-render.so.0", + "so:libxcb.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:cairo-sphinx", + "cmd:cairo-trace" + ] + }, + "py-cffi": { + "versions": { + "1.10.0-r0": 1509476375 + }, + "origin": "py-cffi" + }, + "shorewall-core": { + "versions": { + "5.1.8-r0": 1509477808 + }, + "origin": "shorewall-core", + "provides": [ + "cmd:shorewall" + ] + }, + "uwsgi-syslog": { + "versions": { + "2.0.17-r0": 1522154660 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-yaml-tiny-doc": { + "versions": { + "1.70-r1": 1510352923 + }, + "origin": "perl-yaml-tiny" + }, + "imagemagick-dev": { + "versions": { + "7.0.7.11-r1": 1510748307 + }, + "origin": "imagemagick", + "dependencies": [ + "imagemagick-c++=7.0.7.11-r1", + "imagemagick-libs=7.0.7.11-r1", + "pkgconfig" + ], + "provides": [ + "pc:ImageMagick-7.Q16HDRI=7.0.7", + "pc:ImageMagick=7.0.7", + "pc:Magick++-7.Q16HDRI=7.0.7", + "pc:Magick++=7.0.7", + "pc:MagickCore-7.Q16HDRI=7.0.7", + "pc:MagickCore=7.0.7", + "pc:MagickWand-7.Q16HDRI=7.0.7", + "pc:MagickWand=7.0.7", + "cmd:Magick++-config", + "cmd:MagickCore-config", + "cmd:MagickWand-config" + ] + }, + "gpgmepp": { + "versions": { + "1.9.0-r1": 1510588164 + }, + "origin": "gpgme", + "dependencies": [ + "gnupg", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgpgme.so.11", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgpgmepp.so.6=6.4.0" + ] + }, + "perl-file-copy-recursive-doc": { + "versions": { + "0.38-r1": 1510855671 + }, + "origin": "perl-file-copy-recursive" + }, + "cogl": { + "versions": { + "1.22.2-r0": 1510072961 + }, + "origin": "cogl", + "dependencies": [ + "so:libEGL.so.1", + "so:libX11.so.6", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdrm.so.2", + "so:libgbm.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0" + ], + "provides": [ + "so:libcogl-gles2.so.20=20.4.2", + "so:libcogl-pango.so.20=20.4.2", + "so:libcogl-path.so.20=20.4.2", + "so:libcogl.so.20=20.4.2" + ] + }, + "libidn-doc": { + "versions": { + "1.33-r1": 1509468968 + }, + "origin": "libidn" + }, + "xfsprogs-libs": { + "versions": { + "4.14.0-r1": 1528279973 + }, + "origin": "xfsprogs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhandle.so.1=1.0.3" + ] + }, + "libxkbui": { + "versions": { + "1.0.2-r8": 1509473778 + }, + "origin": "libxkbui", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libxkbfile.so.1" + ], + "provides": [ + "so:libxkbui.so.1=1.0.0" + ] + }, + "lxc-bash-completion": { + "versions": { + "2.1.1-r3": 1533578833 + }, + "origin": "lxc" + }, + "jemalloc-doc": { + "versions": { + "5.0.1-r0": 1509482444 + }, + "origin": "jemalloc" + }, + "busybox-suid": { + "versions": { + "1.27.2-r11": 1528276162 + }, + "origin": "busybox", + "dependencies": [ + "busybox", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bbsuid" + ] + }, + "git-email": { + "versions": { + "2.15.3-r0": 1540401293, + "2.15.4-r0": 1576015190 + }, + "origin": "git", + "dependencies": [ + "perl", + "perl-git=2.15.4-r0", + "perl-net-smtp-ssl", + "perl-authen-sasl", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libpcre2-8.so.0", + "so:libssl.so.44", + "so:libz.so.1" + ] + }, + "audacious-plugins-dbg": { + "versions": { + "3.9-r0": 1510288541 + }, + "origin": "audacious-plugins", + "dependencies": [ + "audacious" + ] + }, + "appstream-glib-lang": { + "versions": { + "0.6.3-r0": 1510067862 + }, + "origin": "appstream-glib" + }, + "dhcpcd-ui": { + "versions": { + "0.7.5-r1": 1510073428 + }, + "origin": "dhcpcd-ui", + "dependencies": [ + "dhcpcd-dbus", + "hicolor-icon-theme", + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libnotify.so.4" + ], + "provides": [ + "cmd:dhcpcd-gtk", + "cmd:dhcpcd-online" + ] + }, + "cdw": { + "versions": { + "0.8.1-r0": 1509491773 + }, + "origin": "cdw", + "dependencies": [ + "cdrkit", + "so:libburn.so.4", + "so:libc.musl-x86_64.so.1", + "so:libcdio.so.16", + "so:libformw.so.6", + "so:libintl.so.8", + "so:libiso9660.so.10", + "so:libmenuw.so.6", + "so:libncursesw.so.6", + "so:libpanelw.so.6" + ], + "provides": [ + "cmd:cdw" + ] + }, + "perl-sub-identify": { + "versions": { + "0.14-r1": 1509474068 + }, + "origin": "perl-sub-identify", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py-sphinx": { + "versions": { + "1.6.5-r0": 1509480690 + }, + "origin": "py-sphinx", + "dependencies": [ + "py3-sphinx", + "py3-sphinx=1.6.5-r0" + ] + }, + "py-django-registration": { + "versions": { + "1.0-r1": 1509474180 + }, + "origin": "py-django-registration", + "dependencies": [ + "python2" + ] + }, + "gettext-static": { + "versions": { + "0.19.8.1-r1": 1509459195 + }, + "origin": "gettext" + }, + "a2ps": { + "versions": { + "4.14-r7": 1510365670 + }, + "origin": "a2ps", + "dependencies": [ + "ghostscript", + "imagemagick", + "perl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:a2ps", + "cmd:card", + "cmd:composeglyphs", + "cmd:fixnt", + "cmd:fixps", + "cmd:ogonkify", + "cmd:pdiff", + "cmd:psmandup", + "cmd:psset", + "cmd:texi2dvi4a2ps" + ] + }, + "vanessa_socket": { + "versions": { + "0.0.13-r0": 1511165897 + }, + "origin": "vanessa_socket", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0", + "so:libvanessa_logger.so.0" + ], + "provides": [ + "so:libvanessa_socket.so.2=2.1.0", + "cmd:vanessa_socket_pipe" + ] + }, + "perl-test-warnings-doc": { + "versions": { + "0.026-r0": 1509468491 + }, + "origin": "perl-test-warnings" + }, + "gstreamer-tools": { + "versions": { + "1.12.3-r0": 1509470920 + }, + "origin": "gstreamer", + "dependencies": [ + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:gst-inspect-1.0", + "cmd:gst-launch-1.0", + "cmd:gst-stats-1.0", + "cmd:gst-typefind-1.0" + ] + }, + "openssl-doc": { + "versions": { + "1.0.2r-r0": 1552814666, + "1.0.2t-r0": 1568300411 + }, + "origin": "openssl" + }, + "perl-type-tiny-doc": { + "versions": { + "1.000006-r0": 1509468522 + }, + "origin": "perl-type-tiny" + }, + "libevent-dev": { + "versions": { + "2.1.8-r2": 1510258602 + }, + "origin": "libevent", + "dependencies": [ + "python2", + "libevent=2.1.8-r2", + "pkgconfig" + ], + "provides": [ + "cmd:event_rpcgen.py" + ] + }, + "gradm": { + "versions": { + "3.1.201607172312-r0": 1509494156 + }, + "origin": "gradm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:gradm", + "cmd:grlearn" + ] + }, + "patchwork-mysql": { + "versions": { + "1.1.3-r0": 1509474238, + "1.1.3-r1": 1562223418 + }, + "origin": "patchwork", + "dependencies": [ + "py-mysqldb" + ] + }, + "lua5.2-subprocess": { + "versions": { + "0.0.20141229-r2": 1509468275 + }, + "origin": "lua-subprocess", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libdnet-dev": { + "versions": { + "1.12-r7": 1509490804 + }, + "origin": "libdnet", + "dependencies": [ + "libdnet=1.12-r7" + ], + "provides": [ + "cmd:dnet-config" + ] + }, + "kamailio-cpl": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libsrdb1.so.1", + "so:libxml2.so.2" + ] + }, + "gnutls-dbg": { + "versions": { + "3.6.1-r0": 1509465331 + }, + "origin": "gnutls" + }, + "perl-package-stash-xs": { + "versions": { + "0.28-r4": 1509474038 + }, + "origin": "perl-package-stash-xs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py-jinja2-doc": { + "versions": { + "2.9.6-r0": 1509476514 + }, + "origin": "py-jinja2" + }, + "dbus": { + "versions": { + "1.10.24-r0": 1509465101, + "1.10.28-r0": 1560765707 + }, + "origin": "dbus", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libexpat.so.1" + ], + "provides": [ + "cmd:dbus-cleanup-sockets", + "cmd:dbus-daemon", + "cmd:dbus-monitor", + "cmd:dbus-run-session", + "cmd:dbus-send", + "cmd:dbus-test-tool", + "cmd:dbus-update-activation-environment", + "cmd:dbus-uuidgen" + ] + }, + "perl-config-inifiles-doc": { + "versions": { + "2.94-r0": 1509489531 + }, + "origin": "perl-config-inifiles" + }, + "udisks2-doc": { + "versions": { + "2.6.5-r0": 1510075149 + }, + "origin": "udisks2" + }, + "avahi-compat-libdns_sd": { + "versions": { + "0.6.32-r4": 1509465449, + "0.6.32-r5": 1563345619 + }, + "origin": "avahi", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdns_sd.so.1=1.0.0" + ] + }, + "perl-test-fatal-doc": { + "versions": { + "0.014-r1": 1509464416 + }, + "origin": "perl-test-fatal" + }, + "openldap-overlay-rwm": { + "versions": { + "2.4.45-r3": 1510258135, + "2.4.46-r0": 1565073941, + "2.4.48-r0": 1566900518 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "parallel-doc": { + "versions": { + "20171122-r0": 1511894075 + }, + "origin": "parallel" + }, + "perl-xml-namespacesupport-doc": { + "versions": { + "1.12-r0": 1509475901 + }, + "origin": "perl-xml-namespacesupport" + }, + "lxsession": { + "versions": { + "0.5.3-r0": 1510073920 + }, + "origin": "lxsession", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpolkit-agent-1.so.0", + "so:libpolkit-gobject-1.so.0", + "so:libunique-1.0.so.0" + ], + "provides": [ + "cmd:lxclipboard", + "cmd:lxlock", + "cmd:lxpolkit", + "cmd:lxsession", + "cmd:lxsession-db", + "cmd:lxsession-default", + "cmd:lxsession-default-apps", + "cmd:lxsession-default-terminal", + "cmd:lxsession-edit", + "cmd:lxsession-logout", + "cmd:lxsession-xdg-autostart", + "cmd:lxsettings-daemon" + ] + }, + "perl-package-anon-doc": { + "versions": { + "0.05-r4": 1509489338 + }, + "origin": "perl-package-anon" + }, + "gnokii": { + "versions": { + "0.6.31-r6": 1510069857 + }, + "origin": "gnokii", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgnokii.so.7", + "so:libintl.so.8" + ], + "provides": [ + "cmd:gnokii", + "cmd:gnokiid", + "cmd:mgnokiidev" + ] + }, + "tumbler-dev": { + "versions": { + "0.2.0-r0": 1510075180 + }, + "origin": "tumbler", + "dependencies": [ + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pkgconfig", + "tumbler=0.2.0-r0" + ], + "provides": [ + "pc:tumbler-1=0.2.0" + ] + }, + "keybinder-doc": { + "versions": { + "0.3.0-r1": 1510069942 + }, + "origin": "keybinder" + }, + "gross-doc": { + "versions": { + "1.0.2-r11": 1509482386 + }, + "origin": "gross" + }, + "samba-common-server-libs": { + "versions": { + "4.7.6-r3": 1555491786 + }, + "origin": "samba", + "dependencies": [ + "so:libCHARSET3-samba4.so", + "so:libacl.so.1", + "so:libaddns-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcli-spoolss-samba4.so", + "so:libcliauth-samba4.so", + "so:libcom_err.so.2", + "so:libcommon-auth-samba4.so", + "so:libcups.so.2", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libevents-samba4.so", + "so:libflag-mapping-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libgssapi-samba4.so.2", + "so:libiov-buf-samba4.so", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libldb.so.1", + "so:liblibcli-lsa3-samba4.so", + "so:liblibcli-netlogon3-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmessages-dgm-samba4.so", + "so:libmsrpc3-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libreplace-samba4.so", + "so:libsamba-cluster-support-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libsecrets3-samba4.so", + "so:libserver-id-db-samba4.so", + "so:libsmb-transport-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-shim-samba4.so", + "so:libsocket-blocking-samba4.so", + "so:libsys-rw-samba4.so", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtime-basic-samba4.so", + "so:libtrusts-util-samba4.so", + "so:libutil-cmdline-samba4.so", + "so:libutil-reg-samba4.so", + "so:libutil-setid-samba4.so", + "so:libutil-tdb-samba4.so", + "so:libwbclient.so.0" + ], + "provides": [ + "so:libads-samba4.so=0", + "so:libauth-samba4.so=0", + "so:libdfs-server-ad-samba4.so=0", + "so:libnetapi.so.0=0", + "so:libnpa-tstream-samba4.so=0", + "so:libprinting-migrate-samba4.so=0", + "so:libsmbd-base-samba4.so=0", + "so:libsmbd-conn-samba4.so=0", + "so:libsmbldap.so.2=2", + "so:libsmbldaphelper-samba4.so=0" + ] + }, + "tcpproxy": { + "versions": { + "2.0.0_beta15-r4": 1509491651 + }, + "origin": "tcpproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tcpproxy" + ] + }, + "qt-webkit": { + "versions": { + "4.8.7-r8": 1510287209 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtDeclarative.so.4", + "so:libQtGui.so.4", + "so:libQtNetwork.so.4", + "so:libX11.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libsqlite3.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libQtWebKit.so.4=4.9.4" + ] + }, + "libasyncns-dev": { + "versions": { + "0.8-r0": 1509490868 + }, + "origin": "libasyncns", + "dependencies": [ + "libasyncns=0.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:libasyncns=0.8" + ] + }, + "cloog": { + "versions": { + "0.18.4-r1": 1509473493 + }, + "origin": "cloog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libisl.so.15" + ], + "provides": [ + "so:libcloog-isl.so.4=4.0.0", + "cmd:cloog" + ] + }, + "fixesproto-doc": { + "versions": { + "5.0-r2": 1509464679 + }, + "origin": "fixesproto" + }, + "font-misc-misc": { + "versions": { + "1.1.2-r1": 1509473680 + }, + "origin": "font-misc-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig", + "util-macros" + ] + }, + "qemu-system-s390x": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-s390x" + ] + }, + "s6-rc": { + "versions": { + "0.2.1.2-r0": 1509492619 + }, + "origin": "s6-rc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexecline.so.2.3", + "so:libs6.so.2.6", + "so:libskarnet.so.2.6" + ], + "provides": [ + "so:libs6rc.so.0.2=0.2.1.2", + "cmd:s6-rc", + "cmd:s6-rc-bundle", + "cmd:s6-rc-compile", + "cmd:s6-rc-db", + "cmd:s6-rc-dryrun", + "cmd:s6-rc-init", + "cmd:s6-rc-update" + ] + }, + "open-isns": { + "versions": { + "0.97-r2": 1510259304 + }, + "origin": "open-isns", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libisns.so.0" + ], + "provides": [ + "cmd:isnsadm", + "cmd:isnsd", + "cmd:isnsdd" + ] + }, + "qextserialport-dev": { + "versions": { + "1.2_rc1-r0": 1510075399 + }, + "origin": "qextserialport", + "dependencies": [ + "qt-dev", + "qextserialport=1.2_rc1-r0" + ] + }, + "libusb-compat-dev": { + "versions": { + "0.1.5-r3": 1509473935 + }, + "origin": "libusb-compat", + "dependencies": [ + "libusb-dev", + "libusb-compat=0.1.5-r3", + "pc:libusb-1.0", + "pkgconfig" + ], + "provides": [ + "pc:libusb=0.1.12", + "cmd:libusb-config" + ] + }, + "xmlto": { + "versions": { + "0.0.28-r2": 1509461795 + }, + "origin": "xmlto", + "dependencies": [ + "libxslt", + "perl-yaml-syck", + "perl-test-pod", + "bash", + "docbook-xsl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xmlif", + "cmd:xmlto" + ] + }, + "hunspell-pt-br": { + "versions": { + "20131017-r0": 1509494665 + }, + "origin": "hunspell-pt-br" + }, + "giblib": { + "versions": { + "1.2.4-r8": 1509470529 + }, + "origin": "giblib", + "dependencies": [ + "so:libImlib2.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgiblib.so.1=1.0.6" + ] + }, + "uwsgi-ping": { + "versions": { + "2.0.17-r0": 1522154656 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "luajit-doc": { + "versions": { + "2.1.0_beta3-r0": 1509474650 + }, + "origin": "luajit" + }, + "unzip": { + "versions": { + "6.0-r3": 1534926377 + }, + "origin": "unzip", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:funzip", + "cmd:unzip", + "cmd:unzipsfx", + "cmd:zipgrep", + "cmd:zipinfo" + ] + }, + "weechat-dev": { + "versions": { + "1.9.1-r1": 1509530222 + }, + "origin": "weechat", + "dependencies": [ + "cmake", + "libintl", + "ncurses-dev", + "gnutls-dev", + "libgcrypt-dev", + "curl-dev", + "aspell-dev", + "lua-dev", + "perl-dev", + "python2-dev", + "ruby-dev", + "zlib-dev", + "pkgconfig" + ], + "provides": [ + "pc:weechat=1.9.1" + ] + }, + "mpg123": { + "versions": { + "1.25.7-r0": 1509707456 + }, + "origin": "mpg123", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmpg123.so.0=0.44.6", + "so:libout123.so.0=0.2.1", + "cmd:mpg123", + "cmd:mpg123-id3dump", + "cmd:mpg123-strip", + "cmd:out123" + ] + }, + "unixodbc": { + "versions": { + "2.3.4-r2": 1509469610 + }, + "origin": "unixodbc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "so:libodbc.so.2=2.0.0", + "so:libodbccr.so.2=2.0.0", + "so:libodbcinst.so.2=2.0.0", + "cmd:dltest", + "cmd:isql", + "cmd:iusql", + "cmd:odbc_config", + "cmd:odbcinst", + "cmd:slencheck" + ] + }, + "py2-docutils": { + "versions": { + "0.13.1-r0": 1509486243 + }, + "origin": "py-docutils", + "dependencies": [ + "py2-pillow", + "py2-roman", + "python2" + ], + "provides": [ + "cmd:rst2html-2", + "cmd:rst2html5-2", + "cmd:rst2latex-2", + "cmd:rst2man-2", + "cmd:rst2odt-2", + "cmd:rst2odt_prepstyles-2", + "cmd:rst2pseudoxml-2", + "cmd:rst2s5-2", + "cmd:rst2xetex-2", + "cmd:rst2xml-2", + "cmd:rstpep2html-2" + ] + }, + "xf86-video-nv": { + "versions": { + "2.1.21-r0": 1510075414 + }, + "origin": "xf86-video-nv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py-boto-doc": { + "versions": { + "2.47.0-r0": 1509472876 + }, + "origin": "py-boto", + "dependencies": [ + "python2" + ] + }, + "mtr-gtk": { + "versions": { + "0.92-r0": 1510072768 + }, + "origin": "mtr", + "dependencies": [ + "ncurses", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:mtr-gtk", + "cmd:mtr-packet-gtk" + ] + }, + "libxdamage": { + "versions": { + "1.1.4-r1": 1509464692 + }, + "origin": "libxdamage", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXdamage.so.1=1.1.0" + ] + }, + "uwsgi-logcrypto": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "pango-dev": { + "versions": { + "1.40.14-r1": 1541519450 + }, + "origin": "pango", + "dependencies": [ + "pango=1.40.14-r1", + "pc:cairo", + "pc:fontconfig", + "pc:freetype2", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:harfbuzz", + "pc:xft", + "pkgconfig" + ], + "provides": [ + "pc:pango=1.40.14", + "pc:pangocairo=1.40.14", + "pc:pangoft2=1.40.14", + "pc:pangoxft=1.40.14" + ] + }, + "xsetmode": { + "versions": { + "1.0.0-r4": 1509490726 + }, + "origin": "xsetmode", + "dependencies": [ + "so:libX11.so.6", + "so:libXi.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xsetmode" + ] + }, + "lua-sql-postgres": { + "versions": { + "2.3.5-r1": 1509488834 + }, + "origin": "lua-sql" + }, + "lua-curl": { + "versions": { + "0.3.7-r1": 1509493018 + }, + "origin": "lua-curl" + }, + "py-django-oscar": { + "versions": { + "1.5.1-r0": 1512067699 + }, + "origin": "py-django-oscar", + "dependencies": [ + "py-babel", + "py-django", + "py-django-extra-views", + "py-django-haystack", + "py-django-phonenumber-field", + "py-django-sorl-thumbnail", + "py-django-tables2", + "py-django-treebeard", + "py-django-widget-tweaks", + "py-factory-boy", + "py-mock", + "py-pillow", + "py-purl", + "py-unidecode" + ] + }, + "perl-module-pluggable-doc": { + "versions": { + "5.2-r0": 1509483532 + }, + "origin": "perl-module-pluggable" + }, + "libunique3-doc": { + "versions": { + "3.0.2-r0": 1510069893 + }, + "origin": "libunique3" + }, + "gst-plugins-bad0.10-lang": { + "versions": { + "0.10.23-r7": 1510287373 + }, + "origin": "gst-plugins-bad0.10" + }, + "exiv2": { + "versions": { + "0.25-r0": 1509475473 + }, + "origin": "exiv2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libexiv2.so.14=14.0.0", + "cmd:exiv2" + ] + }, + "quvi": { + "versions": { + "0.9.5-r4": 1509495086 + }, + "origin": "quvi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libglib-2.0.so.0", + "so:libintl.so.8", + "so:libquvi-0.9-0.9.4.so" + ], + "provides": [ + "cmd:quvi" + ] + }, + "libxml2": { + "versions": { + "2.9.8-r1": 1540398580 + }, + "origin": "libxml2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libxml2.so.2=2.9.8" + ] + }, + "uwsgi-router_redirect": { + "versions": { + "2.0.17-r0": 1522154657 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libexecinfo-dev": { + "versions": { + "1.1-r0": 1509491293 + }, + "origin": "libexecinfo", + "dependencies": [ + "libexecinfo=1.1-r0" + ] + }, + "pigz": { + "versions": { + "2.3.4-r2": 1509494287 + }, + "origin": "pigz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:pigz", + "cmd:unpigz" + ] + }, + "mesa-dri-ati": { + "versions": { + "17.2.4-r1": 1510741945 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libX11-xcb.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_intel.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libxcb-dri2.so.0", + "so:libxcb-dri3.so.0", + "so:libxcb-present.so.0", + "so:libxcb-sync.so.1", + "so:libxcb-xfixes.so.0", + "so:libxcb.so.1", + "so:libxshmfence.so.1", + "so:libz.so.1" + ] + }, + "hypermail": { + "versions": { + "2.3.0-r4": 1509494653 + }, + "origin": "hypermail", + "dependencies": [ + "lua", + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libpcre.so.1" + ], + "provides": [ + "cmd:hypermail", + "cmd:mdir2mbox.lua", + "cmd:msgarchive", + "cmd:rdmsg" + ] + }, + "subunit-dev": { + "versions": { + "1.2.0-r0": 1509481263 + }, + "origin": "subunit", + "dependencies": [ + "pkgconfig", + "subunit-libs=1.2.0-r0" + ], + "provides": [ + "pc:libcppunit_subunit=1.2.0", + "pc:libsubunit=1.2.0" + ] + }, + "pm-utils-doc": { + "versions": { + "1.4.1-r0": 1509491019 + }, + "origin": "pm-utils" + }, + "py-django-tables2": { + "versions": { + "1.16.0-r0": 1511954172 + }, + "origin": "py-django-tables2", + "dependencies": [ + "py-django", + "py-six" + ] + }, + "perl-extutils-installpaths-doc": { + "versions": { + "0.011-r0": 1509474744 + }, + "origin": "perl-extutils-installpaths" + }, + "libmtp": { + "versions": { + "1.1.14-r0": 1509481648 + }, + "origin": "libmtp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libmtp.so.9=9.3.0" + ] + }, + "clutter-lang": { + "versions": { + "1.26.2-r1": 1510073009 + }, + "origin": "clutter" + }, + "stalonetray": { + "versions": { + "0.8.3-r0": 1509489786 + }, + "origin": "stalonetray", + "dependencies": [ + "so:libX11.so.6", + "so:libXpm.so.4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:stalonetray" + ] + }, + "perl-net-snpp": { + "versions": { + "1.17-r2": 1509489394 + }, + "origin": "perl-net-snpp", + "dependencies": [ + "perl" + ] + }, + "py-urlnorm": { + "versions": { + "1.1.2-r1": 1509552774 + }, + "origin": "py-urlnorm", + "dependencies": [ + "python2" + ] + }, + "xcb-util-image-dev": { + "versions": { + "0.4.0-r1": 1509473909 + }, + "origin": "xcb-util-image", + "dependencies": [ + "xcb-util-dev", + "pc:xcb-shm", + "pkgconfig", + "xcb-util-image=0.4.0-r1" + ], + "provides": [ + "pc:xcb-image=0.4.0" + ] + }, + "eudev-libs": { + "versions": { + "3.2.4-r1": 1509466081 + }, + "origin": "eudev", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libudev.so.1=1.6.3" + ] + }, + "doxygen": { + "versions": { + "1.8.13-r1": 1509465011 + }, + "origin": "doxygen", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:doxygen" + ] + }, + "perl-cgi-emulate-psgi": { + "versions": { + "0.23-r0": 1509493702 + }, + "origin": "perl-cgi-emulate-psgi", + "dependencies": [ + "perl-http-message", + "perl-cgi" + ] + }, + "mesa-gl": { + "versions": { + "17.2.4-r1": 1510741947 + }, + "origin": "mesa", + "dependencies": [ + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libexpat.so.1", + "so:libglapi.so.0", + "so:libxcb-dri2.so.0", + "so:libxcb-dri3.so.0", + "so:libxcb-glx.so.0", + "so:libxcb-present.so.0", + "so:libxcb-sync.so.1", + "so:libxcb.so.1", + "so:libxshmfence.so.1" + ], + "provides": [ + "so:libGL.so.1=1.2.0" + ] + }, + "imagemagick-doc": { + "versions": { + "7.0.7.11-r1": 1510748306 + }, + "origin": "imagemagick" + }, + "amavisd-new": { + "versions": { + "2.11.0-r2": 1509517843 + }, + "origin": "amavisd-new", + "dependencies": [ + "sed", + "file", + "perl", + "perl-archive-zip", + "perl-carp", + "perl-convert-tnef", + "perl-compress-raw-zlib", + "perl-convert-uulib", + "perl-digest-md5", + "perl-io", + "perl-exporter", + "perl-io-compress", + "perl-io-stringy", + "perl-mime-tools", + "perl-mailtools", + "perl-socket", + "perl-net-libidn", + "perl-net-server", + "perl-time-hires", + "perl-unix-syslog", + "perl-db", + "perl-mail-dkim", + "/bin/sh" + ], + "provides": [ + "cmd:amavisd", + "cmd:amavisd-nanny", + "cmd:amavisd-release" + ] + }, + "py2-click": { + "versions": { + "6.7-r2": 1509476498 + }, + "origin": "py-click", + "dependencies": [ + "python2" + ] + }, + "dillo-doc": { + "versions": { + "3.0.5-r4": 1510259043 + }, + "origin": "dillo" + }, + "samba-initscript": { + "versions": { + "4.7.6-r3": 1555491786 + }, + "origin": "samba" + }, + "libusb": { + "versions": { + "1.0.21-r0": 1509470806 + }, + "origin": "libusb", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libusb-1.0.so.0=0.1.0" + ] + }, + "py2-django": { + "versions": { + "1.11.20-r0": 1551365028, + "1.11.21-r0": 1561497056, + "1.11.22-r0": 1563783110, + "1.11.23-r0": 1565087790 + }, + "origin": "py-django", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:django-admin-2" + ] + }, + "eggdrop-doc": { + "versions": { + "1.6.21-r2": 1509491901 + }, + "origin": "eggdrop" + }, + "apache-mod-auth-kerb": { + "versions": { + "5.4-r4": 1509495855 + }, + "origin": "apache-mod-auth-kerb", + "dependencies": [ + "apache2", + "heimdal", + "so:libc.musl-x86_64.so.1", + "so:libgssapi.so.3" + ] + }, + "eggdbus": { + "versions": { + "0.6-r5": 1509469812 + }, + "origin": "eggdbus", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libeggdbus-1.so.0=0.0.0", + "cmd:eggdbus-binding-tool", + "cmd:eggdbus-glib-genmarshal" + ] + }, + "perl-io-stringy": { + "versions": { + "2.111-r0": 1509469881 + }, + "origin": "perl-io-stringy", + "dependencies": [ + "perl" + ] + }, + "lftp-doc": { + "versions": { + "4.8.3-r1": 1510259189 + }, + "origin": "lftp" + }, + "gnome-common": { + "versions": { + "3.18.0-r1": 1509481864 + }, + "origin": "gnome-common", + "provides": [ + "cmd:gnome-autogen.sh" + ] + }, + "wxgtk2.8-media": { + "versions": { + "2.8.12.1-r4": 1510928422 + }, + "origin": "wxgtk2.8", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstinterfaces-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libstdc++.so.6", + "so:libwx_baseu-2.8.so.0", + "so:libwx_gtk2u_core-2.8.so.0" + ], + "provides": [ + "so:libwx_gtk2u_media-2.8.so.0=0.8.0" + ] + }, + "automake": { + "versions": { + "1.15.1-r0": 1509456977 + }, + "origin": "automake", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:aclocal", + "cmd:aclocal-1.15", + "cmd:automake", + "cmd:automake-1.15" + ] + }, + "gtest": { + "versions": { + "1.8.0-r1": 1509475787 + }, + "origin": "gtest", + "dependencies": [ + "libgcc", + "bash", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgtest.so=0", + "so:libgtest_main.so=0" + ] + }, + "qemu-system-sh4": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-sh4" + ] + }, + "perl-file-tail-doc": { + "versions": { + "1.3-r1": 1511889532 + }, + "origin": "perl-file-tail" + }, + "py2-urwid": { + "versions": { + "1.3.1-r2": 1509493953 + }, + "origin": "py-urwid", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "font-isas-misc": { + "versions": { + "1.0.3-r0": 1509488868 + }, + "origin": "font-isas-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-time-hires": { + "versions": { + "1.9746-r0": 1509484014 + }, + "origin": "perl-time-hires", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-future": { + "versions": { + "0.37-r0": 1511989972 + }, + "origin": "perl-future" + }, + "libgsf-lang": { + "versions": { + "1.14.41-r0": 1509467200 + }, + "origin": "libgsf" + }, + "minicom": { + "versions": { + "2.7.1-r0": 1509485711 + }, + "origin": "minicom", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:ascii-xfr", + "cmd:minicom", + "cmd:runscript", + "cmd:xminicom" + ] + }, + "mosh-bash-completion": { + "versions": { + "1.3.2-r3": 1510846214 + }, + "origin": "mosh" + }, + "fping-doc": { + "versions": { + "4.0-r0": 1509483914 + }, + "origin": "fping" + }, + "libunique-doc": { + "versions": { + "1.1.6-r6": 1510069913 + }, + "origin": "libunique" + }, + "libpurple-oscar": { + "versions": { + "2.12.0-r2": 1510069753 + }, + "origin": "pidgin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libaim.so=0", + "so:libicq.so=0", + "so:liboscar.so.0=0.0.0" + ] + }, + "libressl-doc": { + "versions": { + "2.6.5-r0": 1529043911 + }, + "origin": "libressl" + }, + "postgresql-bdr-extension-doc": { + "versions": { + "1.0.2-r1": 1510259297 + }, + "origin": "postgresql-bdr-extension" + }, + "libxklavier-doc": { + "versions": { + "5.4-r2": 1509475733 + }, + "origin": "libxklavier" + }, + "qpdf-fix-qdf": { + "versions": { + "7.0.0-r0": 1509482355 + }, + "origin": "qpdf", + "dependencies": [ + "qpdf", + "perl" + ], + "provides": [ + "cmd:fix-qdf" + ] + }, + "nagios-plugins-pgsql": { + "versions": { + "2.2.1-r3": 1510288497 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "irssi-dev": { + "versions": { + "1.0.6-r0": 1519052408, + "1.0.8-r0": 1562236916 + }, + "origin": "irssi" + }, + "mesa-osmesa": { + "versions": { + "17.2.4-r1": 1510741947 + }, + "origin": "mesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libOSMesa.so.8=8.0.0" + ] + }, + "yasm": { + "versions": { + "1.3.0-r1": 1509473589 + }, + "origin": "yasm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:vsyasm", + "cmd:yasm", + "cmd:ytasm" + ] + }, + "libnet": { + "versions": { + "1.1.6-r2": 1509481431 + }, + "origin": "libnet", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnet.so.1=1.7.0" + ] + }, + "perl-config-grammar-doc": { + "versions": { + "1.12-r0": 1509483934 + }, + "origin": "perl-config-grammar" + }, + "libsm-doc": { + "versions": { + "1.2.2-r1": 1509466262 + }, + "origin": "libsm" + }, + "gvfs-cdda": { + "versions": { + "1.34.1-r0": 1511430258, + "1.34.1-r1": 1563787223 + }, + "origin": "gvfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcdio.so.16", + "so:libcdio_cdda.so.2", + "so:libcdio_paranoia.so.2", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libgvfscommon.so", + "so:libgvfsdaemon.so", + "so:libintl.so.8" + ] + }, + "perl-regexp-ipv6-doc": { + "versions": { + "0.03-r0": 1509470471 + }, + "origin": "perl-regexp-ipv6" + }, + "lua5.2-gversion.lua": { + "versions": { + "0.2.0-r1": 1509475305 + }, + "origin": "lua-gversion", + "dependencies": [ + "lua5.2" + ] + }, + "st-doc": { + "versions": { + "0.7-r1": 1509494022 + }, + "origin": "st" + }, + "boost-thread": { + "versions": { + "1.62.0-r5": 1509465881 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_system-mt.so.1.62.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_thread-mt.so.1.62.0=1.62.0" + ] + }, + "xfce4-appfinder-lang": { + "versions": { + "4.12.0-r0": 1510074532 + }, + "origin": "xfce4-appfinder", + "dependencies": [ + "hicolor-icon-theme" + ] + }, + "gmp-dev": { + "versions": { + "6.1.2-r1": 1509456917 + }, + "origin": "gmp", + "dependencies": [ + "gmp=6.1.2-r1", + "libgmpxx=6.1.2-r1" + ] + }, + "patchwork": { + "versions": { + "1.1.3-r0": 1509474240, + "1.1.3-r1": 1562223418 + }, + "origin": "patchwork", + "dependencies": [ + "py-django", + "py-django-registration", + "git", + "py-psycopg2" + ] + }, + "mrtg": { + "versions": { + "2.17.4-r4": 1509482698 + }, + "origin": "mrtg", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3" + ], + "provides": [ + "cmd:cfgmaker", + "cmd:indexmaker", + "cmd:mrtg", + "cmd:mrtg-traffic-sum", + "cmd:rateup" + ] + }, + "ncurses5": { + "versions": { + "5.9-r1": 1509483668 + }, + "origin": "ncurses5" + }, + "scrot-doc": { + "versions": { + "0.8.13-r0": 1509481270 + }, + "origin": "scrot" + }, + "squid-lang-oc": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865669 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "lua5.3-posixtz": { + "versions": { + "0.5-r1": 1509479784 + }, + "origin": "lua-posixtz", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "opennhrp-doc": { + "versions": { + "0.14.1-r6": 1509490848 + }, + "origin": "opennhrp" + }, + "perl-parse-syslog-doc": { + "versions": { + "1.10-r2": 1509479747 + }, + "origin": "perl-parse-syslog" + }, + "perl-net-ssleay": { + "versions": { + "1.82-r0": 1510260115 + }, + "origin": "perl-net-ssleay", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "pcsc-lite-libs": { + "versions": { + "1.8.22-r0": 1509474498 + }, + "origin": "pcsc-lite", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcsclite.so.1=1.0.0", + "so:libpcscspy.so.0=0.0.0" + ] + }, + "android-tools-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503663 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "lua5.2-xml": { + "versions": { + "130610-r5": 1509482647 + }, + "origin": "lua-xml", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "libsecret-doc": { + "versions": { + "0.18.5-r0": 1509483961 + }, + "origin": "libsecret" + }, + "polkit-dev": { + "versions": { + "0.105-r8": 1551780676, + "0.105-r9": 1563792450, + "0.105-r10": 1567523133 + }, + "origin": "polkit", + "dependencies": [ + "eggdbus-dev", + "dbus-glib-dev", + "pc:gio-2.0>=2.18", + "pc:glib-2.0>=2.18", + "pkgconfig", + "polkit=0.105-r10" + ], + "provides": [ + "pc:polkit-agent-1=0.105", + "pc:polkit-backend-1=0.105", + "pc:polkit-gobject-1=0.105" + ] + }, + "cloog-dev": { + "versions": { + "0.18.4-r1": 1509473492 + }, + "origin": "cloog", + "dependencies": [ + "gmp-dev", + "isl-dev", + "cloog=0.18.4-r1", + "pkgconfig" + ], + "provides": [ + "pc:cloog-isl=0.18.4" + ] + }, + "xf86bigfontproto": { + "versions": { + "1.2.0-r5": 1509461949 + }, + "origin": "xf86bigfontproto" + }, + "libvirt-doc": { + "versions": { + "3.9.0-r1": 1510088351, + "5.5.0-r0": 1562165539 + }, + "origin": "libvirt" + }, + "phodav": { + "versions": { + "2.2-r0": 1509492235 + }, + "origin": "phodav", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libsoup-2.4.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "so:libphodav-2.0.so.0=0.0.0" + ] + }, + "tpaste": { + "versions": { + "0.6-r0": 1509477736 + }, + "origin": "sprunge", + "dependencies": [ + "curl" + ], + "provides": [ + "cmd:tpaste" + ] + }, + "perl-params-validate-doc": { + "versions": { + "1.28-r0": 1509470588 + }, + "origin": "perl-params-validate" + }, + "perl-authen-sasl-doc": { + "versions": { + "2.16-r1": 1510352971 + }, + "origin": "perl-authen-sasl" + }, + "figlet": { + "versions": { + "2.2.5-r0": 1509480744 + }, + "origin": "figlet", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:chkfont", + "cmd:figlet", + "cmd:figlist", + "cmd:showfigfonts" + ] + }, + "perl-xml-rss": { + "versions": { + "1.59-r1": 1511389029 + }, + "origin": "perl-xml-rss", + "dependencies": [ + "perl-xml-parser", + "perl-html-parser", + "perl-datetime-format-mail", + "perl-datetime-format-w3cdtf", + "perl-datetime" + ] + }, + "perl-test-exception-doc": { + "versions": { + "0.43-r0": 1509473072 + }, + "origin": "perl-test-exception" + }, + "unixodbc-dev": { + "versions": { + "2.3.4-r2": 1509469609 + }, + "origin": "unixodbc", + "dependencies": [ + "unixodbc=2.3.4-r2" + ] + }, + "unzip-doc": { + "versions": { + "6.0-r3": 1534926377 + }, + "origin": "unzip" + }, + "sntpc": { + "versions": { + "0.9-r6": 1509492306 + }, + "origin": "sntpc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sntpc" + ] + }, + "zip": { + "versions": { + "3.0-r4": 1509457025 + }, + "origin": "zip", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:zip", + "cmd:zipcloak", + "cmd:zipnote", + "cmd:zipsplit" + ] + }, + "twm": { + "versions": { + "1.0.9-r2": 1509493988 + }, + "origin": "twm", + "dependencies": [ + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXmu.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:twm" + ] + }, + "pyflakes": { + "versions": { + "1.6.0-r0": 1509492637 + }, + "origin": "pyflakes", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:pyflakes" + ] + }, + "nginx-mod-http-xslt-filter": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1", + "so:libexslt.so.0", + "so:libxml2.so.2", + "so:libxslt.so.1" + ] + }, + "gsl-doc": { + "versions": { + "2.4-r0": 1509493183 + }, + "origin": "gsl" + }, + "open-vm-tools-dev": { + "versions": { + "10.1.15-r0": 1510922342 + }, + "origin": "open-vm-tools", + "dependencies": [ + "open-vm-tools=10.1.15-r0", + "pkgconfig" + ], + "provides": [ + "pc:libDeployPkg=10.1.15", + "pc:vmguestlib=10.1.15" + ] + }, + "ipset-dev": { + "versions": { + "6.34-r1": 1537515181 + }, + "origin": "ipset", + "dependencies": [ + "libmnl-dev", + "ipset=6.34-r1", + "pc:libmnl>=1", + "pkgconfig" + ], + "provides": [ + "pc:libipset=6.34" + ] + }, + "libepoxy": { + "versions": { + "1.4.3-r1": 1510067311 + }, + "origin": "libepoxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libepoxy.so.0=0.0.0" + ] + }, + "execline-doc": { + "versions": { + "2.3.0.3-r0": 1509488629 + }, + "origin": "execline" + }, + "perl-test-identity-doc": { + "versions": { + "0.01-r0": 1509477787 + }, + "origin": "perl-test-identity" + }, + "libdc1394-doc": { + "versions": { + "2.2.5-r1": 1509480970 + }, + "origin": "libdc1394" + }, + "perl-class-method-modifiers-doc": { + "versions": { + "2.12-r0": 1509479362 + }, + "origin": "perl-class-method-modifiers" + }, + "enca-doc": { + "versions": { + "1.19-r1": 1509480334 + }, + "origin": "enca" + }, + "orbit2": { + "versions": { + "2.14.19-r3": 1510928305 + }, + "origin": "orbit2", + "dependencies": [ + "so:libIDL-2.so.0", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libORBit-2.so.0=0.1.0", + "so:libORBit-imodule-2.so.0=0.0.0", + "so:libORBitCosNaming-2.so.0=0.1.0", + "cmd:ior-decode-2", + "cmd:linc-cleanup-sockets", + "cmd:orbit-idl-2", + "cmd:typelib-dump" + ] + }, + "awstats": { + "versions": { + "7.6-r2": 1515159956 + }, + "origin": "awstats", + "dependencies": [ + "perl", + "perl-uri" + ], + "provides": [ + "cmd:awstats_buildstaticpages.pl", + "cmd:awstats_configure.pl", + "cmd:awstats_exportlib.pl", + "cmd:awstats_updateall.pl", + "cmd:geoip_generator.pl", + "cmd:logresolvemerge.pl", + "cmd:maillogconvert.pl", + "cmd:urlaliasbuilder.pl" + ] + }, + "lxc-bridge": { + "versions": { + "2.1.1-r3": 1533578833 + }, + "origin": "lxc", + "dependencies": [ + "dnsmasq" + ] + }, + "soxr-doc": { + "versions": { + "0.1.2-r0": 1509492272 + }, + "origin": "soxr" + }, + "clang-doc": { + "versions": { + "5.0.0-r0": 1510683246 + }, + "origin": "clang" + }, + "perl-posix-strftime-compiler-doc": { + "versions": { + "0.41-r0": 1509481797 + }, + "origin": "perl-posix-strftime-compiler" + }, + "perl-datetime-format-w3cdtf-doc": { + "versions": { + "0.07-r0": 1510859373 + }, + "origin": "perl-datetime-format-w3cdtf" + }, + "nspr": { + "versions": { + "4.17-r0": 1509479384 + }, + "origin": "nspr", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnspr4.so=0", + "so:libplc4.so=0", + "so:libplds4.so=0" + ] + }, + "perl-html-scrubber-doc": { + "versions": { + "0.11-r0": 1509481322 + }, + "origin": "perl-html-scrubber" + }, + "tsocks": { + "versions": { + "1.8_beta5-r0": 1509496537 + }, + "origin": "tsocks", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libtsocks.so.1.8=1.8", + "cmd:tsocks" + ] + }, + "alpine-doc": { + "versions": { + "2.21-r1": 1510259111 + }, + "origin": "alpine" + }, + "vsftpd-doc": { + "versions": { + "3.0.3-r4": 1510260062 + }, + "origin": "vsftpd" + }, + "rtnppd": { + "versions": { + "1.7b-r8": 1509491629 + }, + "origin": "rtnppd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:rtnppd", + "cmd:testq", + "cmd:tnpppage" + ] + }, + "font-bitstream-type1": { + "versions": { + "1.0.3-r0": 1509489238 + }, + "origin": "font-bitstream-type1", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "libtheora-dev": { + "versions": { + "1.1.1-r13": 1510068272 + }, + "origin": "libtheora", + "dependencies": [ + "libogg-dev", + "libtheora=1.1.1-r13", + "pc:ogg>=1.1", + "pkgconfig" + ], + "provides": [ + "pc:theora=1.1.1", + "pc:theoradec=1.1.1", + "pc:theoraenc=1.1.1" + ] + }, + "libmilter": { + "versions": { + "1.0.2-r5": 1509479369 + }, + "origin": "libmilter", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmilter.so.1.0.2=1.0.2" + ] + }, + "samba-libs": { + "versions": { + "4.7.6-r3": 1555491790 + }, + "origin": "samba", + "dependencies": [ + "so:libasn1-samba4.so.8", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcom_err.so.2", + "so:libgnutls.so.30", + "so:libgssapi-samba4.so.2", + "so:libkrb5-samba4.so.26", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libldb.so.1", + "so:libpopt.so.0", + "so:libreplace-samba4.so", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent.so.0", + "so:libwbclient.so.0", + "so:libz.so.1" + ], + "provides": [ + "so:libCHARSET3-samba4.so=0", + "so:libaddns-samba4.so=0", + "so:libasn1util-samba4.so=0", + "so:libauthkrb5-samba4.so=0", + "so:libcli-cldap-samba4.so=0", + "so:libcli-ldap-common-samba4.so=0", + "so:libcli-nbt-samba4.so=0", + "so:libcli-smb-common-samba4.so=0", + "so:libcliauth-samba4.so=0", + "so:libcmocka-samba4.so=0", + "so:libcommon-auth-samba4.so=0", + "so:libdbwrap-samba4.so=0", + "so:libflag-mapping-samba4.so=0", + "so:libgenrand-samba4.so=0", + "so:libgensec-samba4.so=0", + "so:libgse-samba4.so=0", + "so:libinterfaces-samba4.so=0", + "so:libiov-buf-samba4.so=0", + "so:libkrb5samba-samba4.so=0", + "so:libldbsamba-samba4.so=0", + "so:libmessages-dgm-samba4.so=0", + "so:libmessages-util-samba4.so=0", + "so:libmsghdr-samba4.so=0", + "so:libndr-krb5pac.so.0=0.0.1", + "so:libndr-nbt.so.0=0.0.1", + "so:libndr-samba-samba4.so=0", + "so:libndr-standard.so.0=0.0.1", + "so:libndr.so.0=0.1.0", + "so:libpopt-samba3-samba4.so=0", + "so:libsamba-cluster-support-samba4.so=0", + "so:libsamba-credentials.so.0=0.0.1", + "so:libsamba-debug-samba4.so=0", + "so:libsamba-errors.so.1=1", + "so:libsamba-hostconfig.so.0=0.0.1", + "so:libsamba-modules-samba4.so=0", + "so:libsamba-security-samba4.so=0", + "so:libsamba-sockets-samba4.so=0", + "so:libsamba-util.so.0=0.0.1", + "so:libsamba3-util-samba4.so=0", + "so:libsamdb-common-samba4.so=0", + "so:libsamdb.so.0=0.0.1", + "so:libsecrets3-samba4.so=0", + "so:libserver-id-db-samba4.so=0", + "so:libserver-role-samba4.so=0", + "so:libsmb-transport-samba4.so=0", + "so:libsmbconf.so.0=0", + "so:libsmbd-shim-samba4.so=0", + "so:libsocket-blocking-samba4.so=0", + "so:libsys-rw-samba4.so=0", + "so:libtalloc-report-samba4.so=0", + "so:libtdb-wrap-samba4.so=0", + "so:libtevent-util.so.0=0.0.1", + "so:libtime-basic-samba4.so=0", + "so:libutil-cmdline-samba4.so=0", + "so:libutil-reg-samba4.so=0", + "so:libutil-setid-samba4.so=0", + "so:libutil-tdb-samba4.so=0" + ] + }, + "gtk3-xfce-engine": { + "versions": { + "3.2.0-r2": 1510920446 + }, + "origin": "gtk-xfce-engine", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0" + ] + }, + "qemu-armeb": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-armeb" + ] + }, + "lua5.2-optarg": { + "versions": { + "0.2-r1": 1509462490 + }, + "origin": "lua-optarg", + "dependencies": [ + "lua5.2" + ] + }, + "acf-fetchmail": { + "versions": { + "0.9.0-r2": 1510076434 + }, + "origin": "acf-fetchmail", + "dependencies": [ + "acf-core", + "fetchmail" + ] + }, + "asterisk-alsa": { + "versions": { + "15.6.1-r0": 1537795343, + "15.6.2-r0": 1568705005 + }, + "origin": "asterisk", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.2-libs": { + "versions": { + "5.2.4-r4": 1509459694 + }, + "origin": "lua5.2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblua-5.2.so.0=0.0.0" + ] + }, + "perl-file-slurp-tiny": { + "versions": { + "0.004-r0": 1509494489 + }, + "origin": "perl-file-slurp-tiny" + }, + "perl-devel-checklib": { + "versions": { + "1.11-r1": 1509489315 + }, + "origin": "perl-devel-checklib", + "provides": [ + "cmd:use-devel-checklib" + ] + }, + "myrepos": { + "versions": { + "1.20180726-r0": 1534931445 + }, + "origin": "myrepos", + "dependencies": [ + "perl", + "git" + ], + "provides": [ + "cmd:mr" + ] + }, + "libnetfilter_queue": { + "versions": { + "1.0.2-r0": 1509469244 + }, + "origin": "libnetfilter_queue", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0", + "so:libnfnetlink.so.0" + ], + "provides": [ + "so:libnetfilter_queue.so.1=1.3.0" + ] + }, + "neon-doc": { + "versions": { + "0.30.2-r2": 1510285201 + }, + "origin": "neon" + }, + "libao-doc": { + "versions": { + "1.2.0-r2": 1543926072 + }, + "origin": "libao" + }, + "perl-unix-syslog": { + "versions": { + "1.1-r9": 1509484018 + }, + "origin": "perl-unix-syslog", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "libnetfilter_acct": { + "versions": { + "1.0.3-r0": 1509480361 + }, + "origin": "libnetfilter_acct", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0" + ], + "provides": [ + "so:libnetfilter_acct.so.1=1.0.0" + ] + }, + "collectd-python": { + "versions": { + "5.7.2-r0": 1510069648 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "acf-gross": { + "versions": { + "0.6.0-r2": 1510075435 + }, + "origin": "acf-gross", + "dependencies": [ + "acf-core", + "gross" + ] + }, + "nginx-mod-stream": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "scstadmin-doc": { + "versions": { + "2.2.0-r2": 1509492547 + }, + "origin": "scstadmin" + }, + "perl-html-parser": { + "versions": { + "3.72-r2": 1509464394 + }, + "origin": "perl-html-parser", + "dependencies": [ + "perl-html-tagset", + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-gobject3": { + "versions": { + "3.24.1-r2": 1509481892 + }, + "origin": "py-gobject3", + "dependencies": [ + "py2-cairo", + "so:libc.musl-x86_64.so.1", + "so:libcairo-gobject.so.2", + "so:libcairo.so.2", + "so:libffi.so.6", + "so:libgirepository-1.0.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ] + }, + "perl-package-stash-doc": { + "versions": { + "0.37-r0": 1509474043 + }, + "origin": "perl-package-stash" + }, + "libxaw-doc": { + "versions": { + "1.0.13-r0": 1509480584 + }, + "origin": "libxaw" + }, + "libxext": { + "versions": { + "1.3.3-r2": 1509464538 + }, + "origin": "libxext", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXext.so.6=6.4.0" + ] + }, + "fping": { + "versions": { + "4.0-r0": 1509483914 + }, + "origin": "fping", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:fping" + ] + }, + "py2-sphinx_rtd_theme": { + "versions": { + "0.2.4-r0": 1509469909 + }, + "origin": "py-sphinx_rtd_theme", + "dependencies": [ + "python2" + ] + }, + "libmcrypt": { + "versions": { + "2.5.8-r7": 1509493846 + }, + "origin": "libmcrypt", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmcrypt.so.4=4.4.8" + ] + }, + "pingu-doc": { + "versions": { + "1.5-r1": 1509474485 + }, + "origin": "pingu" + }, + "perl-eval-closure-doc": { + "versions": { + "0.14-r0": 1509481597 + }, + "origin": "perl-eval-closure" + }, + "libcdio-paranoia-doc": { + "versions": { + "0.94_p1-r0": 1509473651 + }, + "origin": "libcdio-paranoia" + }, + "imlib2-doc": { + "versions": { + "1.4.10-r0": 1509470515 + }, + "origin": "imlib2" + }, + "abiword-plugin-bmp": { + "versions": { + "3.0.2-r1": 1510073356 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpng16.so.16", + "so:libstdc++.so.6" + ] + }, + "open-vm-tools-gtk": { + "versions": { + "10.1.15-r0": 1510922342 + }, + "origin": "open-vm-tools", + "dependencies": [ + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXi.so.6", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libXtst.so.6", + "so:libatkmm-1.6.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairomm-1.0.so.1", + "so:libfuse.so.2", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdkmm-2.4.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libglibmm-2.4.so.1", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libgtkmm-2.4.so.1", + "so:libhgfs.so.0", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6", + "so:libtirpc.so.3", + "so:libvmtools.so.0" + ], + "provides": [ + "cmd:vmware-user-suid-wrapper", + "cmd:vmware-vmblock-fuse" + ] + }, + "coova-chilli-doc": { + "versions": { + "1.3.2-r2": 1510287299 + }, + "origin": "coova-chilli" + }, + "opusfile-dev": { + "versions": { + "0.10-r0": 1512032060 + }, + "origin": "opusfile", + "dependencies": [ + "libogg-dev", + "libressl-dev", + "opus-dev", + "opusfile=0.10-r0", + "pc:ogg>=1.3", + "pc:openssl", + "pc:opus>=1.0.1", + "pkgconfig" + ], + "provides": [ + "pc:opusfile=0.10", + "pc:opusurl=0.10" + ] + }, + "libunique": { + "versions": { + "1.1.6-r6": 1510069913 + }, + "origin": "libunique", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libdbus-glib-1.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ], + "provides": [ + "so:libunique-1.0.so.0=0.100.6" + ] + }, + "pam-pgsql": { + "versions": { + "0.7.3.2-r0": 1509494688 + }, + "origin": "pam-pgsql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libpam.so.0", + "so:libpq.so.5" + ] + }, + "squid-lang-uz": { + "versions": { + "3.5.27-r0": 1519824033, + "3.5.27-r1": 1562865670 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-net-openssh": { + "versions": { + "0.74-r0": 1509473012 + }, + "origin": "perl-net-openssh" + }, + "libbsd-doc": { + "versions": { + "0.8.6-r1": 1509461838 + }, + "origin": "libbsd" + }, + "perl-dbd-mysql": { + "versions": { + "4.043-r0": 1509489325 + }, + "origin": "perl-dbd-mysql", + "dependencies": [ + "perl", + "perl-dbi", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "skalibs": { + "versions": { + "2.6.1.0-r0": 1510364878 + }, + "origin": "skalibs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libskarnet.so.2.6=2.6.1.0" + ] + }, + "jwm": { + "versions": { + "2.3.7-r0": 1510075432 + }, + "origin": "jwm", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libXmu.so.6", + "so:libXpm.so.4", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:librsvg-2.so.2" + ], + "provides": [ + "cmd:jwm" + ] + }, + "nginx": { + "versions": { + "1.12.2-r4": 1542814448 + }, + "origin": "nginx", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpcre.so.1", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:nginx" + ] + }, + "libunwind-dbg": { + "versions": { + "1.2.1-r1": 1509493800 + }, + "origin": "libunwind" + }, + "zonenotify": { + "versions": { + "0.1-r3": 1509489771 + }, + "origin": "zonenotify", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:zonenotify" + ] + }, + "ltrace": { + "versions": { + "0.7.3-r1": 1509477339 + }, + "origin": "ltrace", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libelf.so.0" + ], + "provides": [ + "cmd:ltrace" + ] + }, + "libxi-dev": { + "versions": { + "1.7.9-r1": 1509466010 + }, + "origin": "libxi", + "dependencies": [ + "libxi=1.7.9-r1", + "pc:inputproto", + "pc:x11", + "pc:xext", + "pc:xfixes", + "pkgconfig" + ], + "provides": [ + "pc:xi=1.7.9" + ] + }, + "perl-test-simple": { + "versions": { + "1.302118-r0": 1512052687 + }, + "origin": "perl-test-simple", + "provides": [ + "perl-test-tester" + ] + }, + "libnih": { + "versions": { + "1.0.3-r4": 1509473674 + }, + "origin": "libnih", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libexpat.so.1" + ], + "provides": [ + "so:libnih-dbus.so.1=1.0.0", + "so:libnih.so.1=1.0.0", + "cmd:nih-dbus-tool" + ] + }, + "djbdns": { + "versions": { + "1.05-r47": 1509488768 + }, + "origin": "djbdns", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:axfr-get", + "cmd:axfrdns", + "cmd:axfrdns-conf", + "cmd:dnsfilter", + "cmd:dnsipq", + "cmd:dnsmx", + "cmd:dnsname", + "cmd:dnsq", + "cmd:dnstrace", + "cmd:dnstracesort", + "cmd:dnstxt", + "cmd:pickdns", + "cmd:pickdns-conf", + "cmd:pickdns-data", + "cmd:random-ip", + "cmd:rbldns", + "cmd:rbldns-conf", + "cmd:rbldns-data", + "cmd:walldns", + "cmd:walldns-conf" + ] + }, + "boost-system": { + "versions": { + "1.62.0-r5": 1509465880 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libboost_system-mt.so.1.62.0=1.62.0", + "so:libboost_system.so.1.62.0=1.62.0" + ] + }, + "open-isns-lib": { + "versions": { + "0.97-r2": 1510259304 + }, + "origin": "open-isns", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ], + "provides": [ + "so:libisns.so.0=0" + ] + }, + "file-dev": { + "versions": { + "5.32-r0": 1509461604, + "5.32-r1": 1563340161, + "5.32-r2": 1572348414 + }, + "origin": "file", + "dependencies": [ + "libmagic=5.32-r2" + ] + }, + "py3-icu": { + "versions": { + "1.9.6-r3": 1510832509 + }, + "origin": "py-icu", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libicui18n.so.59", + "so:libicuuc.so.59", + "so:libpython3.6m.so.1.0", + "so:libstdc++.so.6" + ] + }, + "acf-heimdal": { + "versions": { + "0.6.0-r2": 1510072579 + }, + "origin": "acf-heimdal", + "dependencies": [ + "acf-core", + "heimdal" + ] + }, + "perl-module-pluggable": { + "versions": { + "5.2-r0": 1509483535 + }, + "origin": "perl-module-pluggable" + }, + "xz-doc": { + "versions": { + "5.2.3-r1": 1509459905 + }, + "origin": "xz" + }, + "jasper-dev": { + "versions": { + "2.0.14-r0": 1509475517 + }, + "origin": "jasper", + "dependencies": [ + "jasper-libs=2.0.14-r0", + "pkgconfig" + ], + "provides": [ + "pc:jasper=2.0.14" + ] + }, + "xf86-video-s3virge-doc": { + "versions": { + "1.10.7-r1": 1510074189 + }, + "origin": "xf86-video-s3virge" + }, + "qca-doc": { + "versions": { + "2.1.3-r4": 1510288320 + }, + "origin": "qca" + }, + "i3lock-doc": { + "versions": { + "2.9.1-r0": 1509491492 + }, + "origin": "i3lock" + }, + "lvm2-extra": { + "versions": { + "2.02.175-r0": 1509462403 + }, + "origin": "lvm2", + "dependencies": [ + "bash", + "coreutils" + ], + "provides": [ + "cmd:blkdeactivate", + "cmd:fsadm", + "cmd:lvmconf", + "cmd:lvmdump" + ] + }, + "py2-gnutls": { + "versions": { + "3.1.1-r0": 1509489166 + }, + "origin": "py-gnutls" + }, + "lua5.2-sql-mysql": { + "versions": { + "2.3.5-r1": 1509488830 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "perl-test-without": { + "versions": { + "0.10-r0": 1509496584 + }, + "origin": "perl-test-without" + }, + "newsbeuter-doc": { + "versions": { + "2.9-r6": 1510288208 + }, + "origin": "newsbeuter" + }, + "perl-x10": { + "versions": { + "0.04-r0": 1509494415 + }, + "origin": "perl-x10", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:x10client", + "cmd:x10server" + ] + }, + "libbonobo-doc": { + "versions": { + "2.32.1-r6": 1510931479 + }, + "origin": "libbonobo" + }, + "bonding": { + "versions": { + "2.6-r3": 1509480115 + }, + "origin": "bonding" + }, + "py-django-simple-captcha": { + "versions": { + "0.4.3-r1": 1509491076 + }, + "origin": "py-django-simple-captcha", + "dependencies": [ + "python2" + ] + }, + "perl-locale-maketext-fuzzy": { + "versions": { + "0.11-r1": 1510848492 + }, + "origin": "perl-locale-maketext-fuzzy", + "dependencies": [ + "perl" + ] + }, + "ldns-doc": { + "versions": { + "1.6.17-r6": 1510258937 + }, + "origin": "ldns" + }, + "lttng-ust-dev": { + "versions": { + "2.10.0-r1": 1509492304 + }, + "origin": "lttng-ust", + "dependencies": [ + "lttng-ust=2.10.0-r1", + "pc:liburcu-bp", + "pkgconfig" + ], + "provides": [ + "pc:lttng-ust=2.10.0" + ] + }, + "frotz": { + "versions": { + "2.44-r0": 1509491524 + }, + "origin": "frotz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:frotz" + ] + }, + "dkimproxy": { + "versions": { + "1.4.1-r5": 1509517849 + }, + "origin": "dkimproxy", + "dependencies": [ + "perl-mail-dkim", + "perl-net-server", + "perl-error", + "/bin/sh" + ], + "provides": [ + "cmd:dkim_responder", + "cmd:dkimproxy.in", + "cmd:dkimproxy.out" + ] + }, + "acl": { + "versions": { + "2.2.52-r3": 1509459588 + }, + "origin": "acl", + "dependencies": [ + "so:libacl.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:chacl", + "cmd:getfacl", + "cmd:setfacl" + ] + }, + "pidgin-doc": { + "versions": { + "2.12.0-r2": 1510069749 + }, + "origin": "pidgin" + }, + "squid-lang-th": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865670 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "powertop": { + "versions": { + "2.9-r1": 1509552798 + }, + "origin": "powertop", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libnl.so.1", + "so:libpci.so.3", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:powertop" + ] + }, + "perl-crypt-des": { + "versions": { + "2.07-r4": 1509471889 + }, + "origin": "perl-crypt-des", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-test-mockobject": { + "versions": { + "1.20161202-r0": 1509489412 + }, + "origin": "perl-test-mockobject", + "dependencies": [ + "perl" + ] + }, + "perl-cgi-session-doc": { + "versions": { + "4.48-r0": 1509475912 + }, + "origin": "perl-cgi-session" + }, + "gd": { + "versions": { + "2.2.5-r3": 1554728246 + }, + "origin": "gd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3" + ], + "provides": [ + "cmd:annotate", + "cmd:gd2copypal", + "cmd:gd2togif", + "cmd:gd2topng", + "cmd:gdcmpgif", + "cmd:gdparttopng", + "cmd:gdtopng", + "cmd:giftogd2", + "cmd:pngtogd", + "cmd:pngtogd2", + "cmd:webpng" + ] + }, + "py-django-pipeline": { + "versions": { + "1.3.25-r0": 1509475944 + }, + "origin": "py-django-pipeline", + "dependencies": [ + "python2" + ] + }, + "ghostscript": { + "versions": { + "9.26-r2": 1554362768, + "9.26-r3": 1565700440, + "9.26-r4": 1571234493 + }, + "origin": "ghostscript", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2", + "so:libcupsimage.so.2", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libjbig2dec.so.0", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libpng16.so.16", + "so:libtiff.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libgs.so.9=9.26", + "so:libijs-0.35.so=0", + "cmd:dvipdf", + "cmd:eps2eps", + "cmd:gs", + "cmd:gsbj", + "cmd:gsc", + "cmd:gsdj", + "cmd:gsdj500", + "cmd:gslj", + "cmd:gslp", + "cmd:gsnd", + "cmd:ijs_client_example", + "cmd:ijs_server_example", + "cmd:lprsetup.sh", + "cmd:pdf2dsc", + "cmd:pdf2ps", + "cmd:pf2afm", + "cmd:pfbtopfa", + "cmd:pphs", + "cmd:printafm", + "cmd:ps2ascii", + "cmd:ps2epsi", + "cmd:ps2pdf", + "cmd:ps2pdf12", + "cmd:ps2pdf13", + "cmd:ps2pdf14", + "cmd:ps2pdfwr", + "cmd:ps2ps", + "cmd:ps2ps2", + "cmd:unix-lpr.sh" + ] + }, + "gvfs-avahi": { + "versions": { + "1.34.1-r0": 1511430258, + "1.34.1-r1": 1563787223 + }, + "origin": "gvfs", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-glib.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgvfscommon.so", + "so:libgvfsdaemon.so", + "so:libintl.so.8" + ] + }, + "libmaa-dev": { + "versions": { + "1.3.2-r0": 1509489137 + }, + "origin": "libmaa", + "dependencies": [ + "libmaa=1.3.2-r0" + ] + }, + "libnetfilter_conntrack-dev": { + "versions": { + "1.0.6-r0": 1509469225 + }, + "origin": "libnetfilter_conntrack", + "dependencies": [ + "libnetfilter_conntrack=1.0.6-r0", + "pc:libnfnetlink", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_conntrack=1.0.6" + ] + }, + "xcb-util-wm-dev": { + "versions": { + "0.4.1-r1": 1509473927 + }, + "origin": "xcb-util-wm", + "dependencies": [ + "xcb-util-dev", + "pkgconfig", + "xcb-util-wm=0.4.1-r1" + ], + "provides": [ + "pc:xcb-ewmh=0.4.1", + "pc:xcb-icccm=0.4.1" + ] + }, + "uwsgi-logpipe": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "abiword-plugin-hancom": { + "versions": { + "3.0.2-r1": 1510073361 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "imlib2": { + "versions": { + "1.4.10-r0": 1509470515 + }, + "origin": "imlib2", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libgif.so.7", + "so:libid3tag.so.0", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libtiff.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libImlib2.so.1=1.4.10", + "cmd:imlib2_bumpmap", + "cmd:imlib2_colorspace", + "cmd:imlib2_conv", + "cmd:imlib2_grab", + "cmd:imlib2_poly", + "cmd:imlib2_show", + "cmd:imlib2_test", + "cmd:imlib2_view" + ] + }, + "lua5.2-dbi-sqlite3": { + "versions": { + "0.6-r1": 1511483402 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "iperf3": { + "versions": { + "3.2-r0": 1509475696 + }, + "origin": "iperf3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libiperf.so.0=0.0.0", + "cmd:iperf3" + ] + }, + "py3-six": { + "versions": { + "1.11.0-r0": 1509465545 + }, + "origin": "py-six", + "dependencies": [ + "python3" + ] + }, + "py3-crypto": { + "versions": { + "2.6.1-r2": 1509483001 + }, + "origin": "py-crypto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libpython3.6m.so.1.0" + ] + }, + "nagios-plugins-all": { + "versions": { + "2.2.1-r3": 1510288499 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins-breeze", + "nagios-plugins-by_ssh", + "nagios-plugins-cluster", + "nagios-plugins-dbi", + "nagios-plugins-dhcp", + "nagios-plugins-dig", + "nagios-plugins-disk_smb", + "nagios-plugins-disk", + "nagios-plugins-dns", + "nagios-plugins-dummy", + "nagios-plugins-file_age", + "nagios-plugins-fping", + "nagios-plugins-hpjd", + "nagios-plugins-http", + "nagios-plugins-icmp", + "nagios-plugins-ide_smart", + "nagios-plugins-ifoperstatus", + "nagios-plugins-ifstatus", + "nagios-plugins-ircd", + "nagios-plugins-ldap", + "nagios-plugins-load", + "nagios-plugins-log", + "nagios-plugins-mailq", + "nagios-plugins-mrtgtraf", + "nagios-plugins-mrtg", + "nagios-plugins-mysql", + "nagios-plugins-nagios", + "nagios-plugins-ntp", + "nagios-plugins-nt", + "nagios-plugins-nwstat", + "nagios-plugins-overcr", + "nagios-plugins-pgsql", + "nagios-plugins-ping", + "nagios-plugins-procs", + "nagios-plugins-radius", + "nagios-plugins-real", + "nagios-plugins-rpc", + "nagios-plugins-sensors", + "nagios-plugins-smtp", + "nagios-plugins-snmp", + "nagios-plugins-ssh", + "nagios-plugins-swap", + "nagios-plugins-time", + "nagios-plugins-ups", + "nagios-plugins-uptime", + "nagios-plugins-users", + "nagios-plugins-wave", + "nagios-plugins-openrc", + "nagios-plugins-tcp" + ] + }, + "libxslt-dev": { + "versions": { + "1.1.31-r1": 1555487793, + "1.1.31-r2": 1572541164 + }, + "origin": "libxslt", + "dependencies": [ + "libxslt=1.1.31-r2", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libexslt=0.8.19", + "pc:libxslt=1.1.31", + "cmd:xslt-config" + ] + }, + "lxc-doc": { + "versions": { + "2.1.1-r3": 1533578832 + }, + "origin": "lxc" + }, + "tcpflow": { + "versions": { + "1.4.5-r3": 1510260713, + "1.5.0-r1": 1562596229 + }, + "origin": "tcpflow", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.0.0", + "so:libgcc_s.so.1", + "so:libpcap.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:tcpflow" + ] + }, + "hexchat-doc": { + "versions": { + "2.12.4-r1": 1510260816 + }, + "origin": "hexchat" + }, + "uwsgi-transformation_gzip": { + "versions": { + "2.0.17-r0": 1522154660 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "freeradius-redis": { + "versions": { + "3.0.15-r4": 1556202797, + "3.0.15-r5": 1566310605 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.15-r5", + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.13" + ], + "provides": [ + "so:rlm_redis.so=0", + "so:rlm_rediswho.so=0" + ] + }, + "gtkspell-lang": { + "versions": { + "2.0.16-r6": 1510069682 + }, + "origin": "gtkspell" + }, + "libtorrent": { + "versions": { + "0.13.6-r3": 1510259942 + }, + "origin": "libtorrent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libtorrent.so.19=19.0.0" + ] + }, + "lua-rex-pcre": { + "versions": { + "2.9.0-r0": 1510619366 + }, + "origin": "lua-rex" + }, + "perl-net-server": { + "versions": { + "2.009-r0": 1509479737 + }, + "origin": "perl-net-server", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:net-server" + ] + }, + "perl-namespace-clean-doc": { + "versions": { + "0.27-r0": 1509474074 + }, + "origin": "perl-namespace-clean" + }, + "kmod-doc": { + "versions": { + "24-r0": 1509462316 + }, + "origin": "kmod" + }, + "tzdata-doc": { + "versions": { + "2019a-r0": 1556626900, + "2019b-r0": 1566548308, + "2019c-r0": 1571319267 + }, + "origin": "tzdata" + }, + "gst-plugins-good0.10-lang": { + "versions": { + "0.10.31-r0": 1510068452 + }, + "origin": "gst-plugins-good0.10" + }, + "perl-net-server-doc": { + "versions": { + "2.009-r0": 1509479736 + }, + "origin": "perl-net-server" + }, + "uwsgi-ugreen": { + "versions": { + "2.0.17-r0": 1522154660 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "mcabber-dev": { + "versions": { + "1.1.0-r0": 1510588318 + }, + "origin": "mcabber", + "dependencies": [ + "pc:glib-2.0", + "pc:gmodule-2.0", + "pc:loudmouth-1.0", + "pkgconfig" + ], + "provides": [ + "pc:mcabber=1.1.0" + ] + }, + "uwsgi-transformation_tofile": { + "versions": { + "2.0.17-r0": 1522154660 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-io-multiplex-doc": { + "versions": { + "1.16-r0": 1509479739 + }, + "origin": "perl-io-multiplex" + }, + "perl-ldap": { + "versions": { + "0.65-r1": 1510564476 + }, + "origin": "perl-ldap", + "dependencies": [ + "perl-convert-asn1" + ], + "provides": [ + "perl-net-ldap" + ] + }, + "libnice-doc": { + "versions": { + "0.1.14-r2": 1510931344 + }, + "origin": "libnice" + }, + "libunistring-doc": { + "versions": { + "0.9.7-r0": 1509458999 + }, + "origin": "libunistring" + }, + "libwebsockets-doc": { + "versions": { + "2.4.0-r1": 1510258054 + }, + "origin": "libwebsockets" + }, + "gcr-doc": { + "versions": { + "3.20.0-r1": 1510073693 + }, + "origin": "gcr" + }, + "libmodplug": { + "versions": { + "0.8.9.0-r0": 1509489449 + }, + "origin": "libmodplug", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libmodplug.so.1=1.0.0" + ] + }, + "at-spi2-core-lang": { + "versions": { + "2.26.2-r0": 1509466052 + }, + "origin": "at-spi2-core" + }, + "v4l-utils-dvbv5": { + "versions": { + "1.12.5-r1": 1510072044 + }, + "origin": "v4l-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libudev.so.1" + ], + "provides": [ + "so:libdvbv5.so.0=0.0.0", + "cmd:dvb-fe-tool", + "cmd:dvb-format-convert", + "cmd:dvbv5-daemon", + "cmd:dvbv5-scan", + "cmd:dvbv5-zap" + ] + }, + "ncdu": { + "versions": { + "1.12-r0": 1509495341 + }, + "origin": "ncdu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:ncdu" + ] + }, + "qemu-microblazeel": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-microblazeel" + ] + }, + "libvirt-uml": { + "versions": { + "3.9.0-r1": 1510088352, + "5.5.0-r0": 1562165540 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-daemon=5.5.0-r0", + "libvirt-common-drivers=5.5.0-r0" + ] + }, + "xf86-video-xgixp-doc": { + "versions": { + "1.8.1-r8": 1510069922 + }, + "origin": "xf86-video-xgixp" + }, + "sems-jsonrpc": { + "versions": { + "1.6.0-r6": 1510260897 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libev.so.4", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "py3-zope-interface": { + "versions": { + "4.3.2-r1": 1509493283 + }, + "origin": "py-zope-interface", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "py3-oauthlib": { + "versions": { + "2.0.6-r1": 1509552760 + }, + "origin": "py-oauthlib", + "dependencies": [ + "py3-crypto", + "py3-jwt", + "python3" + ] + }, + "gnome-vfs-doc": { + "versions": { + "2.24.4-r6": 1510931437 + }, + "origin": "gnome-vfs" + }, + "perl-cgi-psgi": { + "versions": { + "0.15-r1": 1510564479 + }, + "origin": "perl-cgi-psgi", + "dependencies": [ + "perl-cgi" + ] + }, + "augeas-tests": { + "versions": { + "1.9.0-r3": 1511899746 + }, + "origin": "augeas" + }, + "spl": { + "versions": { + "0.7.1-r0": 1509492193 + }, + "origin": "spl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:splat", + "cmd:splslab.py" + ] + }, + "uwsgi-router_redis": { + "versions": { + "2.0.17-r0": 1522154657 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "gtkspell-doc": { + "versions": { + "2.0.16-r6": 1510069681 + }, + "origin": "gtkspell" + }, + "ruby-augeas": { + "versions": { + "0.5.0-r3": 1509482368 + }, + "origin": "ruby-augeas", + "dependencies": [ + "ruby", + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.4" + ] + }, + "libcue": { + "versions": { + "2.1.0-r0": 1509494076 + }, + "origin": "libcue", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcue.so.2=2.1.0" + ] + }, + "perl-module-scandeps": { + "versions": { + "1.24-r0": 1510352936 + }, + "origin": "perl-module-scandeps", + "provides": [ + "cmd:scandeps.pl" + ] + }, + "libsamplerate-dev": { + "versions": { + "0.1.9-r0": 1509473264 + }, + "origin": "libsamplerate", + "dependencies": [ + "libsamplerate=0.1.9-r0", + "pkgconfig" + ], + "provides": [ + "pc:samplerate=0.1.9" + ] + }, + "dovecot-fts-solr": { + "versions": { + "2.2.36.3-r0": 1554102391, + "2.2.36.4-r0": 1567075099 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot", + "so:lib20_fts_plugin.so", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ], + "provides": [ + "so:lib21_fts_solr_plugin.so=0" + ] + }, + "linenoise-dev": { + "versions": { + "1.0-r1": 1509462460 + }, + "origin": "linenoise", + "dependencies": [ + "linenoise=1.0-r1" + ] + }, + "fakeroot": { + "versions": { + "1.21-r1": 1509459623 + }, + "origin": "fakeroot", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfakeroot-0.so=0", + "cmd:faked", + "cmd:fakeroot" + ] + }, + "poppler-doc": { + "versions": { + "0.56.0-r0": 1509465066, + "0.56.0-r1": 1569334548 + }, + "origin": "poppler" + }, + "perl-canary-stability": { + "versions": { + "2012-r0": 1509474458 + }, + "origin": "perl-canary-stability" + }, + "libnice-gstreamer0.10": { + "versions": { + "0.1.14-r2": 1510931344 + }, + "origin": "libnice", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libnice.so.10" + ] + }, + "lua5.2-pty": { + "versions": { + "1.2.1-r1": 1509496423 + }, + "origin": "lua-pty", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "cracklib": { + "versions": { + "2.9.6-r0": 1509485917 + }, + "origin": "cracklib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcrack.so.2=2.9.0", + "cmd:cracklib-check", + "cmd:cracklib-format", + "cmd:cracklib-packer", + "cmd:cracklib-unpacker", + "cmd:create-cracklib-dict" + ] + }, + "gnu-efi": { + "versions": { + "3.0.4-r1": 1509482584 + }, + "origin": "gnu-efi" + }, + "mkfontdir-doc": { + "versions": { + "1.0.7-r1": 1509469874 + }, + "origin": "mkfontdir" + }, + "opus": { + "versions": { + "1.2.1-r1": 1509468917 + }, + "origin": "opus", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libopus.so.0=0.6.1" + ] + }, + "lua5.1-sql-postgres": { + "versions": { + "2.3.5-r1": 1509488829 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "rdiff-backup-doc": { + "versions": { + "1.2.8-r2": 1509483271 + }, + "origin": "rdiff-backup" + }, + "collectd-virt": { + "versions": { + "5.7.2-r0": 1510069650 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libvirt.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "collectd-libvirt" + ] + }, + "perl-encode-locale-doc": { + "versions": { + "1.05-r1": 1509464360 + }, + "origin": "perl-encode-locale" + }, + "perl-symbol-global-name-doc": { + "versions": { + "0.05-r1": 1510564552 + }, + "origin": "perl-symbol-global-name" + }, + "zfs-doc": { + "versions": { + "0.7.3-r0": 1509490073 + }, + "origin": "zfs" + }, + "nfdump-dbg": { + "versions": { + "1.6.15-r0": 1509494467, + "1.6.15-r1": 1574265426 + }, + "origin": "nfdump" + }, + "augeas-dev": { + "versions": { + "1.9.0-r3": 1511899746 + }, + "origin": "augeas", + "dependencies": [ + "augeas-libs=1.9.0-r3", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:augeas=1.9.0" + ] + }, + "xf86-input-libinput": { + "versions": { + "0.26.0-r0": 1510303212 + }, + "origin": "xf86-input-libinput", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libinput.so.10" + ] + }, + "qemu-mipsel": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mipsel" + ] + }, + "perl-tree-simple-doc": { + "versions": { + "1.18-r1": 1509494481 + }, + "origin": "perl-tree-simple" + }, + "glade3-doc": { + "versions": { + "3.8.5-r4": 1510067963 + }, + "origin": "glade3" + }, + "shorewall-doc": { + "versions": { + "5.1.8-r0": 1509481282 + }, + "origin": "shorewall" + }, + "acf-weblog": { + "versions": { + "0.11.1-r1": 1510074829 + }, + "origin": "acf-weblog", + "dependencies": [ + "acf-core", + "lua-sql-postgres", + "wget", + "postgresql-client", + "lua-subprocess", + "/bin/sh" + ], + "provides": [ + "cmd:acf-weblog-update-schema" + ] + }, + "pidgin-dev": { + "versions": { + "2.12.0-r2": 1510069747 + }, + "origin": "pidgin", + "dependencies": [ + "finch=2.12.0-r2", + "libpurple=2.12.0-r2", + "pc:glib-2.0", + "pc:gtk+-2.0", + "pkgconfig" + ], + "provides": [ + "pc:finch=2.12.0", + "pc:gnt=2.12.0", + "pc:pidgin=2.12.0", + "pc:purple=2.12.0" + ] + }, + "herbstluftwm-doc": { + "versions": { + "0.7.0-r1": 1509551853 + }, + "origin": "herbstluftwm" + }, + "font-daewoo-misc": { + "versions": { + "1.0.3-r0": 1509495332 + }, + "origin": "font-daewoo-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "bind-libs": { + "versions": { + "9.11.6_p1-r1": 1556872767, + "9.11.8-r0": 1561323229 + }, + "origin": "bind", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libbind9.so.161=161.0.2", + "so:libdns.so.1106=1106.0.1", + "so:libirs.so.161=161.0.0", + "so:libisc.so.1100=1100.2.0", + "so:libisccc.so.161=161.0.1", + "so:libisccfg.so.163=163.0.1", + "so:liblwres.so.161=161.0.1" + ] + }, + "perl-cache-cache": { + "versions": { + "1.08-r0": 1509469202 + }, + "origin": "perl-cache-cache", + "dependencies": [ + "perl-error", + "perl-digest-sha1" + ] + }, + "freeradius-perl": { + "versions": { + "3.0.15-r4": 1556202796, + "3.0.15-r5": 1566310604 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.15-r5", + "perl", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ], + "provides": [ + "freeradius3-perl=3.0.15-r5", + "so:rlm_perl.so=0" + ] + }, + "uwsgi-emperor_amqp": { + "versions": { + "2.0.17-r0": 1522154654 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "qpdf-libs": { + "versions": { + "7.0.0-r0": 1509482355 + }, + "origin": "qpdf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libjpeg.so.8", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libqpdf.so.18=18.1.0" + ] + }, + "libspf2": { + "versions": { + "1.2.10-r2": 1509491334 + }, + "origin": "libspf2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libspf2.so.2=2.1.0" + ] + }, + "sdl2_mixer": { + "versions": { + "2.0.2-r0": 1511859191 + }, + "origin": "sdl2_mixer", + "dependencies": [ + "so:libSDL2-2.0.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL2_mixer-2.0.so.0=0.2.0" + ] + }, + "perl-digest-sha1": { + "versions": { + "2.13-r9": 1509468921 + }, + "origin": "perl-digest-sha1", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "openldap-overlay-unique": { + "versions": { + "2.4.45-r3": 1510258136, + "2.4.46-r0": 1565073941, + "2.4.48-r0": 1566900518 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "parole-lang": { + "versions": { + "0.9.2-r1": 1510075085 + }, + "origin": "parole", + "dependencies": [ + "gst-plugins-good" + ] + }, + "cvs-doc": { + "versions": { + "1.11.23-r0": 1509481179 + }, + "origin": "cvs" + }, + "py-purl": { + "versions": { + "1.3.1-r0": 1509483543 + }, + "origin": "py-purl", + "dependencies": [ + "py-six" + ] + }, + "freeglut": { + "versions": { + "3.0.0-r0": 1510071762 + }, + "origin": "freeglut", + "dependencies": [ + "so:libGL.so.1", + "so:libX11.so.6", + "so:libXi.so.6", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libglut.so.3=3.10.0" + ] + }, + "mesa-egl": { + "versions": { + "17.2.4-r1": 1510741947 + }, + "origin": "mesa", + "dependencies": [ + "so:libX11-xcb.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libexpat.so.1", + "so:libgbm.so.1", + "so:libwayland-client.so.0", + "so:libwayland-server.so.0", + "so:libxcb-dri2.so.0", + "so:libxcb-dri3.so.0", + "so:libxcb-present.so.0", + "so:libxcb-sync.so.1", + "so:libxcb-xfixes.so.0", + "so:libxcb.so.1", + "so:libxshmfence.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libEGL.so.1=1.0.0" + ] + }, + "py-newt": { + "versions": { + "0.52.20-r0": 1509476145 + }, + "origin": "newt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnewt.so.0.52" + ] + }, + "py3-ipaddress": { + "versions": { + "1.0.18-r1": 1509480832 + }, + "origin": "py-ipaddress", + "dependencies": [ + "python3" + ] + }, + "wv-dev": { + "versions": { + "1.2.9-r3": 1509483981 + }, + "origin": "wv", + "dependencies": [ + "pc:libgsf-1>=1.13.0", + "pkgconfig", + "wv=1.2.9-r3" + ], + "provides": [ + "pc:wv-1.0=1.2.9" + ] + }, + "font-bh-ttf": { + "versions": { + "1.0.3-r0": 1509491291 + }, + "origin": "font-bh-ttf", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-universal-require": { + "versions": { + "0.13-r0": 1509469905 + }, + "origin": "perl-universal-require", + "dependencies": [ + "perl" + ] + }, + "exo": { + "versions": { + "0.11.5-r0": 1510068072 + }, + "origin": "exo", + "dependencies": [ + "hicolor-icon-theme", + "so:libX11.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libxfce4ui-2.so.0", + "so:libxfce4util.so.7" + ], + "provides": [ + "so:libexo-1.so.0=0.1.0", + "so:libexo-2.so.0=0.1.0", + "cmd:exo-csource", + "cmd:exo-desktop-item-edit", + "cmd:exo-open", + "cmd:exo-preferred-applications" + ] + }, + "perl-sys-mmap": { + "versions": { + "0.19-r1": 1509489718 + }, + "origin": "perl-sys-mmap", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-gnome-gnomevfs": { + "versions": { + "2.28.1-r5": 1510933056 + }, + "origin": "py-gnome", + "dependencies": [ + "gnome-vfs", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgnomevfs-2.so.0", + "so:libgobject-2.0.so.0", + "so:libpython2.7.so.1.0" + ] + }, + "linux-hardened-dev": { + "versions": { + "4.9.65-r1": 1511798414 + }, + "origin": "linux-hardened", + "dependencies": [ + "gmp-dev", + "bash", + "so:libc.musl-x86_64.so.1", + "so:libelf.so.1", + "so:libstdc++.so.6" + ] + }, + "boost-date_time": { + "versions": { + "1.62.0-r5": 1509465874 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_date_time-mt.so.1.62.0=1.62.0", + "so:libboost_date_time.so.1.62.0=1.62.0" + ] + }, + "ruby-rdoc": { + "versions": { + "2.4.6-r0": 1557166823 + }, + "origin": "ruby", + "dependencies": [ + "ruby", + "ruby-json", + "ruby-io-console" + ], + "provides": [ + "cmd:rdoc", + "cmd:ri" + ] + }, + "libnl": { + "versions": { + "1.1.4-r0": 1509473008 + }, + "origin": "libnl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnl.so.1=1.1.4" + ] + }, + "libasyncns": { + "versions": { + "0.8-r0": 1509490869 + }, + "origin": "libasyncns", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libasyncns.so.0=0.3.1" + ] + }, + "swatch-doc": { + "versions": { + "3.2.4-r4": 1510564574 + }, + "origin": "swatch" + }, + "serf": { + "versions": { + "1.3.9-r3": 1510314338 + }, + "origin": "serf", + "dependencies": [ + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:libserf-1.so.1=1.3.0" + ] + }, + "libtirpc-doc": { + "versions": { + "1.0.1-r2": 1509469767 + }, + "origin": "libtirpc" + }, + "perl-compress-raw-zlib": { + "versions": { + "2.076-r0": 1511795062 + }, + "origin": "perl-compress-raw-zlib", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "cvechecker": { + "versions": { + "3.8-r1": 1510072447 + }, + "origin": "cvechecker", + "dependencies": [ + "gawk", + "wget", + "libxslt", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libconfig.so.9", + "so:libmysqlclient.so.18", + "so:libsqlite3.so.0" + ], + "provides": [ + "cmd:cvechecker", + "cmd:cvegenversdat", + "cmd:cvereport", + "cmd:cverules", + "cmd:pullcves" + ] + }, + "libvorbis-dev": { + "versions": { + "1.3.6-r2": 1549615971 + }, + "origin": "libvorbis", + "dependencies": [ + "libvorbis=1.3.6-r2", + "pc:ogg", + "pkgconfig" + ], + "provides": [ + "pc:vorbis=1.3.6", + "pc:vorbisenc=1.3.6", + "pc:vorbisfile=1.3.6" + ] + }, + "augeas-libs": { + "versions": { + "1.9.0-r3": 1511899746 + }, + "origin": "augeas", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "so:libaugeas.so.0=0.23.0", + "so:libfa.so.1=1.4.5" + ] + }, + "audit-libs": { + "versions": { + "2.7.7-r1": 1509491479 + }, + "origin": "audit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0" + ], + "provides": [ + "so:libaudit.so.1=1.0.0", + "so:libauparse.so.0=0.0.0" + ] + }, + "libgcrypt": { + "versions": { + "1.8.3-r0": 1529408085, + "1.8.3-r1": 1564327775, + "1.8.3-r2": 1574265351 + }, + "origin": "libgcrypt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgpg-error.so.0" + ], + "provides": [ + "so:libgcrypt.so.20=20.2.3", + "cmd:dumpsexp", + "cmd:hmac256", + "cmd:mpicalc" + ] + }, + "rrdtool": { + "versions": { + "1.5.6-r3": 1512296350 + }, + "origin": "rrdtool", + "dependencies": [ + "font-sony-misc", + "so:libc.musl-x86_64.so.1", + "so:librrd.so.4" + ], + "provides": [ + "cmd:rrdtool" + ] + }, + "perl-data-page-pageset-doc": { + "versions": { + "1.02-r1": 1509493852 + }, + "origin": "perl-data-page-pageset" + }, + "ipvsadm": { + "versions": { + "1.29-r0": 1509494682 + }, + "origin": "ipvsadm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnl.so.1", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:ipvsadm", + "cmd:ipvsadm-restore", + "cmd:ipvsadm-save" + ] + }, + "openssh-sftp-server": { + "versions": { + "7.5_p1-r10": 1551712288 + }, + "origin": "openssh", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "squid-lang-tr": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865670 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "ngircd": { + "versions": { + "24-r2": 1510259315 + }, + "origin": "ngircd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpam.so.0", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:ngircd" + ] + }, + "chrpath": { + "versions": { + "0.16-r1": 1509466380 + }, + "origin": "chrpath", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:chrpath" + ] + }, + "sdl2_image": { + "versions": { + "2.0.2-r1": 1528280868, + "2.0.5-r0": 1568803593 + }, + "origin": "sdl2_image", + "dependencies": [ + "so:libSDL2-2.0.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL2_image-2.0.so.0=0.2.3" + ] + }, + "bluez": { + "versions": { + "5.47-r3": 1510069788 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:bluemoon", + "cmd:bluetoothctl", + "cmd:bluez-simple-agent", + "cmd:btattach", + "cmd:hex2hcd", + "cmd:l2ping", + "cmd:l2test", + "cmd:mpris-proxy", + "cmd:rctest" + ] + }, + "libsecret": { + "versions": { + "0.18.5-r0": 1509483964 + }, + "origin": "libsecret", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libsecret-1.so.0=0.0.0", + "cmd:secret-tool" + ] + }, + "ortp-dev": { + "versions": { + "0.25.0-r0": 1509479727 + }, + "origin": "ortp", + "dependencies": [ + "ortp=0.25.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:ortp=0.25.0" + ] + }, + "perl-business-hours-doc": { + "versions": { + "0.12-r1": 1511889850 + }, + "origin": "perl-business-hours" + }, + "open-lldp-dev": { + "versions": { + "0.9.46-r3": 1510075776 + }, + "origin": "open-lldp", + "dependencies": [ + "open-lldp=0.9.46-r3", + "pkgconfig" + ], + "provides": [ + "pc:liblldp_clif=1.0.0", + "pc:lldpad=0.9.46" + ] + }, + "perl-test-failwarnings": { + "versions": { + "0.008-r1": 1510855678 + }, + "origin": "perl-test-failwarnings" + }, + "libcanberra": { + "versions": { + "0.30-r1": 1510071977 + }, + "origin": "libcanberra", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libltdl.so.7", + "so:libvorbisfile.so.3" + ], + "provides": [ + "so:libcanberra.so.0=0.2.5" + ] + }, + "libatomic_ops-doc": { + "versions": { + "7.4.8-r0": 1509878778 + }, + "origin": "libatomic_ops" + }, + "perl-utils": { + "versions": { + "5.26.3-r0": 1543940997 + }, + "origin": "perl", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:corelist", + "cmd:cpan", + "cmd:encguess", + "cmd:h2ph", + "cmd:instmodsh", + "cmd:json_pp", + "cmd:libnetcfg", + "cmd:perlbug", + "cmd:perlthanks", + "cmd:piconv", + "cmd:pl2pm", + "cmd:prove", + "cmd:ptar", + "cmd:ptardiff", + "cmd:ptargrep", + "cmd:shasum", + "cmd:splain", + "cmd:zipdetails" + ] + }, + "gst-plugins-bad0.10-doc": { + "versions": { + "0.10.23-r7": 1510287374 + }, + "origin": "gst-plugins-bad0.10" + }, + "openbox": { + "versions": { + "3.6.1-r1": 1510073886 + }, + "origin": "openbox", + "dependencies": [ + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libXcursor.so.1", + "so:libXext.so.6", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libintl.so.8", + "so:libobrender.so.32", + "so:libobt.so.2", + "so:libstartup-notification-1.so.0" + ], + "provides": [ + "cmd:obxprop", + "cmd:openbox", + "cmd:openbox-session", + "cmd:setlayout" + ] + }, + "libraw-dev": { + "versions": { + "0.18.6-r0": 1514450639 + }, + "origin": "libraw", + "dependencies": [ + "libraw=0.18.6-r0", + "pkgconfig" + ], + "provides": [ + "pc:libraw=0.18.6", + "pc:libraw_r=0.18.6" + ] + }, + "py-oauthlib": { + "versions": { + "2.0.6-r1": 1509552760 + }, + "origin": "py-oauthlib", + "dependencies": [ + "py-crypto", + "py-jwt" + ] + }, + "xfce4-dev-tools": { + "versions": { + "4.12.0-r0": 1510073856 + }, + "origin": "xfce4-dev-tools", + "dependencies": [ + "automake", + "autoconf", + "make", + "intltool", + "pkgconfig", + "libtool", + "gtk-doc", + "glib-dev", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "cmd:xdt-autogen", + "cmd:xdt-commit", + "cmd:xdt-csource" + ] + }, + "aconf-mod-network": { + "versions": { + "0.6.5-r0": 1510073709 + }, + "origin": "aconf", + "dependencies": [ + "aconf" + ] + }, + "py-gnome-libgnome": { + "versions": { + "2.28.1-r5": 1510933056 + }, + "origin": "py-gnome", + "dependencies": [ + "py-gtk", + "py-gnome-bonobo", + "py-gnome-gnomecanvas", + "py-gnome-gnomevfs", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgnome-2.so.0", + "so:libgobject-2.0.so.0", + "so:libpopt.so.0", + "so:libpython2.7.so.1.0" + ] + }, + "aspell-utils": { + "versions": { + "0.60.6.1-r12": 1509472867, + "0.60.6.1-r13": 1572541141 + }, + "origin": "aspell", + "dependencies": [ + "aspell", + "perl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:aspell-import", + "cmd:precat", + "cmd:preunzip", + "cmd:prezip", + "cmd:prezip-bin", + "cmd:run-with-aspell", + "cmd:word-list-compress" + ] + }, + "openldap-back-relay": { + "versions": { + "2.4.45-r3": 1510258133, + "2.4.46-r0": 1565073939, + "2.4.48-r0": 1566900516 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "lua5.1-cjson": { + "versions": { + "2.1.0-r7": 1509462473 + }, + "origin": "lua-cjson", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-sys-mmap-doc": { + "versions": { + "0.19-r1": 1509489717 + }, + "origin": "perl-sys-mmap" + }, + "libssh": { + "versions": { + "0.7.6-r0": 1540400366 + }, + "origin": "libssh", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libz.so.1" + ], + "provides": [ + "so:libssh.so.4=4.4.3", + "so:libssh_threads.so.4=4.4.3" + ] + }, + "py-lxml": { + "versions": { + "4.1.1-r0": 1510088209 + }, + "origin": "py-lxml" + }, + "font-winitzki-cyrillic": { + "versions": { + "1.0.3-r0": 1509477635 + }, + "origin": "font-winitzki-cyrillic", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-cache-cache-doc": { + "versions": { + "1.08-r0": 1509469199 + }, + "origin": "perl-cache-cache" + }, + "krb5-conf": { + "versions": { + "1.0-r1": 1509469246 + }, + "origin": "krb5-conf" + }, + "perl-crypt-ssleay-doc": { + "versions": { + "0.72-r7": 1510260987 + }, + "origin": "perl-crypt-ssleay" + }, + "libvirt-static": { + "versions": { + "3.9.0-r1": 1510088351, + "5.5.0-r0": 1562165539 + }, + "origin": "libvirt", + "dependencies": [ + "lvm2" + ] + }, + "celt051": { + "versions": { + "0.5.1.3-r0": 1509475638 + }, + "origin": "celt051", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0" + ], + "provides": [ + "so:libcelt051.so.0=0.0.0", + "cmd:celtdec051", + "cmd:celtenc051" + ] + }, + "clamav-doc": { + "versions": { + "0.100.3-r0": 1555508237 + }, + "origin": "clamav" + }, + "net-snmp": { + "versions": { + "5.7.3-r10": 1510259617 + }, + "origin": "net-snmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.30", + "so:libnetsnmpagent.so.30", + "so:libnetsnmpmibs.so.30", + "so:libnetsnmptrapd.so.30" + ], + "provides": [ + "cmd:snmpd", + "cmd:snmptrapd" + ] + }, + "perl-pathtools-doc": { + "versions": { + "3.62-r3": 1509488596 + }, + "origin": "perl-pathtools" + }, + "perl-mail-domainkeys-doc": { + "versions": { + "1.0-r2": 1509494035 + }, + "origin": "perl-mail-domainkeys" + }, + "perl-cgi-emulate-psgi-doc": { + "versions": { + "0.23-r0": 1509493702 + }, + "origin": "perl-cgi-emulate-psgi" + }, + "eudev-dev": { + "versions": { + "3.2.4-r1": 1509466081 + }, + "origin": "eudev", + "dependencies": [ + "eudev=3.2.4-r1", + "pkgconfig" + ], + "provides": [ + "pc:libudev=220", + "pc:udev=220" + ] + }, + "xfce4-power-manager-doc": { + "versions": { + "1.6.0-r2": 1510068241 + }, + "origin": "xfce4-power-manager" + }, + "uwsgi-rpc": { + "versions": { + "2.0.17-r0": 1522154658 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-html-formattext-withlinks-andtables": { + "versions": { + "0.07-r0": 1509481579 + }, + "origin": "perl-html-formattext-withlinks-andtables", + "dependencies": [ + "perl-html-formattext-withlinks" + ] + }, + "perl-list-utilsby-doc": { + "versions": { + "0.10-r0": 1509488642 + }, + "origin": "perl-list-utilsby" + }, + "mpfr3-doc": { + "versions": { + "3.1.5-r1": 1509457049 + }, + "origin": "mpfr3" + }, + "sc-doc": { + "versions": { + "7.16-r4": 1509495483 + }, + "origin": "sc" + }, + "db-utils": { + "versions": { + "5.3.28-r0": 1509469315 + }, + "origin": "db", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so" + ], + "provides": [ + "cmd:db_archive", + "cmd:db_checkpoint", + "cmd:db_deadlock", + "cmd:db_dump", + "cmd:db_hotbackup", + "cmd:db_load", + "cmd:db_log_verify", + "cmd:db_printlog", + "cmd:db_recover", + "cmd:db_replicate", + "cmd:db_stat", + "cmd:db_tuner", + "cmd:db_upgrade", + "cmd:db_verify" + ] + }, + "lua-posix-doc": { + "versions": { + "33.4.0-r0": 1509468341 + }, + "origin": "lua-posix" + }, + "xauth": { + "versions": { + "1.0.10-r1": 1509473717 + }, + "origin": "xauth", + "dependencies": [ + "so:libX11.so.6", + "so:libXau.so.6", + "so:libXext.so.6", + "so:libXmuu.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xauth" + ] + }, + "perl-pathtools": { + "versions": { + "3.62-r3": 1509488596 + }, + "origin": "perl-pathtools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libgd": { + "versions": { + "2.2.5-r3": 1554728246 + }, + "origin": "gd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libwebp.so.7", + "so:libz.so.1" + ], + "provides": [ + "so:libgd.so.3=3.0.5" + ] + }, + "qemu-system-m68k": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-m68k" + ] + }, + "iso-codes": { + "versions": { + "3.75-r0": 1509466022 + }, + "origin": "iso-codes" + }, + "perl-test-inter-doc": { + "versions": { + "1.06-r0": 1509485921 + }, + "origin": "perl-test-inter" + }, + "libobjc": { + "versions": { + "6.4.0-r5": 1509458075 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libobjc.so.4=4.0.0" + ] + }, + "perl-net-http": { + "versions": { + "6.09-r1": 1509464378 + }, + "origin": "perl-net-http", + "dependencies": [ + "perl", + "perl-uri" + ] + }, + "perl-text-password-pronounceable": { + "versions": { + "0.30-r1": 1510564569 + }, + "origin": "perl-text-password-pronounceable", + "dependencies": [ + "perl" + ] + }, + "sems-webconference": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "sems-xmlrpc2di", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "tumbler-doc": { + "versions": { + "0.2.0-r0": 1510075180 + }, + "origin": "tumbler" + }, + "qemu-sparc": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-sparc" + ] + }, + "linux-virthardened": { + "versions": { + "4.9.65-r1": 1511798433 + }, + "origin": "linux-hardened", + "dependencies": [ + "mkinitfs" + ], + "provides": [ + "linux-virtgrsec=4.9.65-r1" + ] + }, + "distcc-doc": { + "versions": { + "3.1-r12": 1509493043 + }, + "origin": "distcc" + }, + "libvirt-qemu": { + "versions": { + "3.9.0-r1": 1510088351, + "5.5.0-r0": 1562165540 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-daemon=5.5.0-r0", + "libvirt-common-drivers=5.5.0-r0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libintl.so.8", + "so:libvirt.so.0", + "so:libxml2.so.2" + ] + }, + "pcre-dev": { + "versions": { + "8.41-r1": 1509464276 + }, + "origin": "pcre", + "dependencies": [ + "libpcre16=8.41-r1", + "libpcre32=8.41-r1", + "libpcrecpp=8.41-r1", + "pcre=8.41-r1", + "pkgconfig" + ], + "provides": [ + "pc:libpcre16=8.41", + "pc:libpcre32=8.41", + "pc:libpcre=8.41", + "pc:libpcrecpp=8.41", + "pc:libpcreposix=8.41", + "cmd:pcre-config" + ] + }, + "orc-doc": { + "versions": { + "0.4.27-r0": 1509469955 + }, + "origin": "orc" + }, + "build-base": { + "versions": { + "0.5-r0": 1509458198 + }, + "origin": "build-base", + "dependencies": [ + "binutils", + "gcc", + "g++", + "make", + "libc-dev", + "fortify-headers" + ] + }, + "expect-dev": { + "versions": { + "5.45-r4": 1509489556 + }, + "origin": "expect", + "dependencies": [ + "tcl-dev" + ] + }, + "postgresql-bdr-contrib": { + "versions": { + "9.4.10_p1-r3": 1510259286 + }, + "origin": "postgresql-bdr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:oid2name", + "cmd:pg_archivecleanup", + "cmd:pg_standby", + "cmd:pg_test_fsync", + "cmd:pg_test_timing", + "cmd:pg_upgrade", + "cmd:pg_xlogdump", + "cmd:pgbench", + "cmd:vacuumlo" + ] + }, + "docs": { + "versions": { + "0.2-r0": 1509492704 + }, + "origin": "docs", + "dependencies": [ + "man" + ] + }, + "appstream-glib-builder": { + "versions": { + "0.6.3-r0": 1510067862 + }, + "origin": "appstream-glib", + "dependencies": [ + "so:libappstream-glib.so.8", + "so:libarchive.so.13", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjson-glib-1.0.so.0", + "so:libpango-1.0.so.0" + ], + "provides": [ + "so:libappstream-builder.so.8=8.0.10", + "cmd:appstream-builder" + ] + }, + "xf86-video-nouveau-doc": { + "versions": { + "1.0.15-r0": 1510074881 + }, + "origin": "xf86-video-nouveau" + }, + "asciidoc-vim": { + "versions": { + "8.6.10-r0": 1509461782 + }, + "origin": "asciidoc" + }, + "perl-crypt-eksblowfish": { + "versions": { + "0.009-r4": 1509470469 + }, + "origin": "perl-crypt-eksblowfish", + "dependencies": [ + "perl-class-mix", + "so:libc.musl-x86_64.so.1" + ] + }, + "libetpan": { + "versions": { + "1.8-r2": 1509721604 + }, + "origin": "libetpan", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:libgnutls.so.30", + "so:libsasl2.so.3", + "so:libz.so.1" + ], + "provides": [ + "so:libetpan.so.20=20.1.0" + ] + }, + "mupdf": { + "versions": { + "1.13.0-r0": 1533746007 + }, + "origin": "mupdf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libharfbuzz.so.0", + "so:libjbig2dec.so.0", + "so:libjpeg.so.8", + "so:libopenjp2.so.7", + "so:libz.so.1" + ], + "provides": [ + "so:libmupdf.so.0=0", + "so:libmupdfthird.so.0=0" + ] + }, + "ltrace-doc": { + "versions": { + "0.7.3-r1": 1509477339 + }, + "origin": "ltrace" + }, + "mpfr3": { + "versions": { + "3.1.5-r1": 1509457050 + }, + "origin": "mpfr3", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10" + ], + "provides": [ + "so:libmpfr.so.4=4.1.5" + ] + }, + "uwsgi-transformation_template": { + "versions": { + "2.0.17-r0": 1522154660 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "psqlodbc": { + "versions": { + "09.06.0410-r0": 1509494071 + }, + "origin": "psqlodbc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libodbcinst.so.2", + "so:libpq.so.5" + ], + "provides": [ + "so:psqlodbca.so=0", + "so:psqlodbcw.so=0" + ] + }, + "isl": { + "versions": { + "0.18-r0": 1509456967 + }, + "origin": "isl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10" + ], + "provides": [ + "so:libisl.so.15=15.3.0" + ] + }, + "dovecot-pigeonhole-plugin-extdata": { + "versions": { + "2.2.36.3-r0": 1554102387, + "2.2.36.4-r0": 1567075098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot-pigeonhole-plugin", + "so:libc.musl-x86_64.so.1" + ] + }, + "attr": { + "versions": { + "2.4.47-r6": 1509457002 + }, + "origin": "attr", + "dependencies": [ + "so:libattr.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:attr", + "cmd:getfattr", + "cmd:setfattr" + ] + }, + "libdnet-doc": { + "versions": { + "1.12-r7": 1509490804 + }, + "origin": "libdnet" + }, + "tcpdump": { + "versions": { + "4.9.2-r1": 1510260551 + }, + "origin": "tcpdump", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:tcpdump" + ] + }, + "ngircd-doc": { + "versions": { + "24-r2": 1510259315 + }, + "origin": "ngircd" + }, + "libogg-dev": { + "versions": { + "1.3.3-r1": 1511046403 + }, + "origin": "libogg", + "dependencies": [ + "libogg=1.3.3-r1", + "pkgconfig" + ], + "provides": [ + "pc:ogg=1.3.3" + ] + }, + "tmux-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503664 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "libxinerama-doc": { + "versions": { + "1.1.3-r1": 1509468244 + }, + "origin": "libxinerama" + }, + "python2-dbg": { + "versions": { + "2.7.15-r2": 1534944322 + }, + "origin": "python2" + }, + "fetchmail": { + "versions": { + "6.3.26-r12": 1510261418 + }, + "origin": "fetchmail", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "cmd:fetchmail" + ] + }, + "lua5.3-evdev": { + "versions": { + "2.2.1-r1": 1509488870 + }, + "origin": "lua-evdev", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "ppp-passprompt": { + "versions": { + "2.4.7-r5": 1509480136 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "kamailio-xml": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libsrdb1.so.1", + "so:libxml2.so.2" + ] + }, + "perl-role-tiny": { + "versions": { + "2.000005-r0": 1509481549 + }, + "origin": "perl-role-tiny" + }, + "perl-html-rewriteattributes": { + "versions": { + "0.05-r1": 1510564566 + }, + "origin": "perl-html-rewriteattributes", + "dependencies": [ + "perl-html-tagset", + "perl-uri", + "perl-html-parser" + ] + }, + "termrec": { + "versions": { + "0.17-r1": 1509491605 + }, + "origin": "termrec", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:liblzma.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libtty.so.0=0.0.0", + "cmd:proxyrec", + "cmd:termcat", + "cmd:termplay", + "cmd:termrec", + "cmd:termtime" + ] + }, + "py-hoedown": { + "versions": { + "0.2.3-r1": 1509552821 + }, + "origin": "py-hoedown", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:hoedownpy" + ] + }, + "lz4-dev": { + "versions": { + "1.8.0-r1": 1509461403 + }, + "origin": "lz4", + "dependencies": [ + "lz4-libs=1.8.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:liblz4=1.8.0" + ] + }, + "libgsf-dev": { + "versions": { + "1.14.41-r0": 1509467200 + }, + "origin": "libgsf", + "dependencies": [ + "bzip2-dev", + "libgsf=1.14.41-r0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libgsf-1=1.14.41" + ] + }, + "taglib-dev": { + "versions": { + "1.11.1-r0": 1509489612 + }, + "origin": "taglib", + "dependencies": [ + "pkgconfig", + "taglib=1.11.1-r0" + ], + "provides": [ + "pc:taglib=1.11.1", + "pc:taglib_c=1.11.1", + "cmd:taglib-config" + ] + }, + "xf86-video-vesa": { + "versions": { + "2.3.4-r2": 1510074819 + }, + "origin": "xf86-video-vesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gigolo-lang": { + "versions": { + "0.4.2-r0": 1510076413 + }, + "origin": "gigolo" + }, + "mariadb": { + "versions": { + "10.1.38-r1": 1551187996, + "10.1.40-r0": 1560354892, + "10.1.41-r0": 1565163116 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common", + "/bin/sh", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libncursesw.so.6", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:aria_chk", + "cmd:aria_dump_log", + "cmd:aria_ftdump", + "cmd:aria_pack", + "cmd:aria_read_log", + "cmd:innochecksum", + "cmd:mariabackup", + "cmd:mbstream", + "cmd:msql2mysql", + "cmd:my_print_defaults", + "cmd:myisamchk", + "cmd:myisamlog", + "cmd:myisampack", + "cmd:mysql_client_test_embedded", + "cmd:mysql_convert_table_format", + "cmd:mysql_embedded", + "cmd:mysql_install_db", + "cmd:mysql_plugin", + "cmd:mysql_secure_installation", + "cmd:mysql_setpermission", + "cmd:mysql_tzinfo_to_sql", + "cmd:mysql_upgrade", + "cmd:mysql_zap", + "cmd:mysqlbinlog", + "cmd:mysqld", + "cmd:mysqld_multi", + "cmd:mysqld_safe", + "cmd:mysqld_safe_helper", + "cmd:mysqlhotcopy", + "cmd:mysqlslap", + "cmd:mysqltest", + "cmd:mysqltest_embedded", + "cmd:mytop", + "cmd:perror", + "cmd:replace", + "cmd:resolve_stack_dump", + "cmd:resolveip", + "cmd:wsrep_sst_mariabackup", + "cmd:wsrep_sst_mysqldump", + "cmd:wsrep_sst_rsync", + "cmd:wsrep_sst_rsync_wan", + "cmd:wsrep_sst_xtrabackup", + "cmd:wsrep_sst_xtrabackup-v2" + ] + }, + "postgresql-plperl-contrib": { + "versions": { + "10.7-r0": 1554274199, + "10.8-r0": 1557668015, + "10.9-r0": 1562225614, + "10.10-r0": 1565683439 + }, + "origin": "postgresql", + "dependencies": [ + "postgresql-plperl", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "git-p4": { + "versions": { + "2.15.3-r0": 1540401293, + "2.15.4-r0": 1576015190 + }, + "origin": "git", + "dependencies": [ + "git=2.15.4-r0", + "git-fast-import=2.15.4-r0" + ] + }, + "perl-sub-identify-doc": { + "versions": { + "0.14-r1": 1509474067 + }, + "origin": "perl-sub-identify" + }, + "py-rrd": { + "versions": { + "1.5.6-r3": 1512296349 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:librrd.so.4" + ] + }, + "perl-sub-exporter-doc": { + "versions": { + "0.987-r0": 1509473981 + }, + "origin": "perl-sub-exporter" + }, + "razor": { + "versions": { + "2.85-r6": 1509491111 + }, + "origin": "razor", + "dependencies": [ + "perl", + "perl-digest-sha1", + "perl-getopt-long", + "perl-uri", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:razor-admin", + "cmd:razor-check", + "cmd:razor-client", + "cmd:razor-report", + "cmd:razor-revoke" + ] + }, + "libfetch-doc": { + "versions": { + "2.33-r2": 1509490729 + }, + "origin": "libfetch" + }, + "guile-libs": { + "versions": { + "2.0.14-r0": 1509468174 + }, + "origin": "guile", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libgc.so.1", + "so:libgmp.so.10", + "so:libltdl.so.7", + "so:libunistring.so.2" + ], + "provides": [ + "so:libguile-2.0.so.22=22.8.1" + ] + }, + "p11-kit-doc": { + "versions": { + "0.23.2-r2": 1509465178 + }, + "origin": "p11-kit" + }, + "flex-doc": { + "versions": { + "2.6.4-r1": 1509456703 + }, + "origin": "flex" + }, + "libee": { + "versions": { + "0.4.1-r0": 1509486165 + }, + "origin": "libee", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libestr.so.0" + ], + "provides": [ + "so:libee.so.0=0.0.0", + "cmd:libee-convert" + ] + }, + "zmap-doc": { + "versions": { + "2.1.1-r1": 1510310598 + }, + "origin": "zmap" + }, + "qt-postgresql": { + "versions": { + "4.8.7-r8": 1510287209 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtSql.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpq.so.5", + "so:libstdc++.so.6" + ] + }, + "diffutils": { + "versions": { + "3.6-r0": 1509459511 + }, + "origin": "diffutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cmp", + "cmd:diff", + "cmd:diff3", + "cmd:sdiff" + ] + }, + "ruby-test-unit": { + "versions": { + "2.4.6-r0": 1557166823 + }, + "origin": "ruby", + "dependencies": [ + "ruby", + "ruby-power_assert" + ] + }, + "imlib2-dev": { + "versions": { + "1.4.10-r0": 1509470514 + }, + "origin": "imlib2", + "dependencies": [ + "freetype-dev", + "libxext-dev", + "libsm-dev", + "imlib2=1.4.10-r0", + "pkgconfig" + ], + "provides": [ + "pc:imlib2=1.4.10", + "cmd:imlib2-config" + ] + }, + "json-glib-dev": { + "versions": { + "1.2.8-r0": 1509480681 + }, + "origin": "json-glib", + "dependencies": [ + "json-glib=1.2.8-r0", + "pc:gio-2.0", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjson-glib-1.0.so.0" + ], + "provides": [ + "pc:json-glib-1.0=1.2.8", + "cmd:json-glib-format", + "cmd:json-glib-validate" + ] + }, + "xorg-cf-files": { + "versions": { + "1.0.6-r0": 1509488821 + }, + "origin": "xorg-cf-files" + }, + "darkstat-doc": { + "versions": { + "3.0.719-r0": 1509486285 + }, + "origin": "darkstat" + }, + "acf-dnsmasq": { + "versions": { + "0.7.1-r0": 1510072792 + }, + "origin": "acf-dnsmasq", + "dependencies": [ + "acf-core", + "dnsmasq" + ] + }, + "perl-cpanel-json-xs-doc": { + "versions": { + "3.0239-r0": 1510352915 + }, + "origin": "perl-cpanel-json-xs" + }, + "farstream-dev": { + "versions": { + "0.2.8-r2": 1510931394 + }, + "origin": "farstream", + "dependencies": [ + "libnice-dev", + "gstreamer-dev", + "gst-plugins-base-dev", + "farstream=0.2.8-r2", + "pc:gstreamer-1.0", + "pc:gstreamer-base-1.0", + "pkgconfig" + ], + "provides": [ + "pc:farstream-0.2=0.2.8" + ] + }, + "perl-text-wrapper-doc": { + "versions": { + "1.04-r0": 1509490810 + }, + "origin": "perl-text-wrapper" + }, + "nagios-plugins-ping": { + "versions": { + "2.2.1-r3": 1510288497 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua-pc": { + "versions": { + "1.0.0-r9": 1509480094 + }, + "origin": "lua-pc", + "dependencies": [ + "lua5.1-pc" + ] + }, + "perl-extutils-config-doc": { + "versions": { + "0.008-r0": 1509474734 + }, + "origin": "perl-extutils-config" + }, + "libasr": { + "versions": { + "1.0.2-r6": 1510258974 + }, + "origin": "libasr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ], + "provides": [ + "so:libasr.so.0=0.0.2" + ] + }, + "lutok": { + "versions": { + "0.4-r1": 1509459745 + }, + "origin": "lutok", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:liblua-5.2.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:liblutok.so.3=3.0.0" + ] + }, + "rtmpdump-doc": { + "versions": { + "2.4_git20160909-r3": 1510259117 + }, + "origin": "rtmpdump" + }, + "ncftp": { + "versions": { + "3.2.6-r1": 1509472913 + }, + "origin": "ncftp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libncftp.so.3=3", + "cmd:ncftp", + "cmd:ncftpbatch", + "cmd:ncftpget", + "cmd:ncftpls", + "cmd:ncftpput", + "cmd:ncftpspooler" + ] + }, + "mosquitto-dev": { + "versions": { + "1.4.15-r0": 1520176487, + "1.4.15-r2": 1563346323 + }, + "origin": "mosquitto", + "dependencies": [ + "mosquitto-libs++=1.4.15-r2", + "mosquitto-libs=1.4.15-r2" + ] + }, + "abiword-plugin-epub": { + "versions": { + "3.0.2-r1": 1510073359 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libgtk-3.so.0", + "so:libstdc++.so.6" + ] + }, + "subunit": { + "versions": { + "1.2.0-r0": 1509481265 + }, + "origin": "subunit", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:subunit-1to2", + "cmd:subunit-2to1", + "cmd:subunit-diff", + "cmd:subunit-filter", + "cmd:subunit-ls", + "cmd:subunit-notify", + "cmd:subunit-output", + "cmd:subunit-stats", + "cmd:subunit-tags", + "cmd:subunit2csv", + "cmd:subunit2disk", + "cmd:subunit2gtk", + "cmd:subunit2junitxml", + "cmd:subunit2pyunit", + "cmd:tap2subunit" + ] + }, + "apr-util-ldap": { + "versions": { + "1.6.1-r1": 1510285059 + }, + "origin": "apr-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libldap-2.4.so.2" + ] + }, + "conntrack-tools-doc": { + "versions": { + "1.4.4-r0": 1509469776 + }, + "origin": "conntrack-tools" + }, + "gettext": { + "versions": { + "0.19.8.1-r1": 1509459197 + }, + "origin": "gettext", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgomp.so.1", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libunistring.so.2", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgettextlib-0.19.8.1.so=0", + "so:libgettextsrc-0.19.8.1.so=0", + "cmd:autopoint", + "cmd:envsubst", + "cmd:gettext", + "cmd:gettext.sh", + "cmd:gettextize", + "cmd:msgattrib", + "cmd:msgcat", + "cmd:msgcmp", + "cmd:msgcomm", + "cmd:msgconv", + "cmd:msgen", + "cmd:msgexec", + "cmd:msgfilter", + "cmd:msgfmt", + "cmd:msggrep", + "cmd:msginit", + "cmd:msgmerge", + "cmd:msgunfmt", + "cmd:msguniq", + "cmd:ngettext", + "cmd:recode-sr-latin", + "cmd:xgettext" + ] + }, + "bsd-compat-headers": { + "versions": { + "0.7.1-r0": 1509458195 + }, + "origin": "libc-dev" + }, + "lua5.3-penlight": { + "versions": { + "1.5.4-r0": 1509485591 + }, + "origin": "lua-penlight", + "dependencies": [ + "lua-penlight-shared", + "lua5.3-filesystem", + "lua-penlight-shared=1.5.4-r0" + ] + }, + "lua5.3-microlight": { + "versions": { + "1.1.1-r2": 1509480787 + }, + "origin": "lua-microlight" + }, + "p11-kit": { + "versions": { + "0.23.2-r2": 1509465179 + }, + "origin": "p11-kit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libtasn1.so.6" + ], + "provides": [ + "so:libp11-kit.so.0=0.1.0", + "cmd:p11-kit", + "cmd:trust" + ] + }, + "ntfs-3g-progs": { + "versions": { + "2017.3.23-r1": 1509489438 + }, + "origin": "ntfs-3g", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libntfs-3g.so.88", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:mkntfs", + "cmd:ntfs-3g.probe", + "cmd:ntfscat", + "cmd:ntfsclone", + "cmd:ntfscluster", + "cmd:ntfscmp", + "cmd:ntfscp", + "cmd:ntfsfix", + "cmd:ntfsinfo", + "cmd:ntfslabel", + "cmd:ntfsls", + "cmd:ntfsresize", + "cmd:ntfsundelete" + ] + }, + "giflib-dev": { + "versions": { + "5.1.4-r1": 1509470478 + }, + "origin": "giflib", + "dependencies": [ + "giflib=5.1.4-r1" + ] + }, + "cyrus-sasl-digestmd5": { + "versions": { + "2.1.26-r11": 1510258045 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "unbound-dev": { + "versions": { + "1.6.7-r1": 1510588257 + }, + "origin": "unbound", + "dependencies": [ + "libressl-dev", + "expat-dev", + "ldns-dev", + "libevent-dev", + "unbound-libs=1.6.7-r1" + ] + }, + "fprobe-doc": { + "versions": { + "1.1-r7": 1509481825 + }, + "origin": "fprobe" + }, + "c-client": { + "versions": { + "2007f-r7": 1510258915 + }, + "origin": "imap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "so:libc-client.so.1=1.0.0" + ] + }, + "haserl": { + "versions": { + "0.9.35-r1": 1509468300 + }, + "origin": "haserl", + "dependencies": [ + "haserl-lua5.3", + "haserl-lua5.2", + "haserl-lua5.1", + "haserl-lua5.3=0.9.35-r1" + ] + }, + "lua5.1-rex-posix": { + "versions": { + "2.9.0-r0": 1510619366 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "ppp-dev": { + "versions": { + "2.4.7-r5": 1509480124 + }, + "origin": "ppp" + }, + "jack": { + "versions": { + "1.9.10-r5": 1509473320 + }, + "origin": "jack", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libgcc_s.so.1", + "so:libsamplerate.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libjack.so.0=0.1.0", + "so:libjacknet.so.0=0.1.0", + "so:libjackserver.so.0=0.1.0", + "cmd:jackd" + ] + }, + "abiword-plugin-wml": { + "versions": { + "3.0.2-r1": 1510073370 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "qemu-mips64": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mips64" + ] + }, + "perl-compress-raw-bzip2": { + "versions": { + "2.074-r1": 1509483998 + }, + "origin": "perl-compress-raw-bzip2", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "e2fsprogs-libs": { + "versions": { + "1.43.7-r0": 1509469274, + "1.43.7-r1": 1571322244 + }, + "origin": "e2fsprogs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2" + ], + "provides": [ + "so:libe2p.so.2=2.3", + "so:libext2fs.so.2=2.4", + "so:libss.so.2=2.0" + ] + }, + "pwclient": { + "versions": { + "1.1.3-r0": 1509474240, + "1.1.3-r1": 1562223418 + }, + "origin": "patchwork", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:pwclient" + ] + }, + "iceauth-doc": { + "versions": { + "1.0.7-r0": 1509482661 + }, + "origin": "iceauth" + }, + "glm-dev": { + "versions": { + "0.9.8.5-r0": 1509494918 + }, + "origin": "glm", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:glm=0.9.8" + ] + }, + "libepoxy-dev": { + "versions": { + "1.4.3-r1": 1510067311 + }, + "origin": "libepoxy", + "dependencies": [ + "libx11-dev", + "mesa-dev", + "libepoxy=1.4.3-r1", + "pkgconfig" + ], + "provides": [ + "pc:epoxy=1.4.3" + ] + }, + "ipsec-tools-doc": { + "versions": { + "0.8.2-r6": 1510261509 + }, + "origin": "ipsec-tools" + }, + "xf86-video-siliconmotion": { + "versions": { + "1.7.9-r0": 1510074838 + }, + "origin": "xf86-video-siliconmotion", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py-requests": { + "versions": { + "2.18.4-r0": 1509483043 + }, + "origin": "py-requests", + "dependencies": [ + "py-chardet", + "py-idna", + "py-certifi", + "py-urllib3" + ] + }, + "perl-mail-clamav-doc": { + "versions": { + "0.29-r12": 1509489511 + }, + "origin": "perl-mail-clamav" + }, + "glamor-egl": { + "versions": { + "0.6.0-r3": 1510068308 + }, + "origin": "glamor-egl", + "dependencies": [ + "so:libEGL.so.1", + "so:libGL.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgbm.so.1" + ], + "provides": [ + "so:libglamor.so.0=0.0.0" + ] + }, + "lua-expat": { + "versions": { + "1.3.0-r2": 1509483373 + }, + "origin": "lua-expat" + }, + "s6-linux-init-doc": { + "versions": { + "0.3.1.0-r0": 1509492622 + }, + "origin": "s6-linux-init" + }, + "libdnet": { + "versions": { + "1.12-r7": 1509490804 + }, + "origin": "libdnet", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdnet.so.1=1.0.1", + "cmd:dnet" + ] + }, + "pssh-doc": { + "versions": { + "2.3.1-r1": 1509494676 + }, + "origin": "pssh" + }, + "py-genshi": { + "versions": { + "0.7-r0": 1509491435 + }, + "origin": "py-genshi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "libidn": { + "versions": { + "1.33-r1": 1509468968 + }, + "origin": "libidn", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libidn.so.11=11.6.16", + "cmd:idn" + ] + }, + "tiff-tools": { + "versions": { + "4.0.10-r0": 1544169734, + "4.0.10-r1": 1566982293, + "4.0.10-r2": 1572815888 + }, + "origin": "tiff", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtiff.so.5" + ], + "provides": [ + "cmd:fax2ps", + "cmd:fax2tiff", + "cmd:pal2rgb", + "cmd:ppm2tiff", + "cmd:raw2tiff", + "cmd:tiff2bw", + "cmd:tiff2pdf", + "cmd:tiff2ps", + "cmd:tiff2rgba", + "cmd:tiffcmp", + "cmd:tiffcp", + "cmd:tiffcrop", + "cmd:tiffdither", + "cmd:tiffdump", + "cmd:tiffinfo", + "cmd:tiffmedian", + "cmd:tiffset", + "cmd:tiffsplit" + ] + }, + "talloc-doc": { + "versions": { + "2.1.10-r0": 1509466375 + }, + "origin": "talloc" + }, + "lxterminal-doc": { + "versions": { + "0.3.0-r2": 1510071920 + }, + "origin": "lxterminal" + }, + "py2-libvirt": { + "versions": { + "3.9.0-r0": 1510088408 + }, + "origin": "py-libvirt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libvirt-lxc.so.0", + "so:libvirt-qemu.so.0", + "so:libvirt.so.0" + ] + }, + "gst-plugins-ugly0.10-lang": { + "versions": { + "0.10.19-r2": 1510072192 + }, + "origin": "gst-plugins-ugly0.10" + }, + "py3-pynacl": { + "versions": { + "1.2.0-r0": 1510088478 + }, + "origin": "py-pynacl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "py-jinja2-vim": { + "versions": { + "2.9.6-r0": 1509476515 + }, + "origin": "py-jinja2", + "dependencies": [ + "vim" + ] + }, + "liblockfile": { + "versions": { + "1.14-r0": 1511990622 + }, + "origin": "liblockfile", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dotlockfile" + ] + }, + "abiword-plugin-applix": { + "versions": { + "3.0.2-r1": 1510073356 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "ca-certificates": { + "versions": { + "20171114-r0": 1510690408, + "20190108-r0": 1558960537 + }, + "origin": "ca-certificates", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ], + "provides": [ + "cmd:c_rehash", + "cmd:update-ca-certificates" + ] + }, + "libblkid": { + "versions": { + "2.31.1-r0": 1541506294 + }, + "origin": "util-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libblkid.so.1=1.1.0" + ] + }, + "py2-asn1": { + "versions": { + "0.4.2-r0": 1511889292 + }, + "origin": "py-asn1", + "dependencies": [ + "python2" + ] + }, + "py2-backports.ssl_match_hostname": { + "versions": { + "3.5.0.1-r1": 1509552713 + }, + "origin": "py2-backports.ssl_match_hostname", + "dependencies": [ + "python2" + ], + "provides": [ + "py-backports.ssl_match_hostname" + ] + }, + "swatch": { + "versions": { + "3.2.4-r4": 1510564578 + }, + "origin": "swatch", + "dependencies": [ + "perl", + "perl-date-calc", + "perl-date-format", + "perl-date-manip", + "perl-file-tail", + "perl-carp-clan" + ], + "provides": [ + "cmd:swatchdog" + ] + }, + "lua5.1-alt-getopt": { + "versions": { + "0.7-r8": 1509480073 + }, + "origin": "lua-alt-getopt" + }, + "unionfs-fuse": { + "versions": { + "1.0-r0": 1509489291 + }, + "origin": "unionfs-fuse", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfuse.so.2" + ], + "provides": [ + "cmd:mount.unionfs", + "cmd:unionfs", + "cmd:unionfsctl" + ] + }, + "lua-sql": { + "versions": { + "2.3.5-r1": 1509488835 + }, + "origin": "lua-sql" + }, + "gcc-objc": { + "versions": { + "6.4.0-r5": 1509458075 + }, + "origin": "gcc", + "dependencies": [ + "libc-dev", + "gcc=6.4.0-r5", + "libobjc=6.4.0-r5", + "libobjc=6.4.0-r5" + ] + }, + "py2-country": { + "versions": { + "17.9.23-r0": 1509493917 + }, + "origin": "py-country", + "dependencies": [ + "python2" + ] + }, + "perl-io-async-doc": { + "versions": { + "0.69-r0": 1509480539 + }, + "origin": "perl-io-async" + }, + "libevdev-dev": { + "versions": { + "1.5.7-r1": 1509475753 + }, + "origin": "libevdev", + "dependencies": [ + "libevdev=1.5.7-r1", + "pkgconfig" + ], + "provides": [ + "pc:libevdev=1.5.7" + ] + }, + "gpgme-dev": { + "versions": { + "1.9.0-r1": 1510588162 + }, + "origin": "gpgme", + "dependencies": [ + "libgpg-error-dev", + "libassuan-dev", + "gpgme=1.9.0-r1", + "gpgmepp=1.9.0-r1" + ], + "provides": [ + "cmd:gpgme-config" + ] + }, + "py-gamin": { + "versions": { + "0.1.10-r10": 1509481666 + }, + "origin": "gamin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgamin-1.so.0" + ] + }, + "sdl-doc": { + "versions": { + "1.2.15-r7": 1510067900, + "1.2.15-r8": 1565787130, + "1.2.15-r9": 1574265376 + }, + "origin": "sdl" + }, + "libnotify-dev": { + "versions": { + "0.7.7-r0": 1510068016 + }, + "origin": "libnotify", + "dependencies": [ + "gdk-pixbuf-dev", + "glib-dev", + "dbus-dev", + "libnotify=0.7.7-r0", + "pc:gdk-pixbuf-2.0", + "pc:gio-2.0>=2.26.0", + "pc:glib-2.0>=2.26.0", + "pkgconfig" + ], + "provides": [ + "pc:libnotify=0.7.7" + ] + }, + "kamailio": { + "versions": { + "5.0.7-r0": 1532960877 + }, + "origin": "kamailio", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libkamailio_ims.so.0=0.1", + "so:libprint.so.1=1.2", + "so:libsrdb1.so.1=1.0", + "so:libsrdb2.so.1=1.0", + "so:libsrutils.so.1=1.0", + "so:libtrie.so.1=1.0", + "cmd:kamailio", + "cmd:kamcmd", + "cmd:kamctl", + "cmd:kamdbctl" + ] + }, + "libgcab": { + "versions": { + "0.7-r1": 1510067823 + }, + "origin": "libgcab", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libz.so.1" + ], + "provides": [ + "so:libgcab-1.0.so.0=0.0.0", + "cmd:gcab" + ] + }, + "findmnt": { + "versions": { + "2.31.1-r0": 1541506294 + }, + "origin": "util-linux", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libmount.so.1", + "so:libsmartcols.so.1" + ], + "provides": [ + "cmd:findmnt" + ] + }, + "squid-lang-uk": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865670 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "dropbear": { + "versions": { + "2018.76-r2": 1537439117 + }, + "origin": "dropbear", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:dropbear", + "cmd:dropbearkey" + ] + }, + "nagios-plugins-smtp": { + "versions": { + "2.2.1-r3": 1510288498 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "linenoise": { + "versions": { + "1.0-r1": 1509462461 + }, + "origin": "linenoise", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblinenoise.so.0=0.0.0" + ] + }, + "icecast-doc": { + "versions": { + "2.4.4-r0": 1541491284 + }, + "origin": "icecast" + }, + "py3-munkres": { + "versions": { + "1.0.12-r0": 1509468389 + }, + "origin": "py-munkres", + "dependencies": [ + "python3" + ] + }, + "collectd-perl": { + "versions": { + "5.7.2-r0": 1510069643 + }, + "origin": "collectd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "mesa-dri-freedreno": { + "versions": { + "17.2.4-r1": 1510741946 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "perl-io-captureoutput-doc": { + "versions": { + "1.1104-r0": 1509489308 + }, + "origin": "perl-io-captureoutput" + }, + "tig": { + "versions": { + "2.3.0-r0": 1511889467 + }, + "origin": "tig", + "dependencies": [ + "git", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:tig" + ] + }, + "libgsf": { + "versions": { + "1.14.41-r0": 1509467200 + }, + "origin": "libgsf", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libgsf-1.so.114=114.0.41", + "cmd:gsf", + "cmd:gsf-vba-dump" + ] + }, + "krb5-pkinit": { + "versions": { + "1.15.4-r0": 1548491443 + }, + "origin": "krb5", + "dependencies": [ + "krb5-conf", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libcrypto.so.42", + "so:libk5crypto.so.3", + "so:libkrb5.so.3", + "so:libkrb5support.so.0" + ] + }, + "perl-datetime-format-natural-doc": { + "versions": { + "1.05-r1": 1511384618 + }, + "origin": "perl-datetime-format-natural" + }, + "libxrandr": { + "versions": { + "1.5.1-r1": 1509466001 + }, + "origin": "libxrandr", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXrandr.so.2=2.2.0" + ] + }, + "net-snmp-gui": { + "versions": { + "5.7.3-r10": 1510259617 + }, + "origin": "net-snmp", + "dependencies": [ + "perl-net-snmp" + ], + "provides": [ + "cmd:tkmib" + ] + }, + "nsd-dbg": { + "versions": { + "4.1.16-r1": 1510260999 + }, + "origin": "nsd" + }, + "perl-apache-session-doc": { + "versions": { + "1.93-r0": 1509491309 + }, + "origin": "perl-apache-session" + }, + "confuse-doc": { + "versions": { + "3.2.1-r0": 1509471872 + }, + "origin": "confuse" + }, + "perl-mailtools": { + "versions": { + "2.19-r0": 1511989745 + }, + "origin": "perl-mailtools", + "dependencies": [ + "perl-timedate" + ], + "provides": [ + "perl-mail-tools=2.19" + ] + }, + "gtk+2.0-dev": { + "versions": { + "2.24.31-r0": 1510066780 + }, + "origin": "gtk+2.0", + "dependencies": [ + "atk-dev", + "cairo-dev", + "intltool", + "libxdamage-dev", + "pango-dev", + "gtk+2.0=2.24.31-r0", + "pc:atk", + "pc:cairo", + "pc:gdk-pixbuf-2.0", + "pc:gio-2.0", + "pc:pango", + "pc:pangocairo", + "pc:pangoft2", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0" + ], + "provides": [ + "pc:gail=2.24.31", + "pc:gdk-2.0=2.24.31", + "pc:gdk-x11-2.0=2.24.31", + "pc:gtk+-2.0=2.24.31", + "pc:gtk+-unix-print-2.0=2.24.31", + "pc:gtk+-x11-2.0=2.24.31", + "cmd:gtk-builder-convert", + "cmd:gtk-demo" + ] + }, + "py-cliapp-doc": { + "versions": { + "1.20150829-r1": 1509552707 + }, + "origin": "py-cliapp" + }, + "perl-timedate-doc": { + "versions": { + "2.30-r1": 1509469036 + }, + "origin": "perl-timedate" + }, + "ntfs-3g-doc": { + "versions": { + "2017.3.23-r1": 1509489437 + }, + "origin": "ntfs-3g" + }, + "libogg-doc": { + "versions": { + "1.3.3-r1": 1511046403 + }, + "origin": "libogg" + }, + "cmocka-dev": { + "versions": { + "1.1.1-r1": 1509476797 + }, + "origin": "cmocka", + "dependencies": [ + "cmocka=1.1.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:cmocka=1.1.1" + ] + }, + "perl-canary-stability-doc": { + "versions": { + "2012-r0": 1509474458 + }, + "origin": "perl-canary-stability" + }, + "acf-samba": { + "versions": { + "0.10.0-r2": 1510069863 + }, + "origin": "acf-samba", + "dependencies": [ + "acf-core", + "samba" + ] + }, + "docbook2x-doc": { + "versions": { + "0.8.8-r6": 1509489152 + }, + "origin": "docbook2x" + }, + "boost-math": { + "versions": { + "1.62.0-r5": 1509465876 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_math_c99-mt.so.1.62.0=1.62.0", + "so:libboost_math_c99.so.1.62.0=1.62.0", + "so:libboost_math_c99f-mt.so.1.62.0=1.62.0", + "so:libboost_math_c99f.so.1.62.0=1.62.0", + "so:libboost_math_c99l-mt.so.1.62.0=1.62.0", + "so:libboost_math_c99l.so.1.62.0=1.62.0", + "so:libboost_math_tr1-mt.so.1.62.0=1.62.0", + "so:libboost_math_tr1.so.1.62.0=1.62.0", + "so:libboost_math_tr1f-mt.so.1.62.0=1.62.0", + "so:libboost_math_tr1f.so.1.62.0=1.62.0", + "so:libboost_math_tr1l-mt.so.1.62.0=1.62.0", + "so:libboost_math_tr1l.so.1.62.0=1.62.0" + ] + }, + "py3-bluez": { + "versions": { + "0.22-r1": 1510075577 + }, + "origin": "py-bluez", + "dependencies": [ + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "qpdf-dev": { + "versions": { + "7.0.0-r0": 1509482354 + }, + "origin": "qpdf", + "dependencies": [ + "pc:libjpeg", + "pc:zlib", + "pkgconfig", + "qpdf-libs=7.0.0-r0" + ], + "provides": [ + "pc:libqpdf=7.0.0" + ] + }, + "acf-snort": { + "versions": { + "0.8.0-r2": 1510848488 + }, + "origin": "acf-snort", + "dependencies": [ + "acf-core", + "snort" + ] + }, + "perl-class-load-doc": { + "versions": { + "0.23-r0": 1509494276 + }, + "origin": "perl-class-load" + }, + "openipmi": { + "versions": { + "2.0.22-r1": 1510260044 + }, + "origin": "openipmi", + "dependencies": [ + "so:libOpenIPMI.so.0", + "so:libOpenIPMIcmdlang.so.0", + "so:libOpenIPMIglib.so.0", + "so:libOpenIPMIposix.so.0", + "so:libOpenIPMIui.so.1", + "so:libOpenIPMIutils.so.0", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libncursesw.so.6", + "so:libnetsnmp.so.30" + ], + "provides": [ + "cmd:ipmi_ui", + "cmd:ipmicmd", + "cmd:ipmish", + "cmd:openipmi_eventd", + "cmd:openipmicmd", + "cmd:openipmish", + "cmd:rmcp_ping", + "cmd:solterm" + ] + }, + "lua5.3-lzlib": { + "versions": { + "0.4.3-r0": 1509494170 + }, + "origin": "lua-lzlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ] + }, + "py2-werkzeug": { + "versions": { + "0.11.15-r2": 1509551863 + }, + "origin": "py-werkzeug", + "dependencies": [ + "python2" + ] + }, + "ksymoops-doc": { + "versions": { + "2.4.11-r7": 1509489729 + }, + "origin": "ksymoops" + }, + "perl-http-message": { + "versions": { + "6.13-r0": 1511871374 + }, + "origin": "perl-http-message", + "dependencies": [ + "perl-lwp-mediatypes", + "perl-encode-locale", + "perl-http-date", + "perl-uri", + "perl-io-html" + ] + }, + "lua-iconv": { + "versions": { + "7-r1": 1509493324 + }, + "origin": "lua-iconv" + }, + "kamailio-ev": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libev.so.4" + ] + }, + "fastjar-doc": { + "versions": { + "0.98-r2": 1509490598 + }, + "origin": "fastjar" + }, + "perl-encode-hanextra-doc": { + "versions": { + "0.23-r2": 1509489060 + }, + "origin": "perl-encode-hanextra" + }, + "gobject-introspection": { + "versions": { + "1.54.1-r0": 1509464659 + }, + "origin": "gobject-introspection", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libgirepository-1.0.so.1=1.0.0" + ] + }, + "libnfs-dev": { + "versions": { + "2.0.0-r0": 1509493088 + }, + "origin": "libnfs", + "dependencies": [ + "libnfs=2.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnfs=2.0.0" + ] + }, + "grep-doc": { + "versions": { + "3.1-r0": 1509468224 + }, + "origin": "grep" + }, + "lua5.2-struct": { + "versions": { + "0.2-r2": 1509494933 + }, + "origin": "lua-struct", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "expect": { + "versions": { + "5.45-r4": 1509489557 + }, + "origin": "expect", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtcl8.6.so" + ], + "provides": [ + "so:libexpect5.45.so=0", + "cmd:autoexpect", + "cmd:autopasswd", + "cmd:cryptdir", + "cmd:decryptdir", + "cmd:dislocate", + "cmd:expect", + "cmd:ftp-rfc", + "cmd:kibitz", + "cmd:lpunlock", + "cmd:mkpasswd", + "cmd:multixterm", + "cmd:passmass", + "cmd:rftp", + "cmd:rlogin-cwd", + "cmd:timed-read", + "cmd:timed-run", + "cmd:tknewsbiff", + "cmd:tkpasswd", + "cmd:unbuffer", + "cmd:weather", + "cmd:xkibitz", + "cmd:xpstat" + ] + }, + "apk-cron": { + "versions": { + "1.0-r0": 1509494401 + }, + "origin": "apk-cron", + "dependencies": [ + "apk-tools" + ] + }, + "acpi-doc": { + "versions": { + "1.7-r2": 1509787633 + }, + "origin": "acpi" + }, + "ruby-json": { + "versions": { + "2.4.6-r0": 1557166822 + }, + "origin": "ruby", + "dependencies": [ + "ruby", + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.4" + ] + }, + "cunit": { + "versions": { + "2.1.3-r1": 1509466104 + }, + "origin": "cunit", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcunit.so.1=1.0.1" + ] + }, + "py3-lxc": { + "versions": { + "2.1.1-r3": 1533578833 + }, + "origin": "lxc", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:liblxc.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "bind": { + "versions": { + "9.11.6_p1-r1": 1556872768, + "9.11.8-r0": 1561323229 + }, + "origin": "bind", + "dependencies": [ + "/bin/sh", + "so:libbind9.so.161", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.42", + "so:libdns.so.1106", + "so:libisc.so.1100", + "so:libisccc.so.161", + "so:libisccfg.so.163", + "so:liblwres.so.161", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:arpaname", + "cmd:ddns-confgen", + "cmd:genrandom", + "cmd:isc-config.sh", + "cmd:isc-hmac-fixup", + "cmd:lwresd", + "cmd:mdig", + "cmd:named", + "cmd:named-checkconf", + "cmd:named-checkzone", + "cmd:named-compilezone", + "cmd:named-journalprint", + "cmd:named-rrchecker", + "cmd:nsec3hash", + "cmd:rndc", + "cmd:rndc-confgen", + "cmd:tsig-keygen" + ] + }, + "spandsp-dev": { + "versions": { + "0.0.6-r0": 1509481503 + }, + "origin": "spandsp", + "dependencies": [ + "tiff-dev", + "pkgconfig", + "spandsp=0.0.6-r0" + ], + "provides": [ + "pc:spandsp=0.0.6" + ] + }, + "xgamma-doc": { + "versions": { + "1.0.6-r0": 1509481749 + }, + "origin": "xgamma" + }, + "lttng-ust": { + "versions": { + "2.10.0-r1": 1509492305 + }, + "origin": "lttng-ust", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liburcu-bp.so.6", + "so:liburcu-cds.so.6" + ], + "provides": [ + "so:liblttng-ust-ctl.so.4=4.0.0", + "so:liblttng-ust-cyg-profile-fast.so.0=0.0.0", + "so:liblttng-ust-cyg-profile.so.0=0.0.0", + "so:liblttng-ust-dl.so.0=0.0.0", + "so:liblttng-ust-fd.so.0=0.0.0", + "so:liblttng-ust-fork.so.0=0.0.0", + "so:liblttng-ust-libc-wrapper.so.0=0.0.0", + "so:liblttng-ust-pthread-wrapper.so.0=0.0.0", + "so:liblttng-ust-tracepoint.so.0=0.0.0", + "so:liblttng-ust.so.0=0.0.0", + "cmd:lttng-gen-tp" + ] + }, + "squid-lang-ru": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865669 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "xf86-video-rendition-doc": { + "versions": { + "4.2.6-r1": 1510074857 + }, + "origin": "xf86-video-rendition" + }, + "iniparser": { + "versions": { + "4.0-r1": 1509486559 + }, + "origin": "iniparser", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libiniparser.so.0=0" + ] + }, + "graphviz-dev": { + "versions": { + "2.40.1-r0": 1510066916 + }, + "origin": "graphviz", + "dependencies": [ + "zlib-dev", + "libpng-dev", + "libjpeg-turbo-dev", + "expat-dev", + "fontconfig-dev", + "libsm-dev", + "libxext-dev", + "cairo-dev", + "pango-dev", + "librsvg-dev", + "gmp-dev", + "freetype-dev", + "graphviz=2.40.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcdt=2.40.1", + "pc:libcgraph=2.40.1", + "pc:libgvc=2.40.1", + "pc:libgvpr=2.40.1", + "pc:liblab_gamut=2.40.1", + "pc:libpathplan=2.40.1", + "pc:libxdot=2.40.1" + ] + }, + "perl-ipc-run3-doc": { + "versions": { + "0.048-r0": 1509481624 + }, + "origin": "perl-ipc-run3" + }, + "libmad-dev": { + "versions": { + "0.15.1b-r7": 1509473572, + "0.15.1b-r8": 1565784570 + }, + "origin": "libmad", + "dependencies": [ + "libmad=0.15.1b-r8", + "pkgconfig" + ], + "provides": [ + "pc:mad=0.15.0b" + ] + }, + "libice-doc": { + "versions": { + "1.0.9-r2": 1509465974 + }, + "origin": "libice" + }, + "clucene-contribs": { + "versions": { + "2.3.3.4-r5": 1509495542 + }, + "origin": "clucene", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libclucene-core.so.1", + "so:libclucene-shared.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libclucene-contribs-lib.so.1=2.3.3.4" + ] + }, + "pwgen": { + "versions": { + "2.08-r0": 1509491101 + }, + "origin": "pwgen", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pwgen" + ] + }, + "udisks-doc": { + "versions": { + "1.0.5-r3": 1510073402 + }, + "origin": "udisks" + }, + "lxc": { + "versions": { + "2.1.1-r3": 1533578833 + }, + "origin": "lxc", + "dependencies": [ + "gzip", + "so:libc.musl-x86_64.so.1", + "so:liblxc.so.1" + ], + "provides": [ + "cmd:init.lxc", + "cmd:init.lxc.static", + "cmd:lxc-attach", + "cmd:lxc-autostart", + "cmd:lxc-cgroup", + "cmd:lxc-checkconfig", + "cmd:lxc-checkpoint", + "cmd:lxc-config", + "cmd:lxc-console", + "cmd:lxc-copy", + "cmd:lxc-create", + "cmd:lxc-destroy", + "cmd:lxc-device", + "cmd:lxc-execute", + "cmd:lxc-freeze", + "cmd:lxc-info", + "cmd:lxc-ls", + "cmd:lxc-monitor", + "cmd:lxc-snapshot", + "cmd:lxc-start", + "cmd:lxc-stop", + "cmd:lxc-top", + "cmd:lxc-unfreeze", + "cmd:lxc-unshare", + "cmd:lxc-usernsexec", + "cmd:lxc-wait" + ] + }, + "busybox": { + "versions": { + "1.27.2-r11": 1528276162 + }, + "origin": "busybox", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "/bin/sh", + "cmd:busybox", + "cmd:sh" + ] + }, + "libdbi-dev": { + "versions": { + "0.9.0-r0": 1509474454 + }, + "origin": "libdbi", + "dependencies": [ + "libdbi=0.9.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:dbi=0.9.0" + ] + }, + "rsyslog-dbg": { + "versions": { + "8.31.0-r0": 1512031983, + "8.31.0-r1": 1571767020 + }, + "origin": "rsyslog" + }, + "mp3splt": { + "versions": { + "2.6.2-r0": 1509488740 + }, + "origin": "mp3splt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmp3splt.so.0" + ], + "provides": [ + "cmd:mp3splt", + "cmd:oggsplt" + ] + }, + "gross-dev": { + "versions": { + "1.0.2-r11": 1509482386 + }, + "origin": "gross" + }, + "parted-dev": { + "versions": { + "3.2-r6": 1509482205 + }, + "origin": "parted", + "dependencies": [ + "parted=3.2-r6", + "pkgconfig" + ], + "provides": [ + "pc:libparted=3.2" + ] + }, + "cairo-dev": { + "versions": { + "1.14.10-r0": 1509464620 + }, + "origin": "cairo", + "dependencies": [ + "fontconfig-dev", + "freetype-dev", + "libxrender-dev", + "pixman-dev", + "xcb-util-dev", + "libxext-dev", + "cairo-tools", + "cairo-gobject=1.14.10-r0", + "cairo=1.14.10-r0", + "pc:fontconfig>=2.2.95", + "pc:freetype2>=9.7.3", + "pc:glib-2.0>=2.14", + "pc:gobject-2.0", + "pc:libpng", + "pc:pixman-1>=0.30.0", + "pc:x11", + "pc:xcb-render>=1.6", + "pc:xcb-shm", + "pc:xcb>=1.6", + "pc:xext", + "pc:xrender>=0.6", + "pkgconfig" + ], + "provides": [ + "pc:cairo-fc=1.14.10", + "pc:cairo-ft=1.14.10", + "pc:cairo-gobject=1.14.10", + "pc:cairo-pdf=1.14.10", + "pc:cairo-png=1.14.10", + "pc:cairo-ps=1.14.10", + "pc:cairo-script=1.14.10", + "pc:cairo-svg=1.14.10", + "pc:cairo-tee=1.14.10", + "pc:cairo-xcb-shm=1.14.10", + "pc:cairo-xcb=1.14.10", + "pc:cairo-xlib-xrender=1.14.10", + "pc:cairo-xlib=1.14.10", + "pc:cairo=1.14.10" + ] + }, + "xfce4-taskmanager-lang": { + "versions": { + "1.2.0-r0": 1510074646 + }, + "origin": "xfce4-taskmanager" + }, + "clamav-dev": { + "versions": { + "0.100.3-r0": 1555508237 + }, + "origin": "clamav", + "dependencies": [ + "libressl-dev", + "clamav-lib=0.100.3-r0", + "clamav-libunrar=0.100.3-r0", + "clamav=0.100.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:libclamav=0.100.3", + "cmd:clamav-config" + ] + }, + "tinyproxy": { + "versions": { + "1.8.4-r3": 1509491456 + }, + "origin": "tinyproxy", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tinyproxy" + ] + }, + "qemu-system-sparc64": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-sparc64" + ] + }, + "py3-urwid": { + "versions": { + "1.3.1-r2": 1509493955 + }, + "origin": "py-urwid", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "cramfs": { + "versions": { + "1.1-r2": 1509474501 + }, + "origin": "cramfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:cramfsck", + "cmd:mkcramfs" + ] + }, + "cunit-doc": { + "versions": { + "2.1.3-r1": 1509466104 + }, + "origin": "cunit" + }, + "libsexy": { + "versions": { + "0.1.11-r7": 1510072499 + }, + "origin": "libsexy", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libsexy.so.2=2.0.4" + ] + }, + "libcap-ng-dev": { + "versions": { + "0.7.8-r1": 1509461309 + }, + "origin": "libcap-ng", + "dependencies": [ + "linux-headers", + "libcap-ng=0.7.8-r1", + "pkgconfig" + ], + "provides": [ + "pc:libcap-ng=0.7.8" + ] + }, + "unbound": { + "versions": { + "1.6.7-r1": 1510588259 + }, + "origin": "unbound", + "dependencies": [ + "dnssec-root", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libevent-2.1.so.6", + "so:libexpat.so.1", + "so:libssl.so.44", + "so:libunbound.so.2" + ], + "provides": [ + "cmd:unbound", + "cmd:unbound-anchor", + "cmd:unbound-checkconf", + "cmd:unbound-control", + "cmd:unbound-control-setup", + "cmd:unbound-host" + ] + }, + "lua5.1-filesystem": { + "versions": { + "1.7.0.2-r0": 1511049451 + }, + "origin": "lua-filesystem", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "alpine-sdk": { + "versions": { + "0.5-r0": 1509462433 + }, + "origin": "alpine-sdk", + "dependencies": [ + "abuild", + "build-base", + "git", + "squashfs-tools", + "xorriso", + "acct", + "mkinitfs", + "mtools" + ] + }, + "perl-extutils-config": { + "versions": { + "0.008-r0": 1509474738 + }, + "origin": "perl-extutils-config" + }, + "cups-filters-doc": { + "versions": { + "1.17.9-r0": 1510075884 + }, + "origin": "cups-filters" + }, + "ghostscript-dbg": { + "versions": { + "9.26-r2": 1554362767, + "9.26-r3": 1565700439, + "9.26-r4": 1571234492 + }, + "origin": "ghostscript" + }, + "libatasmart": { + "versions": { + "0.19-r1": 1509482228 + }, + "origin": "libatasmart", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ], + "provides": [ + "so:libatasmart.so.4=4.0.5", + "cmd:skdump", + "cmd:sktest" + ] + }, + "lua5.1-stringy": { + "versions": { + "0.4.0-r5": 1509480099 + }, + "origin": "lua-stringy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-sub-exporter": { + "versions": { + "0.987-r0": 1509473987 + }, + "origin": "perl-sub-exporter", + "dependencies": [ + "perl-data-optlist", + "perl-sub-install", + "perl-params-util" + ] + }, + "gparted": { + "versions": { + "0.30.0-r0": 1510073982 + }, + "origin": "gparted", + "dependencies": [ + "e2fsprogs", + "ntfs-3g-progs", + "so:libatkmm-1.6.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdkmm-2.4.so.1", + "so:libglib-2.0.so.0", + "so:libglibmm-2.4.so.1", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libgtkmm-2.4.so.1", + "so:libintl.so.8", + "so:libpangomm-1.4.so.1", + "so:libparted-fs-resize.so.0", + "so:libparted.so.2", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:gparted", + "cmd:gpartedbin" + ] + }, + "py-gobject-dev": { + "versions": { + "2.28.6-r6": 1509471051 + }, + "origin": "py-gobject", + "dependencies": [ + "pc:gobject-2.0", + "pc:libffi", + "pkgconfig", + "py-gobject=2.28.6-r6" + ], + "provides": [ + "pc:pygobject-2.0=2.28.6" + ] + }, + "sshfs": { + "versions": { + "2.10-r2": 1509489736 + }, + "origin": "sshfs", + "dependencies": [ + "openssh-client", + "so:libc.musl-x86_64.so.1", + "so:libfuse.so.2", + "so:libglib-2.0.so.0" + ], + "provides": [ + "cmd:sshfs" + ] + }, + "ghostscript-doc": { + "versions": { + "9.26-r2": 1554362767, + "9.26-r3": 1565700439, + "9.26-r4": 1571234492 + }, + "origin": "ghostscript" + }, + "tiff-dev": { + "versions": { + "4.0.10-r0": 1544169734, + "4.0.10-r1": 1566982293, + "4.0.10-r2": 1572815888 + }, + "origin": "tiff", + "dependencies": [ + "zlib-dev", + "libjpeg-turbo-dev", + "pkgconfig", + "tiff=4.0.10-r2" + ], + "provides": [ + "pc:libtiff-4=4.0.10" + ] + }, + "qjson-dev": { + "versions": { + "0.9.0-r0": 1510075392 + }, + "origin": "qjson", + "dependencies": [ + "pc:QtCore", + "pkgconfig", + "qjson=0.9.0-r0" + ], + "provides": [ + "pc:QJson=0.9.0" + ] + }, + "haproxy": { + "versions": { + "1.7.9-r1": 1510261284 + }, + "origin": "haproxy", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:liblua-5.3.so.0", + "so:libpcre.so.1", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:haproxy" + ] + }, + "xscreensaver-lang": { + "versions": { + "5.36-r0": 1510074356 + }, + "origin": "xscreensaver", + "dependencies": [ + "bc" + ] + }, + "wireless-tools-doc": { + "versions": { + "30_pre9-r0": 1509479802 + }, + "origin": "wireless-tools" + }, + "perl-filesys-notify-simple-doc": { + "versions": { + "0.12-r0": 1510588883 + }, + "origin": "perl-filesys-notify-simple" + }, + "kmod-bash-completion": { + "versions": { + "24-r0": 1509462317 + }, + "origin": "kmod" + }, + "squid-lang-bg": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865667 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "resourceproto": { + "versions": { + "1.2.0-r2": 1509468557 + }, + "origin": "resourceproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:resourceproto=1.2.0" + ] + }, + "glew-doc": { + "versions": { + "2.1.0-r0": 1510068629 + }, + "origin": "glew" + }, + "py3-simplejson": { + "versions": { + "3.8.2-r2": 1509494752 + }, + "origin": "py-simplejson", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "memcached": { + "versions": { + "1.5.6-r0": 1520365888 + }, + "origin": "memcached", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libsasl2.so.3", + "so:libseccomp.so.2" + ], + "provides": [ + "cmd:memcached" + ] + }, + "fcgi": { + "versions": { + "2.4.0-r8": 1510330999 + }, + "origin": "fcgi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfcgi.so.0=0.0.0", + "cmd:cgi-fcgi" + ] + }, + "uwsgi-xslt": { + "versions": { + "2.0.17-r0": 1522154661 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2", + "so:libxslt.so.1" + ] + }, + "py3-virtualenv": { + "versions": { + "15.1.0-r0": 1512030542 + }, + "origin": "py-virtualenv", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:virtualenv" + ] + }, + "libotr3": { + "versions": { + "3.2.1-r4": 1509481193 + }, + "origin": "libotr3", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20" + ], + "provides": [ + "so:libotr.so.2=2.2.1" + ] + }, + "cython": { + "versions": { + "0.27.2-r0": 1509484225 + }, + "origin": "cython", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:cygdb", + "cmd:cython", + "cmd:cythonize" + ] + }, + "uwsgi-notfound": { + "versions": { + "2.0.17-r0": 1522154656 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "openldap-overlay-auditlog": { + "versions": { + "2.4.45-r3": 1510258134, + "2.4.46-r0": 1565073939, + "2.4.48-r0": 1566900516 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "shared-mime-info-doc": { + "versions": { + "1.9-r0": 1509464467 + }, + "origin": "shared-mime-info" + }, + "lua5.2-hashids": { + "versions": { + "1.0.6-r1": 1509480766 + }, + "origin": "lua-hashids", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "xvidcore": { + "versions": { + "1.3.4-r0": 1509480933 + }, + "origin": "xvidcore", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libxvidcore.so.4=4.3" + ] + }, + "bind-dnssec-tools": { + "versions": { + "9.11.6_p1-r1": 1556872768, + "9.11.8-r0": 1561323229 + }, + "origin": "bind", + "dependencies": [ + "py3-bind=9.11.8-r0" + ], + "provides": [ + "cmd:dnssec-checkds", + "cmd:dnssec-coverage", + "cmd:dnssec-keymgr" + ] + }, + "xf86-input-vmmouse-doc": { + "versions": { + "13.1.0-r3": 1510074937 + }, + "origin": "xf86-input-vmmouse" + }, + "mosquitto-clients": { + "versions": { + "1.4.15-r0": 1520176488, + "1.4.15-r2": 1563346324 + }, + "origin": "mosquitto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ], + "provides": [ + "cmd:mosquitto_pub", + "cmd:mosquitto_sub" + ] + }, + "gnupg-doc": { + "versions": { + "2.2.3-r1": 1528897438 + }, + "origin": "gnupg" + }, + "xf86-video-tdfx-doc": { + "versions": { + "1.4.7-r0": 1510075838 + }, + "origin": "xf86-video-tdfx" + }, + "unfs3-doc": { + "versions": { + "0.9.22-r4": 1509496016 + }, + "origin": "unfs3" + }, + "glfw-dev": { + "versions": { + "3.2.1-r2": 1510068480 + }, + "origin": "glfw", + "dependencies": [ + "libxinerama-dev", + "linux-headers", + "mesa-dev", + "glfw=3.2.1-r2", + "pc:x11", + "pc:xcursor", + "pc:xinerama", + "pc:xrandr", + "pc:xxf86vm", + "pkgconfig" + ], + "provides": [ + "pc:glfw3=3.2.1" + ] + }, + "apkbuild-gem-resolver": { + "versions": { + "3.1.0-r4": 1527832619, + "3.1.0-r5": 1572356049 + }, + "origin": "abuild", + "dependencies": [ + "ruby", + "ruby-augeas" + ], + "provides": [ + "cmd:apkbuild-gem-resolver" + ] + }, + "python2-doc": { + "versions": { + "2.7.15-r2": 1534944322 + }, + "origin": "python2" + }, + "seavgabios-bin": { + "versions": { + "1.10.3-r0": 1510068884 + }, + "origin": "seabios", + "dependencies": [ + "seabios-bin=1.10.3-r0", + "seavgabios-bin=1.10.3-r0" + ] + }, + "rgb": { + "versions": { + "1.0.6-r0": 1509486279 + }, + "origin": "rgb", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:showrgb" + ] + }, + "gamin": { + "versions": { + "0.1.10-r10": 1509481667 + }, + "origin": "gamin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "so:libfam.so.0=0.0.0", + "so:libgamin-1.so.0=0.1.10" + ] + }, + "py-icu": { + "versions": { + "1.9.6-r3": 1510832509 + }, + "origin": "py-icu" + }, + "nss-tools": { + "versions": { + "3.34.1-r0": 1511890216 + }, + "origin": "nss", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnspr4.so", + "so:libnss3.so", + "so:libnssutil3.so", + "so:libplc4.so", + "so:libplds4.so", + "so:libsmime3.so", + "so:libz.so.1" + ], + "provides": [ + "cmd:certutil", + "cmd:cmsutil", + "cmd:crlutil", + "cmd:modutil", + "cmd:pk12util", + "cmd:shlibsign", + "cmd:signtool", + "cmd:signver", + "cmd:ssltap" + ] + }, + "perl-net-dns-resolver-programmable": { + "versions": { + "0.003-r2": 1509475480 + }, + "origin": "perl-net-dns-resolver-programmable", + "dependencies": [ + "perl", + "perl-net-dns" + ] + }, + "nload": { + "versions": { + "0.7.4-r3": 1509496066 + }, + "origin": "nload", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libformw.so.6", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:nload" + ] + }, + "iptables": { + "versions": { + "1.6.1-r1": 1509469980 + }, + "origin": "iptables", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0", + "so:libnftnl.so.7" + ], + "provides": [ + "so:libip4tc.so.0=0.1.0", + "so:libip6tc.so.0=0.1.0", + "so:libipq.so.0=0.0.0", + "so:libiptc.so.0=0.0.0", + "so:libxtables.so.12=12.0.0", + "cmd:arptables-compat", + "cmd:ebtables-compat", + "cmd:iptables", + "cmd:iptables-compat", + "cmd:iptables-compat-restore", + "cmd:iptables-compat-save", + "cmd:iptables-restore", + "cmd:iptables-restore-translate", + "cmd:iptables-save", + "cmd:iptables-translate", + "cmd:xtables-compat-multi", + "cmd:xtables-multi" + ] + }, + "libnfsidmap-ldap": { + "versions": { + "0.25-r1": 1509492066 + }, + "origin": "libnfsidmap", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "augeas-static": { + "versions": { + "1.9.0-r3": 1511899746 + }, + "origin": "augeas" + }, + "py3-werkzeug": { + "versions": { + "0.11.15-r2": 1509551864 + }, + "origin": "py-werkzeug", + "dependencies": [ + "python3" + ] + }, + "dovecot-pgsql": { + "versions": { + "2.2.36.3-r0": 1554102389, + "2.2.36.4-r0": 1567075098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot-sql", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "so:libdriver_pgsql.so=0" + ] + }, + "squid-lang-it": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-crypt-ssleay": { + "versions": { + "0.72-r7": 1510260987 + }, + "origin": "perl-crypt-ssleay", + "dependencies": [ + "perl-path-class", + "perl-try-tiny", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "neon": { + "versions": { + "0.30.2-r2": 1510285202 + }, + "origin": "neon", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libexpat.so.1", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:libneon.so.27=27.3.2" + ] + }, + "at-spi2-atk": { + "versions": { + "2.26.1-r0": 1509466060 + }, + "origin": "at-spi2-atk", + "dependencies": [ + "so:libatk-1.0.so.0", + "so:libatspi.so.0", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libatk-bridge-2.0.so.0=0.0.0" + ] + }, + "xf86-video-s3-doc": { + "versions": { + "0.6.5-r8": 1510074848 + }, + "origin": "xf86-video-s3" + }, + "clamav-daemon": { + "versions": { + "0.100.3-r0": 1555508237 + }, + "origin": "clamav", + "dependencies": [ + "freshclam", + "logrotate", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libclamav.so.7", + "so:libcrypto.so.42", + "so:libfts.so.0", + "so:libncursesw.so.6", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:clamconf", + "cmd:clamd", + "cmd:clamdscan", + "cmd:clamdtop" + ] + }, + "lua5.1-sec": { + "versions": { + "0.6-r3": 1510260656 + }, + "origin": "lua-sec", + "dependencies": [ + "lua5.1-socket", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "libgcab-dev": { + "versions": { + "0.7-r1": 1510067822 + }, + "origin": "libgcab", + "dependencies": [ + "gettext-dev", + "libgcab=0.7-r1", + "pc:gio-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libgcab-1.0=0.7" + ] + }, + "spice-gtk-lang": { + "versions": { + "0.34-r1": 1510260978 + }, + "origin": "spice-gtk" + }, + "mesa-glapi": { + "versions": { + "17.2.4-r1": 1510741947 + }, + "origin": "mesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libglapi.so.0=0.0.0" + ] + }, + "gnumeric-lang": { + "versions": { + "1.12.36-r0": 1511455617 + }, + "origin": "gnumeric" + }, + "gcc-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503663 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "apkbuild-cpan": { + "versions": { + "3.1.0-r4": 1527832619, + "3.1.0-r5": 1572356049 + }, + "origin": "abuild", + "dependencies": [ + "perl", + "perl-libwww", + "perl-json", + "perl-module-build-tiny" + ], + "provides": [ + "cmd:apkbuild-cpan" + ] + }, + "stfl": { + "versions": { + "0.24-r2": 1509491486 + }, + "origin": "stfl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libstfl.so.0=0.24" + ] + }, + "dmenu": { + "versions": { + "4.7-r0": 1509494019 + }, + "origin": "dmenu", + "dependencies": [ + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1" + ], + "provides": [ + "cmd:dmenu", + "cmd:dmenu_path", + "cmd:dmenu_run", + "cmd:stest" + ] + }, + "bcache-tools-doc": { + "versions": { + "1.0.8-r1": 1509495228 + }, + "origin": "bcache-tools" + }, + "gpicview": { + "versions": { + "0.2.5-r0": 1510074707 + }, + "origin": "gpicview", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8" + ], + "provides": [ + "cmd:gpicview" + ] + }, + "openjpeg-dev": { + "versions": { + "2.3.0-r2": 1552584602 + }, + "origin": "openjpeg", + "dependencies": [ + "openjpeg=2.3.0-r2", + "pkgconfig" + ], + "provides": [ + "pc:libopenjp2=2.3.0" + ] + }, + "git-gui": { + "versions": { + "2.15.3-r0": 1540401295, + "2.15.4-r0": 1576015196 + }, + "origin": "git", + "dependencies": [ + "git=2.15.4-r0", + "tcl", + "tk" + ] + }, + "iaxmodem": { + "versions": { + "1.3.0-r0": 1509494110 + }, + "origin": "iaxmodem", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:iaxmodem" + ] + }, + "e2fsprogs-dev": { + "versions": { + "1.43.7-r0": 1509469274, + "1.43.7-r1": 1571322241 + }, + "origin": "e2fsprogs", + "dependencies": [ + "util-linux-dev", + "e2fsprogs-libs=1.43.7-r1", + "libcom_err=1.43.7-r1", + "pkgconfig" + ], + "provides": [ + "pc:com_err=1.43.7", + "pc:e2p=1.43.7", + "pc:ext2fs=1.43.7", + "pc:ss=1.43.7", + "cmd:compile_et", + "cmd:mk_cmds" + ] + }, + "vsftpd": { + "versions": { + "3.0.3-r4": 1510260062 + }, + "origin": "vsftpd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.42", + "so:libpam.so.0", + "so:libssl.so.44" + ], + "provides": [ + "cmd:vsftpd" + ] + }, + "squid-lang-az": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865666 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-class-accessor-chained-doc": { + "versions": { + "0.01-r0": 1509474101 + }, + "origin": "perl-class-accessor-chained" + }, + "swfdec-mozilla": { + "versions": { + "0.9.2-r0": 1510075257 + }, + "origin": "swfdec-mozilla", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libswfdec-0.9.so.2", + "so:libswfdec-gtk-0.9.so.2" + ] + }, + "libtasn1": { + "versions": { + "4.12-r3": 1519805758, + "4.12-r4": 1563955390 + }, + "origin": "libtasn1", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libtasn1.so.6=6.5.4", + "cmd:asn1Coding", + "cmd:asn1Decoding", + "cmd:asn1Parser" + ] + }, + "collectd-mysql": { + "versions": { + "5.7.2-r0": 1510069647 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "rp-pppoe": { + "versions": { + "3.12-r1": 1541067520 + }, + "origin": "rp-pppoe", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pppoe", + "cmd:pppoe-connect", + "cmd:pppoe-relay", + "cmd:pppoe-server", + "cmd:pppoe-setup", + "cmd:pppoe-sniff", + "cmd:pppoe-start", + "cmd:pppoe-status", + "cmd:pppoe-stop" + ] + }, + "perl-test-harness-doc": { + "versions": { + "3.39-r0": 1509474771 + }, + "origin": "perl-test-harness" + }, + "mcabber-doc": { + "versions": { + "1.1.0-r0": 1510588319 + }, + "origin": "mcabber" + }, + "libgomp": { + "versions": { + "6.4.0-r5": 1509458072 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgomp.so.1=1.0.0" + ] + }, + "libdv": { + "versions": { + "1.0.0-r3": 1510068410 + }, + "origin": "libdv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdv.so.4=4.0.3" + ] + }, + "perl-class-method-modifiers": { + "versions": { + "2.12-r0": 1509479364 + }, + "origin": "perl-class-method-modifiers" + }, + "clucene": { + "versions": { + "2.3.3.4-r5": 1509495542 + }, + "origin": "clucene", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libclucene-core.so.1=2.3.3.4", + "so:libclucene-shared.so.1=2.3.3.4" + ] + }, + "iw": { + "versions": { + "4.9-r0": 1509494921 + }, + "origin": "iw", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnl-3.so.200", + "so:libnl-genl-3.so.200" + ], + "provides": [ + "cmd:iw" + ] + }, + "lua5.2-md5": { + "versions": { + "1.2-r3": 1509468355 + }, + "origin": "lua-md5", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "uwsgi": { + "versions": { + "2.0.17-r0": 1522154661 + }, + "origin": "uwsgi", + "dependencies": [ + "mailcap", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.42", + "so:libjansson.so.4", + "so:libpcre.so.1", + "so:libssl.so.44", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:uwsgi" + ] + }, + "libvirt-client": { + "versions": { + "3.9.0-r1": 1510088351, + "5.5.0-r0": 1562165539 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt", + "pm-utils", + "gnutls-utils", + "netcat-openbsd", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0", + "so:libcurl.so.4", + "so:libdbus-1.so.3", + "so:libdevmapper.so.1.02", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libintl.so.8", + "so:libnl-3.so.200", + "so:libreadline.so.7", + "so:libsasl2.so.3", + "so:libtirpc.so.3", + "so:libxml2.so.2", + "so:libyajl.so.2" + ], + "provides": [ + "so:libvirt-admin.so.0=0.5005.0", + "so:libvirt-lxc.so.0=0.5005.0", + "so:libvirt-qemu.so.0=0.5005.0", + "so:libvirt.so.0=0.5005.0", + "cmd:virsh", + "cmd:virt-admin", + "cmd:virt-host-validate", + "cmd:virt-login-shell", + "cmd:virt-pki-validate", + "cmd:virt-xml-validate" + ] + }, + "ncurses5-libs": { + "versions": { + "5.9-r1": 1509483665 + }, + "origin": "ncurses5", + "dependencies": [ + "ncurses-terminfo-base", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libform.so.5=5.9", + "so:libmenu.so.5=5.9", + "so:libncurses.so.5=5.9", + "so:libpanel.so.5=5.9" + ] + }, + "llvm5-static": { + "versions": { + "5.0.0-r0": 1510682601 + }, + "origin": "llvm5", + "provides": [ + "llvm-static=5.0.0-r0" + ] + }, + "pkgconf-doc": { + "versions": { + "1.3.10-r0": 1509459813 + }, + "origin": "pkgconf" + }, + "ffmpeg": { + "versions": { + "3.4-r1": 1510072148 + }, + "origin": "ffmpeg", + "dependencies": [ + "so:libSDL2-2.0.so.0", + "so:libavcodec.so.57", + "so:libavdevice.so.57", + "so:libavfilter.so.6", + "so:libavformat.so.57", + "so:libavresample.so.3", + "so:libavutil.so.55", + "so:libc.musl-x86_64.so.1", + "so:libpostproc.so.54", + "so:libswresample.so.2", + "so:libswscale.so.4" + ], + "provides": [ + "cmd:ffmpeg", + "cmd:ffplay", + "cmd:ffprobe", + "cmd:ffserver", + "cmd:qt-faststart" + ] + }, + "perl-digest-hmac-doc": { + "versions": { + "1.03-r0": 1509468923 + }, + "origin": "perl-digest-hmac" + }, + "qemu-system-arm": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libstdc++.so.6", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-arm" + ] + }, + "libc-dev": { + "versions": { + "0.7.1-r0": 1509458196 + }, + "origin": "libc-dev", + "dependencies": [ + "musl-dev" + ] + }, + "collectd": { + "versions": { + "5.7.2-r0": 1510069656 + }, + "origin": "collectd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libltdl.so.7", + "so:libyajl.so.2" + ], + "provides": [ + "cmd:collectd", + "cmd:collectdmon" + ] + }, + "gstreamer0.10": { + "versions": { + "0.10.36-r2": 1509470985 + }, + "origin": "gstreamer0.10", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgstbase-0.10.so.0=0.30.0", + "so:libgstcheck-0.10.so.0=0.30.0", + "so:libgstcontroller-0.10.so.0=0.30.0", + "so:libgstdataprotocol-0.10.so.0=0.30.0", + "so:libgstnet-0.10.so.0=0.30.0", + "so:libgstreamer-0.10.so.0=0.30.0" + ] + }, + "libverto-glib": { + "versions": { + "0.3.0-r0": 1509469586 + }, + "origin": "libverto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libverto.so.1" + ], + "provides": [ + "so:libverto-glib.so.1=1.0.0" + ] + }, + "py3-tz": { + "versions": { + "2017.3-r0": 1510732132 + }, + "origin": "py-tz", + "dependencies": [ + "python3" + ] + }, + "perl-email-address": { + "versions": { + "1.908-r0": 1509491037, + "1.912-r0": 1559738452 + }, + "origin": "perl-email-address", + "dependencies": [ + "perl", + "perl-capture-tiny" + ] + }, + "gnumeric": { + "versions": { + "1.12.36-r0": 1511455617 + }, + "origin": "gnumeric", + "dependencies": [ + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgoffice-0.10.so.10", + "so:libgsf-1.so.114", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libspreadsheet-1.12.36.so=0", + "cmd:gnumeric", + "cmd:gnumeric-1.12.36", + "cmd:ssconvert", + "cmd:ssdiff", + "cmd:ssgrep", + "cmd:ssindex" + ] + }, + "perl-regexp-common-net-cidr-doc": { + "versions": { + "0.02-r1": 1510564550 + }, + "origin": "perl-regexp-common-net-cidr" + }, + "acf-iproute2-qos": { + "versions": { + "0.4.0-r2": 1510073019 + }, + "origin": "acf-iproute2-qos", + "dependencies": [ + "acf-core", + "iproute2-qos", + "acf-alpine-baselayout>=0.5.7" + ] + }, + "libssh2-dbg": { + "versions": { + "1.8.2-r0": 1553682023, + "1.9.0-r0": 1571235283, + "1.9.0-r1": 1571996776 + }, + "origin": "libssh2" + }, + "nfdump-doc": { + "versions": { + "1.6.15-r0": 1509494465, + "1.6.15-r1": 1574265425 + }, + "origin": "nfdump" + }, + "guile-dev": { + "versions": { + "2.0.14-r0": 1509468173 + }, + "origin": "guile", + "dependencies": [ + "guile", + "gc-dev", + "guile-libs=2.0.14-r0", + "pkgconfig" + ], + "provides": [ + "pc:guile-2.0=2.0.14", + "cmd:guile-config" + ] + }, + "libvirt-vbox": { + "versions": { + "3.9.0-r1": 1510088352, + "5.5.0-r0": 1562165540 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-daemon=5.5.0-r0", + "libvirt-common-drivers=5.5.0-r0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libvirt.so.0", + "so:libxml2.so.2" + ] + }, + "c-ares-doc": { + "versions": { + "1.13.0-r0": 1509461299 + }, + "origin": "c-ares" + }, + "cython-dev": { + "versions": { + "0.27.2-r0": 1509484225 + }, + "origin": "cython", + "dependencies": [ + "python2-dev", + "py-pgen", + "cython" + ] + }, + "bind-dev": { + "versions": { + "9.11.6_p1-r1": 1556872767, + "9.11.8-r0": 1561323229 + }, + "origin": "bind", + "dependencies": [ + "bind-libs=9.11.8-r0" + ], + "provides": [ + "cmd:bind9-config" + ] + }, + "xf86-video-nv-doc": { + "versions": { + "2.1.21-r0": 1510075413 + }, + "origin": "xf86-video-nv" + }, + "perl-time-modules-doc": { + "versions": { + "2013.0912-r0": 1509493223 + }, + "origin": "perl-time-modules" + }, + "squid-lang-he": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-convert-asn1": { + "versions": { + "0.27-r0": 1509477731 + }, + "origin": "perl-convert-asn1" + }, + "json-c-dev": { + "versions": { + "0.12.1-r1": 1509466321 + }, + "origin": "json-c", + "dependencies": [ + "json-c=0.12.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:json-c=0.12.1" + ] + }, + "vde2-libs": { + "versions": { + "2.3.2-r8": 1510260476 + }, + "origin": "vde2", + "dependencies": [ + "libressl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libvdehist.so.0=0.0.1", + "so:libvdemgmt.so.0=0.0.1", + "so:libvdeplug.so.3=3.0.1", + "so:libvdesnmp.so.0=0.0.1" + ] + }, + "py-sphinx_rtd_theme": { + "versions": { + "0.2.4-r0": 1509469913 + }, + "origin": "py-sphinx_rtd_theme" + }, + "iproute2-qos": { + "versions": { + "0.5-r1": 1509489558 + }, + "origin": "iproute2-qos", + "dependencies": [ + "iproute2" + ], + "provides": [ + "cmd:setup-qos" + ] + }, + "loudmouth-dev": { + "versions": { + "1.5.3-r0": 1509490879 + }, + "origin": "loudmouth", + "dependencies": [ + "pkgconfig", + "gnutls-dev", + "libidn-dev", + "libasyncns-dev", + "check-dev", + "autoconf", + "loudmouth=1.5.3-r0", + "pc:glib-2.0" + ], + "provides": [ + "pc:loudmouth-1.0=1.5.3" + ] + }, + "the_silver_searcher-bash-completion": { + "versions": { + "2.1.0-r2": 1510831170 + }, + "origin": "the_silver_searcher" + }, + "libpurple-bonjour": { + "versions": { + "2.12.0-r2": 1510069751 + }, + "origin": "pidgin", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-glib.so.1", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libintl.so.8", + "so:libxml2.so.2" + ], + "provides": [ + "so:libbonjour.so=0" + ] + }, + "lua5.1-openrc": { + "versions": { + "0.2-r2": 1509492195 + }, + "origin": "lua-openrc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librc.so.1" + ] + }, + "flite-dev": { + "versions": { + "2.0.0-r0": 1509481028 + }, + "origin": "flite", + "dependencies": [ + "flite=2.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:flite=2.0.0" + ] + }, + "giflib-doc": { + "versions": { + "5.1.4-r1": 1509470479 + }, + "origin": "giflib" + }, + "vde2-dev": { + "versions": { + "2.3.2-r8": 1510260476 + }, + "origin": "vde2", + "dependencies": [ + "pkgconfig", + "vde2-libs=2.3.2-r8" + ], + "provides": [ + "pc:vdehist=2.3.2", + "pc:vdemgmt=2.3.2", + "pc:vdeplug=2.3.2", + "pc:vdesnmp=2.3.2" + ] + }, + "fluxbox": { + "versions": { + "1.3.7-r0": 1509491166 + }, + "origin": "fluxbox", + "dependencies": [ + "so:libImlib2.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libXpm.so.4", + "so:libXrandr.so.2", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:fbrun", + "cmd:fbsetbg", + "cmd:fbsetroot", + "cmd:fluxbox", + "cmd:fluxbox-generate_menu", + "cmd:fluxbox-remote", + "cmd:fluxbox-update_configs", + "cmd:startfluxbox" + ] + }, + "gmime-doc": { + "versions": { + "2.6.20-r0": 1510588181 + }, + "origin": "gmime" + }, + "xinput-doc": { + "versions": { + "1.6.2-r0": 1509490859 + }, + "origin": "xinput" + }, + "run-parts-doc": { + "versions": { + "4.8.2-r0": 1509470087 + }, + "origin": "run-parts" + }, + "lua5.3-lustache": { + "versions": { + "1.3.1-r1": 1509472928 + }, + "origin": "lua-lustache", + "dependencies": [ + "lua-lustache-common", + "lua5.3", + "lua-lustache-common=1.3.1-r1" + ] + }, + "py-mccabe": { + "versions": { + "0.6.1-r1": 1511661235 + }, + "origin": "py-mccabe" + }, + "samba-winbind-clients": { + "versions": { + "4.7.6-r3": 1555491787 + }, + "origin": "samba", + "dependencies": [ + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcliauth-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libpopt-samba3-samba4.so", + "so:libpopt.so.0", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsmbconf.so.0", + "so:libtalloc.so.2", + "so:libwbclient.so.0", + "so:libwinbind-client-samba4.so" + ], + "provides": [ + "cmd:ntlm_auth", + "cmd:wbinfo" + ] + }, + "findutils-doc": { + "versions": { + "4.6.0-r0": 1509481487 + }, + "origin": "findutils" + }, + "d-feet": { + "versions": { + "0.3.12-r0": 1510075861 + }, + "origin": "d-feet", + "dependencies": [ + "python2", + "py-gobject3" + ], + "provides": [ + "cmd:d-feet" + ] + }, + "snort-dev": { + "versions": { + "2.9.11-r0": 1510848485 + }, + "origin": "snort", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:snort=2.9.11", + "pc:snort_output=2.9.11", + "pc:snort_preproc=2.9.11" + ] + }, + "perl-mro-compat-doc": { + "versions": { + "0.13-r0": 1509481602 + }, + "origin": "perl-mro-compat" + }, + "nftables-doc": { + "versions": { + "0.8-r0": 1509496566 + }, + "origin": "nftables" + }, + "redis": { + "versions": { + "4.0.11-r0": 1533890747, + "4.0.14-r0": 1564477414 + }, + "origin": "redis", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:redis-benchmark", + "cmd:redis-check-aof", + "cmd:redis-check-rdb", + "cmd:redis-cli", + "cmd:redis-sentinel", + "cmd:redis-server" + ] + }, + "qemu-microblaze": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-microblaze" + ] + }, + "powertop-doc": { + "versions": { + "2.9-r1": 1509552798 + }, + "origin": "powertop" + }, + "wayland-libs-server": { + "versions": { + "1.14.0-r2": 1510066937 + }, + "origin": "wayland", + "dependencies": [ + "wayland-libs-client", + "wayland-libs-cursor", + "wayland-libs-server", + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6" + ], + "provides": [ + "so:libwayland-server.so.0=0.1.0" + ] + }, + "tk": { + "versions": { + "8.6.6-r1": 1509462120 + }, + "origin": "tk", + "dependencies": [ + "so:libX11.so.6", + "so:libXft.so.2", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libtcl8.6.so" + ], + "provides": [ + "so:libtk8.6.so=0", + "cmd:wish", + "cmd:wish8.6" + ] + }, + "perl-time-hires-doc": { + "versions": { + "1.9746-r0": 1509484014 + }, + "origin": "perl-time-hires" + }, + "libxau-dev": { + "versions": { + "1.0.8-r2": 1509461811 + }, + "origin": "libxau", + "dependencies": [ + "libxau=1.0.8-r2", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xau=1.0.8" + ] + }, + "asterisk-sounds-moh": { + "versions": { + "15.6.1-r0": 1537795342, + "15.6.2-r0": 1568705004 + }, + "origin": "asterisk" + }, + "collectd-iptables": { + "versions": { + "5.7.2-r0": 1510069649 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libip4tc.so.0", + "so:libip6tc.so.0" + ] + }, + "pgpool": { + "versions": { + "3.7.0-r0": 1511650825 + }, + "origin": "pgpool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "so:libpcp.so.1=1.0.0", + "cmd:pcp_attach_node", + "cmd:pcp_detach_node", + "cmd:pcp_node_count", + "cmd:pcp_node_info", + "cmd:pcp_pool_status", + "cmd:pcp_proc_count", + "cmd:pcp_proc_info", + "cmd:pcp_promote_node", + "cmd:pcp_recovery_node", + "cmd:pcp_stop_pgpool", + "cmd:pcp_watchdog_info", + "cmd:pg_md5", + "cmd:pgpool", + "cmd:pgpool_setup", + "cmd:watchdog_setup" + ] + }, + "gst-plugins-bad-dev": { + "versions": { + "1.12.3-r2": 1512039893 + }, + "origin": "gst-plugins-bad", + "dependencies": [ + "gst-plugins-bad=1.12.3-r2", + "pc:gstreamer-1.0", + "pc:gstreamer-base-1.0", + "pc:gstreamer-video-1.0", + "pkgconfig" + ], + "provides": [ + "pc:gstreamer-bad-allocators-1.0=1.12.3", + "pc:gstreamer-bad-audio-1.0=1.12.3", + "pc:gstreamer-bad-base-1.0=1.12.3", + "pc:gstreamer-bad-video-1.0=1.12.3", + "pc:gstreamer-codecparsers-1.0=1.12.3", + "pc:gstreamer-gl-1.0=1.12.3", + "pc:gstreamer-insertbin-1.0=1.12.3", + "pc:gstreamer-mpegts-1.0=1.12.3", + "pc:gstreamer-player-1.0=1.12.3", + "pc:gstreamer-plugins-bad-1.0=1.12.3" + ] + }, + "mosh": { + "versions": { + "1.3.2-r3": 1510846214 + }, + "origin": "mosh", + "dependencies": [ + "mosh-client", + "mosh-server" + ], + "provides": [ + "cmd:mosh" + ] + }, + "exo-dev": { + "versions": { + "0.11.5-r0": 1510068072 + }, + "origin": "exo", + "dependencies": [ + "exo=0.11.5-r0", + "pc:gtk+-2.0", + "pc:gtk+-3.0", + "pc:libxfce4util-1.0", + "pkgconfig" + ], + "provides": [ + "pc:exo-1=0.11.5", + "pc:exo-2=0.11.5" + ] + }, + "freeradius": { + "versions": { + "3.0.15-r4": 1556202797, + "3.0.15-r5": 1566310605 + }, + "origin": "freeradius", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libfreeradius-dhcp.so", + "so:libfreeradius-radius.so", + "so:libfreeradius-server.so", + "so:libgdbm.so.4", + "so:libpcap.so.1", + "so:libreadline.so.7", + "so:libssl.so.44", + "so:libtalloc.so.2" + ], + "provides": [ + "freeradius3=3.0.15-r5", + "so:proto_dhcp.so=0", + "so:proto_vmps.so=0", + "so:rlm_always.so=0", + "so:rlm_attr_filter.so=0", + "so:rlm_cache.so=0", + "so:rlm_cache_rbtree.so=0", + "so:rlm_chap.so=0", + "so:rlm_counter.so=0", + "so:rlm_cram.so=0", + "so:rlm_date.so=0", + "so:rlm_detail.so=0", + "so:rlm_dhcp.so=0", + "so:rlm_digest.so=0", + "so:rlm_dynamic_clients.so=0", + "so:rlm_exec.so=0", + "so:rlm_expiration.so=0", + "so:rlm_expr.so=0", + "so:rlm_files.so=0", + "so:rlm_ippool.so=0", + "so:rlm_linelog.so=0", + "so:rlm_logintime.so=0", + "so:rlm_mschap.so=0", + "so:rlm_otp.so=0", + "so:rlm_pap.so=0", + "so:rlm_passwd.so=0", + "so:rlm_preprocess.so=0", + "so:rlm_radutmp.so=0", + "so:rlm_realm.so=0", + "so:rlm_replicate.so=0", + "so:rlm_soh.so=0", + "so:rlm_sometimes.so=0", + "so:rlm_test.so=0", + "so:rlm_unix.so=0", + "so:rlm_unpack.so=0", + "so:rlm_utf8.so=0", + "so:rlm_wimax.so=0", + "cmd:dhcpclient", + "cmd:map_unit", + "cmd:rad_counter", + "cmd:radattr", + "cmd:radcrypt", + "cmd:raddebug", + "cmd:radiusd", + "cmd:radlast", + "cmd:radmin", + "cmd:radsniff", + "cmd:radsqlrelay", + "cmd:radtest", + "cmd:radwho", + "cmd:radzap", + "cmd:rlm_ippool_tool", + "cmd:smbencrypt" + ] + }, + "gpsd": { + "versions": { + "3.16-r3": 1510311401 + }, + "origin": "gpsd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgps.so.22=22.0.0", + "cmd:gpsctl", + "cmd:gpsd", + "cmd:gpsdctl", + "cmd:gpsdecode", + "cmd:gpspipe" + ] + }, + "s6-rc-dev": { + "versions": { + "0.2.1.2-r0": 1509492618 + }, + "origin": "s6-rc", + "dependencies": [ + "s6-rc=0.2.1.2-r0" + ] + }, + "libglade": { + "versions": { + "2.6.4-r14": 1510071791 + }, + "origin": "libglade", + "dependencies": [ + "libxml2-utils", + "/bin/sh", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libglade-2.0.so.0=0.0.7", + "cmd:libglade-convert" + ] + }, + "libdaemon-dev": { + "versions": { + "0.14-r2": 1509465393 + }, + "origin": "libdaemon", + "dependencies": [ + "libdaemon=0.14-r2", + "pkgconfig" + ], + "provides": [ + "pc:libdaemon=0.14" + ] + }, + "xf86-video-ati": { + "versions": { + "7.10.0-r1": 1511981941 + }, + "origin": "xf86-video-ati", + "dependencies": [ + "mesa-dri-ati", + "so:libc.musl-x86_64.so.1", + "so:libdrm_radeon.so.1", + "so:libpciaccess.so.0", + "so:libudev.so.1" + ] + }, + "lua5.1-expat": { + "versions": { + "1.3.0-r2": 1509483364 + }, + "origin": "lua-expat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ] + }, + "e2fsprogs-extra": { + "versions": { + "1.43.7-r0": 1509469275, + "1.43.7-r1": 1571322245 + }, + "origin": "e2fsprogs", + "dependencies": [ + "e2fsprogs", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libe2p.so.2", + "so:libext2fs.so.2", + "so:libss.so.2", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:badblocks", + "cmd:chattr", + "cmd:debugfs", + "cmd:dumpe2fs", + "cmd:e2freefrag", + "cmd:e2image", + "cmd:e2label", + "cmd:e2undo", + "cmd:e4crypt", + "cmd:e4defrag", + "cmd:filefrag", + "cmd:logsave", + "cmd:lsattr", + "cmd:mklost+found", + "cmd:resize2fs", + "cmd:tune2fs" + ] + }, + "libxfce4util-doc": { + "versions": { + "4.12.1-r4": 1509468547 + }, + "origin": "libxfce4util" + }, + "bitlbee-dev": { + "versions": { + "3.5.1-r1": 1510259355 + }, + "origin": "bitlbee", + "dependencies": [ + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:bitlbee=3.5.1" + ] + }, + "xrandr": { + "versions": { + "1.5.0-r1": 1509491917 + }, + "origin": "xrandr", + "dependencies": [ + "so:libX11.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xrandr" + ] + }, + "libiptcdata-dev": { + "versions": { + "1.0.4-r2": 1509496039 + }, + "origin": "libiptcdata", + "dependencies": [ + "libiptcdata=1.0.4-r2", + "pkgconfig" + ], + "provides": [ + "pc:libiptcdata=1.0.4" + ] + }, + "swish-e": { + "versions": { + "2.4.7-r8": 1509483303 + }, + "origin": "swish-e", + "dependencies": [ + "perl-html-parser", + "perl-html-tagset", + "perl-libwww", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libswish-e.so.2=2.0.0", + "cmd:swish-e", + "cmd:swish-filter-test" + ] + }, + "zfs-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503664 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "postgresql-bdr-dev": { + "versions": { + "9.4.10_p1-r3": 1510259283 + }, + "origin": "postgresql-bdr", + "dependencies": [ + "readline-dev", + "libressl-dev", + "zlib-dev", + "libxml2-dev", + "pkgconfig", + "postgresql-bdr=9.4.10_p1-r3" + ], + "provides": [ + "pc:libecpg=9.4.10", + "pc:libecpg_compat=9.4.10", + "pc:libpgtypes=9.4.10", + "pc:libpq=9.4.10" + ] + }, + "py-markupsafe": { + "versions": { + "1.0-r0": 1509476745 + }, + "origin": "py-markupsafe" + }, + "orage-lang": { + "versions": { + "4.12.1-r1": 1510073567 + }, + "origin": "orage", + "dependencies": [ + "hicolor-icon-theme" + ] + }, + "readline-dev": { + "versions": { + "7.0.003-r0": 1509456788 + }, + "origin": "readline", + "dependencies": [ + "libhistory=7.0.003-r0", + "readline=7.0.003-r0" + ] + }, + "ruby": { + "versions": { + "2.4.6-r0": 1557166824 + }, + "origin": "ruby", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.4" + ], + "provides": [ + "cmd:erb", + "cmd:gem", + "cmd:ruby" + ] + }, + "freeradius-postgresql": { + "versions": { + "3.0.15-r4": 1556202796, + "3.0.15-r5": 1566310604 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius-sql=3.0.15-r5", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "freeradius3-postgresql=3.0.15-r5", + "so:rlm_sql_postgresql.so=0" + ] + }, + "ethtool-doc": { + "versions": { + "4.13-r0": 1509492545 + }, + "origin": "ethtool" + }, + "libnet-dev": { + "versions": { + "1.1.6-r2": 1509481427 + }, + "origin": "libnet", + "dependencies": [ + "libnet=1.1.6-r2" + ], + "provides": [ + "cmd:libnet-config" + ] + }, + "libiec61883-dev": { + "versions": { + "1.2.0-r1": 1509470082 + }, + "origin": "libiec61883", + "dependencies": [ + "libraw1394-dev", + "libiec61883=1.2.0-r1", + "pc:libraw1394", + "pkgconfig" + ], + "provides": [ + "pc:libiec61883=1.2.0" + ] + }, + "py-egenix-mx-base-dev": { + "versions": { + "3.2.9-r0": 1509474195 + }, + "origin": "py-egenix-mx-base" + }, + "perl-device-serialport": { + "versions": { + "1.04-r10": 1509489402 + }, + "origin": "perl-device-serialport", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:modemtest" + ] + }, + "perl-date-manip-doc": { + "versions": { + "6.60-r0": 1509492186 + }, + "origin": "perl-date-manip" + }, + "openldap-overlay-sssvlv": { + "versions": { + "2.4.45-r3": 1510258135, + "2.4.46-r0": 1565073941, + "2.4.48-r0": 1566900518 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "rrdtool-doc": { + "versions": { + "1.5.6-r3": 1512296349 + }, + "origin": "rrdtool" + }, + "libnetfilter_cttimeout": { + "versions": { + "1.0.0-r0": 1509469233 + }, + "origin": "libnetfilter_cttimeout", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0" + ], + "provides": [ + "so:libnetfilter_cttimeout.so.1=1.0.0" + ] + }, + "tslib-dev": { + "versions": { + "1.14-r0": 1510573237 + }, + "origin": "tslib", + "dependencies": [ + "pkgconfig", + "tslib=1.14-r0" + ], + "provides": [ + "pc:tslib=1.14" + ] + }, + "py-httplib2": { + "versions": { + "0.10.3-r0": 1509476702 + }, + "origin": "py-httplib2" + }, + "perl-crypt-openssl-rsa": { + "versions": { + "0.28-r9": 1510261379 + }, + "origin": "perl-crypt-openssl-rsa", + "dependencies": [ + "perl", + "perl-crypt-openssl-random", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "libxv-doc": { + "versions": { + "1.0.11-r1": 1509467171 + }, + "origin": "libxv" + }, + "menu-cache-dev": { + "versions": { + "0.5.1-r1": 1509469820 + }, + "origin": "menu-cache", + "dependencies": [ + "menu-cache=0.5.1-r1", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libmenu-cache=0.5.1" + ] + }, + "squark-doc": { + "versions": { + "0.6.1-r1": 1509492123 + }, + "origin": "squark" + }, + "file-doc": { + "versions": { + "5.32-r0": 1509461605, + "5.32-r1": 1563340161, + "5.32-r2": 1572348414 + }, + "origin": "file" + }, + "py-twisted-doc": { + "versions": { + "17.1.0-r0": 1509493297 + }, + "origin": "py-twisted" + }, + "gucharmap": { + "versions": { + "3.18.2-r0": 1510074175 + }, + "origin": "gucharmap", + "dependencies": [ + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0" + ], + "provides": [ + "so:libgucharmap_2_90.so.7=7.0.0", + "cmd:charmap", + "cmd:gnome-character-map", + "cmd:gucharmap" + ] + }, + "gcalcli": { + "versions": { + "3.4.0-r1": 1509551848 + }, + "origin": "gcalcli", + "dependencies": [ + "python3", + "py-google-api-python-client", + "py-dateutil", + "py-gflags", + "py-vobject" + ], + "provides": [ + "cmd:gcalcli" + ] + }, + "gtk+3.0": { + "versions": { + "3.22.21-r0": 1510067781 + }, + "origin": "gtk+3.0", + "dependencies": [ + "shared-mime-info", + "gtk-update-icon-cache", + "/bin/sh", + "so:libX11.so.6", + "so:libXcomposite.so.1", + "so:libXcursor.so.1", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXi.so.6", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libatk-1.0.so.0", + "so:libatk-bridge-2.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo-gobject.so.2", + "so:libcairo.so.2", + "so:libcups.so.2", + "so:libepoxy.so.0", + "so:libfontconfig.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libharfbuzz.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpangoft2-1.0.so.0" + ], + "provides": [ + "so:libgailutil-3.so.0=0.0.0", + "so:libgdk-3.so.0=0.2200.21", + "so:libgtk-3.so.0=0.2200.21", + "cmd:gtk-builder-tool", + "cmd:gtk-encode-symbolic-svg", + "cmd:gtk-launch", + "cmd:gtk-query-immodules-3.0", + "cmd:gtk-query-settings", + "cmd:gtk3-demo", + "cmd:gtk3-demo-application", + "cmd:gtk3-icon-browser", + "cmd:gtk3-widget-factory" + ] + }, + "hwdata": { + "versions": { + "0.305-r0": 1509468678 + }, + "origin": "hwdata", + "dependencies": [ + "hwdata-usb", + "hwdata-pci", + "hwdata-pnp", + "hwdata-oui" + ] + }, + "gamin-dev": { + "versions": { + "0.1.10-r10": 1509481666 + }, + "origin": "gamin", + "dependencies": [ + "gamin=0.1.10-r10", + "pkgconfig" + ], + "provides": [ + "pc:gamin=0.1.10" + ] + }, + "ulogd": { + "versions": { + "2.0.5-r4": 1509480400 + }, + "origin": "ulogd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0", + "so:libnetfilter_acct.so.1", + "so:libnetfilter_conntrack.so.3", + "so:libnetfilter_log.so.1", + "so:libnfnetlink.so.0" + ], + "provides": [ + "cmd:ulogd" + ] + }, + "damageproto": { + "versions": { + "1.2.1-r3": 1509464676 + }, + "origin": "damageproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:damageproto=1.2.1" + ] + }, + "ncurses": { + "versions": { + "6.0_p20171125-r1": 1534862983 + }, + "origin": "ncurses", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:captoinfo", + "cmd:clear", + "cmd:infocmp", + "cmd:infotocap", + "cmd:reset", + "cmd:tabs", + "cmd:tic", + "cmd:toe", + "cmd:tput", + "cmd:tset" + ] + }, + "mcabber": { + "versions": { + "1.1.0-r0": 1510588320 + }, + "origin": "mcabber", + "dependencies": [ + "beep", + "so:libc.musl-x86_64.so.1", + "so:libenchant.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgpgme.so.11", + "so:libidn.so.11", + "so:libloudmouth-1.so.0", + "so:libncursesw.so.6", + "so:libotr.so.2", + "so:libpanelw.so.6" + ], + "provides": [ + "cmd:mcabber" + ] + }, + "geeqie-doc": { + "versions": { + "1.3-r1": 1510075966 + }, + "origin": "geeqie" + }, + "lua5.1-json4": { + "versions": { + "1.0.0-r2": 1509468366 + }, + "origin": "lua-json4" + }, + "graphviz-gtk": { + "versions": { + "2.40.1-r0": 1510066919 + }, + "origin": "graphviz", + "dependencies": [ + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libgvc.so.6", + "so:librsvg-2.so.2" + ], + "provides": [ + "so:libgvplugin_gdk.so.6=6.0.0", + "so:libgvplugin_gtk.so.6=6.0.0", + "so:libgvplugin_rsvg.so.6=6.0.0" + ] + }, + "kamailio-postgres": { + "versions": { + "5.0.7-r0": 1532960874 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "kamailio-db", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5", + "so:libsrdb1.so.1", + "so:libsrdb2.so.1" + ] + }, + "postgresql-bdr-extension": { + "versions": { + "1.0.2-r1": 1510259297 + }, + "origin": "postgresql-bdr-extension", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:bdr_dump", + "cmd:bdr_init_copy", + "cmd:bdr_initial_load" + ] + }, + "py3-httplib2": { + "versions": { + "0.10.3-r0": 1509476701 + }, + "origin": "py-httplib2", + "dependencies": [ + "python3" + ] + }, + "py-templayer": { + "versions": { + "1.5.1-r3": 1509489479 + }, + "origin": "py-templayer", + "dependencies": [ + "python2" + ] + }, + "acf-mariadb": { + "versions": { + "0.2.0-r2": 1510068620 + }, + "origin": "acf-mariadb", + "dependencies": [ + "acf-core", + "mariadb", + "mariadb-client", + "lua-sql-mysql", + "acf-db-lib" + ] + }, + "uwsgi-msgpack": { + "versions": { + "2.0.17-r0": 1522154656 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "liboping-dev": { + "versions": { + "1.10.0-r0": 1509482706 + }, + "origin": "liboping", + "dependencies": [ + "liboping=1.10.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:liboping=1.10.0" + ] + }, + "speexdsp-dev": { + "versions": { + "1.2_rc3-r4": 1509473229 + }, + "origin": "speexdsp", + "dependencies": [ + "pkgconfig", + "speexdsp=1.2_rc3-r4" + ] + }, + "varnish-doc": { + "versions": { + "5.2.1-r0": 1511265090 + }, + "origin": "varnish" + }, + "libarchive-dev": { + "versions": { + "3.3.2-r2": 1510258000, + "3.3.3-r0": 1566312164, + "3.3.3-r1": 1572677952 + }, + "origin": "libarchive", + "dependencies": [ + "libarchive=3.3.3-r1", + "pkgconfig" + ], + "provides": [ + "pc:libarchive=3.3.3" + ] + }, + "e2fsprogs-doc": { + "versions": { + "1.43.7-r0": 1509469274, + "1.43.7-r1": 1571322243 + }, + "origin": "e2fsprogs" + }, + "libmp3splt-dev": { + "versions": { + "0.9.2-r0": 1509475937 + }, + "origin": "libmp3splt", + "dependencies": [ + "pcre-dev", + "libogg-dev", + "libmad-dev", + "libvorbis-dev", + "libid3tag-dev", + "libtool", + "libmp3splt=0.9.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmp3splt=0.9.2" + ] + }, + "apache-mod-auth-ntlm-winbind": { + "versions": { + "0.1-r6": 1509494593 + }, + "origin": "apache-mod-auth-ntlm-winbind", + "dependencies": [ + "apache2", + "samba", + "samba-winbind", + "so:libc.musl-x86_64.so.1" + ] + }, + "mt-st-doc": { + "versions": { + "1.1-r4": 1509496047 + }, + "origin": "mt-st" + }, + "xfce4-taskmanager": { + "versions": { + "1.2.0-r0": 1510074648 + }, + "origin": "xfce4-taskmanager", + "dependencies": [ + "so:libX11.so.6", + "so:libXmu.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:xfce4-taskmanager" + ] + }, + "lua5.3-ldap": { + "versions": { + "1.2.3-r5": 1509485733 + }, + "origin": "lua-ldap", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ] + }, + "perl-encode-dev": { + "versions": { + "2.93-r0": 1511889457 + }, + "origin": "perl-encode", + "dependencies": [ + "perl-encode-utils" + ], + "provides": [ + "cmd:enc2xs" + ] + }, + "libmnl": { + "versions": { + "1.0.4-r0": 1509469219 + }, + "origin": "libmnl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmnl.so.0=0.2.0" + ] + }, + "zlib-doc": { + "versions": { + "1.2.11-r1": 1509456390 + }, + "origin": "zlib" + }, + "proxychains-ng": { + "versions": { + "4.12-r0": 1509482460 + }, + "origin": "proxychains-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libproxychains4.so=0", + "cmd:proxychains", + "cmd:proxychains4" + ] + }, + "libvdpau": { + "versions": { + "1.1.1-r1": 1509467160 + }, + "origin": "libvdpau", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libvdpau.so.1=1.0.0" + ] + }, + "sysfsutils-doc": { + "versions": { + "2.1.0-r8": 1509475365 + }, + "origin": "sysfsutils" + }, + "libxv-dev": { + "versions": { + "1.0.11-r1": 1509467170 + }, + "origin": "libxv", + "dependencies": [ + "libxv=1.0.11-r1", + "pc:videoproto", + "pc:x11", + "pc:xext", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xv=1.0.11" + ] + }, + "ccache": { + "versions": { + "3.3.4-r0": 1509494162 + }, + "origin": "ccache", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:ccache" + ] + }, + "nmap-nping": { + "versions": { + "7.60-r2": 1510261467, + "7.60-r3": 1572297245 + }, + "origin": "nmap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libpcap.so.1", + "so:libssl.so.44", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:nping" + ] + }, + "fluxbox-doc": { + "versions": { + "1.3.7-r0": 1509491166 + }, + "origin": "fluxbox" + }, + "py-idna": { + "versions": { + "2.6-r0": 1509476382 + }, + "origin": "py-idna" + }, + "collectd-curl": { + "versions": { + "5.7.2-r0": 1510069644 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libxml2.so.2", + "so:libyajl.so.2" + ] + }, + "aria2-doc": { + "versions": { + "1.33.1-r1": 1548941587 + }, + "origin": "aria2" + }, + "xf86-video-sis": { + "versions": { + "0.10.9-r0": 1510075908 + }, + "origin": "xf86-video-sis", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "net-snmp-doc": { + "versions": { + "5.7.3-r10": 1510259616 + }, + "origin": "net-snmp" + }, + "kamailio-lua": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ] + }, + "perl-date-extract-doc": { + "versions": { + "0.06-r1": 1510861954 + }, + "origin": "perl-date-extract" + }, + "acf-openssl": { + "versions": { + "0.10.1-r3": 1510070643 + }, + "origin": "acf-openssl", + "dependencies": [ + "acf-core", + "libressl" + ] + }, + "mlmmj": { + "versions": { + "1.3.0-r0": 1509492629 + }, + "origin": "mlmmj", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mlmmj-bounce", + "cmd:mlmmj-list", + "cmd:mlmmj-maintd", + "cmd:mlmmj-make-ml", + "cmd:mlmmj-make-ml.sh", + "cmd:mlmmj-process", + "cmd:mlmmj-receive", + "cmd:mlmmj-recieve", + "cmd:mlmmj-send", + "cmd:mlmmj-sub", + "cmd:mlmmj-unsub" + ] + }, + "musl-utils": { + "versions": { + "1.1.18-r3": 1518031143, + "1.1.18-r4": 1565163296 + }, + "origin": "musl", + "dependencies": [ + "!uclibc-utils", + "scanelf", + "musl=1.1.18-r4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:getconf", + "cmd:getent", + "cmd:iconv", + "cmd:ldconfig" + ] + }, + "lua-subprocess": { + "versions": { + "0.0.20141229-r2": 1509468278 + }, + "origin": "lua-subprocess" + }, + "uwsgi-router_cache": { + "versions": { + "2.0.17-r0": 1522154657 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "patch-doc": { + "versions": { + "2.7.5-r2": 1519825694, + "2.7.5-r3": 1563906800, + "2.7.6-r0": 1565250814 + }, + "origin": "patch" + }, + "libsecret-dev": { + "versions": { + "0.18.5-r0": 1509483959 + }, + "origin": "libsecret", + "dependencies": [ + "glib-dev", + "libsecret=0.18.5-r0", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libsecret-1=0.18.5", + "pc:libsecret-unstable=0.18.5" + ] + }, + "gengetopt": { + "versions": { + "2.22.6-r2": 1509488509 + }, + "origin": "gengetopt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:gengetopt" + ] + }, + "npth-dev": { + "versions": { + "1.5-r1": 1509472897 + }, + "origin": "npth", + "dependencies": [ + "npth=1.5-r1" + ], + "provides": [ + "cmd:npth-config" + ] + }, + "lua5.3-mqtt-publish": { + "versions": { + "0.1-r0": 1509462505 + }, + "origin": "lua-mqtt-publish", + "dependencies": [ + "lua5.3-mosquitto" + ] + }, + "heimdal": { + "versions": { + "7.4.0-r2": 1514545901, + "7.4.0-r3": 1559659765, + "7.4.0-r4": 1562862337 + }, + "origin": "heimdal", + "dependencies": [ + "krb5-conf", + "so:libasn1.so.8", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libgssapi.so.3", + "so:libhcrypto.so.4", + "so:libhdb.so.9", + "so:libheimbase.so.1", + "so:libheimntlm.so.0", + "so:libhx509.so.5", + "so:libkadm5clnt.so.7", + "so:libkadm5srv.so.8", + "so:libkafs.so.0", + "so:libkdc.so.2", + "so:libkrb5.so.26", + "so:libotp.so.0", + "so:libreadline.so.7", + "so:libroken.so.18", + "so:libsl.so.0", + "so:libwind.so.0" + ], + "provides": [ + "cmd:afslog", + "cmd:bsearch", + "cmd:gsstool", + "cmd:heimdal", + "cmd:heimtools", + "cmd:hprop", + "cmd:hpropd", + "cmd:hxtool", + "cmd:idn-lookup", + "cmd:iprop-log", + "cmd:ipropd-master", + "cmd:ipropd-slave", + "cmd:kadmin", + "cmd:kadmind", + "cmd:kcm", + "cmd:kdc", + "cmd:kdestroy", + "cmd:kf", + "cmd:kfd", + "cmd:kgetcred", + "cmd:kimpersonate", + "cmd:kinit", + "cmd:klist", + "cmd:kpasswd", + "cmd:kpasswdd", + "cmd:kstash", + "cmd:kswitch", + "cmd:ktutil", + "cmd:otp", + "cmd:otpprint", + "cmd:pagsh", + "cmd:su" + ] + }, + "screen-doc": { + "versions": { + "4.6.2-r0": 1509492558 + }, + "origin": "screen" + }, + "perl-module-runtime-doc": { + "versions": { + "0.014-r1": 1509470561 + }, + "origin": "perl-module-runtime" + }, + "gnats-doc": { + "versions": { + "4.2.0-r3": 1509495999 + }, + "origin": "gnats" + }, + "libmng": { + "versions": { + "2.0.3-r1": 1509480914 + }, + "origin": "libmng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libmng.so.2=2.0.2" + ] + }, + "graphite2-dev": { + "versions": { + "1.3.10-r0": 1509464858 + }, + "origin": "graphite2", + "dependencies": [ + "freetype-dev", + "graphite2=1.3.10-r0", + "pkgconfig" + ], + "provides": [ + "pc:graphite2=3.0.1" + ] + }, + "cgit-doc": { + "versions": { + "1.1-r3": 1533395015 + }, + "origin": "cgit" + }, + "pianobar-doc": { + "versions": { + "2017.08.30-r0": 1510074140 + }, + "origin": "pianobar" + }, + "xfce4-session-lang": { + "versions": { + "4.12.1-r2": 1510074577 + }, + "origin": "xfce4-session", + "dependencies": [ + "hicolor-icon-theme", + "iceauth", + "dbus-x11" + ] + }, + "eggdrop-logs2html": { + "versions": { + "1.6.21-r2": 1509491903 + }, + "origin": "eggdrop", + "dependencies": [ + "tcl", + "so:libc.musl-x86_64.so.1" + ] + }, + "libnfnetlink": { + "versions": { + "1.0.1-r1": 1509469211 + }, + "origin": "libnfnetlink", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnfnetlink.so.0=0.2.0" + ] + }, + "xscreensaver": { + "versions": { + "5.36-r0": 1510074358 + }, + "origin": "xscreensaver", + "dependencies": [ + "bc", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXinerama.so.1", + "so:libXmu.so.6", + "so:libXrandr.so.2", + "so:libXt.so.6", + "so:libXxf86vm.so.1", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgdk_pixbuf_xlib-2.0.so.0", + "so:libglade-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:xscreensaver", + "cmd:xscreensaver-command", + "cmd:xscreensaver-demo", + "cmd:xscreensaver-getimage", + "cmd:xscreensaver-getimage-file", + "cmd:xscreensaver-getimage-video", + "cmd:xscreensaver-text" + ] + }, + "py3-mccabe": { + "versions": { + "0.6.1-r1": 1511661235 + }, + "origin": "py-mccabe", + "dependencies": [ + "python3" + ] + }, + "qemu-ppc64le": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-ppc64le" + ] + }, + "xspice": { + "versions": { + "0.1.5-r3": 1510073031 + }, + "origin": "xf86-video-qxl", + "dependencies": [ + "python2", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libspice-server.so.1" + ], + "provides": [ + "cmd:Xspice" + ] + }, + "lua-ldap": { + "versions": { + "1.2.3-r5": 1509485734 + }, + "origin": "lua-ldap" + }, + "icu-libs": { + "versions": { + "59.1-r1": 1509464844 + }, + "origin": "icu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libicudata.so.59=59.1", + "so:libicui18n.so.59=59.1", + "so:libicuio.so.59=59.1", + "so:libicutest.so.59=59.1", + "so:libicutu.so.59=59.1", + "so:libicuuc.so.59=59.1" + ] + }, + "py-irc": { + "versions": { + "8.5.1-r0": 1509491083 + }, + "origin": "py-irc" + }, + "musl": { + "versions": { + "1.1.18-r3": 1518031143, + "1.1.18-r4": 1565163296 + }, + "origin": "musl", + "provides": [ + "so:libc.musl-x86_64.so.1=1" + ] + }, + "libmilter-dev": { + "versions": { + "1.0.2-r5": 1509479368 + }, + "origin": "libmilter", + "dependencies": [ + "libmilter=1.0.2-r5" + ] + }, + "perl-hash-multivalue-doc": { + "versions": { + "0.16-r0": 1511871381 + }, + "origin": "perl-hash-multivalue" + }, + "lua5.2-sqlite": { + "versions": { + "0.9.4-r2": 1509482116 + }, + "origin": "lua-sqlite", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "xclip-doc": { + "versions": { + "0.13-r0": 1509495878 + }, + "origin": "xclip" + }, + "gvfs-dav": { + "versions": { + "1.34.1-r0": 1511430258, + "1.34.1-r1": 1563787224 + }, + "origin": "gvfs", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-glib.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgvfscommon.so", + "so:libgvfsdaemon.so", + "so:libintl.so.8", + "so:libsoup-2.4.so.1", + "so:libxml2.so.2" + ] + }, + "py-django-djblets": { + "versions": { + "0.6.31-r0": 1509475948 + }, + "origin": "py-django-djblets", + "dependencies": [ + "python2", + "py-django-pipeline" + ] + }, + "irssi": { + "versions": { + "1.0.6-r0": 1519052408, + "1.0.8-r0": 1562236917 + }, + "origin": "irssi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libncursesw.so.6", + "so:libssl.so.44" + ], + "provides": [ + "cmd:irssi" + ] + }, + "mercurial-doc": { + "versions": { + "4.5.2-r0": 1532937167, + "4.5.2-r1": 1563792023 + }, + "origin": "mercurial" + }, + "s6-portable-utils-doc": { + "versions": { + "2.2.1.1-r0": 1509488754 + }, + "origin": "s6-portable-utils" + }, + "py2-yaml": { + "versions": { + "3.12-r1": 1509485804 + }, + "origin": "py-yaml", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libyaml-0.so.2" + ] + }, + "tcl-dev": { + "versions": { + "8.6.7-r0": 1509460298 + }, + "origin": "tcl", + "dependencies": [ + "tcl", + "pc:zlib>=1.2.3", + "pkgconfig" + ], + "provides": [ + "pc:tcl=8.6.7" + ] + }, + "cryptsetup-doc": { + "versions": { + "1.7.5-r1": 1510261295 + }, + "origin": "cryptsetup" + }, + "sems-diameter_client": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libssl.so.44", + "so:libstdc++.so.6" + ] + }, + "st": { + "versions": { + "0.7-r1": 1509494026 + }, + "origin": "st", + "dependencies": [ + "ncurses-terminfo", + "so:libX11.so.6", + "so:libXft.so.2", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1" + ], + "provides": [ + "cmd:st" + ] + }, + "lucene++-dev": { + "versions": { + "3.0.7-r5": 1509496353 + }, + "origin": "lucene++", + "dependencies": [ + "boost-dev", + "lucene++=3.0.7-r5", + "pkgconfig" + ], + "provides": [ + "pc:liblucene++-contrib=3.0.7", + "pc:liblucene++=3.0.7" + ] + }, + "confuse-dev": { + "versions": { + "3.2.1-r0": 1509471872 + }, + "origin": "confuse", + "dependencies": [ + "confuse=3.2.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libconfuse=3.2.1" + ] + }, + "sshpass-doc": { + "versions": { + "1.06-r0": 1509494947 + }, + "origin": "sshpass" + }, + "krb5-server": { + "versions": { + "1.15.4-r0": 1548491443 + }, + "origin": "krb5", + "dependencies": [ + "libverto-libev", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libgssapi_krb5.so.2", + "so:libgssrpc.so.4", + "so:libk5crypto.so.3", + "so:libkadm5clnt_mit.so.11", + "so:libkadm5srv_mit.so.11", + "so:libkdb5.so.8", + "so:libkdb_ldap.so.1", + "so:libkrb5.so.3", + "so:libkrb5support.so.0", + "so:libss.so.2", + "so:libverto.so.1" + ], + "provides": [ + "cmd:gss-server", + "cmd:kadmin.local", + "cmd:kadmind", + "cmd:kdb5_ldap_util", + "cmd:kdb5_util", + "cmd:kprop", + "cmd:kpropd", + "cmd:kproplog", + "cmd:krb5-send-pr", + "cmd:krb5kdc", + "cmd:sclient", + "cmd:sim_server", + "cmd:sserver", + "cmd:uuserver" + ] + }, + "librtmp": { + "versions": { + "2.4_git20160909-r3": 1510259117 + }, + "origin": "rtmpdump", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:librtmp.so.1=1" + ] + }, + "lua-crypto": { + "versions": { + "0.3.2-r5": 1510261401 + }, + "origin": "lua-crypto" + }, + "aspell-en": { + "versions": { + "2017.01.22-r0": 1509496074 + }, + "origin": "aspell-en" + }, + "nagios-plugins-log": { + "versions": { + "2.2.1-r3": 1510288496 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "grep" + ] + }, + "atop-doc": { + "versions": { + "2.3.0-r1": 1509489334 + }, + "origin": "atop" + }, + "yaml": { + "versions": { + "0.1.7-r0": 1509470097 + }, + "origin": "yaml", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libyaml-0.so.2=2.0.5" + ] + }, + "newsbeuter-lang": { + "versions": { + "2.9-r6": 1510288209 + }, + "origin": "newsbeuter" + }, + "samba-dev": { + "versions": { + "4.7.6-r3": 1555491781 + }, + "origin": "samba", + "dependencies": [ + "libsmbclient=4.7.6-r3", + "libwbclient=4.7.6-r3", + "pc:ldb", + "pc:talloc", + "pc:tevent", + "pkgconfig", + "samba-client-libs=4.7.6-r3", + "samba-common-libs=4.7.6-r3", + "samba-common-server-libs=4.7.6-r3", + "samba-dc-libs=4.7.6-r3", + "samba-libnss-winbind=4.7.6-r3", + "samba-libs=4.7.6-r3" + ], + "provides": [ + "pc:dcerpc=0.0.1", + "pc:dcerpc_samr=0.0.1", + "pc:dcerpc_server=0.0.1", + "pc:ndr=0.1.0", + "pc:ndr_krb5pac=0.0.1", + "pc:ndr_nbt=0.0.1", + "pc:ndr_standard=0.0.1", + "pc:netapi=0", + "pc:samba-credentials=0.0.1", + "pc:samba-hostconfig=0.0.1", + "pc:samba-policy=0.0.1", + "pc:samba-util=0.0.1", + "pc:samdb=0.0.1", + "pc:smbclient=0.2.3", + "pc:wbclient=0.14" + ] + }, + "gnokii-doc": { + "versions": { + "0.6.31-r6": 1510069844 + }, + "origin": "gnokii" + }, + "cegui06": { + "versions": { + "0.6.2b-r14": 1510068748 + }, + "origin": "cegui06", + "dependencies": [ + "so:libGL.so.1", + "so:libGLEW.so.2.1", + "so:libGLU.so.1", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libfreetype.so.6", + "so:libgcc_s.so.1", + "so:liblua.so.5", + "so:libpcre.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libCEGUIBase-0.6.2.so=0", + "so:libCEGUIExpatParser-0.6.2.so=0", + "so:libCEGUIFalagardWRBase-0.6.2.so=0", + "so:libCEGUILuaScriptModule-0.6.2.so=0", + "so:libCEGUIOpenGLRenderer-0.6.2.so=0", + "so:libCEGUITGAImageCodec-0.6.2.so=0", + "so:libCEGUITinyXMLParser-0.6.2.so=0", + "so:libCEGUItoluapp-0.6.2.so=0" + ] + }, + "dropbear-scp": { + "versions": { + "2018.76-r2": 1537439117 + }, + "origin": "dropbear", + "dependencies": [ + "!openssh-client", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:scp" + ] + }, + "byobu-doc": { + "versions": { + "5.123-r1": 1509470002 + }, + "origin": "byobu" + }, + "fontconfig-dev": { + "versions": { + "2.12.6-r0": 1509462083 + }, + "origin": "fontconfig", + "dependencies": [ + "fontconfig=2.12.6-r0", + "pc:expat", + "pc:freetype2", + "pkgconfig" + ], + "provides": [ + "pc:fontconfig=2.12.6" + ] + }, + "mysql-bench": { + "versions": { + "10.1.38-r1": 1551187995, + "10.1.40-r0": 1560354890, + "10.1.41-r0": 1565163115 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-client" + ] + }, + "statserial": { + "versions": { + "1.1-r4": 1509491852 + }, + "origin": "statserial", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:statserial" + ] + }, + "perl-test-failwarnings-doc": { + "versions": { + "0.008-r1": 1510855678 + }, + "origin": "perl-test-failwarnings" + }, + "libburn-doc": { + "versions": { + "1.4.8-r0": 1509462200 + }, + "origin": "libburn" + }, + "gnokii-dev": { + "versions": { + "0.6.31-r6": 1510069842 + }, + "origin": "gnokii", + "dependencies": [ + "gnokii-libs=0.6.31-r6", + "pkgconfig" + ], + "provides": [ + "pc:gnokii=0.6.31", + "pc:xgnokii=1.0" + ] + }, + "libxml2-utils": { + "versions": { + "2.9.8-r1": 1540398579 + }, + "origin": "libxml2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:xmlcatalog", + "cmd:xmllint" + ] + }, + "perl-role-basic-doc": { + "versions": { + "0.13-r0": 1509468425 + }, + "origin": "perl-role-basic" + }, + "xcmsdb-doc": { + "versions": { + "1.0.5-r0": 1509494048 + }, + "origin": "xcmsdb" + }, + "py3-gunicorn": { + "versions": { + "19.7.1-r1": 1509493758 + }, + "origin": "py-gunicorn", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:gunicorn", + "cmd:gunicorn_paster" + ] + }, + "znc-extra": { + "versions": { + "1.7.1-r0": 1531900839, + "1.7.1-r1": 1565877330 + }, + "origin": "znc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "snappy-doc": { + "versions": { + "1.1.4-r2": 1509483342 + }, + "origin": "snappy" + }, + "py-tevent": { + "versions": { + "0.9.34-r0": 1511479236 + }, + "origin": "tevent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libtalloc.so.2", + "so:libtevent.so.0" + ] + }, + "static-routing": { + "versions": { + "1.0-r0": 1509491854 + }, + "origin": "static-routing" + }, + "geeqie-lang": { + "versions": { + "1.3-r1": 1510075967 + }, + "origin": "geeqie" + }, + "perl-html-format-doc": { + "versions": { + "2.11-r0": 1509481563 + }, + "origin": "perl-html-format" + }, + "ntop-dev": { + "versions": { + "5.0.1-r11": 1510314558 + }, + "origin": "ntop", + "dependencies": [ + "ntop=5.0.1-r11" + ] + }, + "libva-dev": { + "versions": { + "1.8.2-r0": 1510071998 + }, + "origin": "libva", + "dependencies": [ + "mesa-dev", + "libva=1.8.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libva-drm=0.40.0", + "pc:libva-egl=0.40.0", + "pc:libva-glx=0.40.0", + "pc:libva-tpi=0.40.0", + "pc:libva-x11=0.40.0", + "pc:libva=0.40.0" + ] + }, + "libxinerama-dev": { + "versions": { + "1.1.3-r1": 1509468244 + }, + "origin": "libxinerama", + "dependencies": [ + "libxinerama=1.1.3-r1", + "pc:x11", + "pc:xext", + "pc:xineramaproto", + "pkgconfig" + ], + "provides": [ + "pc:xinerama=1.1.3" + ] + }, + "perl-namespace-autoclean-doc": { + "versions": { + "0.28-r0": 1509474082 + }, + "origin": "perl-namespace-autoclean" + }, + "hwdata-pci": { + "versions": { + "0.305-r0": 1509468678 + }, + "origin": "hwdata" + }, + "fish-dev": { + "versions": { + "2.6.0-r2": 1511486201 + }, + "origin": "fish", + "dependencies": [ + "fish-tools" + ] + }, + "goffice-doc": { + "versions": { + "0.10.36-r0": 1511455403 + }, + "origin": "goffice" + }, + "lm_sensors-sensord": { + "versions": { + "3.4.0-r4": 1509475444 + }, + "origin": "lm_sensors", + "dependencies": [ + "bash", + "sysfsutils", + "so:libc.musl-x86_64.so.1", + "so:librrd.so.4", + "so:libsensors.so.4" + ], + "provides": [ + "cmd:sensord" + ] + }, + "tiff-doc": { + "versions": { + "4.0.10-r0": 1544169734, + "4.0.10-r1": 1566982293, + "4.0.10-r2": 1572815888 + }, + "origin": "tiff" + }, + "apcupsd-webif": { + "versions": { + "3.14.14-r0": 1509495262 + }, + "origin": "apcupsd", + "dependencies": [ + "util-linux", + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3" + ] + }, + "lxc-lvm": { + "versions": { + "2.1.1-r3": 1533578832 + }, + "origin": "lxc", + "dependencies": [ + "lvm2", + "util-linux", + "lxc" + ] + }, + "perl-fcgi-procmanager-doc": { + "versions": { + "0.28-r0": 1511894069 + }, + "origin": "perl-fcgi-procmanager" + }, + "perl-path-tiny-doc": { + "versions": { + "0.104-r1": 1510855684 + }, + "origin": "perl-path-tiny" + }, + "keybinder": { + "versions": { + "0.3.0-r1": 1510069943 + }, + "origin": "keybinder", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ], + "provides": [ + "so:libkeybinder.so.0=0.1.0" + ] + }, + "cups-libs": { + "versions": { + "2.2.10-r0": 1549287809, + "2.2.12-r0": 1566207591 + }, + "origin": "cups", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30", + "so:libz.so.1" + ], + "provides": [ + "so:libcups.so.2=2", + "so:libcupsimage.so.2=2" + ] + }, + "perl-carp": { + "versions": { + "1.38-r0": 1509483988 + }, + "origin": "perl-carp" + }, + "llvm5-test-utils": { + "versions": { + "5.0.0-r0": 1510682602 + }, + "origin": "llvm5", + "dependencies": [ + "python2", + "py-setuptools", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "llvm-test-utils=5.0.0-r0", + "lit=0.6.0-r0", + "cmd:lit" + ] + }, + "py2-pillow": { + "versions": { + "4.3.0-r0": 1509489958 + }, + "origin": "py-pillow", + "dependencies": [ + "py2-olefile", + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libopenjp2.so.7", + "so:libpython2.7.so.1.0", + "so:libtiff.so.5", + "so:libwebp.so.7", + "so:libwebpmux.so.3", + "so:libz.so.1" + ] + }, + "razor-doc": { + "versions": { + "2.85-r6": 1509491110 + }, + "origin": "razor" + }, + "lua5.2": { + "versions": { + "5.2.4-r4": 1509459695 + }, + "origin": "lua5.2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua-5.2.so.0" + ], + "provides": [ + "cmd:lua5.2", + "cmd:luac5.2" + ] + }, + "lighttpd-mod_auth": { + "versions": { + "1.4.48-r0": 1511925915 + }, + "origin": "lighttpd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libpcre2-32": { + "versions": { + "10.30-r0": 1509461774 + }, + "origin": "pcre2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre2-32.so.0=0.6.0" + ] + }, + "mtdev-dev": { + "versions": { + "1.1.5-r1": 1509475741 + }, + "origin": "mtdev", + "dependencies": [ + "linux-headers", + "mtdev=1.1.5-r1", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libmtdev.so.1" + ], + "provides": [ + "pc:mtdev=1.1.5", + "cmd:mtdev-test" + ] + }, + "freeradius-mssql": { + "versions": { + "3.0.15-r4": 1556202796, + "3.0.15-r5": 1566310603 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius-sql=3.0.15-r5" + ], + "provides": [ + "freeradius3-mssql=3.0.15-r5" + ] + }, + "perl-datetime-format-mail": { + "versions": { + "0.403-r0": 1511386193 + }, + "origin": "perl-datetime-format-mail", + "dependencies": [ + "perl", + "perl-datetime", + "perl-params-validate" + ] + }, + "abiword-plugin-loadbindings": { + "versions": { + "3.0.2-r1": 1510073363 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6", + "so:libxml2.so.2" + ] + }, + "ipptool": { + "versions": { + "2.2.10-r0": 1549287809, + "2.2.12-r0": 1566207592 + }, + "origin": "cups", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2" + ], + "provides": [ + "cmd:ipptool" + ] + }, + "perl-math-round": { + "versions": { + "0.07-r0": 1509483899 + }, + "origin": "perl-math-round", + "dependencies": [ + "perl" + ] + }, + "util-linux-dev": { + "versions": { + "2.31.1-r0": 1541506293 + }, + "origin": "util-linux", + "dependencies": [ + "libblkid=2.31.1-r0", + "libfdisk=2.31.1-r0", + "libmount=2.31.1-r0", + "libsmartcols=2.31.1-r0", + "libuuid=2.31.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:blkid=2.31.1", + "pc:fdisk=2.31.1", + "pc:mount=2.31.1", + "pc:smartcols=2.31.1", + "pc:uuid=2.31.1" + ] + }, + "ppp-pppoe": { + "versions": { + "2.4.7-r5": 1509480131 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pppoe-discovery" + ] + }, + "lua5.3-md5": { + "versions": { + "1.2-r3": 1509468358 + }, + "origin": "lua-md5", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "command-not-found": { + "versions": { + "0.2-r0": 1509688078 + }, + "origin": "command-not-found" + }, + "gettext-asprintf": { + "versions": { + "0.19.8.1-r1": 1509459196 + }, + "origin": "gettext", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libasprintf.so.0=0.0.0" + ] + }, + "ccache-doc": { + "versions": { + "3.3.4-r0": 1509494162 + }, + "origin": "ccache" + }, + "xfce4-notifyd": { + "versions": { + "0.3.6-r0": 1510075459 + }, + "origin": "xfce4-notifyd", + "dependencies": [ + "hicolor-icon-theme", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libnotify.so.4", + "so:libxfce4ui-2.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:xfce4-notifyd-config" + ] + }, + "expat-doc": { + "versions": { + "2.2.5-r0": 1509908121, + "2.2.7-r0": 1561897528, + "2.2.7-r1": 1568353727, + "2.2.8-r0": 1568974114 + }, + "origin": "expat" + }, + "libmng-dev": { + "versions": { + "2.0.3-r1": 1509480913 + }, + "origin": "libmng", + "dependencies": [ + "libjpeg-turbo-dev", + "libmng=2.0.3-r1", + "pkgconfig" + ], + "provides": [ + "pc:libmng=2.0.2" + ] + }, + "ipvsadm-doc": { + "versions": { + "1.29-r0": 1509494681 + }, + "origin": "ipvsadm" + }, + "gcc": { + "versions": { + "6.4.0-r5": 1509458083 + }, + "origin": "gcc", + "dependencies": [ + "binutils", + "isl", + "libgomp=6.4.0-r5", + "libatomic=6.4.0-r5", + "libgomp=6.4.0-r5", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgmp.so.10", + "so:libisl.so.15", + "so:libmpc.so.3", + "so:libmpfr.so.4", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libcc1.so.0=0.0.0", + "so:libcilkrts.so.5=5.0.0", + "so:libitm.so.1=1.0.0", + "pc:libgcj-6=6.4.0", + "cmd:cc", + "cmd:cpp", + "cmd:gcc", + "cmd:gcc-ar", + "cmd:gcc-nm", + "cmd:gcc-ranlib", + "cmd:gcov", + "cmd:gcov-dump", + "cmd:gcov-tool", + "cmd:x86_64-alpine-linux-musl-gcc", + "cmd:x86_64-alpine-linux-musl-gcc-6.4.0", + "cmd:x86_64-alpine-linux-musl-gcc-ar", + "cmd:x86_64-alpine-linux-musl-gcc-nm", + "cmd:x86_64-alpine-linux-musl-gcc-ranlib", + "cmd:x86_64-alpine-linux-musl-gcj" + ] + }, + "libtasn1-dev": { + "versions": { + "4.12-r3": 1519805758, + "4.12-r4": 1563955390 + }, + "origin": "libtasn1", + "dependencies": [ + "libtasn1=4.12-r4", + "pkgconfig" + ], + "provides": [ + "pc:libtasn1=4.12" + ] + }, + "fish": { + "versions": { + "2.6.0-r2": 1511486202 + }, + "origin": "fish", + "dependencies": [ + "bc", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:fish", + "cmd:fish_indent", + "cmd:fish_key_reader" + ] + }, + "freeradius-sqlite": { + "versions": { + "3.0.15-r4": 1556202796, + "3.0.15-r5": 1566310604 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius-sql=3.0.15-r5", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ], + "provides": [ + "freeradius3-sqlite=3.0.15-r5", + "so:rlm_sql_sqlite.so=0" + ] + }, + "libotr3-tools": { + "versions": { + "3.2.1-r4": 1509481190 + }, + "origin": "libotr3", + "dependencies": [ + "libotr3", + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libotr.so.2" + ], + "provides": [ + "cmd:otr_mackey", + "cmd:otr_modify", + "cmd:otr_parse", + "cmd:otr_readforge", + "cmd:otr_remac", + "cmd:otr_sesskeys" + ] + }, + "nagios-plugins-hpjd": { + "versions": { + "2.2.1-r3": 1510288495 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "collectd-ceph": { + "versions": { + "5.7.2-r0": 1510069652 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libyajl.so.2" + ] + }, + "abiword-plugin-xslfo": { + "versions": { + "3.0.2-r1": 1510073370 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "perl-datetime-doc": { + "versions": { + "1.44-r0": 1510859302 + }, + "origin": "perl-datetime" + }, + "xprop": { + "versions": { + "1.2.1-r1": 1509491073 + }, + "origin": "xprop", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xprop" + ] + }, + "libxext-doc": { + "versions": { + "1.3.3-r2": 1509464538 + }, + "origin": "libxext" + }, + "squid-lang-de": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865667 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "avahi-glib": { + "versions": { + "0.6.32-r4": 1509465447, + "0.6.32-r5": 1563345619 + }, + "origin": "avahi", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libavahi-glib.so.1=1.0.2", + "so:libavahi-gobject.so.0=0.0.4" + ] + }, + "xf86-video-tdfx": { + "versions": { + "1.4.7-r0": 1510075839 + }, + "origin": "xf86-video-tdfx", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "nagios-plugins-ldap": { + "versions": { + "2.2.1-r3": 1510288496 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libldap-2.4.so.2" + ] + }, + "nagios-plugins-icmp": { + "versions": { + "2.2.1-r3": 1510288495 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "dovecot-mysql": { + "versions": { + "2.2.36.3-r0": 1554102389, + "2.2.36.4-r0": 1567075098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot-sql", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ], + "provides": [ + "so:libdriver_mysql.so=0" + ] + }, + "boost-signals": { + "versions": { + "1.62.0-r5": 1509465880 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_signals-mt.so.1.62.0=1.62.0", + "so:libboost_signals.so.1.62.0=1.62.0" + ] + }, + "gconf-lang": { + "versions": { + "3.2.6-r1": 1510928342 + }, + "origin": "gconf" + }, + "boost-doc": { + "versions": { + "1.62.0-r5": 1509465873 + }, + "origin": "boost" + }, + "qemu-system-xtensaeb": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-xtensaeb" + ] + }, + "desktop-file-utils": { + "versions": { + "0.23-r0": 1509470841 + }, + "origin": "desktop-file-utils", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:desktop-file-edit", + "cmd:desktop-file-install", + "cmd:desktop-file-validate", + "cmd:update-desktop-database" + ] + }, + "xorriso": { + "versions": { + "1.4.8-r0": 1509462217 + }, + "origin": "libisoburn", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libisoburn.so.1" + ], + "provides": [ + "cmd:mkisofs", + "cmd:osirrox", + "cmd:xorrecord", + "cmd:xorriso", + "cmd:xorrisofs" + ] + }, + "ttf-linux-libertine-doc": { + "versions": { + "5.3.0-r0": 1509469875 + }, + "origin": "ttf-linux-libertine" + }, + "libxfont": { + "versions": { + "1.5.4-r0": 1511976122 + }, + "origin": "libxfont", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfontenc.so.1", + "so:libfreetype.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libXfont.so.1=1.4.1" + ] + }, + "avfs-dev": { + "versions": { + "1.0.5-r0": 1509496597 + }, + "origin": "avfs", + "dependencies": [ + "avfs=1.0.5-r0" + ], + "provides": [ + "cmd:avfs-config" + ] + }, + "libxrender-dev": { + "versions": { + "0.9.10-r2": 1509462094 + }, + "origin": "libxrender", + "dependencies": [ + "libxrender=0.9.10-r2", + "pc:renderproto>=0.9", + "pc:x11", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xrender=0.9.10" + ] + }, + "qemu-system-alpha": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-alpha" + ] + }, + "sendpage": { + "versions": { + "1.0.3-r5": 1509489417 + }, + "origin": "sendpage", + "dependencies": [ + "perl-mailtools", + "perl-net-snpp", + "perl-device-serialport", + "perl-dbi", + "perl-sys-hostname-long", + "perl-test-mockobject" + ], + "provides": [ + "cmd:email2page", + "cmd:sendmail2snpp", + "cmd:sendpage", + "cmd:sendpage-db", + "cmd:snpp" + ] + }, + "pound": { + "versions": { + "2.7-r4": 1510261014 + }, + "origin": "pound", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpcreposix.so.0", + "so:libssl.so.44" + ], + "provides": [ + "cmd:pound", + "cmd:poundctl" + ] + }, + "lua5.3-gversion.lua": { + "versions": { + "0.2.0-r1": 1509475306 + }, + "origin": "lua-gversion", + "dependencies": [ + "lua5.3" + ] + }, + "lua-sqlite": { + "versions": { + "0.9.4-r2": 1509482117 + }, + "origin": "lua-sqlite" + }, + "openldap-overlay-dds": { + "versions": { + "2.4.45-r3": 1510258134, + "2.4.46-r0": 1565073940, + "2.4.48-r0": 1566900517 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "perl-plack": { + "versions": { + "1.0033-r0": 1510588948 + }, + "origin": "perl-plack", + "dependencies": [ + "perl-file-sharedir", + "perl-filesys-notify-simple", + "perl-devel-stacktrace", + "perl-stream-buffered", + "perl-uri", + "perl-devel-stacktrace-ashtml", + "perl-http-body", + "perl-test-tcp", + "perl-try-tiny", + "perl-apache-logformat-compiler", + "perl-hash-multivalue", + "perl-http-message" + ], + "provides": [ + "cmd:plackup" + ] + }, + "xfce4-wavelan-plugin-lang": { + "versions": { + "0.6.0-r0": 1510072789 + }, + "origin": "xfce4-wavelan-plugin" + }, + "rabbitmq-c-dev": { + "versions": { + "0.8.0-r3": 1510260052 + }, + "origin": "rabbitmq-c", + "dependencies": [ + "libressl-dev", + "popt-dev", + "pkgconfig", + "rabbitmq-c=0.8.0-r3" + ], + "provides": [ + "pc:librabbitmq=0.8.0" + ] + }, + "libquvi-doc": { + "versions": { + "0.9.4-r3": 1509492379 + }, + "origin": "libquvi" + }, + "glib-bash-completion": { + "versions": { + "2.54.2-r0": 1509911133, + "2.54.2-r1": 1560764733 + }, + "origin": "glib" + }, + "openobex-doc": { + "versions": { + "1.7.2-r1": 1510071734 + }, + "origin": "openobex" + }, + "libatomic_ops": { + "versions": { + "7.4.8-r0": 1509878778 + }, + "origin": "libatomic_ops", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libatomic_ops.so.1=1.0.4", + "so:libatomic_ops_gpl.so.1=1.0.4" + ] + }, + "iptraf-ng": { + "versions": { + "1.1.4-r4": 1509495004 + }, + "origin": "iptraf-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libpanelw.so.6" + ], + "provides": [ + "cmd:iptraf-ng", + "cmd:rvnamed-ng" + ] + }, + "py-ipaddress": { + "versions": { + "1.0.18-r1": 1509480834 + }, + "origin": "py-ipaddress" + }, + "libgss": { + "versions": { + "0.1.5-r1": 1509493415 + }, + "origin": "libgss", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgss.so.1=1.1.0", + "cmd:gss" + ] + }, + "abuild": { + "versions": { + "3.1.0-r4": 1527832620, + "3.1.0-r5": 1572356050 + }, + "origin": "abuild", + "dependencies": [ + "fakeroot", + "sudo", + "pax-utils", + "libressl", + "apk-tools>=2.0.7-r1", + "libc-utils", + "attr", + "tar", + "pkgconf", + "patch", + "lzip", + "curl", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libz.so.1" + ], + "provides": [ + "cmd:abuild", + "cmd:abuild-addgroup", + "cmd:abuild-adduser", + "cmd:abuild-apk", + "cmd:abuild-fetch", + "cmd:abuild-gzsplit", + "cmd:abuild-keygen", + "cmd:abuild-rmtemp", + "cmd:abuild-sign", + "cmd:abuild-sudo", + "cmd:abuild-tar", + "cmd:abump", + "cmd:apkgrel", + "cmd:buildlab", + "cmd:checkapk", + "cmd:newapkbuild" + ] + }, + "perl-html-tagset": { + "versions": { + "3.20-r1": 1509464387 + }, + "origin": "perl-html-tagset" + }, + "polkit-gnome": { + "versions": { + "0.105-r0": 1510075800 + }, + "origin": "polkit-gnome", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libpolkit-agent-1.so.0", + "so:libpolkit-gobject-1.so.0" + ] + }, + "perl-x10-doc": { + "versions": { + "0.04-r0": 1509494413 + }, + "origin": "perl-x10" + }, + "pekwm-doc": { + "versions": { + "0.1.17-r2": 1509494547 + }, + "origin": "pekwm" + }, + "lm_sensors-doc": { + "versions": { + "3.4.0-r4": 1509475443 + }, + "origin": "lm_sensors" + }, + "grub-efi": { + "versions": { + "2.02-r3": 1509495739 + }, + "origin": "grub", + "dependencies": [ + "grub" + ] + }, + "libraw-doc": { + "versions": { + "0.18.6-r0": 1514450639 + }, + "origin": "libraw" + }, + "pcsc-lite-dev": { + "versions": { + "1.8.22-r0": 1509474497 + }, + "origin": "pcsc-lite", + "dependencies": [ + "eudev-dev", + "pcsc-lite-libs=1.8.22-r0", + "pkgconfig" + ], + "provides": [ + "pc:libpcsclite=1.8.22" + ] + }, + "libxfce4ui-gtk3": { + "versions": { + "4.12.1-r3": 1510067994 + }, + "origin": "libxfce4ui", + "dependencies": [ + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libpango-1.0.so.0", + "so:libstartup-notification-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "so:libxfce4kbd-private-3.so.0=0.0.0", + "so:libxfce4ui-2.so.0=0.0.0" + ] + }, + "liblcms": { + "versions": { + "1.19-r6": 1509477356 + }, + "origin": "lcms", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblcms.so.1=1.0.19" + ] + }, + "nagios-plugins-nagios": { + "versions": { + "2.2.1-r3": 1510288496 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "mcpp-doc": { + "versions": { + "2.7.2-r1": 1509473730 + }, + "origin": "mcpp" + }, + "ncurses-dev": { + "versions": { + "6.0_p20171125-r1": 1534862974 + }, + "origin": "ncurses", + "dependencies": [ + "ncurses-libs=6.0_p20171125-r1", + "pkgconfig" + ], + "provides": [ + "pc:form=6.0.20171125", + "pc:formw=6.0.20171125", + "pc:menu=6.0.20171125", + "pc:menuw=6.0.20171125", + "pc:ncurses=6.0.20171125", + "pc:ncursesw=6.0.20171125", + "pc:panel=6.0.20171125", + "pc:panelw=6.0.20171125", + "cmd:ncursesw6-config" + ] + }, + "abiword-dev": { + "versions": { + "3.0.2-r1": 1510073354 + }, + "origin": "abiword", + "dependencies": [ + "pc:cairo-fc", + "pc:cairo-pdf", + "pc:cairo-ps", + "pc:enchant>=1.2.0", + "pc:fribidi>=0.10.4", + "pc:gio-2.0", + "pc:glib-2.0>=2.6.0", + "pc:gobject-2.0>=2.6.0", + "pc:gthread-2.0>=2.6.0", + "pc:gtk+-3.0>=3.0.8", + "pc:gtk+-unix-print-3.0", + "pc:libgoffice-0.10>=0.10.0", + "pc:libgsf-1>=1.14.18", + "pc:librsvg-2.0>=2.16.0", + "pc:libxslt", + "pc:pangocairo", + "pc:wv-1.0>=1.2.0", + "pc:x11", + "pkgconfig" + ], + "provides": [ + "pc:abiword-3.0=3.0.2" + ] + }, + "liboping-doc": { + "versions": { + "1.10.0-r0": 1509482706 + }, + "origin": "liboping" + }, + "check-dev": { + "versions": { + "0.12.0-r1": 1509461855 + }, + "origin": "check", + "dependencies": [ + "check=0.12.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:check=0.12.0" + ] + }, + "libuv-dbg": { + "versions": { + "1.17.0-r0": 1511652082 + }, + "origin": "libuv" + }, + "nagios-plugins-disk_smb": { + "versions": { + "2.2.1-r3": 1510288494 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "samba-client" + ] + }, + "slang": { + "versions": { + "2.3.1a-r0": 1509476136 + }, + "origin": "slang", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ], + "provides": [ + "so:libslang.so.2=2.3.1", + "cmd:slsh" + ] + }, + "xf86-video-siliconmotion-doc": { + "versions": { + "1.7.9-r0": 1510074837 + }, + "origin": "xf86-video-siliconmotion" + }, + "v4l-utils-doc": { + "versions": { + "1.12.5-r1": 1510072042 + }, + "origin": "v4l-utils" + }, + "lzip": { + "versions": { + "1.19-r1": 1509459871 + }, + "origin": "lzip", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:lzip" + ] + }, + "libffi-dev": { + "versions": { + "3.2.1-r4": 1509458692 + }, + "origin": "libffi", + "dependencies": [ + "libffi=3.2.1-r4", + "pkgconfig" + ], + "provides": [ + "pc:libffi=3.2.1" + ] + }, + "perl-test-warn-doc": { + "versions": { + "0.32-r0": 1509470537 + }, + "origin": "perl-test-warn" + }, + "py-gunicorn": { + "versions": { + "19.7.1-r1": 1509493758 + }, + "origin": "py-gunicorn" + }, + "xf86-video-s3virge": { + "versions": { + "1.10.7-r1": 1510074190 + }, + "origin": "xf86-video-s3virge", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "skytraq-datalogger": { + "versions": { + "0.5.1-r1": 1509474459 + }, + "origin": "skytraq-datalogger", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ], + "provides": [ + "cmd:skytraq-datalogger" + ] + }, + "weechat-python": { + "versions": { + "1.9.1-r1": 1509530223 + }, + "origin": "weechat", + "dependencies": [ + "weechat", + "so:libc.musl-x86_64.so.1" + ] + }, + "uwsgi-zabbix": { + "versions": { + "2.0.17-r0": 1522154661 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "freeradius-ldap": { + "versions": { + "3.0.15-r4": 1556202795, + "3.0.15-r5": 1566310603 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.15-r5", + "so:libc.musl-x86_64.so.1", + "so:libldap-2.4.so.2" + ], + "provides": [ + "freeradius3-ldap=3.0.15-r5", + "so:rlm_ldap.so=0" + ] + }, + "rsync-doc": { + "versions": { + "3.1.3-r0": 1521547982 + }, + "origin": "rsync" + }, + "libxkbcommon-dev": { + "versions": { + "0.7.1-r1": 1509482486 + }, + "origin": "libxkbcommon", + "dependencies": [ + "libxkbcommon=0.7.1-r1", + "pc:xcb", + "pc:xcb-xkb", + "pkgconfig" + ], + "provides": [ + "pc:xkbcommon-x11=0.7.1", + "pc:xkbcommon=0.7.1" + ] + }, + "rabbitmq-c-utils": { + "versions": { + "0.8.0-r3": 1510260051 + }, + "origin": "rabbitmq-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0", + "so:librabbitmq.so.4" + ], + "provides": [ + "cmd:amqp-consume", + "cmd:amqp-declare-queue", + "cmd:amqp-delete-queue", + "cmd:amqp-get", + "cmd:amqp-publish" + ] + }, + "perl-class-tiny": { + "versions": { + "1.006-r0": 1510845710 + }, + "origin": "perl-class-tiny" + }, + "perl-list-someutils-xs-doc": { + "versions": { + "0.55-r0": 1510564670 + }, + "origin": "perl-list-someutils-xs" + }, + "xf86-video-fbdev": { + "versions": { + "0.4.4-r5": 1510069871 + }, + "origin": "xf86-video-fbdev", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "sdl2-dev": { + "versions": { + "2.0.7-r3": 1511364667, + "2.0.10-r0": 1564135866 + }, + "origin": "sdl2", + "dependencies": [ + "directfb-dev", + "pkgconfig", + "sdl2=2.0.10-r0" + ], + "provides": [ + "pc:sdl2=2.0.10", + "cmd:sdl2-config" + ] + }, + "lua5.2-pc": { + "versions": { + "1.0.0-r9": 1509480088 + }, + "origin": "lua-pc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gobject-introspection-doc": { + "versions": { + "1.54.1-r0": 1509464659 + }, + "origin": "gobject-introspection" + }, + "ttf-ubuntu-font-family": { + "versions": { + "0.83-r0": 1509491420 + }, + "origin": "ttf-ubuntu-font-family" + }, + "perl-html-mason-psgihandler-doc": { + "versions": { + "0.53-r0": 1510588956 + }, + "origin": "perl-html-mason-psgihandler" + }, + "openobex-apps": { + "versions": { + "1.7.2-r1": 1510071735 + }, + "origin": "openobex", + "dependencies": [ + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1", + "so:libopenobex.so.2" + ], + "provides": [ + "cmd:ircp", + "cmd:irobex_palm3", + "cmd:irxfer", + "cmd:obex_find", + "cmd:obex_tcp", + "cmd:obex_test" + ] + }, + "perl-mime-tools-doc": { + "versions": { + "5.509-r1": 1509469900 + }, + "origin": "perl-mime-tools" + }, + "ca-certificates-doc": { + "versions": { + "20171114-r0": 1510690408, + "20190108-r0": 1558960536 + }, + "origin": "ca-certificates" + }, + "perl-b-hooks-endofscope-doc": { + "versions": { + "0.21-r0": 1509474009 + }, + "origin": "perl-b-hooks-endofscope" + }, + "guile": { + "versions": { + "2.0.14-r0": 1509468174 + }, + "origin": "guile", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libguile-2.0.so.22" + ], + "provides": [ + "cmd:guild", + "cmd:guile", + "cmd:guile-snarf", + "cmd:guile-tools" + ] + }, + "perl-encode": { + "versions": { + "2.93-r0": 1511889458 + }, + "origin": "perl-encode", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "mtools": { + "versions": { + "4.0.18-r2": 1509462432 + }, + "origin": "mtools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:amuFormat.sh", + "cmd:lz", + "cmd:mattrib", + "cmd:mbadblocks", + "cmd:mcat", + "cmd:mcd", + "cmd:mcheck", + "cmd:mclasserase", + "cmd:mcomp", + "cmd:mcopy", + "cmd:mdel", + "cmd:mdeltree", + "cmd:mdir", + "cmd:mdu", + "cmd:mformat", + "cmd:minfo", + "cmd:mkmanifest", + "cmd:mlabel", + "cmd:mmd", + "cmd:mmount", + "cmd:mmove", + "cmd:mpartition", + "cmd:mrd", + "cmd:mren", + "cmd:mshortname", + "cmd:mshowfat", + "cmd:mtools", + "cmd:mtoolstest", + "cmd:mtype", + "cmd:mxtar", + "cmd:mzip", + "cmd:tgz", + "cmd:uz" + ] + }, + "btrfs-progs-doc": { + "versions": { + "4.13.2-r0": 1509481818 + }, + "origin": "btrfs-progs" + }, + "monit": { + "versions": { + "5.24.0-r1": 1510261491, + "5.24.0-r2": 1559744492 + }, + "origin": "monit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:monit" + ] + }, + "serf-dev": { + "versions": { + "1.3.9-r3": 1510314338 + }, + "origin": "serf", + "dependencies": [ + "pc:libcrypto", + "pc:libssl", + "pkgconfig", + "serf=1.3.9-r3" + ], + "provides": [ + "pc:serf-1=1.3.9" + ] + }, + "gtest-doc": { + "versions": { + "1.8.0-r1": 1509475786 + }, + "origin": "gtest" + }, + "py3-requests-oauthlib": { + "versions": { + "0.8.0-r1": 1509552764 + }, + "origin": "py-requests-oauthlib", + "dependencies": [ + "py3-oauthlib", + "py3-requests", + "python3" + ] + }, + "libotr": { + "versions": { + "4.1.1-r1": 1509473953 + }, + "origin": "libotr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20" + ], + "provides": [ + "so:libotr.so.5=5.1.1" + ] + }, + "xorg-server-dev": { + "versions": { + "1.19.5-r1": 1540838460 + }, + "origin": "xorg-server", + "dependencies": [ + "bigreqsproto", + "compositeproto", + "damageproto", + "fixesproto", + "libepoxy-dev", + "libxfont2-dev", + "mesa-dev", + "recordproto", + "xcmiscproto", + "pc:dri2proto>=2.8", + "pc:dri3proto>=1.0", + "pc:dri>=7.8.0", + "pc:fontsproto>=2.1.3", + "pc:glproto>=1.4.17", + "pc:inputproto>=2.3", + "pc:kbproto>=1.0.3", + "pc:pciaccess>=0.12.901", + "pc:pixman-1>=0.27.2", + "pc:presentproto>=1.0", + "pc:randrproto>=1.5.0", + "pc:renderproto>=0.11", + "pc:resourceproto>=1.2.0", + "pc:scrnsaverproto>=1.1", + "pc:videoproto", + "pc:xextproto>=7.2.99.901", + "pc:xf86driproto>=2.1.0", + "pc:xineramaproto", + "pc:xproto>=7.0.31", + "pkgconfig" + ], + "provides": [ + "pc:xorg-server=1.19.5" + ] + }, + "perl-namespace-autoclean": { + "versions": { + "0.28-r0": 1509474083 + }, + "origin": "perl-namespace-autoclean", + "dependencies": [ + "perl-b-hooks-endofscope", + "perl-namespace-clean" + ] + }, + "spice-dev": { + "versions": { + "0.14.1-r2": 1548934012 + }, + "origin": "spice", + "dependencies": [ + "spice-protocol", + "pixman-dev", + "celt051-dev", + "libxinerama-dev", + "pc:gio-2.0>=2.32", + "pc:glib-2.0>=2.32", + "pc:gobject-2.0>=2.32", + "pc:openssl", + "pc:pixman-1>=0.17.7", + "pc:spice-protocol>=0.12.14", + "pkgconfig", + "spice-server=0.14.1-r2" + ], + "provides": [ + "pc:spice-server=0.14.1" + ] + }, + "links": { + "versions": { + "2.14-r2": 1510259134 + }, + "origin": "links", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:links" + ] + }, + "py-samba": { + "versions": { + "4.7.6-r3": 1555491789 + }, + "origin": "samba", + "dependencies": [ + "py-tdb", + "so:libMESSAGING-SEND-samba4.so", + "so:libMESSAGING-samba4.so", + "so:libauth4-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-ldap-samba4.so", + "so:libcli-nbt-samba4.so", + "so:libcliauth-samba4.so", + "so:libcluster-samba4.so", + "so:libcmdline-credentials-samba4.so", + "so:libdb-glue-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libdcerpc-samba4.so", + "so:libdcerpc.so.0", + "so:libdnsserver-common-samba4.so", + "so:libdsdb-garbage-collect-tombstones-samba4.so", + "so:libevents-samba4.so", + "so:libgensec-samba4.so", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:liblibsmb-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libnetif-samba4.so", + "so:libposix-eadb-samba4.so", + "so:libpyldb-util.so.1", + "so:libpytalloc-util.so.2", + "so:libpython2.7.so.1.0", + "so:libregistry-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-net-samba4.so", + "so:libsamba-passdb.so.0", + "so:libsamba-policy.so.0", + "so:libsamba-python-samba4.so", + "so:libsamba-security-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libsecrets3-samba4.so", + "so:libserver-role-samba4.so", + "so:libsmbclient-raw-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-base-samba4.so", + "so:libsmbd-conn-samba4.so", + "so:libtalloc.so.2", + "so:libtdb-wrap-samba4.so", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-reg-samba4.so", + "so:libxattr-tdb-samba4.so" + ] + }, + "gettext-libs": { + "versions": { + "0.19.8.1-r1": 1509459197 + }, + "origin": "gettext", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libunistring.so.2" + ], + "provides": [ + "so:libgettextpo.so.0=0.5.4" + ] + }, + "openbox-lang": { + "versions": { + "3.6.1-r1": 1510073886 + }, + "origin": "openbox" + }, + "harfbuzz-icu": { + "versions": { + "1.6.3-r0": 1509464873 + }, + "origin": "harfbuzz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libharfbuzz.so.0", + "so:libicuuc.so.59" + ], + "provides": [ + "so:libharfbuzz-icu.so.0=0.10600.3" + ] + }, + "nagios-plugins-nwstat": { + "versions": { + "2.2.1-r3": 1510288497 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "acf-postgresql": { + "versions": { + "0.11.0-r2": 1510076158 + }, + "origin": "acf-postgresql", + "dependencies": [ + "acf-core", + "postgresql", + "acf-db-lib", + "lua-sql-postgres" + ] + }, + "dovecot-doc": { + "versions": { + "2.2.36.3-r0": 1554102386, + "2.2.36.4-r0": 1567075097 + }, + "origin": "dovecot" + }, + "cups-dev": { + "versions": { + "2.2.10-r0": 1549287809, + "2.2.12-r0": 1566207589 + }, + "origin": "cups", + "dependencies": [ + "libgcrypt-dev", + "gnutls-dev", + "zlib-dev", + "cups-libs=2.2.12-r0" + ], + "provides": [ + "cmd:cups-config" + ] + }, + "xmlrpc-c++": { + "versions": { + "1.39.11-r0": 1509482038 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libxmlrpc.so.3", + "so:libxmlrpc_abyss.so.3", + "so:libxmlrpc_server.so.3", + "so:libxmlrpc_server_abyss.so.3", + "so:libxmlrpc_util.so.3" + ], + "provides": [ + "so:libxmlrpc++.so.8=8.39", + "so:libxmlrpc_abyss++.so.8=8.39", + "so:libxmlrpc_cpp.so.8=8.39", + "so:libxmlrpc_packetsocket.so.8=8.39", + "so:libxmlrpc_server++.so.8=8.39", + "so:libxmlrpc_server_abyss++.so.8=8.39", + "so:libxmlrpc_server_cgi++.so.8=8.39", + "so:libxmlrpc_server_pstream++.so.8=8.39", + "so:libxmlrpc_util++.so.8=8.39" + ] + }, + "gdl-dev": { + "versions": { + "3.22.0-r0": 1510074987 + }, + "origin": "gdl", + "dependencies": [ + "gdl=3.22.0-r0", + "pc:gtk+-3.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gdl-3.0=3.22.0" + ] + }, + "xfce4-panel-lang": { + "versions": { + "4.12.1-r1": 1510068174 + }, + "origin": "xfce4-panel", + "dependencies": [ + "hicolor-icon-theme" + ] + }, + "libpciaccess-dev": { + "versions": { + "0.13.5-r1": 1509466088 + }, + "origin": "libpciaccess", + "dependencies": [ + "libpciaccess=0.13.5-r1", + "pkgconfig" + ], + "provides": [ + "pc:pciaccess=0.13.5" + ] + }, + "debian-archive-keyring-doc": { + "versions": { + "2017.6-r0": 1510588269 + }, + "origin": "debian-archive-keyring" + }, + "sysstat": { + "versions": { + "11.6.0-r0": 1509491781 + }, + "origin": "sysstat", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cifsiostat", + "cmd:iostat", + "cmd:mpstat", + "cmd:pidstat", + "cmd:sadf", + "cmd:sar", + "cmd:tapestat" + ] + }, + "recordproto-doc": { + "versions": { + "1.14.2-r2": 1509466032 + }, + "origin": "recordproto" + }, + "perl-lwp-mediatypes": { + "versions": { + "6.02-r1": 1509464357 + }, + "origin": "perl-lwp-mediatypes", + "dependencies": [ + "perl" + ] + }, + "logrotate": { + "versions": { + "3.13.0-r0": 1509473542 + }, + "origin": "logrotate", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:logrotate" + ] + }, + "libcue-dev": { + "versions": { + "2.1.0-r0": 1509494074 + }, + "origin": "libcue", + "dependencies": [ + "libcue=2.1.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcue=2.1.0" + ] + }, + "perl-xml-sax": { + "versions": { + "0.99-r2": 1509475907 + }, + "origin": "perl-xml-sax", + "dependencies": [ + "perl-xml-sax-base", + "perl-xml-namespacesupport", + "/bin/sh" + ] + }, + "fribidi-dev": { + "versions": { + "0.19.7-r0": 1509480343 + }, + "origin": "fribidi", + "dependencies": [ + "fribidi=0.19.7-r0", + "pkgconfig" + ], + "provides": [ + "pc:fribidi=0.19.7" + ] + }, + "rtnppd-dbg": { + "versions": { + "1.7b-r8": 1509491626 + }, + "origin": "rtnppd" + }, + "libxklavier": { + "versions": { + "5.4-r2": 1509475735 + }, + "origin": "libxklavier", + "dependencies": [ + "xkeyboard-config", + "iso-codes", + "so:libX11.so.6", + "so:libXi.so.6", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libxkbfile.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "so:libxklavier.so.16=16.4.0" + ] + }, + "clamav-db": { + "versions": { + "0.100.3-r0": 1555508238 + }, + "origin": "clamav", + "dependencies": [ + "freshclam" + ] + }, + "gst-plugins-ugly0.10": { + "versions": { + "0.10.19-r2": 1510072192 + }, + "origin": "gst-plugins-ugly0.10", + "dependencies": [ + "so:liba52.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcdio.so.16", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstaudio-0.10.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstcdda-0.10.so.0", + "so:libgstpbutils-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgstriff-0.10.so.0", + "so:libgstrtp-0.10.so.0", + "so:libgstrtsp-0.10.so.0", + "so:libgstsdp-0.10.so.0", + "so:libgsttag-0.10.so.0", + "so:libgstvideo-0.10.so.0", + "so:libintl.so.8", + "so:libmad.so.0", + "so:libmp3lame.so.0", + "so:libmpeg2.so.0", + "so:liborc-0.4.so.0", + "so:libx264.so.148" + ] + }, + "perl-cpan-meta-check-doc": { + "versions": { + "0.013-r0": 1509468484 + }, + "origin": "perl-cpan-meta-check" + }, + "libwnck-doc": { + "versions": { + "2.31.0-r5": 1510068129 + }, + "origin": "libwnck" + }, + "aconf-mod-dns-zone": { + "versions": { + "0.6.5-r0": 1510073708 + }, + "origin": "aconf", + "dependencies": [ + "aconf" + ] + }, + "perl-package-deprecationmanager-doc": { + "versions": { + "0.17-r0": 1509493029 + }, + "origin": "perl-package-deprecationmanager" + }, + "ppp-passwordfd": { + "versions": { + "2.4.7-r5": 1509480138 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-netaddr-ip": { + "versions": { + "4.079-r1": 1509485728 + }, + "origin": "perl-netaddr-ip", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "openvswitch-monitor": { + "versions": { + "2.8.1-r2": 1512030506 + }, + "origin": "openvswitch", + "dependencies": [ + "openvswitch", + "py-twisted", + "py-twisted-web2", + "py-qt" + ] + }, + "xf86-video-amdgpu": { + "versions": { + "1.4.0-r0": 1510075100 + }, + "origin": "xf86-video-amdgpu", + "dependencies": [ + "mesa-dri-ati", + "so:libc.musl-x86_64.so.1", + "so:libdrm_amdgpu.so.1", + "so:libgbm.so.1", + "so:libudev.so.1" + ] + }, + "sdl-dev": { + "versions": { + "1.2.15-r7": 1510067895, + "1.2.15-r8": 1565787129, + "1.2.15-r9": 1574265375 + }, + "origin": "sdl", + "dependencies": [ + "libx11-dev", + "pkgconfig", + "sdl=1.2.15-r9" + ], + "provides": [ + "pc:sdl=1.2.15", + "cmd:sdl-config" + ] + }, + "userspace-rcu-dev": { + "versions": { + "0.10.0-r0": 1509479771 + }, + "origin": "userspace-rcu", + "dependencies": [ + "pkgconfig", + "userspace-rcu=0.10.0-r0" + ], + "provides": [ + "pc:liburcu-bp=0.10.0", + "pc:liburcu-cds=0.10.0", + "pc:liburcu-mb=0.10.0", + "pc:liburcu-qsbr=0.10.0", + "pc:liburcu-signal=0.10.0", + "pc:liburcu=0.10.0" + ] + }, + "freeradius-python": { + "versions": { + "3.0.15-r4": 1556202796, + "3.0.15-r5": 1566310604 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.15-r5", + "python2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "freeradius3-python=3.0.15-r5", + "so:rlm_python.so=0" + ] + }, + "qemu-system-ppc64": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-ppc64" + ] + }, + "py3-idna": { + "versions": { + "2.6-r0": 1509476380 + }, + "origin": "py-idna", + "dependencies": [ + "python3" + ] + }, + "fuse": { + "versions": { + "2.9.8-r0": 1532967982 + }, + "origin": "fuse", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfuse.so.2=2.9.8", + "so:libulockmgr.so.1=1.0.1", + "cmd:fusermount", + "cmd:mount.fuse", + "cmd:ulockmgr_server" + ] + }, + "acpica-doc": { + "versions": { + "20170303-r0": 1510068815 + }, + "origin": "acpica" + }, + "cabextract-doc": { + "versions": { + "1.9-r0": 1543333648 + }, + "origin": "cabextract" + }, + "network-extras": { + "versions": { + "1.2-r0": 1509480150 + }, + "origin": "network-extras", + "dependencies": [ + "bridge", + "bonding", + "vlan", + "wpa_supplicant", + "wireless-tools", + "ppp-atm", + "ppp-chat", + "ppp-daemon", + "ppp-l2tp", + "ppp-minconn", + "ppp-passprompt", + "ppp-passwordfd", + "ppp-pppoe", + "ppp-radius", + "ppp-winbind", + "usb-modeswitch" + ] + }, + "lua5.2-cjson": { + "versions": { + "2.1.0-r7": 1509462474 + }, + "origin": "lua-cjson", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "kbd-doc": { + "versions": { + "2.0.4-r2": 1510922531 + }, + "origin": "kbd" + }, + "xz-libs": { + "versions": { + "5.2.3-r1": 1509459906 + }, + "origin": "xz", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblzma.so.5=5.2.3" + ] + }, + "libfetch-dev": { + "versions": { + "2.33-r2": 1509490729 + }, + "origin": "libfetch" + }, + "py2-pygments": { + "versions": { + "2.2.0-r0": 1509493366 + }, + "origin": "py-pygments", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:pygmentize-2" + ] + }, + "libpcre32": { + "versions": { + "8.41-r1": 1509464278 + }, + "origin": "pcre", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre32.so.0=0.0.9" + ] + }, + "font-adobe-utopia-type1": { + "versions": { + "1.0.4-r0": 1509482404 + }, + "origin": "font-adobe-utopia-type1", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "lksctp-tools-doc": { + "versions": { + "1.0.17-r0": 1509492328 + }, + "origin": "lksctp-tools" + }, + "lua5.3-optarg": { + "versions": { + "0.2-r1": 1509462490 + }, + "origin": "lua-optarg", + "dependencies": [ + "lua5.3" + ] + }, + "fts": { + "versions": { + "1.2.7-r0": 1509481205 + }, + "origin": "fts", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfts.so.0=0.0.0" + ] + }, + "slim": { + "versions": { + "1.3.6-r8": 1510067548 + }, + "origin": "slim", + "dependencies": [ + "dbus", + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXmu.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libck-connector.so.0", + "so:libdbus-1.so.3", + "so:libgcc_s.so.1", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libslim.so.1.3.6=1.3.6", + "cmd:slim" + ] + }, + "xf86-video-i740": { + "versions": { + "1.3.6-r0": 1510074898 + }, + "origin": "xf86-video-i740", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "eggdrop": { + "versions": { + "1.6.21-r2": 1509491905 + }, + "origin": "eggdrop", + "dependencies": [ + "tcl", + "so:libc.musl-x86_64.so.1", + "so:libtcl8.6.so", + "so:libz.so.1" + ], + "provides": [ + "cmd:eggdrop" + ] + }, + "lua5.3-dbi-mysql": { + "versions": { + "0.6-r1": 1511483402 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "uwsgi-sslrouter": { + "versions": { + "2.0.17-r0": 1522154659 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "uwsgi-corerouter", + "so:libc.musl-x86_64.so.1" + ] + }, + "nagios-plugins-users": { + "versions": { + "2.2.1-r3": 1510288498 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "open-lldp-doc": { + "versions": { + "0.9.46-r3": 1510075776 + }, + "origin": "open-lldp" + }, + "freeswitch-sounds-es-mx-maria-44100": { + "versions": { + "0-r2": 1509493970 + }, + "origin": "freeswitch-sounds-es-mx-maria-44100" + }, + "a2ps-dev": { + "versions": { + "4.14-r7": 1510365669 + }, + "origin": "a2ps" + }, + "brlaser": { + "versions": { + "3-r0": 1510075895 + }, + "origin": "brlaser", + "dependencies": [ + "cups-filters", + "so:libc.musl-x86_64.so.1", + "so:libcupsimage.so.2", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "tree": { + "versions": { + "1.7.0-r1": 1509489740 + }, + "origin": "tree", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tree" + ] + }, + "kyua": { + "versions": { + "0.13-r1": 1509459786 + }, + "origin": "kyua", + "dependencies": [ + "so:libatf-c++.so.2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:liblutok.so.3", + "so:libsqlite3.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:kyua" + ] + }, + "faad2-doc": { + "versions": { + "2.7-r7": 1509480953, + "2.9.0-r0": 1571918410 + }, + "origin": "faad2" + }, + "perl-devel-symdump": { + "versions": { + "2.18-r0": 1509470433 + }, + "origin": "perl-devel-symdump" + }, + "openssh-server": { + "versions": { + "7.5_p1-r10": 1551712288 + }, + "origin": "openssh", + "dependencies": [ + "openssh-keygen", + "openssh-server-common", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libz.so.1" + ], + "provides": [ + "cmd:sshd" + ] + }, + "vanessa_logger-doc": { + "versions": { + "0.0.10-r0": 1509481340 + }, + "origin": "vanessa_logger" + }, + "lighttpd": { + "versions": { + "1.4.48-r0": 1511925915 + }, + "origin": "lighttpd", + "dependencies": [ + "/bin/sh", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libev.so.4", + "so:libfam.so.0", + "so:libldap-2.4.so.2", + "so:liblua-5.3.so.0", + "so:libpcre.so.1", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:lighttpd", + "cmd:lighttpd-angel" + ] + }, + "gtk-update-icon-cache": { + "versions": { + "2.24.31-r0": 1510066781 + }, + "origin": "gtk+2.0", + "dependencies": [ + "hicolor-icon-theme", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:gtk-update-icon-cache" + ] + }, + "lua-ldbus": { + "versions": { + "20150430-r2": 1509492578 + }, + "origin": "lua-ldbus" + }, + "libmcrypt-doc": { + "versions": { + "2.5.8-r7": 1509493845 + }, + "origin": "libmcrypt" + }, + "zsh-calendar": { + "versions": { + "5.4.2-r1": 1522503663 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "po4a": { + "versions": { + "0.51-r1": 1509459569 + }, + "origin": "po4a", + "dependencies": [ + "perl", + "gettext" + ], + "provides": [ + "cmd:msguntypot", + "cmd:po4a", + "cmd:po4a-build", + "cmd:po4a-gettextize", + "cmd:po4a-normalize", + "cmd:po4a-translate", + "cmd:po4a-updatepo", + "cmd:po4aman-display-po", + "cmd:po4apod-display-po" + ] + }, + "openldap-overlay-deref": { + "versions": { + "2.4.45-r3": 1510258134, + "2.4.46-r0": 1565073940, + "2.4.48-r0": 1566900517 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "bluez-obexd": { + "versions": { + "5.47-r3": 1510069788 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0", + "so:libicalvcal.so.2" + ] + }, + "perl-apache-session": { + "versions": { + "1.93-r0": 1509491312 + }, + "origin": "perl-apache-session" + }, + "gdb": { + "versions": { + "8.0.1-r3": 1509486145 + }, + "origin": "gdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libncursesw.so.6", + "so:libreadline.so.7", + "so:libz.so.1" + ], + "provides": [ + "cmd:gcore", + "cmd:gdb" + ] + }, + "one-context": { + "versions": { + "0.5.4-r0": 1529622755 + }, + "origin": "one-context", + "dependencies": [ + "blkid" + ] + }, + "uwsgi-geoip": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-test-harness": { + "versions": { + "3.39-r0": 1509474773 + }, + "origin": "perl-test-harness" + }, + "libressl2.6-libtls": { + "versions": { + "2.6.5-r0": 1529043884 + }, + "origin": "libressl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "so:libtls.so.16=16.0.1" + ] + }, + "git-doc": { + "versions": { + "2.15.3-r0": 1540401292, + "2.15.4-r0": 1576015189 + }, + "origin": "git" + }, + "squid-lang-el": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865667 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-convert-color": { + "versions": { + "0.11-r0": 1509489725 + }, + "origin": "perl-convert-color", + "dependencies": [ + "perl-list-utilsby", + "perl-module-pluggable" + ] + }, + "ppp-atm": { + "versions": { + "2.4.7-r5": 1509480129 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.3-discount": { + "versions": { + "1.2.10.1-r4": 1509479789 + }, + "origin": "lua-discount", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "quazip-dev": { + "versions": { + "0.7.3-r0": 1510076333 + }, + "origin": "quazip", + "dependencies": [ + "quazip=0.7.3-r0" + ] + }, + "gsm": { + "versions": { + "1.0.16-r0": 1509473212 + }, + "origin": "gsm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgsm.so.1=1.0.12" + ] + }, + "ruby-google-protobuf": { + "versions": { + "3.4.1-r1": 1510846093 + }, + "origin": "protobuf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.4" + ] + }, + "pstree": { + "versions": { + "2.39-r0": 1509493762 + }, + "origin": "pstree", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pstree" + ] + }, + "perl-test-harness-utils": { + "versions": { + "3.39-r0": 1509474772 + }, + "origin": "perl-test-harness", + "dependencies": [ + "perl-test-harness", + "perl" + ], + "provides": [ + "cmd:prove" + ] + }, + "ruby-irb": { + "versions": { + "2.4.6-r0": 1557166822 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ], + "provides": [ + "cmd:irb" + ] + }, + "geoip": { + "versions": { + "1.6.11-r0": 1509474613 + }, + "origin": "geoip", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libGeoIP.so.1=1.6.11", + "cmd:geoiplookup", + "cmd:geoiplookup6" + ] + }, + "libisofs": { + "versions": { + "1.4.8-r0": 1509462207 + }, + "origin": "libisofs", + "dependencies": [ + "so:libacl.so.1", + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libisofs.so.6=6.84.0" + ] + }, + "py-gnome-gconf": { + "versions": { + "2.28.1-r5": 1510933055 + }, + "origin": "py-gnome", + "dependencies": [ + "gconf", + "py-gtk", + "so:libc.musl-x86_64.so.1", + "so:libgconf-2.so.4", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpython2.7.so.1.0" + ] + }, + "cups-filters": { + "versions": { + "1.17.9-r0": 1510075884 + }, + "origin": "cups-filters", + "dependencies": [ + "poppler-utils", + "bc", + "ttf-freefont", + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-glib.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2", + "so:libcupsfilters.so.1", + "so:libcupsimage.so.2", + "so:libfontconfig.so.1", + "so:libfontembed.so.1", + "so:libgcc_s.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libijs-0.35.so", + "so:liblcms2.so.2", + "so:libpoppler.so.67", + "so:libqpdf.so.18", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:cups-browsed", + "cmd:driverless", + "cmd:foomatic-rip", + "cmd:ttfread" + ] + }, + "perl-crypt-x509-doc": { + "versions": { + "0.51-r0": 1509480694 + }, + "origin": "perl-crypt-x509" + }, + "libtirpc-dev": { + "versions": { + "1.0.1-r2": 1509469767 + }, + "origin": "libtirpc", + "dependencies": [ + "krb5-dev", + "bsd-compat-headers", + "libtirpc=1.0.1-r2", + "pkgconfig" + ], + "provides": [ + "pc:libtirpc=1.0.1" + ] + }, + "alpine-baselayout": { + "versions": { + "3.0.5-r2": 1510075862 + }, + "origin": "alpine-baselayout", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mkmntdirs" + ] + }, + "rdesktop": { + "versions": { + "1.8.3-r3": 1510258584 + }, + "origin": "rdesktop", + "dependencies": [ + "so:libX11.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgssglue.so.1", + "so:libsamplerate.so.0", + "so:libssl.so.44" + ], + "provides": [ + "cmd:rdesktop" + ] + }, + "snmptt": { + "versions": { + "1.3-r7": 1509492310 + }, + "origin": "snmptt", + "dependencies": [ + "perl", + "perl-config-inifiles", + "perl-list-moreutils", + "/bin/sh" + ], + "provides": [ + "cmd:snmptt", + "cmd:snmpttconvert", + "cmd:snmpttconvertmib", + "cmd:snmptthandler" + ] + }, + "paxmark": { + "versions": { + "0.11-r0": 1509457003 + }, + "origin": "paxmark", + "dependencies": [ + "attr" + ], + "provides": [ + "cmd:paxmark", + "cmd:paxmark.sh" + ] + }, + "vblade": { + "versions": { + "23-r0": 1509493239 + }, + "origin": "vblade", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:vblade", + "cmd:vbladed" + ] + }, + "tcpdump-doc": { + "versions": { + "4.9.2-r1": 1510260551 + }, + "origin": "tcpdump" + }, + "nss-pam-ldapd": { + "versions": { + "0.9.8-r0": 1509483509 + }, + "origin": "nss-pam-ldapd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2", + "so:libpam.so.0" + ], + "provides": [ + "cmd:nslcd" + ] + }, + "xen-bridge": { + "versions": { + "4.9.4-r0": 1551280495, + "4.9.4-r1": 1558103152 + }, + "origin": "xen", + "dependencies": [ + "dnsmasq" + ] + }, + "ansible-doc": { + "versions": { + "2.4.6.0-r0": 1538034317, + "2.4.6.0-r1": 1568287983 + }, + "origin": "ansible" + }, + "nghttp2-dev": { + "versions": { + "1.28.0-r0": 1511922922, + "1.39.2-r0": 1568186892 + }, + "origin": "nghttp2", + "dependencies": [ + "nghttp2-libs=1.39.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnghttp2=1.39.2" + ] + }, + "lua5.1-sql-sqlite3": { + "versions": { + "2.3.5-r1": 1509488829 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "xfce4-settings-lang": { + "versions": { + "4.12.1-r0": 1510303245 + }, + "origin": "xfce4-settings" + }, + "py2-lockfile": { + "versions": { + "0.12.2-r0": 1511197209 + }, + "origin": "py-lockfile", + "dependencies": [ + "python2" + ] + }, + "mini_httpd": { + "versions": { + "1.27-r1": 1510260671 + }, + "origin": "mini_httpd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "cmd:mini_htpasswd", + "cmd:mini_httpd" + ] + }, + "libnftnl-dev": { + "versions": { + "1.0.8-r1": 1509909171 + }, + "origin": "libnftnl", + "dependencies": [ + "libmnl-dev", + "libnftnl-libs=1.0.8-r1", + "pkgconfig" + ], + "provides": [ + "pc:libnftnl=1.0.8" + ] + }, + "git-svn": { + "versions": { + "2.15.3-r0": 1540401293, + "2.15.4-r0": 1576015190 + }, + "origin": "git", + "dependencies": [ + "perl", + "perl-git-svn=2.15.4-r0", + "perl-subversion", + "perl-term-readkey", + "so:libc.musl-x86_64.so.1", + "so:libpcre2-8.so.0", + "so:libz.so.1" + ] + }, + "py-yaml": { + "versions": { + "3.12-r1": 1509485807 + }, + "origin": "py-yaml" + }, + "squid-lang-ko": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-test-pod-coverage": { + "versions": { + "1.10-r0": 1509470445 + }, + "origin": "perl-test-pod-coverage", + "dependencies": [ + "perl", + "perl-pod-coverage", + "perl-test-pod", + "perl-devel-symdump" + ] + }, + "libsndfile": { + "versions": { + "1.0.28-r4": 1548491634, + "1.0.28-r5": 1563345857 + }, + "origin": "libsndfile", + "dependencies": [ + "so:libFLAC.so.8", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2" + ], + "provides": [ + "so:libsndfile.so.1=1.0.28", + "cmd:sndfile-cmp", + "cmd:sndfile-concat", + "cmd:sndfile-convert", + "cmd:sndfile-deinterleave", + "cmd:sndfile-info", + "cmd:sndfile-interleave", + "cmd:sndfile-metadata-get", + "cmd:sndfile-metadata-set", + "cmd:sndfile-play", + "cmd:sndfile-salvage" + ] + }, + "setxkbmap-doc": { + "versions": { + "1.3.1-r0": 1509496553 + }, + "origin": "setxkbmap" + }, + "mrtg-doc": { + "versions": { + "2.17.4-r4": 1509482696 + }, + "origin": "mrtg" + }, + "openldap-overlay-translucent": { + "versions": { + "2.4.45-r3": 1510258135, + "2.4.46-r0": 1565073941, + "2.4.48-r0": 1566900518 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "iwlwifi-5000-ucode": { + "versions": { + "8.83.5.1-r0": 1509494266 + }, + "origin": "iwlwifi-5000-ucode" + }, + "xf86-video-ark-doc": { + "versions": { + "0.7.5-r7": 1510074926 + }, + "origin": "xf86-video-ark" + }, + "tinc": { + "versions": { + "1.0.35-r0": 1549268950 + }, + "origin": "tinc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:liblzo2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:tincd" + ] + }, + "dillo": { + "versions": { + "3.0.5-r4": 1510259043 + }, + "origin": "dillo", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libfltk.so.1.3", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:dillo", + "cmd:dillo-install-hyphenation", + "cmd:dpid", + "cmd:dpidc" + ] + }, + "ez-ipupdate": { + "versions": { + "3.0.10-r9": 1509490827 + }, + "origin": "ez-ipupdate", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ez-ipupdate" + ] + }, + "acf-dansguardian": { + "versions": { + "0.8.0-r2": 1510070498 + }, + "origin": "acf-dansguardian", + "dependencies": [ + "acf-core", + "dansguardian" + ] + }, + "apache2": { + "versions": { + "2.4.39-r0": 1554306882, + "2.4.41-r0": 1566292329 + }, + "origin": "apache2", + "dependencies": [ + "/bin/sh", + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:fcgistarter", + "cmd:httpd", + "cmd:suexec" + ] + }, + "uwsgi-pty": { + "versions": { + "2.0.17-r0": 1522154656 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "librsvg-dev": { + "versions": { + "2.40.19-r0": 1510066817 + }, + "origin": "librsvg", + "dependencies": [ + "glib-dev", + "gtk+2.0-dev", + "cairo-dev", + "libcroco-dev", + "libgsf-dev", + "librsvg=2.40.19-r0", + "pc:cairo", + "pc:gdk-pixbuf-2.0", + "pc:gio-2.0", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:librsvg-2.0=2.40.19" + ] + }, + "gstreamer-lang": { + "versions": { + "1.12.3-r0": 1509470920 + }, + "origin": "gstreamer" + }, + "libpurple-xmpp": { + "versions": { + "2.12.0-r2": 1510069755 + }, + "origin": "pidgin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libidn.so.11", + "so:libintl.so.8", + "so:libsasl2.so.3", + "so:libxml2.so.2" + ], + "provides": [ + "so:libjabber.so.0=0.0.0", + "so:libxmpp.so=0" + ] + }, + "irssi-proxy": { + "versions": { + "1.0.6-r0": 1519052408, + "1.0.8-r0": 1562236916 + }, + "origin": "irssi", + "dependencies": [ + "irssi", + "so:libc.musl-x86_64.so.1" + ] + }, + "xfburn-doc": { + "versions": { + "0.5.4-r0": 1510074793 + }, + "origin": "xfburn" + }, + "pgpool-dev": { + "versions": { + "3.7.0-r0": 1511650825 + }, + "origin": "pgpool", + "dependencies": [ + "pgpool=3.7.0-r0" + ] + }, + "perl-http-message-doc": { + "versions": { + "6.13-r0": 1511871374 + }, + "origin": "perl-http-message" + }, + "asciidoctor": { + "versions": { + "1.5.6.1-r0": 1509488588 + }, + "origin": "asciidoctor", + "dependencies": [ + "ruby" + ] + }, + "lua5.3-libs": { + "versions": { + "5.3.5-r2": 1557163184 + }, + "origin": "lua5.3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblua-5.3.so.0=0.0.0" + ] + }, + "fontsproto": { + "versions": { + "2.1.3-r2": 1509470393 + }, + "origin": "fontsproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:fontsproto=2.1.3" + ] + }, + "scrnsaverproto": { + "versions": { + "1.2.2-r2": 1509473902 + }, + "origin": "scrnsaverproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:scrnsaverproto=1.2.2" + ] + }, + "lua5.3-pc": { + "versions": { + "1.0.0-r9": 1509480091 + }, + "origin": "lua-pc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "linux-pam": { + "versions": { + "1.3.0-r0": 1509473198 + }, + "origin": "linux-pam", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpam.so.0=0.84.2", + "so:libpam_misc.so.0=0.82.1", + "so:libpamc.so.0=0.82.1", + "cmd:mkhomedir_helper", + "cmd:pam_tally", + "cmd:pam_tally2", + "cmd:pam_timestamp_check", + "cmd:unix_chkpwd", + "cmd:unix_update" + ] + }, + "xwininfo-doc": { + "versions": { + "1.1.3-r0": 1509484045 + }, + "origin": "xwininfo" + }, + "uwsgi-cgi": { + "versions": { + "2.0.17-r0": 1522154653 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-test-output-doc": { + "versions": { + "1.031-r0": 1509485673 + }, + "origin": "perl-test-output" + }, + "mcpp-libs": { + "versions": { + "2.7.2-r1": 1509473730 + }, + "origin": "mcpp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmcpp.so.0=0.3.0" + ] + }, + "perl-params-util-doc": { + "versions": { + "1.07-r5": 1509473959 + }, + "origin": "perl-params-util" + }, + "py3-urllib3": { + "versions": { + "1.22-r0": 1509483033 + }, + "origin": "py-urllib3", + "dependencies": [ + "python3" + ] + }, + "beep": { + "versions": { + "1.3-r2": 1509481228 + }, + "origin": "beep", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:beep" + ] + }, + "xfburn-lang": { + "versions": { + "0.5.4-r0": 1510074794 + }, + "origin": "xfburn", + "dependencies": [ + "desktop-file-utils", + "hicolor-icon-theme" + ] + }, + "zfs": { + "versions": { + "0.7.3-r0": 1509490075 + }, + "origin": "zfs", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libnvpair.so.1", + "so:libuuid.so.1", + "so:libuutil.so.1", + "so:libzfs.so.2", + "so:libzfs_core.so.1", + "so:libzpool.so.2" + ], + "provides": [ + "cmd:fsck.zfs", + "cmd:mount.zfs", + "cmd:raidz_test", + "cmd:zdb", + "cmd:zed", + "cmd:zfs", + "cmd:zgenhostid", + "cmd:zhack", + "cmd:zinject", + "cmd:zpios", + "cmd:zpool", + "cmd:zstreamdump", + "cmd:ztest" + ] + }, + "libxpm-dev": { + "versions": { + "3.5.12-r0": 1509469832 + }, + "origin": "libxpm", + "dependencies": [ + "libxpm=3.5.12-r0", + "pc:x11", + "pkgconfig" + ], + "provides": [ + "pc:xpm=3.5.12" + ] + }, + "beep-doc": { + "versions": { + "1.3-r2": 1509481227 + }, + "origin": "beep" + }, + "postgresql-plpython3": { + "versions": { + "10.7-r0": 1554274199, + "10.8-r0": 1557668016, + "10.9-r0": 1562225614, + "10.10-r0": 1565683439 + }, + "origin": "postgresql", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "kamailio-geoip2": { + "versions": { + "5.0.7-r0": 1532960877 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libmaxminddb.so.0" + ] + }, + "pcre2-dev": { + "versions": { + "10.30-r0": 1509461772 + }, + "origin": "pcre2", + "dependencies": [ + "libedit-dev", + "zlib-dev", + "libpcre2-16=10.30-r0", + "libpcre2-32=10.30-r0", + "pcre2=10.30-r0", + "pkgconfig" + ], + "provides": [ + "pc:libpcre2-16=10.30", + "pc:libpcre2-32=10.30", + "pc:libpcre2-8=10.30", + "pc:libpcre2-posix=10.30", + "cmd:pcre2-config" + ] + }, + "perl-git": { + "versions": { + "2.15.3-r0": 1540401292, + "2.15.4-r0": 1576015190 + }, + "origin": "git", + "dependencies": [ + "git=2.15.4-r0", + "perl-error" + ] + }, + "sdl_image": { + "versions": { + "1.2.12-r3": 1510067914, + "1.2.12-r4": 1574265385 + }, + "origin": "sdl_image", + "dependencies": [ + "so:libSDL-1.2.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL_image-1.2.so.0=0.8.4" + ] + }, + "perl-cgi-session": { + "versions": { + "4.48-r0": 1509475912 + }, + "origin": "perl-cgi-session", + "dependencies": [ + "perl" + ] + }, + "lua5.3-dbi-postgresql": { + "versions": { + "0.6-r1": 1511483402 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "bzip2-doc": { + "versions": { + "1.0.6-r6": 1509456387, + "1.0.6-r7": 1562268494 + }, + "origin": "bzip2" + }, + "glade3": { + "versions": { + "3.8.5-r4": 1510067964 + }, + "origin": "glade3", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgladeui-1.so.11=11.2.0", + "cmd:glade-3" + ] + }, + "perl-extutils-pkgconfig-doc": { + "versions": { + "1.16-r1": 1509491043 + }, + "origin": "perl-extutils-pkgconfig" + }, + "lua5.1-hashids": { + "versions": { + "1.0.6-r1": 1509480766 + }, + "origin": "lua-hashids", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "openldap-overlay-seqmod": { + "versions": { + "2.4.45-r3": 1510258135, + "2.4.46-r0": 1565073941, + "2.4.48-r0": 1566900518 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libldap_r-2.4.so.2" + ] + }, + "libnjb-examples": { + "versions": { + "2.2.7-r4": 1509481679 + }, + "origin": "libnjb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libnjb.so.5", + "so:libz.so.1" + ], + "provides": [ + "cmd:njb-cursesplay", + "cmd:njb-delfile", + "cmd:njb-deltr", + "cmd:njb-dumpeax", + "cmd:njb-dumptime", + "cmd:njb-files", + "cmd:njb-fwupgrade", + "cmd:njb-getfile", + "cmd:njb-getowner", + "cmd:njb-gettr", + "cmd:njb-getusage", + "cmd:njb-handshake", + "cmd:njb-pl", + "cmd:njb-play", + "cmd:njb-playlists", + "cmd:njb-sendfile", + "cmd:njb-sendtr", + "cmd:njb-setowner", + "cmd:njb-setpbm", + "cmd:njb-settime", + "cmd:njb-tagtr", + "cmd:njb-tracks" + ] + }, + "libdvdcss-doc": { + "versions": { + "1.4.0-r1": 1509480289 + }, + "origin": "libdvdcss" + }, + "keyutils-doc": { + "versions": { + "1.5.10-r0": 1509469694 + }, + "origin": "keyutils" + }, + "xfwm4-lang": { + "versions": { + "4.12.4-r1": 1510074449 + }, + "origin": "xfwm4", + "dependencies": [ + "hicolor-icon-theme" + ] + }, + "bluez-btmon": { + "versions": { + "5.47-r3": 1510069787 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ], + "provides": [ + "cmd:btmon" + ] + }, + "libnftnl-libs": { + "versions": { + "1.0.8-r1": 1509909171 + }, + "origin": "libnftnl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjansson.so.4", + "so:libmnl.so.0" + ], + "provides": [ + "so:libnftnl.so.7=7.0.0" + ] + }, + "py3-future": { + "versions": { + "0.15.2-r4": 1509491794 + }, + "origin": "py-future", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:futurize", + "cmd:pasteurize" + ] + }, + "sc": { + "versions": { + "7.16-r4": 1509495486 + }, + "origin": "sc", + "dependencies": [ + "ncurses", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:psc", + "cmd:sc", + "cmd:scqref" + ] + }, + "perl-mime-types-doc": { + "versions": { + "2.14-r0": 1511188636 + }, + "origin": "perl-mime-types" + }, + "libproxy-bin": { + "versions": { + "0.4.15-r0": 1509468257 + }, + "origin": "libproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libproxy.so.1" + ], + "provides": [ + "cmd:proxy" + ] + }, + "libfprint-dev": { + "versions": { + "0.7.0-r0": 1509486009 + }, + "origin": "libfprint", + "dependencies": [ + "libusb-dev", + "libfprint=0.7.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libfprint=0.7.0" + ] + }, + "py2-olefile": { + "versions": { + "0.43-r2": 1509489936 + }, + "origin": "py-olefile" + }, + "lua5.2-sql-sqlite3": { + "versions": { + "2.3.5-r1": 1509488831 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "cksfv-doc": { + "versions": { + "1.3.14-r4": 1509486269 + }, + "origin": "cksfv" + }, + "perl-text-soundex-doc": { + "versions": { + "3.05-r0": 1509470836 + }, + "origin": "perl-text-soundex" + }, + "libcdio++": { + "versions": { + "0.94-r0": 1509473627 + }, + "origin": "libcdio", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcdio.so.16", + "so:libiso9660.so.10", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libcdio++.so.0=0.0.2", + "so:libiso9660++.so.0=0.0.0" + ] + }, + "perl-list-allutils-doc": { + "versions": { + "0.14-r0": 1509493247 + }, + "origin": "perl-list-allutils" + }, + "py-django-phonenumber-field": { + "versions": { + "1.3.0-r0": 1509490141 + }, + "origin": "py-django-phonenumber-field", + "dependencies": [ + "py-babel", + "py-django", + "py-phonenumbers" + ] + }, + "libquvi-scripts": { + "versions": { + "0.9.20131130-r0": 1509491410 + }, + "origin": "libquvi-scripts" + }, + "py2-cffi": { + "versions": { + "1.10.0-r0": 1509476374 + }, + "origin": "py-cffi", + "dependencies": [ + "py2-cparser", + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libpython2.7.so.1.0" + ] + }, + "py-jwt-cli": { + "versions": { + "1.5.0-r1": 1509552755 + }, + "origin": "py-jwt", + "dependencies": [ + "py3-jwt" + ], + "provides": [ + "cmd:pyjwt" + ] + }, + "varnish-geoip": { + "versions": { + "5.2.1-r0": 1511265090 + }, + "origin": "varnish", + "dependencies": [ + "libmaxminddb-dev" + ] + }, + "pjproject": { + "versions": { + "2.5.5-r3": 1510258881 + }, + "origin": "pjproject", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libgsm.so.1", + "so:libsamplerate.so.0", + "so:libspeex.so.1", + "so:libspeexdsp.so.1", + "so:libssl.so.44", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libg7221codec.so.2=2", + "so:libilbccodec.so.2=2", + "so:libpj.so.2=2", + "so:libpjlib-util.so.2=2", + "so:libpjmedia-audiodev.so.2=2", + "so:libpjmedia-codec.so.2=2", + "so:libpjmedia-videodev.so.2=2", + "so:libpjmedia.so.2=2", + "so:libpjnath.so.2=2", + "so:libpjsip-simple.so.2=2", + "so:libpjsip-ua.so.2=2", + "so:libpjsip.so.2=2", + "so:libpjsua.so.2=2", + "so:libpjsua2.so.2=2", + "so:libyuv.so.2=2" + ] + }, + "znc-modtcl": { + "versions": { + "1.7.1-r0": 1531900839, + "1.7.1-r1": 1565877330 + }, + "origin": "znc", + "dependencies": [ + "znc", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libtcl8.6.so" + ] + }, + "rdesktop-doc": { + "versions": { + "1.8.3-r3": 1510258584 + }, + "origin": "rdesktop" + }, + "libuv-dev": { + "versions": { + "1.17.0-r0": 1511652082 + }, + "origin": "libuv", + "dependencies": [ + "libuv=1.17.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libuv=1.17.0" + ] + }, + "galculator-doc": { + "versions": { + "2.1.4-r0": 1510075351 + }, + "origin": "galculator" + }, + "vte": { + "versions": { + "0.28.2-r13": 1510071903 + }, + "origin": "vte", + "dependencies": [ + "so:libX11.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0" + ], + "provides": [ + "so:libvte.so.9=9.2800.2", + "cmd:vte" + ] + }, + "libwebsockets-test": { + "versions": { + "2.4.0-r1": 1510258054 + }, + "origin": "libwebsockets", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:libwebsockets-test-client", + "cmd:libwebsockets-test-echo", + "cmd:libwebsockets-test-fraggle", + "cmd:libwebsockets-test-fuzxy", + "cmd:libwebsockets-test-ping", + "cmd:libwebsockets-test-server", + "cmd:libwebsockets-test-server-extpoll" + ] + }, + "lynx-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503664 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "strongswan": { + "versions": { + "5.6.3-r2": 1539005310 + }, + "origin": "strongswan", + "dependencies": [ + "iproute2", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libgmp.so.10", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:libcharon.so.0=0.0.0", + "so:libradius.so.0=0.0.0", + "so:libsimaka.so.0=0.0.0", + "so:libstrongswan.so.0=0.0.0", + "so:libtls.so.0=0.0.0", + "so:libvici.so.0=0.0.0", + "cmd:charon-cmd", + "cmd:ipsec", + "cmd:pki", + "cmd:swanctl" + ] + }, + "perl-test-sharedfork": { + "versions": { + "0.35-r0": 1509481780 + }, + "origin": "perl-test-sharedfork", + "dependencies": [ + "perl", + "perl-test-requires" + ] + }, + "py-lockfile": { + "versions": { + "0.12.2-r0": 1511197215 + }, + "origin": "py-lockfile" + }, + "yaml-dev": { + "versions": { + "0.1.7-r0": 1509470097 + }, + "origin": "yaml", + "dependencies": [ + "pkgconfig", + "yaml=0.1.7-r0" + ], + "provides": [ + "pc:yaml-0.1=0.1.7" + ] + }, + "perl-module-runtime": { + "versions": { + "0.014-r1": 1509470564 + }, + "origin": "perl-module-runtime", + "dependencies": [ + "perl-params-classify", + "perl-module-build" + ] + }, + "mg-doc": { + "versions": { + "20171014-r0": 1509494079 + }, + "origin": "mg" + }, + "libavc1394": { + "versions": { + "0.5.4-r1": 1509489638 + }, + "origin": "libavc1394", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libraw1394.so.11" + ], + "provides": [ + "so:libavc1394.so.0=0.3.0", + "so:librom1394.so.0=0.3.0", + "cmd:dvcont", + "cmd:mkrfc2734", + "cmd:panelctl" + ] + }, + "libxrender": { + "versions": { + "0.9.10-r2": 1509462095 + }, + "origin": "libxrender", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXrender.so.1=1.3.0" + ] + }, + "libpcre16": { + "versions": { + "8.41-r1": 1509464278 + }, + "origin": "pcre", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre16.so.0=0.2.9" + ] + }, + "qemu-system-i386": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-i386" + ] + }, + "lua5.2-lzmq": { + "versions": { + "0.4.4-r0": 1509475920 + }, + "origin": "lua-lzmq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5", + "so:libzmq.so.5" + ] + }, + "perl-scalar-list-utils": { + "versions": { + "1.49-r0": 1509485608 + }, + "origin": "perl-scalar-list-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-funcsigs": { + "versions": { + "1.0.2-r1": 1509485905 + }, + "origin": "py-funcsigs", + "dependencies": [ + "python2" + ] + }, + "re2c-doc": { + "versions": { + "1.0.2-r0": 1509475772 + }, + "origin": "re2c" + }, + "libfastjson": { + "versions": { + "0.99.7-r0": 1509486267 + }, + "origin": "libfastjson", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfastjson.so.4=4.2.0" + ] + }, + "freeradius-sql": { + "versions": { + "3.0.15-r4": 1556202796, + "3.0.15-r5": 1566310603 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.15-r5", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "freeradius3-sql=3.0.15-r5", + "so:rlm_sql.so=0", + "so:rlm_sql_null.so=0", + "so:rlm_sqlcounter.so=0", + "so:rlm_sqlippool.so=0" + ] + }, + "upower-doc": { + "versions": { + "0.99.6-r0": 1510068213 + }, + "origin": "upower" + }, + "rsyslog-snmp": { + "versions": { + "8.31.0-r0": 1512031981, + "8.31.0-r1": 1571767017 + }, + "origin": "rsyslog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.30" + ] + }, + "poppler-glib": { + "versions": { + "0.56.0-r0": 1509465067, + "0.56.0-r1": 1569334548 + }, + "origin": "poppler", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libfreetype.so.6", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpoppler.so.67", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libpoppler-glib.so.8=8.9.0" + ] + }, + "garcon-lang": { + "versions": { + "0.6.1-r1": 1510068108 + }, + "origin": "garcon" + }, + "py2-ipaddress": { + "versions": { + "1.0.18-r1": 1509480833 + }, + "origin": "py-ipaddress", + "dependencies": [ + "python2" + ] + }, + "wireless-tools-dev": { + "versions": { + "30_pre9-r0": 1509479802 + }, + "origin": "wireless-tools" + }, + "pm-utils": { + "versions": { + "1.4.1-r0": 1509491020 + }, + "origin": "pm-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:on_ac_power", + "cmd:pm-is-supported", + "cmd:pm-powersave" + ] + }, + "gnutls-utils": { + "versions": { + "3.6.1-r0": 1509465348 + }, + "origin": "gnutls", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30", + "so:libtasn1.so.6" + ], + "provides": [ + "cmd:certtool", + "cmd:gnutls-cli", + "cmd:gnutls-cli-debug", + "cmd:gnutls-serv", + "cmd:ocsptool", + "cmd:p11tool", + "cmd:psktool", + "cmd:srptool" + ] + }, + "ldns-dev": { + "versions": { + "1.6.17-r6": 1510258933 + }, + "origin": "ldns", + "dependencies": [ + "libressl-dev", + "ldns=1.6.17-r6", + "pc:libcrypto", + "pkgconfig" + ], + "provides": [ + "pc:libldns=1.6.17", + "cmd:ldns-config" + ] + }, + "py-django-treebeard": { + "versions": { + "4.1.2-r0": 1509494272 + }, + "origin": "py-django-treebeard", + "dependencies": [ + "py-django" + ] + }, + "py-oauth2client": { + "versions": { + "4.1.2-r1": 1509551799 + }, + "origin": "py-oauth2client", + "dependencies": [ + "py-asn1", + "py-httplib2", + "py-asn1-modules", + "py-rsa", + "py-six" + ] + }, + "libwebp": { + "versions": { + "0.6.0-r1": 1509473039 + }, + "origin": "libwebp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libwebp.so.7=7.0.0", + "so:libwebpdecoder.so.3=3.0.0", + "so:libwebpdemux.so.2=2.0.2", + "so:libwebpmux.so.3=3.0.0" + ] + }, + "qemu-mips64el": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mips64el" + ] + }, + "opensmtpd": { + "versions": { + "6.0.2p1-r7": 1510259021 + }, + "origin": "opensmtpd", + "dependencies": [ + "!postfix", + "/bin/sh", + "so:libasr.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libdb-5.3.so", + "so:libevent-2.1.so.6", + "so:libfts.so.0", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:smtpctl", + "cmd:smtpd" + ] + }, + "sdl": { + "versions": { + "1.2.15-r7": 1510067903, + "1.2.15-r8": 1565787130, + "1.2.15-r9": 1574265376 + }, + "origin": "sdl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL-1.2.so.0=0.11.4" + ] + }, + "fltk-doc": { + "versions": { + "1.3.4-r0": 1510072236 + }, + "origin": "fltk" + }, + "dconf-dev": { + "versions": { + "0.26.0-r1": 1509492217 + }, + "origin": "dconf", + "dependencies": [ + "dconf=0.26.0-r1", + "pc:gio-2.0>=2.25.7", + "pkgconfig" + ], + "provides": [ + "pc:dconf=0.26.0" + ] + }, + "newt": { + "versions": { + "0.52.20-r0": 1509476146 + }, + "origin": "newt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0", + "so:libslang.so.2" + ], + "provides": [ + "so:libnewt.so.0.52=0.52.20", + "cmd:whiptail" + ] + }, + "spice-webdavd": { + "versions": { + "2.2-r0": 1509492233 + }, + "origin": "phodav", + "dependencies": [ + "so:libavahi-gobject.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "cmd:spice-webdavd" + ] + }, + "dmenu-doc": { + "versions": { + "4.7-r0": 1509494018 + }, + "origin": "dmenu" + }, + "libmaxminddb-dev": { + "versions": { + "1.3.1-r0": 1511893707 + }, + "origin": "libmaxminddb", + "dependencies": [ + "libmaxminddb=1.3.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmaxminddb=1.3.1" + ] + }, + "liblastfm-dev": { + "versions": { + "1.0.9-r1": 1510074386 + }, + "origin": "liblastfm", + "dependencies": [ + "qt-dev", + "libsamplerate-dev", + "fftw-dev", + "liblastfm=1.0.9-r1" + ] + }, + "poppler-utils": { + "versions": { + "0.56.0-r0": 1509465066, + "0.56.0-r1": 1569334548 + }, + "origin": "poppler", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libfreetype.so.6", + "so:liblcms2.so.2", + "so:libpoppler.so.67", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:pdfdetach", + "cmd:pdffonts", + "cmd:pdfimages", + "cmd:pdfinfo", + "cmd:pdfseparate", + "cmd:pdftocairo", + "cmd:pdftohtml", + "cmd:pdftoppm", + "cmd:pdftops", + "cmd:pdftotext", + "cmd:pdfunite" + ] + }, + "liblogging-dev": { + "versions": { + "1.0.6-r0": 1509517790 + }, + "origin": "liblogging", + "dependencies": [ + "liblogging=1.0.6-r0", + "pkgconfig" + ], + "provides": [ + "pc:liblogging-stdlog=1.0.6" + ] + }, + "perl-sub-info": { + "versions": { + "0.002-r0": 1509481620 + }, + "origin": "perl-sub-info", + "dependencies": [ + "perl-importer", + "perl-test-simple" + ] + }, + "squid-lang-ar": { + "versions": { + "3.5.27-r0": 1519824030, + "3.5.27-r1": 1562865666 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-log-any": { + "versions": { + "1.049-r0": 1509470557 + }, + "origin": "perl-log-any" + }, + "audit": { + "versions": { + "2.7.7-r1": 1509491479 + }, + "origin": "audit", + "dependencies": [ + "so:libaudit.so.1", + "so:libauparse.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0" + ], + "provides": [ + "cmd:audisp-remote", + "cmd:audispd", + "cmd:auditctl", + "cmd:auditd", + "cmd:augenrules", + "cmd:aulast", + "cmd:aulastlog", + "cmd:aureport", + "cmd:ausearch", + "cmd:ausyscall", + "cmd:autrace", + "cmd:auvirt" + ] + }, + "py-six": { + "versions": { + "1.11.0-r0": 1509465546 + }, + "origin": "py-six" + }, + "duply-doc": { + "versions": { + "2.0.3-r0": 1510588387 + }, + "origin": "duply" + }, + "xf86-video-ast-doc": { + "versions": { + "1.1.5-r1": 1510074917 + }, + "origin": "xf86-video-ast" + }, + "xcmsdb": { + "versions": { + "1.0.5-r0": 1509494048 + }, + "origin": "xcmsdb", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xcmsdb" + ] + }, + "xf86bigfontproto-dev": { + "versions": { + "1.2.0-r5": 1509461949 + }, + "origin": "xf86bigfontproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xf86bigfontproto=1.2.0" + ] + }, + "perl-dbd-sqlite-doc": { + "versions": { + "1.54-r1": 1509477495 + }, + "origin": "perl-dbd-sqlite" + }, + "nasm": { + "versions": { + "2.13.01-r0": 1509464902 + }, + "origin": "nasm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:nasm", + "cmd:ndisasm" + ] + }, + "perl-exporter-tiny-doc": { + "versions": { + "1.000000-r0": 1509468499 + }, + "origin": "perl-exporter-tiny" + }, + "usbutils-doc": { + "versions": { + "009-r0": 1511890229 + }, + "origin": "usbutils" + }, + "lvm2-openrc": { + "versions": { + "2.02.175-r0": 1509462402 + }, + "origin": "lvm2", + "dependencies": [ + "lvm2-libs=2.02.175-r0" + ] + }, + "polkit-gnome-lang": { + "versions": { + "0.105-r0": 1510075797 + }, + "origin": "polkit-gnome" + }, + "py-munkres": { + "versions": { + "1.0.12-r0": 1509468390 + }, + "origin": "py-munkres" + }, + "lua5.2-sircbot": { + "versions": { + "0.4-r1": 1509492356 + }, + "origin": "sircbot", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gtk-engines-crux": { + "versions": { + "2.21.0-r2": 1510071722 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "altermime": { + "versions": { + "0.3.11-r0": 1509481330 + }, + "origin": "altermime", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:altermime" + ] + }, + "presentproto": { + "versions": { + "1.1-r1": 1509466249 + }, + "origin": "presentproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:presentproto=1.1" + ] + }, + "perl-net-snmp-doc": { + "versions": { + "6.0.1-r2": 1509471906 + }, + "origin": "perl-net-snmp" + }, + "espeak": { + "versions": { + "1.48.04-r1": 1509472851 + }, + "origin": "espeak", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libespeak.so.1=1.1.48", + "cmd:espeak" + ] + }, + "xfce4-notifyd-lang": { + "versions": { + "0.3.6-r0": 1510075457 + }, + "origin": "xfce4-notifyd", + "dependencies": [ + "hicolor-icon-theme" + ] + }, + "xfdesktop-lang": { + "versions": { + "4.12.4-r0": 1510074490 + }, + "origin": "xfdesktop", + "dependencies": [ + "hicolor-icon-theme" + ] + }, + "nagios-plugins-by_ssh": { + "versions": { + "2.2.1-r3": 1510288494 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "openssh-client", + "so:libc.musl-x86_64.so.1" + ] + }, + "libcap-dev": { + "versions": { + "2.25-r1": 1509459572 + }, + "origin": "libcap", + "dependencies": [ + "linux-headers", + "libcap=2.25-r1", + "pkgconfig" + ], + "provides": [ + "pc:libcap=2.25" + ] + }, + "htop-doc": { + "versions": { + "2.0.2-r0": 1509494928 + }, + "origin": "htop" + }, + "lua-posix": { + "versions": { + "33.4.0-r0": 1509468348 + }, + "origin": "lua-posix" + }, + "perl-test-fatal": { + "versions": { + "0.014-r1": 1509464416 + }, + "origin": "perl-test-fatal", + "dependencies": [ + "perl-try-tiny" + ] + }, + "libxtst-dev": { + "versions": { + "1.2.3-r1": 1509466038 + }, + "origin": "libxtst", + "dependencies": [ + "inputproto", + "libxtst=1.2.3-r1", + "pc:recordproto", + "pc:x11", + "pc:xext", + "pc:xextproto", + "pc:xi", + "pkgconfig" + ], + "provides": [ + "pc:xtst=1.2.3" + ] + }, + "freetds-doc": { + "versions": { + "1.00.44-r0": 1509480207 + }, + "origin": "freetds" + }, + "exiv2-dev": { + "versions": { + "0.25-r0": 1509475472 + }, + "origin": "exiv2", + "dependencies": [ + "expat-dev", + "zlib-dev", + "exiv2=0.25-r0", + "pkgconfig" + ], + "provides": [ + "pc:exiv2=0.25" + ] + }, + "setpriv": { + "versions": { + "2.31.1-r0": 1541506295 + }, + "origin": "util-linux", + "dependencies": [ + "findmnt", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0" + ], + "provides": [ + "cmd:setpriv" + ] + }, + "sipp": { + "versions": { + "3.3-r9": 1510261312 + }, + "origin": "sipp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libpcap.so.1", + "so:libsctp.so.1", + "so:libssl.so.44", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:sipp" + ] + }, + "libgphoto2-dev": { + "versions": { + "2.5.14-r0": 1509481938 + }, + "origin": "libgphoto2", + "dependencies": [ + "libexif-dev", + "libusb-dev", + "libgphoto2=2.5.14-r0", + "pc:libexif>=0.6.13", + "pkgconfig" + ], + "provides": [ + "pc:libgphoto2=2.5.14", + "pc:libgphoto2_port=0.12.0", + "cmd:gphoto2-config", + "cmd:gphoto2-port-config" + ] + }, + "jasper": { + "versions": { + "2.0.14-r0": 1509475517 + }, + "origin": "jasper", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjasper.so.4" + ], + "provides": [ + "cmd:imgcmp", + "cmd:imginfo", + "cmd:jasper" + ] + }, + "shared-mime-info": { + "versions": { + "1.9-r0": 1509464467 + }, + "origin": "shared-mime-info", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:update-mime-database" + ] + }, + "qemu-system-tricore": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-tricore" + ] + }, + "libid3tag-dev": { + "versions": { + "0.15.1b-r6": 1509470495 + }, + "origin": "libid3tag", + "dependencies": [ + "libid3tag=0.15.1b-r6", + "pkgconfig" + ], + "provides": [ + "pc:id3tag=0.15.1b" + ] + }, + "tarsnap": { + "versions": { + "1.0.39-r2": 1510258955 + }, + "origin": "tarsnap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libz.so.1" + ], + "provides": [ + "cmd:tarsnap", + "cmd:tarsnap-keygen", + "cmd:tarsnap-keymgmt", + "cmd:tarsnap-keyregen", + "cmd:tarsnap-recrypt" + ] + }, + "bitlbee": { + "versions": { + "3.5.1-r1": 1510259355 + }, + "origin": "bitlbee", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libssl.so.44" + ], + "provides": [ + "cmd:bitlbee" + ] + }, + "flake8": { + "versions": { + "3.4.1-r0": 1509492641 + }, + "origin": "flake8", + "dependencies": [ + "python2", + "py-setuptools", + "py-mccabe", + "py-pep8", + "pyflakes" + ], + "provides": [ + "cmd:flake8" + ] + }, + "perl-class-accessor-chained": { + "versions": { + "0.01-r0": 1509474101 + }, + "origin": "perl-class-accessor-chained", + "dependencies": [ + "perl-class-accessor" + ] + }, + "perl-file-next": { + "versions": { + "1.16-r0": 1509491910 + }, + "origin": "perl-file-next" + }, + "xmodmap-doc": { + "versions": { + "1.0.9-r2": 1509473722 + }, + "origin": "xmodmap" + }, + "lua-rrd": { + "versions": { + "1.5.6-r3": 1512296349 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5", + "so:librrd.so.4" + ] + }, + "asterisk-dbg": { + "versions": { + "15.6.1-r0": 1537795340, + "15.6.2-r0": 1568705001 + }, + "origin": "asterisk" + }, + "snownews-doc": { + "versions": { + "1.5.12-r6": 1510260845 + }, + "origin": "snownews" + }, + "pgtcl-doc": { + "versions": { + "2.1.0-r0": 1509489161 + }, + "origin": "pgtcl" + }, + "py3-yaml": { + "versions": { + "3.12-r1": 1509485806 + }, + "origin": "py-yaml", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0", + "so:libyaml-0.so.2" + ] + }, + "lua5.2-sec": { + "versions": { + "0.6-r3": 1510260656 + }, + "origin": "lua-sec", + "dependencies": [ + "lua5.2-socket", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "libnetfilter_acct-dev": { + "versions": { + "1.0.3-r0": 1509480361 + }, + "origin": "libnetfilter_acct", + "dependencies": [ + "libnetfilter_acct=1.0.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_acct=1.0.3" + ] + }, + "indent-lang": { + "versions": { + "2.2.11-r1": 1509496389 + }, + "origin": "indent" + }, + "kamailio-websocket": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libunistring.so.2" + ] + }, + "tcl-doc": { + "versions": { + "8.6.7-r0": 1509460296 + }, + "origin": "tcl" + }, + "gst-plugins-bad-lang": { + "versions": { + "1.12.3-r2": 1512039893 + }, + "origin": "gst-plugins-bad" + }, + "py-gnutls": { + "versions": { + "3.1.1-r0": 1509489167 + }, + "origin": "py-gnutls" + }, + "rsync-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503664 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "dbus-x11": { + "versions": { + "1.10.24-r0": 1509465101, + "1.10.28-r0": 1560765707 + }, + "origin": "dbus", + "dependencies": [ + "dbus=1.10.28-r0", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3" + ], + "provides": [ + "cmd:dbus-launch" + ] + }, + "kamailio-debugger": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libprint.so.1", + "so:libsrutils.so.1" + ] + }, + "minizip-dev": { + "versions": { + "1.2.11-r0": 1509495061 + }, + "origin": "minizip", + "dependencies": [ + "minizip=1.2.11-r0", + "pkgconfig" + ], + "provides": [ + "pc:minizip=1.2.11" + ] + }, + "sed-doc": { + "versions": { + "4.4-r1": 1509462307 + }, + "origin": "sed" + }, + "abiword-plugin-pdb": { + "versions": { + "3.0.2-r1": 1510073367 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "tlsdate": { + "versions": { + "0.0.13-r5": 1510259209 + }, + "origin": "tlsdate", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libevent-2.1.so.6", + "so:libssl.so.44" + ], + "provides": [ + "cmd:tlsdate", + "cmd:tlsdate-helper", + "cmd:tlsdated" + ] + }, + "sshfs-doc": { + "versions": { + "2.10-r2": 1509489736 + }, + "origin": "sshfs" + }, + "openldap-overlay-all": { + "versions": { + "2.4.45-r3": 1510258132, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900515 + }, + "origin": "openldap", + "dependencies": [ + "openldap-overlay-accesslog", + "openldap-overlay-auditlog", + "openldap-overlay-collect", + "openldap-overlay-constraint", + "openldap-overlay-dds", + "openldap-overlay-deref", + "openldap-overlay-dyngroup", + "openldap-overlay-dynlist", + "openldap-overlay-memberof", + "openldap-overlay-ppolicy", + "openldap-overlay-proxycache", + "openldap-overlay-refint", + "openldap-overlay-retcode", + "openldap-overlay-rwm", + "openldap-overlay-seqmod", + "openldap-overlay-sssvlv", + "openldap-overlay-syncprov", + "openldap-overlay-translucent", + "openldap-overlay-unique", + "openldap-overlay-valsort" + ] + }, + "uwsgi-router_memcached": { + "versions": { + "2.0.17-r0": 1522154657 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-cairo": { + "versions": { + "1.10.0-r0": 1509471034 + }, + "origin": "py2-cairo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libpython2.7.so.1.0" + ] + }, + "python3": { + "versions": { + "3.6.8-r0": 1555928809, + "3.6.9-r0": 1571233996, + "3.6.9-r1": 1571314640 + }, + "origin": "python3", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libexpat.so.1", + "so:libffi.so.6", + "so:libgdbm.so.4", + "so:libgdbm_compat.so.4", + "so:liblzma.so.5", + "so:libncursesw.so.6", + "so:libpanelw.so.6", + "so:libreadline.so.7", + "so:libsqlite3.so.0", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "py3-pip", + "so:libpython3.6m.so.1.0=1.0", + "so:libpython3.so=0", + "cmd:2to3", + "cmd:2to3-3.6", + "cmd:easy_install-3.6", + "cmd:pip3", + "cmd:pip3.6", + "cmd:pydoc3", + "cmd:pydoc3.6", + "cmd:python3", + "cmd:python3.6", + "cmd:python3.6m", + "cmd:pyvenv", + "cmd:pyvenv-3.6" + ] + }, + "zsh": { + "versions": { + "5.4.2-r1": 1522503664 + }, + "origin": "zsh", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:zsh", + "cmd:zsh-5.4.2" + ] + }, + "iwlwifi-1000-ucode-doc": { + "versions": { + "39.31.5.1-r0": 1509483628 + }, + "origin": "iwlwifi-1000-ucode" + }, + "mc-doc": { + "versions": { + "4.8.20-r0": 1511871582 + }, + "origin": "mc" + }, + "perl-datetime-locale": { + "versions": { + "1.17-r0": 1510855741 + }, + "origin": "perl-datetime-locale", + "dependencies": [ + "perl", + "perl-list-moreutils", + "perl-params-validate", + "perl-dist-checkconflicts", + "perl-cpan-meta-check", + "perl-test-fatal", + "perl-test-requires", + "perl-test-warnings", + "perl-scalar-list-utils", + "perl-params-validationcompiler", + "perl-file-sharedir", + "perl-file-sharedir-install", + "perl-namespace-autoclean" + ] + }, + "libgcab-doc": { + "versions": { + "0.7-r1": 1510067822 + }, + "origin": "libgcab" + }, + "krb5-doc": { + "versions": { + "1.15.4-r0": 1548491443 + }, + "origin": "krb5" + }, + "conky-doc": { + "versions": { + "1.10.6-r0": 1512047603 + }, + "origin": "conky" + }, + "xfconf-doc": { + "versions": { + "4.12.1-r2": 1510067933 + }, + "origin": "xfconf" + }, + "xf86-input-evdev-doc": { + "versions": { + "2.10.5-r0": 1510074955 + }, + "origin": "xf86-input-evdev" + }, + "py2-zope-interface": { + "versions": { + "4.3.2-r1": 1509493281 + }, + "origin": "py-zope-interface", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "py-werkzeug": { + "versions": { + "0.11.15-r2": 1509551864 + }, + "origin": "py-werkzeug" + }, + "libxft-dev": { + "versions": { + "2.3.2-r2": 1509462101 + }, + "origin": "libxft", + "dependencies": [ + "zlib-dev", + "libxft=2.3.2-r2", + "pc:fontconfig", + "pc:freetype2", + "pc:xproto", + "pc:xrender", + "pkgconfig" + ], + "provides": [ + "pc:xft=2.3.2" + ] + }, + "libseccomp-dev": { + "versions": { + "2.3.2-r1": 1519818661 + }, + "origin": "libseccomp", + "dependencies": [ + "linux-headers", + "libseccomp=2.3.2-r1", + "pkgconfig" + ], + "provides": [ + "pc:libseccomp=2.3.2" + ] + }, + "collectd-sensors": { + "versions": { + "5.7.2-r0": 1510069648 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libsensors.so.4" + ] + }, + "gmime": { + "versions": { + "2.6.20-r0": 1510588183 + }, + "origin": "gmime", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libz.so.1" + ], + "provides": [ + "so:libgmime-2.6.so.0=0.620.0" + ] + }, + "znc-doc": { + "versions": { + "1.7.1-r0": 1531900839, + "1.7.1-r1": 1565877330 + }, + "origin": "znc" + }, + "perl-file-slurp-tiny-doc": { + "versions": { + "0.004-r0": 1509494486 + }, + "origin": "perl-file-slurp-tiny" + }, + "libxkbfile": { + "versions": { + "1.0.9-r2": 1509473691 + }, + "origin": "libxkbfile", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libxkbfile.so.1=1.0.2" + ] + }, + "polkit-lang": { + "versions": { + "0.105-r8": 1551780676, + "0.105-r9": 1563792450, + "0.105-r10": 1567523133 + }, + "origin": "polkit" + }, + "lua5.2-file-magic": { + "versions": { + "0.2-r0": 1509485935 + }, + "origin": "lua5.2-file-magic", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmagic.so.1" + ] + }, + "lua5.2-json4": { + "versions": { + "1.0.0-r2": 1509468368 + }, + "origin": "lua-json4" + }, + "ncurses-doc": { + "versions": { + "6.0_p20171125-r1": 1534862979 + }, + "origin": "ncurses" + }, + "pcsc-lite-doc": { + "versions": { + "1.8.22-r0": 1509474497 + }, + "origin": "pcsc-lite" + }, + "libavc1394-doc": { + "versions": { + "0.5.4-r1": 1509489638 + }, + "origin": "libavc1394" + }, + "libssh-dev": { + "versions": { + "0.7.6-r0": 1540400365 + }, + "origin": "libssh", + "dependencies": [ + "libssh=0.7.6-r0", + "pkgconfig" + ], + "provides": [ + "pc:libssh=0.7.6", + "pc:libssh_threads=0.7.6" + ] + }, + "syslinux-dev": { + "versions": { + "6.04_pre1-r1": 1509482642 + }, + "origin": "syslinux" + }, + "llvm5": { + "versions": { + "5.0.0-r0": 1510682603 + }, + "origin": "llvm5", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "llvm=5.0.0-r0", + "cmd:bugpoint", + "cmd:llc", + "cmd:lli", + "cmd:llvm-ar", + "cmd:llvm-as", + "cmd:llvm-bcanalyzer", + "cmd:llvm-c-test", + "cmd:llvm-cat", + "cmd:llvm-cov", + "cmd:llvm-cvtres", + "cmd:llvm-cxxdump", + "cmd:llvm-cxxfilt", + "cmd:llvm-diff", + "cmd:llvm-dis", + "cmd:llvm-dlltool", + "cmd:llvm-dsymutil", + "cmd:llvm-dwarfdump", + "cmd:llvm-dwp", + "cmd:llvm-extract", + "cmd:llvm-lib", + "cmd:llvm-link", + "cmd:llvm-lto", + "cmd:llvm-lto2", + "cmd:llvm-mc", + "cmd:llvm-mcmarkup", + "cmd:llvm-modextract", + "cmd:llvm-mt", + "cmd:llvm-nm", + "cmd:llvm-objdump", + "cmd:llvm-opt-report", + "cmd:llvm-pdbutil", + "cmd:llvm-profdata", + "cmd:llvm-ranlib", + "cmd:llvm-readelf", + "cmd:llvm-readobj", + "cmd:llvm-rtdyld", + "cmd:llvm-size", + "cmd:llvm-split", + "cmd:llvm-stress", + "cmd:llvm-strings", + "cmd:llvm-symbolizer", + "cmd:llvm-tblgen", + "cmd:llvm-xray", + "cmd:opt", + "cmd:sancov", + "cmd:sanstats", + "cmd:verify-uselistorder" + ] + }, + "xf86-input-mouse": { + "versions": { + "1.9.2-r0": 1510074098 + }, + "origin": "xf86-input-mouse", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "chrony": { + "versions": { + "3.2-r2": 1518706488 + }, + "origin": "chrony", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2" + ], + "provides": [ + "cmd:chronyc", + "cmd:chronyd" + ] + }, + "perl-cgi": { + "versions": { + "4.36-r0": 1509470551 + }, + "origin": "perl-cgi", + "dependencies": [ + "perl-html-parser" + ] + }, + "libtheora": { + "versions": { + "1.1.1-r13": 1510068273 + }, + "origin": "libtheora", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0" + ], + "provides": [ + "so:libtheora.so.0=0.3.10", + "so:libtheoradec.so.1=1.1.4", + "so:libtheoraenc.so.1=1.1.2" + ] + }, + "perl-cache-simple-timedexpiry-doc": { + "versions": { + "0.27-r1": 1510564524 + }, + "origin": "perl-cache-simple-timedexpiry" + }, + "perl-lwp-useragent-determined-doc": { + "versions": { + "1.07-r0": 1509495863 + }, + "origin": "perl-lwp-useragent-determined" + }, + "squid-lang-pt": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865669 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-test-requiresinternet": { + "versions": { + "0.05-r0": 1509464420 + }, + "origin": "perl-test-requiresinternet" + }, + "mesa-dri-vmwgfx": { + "versions": { + "17.2.4-r1": 1510741949 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "sysstat-doc": { + "versions": { + "11.6.0-r0": 1509491780 + }, + "origin": "sysstat" + }, + "mupdf-x11": { + "versions": { + "1.13.0-r0": 1533746007 + }, + "origin": "mupdf", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1", + "so:libmupdf.so.0", + "so:libmupdfthird.so.0" + ], + "provides": [ + "cmd:mupdf-x11" + ] + }, + "tslib-doc": { + "versions": { + "1.14-r0": 1510573237 + }, + "origin": "tslib" + }, + "uwsgi-curl_cron": { + "versions": { + "2.0.17-r0": 1522154654 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "perl-lwp-useragent-determined": { + "versions": { + "1.07-r0": 1509495867 + }, + "origin": "perl-lwp-useragent-determined", + "dependencies": [ + "perl", + "perl-libwww" + ] + }, + "gparted-doc": { + "versions": { + "0.30.0-r0": 1510073982 + }, + "origin": "gparted" + }, + "py2-tz": { + "versions": { + "2017.3-r0": 1510732131 + }, + "origin": "py-tz", + "dependencies": [ + "python2" + ] + }, + "libunique3": { + "versions": { + "3.0.2-r0": 1510069893 + }, + "origin": "libunique3", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk-3.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0" + ], + "provides": [ + "so:libunique-3.0.so.0=0.0.2" + ] + }, + "nagios-plugins-dns": { + "versions": { + "2.2.1-r3": 1510288494 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "bind-tools", + "so:libc.musl-x86_64.so.1" + ] + }, + "libx11-doc": { + "versions": { + "1.6.6-r0": 1538999955 + }, + "origin": "libx11" + }, + "perl-test-warnings": { + "versions": { + "0.026-r0": 1509468494 + }, + "origin": "perl-test-warnings" + }, + "asterisk-dev": { + "versions": { + "15.6.1-r0": 1537795340, + "15.6.2-r0": 1568705002 + }, + "origin": "asterisk", + "dependencies": [ + "asterisk" + ] + }, + "perl-list-moreutils-doc": { + "versions": { + "0.419-r1": 1509474021 + }, + "origin": "perl-list-moreutils" + }, + "curl-dev": { + "versions": { + "7.61.1-r2": 1551780214, + "7.61.1-r3": 1568722567 + }, + "origin": "curl", + "dependencies": [ + "libcurl=7.61.1-r3", + "pkgconfig" + ], + "provides": [ + "pc:libcurl=7.61.1", + "cmd:curl-config" + ] + }, + "squid-lang-fi": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865667 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "libisoburn-dev": { + "versions": { + "1.4.8-r0": 1509462217 + }, + "origin": "libisoburn", + "dependencies": [ + "libisoburn=1.4.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:libisoburn-1=1.4.8" + ] + }, + "clucene-dev": { + "versions": { + "2.3.3.4-r5": 1509495541 + }, + "origin": "clucene", + "dependencies": [ + "zlib-dev", + "boost-dev", + "clucene-contribs=2.3.3.4-r5", + "clucene=2.3.3.4-r5", + "pkgconfig" + ], + "provides": [ + "pc:libclucene-core=2.3.3.4" + ] + }, + "ruby-doc": { + "versions": { + "2.4.6-r0": 1557166818 + }, + "origin": "ruby" + }, + "xbacklight-doc": { + "versions": { + "1.2.1-r0": 1509491141 + }, + "origin": "xbacklight" + }, + "micro-tetris": { + "versions": { + "1.2.1-r0": 1509496068 + }, + "origin": "micro-tetris", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tetris" + ] + }, + "xbanish": { + "versions": { + "1.5-r0": 1509489791 + }, + "origin": "xbanish", + "dependencies": [ + "so:libX11.so.6", + "so:libXfixes.so.3", + "so:libXi.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xbanish" + ] + }, + "libxkbui-dev": { + "versions": { + "1.0.2-r8": 1509473777 + }, + "origin": "libxkbui", + "dependencies": [ + "xproto", + "libxkbui=1.0.2-r8", + "pc:kbproto", + "pc:x11", + "pc:xt", + "pkgconfig" + ], + "provides": [ + "pc:xkbui=1.0.2" + ] + }, + "sqlite-dev": { + "versions": { + "3.25.3-r0": 1548491335, + "3.25.3-r1": 1563951530, + "3.25.3-r2": 1571590443 + }, + "origin": "sqlite", + "dependencies": [ + "pkgconfig", + "sqlite-libs=3.25.3-r2" + ], + "provides": [ + "pc:sqlite3=3.25.3" + ] + }, + "squid-lang-fr": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "popt": { + "versions": { + "1.16-r7": 1509462411 + }, + "origin": "popt", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpopt.so.0=0.0.0" + ] + }, + "sylpheed-lang": { + "versions": { + "3.6.0-r1": 1510588302 + }, + "origin": "sylpheed", + "dependencies": [ + "pinentry-gtk" + ] + }, + "glibmm": { + "versions": { + "2.50.1-r0": 1509472980 + }, + "origin": "glibmm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgiomm-2.4.so.1=1.3.0", + "so:libglibmm-2.4.so.1=1.3.0", + "so:libglibmm_generate_extra_defs-2.4.so.1=1.3.0" + ] + }, + "perl-cgi-fast": { + "versions": { + "2.13-r0": 1511894072 + }, + "origin": "perl-cgi-fast", + "dependencies": [ + "perl-cgi", + "perl-fcgi" + ] + }, + "perl-module-util": { + "versions": { + "1.09-r0": 1509485619 + }, + "origin": "perl-module-util", + "provides": [ + "cmd:pm_which" + ] + }, + "perl-socket6": { + "versions": { + "0.28-r0": 1509494721 + }, + "origin": "perl-socket6", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "lua-asn1": { + "versions": { + "2.1.0-r1": 1514491124 + }, + "origin": "lua-asn1", + "dependencies": [ + "lua-stringy" + ] + }, + "nagios-plugins-mrtgtraf": { + "versions": { + "2.2.1-r3": 1510288496 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.1-penlight": { + "versions": { + "1.5.4-r0": 1509485590 + }, + "origin": "lua-penlight", + "dependencies": [ + "lua-penlight-shared", + "lua5.1-filesystem", + "lua-penlight-shared=1.5.4-r0" + ] + }, + "gdb-doc": { + "versions": { + "8.0.1-r3": 1509486145 + }, + "origin": "gdb" + }, + "lua5.3-augeas": { + "versions": { + "0.1.2-r3": 1509473126 + }, + "origin": "lua-augeas", + "dependencies": [ + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1" + ] + }, + "py3-sphinx": { + "versions": { + "1.6.5-r0": 1509480689 + }, + "origin": "py-sphinx", + "dependencies": [ + "make", + "py3-docutils", + "py3-jinja2", + "py3-pygments", + "py3-six", + "py3-sphinx_rtd_theme", + "py3-alabaster<0.8", + "py3-babel", + "py3-snowballstemmer", + "py3-imagesize", + "py3-requests", + "py3-sphinxcontrib-websupport", + "python3" + ], + "provides": [ + "cmd:sphinx-apidoc-3", + "cmd:sphinx-autogen-3", + "cmd:sphinx-build-3", + "cmd:sphinx-quickstart-3" + ] + }, + "bzr-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503663 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "wpa_supplicant-doc": { + "versions": { + "2.6-r9": 1534860111, + "2.6-r10": 1559719897, + "2.6-r11": 1568726389 + }, + "origin": "wpa_supplicant" + }, + "perl-importer-doc": { + "versions": { + "0.024-r0": 1509481614 + }, + "origin": "perl-importer" + }, + "ttf-droid-nonlatin": { + "versions": { + "20121017-r0": 1509491428 + }, + "origin": "ttf-droid", + "dependencies": [ + "fontconfig" + ] + }, + "perl-http-cookies-doc": { + "versions": { + "6.04-r0": 1511871378 + }, + "origin": "perl-http-cookies" + }, + "xfontsel-doc": { + "versions": { + "1.0.5-r1": 1509481102 + }, + "origin": "xfontsel" + }, + "xf86miscproto": { + "versions": { + "0.9.3-r4": 1509473656 + }, + "origin": "xf86miscproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xf86miscproto=0.9.3" + ] + }, + "sems-mailbox": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "sems-ivr" + ] + }, + "btrfs-progs-dev": { + "versions": { + "4.13.2-r0": 1509481818 + }, + "origin": "btrfs-progs", + "dependencies": [ + "btrfs-progs-libs=4.13.2-r0" + ] + }, + "nginx-mod-http-cache-purge": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "curl-doc": { + "versions": { + "7.61.1-r2": 1551780213, + "7.61.1-r3": 1568722567 + }, + "origin": "curl" + }, + "openldap-overlay-accesslog": { + "versions": { + "2.4.45-r3": 1510258134, + "2.4.46-r0": 1565073939, + "2.4.48-r0": 1566900516 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "lvm2-doc": { + "versions": { + "2.02.175-r0": 1509462402 + }, + "origin": "lvm2" + }, + "fuse-dev": { + "versions": { + "2.9.8-r0": 1532967982 + }, + "origin": "fuse", + "dependencies": [ + "fuse=2.9.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:fuse=2.9.8" + ] + }, + "perl-file-sharedir-install": { + "versions": { + "0.11-r0": 1511889856 + }, + "origin": "perl-file-sharedir-install" + }, + "libsm": { + "versions": { + "1.2.2-r1": 1509466263 + }, + "origin": "libsm", + "dependencies": [ + "so:libICE.so.6", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libSM.so.6=6.0.1" + ] + }, + "py-olefile": { + "versions": { + "0.43-r2": 1509489937 + }, + "origin": "py-olefile" + }, + "xf86-video-r128": { + "versions": { + "6.10.2-r0": 1510074869 + }, + "origin": "xf86-video-r128", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "compiler-rt": { + "versions": { + "5.0.0-r0": 1510683366 + }, + "origin": "compiler-rt" + }, + "perl-netaddr-ip-doc": { + "versions": { + "4.079-r1": 1509485728 + }, + "origin": "perl-netaddr-ip" + }, + "llvm5-dev": { + "versions": { + "5.0.0-r0": 1510682602 + }, + "origin": "llvm5", + "dependencies": [ + "llvm5=5.0.0-r0", + "llvm5-libs=5.0.0-r0", + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "llvm-dev=5.0.0-r0", + "cmd:llvm-config" + ] + }, + "acf-db-lib": { + "versions": { + "0.2.1-r2": 1510068616 + }, + "origin": "acf-db", + "dependencies": [ + "acf-core", + "lua", + "acf-lib" + ] + }, + "spl-vanilla-dev": { + "versions": { + "4.9.161-r0": 1551779820, + "4.9.182-r0": 1560866250 + }, + "origin": "spl-vanilla", + "dependencies": [ + "linux-vanilla-dev=4.9.182-r0" + ] + }, + "nagios-plugins-ntp": { + "versions": { + "2.2.1-r3": 1510288497 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "collectd-bind": { + "versions": { + "5.7.2-r0": 1510069649 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libxml2.so.2" + ] + }, + "snownews-lang": { + "versions": { + "1.5.12-r6": 1510260845 + }, + "origin": "snownews", + "dependencies": [ + "ncurses" + ] + }, + "lame-doc": { + "versions": { + "3.100-r0": 1510088509 + }, + "origin": "lame" + }, + "perl-class-mix-doc": { + "versions": { + "0.005-r0": 1509470458 + }, + "origin": "perl-class-mix" + }, + "nss-pam-ldapd-doc": { + "versions": { + "0.9.8-r0": 1509483509 + }, + "origin": "nss-pam-ldapd" + }, + "libquvi": { + "versions": { + "0.9.4-r3": 1509492381 + }, + "origin": "libquvi", + "dependencies": [ + "libquvi-scripts", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libgcrypt.so.20", + "so:libglib-2.0.so.0", + "so:libgpg-error.so.0", + "so:libintl.so.8", + "so:liblua.so.5", + "so:libproxy.so.1" + ], + "provides": [ + "so:libquvi-0.9-0.9.4.so=0" + ] + }, + "aspell-de": { + "versions": { + "20030222-r1": 1509480778 + }, + "origin": "aspell-de" + }, + "motif-dev": { + "versions": { + "2.3.4-r2": 1509495434 + }, + "origin": "motif", + "dependencies": [ + "libx11-dev", + "libxft-dev", + "libxt-dev", + "libxpm-dev", + "libxext-dev", + "xbitmaps", + "motif=2.3.4-r2" + ] + }, + "zmap": { + "versions": { + "2.1.1-r1": 1510310599 + }, + "origin": "zmap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libjson-c.so.2", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:zblacklist", + "cmd:zmap", + "cmd:ztee" + ] + }, + "xf86-input-synaptics": { + "versions": { + "1.9.0-r1": 1510073651 + }, + "origin": "xf86-input-synaptics", + "dependencies": [ + "so:libX11.so.6", + "so:libXi.so.6", + "so:libXtst.so.6", + "so:libc.musl-x86_64.so.1", + "so:libevdev.so.2" + ], + "provides": [ + "cmd:synclient", + "cmd:syndaemon" + ] + }, + "freeswitch-sounds-ru-RU-elena-32000": { + "versions": { + "1.0.12-r1": 1509491857 + }, + "origin": "freeswitch-sounds-ru-RU-elena-32000" + }, + "lz4-tests": { + "versions": { + "1.8.0-r1": 1509461405 + }, + "origin": "lz4" + }, + "nagios-plugins-overcr": { + "versions": { + "2.2.1-r3": 1510288497 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-module-build": { + "versions": { + "0.4224-r0": 1509459457 + }, + "origin": "perl-module-build", + "provides": [ + "cmd:config_data" + ] + }, + "lua-xctrl-doc": { + "versions": { + "2015.04.10-r2": 1509475303 + }, + "origin": "lua-xctrl" + }, + "mupdf-doc": { + "versions": { + "1.13.0-r0": 1533746007 + }, + "origin": "mupdf" + }, + "ldoc": { + "versions": { + "1.4.6-r0": 1509486331 + }, + "origin": "ldoc", + "dependencies": [ + "lua5.2-penlight", + "lua5.2" + ], + "provides": [ + "cmd:ldoc" + ] + }, + "libdc1394": { + "versions": { + "2.2.5-r1": 1509480972 + }, + "origin": "libdc1394", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libraw1394.so.11", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libdc1394.so.22=22.2.1", + "cmd:dc1394_reset_bus" + ] + }, + "f2fs-tools-doc": { + "versions": { + "1.6.1-r0": 1509483522 + }, + "origin": "f2fs-tools" + }, + "openldap-back-bdb": { + "versions": { + "2.4.45-r3": 1510258132, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900515 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "gdbm-doc": { + "versions": { + "1.13-r1": 1509458650 + }, + "origin": "gdbm" + }, + "perl-datetime-locale-doc": { + "versions": { + "1.17-r0": 1510855741 + }, + "origin": "perl-datetime-locale" + }, + "freeradius-dbg": { + "versions": { + "3.0.15-r4": 1556202795, + "3.0.15-r5": 1566310602 + }, + "origin": "freeradius" + }, + "dtc": { + "versions": { + "1.4.4-r0": 1509476729 + }, + "origin": "dtc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:convert-dtsv0", + "cmd:dtc", + "cmd:dtdiff", + "cmd:fdtdump", + "cmd:fdtget", + "cmd:fdtput" + ] + }, + "opus-dev": { + "versions": { + "1.2.1-r1": 1509468917 + }, + "origin": "opus", + "dependencies": [ + "opus=1.2.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:opus=1.2.1" + ] + }, + "debootstrap-doc": { + "versions": { + "1.0.92-r0": 1510588271 + }, + "origin": "debootstrap" + }, + "libmatroska-dev": { + "versions": { + "1.4.8-r0": 1509479839 + }, + "origin": "libmatroska", + "dependencies": [ + "libmatroska=1.4.8-r0", + "pc:libebml", + "pkgconfig" + ], + "provides": [ + "pc:libmatroska=1.4.8" + ] + }, + "collectd-ping": { + "versions": { + "5.7.2-r0": 1510069655 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:liboping.so.0" + ] + }, + "qemu-m68k": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-m68k" + ] + }, + "flex": { + "versions": { + "2.6.4-r1": 1509456704 + }, + "origin": "flex", + "dependencies": [ + "m4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:flex", + "cmd:flex++", + "cmd:lex" + ] + }, + "libvorbis": { + "versions": { + "1.3.6-r2": 1549615971 + }, + "origin": "libvorbis", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0" + ], + "provides": [ + "so:libvorbis.so.0=0.4.8", + "so:libvorbisenc.so.2=2.0.11", + "so:libvorbisfile.so.3=3.3.7" + ] + }, + "qemu-system-xtensa": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-xtensa" + ] + }, + "py2-wtforms": { + "versions": { + "2.1-r0": 1509476538 + }, + "origin": "py-wtforms", + "dependencies": [ + "python2" + ] + }, + "perl-extutils-pkgconfig": { + "versions": { + "1.16-r1": 1509491043 + }, + "origin": "perl-extutils-pkgconfig" + }, + "acf-openssh": { + "versions": { + "0.11.2-r0": 1510075988 + }, + "origin": "acf-openssh", + "dependencies": [ + "acf-core", + "openssh" + ] + }, + "gtkspell": { + "versions": { + "2.0.16-r6": 1510069682 + }, + "origin": "gtkspell", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libenchant.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libgtkspell.so.0=0.0.0" + ] + }, + "apcupsd-doc": { + "versions": { + "3.14.14-r0": 1509495261 + }, + "origin": "apcupsd" + }, + "postfix": { + "versions": { + "3.2.4-r1": 1510285341 + }, + "origin": "postfix", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libdb-5.3.so", + "so:libsasl2.so.3", + "so:libssl.so.44" + ], + "provides": [ + "so:libpostfix-dns.so=0", + "so:libpostfix-global.so=0", + "so:libpostfix-master.so=0", + "so:libpostfix-tls.so=0", + "so:libpostfix-util.so=0", + "cmd:mailq", + "cmd:newaliases", + "cmd:postalias", + "cmd:postcat", + "cmd:postconf", + "cmd:postdrop", + "cmd:postfix", + "cmd:postkick", + "cmd:postlock", + "cmd:postlog", + "cmd:postmap", + "cmd:postmulti", + "cmd:postqueue", + "cmd:postsuper", + "cmd:sendmail" + ] + }, + "sipcalc": { + "versions": { + "1.1.6-r0": 1509492402 + }, + "origin": "sipcalc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sipcalc" + ] + }, + "sysfsutils-dev": { + "versions": { + "2.1.0-r8": 1509475364 + }, + "origin": "sysfsutils", + "dependencies": [ + "sysfsutils=2.1.0-r8" + ] + }, + "lua5.3-alt-getopt": { + "versions": { + "0.7-r8": 1509480079 + }, + "origin": "lua-alt-getopt" + }, + "mod_dav_svn": { + "versions": { + "1.9.7-r0": 1510314501, + "1.9.12-r0": 1565086191 + }, + "origin": "subversion", + "dependencies": [ + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libsvn_delta-1.so.0", + "so:libsvn_fs-1.so.0", + "so:libsvn_repos-1.so.0", + "so:libsvn_subr-1.so.0" + ] + }, + "atop": { + "versions": { + "2.3.0-r1": 1509489335 + }, + "origin": "atop", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:atop", + "cmd:atop-2.3.0", + "cmd:atopacctd", + "cmd:atopsar", + "cmd:atopsar-2.3.0" + ] + }, + "usbutils": { + "versions": { + "009-r0": 1511890229 + }, + "origin": "usbutils", + "dependencies": [ + "hwdata-usb", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "pc:usbutils=009", + "cmd:lsusb", + "cmd:lsusb.py", + "cmd:usb-devices", + "cmd:usbhid-dump" + ] + }, + "apache-mod-auth-radius": { + "versions": { + "1.5.8-r3": 1509483219 + }, + "origin": "apache-mod-auth-radius", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1" + ] + }, + "aconf": { + "versions": { + "0.6.5-r0": 1510073710 + }, + "origin": "aconf", + "dependencies": [ + "lua5.2-augeas", + "lua5.2-b64", + "lua5.2-cjson", + "lua5.2-crypto", + "lua5.2-file-magic", + "lua5.2-openrc", + "lua5.2-posix", + "lua5.2-stringy", + "uwsgi", + "uwsgi-lua" + ], + "provides": [ + "cmd:aconfd" + ] + }, + "cegui06-dev": { + "versions": { + "0.6.2b-r14": 1510068747 + }, + "origin": "cegui06", + "dependencies": [ + "cegui06=0.6.2b-r14", + "pkgconfig" + ], + "provides": [ + "pc:CEGUI-OPENGL=0.6.2", + "pc:CEGUI=0.6.2" + ] + }, + "xf86-video-dummy": { + "versions": { + "0.3.8-r0": 1510072535 + }, + "origin": "xf86-video-dummy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "slim-doc": { + "versions": { + "1.3.6-r8": 1510067547 + }, + "origin": "slim" + }, + "py-pillow": { + "versions": { + "4.3.0-r0": 1509489960 + }, + "origin": "py-pillow", + "dependencies": [ + "py-olefile" + ] + }, + "perl-test-simple-doc": { + "versions": { + "1.302118-r0": 1512052687 + }, + "origin": "perl-test-simple" + }, + "py-flask-wtf": { + "versions": { + "0.14.2-r0": 1509476546 + }, + "origin": "py-flask-wtf", + "dependencies": [ + "py-flask", + "py-wtforms" + ] + }, + "font-misc-ethiopic": { + "versions": { + "1.0.3-r0": 1509479843 + }, + "origin": "font-misc-ethiopic", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "py2-flask-wtf": { + "versions": { + "0.14.2-r0": 1509476545 + }, + "origin": "py-flask-wtf", + "dependencies": [ + "py2-flask", + "py2-wtforms", + "python2" + ] + }, + "librrd": { + "versions": { + "1.5.6-r3": 1512296349 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpng16.so.16", + "so:libxml2.so.2" + ], + "provides": [ + "so:librrd.so.4=4.3.5" + ] + }, + "py-boto": { + "versions": { + "2.47.0-r0": 1509472877 + }, + "origin": "py-boto", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:asadmin", + "cmd:bundle_image", + "cmd:cfadmin", + "cmd:cq", + "cmd:cwutil", + "cmd:dynamodb_dump", + "cmd:dynamodb_load", + "cmd:elbadmin", + "cmd:fetch_file", + "cmd:glacier", + "cmd:instance_events", + "cmd:kill_instance", + "cmd:launch_instance", + "cmd:list_instances", + "cmd:lss3", + "cmd:mturk", + "cmd:pyami_sendmail", + "cmd:route53", + "cmd:s3put", + "cmd:sdbadmin", + "cmd:taskadmin" + ] + }, + "ttf-droid": { + "versions": { + "20121017-r0": 1509491428 + }, + "origin": "ttf-droid", + "dependencies": [ + "fontconfig" + ] + }, + "compat-pvgrub": { + "versions": { + "1-r0": 1509476705 + }, + "origin": "compat-pvgrub", + "dependencies": [ + "/bin/sh" + ], + "provides": [ + "cmd:update-pvgrub" + ] + }, + "consolekit2": { + "versions": { + "1.2.0-r2": 1510067530 + }, + "origin": "consolekit2", + "dependencies": [ + "polkit", + "eudev", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libpolkit-gobject-1.so.0", + "so:libudev.so.1", + "so:libz.so.1" + ], + "provides": [ + "consolekit=1.2.0", + "so:libck-connector.so.0=0.0.0", + "so:libconsolekit.so.1=1.0.0", + "cmd:ck-history", + "cmd:ck-launch-session", + "cmd:ck-list-sessions", + "cmd:ck-log-system-restart", + "cmd:ck-log-system-start", + "cmd:ck-log-system-stop", + "cmd:console-kit-daemon" + ] + }, + "xinit": { + "versions": { + "1.3.4-r1": 1509473741 + }, + "origin": "xinit", + "dependencies": [ + "xauth", + "mcookie", + "xmodmap", + "xrdb", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:startx", + "cmd:xinit" + ] + }, + "libxshmfence-dev": { + "versions": { + "1.2-r2": 1509466255 + }, + "origin": "libxshmfence", + "dependencies": [ + "linux-headers", + "libxshmfence=1.2-r2", + "pkgconfig" + ], + "provides": [ + "pc:xshmfence=1.2" + ] + }, + "abiword-plugin-iscii": { + "versions": { + "3.0.2-r1": 1510073362 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "qemu-mipsn32el": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mipsn32el" + ] + }, + "run-parts": { + "versions": { + "4.8.2-r0": 1509470087 + }, + "origin": "run-parts", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:run-parts" + ] + }, + "lua5.2-mosquitto": { + "versions": { + "0.2-r1": 1509496427 + }, + "origin": "lua-mosquitto", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ] + }, + "font-misc-meltho": { + "versions": { + "1.0.3-r0": 1509494119 + }, + "origin": "font-misc-meltho", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-devel-globaldestruction": { + "versions": { + "0.14-r0": 1509475925 + }, + "origin": "perl-devel-globaldestruction", + "dependencies": [ + "perl-sub-exporter-progressive" + ] + }, + "perl-sys-hostname-long": { + "versions": { + "1.5-r0": 1509489405 + }, + "origin": "perl-sys-hostname-long", + "dependencies": [ + "perl" + ] + }, + "lua5.2-lub": { + "versions": { + "1.1.0-r1": 1509475704 + }, + "origin": "lua-lub", + "dependencies": [ + "lua5.2", + "lua5.2-filesystem" + ] + }, + "checkbashisms": { + "versions": { + "2.0.0.2-r5": 1509491256 + }, + "origin": "checkbashisms", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:checkbashisms" + ] + }, + "dropbear-ssh": { + "versions": { + "2018.76-r2": 1537439117 + }, + "origin": "dropbear", + "dependencies": [ + "dropbear-dbclient", + "!openssh-client", + "dropbear-dbclient=2018.76-r2" + ] + }, + "libdvdnav": { + "versions": { + "5.0.3-r0": 1509481834 + }, + "origin": "libdvdnav", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdvdread.so.4" + ], + "provides": [ + "so:libdvdnav.so.4=4.2.0" + ] + }, + "py-farstream0.1": { + "versions": { + "0.1.2-r2": 1510933113 + }, + "origin": "farstream0.1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfarstream-0.1.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-0.10.so.0" + ] + }, + "lua-lzlib": { + "versions": { + "0.4.3-r0": 1509494174 + }, + "origin": "lua-lzlib" + }, + "libnetfilter_queue-dev": { + "versions": { + "1.0.2-r0": 1509469244 + }, + "origin": "libnetfilter_queue", + "dependencies": [ + "libnetfilter_queue=1.0.2-r0", + "pc:libnfnetlink", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_queue=1.0.2" + ] + }, + "perl-data-uuid": { + "versions": { + "1.221-r1": 1509477323 + }, + "origin": "perl-data-uuid", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "procps-doc": { + "versions": { + "3.3.12-r3": 1509492055 + }, + "origin": "procps" + }, + "xwininfo": { + "versions": { + "1.1.3-r0": 1509484045 + }, + "origin": "xwininfo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-shape.so.0", + "so:libxcb.so.1" + ], + "provides": [ + "cmd:xwininfo" + ] + }, + "tk-dev": { + "versions": { + "8.6.6-r1": 1509462117 + }, + "origin": "tk", + "dependencies": [ + "tcl-dev", + "libx11-dev", + "libxft-dev", + "fontconfig-dev", + "pc:tcl>=8.6", + "pkgconfig" + ], + "provides": [ + "pc:tk=8.6.6" + ] + }, + "rsyslog-mysql": { + "versions": { + "8.31.0-r0": 1512031980, + "8.31.0-r1": 1571767017 + }, + "origin": "rsyslog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "openldap-clients": { + "versions": { + "2.4.45-r3": 1510258132, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900514 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libsasl2.so.3" + ], + "provides": [ + "cmd:ldapadd", + "cmd:ldapcompare", + "cmd:ldapdelete", + "cmd:ldapexop", + "cmd:ldapmodify", + "cmd:ldapmodrdn", + "cmd:ldappasswd", + "cmd:ldapsearch", + "cmd:ldapurl", + "cmd:ldapwhoami" + ] + }, + "gnome-doc-utils": { + "versions": { + "0.20.10-r2": 1509627733 + }, + "origin": "gnome-doc-utils", + "dependencies": [ + "python2", + "docbook-xml", + "rarian", + "py2-libxml2", + "libxslt" + ], + "provides": [ + "cmd:gnome-doc-prepare", + "cmd:gnome-doc-tool", + "cmd:xml2po" + ] + }, + "py-cparser": { + "versions": { + "2.18-r0": 1509483349 + }, + "origin": "py-cparser" + }, + "libidl-dev": { + "versions": { + "0.8.14-r2": 1510928272 + }, + "origin": "libidl", + "dependencies": [ + "glib-dev", + "libidl=0.8.14-r2", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libIDL-2.0=0.8.14" + ] + }, + "lcms-dev": { + "versions": { + "1.19-r6": 1509477352 + }, + "origin": "lcms", + "dependencies": [ + "liblcms=1.19-r6", + "pkgconfig" + ], + "provides": [ + "pc:lcms=1.19" + ] + }, + "daq": { + "versions": { + "2.0.6-r2": 1510839244 + }, + "origin": "daq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "so:libdaq.so.2=2.0.4", + "so:libsfbpf.so.0=0.0.1" + ] + }, + "mg": { + "versions": { + "20171014-r0": 1509494079 + }, + "origin": "mg", + "dependencies": [ + "so:libbsd.so.0", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:mg" + ] + }, + "py3-backports_abc": { + "versions": { + "0.4-r3": 1509552782 + }, + "origin": "py-backports_abc", + "dependencies": [ + "python3" + ] + }, + "libvirt-daemon": { + "versions": { + "3.9.0-r1": 1510088351, + "5.5.0-r0": 1562165539 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-client", + "bridge-utils", + "dmidecode", + "dnsmasq", + "ebtables", + "ip6tables", + "iptables", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libintl.so.8", + "so:libtirpc.so.3", + "so:libvirt-lxc.so.0", + "so:libvirt-qemu.so.0", + "so:libvirt.so.0" + ], + "provides": [ + "cmd:libvirtd" + ] + }, + "sshguard-doc": { + "versions": { + "2.0.0-r1": 1509483910 + }, + "origin": "sshguard" + }, + "iptables-dev": { + "versions": { + "1.6.1-r1": 1509469980 + }, + "origin": "iptables", + "dependencies": [ + "linux-headers", + "iptables=1.6.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:libip4tc=1.6.1", + "pc:libip6tc=1.6.1", + "pc:libipq=1.6.1", + "pc:libiptc=1.6.1", + "pc:xtables=1.6.1" + ] + }, + "acf-asterisk": { + "versions": { + "0.7.0-r2": 1510076281 + }, + "origin": "acf-asterisk", + "dependencies": [ + "acf-core", + "asterisk" + ] + }, + "perl-subversion": { + "versions": { + "1.9.7-r0": 1510314502, + "1.9.12-r0": 1565086191 + }, + "origin": "subversion", + "dependencies": [ + "so:libapr-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libsvn_client-1.so.0", + "so:libsvn_delta-1.so.0", + "so:libsvn_diff-1.so.0", + "so:libsvn_fs-1.so.0", + "so:libsvn_ra-1.so.0", + "so:libsvn_repos-1.so.0", + "so:libsvn_subr-1.so.0", + "so:libsvn_wc-1.so.0" + ], + "provides": [ + "so:libsvn_swig_perl-1.so.0=0.0.0" + ] + }, + "execline": { + "versions": { + "2.3.0.3-r0": 1509488629 + }, + "origin": "execline", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libskarnet.so.2.6" + ], + "provides": [ + "so:libexecline.so.2.3=2.3.0.3", + "cmd:background", + "cmd:backtick", + "cmd:cd", + "cmd:define", + "cmd:dollarat", + "cmd:elgetopt", + "cmd:elgetpositionals", + "cmd:elglob", + "cmd:emptyenv", + "cmd:exec", + "cmd:execlineb", + "cmd:exit", + "cmd:export", + "cmd:fdblock", + "cmd:fdclose", + "cmd:fdmove", + "cmd:fdreserve", + "cmd:fdswap", + "cmd:forbacktickx", + "cmd:foreground", + "cmd:forstdin", + "cmd:forx", + "cmd:getcwd", + "cmd:getpid", + "cmd:heredoc", + "cmd:homeof", + "cmd:if", + "cmd:ifelse", + "cmd:ifte", + "cmd:ifthenelse", + "cmd:import", + "cmd:importas", + "cmd:loopwhilex", + "cmd:multidefine", + "cmd:multisubstitute", + "cmd:pipeline", + "cmd:piperw", + "cmd:redirfd", + "cmd:runblock", + "cmd:shift", + "cmd:trap", + "cmd:tryexec", + "cmd:umask", + "cmd:unexport", + "cmd:wait", + "cmd:withstdinas" + ] + }, + "perl-class-load": { + "versions": { + "0.23-r0": 1509494278 + }, + "origin": "perl-class-load", + "dependencies": [ + "perl-data-optlist", + "perl-module-runtime", + "perl-module-implementation", + "perl-try-tiny", + "perl-namespace-clean", + "perl-package-stash" + ] + }, + "libotr3-dev": { + "versions": { + "3.2.1-r4": 1509481186 + }, + "origin": "libotr3", + "dependencies": [ + "libgcrypt-dev", + "libotr3=3.2.1-r4", + "pkgconfig" + ], + "provides": [ + "pc:libotr=3.1.0" + ] + }, + "libmikmod-doc": { + "versions": { + "3.3.11.1-r0": 1509481761 + }, + "origin": "libmikmod" + }, + "monkeysphere-doc": { + "versions": { + "0.41-r0": 1510588389 + }, + "origin": "monkeysphere" + }, + "openldap-overlay-ppolicy": { + "versions": { + "2.4.45-r3": 1510258135, + "2.4.46-r0": 1565073940, + "2.4.48-r0": 1566900517 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2", + "so:libltdl.so.7" + ] + }, + "asterisk-sounds-en": { + "versions": { + "15.6.1-r0": 1537795342, + "15.6.2-r0": 1568705004 + }, + "origin": "asterisk" + }, + "patch": { + "versions": { + "2.7.5-r2": 1519825694, + "2.7.5-r3": 1563906801, + "2.7.6-r0": 1565250814 + }, + "origin": "patch", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:patch" + ] + }, + "vimdiff": { + "versions": { + "8.0.1359-r0": 1512029968, + "8.0.1359-r1": 1559759711, + "8.0.1359-r2": 1561188677 + }, + "origin": "vim", + "dependencies": [ + "diffutils", + "vim=8.0.1359-r2" + ] + }, + "cyrus-sasl-gssapi": { + "versions": { + "2.1.26-r11": 1510258044 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgssapi.so.3" + ] + }, + "libtasn1-doc": { + "versions": { + "4.12-r3": 1519805758, + "4.12-r4": 1563955390 + }, + "origin": "libtasn1" + }, + "v86d": { + "versions": { + "0.1.10-r1": 1509491299 + }, + "origin": "v86d", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:v86d" + ] + }, + "nasm-doc": { + "versions": { + "2.13.01-r0": 1509464901 + }, + "origin": "nasm" + }, + "apache2-ldap": { + "versions": { + "2.4.39-r0": 1554306881, + "2.4.41-r0": 1566292327 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "apr-util-ldap", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ] + }, + "perl-regexp-common": { + "versions": { + "2016010801-r0": 1509485699 + }, + "origin": "perl-regexp-common" + }, + "acf-tcpproxy": { + "versions": { + "0.6.0-r2": 1510073457 + }, + "origin": "acf-tcpproxy", + "dependencies": [ + "acf-core", + "tcpproxy" + ] + }, + "freeglut-doc": { + "versions": { + "3.0.0-r0": 1510071761 + }, + "origin": "freeglut" + }, + "mpfr-dev": { + "versions": { + "3.1.5-r1": 1509457050 + }, + "origin": "mpfr3", + "dependencies": [ + "mpfr3=3.1.5-r1" + ] + }, + "gnupg1-doc": { + "versions": { + "1.4.23-r0": 1534431038 + }, + "origin": "gnupg1", + "provides": [ + "gnupg-doc=1.4.23-r0" + ] + }, + "samba-dc-libs": { + "versions": { + "4.7.6-r3": 1555491788 + }, + "origin": "samba", + "dependencies": [ + "samba-server=4.7.6-r3", + "samba-client=4.7.6-r3", + "samba-common-tools=4.7.6-r3", + "so:libMESSAGING-SEND-samba4.so", + "so:libMESSAGING-samba4.so", + "so:libasn1-samba4.so.8", + "so:libasn1util-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-ldap-samba4.so", + "so:libcli-nbt-samba4.so", + "so:libcliauth-samba4.so", + "so:libcom_err.so.2", + "so:libcommon-auth-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba4.so", + "so:libdcerpc.so.0", + "so:libdsdb-garbage-collect-tombstones-samba4.so", + "so:libevents-samba4.so", + "so:libflag-mapping-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgnutls.so.30", + "so:libhcrypto-samba4.so.5", + "so:libhx509-samba4.so.5", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:libmessages-dgm-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libnetif-samba4.so", + "so:libnpa-tstream-samba4.so", + "so:libpopt.so.0", + "so:libpython2.7.so.1.0", + "so:libreplace-samba4.so", + "so:libroken-samba4.so.19", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-net-samba4.so", + "so:libsamba-python-samba4.so", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libsmb-transport-samba4.so", + "so:libsmbclient-raw-samba4.so", + "so:libsocket-blocking-samba4.so", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libwbclient.so.0" + ], + "provides": [ + "so:libHDB-SAMBA4-samba4.so=0", + "so:libLIBWBCLIENT-OLD-samba4.so=0", + "so:libauth-unix-token-samba4.so=0", + "so:libauth4-samba4.so=0", + "so:libcluster-samba4.so=0", + "so:libdb-glue-samba4.so=0", + "so:libdcerpc-samr.so.0=0.0.1", + "so:libdcerpc-server.so.0=0.0.1", + "so:libdnsserver-common-samba4.so=0", + "so:libdsdb-module-samba4.so=0", + "so:libhdb-samba4.so.11=11.0.2", + "so:libkdc-samba4.so.2=2.0.0", + "so:libpac-samba4.so=0", + "so:libposix-eadb-samba4.so=0", + "so:libprocess-model-samba4.so=0", + "so:libsamba-policy.so.0=0.0.1", + "so:libservice-samba4.so=0", + "so:libshares-samba4.so=0" + ] + }, + "lua5.3-stdlib": { + "versions": { + "41.2.0-r0": 1509495246 + }, + "origin": "lua-stdlib" + }, + "perl-io-socket-ssl-doc": { + "versions": { + "2.048-r1": 1509474730 + }, + "origin": "perl-io-socket-ssl" + }, + "lua5.2-iconv": { + "versions": { + "7-r1": 1509493323 + }, + "origin": "lua-iconv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "kmod": { + "versions": { + "24-r0": 1509462317 + }, + "origin": "kmod", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:liblzma.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libkmod.so.2=2.3.2", + "cmd:depmod", + "cmd:insmod", + "cmd:kmod", + "cmd:lsmod", + "cmd:modinfo", + "cmd:modprobe", + "cmd:rmmod" + ] + }, + "cifs-utils": { + "versions": { + "6.7-r1": 1509475351 + }, + "origin": "cifs-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libkeyutils.so.1", + "so:libkrb5.so.3", + "so:libtalloc.so.2" + ], + "provides": [ + "cmd:cifs.upcall", + "cmd:mount.cifs" + ] + }, + "nano": { + "versions": { + "2.9.1-r0": 1512032033 + }, + "origin": "nano", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmagic.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:nano", + "cmd:rnano" + ] + }, + "libpthread-stubs": { + "versions": { + "0.3-r4": 1509461820 + }, + "origin": "libpthread-stubs", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:pthread-stubs=0.3" + ] + }, + "udev-init-scripts": { + "versions": { + "32-r1": 1509466061 + }, + "origin": "udev-init-scripts", + "dependencies": [ + "/bin/sh" + ] + }, + "perl-io-compress": { + "versions": { + "2.074-r0": 1509484002 + }, + "origin": "perl-io-compress", + "dependencies": [ + "perl", + "perl-compress-raw-bzip2", + "perl-compress-raw-zlib" + ] + }, + "v4l-utils-libs": { + "versions": { + "1.12.5-r1": 1510072045 + }, + "origin": "v4l-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8" + ], + "provides": [ + "so:libv4l1.so.0=0.0.0", + "so:libv4l2.so.0=0.0.0", + "so:libv4l2rds.so.0=0.0.0", + "so:libv4lconvert.so.0=0.0.0" + ] + }, + "pound-doc": { + "versions": { + "2.7-r4": 1510261014 + }, + "origin": "pound" + }, + "xhost-doc": { + "versions": { + "1.0.7-r1": 1509483059 + }, + "origin": "xhost" + }, + "freeswitch": { + "versions": { + "1.6.19-r0": 1510585358 + }, + "origin": "freeswitch", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libedit.so.0", + "so:libgcc_s.so.1", + "so:libilbc.so.0", + "so:libjpeg.so.8", + "so:libldns.so.1", + "so:liblua-5.2.so.0", + "so:libmp3lame.so.0", + "so:libmpg123.so.0", + "so:libncursesw.so.6", + "so:libodbc.so.2", + "so:libopus.so.0", + "so:libpcre.so.1", + "so:libportaudio.so.2", + "so:libpq.so.5", + "so:libshout.so.3", + "so:libsndfile.so.1", + "so:libspeex.so.1", + "so:libspeexdsp.so.1", + "so:libsqlite3.so.0", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libfreeswitch.so.1=1.0.0", + "cmd:freeswitch", + "cmd:fs_cli", + "cmd:fs_encode", + "cmd:fs_ivrd", + "cmd:fsxs", + "cmd:gentls_cert", + "cmd:tone2wav" + ] + }, + "libtheora-examples": { + "versions": { + "1.1.1-r13": 1510068273 + }, + "origin": "libtheora", + "dependencies": [ + "so:libSDL-1.2.so.0", + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libtheoradec.so.1", + "so:libtheoraenc.so.1", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2" + ], + "provides": [ + "cmd:dump_psnr", + "cmd:dump_video", + "cmd:encoder_example", + "cmd:player_example" + ] + }, + "ghi-doc": { + "versions": { + "1.2.0-r1": 1509552714 + }, + "origin": "ghi" + }, + "libasr-dev": { + "versions": { + "1.0.2-r6": 1510258973 + }, + "origin": "libasr", + "dependencies": [ + "libasr=1.0.2-r6" + ] + }, + "pcre2-doc": { + "versions": { + "10.30-r0": 1509461773 + }, + "origin": "pcre2" + }, + "perl-module-versions-report": { + "versions": { + "1.06-r0": 1509495045 + }, + "origin": "perl-module-versions-report", + "dependencies": [ + "perl" + ] + }, + "lighttpd-doc": { + "versions": { + "1.4.48-r0": 1511925914 + }, + "origin": "lighttpd" + }, + "perl-set-intspan": { + "versions": { + "1.19-r0": 1509480155 + }, + "origin": "perl-set-intspan" + }, + "gphoto2-doc": { + "versions": { + "2.5.14-r0": 1509493217 + }, + "origin": "gphoto2" + }, + "devicemaster-linux": { + "versions": { + "7.15-r0": 1509481943 + }, + "origin": "devicemaster-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:nslinkadmin", + "cmd:nslinkd", + "cmd:nslinkrelease", + "cmd:nslinktool" + ] + }, + "lutok-doc": { + "versions": { + "0.4-r1": 1509459744 + }, + "origin": "lutok" + }, + "xscreensaver-doc": { + "versions": { + "5.36-r0": 1510074348 + }, + "origin": "xscreensaver" + }, + "varnish-libs": { + "versions": { + "5.2.1-r0": 1511265090 + }, + "origin": "varnish", + "dependencies": [ + "gcc", + "libc-dev", + "libgcc", + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ], + "provides": [ + "so:libvarnishapi.so.1=1.0.6" + ] + }, + "libfdt": { + "versions": { + "1.4.4-r0": 1509476728 + }, + "origin": "dtc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfdt.so.1=0" + ] + }, + "xf86-video-i128-doc": { + "versions": { + "1.3.6-r8": 1510074907 + }, + "origin": "xf86-video-i128" + }, + "clang-dev": { + "versions": { + "5.0.0-r0": 1510683246 + }, + "origin": "clang", + "dependencies": [ + "clang=5.0.0-r0", + "clang-libs=5.0.0-r0" + ] + }, + "libcanberra-doc": { + "versions": { + "0.30-r1": 1510071975 + }, + "origin": "libcanberra" + }, + "spl-doc": { + "versions": { + "0.7.1-r0": 1509492193 + }, + "origin": "spl" + }, + "libwebp-tools": { + "versions": { + "0.6.0-r1": 1509473039 + }, + "origin": "libwebp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgif.so.7", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libtiff.so.5", + "so:libwebp.so.7", + "so:libwebpmux.so.3" + ], + "provides": [ + "cmd:cwebp", + "cmd:dwebp", + "cmd:gif2webp", + "cmd:img2webp", + "cmd:webpmux" + ] + }, + "compositeproto": { + "versions": { + "0.4.2-r4": 1509465978 + }, + "origin": "compositeproto", + "dependencies": [ + "fixesproto", + "pkgconfig" + ], + "provides": [ + "pc:compositeproto=0.4.2" + ] + }, + "git-subtree-doc": { + "versions": { + "2.15.3-r0": 1540401295, + "2.15.4-r0": 1576015196 + }, + "origin": "git" + }, + "libasyncns-doc": { + "versions": { + "0.8-r0": 1509490869 + }, + "origin": "libasyncns" + }, + "lua-inspect": { + "versions": { + "3.1.0-r1": 1509491027 + }, + "origin": "lua-inspect" + }, + "testdisk-doc": { + "versions": { + "7.0-r4": 1509491593 + }, + "origin": "testdisk" + }, + "lzo-dev": { + "versions": { + "2.10-r2": 1509462187 + }, + "origin": "lzo", + "dependencies": [ + "lzo=2.10-r2", + "pkgconfig" + ], + "provides": [ + "pc:lzo2=2.10" + ] + }, + "perl-io-html-doc": { + "versions": { + "1.001-r1": 1509464367 + }, + "origin": "perl-io-html" + }, + "py-django-widget-tweaks": { + "versions": { + "1.4.1-r0": 1509480739 + }, + "origin": "py-django-widget-tweaks", + "dependencies": [ + "py-django" + ] + }, + "subunit-libs": { + "versions": { + "1.2.0-r0": 1509481264 + }, + "origin": "subunit", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libcppunit_subunit.so.0=0.0.0", + "so:libsubunit.so.0=0.0.0" + ] + }, + "perl-async-mergepoint": { + "versions": { + "0.04-r0": 1509494970 + }, + "origin": "perl-async-mergepoint" + }, + "xorg-cf-files-doc": { + "versions": { + "1.0.6-r0": 1509488818 + }, + "origin": "xorg-cf-files" + }, + "py2-pylast": { + "versions": { + "1.9.0-r0": 1509491031 + }, + "origin": "py-pylast", + "dependencies": [ + "python2" + ] + }, + "nginx-vim": { + "versions": { + "1.12.2-r4": 1542814446 + }, + "origin": "nginx" + }, + "dmidecode-doc": { + "versions": { + "3.1-r0": 1509482141 + }, + "origin": "dmidecode" + }, + "xen-libs": { + "versions": { + "4.9.4-r0": 1551280495, + "4.9.4-r1": 1558103152 + }, + "origin": "xen", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libext2fs.so.2", + "so:liblzma.so.5", + "so:liblzo2.so.2", + "so:libnl-3.so.200", + "so:libnl-route-3.so.200", + "so:libuuid.so.1", + "so:libyajl.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libblktapctl.so.1.0=1.0.0", + "so:libfsimage.so.1.0=1.0.0", + "so:libvhd.so.1.0=1.0.0", + "so:libxencall.so.1=1.0", + "so:libxenctrl.so.4.9=4.9.0", + "so:libxendevicemodel.so.1=1.0", + "so:libxenevtchn.so.1=1.0", + "so:libxenforeignmemory.so.1=1.1", + "so:libxengnttab.so.1=1.1", + "so:libxenguest.so.4.9=4.9.0", + "so:libxenlight.so.4.9=4.9.0", + "so:libxenstat.so.0=0.0", + "so:libxenstore.so.3.0=3.0.3", + "so:libxentoollog.so.1=1.0", + "so:libxenvchan.so.4.9=4.9.0", + "so:libxlutil.so.4.9=4.9.0" + ] + }, + "pkgconf": { + "versions": { + "1.3.10-r0": 1509459814 + }, + "origin": "pkgconf", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "pkgconfig=1", + "so:libpkgconf.so.2=2.0.0", + "cmd:pkg-config", + "cmd:pkgconf" + ] + }, + "xfce4": { + "versions": { + "4.12.0-r1": 1510920462 + }, + "origin": "xfce4", + "dependencies": [ + "exo>=0.10.3", + "garcon>=0.4.0", + "gtk-xfce-engine>=3.2.0", + "libxfce4ui>=4.12.0", + "libxfce4util>=4.12.0", + "thunar>=1.6.6", + "ttf-dejavu", + "xfce4-appfinder>=4.12.0", + "xfce4-mixer", + "xfce4-panel>=4.12.0", + "xfce4-power-manager>=1.4.3", + "xfce4-session>=4.12.0", + "xfce4-settings>=4.12.0", + "xfce4-terminal", + "xfconf>=4.12.0", + "xfdesktop>=4.12.0", + "xfwm4>=4.12.0" + ] + }, + "pidgin-lang": { + "versions": { + "2.12.0-r2": 1510069750 + }, + "origin": "pidgin" + }, + "perl-socket-getaddrinfo-doc": { + "versions": { + "0.20-r2": 1509488746 + }, + "origin": "perl-socket-getaddrinfo" + }, + "tar-doc": { + "versions": { + "1.32-r0": 1551092030 + }, + "origin": "tar" + }, + "mini_httpd-doc": { + "versions": { + "1.27-r1": 1510260670 + }, + "origin": "mini_httpd" + }, + "libvirt-lang": { + "versions": { + "3.9.0-r1": 1510088351, + "5.5.0-r0": 1562165540 + }, + "origin": "libvirt", + "dependencies": [ + "lvm2" + ] + }, + "libnice-dev": { + "versions": { + "0.1.14-r2": 1510931344 + }, + "origin": "libnice", + "dependencies": [ + "libnice=0.1.14-r2", + "pc:gio-2.0>=2.44", + "pc:glib-2.0>=2.44", + "pc:gnutls>=2.12.0", + "pc:gobject-2.0>=2.44", + "pc:gthread-2.0", + "pkgconfig" + ], + "provides": [ + "pc:nice=0.1.14" + ] + }, + "flac-doc": { + "versions": { + "1.3.2-r0": 1509473288 + }, + "origin": "flac" + }, + "man": { + "versions": { + "1.14.3-r0": 1509459637 + }, + "origin": "mdocml", + "dependencies": [ + "mdocml" + ] + }, + "smartmontools-doc": { + "versions": { + "6.6-r0": 1510573357 + }, + "origin": "smartmontools" + }, + "py-paramiko": { + "versions": { + "2.4.2-r0": 1551364326 + }, + "origin": "py-paramiko", + "dependencies": [ + "py-asn1", + "py-cryptography", + "py-bcrypt", + "py-pynacl" + ] + }, + "xfce4-clipman-plugin-lang": { + "versions": { + "1.4.2-r0": 1510073453 + }, + "origin": "xfce4-clipman-plugin" + }, + "libotr-doc": { + "versions": { + "4.1.1-r1": 1509473950 + }, + "origin": "libotr" + }, + "xfdesktop-doc": { + "versions": { + "4.12.4-r0": 1510074490 + }, + "origin": "xfdesktop" + }, + "util-linux": { + "versions": { + "2.31.1-r0": 1541506295 + }, + "origin": "util-linux", + "dependencies": [ + "findmnt", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfdisk.so.1", + "so:libmount.so.1", + "so:libncursesw.so.6", + "so:libsmartcols.so.1", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:addpart", + "cmd:agetty", + "cmd:blkdiscard", + "cmd:blockdev", + "cmd:cal", + "cmd:chcpu", + "cmd:chmem", + "cmd:chrt", + "cmd:col", + "cmd:colcrt", + "cmd:colrm", + "cmd:column", + "cmd:ctrlaltdel", + "cmd:delpart", + "cmd:dmesg", + "cmd:eject", + "cmd:fallocate", + "cmd:fdformat", + "cmd:fdisk", + "cmd:fincore", + "cmd:findfs", + "cmd:flock", + "cmd:fsck", + "cmd:fsck.cramfs", + "cmd:fsck.minix", + "cmd:fsfreeze", + "cmd:fstrim", + "cmd:getopt", + "cmd:hexdump", + "cmd:hwclock", + "cmd:i386", + "cmd:ionice", + "cmd:ipcmk", + "cmd:ipcrm", + "cmd:ipcs", + "cmd:isosize", + "cmd:ldattach", + "cmd:linux32", + "cmd:linux64", + "cmd:logger", + "cmd:look", + "cmd:losetup", + "cmd:lsblk", + "cmd:lscpu", + "cmd:lsipc", + "cmd:lslocks", + "cmd:lslogins", + "cmd:lsmem", + "cmd:lsns", + "cmd:mesg", + "cmd:mkfs", + "cmd:mkfs.bfs", + "cmd:mkfs.cramfs", + "cmd:mkfs.minix", + "cmd:mkswap", + "cmd:more", + "cmd:mount", + "cmd:mountpoint", + "cmd:namei", + "cmd:nologin", + "cmd:nsenter", + "cmd:partx", + "cmd:pivot_root", + "cmd:prlimit", + "cmd:raw", + "cmd:readprofile", + "cmd:rename", + "cmd:renice", + "cmd:resizepart", + "cmd:rev", + "cmd:rfkill", + "cmd:rtcwake", + "cmd:script", + "cmd:scriptreplay", + "cmd:setarch", + "cmd:setsid", + "cmd:setterm", + "cmd:swaplabel", + "cmd:swapoff", + "cmd:swapon", + "cmd:switch_root", + "cmd:taskset", + "cmd:ul", + "cmd:umount", + "cmd:uname26", + "cmd:unshare", + "cmd:utmpdump", + "cmd:uuidgen", + "cmd:uuidparse", + "cmd:wall", + "cmd:wdctl", + "cmd:whereis", + "cmd:wipefs", + "cmd:x86_64", + "cmd:zramctl" + ] + }, + "py-hiredis": { + "versions": { + "0.1.4-r1": 1509552775 + }, + "origin": "py-hiredis", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.13", + "so:libpython2.7.so.1.0" + ] + }, + "audit-static": { + "versions": { + "2.7.7-r1": 1509491478 + }, + "origin": "audit" + }, + "glib-lang": { + "versions": { + "2.54.2-r0": 1509911133, + "2.54.2-r1": 1560764732 + }, + "origin": "glib" + }, + "libpri-dev": { + "versions": { + "1.6.0-r0": 1509476160 + }, + "origin": "libpri", + "dependencies": [ + "libpri=1.6.0-r0" + ] + }, + "perl-crypt-openssl-random": { + "versions": { + "0.11-r4": 1510260064 + }, + "origin": "perl-crypt-openssl-random", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "aspell-fr": { + "versions": { + "0.50_p3-r1": 1509495844 + }, + "origin": "aspell-fr" + }, + "dropbear-convert": { + "versions": { + "2018.76-r2": 1537439117 + }, + "origin": "dropbear", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dropbearconvert" + ] + }, + "gzip": { + "versions": { + "1.8-r0": 1509456994 + }, + "origin": "gzip", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:gunzip", + "cmd:gzexe", + "cmd:gzip", + "cmd:uncompress", + "cmd:zcat", + "cmd:zcmp", + "cmd:zdiff", + "cmd:zegrep", + "cmd:zfgrep", + "cmd:zforce", + "cmd:zgrep", + "cmd:zless", + "cmd:zmore", + "cmd:znew" + ] + }, + "perl-switch-doc": { + "versions": { + "2.17-r0": 1509475519 + }, + "origin": "perl-switch" + }, + "libnjb": { + "versions": { + "2.2.7-r4": 1509481679 + }, + "origin": "libnjb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-0.1.so.4" + ], + "provides": [ + "so:libnjb.so.5=5.1.1" + ] + }, + "gnome-vfs-dev": { + "versions": { + "2.24.4-r6": 1510931435 + }, + "origin": "gnome-vfs", + "dependencies": [ + "gconf-dev", + "libxml2-dev", + "dbus-glib-dev", + "gamin-dev", + "gnome-vfs=2.24.4-r6", + "pc:gconf-2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gnome-vfs-2.0=2.24.4", + "pc:gnome-vfs-module-2.0=2.24.4" + ] + }, + "perl-css-minifier-xs-doc": { + "versions": { + "0.09-r3": 1509489143 + }, + "origin": "perl-css-minifier-xs" + }, + "libgcj": { + "versions": { + "6.4.0-r5": 1509458076 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgmp.so.10", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libgcj-tools.so.17=17.0.0", + "so:libgcj.so.17=17.0.0", + "so:libgcj_bc.so.1=1.0.0", + "so:libgij.so.17=17.0.0", + "so:libjavamath.so=0", + "so:libjvm.so=0", + "cmd:aot-compile", + "cmd:gappletviewer", + "cmd:gc-analyze", + "cmd:gij", + "cmd:gjar", + "cmd:gjarsigner", + "cmd:gkeytool", + "cmd:gnative2ascii", + "cmd:gorbd", + "cmd:grmic", + "cmd:grmid", + "cmd:grmiregistry", + "cmd:gserialver", + "cmd:gtnameserv", + "cmd:jv-convert", + "cmd:rebuild-gcj-db" + ] + }, + "py-oauth2": { + "versions": { + "1.9.0-r0": 1509493379 + }, + "origin": "py-oauth2", + "dependencies": [ + "python2", + "py-httplib2" + ] + }, + "glib-dev": { + "versions": { + "2.54.2-r0": 1509911133, + "2.54.2-r1": 1560764732 + }, + "origin": "glib", + "dependencies": [ + "perl", + "python2", + "gettext-dev", + "zlib-dev", + "bzip2-dev", + "libffi-dev", + "util-linux-dev", + "glib=2.54.2-r1", + "pc:libpcre", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "pc:gio-2.0=2.54.2", + "pc:gio-unix-2.0=2.54.2", + "pc:glib-2.0=2.54.2", + "pc:gmodule-2.0=2.54.2", + "pc:gmodule-export-2.0=2.54.2", + "pc:gmodule-no-export-2.0=2.54.2", + "pc:gobject-2.0=2.54.2", + "pc:gthread-2.0=2.54.2", + "cmd:gdbus-codegen", + "cmd:glib-compile-resources", + "cmd:glib-genmarshal", + "cmd:glib-gettextize", + "cmd:glib-mkenums", + "cmd:gobject-query", + "cmd:gresource", + "cmd:gtester", + "cmd:gtester-report" + ] + }, + "perl-data-guid-doc": { + "versions": { + "0.049-r0": 1509479283 + }, + "origin": "perl-data-guid" + }, + "avahi-doc": { + "versions": { + "0.6.32-r4": 1509465446, + "0.6.32-r5": 1563345619 + }, + "origin": "avahi" + }, + "samba-common": { + "versions": { + "4.7.6-r3": 1555491786 + }, + "origin": "samba" + }, + "lvm2": { + "versions": { + "2.02.175-r0": 1509462403 + }, + "origin": "lvm2", + "dependencies": [ + "lvm2-libs=2.02.175-r0", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper-event.so.1.02", + "so:libdevmapper.so.1.02" + ], + "provides": [ + "cmd:lvchange", + "cmd:lvconvert", + "cmd:lvcreate", + "cmd:lvdisplay", + "cmd:lvextend", + "cmd:lvm", + "cmd:lvmconfig", + "cmd:lvmdiskscan", + "cmd:lvmsadc", + "cmd:lvmsar", + "cmd:lvreduce", + "cmd:lvremove", + "cmd:lvrename", + "cmd:lvresize", + "cmd:lvs", + "cmd:lvscan", + "cmd:pvchange", + "cmd:pvck", + "cmd:pvcreate", + "cmd:pvdisplay", + "cmd:pvmove", + "cmd:pvremove", + "cmd:pvresize", + "cmd:pvs", + "cmd:pvscan", + "cmd:vgcfgbackup", + "cmd:vgcfgrestore", + "cmd:vgchange", + "cmd:vgck", + "cmd:vgconvert", + "cmd:vgcreate", + "cmd:vgdisplay", + "cmd:vgexport", + "cmd:vgextend", + "cmd:vgimport", + "cmd:vgimportclone", + "cmd:vgmerge", + "cmd:vgmknodes", + "cmd:vgreduce", + "cmd:vgremove", + "cmd:vgrename", + "cmd:vgs", + "cmd:vgscan", + "cmd:vgsplit" + ] + }, + "perl-net-cidr-doc": { + "versions": { + "0.17-r0": 1509486329 + }, + "origin": "perl-net-cidr" + }, + "cadaver": { + "versions": { + "0.23.3-r3": 1510310613 + }, + "origin": "cadaver", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libneon.so.27" + ], + "provides": [ + "cmd:cadaver" + ] + }, + "boost-python3": { + "versions": { + "1.62.0-r5": 1509465877 + }, + "origin": "boost", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpython3.6m.so.1.0" + ], + "provides": [ + "so:libboost_python3-mt.so.1.62.0=1.62.0", + "so:libboost_python3.so.1.62.0=1.62.0" + ] + }, + "dpkg": { + "versions": { + "1.18.24-r0": 1509494231 + }, + "origin": "dpkg", + "dependencies": [ + "xz", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:dpkg", + "cmd:dpkg-buildflags", + "cmd:dpkg-deb", + "cmd:dpkg-divert", + "cmd:dpkg-genbuildinfo", + "cmd:dpkg-maintscript-helper", + "cmd:dpkg-mergechangelogs", + "cmd:dpkg-parsechangelog", + "cmd:dpkg-query", + "cmd:dpkg-split", + "cmd:dpkg-statoverride", + "cmd:dpkg-trigger", + "cmd:update-alternatives" + ] + }, + "rrdcollect": { + "versions": { + "0.2.10-r1": 1509492721 + }, + "origin": "rrdcollect", + "dependencies": [ + "rrdtool", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:rrdcollect" + ] + }, + "openipmi-dev": { + "versions": { + "2.0.22-r1": 1510260043 + }, + "origin": "openipmi", + "dependencies": [ + "openipmi-lanserv=2.0.22-r1", + "openipmi-libs=2.0.22-r1", + "pc:ncurses", + "pkgconfig" + ], + "provides": [ + "pc:OpenIPMI=2.0.22", + "pc:OpenIPMIcmdlang=2.0.22", + "pc:OpenIPMIglib=2.0.22", + "pc:OpenIPMIposix=2.0.22", + "pc:OpenIPMIpthread=2.0.22", + "pc:OpenIPMIui=2.0.22", + "pc:OpenIPMIutils=2.0.22" + ] + }, + "nginx-mod-http-redis2": { + "versions": { + "1.12.2-r4": 1542814448 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "wget-doc": { + "versions": { + "1.20.3-r0": 1554724053 + }, + "origin": "wget" + }, + "py2-openssl": { + "versions": { + "17.5.0-r0": 1548491341 + }, + "origin": "py-openssl", + "dependencies": [ + "py2-cryptography", + "py2-six", + "python2" + ] + }, + "btrfs-progs": { + "versions": { + "4.13.2-r0": 1509481819 + }, + "origin": "btrfs-progs", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:liblzo2.so.2", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:btrfs", + "cmd:btrfsck", + "cmd:fsck.btrfs", + "cmd:mkfs.btrfs" + ] + }, + "python3-doc": { + "versions": { + "3.6.8-r0": 1555928808, + "3.6.9-r0": 1571233994, + "3.6.9-r1": 1571314638 + }, + "origin": "python3" + }, + "acpica": { + "versions": { + "20170303-r0": 1510068817 + }, + "origin": "acpica", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:acpibin", + "cmd:acpidump", + "cmd:acpiexamples", + "cmd:acpiexec", + "cmd:acpihelp", + "cmd:acpinames", + "cmd:acpisrc", + "cmd:acpixtract" + ] + }, + "libtorrent-dev": { + "versions": { + "0.13.6-r3": 1510259942 + }, + "origin": "libtorrent", + "dependencies": [ + "libtorrent=0.13.6-r3", + "pkgconfig" + ], + "provides": [ + "pc:libtorrent=0.13.6" + ] + }, + "collectd-lvm": { + "versions": { + "5.7.2-r0": 1510069653 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:liblvm2app.so.2.2" + ] + }, + "audacious": { + "versions": { + "3.9-r0": 1510072525 + }, + "origin": "audacious", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libaudcore.so.5=5.0.0", + "so:libaudgui.so.5=5.0.0", + "so:libaudtag.so.3=3.0.0", + "cmd:audacious", + "cmd:audtool" + ] + }, + "fcgiwrap-doc": { + "versions": { + "1.1.0-r2": 1510933068 + }, + "origin": "fcgiwrap" + }, + "udisks2-dev": { + "versions": { + "2.6.5-r0": 1510075148 + }, + "origin": "udisks2", + "dependencies": [ + "gobject-introspection-dev", + "polkit-dev", + "libatasmart-dev", + "libgudev-dev", + "acl-dev", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig", + "udisks2-libs=2.6.5-r0" + ], + "provides": [ + "pc:udisks2=2.6.5" + ] + }, + "perl-mail-imapclient": { + "versions": { + "3.39-r0": 1509494893 + }, + "origin": "perl-mail-imapclient", + "dependencies": [ + "perl-parse-recdescent" + ] + }, + "nfs-utils-doc": { + "versions": { + "2.1.1-r4": 1509492106 + }, + "origin": "nfs-utils" + }, + "libcmph": { + "versions": { + "2.0-r1": 1509482578 + }, + "origin": "cmph", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcmph.so.0=0.0.0" + ] + }, + "mksh": { + "versions": { + "56b-r0": 1509491851 + }, + "origin": "mksh", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mksh" + ] + }, + "uwsgi-redislog": { + "versions": { + "2.0.17-r0": 1522154656 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-devel-stacktrace-ashtml-doc": { + "versions": { + "0.15-r0": 1510564491 + }, + "origin": "perl-devel-stacktrace-ashtml" + }, + "patchwork-postgresql": { + "versions": { + "1.1.3-r0": 1509474239, + "1.1.3-r1": 1562223418 + }, + "origin": "patchwork", + "dependencies": [ + "py-psycopg2" + ] + }, + "supervisor": { + "versions": { + "3.3.3-r1": 1509552736 + }, + "origin": "supervisor", + "dependencies": [ + "python2", + "py-meld3", + "py-setuptools" + ], + "provides": [ + "cmd:echo_supervisord_conf", + "cmd:pidproxy", + "cmd:supervisorctl", + "cmd:supervisord" + ] + }, + "libmilter-doc": { + "versions": { + "1.0.2-r5": 1509479369 + }, + "origin": "libmilter" + }, + "py3-libxml2": { + "versions": { + "2.9.8-r1": 1540398580 + }, + "origin": "libxml2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0", + "so:libxml2.so.2" + ] + }, + "perl-test-exception": { + "versions": { + "0.43-r0": 1509473075 + }, + "origin": "perl-test-exception", + "dependencies": [ + "perl-sub-uplevel" + ] + }, + "api-sanity-checker": { + "versions": { + "1.98.7-r0": 1510075239 + }, + "origin": "api-sanity-checker", + "dependencies": [ + "perl", + "build-base" + ], + "provides": [ + "cmd:api-sanity-checker" + ] + }, + "py-crypto": { + "versions": { + "2.6.1-r2": 1509483004 + }, + "origin": "py-crypto" + }, + "xdriinfo-doc": { + "versions": { + "1.0.5-r0": 1510074964 + }, + "origin": "xdriinfo" + }, + "man-pages": { + "versions": { + "4.14-r0": 1512031990 + }, + "origin": "man-pages" + }, + "lua5.3-lyaml": { + "versions": { + "6.1.3-r1": 1509473485 + }, + "origin": "lua-lyaml", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libyaml-0.so.2" + ] + }, + "acf-unbound": { + "versions": { + "0.1.0-r2": 1510588360 + }, + "origin": "acf-unbound", + "dependencies": [ + "acf-core", + "unbound" + ] + }, + "zfs-vanilla": { + "versions": { + "4.9.161-r0": 1551780615, + "4.9.182-r0": 1560866659 + }, + "origin": "zfs-vanilla", + "dependencies": [ + "spl-vanilla", + "linux-vanilla=4.9.182-r0" + ] + }, + "rlog": { + "versions": { + "1.4-r4": 1509475981 + }, + "origin": "rlog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:librlog.so.5=5.0.0" + ] + }, + "libcdio-tools": { + "versions": { + "0.94-r0": 1509473627 + }, + "origin": "libcdio", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcddb.so.2", + "so:libcdio.so.16", + "so:libiso9660.so.10", + "so:libncursesw.so.6", + "so:libudf.so.0" + ], + "provides": [ + "cmd:cd-drive", + "cmd:cd-info", + "cmd:cd-read", + "cmd:cdda-player", + "cmd:iso-info", + "cmd:iso-read", + "cmd:mmc-tool" + ] + }, + "sg3_utils-dev": { + "versions": { + "1.42-r0": 1509482217 + }, + "origin": "sg3_utils", + "dependencies": [ + "sg3_utils=1.42-r0" + ] + }, + "samba-common-libs": { + "versions": { + "4.7.6-r3": 1555491786 + }, + "origin": "samba", + "dependencies": [ + "so:libCHARSET3-samba4.so", + "so:libasn1util-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcliauth-samba4.so", + "so:libcom_err.so.2", + "so:libdbwrap-samba4.so", + "so:libevents-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:libmessages-dgm-samba4.so", + "so:libmessages-util-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libsecrets3-samba4.so", + "so:libserver-id-db-samba4.so", + "so:libsmb-transport-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-shim-samba4.so", + "so:libsocket-blocking-samba4.so", + "so:libtalloc.so.2", + "so:libtdb-wrap-samba4.so", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-cmdline-samba4.so", + "so:libutil-setid-samba4.so", + "so:libutil-tdb-samba4.so", + "so:libwbclient.so.0" + ], + "provides": [ + "so:libMESSAGING-SEND-samba4.so=0", + "so:libcli-spoolss-samba4.so=0", + "so:libdcerpc-binding.so.0=0.0.1", + "so:libdcerpc-samba-samba4.so=0", + "so:liblibcli-lsa3-samba4.so=0", + "so:liblibcli-netlogon3-samba4.so=0", + "so:liblibsmb-samba4.so=0", + "so:libmsrpc3-samba4.so=0", + "so:libndr-samba4.so=0", + "so:libsamba-passdb.so.0=0.27.0", + "so:libtrusts-util-samba4.so=0" + ] + }, + "s6-networking": { + "versions": { + "2.3.0.2-r1": 1510260983 + }, + "origin": "s6-networking", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libs6.so.2.6", + "so:libs6dns.so.2.2", + "so:libskarnet.so.2.6", + "so:libtls.so.16" + ], + "provides": [ + "so:libs6net.so.2.3=2.3.0.2", + "so:libstls.so.2.3=2.3.0.2", + "cmd:minidentd", + "cmd:s6-clockadd", + "cmd:s6-clockview", + "cmd:s6-getservbyname", + "cmd:s6-ident-client", + "cmd:s6-sntpclock", + "cmd:s6-taiclock", + "cmd:s6-taiclockd", + "cmd:s6-tcpclient", + "cmd:s6-tcpserver", + "cmd:s6-tcpserver-access", + "cmd:s6-tcpserver4", + "cmd:s6-tcpserver4-socketbinder", + "cmd:s6-tcpserver4d", + "cmd:s6-tcpserver6", + "cmd:s6-tcpserver6-socketbinder", + "cmd:s6-tcpserver6d", + "cmd:s6-tlsc", + "cmd:s6-tlsclient", + "cmd:s6-tlsd", + "cmd:s6-tlsserver" + ] + }, + "perl-net-async-http": { + "versions": { + "0.40-r0": 1509480558 + }, + "origin": "perl-net-async-http", + "dependencies": [ + "perl-io-async", + "perl-future", + "perl-uri", + "perl-http-message", + "perl-struct-dumb" + ] + }, + "nagios-plugins": { + "versions": { + "2.2.1-r3": 1510288499 + }, + "origin": "nagios-plugins", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ] + }, + "appstream-glib": { + "versions": { + "0.6.3-r0": 1510067863 + }, + "origin": "appstream-glib", + "dependencies": [ + "so:libarchive.so.13", + "so:libc.musl-x86_64.so.1", + "so:libgcab-1.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libsoup-2.4.so.1", + "so:libuuid.so.1", + "so:libyaml-0.so.2" + ], + "provides": [ + "so:libappstream-glib.so.8=8.0.10", + "cmd:appstream-compose", + "cmd:appstream-util" + ] + }, + "font-arabic-misc": { + "versions": { + "1.0.3-r0": 1509495842 + }, + "origin": "font-arabic-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "autoconf": { + "versions": { + "2.69-r0": 1509456973 + }, + "origin": "autoconf", + "dependencies": [ + "m4", + "perl" + ], + "provides": [ + "cmd:autoconf", + "cmd:autoheader", + "cmd:autom4te", + "cmd:autoreconf", + "cmd:autoscan", + "cmd:autoupdate", + "cmd:ifnames" + ] + }, + "perl-specio": { + "versions": { + "0.36-r0": 1509481612 + }, + "origin": "perl-specio", + "dependencies": [ + "perl-test-needs", + "perl-devel-stacktrace", + "perl-eval-closure", + "perl-mro-compat", + "perl-role-tiny", + "perl-test-fatal", + "perl-module-runtime" + ] + }, + "nmap-doc": { + "versions": { + "7.60-r2": 1510261467, + "7.60-r3": 1572297244 + }, + "origin": "nmap" + }, + "rtapd-dbg": { + "versions": { + "1.7-r6": 1509491636 + }, + "origin": "rtapd", + "dependencies": [ + "rtnppd" + ] + }, + "sfic": { + "versions": { + "0.1.7-r6": 1509492421 + }, + "origin": "sfic", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtdb.so.1" + ], + "provides": [ + "cmd:sfic" + ] + }, + "abiword-plugins": { + "versions": { + "3.0.2-r1": 1510073355 + }, + "origin": "abiword", + "dependencies": [ + "abiword-plugin-applix", + "abiword-plugin-babelfish", + "abiword-plugin-bmp", + "abiword-plugin-clarisworks", + "abiword-plugin-collab", + "abiword-plugin-docbook", + "abiword-plugin-eml", + "abiword-plugin-epub", + "abiword-plugin-freetranslation", + "abiword-plugin-garble", + "abiword-plugin-gdict", + "abiword-plugin-gimp", + "abiword-plugin-google", + "abiword-plugin-hancom", + "abiword-plugin-hrtext", + "abiword-plugin-iscii", + "abiword-plugin-kword", + "abiword-plugin-latex", + "abiword-plugin-loadbindings", + "abiword-plugin-mht", + "abiword-plugin-mif", + "abiword-plugin-mswrite", + "abiword-plugin-openwriter", + "abiword-plugin-openxml", + "abiword-plugin-opml", + "abiword-plugin-paint", + "abiword-plugin-passepartout", + "abiword-plugin-pdb", + "abiword-plugin-pdf", + "abiword-plugin-presentation", + "abiword-plugin-s5", + "abiword-plugin-sdw", + "abiword-plugin-t602", + "abiword-plugin-urldict", + "abiword-plugin-wikipedia", + "abiword-plugin-wml", + "abiword-plugin-xslfo" + ] + }, + "libdvdread-doc": { + "versions": { + "5.0.3-r1": 1509480296 + }, + "origin": "libdvdread" + }, + "opennhrp": { + "versions": { + "0.14.1-r6": 1509490851 + }, + "origin": "opennhrp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2" + ], + "provides": [ + "cmd:opennhrp", + "cmd:opennhrpctl" + ] + }, + "kbproto-doc": { + "versions": { + "1.0.7-r2": 1509461960 + }, + "origin": "kbproto" + }, + "libbonobo-lang": { + "versions": { + "2.32.1-r6": 1510931481 + }, + "origin": "libbonobo" + }, + "libquvi-scripts-dev": { + "versions": { + "0.9.20131130-r0": 1509491408 + }, + "origin": "libquvi-scripts", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:libquvi-scripts-0.9=0.9.20131130" + ] + }, + "perl-class-inspector": { + "versions": { + "1.28-r1": 1510564482 + }, + "origin": "perl-class-inspector", + "dependencies": [ + "perl" + ] + }, + "busybox-initscripts": { + "versions": { + "3.1-r3": 1549616267 + }, + "origin": "busybox-initscripts", + "dependencies": [ + "busybox", + "openrc" + ] + }, + "xprop-doc": { + "versions": { + "1.2.1-r1": 1509491073 + }, + "origin": "xprop" + }, + "openldap-overlay-dyngroup": { + "versions": { + "2.4.45-r3": 1510258134, + "2.4.46-r0": 1565073940, + "2.4.48-r0": 1566900517 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "perl-io": { + "versions": { + "1.25-r4": 1509483995 + }, + "origin": "perl-io", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "ucarp": { + "versions": { + "1.5.2-r7": 1509494982 + }, + "origin": "ucarp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:ucarp" + ] + }, + "perl-pod-coverage": { + "versions": { + "0.23-r0": 1509470436 + }, + "origin": "perl-pod-coverage", + "dependencies": [ + "perl", + "perl-devel-symdump", + "perl-test-pod" + ], + "provides": [ + "cmd:pod_cover" + ] + }, + "perl-test-refcount-doc": { + "versions": { + "0.08-r0": 1509477778 + }, + "origin": "perl-test-refcount" + }, + "lua5.2-apk": { + "versions": { + "2.10.1-r0": 1536582152 + }, + "origin": "apk-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ] + }, + "libsecret-lang": { + "versions": { + "0.18.5-r0": 1509483962 + }, + "origin": "libsecret" + }, + "jwm-doc": { + "versions": { + "2.3.7-r0": 1510075431 + }, + "origin": "jwm" + }, + "libxklavier-dev": { + "versions": { + "5.4-r2": 1509475732 + }, + "origin": "libxklavier", + "dependencies": [ + "libxml2-dev", + "glib-dev", + "gettext-dev", + "libxkbfile-dev", + "libxklavier=5.4-r2", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libxklavier=5.4" + ] + }, + "mpc1-dev": { + "versions": { + "1.0.3-r1": 1509457077 + }, + "origin": "mpc1", + "dependencies": [ + "mpc1=1.0.3-r1" + ] + }, + "thunar-lang": { + "versions": { + "1.6.12-r0": 1510072625 + }, + "origin": "thunar", + "dependencies": [ + "desktop-file-utils", + "hicolor-icon-theme", + "shared-mime-info" + ] + }, + "x265": { + "versions": { + "2.5-r0": 1509482548 + }, + "origin": "x265", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libx265.so.130=130", + "cmd:x265" + ] + }, + "sic": { + "versions": { + "1.2-r0": 1509474248 + }, + "origin": "sic", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sic" + ] + }, + "qemu-doc": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu" + }, + "patchwork-uwsgi-nginx": { + "versions": { + "1.1.3-r0": 1509474238, + "1.1.3-r1": 1562223418 + }, + "origin": "patchwork", + "dependencies": [ + "nginx", + "uwsgi-python" + ] + }, + "graphicsmagick-zsh-completion": { + "versions": { + "5.4.2-r1": 1522503664 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "randrproto": { + "versions": { + "1.5.0-r2": 1509465995 + }, + "origin": "randrproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:randrproto=1.5.0" + ] + }, + "lua5.1-crypto": { + "versions": { + "0.3.2-r5": 1510261400 + }, + "origin": "lua-crypto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "py-gst0.10": { + "versions": { + "0.10.22-r0": 1510072577 + }, + "origin": "py-gst0.10", + "dependencies": [ + "py-gtk", + "py-gobject", + "pc:gstreamer-0.10", + "pc:pygobject-2.0", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstaudio-0.10.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstcontroller-0.10.so.0", + "so:libgstdataprotocol-0.10.so.0", + "so:libgstinterfaces-0.10.so.0", + "so:libgstnet-0.10.so.0", + "so:libgstpbutils-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgsttag-0.10.so.0", + "so:libgstvideo-0.10.so.0" + ], + "provides": [ + "pc:gst-python-0.10=0.10.22" + ] + }, + "ddate": { + "versions": { + "0.2.2-r0": 1509492725 + }, + "origin": "ddate", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ddate" + ] + }, + "collectd-snmp": { + "versions": { + "5.7.2-r0": 1510069643 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.30" + ] + }, + "arpon-doc": { + "versions": { + "3.0-r0": 1509495304 + }, + "origin": "arpon" + }, + "sipsak-doc": { + "versions": { + "0.9.6-r9": 1510260059 + }, + "origin": "sipsak" + }, + "xfce4-session": { + "versions": { + "4.12.1-r2": 1510074580 + }, + "origin": "xfce4-session", + "dependencies": [ + "hicolor-icon-theme", + "iceauth", + "dbus-x11", + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libwnck-1.so.22", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "so:libxfsm-4.6.so.0=0.0.0", + "cmd:startxfce4", + "cmd:xfce4-session", + "cmd:xfce4-session-logout", + "cmd:xfce4-session-settings", + "cmd:xflock4" + ] + }, + "mysql-client": { + "versions": { + "10.1.38-r1": 1551187995, + "10.1.40-r0": 1560354890, + "10.1.41-r0": 1565163115 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-client" + ] + }, + "nagios-plugins-real": { + "versions": { + "2.2.1-r3": 1510288497 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "m4-doc": { + "versions": { + "1.4.18-r0": 1509456653 + }, + "origin": "m4" + }, + "weechat-perl": { + "versions": { + "1.9.1-r1": 1509530223 + }, + "origin": "weechat", + "dependencies": [ + "weechat", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "bluez-libs": { + "versions": { + "5.47-r3": 1510069786 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libbluetooth.so.3=3.18.16" + ] + }, + "faad2-dev": { + "versions": { + "2.7-r7": 1509480952, + "2.9.0-r0": 1571918409 + }, + "origin": "faad2", + "dependencies": [ + "faad2=2.9.0-r0" + ] + }, + "xfce4-whiskermenu-plugin-lang": { + "versions": { + "1.7.3-r0": 1510073068 + }, + "origin": "xfce4-whiskermenu-plugin" + }, + "icu": { + "versions": { + "59.1-r1": 1509464844 + }, + "origin": "icu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libicui18n.so.59", + "so:libicuio.so.59", + "so:libicutu.so.59", + "so:libicuuc.so.59", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:derb", + "cmd:escapesrc", + "cmd:genbrk", + "cmd:genccode", + "cmd:gencfu", + "cmd:gencmn", + "cmd:gencnval", + "cmd:gendict", + "cmd:gennorm2", + "cmd:genrb", + "cmd:gensprep", + "cmd:icuinfo", + "cmd:icupkg", + "cmd:makeconv", + "cmd:pkgdata", + "cmd:uconv" + ] + }, + "xcmiscproto": { + "versions": { + "1.2.2-r2": 1509473766 + }, + "origin": "xcmiscproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xcmiscproto=1.2.2" + ] + }, + "spice-server": { + "versions": { + "0.14.1-r2": 1548934012 + }, + "origin": "spice", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcelt051.so.0", + "so:libcrypto.so.42", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libjpeg.so.8", + "so:libopus.so.0", + "so:libpixman-1.so.0", + "so:libsasl2.so.3", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:libspice-server.so.1=1.12.5" + ] + }, + "libbonobo-dev": { + "versions": { + "2.32.1-r6": 1510931477 + }, + "origin": "libbonobo", + "dependencies": [ + "gtk+2.0-dev", + "libidl-dev", + "orbit2-dev", + "popt-dev", + "libxml2-dev", + "libbonobo=2.32.1-r6", + "pc:ORBit-2.0", + "pc:glib-2.0", + "pc:gmodule-no-export-2.0", + "pkgconfig" + ], + "provides": [ + "pc:bonobo-activation-2.0=2.32.1", + "pc:libbonobo-2.0=2.32.1" + ] + }, + "squid-lang-ca": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865667 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "libgphoto2": { + "versions": { + "2.5.14-r0": 1509481939 + }, + "origin": "libgphoto2", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libexif.so.12", + "so:libjpeg.so.8", + "so:libltdl.so.7", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libgphoto2.so.6=6.0.0", + "so:libgphoto2_port.so.12=12.0.0" + ] + }, + "dnscache": { + "versions": { + "1.05-r47": 1509488767 + }, + "origin": "djbdns", + "dependencies": [ + "djbdns-common", + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dnscache", + "cmd:dnscache-conf" + ] + }, + "xmlindent-doc": { + "versions": { + "0.2.17-r0": 1509490829 + }, + "origin": "xmlindent" + }, + "groff-doc": { + "versions": { + "1.22.3-r2": 1509460767 + }, + "origin": "groff" + }, + "perl-yaml-tiny": { + "versions": { + "1.70-r1": 1510352923 + }, + "origin": "perl-yaml-tiny" + }, + "gstreamer-doc": { + "versions": { + "1.12.3-r0": 1509470920 + }, + "origin": "gstreamer" + }, + "fsarchiver": { + "versions": { + "0.8.2-r0": 1509485944 + }, + "origin": "fsarchiver", + "dependencies": [ + "so:libblkid.so.1", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libe2p.so.2", + "so:libext2fs.so.2", + "so:libgcrypt.so.20", + "so:liblzma.so.5", + "so:liblzo2.so.2", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:fsarchiver" + ] + }, + "rhash-doc": { + "versions": { + "1.3.5-r1": 1509461594 + }, + "origin": "rhash" + }, + "perl-text-reform-doc": { + "versions": { + "1.20-r0": 1509475836 + }, + "origin": "perl-text-reform" + }, + "lua-evdev": { + "versions": { + "2.2.1-r1": 1509488871 + }, + "origin": "lua-evdev" + }, + "twm-doc": { + "versions": { + "1.0.9-r2": 1509493987 + }, + "origin": "twm" + }, + "nmap-ncat": { + "versions": { + "7.60-r2": 1510261467, + "7.60-r3": 1572297245 + }, + "origin": "nmap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpcap.so.1", + "so:libssl.so.44" + ], + "provides": [ + "cmd:ncat" + ] + }, + "samba-doc": { + "versions": { + "4.7.6-r3": 1555491786 + }, + "origin": "samba" + }, + "samba-client": { + "versions": { + "4.7.6-r3": 1555491787 + }, + "origin": "samba", + "dependencies": [ + "samba-common=4.7.6-r3", + "so:libCHARSET3-samba4.so", + "so:libaddns-samba4.so", + "so:libarchive.so.13", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcli-spoolss-samba4.so", + "so:libcliauth-samba4.so", + "so:libcmdline-credentials-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libevents-samba4.so", + "so:libformw.so.6", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:liblibcli-lsa3-samba4.so", + "so:liblibcli-netlogon3-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmsrpc3-samba4.so", + "so:libncursesw.so.6", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libpanelw.so.6", + "so:libpopt-samba3-samba4.so", + "so:libpopt.so.0", + "so:libregistry-samba4.so", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb-common-samba4.so", + "so:libsecrets3-samba4.so", + "so:libserver-role-samba4.so", + "so:libsmbclient-raw-samba4.so", + "so:libsmbclient.so.0", + "so:libsmbconf.so.0", + "so:libtalloc.so.2", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtrusts-util-samba4.so", + "so:libutil-cmdline-samba4.so", + "so:libutil-reg-samba4.so", + "so:libutil-tdb-samba4.so" + ], + "provides": [ + "cmd:cifsdd", + "cmd:dbwrap_tool", + "cmd:findsmb", + "cmd:mvxattr", + "cmd:nmblookup", + "cmd:oLschema2ldif", + "cmd:regdiff", + "cmd:regpatch", + "cmd:regshell", + "cmd:regtree", + "cmd:rpcclient", + "cmd:samba-regedit", + "cmd:sharesec", + "cmd:smbcacls", + "cmd:smbclient", + "cmd:smbcquotas", + "cmd:smbget", + "cmd:smbprint", + "cmd:smbspool", + "cmd:smbtar", + "cmd:smbtree" + ] + }, + "gnome-vfs": { + "versions": { + "2.24.4-r6": 1510931441 + }, + "origin": "gnome-vfs", + "dependencies": [ + "gnome-mime-data", + "/bin/sh", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libfam.so.0", + "so:libgconf-2.so.4", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libgnomevfs-2.so.0=0.2400.4", + "cmd:gnomevfs-cat", + "cmd:gnomevfs-copy", + "cmd:gnomevfs-df", + "cmd:gnomevfs-info", + "cmd:gnomevfs-ls", + "cmd:gnomevfs-mkdir", + "cmd:gnomevfs-monitor", + "cmd:gnomevfs-mv", + "cmd:gnomevfs-rm" + ] + }, + "libxfce4util-lang": { + "versions": { + "4.12.1-r4": 1509468547 + }, + "origin": "libxfce4util" + }, + "automake-doc": { + "versions": { + "1.15.1-r0": 1509456977 + }, + "origin": "automake" + }, + "py-parsing": { + "versions": { + "2.2.0-r0": 1509475646 + }, + "origin": "py-parsing" + }, + "libaio": { + "versions": { + "0.3.110-r1": 1509471437 + }, + "origin": "libaio", + "provides": [ + "so:libaio.so.1=1.0.1" + ] + }, + "orage": { + "versions": { + "4.12.1-r1": 1510073570 + }, + "origin": "orage", + "dependencies": [ + "hicolor-icon-theme", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libical.so.2", + "so:libicalss.so.2", + "so:libintl.so.8", + "so:libnotify.so.4", + "so:libpango-1.0.so.0", + "so:libpopt.so.0", + "so:libxfce4panel-1.0.so.4", + "so:libxfce4util.so.7" + ], + "provides": [ + "cmd:globaltime", + "cmd:orage", + "cmd:tz_convert" + ] + }, + "xfce4-notifyd-doc": { + "versions": { + "0.3.6-r0": 1510075458 + }, + "origin": "xfce4-notifyd" + }, + "perl-datetime-format-natural": { + "versions": { + "1.05-r1": 1511384618 + }, + "origin": "perl-datetime-format-natural", + "dependencies": [ + "perl-datetime-timezone", + "perl-clone", + "perl-params-validate", + "perl-list-moreutils", + "perl-datetime", + "perl-boolean" + ], + "provides": [ + "cmd:dateparse" + ] + }, + "yajl": { + "versions": { + "2.1.0-r0": 1509475323 + }, + "origin": "yajl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libyajl.so.2=2.1.0" + ] + }, + "perl-test-script-doc": { + "versions": { + "1.23-r0": 1510588920 + }, + "origin": "perl-test-script" + }, + "gdk-pixbuf-dev": { + "versions": { + "2.36.10-r0": 1509465514 + }, + "origin": "gdk-pixbuf", + "dependencies": [ + "gdk-pixbuf=2.36.10-r0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:libpng16", + "pkgconfig" + ], + "provides": [ + "pc:gdk-pixbuf-2.0=2.36.10", + "pc:gdk-pixbuf-xlib-2.0=2.36.10" + ] + }, + "py-libproxy": { + "versions": { + "0.4.15-r0": 1509468258 + }, + "origin": "libproxy" + }, + "perl-gd": { + "versions": { + "2.67-r0": 1511805778 + }, + "origin": "perl-gd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3" + ], + "provides": [ + "cmd:bdf2gdfont.pl" + ] + }, + "perl-filesys-notify-simple": { + "versions": { + "0.12-r0": 1510588883 + }, + "origin": "perl-filesys-notify-simple", + "dependencies": [ + "perl" + ] + }, + "pangomm-doc": { + "versions": { + "2.40.1-r0": 1509473003 + }, + "origin": "pangomm" + }, + "gvfs-archive": { + "versions": { + "1.34.1-r0": 1511430258, + "1.34.1-r1": 1563787223 + }, + "origin": "gvfs", + "dependencies": [ + "so:libarchive.so.13", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgvfscommon.so", + "so:libgvfsdaemon.so", + "so:libintl.so.8" + ] + }, + "acf-ppp": { + "versions": { + "0.5.0-r2": 1510074011 + }, + "origin": "acf-ppp", + "dependencies": [ + "acf-core", + "ppp" + ] + }, + "ed-doc": { + "versions": { + "1.14.2-r2": 1509465946 + }, + "origin": "ed" + }, + "heimdal-doc": { + "versions": { + "7.4.0-r2": 1514545900, + "7.4.0-r3": 1559659764, + "7.4.0-r4": 1562862335 + }, + "origin": "heimdal" + }, + "abiword-plugin-latex": { + "versions": { + "3.0.2-r1": 1510073363 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "sngtc_client": { + "versions": { + "1.3.7-r1": 1509481973 + }, + "origin": "sngtc_client", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libortp.so.10" + ], + "provides": [ + "so:libsngtc_node.so=0", + "cmd:sngtc_client" + ] + }, + "rsyslog-doc": { + "versions": { + "8.31.0-r0": 1512031980, + "8.31.0-r1": 1571767017 + }, + "origin": "rsyslog" + }, + "glib-static": { + "versions": { + "2.54.2-r0": 1509911133, + "2.54.2-r1": 1560764732 + }, + "origin": "glib", + "dependencies": [ + "gettext-static" + ] + }, + "acct": { + "versions": { + "6.6.4-r0": 1509462238 + }, + "origin": "acct", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ac", + "cmd:accton", + "cmd:dump-acct", + "cmd:dump-utmp", + "cmd:last", + "cmd:lastcomm", + "cmd:sa" + ] + }, + "espeak-dev": { + "versions": { + "1.48.04-r1": 1509472850 + }, + "origin": "espeak", + "dependencies": [ + "espeak=1.48.04-r1" + ] + }, + "perl-javascript-minifier-xs-doc": { + "versions": { + "0.11-r3": 1509495049 + }, + "origin": "perl-javascript-minifier-xs" + }, + "perl-file-which": { + "versions": { + "1.22-r0": 1509495220 + }, + "origin": "perl-file-which" + }, + "dhcp-dev": { + "versions": { + "4.3.5-r0": 1509496512 + }, + "origin": "dhcp" + }, + "cdw-doc": { + "versions": { + "0.8.1-r0": 1509491772 + }, + "origin": "cdw" + }, + "abiword-plugin-s5": { + "versions": { + "3.0.2-r1": 1510073368 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "perl-scalar-list-utils-doc": { + "versions": { + "1.49-r0": 1509485607 + }, + "origin": "perl-scalar-list-utils" + }, + "liboil": { + "versions": { + "0.3.17-r5": 1509471016 + }, + "origin": "liboil", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liboil-0.3.so.0=0.3.0", + "cmd:oil-bugreport" + ] + }, + "snappy": { + "versions": { + "1.1.4-r2": 1509483343 + }, + "origin": "snappy", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libsnappy.so.1=1.3.1" + ] + }, + "abiword-plugin-collab": { + "versions": { + "3.0.2-r1": 1510073357 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libgtk-3.so.0", + "so:libstdc++.so.6", + "so:libxml2.so.2" + ] + }, + "gst-ffmpeg0.10": { + "versions": { + "0.10.13-r1": 1510076394 + }, + "origin": "gst-ffmpeg0.10", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstaudio-0.10.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstpbutils-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgstvideo-0.10.so.0", + "so:liborc-0.4.so.0", + "so:libz.so.1" + ] + }, + "perl-text-vfile-asdata-doc": { + "versions": { + "0.08-r0": 1509474104 + }, + "origin": "perl-text-vfile-asdata" + }, + "linux-hardened": { + "versions": { + "4.9.65-r1": 1511798484 + }, + "origin": "linux-hardened", + "dependencies": [ + "mkinitfs", + "linux-firmware" + ], + "provides": [ + "linux-grsec=4.9.65-r1" + ] + }, + "xkbcomp": { + "versions": { + "1.4.0-r2": 1509473697 + }, + "origin": "xkbcomp", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libxkbfile.so.1" + ], + "provides": [ + "cmd:xkbcomp" + ] + }, + "cryptsetup-dev": { + "versions": { + "1.7.5-r1": 1510261295 + }, + "origin": "cryptsetup", + "dependencies": [ + "cryptsetup-libs=1.7.5-r1", + "pkgconfig" + ], + "provides": [ + "pc:libcryptsetup=1.7.5" + ] + }, + "libzdb": { + "versions": { + "3.1-r1": 1509491013 + }, + "origin": "libzdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18", + "so:libpq.so.5", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:libzdb.so.11=11.0.0" + ] + }, + "taskd-doc": { + "versions": { + "1.1.0-r4": 1509491726 + }, + "origin": "taskd" + }, + "stfl-dev": { + "versions": { + "0.24-r2": 1509491482 + }, + "origin": "stfl", + "dependencies": [ + "pkgconfig", + "stfl=0.24-r2" + ], + "provides": [ + "pc:stfl=0.24" + ] + }, + "binutils-doc": { + "versions": { + "2.30-r1": 1527754981, + "2.30-r2": 1565788943 + }, + "origin": "binutils" + }, + "perl-uri": { + "versions": { + "1.71-r1": 1509464365 + }, + "origin": "perl-uri", + "dependencies": [ + "perl" + ] + }, + "perl-convert-color-doc": { + "versions": { + "0.11-r0": 1509489722 + }, + "origin": "perl-convert-color" + }, + "sqsh-doc": { + "versions": { + "2.5.16.1-r2": 1509493717 + }, + "origin": "sqsh" + }, + "py-flask-oauthlib": { + "versions": { + "0.9.3-r2": 1509552815 + }, + "origin": "py-flask-oauthlib", + "dependencies": [ + "py-flask", + "py-requests-oauthlib" + ] + }, + "gnu-efi-dev": { + "versions": { + "3.0.4-r1": 1509482583 + }, + "origin": "gnu-efi", + "dependencies": [ + "gnu-efi" + ] + }, + "cabextract": { + "versions": { + "1.9-r0": 1543333648 + }, + "origin": "cabextract", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmspack.so.0" + ], + "provides": [ + "cmd:cabextract" + ] + }, + "libxext-dev": { + "versions": { + "1.3.3-r2": 1509464538 + }, + "origin": "libxext", + "dependencies": [ + "libxau-dev", + "libxext=1.3.3-r2", + "pc:x11", + "pc:xextproto", + "pkgconfig" + ], + "provides": [ + "pc:xext=1.3.3" + ] + }, + "libart-lgpl": { + "versions": { + "2.3.21-r5": 1509475376 + }, + "origin": "libart-lgpl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libart_lgpl_2.so.2=2.3.21" + ] + }, + "perl-doc": { + "versions": { + "5.26.3-r0": 1543940996 + }, + "origin": "perl" + }, + "enchant": { + "versions": { + "1.6.0-r12": 1509477683 + }, + "origin": "enchant", + "dependencies": [ + "so:libaspell.so.15", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libhunspell-1.6.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libenchant.so.1=1.6.0", + "cmd:enchant", + "cmd:enchant-lsmod" + ] + }, + "znc": { + "versions": { + "1.7.1-r0": 1531900840, + "1.7.1-r1": 1565877331 + }, + "origin": "znc", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libicuuc.so.59", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:znc" + ] + }, + "lua5.1-sql-odbc": { + "versions": { + "2.3.5-r1": 1509488829 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ] + }, + "hunspell-dev": { + "versions": { + "1.6.2-r1": 1509477667 + }, + "origin": "hunspell", + "dependencies": [ + "hunspell=1.6.2-r1", + "pkgconfig" + ], + "provides": [ + "pc:hunspell=1.6.2" + ] + }, + "miniperl": { + "versions": { + "5.26.3-r0": 1543940997 + }, + "origin": "perl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:miniperl" + ] + }, + "libgfortran": { + "versions": { + "6.4.0-r5": 1509458077 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libquadmath.so.0" + ], + "provides": [ + "so:libgfortran.so.3=3.0.0" + ] + }, + "xf86-video-dummy-doc": { + "versions": { + "0.3.8-r0": 1510072534 + }, + "origin": "xf86-video-dummy" + }, + "mtr-doc": { + "versions": { + "0.92-r0": 1510072768 + }, + "origin": "mtr" + }, + "dhcpcd": { + "versions": { + "6.11.5-r1": 1509494693 + }, + "origin": "dhcpcd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dhcpcd" + ] + }, + "xvfb": { + "versions": { + "1.19.5-r1": 1540838460 + }, + "origin": "xorg-server", + "dependencies": [ + "font-misc-misc", + "font-cursor-misc", + "xkeyboard-config", + "xkbcomp", + "xinit", + "so:libGL.so.1", + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.0.0", + "so:libpixman-1.so.0" + ], + "provides": [ + "cmd:Xvfb" + ] + }, + "sshguard": { + "versions": { + "2.0.0-r1": 1509483910 + }, + "origin": "sshguard", + "dependencies": [ + "iptables", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sshguard" + ] + }, + "docbook-xsl": { + "versions": { + "1.79.1-r1": 1509459379 + }, + "origin": "docbook-xsl", + "dependencies": [ + "libxml2-utils", + "libxslt", + "docbook-xml", + "/bin/sh" + ] + }, + "xf86-video-rendition": { + "versions": { + "4.2.6-r1": 1510074858 + }, + "origin": "xf86-video-rendition", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "pam-pgsql-doc": { + "versions": { + "0.7.3.2-r0": 1509494687 + }, + "origin": "pam-pgsql" + }, + "perl-business-hours": { + "versions": { + "0.12-r1": 1511889850 + }, + "origin": "perl-business-hours", + "dependencies": [ + "perl-set-intspan" + ] + }, + "font-xfree86-type1": { + "versions": { + "1.0.4-r0": 1509476739 + }, + "origin": "font-xfree86-type1", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "c-ares-dev": { + "versions": { + "1.13.0-r0": 1509461299 + }, + "origin": "c-ares", + "dependencies": [ + "c-ares=1.13.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcares=1.13.0" + ] + }, + "aconf-doc": { + "versions": { + "0.6.5-r0": 1510073707 + }, + "origin": "aconf" + }, + "acf-iptables": { + "versions": { + "0.7.1-r2": 1510070011 + }, + "origin": "acf-iptables", + "dependencies": [ + "acf-core", + "iptables" + ] + }, + "gnutls-c++": { + "versions": { + "3.6.1-r0": 1509465349 + }, + "origin": "gnutls", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgnutlsxx.so.28=28.1.0" + ] + }, + "xsetmode-doc": { + "versions": { + "1.0.0-r4": 1509490726 + }, + "origin": "xsetmode" + }, + "lua-penlight": { + "versions": { + "1.5.4-r0": 1509485592 + }, + "origin": "lua-penlight" + }, + "lua5.2-cqueues": { + "versions": { + "20171014-r0": 1510619111 + }, + "origin": "lua-cqueues", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "compositeproto-doc": { + "versions": { + "0.4.2-r4": 1509465977 + }, + "origin": "compositeproto" + }, + "abiword-plugin-clarisworks": { + "versions": { + "3.0.2-r1": 1510073357 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "partimage": { + "versions": { + "0.6.9-r4": 1510260489 + }, + "origin": "partimage", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libnewt.so.0.52", + "so:libslang.so.2", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:partimage", + "cmd:partimaged" + ] + }, + "libcap-doc": { + "versions": { + "2.25-r1": 1509459572 + }, + "origin": "libcap" + }, + "dev86-doc": { + "versions": { + "0.16.21-r0": 1509479308 + }, + "origin": "dev86" + }, + "python3-dev": { + "versions": { + "3.6.8-r0": 1555928808, + "3.6.9-r0": 1571233994, + "3.6.9-r1": 1571314638 + }, + "origin": "python3", + "dependencies": [ + "pkgconfig", + "python3=3.6.9-r1" + ], + "provides": [ + "pc:python-3.6=3.6", + "pc:python-3.6m=3.6", + "pc:python3=3.6", + "cmd:python3-config", + "cmd:python3.6-config", + "cmd:python3.6m-config" + ] + }, + "py-nose-doc": { + "versions": { + "1.3.7-r2": 1509552699 + }, + "origin": "py-nose" + }, + "libnetfilter_cthelper-dev": { + "versions": { + "1.0.0-r0": 1509469238 + }, + "origin": "libnetfilter_cthelper", + "dependencies": [ + "libnetfilter_cthelper=1.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_cthelper=1.0.0" + ] + }, + "sylpheed": { + "versions": { + "3.6.0-r1": 1510588302 + }, + "origin": "sylpheed", + "dependencies": [ + "pinentry-gtk", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcrypto.so.42", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgpgme.so.11", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libssl.so.44" + ], + "provides": [ + "so:libsylph-0.so.1=1.2.1", + "so:libsylpheed-plugin-0.so.1=1.2.1", + "cmd:sylpheed" + ] + }, + "py-itsdangerous": { + "versions": { + "0.24-r3": 1509551859 + }, + "origin": "py-itsdangerous" + }, + "py3-phonenumbers": { + "versions": { + "8.8.6-r0": 1511018982 + }, + "origin": "py-phonenumbers", + "dependencies": [ + "python3" + ] + }, + "perl-convert-uulib-doc": { + "versions": { + "1.5-r2": 1509483418 + }, + "origin": "perl-convert-uulib" + }, + "haserl-lua5.2": { + "versions": { + "0.9.35-r1": 1509468296 + }, + "origin": "haserl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua-5.2.so.0" + ], + "provides": [ + "cmd:haserl-lua5.2" + ] + }, + "libiec61883-utils": { + "versions": { + "1.2.0-r1": 1509470083 + }, + "origin": "libiec61883", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libiec61883.so.0", + "so:libraw1394.so.11" + ], + "provides": [ + "cmd:plugctl", + "cmd:plugreport" + ] + }, + "snort-doc": { + "versions": { + "2.9.11-r0": 1510848484 + }, + "origin": "snort" + }, + "perl-try-tiny": { + "versions": { + "0.22-r1": 1509464413 + }, + "origin": "perl-try-tiny" + }, + "lua5.1-microlight": { + "versions": { + "1.1.1-r2": 1509480781 + }, + "origin": "lua-microlight" + }, + "dahdi-tools-dev": { + "versions": { + "2.11.1-r0": 1509476155 + }, + "origin": "dahdi-tools", + "dependencies": [ + "bsd-compat-headers", + "linux-headers", + "dahdi-linux-dev", + "newt-dev", + "dahdi-tools=2.11.1-r0" + ] + }, + "farstream-doc": { + "versions": { + "0.2.8-r2": 1510931394 + }, + "origin": "farstream" + }, + "libqrencode": { + "versions": { + "4.0.0-r0": 1509482400 + }, + "origin": "libqrencode", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpng16.so.16" + ], + "provides": [ + "so:libqrencode.so.4=4.0.0", + "cmd:qrencode" + ] + }, + "kamailio-snmpstats": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.30", + "so:libnetsnmpagent.so.30" + ] + }, + "librevenge": { + "versions": { + "0.0.4-r0": 1509495169 + }, + "origin": "librevenge", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:librevenge-0.0.so.0=0.0.4", + "so:librevenge-generators-0.0.so.0=0.0.4", + "so:librevenge-stream-0.0.so.0=0.0.4" + ] + }, + "libtool-doc": { + "versions": { + "2.4.6-r4": 1509456826 + }, + "origin": "libtool" + }, + "htop": { + "versions": { + "2.0.2-r0": 1509494929 + }, + "origin": "htop", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:htop" + ] + }, + "libdvdread": { + "versions": { + "5.0.3-r1": 1509480296 + }, + "origin": "libdvdread", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdvdcss.so.2" + ], + "provides": [ + "so:libdvdread.so.4=4.2.0" + ] + }, + "acf-alpine-conf": { + "versions": { + "0.9.0-r3": 1510076342 + }, + "origin": "acf-alpine-conf", + "dependencies": [ + "acf-core", + "lua-posix", + "libressl" + ] + }, + "mercurial-bash-completion": { + "versions": { + "4.5.2-r0": 1532937173, + "4.5.2-r1": 1563792023 + }, + "origin": "mercurial" + }, + "py-tracing": { + "versions": { + "0.8-r1": 1509552705 + }, + "origin": "py-tracing", + "dependencies": [ + "python2" + ] + }, + "coova-chilli": { + "versions": { + "1.3.2-r2": 1510287299 + }, + "origin": "coova-chilli", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "so:libbstring.so.0=0.0.0", + "so:libchilli.so.0=0.0.0", + "cmd:chilli", + "cmd:chilli_opt", + "cmd:chilli_query", + "cmd:chilli_radconfig", + "cmd:chilli_redir", + "cmd:chilli_response", + "cmd:chilli_script" + ] + }, + "py-pynacl": { + "versions": { + "1.2.0-r0": 1510088490 + }, + "origin": "py-pynacl" + }, + "libverto-libevent": { + "versions": { + "0.3.0-r0": 1509469586 + }, + "origin": "libverto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libverto.so.1" + ], + "provides": [ + "so:libverto-libevent.so.1=1.0.0" + ] + }, + "qemu-sh4eb": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-sh4eb" + ] + }, + "usb-modeswitch": { + "versions": { + "2.5.1-r0": 1509480148 + }, + "origin": "usb-modeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "cmd:usb_modeswitch" + ] + }, + "git-daemon": { + "versions": { + "2.15.3-r0": 1540401293, + "2.15.4-r0": 1576015190 + }, + "origin": "git", + "dependencies": [ + "git=2.15.4-r0", + "so:libc.musl-x86_64.so.1", + "so:libpcre2-8.so.0", + "so:libz.so.1" + ] + }, + "gawk": { + "versions": { + "4.2.0-r0": 1509457020 + }, + "origin": "gawk", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:awk", + "cmd:gawk", + "cmd:gawk-4.2.0" + ] + }, + "thunar-vcs-plugin-git": { + "versions": { + "0.1.4-r6": 1510314590 + }, + "origin": "thunar-vcs-plugin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libxfce4util.so.7" + ] + }, + "libsoup": { + "versions": { + "2.60.2-r0": 1509480670 + }, + "origin": "libsoup", + "dependencies": [ + "glib-networking", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libsqlite3.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libsoup-2.4.so.1=1.8.0", + "so:libsoup-gnome-2.4.so.1=1.8.0" + ] + }, + "lua5.2-bitlib": { + "versions": { + "5.3.0-r0": 1509468307 + }, + "origin": "lua-bitlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libusb-dev": { + "versions": { + "1.0.21-r0": 1509470805 + }, + "origin": "libusb", + "dependencies": [ + "libusb=1.0.21-r0", + "pkgconfig" + ], + "provides": [ + "pc:libusb-1.0=1.0.21" + ] + }, + "openvpn": { + "versions": { + "2.4.4-r1": 1510259339 + }, + "origin": "openvpn", + "dependencies": [ + "iproute2", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:liblzo2.so.2", + "so:libssl.so.44" + ], + "provides": [ + "cmd:openvpn" + ] + }, + "perl-carp-doc": { + "versions": { + "1.38-r0": 1509483987 + }, + "origin": "perl-carp" + }, + "xfce4-terminal-doc": { + "versions": { + "0.6.3-r1": 1510074612 + }, + "origin": "xfce4-terminal" + }, + "perl-crypt-eksblowfish-doc": { + "versions": { + "0.009-r4": 1509470468 + }, + "origin": "perl-crypt-eksblowfish" + }, + "lua5.1-ldbus": { + "versions": { + "20150430-r2": 1509492574 + }, + "origin": "lua-ldbus", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3" + ] + }, + "gapk": { + "versions": { + "0.1-r1": 1510068290 + }, + "origin": "gapk", + "dependencies": [ + "apk-tools", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0" + ], + "provides": [ + "cmd:gapk" + ] + }, + "tzdata": { + "versions": { + "2019a-r0": 1556626900, + "2019b-r0": 1566548308, + "2019c-r0": 1571319267 + }, + "origin": "tzdata", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:posixtz", + "cmd:zdump", + "cmd:zic" + ] + }, + "uwsgi-pam": { + "versions": { + "2.0.17-r0": 1522154656 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0" + ] + }, + "perl-mail-spamassassin-doc": { + "versions": { + "3.4.1-r2": 1509517006 + }, + "origin": "perl-mail-spamassassin" + }, + "igmpproxy-doc": { + "versions": { + "0.1-r5": 1509489516 + }, + "origin": "igmpproxy" + }, + "uwsgi-cheaper_busyness": { + "versions": { + "2.0.17-r0": 1522154654 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "qca": { + "versions": { + "2.1.3-r4": 1510288320 + }, + "origin": "qca", + "dependencies": [ + "so:libQtCore.so.4", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libsasl2.so.3", + "so:libssl.so.44", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libqca.so.2=2.1.3", + "cmd:mozcerts", + "cmd:qcatool" + ] + }, + "f2fs-tools-libs": { + "versions": { + "1.6.1-r0": 1509483524 + }, + "origin": "f2fs-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libf2fs.so.0=0.0.1", + "so:libf2fs_format.so.0=0.0.1" + ] + }, + "libvirt": { + "versions": { + "3.9.0-r1": 1510088352, + "5.5.0-r0": 1562165541 + }, + "origin": "libvirt", + "dependencies": [ + "lvm2", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libparted.so.2", + "so:libtirpc.so.3", + "so:libvirt.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:virtlockd", + "cmd:virtlogd" + ] + }, + "glade-lang": { + "versions": { + "3.20.1-r0": 1510073638 + }, + "origin": "glade" + }, + "xtables-addons-hardened": { + "versions": { + "4.9.65-r1": 1511799238 + }, + "origin": "xtables-addons-hardened", + "dependencies": [ + "linux-hardened=4.9.65-r1" + ], + "provides": [ + "xtables-addons-grsec=4.9.65-r1" + ] + }, + "minicom-lang": { + "versions": { + "2.7.1-r0": 1509485710 + }, + "origin": "minicom" + }, + "font-misc-cyrillic": { + "versions": { + "1.0.3-r0": 1509494291 + }, + "origin": "font-misc-cyrillic", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "apr-util-dbd_sqlite3": { + "versions": { + "1.6.1-r1": 1510285059 + }, + "origin": "apr-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "libxrender-doc": { + "versions": { + "0.9.10-r2": 1509462094 + }, + "origin": "libxrender" + }, + "libebml": { + "versions": { + "1.3.5-r0": 1509479829, + "1.3.5-r1": 1564417272 + }, + "origin": "libebml", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libebml.so.4=4.0.0" + ] + }, + "squid-lang-ka": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-text-wrapper": { + "versions": { + "1.04-r0": 1509490816 + }, + "origin": "perl-text-wrapper" + }, + "libksba-dev": { + "versions": { + "1.3.5-r0": 1509472890 + }, + "origin": "libksba", + "dependencies": [ + "libksba=1.3.5-r0" + ], + "provides": [ + "cmd:ksba-config" + ] + }, + "swfdec": { + "versions": { + "0.9.2-r0": 1510073747 + }, + "origin": "swfdec", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstpbutils-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgtk-x11-2.0.so.0", + "so:liboil-0.3.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libsoup-2.4.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libswfdec-0.9.so.2=2.0.0", + "so:libswfdec-gtk-0.9.so.2=2.0.0" + ] + }, + "py2-purl": { + "versions": { + "1.3.1-r0": 1509483541 + }, + "origin": "py-purl", + "dependencies": [ + "py2-six", + "python2" + ] + }, + "sems-doc": { + "versions": { + "1.6.0-r6": 1510260895 + }, + "origin": "sems" + }, + "libburn-dev": { + "versions": { + "1.4.8-r0": 1509462199 + }, + "origin": "libburn", + "dependencies": [ + "libburn=1.4.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:libburn-1=1.4.8" + ] + }, + "postfix-ldap": { + "versions": { + "3.2.4-r1": 1510285340 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ], + "provides": [ + "so:postfix-ldap.so=0" + ] + }, + "cdparanoia-libs": { + "versions": { + "10.2-r7": 1509471029 + }, + "origin": "cdparanoia", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcdda_interface.so.0=0.10.2", + "so:libcdda_paranoia.so.0=0.10.2" + ] + }, + "axel-doc": { + "versions": { + "2.4-r1": 1509494657 + }, + "origin": "axel" + }, + "lm_sensors": { + "versions": { + "3.4.0-r4": 1509475444 + }, + "origin": "lm_sensors", + "dependencies": [ + "bash", + "sysfsutils", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsensors.so.4=4.4.0", + "cmd:fancontrol", + "cmd:isadump", + "cmd:isaset", + "cmd:pwmconfig", + "cmd:sensors" + ] + }, + "kamailio-xmpp": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ] + }, + "libpciaccess": { + "versions": { + "0.13.5-r1": 1509466089 + }, + "origin": "libpciaccess", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpciaccess.so.0=0.11.1" + ] + }, + "perl-log-dispatch-doc": { + "versions": { + "2.67-r0": 1510953098 + }, + "origin": "perl-log-dispatch" + }, + "isl-dev": { + "versions": { + "0.18-r0": 1509456966 + }, + "origin": "isl", + "dependencies": [ + "gmp-dev", + "isl=0.18-r0", + "pkgconfig" + ], + "provides": [ + "pc:isl=0.18" + ] + }, + "openobex-dev": { + "versions": { + "1.7.2-r1": 1510071733 + }, + "origin": "openobex", + "dependencies": [ + "libusb-dev", + "openobex=1.7.2-r1", + "pc:libusb-1.0", + "pkgconfig" + ], + "provides": [ + "pc:openobex=1.7.2" + ] + }, + "ghostscript-dev": { + "versions": { + "9.26-r2": 1554362768, + "9.26-r3": 1565700439, + "9.26-r4": 1571234493 + }, + "origin": "ghostscript", + "dependencies": [ + "ghostscript=9.26-r4", + "pkgconfig" + ], + "provides": [ + "pc:ijs=0.35" + ] + }, + "perl-class-mix": { + "versions": { + "0.005-r0": 1509470460 + }, + "origin": "perl-class-mix", + "dependencies": [ + "perl-params-classify" + ] + }, + "gtk+2.0": { + "versions": { + "2.24.31-r0": 1510066782 + }, + "origin": "gtk+2.0", + "dependencies": [ + "shared-mime-info", + "gtk-update-icon-cache", + "/bin/sh", + "so:libX11.so.6", + "so:libXcomposite.so.1", + "so:libXcursor.so.1", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXi.so.6", + "so:libXrandr.so.2", + "so:libXrender.so.1", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcups.so.2", + "so:libfontconfig.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpangoft2-1.0.so.0" + ], + "provides": [ + "so:libgailutil.so.18=18.0.1", + "so:libgdk-x11-2.0.so.0=0.2400.31", + "so:libgtk-x11-2.0.so.0=0.2400.31", + "cmd:gtk-query-immodules-2.0" + ] + }, + "perl-fcgi-doc": { + "versions": { + "0.78-r1": 1509474656 + }, + "origin": "perl-fcgi" + }, + "acf-pingu": { + "versions": { + "0.4.0-r2": 1510074967 + }, + "origin": "acf-pingu", + "dependencies": [ + "acf-core", + "pingu" + ] + }, + "perl-http-body": { + "versions": { + "1.17-r0": 1509481772 + }, + "origin": "perl-http-body", + "dependencies": [ + "perl", + "perl-http-message", + "perl-uri" + ] + }, + "lua-socket": { + "versions": { + "3.0_rc1_git20160306-r2": 1509480763 + }, + "origin": "lua-socket" + }, + "freeradius-doc": { + "versions": { + "3.0.15-r4": 1556202795, + "3.0.15-r5": 1566310602 + }, + "origin": "freeradius" + }, + "perl-font-afm-doc": { + "versions": { + "1.20-r0": 1509481557 + }, + "origin": "perl-font-afm" + }, + "py-ediarpc": { + "versions": { + "0.3-r0": 1509493833 + }, + "origin": "py-ediarpc", + "dependencies": [ + "python2" + ] + }, + "py3-country": { + "versions": { + "17.9.23-r0": 1509493919 + }, + "origin": "py-country", + "dependencies": [ + "python3" + ] + }, + "dtc-dev": { + "versions": { + "1.4.4-r0": 1509476728 + }, + "origin": "dtc", + "dependencies": [ + "libfdt=1.4.4-r0" + ] + }, + "herbstluftwm": { + "versions": { + "0.7.0-r1": 1509551853 + }, + "origin": "herbstluftwm", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXinerama.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:dmenu_run_hlwm", + "cmd:herbstclient", + "cmd:herbstluftwm" + ] + }, + "util-macros": { + "versions": { + "1.19.1-r1": 1509461801 + }, + "origin": "util-macros" + }, + "lua5.1-mosquitto": { + "versions": { + "0.2-r1": 1509496426 + }, + "origin": "lua-mosquitto", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ] + }, + "ppp-chat": { + "versions": { + "2.4.7-r5": 1509480126 + }, + "origin": "ppp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:chat" + ] + }, + "recode-dev": { + "versions": { + "3.6-r1": 1509485758 + }, + "origin": "recode", + "dependencies": [ + "recode=3.6-r1" + ] + }, + "gengetopt-doc": { + "versions": { + "2.22.6-r2": 1509488507 + }, + "origin": "gengetopt" + }, + "bcache-tools": { + "versions": { + "1.0.8-r1": 1509495229 + }, + "origin": "bcache-tools", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:bcache-super-show", + "cmd:make-bcache" + ] + }, + "uwsgi-forkptyrouter": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "uwsgi-corerouter", + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-ecdsa": { + "versions": { + "0.13-r5": 1509494956 + }, + "origin": "py-ecdsa", + "dependencies": [ + "py2-crypto" + ] + }, + "jansson": { + "versions": { + "2.10-r0": 1509472129 + }, + "origin": "jansson", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libjansson.so.4=4.10.0" + ] + }, + "lua5.1-yaml": { + "versions": { + "1.1.2-r1": 1509488584 + }, + "origin": "lua-yaml", + "dependencies": [ + "lua5.1", + "lua5.1-lub", + "so:libc.musl-x86_64.so.1" + ] + }, + "abiword-plugin-mht": { + "versions": { + "3.0.2-r1": 1510073363 + }, + "origin": "abiword" + }, + "speedtest-cli": { + "versions": { + "1.0.7-r0": 1509540426 + }, + "origin": "speedtest-cli", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:speedtest", + "cmd:speedtest-cli" + ] + }, + "py2-dateutil": { + "versions": { + "2.6.1-r0": 1509475853 + }, + "origin": "py-dateutil", + "dependencies": [ + "py2-six", + "python2" + ] + }, + "gstreamer0.10-dev": { + "versions": { + "0.10.36-r2": 1509470980 + }, + "origin": "gstreamer0.10", + "dependencies": [ + "glib-dev", + "libxml2-dev", + "gstreamer0.10=0.10.36-r2", + "pc:glib-2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gstreamer-0.10=0.10.36", + "pc:gstreamer-base-0.10=0.10.36", + "pc:gstreamer-check-0.10=0.10.36", + "pc:gstreamer-controller-0.10=0.10.36", + "pc:gstreamer-dataprotocol-0.10=0.10.36", + "pc:gstreamer-net-0.10=0.10.36" + ] + }, + "lxc-templates": { + "versions": { + "2.1.1-r3": 1533578833 + }, + "origin": "lxc", + "dependencies": [ + "bash", + "tar" + ] + }, + "xf86-video-sunleo": { + "versions": { + "1.2.2-r0": 1510074827 + }, + "origin": "xf86-video-sunleo", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "glib-doc": { + "versions": { + "2.54.2-r0": 1509911132, + "2.54.2-r1": 1560764732 + }, + "origin": "glib" + }, + "b43-fwcutter-doc": { + "versions": { + "019-r0": 1509495308 + }, + "origin": "b43-fwcutter" + }, + "giflib": { + "versions": { + "5.1.4-r1": 1509470482 + }, + "origin": "giflib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgif.so.7=7.0.0" + ] + }, + "kamailio-dbtext": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "kamailio-db", + "so:libc.musl-x86_64.so.1", + "so:libsrdb1.so.1" + ] + }, + "xineramaproto": { + "versions": { + "1.2.1-r3": 1509468238 + }, + "origin": "xineramaproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xineramaproto=1.2.1" + ] + }, + "collectd-dns": { + "versions": { + "5.7.2-r0": 1510069655 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ] + }, + "xf86-video-vesa-doc": { + "versions": { + "2.3.4-r2": 1510074818 + }, + "origin": "xf86-video-vesa" + }, + "perl-net-dns-resolver-programmable-doc": { + "versions": { + "0.003-r2": 1509475479 + }, + "origin": "perl-net-dns-resolver-programmable" + }, + "gconf-doc": { + "versions": { + "3.2.6-r1": 1510928342 + }, + "origin": "gconf" + }, + "font-jis-misc": { + "versions": { + "1.0.3-r0": 1509495351 + }, + "origin": "font-jis-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "gtk-engines-thinice": { + "versions": { + "2.21.0-r2": 1510071725 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "groff": { + "versions": { + "1.22.3-r2": 1509460768 + }, + "origin": "groff", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:addftinfo", + "cmd:afmtodit", + "cmd:chem", + "cmd:eqn", + "cmd:eqn2graph", + "cmd:gdiffmk", + "cmd:glilypond", + "cmd:gperl", + "cmd:gpinyin", + "cmd:grap2graph", + "cmd:grn", + "cmd:grodvi", + "cmd:groff", + "cmd:groffer", + "cmd:grog", + "cmd:grolbp", + "cmd:grolj4", + "cmd:gropdf", + "cmd:grops", + "cmd:grotty", + "cmd:hpftodit", + "cmd:indxbib", + "cmd:lkbib", + "cmd:lookbib", + "cmd:mmroff", + "cmd:neqn", + "cmd:nroff", + "cmd:pdfmom", + "cmd:pdfroff", + "cmd:pfbtops", + "cmd:pic", + "cmd:pic2graph", + "cmd:post-grohtml", + "cmd:pre-grohtml", + "cmd:preconv", + "cmd:refer", + "cmd:roff2dvi", + "cmd:roff2html", + "cmd:roff2pdf", + "cmd:roff2ps", + "cmd:roff2text", + "cmd:roff2x", + "cmd:soelim", + "cmd:tbl", + "cmd:tfmtodit", + "cmd:troff" + ] + }, + "unbound-doc": { + "versions": { + "1.6.7-r1": 1510588258 + }, + "origin": "unbound" + }, + "nmap-scripts": { + "versions": { + "7.60-r2": 1510261467, + "7.60-r3": 1572297244 + }, + "origin": "nmap", + "dependencies": [ + "nmap-nselibs" + ] + }, + "perl-posix-strftime-compiler": { + "versions": { + "0.41-r0": 1509481800 + }, + "origin": "perl-posix-strftime-compiler", + "dependencies": [ + "tzdata" + ] + }, + "perl-cps-doc": { + "versions": { + "0.18-r0": 1509477797 + }, + "origin": "perl-cps" + }, + "mariadb-client": { + "versions": { + "10.1.38-r1": 1551187990, + "10.1.40-r0": 1560354887, + "10.1.41-r0": 1565163111 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libncursesw.so.6", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:myisam_ftdump", + "cmd:mysql", + "cmd:mysql_find_rows", + "cmd:mysql_fix_extensions", + "cmd:mysql_waitpid", + "cmd:mysqlaccess", + "cmd:mysqladmin", + "cmd:mysqlbug", + "cmd:mysqlcheck", + "cmd:mysqldump", + "cmd:mysqldumpslow", + "cmd:mysqlimport", + "cmd:mysqlshow" + ] + }, + "perl-rrd": { + "versions": { + "1.5.6-r3": 1512296349 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librrd.so.4" + ] + }, + "goffice-dev": { + "versions": { + "0.10.36-r0": 1511455403 + }, + "origin": "goffice", + "dependencies": [ + "glib-dev", + "libgsf-dev", + "cairo-dev", + "libxml2-dev", + "gtk+3.0-dev", + "librsvg-dev", + "goffice=0.10.36-r0", + "pc:cairo", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:gtk+-3.0", + "pc:libgsf-1", + "pc:librsvg-2.0", + "pc:libxml-2.0", + "pc:libxslt", + "pc:pangocairo", + "pkgconfig" + ], + "provides": [ + "pc:libgoffice-0.10=0.10.36" + ] + }, + "libproc": { + "versions": { + "3.3.12-r3": 1509492055 + }, + "origin": "procps", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libprocps.so.6=6.0.0" + ] + }, + "ilbc-dev": { + "versions": { + "0.0.1-r0": 1510584810 + }, + "origin": "ilbc", + "dependencies": [ + "ilbc=0.0.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:ilbc=0.0.1" + ] + }, + "gnokii-libs": { + "versions": { + "0.6.31-r6": 1510069846 + }, + "origin": "gnokii", + "dependencies": [ + "so:libXpm.so.4", + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libical.so.2", + "so:libintl.so.8", + "so:libusb-0.1.so.4" + ], + "provides": [ + "so:libgnokii.so.7=7.0.0" + ] + }, + "git-gitweb": { + "versions": { + "2.15.3-r0": 1540401293, + "2.15.4-r0": 1576015190 + }, + "origin": "git", + "dependencies": [ + "git=2.15.4-r0", + "perl" + ] + }, + "lua5.1-dbi-postgresql": { + "versions": { + "0.6-r1": 1511483402 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "openssh-doc": { + "versions": { + "7.5_p1-r10": 1551712287 + }, + "origin": "openssh" + }, + "lua5.2-feedparser": { + "versions": { + "0.71-r0": 1509485579 + }, + "origin": "lua-feedparser", + "dependencies": [ + "lua5.2-expat", + "lua-feedparser-common", + "lua-feedparser-common=0.71-r0" + ] + }, + "acf-lighttpd": { + "versions": { + "0.6.0-r2": 1510072476 + }, + "origin": "acf-lighttpd", + "dependencies": [ + "acf-core", + "lighttpd" + ] + }, + "perl-socket": { + "versions": { + "2.024-r2": 1509484006 + }, + "origin": "perl-socket", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libdv-tools": { + "versions": { + "1.0.0-r3": 1510068409 + }, + "origin": "libdv", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdv.so.4", + "so:libgdk-x11-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ], + "provides": [ + "cmd:dubdv", + "cmd:dvconnect", + "cmd:encodedv", + "cmd:playdv" + ] + }, + "swish-e-dev": { + "versions": { + "2.4.7-r8": 1509483301 + }, + "origin": "swish-e", + "dependencies": [ + "pkgconfig", + "swish-e=2.4.7-r8" + ], + "provides": [ + "pc:swish-e=2.4.7", + "cmd:swish-config" + ] + }, + "flex-dev": { + "versions": { + "2.6.4-r1": 1509456704 + }, + "origin": "flex", + "dependencies": [ + "flex", + "flex-libs=2.6.4-r1" + ] + }, + "cppunit": { + "versions": { + "1.14.0-r0": 1509481255 + }, + "origin": "cppunit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libcppunit-1.14.so.0=0.0.0", + "cmd:DllPlugInTester" + ] + }, + "perl-db_file-doc": { + "versions": { + "1.840-r1": 1509491809 + }, + "origin": "perl-db_file" + }, + "grep": { + "versions": { + "3.1-r0": 1509468224 + }, + "origin": "grep", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ], + "provides": [ + "cmd:egrep", + "cmd:fgrep", + "cmd:grep" + ] + }, + "openbox-libs": { + "versions": { + "3.6.1-r1": 1510073885 + }, + "origin": "openbox", + "dependencies": [ + "so:libImlib2.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangoxft-1.0.so.0", + "so:librsvg-2.so.2", + "so:libxml2.so.2" + ], + "provides": [ + "so:libobrender.so.32=32.0.0", + "so:libobt.so.2=2.0.2" + ] + }, + "libcdio": { + "versions": { + "0.94-r0": 1509473628 + }, + "origin": "libcdio", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcdio.so.16=16.0.0", + "so:libiso9660.so.10=10.0.0", + "so:libudf.so.0=0.0.0" + ] + }, + "py2-mock": { + "versions": { + "2.0.0-r3": 1509493394 + }, + "origin": "py-mock", + "dependencies": [ + "py2-pbr", + "py2-six", + "py2-funcsigs", + "python2" + ] + }, + "perl-extutils-helpers-doc": { + "versions": { + "0.026-r0": 1509474753 + }, + "origin": "perl-extutils-helpers" + }, + "libdv-doc": { + "versions": { + "1.0.0-r3": 1510068409 + }, + "origin": "libdv" + }, + "ragel": { + "versions": { + "6.10-r0": 1509490669 + }, + "origin": "ragel", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:ragel" + ] + }, + "drbd-utils-doc": { + "versions": { + "9.1.1-r0": 1511795634 + }, + "origin": "drbd-utils" + }, + "xf86-video-vmware": { + "versions": { + "13.2.1-r0": 1510072912 + }, + "origin": "xf86-video-vmware", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libxatracker.so.2" + ] + }, + "ncftp-doc": { + "versions": { + "3.2.6-r1": 1509472911 + }, + "origin": "ncftp" + }, + "tevent": { + "versions": { + "0.9.34-r0": 1511479236 + }, + "origin": "tevent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtalloc.so.2" + ], + "provides": [ + "so:libtevent.so.0=0.9.34" + ] + }, + "xf86-video-ast": { + "versions": { + "1.1.5-r1": 1510074918 + }, + "origin": "xf86-video-ast", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "ngrep-doc": { + "versions": { + "1.45-r4": 1509489072 + }, + "origin": "ngrep" + }, + "acf-mdadm": { + "versions": { + "0.5.0-r2": 1510072261 + }, + "origin": "acf-mdadm", + "dependencies": [ + "acf-core", + "mdadm" + ] + }, + "spamassassin-client": { + "versions": { + "3.4.1-r8": 1509517412 + }, + "origin": "spamassassin", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:spamc" + ] + }, + "mrxvt-doc": { + "versions": { + "0.5.4-r7": 1509469844 + }, + "origin": "mrxvt" + }, + "gst-plugins-base0.10-dev": { + "versions": { + "0.10.36-r3": 1510068367 + }, + "origin": "gst-plugins-base0.10", + "dependencies": [ + "gstreamer0.10-dev", + "gst-plugins-base0.10=0.10.36-r3", + "pc:glib-2.0", + "pc:gstreamer-0.10", + "pc:gstreamer-base-0.10", + "pkgconfig" + ], + "provides": [ + "pc:gstreamer-app-0.10=0.10.36", + "pc:gstreamer-audio-0.10=0.10.36", + "pc:gstreamer-cdda-0.10=0.10.36", + "pc:gstreamer-fft-0.10=0.10.36", + "pc:gstreamer-floatcast-0.10=0.10.36", + "pc:gstreamer-interfaces-0.10=0.10.36", + "pc:gstreamer-netbuffer-0.10=0.10.36", + "pc:gstreamer-pbutils-0.10=0.10.36", + "pc:gstreamer-plugins-base-0.10=0.10.36", + "pc:gstreamer-riff-0.10=0.10.36", + "pc:gstreamer-rtp-0.10=0.10.36", + "pc:gstreamer-rtsp-0.10=0.10.36", + "pc:gstreamer-sdp-0.10=0.10.36", + "pc:gstreamer-tag-0.10=0.10.36", + "pc:gstreamer-video-0.10=0.10.36" + ] + }, + "qt": { + "versions": { + "4.8.7-r8": 1510287210 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libQtCore.so.4=4.8.7", + "so:libQtDBus.so.4=4.8.7", + "so:libQtNetwork.so.4=4.8.7", + "so:libQtScript.so.4=4.8.7", + "so:libQtSql.so.4=4.8.7", + "so:libQtTest.so.4=4.8.7", + "so:libQtXml.so.4=4.8.7", + "so:libQtXmlPatterns.so.4=4.8.7", + "cmd:qdbus" + ] + }, + "py-factory-boy": { + "versions": { + "2.9.2-r0": 1509493824 + }, + "origin": "py-factory-boy" + }, + "font-bh-lucidatypewriter-75dpi": { + "versions": { + "1.0.3-r0": 1509494513 + }, + "origin": "font-bh-lucidatypewriter-75dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "lua5.2-rex-posix": { + "versions": { + "2.9.0-r0": 1510619366 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "dri2proto-doc": { + "versions": { + "2.8-r2": 1509466228 + }, + "origin": "dri2proto" + }, + "freeradius-pam": { + "versions": { + "3.0.15-r4": 1556202797, + "3.0.15-r5": 1566310604 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.15-r5", + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0" + ], + "provides": [ + "freeradius3-pam=3.0.15-r5", + "so:rlm_pam.so=0" + ] + }, + "libbsd": { + "versions": { + "0.8.6-r1": 1509461838 + }, + "origin": "libbsd", + "dependencies": [ + "musl>=1.1.16-r22", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libbsd.so.0=0.8.6" + ] + }, + "btrfs-progs-bash-completion": { + "versions": { + "4.13.2-r0": 1509481818 + }, + "origin": "btrfs-progs" + }, + "cvs": { + "versions": { + "1.11.23-r0": 1509481180 + }, + "origin": "cvs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cvs", + "cmd:cvsbug", + "cmd:rcs2log" + ] + }, + "py-webassets": { + "versions": { + "0.12.1-r1": 1509551873 + }, + "origin": "py-webassets", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:webassets" + ] + }, + "xcb-util-image": { + "versions": { + "0.4.0-r1": 1509473909 + }, + "origin": "xcb-util-image", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-shm.so.0", + "so:libxcb-util.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-image.so.0=0.0.0" + ] + }, + "randrproto-doc": { + "versions": { + "1.5.0-r2": 1509465995 + }, + "origin": "randrproto" + }, + "ngrep": { + "versions": { + "1.45-r4": 1509489072 + }, + "origin": "ngrep", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:ngrep" + ] + }, + "perl-variable-magic-doc": { + "versions": { + "0.62-r0": 1510087954 + }, + "origin": "perl-variable-magic" + }, + "multipath-tools-doc": { + "versions": { + "0.7.4-r0": 1510816244 + }, + "origin": "multipath-tools" + }, + "libidl-doc": { + "versions": { + "0.8.14-r2": 1510928272 + }, + "origin": "libidl" + }, + "postgresql-pltcl": { + "versions": { + "10.7-r0": 1554274199, + "10.8-r0": 1557668015, + "10.9-r0": 1562225614, + "10.10-r0": 1565683438 + }, + "origin": "postgresql", + "dependencies": [ + "pgtcl", + "so:libc.musl-x86_64.so.1", + "so:libtcl8.6.so" + ] + }, + "acf-kamailio": { + "versions": { + "0.10.0-r2": 1510076040 + }, + "origin": "acf-kamailio", + "dependencies": [ + "acf-core", + "kamailio", + "acf-db-lib" + ] + }, + "spice": { + "versions": { + "0.14.1-r2": 1548934012 + }, + "origin": "spice" + }, + "libgcc": { + "versions": { + "6.4.0-r5": 1509458071 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgcc_s.so.1=1" + ] + }, + "qemu-ppc64abi32": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-ppc64abi32" + ] + }, + "perl-unix-syslog-doc": { + "versions": { + "1.1-r9": 1509484017 + }, + "origin": "perl-unix-syslog" + }, + "py2-tornado": { + "versions": { + "4.5.2-r1": 1509551888 + }, + "origin": "py-tornado", + "dependencies": [ + "py2-backports_abc", + "py2-certifi", + "py2-singledispatch", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "freeswitch-sounds-ru-RU-elena-8000": { + "versions": { + "1.0.12-r1": 1509496008 + }, + "origin": "freeswitch-sounds-ru-RU-elena-8000" + }, + "apache2-lua": { + "versions": { + "2.4.39-r0": 1554306881, + "2.4.41-r0": 1566292328 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ] + }, + "libdrm": { + "versions": { + "2.4.88-r0": 1510649882 + }, + "origin": "libdrm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpciaccess.so.0" + ], + "provides": [ + "so:libdrm.so.2=2.4.0", + "so:libdrm_amdgpu.so.1=1.0.0", + "so:libdrm_freedreno.so.1=1.0.0", + "so:libdrm_intel.so.1=1.0.0", + "so:libdrm_nouveau.so.2=2.0.0", + "so:libdrm_radeon.so.1=1.0.1", + "so:libkms.so.1=1.0.0" + ] + }, + "tar": { + "versions": { + "1.32-r0": 1551092030 + }, + "origin": "tar", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tar" + ] + }, + "nodejs-doc": { + "versions": { + "8.9.3-r1": 1522616958 + }, + "origin": "nodejs" + }, + "py-enum34": { + "versions": { + "1.1.6-r2": 1509493830 + }, + "origin": "py-enum34" + }, + "portaudio-dev": { + "versions": { + "19-r1": 1509473342 + }, + "origin": "portaudio", + "dependencies": [ + "alsa-lib-dev", + "pc:alsa", + "pkgconfig", + "portaudio=19-r1" + ], + "provides": [ + "pc:portaudio-2.0=19" + ] + }, + "openjpeg": { + "versions": { + "2.3.0-r2": 1552584602 + }, + "origin": "openjpeg", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libopenjp2.so.7=2.3.0" + ] + }, + "expat-dev": { + "versions": { + "2.2.5-r0": 1509908121, + "2.2.7-r0": 1561897528, + "2.2.7-r1": 1568353727, + "2.2.8-r0": 1568974114 + }, + "origin": "expat", + "dependencies": [ + "expat=2.2.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:expat=2.2.8" + ] + }, + "perl-test-number-delta": { + "versions": { + "1.06-r0": 1509481695 + }, + "origin": "perl-test-number-delta" + }, + "openldap-overlay-valsort": { + "versions": { + "2.4.45-r3": 1510258136, + "2.4.46-r0": 1565073941, + "2.4.48-r0": 1566900518 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "rrdtool-cgi": { + "versions": { + "1.5.6-r3": 1512296349 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librrd.so.4" + ], + "provides": [ + "cmd:rrdcgi" + ] + }, + "mailcap-doc": { + "versions": { + "2.1.48-r0": 1509474792 + }, + "origin": "mailcap" + }, + "sudo-dev": { + "versions": { + "1.8.21_p2-r1": 1509459675 + }, + "origin": "sudo" + }, + "openldap-overlay-refint": { + "versions": { + "2.4.45-r3": 1510258135, + "2.4.46-r0": 1565073940, + "2.4.48-r0": 1566900517 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "mdocml": { + "versions": { + "1.14.3-r0": 1509459639 + }, + "origin": "mdocml", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libmandoc.so=0", + "cmd:demandoc", + "cmd:mandoc" + ] + }, + "msmtp-doc": { + "versions": { + "1.6.6-r2": 1510261496 + }, + "origin": "msmtp" + }, + "perdition-doc": { + "versions": { + "1.18-r10": 1510260023 + }, + "origin": "perdition" + }, + "elfutils-dev": { + "versions": { + "0.168-r1": 1509477711 + }, + "origin": "elfutils", + "dependencies": [ + "elfutils-libelf=0.168-r1" + ] + }, + "acf-dovecot": { + "versions": { + "0.6.0-r2": 1510073823 + }, + "origin": "acf-dovecot", + "dependencies": [ + "acf-core", + "dovecot" + ] + }, + "libquvi-dev": { + "versions": { + "0.9.4-r3": 1509492378 + }, + "origin": "libquvi", + "dependencies": [ + "libquvi-scripts-dev", + "curl-dev", + "lua5.1-dev", + "libquvi=0.9.4-r3", + "pkgconfig" + ], + "provides": [ + "pc:libquvi-0.9=0.9.4" + ] + }, + "dialog": { + "versions": { + "1.3.20170509-r0": 1509468657 + }, + "origin": "dialog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:dialog" + ] + }, + "xf86-video-qxl-doc": { + "versions": { + "0.1.5-r3": 1510073030 + }, + "origin": "xf86-video-qxl" + }, + "perl-dbd-sqlite": { + "versions": { + "1.54-r1": 1509477496 + }, + "origin": "perl-dbd-sqlite", + "dependencies": [ + "perl-dbi", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "nagios-plugins-fping": { + "versions": { + "2.2.1-r3": 1510288495 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "fping", + "so:libc.musl-x86_64.so.1" + ] + }, + "pango-doc": { + "versions": { + "1.40.14-r1": 1541519450 + }, + "origin": "pango" + }, + "gcr-lang": { + "versions": { + "3.20.0-r1": 1510073693 + }, + "origin": "gcr" + }, + "db-dev": { + "versions": { + "5.3.28-r0": 1509469314 + }, + "origin": "db", + "dependencies": [ + "db-c++=5.3.28-r0", + "db=5.3.28-r0" + ] + }, + "cutter": { + "versions": { + "1.04-r0": 1509475709 + }, + "origin": "cutter", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cutter" + ] + }, + "gnokii-smsd-pgsql": { + "versions": { + "0.6.31-r6": 1510069851 + }, + "origin": "gnokii", + "dependencies": [ + "gnokii-smsd", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "fftw-double-libs": { + "versions": { + "3.3.6p2-r0": 1509468831 + }, + "origin": "fftw", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfftw3.so.3=3.5.6", + "so:libfftw3_threads.so.3=3.5.6" + ] + }, + "transmission": { + "versions": { + "2.92-r8": 1510932986 + }, + "origin": "transmission", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libevent-2.1.so.6", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:transmission-gtk" + ] + }, + "lxdm": { + "versions": { + "0.5.3-r1": 1510070032 + }, + "origin": "lxdm", + "dependencies": [ + "bash", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libck-connector.so.0", + "so:libdbus-1.so.3", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libxcb.so.1" + ], + "provides": [ + "cmd:lxdm", + "cmd:lxdm-binary", + "cmd:lxdm-config" + ] + }, + "harfbuzz-dev": { + "versions": { + "1.6.3-r0": 1509464873 + }, + "origin": "harfbuzz", + "dependencies": [ + "harfbuzz-icu=1.6.3-r0", + "harfbuzz=1.6.3-r0", + "pc:glib-2.0>=2.19.1", + "pc:graphite2", + "pc:icu-uc", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libfreetype.so.6", + "so:libglib-2.0.so.0", + "so:libharfbuzz.so.0" + ], + "provides": [ + "pc:harfbuzz-icu=1.6.3", + "pc:harfbuzz=1.6.3", + "cmd:hb-ot-shape-closure", + "cmd:hb-shape", + "cmd:hb-view" + ] + }, + "rabbitmq-c": { + "versions": { + "0.8.0-r3": 1510260052 + }, + "origin": "rabbitmq-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "so:librabbitmq.so.4=4.2.0" + ] + }, + "perl-list-utilsby": { + "versions": { + "0.10-r0": 1509488645 + }, + "origin": "perl-list-utilsby" + }, + "perl-php-serialization": { + "versions": { + "0.34-r1": 1509477756 + }, + "origin": "perl-php-serialization", + "dependencies": [ + "perl" + ] + }, + "lua-augeas": { + "versions": { + "0.1.2-r3": 1509473129 + }, + "origin": "lua-augeas" + }, + "a52dec": { + "versions": { + "0.7.4-r6": 1510072176 + }, + "origin": "a52dec", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liba52.so.0=0.0.0", + "cmd:a52dec", + "cmd:extract_a52" + ] + }, + "procmail": { + "versions": { + "3.22-r3": 1513159329 + }, + "origin": "procmail", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:formail", + "cmd:lockfile", + "cmd:mailstat", + "cmd:procmail" + ] + }, + "lua-xml": { + "versions": { + "130610-r5": 1509482648 + }, + "origin": "lua-xml" + }, + "vanessa_logger-dev": { + "versions": { + "0.0.10-r0": 1509481336 + }, + "origin": "vanessa_logger", + "dependencies": [ + "pkgconfig", + "vanessa_logger=0.0.10-r0" + ], + "provides": [ + "pc:vanessa-logger=0.0.10" + ] + }, + "lua5.3-sql-postgres": { + "versions": { + "2.3.5-r1": 1509488832 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "lua-ossl": { + "versions": { + "20171028-r1": 1510260842 + }, + "origin": "lua-ossl" + }, + "postgrey": { + "versions": { + "1.37-r0": 1509479750 + }, + "origin": "postgrey", + "dependencies": [ + "perl", + "perl-db", + "perl-net-dns", + "perl-net-server", + "perl-io-multiplex", + "perl-net-rblclient", + "perl-parse-syslog", + "/bin/sh" + ], + "provides": [ + "cmd:postgrey", + "cmd:postgreyreport" + ] + }, + "device-mapper": { + "versions": { + "2.02.175-r0": 1509462402 + }, + "origin": "lvm2", + "dependencies": [ + "lvm2-libs=2.02.175-r0", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02" + ], + "provides": [ + "cmd:dmsetup", + "cmd:dmstats" + ] + }, + "py2-pynacl": { + "versions": { + "1.2.0-r0": 1510088490 + }, + "origin": "py-pynacl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "cracklib-dev": { + "versions": { + "2.9.6-r0": 1509485916 + }, + "origin": "cracklib", + "dependencies": [ + "cracklib=2.9.6-r0" + ] + }, + "boost-graph": { + "versions": { + "1.62.0-r5": 1509465875 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_regex-mt.so.1.62.0", + "so:libboost_regex.so.1.62.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_graph-mt.so.1.62.0=1.62.0", + "so:libboost_graph.so.1.62.0=1.62.0" + ] + }, + "rrdtool-dev": { + "versions": { + "1.5.6-r3": 1512296349 + }, + "origin": "rrdtool", + "dependencies": [ + "librrd-th=1.5.6-r3", + "librrd=1.5.6-r3", + "pkgconfig" + ], + "provides": [ + "pc:librrd=1.5.6" + ] + }, + "dbus-doc": { + "versions": { + "1.10.24-r0": 1509465101, + "1.10.28-r0": 1560765707 + }, + "origin": "dbus" + }, + "zip-doc": { + "versions": { + "3.0-r4": 1509457024 + }, + "origin": "zip" + }, + "hwdata-oui": { + "versions": { + "0.305-r0": 1509468678 + }, + "origin": "hwdata" + }, + "gtkspell-dev": { + "versions": { + "2.0.16-r6": 1510069680 + }, + "origin": "gtkspell", + "dependencies": [ + "gtk+2.0-dev", + "gtkspell=2.0.16-r6", + "pc:gtk+-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gtkspell-2.0=2.0.16" + ] + }, + "ncdu-doc": { + "versions": { + "1.12-r0": 1509495340 + }, + "origin": "ncdu" + }, + "tevent-dev": { + "versions": { + "0.9.34-r0": 1511479236 + }, + "origin": "tevent", + "dependencies": [ + "pc:talloc", + "pkgconfig", + "tevent=0.9.34-r0" + ], + "provides": [ + "pc:tevent=0.9.34" + ] + }, + "py-snowballstemmer": { + "versions": { + "1.2.1-r1": 1509489497 + }, + "origin": "py-snowballstemmer" + }, + "mrxvt": { + "versions": { + "0.5.4-r7": 1509469848 + }, + "origin": "mrxvt", + "dependencies": [ + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXpm.so.4", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libjpeg.so.8" + ], + "provides": [ + "cmd:mrxvt" + ] + }, + "openldap-dev": { + "versions": { + "2.4.45-r3": 1510258130, + "2.4.46-r0": 1565073935, + "2.4.48-r0": 1566900512 + }, + "origin": "openldap", + "dependencies": [ + "libressl-dev", + "cyrus-sasl-dev", + "util-linux-dev", + "libldap=2.4.48-r0" + ] + }, + "lua5.2-socket": { + "versions": { + "3.0_rc1_git20160306-r2": 1509480760 + }, + "origin": "lua-socket", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "epris": { + "versions": { + "0.2-r4": 1509482713 + }, + "origin": "epris", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgthread-2.0.so.0" + ], + "provides": [ + "cmd:epr" + ] + }, + "gnokii-lang": { + "versions": { + "0.6.31-r6": 1510069856 + }, + "origin": "gnokii" + }, + "perl-data-page": { + "versions": { + "2.02-r1": 1509493850 + }, + "origin": "perl-data-page", + "dependencies": [ + "perl-class-accessor-chained", + "perl-test-exception" + ] + }, + "lua5.1-subprocess": { + "versions": { + "0.0.20141229-r2": 1509468272 + }, + "origin": "lua-subprocess", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gst-plugins-ugly": { + "versions": { + "1.12.3-r0": 1510075820 + }, + "origin": "gst-plugins-ugly", + "dependencies": [ + "so:liba52.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcdio.so.16", + "so:libdvdread.so.4", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstaudio-1.0.so.0", + "so:libgstbase-1.0.so.0", + "so:libgstpbutils-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgstriff-1.0.so.0", + "so:libgstrtp-1.0.so.0", + "so:libgstrtsp-1.0.so.0", + "so:libgstsdp-1.0.so.0", + "so:libgsttag-1.0.so.0", + "so:libgstvideo-1.0.so.0", + "so:libintl.so.8", + "so:libmp3lame.so.0", + "so:libmpeg2.so.0", + "so:libmpg123.so.0", + "so:liborc-0.4.so.0", + "so:libx264.so.148" + ] + }, + "thunar": { + "versions": { + "1.6.12-r0": 1510072626 + }, + "origin": "thunar", + "dependencies": [ + "desktop-file-utils", + "hicolor-icon-theme", + "shared-mime-info", + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libexif.so.12", + "so:libexo-1.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libintl.so.8", + "so:libnotify.so.4", + "so:libpango-1.0.so.0", + "so:libpcre.so.1", + "so:libxfce4panel-1.0.so.4", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "so:libthunarx-2.so.0=0.0.0", + "cmd:Thunar", + "cmd:thunar", + "cmd:thunar-settings" + ] + }, + "ffmpeg-libs": { + "versions": { + "3.4-r1": 1510072147 + }, + "origin": "ffmpeg", + "dependencies": [ + "so:libSDL2-2.0.so.0", + "so:libX11.so.6", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30", + "so:libmp3lame.so.0", + "so:libopus.so.0", + "so:librtmp.so.1", + "so:libtheoradec.so.1", + "so:libtheoraenc.so.1", + "so:libv4l2.so.0", + "so:libva-drm.so.1", + "so:libva-x11.so.1", + "so:libva.so.1", + "so:libvdpau.so.1", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2", + "so:libvpx.so.4", + "so:libx264.so.148", + "so:libx265.so.130", + "so:libxcb-shape.so.0", + "so:libxcb-shm.so.0", + "so:libxcb-xfixes.so.0", + "so:libxcb.so.1", + "so:libxvidcore.so.4", + "so:libz.so.1" + ], + "provides": [ + "so:libavcodec.so.57=57.107.100", + "so:libavdevice.so.57=57.10.100", + "so:libavfilter.so.6=6.107.100", + "so:libavformat.so.57=57.83.100", + "so:libavresample.so.3=3.7.0", + "so:libavutil.so.55=55.78.100", + "so:libpostproc.so.54=54.7.100", + "so:libswresample.so.2=2.9.100", + "so:libswscale.so.4=4.8.100" + ] + }, + "librsvg": { + "versions": { + "2.40.19-r0": 1510066818 + }, + "origin": "librsvg", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcroco-0.6.so.3", + "so:libfontconfig.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpangoft2-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:librsvg-2.so.2=2.40.19", + "cmd:rsvg-convert" + ] + }, + "asterisk-odbc": { + "versions": { + "15.6.1-r0": 1537795340, + "15.6.2-r0": 1568705002 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ] + }, + "ppp-l2tp": { + "versions": { + "2.4.7-r5": 1509480133 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "dtach": { + "versions": { + "0.9-r0": 1509475626 + }, + "origin": "dtach", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dtach" + ] + }, + "libseccomp": { + "versions": { + "2.3.2-r1": 1519818661 + }, + "origin": "libseccomp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libseccomp.so.2=2.3.2", + "cmd:scmp_sys_resolver" + ] + }, + "xf86-video-savage": { + "versions": { + "2.3.9-r0": 1510076050 + }, + "origin": "xf86-video-savage", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "xf86-input-keyboard-doc": { + "versions": { + "1.9.0-r0": 1510074945 + }, + "origin": "xf86-input-keyboard" + }, + "zfs-hardened": { + "versions": { + "4.9.65-r1": 1511799189 + }, + "origin": "zfs-hardened", + "dependencies": [ + "spl-hardened", + "linux-hardened=4.9.65-r1" + ], + "provides": [ + "zfs-grsec=4.9.65-r1" + ] + }, + "libressl-dev": { + "versions": { + "2.6.5-r0": 1529043884 + }, + "origin": "libressl", + "dependencies": [ + "libressl2.6-libcrypto=2.6.5-r0", + "libressl2.6-libssl=2.6.5-r0", + "libressl2.6-libtls=2.6.5-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcrypto=2.6.5", + "pc:libssl=2.6.5", + "pc:libtls=2.6.5", + "pc:openssl=2.6.5" + ] + }, + "py3-sphinx_rtd_theme": { + "versions": { + "0.2.4-r0": 1509469911 + }, + "origin": "py-sphinx_rtd_theme", + "dependencies": [ + "python3" + ] + }, + "patchutils-doc": { + "versions": { + "0.3.4-r0": 1509495751 + }, + "origin": "patchutils" + }, + "elinks-lang": { + "versions": { + "0.13-r4": 1510261265 + }, + "origin": "elinks" + }, + "audacious-dbg": { + "versions": { + "3.9-r0": 1510072525 + }, + "origin": "audacious" + }, + "perl-plack-doc": { + "versions": { + "1.0033-r0": 1510588943 + }, + "origin": "perl-plack" + }, + "font-schumacher-misc": { + "versions": { + "1.1.2-r0": 1509491122 + }, + "origin": "font-schumacher-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "xcmiscproto-doc": { + "versions": { + "1.2.2-r2": 1509473766 + }, + "origin": "xcmiscproto" + }, + "make": { + "versions": { + "4.2.1-r0": 1509458175 + }, + "origin": "make", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:make" + ] + }, + "py2-flask-oauthlib": { + "versions": { + "0.9.3-r2": 1509552814 + }, + "origin": "py-flask-oauthlib", + "dependencies": [ + "py2-flask", + "py2-requests-oauthlib", + "python2" + ] + }, + "mdadm": { + "versions": { + "4.0-r0": 1509476490 + }, + "origin": "mdadm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mdadm", + "cmd:mdmon" + ] + }, + "freeswitch-perl": { + "versions": { + "1.6.19-r0": 1510585358 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreeswitch.so.1", + "so:libgcc_s.so.1", + "so:libperl.so", + "so:libstdc++.so.6" + ] + }, + "perl-mime-lite-doc": { + "versions": { + "3.030-r1": 1509495012 + }, + "origin": "perl-mime-lite" + }, + "lua-lustache-common": { + "versions": { + "1.3.1-r1": 1509472926 + }, + "origin": "lua-lustache" + }, + "gc": { + "versions": { + "7.6.0-r1": 1509467221 + }, + "origin": "gc", + "dependencies": [ + "so:libatomic_ops.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcord.so.1=1.0.3", + "so:libgc.so.1=1.0.3" + ] + }, + "upower-lang": { + "versions": { + "0.99.6-r0": 1510068213 + }, + "origin": "upower" + }, + "py-alabaster": { + "versions": { + "0.7.10-r0": 1509476479 + }, + "origin": "py-alabaster" + }, + "perl-dbi-doc": { + "versions": { + "1.637-r0": 1509477478 + }, + "origin": "perl-dbi" + }, + "lua5.3-ossl": { + "versions": { + "20171028-r1": 1510260842 + }, + "origin": "lua-ossl", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "lua-yaml": { + "versions": { + "1.1.2-r1": 1509488586 + }, + "origin": "lua-yaml" + }, + "libvirt-lxc": { + "versions": { + "3.9.0-r1": 1510088351, + "5.5.0-r0": 1562165540 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-daemon=5.5.0-r0", + "libvirt-common-drivers=5.5.0-r0", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0", + "so:libfuse.so.2", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libtirpc.so.3", + "so:libvirt.so.0", + "so:libxml2.so.2" + ] + }, + "mesa-dri-nouveau": { + "versions": { + "17.2.4-r1": 1510741946 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libX11-xcb.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_intel.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libxcb-dri2.so.0", + "so:libxcb-dri3.so.0", + "so:libxcb-present.so.0", + "so:libxcb-sync.so.1", + "so:libxcb-xfixes.so.0", + "so:libxcb.so.1", + "so:libxshmfence.so.1", + "so:libz.so.1" + ] + }, + "highlight": { + "versions": { + "3.41-r0": 1512029997 + }, + "origin": "highlight", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:liblua-5.3.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:highlight" + ] + }, + "tslib": { + "versions": { + "1.14-r0": 1510573238 + }, + "origin": "tslib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libts.so.0=0.8.1", + "cmd:ts_calibrate", + "cmd:ts_finddev", + "cmd:ts_harvest", + "cmd:ts_print", + "cmd:ts_print_mt", + "cmd:ts_print_raw", + "cmd:ts_test", + "cmd:ts_test_mt", + "cmd:ts_uinput", + "cmd:ts_verify" + ] + }, + "eudev-netifnames": { + "versions": { + "3.2.4-r1": 1509466081 + }, + "origin": "eudev", + "dependencies": [ + "udev-init-scripts" + ] + }, + "dovecot-ldap": { + "versions": { + "2.2.36.3-r0": 1554102390, + "2.2.36.4-r0": 1567075099 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot", + "so:libc.musl-x86_64.so.1", + "so:libdovecot.so.0", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ], + "provides": [ + "so:libdovecot-ldap.so.0=0.0.0" + ] + }, + "perl-mail-spf": { + "versions": { + "2.9.0-r2": 1509491252 + }, + "origin": "perl-mail-spf", + "dependencies": [ + "perl", + "perl-error", + "perl-net-dns", + "perl-uri", + "perl-netaddr-ip", + "perl-net-dns-resolver-programmable" + ] + }, + "perl-module-scandeps-doc": { + "versions": { + "1.24-r0": 1510352936 + }, + "origin": "perl-module-scandeps" + }, + "py-wtforms": { + "versions": { + "2.1-r0": 1509476540 + }, + "origin": "py-wtforms" + }, + "perl-email-address-doc": { + "versions": { + "1.908-r0": 1509491035, + "1.912-r0": 1559738452 + }, + "origin": "perl-email-address" + }, + "sdl2_ttf": { + "versions": { + "2.0.14-r1": 1510310961 + }, + "origin": "sdl2_ttf", + "dependencies": [ + "so:libSDL2-2.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6" + ], + "provides": [ + "so:libSDL2_ttf-2.0.so.0=0.14.0" + ] + }, + "libnotify": { + "versions": { + "0.7.7-r0": 1510068017 + }, + "origin": "libnotify", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libnotify.so.4=4.0.0", + "cmd:notify-send" + ] + }, + "dnstop": { + "versions": { + "20140915-r3": 1509485932 + }, + "origin": "dnstop", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:dnstop" + ] + }, + "uwsgi-symcall": { + "versions": { + "2.0.17-r0": 1522154660 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "goffice-lang": { + "versions": { + "0.10.36-r0": 1511455403 + }, + "origin": "goffice" + }, + "xfce4-battery-plugin-lang": { + "versions": { + "1.0.5-r2": 1510072891 + }, + "origin": "xfce4-battery-plugin" + }, + "sqsh": { + "versions": { + "2.5.16.1-r2": 1509493718 + }, + "origin": "sqsh", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libct.so.4", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:sqsh" + ] + }, + "xorg-server-dbg": { + "versions": { + "1.19.5-r1": 1540838459 + }, + "origin": "xorg-server", + "dependencies": [ + "font-misc-misc", + "font-cursor-misc", + "xkeyboard-config", + "xkbcomp", + "xinit" + ] + }, + "py3-nose": { + "versions": { + "1.3.7-r2": 1509552700 + }, + "origin": "py-nose", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:nosetests", + "cmd:nosetests-3.6" + ] + }, + "perl-boolean": { + "versions": { + "0.46-r0": 1509485612 + }, + "origin": "perl-boolean" + }, + "atf": { + "versions": { + "0.21-r1": 1509459731 + }, + "origin": "atf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libatf-c++.so.2=2.0.0", + "so:libatf-c.so.1=1.0.0", + "cmd:atf-sh" + ] + }, + "gtkmm-dev": { + "versions": { + "2.24.5-r0": 1510073814 + }, + "origin": "gtkmm", + "dependencies": [ + "atkmm-dev", + "gtk+2.0-dev", + "glibmm-dev", + "pangomm-dev", + "gtkmm=2.24.5-r0", + "pc:atkmm-1.6>=2.22.2", + "pc:giomm-2.4>=2.27.93", + "pc:gtk+-2.0>=2.24.0", + "pc:gtk+-unix-print-2.0", + "pc:pangomm-1.4>=2.27.1", + "pkgconfig" + ], + "provides": [ + "pc:gdkmm-2.4=2.24.5", + "pc:gtkmm-2.4=2.24.5" + ] + }, + "luajit-dev": { + "versions": { + "2.1.0_beta3-r0": 1509474649 + }, + "origin": "luajit", + "dependencies": [ + "luajit=2.1.0_beta3-r0", + "pkgconfig" + ] + }, + "libwbclient": { + "versions": { + "4.7.6-r3": 1555491787 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libreplace-samba4.so=0", + "so:libwbclient.so.0=0.14", + "so:libwinbind-client-samba4.so=0" + ] + }, + "nagios": { + "versions": { + "3.5.1-r4": 1509494205 + }, + "origin": "nagios", + "dependencies": [ + "perl", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3", + "so:libperl.so" + ], + "provides": [ + "cmd:nagios", + "cmd:nagiostats" + ] + }, + "python3-dbg": { + "versions": { + "3.6.8-r0": 1555928807, + "3.6.9-r0": 1571233993, + "3.6.9-r1": 1571314638 + }, + "origin": "python3" + }, + "lua-gversion": { + "versions": { + "0.2.0-r1": 1509475306 + }, + "origin": "lua-gversion" + }, + "alsaconf": { + "versions": { + "1.1.4-r0": 1509468851 + }, + "origin": "alsa-utils", + "dependencies": [ + "alsa-utils", + "bash" + ], + "provides": [ + "cmd:alsaconf" + ] + }, + "busybox-extras": { + "versions": { + "1.27.2-r11": 1528276162 + }, + "origin": "busybox", + "dependencies": [ + "busybox", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:busybox-extras" + ] + }, + "font-vollkorn": { + "versions": { + "4.015-r0": 1509967842 + }, + "origin": "font-vollkorn" + }, + "libgmpxx": { + "versions": { + "6.1.2-r1": 1509456917 + }, + "origin": "gmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgmp.so.10", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgmpxx.so.4=4.5.2" + ] + }, + "quagga-dev": { + "versions": { + "1.2.4-r0": 1519133992 + }, + "origin": "quagga", + "dependencies": [ + "quagga=1.2.4-r0" + ] + }, + "qemu-system-microblazeel": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-microblazeel" + ] + }, + "mc-lang": { + "versions": { + "4.8.20-r0": 1511871582 + }, + "origin": "mc" + }, + "abiword-plugin-kword": { + "versions": { + "3.0.2-r1": 1510073362 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "abiword-plugin-presentation": { + "versions": { + "3.0.2-r1": 1510073367 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "dahdi-linux-hardened": { + "versions": { + "4.9.65-r1": 1511798670 + }, + "origin": "dahdi-linux-hardened", + "dependencies": [ + "dahdi-linux", + "linux-hardened=4.9.65-r1" + ], + "provides": [ + "dahdi-linux-grsec=4.9.65-r1" + ] + }, + "py2-flask": { + "versions": { + "0.12.2-r1": 1509551869 + }, + "origin": "py-flask", + "dependencies": [ + "py2-click", + "py2-itsdangerous", + "py2-jinja2", + "py2-werkzeug", + "python2" + ], + "provides": [ + "cmd:flask" + ] + }, + "lua5.2-yaml": { + "versions": { + "1.1.2-r1": 1509488585 + }, + "origin": "lua-yaml", + "dependencies": [ + "lua5.2", + "lua5.2-lub", + "so:libc.musl-x86_64.so.1" + ] + }, + "nagios-plugins-mysql": { + "versions": { + "2.2.1-r3": 1510288496 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "xfsprogs-dev": { + "versions": { + "4.14.0-r1": 1528279972 + }, + "origin": "xfsprogs", + "dependencies": [ + "xfsprogs-libs=4.14.0-r1" + ] + }, + "devicemaster-linux-hardened": { + "versions": { + "4.9.65-r1": 1511799326 + }, + "origin": "devicemaster-linux-hardened", + "dependencies": [ + "linux-hardened=4.9.65-r1" + ], + "provides": [ + "devicemaster-linux-grsec=4.9.65-r1" + ] + }, + "sgdisk": { + "versions": { + "1.0.3-r0": 1509495223 + }, + "origin": "gptfdisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpopt.so.0", + "so:libstdc++.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:sgdisk" + ] + }, + "git-fast-import": { + "versions": { + "2.15.3-r0": 1540401293, + "2.15.4-r0": 1576015190 + }, + "origin": "git", + "dependencies": [ + "git=2.15.4-r0", + "so:libc.musl-x86_64.so.1", + "so:libpcre2-8.so.0", + "so:libz.so.1" + ] + }, + "dri3proto": { + "versions": { + "1.0-r2": 1509466244 + }, + "origin": "dri3proto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:dri3proto=1.0" + ] + }, + "perl-server-starter": { + "versions": { + "0.15-r1": 1510564665 + }, + "origin": "perl-server-starter", + "dependencies": [ + "perl-proc-wait3", + "perl-list-moreutils", + "perl-scope-guard" + ], + "provides": [ + "cmd:start_server" + ] + }, + "fltk-fluid": { + "versions": { + "1.3.4-r0": 1510072238 + }, + "origin": "fltk", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libfltk.so.1.3", + "so:libfltk_forms.so.1.3", + "so:libfltk_images.so.1.3", + "so:libpng16.so.16", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:fluid" + ] + }, + "libgssglue": { + "versions": { + "0.4-r0": 1509479797 + }, + "origin": "libgssglue", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgssglue.so.1=1.0.0" + ] + }, + "appstream-glib-dev": { + "versions": { + "0.6.3-r0": 1510067860 + }, + "origin": "appstream-glib", + "dependencies": [ + "glib-dev", + "gdk-pixbuf-dev", + "appstream-glib-builder=0.6.3-r0", + "appstream-glib=0.6.3-r0", + "pc:gdk-pixbuf-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:libarchive", + "pc:libgcab-1.0", + "pc:uuid", + "pkgconfig" + ], + "provides": [ + "pc:appstream-builder=0.6.3", + "pc:appstream-glib=0.6.3" + ] + }, + "qemu-system-x86_64": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-x86_64" + ] + }, + "lua-optarg": { + "versions": { + "0.2-r1": 1509462495 + }, + "origin": "lua-optarg" + }, + "collectd-log_logstash": { + "versions": { + "5.7.2-r0": 1510069652 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libyajl.so.2" + ] + }, + "postfix-stone": { + "versions": { + "3.2.4-r1": 1510285340 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpostfix-global.so", + "so:libpostfix-util.so" + ], + "provides": [ + "cmd:fsstone", + "cmd:qmqp-sink", + "cmd:qmqp-source", + "cmd:smtp-sink", + "cmd:smtp-source" + ] + }, + "libinput-libs": { + "versions": { + "1.9.2-r0": 1511276852 + }, + "origin": "libinput", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libevdev.so.2", + "so:libmtdev.so.1", + "so:libudev.so.1" + ], + "provides": [ + "so:libinput.so.10=10.13.0" + ] + }, + "s6-linux-utils-doc": { + "versions": { + "2.4.0.1-r0": 1509468269 + }, + "origin": "s6-linux-utils" + }, + "xfce4-power-manager": { + "versions": { + "1.6.0-r2": 1510068248 + }, + "origin": "xfce4-power-manager", + "dependencies": [ + "polkit", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libnotify.so.4", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libupower-glib.so.3", + "so:libxfce4panel-2.0.so.4", + "so:libxfce4ui-2.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:xfce4-pm-helper", + "cmd:xfce4-power-manager", + "cmd:xfce4-power-manager-settings", + "cmd:xfpm-power-backlight-helper" + ] + }, + "lua5.1-struct": { + "versions": { + "0.2-r2": 1509494932 + }, + "origin": "lua-struct", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "qemu-ppc64": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-ppc64" + ] + }, + "collectd-network": { + "versions": { + "5.7.2-r0": 1510069646 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20" + ] + }, + "perl-dist-checkconflicts-doc": { + "versions": { + "0.11-r0": 1509474024 + }, + "origin": "perl-dist-checkconflicts" + }, + "ruby-libs": { + "versions": { + "2.4.6-r0": 1557166823 + }, + "origin": "ruby", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libffi.so.6", + "so:libgdbm.so.4", + "so:libgdbm_compat.so.4", + "so:libgmp.so.10", + "so:libreadline.so.7", + "so:libssl.so.44", + "so:libyaml-0.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libruby.so.2.4=2.4.6" + ] + }, + "perl-apache-logformat-compiler": { + "versions": { + "0.32-r0": 1509481806 + }, + "origin": "perl-apache-logformat-compiler", + "dependencies": [ + "perl-posix-strftime-compiler" + ] + }, + "clamsmtp-doc": { + "versions": { + "1.10-r15": 1509468380 + }, + "origin": "clamsmtp" + }, + "perl-string-shellquote": { + "versions": { + "1.04-r0": 1509494707 + }, + "origin": "perl-string-shellquote", + "provides": [ + "cmd:shell-quote" + ] + }, + "lua5.1-lustache": { + "versions": { + "1.3.1-r1": 1509472927 + }, + "origin": "lua-lustache", + "dependencies": [ + "lua-lustache-common", + "lua5.1", + "lua-lustache-common=1.3.1-r1" + ] + }, + "autoconf-doc": { + "versions": { + "2.69-r0": 1509456973 + }, + "origin": "autoconf" + }, + "perl-clone": { + "versions": { + "0.39-r1": 1509477482 + }, + "origin": "perl-clone", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.3-maxminddb": { + "versions": { + "0.1-r1": 1509496574 + }, + "origin": "lua-maxminddb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmaxminddb.so.0" + ] + }, + "perl-parse-recdescent": { + "versions": { + "1.967015-r0": 1509489504 + }, + "origin": "perl-parse-recdescent", + "dependencies": [ + "perl" + ] + }, + "fftw-doc": { + "versions": { + "3.3.6p2-r0": 1509468830 + }, + "origin": "fftw" + }, + "libxcomposite-doc": { + "versions": { + "0.4.4-r1": 1509465984 + }, + "origin": "libxcomposite" + }, + "bluez-bccmd": { + "versions": { + "5.47-r3": 1510069786 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bccmd" + ] + }, + "py3-libvirt": { + "versions": { + "3.9.0-r0": 1510088399 + }, + "origin": "py-libvirt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0", + "so:libvirt-lxc.so.0", + "so:libvirt-qemu.so.0", + "so:libvirt.so.0" + ] + }, + "openntpd-doc": { + "versions": { + "6.2_p3-r0": 1510618938 + }, + "origin": "openntpd" + }, + "snappy-dbg": { + "versions": { + "1.1.4-r2": 1509483342 + }, + "origin": "snappy" + }, + "poppler-dev": { + "versions": { + "0.56.0-r0": 1509465065, + "0.56.0-r1": 1569334548 + }, + "origin": "poppler", + "dependencies": [ + "cairo-dev", + "glib-dev", + "pc:cairo>=1.10.0", + "pc:gio-2.0>=2.41", + "pc:glib-2.0>=2.41", + "pc:gobject-2.0>=2.41", + "pkgconfig", + "poppler-glib=0.56.0-r1", + "poppler=0.56.0-r1" + ], + "provides": [ + "pc:poppler-cairo=0.56.0", + "pc:poppler-cpp=0.56.0", + "pc:poppler-glib=0.56.0", + "pc:poppler-splash=0.56.0", + "pc:poppler=0.56.0" + ] + }, + "lua5.2-openrc": { + "versions": { + "0.2-r2": 1509492199 + }, + "origin": "lua-openrc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librc.so.1" + ] + }, + "acf-openvpn": { + "versions": { + "0.11.1-r2": 1510075102 + }, + "origin": "acf-openvpn", + "dependencies": [ + "acf-core", + "openvpn" + ] + }, + "qemu-system-nios2": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-nios2" + ] + }, + "lua5.1-lzmq": { + "versions": { + "0.4.4-r0": 1509475919 + }, + "origin": "lua-lzmq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5", + "so:libzmq.so.5" + ] + }, + "kamailio-ldap": { + "versions": { + "5.0.7-r0": 1532960875 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libldap-2.4.so.2", + "so:libsrdb2.so.1" + ] + }, + "libpcrecpp": { + "versions": { + "8.41-r1": 1509464277 + }, + "origin": "pcre", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpcre.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libpcrecpp.so.0=0.0.1" + ] + }, + "perl-inline-c": { + "versions": { + "0.76-r0": 1509480894 + }, + "origin": "perl-inline-c" + }, + "paxctl-doc": { + "versions": { + "0.9-r0": 1509495225 + }, + "origin": "paxctl" + }, + "lighttpd-mod_webdav": { + "versions": { + "1.4.48-r0": 1511925915 + }, + "origin": "lighttpd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0", + "so:libuuid.so.1", + "so:libxml2.so.2" + ] + }, + "libssl1.0": { + "versions": { + "1.0.2r-r0": 1552814669, + "1.0.2t-r0": 1568300415 + }, + "origin": "openssl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.0.0" + ], + "provides": [ + "so:libssl.so.1.0.0=1.0.0" + ] + }, + "py-larch": { + "versions": { + "1.20131130-r1": 1509552711 + }, + "origin": "py-larch", + "dependencies": [ + "python2", + "py-tracing", + "py-cliapp", + "py-ttystatus" + ], + "provides": [ + "cmd:fsck-larch" + ] + }, + "gnokii-smsd-sqlite": { + "versions": { + "0.6.31-r6": 1510069854 + }, + "origin": "gnokii", + "dependencies": [ + "gnokii-smsd", + "so:libc.musl-x86_64.so.1" + ] + }, + "cjdns-doc": { + "versions": { + "20-r1": 1510601040 + }, + "origin": "cjdns" + }, + "avahi-ui-tools": { + "versions": { + "0.6.31-r7": 1510076323 + }, + "origin": "avahi-ui", + "dependencies": [ + "py-gtk", + "py-dbus", + "py-gdbm", + "py-avahi", + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-ui-gtk3.so.0", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:avahi-bookmarks", + "cmd:avahi-discover", + "cmd:bshell", + "cmd:bssh", + "cmd:bvnc" + ] + }, + "libsoup-doc": { + "versions": { + "2.60.2-r0": 1509480669 + }, + "origin": "libsoup" + }, + "gtk+": { + "versions": { + "2.24.31-r0": 1512030427 + }, + "origin": "gtk+", + "dependencies": [ + "gtk+2.0>=2.24.31" + ] + }, + "libnfsidmap": { + "versions": { + "0.25-r1": 1509492068 + }, + "origin": "libnfsidmap", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnfsidmap.so.0=0.3.0" + ] + }, + "geoip-dev": { + "versions": { + "1.6.11-r0": 1509474612 + }, + "origin": "geoip", + "dependencies": [ + "geoip=1.6.11-r0", + "pkgconfig" + ], + "provides": [ + "pc:geoip=1.6.11" + ] + }, + "uwsgi-tuntap": { + "versions": { + "2.0.17-r0": 1522154660 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "sic-doc": { + "versions": { + "1.2-r0": 1509474242 + }, + "origin": "sic" + }, + "scanelf": { + "versions": { + "1.2.2-r1": 1509459679 + }, + "origin": "pax-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:scanelf" + ] + }, + "libxfce4util-dev": { + "versions": { + "4.12.1-r4": 1509468547 + }, + "origin": "libxfce4util", + "dependencies": [ + "libxfce4util=4.12.1-r4", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libxfce4util-1.0=4.12.1" + ] + }, + "perl-digest-hmac": { + "versions": { + "1.03-r0": 1509468924 + }, + "origin": "perl-digest-hmac", + "dependencies": [ + "perl", + "perl-digest-sha1" + ] + }, + "freeswitch-timezones": { + "versions": { + "1.6.19-r0": 1510585357 + }, + "origin": "freeswitch" + }, + "unfs3": { + "versions": { + "0.9.22-r4": 1509496017 + }, + "origin": "unfs3", + "dependencies": [ + "rpcbind", + "so:libc.musl-x86_64.so.1", + "so:libfl.so.2", + "so:libtirpc.so.3" + ], + "provides": [ + "cmd:unfsd" + ] + }, + "py3-dateutil": { + "versions": { + "2.6.1-r0": 1509475854 + }, + "origin": "py-dateutil", + "dependencies": [ + "py3-six", + "python3" + ] + }, + "usbredir-dev": { + "versions": { + "0.7.1-r0": 1509707469 + }, + "origin": "usbredir", + "dependencies": [ + "libusb-dev", + "pc:libusb-1.0", + "pkgconfig", + "usbredir=0.7.1-r0" + ], + "provides": [ + "pc:libusbredirhost=0.7.1", + "pc:libusbredirparser-0.5=0.7.1" + ] + }, + "apr": { + "versions": { + "1.6.3-r0": 1509476182 + }, + "origin": "apr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libapr-1.so.0=0.6.3" + ] + }, + "perl-struct-dumb-doc": { + "versions": { + "0.07-r0": 1509480426 + }, + "origin": "perl-struct-dumb" + }, + "openresolv-doc": { + "versions": { + "3.9.0-r0": 1509476550 + }, + "origin": "openresolv" + }, + "mousepad": { + "versions": { + "0.4.0-r2": 1510073846 + }, + "origin": "mousepad", + "dependencies": [ + "desktop-file-utils", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libgtksourceview-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0" + ], + "provides": [ + "cmd:mousepad" + ] + }, + "aspell-compat": { + "versions": { + "0.60.6.1-r12": 1509472867, + "0.60.6.1-r13": 1572541141 + }, + "origin": "aspell", + "dependencies": [ + "aspell" + ], + "provides": [ + "cmd:ispell", + "cmd:spell" + ] + }, + "perl-test2-plugin-nowarnings-doc": { + "versions": { + "0.06-r0": 1510588886 + }, + "origin": "perl-test2-plugin-nowarnings" + }, + "spamassassin": { + "versions": { + "3.4.1-r8": 1509517414 + }, + "origin": "spamassassin", + "dependencies": [ + "perl-mail-spamassassin", + "curl" + ], + "provides": [ + "cmd:sa-awl", + "cmd:sa-check_spamd", + "cmd:sa-learn", + "cmd:sa-update", + "cmd:spamassassin", + "cmd:spamd" + ] + }, + "py3-snowballstemmer": { + "versions": { + "1.2.1-r1": 1509489495 + }, + "origin": "py-snowballstemmer", + "dependencies": [ + "python3" + ] + }, + "newsbeuter": { + "versions": { + "2.9-r6": 1510288209 + }, + "origin": "newsbeuter", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libjson-c.so.2", + "so:libncursesw.so.6", + "so:libsqlite3.so.0", + "so:libstdc++.so.6", + "so:libstfl.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:newsbeuter", + "cmd:podbeuter" + ] + }, + "mwm": { + "versions": { + "2.3.4-r2": 1509495433 + }, + "origin": "motif", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXm.so.4", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mwm", + "cmd:xmbind" + ] + }, + "lua-crypto-dev": { + "versions": { + "0.3.2-r5": 1510261400 + }, + "origin": "lua-crypto", + "dependencies": [ + "pc:openssl", + "pkgconfig" + ], + "provides": [ + "pc:luacrypto=0.3.1" + ] + }, + "libxcursor": { + "versions": { + "1.1.15-r0": 1511975691 + }, + "origin": "libxcursor", + "dependencies": [ + "so:libX11.so.6", + "so:libXfixes.so.3", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXcursor.so.1=1.0.2" + ] + }, + "libmount": { + "versions": { + "2.31.1-r0": 1541506294 + }, + "origin": "util-linux", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmount.so.1=1.1.0" + ] + }, + "tcl-tls": { + "versions": { + "1.7.10-r2": 1510260768 + }, + "origin": "tcl-tls", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "abiword-plugin-pdf": { + "versions": { + "3.0.2-r1": 1510073367 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "bigreqsproto": { + "versions": { + "1.1.2-r3": 1509470423 + }, + "origin": "bigreqsproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:bigreqsproto=1.1.2" + ] + }, + "printproto": { + "versions": { + "1.0.5-r2": 1509494090 + }, + "origin": "printproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:printproto=1.0.5" + ] + }, + "fontconfig": { + "versions": { + "2.12.6-r0": 1509462083 + }, + "origin": "fontconfig", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libfreetype.so.6" + ], + "provides": [ + "so:libfontconfig.so.1=1.10.1", + "cmd:fc-cache", + "cmd:fc-cat", + "cmd:fc-list", + "cmd:fc-match", + "cmd:fc-pattern", + "cmd:fc-query", + "cmd:fc-scan", + "cmd:fc-validate" + ] + }, + "bitchx": { + "versions": { + "1.2.1-r7": 1510260134 + }, + "origin": "bitchx", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libncursesw.so.6", + "so:libssl.so.44" + ], + "provides": [ + "cmd:BitchX", + "cmd:BitchX-1.2.1", + "cmd:scr-bx" + ] + }, + "nginx-mod-http-lua": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "nginx-mod-devel-kit", + "so:libc.musl-x86_64.so.1", + "so:libluajit-5.1.so.2" + ], + "provides": [ + "nginx-lua" + ] + }, + "lua5.3-lxc": { + "versions": { + "2.1.1-r3": 1533578833 + }, + "origin": "lxc", + "dependencies": [ + "gzip", + "so:libc.musl-x86_64.so.1", + "so:liblxc.so.1" + ] + }, + "lua5.1-ossl": { + "versions": { + "20171028-r1": 1510260842 + }, + "origin": "lua-ossl", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ] + }, + "libsoup-lang": { + "versions": { + "2.60.2-r0": 1509480670 + }, + "origin": "libsoup", + "dependencies": [ + "glib-networking" + ] + }, + "libmodplug-doc": { + "versions": { + "0.8.9.0-r0": 1509489449 + }, + "origin": "libmodplug" + }, + "perl-html-tree": { + "versions": { + "5.07-r0": 1509481555 + }, + "origin": "perl-html-tree", + "dependencies": [ + "perl-html-tagset", + "perl-html-parser" + ], + "provides": [ + "cmd:htmltree" + ] + }, + "xfconf-dev": { + "versions": { + "4.12.1-r2": 1510067932 + }, + "origin": "xfconf", + "dependencies": [ + "pc:dbus-1", + "pc:dbus-glib-1", + "pc:gobject-2.0", + "pkgconfig", + "xfconf=4.12.1-r2" + ], + "provides": [ + "pc:libxfconf-0=4.12.1" + ] + }, + "dzen-gadgets": { + "versions": { + "0.9.5-r3": 1509787635 + }, + "origin": "dzen", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dbar", + "cmd:gcpubar", + "cmd:gdbar", + "cmd:textwidth" + ] + }, + "perl-carp-clan": { + "versions": { + "6.06-r1": 1509473080 + }, + "origin": "perl-carp-clan", + "dependencies": [ + "perl", + "perl-test-exception" + ] + }, + "mdocml-dev": { + "versions": { + "1.14.3-r0": 1509459636 + }, + "origin": "mdocml" + }, + "mtr": { + "versions": { + "0.92-r0": 1510072769 + }, + "origin": "mtr", + "dependencies": [ + "ncurses", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:mtr", + "cmd:mtr-packet" + ] + }, + "perl-fcgi-procmanager": { + "versions": { + "0.28-r0": 1511894069 + }, + "origin": "perl-fcgi-procmanager", + "dependencies": [ + "perl" + ] + }, + "udisks-lang": { + "versions": { + "1.0.5-r3": 1510073403 + }, + "origin": "udisks" + }, + "sems-speex": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libspeex.so.1" + ] + }, + "libical": { + "versions": { + "2.0.0-r2": 1509468579 + }, + "origin": "libical", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libical.so.2=2.0.0", + "so:libical_cxx.so.2=2.0.0", + "so:libicalss.so.2=2.0.0", + "so:libicalss_cxx.so.2=2.0.0", + "so:libicalvcal.so.2=2.0.0" + ] + }, + "lvm2-dev": { + "versions": { + "2.02.175-r0": 1509462401 + }, + "origin": "lvm2", + "dependencies": [ + "linux-headers", + "device-mapper-event-libs=2.02.175-r0", + "device-mapper-libs=2.02.175-r0", + "lvm2-libs=2.02.175-r0", + "pc:blkid", + "pkgconfig" + ], + "provides": [ + "pc:devmapper-event=1.02.144", + "pc:devmapper=1.02.144", + "pc:lvm2app=2.2" + ] + }, + "qemu-system-sparc": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-sparc" + ] + }, + "bc": { + "versions": { + "1.07.1-r0": 1509465953 + }, + "origin": "bc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:bc", + "cmd:dc" + ] + }, + "perl-term-readkey": { + "versions": { + "2.37-r1": 1509494716 + }, + "origin": "perl-term-readkey", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "acf-clamav": { + "versions": { + "0.8.0-r2": 1510068298 + }, + "origin": "acf-clamav", + "dependencies": [ + "acf-core", + "clamav" + ] + }, + "ssmtp": { + "versions": { + "2.64-r12": 1510260822 + }, + "origin": "ssmtp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "cmd:sendmail", + "cmd:ssmtp" + ] + }, + "lua5.1-maxminddb": { + "versions": { + "0.1-r1": 1509496568 + }, + "origin": "lua-maxminddb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmaxminddb.so.0" + ] + }, + "uwsgi-signal": { + "versions": { + "2.0.17-r0": 1522154659 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-test-longstring": { + "versions": { + "0.15-r0": 1509470426 + }, + "origin": "perl-test-longstring", + "dependencies": [ + "perl" + ] + }, + "openldap-back-null": { + "versions": { + "2.4.45-r3": 1510258133, + "2.4.46-r0": 1565073939, + "2.4.48-r0": 1566900516 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "lua5.2-evdev": { + "versions": { + "2.2.1-r1": 1509488870 + }, + "origin": "lua-evdev", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "userspace-rcu": { + "versions": { + "0.10.0-r0": 1509479773 + }, + "origin": "userspace-rcu", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liburcu-bp.so.6=6.0.0", + "so:liburcu-cds.so.6=6.0.0", + "so:liburcu-common.so.6=6.0.0", + "so:liburcu-mb.so.6=6.0.0", + "so:liburcu-qsbr.so.6=6.0.0", + "so:liburcu-signal.so.6=6.0.0", + "so:liburcu.so.6=6.0.0" + ] + }, + "lua5.2-rex-pcre": { + "versions": { + "2.9.0-r0": 1510619366 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ] + }, + "live-media-utils": { + "versions": { + "2017.10.28-r0": 1510046892 + }, + "origin": "live-media", + "dependencies": [ + "so:libBasicUsageEnvironment.so.1", + "so:libUsageEnvironment.so.3", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgroupsock.so.8", + "so:libliveMedia.so.61", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:MPEG2TransportStreamIndexer", + "cmd:openRTSP", + "cmd:playSIP", + "cmd:registerRTSPStream", + "cmd:sapWatch", + "cmd:testAMRAudioStreamer", + "cmd:testDVVideoStreamer", + "cmd:testH264VideoStreamer", + "cmd:testH264VideoToTransportStream", + "cmd:testH265VideoStreamer", + "cmd:testH265VideoToTransportStream", + "cmd:testMKVStreamer", + "cmd:testMP3Receiver", + "cmd:testMP3Streamer", + "cmd:testMPEG1or2AudioVideoStreamer", + "cmd:testMPEG1or2ProgramToTransportStream", + "cmd:testMPEG1or2Splitter", + "cmd:testMPEG1or2VideoReceiver", + "cmd:testMPEG1or2VideoStreamer", + "cmd:testMPEG2TransportReceiver", + "cmd:testMPEG2TransportStreamTrickPlay", + "cmd:testMPEG2TransportStreamer", + "cmd:testMPEG4VideoStreamer", + "cmd:testOggStreamer", + "cmd:testOnDemandRTSPServer", + "cmd:testRTSPClient", + "cmd:testRelay", + "cmd:testReplicator", + "cmd:testWAVAudioStreamer", + "cmd:vobStreamer" + ] + }, + "openldap-overlay-constraint": { + "versions": { + "2.4.45-r3": 1510258134, + "2.4.46-r0": 1565073940, + "2.4.48-r0": 1566900517 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "perl-heap-doc": { + "versions": { + "0.80-r1": 1511989874 + }, + "origin": "perl-heap" + }, + "xcb-util-keysyms-dev": { + "versions": { + "0.4.0-r1": 1509473914 + }, + "origin": "xcb-util-keysyms", + "dependencies": [ + "xcb-util-dev", + "pkgconfig", + "xcb-util-keysyms=0.4.0-r1" + ], + "provides": [ + "pc:xcb-keysyms=0.4.0" + ] + }, + "lua5.1-sql-mysql": { + "versions": { + "2.3.5-r1": 1509488828 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "pangox-compat": { + "versions": { + "0.0.2-r0": 1509482459 + }, + "origin": "pangox-compat", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpango-1.0.so.0" + ], + "provides": [ + "so:libpangox-1.0.so.0=0.0.0" + ] + }, + "uwsgi-rawrouter": { + "versions": { + "2.0.17-r0": 1522154656 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "uwsgi-corerouter", + "so:libc.musl-x86_64.so.1" + ] + }, + "protobuf": { + "versions": { + "3.4.1-r1": 1510846094 + }, + "origin": "protobuf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libprotobuf-lite.so.14=14.0.0", + "so:libprotobuf.so.14=14.0.0", + "so:libprotoc.so.14=14.0.0", + "cmd:protoc" + ] + }, + "btrfs-progs-libs": { + "versions": { + "4.13.2-r0": 1509481818 + }, + "origin": "btrfs-progs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libbtrfs.so.0=0.1" + ] + }, + "thunar-vcs-plugin-lang": { + "versions": { + "0.1.4-r6": 1510314592 + }, + "origin": "thunar-vcs-plugin" + }, + "py3-rsa": { + "versions": { + "3.4.2-r2": 1509551793 + }, + "origin": "py-rsa", + "dependencies": [ + "py3-asn1", + "python3" + ], + "provides": [ + "cmd:pyrsa-decrypt", + "cmd:pyrsa-decrypt-bigfile", + "cmd:pyrsa-encrypt", + "cmd:pyrsa-encrypt-bigfile", + "cmd:pyrsa-keygen", + "cmd:pyrsa-priv2pub", + "cmd:pyrsa-sign", + "cmd:pyrsa-verify" + ] + }, + "grub-dev": { + "versions": { + "2.02-r3": 1509495739 + }, + "origin": "grub" + }, + "consolekit2-lang": { + "versions": { + "1.2.0-r2": 1510067530 + }, + "origin": "consolekit2", + "dependencies": [ + "polkit", + "eudev" + ] + }, + "vanessa_socket-doc": { + "versions": { + "0.0.13-r0": 1511165897 + }, + "origin": "vanessa_socket" + }, + "py2-nose": { + "versions": { + "1.3.7-r2": 1509552700 + }, + "origin": "py-nose", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:nosetests-2.7" + ] + }, + "chezdav": { + "versions": { + "2.2-r0": 1509492232 + }, + "origin": "phodav", + "dependencies": [ + "so:libavahi-gobject.so.0", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libphodav-2.0.so.0", + "so:libsoup-2.4.so.1" + ], + "provides": [ + "cmd:chezdav" + ] + }, + "tmux-doc": { + "versions": { + "2.6-r0": 1509469998 + }, + "origin": "tmux" + }, + "gptfdisk": { + "versions": { + "1.0.3-r0": 1509495224 + }, + "origin": "gptfdisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libstdc++.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:cgdisk", + "cmd:fixparts", + "cmd:gdisk" + ] + }, + "enchant-dev": { + "versions": { + "1.6.0-r12": 1509477681 + }, + "origin": "enchant", + "dependencies": [ + "glib-dev", + "enchant=1.6.0-r12", + "pc:glib-2.0", + "pc:gmodule-no-export-2.0", + "pkgconfig" + ], + "provides": [ + "pc:enchant=1.6.0" + ] + }, + "wxgtk2.8-lang": { + "versions": { + "2.8.12.1-r4": 1510928419 + }, + "origin": "wxgtk2.8" + }, + "squid-lang-lv": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-test-requiresinternet-doc": { + "versions": { + "0.05-r0": 1509464419 + }, + "origin": "perl-test-requiresinternet" + }, + "gdl-lang": { + "versions": { + "3.22.0-r0": 1510074987 + }, + "origin": "gdl" + }, + "cairomm": { + "versions": { + "1.12.2-r0": 1509472991 + }, + "origin": "cairomm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgcc_s.so.1", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libcairomm-1.0.so.1=1.4.0" + ] + }, + "py2-rsa": { + "versions": { + "3.4.2-r2": 1509551794 + }, + "origin": "py-rsa", + "dependencies": [ + "py2-asn1", + "python2" + ], + "provides": [ + "cmd:pyrsa-decrypt", + "cmd:pyrsa-decrypt-bigfile", + "cmd:pyrsa-encrypt", + "cmd:pyrsa-encrypt-bigfile", + "cmd:pyrsa-keygen", + "cmd:pyrsa-priv2pub", + "cmd:pyrsa-sign", + "cmd:pyrsa-verify" + ] + }, + "font-adobe-utopia-100dpi": { + "versions": { + "1.0.4-r0": 1509477309 + }, + "origin": "font-adobe-utopia-100dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "py-dbus-doc": { + "versions": { + "1.2.0-r2": 1509465382 + }, + "origin": "py-dbus" + }, + "nghttp2-libs": { + "versions": { + "1.28.0-r0": 1511922922, + "1.39.2-r0": 1568186892 + }, + "origin": "nghttp2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnghttp2.so.14=14.18.0" + ] + }, + "uwsgi-router_rewrite": { + "versions": { + "2.0.17-r0": 1522154658 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "mdadm-doc": { + "versions": { + "4.0-r0": 1509476487 + }, + "origin": "mdadm" + }, + "knock-doc": { + "versions": { + "0.7-r1": 1509480796 + }, + "origin": "knock" + }, + "lcms2": { + "versions": { + "2.8-r1": 1509464953 + }, + "origin": "lcms2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblcms2.so.2=2.0.8" + ] + }, + "xfce4-session-dev": { + "versions": { + "4.12.1-r2": 1510074571 + }, + "origin": "xfce4-session", + "dependencies": [ + "libxfce4ui-dev", + "xfconf-dev", + "pc:libxfce4ui-1", + "pc:libxfconf-0", + "pkgconfig", + "xfce4-session=4.12.1-r2" + ], + "provides": [ + "pc:xfce4-session-2.0=4.12.1" + ] + }, + "kbd": { + "versions": { + "2.0.4-r2": 1510922533 + }, + "origin": "kbd", + "dependencies": [ + "kbd-misc", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:chvt", + "cmd:deallocvt", + "cmd:dumpkeys", + "cmd:fgconsole", + "cmd:getkeycodes", + "cmd:kbd_mode", + "cmd:kbdinfo", + "cmd:kbdrate", + "cmd:loadkeys", + "cmd:loadunimap", + "cmd:mapscrn", + "cmd:open", + "cmd:openvt", + "cmd:psfaddtable", + "cmd:psfgettable", + "cmd:psfstriptable", + "cmd:psfxtable", + "cmd:resizecons", + "cmd:setfont", + "cmd:setkeycodes", + "cmd:setleds", + "cmd:setmetamode", + "cmd:setvtrgb", + "cmd:showconsolefont", + "cmd:showkey", + "cmd:unicode_start", + "cmd:unicode_stop" + ] + }, + "libgudev-dev": { + "versions": { + "230-r2": 1509470858 + }, + "origin": "libgudev", + "dependencies": [ + "libgudev=230-r2", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gudev-1.0=230" + ] + }, + "libexif": { + "versions": { + "0.6.21-r2": 1539006612 + }, + "origin": "libexif", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libexif.so.12=12.3.3" + ] + }, + "openipmi-doc": { + "versions": { + "2.0.22-r1": 1510260043 + }, + "origin": "openipmi" + }, + "nginx-mod-http-lua-upstream": { + "versions": { + "1.12.2-r4": 1542814448 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "nginx-mod-http-lua", + "so:libc.musl-x86_64.so.1" + ] + }, + "py3-itsdangerous": { + "versions": { + "0.24-r3": 1509551859 + }, + "origin": "py-itsdangerous", + "dependencies": [ + "python3" + ] + }, + "smokeping": { + "versions": { + "2.6.11-r3": 1510564559 + }, + "origin": "smokeping", + "dependencies": [ + "perl", + "fping", + "rrdtool", + "perl-rrd", + "perl-uri", + "perl-digest-hmac", + "perl-cgi-session", + "perl-io-tty", + "perl-libwww", + "perl-ldap", + "perl-snmp-session", + "perl-net-dns", + "perl-net-openssh", + "perl-net-snmp", + "perl-net-telnet", + "perl-fcgi", + "perl-config-grammar", + "perl-cgi", + "perl-cgi-fast", + "/bin/sh" + ], + "provides": [ + "cmd:smokeinfo", + "cmd:smokeping", + "cmd:smokeping_cgi", + "cmd:tSmoke" + ] + }, + "myrepos-doc": { + "versions": { + "1.20180726-r0": 1534931445 + }, + "origin": "myrepos" + }, + "freeswitch-freetdm": { + "versions": { + "1.6.19-r0": 1510585357 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexecinfo.so.1", + "so:libfreeswitch.so.1", + "so:libpri.so.1.4" + ], + "provides": [ + "so:libfreetdm.so.1=1.0.0" + ] + }, + "libdbi-doc": { + "versions": { + "0.9.0-r0": 1509474452 + }, + "origin": "libdbi" + }, + "qt-lang": { + "versions": { + "4.8.7-r8": 1510287210 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates" + ] + }, + "perl-exporter-doc": { + "versions": { + "5.72-r1": 1509483328 + }, + "origin": "perl-exporter" + }, + "py3-tornado": { + "versions": { + "4.5.2-r1": 1509551890 + }, + "origin": "py-tornado", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "libnl3-doc": { + "versions": { + "3.2.28-r1": 1509475886 + }, + "origin": "libnl3" + }, + "net-tools": { + "versions": { + "1.60_git20140218-r1": 1509492734 + }, + "origin": "net-tools", + "dependencies": [ + "mii-tool", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:arp", + "cmd:dnsdomainname", + "cmd:domainname", + "cmd:hostname", + "cmd:ifconfig", + "cmd:ipmaddr", + "cmd:iptunnel", + "cmd:nameif", + "cmd:netstat", + "cmd:nisdomainname", + "cmd:plipconfig", + "cmd:rarp", + "cmd:route", + "cmd:slattach", + "cmd:ypdomainname" + ] + }, + "perl-package-deprecationmanager": { + "versions": { + "0.17-r0": 1509493030 + }, + "origin": "perl-package-deprecationmanager", + "dependencies": [ + "perl-list-moreutils", + "perl-sub-install", + "perl-params-util", + "perl-test-fatal", + "perl-test-requires", + "perl-package-stash", + "perl-test-warnings", + "perl-sub-name" + ] + }, + "openssh-client": { + "versions": { + "7.5_p1-r10": 1551712287 + }, + "origin": "openssh", + "dependencies": [ + "openssh-keygen", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libz.so.1" + ], + "provides": [ + "cmd:findssl.sh", + "cmd:scp", + "cmd:sftp", + "cmd:ssh", + "cmd:ssh-add", + "cmd:ssh-agent", + "cmd:ssh-copy-id", + "cmd:ssh-keyscan", + "cmd:ssh-pkcs11-helper" + ] + }, + "setup-box": { + "versions": { + "1.0.1-r0": 1509490948 + }, + "origin": "setup-box", + "dependencies": [ + "jq" + ], + "provides": [ + "cmd:setup-box" + ] + }, + "py-bluez": { + "versions": { + "0.22-r1": 1510075579 + }, + "origin": "py-bluez" + }, + "libnfsidmap-dev": { + "versions": { + "0.25-r1": 1509492062 + }, + "origin": "libnfsidmap", + "dependencies": [ + "libnfsidmap=0.25-r1", + "pkgconfig" + ], + "provides": [ + "pc:libnfsidmap=0.25" + ] + }, + "aports-build": { + "versions": { + "1.2-r0": 1509462510 + }, + "origin": "aports-build", + "dependencies": [ + "abuild>2.20.0", + "mosquitto-clients", + "alpine-sdk", + "mqtt-exec", + "rsync", + "lua-aports", + "lua5.2-cjson", + "lua5.2-mqtt-publish", + "/bin/sh" + ], + "provides": [ + "cmd:aports-build" + ] + }, + "libdv-dev": { + "versions": { + "1.0.0-r3": 1510068408 + }, + "origin": "libdv", + "dependencies": [ + "libdv=1.0.0-r3", + "pkgconfig" + ], + "provides": [ + "pc:libdv=1.0.0" + ] + }, + "nginx-mod-http-nchan": { + "versions": { + "1.12.2-r4": 1542814448 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "nagios-plugins-breeze": { + "versions": { + "2.2.1-r3": 1510288494 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins" + ] + }, + "usb-modeswitch-udev": { + "versions": { + "2.5.1-r0": 1509480148 + }, + "origin": "usb-modeswitch", + "dependencies": [ + "tcl", + "eudev", + "usb-modeswitch=2.5.1-r0" + ], + "provides": [ + "cmd:usb_modeswitch_dispatcher" + ] + }, + "libraw1394-tools": { + "versions": { + "2.1.2-r0": 1509470074 + }, + "origin": "libraw1394", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libraw1394.so.11" + ], + "provides": [ + "cmd:dumpiso", + "cmd:sendiso", + "cmd:testlibraw" + ] + }, + "cpufrequtils-doc": { + "versions": { + "008-r4": 1509486336 + }, + "origin": "cpufrequtils" + }, + "perl-role-tiny-doc": { + "versions": { + "2.000005-r0": 1509481549 + }, + "origin": "perl-role-tiny" + }, + "libdaemon-doc": { + "versions": { + "0.14-r2": 1509465393 + }, + "origin": "libdaemon" + }, + "gross": { + "versions": { + "1.0.2-r11": 1509482387 + }, + "origin": "gross", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2" + ], + "provides": [ + "so:grosscheck.so=0", + "cmd:gclient", + "cmd:grossd" + ] + }, + "gdk-pixbuf": { + "versions": { + "2.36.10-r0": 1509465514 + }, + "origin": "gdk-pixbuf", + "dependencies": [ + "shared-mime-info", + "/bin/sh", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:libpng16.so.16" + ], + "provides": [ + "so:libgdk_pixbuf-2.0.so.0=0.3610.0", + "so:libgdk_pixbuf_xlib-2.0.so.0=0.3610.0", + "cmd:gdk-pixbuf-csource", + "cmd:gdk-pixbuf-pixdata", + "cmd:gdk-pixbuf-query-loaders", + "cmd:gdk-pixbuf-thumbnailer" + ] + }, + "rpcgen": { + "versions": { + "2.1.1-r4": 1509492107 + }, + "origin": "nfs-utils", + "dependencies": [ + "rpcbind", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:rpcgen" + ] + }, + "py-babel": { + "versions": { + "2.5.1-r0": 1509490097 + }, + "origin": "py-babel", + "dependencies": [ + "py-tz" + ] + }, + "liblastfm": { + "versions": { + "1.0.9-r1": 1510074389 + }, + "origin": "liblastfm", + "dependencies": [ + "so:libQtCore.so.4", + "so:libQtDBus.so.4", + "so:libQtNetwork.so.4", + "so:libQtSql.so.4", + "so:libQtXml.so.4", + "so:libc.musl-x86_64.so.1", + "so:libfftw3f.so.3", + "so:libgcc_s.so.1", + "so:libsamplerate.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:liblastfm.so.1=1.0.9", + "so:liblastfm_fingerprint.so.1=1.0.9" + ] + }, + "gtk-murrine-engine": { + "versions": { + "0.98.2-r2": 1510075986 + }, + "origin": "gtk-murrine-engine", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpixman-1.so.0" + ] + }, + "udisks2-libs": { + "versions": { + "2.6.5-r0": 1510075152 + }, + "origin": "udisks2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libudisks2.so.0=0.0.0" + ] + }, + "spl-vanilla": { + "versions": { + "4.9.161-r0": 1551779820, + "4.9.182-r0": 1560866250 + }, + "origin": "spl-vanilla", + "dependencies": [ + "linux-vanilla=4.9.182-r0" + ] + }, + "axel": { + "versions": { + "2.4-r1": 1509494658 + }, + "origin": "axel", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:axel" + ] + }, + "lua-filesystem": { + "versions": { + "1.7.0.2-r0": 1511049451 + }, + "origin": "lua-filesystem" + }, + "libcanberra-dev": { + "versions": { + "0.30-r1": 1510071974 + }, + "origin": "libcanberra", + "dependencies": [ + "gtk+-dev", + "libogg-dev", + "libvorbis-dev", + "alsa-lib-dev", + "libtool", + "gtk+3.0-dev", + "libcanberra-gtk2=0.30-r1", + "libcanberra-gtk3=0.30-r1", + "libcanberra=0.30-r1", + "pc:gdk-2.0", + "pc:gdk-3.0", + "pc:gtk+-2.0", + "pc:gtk+-3.0", + "pkgconfig" + ], + "provides": [ + "pc:libcanberra-gtk3=0.30", + "pc:libcanberra-gtk=0.30", + "pc:libcanberra=0.30" + ] + }, + "sox": { + "versions": { + "14.4.2-r0": 1510074688 + }, + "origin": "sox", + "dependencies": [ + "so:libFLAC.so.8", + "so:libao.so.4", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libgomp.so.1", + "so:libgsm.so.1", + "so:libltdl.so.7", + "so:libmad.so.0", + "so:libmagic.so.1", + "so:libmp3lame.so.0", + "so:libogg.so.0", + "so:libopusfile.so.0", + "so:libsndfile.so.1", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2", + "so:libvorbisfile.so.3" + ], + "provides": [ + "so:libsox.so.3=3.0.0", + "cmd:play", + "cmd:rec", + "cmd:sox", + "cmd:soxi" + ] + }, + "lddtree": { + "versions": { + "1.26-r1": 1509462282 + }, + "origin": "lddtree", + "dependencies": [ + "scanelf" + ], + "provides": [ + "cmd:lddtree" + ] + }, + "nagios-plugins-file_age": { + "versions": { + "2.2.1-r3": 1510288495 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins" + ] + }, + "glib": { + "versions": { + "2.54.2-r0": 1509911133, + "2.54.2-r1": 1560764733 + }, + "origin": "glib", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libintl.so.8", + "so:libmount.so.1", + "so:libpcre.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libgio-2.0.so.0=0.5400.2", + "so:libglib-2.0.so.0=0.5400.2", + "so:libgmodule-2.0.so.0=0.5400.2", + "so:libgobject-2.0.so.0=0.5400.2", + "so:libgthread-2.0.so.0=0.5400.2", + "cmd:gapplication", + "cmd:gdbus", + "cmd:gio", + "cmd:gio-querymodules", + "cmd:glib-compile-schemas", + "cmd:gsettings" + ] + }, + "freeradius-lib": { + "versions": { + "3.0.15-r4": 1556202796, + "3.0.15-r5": 1566310603 + }, + "origin": "freeradius", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpcap.so.1", + "so:libssl.so.44", + "so:libtalloc.so.2" + ], + "provides": [ + "so:libfreeradius-dhcp.so=0", + "so:libfreeradius-eap.so=0", + "so:libfreeradius-radius.so=0", + "so:libfreeradius-server.so=0" + ] + }, + "qt-config": { + "versions": { + "4.8.7-r8": 1510287208 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtGui.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:qtconfig" + ] + }, + "lua5.3-lzmq": { + "versions": { + "0.4.4-r0": 1509475920 + }, + "origin": "lua-lzmq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5", + "so:libzmq.so.5" + ] + }, + "exiv2-doc": { + "versions": { + "0.25-r0": 1509475473 + }, + "origin": "exiv2" + }, + "perl-params-classify-doc": { + "versions": { + "0.015-r0": 1509470454 + }, + "origin": "perl-params-classify" + }, + "xf86-video-ark": { + "versions": { + "0.7.5-r7": 1510074927 + }, + "origin": "xf86-video-ark", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-tree-dag_node": { + "versions": { + "1.29-r0": 1509494497 + }, + "origin": "perl-tree-dag_node", + "dependencies": [ + "perl-file-slurp-tiny" + ] + }, + "xkbcomp-dev": { + "versions": { + "1.4.0-r2": 1509473696 + }, + "origin": "xkbcomp", + "dependencies": [ + "pc:x11", + "pc:xkbfile", + "pc:xproto>=7.0.17", + "pkgconfig" + ], + "provides": [ + "pc:xkbcomp=1.4.0" + ] + }, + "pkgconf-dev": { + "versions": { + "1.3.10-r0": 1509459814 + }, + "origin": "pkgconf", + "dependencies": [ + "pkgconf=1.3.10-r0", + "pkgconfig" + ], + "provides": [ + "pc:libpkgconf=1.3.10" + ] + }, + "gdk-pixbuf-doc": { + "versions": { + "2.36.10-r0": 1509465514 + }, + "origin": "gdk-pixbuf" + }, + "aconf-mod-openssh": { + "versions": { + "0.6.5-r0": 1510073710 + }, + "origin": "aconf", + "dependencies": [ + "aconf", + "openssh" + ] + }, + "aria2": { + "versions": { + "1.33.1-r1": 1548941587 + }, + "origin": "aria2", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libgmp.so.10", + "so:libgnutls.so.30", + "so:libnettle.so.6", + "so:libsqlite3.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:aria2c" + ] + }, + "pekwm": { + "versions": { + "0.1.17-r2": 1509494548 + }, + "origin": "pekwm", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXpm.so.4", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpng16.so.16", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:pekwm" + ] + }, + "nginx-mod-http-echo": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "iftop": { + "versions": { + "0.17-r7": 1509489489 + }, + "origin": "iftop", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:iftop" + ] + }, + "dhcpcd-ui-doc": { + "versions": { + "0.7.5-r1": 1510073427 + }, + "origin": "dhcpcd-ui" + }, + "msmtp-vim": { + "versions": { + "1.6.6-r2": 1510261496 + }, + "origin": "msmtp" + }, + "libglade-dev": { + "versions": { + "2.6.4-r14": 1510071790 + }, + "origin": "libglade", + "dependencies": [ + "gtk+2.0-dev", + "libxml2-dev", + "libglade=2.6.4-r14", + "pc:gtk+-2.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libglade-2.0=2.6.4" + ] + }, + "dev86": { + "versions": { + "0.16.21-r0": 1509479310 + }, + "origin": "dev86", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ar86", + "cmd:as86", + "cmd:bcc", + "cmd:ld86", + "cmd:nm86", + "cmd:objdump86", + "cmd:size86" + ] + }, + "cifs-utils-doc": { + "versions": { + "6.7-r1": 1509475350 + }, + "origin": "cifs-utils" + }, + "xrandr-doc": { + "versions": { + "1.5.0-r1": 1509491916 + }, + "origin": "xrandr" + }, + "postfix-pgsql": { + "versions": { + "3.2.4-r1": 1510285340 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "so:postfix-pgsql.so=0" + ] + }, + "xcb-util-cursor": { + "versions": { + "0.1.3-r1": 1509491137 + }, + "origin": "xcb-util-cursor", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-image.so.0", + "so:libxcb-render-util.so.0", + "so:libxcb-render.so.0", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-cursor.so.0=0.0.0" + ] + }, + "ragel-doc": { + "versions": { + "6.10-r0": 1509490668 + }, + "origin": "ragel" + }, + "perl-ipc-system-simple": { + "versions": { + "1.25-r0": 1509495320 + }, + "origin": "perl-ipc-system-simple" + }, + "perl-net-ssleay-doc": { + "versions": { + "1.82-r0": 1510260115 + }, + "origin": "perl-net-ssleay" + }, + "lua5.3-sql-sqlite3": { + "versions": { + "2.3.5-r1": 1509488833 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "phodav-doc": { + "versions": { + "2.2-r0": 1509492230 + }, + "origin": "phodav" + }, + "duplicity-lang": { + "versions": { + "0.7.13.1-r0": 1510588266 + }, + "origin": "duplicity", + "dependencies": [ + "python2", + "py-boto", + "gnupg", + "ncftp", + "py-lockfile" + ] + }, + "binutils-dev": { + "versions": { + "2.30-r1": 1527754980, + "2.30-r2": 1565788943 + }, + "origin": "binutils", + "dependencies": [ + "binutils-libs=2.30-r2" + ] + }, + "lcms-doc": { + "versions": { + "1.19-r6": 1509477354 + }, + "origin": "lcms" + }, + "procps-dev": { + "versions": { + "3.3.12-r3": 1509492054 + }, + "origin": "procps", + "dependencies": [ + "libproc=3.3.12-r3", + "pkgconfig" + ] + }, + "po4a-lang": { + "versions": { + "0.51-r1": 1509459568 + }, + "origin": "po4a", + "dependencies": [ + "perl", + "gettext" + ] + }, + "libnetfilter_conntrack": { + "versions": { + "1.0.6-r0": 1509469226 + }, + "origin": "libnetfilter_conntrack", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0", + "so:libnfnetlink.so.0" + ], + "provides": [ + "so:libnetfilter_conntrack.so.3=3.6.0" + ] + }, + "py3-gobject3": { + "versions": { + "3.24.1-r2": 1509481893 + }, + "origin": "py-gobject3", + "dependencies": [ + "py3-cairo", + "so:libc.musl-x86_64.so.1", + "so:libcairo-gobject.so.2", + "so:libcairo.so.2", + "so:libffi.so.6", + "so:libgirepository-1.0.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ] + }, + "apache2-proxy": { + "versions": { + "2.4.39-r0": 1554306882, + "2.4.41-r0": 1566292328 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-pbr": { + "versions": { + "3.1.1-r0": 1509480861 + }, + "origin": "py-pbr" + }, + "xcb-proto": { + "versions": { + "1.12-r1": 1509461815 + }, + "origin": "xcb-proto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xcb-proto=1.12" + ] + }, + "irssi-perl": { + "versions": { + "1.0.6-r0": 1519052408, + "1.0.8-r0": 1562236917 + }, + "origin": "irssi", + "dependencies": [ + "irssi", + "perl", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "perl-encode-hanextra": { + "versions": { + "0.23-r2": 1509489061 + }, + "origin": "perl-encode-hanextra", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "meson-doc": { + "versions": { + "0.43.0-r0": 1509475829 + }, + "origin": "meson" + }, + "libxkbcommon": { + "versions": { + "0.7.1-r1": 1509482486 + }, + "origin": "libxkbcommon", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-xkb.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxkbcommon-x11.so.0=0.0.0", + "so:libxkbcommon.so.0=0.0.0" + ] + }, + "abiword-doc": { + "versions": { + "3.0.2-r1": 1510073355 + }, + "origin": "abiword" + }, + "gmp": { + "versions": { + "6.1.2-r1": 1509456917 + }, + "origin": "gmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgmp.so.10=10.3.2" + ] + }, + "yeahconsole": { + "versions": { + "0.3.4-r0": 1509490577 + }, + "origin": "yeahconsole", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:yeahconsole" + ] + }, + "libinput-doc": { + "versions": { + "1.9.2-r0": 1511276852 + }, + "origin": "libinput" + }, + "py2-icu": { + "versions": { + "1.9.6-r3": 1510832509 + }, + "origin": "py-icu", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libicui18n.so.59", + "so:libicuuc.so.59", + "so:libpython2.7.so.1.0", + "so:libstdc++.so.6" + ] + }, + "abiword-plugin-google": { + "versions": { + "3.0.2-r1": 1510073361 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "xf86-input-vmmouse": { + "versions": { + "13.1.0-r3": 1510074938 + }, + "origin": "xf86-input-vmmouse", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ], + "provides": [ + "cmd:vmmouse_detect" + ] + }, + "gtk-engines": { + "versions": { + "2.21.0-r2": 1510071725 + }, + "origin": "gtk-engines", + "dependencies": [ + "gtk-engines-clearlooks", + "gtk-engines-crux", + "gtk-engines-industrial", + "gtk-engines-mist", + "gtk-engines-redmond", + "gtk-engines-thinice", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "btrfs-progs-extra": { + "versions": { + "4.13.2-r0": 1509481819 + }, + "origin": "btrfs-progs", + "dependencies": [ + "btrfs-progs", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libext2fs.so.2", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:btrfs-convert", + "cmd:btrfs-debug-tree", + "cmd:btrfs-find-root", + "cmd:btrfs-image", + "cmd:btrfs-map-logical", + "cmd:btrfs-select-super", + "cmd:btrfs-zero-log", + "cmd:btrfstune" + ] + }, + "cgdb-doc": { + "versions": { + "0.7.0-r1": 1509495074 + }, + "origin": "cgdb" + }, + "perl-email-address-list": { + "versions": { + "0.05-r2": 1510564503 + }, + "origin": "perl-email-address-list", + "dependencies": [ + "perl", + "perl-email-address" + ] + }, + "cups-lang": { + "versions": { + "2.2.10-r0": 1549287809, + "2.2.12-r0": 1566207597 + }, + "origin": "cups", + "dependencies": [ + "cups-client", + "poppler-utils", + "libressl", + "dbus" + ] + }, + "collectd-ascent": { + "versions": { + "5.7.2-r0": 1510069650 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libxml2.so.2" + ] + }, + "smokeping-doc": { + "versions": { + "2.6.11-r3": 1510564558 + }, + "origin": "smokeping" + }, + "py3-oauth2client": { + "versions": { + "4.1.2-r1": 1509551799 + }, + "origin": "py-oauth2client", + "dependencies": [ + "py3asn1", + "py3httplib2", + "py3asn1-modules", + "py3rsa", + "py3six", + "python3" + ] + }, + "apk-tools": { + "versions": { + "2.10.1-r0": 1536582152 + }, + "origin": "apk-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:apk" + ] + }, + "py3-asn1crypto": { + "versions": { + "0.23.0-r0": 1509476390 + }, + "origin": "py-asn1crypto", + "dependencies": [ + "python3" + ] + }, + "openldap-doc": { + "versions": { + "2.4.45-r3": 1510258131, + "2.4.46-r0": 1565073937, + "2.4.48-r0": 1566900514 + }, + "origin": "openldap" + }, + "less-doc": { + "versions": { + "520-r0": 1509496059 + }, + "origin": "less" + }, + "lame": { + "versions": { + "3.100-r0": 1510088509 + }, + "origin": "lame", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "so:libmp3lame.so.0=0.0.0", + "cmd:lame" + ] + }, + "libgcab-lang": { + "versions": { + "0.7-r1": 1510067822 + }, + "origin": "libgcab" + }, + "font-sony-misc": { + "versions": { + "1.0.3-r0": 1509475370 + }, + "origin": "font-sony-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "jasper-doc": { + "versions": { + "2.0.14-r0": 1509475517 + }, + "origin": "jasper" + }, + "confuse": { + "versions": { + "3.2.1-r0": 1509471872 + }, + "origin": "confuse", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libconfuse.so.2=2.0.0" + ] + }, + "lua5.2-sql-postgres": { + "versions": { + "2.3.5-r1": 1509488830 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "gnutls-dev": { + "versions": { + "3.6.1-r0": 1509465348 + }, + "origin": "gnutls", + "dependencies": [ + "gnutls-c++=3.6.1-r0", + "gnutls=3.6.1-r0", + "pc:hogweed", + "pc:libtasn1", + "pc:nettle", + "pc:p11-kit-1", + "pkgconfig" + ], + "provides": [ + "pc:gnutls=3.6.1" + ] + }, + "transmission-doc": { + "versions": { + "2.92-r8": 1510932986 + }, + "origin": "transmission" + }, + "swfdec-doc": { + "versions": { + "0.9.2-r0": 1510073744 + }, + "origin": "swfdec" + }, + "perl-test-inter": { + "versions": { + "1.06-r0": 1509485924 + }, + "origin": "perl-test-inter" + }, + "libpng-dev": { + "versions": { + "1.6.37-r0": 1557132258 + }, + "origin": "libpng", + "dependencies": [ + "libpng=1.6.37-r0", + "pc:zlib", + "pkgconfig" + ], + "provides": [ + "pc:libpng16=1.6.37", + "pc:libpng=1.6.37", + "cmd:libpng-config", + "cmd:libpng16-config" + ] + }, + "monkeysphere": { + "versions": { + "0.41-r0": 1510588391 + }, + "origin": "monkeysphere", + "dependencies": [ + "libressl", + "gnupg", + "so:libassuan.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libgpg-error.so.0" + ], + "provides": [ + "cmd:agent-transfer", + "cmd:monkeysphere", + "cmd:monkeysphere-authentication", + "cmd:monkeysphere-host", + "cmd:openpgp2pem", + "cmd:openpgp2spki", + "cmd:openpgp2ssh", + "cmd:pem2openpgp" + ] + }, + "lua-cqueues": { + "versions": { + "20171014-r0": 1510619111 + }, + "origin": "lua-cqueues" + }, + "swfdec-dev": { + "versions": { + "0.9.2-r0": 1510073741 + }, + "origin": "swfdec", + "dependencies": [ + "glib-dev", + "liboil-dev", + "cairo-dev", + "pango-dev", + "gtk+2.0-dev", + "libsoup-dev", + "gstreamer0.10-dev", + "gst-plugins-base0.10-dev", + "alsa-lib-dev", + "pc:cairo", + "pc:glib-2.0>=2.16", + "pc:gobject-2.0>=2.16", + "pc:gtk+-2.0>=2.8.0", + "pc:liboil-0.3", + "pc:pangocairo", + "pkgconfig", + "swfdec=0.9.2-r0" + ], + "provides": [ + "pc:swfdec-0.9=0.9.2", + "pc:swfdec-gtk-0.9=0.9.2" + ] + }, + "py2-asn1crypto": { + "versions": { + "0.23.0-r0": 1509476391 + }, + "origin": "py-asn1crypto", + "dependencies": [ + "python2" + ] + }, + "glm": { + "versions": { + "0.9.8.5-r0": 1509494919 + }, + "origin": "glm" + }, + "libcrystalhd": { + "versions": { + "20130708-r2": 1509493205 + }, + "origin": "libcrystalhd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libcrystalhd.so.3=3.6" + ] + }, + "audit-doc": { + "versions": { + "2.7.7-r1": 1509491479 + }, + "origin": "audit" + }, + "librsvg-doc": { + "versions": { + "2.40.19-r0": 1510066818 + }, + "origin": "librsvg" + }, + "perl-sub-name-doc": { + "versions": { + "0.21-r1": 1509474063 + }, + "origin": "perl-sub-name" + }, + "perl-file-remove": { + "versions": { + "1.57-r0": 1510352929 + }, + "origin": "perl-file-remove" + }, + "py-urlgrabber": { + "versions": { + "3.10.1-r1": 1509468416 + }, + "origin": "py-urlgrabber", + "dependencies": [ + "python2", + "py-curl" + ], + "provides": [ + "cmd:urlgrabber" + ] + }, + "gtk+3.0-dev": { + "versions": { + "3.22.21-r0": 1510067778 + }, + "origin": "gtk+3.0", + "dependencies": [ + "at-spi2-atk-dev", + "atk-dev", + "cairo-dev", + "fontconfig-dev", + "gdk-pixbuf-dev", + "glib-dev", + "intltool", + "libepoxy-dev", + "libx11-dev", + "libxcomposite-dev", + "libxcursor-dev", + "libxdamage-dev", + "libxext-dev", + "libxfixes-dev", + "libxi-dev", + "libxinerama-dev", + "libxrandr-dev", + "pango-dev", + "gtk+3.0=3.22.21-r0", + "pc:atk", + "pc:atk-bridge-2.0", + "pc:atk>=2.15.1", + "pc:cairo-gobject>=1.14.0", + "pc:cairo-xlib", + "pc:cairo>=1.14.0", + "pc:epoxy>=1.0", + "pc:fontconfig", + "pc:gdk-pixbuf-2.0>=2.30.0", + "pc:gio-2.0>=2.49.4", + "pc:gio-unix-2.0>=2.49.4", + "pc:pango", + "pc:pangocairo", + "pc:pangoft2", + "pc:x11", + "pc:xcomposite", + "pc:xcursor", + "pc:xdamage", + "pc:xext", + "pc:xfixes", + "pc:xi", + "pc:xinerama", + "pc:xrandr", + "pkgconfig" + ], + "provides": [ + "pc:gail-3.0=3.22.21", + "pc:gdk-3.0=3.22.21", + "pc:gdk-x11-3.0=3.22.21", + "pc:gtk+-3.0=3.22.21", + "pc:gtk+-unix-print-3.0=3.22.21", + "pc:gtk+-x11-3.0=3.22.21" + ] + }, + "newt-dev": { + "versions": { + "0.52.20-r0": 1509476145 + }, + "origin": "newt", + "dependencies": [ + "newt=0.52.20-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnewt=0.52.20" + ] + }, + "nmap-nselibs": { + "versions": { + "7.60-r2": 1510261467, + "7.60-r3": 1572297245 + }, + "origin": "nmap" + }, + "ipsec-tools-dev": { + "versions": { + "0.8.2-r6": 1510261510 + }, + "origin": "ipsec-tools" + }, + "liblogging-doc": { + "versions": { + "1.0.6-r0": 1509517791 + }, + "origin": "liblogging" + }, + "ppp-doc": { + "versions": { + "2.4.7-r5": 1509480123 + }, + "origin": "ppp" + }, + "xf86-video-ati-doc": { + "versions": { + "7.10.0-r1": 1511981941 + }, + "origin": "xf86-video-ati" + }, + "qemu-system-moxie": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-moxie" + ] + }, + "netcf-libs": { + "versions": { + "0.2.8-r4": 1509491201 + }, + "origin": "netcf", + "dependencies": [ + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1", + "so:libexslt.so.0", + "so:libnl-3.so.200", + "so:libnl-route-3.so.200", + "so:libxml2.so.2", + "so:libxslt.so.1" + ], + "provides": [ + "so:libnetcf.so.1=1.4.0" + ] + }, + "mdocml-doc": { + "versions": { + "1.14.3-r0": 1509459635 + }, + "origin": "mdocml" + }, + "jpeg-dev": { + "versions": { + "8-r6": 1509469915 + }, + "origin": "jpeg", + "dependencies": [ + "libjpeg-turbo-dev" + ] + }, + "pcre2": { + "versions": { + "10.30-r0": 1509461775 + }, + "origin": "pcre2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre2-8.so.0=0.6.0", + "so:libpcre2-posix.so.2=2.0.0" + ] + }, + "lua5.1-lzlib": { + "versions": { + "0.4.3-r0": 1509494164 + }, + "origin": "lua-lzlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ] + }, + "bwm-ng": { + "versions": { + "0.6.1-r3": 1509475295 + }, + "origin": "bwm-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:bwm-ng" + ] + }, + "perl-yaml-libyaml-doc": { + "versions": { + "0.65-r2": 1509494320 + }, + "origin": "perl-yaml-libyaml" + }, + "xfsprogs-extra": { + "versions": { + "4.14.0-r1": 1528279973 + }, + "origin": "xfsprogs", + "dependencies": [ + "xfsprogs", + "so:libc.musl-x86_64.so.1", + "so:libhandle.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:xfs_admin", + "cmd:xfs_bmap", + "cmd:xfs_copy", + "cmd:xfs_db", + "cmd:xfs_estimate", + "cmd:xfs_freeze", + "cmd:xfs_fsr", + "cmd:xfs_growfs", + "cmd:xfs_info", + "cmd:xfs_io", + "cmd:xfs_logprint", + "cmd:xfs_mdrestore", + "cmd:xfs_metadump", + "cmd:xfs_mkfile", + "cmd:xfs_ncheck", + "cmd:xfs_quota", + "cmd:xfs_rtcp", + "cmd:xfs_spaceman" + ] + }, + "py-meld3": { + "versions": { + "1.0.2-r1": 1509552703 + }, + "origin": "py-meld3", + "dependencies": [ + "python2" + ] + }, + "perl-devel-globaldestruction-doc": { + "versions": { + "0.14-r0": 1509475924 + }, + "origin": "perl-devel-globaldestruction" + }, + "pidgin-otr-lang": { + "versions": { + "4.0.2-r0": 1510072469 + }, + "origin": "pidgin-otr", + "dependencies": [ + "pidgin" + ] + }, + "debootstrap": { + "versions": { + "1.0.92-r0": 1510588271 + }, + "origin": "debootstrap", + "dependencies": [ + "debian-archive-keyring", + "dpkg", + "tar" + ], + "provides": [ + "cmd:debootstrap" + ] + }, + "snort": { + "versions": { + "2.9.11-r0": 1510848485 + }, + "origin": "snort", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libdnet.so.1", + "so:libpcap.so.1", + "so:libpcre.so.1", + "so:libsfbpf.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:snort", + "cmd:u2boat", + "cmd:u2spewfoo" + ] + }, + "charybdis": { + "versions": { + "3.5.5-r3": 1510260652 + }, + "origin": "charybdis", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:libratbox.so=0", + "cmd:charybdis-bantool", + "cmd:charybdis-genssl", + "cmd:charybdis-ircd", + "cmd:charybdis-mkpasswd", + "cmd:charybdis-viconf", + "cmd:charybdis-vimotd" + ] + }, + "netcf-doc": { + "versions": { + "0.2.8-r4": 1509491201 + }, + "origin": "netcf" + }, + "perl-list-someutils-xs": { + "versions": { + "0.55-r0": 1510564670 + }, + "origin": "perl-list-someutils-xs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "aaudit-server": { + "versions": { + "0.7.2-r1": 1510074392 + }, + "origin": "aaudit", + "dependencies": [ + "aaudit", + "git", + "lua5.2", + "lua5.2-posix", + "lua5.2-cjson", + "lua5.2-lzlib" + ], + "provides": [ + "cmd:aaudit-repo", + "cmd:aaudit-update-keys" + ] + }, + "py-gflags": { + "versions": { + "3.1.1-r1": 1509551806 + }, + "origin": "py-gflags" + }, + "fsarchiver-doc": { + "versions": { + "0.8.2-r0": 1509485944 + }, + "origin": "fsarchiver" + }, + "ffmpeg-dev": { + "versions": { + "3.4-r1": 1510072146 + }, + "origin": "ffmpeg", + "dependencies": [ + "ffmpeg-libs=3.4-r1", + "pkgconfig" + ], + "provides": [ + "pc:libavcodec=57.107.100", + "pc:libavdevice=57.10.100", + "pc:libavfilter=6.107.100", + "pc:libavformat=57.83.100", + "pc:libavresample=3.7.0", + "pc:libavutil=55.78.100", + "pc:libpostproc=54.7.100", + "pc:libswresample=2.9.100", + "pc:libswscale=4.8.100" + ] + }, + "py-pillow-doc": { + "versions": { + "4.3.0-r0": 1509489959 + }, + "origin": "py-pillow", + "dependencies": [ + "py-olefile" + ] + }, + "fastjar": { + "versions": { + "0.98-r2": 1509490598 + }, + "origin": "fastjar", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:fastjar", + "cmd:grepjar" + ] + }, + "slang-doc": { + "versions": { + "2.3.1a-r0": 1509476136 + }, + "origin": "slang" + }, + "uriparser-dev": { + "versions": { + "0.8.4-r0": 1509494962 + }, + "origin": "uriparser", + "dependencies": [ + "pkgconfig", + "uriparser=0.8.4-r0" + ], + "provides": [ + "pc:liburiparser=0.8.4" + ] + }, + "rpcbind": { + "versions": { + "0.2.4-r0": 1509488291 + }, + "origin": "rpcbind", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libtirpc.so.3" + ], + "provides": [ + "cmd:rpcbind", + "cmd:rpcinfo" + ] + }, + "jack-doc": { + "versions": { + "1.9.10-r5": 1509473318 + }, + "origin": "jack" + }, + "dhcp-doc": { + "versions": { + "4.3.5-r0": 1509496512 + }, + "origin": "dhcp" + }, + "cmph-doc": { + "versions": { + "2.0-r1": 1509482578 + }, + "origin": "cmph" + }, + "xf86-input-evdev-dev": { + "versions": { + "2.10.5-r0": 1510074956 + }, + "origin": "xf86-input-evdev", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xorg-evdev=2.10.5" + ] + }, + "perl-email-date-format-doc": { + "versions": { + "1.005-r0": 1509489452 + }, + "origin": "perl-email-date-format" + }, + "gst-plugins-base-dev": { + "versions": { + "1.12.3-r0": 1510070597 + }, + "origin": "gst-plugins-base", + "dependencies": [ + "gst-plugins-base=1.12.3-r0", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gstreamer-1.0", + "pc:gstreamer-base-1.0", + "pkgconfig" + ], + "provides": [ + "pc:gstreamer-allocators-1.0=1.12.3", + "pc:gstreamer-app-1.0=1.12.3", + "pc:gstreamer-audio-1.0=1.12.3", + "pc:gstreamer-fft-1.0=1.12.3", + "pc:gstreamer-pbutils-1.0=1.12.3", + "pc:gstreamer-plugins-base-1.0=1.12.3", + "pc:gstreamer-riff-1.0=1.12.3", + "pc:gstreamer-rtp-1.0=1.12.3", + "pc:gstreamer-rtsp-1.0=1.12.3", + "pc:gstreamer-sdp-1.0=1.12.3", + "pc:gstreamer-tag-1.0=1.12.3", + "pc:gstreamer-video-1.0=1.12.3" + ] + }, + "perl-dbix-dbschema": { + "versions": { + "0.39-r0": 1509477489 + }, + "origin": "perl-dbix-dbschema", + "dependencies": [ + "perl", + "perl-dbi" + ] + }, + "perl-html-tree-doc": { + "versions": { + "5.07-r0": 1509481555 + }, + "origin": "perl-html-tree" + }, + "font-bitstream-100dpi": { + "versions": { + "1.0.3-r0": 1509494062 + }, + "origin": "font-bitstream-100dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "iproute2-doc": { + "versions": { + "4.13.0-r0": 1509481070 + }, + "origin": "iproute2" + }, + "alsa-utils": { + "versions": { + "1.1.4-r0": 1509468852 + }, + "origin": "alsa-utils", + "dependencies": [ + "dialog", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libfftw3f.so.3", + "so:libformw.so.6", + "so:libmenuw.so.6", + "so:libncursesw.so.6", + "so:libpanelw.so.6" + ], + "provides": [ + "cmd:aconnect", + "cmd:alsa-info.sh", + "cmd:alsabat", + "cmd:alsabat-test.sh", + "cmd:alsactl", + "cmd:alsaloop", + "cmd:alsamixer", + "cmd:alsatplg", + "cmd:alsaucm", + "cmd:amidi", + "cmd:amixer", + "cmd:aplay", + "cmd:aplaymidi", + "cmd:arecord", + "cmd:arecordmidi", + "cmd:aseqdump", + "cmd:aseqnet", + "cmd:iecset", + "cmd:speaker-test" + ] + }, + "soxr-dev": { + "versions": { + "0.1.2-r0": 1509492269 + }, + "origin": "soxr", + "dependencies": [ + "cmake", + "pkgconfig", + "soxr=0.1.2-r0" + ], + "provides": [ + "pc:soxr-lsr=0.1.2", + "pc:soxr=0.1.2" + ] + }, + "xorg-server-doc": { + "versions": { + "1.19.5-r1": 1540838460 + }, + "origin": "xorg-server" + }, + "lua5.1": { + "versions": { + "5.1.5-r3": 1509462459 + }, + "origin": "lua5.1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ], + "provides": [ + "cmd:lua5.1", + "cmd:luac5.1" + ] + }, + "mariadb-libs": { + "versions": { + "10.1.38-r1": 1551187990, + "10.1.40-r0": 1560354887, + "10.1.41-r0": 1565163111 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libmysqld.so.18=18" + ] + }, + "perl-convert-asn1-doc": { + "versions": { + "0.27-r0": 1509477727 + }, + "origin": "perl-convert-asn1" + }, + "gphoto2": { + "versions": { + "2.5.14-r0": 1509493217 + }, + "origin": "gphoto2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexif.so.12", + "so:libgphoto2.so.6", + "so:libgphoto2_port.so.12", + "so:libintl.so.8", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:gphoto2" + ] + }, + "acf-ipsec-tools": { + "versions": { + "0.12.0-r2": 1510072901 + }, + "origin": "acf-ipsec-tools", + "dependencies": [ + "acf-core", + "ipsec-tools" + ] + }, + "py2-markupsafe": { + "versions": { + "1.0-r0": 1509476744 + }, + "origin": "py-markupsafe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "tdb-dev": { + "versions": { + "1.3.15-r0": 1509486503 + }, + "origin": "tdb", + "dependencies": [ + "python2", + "pkgconfig", + "tdb-libs=1.3.15-r0" + ], + "provides": [ + "pc:tdb=1.3.15" + ] + }, + "avahi-ui": { + "versions": { + "0.6.31-r7": 1510076324 + }, + "origin": "avahi-ui", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-glib.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libavahi-ui.so.0=0.1.4" + ] + }, + "libpciaccess-doc": { + "versions": { + "0.13.5-r1": 1509466088 + }, + "origin": "libpciaccess" + }, + "garcon-dev": { + "versions": { + "0.6.1-r1": 1510068107 + }, + "origin": "garcon", + "dependencies": [ + "garcon=0.6.1-r1", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pc:gtk+-2.0", + "pc:gtk+-3.0", + "pc:libxfce4ui-1", + "pc:libxfce4ui-2", + "pkgconfig" + ], + "provides": [ + "pc:garcon-1=0.6.1", + "pc:garcon-gtk2-1=0.6.1", + "pc:garcon-gtk3-1=0.6.1" + ] + }, + "nginx-mod-stream-geoip": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-math-round-doc": { + "versions": { + "0.07-r0": 1509483897 + }, + "origin": "perl-math-round" + }, + "libgphoto2-doc": { + "versions": { + "2.5.14-r0": 1509481938 + }, + "origin": "libgphoto2" + }, + "ulogd-pgsql": { + "versions": { + "2.0.5-r4": 1509480396 + }, + "origin": "ulogd", + "dependencies": [ + "ulogd", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "ghi": { + "versions": { + "1.2.0-r1": 1509552714 + }, + "origin": "ghi", + "dependencies": [ + "ruby", + "ncurses" + ], + "provides": [ + "cmd:ghi" + ] + }, + "abiword-plugin-garble": { + "versions": { + "3.0.2-r1": 1510073359 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libstdc++.so.6", + "so:libxml2.so.2" + ] + }, + "vte-doc": { + "versions": { + "0.28.2-r13": 1510071902 + }, + "origin": "vte" + }, + "rtpproxy-tools": { + "versions": { + "2.0.0-r4": 1509492568 + }, + "origin": "rtpproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsndfile.so.1" + ], + "provides": [ + "cmd:extractaudio", + "cmd:makeann" + ] + }, + "mp3splt-gtk": { + "versions": { + "0.9.2-r2": 1510070623 + }, + "origin": "mp3splt-gtk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libmp3splt.so.0" + ], + "provides": [ + "cmd:mp3splt-gtk" + ] + }, + "openvpn-ad-check": { + "versions": { + "1.1-r2": 1509491056 + }, + "origin": "openvpn-ad-check", + "dependencies": [ + "openvpn", + "lua-ldap" + ] + }, + "vanessa_adt": { + "versions": { + "0.0.9-r0": 1509481362 + }, + "origin": "vanessa_adt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libvanessa_logger.so.0" + ], + "provides": [ + "so:libvanessa_adt.so.1=1.0.0" + ] + }, + "abiword-plugin-gimp": { + "versions": { + "3.0.2-r1": 1510073360 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "strongswan-doc": { + "versions": { + "5.6.3-r2": 1539005308 + }, + "origin": "strongswan" + }, + "py-roman": { + "versions": { + "2.0.0-r2": 1509493357 + }, + "origin": "py-roman" + }, + "dansguardian-doc": { + "versions": { + "2.12.0.3-r4": 1509494014 + }, + "origin": "dansguardian" + }, + "apr-util-dbm_db": { + "versions": { + "1.6.1-r1": 1510285059 + }, + "origin": "apr-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so" + ] + }, + "jbig2dec-dev": { + "versions": { + "0.14-r0": 1509469939 + }, + "origin": "jbig2dec", + "dependencies": [ + "jbig2dec=0.14-r0" + ] + }, + "rlog-doc": { + "versions": { + "1.4-r4": 1509475977 + }, + "origin": "rlog" + }, + "acf-quagga": { + "versions": { + "0.10.1-r1": 1510075434 + }, + "origin": "acf-quagga", + "dependencies": [ + "acf-core", + "lua-socket", + "quagga" + ] + }, + "mysql": { + "versions": { + "10.1.38-r1": 1551187995, + "10.1.40-r0": 1560354890, + "10.1.41-r0": 1565163115 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb" + ] + }, + "py3-cffi": { + "versions": { + "1.10.0-r0": 1509476372 + }, + "origin": "py-cffi", + "dependencies": [ + "py3-cparser", + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libpython3.6m.so.1.0" + ] + }, + "libdvdnav-dev": { + "versions": { + "5.0.3-r0": 1509481832 + }, + "origin": "libdvdnav", + "dependencies": [ + "libdvdread-dev>=5.0.3", + "libdvdnav=5.0.3-r0", + "pc:dvdread>=4.1.2", + "pkgconfig" + ], + "provides": [ + "pc:dvdnav=5.0.3" + ] + }, + "duplicity": { + "versions": { + "0.7.13.1-r0": 1510588267 + }, + "origin": "duplicity", + "dependencies": [ + "python2", + "py-boto", + "gnupg", + "ncftp", + "py-lockfile", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:librsync.so.2" + ], + "provides": [ + "cmd:duplicity", + "cmd:rdiffdir" + ] + }, + "perl-net-dns-resolver-mock": { + "versions": { + "1.20170814-r0": 1509469054 + }, + "origin": "perl-net-dns-resolver-mock", + "dependencies": [ + "perl-net-dns" + ] + }, + "sudo-doc": { + "versions": { + "1.8.21_p2-r1": 1509459675 + }, + "origin": "sudo" + }, + "perl-encode-doc": { + "versions": { + "2.93-r0": 1511889457 + }, + "origin": "perl-encode" + }, + "libcap-ng-doc": { + "versions": { + "0.7.8-r1": 1509461310 + }, + "origin": "libcap-ng" + }, + "bbsuid": { + "versions": { + "0.6-r0": 1509494150 + }, + "origin": "bbsuid", + "dependencies": [ + "busybox" + ] + }, + "pacman-dev": { + "versions": { + "5.0.2-r1": 1510588357 + }, + "origin": "pacman", + "dependencies": [ + "libarchive-dev", + "curl-dev", + "libressl-dev", + "gpgme-dev", + "gettext-dev", + "pacman=5.0.2-r1", + "pkgconfig" + ], + "provides": [ + "pc:libalpm=10.0.2" + ] + }, + "libevent": { + "versions": { + "2.1.8-r2": 1510258602 + }, + "origin": "libevent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "so:libevent-2.1.so.6=6.0.2", + "so:libevent_core-2.1.so.6=6.0.2", + "so:libevent_extra-2.1.so.6=6.0.2", + "so:libevent_openssl-2.1.so.6=6.0.2", + "so:libevent_pthreads-2.1.so.6=6.0.2" + ] + }, + "py-eyed3": { + "versions": { + "0.7.8-r1": 1509551898 + }, + "origin": "py-eyed3", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:eyeD3" + ] + }, + "lua-inspect-doc": { + "versions": { + "3.1.0-r1": 1509491025 + }, + "origin": "lua-inspect" + }, + "perl-file-listing-doc": { + "versions": { + "6.04-r1": 1509464400 + }, + "origin": "perl-file-listing" + }, + "conntrack-tools": { + "versions": { + "1.4.4-r0": 1509469777 + }, + "origin": "conntrack-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0", + "so:libnetfilter_conntrack.so.3", + "so:libnetfilter_cthelper.so.0", + "so:libnetfilter_cttimeout.so.1", + "so:libnetfilter_queue.so.1", + "so:libnfnetlink.so.0" + ], + "provides": [ + "cmd:conntrack", + "cmd:conntrackd", + "cmd:nfct" + ] + }, + "thunar-volman": { + "versions": { + "0.8.1-r2": 1510075205 + }, + "origin": "thunar-volman", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexo-1.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libnotify.so.4", + "so:libpango-1.0.so.0", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:thunar-volman", + "cmd:thunar-volman-settings" + ] + }, + "perl-class-inspector-doc": { + "versions": { + "1.28-r1": 1510564482 + }, + "origin": "perl-class-inspector" + }, + "perl-file-listing": { + "versions": { + "6.04-r1": 1509464400 + }, + "origin": "perl-file-listing", + "dependencies": [ + "perl", + "perl-http-date" + ] + }, + "libgee-dev": { + "versions": { + "0.20.0-r0": 1509468648 + }, + "origin": "libgee", + "dependencies": [ + "libgee=0.20.0-r0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gee-0.8=0.20.0" + ] + }, + "apache2-http2": { + "versions": { + "2.4.39-r0": 1554306881, + "2.4.41-r0": 1566292327 + }, + "origin": "apache2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libnghttp2.so.14" + ] + }, + "libdvdread-dev": { + "versions": { + "5.0.3-r1": 1509480295 + }, + "origin": "libdvdread", + "dependencies": [ + "libdvdread=5.0.3-r1", + "pc:libdvdcss>=1.2", + "pkgconfig" + ], + "provides": [ + "pc:dvdread=5.0.3" + ] + }, + "libgpg-error-lisp": { + "versions": { + "1.27-r1": 1509459212 + }, + "origin": "libgpg-error" + }, + "mutt-lang": { + "versions": { + "1.10.1-r0": 1532446898 + }, + "origin": "mutt" + }, + "libraw1394": { + "versions": { + "2.1.2-r0": 1509470075 + }, + "origin": "libraw1394", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libraw1394.so.11=11.1.0" + ] + }, + "perl-mail-dkim": { + "versions": { + "0.44-r0": 1509516996 + }, + "origin": "perl-mail-dkim", + "dependencies": [ + "perl", + "perl-net-dns", + "perl-net-ip", + "perl-mailtools", + "perl-crypt-openssl-rsa", + "perl-yaml-xs", + "perl-net-dns-resolver-mock" + ] + }, + "lz4-doc": { + "versions": { + "1.8.0-r1": 1509461404 + }, + "origin": "lz4" + }, + "apache2-dev": { + "versions": { + "2.4.39-r0": 1554306881, + "2.4.41-r0": 1566292327 + }, + "origin": "apache2", + "dependencies": [ + "perl", + "apr-util-dev" + ], + "provides": [ + "cmd:apxs" + ] + }, + "dejagnu": { + "versions": { + "1.6-r1": 1509491945 + }, + "origin": "dejagnu", + "dependencies": [ + "expect" + ], + "provides": [ + "cmd:runtest" + ] + }, + "uwsgi-nagios": { + "versions": { + "2.0.17-r0": 1522154656 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-xml-simple-doc": { + "versions": { + "2.24-r0": 1509477314 + }, + "origin": "perl-xml-simple" + }, + "bridge-utils-doc": { + "versions": { + "1.6-r0": 1509491170 + }, + "origin": "bridge-utils" + }, + "uwsgi-router_hash": { + "versions": { + "2.0.17-r0": 1522154657 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "qemu-aarch64": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-aarch64" + ] + }, + "qemu-openrc": { + "versions": { + "0.6.0-r0": 1510070486 + }, + "origin": "qemu-openrc", + "dependencies": [ + "qemu", + "socat", + "/bin/sh" + ], + "provides": [ + "cmd:qemush" + ] + }, + "qt-dev": { + "versions": { + "4.8.7-r8": 1510287207 + }, + "origin": "qt", + "dependencies": [ + "mesa-dev", + "libice-dev", + "libsm-dev", + "libx11-dev", + "libxext-dev", + "libxrender-dev", + "alsa-lib-dev", + "libressl-dev", + "fontconfig-dev", + "freetype-dev", + "glib-dev", + "libpng-dev", + "zlib-dev", + "sqlite-dev", + "dbus-dev", + "pkgconfig", + "qt-webkit=4.8.7-r8", + "qt-x11=4.8.7-r8", + "qt=4.8.7-r8", + "so:libQt3Support.so.4", + "so:libQtCore.so.4", + "so:libQtDBus.so.4", + "so:libQtDeclarative.so.4", + "so:libQtDesigner.so.4", + "so:libQtDesignerComponents.so.4", + "so:libQtGui.so.4", + "so:libQtHelp.so.4", + "so:libQtNetwork.so.4", + "so:libQtWebKit.so.4", + "so:libQtXml.so.4", + "so:libQtXmlPatterns.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "pc:Qt3Support=4.8.7", + "pc:QtCLucene=4.8.7", + "pc:QtCore=4.8.7", + "pc:QtDBus=4.8.7", + "pc:QtDeclarative=4.8.7", + "pc:QtDesigner=4.8.7", + "pc:QtDesignerComponents=4.8.7", + "pc:QtGui=4.8.7", + "pc:QtHelp=4.8.7", + "pc:QtMultimedia=4.8.7", + "pc:QtNetwork=4.8.7", + "pc:QtOpenGL=4.8.7", + "pc:QtScript=4.8.7", + "pc:QtScriptTools=4.8.7", + "pc:QtSql=4.8.7", + "pc:QtSvg=4.8.7", + "pc:QtTest=4.8.7", + "pc:QtUiTools=4.8.7", + "pc:QtWebKit=4.9.4", + "pc:QtXml=4.8.7", + "pc:QtXmlPatterns=4.8.7", + "cmd:designer", + "cmd:lconvert", + "cmd:linguist", + "cmd:lrelease", + "cmd:lupdate", + "cmd:moc", + "cmd:pixeltool", + "cmd:qcollectiongenerator", + "cmd:qdbuscpp2xml", + "cmd:qdbusxml2cpp", + "cmd:qdoc3", + "cmd:qhelpconverter", + "cmd:qhelpgenerator", + "cmd:qmake", + "cmd:qt3to4", + "cmd:qttracereplay", + "cmd:rcc", + "cmd:uic", + "cmd:uic3", + "cmd:xmlpatterns", + "cmd:xmlpatternsvalidator" + ] + }, + "lua-maxminddb": { + "versions": { + "0.1-r1": 1509496577 + }, + "origin": "lua-maxminddb", + "dependencies": [ + "lua5.1-maxminddb", + "lua5.2-maxminddb", + "lua5.3-maxminddb" + ] + }, + "ruby-rake": { + "versions": { + "2.4.6-r0": 1557166822 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ], + "provides": [ + "cmd:rake" + ] + }, + "fuse-doc": { + "versions": { + "2.9.8-r0": 1532967982 + }, + "origin": "fuse" + }, + "glu": { + "versions": { + "9.0.0-r3": 1510067879 + }, + "origin": "glu", + "dependencies": [ + "so:libGL.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libGLU.so.1=1.3.1" + ] + }, + "libxft": { + "versions": { + "2.3.2-r2": 1509462101 + }, + "origin": "libxft", + "dependencies": [ + "so:libX11.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6" + ], + "provides": [ + "so:libXft.so.2=2.3.2" + ] + }, + "aconf-mod-dnsmasq": { + "versions": { + "0.6.5-r0": 1510073707 + }, + "origin": "aconf", + "dependencies": [ + "aconf", + "dnsmasq" + ] + }, + "libjpeg-turbo": { + "versions": { + "1.5.3-r2": 1537879743, + "1.5.3-r3": 1563792634 + }, + "origin": "libjpeg-turbo", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libjpeg.so.8=8.1.2", + "so:libturbojpeg.so.0=0.1.0" + ] + }, + "uwsgi-corerouter": { + "versions": { + "2.0.17-r0": 1522154654 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "qemu-tilegx": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-tilegx" + ] + }, + "mtx": { + "versions": { + "1.3.12-r2": 1509482374 + }, + "origin": "mtx", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:loaderinfo", + "cmd:mtx", + "cmd:scsieject", + "cmd:scsitape", + "cmd:tapeinfo" + ] + }, + "gst-plugins-good0.10": { + "versions": { + "0.10.31-r0": 1510068454 + }, + "origin": "gst-plugins-good0.10", + "dependencies": [ + "so:libFLAC.so.8", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libavc1394.so.0", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdv.so.4", + "so:libgcc_s.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstaudio-0.10.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstcontroller-0.10.so.0", + "so:libgstfft-0.10.so.0", + "so:libgstinterfaces-0.10.so.0", + "so:libgstnetbuffer-0.10.so.0", + "so:libgstpbutils-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgstriff-0.10.so.0", + "so:libgstrtp-0.10.so.0", + "so:libgstrtsp-0.10.so.0", + "so:libgstsdp-0.10.so.0", + "so:libgsttag-0.10.so.0", + "so:libgstvideo-0.10.so.0", + "so:libiec61883.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:liborc-0.4.so.0", + "so:libraw1394.so.11", + "so:librom1394.so.0", + "so:libshout.so.3", + "so:libsoup-2.4.so.1", + "so:libsoup-gnome-2.4.so.1", + "so:libspeex.so.1", + "so:libstdc++.so.6", + "so:libtag.so.1", + "so:libwavpack.so.1", + "so:libxml2.so.2", + "so:libz.so.1" + ] + }, + "pidgin-otr": { + "versions": { + "4.0.2-r0": 1510072472 + }, + "origin": "pidgin-otr", + "dependencies": [ + "pidgin", + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libotr.so.5" + ] + }, + "cups-filters-dev": { + "versions": { + "1.17.9-r0": 1510075883 + }, + "origin": "cups-filters", + "dependencies": [ + "cups-filters-libs=1.17.9-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcupsfilters=1.17.9", + "pc:libfontembed=1.17.9" + ] + }, + "jasper-libs": { + "versions": { + "2.0.14-r0": 1509475517 + }, + "origin": "jasper", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8" + ], + "provides": [ + "so:libjasper.so.4=4.0.0" + ] + }, + "py-twitter": { + "versions": { + "3.1-r2": 1509552772 + }, + "origin": "py-twitter", + "dependencies": [ + "py-future", + "py-requests", + "py-requests-oauthlib" + ] + }, + "openipmi-libs": { + "versions": { + "2.0.22-r1": 1510260044 + }, + "origin": "openipmi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libglib-2.0.so.0" + ], + "provides": [ + "so:libOpenIPMI.so.0=0.0.5", + "so:libOpenIPMIcmdlang.so.0=0.0.5", + "so:libOpenIPMIglib.so.0=0.0.1", + "so:libOpenIPMIposix.so.0=0.0.1", + "so:libOpenIPMIpthread.so.0=0.0.1", + "so:libOpenIPMIui.so.1=1.0.1", + "so:libOpenIPMIutils.so.0=0.0.1" + ] + }, + "bc-doc": { + "versions": { + "1.07.1-r0": 1509465953 + }, + "origin": "bc" + }, + "qemu-mipsn32": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mipsn32" + ] + }, + "directfb-dev": { + "versions": { + "1.7.7-r1": 1509488976 + }, + "origin": "directfb", + "dependencies": [ + "directfb=1.7.7-r1", + "pkgconfig" + ], + "provides": [ + "pc:++dfb=1.7.7", + "pc:direct=1.7.7", + "pc:directfb-internal=1.7.7", + "pc:directfb=1.7.7", + "pc:fusion=1.7.7", + "cmd:directfb-config" + ] + }, + "py-libxslt": { + "versions": { + "1.1.31-r1": 1555487793, + "1.1.31-r2": 1572541164 + }, + "origin": "libxslt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexslt.so.0", + "so:libpython2.7.so.1.0", + "so:libxml2.so.2", + "so:libxslt.so.1" + ] + }, + "libseccomp-doc": { + "versions": { + "2.3.2-r1": 1519818661 + }, + "origin": "libseccomp" + }, + "xfce4-notes-plugin": { + "versions": { + "1.8.1-r0": 1510074769 + }, + "origin": "xfce4-notes-plugin", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libunique-1.0.so.0", + "so:libxfce4panel-1.0.so.4", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:xfce4-notes", + "cmd:xfce4-notes-settings", + "cmd:xfce4-popup-notes" + ] + }, + "perl-net-http-doc": { + "versions": { + "6.09-r1": 1509464377 + }, + "origin": "perl-net-http" + }, + "procps": { + "versions": { + "3.3.12-r3": 1509492056 + }, + "origin": "procps", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libprocps.so.6" + ], + "provides": [ + "cmd:free", + "cmd:pgrep", + "cmd:pidof", + "cmd:pkill", + "cmd:pmap", + "cmd:ps", + "cmd:pwdx", + "cmd:slabtop", + "cmd:sysctl", + "cmd:tload", + "cmd:top", + "cmd:uptime", + "cmd:vmstat", + "cmd:w", + "cmd:watch" + ] + }, + "perdition": { + "versions": { + "1.18-r10": 1510260023 + }, + "origin": "perdition", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgdbm.so.4", + "so:libpopt.so.0", + "so:libssl.so.44", + "so:libvanessa_adt.so.1", + "so:libvanessa_logger.so.0", + "so:libvanessa_socket.so.2" + ], + "provides": [ + "so:libperditiondb_gdbm.so.0=0.0.0", + "cmd:makegdbm", + "cmd:perdition", + "cmd:perdition.imap4", + "cmd:perdition.imap4s", + "cmd:perdition.imaps", + "cmd:perdition.pop3", + "cmd:perdition.pop3s" + ] + }, + "sems-registrar": { + "versions": { + "1.6.0-r6": 1510260897 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "s6-dns": { + "versions": { + "2.2.0.1-r0": 1509480282 + }, + "origin": "s6-dns", + "dependencies": [ + "skalibs", + "so:libc.musl-x86_64.so.1", + "so:libskarnet.so.2.6" + ], + "provides": [ + "so:libs6dns.so.2.2=2.2.0.1", + "so:libskadns.so.2.2=2.2.0.1", + "cmd:s6-dnsip4", + "cmd:s6-dnsip4-filter", + "cmd:s6-dnsip6", + "cmd:s6-dnsip6-filter", + "cmd:s6-dnsmx", + "cmd:s6-dnsname", + "cmd:s6-dnsname-filter", + "cmd:s6-dnsns", + "cmd:s6-dnsq", + "cmd:s6-dnsqr", + "cmd:s6-dnsqualify", + "cmd:s6-dnssoa", + "cmd:s6-dnssrv", + "cmd:s6-dnstxt", + "cmd:s6-randomip", + "cmd:skadnsd" + ] + }, + "libxmu-doc": { + "versions": { + "1.1.2-r1": 1509473711 + }, + "origin": "libxmu" + }, + "py-musicbrainzngs": { + "versions": { + "0.6-r0": 1509483362 + }, + "origin": "py-musicbrainzngs", + "dependencies": [ + "python2" + ] + }, + "fcgiwrap": { + "versions": { + "1.1.0-r2": 1510933068 + }, + "origin": "fcgiwrap", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libfcgi.so.0" + ], + "provides": [ + "cmd:fcgiwrap" + ] + }, + "cmake": { + "versions": { + "3.9.5-r0": 1509907173 + }, + "origin": "cmake", + "dependencies": [ + "so:libarchive.so.13", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libexpat.so.1", + "so:libformw.so.6", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:librhash.so.0", + "so:libstdc++.so.6", + "so:libuv.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:ccmake", + "cmd:cmake", + "cmd:cpack", + "cmd:ctest" + ] + }, + "font-bh-lucidatypewriter-100dpi": { + "versions": { + "1.0.3-r0": 1509492731 + }, + "origin": "font-bh-lucidatypewriter-100dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "gnupg1": { + "versions": { + "1.4.23-r0": 1534431039 + }, + "origin": "gnupg1", + "dependencies": [ + "pinentry", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libz.so.1" + ], + "provides": [ + "gnupg=1.4.23-r0", + "cmd:gpg", + "cmd:gpg-zip", + "cmd:gpgsplit", + "cmd:gpgv" + ] + }, + "py-funcsigs": { + "versions": { + "1.0.2-r1": 1509485908 + }, + "origin": "py-funcsigs" + }, + "perl-datetime-timezone-doc": { + "versions": { + "2.15-r0": 1511251871 + }, + "origin": "perl-datetime-timezone" + }, + "lua5.3-inspect": { + "versions": { + "3.1.0-r1": 1509491026 + }, + "origin": "lua-inspect", + "dependencies": [ + "lua5.3" + ] + }, + "libao": { + "versions": { + "1.2.0-r2": 1543926072 + }, + "origin": "libao", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libao.so.4=4.1.0" + ] + }, + "perl-mail-imapclient-doc": { + "versions": { + "3.39-r0": 1509494892 + }, + "origin": "perl-mail-imapclient" + }, + "py-talloc": { + "versions": { + "2.1.10-r0": 1509466374 + }, + "origin": "talloc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libtalloc.so.2" + ], + "provides": [ + "so:libpytalloc-util.so.2=2.1.10" + ] + }, + "sessreg-doc": { + "versions": { + "1.1.1-r0": 1509480573 + }, + "origin": "sessreg" + }, + "gawk-doc": { + "versions": { + "4.2.0-r0": 1509457020 + }, + "origin": "gawk" + }, + "libassuan": { + "versions": { + "2.4.4-r0": 1512029943 + }, + "origin": "libassuan", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgpg-error.so.0" + ], + "provides": [ + "so:libassuan.so.0=0.7.4" + ] + }, + "libxdmcp-doc": { + "versions": { + "1.1.2-r4": 1509461845 + }, + "origin": "libxdmcp" + }, + "git-bash-completion": { + "versions": { + "2.15.3-r0": 1540401292, + "2.15.4-r0": 1576015189 + }, + "origin": "git" + }, + "snownews": { + "versions": { + "1.5.12-r6": 1510260846 + }, + "origin": "snownews", + "dependencies": [ + "ncurses", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:opml2snow", + "cmd:snow2opml", + "cmd:snownews" + ] + }, + "rrdtool-utils": { + "versions": { + "1.5.6-r3": 1512296349 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "cmd:rrdcreate", + "cmd:rrdinfo", + "cmd:rrdupdate" + ] + }, + "py-irc-scripts": { + "versions": { + "8.5.1-r0": 1509491081 + }, + "origin": "py-irc" + }, + "linux-vanilla": { + "versions": { + "4.9.161-r0": 1551779592, + "4.9.182-r0": 1560866043 + }, + "origin": "linux-vanilla", + "dependencies": [ + "mkinitfs", + "linux-firmware" + ] + }, + "py-imaging": { + "versions": { + "1.1.7-r4": 1509493735 + }, + "origin": "py-imaging", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libjpeg.so.8", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:pilconvert.py", + "cmd:pildriver.py", + "cmd:pilfile.py", + "cmd:pilfont.py", + "cmd:pilprint.py" + ] + }, + "drbd-utils": { + "versions": { + "9.1.1-r0": 1511795634 + }, + "origin": "drbd-utils", + "dependencies": [ + "bash", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "drbd", + "cmd:drbd-overview", + "cmd:drbdadm", + "cmd:drbdmeta", + "cmd:drbdmon", + "cmd:drbdsetup" + ] + }, + "tdb-doc": { + "versions": { + "1.3.15-r0": 1509486505 + }, + "origin": "tdb" + }, + "wxgtk2.8": { + "versions": { + "2.8.12.1-r4": 1510928427 + }, + "origin": "wxgtk2.8", + "dependencies": [ + "so:libSDL-1.2.so.0", + "so:libSM.so.6", + "so:libXinerama.so.1", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libjpeg.so.8", + "so:libpango-1.0.so.0", + "so:libpng16.so.16", + "so:libstdc++.so.6", + "so:libtiff.so.5", + "so:libwx_baseu-2.8.so.0", + "so:libwx_baseu_xml-2.8.so.0" + ], + "provides": [ + "so:libwx_gtk2u_adv-2.8.so.0=0.8.0", + "so:libwx_gtk2u_aui-2.8.so.0=0.8.0", + "so:libwx_gtk2u_core-2.8.so.0=0.8.0", + "so:libwx_gtk2u_html-2.8.so.0=0.8.0", + "so:libwx_gtk2u_qa-2.8.so.0=0.8.0", + "so:libwx_gtk2u_richtext-2.8.so.0=0.8.0", + "so:libwx_gtk2u_xrc-2.8.so.0=0.8.0" + ] + }, + "sylpheed-dev": { + "versions": { + "3.6.0-r1": 1510588302 + }, + "origin": "sylpheed", + "dependencies": [ + "sylpheed=3.6.0-r1" + ] + }, + "freetds": { + "versions": { + "1.00.44-r0": 1509480207 + }, + "origin": "freetds", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2", + "so:libodbcinst.so.2", + "so:libreadline.so.7" + ], + "provides": [ + "so:libct.so.4=4.0.0", + "so:libsybdb.so.5=5.1.0", + "so:libtdsodbc.so.0=0.0.0", + "cmd:bsqldb", + "cmd:bsqlodbc", + "cmd:datacopy", + "cmd:defncopy", + "cmd:fisql", + "cmd:freebcp", + "cmd:osql", + "cmd:tdspool", + "cmd:tsql" + ] + }, + "collectd-ipmi": { + "versions": { + "5.7.2-r0": 1510069654 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libOpenIPMI.so.0", + "so:libOpenIPMIpthread.so.0", + "so:libc.musl-x86_64.so.1" + ] + }, + "acf-lib-lua5.2": { + "versions": { + "0.10.1-r0": 1510068294 + }, + "origin": "acf-lib" + }, + "gucharmap-dev": { + "versions": { + "3.18.2-r0": 1510074174 + }, + "origin": "gucharmap", + "dependencies": [ + "gucharmap=3.18.2-r0", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gtk+-3.0", + "pkgconfig" + ], + "provides": [ + "pc:gucharmap-2.90=3.18.2" + ] + }, + "byobu": { + "versions": { + "5.123-r1": 1509470003 + }, + "origin": "byobu", + "dependencies": [ + "python3", + "tmux" + ], + "provides": [ + "cmd:byobu", + "cmd:byobu-config", + "cmd:byobu-ctrl-a", + "cmd:byobu-disable", + "cmd:byobu-disable-prompt", + "cmd:byobu-enable", + "cmd:byobu-enable-prompt", + "cmd:byobu-export", + "cmd:byobu-janitor", + "cmd:byobu-keybindings", + "cmd:byobu-launch", + "cmd:byobu-launcher", + "cmd:byobu-launcher-install", + "cmd:byobu-launcher-uninstall", + "cmd:byobu-layout", + "cmd:byobu-prompt", + "cmd:byobu-quiet", + "cmd:byobu-reconnect-sockets", + "cmd:byobu-screen", + "cmd:byobu-select-backend", + "cmd:byobu-select-profile", + "cmd:byobu-select-session", + "cmd:byobu-shell", + "cmd:byobu-silent", + "cmd:byobu-status", + "cmd:byobu-status-detail", + "cmd:byobu-tmux", + "cmd:byobu-ugraph", + "cmd:byobu-ulevel", + "cmd:col1", + "cmd:ctail", + "cmd:manifest", + "cmd:purge-old-kernels", + "cmd:vigpg", + "cmd:wifi-status" + ] + }, + "farstream0.1-doc": { + "versions": { + "0.1.2-r2": 1510933112 + }, + "origin": "farstream0.1" + }, + "presentproto-doc": { + "versions": { + "1.1-r1": 1509466248 + }, + "origin": "presentproto" + }, + "squid-lang-lt": { + "versions": { + "3.5.27-r0": 1519824032, + "3.5.27-r1": 1562865668 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "libgnome-dev": { + "versions": { + "2.32.1-r7": 1510931516 + }, + "origin": "libgnome", + "dependencies": [ + "gtk+3.0-dev", + "gconf-dev", + "libcanberra-dev", + "gnome-vfs-dev", + "libbonobo-dev", + "libgnome=2.32.1-r7", + "pc:ORBit-2.0", + "pc:gconf-2.0", + "pc:glib-2.0", + "pc:gnome-vfs-2.0", + "pc:libbonobo-2.0", + "pc:libcanberra>=0", + "pkgconfig" + ], + "provides": [ + "pc:libgnome-2.0=2.32.1" + ] + }, + "libjpeg-turbo-utils": { + "versions": { + "1.5.3-r2": 1537879742, + "1.5.3-r3": 1563792633 + }, + "origin": "libjpeg-turbo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8", + "so:libturbojpeg.so.0" + ], + "provides": [ + "cmd:cjpeg", + "cmd:djpeg", + "cmd:jpegtran", + "cmd:rdjpgcom", + "cmd:tjbench", + "cmd:wrjpgcom" + ] + }, + "lcms": { + "versions": { + "1.19-r6": 1509477358 + }, + "origin": "lcms", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8", + "so:liblcms.so.1", + "so:libtiff.so.5" + ], + "provides": [ + "cmd:icc2ps", + "cmd:icclink", + "cmd:icctrans", + "cmd:jpegicc", + "cmd:tiffdiff", + "cmd:tifficc", + "cmd:wtpt" + ] + }, + "squid-lang-zh": { + "versions": { + "3.5.27-r0": 1519824033, + "3.5.27-r1": 1562865670 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "flac-dev": { + "versions": { + "1.3.2-r0": 1509473287 + }, + "origin": "flac", + "dependencies": [ + "flac=1.3.2-r0", + "pc:ogg", + "pkgconfig" + ], + "provides": [ + "pc:flac++=1.3.2", + "pc:flac=1.3.2" + ] + }, + "po4a-doc": { + "versions": { + "0.51-r1": 1509459568 + }, + "origin": "po4a" + }, + "libwnck-dev": { + "versions": { + "2.31.0-r5": 1510068128 + }, + "origin": "libwnck", + "dependencies": [ + "glib-dev", + "gtk+2.0-dev", + "libx11-dev", + "pango-dev", + "cairo-dev", + "startup-notification-dev", + "libxres-dev", + "libwnck=2.31.0-r5", + "pc:cairo", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:gtk+-2.0", + "pc:libstartup-notification-1.0", + "pc:pango", + "pc:x11", + "pc:xres", + "pkgconfig" + ], + "provides": [ + "pc:libwnck-1.0=2.31.0" + ] + }, + "wavpack": { + "versions": { + "5.1.0-r3": 1548940101, + "5.1.0-r4": 1566809779 + }, + "origin": "wavpack", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libwavpack.so.1=1.2.0", + "cmd:wavpack", + "cmd:wvgain", + "cmd:wvtag", + "cmd:wvunpack" + ] + }, + "perl-net-cidr": { + "versions": { + "0.17-r0": 1509486330 + }, + "origin": "perl-net-cidr", + "dependencies": [ + "perl" + ] + }, + "encfs-doc": { + "versions": { + "1.8.1-r7": 1510258715 + }, + "origin": "encfs" + }, + "ksymoops": { + "versions": { + "2.4.11-r7": 1509489730 + }, + "origin": "ksymoops", + "dependencies": [ + "so:libbfd-2.28.so", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ksymoops" + ] + }, + "v4l-utils": { + "versions": { + "1.12.5-r1": 1510072048 + }, + "origin": "v4l-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libstdc++.so.6", + "so:libudev.so.1", + "so:libv4l2.so.0", + "so:libv4l2rds.so.0" + ], + "provides": [ + "cmd:cec-compliance", + "cmd:cec-ctl", + "cmd:cec-follower", + "cmd:cx18-ctl", + "cmd:decode_tm6000", + "cmd:ir-ctl", + "cmd:ivtv-ctl", + "cmd:media-ctl", + "cmd:rds-ctl", + "cmd:v4l2-compliance", + "cmd:v4l2-ctl", + "cmd:v4l2-dbg", + "cmd:v4l2-sysfs-path" + ] + }, + "umurmur": { + "versions": { + "0.2.17-r1": 1510846249 + }, + "origin": "umurmur", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libconfig.so.9", + "so:libcrypto.so.42", + "so:libprotobuf-c.so.1", + "so:libssl.so.44" + ], + "provides": [ + "cmd:umurmurd" + ] + }, + "fontsproto-doc": { + "versions": { + "2.1.3-r2": 1509470392 + }, + "origin": "fontsproto" + }, + "mariadb-bench": { + "versions": { + "10.1.38-r1": 1551187991, + "10.1.40-r0": 1560354887, + "10.1.41-r0": 1565163111 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common" + ] + }, + "nftables": { + "versions": { + "0.8-r0": 1509496566 + }, + "origin": "nftables", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libmnl.so.0", + "so:libnftnl.so.7", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:nft" + ] + }, + "libxrandr-doc": { + "versions": { + "1.5.1-r1": 1509466001 + }, + "origin": "libxrandr" + }, + "avahi": { + "versions": { + "0.6.32-r4": 1509465450, + "0.6.32-r5": 1563345620 + }, + "origin": "avahi", + "dependencies": [ + "/bin/sh", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libdaemon.so.0", + "so:libdbus-1.so.3", + "so:libexpat.so.1" + ], + "provides": [ + "so:libavahi-core.so.7=7.0.2", + "cmd:avahi-daemon", + "cmd:avahi-dnsconfd" + ] + }, + "at-spi2-core-doc": { + "versions": { + "2.26.2-r0": 1509466052 + }, + "origin": "at-spi2-core" + }, + "gcc-doc": { + "versions": { + "6.4.0-r5": 1509458070 + }, + "origin": "gcc" + }, + "xkeyboard-config": { + "versions": { + "2.22-r0": 1509473703 + }, + "origin": "xkeyboard-config" + }, + "lua5.2-alt-getopt": { + "versions": { + "0.7-r8": 1509480076 + }, + "origin": "lua-alt-getopt" + }, + "zeromq-doc": { + "versions": { + "4.2.5-r0": 1549279390, + "4.2.5-r1": 1563908217 + }, + "origin": "zeromq" + }, + "libev-dev": { + "versions": { + "4.24-r0": 1509469568 + }, + "origin": "libev", + "dependencies": [ + "libev=4.24-r0", + "pkgconfig" + ], + "provides": [ + "pc:libev=4.24" + ] + }, + "py3-roman": { + "versions": { + "2.0.0-r2": 1509493355 + }, + "origin": "py-roman", + "dependencies": [ + "python3" + ] + }, + "kamailio-jansson": { + "versions": { + "5.0.7-r0": 1532960877 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libjansson.so.4" + ] + }, + "librevenge-doc": { + "versions": { + "0.0.4-r0": 1509495167 + }, + "origin": "librevenge" + }, + "libgpg-error": { + "versions": { + "1.27-r1": 1509459212 + }, + "origin": "libgpg-error", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgpg-error.so.0=0.22.0", + "cmd:gpg-error" + ] + }, + "gengetopt-dev": { + "versions": { + "2.22.6-r2": 1509488506 + }, + "origin": "gengetopt", + "dependencies": [ + "gengetopt" + ] + }, + "binutils-gold": { + "versions": { + "2.30-r1": 1527754981, + "2.30-r2": 1565788943 + }, + "origin": "binutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:ld.gold" + ] + }, + "gstreamer-dev": { + "versions": { + "1.12.3-r0": 1509470920 + }, + "origin": "gstreamer", + "dependencies": [ + "libxml2-dev", + "gstreamer=1.12.3-r0", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gstreamer-1.0=1.12.3", + "pc:gstreamer-base-1.0=1.12.3", + "pc:gstreamer-check-1.0=1.12.3", + "pc:gstreamer-controller-1.0=1.12.3", + "pc:gstreamer-net-1.0=1.12.3" + ] + }, + "renderproto": { + "versions": { + "0.11.1-r3": 1509462089 + }, + "origin": "renderproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:renderproto=0.11.1" + ] + }, + "mosquitto-libs": { + "versions": { + "1.4.15-r0": 1520176488, + "1.4.15-r2": 1563346324 + }, + "origin": "mosquitto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "so:libmosquitto.so.1=1" + ] + }, + "libc-utils": { + "versions": { + "0.7.1-r0": 1509458194 + }, + "origin": "libc-dev", + "dependencies": [ + "musl-utils" + ] + }, + "lvm2-dmeventd": { + "versions": { + "2.02.175-r0": 1509462402 + }, + "origin": "lvm2", + "dependencies": [ + "lvm2-libs=2.02.175-r0", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper-event.so.1.02", + "so:libdevmapper.so.1.02" + ], + "provides": [ + "cmd:dmeventd" + ] + }, + "xfwm4": { + "versions": { + "4.12.4-r1": 1510074449 + }, + "origin": "xfwm4", + "dependencies": [ + "hicolor-icon-theme", + "so:libX11.so.6", + "so:libXcomposite.so.1", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXrandr.so.2", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdbus-glib-1.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libstartup-notification-1.so.0", + "so:libwnck-1.so.22", + "so:libxfce4kbd-private-2.so.0", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:xfwm4", + "cmd:xfwm4-settings", + "cmd:xfwm4-tweaks-settings", + "cmd:xfwm4-workspace-settings" + ] + }, + "libsigc++-doc": { + "versions": { + "2.10.0-r1": 1509472940 + }, + "origin": "libsigc++" + }, + "nginx-mod-rtmp": { + "versions": { + "1.12.2-r4": 1542814448 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "nginx-rtmp" + ] + }, + "perl-dev": { + "versions": { + "5.26.3-r0": 1543940997 + }, + "origin": "perl", + "dependencies": [ + "perl-utils" + ], + "provides": [ + "cmd:enc2xs", + "cmd:h2xs", + "cmd:perlivp", + "cmd:xsubpp" + ] + }, + "libksba": { + "versions": { + "1.3.5-r0": 1509472891 + }, + "origin": "libksba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgpg-error.so.0" + ], + "provides": [ + "so:libksba.so.8=8.11.6" + ] + }, + "font-adobe-75dpi": { + "versions": { + "1.0.3-r0": 1509495298 + }, + "origin": "font-adobe-75dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "portaudio": { + "versions": { + "19-r1": 1509473346 + }, + "origin": "portaudio", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libjack.so.0" + ], + "provides": [ + "so:libportaudio.so.2=2.0.0" + ] + }, + "libuv": { + "versions": { + "1.17.0-r0": 1511652082 + }, + "origin": "libuv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libuv.so.1=1.0.0" + ] + }, + "zfs-hardened-dev": { + "versions": { + "4.9.65-r1": 1511799189 + }, + "origin": "zfs-hardened", + "dependencies": [ + "glib-dev", + "e2fsprogs-dev", + "util-linux-dev", + "libtirpc-dev", + "linux-hardened-dev=4.9.65-r1", + "spl-hardened-dev" + ] + }, + "libotr-dev": { + "versions": { + "4.1.1-r1": 1509473948 + }, + "origin": "libotr", + "dependencies": [ + "libgcrypt-dev", + "libotr=4.1.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:libotr=4.1.1" + ] + }, + "gtk-doc": { + "versions": { + "1.26-r2": 1509628913 + }, + "origin": "gtk-doc", + "dependencies": [ + "docbook-xsl", + "gnome-doc-utils", + "perl", + "python2", + "py2-six", + "pkgconf", + "glib-dev", + "highlight" + ], + "provides": [ + "cmd:gtkdoc-check", + "cmd:gtkdoc-depscan", + "cmd:gtkdoc-fixxref", + "cmd:gtkdoc-mkdb", + "cmd:gtkdoc-mkhtml", + "cmd:gtkdoc-mkman", + "cmd:gtkdoc-mkpdf", + "cmd:gtkdoc-rebase", + "cmd:gtkdoc-scan", + "cmd:gtkdoc-scangobj", + "cmd:gtkdocize" + ] + }, + "openbox-doc": { + "versions": { + "3.6.1-r1": 1510073884 + }, + "origin": "openbox" + }, + "perl-module-implementation-doc": { + "versions": { + "0.09-r0": 1509470579 + }, + "origin": "perl-module-implementation" + }, + "qt-mysql": { + "versions": { + "4.8.7-r8": 1510287209 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtSql.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libmysqlclient.so.18", + "so:libstdc++.so.6" + ] + }, + "gtkmm-doc": { + "versions": { + "2.24.5-r0": 1510073814 + }, + "origin": "gtkmm" + }, + "openvswitch-dbg": { + "versions": { + "2.8.1-r2": 1512030506 + }, + "origin": "openvswitch" + }, + "wavpack-dev": { + "versions": { + "5.1.0-r3": 1548940101, + "5.1.0-r4": 1566809779 + }, + "origin": "wavpack", + "dependencies": [ + "pkgconfig", + "wavpack=5.1.0-r4" + ], + "provides": [ + "pc:wavpack=5.1.0" + ] + }, + "at-spi2-core": { + "versions": { + "2.26.2-r0": 1509466052 + }, + "origin": "at-spi2-core", + "dependencies": [ + "so:libX11.so.6", + "so:libXtst.so.6", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libatspi.so.0=0.0.1" + ] + }, + "perl-crypt-openssl-random-doc": { + "versions": { + "0.11-r4": 1510260064 + }, + "origin": "perl-crypt-openssl-random" + }, + "lxpolkit": { + "versions": { + "0.1.0-r1": 1510076430 + }, + "origin": "lxpolkit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpolkit-agent-1.so.0", + "so:libpolkit-gobject-1.so.0" + ] + }, + "bitchx-doc": { + "versions": { + "1.2.1-r7": 1510260133 + }, + "origin": "bitchx" + }, + "libxcursor-doc": { + "versions": { + "1.1.15-r0": 1511975691 + }, + "origin": "libxcursor" + }, + "py-exifread": { + "versions": { + "2.1.2-r1": 1509552778 + }, + "origin": "py-exifread", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:EXIF.py" + ] + }, + "spamassassin-doc": { + "versions": { + "3.4.1-r8": 1509517411 + }, + "origin": "spamassassin" + }, + "py-vobject": { + "versions": { + "0.9.5-r2": 1510832515 + }, + "origin": "py-vobject", + "provides": [ + "cmd:change_tz", + "cmd:ics_diff" + ] + }, + "ipfw": { + "versions": { + "3.0_git20130607-r1": 1509495344 + }, + "origin": "ipfw", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ipfw" + ] + }, + "lua5.1-optarg": { + "versions": { + "0.2-r1": 1509462489 + }, + "origin": "lua-optarg", + "dependencies": [ + "lua5.1" + ] + }, + "gparted-lang": { + "versions": { + "0.30.0-r0": 1510073982 + }, + "origin": "gparted", + "dependencies": [ + "e2fsprogs", + "ntfs-3g-progs" + ] + }, + "squark": { + "versions": { + "0.6.1-r1": 1509492126 + }, + "origin": "squark", + "dependencies": [ + "haserl", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcmph.so.0", + "so:liblua.so.5", + "so:libnetsnmp.so.30" + ], + "provides": [ + "cmd:sqdb-build.lua", + "cmd:squark-auth-ip", + "cmd:squark-auth-snmp", + "cmd:squark-filter" + ] + }, + "perl-async-mergepoint-doc": { + "versions": { + "0.04-r0": 1509494969 + }, + "origin": "perl-async-mergepoint" + }, + "libtirpc-dbg": { + "versions": { + "1.0.1-r2": 1509469768 + }, + "origin": "libtirpc" + }, + "rp-pppoe-doc": { + "versions": { + "3.12-r1": 1541067520 + }, + "origin": "rp-pppoe" + }, + "libee-dev": { + "versions": { + "0.4.1-r0": 1509486165 + }, + "origin": "libee", + "dependencies": [ + "libee=0.4.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libee=0.4.1" + ] + }, + "hvtools": { + "versions": { + "4.11.9-r0": 1509489575 + }, + "origin": "hvtools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:hv_fcopy_daemon", + "cmd:hv_kvp_daemon", + "cmd:hv_vss_daemon" + ] + }, + "py2-sphinxcontrib-websupport": { + "versions": { + "1.0.1-r3": 1509493319 + }, + "origin": "py-sphinxcontrib-websupport", + "dependencies": [ + "python2" + ] + }, + "qemu-s390x": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-s390x" + ] + }, + "lua5.1-curl": { + "versions": { + "0.3.7-r1": 1509493017 + }, + "origin": "lua-curl", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "py2-cryptography": { + "versions": { + "2.0.3-r1": 1510288192, + "2.1.4-r0": 1559543210 + }, + "origin": "py-cryptography", + "dependencies": [ + "py2-cffi", + "py2-idna", + "py2-asn1crypto", + "py2-six", + "py2-ipaddress", + "py-enum34", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpython2.7.so.1.0", + "so:libssl.so.44" + ] + }, + "xfce4-cpugraph-plugin-lang": { + "versions": { + "1.0.5-r1": 1510074086 + }, + "origin": "xfce4-cpugraph-plugin" + }, + "frotz-doc": { + "versions": { + "2.44-r0": 1509491523 + }, + "origin": "frotz" + }, + "ckbcomp": { + "versions": { + "1.170-r0": 1510922360 + }, + "origin": "ckbcomp", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:ckbcomp" + ] + }, + "xf86-video-modesetting": { + "versions": { + "0.9.0-r6": 1510074885 + }, + "origin": "xf86-video-modesetting", + "dependencies": [ + "xorg-server" + ] + }, + "perl-probe-perl": { + "versions": { + "0.03-r0": 1509482467 + }, + "origin": "perl-probe-perl" + }, + "strace-doc": { + "versions": { + "4.19-r0": 1509477631 + }, + "origin": "strace" + }, + "galculator-lang": { + "versions": { + "2.1.4-r0": 1510075353 + }, + "origin": "galculator" + }, + "py-unbound": { + "versions": { + "1.6.7-r1": 1510588258 + }, + "origin": "unbound", + "dependencies": [ + "dnssec-root", + "so:libc.musl-x86_64.so.1", + "so:libunbound.so.2" + ] + }, + "procmail-doc": { + "versions": { + "3.22-r3": 1513159329 + }, + "origin": "procmail" + }, + "gvfs-doc": { + "versions": { + "1.34.1-r0": 1511430257, + "1.34.1-r1": 1563787223 + }, + "origin": "gvfs" + }, + "gdbm-dev": { + "versions": { + "1.13-r1": 1509458649 + }, + "origin": "gdbm", + "dependencies": [ + "gdbm=1.13-r1" + ] + }, + "qemu-or1k": { + "versions": { + "2.10.1-r3": 1519746240 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-or1k" + ] + }, + "qt-sqlite": { + "versions": { + "4.8.7-r8": 1510287208 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtSql.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libsqlite3.so.0", + "so:libstdc++.so.6" + ] + }, + "libmemcached": { + "versions": { + "1.0.18-r2": 1520366185 + }, + "origin": "libmemcached", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libmemcached.so.11", + "so:libmemcachedutil.so.2", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:memcapable", + "cmd:memcat", + "cmd:memcp", + "cmd:memdump", + "cmd:memerror", + "cmd:memexist", + "cmd:memflush", + "cmd:memparse", + "cmd:memping", + "cmd:memrm", + "cmd:memslap", + "cmd:memstat", + "cmd:memtouch" + ] + }, + "the_silver_searcher": { + "versions": { + "2.1.0-r2": 1510831171 + }, + "origin": "the_silver_searcher", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblzma.so.5", + "so:libpcre.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:ag" + ] + }, + "freetype-doc": { + "versions": { + "2.8.1-r4": 1549555370 + }, + "origin": "freetype" + }, + "polkit-doc": { + "versions": { + "0.105-r8": 1551780676, + "0.105-r9": 1563792450, + "0.105-r10": 1567523133 + }, + "origin": "polkit" + }, + "xcb-util-cursor-dev": { + "versions": { + "0.1.3-r1": 1509491136 + }, + "origin": "xcb-util-cursor", + "dependencies": [ + "xcb-util-dev", + "pc:xcb-image", + "pc:xcb-render", + "pc:xcb-renderutil", + "pkgconfig", + "xcb-util-cursor=0.1.3-r1" + ], + "provides": [ + "pc:xcb-cursor=0.1.3" + ] + }, + "libnl-dev": { + "versions": { + "1.1.4-r0": 1509473007 + }, + "origin": "libnl", + "dependencies": [ + "libnl=1.1.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnl-1=1.1.4" + ] + }, + "skalibs-doc": { + "versions": { + "2.6.1.0-r0": 1510364877 + }, + "origin": "skalibs" + }, + "libverto": { + "versions": { + "0.3.0-r0": 1509469587 + }, + "origin": "libverto", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libverto.so.1=1.0.0" + ] + }, + "spice-protocol": { + "versions": { + "0.12.14-r0": 1541599121 + }, + "origin": "spice-protocol", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:spice-protocol=0.12.14" + ] + }, + "py2-pip": { + "versions": { + "9.0.1-r1": 1509493196 + }, + "origin": "py2-pip", + "dependencies": [ + "python2", + "py-setuptools" + ], + "provides": [ + "py-pip=9.0.1-r1", + "cmd:pip", + "cmd:pip2", + "cmd:pip2.7" + ] + }, + "motif-doc": { + "versions": { + "2.3.4-r2": 1509495442 + }, + "origin": "motif" + }, + "lua5.1-socket": { + "versions": { + "3.0_rc1_git20160306-r2": 1509480759 + }, + "origin": "lua-socket", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-text-vfile-asdata": { + "versions": { + "0.08-r0": 1509474110 + }, + "origin": "perl-text-vfile-asdata", + "dependencies": [ + "perl-class-accessor-chained" + ] + }, + "giblib-dev": { + "versions": { + "1.2.4-r8": 1509470529 + }, + "origin": "giblib", + "dependencies": [ + "imlib2-dev", + "freetype-dev", + "zlib-dev", + "libx11-dev", + "libxext-dev", + "giblib=1.2.4-r8", + "pkgconfig" + ], + "provides": [ + "pc:giblib=1.2.4", + "cmd:giblib-config" + ] + }, + "font-bitstream-speedo": { + "versions": { + "1.0.2-r0": 1509475953 + }, + "origin": "font-bitstream-speedo", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-font-afm": { + "versions": { + "1.20-r0": 1509481558 + }, + "origin": "perl-font-afm" + }, + "libnfsidmap-doc": { + "versions": { + "0.25-r1": 1509492064 + }, + "origin": "libnfsidmap" + }, + "coreutils-doc": { + "versions": { + "8.28-r0": 1509462378 + }, + "origin": "coreutils" + }, + "perl-file-next-doc": { + "versions": { + "1.16-r0": 1509491909 + }, + "origin": "perl-file-next" + }, + "libotr-tools": { + "versions": { + "4.1.1-r1": 1509473951 + }, + "origin": "libotr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libotr.so.5" + ], + "provides": [ + "cmd:otr_mackey", + "cmd:otr_modify", + "cmd:otr_parse", + "cmd:otr_readforge", + "cmd:otr_remac", + "cmd:otr_sesskeys" + ] + }, + "py-cryptography": { + "versions": { + "2.0.3-r1": 1510288193, + "2.1.4-r0": 1559543210 + }, + "origin": "py-cryptography", + "dependencies": [ + "py-cffi", + "py-idna", + "py-asn1crypto", + "py-six" + ] + }, + "charybdis-dev": { + "versions": { + "3.5.5-r3": 1510260651 + }, + "origin": "charybdis", + "dependencies": [ + "pkgconfig" + ] + }, + "gtk+3.0-doc": { + "versions": { + "3.22.21-r0": 1510067779 + }, + "origin": "gtk+3.0" + }, + "libxxf86vm": { + "versions": { + "1.1.4-r1": 1509466237 + }, + "origin": "libxxf86vm", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXxf86vm.so.1=1.0.0" + ] + }, + "perl-params-validate": { + "versions": { + "1.28-r0": 1509470588 + }, + "origin": "perl-params-validate", + "dependencies": [ + "perl-module-implementation", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-html-scrubber": { + "versions": { + "0.11-r0": 1509481325 + }, + "origin": "perl-html-scrubber", + "dependencies": [ + "perl-html-parser" + ] + }, + "net-tools-doc": { + "versions": { + "1.60_git20140218-r1": 1509492734 + }, + "origin": "net-tools" + }, + "py-certifi": { + "versions": { + "2017.7.27.1-r1": 1509551896 + }, + "origin": "py-certifi" + }, + "perl-variable-magic": { + "versions": { + "0.62-r0": 1510087954 + }, + "origin": "perl-variable-magic", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.2-microlight": { + "versions": { + "1.1.1-r2": 1509480784 + }, + "origin": "lua-microlight" + }, + "ipsec-tools": { + "versions": { + "0.8.2-r6": 1510261510 + }, + "origin": "ipsec-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ], + "provides": [ + "cmd:plainrsa-gen", + "cmd:racoon", + "cmd:racoonctl", + "cmd:setkey" + ] + }, + "libgit2": { + "versions": { + "0.25.1-r4": 1510288231 + }, + "origin": "libgit2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libssh2.so.1", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "libgit2-libs", + "so:libgit2.so.25=0.25.1" + ] + }, + "freeswitch-sounds-en-us-callie-32000": { + "versions": { + "1.0.16-r1": 1509482236 + }, + "origin": "freeswitch-sounds-en-us-callie-32000" + }, + "lua5.2-expat": { + "versions": { + "1.3.0-r2": 1509483367 + }, + "origin": "lua-expat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ] + }, + "talloc-dev": { + "versions": { + "2.1.10-r0": 1509466374 + }, + "origin": "talloc", + "dependencies": [ + "pkgconfig", + "py-talloc=2.1.10-r0", + "talloc=2.1.10-r0" + ], + "provides": [ + "pc:pytalloc-util=2.1.10", + "pc:talloc=2.1.10" + ] + }, + "libxdmcp-dev": { + "versions": { + "1.1.2-r4": 1509461845 + }, + "origin": "libxdmcp", + "dependencies": [ + "libxdmcp=1.1.2-r4", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xdmcp=1.1.2" + ] + }, + "open-vm-tools-dbg": { + "versions": { + "10.1.15-r0": 1510922342 + }, + "origin": "open-vm-tools" + }, + "lua": { + "versions": { + "5.1.5-r4": 1509471882 + }, + "origin": "lua", + "dependencies": [ + "lua5.1" + ] + }, + "squid-lang-vi": { + "versions": { + "3.5.27-r0": 1519824033, + "3.5.27-r1": 1562865670 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "py2-cairo-dev": { + "versions": { + "1.10.0-r0": 1509471033 + }, + "origin": "py2-cairo", + "dependencies": [ + "pc:cairo", + "pkgconfig" + ], + "provides": [ + "pc:pycairo=1.10.0" + ] + }, + "geoip-doc": { + "versions": { + "1.6.11-r0": 1509474613 + }, + "origin": "geoip" + }, + "libmemcached-dev": { + "versions": { + "1.0.18-r2": 1520366184 + }, + "origin": "libmemcached", + "dependencies": [ + "cyrus-sasl-dev", + "libmemcached-libs=1.0.18-r2", + "pkgconfig" + ], + "provides": [ + "pc:libmemcached=1.0.18" + ] + }, + "perl-stream-buffered": { + "versions": { + "0.02-r1": 1510564489 + }, + "origin": "perl-stream-buffered" + }, + "lua5.1-pc": { + "versions": { + "1.0.0-r9": 1509480085 + }, + "origin": "lua-pc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "subversion-doc": { + "versions": { + "1.9.7-r0": 1510314500, + "1.9.12-r0": 1565086191 + }, + "origin": "subversion" + }, + "mkfontdir": { + "versions": { + "1.0.7-r1": 1509469874 + }, + "origin": "mkfontdir", + "dependencies": [ + "mkfontscale", + "/bin/sh" + ], + "provides": [ + "cmd:mkfontdir" + ] + }, + "keyutils-dev": { + "versions": { + "1.5.10-r0": 1509469693 + }, + "origin": "keyutils", + "dependencies": [ + "keyutils-libs=1.5.10-r0" + ] + }, + "lua5.2-mqtt-publish": { + "versions": { + "0.1-r0": 1509462502 + }, + "origin": "lua-mqtt-publish", + "dependencies": [ + "lua5.2-mosquitto" + ] + }, + "dhcpcd-dbus": { + "versions": { + "0.6.1-r3": 1509486340 + }, + "origin": "dhcpcd-dbus", + "dependencies": [ + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3" + ] + }, + "perl-libwww": { + "versions": { + "6.29-r0": 1510038531 + }, + "origin": "perl-libwww", + "dependencies": [ + "perl-http-date", + "perl-http-cookies", + "perl-net-http", + "perl-http-daemon", + "perl-html-parser", + "perl-file-listing", + "perl-www-robotrules", + "perl-http-negotiate", + "perl-uri", + "perl-http-message", + "perl-lwp-mediatypes", + "perl-encode-locale", + "perl-try-tiny" + ], + "provides": [ + "cmd:lwp-download", + "cmd:lwp-dump", + "cmd:lwp-mirror", + "cmd:lwp-request" + ] + }, + "collectd-write_redis": { + "versions": { + "5.7.2-r0": 1510069651 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.13" + ] + }, + "zsh-zftp": { + "versions": { + "5.4.2-r1": 1522503663 + }, + "origin": "zsh", + "dependencies": [ + "zsh", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-mime-lite": { + "versions": { + "3.030-r1": 1509495013 + }, + "origin": "perl-mime-lite", + "dependencies": [ + "perl", + "perl-mime-types", + "perl-email-date-format", + "perl-mailtools", + "perl-test-pod-coverage" + ] + }, + "arpwatch": { + "versions": { + "2.1a15-r16": 1510075585 + }, + "origin": "arpwatch", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:arp2ethers", + "cmd:arpfetch", + "cmd:arpsnmp", + "cmd:arpwatch", + "cmd:bihourly.sh", + "cmd:massagevendor" + ] + }, + "open-vm-tools": { + "versions": { + "10.1.15-r0": 1510922342 + }, + "origin": "open-vm-tools", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libfuse.so.2", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libicui18n.so.59", + "so:libicuuc.so.59", + "so:libintl.so.8", + "so:libmspack.so.0", + "so:libssl.so.44", + "so:libtirpc.so.3" + ], + "provides": [ + "so:libDeployPkg.so.0=0.0.0", + "so:libguestlib.so.0=0.0.0", + "so:libhgfs.so.0=0.0.0", + "so:libvmtools.so.0=0.0.0", + "cmd:mount.vmhgfs", + "cmd:vm-support", + "cmd:vmhgfs-fuse", + "cmd:vmtoolsd", + "cmd:vmware-checkvm", + "cmd:vmware-guestproxycerttool", + "cmd:vmware-hgfsclient", + "cmd:vmware-namespace-cmd", + "cmd:vmware-rpctool", + "cmd:vmware-toolbox-cmd", + "cmd:vmware-xferlogs" + ] + }, + "libpng-utils": { + "versions": { + "1.6.37-r0": 1557132258 + }, + "origin": "libpng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpng16.so.16", + "so:libz.so.1" + ], + "provides": [ + "cmd:png-fix-itxt", + "cmd:pngfix" + ] + }, + "startup-notification-dev": { + "versions": { + "0.12-r3": 1509468553 + }, + "origin": "startup-notification", + "dependencies": [ + "libsm-dev", + "xcb-util-dev", + "pkgconfig", + "startup-notification=0.12-r3" + ], + "provides": [ + "pc:libstartup-notification-1.0=0.12" + ] + }, + "quagga-doc": { + "versions": { + "1.2.4-r0": 1519133992 + }, + "origin": "quagga" + }, + "sg3_utils": { + "versions": { + "1.42-r0": 1509482219 + }, + "origin": "sg3_utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsgutils2.so.2=2.0.0", + "cmd:scsi_logging_level", + "cmd:scsi_mandat", + "cmd:scsi_readcap", + "cmd:scsi_ready", + "cmd:scsi_satl", + "cmd:scsi_start", + "cmd:scsi_stop", + "cmd:scsi_temperature", + "cmd:sg_compare_and_write", + "cmd:sg_copy_results", + "cmd:sg_dd", + "cmd:sg_decode_sense", + "cmd:sg_emc_trespass", + "cmd:sg_format", + "cmd:sg_get_config", + "cmd:sg_get_lba_status", + "cmd:sg_ident", + "cmd:sg_inq", + "cmd:sg_logs", + "cmd:sg_luns", + "cmd:sg_map", + "cmd:sg_map26", + "cmd:sg_modes", + "cmd:sg_opcodes", + "cmd:sg_persist", + "cmd:sg_prevent", + "cmd:sg_raw", + "cmd:sg_rbuf", + "cmd:sg_rdac", + "cmd:sg_read", + "cmd:sg_read_attr", + "cmd:sg_read_block_limits", + "cmd:sg_read_buffer", + "cmd:sg_read_long", + "cmd:sg_readcap", + "cmd:sg_reassign", + "cmd:sg_referrals", + "cmd:sg_rep_zones", + "cmd:sg_requests", + "cmd:sg_reset", + "cmd:sg_reset_wp", + "cmd:sg_rmsn", + "cmd:sg_rtpg", + "cmd:sg_safte", + "cmd:sg_sanitize", + "cmd:sg_sat_identify", + "cmd:sg_sat_phy_event", + "cmd:sg_sat_read_gplog", + "cmd:sg_sat_set_features", + "cmd:sg_scan", + "cmd:sg_senddiag", + "cmd:sg_ses", + "cmd:sg_ses_microcode", + "cmd:sg_start", + "cmd:sg_stpg", + "cmd:sg_sync", + "cmd:sg_test_rwbuf", + "cmd:sg_timestamp", + "cmd:sg_turs", + "cmd:sg_unmap", + "cmd:sg_verify", + "cmd:sg_vpd", + "cmd:sg_wr_mode", + "cmd:sg_write_buffer", + "cmd:sg_write_long", + "cmd:sg_write_same", + "cmd:sg_write_verify", + "cmd:sg_xcopy", + "cmd:sg_zone", + "cmd:sginfo", + "cmd:sgm_dd", + "cmd:sgp_dd" + ] + }, + "perl-html-quoted-doc": { + "versions": { + "0.04-r0": 1511989791 + }, + "origin": "perl-html-quoted" + }, + "perl-file-temp": { + "versions": { + "0.2304-r0": 1509495373 + }, + "origin": "perl-file-temp" + }, + "perl-css-minifier-xs": { + "versions": { + "0.09-r3": 1509489144 + }, + "origin": "perl-css-minifier-xs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "aaudit": { + "versions": { + "0.7.2-r1": 1510074394 + }, + "origin": "aaudit", + "dependencies": [ + "lua5.2", + "lua5.2-posix", + "lua5.2-cjson", + "lua5.2-pc", + "lua5.2-socket" + ], + "provides": [ + "cmd:aaudit" + ] + }, + "qjson": { + "versions": { + "0.9.0-r0": 1510075393 + }, + "origin": "qjson", + "dependencies": [ + "so:libQtCore.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libqjson.so.0=0.9.0" + ] + }, + "libfontenc-dev": { + "versions": { + "1.1.3-r2": 1509469860 + }, + "origin": "libfontenc", + "dependencies": [ + "libfontenc=1.1.3-r2", + "pkgconfig" + ], + "provides": [ + "pc:fontenc=1.1.3" + ] + }, + "cmocka": { + "versions": { + "1.1.1-r1": 1509476798 + }, + "origin": "cmocka", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcmocka.so.0=0.4.1" + ] + }, + "lsof": { + "versions": { + "4.89-r1": 1510330781 + }, + "origin": "lsof", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:lsof" + ] + }, + "openldap-backend-all": { + "versions": { + "2.4.45-r3": 1510258132, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900515 + }, + "origin": "openldap", + "dependencies": [ + "openldap-back-bdb", + "openldap-back-dnssrv", + "openldap-back-hdb", + "openldap-back-ldap", + "openldap-back-mdb", + "openldap-back-meta", + "openldap-back-monitor", + "openldap-back-null", + "openldap-back-passwd", + "openldap-back-relay", + "openldap-back-shell", + "openldap-back-sql", + "openldap-back-sock" + ] + }, + "desktop-file-utils-doc": { + "versions": { + "0.23-r0": 1509470840 + }, + "origin": "desktop-file-utils" + }, + "perl-crypt-x509": { + "versions": { + "0.51-r0": 1509480699 + }, + "origin": "perl-crypt-x509", + "dependencies": [ + "perl-convert-asn1" + ] + }, + "links-doc": { + "versions": { + "2.14-r2": 1510259134 + }, + "origin": "links" + }, + "acct-doc": { + "versions": { + "6.6.4-r0": 1509462238 + }, + "origin": "acct" + }, + "abiword-plugin-freetranslation": { + "versions": { + "3.0.2-r1": 1510073359 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "xfce4-clipman-plugin": { + "versions": { + "1.4.2-r0": 1510073453 + }, + "origin": "xfce4-clipman-plugin", + "dependencies": [ + "so:libX11.so.6", + "so:libXtst.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libxfce4panel-2.0.so.4", + "so:libxfce4ui-2.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "cmd:xfce4-clipman", + "cmd:xfce4-clipman-settings", + "cmd:xfce4-popup-clipman", + "cmd:xfce4-popup-clipman-actions" + ] + }, + "gtk-engines-redmond": { + "versions": { + "2.21.0-r2": 1510071724 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "pidgin-sipe": { + "versions": { + "1.22.1-r0": 1510931372 + }, + "origin": "pidgin-sipe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmime-2.6.so.0", + "so:libgobject-2.0.so.0", + "so:libgssapi.so.3", + "so:libintl.so.8", + "so:libnspr4.so", + "so:libnss3.so", + "so:libpurple.so.0", + "so:libsmime3.so", + "so:libxml2.so.2" + ] + }, + "mt-st": { + "versions": { + "1.1-r4": 1509496051 + }, + "origin": "mt-st", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mt", + "cmd:stinit" + ] + }, + "ppp": { + "versions": { + "2.4.7-r5": 1509480143 + }, + "origin": "ppp", + "dependencies": [ + "ppp-chat", + "ppp-radius", + "ppp-atm", + "ppp-pppoe", + "ppp-l2tp", + "ppp-winbind", + "ppp-passprompt", + "ppp-passwordfd", + "ppp-minconn", + "ppp-daemon" + ] + }, + "nagios-plugins-mailq": { + "versions": { + "2.2.1-r3": 1510288496 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins" + ] + }, + "clutter-doc": { + "versions": { + "1.26.2-r1": 1510073008 + }, + "origin": "clutter" + }, + "py2-backports_abc": { + "versions": { + "0.4-r3": 1509552781 + }, + "origin": "py-backports_abc", + "dependencies": [ + "python2" + ] + }, + "py-subversion": { + "versions": { + "1.9.7-r0": 1510314503, + "1.9.12-r0": 1565086191 + }, + "origin": "subversion", + "dependencies": [ + "so:libapr-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libsvn_client-1.so.0", + "so:libsvn_delta-1.so.0", + "so:libsvn_diff-1.so.0", + "so:libsvn_fs-1.so.0", + "so:libsvn_ra-1.so.0", + "so:libsvn_repos-1.so.0", + "so:libsvn_subr-1.so.0", + "so:libsvn_wc-1.so.0" + ], + "provides": [ + "so:libsvn_swig_py-1.so.0=0.0.0" + ] + }, + "libxfont-dev": { + "versions": { + "1.5.4-r0": 1511976122 + }, + "origin": "libxfont", + "dependencies": [ + "libxfont=1.5.4-r0", + "pc:fontenc", + "pc:fontsproto", + "pc:freetype2", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xfont=1.5.4" + ] + }, + "qemu-system-aarch64": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libstdc++.so.6", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-aarch64" + ] + }, + "py3-click": { + "versions": { + "6.7-r2": 1509476499 + }, + "origin": "py-click", + "dependencies": [ + "python3" + ] + }, + "py-docutils-doc": { + "versions": { + "0.13.1-r0": 1509486247 + }, + "origin": "py-docutils", + "dependencies": [ + "py3-docutils" + ] + }, + "unifont": { + "versions": { + "9.0.06-r0": 1509491375 + }, + "origin": "unifont", + "dependencies": [ + "bdftopcf", + "perl-gd", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bdfimplode", + "cmd:hex2bdf", + "cmd:hex2sfd", + "cmd:hexbraille", + "cmd:hexdraw", + "cmd:hexkinya", + "cmd:hexmerge", + "cmd:johab2ucs2", + "cmd:unibdf2hex", + "cmd:unibmp2hex", + "cmd:unicoverage", + "cmd:unidup", + "cmd:unifont-viewer", + "cmd:unifontchojung", + "cmd:unifontksx", + "cmd:unifontpic", + "cmd:unigencircles", + "cmd:unigenwidth", + "cmd:unihex2bmp", + "cmd:unihex2png", + "cmd:unihexfill", + "cmd:unihexgen", + "cmd:unipagecount", + "cmd:unipng2hex" + ] + }, + "dnsmasq": { + "versions": { + "2.78-r3": 1540537793 + }, + "origin": "dnsmasq", + "dependencies": [ + "!dnsmasq-dnssec", + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dnsmasq" + ] + }, + "lynx-doc": { + "versions": { + "2.8.8_p2-r6": 1510842477 + }, + "origin": "lynx" + }, + "py-gtk-doc": { + "versions": { + "2.24.0-r14": 1510071866 + }, + "origin": "py-gtk" + }, + "augeas": { + "versions": { + "1.9.0-r3": 1511899746 + }, + "origin": "augeas", + "dependencies": [ + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1", + "so:libfa.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:augparse", + "cmd:augtool", + "cmd:fadot" + ] + }, + "python3-tests": { + "versions": { + "3.6.8-r0": 1555928808, + "3.6.9-r0": 1571233994, + "3.6.9-r1": 1571314638 + }, + "origin": "python3" + }, + "qemu-system-mips64el": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-mips64el" + ] + }, + "jsoncpp-dev": { + "versions": { + "1.8.1-r1": 1510311447 + }, + "origin": "jsoncpp", + "dependencies": [ + "jsoncpp=1.8.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:jsoncpp=1.8.1" + ] + }, + "perl-devel-stacktrace": { + "versions": { + "2.02-r0": 1509468385 + }, + "origin": "perl-devel-stacktrace" + }, + "py3-pylast": { + "versions": { + "1.9.0-r0": 1509491030 + }, + "origin": "py-pylast", + "dependencies": [ + "python3" + ] + }, + "pango": { + "versions": { + "1.40.14-r1": 1541519450 + }, + "origin": "pango", + "dependencies": [ + "/bin/sh", + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libharfbuzz.so.0" + ], + "provides": [ + "so:libpango-1.0.so.0=0.4000.14", + "so:libpangocairo-1.0.so.0=0.4000.14", + "so:libpangoft2-1.0.so.0=0.4000.14", + "so:libpangoxft-1.0.so.0=0.4000.14", + "cmd:pango-view" + ] + }, + "openrc": { + "versions": { + "0.24.1-r4": 1511887835 + }, + "origin": "openrc", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libeinfo.so.1=1", + "so:librc.so.1=1", + "cmd:openrc", + "cmd:openrc-run", + "cmd:rc", + "cmd:rc-service", + "cmd:rc-sstat", + "cmd:rc-status", + "cmd:rc-update", + "cmd:runscript", + "cmd:service", + "cmd:start-stop-daemon", + "cmd:supervise-daemon" + ] + }, + "apache2-ctl": { + "versions": { + "2.4.39-r0": 1554306880, + "2.4.41-r0": 1566292326 + }, + "origin": "apache2", + "dependencies": [ + "lynx" + ], + "provides": [ + "cmd:apachectl" + ] + }, + "perl-dbix-searchbuilder-doc": { + "versions": { + "1.67-r1": 1510588867 + }, + "origin": "perl-dbix-searchbuilder" + }, + "perl-error": { + "versions": { + "0.17025-r0": 1509461799 + }, + "origin": "perl-error", + "dependencies": [ + "perl" + ] + }, + "clang": { + "versions": { + "5.0.0-r0": 1510683247 + }, + "origin": "clang", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libclang.so.5.0", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:c-index-test", + "cmd:clang", + "cmd:clang++", + "cmd:clang-5.0", + "cmd:clang-check", + "cmd:clang-cl", + "cmd:clang-cpp", + "cmd:clang-format", + "cmd:clang-import-test", + "cmd:clang-offload-bundler", + "cmd:clang-rename", + "cmd:git-clang-format" + ] + }, + "wayland-protocols": { + "versions": { + "1.11-r0": 1510088213 + }, + "origin": "wayland-protocols", + "provides": [ + "wayland-protocols-dev" + ] + }, + "perl-future-doc": { + "versions": { + "0.37-r0": 1511989972 + }, + "origin": "perl-future" + }, + "cyrus-sasl-crammd5": { + "versions": { + "2.1.26-r11": 1510258045 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libtxc_dxtn-dev": { + "versions": { + "1.0.1-r5": 1510075567 + }, + "origin": "libtxc_dxtn", + "dependencies": [ + "mesa-dev" + ] + }, + "py-country": { + "versions": { + "17.9.23-r0": 1509493919 + }, + "origin": "py-country" + }, + "kamailio-extras": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libhiredis.so.0.13", + "so:libsrdb1.so.1", + "so:libsrutils.so.1" + ] + }, + "mercurial": { + "versions": { + "4.5.2-r0": 1532937176, + "4.5.2-r1": 1563792023 + }, + "origin": "mercurial", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:hg", + "cmd:hgeditor", + "cmd:hgk" + ] + }, + "libpcap-doc": { + "versions": { + "1.8.1-r1": 1509475964 + }, + "origin": "libpcap" + }, + "testdisk": { + "versions": { + "7.0-r4": 1509491594 + }, + "origin": "testdisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libext2fs.so.2", + "so:libjpeg.so.8", + "so:libncursesw.so.6", + "so:libntfs-3g.so.88", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:fidentify", + "cmd:photorec", + "cmd:testdisk" + ] + }, + "gdl": { + "versions": { + "3.22.0-r0": 1510074988 + }, + "origin": "gdl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgdl-3.so.5=5.0.9" + ] + }, + "syslog-ng-json": { + "versions": { + "3.9.1-r3": 1510259404 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjson-c.so.2", + "so:libsyslog-ng-3.9.so.0" + ] + }, + "privoxy-doc": { + "versions": { + "3.0.26-r0": 1509495184 + }, + "origin": "privoxy" + }, + "uwsgi-stats_pusher_statsd": { + "versions": { + "2.0.17-r0": 1522154659 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "gmime-dev": { + "versions": { + "2.6.20-r0": 1510588179 + }, + "origin": "gmime", + "dependencies": [ + "glib-dev", + "gpgme-dev", + "gmime=2.6.20-r0", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gmime-2.6=2.6.20" + ] + }, + "ppp-winbind": { + "versions": { + "2.4.7-r5": 1509480134 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-yaml-syck": { + "versions": { + "1.30-r1": 1509461788 + }, + "origin": "perl-yaml-syck", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "net-snmp-agent-libs": { + "versions": { + "5.7.3-r10": 1510259616 + }, + "origin": "net-snmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.30" + ], + "provides": [ + "so:libnetsnmpagent.so.30=30.0.3", + "so:libnetsnmphelpers.so.30=30.0.3", + "so:libnetsnmpmibs.so.30=30.0.3", + "so:libnetsnmptrapd.so.30=30.0.3" + ] + }, + "xmlrpc-c": { + "versions": { + "1.39.11-r0": 1509482043 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "so:libxmlrpc.so.3=3.39", + "so:libxmlrpc_server.so.3=3.39", + "so:libxmlrpc_util.so.3=3.39" + ] + }, + "py-ldb": { + "versions": { + "1.3.0-r1": 1534939012 + }, + "origin": "ldb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libldb.so.1", + "so:libpython2.7.so.1.0", + "so:libtalloc.so.2" + ], + "provides": [ + "so:libpyldb-util.so.1=1.3.0" + ] + }, + "udns": { + "versions": { + "0.4-r0": 1509491398 + }, + "origin": "udns", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libudns.so.0=0", + "cmd:dnsget", + "cmd:ex-rdns", + "cmd:rblcheck" + ] + }, + "librsync-doc": { + "versions": { + "2.0.0-r1": 1509472924 + }, + "origin": "librsync" + }, + "f2fs-tools": { + "versions": { + "1.6.1-r0": 1509483525 + }, + "origin": "f2fs-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libf2fs.so.0", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:defrag.f2fs", + "cmd:dump.f2fs", + "cmd:f2fstat", + "cmd:fibmap.f2fs", + "cmd:fsck.f2fs", + "cmd:mkfs.f2fs", + "cmd:parse.f2fs" + ] + }, + "libmms": { + "versions": { + "0.6.4-r0": 1509480980 + }, + "origin": "libmms", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmms.so.0=0.0.2" + ] + }, + "libxslt": { + "versions": { + "1.1.31-r1": 1555487793, + "1.1.31-r2": 1572541164 + }, + "origin": "libxslt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libxml2.so.2" + ], + "provides": [ + "so:libexslt.so.0=0.8.19", + "so:libxslt.so.1=1.1.31", + "cmd:xsltproc" + ] + }, + "libxaw-dev": { + "versions": { + "1.0.13-r0": 1509480583 + }, + "origin": "libxaw", + "dependencies": [ + "xproto", + "libxmu-dev", + "libxpm-dev", + "libxext-dev", + "libxaw=1.0.13-r0", + "pc:x11", + "pc:xext", + "pc:xmu", + "pc:xpm", + "pc:xproto", + "pc:xt", + "pkgconfig" + ], + "provides": [ + "pc:xaw7=1.0.13" + ] + }, + "sngtc_client-dev": { + "versions": { + "1.3.7-r1": 1509481973 + }, + "origin": "sngtc_client", + "dependencies": [ + "sngtc_client" + ] + }, + "ansible": { + "versions": { + "2.4.6.0-r0": 1538034318, + "2.4.6.0-r1": 1568287983 + }, + "origin": "ansible", + "dependencies": [ + "python2", + "py2-yaml", + "py2-paramiko", + "py2-jinja2", + "py2-markupsafe", + "py2-crypto" + ], + "provides": [ + "cmd:ansible", + "cmd:ansible-config", + "cmd:ansible-connection", + "cmd:ansible-console", + "cmd:ansible-doc", + "cmd:ansible-galaxy", + "cmd:ansible-inventory", + "cmd:ansible-playbook", + "cmd:ansible-pull", + "cmd:ansible-vault" + ] + }, + "py-flask-assets": { + "versions": { + "0.10-r1": 1509551875 + }, + "origin": "py-flask-assets", + "dependencies": [ + "python2", + "py-flask", + "py-webassets" + ] + }, + "apache2-icons": { + "versions": { + "2.4.39-r0": 1554306881, + "2.4.41-r0": 1566292327 + }, + "origin": "apache2" + }, + "hylafax-doc": { + "versions": { + "6.0.6-r4": 1539067243 + }, + "origin": "hylafax" + }, + "sparsehash": { + "versions": { + "2.0.3-r0": 1509489128 + }, + "origin": "sparsehash", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:libsparsehash=2.0.2" + ] + }, + "opensp": { + "versions": { + "1.5.2-r0": 1509488687 + }, + "origin": "opensp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libosp.so.5=5.0.0", + "cmd:onsgmls", + "cmd:osgmlnorm", + "cmd:ospam", + "cmd:ospcat", + "cmd:ospent", + "cmd:osx" + ] + }, + "asterisk": { + "versions": { + "15.6.1-r0": 1537795343, + "15.6.2-r0": 1568705005 + }, + "origin": "asterisk", + "dependencies": [ + "/bin/sh", + "so:libc-client.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libedit.so.0", + "so:libjansson.so.4", + "so:liblua.so.5", + "so:libpj.so.2", + "so:libpjlib-util.so.2", + "so:libpjmedia.so.2", + "so:libpjnath.so.2", + "so:libpjsip-simple.so.2", + "so:libpjsip-ua.so.2", + "so:libpjsip.so.2", + "so:libportaudio.so.2", + "so:libsqlite3.so.0", + "so:libssl.so.44", + "so:libuuid.so.1", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libasteriskssl.so.1=1", + "cmd:astcanary", + "cmd:astdb2bdb", + "cmd:astdb2sqlite3", + "cmd:asterisk", + "cmd:astgenkey", + "cmd:astversion", + "cmd:autosupport", + "cmd:rasterisk", + "cmd:safe_asterisk" + ] + }, + "libgsf-doc": { + "versions": { + "1.14.41-r0": 1509467200 + }, + "origin": "libgsf" + }, + "apache2-doc": { + "versions": { + "2.4.39-r0": 1554306881, + "2.4.41-r0": 1566292327 + }, + "origin": "apache2" + }, + "py-pygments": { + "versions": { + "2.2.0-r0": 1509493373 + }, + "origin": "py-pygments", + "dependencies": [ + "py3-pygments", + "py3-pygments=2.2.0-r0" + ] + }, + "bacula-sqlite": { + "versions": { + "9.0.5-r3": 1512992127 + }, + "origin": "bacula", + "dependencies": [ + "bacula", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:libbaccats-sqlite3-9.0.5.so=0" + ] + }, + "ldb-dev": { + "versions": { + "1.3.0-r1": 1534939012 + }, + "origin": "ldb", + "dependencies": [ + "ldb=1.3.0-r1", + "pc:talloc", + "pc:tdb", + "pkgconfig", + "py-ldb=1.3.0-r1" + ], + "provides": [ + "pc:ldb=1.3.0", + "pc:pyldb-util=1.3.0" + ] + }, + "monit-doc": { + "versions": { + "5.24.0-r1": 1510261491, + "5.24.0-r2": 1559744492 + }, + "origin": "monit" + }, + "abiword-plugin-docbook": { + "versions": { + "3.0.2-r1": 1510073358 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "py3-olefile": { + "versions": { + "0.43-r2": 1509489936 + }, + "origin": "py-olefile" + }, + "perl-path-tiny": { + "versions": { + "0.104-r1": 1510855685 + }, + "origin": "perl-path-tiny" + }, + "lua5.3-sqlite": { + "versions": { + "0.9.4-r2": 1509482116 + }, + "origin": "lua-sqlite", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "lua5.3-sql-mysql": { + "versions": { + "2.3.5-r1": 1509488832 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libmysqlclient.so.18" + ] + }, + "xset-doc": { + "versions": { + "1.2.3-r1": 1509481585 + }, + "origin": "xset" + }, + "i3lock": { + "versions": { + "2.9.1-r0": 1509491492 + }, + "origin": "i3lock", + "dependencies": [ + "xkeyboard-config", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libev.so.4", + "so:libpam.so.0", + "so:libxcb-image.so.0", + "so:libxcb-util.so.1", + "so:libxcb-xinerama.so.0", + "so:libxcb-xkb.so.1", + "so:libxcb.so.1", + "so:libxkbcommon-x11.so.0", + "so:libxkbcommon.so.0" + ], + "provides": [ + "cmd:i3lock" + ] + }, + "openssl-dev": { + "versions": { + "1.0.2r-r0": 1552814650, + "1.0.2t-r0": 1568300394 + }, + "origin": "openssl", + "dependencies": [ + "zlib-dev", + "libcrypto1.0=1.0.2t-r0", + "libssl1.0=1.0.2t-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcrypto=1.0.2t", + "pc:libssl=1.0.2t", + "pc:openssl=1.0.2t" + ] + }, + "iptraf-ng-doc": { + "versions": { + "1.1.4-r4": 1509495003 + }, + "origin": "iptraf-ng" + }, + "cairo-dbg": { + "versions": { + "1.14.10-r0": 1509464621 + }, + "origin": "cairo" + }, + "freeradius-dev": { + "versions": { + "3.0.15-r4": 1556202795, + "3.0.15-r5": 1566310603 + }, + "origin": "freeradius" + }, + "avahi-lang": { + "versions": { + "0.6.32-r4": 1509465449, + "0.6.32-r5": 1563345619 + }, + "origin": "avahi" + }, + "sqlite-doc": { + "versions": { + "3.25.3-r0": 1548491335, + "3.25.3-r1": 1563951530, + "3.25.3-r2": 1571590443 + }, + "origin": "sqlite" + }, + "rtorrent": { + "versions": { + "0.9.6-r2": 1509492669 + }, + "origin": "rtorrent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libstdc++.so.6", + "so:libtorrent.so.19", + "so:libxmlrpc.so.3", + "so:libxmlrpc_server.so.3", + "so:libxmlrpc_util.so.3" + ], + "provides": [ + "cmd:rtorrent" + ] + }, + "libdc1394-dev": { + "versions": { + "2.2.5-r1": 1509480969 + }, + "origin": "libdc1394", + "dependencies": [ + "libusb-dev", + "libraw1394-dev", + "libdc1394=2.2.5-r1", + "pkgconfig" + ], + "provides": [ + "pc:libdc1394-2=2.2.5" + ] + }, + "perl-term-table-doc": { + "versions": { + "0.012-r0": 1509631261 + }, + "origin": "perl-term-table" + }, + "xf86-input-synaptics-dev": { + "versions": { + "1.9.0-r1": 1510073650 + }, + "origin": "xf86-input-synaptics", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xorg-synaptics=1.9.0" + ] + }, + "audacious-plugins": { + "versions": { + "3.9-r0": 1510288542 + }, + "origin": "audacious-plugins", + "dependencies": [ + "audacious", + "so:libFLAC.so.8", + "so:libSDL-1.2.so.0", + "so:libX11.so.6", + "so:libXcomposite.so.1", + "so:libXrender.so.1", + "so:libasound.so.2", + "so:libaudcore.so.5", + "so:libaudgui.so.5", + "so:libaudtag.so.3", + "so:libavcodec.so.57", + "so:libavformat.so.57", + "so:libavutil.so.55", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcddb.so.2", + "so:libcdio.so.16", + "so:libcdio_cdda.so.2", + "so:libcue.so.2", + "so:libcurl.so.4", + "so:libdbus-glib-1.so.2", + "so:libfaad.so.2", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libmms.so.0", + "so:libmp3lame.so.0", + "so:libmpg123.so.0", + "so:libneon.so.27", + "so:libnotify.so.4", + "so:libogg.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libsamplerate.so.0", + "so:libsndfile.so.1", + "so:libstdc++.so.6", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2", + "so:libvorbisfile.so.3", + "so:libwavpack.so.1", + "so:libxml2.so.2", + "so:libz.so.1" + ] + }, + "qemu-system-lm32": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libX11.so.6", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-lm32" + ] + }, + "s6-dev": { + "versions": { + "2.6.1.1-r0": 1510364940 + }, + "origin": "s6", + "dependencies": [ + "s6=2.6.1.1-r0" + ] + }, + "perl-mail-domainkeys": { + "versions": { + "1.0-r2": 1509494036 + }, + "origin": "perl-mail-domainkeys", + "dependencies": [ + "perl" + ] + }, + "putty-doc": { + "versions": { + "0.71-r1": 1554726628 + }, + "origin": "putty" + }, + "awall": { + "versions": { + "1.5.0-r0": 1509742667 + }, + "origin": "awall", + "dependencies": [ + "bind-tools", + "ip6tables", + "ipset", + "iptables", + "lua5.2", + "lua5.2-alt-getopt", + "lua5.2-cjson", + "lua5.2-pc", + "lua5.2-posix", + "lua5.2-stringy", + "xtables-addons", + "/bin/sh" + ], + "provides": [ + "cmd:awall" + ] + }, + "uwsgi-webdav": { + "versions": { + "2.0.17-r0": 1522154660 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2" + ] + }, + "util-linux-bash-completion": { + "versions": { + "2.31.1-r0": 1541506294 + }, + "origin": "util-linux" + }, + "lzo": { + "versions": { + "2.10-r2": 1509462188 + }, + "origin": "lzo", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblzo2.so.2=2.0.0" + ] + }, + "glade-dev": { + "versions": { + "3.20.1-r0": 1510073637 + }, + "origin": "glade", + "dependencies": [ + "glade=3.20.1-r0", + "pc:gtk+-3.0>=2.91.2", + "pc:libxml-2.0>=2.4.0", + "pkgconfig" + ], + "provides": [ + "pc:gladeui-2.0=3.20.1" + ] + }, + "libxfont2-dev": { + "versions": { + "2.0.3-r0": 1511975683 + }, + "origin": "libxfont2", + "dependencies": [ + "libxfont2=2.0.3-r0", + "pc:fontenc", + "pc:fontsproto", + "pc:freetype2", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xfont2=2.0.3" + ] + }, + "openvpn-dev": { + "versions": { + "2.4.4-r1": 1510259339 + }, + "origin": "openvpn" + }, + "alsa-utils-doc": { + "versions": { + "1.1.4-r0": 1509468851 + }, + "origin": "alsa-utils" + }, + "ruby-bundler": { + "versions": { + "1.16.1-r0": 1521049537 + }, + "origin": "ruby-bundler", + "dependencies": [ + "ruby", + "ruby-io-console" + ] + }, + "gcc-java": { + "versions": { + "6.4.0-r5": 1509458077 + }, + "origin": "gcc", + "dependencies": [ + "zlib-dev", + "gcc=6.4.0-r5", + "libgcj=6.4.0-r5", + "libgcj=6.4.0-r5", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgcj-tools.so.17", + "so:libgcj.so.17", + "so:libgcj_bc.so.1", + "so:libgmp.so.10", + "so:libisl.so.15", + "so:libmpc.so.3", + "so:libmpfr.so.4", + "so:libz.so.1" + ], + "provides": [ + "cmd:gcj", + "cmd:gcj-dbtool", + "cmd:gcjh", + "cmd:gjavah", + "cmd:jcf-dump" + ] + }, + "perl-params-validationcompiler-doc": { + "versions": { + "0.24-r1": 1510588892 + }, + "origin": "perl-params-validationcompiler" + }, + "pax-utils-doc": { + "versions": { + "1.2.2-r1": 1509459679 + }, + "origin": "pax-utils" + }, + "perl-net-ip": { + "versions": { + "1.26-r0": 1509469034 + }, + "origin": "perl-net-ip", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:ipcount", + "cmd:iptab" + ] + }, + "subversion-dev": { + "versions": { + "1.9.7-r0": 1510314499, + "1.9.12-r0": 1565086191 + }, + "origin": "subversion", + "dependencies": [ + "perl-subversion=1.9.12-r0", + "py-subversion=1.9.12-r0", + "subversion-libs=1.9.12-r0" + ] + }, + "lua5.1-stdlib": { + "versions": { + "41.2.0-r0": 1509495242 + }, + "origin": "lua-stdlib" + }, + "acf-provisioning": { + "versions": { + "0.10.1-r0": 1510075372 + }, + "origin": "acf-provisioning", + "dependencies": [ + "acf-core", + "lua-sql-postgres", + "postgresql-client", + "lua-posixtz", + "lua-xml" + ] + }, + "acf-jquery": { + "versions": { + "0.4.2-r1": 1510068292 + }, + "origin": "acf-jquery" + }, + "xmlindent": { + "versions": { + "0.2.17-r0": 1509490834 + }, + "origin": "xmlindent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfl.so.2" + ], + "provides": [ + "cmd:xmlindent" + ] + }, + "perl-inline-doc": { + "versions": { + "0.80-r0": 1509489227 + }, + "origin": "perl-inline" + }, + "libxml2-doc": { + "versions": { + "2.9.8-r1": 1540398579 + }, + "origin": "libxml2" + }, + "libxres-dev": { + "versions": { + "1.2.0-r0": 1509468563 + }, + "origin": "libxres", + "dependencies": [ + "resourceproto", + "libxres=1.2.0-r0", + "pc:x11", + "pc:xext", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xres=1.2.0" + ] + }, + "jack-dev": { + "versions": { + "1.9.10-r5": 1509473318 + }, + "origin": "jack", + "dependencies": [ + "jack=1.9.10-r5", + "pkgconfig" + ], + "provides": [ + "pc:jack=1.9.10" + ] + }, + "libsndfile-doc": { + "versions": { + "1.0.28-r4": 1548491633, + "1.0.28-r5": 1563345857 + }, + "origin": "libsndfile" + }, + "gcr-dev": { + "versions": { + "3.20.0-r1": 1510073692 + }, + "origin": "gcr", + "dependencies": [ + "glib-dev", + "gtk+3.0-dev", + "libgcrypt-dev", + "p11-kit-dev", + "gcr=3.20.0-r1", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:gtk+-3.0", + "pc:p11-kit-1", + "pkgconfig" + ], + "provides": [ + "pc:gck-1=3.20.0", + "pc:gcr-3=3.20.0", + "pc:gcr-base-3=3.20.0", + "pc:gcr-ui-3=3.20.0" + ] + }, + "lua5.2-stringy": { + "versions": { + "0.4.0-r5": 1509480101 + }, + "origin": "lua-stringy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libwebsockets-dev": { + "versions": { + "2.4.0-r1": 1510258054 + }, + "origin": "libwebsockets", + "dependencies": [ + "libwebsockets=2.4.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:libwebsockets=2.4.0", + "pc:libwebsockets_static=2.4.0" + ] + }, + "qt-private-dev": { + "versions": { + "4.8.7-r8": 1510287210 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates" + ] + }, + "py2-gunicorn": { + "versions": { + "19.7.1-r1": 1509493756 + }, + "origin": "py-gunicorn", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:gunicorn", + "cmd:gunicorn_paster" + ] + }, + "libunwind-dev": { + "versions": { + "1.2.1-r1": 1509493800 + }, + "origin": "libunwind", + "dependencies": [ + "libexecinfo-dev", + "libunwind=1.2.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:libunwind-coredump=1.21", + "pc:libunwind-generic=1.21", + "pc:libunwind-ptrace=1.21", + "pc:libunwind-setjmp=1.21", + "pc:libunwind=1.21" + ] + }, + "py-gnome": { + "versions": { + "2.28.1-r5": 1510933057 + }, + "origin": "py-gnome", + "dependencies": [ + "py-gnome-bonobo", + "py-gnome-gconf", + "py-gnome-libgnome", + "py-gnome-gnomevfs" + ] + }, + "hdparm": { + "versions": { + "9.52-r0": 1509479800 + }, + "origin": "hdparm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:hdparm", + "cmd:idectl", + "cmd:ultrabayd" + ] + }, + "kmod-dev": { + "versions": { + "24-r0": 1509462316 + }, + "origin": "kmod", + "dependencies": [ + "kmod=24-r0", + "pkgconfig" + ], + "provides": [ + "pc:libkmod=24" + ] + }, + "x264-dev": { + "versions": { + "20170930-r0": 1509473602 + }, + "origin": "x264", + "dependencies": [ + "pkgconfig", + "x264-libs=20170930-r0" + ], + "provides": [ + "pc:x264=0.148.x" + ] + }, + "perl-tree-simple": { + "versions": { + "1.18-r1": 1509494482 + }, + "origin": "perl-tree-simple", + "dependencies": [ + "perl", + "perl-test-exception" + ] + }, + "taglib": { + "versions": { + "1.11.1-r0": 1509489613 + }, + "origin": "taglib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libtag.so.1=1.17.0", + "so:libtag_c.so.0=0.0.0" + ] + }, + "kamailio-uuid": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ] + }, + "libgee": { + "versions": { + "0.20.0-r0": 1509468648 + }, + "origin": "libgee", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libgee-0.8.so.2=2.6.0" + ] + }, + "py2-future": { + "versions": { + "0.15.2-r4": 1509491797 + }, + "origin": "py-future", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:futurize", + "cmd:pasteurize" + ] + }, + "daq-dev": { + "versions": { + "2.0.6-r2": 1510839244 + }, + "origin": "daq", + "dependencies": [ + "daq=2.0.6-r2" + ], + "provides": [ + "cmd:daq-modules-config" + ] + }, + "appstream-glib-doc": { + "versions": { + "0.6.3-r0": 1510067861 + }, + "origin": "appstream-glib" + }, + "py2-urllib3": { + "versions": { + "1.22-r0": 1509483031 + }, + "origin": "py-urllib3", + "dependencies": [ + "python2" + ] + }, + "perl-ipc-system-simple-doc": { + "versions": { + "1.25-r0": 1509495315 + }, + "origin": "perl-ipc-system-simple" + }, + "samba-winbind-krb5-locator": { + "versions": { + "4.7.6-r3": 1555491787 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libwbclient.so.0" + ], + "provides": [ + "so:winbind_krb5_locator.so=0" + ] + }, + "glade": { + "versions": { + "3.20.1-r0": 1510073638 + }, + "origin": "glade", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgladeui-2.so.6=6.3.1", + "cmd:glade", + "cmd:glade-previewer" + ] + }, + "perl-data-page-doc": { + "versions": { + "2.02-r1": 1509493849 + }, + "origin": "perl-data-page" + }, + "syslinux-doc": { + "versions": { + "6.04_pre1-r1": 1509482641 + }, + "origin": "syslinux" + }, + "flashrom": { + "versions": { + "0.9.9-r2": 1509482145 + }, + "origin": "flashrom", + "dependencies": [ + "dmidecode", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libpci.so.3", + "so:libusb-0.1.so.4", + "so:libusb-1.0.so.0" + ], + "provides": [ + "cmd:flashrom" + ] + }, + "arpon": { + "versions": { + "3.0-r0": 1509495305 + }, + "origin": "arpon", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdnet.so.1", + "so:libnet.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:arpon" + ] + }, + "xorg-server-xwayland": { + "versions": { + "1.19.5-r1": 1540838460 + }, + "origin": "xorg-server", + "dependencies": [ + "font-misc-misc", + "font-cursor-misc", + "xkeyboard-config", + "xkbcomp", + "xinit", + "so:libGL.so.1", + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.0.0", + "so:libdrm.so.2", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libpixman-1.so.0", + "so:libwayland-client.so.0", + "so:libxshmfence.so.1" + ], + "provides": [ + "cmd:Xwayland" + ] + }, + "py-simplejson": { + "versions": { + "3.8.2-r2": 1509494754 + }, + "origin": "py-simplejson" + }, + "apache2-error": { + "versions": { + "2.4.39-r0": 1554306881, + "2.4.41-r0": 1566292327 + }, + "origin": "apache2" + }, + "jwhois": { + "versions": { + "4.0-r4": 1509493815 + }, + "origin": "jwhois", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4" + ], + "provides": [ + "cmd:jwhois" + ] + }, + "liblogging": { + "versions": { + "1.0.6-r0": 1509517792 + }, + "origin": "liblogging", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblogging-stdlog.so.0=0.1.0", + "cmd:stdlogctl" + ] + }, + "skalibs-dev": { + "versions": { + "2.6.1.0-r0": 1510364877 + }, + "origin": "skalibs", + "dependencies": [ + "skalibs=2.6.1.0-r0" + ] + }, + "faac-dev": { + "versions": { + "1.28-r11": 1509470017 + }, + "origin": "faac", + "dependencies": [ + "faac=1.28-r11" + ] + }, + "ruby-xmlrpc": { + "versions": { + "2.4.6-r0": 1557166823 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ] + }, + "sendpage-doc": { + "versions": { + "1.0.3-r5": 1509489416 + }, + "origin": "sendpage" + }, + "py3-lxml": { + "versions": { + "4.1.1-r0": 1510088209 + }, + "origin": "py-lxml", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexslt.so.0", + "so:libpython3.6m.so.1.0", + "so:libxml2.so.2", + "so:libxslt.so.1" + ] + }, + "font-sun-misc": { + "versions": { + "1.0.3-r0": 1509494263 + }, + "origin": "font-sun-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "qemu-alpha": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-alpha" + ] + }, + "gst-plugins-bad0.10-dev": { + "versions": { + "0.10.23-r7": 1510287373 + }, + "origin": "gst-plugins-bad0.10", + "dependencies": [ + "gst-plugins-bad0.10=0.10.23-r7", + "pc:gstreamer-0.10", + "pc:gstreamer-base-0.10", + "pkgconfig" + ], + "provides": [ + "pc:gstreamer-basevideo-0.10=0.10.23", + "pc:gstreamer-codecparsers-0.10=0.10.23", + "pc:gstreamer-plugins-bad-0.10=0.10.23" + ] + }, + "p11-kit-dev": { + "versions": { + "0.23.2-r2": 1509465178 + }, + "origin": "p11-kit", + "dependencies": [ + "p11-kit=0.23.2-r2", + "pkgconfig" + ], + "provides": [ + "pc:p11-kit-1=0.23.2" + ] + }, + "cdparanoia": { + "versions": { + "10.2-r7": 1509471030 + }, + "origin": "cdparanoia", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcdda_interface.so.0", + "so:libcdda_paranoia.so.0" + ], + "provides": [ + "cmd:cdparanoia" + ] + }, + "java-gcj-compat": { + "versions": { + "6.4.0-r8": 1509490656 + }, + "origin": "java-gcj-compat", + "dependencies": [ + "fastjar", + "gcc-java=6.4.0-r5", + "java-common", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgcj.so.17" + ] + }, + "perl-file-tail": { + "versions": { + "1.3-r1": 1511889532 + }, + "origin": "perl-file-tail" + }, + "ruby-did_you_mean": { + "versions": { + "2.4.6-r0": 1557166822 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ] + }, + "openldap-back-mdb": { + "versions": { + "2.4.45-r3": 1510258133, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900515 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "slim-themes": { + "versions": { + "1.2.3-r2": 1510067550 + }, + "origin": "slim-themes", + "dependencies": [ + "slim" + ] + }, + "bitlbee-doc": { + "versions": { + "3.5.1-r1": 1510259355 + }, + "origin": "bitlbee" + }, + "openldap-back-hdb": { + "versions": { + "2.4.45-r3": 1510258132, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900515 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "speex-doc": { + "versions": { + "1.2.0-r0": 1509473246 + }, + "origin": "speex" + }, + "perl-test-output": { + "versions": { + "1.031-r0": 1509485674 + }, + "origin": "perl-test-output", + "dependencies": [ + "perl-capture-tiny", + "perl-sub-exporter", + "perl-test-simple" + ] + }, + "py-gnome-dev": { + "versions": { + "2.28.1-r5": 1510933054 + }, + "origin": "py-gnome", + "dependencies": [ + "gtk+2.0-dev", + "libgnome-dev", + "py-gobject-dev", + "py-gtk-dev", + "python2-dev", + "pkgconfig" + ], + "provides": [ + "pc:gnome-python-2.0=2.28.1" + ] + }, + "py3-uritemplate.py": { + "versions": { + "3.0.0-r0": 1509486306 + }, + "origin": "py-uritemplate", + "dependencies": [ + "python3" + ] + }, + "ircii": { + "versions": { + "20111115-r4": 1509492742 + }, + "origin": "ircii", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:irc", + "cmd:irc-20111115", + "cmd:ircbug", + "cmd:ircflush" + ] + }, + "py-ecdsa": { + "versions": { + "0.13-r5": 1509494957 + }, + "origin": "py-ecdsa" + }, + "iso-codes-lang": { + "versions": { + "3.75-r0": 1509466020 + }, + "origin": "iso-codes" + }, + "perl-css-squish": { + "versions": { + "0.10-r0": 1509470430 + }, + "origin": "perl-css-squish", + "dependencies": [ + "perl", + "perl-uri", + "perl-test-longstring" + ] + }, + "hunspell": { + "versions": { + "1.6.2-r1": 1509477668 + }, + "origin": "hunspell", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libreadline.so.7", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libhunspell-1.6.so.0=0.0.1", + "cmd:affixcompress", + "cmd:analyze", + "cmd:chmorph", + "cmd:hunspell", + "cmd:hunzip", + "cmd:hzip", + "cmd:ispellaff2myspell", + "cmd:makealias", + "cmd:munch", + "cmd:unmunch", + "cmd:wordforms", + "cmd:wordlist2hunspell" + ] + }, + "libxxf86dga-dev": { + "versions": { + "1.1.4-r1": 1509473787 + }, + "origin": "libxxf86dga", + "dependencies": [ + "libxxf86dga=1.1.4-r1", + "pc:x11", + "pc:xext", + "pc:xf86dgaproto", + "pkgconfig" + ], + "provides": [ + "pc:xxf86dga=1.1.4" + ] + }, + "py-requests-oauthlib": { + "versions": { + "0.8.0-r1": 1509552764 + }, + "origin": "py-requests-oauthlib", + "dependencies": [ + "py-oauthlib", + "py-requests" + ] + }, + "ciwiki": { + "versions": { + "2.0.5-r1": 1509493443 + }, + "origin": "ciwiki", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ciwiki" + ] + }, + "libpng-doc": { + "versions": { + "1.6.37-r0": 1557132257 + }, + "origin": "libpng" + }, + "clamav-milter": { + "versions": { + "0.100.3-r0": 1555508238 + }, + "origin": "clamav", + "dependencies": [ + "clamav-scanner", + "clamav-daemon", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libmilter.so.1.0.2", + "so:libssl.so.44" + ], + "provides": [ + "cmd:clamav-milter" + ] + }, + "acl-doc": { + "versions": { + "2.2.52-r3": 1509459587 + }, + "origin": "acl" + }, + "perl-test-requires-doc": { + "versions": { + "0.10-r0": 1509470570 + }, + "origin": "perl-test-requires" + }, + "squid-lang-af": { + "versions": { + "3.5.27-r0": 1519824030, + "3.5.27-r1": 1562865666 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-class-container": { + "versions": { + "0.12-r1": 1509470591 + }, + "origin": "perl-class-container", + "dependencies": [ + "perl-params-validate" + ] + }, + "libogg": { + "versions": { + "1.3.3-r1": 1511046403 + }, + "origin": "libogg", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libogg.so.0=0.8.3" + ] + }, + "libmad": { + "versions": { + "0.15.1b-r7": 1509473573, + "0.15.1b-r8": 1565784570 + }, + "origin": "libmad", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmad.so.0=0.2.1" + ] + }, + "py2-unidecode": { + "versions": { + "0.04.21-r0": 1509493255 + }, + "origin": "py-unidecode", + "dependencies": [ + "py3-unidecode", + "python2" + ], + "provides": [ + "cmd:unidecode-2" + ] + }, + "nagios-plugins-cluster": { + "versions": { + "2.2.1-r3": 1510288494 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "ristretto-lang": { + "versions": { + "0.8.2-r0": 1510072649 + }, + "origin": "ristretto", + "dependencies": [ + "desktop-file-utils", + "hicolor-icon-theme" + ] + }, + "perl-test-mockobject-doc": { + "versions": { + "1.20161202-r0": 1509489410 + }, + "origin": "perl-test-mockobject" + }, + "uwsgi-http": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "uwsgi-corerouter", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-package-stash": { + "versions": { + "0.37-r0": 1509474047 + }, + "origin": "perl-package-stash", + "dependencies": [ + "perl-dist-checkconflicts", + "perl-package-stash-xs", + "perl-module-implementation" + ], + "provides": [ + "cmd:package-stash-conflicts" + ] + }, + "uwsgi-router_radius": { + "versions": { + "2.0.17-r0": 1522154657 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libidl": { + "versions": { + "0.8.14-r2": 1510928272 + }, + "origin": "libidl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "so:libIDL-2.so.0=0.0.0", + "cmd:libIDL-config-2" + ] + }, + "abiword-plugin-wikipedia": { + "versions": { + "3.0.2-r1": 1510073369 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "openvswitch-bash-completion": { + "versions": { + "2.8.1-r2": 1512030506 + }, + "origin": "openvswitch" + }, + "gvfs-dev": { + "versions": { + "1.34.1-r0": 1511430257, + "1.34.1-r1": 1563787223 + }, + "origin": "gvfs" + }, + "libxfixes-dev": { + "versions": { + "5.0.3-r1": 1509464684 + }, + "origin": "libxfixes", + "dependencies": [ + "libxfixes=5.0.3-r1", + "pc:fixesproto>=5.0", + "pc:x11", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xfixes=5.0.3" + ] + }, + "libressl-dbg": { + "versions": { + "2.6.5-r0": 1529043883 + }, + "origin": "libressl" + }, + "libnl3": { + "versions": { + "3.2.28-r1": 1509475887 + }, + "origin": "libnl3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnl-3.so.200=200.23.0", + "so:libnl-genl-3.so.200=200.23.0", + "so:libnl-idiag-3.so.200=200.23.0", + "so:libnl-nf-3.so.200=200.23.0", + "so:libnl-route-3.so.200=200.23.0", + "so:libnl-xfrm-3.so.200=200.23.0" + ] + }, + "libsmbclient": { + "versions": { + "4.7.6-r3": 1555491786 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcli-smb-common-samba4.so", + "so:libdcerpc-samba-samba4.so", + "so:libgse-samba4.so", + "so:liblibcli-lsa3-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmsrpc3-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-security-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsecrets3-samba4.so", + "so:libsmbconf.so.0", + "so:libtalloc.so.2", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-cmdline-samba4.so" + ], + "provides": [ + "so:libsmbclient.so.0=0.2.3" + ] + }, + "gtest-dev": { + "versions": { + "1.8.0-r1": 1509475785 + }, + "origin": "gtest", + "dependencies": [ + "python2", + "cmake" + ] + }, + "fish-doc": { + "versions": { + "2.6.0-r2": 1511486201 + }, + "origin": "fish" + }, + "libxtst": { + "versions": { + "1.2.3-r1": 1509466039 + }, + "origin": "libxtst", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXtst.so.6=6.1.0" + ] + }, + "uwsgi-echo": { + "versions": { + "2.0.17-r0": 1522154654 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "lucene++": { + "versions": { + "3.0.7-r5": 1509496354 + }, + "origin": "lucene++", + "dependencies": [ + "so:libboost_filesystem-mt.so.1.62.0", + "so:libboost_iostreams-mt.so.1.62.0", + "so:libboost_regex-mt.so.1.62.0", + "so:libboost_system-mt.so.1.62.0", + "so:libboost_thread-mt.so.1.62.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:liblucene++-contrib.so.0=3.0.7", + "so:liblucene++.so.0=3.0.7" + ] + }, + "gvpe": { + "versions": { + "2.25-r4": 1510259350 + }, + "origin": "gvpe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libgmp.so.10", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:gvpe", + "cmd:gvpectrl" + ] + }, + "augeas-doc": { + "versions": { + "1.9.0-r3": 1511899746 + }, + "origin": "augeas" + }, + "lua5.3-hashids": { + "versions": { + "1.0.6-r1": 1509480766 + }, + "origin": "lua-hashids", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-text-autoformat": { + "versions": { + "1.669004-r0": 1509475845 + }, + "origin": "perl-text-autoformat", + "dependencies": [ + "perl", + "perl-text-reform" + ] + }, + "lua-stdlib": { + "versions": { + "41.2.0-r0": 1509495249 + }, + "origin": "lua-stdlib" + }, + "postgresql-contrib": { + "versions": { + "10.7-r0": 1554274195, + "10.8-r0": 1557668011, + "10.9-r0": 1562225611, + "10.10-r0": 1565683434 + }, + "origin": "postgresql", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpq.so.5", + "so:libssl.so.44", + "so:libuuid.so.1", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:oid2name", + "cmd:pg_standby", + "cmd:vacuumlo" + ] + }, + "ldns": { + "versions": { + "1.6.17-r6": 1510258937 + }, + "origin": "ldns", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ], + "provides": [ + "so:libldns.so.1=1.6.17" + ] + }, + "nginx-mod-http-perl": { + "versions": { + "1.12.2-r4": 1542814447 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "privoxy": { + "versions": { + "3.0.26-r0": 1509495185 + }, + "origin": "privoxy", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1", + "so:libpcreposix.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:privoxy" + ] + }, + "openssl": { + "versions": { + "1.0.2r-r0": 1552814669, + "1.0.2t-r0": 1568300415 + }, + "origin": "openssl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.0.0", + "so:libssl.so.1.0.0" + ], + "provides": [ + "cmd:openssl" + ] + }, + "perl-date-calc": { + "versions": { + "6.4-r1": 1509485626 + }, + "origin": "perl-date-calc", + "dependencies": [ + "perl" + ] + }, + "perl-control-x10": { + "versions": { + "2.09-r1": 1509491054 + }, + "origin": "perl-control-x10", + "dependencies": [ + "perl" + ] + }, + "ortp": { + "versions": { + "0.25.0-r0": 1509479730 + }, + "origin": "ortp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libortp.so.10=10.0.0" + ] + }, + "gst-plugins-bad": { + "versions": { + "1.12.3-r2": 1512039894 + }, + "origin": "gst-plugins-bad", + "dependencies": [ + "so:libEGL.so.1", + "so:libGL.so.1", + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcrypto.so.42", + "so:libcurl.so.4", + "so:libdc1394.so.22", + "so:libdirectfb-1.7.so.7", + "so:libdrm.so.2", + "so:libfaac.so.0", + "so:libfaad.so.2", + "so:libflite.so.1", + "so:libflite_cmu_us_kal.so.1", + "so:libflite_cmulex.so.1", + "so:libflite_usenglish.so.1", + "so:libgcc_s.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsm.so.1", + "so:libgstallocators-1.0.so.0", + "so:libgstapp-1.0.so.0", + "so:libgstaudio-1.0.so.0", + "so:libgstbase-1.0.so.0", + "so:libgstcontroller-1.0.so.0", + "so:libgstfft-1.0.so.0", + "so:libgstnet-1.0.so.0", + "so:libgstpbutils-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgstrtp-1.0.so.0", + "so:libgstsdp-1.0.so.0", + "so:libgsttag-1.0.so.0", + "so:libgstvideo-1.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:libmms.so.0", + "so:libmodplug.so.1", + "so:libneon.so.27", + "so:liborc-0.4.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpng16.so.16", + "so:librsvg-2.so.2", + "so:libspandsp.so.2", + "so:libssl.so.44", + "so:libstdc++.so.6", + "so:libvdpau.so.1", + "so:libwayland-client.so.0", + "so:libwayland-egl.so.1", + "so:libwebp.so.7", + "so:libx265.so.130", + "so:libxcb.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgstadaptivedemux-1.0.so.0=0.1203.0", + "so:libgstbadallocators-1.0.so.0=0.1203.0", + "so:libgstbadaudio-1.0.so.0=0.1203.0", + "so:libgstbadbase-1.0.so.0=0.1203.0", + "so:libgstbadvideo-1.0.so.0=0.1203.0", + "so:libgstbasecamerabinsrc-1.0.so.0=0.1203.0", + "so:libgstcodecparsers-1.0.so.0=0.1203.0", + "so:libgstgl-1.0.so.0=0.1203.0", + "so:libgstinsertbin-1.0.so.0=0.1203.0", + "so:libgstmpegts-1.0.so.0=0.1203.0", + "so:libgstphotography-1.0.so.0=0.1203.0", + "so:libgstplayer-1.0.so.0=0.1203.0", + "so:libgsturidownloader-1.0.so.0=0.1203.0" + ] + }, + "xen": { + "versions": { + "4.9.4-r0": 1551280496, + "4.9.4-r1": 1558103152 + }, + "origin": "xen", + "dependencies": [ + "bash", + "iproute2", + "logrotate", + "syslinux", + "so:libaio.so.1", + "so:libblktapctl.so.1.0", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libfsimage.so.1.0", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libpci.so.3", + "so:libpixman-1.so.0", + "so:libpython2.7.so.1.0", + "so:libspice-server.so.1", + "so:libvhd.so.1.0", + "so:libxenctrl.so.4.9", + "so:libxenevtchn.so.1", + "so:libxenforeignmemory.so.1", + "so:libxengnttab.so.1", + "so:libxenguest.so.4.9", + "so:libxenlight.so.4.9", + "so:libxenstat.so.0", + "so:libxenstore.so.3.0", + "so:libxentoollog.so.1", + "so:libxlutil.so.4.9", + "so:libyajl.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:flask-get-bool", + "cmd:flask-getenforce", + "cmd:flask-label-pci", + "cmd:flask-loadpolicy", + "cmd:flask-set-bool", + "cmd:flask-setenforce", + "cmd:gdbsx", + "cmd:img2qcow", + "cmd:kdd", + "cmd:lock-util", + "cmd:qcow-create", + "cmd:qcow2raw", + "cmd:qemu-img-xen", + "cmd:qemu-nbd-xen", + "cmd:tap-ctl", + "cmd:tapdisk-client", + "cmd:tapdisk-diff", + "cmd:tapdisk-stream", + "cmd:tapdisk2", + "cmd:td-util", + "cmd:vhd-update", + "cmd:vhd-util", + "cmd:xen-bugtool", + "cmd:xen-cpuid", + "cmd:xen-detect", + "cmd:xen-hptool", + "cmd:xen-hvmcrash", + "cmd:xen-hvmctx", + "cmd:xen-livepatch", + "cmd:xen-lowmemd", + "cmd:xen-mfndump", + "cmd:xen-ringwatch", + "cmd:xen-tmem-list-parse", + "cmd:xenalyze", + "cmd:xenbaked", + "cmd:xencons", + "cmd:xenconsoled", + "cmd:xencov", + "cmd:xencov_split", + "cmd:xenlockprof", + "cmd:xenmon.py", + "cmd:xenperf", + "cmd:xenpm", + "cmd:xenpmd", + "cmd:xenstore", + "cmd:xenstore-chmod", + "cmd:xenstore-control", + "cmd:xenstore-exists", + "cmd:xenstore-list", + "cmd:xenstore-ls", + "cmd:xenstore-read", + "cmd:xenstore-rm", + "cmd:xenstore-watch", + "cmd:xenstore-write", + "cmd:xenstored", + "cmd:xentop", + "cmd:xentrace", + "cmd:xentrace_format", + "cmd:xentrace_setmask", + "cmd:xentrace_setsize", + "cmd:xenwatchdogd", + "cmd:xl" + ] + }, + "fcgi-dev": { + "versions": { + "2.4.0-r8": 1510330997 + }, + "origin": "fcgi", + "dependencies": [ + "fcgi++=2.4.0-r8", + "fcgi=2.4.0-r8" + ] + }, + "libotr3-doc": { + "versions": { + "3.2.1-r4": 1509481188 + }, + "origin": "libotr3", + "dependencies": [ + "libotr3" + ] + }, + "abuild-rootbld": { + "versions": { + "3.1.0-r4": 1527832619, + "3.1.0-r5": 1572356050 + }, + "origin": "abuild", + "dependencies": [ + "abuild", + "bubblewrap", + "gettext", + "git" + ] + }, + "librsync": { + "versions": { + "2.0.0-r1": 1509472925 + }, + "origin": "librsync", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0" + ], + "provides": [ + "so:librsync.so.2=2.0.0", + "cmd:rdiff" + ] + }, + "perl-ldap-doc": { + "versions": { + "0.65-r1": 1510564476 + }, + "origin": "perl-ldap" + }, + "perl-dbix-searchbuilder": { + "versions": { + "1.67-r1": 1510588867 + }, + "origin": "perl-dbix-searchbuilder", + "dependencies": [ + "perl-class-returnvalue", + "perl-dbi", + "perl-cache-simple-timedexpiry", + "perl-class-accessor", + "perl-clone", + "perl-want", + "perl-dbix-dbschema" + ] + }, + "udisks2-lang": { + "versions": { + "2.6.5-r0": 1510075151 + }, + "origin": "udisks2" + }, + "adwaita-icon-theme-dev": { + "versions": { + "3.26.0-r0": 1510076148 + }, + "origin": "adwaita-icon-theme", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:adwaita-icon-theme=3.26.0" + ] + }, + "minicom-doc": { + "versions": { + "2.7.1-r0": 1509485710 + }, + "origin": "minicom" + }, + "jbig2dec-doc": { + "versions": { + "0.14-r0": 1509469939 + }, + "origin": "jbig2dec" + }, + "perl-net-dns-doc": { + "versions": { + "1.13-r0": 1509469031 + }, + "origin": "perl-net-dns" + }, + "xrefresh": { + "versions": { + "1.0.5-r0": 1509494699 + }, + "origin": "xrefresh", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xrefresh" + ] + }, + "libxfce4ui": { + "versions": { + "4.12.1-r3": 1510067994 + }, + "origin": "libxfce4ui", + "dependencies": [ + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libstartup-notification-1.so.0", + "so:libxfce4util.so.7", + "so:libxfconf-0.so.2" + ], + "provides": [ + "so:libxfce4kbd-private-2.so.0=0.0.0", + "so:libxfce4ui-1.so.0=0.0.0", + "cmd:xfce4-about" + ] + }, + "mutt": { + "versions": { + "1.10.1-r0": 1532446898 + }, + "origin": "mutt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgdbm.so.4", + "so:libgpgme.so.11", + "so:libidn.so.11", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libsasl2.so.3", + "so:libssl.so.44" + ], + "provides": [ + "cmd:mutt", + "cmd:pgpewrap", + "cmd:pgpring", + "cmd:smime_keys" + ] + }, + "zsh-vcs": { + "versions": { + "5.4.2-r1": 1522503663 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "xrdp": { + "versions": { + "0.9.2-r3": 1510260212 + }, + "origin": "xrdp", + "dependencies": [ + "so:libX11.so.6", + "so:libXfixes.so.3", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libfuse.so.2", + "so:libssl.so.44", + "so:libturbojpeg.so.0" + ], + "provides": [ + "so:libcommon.so.0=0.0.0", + "so:libmc.so=0", + "so:libpainter.so.0=0.0.0", + "so:librfxencode.so.0=0.0.0", + "so:libscp.so.0=0.0.0", + "so:libvnc.so=0", + "so:libxrdp.so.0=0.0.0", + "so:libxrdpapi.so.0=0.0.0", + "so:libxup.so=0", + "cmd:xrdp", + "cmd:xrdp-chansrv", + "cmd:xrdp-dis", + "cmd:xrdp-genkeymap", + "cmd:xrdp-keygen", + "cmd:xrdp-sesadmin", + "cmd:xrdp-sesman", + "cmd:xrdp-sesrun" + ] + }, + "libgssglue-dev": { + "versions": { + "0.4-r0": 1509479796 + }, + "origin": "libgssglue", + "dependencies": [ + "libgssglue=0.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:libgssglue=0.4" + ] + }, + "sircbot": { + "versions": { + "0.4-r1": 1509492362 + }, + "origin": "sircbot", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sircbot", + "cmd:sircbot-send" + ] + }, + "db": { + "versions": { + "5.3.28-r0": 1509469316 + }, + "origin": "db", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdb-5.3.so=0" + ] + }, + "gzip-doc": { + "versions": { + "1.8-r0": 1509456993 + }, + "origin": "gzip" + }, + "imagemagick-libs": { + "versions": { + "7.0.7.11-r1": 1510748307 + }, + "origin": "imagemagick", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libgcc_s.so.1", + "so:libgomp.so.1", + "so:liblcms2.so.2", + "so:libltdl.so.7", + "so:libz.so.1" + ], + "provides": [ + "so:libMagickCore-7.Q16HDRI.so.4=4.0.0", + "so:libMagickWand-7.Q16HDRI.so.4=4.0.0" + ] + }, + "py-setuptools": { + "versions": { + "33.1.1-r1": 1509464242 + }, + "origin": "py-setuptools", + "dependencies": [ + "python2" + ], + "provides": [ + "py2-setuptools=33.1.1-r1", + "cmd:easy_install-2.7" + ] + }, + "dropbear-dbclient": { + "versions": { + "2018.76-r2": 1537439117 + }, + "origin": "dropbear", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:dbclient" + ] + }, + "gtkman-lang": { + "versions": { + "1.0.1-r0": 1510073898 + }, + "origin": "gtkman", + "dependencies": [ + "py-gtk" + ] + }, + "unifont-dev": { + "versions": { + "9.0.06-r0": 1509491372 + }, + "origin": "unifont" + }, + "pcre2-tools": { + "versions": { + "10.30-r0": 1509461774 + }, + "origin": "pcre2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libedit.so.0", + "so:libpcre2-16.so.0", + "so:libpcre2-32.so.0", + "so:libpcre2-8.so.0", + "so:libpcre2-posix.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:pcre2grep", + "cmd:pcre2test" + ] + }, + "xorg-server": { + "versions": { + "1.19.5-r1": 1540838460 + }, + "origin": "xorg-server", + "dependencies": [ + "font-misc-misc", + "font-cursor-misc", + "xkeyboard-config", + "xkbcomp", + "xinit", + "so:libGL.so.1", + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.0.0", + "so:libdrm.so.2", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libpciaccess.so.0", + "so:libpixman-1.so.0", + "so:libudev.so.1", + "so:libxshmfence.so.1" + ], + "provides": [ + "cmd:X", + "cmd:Xorg", + "cmd:cvt", + "cmd:gtf" + ] + }, + "tumbler": { + "versions": { + "0.2.0-r0": 1510075181 + }, + "origin": "tumbler", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcurl.so.4", + "so:libfreetype.so.6", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libpoppler-glib.so.8" + ], + "provides": [ + "so:libtumbler-1.so.0=0.0.0" + ] + }, + "perl-file-which-doc": { + "versions": { + "1.22-r0": 1509495219 + }, + "origin": "perl-file-which" + }, + "perl-class-singleton-doc": { + "versions": { + "1.5-r0": 1509485594 + }, + "origin": "perl-class-singleton" + }, + "py-jinja2": { + "versions": { + "2.9.6-r0": 1509476516 + }, + "origin": "py-jinja2" + }, + "perl-image-exiftool-doc": { + "versions": { + "10.55-r0": 1509495133 + }, + "origin": "perl-image-exiftool" + }, + "perl-test-number-delta-doc": { + "versions": { + "1.06-r0": 1509481691 + }, + "origin": "perl-test-number-delta" + }, + "gtkmm": { + "versions": { + "2.24.5-r0": 1510073814 + }, + "origin": "gtkmm", + "dependencies": [ + "so:libatkmm-1.6.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairomm-1.0.so.1", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgiomm-2.4.so.1", + "so:libglib-2.0.so.0", + "so:libglibmm-2.4.so.1", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpangomm-1.4.so.1", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgdkmm-2.4.so.1=1.1.0", + "so:libgtkmm-2.4.so.1=1.1.0" + ] + }, + "pjproject-dev": { + "versions": { + "2.5.5-r3": 1510258880 + }, + "origin": "pjproject", + "dependencies": [ + "libressl-dev", + "alsa-lib-dev", + "gsm-dev", + "speex-dev", + "speexdsp-dev", + "portaudio-dev", + "libsrtp-dev", + "libsamplerate-dev", + "pjproject=2.5.5-r3", + "pkgconfig" + ], + "provides": [ + "pc:libpjproject=2.5.5" + ] + }, + "git-subtree": { + "versions": { + "2.15.3-r0": 1540401294, + "2.15.4-r0": 1576015194 + }, + "origin": "git", + "dependencies": [ + "git=2.15.4-r0" + ] + }, + "taskd": { + "versions": { + "1.1.0-r4": 1509491729 + }, + "origin": "taskd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libstdc++.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:taskd", + "cmd:taskdctl" + ] + }, + "mosh-client": { + "versions": { + "1.3.2-r3": 1510846214 + }, + "origin": "mosh", + "dependencies": [ + "openssh-client", + "perl-io-tty", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libprotobuf.so.14", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:mosh-client" + ] + }, + "sems-conf_auth": { + "versions": { + "1.6.0-r6": 1510260895 + }, + "origin": "sems", + "dependencies": [ + "sems", + "sems-ivr" + ] + }, + "gnumeric-doc": { + "versions": { + "1.12.36-r0": 1511455615 + }, + "origin": "gnumeric" + }, + "cpufreqd": { + "versions": { + "2.4.2-r4": 1509492587 + }, + "origin": "cpufreqd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcpufreq.so.0", + "so:libsysfs.so.2" + ], + "provides": [ + "so:cpufreqd_acpi.so=0", + "so:cpufreqd_apm.so=0", + "so:cpufreqd_cpu.so=0", + "so:cpufreqd_exec.so=0", + "so:cpufreqd_governor_parameters.so=0", + "so:cpufreqd_nforce2.so=0", + "so:cpufreqd_pmu.so=0", + "so:cpufreqd_programs.so=0", + "so:cpufreqd_tau.so=0", + "cmd:cpufreqd", + "cmd:cpufreqd-get", + "cmd:cpufreqd-set" + ] + }, + "freeswitch-sounds-pt-BR-karina-8000": { + "versions": { + "1.0.51-r1": 1509495978 + }, + "origin": "freeswitch-sounds-pt-BR-karina-8000" + }, + "mkfontscale": { + "versions": { + "1.1.2-r1": 1509469866 + }, + "origin": "mkfontscale", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libfontenc.so.1", + "so:libfreetype.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:mkfontscale" + ] + }, + "libunwind": { + "versions": { + "1.2.1-r1": 1509493801 + }, + "origin": "libunwind", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libunwind-coredump.so.0=0.0.0", + "so:libunwind-ptrace.so.0=0.0.0", + "so:libunwind-setjmp.so.0=0.0.0", + "so:libunwind-x86_64.so.8=8.0.1", + "so:libunwind.so.8=8.0.1" + ] + }, + "perl-protocol-websocket": { + "versions": { + "0.20-r0": 1509474791 + }, + "origin": "perl-protocol-websocket", + "dependencies": [ + "perl" + ] + }, + "gst-plugins-base-doc": { + "versions": { + "1.12.3-r0": 1510070597 + }, + "origin": "gst-plugins-base" + }, + "xmlrpc-c-abyss": { + "versions": { + "1.39.11-r0": 1509482041 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxmlrpc.so.3", + "so:libxmlrpc_server.so.3", + "so:libxmlrpc_util.so.3" + ], + "provides": [ + "so:libxmlrpc_abyss.so.3=3.39", + "so:libxmlrpc_server_abyss.so.3=3.39" + ] + }, + "gsm-doc": { + "versions": { + "1.0.16-r0": 1509473211 + }, + "origin": "gsm" + }, + "intltool-doc": { + "versions": { + "0.51.0-r4": 1509464451 + }, + "origin": "intltool" + }, + "kamailio-http_async": { + "versions": { + "5.0.7-r0": 1532960877 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libevent-2.1.so.6" + ] + }, + "shorewall6": { + "versions": { + "5.1.8-r0": 1509489476 + }, + "origin": "shorewall6", + "dependencies": [ + "shorewall-core", + "perl", + "ip6tables", + "iproute2" + ] + }, + "popt-dev": { + "versions": { + "1.16-r7": 1509462411 + }, + "origin": "popt", + "dependencies": [ + "pkgconfig", + "popt=1.16-r7" + ], + "provides": [ + "pc:popt=1.16" + ] + }, + "perl-class-tiny-doc": { + "versions": { + "1.006-r0": 1510845710 + }, + "origin": "perl-class-tiny" + }, + "farstream0.1-dev": { + "versions": { + "0.1.2-r2": 1510933111 + }, + "origin": "farstream0.1", + "dependencies": [ + "libnice-dev", + "gstreamer0.10-dev", + "gst-plugins-base0.10-dev", + "farstream0.1=0.1.2-r2", + "pc:gstreamer-0.10", + "pc:gstreamer-base-0.10", + "pkgconfig" + ], + "provides": [ + "pc:farstream-0.1=0.1.2" + ] + }, + "perl-date-format-doc": { + "versions": { + "2.30-r0": 1510564547 + }, + "origin": "perl-date-format" + }, + "perl-exception-class": { + "versions": { + "1.43-r0": 1509470598 + }, + "origin": "perl-exception-class", + "dependencies": [ + "perl-devel-stacktrace", + "perl-class-data-inheritable" + ] + }, + "git-perl": { + "versions": { + "2.15.3-r0": 1540401295, + "2.15.4-r0": 1576015196 + }, + "origin": "git", + "dependencies": [ + "git=2.15.4-r0", + "perl-git=2.15.4-r0", + "perl" + ] + }, + "busybox-static": { + "versions": { + "1.27.2-r11": 1528276162 + }, + "origin": "busybox", + "provides": [ + "cmd:busybox.static" + ] + }, + "lua5.3-dev": { + "versions": { + "5.3.5-r2": 1557163184 + }, + "origin": "lua5.3", + "dependencies": [ + "lua5.3", + "lua5.3-libs=5.3.5-r2", + "pkgconfig" + ], + "provides": [ + "pc:lua5.3=5.3.5" + ] + }, + "libzmq": { + "versions": { + "4.2.5-r0": 1549279390, + "4.2.5-r1": 1563908217 + }, + "origin": "zeromq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libsodium.so.23", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libzmq.so.5=5.1.5" + ] + }, + "perl-switch": { + "versions": { + "2.17-r0": 1509475524 + }, + "origin": "perl-switch", + "dependencies": [ + "perl" + ] + }, + "iwlwifi-5150-ucode-doc": { + "versions": { + "8.24.2.2-r1": 1509475697 + }, + "origin": "iwlwifi-5150-ucode" + }, + "sems-ivr": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "python2", + "so:libc.musl-x86_64.so.1", + "so:libflite.so.1", + "so:libflite_cmu_us_kal.so.1", + "so:libflite_cmulex.so.1", + "so:libflite_usenglish.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "xfce4-panel-doc": { + "versions": { + "4.12.1-r1": 1510068172 + }, + "origin": "xfce4-panel" + }, + "lxc-download": { + "versions": { + "2.1.1-r3": 1533578833 + }, + "origin": "lxc", + "dependencies": [ + "lxc", + "gnupg", + "tar", + "xz", + "wget" + ] + }, + "xf86-video-chips-doc": { + "versions": { + "1.2.7-r0": 1510074063 + }, + "origin": "xf86-video-chips" + }, + "loudmouth-doc": { + "versions": { + "1.5.3-r0": 1509490879 + }, + "origin": "loudmouth" + }, + "libcap-ng": { + "versions": { + "0.7.8-r1": 1509461312 + }, + "origin": "libcap-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcap-ng.so.0=0.0.0" + ] + }, + "lua-dev": { + "versions": { + "5.1.5-r4": 1509471873 + }, + "origin": "lua", + "dependencies": [ + "lua5.1-dev", + "pkgconfig" + ] + }, + "libqrencode-doc": { + "versions": { + "4.0.0-r0": 1509482400 + }, + "origin": "libqrencode" + }, + "udisks-dev": { + "versions": { + "1.0.5-r3": 1510073401 + }, + "origin": "udisks", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:udisks=1.0.5" + ] + }, + "aspell": { + "versions": { + "0.60.6.1-r12": 1509472868, + "0.60.6.1-r13": 1572541141 + }, + "origin": "aspell", + "dependencies": [ + "so:libaspell.so.15", + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:aspell" + ] + }, + "kamailio-authephemeral": { + "versions": { + "5.0.7-r0": 1532960876 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "libsoup-dev": { + "versions": { + "2.60.2-r0": 1509480669 + }, + "origin": "libsoup", + "dependencies": [ + "gnutls-dev", + "sqlite-dev", + "libsoup=2.60.2-r0", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libsoup-2.4=2.60.2", + "pc:libsoup-gnome-2.4=2.60.2" + ] + }, + "lua5.1-iconv": { + "versions": { + "7-r1": 1509493322 + }, + "origin": "lua-iconv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gvpe-doc": { + "versions": { + "2.25-r4": 1510259350 + }, + "origin": "gvpe" + }, + "xrdp-doc": { + "versions": { + "0.9.2-r3": 1510260212 + }, + "origin": "xrdp" + }, + "rxvt-unicode": { + "versions": { + "9.22-r2": 1509476790 + }, + "origin": "rxvt-unicode", + "dependencies": [ + "rxvt-unicode-terminfo", + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libgcc_s.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libperl.so", + "so:libstartup-notification-1.so.0" + ], + "provides": [ + "cmd:urxvt", + "cmd:urxvtc", + "cmd:urxvtd" + ] + }, + "py2-vte": { + "versions": { + "0.28.2-r13": 1510071903 + }, + "origin": "vte", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libvte.so.9" + ] + }, + "perl-list-allutils": { + "versions": { + "0.14-r0": 1509493248 + }, + "origin": "perl-list-allutils", + "dependencies": [ + "perl-list-utilsby", + "perl-list-someutils", + "perl-scalar-list-utils" + ] + }, + "libfastjson-dbg": { + "versions": { + "0.99.7-r0": 1509486267 + }, + "origin": "libfastjson" + }, + "graphite2": { + "versions": { + "1.3.10-r0": 1509464858 + }, + "origin": "graphite2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgraphite2.so.3=3.0.1" + ] + }, + "xz": { + "versions": { + "5.2.3-r1": 1509459906 + }, + "origin": "xz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblzma.so.5" + ], + "provides": [ + "cmd:lzcat", + "cmd:lzcmp", + "cmd:lzdiff", + "cmd:lzegrep", + "cmd:lzfgrep", + "cmd:lzgrep", + "cmd:lzless", + "cmd:lzma", + "cmd:lzmadec", + "cmd:lzmainfo", + "cmd:lzmore", + "cmd:unlzma", + "cmd:unxz", + "cmd:xz", + "cmd:xzcat", + "cmd:xzcmp", + "cmd:xzdec", + "cmd:xzdiff", + "cmd:xzegrep", + "cmd:xzfgrep", + "cmd:xzgrep", + "cmd:xzless", + "cmd:xzmore" + ] + }, + "py2-alabaster": { + "versions": { + "0.7.10-r0": 1509476478 + }, + "origin": "py-alabaster", + "dependencies": [ + "python2" + ] + }, + "perl-test-deep": { + "versions": { + "1.126-r0": 1509468481 + }, + "origin": "perl-test-deep", + "dependencies": [ + "perl", + "perl-test-tester", + "perl-test-nowarnings" + ] + }, + "gpicview-lang": { + "versions": { + "0.2.5-r0": 1510074706 + }, + "origin": "gpicview" + }, + "zd1211-firmware": { + "versions": { + "1.5-r0": 1509496083 + }, + "origin": "zd1211-firmware" + }, + "xfce4-panel-dev": { + "versions": { + "4.12.1-r1": 1510068171 + }, + "origin": "xfce4-panel", + "dependencies": [ + "libxfce4util-dev", + "gtk+2.0-dev", + "gtk+3.0-dev", + "pc:glib-2.0", + "pc:gmodule-2.0", + "pc:gtk+-2.0", + "pc:gtk+-3.0", + "pc:libxfce4util-1.0", + "pkgconfig", + "xfce4-panel=4.12.1-r1" + ], + "provides": [ + "pc:libxfce4panel-1.0=4.12.1", + "pc:libxfce4panel-2.0=4.12.1" + ] + }, + "uwsgi-router_http": { + "versions": { + "2.0.17-r0": 1522154657 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "nagios-plugins-ups": { + "versions": { + "2.2.1-r3": 1510288498 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua-stringy": { + "versions": { + "0.4.0-r5": 1509480105 + }, + "origin": "lua-stringy" + }, + "cksfv": { + "versions": { + "1.3.14-r4": 1509486269 + }, + "origin": "cksfv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cksfv" + ] + }, + "acf-awall": { + "versions": { + "0.4.1-r2": 1510072215 + }, + "origin": "acf-awall", + "dependencies": [ + "acf-core", + "awall" + ] + }, + "nagios-plugins-wave": { + "versions": { + "2.2.1-r3": 1510288499 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins" + ] + }, + "http-parser": { + "versions": { + "2.7.1-r1": 1509483708 + }, + "origin": "http-parser", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhttp_parser.so.2.7.1=2.7.1" + ] + }, + "py2-oauthlib": { + "versions": { + "2.0.6-r1": 1509552759 + }, + "origin": "py-oauthlib", + "dependencies": [ + "py2-crypto", + "py2-jwt", + "python2" + ] + }, + "cpulimit": { + "versions": { + "0.2-r0": 1509489640 + }, + "origin": "cpulimit", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cpulimit" + ] + }, + "vde2": { + "versions": { + "2.3.2-r8": 1510260476 + }, + "origin": "vde2", + "dependencies": [ + "libressl", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libpcap.so.1", + "so:libvdehist.so.0", + "so:libvdemgmt.so.0", + "so:libvdeplug.so.3" + ], + "provides": [ + "cmd:dpipe", + "cmd:slirpvde", + "cmd:unixcmd", + "cmd:unixterm", + "cmd:vde_autolink", + "cmd:vde_cryptcab", + "cmd:vde_l3", + "cmd:vde_over_ns", + "cmd:vde_pcapplug", + "cmd:vde_plug", + "cmd:vde_plug2tap", + "cmd:vde_switch", + "cmd:vde_tunctl", + "cmd:vdecmd", + "cmd:vdekvm", + "cmd:vdeq", + "cmd:vdeqemu", + "cmd:vdeterm", + "cmd:wirefilter" + ] + }, + "libvpx": { + "versions": { + "1.6.1-r0": 1509480734 + }, + "origin": "libvpx", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libvpx.so.4=4.1.0" + ] + }, + "sems-annrecorder": { + "versions": { + "1.6.0-r6": 1510260897 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "perl-devel-symdump-doc": { + "versions": { + "2.18-r0": 1509470432 + }, + "origin": "perl-devel-symdump" + }, + "ruby-power_assert": { + "versions": { + "2.4.6-r0": 1557166822 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ] + }, + "aspell-lang": { + "versions": { + "0.60.6.1-r12": 1509472868, + "0.60.6.1-r13": 1572541141 + }, + "origin": "aspell" + }, + "libpri": { + "versions": { + "1.6.0-r0": 1509476161 + }, + "origin": "libpri", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpri.so.1.4=1.4" + ] + }, + "fltk-dev": { + "versions": { + "1.3.4-r0": 1510072237 + }, + "origin": "fltk", + "dependencies": [ + "libx11-dev", + "libxext-dev", + "libxft-dev", + "mesa-dev", + "fltk=1.3.4-r0" + ], + "provides": [ + "cmd:fltk-config" + ] + }, + "uwsgi-logsocket": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libxxf86vm-dev": { + "versions": { + "1.1.4-r1": 1509466237 + }, + "origin": "libxxf86vm", + "dependencies": [ + "libxxf86vm=1.1.4-r1", + "pc:x11", + "pc:xext", + "pc:xf86vidmodeproto", + "pkgconfig" + ], + "provides": [ + "pc:xxf86vm=1.1.4" + ] + }, + "bzip2-dev": { + "versions": { + "1.0.6-r6": 1509456387, + "1.0.6-r7": 1562268494 + }, + "origin": "bzip2", + "dependencies": [ + "libbz2=1.0.6-r7" + ] + }, + "rpcbind-dbg": { + "versions": { + "0.2.4-r0": 1509488290 + }, + "origin": "rpcbind" + }, + "py3-certifi": { + "versions": { + "2017.7.27.1-r1": 1509551896 + }, + "origin": "py-certifi", + "dependencies": [ + "python3" + ] + }, + "libbsd-dev": { + "versions": { + "0.8.6-r1": 1509461837 + }, + "origin": "libbsd", + "dependencies": [ + "bsd-compat-headers", + "linux-headers", + "libbsd=0.8.6-r1", + "pkgconfig" + ], + "provides": [ + "pc:libbsd-overlay=0.8.6", + "pc:libbsd=0.8.6" + ] + }, + "patchwork-uwsgi-apache2": { + "versions": { + "1.1.3-r0": 1509474237, + "1.1.3-r1": 1562223418 + }, + "origin": "patchwork", + "dependencies": [ + "apache2", + "apache2-mod-wsgi" + ] + }, + "farstream": { + "versions": { + "0.2.8-r2": 1510931395 + }, + "origin": "farstream", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-1.0.so.0", + "so:libgstnet-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgstrtp-1.0.so.0", + "so:libnice.so.10" + ], + "provides": [ + "so:libfarstream-0.2.so.5=5.1.0" + ] + }, + "lua-pingu": { + "versions": { + "1.5-r1": 1509474486 + }, + "origin": "pingu", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libarchive-tools": { + "versions": { + "3.3.2-r2": 1510258000, + "3.3.3-r0": 1566312165, + "3.3.3-r1": 1572677952 + }, + "origin": "libarchive", + "dependencies": [ + "so:libacl.so.1", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:liblz4.so.1", + "so:liblzma.so.5", + "so:libz.so.1" + ], + "provides": [ + "cmd:bsdcat", + "cmd:bsdcpio", + "cmd:bsdtar" + ] + }, + "py-gtk": { + "versions": { + "2.24.0-r14": 1510071867 + }, + "origin": "py-gtk", + "dependencies": [ + "py2-cairo", + "py-gobject", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglade-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0" + ], + "provides": [ + "cmd:pygtk-codegen-2.0" + ] + }, + "nodejs-dev": { + "versions": { + "8.9.3-r1": 1522616958 + }, + "origin": "nodejs", + "dependencies": [ + "libuv" + ], + "provides": [ + "nodejs-lts-dev=8.9.3" + ] + }, + "gst-plugins-ugly-lang": { + "versions": { + "1.12.3-r0": 1510075819 + }, + "origin": "gst-plugins-ugly" + }, + "atkmm-dev": { + "versions": { + "2.24.2-r0": 1509483360 + }, + "origin": "atkmm", + "dependencies": [ + "atkmm=2.24.2-r0", + "pc:atk>=1.18", + "pc:glibmm-2.4>=2.46.2", + "pkgconfig" + ], + "provides": [ + "pc:atkmm-1.6=2.24.2" + ] + }, + "radvd": { + "versions": { + "2.16-r0": 1509493026 + }, + "origin": "radvd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:radvd", + "cmd:radvdump" + ] + }, + "oniguruma": { + "versions": { + "6.6.1-r0": 1509470649 + }, + "origin": "oniguruma", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libonig.so.4=4.0.0" + ] + }, + "darkstat": { + "versions": { + "3.0.719-r0": 1509486286 + }, + "origin": "darkstat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:darkstat" + ] + }, + "gstreamer0.10-doc": { + "versions": { + "0.10.36-r2": 1509470981 + }, + "origin": "gstreamer0.10" + }, + "openjade": { + "versions": { + "1.3.2-r5": 1509488730 + }, + "origin": "openjade", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libogrove.so.0", + "so:libosp.so.5", + "so:libospgrove.so.0", + "so:libostyle.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:openjade" + ] + }, + "blkid": { + "versions": { + "2.31.1-r0": 1541506295 + }, + "origin": "util-linux", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:blkid" + ] + }, + "libxaw": { + "versions": { + "1.0.13-r0": 1509480584 + }, + "origin": "libxaw", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXmu.so.6", + "so:libXpm.so.4", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXaw.so.7=7.0.0" + ] + }, + "py-gobject3-dev": { + "versions": { + "3.24.1-r2": 1509481893 + }, + "origin": "py-gobject3", + "dependencies": [ + "pc:gobject-2.0", + "pc:libffi", + "pkgconfig" + ], + "provides": [ + "pc:pygobject-3.0=3.24.1" + ] + }, + "perl-sub-exporter-progressive-doc": { + "versions": { + "0.001013-r0": 1509473994 + }, + "origin": "perl-sub-exporter-progressive" + }, + "polkit": { + "versions": { + "0.105-r8": 1551780676, + "0.105-r9": 1563792450, + "0.105-r10": 1567523134 + }, + "origin": "polkit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libpolkit-agent-1.so.0=0.0.0", + "so:libpolkit-backend-1.so.0=0.0.0", + "so:libpolkit-gobject-1.so.0=0.0.0", + "cmd:pk-example-frobnicate", + "cmd:pkaction", + "cmd:pkcheck", + "cmd:pkexec", + "cmd:pkttyagent" + ] + }, + "tsocks-doc": { + "versions": { + "1.8_beta5-r0": 1509496537 + }, + "origin": "tsocks" + }, + "mp3splt-doc": { + "versions": { + "2.6.2-r0": 1509488737 + }, + "origin": "mp3splt" + }, + "perl-xml-sax-doc": { + "versions": { + "0.99-r2": 1509475904 + }, + "origin": "perl-xml-sax" + }, + "xen-hypervisor": { + "versions": { + "4.9.4-r0": 1551280495, + "4.9.4-r1": 1558103152 + }, + "origin": "xen" + }, + "nghttp2-doc": { + "versions": { + "1.28.0-r0": 1511922922, + "1.39.2-r0": 1568186892 + }, + "origin": "nghttp2" + }, + "debian-archive-keyring": { + "versions": { + "2017.6-r0": 1510588269 + }, + "origin": "debian-archive-keyring", + "dependencies": [ + "gnupg" + ] + }, + "libxpm-doc": { + "versions": { + "3.5.12-r0": 1509469832 + }, + "origin": "libxpm" + }, + "haserl-lua5.3": { + "versions": { + "0.9.35-r1": 1509468294 + }, + "origin": "haserl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua-5.3.so.0" + ], + "provides": [ + "cmd:haserl-lua5.3" + ] + }, + "cppunit-doc": { + "versions": { + "1.14.0-r0": 1509481255 + }, + "origin": "cppunit" + }, + "lua5.3-openrc": { + "versions": { + "0.2-r2": 1509492202 + }, + "origin": "lua-openrc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librc.so.1" + ] + }, + "postgresql-doc": { + "versions": { + "10.7-r0": 1554274198, + "10.8-r0": 1557668014, + "10.9-r0": 1562225613, + "10.10-r0": 1565683438 + }, + "origin": "postgresql" + }, + "mpg123-dev": { + "versions": { + "1.25.7-r0": 1509707456 + }, + "origin": "mpg123", + "dependencies": [ + "mpg123=1.25.7-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmpg123=1.25.7", + "pc:libout123=1.25.7" + ] + }, + "perl-string-shellquote-doc": { + "versions": { + "1.04-r0": 1509494702 + }, + "origin": "perl-string-shellquote" + }, + "nagios-plugins-ide_smart": { + "versions": { + "2.2.1-r3": 1510288495 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "mp3splt-gtk-lang": { + "versions": { + "0.9.2-r2": 1510070623 + }, + "origin": "mp3splt-gtk" + }, + "libexif-doc": { + "versions": { + "0.6.21-r2": 1539006611 + }, + "origin": "libexif" + }, + "libelf-dev": { + "versions": { + "0.8.13-r3": 1509468228 + }, + "origin": "libelf", + "dependencies": [ + "libelf=0.8.13-r3", + "pkgconfig" + ], + "provides": [ + "pc:libelf=0.8.13" + ] + }, + "nagios-plugins-sensors": { + "versions": { + "2.2.1-r3": 1510288498 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "grep", + "lm_sensors" + ] + }, + "hunspell-en": { + "versions": { + "2017.01.22-r0": 1511186496 + }, + "origin": "hunspell-en" + }, + "acf-lib": { + "versions": { + "0.10.1-r0": 1510068294 + }, + "origin": "acf-lib", + "dependencies": [ + "lua-subprocess" + ] + }, + "xf86-input-libinput-dev": { + "versions": { + "0.26.0-r0": 1510303211 + }, + "origin": "xf86-input-libinput", + "dependencies": [ + "libinput-dev", + "xorg-server-dev", + "pkgconfig" + ], + "provides": [ + "pc:xorg-libinput=0.26.0" + ] + }, + "libidn-dev": { + "versions": { + "1.33-r1": 1509468968 + }, + "origin": "libidn", + "dependencies": [ + "libidn=1.33-r1", + "pkgconfig" + ], + "provides": [ + "pc:libidn=1.33" + ] + }, + "dejagnu-dev": { + "versions": { + "1.6-r1": 1509491945 + }, + "origin": "dejagnu" + }, + "bzip2": { + "versions": { + "1.0.6-r6": 1509456387, + "1.0.6-r7": 1562268494 + }, + "origin": "bzip2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bunzip2", + "cmd:bzcat", + "cmd:bzcmp", + "cmd:bzdiff", + "cmd:bzegrep", + "cmd:bzfgrep", + "cmd:bzgrep", + "cmd:bzip2", + "cmd:bzip2recover", + "cmd:bzless", + "cmd:bzmore" + ] + }, + "perl-scope-guard": { + "versions": { + "0.21-r0": 1509482412 + }, + "origin": "perl-scope-guard", + "dependencies": [ + "perl" + ] + }, + "mesa-dri-virtio": { + "versions": { + "17.2.4-r1": 1510741947 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "stalonetray-doc": { + "versions": { + "0.8.3-r0": 1509489782 + }, + "origin": "stalonetray" + }, + "cpufrequtils": { + "versions": { + "008-r4": 1509486337 + }, + "origin": "cpufrequtils", + "dependencies": [ + "sysfsutils", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcpufreq.so.0=0.0.0", + "cmd:cpufreq-aperf", + "cmd:cpufreq-info", + "cmd:cpufreq-set" + ] + }, + "perl-specio-doc": { + "versions": { + "0.36-r0": 1509481612 + }, + "origin": "perl-specio" + }, + "perl-http-negotiate": { + "versions": { + "6.01-r1": 1509464410 + }, + "origin": "perl-http-negotiate", + "dependencies": [ + "perl", + "perl-http-message" + ] + }, + "gnutls": { + "versions": { + "3.6.1-r0": 1509465349 + }, + "origin": "gnutls", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libhogweed.so.4", + "so:libnettle.so.6", + "so:libp11-kit.so.0", + "so:libtasn1.so.6", + "so:libunistring.so.2" + ], + "provides": [ + "so:libgnutls.so.30=30.20.1" + ] + }, + "xvinfo-doc": { + "versions": { + "1.1.3-r0": 1509492434 + }, + "origin": "xvinfo" + }, + "nagios-plugins-tcp": { + "versions": { + "2.2.1-r3": 1510288499 + }, + "origin": "nagios-plugins", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "nagios-plugins-clamdnagios-plugins-ftpnagios-plugins-imapnagios-plugins-jabbernagios-plugins-nntpnagios-plugins-nntpsnagios-plugins-popnagios-plugins-simapnagios-plugins-spopnagios-plugins-ssmtpnagios-plugins-udp" + ] + }, + "cairo-gobject": { + "versions": { + "1.14.10-r0": 1509464620 + }, + "origin": "cairo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libcairo-gobject.so.2=2.11400.10" + ] + }, + "uwsgi-gevent": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libwebp-dev": { + "versions": { + "0.6.0-r1": 1509473038 + }, + "origin": "libwebp", + "dependencies": [ + "libwebp=0.6.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:libwebp=0.6.0", + "pc:libwebpdecoder=0.6.0", + "pc:libwebpdemux=0.6.0", + "pc:libwebpmux=0.6.0" + ] + }, + "asterisk-tds": { + "versions": { + "15.6.1-r0": 1537795340, + "15.6.2-r0": 1568705002 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsybdb.so.5" + ] + }, + "xf86-video-intel-doc": { + "versions": { + "2.99.917_git20170325-r0": 1510076034 + }, + "origin": "xf86-video-intel" + }, + "acf-amavisd-new": { + "versions": { + "0.4.0-r2": 1510076339 + }, + "origin": "acf-amavisd-new", + "dependencies": [ + "acf-core", + "amavisd-new" + ] + }, + "http-parser-dev": { + "versions": { + "2.7.1-r1": 1509483707 + }, + "origin": "http-parser", + "dependencies": [ + "http-parser=2.7.1-r1" + ] + }, + "sqlite-libs": { + "versions": { + "3.25.3-r0": 1548491335, + "3.25.3-r1": 1563951530, + "3.25.3-r2": 1571590443 + }, + "origin": "sqlite", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsqlite3.so.0=0.8.6" + ] + }, + "cups-client": { + "versions": { + "2.2.10-r0": 1549287809, + "2.2.12-r0": 1566207596 + }, + "origin": "cups", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2", + "so:libcupsimage.so.2", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:accept", + "cmd:cancel", + "cmd:cupsaccept", + "cmd:cupsaddsmb", + "cmd:cupsctl", + "cmd:cupsdisable", + "cmd:cupsenable", + "cmd:cupstestdsc", + "cmd:cupstestppd", + "cmd:ippfind", + "cmd:lp", + "cmd:lpadmin", + "cmd:lpc", + "cmd:lpinfo", + "cmd:lpmove", + "cmd:lpoptions", + "cmd:lpq", + "cmd:lpr", + "cmd:lprm", + "cmd:lpstat", + "cmd:ppdc", + "cmd:ppdhtml", + "cmd:ppdi", + "cmd:ppdmerge", + "cmd:ppdpo", + "cmd:reject" + ] + }, + "strongswan-dbg": { + "versions": { + "5.6.3-r2": 1539005310 + }, + "origin": "strongswan", + "dependencies": [ + "iproute2" + ] + }, + "recordproto": { + "versions": { + "1.14.2-r2": 1509466033 + }, + "origin": "recordproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:recordproto=1.14.2" + ] + }, + "perl-module-implementation": { + "versions": { + "0.09-r0": 1509470580 + }, + "origin": "perl-module-implementation", + "dependencies": [ + "perl-try-tiny", + "perl-module-runtime" + ] + }, + "highlight-doc": { + "versions": { + "3.41-r0": 1512029997 + }, + "origin": "highlight" + }, + "py-libvirt": { + "versions": { + "3.9.0-r0": 1510088408 + }, + "origin": "py-libvirt" + }, + "cups-doc": { + "versions": { + "2.2.10-r0": 1549287809, + "2.2.12-r0": 1566207590 + }, + "origin": "cups" + }, + "ruby-minitest": { + "versions": { + "2.4.6-r0": 1557166822 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ] + }, + "rhash": { + "versions": { + "1.3.5-r1": 1509461596 + }, + "origin": "rhash", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librhash.so.0" + ], + "provides": [ + "cmd:ed2k-link", + "cmd:gost-hash", + "cmd:has160-hash", + "cmd:magnet-link", + "cmd:rhash", + "cmd:sfv-hash", + "cmd:tiger-hash", + "cmd:tth-hash", + "cmd:whirlpool-hash" + ] + }, + "qemu-arm": { + "versions": { + "2.10.1-r3": 1519746239 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-arm" + ] + }, + "kbd-vlock": { + "versions": { + "2.0.4-r2": 1510922532 + }, + "origin": "kbd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0", + "so:libpam_misc.so.0" + ], + "provides": [ + "cmd:vlock" + ] + }, + "dbus-glib-doc": { + "versions": { + "0.108-r1": 1509465372 + }, + "origin": "dbus-glib" + }, + "xfce4-mixer-lang": { + "versions": { + "4.11.0-r2": 1510069965 + }, + "origin": "xfce4-mixer", + "dependencies": [ + "hicolor-icon-theme" + ] + }, + "mtdev": { + "versions": { + "1.1.5-r1": 1509475744 + }, + "origin": "mtdev", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmtdev.so.1=1.0.0" + ] + }, + "linux-virthardened-dev": { + "versions": { + "4.9.65-r1": 1511798478 + }, + "origin": "linux-hardened", + "dependencies": [ + "gmp-dev", + "bash", + "so:libc.musl-x86_64.so.1", + "so:libelf.so.1", + "so:libstdc++.so.6" + ] + }, + "live-media-dev": { + "versions": { + "2017.10.28-r0": 1510046891 + }, + "origin": "live-media", + "dependencies": [ + "live-media=2017.10.28-r0" + ] + }, + "feh": { + "versions": { + "2.22-r0": 1509878783 + }, + "origin": "feh", + "dependencies": [ + "so:libImlib2.so.1", + "so:libX11.so.6", + "so:libXinerama.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libpng16.so.16" + ], + "provides": [ + "cmd:feh" + ] + }, + "uwsgi-rsyslog": { + "versions": { + "2.0.17-r0": 1522154659 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "xextproto": { + "versions": { + "7.3.0-r2": 1509461947 + }, + "origin": "xextproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xextproto=7.3.0" + ] + }, + "thunar-archive-plugin-lang": { + "versions": { + "0.3.1-r1": 1510075227 + }, + "origin": "thunar-archive-plugin" + }, + "libgsasl": { + "versions": { + "1.8.0-r2": 1509493437 + }, + "origin": "libgsasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libidn.so.11" + ], + "provides": [ + "so:libgsasl.so.7=7.9.6" + ] + }, + "freeradius-krb5": { + "versions": { + "3.0.15-r4": 1556202797, + "3.0.15-r5": 1566310605 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.15-r5", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libkrb5.so.3" + ], + "provides": [ + "freeradius3-krb5=3.0.15-r5", + "so:rlm_krb5.so=0" + ] + }, + "perl-http-negotiate-doc": { + "versions": { + "6.01-r1": 1509464410 + }, + "origin": "perl-http-negotiate" + }, + "hunspell-lang": { + "versions": { + "1.6.2-r1": 1509477668 + }, + "origin": "hunspell" + }, + "uwsgi-router_uwsgi": { + "versions": { + "2.0.17-r0": 1522154658 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "squid": { + "versions": { + "3.5.27-r0": 1519824033, + "3.5.27-r1": 1562865670 + }, + "origin": "squid", + "dependencies": [ + "logrotate", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.42", + "so:libdb-5.3.so", + "so:libgcc_s.so.1", + "so:libltdl.so.7", + "so:libssl.so.44", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:purge", + "cmd:squid", + "cmd:squidclient" + ] + }, + "bash": { + "versions": { + "4.4.19-r1": 1518031135 + }, + "origin": "bash", + "dependencies": [ + "/bin/sh", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:bash", + "cmd:bashbug" + ] + }, + "lua5.1-dbi-sqlite3": { + "versions": { + "0.6-r1": 1511483402 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "postgresql-plperl": { + "versions": { + "10.7-r0": 1554274199, + "10.8-r0": 1557668015, + "10.9-r0": 1562225614, + "10.10-r0": 1565683438 + }, + "origin": "postgresql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "libestr-dev": { + "versions": { + "0.1.10-r0": 1509486151 + }, + "origin": "libestr", + "dependencies": [ + "libestr=0.1.10-r0", + "pkgconfig" + ], + "provides": [ + "pc:libestr=0.1.10" + ] + }, + "chrpath-doc": { + "versions": { + "0.16-r1": 1509466380 + }, + "origin": "chrpath" + }, + "cairo-doc": { + "versions": { + "1.14.10-r0": 1509464620 + }, + "origin": "cairo" + }, + "perl-test-identity": { + "versions": { + "0.01-r0": 1509477788 + }, + "origin": "perl-test-identity" + }, + "libart-lgpl-dev": { + "versions": { + "2.3.21-r5": 1509475376 + }, + "origin": "libart-lgpl", + "dependencies": [ + "libart-lgpl=2.3.21-r5", + "pkgconfig" + ], + "provides": [ + "pc:libart-2.0=2.3.21", + "cmd:libart2-config" + ] + }, + "lua-dns": { + "versions": { + "20080404-r2": 1509488749 + }, + "origin": "lua-dns", + "dependencies": [ + "lua", + "lua-socket" + ] + }, + "usbredir": { + "versions": { + "0.7.1-r0": 1509707469 + }, + "origin": "usbredir", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libusbredirhost.so.1=1.0.0", + "so:libusbredirparser.so.1=1.0.0" + ] + }, + "boost-dev": { + "versions": { + "1.62.0-r5": 1509465870 + }, + "origin": "boost", + "dependencies": [ + "linux-headers", + "boost-date_time=1.62.0-r5", + "boost-filesystem=1.62.0-r5", + "boost-graph=1.62.0-r5", + "boost-iostreams=1.62.0-r5", + "boost-math=1.62.0-r5", + "boost-prg_exec_monitor=1.62.0-r5", + "boost-program_options=1.62.0-r5", + "boost-python3=1.62.0-r5", + "boost-python=1.62.0-r5", + "boost-random=1.62.0-r5", + "boost-regex=1.62.0-r5", + "boost-serialization=1.62.0-r5", + "boost-signals=1.62.0-r5", + "boost-system=1.62.0-r5", + "boost-thread=1.62.0-r5", + "boost-unit_test_framework=1.62.0-r5", + "boost-wave=1.62.0-r5", + "boost-wserialization=1.62.0-r5", + "boost=1.62.0-r5" + ] + }, + "py3-flask-wtf": { + "versions": { + "0.14.2-r0": 1509476546 + }, + "origin": "py-flask-wtf", + "dependencies": [ + "py3-flask", + "py3-wtforms", + "python3" + ] + }, + "texinfo-doc": { + "versions": { + "6.5-r0": 1509456625 + }, + "origin": "texinfo" + }, + "xfce4-cpufreq-plugin": { + "versions": { + "1.1.3-r2": 1510074007 + }, + "origin": "xfce4-cpufreq-plugin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libxfce4panel-1.0.so.4", + "so:libxfce4ui-1.so.0", + "so:libxfce4util.so.7" + ] + }, + "irssi-doc": { + "versions": { + "1.0.6-r0": 1519052408, + "1.0.8-r0": 1562236916 + }, + "origin": "irssi" + }, + "g++": { + "versions": { + "6.4.0-r5": 1509458073 + }, + "origin": "gcc", + "dependencies": [ + "libstdc++=6.4.0-r5", + "gcc=6.4.0-r5", + "libc-dev", + "libstdc++=6.4.0-r5", + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libisl.so.15", + "so:libmpc.so.3", + "so:libmpfr.so.4", + "so:libz.so.1" + ], + "provides": [ + "cmd:c++", + "cmd:g++", + "cmd:x86_64-alpine-linux-musl-c++", + "cmd:x86_64-alpine-linux-musl-g++" + ] + }, + "tcpflow-doc": { + "versions": { + "1.4.5-r3": 1510260713, + "1.5.0-r1": 1562596229 + }, + "origin": "tcpflow" + }, + "execline-dev": { + "versions": { + "2.3.0.3-r0": 1509488629 + }, + "origin": "execline", + "dependencies": [ + "execline=2.3.0.3-r0" + ] + }, + "apache2-webdav": { + "versions": { + "2.4.39-r0": 1554306882, + "2.4.41-r0": 1566292328 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1" + ] + }, + "fossil": { + "versions": { + "2.3-r1": 1510260449 + }, + "origin": "fossil", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libsqlite3.so.0", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "cmd:fossil" + ] + }, + "lxc-libs": { + "versions": { + "2.1.1-r3": 1533578833 + }, + "origin": "lxc", + "dependencies": [ + "gzip", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libseccomp.so.2" + ], + "provides": [ + "so:liblxc.so.1=1.3.0" + ] + }, + "pssh": { + "versions": { + "2.3.1-r1": 1509494678 + }, + "origin": "pssh", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:pnuke", + "cmd:prsync", + "cmd:pscp", + "cmd:pslurp", + "cmd:pssh" + ] + }, + "py3-cairo": { + "versions": { + "1.10.0-r0": 1509481861 + }, + "origin": "py3-cairo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libpython3.6m.so.1.0" + ] + }, + "abiword-plugin-eml": { + "versions": { + "3.0.2-r1": 1510073358 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "py2-imagesize": { + "versions": { + "0.7.1-r2": 1509474090 + }, + "origin": "py-imagesize", + "dependencies": [ + "python2" + ] + }, + "uwsgi-router_static": { + "versions": { + "2.0.17-r0": 1522154658 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "xtrans": { + "versions": { + "1.3.5-r1": 1509461954 + }, + "origin": "xtrans" + }, + "font-adobe-utopia-75dpi": { + "versions": { + "1.0.4-r0": 1509489768 + }, + "origin": "font-adobe-utopia-75dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "oidentd": { + "versions": { + "2.0.8-r5": 1509483895 + }, + "origin": "oidentd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:oidentd" + ] + }, + "mpc1-doc": { + "versions": { + "1.0.3-r1": 1509457077 + }, + "origin": "mpc1" + }, + "libxcb-doc": { + "versions": { + "1.12-r1": 1509461940 + }, + "origin": "libxcb" + }, + "usb-modeswitch-doc": { + "versions": { + "2.5.1-r0": 1509480147 + }, + "origin": "usb-modeswitch" + }, + "py3-pillow": { + "versions": { + "4.3.0-r0": 1509489959 + }, + "origin": "py-pillow", + "dependencies": [ + "py3-olefile", + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libopenjp2.so.7", + "so:libpython3.6m.so.1.0", + "so:libtiff.so.5", + "so:libwebp.so.7", + "so:libwebpmux.so.3", + "so:libz.so.1" + ] + }, + "renderproto-doc": { + "versions": { + "0.11.1-r3": 1509462088 + }, + "origin": "renderproto" + }, + "py-egenix-mx-base": { + "versions": { + "3.2.9-r0": 1509474197 + }, + "origin": "py-egenix-mx-base", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "squid-lang-es": { + "versions": { + "3.5.27-r0": 1519824031, + "3.5.27-r1": 1562865667 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "libmemcached-doc": { + "versions": { + "1.0.18-r2": 1520366184 + }, + "origin": "libmemcached" + }, + "libunistring": { + "versions": { + "0.9.7-r0": 1509459000 + }, + "origin": "libunistring", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libunistring.so.2=2.0.0" + ] + }, + "valgrind-doc": { + "versions": { + "3.12.0-r1": 1509494840 + }, + "origin": "valgrind" + }, + "nsd-doc": { + "versions": { + "4.1.16-r1": 1510260999 + }, + "origin": "nsd" + }, + "zfs-dev": { + "versions": { + "0.7.3-r0": 1509490073 + }, + "origin": "zfs", + "dependencies": [ + "glib-dev", + "e2fsprogs-dev", + "util-linux-dev", + "libtirpc-dev", + "attr-dev", + "pkgconfig", + "zfs-libs=0.7.3-r0" + ], + "provides": [ + "pc:libzfs=0.7.3", + "pc:libzfs_core=0.7.3" + ] + }, + "nginx-mod-http-shibboleth": { + "versions": { + "1.12.2-r4": 1542814448 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.3-lub": { + "versions": { + "1.1.0-r1": 1509475704 + }, + "origin": "lua-lub", + "dependencies": [ + "lua5.3", + "lua5.3-filesystem" + ] + }, + "vanessa_adt-dev": { + "versions": { + "0.0.9-r0": 1509481358 + }, + "origin": "vanessa_adt", + "dependencies": [ + "vanessa_logger-dev", + "vanessa_adt=0.0.9-r0" + ] + }, + "libxdamage-dev": { + "versions": { + "1.1.4-r1": 1509464691 + }, + "origin": "libxdamage", + "dependencies": [ + "damageproto", + "fixesproto", + "libxdamage=1.1.4-r1", + "pc:damageproto>=1.1", + "pc:x11", + "pc:xfixes", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xdamage=1.1.4" + ] + }, + "ciwiki-doc": { + "versions": { + "2.0.5-r1": 1509493442 + }, + "origin": "ciwiki" + }, + "spl-hardened": { + "versions": { + "4.9.65-r1": 1511798817 + }, + "origin": "spl-hardened", + "dependencies": [ + "linux-hardened=4.9.65-r1" + ], + "provides": [ + "spl-grsec=4.9.65-r1" + ] + }, + "perl-heap": { + "versions": { + "0.80-r1": 1511989874 + }, + "origin": "perl-heap" + }, + "apr-util-dev": { + "versions": { + "1.6.1-r1": 1510285059 + }, + "origin": "apr-util", + "dependencies": [ + "expat-dev", + "apr-dev", + "openldap-dev", + "sqlite-dev", + "postgresql-dev", + "db-dev", + "libressl-dev", + "apr-util=1.6.1-r1", + "pc:apr-1", + "pkgconfig" + ], + "provides": [ + "pc:apr-util-1=1.6.1", + "cmd:apu-1-config" + ] + }, + "open-lldp": { + "versions": { + "0.9.46-r3": 1510075777 + }, + "origin": "open-lldp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libconfig.so.9", + "so:libnl.so.1" + ], + "provides": [ + "so:liblldp_clif.so.1=1.0.0", + "cmd:dcbtool", + "cmd:lldpad", + "cmd:lldptool" + ] + }, + "perl-regexp-ipv6": { + "versions": { + "0.03-r0": 1509470472 + }, + "origin": "perl-regexp-ipv6", + "dependencies": [ + "perl" + ] + }, + "libnetfilter_cttimeout-dev": { + "versions": { + "1.0.0-r0": 1509469232 + }, + "origin": "libnetfilter_cttimeout", + "dependencies": [ + "libmnl-dev", + "libnetfilter_cttimeout=1.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_cttimeout=1.0.0" + ] + }, + "fftw": { + "versions": { + "3.3.6p2-r0": 1509468831 + }, + "origin": "fftw", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfftw3.so.3", + "so:libfftw3_threads.so.3", + "so:libfftw3f.so.3", + "so:libfftw3f_threads.so.3", + "so:libfftw3l.so.3", + "so:libfftw3l_threads.so.3" + ], + "provides": [ + "cmd:fftw-wisdom", + "cmd:fftw-wisdom-to-conf", + "cmd:fftwf-wisdom", + "cmd:fftwl-wisdom" + ] + }, + "qemu-img": { + "versions": { + "2.10.1-r3": 1519746244 + }, + "origin": "qemu", + "dependencies": [ + "so:libaio.so.1", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libssh2.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-img", + "cmd:qemu-io", + "cmd:qemu-nbd" + ] + }, + "jack-example-clients": { + "versions": { + "1.9.10-r5": 1509473320 + }, + "origin": "jack", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libjack.so.0", + "so:libjacknet.so.0", + "so:libjackserver.so.0", + "so:libreadline.so.7", + "so:libsamplerate.so.0", + "so:libsndfile.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:alsa_in", + "cmd:alsa_out", + "cmd:jack_alias", + "cmd:jack_bufsize", + "cmd:jack_connect", + "cmd:jack_control", + "cmd:jack_cpu", + "cmd:jack_cpu_load", + "cmd:jack_disconnect", + "cmd:jack_evmon", + "cmd:jack_freewheel", + "cmd:jack_iodelay", + "cmd:jack_latent_client", + "cmd:jack_load", + "cmd:jack_lsp", + "cmd:jack_metro", + "cmd:jack_midi_dump", + "cmd:jack_midi_latency_test", + "cmd:jack_midiseq", + "cmd:jack_midisine", + "cmd:jack_monitor_client", + "cmd:jack_multiple_metro", + "cmd:jack_net_master", + "cmd:jack_net_slave", + "cmd:jack_netsource", + "cmd:jack_rec", + "cmd:jack_samplerate", + "cmd:jack_server_control", + "cmd:jack_session_notify", + "cmd:jack_showtime", + "cmd:jack_simple_client", + "cmd:jack_simple_session_client", + "cmd:jack_test", + "cmd:jack_thru", + "cmd:jack_transport", + "cmd:jack_unload", + "cmd:jack_wait", + "cmd:jack_zombie" + ] + }, + "libtirpc": { + "versions": { + "1.0.1-r2": 1509469768 + }, + "origin": "libtirpc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgssapi_krb5.so.2" + ], + "provides": [ + "so:libtirpc.so.3=3.0.0" + ] + }, + "bash-completion": { + "versions": { + "2.7-r3": 1510573048 + }, + "origin": "bash-completion", + "dependencies": [ + "bash" + ] + }, + "perl-cpan-meta-check": { + "versions": { + "0.013-r0": 1509468486 + }, + "origin": "perl-cpan-meta-check", + "dependencies": [ + "perl-module-metadata>=1.000023" + ] + }, + "freeswitch-sounds-en-us-callie-8000": { + "versions": { + "1.0.51-r0": 1509476708 + }, + "origin": "freeswitch-sounds-en-us-callie-8000" + }, + "menu-cache": { + "versions": { + "0.5.1-r1": 1509469822 + }, + "origin": "menu-cache", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libmenu-cache.so.3=3.0.1" + ] + }, + "mesa-dev": { + "versions": { + "17.2.4-r1": 1510741944 + }, + "origin": "mesa", + "dependencies": [ + "libdrm-dev", + "dri2proto", + "libxext-dev", + "libxdamage-dev", + "libxcb-dev", + "glproto", + "dri3proto", + "presentproto", + "libxshmfence-dev", + "mesa-egl=17.2.4-r1", + "mesa-gbm=17.2.4-r1", + "mesa-gl=17.2.4-r1", + "mesa-glapi=17.2.4-r1", + "mesa-gles=17.2.4-r1", + "mesa-libwayland-egl=17.2.4-r1", + "mesa-osmesa=17.2.4-r1", + "mesa-xatracker=17.2.4-r1", + "pc:libdrm>=2.4.75", + "pc:wayland-client", + "pc:x11", + "pc:x11-xcb", + "pc:xcb", + "pc:xcb-dri2>=1.8", + "pc:xcb-glx>=1.8.1", + "pc:xdamage>=1.1", + "pc:xext", + "pc:xfixes", + "pc:xxf86vm", + "pkgconfig" + ], + "provides": [ + "pc:dri=17.2.4", + "pc:egl=17.2.4", + "pc:gbm=17.2.4", + "pc:gl=17.2.4", + "pc:glesv1_cm=17.2.4", + "pc:glesv2=17.2.4", + "pc:osmesa=8", + "pc:wayland-egl=17.2.4", + "pc:xatracker=2.3.0" + ] + }, + "jansson-dev": { + "versions": { + "2.10-r0": 1509472128 + }, + "origin": "jansson", + "dependencies": [ + "jansson=2.10-r0", + "pkgconfig" + ], + "provides": [ + "pc:jansson=2.10" + ] + }, + "hexchat": { + "versions": { + "2.12.4-r1": 1510260816 + }, + "origin": "hexchat", + "dependencies": [ + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libdbus-glib-1.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:liblua-5.3.so.0", + "so:libnotify.so.4", + "so:libpango-1.0.so.0", + "so:libproxy.so.1", + "so:libssl.so.44" + ], + "provides": [ + "pc:hexchat-plugin=2.12.4", + "cmd:hexchat" + ] + }, + "fftw-long-double-libs": { + "versions": { + "3.3.6p2-r0": 1509468830 + }, + "origin": "fftw", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfftw3l.so.3=3.5.6", + "so:libfftw3l_threads.so.3=3.5.6" + ] + }, + "terminus-font": { + "versions": { + "4.46-r0": 1509491617 + }, + "origin": "terminus-font" + }, + "py-click": { + "versions": { + "6.7-r2": 1509476499 + }, + "origin": "py-click" + }, + "perl-cgi-psgi-doc": { + "versions": { + "0.15-r1": 1510564479 + }, + "origin": "perl-cgi-psgi" + }, + "freeradius-checkrad": { + "versions": { + "3.0.15-r4": 1556202797, + "3.0.15-r5": 1566310605 + }, + "origin": "freeradius", + "dependencies": [ + "perl", + "perl-net-telnet", + "perl-snmp-session", + "net-snmp-tools" + ], + "provides": [ + "cmd:checkrad" + ] + }, + "ir_keytable": { + "versions": { + "1.12.5-r1": 1510072047 + }, + "origin": "v4l-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8" + ], + "provides": [ + "cmd:ir-keytable" + ] + }, + "rrdtool-cached": { + "versions": { + "1.5.6-r3": 1512296349 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:librrd_th.so.4" + ], + "provides": [ + "cmd:rrdcached" + ] + }, + "gstreamer": { + "versions": { + "1.12.3-r0": 1509470920 + }, + "origin": "gstreamer", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libgstbase-1.0.so.0=0.1203.0", + "so:libgstcheck-1.0.so.0=0.1203.0", + "so:libgstcontroller-1.0.so.0=0.1203.0", + "so:libgstnet-1.0.so.0=0.1203.0", + "so:libgstreamer-1.0.so.0=0.1203.0" + ] + }, + "perl-html-mason-doc": { + "versions": { + "1.58-r0": 1509470628 + }, + "origin": "perl-html-mason" + }, + "libconfig": { + "versions": { + "1.5-r3": 1509706433 + }, + "origin": "libconfig", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libconfig.so.9=9.2.0" + ] + }, + "libnfnetlink-dev": { + "versions": { + "1.0.1-r1": 1509469209 + }, + "origin": "libnfnetlink", + "dependencies": [ + "libnfnetlink=1.0.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:libnfnetlink=1.0.1" + ] + }, + "libassuan-dev": { + "versions": { + "2.4.4-r0": 1512029943 + }, + "origin": "libassuan", + "dependencies": [ + "libassuan=2.4.4-r0" + ], + "provides": [ + "cmd:libassuan-config" + ] + }, + "py-jwt": { + "versions": { + "1.5.0-r1": 1509552755 + }, + "origin": "py-jwt" + }, + "lua-md5": { + "versions": { + "1.2-r3": 1509468361 + }, + "origin": "lua-md5" + }, + "lsyncd": { + "versions": { + "2.2.2-r0": 1509493062 + }, + "origin": "lsyncd", + "dependencies": [ + "rsync", + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ], + "provides": [ + "cmd:lsyncd" + ] + }, + "fakeroot-doc": { + "versions": { + "1.21-r1": 1509459622 + }, + "origin": "fakeroot" + }, + "readline": { + "versions": { + "7.0.003-r0": 1509456790 + }, + "origin": "readline", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "so:libreadline.so.7=7.0" + ] + }, + "libquadmath": { + "versions": { + "6.4.0-r5": 1509458072 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libquadmath.so.0=0.0.0" + ] + }, + "perl-set-intspan-doc": { + "versions": { + "1.19-r0": 1509480155 + }, + "origin": "perl-set-intspan" + }, + "gst-plugins-base-lang": { + "versions": { + "1.12.3-r0": 1510070597 + }, + "origin": "gst-plugins-base" + }, + "perl-getopt-long": { + "versions": { + "2.50-r0": 1509491105 + }, + "origin": "perl-getopt-long" + }, + "spice-gtk-tools": { + "versions": { + "0.34-r1": 1510260978 + }, + "origin": "spice-gtk", + "dependencies": [ + "so:libacl.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgtk-3.so.0", + "so:libpolkit-gobject-1.so.0", + "so:libspice-client-glib-2.0.so.8", + "so:libspice-client-gtk-3.0.so.5" + ], + "provides": [ + "cmd:spice-client-glib-usb-acl-helper", + "cmd:spicy", + "cmd:spicy-screenshot", + "cmd:spicy-stats" + ] + }, + "patchutils": { + "versions": { + "0.3.4-r0": 1509495754 + }, + "origin": "patchutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:combinediff", + "cmd:dehtmldiff", + "cmd:editdiff", + "cmd:espdiff", + "cmd:filterdiff", + "cmd:fixcvsdiff", + "cmd:flipdiff", + "cmd:grepdiff", + "cmd:interdiff", + "cmd:lsdiff", + "cmd:recountdiff", + "cmd:rediff", + "cmd:splitdiff", + "cmd:unwrapdiff" + ] + }, + "py-gtk-dev": { + "versions": { + "2.24.0-r14": 1510071866 + }, + "origin": "py-gtk", + "dependencies": [ + "py-gobject-dev", + "py-gtk", + "pc:gtk+-2.0", + "pc:pygobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:pygtk-2.0=2.24.0" + ] + }, + "nagios-plugins-time": { + "versions": { + "2.2.1-r3": 1510288498 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "poppler": { + "versions": { + "0.56.0-r0": 1509465068, + "0.56.0-r1": 1569334548 + }, + "origin": "poppler", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libopenjp2.so.7", + "so:libpng16.so.16", + "so:libstdc++.so.6", + "so:libtiff.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libpoppler-cpp.so.0=0.3.0", + "so:libpoppler.so.67=67.0.0" + ] + }, + "perl-scope-guard-doc": { + "versions": { + "0.21-r0": 1509482409 + }, + "origin": "perl-scope-guard" + }, + "obex-data-server-doc": { + "versions": { + "0.4.6-r4": 1510071752 + }, + "origin": "obex-data-server" + }, + "clang-static": { + "versions": { + "5.0.0-r0": 1510683246 + }, + "origin": "clang" + }, + "awall-masquerade": { + "versions": { + "1.5.0-r0": 1509742667 + }, + "origin": "awall", + "dependencies": [ + "awall" + ] + }, + "aoetools-doc": { + "versions": { + "37-r0": 1509477685 + }, + "origin": "aoetools" + }, + "libconfig-dev": { + "versions": { + "1.5-r3": 1509706433 + }, + "origin": "libconfig", + "dependencies": [ + "libconfig++=1.5-r3", + "libconfig=1.5-r3", + "pkgconfig" + ], + "provides": [ + "pc:libconfig++=1.5", + "pc:libconfig=1.5" + ] + }, + "qemu-system-ppcemb": { + "versions": { + "2.10.1-r3": 1519746243 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-ppcemb" + ] + }, + "lua5.2-ldbus": { + "versions": { + "20150430-r2": 1509492576 + }, + "origin": "lua-ldbus", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3" + ] + }, + "nagios-web": { + "versions": { + "3.5.1-r4": 1509494203 + }, + "origin": "nagios", + "dependencies": [ + "perl" + ] + }, + "expat": { + "versions": { + "2.2.5-r0": 1509908122, + "2.2.7-r0": 1561897528, + "2.2.7-r1": 1568353727, + "2.2.8-r0": 1568974114 + }, + "origin": "expat", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libexpat.so.1=1.6.10", + "cmd:xmlwf" + ] + }, + "bluez-doc": { + "versions": { + "5.47-r3": 1510069786 + }, + "origin": "bluez" + }, + "xdpyinfo-doc": { + "versions": { + "1.3.2-r0": 1509491067 + }, + "origin": "xdpyinfo" + }, + "perl-test-manifest": { + "versions": { + "2.02-r0": 1509471896 + }, + "origin": "perl-test-manifest" + }, + "perl-io-stringy-doc": { + "versions": { + "2.111-r0": 1509469879 + }, + "origin": "perl-io-stringy" + }, + "libpaper-dev": { + "versions": { + "1.1.24-r3": 1509465358 + }, + "origin": "libpaper", + "dependencies": [ + "libpaper=1.1.24-r3" + ] + }, + "iperf3-doc": { + "versions": { + "3.2-r0": 1509475695 + }, + "origin": "iperf3" + }, + "perl-class-data-inheritable": { + "versions": { + "0.08-r0": 1509470594 + }, + "origin": "perl-class-data-inheritable" + }, + "lua5.3-pty": { + "versions": { + "1.2.1-r1": 1509496424 + }, + "origin": "lua-pty", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-idna": { + "versions": { + "2.6-r0": 1509476381 + }, + "origin": "py-idna", + "dependencies": [ + "python2" + ] + }, + "jpeg": { + "versions": { + "8-r6": 1509469923 + }, + "origin": "jpeg", + "dependencies": [ + "libjpeg-turbo-utils" + ] + }, + "perl-css-squish-doc": { + "versions": { + "0.10-r0": 1509470429 + }, + "origin": "perl-css-squish" + }, + "libressl2.6-libcrypto": { + "versions": { + "2.6.5-r0": 1529043883 + }, + "origin": "libressl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcrypto.so.42=42.0.0" + ] + }, + "gcc-gnat": { + "versions": { + "6.4.0-r5": 1509458081 + }, + "origin": "gcc", + "dependencies": [ + "gcc=6.4.0-r5", + "libgnat=6.4.0-r5", + "libgnat=6.4.0-r5", + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libisl.so.15", + "so:libmpc.so.3", + "so:libmpfr.so.4", + "so:libz.so.1" + ], + "provides": [ + "cmd:gnat", + "cmd:gnatbind", + "cmd:gnatchop", + "cmd:gnatclean", + "cmd:gnatfind", + "cmd:gnatkr", + "cmd:gnatlink", + "cmd:gnatls", + "cmd:gnatmake", + "cmd:gnatname", + "cmd:gnatprep", + "cmd:gnatxref" + ] + }, + "cgdb": { + "versions": { + "0.7.0-r1": 1509495074 + }, + "origin": "cgdb", + "dependencies": [ + "gdb", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libreadline.so.7", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:cgdb" + ] + }, + "gnome-doc-utils-doc": { + "versions": { + "0.20.10-r2": 1509627733 + }, + "origin": "gnome-doc-utils" + }, + "perl-test-leaktrace-doc": { + "versions": { + "0.15-r3": 1509474016 + }, + "origin": "perl-test-leaktrace" + }, + "conky": { + "versions": { + "1.10.6-r0": 1512047603 + }, + "origin": "conky", + "dependencies": [ + "so:libImlib2.so.1", + "so:libX11.so.6", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libgcc_s.so.1", + "so:liblua-5.2.so.0", + "so:libncursesw.so.6", + "so:libstdc++.so.6", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:conky" + ] + }, + "gpsd-dev": { + "versions": { + "3.16-r3": 1510311401 + }, + "origin": "gpsd", + "dependencies": [ + "gpsd=3.16-r3" + ] + }, + "perl-test-mockrandom-doc": { + "versions": { + "1.01-r1": 1510855674 + }, + "origin": "perl-test-mockrandom" + }, + "llvm5-libs": { + "versions": { + "5.0.0-r0": 1510682601 + }, + "origin": "llvm5", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "llvm-libs=5.0.0-r0", + "so:libLLVM-5.0.so=0" + ] + }, + "vde2-doc": { + "versions": { + "2.3.2-r8": 1510260476 + }, + "origin": "vde2" + }, + "libxscrnsaver-doc": { + "versions": { + "1.2.2-r1": 1509480774 + }, + "origin": "libxscrnsaver" + }, + "speexdsp": { + "versions": { + "1.2_rc3-r4": 1509473234 + }, + "origin": "speexdsp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libspeexdsp.so.1=1.5.0" + ] + }, + "lua5.2-crypto": { + "versions": { + "0.3.2-r5": 1510261400 + }, + "origin": "lua-crypto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "libxxf86dga-doc": { + "versions": { + "1.1.4-r1": 1509473787 + }, + "origin": "libxxf86dga" + }, + "py-django-sorl-thumbnail": { + "versions": { + "12.4.1-r0": 1517313676 + }, + "origin": "py-django-sorl-thumbnail", + "dependencies": [ + "py-django", + "py-pillow" + ] + }, + "nagios-plugins-dbi": { + "versions": { + "2.2.1-r3": 1510288494 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libdbi.so.1" + ] + }, + "openldap-overlay-collect": { + "versions": { + "2.4.45-r3": 1510258134, + "2.4.46-r0": 1565073939, + "2.4.48-r0": 1566900516 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "dri2proto": { + "versions": { + "2.8-r2": 1509466228 + }, + "origin": "dri2proto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:dri2proto=2.8" + ] + }, + "byacc-doc": { + "versions": { + "20170709-r0": 1510310594 + }, + "origin": "byacc" + }, + "libva-intel-driver": { + "versions": { + "1.7.3-r0": 1510073939 + }, + "origin": "libva-intel-driver", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_intel.so.1" + ] + }, + "perl-crypt-des-doc": { + "versions": { + "2.07-r4": 1509471888 + }, + "origin": "perl-crypt-des" + }, + "udns-dev": { + "versions": { + "0.4-r0": 1509491388 + }, + "origin": "udns", + "dependencies": [ + "udns=0.4-r0" + ] + }, + "json-c-static": { + "versions": { + "0.12.1-r1": 1509466320 + }, + "origin": "json-c" + }, + "libxft-doc": { + "versions": { + "2.3.2-r2": 1509462101 + }, + "origin": "libxft" + }, + "devicemaster-linux-doc": { + "versions": { + "7.15-r0": 1509481942 + }, + "origin": "devicemaster-linux" + }, + "libgssglue-doc": { + "versions": { + "0.4-r0": 1509479796 + }, + "origin": "libgssglue" + }, + "perl-net-libidn": { + "versions": { + "0.12-r3": 1509468972 + }, + "origin": "perl-net-libidn", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libidn.so.11" + ] + }, + "ruby-io-console": { + "versions": { + "2.4.6-r0": 1557166822 + }, + "origin": "ruby", + "dependencies": [ + "ruby", + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.4" + ] + }, + "bacula-client": { + "versions": { + "9.0.5-r3": 1512992127 + }, + "origin": "bacula", + "dependencies": [ + "so:libacl.so.1", + "so:libbac-9.0.5.so", + "so:libbaccfg-9.0.5.so", + "so:libbacfind-9.0.5.so", + "so:libc.musl-x86_64.so.1", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:bacula", + "cmd:bacula-fd", + "cmd:bconsole", + "cmd:btraceback" + ] + }, + "mupdf-dev": { + "versions": { + "1.13.0-r0": 1533746007 + }, + "origin": "mupdf", + "dependencies": [ + "mupdf=1.13.0-r0" + ] + }, + "thunar-vcs-plugin": { + "versions": { + "0.1.4-r6": 1510314594 + }, + "origin": "thunar-vcs-plugin", + "dependencies": [ + "so:libapr-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libsvn_client-1.so.0", + "so:libsvn_fs-1.so.0", + "so:libsvn_subr-1.so.0", + "so:libsvn_wc-1.so.0" + ] + }, + "fribidi": { + "versions": { + "0.19.7-r0": 1509480347 + }, + "origin": "fribidi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfribidi.so.0=0.3.6", + "cmd:fribidi" + ] + }, + "newt-lang": { + "versions": { + "0.52.20-r0": 1509476146 + }, + "origin": "newt" + }, + "txt2man-doc": { + "versions": { + "1.6.0-r0": 1509475653 + }, + "origin": "txt2man" + }, + "nmap": { + "versions": { + "7.60-r2": 1510261468, + "7.60-r3": 1572297245 + }, + "origin": "nmap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libgcc_s.so.1", + "so:libpcap.so.1", + "so:libssl.so.44", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:nmap" + ] + }, + "memcached-dev": { + "versions": { + "1.5.6-r0": 1520365888 + }, + "origin": "memcached", + "dependencies": [ + "memcached=1.5.6-r0" + ] + }, + "perl-http-cookies": { + "versions": { + "6.04-r0": 1511871378 + }, + "origin": "perl-http-cookies", + "dependencies": [ + "perl", + "perl-http-date", + "perl-http-message" + ] + }, + "perl-params-validationcompiler": { + "versions": { + "0.24-r1": 1510588893 + }, + "origin": "perl-params-validationcompiler", + "dependencies": [ + "perl-specio", + "perl-test2-suite", + "perl-test-simple", + "perl-test-without-module", + "perl-eval-closure", + "perl-exception-class", + "perl-test2-plugin-nowarnings", + "perl-role-tiny", + "perl-mro-compat" + ] + }, + "mesa-xatracker": { + "versions": { + "17.2.4-r1": 1510741947 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_freedreno.so.1", + "so:libdrm_nouveau.so.2", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libxatracker.so.2=2.3.0" + ] + }, + "perl-devel-stacktrace-doc": { + "versions": { + "2.02-r0": 1509468384 + }, + "origin": "perl-devel-stacktrace" + }, + "py3-asn1": { + "versions": { + "0.4.2-r0": 1511889291 + }, + "origin": "py-asn1", + "dependencies": [ + "python3" + ] + }, + "libcrypto1.0": { + "versions": { + "1.0.2r-r0": 1552814668, + "1.0.2t-r0": 1568300415 + }, + "origin": "openssl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libcrypto.so.1.0.0=1.0.0" + ] + }, + "icecast": { + "versions": { + "2.4.4-r0": 1541491284 + }, + "origin": "icecast", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libogg.so.0", + "so:libssl.so.44", + "so:libtheora.so.0", + "so:libvorbis.so.0", + "so:libxml2.so.2", + "so:libxslt.so.1" + ], + "provides": [ + "cmd:icecast" + ] + }, + "lvm2-libs": { + "versions": { + "2.02.175-r0": 1509462402 + }, + "origin": "lvm2", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper-event.so.1.02", + "so:libdevmapper.so.1.02" + ], + "provides": [ + "so:libdevmapper-event-lvm2.so.2.02=2.02", + "so:liblvm2app.so.2.2=2.2", + "so:liblvm2cmd.so.2.02=2.02" + ] + }, + "xf86-video-i128": { + "versions": { + "1.3.6-r8": 1510074908 + }, + "origin": "xf86-video-i128", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gnuchess": { + "versions": { + "6.2.5-r0": 1509496082 + }, + "origin": "gnuchess", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:gnuchess", + "cmd:gnuchessu", + "cmd:gnuchessx" + ] + }, + "fixesproto": { + "versions": { + "5.0-r2": 1509464679 + }, + "origin": "fixesproto", + "dependencies": [ + "xextproto", + "pc:xextproto>=7.0.99.1", + "pkgconfig" + ], + "provides": [ + "pc:fixesproto=5.0" + ] + }, + "darkhttpd": { + "versions": { + "1.12-r2": 1526305370 + }, + "origin": "darkhttpd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:darkhttpd" + ] + }, + "rsyslog-tls": { + "versions": { + "8.31.0-r0": 1512031980, + "8.31.0-r1": 1571767017 + }, + "origin": "rsyslog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30" + ] + }, + "py3-django": { + "versions": { + "1.11.20-r0": 1551365037, + "1.11.21-r0": 1561497063, + "1.11.22-r0": 1563783118, + "1.11.23-r0": 1565087799 + }, + "origin": "py-django", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:django-admin-3" + ] + }, + "dropbear-doc": { + "versions": { + "2018.76-r2": 1537439117 + }, + "origin": "dropbear" + }, + "samba-server": { + "versions": { + "4.7.6-r3": 1555491788 + }, + "origin": "samba", + "dependencies": [ + "samba-common=4.7.6-r3", + "samba-initscript=4.7.6-r3", + "so:libCHARSET3-samba4.so", + "so:libauth-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-nbt-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcliauth-samba4.so", + "so:libdbwrap-samba4.so", + "so:libgenrand-samba4.so", + "so:libgse-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmessages-dgm-samba4.so", + "so:libmsghdr-samba4.so", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libpopt-samba3-samba4.so", + "so:libpopt.so.0", + "so:libreplace-samba4.so", + "so:libsamba-cluster-support-samba4.so", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsecrets3-samba4.so", + "so:libserver-id-db-samba4.so", + "so:libsmb-transport-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-base-samba4.so", + "so:libsmbd-shim-samba4.so", + "so:libsocket-blocking-samba4.so", + "so:libsys-rw-samba4.so", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-reg-samba4.so", + "so:libutil-tdb-samba4.so" + ], + "provides": [ + "so:libxattr-tdb-samba4.so=0", + "cmd:eventlogadm", + "cmd:nmbd", + "cmd:smbd", + "cmd:smbstatus" + ] + }, + "pcre": { + "versions": { + "8.41-r1": 1509464278 + }, + "origin": "pcre", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre.so.1=1.2.9", + "so:libpcreposix.so.0=0.0.5" + ] + }, + "ntfs-3g-dev": { + "versions": { + "2017.3.23-r1": 1509489437 + }, + "origin": "ntfs-3g", + "dependencies": [ + "ntfs-3g-libs=2017.3.23-r1", + "pkgconfig" + ], + "provides": [ + "pc:libntfs-3g=2017.3.23" + ] + }, + "ssl_client": { + "versions": { + "1.27.2-r11": 1528276162 + }, + "origin": "busybox", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtls.so.16" + ], + "provides": [ + "cmd:ssl_client" + ] + }, + "s6-networking-doc": { + "versions": { + "2.3.0.2-r1": 1510260983 + }, + "origin": "s6-networking" + }, + "git": { + "versions": { + "2.15.3-r0": 1540401303, + "2.15.4-r0": 1576015212 + }, + "origin": "git", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libexpat.so.1", + "so:libpcre2-8.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:git", + "cmd:git-receive-pack", + "cmd:git-shell", + "cmd:git-upload-archive", + "cmd:git-upload-pack" + ] + }, + "perl-io-captureoutput": { + "versions": { + "1.1104-r0": 1509489308 + }, + "origin": "perl-io-captureoutput" + }, + "cryptsetup": { + "versions": { + "1.7.5-r1": 1510261295 + }, + "origin": "cryptsetup", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcryptsetup.so.4", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:cryptsetup", + "cmd:veritysetup" + ] + }, + "collectd-disk": { + "versions": { + "5.7.2-r0": 1510069652 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ] + }, + "intltool": { + "versions": { + "0.51.0-r4": 1509464452 + }, + "origin": "intltool", + "dependencies": [ + "perl", + "perl-xml-parser", + "gettext", + "file" + ], + "provides": [ + "cmd:intltool-extract", + "cmd:intltool-merge", + "cmd:intltool-prepare", + "cmd:intltool-update", + "cmd:intltoolize" + ] + }, + "libxfce4ui-dev": { + "versions": { + "4.12.1-r3": 1510067993 + }, + "origin": "libxfce4ui", + "dependencies": [ + "startup-notification-dev", + "libxfce4ui-gtk3=4.12.1-r3", + "libxfce4ui=4.12.1-r3", + "pc:gdk-2.0", + "pc:gdk-3.0", + "pc:gtk+-2.0", + "pc:gtk+-3.0", + "pc:libxfce4util-1.0", + "pc:libxfconf-0", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libgladeui-1.so.11", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libxfce4ui-1.so.0" + ], + "provides": [ + "pc:libxfce4kbd-private-2=4.12.1", + "pc:libxfce4kbd-private-3=4.12.1", + "pc:libxfce4ui-1=4.12.1", + "pc:libxfce4ui-2=4.12.1" + ] + }, + "asterisk-speex": { + "versions": { + "15.6.1-r0": 1537795343, + "15.6.2-r0": 1568705005 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libspeex.so.1", + "so:libspeexdsp.so.1" + ] + }, + "udns-doc": { + "versions": { + "0.4-r0": 1509491393 + }, + "origin": "udns" + }, + "perl-test2-suite": { + "versions": { + "0.000083-r0": 1510564541 + }, + "origin": "perl-test2-suite", + "dependencies": [ + "perl-test-simple", + "perl-importer", + "perl-term-table", + "perl-sub-info" + ] + }, + "gst-plugins-good-doc": { + "versions": { + "1.12.3-r0": 1510075045 + }, + "origin": "gst-plugins-good" + }, + "boost-program_options": { + "versions": { + "1.62.0-r5": 1509465877 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_program_options-mt.so.1.62.0=1.62.0", + "so:libboost_program_options.so.1.62.0=1.62.0" + ] + }, + "dmidecode": { + "versions": { + "3.1-r0": 1509482141 + }, + "origin": "dmidecode", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:biosdecode", + "cmd:dmidecode", + "cmd:ownership", + "cmd:vpddecode" + ] + }, + "alpine": { + "versions": { + "2.21-r1": 1510259111 + }, + "origin": "alpine", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libncursesw.so.6", + "so:libssl.so.44" + ], + "provides": [ + "cmd:alpine", + "cmd:pico", + "cmd:pilot", + "cmd:rpdump", + "cmd:rpload" + ] + }, + "pcre-tools": { + "versions": { + "8.41-r1": 1509464277 + }, + "origin": "pcre", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1", + "so:libpcre16.so.0", + "so:libpcre32.so.0", + "so:libpcreposix.so.0" + ], + "provides": [ + "cmd:pcregrep", + "cmd:pcretest" + ] + }, + "videoproto": { + "versions": { + "2.3.3-r1": 1509467165 + }, + "origin": "videoproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:videoproto=2.3.3" + ] + }, + "gtk-engines-mist": { + "versions": { + "2.21.0-r2": 1510071723 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "libmng-doc": { + "versions": { + "2.0.3-r1": 1509480914 + }, + "origin": "libmng" + }, + "dconf": { + "versions": { + "0.26.0-r1": 1509492217 + }, + "origin": "dconf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libdconf.so.1=1.0.0", + "cmd:dconf" + ] + }, + "zfs-udev": { + "versions": { + "0.7.3-r0": 1509490074 + }, + "origin": "zfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "acf-openntpd": { + "versions": { + "0.9.0-r2": 1510073454 + }, + "origin": "acf-openntpd", + "dependencies": [ + "acf-core", + "openntpd" + ] + }, + "libestr": { + "versions": { + "0.1.10-r0": 1509486154 + }, + "origin": "libestr", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libestr.so.0=0.0.0" + ] + }, + "subversion-libs": { + "versions": { + "1.9.7-r0": 1510314504, + "1.9.12-r0": 1565086191 + }, + "origin": "subversion", + "dependencies": [ + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:libexpat.so.1", + "so:libsasl2.so.3", + "so:libserf-1.so.1", + "so:libsqlite3.so.0", + "so:libz.so.1" + ], + "provides": [ + "so:libsvn_client-1.so.0=0.0.0", + "so:libsvn_delta-1.so.0=0.0.0", + "so:libsvn_diff-1.so.0=0.0.0", + "so:libsvn_fs-1.so.0=0.0.0", + "so:libsvn_fs_base-1.so.0=0.0.0", + "so:libsvn_fs_fs-1.so.0=0.0.0", + "so:libsvn_fs_util-1.so.0=0.0.0", + "so:libsvn_fs_x-1.so.0=0.0.0", + "so:libsvn_ra-1.so.0=0.0.0", + "so:libsvn_ra_local-1.so.0=0.0.0", + "so:libsvn_ra_serf-1.so.0=0.0.0", + "so:libsvn_ra_svn-1.so.0=0.0.0", + "so:libsvn_repos-1.so.0=0.0.0", + "so:libsvn_subr-1.so.0=0.0.0", + "so:libsvn_wc-1.so.0=0.0.0" + ] + }, + "cdparanoia-dev": { + "versions": { + "10.2-r7": 1509471028 + }, + "origin": "cdparanoia", + "dependencies": [ + "cdparanoia-libs=10.2-r7" + ] + }, + "tumbler-lang": { + "versions": { + "0.2.0-r0": 1510075180 + }, + "origin": "tumbler" + }, + "py-tz": { + "versions": { + "2017.3-r0": 1510732132 + }, + "origin": "py-tz" + }, + "ldns-tools": { + "versions": { + "1.6.17-r6": 1510258937 + }, + "origin": "ldns", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libldns.so.1", + "so:libssl.so.44" + ], + "provides": [ + "cmd:ldns-chaos", + "cmd:ldns-compare-zones", + "cmd:ldns-dane", + "cmd:ldns-dpa", + "cmd:ldns-gen-zone", + "cmd:ldns-key2ds", + "cmd:ldns-keyfetcher", + "cmd:ldns-keygen", + "cmd:ldns-mx", + "cmd:ldns-notify", + "cmd:ldns-nsec3-hash", + "cmd:ldns-read-zone", + "cmd:ldns-resolver", + "cmd:ldns-revoke", + "cmd:ldns-rrsig", + "cmd:ldns-signzone", + "cmd:ldns-test-edns", + "cmd:ldns-testns", + "cmd:ldns-update", + "cmd:ldns-verify-zone", + "cmd:ldns-version", + "cmd:ldns-walk", + "cmd:ldns-zcat", + "cmd:ldns-zsplit", + "cmd:ldnsd" + ] + }, + "upower-dev": { + "versions": { + "0.99.6-r0": 1510068213 + }, + "origin": "upower", + "dependencies": [ + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pkgconfig", + "upower=0.99.6-r0" + ], + "provides": [ + "pc:upower-glib=0.99.6" + ] + }, + "nagios-plugins-swap": { + "versions": { + "2.2.1-r3": 1510288498 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "dconf-doc": { + "versions": { + "0.26.0-r1": 1509492217 + }, + "origin": "dconf" + }, + "pinentry": { + "versions": { + "1.0.0-r0": 1510587945 + }, + "origin": "pinentry", + "dependencies": [ + "/bin/sh", + "so:libassuan.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libgpg-error.so.0", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:pinentry-curses" + ] + }, + "drbd9-hardened": { + "versions": { + "4.9.65-r1": 1511799284 + }, + "origin": "drbd9-hardened", + "dependencies": [ + "linux-hardened=4.9.65-r1" + ], + "provides": [ + "drbd9-grsec=4.9.65-r1" + ] + }, + "libev": { + "versions": { + "4.24-r0": 1509469569 + }, + "origin": "libev", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libev.so.4=4.0.0" + ] + }, + "libpurple": { + "versions": { + "2.12.0-r2": 1510069758 + }, + "origin": "pidgin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libidn.so.11", + "so:libintl.so.8", + "so:libnspr4.so", + "so:libnss3.so", + "so:libsasl2.so.3", + "so:libsmime3.so", + "so:libssl3.so", + "so:libxml2.so.2" + ], + "provides": [ + "so:autoaccept.so=0", + "so:buddynote.so=0", + "so:idle.so=0", + "so:joinpart.so=0", + "so:libgg.so=0", + "so:libirc.so=0", + "so:libnovell.so=0", + "so:libpurple.so.0=0.12.0", + "so:libsimple.so=0", + "so:libzephyr.so=0", + "so:log_reader.so=0", + "so:newline.so=0", + "so:nss-prefs.so=0", + "so:offlinemsg.so=0", + "so:psychic.so=0", + "so:ssl-nss.so=0", + "so:ssl.so=0", + "so:statenotify.so=0" + ] + }, + "lua5.3-socket": { + "versions": { + "3.0_rc1_git20160306-r2": 1509480762 + }, + "origin": "lua-socket", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "sems-xmlrpc2di": { + "versions": { + "1.6.0-r6": 1510260896 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libssl.so.44", + "so:libstdc++.so.6" + ] + }, + "atkmm": { + "versions": { + "2.24.2-r0": 1509483360 + }, + "origin": "atkmm", + "dependencies": [ + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libglibmm-2.4.so.1", + "so:libgobject-2.0.so.0", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libatkmm-1.6.so.1=1.1.0" + ] + }, + "unionfs-fuse-doc": { + "versions": { + "1.0-r0": 1509489287 + }, + "origin": "unionfs-fuse" + }, + "ser2net": { + "versions": { + "3.4-r1": 1509492428 + }, + "origin": "ser2net", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ser2net" + ] + }, + "perl-http-daemon-doc": { + "versions": { + "6.01-r1": 1509464384 + }, + "origin": "perl-http-daemon" + }, + "fribidi-doc": { + "versions": { + "0.19.7-r0": 1509480345 + }, + "origin": "fribidi" + }, + "dnsmasq-doc": { + "versions": { + "2.78-r3": 1540537793 + }, + "origin": "dnsmasq" + }, + "perl-scope-upper": { + "versions": { + "0.30-r0": 1509952235 + }, + "origin": "perl-scope-upper", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-json-maybexs-doc": { + "versions": { + "1.003009-r0": 1510352918 + }, + "origin": "perl-json-maybexs" + }, + "lua-lyaml": { + "versions": { + "6.1.3-r1": 1509473486 + }, + "origin": "lua-lyaml" + }, + "dbus-glib": { + "versions": { + "0.108-r1": 1509465372 + }, + "origin": "dbus-glib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libexpat.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libdbus-glib-1.so.2=2.3.3", + "cmd:dbus-binding-tool" + ] + }, + "garcon-doc": { + "versions": { + "0.6.1-r1": 1510068108 + }, + "origin": "garcon" + }, + "help2man": { + "versions": { + "1.47.5-r0": 1509456690 + }, + "origin": "help2man", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:help2man" + ] + }, + "perl-config-inifiles": { + "versions": { + "2.94-r0": 1509489532 + }, + "origin": "perl-config-inifiles", + "dependencies": [ + "perl-list-moreutils" + ] + }, + "py3-docutils": { + "versions": { + "0.13.1-r0": 1509486245 + }, + "origin": "py-docutils", + "dependencies": [ + "py3-pillow", + "py3-roman", + "python3" + ], + "provides": [ + "cmd:rst2html-3", + "cmd:rst2html5-3", + "cmd:rst2latex-3", + "cmd:rst2man-3", + "cmd:rst2odt-3", + "cmd:rst2odt_prepstyles-3", + "cmd:rst2pseudoxml-3", + "cmd:rst2s5-3", + "cmd:rst2xetex-3", + "cmd:rst2xml-3", + "cmd:rstpep2html-3" + ] + }, + "ntfs-3g": { + "versions": { + "2017.3.23-r1": 1509489439 + }, + "origin": "ntfs-3g", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libntfs-3g.so.88" + ], + "provides": [ + "cmd:lowntfs-3g", + "cmd:ntfs-3g" + ] + }, + "vte-dev": { + "versions": { + "0.28.2-r13": 1510071902 + }, + "origin": "vte", + "dependencies": [ + "pango-dev", + "gtk+2.0-dev", + "pc:cairo-xlib", + "pc:gio-2.0", + "pc:gio-unix-2.0", + "pc:glib-2.0>=2.26.0", + "pc:gobject-2.0", + "pc:gtk+-2.0>=2.20.0", + "pc:pango>=1.22.0", + "pc:x11", + "pkgconfig", + "vte=0.28.2-r13" + ], + "provides": [ + "pc:pyvte=0.28.2", + "pc:vte=0.28.2" + ] + }, + "uwsgi-zergpool": { + "versions": { + "2.0.17-r0": 1522154661 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "hostapd": { + "versions": { + "2.6-r3": 1510261213, + "2.6-r4": 1559723195, + "2.6-r5": 1559724985, + "2.6-r6": 1568724303 + }, + "origin": "hostapd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libnl-3.so.200", + "so:libnl-genl-3.so.200", + "so:libssl.so.44" + ], + "provides": [ + "cmd:hostapd", + "cmd:hostapd_cli", + "cmd:nt_password_hash" + ] + }, + "giflib-utils": { + "versions": { + "5.1.4-r1": 1509470481 + }, + "origin": "giflib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgif.so.7" + ], + "provides": [ + "cmd:gif2rgb", + "cmd:gifbuild", + "cmd:gifclrmp", + "cmd:gifecho", + "cmd:giffix", + "cmd:gifinto", + "cmd:giftext", + "cmd:giftool" + ] + }, + "libasr-doc": { + "versions": { + "1.0.2-r6": 1510258974 + }, + "origin": "libasr" + }, + "xf86-video-apm": { + "versions": { + "1.2.5-r8": 1510070495 + }, + "origin": "xf86-video-apm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "msmtp": { + "versions": { + "1.6.6-r2": 1510261496 + }, + "origin": "msmtp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "cmd:msmtp" + ] + }, + "apache-mod-fcgid": { + "versions": { + "2.3.9-r1": 1509492409 + }, + "origin": "apache-mod-fcgid", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1" + ] + }, + "scrnsaverproto-doc": { + "versions": { + "1.2.2-r2": 1509473902 + }, + "origin": "scrnsaverproto" + }, + "itstool-doc": { + "versions": { + "2.0.4-r3": 1511265097 + }, + "origin": "itstool" + }, + "perl-devel-checkbin-doc": { + "versions": { + "0.02-r0": 1509474054 + }, + "origin": "perl-devel-checkbin" + }, + "dovecot-gssapi": { + "versions": { + "2.2.36.3-r0": 1554102390, + "2.2.36.4-r0": 1567075099 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot", + "so:libc.musl-x86_64.so.1", + "so:libgssapi.so.3" + ] + }, + "perl-test-requires": { + "versions": { + "0.10-r0": 1509470573 + }, + "origin": "perl-test-requires" + }, + "unixodbc-doc": { + "versions": { + "2.3.4-r2": 1509469609 + }, + "origin": "unixodbc" + }, + "py2-requests": { + "versions": { + "2.18.4-r0": 1509483042 + }, + "origin": "py-requests", + "dependencies": [ + "py2-chardet", + "py2-idna", + "py2-certifi", + "py2-urllib3", + "python2" + ] + }, + "cjdns": { + "versions": { + "20-r1": 1510601040 + }, + "origin": "cjdns", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cjdroute" + ] + }, + "inotify-tools-doc": { + "versions": { + "3.14-r2": 1509475674 + }, + "origin": "inotify-tools" + }, + "duplicity-doc": { + "versions": { + "0.7.13.1-r0": 1510588266 + }, + "origin": "duplicity" + }, + "perl-log-dispatch": { + "versions": { + "2.67-r0": 1510953098 + }, + "origin": "perl-log-dispatch", + "dependencies": [ + "perl-module-runtime", + "perl-params-validate", + "perl-dist-checkconflicts", + "perl-devel-globaldestruction" + ] + }, + "feh-doc": { + "versions": { + "2.22-r0": 1509878783 + }, + "origin": "feh" + }, + "openldap-overlay-dynlist": { + "versions": { + "2.4.45-r3": 1510258134, + "2.4.46-r0": 1565073940, + "2.4.48-r0": 1566900517 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "collectd-rrdtool": { + "versions": { + "5.7.2-r0": 1510069647 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:librrd_th.so.4" + ] + }, + "perl-date-manip": { + "versions": { + "6.60-r0": 1509492186 + }, + "origin": "perl-date-manip", + "dependencies": [ + "perl-yaml-syck" + ], + "provides": [ + "cmd:dm_date", + "cmd:dm_zdump" + ] + }, + "py-dateutil": { + "versions": { + "2.6.1-r0": 1509475855 + }, + "origin": "py-dateutil", + "dependencies": [ + "py-six" + ] + }, + "perl-timedate": { + "versions": { + "2.30-r1": 1509469038 + }, + "origin": "perl-timedate", + "provides": [ + "perl-time-date=2.30" + ] + }, + "nginx-mod-http-upload-progress": { + "versions": { + "1.12.2-r4": 1542814448 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "znc-dev": { + "versions": { + "1.7.1-r0": 1531900839, + "1.7.1-r1": 1565877330 + }, + "origin": "znc", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:znc=1.7.1", + "cmd:znc-buildmod" + ] + }, + "xf86-video-vmware-doc": { + "versions": { + "13.2.1-r0": 1510072911 + }, + "origin": "xf86-video-vmware" + }, + "tig-doc": { + "versions": { + "2.3.0-r0": 1511889467 + }, + "origin": "tig" + }, + "lua-sql-sqlite3": { + "versions": { + "2.3.5-r1": 1509488834 + }, + "origin": "lua-sql" + }, + "apache-mod-fcgid-doc": { + "versions": { + "2.3.9-r1": 1509492408 + }, + "origin": "apache-mod-fcgid" + }, + "lm_sensors-detect": { + "versions": { + "3.4.0-r4": 1509475443 + }, + "origin": "lm_sensors", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:sensors-conf-convert", + "cmd:sensors-detect" + ] + }, + "perl-test-file-sharedir-doc": { + "versions": { + "1.001002-r0": 1510855689 + }, + "origin": "perl-test-file-sharedir" + }, + "darkice-doc": { + "versions": { + "1.3-r0": 1509470067 + }, + "origin": "darkice" + }, + "py2-roman": { + "versions": { + "2.0.0-r2": 1509493353 + }, + "origin": "py-roman", + "dependencies": [ + "python2" + ] + }, + "py-uritemplate": { + "versions": { + "3.0.0-r0": 1509486308 + }, + "origin": "py-uritemplate" + }, + "perl-uri-doc": { + "versions": { + "1.71-r1": 1509464365 + }, + "origin": "perl-uri" + }, + "qemu-system-cris": { + "versions": { + "2.10.1-r3": 1519746241 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-cris" + ] + }, + "libnfs-doc": { + "versions": { + "2.0.0-r0": 1509493089 + }, + "origin": "libnfs" + }, + "py2-snowballstemmer": { + "versions": { + "1.2.1-r1": 1509489493 + }, + "origin": "py-snowballstemmer", + "dependencies": [ + "python2" + ] + }, + "lua5.2-dev": { + "versions": { + "5.2.4-r4": 1509459694 + }, + "origin": "lua5.2", + "dependencies": [ + "lua5.2", + "lua5.2-libs=5.2.4-r4", + "pkgconfig" + ], + "provides": [ + "pc:lua5.2=5.2.4" + ] + }, + "kyua-doc": { + "versions": { + "0.13-r1": 1509459786 + }, + "origin": "kyua" + }, + "atk": { + "versions": { + "2.26.1-r1": 1509464672 + }, + "origin": "atk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libatk-1.0.so.0=0.22610.1" + ] + }, + "py3-bind": { + "versions": { + "9.11.6_p1-r1": 1556872768, + "9.11.8-r0": 1561323229 + }, + "origin": "bind", + "dependencies": [ + "python3", + "py3-ply" + ] + }, + "xfconf-lang": { + "versions": { + "4.12.1-r2": 1510067933 + }, + "origin": "xfconf" + }, + "directfb": { + "versions": { + "1.7.7-r1": 1509488976 + }, + "origin": "directfb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libfreetype.so.6", + "so:libgcc_s.so.1", + "so:libkms.so.1", + "so:libpng16.so.16", + "so:libstdc++.so.6", + "so:libts.so.0" + ], + "provides": [ + "so:lib++dfb-1.7.so.7=7.0.0", + "so:libdirect-1.7.so.7=7.0.0", + "so:libdirectfb-1.7.so.7=7.0.0", + "so:libfusion-1.7.so.7=7.0.0", + "cmd:dfbdump", + "cmd:dfbdumpinput", + "cmd:dfbfx", + "cmd:dfbg", + "cmd:dfbinfo", + "cmd:dfbinput", + "cmd:dfbinspector", + "cmd:dfblayer", + "cmd:dfbmaster", + "cmd:dfbpenmount", + "cmd:dfbplay", + "cmd:dfbscreen", + "cmd:dfbshow", + "cmd:dfbswitch", + "cmd:directfb-csource", + "cmd:mkdfiff", + "cmd:mkdgiff", + "cmd:mkdgifft" + ] + }, + "libechonest": { + "versions": { + "2.3.1-r0": 1510075926 + }, + "origin": "libechonest", + "dependencies": [ + "so:libQtCore.so.4", + "so:libQtNetwork.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libqjson.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libechonest.so.2.3=2.3.1" + ] + }, + "a2ps-doc": { + "versions": { + "4.14-r7": 1510365670 + }, + "origin": "a2ps" + }, + "openldap-back-ldap": { + "versions": { + "2.4.45-r3": 1510258132, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900515 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "s6-portable-utils": { + "versions": { + "2.2.1.1-r0": 1509488755 + }, + "origin": "s6-portable-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libskarnet.so.2.6" + ], + "provides": [ + "cmd:s6-basename", + "cmd:s6-cat", + "cmd:s6-chmod", + "cmd:s6-chown", + "cmd:s6-clock", + "cmd:s6-cut", + "cmd:s6-dirname", + "cmd:s6-dumpenv", + "cmd:s6-echo", + "cmd:s6-env", + "cmd:s6-expr", + "cmd:s6-false", + "cmd:s6-format-filter", + "cmd:s6-grep", + "cmd:s6-head", + "cmd:s6-hiercopy", + "cmd:s6-linkname", + "cmd:s6-ln", + "cmd:s6-ls", + "cmd:s6-maximumtime", + "cmd:s6-mkdir", + "cmd:s6-mkfifo", + "cmd:s6-nice", + "cmd:s6-nuke", + "cmd:s6-pause", + "cmd:s6-printenv", + "cmd:s6-quote", + "cmd:s6-quote-filter", + "cmd:s6-rename", + "cmd:s6-rmrf", + "cmd:s6-seq", + "cmd:s6-sleep", + "cmd:s6-sort", + "cmd:s6-sync", + "cmd:s6-tail", + "cmd:s6-test", + "cmd:s6-touch", + "cmd:s6-true", + "cmd:s6-uniquename", + "cmd:s6-unquote", + "cmd:s6-unquote-filter", + "cmd:s6-update-symlinks", + "cmd:seekablepipe" + ] + }, + "indent-doc": { + "versions": { + "2.2.11-r1": 1509496389 + }, + "origin": "indent" + }, + "abiword-plugin-t602": { + "versions": { + "3.0.2-r1": 1510073369 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libstdc++.so.6" + ] + }, + "poppler-qt4": { + "versions": { + "0.56.0-r0": 1510071693 + }, + "origin": "poppler-qt4", + "dependencies": [ + "so:libQtCore.so.4", + "so:libQtGui.so.4", + "so:libQtXml.so.4", + "so:libc.musl-x86_64.so.1", + "so:libpoppler.so.67", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libpoppler-qt4.so.4=4.11.0" + ] + }, + "boost-regex": { + "versions": { + "1.62.0-r5": 1509465879 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_regex-mt.so.1.62.0=1.62.0", + "so:libboost_regex.so.1.62.0=1.62.0" + ] + }, + "xz-dev": { + "versions": { + "5.2.3-r1": 1509459904 + }, + "origin": "xz", + "dependencies": [ + "pkgconfig", + "xz-libs=5.2.3-r1" + ], + "provides": [ + "pc:liblzma=5.2.3" + ] + }, + "py-psycopg2": { + "versions": { + "2.7.3.2-r0": 1510619592 + }, + "origin": "py-psycopg2" + }, + "perl-struct-dumb": { + "versions": { + "0.07-r0": 1509480429 + }, + "origin": "perl-struct-dumb" + }, + "samba-libs-py": { + "versions": { + "4.7.6-r3": 1555491789 + }, + "origin": "samba", + "dependencies": [ + "so:libMESSAGING-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-ldap-samba4.so", + "so:libcliauth-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libdcerpc.so.0", + "so:libevents-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:libndr-samba-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libpytalloc-util.so.2", + "so:libpython2.7.so.1.0", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libserver-role-samba4.so", + "so:libsmbpasswdparser-samba4.so", + "so:libtalloc.so.2", + "so:libtevent-util.so.0", + "so:libtevent.so.0" + ], + "provides": [ + "so:libsamba-net-samba4.so=0", + "so:libsamba-python-samba4.so=0" + ] + }, + "nagios-plugins-ifstatus": { + "versions": { + "2.2.1-r3": 1510288495 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins" + ] + }, + "glade3-lang": { + "versions": { + "3.8.5-r4": 1510067964 + }, + "origin": "glade3" + }, + "perl-time-modules": { + "versions": { + "2013.0912-r0": 1509493229 + }, + "origin": "perl-time-modules", + "dependencies": [ + "perl" + ] + }, + "lua-struct": { + "versions": { + "0.2-r2": 1509494934 + }, + "origin": "lua-struct" + }, + "eventlog-dev": { + "versions": { + "0.2.12-r2": 1509466297 + }, + "origin": "eventlog", + "dependencies": [ + "eventlog=0.2.12-r2", + "pkgconfig" + ], + "provides": [ + "pc:eventlog=0.2.12" + ] + }, + "pciutils": { + "versions": { + "3.5.6-r0": 1511165900 + }, + "origin": "pciutils", + "dependencies": [ + "hwdata-pci", + "so:libc.musl-x86_64.so.1", + "so:libpci.so.3" + ], + "provides": [ + "cmd:lspci", + "cmd:setpci", + "cmd:update-pciids" + ] + }, + "libgss-dev": { + "versions": { + "0.1.5-r1": 1509493414 + }, + "origin": "libgss", + "dependencies": [ + "pkgconfig", + "libgss=0.1.5-r1" + ], + "provides": [ + "pc:gss=0.1.5" + ] + }, + "lua5.2-dbi-postgresql": { + "versions": { + "0.6-r1": 1511483402 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "xdelta3-doc": { + "versions": { + "3.0.11-r0": 1509491097 + }, + "origin": "xdelta3" + }, + "soxr": { + "versions": { + "0.1.2-r0": 1509492275 + }, + "origin": "soxr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgomp.so.1" + ], + "provides": [ + "so:libsoxr-lsr.so.0=0.1.9", + "so:libsoxr.so.0=0.1.1" + ] + }, + "perl-net-smtp-tls-butmaintained-doc": { + "versions": { + "0.24-r0": 1509494937 + }, + "origin": "perl-net-smtp-tls-butmaintained" + }, + "ncurses-static": { + "versions": { + "6.0_p20171125-r1": 1534862974 + }, + "origin": "ncurses" + }, + "libcroco-dev": { + "versions": { + "0.6.12-r0": 1509467188, + "0.6.12-r1": 1563788970 + }, + "origin": "libcroco", + "dependencies": [ + "glib-dev", + "libxml2-dev", + "pkgconfig", + "libcroco=0.6.12-r1", + "pc:glib-2.0", + "pc:libxml-2.0" + ], + "provides": [ + "pc:libcroco-0.6=0.6.12", + "cmd:croco-0.6-config" + ] + }, + "acf-opennhrp": { + "versions": { + "0.10.0-r2": 1510076038 + }, + "origin": "acf-opennhrp", + "dependencies": [ + "acf-core", + "lua-posix", + "opennhrp" + ] + }, + "asterisk-fax": { + "versions": { + "15.6.1-r0": 1537795340, + "15.6.2-r0": 1568705002 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libspandsp.so.2" + ] + }, + "gvfs": { + "versions": { + "1.34.1-r0": 1511430258, + "1.34.1-r1": 1563787224 + }, + "origin": "gvfs", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgcr-base-3.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libintl.so.8", + "so:libpolkit-gobject-1.so.0", + "so:libsoup-2.4.so.1", + "so:libudev.so.1", + "so:libudisks2.so.0" + ], + "provides": [ + "so:libgvfscommon.so=0", + "so:libgvfsdaemon.so=0", + "cmd:gvfs-cat", + "cmd:gvfs-copy", + "cmd:gvfs-info", + "cmd:gvfs-less", + "cmd:gvfs-ls", + "cmd:gvfs-mime", + "cmd:gvfs-mkdir", + "cmd:gvfs-monitor-dir", + "cmd:gvfs-monitor-file", + "cmd:gvfs-mount", + "cmd:gvfs-move", + "cmd:gvfs-open", + "cmd:gvfs-rename", + "cmd:gvfs-rm", + "cmd:gvfs-save", + "cmd:gvfs-set-attribute", + "cmd:gvfs-trash", + "cmd:gvfs-tree" + ] + }, + "gtksourceview2": { + "versions": { + "2.10.5-r0": 1510067576 + }, + "origin": "gtksourceview2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgtksourceview-2.0.so.0=0.0.0" + ] + }, + "libconfig-doc": { + "versions": { + "1.5-r3": 1509706433 + }, + "origin": "libconfig" + }, + "openldap-back-dnssrv": { + "versions": { + "2.4.45-r3": 1510258132, + "2.4.46-r0": 1565073938, + "2.4.48-r0": 1566900515 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "kamailio-kazoo": { + "versions": { + "5.0.7-r0": 1532960877 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libjson-c.so.2", + "so:librabbitmq.so.4", + "so:libsrdb1.so.1", + "so:libuuid.so.1" + ] + }, + "pixman": { + "versions": { + "0.34.0-r3": 1509545247 + }, + "origin": "pixman", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpixman-1.so.0=0.34.0" + ] + }, + "gdbm": { + "versions": { + "1.13-r1": 1509458650 + }, + "origin": "gdbm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgdbm.so.4=4.0.0", + "so:libgdbm_compat.so.4=4.0.0", + "cmd:gdbm_dump", + "cmd:gdbm_load", + "cmd:gdbmtool" + ] + }, + "font-ibm-type1": { + "versions": { + "1.0.3-r0": 1509493385 + }, + "origin": "font-ibm-type1", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "abiword-plugin-mif": { + "versions": { + "3.0.2-r1": 1510073364 + }, + "origin": "abiword", + "dependencies": [ + "so:libabiword-3.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ] + }, + "freeswitch-sounds-music-32000": { + "versions": { + "1.0.8-r1": 1509495910 + }, + "origin": "freeswitch-sounds-music-32000" + }, + "cyrus-sasl-scram": { + "versions": { + "2.1.26-r11": 1510258044 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42" + ] + }, + "libcdio-dev": { + "versions": { + "0.94-r0": 1509473626 + }, + "origin": "libcdio", + "dependencies": [ + "libcdio++=0.94-r0", + "libcdio=0.94-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcdio++=0.94", + "pc:libcdio=0.94", + "pc:libiso9660++=0.94", + "pc:libiso9660=0.94", + "pc:libudf=0.94" + ] + }, + "uwsgi-python": { + "versions": { + "2.0.17-r0": 1522154656 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "openssl-dbg": { + "versions": { + "1.0.2r-r0": 1552814650, + "1.0.2t-r0": 1568300394 + }, + "origin": "openssl" + }, + "lua5.3-xml": { + "versions": { + "130610-r5": 1509482648 + }, + "origin": "lua-xml", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "libdvbcsa-dev": { + "versions": { + "1.1.0-r0": 1509488846 + }, + "origin": "libdvbcsa", + "dependencies": [ + "libdvbcsa=1.1.0-r0" + ] + }, + "font-alias": { + "versions": { + "1.0.3-r1": 1509470382 + }, + "origin": "font-alias" + }, + "enca-dev": { + "versions": { + "1.19-r1": 1509480334 + }, + "origin": "enca", + "dependencies": [ + "enca=1.19-r1", + "pkgconfig" + ], + "provides": [ + "pc:enca=1.19" + ] + }, + "cogl-lang": { + "versions": { + "1.22.2-r0": 1510072960 + }, + "origin": "cogl" + }, + "gpgme": { + "versions": { + "1.9.0-r1": 1510588164 + }, + "origin": "gpgme", + "dependencies": [ + "gnupg", + "so:libassuan.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgpg-error.so.0" + ], + "provides": [ + "so:libgpgme.so.11=11.18.0", + "cmd:gpgme-tool" + ] + }, + "ctags": { + "versions": { + "5.8-r5": 1509491649 + }, + "origin": "ctags", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ctags" + ] + }, + "perl-javascript-minifier-xs": { + "versions": { + "0.11-r3": 1509495050 + }, + "origin": "perl-javascript-minifier-xs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "eventlog": { + "versions": { + "0.2.12-r2": 1509466297 + }, + "origin": "eventlog", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libevtlog.so.0=0.0.0" + ] + }, + "pidgin": { + "versions": { + "2.12.0-r2": 1510069759 + }, + "origin": "pidgin", + "dependencies": [ + "so:libICE.so.6", + "so:libSM.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libgtkspell.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpurple.so.0" + ], + "provides": [ + "cmd:pidgin" + ] + }, + "seabios": { + "versions": { + "1.10.3-r0": 1510068884 + }, + "origin": "seabios", + "dependencies": [ + "seabios-bin=1.10.3-r0", + "seavgabios-bin=1.10.3-r0" + ] + }, + "shorewall-core-doc": { + "versions": { + "5.1.8-r0": 1509477808 + }, + "origin": "shorewall-core" + }, + "hiredis-dev": { + "versions": { + "0.13.3-r1": 1509551786 + }, + "origin": "hiredis", + "dependencies": [ + "hiredis=0.13.3-r1", + "pkgconfig" + ], + "provides": [ + "pc:hiredis=0.13.3" + ] + }, + "iwlwifi-6000-ucode": { + "versions": { + "9.221.4.1-r0": 1509495920 + }, + "origin": "iwlwifi-6000-ucode" + }, + "cpufrequtils-dev": { + "versions": { + "008-r4": 1509486334 + }, + "origin": "cpufrequtils", + "dependencies": [ + "cpufrequtils=008-r4" + ] + }, + "ircservices-doc": { + "versions": { + "5.1.24-r4": 1509481222 + }, + "origin": "ircservices" + }, + "git-gitk": { + "versions": { + "2.15.3-r0": 1540401295, + "2.15.4-r0": 1576015196 + }, + "origin": "git", + "dependencies": [ + "git=2.15.4-r0", + "tcl", + "tk" + ], + "provides": [ + "cmd:gitk" + ] + }, + "xautolock": { + "versions": { + "2.2-r3": 1510072542 + }, + "origin": "xautolock", + "dependencies": [ + "so:libX11.so.6", + "so:libXss.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xautolock" + ] + }, + "hiredis": { + "versions": { + "0.13.3-r1": 1509551786 + }, + "origin": "hiredis", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhiredis.so.0.13=0.13" + ] + }, + "email": { + "versions": { + "3.1.4-r4": 1510260519 + }, + "origin": "email", + "dependencies": [ + "libressl", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "cmd:email" + ] + }, + "alpine-keys": { + "versions": { + "2.1-r1": 1510072219 + }, + "origin": "alpine-keys" + }, + "iptables-doc": { + "versions": { + "1.6.1-r1": 1509469980 + }, + "origin": "iptables" + }, + "zsh-doc": { + "versions": { + "5.4.2-r1": 1522503663 + }, + "origin": "zsh" + }, + "lsof-doc": { + "versions": { + "4.89-r1": 1510330780 + }, + "origin": "lsof" + }, + "uwsgi-logzmq": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libzmq.so.5" + ] + }, + "mupdf-gl": { + "versions": { + "1.13.0-r0": 1533746007 + }, + "origin": "mupdf", + "dependencies": [ + "so:libGL.so.1", + "so:libc.musl-x86_64.so.1", + "so:libglut.so.3", + "so:libmupdf.so.0", + "so:libmupdfthird.so.0" + ], + "provides": [ + "cmd:mupdf-gl" + ] + }, + "jq": { + "versions": { + "1.5-r5": 1525102934 + }, + "origin": "jq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libonig.so.4" + ], + "provides": [ + "so:libjq.so.1=1.0.4", + "cmd:jq" + ] + }, + "parted": { + "versions": { + "3.2-r6": 1509482206 + }, + "origin": "parted", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02", + "so:libncursesw.so.6", + "so:libreadline.so.7", + "so:libuuid.so.1" + ], + "provides": [ + "so:libparted-fs-resize.so.0=0.0.1", + "so:libparted.so.2=2.0.1", + "cmd:parted", + "cmd:partprobe" + ] + }, + "dhcp-dbg": { + "versions": { + "4.3.5-r0": 1509496513 + }, + "origin": "dhcp" + }, + "perl-devel-checkbin": { + "versions": { + "0.02-r0": 1509474057 + }, + "origin": "perl-devel-checkbin" + }, + "crconf": { + "versions": { + "0_pre2-r0": 1509483276 + }, + "origin": "crconf", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:crconf" + ] + }, + "pdnsd-doc": { + "versions": { + "1.2.9a-r5": 1509492601 + }, + "origin": "pdnsd" + }, + "ldapvi-doc": { + "versions": { + "1.7-r8": 1510261318 + }, + "origin": "ldapvi" + }, + "dbus-glib-dev": { + "versions": { + "0.108-r1": 1509465372 + }, + "origin": "dbus-glib", + "dependencies": [ + "dbus-glib=0.108-r1", + "pc:dbus-1", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:dbus-glib-1=0.108" + ] + }, + "xf86-video-r128-doc": { + "versions": { + "6.10.2-r0": 1510074868 + }, + "origin": "xf86-video-r128" + }, + "lua5.3-yaml": { + "versions": { + "1.1.2-r1": 1509488585 + }, + "origin": "lua-yaml", + "dependencies": [ + "lua5.3", + "lua5.3-lub", + "so:libc.musl-x86_64.so.1" + ] + }, + "gst-plugins-good-lang": { + "versions": { + "1.12.3-r0": 1510075044 + }, + "origin": "gst-plugins-good" + }, + "lua-sircbot": { + "versions": { + "0.4-r1": 1509492350 + }, + "origin": "sircbot" + }, + "elfutils": { + "versions": { + "0.168-r1": 1509477712 + }, + "origin": "elfutils" + }, + "termrec-doc": { + "versions": { + "0.17-r1": 1509491605 + }, + "origin": "termrec" + }, + "perl-php-serialization-doc": { + "versions": { + "0.34-r1": 1509477756 + }, + "origin": "perl-php-serialization" + }, + "perl-text-wikiformat": { + "versions": { + "0.81-r0": 1509482363 + }, + "origin": "perl-text-wikiformat", + "dependencies": [ + "perl-uri" + ] + }, + "cyrus-sasl-dev": { + "versions": { + "2.1.26-r11": 1510258044 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "libsasl=2.1.26-r11", + "pkgconfig" + ], + "provides": [ + "pc:libsasl2=2.1.26" + ] + }, + "libzdb-dev": { + "versions": { + "3.1-r1": 1509491012 + }, + "origin": "libzdb", + "dependencies": [ + "flex-dev", + "sqlite-dev", + "mariadb-dev", + "postgresql-dev", + "libzdb=3.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:zdb=3.1" + ] + }, + "pixman-dbg": { + "versions": { + "0.34.0-r3": 1509545247 + }, + "origin": "pixman" + }, + "openldap-back-passwd": { + "versions": { + "2.4.45-r3": 1510258133, + "2.4.46-r0": 1565073939, + "2.4.48-r0": 1566900516 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "freetds-dev": { + "versions": { + "1.00.44-r0": 1509480207 + }, + "origin": "freetds", + "dependencies": [ + "freetds=1.00.44-r0" + ] + }, + "mdocml-apropos": { + "versions": { + "1.14.3-r0": 1509459638 + }, + "origin": "mdocml", + "dependencies": [ + "/bin/sh", + "mdocml=1.14.3-r0" + ] + }, + "makedepend": { + "versions": { + "1.0.5-r1": 1509466284 + }, + "origin": "makedepend", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:makedepend" + ] + }, + "keyutils": { + "versions": { + "1.5.10-r0": 1509469695 + }, + "origin": "keyutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libkeyutils.so.1" + ], + "provides": [ + "cmd:key.dns_resolver", + "cmd:keyctl", + "cmd:request-key" + ] + }, + "inotify-tools": { + "versions": { + "3.14-r2": 1509475675 + }, + "origin": "inotify-tools", + "dependencies": [ + "!inotify-tools-inc", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libinotifytools.so.0=0.4.1", + "cmd:inotifywait", + "cmd:inotifywatch" + ] + }, + "vim": { + "versions": { + "8.0.1359-r0": 1512029968, + "8.0.1359-r1": 1559759711, + "8.0.1359-r2": 1561188678 + }, + "origin": "vim", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua-5.2.so.0", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:ex", + "cmd:rview", + "cmd:rvim", + "cmd:view", + "cmd:vim", + "cmd:vimtutor", + "cmd:xxd" + ] + }, + "perl-net-rblclient": { + "versions": { + "0.5-r2": 1509479745 + }, + "origin": "perl-net-rblclient", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:spamalyze" + ] + }, + "coova-chilli-dev": { + "versions": { + "1.3.2-r2": 1510287299 + }, + "origin": "coova-chilli", + "dependencies": [ + "coova-chilli=1.3.2-r2" + ] + }, + "lua-crypto-doc": { + "versions": { + "0.3.2-r5": 1510261400 + }, + "origin": "lua-crypto" + }, + "openldap-back-monitor": { + "versions": { + "2.4.45-r3": 1510258133, + "2.4.46-r0": 1565073939, + "2.4.48-r0": 1566900516 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "xfbdev": { + "versions": { + "1.19.5-r1": 1540838460 + }, + "origin": "xorg-server", + "dependencies": [ + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.0.0", + "so:libpixman-1.so.0", + "so:libudev.so.1" + ], + "provides": [ + "cmd:Xfbdev" + ] + }, + "collectd-doc": { + "versions": { + "5.7.2-r0": 1510069642 + }, + "origin": "collectd" + }, + "mkinitfs": { + "versions": { + "3.2.0-r4": 1525036846 + }, + "origin": "mkinitfs", + "dependencies": [ + "busybox", + "apk-tools>=2.8.2", + "lddtree>=1.25", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcryptsetup.so.4", + "so:libkmod.so.2" + ], + "provides": [ + "cmd:bootchartd", + "cmd:mkinitfs", + "cmd:nlplug-findfs" + ] + }, + "perl-net-smtp-ssl": { + "versions": { + "1.04-r0": 1509494478 + }, + "origin": "perl-net-smtp-ssl", + "dependencies": [ + "perl-io-socket-ssl", + "perl-net-ssleay" + ] + }, + "iperf": { + "versions": { + "2.0.9-r1": 1509482124 + }, + "origin": "iperf", + "dependencies": [ + "!iperf3", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:iperf" + ] + }, + "net-snmp-dev": { + "versions": { + "5.7.3-r10": 1510259616 + }, + "origin": "net-snmp", + "dependencies": [ + "libressl-dev", + "net-snmp-agent-libs=5.7.3-r10", + "net-snmp-libs=5.7.3-r10" + ], + "provides": [ + "cmd:net-snmp-config" + ] + }, + "kbd-bkeymaps": { + "versions": { + "2.0.4-r2": 1510922528 + }, + "origin": "kbd", + "provides": [ + "bkeymaps" + ] + }, + "freeswitch-sounds-fr-ca-june-8000": { + "versions": { + "1.0.51-r0": 1509483411 + }, + "origin": "freeswitch-sounds-fr-ca-june-8000" + }, + "libmaxminddb-doc": { + "versions": { + "1.3.1-r0": 1511893707 + }, + "origin": "libmaxminddb" + }, + "openvswitch": { + "versions": { + "2.8.1-r2": 1512030507 + }, + "origin": "openvswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libssl.so.44" + ], + "provides": [ + "cmd:ovn-controller", + "cmd:ovn-controller-vtep", + "cmd:ovn-detrace", + "cmd:ovn-docker-overlay-driver", + "cmd:ovn-docker-underlay-driver", + "cmd:ovn-nbctl", + "cmd:ovn-northd", + "cmd:ovn-sbctl", + "cmd:ovn-trace", + "cmd:ovs-appctl", + "cmd:ovs-bugtool", + "cmd:ovs-docker", + "cmd:ovs-dpctl", + "cmd:ovs-dpctl-top", + "cmd:ovs-l3ping", + "cmd:ovs-ofctl", + "cmd:ovs-parse-backtrace", + "cmd:ovs-pcap", + "cmd:ovs-pki", + "cmd:ovs-tcpdump", + "cmd:ovs-tcpundump", + "cmd:ovs-test", + "cmd:ovs-testcontroller", + "cmd:ovs-vlan-bug-workaround", + "cmd:ovs-vlan-test", + "cmd:ovs-vsctl", + "cmd:ovs-vswitchd", + "cmd:ovsdb-client", + "cmd:ovsdb-server", + "cmd:ovsdb-tool", + "cmd:vtep-ctl" + ] + }, + "lua5.3-mosquitto": { + "versions": { + "0.2-r1": 1509496427 + }, + "origin": "lua-mosquitto", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ] + }, + "rsyslog-hiredis": { + "versions": { + "8.31.0-r0": 1512031981, + "8.31.0-r1": 1571767017 + }, + "origin": "rsyslog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.13" + ] + }, + "jbig2dec": { + "versions": { + "0.14-r0": 1509469939 + }, + "origin": "jbig2dec", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libjbig2dec.so.0=0.0.0", + "cmd:jbig2dec" + ] + }, + "s6-networking-dev": { + "versions": { + "2.3.0.2-r1": 1510260983 + }, + "origin": "s6-networking", + "dependencies": [ + "s6-networking=2.3.0.2-r1" + ] + }, + "perl-dbd-pg-doc": { + "versions": { + "3.7.0-r0": 1509491418 + }, + "origin": "perl-dbd-pg" + }, + "gettext-doc": { + "versions": { + "0.19.8.1-r1": 1509459195 + }, + "origin": "gettext" + }, + "libmtp-examples": { + "versions": { + "1.1.14-r0": 1509481648 + }, + "origin": "libmtp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmtp.so.9" + ], + "provides": [ + "cmd:mtp-albumart", + "cmd:mtp-albums", + "cmd:mtp-connect", + "cmd:mtp-delfile", + "cmd:mtp-detect", + "cmd:mtp-emptyfolders", + "cmd:mtp-files", + "cmd:mtp-filetree", + "cmd:mtp-folders", + "cmd:mtp-format", + "cmd:mtp-getfile", + "cmd:mtp-getplaylist", + "cmd:mtp-hotplug", + "cmd:mtp-newfolder", + "cmd:mtp-newplaylist", + "cmd:mtp-playlists", + "cmd:mtp-reset", + "cmd:mtp-sendfile", + "cmd:mtp-sendtr", + "cmd:mtp-thumb", + "cmd:mtp-tracks", + "cmd:mtp-trexist" + ] + }, + "perl-db_file": { + "versions": { + "1.840-r1": 1509491810 + }, + "origin": "perl-db_file", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so" + ] + }, + "sg3_utils-doc": { + "versions": { + "1.42-r0": 1509482218 + }, + "origin": "sg3_utils" + }, + "perl-devel-checklib-doc": { + "versions": { + "1.11-r1": 1509489314 + }, + "origin": "perl-devel-checklib" + }, + "pciutils-doc": { + "versions": { + "3.5.6-r0": 1511165899 + }, + "origin": "pciutils" + }, + "gnome-mime-data": { + "versions": { + "2.18.0-r1": 1509481656 + }, + "origin": "gnome-mime-data" + }, + "nagios-plugins-ircd": { + "versions": { + "2.2.1-r3": 1510288496 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins" + ] + }, + "qemu-system-mips64": { + "versions": { + "2.10.1-r3": 1519746242 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libasound.so.2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libnfs.so.11", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-mips64" + ] + }, + "perl-file-sharedir-doc": { + "versions": { + "1.104-r0": 1510564485 + }, + "origin": "perl-file-sharedir" + }, + "libxrandr-dev": { + "versions": { + "1.5.1-r1": 1509466001 + }, + "origin": "libxrandr", + "dependencies": [ + "randrproto", + "libxext-dev", + "libxrandr=1.5.1-r1", + "pc:randrproto>=1.5", + "pc:x11", + "pc:xext", + "pc:xproto", + "pc:xrender", + "pkgconfig" + ], + "provides": [ + "pc:xrandr=1.5.1" + ] + }, + "bison-doc": { + "versions": { + "3.0.4-r0": 1509456687 + }, + "origin": "bison" + }, + "perl-mime-base64-doc": { + "versions": { + "3.15-r4": 1509491804 + }, + "origin": "perl-mime-base64" + }, + "gtkman": { + "versions": { + "1.0.1-r0": 1510073899 + }, + "origin": "gtkman", + "dependencies": [ + "py-gtk" + ], + "provides": [ + "cmd:gtkman" + ] + }, + "lua-rex-posix": { + "versions": { + "2.9.0-r0": 1510619366 + }, + "origin": "lua-rex" + }, + "memcached-doc": { + "versions": { + "1.5.6-r0": 1520365888 + }, + "origin": "memcached" + }, + "logcheck-doc": { + "versions": { + "1.3.18-r0": 1509468630 + }, + "origin": "logcheck" + }, + "pcmciautils-doc": { + "versions": { + "018-r1": 1509482231 + }, + "origin": "pcmciautils" + }, + "icu-doc": { + "versions": { + "59.1-r1": 1509464843 + }, + "origin": "icu" + }, + "perl-exporter": { + "versions": { + "5.72-r1": 1509483328 + }, + "origin": "perl-exporter" + }, + "perl-convert-uulib": { + "versions": { + "1.5-r2": 1509483418 + }, + "origin": "perl-convert-uulib", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "flashrom-doc": { + "versions": { + "0.9.9-r2": 1509482144 + }, + "origin": "flashrom" + }, + "dahdi-tools": { + "versions": { + "2.11.1-r0": 1509476156 + }, + "origin": "dahdi-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnewt.so.0.52" + ], + "provides": [ + "so:libtonezone.so.2=2.0.0", + "cmd:dahdi_cfg", + "cmd:dahdi_genconf", + "cmd:dahdi_hardware", + "cmd:dahdi_maint", + "cmd:dahdi_monitor", + "cmd:dahdi_registration", + "cmd:dahdi_scan", + "cmd:dahdi_span_assignments", + "cmd:dahdi_span_types", + "cmd:dahdi_speed", + "cmd:dahdi_test", + "cmd:dahdi_tool", + "cmd:dahdi_waitfor_span_assignments", + "cmd:fxotune", + "cmd:lsdahdi", + "cmd:sethdlc", + "cmd:twinstar", + "cmd:xpp_blink", + "cmd:xpp_sync" + ] + }, + "libmspack": { + "versions": { + "0.8_alpha-r0": 1543321986 + }, + "origin": "libmspack", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmspack.so.0=0.1.0", + "cmd:cabrip", + "cmd:chmextract", + "cmd:msexpand", + "cmd:oabextract" + ] + }, + "multisort": { + "versions": { + "1.1-r0": 1509482417 + }, + "origin": "multisort", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:multisort" + ] + }, + "gettext-lang": { + "versions": { + "0.19.8.1-r1": 1509459196 + }, + "origin": "gettext" + }, + "dzen": { + "versions": { + "0.9.5-r3": 1509787635 + }, + "origin": "dzen", + "dependencies": [ + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libXpm.so.4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dzen2" + ] + }, + "py3-cparser": { + "versions": { + "2.18-r0": 1509483347 + }, + "origin": "py-cparser", + "dependencies": [ + "python3" + ] + }, + "postgresql-client": { + "versions": { + "10.7-r0": 1554274199, + "10.8-r0": 1557668015, + "10.9-r0": 1562225614, + "10.10-r0": 1565683438 + }, + "origin": "postgresql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libedit.so.0", + "so:libpq.so.5", + "so:libz.so.1" + ], + "provides": [ + "cmd:clusterdb", + "cmd:createdb", + "cmd:createuser", + "cmd:dropdb", + "cmd:dropuser", + "cmd:pg_basebackup", + "cmd:pg_dump", + "cmd:pg_dumpall", + "cmd:pg_isready", + "cmd:pg_receivewal", + "cmd:pg_recvlogical", + "cmd:pg_restore", + "cmd:psql", + "cmd:reindexdb", + "cmd:vacuumdb" + ] + }, + "sprunge": { + "versions": { + "0.6-r0": 1509477746 + }, + "origin": "sprunge", + "dependencies": [ + "curl" + ], + "provides": [ + "cmd:sprunge" + ] + }, + "grub-xenhost": { + "versions": { + "2.02-r3": 1509495739 + }, + "origin": "grub" + }, + "lua5.2-lzlib": { + "versions": { + "0.4.3-r0": 1509494167 + }, + "origin": "lua-lzlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ] + }, + "libnice-dbg": { + "versions": { + "0.1.14-r2": 1510931344 + }, + "origin": "libnice" + }, + "drbd-utils-pacemaker": { + "versions": { + "9.1.1-r0": 1511795634 + }, + "origin": "drbd-utils", + "dependencies": [ + "drbd" + ] + }, + "fortify-headers": { + "versions": { + "0.9-r0": 1509458197 + }, + "origin": "fortify-headers" + }, + "haveged-doc": { + "versions": { + "1.9.2-r0": 1511893694 + }, + "origin": "haveged" + }, + "uwsgi-legion_cache_fetch": { + "versions": { + "2.0.17-r0": 1522154655 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "varnish-dev": { + "versions": { + "5.2.1-r0": 1511265090 + }, + "origin": "varnish", + "dependencies": [ + "pkgconfig", + "varnish-libs=5.2.1-r0" + ], + "provides": [ + "pc:varnishapi=5.2.1" + ] + }, + "ldapvi": { + "versions": { + "1.7-r8": 1510261318 + }, + "origin": "ldapvi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.42", + "so:libglib-2.0.so.0", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libncursesw.so.6", + "so:libpopt.so.0", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:ldapvi" + ] + }, + "perl-snmp-session": { + "versions": { + "1.13-r2": 1509483931 + }, + "origin": "perl-snmp-session", + "dependencies": [ + "perl" + ] + }, + "samba": { + "versions": { + "4.7.6-r3": 1555491790 + }, + "origin": "samba", + "dependencies": [ + "samba-server=4.7.6-r3", + "samba-client=4.7.6-r3", + "samba-common-tools=4.7.6-r3" + ] + }, + "mpc1": { + "versions": { + "1.0.3-r1": 1509457078 + }, + "origin": "mpc1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libmpfr.so.4" + ], + "provides": [ + "so:libmpc.so.3=3.0.0" + ] + }, + "faad2": { + "versions": { + "2.7-r7": 1509480955, + "2.9.0-r0": 1571918410 + }, + "origin": "faad2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfaad.so.2=2.0.0", + "so:libfaad_drm.so.2=2.0.0", + "cmd:faad" + ] + }, + "iwlwifi-5000-ucode-doc": { + "versions": { + "8.83.5.1-r0": 1509494264 + }, + "origin": "iwlwifi-5000-ucode" + }, + "cfdisk": { + "versions": { + "2.31.1-r0": 1541506294 + }, + "origin": "util-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfdisk.so.1", + "so:libmount.so.1", + "so:libncursesw.so.6", + "so:libsmartcols.so.1" + ], + "provides": [ + "cmd:cfdisk" + ] + }, + "qpdf": { + "versions": { + "7.0.0-r0": 1509482356 + }, + "origin": "qpdf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libqpdf.so.18", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:qpdf", + "cmd:zlib-flate" + ] + }, + "rarian": { + "versions": { + "0.8.1-r6": 1509465523 + }, + "origin": "rarian", + "dependencies": [ + "bash", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:librarian.so.0=0.0.0", + "cmd:rarian-example", + "cmd:rarian-sk-extract", + "cmd:rarian-sk-gen-uuid", + "cmd:rarian-sk-get-cl", + "cmd:rarian-sk-get-content-list", + "cmd:rarian-sk-get-extended-content-list", + "cmd:rarian-sk-get-scripts", + "cmd:rarian-sk-install", + "cmd:rarian-sk-migrate", + "cmd:rarian-sk-preinstall", + "cmd:rarian-sk-rebuild", + "cmd:rarian-sk-update", + "cmd:scrollkeeper-extract", + "cmd:scrollkeeper-gen-seriesid", + "cmd:scrollkeeper-get-cl", + "cmd:scrollkeeper-get-content-list", + "cmd:scrollkeeper-get-extended-content-list", + "cmd:scrollkeeper-get-index-from-docpath", + "cmd:scrollkeeper-get-toc-from-docpath", + "cmd:scrollkeeper-get-toc-from-id", + "cmd:scrollkeeper-install", + "cmd:scrollkeeper-preinstall", + "cmd:scrollkeeper-rebuilddb", + "cmd:scrollkeeper-uninstall", + "cmd:scrollkeeper-update" + ] + }, + "mksh-doc": { + "versions": { + "56b-r0": 1509491850 + }, + "origin": "mksh" + }, + "openjade-dev": { + "versions": { + "1.3.2-r5": 1509488724 + }, + "origin": "openjade", + "dependencies": [ + "openjade-libs=1.3.2-r5" + ] + }, + "dhcrelay": { + "versions": { + "4.3.5-r0": 1509496514 + }, + "origin": "dhcp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "cmd:dhcrelay" + ] + }, + "avfs": { + "versions": { + "1.0.5-r0": 1509496598 + }, + "origin": "avfs", + "dependencies": [ + "bash", + "so:libc.musl-x86_64.so.1", + "so:libfuse.so.2" + ], + "provides": [ + "so:libavfs.so.0=0.0.2", + "cmd:avfsd", + "cmd:davpass", + "cmd:ftppass", + "cmd:mountavfs", + "cmd:umountavfs" + ] + }, + "libisofs-dev": { + "versions": { + "1.4.8-r0": 1509462207 + }, + "origin": "libisofs", + "dependencies": [ + "libisofs=1.4.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:libisofs-1=1.4.8" + ] + }, + "libsasl": { + "versions": { + "2.1.26-r11": 1510258045 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so" + ], + "provides": [ + "so:libsasl2.so.3=3.0.0" + ] + }, + "libjpeg-turbo-doc": { + "versions": { + "1.5.3-r2": 1537879742, + "1.5.3-r3": 1563792633 + }, + "origin": "libjpeg-turbo" + }, + "lua5.3-iconv": { + "versions": { + "7-r1": 1509493324 + }, + "origin": "lua-iconv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libintl": { + "versions": { + "0.19.8.1-r1": 1509459196 + }, + "origin": "gettext", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libintl.so.8=8.1.5" + ] + }, + "cadaver-doc": { + "versions": { + "0.23.3-r3": 1510310613 + }, + "origin": "cadaver" + }, + "sems-voicemail": { + "versions": { + "1.6.0-r6": 1510260897 + }, + "origin": "sems", + "dependencies": [ + "sems", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "dovecot": { + "versions": { + "2.2.36.3-r0": 1554102392, + "2.2.36.4-r0": 1567075099 + }, + "origin": "dovecot", + "dependencies": [ + "libressl", + "/bin/sh", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.42", + "so:libssl.so.44", + "so:libz.so.1" + ], + "provides": [ + "so:lib01_acl_plugin.so=0", + "so:lib02_imap_acl_plugin.so=0", + "so:lib02_lazy_expunge_plugin.so=0", + "so:lib05_mail_crypt_acl_plugin.so=0", + "so:lib05_pop3_migration_plugin.so=0", + "so:lib05_snarf_plugin.so=0", + "so:lib10_last_login_plugin.so=0", + "so:lib10_mail_crypt_plugin.so=0", + "so:lib10_mail_filter_plugin.so=0", + "so:lib10_quota_plugin.so=0", + "so:lib11_imap_quota_plugin.so=0", + "so:lib11_trash_plugin.so=0", + "so:lib15_notify_plugin.so=0", + "so:lib20_autocreate_plugin.so=0", + "so:lib20_charset_alias_plugin.so=0", + "so:lib20_expire_plugin.so=0", + "so:lib20_fts_plugin.so=0", + "so:lib20_listescape_plugin.so=0", + "so:lib20_mail_log_plugin.so=0", + "so:lib20_mailbox_alias_plugin.so=0", + "so:lib20_notify_status_plugin.so=0", + "so:lib20_push_notification_plugin.so=0", + "so:lib20_quota_clone_plugin.so=0", + "so:lib20_replication_plugin.so=0", + "so:lib20_var_expand_crypt.so=0", + "so:lib20_virtual_plugin.so=0", + "so:lib20_zlib_plugin.so=0", + "so:lib21_fts_squat_plugin.so=0", + "so:lib30_imap_zlib_plugin.so=0", + "so:lib90_stats_plugin.so=0", + "so:lib95_imap_stats_plugin.so=0", + "so:lib99_welcome_plugin.so=0", + "so:libdcrypt_openssl.so=0", + "so:libdovecot-compression.so.0=0.0.0", + "so:libdovecot-dsync.so.0=0.0.0", + "so:libdovecot-fts.so.0=0.0.0", + "so:libdovecot-lda.so.0=0.0.0", + "so:libdovecot-login.so.0=0.0.0", + "so:libdovecot-storage.so.0=0.0.0", + "so:libdovecot.so.0=0.0.0", + "so:libfs_compress.so=0", + "so:libfs_crypt.so=0", + "so:libfs_mail_crypt.so=0", + "so:libssl_iostream_openssl.so=0", + "so:libstats_auth.so=0", + "so:libstats_mail.so=0", + "cmd:doveadm", + "cmd:doveconf", + "cmd:dovecot", + "cmd:dsync" + ] + }, + "syslinux": { + "versions": { + "6.04_pre1-r1": 1509482644 + }, + "origin": "syslinux", + "dependencies": [ + "mtools", + "blkid", + "mkinitfs", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:cat.c32=0", + "so:chain.c32=0", + "so:cmd.c32=0", + "so:cmenu.c32=0", + "so:config.c32=0", + "so:cptime.c32=0", + "so:cpu.c32=0", + "so:cpuid.c32=0", + "so:cpuidtest.c32=0", + "so:debug.c32=0", + "so:dhcp.c32=0", + "so:dir.c32=0", + "so:disk.c32=0", + "so:dmi.c32=0", + "so:dmitest.c32=0", + "so:elf.c32=0", + "so:ethersel.c32=0", + "so:gfxboot.c32=0", + "so:gpxecmd.c32=0", + "so:hdt.c32=0", + "so:hexdump.c32=0", + "so:host.c32=0", + "so:ifcpu.c32=0", + "so:ifcpu64.c32=0", + "so:ifmemdsk.c32=0", + "so:ifplop.c32=0", + "so:kbdmap.c32=0", + "so:kontron_wdt.c32=0", + "so:ldlinux.c32=0", + "so:lfs.c32=0", + "so:libcom32.c32=0", + "so:libgpl.c32=0", + "so:liblua.c32=0", + "so:libmenu.c32=0", + "so:libutil.c32=0", + "so:linux.c32=0", + "so:ls.c32=0", + "so:lua.c32=0", + "so:mboot.c32=0", + "so:meminfo.c32=0", + "so:menu.c32=0", + "so:pci.c32=0", + "so:pcitest.c32=0", + "so:pmload.c32=0", + "so:poweroff.c32=0", + "so:prdhcp.c32=0", + "so:pwd.c32=0", + "so:pxechn.c32=0", + "so:reboot.c32=0", + "so:rosh.c32=0", + "so:sanboot.c32=0", + "so:sdi.c32=0", + "so:sysdump.c32=0", + "so:syslinux.c32=0", + "so:vesa.c32=0", + "so:vesainfo.c32=0", + "so:vesamenu.c32=0", + "so:vpdtest.c32=0", + "so:whichsys.c32=0", + "so:zzjson.c32=0", + "cmd:extlinux", + "cmd:gethostip", + "cmd:isohybrid", + "cmd:isohybrid.pl", + "cmd:keytab-lilo", + "cmd:lss16toppm", + "cmd:md5pass", + "cmd:memdiskfind", + "cmd:mkdiskimage", + "cmd:ppmtolss16", + "cmd:pxelinux-options", + "cmd:sha1pass", + "cmd:syslinux", + "cmd:syslinux2ansi", + "cmd:update-extlinux" + ] + }, + "ghostscript-fonts": { + "versions": { + "8.11-r1": 1509489326 + }, + "origin": "ghostscript-fonts" + }, + "libquvi-scripts-doc": { + "versions": { + "0.9.20131130-r0": 1509491409 + }, + "origin": "libquvi-scripts" + }, + "freeswitch-perlesl": { + "versions": { + "1.6.19-r0": 1510585358 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libperl.so", + "so:libstdc++.so.6" + ] + }, + "py-urlgrabber-doc": { + "versions": { + "3.10.1-r1": 1509468415 + }, + "origin": "py-urlgrabber" + }, + "lua5.1-bitlib": { + "versions": { + "5.3.0-r0": 1509468304 + }, + "origin": "lua-bitlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py-imagesize": { + "versions": { + "0.7.1-r2": 1509474094 + }, + "origin": "py-imagesize" + }, + "py-egenix-mx-base-doc": { + "versions": { + "3.2.9-r0": 1509474193 + }, + "origin": "py-egenix-mx-base" + }, + "perl-apache-logformat-compiler-doc": { + "versions": { + "0.32-r0": 1509481806 + }, + "origin": "perl-apache-logformat-compiler" + }, + "lcms2-utils": { + "versions": { + "2.8-r1": 1509464952 + }, + "origin": "lcms2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libtiff.so.5" + ], + "provides": [ + "cmd:jpgicc", + "cmd:linkicc", + "cmd:psicc", + "cmd:tificc", + "cmd:transicc" + ] + }, + "mcpp-dev": { + "versions": { + "2.7.2-r1": 1509473730 + }, + "origin": "mcpp", + "dependencies": [ + "mcpp-libs=2.7.2-r1" + ] + }, + "perl-test-mocktime-doc": { + "versions": { + "0.15-r0": 1509475510 + }, + "origin": "perl-test-mocktime" + }, + "perl-archive-zip-doc": { + "versions": { + "1.59-r0": 1509483547 + }, + "origin": "perl-archive-zip" + }, + "py-flask": { + "versions": { + "0.12.2-r1": 1509551869 + }, + "origin": "py-flask", + "dependencies": [ + "py-click", + "py-itsdangerous", + "py-jinja2", + "py-werkzeug" + ] + }, + "perl-devel-stacktrace-ashtml": { + "versions": { + "0.15-r0": 1510564492 + }, + "origin": "perl-devel-stacktrace-ashtml", + "dependencies": [ + "perl", + "perl-devel-stacktrace" + ] + }, + "py-simpleparse": { + "versions": { + "2.2.0-r0": 1509495041 + }, + "origin": "py-simpleparse", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "perl-module-build-tiny-doc": { + "versions": { + "0.039-r0": 1509474778 + }, + "origin": "perl-module-build-tiny" + }, + "perl-locale-maketext-lexicon": { + "versions": { + "1.00-r0": 1509494308 + }, + "origin": "perl-locale-maketext-lexicon", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:xgettext.pl" + ] + }, + "gtk-engines-industrial": { + "versions": { + "2.21.0-r2": 1510071723 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "libinput-dev": { + "versions": { + "1.9.2-r0": 1511276852 + }, + "origin": "libinput", + "dependencies": [ + "libinput-libs=1.9.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libinput=1.9.2" + ] + }, + "uwsgi-dumbloop": { + "versions": { + "2.0.17-r0": 1522154654 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-mozilla-ca": { + "versions": { + "20160104-r0": 1509494409 + }, + "origin": "perl-mozilla-ca" + }, + "gnome-mime-data-lang": { + "versions": { + "2.18.0-r1": 1509481655 + }, + "origin": "gnome-mime-data" + }, + "gfortran": { + "versions": { + "6.4.0-r5": 1509458079 + }, + "origin": "gcc", + "dependencies": [ + "gcc=6.4.0-r5", + "libgfortran=6.4.0-r5", + "libquadmath=6.4.0-r5", + "libgfortran=6.4.0-r5", + "libquadmath=6.4.0-r5", + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libisl.so.15", + "so:libmpc.so.3", + "so:libmpfr.so.4", + "so:libz.so.1" + ], + "provides": [ + "cmd:gfortran", + "cmd:x86_64-alpine-linux-musl-gfortran" + ] + }, + "lua5.1-dev": { + "versions": { + "5.1.5-r3": 1509462457 + }, + "origin": "lua5.1", + "dependencies": [ + "lua5.1", + "lua5.1-libs=5.1.5-r3", + "pkgconfig" + ], + "provides": [ + "pc:lua5.1=5.1.5" + ] + }, + "fetchmail-doc": { + "versions": { + "6.3.26-r12": 1510261418 + }, + "origin": "fetchmail" + }, + "aspell-libs": { + "versions": { + "0.60.6.1-r12": 1509472868, + "0.60.6.1-r13": 1572541141 + }, + "origin": "aspell", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libaspell.so.15=15.1.5", + "so:libpspell.so.15=15.1.5" + ] + }, + "perl-net-cidr-lite-doc": { + "versions": { + "0.21-r2": 1509496071 + }, + "origin": "perl-net-cidr-lite" + }, + "rsyslog-pgsql": { + "versions": { + "8.31.0-r0": 1512031980, + "8.31.0-r1": 1571767017 + }, + "origin": "rsyslog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "perl-text-reform": { + "versions": { + "1.20-r0": 1509475837 + }, + "origin": "perl-text-reform", + "dependencies": [ + "perl" + ] + }, + "py-avahi": { + "versions": { + "0.6.32-r4": 1509465450, + "0.6.32-r5": 1563345619 + }, + "origin": "avahi" + }, + "libvpx-dev": { + "versions": { + "1.6.1-r0": 1509480733 + }, + "origin": "libvpx", + "dependencies": [ + "libvpx=1.6.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:vpx=1.6.1" + ] + }, + "libid3tag": { + "versions": { + "0.15.1b-r6": 1509470497 + }, + "origin": "libid3tag", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libid3tag.so.0=0.3.0" + ] + }, + "gst-plugins-base0.10-doc": { + "versions": { + "0.10.36-r3": 1510068367 + }, + "origin": "gst-plugins-base0.10" + }, + "cyrus-sasl": { + "versions": { + "2.1.26-r11": 1510258045 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:libkrb5.so.26", + "so:libroken.so.18", + "so:libsasl2.so.3" + ], + "provides": [ + "cmd:pluginviewer", + "cmd:saslauthd", + "cmd:sasldblistusers2", + "cmd:saslpasswd2", + "cmd:testsaslauthd" + ] + }, + "xev": { + "versions": { + "1.2.2-r0": 1509491061 + }, + "origin": "xev", + "dependencies": [ + "so:libX11.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xev" + ] + }, + "v4l-utils-dev": { + "versions": { + "1.12.5-r1": 1510072040 + }, + "origin": "v4l-utils", + "dependencies": [ + "pkgconfig", + "v4l-utils-dvbv5=1.12.5-r1", + "v4l-utils-libs=1.12.5-r1" + ], + "provides": [ + "pc:libdvbv5=1.12.5", + "pc:libv4l1=1.12.5", + "pc:libv4l2=1.12.5", + "pc:libv4l2rds=1.12.5", + "pc:libv4lconvert=1.12.5" + ] + }, + "py3-sphinxcontrib-websupport": { + "versions": { + "1.0.1-r3": 1509493320 + }, + "origin": "py-sphinxcontrib-websupport", + "dependencies": [ + "python3" + ] + } + }, + "provide": { + "so": { + "libflite.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmu_grapheme_lang.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmu_grapheme_lex.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmu_indic_lang.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmu_indic_lex.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmu_time_awb.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmu_us_awb.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmu_us_kal.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmu_us_kal16.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmu_us_rms.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmu_us_slt.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_cmulex.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libflite_usenglish.so.1": { + "package": "flite", + "version": "2.0.0" + }, + "libcryptsetup.so.4": { + "package": "cryptsetup-libs", + "version": "4.7.0" + }, + "liblber-2.4.so.2": { + "package": "libldap", + "version": "2.10.11" + }, + "libldap-2.4.so.2": { + "package": "libldap", + "version": "2.10.11" + }, + "libldap_r-2.4.so.2": { + "package": "libldap", + "version": "2.10.11" + }, + "libsmbpasswdparser-samba4.so": { + "package": "samba-dc", + "version": "0" + }, + "libupower-glib.so.3": { + "package": "upower", + "version": "3.0.1" + }, + "libxcb-keysyms.so.1": { + "package": "xcb-util-keysyms", + "version": "1.0.0" + }, + "libgdkglext-x11-1.0.so.0": { + "package": "gtkglext", + "version": "0.0.0" + }, + "libgtkglext-x11-1.0.so.0": { + "package": "gtkglext", + "version": "0.0.0" + }, + "libass.so.9": { + "package": "libass", + "version": "9.0.1" + }, + "libclutter-1.0.so.0": { + "package": "clutter", + "version": "0.2600.2" + }, + "libalpm.so.10": { + "package": "pacman", + "version": "10.0.2" + }, + "libxfce4util.so.7": { + "package": "libxfce4util", + "version": "7.0.0" + }, + "lib21_fts_lucene_plugin.so": { + "package": "dovecot-fts-lucene", + "version": "0" + }, + "libexecinfo.so.1": { + "package": "libexecinfo", + "version": "1" + }, + "libboost_filesystem-mt.so.1.62.0": { + "package": "boost-filesystem", + "version": "1.62.0" + }, + "libboost_filesystem.so.1.62.0": { + "package": "boost-filesystem", + "version": "1.62.0" + }, + "liblua.so.5": { + "package": "lua5.1-libs", + "version": "5.1.4" + }, + "libfarstream-0.1.so.0": { + "package": "farstream0.1", + "version": "0.0.1" + }, + "libasn1-samba4.so.8": { + "package": "samba-heimdal-libs", + "version": "8.0.0" + }, + "libgssapi-samba4.so.2": { + "package": "samba-heimdal-libs", + "version": "2.0.0" + }, + "libhcrypto-samba4.so.5": { + "package": "samba-heimdal-libs", + "version": "5.0.1" + }, + "libheimbase-samba4.so.1": { + "package": "samba-heimdal-libs", + "version": "1.0.0" + }, + "libheimntlm-samba4.so.1": { + "package": "samba-heimdal-libs", + "version": "1.0.1" + }, + "libhx509-samba4.so.5": { + "package": "samba-heimdal-libs", + "version": "5.0.0" + }, + "libkrb5-samba4.so.26": { + "package": "samba-heimdal-libs", + "version": "26.0.0" + }, + "libroken-samba4.so.19": { + "package": "samba-heimdal-libs", + "version": "19.0.1" + }, + "libwind-samba4.so.0": { + "package": "samba-heimdal-libs", + "version": "0.0.0" + }, + "libmpeg2.so.0": { + "package": "libmpeg2", + "version": "0.1.0" + }, + "libmpeg2convert.so.0": { + "package": "libmpeg2", + "version": "0.0.0" + }, + "libcroco-0.6.so.3": { + "package": "libcroco", + "version": "3.0.1" + }, + "libguess.so.1": { + "package": "libguess", + "version": "1.0.0" + }, + "libpyglib-2.0-python.so.0": { + "package": "py-gobject", + "version": "0.0.0" + }, + "libsyslog-ng-3.9.so.0": { + "package": "syslog-ng", + "version": "0.0.0" + }, + "libboost_serialization-mt.so.1.62.0": { + "package": "boost-serialization", + "version": "1.62.0" + }, + "libboost_serialization.so.1.62.0": { + "package": "boost-serialization", + "version": "1.62.0" + }, + "libgbm.so.1": { + "package": "mesa-gbm", + "version": "1.0.0" + }, + "libz.so.1": { + "package": "zlib", + "version": "1.2.11" + }, + "libdbus-1.so.3": { + "package": "dbus-libs", + "version": "3.14.16" + }, + "libabiword-3.0.so": { + "package": "abiword", + "version": "0" + }, + "libtiff.so.5": { + "package": "tiff", + "version": "5.4.0" + }, + "libtinyxml.so.0": { + "package": "tinyxml", + "version": "0.2.6.2" + }, + "libsamplerate.so.0": { + "package": "libsamplerate", + "version": "0.1.8" + }, + "libx264.so.148": { + "package": "x264-libs", + "version": "148" + }, + "librhash.so.0": { + "package": "rhash-libs", + "version": "0" + }, + "libmowgli-2.so.0": { + "package": "libmowgli", + "version": "0.0.0" + }, + "libsodium.so.23": { + "package": "libsodium", + "version": "23.0.0" + }, + "libboost_python-mt.so.1.62.0": { + "package": "boost-python", + "version": "1.62.0" + }, + "libboost_python.so.1.62.0": { + "package": "boost-python", + "version": "1.62.0" + }, + "libsigc-2.0.so.0": { + "package": "libsigc++", + "version": "0.0.0" + }, + "libXpm.so.4": { + "package": "libxpm", + "version": "4.11.0" + }, + "libconfig++.so.9": { + "package": "libconfig++", + "version": "9.2.0" + }, + "libldb.so.1": { + "package": "ldb", + "version": "1.3.0" + }, + "libxmlrpc_client++.so.8": { + "package": "xmlrpc-c-client++", + "version": "8.39" + }, + "libproxy.so.1": { + "package": "libproxy", + "version": "1.0.0" + }, + "librrd_th.so.4": { + "package": "librrd-th", + "version": "4.3.5" + }, + "libXv.so.1": { + "package": "libxv", + "version": "1.0.0" + }, + "libxshmfence.so.1": { + "package": "libxshmfence", + "version": "1.0.0" + }, + "libunbound.so.2": { + "package": "unbound-libs", + "version": "2.5.6" + }, + "libnfs.so.11": { + "package": "libnfs", + "version": "11.0.0" + }, + "libdevmapper.so.1.02": { + "package": "device-mapper-libs", + "version": "1.02" + }, + "libspice-client-glib-2.0.so.8": { + "package": "spice-glib", + "version": "8.6.0" + }, + "libspice-controller.so.0": { + "package": "spice-glib", + "version": "0.0.0" + }, + "libnpth.so.0": { + "package": "npth", + "version": "0.1.1" + }, + "libfreeradius-client.so.2": { + "package": "freeradius-client", + "version": "2.0.0" + }, + "libboost_atomic-mt.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_chrono-mt.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_chrono.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_container-mt.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_container.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_context-mt.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_coroutine-mt.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_coroutine.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_locale-mt.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_log-mt.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_log.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_log_setup-mt.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_log_setup.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_timer-mt.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_timer.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_type_erasure-mt.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libboost_type_erasure.so.1.62.0": { + "package": "boost", + "version": "1.62.0" + }, + "libjemalloc.so.2": { + "package": "jemalloc", + "version": "2" + }, + "libtdb.so.1": { + "package": "tdb-libs", + "version": "1.3.15" + }, + "libbonobo-2.so.0": { + "package": "libbonobo", + "version": "0.0.0" + }, + "libbonobo-activation.so.4": { + "package": "libbonobo", + "version": "4.0.0" + }, + "libelf.so.1": { + "package": "elfutils-libelf", + "version": "0" + }, + "libiec61883.so.0": { + "package": "libiec61883", + "version": "0.1.1" + }, + "libMagick++-7.Q16HDRI.so.3": { + "package": "imagemagick-c++", + "version": "3.0.0" + }, + "libraw.so.16": { + "package": "libraw", + "version": "16.0.0" + }, + "libraw_r.so.16": { + "package": "libraw", + "version": "16.0.0" + }, + "libpq.so.5": { + "package": "libpq", + "version": "5.10" + }, + "libpng16.so.16": { + "package": "libpng", + "version": "16.37.0" + }, + "libburn.so.4": { + "package": "libburn", + "version": "4.101.0" + }, + "libclamav.so.7": { + "package": "clamav-lib", + "version": "7.1.2" + }, + "libgck-1.so.0": { + "package": "gcr", + "version": "0.0.0" + }, + "libgcr-base-3.so.1": { + "package": "gcr", + "version": "1.0.0" + }, + "libgcr-ui-3.so.1": { + "package": "gcr", + "version": "1.0.0" + }, + "liblz4.so.1": { + "package": "lz4-libs", + "version": "1.8.0" + }, + "libclamunrar.so.7": { + "package": "clamav-libunrar", + "version": "7.1.2" + }, + "libclamunrar_iface.so.7": { + "package": "clamav-libunrar", + "version": "7.1.2" + }, + "libtcl8.6.so": { + "package": "tcl", + "version": "0" + }, + "libtxc_dxtn.so": { + "package": "libtxc_dxtn", + "version": "0" + }, + "libbfd-2.30.so": { + "package": "binutils-libs", + "version": "0" + }, + "libopcodes-2.30.so": { + "package": "binutils-libs", + "version": "0" + }, + "libshout.so.3": { + "package": "libshout", + "version": "3.2.0" + }, + "libstdc++.so.6": { + "package": "libstdc++", + "version": "6.0.22" + }, + "libfetch.so": { + "package": "libfetch", + "version": "0" + }, + "libssl.so.44": { + "package": "libressl2.6-libssl", + "version": "44.0.1" + }, + "libfdisk.so.1": { + "package": "libfdisk", + "version": "1.1.0" + }, + "libusb-0.1.so.4": { + "package": "libusb-compat", + "version": "4.4.4" + }, + "libssh2.so.1": { + "package": "libssh2", + "version": "1.0.1" + }, + "libhavege.so.1": { + "package": "haveged", + "version": "1.1.0" + }, + "libkeyutils.so.1": { + "package": "keyutils-libs", + "version": "1.6" + }, + "libdlz-bind9-for-torture-samba4.so": { + "package": "samba-test", + "version": "0" + }, + "libtorture-samba4.so": { + "package": "samba-test", + "version": "0" + }, + "libasound.so.2": { + "package": "alsa-lib", + "version": "2.0.0" + }, + "libgconf-2.so.4": { + "package": "gconf", + "version": "4.1.5" + }, + "group_file.so": { + "package": "sudo", + "version": "0" + }, + "libsudo_noexec.so": { + "package": "sudo", + "version": "0" + }, + "libsudo_util.so.0": { + "package": "sudo", + "version": "0.0.0" + }, + "sudoers.so": { + "package": "sudo", + "version": "0" + }, + "system_group.so": { + "package": "sudo", + "version": "0" + }, + "libavahi-ui-gtk3.so.0": { + "package": "avahi-ui-gtk3", + "version": "0.1.4" + }, + "libvanessa_logger.so.0": { + "package": "vanessa_logger", + "version": "0.0.5" + }, + "libwebsockets.so.12": { + "package": "libwebsockets", + "version": "12" + }, + "libhashkit.so.2": { + "package": "libmemcached-libs", + "version": "2.0.0" + }, + "libmemcached.so.11": { + "package": "libmemcached-libs", + "version": "11.0.0" + }, + "libmemcachedprotocol.so.0": { + "package": "libmemcached-libs", + "version": "0.0.0" + }, + "libmemcachedutil.so.2": { + "package": "libmemcached-libs", + "version": "2.0.0" + }, + "libminizip.so.1": { + "package": "minizip", + "version": "1.0.0" + }, + "libebt_802_3.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_among.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_arp.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_arpreply.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_ip.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_ip6.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_limit.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_log.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_mark.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_mark_m.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_nat.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_nflog.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_pkttype.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_redirect.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_standard.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_stp.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_ulog.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_vlan.so": { + "package": "ebtables", + "version": "0" + }, + "libebtable_broute.so": { + "package": "ebtables", + "version": "0" + }, + "libebtable_filter.so": { + "package": "ebtables", + "version": "0" + }, + "libebtable_nat.so": { + "package": "ebtables", + "version": "0" + }, + "libebtc.so": { + "package": "ebtables", + "version": "0" + }, + "libldb-cmdline.so": { + "package": "ldb-tools", + "version": "0" + }, + "libcap.so.2": { + "package": "libcap", + "version": "2.25" + }, + "libBasicUsageEnvironment.so.1": { + "package": "live-media", + "version": "1.0.0" + }, + "libUsageEnvironment.so.3": { + "package": "live-media", + "version": "3.1.0" + }, + "libgroupsock.so.8": { + "package": "live-media", + "version": "8.1.1" + }, + "libliveMedia.so.61": { + "package": "live-media", + "version": "61.0.0" + }, + "libgarcon-1.so.0": { + "package": "garcon", + "version": "0.0.0" + }, + "libgarcon-gtk2-1.so.0": { + "package": "garcon", + "version": "0.0.0" + }, + "libgarcon-gtk3-1.so.0": { + "package": "garcon", + "version": "0.0.0" + }, + "libwayland-client.so.0": { + "package": "wayland-libs-client", + "version": "0.3.0" + }, + "libxcb-render-util.so.0": { + "package": "xcb-util-renderutil", + "version": "0.0.0" + }, + "libverto-libev.so.1": { + "package": "libverto-libev", + "version": "1.0.0" + }, + "libhogweed.so.4": { + "package": "nettle", + "version": "4.3" + }, + "libnettle.so.6": { + "package": "nettle", + "version": "6.3" + }, + "libgssapi_krb5.so.2": { + "package": "krb5-libs", + "version": "2.2" + }, + "libgssrpc.so.4": { + "package": "krb5-libs", + "version": "4.2" + }, + "libk5crypto.so.3": { + "package": "krb5-libs", + "version": "3.1" + }, + "libkadm5clnt_mit.so.11": { + "package": "krb5-libs", + "version": "11.0" + }, + "libkadm5srv_mit.so.11": { + "package": "krb5-libs", + "version": "11.0" + }, + "libkdb5.so.8": { + "package": "krb5-libs", + "version": "8.0" + }, + "libkrad.so.0": { + "package": "krb5-libs", + "version": "0.0" + }, + "libkrb5.so.3": { + "package": "krb5-libs", + "version": "3.3" + }, + "libkrb5support.so.0": { + "package": "krb5-libs", + "version": "0.1" + }, + "libboost_wave-mt.so.1.62.0": { + "package": "boost-wave", + "version": "1.62.0" + }, + "libogrove.so.0": { + "package": "openjade-libs", + "version": "0.0.1" + }, + "libospgrove.so.0": { + "package": "openjade-libs", + "version": "0.0.1" + }, + "libostyle.so.0": { + "package": "openjade-libs", + "version": "0.0.1" + }, + "libgtest1.so.34": { + "package": "nss-dev", + "version": "34" + }, + "libnsssysinit.so": { + "package": "nss-dev", + "version": "34" + }, + "libSDL_mixer-1.2.so.0": { + "package": "sdl_mixer", + "version": "0.12.0" + }, + "libX11-xcb.so.1": { + "package": "libx11", + "version": "1.0.0" + }, + "libX11.so.6": { + "package": "libx11", + "version": "6.3.0" + }, + "libmatroska.so.6": { + "package": "libmatroska", + "version": "6.0.0" + }, + "libsysfs.so.2": { + "package": "sysfsutils", + "version": "2.0.1" + }, + "rlm_rest.so": { + "package": "freeradius-rest", + "version": "0" + }, + "libmicrohttpd.so.12": { + "package": "libmicrohttpd", + "version": "12.45.0" + }, + "libboost_random-mt.so.1.62.0": { + "package": "boost-random", + "version": "1.62.0" + }, + "libboost_random.so.1.62.0": { + "package": "boost-random", + "version": "1.62.0" + }, + "libgoffice-0.10.so.10": { + "package": "goffice", + "version": "10.0.36" + }, + "libdevmapper-event.so.1.02": { + "package": "device-mapper-event-libs", + "version": "1.02" + }, + "libXxf86misc.so.1": { + "package": "libxxf86misc", + "version": "1.1.0" + }, + "libQt3Support.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtCLucene.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtDeclarative.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtDesigner.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtDesignerComponents.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtGui.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtHelp.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtMultimedia.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtOpenGL.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtScriptTools.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtSvg.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libenca.so.0": { + "package": "enca", + "version": "0.5.1" + }, + "openvpn-auth-ldap.so": { + "package": "openvpn-auth-ldap", + "version": "0" + }, + "libcddb.so.2": { + "package": "libcddb", + "version": "2.2.3" + }, + "libGLESv1_CM.so.1": { + "package": "mesa-gles", + "version": "1.1.0" + }, + "libGLESv2.so.2": { + "package": "mesa-gles", + "version": "2.0.0" + }, + "libasn1.so.8": { + "package": "heimdal-libs", + "version": "8.0.0" + }, + "libgssapi.so.3": { + "package": "heimdal-libs", + "version": "3.0.0" + }, + "libhcrypto.so.4": { + "package": "heimdal-libs", + "version": "4.1.0" + }, + "libhdb.so.9": { + "package": "heimdal-libs", + "version": "9.2.0" + }, + "libheimbase.so.1": { + "package": "heimdal-libs", + "version": "1.0.0" + }, + "libheimntlm.so.0": { + "package": "heimdal-libs", + "version": "0.1.0" + }, + "libhx509.so.5": { + "package": "heimdal-libs", + "version": "5.0.0" + }, + "libkadm5clnt.so.7": { + "package": "heimdal-libs", + "version": "7.0.1" + }, + "libkadm5srv.so.8": { + "package": "heimdal-libs", + "version": "8.0.1" + }, + "libkafs.so.0": { + "package": "heimdal-libs", + "version": "0.5.1" + }, + "libkdc.so.2": { + "package": "heimdal-libs", + "version": "2.0.0" + }, + "libkrb5.so.26": { + "package": "heimdal-libs", + "version": "26.0.0" + }, + "libotp.so.0": { + "package": "heimdal-libs", + "version": "0.1.5" + }, + "libroken.so.18": { + "package": "heimdal-libs", + "version": "18.1.0" + }, + "libsl.so.0": { + "package": "heimdal-libs", + "version": "0.2.1" + }, + "libwind.so.0": { + "package": "heimdal-libs", + "version": "0.0.0" + }, + "windc.so.0": { + "package": "heimdal-libs", + "version": "0.0.0" + }, + "libcli-ldap-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libcmdline-credentials-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libdcerpc.so.0": { + "package": "samba-client-libs", + "version": "0.0.1" + }, + "libdsdb-garbage-collect-tombstones-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libevents-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libhttp-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libnetif-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libregistry-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libsmbclient-raw-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libecpg.so.6": { + "package": "postgresql-libs", + "version": "6.10" + }, + "libecpg_compat.so.3": { + "package": "postgresql-libs", + "version": "3.10" + }, + "libpgtypes.so.3": { + "package": "postgresql-libs", + "version": "3.10" + }, + "libnetsnmp.so.30": { + "package": "net-snmp-libs", + "version": "30.0.3" + }, + "libxmlrpc_client.so.3": { + "package": "xmlrpc-c-client", + "version": "3.39" + }, + "libluajit-5.1.so.2": { + "package": "luajit", + "version": "2.1.0" + }, + "libIPMIlanserv.so.0": { + "package": "openipmi-lanserv", + "version": "0.0.1" + }, + "libXt.so.6": { + "package": "libxt", + "version": "6.0.0" + }, + "libgstbasecamerabinsrc-0.10.so.23": { + "package": "gst-plugins-bad0.10", + "version": "23.0.0" + }, + "libgstbasevideo-0.10.so.23": { + "package": "gst-plugins-bad0.10", + "version": "23.0.0" + }, + "libgstcodecparsers-0.10.so.23": { + "package": "gst-plugins-bad0.10", + "version": "23.0.0" + }, + "libgstphotography-0.10.so.23": { + "package": "gst-plugins-bad0.10", + "version": "23.0.0" + }, + "libgstsignalprocessor-0.10.so.23": { + "package": "gst-plugins-bad0.10", + "version": "23.0.0" + }, + "libdvbcsa.so.1": { + "package": "libdvbcsa", + "version": "1.0.1" + }, + "libjson-glib-1.0.so.0": { + "package": "json-glib", + "version": "0.200.8" + }, + "libfl.so.2": { + "package": "flex-libs", + "version": "2.0.0" + }, + "libqextserialport.so.1": { + "package": "qextserialport", + "version": "1.2.0" + }, + "libboost_unit_test_framework-mt.so.1.62.0": { + "package": "boost-unit_test_framework", + "version": "1.62.0" + }, + "libboost_unit_test_framework.so.1.62.0": { + "package": "boost-unit_test_framework", + "version": "1.62.0" + }, + "libXfont2.so.2": { + "package": "libxfont2", + "version": "2.0.0" + }, + "libMrm.so.4": { + "package": "motif", + "version": "4.0.4" + }, + "libUil.so.4": { + "package": "motif", + "version": "4.0.4" + }, + "libXm.so.4": { + "package": "motif", + "version": "4.0.4" + }, + "libcanberra-gtk3.so.0": { + "package": "libcanberra-gtk3", + "version": "0.1.9" + }, + "bpipe-fd.so": { + "package": "bacula-libs", + "version": "0" + }, + "libbac-9.0.5.so": { + "package": "bacula-libs", + "version": "0" + }, + "libbaccfg-9.0.5.so": { + "package": "bacula-libs", + "version": "0" + }, + "libbacfind-9.0.5.so": { + "package": "bacula-libs", + "version": "0" + }, + "libbacsd-9.0.5.so": { + "package": "bacula-libs", + "version": "0" + }, + "libbacsql-9.0.5.so": { + "package": "bacula-libs", + "version": "0" + }, + "libavahi-client.so.3": { + "package": "avahi-libs", + "version": "3.2.9" + }, + "libavahi-common.so.3": { + "package": "avahi-libs", + "version": "3.5.3" + }, + "libiptcdata.so.0": { + "package": "libiptcdata", + "version": "0.3.3" + }, + "libGLEW.so.2.1": { + "package": "glew", + "version": "2.1.0" + }, + "libcollectdclient.so.1": { + "package": "collectd-libs", + "version": "1.0.0" + }, + "libwv-1.2.so.4": { + "package": "wv", + "version": "4.0.5" + }, + "libbaccats-postgresql-9.0.5.so": { + "package": "bacula-pgsql", + "version": "0" + }, + "libxdg-basedir.so.1": { + "package": "libxdg-basedir", + "version": "1.2.0" + }, + "libjson-c.so.2": { + "package": "json-c", + "version": "2.0.2" + }, + "libnetflowPlugin-5.0.1.so": { + "package": "ntop", + "version": "0" + }, + "libntop-5.0.1.so": { + "package": "ntop", + "version": "0" + }, + "libntopreport-5.0.1.so": { + "package": "ntop", + "version": "0" + }, + "librrdPlugin-5.0.1.so": { + "package": "ntop", + "version": "0" + }, + "libsflowPlugin-5.0.1.so": { + "package": "ntop", + "version": "0" + }, + "libfreebl3.so": { + "package": "nss", + "version": "34" + }, + "libgtestutil.so.34": { + "package": "nss", + "version": "34" + }, + "libnss3.so": { + "package": "nss", + "version": "34" + }, + "libnssckbi.so": { + "package": "nss", + "version": "34" + }, + "libnssdbm3.so": { + "package": "nss", + "version": "34" + }, + "libnssutil3.so": { + "package": "nss", + "version": "34" + }, + "libsmime3.so": { + "package": "nss", + "version": "34" + }, + "libsoftokn3.so": { + "package": "nss", + "version": "34" + }, + "libssl3.so": { + "package": "nss", + "version": "34" + }, + "libwx_baseu-2.8.so.0": { + "package": "wxgtk2.8-base", + "version": "0.8.0" + }, + "libwx_baseu_net-2.8.so.0": { + "package": "wxgtk2.8-base", + "version": "0.8.0" + }, + "libwx_baseu_xml-2.8.so.0": { + "package": "wxgtk2.8-base", + "version": "0.8.0" + }, + "libatomic.so.1": { + "package": "libatomic", + "version": "1.2.0" + }, + "libxcb-ewmh.so.2": { + "package": "xcb-util-wm", + "version": "2.0.0" + }, + "libxcb-icccm.so.4": { + "package": "xcb-util-wm", + "version": "4.0.0" + }, + "rlm_sql_unixodbc.so": { + "package": "freeradius-unixodbc", + "version": "0" + }, + "libfpm_pb.so.0": { + "package": "quagga", + "version": "0.0.0" + }, + "libospf.so.0": { + "package": "quagga", + "version": "0.0.0" + }, + "libospfapiclient.so.0": { + "package": "quagga", + "version": "0.0.0" + }, + "libquagga_pb.so.0": { + "package": "quagga", + "version": "0.0.0" + }, + "libzebra.so.1": { + "package": "quagga", + "version": "1.0.0" + }, + "libXp.so.6": { + "package": "libxp", + "version": "6.2.0" + }, + "libgstallocators-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libgstapp-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libgstaudio-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libgstfft-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libgstpbutils-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libgstriff-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libgstrtp-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libgstrtsp-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libgstsdp-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libgsttag-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libgstvideo-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1203.0" + }, + "libkdb_ldap.so.1": { + "package": "krb5-server-ldap", + "version": "1.0" + }, + "libfltk.so.1.3": { + "package": "fltk", + "version": "1.3" + }, + "libfltk_forms.so.1.3": { + "package": "fltk", + "version": "1.3" + }, + "libfltk_gl.so.1.3": { + "package": "fltk", + "version": "1.3" + }, + "libfltk_images.so.1.3": { + "package": "fltk", + "version": "1.3" + }, + "libnfdump-1.6.15.so": { + "package": "nfdump", + "version": "0" + }, + "libspice-client-gtk-3.0.so.5": { + "package": "spice-gtk", + "version": "5.0.0" + }, + "libfprint.so.0": { + "package": "libfprint", + "version": "0.0.0" + }, + "libxfce4panel-1.0.so.4": { + "package": "xfce4-panel", + "version": "4.0.0" + }, + "libxfce4panel-2.0.so.4": { + "package": "xfce4-panel", + "version": "4.0.0" + }, + "postfix-sqlite.so": { + "package": "postfix-sqlite", + "version": "0" + }, + "postfix-mysql.so": { + "package": "postfix-mysql", + "version": "0" + }, + "liborc-0.4.so.0": { + "package": "orc", + "version": "0.27.0" + }, + "liborc-test-0.4.so.0": { + "package": "orc", + "version": "0.27.0" + }, + "libnetfilter_log.so.1": { + "package": "libnetfilter_log", + "version": "1.1.0" + }, + "libnetfilter_log_libipulog.so.1": { + "package": "libnetfilter_log", + "version": "1.0.0" + }, + "libnice.so.10": { + "package": "libnice", + "version": "10.7.0" + }, + "libcares.so.2": { + "package": "c-ares", + "version": "2.2.0" + }, + "libspeex.so.1": { + "package": "speex", + "version": "1.5.1" + }, + "rlm_eap.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_fast.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_gtc.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_leap.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_md5.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_mschapv2.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_peap.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_pwd.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_sim.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_tls.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_ttls.so": { + "package": "freeradius-eap", + "version": "0" + }, + "libpci.so.3": { + "package": "pciutils-libs", + "version": "3.5.6" + }, + "libXcomposite.so.1": { + "package": "libxcomposite", + "version": "1.0.0" + }, + "libgccpp.so.1": { + "package": "libgc++", + "version": "1.0.3" + }, + "libhowl.so.0": { + "package": "avahi-compat-howl", + "version": "0.0.0" + }, + "libgnarl-6.so": { + "package": "libgnat", + "version": "0" + }, + "libgnat-6.so": { + "package": "libgnat", + "version": "0" + }, + "libxfconf-0.so.2": { + "package": "xfconf", + "version": "2.0.0" + }, + "libgpo-samba4.so": { + "package": "samba-common-tools", + "version": "0" + }, + "libnet-keytab-samba4.so": { + "package": "samba-common-tools", + "version": "0" + }, + "libfcgi++.so.0": { + "package": "fcgi++", + "version": "0.0.0" + }, + "libboost_wserialization-mt.so.1.62.0": { + "package": "boost-wserialization", + "version": "1.62.0" + }, + "libboost_wserialization.so.1.62.0": { + "package": "boost-wserialization", + "version": "1.62.0" + }, + "libnetfilter_cthelper.so.0": { + "package": "libnetfilter_cthelper", + "version": "0.0.0" + }, + "libharfbuzz.so.0": { + "package": "harfbuzz", + "version": "0.10600.3" + }, + "libcairo-script-interpreter.so.2": { + "package": "cairo", + "version": "2.11400.10" + }, + "libcairo.so.2": { + "package": "cairo", + "version": "2.11400.10" + }, + "libgnt.so.0": { + "package": "finch", + "version": "0.8.10" + }, + "libboost_prg_exec_monitor-mt.so.1.62.0": { + "package": "boost-prg_exec_monitor", + "version": "1.62.0" + }, + "libboost_prg_exec_monitor.so.1.62.0": { + "package": "boost-prg_exec_monitor", + "version": "1.62.0" + }, + "libtalloc.so.2": { + "package": "talloc", + "version": "2.1.10" + }, + "libwayland-cursor.so.0": { + "package": "wayland-libs-cursor", + "version": "0.0.0" + }, + "libopusfile.so.0": { + "package": "opusfile", + "version": "0.4.3" + }, + "libopusurl.so.0": { + "package": "opusfile", + "version": "0.4.3" + }, + "libcupsfilters.so.1": { + "package": "cups-filters-libs", + "version": "1.0.0" + }, + "libfontembed.so.1": { + "package": "cups-filters-libs", + "version": "1.0.0" + }, + "libltdl.so.7": { + "package": "libltdl", + "version": "7.3.1" + }, + "libmaa.so.3": { + "package": "libmaa", + "version": "3.0.0" + }, + "libXxf86dga.so.1": { + "package": "libxxf86dga", + "version": "1.0.0" + }, + "libevdev.so.2": { + "package": "libevdev", + "version": "2.1.19" + }, + "liburiparser.so.1": { + "package": "uriparser", + "version": "1.0.20" + }, + "libdvbpsi.so.10": { + "package": "libdvbpsi", + "version": "10.0.0" + }, + "libpangomm-1.4.so.1": { + "package": "pangomm", + "version": "1.0.30" + }, + "libmaxminddb.so.0": { + "package": "libmaxminddb", + "version": "0.0.7" + }, + "libmp3splt.so.0": { + "package": "libmp3splt", + "version": "0.0.9" + }, + "libhistory.so.7": { + "package": "libhistory", + "version": "7.0" + }, + "libpaper.so.1": { + "package": "libpaper", + "version": "1.1.2" + }, + "libformw.so.6": { + "package": "ncurses-libs", + "version": "6.0" + }, + "libmenuw.so.6": { + "package": "ncurses-libs", + "version": "6.0" + }, + "libncursesw.so.6": { + "package": "ncurses-libs", + "version": "6.0" + }, + "libpanelw.so.6": { + "package": "ncurses-libs", + "version": "6.0" + }, + "libcurl.so.4": { + "package": "libcurl", + "version": "4.5.0" + }, + "libmosquittopp.so.1": { + "package": "mosquitto-libs++", + "version": "1" + }, + "liboping.so.0": { + "package": "liboping", + "version": "0.3.0" + }, + "libICE.so.6": { + "package": "libice", + "version": "6.3.0" + }, + "libdmmp.so.0.1.0": { + "package": "multipath-tools", + "version": "0.1.0" + }, + "libmpathcmd.so.0": { + "package": "multipath-tools", + "version": "0" + }, + "libmpathpersist.so.0": { + "package": "multipath-tools", + "version": "0" + }, + "libmultipath.so.0": { + "package": "multipath-tools", + "version": "0" + }, + "libXfixes.so.3": { + "package": "libxfixes", + "version": "3.1.0" + }, + "libelf.so.0": { + "package": "libelf", + "version": "0" + }, + "libcom_err.so.2": { + "package": "libcom_err", + "version": "2.1" + }, + "libXmu.so.6": { + "package": "libxmu", + "version": "6.2.0" + }, + "libXmuu.so.1": { + "package": "libxmu", + "version": "1.0.0" + }, + "libgnomekbd.so.8": { + "package": "libgnomekbd", + "version": "8.0.0" + }, + "libgnomekbdui.so.8": { + "package": "libgnomekbd", + "version": "8.0.0" + }, + "libnss_winbind.so.2": { + "package": "samba-libnss-winbind", + "version": "2" + }, + "libnss_wins.so.2": { + "package": "samba-libnss-winbind", + "version": "2" + }, + "libXss.so.1": { + "package": "libxscrnsaver", + "version": "1.0.0" + }, + "libpgm-5.2.so.0": { + "package": "openpgm", + "version": "0.0.122" + }, + "libmysqlclient.so.18": { + "package": "mariadb-client-libs", + "version": "18.0.0" + }, + "libloudmouth-1.so.0": { + "package": "loudmouth", + "version": "0.1.0" + }, + "libfontenc.so.1": { + "package": "libfontenc", + "version": "1.0.0" + }, + "libs6.so.2.6": { + "package": "s6", + "version": "2.6.1.1" + }, + "libMESSAGING-samba4.so": { + "package": "samba-server-libs", + "version": "0" + }, + "libdcerpc-samba4.so": { + "package": "samba-server-libs", + "version": "0" + }, + "libidmap-samba4.so": { + "package": "samba-server-libs", + "version": "0" + }, + "libnon-posix-acls-samba4.so": { + "package": "samba-server-libs", + "version": "0" + }, + "libnss-info-samba4.so": { + "package": "samba-server-libs", + "version": "0" + }, + "libdriver_sqlite.so": { + "package": "dovecot-sqlite", + "version": "0" + }, + "libfaac.so.0": { + "package": "faac", + "version": "0.0.0" + }, + "libclammspack.so.0": { + "package": "clamav", + "version": "0.1.0" + }, + "libxt_ACCOUNT_cl.so.0": { + "package": "xtables-addons", + "version": "0.0.0" + }, + "libntfs-3g.so.88": { + "package": "ntfs-3g-libs", + "version": "88.0.0" + }, + "libI810XvMC.so.1": { + "package": "xf86-video-intel", + "version": "1.0.0" + }, + "libIntelXvMC.so.1": { + "package": "xf86-video-intel", + "version": "1.0.0" + }, + "libXRes.so.1": { + "package": "libxres", + "version": "1.0.0" + }, + "libperl.so": { + "package": "perl", + "version": "0" + }, + "libva-drm.so.1": { + "package": "libva", + "version": "1.4000.0" + }, + "libva-egl.so.1": { + "package": "libva", + "version": "1.4000.0" + }, + "libva-glx.so.1": { + "package": "libva", + "version": "1.4000.0" + }, + "libva-tpi.so.1": { + "package": "libva", + "version": "1.4000.0" + }, + "libva-x11.so.1": { + "package": "libva", + "version": "1.4000.0" + }, + "libva.so.1": { + "package": "libva", + "version": "1.4000.0" + }, + "libbluray.so.2": { + "package": "libbluray", + "version": "2.0.0" + }, + "libspandsp.so.2": { + "package": "spandsp", + "version": "2.0.0" + }, + "libfreetype.so.6": { + "package": "freetype", + "version": "6.15.0" + }, + "libquazip.so.1": { + "package": "quazip", + "version": "1.0.0" + }, + "libsctp.so.1": { + "package": "lksctp-tools", + "version": "1.0.17" + }, + "libcheck.so.0": { + "package": "check", + "version": "0.0.0" + }, + "libisoburn.so.1": { + "package": "libisoburn", + "version": "1.105.0" + }, + "libcdio_cdda.so.2": { + "package": "libcdio-paranoia", + "version": "2.0.0" + }, + "libcdio_paranoia.so.2": { + "package": "libcdio-paranoia", + "version": "2.0.0" + }, + "rlm_sql_mysql.so": { + "package": "freeradius-mysql", + "version": "0" + }, + "libdbi.so.1": { + "package": "libdbi", + "version": "1.1.0" + }, + "lib90_sieve_plugin.so": { + "package": "dovecot-pigeonhole-plugin", + "version": "0" + }, + "lib95_imap_sieve_plugin.so": { + "package": "dovecot-pigeonhole-plugin", + "version": "0" + }, + "libdovecot-sieve.so.0": { + "package": "dovecot-pigeonhole-plugin", + "version": "0.0.0" + }, + "libvala-0.36.so.0": { + "package": "vala", + "version": "0.0.0" + }, + "libchromeXvMC.so.1": { + "package": "xf86-video-openchrome", + "version": "1.0.0" + }, + "libchromeXvMCPro.so.1": { + "package": "xf86-video-openchrome", + "version": "1.0.0" + }, + "libXdmcp.so.6": { + "package": "libxdmcp", + "version": "6.0.0" + }, + "libdovecot-sql.so.0": { + "package": "dovecot-sql", + "version": "0.0.0" + }, + "libsmartcols.so.1": { + "package": "libsmartcols", + "version": "1.1.0" + }, + "libbaccats-mysql-9.0.5.so": { + "package": "bacula-mysql", + "version": "0" + }, + "libnl-cli-3.so.200": { + "package": "libnl3-cli", + "version": "200.23.0" + }, + "libXi.so.6": { + "package": "libxi", + "version": "6.1.0" + }, + "libopenobex.so.2": { + "package": "openobex", + "version": "1.7.2" + }, + "libxcb-util.so.1": { + "package": "xcb-util", + "version": "1.0.0" + }, + "libwnck-1.so.22": { + "package": "libwnck", + "version": "22.4.0" + }, + "libxcb-composite.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-damage.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-dpms.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-dri2.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-dri3.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-glx.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-present.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-randr.so.0": { + "package": "libxcb", + "version": "0.1.0" + }, + "libxcb-record.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-render.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-res.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-screensaver.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-shape.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-shm.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-sync.so.1": { + "package": "libxcb", + "version": "1.0.0" + }, + "libxcb-xf86dri.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-xfixes.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-xinerama.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-xinput.so.0": { + "package": "libxcb", + "version": "0.1.0" + }, + "libxcb-xkb.so.1": { + "package": "libxcb", + "version": "1.0.0" + }, + "libxcb-xtest.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-xv.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-xvmc.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb.so.1": { + "package": "libxcb", + "version": "1.1.0" + }, + "libgstapp-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstaudio-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstcdda-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstfft-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstinterfaces-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstnetbuffer-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstpbutils-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstriff-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstrtp-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstrtsp-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstsdp-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgsttag-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstvideo-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libaprutil-1.so.0": { + "package": "apr-util", + "version": "0.6.1" + }, + "libXinerama.so.1": { + "package": "libxinerama", + "version": "1.0.0" + }, + "postfix-pcre.so": { + "package": "postfix-pcre", + "version": "0" + }, + "libacl.so.1": { + "package": "libacl", + "version": "1.1.0" + }, + "libgsl.so.23": { + "package": "gsl", + "version": "23.0.0" + }, + "libgslcblas.so.0": { + "package": "gsl", + "version": "0.0.0" + }, + "libglfw.so.3": { + "package": "glfw", + "version": "3.2" + }, + "libipset.so.3": { + "package": "ipset", + "version": "3.7.0" + }, + "libmagic.so.1": { + "package": "libmagic", + "version": "1.0.0" + }, + "libdb_cxx-5.3.so": { + "package": "db-c++", + "version": "0" + }, + "libXau.so.6": { + "package": "libxau", + "version": "6.0.0" + }, + "libilbc.so.0": { + "package": "ilbc", + "version": "0.0.2" + }, + "librecode.so.0": { + "package": "recode", + "version": "0.0.0" + }, + "postgresql-bdr:libecpg.so.6": { + "package": "postgresql-bdr", + "version": "6.6" + }, + "postgresql-bdr:libecpg_compat.so.3": { + "package": "postgresql-bdr", + "version": "3.6" + }, + "postgresql-bdr:libpgtypes.so.3": { + "package": "postgresql-bdr", + "version": "3.5" + }, + "libboost_iostreams-mt.so.1.62.0": { + "package": "boost-iostreams", + "version": "1.62.0" + }, + "libboost_iostreams.so.1.62.0": { + "package": "boost-iostreams", + "version": "1.62.0" + }, + "libgudev-1.0.so.0": { + "package": "libgudev", + "version": "0.2.0" + }, + "libpython2.7.so.1.0": { + "package": "python2", + "version": "1.0" + }, + "libattr.so.1": { + "package": "libattr", + "version": "1.1.0" + }, + "libuuid.so.1": { + "package": "libuuid", + "version": "1.3.0" + }, + "librest-0.7.so.0": { + "package": "rest", + "version": "0.0.0" + }, + "librest-extras-0.7.so.0": { + "package": "rest", + "version": "0.0.0" + }, + "libclang.so.5.0": { + "package": "clang-libs", + "version": "5.0" + }, + "libdvdcss.so.2": { + "package": "libdvdcss", + "version": "2.2.0" + }, + "libdaemon.so.0": { + "package": "libdaemon", + "version": "0.5.0" + }, + "libgnome-2.so.0": { + "package": "libgnome", + "version": "0.3200.1" + }, + "libjsoncpp.so.0": { + "package": "jsoncpp", + "version": "0.0.0" + }, + "libarchive.so.13": { + "package": "libarchive", + "version": "13.3.3" + }, + "libpcre2-16.so.0": { + "package": "libpcre2-16", + "version": "0.6.0" + }, + "libffi.so.6": { + "package": "libffi", + "version": "6.0.4" + }, + "libcdt.so.5": { + "package": "graphviz", + "version": "5.0.0" + }, + "libcgraph.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvc.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvplugin_core.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvplugin_dot_layout.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvplugin_neato_layout.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvplugin_pango.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvplugin_xlib.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvpr.so.2": { + "package": "graphviz", + "version": "2.0.0" + }, + "liblab_gamut.so.1": { + "package": "graphviz", + "version": "1.0.0" + }, + "libpathplan.so.4": { + "package": "graphviz", + "version": "4.0.0" + }, + "libxdot.so.4": { + "package": "graphviz", + "version": "4.0.0" + }, + "libvncclient.so.1": { + "package": "libvncserver", + "version": "1.0.0" + }, + "libvncserver.so.1": { + "package": "libvncserver", + "version": "1.0.0" + }, + "libXvMC.so.1": { + "package": "libxvmc", + "version": "1.0.0" + }, + "libXvMCW.so.1": { + "package": "libxvmc", + "version": "1.0.0" + }, + "libedit.so.0": { + "package": "libedit", + "version": "0.0.56" + }, + "libstartup-notification-1.so.0": { + "package": "startup-notification", + "version": "0.0.0" + }, + "libhylafax-6.0.so.6": { + "package": "hylafax", + "version": "6" + }, + "libFLAC++.so.6": { + "package": "flac", + "version": "6.3.0" + }, + "libFLAC.so.8": { + "package": "flac", + "version": "8.3.0" + }, + "libpcap.so.1": { + "package": "libpcap", + "version": "1.8.1" + }, + "libSDL2-2.0.so.0": { + "package": "sdl2", + "version": "0.10.0" + }, + "libprotobuf-c.so.1": { + "package": "protobuf-c", + "version": "1.0.0" + }, + "cmd-mirror.so": { + "package": "lftp", + "version": "0" + }, + "cmd-sleep.so": { + "package": "lftp", + "version": "0" + }, + "cmd-torrent.so": { + "package": "lftp", + "version": "0" + }, + "liblftp-jobs.so.0": { + "package": "lftp", + "version": "0.0.0" + }, + "liblftp-network.so": { + "package": "lftp", + "version": "0" + }, + "liblftp-pty.so": { + "package": "lftp", + "version": "0" + }, + "liblftp-tasks.so.0": { + "package": "lftp", + "version": "0.0.0" + }, + "proto-file.so": { + "package": "lftp", + "version": "0" + }, + "proto-fish.so": { + "package": "lftp", + "version": "0" + }, + "proto-ftp.so": { + "package": "lftp", + "version": "0" + }, + "proto-http.so": { + "package": "lftp", + "version": "0" + }, + "proto-sftp.so": { + "package": "lftp", + "version": "0" + }, + "libcanberra-gtk.so.0": { + "package": "libcanberra-gtk2", + "version": "0.1.9" + }, + "libnvpair.so.1": { + "package": "zfs-libs", + "version": "1.0.1" + }, + "libuutil.so.1": { + "package": "zfs-libs", + "version": "1.0.1" + }, + "libzfs.so.2": { + "package": "zfs-libs", + "version": "2.0.0" + }, + "libzfs_core.so.1": { + "package": "zfs-libs", + "version": "1.0.0" + }, + "libzpool.so.2": { + "package": "zfs-libs", + "version": "2.0.0" + }, + "libformw.so.5": { + "package": "ncurses5-widec-libs", + "version": "5.9" + }, + "libmenuw.so.5": { + "package": "ncurses5-widec-libs", + "version": "5.9" + }, + "libncursesw.so.5": { + "package": "ncurses5-widec-libs", + "version": "5.9" + }, + "libpanelw.so.5": { + "package": "ncurses5-widec-libs", + "version": "5.9" + }, + "libmikmod.so.3": { + "package": "libmikmod", + "version": "3.3.0" + }, + "libwayland-egl.so.1": { + "package": "mesa-libwayland-egl", + "version": "1.0.0" + }, + "libbz2.so.1": { + "package": "libbz2", + "version": "1.0.6" + }, + "libfftw3f.so.3": { + "package": "fftw-single-libs", + "version": "3.5.6" + }, + "libfftw3f_threads.so.3": { + "package": "fftw-single-libs", + "version": "3.5.6" + }, + "libencfs.so.6": { + "package": "encfs", + "version": "6.0.2" + }, + "libgpgmepp.so.6": { + "package": "gpgmepp", + "version": "6.4.0" + }, + "libcogl-gles2.so.20": { + "package": "cogl", + "version": "20.4.2" + }, + "libcogl-pango.so.20": { + "package": "cogl", + "version": "20.4.2" + }, + "libcogl-path.so.20": { + "package": "cogl", + "version": "20.4.2" + }, + "libcogl.so.20": { + "package": "cogl", + "version": "20.4.2" + }, + "libhandle.so.1": { + "package": "xfsprogs-libs", + "version": "1.0.3" + }, + "libxkbui.so.1": { + "package": "libxkbui", + "version": "1.0.0" + }, + "libvanessa_socket.so.2": { + "package": "vanessa_socket", + "version": "2.1.0" + }, + "libdns_sd.so.1": { + "package": "avahi-compat-libdns_sd", + "version": "1.0.0" + }, + "libads-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libauth-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libdfs-server-ad-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libnetapi.so.0": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libnpa-tstream-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libprinting-migrate-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libsmbd-base-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libsmbd-conn-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libsmbldap.so.2": { + "package": "samba-common-server-libs", + "version": "2" + }, + "libsmbldaphelper-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libQtWebKit.so.4": { + "package": "qt-webkit", + "version": "4.9.4" + }, + "libcloog-isl.so.4": { + "package": "cloog", + "version": "4.0.0" + }, + "libs6rc.so.0.2": { + "package": "s6-rc", + "version": "0.2.1.2" + }, + "libgiblib.so.1": { + "package": "giblib", + "version": "1.0.6" + }, + "libmpg123.so.0": { + "package": "mpg123", + "version": "0.44.6" + }, + "libout123.so.0": { + "package": "mpg123", + "version": "0.2.1" + }, + "libodbc.so.2": { + "package": "unixodbc", + "version": "2.0.0" + }, + "libodbccr.so.2": { + "package": "unixodbc", + "version": "2.0.0" + }, + "libodbcinst.so.2": { + "package": "unixodbc", + "version": "2.0.0" + }, + "libXdamage.so.1": { + "package": "libxdamage", + "version": "1.1.0" + }, + "libexiv2.so.14": { + "package": "exiv2", + "version": "14.0.0" + }, + "libxml2.so.2": { + "package": "libxml2", + "version": "2.9.8" + }, + "libmtp.so.9": { + "package": "libmtp", + "version": "9.3.0" + }, + "libudev.so.1": { + "package": "eudev-libs", + "version": "1.6.3" + }, + "libGL.so.1": { + "package": "mesa-gl", + "version": "1.2.0" + }, + "libusb-1.0.so.0": { + "package": "libusb", + "version": "0.1.0" + }, + "libeggdbus-1.so.0": { + "package": "eggdbus", + "version": "0.0.0" + }, + "libwx_gtk2u_media-2.8.so.0": { + "package": "wxgtk2.8-media", + "version": "0.8.0" + }, + "libgtest.so": { + "package": "gtest", + "version": "0" + }, + "libgtest_main.so": { + "package": "gtest", + "version": "0" + }, + "libaim.so": { + "package": "libpurple-oscar", + "version": "0" + }, + "libicq.so": { + "package": "libpurple-oscar", + "version": "0" + }, + "liboscar.so.0": { + "package": "libpurple-oscar", + "version": "0.0.0" + }, + "libOSMesa.so.8": { + "package": "mesa-osmesa", + "version": "8.0.0" + }, + "libnet.so.1": { + "package": "libnet", + "version": "1.7.0" + }, + "libboost_thread-mt.so.1.62.0": { + "package": "boost-thread", + "version": "1.62.0" + }, + "libpcsclite.so.1": { + "package": "pcsc-lite-libs", + "version": "1.0.0" + }, + "libpcscspy.so.0": { + "package": "pcsc-lite-libs", + "version": "0.0.0" + }, + "libphodav-2.0.so.0": { + "package": "phodav", + "version": "0.0.0" + }, + "libepoxy.so.0": { + "package": "libepoxy", + "version": "0.0.0" + }, + "libORBit-2.so.0": { + "package": "orbit2", + "version": "0.1.0" + }, + "libORBit-imodule-2.so.0": { + "package": "orbit2", + "version": "0.0.0" + }, + "libORBitCosNaming-2.so.0": { + "package": "orbit2", + "version": "0.1.0" + }, + "libnspr4.so": { + "package": "nspr", + "version": "0" + }, + "libplc4.so": { + "package": "nspr", + "version": "0" + }, + "libplds4.so": { + "package": "nspr", + "version": "0" + }, + "libtsocks.so.1.8": { + "package": "tsocks", + "version": "1.8" + }, + "libmilter.so.1.0.2": { + "package": "libmilter", + "version": "1.0.2" + }, + "libCHARSET3-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libaddns-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libasn1util-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libauthkrb5-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcli-cldap-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcli-ldap-common-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcli-nbt-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcli-smb-common-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcliauth-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcmocka-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcommon-auth-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libdbwrap-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libflag-mapping-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libgenrand-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libgensec-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libgse-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libinterfaces-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libiov-buf-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libkrb5samba-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libldbsamba-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libmessages-dgm-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libmessages-util-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libmsghdr-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libndr-krb5pac.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libndr-nbt.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libndr-samba-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libndr-standard.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libndr.so.0": { + "package": "samba-libs", + "version": "0.1.0" + }, + "libpopt-samba3-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-cluster-support-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-credentials.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libsamba-debug-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-errors.so.1": { + "package": "samba-libs", + "version": "1" + }, + "libsamba-hostconfig.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libsamba-modules-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-security-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-sockets-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-util.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libsamba3-util-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamdb-common-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamdb.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libsecrets3-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libserver-id-db-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libserver-role-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsmb-transport-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsmbconf.so.0": { + "package": "samba-libs", + "version": "0" + }, + "libsmbd-shim-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsocket-blocking-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsys-rw-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libtalloc-report-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libtdb-wrap-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libtevent-util.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libtime-basic-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libutil-cmdline-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libutil-reg-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libutil-setid-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libutil-tdb-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "liblua-5.2.so.0": { + "package": "lua5.2-libs", + "version": "0.0.0" + }, + "libnetfilter_queue.so.1": { + "package": "libnetfilter_queue", + "version": "1.3.0" + }, + "libnetfilter_acct.so.1": { + "package": "libnetfilter_acct", + "version": "1.0.0" + }, + "libXext.so.6": { + "package": "libxext", + "version": "6.4.0" + }, + "libmcrypt.so.4": { + "package": "libmcrypt", + "version": "4.4.8" + }, + "libunique-1.0.so.0": { + "package": "libunique", + "version": "0.100.6" + }, + "libskarnet.so.2.6": { + "package": "skalibs", + "version": "2.6.1.0" + }, + "libnih-dbus.so.1": { + "package": "libnih", + "version": "1.0.0" + }, + "libnih.so.1": { + "package": "libnih", + "version": "1.0.0" + }, + "libboost_system-mt.so.1.62.0": { + "package": "boost-system", + "version": "1.62.0" + }, + "libboost_system.so.1.62.0": { + "package": "boost-system", + "version": "1.62.0" + }, + "libisns.so.0": { + "package": "open-isns-lib", + "version": "0" + }, + "libgs.so.9": { + "package": "ghostscript", + "version": "9.26" + }, + "libijs-0.35.so": { + "package": "ghostscript", + "version": "0" + }, + "libImlib2.so.1": { + "package": "imlib2", + "version": "1.4.10" + }, + "libiperf.so.0": { + "package": "iperf3", + "version": "0.0.0" + }, + "rlm_redis.so": { + "package": "freeradius-redis", + "version": "0" + }, + "rlm_rediswho.so": { + "package": "freeradius-redis", + "version": "0" + }, + "libtorrent.so.19": { + "package": "libtorrent", + "version": "19.0.0" + }, + "libmodplug.so.1": { + "package": "libmodplug", + "version": "1.0.0" + }, + "libdvbv5.so.0": { + "package": "v4l-utils-dvbv5", + "version": "0.0.0" + }, + "libcue.so.2": { + "package": "libcue", + "version": "2.1.0" + }, + "lib21_fts_solr_plugin.so": { + "package": "dovecot-fts-solr", + "version": "0" + }, + "libfakeroot-0.so": { + "package": "fakeroot", + "version": "0" + }, + "libcrack.so.2": { + "package": "cracklib", + "version": "2.9.0" + }, + "libopus.so.0": { + "package": "opus", + "version": "0.6.1" + }, + "libbind9.so.161": { + "package": "bind-libs", + "version": "161.0.2" + }, + "libdns.so.1105": { + "package": "bind-libs", + "version": "1105.0.0" + }, + "libirs.so.161": { + "package": "bind-libs", + "version": "161.0.0" + }, + "libisc.so.1100": { + "package": "bind-libs", + "version": "1100.2.0" + }, + "libisccc.so.161": { + "package": "bind-libs", + "version": "161.0.1" + }, + "libisccfg.so.163": { + "package": "bind-libs", + "version": "163.0.1" + }, + "liblwres.so.161": { + "package": "bind-libs", + "version": "161.0.1" + }, + "rlm_perl.so": { + "package": "freeradius-perl", + "version": "0" + }, + "libqpdf.so.18": { + "package": "qpdf-libs", + "version": "18.1.0" + }, + "libspf2.so.2": { + "package": "libspf2", + "version": "2.1.0" + }, + "libSDL2_mixer-2.0.so.0": { + "package": "sdl2_mixer", + "version": "0.2.0" + }, + "libglut.so.3": { + "package": "freeglut", + "version": "3.10.0" + }, + "libEGL.so.1": { + "package": "mesa-egl", + "version": "1.0.0" + }, + "libexo-1.so.0": { + "package": "exo", + "version": "0.1.0" + }, + "libexo-2.so.0": { + "package": "exo", + "version": "0.1.0" + }, + "libboost_date_time-mt.so.1.62.0": { + "package": "boost-date_time", + "version": "1.62.0" + }, + "libboost_date_time.so.1.62.0": { + "package": "boost-date_time", + "version": "1.62.0" + }, + "libnl.so.1": { + "package": "libnl", + "version": "1.1.4" + }, + "libasyncns.so.0": { + "package": "libasyncns", + "version": "0.3.1" + }, + "libserf-1.so.1": { + "package": "serf", + "version": "1.3.0" + }, + "libaugeas.so.0": { + "package": "augeas-libs", + "version": "0.23.0" + }, + "libfa.so.1": { + "package": "augeas-libs", + "version": "1.4.5" + }, + "libaudit.so.1": { + "package": "audit-libs", + "version": "1.0.0" + }, + "libauparse.so.0": { + "package": "audit-libs", + "version": "0.0.0" + }, + "libgcrypt.so.20": { + "package": "libgcrypt", + "version": "20.2.3" + }, + "libSDL2_image-2.0.so.0": { + "package": "sdl2_image", + "version": "0.2.3" + }, + "libsecret-1.so.0": { + "package": "libsecret", + "version": "0.0.0" + }, + "libcanberra.so.0": { + "package": "libcanberra", + "version": "0.2.5" + }, + "libssh.so.4": { + "package": "libssh", + "version": "4.4.3" + }, + "libssh_threads.so.4": { + "package": "libssh", + "version": "4.4.3" + }, + "libcelt051.so.0": { + "package": "celt051", + "version": "0.0.0" + }, + "libgd.so.3": { + "package": "libgd", + "version": "3.0.5" + }, + "libobjc.so.4": { + "package": "libobjc", + "version": "4.0.0" + }, + "libappstream-builder.so.8": { + "package": "appstream-glib-builder", + "version": "8.0.10" + }, + "libetpan.so.20": { + "package": "libetpan", + "version": "20.1.0" + }, + "libmupdf.so.0": { + "package": "mupdf", + "version": "0" + }, + "libmupdfthird.so.0": { + "package": "mupdf", + "version": "0" + }, + "libmpfr.so.4": { + "package": "mpfr3", + "version": "4.1.5" + }, + "psqlodbca.so": { + "package": "psqlodbc", + "version": "0" + }, + "psqlodbcw.so": { + "package": "psqlodbc", + "version": "0" + }, + "libisl.so.15": { + "package": "isl", + "version": "15.3.0" + }, + "libtty.so.0": { + "package": "termrec", + "version": "0.0.0" + }, + "libguile-2.0.so.22": { + "package": "guile-libs", + "version": "22.8.1" + }, + "libee.so.0": { + "package": "libee", + "version": "0.0.0" + }, + "libasr.so.0": { + "package": "libasr", + "version": "0.0.2" + }, + "liblutok.so.3": { + "package": "lutok", + "version": "3.0.0" + }, + "libncftp.so.3": { + "package": "ncftp", + "version": "3" + }, + "libgettextlib-0.19.8.1.so": { + "package": "gettext", + "version": "0" + }, + "libgettextsrc-0.19.8.1.so": { + "package": "gettext", + "version": "0" + }, + "libp11-kit.so.0": { + "package": "p11-kit", + "version": "0.1.0" + }, + "libc-client.so.1": { + "package": "c-client", + "version": "1.0.0" + }, + "libjack.so.0": { + "package": "jack", + "version": "0.1.0" + }, + "libjacknet.so.0": { + "package": "jack", + "version": "0.1.0" + }, + "libjackserver.so.0": { + "package": "jack", + "version": "0.1.0" + }, + "libe2p.so.2": { + "package": "e2fsprogs-libs", + "version": "2.3" + }, + "libext2fs.so.2": { + "package": "e2fsprogs-libs", + "version": "2.4" + }, + "libss.so.2": { + "package": "e2fsprogs-libs", + "version": "2.0" + }, + "libglamor.so.0": { + "package": "glamor-egl", + "version": "0.0.0" + }, + "libdnet.so.1": { + "package": "libdnet", + "version": "1.0.1" + }, + "libidn.so.11": { + "package": "libidn", + "version": "11.6.16" + }, + "libblkid.so.1": { + "package": "libblkid", + "version": "1.1.0" + }, + "libkamailio_ims.so.0": { + "package": "kamailio", + "version": "0.1" + }, + "libprint.so.1": { + "package": "kamailio", + "version": "1.2" + }, + "libsrdb1.so.1": { + "package": "kamailio", + "version": "1.0" + }, + "libsrdb2.so.1": { + "package": "kamailio", + "version": "1.0" + }, + "libsrutils.so.1": { + "package": "kamailio", + "version": "1.0" + }, + "libtrie.so.1": { + "package": "kamailio", + "version": "1.0" + }, + "libgcab-1.0.so.0": { + "package": "libgcab", + "version": "0.0.0" + }, + "liblinenoise.so.0": { + "package": "linenoise", + "version": "0.0.0" + }, + "libgsf-1.so.114": { + "package": "libgsf", + "version": "114.0.41" + }, + "libXrandr.so.2": { + "package": "libxrandr", + "version": "2.2.0" + }, + "libboost_math_c99-mt.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_c99.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_c99f-mt.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_c99f.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_c99l-mt.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_c99l.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_tr1-mt.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_tr1.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_tr1f-mt.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_tr1f.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_tr1l-mt.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libboost_math_tr1l.so.1.62.0": { + "package": "boost-math", + "version": "1.62.0" + }, + "libgirepository-1.0.so.1": { + "package": "gobject-introspection", + "version": "1.0.0" + }, + "libexpect5.45.so": { + "package": "expect", + "version": "0" + }, + "libcunit.so.1": { + "package": "cunit", + "version": "1.0.1" + }, + "liblttng-ust-ctl.so.4": { + "package": "lttng-ust", + "version": "4.0.0" + }, + "liblttng-ust-cyg-profile-fast.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-cyg-profile.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-dl.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-fd.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-fork.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-libc-wrapper.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-pthread-wrapper.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-tracepoint.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "libiniparser.so.0": { + "package": "iniparser", + "version": "0" + }, + "libclucene-contribs-lib.so.1": { + "package": "clucene-contribs", + "version": "2.3.3.4" + }, + "libsexy.so.2": { + "package": "libsexy", + "version": "2.0.4" + }, + "libatasmart.so.4": { + "package": "libatasmart", + "version": "4.0.5" + }, + "libfcgi.so.0": { + "package": "fcgi", + "version": "0.0.0" + }, + "libotr.so.2": { + "package": "libotr3", + "version": "2.2.1" + }, + "libxvidcore.so.4": { + "package": "xvidcore", + "version": "4.3" + }, + "libfam.so.0": { + "package": "gamin", + "version": "0.0.0" + }, + "libgamin-1.so.0": { + "package": "gamin", + "version": "0.1.10" + }, + "libip4tc.so.0": { + "package": "iptables", + "version": "0.1.0" + }, + "libip6tc.so.0": { + "package": "iptables", + "version": "0.1.0" + }, + "libipq.so.0": { + "package": "iptables", + "version": "0.0.0" + }, + "libiptc.so.0": { + "package": "iptables", + "version": "0.0.0" + }, + "libxtables.so.12": { + "package": "iptables", + "version": "12.0.0" + }, + "libdriver_pgsql.so": { + "package": "dovecot-pgsql", + "version": "0" + }, + "libneon.so.27": { + "package": "neon", + "version": "27.3.2" + }, + "libatk-bridge-2.0.so.0": { + "package": "at-spi2-atk", + "version": "0.0.0" + }, + "libglapi.so.0": { + "package": "mesa-glapi", + "version": "0.0.0" + }, + "libstfl.so.0": { + "package": "stfl", + "version": "0.24" + }, + "libtasn1.so.6": { + "package": "libtasn1", + "version": "6.5.4" + }, + "libgomp.so.1": { + "package": "libgomp", + "version": "1.0.0" + }, + "libdv.so.4": { + "package": "libdv", + "version": "4.0.3" + }, + "libclucene-core.so.1": { + "package": "clucene", + "version": "2.3.3.4" + }, + "libclucene-shared.so.1": { + "package": "clucene", + "version": "2.3.3.4" + }, + "libvirt-admin.so.0": { + "package": "libvirt-client", + "version": "0.5005.0" + }, + "libvirt-lxc.so.0": { + "package": "libvirt-client", + "version": "0.5005.0" + }, + "libvirt-qemu.so.0": { + "package": "libvirt-client", + "version": "0.5005.0" + }, + "libvirt.so.0": { + "package": "libvirt-client", + "version": "0.5005.0" + }, + "libform.so.5": { + "package": "ncurses5-libs", + "version": "5.9" + }, + "libmenu.so.5": { + "package": "ncurses5-libs", + "version": "5.9" + }, + "libncurses.so.5": { + "package": "ncurses5-libs", + "version": "5.9" + }, + "libpanel.so.5": { + "package": "ncurses5-libs", + "version": "5.9" + }, + "libgstbase-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libgstcheck-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libgstcontroller-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libgstdataprotocol-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libgstnet-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libgstreamer-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libverto-glib.so.1": { + "package": "libverto-glib", + "version": "1.0.0" + }, + "libspreadsheet-1.12.36.so": { + "package": "gnumeric", + "version": "0" + }, + "libvdehist.so.0": { + "package": "vde2-libs", + "version": "0.0.1" + }, + "libvdemgmt.so.0": { + "package": "vde2-libs", + "version": "0.0.1" + }, + "libvdeplug.so.3": { + "package": "vde2-libs", + "version": "3.0.1" + }, + "libvdesnmp.so.0": { + "package": "vde2-libs", + "version": "0.0.1" + }, + "libbonjour.so": { + "package": "libpurple-bonjour", + "version": "0" + }, + "libwayland-server.so.0": { + "package": "wayland-libs-server", + "version": "0.1.0" + }, + "libtk8.6.so": { + "package": "tk", + "version": "0" + }, + "libpcp.so.1": { + "package": "pgpool", + "version": "1.0.0" + }, + "proto_dhcp.so": { + "package": "freeradius", + "version": "0" + }, + "proto_vmps.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_always.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_attr_filter.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_cache.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_cache_rbtree.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_chap.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_counter.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_cram.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_date.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_detail.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_dhcp.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_digest.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_dynamic_clients.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_exec.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_expiration.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_expr.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_files.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_ippool.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_linelog.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_logintime.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_mschap.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_otp.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_pap.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_passwd.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_preprocess.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_radutmp.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_realm.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_replicate.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_soh.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_sometimes.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_test.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_unix.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_unpack.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_utf8.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_wimax.so": { + "package": "freeradius", + "version": "0" + }, + "libgps.so.22": { + "package": "gpsd", + "version": "22.0.0" + }, + "libglade-2.0.so.0": { + "package": "libglade", + "version": "0.0.7" + }, + "libswish-e.so.2": { + "package": "swish-e", + "version": "2.0.0" + }, + "rlm_sql_postgresql.so": { + "package": "freeradius-postgresql", + "version": "0" + }, + "libnetfilter_cttimeout.so.1": { + "package": "libnetfilter_cttimeout", + "version": "1.0.0" + }, + "libgucharmap_2_90.so.7": { + "package": "gucharmap", + "version": "7.0.0" + }, + "libgailutil-3.so.0": { + "package": "gtk+3.0", + "version": "0.0.0" + }, + "libgdk-3.so.0": { + "package": "gtk+3.0", + "version": "0.2200.21" + }, + "libgtk-3.so.0": { + "package": "gtk+3.0", + "version": "0.2200.21" + }, + "libgvplugin_gdk.so.6": { + "package": "graphviz-gtk", + "version": "6.0.0" + }, + "libgvplugin_gtk.so.6": { + "package": "graphviz-gtk", + "version": "6.0.0" + }, + "libgvplugin_rsvg.so.6": { + "package": "graphviz-gtk", + "version": "6.0.0" + }, + "libmnl.so.0": { + "package": "libmnl", + "version": "0.2.0" + }, + "libproxychains4.so": { + "package": "proxychains-ng", + "version": "0" + }, + "libvdpau.so.1": { + "package": "libvdpau", + "version": "1.0.0" + }, + "libmng.so.2": { + "package": "libmng", + "version": "2.0.2" + }, + "libnfnetlink.so.0": { + "package": "libnfnetlink", + "version": "0.2.0" + }, + "libicudata.so.59": { + "package": "icu-libs", + "version": "59.1" + }, + "libicui18n.so.59": { + "package": "icu-libs", + "version": "59.1" + }, + "libicuio.so.59": { + "package": "icu-libs", + "version": "59.1" + }, + "libicutest.so.59": { + "package": "icu-libs", + "version": "59.1" + }, + "libicutu.so.59": { + "package": "icu-libs", + "version": "59.1" + }, + "libicuuc.so.59": { + "package": "icu-libs", + "version": "59.1" + }, + "libc.musl-x86_64.so.1": { + "package": "musl", + "version": "1" + }, + "librtmp.so.1": { + "package": "librtmp", + "version": "1" + }, + "libyaml-0.so.2": { + "package": "yaml", + "version": "2.0.5" + }, + "libCEGUIBase-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUIExpatParser-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUIFalagardWRBase-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUILuaScriptModule-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUIOpenGLRenderer-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUITGAImageCodec-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUITinyXMLParser-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUItoluapp-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libkeybinder.so.0": { + "package": "keybinder", + "version": "0.1.0" + }, + "libcups.so.2": { + "package": "cups-libs", + "version": "2" + }, + "libcupsimage.so.2": { + "package": "cups-libs", + "version": "2" + }, + "libpcre2-32.so.0": { + "package": "libpcre2-32", + "version": "0.6.0" + }, + "libasprintf.so.0": { + "package": "gettext-asprintf", + "version": "0.0.0" + }, + "libcc1.so.0": { + "package": "gcc", + "version": "0.0.0" + }, + "libcilkrts.so.5": { + "package": "gcc", + "version": "5.0.0" + }, + "libitm.so.1": { + "package": "gcc", + "version": "1.0.0" + }, + "rlm_sql_sqlite.so": { + "package": "freeradius-sqlite", + "version": "0" + }, + "libavahi-glib.so.1": { + "package": "avahi-glib", + "version": "1.0.2" + }, + "libavahi-gobject.so.0": { + "package": "avahi-glib", + "version": "0.0.4" + }, + "libdriver_mysql.so": { + "package": "dovecot-mysql", + "version": "0" + }, + "libboost_signals-mt.so.1.62.0": { + "package": "boost-signals", + "version": "1.62.0" + }, + "libboost_signals.so.1.62.0": { + "package": "boost-signals", + "version": "1.62.0" + }, + "libXfont.so.1": { + "package": "libxfont", + "version": "1.4.1" + }, + "libatomic_ops.so.1": { + "package": "libatomic_ops", + "version": "1.0.4" + }, + "libatomic_ops_gpl.so.1": { + "package": "libatomic_ops", + "version": "1.0.4" + }, + "libgss.so.1": { + "package": "libgss", + "version": "1.1.0" + }, + "libxfce4kbd-private-3.so.0": { + "package": "libxfce4ui-gtk3", + "version": "0.0.0" + }, + "libxfce4ui-2.so.0": { + "package": "libxfce4ui-gtk3", + "version": "0.0.0" + }, + "liblcms.so.1": { + "package": "liblcms", + "version": "1.0.19" + }, + "libslang.so.2": { + "package": "slang", + "version": "2.3.1" + }, + "rlm_ldap.so": { + "package": "freeradius-ldap", + "version": "0" + }, + "libotr.so.5": { + "package": "libotr", + "version": "5.1.1" + }, + "libgettextpo.so.0": { + "package": "gettext-libs", + "version": "0.5.4" + }, + "libharfbuzz-icu.so.0": { + "package": "harfbuzz-icu", + "version": "0.10600.3" + }, + "libxmlrpc++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_abyss++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_cpp.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_packetsocket.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_server++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_server_abyss++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_server_cgi++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_server_pstream++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_util++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxklavier.so.16": { + "package": "libxklavier", + "version": "16.4.0" + }, + "rlm_python.so": { + "package": "freeradius-python", + "version": "0" + }, + "libfuse.so.2": { + "package": "fuse", + "version": "2.9.8" + }, + "libulockmgr.so.1": { + "package": "fuse", + "version": "1.0.1" + }, + "liblzma.so.5": { + "package": "xz-libs", + "version": "5.2.3" + }, + "libpcre32.so.0": { + "package": "libpcre32", + "version": "0.0.9" + }, + "libfts.so.0": { + "package": "fts", + "version": "0.0.0" + }, + "libslim.so.1.3.6": { + "package": "slim", + "version": "1.3.6" + }, + "libtls.so.16": { + "package": "libressl2.6-libtls", + "version": "16.0.1" + }, + "libgsm.so.1": { + "package": "gsm", + "version": "1.0.12" + }, + "libGeoIP.so.1": { + "package": "geoip", + "version": "1.6.11" + }, + "libisofs.so.6": { + "package": "libisofs", + "version": "6.84.0" + }, + "libsndfile.so.1": { + "package": "libsndfile", + "version": "1.0.28" + }, + "libjabber.so.0": { + "package": "libpurple-xmpp", + "version": "0.0.0" + }, + "libxmpp.so": { + "package": "libpurple-xmpp", + "version": "0" + }, + "liblua-5.3.so.0": { + "package": "lua5.3-libs", + "version": "0.0.0" + }, + "libpam.so.0": { + "package": "linux-pam", + "version": "0.84.2" + }, + "libpam_misc.so.0": { + "package": "linux-pam", + "version": "0.82.1" + }, + "libpamc.so.0": { + "package": "linux-pam", + "version": "0.82.1" + }, + "libmcpp.so.0": { + "package": "mcpp-libs", + "version": "0.3.0" + }, + "libSDL_image-1.2.so.0": { + "package": "sdl_image", + "version": "0.8.4" + }, + "libgladeui-1.so.11": { + "package": "glade3", + "version": "11.2.0" + }, + "libnftnl.so.7": { + "package": "libnftnl-libs", + "version": "7.0.0" + }, + "libcdio++.so.0": { + "package": "libcdio++", + "version": "0.0.2" + }, + "libiso9660++.so.0": { + "package": "libcdio++", + "version": "0.0.0" + }, + "libg7221codec.so.2": { + "package": "pjproject", + "version": "2" + }, + "libilbccodec.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpj.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjlib-util.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjmedia-audiodev.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjmedia-codec.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjmedia-videodev.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjmedia.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjnath.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjsip-simple.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjsip-ua.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjsip.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjsua.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjsua2.so.2": { + "package": "pjproject", + "version": "2" + }, + "libyuv.so.2": { + "package": "pjproject", + "version": "2" + }, + "libvte.so.9": { + "package": "vte", + "version": "9.2800.2" + }, + "libcharon.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libradius.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libsimaka.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libstrongswan.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libtls.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libvici.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libavc1394.so.0": { + "package": "libavc1394", + "version": "0.3.0" + }, + "librom1394.so.0": { + "package": "libavc1394", + "version": "0.3.0" + }, + "libXrender.so.1": { + "package": "libxrender", + "version": "1.3.0" + }, + "libpcre16.so.0": { + "package": "libpcre16", + "version": "0.2.9" + }, + "libfastjson.so.4": { + "package": "libfastjson", + "version": "4.2.0" + }, + "rlm_sql.so": { + "package": "freeradius-sql", + "version": "0" + }, + "rlm_sql_null.so": { + "package": "freeradius-sql", + "version": "0" + }, + "rlm_sqlcounter.so": { + "package": "freeradius-sql", + "version": "0" + }, + "rlm_sqlippool.so": { + "package": "freeradius-sql", + "version": "0" + }, + "libpoppler-glib.so.8": { + "package": "poppler-glib", + "version": "8.9.0" + }, + "libwebp.so.7": { + "package": "libwebp", + "version": "7.0.0" + }, + "libwebpdecoder.so.3": { + "package": "libwebp", + "version": "3.0.0" + }, + "libwebpdemux.so.2": { + "package": "libwebp", + "version": "2.0.2" + }, + "libwebpmux.so.3": { + "package": "libwebp", + "version": "3.0.0" + }, + "libSDL-1.2.so.0": { + "package": "sdl", + "version": "0.11.4" + }, + "libnewt.so.0.52": { + "package": "newt", + "version": "0.52.20" + }, + "libespeak.so.1": { + "package": "espeak", + "version": "1.1.48" + }, + "libpython3.6m.so.1.0": { + "package": "python3", + "version": "1.0" + }, + "libpython3.so": { + "package": "python3", + "version": "0" + }, + "libgmime-2.6.so.0": { + "package": "gmime", + "version": "0.620.0" + }, + "libxkbfile.so.1": { + "package": "libxkbfile", + "version": "1.0.2" + }, + "libtheora.so.0": { + "package": "libtheora", + "version": "0.3.10" + }, + "libtheoradec.so.1": { + "package": "libtheora", + "version": "1.1.4" + }, + "libtheoraenc.so.1": { + "package": "libtheora", + "version": "1.1.2" + }, + "libunique-3.0.so.0": { + "package": "libunique3", + "version": "0.0.2" + }, + "libpopt.so.0": { + "package": "popt", + "version": "0.0.0" + }, + "libgiomm-2.4.so.1": { + "package": "glibmm", + "version": "1.3.0" + }, + "libglibmm-2.4.so.1": { + "package": "glibmm", + "version": "1.3.0" + }, + "libglibmm_generate_extra_defs-2.4.so.1": { + "package": "glibmm", + "version": "1.3.0" + }, + "libSM.so.6": { + "package": "libsm", + "version": "6.0.1" + }, + "libquvi-0.9-0.9.4.so": { + "package": "libquvi", + "version": "0" + }, + "libdc1394.so.22": { + "package": "libdc1394", + "version": "22.2.1" + }, + "libvorbis.so.0": { + "package": "libvorbis", + "version": "0.4.8" + }, + "libvorbisenc.so.2": { + "package": "libvorbis", + "version": "2.0.11" + }, + "libvorbisfile.so.3": { + "package": "libvorbis", + "version": "3.3.7" + }, + "libgtkspell.so.0": { + "package": "gtkspell", + "version": "0.0.0" + }, + "libpostfix-dns.so": { + "package": "postfix", + "version": "0" + }, + "libpostfix-global.so": { + "package": "postfix", + "version": "0" + }, + "libpostfix-master.so": { + "package": "postfix", + "version": "0" + }, + "libpostfix-tls.so": { + "package": "postfix", + "version": "0" + }, + "libpostfix-util.so": { + "package": "postfix", + "version": "0" + }, + "librrd.so.4": { + "package": "librrd", + "version": "4.3.5" + }, + "libck-connector.so.0": { + "package": "consolekit2", + "version": "0.0.0" + }, + "libconsolekit.so.1": { + "package": "consolekit2", + "version": "1.0.0" + }, + "libdvdnav.so.4": { + "package": "libdvdnav", + "version": "4.2.0" + }, + "libdaq.so.2": { + "package": "daq", + "version": "2.0.4" + }, + "libsfbpf.so.0": { + "package": "daq", + "version": "0.0.1" + }, + "libsvn_swig_perl-1.so.0": { + "package": "perl-subversion", + "version": "0.0.0" + }, + "libexecline.so.2.3": { + "package": "execline", + "version": "2.3.0.3" + }, + "libHDB-SAMBA4-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libLIBWBCLIENT-OLD-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libauth-unix-token-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libauth4-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libcluster-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libdb-glue-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libdcerpc-samr.so.0": { + "package": "samba-dc-libs", + "version": "0.0.1" + }, + "libdcerpc-server.so.0": { + "package": "samba-dc-libs", + "version": "0.0.1" + }, + "libdnsserver-common-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libdsdb-module-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libhdb-samba4.so.11": { + "package": "samba-dc-libs", + "version": "11.0.2" + }, + "libkdc-samba4.so.2": { + "package": "samba-dc-libs", + "version": "2.0.0" + }, + "libpac-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libposix-eadb-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libprocess-model-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libsamba-policy.so.0": { + "package": "samba-dc-libs", + "version": "0.0.1" + }, + "libservice-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libshares-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libkmod.so.2": { + "package": "kmod", + "version": "2.3.2" + }, + "libv4l1.so.0": { + "package": "v4l-utils-libs", + "version": "0.0.0" + }, + "libv4l2.so.0": { + "package": "v4l-utils-libs", + "version": "0.0.0" + }, + "libv4l2rds.so.0": { + "package": "v4l-utils-libs", + "version": "0.0.0" + }, + "libv4lconvert.so.0": { + "package": "v4l-utils-libs", + "version": "0.0.0" + }, + "libfreeswitch.so.1": { + "package": "freeswitch", + "version": "1.0.0" + }, + "libvarnishapi.so.1": { + "package": "varnish-libs", + "version": "1.0.6" + }, + "libfdt.so.1": { + "package": "libfdt", + "version": "0" + }, + "libcppunit_subunit.so.0": { + "package": "subunit-libs", + "version": "0.0.0" + }, + "libsubunit.so.0": { + "package": "subunit-libs", + "version": "0.0.0" + }, + "libblktapctl.so.1.0": { + "package": "xen-libs", + "version": "1.0.0" + }, + "libfsimage.so.1.0": { + "package": "xen-libs", + "version": "1.0.0" + }, + "libvhd.so.1.0": { + "package": "xen-libs", + "version": "1.0.0" + }, + "libxencall.so.1": { + "package": "xen-libs", + "version": "1.0" + }, + "libxenctrl.so.4.9": { + "package": "xen-libs", + "version": "4.9.0" + }, + "libxendevicemodel.so.1": { + "package": "xen-libs", + "version": "1.0" + }, + "libxenevtchn.so.1": { + "package": "xen-libs", + "version": "1.0" + }, + "libxenforeignmemory.so.1": { + "package": "xen-libs", + "version": "1.1" + }, + "libxengnttab.so.1": { + "package": "xen-libs", + "version": "1.1" + }, + "libxenguest.so.4.9": { + "package": "xen-libs", + "version": "4.9.0" + }, + "libxenlight.so.4.9": { + "package": "xen-libs", + "version": "4.9.0" + }, + "libxenstat.so.0": { + "package": "xen-libs", + "version": "0.0" + }, + "libxenstore.so.3.0": { + "package": "xen-libs", + "version": "3.0.3" + }, + "libxentoollog.so.1": { + "package": "xen-libs", + "version": "1.0" + }, + "libxenvchan.so.4.9": { + "package": "xen-libs", + "version": "4.9.0" + }, + "libxlutil.so.4.9": { + "package": "xen-libs", + "version": "4.9.0" + }, + "libpkgconf.so.2": { + "package": "pkgconf", + "version": "2.0.0" + }, + "libnjb.so.5": { + "package": "libnjb", + "version": "5.1.1" + }, + "libgcj-tools.so.17": { + "package": "libgcj", + "version": "17.0.0" + }, + "libgcj.so.17": { + "package": "libgcj", + "version": "17.0.0" + }, + "libgcj_bc.so.1": { + "package": "libgcj", + "version": "1.0.0" + }, + "libgij.so.17": { + "package": "libgcj", + "version": "17.0.0" + }, + "libjavamath.so": { + "package": "libgcj", + "version": "0" + }, + "libjvm.so": { + "package": "libgcj", + "version": "0" + }, + "libboost_python3-mt.so.1.62.0": { + "package": "boost-python3", + "version": "1.62.0" + }, + "libboost_python3.so.1.62.0": { + "package": "boost-python3", + "version": "1.62.0" + }, + "libaudcore.so.5": { + "package": "audacious", + "version": "5.0.0" + }, + "libaudgui.so.5": { + "package": "audacious", + "version": "5.0.0" + }, + "libaudtag.so.3": { + "package": "audacious", + "version": "3.0.0" + }, + "libcmph.so.0": { + "package": "libcmph", + "version": "0.0.0" + }, + "librlog.so.5": { + "package": "rlog", + "version": "5.0.0" + }, + "libMESSAGING-SEND-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libcli-spoolss-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libdcerpc-binding.so.0": { + "package": "samba-common-libs", + "version": "0.0.1" + }, + "libdcerpc-samba-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "liblibcli-lsa3-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "liblibcli-netlogon3-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "liblibsmb-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libmsrpc3-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libndr-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libsamba-passdb.so.0": { + "package": "samba-common-libs", + "version": "0.27.0" + }, + "libtrusts-util-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libs6net.so.2.3": { + "package": "s6-networking", + "version": "2.3.0.2" + }, + "libstls.so.2.3": { + "package": "s6-networking", + "version": "2.3.0.2" + }, + "libappstream-glib.so.8": { + "package": "appstream-glib", + "version": "8.0.10" + }, + "libx265.so.130": { + "package": "x265", + "version": "130" + }, + "libxfsm-4.6.so.0": { + "package": "xfce4-session", + "version": "0.0.0" + }, + "libbluetooth.so.3": { + "package": "bluez-libs", + "version": "3.18.16" + }, + "libspice-server.so.1": { + "package": "spice-server", + "version": "1.12.5" + }, + "libgphoto2.so.6": { + "package": "libgphoto2", + "version": "6.0.0" + }, + "libgphoto2_port.so.12": { + "package": "libgphoto2", + "version": "12.0.0" + }, + "libgnomevfs-2.so.0": { + "package": "gnome-vfs", + "version": "0.2400.4" + }, + "libaio.so.1": { + "package": "libaio", + "version": "1.0.1" + }, + "libyajl.so.2": { + "package": "yajl", + "version": "2.1.0" + }, + "libsngtc_node.so": { + "package": "sngtc_client", + "version": "0" + }, + "liboil-0.3.so.0": { + "package": "liboil", + "version": "0.3.0" + }, + "libsnappy.so.1": { + "package": "snappy", + "version": "1.3.1" + }, + "libzdb.so.11": { + "package": "libzdb", + "version": "11.0.0" + }, + "libart_lgpl_2.so.2": { + "package": "libart-lgpl", + "version": "2.3.21" + }, + "libenchant.so.1": { + "package": "enchant", + "version": "1.6.0" + }, + "libgfortran.so.3": { + "package": "libgfortran", + "version": "3.0.0" + }, + "libgnutlsxx.so.28": { + "package": "gnutls-c++", + "version": "28.1.0" + }, + "libsylph-0.so.1": { + "package": "sylpheed", + "version": "1.2.1" + }, + "libsylpheed-plugin-0.so.1": { + "package": "sylpheed", + "version": "1.2.1" + }, + "libqrencode.so.4": { + "package": "libqrencode", + "version": "4.0.0" + }, + "librevenge-0.0.so.0": { + "package": "librevenge", + "version": "0.0.4" + }, + "librevenge-generators-0.0.so.0": { + "package": "librevenge", + "version": "0.0.4" + }, + "librevenge-stream-0.0.so.0": { + "package": "librevenge", + "version": "0.0.4" + }, + "libdvdread.so.4": { + "package": "libdvdread", + "version": "4.2.0" + }, + "libbstring.so.0": { + "package": "coova-chilli", + "version": "0.0.0" + }, + "libchilli.so.0": { + "package": "coova-chilli", + "version": "0.0.0" + }, + "libverto-libevent.so.1": { + "package": "libverto-libevent", + "version": "1.0.0" + }, + "libsoup-2.4.so.1": { + "package": "libsoup", + "version": "1.8.0" + }, + "libsoup-gnome-2.4.so.1": { + "package": "libsoup", + "version": "1.8.0" + }, + "libqca.so.2": { + "package": "qca", + "version": "2.1.3" + }, + "libf2fs.so.0": { + "package": "f2fs-tools-libs", + "version": "0.0.1" + }, + "libf2fs_format.so.0": { + "package": "f2fs-tools-libs", + "version": "0.0.1" + }, + "libebml.so.4": { + "package": "libebml", + "version": "4.0.0" + }, + "libswfdec-0.9.so.2": { + "package": "swfdec", + "version": "2.0.0" + }, + "libswfdec-gtk-0.9.so.2": { + "package": "swfdec", + "version": "2.0.0" + }, + "postfix-ldap.so": { + "package": "postfix-ldap", + "version": "0" + }, + "libcdda_interface.so.0": { + "package": "cdparanoia-libs", + "version": "0.10.2" + }, + "libcdda_paranoia.so.0": { + "package": "cdparanoia-libs", + "version": "0.10.2" + }, + "libsensors.so.4": { + "package": "lm_sensors", + "version": "4.4.0" + }, + "libpciaccess.so.0": { + "package": "libpciaccess", + "version": "0.11.1" + }, + "libgailutil.so.18": { + "package": "gtk+2.0", + "version": "18.0.1" + }, + "libgdk-x11-2.0.so.0": { + "package": "gtk+2.0", + "version": "0.2400.31" + }, + "libgtk-x11-2.0.so.0": { + "package": "gtk+2.0", + "version": "0.2400.31" + }, + "libjansson.so.4": { + "package": "jansson", + "version": "4.10.0" + }, + "libgif.so.7": { + "package": "giflib", + "version": "7.0.0" + }, + "libprocps.so.6": { + "package": "libproc", + "version": "6.0.0" + }, + "libgnokii.so.7": { + "package": "gnokii-libs", + "version": "7.0.0" + }, + "libcppunit-1.14.so.0": { + "package": "cppunit", + "version": "0.0.0" + }, + "libobrender.so.32": { + "package": "openbox-libs", + "version": "32.0.0" + }, + "libobt.so.2": { + "package": "openbox-libs", + "version": "2.0.2" + }, + "libcdio.so.16": { + "package": "libcdio", + "version": "16.0.0" + }, + "libiso9660.so.10": { + "package": "libcdio", + "version": "10.0.0" + }, + "libudf.so.0": { + "package": "libcdio", + "version": "0.0.0" + }, + "libtevent.so.0": { + "package": "tevent", + "version": "0.9.34" + }, + "libQtCore.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtDBus.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtNetwork.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtScript.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtSql.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtTest.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtXml.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtXmlPatterns.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "rlm_pam.so": { + "package": "freeradius-pam", + "version": "0" + }, + "libbsd.so.0": { + "package": "libbsd", + "version": "0.8.6" + }, + "libxcb-image.so.0": { + "package": "xcb-util-image", + "version": "0.0.0" + }, + "libgcc_s.so.1": { + "package": "libgcc", + "version": "1" + }, + "libdrm.so.2": { + "package": "libdrm", + "version": "2.4.0" + }, + "libdrm_amdgpu.so.1": { + "package": "libdrm", + "version": "1.0.0" + }, + "libdrm_freedreno.so.1": { + "package": "libdrm", + "version": "1.0.0" + }, + "libdrm_intel.so.1": { + "package": "libdrm", + "version": "1.0.0" + }, + "libdrm_nouveau.so.2": { + "package": "libdrm", + "version": "2.0.0" + }, + "libdrm_radeon.so.1": { + "package": "libdrm", + "version": "1.0.1" + }, + "libkms.so.1": { + "package": "libdrm", + "version": "1.0.0" + }, + "libopenjp2.so.7": { + "package": "openjpeg", + "version": "2.3.0" + }, + "libmandoc.so": { + "package": "mdocml", + "version": "0" + }, + "libfftw3.so.3": { + "package": "fftw-double-libs", + "version": "3.5.6" + }, + "libfftw3_threads.so.3": { + "package": "fftw-double-libs", + "version": "3.5.6" + }, + "librabbitmq.so.4": { + "package": "rabbitmq-c", + "version": "4.2.0" + }, + "liba52.so.0": { + "package": "a52dec", + "version": "0.0.0" + }, + "libboost_graph-mt.so.1.62.0": { + "package": "boost-graph", + "version": "1.62.0" + }, + "libboost_graph.so.1.62.0": { + "package": "boost-graph", + "version": "1.62.0" + }, + "libthunarx-2.so.0": { + "package": "thunar", + "version": "0.0.0" + }, + "libavcodec.so.57": { + "package": "ffmpeg-libs", + "version": "57.107.100" + }, + "libavdevice.so.57": { + "package": "ffmpeg-libs", + "version": "57.10.100" + }, + "libavfilter.so.6": { + "package": "ffmpeg-libs", + "version": "6.107.100" + }, + "libavformat.so.57": { + "package": "ffmpeg-libs", + "version": "57.83.100" + }, + "libavresample.so.3": { + "package": "ffmpeg-libs", + "version": "3.7.0" + }, + "libavutil.so.55": { + "package": "ffmpeg-libs", + "version": "55.78.100" + }, + "libpostproc.so.54": { + "package": "ffmpeg-libs", + "version": "54.7.100" + }, + "libswresample.so.2": { + "package": "ffmpeg-libs", + "version": "2.9.100" + }, + "libswscale.so.4": { + "package": "ffmpeg-libs", + "version": "4.8.100" + }, + "librsvg-2.so.2": { + "package": "librsvg", + "version": "2.40.19" + }, + "libseccomp.so.2": { + "package": "libseccomp", + "version": "2.3.2" + }, + "libcord.so.1": { + "package": "gc", + "version": "1.0.3" + }, + "libgc.so.1": { + "package": "gc", + "version": "1.0.3" + }, + "libts.so.0": { + "package": "tslib", + "version": "0.8.1" + }, + "libdovecot-ldap.so.0": { + "package": "dovecot-ldap", + "version": "0.0.0" + }, + "libSDL2_ttf-2.0.so.0": { + "package": "sdl2_ttf", + "version": "0.14.0" + }, + "libnotify.so.4": { + "package": "libnotify", + "version": "4.0.0" + }, + "libatf-c++.so.2": { + "package": "atf", + "version": "2.0.0" + }, + "libatf-c.so.1": { + "package": "atf", + "version": "1.0.0" + }, + "libreplace-samba4.so": { + "package": "libwbclient", + "version": "0" + }, + "libwbclient.so.0": { + "package": "libwbclient", + "version": "0.14" + }, + "libwinbind-client-samba4.so": { + "package": "libwbclient", + "version": "0" + }, + "libgmpxx.so.4": { + "package": "libgmpxx", + "version": "4.5.2" + }, + "libgssglue.so.1": { + "package": "libgssglue", + "version": "1.0.0" + }, + "libinput.so.10": { + "package": "libinput-libs", + "version": "10.13.0" + }, + "libruby.so.2.4": { + "package": "ruby-libs", + "version": "2.4.6" + }, + "libpcrecpp.so.0": { + "package": "libpcrecpp", + "version": "0.0.1" + }, + "libssl.so.1.0.0": { + "package": "libssl1.0", + "version": "1.0.0" + }, + "libnfsidmap.so.0": { + "package": "libnfsidmap", + "version": "0.3.0" + }, + "libapr-1.so.0": { + "package": "apr", + "version": "0.6.3" + }, + "libXcursor.so.1": { + "package": "libxcursor", + "version": "1.0.2" + }, + "libmount.so.1": { + "package": "libmount", + "version": "1.1.0" + }, + "libfontconfig.so.1": { + "package": "fontconfig", + "version": "1.10.1" + }, + "libical.so.2": { + "package": "libical", + "version": "2.0.0" + }, + "libical_cxx.so.2": { + "package": "libical", + "version": "2.0.0" + }, + "libicalss.so.2": { + "package": "libical", + "version": "2.0.0" + }, + "libicalss_cxx.so.2": { + "package": "libical", + "version": "2.0.0" + }, + "libicalvcal.so.2": { + "package": "libical", + "version": "2.0.0" + }, + "liburcu-bp.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu-cds.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu-common.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu-mb.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu-qsbr.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu-signal.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "libpangox-1.0.so.0": { + "package": "pangox-compat", + "version": "0.0.0" + }, + "libprotobuf-lite.so.14": { + "package": "protobuf", + "version": "14.0.0" + }, + "libprotobuf.so.14": { + "package": "protobuf", + "version": "14.0.0" + }, + "libprotoc.so.14": { + "package": "protobuf", + "version": "14.0.0" + }, + "libbtrfs.so.0": { + "package": "btrfs-progs-libs", + "version": "0.1" + }, + "libcairomm-1.0.so.1": { + "package": "cairomm", + "version": "1.4.0" + }, + "libnghttp2.so.14": { + "package": "nghttp2-libs", + "version": "14.18.0" + }, + "liblcms2.so.2": { + "package": "lcms2", + "version": "2.0.8" + }, + "libexif.so.12": { + "package": "libexif", + "version": "12.3.3" + }, + "libfreetdm.so.1": { + "package": "freeswitch-freetdm", + "version": "1.0.0" + }, + "grosscheck.so": { + "package": "gross", + "version": "0" + }, + "libgdk_pixbuf-2.0.so.0": { + "package": "gdk-pixbuf", + "version": "0.3610.0" + }, + "libgdk_pixbuf_xlib-2.0.so.0": { + "package": "gdk-pixbuf", + "version": "0.3610.0" + }, + "liblastfm.so.1": { + "package": "liblastfm", + "version": "1.0.9" + }, + "liblastfm_fingerprint.so.1": { + "package": "liblastfm", + "version": "1.0.9" + }, + "libudisks2.so.0": { + "package": "udisks2-libs", + "version": "0.0.0" + }, + "libsox.so.3": { + "package": "sox", + "version": "3.0.0" + }, + "libgio-2.0.so.0": { + "package": "glib", + "version": "0.5400.2" + }, + "libglib-2.0.so.0": { + "package": "glib", + "version": "0.5400.2" + }, + "libgmodule-2.0.so.0": { + "package": "glib", + "version": "0.5400.2" + }, + "libgobject-2.0.so.0": { + "package": "glib", + "version": "0.5400.2" + }, + "libgthread-2.0.so.0": { + "package": "glib", + "version": "0.5400.2" + }, + "libfreeradius-dhcp.so": { + "package": "freeradius-lib", + "version": "0" + }, + "libfreeradius-eap.so": { + "package": "freeradius-lib", + "version": "0" + }, + "libfreeradius-radius.so": { + "package": "freeradius-lib", + "version": "0" + }, + "libfreeradius-server.so": { + "package": "freeradius-lib", + "version": "0" + }, + "postfix-pgsql.so": { + "package": "postfix-pgsql", + "version": "0" + }, + "libxcb-cursor.so.0": { + "package": "xcb-util-cursor", + "version": "0.0.0" + }, + "libnetfilter_conntrack.so.3": { + "package": "libnetfilter_conntrack", + "version": "3.6.0" + }, + "libxkbcommon-x11.so.0": { + "package": "libxkbcommon", + "version": "0.0.0" + }, + "libxkbcommon.so.0": { + "package": "libxkbcommon", + "version": "0.0.0" + }, + "libgmp.so.10": { + "package": "gmp", + "version": "10.3.2" + }, + "libmp3lame.so.0": { + "package": "lame", + "version": "0.0.0" + }, + "libconfuse.so.2": { + "package": "confuse", + "version": "2.0.0" + }, + "libcrystalhd.so.3": { + "package": "libcrystalhd", + "version": "3.6" + }, + "libnetcf.so.1": { + "package": "netcf-libs", + "version": "1.4.0" + }, + "libpcre2-8.so.0": { + "package": "pcre2", + "version": "0.6.0" + }, + "libpcre2-posix.so.2": { + "package": "pcre2", + "version": "2.0.0" + }, + "libratbox.so": { + "package": "charybdis", + "version": "0" + }, + "libmysqld.so.18": { + "package": "mariadb-libs", + "version": "18" + }, + "libavahi-ui.so.0": { + "package": "avahi-ui", + "version": "0.1.4" + }, + "libvanessa_adt.so.1": { + "package": "vanessa_adt", + "version": "1.0.0" + }, + "libevent-2.1.so.6": { + "package": "libevent", + "version": "6.0.2" + }, + "libevent_core-2.1.so.6": { + "package": "libevent", + "version": "6.0.2" + }, + "libevent_extra-2.1.so.6": { + "package": "libevent", + "version": "6.0.2" + }, + "libevent_openssl-2.1.so.6": { + "package": "libevent", + "version": "6.0.2" + }, + "libevent_pthreads-2.1.so.6": { + "package": "libevent", + "version": "6.0.2" + }, + "libraw1394.so.11": { + "package": "libraw1394", + "version": "11.1.0" + }, + "libGLU.so.1": { + "package": "glu", + "version": "1.3.1" + }, + "libXft.so.2": { + "package": "libxft", + "version": "2.3.2" + }, + "libjpeg.so.8": { + "package": "libjpeg-turbo", + "version": "8.1.2" + }, + "libturbojpeg.so.0": { + "package": "libjpeg-turbo", + "version": "0.1.0" + }, + "libjasper.so.4": { + "package": "jasper-libs", + "version": "4.0.0" + }, + "libOpenIPMI.so.0": { + "package": "openipmi-libs", + "version": "0.0.5" + }, + "libOpenIPMIcmdlang.so.0": { + "package": "openipmi-libs", + "version": "0.0.5" + }, + "libOpenIPMIglib.so.0": { + "package": "openipmi-libs", + "version": "0.0.1" + }, + "libOpenIPMIposix.so.0": { + "package": "openipmi-libs", + "version": "0.0.1" + }, + "libOpenIPMIpthread.so.0": { + "package": "openipmi-libs", + "version": "0.0.1" + }, + "libOpenIPMIui.so.1": { + "package": "openipmi-libs", + "version": "1.0.1" + }, + "libOpenIPMIutils.so.0": { + "package": "openipmi-libs", + "version": "0.0.1" + }, + "libperditiondb_gdbm.so.0": { + "package": "perdition", + "version": "0.0.0" + }, + "libs6dns.so.2.2": { + "package": "s6-dns", + "version": "2.2.0.1" + }, + "libskadns.so.2.2": { + "package": "s6-dns", + "version": "2.2.0.1" + }, + "libao.so.4": { + "package": "libao", + "version": "4.1.0" + }, + "libpytalloc-util.so.2": { + "package": "py-talloc", + "version": "2.1.10" + }, + "libassuan.so.0": { + "package": "libassuan", + "version": "0.7.4" + }, + "libwx_gtk2u_adv-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_aui-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_core-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_html-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_qa-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_richtext-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_xrc-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libct.so.4": { + "package": "freetds", + "version": "4.0.0" + }, + "libsybdb.so.5": { + "package": "freetds", + "version": "5.1.0" + }, + "libtdsodbc.so.0": { + "package": "freetds", + "version": "0.0.0" + }, + "libwavpack.so.1": { + "package": "wavpack", + "version": "1.2.0" + }, + "libavahi-core.so.7": { + "package": "avahi", + "version": "7.0.2" + }, + "libgpg-error.so.0": { + "package": "libgpg-error", + "version": "0.22.0" + }, + "libmosquitto.so.1": { + "package": "mosquitto-libs", + "version": "1" + }, + "libksba.so.8": { + "package": "libksba", + "version": "8.11.6" + }, + "libportaudio.so.2": { + "package": "portaudio", + "version": "2.0.0" + }, + "libuv.so.1": { + "package": "libuv", + "version": "1.0.0" + }, + "libatspi.so.0": { + "package": "at-spi2-core", + "version": "0.0.1" + }, + "libverto.so.1": { + "package": "libverto", + "version": "1.0.0" + }, + "libXxf86vm.so.1": { + "package": "libxxf86vm", + "version": "1.0.0" + }, + "libgit2.so.25": { + "package": "libgit2", + "version": "0.25.1" + }, + "libDeployPkg.so.0": { + "package": "open-vm-tools", + "version": "0.0.0" + }, + "libguestlib.so.0": { + "package": "open-vm-tools", + "version": "0.0.0" + }, + "libhgfs.so.0": { + "package": "open-vm-tools", + "version": "0.0.0" + }, + "libvmtools.so.0": { + "package": "open-vm-tools", + "version": "0.0.0" + }, + "libsgutils2.so.2": { + "package": "sg3_utils", + "version": "2.0.0" + }, + "libqjson.so.0": { + "package": "qjson", + "version": "0.9.0" + }, + "libcmocka.so.0": { + "package": "cmocka", + "version": "0.4.1" + }, + "libsvn_swig_py-1.so.0": { + "package": "py-subversion", + "version": "0.0.0" + }, + "libpango-1.0.so.0": { + "package": "pango", + "version": "0.4000.14" + }, + "libpangocairo-1.0.so.0": { + "package": "pango", + "version": "0.4000.14" + }, + "libpangoft2-1.0.so.0": { + "package": "pango", + "version": "0.4000.14" + }, + "libpangoxft-1.0.so.0": { + "package": "pango", + "version": "0.4000.14" + }, + "libeinfo.so.1": { + "package": "openrc", + "version": "1" + }, + "librc.so.1": { + "package": "openrc", + "version": "1" + }, + "libgdl-3.so.5": { + "package": "gdl", + "version": "5.0.9" + }, + "libnetsnmpagent.so.30": { + "package": "net-snmp-agent-libs", + "version": "30.0.3" + }, + "libnetsnmphelpers.so.30": { + "package": "net-snmp-agent-libs", + "version": "30.0.3" + }, + "libnetsnmpmibs.so.30": { + "package": "net-snmp-agent-libs", + "version": "30.0.3" + }, + "libnetsnmptrapd.so.30": { + "package": "net-snmp-agent-libs", + "version": "30.0.3" + }, + "libxmlrpc.so.3": { + "package": "xmlrpc-c", + "version": "3.39" + }, + "libxmlrpc_server.so.3": { + "package": "xmlrpc-c", + "version": "3.39" + }, + "libxmlrpc_util.so.3": { + "package": "xmlrpc-c", + "version": "3.39" + }, + "libpyldb-util.so.1": { + "package": "py-ldb", + "version": "1.3.0" + }, + "libudns.so.0": { + "package": "udns", + "version": "0" + }, + "libmms.so.0": { + "package": "libmms", + "version": "0.0.2" + }, + "libexslt.so.0": { + "package": "libxslt", + "version": "0.8.19" + }, + "libxslt.so.1": { + "package": "libxslt", + "version": "1.1.31" + }, + "libosp.so.5": { + "package": "opensp", + "version": "5.0.0" + }, + "libasteriskssl.so.1": { + "package": "asterisk", + "version": "1" + }, + "libbaccats-sqlite3-9.0.5.so": { + "package": "bacula-sqlite", + "version": "0" + }, + "liblzo2.so.2": { + "package": "lzo", + "version": "2.0.0" + }, + "libtag.so.1": { + "package": "taglib", + "version": "1.17.0" + }, + "libtag_c.so.0": { + "package": "taglib", + "version": "0.0.0" + }, + "libgee-0.8.so.2": { + "package": "libgee", + "version": "2.6.0" + }, + "winbind_krb5_locator.so": { + "package": "samba-winbind-krb5-locator", + "version": "0" + }, + "libgladeui-2.so.6": { + "package": "glade", + "version": "6.3.1" + }, + "liblogging-stdlog.so.0": { + "package": "liblogging", + "version": "0.1.0" + }, + "libhunspell-1.6.so.0": { + "package": "hunspell", + "version": "0.0.1" + }, + "libogg.so.0": { + "package": "libogg", + "version": "0.8.3" + }, + "libmad.so.0": { + "package": "libmad", + "version": "0.2.1" + }, + "libIDL-2.so.0": { + "package": "libidl", + "version": "0.0.0" + }, + "libnl-3.so.200": { + "package": "libnl3", + "version": "200.23.0" + }, + "libnl-genl-3.so.200": { + "package": "libnl3", + "version": "200.23.0" + }, + "libnl-idiag-3.so.200": { + "package": "libnl3", + "version": "200.23.0" + }, + "libnl-nf-3.so.200": { + "package": "libnl3", + "version": "200.23.0" + }, + "libnl-route-3.so.200": { + "package": "libnl3", + "version": "200.23.0" + }, + "libnl-xfrm-3.so.200": { + "package": "libnl3", + "version": "200.23.0" + }, + "libsmbclient.so.0": { + "package": "libsmbclient", + "version": "0.2.3" + }, + "libXtst.so.6": { + "package": "libxtst", + "version": "6.1.0" + }, + "liblucene++-contrib.so.0": { + "package": "lucene++", + "version": "3.0.7" + }, + "liblucene++.so.0": { + "package": "lucene++", + "version": "3.0.7" + }, + "libldns.so.1": { + "package": "ldns", + "version": "1.6.17" + }, + "libortp.so.10": { + "package": "ortp", + "version": "10.0.0" + }, + "libgstadaptivedemux-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstbadallocators-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstbadaudio-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstbadbase-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstbadvideo-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstbasecamerabinsrc-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstcodecparsers-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstgl-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstinsertbin-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstmpegts-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstphotography-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgstplayer-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "libgsturidownloader-1.0.so.0": { + "package": "gst-plugins-bad", + "version": "0.1203.0" + }, + "librsync.so.2": { + "package": "librsync", + "version": "2.0.0" + }, + "libxfce4kbd-private-2.so.0": { + "package": "libxfce4ui", + "version": "0.0.0" + }, + "libxfce4ui-1.so.0": { + "package": "libxfce4ui", + "version": "0.0.0" + }, + "libcommon.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "libmc.so": { + "package": "xrdp", + "version": "0" + }, + "libpainter.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "librfxencode.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "libscp.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "libvnc.so": { + "package": "xrdp", + "version": "0" + }, + "libxrdp.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "libxrdpapi.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "libxup.so": { + "package": "xrdp", + "version": "0" + }, + "libdb-5.3.so": { + "package": "db", + "version": "0" + }, + "libMagickCore-7.Q16HDRI.so.4": { + "package": "imagemagick-libs", + "version": "4.0.0" + }, + "libMagickWand-7.Q16HDRI.so.4": { + "package": "imagemagick-libs", + "version": "4.0.0" + }, + "libtumbler-1.so.0": { + "package": "tumbler", + "version": "0.0.0" + }, + "libgdkmm-2.4.so.1": { + "package": "gtkmm", + "version": "1.1.0" + }, + "libgtkmm-2.4.so.1": { + "package": "gtkmm", + "version": "1.1.0" + }, + "cpufreqd_acpi.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_apm.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_cpu.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_exec.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_governor_parameters.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_nforce2.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_pmu.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_programs.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_tau.so": { + "package": "cpufreqd", + "version": "0" + }, + "libunwind-coredump.so.0": { + "package": "libunwind", + "version": "0.0.0" + }, + "libunwind-ptrace.so.0": { + "package": "libunwind", + "version": "0.0.0" + }, + "libunwind-setjmp.so.0": { + "package": "libunwind", + "version": "0.0.0" + }, + "libunwind-x86_64.so.8": { + "package": "libunwind", + "version": "8.0.1" + }, + "libunwind.so.8": { + "package": "libunwind", + "version": "8.0.1" + }, + "libxmlrpc_abyss.so.3": { + "package": "xmlrpc-c-abyss", + "version": "3.39" + }, + "libxmlrpc_server_abyss.so.3": { + "package": "xmlrpc-c-abyss", + "version": "3.39" + }, + "libzmq.so.5": { + "package": "libzmq", + "version": "5.1.5" + }, + "libcap-ng.so.0": { + "package": "libcap-ng", + "version": "0.0.0" + }, + "libgraphite2.so.3": { + "package": "graphite2", + "version": "3.0.1" + }, + "libhttp_parser.so.2.7.1": { + "package": "http-parser", + "version": "2.7.1" + }, + "libvpx.so.4": { + "package": "libvpx", + "version": "4.1.0" + }, + "libpri.so.1.4": { + "package": "libpri", + "version": "1.4" + }, + "libfarstream-0.2.so.5": { + "package": "farstream", + "version": "5.1.0" + }, + "libonig.so.4": { + "package": "oniguruma", + "version": "4.0.0" + }, + "libXaw.so.7": { + "package": "libxaw", + "version": "7.0.0" + }, + "libpolkit-agent-1.so.0": { + "package": "polkit", + "version": "0.0.0" + }, + "libpolkit-backend-1.so.0": { + "package": "polkit", + "version": "0.0.0" + }, + "libpolkit-gobject-1.so.0": { + "package": "polkit", + "version": "0.0.0" + }, + "libcpufreq.so.0": { + "package": "cpufrequtils", + "version": "0.0.0" + }, + "libgnutls.so.30": { + "package": "gnutls", + "version": "30.20.1" + }, + "libcairo-gobject.so.2": { + "package": "cairo-gobject", + "version": "2.11400.10" + }, + "libsqlite3.so.0": { + "package": "sqlite-libs", + "version": "0.8.6" + }, + "libmtdev.so.1": { + "package": "mtdev", + "version": "1.0.0" + }, + "libgsasl.so.7": { + "package": "libgsasl", + "version": "7.9.6" + }, + "rlm_krb5.so": { + "package": "freeradius-krb5", + "version": "0" + }, + "libusbredirhost.so.1": { + "package": "usbredir", + "version": "1.0.0" + }, + "libusbredirparser.so.1": { + "package": "usbredir", + "version": "1.0.0" + }, + "liblxc.so.1": { + "package": "lxc-libs", + "version": "1.3.0" + }, + "libunistring.so.2": { + "package": "libunistring", + "version": "2.0.0" + }, + "liblldp_clif.so.1": { + "package": "open-lldp", + "version": "1.0.0" + }, + "libtirpc.so.3": { + "package": "libtirpc", + "version": "3.0.0" + }, + "libmenu-cache.so.3": { + "package": "menu-cache", + "version": "3.0.1" + }, + "libfftw3l.so.3": { + "package": "fftw-long-double-libs", + "version": "3.5.6" + }, + "libfftw3l_threads.so.3": { + "package": "fftw-long-double-libs", + "version": "3.5.6" + }, + "libgstbase-1.0.so.0": { + "package": "gstreamer", + "version": "0.1203.0" + }, + "libgstcheck-1.0.so.0": { + "package": "gstreamer", + "version": "0.1203.0" + }, + "libgstcontroller-1.0.so.0": { + "package": "gstreamer", + "version": "0.1203.0" + }, + "libgstnet-1.0.so.0": { + "package": "gstreamer", + "version": "0.1203.0" + }, + "libgstreamer-1.0.so.0": { + "package": "gstreamer", + "version": "0.1203.0" + }, + "libconfig.so.9": { + "package": "libconfig", + "version": "9.2.0" + }, + "libreadline.so.7": { + "package": "readline", + "version": "7.0" + }, + "libquadmath.so.0": { + "package": "libquadmath", + "version": "0.0.0" + }, + "libpoppler-cpp.so.0": { + "package": "poppler", + "version": "0.3.0" + }, + "libpoppler.so.67": { + "package": "poppler", + "version": "67.0.0" + }, + "libexpat.so.1": { + "package": "expat", + "version": "1.6.10" + }, + "libcrypto.so.42": { + "package": "libressl2.6-libcrypto", + "version": "42.0.0" + }, + "libLLVM-5.0.so": { + "package": "llvm5-libs", + "version": "0" + }, + "libspeexdsp.so.1": { + "package": "speexdsp", + "version": "1.5.0" + }, + "libfribidi.so.0": { + "package": "fribidi", + "version": "0.3.6" + }, + "libxatracker.so.2": { + "package": "mesa-xatracker", + "version": "2.3.0" + }, + "libcrypto.so.1.0.0": { + "package": "libcrypto1.0", + "version": "1.0.0" + }, + "libdevmapper-event-lvm2.so.2.02": { + "package": "lvm2-libs", + "version": "2.02" + }, + "liblvm2app.so.2.2": { + "package": "lvm2-libs", + "version": "2.2" + }, + "liblvm2cmd.so.2.02": { + "package": "lvm2-libs", + "version": "2.02" + }, + "libxattr-tdb-samba4.so": { + "package": "samba-server", + "version": "0" + }, + "libpcre.so.1": { + "package": "pcre", + "version": "1.2.9" + }, + "libpcreposix.so.0": { + "package": "pcre", + "version": "0.0.5" + }, + "libboost_program_options-mt.so.1.62.0": { + "package": "boost-program_options", + "version": "1.62.0" + }, + "libboost_program_options.so.1.62.0": { + "package": "boost-program_options", + "version": "1.62.0" + }, + "libdconf.so.1": { + "package": "dconf", + "version": "1.0.0" + }, + "libestr.so.0": { + "package": "libestr", + "version": "0.0.0" + }, + "libsvn_client-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_delta-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_diff-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_fs-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_fs_base-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_fs_fs-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_fs_util-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_fs_x-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_ra-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_ra_local-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_ra_serf-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_ra_svn-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_repos-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_subr-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_wc-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libev.so.4": { + "package": "libev", + "version": "4.0.0" + }, + "autoaccept.so": { + "package": "libpurple", + "version": "0" + }, + "buddynote.so": { + "package": "libpurple", + "version": "0" + }, + "idle.so": { + "package": "libpurple", + "version": "0" + }, + "joinpart.so": { + "package": "libpurple", + "version": "0" + }, + "libgg.so": { + "package": "libpurple", + "version": "0" + }, + "libirc.so": { + "package": "libpurple", + "version": "0" + }, + "libnovell.so": { + "package": "libpurple", + "version": "0" + }, + "libpurple.so.0": { + "package": "libpurple", + "version": "0.12.0" + }, + "libsimple.so": { + "package": "libpurple", + "version": "0" + }, + "libzephyr.so": { + "package": "libpurple", + "version": "0" + }, + "log_reader.so": { + "package": "libpurple", + "version": "0" + }, + "newline.so": { + "package": "libpurple", + "version": "0" + }, + "nss-prefs.so": { + "package": "libpurple", + "version": "0" + }, + "offlinemsg.so": { + "package": "libpurple", + "version": "0" + }, + "psychic.so": { + "package": "libpurple", + "version": "0" + }, + "ssl-nss.so": { + "package": "libpurple", + "version": "0" + }, + "ssl.so": { + "package": "libpurple", + "version": "0" + }, + "statenotify.so": { + "package": "libpurple", + "version": "0" + }, + "libatkmm-1.6.so.1": { + "package": "atkmm", + "version": "1.1.0" + }, + "libdbus-glib-1.so.2": { + "package": "dbus-glib", + "version": "2.3.3" + }, + "libatk-1.0.so.0": { + "package": "atk", + "version": "0.22610.1" + }, + "lib++dfb-1.7.so.7": { + "package": "directfb", + "version": "7.0.0" + }, + "libdirect-1.7.so.7": { + "package": "directfb", + "version": "7.0.0" + }, + "libdirectfb-1.7.so.7": { + "package": "directfb", + "version": "7.0.0" + }, + "libfusion-1.7.so.7": { + "package": "directfb", + "version": "7.0.0" + }, + "libechonest.so.2.3": { + "package": "libechonest", + "version": "2.3.1" + }, + "libpoppler-qt4.so.4": { + "package": "poppler-qt4", + "version": "4.11.0" + }, + "libboost_regex-mt.so.1.62.0": { + "package": "boost-regex", + "version": "1.62.0" + }, + "libboost_regex.so.1.62.0": { + "package": "boost-regex", + "version": "1.62.0" + }, + "libsamba-net-samba4.so": { + "package": "samba-libs-py", + "version": "0" + }, + "libsamba-python-samba4.so": { + "package": "samba-libs-py", + "version": "0" + }, + "libsoxr-lsr.so.0": { + "package": "soxr", + "version": "0.1.9" + }, + "libsoxr.so.0": { + "package": "soxr", + "version": "0.1.1" + }, + "libgvfscommon.so": { + "package": "gvfs", + "version": "0" + }, + "libgvfsdaemon.so": { + "package": "gvfs", + "version": "0" + }, + "libgtksourceview-2.0.so.0": { + "package": "gtksourceview2", + "version": "0.0.0" + }, + "libpixman-1.so.0": { + "package": "pixman", + "version": "0.34.0" + }, + "libgdbm.so.4": { + "package": "gdbm", + "version": "4.0.0" + }, + "libgdbm_compat.so.4": { + "package": "gdbm", + "version": "4.0.0" + }, + "libgpgme.so.11": { + "package": "gpgme", + "version": "11.18.0" + }, + "libevtlog.so.0": { + "package": "eventlog", + "version": "0.0.0" + }, + "libhiredis.so.0.13": { + "package": "hiredis", + "version": "0.13" + }, + "libjq.so.1": { + "package": "jq", + "version": "1.0.4" + }, + "libparted-fs-resize.so.0": { + "package": "parted", + "version": "0.0.1" + }, + "libparted.so.2": { + "package": "parted", + "version": "2.0.1" + }, + "libinotifytools.so.0": { + "package": "inotify-tools", + "version": "0.4.1" + }, + "libjbig2dec.so.0": { + "package": "jbig2dec", + "version": "0.0.0" + }, + "libtonezone.so.2": { + "package": "dahdi-tools", + "version": "2.0.0" + }, + "libmspack.so.0": { + "package": "libmspack", + "version": "0.1.0" + }, + "libmpc.so.3": { + "package": "mpc1", + "version": "3.0.0" + }, + "libfaad.so.2": { + "package": "faad2", + "version": "2.0.0" + }, + "librarian.so.0": { + "package": "rarian", + "version": "0.0.0" + }, + "libavfs.so.0": { + "package": "avfs", + "version": "0.0.2" + }, + "libsasl2.so.3": { + "package": "libsasl", + "version": "3.0.0" + }, + "libintl.so.8": { + "package": "libintl", + "version": "8.1.5" + }, + "lib01_acl_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib02_imap_acl_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib02_lazy_expunge_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib05_mail_crypt_acl_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib05_pop3_migration_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib05_snarf_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib10_last_login_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib10_mail_crypt_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib10_mail_filter_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib10_quota_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib11_imap_quota_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib11_trash_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib15_notify_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_autocreate_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_charset_alias_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_expire_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_fts_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_listescape_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_mail_log_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_mailbox_alias_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_notify_status_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_push_notification_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_quota_clone_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_replication_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_var_expand_crypt.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_virtual_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_zlib_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib21_fts_squat_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib30_imap_zlib_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib90_stats_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib95_imap_stats_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib99_welcome_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "libdcrypt_openssl.so": { + "package": "dovecot", + "version": "0" + }, + "libdovecot-compression.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot-dsync.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot-fts.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot-lda.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot-login.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot-storage.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libfs_compress.so": { + "package": "dovecot", + "version": "0" + }, + "libfs_crypt.so": { + "package": "dovecot", + "version": "0" + }, + "libfs_mail_crypt.so": { + "package": "dovecot", + "version": "0" + }, + "libssl_iostream_openssl.so": { + "package": "dovecot", + "version": "0" + }, + "libstats_auth.so": { + "package": "dovecot", + "version": "0" + }, + "libstats_mail.so": { + "package": "dovecot", + "version": "0" + }, + "cat.c32": { + "package": "syslinux", + "version": "0" + }, + "chain.c32": { + "package": "syslinux", + "version": "0" + }, + "cmd.c32": { + "package": "syslinux", + "version": "0" + }, + "cmenu.c32": { + "package": "syslinux", + "version": "0" + }, + "config.c32": { + "package": "syslinux", + "version": "0" + }, + "cptime.c32": { + "package": "syslinux", + "version": "0" + }, + "cpu.c32": { + "package": "syslinux", + "version": "0" + }, + "cpuid.c32": { + "package": "syslinux", + "version": "0" + }, + "cpuidtest.c32": { + "package": "syslinux", + "version": "0" + }, + "debug.c32": { + "package": "syslinux", + "version": "0" + }, + "dhcp.c32": { + "package": "syslinux", + "version": "0" + }, + "dir.c32": { + "package": "syslinux", + "version": "0" + }, + "disk.c32": { + "package": "syslinux", + "version": "0" + }, + "dmi.c32": { + "package": "syslinux", + "version": "0" + }, + "dmitest.c32": { + "package": "syslinux", + "version": "0" + }, + "elf.c32": { + "package": "syslinux", + "version": "0" + }, + "ethersel.c32": { + "package": "syslinux", + "version": "0" + }, + "gfxboot.c32": { + "package": "syslinux", + "version": "0" + }, + "gpxecmd.c32": { + "package": "syslinux", + "version": "0" + }, + "hdt.c32": { + "package": "syslinux", + "version": "0" + }, + "hexdump.c32": { + "package": "syslinux", + "version": "0" + }, + "host.c32": { + "package": "syslinux", + "version": "0" + }, + "ifcpu.c32": { + "package": "syslinux", + "version": "0" + }, + "ifcpu64.c32": { + "package": "syslinux", + "version": "0" + }, + "ifmemdsk.c32": { + "package": "syslinux", + "version": "0" + }, + "ifplop.c32": { + "package": "syslinux", + "version": "0" + }, + "kbdmap.c32": { + "package": "syslinux", + "version": "0" + }, + "kontron_wdt.c32": { + "package": "syslinux", + "version": "0" + }, + "ldlinux.c32": { + "package": "syslinux", + "version": "0" + }, + "lfs.c32": { + "package": "syslinux", + "version": "0" + }, + "libcom32.c32": { + "package": "syslinux", + "version": "0" + }, + "libgpl.c32": { + "package": "syslinux", + "version": "0" + }, + "liblua.c32": { + "package": "syslinux", + "version": "0" + }, + "libmenu.c32": { + "package": "syslinux", + "version": "0" + }, + "libutil.c32": { + "package": "syslinux", + "version": "0" + }, + "linux.c32": { + "package": "syslinux", + "version": "0" + }, + "ls.c32": { + "package": "syslinux", + "version": "0" + }, + "lua.c32": { + "package": "syslinux", + "version": "0" + }, + "mboot.c32": { + "package": "syslinux", + "version": "0" + }, + "meminfo.c32": { + "package": "syslinux", + "version": "0" + }, + "menu.c32": { + "package": "syslinux", + "version": "0" + }, + "pci.c32": { + "package": "syslinux", + "version": "0" + }, + "pcitest.c32": { + "package": "syslinux", + "version": "0" + }, + "pmload.c32": { + "package": "syslinux", + "version": "0" + }, + "poweroff.c32": { + "package": "syslinux", + "version": "0" + }, + "prdhcp.c32": { + "package": "syslinux", + "version": "0" + }, + "pwd.c32": { + "package": "syslinux", + "version": "0" + }, + "pxechn.c32": { + "package": "syslinux", + "version": "0" + }, + "reboot.c32": { + "package": "syslinux", + "version": "0" + }, + "rosh.c32": { + "package": "syslinux", + "version": "0" + }, + "sanboot.c32": { + "package": "syslinux", + "version": "0" + }, + "sdi.c32": { + "package": "syslinux", + "version": "0" + }, + "sysdump.c32": { + "package": "syslinux", + "version": "0" + }, + "syslinux.c32": { + "package": "syslinux", + "version": "0" + }, + "vesa.c32": { + "package": "syslinux", + "version": "0" + }, + "vesainfo.c32": { + "package": "syslinux", + "version": "0" + }, + "vesamenu.c32": { + "package": "syslinux", + "version": "0" + }, + "vpdtest.c32": { + "package": "syslinux", + "version": "0" + }, + "whichsys.c32": { + "package": "syslinux", + "version": "0" + }, + "zzjson.c32": { + "package": "syslinux", + "version": "0" + }, + "libaspell.so.15": { + "package": "aspell-libs", + "version": "15.1.5" + }, + "libpspell.so.15": { + "package": "aspell-libs", + "version": "15.1.5" + }, + "libid3tag.so.0": { + "package": "libid3tag", + "version": "0.3.0" + }, + "libdns.so.1106": { + "package": "bind-libs", + "version": "1106.0.1" + }, + "libfaad_drm.so.2": { + "package": "faad2", + "version": "2.0.0" + } + }, + "package": { + "python-tests": { + "package": "python2-tests", + "version": "2.7.15-r2" + }, + "python-dev": { + "package": "python2-dev", + "version": "2.7.15-r2" + }, + "mysql-dev": { + "package": "mariadb-dev", + "version": "10.1.41-r0" + }, + "py-singledispatch": { + "package": "py2-singledispatch", + "version": 0 + }, + "python-gdbm": { + "package": "py-gdbm", + "version": "2.7.15-r2" + }, + "freeradius3-unixodbc": { + "package": "freeradius-unixodbc", + "version": "3.0.15-r5" + }, + "quagga-nhrp": { + "package": "quagga", + "version": "1.2.4" + }, + "freeradius3-eap": { + "package": "freeradius-eap", + "version": "3.0.15-r5" + }, + "ncurses-widec-libs": { + "package": "ncurses-libs", + "version": "6.0_p20171125-r1" + }, + "perl-yaml-xs": { + "package": "perl-yaml-libyaml", + "version": "0.65-r2" + }, + "freeradius3-mysql": { + "package": "freeradius-mysql", + "version": "3.0.15-r5" + }, + "nodejs-lts": { + "package": "nodejs", + "version": "8.9.3" + }, + "nodejs-current-npm": { + "package": "nodejs-npm", + "version": 0 + }, + "freeradius3-radclient": { + "package": "freeradius-radclient", + "version": "3.0.15-r5" + }, + "postgresql": { + "package": "postgresql-bdr", + "version": 0 + }, + "python": { + "package": "python2", + "version": "2.7.15-r2" + }, + "udev": { + "package": "eudev", + "version": "176" + }, + "perl-encode-piconv": { + "package": "perl-encode-utils", + "version": 0 + }, + "perl-test-tester": { + "package": "perl-test-simple", + "version": 0 + }, + "perl-net-ldap": { + "package": "perl-ldap", + "version": 0 + }, + "collectd-libvirt": { + "package": "collectd-virt", + "version": 0 + }, + "freeradius3-perl": { + "package": "freeradius-perl", + "version": "3.0.15-r5" + }, + "linux-virtgrsec": { + "package": "linux-virthardened", + "version": "4.9.65-r1" + }, + "py-backports.ssl_match_hostname": { + "package": "py2-backports.ssl_match_hostname", + "version": 0 + }, + "perl-mail-tools": { + "package": "perl-mailtools", + "version": "2.19" + }, + "/bin/sh": { + "package": "busybox", + "version": 0 + }, + "llvm-static": { + "package": "llvm5-static", + "version": "5.0.0-r0" + }, + "freeradius3": { + "package": "freeradius", + "version": "3.0.15-r5" + }, + "freeradius3-postgresql": { + "package": "freeradius-postgresql", + "version": "3.0.15-r5" + }, + "llvm-test-utils": { + "package": "llvm5-test-utils", + "version": "5.0.0-r0" + }, + "lit": { + "package": "llvm5-test-utils", + "version": "0.6.0-r0" + }, + "freeradius3-mssql": { + "package": "freeradius-mssql", + "version": "3.0.15-r5" + }, + "freeradius3-sqlite": { + "package": "freeradius-sqlite", + "version": "3.0.15-r5" + }, + "freeradius3-ldap": { + "package": "freeradius-ldap", + "version": "3.0.15-r5" + }, + "freeradius3-python": { + "package": "freeradius-python", + "version": "3.0.15-r5" + }, + "freeradius3-sql": { + "package": "freeradius-sql", + "version": "3.0.15-r5" + }, + "py3-pip": { + "package": "python3", + "version": 0 + }, + "llvm": { + "package": "llvm5", + "version": "5.0.0-r0" + }, + "llvm-dev": { + "package": "llvm5-dev", + "version": "5.0.0-r0" + }, + "consolekit": { + "package": "consolekit2", + "version": "1.2.0" + }, + "gnupg-doc": { + "package": "gnupg1-doc", + "version": "1.4.23-r0" + }, + "pkgconfig": { + "package": "pkgconf", + "version": "1" + }, + "linux-grsec": { + "package": "linux-hardened", + "version": "4.9.65-r1" + }, + "xtables-addons-grsec": { + "package": "xtables-addons-hardened", + "version": "4.9.65-r1" + }, + "freeradius3-pam": { + "package": "freeradius-pam", + "version": "3.0.15-r5" + }, + "zfs-grsec": { + "package": "zfs-hardened", + "version": "4.9.65-r1" + }, + "dahdi-linux-grsec": { + "package": "dahdi-linux-hardened", + "version": "4.9.65-r1" + }, + "devicemaster-linux-grsec": { + "package": "devicemaster-linux-hardened", + "version": "4.9.65-r1" + }, + "nginx-lua": { + "package": "nginx-mod-http-lua", + "version": 0 + }, + "gnupg": { + "package": "gnupg1", + "version": "1.4.23-r0" + }, + "drbd": { + "package": "drbd-utils", + "version": 0 + }, + "nginx-rtmp": { + "package": "nginx-mod-rtmp", + "version": 0 + }, + "py-pip": { + "package": "py2-pip", + "version": "9.0.1-r1" + }, + "libgit2-libs": { + "package": "libgit2", + "version": 0 + }, + "wayland-protocols-dev": { + "package": "wayland-protocols", + "version": 0 + }, + "py2-setuptools": { + "package": "py-setuptools", + "version": "33.1.1-r1" + }, + "nodejs-lts-dev": { + "package": "nodejs-dev", + "version": "8.9.3" + }, + "nagios-plugins-clamdnagios-plugins-ftpnagios-plugins-imapnagios-plugins-jabbernagios-plugins-nntpnagios-plugins-nntpsnagios-plugins-popnagios-plugins-simapnagios-plugins-spopnagios-plugins-ssmtpnagios-plugins-udp": { + "package": "nagios-plugins-tcp", + "version": 0 + }, + "freeradius3-krb5": { + "package": "freeradius-krb5", + "version": "3.0.15-r5" + }, + "spl-grsec": { + "package": "spl-hardened", + "version": "4.9.65-r1" + }, + "llvm-libs": { + "package": "llvm5-libs", + "version": "5.0.0-r0" + }, + "drbd9-grsec": { + "package": "drbd9-hardened", + "version": "4.9.65-r1" + }, + "perl-time-date": { + "package": "perl-timedate", + "version": "2.30" + }, + "bkeymaps": { + "package": "kbd-bkeymaps", + "version": 0 + } + } + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/imgconf/apk/testdata/history_v3.9.json b/pkg/fanal/analyzer/imgconf/apk/testdata/history_v3.9.json new file mode 100644 index 000000000000..39d9ec0b363f --- /dev/null +++ b/pkg/fanal/analyzer/imgconf/apk/testdata/history_v3.9.json @@ -0,0 +1,75866 @@ +{ + "package": { + "perl-cgi-emulate-psgi": { + "versions": { + "0.23-r0": 1545229757 + }, + "origin": "perl-cgi-emulate-psgi", + "dependencies": [ + "perl-http-message", + "perl-cgi" + ] + }, + "samba-common": { + "versions": { + "4.8.11-r1": 1555334972 + }, + "origin": "samba" + }, + "shared-mime-info": { + "versions": { + "1.10-r0": 1542821941 + }, + "origin": "shared-mime-info", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:update-mime-database" + ] + }, + "iputils": { + "versions": { + "20180629-r1": 1545209661 + }, + "origin": "iputils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.1.1" + ], + "provides": [ + "cmd:arping", + "cmd:clockdiff", + "cmd:ipg", + "cmd:ninfod", + "cmd:ping", + "cmd:ping6", + "cmd:rarpd", + "cmd:rdisc", + "cmd:tftpd", + "cmd:tracepath", + "cmd:tracepath6", + "cmd:traceroute6" + ] + }, + "py3-backports_abc": { + "versions": { + "0.5-r0": 1545293194 + }, + "origin": "py-backports_abc", + "dependencies": [ + "python3" + ] + }, + "eggdbus-dev": { + "versions": { + "0.6-r5": 1543935571 + }, + "origin": "eggdbus", + "dependencies": [ + "eggdbus=0.6-r5", + "pc:gio-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:eggdbus-1=0.6" + ] + }, + "qemu-audio-sdl": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "so:libSDL2-2.0.so.0", + "so:libc.musl-x86_64.so.1" + ] + }, + "freeglut": { + "versions": { + "3.0.0-r0": 1545291114 + }, + "origin": "freeglut", + "dependencies": [ + "so:libGL.so.1", + "so:libX11.so.6", + "so:libXi.so.6", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libglut.so.3=3.10.0" + ] + }, + "rabbitmq-c": { + "versions": { + "0.8.0-r5": 1543923901 + }, + "origin": "rabbitmq-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "so:librabbitmq.so.4=4.2.0" + ] + }, + "font-bh-100dpi": { + "versions": { + "1.0.3-r0": 1545229751 + }, + "origin": "font-bh-100dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "udisks": { + "versions": { + "1.0.5-r3": 1545069135 + }, + "origin": "udisks", + "dependencies": [ + "so:libatasmart.so.4", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libdevmapper.so.1.02", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libparted.so.2", + "so:libpolkit-gobject-1.so.0", + "so:libsgutils2.so.2", + "so:libudev.so.1" + ], + "provides": [ + "cmd:udisks", + "cmd:udisks-tcp-bridge", + "cmd:umount.udisks" + ] + }, + "git-fast-import": { + "versions": { + "2.20.1-r0": 1545214194 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0", + "so:libc.musl-x86_64.so.1", + "so:libpcre2-8.so.0", + "so:libz.so.1" + ] + }, + "perl-parse-recdescent-doc": { + "versions": { + "1.967015-r1": 1545067081 + }, + "origin": "perl-parse-recdescent" + }, + "py-cliapp": { + "versions": { + "1.20170823-r0": 1544798597 + }, + "origin": "py-cliapp", + "dependencies": [ + "python2", + "python3" + ] + }, + "perl-encode": { + "versions": { + "2.98-r0": 1543935165 + }, + "origin": "perl-encode", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "cyrus-sasl-gs2": { + "versions": { + "2.1.27-r1": 1550353527 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgssapi_krb5.so.2" + ] + }, + "lua5.2-posix": { + "versions": { + "33.4.0-r1": 1546010825 + }, + "origin": "lua-posix", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "dconf-dev": { + "versions": { + "0.26.0-r1": 1545302104 + }, + "origin": "dconf", + "dependencies": [ + "dconf=0.26.0-r1", + "pc:gio-2.0>=2.25.7", + "pkgconfig" + ], + "provides": [ + "pc:dconf=0.26.0" + ] + }, + "gtksourceview2": { + "versions": { + "2.10.5-r0": 1545292667 + }, + "origin": "gtksourceview2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgtksourceview-2.0.so.0=0.0.0" + ] + }, + "a52dec": { + "versions": { + "0.7.4-r7": 1545214243 + }, + "origin": "a52dec", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liba52.so.0=0.0.0", + "cmd:a52dec", + "cmd:extract_a52" + ] + }, + "xen-dev": { + "versions": { + "4.11.1-r1": 1545075896 + }, + "origin": "xen", + "dependencies": [ + "openssl-dev", + "python2-dev", + "e2fsprogs-dev", + "gettext", + "zlib-dev", + "ncurses-dev", + "dev86", + "texinfo", + "perl", + "pciutils-dev", + "glib-dev", + "yajl-dev", + "libnl3-dev", + "spice-dev", + "gnutls-dev", + "curl-dev", + "libaio-dev", + "lzo-dev", + "xz-dev", + "util-linux-dev", + "e2fsprogs-dev", + "linux-headers", + "argp-standalone", + "perl-dev", + "flex", + "bison", + "xen-libs=4.11.1-r1" + ] + }, + "linux-firmware-korg": { + "versions": { + "20190322-r0": 1554980651 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "rrdtool": { + "versions": { + "1.7.0-r0": 1542924810 + }, + "origin": "rrdtool", + "dependencies": [ + "font-sony-misc", + "so:libc.musl-x86_64.so.1", + "so:librrd.so.8" + ], + "provides": [ + "cmd:rrdtool" + ] + }, + "gnats": { + "versions": { + "4.2.0-r5": 1545209105 + }, + "origin": "gnats", + "dependencies": [ + "postfix", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:edit-pr", + "cmd:getclose", + "cmd:install-sid", + "cmd:query-pr", + "cmd:send-pr" + ] + }, + "qemu-tilegx": { + "versions": { + "3.1.0-r3": 1551107306 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-tilegx" + ] + }, + "syslinux": { + "versions": { + "6.04_pre1-r2": 1543935716 + }, + "origin": "syslinux", + "dependencies": [ + "mtools", + "blkid", + "mkinitfs", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:cat.c32=0", + "so:chain.c32=0", + "so:cmd.c32=0", + "so:cmenu.c32=0", + "so:config.c32=0", + "so:cptime.c32=0", + "so:cpu.c32=0", + "so:cpuid.c32=0", + "so:cpuidtest.c32=0", + "so:debug.c32=0", + "so:dhcp.c32=0", + "so:dir.c32=0", + "so:disk.c32=0", + "so:dmi.c32=0", + "so:dmitest.c32=0", + "so:elf.c32=0", + "so:ethersel.c32=0", + "so:gfxboot.c32=0", + "so:gpxecmd.c32=0", + "so:hdt.c32=0", + "so:hexdump.c32=0", + "so:host.c32=0", + "so:ifcpu.c32=0", + "so:ifcpu64.c32=0", + "so:ifmemdsk.c32=0", + "so:ifplop.c32=0", + "so:kbdmap.c32=0", + "so:kontron_wdt.c32=0", + "so:ldlinux.c32=0", + "so:lfs.c32=0", + "so:libcom32.c32=0", + "so:libgpl.c32=0", + "so:liblua.c32=0", + "so:libmenu.c32=0", + "so:libutil.c32=0", + "so:linux.c32=0", + "so:ls.c32=0", + "so:lua.c32=0", + "so:mboot.c32=0", + "so:meminfo.c32=0", + "so:menu.c32=0", + "so:pci.c32=0", + "so:pcitest.c32=0", + "so:pmload.c32=0", + "so:poweroff.c32=0", + "so:prdhcp.c32=0", + "so:pwd.c32=0", + "so:pxechn.c32=0", + "so:reboot.c32=0", + "so:rosh.c32=0", + "so:sanboot.c32=0", + "so:sdi.c32=0", + "so:sysdump.c32=0", + "so:syslinux.c32=0", + "so:vesa.c32=0", + "so:vesainfo.c32=0", + "so:vesamenu.c32=0", + "so:vpdtest.c32=0", + "so:whichsys.c32=0", + "so:zzjson.c32=0", + "cmd:extlinux", + "cmd:gethostip", + "cmd:isohybrid", + "cmd:isohybrid.pl", + "cmd:keytab-lilo", + "cmd:lss16toppm", + "cmd:md5pass", + "cmd:memdiskfind", + "cmd:mkdiskimage", + "cmd:ppmtolss16", + "cmd:pxelinux-options", + "cmd:sha1pass", + "cmd:syslinux", + "cmd:syslinux2ansi", + "cmd:update-extlinux" + ] + }, + "parallel": { + "versions": { + "20180622-r0": 1545154588 + }, + "origin": "parallel", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:env_parallel", + "cmd:env_parallel.ash", + "cmd:env_parallel.bash", + "cmd:env_parallel.csh", + "cmd:env_parallel.dash", + "cmd:env_parallel.fish", + "cmd:env_parallel.ksh", + "cmd:env_parallel.pdksh", + "cmd:env_parallel.sh", + "cmd:env_parallel.tcsh", + "cmd:env_parallel.zsh", + "cmd:niceload", + "cmd:parallel", + "cmd:parcat", + "cmd:parset", + "cmd:sem", + "cmd:sql" + ] + }, + "keybinder-doc": { + "versions": { + "0.3.0-r1": 1545076730 + }, + "origin": "keybinder" + }, + "syslog-ng-examples": { + "versions": { + "3.19.1-r0": 1548543150 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libevtlog-3.19.so.0", + "so:libglib-2.0.so.0", + "so:libivykis.so.0", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "py-more-itertools": { + "versions": { + "4.1.0-r0": 1542824887 + }, + "origin": "py-more-itertools" + }, + "libidn-dev": { + "versions": { + "1.35-r0": 1542924920 + }, + "origin": "libidn", + "dependencies": [ + "libidn=1.35-r0", + "pkgconfig" + ], + "provides": [ + "pc:libidn=1.35" + ] + }, + "glade3-dev": { + "versions": { + "3.8.5-r5": 1543998724 + }, + "origin": "glade3", + "dependencies": [ + "glade3=3.8.5-r5", + "pc:gtk+-2.0>=2.14.0", + "pc:libxml-2.0>=2.4.0", + "pkgconfig" + ], + "provides": [ + "pc:gladeui-1.0=3.8.5" + ] + }, + "haveged-doc": { + "versions": { + "1.9.4-r4": 1557129010 + }, + "origin": "haveged" + }, + "atk-dev": { + "versions": { + "2.30.0-r0": 1542823743 + }, + "origin": "atk", + "dependencies": [ + "atk=2.30.0-r0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:atk=2.30.0" + ] + }, + "perl-test-fatal": { + "versions": { + "0.014-r1": 1542821869 + }, + "origin": "perl-test-fatal", + "dependencies": [ + "perl-try-tiny" + ] + }, + "lua5.1-curl": { + "versions": { + "0.3.8-r1": 1545224117 + }, + "origin": "lua-curl", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "perl-class-container-doc": { + "versions": { + "0.13-r0": 1543254089 + }, + "origin": "perl-class-container" + }, + "fribidi": { + "versions": { + "1.0.5-r0": 1542824048 + }, + "origin": "fribidi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfribidi.so.0=0.4.0", + "cmd:fribidi" + ] + }, + "paxctl-doc": { + "versions": { + "0.9-r0": 1545223087 + }, + "origin": "paxctl" + }, + "nfdump-doc": { + "versions": { + "1.6.17-r0": 1545223022 + }, + "origin": "nfdump" + }, + "py3-packaging": { + "versions": { + "17.1-r0": 1542825057 + }, + "origin": "py-packaging", + "dependencies": [ + "py3-parsing", + "py3-six", + "python3" + ] + }, + "libxt-dev": { + "versions": { + "1.1.5-r2": 1542883332 + }, + "origin": "libxt", + "dependencies": [ + "libsm-dev", + "libxt=1.1.5-r2", + "pc:ice", + "pc:sm", + "pc:x11", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xt=1.1.5" + ] + }, + "syslog-ng-graphite": { + "versions": { + "3.19.1-r0": 1548543150 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "py3-cryptography": { + "versions": { + "2.4.2-r2": 1545067164 + }, + "origin": "py-cryptography", + "dependencies": [ + "py3-cffi", + "py3-idna", + "py3-asn1crypto", + "py3-six", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpython3.6m.so.1.0", + "so:libssl.so.1.1" + ] + }, + "py-sphinx": { + "versions": { + "1.8.3-r0": 1548111932 + }, + "origin": "py-sphinx", + "dependencies": [ + "py3-sphinx", + "py3-sphinx=1.8.3-r0" + ] + }, + "strace-doc": { + "versions": { + "4.24-r0": 1543928279 + }, + "origin": "strace" + }, + "qemu-system-riscv32": { + "versions": { + "3.1.0-r3": 1551107305 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-riscv32" + ] + }, + "bluez-libs": { + "versions": { + "5.50-r0": 1545822205 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libbluetooth.so.3=3.18.16" + ] + }, + "subversion-doc": { + "versions": { + "1.11.1-r0": 1548692375 + }, + "origin": "subversion" + }, + "libxdamage": { + "versions": { + "1.1.4-r2": 1542823760 + }, + "origin": "libxdamage", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXdamage.so.1=1.1.0" + ] + }, + "xf86-video-vmware-doc": { + "versions": { + "13.3.0-r0": 1545076716 + }, + "origin": "xf86-video-vmware" + }, + "xf86-video-glint-doc": { + "versions": { + "1.2.9-r3": 1545300613 + }, + "origin": "xf86-video-glint" + }, + "ipvsadm-doc": { + "versions": { + "1.29-r0": 1545118132 + }, + "origin": "ipvsadm" + }, + "jansson-dev": { + "versions": { + "2.11-r0": 1542893194 + }, + "origin": "jansson", + "dependencies": [ + "jansson=2.11-r0", + "pkgconfig" + ], + "provides": [ + "pc:jansson=2.11" + ] + }, + "obex-data-server-doc": { + "versions": { + "0.4.6-r4": 1545069103 + }, + "origin": "obex-data-server" + }, + "libgmpxx": { + "versions": { + "6.1.2-r1": 1542301441 + }, + "origin": "gmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgmp.so.10", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgmpxx.so.4=4.5.2" + ] + }, + "util-linux": { + "versions": { + "2.33-r0": 1545307438 + }, + "origin": "util-linux", + "dependencies": [ + "findmnt", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfdisk.so.1", + "so:libmount.so.1", + "so:libncursesw.so.6", + "so:libsmartcols.so.1", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:addpart", + "cmd:agetty", + "cmd:blkdiscard", + "cmd:blkzone", + "cmd:blockdev", + "cmd:cal", + "cmd:chcpu", + "cmd:chmem", + "cmd:choom", + "cmd:chrt", + "cmd:col", + "cmd:colcrt", + "cmd:colrm", + "cmd:column", + "cmd:ctrlaltdel", + "cmd:delpart", + "cmd:dmesg", + "cmd:eject", + "cmd:fallocate", + "cmd:fdformat", + "cmd:fdisk", + "cmd:fincore", + "cmd:findfs", + "cmd:flock", + "cmd:fsck", + "cmd:fsck.cramfs", + "cmd:fsck.minix", + "cmd:fsfreeze", + "cmd:fstrim", + "cmd:getopt", + "cmd:hexdump", + "cmd:hwclock", + "cmd:i386", + "cmd:ionice", + "cmd:ipcmk", + "cmd:ipcrm", + "cmd:ipcs", + "cmd:isosize", + "cmd:ldattach", + "cmd:linux32", + "cmd:linux64", + "cmd:logger", + "cmd:look", + "cmd:losetup", + "cmd:lsblk", + "cmd:lscpu", + "cmd:lsipc", + "cmd:lslocks", + "cmd:lslogins", + "cmd:lsmem", + "cmd:lsns", + "cmd:mesg", + "cmd:mkfs", + "cmd:mkfs.bfs", + "cmd:mkfs.cramfs", + "cmd:mkfs.minix", + "cmd:mkswap", + "cmd:more", + "cmd:mount", + "cmd:mountpoint", + "cmd:namei", + "cmd:nologin", + "cmd:nsenter", + "cmd:partx", + "cmd:pivot_root", + "cmd:prlimit", + "cmd:raw", + "cmd:readprofile", + "cmd:rename", + "cmd:renice", + "cmd:resizepart", + "cmd:rev", + "cmd:rfkill", + "cmd:rtcwake", + "cmd:script", + "cmd:scriptreplay", + "cmd:setarch", + "cmd:setsid", + "cmd:setterm", + "cmd:swaplabel", + "cmd:swapoff", + "cmd:swapon", + "cmd:switch_root", + "cmd:taskset", + "cmd:ul", + "cmd:umount", + "cmd:uname26", + "cmd:unshare", + "cmd:utmpdump", + "cmd:uuidgen", + "cmd:uuidparse", + "cmd:wall", + "cmd:wdctl", + "cmd:whereis", + "cmd:wipefs", + "cmd:x86_64", + "cmd:zramctl" + ] + }, + "zfs-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308087 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "gzip-doc": { + "versions": { + "1.10-r0": 1546345202 + }, + "origin": "gzip" + }, + "gptfdisk-doc": { + "versions": { + "1.0.4-r0": 1543926505 + }, + "origin": "gptfdisk" + }, + "libpciaccess-doc": { + "versions": { + "0.14-r0": 1545838880 + }, + "origin": "libpciaccess" + }, + "perl-gdtextutil-doc": { + "versions": { + "0.86-r0": 1543248468 + }, + "origin": "perl-gdtextutil" + }, + "ngrep": { + "versions": { + "1.45-r4": 1543242183 + }, + "origin": "ngrep", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:ngrep" + ] + }, + "charybdis-doc": { + "versions": { + "3.5.6-r1": 1542883316 + }, + "origin": "charybdis" + }, + "b43-fwcutter": { + "versions": { + "019-r0": 1545163908 + }, + "origin": "b43-fwcutter", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:b43-fwcutter" + ] + }, + "gnupg-doc": { + "versions": { + "2.2.12-r0": 1545746222 + }, + "origin": "gnupg" + }, + "gtk+2.0": { + "versions": { + "2.24.32-r1": 1543925514 + }, + "origin": "gtk+2.0", + "dependencies": [ + "shared-mime-info", + "gtk-update-icon-cache", + "/bin/sh", + "so:libX11.so.6", + "so:libXcomposite.so.1", + "so:libXcursor.so.1", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXi.so.6", + "so:libXrandr.so.2", + "so:libXrender.so.1", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcups.so.2", + "so:libfontconfig.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpangoft2-1.0.so.0" + ], + "provides": [ + "so:libgailutil.so.18=18.0.1", + "so:libgdk-x11-2.0.so.0=0.2400.32", + "so:libgtk-x11-2.0.so.0=0.2400.32", + "cmd:gtk-query-immodules-2.0" + ] + }, + "libxml2-dev": { + "versions": { + "2.9.9-r1": 1551105514 + }, + "origin": "libxml2", + "dependencies": [ + "zlib-dev", + "libxml2=2.9.9-r1", + "pkgconfig" + ], + "provides": [ + "pc:libxml-2.0=2.9.9", + "cmd:xml2-config" + ] + }, + "musl-dev": { + "versions": { + "1.1.20-r4": 1552989415 + }, + "origin": "musl", + "dependencies": [ + "musl=1.1.20-r4" + ] + }, + "pptpd": { + "versions": { + "1.4.0-r1": 1544000994 + }, + "origin": "pptpd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bcrelay", + "cmd:pptpctrl", + "cmd:pptpd" + ] + }, + "smartmontools-doc": { + "versions": { + "6.6-r1": 1545163011 + }, + "origin": "smartmontools" + }, + "postfix-policyd-spf-perl": { + "versions": { + "2.007-r2": 1545069435 + }, + "origin": "postfix-policyd-spf-perl", + "dependencies": [ + "perl", + "perl-mail-spf", + "perl-netaddr-ip" + ], + "provides": [ + "cmd:postfix-policyd-spf-perl" + ] + }, + "clutter-lang": { + "versions": { + "1.26.2-r2": 1545293148 + }, + "origin": "clutter" + }, + "hwdata-usb": { + "versions": { + "0.318-r0": 1545746062 + }, + "origin": "hwdata" + }, + "ruby-augeas": { + "versions": { + "0.5.0-r4": 1545301045 + }, + "origin": "ruby-augeas", + "dependencies": [ + "ruby", + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.5" + ] + }, + "libtiffxx": { + "versions": { + "4.0.10-r0": 1543921906 + }, + "origin": "tiff", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6", + "so:libtiff.so.5" + ], + "provides": [ + "so:libtiffxx.so.5=5.4.0" + ] + }, + "gcc-gnat": { + "versions": { + "8.3.0-r0": 1554745499 + }, + "origin": "gcc", + "dependencies": [ + "gcc=8.3.0-r0", + "libgnat=8.3.0-r0", + "libgnat=8.3.0-r0", + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libisl.so.15", + "so:libmpc.so.3", + "so:libmpfr.so.4", + "so:libz.so.1" + ], + "provides": [ + "cmd:gnat", + "cmd:gnatbind", + "cmd:gnatchop", + "cmd:gnatclean", + "cmd:gnatfind", + "cmd:gnatkr", + "cmd:gnatlink", + "cmd:gnatls", + "cmd:gnatmake", + "cmd:gnatname", + "cmd:gnatprep", + "cmd:gnatxref" + ] + }, + "nmap-nping": { + "versions": { + "7.70-r3": 1545163173 + }, + "origin": "nmap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libpcap.so.1", + "so:libssl.so.1.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:nping" + ] + }, + "py-mock": { + "versions": { + "2.0.0-r3": 1543925747 + }, + "origin": "py-mock", + "dependencies": [ + "py-pbr", + "py-six" + ] + }, + "lighttpd-mod_webdav": { + "versions": { + "1.4.52-r0": 1545207630 + }, + "origin": "lighttpd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0", + "so:libuuid.so.1", + "so:libxml2.so.2" + ] + }, + "qemu-mipsn32el": { + "versions": { + "3.1.0-r3": 1551107301 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mipsn32el" + ] + }, + "xcb-util-cursor": { + "versions": { + "0.1.3-r1": 1545163435 + }, + "origin": "xcb-util-cursor", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-image.so.0", + "so:libxcb-render-util.so.0", + "so:libxcb-render.so.0", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-cursor.so.0=0.0.0" + ] + }, + "harfbuzz-static": { + "versions": { + "2.2.0-r0": 1545249742 + }, + "origin": "harfbuzz" + }, + "libtls-standalone-dbg": { + "versions": { + "2.7.4-r6": 1546784623 + }, + "origin": "libtls-standalone", + "dependencies": [ + "ca-certificates-cacert" + ] + }, + "apache2-ldap": { + "versions": { + "2.4.39-r0": 1554306835 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "apr-util-ldap", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ] + }, + "qemu-microblaze": { + "versions": { + "3.1.0-r3": 1551107300 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-microblaze" + ] + }, + "iwlwifi-5000-ucode-doc": { + "versions": { + "8.83.5.1-r0": 1542845811 + }, + "origin": "iwlwifi-5000-ucode" + }, + "lksctp-tools-dev": { + "versions": { + "1.0.17-r0": 1543241223 + }, + "origin": "lksctp-tools", + "dependencies": [ + "lksctp-tools=1.0.17-r0", + "pkgconfig" + ], + "provides": [ + "pc:libsctp=1.0.17" + ] + }, + "py2-tox": { + "versions": { + "3.2.1-r1": 1545060599 + }, + "origin": "py-tox", + "dependencies": [ + "py2-py", + "py2-pluggy", + "py2-argparse", + "py-virtualenv", + "python2" + ], + "provides": [ + "cmd:tox", + "cmd:tox-quickstart" + ] + }, + "qemu-system-riscv64": { + "versions": { + "3.1.0-r3": 1551107305 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-riscv64" + ] + }, + "qemu-ui-sdl": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "so:libSDL2-2.0.so.0", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ] + }, + "gettext-dbg": { + "versions": { + "0.19.8.1-r4": 1542304411 + }, + "origin": "gettext" + }, + "py2-avahi": { + "versions": { + "0.7-r1": 1543925312 + }, + "origin": "avahi", + "dependencies": [ + "avahi=0.7-r1", + "python2" + ], + "provides": [ + "py-avahi=0.7-r1" + ] + }, + "libetpan": { + "versions": { + "1.9.2-r0": 1548063823 + }, + "origin": "libetpan", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:libgnutls.so.30", + "so:libsasl2.so.3", + "so:libz.so.1" + ], + "provides": [ + "so:libetpan.so.20=20.3.0" + ] + }, + "py-icu": { + "versions": { + "1.9.8-r2": 1545165267 + }, + "origin": "py-icu" + }, + "perl-scope-guard-doc": { + "versions": { + "0.21-r0": 1542924584 + }, + "origin": "perl-scope-guard" + }, + "uwsgi-webdav": { + "versions": { + "2.0.17.1-r0": 1545062214 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2" + ] + }, + "linux-firmware-microchip": { + "versions": { + "20190322-r0": 1554980650 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "tzdata": { + "versions": { + "2019a-r0": 1555329220 + }, + "origin": "tzdata", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:posixtz", + "cmd:zdump", + "cmd:zic" + ] + }, + "nghttp2-libs": { + "versions": { + "1.35.1-r0": 1545858450 + }, + "origin": "nghttp2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnghttp2.so.14=14.17.1" + ] + }, + "sprunge": { + "versions": { + "0.6-r0": 1545076751 + }, + "origin": "sprunge", + "dependencies": [ + "curl" + ], + "provides": [ + "cmd:sprunge" + ] + }, + "rsyslog-http": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ], + "provides": [ + "rsyslog-omhttp=8.40.0-r3", + "rsyslog-fmhttp=8.40.0-r3" + ] + }, + "uwsgi-transformation_offload": { + "versions": { + "2.0.17.1-r0": 1545062212 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "udisks-dev": { + "versions": { + "1.0.5-r3": 1545069134 + }, + "origin": "udisks", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:udisks=1.0.5" + ] + }, + "rrdtool-utils": { + "versions": { + "1.7.0-r0": 1542924807 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "cmd:rrdcreate", + "cmd:rrdinfo", + "cmd:rrdupdate" + ] + }, + "su-exec": { + "versions": { + "0.2-r0": 1543999480 + }, + "origin": "su-exec", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:su-exec" + ] + }, + "qemu-sh4eb": { + "versions": { + "3.1.0-r3": 1551107302 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-sh4eb" + ] + }, + "drill": { + "versions": { + "1.7.0-r2": 1545117117 + }, + "origin": "ldns", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libldns.so.2" + ], + "provides": [ + "cmd:drill" + ] + }, + "vde2": { + "versions": { + "2.3.2-r10": 1545074025 + }, + "origin": "vde2", + "dependencies": [ + "openssl", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpcap.so.1", + "so:libpython2.7.so.1.0", + "so:libvdehist.so.0", + "so:libvdemgmt.so.0", + "so:libvdeplug.so.3" + ], + "provides": [ + "cmd:dpipe", + "cmd:slirpvde", + "cmd:unixcmd", + "cmd:unixterm", + "cmd:vde_autolink", + "cmd:vde_cryptcab", + "cmd:vde_l3", + "cmd:vde_over_ns", + "cmd:vde_pcapplug", + "cmd:vde_plug", + "cmd:vde_plug2tap", + "cmd:vde_switch", + "cmd:vde_tunctl", + "cmd:vdecmd", + "cmd:vdekvm", + "cmd:vdeq", + "cmd:vdeqemu", + "cmd:vdeterm", + "cmd:wirefilter" + ] + }, + "py3-lxml": { + "versions": { + "4.2.5-r0": 1545065048 + }, + "origin": "py-lxml", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexslt.so.0", + "so:libpython3.6m.so.1.0", + "so:libxml2.so.2", + "so:libxslt.so.1" + ] + }, + "gnutls-utils": { + "versions": { + "3.6.7-r0": 1555049964 + }, + "origin": "gnutls", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30", + "so:libtasn1.so.6" + ], + "provides": [ + "cmd:certtool", + "cmd:gnutls-cli", + "cmd:gnutls-cli-debug", + "cmd:gnutls-serv", + "cmd:ocsptool", + "cmd:p11tool", + "cmd:psktool", + "cmd:srptool" + ] + }, + "perl-mail-imapclient-doc": { + "versions": { + "3.39-r0": 1545075908 + }, + "origin": "perl-mail-imapclient" + }, + "mariadb-connector-c": { + "versions": { + "3.0.8-r0": 1547470538 + }, + "origin": "mariadb-connector-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:libmariadb.so.3=3" + ] + }, + "xf86-input-mouse-doc": { + "versions": { + "1.9.3-r1": 1545300644 + }, + "origin": "xf86-input-mouse" + }, + "xdelta3-doc": { + "versions": { + "3.0.11-r0": 1545207370 + }, + "origin": "xdelta3" + }, + "avahi-dev": { + "versions": { + "0.7-r1": 1543925309 + }, + "origin": "avahi", + "dependencies": [ + "gdbm-dev", + "avahi-compat-howl=0.7-r1", + "avahi-compat-libdns_sd=0.7-r1", + "avahi-glib=0.7-r1", + "avahi-libs=0.7-r1", + "avahi=0.7-r1", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:avahi-client=0.7", + "pc:avahi-compat-howl=0.9.8", + "pc:avahi-compat-libdns_sd=0.7", + "pc:avahi-core=0.7", + "pc:avahi-glib=0.7", + "pc:avahi-gobject=0.7", + "pc:howl=0.9.8", + "pc:libdns_sd=0.7" + ] + }, + "uwsgi-carbon": { + "versions": { + "2.0.17.1-r0": 1545062196 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "dahdi-tools-dev": { + "versions": { + "2.11.1-r0": 1543932615 + }, + "origin": "dahdi-tools", + "dependencies": [ + "bsd-compat-headers", + "linux-headers", + "dahdi-linux-dev", + "newt-dev", + "dahdi-tools=2.11.1-r0" + ] + }, + "wxgtk2.8-lang": { + "versions": { + "2.8.12.1-r4": 1545075286 + }, + "origin": "wxgtk2.8" + }, + "ragel": { + "versions": { + "6.10-r0": 1545208963 + }, + "origin": "ragel", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:ragel" + ] + }, + "pacman": { + "versions": { + "5.0.2-r4": 1543932340 + }, + "origin": "pacman", + "dependencies": [ + "libarchive-tools", + "so:libarchive.so.13", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libgpgme.so.11", + "so:libintl.so.8" + ], + "provides": [ + "so:libalpm.so.10=10.0.2", + "cmd:cleanupdelta", + "cmd:makepkg", + "cmd:makepkg-template", + "cmd:pacman", + "cmd:pacman-db-upgrade", + "cmd:pacman-key", + "cmd:pacman-optimize", + "cmd:pacsort", + "cmd:pactree", + "cmd:pkgdelta", + "cmd:repo-add", + "cmd:repo-elephant", + "cmd:repo-remove", + "cmd:testpkg", + "cmd:vercmp" + ] + }, + "libjpeg-turbo-utils": { + "versions": { + "1.5.3-r4": 1547027583 + }, + "origin": "libjpeg-turbo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8", + "so:libturbojpeg.so.0" + ], + "provides": [ + "cmd:cjpeg", + "cmd:djpeg", + "cmd:jpegtran", + "cmd:rdjpgcom", + "cmd:tjbench", + "cmd:wrjpgcom" + ] + }, + "irssi-proxy": { + "versions": { + "1.1.2-r0": 1547738041 + }, + "origin": "irssi", + "dependencies": [ + "irssi", + "so:libc.musl-x86_64.so.1" + ] + }, + "rp-pppoe-openrc": { + "versions": { + "3.13-r0": 1545223084 + }, + "origin": "rp-pppoe" + }, + "xmlrpc-c": { + "versions": { + "1.39.13-r0": 1545665221 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "so:libxmlrpc.so.3=3.39", + "so:libxmlrpc_server.so.3=3.39", + "so:libxmlrpc_util.so.3=3.39" + ] + }, + "joe": { + "versions": { + "4.6-r0": 1543221866 + }, + "origin": "joe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:jmacs", + "cmd:joe", + "cmd:jpico", + "cmd:jstar", + "cmd:rjoe" + ] + }, + "gcc-objc": { + "versions": { + "8.3.0-r0": 1554745498 + }, + "origin": "gcc", + "dependencies": [ + "libc-dev", + "gcc=8.3.0-r0", + "libobjc=8.3.0-r0", + "libobjc=8.3.0-r0" + ] + }, + "squark-dbg": { + "versions": { + "0.6.1-r1": 1545060928 + }, + "origin": "squark", + "dependencies": [ + "haserl" + ] + }, + "spawn-fcgi-doc": { + "versions": { + "1.6.4-r3": 1542821586 + }, + "origin": "spawn-fcgi" + }, + "byacc-doc": { + "versions": { + "20180609-r0": 1545062012 + }, + "origin": "byacc" + }, + "uwsgi-logpipe": { + "versions": { + "2.0.17.1-r0": 1545062202 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-mozilla-ca-doc": { + "versions": { + "20160104-r0": 1545223045 + }, + "origin": "perl-mozilla-ca" + }, + "syslog-ng-amqp": { + "versions": { + "3.19.1-r0": 1548543150 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librabbitmq.so.4", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "linux-firmware-sun": { + "versions": { + "20190322-r0": 1554980647 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "lua-filesystem": { + "versions": { + "1.7.0.2-r0": 1542820936 + }, + "origin": "lua-filesystem" + }, + "krb5": { + "versions": { + "1.15.5-r0": 1548094458 + }, + "origin": "krb5", + "dependencies": [ + "krb5-conf", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libgssapi_krb5.so.2", + "so:libk5crypto.so.3", + "so:libkadm5clnt_mit.so.11", + "so:libkadm5srv_mit.so.11", + "so:libkdb5.so.8", + "so:libkrb5.so.3", + "so:libkrb5support.so.0", + "so:libss.so.2" + ], + "provides": [ + "cmd:gss-client", + "cmd:k5srvutil", + "cmd:kadmin", + "cmd:kdestroy", + "cmd:kinit", + "cmd:klist", + "cmd:kpasswd", + "cmd:ksu", + "cmd:kswitch", + "cmd:ktutil", + "cmd:kvno", + "cmd:sim_client", + "cmd:uuclient" + ] + }, + "libxkbcommon": { + "versions": { + "0.8.2-r0": 1545857327 + }, + "origin": "libxkbcommon", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-xkb.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxkbcommon-x11.so.0=0.0.0", + "so:libxkbcommon.so.0=0.0.0" + ] + }, + "py3-future": { + "versions": { + "0.17.0-r0": 1545213720 + }, + "origin": "py-future", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:futurize", + "cmd:pasteurize" + ] + }, + "py-gnome-libgnome": { + "versions": { + "2.28.1-r5": 1545301292 + }, + "origin": "py-gnome", + "dependencies": [ + "py-gtk", + "py-gnome-bonobo", + "py-gnome-gnomecanvas", + "py-gnome-gnomevfs", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgnome-2.so.0", + "so:libgobject-2.0.so.0", + "so:libpopt.so.0", + "so:libpython2.7.so.1.0" + ] + }, + "samba-client-libs": { + "versions": { + "4.8.11-r1": 1555334973 + }, + "origin": "samba", + "dependencies": [ + "so:libaddns-samba4.so", + "so:libasn1util-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-nbt-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcliauth-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libinterfaces-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libpopt.so.0", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsmb-transport-samba4.so", + "so:libsmbconf.so.0", + "so:libsocket-blocking-samba4.so", + "so:libsys-rw-samba4.so", + "so:libtalloc.so.2", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-cmdline-samba4.so", + "so:libutil-reg-samba4.so" + ], + "provides": [ + "so:libcli-ldap-samba4.so=0", + "so:libcmdline-contexts-samba4.so=0", + "so:libcmdline-credentials-samba4.so=0", + "so:libdcerpc.so.0=0.0.1", + "so:libdsdb-garbage-collect-tombstones-samba4.so=0", + "so:libevents-samba4.so=0", + "so:libhttp-samba4.so=0", + "so:libnetif-samba4.so=0", + "so:libpopt-samba3-cmdline-samba4.so=0", + "so:libregistry-samba4.so=0", + "so:libsmbclient-raw-samba4.so=0" + ] + }, + "pixman-static": { + "versions": { + "0.34.0-r6": 1542822838 + }, + "origin": "pixman" + }, + "libtool-doc": { + "versions": { + "2.4.6-r5": 1542301346 + }, + "origin": "libtool" + }, + "iwlwifi-1000-ucode-doc": { + "versions": { + "39.31.5.1-r0": 1545076625 + }, + "origin": "iwlwifi-1000-ucode" + }, + "pciutils-libs": { + "versions": { + "3.6.2-r0": 1543921866 + }, + "origin": "pciutils", + "dependencies": [ + "hwdata-pci", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpci.so.3=3.6.2" + ] + }, + "perl-html-mason-psgihandler-doc": { + "versions": { + "0.53-r0": 1545299872 + }, + "origin": "perl-html-mason-psgihandler" + }, + "gtk-engines-thinice": { + "versions": { + "2.21.0-r2": 1545289338 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "s6-networking-doc": { + "versions": { + "2.3.0.3-r1": 1546957736 + }, + "origin": "s6-networking" + }, + "c-ares-doc": { + "versions": { + "1.15.0-r0": 1544791695 + }, + "origin": "c-ares" + }, + "freeswitch-sounds-en-us-callie-8000": { + "versions": { + "1.0.51-r0": 1545116746 + }, + "origin": "freeswitch-sounds-en-us-callie-8000" + }, + "ninja-doc": { + "versions": { + "1.8.2-r1": 1542822428 + }, + "origin": "ninja" + }, + "imagemagick-c++": { + "versions": { + "7.0.8.44-r0": 1557126217 + }, + "origin": "imagemagick", + "dependencies": [ + "so:libMagickCore-7.Q16HDRI.so.6", + "so:libMagickWand-7.Q16HDRI.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libMagick++-7.Q16HDRI.so.4=4.0.0" + ] + }, + "xsetmode": { + "versions": { + "1.0.0-r4": 1545075298 + }, + "origin": "xsetmode", + "dependencies": [ + "so:libX11.so.6", + "so:libXi.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xsetmode" + ] + }, + "collectd-write_redis": { + "versions": { + "5.8.0-r3": 1546422677 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.14" + ] + }, + "gtkglext": { + "versions": { + "1.2.0-r12": 1544001308 + }, + "origin": "gtkglext", + "dependencies": [ + "so:libGL.so.1", + "so:libGLU.so.1", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangox-1.0.so.0" + ], + "provides": [ + "so:libgdkglext-x11-1.0.so.0=0.0.0", + "so:libgtkglext-x11-1.0.so.0=0.0.0" + ] + }, + "qemu-system-mips": { + "versions": { + "3.1.0-r3": 1551107303 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libstdc++.so.6", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-mips" + ] + }, + "indent-doc": { + "versions": { + "2.2.12-r0": 1545299643 + }, + "origin": "indent" + }, + "skalibs-doc": { + "versions": { + "2.7.0.0-r0": 1543221683 + }, + "origin": "skalibs" + }, + "dovecot": { + "versions": { + "2.3.6-r0": 1557134099 + }, + "origin": "dovecot", + "dependencies": [ + "openssl", + "/bin/sh", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:lib01_acl_plugin.so=0", + "so:lib02_imap_acl_plugin.so=0", + "so:lib02_lazy_expunge_plugin.so=0", + "so:lib05_mail_crypt_acl_plugin.so=0", + "so:lib05_pop3_migration_plugin.so=0", + "so:lib05_snarf_plugin.so=0", + "so:lib10_last_login_plugin.so=0", + "so:lib10_mail_crypt_plugin.so=0", + "so:lib10_mail_filter_plugin.so=0", + "so:lib10_quota_plugin.so=0", + "so:lib11_imap_quota_plugin.so=0", + "so:lib11_trash_plugin.so=0", + "so:lib15_notify_plugin.so=0", + "so:lib20_autocreate_plugin.so=0", + "so:lib20_charset_alias_plugin.so=0", + "so:lib20_expire_plugin.so=0", + "so:lib20_fts_plugin.so=0", + "so:lib20_listescape_plugin.so=0", + "so:lib20_mail_log_plugin.so=0", + "so:lib20_mailbox_alias_plugin.so=0", + "so:lib20_notify_status_plugin.so=0", + "so:lib20_push_notification_plugin.so=0", + "so:lib20_quota_clone_plugin.so=0", + "so:lib20_replication_plugin.so=0", + "so:lib20_var_expand_crypt.so=0", + "so:lib20_virtual_plugin.so=0", + "so:lib20_zlib_plugin.so=0", + "so:lib21_fts_squat_plugin.so=0", + "so:lib30_imap_zlib_plugin.so=0", + "so:lib90_old_stats_plugin.so=0", + "so:lib95_imap_old_stats_plugin.so=0", + "so:lib99_welcome_plugin.so=0", + "so:libdcrypt_openssl.so=0", + "so:libdovecot-compression.so.0=0.0.0", + "so:libdovecot-dsync.so.0=0.0.0", + "so:libdovecot-fts.so.0=0.0.0", + "so:libdovecot-lda.so.0=0.0.0", + "so:libdovecot-login.so.0=0.0.0", + "so:libdovecot-storage.so.0=0.0.0", + "so:libdovecot.so.0=0.0.0", + "so:libfs_compress.so=0", + "so:libfs_crypt.so=0", + "so:libfs_mail_crypt.so=0", + "so:libold_stats_mail.so=0", + "so:libssl_iostream_openssl.so=0", + "so:libstats_auth.so=0", + "cmd:doveadm", + "cmd:doveconf", + "cmd:dovecot", + "cmd:dsync" + ] + }, + "gnokii-doc": { + "versions": { + "0.6.31-r8": 1545300479 + }, + "origin": "gnokii" + }, + "libxfixes-doc": { + "versions": { + "5.0.3-r2": 1542823751 + }, + "origin": "libxfixes" + }, + "perl-filesys-notify-simple-doc": { + "versions": { + "0.13-r0": 1543927835 + }, + "origin": "perl-filesys-notify-simple" + }, + "libfdisk": { + "versions": { + "2.33-r0": 1545307436 + }, + "origin": "util-linux", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libfdisk.so.1=1.1.0" + ] + }, + "perl-control-x10-doc": { + "versions": { + "2.09-r1": 1545253944 + }, + "origin": "perl-control-x10" + }, + "atop-doc": { + "versions": { + "2.3.0-r1": 1543226695 + }, + "origin": "atop" + }, + "uwsgi-router_redis": { + "versions": { + "2.0.17.1-r0": 1545062208 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-pgen": { + "versions": { + "2.7.10-r0": 1545300069 + }, + "origin": "py-pgen", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pgen" + ] + }, + "clamav-milter": { + "versions": { + "0.100.3-r0": 1555507336 + }, + "origin": "clamav", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libmilter.so.1.0.2", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:clamav-milter" + ] + }, + "aria2": { + "versions": { + "1.34.0-r1": 1543998971 + }, + "origin": "aria2", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libgmp.so.10", + "so:libgnutls.so.30", + "so:libnettle.so.6", + "so:libsqlite3.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:aria2c" + ] + }, + "perl-convert-asn1": { + "versions": { + "0.27-r0": 1543081688 + }, + "origin": "perl-convert-asn1" + }, + "unrar": { + "versions": { + "5.6.8-r0": 1545069402 + }, + "origin": "unrar", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:unrar" + ] + }, + "speex-dev": { + "versions": { + "1.2.0-r0": 1544799247 + }, + "origin": "speex", + "dependencies": [ + "pkgconfig", + "speex=1.2.0-r0" + ], + "provides": [ + "pc:speex=1.2.0" + ] + }, + "perl-business-hours": { + "versions": { + "0.13-r0": 1547709105 + }, + "origin": "perl-business-hours", + "dependencies": [ + "perl-set-intspan" + ] + }, + "xorg-server-xwayland": { + "versions": { + "1.20.3-r1": 1543928069 + }, + "origin": "xorg-server", + "dependencies": [ + "font-misc-misc", + "font-cursor-misc", + "xkeyboard-config", + "xkbcomp", + "xinit", + "so:libGL.so.1", + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libdrm.so.2", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libpixman-1.so.0", + "so:libwayland-client.so.0", + "so:libxshmfence.so.1" + ], + "provides": [ + "cmd:Xwayland" + ] + }, + "apcupsd-webif": { + "versions": { + "3.14.14-r0": 1545293177 + }, + "origin": "apcupsd", + "dependencies": [ + "util-linux", + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3" + ] + }, + "kamailio-sqlite": { + "versions": { + "5.2.0-r1": 1546423169 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "kamailio-db", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0", + "so:libsrdb1.so.1" + ] + }, + "libtasn1-dev": { + "versions": { + "4.13-r0": 1542824444 + }, + "origin": "libtasn1", + "dependencies": [ + "libtasn1=4.13-r0", + "pkgconfig" + ], + "provides": [ + "pc:libtasn1=4.13" + ] + }, + "gdk-pixbuf-lang": { + "versions": { + "2.36.11-r2": 1543253990 + }, + "origin": "gdk-pixbuf", + "dependencies": [ + "shared-mime-info" + ] + }, + "libxfont-dev": { + "versions": { + "1.5.4-r1": 1542924728 + }, + "origin": "libxfont", + "dependencies": [ + "libxfont=1.5.4-r1", + "pc:fontenc", + "pc:fontsproto", + "pc:freetype2", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xfont=1.5.4" + ] + }, + "dovecot-fts-solr": { + "versions": { + "2.3.6-r0": 1557134099 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot=2.3.6-r0", + "so:lib20_fts_plugin.so", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ], + "provides": [ + "so:lib21_fts_solr_plugin.so=0" + ] + }, + "gpgme": { + "versions": { + "1.12.0-r3": 1543932322 + }, + "origin": "gpgme", + "dependencies": [ + "gnupg", + "so:libassuan.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgpg-error.so.0" + ], + "provides": [ + "so:libgpgme.so.11=11.21.0", + "cmd:gpgme-json", + "cmd:gpgme-tool" + ] + }, + "mlmmj": { + "versions": { + "1.3.0-r0": 1542883220 + }, + "origin": "mlmmj", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mlmmj-bounce", + "cmd:mlmmj-list", + "cmd:mlmmj-maintd", + "cmd:mlmmj-make-ml", + "cmd:mlmmj-make-ml.sh", + "cmd:mlmmj-process", + "cmd:mlmmj-receive", + "cmd:mlmmj-recieve", + "cmd:mlmmj-send", + "cmd:mlmmj-sub", + "cmd:mlmmj-unsub" + ] + }, + "bridge-utils-doc": { + "versions": { + "1.6-r0": 1543935359 + }, + "origin": "bridge-utils" + }, + "s6-networking-dev": { + "versions": { + "2.3.0.3-r1": 1546957736 + }, + "origin": "s6-networking", + "dependencies": [ + "s6-networking=2.3.0.3-r1" + ] + }, + "kamailio-doc": { + "versions": { + "5.2.0-r1": 1546423162 + }, + "origin": "kamailio" + }, + "ctags": { + "versions": { + "5.8-r5": 1545301883 + }, + "origin": "ctags", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ctags" + ] + }, + "gnokii-smsd": { + "versions": { + "0.6.31-r8": 1545300479 + }, + "origin": "gnokii", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnokii.so.7", + "so:libintl.so.8" + ], + "provides": [ + "cmd:smsd" + ] + }, + "dhcp-doc": { + "versions": { + "4.4.1-r1": 1543928451 + }, + "origin": "dhcp" + }, + "qemu-modules": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "qemu-audio-alsa", + "qemu-audio-oss", + "qemu-audio-sdl", + "qemu-block-curl", + "qemu-block-dmg-bz2", + "qemu-block-nfs", + "qemu-block-ssh", + "qemu-ui-curses", + "qemu-ui-gtk", + "qemu-ui-sdl" + ] + }, + "ruby-test-unit": { + "versions": { + "2.5.5-r0": 1557164837 + }, + "origin": "ruby", + "dependencies": [ + "ruby", + "ruby-power_assert" + ] + }, + "stfl": { + "versions": { + "0.24-r2": 1545075303 + }, + "origin": "stfl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libstfl.so.0=0.24" + ] + }, + "linux-firmware-mwlwifi": { + "versions": { + "20190322-r0": 1554980650 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "py3-asn1crypto": { + "versions": { + "0.24.0-r0": 1543246738 + }, + "origin": "py-asn1crypto", + "dependencies": [ + "python3" + ] + }, + "acl-doc": { + "versions": { + "2.2.52-r5": 1542304042 + }, + "origin": "acl" + }, + "lua5.2-rex-posix": { + "versions": { + "2.9.0-r0": 1545209204 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libunique-dev": { + "versions": { + "1.1.6-r6": 1545299840 + }, + "origin": "libunique", + "dependencies": [ + "libunique=1.1.6-r6", + "pc:gtk+-2.0", + "pkgconfig" + ], + "provides": [ + "pc:unique-1.0=1.1.6" + ] + }, + "openntpd": { + "versions": { + "6.2_p3-r2": 1543230955 + }, + "origin": "openntpd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtls-standalone.so.1" + ], + "provides": [ + "cmd:ntpctl", + "cmd:ntpd" + ] + }, + "libmikmod-dev": { + "versions": { + "3.3.11.1-r0": 1545162983 + }, + "origin": "libmikmod", + "dependencies": [ + "libmikmod=3.3.11.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmikmod=3.3.11", + "cmd:libmikmod-config" + ] + }, + "faad2-doc": { + "versions": { + "2.7-r8": 1543998810 + }, + "origin": "faad2" + }, + "drbd-utils": { + "versions": { + "9.7.1-r0": 1545746125 + }, + "origin": "drbd-utils", + "dependencies": [ + "bash", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "drbd=9.7.1-r0", + "cmd:drbdadm", + "cmd:drbdmeta", + "cmd:drbdmon", + "cmd:drbdsetup" + ] + }, + "xfsprogs": { + "versions": { + "4.19.0-r1": 1546597448 + }, + "origin": "xfsprogs", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:fsck.xfs", + "cmd:mkfs.xfs", + "cmd:xfs_repair" + ] + }, + "perl-extutils-pkgconfig": { + "versions": { + "1.16-r1": 1543077189 + }, + "origin": "perl-extutils-pkgconfig" + }, + "opennhrp": { + "versions": { + "0.14.1-r6": 1545069405 + }, + "origin": "opennhrp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2" + ], + "provides": [ + "cmd:opennhrp", + "cmd:opennhrpctl" + ] + }, + "perl-params-util-doc": { + "versions": { + "1.07-r5": 1542845639 + }, + "origin": "perl-params-util" + }, + "perl-config-grammar-doc": { + "versions": { + "1.12-r0": 1543999449 + }, + "origin": "perl-config-grammar" + }, + "perl-convert-binhex-doc": { + "versions": { + "1.125-r1": 1544001491 + }, + "origin": "perl-convert-binhex" + }, + "cups-lang": { + "versions": { + "2.2.10-r0": 1545910846 + }, + "origin": "cups", + "dependencies": [ + "cups-client", + "poppler-utils", + "openssl", + "dbus" + ] + }, + "libquadmath": { + "versions": { + "8.3.0-r0": 1554745497 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libquadmath.so.0=0.0.0" + ] + }, + "freeswitch-sounds-music-32000": { + "versions": { + "1.0.8-r1": 1545075920 + }, + "origin": "freeswitch-sounds-music-32000" + }, + "perl-ipc-system-simple-doc": { + "versions": { + "1.25-r0": 1542924660 + }, + "origin": "perl-ipc-system-simple" + }, + "zeromq-dev": { + "versions": { + "4.3.1-r0": 1548271358 + }, + "origin": "zeromq", + "dependencies": [ + "libzmq=4.3.1-r0", + "pc:libsodium", + "pkgconfig" + ], + "provides": [ + "pc:libzmq=4.3.1" + ] + }, + "duplicity-doc": { + "versions": { + "0.7.18.2-r0": 1544000473 + }, + "origin": "duplicity" + }, + "acf-openntpd": { + "versions": { + "0.9.0-r2": 1544799255 + }, + "origin": "acf-openntpd", + "dependencies": [ + "acf-core", + "openntpd" + ] + }, + "py3-mako": { + "versions": { + "1.0.7-r0": 1543220555 + }, + "origin": "py-mako", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:mako-render" + ] + }, + "squid-lang-el": { + "versions": { + "4.4-r1": 1545216324 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "acf-squid": { + "versions": { + "0.11.0-r2": 1545216338 + }, + "origin": "acf-squid", + "dependencies": [ + "acf-core", + "squid" + ] + }, + "vim": { + "versions": { + "8.1.0630-r0": 1545664995 + }, + "origin": "vim", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua-5.3.so.0", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:ex", + "cmd:rview", + "cmd:rvim", + "cmd:view", + "cmd:vim", + "cmd:vimtutor", + "cmd:xxd" + ] + }, + "libice-dev": { + "versions": { + "1.0.9-r3": 1542824332 + }, + "origin": "libice", + "dependencies": [ + "libice=1.0.9-r3", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:ice=1.0.9" + ] + }, + "gstreamer-doc": { + "versions": { + "1.14.4-r0": 1543254159 + }, + "origin": "gstreamer" + }, + "font-winitzki-cyrillic": { + "versions": { + "1.0.3-r0": 1545224037 + }, + "origin": "font-winitzki-cyrillic", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "font-adobe-utopia-75dpi": { + "versions": { + "1.0.4-r0": 1545062788 + }, + "origin": "font-adobe-utopia-75dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "syslog-ng-geoip": { + "versions": { + "3.19.1-r0": 1548543150 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "gnats-doc": { + "versions": { + "4.2.0-r5": 1545209105 + }, + "origin": "gnats" + }, + "cfdisk": { + "versions": { + "2.33-r0": 1545307437 + }, + "origin": "util-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfdisk.so.1", + "so:libmount.so.1", + "so:libncursesw.so.6", + "so:libsmartcols.so.1" + ], + "provides": [ + "cmd:cfdisk" + ] + }, + "haserl-lua5.3": { + "versions": { + "0.9.35-r1": 1542883797 + }, + "origin": "haserl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua-5.3.so.0" + ], + "provides": [ + "cmd:haserl-lua5.3" + ] + }, + "unifont": { + "versions": { + "11.0.01-r1": 1543077262 + }, + "origin": "unifont", + "dependencies": [ + "bdftopcf", + "perl-gd", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bdfimplode", + "cmd:hex2bdf", + "cmd:hex2sfd", + "cmd:hexbraille", + "cmd:hexdraw", + "cmd:hexkinya", + "cmd:hexmerge", + "cmd:johab2ucs2", + "cmd:unibdf2hex", + "cmd:unibmp2hex", + "cmd:unicoverage", + "cmd:unidup", + "cmd:unifont-viewer", + "cmd:unifont1per", + "cmd:unifontchojung", + "cmd:unifontksx", + "cmd:unifontpic", + "cmd:unigencircles", + "cmd:unigenwidth", + "cmd:unihex2bmp", + "cmd:unihex2png", + "cmd:unihexfill", + "cmd:unihexgen", + "cmd:unipagecount", + "cmd:unipng2hex" + ] + }, + "bzip2": { + "versions": { + "1.0.6-r6": 1542300119 + }, + "origin": "bzip2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bunzip2", + "cmd:bzcat", + "cmd:bzcmp", + "cmd:bzdiff", + "cmd:bzegrep", + "cmd:bzfgrep", + "cmd:bzgrep", + "cmd:bzip2", + "cmd:bzip2recover", + "cmd:bzless", + "cmd:bzmore" + ] + }, + "dhcpcd": { + "versions": { + "7.0.8-r0": 1545207907 + }, + "origin": "dhcpcd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dhcpcd" + ] + }, + "font-bh-type1": { + "versions": { + "1.0.3-r0": 1543934557 + }, + "origin": "font-bh-type1", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-package-deprecationmanager-doc": { + "versions": { + "0.17-r0": 1542845764 + }, + "origin": "perl-package-deprecationmanager" + }, + "squark-doc": { + "versions": { + "0.6.1-r1": 1545060928 + }, + "origin": "squark" + }, + "swatch": { + "versions": { + "3.2.4-r4": 1545116516 + }, + "origin": "swatch", + "dependencies": [ + "perl", + "perl-date-calc", + "perl-date-format", + "perl-date-manip", + "perl-file-tail", + "perl-carp-clan" + ], + "provides": [ + "cmd:swatchdog" + ] + }, + "zsh-doc": { + "versions": { + "5.6.2-r0": 1545308085 + }, + "origin": "zsh" + }, + "perl-file-slurper-doc": { + "versions": { + "0.010-r0": 1545075942 + }, + "origin": "perl-file-slurper" + }, + "rhash": { + "versions": { + "1.3.6-r2": 1542820449 + }, + "origin": "rhash", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librhash.so.0" + ], + "provides": [ + "cmd:ed2k-link", + "cmd:edonr256-hash", + "cmd:edonr512-hash", + "cmd:gost-hash", + "cmd:has160-hash", + "cmd:magnet-link", + "cmd:rhash", + "cmd:sfv-hash", + "cmd:tiger-hash", + "cmd:tth-hash", + "cmd:whirlpool-hash" + ] + }, + "qemu-system-mips64": { + "versions": { + "3.1.0-r3": 1551107304 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libstdc++.so.6", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-mips64" + ] + }, + "enca": { + "versions": { + "1.19-r1": 1545076573 + }, + "origin": "enca", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libenca.so.0=0.5.1", + "cmd:enca", + "cmd:enconv" + ] + }, + "file-dev": { + "versions": { + "5.36-r0": 1557160812 + }, + "origin": "file", + "dependencies": [ + "libmagic=5.36-r0" + ] + }, + "mailx": { + "versions": { + "8.1.1-r1": 1545075412 + }, + "origin": "mailx", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mail" + ] + }, + "py-rrd": { + "versions": { + "1.7.0-r0": 1542924804 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:librrd.so.8" + ] + }, + "eudev-libs": { + "versions": { + "3.2.7-r0": 1542845384 + }, + "origin": "eudev", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libudev.so.1=1.6.3" + ] + }, + "nghttp2-dev": { + "versions": { + "1.35.1-r0": 1545858449 + }, + "origin": "nghttp2", + "dependencies": [ + "nghttp2-libs=1.35.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnghttp2=1.35.1" + ] + }, + "e2fsprogs-dev": { + "versions": { + "1.44.5-r0": 1545745857 + }, + "origin": "e2fsprogs", + "dependencies": [ + "util-linux-dev", + "e2fsprogs-libs=1.44.5-r0", + "libcom_err=1.44.5-r0", + "pkgconfig" + ], + "provides": [ + "pc:com_err=1.44.5", + "pc:e2p=1.44.5", + "pc:ext2fs=1.44.5", + "pc:ss=1.44.5", + "cmd:compile_et", + "cmd:mk_cmds" + ] + }, + "snort-openrc": { + "versions": { + "2.9.12-r0": 1545301013 + }, + "origin": "snort" + }, + "jack-doc": { + "versions": { + "1.9.12-r0": 1545073439 + }, + "origin": "jack" + }, + "libtxc_dxtn": { + "versions": { + "1.0.1-r5": 1545289388 + }, + "origin": "libtxc_dxtn", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libtxc_dxtn.so=0" + ] + }, + "kamailio-xml": { + "versions": { + "5.2.0-r1": 1546423170 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libsrdb1.so.1", + "so:libxml2.so.2" + ] + }, + "iperf3-openrc": { + "versions": { + "3.6-r0": 1543932136 + }, + "origin": "iperf3" + }, + "py-packaging": { + "versions": { + "17.1-r0": 1542825057 + }, + "origin": "py-packaging", + "dependencies": [ + "py-parsing", + "py-six" + ] + }, + "smartmontools": { + "versions": { + "6.6-r1": 1545163012 + }, + "origin": "smartmontools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:smartctl", + "cmd:smartd", + "cmd:update-smart-drivedb" + ] + }, + "sngtc_client": { + "versions": { + "1.3.7-r1": 1545117161 + }, + "origin": "sngtc_client", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libortp.so.10" + ], + "provides": [ + "so:libsngtc_node.so=0", + "cmd:sngtc_client" + ] + }, + "perl-html-parser-doc": { + "versions": { + "3.72-r2": 1542821838 + }, + "origin": "perl-html-parser" + }, + "lua-ldbus": { + "versions": { + "20150430-r2": 1545300027 + }, + "origin": "lua-ldbus" + }, + "cmph": { + "versions": { + "2.0-r1": 1544000442 + }, + "origin": "cmph", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcmph.so.0" + ], + "provides": [ + "cmd:cmph" + ] + }, + "lxc-templates-legacy": { + "versions": { + "3.0.3-r0": 1546416504 + }, + "origin": "lxc-templates-legacy", + "dependencies": [ + "bash", + "tar" + ] + }, + "uwsgi-router_expires": { + "versions": { + "2.0.17.1-r0": 1545062206 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libmspack-dev": { + "versions": { + "0.8_alpha-r0": 1543925821 + }, + "origin": "libmspack", + "dependencies": [ + "libmspack=0.8_alpha-r0", + "pkgconfig" + ] + }, + "perl-io-captureoutput-doc": { + "versions": { + "1.1104-r0": 1543259612 + }, + "origin": "perl-io-captureoutput" + }, + "patchutils-doc": { + "versions": { + "0.3.4-r1": 1548427864 + }, + "origin": "patchutils" + }, + "mg-doc": { + "versions": { + "20180824-r0": 1545208623 + }, + "origin": "mg" + }, + "lzo-dev": { + "versions": { + "2.10-r2": 1542985321 + }, + "origin": "lzo", + "dependencies": [ + "lzo=2.10-r2", + "pkgconfig" + ], + "provides": [ + "pc:lzo2=2.10" + ] + }, + "acpi": { + "versions": { + "1.7-r2": 1545300419 + }, + "origin": "acpi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:acpi" + ] + }, + "libxpm": { + "versions": { + "3.5.12-r0": 1542894077 + }, + "origin": "libxpm", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXpm.so.4=4.11.0", + "cmd:cxpm", + "cmd:sxpm" + ] + }, + "gmock": { + "versions": { + "1.8.1-r0": 1542822390 + }, + "origin": "gtest", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgtest.so", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgmock.so=0", + "so:libgmock_main.so=0" + ] + }, + "py-django-simple-captcha": { + "versions": { + "0.4.3-r1": 1543934502 + }, + "origin": "py-django-simple-captcha", + "dependencies": [ + "python2" + ] + }, + "libxv-dev": { + "versions": { + "1.0.11-r2": 1542900290 + }, + "origin": "libxv", + "dependencies": [ + "libxv=1.0.11-r2", + "pc:videoproto", + "pc:x11", + "pc:xext", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xv=1.0.11" + ] + }, + "xdelta3": { + "versions": { + "3.0.11-r0": 1545207373 + }, + "origin": "xdelta3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xdelta3" + ] + }, + "vim-doc": { + "versions": { + "8.1.0630-r0": 1545664991 + }, + "origin": "vim" + }, + "iaxmodem": { + "versions": { + "1.3.0-r0": 1545214093 + }, + "origin": "iaxmodem", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:iaxmodem" + ] + }, + "alsa-lib-dev": { + "versions": { + "1.1.8-r0": 1547112801 + }, + "origin": "alsa-lib", + "dependencies": [ + "alsa-lib=1.1.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:alsa=1.1.8" + ] + }, + "task-doc": { + "versions": { + "2.5.1-r0": 1545254342 + }, + "origin": "task" + }, + "nagios-plugins-uptime": { + "versions": { + "2.2.1-r6": 1543933913 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "rtmpdump-doc": { + "versions": { + "2.4_git20160909-r6": 1545215000 + }, + "origin": "rtmpdump" + }, + "hunspell": { + "versions": { + "1.6.2-r1": 1542883765 + }, + "origin": "hunspell", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libreadline.so.7", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libhunspell-1.6.so.0=0.0.1", + "cmd:affixcompress", + "cmd:analyze", + "cmd:chmorph", + "cmd:hunspell", + "cmd:hunzip", + "cmd:hzip", + "cmd:ispellaff2myspell", + "cmd:makealias", + "cmd:munch", + "cmd:unmunch", + "cmd:wordforms", + "cmd:wordlist2hunspell" + ] + }, + "execline-doc": { + "versions": { + "2.5.0.1-r0": 1543221686 + }, + "origin": "execline" + }, + "mariadb-openrc": { + "versions": { + "10.3.13-r1": 1557431734 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common" + ] + }, + "linenoise-dev": { + "versions": { + "1.0-r1": 1542304877 + }, + "origin": "linenoise", + "dependencies": [ + "linenoise=1.0-r1" + ] + }, + "perl-probe-perl-doc": { + "versions": { + "0.03-r0": 1545061087 + }, + "origin": "perl-probe-perl" + }, + "libee": { + "versions": { + "0.4.1-r0": 1545292595 + }, + "origin": "libee", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libestr.so.0" + ], + "provides": [ + "so:libee.so.0=0.0.0", + "cmd:libee-convert" + ] + }, + "xorgproto": { + "versions": { + "2018.4-r0": 1542822442 + }, + "origin": "xorgproto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "bigreqsproto=2018.4-r0", + "compositeproto=2018.4-r0", + "damageproto=2018.4-r0", + "dri2proto=2018.4-r0", + "dri3proto=2018.4-r0", + "fixesproto=2018.4-r0", + "fontsproto=2018.4-r0", + "glproto=2018.4-r0", + "inputproto=2018.4-r0", + "kbproto=2018.4-r0", + "presentproto=2018.4-r0", + "printproto=2018.4-r0", + "randrproto=2018.4-r0", + "recordproto=2018.4-r0", + "renderproto=2018.4-r0", + "resourceproto=2018.4-r0", + "scrnsaverproto=2018.4-r0", + "videoproto=2018.4-r0", + "xcmiscproto=2018.4-r0", + "xextproto=2018.4-r0", + "xf86bigfontproto=2018.4-r0", + "xf86dgaproto=2018.4-r0", + "xf86driproto=2018.4-r0", + "xf86miscproto=2018.4-r0", + "xf86vidmodeproto=2018.4-r0", + "xineramaproto=2018.4-r0", + "xproto=2018.4-r0", + "pc:bigreqsproto=1.1.2", + "pc:compositeproto=0.4.2", + "pc:damageproto=1.2.1", + "pc:dmxproto=2.3.1", + "pc:dri2proto=2.8", + "pc:dri3proto=1.2", + "pc:evieproto=1.1.1", + "pc:fixesproto=5.0", + "pc:fontcacheproto=0.1.3", + "pc:fontsproto=2.1.3", + "pc:glproto=1.4.17", + "pc:inputproto=2.3.2", + "pc:kbproto=1.0.7", + "pc:lg3dproto=5.0", + "pc:presentproto=1.2", + "pc:printproto=1.0.5", + "pc:randrproto=1.6.0", + "pc:recordproto=1.14.2", + "pc:renderproto=0.11.1", + "pc:resourceproto=1.2.0", + "pc:scrnsaverproto=1.2.2", + "pc:trapproto=3.4.3", + "pc:videoproto=2.3.3", + "pc:xcalibrateproto=0.1.0", + "pc:xcmiscproto=1.2.2", + "pc:xextproto=7.3.0", + "pc:xf86bigfontproto=1.2.0", + "pc:xf86dgaproto=2.1", + "pc:xf86driproto=2.1.1", + "pc:xf86miscproto=0.9.3", + "pc:xf86rushproto=1.2.2", + "pc:xf86vidmodeproto=2.3.1", + "pc:xineramaproto=1.2.1", + "pc:xproto=7.0.32", + "pc:xproxymngproto=1.0.3" + ] + }, + "wget": { + "versions": { + "1.20.3-r0": 1554718236 + }, + "origin": "wget", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:wget" + ] + }, + "freeswitch-freetdm": { + "versions": { + "1.8.2-r1": 1545117892 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexecinfo.so.1", + "so:libfreeswitch.so.1", + "so:libpri.so.1.4" + ], + "provides": [ + "so:libfreetdm.so.1=1.0.0" + ] + }, + "py-asn1crypto": { + "versions": { + "0.24.0-r0": 1543246741 + }, + "origin": "py-asn1crypto" + }, + "xf86-video-openchrome": { + "versions": { + "0.6.0-r4": 1545249767 + }, + "origin": "xf86-video-openchrome", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libXvMC.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2" + ], + "provides": [ + "so:libchromeXvMC.so.1=1.0.0", + "so:libchromeXvMCPro.so.1=1.0.0" + ] + }, + "halberd-doc": { + "versions": { + "0.2.4-r1": 1545299950 + }, + "origin": "halberd" + }, + "perl-http-body": { + "versions": { + "1.17-r0": 1544001351 + }, + "origin": "perl-http-body", + "dependencies": [ + "perl", + "perl-http-message", + "perl-uri" + ] + }, + "perl-digest-hmac-doc": { + "versions": { + "1.03-r0": 1542924831 + }, + "origin": "perl-digest-hmac" + }, + "imagemagick-libs": { + "versions": { + "7.0.8.44-r0": 1557126217 + }, + "origin": "imagemagick", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:liblcms2.so.2", + "so:libltdl.so.7", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libMagickCore-7.Q16HDRI.so.6=6.0.0", + "so:libMagickWand-7.Q16HDRI.so.6=6.0.0" + ] + }, + "gparted-doc": { + "versions": { + "0.33.0-r0": 1545746178 + }, + "origin": "gparted" + }, + "ksymoops": { + "versions": { + "2.4.11-r7": 1545060803 + }, + "origin": "ksymoops", + "dependencies": [ + "so:libbfd-2.31.1.so", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ksymoops" + ] + }, + "djbdns-common": { + "versions": { + "1.05-r47": 1545208950 + }, + "origin": "djbdns", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dnsip", + "cmd:dnsqr" + ] + }, + "imagemagick": { + "versions": { + "7.0.8.44-r0": 1557126218 + }, + "origin": "imagemagick", + "dependencies": [ + "so:libMagickCore-7.Q16HDRI.so.6", + "so:libMagickWand-7.Q16HDRI.so.6", + "so:libX11.so.6", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgs.so.9", + "so:libjpeg.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpng16.so.16", + "so:librsvg-2.so.2", + "so:libtiff.so.5", + "so:libwebp.so.7", + "so:libwebpmux.so.3", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:animate", + "cmd:compare", + "cmd:composite", + "cmd:conjure", + "cmd:convert", + "cmd:display", + "cmd:identify", + "cmd:import", + "cmd:magick", + "cmd:magick-script", + "cmd:mogrify", + "cmd:montage", + "cmd:stream" + ] + }, + "qemu-ui-gtk": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libepoxy.so.0", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libvte-2.91.so.0" + ] + }, + "perl-device-serialport": { + "versions": { + "1.04-r10": 1545062312 + }, + "origin": "perl-device-serialport", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:modemtest" + ] + }, + "gsm-tools": { + "versions": { + "1.0.18-r0": 1543927819 + }, + "origin": "gsm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgsm.so.1" + ], + "provides": [ + "cmd:tcat", + "cmd:toast", + "cmd:untoast" + ] + }, + "perl-crypt-eksblowfish": { + "versions": { + "0.009-r4": 1545213647 + }, + "origin": "perl-crypt-eksblowfish", + "dependencies": [ + "perl-class-mix", + "so:libc.musl-x86_64.so.1" + ] + }, + "libnfsidmap-ldap": { + "versions": { + "2.3.2-r1": 1543933423 + }, + "origin": "nfs-utils", + "dependencies": [ + "rpcbind", + "so:libc.musl-x86_64.so.1", + "so:libldap-2.4.so.2" + ] + }, + "partimage": { + "versions": { + "0.6.9-r6": 1543924736 + }, + "origin": "partimage", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libnewt.so.0.52", + "so:libslang.so.2", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:partimage", + "cmd:partimaged" + ] + }, + "freeswitch-dbg": { + "versions": { + "1.8.2-r1": 1545117891 + }, + "origin": "freeswitch" + }, + "libdnet-dev": { + "versions": { + "1.12-r7": 1545076672 + }, + "origin": "libdnet", + "dependencies": [ + "libdnet=1.12-r7" + ], + "provides": [ + "cmd:dnet-config" + ] + }, + "ngrep-dbg": { + "versions": { + "1.45-r4": 1543242182 + }, + "origin": "ngrep" + }, + "hunspell-en": { + "versions": { + "2018.04.16-r0": 1543928313 + }, + "origin": "hunspell-en" + }, + "lxc-doc": { + "versions": { + "3.1.0-r1": 1549966161 + }, + "origin": "lxc" + }, + "xvinfo-doc": { + "versions": { + "1.1.3-r0": 1545301889 + }, + "origin": "xvinfo" + }, + "libhistory": { + "versions": { + "7.0.003-r1": 1542301065 + }, + "origin": "readline", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhistory.so.7=7.0" + ] + }, + "libdvdread": { + "versions": { + "6.0.0-r0": 1545838475 + }, + "origin": "libdvdread", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdvdcss.so.2" + ], + "provides": [ + "so:libdvdread.so.4=4.2.0" + ] + }, + "http-parser-dev": { + "versions": { + "2.8.1-r0": 1543934665 + }, + "origin": "http-parser", + "dependencies": [ + "http-parser=2.8.1-r0" + ] + }, + "debootstrap-doc": { + "versions": { + "1.0.111-r0": 1545257151 + }, + "origin": "debootstrap" + }, + "perl-class-tiny-doc": { + "versions": { + "1.006-r0": 1543076768 + }, + "origin": "perl-class-tiny" + }, + "m4-doc": { + "versions": { + "1.4.18-r1": 1542300481 + }, + "origin": "m4" + }, + "icu-dev": { + "versions": { + "62.1-r0": 1542823973 + }, + "origin": "icu", + "dependencies": [ + "icu=62.1-r0", + "icu-libs=62.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:icu-i18n=62.1", + "pc:icu-io=62.1", + "pc:icu-uc=62.1", + "cmd:icu-config" + ] + }, + "monit": { + "versions": { + "5.25.2-r0": 1545858142 + }, + "origin": "monit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:monit" + ] + }, + "device-mapper-libs": { + "versions": { + "2.02.182-r0": 1543928934 + }, + "origin": "lvm2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdevmapper.so.1.02=1.02" + ] + }, + "squid-lang-zh": { + "versions": { + "4.4-r1": 1545216331 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "autoconf": { + "versions": { + "2.69-r2": 1542301300 + }, + "origin": "autoconf", + "dependencies": [ + "m4", + "perl" + ], + "provides": [ + "cmd:autoconf", + "cmd:autoheader", + "cmd:autom4te", + "cmd:autoreconf", + "cmd:autoscan", + "cmd:autoupdate", + "cmd:ifnames" + ] + }, + "perl-data-page": { + "versions": { + "2.02-r1": 1544000370 + }, + "origin": "perl-data-page", + "dependencies": [ + "perl-class-accessor-chained", + "perl-test-exception" + ] + }, + "ckbcomp-doc": { + "versions": { + "1.187-r0": 1545292994 + }, + "origin": "ckbcomp" + }, + "myrepos-doc": { + "versions": { + "1.20180726-r0": 1542893508 + }, + "origin": "myrepos" + }, + "zd1211-firmware": { + "versions": { + "1.5-r0": 1544799019 + }, + "origin": "zd1211-firmware" + }, + "ferm": { + "versions": { + "2.4.1-r2": 1545292992 + }, + "origin": "ferm", + "dependencies": [ + "perl", + "iptables" + ], + "provides": [ + "cmd:ferm", + "cmd:import-ferm" + ] + }, + "drbd-utils-pacemaker": { + "versions": { + "9.7.1-r0": 1545746125 + }, + "origin": "drbd-utils", + "dependencies": [ + "drbd-utils=9.7.1-r0" + ] + }, + "py3-idna": { + "versions": { + "2.7-r0": 1542825008 + }, + "origin": "py-idna", + "dependencies": [ + "python3" + ] + }, + "chrony-doc": { + "versions": { + "3.4-r1": 1545841267 + }, + "origin": "chrony" + }, + "py-paramiko-demos": { + "versions": { + "2.4.2-r0": 1545216486 + }, + "origin": "py-paramiko", + "dependencies": [ + "py-asn1", + "py-cryptography", + "py-bcrypt", + "py-pynacl" + ] + }, + "perl-devel-stacktrace-ashtml": { + "versions": { + "0.15-r0": 1544792342 + }, + "origin": "perl-devel-stacktrace-ashtml", + "dependencies": [ + "perl", + "perl-devel-stacktrace" + ] + }, + "perl-fcgi-procmanager-doc": { + "versions": { + "0.28-r0": 1543924483 + }, + "origin": "perl-fcgi-procmanager" + }, + "uwsgi-transformation_tofile": { + "versions": { + "2.0.17.1-r0": 1545062213 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "py3-lxc": { + "versions": { + "3.0.3-r0": 1546416509 + }, + "origin": "py3-lxc", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:liblxc.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "avahi-compat-howl": { + "versions": { + "0.7-r1": 1543925311 + }, + "origin": "avahi", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhowl.so.0=0.0.0" + ] + }, + "gnupg": { + "versions": { + "2.2.12-r0": 1545746222 + }, + "origin": "gnupg", + "dependencies": [ + "pinentry", + "so:libassuan.so.0", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libgnutls.so.30", + "so:libgpg-error.so.0", + "so:libksba.so.8", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libnpth.so.0", + "so:libsqlite3.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:addgnupghome", + "cmd:applygnupgdefaults", + "cmd:dirmngr", + "cmd:dirmngr-client", + "cmd:gpg", + "cmd:gpg-agent", + "cmd:gpg-connect-agent", + "cmd:gpg-wks-server", + "cmd:gpg-zip", + "cmd:gpg2", + "cmd:gpgconf", + "cmd:gpgparsemail", + "cmd:gpgscm", + "cmd:gpgsm", + "cmd:gpgtar", + "cmd:gpgv", + "cmd:gpgv2", + "cmd:kbxutil", + "cmd:watchgnupg" + ] + }, + "unixodbc-dev": { + "versions": { + "2.3.7-r0": 1542845421 + }, + "origin": "unixodbc", + "dependencies": [ + "pkgconfig", + "unixodbc=2.3.7-r0" + ], + "provides": [ + "pc:odbc=2.3.7", + "pc:odbccr=2.3.7", + "pc:odbcinst=2.3.7" + ] + }, + "cegui06-dev": { + "versions": { + "0.6.2b-r15": 1543935898 + }, + "origin": "cegui06", + "dependencies": [ + "cegui06=0.6.2b-r15", + "pkgconfig" + ], + "provides": [ + "pc:CEGUI-OPENGL=0.6.2", + "pc:CEGUI=0.6.2" + ] + }, + "perl-convert-color-doc": { + "versions": { + "0.11-r0": 1545062507 + }, + "origin": "perl-convert-color" + }, + "xcb-util-keysyms-dev": { + "versions": { + "0.4.0-r1": 1543927910 + }, + "origin": "xcb-util-keysyms", + "dependencies": [ + "xcb-util-dev", + "pkgconfig", + "xcb-util-keysyms=0.4.0-r1" + ], + "provides": [ + "pc:xcb-keysyms=0.4.0" + ] + }, + "ferm-doc": { + "versions": { + "2.4.1-r2": 1545292991 + }, + "origin": "ferm" + }, + "qemu-block-curl": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libglib-2.0.so.0" + ] + }, + "libestr-dev": { + "versions": { + "0.1.10-r0": 1545060618 + }, + "origin": "libestr", + "dependencies": [ + "libestr=0.1.10-r0", + "pkgconfig" + ], + "provides": [ + "pc:libestr=0.1.10" + ] + }, + "samba-client": { + "versions": { + "4.8.11-r1": 1555334973 + }, + "origin": "samba", + "dependencies": [ + "samba-common=4.8.11-r1", + "so:libCHARSET3-samba4.so", + "so:libaddns-samba4.so", + "so:libarchive.so.13", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcli-spoolss-samba4.so", + "so:libcliauth-samba4.so", + "so:libcmdline-contexts-samba4.so", + "so:libcmdline-credentials-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libevents-samba4.so", + "so:libformw.so.6", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libkrb5-samba4.so.26", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:liblibcli-lsa3-samba4.so", + "so:liblibcli-netlogon3-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmsrpc3-samba4.so", + "so:libncursesw.so.6", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libpanelw.so.6", + "so:libpopt-samba3-cmdline-samba4.so", + "so:libpopt-samba3-samba4.so", + "so:libpopt.so.0", + "so:libregistry-samba4.so", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb-common-samba4.so", + "so:libsecrets3-samba4.so", + "so:libserver-role-samba4.so", + "so:libsmbclient-raw-samba4.so", + "so:libsmbclient.so.0", + "so:libsmbconf.so.0", + "so:libsocket-blocking-samba4.so", + "so:libtalloc.so.2", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtrusts-util-samba4.so", + "so:libutil-cmdline-samba4.so", + "so:libutil-reg-samba4.so", + "so:libutil-tdb-samba4.so" + ], + "provides": [ + "cmd:cifsdd", + "cmd:dbwrap_tool", + "cmd:findsmb", + "cmd:mvxattr", + "cmd:nmblookup", + "cmd:oLschema2ldif", + "cmd:regdiff", + "cmd:regpatch", + "cmd:regshell", + "cmd:regtree", + "cmd:rpcclient", + "cmd:samba-regedit", + "cmd:sharesec", + "cmd:smbcacls", + "cmd:smbclient", + "cmd:smbcquotas", + "cmd:smbget", + "cmd:smbprint", + "cmd:smbspool", + "cmd:smbtar", + "cmd:smbtree" + ] + }, + "spamassassin": { + "versions": { + "3.4.2-r0": 1545061983 + }, + "origin": "spamassassin", + "dependencies": [ + "perl-mail-spamassassin", + "curl" + ], + "provides": [ + "cmd:sa-awl", + "cmd:sa-check_spamd", + "cmd:sa-learn", + "cmd:sa-update", + "cmd:spamassassin", + "cmd:spamd" + ] + }, + "findutils-doc": { + "versions": { + "4.6.0-r1": 1545073419 + }, + "origin": "findutils" + }, + "awall": { + "versions": { + "1.6.9-r0": 1548501700 + }, + "origin": "awall", + "dependencies": [ + "drill", + "ip6tables", + "ipset", + "iptables", + "lua5.2", + "lua5.2-alt-getopt", + "lua5.2-cjson", + "lua5.2-pc", + "lua5.2-posix", + "lua5.2-stringy", + "xtables-addons", + "/bin/sh" + ], + "provides": [ + "cmd:awall" + ] + }, + "ltrace-doc": { + "versions": { + "0.7.3-r1": 1544798547 + }, + "origin": "ltrace" + }, + "upower-lang": { + "versions": { + "0.99.7-r0": 1545069386 + }, + "origin": "upower" + }, + "perl-module-refresh-doc": { + "versions": { + "0.17-r1": 1545292777 + }, + "origin": "perl-module-refresh" + }, + "wayland-libs-cursor": { + "versions": { + "1.16.0-r0": 1544001083 + }, + "origin": "wayland", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libwayland-client.so.0" + ], + "provides": [ + "so:libwayland-cursor.so.0=0.0.0" + ] + }, + "polkit-doc": { + "versions": { + "0.105-r9": 1547130960 + }, + "origin": "polkit" + }, + "libnice-dbg": { + "versions": { + "0.1.14-r2": 1543928834 + }, + "origin": "libnice" + }, + "uwsgi-router_uwsgi": { + "versions": { + "2.0.17.1-r0": 1545062209 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "xhost-doc": { + "versions": { + "1.0.7-r1": 1545300591 + }, + "origin": "xhost" + }, + "libgsf-lang": { + "versions": { + "1.14.44-r0": 1543926574 + }, + "origin": "libgsf" + }, + "py3-ecdsa": { + "versions": { + "0.13-r5": 1545301307 + }, + "origin": "py-ecdsa", + "dependencies": [ + "py3-crypto" + ] + }, + "perl-javascript-minifier-xs": { + "versions": { + "0.11-r3": 1543998978 + }, + "origin": "perl-javascript-minifier-xs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "lksctp-tools": { + "versions": { + "1.0.17-r0": 1543241224 + }, + "origin": "lksctp-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsctp.so.1=1.0.17", + "cmd:checksctp", + "cmd:sctp_darn", + "cmd:sctp_status", + "cmd:sctp_test", + "cmd:withsctp" + ] + }, + "git-subtree": { + "versions": { + "2.20.1-r0": 1545214195 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0" + ] + }, + "aoetools-doc": { + "versions": { + "37-r0": 1545073928 + }, + "origin": "aoetools" + }, + "perl-heap-doc": { + "versions": { + "0.80-r1": 1545213984 + }, + "origin": "perl-heap" + }, + "linux-firmware-matrox": { + "versions": { + "20190322-r0": 1554980651 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "dillo": { + "versions": { + "3.0.5-r6": 1545302349 + }, + "origin": "dillo", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libfltk.so.1.3", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:dillo", + "cmd:dillo-install-hyphenation", + "cmd:dpid", + "cmd:dpidc" + ] + }, + "jwhois": { + "versions": { + "4.0-r4": 1545300910 + }, + "origin": "jwhois", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4" + ], + "provides": [ + "cmd:jwhois" + ] + }, + "glib-dbg": { + "versions": { + "2.58.1-r2": 1545290824 + }, + "origin": "glib" + }, + "protobuf-dev": { + "versions": { + "3.6.1-r1": 1543931874 + }, + "origin": "protobuf", + "dependencies": [ + "zlib-dev", + "pkgconfig", + "protobuf=3.6.1-r1" + ], + "provides": [ + "pc:protobuf-lite=3.6.1", + "pc:protobuf=3.6.1" + ] + }, + "py3-cparser": { + "versions": { + "2.19-r0": 1546791396 + }, + "origin": "py-cparser", + "dependencies": [ + "python3" + ] + }, + "py3-simplejson": { + "versions": { + "3.15.0-r0": 1543998766 + }, + "origin": "py-simplejson", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "uwsgi-router_rewrite": { + "versions": { + "2.0.17.1-r0": 1545062208 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "mesa-dri-virtio": { + "versions": { + "18.1.7-r2": 1554455971 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "freetds-dev": { + "versions": { + "1.00.104-r0": 1543934550 + }, + "origin": "freetds", + "dependencies": [ + "freetds=1.00.104-r0" + ] + }, + "polkit-gnome-lang": { + "versions": { + "0.105-r1": 1544798743 + }, + "origin": "polkit-gnome" + }, + "lua5.1-rex-pcre": { + "versions": { + "2.9.0-r0": 1545209199 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ] + }, + "mupdf-doc": { + "versions": { + "1.13.0-r2": 1544000575 + }, + "origin": "mupdf" + }, + "parallel-doc": { + "versions": { + "20180622-r0": 1545154587 + }, + "origin": "parallel" + }, + "ghostscript-dev": { + "versions": { + "9.26-r2": 1554362638 + }, + "origin": "ghostscript", + "dependencies": [ + "ghostscript=9.26-r2", + "pkgconfig" + ], + "provides": [ + "pc:ijs=0.35" + ] + }, + "libdrm-doc": { + "versions": { + "2.4.96-r0": 1544791765 + }, + "origin": "libdrm" + }, + "hunspell-doc": { + "versions": { + "1.6.2-r1": 1542883764 + }, + "origin": "hunspell" + }, + "perl-path-tiny": { + "versions": { + "0.108-r0": 1543238927 + }, + "origin": "perl-path-tiny" + }, + "libdc1394-doc": { + "versions": { + "2.2.5-r1": 1545249933 + }, + "origin": "libdc1394" + }, + "lua-socket": { + "versions": { + "3.0_rc1_git20160306-r2": 1543934477 + }, + "origin": "lua-socket" + }, + "mariadb-embedded": { + "versions": { + "10.3.13-r1": 1557431735 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common=10.3.13-r1", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:liblzma.so.5", + "so:libpcre.so.1", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libmariadbd.so.19=19" + ] + }, + "elinks-doc": { + "versions": { + "0.13-r6": 1543934619 + }, + "origin": "elinks" + }, + "speex-tools": { + "versions": { + "1.2.0-r0": 1544799250 + }, + "origin": "speex", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libspeex.so.1", + "so:libspeexdsp.so.1" + ], + "provides": [ + "cmd:speexdec", + "cmd:speexenc" + ] + }, + "qemu-ppc64le": { + "versions": { + "3.1.0-r3": 1551107301 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-ppc64le" + ] + }, + "perl-locale-maketext-lexicon": { + "versions": { + "1.00-r0": 1543249752 + }, + "origin": "perl-locale-maketext-lexicon", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:xgettext.pl" + ] + }, + "perl-datetime-format-w3cdtf-doc": { + "versions": { + "0.07-r0": 1543239033 + }, + "origin": "perl-datetime-format-w3cdtf" + }, + "gc": { + "versions": { + "7.6.4-r2": 1543226893 + }, + "origin": "gc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcord.so.1=1.3.0", + "so:libgc.so.1=1.3.2" + ] + }, + "linux-firmware-dsp56k": { + "versions": { + "20190322-r0": 1554980652 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "xsetroot-doc": { + "versions": { + "1.1.2-r0": 1545069360 + }, + "origin": "xsetroot" + }, + "mdocml-soelim": { + "versions": { + "1.14.3-r0": 1542304783 + }, + "origin": "mdocml", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:soelim" + ] + }, + "perl-cps-doc": { + "versions": { + "0.18-r0": 1545289360 + }, + "origin": "perl-cps" + }, + "perl-ipc-sharelite-doc": { + "versions": { + "0.17-r2": 1543222926 + }, + "origin": "perl-ipc-sharelite" + }, + "librevenge-doc": { + "versions": { + "0.0.4-r2": 1543932070 + }, + "origin": "librevenge" + }, + "perl-test-exception-doc": { + "versions": { + "0.43-r0": 1542883362 + }, + "origin": "perl-test-exception" + }, + "libcdio-paranoia-doc": { + "versions": { + "0.94_p1-r1": 1543248403 + }, + "origin": "libcdio-paranoia" + }, + "uriparser-dev": { + "versions": { + "0.9.1-r0": 1548111967 + }, + "origin": "uriparser", + "dependencies": [ + "pkgconfig", + "uriparser=0.9.1-r0" + ], + "provides": [ + "pc:liburiparser=0.9.1" + ] + }, + "lua5.3-xml": { + "versions": { + "130610-r5": 1543998788 + }, + "origin": "lua-xml", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "mesa-dri-freedreno": { + "versions": { + "18.1.7-r2": 1554455970 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "jack": { + "versions": { + "1.9.12-r0": 1545073440 + }, + "origin": "jack", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libgcc_s.so.1", + "so:libsamplerate.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libjack.so.0=0.1.0", + "so:libjacknet.so.0=0.1.0", + "so:libjackserver.so.0=0.1.0", + "cmd:jackd" + ] + }, + "perl-yaml-tiny-doc": { + "versions": { + "1.73-r0": 1542893321 + }, + "origin": "perl-yaml-tiny" + }, + "python3": { + "versions": { + "3.6.8-r2": 1554747639 + }, + "origin": "python3", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libexpat.so.1", + "so:libffi.so.6", + "so:libgdbm.so.4", + "so:libgdbm_compat.so.4", + "so:liblzma.so.5", + "so:libncursesw.so.6", + "so:libpanelw.so.6", + "so:libreadline.so.7", + "so:libsqlite3.so.0", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "py3-pip", + "so:libpython3.6m.so.1.0=1.0", + "so:libpython3.so=0", + "cmd:2to3", + "cmd:2to3-3.6", + "cmd:easy_install-3.6", + "cmd:pip3", + "cmd:pip3.6", + "cmd:pydoc3", + "cmd:pydoc3.6", + "cmd:python3", + "cmd:python3.6", + "cmd:python3.6m", + "cmd:pyvenv", + "cmd:pyvenv-3.6" + ] + }, + "gengetopt-dev": { + "versions": { + "2.22.6-r2": 1545062092 + }, + "origin": "gengetopt", + "dependencies": [ + "gengetopt" + ] + }, + "xbitmaps": { + "versions": { + "1.1.2-r0": 1545062458 + }, + "origin": "xbitmaps", + "dependencies": [ + "util-macros" + ] + }, + "ldb": { + "versions": { + "1.3.8-r0": 1555334661 + }, + "origin": "ldb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent.so.0" + ], + "provides": [ + "so:libldb.so.1=1.3.8" + ] + }, + "libraw1394-tools": { + "versions": { + "2.1.2-r1": 1543932098 + }, + "origin": "libraw1394", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libraw1394.so.11" + ], + "provides": [ + "cmd:dumpiso", + "cmd:sendiso", + "cmd:testlibraw" + ] + }, + "netcf-libs": { + "versions": { + "0.2.8-r5": 1543935400 + }, + "origin": "netcf", + "dependencies": [ + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1", + "so:libexslt.so.0", + "so:libnl-3.so.200", + "so:libnl-route-3.so.200", + "so:libxml2.so.2", + "so:libxslt.so.1" + ], + "provides": [ + "so:libnetcf.so.1=1.4.0" + ] + }, + "lua5.2-pty": { + "versions": { + "1.2.1-r1": 1545216359 + }, + "origin": "lua-pty", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "rsyslog-gssapi": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libgssapi_krb5.so.2" + ], + "provides": [ + "rsyslog-lmgssutil=8.40.0-r3", + "rsyslog-imgssapi=8.40.0-r3", + "rsyslog-omgssapi=8.40.0-r3" + ] + }, + "perl-net-smtp-ssl-doc": { + "versions": { + "1.04-r0": 1545209112 + }, + "origin": "perl-net-smtp-ssl" + }, + "dropbear-convert": { + "versions": { + "2018.76-r2": 1545208976 + }, + "origin": "dropbear", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dropbearconvert" + ] + }, + "nfs-utils-doc": { + "versions": { + "2.3.2-r1": 1543933421 + }, + "origin": "nfs-utils" + }, + "dwm": { + "versions": { + "6.1-r2": 1545292628 + }, + "origin": "dwm", + "dependencies": [ + "dmenu", + "st", + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1" + ], + "provides": [ + "cmd:dwm" + ] + }, + "lua5.2-sql-mysql": { + "versions": { + "2.3.5-r2": 1543924400 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "syslog-ng-sql": { + "versions": { + "3.19.1-r0": 1548543151 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libdbi.so.1", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "motif-dev": { + "versions": { + "2.3.4-r2": 1545223193 + }, + "origin": "motif", + "dependencies": [ + "libx11-dev", + "libxft-dev", + "libxt-dev", + "libxpm-dev", + "libxext-dev", + "xbitmaps", + "motif=2.3.4-r2" + ] + }, + "rrdtool-cached": { + "versions": { + "1.7.0-r0": 1542924805 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:librrd.so.8" + ], + "provides": [ + "cmd:rrdcached" + ] + }, + "perl-io-multiplex-doc": { + "versions": { + "1.16-r1": 1545163452 + }, + "origin": "perl-io-multiplex" + }, + "py3-argparse": { + "versions": { + "1.4.0-r2": 1545060593 + }, + "origin": "py-argparse", + "dependencies": [ + "python3" + ] + }, + "xfsprogs-extra": { + "versions": { + "4.19.0-r1": 1546597448 + }, + "origin": "xfsprogs", + "dependencies": [ + "xfsprogs", + "python3", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:xfs_admin", + "cmd:xfs_bmap", + "cmd:xfs_copy", + "cmd:xfs_db", + "cmd:xfs_estimate", + "cmd:xfs_freeze", + "cmd:xfs_fsr", + "cmd:xfs_growfs", + "cmd:xfs_info", + "cmd:xfs_io", + "cmd:xfs_logprint", + "cmd:xfs_mdrestore", + "cmd:xfs_metadump", + "cmd:xfs_mkfile", + "cmd:xfs_ncheck", + "cmd:xfs_quota", + "cmd:xfs_rtcp", + "cmd:xfs_scrub", + "cmd:xfs_scrub_all", + "cmd:xfs_spaceman" + ] + }, + "make-doc": { + "versions": { + "4.2.1-r2": 1542302737 + }, + "origin": "make" + }, + "perl-term-table-doc": { + "versions": { + "0.012-r0": 1542972988 + }, + "origin": "perl-term-table" + }, + "help2man-doc": { + "versions": { + "1.47.8-r0": 1545745816 + }, + "origin": "help2man" + }, + "perl-locale-maketext-fuzzy-doc": { + "versions": { + "0.11-r1": 1544000295 + }, + "origin": "perl-locale-maketext-fuzzy" + }, + "py-dbus": { + "versions": { + "1.2.8-r1": 1543925185 + }, + "origin": "py-dbus", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0" + ] + }, + "expat-doc": { + "versions": { + "2.2.6-r0": 1542302776 + }, + "origin": "expat" + }, + "perl-plack": { + "versions": { + "1.0033-r0": 1544792456 + }, + "origin": "perl-plack", + "dependencies": [ + "perl-file-sharedir", + "perl-filesys-notify-simple", + "perl-devel-stacktrace", + "perl-stream-buffered", + "perl-uri", + "perl-devel-stacktrace-ashtml", + "perl-http-body", + "perl-test-tcp", + "perl-try-tiny", + "perl-apache-logformat-compiler", + "perl-hash-multivalue", + "perl-http-message" + ], + "provides": [ + "cmd:plackup" + ] + }, + "gstreamer-dev": { + "versions": { + "1.14.4-r0": 1543254159 + }, + "origin": "gstreamer", + "dependencies": [ + "libxml2-dev", + "gstreamer=1.14.4-r0", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gstreamer-1.0=1.14.4", + "pc:gstreamer-base-1.0=1.14.4", + "pc:gstreamer-check-1.0=1.14.4", + "pc:gstreamer-controller-1.0=1.14.4", + "pc:gstreamer-net-1.0=1.14.4" + ] + }, + "lua5.3-lzmq": { + "versions": { + "0.4.4-r0": 1545076585 + }, + "origin": "lua-lzmq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5", + "so:libzmq.so.5" + ] + }, + "lua5.3-hashids": { + "versions": { + "1.0.6-r1": 1545209084 + }, + "origin": "lua-hashids", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-capture-tiny": { + "versions": { + "0.48-r0": 1542893272 + }, + "origin": "perl-capture-tiny" + }, + "fftw": { + "versions": { + "3.3.8-r0": 1543926479 + }, + "origin": "fftw", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfftw3.so.3", + "so:libfftw3_threads.so.3", + "so:libfftw3f.so.3", + "so:libfftw3f_threads.so.3", + "so:libfftw3l.so.3", + "so:libfftw3l_threads.so.3" + ], + "provides": [ + "cmd:fftw-wisdom", + "cmd:fftw-wisdom-to-conf", + "cmd:fftwf-wisdom", + "cmd:fftwl-wisdom" + ] + }, + "flac-dev": { + "versions": { + "1.3.2-r2": 1545068440 + }, + "origin": "flac", + "dependencies": [ + "flac=1.3.2-r2", + "pc:ogg", + "pkgconfig" + ], + "provides": [ + "pc:flac++=1.3.2", + "pc:flac=1.3.2" + ] + }, + "acf-ppp": { + "versions": { + "0.5.0-r2": 1543999028 + }, + "origin": "acf-ppp", + "dependencies": [ + "acf-core", + "ppp" + ] + }, + "py-bluez": { + "versions": { + "0.22-r1": 1545062452 + }, + "origin": "py-bluez" + }, + "perl-dbi-doc": { + "versions": { + "1.642-r0": 1544792614 + }, + "origin": "perl-dbi" + }, + "lua5.1-filesystem": { + "versions": { + "1.7.0.2-r0": 1542820932 + }, + "origin": "lua-filesystem", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "ruby-rake": { + "versions": { + "2.5.5-r0": 1557164837 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ], + "provides": [ + "cmd:rake" + ] + }, + "py2-scandir": { + "versions": { + "1.9.0-r1": 1542824906 + }, + "origin": "py-scandir", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "libdvdnav": { + "versions": { + "5.0.3-r1": 1543999001 + }, + "origin": "libdvdnav", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdvdread.so.4" + ], + "provides": [ + "so:libdvdnav.so.4=4.2.0" + ] + }, + "perl-stream-buffered": { + "versions": { + "0.03-r0": 1544792337 + }, + "origin": "perl-stream-buffered" + }, + "lua5.2-mqtt-publish": { + "versions": { + "0.3-r0": 1542820948 + }, + "origin": "lua-mqtt-publish", + "dependencies": [ + "lua5.2-mosquitto" + ] + }, + "newt": { + "versions": { + "0.52.20-r0": 1543924705 + }, + "origin": "newt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0", + "so:libslang.so.2" + ], + "provides": [ + "so:libnewt.so.0.52=0.52.20", + "cmd:whiptail" + ] + }, + "menu-cache": { + "versions": { + "0.5.1-r2": 1543999314 + }, + "origin": "menu-cache", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libmenu-cache.so.3=3.0.1" + ] + }, + "tftp-hpa-doc": { + "versions": { + "5.2-r2": 1545207835 + }, + "origin": "tftp-hpa" + }, + "libnotify-dev": { + "versions": { + "0.7.7-r2": 1544001119 + }, + "origin": "libnotify", + "dependencies": [ + "gdk-pixbuf-dev", + "glib-dev", + "dbus-dev", + "libnotify=0.7.7-r2", + "pc:gdk-pixbuf-2.0", + "pc:gio-2.0>=2.26.0", + "pc:glib-2.0>=2.26.0", + "pkgconfig" + ], + "provides": [ + "pc:libnotify=0.7.7" + ] + }, + "py-vobject": { + "versions": { + "0.9.6.1-r0": 1545165276 + }, + "origin": "py-vobject", + "provides": [ + "cmd:change_tz", + "cmd:ics_diff" + ] + }, + "tslib": { + "versions": { + "1.18-r0": 1545859355 + }, + "origin": "tslib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libts.so.0=0.10.0", + "cmd:ts_calibrate", + "cmd:ts_conf", + "cmd:ts_finddev", + "cmd:ts_harvest", + "cmd:ts_print", + "cmd:ts_print_mt", + "cmd:ts_print_raw", + "cmd:ts_test", + "cmd:ts_test_mt", + "cmd:ts_uinput", + "cmd:ts_verify" + ] + }, + "kamailio-redis": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.14", + "so:libsrdb1.so.1" + ] + }, + "perl-mime-lite": { + "versions": { + "3.030-r1": 1543226640 + }, + "origin": "perl-mime-lite", + "dependencies": [ + "perl", + "perl-mime-types", + "perl-email-date-format", + "perl-mailtools", + "perl-test-pod-coverage" + ] + }, + "udisks2-libs": { + "versions": { + "2.6.5-r1": 1545293059 + }, + "origin": "udisks2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libudisks2.so.0=0.0.0" + ] + }, + "nagios-plugins-file_age": { + "versions": { + "2.2.1-r6": 1543933904 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "perl" + ] + }, + "putty": { + "versions": { + "0.71-r1": 1554726207 + }, + "origin": "putty", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:plink", + "cmd:pscp", + "cmd:psftp", + "cmd:puttygen" + ] + }, + "graphicsmagick-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "xauth": { + "versions": { + "1.0.10-r1": 1542973224 + }, + "origin": "xauth", + "dependencies": [ + "so:libX11.so.6", + "so:libXau.so.6", + "so:libXext.so.6", + "so:libXmuu.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xauth" + ] + }, + "py2-gnutls": { + "versions": { + "3.1.2-r0": 1545062790 + }, + "origin": "py-gnutls" + }, + "confuse-dev": { + "versions": { + "3.2.2-r0": 1545922117 + }, + "origin": "confuse", + "dependencies": [ + "confuse=3.2.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libconfuse=3.2.2" + ] + }, + "weechat-perl": { + "versions": { + "2.3-r0": 1545299933 + }, + "origin": "weechat", + "dependencies": [ + "weechat", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "nginx-mod-stream": { + "versions": { + "1.14.2-r1": 1557179820 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "mesa-glapi": { + "versions": { + "18.1.7-r2": 1554455971 + }, + "origin": "mesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libglapi.so.0=0.0.0" + ] + }, + "freeglut-doc": { + "versions": { + "3.0.0-r0": 1545291114 + }, + "origin": "freeglut" + }, + "btrfs-progs-bash-completion": { + "versions": { + "4.19.1-r0": 1545665047 + }, + "origin": "btrfs-progs" + }, + "oniguruma-dev": { + "versions": { + "6.9.1-r0": 1545858473 + }, + "origin": "oniguruma", + "dependencies": [ + "oniguruma=6.9.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:oniguruma=6.9.1", + "cmd:onig-config" + ] + }, + "zeromq-doc": { + "versions": { + "4.3.1-r0": 1548271359 + }, + "origin": "zeromq" + }, + "unionfs-fuse": { + "versions": { + "1.0-r0": 1545163920 + }, + "origin": "unionfs-fuse", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfuse.so.2" + ], + "provides": [ + "cmd:mount.unionfs", + "cmd:unionfs", + "cmd:unionfsctl" + ] + }, + "enchant-doc": { + "versions": { + "1.6.0-r13": 1542965845 + }, + "origin": "enchant" + }, + "harfbuzz-icu": { + "versions": { + "2.2.0-r0": 1545249742 + }, + "origin": "harfbuzz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libharfbuzz.so.0", + "so:libicuuc.so.62" + ], + "provides": [ + "so:libharfbuzz-icu.so.0=0.20200.0" + ] + }, + "ppp-pppoe": { + "versions": { + "2.4.7-r6": 1543999021 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pppoe-discovery" + ] + }, + "perl-http-body-doc": { + "versions": { + "1.17-r0": 1544001350 + }, + "origin": "perl-http-body" + }, + "bitlbee-openrc": { + "versions": { + "3.5.1-r4": 1543248420 + }, + "origin": "bitlbee" + }, + "orc-compiler": { + "versions": { + "0.4.28-r0": 1542883449 + }, + "origin": "orc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liborc-0.4.so.0" + ], + "provides": [ + "cmd:orcc" + ] + }, + "serf": { + "versions": { + "1.3.9-r5": 1545068687 + }, + "origin": "serf", + "dependencies": [ + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:libserf-1.so.1=1.3.0" + ] + }, + "lua-stringy": { + "versions": { + "0.5.0-r1": 1545116590 + }, + "origin": "lua-stringy" + }, + "lua5.2-lyaml": { + "versions": { + "6.2-r3": 1545076421 + }, + "origin": "lua-lyaml", + "dependencies": [ + "lua5.2", + "lua5.2-stdlib-normalize", + "so:libc.musl-x86_64.so.1", + "so:libyaml-0.so.2" + ] + }, + "py-dbus-doc": { + "versions": { + "1.2.8-r1": 1543925185 + }, + "origin": "py-dbus" + }, + "libxxf86vm": { + "versions": { + "1.1.4-r2": 1542900278 + }, + "origin": "libxxf86vm", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXxf86vm.so.1=1.0.0" + ] + }, + "qemu-s390x": { + "versions": { + "3.1.0-r3": 1551107302 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-s390x" + ] + }, + "libressl-dev": { + "versions": { + "2.7.5-r0": 1551116832 + }, + "origin": "libressl", + "dependencies": [ + "libressl2.7-libcrypto=2.7.5-r0", + "libressl2.7-libssl=2.7.5-r0", + "libressl2.7-libtls=2.7.5-r0", + "pkgconfig" + ], + "provides": [ + "pc:libressl:libcrypto=2.7.5", + "pc:libressl:libssl=2.7.5", + "pc:libressl:libtls=2.7.5", + "pc:libressl:openssl=2.7.5" + ] + }, + "ssh-getkey-ldap": { + "versions": { + "0.1.2-r0": 1544792481 + }, + "origin": "ssh-getkey-ldap", + "dependencies": [ + "lua", + "lua-ldap", + "/bin/sh" + ], + "provides": [ + "cmd:ssh-getkey-ldap" + ] + }, + "py-pep8": { + "versions": { + "1.7.1-r0": 1542902308 + }, + "origin": "py-pep8", + "dependencies": [ + "py3-pep8=1.7.1-r0" + ] + }, + "re2c-doc": { + "versions": { + "1.1.1-r0": 1542822316 + }, + "origin": "re2c" + }, + "libnfnetlink-dev": { + "versions": { + "1.0.1-r1": 1543924449 + }, + "origin": "libnfnetlink", + "dependencies": [ + "libnfnetlink=1.0.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:libnfnetlink=1.0.1" + ] + }, + "confuse": { + "versions": { + "3.2.2-r0": 1545922117 + }, + "origin": "confuse", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libconfuse.so.2=2.0.0" + ] + }, + "qemu-alpha": { + "versions": { + "3.1.0-r3": 1551107299 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-alpha" + ] + }, + "gparted": { + "versions": { + "0.33.0-r0": 1545746178 + }, + "origin": "gparted", + "dependencies": [ + "e2fsprogs", + "ntfs-3g-progs", + "so:libatkmm-1.6.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairomm-1.0.so.1", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdkmm-2.4.so.1", + "so:libglib-2.0.so.0", + "so:libglibmm-2.4.so.1", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libgtkmm-2.4.so.1", + "so:libintl.so.8", + "so:libpangomm-1.4.so.1", + "so:libparted-fs-resize.so.0", + "so:libparted.so.2", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:gparted", + "cmd:gpartedbin" + ] + }, + "perl-crypt-ssleay-doc": { + "versions": { + "0.72-r9": 1545062782 + }, + "origin": "perl-crypt-ssleay" + }, + "perl-devel-globaldestruction-doc": { + "versions": { + "0.14-r0": 1545162995 + }, + "origin": "perl-devel-globaldestruction" + }, + "nagios-plugins-load": { + "versions": { + "2.2.1-r6": 1543933907 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "snmptt-openrc": { + "versions": { + "1.4-r0": 1542924637 + }, + "origin": "snmptt", + "dependencies": [ + "perl", + "perl-config-inifiles", + "perl-list-moreutils" + ] + }, + "apr-util-dbd_sqlite3": { + "versions": { + "1.6.1-r5": 1545061010 + }, + "origin": "apr-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "perl-test-simple": { + "versions": { + "1.302141-r0": 1544792380 + }, + "origin": "perl-test-simple", + "provides": [ + "perl-test-tester" + ] + }, + "perl-digest-md5": { + "versions": { + "2.55-r2": 1544001430 + }, + "origin": "perl-digest-md5", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "orc-dev": { + "versions": { + "0.4.28-r0": 1542883449 + }, + "origin": "orc", + "dependencies": [ + "orc=0.4.28-r0", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:liborc-0.4.so.0", + "so:liborc-test-0.4.so.0" + ], + "provides": [ + "pc:orc-0.4=0.4.28", + "cmd:orc-bugreport" + ] + }, + "libattr": { + "versions": { + "2.4.47-r7": 1542301506 + }, + "origin": "attr", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libattr.so.1=1.1.0" + ] + }, + "libvdpau-dev": { + "versions": { + "1.1.1-r2": 1542900270 + }, + "origin": "libvdpau", + "dependencies": [ + "libvdpau=1.1.1-r2", + "pc:x11", + "pkgconfig" + ], + "provides": [ + "pc:vdpau=1.1.1" + ] + }, + "gnokii-lang": { + "versions": { + "0.6.31-r8": 1545300480 + }, + "origin": "gnokii" + }, + "ghostscript-doc": { + "versions": { + "9.26-r2": 1554362637 + }, + "origin": "ghostscript" + }, + "openldap-overlay-dynlist": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "net-snmp-dev": { + "versions": { + "5.8-r0": 1543923349 + }, + "origin": "net-snmp", + "dependencies": [ + "openssl-dev", + "net-snmp-agent-libs=5.8-r0", + "net-snmp-libs=5.8-r0" + ], + "provides": [ + "cmd:net-snmp-config" + ] + }, + "mksh-doc": { + "versions": { + "56c-r0": 1543925653 + }, + "origin": "mksh" + }, + "dahdi-linux-vanilla-dev": { + "versions": { + "4.19.41-r0": 1557400160 + }, + "origin": "dahdi-linux-vanilla", + "dependencies": [ + "dahdi-linux-dev" + ] + }, + "intltool": { + "versions": { + "0.51.0-r4": 1542821919 + }, + "origin": "intltool", + "dependencies": [ + "perl", + "perl-xml-parser", + "gettext", + "file" + ], + "provides": [ + "cmd:intltool-extract", + "cmd:intltool-merge", + "cmd:intltool-prepare", + "cmd:intltool-update", + "cmd:intltoolize" + ] + }, + "ircii-doc": { + "versions": { + "20111115-r4": 1545213657 + }, + "origin": "ircii" + }, + "py-jwt": { + "versions": { + "1.6.4-r0": 1543249954 + }, + "origin": "py-jwt" + }, + "lua-xctrl": { + "versions": { + "2015.04.10-r2": 1545208665 + }, + "origin": "lua-xctrl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-sys-hostname-long": { + "versions": { + "1.5-r0": 1545116817 + }, + "origin": "perl-sys-hostname-long", + "dependencies": [ + "perl" + ] + }, + "bind": { + "versions": { + "9.12.4_p1-r1": 1556801608 + }, + "origin": "bind", + "dependencies": [ + "/bin/sh", + "so:libbind9.so.1201", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.1.1", + "so:libdns.so.1208", + "so:libisc.so.1204", + "so:libisccc.so.1201", + "so:libisccfg.so.1203", + "so:libjson-c.so.4", + "so:libldap-2.4.so.2", + "so:libns.so.1207", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:arpaname", + "cmd:ddns-confgen", + "cmd:genrandom", + "cmd:isc-config.sh", + "cmd:mdig", + "cmd:named", + "cmd:named-checkconf", + "cmd:named-checkzone", + "cmd:named-compilezone", + "cmd:named-journalprint", + "cmd:named-rrchecker", + "cmd:nsec3hash", + "cmd:rndc", + "cmd:rndc-confgen", + "cmd:tsig-keygen" + ] + }, + "lxc-pam": { + "versions": { + "3.1.0-r1": 1549966161 + }, + "origin": "lxc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0" + ] + }, + "dmidecode-doc": { + "versions": { + "3.2-r0": 1543921858 + }, + "origin": "dmidecode" + }, + "qextserialport": { + "versions": { + "1.2_rc1-r0": 1545300097 + }, + "origin": "qextserialport", + "dependencies": [ + "so:libQtCore.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libqextserialport.so.1=1.2.0" + ] + }, + "openldap-overlay-constraint": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "py-gtk-dev": { + "versions": { + "2.24.0-r15": 1543927627 + }, + "origin": "py-gtk", + "dependencies": [ + "py-gobject-dev", + "py-gtk", + "pc:gtk+-2.0", + "pc:pygobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:pygtk-2.0=2.24.0" + ] + }, + "perl-net-async-http-doc": { + "versions": { + "0.42-r0": 1545209618 + }, + "origin": "perl-net-async-http" + }, + "mii-tool": { + "versions": { + "1.60_git20140218-r2": 1545216352 + }, + "origin": "net-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mii-tool" + ] + }, + "flex-doc": { + "versions": { + "2.6.4-r1": 1542300944 + }, + "origin": "flex" + }, + "jbig2dec-doc": { + "versions": { + "0.15-r0": 1545207929 + }, + "origin": "jbig2dec" + }, + "pngcrush": { + "versions": { + "1.8.13-r0": 1545209638 + }, + "origin": "pngcrush", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpng16.so.16", + "so:libz.so.1" + ], + "provides": [ + "cmd:pngcrush" + ] + }, + "cryptsetup1": { + "versions": { + "1.7.5-r4": 1545292766 + }, + "origin": "cryptsetup1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcryptsetup.so.4", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:cryptsetup", + "cmd:veritysetup" + ] + }, + "dnsmasq-dnssec": { + "versions": { + "2.80-r3": 1545223253 + }, + "origin": "dnsmasq", + "dependencies": [ + "!dnsmasq", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libhogweed.so.4", + "so:libnettle.so.6" + ], + "provides": [ + "cmd:dnsmasq" + ] + }, + "openbox-doc": { + "versions": { + "3.6.1-r2": 1545207327 + }, + "origin": "openbox" + }, + "openjade-dev": { + "versions": { + "1.3.2-r5": 1542893150 + }, + "origin": "openjade", + "dependencies": [ + "openjade-libs=1.3.2-r5" + ] + }, + "gawk": { + "versions": { + "4.2.1-r0": 1542301294 + }, + "origin": "gawk", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:awk", + "cmd:gawk", + "cmd:gawk-4.2.1" + ] + }, + "ntfs-3g-dev": { + "versions": { + "2017.3.23-r1": 1542973170 + }, + "origin": "ntfs-3g", + "dependencies": [ + "ntfs-3g-libs=2017.3.23-r1", + "pkgconfig" + ], + "provides": [ + "pc:libntfs-3g=2017.3.23" + ] + }, + "sfic": { + "versions": { + "0.1.7-r6": 1545208619 + }, + "origin": "sfic", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtdb.so.1" + ], + "provides": [ + "cmd:sfic" + ] + }, + "awstats-doc": { + "versions": { + "7.7-r0": 1545069107 + }, + "origin": "awstats" + }, + "hiredis": { + "versions": { + "0.14.0-r0": 1545837197 + }, + "origin": "hiredis", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhiredis.so.0.14=0.14" + ] + }, + "syslinux-dev": { + "versions": { + "6.04_pre1-r2": 1543935715 + }, + "origin": "syslinux" + }, + "dahdi-linux-vanilla": { + "versions": { + "4.19.41-r0": 1557400160 + }, + "origin": "dahdi-linux-vanilla", + "dependencies": [ + "dahdi-linux", + "linux-vanilla=4.19.41-r0" + ] + }, + "qemu-ui-curses": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libncursesw.so.6" + ] + }, + "libgee-dev": { + "versions": { + "0.20.1-r0": 1545060547 + }, + "origin": "libgee", + "dependencies": [ + "libgee=0.20.1-r0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gee-0.8=0.20.1" + ] + }, + "tumbler-lang": { + "versions": { + "0.2.3-r0": 1545859329 + }, + "origin": "tumbler" + }, + "lua5.3-posixtz": { + "versions": { + "0.5-r1": 1543928315 + }, + "origin": "lua-posixtz", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "pangox-compat-dev": { + "versions": { + "0.0.2-r0": 1544001258 + }, + "origin": "pangox-compat", + "dependencies": [ + "pango-dev", + "libice-dev", + "pangox-compat=0.0.2-r0", + "pc:pango", + "pkgconfig" + ], + "provides": [ + "pc:pangox=1.30" + ] + }, + "libmicrohttpd-doc": { + "versions": { + "0.9.62-r0": 1545300153 + }, + "origin": "libmicrohttpd" + }, + "perl-mail-imapclient": { + "versions": { + "3.39-r0": 1545075911 + }, + "origin": "perl-mail-imapclient", + "dependencies": [ + "perl-parse-recdescent" + ] + }, + "ipsec-tools-dev": { + "versions": { + "0.8.2-r8": 1543246797 + }, + "origin": "ipsec-tools" + }, + "kamailio-dbtext": { + "versions": { + "5.2.0-r1": 1546423169 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "kamailio-db", + "so:libc.musl-x86_64.so.1", + "so:libsrdb1.so.1" + ] + }, + "libpcre2-32": { + "versions": { + "10.32-r1": 1545069083 + }, + "origin": "pcre2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre2-32.so.0=0.7.1" + ] + }, + "bluez-firmware": { + "versions": { + "1.2-r0": 1543998829 + }, + "origin": "bluez-firmware" + }, + "acf-postfix": { + "versions": { + "0.10.0-r2": 1544798745 + }, + "origin": "acf-postfix", + "dependencies": [ + "acf-core", + "postfix" + ] + }, + "uwsgi-alarm_curl": { + "versions": { + "2.0.17.1-r0": 1545062196 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "acpi-doc": { + "versions": { + "1.7-r2": 1545300419 + }, + "origin": "acpi" + }, + "libtasn1": { + "versions": { + "4.13-r0": 1542824446 + }, + "origin": "libtasn1", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libtasn1.so.6=6.5.5", + "cmd:asn1Coding", + "cmd:asn1Decoding", + "cmd:asn1Parser" + ] + }, + "ansible-doc": { + "versions": { + "2.7.0-r1": 1545223230 + }, + "origin": "ansible" + }, + "freeswitch-sangoma": { + "versions": { + "1.8.2-r1": 1545117892 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreeswitch.so.1", + "so:libsngtc_node.so" + ] + }, + "acf-kamailio": { + "versions": { + "0.10.0-r2": 1543924389 + }, + "origin": "acf-kamailio", + "dependencies": [ + "acf-core", + "kamailio", + "acf-db-lib" + ] + }, + "xl2tpd": { + "versions": { + "1.3.10.1-r0": 1545300583 + }, + "origin": "xl2tpd", + "dependencies": [ + "ppp-l2tp", + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:pfc", + "cmd:xl2tpd", + "cmd:xl2tpd-control" + ] + }, + "lua5.3-graphviz": { + "versions": { + "2.40.1-r1": 1543926741 + }, + "origin": "graphviz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcgraph.so.6", + "so:libgvc.so.6", + "so:liblua-5.3.so.0" + ], + "provides": [ + "lua-graphviz=2.40.1-r1" + ] + }, + "openldap-overlay-seqmod": { + "versions": { + "2.4.47-r2": 1546016480 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libldap_r-2.4.so.2" + ] + }, + "perl-config-grammar": { + "versions": { + "1.12-r0": 1543999452 + }, + "origin": "perl-config-grammar" + }, + "tumbler-dev": { + "versions": { + "0.2.3-r0": 1545859329 + }, + "origin": "tumbler", + "dependencies": [ + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pkgconfig", + "tumbler=0.2.3-r0" + ], + "provides": [ + "pc:tumbler-1=0.2.3" + ] + }, + "perl-locale-maketext-lexicon-doc": { + "versions": { + "1.00-r0": 1543249751 + }, + "origin": "perl-locale-maketext-lexicon" + }, + "xscreensaver": { + "versions": { + "5.40-r0": 1545224089 + }, + "origin": "xscreensaver", + "dependencies": [ + "bc", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXinerama.so.1", + "so:libXmu.so.6", + "so:libXrandr.so.2", + "so:libXt.so.6", + "so:libXxf86vm.so.1", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgdk_pixbuf_xlib-2.0.so.0", + "so:libglade-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:xscreensaver", + "cmd:xscreensaver-command", + "cmd:xscreensaver-demo", + "cmd:xscreensaver-getimage", + "cmd:xscreensaver-getimage-file", + "cmd:xscreensaver-getimage-video", + "cmd:xscreensaver-text" + ] + }, + "pcsc-lite-dev": { + "versions": { + "1.8.24-r1": 1545060761 + }, + "origin": "pcsc-lite", + "dependencies": [ + "eudev-dev", + "pcsc-lite-libs=1.8.24-r1", + "pkgconfig" + ], + "provides": [ + "pc:libpcsclite=1.8.24" + ] + }, + "libsndfile-dev": { + "versions": { + "1.0.28-r8": 1555066604 + }, + "origin": "libsndfile", + "dependencies": [ + "flac-dev", + "libvorbis-dev", + "libogg-dev", + "libsndfile=1.0.28-r8", + "pkgconfig" + ], + "provides": [ + "pc:sndfile=1.0.28" + ] + }, + "librsvg-dev": { + "versions": { + "2.40.20-r0": 1543926594 + }, + "origin": "librsvg", + "dependencies": [ + "gtk+2.0-dev", + "libcroco-dev", + "libgsf-dev", + "librsvg=2.40.20-r0", + "pc:cairo", + "pc:gdk-pixbuf-2.0", + "pc:gio-2.0", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:librsvg-2.0=2.40.20" + ] + }, + "acf-lvm2": { + "versions": { + "0.7.0-r2": 1543999366 + }, + "origin": "acf-lvm2", + "dependencies": [ + "acf-core", + "lvm2" + ] + }, + "vlan": { + "versions": { + "2.2-r0": 1545062235 + }, + "origin": "vlan" + }, + "squid-lang-pl": { + "versions": { + "4.4-r1": 1545216328 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "gnome-vfs-doc": { + "versions": { + "2.24.4-r6": 1545299690 + }, + "origin": "gnome-vfs" + }, + "perl-html-mason": { + "versions": { + "1.58-r0": 1545229451 + }, + "origin": "perl-html-mason", + "dependencies": [ + "perl-cgi", + "perl-cache-cache", + "perl-log-any", + "perl-html-parser", + "perl-class-container", + "perl-params-validate", + "perl-exception-class" + ], + "provides": [ + "cmd:convert0.6.README", + "cmd:convert0.6.pl", + "cmd:convert0.8.README", + "cmd:convert0.8.pl", + "cmd:mason.pl" + ] + }, + "alpine-sdk": { + "versions": { + "1.0-r0": 1545214216 + }, + "origin": "alpine-sdk", + "dependencies": [ + "abuild", + "build-base", + "git" + ] + }, + "perl-html-formattext-withlinks": { + "versions": { + "0.15-r0": 1545075967 + }, + "origin": "perl-html-formattext-withlinks", + "dependencies": [ + "perl-html-format", + "perl-uri", + "perl-html-tree" + ] + }, + "py-sphinxcontrib-websupport": { + "versions": { + "1.0.1-r3": 1542825040 + }, + "origin": "py-sphinxcontrib-websupport" + }, + "xf86-input-evdev": { + "versions": { + "2.10.6-r0": 1545229729 + }, + "origin": "xf86-input-evdev", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libevdev.so.2", + "so:libmtdev.so.1", + "so:libudev.so.1" + ] + }, + "libcdio-paranoia": { + "versions": { + "0.94_p1-r1": 1543248404 + }, + "origin": "libcdio-paranoia", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcdio.so.16" + ], + "provides": [ + "so:libcdio_cdda.so.2=2.0.0", + "so:libcdio_paranoia.so.2=2.0.0", + "cmd:cd-paranoia" + ] + }, + "xen": { + "versions": { + "4.11.1-r1": 1545075897 + }, + "origin": "xen", + "dependencies": [ + "bash", + "iproute2", + "logrotate", + "syslinux", + "so:libaio.so.1", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libfsimage.so.1.0", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libpci.so.3", + "so:libpixman-1.so.0", + "so:libpython2.7.so.1.0", + "so:libspice-server.so.1", + "so:libssh2.so.1", + "so:libxenctrl.so.4.11", + "so:libxendevicemodel.so.1", + "so:libxenevtchn.so.1", + "so:libxenforeignmemory.so.1", + "so:libxengnttab.so.1", + "so:libxenguest.so.4.11", + "so:libxenlight.so.4.11", + "so:libxenstat.so.0", + "so:libxenstore.so.3.0", + "so:libxentoollog.so.1", + "so:libxlutil.so.4.11", + "so:libyajl.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:flask-get-bool", + "cmd:flask-getenforce", + "cmd:flask-label-pci", + "cmd:flask-loadpolicy", + "cmd:flask-set-bool", + "cmd:flask-setenforce", + "cmd:gdbsx", + "cmd:kdd", + "cmd:qemu-img-xen", + "cmd:qemu-nbd-xen", + "cmd:xen-bugtool", + "cmd:xen-cpuid", + "cmd:xen-detect", + "cmd:xen-diag", + "cmd:xen-hptool", + "cmd:xen-hvmcrash", + "cmd:xen-hvmctx", + "cmd:xen-livepatch", + "cmd:xen-lowmemd", + "cmd:xen-mfndump", + "cmd:xen-ringwatch", + "cmd:xen-tmem-list-parse", + "cmd:xenalyze", + "cmd:xenbaked", + "cmd:xencons", + "cmd:xenconsoled", + "cmd:xencov", + "cmd:xencov_split", + "cmd:xenlockprof", + "cmd:xenmon.py", + "cmd:xenperf", + "cmd:xenpm", + "cmd:xenpmd", + "cmd:xenstore", + "cmd:xenstore-chmod", + "cmd:xenstore-control", + "cmd:xenstore-exists", + "cmd:xenstore-list", + "cmd:xenstore-ls", + "cmd:xenstore-read", + "cmd:xenstore-rm", + "cmd:xenstore-watch", + "cmd:xenstore-write", + "cmd:xenstored", + "cmd:xentop", + "cmd:xentrace", + "cmd:xentrace_format", + "cmd:xentrace_setmask", + "cmd:xentrace_setsize", + "cmd:xenwatchdogd", + "cmd:xl" + ] + }, + "py-gobject3": { + "versions": { + "3.28.2-r0": 1543925246 + }, + "origin": "py-gobject3" + }, + "serf-dev": { + "versions": { + "1.3.9-r5": 1545068687 + }, + "origin": "serf", + "dependencies": [ + "pc:libcrypto", + "pc:libssl", + "pkgconfig", + "serf=1.3.9-r5" + ], + "provides": [ + "pc:serf-1=1.3.9" + ] + }, + "py-attrs": { + "versions": { + "18.2.0-r0": 1542824881 + }, + "origin": "py-attrs" + }, + "py-rsa": { + "versions": { + "3.4.2-r2": 1545116598 + }, + "origin": "py-rsa", + "dependencies": [ + "py-asn1" + ] + }, + "twm": { + "versions": { + "1.0.10-r0": 1545209633 + }, + "origin": "twm", + "dependencies": [ + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXmu.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:twm" + ] + }, + "lua-penlight": { + "versions": { + "1.5.4-r2": 1543223291 + }, + "origin": "lua-penlight", + "dependencies": [ + "lua", + "lua-filesystem" + ], + "provides": [ + "lua-penlight-shared=1.5.4-r2", + "lua5.1-penlight=1.5.4-r2", + "lua5.2-penlight=1.5.4-r2", + "lua5.3-penlight=1.5.4-r2" + ] + }, + "libechonest-dev": { + "versions": { + "2.3.1-r0": 1545209019 + }, + "origin": "libechonest", + "dependencies": [ + "qjson-dev", + "qt-dev", + "libechonest=2.3.1-r0", + "pc:QJson", + "pc:QtCore", + "pc:QtNetwork", + "pkgconfig" + ], + "provides": [ + "pc:libechonest=2.3.1" + ] + }, + "postgresql-plperl-contrib": { + "versions": { + "11.2-r0": 1554274176 + }, + "origin": "postgresql", + "dependencies": [ + "postgresql-plperl", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "acf-openssl": { + "versions": { + "0.10.1-r3": 1543254043 + }, + "origin": "acf-openssl", + "dependencies": [ + "acf-core", + "libressl" + ] + }, + "iptables": { + "versions": { + "1.6.2-r1": 1545062497 + }, + "origin": "iptables", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0", + "so:libnftnl.so.7" + ], + "provides": [ + "so:libip4tc.so.0=0.1.0", + "so:libip6tc.so.0=0.1.0", + "so:libipq.so.0=0.0.0", + "so:libiptc.so.0=0.0.0", + "so:libxtables.so.12=12.0.0", + "cmd:arptables-compat", + "cmd:ebtables-compat", + "cmd:iptables", + "cmd:iptables-compat", + "cmd:iptables-compat-restore", + "cmd:iptables-compat-save", + "cmd:iptables-restore", + "cmd:iptables-restore-translate", + "cmd:iptables-save", + "cmd:iptables-translate", + "cmd:xtables-compat-multi", + "cmd:xtables-multi" + ] + }, + "libxv": { + "versions": { + "1.0.11-r2": 1542900291 + }, + "origin": "libxv", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXv.so.1=1.0.0" + ] + }, + "dhcpcd-doc": { + "versions": { + "7.0.8-r0": 1545207907 + }, + "origin": "dhcpcd" + }, + "sdl-doc": { + "versions": { + "1.2.15-r9": 1545292836 + }, + "origin": "sdl" + }, + "libressl2.7-libcrypto": { + "versions": { + "2.7.5-r0": 1551116832 + }, + "origin": "libressl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcrypto.so.43=43.0.1" + ] + }, + "perl-role-basic": { + "versions": { + "0.13-r0": 1543925830 + }, + "origin": "perl-role-basic" + }, + "freeradius-krb5": { + "versions": { + "3.0.17-r5": 1556201989 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.17-r5", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libkrb5.so.3" + ], + "provides": [ + "freeradius3-krb5=3.0.17-r5", + "so:rlm_krb5.so=0" + ] + }, + "acf-jquery": { + "versions": { + "0.4.2-r1": 1542883772 + }, + "origin": "acf-jquery" + }, + "encodings": { + "versions": { + "1.0.4-r1": 1542924695 + }, + "origin": "encodings" + }, + "py-flask-wtf": { + "versions": { + "0.14.2-r0": 1545213683 + }, + "origin": "py-flask-wtf", + "dependencies": [ + "py-flask", + "py-wtforms" + ] + }, + "sqsh": { + "versions": { + "2.5.16.1-r2": 1545300948 + }, + "origin": "sqsh", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libct.so.4", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:sqsh" + ] + }, + "perl-path-tiny-doc": { + "versions": { + "0.108-r0": 1543238926 + }, + "origin": "perl-path-tiny" + }, + "py3-nose": { + "versions": { + "1.3.7-r2": 1544797226 + }, + "origin": "py-nose", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:nosetests", + "cmd:nosetests-3.6" + ] + }, + "gstreamer": { + "versions": { + "1.14.4-r0": 1543254160 + }, + "origin": "gstreamer", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libgstbase-1.0.so.0=0.1404.0", + "so:libgstcheck-1.0.so.0=0.1404.0", + "so:libgstcontroller-1.0.so.0=0.1404.0", + "so:libgstnet-1.0.so.0=0.1404.0", + "so:libgstreamer-1.0.so.0=0.1404.0" + ] + }, + "bzip2-doc": { + "versions": { + "1.0.6-r6": 1542300119 + }, + "origin": "bzip2" + }, + "git-doc": { + "versions": { + "2.20.1-r0": 1545214194 + }, + "origin": "git" + }, + "gdl": { + "versions": { + "3.28.0-r0": 1545215938 + }, + "origin": "gdl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgdl-3.so.5=5.0.9" + ] + }, + "loudmouth-doc": { + "versions": { + "1.5.3-r1": 1543226581 + }, + "origin": "loudmouth" + }, + "apache2-webdav": { + "versions": { + "2.4.39-r0": 1554306836 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1" + ] + }, + "linux-firmware-3com": { + "versions": { + "20190322-r0": 1554980655 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "openldap-overlay-all": { + "versions": { + "2.4.47-r2": 1546016477 + }, + "origin": "openldap", + "dependencies": [ + "openldap-overlay-accesslog", + "openldap-overlay-auditlog", + "openldap-overlay-collect", + "openldap-overlay-constraint", + "openldap-overlay-dds", + "openldap-overlay-deref", + "openldap-overlay-dyngroup", + "openldap-overlay-dynlist", + "openldap-overlay-memberof", + "openldap-overlay-ppolicy", + "openldap-overlay-proxycache", + "openldap-overlay-refint", + "openldap-overlay-retcode", + "openldap-overlay-rwm", + "openldap-overlay-seqmod", + "openldap-overlay-sssvlv", + "openldap-overlay-syncprov", + "openldap-overlay-translucent", + "openldap-overlay-unique", + "openldap-overlay-valsort" + ] + }, + "libtls-standalone": { + "versions": { + "2.7.4-r6": 1546784623 + }, + "origin": "libtls-standalone", + "dependencies": [ + "ca-certificates-cacert", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libtls-standalone.so.1=1.0.0" + ] + }, + "py-roman": { + "versions": { + "2.0.0-r3": 1543998690 + }, + "origin": "py-roman" + }, + "py-pathlib2": { + "versions": { + "2.3.2-r0": 1542824912 + }, + "origin": "py-pathlib2", + "dependencies": [ + "py-six", + "py-scandir" + ] + }, + "gtkspell-dev": { + "versions": { + "2.0.16-r7": 1545215214 + }, + "origin": "gtkspell", + "dependencies": [ + "gtkspell=2.0.16-r7", + "pc:gtk+-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gtkspell-2.0=2.0.16" + ] + }, + "mt-st-doc": { + "versions": { + "1.1-r4": 1543223638 + }, + "origin": "mt-st" + }, + "nagios-plugins-mrtgtraf": { + "versions": { + "2.2.1-r6": 1543933908 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "tpaste": { + "versions": { + "0.6-r0": 1545076745 + }, + "origin": "sprunge", + "dependencies": [ + "curl" + ], + "provides": [ + "cmd:tpaste" + ] + }, + "perl-date-calc-doc": { + "versions": { + "6.4-r1": 1545060705 + }, + "origin": "perl-date-calc" + }, + "linux-firmware-mwl8k": { + "versions": { + "20190322-r0": 1554980650 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "perl-cache-simple-timedexpiry": { + "versions": { + "0.27-r1": 1544798626 + }, + "origin": "perl-cache-simple-timedexpiry", + "dependencies": [ + "perl" + ] + }, + "blkid": { + "versions": { + "2.33-r0": 1545307437 + }, + "origin": "util-linux", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:blkid" + ] + }, + "htop": { + "versions": { + "2.2.0-r0": 1545207288 + }, + "origin": "htop", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:htop" + ] + }, + "docbook2x": { + "versions": { + "0.8.8-r6": 1542893185 + }, + "origin": "docbook2x", + "dependencies": [ + "texinfo", + "openjade", + "docbook-xml", + "docbook-xsl", + "perl-xml-sax", + "libxslt", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:db2x_manxml", + "cmd:db2x_texixml", + "cmd:db2x_xsltproc", + "cmd:docbook2x-man", + "cmd:docbook2x-texi", + "cmd:sgml2xml-isoent", + "cmd:utf8trans" + ] + }, + "argon2-dev": { + "versions": { + "20171227-r1": 1543928961 + }, + "origin": "argon2", + "dependencies": [ + "argon2-libs=20171227-r1" + ] + }, + "qemu-sh4": { + "versions": { + "3.1.0-r3": 1551107302 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-sh4" + ] + }, + "lm_sensors-doc": { + "versions": { + "3.4.0-r6": 1542924817 + }, + "origin": "lm_sensors" + }, + "py2-libxml2": { + "versions": { + "2.9.9-r1": 1551105514 + }, + "origin": "libxml2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libxml2.so.2" + ] + }, + "perl-net-libidn": { + "versions": { + "0.12-r5": 1542924924 + }, + "origin": "perl-net-libidn", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libidn.so.12" + ] + }, + "figlet-doc": { + "versions": { + "2.2.5-r0": 1545216340 + }, + "origin": "figlet" + }, + "device-mapper-event-libs": { + "versions": { + "2.02.182-r0": 1543928934 + }, + "origin": "lvm2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02" + ], + "provides": [ + "so:libdevmapper-event.so.1.02=1.02" + ] + }, + "acf-heimdal": { + "versions": { + "0.6.0-r2": 1543934553 + }, + "origin": "acf-heimdal", + "dependencies": [ + "acf-core", + "heimdal" + ] + }, + "squid-lang-ja": { + "versions": { + "4.4-r1": 1545216327 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-type-tiny": { + "versions": { + "1.000006-r0": 1542973083 + }, + "origin": "perl-type-tiny", + "dependencies": [ + "perl-exporter-tiny" + ] + }, + "lua5.1-ossl": { + "versions": { + "20180708-r2": 1545076447 + }, + "origin": "lua-ossl", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "perl-class-tiny": { + "versions": { + "1.006-r0": 1543076769 + }, + "origin": "perl-class-tiny" + }, + "pango-doc": { + "versions": { + "1.42.4-r0": 1542824069 + }, + "origin": "pango" + }, + "perl-control-x10": { + "versions": { + "2.09-r1": 1545253948 + }, + "origin": "perl-control-x10", + "dependencies": [ + "perl" + ] + }, + "perl-compress-raw-bzip2": { + "versions": { + "2.084-r0": 1546944093 + }, + "origin": "perl-compress-raw-bzip2", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "freeswitch-perl": { + "versions": { + "1.8.2-r1": 1545117893 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreeswitch.so.1", + "so:libgcc_s.so.1", + "so:libperl.so", + "so:libstdc++.so.6" + ] + }, + "perl-file-sharedir": { + "versions": { + "1.116-r0": 1543238884 + }, + "origin": "perl-file-sharedir", + "dependencies": [ + "perl", + "perl-class-inspector" + ] + }, + "libnetfilter_log-dev": { + "versions": { + "1.0.1-r2": 1543924455 + }, + "origin": "libnetfilter_log", + "dependencies": [ + "linux-headers", + "libnetfilter_log=1.0.1-r2", + "pc:libnfnetlink", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_log=1.0.1" + ] + }, + "lua5.2-dbi-mysql": { + "versions": { + "0.6-r3": 1545214221 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "startup-notification-dev": { + "versions": { + "0.12-r3": 1543253999 + }, + "origin": "startup-notification", + "dependencies": [ + "libsm-dev", + "xcb-util-dev", + "pkgconfig", + "startup-notification=0.12-r3" + ], + "provides": [ + "pc:libstartup-notification-1.0=0.12" + ] + }, + "freeradius-lib": { + "versions": { + "3.0.17-r5": 1556201988 + }, + "origin": "freeradius", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpcap.so.1", + "so:libssl.so.1.1", + "so:libtalloc.so.2" + ], + "provides": [ + "so:libfreeradius-dhcp.so=0", + "so:libfreeradius-eap.so=0", + "so:libfreeradius-radius.so=0", + "so:libfreeradius-server.so=0" + ] + }, + "uwsgi-router_cache": { + "versions": { + "2.0.17.1-r0": 1545062206 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "msmtp-lang": { + "versions": { + "1.8.1-r1": 1545062528 + }, + "origin": "msmtp" + }, + "termrec-doc": { + "versions": { + "0.17-r1": 1545249955 + }, + "origin": "termrec" + }, + "linux-firmware-vxge": { + "versions": { + "20190322-r0": 1554980646 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "ipfw": { + "versions": { + "3.0_git20130607-r1": 1545075363 + }, + "origin": "ipfw", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ipfw" + ] + }, + "mariadb-static": { + "versions": { + "10.3.13-r1": 1557431725 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common" + ] + }, + "gross": { + "versions": { + "1.0.2-r11": 1545062673 + }, + "origin": "gross", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2" + ], + "provides": [ + "so:grosscheck.so=0", + "cmd:gclient", + "cmd:grossd" + ] + }, + "freeswitch-perlesl": { + "versions": { + "1.8.2-r1": 1545117893 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libperl.so", + "so:libstdc++.so.6" + ] + }, + "perl-data-hexdump": { + "versions": { + "0.02-r1": 1545664906 + }, + "origin": "perl-data-hexdump", + "provides": [ + "cmd:hexdump" + ] + }, + "gconf": { + "versions": { + "3.2.6-r4": 1545075211 + }, + "origin": "gconf", + "dependencies": [ + "so:libORBit-2.so.0", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libintl.so.8", + "so:libpolkit-gobject-1.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgconf-2.so.4=4.1.5", + "cmd:gconf-merge-tree", + "cmd:gconftool-2", + "cmd:gsettings-data-convert", + "cmd:gsettings-schema-convert" + ] + }, + "lua-json4": { + "versions": { + "1.0.0-r3": 1543246771 + }, + "origin": "lua-json4", + "dependencies": [ + "lua" + ], + "provides": [ + "lua5.1-json4=1.0.0-r3", + "lua5.2-json4=1.0.0-r3", + "lua5.3-json4=1.0.0-r3" + ] + }, + "libxshmfence": { + "versions": { + "1.3-r0": 1542900235 + }, + "origin": "libxshmfence", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libxshmfence.so.1=1.0.0" + ] + }, + "freeradius-postgresql": { + "versions": { + "3.0.17-r5": 1556201988 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius-sql=3.0.17-r5", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "freeradius3-postgresql=3.0.17-r5", + "so:rlm_sql_postgresql.so=0" + ] + }, + "gummiboot-doc": { + "versions": { + "48.1-r0": 1545073320 + }, + "origin": "gummiboot" + }, + "loudmouth": { + "versions": { + "1.5.3-r1": 1543226581 + }, + "origin": "loudmouth", + "dependencies": [ + "so:libasyncns.so.0", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libgobject-2.0.so.0", + "so:libidn.so.12", + "so:libintl.so.8" + ], + "provides": [ + "so:libloudmouth-1.so.0=0.1.0" + ] + }, + "makedepend": { + "versions": { + "1.0.5-r2": 1543220550 + }, + "origin": "makedepend", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:makedepend" + ] + }, + "lua5.3-bit32": { + "versions": { + "5.3.0-r2": 1546011988 + }, + "origin": "lua-bit32", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "freeswitch-sounds-en-us-callie-32000": { + "versions": { + "1.0.16-r1": 1545235371 + }, + "origin": "freeswitch-sounds-en-us-callie-32000" + }, + "bmd-tools": { + "versions": { + "1.0.2-r0": 1545062473 + }, + "origin": "bmd-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "cmd:bmd-extractfw", + "cmd:bmd-streamer" + ] + }, + "kamailio-memcached": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libmemcached.so.11" + ] + }, + "jwhois-doc": { + "versions": { + "4.0-r4": 1545300909 + }, + "origin": "jwhois" + }, + "libsasl": { + "versions": { + "2.1.27-r1": 1550353527 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so" + ], + "provides": [ + "so:libsasl2.so.3=3.0.0" + ] + }, + "py3-atomicwrites": { + "versions": { + "1.1.5-r0": 1542824873 + }, + "origin": "py-atomicwrites", + "dependencies": [ + "python3" + ] + }, + "lockfile-progs": { + "versions": { + "0.1.18-r0": 1546267694 + }, + "origin": "lockfile-progs", + "dependencies": [ + "liblockfile", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:lockfile-check", + "cmd:lockfile-create", + "cmd:lockfile-remove", + "cmd:lockfile-touch", + "cmd:mail-lock", + "cmd:mail-touchlock", + "cmd:mail-unlock" + ] + }, + "vanessa_socket-doc": { + "versions": { + "0.0.13-r0": 1545116979 + }, + "origin": "vanessa_socket" + }, + "perl-crypt-ssleay": { + "versions": { + "0.72-r9": 1545062783 + }, + "origin": "perl-crypt-ssleay", + "dependencies": [ + "perl-path-class", + "perl-try-tiny", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "libfastjson": { + "versions": { + "0.99.8-r1": 1545060652 + }, + "origin": "libfastjson", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfastjson.so.4=4.2.0" + ] + }, + "py3-virtualenv": { + "versions": { + "15.1.0-r0": 1545066962 + }, + "origin": "py-virtualenv", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:virtualenv" + ] + }, + "udisks2-dev": { + "versions": { + "2.6.5-r1": 1545293053 + }, + "origin": "udisks2", + "dependencies": [ + "gobject-introspection-dev", + "polkit-dev", + "libatasmart-dev", + "libgudev-dev", + "acl-dev", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig", + "udisks2-libs=2.6.5-r1" + ], + "provides": [ + "pc:udisks2=2.6.5" + ] + }, + "py-urlnorm": { + "versions": { + "1.1.2-r1": 1542924622 + }, + "origin": "py-urlnorm", + "dependencies": [ + "python2" + ] + }, + "ipsec-tools": { + "versions": { + "0.8.2-r8": 1543246797 + }, + "origin": "ipsec-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ], + "provides": [ + "cmd:plainrsa-gen", + "cmd:racoon", + "cmd:racoonctl", + "cmd:setkey" + ] + }, + "perl-test-file-sharedir-doc": { + "versions": { + "1.001002-r0": 1543238931 + }, + "origin": "perl-test-file-sharedir" + }, + "gvpe-doc": { + "versions": { + "3.1-r0": 1545214040 + }, + "origin": "gvpe" + }, + "rsyslog-mmcount": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-django-oscar": { + "versions": { + "1.6.4-r0": 1543925765 + }, + "origin": "py-django-oscar", + "dependencies": [ + "py-babel", + "py-django", + "py-django-extra-views", + "py-django-haystack", + "py-django-phonenumber-field", + "py-django-sorl-thumbnail", + "py-django-tables2", + "py-django-treebeard", + "py-django-widget-tweaks", + "py-factory-boy", + "py-mock", + "py-pillow", + "py-purl", + "py-unidecode" + ] + }, + "perl-test-exception": { + "versions": { + "0.43-r0": 1542883368 + }, + "origin": "perl-test-exception", + "dependencies": [ + "perl-sub-uplevel" + ] + }, + "syslog-ng-json": { + "versions": { + "3.19.1-r0": 1548543151 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjson-c.so.4", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "nagios-plugins-rpc": { + "versions": { + "2.2.1-r6": 1543933911 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "perl" + ] + }, + "btrfs-progs": { + "versions": { + "4.19.1-r0": 1545665054 + }, + "origin": "btrfs-progs", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:liblzo2.so.2", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:btrfs", + "cmd:btrfsck", + "cmd:fsck.btrfs", + "cmd:mkfs.btrfs" + ] + }, + "rhash-libs": { + "versions": { + "1.3.6-r2": 1542820449 + }, + "origin": "rhash", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ], + "provides": [ + "so:librhash.so.0=0" + ] + }, + "py-paramiko-doc": { + "versions": { + "2.4.2-r0": 1545216485 + }, + "origin": "py-paramiko", + "dependencies": [ + "py-asn1", + "py-cryptography", + "py-bcrypt", + "py-pynacl" + ] + }, + "p11-kit-dev": { + "versions": { + "0.23.14-r0": 1544791899 + }, + "origin": "p11-kit", + "dependencies": [ + "p11-kit=0.23.14-r0", + "pkgconfig" + ], + "provides": [ + "pc:p11-kit-1=0.23.14" + ] + }, + "lua-lustache": { + "versions": { + "1.3.1-r2": 1545076397 + }, + "origin": "lua-lustache", + "dependencies": [ + "lua" + ], + "provides": [ + "lua-lustache-common=1.3.1-r2", + "lua5.1-lustache=1.3.1-r2", + "lua5.2-lustache=1.3.1-r2", + "lua5.3-lustache=1.3.1-r2" + ] + }, + "libqrencode": { + "versions": { + "4.0.2-r0": 1543226703 + }, + "origin": "libqrencode", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpng16.so.16" + ], + "provides": [ + "so:libqrencode.so.4=4.0.2", + "cmd:qrencode" + ] + }, + "libisofs": { + "versions": { + "1.4.8-r0": 1544797288 + }, + "origin": "libisofs", + "dependencies": [ + "so:libacl.so.1", + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libisofs.so.6=6.84.0" + ] + }, + "perl-dbd-pg": { + "versions": { + "3.7.4-r0": 1542972915 + }, + "origin": "perl-dbd-pg", + "dependencies": [ + "perl-dbi", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "font-alias": { + "versions": { + "1.0.3-r1": 1542924698 + }, + "origin": "font-alias" + }, + "gsl-dev": { + "versions": { + "2.5-r0": 1545117967 + }, + "origin": "gsl", + "dependencies": [ + "gsl=2.5-r0", + "pkgconfig" + ], + "provides": [ + "pc:gsl=2.5", + "cmd:gsl-config" + ] + }, + "compat-pvgrub": { + "versions": { + "1-r0": 1545165192 + }, + "origin": "compat-pvgrub", + "dependencies": [ + "/bin/sh" + ], + "provides": [ + "cmd:update-pvgrub" + ] + }, + "perl-role-tiny-doc": { + "versions": { + "2.000006-r0": 1542972958 + }, + "origin": "perl-role-tiny" + }, + "acf-ipsec-tools": { + "versions": { + "0.12.0-r2": 1543246798 + }, + "origin": "acf-ipsec-tools", + "dependencies": [ + "acf-core", + "ipsec-tools" + ] + }, + "openobex-apps": { + "versions": { + "1.7.2-r1": 1545069091 + }, + "origin": "openobex", + "dependencies": [ + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1", + "so:libopenobex.so.2" + ], + "provides": [ + "cmd:ircp", + "cmd:irobex_palm3", + "cmd:irxfer", + "cmd:obex_find", + "cmd:obex_tcp", + "cmd:obex_test" + ] + }, + "postgresql-plpython2-contrib": { + "versions": { + "11.2-r0": 1554274176 + }, + "origin": "postgresql", + "dependencies": [ + "postgresql-plpython2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "gtk+2.0-doc": { + "versions": { + "2.24.32-r1": 1543925513 + }, + "origin": "gtk+2.0" + }, + "linux-firmware-ti-keystone": { + "versions": { + "20190322-r0": 1554980647 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "apkbuild-gem-resolver": { + "versions": { + "3.3.1-r0": 1551786762 + }, + "origin": "abuild", + "dependencies": [ + "ruby", + "ruby-augeas" + ], + "provides": [ + "cmd:apkbuild-gem-resolver" + ] + }, + "openldap-back-mdb": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "libarchive": { + "versions": { + "3.3.2-r4": 1542820311 + }, + "origin": "libarchive", + "dependencies": [ + "so:libacl.so.1", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libexpat.so.1", + "so:liblz4.so.1", + "so:liblzma.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libarchive.so.13=13.3.2" + ] + }, + "minicom-doc": { + "versions": { + "2.7.1-r0": 1544799265 + }, + "origin": "minicom" + }, + "py-tox": { + "versions": { + "3.2.1-r1": 1545060603 + }, + "origin": "py-tox", + "dependencies": [ + "py-py", + "py-pluggy", + "py-argparse" + ] + }, + "socat": { + "versions": { + "1.7.3.2-r5": 1543259609 + }, + "origin": "socat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libreadline.so.7", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:filan", + "cmd:procan", + "cmd:socat" + ] + }, + "lua-penlight-doc": { + "versions": { + "1.5.4-r2": 1543223290 + }, + "origin": "lua-penlight" + }, + "perl-params-classify": { + "versions": { + "0.015-r0": 1542845689 + }, + "origin": "perl-params-classify", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "rpcbind": { + "versions": { + "0.2.4-r1": 1543933379 + }, + "origin": "rpcbind", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libtirpc.so.3" + ], + "provides": [ + "cmd:rpcbind", + "cmd:rpcinfo" + ] + }, + "libxscrnsaver-dev": { + "versions": { + "1.2.2-r3": 1543932085 + }, + "origin": "libxscrnsaver", + "dependencies": [ + "libxext-dev", + "libxscrnsaver=1.2.2-r3", + "pc:scrnsaverproto", + "pc:x11", + "pc:xext", + "pkgconfig" + ], + "provides": [ + "pc:xscrnsaver=1.2.2" + ] + }, + "python2-tests": { + "versions": { + "2.7.16-r1": 1557171398 + }, + "origin": "python2", + "provides": [ + "python-tests=2.7.16-r1" + ] + }, + "s6-dns-dev": { + "versions": { + "2.3.0.1-r0": 1545062684 + }, + "origin": "s6-dns", + "dependencies": [ + "s6-dns=2.3.0.1-r0" + ] + }, + "libxcb-doc": { + "versions": { + "1.13-r2": 1542822658 + }, + "origin": "libxcb" + }, + "eudev-doc": { + "versions": { + "3.2.7-r0": 1542845384 + }, + "origin": "eudev" + }, + "lz4": { + "versions": { + "1.8.3-r2": 1545154434 + }, + "origin": "lz4", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:lz4", + "cmd:lz4c", + "cmd:lz4cat", + "cmd:unlz4" + ] + }, + "cpufrequtils": { + "versions": { + "008-r4": 1543927815 + }, + "origin": "cpufrequtils", + "dependencies": [ + "sysfsutils", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcpufreq.so.0=0.0.0", + "cmd:cpufreq-aperf", + "cmd:cpufreq-info", + "cmd:cpufreq-set" + ] + }, + "rsyslog-dbg": { + "versions": { + "8.40.0-r3": 1548686789 + }, + "origin": "rsyslog" + }, + "libunistring": { + "versions": { + "0.9.10-r0": 1542304036 + }, + "origin": "libunistring", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libunistring.so.2=2.1.0" + ] + }, + "perl-mime-base64-doc": { + "versions": { + "3.15-r4": 1545061195 + }, + "origin": "perl-mime-base64" + }, + "libunique3-dev": { + "versions": { + "3.0.2-r0": 1543927461 + }, + "origin": "libunique3", + "dependencies": [ + "libunique3=3.0.2-r0", + "pc:gtk+-3.0", + "pkgconfig" + ], + "provides": [ + "pc:unique-3.0=3.0.2" + ] + }, + "uwsgi-syslog": { + "versions": { + "2.0.17.1-r0": 1545062212 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "xf86-video-qxl-doc": { + "versions": { + "0.1.5-r5": 1545292750 + }, + "origin": "xf86-video-qxl" + }, + "nagios-plugins-users": { + "versions": { + "2.2.1-r6": 1543933913 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "rxvt-unicode-doc": { + "versions": { + "9.22-r4": 1543254025 + }, + "origin": "rxvt-unicode" + }, + "py3-gunicorn": { + "versions": { + "19.7.1-r1": 1545214070 + }, + "origin": "py-gunicorn", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:gunicorn", + "cmd:gunicorn_paster" + ] + }, + "consolekit2-lang": { + "versions": { + "1.2.1-r1": 1545062380 + }, + "origin": "consolekit2", + "dependencies": [ + "polkit", + "eudev" + ] + }, + "lua5.1-doc": { + "versions": { + "5.1.5-r7": 1542820876 + }, + "origin": "lua5.1", + "provides": [ + "lua-doc" + ] + }, + "tarsnap": { + "versions": { + "1.0.39-r4": 1544000651 + }, + "origin": "tarsnap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:tarsnap", + "cmd:tarsnap-keygen", + "cmd:tarsnap-keymgmt", + "cmd:tarsnap-keyregen", + "cmd:tarsnap-recrypt" + ] + }, + "fltk-doc": { + "versions": { + "1.3.4-r2": 1545076700 + }, + "origin": "fltk" + }, + "check-doc": { + "versions": { + "0.12.0-r1": 1542822523 + }, + "origin": "check" + }, + "jasper-doc": { + "versions": { + "2.0.14-r0": 1543932347 + }, + "origin": "jasper" + }, + "sdl2_mixer-dev": { + "versions": { + "2.0.4-r0": 1545208849 + }, + "origin": "sdl2_mixer", + "dependencies": [ + "pc:sdl2>=2.0.7", + "pkgconfig", + "sdl2_mixer=2.0.4-r0" + ], + "provides": [ + "pc:SDL2_mixer=2.0.4" + ] + }, + "ed-doc": { + "versions": { + "1.14.2-r2": 1542304951 + }, + "origin": "ed" + }, + "nginx-mod-http-echo": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-io-doc": { + "versions": { + "1.25-r4": 1544001435 + }, + "origin": "perl-io" + }, + "uwsgi-lua": { + "versions": { + "2.0.17.1-r0": 1545062203 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:liblua-5.3.so.0" + ] + }, + "g++": { + "versions": { + "8.3.0-r0": 1554745498 + }, + "origin": "gcc", + "dependencies": [ + "libstdc++=8.3.0-r0", + "gcc=8.3.0-r0", + "libc-dev", + "libstdc++=8.3.0-r0", + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libisl.so.15", + "so:libmpc.so.3", + "so:libmpfr.so.4", + "so:libz.so.1" + ], + "provides": [ + "cmd:c++", + "cmd:g++", + "cmd:x86_64-alpine-linux-musl-c++", + "cmd:x86_64-alpine-linux-musl-g++" + ] + }, + "dvgrab": { + "versions": { + "3.5-r3": 1543999358 + }, + "origin": "dvgrab", + "dependencies": [ + "so:libavc1394.so.0", + "so:libc.musl-x86_64.so.1", + "so:libdv.so.4", + "so:libgcc_s.so.1", + "so:libiec61883.so.0", + "so:libjpeg.so.8", + "so:libraw1394.so.11", + "so:librom1394.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:dvgrab" + ] + }, + "xorg-server-dbg": { + "versions": { + "1.20.3-r1": 1543928067 + }, + "origin": "xorg-server", + "dependencies": [ + "font-misc-misc", + "font-cursor-misc", + "xkeyboard-config", + "xkbcomp", + "xinit" + ] + }, + "libxklavier-doc": { + "versions": { + "5.4-r4": 1546458613 + }, + "origin": "libxklavier" + }, + "dtach-doc": { + "versions": { + "0.9-r1": 1543254079 + }, + "origin": "dtach" + }, + "py-django-sorl-thumbnail": { + "versions": { + "12.4.1-r0": 1543925696 + }, + "origin": "py-django-sorl-thumbnail", + "dependencies": [ + "py-django", + "py-pillow" + ] + }, + "uwsgi-sslrouter": { + "versions": { + "2.0.17.1-r0": 1545062210 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "uwsgi-corerouter", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-html-mason-doc": { + "versions": { + "1.58-r0": 1545229449 + }, + "origin": "perl-html-mason" + }, + "bind-libs": { + "versions": { + "9.12.4_p1-r1": 1556801608 + }, + "origin": "bind", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libgssapi_krb5.so.2", + "so:libjson-c.so.4", + "so:libkrb5.so.3", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libbind9.so.1201=1201.0.1", + "so:libdns.so.1208=1208.0.0", + "so:libirs.so.1201=1201.0.1", + "so:libisc.so.1204=1204.1.0", + "so:libisccc.so.1201=1201.0.1", + "so:libisccfg.so.1203=1203.0.1", + "so:libns.so.1207=1207.0.0" + ] + }, + "gettext-static": { + "versions": { + "0.19.8.1-r4": 1542304412 + }, + "origin": "gettext" + }, + "postgresql-bdr-extension-doc": { + "versions": { + "1.0.2-r4": 1543223174 + }, + "origin": "postgresql-bdr-extension" + }, + "speexdsp": { + "versions": { + "1.2_rc3-r5": 1544799236 + }, + "origin": "speexdsp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libspeexdsp.so.1=1.5.0" + ] + }, + "gnutls-c++": { + "versions": { + "3.6.7-r0": 1555049965 + }, + "origin": "gnutls", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgnutlsxx.so.28=28.1.0" + ] + }, + "xinput": { + "versions": { + "1.6.2-r0": 1545235264 + }, + "origin": "xinput", + "dependencies": [ + "so:libX11.so.6", + "so:libXi.so.6", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xinput" + ] + }, + "libgnat": { + "versions": { + "8.3.0-r0": 1554745498 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libgnarl-8.so=0", + "so:libgnat-8.so=0" + ] + }, + "duplicity": { + "versions": { + "0.7.18.2-r0": 1544000473 + }, + "origin": "duplicity", + "dependencies": [ + "python2", + "py-boto", + "gnupg", + "ncftp", + "py2-fasteners", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:librsync.so.2" + ], + "provides": [ + "cmd:duplicity", + "cmd:rdiffdir" + ] + }, + "faac-dev": { + "versions": { + "1.28-r12": 1545165108 + }, + "origin": "faac", + "dependencies": [ + "faac=1.28-r12" + ] + }, + "perl-test-script": { + "versions": { + "1.25-r0": 1545061105 + }, + "origin": "perl-test-script", + "dependencies": [ + "perl-ipc-run3", + "perl-probe-perl", + "perl-test-simple" + ] + }, + "mtr": { + "versions": { + "0.92-r0": 1545163038 + }, + "origin": "mtr", + "dependencies": [ + "ncurses", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:mtr", + "cmd:mtr-packet" + ] + }, + "nginx-mod-http-geoip": { + "versions": { + "1.14.2-r1": 1557179820 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-pathtools-doc": { + "versions": { + "3.75-r0": 1545163545 + }, + "origin": "perl-pathtools" + }, + "asterisk-sounds-en": { + "versions": { + "15.7.1-r0": 1546247585 + }, + "origin": "asterisk" + }, + "perl-uri": { + "versions": { + "1.74-r0": 1542821787 + }, + "origin": "perl-uri", + "dependencies": [ + "perl" + ] + }, + "a2ps-doc": { + "versions": { + "4.14-r7": 1545209605 + }, + "origin": "a2ps" + }, + "xf86-input-vmmouse-doc": { + "versions": { + "13.1.0-r4": 1545207341 + }, + "origin": "xf86-input-vmmouse" + }, + "snappy-doc": { + "versions": { + "1.1.7-r1": 1545208921 + }, + "origin": "snappy" + }, + "gpsd-doc": { + "versions": { + "3.18.1-r1": 1549881587 + }, + "origin": "gpsd" + }, + "net-tools": { + "versions": { + "1.60_git20140218-r2": 1545216354 + }, + "origin": "net-tools", + "dependencies": [ + "mii-tool", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:arp", + "cmd:dnsdomainname", + "cmd:domainname", + "cmd:hostname", + "cmd:ifconfig", + "cmd:ipmaddr", + "cmd:iptunnel", + "cmd:nameif", + "cmd:netstat", + "cmd:nisdomainname", + "cmd:plipconfig", + "cmd:rarp", + "cmd:route", + "cmd:slattach", + "cmd:ypdomainname" + ] + }, + "scstadmin": { + "versions": { + "2.2.0-r2": 1545223236 + }, + "origin": "scstadmin", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:scstadmin" + ] + }, + "git-email": { + "versions": { + "2.20.1-r0": 1545214194 + }, + "origin": "git", + "dependencies": [ + "perl", + "perl-git=2.20.1-r0", + "perl-net-smtp-ssl", + "perl-authen-sasl", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libpcre2-8.so.0", + "so:libssl.so.1.1", + "so:libz.so.1" + ] + }, + "samba-heimdal-libs": { + "versions": { + "4.8.11-r1": 1555334974 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2" + ], + "provides": [ + "so:libasn1-samba4.so.8=8.0.0", + "so:libgssapi-samba4.so.2=2.0.0", + "so:libhcrypto-samba4.so.5=5.0.1", + "so:libheimbase-samba4.so.1=1.0.0", + "so:libheimntlm-samba4.so.1=1.0.1", + "so:libhx509-samba4.so.5=5.0.0", + "so:libkrb5-samba4.so.26=26.0.0", + "so:libroken-samba4.so.19=19.0.1", + "so:libwind-samba4.so.0=0.0.0" + ] + }, + "libexecinfo-dev": { + "versions": { + "1.1-r0": 1545117122 + }, + "origin": "libexecinfo", + "dependencies": [ + "libexecinfo=1.1-r0" + ] + }, + "perl-regexp-common": { + "versions": { + "2017060201-r0": 1543924764 + }, + "origin": "perl-regexp-common" + }, + "libxrender-dev": { + "versions": { + "0.9.10-r3": 1542822735 + }, + "origin": "libxrender", + "dependencies": [ + "libxrender=0.9.10-r3", + "pc:renderproto>=0.9", + "pc:x11", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xrender=0.9.10" + ] + }, + "lxterminal-doc": { + "versions": { + "0.3.2-r0": 1543927689 + }, + "origin": "lxterminal" + }, + "gnokii": { + "versions": { + "0.6.31-r8": 1545300480 + }, + "origin": "gnokii", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgnokii.so.7", + "so:libintl.so.8" + ], + "provides": [ + "cmd:gnokii", + "cmd:gnokiid", + "cmd:mgnokiidev" + ] + }, + "libvirt-bash-completion": { + "versions": { + "4.10.0-r1": 1547051658 + }, + "origin": "libvirt" + }, + "augeas-doc": { + "versions": { + "1.11.0-r0": 1542924581 + }, + "origin": "augeas" + }, + "kamailio-postgres": { + "versions": { + "5.2.0-r1": 1546423169 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "kamailio-db", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5", + "so:libsrdb1.so.1", + "so:libsrdb2.so.1" + ] + }, + "dovecot-sql": { + "versions": { + "2.3.6-r0": 1557134098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libdovecot.so.0" + ], + "provides": [ + "so:libdovecot-sql.so.0=0.0.0" + ] + }, + "libatomic_ops-dev": { + "versions": { + "7.6.4-r0": 1543226875 + }, + "origin": "libatomic_ops", + "dependencies": [ + "libatomic_ops=7.6.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:atomic_ops=7.6.4" + ] + }, + "xcb-util-image-dev": { + "versions": { + "0.4.0-r1": 1543927899 + }, + "origin": "xcb-util-image", + "dependencies": [ + "xcb-util-dev", + "pc:xcb-shm", + "pkgconfig", + "xcb-util-image=0.4.0-r1" + ], + "provides": [ + "pc:xcb-image=0.4.0" + ] + }, + "lua5.2-md5": { + "versions": { + "1.2-r3": 1542883861 + }, + "origin": "lua-md5", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libx11": { + "versions": { + "1.6.7-r0": 1542822727 + }, + "origin": "libx11", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libX11-xcb.so.1=1.0.0", + "so:libX11.so.6=6.3.0" + ] + }, + "ngircd": { + "versions": { + "24-r4": 1544001364 + }, + "origin": "ngircd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpam.so.0", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:ngircd" + ] + }, + "protobuf-vim": { + "versions": { + "3.6.1-r1": 1543931874 + }, + "origin": "protobuf" + }, + "gc-dev": { + "versions": { + "7.6.4-r2": 1543226891 + }, + "origin": "gc", + "dependencies": [ + "gc=7.6.4-r2", + "libgc++=7.6.4-r2", + "pkgconfig" + ], + "provides": [ + "pc:bdw-gc=7.6.4" + ] + }, + "lua5.2-stdlib": { + "versions": { + "41.2.1-r0": 1545292924 + }, + "origin": "lua-stdlib" + }, + "perl-list-someutils-doc": { + "versions": { + "0.55-r0": 1543934260 + }, + "origin": "perl-list-someutils" + }, + "libucontext-dev": { + "versions": { + "0.1.3-r0": 1545207700 + }, + "origin": "libucontext", + "dependencies": [ + "libucontext=0.1.3-r0" + ] + }, + "lua5.2-evdev": { + "versions": { + "2.2.1-r1": 1545076553 + }, + "origin": "lua-evdev", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "loudmouth-dev": { + "versions": { + "1.5.3-r1": 1543226580 + }, + "origin": "loudmouth", + "dependencies": [ + "pkgconfig", + "gnutls-dev", + "libidn-dev", + "libasyncns-dev", + "check-dev", + "autoconf", + "loudmouth=1.5.3-r1", + "pc:glib-2.0" + ], + "provides": [ + "pc:loudmouth-1.0=1.5.3" + ] + }, + "apache2-dev": { + "versions": { + "2.4.39-r0": 1554306834 + }, + "origin": "apache2", + "dependencies": [ + "perl", + "apr-util-dev" + ], + "provides": [ + "cmd:apxs" + ] + }, + "bind-doc": { + "versions": { + "9.12.4_p1-r1": 1556801607 + }, + "origin": "bind" + }, + "apk-cron": { + "versions": { + "1.0-r1": 1544798533 + }, + "origin": "apk-cron", + "dependencies": [ + "apk-tools" + ] + }, + "libinput-libs": { + "versions": { + "1.12.4-r0": 1545734494 + }, + "origin": "libinput", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libevdev.so.2", + "so:libmtdev.so.1", + "so:libudev.so.1" + ], + "provides": [ + "so:libinput.so.10=10.13.0" + ] + }, + "lua5.1-alt-getopt": { + "versions": { + "0.7-r8": 1544799002 + }, + "origin": "lua-alt-getopt" + }, + "apr-util-ldap": { + "versions": { + "1.6.1-r5": 1545061010 + }, + "origin": "apr-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libldap-2.4.so.2" + ] + }, + "bwm-ng": { + "versions": { + "0.6.1-r4": 1545073954 + }, + "origin": "bwm-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:bwm-ng" + ] + }, + "fribidi-doc": { + "versions": { + "1.0.5-r0": 1542824048 + }, + "origin": "fribidi" + }, + "orbit2-doc": { + "versions": { + "2.14.19-r4": 1543223378 + }, + "origin": "orbit2" + }, + "uwsgi-corerouter": { + "versions": { + "2.0.17.1-r0": 1545062197 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "linux-firmware-yam": { + "versions": { + "20190322-r0": 1554980646 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "perl-file-sharedir-doc": { + "versions": { + "1.116-r0": 1543238884 + }, + "origin": "perl-file-sharedir" + }, + "icu": { + "versions": { + "62.1-r0": 1542823974 + }, + "origin": "icu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libicui18n.so.62", + "so:libicuio.so.62", + "so:libicutu.so.62", + "so:libicuuc.so.62", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:derb", + "cmd:escapesrc", + "cmd:genbrk", + "cmd:genccode", + "cmd:gencfu", + "cmd:gencmn", + "cmd:gencnval", + "cmd:gendict", + "cmd:gennorm2", + "cmd:genrb", + "cmd:gensprep", + "cmd:icuinfo", + "cmd:icupkg", + "cmd:makeconv", + "cmd:pkgdata", + "cmd:uconv" + ] + }, + "kamailio-rabbitmq": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:librabbitmq.so.4", + "so:libuuid.so.1" + ] + }, + "libev-doc": { + "versions": { + "4.25-r0": 1545838253 + }, + "origin": "libev" + }, + "libgcab-dev": { + "versions": { + "0.7-r2": 1543249782 + }, + "origin": "libgcab", + "dependencies": [ + "gettext-dev", + "libgcab=0.7-r2", + "pc:gio-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libgcab-1.0=0.7" + ] + }, + "collectd-virt": { + "versions": { + "5.8.0-r3": 1546422677 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libvirt.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "collectd-libvirt" + ] + }, + "py3-bind": { + "versions": { + "9.12.4_p1-r1": 1556801608 + }, + "origin": "bind", + "dependencies": [ + "python3", + "py3-ply" + ] + }, + "cdparanoia-doc": { + "versions": { + "10.2-r7": 1543254198 + }, + "origin": "cdparanoia" + }, + "xf86-video-siliconmotion": { + "versions": { + "1.7.9-r3": 1545235258 + }, + "origin": "xf86-video-siliconmotion", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "arpon-doc": { + "versions": { + "3.0-r2": 1545224126 + }, + "origin": "arpon" + }, + "perl-extutils-pkgconfig-doc": { + "versions": { + "1.16-r1": 1543077187 + }, + "origin": "perl-extutils-pkgconfig" + }, + "dev86-doc": { + "versions": { + "0.16.21-r0": 1543935746 + }, + "origin": "dev86" + }, + "freeswitch-sample-config": { + "versions": { + "1.8.2-r1": 1545117892 + }, + "origin": "freeswitch", + "dependencies": [ + "freeswitch-timezones" + ] + }, + "libxxf86vm-doc": { + "versions": { + "1.1.4-r2": 1542900278 + }, + "origin": "libxxf86vm" + }, + "dialog-doc": { + "versions": { + "1.3.20180621-r0": 1543925865 + }, + "origin": "dialog" + }, + "nagios-plugins-snmp": { + "versions": { + "2.2.1-r6": 1543933912 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "net-snmp-tools", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.3-sql-postgres": { + "versions": { + "2.3.5-r2": 1543924404 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "xen-libs": { + "versions": { + "4.11.1-r1": 1545075896 + }, + "origin": "xen", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libext2fs.so.2", + "so:liblzma.so.5", + "so:liblzo2.so.2", + "so:libnl-3.so.200", + "so:libnl-route-3.so.200", + "so:libuuid.so.1", + "so:libyajl.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libfsimage.so.1.0=1.0.0", + "so:libxencall.so.1=1.1", + "so:libxenctrl.so.4.11=4.11.0", + "so:libxendevicemodel.so.1=1.2", + "so:libxenevtchn.so.1=1.1", + "so:libxenforeignmemory.so.1=1.3", + "so:libxengnttab.so.1=1.1", + "so:libxenguest.so.4.11=4.11.0", + "so:libxenlight.so.4.11=4.11.0", + "so:libxenstat.so.0=0.0", + "so:libxenstore.so.3.0=3.0.3", + "so:libxentoolcore.so.1=1.0", + "so:libxentoollog.so.1=1.0", + "so:libxenvchan.so.4.11=4.11.0", + "so:libxlutil.so.4.11=4.11.0" + ] + }, + "goaccess-doc": { + "versions": { + "1.2-r1": 1545116583 + }, + "origin": "goaccess" + }, + "libnetfilter_acct-dev": { + "versions": { + "1.0.3-r0": 1545209157 + }, + "origin": "libnetfilter_acct", + "dependencies": [ + "libnetfilter_acct=1.0.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_acct=1.0.3" + ] + }, + "libnjb-dev": { + "versions": { + "2.2.7-r4": 1545300189 + }, + "origin": "libnjb", + "dependencies": [ + "libusb-compat-dev", + "libnjb=2.2.7-r4", + "pkgconfig" + ], + "provides": [ + "pc:libnjb=2.2.7" + ] + }, + "pgpool": { + "versions": { + "3.7.5-r0": 1545073842 + }, + "origin": "pgpool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "so:libpcp.so.1=1.0.0", + "cmd:pcp_attach_node", + "cmd:pcp_detach_node", + "cmd:pcp_node_count", + "cmd:pcp_node_info", + "cmd:pcp_pool_status", + "cmd:pcp_proc_count", + "cmd:pcp_proc_info", + "cmd:pcp_promote_node", + "cmd:pcp_recovery_node", + "cmd:pcp_stop_pgpool", + "cmd:pcp_watchdog_info", + "cmd:pg_md5", + "cmd:pgpool", + "cmd:pgpool_setup", + "cmd:watchdog_setup" + ] + }, + "perl-list-allutils": { + "versions": { + "0.14-r0": 1545214301 + }, + "origin": "perl-list-allutils", + "dependencies": [ + "perl-list-utilsby", + "perl-list-someutils", + "perl-scalar-list-utils" + ] + }, + "fltk": { + "versions": { + "1.3.4-r2": 1545076702 + }, + "origin": "fltk", + "dependencies": [ + "so:libGL.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libfltk.so.1.3=1.3", + "so:libfltk_forms.so.1.3=1.3", + "so:libfltk_gl.so.1.3=1.3", + "so:libfltk_images.so.1.3=1.3" + ] + }, + "awstats": { + "versions": { + "7.7-r0": 1545069108 + }, + "origin": "awstats", + "dependencies": [ + "perl", + "perl-uri" + ], + "provides": [ + "cmd:awstats_buildstaticpages.pl", + "cmd:awstats_configure.pl", + "cmd:awstats_exportlib.pl", + "cmd:awstats_updateall.pl", + "cmd:geoip_generator.pl", + "cmd:logresolvemerge.pl", + "cmd:maillogconvert.pl", + "cmd:urlaliasbuilder.pl" + ] + }, + "python2-dev": { + "versions": { + "2.7.16-r1": 1557171398 + }, + "origin": "python2", + "dependencies": [ + "pkgconfig", + "python2=2.7.16-r1" + ], + "provides": [ + "python-dev=2.7.16-r1", + "pc:python-2.7=2.7", + "pc:python2=2.7", + "pc:python=2.7", + "cmd:python-config", + "cmd:python2-config", + "cmd:python2.7-config" + ] + }, + "linux-pam": { + "versions": { + "1.3.0-r0": 1542820663 + }, + "origin": "linux-pam", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpam.so.0=0.84.2", + "so:libpam_misc.so.0=0.82.1", + "so:libpamc.so.0=0.82.1", + "cmd:mkhomedir_helper", + "cmd:pam_tally", + "cmd:pam_tally2", + "cmd:pam_timestamp_check", + "cmd:unix_chkpwd", + "cmd:unix_update" + ] + }, + "openvswitch-ovn": { + "versions": { + "2.10.1-r0": 1544000345 + }, + "origin": "openvswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:ovn-controller", + "cmd:ovn-controller-vtep", + "cmd:ovn-detrace", + "cmd:ovn-docker-overlay-driver", + "cmd:ovn-docker-underlay-driver", + "cmd:ovn-nbctl", + "cmd:ovn-northd", + "cmd:ovn-sbctl", + "cmd:ovn-trace" + ] + }, + "bbsuid": { + "versions": { + "0.6-r0": 1543934485 + }, + "origin": "bbsuid", + "dependencies": [ + "busybox" + ] + }, + "py2-sphinx_rtd_theme": { + "versions": { + "0.4.2-r0": 1544791909 + }, + "origin": "py-sphinx_rtd_theme", + "dependencies": [ + "python2" + ] + }, + "py2-fasteners": { + "versions": { + "0.14.1-r0": 1544000469 + }, + "origin": "py2-fasteners", + "dependencies": [ + "python2", + "py2-monotonic", + "py2-six" + ] + }, + "mini_httpd-doc": { + "versions": { + "1.29-r2": 1544000654 + }, + "origin": "mini_httpd" + }, + "libgsasl-dev": { + "versions": { + "1.8.0-r3": 1545215966 + }, + "origin": "libgsasl", + "dependencies": [ + "pkgconfig", + "libgsasl=1.8.0-r3" + ], + "provides": [ + "pc:libgsasl=1.8.0" + ] + }, + "perl-pod-coverage": { + "versions": { + "0.23-r0": 1542845663 + }, + "origin": "perl-pod-coverage", + "dependencies": [ + "perl", + "perl-devel-symdump", + "perl-test-pod" + ], + "provides": [ + "cmd:pod_cover" + ] + }, + "py2-nose": { + "versions": { + "1.3.7-r2": 1544797223 + }, + "origin": "py-nose", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:nosetests-2.7" + ] + }, + "perl-test-tcp-doc": { + "versions": { + "2.19-r1": 1543249856 + }, + "origin": "perl-test-tcp" + }, + "socat-doc": { + "versions": { + "1.7.3.2-r5": 1543259608 + }, + "origin": "socat" + }, + "xvidcore-dev": { + "versions": { + "1.3.4-r1": 1545302181 + }, + "origin": "xvidcore", + "dependencies": [ + "xvidcore=1.3.4-r1" + ] + }, + "feh-doc": { + "versions": { + "3.1.1-r0": 1545060588 + }, + "origin": "feh" + }, + "zonenotify": { + "versions": { + "0.1-r3": 1545060560 + }, + "origin": "zonenotify", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:zonenotify" + ] + }, + "gnome-doc-utils-doc": { + "versions": { + "0.20.10-r2": 1543254073 + }, + "origin": "gnome-doc-utils" + }, + "libgssglue-doc": { + "versions": { + "0.4-r1": 1543248485 + }, + "origin": "libgssglue" + }, + "ghostscript-gtk": { + "versions": { + "9.26-r2": 1554362638 + }, + "origin": "ghostscript", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgs.so.9", + "so:libgtk-3.so.0" + ], + "provides": [ + "cmd:gsx" + ] + }, + "squid-doc": { + "versions": { + "4.4-r1": 1545216322 + }, + "origin": "squid" + }, + "qemu-system-mips64el": { + "versions": { + "3.1.0-r3": 1551107304 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libstdc++.so.6", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-mips64el" + ] + }, + "avfs-dev": { + "versions": { + "1.0.6-r0": 1545665869 + }, + "origin": "avfs", + "dependencies": [ + "avfs=1.0.6-r0" + ], + "provides": [ + "cmd:avfs-config" + ] + }, + "cracklib": { + "versions": { + "2.9.6-r0": 1545060800 + }, + "origin": "cracklib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcrack.so.2=2.9.0", + "cmd:cracklib-check", + "cmd:cracklib-format", + "cmd:cracklib-packer", + "cmd:cracklib-unpacker", + "cmd:create-cracklib-dict" + ] + }, + "freeradius-python": { + "versions": { + "3.0.17-r5": 1556201988 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.17-r5", + "python2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "freeradius3-python=3.0.17-r5", + "so:rlm_python.so=0" + ] + }, + "py2-munkres": { + "versions": { + "1.0.12-r0": 1545292843 + }, + "origin": "py-munkres", + "dependencies": [ + "python2" + ] + }, + "git-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "dpkg-dev": { + "versions": { + "1.19.2-r0": 1545820223 + }, + "origin": "dpkg", + "dependencies": [ + "perl", + "pkgconfig" + ], + "provides": [ + "pc:libdpkg=1.19.2", + "cmd:dpkg-architecture", + "cmd:dpkg-buildpackage", + "cmd:dpkg-checkbuilddeps", + "cmd:dpkg-distaddfile", + "cmd:dpkg-genchanges", + "cmd:dpkg-gencontrol", + "cmd:dpkg-gensymbols", + "cmd:dpkg-name", + "cmd:dpkg-scanpackages", + "cmd:dpkg-scansources", + "cmd:dpkg-shlibdeps", + "cmd:dpkg-source", + "cmd:dpkg-vendor" + ] + }, + "libgpg-error-doc": { + "versions": { + "1.33-r0": 1545838267 + }, + "origin": "libgpg-error" + }, + "freeswitch-sounds-pt-BR-karina-8000": { + "versions": { + "1.0.51-r1": 1544799193 + }, + "origin": "freeswitch-sounds-pt-BR-karina-8000" + }, + "py2-talloc": { + "versions": { + "2.1.14-r0": 1543220636 + }, + "origin": "talloc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libtalloc.so.2" + ], + "provides": [ + "py-talloc=2.1.14-r0", + "so:libpytalloc-util.so.2=2.1.14" + ] + }, + "a52dec-dev": { + "versions": { + "0.7.4-r7": 1545214242 + }, + "origin": "a52dec", + "dependencies": [ + "a52dec=0.7.4-r7" + ] + }, + "lua5.3-dbi-sqlite3": { + "versions": { + "0.6-r3": 1545214226 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "lua5.3-pty": { + "versions": { + "1.2.1-r1": 1545216361 + }, + "origin": "lua-pty", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "usb-modeswitch-doc": { + "versions": { + "2.5.2-r0": 1543932709 + }, + "origin": "usb-modeswitch" + }, + "perl-extutils-helpers": { + "versions": { + "0.026-r0": 1543238834 + }, + "origin": "perl-extutils-helpers" + }, + "cryptsetup": { + "versions": { + "2.0.6-r0": 1545746292 + }, + "origin": "cryptsetup", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcryptsetup.so.12", + "so:libpopt.so.0", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:cryptsetup", + "cmd:cryptsetup-reencrypt", + "cmd:integritysetup", + "cmd:veritysetup" + ] + }, + "libpq": { + "versions": { + "11.2-r0": 1554274176 + }, + "origin": "postgresql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libldap_r-2.4.so.2", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libpq.so.5=5.11" + ] + }, + "krb5-server-ldap": { + "versions": { + "1.15.5-r0": 1548094457 + }, + "origin": "krb5", + "dependencies": [ + "krb5-conf", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libgssrpc.so.4", + "so:libkadm5srv_mit.so.11", + "so:libkdb5.so.8", + "so:libkrb5.so.3", + "so:libkrb5support.so.0", + "so:libldap-2.4.so.2" + ], + "provides": [ + "so:libkdb_ldap.so.1=1.0" + ] + }, + "perl-want-doc": { + "versions": { + "0.29-r1": 1543932632 + }, + "origin": "perl-want" + }, + "rtmpdump": { + "versions": { + "2.4_git20160909-r6": 1545215000 + }, + "origin": "rtmpdump", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librtmp.so.1" + ], + "provides": [ + "cmd:rtmpdump", + "cmd:rtmpgw", + "cmd:rtmpsrv", + "cmd:rtmpsuck" + ] + }, + "xkbcomp": { + "versions": { + "1.4.2-r0": 1542973209 + }, + "origin": "xkbcomp", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libxkbfile.so.1" + ], + "provides": [ + "cmd:xkbcomp" + ] + }, + "minizip": { + "versions": { + "1.2.11-r0": 1545075940 + }, + "origin": "minizip", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libminizip.so.1=1.0.0" + ] + }, + "abuild-doc": { + "versions": { + "3.3.1-r0": 1551786762 + }, + "origin": "abuild" + }, + "tcpflow": { + "versions": { + "1.5.0-r0": 1545207692 + }, + "origin": "tcpflow", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libpcap.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:tcpflow" + ] + }, + "perl-net-http": { + "versions": { + "6.09-r1": 1542821815 + }, + "origin": "perl-net-http", + "dependencies": [ + "perl", + "perl-uri" + ] + }, + "uwsgi-graylog2": { + "versions": { + "2.0.17.1-r0": 1545062201 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ] + }, + "libxpm-dev": { + "versions": { + "3.5.12-r0": 1542894077 + }, + "origin": "libxpm", + "dependencies": [ + "libxpm=3.5.12-r0", + "pc:x11", + "pkgconfig" + ], + "provides": [ + "pc:xpm=3.5.12" + ] + }, + "xhost": { + "versions": { + "1.0.7-r1": 1545300592 + }, + "origin": "xhost", + "dependencies": [ + "so:libX11.so.6", + "so:libXmuu.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xhost" + ] + }, + "perl-dbd-sqlite": { + "versions": { + "1.62-r0": 1546944026 + }, + "origin": "perl-dbd-sqlite", + "dependencies": [ + "perl-dbi", + "so:libc.musl-x86_64.so.1" + ] + }, + "pwgen-doc": { + "versions": { + "2.08-r0": 1545290834 + }, + "origin": "pwgen" + }, + "elinks-lang": { + "versions": { + "0.13-r6": 1543934620 + }, + "origin": "elinks" + }, + "bluez-hid2hci": { + "versions": { + "5.50-r0": 1545822205 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ] + }, + "gcc-doc": { + "versions": { + "8.3.0-r0": 1554745497 + }, + "origin": "gcc" + }, + "lua5.1-lub": { + "versions": { + "1.1.0-r1": 1544000680 + }, + "origin": "lua-lub", + "dependencies": [ + "lua5.1", + "lua5.1-filesystem" + ] + }, + "avahi-glib": { + "versions": { + "0.7-r1": 1543925310 + }, + "origin": "avahi", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libavahi-glib.so.1=1.0.2", + "so:libavahi-gobject.so.0=0.0.5" + ] + }, + "libxau-doc": { + "versions": { + "1.0.8-r3": 1542822450 + }, + "origin": "libxau" + }, + "libmikmod-doc": { + "versions": { + "3.3.11.1-r0": 1545162983 + }, + "origin": "libmikmod" + }, + "libiptcdata-doc": { + "versions": { + "1.0.4-r2": 1543932145 + }, + "origin": "libiptcdata" + }, + "py2-pygments": { + "versions": { + "2.2.0-r0": 1542824941 + }, + "origin": "py-pygments", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:pygmentize-2" + ] + }, + "perl-email-address-list": { + "versions": { + "0.05-r2": 1542893363 + }, + "origin": "perl-email-address-list", + "dependencies": [ + "perl", + "perl-email-address" + ] + }, + "kbd-bkeymaps": { + "versions": { + "2.0.4-r3": 1545215186 + }, + "origin": "kbd", + "provides": [ + "bkeymaps" + ] + }, + "mini_httpd-openrc": { + "versions": { + "1.29-r2": 1544000654 + }, + "origin": "mini_httpd" + }, + "libvirt-xen": { + "versions": { + "4.10.0-r1": 1547051658 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-daemon=4.10.0-r1", + "libvirt-common-drivers=4.10.0-r1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libvirt.so.0", + "so:libxenlight.so.4.11", + "so:libxenstore.so.3.0", + "so:libxentoollog.so.1", + "so:libxlutil.so.4.11", + "so:libxml2.so.2" + ] + }, + "xdm-doc": { + "versions": { + "1.1.11-r5": 1542985407 + }, + "origin": "xdm" + }, + "s6-rc-doc": { + "versions": { + "0.4.1.0-r0": 1545076680 + }, + "origin": "s6-rc" + }, + "dovecot-ldap": { + "versions": { + "2.3.6-r0": 1557134098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libdovecot.so.0", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ], + "provides": [ + "so:libdovecot-ldap.so.0=0.0.0" + ] + }, + "xf86-input-mouse": { + "versions": { + "1.9.3-r1": 1545300645 + }, + "origin": "xf86-input-mouse", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "coova-chilli": { + "versions": { + "1.4-r2": 1545163684 + }, + "origin": "coova-chilli", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libbstring.so.0=0.0.0", + "so:libchilli.so.0=0.0.0", + "cmd:chilli", + "cmd:chilli_opt", + "cmd:chilli_query", + "cmd:chilli_radconfig", + "cmd:chilli_redir", + "cmd:chilli_response", + "cmd:chilli_script" + ] + }, + "pigz": { + "versions": { + "2.4-r0": 1542820956 + }, + "origin": "pigz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:pigz", + "cmd:unpigz" + ] + }, + "lua-sircbot": { + "versions": { + "0.4-r2": 1543924832 + }, + "origin": "sircbot" + }, + "lxdm-lang": { + "versions": { + "0.5.3-r1": 1545215913 + }, + "origin": "lxdm", + "dependencies": [ + "bash" + ] + }, + "gst-plugins-base-doc": { + "versions": { + "1.14.4-r0": 1543928664 + }, + "origin": "gst-plugins-base" + }, + "uwsgi-emperor_zeromq": { + "versions": { + "2.0.17.1-r0": 1545062199 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libzmq.so.5" + ] + }, + "perl-html-rewriteattributes": { + "versions": { + "0.05-r1": 1545289346 + }, + "origin": "perl-html-rewriteattributes", + "dependencies": [ + "perl-html-tagset", + "perl-uri", + "perl-html-parser" + ] + }, + "postfix-stone": { + "versions": { + "3.3.2-r0": 1547130363 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpostfix-global.so", + "so:libpostfix-util.so" + ], + "provides": [ + "cmd:fsstone", + "cmd:qmqp-sink", + "cmd:qmqp-source", + "cmd:smtp-sink", + "cmd:smtp-source" + ] + }, + "lame-doc": { + "versions": { + "3.100-r0": 1545117086 + }, + "origin": "lame" + }, + "sqlite": { + "versions": { + "3.26.0-r3": 1546255354 + }, + "origin": "sqlite", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:sqlite3" + ] + }, + "cogl-lang": { + "versions": { + "1.22.2-r0": 1544000912 + }, + "origin": "cogl" + }, + "perl-cgi-emulate-psgi-doc": { + "versions": { + "0.23-r0": 1545229754 + }, + "origin": "perl-cgi-emulate-psgi" + }, + "collectd-mqtt": { + "versions": { + "5.8.0-r3": 1546422678 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ] + }, + "postgresql-plpython3": { + "versions": { + "11.2-r0": 1554274177 + }, + "origin": "postgresql", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "xfsprogs-libs": { + "versions": { + "4.19.0-r1": 1546597448 + }, + "origin": "xfsprogs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhandle.so.1=1.0.3" + ] + }, + "libaio-dev": { + "versions": { + "0.3.111-r0": 1542845871 + }, + "origin": "libaio", + "dependencies": [ + "libaio=0.3.111-r0" + ] + }, + "perl-db-doc": { + "versions": { + "0.55-r1": 1542985267 + }, + "origin": "perl-db" + }, + "xcb-util": { + "versions": { + "0.4.0-r1": 1542822846 + }, + "origin": "xcb-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-util.so.1=1.0.0" + ] + }, + "perl-server-starter": { + "versions": { + "0.15-r1": 1543249942 + }, + "origin": "perl-server-starter", + "dependencies": [ + "perl-proc-wait3", + "perl-list-moreutils", + "perl-scope-guard" + ], + "provides": [ + "cmd:start_server" + ] + }, + "libotr3-doc": { + "versions": { + "3.2.1-r4": 1543230934 + }, + "origin": "libotr3", + "dependencies": [ + "libotr3" + ] + }, + "libraw1394-dev": { + "versions": { + "2.1.2-r1": 1543932096 + }, + "origin": "libraw1394", + "dependencies": [ + "libraw1394=2.1.2-r1", + "pkgconfig" + ], + "provides": [ + "pc:libraw1394=2.1.2" + ] + }, + "ruby-bundler": { + "versions": { + "1.17.1-r0": 1545301042 + }, + "origin": "ruby-bundler", + "dependencies": [ + "ruby", + "ruby-etc", + "ruby-io-console" + ], + "provides": [ + "cmd:bundle", + "cmd:bundler" + ] + }, + "lua5.2-lzlib": { + "versions": { + "0.4.3-r0": 1542845781 + }, + "origin": "lua-lzlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ] + }, + "icu-static": { + "versions": { + "62.1-r0": 1542823972 + }, + "origin": "icu" + }, + "qemu-microblazeel": { + "versions": { + "3.1.0-r3": 1551107300 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-microblazeel" + ] + }, + "glade3": { + "versions": { + "3.8.5-r5": 1543998728 + }, + "origin": "glade3", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgladeui-1.so.11=11.2.0", + "cmd:glade-3" + ] + }, + "cd-discid": { + "versions": { + "1.4-r2": 1545117998 + }, + "origin": "cd-discid", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cd-discid" + ] + }, + "quagga-dev": { + "versions": { + "1.2.4-r1": 1545068939 + }, + "origin": "quagga", + "dependencies": [ + "quagga=1.2.4-r1" + ] + }, + "libdv-tools": { + "versions": { + "1.0.0-r3": 1543999341 + }, + "origin": "libdv", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdv.so.4", + "so:libgdk-x11-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ], + "provides": [ + "cmd:dubdv", + "cmd:dvconnect", + "cmd:encodedv", + "cmd:playdv" + ] + }, + "py-django-treebeard": { + "versions": { + "4.3-r0": 1543925708 + }, + "origin": "py-django-treebeard", + "dependencies": [ + "py-django" + ] + }, + "which": { + "versions": { + "2.21-r1": 1543929035 + }, + "origin": "which", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:which" + ] + }, + "ppp": { + "versions": { + "2.4.7-r6": 1543999024 + }, + "origin": "ppp", + "dependencies": [ + "ppp-chat", + "ppp-radius", + "ppp-atm", + "ppp-pppoe", + "ppp-l2tp", + "ppp-winbind", + "ppp-passprompt", + "ppp-passwordfd", + "ppp-minconn", + "ppp-daemon" + ] + }, + "gobject-introspection-doc": { + "versions": { + "1.56.1-r0": 1542823015 + }, + "origin": "gobject-introspection" + }, + "hylafax-doc": { + "versions": { + "6.0.7-r0": 1545746106 + }, + "origin": "hylafax" + }, + "lua5.1-openrc": { + "versions": { + "0.2-r3": 1545066956 + }, + "origin": "lua-openrc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librc.so.1" + ] + }, + "lua5.1-dbi-mysql": { + "versions": { + "0.6-r3": 1545214218 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "asterisk-mobile": { + "versions": { + "15.7.1-r0": 1546247585 + }, + "origin": "asterisk", + "dependencies": [ + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "gnome-common": { + "versions": { + "3.18.0-r1": 1543925206 + }, + "origin": "gnome-common", + "provides": [ + "cmd:gnome-autogen.sh" + ] + }, + "distcc": { + "versions": { + "3.3.2-r0": 1544799001 + }, + "origin": "distcc", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:distcc", + "cmd:distccd", + "cmd:distccmon-text", + "cmd:lsdistcc", + "cmd:update-distcc-symlinks" + ] + }, + "gtkglext-doc": { + "versions": { + "1.2.0-r12": 1544001308 + }, + "origin": "gtkglext" + }, + "linux-firmware-ttusb-budget": { + "versions": { + "20190322-r0": 1554980647 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "dnstop": { + "versions": { + "20140915-r3": 1545165121 + }, + "origin": "dnstop", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:dnstop" + ] + }, + "perl-data-guid-doc": { + "versions": { + "0.049-r0": 1545163358 + }, + "origin": "perl-data-guid" + }, + "vde2-doc": { + "versions": { + "2.3.2-r10": 1545074024 + }, + "origin": "vde2" + }, + "avahi-tools": { + "versions": { + "0.7-r1": 1543925310 + }, + "origin": "avahi", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libintl.so.8" + ], + "provides": [ + "cmd:avahi-browse", + "cmd:avahi-browse-domains", + "cmd:avahi-publish", + "cmd:avahi-publish-address", + "cmd:avahi-publish-service", + "cmd:avahi-resolve", + "cmd:avahi-resolve-address", + "cmd:avahi-resolve-host-name", + "cmd:avahi-set-host-name" + ] + }, + "freeswitch-flite": { + "versions": { + "1.8.2-r1": 1545117891 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libflite.so.1", + "so:libflite_cmu_us_awb.so.1", + "so:libflite_cmu_us_kal.so.1", + "so:libflite_cmu_us_kal16.so.1", + "so:libflite_cmu_us_rms.so.1", + "so:libflite_cmu_us_slt.so.1", + "so:libflite_usenglish.so.1", + "so:libfreeswitch.so.1" + ] + }, + "lua-dmvpn": { + "versions": { + "1.0.2-r0": 1553425622 + }, + "origin": "dmvpn", + "dependencies": [ + "lua-asn1" + ] + }, + "libgc++": { + "versions": { + "7.6.4-r2": 1543226893 + }, + "origin": "gc", + "dependencies": [ + "so:libgc.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgccpp.so.1=1.3.1" + ] + }, + "asterisk-cdr-mysql": { + "versions": { + "15.7.1-r0": 1546247586 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "libdaemon": { + "versions": { + "0.14-r2": 1543925257 + }, + "origin": "libdaemon", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdaemon.so.0=0.5.0" + ] + }, + "gradm": { + "versions": { + "3.1.201607172312-r0": 1545299891 + }, + "origin": "gradm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:gradm", + "cmd:grlearn" + ] + }, + "sdl_image-dev": { + "versions": { + "1.2.12-r4": 1545302048 + }, + "origin": "sdl_image", + "dependencies": [ + "pc:sdl>=1.2.10", + "pkgconfig", + "sdl_image=1.2.12-r4" + ], + "provides": [ + "pc:SDL_image=1.2.12" + ] + }, + "orc": { + "versions": { + "0.4.28-r0": 1542883449 + }, + "origin": "orc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liborc-0.4.so.0=0.28.0", + "so:liborc-test-0.4.so.0=0.28.0" + ] + }, + "kamailio-cpl": { + "versions": { + "5.2.0-r1": 1546423170 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libsrdb1.so.1", + "so:libxml2.so.2" + ] + }, + "taskd-pki": { + "versions": { + "1.1.0-r4": 1545163049 + }, + "origin": "taskd", + "dependencies": [ + "taskd", + "gnutls-utils" + ] + }, + "lua-subprocess": { + "versions": { + "0.0.20141229-r2": 1542883782 + }, + "origin": "lua-subprocess" + }, + "libxkbcommon-dev": { + "versions": { + "0.8.2-r0": 1545857326 + }, + "origin": "libxkbcommon", + "dependencies": [ + "libxkbcommon=0.8.2-r0", + "pc:xcb", + "pc:xcb-xkb", + "pkgconfig" + ], + "provides": [ + "pc:xkbcommon-x11=0.8.2", + "pc:xkbcommon=0.8.2" + ] + }, + "lua-rex-posix": { + "versions": { + "2.9.0-r0": 1545209198 + }, + "origin": "lua-rex" + }, + "librevenge-dev": { + "versions": { + "0.0.4-r2": 1543932070 + }, + "origin": "librevenge", + "dependencies": [ + "boost-dev", + "zlib-dev", + "cppunit-dev", + "librevenge=0.0.4-r2", + "pkgconfig" + ], + "provides": [ + "pc:librevenge-0.0=0.0.4", + "pc:librevenge-generators-0.0=0.0.4", + "pc:librevenge-stream-0.0=0.0.4" + ] + }, + "libpciaccess-dev": { + "versions": { + "0.14-r0": 1545838880 + }, + "origin": "libpciaccess", + "dependencies": [ + "libpciaccess=0.14-r0", + "pkgconfig" + ], + "provides": [ + "pc:pciaccess=0.14" + ] + }, + "qemu-block-dmg-bz2": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "axel-doc": { + "versions": { + "2.16.1-r2": 1545229308 + }, + "origin": "axel" + }, + "quazip-dev": { + "versions": { + "0.7.3-r0": 1545118030 + }, + "origin": "quazip", + "dependencies": [ + "quazip=0.7.3-r0" + ] + }, + "libcanberra-dev": { + "versions": { + "0.30-r2": 1545118058 + }, + "origin": "libcanberra", + "dependencies": [ + "gtk+-dev", + "libogg-dev", + "libvorbis-dev", + "alsa-lib-dev", + "libtool", + "gtk+3.0-dev", + "libcanberra-gtk2=0.30-r2", + "libcanberra-gtk3=0.30-r2", + "libcanberra=0.30-r2", + "pc:gdk-2.0", + "pc:gdk-3.0", + "pc:gtk+-2.0", + "pc:gtk+-3.0", + "pkgconfig" + ], + "provides": [ + "pc:libcanberra-gtk3=0.30", + "pc:libcanberra-gtk=0.30", + "pc:libcanberra=0.30" + ] + }, + "virglrenderer-doc": { + "versions": { + "0.7.0-r1": 1547472270 + }, + "origin": "virglrenderer" + }, + "pciutils-dev": { + "versions": { + "3.6.2-r0": 1543921866 + }, + "origin": "pciutils", + "dependencies": [ + "pciutils-libs=3.6.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libpci=3.6.2" + ] + }, + "xf86-video-qxl": { + "versions": { + "0.1.5-r5": 1545292752 + }, + "origin": "xf86-video-qxl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "qt-webkit": { + "versions": { + "4.8.7-r11": 1545075089 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtDeclarative.so.4", + "so:libQtGui.so.4", + "so:libQtNetwork.so.4", + "so:libX11.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libsqlite3.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libQtWebKit.so.4=4.9.4" + ] + }, + "kamailio-ev": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libev.so.4" + ] + }, + "liblogging-dev": { + "versions": { + "1.0.6-r0": 1545060626 + }, + "origin": "liblogging", + "dependencies": [ + "liblogging=1.0.6-r0", + "pkgconfig" + ], + "provides": [ + "pc:liblogging-stdlog=1.0.6" + ] + }, + "readline-dev": { + "versions": { + "7.0.003-r1": 1542301064 + }, + "origin": "readline", + "dependencies": [ + "libhistory=7.0.003-r1", + "readline=7.0.003-r1" + ] + }, + "libssh": { + "versions": { + "0.7.6-r1": 1545300250 + }, + "origin": "libssh", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:libssh.so.4=4.4.3", + "so:libssh_threads.so.4=4.4.3" + ] + }, + "checkbashisms-doc": { + "versions": { + "2.18.11-r0": 1545292631 + }, + "origin": "checkbashisms" + }, + "perl-yaml-tiny": { + "versions": { + "1.73-r0": 1542893323 + }, + "origin": "perl-yaml-tiny" + }, + "gettext-asprintf": { + "versions": { + "0.19.8.1-r4": 1542304413 + }, + "origin": "gettext", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libasprintf.so.0=0.0.0" + ] + }, + "samba-common-server-libs": { + "versions": { + "4.8.11-r1": 1555334973 + }, + "origin": "samba", + "dependencies": [ + "so:libCHARSET3-samba4.so", + "so:libacl.so.1", + "so:libaddns-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcli-spoolss-samba4.so", + "so:libcliauth-samba4.so", + "so:libcom_err.so.2", + "so:libcommon-auth-samba4.so", + "so:libcups.so.2", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libevents-samba4.so", + "so:libflag-mapping-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libgssapi-samba4.so.2", + "so:libiov-buf-samba4.so", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libldb.so.1", + "so:liblibcli-lsa3-samba4.so", + "so:liblibcli-netlogon3-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmessages-dgm-samba4.so", + "so:libmsrpc3-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libpam.so.0", + "so:libreplace-samba4.so", + "so:libsamba-cluster-support-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libsecrets3-samba4.so", + "so:libserver-id-db-samba4.so", + "so:libsmb-transport-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-shim-samba4.so", + "so:libsocket-blocking-samba4.so", + "so:libsys-rw-samba4.so", + "so:libtalloc.so.2", + "so:libtdb-wrap-samba4.so", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtime-basic-samba4.so", + "so:libtrusts-util-samba4.so", + "so:libutil-cmdline-samba4.so", + "so:libutil-reg-samba4.so", + "so:libutil-setid-samba4.so", + "so:libutil-tdb-samba4.so", + "so:libwbclient.so.0" + ], + "provides": [ + "so:libads-samba4.so=0", + "so:libauth-samba4.so=0", + "so:libdfs-server-ad-samba4.so=0", + "so:libnetapi.so.0=0", + "so:libnpa-tstream-samba4.so=0", + "so:libprinting-migrate-samba4.so=0", + "so:libsmbd-base-samba4.so=0", + "so:libsmbd-conn-samba4.so=0", + "so:libsmbldap.so.2=2", + "so:libsmbldaphelper-samba4.so=0" + ] + }, + "py-pluggy": { + "versions": { + "0.7.1-r0": 1542824895 + }, + "origin": "py-pluggy" + }, + "qemu": { + "versions": { + "3.1.0-r3": 1551107308 + }, + "origin": "qemu", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0", + "so:libcap.so.2", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libnettle.so.6", + "so:libxkbcommon.so.0" + ], + "provides": [ + "cmd:qemu-edid", + "cmd:qemu-keymap", + "cmd:qemu-pr-helper", + "cmd:virtfs-proxy-helper" + ] + }, + "lxsession-lang": { + "versions": { + "0.5.3-r0": 1545299866 + }, + "origin": "lxsession" + }, + "perl-io": { + "versions": { + "1.25-r4": 1544001438 + }, + "origin": "perl-io", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-file-remove-doc": { + "versions": { + "1.58-r0": 1544792322 + }, + "origin": "perl-file-remove" + }, + "gd": { + "versions": { + "2.2.5-r3": 1554727880 + }, + "origin": "gd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3" + ], + "provides": [ + "cmd:annotate", + "cmd:gd2copypal", + "cmd:gd2togif", + "cmd:gd2topng", + "cmd:gdcmpgif", + "cmd:gdparttopng", + "cmd:gdtopng", + "cmd:giftogd2", + "cmd:pngtogd", + "cmd:pngtogd2", + "cmd:webpng" + ] + }, + "recode-dev": { + "versions": { + "3.6-r2": 1543245780 + }, + "origin": "recode", + "dependencies": [ + "recode=3.6-r2" + ] + }, + "openbox-gnome": { + "versions": { + "3.6.1-r2": 1545207328 + }, + "origin": "openbox", + "dependencies": [ + "so:libX11.so.6", + "so:libXau.so.6", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "cmd:gdm-control", + "cmd:gnome-panel-control", + "cmd:openbox-gnome-session" + ] + }, + "bsd-compat-headers": { + "versions": { + "0.7.1-r0": 1542302754 + }, + "origin": "libc-dev" + }, + "perl-list-utilsby": { + "versions": { + "0.11-r0": 1545062502 + }, + "origin": "perl-list-utilsby" + }, + "speexdsp-dev": { + "versions": { + "1.2_rc3-r5": 1544799233 + }, + "origin": "speexdsp", + "dependencies": [ + "pkgconfig", + "speexdsp=1.2_rc3-r5" + ] + }, + "pjproject": { + "versions": { + "2.7.2-r4": 1545073633 + }, + "origin": "pjproject", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libgsm.so.1", + "so:libsamplerate.so.0", + "so:libspeex.so.1", + "so:libspeexdsp.so.1", + "so:libssl.so.1.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libg7221codec.so.2=2", + "so:libilbccodec.so.2=2", + "so:libpj.so.2=2", + "so:libpjlib-util.so.2=2", + "so:libpjmedia-audiodev.so.2=2", + "so:libpjmedia-codec.so.2=2", + "so:libpjmedia-videodev.so.2=2", + "so:libpjmedia.so.2=2", + "so:libpjnath.so.2=2", + "so:libpjsip-simple.so.2=2", + "so:libpjsip-ua.so.2=2", + "so:libpjsip.so.2=2", + "so:libpjsua.so.2=2", + "so:libpjsua2.so.2=2", + "so:libyuv.so.2=2" + ] + }, + "postgresql-bdr-dev": { + "versions": { + "9.4.14_p1-r1": 1543223160 + }, + "origin": "postgresql-bdr", + "dependencies": [ + "readline-dev", + "openssl-dev", + "zlib-dev", + "libxml2-dev", + "pkgconfig", + "postgresql-bdr=9.4.14_p1-r1" + ], + "provides": [ + "pc:libecpg=9.4.14", + "pc:libecpg_compat=9.4.14", + "pc:libpgtypes=9.4.14", + "pc:libpq=9.4.14" + ] + }, + "geoip": { + "versions": { + "1.6.12-r1": 1544799416 + }, + "origin": "geoip", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libGeoIP.so.1=1.6.12", + "cmd:geoiplookup", + "cmd:geoiplookup6" + ] + }, + "libxext-doc": { + "versions": { + "1.3.3-r3": 1542822855 + }, + "origin": "libxext" + }, + "libldap": { + "versions": { + "2.4.47-r2": 1546016477 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libsasl2.so.3", + "so:libssl.so.1.1" + ], + "provides": [ + "so:liblber-2.4.so.2=2.10.10", + "so:libldap-2.4.so.2=2.10.10", + "so:libldap_r-2.4.so.2=2.10.10" + ] + }, + "lua-b64": { + "versions": { + "0.1-r2": 1545293188 + }, + "origin": "lua-b64", + "dependencies": [ + "lua" + ] + }, + "lua5.3-rex-posix": { + "versions": { + "2.9.0-r0": 1545209207 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "fontconfig-dev": { + "versions": { + "2.13.1-r0": 1545745987 + }, + "origin": "fontconfig", + "dependencies": [ + "fontconfig=2.13.1-r0", + "pc:expat", + "pc:freetype2>=21.0.15", + "pc:uuid", + "pkgconfig" + ], + "provides": [ + "pc:fontconfig=2.13.1" + ] + }, + "libnetfilter_cthelper-dev": { + "versions": { + "1.0.0-r0": 1545073849 + }, + "origin": "libnetfilter_cthelper", + "dependencies": [ + "libnetfilter_cthelper=1.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_cthelper=1.0.0" + ] + }, + "feh": { + "versions": { + "3.1.1-r0": 1545060588 + }, + "origin": "feh", + "dependencies": [ + "so:libImlib2.so.1", + "so:libX11.so.6", + "so:libXinerama.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libpng16.so.16" + ], + "provides": [ + "cmd:feh" + ] + }, + "ncurses-terminfo-base": { + "versions": { + "6.1_p20190105-r0": 1546948259 + }, + "origin": "ncurses" + }, + "perl-test-nowarnings": { + "versions": { + "1.04-r1": 1542845590 + }, + "origin": "perl-test-nowarnings", + "dependencies": [ + "perl", + "perl-test-simple" + ] + }, + "partimage-doc": { + "versions": { + "0.6.9-r6": 1543924736 + }, + "origin": "partimage" + }, + "dropbear-doc": { + "versions": { + "2018.76-r2": 1545208975 + }, + "origin": "dropbear" + }, + "lua5.3-lxc": { + "versions": { + "3.0.2-r0": 1546416498 + }, + "origin": "lua-lxc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblxc.so.1" + ] + }, + "iceauth": { + "versions": { + "1.0.8-r0": 1545214113 + }, + "origin": "iceauth", + "dependencies": [ + "so:libICE.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:iceauth" + ] + }, + "dovecot-pgsql": { + "versions": { + "2.3.6-r0": 1557134098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot-sql=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "so:libdriver_pgsql.so=0" + ] + }, + "perl-mail-spf": { + "versions": { + "2.9.0-r2": 1542925020 + }, + "origin": "perl-mail-spf", + "dependencies": [ + "perl", + "perl-error", + "perl-net-dns", + "perl-uri", + "perl-netaddr-ip", + "perl-net-dns-resolver-programmable" + ] + }, + "libarchive-dev": { + "versions": { + "3.3.2-r4": 1542820310 + }, + "origin": "libarchive", + "dependencies": [ + "libarchive=3.3.2-r4", + "pkgconfig" + ], + "provides": [ + "pc:libarchive=3.3.2" + ] + }, + "rrdtool-doc": { + "versions": { + "1.7.0-r0": 1542924800 + }, + "origin": "rrdtool" + }, + "libxau": { + "versions": { + "1.0.8-r3": 1542822450 + }, + "origin": "libxau", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXau.so.6=6.0.0" + ] + }, + "libmaa-dev": { + "versions": { + "1.3.2-r1": 1545209186 + }, + "origin": "libmaa", + "dependencies": [ + "libmaa=1.3.2-r1" + ] + }, + "open-isns-doc": { + "versions": { + "0.97-r4": 1542883211 + }, + "origin": "open-isns" + }, + "xdpyinfo-doc": { + "versions": { + "1.3.2-r0": 1545069116 + }, + "origin": "xdpyinfo" + }, + "f2fs-tools": { + "versions": { + "1.12.0-r0": 1546344802 + }, + "origin": "f2fs-tools", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libf2fs.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:defrag.f2fs", + "cmd:dump.f2fs", + "cmd:f2fscrypt", + "cmd:f2fstat", + "cmd:fibmap.f2fs", + "cmd:fsck.f2fs", + "cmd:mkfs.f2fs", + "cmd:parse.f2fs", + "cmd:resize.f2fs", + "cmd:sg_write_buffer", + "cmd:sload.f2fs" + ] + }, + "font-ibm-type1": { + "versions": { + "1.0.3-r0": 1545116965 + }, + "origin": "font-ibm-type1", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "libva-glx": { + "versions": { + "2.2.0-r0": 1545300086 + }, + "origin": "libva-glx", + "dependencies": [ + "so:libGL.so.1", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libva-x11.so.2", + "so:libva.so.2" + ], + "provides": [ + "so:libva-glx.so.2=2.200.0" + ] + }, + "slim-themes": { + "versions": { + "1.2.3-r3": 1545293196 + }, + "origin": "slim-themes", + "dependencies": [ + "slim" + ] + }, + "lua5.1-discount": { + "versions": { + "1.2.10.1-r4": 1545209119 + }, + "origin": "lua-discount", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-exporter": { + "versions": { + "5.73-r0": 1544001442 + }, + "origin": "perl-exporter" + }, + "libnice-gstreamer": { + "versions": { + "0.1.14-r2": 1543928835 + }, + "origin": "libnice", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libnice.so.10" + ] + }, + "xf86-video-sis": { + "versions": { + "0.10.9-r3": 1545253991 + }, + "origin": "xf86-video-sis", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libguess-dev": { + "versions": { + "1.2-r0": 1543998752 + }, + "origin": "libguess", + "dependencies": [ + "libguess=1.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libguess=1.2" + ] + }, + "perl-file-copy-recursive-doc": { + "versions": { + "0.44-r0": 1543238906 + }, + "origin": "perl-file-copy-recursive" + }, + "uwsgi-symcall": { + "versions": { + "2.0.17.1-r0": 1545062211 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-file-next": { + "versions": { + "1.16-r0": 1544000675 + }, + "origin": "perl-file-next" + }, + "xmlrpc-c-dev": { + "versions": { + "1.39.13-r0": 1545665208 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "libxml2-dev", + "xmlrpc-c++=1.39.13-r0", + "xmlrpc-c-abyss=1.39.13-r0", + "xmlrpc-c-client++=1.39.13-r0", + "xmlrpc-c-client=1.39.13-r0", + "xmlrpc-c=1.39.13-r0" + ], + "provides": [ + "cmd:xmlrpc-c-config" + ] + }, + "ttf-linux-libertine": { + "versions": { + "5.3.0-r0": 1544000077 + }, + "origin": "ttf-linux-libertine", + "dependencies": [ + "fontconfig", + "encodings", + "mkfontdir", + "mkfontscale" + ] + }, + "apache2": { + "versions": { + "2.4.39-r0": 1554306836 + }, + "origin": "apache2", + "dependencies": [ + "/bin/sh", + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:fcgistarter", + "cmd:httpd", + "cmd:suexec" + ] + }, + "aaudit": { + "versions": { + "0.7.2-r1": 1543934483 + }, + "origin": "aaudit", + "dependencies": [ + "lua5.2", + "lua5.2-posix", + "lua5.2-cjson", + "lua5.2-pc", + "lua5.2-socket" + ], + "provides": [ + "cmd:aaudit" + ] + }, + "mtr-gtk": { + "versions": { + "0.92-r0": 1545163036 + }, + "origin": "mtr", + "dependencies": [ + "ncurses", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:mtr-gtk", + "cmd:mtr-packet-gtk" + ] + }, + "cmph-doc": { + "versions": { + "2.0-r1": 1544000441 + }, + "origin": "cmph" + }, + "py-gtk-doc": { + "versions": { + "2.24.0-r15": 1543927628 + }, + "origin": "py-gtk" + }, + "acf-unbound": { + "versions": { + "0.1.0-r2": 1543932151 + }, + "origin": "acf-unbound", + "dependencies": [ + "acf-core", + "unbound" + ] + }, + "json-c0.12": { + "versions": { + "0.12.1-r2": 1545301158 + }, + "origin": "json-c0.12", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libjson-c.so.2=2.0.2" + ] + }, + "py3-paramiko": { + "versions": { + "2.4.2-r0": 1545216486 + }, + "origin": "py-paramiko", + "dependencies": [ + "py3-asn1", + "py3-cryptography", + "py3-bcrypt", + "py3-pynacl", + "python3" + ] + }, + "py3-gobject3": { + "versions": { + "3.28.2-r0": 1543925244 + }, + "origin": "py-gobject3", + "dependencies": [ + "py3-cairo", + "so:libc.musl-x86_64.so.1", + "so:libcairo-gobject.so.2", + "so:libcairo.so.2", + "so:libffi.so.6", + "so:libgirepository-1.0.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ] + }, + "rsyslog-doc": { + "versions": { + "8.40.0-r3": 1548686787 + }, + "origin": "rsyslog" + }, + "libxrender-doc": { + "versions": { + "0.9.10-r3": 1542822736 + }, + "origin": "libxrender" + }, + "libgcab-doc": { + "versions": { + "0.7-r2": 1543249783 + }, + "origin": "libgcab" + }, + "aconf-mod-network": { + "versions": { + "0.7.1-r0": 1553432497 + }, + "origin": "aconf", + "dependencies": [ + "aconf" + ] + }, + "nginx-mod-http-perl": { + "versions": { + "1.14.2-r1": 1557179820 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "conntrack-tools-doc": { + "versions": { + "1.4.4-r0": 1545301898 + }, + "origin": "conntrack-tools" + }, + "perl-time-modules": { + "versions": { + "2013.0912-r0": 1545061993 + }, + "origin": "perl-time-modules", + "dependencies": [ + "perl" + ] + }, + "perl-unix-syslog": { + "versions": { + "1.1-r11": 1545067185 + }, + "origin": "perl-unix-syslog", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "qt": { + "versions": { + "4.8.7-r11": 1545075091 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libQtCore.so.4=4.8.7", + "so:libQtDBus.so.4=4.8.7", + "so:libQtNetwork.so.4=4.8.7", + "so:libQtScript.so.4=4.8.7", + "so:libQtSql.so.4=4.8.7", + "so:libQtTest.so.4=4.8.7", + "so:libQtXml.so.4=4.8.7", + "so:libQtXmlPatterns.so.4=4.8.7", + "cmd:qdbus" + ] + }, + "libnl3-cli": { + "versions": { + "3.4.0-r0": 1542965892 + }, + "origin": "libnl3", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnl-3.so.200", + "so:libnl-genl-3.so.200", + "so:libnl-idiag-3.so.200", + "so:libnl-nf-3.so.200", + "so:libnl-route-3.so.200" + ], + "provides": [ + "so:libnl-cli-3.so.200=200.26.0", + "cmd:genl-ctrl-list", + "cmd:idiag-socket-details", + "cmd:nf-ct-add", + "cmd:nf-ct-list", + "cmd:nf-exp-add", + "cmd:nf-exp-delete", + "cmd:nf-exp-list", + "cmd:nf-log", + "cmd:nf-monitor", + "cmd:nf-queue", + "cmd:nl-addr-add", + "cmd:nl-addr-delete", + "cmd:nl-addr-list", + "cmd:nl-class-add", + "cmd:nl-class-delete", + "cmd:nl-class-list", + "cmd:nl-classid-lookup", + "cmd:nl-cls-add", + "cmd:nl-cls-delete", + "cmd:nl-cls-list", + "cmd:nl-fib-lookup", + "cmd:nl-link-enslave", + "cmd:nl-link-ifindex2name", + "cmd:nl-link-list", + "cmd:nl-link-name2ifindex", + "cmd:nl-link-release", + "cmd:nl-link-set", + "cmd:nl-link-stats", + "cmd:nl-list-caches", + "cmd:nl-list-sockets", + "cmd:nl-monitor", + "cmd:nl-neigh-add", + "cmd:nl-neigh-delete", + "cmd:nl-neigh-list", + "cmd:nl-neightbl-list", + "cmd:nl-pktloc-lookup", + "cmd:nl-qdisc-add", + "cmd:nl-qdisc-delete", + "cmd:nl-qdisc-list", + "cmd:nl-route-add", + "cmd:nl-route-delete", + "cmd:nl-route-get", + "cmd:nl-route-list", + "cmd:nl-rule-list", + "cmd:nl-tctree-list", + "cmd:nl-util-addr" + ] + }, + "nrpe": { + "versions": { + "3.2.1-r0": 1545249893 + }, + "origin": "nrpe", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:nrpe", + "cmd:nrpe-uninstall" + ] + }, + "openldap-overlay-dyngroup": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "nginx-mod-stream-js": { + "versions": { + "1.14.2-r1": 1557179820 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "nginx-mod-stream", + "so:libc.musl-x86_64.so.1" + ] + }, + "acf-freeradius3": { + "versions": { + "0.3.1-r0": 1545062651 + }, + "origin": "acf-freeradius3", + "dependencies": [ + "acf-core", + "freeradius>3" + ] + }, + "acf-quagga": { + "versions": { + "0.10.1-r1": 1545069552 + }, + "origin": "acf-quagga", + "dependencies": [ + "acf-core", + "lua-socket", + "quagga" + ] + }, + "libpaper-doc": { + "versions": { + "1.1.24-r4": 1542824769 + }, + "origin": "libpaper" + }, + "gstreamer0.10": { + "versions": { + "0.10.36-r3": 1544000752 + }, + "origin": "gstreamer0.10", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgstbase-0.10.so.0=0.30.0", + "so:libgstcheck-0.10.so.0=0.30.0", + "so:libgstcontroller-0.10.so.0=0.30.0", + "so:libgstdataprotocol-0.10.so.0=0.30.0", + "so:libgstnet-0.10.so.0=0.30.0", + "so:libgstreamer-0.10.so.0=0.30.0" + ] + }, + "boost-static": { + "versions": { + "1.67.0-r2": 1542823625 + }, + "origin": "boost" + }, + "glfw": { + "versions": { + "3.2.1-r2": 1545299815 + }, + "origin": "glfw", + "dependencies": [ + "so:libX11.so.6", + "so:libXcursor.so.1", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libglfw.so.3=3.2" + ] + }, + "subversion-libs": { + "versions": { + "1.11.1-r0": 1548692375 + }, + "origin": "subversion", + "dependencies": [ + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:libexpat.so.1", + "so:liblz4.so.1", + "so:libsasl2.so.3", + "so:libserf-1.so.1", + "so:libsqlite3.so.0", + "so:libz.so.1" + ], + "provides": [ + "so:libsvn_client-1.so.0=0.0.0", + "so:libsvn_delta-1.so.0=0.0.0", + "so:libsvn_diff-1.so.0=0.0.0", + "so:libsvn_fs-1.so.0=0.0.0", + "so:libsvn_fs_base-1.so.0=0.0.0", + "so:libsvn_fs_fs-1.so.0=0.0.0", + "so:libsvn_fs_util-1.so.0=0.0.0", + "so:libsvn_fs_x-1.so.0=0.0.0", + "so:libsvn_ra-1.so.0=0.0.0", + "so:libsvn_ra_local-1.so.0=0.0.0", + "so:libsvn_ra_serf-1.so.0=0.0.0", + "so:libsvn_ra_svn-1.so.0=0.0.0", + "so:libsvn_repos-1.so.0=0.0.0", + "so:libsvn_subr-1.so.0=0.0.0", + "so:libsvn_wc-1.so.0=0.0.0" + ] + }, + "awall-policies": { + "versions": { + "1.6.9-r0": 1548501700 + }, + "origin": "awall", + "dependencies": [ + "awall" + ], + "provides": [ + "cmd:setup-firewall" + ] + }, + "gtk+3.0": { + "versions": { + "3.24.1-r0": 1543927436 + }, + "origin": "gtk+3.0", + "dependencies": [ + "shared-mime-info", + "gtk-update-icon-cache", + "/bin/sh", + "so:libX11.so.6", + "so:libXcomposite.so.1", + "so:libXcursor.so.1", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXi.so.6", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libatk-1.0.so.0", + "so:libatk-bridge-2.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo-gobject.so.2", + "so:libcairo.so.2", + "so:libcups.so.2", + "so:libepoxy.so.0", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libharfbuzz.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpangoft2-1.0.so.0", + "so:libwayland-client.so.0", + "so:libwayland-cursor.so.0", + "so:libwayland-egl.so.1", + "so:libxkbcommon.so.0" + ], + "provides": [ + "so:libgailutil-3.so.0=0.0.0", + "so:libgdk-3.so.0=0.2400.1", + "so:libgtk-3.so.0=0.2400.1", + "cmd:gtk-builder-tool", + "cmd:gtk-encode-symbolic-svg", + "cmd:gtk-launch", + "cmd:gtk-query-immodules-3.0", + "cmd:gtk-query-settings", + "cmd:gtk3-demo", + "cmd:gtk3-demo-application", + "cmd:gtk3-icon-browser", + "cmd:gtk3-widget-factory" + ] + }, + "freeswitch-sounds-es-mx-maria-44100": { + "versions": { + "0-r2": 1542893557 + }, + "origin": "freeswitch-sounds-es-mx-maria-44100" + }, + "perl-module-implementation": { + "versions": { + "0.09-r0": 1542845724 + }, + "origin": "perl-module-implementation", + "dependencies": [ + "perl-try-tiny", + "perl-module-runtime" + ] + }, + "mesa-dev": { + "versions": { + "18.1.7-r2": 1554455968 + }, + "origin": "mesa", + "dependencies": [ + "libdrm-dev", + "libxext-dev", + "libxdamage-dev", + "libxcb-dev", + "libxshmfence-dev", + "mesa-egl=18.1.7-r2", + "mesa-gbm=18.1.7-r2", + "mesa-gl=18.1.7-r2", + "mesa-glapi=18.1.7-r2", + "mesa-gles=18.1.7-r2", + "mesa-osmesa=18.1.7-r2", + "mesa-xatracker=18.1.7-r2", + "pc:libdrm>=2.4.75", + "pc:x11", + "pc:x11-xcb", + "pc:xcb", + "pc:xcb-dri2>=1.8", + "pc:xcb-glx>=1.8.1", + "pc:xdamage>=1.1", + "pc:xext", + "pc:xfixes", + "pc:xxf86vm", + "pkgconfig" + ], + "provides": [ + "pc:dri=18.1.7", + "pc:egl=18.1.7", + "pc:gbm=18.1.7", + "pc:gl=18.1.7", + "pc:glesv1_cm=18.1.7", + "pc:glesv2=18.1.7", + "pc:osmesa=8", + "pc:xatracker=2.3.0" + ] + }, + "iptables-doc": { + "versions": { + "1.6.2-r1": 1545062496 + }, + "origin": "iptables" + }, + "gtkmm-doc": { + "versions": { + "2.24.5-r0": 1543925601 + }, + "origin": "gtkmm" + }, + "perl-encode-doc": { + "versions": { + "2.98-r0": 1543935163 + }, + "origin": "perl-encode" + }, + "mesa-egl": { + "versions": { + "18.1.7-r2": 1554455971 + }, + "origin": "mesa", + "dependencies": [ + "so:libX11-xcb.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libexpat.so.1", + "so:libgbm.so.1", + "so:libglapi.so.0", + "so:libwayland-client.so.0", + "so:libwayland-server.so.0", + "so:libxcb-dri2.so.0", + "so:libxcb-dri3.so.0", + "so:libxcb-present.so.0", + "so:libxcb-sync.so.1", + "so:libxcb-xfixes.so.0", + "so:libxcb.so.1", + "so:libxshmfence.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libEGL.so.1=1.0.0" + ] + }, + "libxklavier": { + "versions": { + "5.4-r4": 1546458613 + }, + "origin": "libxklavier", + "dependencies": [ + "xkeyboard-config", + "iso-codes", + "so:libX11.so.6", + "so:libXi.so.6", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libxkbfile.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "so:libxklavier.so.16=16.4.0" + ] + }, + "swish-e": { + "versions": { + "2.4.7-r8": 1545163776 + }, + "origin": "swish-e", + "dependencies": [ + "perl-html-parser", + "perl-html-tagset", + "perl-libwww", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libswish-e.so.2=2.0.0", + "cmd:swish-e", + "cmd:swish-filter-test" + ] + }, + "e2fsprogs-doc": { + "versions": { + "1.44.5-r0": 1545745858 + }, + "origin": "e2fsprogs" + }, + "rpcbind-doc": { + "versions": { + "0.2.4-r1": 1543933378 + }, + "origin": "rpcbind" + }, + "py3-dateutil": { + "versions": { + "2.7.3-r0": 1543248498 + }, + "origin": "py-dateutil", + "dependencies": [ + "py3-six", + "python3" + ] + }, + "perl-parse-recdescent": { + "versions": { + "1.967015-r1": 1545067083 + }, + "origin": "perl-parse-recdescent", + "dependencies": [ + "perl" + ] + }, + "py2-urwid": { + "versions": { + "1.3.1-r2": 1542981236 + }, + "origin": "py-urwid", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "xscreensaver-extras": { + "versions": { + "5.40-r0": 1545224088 + }, + "origin": "xscreensaver", + "dependencies": [ + "bc", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXmu.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgdk_pixbuf_xlib-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libjpeg.so.8" + ] + }, + "uwsgi-logfile": { + "versions": { + "2.0.17.1-r0": 1545062202 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "rp-pppoe": { + "versions": { + "3.13-r0": 1545223084 + }, + "origin": "rp-pppoe", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pppoe", + "cmd:pppoe-connect", + "cmd:pppoe-relay", + "cmd:pppoe-server", + "cmd:pppoe-setup", + "cmd:pppoe-sniff", + "cmd:pppoe-start", + "cmd:pppoe-status", + "cmd:pppoe-stop" + ] + }, + "diffutils": { + "versions": { + "3.7-r0": 1546846563 + }, + "origin": "diffutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cmp", + "cmd:diff", + "cmd:diff3", + "cmd:sdiff" + ] + }, + "apache2-error": { + "versions": { + "2.4.39-r0": 1554306835 + }, + "origin": "apache2" + }, + "xtrans": { + "versions": { + "1.3.5-r1": 1542822666 + }, + "origin": "xtrans" + }, + "xfontsel-doc": { + "versions": { + "1.0.6-r0": 1545215013 + }, + "origin": "xfontsel" + }, + "nfdump-openrc": { + "versions": { + "1.6.17-r0": 1545223024 + }, + "origin": "nfdump" + }, + "fetchmailconf": { + "versions": { + "6.3.26-r14": 1544001391 + }, + "origin": "fetchmail", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:fetchmailconf" + ] + }, + "mesa-dri-swrast": { + "versions": { + "18.1.7-r2": 1554455970 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "pssh": { + "versions": { + "2.3.1-r1": 1545302366 + }, + "origin": "pssh", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:pnuke", + "cmd:prsync", + "cmd:pscp", + "cmd:pslurp", + "cmd:pssh" + ] + }, + "transmission-daemon": { + "versions": { + "2.94-r1": 1545208748 + }, + "origin": "transmission", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libevent-2.1.so.6", + "so:libintl.so.8", + "so:libz.so.1" + ], + "provides": [ + "cmd:transmission-daemon" + ] + }, + "dbus": { + "versions": { + "1.10.24-r1": 1542824378 + }, + "origin": "dbus", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libexpat.so.1" + ], + "provides": [ + "cmd:dbus-cleanup-sockets", + "cmd:dbus-daemon", + "cmd:dbus-monitor", + "cmd:dbus-run-session", + "cmd:dbus-send", + "cmd:dbus-test-tool", + "cmd:dbus-update-activation-environment", + "cmd:dbus-uuidgen" + ] + }, + "cairo-doc": { + "versions": { + "1.16.0-r1": 1546948314 + }, + "origin": "cairo" + }, + "linux-vanilla": { + "versions": { + "4.19.41-r0": 1557400012 + }, + "origin": "linux-vanilla", + "dependencies": [ + "mkinitfs", + "linux-firmware-any" + ] + }, + "squid-lang-sl": { + "versions": { + "4.4-r1": 1545216329 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "conky-doc": { + "versions": { + "1.11.1-r0": 1545746033 + }, + "origin": "conky" + }, + "json-glib-tests": { + "versions": { + "1.4.4-r0": 1545837175 + }, + "origin": "json-glib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libjson-glib-1.0.so.0" + ] + }, + "libevdev": { + "versions": { + "1.6.0-r0": 1545838440 + }, + "origin": "libevdev", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libevdev.so.2=2.2.0", + "cmd:libevdev-tweak-device", + "cmd:mouse-dpi-tool", + "cmd:touchpad-edge-detector" + ] + }, + "slim": { + "versions": { + "1.3.6-r8": 1545229417 + }, + "origin": "slim", + "dependencies": [ + "dbus", + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXmu.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libck-connector.so.0", + "so:libdbus-1.so.3", + "so:libgcc_s.so.1", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libslim.so.1.3.6=1.3.6", + "cmd:slim" + ] + }, + "sshguard": { + "versions": { + "2.2.0-r0": 1545300225 + }, + "origin": "sshguard", + "dependencies": [ + "iptables", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sshguard" + ] + }, + "freeswitch-dev": { + "versions": { + "1.8.2-r1": 1545117891 + }, + "origin": "freeswitch", + "dependencies": [ + "freeswitch-freetdm=1.8.2-r1", + "freeswitch=1.8.2-r1", + "pkgconfig" + ], + "provides": [ + "pc:freeswitch=1.8.2", + "pc:freetdm=0.1" + ] + }, + "wireless-tools-dev": { + "versions": { + "30_pre9-r0": 1545062248 + }, + "origin": "wireless-tools" + }, + "acf-alpine-conf": { + "versions": { + "0.9.0-r3": 1545116967 + }, + "origin": "acf-alpine-conf", + "dependencies": [ + "acf-core", + "lua-posix", + "libressl" + ] + }, + "libxpm-doc": { + "versions": { + "3.5.12-r0": 1542894077 + }, + "origin": "libxpm" + }, + "atkmm": { + "versions": { + "2.24.2-r1": 1544799364 + }, + "origin": "atkmm", + "dependencies": [ + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libglibmm-2.4.so.1", + "so:libgobject-2.0.so.0", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libatkmm-1.6.so.1=1.1.0" + ] + }, + "libunwind-dev": { + "versions": { + "1.2.1-r3": 1545915765 + }, + "origin": "libunwind", + "dependencies": [ + "libexecinfo-dev", + "libunwind=1.2.1-r3", + "pkgconfig" + ], + "provides": [ + "pc:libunwind-coredump=1.21", + "pc:libunwind-generic=1.21", + "pc:libunwind-ptrace=1.21", + "pc:libunwind-setjmp=1.21", + "pc:libunwind=1.21" + ] + }, + "icu-doc": { + "versions": { + "62.1-r0": 1542823973 + }, + "origin": "icu" + }, + "bitlbee-otr": { + "versions": { + "3.5.1-r4": 1543248420 + }, + "origin": "bitlbee", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libotr.so.5" + ] + }, + "st-doc": { + "versions": { + "0.8.1-r0": 1545292621 + }, + "origin": "st" + }, + "ncftp-doc": { + "versions": { + "3.2.6-r1": 1544000463 + }, + "origin": "ncftp" + }, + "lzo": { + "versions": { + "2.10-r2": 1542985324 + }, + "origin": "lzo", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblzo2.so.2=2.0.0" + ] + }, + "userspace-rcu-dev": { + "versions": { + "0.10.1-r0": 1543249589 + }, + "origin": "userspace-rcu", + "dependencies": [ + "pkgconfig", + "userspace-rcu=0.10.1-r0" + ], + "provides": [ + "pc:liburcu-bp=0.10.1", + "pc:liburcu-cds=0.10.1", + "pc:liburcu-mb=0.10.1", + "pc:liburcu-qsbr=0.10.1", + "pc:liburcu-signal=0.10.1", + "pc:liburcu=0.10.1" + ] + }, + "llvm5-static": { + "versions": { + "5.0.2-r0": 1546874979 + }, + "origin": "llvm5", + "provides": [ + "llvm-static=5.0.2-r0" + ] + }, + "rdiff-backup-doc": { + "versions": { + "1.2.8-r2": 1545213660 + }, + "origin": "rdiff-backup" + }, + "postfix-sqlite": { + "versions": { + "3.3.2-r0": 1547130363 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:postfix-sqlite.so=0" + ] + }, + "gtk+2.0-lang": { + "versions": { + "2.24.32-r1": 1543925513 + }, + "origin": "gtk+2.0", + "dependencies": [ + "shared-mime-info", + "gtk-update-icon-cache" + ] + }, + "perl-test-output-doc": { + "versions": { + "1.031-r0": 1545300171 + }, + "origin": "perl-test-output" + }, + "libasyncns-doc": { + "versions": { + "0.8-r1": 1543226569 + }, + "origin": "libasyncns" + }, + "perl-variable-magic": { + "versions": { + "0.62-r0": 1542973102 + }, + "origin": "perl-variable-magic", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gtk+2.0-dev": { + "versions": { + "2.24.32-r1": 1543925512 + }, + "origin": "gtk+2.0", + "dependencies": [ + "atk-dev", + "cairo-dev", + "intltool", + "libxdamage-dev", + "pango-dev", + "gtk+2.0=2.24.32-r1", + "pc:atk", + "pc:cairo", + "pc:gdk-pixbuf-2.0", + "pc:gio-2.0", + "pc:pango", + "pc:pangocairo", + "pc:pangoft2", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0" + ], + "provides": [ + "pc:gail=2.24.32", + "pc:gdk-2.0=2.24.32", + "pc:gdk-x11-2.0=2.24.32", + "pc:gtk+-2.0=2.24.32", + "pc:gtk+-unix-print-2.0=2.24.32", + "pc:gtk+-x11-2.0=2.24.32", + "cmd:gtk-builder-convert", + "cmd:gtk-demo" + ] + }, + "gnutls-dev": { + "versions": { + "3.6.7-r0": 1555049964 + }, + "origin": "gnutls", + "dependencies": [ + "gnutls-c++=3.6.7-r0", + "gnutls=3.6.7-r0", + "pc:hogweed", + "pc:libtasn1", + "pc:nettle", + "pc:p11-kit-1", + "pkgconfig" + ], + "provides": [ + "pc:gnutls=3.6.7" + ] + }, + "libell": { + "versions": { + "0.16-r0": 1545822164 + }, + "origin": "libell", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libell.so.0=0.0.2" + ] + }, + "nginx-mod-mail": { + "versions": { + "1.14.2-r1": 1557179820 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "alpine-ipxe-ipxe_pxe": { + "versions": { + "1.0_git20180825-r1": 1545986078 + }, + "origin": "alpine-ipxe" + }, + "argon2": { + "versions": { + "20171227-r1": 1543928962 + }, + "origin": "argon2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:argon2" + ] + }, + "linux-firmware-none": { + "versions": { + "20190322-r0": 1554980655 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "sharutils-lang": { + "versions": { + "4.15.2-r0": 1543929025 + }, + "origin": "sharutils", + "dependencies": [ + "bzip2" + ] + }, + "libmnl": { + "versions": { + "1.0.4-r0": 1542883675 + }, + "origin": "libmnl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmnl.so.0=0.2.0" + ] + }, + "liblognorm": { + "versions": { + "2.0.6-r1": 1548783946 + }, + "origin": "liblognorm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libestr.so.0", + "so:libfastjson.so.4" + ], + "provides": [ + "so:liblognorm.so.5=5.1.0", + "cmd:lognormalizer" + ] + }, + "myrepos": { + "versions": { + "1.20180726-r0": 1542893509 + }, + "origin": "myrepos", + "dependencies": [ + "perl", + "git" + ], + "provides": [ + "cmd:mr" + ] + }, + "libmaxminddb-dev": { + "versions": { + "1.3.2-r0": 1543226665 + }, + "origin": "libmaxminddb", + "dependencies": [ + "libmaxminddb=1.3.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmaxminddb=1.3.2" + ] + }, + "darkhttpd": { + "versions": { + "1.12-r2": 1543223261 + }, + "origin": "darkhttpd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:darkhttpd" + ] + }, + "git-p4": { + "versions": { + "2.20.1-r0": 1545214195 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0", + "git-fast-import=2.20.1-r0" + ] + }, + "xtables-addons": { + "versions": { + "3.2-r0": 1545154536 + }, + "origin": "xtables-addons", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxtables.so.12" + ], + "provides": [ + "so:libxt_ACCOUNT_cl.so.0=0.0.0", + "cmd:iptaccount" + ] + }, + "ddate": { + "versions": { + "0.2.2-r0": 1545207008 + }, + "origin": "ddate", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ddate" + ] + }, + "audit": { + "versions": { + "2.8.4-r0": 1543245856 + }, + "origin": "audit", + "dependencies": [ + "so:libaudit.so.1", + "so:libauparse.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0" + ], + "provides": [ + "cmd:audisp-remote", + "cmd:audispd", + "cmd:auditctl", + "cmd:auditd", + "cmd:augenrules", + "cmd:aulast", + "cmd:aulastlog", + "cmd:aureport", + "cmd:ausearch", + "cmd:ausyscall", + "cmd:autrace", + "cmd:auvirt" + ] + }, + "spl-vanilla": { + "versions": { + "4.19.41-r0": 1557400331 + }, + "origin": "spl-vanilla", + "dependencies": [ + "linux-vanilla=4.19.41-r0" + ] + }, + "rtpproxy-doc": { + "versions": { + "2.0.0-r4": 1545301076 + }, + "origin": "rtpproxy" + }, + "drbd-utils-doc": { + "versions": { + "9.7.1-r0": 1545746125 + }, + "origin": "drbd-utils" + }, + "cmake-bash-completion": { + "versions": { + "3.13.0-r0": 1542820577 + }, + "origin": "cmake" + }, + "udisks2-doc": { + "versions": { + "2.6.5-r1": 1545293055 + }, + "origin": "udisks2" + }, + "perl-test-eol": { + "versions": { + "2.00-r0": 1545075952 + }, + "origin": "perl-test-eol" + }, + "setxkbmap": { + "versions": { + "1.3.1-r1": 1545209626 + }, + "origin": "setxkbmap", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libxkbfile.so.1" + ], + "provides": [ + "cmd:setxkbmap" + ] + }, + "libunique3-doc": { + "versions": { + "3.0.2-r0": 1543927462 + }, + "origin": "libunique3" + }, + "lua-lxc": { + "versions": { + "3.0.2-r0": 1546416498 + }, + "origin": "lua-lxc" + }, + "boost-fiber": { + "versions": { + "1.67.0-r2": 1542823631 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_context-mt.so.1.67.0", + "so:libboost_filesystem-mt.so.1.67.0", + "so:libboost_system-mt.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_fiber-mt.so.1.67.0=1.67.0" + ] + }, + "polkit": { + "versions": { + "0.105-r9": 1547130960 + }, + "origin": "polkit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libpolkit-agent-1.so.0=0.0.0", + "so:libpolkit-backend-1.so.0=0.0.0", + "so:libpolkit-gobject-1.so.0=0.0.0", + "cmd:pk-example-frobnicate", + "cmd:pkaction", + "cmd:pkcheck", + "cmd:pkexec", + "cmd:pkttyagent" + ] + }, + "upower": { + "versions": { + "0.99.7-r0": 1545069387 + }, + "origin": "upower", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libupower-glib.so.3=3.0.1", + "cmd:upower" + ] + }, + "perl-css-squish": { + "versions": { + "0.10-r0": 1544000384 + }, + "origin": "perl-css-squish", + "dependencies": [ + "perl", + "perl-uri", + "perl-test-longstring" + ] + }, + "aconf-mod-openssh": { + "versions": { + "0.7.1-r0": 1553432498 + }, + "origin": "aconf", + "dependencies": [ + "aconf", + "openssh" + ] + }, + "xf86-video-i740-doc": { + "versions": { + "1.3.6-r3": 1545299626 + }, + "origin": "xf86-video-i740" + }, + "lua-pty": { + "versions": { + "1.2.1-r1": 1545216362 + }, + "origin": "lua-pty" + }, + "boost-filesystem": { + "versions": { + "1.67.0-r2": 1542823631 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_system-mt.so.1.67.0", + "so:libboost_system.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_filesystem-mt.so.1.67.0=1.67.0", + "so:libboost_filesystem.so.1.67.0=1.67.0" + ] + }, + "freeswitch-snmp": { + "versions": { + "1.8.2-r1": 1545117893 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreeswitch.so.1", + "so:libnetsnmp.so.35", + "so:libnetsnmpagent.so.35" + ] + }, + "tdb-libs": { + "versions": { + "1.3.16-r0": 1543933255 + }, + "origin": "tdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libtdb.so.1=1.3.16" + ] + }, + "rsyslog-pmlastmsg": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "opensp-doc": { + "versions": { + "1.5.2-r0": 1542893110 + }, + "origin": "opensp" + }, + "byobu": { + "versions": { + "5.127-r0": 1545062321 + }, + "origin": "byobu", + "dependencies": [ + "python3", + "tmux" + ], + "provides": [ + "cmd:byobu", + "cmd:byobu-config", + "cmd:byobu-ctrl-a", + "cmd:byobu-disable", + "cmd:byobu-disable-prompt", + "cmd:byobu-enable", + "cmd:byobu-enable-prompt", + "cmd:byobu-export", + "cmd:byobu-janitor", + "cmd:byobu-keybindings", + "cmd:byobu-launch", + "cmd:byobu-launcher", + "cmd:byobu-launcher-install", + "cmd:byobu-launcher-uninstall", + "cmd:byobu-layout", + "cmd:byobu-prompt", + "cmd:byobu-quiet", + "cmd:byobu-reconnect-sockets", + "cmd:byobu-screen", + "cmd:byobu-select-backend", + "cmd:byobu-select-profile", + "cmd:byobu-select-session", + "cmd:byobu-shell", + "cmd:byobu-silent", + "cmd:byobu-status", + "cmd:byobu-status-detail", + "cmd:byobu-tmux", + "cmd:byobu-ugraph", + "cmd:byobu-ulevel", + "cmd:col1", + "cmd:ctail", + "cmd:manifest", + "cmd:purge-old-kernels", + "cmd:vigpg", + "cmd:wifi-status" + ] + }, + "eggdbus": { + "versions": { + "0.6-r5": 1543935571 + }, + "origin": "eggdbus", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libeggdbus-1.so.0=0.0.0", + "cmd:eggdbus-binding-tool", + "cmd:eggdbus-glib-genmarshal" + ] + }, + "mkfontdir": { + "versions": { + "1.0.7-r1": 1542924704 + }, + "origin": "mkfontdir", + "dependencies": [ + "mkfontscale", + "/bin/sh" + ], + "provides": [ + "cmd:mkfontdir" + ] + }, + "libnice": { + "versions": { + "0.1.14-r2": 1543928835 + }, + "origin": "libnice", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libnice.so.10=10.7.0", + "cmd:sdp-example", + "cmd:simple-example", + "cmd:stunbdc", + "cmd:stund", + "cmd:threaded-example" + ] + }, + "libzdb-dev": { + "versions": { + "3.1-r2": 1543221886 + }, + "origin": "libzdb", + "dependencies": [ + "flex-dev", + "sqlite-dev", + "mariadb-connector-c-dev", + "postgresql-dev", + "libzdb=3.1-r2", + "pkgconfig" + ], + "provides": [ + "pc:zdb=3.1" + ] + }, + "ack": { + "versions": { + "2.24-r0": 1545224032 + }, + "origin": "ack", + "dependencies": [ + "perl-file-next", + "perl" + ], + "provides": [ + "cmd:ack" + ] + }, + "makedepend-doc": { + "versions": { + "1.0.5-r2": 1543220550 + }, + "origin": "makedepend" + }, + "uwsgi-cache": { + "versions": { + "2.0.17.1-r0": 1545062196 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "collectd-perl": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "alsa-utils-openrc": { + "versions": { + "1.1.8-r0": 1547112822 + }, + "origin": "alsa-utils", + "dependencies": [ + "dialog" + ] + }, + "linux-firmware-atmel": { + "versions": { + "20190322-r0": 1554980654 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "font-bitstream-100dpi": { + "versions": { + "1.0.3-r0": 1543932149 + }, + "origin": "font-bitstream-100dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "font-misc-misc": { + "versions": { + "1.1.2-r1": 1542973188 + }, + "origin": "font-misc-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig", + "util-macros" + ] + }, + "openjpeg": { + "versions": { + "2.3.0-r3": 1552584664 + }, + "origin": "openjpeg", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libopenjp2.so.7=2.3.0" + ] + }, + "yasm-dev": { + "versions": { + "1.3.0-r1": 1545117177 + }, + "origin": "yasm" + }, + "linux-firmware-kaweth": { + "versions": { + "20190322-r0": 1554980651 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "perl-mail-clamav-doc": { + "versions": { + "0.29-r14": 1545067091 + }, + "origin": "perl-mail-clamav" + }, + "fuse-openrc": { + "versions": { + "3.2.6-r1": 1548943636 + }, + "origin": "fuse3", + "dependencies": [ + "fuse-common" + ] + }, + "linux-firmware-imx": { + "versions": { + "20190322-r0": 1554980652 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "sqlite-doc": { + "versions": { + "3.26.0-r3": 1546255353 + }, + "origin": "sqlite" + }, + "fltk-fluid": { + "versions": { + "1.3.4-r2": 1545076701 + }, + "origin": "fltk", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libfltk.so.1.3", + "so:libfltk_forms.so.1.3", + "so:libfltk_images.so.1.3", + "so:libpng16.so.16", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:fluid" + ] + }, + "acpica": { + "versions": { + "20181213-r0": 1545995082 + }, + "origin": "acpica", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:acpibin", + "cmd:acpidump", + "cmd:acpiexamples", + "cmd:acpiexec", + "cmd:acpihelp", + "cmd:acpinames", + "cmd:acpisrc", + "cmd:acpixtract" + ] + }, + "gpsd-dev": { + "versions": { + "3.18.1-r1": 1549881587 + }, + "origin": "gpsd", + "dependencies": [ + "gpsd=3.18.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:libgps=3.18.1" + ] + }, + "grep": { + "versions": { + "3.1-r2": 1543926780 + }, + "origin": "grep", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ], + "provides": [ + "cmd:egrep", + "cmd:fgrep", + "cmd:grep" + ] + }, + "libotr3": { + "versions": { + "3.2.1-r4": 1543230935 + }, + "origin": "libotr3", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20" + ], + "provides": [ + "so:libotr.so.2=2.2.1" + ] + }, + "motif": { + "versions": { + "2.3.4-r2": 1545223206 + }, + "origin": "motif", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1" + ], + "provides": [ + "so:libMrm.so.4=4.0.4", + "so:libUil.so.4=4.0.4", + "so:libXm.so.4=4.0.4", + "cmd:uil" + ] + }, + "py2-zope-interface": { + "versions": { + "4.6.0-r0": 1548537849 + }, + "origin": "py-zope-interface", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "perl-test-harness-doc": { + "versions": { + "3.42-r0": 1543238856 + }, + "origin": "perl-test-harness" + }, + "py-irc-scripts": { + "versions": { + "8.5.1-r0": 1545060749 + }, + "origin": "py-irc" + }, + "py3-uritemplate": { + "versions": { + "3.0.0-r1": 1545207293 + }, + "origin": "py-uritemplate", + "dependencies": [ + "python3" + ], + "provides": [ + "py3-uritemplate.py" + ] + }, + "perl-file-slurper": { + "versions": { + "0.010-r0": 1545075943 + }, + "origin": "perl-file-slurper" + }, + "ethtool-doc": { + "versions": { + "4.19-r0": 1545820353 + }, + "origin": "ethtool" + }, + "ivykis-dev": { + "versions": { + "0.42.3-r0": 1548543104 + }, + "origin": "ivykis", + "dependencies": [ + "ivykis=0.42.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:ivykis=0.42.3" + ] + }, + "perl-text-wikiformat": { + "versions": { + "0.81-r0": 1545076739 + }, + "origin": "perl-text-wikiformat", + "dependencies": [ + "perl-uri" + ] + }, + "mqtt-exec-dbg": { + "versions": { + "0.4-r4": 1545162969 + }, + "origin": "mqtt-exec" + }, + "imake": { + "versions": { + "1.0.7-r2": 1543932076 + }, + "origin": "imake", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ccmakedep", + "cmd:cleanlinks", + "cmd:imake", + "cmd:makeg", + "cmd:mergelib", + "cmd:mkdirhier", + "cmd:mkhtmlindex", + "cmd:revpath", + "cmd:xmkmf" + ] + }, + "libpng-utils": { + "versions": { + "1.6.37-r0": 1557129095 + }, + "origin": "libpng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpng16.so.16", + "so:libz.so.1" + ], + "provides": [ + "cmd:png-fix-itxt", + "cmd:pngfix" + ] + }, + "xf86-video-vesa": { + "versions": { + "2.3.4-r5": 1545116527 + }, + "origin": "xf86-video-vesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "linenoise": { + "versions": { + "1.0-r1": 1542304878 + }, + "origin": "linenoise", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblinenoise.so.0=0.0.0" + ] + }, + "dropbear-ssh": { + "versions": { + "2018.76-r2": 1545208976 + }, + "origin": "dropbear", + "dependencies": [ + "dropbear-dbclient", + "!openssh-client", + "dropbear-dbclient=2018.76-r2" + ] + }, + "sqlite-static": { + "versions": { + "3.26.0-r3": 1546255353 + }, + "origin": "sqlite" + }, + "qemu-system-i386": { + "versions": { + "3.1.0-r3": 1551107303 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-i386" + ] + }, + "nagios-plugins-log": { + "versions": { + "2.2.1-r6": 1543933907 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "grep" + ] + }, + "dzen-gadgets": { + "versions": { + "0.9.5-r3": 1545062316 + }, + "origin": "dzen", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dbar", + "cmd:gcpubar", + "cmd:gdbar", + "cmd:textwidth" + ] + }, + "ldns-tools": { + "versions": { + "1.7.0-r2": 1545117118 + }, + "origin": "ldns", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libldns.so.2", + "so:libpcap.so.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:ldns-chaos", + "cmd:ldns-compare-zones", + "cmd:ldns-dane", + "cmd:ldns-dpa", + "cmd:ldns-gen-zone", + "cmd:ldns-key2ds", + "cmd:ldns-keyfetcher", + "cmd:ldns-keygen", + "cmd:ldns-mx", + "cmd:ldns-notify", + "cmd:ldns-nsec3-hash", + "cmd:ldns-read-zone", + "cmd:ldns-resolver", + "cmd:ldns-revoke", + "cmd:ldns-rrsig", + "cmd:ldns-signzone", + "cmd:ldns-test-edns", + "cmd:ldns-testns", + "cmd:ldns-update", + "cmd:ldns-verify-zone", + "cmd:ldns-version", + "cmd:ldns-walk", + "cmd:ldns-zcat", + "cmd:ldns-zsplit", + "cmd:ldnsd" + ] + }, + "py-flask-assets": { + "versions": { + "0.12-r0": 1545214106 + }, + "origin": "py-flask-assets", + "dependencies": [ + "python2", + "py-flask", + "py-webassets" + ] + }, + "abuild": { + "versions": { + "3.3.1-r0": 1551786762 + }, + "origin": "abuild", + "dependencies": [ + "fakeroot", + "sudo", + "pax-utils", + "openssl", + "apk-tools>=2.0.7-r1", + "libc-utils", + "attr", + "tar", + "pkgconf", + "patch", + "lzip", + "curl", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:abuild", + "cmd:abuild-addgroup", + "cmd:abuild-adduser", + "cmd:abuild-apk", + "cmd:abuild-fetch", + "cmd:abuild-gzsplit", + "cmd:abuild-keygen", + "cmd:abuild-rmtemp", + "cmd:abuild-sign", + "cmd:abuild-sudo", + "cmd:abuild-tar", + "cmd:abump", + "cmd:apkgrel", + "cmd:buildlab", + "cmd:checkapk", + "cmd:newapkbuild" + ] + }, + "xf86-input-libinput-dev": { + "versions": { + "0.28.1-r0": 1545207900 + }, + "origin": "xf86-input-libinput", + "dependencies": [ + "libinput-dev", + "xorg-server-dev", + "pkgconfig" + ], + "provides": [ + "pc:xorg-libinput=0.28.1" + ] + }, + "kyua-doc": { + "versions": { + "0.13-r3": 1542304932 + }, + "origin": "kyua" + }, + "openldap-overlay-collect": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "nsd-dbg": { + "versions": { + "4.1.26-r0": 1546948337 + }, + "origin": "nsd" + }, + "lua5.2-posixtz": { + "versions": { + "0.5-r1": 1543928315 + }, + "origin": "lua-posixtz", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "pekwm": { + "versions": { + "0.1.17-r3": 1545069433 + }, + "origin": "pekwm", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXpm.so.4", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpng16.so.16", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:pekwm" + ] + }, + "jq": { + "versions": { + "1.6-r0": 1544000550 + }, + "origin": "jq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libonig.so.5" + ], + "provides": [ + "so:libjq.so.1=1.0.4", + "cmd:jq" + ] + }, + "perl-ipc-sharelite": { + "versions": { + "0.17-r2": 1543222928 + }, + "origin": "perl-ipc-sharelite", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "sudo-dev": { + "versions": { + "1.8.25_p1-r2": 1542304827 + }, + "origin": "sudo" + }, + "openssh-keysign": { + "versions": { + "7.9_p1-r5": 1556034593 + }, + "origin": "openssh", + "dependencies": [ + "openssh-client", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libz.so.1" + ] + }, + "perl-net-smtp-tls-butmaintained": { + "versions": { + "0.24-r0": 1545235364 + }, + "origin": "perl-net-smtp-tls-butmaintained", + "dependencies": [ + "perl-net-ssleay", + "perl-io-socket-ssl", + "perl-digest-hmac" + ] + }, + "xf86-video-nouveau": { + "versions": { + "1.0.15-r3": 1545299946 + }, + "origin": "xf86-video-nouveau", + "dependencies": [ + "mesa-dri-nouveau", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_nouveau.so.2", + "so:libudev.so.1" + ] + }, + "weechat-ruby": { + "versions": { + "2.3-r0": 1545299934 + }, + "origin": "weechat", + "dependencies": [ + "weechat", + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.5" + ] + }, + "perl-html-formattext-withlinks-andtables-doc": { + "versions": { + "0.07-r0": 1545075973 + }, + "origin": "perl-html-formattext-withlinks-andtables" + }, + "oniguruma": { + "versions": { + "6.9.1-r0": 1545858473 + }, + "origin": "oniguruma", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libonig.so.5=5.0.0" + ] + }, + "glib-static": { + "versions": { + "2.58.1-r2": 1545290824 + }, + "origin": "glib", + "dependencies": [ + "gettext-static" + ] + }, + "py-cffi": { + "versions": { + "1.11.5-r0": 1543924872 + }, + "origin": "py-cffi" + }, + "linux-firmware-emi62": { + "versions": { + "20190322-r0": 1554980652 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "boost-date_time": { + "versions": { + "1.67.0-r2": 1542823631 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_date_time-mt.so.1.67.0=1.67.0", + "so:libboost_date_time.so.1.67.0=1.67.0" + ] + }, + "py2-phonenumbers": { + "versions": { + "8.10.4-r0": 1548490629 + }, + "origin": "py-phonenumbers", + "dependencies": [ + "python2" + ] + }, + "py3-more-itertools": { + "versions": { + "4.1.0-r0": 1542824886 + }, + "origin": "py-more-itertools", + "dependencies": [ + "python3" + ] + }, + "openssl": { + "versions": { + "1.1.1b-r1": 1552660176 + }, + "origin": "openssl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:openssl" + ] + }, + "perl-convert-tnef-doc": { + "versions": { + "0.18-r1": 1544001410 + }, + "origin": "perl-convert-tnef" + }, + "bitlbee": { + "versions": { + "3.5.1-r4": 1543248421 + }, + "origin": "bitlbee", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:bitlbee" + ] + }, + "docbook2x-doc": { + "versions": { + "0.8.8-r6": 1542893185 + }, + "origin": "docbook2x" + }, + "kmod-dev": { + "versions": { + "24-r1": 1542845353 + }, + "origin": "kmod", + "dependencies": [ + "kmod=24-r1", + "pkgconfig" + ], + "provides": [ + "pc:libkmod=24" + ] + }, + "py-hoedown": { + "versions": { + "0.2.3-r1": 1545116563 + }, + "origin": "py-hoedown", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:hoedownpy" + ] + }, + "xspice": { + "versions": { + "0.1.5-r5": 1545292751 + }, + "origin": "xf86-video-qxl", + "dependencies": [ + "python2", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libspice-server.so.1" + ], + "provides": [ + "cmd:Xspice" + ] + }, + "mcpp-dev": { + "versions": { + "2.7.2-r1": 1542883471 + }, + "origin": "mcpp", + "dependencies": [ + "mcpp-libs=2.7.2-r1" + ] + }, + "giflib-dev": { + "versions": { + "5.1.4-r2": 1543077198 + }, + "origin": "giflib", + "dependencies": [ + "giflib=5.1.4-r2" + ] + }, + "libcap-ng-utils": { + "versions": { + "0.7.9-r1": 1545918386 + }, + "origin": "libcap-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0" + ], + "provides": [ + "cmd:captest", + "cmd:filecap", + "cmd:netcap", + "cmd:pscap" + ] + }, + "perl-html-mason-psgihandler": { + "versions": { + "0.53-r0": 1545299878 + }, + "origin": "perl-html-mason-psgihandler", + "dependencies": [ + "perl-cgi-psgi", + "perl-html-mason" + ] + }, + "perl-cgi-fast": { + "versions": { + "2.13-r0": 1543242214 + }, + "origin": "perl-cgi-fast", + "dependencies": [ + "perl-cgi", + "perl-fcgi" + ] + }, + "testdisk-doc": { + "versions": { + "7.0-r4": 1544001241 + }, + "origin": "testdisk" + }, + "libshout-dev": { + "versions": { + "2.4.1-r5": 1545117135 + }, + "origin": "libshout", + "dependencies": [ + "libshout=2.4.1-r5", + "pc:ogg", + "pc:speex", + "pc:theora", + "pc:vorbis", + "pkgconfig" + ], + "provides": [ + "pc:shout=2.4.1" + ] + }, + "openldap-back-ldap": { + "versions": { + "2.4.47-r2": 1546016477 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "kbd-vlock": { + "versions": { + "2.0.4-r3": 1545215191 + }, + "origin": "kbd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0", + "so:libpam_misc.so.0" + ], + "provides": [ + "cmd:vlock" + ] + }, + "squid-lang-he": { + "versions": { + "4.4-r1": 1545216326 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "unifont-dev": { + "versions": { + "11.0.01-r1": 1543077262 + }, + "origin": "unifont" + }, + "lua-rrd": { + "versions": { + "1.7.0-r0": 1542924803 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5", + "so:librrd.so.8" + ] + }, + "lmdb-tools": { + "versions": { + "0.9.23-r0": 1547025175 + }, + "origin": "lmdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblmdb.so.0" + ], + "provides": [ + "cmd:mdb_copy", + "cmd:mdb_dump", + "cmd:mdb_load", + "cmd:mdb_stat" + ] + }, + "lua5.3-mqtt-publish": { + "versions": { + "0.3-r0": 1542820948 + }, + "origin": "lua-mqtt-publish", + "dependencies": [ + "lua5.3-mosquitto" + ] + }, + "py-gnome-gnomevfs": { + "versions": { + "2.28.1-r5": 1545301294 + }, + "origin": "py-gnome", + "dependencies": [ + "gnome-vfs", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgnomevfs-2.so.0", + "so:libgobject-2.0.so.0", + "so:libpython2.7.so.1.0" + ] + }, + "perl-sys-mmap": { + "versions": { + "0.19-r1": 1545075424 + }, + "origin": "perl-sys-mmap", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "libcmph": { + "versions": { + "2.0-r1": 1544000441 + }, + "origin": "cmph", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcmph.so.0=0.0.0" + ] + }, + "irqbalance": { + "versions": { + "1.5.0-r0": 1545837194 + }, + "origin": "irqbalance", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:irqbalance", + "cmd:irqbalance-ui" + ] + }, + "py3-setuptools": { + "versions": { + "40.6.3-r0": 1547652242 + }, + "origin": "py-setuptools", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:easy_install-3.6" + ] + }, + "rlog": { + "versions": { + "1.4-r4": 1545061037 + }, + "origin": "rlog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:librlog.so.5=5.0.0" + ] + }, + "lua5.1-lzlib": { + "versions": { + "0.4.3-r0": 1542845777 + }, + "origin": "lua-lzlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ] + }, + "nagios-plugins-nwstat": { + "versions": { + "2.2.1-r6": 1543933909 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "cutter": { + "versions": { + "1.04-r1": 1543927857 + }, + "origin": "cutter", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cutter" + ] + }, + "nginx-mod-http-redis2": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "cmake-doc": { + "versions": { + "3.13.0-r0": 1542820577 + }, + "origin": "cmake" + }, + "cvechecker-doc": { + "versions": { + "3.9-r1": 1543249725 + }, + "origin": "cvechecker" + }, + "py-gobject3-dev": { + "versions": { + "3.28.2-r0": 1543925245 + }, + "origin": "py-gobject3", + "dependencies": [ + "pc:gobject-2.0", + "pc:libffi", + "pkgconfig" + ], + "provides": [ + "pc:pygobject-3.0=3.28.2" + ] + }, + "busybox": { + "versions": { + "1.29.3-r10": 1548315956 + }, + "origin": "busybox", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "/bin/sh", + "cmd:busybox", + "cmd:sh" + ] + }, + "gnupg-scdaemon": { + "versions": { + "2.2.12-r0": 1545746222 + }, + "origin": "gnupg", + "dependencies": [ + "gnupg", + "/bin/sh", + "so:libassuan.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libgpg-error.so.0", + "so:libksba.so.8", + "so:libnpth.so.0", + "so:libusb-1.0.so.0" + ] + }, + "libgphoto2-doc": { + "versions": { + "2.5.16-r0": 1545116802 + }, + "origin": "libgphoto2" + }, + "perl": { + "versions": { + "5.26.3-r0": 1543998674 + }, + "origin": "perl", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libperl.so=0", + "cmd:perl", + "cmd:perl5.26.3", + "cmd:perldoc", + "cmd:pod2html", + "cmd:pod2man", + "cmd:pod2text", + "cmd:pod2usage", + "cmd:podchecker", + "cmd:podselect" + ] + }, + "perl-lwp-mediatypes": { + "versions": { + "6.02-r1": 1542821765 + }, + "origin": "perl-lwp-mediatypes", + "dependencies": [ + "perl" + ] + }, + "libasr-dev": { + "versions": { + "1.0.2-r9": 1543933802 + }, + "origin": "libasr", + "dependencies": [ + "libasr=1.0.2-r9" + ] + }, + "drbd-vanilla": { + "versions": { + "4.19.41-r0": 1557400098 + }, + "origin": "drbd-vanilla", + "dependencies": [ + "linux-vanilla=4.19.41-r0" + ] + }, + "minizip-dev": { + "versions": { + "1.2.11-r0": 1545075939 + }, + "origin": "minizip", + "dependencies": [ + "minizip=1.2.11-r0", + "pkgconfig" + ], + "provides": [ + "pc:minizip=1.2.11" + ] + }, + "boost-python3": { + "versions": { + "1.67.0-r2": 1542823632 + }, + "origin": "boost", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpython3.6m.so.1.0" + ], + "provides": [ + "so:libboost_python36-mt.so.1.67.0=1.67.0", + "so:libboost_python36.so.1.67.0=1.67.0" + ] + }, + "sircbot": { + "versions": { + "0.4-r2": 1543924833 + }, + "origin": "sircbot", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sircbot", + "cmd:sircbot-send" + ] + }, + "qemu-openrc": { + "versions": { + "0.6.0-r0": 1545215888 + }, + "origin": "qemu-openrc", + "dependencies": [ + "qemu", + "socat", + "/bin/sh" + ], + "provides": [ + "cmd:qemush" + ] + }, + "usb-modeswitch-udev": { + "versions": { + "2.5.2-r0": 1543932711 + }, + "origin": "usb-modeswitch", + "dependencies": [ + "tcl", + "eudev", + "usb-modeswitch=2.5.2-r0" + ], + "provides": [ + "cmd:usb_modeswitch_dispatcher" + ] + }, + "xen-hypervisor": { + "versions": { + "4.11.1-r1": 1545075897 + }, + "origin": "xen" + }, + "perl-hash-multivalue": { + "versions": { + "0.16-r0": 1543226611 + }, + "origin": "perl-hash-multivalue", + "dependencies": [ + "perl" + ] + }, + "perl-test-mockrandom": { + "versions": { + "1.01-r1": 1543238912 + }, + "origin": "perl-test-mockrandom" + }, + "mpt-status": { + "versions": { + "1.2.0-r0": 1545069140 + }, + "origin": "mpt-status", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mpt-status" + ] + }, + "lua5.2-xml": { + "versions": { + "130610-r5": 1543998786 + }, + "origin": "lua-xml", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-date-extract-doc": { + "versions": { + "0.06-r1": 1545163120 + }, + "origin": "perl-date-extract" + }, + "lua-ossl": { + "versions": { + "20180708-r2": 1545076448 + }, + "origin": "lua-ossl" + }, + "libgfortran": { + "versions": { + "8.3.0-r0": 1554745498 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libquadmath.so.0" + ], + "provides": [ + "so:libgfortran.so.5=5.0.0" + ] + }, + "perl-module-metadata": { + "versions": { + "1.000033-r0": 1542845546 + }, + "origin": "perl-module-metadata" + }, + "nginx-mod-http-xslt-filter": { + "versions": { + "1.14.2-r1": 1557179820 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1", + "so:libexslt.so.0", + "so:libxml2.so.2", + "so:libxslt.so.1" + ] + }, + "uwsgi-rrdtool": { + "versions": { + "2.0.17.1-r0": 1545062209 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "scstadmin-doc": { + "versions": { + "2.2.0-r2": 1545223236 + }, + "origin": "scstadmin" + }, + "perl-apache-logformat-compiler": { + "versions": { + "0.32-r0": 1544792423 + }, + "origin": "perl-apache-logformat-compiler", + "dependencies": [ + "perl-posix-strftime-compiler" + ] + }, + "perl-uri-doc": { + "versions": { + "1.74-r0": 1542821786 + }, + "origin": "perl-uri" + }, + "gstreamer0.10-tools": { + "versions": { + "0.10.36-r3": 1544000752 + }, + "origin": "gstreamer0.10", + "dependencies": [ + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-0.10.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:gst-feedback", + "cmd:gst-feedback-0.10", + "cmd:gst-inspect", + "cmd:gst-inspect-0.10", + "cmd:gst-launch", + "cmd:gst-launch-0.10", + "cmd:gst-typefind", + "cmd:gst-typefind-0.10", + "cmd:gst-xmlinspect", + "cmd:gst-xmlinspect-0.10", + "cmd:gst-xmllaunch", + "cmd:gst-xmllaunch-0.10" + ] + }, + "postgresql-pltcl": { + "versions": { + "11.2-r0": 1554274176 + }, + "origin": "postgresql", + "dependencies": [ + "pgtcl", + "so:libc.musl-x86_64.so.1", + "so:libtcl8.6.so" + ] + }, + "lua5.3-augeas": { + "versions": { + "0.1.2-r4": 1543246061 + }, + "origin": "lua-augeas", + "dependencies": [ + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1" + ] + }, + "alsaconf": { + "versions": { + "1.1.8-r0": 1547112822 + }, + "origin": "alsa-utils", + "dependencies": [ + "alsa-utils", + "bash" + ], + "provides": [ + "cmd:alsaconf" + ] + }, + "cramfs": { + "versions": { + "1.1-r2": 1545165278 + }, + "origin": "cramfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:cramfsck", + "cmd:mkcramfs" + ] + }, + "f2fs-tools-dev": { + "versions": { + "1.12.0-r0": 1546344802 + }, + "origin": "f2fs-tools", + "dependencies": [ + "f2fs-tools-libs=1.12.0-r0" + ] + }, + "hunspell-pt-br": { + "versions": { + "20131017-r0": 1545300017 + }, + "origin": "hunspell-pt-br" + }, + "ulogd": { + "versions": { + "2.0.7-r0": 1545300897 + }, + "origin": "ulogd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0", + "so:libnetfilter_acct.so.1", + "so:libnetfilter_conntrack.so.3", + "so:libnetfilter_log.so.1", + "so:libnfnetlink.so.0" + ], + "provides": [ + "cmd:ulogd" + ] + }, + "lcms2": { + "versions": { + "2.9-r1": 1542824190 + }, + "origin": "lcms2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblcms2.so.2=2.0.8" + ] + }, + "linux-firmware-cis": { + "versions": { + "20190322-r0": 1554980653 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "collectd": { + "versions": { + "5.8.0-r3": 1546422679 + }, + "origin": "collectd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libnetsnmpagent.so.35", + "so:libyajl.so.2" + ], + "provides": [ + "cmd:collectd", + "cmd:collectdmon" + ] + }, + "xorriso": { + "versions": { + "1.4.8-r0": 1544797305 + }, + "origin": "libisoburn", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libisoburn.so.1" + ], + "provides": [ + "cmd:mkisofs", + "cmd:osirrox", + "cmd:xorrecord", + "cmd:xorriso", + "cmd:xorrisofs" + ] + }, + "lxc-libs": { + "versions": { + "3.1.0-r1": 1549966161 + }, + "origin": "lxc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libseccomp.so.2" + ], + "provides": [ + "so:liblxc.so.1=1.5.0" + ] + }, + "libnfs-dev": { + "versions": { + "3.0.0-r0": 1544799293 + }, + "origin": "libnfs", + "dependencies": [ + "libnfs=3.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnfs=3.0.0" + ] + }, + "mp3splt-gtk-doc": { + "versions": { + "0.9.2-r2": 1545069036 + }, + "origin": "mp3splt-gtk" + }, + "py3-six": { + "versions": { + "1.11.0-r0": 1542823019 + }, + "origin": "py-six", + "dependencies": [ + "python3" + ] + }, + "charybdis-dev": { + "versions": { + "3.5.6-r1": 1542883316 + }, + "origin": "charybdis", + "dependencies": [ + "pkgconfig" + ] + }, + "libgpg-error": { + "versions": { + "1.33-r0": 1545838267 + }, + "origin": "libgpg-error", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgpg-error.so.0=0.25.0", + "cmd:gpg-error", + "cmd:yat2m" + ] + }, + "perl-extutils-config": { + "versions": { + "0.008-r0": 1542924647 + }, + "origin": "perl-extutils-config" + }, + "py-pillow": { + "versions": { + "5.4.1-r0": 1548112615 + }, + "origin": "py-pillow", + "dependencies": [ + "py-olefile" + ] + }, + "gawk-doc": { + "versions": { + "4.2.1-r0": 1542301294 + }, + "origin": "gawk" + }, + "lua-yaml": { + "versions": { + "1.1.2-r1": 1544000073 + }, + "origin": "lua-yaml" + }, + "jbig2dec": { + "versions": { + "0.15-r0": 1545207929 + }, + "origin": "jbig2dec", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libjbig2dec.so.0=0.0.0", + "cmd:jbig2dec" + ] + }, + "tango-icon-theme": { + "versions": { + "0.8.90-r4": 1545235345 + }, + "origin": "tango-icon-theme" + }, + "libetpan-dev": { + "versions": { + "1.9.2-r0": 1548063822 + }, + "origin": "libetpan", + "dependencies": [ + "cyrus-sasl-dev", + "db-dev", + "libetpan=1.9.2-r0" + ], + "provides": [ + "cmd:libetpan-config" + ] + }, + "zstd-libs": { + "versions": { + "1.3.8-r0": 1546966444 + }, + "origin": "zstd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libzstd.so.1=1.3.8" + ] + }, + "collectd-log_logstash": { + "versions": { + "5.8.0-r3": 1546422678 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libyajl.so.2" + ] + }, + "perl-super": { + "versions": { + "1.20141117-r0": 1543932005 + }, + "origin": "perl-super", + "dependencies": [ + "perl" + ] + }, + "xf86-input-synaptics-doc": { + "versions": { + "1.9.1-r1": 1545154609 + }, + "origin": "xf86-input-synaptics" + }, + "xautolock-doc": { + "versions": { + "2.2-r4": 1543932088 + }, + "origin": "xautolock" + }, + "devicemaster-linux-doc": { + "versions": { + "7.15-r0": 1543999030 + }, + "origin": "devicemaster-linux" + }, + "perl-ipc-run3-doc": { + "versions": { + "0.048-r0": 1542973046 + }, + "origin": "perl-ipc-run3" + }, + "openldap-overlay-deref": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "py-yaml": { + "versions": { + "4.1-r0": 1544798590 + }, + "origin": "py-yaml" + }, + "squid-lang-uk": { + "versions": { + "4.4-r1": 1545216330 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "gross-doc": { + "versions": { + "1.0.2-r11": 1545062670 + }, + "origin": "gross" + }, + "cpufreqd-doc": { + "versions": { + "2.4.2-r4": 1543998859 + }, + "origin": "cpufreqd" + }, + "rsyslog-rabbitmq": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:librabbitmq.so.4" + ], + "provides": [ + "rsyslog-omrabbitmq=8.40.0-r3" + ] + }, + "perl-test-refcount": { + "versions": { + "0.08-r0": 1544799031 + }, + "origin": "perl-test-refcount" + }, + "lua5.1-mqtt-publish": { + "versions": { + "0.3-r0": 1542820947 + }, + "origin": "lua-mqtt-publish", + "dependencies": [ + "lua5.1-mosquitto" + ] + }, + "elinks": { + "versions": { + "0.13-r6": 1543934620 + }, + "origin": "elinks", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libexpat.so.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:elinks" + ] + }, + "uwsgi-cheaper_busyness": { + "versions": { + "2.0.17.1-r0": 1545062197 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "busybox-extras": { + "versions": { + "1.29.3-r10": 1548315956 + }, + "origin": "busybox", + "dependencies": [ + "busybox", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:busybox-extras" + ] + }, + "perl-devel-symdump-doc": { + "versions": { + "2.18-r0": 1542845656 + }, + "origin": "perl-devel-symdump" + }, + "py3-lockfile": { + "versions": { + "0.12.2-r0": 1544799212 + }, + "origin": "py-lockfile", + "dependencies": [ + "python3" + ] + }, + "msmtp-doc": { + "versions": { + "1.8.1-r1": 1545062528 + }, + "origin": "msmtp" + }, + "freeradius-rest": { + "versions": { + "3.0.17-r5": 1556201989 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.17-r5", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libjson-c.so.4" + ], + "provides": [ + "so:rlm_rest.so=0" + ] + }, + "lxsession": { + "versions": { + "0.5.3-r0": 1545299866 + }, + "origin": "lxsession", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpolkit-agent-1.so.0", + "so:libpolkit-gobject-1.so.0", + "so:libunique-1.0.so.0" + ], + "provides": [ + "cmd:lxclipboard", + "cmd:lxlock", + "cmd:lxpolkit", + "cmd:lxsession", + "cmd:lxsession-db", + "cmd:lxsession-default", + "cmd:lxsession-default-apps", + "cmd:lxsession-default-terminal", + "cmd:lxsession-edit", + "cmd:lxsession-logout", + "cmd:lxsession-xdg-autostart", + "cmd:lxsettings-daemon" + ] + }, + "pingu-doc": { + "versions": { + "1.5-r2": 1544000080 + }, + "origin": "pingu" + }, + "audit-openrc": { + "versions": { + "2.8.4-r0": 1543245856 + }, + "origin": "audit" + }, + "kmod": { + "versions": { + "24-r1": 1542845356 + }, + "origin": "kmod", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:liblzma.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libkmod.so.2=2.3.2", + "cmd:depmod", + "cmd:insmod", + "cmd:kmod", + "cmd:lsmod", + "cmd:modinfo", + "cmd:modprobe", + "cmd:rmmod" + ] + }, + "linux-firmware-ess": { + "versions": { + "20190322-r0": 1554980652 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "tcl": { + "versions": { + "8.6.9-r0": 1545915062 + }, + "origin": "tcl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libtcl8.6.so=0", + "cmd:tclsh", + "cmd:tclsh8.6" + ] + }, + "nginx-mod-http-set-misc": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "nginx-mod-devel-kit", + "so:libc.musl-x86_64.so.1" + ] + }, + "freeradius-dev": { + "versions": { + "3.0.17-r5": 1556201987 + }, + "origin": "freeradius" + }, + "qt-dev": { + "versions": { + "4.8.7-r11": 1545075085 + }, + "origin": "qt", + "dependencies": [ + "mesa-dev", + "libice-dev", + "libsm-dev", + "libx11-dev", + "libxext-dev", + "libxrender-dev", + "alsa-lib-dev", + "openssl-dev", + "fontconfig-dev", + "freetype-dev", + "glib-dev", + "libpng-dev", + "zlib-dev", + "sqlite-dev", + "dbus-dev", + "pkgconfig", + "qt-webkit=4.8.7-r11", + "qt-x11=4.8.7-r11", + "qt=4.8.7-r11", + "so:libQt3Support.so.4", + "so:libQtCore.so.4", + "so:libQtDBus.so.4", + "so:libQtDeclarative.so.4", + "so:libQtDesigner.so.4", + "so:libQtDesignerComponents.so.4", + "so:libQtGui.so.4", + "so:libQtHelp.so.4", + "so:libQtNetwork.so.4", + "so:libQtWebKit.so.4", + "so:libQtXml.so.4", + "so:libQtXmlPatterns.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "pc:Qt3Support=4.8.7", + "pc:QtCLucene=4.8.7", + "pc:QtCore=4.8.7", + "pc:QtDBus=4.8.7", + "pc:QtDeclarative=4.8.7", + "pc:QtDesigner=4.8.7", + "pc:QtDesignerComponents=4.8.7", + "pc:QtGui=4.8.7", + "pc:QtHelp=4.8.7", + "pc:QtMultimedia=4.8.7", + "pc:QtNetwork=4.8.7", + "pc:QtOpenGL=4.8.7", + "pc:QtScript=4.8.7", + "pc:QtScriptTools=4.8.7", + "pc:QtSql=4.8.7", + "pc:QtSvg=4.8.7", + "pc:QtTest=4.8.7", + "pc:QtUiTools=4.8.7", + "pc:QtWebKit=4.9.4", + "pc:QtXml=4.8.7", + "pc:QtXmlPatterns=4.8.7", + "cmd:designer", + "cmd:lconvert", + "cmd:linguist", + "cmd:lrelease", + "cmd:lupdate", + "cmd:moc", + "cmd:pixeltool", + "cmd:qcollectiongenerator", + "cmd:qdbuscpp2xml", + "cmd:qdbusxml2cpp", + "cmd:qdoc3", + "cmd:qhelpconverter", + "cmd:qhelpgenerator", + "cmd:qmake", + "cmd:qt3to4", + "cmd:qttracereplay", + "cmd:rcc", + "cmd:uic", + "cmd:uic3", + "cmd:xmlpatterns", + "cmd:xmlpatternsvalidator" + ] + }, + "wxgtk2.8-base": { + "versions": { + "2.8.12.1-r4": 1545075287 + }, + "origin": "wxgtk2.8", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libwx_baseu-2.8.so.0=0.8.0", + "so:libwx_baseu_net-2.8.so.0=0.8.0", + "so:libwx_baseu_xml-2.8.so.0=0.8.0" + ] + }, + "libidl-doc": { + "versions": { + "0.8.14-r2": 1543223346 + }, + "origin": "libidl" + }, + "gnutls-doc": { + "versions": { + "3.6.7-r0": 1555049964 + }, + "origin": "gnutls" + }, + "less-doc": { + "versions": { + "530-r0": 1542301312 + }, + "origin": "less" + }, + "mosquitto-openrc": { + "versions": { + "1.5.6-r0": 1549970968 + }, + "origin": "mosquitto" + }, + "lvm2-extra": { + "versions": { + "2.02.182-r0": 1543928934 + }, + "origin": "lvm2", + "dependencies": [ + "bash", + "coreutils" + ], + "provides": [ + "cmd:blkdeactivate", + "cmd:fsadm", + "cmd:lvmconf", + "cmd:lvmdump" + ] + }, + "freeradius-redis": { + "versions": { + "3.0.17-r5": 1556201989 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.17-r5", + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.14" + ], + "provides": [ + "so:rlm_redis.so=0", + "so:rlm_rediswho.so=0" + ] + }, + "avahi-ui": { + "versions": { + "0.6.31-r7": 1545163747 + }, + "origin": "avahi-ui", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-glib.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libavahi-ui.so.0=0.1.4" + ] + }, + "py3-py": { + "versions": { + "1.5.3-r0": 1542824876 + }, + "origin": "py-py", + "dependencies": [ + "python3" + ] + }, + "acf-alpine-baselayout": { + "versions": { + "0.13.2-r0": 1545116952 + }, + "origin": "acf-alpine-baselayout", + "dependencies": [ + "acf-core", + "lua-json4", + "lua-posix" + ] + }, + "perl-module-runtime-doc": { + "versions": { + "0.016-r1": 1542845694 + }, + "origin": "perl-module-runtime" + }, + "gdk-pixbuf-dev": { + "versions": { + "2.36.11-r2": 1543253989 + }, + "origin": "gdk-pixbuf", + "dependencies": [ + "gdk-pixbuf=2.36.11-r2", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:libpng16", + "pkgconfig" + ], + "provides": [ + "pc:gdk-pixbuf-2.0=2.36.11", + "pc:gdk-pixbuf-xlib-2.0=2.36.11" + ] + }, + "speex-doc": { + "versions": { + "1.2.0-r0": 1544799248 + }, + "origin": "speex" + }, + "nagios-web": { + "versions": { + "3.5.1-r4": 1543924480 + }, + "origin": "nagios", + "dependencies": [ + "perl" + ] + }, + "uriparser": { + "versions": { + "0.9.1-r0": 1548111967 + }, + "origin": "uriparser", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liburiparser.so.1=1.0.24", + "cmd:uriparse" + ] + }, + "libunique3": { + "versions": { + "3.0.2-r0": 1543927464 + }, + "origin": "libunique3", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk-3.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0" + ], + "provides": [ + "so:libunique-3.0.so.0=0.0.2" + ] + }, + "collectd-ipmi": { + "versions": { + "5.8.0-r3": 1546422678 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libOpenIPMI.so.0", + "so:libOpenIPMIpthread.so.0", + "so:libc.musl-x86_64.so.1" + ] + }, + "subversion": { + "versions": { + "1.11.1-r0": 1548692376 + }, + "origin": "subversion", + "dependencies": [ + "/bin/sh", + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libsvn_client-1.so.0", + "so:libsvn_delta-1.so.0", + "so:libsvn_diff-1.so.0", + "so:libsvn_fs-1.so.0", + "so:libsvn_fs_fs-1.so.0", + "so:libsvn_ra-1.so.0", + "so:libsvn_ra_svn-1.so.0", + "so:libsvn_repos-1.so.0", + "so:libsvn_subr-1.so.0", + "so:libsvn_wc-1.so.0" + ], + "provides": [ + "cmd:svn", + "cmd:svnadmin", + "cmd:svnbench", + "cmd:svndumpfilter", + "cmd:svnfsfs", + "cmd:svnlook", + "cmd:svnmucc", + "cmd:svnrdump", + "cmd:svnserve", + "cmd:svnsync", + "cmd:svnversion" + ] + }, + "net-tools-dbg": { + "versions": { + "1.60_git20140218-r2": 1545216351 + }, + "origin": "net-tools", + "dependencies": [ + "mii-tool" + ] + }, + "userspace-rcu": { + "versions": { + "0.10.1-r0": 1543249590 + }, + "origin": "userspace-rcu", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liburcu-bp.so.6=6.0.0", + "so:liburcu-cds.so.6=6.0.0", + "so:liburcu-common.so.6=6.0.0", + "so:liburcu-mb.so.6=6.0.0", + "so:liburcu-qsbr.so.6=6.0.0", + "so:liburcu-signal.so.6=6.0.0", + "so:liburcu.so.6=6.0.0" + ] + }, + "python2-wininst": { + "versions": { + "2.7.16-r1": 1557171399 + }, + "origin": "python2" + }, + "py-google-api-python-client": { + "versions": { + "1.7.7-r0": 1546936521 + }, + "origin": "py-google-api-python-client", + "dependencies": [ + "py-httplib2", + "py-oauth2client", + "py-uritemplate", + "py-six" + ] + }, + "perl-db_file": { + "versions": { + "1.843-r0": 1545061210 + }, + "origin": "perl-db_file", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so" + ] + }, + "py-boto": { + "versions": { + "2.49.0-r0": 1544000446 + }, + "origin": "py-boto", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:asadmin", + "cmd:bundle_image", + "cmd:cfadmin", + "cmd:cq", + "cmd:cwutil", + "cmd:dynamodb_dump", + "cmd:dynamodb_load", + "cmd:elbadmin", + "cmd:fetch_file", + "cmd:glacier", + "cmd:instance_events", + "cmd:kill_instance", + "cmd:launch_instance", + "cmd:list_instances", + "cmd:lss3", + "cmd:mturk", + "cmd:pyami_sendmail", + "cmd:route53", + "cmd:s3put", + "cmd:sdbadmin", + "cmd:taskadmin" + ] + }, + "perl-file-which-doc": { + "versions": { + "1.22-r0": 1545214292 + }, + "origin": "perl-file-which" + }, + "procps-lang": { + "versions": { + "3.3.15-r0": 1543934062 + }, + "origin": "procps" + }, + "py3-icu": { + "versions": { + "1.9.8-r2": 1545165267 + }, + "origin": "py-icu", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libicui18n.so.62", + "so:libicuuc.so.62", + "so:libpython3.6m.so.1.0", + "so:libstdc++.so.6" + ] + }, + "py3-tox": { + "versions": { + "3.2.1-r1": 1545060602 + }, + "origin": "py-tox", + "dependencies": [ + "py3-py", + "py3-pluggy", + "py3-argparse", + "python3" + ], + "provides": [ + "cmd:tox", + "cmd:tox-quickstart" + ] + }, + "aspell-fr": { + "versions": { + "0.50_p3-r1": 1545116568 + }, + "origin": "aspell-fr" + }, + "json-glib": { + "versions": { + "1.4.4-r0": 1545837175 + }, + "origin": "json-glib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libjson-glib-1.0.so.0=0.400.4" + ] + }, + "graphite2-static": { + "versions": { + "1.3.12-r1": 1542823993 + }, + "origin": "graphite2" + }, + "cairo-dev": { + "versions": { + "1.16.0-r1": 1546948314 + }, + "origin": "cairo", + "dependencies": [ + "fontconfig-dev", + "freetype-dev", + "libxrender-dev", + "pixman-dev", + "xcb-util-dev", + "libxext-dev", + "cairo-tools", + "cairo-gobject=1.16.0-r1", + "cairo=1.16.0-r1", + "pc:fontconfig>=2.2.95", + "pc:freetype2>=9.7.3", + "pc:glib-2.0>=2.14", + "pc:gobject-2.0", + "pc:libpng", + "pc:pixman-1>=0.30.0", + "pc:x11", + "pc:xcb-render>=1.6", + "pc:xcb-shm", + "pc:xcb>=1.6", + "pc:xext", + "pc:xrender>=0.6", + "pkgconfig" + ], + "provides": [ + "pc:cairo-fc=1.16.0", + "pc:cairo-ft=1.16.0", + "pc:cairo-gobject=1.16.0", + "pc:cairo-pdf=1.16.0", + "pc:cairo-png=1.16.0", + "pc:cairo-ps=1.16.0", + "pc:cairo-script=1.16.0", + "pc:cairo-svg=1.16.0", + "pc:cairo-tee=1.16.0", + "pc:cairo-xcb-shm=1.16.0", + "pc:cairo-xcb=1.16.0", + "pc:cairo-xlib-xrender=1.16.0", + "pc:cairo-xlib=1.16.0", + "pc:cairo=1.16.0" + ] + }, + "perl-dbd-sqlite-doc": { + "versions": { + "1.62-r0": 1546944025 + }, + "origin": "perl-dbd-sqlite" + }, + "avahi-ui-tools": { + "versions": { + "0.6.31-r7": 1545163746 + }, + "origin": "avahi-ui", + "dependencies": [ + "py-gtk", + "py-dbus", + "py-gdbm", + "py2-avahi", + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-ui-gtk3.so.0", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:avahi-bookmarks", + "cmd:avahi-discover", + "cmd:bshell", + "cmd:bssh", + "cmd:bvnc" + ] + }, + "lua5.1-iconv": { + "versions": { + "7-r1": 1545062767 + }, + "origin": "lua-iconv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "xf86-input-evdev-doc": { + "versions": { + "2.10.6-r0": 1545229727 + }, + "origin": "xf86-input-evdev" + }, + "perl-css-minifier-xs": { + "versions": { + "0.09-r3": 1545062656 + }, + "origin": "perl-css-minifier-xs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "dialog": { + "versions": { + "1.3.20180621-r0": 1543925866 + }, + "origin": "dialog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:dialog" + ] + }, + "xrdp-dev": { + "versions": { + "0.9.9-r0": 1545859298 + }, + "origin": "xrdp", + "dependencies": [ + "pkgconfig", + "xrdp=0.9.9-r0" + ], + "provides": [ + "pc:libpainter=0.1.1", + "pc:rfxcodec=0.1.4", + "pc:xrdp=0.9.9" + ] + }, + "libcap-ng": { + "versions": { + "0.7.9-r1": 1545918386 + }, + "origin": "libcap-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcap-ng.so.0=0.0.0" + ] + }, + "hicolor-icon-theme": { + "versions": { + "0.17-r0": 1543925362 + }, + "origin": "hicolor-icon-theme" + }, + "acl": { + "versions": { + "2.2.52-r5": 1542304043 + }, + "origin": "acl", + "dependencies": [ + "so:libacl.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:chacl", + "cmd:getfacl", + "cmd:setfacl" + ] + }, + "ed": { + "versions": { + "1.14.2-r2": 1542304952 + }, + "origin": "ed", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ed", + "cmd:red" + ] + }, + "perl-authen-sasl-doc": { + "versions": { + "2.16-r1": 1545215984 + }, + "origin": "perl-authen-sasl" + }, + "guile-libs": { + "versions": { + "2.0.14-r0": 1543227920 + }, + "origin": "guile", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libgc.so.1", + "so:libgmp.so.10", + "so:libltdl.so.7", + "so:libunistring.so.2" + ], + "provides": [ + "so:libguile-2.0.so.22=22.8.1" + ] + }, + "sdl2_ttf-dev": { + "versions": { + "2.0.14-r1": 1543934466 + }, + "origin": "sdl2_ttf", + "dependencies": [ + "freetype-dev", + "sdl2-dev", + "pc:sdl2>=2.0.0", + "pkgconfig", + "sdl2_ttf=2.0.14-r1" + ], + "provides": [ + "pc:SDL2_ttf=2.0.14" + ] + }, + "nginx-mod-stream-geoip": { + "versions": { + "1.14.2-r1": 1557179820 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "mesa-dri-intel": { + "versions": { + "18.1.7-r2": 1554455972 + }, + "origin": "mesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_intel.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "taskd": { + "versions": { + "1.1.0-r4": 1545163049 + }, + "origin": "taskd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libstdc++.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:taskd", + "cmd:taskdctl" + ] + }, + "zlib": { + "versions": { + "1.2.11-r1": 1542300123 + }, + "origin": "zlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libz.so.1=1.2.11" + ] + }, + "heimdal-openrc": { + "versions": { + "7.5.0-r2": 1542819374 + }, + "origin": "heimdal", + "dependencies": [ + "krb5-conf" + ] + }, + "py3-django-tables2": { + "versions": { + "1.21.2-r0": 1543925703 + }, + "origin": "py-django-tables2", + "dependencies": [ + "py3-django", + "py3-six", + "python3" + ] + }, + "libgpg-error-dev": { + "versions": { + "1.33-r0": 1545838267 + }, + "origin": "libgpg-error", + "dependencies": [ + "libgpg-error=1.33-r0", + "pkgconfig" + ], + "provides": [ + "pc:gpg-error=1.33", + "cmd:gpg-error-config", + "cmd:gpgrt-config" + ] + }, + "perl-dbi": { + "versions": { + "1.642-r0": 1544792614 + }, + "origin": "perl-dbi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dbilogstrip", + "cmd:dbiprof", + "cmd:dbiproxy" + ] + }, + "gstreamer-tools": { + "versions": { + "1.14.4-r0": 1543254159 + }, + "origin": "gstreamer", + "dependencies": [ + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:gst-inspect-1.0", + "cmd:gst-launch-1.0", + "cmd:gst-stats-1.0", + "cmd:gst-typefind-1.0" + ] + }, + "xcmsdb-doc": { + "versions": { + "1.0.5-r0": 1545293076 + }, + "origin": "xcmsdb" + }, + "mosquitto-dev": { + "versions": { + "1.5.6-r0": 1549970968 + }, + "origin": "mosquitto", + "dependencies": [ + "mosquitto-libs++=1.5.6-r0", + "mosquitto-libs=1.5.6-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmosquitto=1.5.6", + "pc:libmosquittopp=1.5.6" + ] + }, + "asterisk-dahdi": { + "versions": { + "15.7.1-r0": 1546247586 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpri.so.1.4", + "so:libtonezone.so.2" + ] + }, + "lua5.2-stringy": { + "versions": { + "0.5.0-r1": 1545116589 + }, + "origin": "lua-stringy", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "lutok-dev": { + "versions": { + "0.4-r2": 1542304897 + }, + "origin": "lutok", + "dependencies": [ + "lua5.3-dev", + "lutok=0.4-r2", + "pkgconfig" + ], + "provides": [ + "pc:lutok=0.4" + ] + }, + "py3-requests-oauthlib": { + "versions": { + "0.8.0-r1": 1545062565 + }, + "origin": "py-requests-oauthlib", + "dependencies": [ + "py3-oauthlib", + "py3-requests", + "python3" + ] + }, + "libinput-dev": { + "versions": { + "1.12.4-r0": 1545734494 + }, + "origin": "libinput", + "dependencies": [ + "libinput-libs=1.12.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:libinput=1.12.4" + ] + }, + "wv-dev": { + "versions": { + "1.2.9-r3": 1545300661 + }, + "origin": "wv", + "dependencies": [ + "pc:libgsf-1>=1.13.0", + "pkgconfig", + "wv=1.2.9-r3" + ], + "provides": [ + "pc:wv-1.0=1.2.9" + ] + }, + "gperf-doc": { + "versions": { + "3.1-r2": 1542822039 + }, + "origin": "gperf" + }, + "slang-dev": { + "versions": { + "2.3.2-r0": 1543924688 + }, + "origin": "slang", + "dependencies": [ + "pkgconfig", + "slang=2.3.2-r0" + ], + "provides": [ + "pc:slang=2.3.2" + ] + }, + "swish-e-doc": { + "versions": { + "2.4.7-r8": 1545163773 + }, + "origin": "swish-e" + }, + "frotz-doc": { + "versions": { + "2.44-r0": 1545293066 + }, + "origin": "frotz" + }, + "haveged-openrc": { + "versions": { + "1.9.4-r4": 1557129010 + }, + "origin": "haveged" + }, + "lua-ldap": { + "versions": { + "1.2.3-r5": 1543223255 + }, + "origin": "lua-ldap" + }, + "samba-doc": { + "versions": { + "4.8.11-r1": 1555334972 + }, + "origin": "samba" + }, + "audit-static": { + "versions": { + "2.8.4-r0": 1543245855 + }, + "origin": "audit" + }, + "rsyslog-mmfields": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-gtk": { + "versions": { + "2.24.0-r15": 1543927630 + }, + "origin": "py-gtk", + "dependencies": [ + "py2-cairo", + "py-gobject", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglade-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0" + ], + "provides": [ + "cmd:pygtk-codegen-2.0" + ] + }, + "iwlwifi-5150-ucode": { + "versions": { + "8.24.2.2-r1": 1545208933 + }, + "origin": "iwlwifi-5150-ucode" + }, + "mercurial-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308087, + "4.9.1-r0": 1557160461 + }, + "origin": "mercurial", + "dependencies": [ + "zsh" + ] + }, + "acf-asterisk": { + "versions": { + "0.7.0-r2": 1545073807 + }, + "origin": "acf-asterisk", + "dependencies": [ + "acf-core", + "asterisk" + ] + }, + "perl-test-mocktime-doc": { + "versions": { + "0.17-r0": 1544792416 + }, + "origin": "perl-test-mocktime" + }, + "util-linux-dev": { + "versions": { + "2.33-r0": 1545307436 + }, + "origin": "util-linux", + "dependencies": [ + "libblkid=2.33-r0", + "libfdisk=2.33-r0", + "libmount=2.33-r0", + "libsmartcols=2.33-r0", + "libuuid=2.33-r0", + "pkgconfig" + ], + "provides": [ + "pc:blkid=2.33.0", + "pc:fdisk=2.33.0", + "pc:mount=2.33.0", + "pc:smartcols=2.33.0", + "pc:uuid=2.33.0" + ] + }, + "python3-dev": { + "versions": { + "3.6.8-r2": 1554747638 + }, + "origin": "python3", + "dependencies": [ + "pkgconfig", + "python3=3.6.8-r2" + ], + "provides": [ + "pc:python-3.6=3.6", + "pc:python-3.6m=3.6", + "pc:python3=3.6", + "cmd:python3-config", + "cmd:python3.6-config", + "cmd:python3.6m-config" + ] + }, + "libxft-dev": { + "versions": { + "2.3.2-r3": 1542824035 + }, + "origin": "libxft", + "dependencies": [ + "zlib-dev", + "libxft=2.3.2-r3", + "pc:fontconfig", + "pc:freetype2", + "pc:xproto", + "pc:xrender", + "pkgconfig" + ], + "provides": [ + "pc:xft=2.3.2" + ] + }, + "libuuid": { + "versions": { + "2.33-r0": 1545307436 + }, + "origin": "util-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libuuid.so.1=1.3.0" + ] + }, + "sdl2_image": { + "versions": { + "2.0.4-r0": 1545062544 + }, + "origin": "sdl2_image", + "dependencies": [ + "so:libSDL2-2.0.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL2_image-2.0.so.0=0.2.2" + ] + }, + "gst-plugins-base-dev": { + "versions": { + "1.14.4-r0": 1543928664 + }, + "origin": "gst-plugins-base", + "dependencies": [ + "gst-plugins-base=1.14.4-r0", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gstreamer-1.0", + "pc:gstreamer-base-1.0", + "pc:orc-0.4", + "pkgconfig" + ], + "provides": [ + "pc:gstreamer-allocators-1.0=1.14.4", + "pc:gstreamer-app-1.0=1.14.4", + "pc:gstreamer-audio-1.0=1.14.4", + "pc:gstreamer-fft-1.0=1.14.4", + "pc:gstreamer-gl-1.0=1.14.4", + "pc:gstreamer-pbutils-1.0=1.14.4", + "pc:gstreamer-plugins-base-1.0=1.14.4", + "pc:gstreamer-riff-1.0=1.14.4", + "pc:gstreamer-rtp-1.0=1.14.4", + "pc:gstreamer-rtsp-1.0=1.14.4", + "pc:gstreamer-sdp-1.0=1.14.4", + "pc:gstreamer-tag-1.0=1.14.4", + "pc:gstreamer-video-1.0=1.14.4" + ] + }, + "libxext-dev": { + "versions": { + "1.3.3-r3": 1542822854 + }, + "origin": "libxext", + "dependencies": [ + "libxau-dev", + "libxext=1.3.3-r3", + "pc:x11", + "pc:xextproto", + "pkgconfig" + ], + "provides": [ + "pc:xext=1.3.3" + ] + }, + "lua5.3-cjson": { + "versions": { + "2.1.0-r8": 1542820884 + }, + "origin": "lua-cjson", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "recode": { + "versions": { + "3.6-r2": 1543245780 + }, + "origin": "recode", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:librecode.so.0=0.0.0", + "cmd:recode" + ] + }, + "groff-doc": { + "versions": { + "1.22.3-r2": 1542819579 + }, + "origin": "groff" + }, + "xf86-video-s3": { + "versions": { + "0.6.5-r11": 1544792315 + }, + "origin": "xf86-video-s3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libsodium-dev": { + "versions": { + "1.0.16-r0": 1544000835 + }, + "origin": "libsodium", + "dependencies": [ + "libsodium=1.0.16-r0", + "pkgconfig" + ], + "provides": [ + "pc:libsodium=1.0.16" + ] + }, + "qemu-mips": { + "versions": { + "3.1.0-r3": 1551107300 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mips" + ] + }, + "perl-mail-clamav": { + "versions": { + "0.29-r14": 1545067092 + }, + "origin": "perl-mail-clamav", + "dependencies": [ + "perl", + "clamav", + "so:libc.musl-x86_64.so.1", + "so:libclamav.so.7" + ] + }, + "xfsprogs-dev": { + "versions": { + "4.19.0-r1": 1546597447 + }, + "origin": "xfsprogs", + "dependencies": [ + "xfsprogs-libs=4.19.0-r1" + ] + }, + "perl-html-formattext-withlinks-andtables": { + "versions": { + "0.07-r0": 1545075975 + }, + "origin": "perl-html-formattext-withlinks-andtables", + "dependencies": [ + "perl-html-formattext-withlinks" + ] + }, + "aspell-dev": { + "versions": { + "0.60.6.1-r13": 1542965830 + }, + "origin": "aspell", + "dependencies": [ + "aspell-utils", + "aspell-libs=0.60.6.1-r13" + ], + "provides": [ + "cmd:pspell-config" + ] + }, + "apk-tools-static": { + "versions": { + "2.10.3-r1": 1547112494 + }, + "origin": "apk-tools", + "provides": [ + "cmd:apk.static" + ] + }, + "openldap-clients": { + "versions": { + "2.4.47-r2": 1546016477 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libsasl2.so.3" + ], + "provides": [ + "cmd:ldapadd", + "cmd:ldapcompare", + "cmd:ldapdelete", + "cmd:ldapexop", + "cmd:ldapmodify", + "cmd:ldapmodrdn", + "cmd:ldappasswd", + "cmd:ldapsearch", + "cmd:ldapurl", + "cmd:ldapwhoami" + ] + }, + "perl-inline-c": { + "versions": { + "0.78-r0": 1545067076 + }, + "origin": "perl-inline-c" + }, + "syslog-ng-xml": { + "versions": { + "3.19.1-r0": 1548543151 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "tcl-doc": { + "versions": { + "8.6.9-r0": 1545915060 + }, + "origin": "tcl" + }, + "lua5.3-ldbus": { + "versions": { + "20150430-r2": 1545300024 + }, + "origin": "lua-ldbus", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3" + ] + }, + "pcre-doc": { + "versions": { + "8.42-r1": 1545067004 + }, + "origin": "pcre" + }, + "usbredir-doc": { + "versions": { + "0.7.1-r0": 1543223286 + }, + "origin": "usbredir" + }, + "py2-openssl": { + "versions": { + "18.0.0-r0": 1545223367 + }, + "origin": "py-openssl", + "dependencies": [ + "py2-cryptography", + "py2-six", + "python2" + ] + }, + "hwdata-pci": { + "versions": { + "0.318-r0": 1545746063 + }, + "origin": "hwdata" + }, + "tdb": { + "versions": { + "1.3.16-r0": 1543933256 + }, + "origin": "tdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtdb.so.1" + ], + "provides": [ + "cmd:tdbbackup", + "cmd:tdbdump", + "cmd:tdbrestore", + "cmd:tdbtool" + ] + }, + "lua5.3-pc": { + "versions": { + "1.0.0-r9": 1543928870 + }, + "origin": "lua-pc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "knock-doc": { + "versions": { + "0.7-r2": 1551279491 + }, + "origin": "knock" + }, + "coreutils-doc": { + "versions": { + "8.30-r0": 1542304119 + }, + "origin": "coreutils" + }, + "open-isns-dev": { + "versions": { + "0.97-r4": 1542883211 + }, + "origin": "open-isns", + "dependencies": [ + "open-isns-lib=0.97-r4" + ] + }, + "rsyslog-elasticsearch": { + "versions": { + "8.40.0-r3": 1548686789 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ], + "provides": [ + "rsyslog-omelasticsearch=8.40.0-r3" + ] + }, + "xen-doc": { + "versions": { + "4.11.1-r1": 1545075896 + }, + "origin": "xen" + }, + "perl-set-intspan": { + "versions": { + "1.19-r0": 1545213692 + }, + "origin": "perl-set-intspan" + }, + "mercurial-bash-completion": { + "versions": { + "4.9.1-r0": 1557160461 + }, + "origin": "mercurial" + }, + "dhclient": { + "versions": { + "4.4.1-r1": 1543928453 + }, + "origin": "dhcp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "cmd:dhclient", + "cmd:dhclient-script" + ] + }, + "py3-urllib3": { + "versions": { + "1.22-r0": 1542825022 + }, + "origin": "py-urllib3", + "dependencies": [ + "python3" + ] + }, + "linux-firmware-yamaha": { + "versions": { + "20190322-r0": 1554980646 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "rsyslog-libdbi": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libdbi.so.1" + ], + "provides": [ + "rsyslog-omlibdbi=8.40.0-r3" + ] + }, + "py3-pep8": { + "versions": { + "1.7.1-r0": 1542902307 + }, + "origin": "py-pep8", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:pep8-3" + ] + }, + "net-snmp-perl": { + "versions": { + "5.8-r0": 1543923350 + }, + "origin": "net-snmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.35", + "so:libnetsnmpagent.so.35", + "so:libnetsnmptrapd.so.35" + ], + "provides": [ + "cmd:mib2c", + "cmd:mib2c-update", + "cmd:net-snmp-cert", + "cmd:snmp-bridge-mib", + "cmd:traptoemail" + ] + }, + "lttng-ust": { + "versions": { + "2.10.1-r0": 1543249619 + }, + "origin": "lttng-ust", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liburcu-bp.so.6", + "so:liburcu-cds.so.6" + ], + "provides": [ + "so:liblttng-ust-ctl.so.4=4.0.0", + "so:liblttng-ust-cyg-profile-fast.so.0=0.0.0", + "so:liblttng-ust-cyg-profile.so.0=0.0.0", + "so:liblttng-ust-dl.so.0=0.0.0", + "so:liblttng-ust-fd.so.0=0.0.0", + "so:liblttng-ust-fork.so.0=0.0.0", + "so:liblttng-ust-libc-wrapper.so.0=0.0.0", + "so:liblttng-ust-pthread-wrapper.so.0=0.0.0", + "so:liblttng-ust-tracepoint.so.0=0.0.0", + "so:liblttng-ust.so.0=0.0.0", + "cmd:lttng-gen-tp" + ] + }, + "atf-doc": { + "versions": { + "0.21-r1": 1542304874 + }, + "origin": "atf" + }, + "iptraf-ng-doc": { + "versions": { + "1.1.4-r4": 1545214012 + }, + "origin": "iptraf-ng" + }, + "popt-dev": { + "versions": { + "1.16-r7": 1542820792 + }, + "origin": "popt", + "dependencies": [ + "pkgconfig", + "popt=1.16-r7" + ], + "provides": [ + "pc:popt=1.16" + ] + }, + "glib": { + "versions": { + "2.58.1-r2": 1545290825 + }, + "origin": "glib", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libintl.so.8", + "so:libmount.so.1", + "so:libpcre.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libgio-2.0.so.0=0.5800.1", + "so:libglib-2.0.so.0=0.5800.1", + "so:libgmodule-2.0.so.0=0.5800.1", + "so:libgobject-2.0.so.0=0.5800.1", + "so:libgthread-2.0.so.0=0.5800.1", + "cmd:gapplication", + "cmd:gdbus", + "cmd:gio", + "cmd:gio-launch-desktop", + "cmd:gio-querymodules", + "cmd:glib-compile-schemas", + "cmd:gsettings" + ] + }, + "cdrkit-doc": { + "versions": { + "1.1.11-r2": 1545075404 + }, + "origin": "cdrkit" + }, + "perl-getopt-long": { + "versions": { + "2.50-r0": 1543922468 + }, + "origin": "perl-getopt-long" + }, + "perl-cgi-session": { + "versions": { + "4.48-r0": 1543999428 + }, + "origin": "perl-cgi-session", + "dependencies": [ + "perl" + ] + }, + "py3-google-api-python-client": { + "versions": { + "1.7.7-r0": 1546936520 + }, + "origin": "py-google-api-python-client", + "dependencies": [ + "py3-httplib2", + "py3-oauth2client", + "py3-uritemplate", + "py3-six", + "python3" + ] + }, + "alpine-ipxe-ipxe_iso": { + "versions": { + "1.0_git20180825-r1": 1545986078 + }, + "origin": "alpine-ipxe" + }, + "pjproject-dbg": { + "versions": { + "2.7.2-r4": 1545073631 + }, + "origin": "pjproject" + }, + "bison-doc": { + "versions": { + "3.0.5-r0": 1542300926 + }, + "origin": "bison" + }, + "ruby-io-console": { + "versions": { + "2.5.5-r0": 1557164836 + }, + "origin": "ruby", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.5" + ] + }, + "libbonobo-lang": { + "versions": { + "2.32.1-r6": 1545299731 + }, + "origin": "libbonobo" + }, + "py3-openssl": { + "versions": { + "18.0.0-r0": 1545223366 + }, + "origin": "py-openssl", + "dependencies": [ + "py3-cryptography", + "py3-six", + "python3" + ] + }, + "vblade": { + "versions": { + "24-r0": 1543222855 + }, + "origin": "vblade", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:vblade", + "cmd:vbladed" + ] + }, + "py-egenix-mx-base-doc": { + "versions": { + "3.2.9-r0": 1542821601 + }, + "origin": "py-egenix-mx-base" + }, + "postgresql-plpython3-contrib": { + "versions": { + "11.2-r0": 1554274177 + }, + "origin": "postgresql", + "dependencies": [ + "postgresql-plpython3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "gpgme-dev": { + "versions": { + "1.12.0-r3": 1543932321 + }, + "origin": "gpgme", + "dependencies": [ + "libgpg-error-dev", + "libassuan-dev", + "gpgme=1.12.0-r3", + "gpgmepp=1.12.0-r3" + ], + "provides": [ + "cmd:gpgme-config" + ] + }, + "p11-kit": { + "versions": { + "0.23.14-r0": 1544791899 + }, + "origin": "p11-kit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6" + ], + "provides": [ + "so:libp11-kit.so.0=0.3.0", + "cmd:p11-kit" + ] + }, + "nfs-utils-dbg": { + "versions": { + "2.3.2-r1": 1543933421 + }, + "origin": "nfs-utils", + "dependencies": [ + "rpcbind" + ] + }, + "musl": { + "versions": { + "1.1.20-r4": 1552989415 + }, + "origin": "musl", + "provides": [ + "so:libc.musl-x86_64.so.1=1" + ] + }, + "yeahconsole": { + "versions": { + "0.3.4-r0": 1543998770 + }, + "origin": "yeahconsole", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:yeahconsole" + ] + }, + "py3-curl": { + "versions": { + "7.43.0.2-r1": 1543935132 + }, + "origin": "py-curl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libpython3.6m.so.1.0", + "so:libssl.so.1.1" + ] + }, + "minicom-lang": { + "versions": { + "2.7.1-r0": 1544799266 + }, + "origin": "minicom" + }, + "rsyslog-testing": { + "versions": { + "8.40.0-r3": 1548686792 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "rsyslog-omtesting=8.40.0-r3" + ] + }, + "libksba-doc": { + "versions": { + "1.3.5-r0": 1543932190 + }, + "origin": "libksba" + }, + "augeas": { + "versions": { + "1.11.0-r0": 1542924581 + }, + "origin": "augeas", + "dependencies": [ + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1", + "so:libfa.so.1", + "so:libgcc_s.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:augmatch", + "cmd:augparse", + "cmd:augtool", + "cmd:fadot" + ] + }, + "perl-http-negotiate": { + "versions": { + "6.01-r1": 1542821863 + }, + "origin": "perl-http-negotiate", + "dependencies": [ + "perl", + "perl-http-message" + ] + }, + "nagios-plugins": { + "versions": { + "2.2.1-r6": 1543933915 + }, + "origin": "nagios-plugins", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ] + }, + "libnftnl-dev": { + "versions": { + "1.1.1-r0": 1542893202 + }, + "origin": "libnftnl", + "dependencies": [ + "libmnl-dev", + "libnftnl-libs=1.1.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnftnl=1.1.1" + ] + }, + "procps": { + "versions": { + "3.3.15-r0": 1543934064 + }, + "origin": "procps", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libprocps.so.7" + ], + "provides": [ + "cmd:free", + "cmd:pgrep", + "cmd:pidof", + "cmd:pkill", + "cmd:pmap", + "cmd:ps", + "cmd:pwdx", + "cmd:slabtop", + "cmd:sysctl", + "cmd:tload", + "cmd:top", + "cmd:uptime", + "cmd:vmstat", + "cmd:w", + "cmd:watch" + ] + }, + "varnish": { + "versions": { + "6.1.1-r0": 1545300752 + }, + "origin": "varnish", + "dependencies": [ + "gcc", + "libc-dev", + "libgcc", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libedit.so.0", + "so:libexecinfo.so.1", + "so:libncursesw.so.6", + "so:libpcre.so.1", + "so:libvarnishapi.so.2" + ], + "provides": [ + "cmd:varnishadm", + "cmd:varnishd", + "cmd:varnishhist", + "cmd:varnishlog", + "cmd:varnishncsa", + "cmd:varnishstat", + "cmd:varnishtest", + "cmd:varnishtop" + ] + }, + "nagios-plugins-by_ssh": { + "versions": { + "2.2.1-r6": 1543933902 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "openssh-client", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-email-address-list-doc": { + "versions": { + "0.05-r2": 1542893362 + }, + "origin": "perl-email-address-list" + }, + "lua5.1-dbi-sqlite3": { + "versions": { + "0.6-r3": 1545214220 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "yajl-tools": { + "versions": { + "2.1.0-r0": 1543935354 + }, + "origin": "yajl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:json_reformat", + "cmd:json_verify" + ] + }, + "lcms-doc": { + "versions": { + "1.19-r8": 1546249798 + }, + "origin": "lcms" + }, + "charybdis": { + "versions": { + "3.5.6-r1": 1542883317 + }, + "origin": "charybdis", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:libratbox.so=0", + "cmd:charybdis-bantool", + "cmd:charybdis-genssl", + "cmd:charybdis-ircd", + "cmd:charybdis-mkpasswd", + "cmd:charybdis-viconf", + "cmd:charybdis-vimotd" + ] + }, + "perl-net-http-doc": { + "versions": { + "6.09-r1": 1542821813 + }, + "origin": "perl-net-http" + }, + "nagios-plugins-time": { + "versions": { + "2.2.1-r6": 1543933913 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "sqlite-dev": { + "versions": { + "3.26.0-r3": 1546255353 + }, + "origin": "sqlite", + "dependencies": [ + "pkgconfig", + "sqlite-libs=3.26.0-r3" + ], + "provides": [ + "pc:sqlite3=3.26.0" + ] + }, + "perl-class-returnvalue-doc": { + "versions": { + "0.55-r1": 1544798600 + }, + "origin": "perl-class-returnvalue" + }, + "kamailio-http_async": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libevent-2.1.so.6" + ] + }, + "unixodbc-doc": { + "versions": { + "2.3.7-r0": 1542845421 + }, + "origin": "unixodbc" + }, + "util-macros": { + "versions": { + "1.19.2-r0": 1542822437 + }, + "origin": "util-macros" + }, + "perl-net-snpp": { + "versions": { + "1.17-r2": 1544000615 + }, + "origin": "perl-net-snpp", + "dependencies": [ + "perl" + ] + }, + "uwsgi-msgpack": { + "versions": { + "2.0.17.1-r0": 1545062203 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "font-adobe-utopia-type1": { + "versions": { + "1.0.4-r0": 1545216492 + }, + "origin": "font-adobe-utopia-type1", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-cgi-psgi-doc": { + "versions": { + "0.15-r1": 1545208755 + }, + "origin": "perl-cgi-psgi" + }, + "links": { + "versions": { + "2.15-r2": 1545207743 + }, + "origin": "links", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:links" + ] + }, + "squid-lang-pt": { + "versions": { + "4.4-r1": 1545216328 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "liblockfile-dev": { + "versions": { + "1.14-r0": 1545075166 + }, + "origin": "liblockfile" + }, + "lua-stdlib": { + "versions": { + "41.2.1-r0": 1545292926 + }, + "origin": "lua-stdlib" + }, + "py2-pyflakes": { + "versions": { + "1.6.0-r3": 1545073865 + }, + "origin": "py-pyflakes", + "dependencies": [ + "python2" + ], + "provides": [ + "pyflakes=1.6.0-r3", + "cmd:pyflakes-2" + ] + }, + "libavc1394": { + "versions": { + "0.5.4-r2": 1543932111 + }, + "origin": "libavc1394", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libraw1394.so.11" + ], + "provides": [ + "so:libavc1394.so.0=0.3.0", + "so:librom1394.so.0=0.3.0", + "cmd:dvcont", + "cmd:mkrfc2734", + "cmd:panelctl" + ] + }, + "py-cairo-dev": { + "versions": { + "1.16.3-r0": 1543925199 + }, + "origin": "py-cairo", + "dependencies": [ + "pc:cairo", + "pkgconfig" + ], + "provides": [ + "pc:py3cairo=1.16.3", + "pc:pycairo=1.16.3" + ] + }, + "nagios-plugins-swap": { + "versions": { + "2.2.1-r6": 1543933912 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.2-augeas": { + "versions": { + "0.1.2-r4": 1543246060 + }, + "origin": "lua-augeas", + "dependencies": [ + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1" + ] + }, + "linux-firmware-rtw88": { + "versions": { + "20190322-r0": 1554980648 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "lua5.2-expat": { + "versions": { + "1.3.0-r2": 1544000280 + }, + "origin": "lua-expat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ] + }, + "lua5.2-sql-postgres": { + "versions": { + "2.3.5-r2": 1543924401 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "xinput-doc": { + "versions": { + "1.6.2-r0": 1545235264 + }, + "origin": "xinput" + }, + "ruby-bigdecimal": { + "versions": { + "2.5.5-r0": 1557164836 + }, + "origin": "ruby", + "dependencies": [ + "ruby-libs", + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.5" + ] + }, + "nsd-doc": { + "versions": { + "4.1.26-r0": 1546948336 + }, + "origin": "nsd" + }, + "libwebp": { + "versions": { + "1.0.1-r0": 1545856969 + }, + "origin": "libwebp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libwebp.so.7=7.0.3", + "so:libwebpdecoder.so.3=3.0.3", + "so:libwebpdemux.so.2=2.0.5", + "so:libwebpmux.so.3=3.0.3" + ] + }, + "pcsc-lite": { + "versions": { + "1.8.24-r1": 1545060762 + }, + "origin": "pcsc-lite", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ], + "provides": [ + "cmd:pcsc-spy", + "cmd:pcscd" + ] + }, + "zeromq": { + "versions": { + "4.3.1-r0": 1548271359 + }, + "origin": "zeromq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libzmq.so.5" + ], + "provides": [ + "cmd:curve_keygen" + ] + }, + "paxmark": { + "versions": { + "0.11-r0": 1542301507 + }, + "origin": "paxmark", + "dependencies": [ + "attr" + ], + "provides": [ + "cmd:paxmark", + "cmd:paxmark.sh" + ] + }, + "libgss-doc": { + "versions": { + "0.1.5-r1": 1543998825 + }, + "origin": "libgss" + }, + "libgcab-lang": { + "versions": { + "0.7-r2": 1543249784 + }, + "origin": "libgcab" + }, + "qemu-audio-oss": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ] + }, + "xf86-video-chips-doc": { + "versions": { + "1.2.7-r3": 1545300414 + }, + "origin": "xf86-video-chips" + }, + "gtk-engines-redmond": { + "versions": { + "2.21.0-r2": 1545289337 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "tiff-doc": { + "versions": { + "4.0.10-r0": 1543921905 + }, + "origin": "tiff" + }, + "py-tornado": { + "versions": { + "4.5.2-r1": 1542845331 + }, + "origin": "py-tornado" + }, + "font-bitstream-type1": { + "versions": { + "1.0.3-r0": 1545300424 + }, + "origin": "font-bitstream-type1", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "squid-lang-az": { + "versions": { + "4.4-r1": 1545216323 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "openldap-back-passwd": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "libxklavier-dev": { + "versions": { + "5.4-r4": 1546458613 + }, + "origin": "libxklavier", + "dependencies": [ + "gettext-dev", + "libxkbfile-dev", + "libxklavier=5.4-r4", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libxklavier=5.4" + ] + }, + "libdrm-dev": { + "versions": { + "2.4.96-r0": 1544791765 + }, + "origin": "libdrm", + "dependencies": [ + "linux-headers", + "libdrm=2.4.96-r0", + "pkgconfig" + ], + "provides": [ + "pc:libdrm=2.4.96", + "pc:libdrm_amdgpu=2.4.96", + "pc:libdrm_freedreno=2.4.96", + "pc:libdrm_intel=2.4.96", + "pc:libdrm_nouveau=2.4.96", + "pc:libdrm_radeon=2.4.96", + "pc:libkms=1.0.0" + ] + }, + "shorewall6-doc": { + "versions": { + "5.2.2-r0": 1548095417 + }, + "origin": "shorewall6" + }, + "gtest-dev": { + "versions": { + "1.8.1-r0": 1542822389 + }, + "origin": "gtest", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:gmock=1.8.1", + "pc:gmock_main=1.8.1", + "pc:gtest=1.8.1", + "pc:gtest_main=1.8.1" + ] + }, + "cogl-dev": { + "versions": { + "1.22.2-r0": 1544000911 + }, + "origin": "cogl", + "dependencies": [ + "glib-dev", + "mesa-dev", + "libdrm-dev", + "libxdamage-dev", + "libxcomposite-dev", + "libxrandr-dev", + "gdk-pixbuf-dev", + "pango-dev", + "cairo-dev", + "gobject-introspection-dev", + "libxext-dev", + "eudev-dev", + "cogl=1.22.2-r0", + "pc:cairo>=1.10", + "pc:egl", + "pc:gbm", + "pc:gdk-pixbuf-2.0>=2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:libdrm", + "pc:x11", + "pc:xcomposite>=0.4", + "pc:xdamage", + "pc:xext", + "pc:xfixes>=3", + "pc:xrandr>=1.2", + "pkgconfig" + ], + "provides": [ + "pc:cogl-1.0=1.22.2", + "pc:cogl-2.0-experimental=2.0.0", + "pc:cogl-gl-1.0=1.22.2", + "pc:cogl-gles2-1.0=1.22.2", + "pc:cogl-gles2-2.0-experimental=2.0.0", + "pc:cogl-pango-1.0=1.22.2", + "pc:cogl-pango-2.0-experimental=1.22.2", + "pc:cogl-path-1.0=1.22.2", + "pc:cogl-path-2.0-experimental=1.22.2" + ] + }, + "perl-file-slurp-tiny": { + "versions": { + "0.004-r0": 1545213668 + }, + "origin": "perl-file-slurp-tiny" + }, + "libresample": { + "versions": { + "0.1.3-r1": 1545073659 + }, + "origin": "libresample" + }, + "perl-crypt-rijndael-doc": { + "versions": { + "1.13-r0": 1543923070 + }, + "origin": "perl-crypt-rijndael" + }, + "py3-tdb": { + "versions": { + "1.3.16-r0": 1543933254 + }, + "origin": "tdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0", + "so:libtdb.so.1" + ] + }, + "wipe-doc": { + "versions": { + "2.3.1-r0": 1543924496 + }, + "origin": "wipe" + }, + "alpine-ipxe-undionly_kpxe": { + "versions": { + "1.0_git20180825-r1": 1545986078 + }, + "origin": "alpine-ipxe" + }, + "libtheora": { + "versions": { + "1.1.1-r13": 1543928533 + }, + "origin": "libtheora", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0" + ], + "provides": [ + "so:libtheora.so.0=0.3.10", + "so:libtheoradec.so.1=1.1.4", + "so:libtheoraenc.so.1=1.1.2" + ] + }, + "boost-system": { + "versions": { + "1.67.0-r2": 1542823633 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libboost_system-mt.so.1.67.0=1.67.0", + "so:libboost_system.so.1.67.0=1.67.0" + ] + }, + "nginx-mod-devel-kit": { + "versions": { + "1.14.2-r1": 1557179820 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "uwsgi-gevent3": { + "versions": { + "2.0.17.1-r0": 1545062215 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "linux-firmware-moxa": { + "versions": { + "20190322-r0": 1554980650 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "distcc-pump": { + "versions": { + "3.3.2-r0": 1544799001 + }, + "origin": "distcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ], + "provides": [ + "cmd:pump" + ] + }, + "gtkspell-doc": { + "versions": { + "2.0.16-r7": 1545215215 + }, + "origin": "gtkspell" + }, + "linux-firmware-dabusb": { + "versions": { + "20190322-r0": 1554980653 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "privoxy": { + "versions": { + "3.0.26-r0": 1545154403 + }, + "origin": "privoxy", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1", + "so:libpcreposix.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:privoxy" + ] + }, + "lua5.1-lxc": { + "versions": { + "3.0.2-r0": 1546416498 + }, + "origin": "lua-lxc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblxc.so.1" + ] + }, + "expat": { + "versions": { + "2.2.6-r0": 1542302776 + }, + "origin": "expat", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libexpat.so.1=1.6.8", + "cmd:xmlwf" + ] + }, + "automake": { + "versions": { + "1.16.1-r0": 1542301305 + }, + "origin": "automake", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:aclocal", + "cmd:aclocal-1.16", + "cmd:automake", + "cmd:automake-1.16" + ] + }, + "perl-cache-cache": { + "versions": { + "1.08-r0": 1543223060 + }, + "origin": "perl-cache-cache", + "dependencies": [ + "perl-error", + "perl-digest-sha1" + ] + }, + "perl-encode-locale": { + "versions": { + "1.05-r1": 1542821771 + }, + "origin": "perl-encode-locale", + "dependencies": [ + "perl" + ] + }, + "fprobe-ulog": { + "versions": { + "1.2-r4": 1543924459 + }, + "origin": "fprobe-ulog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetfilter_log.so.1", + "so:libnfnetlink.so.0" + ], + "provides": [ + "cmd:fprobe-ulog" + ] + }, + "irssi": { + "versions": { + "1.1.2-r0": 1547738041 + }, + "origin": "irssi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libncursesw.so.6", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:irssi" + ] + }, + "glew": { + "versions": { + "2.1.0-r0": 1543935774 + }, + "origin": "glew", + "dependencies": [ + "so:libGL.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libGLEW.so.2.1=2.1.0" + ] + }, + "vte3-doc": { + "versions": { + "0.52.2-r0": 1545215298 + }, + "origin": "vte3" + }, + "font-bh-lucidatypewriter-75dpi": { + "versions": { + "1.0.3-r0": 1545116961 + }, + "origin": "font-bh-lucidatypewriter-75dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "pcre2-doc": { + "versions": { + "10.32-r1": 1545069083 + }, + "origin": "pcre2" + }, + "tar-doc": { + "versions": { + "1.32-r0": 1551091781 + }, + "origin": "tar" + }, + "netcf-dev": { + "versions": { + "0.2.8-r5": 1543935398 + }, + "origin": "netcf", + "dependencies": [ + "netcf-libs=0.2.8-r5", + "pkgconfig" + ], + "provides": [ + "pc:netcf=0.2.8" + ] + }, + "paxctl": { + "versions": { + "0.9-r0": 1545223088 + }, + "origin": "paxctl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:paxctl" + ] + }, + "fuse3-doc": { + "versions": { + "3.2.6-r1": 1548943636 + }, + "origin": "fuse3" + }, + "kyua": { + "versions": { + "0.13-r3": 1542304934 + }, + "origin": "kyua", + "dependencies": [ + "so:libatf-c++.so.2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:liblutok.so.3", + "so:libsqlite3.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:kyua" + ] + }, + "dovecot-doc": { + "versions": { + "2.3.6-r0": 1557134097 + }, + "origin": "dovecot" + }, + "perl-regexp-ipv6": { + "versions": { + "0.03-r0": 1545289354 + }, + "origin": "perl-regexp-ipv6", + "dependencies": [ + "perl" + ] + }, + "libgssglue-dev": { + "versions": { + "0.4-r1": 1543248484 + }, + "origin": "libgssglue", + "dependencies": [ + "libgssglue=0.4-r1", + "pkgconfig" + ], + "provides": [ + "pc:libgssglue=0.4" + ] + }, + "protobuf": { + "versions": { + "3.6.1-r1": 1543931874 + }, + "origin": "protobuf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libprotobuf-lite.so.17=17.0.0", + "so:libprotobuf.so.17=17.0.0", + "so:libprotoc.so.17=17.0.0", + "cmd:protoc" + ] + }, + "libcroco-dev": { + "versions": { + "0.6.12-r1": 1543926545 + }, + "origin": "libcroco", + "dependencies": [ + "libcroco=0.6.12-r1", + "pc:glib-2.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libcroco-0.6=0.6.12", + "cmd:croco-0.6-config" + ] + }, + "perl-file-rsync-doc": { + "versions": { + "0.74-r1": 1545299965 + }, + "origin": "perl-file-rsync" + }, + "perl-javascript-minifier": { + "versions": { + "1.14-r0": 1545300399 + }, + "origin": "perl-javascript-minifier" + }, + "perl-email-date-format": { + "versions": { + "1.005-r0": 1543226621 + }, + "origin": "perl-email-date-format" + }, + "monit-openrc": { + "versions": { + "5.25.2-r0": 1545858142 + }, + "origin": "monit" + }, + "perl-symbol-global-name-doc": { + "versions": { + "0.05-r1": 1545223347 + }, + "origin": "perl-symbol-global-name" + }, + "lxterminal-lang": { + "versions": { + "0.3.2-r0": 1543927689 + }, + "origin": "lxterminal" + }, + "dzen": { + "versions": { + "0.9.5-r3": 1545062317 + }, + "origin": "dzen", + "dependencies": [ + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libXpm.so.4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dzen2" + ] + }, + "highlight": { + "versions": { + "3.46-r0": 1542823672 + }, + "origin": "highlight", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:liblua-5.3.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:highlight" + ] + }, + "qt-odbc": { + "versions": { + "4.8.7-r11": 1545075087 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtSql.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libodbc.so.2", + "so:libstdc++.so.6" + ] + }, + "py2-django": { + "versions": { + "1.11.20-r0": 1551267356 + }, + "origin": "py-django", + "dependencies": [ + "py2-tz", + "python2" + ], + "provides": [ + "cmd:django-admin-2" + ] + }, + "dahdi-linux": { + "versions": { + "3.0.0-r0": 1545911874 + }, + "origin": "dahdi-linux" + }, + "bubblewrap": { + "versions": { + "0.3.1-r0": 1546247687 + }, + "origin": "bubblewrap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2" + ], + "provides": [ + "cmd:bwrap" + ] + }, + "perl-xml-sax-doc": { + "versions": { + "0.99-r2": 1542893167 + }, + "origin": "perl-xml-sax" + }, + "guile-dev": { + "versions": { + "2.0.14-r0": 1543227919 + }, + "origin": "guile", + "dependencies": [ + "guile", + "gc-dev", + "guile-libs=2.0.14-r0", + "pkgconfig" + ], + "provides": [ + "pc:guile-2.0=2.0.14", + "cmd:guile-config" + ] + }, + "cracklib-words": { + "versions": { + "20080507-r2": 1542965863 + }, + "origin": "cracklib-words" + }, + "py2-snowballstemmer": { + "versions": { + "1.2.1-r1": 1542824983 + }, + "origin": "py-snowballstemmer", + "dependencies": [ + "python2" + ] + }, + "acf-freeswitch-vmail": { + "versions": { + "0.6.2-r1": 1545209644 + }, + "origin": "acf-freeswitch-vmail", + "dependencies": [ + "acf-core", + "lua-sql-sqlite3", + "lua-socket", + "freeswitch", + "/bin/sh" + ] + }, + "perl-business-hours-doc": { + "versions": { + "0.13-r0": 1547709105 + }, + "origin": "perl-business-hours" + }, + "libxres-dev": { + "versions": { + "1.2.0-r1": 1545302056 + }, + "origin": "libxres", + "dependencies": [ + "xorgproto", + "libxres=1.2.0-r1", + "pc:x11", + "pc:xext", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xres=1.2.0" + ] + }, + "gperf": { + "versions": { + "3.1-r2": 1542822039 + }, + "origin": "gperf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:gperf" + ] + }, + "miniperl": { + "versions": { + "5.26.3-r0": 1543998673 + }, + "origin": "perl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:miniperl" + ] + }, + "flite": { + "versions": { + "2.1-r0": 1545117049 + }, + "origin": "flite", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libflite.so.1=2.1", + "so:libflite_cmu_grapheme_lang.so.1=2.1", + "so:libflite_cmu_grapheme_lex.so.1=2.1", + "so:libflite_cmu_indic_lang.so.1=2.1", + "so:libflite_cmu_indic_lex.so.1=2.1", + "so:libflite_cmu_time_awb.so.1=2.1", + "so:libflite_cmu_us_awb.so.1=2.1", + "so:libflite_cmu_us_kal.so.1=2.1", + "so:libflite_cmu_us_kal16.so.1=2.1", + "so:libflite_cmu_us_rms.so.1=2.1", + "so:libflite_cmu_us_slt.so.1=2.1", + "so:libflite_cmulex.so.1=2.1", + "so:libflite_usenglish.so.1=2.1", + "cmd:flite", + "cmd:flite_cmu_time_awb", + "cmd:flite_cmu_us_awb", + "cmd:flite_cmu_us_kal", + "cmd:flite_cmu_us_kal16", + "cmd:flite_cmu_us_rms", + "cmd:flite_cmu_us_slt", + "cmd:flite_time" + ] + }, + "qemu-hppa": { + "versions": { + "3.1.0-r3": 1551107300 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-hppa" + ] + }, + "perl-capture-tiny-doc": { + "versions": { + "0.48-r0": 1542893272 + }, + "origin": "perl-capture-tiny" + }, + "uwsgi-nagios": { + "versions": { + "2.0.17.1-r0": 1545062203 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "nmap": { + "versions": { + "7.70-r3": 1545163173 + }, + "origin": "nmap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:liblua-5.3.so.0", + "so:libpcap.so.1", + "so:libpcre.so.1", + "so:libssh2.so.1", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:nmap" + ] + }, + "py-gflags": { + "versions": { + "3.1.1-r1": 1545208600 + }, + "origin": "py-gflags" + }, + "xrdb-doc": { + "versions": { + "1.1.1-r0": 1542883478 + }, + "origin": "xrdb" + }, + "py3-certifi": { + "versions": { + "2018.4.16-r0": 1542825014 + }, + "origin": "py-certifi", + "dependencies": [ + "python3" + ] + }, + "qpdf-libs": { + "versions": { + "8.3.0-r0": 1547117323 + }, + "origin": "qpdf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libjpeg.so.8", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libqpdf.so.21=21.3.0" + ] + }, + "perl-file-remove": { + "versions": { + "1.58-r0": 1544792322 + }, + "origin": "perl-file-remove" + }, + "libev": { + "versions": { + "4.25-r0": 1545838253 + }, + "origin": "libev", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libev.so.4=4.0.0" + ] + }, + "slim-doc": { + "versions": { + "1.3.6-r8": 1545229417 + }, + "origin": "slim" + }, + "openldap-overlay-sssvlv": { + "versions": { + "2.4.47-r2": 1546016480 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "perl-mime-types": { + "versions": { + "2.17-r0": 1543226616 + }, + "origin": "perl-mime-types" + }, + "collectd-python": { + "versions": { + "5.8.0-r3": 1546422677 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "perl-mail-spf-doc": { + "versions": { + "2.9.0-r2": 1542925015 + }, + "origin": "perl-mail-spf" + }, + "perl-data-hexdump-doc": { + "versions": { + "0.02-r1": 1545664905 + }, + "origin": "perl-data-hexdump" + }, + "perl-gdtextutil": { + "versions": { + "0.86-r0": 1543248473 + }, + "origin": "perl-gdtextutil", + "dependencies": [ + "perl-gd" + ] + }, + "linux-firmware-mrvl": { + "versions": { + "20190322-r0": 1554980650 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "py-flask-oauthlib": { + "versions": { + "0.9.5-r0": 1545164578 + }, + "origin": "py-flask-oauthlib", + "dependencies": [ + "py-flask", + "py-requests-oauthlib" + ] + }, + "tcpdump-doc": { + "versions": { + "4.9.2-r4": 1545300929 + }, + "origin": "tcpdump" + }, + "perl-netaddr-ip": { + "versions": { + "4.079-r1": 1542925008 + }, + "origin": "perl-netaddr-ip", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "varnish-doc": { + "versions": { + "6.1.1-r0": 1545300751 + }, + "origin": "varnish" + }, + "rabbitmq-c-dev": { + "versions": { + "0.8.0-r5": 1543923901 + }, + "origin": "rabbitmq-c", + "dependencies": [ + "openssl-dev", + "popt-dev", + "pkgconfig", + "rabbitmq-c=0.8.0-r5" + ], + "provides": [ + "pc:librabbitmq=0.8.0" + ] + }, + "po4a": { + "versions": { + "0.52-r1": 1542304731 + }, + "origin": "po4a", + "dependencies": [ + "perl", + "gettext" + ], + "provides": [ + "cmd:msguntypot", + "cmd:po4a", + "cmd:po4a-build", + "cmd:po4a-gettextize", + "cmd:po4a-normalize", + "cmd:po4a-translate", + "cmd:po4a-updatepo", + "cmd:po4aman-display-po", + "cmd:po4apod-display-po" + ] + }, + "grep-doc": { + "versions": { + "3.1-r2": 1543926780 + }, + "origin": "grep" + }, + "linux-pam-doc": { + "versions": { + "1.3.0-r0": 1542820661 + }, + "origin": "linux-pam" + }, + "clamav-db": { + "versions": { + "0.100.3-r0": 1555507335 + }, + "origin": "clamav", + "dependencies": [ + "freshclam" + ] + }, + "cciss_vol_status": { + "versions": { + "1.12-r0": 1545073935 + }, + "origin": "cciss_vol_status", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cciss_vol_status" + ] + }, + "v86d": { + "versions": { + "0.1.10-r1": 1545229398 + }, + "origin": "v86d", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:v86d" + ] + }, + "libexif-doc": { + "versions": { + "0.6.21-r3": 1545116758 + }, + "origin": "libexif" + }, + "py-gnome-dev": { + "versions": { + "2.28.1-r5": 1545301285 + }, + "origin": "py-gnome", + "dependencies": [ + "gtk+2.0-dev", + "libgnome-dev", + "py-gobject-dev", + "py-gtk-dev", + "python2-dev", + "pkgconfig" + ], + "provides": [ + "pc:gnome-python-2.0=2.28.1" + ] + }, + "libee-dev": { + "versions": { + "0.4.1-r0": 1545292594 + }, + "origin": "libee", + "dependencies": [ + "libee=0.4.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libee=0.4.1" + ] + }, + "libmspack": { + "versions": { + "0.8_alpha-r0": 1543925822 + }, + "origin": "libmspack", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmspack.so.0=0.1.0" + ] + }, + "dmenu": { + "versions": { + "4.8-r0": 1543222729 + }, + "origin": "dmenu", + "dependencies": [ + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1" + ], + "provides": [ + "cmd:dmenu", + "cmd:dmenu_path", + "cmd:dmenu_run", + "cmd:stest" + ] + }, + "sendpage": { + "versions": { + "1.0.3-r5": 1545116829 + }, + "origin": "sendpage", + "dependencies": [ + "perl-mailtools", + "perl-net-snpp", + "perl-device-serialport", + "perl-dbi", + "perl-sys-hostname-long", + "perl-test-mockobject" + ], + "provides": [ + "cmd:email2page", + "cmd:sendmail2snpp", + "cmd:sendpage", + "cmd:sendpage-db", + "cmd:snpp" + ] + }, + "jack-dbus": { + "versions": { + "1.9.12-r0": 1545073439 + }, + "origin": "jack", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libexpat.so.1", + "so:libjackserver.so.0" + ], + "provides": [ + "cmd:jackdbus" + ] + }, + "libxkbfile": { + "versions": { + "1.0.9-r2": 1542973201 + }, + "origin": "libxkbfile", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libxkbfile.so.1=1.0.2" + ] + }, + "libburn-doc": { + "versions": { + "1.5.0-r0": 1545837389 + }, + "origin": "libburn" + }, + "findmnt": { + "versions": { + "2.33-r0": 1545307437 + }, + "origin": "util-linux", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libmount.so.1", + "so:libsmartcols.so.1" + ], + "provides": [ + "cmd:findmnt" + ] + }, + "perl-timedate": { + "versions": { + "2.30-r1": 1543226629 + }, + "origin": "perl-timedate", + "provides": [ + "perl-time-date=2.30" + ] + }, + "uwsgi-transformation_chunked": { + "versions": { + "2.0.17.1-r0": 1545062212 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "xmodmap-doc": { + "versions": { + "1.0.9-r2": 1542973230 + }, + "origin": "xmodmap" + }, + "qemu-sparc32plus": { + "versions": { + "3.1.0-r3": 1551107302 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-sparc32plus" + ] + }, + "mt-st": { + "versions": { + "1.1-r4": 1543223640 + }, + "origin": "mt-st", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mt", + "cmd:stinit" + ] + }, + "py-twitter": { + "versions": { + "3.5-r0": 1546524754 + }, + "origin": "py-twitter", + "dependencies": [ + "py-future", + "py-requests", + "py-requests-oauthlib" + ] + }, + "qemu-cris": { + "versions": { + "3.1.0-r3": 1551107300 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-cris" + ] + }, + "clamav-libunrar": { + "versions": { + "0.100.3-r0": 1555507335 + }, + "origin": "clamav" + }, + "ccache": { + "versions": { + "3.5.1-r0": 1547294261 + }, + "origin": "ccache", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:ccache" + ] + }, + "perl-namespace-clean-doc": { + "versions": { + "0.27-r0": 1542973123 + }, + "origin": "perl-namespace-clean" + }, + "dropbear-openrc": { + "versions": { + "2018.76-r2": 1545208975 + }, + "origin": "dropbear" + }, + "py2-oauth2client": { + "versions": { + "4.1.2-r2": 1545229290 + }, + "origin": "py-oauth2client", + "dependencies": [ + "py2-asn1", + "py2-httplib2", + "py2-asn1-modules", + "py2-rsa", + "py2-six", + "python2" + ] + }, + "s6-rc-dev": { + "versions": { + "0.4.1.0-r0": 1545076679 + }, + "origin": "s6-rc", + "dependencies": [ + "s6-rc=0.4.1.0-r0" + ] + }, + "squid-lang-ro": { + "versions": { + "4.4-r1": 1545216329 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "e2fsprogs-libs": { + "versions": { + "1.44.5-r0": 1545745858 + }, + "origin": "e2fsprogs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2" + ], + "provides": [ + "so:libe2p.so.2=2.3", + "so:libext2fs.so.2=2.4", + "so:libss.so.2=2.0" + ] + }, + "perl-try-tiny": { + "versions": { + "0.22-r1": 1542821798 + }, + "origin": "perl-try-tiny" + }, + "dtach": { + "versions": { + "0.9-r1": 1543254080 + }, + "origin": "dtach", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dtach" + ] + }, + "xcmsdb": { + "versions": { + "1.0.5-r0": 1545293076 + }, + "origin": "xcmsdb", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xcmsdb" + ] + }, + "postfix-openrc": { + "versions": { + "3.3.2-r0": 1547130362 + }, + "origin": "postfix" + }, + "syslog-ng-map-value-pairs": { + "versions": { + "3.19.1-r0": 1548543151 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "patch-doc": { + "versions": { + "2.7.6-r4": 1548420016 + }, + "origin": "patch" + }, + "xf86-input-libinput-doc": { + "versions": { + "0.28.1-r0": 1545207901 + }, + "origin": "xf86-input-libinput" + }, + "xf86-input-synaptics-dev": { + "versions": { + "1.9.1-r1": 1545154611 + }, + "origin": "xf86-input-synaptics", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xorg-synaptics=1.9.1" + ] + }, + "asciidoc-doc": { + "versions": { + "8.6.10-r0": 1542883777 + }, + "origin": "asciidoc" + }, + "lua5.3-evdev": { + "versions": { + "2.2.1-r1": 1545076555 + }, + "origin": "lua-evdev", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "lm_sensors-detect": { + "versions": { + "3.4.0-r6": 1542924818 + }, + "origin": "lm_sensors", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:sensors-conf-convert", + "cmd:sensors-detect" + ] + }, + "unbound-migrate": { + "versions": { + "1.8.3-r1": 1555953584 + }, + "origin": "unbound", + "dependencies": [ + "dnssec-root" + ], + "provides": [ + "cmd:migrate-dnscache-to-unbound" + ] + }, + "ix": { + "versions": { + "0.6-r0": 1545076749 + }, + "origin": "sprunge", + "dependencies": [ + "curl" + ], + "provides": [ + "cmd:ix" + ] + }, + "cryptsetup-libs": { + "versions": { + "2.0.6-r0": 1545746292 + }, + "origin": "cryptsetup", + "dependencies": [ + "so:libargon2.so.1", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libdevmapper.so.1.02", + "so:libjson-c.so.4", + "so:libuuid.so.1" + ], + "provides": [ + "so:libcryptsetup.so.12=12.3.0" + ] + }, + "libxrandr-doc": { + "versions": { + "1.5.1-r2": 1542972233 + }, + "origin": "libxrandr" + }, + "clang-dev": { + "versions": { + "5.0.2-r0": 1546873920 + }, + "origin": "clang", + "dependencies": [ + "clang=5.0.2-r0", + "clang-libs=5.0.2-r0" + ] + }, + "acl-dev": { + "versions": { + "2.2.52-r5": 1542304042 + }, + "origin": "acl", + "dependencies": [ + "libacl=2.2.52-r5" + ] + }, + "py2-libvirt": { + "versions": { + "4.10.0-r0": 1545291084 + }, + "origin": "py-libvirt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libvirt-lxc.so.0", + "so:libvirt-qemu.so.0", + "so:libvirt.so.0" + ] + }, + "nginx-mod-http-lua-upstream": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "nginx-mod-http-lua", + "so:libc.musl-x86_64.so.1" + ] + }, + "qemu-system-microblaze": { + "versions": { + "3.1.0-r3": 1551107303 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-microblaze" + ] + }, + "perl-file-temp-doc": { + "versions": { + "0.2308-r0": 1543226522 + }, + "origin": "perl-file-temp" + }, + "imap-dev": { + "versions": { + "2007f-r9": 1545062297 + }, + "origin": "imap", + "dependencies": [ + "c-client=2007f-r9" + ] + }, + "py-dateutil": { + "versions": { + "2.7.3-r0": 1543248498 + }, + "origin": "py-dateutil", + "dependencies": [ + "py-six" + ] + }, + "pcre-tools": { + "versions": { + "8.42-r1": 1545067004 + }, + "origin": "pcre", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1", + "so:libpcre16.so.0", + "so:libpcre32.so.0", + "so:libpcreposix.so.0" + ], + "provides": [ + "cmd:pcregrep", + "cmd:pcretest" + ] + }, + "qt-x11": { + "versions": { + "4.8.7-r11": 1545075089 + }, + "origin": "qt", + "dependencies": [ + "hicolor-icon-theme", + "so:libGL.so.1", + "so:libICE.so.6", + "so:libQtCore.so.4", + "so:libQtDBus.so.4", + "so:libQtNetwork.so.4", + "so:libQtScript.so.4", + "so:libQtSql.so.4", + "so:libQtXml.so.4", + "so:libQtXmlPatterns.so.4", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libjpeg.so.8", + "so:libmng.so.2", + "so:libpng16.so.16", + "so:libstdc++.so.6", + "so:libtiff.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libQt3Support.so.4=4.8.7", + "so:libQtCLucene.so.4=4.8.7", + "so:libQtDeclarative.so.4=4.8.7", + "so:libQtDesigner.so.4=4.8.7", + "so:libQtDesignerComponents.so.4=4.8.7", + "so:libQtGui.so.4=4.8.7", + "so:libQtHelp.so.4=4.8.7", + "so:libQtMultimedia.so.4=4.8.7", + "so:libQtOpenGL.so.4=4.8.7", + "so:libQtScriptTools.so.4=4.8.7", + "so:libQtSvg.so.4=4.8.7", + "cmd:qdbusviewer", + "cmd:qmlplugindump", + "cmd:qmlviewer" + ] + }, + "linux-firmware-tehuti": { + "versions": { + "20190322-r0": 1554980647 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "py-gnutls": { + "versions": { + "3.1.2-r0": 1545062790 + }, + "origin": "py-gnutls" + }, + "perl-role-tiny": { + "versions": { + "2.000006-r0": 1542972959 + }, + "origin": "perl-role-tiny" + }, + "kamailio-dbg": { + "versions": { + "5.2.0-r1": 1546423169 + }, + "origin": "kamailio", + "dependencies": [ + "gawk" + ] + }, + "libdnet-doc": { + "versions": { + "1.12-r7": 1545076673 + }, + "origin": "libdnet" + }, + "hwdata-pnp": { + "versions": { + "0.318-r0": 1545746063 + }, + "origin": "hwdata" + }, + "perl-file-next-doc": { + "versions": { + "1.16-r0": 1544000673 + }, + "origin": "perl-file-next" + }, + "soxr-doc": { + "versions": { + "0.1.3-r0": 1545208985 + }, + "origin": "soxr" + }, + "setup-box": { + "versions": { + "1.0.1-r0": 1544000552 + }, + "origin": "setup-box", + "dependencies": [ + "jq" + ], + "provides": [ + "cmd:setup-box" + ] + }, + "byobu-doc": { + "versions": { + "5.127-r0": 1545062321 + }, + "origin": "byobu" + }, + "testdisk": { + "versions": { + "7.0-r4": 1544001241 + }, + "origin": "testdisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libext2fs.so.2", + "so:libjpeg.so.8", + "so:libncursesw.so.6", + "so:libntfs-3g.so.88", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:fidentify", + "cmd:photorec", + "cmd:testdisk" + ] + }, + "dbus-dev": { + "versions": { + "1.10.24-r1": 1542824377 + }, + "origin": "dbus", + "dependencies": [ + "util-linux-dev", + "dbus-libs=1.10.24-r1", + "pkgconfig" + ], + "provides": [ + "pc:dbus-1=1.10.24" + ] + }, + "psqlodbc": { + "versions": { + "09.06.0410-r1": 1544792479 + }, + "origin": "psqlodbc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libodbcinst.so.2", + "so:libpq.so.5" + ], + "provides": [ + "so:psqlodbca.so=0", + "so:psqlodbcw.so=0" + ] + }, + "darkice": { + "versions": { + "1.3-r0": 1545302359 + }, + "origin": "darkice", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libfaac.so.0", + "so:libgcc_s.so.1", + "so:libmp3lame.so.0", + "so:libogg.so.0", + "so:libstdc++.so.6", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2" + ], + "provides": [ + "cmd:darkice" + ] + }, + "xorg-server-dev": { + "versions": { + "1.20.3-r1": 1543928068 + }, + "origin": "xorg-server", + "dependencies": [ + "libepoxy-dev", + "libxfont2-dev", + "mesa-dev", + "pc:dri2proto>=2.8", + "pc:dri3proto>=1.2", + "pc:dri>=7.8.0", + "pc:fontsproto>=2.1.3", + "pc:glproto>=1.4.17", + "pc:inputproto>=2.3", + "pc:kbproto>=1.0.3", + "pc:pciaccess>=0.12.901", + "pc:pixman-1>=0.27.2", + "pc:presentproto>=1.1", + "pc:randrproto>=1.6.0", + "pc:renderproto>=0.11", + "pc:resourceproto>=1.2.0", + "pc:scrnsaverproto>=1.1", + "pc:videoproto", + "pc:xextproto>=7.2.99.901", + "pc:xf86driproto>=2.1.0", + "pc:xineramaproto", + "pc:xproto>=7.0.31", + "pkgconfig" + ], + "provides": [ + "pc:xorg-server=1.20.3" + ] + }, + "openldap-overlay-retcode": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "iperf-doc": { + "versions": { + "2.0.10-r1": 1543222921 + }, + "origin": "iperf" + }, + "inotify-tools": { + "versions": { + "3.20.1-r1": 1545302268 + }, + "origin": "inotify-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libinotifytools.so.0=0.4.1", + "cmd:inotifywait", + "cmd:inotifywatch" + ] + }, + "lua5.2-apk": { + "versions": { + "2.10.3-r1": 1547112494 + }, + "origin": "apk-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ] + }, + "py3-asn1-modules": { + "versions": { + "0.2.2-r0": 1543928876 + }, + "origin": "py-asn1-modules", + "dependencies": [ + "py3-openssl", + "py3-asn1" + ] + }, + "spamassassin-client": { + "versions": { + "3.4.2-r0": 1545061981 + }, + "origin": "spamassassin", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:spamc" + ] + }, + "aspell-uk": { + "versions": { + "1.4.0-r1": 1545235381 + }, + "origin": "aspell-uk" + }, + "gnokii-smsd-pgsql": { + "versions": { + "0.6.31-r8": 1545300480 + }, + "origin": "gnokii", + "dependencies": [ + "gnokii-smsd", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "libdc1394-dev": { + "versions": { + "2.2.5-r1": 1545249932 + }, + "origin": "libdc1394", + "dependencies": [ + "libusb-dev", + "libraw1394-dev", + "libdc1394=2.2.5-r1", + "pkgconfig" + ], + "provides": [ + "pc:libdc1394-2=2.2.5" + ] + }, + "jsoncpp-dev": { + "versions": { + "1.8.4-r0": 1545075439 + }, + "origin": "jsoncpp", + "dependencies": [ + "jsoncpp=1.8.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:jsoncpp=1.8.4" + ] + }, + "seabios": { + "versions": { + "1.11.0-r0": 1543936027 + }, + "origin": "seabios", + "dependencies": [ + "seabios-bin=1.11.0-r0", + "seavgabios-bin=1.11.0-r0" + ] + }, + "perl-try-tiny-doc": { + "versions": { + "0.22-r1": 1542821796 + }, + "origin": "perl-try-tiny" + }, + "lame-dev": { + "versions": { + "3.100-r0": 1545117086 + }, + "origin": "lame", + "dependencies": [ + "lame=3.100-r0" + ] + }, + "ipsec-tools-doc": { + "versions": { + "0.8.2-r8": 1543246797 + }, + "origin": "ipsec-tools" + }, + "perl-clone": { + "versions": { + "0.41-r1": 1546096794 + }, + "origin": "perl-clone", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "uwsgi-echo": { + "versions": { + "2.0.17.1-r0": 1545062198 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "shorewall-doc": { + "versions": { + "5.2.2-r0": 1548095422 + }, + "origin": "shorewall" + }, + "py-farstream0.1": { + "versions": { + "0.1.2-r2": 1545069353 + }, + "origin": "farstream0.1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfarstream-0.1.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-0.10.so.0" + ] + }, + "rp-pppoe-doc": { + "versions": { + "3.13-r0": 1545223084 + }, + "origin": "rp-pppoe" + }, + "perl-module-util-doc": { + "versions": { + "1.09-r0": 1545163058 + }, + "origin": "perl-module-util" + }, + "libmpeg2": { + "versions": { + "0.5.1-r8": 1544000976 + }, + "origin": "libmpeg2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmpeg2.so.0=0.1.0", + "so:libmpeg2convert.so.0=0.0.0", + "cmd:corrupt_mpeg2", + "cmd:extract_mpeg2" + ] + }, + "libmng": { + "versions": { + "2.0.3-r1": 1545062629 + }, + "origin": "libmng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libmng.so.2=2.0.2" + ] + }, + "lua5.3-sql-sqlite3": { + "versions": { + "2.3.5-r2": 1543924405 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "perl-exporter-tiny": { + "versions": { + "1.002001-r0": 1542845620 + }, + "origin": "perl-exporter-tiny" + }, + "libmms": { + "versions": { + "0.6.4-r0": 1544001046 + }, + "origin": "libmms", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmms.so.0=0.0.2" + ] + }, + "tinydns": { + "versions": { + "1.05-r47": 1545208950 + }, + "origin": "djbdns", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tinydns", + "cmd:tinydns-conf", + "cmd:tinydns-data", + "cmd:tinydns-edit", + "cmd:tinydns-get" + ] + }, + "perl-libwww": { + "versions": { + "6.36-r0": 1544791738 + }, + "origin": "perl-libwww", + "dependencies": [ + "perl-http-date", + "perl-http-cookies", + "perl-net-http", + "perl-http-daemon", + "perl-html-parser", + "perl-file-listing", + "perl-www-robotrules", + "perl-http-negotiate", + "perl-uri", + "perl-http-message", + "perl-lwp-mediatypes", + "perl-encode-locale", + "perl-try-tiny" + ], + "provides": [ + "cmd:lwp-download", + "cmd:lwp-dump", + "cmd:lwp-mirror", + "cmd:lwp-request" + ] + }, + "postgresql-client": { + "versions": { + "11.2-r0": 1554274176 + }, + "origin": "postgresql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libedit.so.0", + "so:libpq.so.5", + "so:libz.so.1" + ], + "provides": [ + "cmd:clusterdb", + "cmd:createdb", + "cmd:createuser", + "cmd:dropdb", + "cmd:dropuser", + "cmd:pg_basebackup", + "cmd:pg_dump", + "cmd:pg_dumpall", + "cmd:pg_isready", + "cmd:pg_receivewal", + "cmd:pg_recvlogical", + "cmd:pg_restore", + "cmd:psql", + "cmd:reindexdb", + "cmd:vacuumdb" + ] + }, + "faac": { + "versions": { + "1.28-r12": 1545165108 + }, + "origin": "faac", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libfaac.so.0=0.0.0", + "cmd:faac" + ] + }, + "py2-tornado": { + "versions": { + "4.5.2-r1": 1542845326 + }, + "origin": "py-tornado", + "dependencies": [ + "py2-backports_abc", + "py2-certifi", + "py2-singledispatch", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "ruby-doc": { + "versions": { + "2.5.5-r0": 1557164832 + }, + "origin": "ruby" + }, + "libxtst-doc": { + "versions": { + "1.2.3-r2": 1543241194 + }, + "origin": "libxtst" + }, + "py3-roman": { + "versions": { + "2.0.0-r3": 1543998690 + }, + "origin": "py-roman", + "dependencies": [ + "python3" + ] + }, + "libasr-doc": { + "versions": { + "1.0.2-r9": 1543933802 + }, + "origin": "libasr" + }, + "libedit": { + "versions": { + "20181209.3.1-r0": 1545839273 + }, + "origin": "libedit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "so:libedit.so.0=0.0.59" + ] + }, + "perl-apache-session-doc": { + "versions": { + "1.93-r0": 1543927695 + }, + "origin": "perl-apache-session" + }, + "c-ares": { + "versions": { + "1.15.0-r0": 1544791695 + }, + "origin": "c-ares", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcares.so.2=2.3.0" + ] + }, + "perl-inline": { + "versions": { + "0.80-r1": 1545067072 + }, + "origin": "perl-inline", + "dependencies": [ + "perl" + ] + }, + "zfs-dev": { + "versions": { + "0.7.12-r1": 1552933678 + }, + "origin": "zfs", + "dependencies": [ + "glib-dev", + "e2fsprogs-dev", + "util-linux-dev", + "libtirpc-dev", + "attr-dev", + "pkgconfig", + "zfs-libs=0.7.12-r1" + ], + "provides": [ + "pc:libzfs=0.7.12", + "pc:libzfs_core=0.7.12" + ] + }, + "perl-email-date-format-doc": { + "versions": { + "1.005-r0": 1543226619 + }, + "origin": "perl-email-date-format" + }, + "qemu-system-xtensaeb": { + "versions": { + "3.1.0-r3": 1551107306 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-xtensaeb" + ] + }, + "syslog-ng-stardate": { + "versions": { + "3.19.1-r0": 1548543151 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "tinyxml": { + "versions": { + "2.6.2-r1": 1545292911 + }, + "origin": "tinyxml", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libtinyxml.so.0=0.2.6.2" + ] + }, + "guile": { + "versions": { + "2.0.14-r0": 1543227921 + }, + "origin": "guile", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libguile-2.0.so.22" + ], + "provides": [ + "cmd:guild", + "cmd:guile", + "cmd:guile-snarf", + "cmd:guile-tools" + ] + }, + "heimdal": { + "versions": { + "7.5.0-r2": 1542819374 + }, + "origin": "heimdal", + "dependencies": [ + "krb5-conf", + "so:libasn1.so.8", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libgssapi.so.3", + "so:libhcrypto.so.4", + "so:libhdb.so.9", + "so:libheimbase.so.1", + "so:libheimntlm.so.0", + "so:libhx509.so.5", + "so:libkadm5clnt.so.7", + "so:libkadm5srv.so.8", + "so:libkafs.so.0", + "so:libkdc.so.2", + "so:libkrb5.so.26", + "so:libotp.so.0", + "so:libreadline.so.7", + "so:libroken.so.18", + "so:libsl.so.0", + "so:libwind.so.0" + ], + "provides": [ + "cmd:afslog", + "cmd:bsearch", + "cmd:gsstool", + "cmd:heimdal", + "cmd:heimtools", + "cmd:hprop", + "cmd:hpropd", + "cmd:hxtool", + "cmd:idn-lookup", + "cmd:iprop-log", + "cmd:ipropd-master", + "cmd:ipropd-slave", + "cmd:kadmin", + "cmd:kadmind", + "cmd:kcm", + "cmd:kdc", + "cmd:kdestroy", + "cmd:kf", + "cmd:kfd", + "cmd:kgetcred", + "cmd:kimpersonate", + "cmd:kinit", + "cmd:klist", + "cmd:kpasswd", + "cmd:kpasswdd", + "cmd:kstash", + "cmd:kswitch", + "cmd:ktutil", + "cmd:otp", + "cmd:otpprint", + "cmd:pagsh", + "cmd:su" + ] + }, + "py-gtk-demo": { + "versions": { + "2.24.0-r15": 1543927629 + }, + "origin": "py-gtk", + "dependencies": [ + "py-gtk" + ], + "provides": [ + "cmd:pygtk-demo" + ] + }, + "libvirt-vbox": { + "versions": { + "4.10.0-r1": 1547051658 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-daemon=4.10.0-r1", + "libvirt-common-drivers=4.10.0-r1", + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libvirt.so.0", + "so:libxml2.so.2" + ] + }, + "py-itsdangerous": { + "versions": { + "0.24-r3": 1545164553 + }, + "origin": "py-itsdangerous" + }, + "perl-sub-install-doc": { + "versions": { + "0.928-r0": 1542845631 + }, + "origin": "perl-sub-install" + }, + "gdbm-dev": { + "versions": { + "1.13-r1": 1542303058 + }, + "origin": "gdbm", + "dependencies": [ + "gdbm=1.13-r1" + ] + }, + "kamailio-carrierroute": { + "versions": { + "5.2.0-r1": 1546423170 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libsrdb1.so.1", + "so:libtrie.so.1" + ] + }, + "xf86-input-synaptics": { + "versions": { + "1.9.1-r1": 1545154612 + }, + "origin": "xf86-input-synaptics", + "dependencies": [ + "so:libX11.so.6", + "so:libXi.so.6", + "so:libXtst.so.6", + "so:libc.musl-x86_64.so.1", + "so:libevdev.so.2" + ], + "provides": [ + "cmd:synclient", + "cmd:syndaemon" + ] + }, + "udns-doc": { + "versions": { + "0.4-r0": 1542924405 + }, + "origin": "udns" + }, + "mrxvt": { + "versions": { + "0.5.4-r7": 1545073299 + }, + "origin": "mrxvt", + "dependencies": [ + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXpm.so.4", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libjpeg.so.8" + ], + "provides": [ + "cmd:mrxvt" + ] + }, + "perl-parse-syslog-doc": { + "versions": { + "1.10-r3": 1545073946 + }, + "origin": "perl-parse-syslog" + }, + "cppunit-dev": { + "versions": { + "1.14.0-r0": 1543932038 + }, + "origin": "cppunit", + "dependencies": [ + "cppunit=1.14.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:cppunit=1.14.0" + ] + }, + "mosquitto-libs++": { + "versions": { + "1.5.6-r0": 1549970968 + }, + "origin": "mosquitto", + "dependencies": [ + "so:libmosquitto.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libmosquittopp.so.1=1" + ] + }, + "s6-rc": { + "versions": { + "0.4.1.0-r0": 1545076681 + }, + "origin": "s6-rc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexecline.so.2.5", + "so:libs6.so.2.7", + "so:libskarnet.so.2.7" + ], + "provides": [ + "so:libs6rc.so.0.4=0.4.1.0", + "cmd:s6-rc", + "cmd:s6-rc-bundle", + "cmd:s6-rc-compile", + "cmd:s6-rc-db", + "cmd:s6-rc-dryrun", + "cmd:s6-rc-format-upgrade", + "cmd:s6-rc-init", + "cmd:s6-rc-update" + ] + }, + "libdvdread-doc": { + "versions": { + "6.0.0-r0": 1545838475 + }, + "origin": "libdvdread" + }, + "perl-archive-zip": { + "versions": { + "1.64-r0": 1544001402 + }, + "origin": "perl-archive-zip", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:crc32" + ] + }, + "nfs-utils-dev": { + "versions": { + "2.3.2-r1": 1543933420 + }, + "origin": "nfs-utils", + "dependencies": [ + "libnfsidmap=2.3.2-r1", + "pkgconfig" + ], + "provides": [ + "pc:libnfsidmap=2.3.2" + ] + }, + "multipath-tools-doc": { + "versions": { + "0.7.9-r0": 1545224102 + }, + "origin": "multipath-tools" + }, + "subunit-dev": { + "versions": { + "1.2.0-r0": 1543933432 + }, + "origin": "subunit", + "dependencies": [ + "pkgconfig", + "subunit-libs=1.2.0-r0" + ], + "provides": [ + "pc:libcppunit_subunit=1.2.0", + "pc:libsubunit=1.2.0" + ] + }, + "upower-doc": { + "versions": { + "0.99.7-r0": 1545069385 + }, + "origin": "upower" + }, + "acf-amavisd-new": { + "versions": { + "0.4.0-r2": 1545299896 + }, + "origin": "acf-amavisd-new", + "dependencies": [ + "acf-core", + "amavisd-new" + ] + }, + "parted": { + "versions": { + "3.2-r7": 1543935531 + }, + "origin": "parted", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02", + "so:libncursesw.so.6", + "so:libreadline.so.7", + "so:libuuid.so.1" + ], + "provides": [ + "so:libparted-fs-resize.so.0=0.0.1", + "so:libparted.so.2=2.0.1", + "cmd:parted", + "cmd:partprobe" + ] + }, + "freeradius-unixodbc": { + "versions": { + "3.0.17-r5": 1556201988 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.17-r5", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ], + "provides": [ + "freeradius3-unixodbc=3.0.17-r5", + "so:rlm_sql_unixodbc.so=0" + ] + }, + "xgamma-doc": { + "versions": { + "1.0.6-r0": 1545214307 + }, + "origin": "xgamma" + }, + "cracklib-dev": { + "versions": { + "2.9.6-r0": 1545060799 + }, + "origin": "cracklib", + "dependencies": [ + "cracklib=2.9.6-r0" + ] + }, + "bitlbee-doc": { + "versions": { + "3.5.1-r4": 1543248420 + }, + "origin": "bitlbee" + }, + "perl-io-async": { + "versions": { + "0.72-r0": 1544799158 + }, + "origin": "perl-io-async", + "dependencies": [ + "perl-struct-dumb", + "perl-future" + ] + }, + "libepoxy": { + "versions": { + "1.5.3-r0": 1545838421 + }, + "origin": "libepoxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libepoxy.so.0=0.0.0" + ] + }, + "ldoc": { + "versions": { + "1.4.6-r1": 1545300108 + }, + "origin": "ldoc", + "dependencies": [ + "lua5.3-penlight", + "lua5.3" + ], + "provides": [ + "cmd:ldoc" + ] + }, + "lua-mosquitto": { + "versions": { + "0.2-r1": 1543932703 + }, + "origin": "lua-mosquitto" + }, + "iceauth-doc": { + "versions": { + "1.0.8-r0": 1545214112 + }, + "origin": "iceauth" + }, + "py2-django-tables2": { + "versions": { + "1.21.2-r0": 1543925702 + }, + "origin": "py-django-tables2", + "dependencies": [ + "py2-django", + "py2-six", + "python2" + ], + "provides": [ + "py-django-tables2-r1.21.2" + ] + }, + "xf86-video-ati": { + "versions": { + "18.1.0-r0": 1545223268 + }, + "origin": "xf86-video-ati", + "dependencies": [ + "mesa-dri-ati", + "so:libc.musl-x86_64.so.1", + "so:libdrm_radeon.so.1", + "so:libgbm.so.1", + "so:libpciaccess.so.0", + "so:libudev.so.1" + ] + }, + "perl-data-uuid": { + "versions": { + "1.221-r1": 1545163354 + }, + "origin": "perl-data-uuid", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libnfnetlink": { + "versions": { + "1.0.1-r1": 1543924449 + }, + "origin": "libnfnetlink", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnfnetlink.so.0=0.2.0" + ] + }, + "lua5.2-dbi-postgresql": { + "versions": { + "0.6-r3": 1545214222 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "linux-firmware-bnx2": { + "versions": { + "20190322-r0": 1554980654 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "py-oauth2": { + "versions": { + "1.9.0-r0": 1543245788 + }, + "origin": "py-oauth2", + "dependencies": [ + "python2", + "py-httplib2" + ] + }, + "libnfs-doc": { + "versions": { + "3.0.0-r0": 1544799294 + }, + "origin": "libnfs" + }, + "squashfs-tools": { + "versions": { + "4.3-r5": 1543926510 + }, + "origin": "squashfs-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblz4.so.1", + "so:liblzma.so.5", + "so:liblzo2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:mksquashfs", + "cmd:unsquashfs" + ] + }, + "perl-class-returnvalue": { + "versions": { + "0.55-r1": 1544798602 + }, + "origin": "perl-class-returnvalue", + "dependencies": [ + "perl", + "perl-devel-stacktrace" + ] + }, + "libatomic": { + "versions": { + "8.3.0-r0": 1554745497 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libatomic.so.1=1.2.0" + ] + }, + "libintl": { + "versions": { + "0.19.8.1-r4": 1542304412 + }, + "origin": "gettext", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libintl.so.8=8.1.5" + ] + }, + "mrxvt-doc": { + "versions": { + "0.5.4-r7": 1545073299 + }, + "origin": "mrxvt" + }, + "libltdl": { + "versions": { + "2.4.6-r5": 1542301345 + }, + "origin": "libtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libltdl.so.7=7.3.1" + ] + }, + "apache2-doc": { + "versions": { + "2.4.39-r0": 1554306834 + }, + "origin": "apache2" + }, + "qemu-or1k": { + "versions": { + "3.1.0-r3": 1551107301 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-or1k" + ] + }, + "uwsgi-spooler": { + "versions": { + "2.0.17.1-r0": 1545062210 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "rtmpdump-dev": { + "versions": { + "2.4_git20160909-r6": 1545214999 + }, + "origin": "rtmpdump", + "dependencies": [ + "zlib-dev", + "librtmp=2.4_git20160909-r6", + "pc:gnutls", + "pc:hogweed", + "pc:nettle", + "pkgconfig" + ], + "provides": [ + "pc:librtmp=v2.4" + ] + }, + "samba-common-tools": { + "versions": { + "4.8.11-r1": 1555334972 + }, + "origin": "samba", + "dependencies": [ + "so:libCHARSET3-samba4.so", + "so:libaddns-samba4.so", + "so:libads-samba4.so", + "so:libauth-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-smb-common-samba4.so", + "so:libcli-spoolss-samba4.so", + "so:libcliauth-samba4.so", + "so:libcmdline-contexts-samba4.so", + "so:libcom_err.so.2", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libflag-mapping-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libgssapi-samba4.so.2", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldap-2.4.so.2", + "so:liblibcli-lsa3-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmsrpc3-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libnetapi.so.0", + "so:libpopt-samba3-cmdline-samba4.so", + "so:libpopt-samba3-samba4.so", + "so:libpopt.so.0", + "so:libprinting-migrate-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-modules-samba4.so", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb.so.0", + "so:libsecrets3-samba4.so", + "so:libserver-id-db-samba4.so", + "so:libserver-role-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-base-samba4.so", + "so:libsmbd-shim-samba4.so", + "so:libsmbldap.so.2", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtime-basic-samba4.so", + "so:libtrusts-util-samba4.so", + "so:libutil-reg-samba4.so", + "so:libutil-tdb-samba4.so", + "so:libwbclient.so.0" + ], + "provides": [ + "so:libgpext-samba4.so=0", + "so:libnet-keytab-samba4.so=0", + "cmd:net", + "cmd:pdbedit", + "cmd:profiles", + "cmd:smbcontrol", + "cmd:smbpasswd", + "cmd:testparm" + ] + }, + "openldap-overlay-syncprov": { + "versions": { + "2.4.47-r2": 1546016480 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "openssl-dev": { + "versions": { + "1.1.1b-r1": 1552660099 + }, + "origin": "openssl", + "dependencies": [ + "libcrypto1.1=1.1.1b-r1", + "libssl1.1=1.1.1b-r1", + "pkgconfig" + ], + "provides": [ + "pc:libcrypto=1.1.1b", + "pc:libssl=1.1.1b", + "pc:openssl=1.1.1b" + ] + }, + "uwsgi-router_radius": { + "versions": { + "2.0.17.1-r0": 1545062207 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "nmap-scripts": { + "versions": { + "7.70-r3": 1545163172 + }, + "origin": "nmap", + "dependencies": [ + "nmap-nselibs" + ] + }, + "libmilter-doc": { + "versions": { + "1.0.2-r6": 1543927484 + }, + "origin": "libmilter" + }, + "ruby-sdbm": { + "versions": { + "2.5.5-r0": 1557164837 + }, + "origin": "ruby", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.5" + ] + }, + "py2-dateutil": { + "versions": { + "2.7.3-r0": 1543248495 + }, + "origin": "py-dateutil", + "dependencies": [ + "py2-six", + "python2" + ] + }, + "perl-datetime-timezone": { + "versions": { + "2.19-r0": 1542973145 + }, + "origin": "perl-datetime-timezone", + "dependencies": [ + "perl-class-singleton", + "perl-params-validationcompiler", + "perl-namespace-autoclean", + "perl-try-tiny", + "perl-module-runtime", + "perl-specio" + ] + }, + "compiler-rt": { + "versions": { + "5.0.2-r0": 1546873979 + }, + "origin": "compiler-rt" + }, + "postgresql-contrib": { + "versions": { + "11.2-r0": 1554274171 + }, + "origin": "postgresql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpq.so.5", + "so:libssl.so.1.1", + "so:libuuid.so.1", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:oid2name", + "cmd:pg_standby", + "cmd:vacuumlo" + ] + }, + "sfic-doc": { + "versions": { + "0.1.7-r6": 1545208618 + }, + "origin": "sfic" + }, + "xev": { + "versions": { + "1.2.2-r0": 1545301876 + }, + "origin": "xev", + "dependencies": [ + "so:libX11.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xev" + ] + }, + "libgudev-dev": { + "versions": { + "230-r2": 1542845392 + }, + "origin": "libgudev", + "dependencies": [ + "libgudev=230-r2", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gudev-1.0=230" + ] + }, + "xen-bridge": { + "versions": { + "4.11.1-r1": 1545075897 + }, + "origin": "xen", + "dependencies": [ + "dnsmasq" + ] + }, + "perl-text-autoformat": { + "versions": { + "1.74-r0": 1545118022 + }, + "origin": "perl-text-autoformat", + "dependencies": [ + "perl", + "perl-text-reform" + ] + }, + "freetds-doc": { + "versions": { + "1.00.104-r0": 1543934550 + }, + "origin": "freetds" + }, + "gnuchess-doc": { + "versions": { + "6.2.5-r1": 1545302331 + }, + "origin": "gnuchess" + }, + "vde2-libs": { + "versions": { + "2.3.2-r10": 1545074025 + }, + "origin": "vde2", + "dependencies": [ + "openssl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libvdehist.so.0=0.0.1", + "so:libvdemgmt.so.0=0.0.1", + "so:libvdeplug.so.3=3.0.1", + "so:libvdesnmp.so.0=0.0.1" + ] + }, + "py3-pytest": { + "versions": { + "4.1.0-r0": 1548112480 + }, + "origin": "pytest", + "dependencies": [ + "py3-atomicwrites", + "py3-py", + "py3-six", + "py3-attrs", + "py3-more-itertools", + "py3-pluggy", + "py3-setuptools", + "python3" + ], + "provides": [ + "cmd:py.test-3", + "cmd:pytest-3" + ] + }, + "dovecot-pigeonhole-plugin-ldap": { + "versions": { + "2.3.6-r0": 1557134098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot-pigeonhole-plugin=2.3.6-r0", + "dovecot-ldap=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ] + }, + "cjdns-openrc": { + "versions": { + "20.2-r1": 1546423250 + }, + "origin": "cjdns" + }, + "lua-evdev": { + "versions": { + "2.2.1-r1": 1545076556 + }, + "origin": "lua-evdev" + }, + "perl-dbd-odbc-doc": { + "versions": { + "1.60-r0": 1545061020 + }, + "origin": "perl-dbd-odbc" + }, + "fakeroot": { + "versions": { + "1.23-r0": 1542304762 + }, + "origin": "fakeroot", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfakeroot-0.so=0", + "cmd:faked", + "cmd:fakeroot" + ] + }, + "libseccomp": { + "versions": { + "2.3.3-r1": 1543923554 + }, + "origin": "libseccomp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libseccomp.so.2=2.3.3", + "cmd:scmp_sys_resolver" + ] + }, + "keyutils": { + "versions": { + "1.6-r0": 1545837151 + }, + "origin": "keyutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libkeyutils.so.1" + ], + "provides": [ + "cmd:key.dns_resolver", + "cmd:keyctl", + "cmd:request-key" + ] + }, + "libxcursor-dev": { + "versions": { + "1.1.15-r1": 1543925379 + }, + "origin": "libxcursor", + "dependencies": [ + "libxcursor=1.1.15-r1", + "pc:x11", + "pc:xfixes", + "pc:xproto", + "pc:xrender", + "pkgconfig" + ], + "provides": [ + "pc:xcursor=1.1.15" + ] + }, + "dhcpcd-openrc": { + "versions": { + "7.0.8-r0": 1545207907 + }, + "origin": "dhcpcd" + }, + "ldb-dev": { + "versions": { + "1.3.8-r0": 1555334661 + }, + "origin": "ldb", + "dependencies": [ + "ldb=1.3.8-r0", + "pc:talloc", + "pc:tdb", + "pkgconfig", + "py2-ldb=1.3.8-r0", + "py3-ldb=1.3.8-r0" + ], + "provides": [ + "pc:ldb=1.3.8", + "pc:pyldb-util.cpython-36m-x86_64-linux-gnu=1.3.8", + "pc:pyldb-util=1.3.8" + ] + }, + "perl-posix-strftime-compiler-doc": { + "versions": { + "0.42-r0": 1544792383 + }, + "origin": "perl-posix-strftime-compiler" + }, + "udns": { + "versions": { + "0.4-r0": 1542924414 + }, + "origin": "udns", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libudns.so.0=0", + "cmd:dnsget", + "cmd:ex-rdns", + "cmd:rblcheck" + ] + }, + "zstd": { + "versions": { + "1.3.8-r0": 1546966444 + }, + "origin": "zstd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:unzstd", + "cmd:zstd", + "cmd:zstdcat", + "cmd:zstdgrep", + "cmd:zstdless", + "cmd:zstdmt" + ] + }, + "perl-gd": { + "versions": { + "2.67-r0": 1543077253 + }, + "origin": "perl-gd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3" + ], + "provides": [ + "cmd:bdf2gdfont.pl" + ] + }, + "lxc-templates": { + "versions": { + "3.1.0-r1": 1549966162 + }, + "origin": "lxc", + "dependencies": [ + "tar" + ] + }, + "py2-pynacl": { + "versions": { + "1.3.0-r1": 1546125263 + }, + "origin": "py-pynacl", + "dependencies": [ + "py2-cffi", + "py2-six", + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "py2-mccabe": { + "versions": { + "0.6.1-r1": 1545060941 + }, + "origin": "py-mccabe", + "dependencies": [ + "python2" + ] + }, + "libcap-doc": { + "versions": { + "2.26-r0": 1546585711 + }, + "origin": "libcap" + }, + "audit-doc": { + "versions": { + "2.8.4-r0": 1543245856 + }, + "origin": "audit" + }, + "jasper-libs": { + "versions": { + "2.0.14-r0": 1543932348 + }, + "origin": "jasper", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8" + ], + "provides": [ + "so:libjasper.so.4=4.0.0" + ] + }, + "postgresql-pllua": { + "versions": { + "2.0-r0": 1544798727 + }, + "origin": "postgresql-pllua", + "dependencies": [ + "postgresql", + "so:libc.musl-x86_64.so.1", + "so:libluajit-5.1.so.2" + ], + "provides": [ + "pllua=2.0-r0" + ] + }, + "lua5.3-file-magic": { + "versions": { + "0.2-r1": 1545116948 + }, + "origin": "lua-file-magic", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libmagic.so.1" + ] + }, + "libxrandr": { + "versions": { + "1.5.1-r2": 1542972233 + }, + "origin": "libxrandr", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXrandr.so.2=2.2.0" + ] + }, + "lutok": { + "versions": { + "0.4-r2": 1542304899 + }, + "origin": "lutok", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:liblua-5.3.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:liblutok.so.3=3.0.0" + ] + }, + "fribidi-dev": { + "versions": { + "1.0.5-r0": 1542824048 + }, + "origin": "fribidi", + "dependencies": [ + "fribidi=1.0.5-r0", + "pkgconfig" + ], + "provides": [ + "pc:fribidi=1.0.5" + ] + }, + "distcc-doc": { + "versions": { + "3.3.2-r0": 1544799001 + }, + "origin": "distcc" + }, + "py3-cliapp": { + "versions": { + "1.20170823-r0": 1544798595 + }, + "origin": "py-cliapp", + "dependencies": [ + "python2", + "python3", + "python3" + ] + }, + "wpa_supplicant-openrc": { + "versions": { + "2.7-r2": 1555501504 + }, + "origin": "wpa_supplicant" + }, + "perl-test2-suite-doc": { + "versions": { + "0.000114-r0": 1542973041 + }, + "origin": "perl-test2-suite" + }, + "one-context": { + "versions": { + "0.5.5-r0": 1544798553 + }, + "origin": "one-context", + "dependencies": [ + "blkid" + ] + }, + "libxdmcp-dev": { + "versions": { + "1.1.2-r5": 1542822510 + }, + "origin": "libxdmcp", + "dependencies": [ + "libxdmcp=1.1.2-r5", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xdmcp=1.1.2" + ] + }, + "libtool": { + "versions": { + "2.4.6-r5": 1542301347 + }, + "origin": "libtool", + "dependencies": [ + "bash", + "libltdl" + ], + "provides": [ + "cmd:libtool", + "cmd:libtoolize" + ] + }, + "perl-net-rblclient": { + "versions": { + "0.5-r3": 1545214329 + }, + "origin": "perl-net-rblclient", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:spamalyze" + ] + }, + "file": { + "versions": { + "5.36-r0": 1557160813 + }, + "origin": "file", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmagic.so.1" + ], + "provides": [ + "cmd:file" + ] + }, + "umurmur-doc": { + "versions": { + "0.2.17-r3": 1545207064 + }, + "origin": "umurmur" + }, + "perl-devel-checkbin": { + "versions": { + "0.02-r0": 1542845750 + }, + "origin": "perl-devel-checkbin" + }, + "kamailio-jansson": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libjansson.so.4" + ] + }, + "jack-dev": { + "versions": { + "1.9.12-r0": 1545073438 + }, + "origin": "jack", + "dependencies": [ + "jack=1.9.12-r0", + "pkgconfig" + ], + "provides": [ + "pc:jack=1.9.12" + ] + }, + "freetype-doc": { + "versions": { + "2.9.1-r2": 1542822031 + }, + "origin": "freetype" + }, + "libmodplug-doc": { + "versions": { + "0.8.9.0-r0": 1545208837 + }, + "origin": "libmodplug" + }, + "cyrus-sasl-doc": { + "versions": { + "2.1.27-r1": 1550353527 + }, + "origin": "cyrus-sasl" + }, + "libgit2-tests": { + "versions": { + "0.27.7-r0": 1545838464 + }, + "origin": "libgit2" + }, + "acf-openssh": { + "versions": { + "0.11.2-r0": 1545292772 + }, + "origin": "acf-openssh", + "dependencies": [ + "acf-core", + "openssh" + ] + }, + "qemu-system-unicore32": { + "versions": { + "3.1.0-r3": 1551107306 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-unicore32" + ] + }, + "libtirpc": { + "versions": { + "1.0.3-r0": 1543223618 + }, + "origin": "libtirpc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgssapi_krb5.so.2" + ], + "provides": [ + "so:libtirpc.so.3=3.0.0" + ] + }, + "lua5.2-socket": { + "versions": { + "3.0_rc1_git20160306-r2": 1543934472 + }, + "origin": "lua-socket", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "mrtg-doc": { + "versions": { + "2.17.7-r0": 1545858156 + }, + "origin": "mrtg" + }, + "libass": { + "versions": { + "0.14.0-r0": 1545300165 + }, + "origin": "libass", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libfribidi.so.0" + ], + "provides": [ + "so:libass.so.9=9.0.2" + ] + }, + "openldap-overlay-refint": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "pkgconf-doc": { + "versions": { + "1.6.0-r0": 1547496958 + }, + "origin": "pkgconf" + }, + "keyutils-doc": { + "versions": { + "1.6-r0": 1545837151 + }, + "origin": "keyutils" + }, + "libmodplug": { + "versions": { + "0.8.9.0-r0": 1545208838 + }, + "origin": "libmodplug", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libmodplug.so.1=1.0.0" + ] + }, + "nagios-plugins-fping": { + "versions": { + "2.2.1-r6": 1543933905 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "fping", + "so:libc.musl-x86_64.so.1" + ] + }, + "ilbc-dev": { + "versions": { + "0.0.1-r0": 1545117064 + }, + "origin": "ilbc", + "dependencies": [ + "ilbc=0.0.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:ilbc=0.0.1" + ] + }, + "rdesktop-doc": { + "versions": { + "1.8.3-r5": 1545068564 + }, + "origin": "rdesktop" + }, + "xmlto-doc": { + "versions": { + "0.0.28-r2": 1542822498 + }, + "origin": "xmlto" + }, + "rarian-dev": { + "versions": { + "0.8.1-r7": 1542823681 + }, + "origin": "rarian", + "dependencies": [ + "pkgconfig", + "rarian=0.8.1-r7" + ], + "provides": [ + "pc:rarian=0.8.1", + "cmd:rarian-sk-config", + "cmd:scrollkeeper-config" + ] + }, + "gobject-introspection-dev": { + "versions": { + "1.56.1-r0": 1542823016 + }, + "origin": "gobject-introspection", + "dependencies": [ + "python3", + "cairo-dev", + "libtool", + "gobject-introspection=1.56.1-r0", + "pc:glib-2.0", + "pc:gmodule-2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:libffi", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libgio-2.0.so.0", + "so:libgirepository-1.0.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "pc:gobject-introspection-1.0=1.56.1", + "pc:gobject-introspection-no-export-1.0=1.56.1", + "cmd:g-ir-annotation-tool", + "cmd:g-ir-compiler", + "cmd:g-ir-generate", + "cmd:g-ir-inspect", + "cmd:g-ir-scanner" + ] + }, + "udisks2-lang": { + "versions": { + "2.6.5-r1": 1545293057 + }, + "origin": "udisks2" + }, + "qemu-sparc": { + "versions": { + "3.1.0-r3": 1551107302 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-sparc" + ] + }, + "font-sun-misc": { + "versions": { + "1.0.3-r0": 1545165113 + }, + "origin": "font-sun-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "libcroco": { + "versions": { + "0.6.12-r1": 1543926546 + }, + "origin": "libcroco", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libcroco-0.6.so.3=3.0.1", + "cmd:csslint-0.6" + ] + }, + "perl-time-hires": { + "versions": { + "1.9758-r0": 1545061218 + }, + "origin": "perl-time-hires", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "mariadb-common": { + "versions": { + "10.3.13-r1": 1557431734 + }, + "origin": "mariadb", + "dependencies": [ + "/bin/sh" + ] + }, + "libxext": { + "versions": { + "1.3.3-r3": 1542822855 + }, + "origin": "libxext", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXext.so.6=6.4.0" + ] + }, + "py-bcrypt": { + "versions": { + "3.1.4-r0": 1545216390 + }, + "origin": "py-bcrypt" + }, + "perl-log-any": { + "versions": { + "1.707-r0": 1545208928 + }, + "origin": "perl-log-any" + }, + "perl-scope-upper": { + "versions": { + "0.31-r0": 1545164596 + }, + "origin": "perl-scope-upper", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-imagesize": { + "versions": { + "1.1.0-r0": 1542824996 + }, + "origin": "py-imagesize", + "dependencies": [ + "python2" + ] + }, + "tiff": { + "versions": { + "4.0.10-r0": 1543921906 + }, + "origin": "tiff", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8", + "so:libz.so.1" + ], + "provides": [ + "so:libtiff.so.5=5.4.0" + ] + }, + "lxc-lvm": { + "versions": { + "3.1.0-r1": 1549966161 + }, + "origin": "lxc", + "dependencies": [ + "lxc=3.1.0-r1", + "lvm2", + "util-linux" + ] + }, + "py-pynacl": { + "versions": { + "1.3.0-r1": 1546125263 + }, + "origin": "py-pynacl", + "dependencies": [ + "py-cffi", + "py-six" + ] + }, + "py2-jinja2": { + "versions": { + "2.10-r2": 1542824936 + }, + "origin": "py-jinja2", + "dependencies": [ + "py2-markupsafe", + "python2" + ] + }, + "py2-country": { + "versions": { + "18.12.8-r0": 1548109640 + }, + "origin": "py-country", + "dependencies": [ + "python2" + ] + }, + "liboping-doc": { + "versions": { + "1.10.0-r0": 1545214322 + }, + "origin": "liboping" + }, + "syslog-ng-http": { + "versions": { + "3.19.1-r0": 1548543151 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "perl-exporter-tiny-doc": { + "versions": { + "1.002001-r0": 1542845619 + }, + "origin": "perl-exporter-tiny" + }, + "xclip": { + "versions": { + "0.13-r0": 1542883354 + }, + "origin": "xclip", + "dependencies": [ + "so:libX11.so.6", + "so:libXmu.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xclip", + "cmd:xclip-copyfile", + "cmd:xclip-cutfile", + "cmd:xclip-pastefile" + ] + }, + "directfb-doc": { + "versions": { + "1.7.7-r1": 1543934410 + }, + "origin": "directfb" + }, + "sipcalc": { + "versions": { + "1.1.6-r0": 1545164539 + }, + "origin": "sipcalc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sipcalc" + ] + }, + "py-purl": { + "versions": { + "1.4-r0": 1543925756 + }, + "origin": "py-purl", + "dependencies": [ + "py-six" + ] + }, + "mosh": { + "versions": { + "1.3.2-r7": 1543932001 + }, + "origin": "mosh", + "dependencies": [ + "mosh-client", + "mosh-server" + ], + "provides": [ + "cmd:mosh" + ] + }, + "fftw-long-double-libs": { + "versions": { + "3.3.8-r0": 1543926479 + }, + "origin": "fftw", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfftw3l.so.3=3.5.8", + "so:libfftw3l_threads.so.3=3.5.8" + ] + }, + "xf86-video-siliconmotion-doc": { + "versions": { + "1.7.9-r3": 1545235257 + }, + "origin": "xf86-video-siliconmotion" + }, + "hostapd-doc": { + "versions": { + "2.7-r0": 1547822982 + }, + "origin": "hostapd" + }, + "aconf-mod-dnsmasq": { + "versions": { + "0.7.1-r0": 1553432497 + }, + "origin": "aconf", + "dependencies": [ + "aconf", + "dnsmasq" + ] + }, + "libgsasl": { + "versions": { + "1.8.0-r3": 1545215966 + }, + "origin": "libgsasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libidn.so.12" + ], + "provides": [ + "so:libgsasl.so.7=7.9.6" + ] + }, + "rsyslog-uxsock": { + "versions": { + "8.40.0-r3": 1548686792 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "rsyslog-omuxsock=8.40.0-r3" + ] + }, + "nagios-plugins-ntp": { + "versions": { + "2.2.1-r6": 1543933909 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "acf-samba": { + "versions": { + "0.10.0-r2": 1545290860 + }, + "origin": "acf-samba", + "dependencies": [ + "acf-core", + "samba" + ] + }, + "gnutls-dbg": { + "versions": { + "3.6.7-r0": 1555049934 + }, + "origin": "gnutls" + }, + "nagios-plugins-nt": { + "versions": { + "2.2.1-r6": 1543933909 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-mail-dkim-doc": { + "versions": { + "0.54-r0": 1545061192 + }, + "origin": "perl-mail-dkim" + }, + "perl-text-password-pronounceable-doc": { + "versions": { + "0.30-r1": 1543928463 + }, + "origin": "perl-text-password-pronounceable" + }, + "gtkspell": { + "versions": { + "2.0.16-r7": 1545215217 + }, + "origin": "gtkspell", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libenchant.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libgtkspell.so.0=0.0.0" + ] + }, + "font-mutt-misc": { + "versions": { + "1.0.3-r0": 1545216347 + }, + "origin": "font-mutt-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "tinyproxy-doc": { + "versions": { + "1.10.0-r1": 1551109287 + }, + "origin": "tinyproxy" + }, + "libtasn1-doc": { + "versions": { + "4.13-r0": 1542824445 + }, + "origin": "libtasn1" + }, + "sic": { + "versions": { + "1.2-r1": 1543248489 + }, + "origin": "sic", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sic" + ] + }, + "bzip2-dev": { + "versions": { + "1.0.6-r6": 1542300119 + }, + "origin": "bzip2", + "dependencies": [ + "libbz2=1.0.6-r6" + ] + }, + "lua-posix-doc": { + "versions": { + "33.4.0-r1": 1546010825 + }, + "origin": "lua-posix" + }, + "libxmu-doc": { + "versions": { + "1.1.2-r1": 1542883345 + }, + "origin": "libxmu" + }, + "acf-dansguardian": { + "versions": { + "0.8.0-r2": 1545207100 + }, + "origin": "acf-dansguardian", + "dependencies": [ + "acf-core", + "dansguardian" + ] + }, + "font-adobe-utopia-100dpi": { + "versions": { + "1.0.4-r0": 1543924839 + }, + "origin": "font-adobe-utopia-100dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "py-django-phonenumber-field": { + "versions": { + "2.0.0-r0": 1543925692 + }, + "origin": "py-django-phonenumber-field", + "dependencies": [ + "py-babel", + "py-django", + "py-phonenumbers" + ] + }, + "py2-backports_abc": { + "versions": { + "0.5-r0": 1545293193 + }, + "origin": "py-backports_abc", + "dependencies": [ + "python2" + ] + }, + "qemu-system-sh4": { + "versions": { + "3.1.0-r3": 1551107305 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-sh4" + ] + }, + "libpaper-dev": { + "versions": { + "1.1.24-r4": 1542824768 + }, + "origin": "libpaper", + "dependencies": [ + "libpaper=1.1.24-r4" + ] + }, + "perl-io-html-doc": { + "versions": { + "1.001-r1": 1542821789 + }, + "origin": "perl-io-html" + }, + "openssh-doc": { + "versions": { + "7.9_p1-r5": 1556034587 + }, + "origin": "openssh" + }, + "chrpath-doc": { + "versions": { + "0.16-r1": 1542900311 + }, + "origin": "chrpath" + }, + "perl-namespace-autoclean-doc": { + "versions": { + "0.28-r0": 1542973134 + }, + "origin": "perl-namespace-autoclean" + }, + "tinc-doc": { + "versions": { + "1.0.35-r1": 1545299617 + }, + "origin": "tinc" + }, + "ncurses-libs": { + "versions": { + "6.1_p20190105-r0": 1546948259 + }, + "origin": "ncurses", + "dependencies": [ + "ncurses-terminfo-base", + "ncurses-terminfo=6.1_p20190105-r0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "ncurses-widec-libs=6.1_p20190105-r0", + "so:libformw.so.6=6.1", + "so:libmenuw.so.6=6.1", + "so:libncursesw.so.6=6.1", + "so:libpanelw.so.6=6.1" + ] + }, + "perl-specio-doc": { + "versions": { + "0.42-r0": 1542972980 + }, + "origin": "perl-specio" + }, + "libinput-doc": { + "versions": { + "1.12.4-r0": 1545734494 + }, + "origin": "libinput" + }, + "perl-test-warnings-doc": { + "versions": { + "0.026-r0": 1542845607 + }, + "origin": "perl-test-warnings" + }, + "kamailio-jsdt": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1" + ] + }, + "snappy-dev": { + "versions": { + "1.1.7-r1": 1545208921 + }, + "origin": "snappy", + "dependencies": [ + "snappy=1.1.7-r1" + ] + }, + "mod_dav_svn": { + "versions": { + "1.11.1-r0": 1548692375 + }, + "origin": "subversion", + "dependencies": [ + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libsvn_delta-1.so.0", + "so:libsvn_fs-1.so.0", + "so:libsvn_repos-1.so.0", + "so:libsvn_subr-1.so.0" + ] + }, + "twm-doc": { + "versions": { + "1.0.10-r0": 1545209633 + }, + "origin": "twm" + }, + "py2-pbr": { + "versions": { + "5.1.1-r0": 1545235274 + }, + "origin": "py-pbr", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:pbr" + ] + }, + "aaudit-server": { + "versions": { + "0.7.2-r1": 1543934481 + }, + "origin": "aaudit", + "dependencies": [ + "aaudit", + "git", + "lua5.2", + "lua5.2-posix", + "lua5.2-cjson", + "lua5.2-lzlib" + ], + "provides": [ + "cmd:aaudit-repo", + "cmd:aaudit-update-keys" + ] + }, + "libxinerama-dev": { + "versions": { + "1.1.4-r1": 1543077317 + }, + "origin": "libxinerama", + "dependencies": [ + "libxinerama=1.1.4-r1", + "pc:x11", + "pc:xext", + "pc:xineramaproto", + "pkgconfig" + ], + "provides": [ + "pc:xinerama=1.1.4" + ] + }, + "yasm": { + "versions": { + "1.3.0-r1": 1545117180 + }, + "origin": "yasm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:vsyasm", + "cmd:yasm", + "cmd:ytasm" + ] + }, + "pspg": { + "versions": { + "1.6.3-r0": 1545292943 + }, + "origin": "pspg", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libpanelw.so.6", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:pspg" + ] + }, + "linux-firmware-i915": { + "versions": { + "20190322-r0": 1554980652 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "aspell-utils": { + "versions": { + "0.60.6.1-r13": 1542965830 + }, + "origin": "aspell", + "dependencies": [ + "aspell", + "perl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:aspell-import", + "cmd:precat", + "cmd:preunzip", + "cmd:prezip", + "cmd:prezip-bin", + "cmd:run-with-aspell", + "cmd:word-list-compress" + ] + }, + "libgomp": { + "versions": { + "8.3.0-r0": 1554745497 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgomp.so.1=1.0.0" + ] + }, + "perl-convert-binhex": { + "versions": { + "1.125-r1": 1544001492 + }, + "origin": "perl-convert-binhex", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:binhex.pl", + "cmd:debinhex.pl" + ] + }, + "net-snmp-agent-libs": { + "versions": { + "5.8-r0": 1543923350 + }, + "origin": "net-snmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.35" + ], + "provides": [ + "so:libnetsnmpagent.so.35=35.0.0", + "so:libnetsnmphelpers.so.35=35.0.0", + "so:libnetsnmpmibs.so.35=35.0.0", + "so:libnetsnmptrapd.so.35=35.0.0" + ] + }, + "ppp-minconn": { + "versions": { + "2.4.7-r6": 1543999023 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "yajl-dev": { + "versions": { + "2.1.0-r0": 1543935354 + }, + "origin": "yajl", + "dependencies": [ + "pkgconfig", + "yajl=2.1.0-r0" + ], + "provides": [ + "pc:yajl=2.1.0" + ] + }, + "libmicrohttpd": { + "versions": { + "0.9.62-r0": 1545300154 + }, + "origin": "libmicrohttpd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30" + ], + "provides": [ + "so:libmicrohttpd.so.12=12.49.0" + ] + }, + "libffi": { + "versions": { + "3.2.1-r6": 1545154420 + }, + "origin": "libffi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libffi.so.6=6.0.4" + ] + }, + "kamailio-radius": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libfreeradius-client.so.2" + ] + }, + "zmap-doc": { + "versions": { + "2.1.1-r3": 1545062097 + }, + "origin": "zmap" + }, + "py-pygments-doc": { + "versions": { + "2.2.0-r0": 1542824950 + }, + "origin": "py-pygments" + }, + "libnih": { + "versions": { + "1.0.3-r5": 1545062345 + }, + "origin": "libnih", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libexpat.so.1" + ], + "provides": [ + "so:libnih-dbus.so.1=1.0.0", + "so:libnih.so.1=1.0.0", + "cmd:nih-dbus-tool" + ] + }, + "poppler-qt4-dev": { + "versions": { + "0.56.0-r0": 1545254179 + }, + "origin": "poppler-qt4", + "dependencies": [ + "jpeg-dev", + "cairo-dev", + "libxml2-dev", + "fontconfig-dev", + "qt-dev", + "poppler-dev>=0.56.0", + "lcms2-dev", + "openjpeg-dev", + "pkgconfig", + "poppler-qt4=0.56.0-r0" + ], + "provides": [ + "pc:poppler-qt4=0.56.0" + ] + }, + "mailcap": { + "versions": { + "2.1.48-r0": 1545062136 + }, + "origin": "mailcap" + }, + "atf-dev": { + "versions": { + "0.21-r1": 1542304873 + }, + "origin": "atf", + "dependencies": [ + "atf=0.21-r1", + "pkgconfig" + ], + "provides": [ + "pc:atf-c++=0.21", + "pc:atf-c=0.21", + "pc:atf-sh=0.21" + ] + }, + "perl-date-format-doc": { + "versions": { + "2.30-r0": 1543924841 + }, + "origin": "perl-date-format" + }, + "lua-bit32": { + "versions": { + "5.3.0-r2": 1546011988 + }, + "origin": "lua-bit32" + }, + "libx11-doc": { + "versions": { + "1.6.7-r0": 1542822727 + }, + "origin": "libx11" + }, + "jq-dev": { + "versions": { + "1.6-r0": 1544000550 + }, + "origin": "jq", + "dependencies": [ + "jq=1.6-r0" + ] + }, + "libx11-dev": { + "versions": { + "1.6.7-r0": 1542822710 + }, + "origin": "libx11", + "dependencies": [ + "libxcb-dev", + "xtrans", + "libx11=1.6.7-r0", + "pc:kbproto", + "pc:xcb", + "pc:xcb>=1.11.1", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:x11-xcb=1.6.7", + "pc:x11=1.6.7" + ] + }, + "rrsync": { + "versions": { + "3.1.3-r1": 1542820865 + }, + "origin": "rsync", + "dependencies": [ + "rsync", + "perl" + ], + "provides": [ + "cmd:rrsync" + ] + }, + "lua5.2-lxc": { + "versions": { + "3.0.2-r0": 1546416498 + }, + "origin": "lua-lxc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblxc.so.1" + ] + }, + "libcdio-tools": { + "versions": { + "0.94-r2": 1543248376 + }, + "origin": "libcdio", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcddb.so.2", + "so:libcdio.so.16", + "so:libiso9660.so.10", + "so:libncursesw.so.6", + "so:libudf.so.0" + ], + "provides": [ + "cmd:cd-drive", + "cmd:cd-info", + "cmd:cd-read", + "cmd:cdda-player", + "cmd:iso-info", + "cmd:iso-read", + "cmd:mmc-tool" + ] + }, + "openipmi-dev": { + "versions": { + "2.0.25-r1": 1545214269 + }, + "origin": "openipmi", + "dependencies": [ + "openipmi-lanserv=2.0.25-r1", + "openipmi-libs=2.0.25-r1", + "pc:ncurses", + "pkgconfig" + ], + "provides": [ + "pc:OpenIPMI=2.0.25", + "pc:OpenIPMIcmdlang=2.0.25", + "pc:OpenIPMIglib=2.0.25", + "pc:OpenIPMIposix=2.0.25", + "pc:OpenIPMIpthread=2.0.25", + "pc:OpenIPMIui=2.0.25", + "pc:OpenIPMIutils=2.0.25" + ] + }, + "collectd-nginx": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "spandsp": { + "versions": { + "0.0.6-r1": 1545815280 + }, + "origin": "spandsp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtiff.so.5" + ], + "provides": [ + "so:libspandsp.so.2=2.0.0" + ] + }, + "perl-module-versions-report": { + "versions": { + "1.06-r0": 1545067097 + }, + "origin": "perl-module-versions-report", + "dependencies": [ + "perl" + ] + }, + "privoxy-doc": { + "versions": { + "3.0.26-r0": 1545154403 + }, + "origin": "privoxy" + }, + "asterisk-curl": { + "versions": { + "15.7.1-r0": 1546247586 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "poppler-dev": { + "versions": { + "0.56.0-r1": 1542824315 + }, + "origin": "poppler", + "dependencies": [ + "cairo-dev", + "glib-dev", + "pc:cairo>=1.10.0", + "pc:gio-2.0>=2.41", + "pc:glib-2.0>=2.41", + "pc:gobject-2.0>=2.41", + "pkgconfig", + "poppler-glib=0.56.0-r1", + "poppler=0.56.0-r1" + ], + "provides": [ + "pc:poppler-cairo=0.56.0", + "pc:poppler-cpp=0.56.0", + "pc:poppler-glib=0.56.0", + "pc:poppler-splash=0.56.0", + "pc:poppler=0.56.0" + ] + }, + "perl-net-snmp-doc": { + "versions": { + "6.0.1-r2": 1543923074 + }, + "origin": "perl-net-snmp" + }, + "rarian": { + "versions": { + "0.8.1-r7": 1542823683 + }, + "origin": "rarian", + "dependencies": [ + "bash", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:librarian.so.0=0.0.0", + "cmd:rarian-example", + "cmd:rarian-sk-extract", + "cmd:rarian-sk-gen-uuid", + "cmd:rarian-sk-get-cl", + "cmd:rarian-sk-get-content-list", + "cmd:rarian-sk-get-extended-content-list", + "cmd:rarian-sk-get-scripts", + "cmd:rarian-sk-install", + "cmd:rarian-sk-migrate", + "cmd:rarian-sk-preinstall", + "cmd:rarian-sk-rebuild", + "cmd:rarian-sk-update", + "cmd:scrollkeeper-extract", + "cmd:scrollkeeper-gen-seriesid", + "cmd:scrollkeeper-get-cl", + "cmd:scrollkeeper-get-content-list", + "cmd:scrollkeeper-get-extended-content-list", + "cmd:scrollkeeper-get-index-from-docpath", + "cmd:scrollkeeper-get-toc-from-docpath", + "cmd:scrollkeeper-get-toc-from-id", + "cmd:scrollkeeper-install", + "cmd:scrollkeeper-preinstall", + "cmd:scrollkeeper-rebuilddb", + "cmd:scrollkeeper-uninstall", + "cmd:scrollkeeper-update" + ] + }, + "zip": { + "versions": { + "3.0-r7": 1542301519 + }, + "origin": "zip", + "dependencies": [ + "unzip", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:zip", + "cmd:zipcloak", + "cmd:zipnote", + "cmd:zipsplit" + ] + }, + "tlsdate": { + "versions": { + "0.0.13-r7": 1545214008 + }, + "origin": "tlsdate", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.43", + "so:libevent-2.1.so.6", + "so:libssl.so.45" + ], + "provides": [ + "cmd:tlsdate", + "cmd:tlsdate-helper", + "cmd:tlsdated" + ] + }, + "check-dev": { + "versions": { + "0.12.0-r1": 1542822524 + }, + "origin": "check", + "dependencies": [ + "check=0.12.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:check=0.12.0" + ] + }, + "perl-class-mix-doc": { + "versions": { + "0.006-r0": 1545163343 + }, + "origin": "perl-class-mix" + }, + "rsyslog": { + "versions": { + "8.40.0-r3": 1548686792 + }, + "origin": "rsyslog", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libestr.so.0", + "so:libfastjson.so.4", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:rsyslogd" + ] + }, + "spice": { + "versions": { + "0.14.1-r6": 1548919556 + }, + "origin": "spice" + }, + "cairo-tools": { + "versions": { + "1.16.0-r1": 1546948315 + }, + "origin": "cairo", + "dependencies": [ + "so:libX11.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo-script-interpreter.so.2", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libpixman-1.so.0", + "so:libxcb-render.so.0", + "so:libxcb.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:cairo-sphinx", + "cmd:cairo-trace" + ] + }, + "perl-carp-doc": { + "versions": { + "1.38-r0": 1544001404 + }, + "origin": "perl-carp" + }, + "asciidoc-vim": { + "versions": { + "8.6.10-r0": 1542883778 + }, + "origin": "asciidoc" + }, + "alpine-mirrors": { + "versions": { + "3.5.10-r0": 1547140761 + }, + "origin": "alpine-mirrors" + }, + "perl-mailtools": { + "versions": { + "2.19-r0": 1543226635 + }, + "origin": "perl-mailtools", + "dependencies": [ + "perl-timedate" + ], + "provides": [ + "perl-mail-tools=2.19" + ] + }, + "py2-ecdsa": { + "versions": { + "0.13-r5": 1545301309 + }, + "origin": "py-ecdsa", + "dependencies": [ + "py2-crypto" + ] + }, + "ttf-liberation": { + "versions": { + "2.00.1-r1": 1545069526 + }, + "origin": "ttf-liberation", + "dependencies": [ + "fontconfig" + ] + }, + "perl-sub-exporter-progressive-doc": { + "versions": { + "0.001013-r0": 1542883263 + }, + "origin": "perl-sub-exporter-progressive" + }, + "boost-chrono": { + "versions": { + "1.67.0-r2": 1542823630 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_system-mt.so.1.67.0", + "so:libboost_system.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_chrono-mt.so.1.67.0=1.67.0", + "so:libboost_chrono.so.1.67.0=1.67.0" + ] + }, + "kamailio-ldap": { + "versions": { + "5.2.0-r1": 1546423170 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libldap-2.4.so.2", + "so:libsrdb2.so.1" + ] + }, + "libvirt-dev": { + "versions": { + "4.10.0-r1": 1547051657 + }, + "origin": "libvirt", + "dependencies": [ + "libtirpc-dev", + "libvirt-libs=4.10.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:libvirt-admin=4.10.0", + "pc:libvirt-lxc=4.10.0", + "pc:libvirt-qemu=4.10.0", + "pc:libvirt=4.10.0" + ] + }, + "vanessa_adt": { + "versions": { + "0.0.9-r0": 1545223299 + }, + "origin": "vanessa_adt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libvanessa_logger.so.0" + ], + "provides": [ + "so:libvanessa_adt.so.1=1.0.0" + ] + }, + "clucene": { + "versions": { + "2.3.3.4-r5": 1543999112 + }, + "origin": "clucene", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libclucene-core.so.1=2.3.3.4", + "so:libclucene-shared.so.1=2.3.3.4" + ] + }, + "lua-aports": { + "versions": { + "1.0.0-r0": 1547042898 + }, + "origin": "lua-aports", + "dependencies": [ + "lua5.2-cjson", + "lua5.2-filesystem", + "lua5.2-optarg", + "lua5.2" + ], + "provides": [ + "cmd:ap", + "cmd:buildrepo" + ] + }, + "xrefresh": { + "versions": { + "1.0.6-r0": 1545290858 + }, + "origin": "xrefresh", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xrefresh" + ] + }, + "mpg123-doc": { + "versions": { + "1.25.10-r0": 1545117149 + }, + "origin": "mpg123" + }, + "gst-plugins-base-lang": { + "versions": { + "1.14.4-r0": 1543928665 + }, + "origin": "gst-plugins-base" + }, + "acf-lighttpd": { + "versions": { + "0.6.0-r2": 1545207632 + }, + "origin": "acf-lighttpd", + "dependencies": [ + "acf-core", + "lighttpd" + ] + }, + "libxxf86misc-dev": { + "versions": { + "1.0.3-r2": 1543927841 + }, + "origin": "libxxf86misc", + "dependencies": [ + "libxxf86misc=1.0.3-r2", + "pc:x11", + "pc:xext", + "pc:xf86miscproto", + "pkgconfig" + ], + "provides": [ + "pc:xxf86misc=1.0.3" + ] + }, + "lua-xctrl-doc": { + "versions": { + "2015.04.10-r2": 1545208663 + }, + "origin": "lua-xctrl" + }, + "mpfr3-doc": { + "versions": { + "3.1.5-r1": 1542301546 + }, + "origin": "mpfr3" + }, + "libwebp-dev": { + "versions": { + "1.0.1-r0": 1545856968 + }, + "origin": "libwebp", + "dependencies": [ + "libwebp=1.0.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libwebp=1.0.1", + "pc:libwebpdecoder=1.0.1", + "pc:libwebpdemux=1.0.1", + "pc:libwebpmux=1.0.1" + ] + }, + "py-lxml": { + "versions": { + "4.2.5-r0": 1545065048 + }, + "origin": "py-lxml" + }, + "samba-libnss-winbind": { + "versions": { + "4.8.11-r1": 1555334974 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libwbclient.so.0", + "so:libwinbind-client-samba4.so" + ], + "provides": [ + "so:libnss_winbind.so.2=2", + "so:libnss_wins.so.2=2" + ] + }, + "gtk-engines-industrial": { + "versions": { + "2.21.0-r2": 1545289337 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "libmaxminddb-doc": { + "versions": { + "1.3.2-r0": 1543226666 + }, + "origin": "libmaxminddb" + }, + "py3-jwt": { + "versions": { + "1.6.4-r0": 1543249954 + }, + "origin": "py-jwt", + "dependencies": [ + "python3" + ] + }, + "py3-funcsigs": { + "versions": { + "1.0.2-r1": 1542824899 + }, + "origin": "py-funcsigs", + "dependencies": [ + "python3" + ] + }, + "perl-digest-md5-doc": { + "versions": { + "2.55-r2": 1544001428 + }, + "origin": "perl-digest-md5" + }, + "xprop": { + "versions": { + "1.2.3-r0": 1543254086 + }, + "origin": "xprop", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xprop" + ] + }, + "py-cryptography": { + "versions": { + "2.4.2-r2": 1545067167 + }, + "origin": "py-cryptography", + "dependencies": [ + "py-cffi", + "py-idna", + "py-asn1crypto", + "py-six" + ] + }, + "font-util": { + "versions": { + "1.3.1-r2": 1542924710 + }, + "origin": "font-util", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bdftruncate", + "cmd:ucs2any" + ] + }, + "squid-lang-af": { + "versions": { + "4.4-r1": 1545216322 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "fftw-dev": { + "versions": { + "3.3.8-r0": 1543926477 + }, + "origin": "fftw", + "dependencies": [ + "fftw-double-libs=3.3.8-r0", + "fftw-long-double-libs=3.3.8-r0", + "fftw-single-libs=3.3.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:fftw3=3.3.8", + "pc:fftw3f=3.3.8", + "pc:fftw3l=3.3.8" + ] + }, + "supervisor": { + "versions": { + "3.3.4-r1": 1545062792 + }, + "origin": "supervisor", + "dependencies": [ + "python2", + "py-meld3", + "py-setuptools" + ], + "provides": [ + "cmd:echo_supervisord_conf", + "cmd:pidproxy", + "cmd:supervisorctl", + "cmd:supervisord" + ] + }, + "font-cursor-misc": { + "versions": { + "1.0.3-r1": 1542973193 + }, + "origin": "font-cursor-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig", + "util-macros" + ] + }, + "cgdb-doc": { + "versions": { + "0.7.0-r2": 1545214140 + }, + "origin": "cgdb" + }, + "libxscrnsaver-doc": { + "versions": { + "1.2.2-r3": 1543932085 + }, + "origin": "libxscrnsaver" + }, + "lzip": { + "versions": { + "1.20-r0": 1542304994 + }, + "origin": "lzip", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:lzip" + ] + }, + "jpeg-dev": { + "versions": { + "8-r6": 1542893529 + }, + "origin": "jpeg", + "dependencies": [ + "libjpeg-turbo-dev" + ] + }, + "qemu-arm": { + "versions": { + "3.1.0-r3": 1551107299 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-arm" + ] + }, + "py-py": { + "versions": { + "1.5.3-r0": 1542824877 + }, + "origin": "py-py" + }, + "nmap-doc": { + "versions": { + "7.70-r3": 1545163172 + }, + "origin": "nmap" + }, + "libgit2": { + "versions": { + "0.27.7-r0": 1545838466 + }, + "origin": "libgit2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libssh2.so.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "libgit2-libs", + "so:libgit2.so.27=0.27.7" + ] + }, + "squid-lang-hy": { + "versions": { + "4.4-r1": 1545216326 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-extutils-installpaths": { + "versions": { + "0.012-r0": 1542924655 + }, + "origin": "perl-extutils-installpaths", + "dependencies": [ + "perl-extutils-config" + ] + }, + "valgrind": { + "versions": { + "3.14.0-r0": 1545300865 + }, + "origin": "valgrind", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:callgrind_annotate", + "cmd:callgrind_control", + "cmd:cg_annotate", + "cmd:cg_diff", + "cmd:cg_merge", + "cmd:ms_print", + "cmd:valgrind", + "cmd:valgrind-di-server", + "cmd:valgrind-listener", + "cmd:vgdb" + ] + }, + "py3-django": { + "versions": { + "1.11.20-r0": 1551267363 + }, + "origin": "py-django", + "dependencies": [ + "py3-tz", + "python3" + ], + "provides": [ + "cmd:django-admin-3" + ] + }, + "m4": { + "versions": { + "1.4.18-r1": 1542300482 + }, + "origin": "m4", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:m4" + ] + }, + "mdocml-doc": { + "versions": { + "1.14.3-r0": 1542304776 + }, + "origin": "mdocml" + }, + "perl-text-reform": { + "versions": { + "1.20-r0": 1545118017 + }, + "origin": "perl-text-reform", + "dependencies": [ + "perl" + ] + }, + "linux-firmware-av7110": { + "versions": { + "20190322-r0": 1554980654 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "libvirt-libs": { + "versions": { + "4.10.0-r1": 1547051657 + }, + "origin": "libvirt", + "dependencies": [ + "lvm2", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0", + "so:libcurl.so.4", + "so:libdbus-1.so.3", + "so:libdevmapper.so.1.02", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libintl.so.8", + "so:libnl-3.so.200", + "so:libsasl2.so.3", + "so:libssh2.so.1", + "so:libtirpc.so.3", + "so:libxml2.so.2", + "so:libyajl.so.2" + ], + "provides": [ + "so:libvirt-admin.so.0=0.4010.0", + "so:libvirt-lxc.so.0=0.4010.0", + "so:libvirt-qemu.so.0=0.4010.0", + "so:libvirt.so.0=0.4010.0" + ] + }, + "vala": { + "versions": { + "0.42.4-r0": 1545859420 + }, + "origin": "vala", + "dependencies": [ + "glib-dev", + "pc:glib-2.0", + "pc:gmodule-2.0", + "pc:gobject-2.0", + "pc:libgvc", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libcgraph.so.6", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgvc.so.6" + ], + "provides": [ + "so:libvala-0.42.so.0=0.0.0", + "so:libvalaccodegen.so=0", + "so:libvaladoc-0.42.so.0=0.0.0", + "pc:libvala-0.42=0.42.4", + "pc:valadoc-0.42=0.42.4", + "pc:vapigen-0.42=0.42.4", + "pc:vapigen=0.42.4", + "cmd:vala", + "cmd:vala-0.42", + "cmd:vala-gen-introspect", + "cmd:vala-gen-introspect-0.42", + "cmd:valac", + "cmd:valac-0.42", + "cmd:valadoc", + "cmd:valadoc-0.42", + "cmd:vapigen", + "cmd:vapigen-0.42" + ] + }, + "gengetopt": { + "versions": { + "2.22.6-r2": 1545062093 + }, + "origin": "gengetopt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:gengetopt" + ] + }, + "subversion-dev": { + "versions": { + "1.11.1-r0": 1548692374 + }, + "origin": "subversion", + "dependencies": [ + "perl-subversion=1.11.1-r0", + "py-subversion=1.11.1-r0", + "subversion-libs=1.11.1-r0" + ] + }, + "apg-doc": { + "versions": { + "2.2.3-r4": 1545207841 + }, + "origin": "apg" + }, + "libgnome-dev": { + "versions": { + "2.32.1-r7": 1545299764 + }, + "origin": "libgnome", + "dependencies": [ + "gtk+3.0-dev", + "gconf-dev", + "libcanberra-dev", + "gnome-vfs-dev", + "libbonobo-dev", + "libgnome=2.32.1-r7", + "pc:ORBit-2.0", + "pc:gconf-2.0", + "pc:glib-2.0", + "pc:gnome-vfs-2.0", + "pc:libbonobo-2.0", + "pc:libcanberra>=0", + "pkgconfig" + ], + "provides": [ + "pc:libgnome-2.0=2.32.1" + ] + }, + "lua-optarg": { + "versions": { + "0.2-r1": 1542820943 + }, + "origin": "lua-optarg" + }, + "libatasmart-doc": { + "versions": { + "0.19-r1": 1545062469 + }, + "origin": "libatasmart" + }, + "sshpass-doc": { + "versions": { + "1.06-r0": 1542985469 + }, + "origin": "sshpass" + }, + "jack-example-clients": { + "versions": { + "1.9.12-r0": 1545073440 + }, + "origin": "jack", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libjack.so.0", + "so:libjacknet.so.0", + "so:libjackserver.so.0", + "so:libreadline.so.7", + "so:libsamplerate.so.0", + "so:libsndfile.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:alsa_in", + "cmd:alsa_out", + "cmd:jack_alias", + "cmd:jack_bufsize", + "cmd:jack_connect", + "cmd:jack_control", + "cmd:jack_cpu", + "cmd:jack_cpu_load", + "cmd:jack_disconnect", + "cmd:jack_evmon", + "cmd:jack_freewheel", + "cmd:jack_iodelay", + "cmd:jack_latent_client", + "cmd:jack_load", + "cmd:jack_lsp", + "cmd:jack_metro", + "cmd:jack_midi_dump", + "cmd:jack_midi_latency_test", + "cmd:jack_midiseq", + "cmd:jack_midisine", + "cmd:jack_monitor_client", + "cmd:jack_multiple_metro", + "cmd:jack_net_master", + "cmd:jack_net_slave", + "cmd:jack_netsource", + "cmd:jack_rec", + "cmd:jack_samplerate", + "cmd:jack_server_control", + "cmd:jack_session_notify", + "cmd:jack_showtime", + "cmd:jack_simdtests", + "cmd:jack_simple_client", + "cmd:jack_simple_session_client", + "cmd:jack_test", + "cmd:jack_thru", + "cmd:jack_transport", + "cmd:jack_unload", + "cmd:jack_wait", + "cmd:jack_zombie" + ] + }, + "linux-firmware-slicoss": { + "versions": { + "20190322-r0": 1554980648 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "perl-test-fatal-doc": { + "versions": { + "0.014-r1": 1542821867 + }, + "origin": "perl-test-fatal" + }, + "perl-net-async-http": { + "versions": { + "0.42-r0": 1545209620 + }, + "origin": "perl-net-async-http", + "dependencies": [ + "perl-io-async", + "perl-future", + "perl-uri", + "perl-http-message", + "perl-struct-dumb" + ] + }, + "libxt-doc": { + "versions": { + "1.1.5-r2": 1542883334 + }, + "origin": "libxt" + }, + "linux-firmware-rtl_bt": { + "versions": { + "20190322-r0": 1554980648 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "fish": { + "versions": { + "2.7.1-r0": 1545302080 + }, + "origin": "fish", + "dependencies": [ + "bc", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libpcre2-32.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:fish", + "cmd:fish_indent", + "cmd:fish_key_reader" + ] + }, + "libmatroska-dev": { + "versions": { + "1.4.9-r0": 1543928899 + }, + "origin": "libmatroska", + "dependencies": [ + "libmatroska=1.4.9-r0", + "pc:libebml", + "pkgconfig" + ], + "provides": [ + "pc:libmatroska=1.4.9" + ] + }, + "libgphoto2-dev": { + "versions": { + "2.5.16-r0": 1545116801 + }, + "origin": "libgphoto2", + "dependencies": [ + "libexif-dev", + "libusb-dev", + "libgphoto2=2.5.16-r0", + "pc:libexif>=0.6.13", + "pkgconfig" + ], + "provides": [ + "pc:libgphoto2=2.5.16", + "pc:libgphoto2_port=0.12.0", + "cmd:gphoto2-config", + "cmd:gphoto2-port-config" + ] + }, + "alpine-ipxe-ipxe_efi": { + "versions": { + "1.0_git20180825-r1": 1545986078 + }, + "origin": "alpine-ipxe" + }, + "perl-namespace-autoclean": { + "versions": { + "0.28-r0": 1542973137 + }, + "origin": "perl-namespace-autoclean", + "dependencies": [ + "perl-b-hooks-endofscope", + "perl-namespace-clean" + ] + }, + "py2-pillow": { + "versions": { + "5.4.1-r0": 1548112614 + }, + "origin": "py-pillow", + "dependencies": [ + "py2-olefile", + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libopenjp2.so.7", + "so:libpython2.7.so.1.0", + "so:libtiff.so.5", + "so:libwebp.so.7", + "so:libwebpdemux.so.2", + "so:libwebpmux.so.3", + "so:libz.so.1" + ] + }, + "libverto-libev": { + "versions": { + "0.3.0-r1": 1543223525 + }, + "origin": "libverto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libev.so.4", + "so:libverto.so.1" + ], + "provides": [ + "so:libverto-libev.so.1=1.0.0" + ] + }, + "soxr": { + "versions": { + "0.1.3-r0": 1545208988 + }, + "origin": "soxr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgomp.so.1" + ], + "provides": [ + "so:libsoxr-lsr.so.0=0.1.9", + "so:libsoxr.so.0=0.1.2" + ] + }, + "libksba-dev": { + "versions": { + "1.3.5-r0": 1543932189 + }, + "origin": "libksba", + "dependencies": [ + "libksba=1.3.5-r0" + ], + "provides": [ + "cmd:ksba-config" + ] + }, + "jfsutils": { + "versions": { + "1.1.15-r1": 1542883492 + }, + "origin": "jfsutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:fsck.jfs", + "cmd:jfs_debugfs", + "cmd:jfs_fsck", + "cmd:jfs_fscklog", + "cmd:jfs_logdump", + "cmd:jfs_mkfs", + "cmd:jfs_tune", + "cmd:mkfs.jfs" + ] + }, + "bc-doc": { + "versions": { + "1.07.1-r0": 1542823693 + }, + "origin": "bc" + }, + "perl-datetime-format-natural-doc": { + "versions": { + "1.06-r0": 1545163113 + }, + "origin": "perl-datetime-format-natural" + }, + "openbox-libs": { + "versions": { + "3.6.1-r2": 1545207327 + }, + "origin": "openbox", + "dependencies": [ + "so:libImlib2.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangoxft-1.0.so.0", + "so:librsvg-2.so.2", + "so:libxml2.so.2" + ], + "provides": [ + "so:libobrender.so.32=32.0.0", + "so:libobt.so.2=2.0.2" + ] + }, + "mtx": { + "versions": { + "1.3.12-r2": 1545207723 + }, + "origin": "mtx", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:loaderinfo", + "cmd:mtx", + "cmd:scsieject", + "cmd:scsitape", + "cmd:tapeinfo" + ] + }, + "snort": { + "versions": { + "2.9.12-r0": 1545301013 + }, + "origin": "snort", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libdnet.so.1", + "so:libpcap.so.1", + "so:libpcre.so.1", + "so:libsfbpf.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:snort", + "cmd:u2boat", + "cmd:u2spewfoo" + ] + }, + "perl-list-moreutils-doc": { + "versions": { + "0.419-r1": 1542845623 + }, + "origin": "perl-list-moreutils" + }, + "squid-lang-it": { + "versions": { + "4.4-r1": 1545216326 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-digest-sha1-doc": { + "versions": { + "2.13-r9": 1542924825 + }, + "origin": "perl-digest-sha1" + }, + "fortify-headers": { + "versions": { + "1.0-r0": 1542302757 + }, + "origin": "fortify-headers" + }, + "dahdi-tools": { + "versions": { + "2.11.1-r0": 1543932616 + }, + "origin": "dahdi-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnewt.so.0.52" + ], + "provides": [ + "so:libtonezone.so.2=2.0.0", + "cmd:dahdi_cfg", + "cmd:dahdi_genconf", + "cmd:dahdi_hardware", + "cmd:dahdi_maint", + "cmd:dahdi_monitor", + "cmd:dahdi_registration", + "cmd:dahdi_scan", + "cmd:dahdi_span_assignments", + "cmd:dahdi_span_types", + "cmd:dahdi_speed", + "cmd:dahdi_test", + "cmd:dahdi_tool", + "cmd:dahdi_waitfor_span_assignments", + "cmd:fxotune", + "cmd:lsdahdi", + "cmd:sethdlc", + "cmd:twinstar", + "cmd:xpp_blink", + "cmd:xpp_sync" + ] + }, + "lua5.2-lub": { + "versions": { + "1.1.0-r1": 1544000682 + }, + "origin": "lua-lub", + "dependencies": [ + "lua5.2", + "lua5.2-filesystem" + ] + }, + "xfsprogs-doc": { + "versions": { + "4.19.0-r1": 1546597448 + }, + "origin": "xfsprogs" + }, + "mkinitfs-doc": { + "versions": { + "3.4.1-r0": 1551202041 + }, + "origin": "mkinitfs" + }, + "squid-lang-id": { + "versions": { + "4.4-r1": 1545216326 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "udisks2": { + "versions": { + "2.6.5-r1": 1545293060 + }, + "origin": "udisks2", + "dependencies": [ + "so:libacl.so.1", + "so:libatasmart.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgudev-1.0.so.0", + "so:libpolkit-agent-1.so.0", + "so:libpolkit-gobject-1.so.0", + "so:libudisks2.so.0" + ], + "provides": [ + "cmd:udisksctl", + "cmd:umount.udisks2" + ] + }, + "cjdns": { + "versions": { + "20.2-r1": 1546423250 + }, + "origin": "cjdns", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cjdroute" + ] + }, + "xf86-video-s3virge-doc": { + "versions": { + "1.10.7-r4": 1545069464 + }, + "origin": "xf86-video-s3virge" + }, + "squid-lang-fa": { + "versions": { + "4.4-r1": 1545216325 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "bluez-dev": { + "versions": { + "5.50-r0": 1545822205 + }, + "origin": "bluez", + "dependencies": [ + "bluez-libs=5.50-r0", + "pkgconfig" + ], + "provides": [ + "pc:bluez=5.50" + ] + }, + "linux-firmware-amd": { + "versions": { + "20190322-r0": 1554980655 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "xwininfo-doc": { + "versions": { + "1.1.4-r0": 1545249942 + }, + "origin": "xwininfo" + }, + "libnjb-examples": { + "versions": { + "2.2.7-r4": 1545300191 + }, + "origin": "libnjb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libnjb.so.5", + "so:libz.so.1" + ], + "provides": [ + "cmd:njb-cursesplay", + "cmd:njb-delfile", + "cmd:njb-deltr", + "cmd:njb-dumpeax", + "cmd:njb-dumptime", + "cmd:njb-files", + "cmd:njb-fwupgrade", + "cmd:njb-getfile", + "cmd:njb-getowner", + "cmd:njb-gettr", + "cmd:njb-getusage", + "cmd:njb-handshake", + "cmd:njb-pl", + "cmd:njb-play", + "cmd:njb-playlists", + "cmd:njb-sendfile", + "cmd:njb-sendtr", + "cmd:njb-setowner", + "cmd:njb-setpbm", + "cmd:njb-settime", + "cmd:njb-tagtr", + "cmd:njb-tracks" + ] + }, + "libressl": { + "versions": { + "2.7.5-r0": 1551116871 + }, + "origin": "libressl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.43", + "so:libssl.so.45", + "so:libtls.so.17" + ], + "provides": [ + "cmd:libressl", + "cmd:ocspcheck", + "cmd:openssl" + ] + }, + "nss-dev": { + "versions": { + "3.41-r0": 1545307910 + }, + "origin": "nss", + "dependencies": [ + "nss", + "nspr-dev", + "pc:nspr>=3.41", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libnspr4.so", + "so:libnssutil3.so", + "so:libplc4.so", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgtest1.so.41=41", + "so:libnsssysinit.so=41", + "pc:mozilla-nss=3.41", + "pc:nss-softokn=3.41", + "pc:nss-util=3.41", + "pc:nss=3.41", + "cmd:nss-config" + ] + }, + "libcrystalhd-dev": { + "versions": { + "20130708-r2": 1543924960 + }, + "origin": "libcrystalhd", + "dependencies": [ + "libcrystalhd=20130708-r2" + ] + }, + "linux-firmware-qlogic": { + "versions": { + "20190322-r0": 1554980649 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "nsd": { + "versions": { + "4.1.26-r0": 1546948337 + }, + "origin": "nsd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libevent-2.1.so.6", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:nsd", + "cmd:nsd-checkconf", + "cmd:nsd-checkzone", + "cmd:nsd-control", + "cmd:nsd-control-setup" + ] + }, + "rsyslog-mmaudit": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "glade3-doc": { + "versions": { + "3.8.5-r5": 1543998725 + }, + "origin": "glade3" + }, + "zsh-zftp": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh", + "so:libc.musl-x86_64.so.1" + ] + }, + "linux-firmware-ath6k": { + "versions": { + "20190322-r0": 1554980654 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "collectd-sensors": { + "versions": { + "5.8.0-r3": 1546422677 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libsensors.so.4" + ] + }, + "jq-doc": { + "versions": { + "1.6-r0": 1544000549 + }, + "origin": "jq" + }, + "libvirt-static": { + "versions": { + "4.10.0-r1": 1547051657 + }, + "origin": "libvirt", + "dependencies": [ + "lvm2" + ] + }, + "libxxf86dga": { + "versions": { + "1.1.4-r2": 1542845775 + }, + "origin": "libxxf86dga", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXxf86dga.so.1=1.0.0" + ] + }, + "musl-dbg": { + "versions": { + "1.1.20-r4": 1552989415 + }, + "origin": "musl" + }, + "iproute2-bash-completion": { + "versions": { + "4.19.0-r0": 1543223212 + }, + "origin": "iproute2" + }, + "tsocks": { + "versions": { + "1.8_beta5-r0": 1545207039 + }, + "origin": "tsocks", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libtsocks.so.1.8=1.8", + "cmd:tsocks" + ] + }, + "cvs-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "mesa-dri-nouveau": { + "versions": { + "18.1.7-r2": 1554455970 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libX11-xcb.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_intel.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libxcb-dri2.so.0", + "so:libxcb-dri3.so.0", + "so:libxcb-present.so.0", + "so:libxcb-sync.so.1", + "so:libxcb-xfixes.so.0", + "so:libxcb.so.1", + "so:libxshmfence.so.1", + "so:libz.so.1" + ] + }, + "perl-xml-sax": { + "versions": { + "0.99-r2": 1542893171 + }, + "origin": "perl-xml-sax", + "dependencies": [ + "perl-xml-sax-base", + "perl-xml-namespacesupport", + "/bin/sh" + ] + }, + "json-glib-doc": { + "versions": { + "1.4.4-r0": 1545837175 + }, + "origin": "json-glib" + }, + "lua-sql-postgres": { + "versions": { + "2.3.5-r2": 1543924408 + }, + "origin": "lua-sql" + }, + "py-subversion": { + "versions": { + "1.11.1-r0": 1548692375 + }, + "origin": "subversion", + "dependencies": [ + "so:libapr-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libsvn_client-1.so.0", + "so:libsvn_delta-1.so.0", + "so:libsvn_diff-1.so.0", + "so:libsvn_fs-1.so.0", + "so:libsvn_ra-1.so.0", + "so:libsvn_repos-1.so.0", + "so:libsvn_subr-1.so.0", + "so:libsvn_wc-1.so.0" + ], + "provides": [ + "so:libsvn_swig_py-1.so.0=0.0.0" + ] + }, + "py-future": { + "versions": { + "0.17.0-r0": 1545213721 + }, + "origin": "py-future" + }, + "py-django-haystack": { + "versions": { + "2.8.1-r1": 1543925662 + }, + "origin": "py-django-haystack", + "dependencies": [ + "py-django", + "py-setuptools" + ] + }, + "ruby-did_you_mean": { + "versions": { + "2.5.5-r0": 1557164836 + }, + "origin": "ruby", + "dependencies": [ + "ruby-libs" + ] + }, + "perl-net-dns-resolver-programmable": { + "versions": { + "0.009-r1": 1542925012 + }, + "origin": "perl-net-dns-resolver-programmable", + "dependencies": [ + "perl", + "perl-net-dns" + ] + }, + "asterisk-doc": { + "versions": { + "15.7.1-r0": 1546247583 + }, + "origin": "asterisk" + }, + "libsamplerate-doc": { + "versions": { + "0.1.9-r1": 1545068553 + }, + "origin": "libsamplerate" + }, + "py-flake8": { + "versions": { + "3.4.1-r2": 1545223360 + }, + "origin": "py-flake8", + "dependencies": [ + "py-mccabe", + "py-pep8", + "py-pyflakes", + "py3-flake8=3.4.1-r2" + ] + }, + "perl-path-class-doc": { + "versions": { + "0.37-r0": 1543246837 + }, + "origin": "perl-path-class" + }, + "transmission-lang": { + "versions": { + "2.94-r1": 1545208750 + }, + "origin": "transmission" + }, + "libssl1.1": { + "versions": { + "1.1.1b-r1": 1552660176 + }, + "origin": "openssl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ], + "provides": [ + "so:libssl.so.1.1=1.1" + ] + }, + "linux-firmware-nvidia": { + "versions": { + "20190322-r0": 1554980650 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "py2-icu": { + "versions": { + "1.9.8-r2": 1545165266 + }, + "origin": "py-icu", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libicui18n.so.62", + "so:libicuuc.so.62", + "so:libpython2.7.so.1.0", + "so:libstdc++.so.6" + ] + }, + "libxdamage-dev": { + "versions": { + "1.1.4-r2": 1542823760 + }, + "origin": "libxdamage", + "dependencies": [ + "libxdamage=1.1.4-r2", + "pc:damageproto>=1.1", + "pc:x11", + "pc:xfixes", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xdamage=1.1.4" + ] + }, + "libotr-tools": { + "versions": { + "4.1.1-r1": 1543248414 + }, + "origin": "libotr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libotr.so.5" + ], + "provides": [ + "cmd:otr_mackey", + "cmd:otr_modify", + "cmd:otr_parse", + "cmd:otr_readforge", + "cmd:otr_remac", + "cmd:otr_sesskeys" + ] + }, + "strongswan-doc": { + "versions": { + "5.7.2-r1": 1557161392 + }, + "origin": "strongswan" + }, + "lua-inspect-doc": { + "versions": { + "3.1.1-r1": 1545075417 + }, + "origin": "lua-inspect" + }, + "wayland-libs-server": { + "versions": { + "1.16.0-r0": 1544001083 + }, + "origin": "wayland", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6" + ], + "provides": [ + "so:libwayland-server.so.0=0.1.0" + ] + }, + "libqrencode-dev": { + "versions": { + "4.0.2-r0": 1543226702 + }, + "origin": "libqrencode", + "dependencies": [ + "libqrencode=4.0.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libqrencode=4.0.2" + ] + }, + "open-isns-lib": { + "versions": { + "0.97-r4": 1542883211 + }, + "origin": "open-isns", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ], + "provides": [ + "so:libisns.so.0=0" + ] + }, + "cdparanoia": { + "versions": { + "10.2-r7": 1543254200 + }, + "origin": "cdparanoia", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcdda_interface.so.0", + "so:libcdda_paranoia.so.0" + ], + "provides": [ + "cmd:cdparanoia" + ] + }, + "nagios-plugins-dbi": { + "versions": { + "2.2.1-r6": 1543933903 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libdbi.so.1" + ] + }, + "lzip-doc": { + "versions": { + "1.20-r0": 1542304994 + }, + "origin": "lzip" + }, + "samba-libs-py": { + "versions": { + "4.8.11-r1": 1555334976 + }, + "origin": "samba", + "dependencies": [ + "so:libMESSAGING-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-ldap-samba4.so", + "so:libcliauth-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libdcerpc.so.0", + "so:libevents-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:libndr-samba-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libpytalloc-util.so.2", + "so:libpython2.7.so.1.0", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libserver-role-samba4.so", + "so:libsmbpasswdparser-samba4.so", + "so:libtalloc.so.2", + "so:libtevent-util.so.0", + "so:libtevent.so.0" + ], + "provides": [ + "so:libsamba-net-samba4.so=0", + "so:libsamba-python-samba4.so=0" + ] + }, + "perl-role-basic-doc": { + "versions": { + "0.13-r0": 1543925825 + }, + "origin": "perl-role-basic" + }, + "at-spi2-atk-dev": { + "versions": { + "2.26.2-r0": 1543926534 + }, + "origin": "at-spi2-atk", + "dependencies": [ + "dbus-dev", + "atk-dev", + "at-spi2-atk=2.26.2-r0", + "pc:atspi-2", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:atk-bridge-2.0=2.26.2" + ] + }, + "nagios-plugins-cluster": { + "versions": { + "2.2.1-r6": 1543933902 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "inotify-tools-dev": { + "versions": { + "3.20.1-r1": 1545302266 + }, + "origin": "inotify-tools", + "dependencies": [ + "inotify-tools=3.20.1-r1" + ] + }, + "linux-firmware-brcm": { + "versions": { + "20190322-r0": 1554980653 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "perl-cpanel-json-xs-doc": { + "versions": { + "4.08-r0": 1544792332 + }, + "origin": "perl-cpanel-json-xs" + }, + "linux-firmware-cxgb3": { + "versions": { + "20190322-r0": 1554980653 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "lua5.2-libs": { + "versions": { + "5.2.4-r7": 1542304843 + }, + "origin": "lua5.2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblua-5.2.so.0=0.0.0" + ] + }, + "xf86-video-amdgpu-doc": { + "versions": { + "18.0.1-r2": 1545215978 + }, + "origin": "xf86-video-amdgpu" + }, + "screen-doc": { + "versions": { + "4.6.2-r0": 1545290724 + }, + "origin": "screen" + }, + "xmlrpc-c++": { + "versions": { + "1.39.13-r0": 1545665217 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libxmlrpc.so.3", + "so:libxmlrpc_abyss.so.3", + "so:libxmlrpc_server.so.3", + "so:libxmlrpc_server_abyss.so.3", + "so:libxmlrpc_util.so.3" + ], + "provides": [ + "so:libxmlrpc++.so.8=8.39", + "so:libxmlrpc_abyss++.so.8=8.39", + "so:libxmlrpc_cpp.so.8=8.39", + "so:libxmlrpc_packetsocket.so.8=8.39", + "so:libxmlrpc_server++.so.8=8.39", + "so:libxmlrpc_server_abyss++.so.8=8.39", + "so:libxmlrpc_server_cgi++.so.8=8.39", + "so:libxmlrpc_server_pstream++.so.8=8.39", + "so:libxmlrpc_util++.so.8=8.39" + ] + }, + "xf86-video-dummy": { + "versions": { + "0.3.8-r3": 1545300623 + }, + "origin": "xf86-video-dummy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "rsyslog-mmutf8fix": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "newt-doc": { + "versions": { + "0.52.20-r0": 1543924700 + }, + "origin": "newt" + }, + "motif-doc": { + "versions": { + "2.3.4-r2": 1545223206 + }, + "origin": "motif" + }, + "python2-doc": { + "versions": { + "2.7.16-r1": 1557171398 + }, + "origin": "python2" + }, + "libjpeg-turbo-doc": { + "versions": { + "1.5.3-r4": 1547027583 + }, + "origin": "libjpeg-turbo" + }, + "mesa-dri-vmwgfx": { + "versions": { + "18.1.7-r2": 1554455972 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "fprobe-ulog-doc": { + "versions": { + "1.2-r4": 1543924459 + }, + "origin": "fprobe-ulog" + }, + "libcanberra-doc": { + "versions": { + "0.30-r2": 1545118058 + }, + "origin": "libcanberra" + }, + "quagga-doc": { + "versions": { + "1.2.4-r1": 1545068940 + }, + "origin": "quagga" + }, + "popt-doc": { + "versions": { + "1.16-r7": 1542820790 + }, + "origin": "popt" + }, + "perl-cgi-session-doc": { + "versions": { + "4.48-r0": 1543999426 + }, + "origin": "perl-cgi-session" + }, + "perl-date-manip": { + "versions": { + "6.75-r1": 1546098110 + }, + "origin": "perl-date-manip", + "dependencies": [ + "perl-yaml-syck" + ], + "provides": [ + "cmd:dm_date", + "cmd:dm_zdump" + ] + }, + "libgss": { + "versions": { + "0.1.5-r1": 1543998826 + }, + "origin": "libgss", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgss.so.1=1.1.0", + "cmd:gss" + ] + }, + "perl-test-pod": { + "versions": { + "1.52-r0": 1542822494 + }, + "origin": "perl-test-pod" + }, + "libspf2-tools": { + "versions": { + "1.2.10-r3": 1546267711 + }, + "origin": "libspf2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libspf2.so.2" + ], + "provides": [ + "cmd:spfd", + "cmd:spfquery" + ] + }, + "perl-fcgi": { + "versions": { + "0.78-r2": 1543242209 + }, + "origin": "perl-fcgi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "openldap-back-shell": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "libressl-doc": { + "versions": { + "2.7.5-r0": 1551116864 + }, + "origin": "libressl" + }, + "glib-lang": { + "versions": { + "2.58.1-r2": 1545290825 + }, + "origin": "glib" + }, + "cunit": { + "versions": { + "2.1.3-r1": 1542900198 + }, + "origin": "cunit", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcunit.so.1=1.0.1" + ] + }, + "freetds": { + "versions": { + "1.00.104-r0": 1543934551 + }, + "origin": "freetds", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libodbc.so.2", + "so:libodbcinst.so.2", + "so:libreadline.so.7", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libct.so.4=4.0.0", + "so:libsybdb.so.5=5.1.0", + "so:libtdsodbc.so.0=0.0.0", + "cmd:bsqldb", + "cmd:bsqlodbc", + "cmd:datacopy", + "cmd:defncopy", + "cmd:fisql", + "cmd:freebcp", + "cmd:osql", + "cmd:tdspool", + "cmd:tsql" + ] + }, + "cryptsetup-openrc": { + "versions": { + "2.0.6-r0": 1545746292 + }, + "origin": "cryptsetup" + }, + "xf86-video-vesa-doc": { + "versions": { + "2.3.4-r5": 1545116526 + }, + "origin": "xf86-video-vesa" + }, + "aria2-bash-completion": { + "versions": { + "1.34.0-r1": 1543998971 + }, + "origin": "aria2", + "dependencies": [ + "ca-certificates" + ] + }, + "py-templayer": { + "versions": { + "1.5.1-r3": 1543935169 + }, + "origin": "py-templayer", + "dependencies": [ + "python2" + ] + }, + "perl-file-tail-doc": { + "versions": { + "1.3-r1": 1545116501 + }, + "origin": "perl-file-tail" + }, + "sylpheed-dev": { + "versions": { + "3.7.0-r2": 1544799189 + }, + "origin": "sylpheed", + "dependencies": [ + "sylpheed=3.7.0-r2" + ] + }, + "perl-protocol-websocket-doc": { + "versions": { + "0.20-r0": 1545164019 + }, + "origin": "perl-protocol-websocket" + }, + "razor": { + "versions": { + "2.85-r7": 1545292686 + }, + "origin": "razor", + "dependencies": [ + "perl", + "perl-digest-sha1", + "perl-getopt-long", + "perl-uri", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:razor-admin", + "cmd:razor-check", + "cmd:razor-client", + "cmd:razor-report", + "cmd:razor-revoke" + ] + }, + "lua5.3-sql-mysql": { + "versions": { + "2.3.5-r2": 1543924403 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "acf-apk-tools": { + "versions": { + "0.11.0-r1": 1545249838 + }, + "origin": "acf-apk-tools", + "dependencies": [ + "acf-core", + "lua-posix", + "apk-tools" + ] + }, + "sdl": { + "versions": { + "1.2.15-r9": 1545292836 + }, + "origin": "sdl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL-1.2.so.0=0.11.4" + ] + }, + "htop-doc": { + "versions": { + "2.2.0-r0": 1545207287 + }, + "origin": "htop" + }, + "perl-io-tty-doc": { + "versions": { + "1.12-r4": 1542972211 + }, + "origin": "perl-io-tty" + }, + "swig": { + "versions": { + "3.0.12-r4": 1543228065 + }, + "origin": "swig", + "dependencies": [ + "guile", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpcre.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:ccache-swig", + "cmd:swig" + ] + }, + "s6-portable-utils-doc": { + "versions": { + "2.2.1.2-r0": 1545069441 + }, + "origin": "s6-portable-utils" + }, + "collectd-snmp": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.35" + ] + }, + "libbonobo-doc": { + "versions": { + "2.32.1-r6": 1545299730 + }, + "origin": "libbonobo" + }, + "asciidoc": { + "versions": { + "8.6.10-r0": 1542883778 + }, + "origin": "asciidoc", + "dependencies": [ + "python2", + "libxml2-utils", + "docbook-xsl" + ], + "provides": [ + "cmd:a2x", + "cmd:a2x.py", + "cmd:asciidoc", + "cmd:asciidoc.py" + ] + }, + "lm_sensors-dev": { + "versions": { + "3.4.0-r6": 1542924817 + }, + "origin": "lm_sensors", + "dependencies": [ + "lm_sensors=3.4.0-r6" + ] + }, + "perl-yaml-libyaml-doc": { + "versions": { + "0.72-r0": 1545061179 + }, + "origin": "perl-yaml-libyaml" + }, + "perl-mail-domainkeys-doc": { + "versions": { + "1.0-r2": 1545060514 + }, + "origin": "perl-mail-domainkeys" + }, + "lucene++": { + "versions": { + "3.0.7-r8": 1545256828 + }, + "origin": "lucene++", + "dependencies": [ + "so:libboost_filesystem-mt.so.1.67.0", + "so:libboost_iostreams-mt.so.1.67.0", + "so:libboost_regex-mt.so.1.67.0", + "so:libboost_system-mt.so.1.67.0", + "so:libboost_thread-mt.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:liblucene++-contrib.so.0=3.0.7", + "so:liblucene++.so.0=3.0.7" + ] + }, + "boost-iostreams": { + "versions": { + "1.67.0-r2": 1542823632 + }, + "origin": "boost", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libboost_iostreams-mt.so.1.67.0=1.67.0", + "so:libboost_iostreams.so.1.67.0=1.67.0" + ] + }, + "libtheora-examples": { + "versions": { + "1.1.1-r13": 1543928531 + }, + "origin": "libtheora", + "dependencies": [ + "so:libSDL-1.2.so.0", + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libtheoradec.so.1", + "so:libtheoraenc.so.1", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2" + ], + "provides": [ + "cmd:dump_psnr", + "cmd:dump_video", + "cmd:encoder_example", + "cmd:player_example" + ] + }, + "lua-maxminddb": { + "versions": { + "0.1-r1": 1543226683 + }, + "origin": "lua-maxminddb", + "dependencies": [ + "lua5.1-maxminddb", + "lua5.2-maxminddb", + "lua5.3-maxminddb" + ] + }, + "libseccomp-dev": { + "versions": { + "2.3.3-r1": 1543923552 + }, + "origin": "libseccomp", + "dependencies": [ + "linux-headers", + "libseccomp=2.3.3-r1", + "pkgconfig" + ], + "provides": [ + "pc:libseccomp=2.3.3" + ] + }, + "lvm2": { + "versions": { + "2.02.182-r0": 1543928934 + }, + "origin": "lvm2", + "dependencies": [ + "lvm2-libs=2.02.182-r0", + "so:libaio.so.1", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper-event.so.1.02", + "so:libdevmapper.so.1.02" + ], + "provides": [ + "cmd:lvchange", + "cmd:lvconvert", + "cmd:lvcreate", + "cmd:lvdisplay", + "cmd:lvextend", + "cmd:lvm", + "cmd:lvmconfig", + "cmd:lvmdiskscan", + "cmd:lvmsadc", + "cmd:lvmsar", + "cmd:lvreduce", + "cmd:lvremove", + "cmd:lvrename", + "cmd:lvresize", + "cmd:lvs", + "cmd:lvscan", + "cmd:pvchange", + "cmd:pvck", + "cmd:pvcreate", + "cmd:pvdisplay", + "cmd:pvmove", + "cmd:pvremove", + "cmd:pvresize", + "cmd:pvs", + "cmd:pvscan", + "cmd:vgcfgbackup", + "cmd:vgcfgrestore", + "cmd:vgchange", + "cmd:vgck", + "cmd:vgconvert", + "cmd:vgcreate", + "cmd:vgdisplay", + "cmd:vgexport", + "cmd:vgextend", + "cmd:vgimport", + "cmd:vgimportclone", + "cmd:vgmerge", + "cmd:vgmknodes", + "cmd:vgreduce", + "cmd:vgremove", + "cmd:vgrename", + "cmd:vgs", + "cmd:vgscan", + "cmd:vgsplit" + ] + }, + "perl-lwp-useragent-determined": { + "versions": { + "1.07-r0": 1545062801 + }, + "origin": "perl-lwp-useragent-determined", + "dependencies": [ + "perl", + "perl-libwww" + ] + }, + "lua-cjson": { + "versions": { + "2.1.0-r8": 1542820885 + }, + "origin": "lua-cjson" + }, + "perl-test-inter": { + "versions": { + "1.07-r0": 1543077266 + }, + "origin": "perl-test-inter" + }, + "libgss-dev": { + "versions": { + "0.1.5-r1": 1543998824 + }, + "origin": "libgss", + "dependencies": [ + "pkgconfig", + "libgss=0.1.5-r1" + ], + "provides": [ + "pc:gss=0.1.5" + ] + }, + "sessreg": { + "versions": { + "1.1.1-r1": 1542985395 + }, + "origin": "sessreg", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sessreg" + ] + }, + "perl-test-simple-doc": { + "versions": { + "1.302141-r0": 1544792380 + }, + "origin": "perl-test-simple" + }, + "unfs3-doc": { + "versions": { + "0.9.22-r4": 1545222983 + }, + "origin": "unfs3" + }, + "perl-module-metadata-doc": { + "versions": { + "1.000033-r0": 1542845543 + }, + "origin": "perl-module-metadata" + }, + "hvtools": { + "versions": { + "4.11.9-r0": 1543249741 + }, + "origin": "hvtools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:hv_fcopy_daemon", + "cmd:hv_kvp_daemon", + "cmd:hv_vss_daemon" + ] + }, + "elfutils-libelf": { + "versions": { + "0.168-r2": 1546849158 + }, + "origin": "elfutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libelf.so.1=0" + ] + }, + "boost-graph": { + "versions": { + "1.67.0-r2": 1542823632 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_regex-mt.so.1.67.0", + "so:libboost_regex.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_graph-mt.so.1.67.0=1.67.0", + "so:libboost_graph.so.1.67.0=1.67.0" + ] + }, + "frotz": { + "versions": { + "2.44-r0": 1545293068 + }, + "origin": "frotz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:frotz" + ] + }, + "libpcre16": { + "versions": { + "8.42-r1": 1545067005 + }, + "origin": "pcre", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre16.so.0=0.2.10" + ] + }, + "lua-pc": { + "versions": { + "1.0.0-r9": 1543928870 + }, + "origin": "lua-pc", + "dependencies": [ + "lua5.1-pc" + ] + }, + "gsl": { + "versions": { + "2.5-r0": 1545117968 + }, + "origin": "gsl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgsl.so.23=23.1.0", + "so:libgslcblas.so.0=0.0.0", + "cmd:gsl-histogram", + "cmd:gsl-randist" + ] + }, + "f2fs-tools-libs": { + "versions": { + "1.12.0-r0": 1546344802 + }, + "origin": "f2fs-tools", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libf2fs.so.6=6.0.0", + "so:libf2fs_format.so.5=5.0.0" + ] + }, + "collectd-dev": { + "versions": { + "5.8.0-r3": 1546422675 + }, + "origin": "collectd", + "dependencies": [ + "collectd-libs=5.8.0-r3", + "pkgconfig" + ], + "provides": [ + "pc:libcollectdclient=5.8.0" + ] + }, + "dmvpn-ca": { + "versions": { + "1.0.2-r0": 1553425621 + }, + "origin": "dmvpn", + "dependencies": [ + "lua5.2", + "lua5.2-lyaml", + "lua5.2-ossl", + "lua5.2-posix", + "lua5.2-sql-sqlite3", + "lua5.2-stringy", + "lua-asn1", + "lua-dmvpn" + ], + "provides": [ + "cmd:dmvpn-ca" + ] + }, + "perl-regexp-ipv6-doc": { + "versions": { + "0.03-r0": 1545289352 + }, + "origin": "perl-regexp-ipv6" + }, + "c-client": { + "versions": { + "2007f-r9": 1545062297 + }, + "origin": "imap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libc-client.so.1=1.0.0" + ] + }, + "neon-dev": { + "versions": { + "0.30.2-r5": 1542883669 + }, + "origin": "neon", + "dependencies": [ + "expat-dev", + "openssl-dev", + "zlib-dev", + "neon=0.30.2-r5", + "pkgconfig" + ], + "provides": [ + "pc:neon=0.30.2", + "cmd:neon-config" + ] + }, + "mcabber-dev": { + "versions": { + "1.1.0-r2": 1545214061 + }, + "origin": "mcabber", + "dependencies": [ + "pc:glib-2.0", + "pc:gmodule-2.0", + "pc:loudmouth-1.0", + "pkgconfig" + ], + "provides": [ + "pc:mcabber=1.1.0" + ] + }, + "perl-sub-identify-doc": { + "versions": { + "0.14-r1": 1542973112 + }, + "origin": "perl-sub-identify" + }, + "expat-dev": { + "versions": { + "2.2.6-r0": 1542302776 + }, + "origin": "expat", + "dependencies": [ + "expat=2.2.6-r0", + "pkgconfig" + ], + "provides": [ + "pc:expat=2.2.6" + ] + }, + "pound": { + "versions": { + "2.8-r0": 1545207004 + }, + "origin": "pound", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpcreposix.so.0", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:pound", + "cmd:poundctl" + ] + }, + "libssh2": { + "versions": { + "1.8.2-r0": 1553681860 + }, + "origin": "libssh2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:libssh2.so.1=1.0.1" + ] + }, + "speexdsp-doc": { + "versions": { + "1.2_rc3-r5": 1544799235 + }, + "origin": "speexdsp" + }, + "fish-dev": { + "versions": { + "2.7.1-r0": 1545302078 + }, + "origin": "fish", + "dependencies": [ + "fish-tools" + ] + }, + "gcr-lang": { + "versions": { + "3.28.0-r0": 1545300012 + }, + "origin": "gcr" + }, + "libsecret-dev": { + "versions": { + "0.18.6-r0": 1545291102 + }, + "origin": "libsecret", + "dependencies": [ + "glib-dev", + "libsecret=0.18.6-r0", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libsecret-1=0.18.6", + "pc:libsecret-unstable=0.18.6" + ] + }, + "perl-text-csv-doc": { + "versions": { + "1.97-r0": 1545163609 + }, + "origin": "perl-text-csv" + }, + "perl-http-negotiate-doc": { + "versions": { + "6.01-r1": 1542821862 + }, + "origin": "perl-http-negotiate" + }, + "perl-devel-checklib-doc": { + "versions": { + "1.13-r0": 1543922455 + }, + "origin": "perl-devel-checklib" + }, + "xf86-video-sunleo": { + "versions": { + "1.2.2-r3": 1545293002 + }, + "origin": "xf86-video-sunleo", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "xcb-util-wm": { + "versions": { + "0.4.1-r1": 1543927935 + }, + "origin": "xcb-util-wm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-ewmh.so.2=2.0.0", + "so:libxcb-icccm.so.4=4.0.0" + ] + }, + "libsigc++": { + "versions": { + "2.10.0-r1": 1543077171 + }, + "origin": "libsigc++", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libsigc-2.0.so.0=0.0.0" + ] + }, + "boost-wserialization": { + "versions": { + "1.67.0-r2": 1542823634 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_serialization-mt.so.1.67.0", + "so:libboost_serialization.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_wserialization-mt.so.1.67.0=1.67.0", + "so:libboost_wserialization.so.1.67.0=1.67.0" + ] + }, + "py-genshi": { + "versions": { + "0.7-r0": 1543924847 + }, + "origin": "py-genshi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "celt051": { + "versions": { + "0.5.1.3-r0": 1543935762 + }, + "origin": "celt051", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0" + ], + "provides": [ + "so:libcelt051.so.0=0.0.0", + "cmd:celtdec051", + "cmd:celtenc051" + ] + }, + "libxres-doc": { + "versions": { + "1.2.0-r1": 1545302056 + }, + "origin": "libxres" + }, + "flex-libs": { + "versions": { + "2.6.4-r1": 1542300944 + }, + "origin": "flex", + "dependencies": [ + "m4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfl.so.2=2.0.0" + ] + }, + "perl-data-page-doc": { + "versions": { + "2.02-r1": 1544000368 + }, + "origin": "perl-data-page" + }, + "libmtp": { + "versions": { + "1.1.15-r0": 1543924442 + }, + "origin": "libmtp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libmtp.so.9=9.4.0" + ] + }, + "libblkid": { + "versions": { + "2.33-r0": 1545307436 + }, + "origin": "util-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libblkid.so.1=1.1.0" + ] + }, + "alsa-utils": { + "versions": { + "1.1.8-r0": 1547112822 + }, + "origin": "alsa-utils", + "dependencies": [ + "dialog", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libfftw3f.so.3", + "so:libformw.so.6", + "so:libmenuw.so.6", + "so:libncursesw.so.6", + "so:libpanelw.so.6" + ], + "provides": [ + "cmd:aconnect", + "cmd:alsa-info.sh", + "cmd:alsabat", + "cmd:alsabat-test.sh", + "cmd:alsactl", + "cmd:alsaloop", + "cmd:alsamixer", + "cmd:alsatplg", + "cmd:alsaucm", + "cmd:amidi", + "cmd:amixer", + "cmd:aplay", + "cmd:aplaymidi", + "cmd:arecord", + "cmd:arecordmidi", + "cmd:aseqdump", + "cmd:aseqnet", + "cmd:axfer", + "cmd:iecset", + "cmd:speaker-test" + ] + }, + "lua5.2-curl": { + "versions": { + "0.3.8-r1": 1545224117 + }, + "origin": "lua-curl", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "py2-httplib2": { + "versions": { + "0.12.0-r0": 1548112518 + }, + "origin": "py-httplib2", + "dependencies": [ + "python2" + ] + }, + "nginx-vim": { + "versions": { + "1.14.2-r1": 1557179819 + }, + "origin": "nginx" + }, + "mesa-vulkan-intel": { + "versions": { + "18.1.7-r2": 1554455972 + }, + "origin": "mesa", + "dependencies": [ + "so:libX11-xcb.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libwayland-client.so.0", + "so:libxcb-dri3.so.0", + "so:libxcb-present.so.0", + "so:libxcb-sync.so.1", + "so:libxcb.so.1", + "so:libxshmfence.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libvulkan_intel.so=0" + ] + }, + "nginx-mod-http-nchan": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-alabaster": { + "versions": { + "0.7.12-r0": 1542824966 + }, + "origin": "py-alabaster", + "dependencies": [ + "python2" + ] + }, + "nano": { + "versions": { + "3.2-r0": 1545292880 + }, + "origin": "nano", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmagic.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:nano", + "cmd:rnano" + ] + }, + "libcanberra": { + "versions": { + "0.30-r2": 1545118059 + }, + "origin": "libcanberra", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libltdl.so.7", + "so:libvorbisfile.so.3" + ], + "provides": [ + "so:libcanberra.so.0=0.2.5" + ] + }, + "libxft-doc": { + "versions": { + "2.3.2-r3": 1542824036 + }, + "origin": "libxft" + }, + "lua5.1-microlight": { + "versions": { + "1.1.1-r2": 1543249965 + }, + "origin": "lua-microlight" + }, + "libpciaccess": { + "versions": { + "0.14-r0": 1545838880 + }, + "origin": "libpciaccess", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpciaccess.so.0=0.11.1" + ] + }, + "libbonobo-dev": { + "versions": { + "2.32.1-r6": 1545299729 + }, + "origin": "libbonobo", + "dependencies": [ + "gtk+2.0-dev", + "libidl-dev", + "orbit2-dev", + "popt-dev", + "libxml2-dev", + "libbonobo=2.32.1-r6", + "pc:ORBit-2.0", + "pc:glib-2.0", + "pc:gmodule-no-export-2.0", + "pkgconfig" + ], + "provides": [ + "pc:bonobo-activation-2.0=2.32.1", + "pc:libbonobo-2.0=2.32.1" + ] + }, + "perl-class-inspector-doc": { + "versions": { + "1.32-r0": 1543238829 + }, + "origin": "perl-class-inspector" + }, + "lz4-tests": { + "versions": { + "1.8.3-r2": 1545154434 + }, + "origin": "lz4", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libpng-static": { + "versions": { + "1.6.37-r0": 1557129094 + }, + "origin": "libpng" + }, + "py-uritemplate": { + "versions": { + "3.0.0-r1": 1545207296 + }, + "origin": "py-uritemplate" + }, + "xdm": { + "versions": { + "1.1.11-r5": 1542985410 + }, + "origin": "xdm", + "dependencies": [ + "sessreg", + "so:libX11.so.6", + "so:libXau.so.6", + "so:libXaw.so.7", + "so:libXdmcp.so.6", + "so:libXext.so.6", + "so:libXmu.so.6", + "so:libXpm.so.4", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0" + ], + "provides": [ + "cmd:xdm" + ] + }, + "perl-server-starter-doc": { + "versions": { + "0.15-r1": 1543249940 + }, + "origin": "perl-server-starter" + }, + "lua5.2-subprocess": { + "versions": { + "0.0.20141229-r2": 1542883782 + }, + "origin": "lua-subprocess", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "ntfs-3g": { + "versions": { + "2017.3.23-r1": 1542973176 + }, + "origin": "ntfs-3g", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libntfs-3g.so.88" + ], + "provides": [ + "cmd:lowntfs-3g", + "cmd:ntfs-3g" + ] + }, + "openvswitch-bash-completion": { + "versions": { + "2.10.1-r0": 1544000344 + }, + "origin": "openvswitch" + }, + "cpulimit": { + "versions": { + "0.2-r0": 1545062299 + }, + "origin": "cpulimit", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cpulimit" + ] + }, + "libxcursor": { + "versions": { + "1.1.15-r1": 1543925380 + }, + "origin": "libxcursor", + "dependencies": [ + "so:libX11.so.6", + "so:libXfixes.so.3", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXcursor.so.1=1.0.2" + ] + }, + "nagios-plugins-ldap": { + "versions": { + "2.2.1-r6": 1543933907 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libldap-2.4.so.2" + ] + }, + "tk-doc": { + "versions": { + "8.6.9-r0": 1545915088 + }, + "origin": "tk" + }, + "py2-bcrypt": { + "versions": { + "3.1.4-r0": 1545216388 + }, + "origin": "py-bcrypt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "eudev": { + "versions": { + "3.2.7-r0": 1542845385 + }, + "origin": "eudev", + "dependencies": [ + "udev-init-scripts", + "eudev-libs=3.2.7-r0", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libkmod.so.2" + ], + "provides": [ + "udev=176", + "cmd:setup-udev", + "cmd:udevadm", + "cmd:udevd" + ] + }, + "xcb-util-wm-dev": { + "versions": { + "0.4.1-r1": 1543927933 + }, + "origin": "xcb-util-wm", + "dependencies": [ + "xcb-util-dev", + "pkgconfig", + "xcb-util-wm=0.4.1-r1" + ], + "provides": [ + "pc:xcb-ewmh=0.4.1", + "pc:xcb-icccm=0.4.1" + ] + }, + "xf86-video-ark-doc": { + "versions": { + "0.7.5-r10": 1543928085 + }, + "origin": "xf86-video-ark" + }, + "libisoburn": { + "versions": { + "1.4.8-r0": 1544797306 + }, + "origin": "libisoburn", + "dependencies": [ + "so:libburn.so.4", + "so:libc.musl-x86_64.so.1", + "so:libedit.so.0", + "so:libisofs.so.6" + ], + "provides": [ + "so:libisoburn.so.1=1.105.0" + ] + }, + "freeswitch": { + "versions": { + "1.8.2-r1": 1545117893 + }, + "origin": "freeswitch", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libedit.so.0", + "so:libgcc_s.so.1", + "so:libilbc.so.0", + "so:libjpeg.so.8", + "so:libldns.so.2", + "so:liblua-5.3.so.0", + "so:libmp3lame.so.0", + "so:libmpg123.so.0", + "so:libncursesw.so.6", + "so:libodbc.so.2", + "so:libopus.so.0", + "so:libpcre.so.1", + "so:libportaudio.so.2", + "so:libpq.so.5", + "so:libshout.so.3", + "so:libsndfile.so.1", + "so:libspeex.so.1", + "so:libspeexdsp.so.1", + "so:libsqlite3.so.0", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libtiff.so.5", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libfreeswitch.so.1=1.0.0", + "cmd:freeswitch", + "cmd:fs_cli", + "cmd:fs_encode", + "cmd:fs_ivrd", + "cmd:fsxs", + "cmd:gentls_cert", + "cmd:tone2wav" + ] + }, + "pcsc-lite-doc": { + "versions": { + "1.8.24-r1": 1545060761 + }, + "origin": "pcsc-lite" + }, + "nagios-plugins-ups": { + "versions": { + "2.2.1-r6": 1543933913 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-ipaddress": { + "versions": { + "1.0.22-r0": 1543926515 + }, + "origin": "py-ipaddress" + }, + "re2c": { + "versions": { + "1.1.1-r0": 1542822316 + }, + "origin": "re2c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:re2c" + ] + }, + "sendpage-doc": { + "versions": { + "1.0.3-r5": 1545116828 + }, + "origin": "sendpage" + }, + "xorg-server": { + "versions": { + "1.20.3-r1": 1543928069 + }, + "origin": "xorg-server", + "dependencies": [ + "font-misc-misc", + "font-cursor-misc", + "xkeyboard-config", + "xkbcomp", + "xinit", + "so:libGL.so.1", + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libdrm.so.2", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libpciaccess.so.0", + "so:libpixman-1.so.0", + "so:libudev.so.1", + "so:libxshmfence.so.1" + ], + "provides": [ + "cmd:X", + "cmd:Xorg", + "cmd:cvt", + "cmd:gtf" + ] + }, + "orbit2-dev": { + "versions": { + "2.14.19-r4": 1543223377 + }, + "origin": "orbit2", + "dependencies": [ + "orbit2=2.14.19-r4", + "pc:glib-2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pc:libIDL-2.0", + "pkgconfig" + ], + "provides": [ + "pc:ORBit-2.0=2.14.19", + "pc:ORBit-CosNaming-2.0=2.14.19", + "pc:ORBit-idl-2.0=2.14.19", + "pc:ORBit-imodule-2.0=2.14.19", + "cmd:orbit2-config" + ] + }, + "glibmm": { + "versions": { + "2.56.0-r1": 1544799352 + }, + "origin": "glibmm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgiomm-2.4.so.1=1.3.0", + "so:libglibmm-2.4.so.1=1.3.0", + "so:libglibmm_generate_extra_defs-2.4.so.1=1.3.0" + ] + }, + "py3-pylast": { + "versions": { + "2.4.0-r0": 1545301100 + }, + "origin": "py-pylast", + "dependencies": [ + "python3" + ] + }, + "rsyslog-snmp": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.35" + ], + "provides": [ + "rsyslog-omsnmp=8.40.0-r3" + ] + }, + "xf86-input-keyboard": { + "versions": { + "1.9.0-r1": 1545073858 + }, + "origin": "xf86-input-keyboard", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "minicom": { + "versions": { + "2.7.1-r0": 1544799266 + }, + "origin": "minicom", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:ascii-xfr", + "cmd:minicom", + "cmd:runscript", + "cmd:xminicom" + ] + }, + "mesa-osmesa": { + "versions": { + "18.1.7-r2": 1554455971 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libOSMesa.so.8=8.0.0" + ] + }, + "libxcb": { + "versions": { + "1.13-r2": 1542822659 + }, + "origin": "libxcb", + "dependencies": [ + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libxcb-composite.so.0=0.0.0", + "so:libxcb-damage.so.0=0.0.0", + "so:libxcb-dpms.so.0=0.0.0", + "so:libxcb-dri2.so.0=0.0.0", + "so:libxcb-dri3.so.0=0.0.0", + "so:libxcb-glx.so.0=0.0.0", + "so:libxcb-present.so.0=0.0.0", + "so:libxcb-randr.so.0=0.1.0", + "so:libxcb-record.so.0=0.0.0", + "so:libxcb-render.so.0=0.0.0", + "so:libxcb-res.so.0=0.0.0", + "so:libxcb-screensaver.so.0=0.0.0", + "so:libxcb-shape.so.0=0.0.0", + "so:libxcb-shm.so.0=0.0.0", + "so:libxcb-sync.so.1=1.0.0", + "so:libxcb-xf86dri.so.0=0.0.0", + "so:libxcb-xfixes.so.0=0.0.0", + "so:libxcb-xinerama.so.0=0.0.0", + "so:libxcb-xinput.so.0=0.1.0", + "so:libxcb-xkb.so.1=1.0.0", + "so:libxcb-xtest.so.0=0.0.0", + "so:libxcb-xv.so.0=0.0.0", + "so:libxcb-xvmc.so.0=0.0.0", + "so:libxcb.so.1=1.1.0" + ] + }, + "jwm-doc": { + "versions": { + "2.3.7-r0": 1545292614 + }, + "origin": "jwm" + }, + "opensp-lang": { + "versions": { + "1.5.2-r0": 1542893110 + }, + "origin": "opensp" + }, + "py-larch": { + "versions": { + "1.20131130-r1": 1545209178 + }, + "origin": "py-larch", + "dependencies": [ + "python2", + "py-tracing", + "py-cliapp", + "py-ttystatus" + ], + "provides": [ + "cmd:fsck-larch" + ] + }, + "py-click": { + "versions": { + "6.7-r2": 1545116992 + }, + "origin": "py-click" + }, + "luajit-dev": { + "versions": { + "2.1.0_beta3-r4": 1542820926 + }, + "origin": "luajit", + "dependencies": [ + "luajit=2.1.0_beta3-r4", + "pkgconfig" + ] + }, + "libshout-doc": { + "versions": { + "2.4.1-r5": 1545117135 + }, + "origin": "libshout" + }, + "lua-lyaml": { + "versions": { + "6.2-r3": 1545076423 + }, + "origin": "lua-lyaml" + }, + "libxt": { + "versions": { + "1.1.5-r2": 1542883335 + }, + "origin": "libxt", + "dependencies": [ + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXt.so.6=6.0.0" + ] + }, + "lua5.2-filesystem": { + "versions": { + "1.7.0.2-r0": 1542820934 + }, + "origin": "lua-filesystem", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "desktop-file-utils": { + "versions": { + "0.23-r1": 1545116737 + }, + "origin": "desktop-file-utils", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:desktop-file-edit", + "cmd:desktop-file-install", + "cmd:desktop-file-validate", + "cmd:update-desktop-database" + ] + }, + "py3-attrs": { + "versions": { + "18.2.0-r0": 1542824881 + }, + "origin": "py-attrs", + "dependencies": [ + "python3" + ], + "provides": [ + "py-attrs-tools" + ] + }, + "dovecot-openrc": { + "versions": { + "2.3.6-r0": 1557134097 + }, + "origin": "dovecot", + "dependencies": [ + "openssl" + ] + }, + "ruby-minitest": { + "versions": { + "2.5.5-r0": 1557164837 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ] + }, + "ebtables": { + "versions": { + "2.0.10.4-r2": 1543935617 + }, + "origin": "ebtables", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libebt_802_3.so=0", + "so:libebt_among.so=0", + "so:libebt_arp.so=0", + "so:libebt_arpreply.so=0", + "so:libebt_ip.so=0", + "so:libebt_ip6.so=0", + "so:libebt_limit.so=0", + "so:libebt_log.so=0", + "so:libebt_mark.so=0", + "so:libebt_mark_m.so=0", + "so:libebt_nat.so=0", + "so:libebt_nflog.so=0", + "so:libebt_pkttype.so=0", + "so:libebt_redirect.so=0", + "so:libebt_standard.so=0", + "so:libebt_stp.so=0", + "so:libebt_ulog.so=0", + "so:libebt_vlan.so=0", + "so:libebtable_broute.so=0", + "so:libebtable_filter.so=0", + "so:libebtable_nat.so=0", + "so:libebtc.so=0", + "cmd:ebtables", + "cmd:ebtables-restore", + "cmd:ebtables-save" + ] + }, + "asterisk-pgsql": { + "versions": { + "15.7.1-r0": 1546247583 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "sipsak-dbg": { + "versions": { + "0.9.6-r11": 1543077292 + }, + "origin": "sipsak" + }, + "cvs": { + "versions": { + "1.11.23-r0": 1542893528 + }, + "origin": "cvs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cvs", + "cmd:cvsbug", + "cmd:rcs2log" + ] + }, + "py-oauth2client": { + "versions": { + "4.1.2-r2": 1545229294 + }, + "origin": "py-oauth2client", + "dependencies": [ + "py-asn1", + "py-httplib2", + "py-asn1-modules", + "py-rsa", + "py-six" + ] + }, + "mdocml": { + "versions": { + "1.14.3-r0": 1542304784 + }, + "origin": "mdocml", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libmandoc.so=0", + "cmd:demandoc", + "cmd:mandoc" + ] + }, + "perl-socket": { + "versions": { + "2.027-r0": 1543922479 + }, + "origin": "perl-socket", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-io-tty": { + "versions": { + "1.12-r4": 1542972213 + }, + "origin": "perl-io-tty", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "dwm-doc": { + "versions": { + "6.1-r2": 1545292628 + }, + "origin": "dwm" + }, + "perl-carp-clan-doc": { + "versions": { + "6.06-r1": 1545116506 + }, + "origin": "perl-carp-clan" + }, + "mariadb-bench": { + "versions": { + "10.3.13-r1": 1557431735 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common" + ] + }, + "sg3_utils": { + "versions": { + "1.42-r1": 1544000669 + }, + "origin": "sg3_utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsgutils2.so.2=2.0.0", + "cmd:scsi_logging_level", + "cmd:scsi_mandat", + "cmd:scsi_readcap", + "cmd:scsi_ready", + "cmd:scsi_satl", + "cmd:scsi_start", + "cmd:scsi_stop", + "cmd:scsi_temperature", + "cmd:sg_compare_and_write", + "cmd:sg_copy_results", + "cmd:sg_dd", + "cmd:sg_decode_sense", + "cmd:sg_emc_trespass", + "cmd:sg_format", + "cmd:sg_get_config", + "cmd:sg_get_lba_status", + "cmd:sg_ident", + "cmd:sg_inq", + "cmd:sg_logs", + "cmd:sg_luns", + "cmd:sg_map", + "cmd:sg_map26", + "cmd:sg_modes", + "cmd:sg_opcodes", + "cmd:sg_persist", + "cmd:sg_prevent", + "cmd:sg_raw", + "cmd:sg_rbuf", + "cmd:sg_rdac", + "cmd:sg_read", + "cmd:sg_read_attr", + "cmd:sg_read_block_limits", + "cmd:sg_read_buffer", + "cmd:sg_read_long", + "cmd:sg_readcap", + "cmd:sg_reassign", + "cmd:sg_referrals", + "cmd:sg_rep_zones", + "cmd:sg_requests", + "cmd:sg_reset", + "cmd:sg_reset_wp", + "cmd:sg_rmsn", + "cmd:sg_rtpg", + "cmd:sg_safte", + "cmd:sg_sanitize", + "cmd:sg_sat_identify", + "cmd:sg_sat_phy_event", + "cmd:sg_sat_read_gplog", + "cmd:sg_sat_set_features", + "cmd:sg_scan", + "cmd:sg_senddiag", + "cmd:sg_ses", + "cmd:sg_ses_microcode", + "cmd:sg_start", + "cmd:sg_stpg", + "cmd:sg_sync", + "cmd:sg_test_rwbuf", + "cmd:sg_timestamp", + "cmd:sg_turs", + "cmd:sg_unmap", + "cmd:sg_verify", + "cmd:sg_vpd", + "cmd:sg_wr_mode", + "cmd:sg_write_buffer", + "cmd:sg_write_long", + "cmd:sg_write_same", + "cmd:sg_write_verify", + "cmd:sg_xcopy", + "cmd:sg_zone", + "cmd:sginfo", + "cmd:sgm_dd", + "cmd:sgp_dd" + ] + }, + "openvpn-dev": { + "versions": { + "2.4.6-r4": 1543223245 + }, + "origin": "openvpn" + }, + "libwebsockets-doc": { + "versions": { + "3.1.0-r0": 1545856933 + }, + "origin": "libwebsockets" + }, + "gtkglext-dev": { + "versions": { + "1.2.0-r12": 1544001307 + }, + "origin": "gtkglext", + "dependencies": [ + "gtk+2.0-dev", + "mesa-dev", + "libice-dev", + "libxxf86vm-dev", + "libxi-dev", + "libx11-dev", + "libxt-dev", + "glu-dev", + "pangox-compat-dev", + "gtkglext=1.2.0-r12", + "pc:gdk-2.0", + "pc:gmodule-2.0", + "pc:gtk+-2.0", + "pc:pango", + "pc:pangox", + "pkgconfig" + ], + "provides": [ + "pc:gdkglext-1.0=1.2.0", + "pc:gdkglext-x11-1.0=1.2.0", + "pc:gtkglext-1.0=1.2.0", + "pc:gtkglext-x11-1.0=1.2.0" + ] + }, + "glibmm-dev": { + "versions": { + "2.56.0-r1": 1544799351 + }, + "origin": "glibmm", + "dependencies": [ + "libsigc++-dev", + "glibmm=2.56.0-r1", + "pc:gio-2.0", + "pc:gobject-2.0", + "pc:sigc++-2.0", + "pkgconfig" + ], + "provides": [ + "pc:giomm-2.4=2.56.0", + "pc:glibmm-2.4=2.56.0" + ] + }, + "mupdf": { + "versions": { + "1.13.0-r2": 1544000576 + }, + "origin": "mupdf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libharfbuzz.so.0", + "so:libjbig2dec.so.0", + "so:libjpeg.so.8", + "so:libopenjp2.so.7", + "so:libz.so.1" + ], + "provides": [ + "so:libmupdf.so.0=0", + "so:libmupdfthird.so.0=0" + ] + }, + "linux-firmware-cxgb4": { + "versions": { + "20190322-r0": 1554980653 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "dropbear": { + "versions": { + "2018.76-r2": 1545208977 + }, + "origin": "dropbear", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:dropbear", + "cmd:dropbearkey" + ] + }, + "acct-doc": { + "versions": { + "6.6.4-r0": 1545209152 + }, + "origin": "acct" + }, + "lua-cqueues": { + "versions": { + "20171014-r3": 1546520956 + }, + "origin": "lua-cqueues" + }, + "libmms-dev": { + "versions": { + "0.6.4-r0": 1544001045 + }, + "origin": "libmms", + "dependencies": [ + "libmms=0.6.4-r0", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libmms=0.6.4" + ] + }, + "cyrus-sasl-crammd5": { + "versions": { + "2.1.27-r1": 1550353528 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "consolekit2-dev": { + "versions": { + "1.2.1-r1": 1545062380 + }, + "origin": "consolekit2", + "dependencies": [ + "consolekit2=1.2.1-r1", + "pc:dbus-1", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:ck-connector=1.2.1", + "pc:libconsolekit=1.2.1" + ] + }, + "liblogging-doc": { + "versions": { + "1.0.6-r0": 1545060628 + }, + "origin": "liblogging" + }, + "lua-microlight": { + "versions": { + "1.1.1-r2": 1543249978 + }, + "origin": "lua-microlight" + }, + "libxcomposite-dev": { + "versions": { + "0.4.4-r2": 1543925370 + }, + "origin": "libxcomposite", + "dependencies": [ + "libxext-dev", + "libxcomposite=0.4.4-r2", + "pc:compositeproto>=0.4", + "pc:x11", + "pc:xfixes", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xcomposite=0.4.4" + ] + }, + "epris": { + "versions": { + "0.2-r4": 1545300236 + }, + "origin": "epris", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgthread-2.0.so.0" + ], + "provides": [ + "cmd:epr" + ] + }, + "py3-ipaddress": { + "versions": { + "1.0.22-r0": 1543926512 + }, + "origin": "py-ipaddress", + "dependencies": [ + "python3" + ] + }, + "py-boto-doc": { + "versions": { + "2.49.0-r0": 1544000446 + }, + "origin": "py-boto", + "dependencies": [ + "python2" + ] + }, + "uvncrepeater": { + "versions": { + "014-r7": 1545224119 + }, + "origin": "uvncrepeater", + "dependencies": [ + "openrc>=0.6", + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:repeater" + ] + }, + "py3-pyflakes": { + "versions": { + "1.6.0-r3": 1545073863 + }, + "origin": "py-pyflakes", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:pyflakes-3" + ] + }, + "py-jinja2-doc": { + "versions": { + "2.10-r2": 1542824937 + }, + "origin": "py-jinja2" + }, + "librrd": { + "versions": { + "1.7.0-r0": 1542924809 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpng16.so.16", + "so:libxml2.so.2" + ], + "provides": [ + "so:librrd.so.8=8.1.0" + ] + }, + "abi-compliance-checker": { + "versions": { + "2.3-r0": 1545745819 + }, + "origin": "abi-compliance-checker", + "dependencies": [ + "perl", + "build-base" + ], + "provides": [ + "cmd:abi-compliance-checker" + ] + }, + "chrpath": { + "versions": { + "0.16-r1": 1542900311 + }, + "origin": "chrpath", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:chrpath" + ] + }, + "perl-fcgi-doc": { + "versions": { + "0.78-r2": 1543242207 + }, + "origin": "perl-fcgi" + }, + "lua5.3-maxminddb": { + "versions": { + "0.1-r1": 1543226678 + }, + "origin": "lua-maxminddb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmaxminddb.so.0" + ] + }, + "conky": { + "versions": { + "1.11.1-r0": 1545746033 + }, + "origin": "conky", + "dependencies": [ + "so:libImlib2.so.1", + "so:libX11.so.6", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libgcc_s.so.1", + "so:liblua-5.3.so.0", + "so:libncursesw.so.6", + "so:libstdc++.so.6", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:conky" + ] + }, + "perl-io-async-doc": { + "versions": { + "0.72-r0": 1544799157 + }, + "origin": "perl-io-async" + }, + "perl-datetime": { + "versions": { + "1.44-r0": 1543239028 + }, + "origin": "perl-datetime", + "dependencies": [ + "perl-datetime-locale", + "perl-try-tiny", + "perl-dist-checkconflicts", + "perl-params-validationcompiler", + "perl-datetime-timezone", + "perl-namespace-autoclean", + "perl-specio", + "so:libc.musl-x86_64.so.1" + ] + }, + "nss-pam-ldapd": { + "versions": { + "0.9.8-r0": 1542845539 + }, + "origin": "nss-pam-ldapd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2", + "so:libpam.so.0" + ], + "provides": [ + "cmd:nslcd" + ] + }, + "dnsmasq-doc": { + "versions": { + "2.80-r3": 1545223253 + }, + "origin": "dnsmasq" + }, + "nagios-plugins-all": { + "versions": { + "2.2.1-r6": 1543933914 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins-breeze", + "nagios-plugins-by_ssh", + "nagios-plugins-cluster", + "nagios-plugins-dbi", + "nagios-plugins-dhcp", + "nagios-plugins-dig", + "nagios-plugins-disk_smb", + "nagios-plugins-disk", + "nagios-plugins-dns", + "nagios-plugins-dummy", + "nagios-plugins-file_age", + "nagios-plugins-fping", + "nagios-plugins-hpjd", + "nagios-plugins-http", + "nagios-plugins-icmp", + "nagios-plugins-ide_smart", + "nagios-plugins-ifoperstatus", + "nagios-plugins-ifstatus", + "nagios-plugins-ircd", + "nagios-plugins-ldap", + "nagios-plugins-load", + "nagios-plugins-log", + "nagios-plugins-mailq", + "nagios-plugins-mrtgtraf", + "nagios-plugins-mrtg", + "nagios-plugins-mysql", + "nagios-plugins-nagios", + "nagios-plugins-ntp", + "nagios-plugins-nt", + "nagios-plugins-nwstat", + "nagios-plugins-overcr", + "nagios-plugins-pgsql", + "nagios-plugins-ping", + "nagios-plugins-procs", + "nagios-plugins-radius", + "nagios-plugins-real", + "nagios-plugins-rpc", + "nagios-plugins-sensors", + "nagios-plugins-smtp", + "nagios-plugins-snmp", + "nagios-plugins-ssh", + "nagios-plugins-swap", + "nagios-plugins-time", + "nagios-plugins-ups", + "nagios-plugins-uptime", + "nagios-plugins-users", + "nagios-plugins-wave", + "nagios-plugins-openrc", + "nagios-plugins-tcp" + ] + }, + "lua5.1-cjson": { + "versions": { + "2.1.0-r8": 1542820882 + }, + "origin": "lua-cjson", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "linux-virt-dev": { + "versions": { + "4.19.41-r0": 1557400007 + }, + "origin": "linux-vanilla", + "dependencies": [ + "perl", + "gmp-dev", + "elfutils-dev", + "bash", + "flex", + "bison", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libelf.so.1" + ] + }, + "freeradius-doc": { + "versions": { + "3.0.17-r5": 1556201987 + }, + "origin": "freeradius" + }, + "dnssec-root": { + "versions": { + "20190225-r0": 1551205319 + }, + "origin": "dnssec-root" + }, + "py2-lxml": { + "versions": { + "4.2.5-r0": 1545065047 + }, + "origin": "py-lxml", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexslt.so.0", + "so:libpython2.7.so.1.0", + "so:libxml2.so.2", + "so:libxslt.so.1" + ] + }, + "avahi-compat-libdns_sd": { + "versions": { + "0.7-r1": 1543925311 + }, + "origin": "avahi", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdns_sd.so.1=1.0.0" + ] + }, + "qemu-lang": { + "versions": { + "3.1.0-r3": 1551107299 + }, + "origin": "qemu" + }, + "gnu-efi": { + "versions": { + "3.0.4-r1": 1543935645 + }, + "origin": "gnu-efi" + }, + "cryptsetup-doc": { + "versions": { + "2.0.6-r0": 1545746292 + }, + "origin": "cryptsetup" + }, + "py-six": { + "versions": { + "1.11.0-r0": 1542823023 + }, + "origin": "py-six" + }, + "uwsgi-logzmq": { + "versions": { + "2.0.17.1-r0": 1545062202 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libzmq.so.5" + ] + }, + "libxxf86misc-doc": { + "versions": { + "1.0.3-r2": 1543927842 + }, + "origin": "libxxf86misc" + }, + "slibtool": { + "versions": { + "0.5.26-r0": 1545213733 + }, + "origin": "slibtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:clibtool", + "cmd:clibtool-shared", + "cmd:clibtool-static", + "cmd:dlibtool", + "cmd:dlibtool-shared", + "cmd:dlibtool-static", + "cmd:rclibtool", + "cmd:rdclibtool", + "cmd:rdlibtool", + "cmd:rlibtool", + "cmd:slibtool", + "cmd:slibtool-shared", + "cmd:slibtool-static" + ] + }, + "eggdbus-doc": { + "versions": { + "0.6-r5": 1543935571 + }, + "origin": "eggdbus" + }, + "lua5.3-discount": { + "versions": { + "1.2.10.1-r4": 1545209123 + }, + "origin": "lua-discount", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libverto-libevent": { + "versions": { + "0.3.0-r1": 1543223526 + }, + "origin": "libverto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libverto.so.1" + ], + "provides": [ + "so:libverto-libevent.so.1=1.0.0" + ] + }, + "mtr-doc": { + "versions": { + "0.92-r0": 1545163034 + }, + "origin": "mtr" + }, + "libpng-dev": { + "versions": { + "1.6.37-r0": 1557129094 + }, + "origin": "libpng", + "dependencies": [ + "libpng=1.6.37-r0", + "pc:zlib", + "pkgconfig" + ], + "provides": [ + "pc:libpng16=1.6.37", + "pc:libpng=1.6.37", + "cmd:libpng-config", + "cmd:libpng16-config" + ] + }, + "hunspell-pt": { + "versions": { + "20170814-r0": 1545223270 + }, + "origin": "hunspell-pt" + }, + "build-base": { + "versions": { + "0.5-r1": 1542302758 + }, + "origin": "build-base", + "dependencies": [ + "binutils", + "file", + "gcc", + "g++", + "make", + "libc-dev", + "fortify-headers" + ] + }, + "py-meld3": { + "versions": { + "1.0.2-r1": 1542972217 + }, + "origin": "py-meld3", + "dependencies": [ + "python2" + ] + }, + "py3-country": { + "versions": { + "18.12.8-r0": 1548109641 + }, + "origin": "py-country", + "dependencies": [ + "python3" + ] + }, + "sshfs": { + "versions": { + "3.5.1-r0": 1548094627 + }, + "origin": "sshfs", + "dependencies": [ + "openssh-client", + "so:libc.musl-x86_64.so.1", + "so:libfuse3.so.3", + "so:libglib-2.0.so.0" + ], + "provides": [ + "cmd:mount.fuse.sshfs", + "cmd:mount.sshfs", + "cmd:sshfs" + ] + }, + "py-httplib2": { + "versions": { + "0.12.0-r0": 1548112519 + }, + "origin": "py-httplib2" + }, + "samba-winbind-clients": { + "versions": { + "4.8.11-r1": 1555334974 + }, + "origin": "samba", + "dependencies": [ + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcliauth-samba4.so", + "so:libcmdline-contexts-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libpopt-samba3-samba4.so", + "so:libpopt.so.0", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsmbconf.so.0", + "so:libtalloc.so.2", + "so:libwbclient.so.0", + "so:libwinbind-client-samba4.so" + ], + "provides": [ + "cmd:ntlm_auth", + "cmd:wbinfo" + ] + }, + "links-doc": { + "versions": { + "2.15-r2": 1545207743 + }, + "origin": "links" + }, + "linux-firmware-e100": { + "versions": { + "20190322-r0": 1554980652 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "uwsgi-router_basicauth": { + "versions": { + "2.0.17.1-r0": 1545062205 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-set-intspan-doc": { + "versions": { + "1.19-r0": 1545213690 + }, + "origin": "perl-set-intspan" + }, + "nfs-utils": { + "versions": { + "2.3.2-r1": 1543933423 + }, + "origin": "nfs-utils", + "dependencies": [ + "rpcbind", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcom_err.so.2", + "so:libdevmapper.so.1.02", + "so:libevent-2.1.so.6", + "so:libgssapi_krb5.so.2", + "so:libkeyutils.so.1", + "so:libkrb5.so.3", + "so:libmount.so.1", + "so:libnfsidmap.so.1", + "so:libsqlite3.so.0", + "so:libtirpc.so.3" + ], + "provides": [ + "cmd:blkmapd", + "cmd:exportfs", + "cmd:mount.nfs", + "cmd:mount.nfs4", + "cmd:mountstats", + "cmd:nfsconf", + "cmd:nfsdcltrack", + "cmd:nfsidmap", + "cmd:nfsiostat", + "cmd:nfsstat", + "cmd:osd_login", + "cmd:rpc.gssd", + "cmd:rpc.idmapd", + "cmd:rpc.mountd", + "cmd:rpc.nfsd", + "cmd:rpc.statd", + "cmd:rpc.svcgssd", + "cmd:rpcdebug", + "cmd:showmount", + "cmd:sm-notify", + "cmd:start-statd", + "cmd:umount.nfs", + "cmd:umount.nfs4" + ] + }, + "perl-html-parser": { + "versions": { + "3.72-r2": 1542821840 + }, + "origin": "perl-html-parser", + "dependencies": [ + "perl-html-tagset", + "so:libc.musl-x86_64.so.1" + ] + }, + "kamailio-websocket": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libunistring.so.2" + ] + }, + "libwebsockets-test": { + "versions": { + "3.1.0-r0": 1545856933 + }, + "origin": "libwebsockets", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:libwebsockets-test-client", + "cmd:libwebsockets-test-lejp", + "cmd:libwebsockets-test-server", + "cmd:libwebsockets-test-server-extpoll" + ] + }, + "perl-gdgraph-doc": { + "versions": { + "1.54-r0": 1545060786 + }, + "origin": "perl-gdgraph" + }, + "openbox": { + "versions": { + "3.6.1-r2": 1545207329 + }, + "origin": "openbox", + "dependencies": [ + "so:libICE.so.6", + "so:libSM.so.6", + "so:libX11.so.6", + "so:libXcursor.so.1", + "so:libXext.so.6", + "so:libXinerama.so.1", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libintl.so.8", + "so:libobrender.so.32", + "so:libobt.so.2", + "so:libstartup-notification-1.so.0" + ], + "provides": [ + "cmd:obxprop", + "cmd:openbox", + "cmd:openbox-session", + "cmd:setlayout" + ] + }, + "acf-postgresql": { + "versions": { + "0.11.0-r2": 1543924416 + }, + "origin": "acf-postgresql", + "dependencies": [ + "acf-core", + "postgresql", + "acf-db-lib", + "lua-sql-postgres" + ] + }, + "libxi": { + "versions": { + "1.7.9-r2": 1543228126 + }, + "origin": "libxi", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXi.so.6=6.1.0" + ] + }, + "libcap": { + "versions": { + "2.26-r0": 1546585711 + }, + "origin": "libcap", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcap.so.2=2.26", + "cmd:capsh", + "cmd:getcap", + "cmd:getpcaps", + "cmd:setcap" + ] + }, + "libfontenc-dev": { + "versions": { + "1.1.3-r3": 1542924592 + }, + "origin": "libfontenc", + "dependencies": [ + "libfontenc=1.1.3-r3", + "pkgconfig" + ], + "provides": [ + "pc:fontenc=1.1.3" + ] + }, + "lua5.3-stringy": { + "versions": { + "0.5.0-r1": 1545116589 + }, + "origin": "lua-stringy", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "vanessa_logger-dev": { + "versions": { + "0.0.10-r0": 1542893597 + }, + "origin": "vanessa_logger", + "dependencies": [ + "pkgconfig", + "vanessa_logger=0.0.10-r0" + ], + "provides": [ + "pc:vanessa-logger=0.0.10" + ] + }, + "libfastjson-dev": { + "versions": { + "0.99.8-r1": 1545060650 + }, + "origin": "libfastjson", + "dependencies": [ + "libfastjson=0.99.8-r1", + "pkgconfig" + ], + "provides": [ + "pc:libfastjson=0.99.8" + ] + }, + "mosh-doc": { + "versions": { + "1.3.2-r7": 1543932000 + }, + "origin": "mosh" + }, + "rpcbind-dbg": { + "versions": { + "0.2.4-r1": 1543933378 + }, + "origin": "rpcbind" + }, + "linux-firmware-other": { + "versions": { + "20190322-r0": 1554980655 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "perl-socket-getaddrinfo": { + "versions": { + "0.22-r0": 1542883395 + }, + "origin": "perl-socket-getaddrinfo", + "dependencies": [ + "perl-extutils-cchecker" + ], + "provides": [ + "cmd:getaddrinfo", + "cmd:getnameinfo" + ] + }, + "xf86-video-r128": { + "versions": { + "6.11.0-r0": 1545060960 + }, + "origin": "xf86-video-r128", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "fftw-doc": { + "versions": { + "3.3.8-r0": 1543926478 + }, + "origin": "fftw" + }, + "lua5.1-socket": { + "versions": { + "3.0_rc1_git20160306-r2": 1543934470 + }, + "origin": "lua-socket", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gmp-doc": { + "versions": { + "6.1.2-r1": 1542301439 + }, + "origin": "gmp" + }, + "giflib-utils": { + "versions": { + "5.1.4-r2": 1543077199 + }, + "origin": "giflib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgif.so.7" + ], + "provides": [ + "cmd:gif2rgb", + "cmd:gifbuild", + "cmd:gifclrmp", + "cmd:gifecho", + "cmd:giffix", + "cmd:gifinto", + "cmd:giftext", + "cmd:giftool" + ] + }, + "libbsd-dev": { + "versions": { + "0.8.6-r2": 1542822478 + }, + "origin": "libbsd", + "dependencies": [ + "bsd-compat-headers", + "linux-headers", + "libbsd=0.8.6-r2", + "pkgconfig" + ], + "provides": [ + "pc:libbsd-overlay=0.8.6", + "pc:libbsd=0.8.6" + ] + }, + "boost-signals": { + "versions": { + "1.67.0-r2": 1542823633 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_signals-mt.so.1.67.0=1.67.0", + "so:libboost_signals.so.1.67.0=1.67.0" + ] + }, + "gfortran": { + "versions": { + "8.3.0-r0": 1554745498 + }, + "origin": "gcc", + "dependencies": [ + "gcc=8.3.0-r0", + "libgfortran=8.3.0-r0", + "libquadmath=8.3.0-r0", + "libgfortran=8.3.0-r0", + "libquadmath=8.3.0-r0", + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libisl.so.15", + "so:libmpc.so.3", + "so:libmpfr.so.4", + "so:libz.so.1" + ], + "provides": [ + "cmd:gfortran", + "cmd:x86_64-alpine-linux-musl-gfortran" + ] + }, + "drbd-utils-bash-completion": { + "versions": { + "9.7.1-r0": 1545746125 + }, + "origin": "drbd-utils" + }, + "giflib-doc": { + "versions": { + "5.1.4-r2": 1543077198 + }, + "origin": "giflib" + }, + "geeqie-lang": { + "versions": { + "1.4-r0": 1545292985 + }, + "origin": "geeqie" + }, + "perl-xml-sax-base": { + "versions": { + "1.09-r0": 1542893156 + }, + "origin": "perl-xml-sax-base", + "dependencies": [ + "perl" + ] + }, + "acf-openvpn": { + "versions": { + "0.11.1-r2": 1543932518 + }, + "origin": "acf-openvpn", + "dependencies": [ + "acf-core", + "openvpn" + ] + }, + "haveged": { + "versions": { + "1.9.4-r4": 1557129010 + }, + "origin": "haveged", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhavege.so.1=1.1.0", + "cmd:haveged" + ] + }, + "mesa-gl": { + "versions": { + "18.1.7-r2": 1554455971 + }, + "origin": "mesa", + "dependencies": [ + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libexpat.so.1", + "so:libglapi.so.0", + "so:libxcb-dri2.so.0", + "so:libxcb-dri3.so.0", + "so:libxcb-glx.so.0", + "so:libxcb-present.so.0", + "so:libxcb-sync.so.1", + "so:libxcb.so.1", + "so:libxshmfence.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libGL.so.1=1.2.0" + ] + }, + "herbstluftwm-zsh-completion": { + "versions": { + "0.7.1-r0": 1545300036 + }, + "origin": "herbstluftwm" + }, + "py-nose-doc": { + "versions": { + "1.3.7-r2": 1544797221 + }, + "origin": "py-nose" + }, + "perl-test-requires": { + "versions": { + "0.10-r0": 1542845649 + }, + "origin": "perl-test-requires" + }, + "gdk-pixbuf": { + "versions": { + "2.36.11-r2": 1543253991 + }, + "origin": "gdk-pixbuf", + "dependencies": [ + "shared-mime-info", + "/bin/sh", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libtiff.so.5" + ], + "provides": [ + "so:libgdk_pixbuf-2.0.so.0=0.3611.0", + "so:libgdk_pixbuf_xlib-2.0.so.0=0.3611.0", + "cmd:gdk-pixbuf-csource", + "cmd:gdk-pixbuf-pixdata", + "cmd:gdk-pixbuf-query-loaders", + "cmd:gdk-pixbuf-thumbnailer" + ] + }, + "libksba": { + "versions": { + "1.3.5-r0": 1543932192 + }, + "origin": "libksba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgpg-error.so.0" + ], + "provides": [ + "so:libksba.so.8=8.11.6" + ] + }, + "espeak": { + "versions": { + "1.48.04-r1": 1542973182 + }, + "origin": "espeak", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libespeak.so.1=1.1.48", + "cmd:espeak" + ] + }, + "boost-unit_test_framework": { + "versions": { + "1.67.0-r2": 1542823634 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_unit_test_framework-mt.so.1.67.0=1.67.0", + "so:libboost_unit_test_framework.so.1.67.0=1.67.0" + ] + }, + "make": { + "versions": { + "4.2.1-r2": 1542302738 + }, + "origin": "make", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:make" + ] + }, + "perl-datetime-locale": { + "versions": { + "1.22-r0": 1543238993 + }, + "origin": "perl-datetime-locale", + "dependencies": [ + "perl", + "perl-cpan-meta-check", + "perl-dist-checkconflicts", + "perl-file-sharedir", + "perl-file-sharedir-install", + "perl-list-moreutils", + "perl-namespace-autoclean", + "perl-params-validate", + "perl-params-validationcompiler", + "perl-scalar-list-utils", + "perl-test-fatal", + "perl-test-requires", + "perl-test-warnings" + ] + }, + "nginx-mod-http-shibboleth": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "ssl_client": { + "versions": { + "1.29.3-r10": 1548315956 + }, + "origin": "busybox", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtls-standalone.so.1" + ], + "provides": [ + "cmd:ssl_client" + ] + }, + "openjade": { + "versions": { + "1.3.2-r5": 1542893151 + }, + "origin": "openjade", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libogrove.so.0", + "so:libosp.so.5", + "so:libospgrove.so.0", + "so:libostyle.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:openjade" + ] + }, + "libgssglue": { + "versions": { + "0.4-r1": 1543248486 + }, + "origin": "libgssglue", + "dependencies": [ + "heimdal-dev", + "heimdal-libs", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgssglue.so.1=1.0.0" + ] + }, + "lua5.1-augeas": { + "versions": { + "0.1.2-r4": 1543246060 + }, + "origin": "lua-augeas", + "dependencies": [ + "so:libaugeas.so.0", + "so:libc.musl-x86_64.so.1" + ] + }, + "font-schumacher-misc": { + "versions": { + "1.1.2-r0": 1545075918 + }, + "origin": "font-schumacher-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "libtxc_dxtn-dev": { + "versions": { + "1.0.1-r5": 1545289386 + }, + "origin": "libtxc_dxtn", + "dependencies": [ + "mesa-dev" + ] + }, + "qemu-system-aarch64": { + "versions": { + "3.1.0-r3": 1551107302 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libstdc++.so.6", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-aarch64" + ] + }, + "xcb-util-renderutil-dev": { + "versions": { + "0.3.9-r1": 1543927920 + }, + "origin": "xcb-util-renderutil", + "dependencies": [ + "xcb-util-dev", + "pc:xcb-render", + "pkgconfig", + "xcb-util-renderutil=0.3.9-r1" + ], + "provides": [ + "pc:xcb-renderutil=0.3.9" + ] + }, + "perl-crypt-des-doc": { + "versions": { + "2.07-r4": 1543923063 + }, + "origin": "perl-crypt-des" + }, + "perl-filesys-notify-simple": { + "versions": { + "0.13-r0": 1543927836 + }, + "origin": "perl-filesys-notify-simple", + "dependencies": [ + "perl" + ] + }, + "libnih-dev": { + "versions": { + "1.0.3-r5": 1545062343 + }, + "origin": "libnih", + "dependencies": [ + "dbus-dev", + "expat-dev", + "libnih=1.0.3-r5", + "pkgconfig" + ], + "provides": [ + "pc:libnih-dbus=1.0.3", + "pc:libnih=1.0.3" + ] + }, + "swish-e-dev": { + "versions": { + "2.4.7-r8": 1545163770 + }, + "origin": "swish-e", + "dependencies": [ + "pkgconfig", + "swish-e=2.4.7-r8" + ], + "provides": [ + "pc:swish-e=2.4.7", + "cmd:swish-config" + ] + }, + "lua5.3-sec": { + "versions": { + "0.7-r1": 1545075388 + }, + "origin": "lua-sec", + "dependencies": [ + "lua5.3-socket", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "xautolock": { + "versions": { + "2.2-r4": 1543932089 + }, + "origin": "xautolock", + "dependencies": [ + "so:libX11.so.6", + "so:libXss.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xautolock" + ] + }, + "elfutils": { + "versions": { + "0.168-r2": 1546849158 + }, + "origin": "elfutils" + }, + "sysklogd": { + "versions": { + "1.5.1-r1": 1545213704 + }, + "origin": "sysklogd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:klogd", + "cmd:syslogd" + ] + }, + "clang": { + "versions": { + "5.0.2-r0": 1546873921 + }, + "origin": "clang", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libclang.so.5.0", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:c-index-test", + "cmd:clang", + "cmd:clang++", + "cmd:clang-5.0", + "cmd:clang-check", + "cmd:clang-cl", + "cmd:clang-cpp", + "cmd:clang-format", + "cmd:clang-import-test", + "cmd:clang-offload-bundler", + "cmd:clang-rename", + "cmd:git-clang-format" + ] + }, + "grub-efi": { + "versions": { + "2.02-r14": 1548432370 + }, + "origin": "grub", + "dependencies": [ + "grub" + ] + }, + "recordmydesktop": { + "versions": { + "0.3.8.1-r2": 1545302039 + }, + "origin": "recordmydesktop", + "dependencies": [ + "so:libX11.so.6", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libtheora.so.0", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:recordmydesktop" + ] + }, + "openldap-overlay-translucent": { + "versions": { + "2.4.47-r2": 1546016480 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "openldap-back-meta": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "py2-simplejson": { + "versions": { + "3.15.0-r0": 1543998765 + }, + "origin": "py-simplejson", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "heimdal-dev": { + "versions": { + "7.5.0-r2": 1542819373 + }, + "origin": "heimdal", + "dependencies": [ + "openssl-dev", + "e2fsprogs-dev", + "db-dev", + "heimdal-libs=7.5.0-r2", + "pkgconfig" + ], + "provides": [ + "pc:heimdal-gssapi=7.5.0", + "pc:heimdal-kadm-client=7.5.0", + "pc:heimdal-kadm-server=7.5.0", + "pc:heimdal-krb5=7.5.0", + "pc:kadm-client=7.5.0", + "pc:kadm-server=7.5.0", + "pc:kafs=7.5.0", + "pc:krb5-gssapi=7.5.0", + "pc:krb5=7.5.0", + "cmd:krb5-config" + ] + }, + "cryptsetup-dev": { + "versions": { + "2.0.6-r0": 1545746292 + }, + "origin": "cryptsetup", + "dependencies": [ + "cryptsetup-libs=2.0.6-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcryptsetup=2.0.6" + ] + }, + "rtpproxy-tools": { + "versions": { + "2.0.0-r4": 1545301082 + }, + "origin": "rtpproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsndfile.so.1" + ], + "provides": [ + "cmd:extractaudio", + "cmd:makeann" + ] + }, + "pekwm-doc": { + "versions": { + "0.1.17-r3": 1545069432 + }, + "origin": "pekwm" + }, + "py-newt": { + "versions": { + "0.52.20-r0": 1543924701 + }, + "origin": "newt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnewt.so.0.52" + ] + }, + "libxp-doc": { + "versions": { + "1.0.3-r2": 1545224134 + }, + "origin": "libxp" + }, + "apr-util-dev": { + "versions": { + "1.6.1-r5": 1545061009 + }, + "origin": "apr-util", + "dependencies": [ + "expat-dev", + "apr-dev", + "openldap-dev", + "sqlite-dev", + "postgresql-dev", + "db-dev", + "openssl-dev", + "mariadb-dev", + "apr-util=1.6.1-r5", + "pc:apr-1", + "pkgconfig" + ], + "provides": [ + "pc:apr-util-1=1.6.1", + "cmd:apu-1-config" + ] + }, + "nagios-plugins-wave": { + "versions": { + "2.2.1-r6": 1543933914 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "perl" + ] + }, + "perl-test-inter-doc": { + "versions": { + "1.07-r0": 1543077266 + }, + "origin": "perl-test-inter" + }, + "py-snowballstemmer": { + "versions": { + "1.2.1-r1": 1542824990 + }, + "origin": "py-snowballstemmer" + }, + "libestr": { + "versions": { + "0.1.10-r0": 1545060619 + }, + "origin": "libestr", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libestr.so.0=0.0.0" + ] + }, + "sipsak": { + "versions": { + "0.9.6-r11": 1543077292 + }, + "origin": "sipsak", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libcrypto.so.1.1" + ], + "provides": [ + "cmd:sipsak" + ] + }, + "py3-chardet": { + "versions": { + "3.0.4-r0": 1542825003 + }, + "origin": "py-chardet", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:chardetect" + ] + }, + "libssh2-dev": { + "versions": { + "1.8.2-r0": 1553681858 + }, + "origin": "libssh2", + "dependencies": [ + "libssh2=1.8.2-r0", + "pc:libcrypto", + "pc:libssl", + "pc:zlib", + "pkgconfig" + ], + "provides": [ + "pc:libssh2=1.8.2" + ] + }, + "ruby-irb": { + "versions": { + "2.5.5-r0": 1557164837 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ], + "provides": [ + "cmd:irb" + ] + }, + "py2-lockfile": { + "versions": { + "0.12.2-r0": 1544799204 + }, + "origin": "py-lockfile", + "dependencies": [ + "python2" + ] + }, + "argon2-libs": { + "versions": { + "20171227-r1": 1543928961 + }, + "origin": "argon2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libargon2.so.1=1" + ] + }, + "shared-mime-info-doc": { + "versions": { + "1.10-r0": 1542821940 + }, + "origin": "shared-mime-info" + }, + "ttf-linux-libertine-doc": { + "versions": { + "5.3.0-r0": 1544000076 + }, + "origin": "ttf-linux-libertine" + }, + "py3-markupsafe": { + "versions": { + "1.1.0-r0": 1544791904 + }, + "origin": "py-markupsafe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "gtk+": { + "versions": { + "2.24.31-r0": 1543928745 + }, + "origin": "gtk+", + "dependencies": [ + "gtk+2.0>=2.24.31" + ] + }, + "vte3": { + "versions": { + "0.52.2-r0": 1545215300 + }, + "origin": "vte3", + "dependencies": [ + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo-gobject.so.2", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgnutls.so.30", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpcre2-8.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libvte-2.91.so.0=0.5200.2", + "cmd:vte-2.91" + ] + }, + "swatch-doc": { + "versions": { + "3.2.4-r4": 1545116516 + }, + "origin": "swatch" + }, + "perl-lwp-mediatypes-doc": { + "versions": { + "6.02-r1": 1542821763 + }, + "origin": "perl-lwp-mediatypes" + }, + "kamailio-outbound": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ] + }, + "exiftool": { + "versions": { + "11.23-r0": 1545835015 + }, + "origin": "perl-image-exiftool", + "dependencies": [ + "perl-image-exiftool" + ], + "provides": [ + "cmd:exiftool" + ] + }, + "multisort": { + "versions": { + "1.1-r0": 1545163700 + }, + "origin": "multisort", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:multisort" + ] + }, + "fcgi": { + "versions": { + "2.4.0-r8": 1543238826 + }, + "origin": "fcgi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfcgi.so.0=0.0.0", + "cmd:cgi-fcgi" + ] + }, + "usbredir-dev": { + "versions": { + "0.7.1-r0": 1543223285 + }, + "origin": "usbredir", + "dependencies": [ + "libusb-dev", + "pc:libusb-1.0", + "pkgconfig", + "usbredir=0.7.1-r0" + ], + "provides": [ + "pc:libusbredirhost=0.7.1", + "pc:libusbredirparser-0.5=0.7.1" + ] + }, + "mc": { + "versions": { + "4.8.22-r0": 1547126443 + }, + "origin": "mc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libssh2.so.1" + ], + "provides": [ + "cmd:mc", + "cmd:mcdiff", + "cmd:mcedit", + "cmd:mcview" + ] + }, + "libspf2": { + "versions": { + "1.2.10-r3": 1546267712 + }, + "origin": "libspf2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libspf2.so.2=2.1.0" + ] + }, + "mysql-client": { + "versions": { + "10.3.13-r1": 1557431735 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-client" + ] + }, + "perl-net-server-doc": { + "versions": { + "2.009-r1": 1545213952 + }, + "origin": "perl-net-server" + }, + "perl-scope-upper-doc": { + "versions": { + "0.31-r0": 1545164596 + }, + "origin": "perl-scope-upper" + }, + "lua5.1-evdev": { + "versions": { + "2.2.1-r1": 1545076551 + }, + "origin": "lua-evdev", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-list-utilsby-doc": { + "versions": { + "0.11-r0": 1545062502 + }, + "origin": "perl-list-utilsby" + }, + "man": { + "versions": { + "1.14.3-r0": 1542304779 + }, + "origin": "mdocml", + "dependencies": [ + "mdocml" + ] + }, + "lsyncd-doc": { + "versions": { + "2.2.3-r1": 1545253927 + }, + "origin": "lsyncd" + }, + "spandsp-dev": { + "versions": { + "0.0.6-r1": 1545815280 + }, + "origin": "spandsp", + "dependencies": [ + "pkgconfig", + "spandsp=0.0.6-r1" + ], + "provides": [ + "pc:spandsp=0.0.6" + ] + }, + "scrot-doc": { + "versions": { + "0.8.18-r0": 1545299549 + }, + "origin": "scrot" + }, + "gconf-doc": { + "versions": { + "3.2.6-r4": 1545075211 + }, + "origin": "gconf" + }, + "perl-list-someutils-xs": { + "versions": { + "0.55-r0": 1543934254 + }, + "origin": "perl-list-someutils-xs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-eval-closure-doc": { + "versions": { + "0.14-r0": 1542972940 + }, + "origin": "perl-eval-closure" + }, + "ruby-google-protobuf": { + "versions": { + "3.6.1-r1": 1543931874 + }, + "origin": "protobuf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.5" + ] + }, + "libxkbui-dev": { + "versions": { + "1.0.2-r9": 1543927889 + }, + "origin": "libxkbui", + "dependencies": [ + "xorgproto", + "libxkbui=1.0.2-r9", + "pc:kbproto", + "pc:x11", + "pc:xt", + "pkgconfig" + ], + "provides": [ + "pc:xkbui=1.0.2" + ] + }, + "py-hgtools": { + "versions": { + "6.5.2-r0": 1545060744 + }, + "origin": "py-hgtools" + }, + "audit-dev": { + "versions": { + "2.8.4-r0": 1543245855 + }, + "origin": "audit", + "dependencies": [ + "linux-headers", + "audit-libs=2.8.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:audit=2.8.4", + "pc:auparse=2.8.4" + ] + }, + "tsocks-doc": { + "versions": { + "1.8_beta5-r0": 1545207038 + }, + "origin": "tsocks" + }, + "kamailio-sctp": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libsctp.so.1" + ] + }, + "iperf-openrc": { + "versions": { + "2.0.10-r1": 1543222922 + }, + "origin": "iperf", + "dependencies": [ + "!iperf3" + ] + }, + "libwbclient": { + "versions": { + "4.8.11-r1": 1555334974 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libreplace-samba4.so=0", + "so:libwbclient.so.0=0.14", + "so:libwinbind-client-samba4.so=0" + ] + }, + "bdftopcf": { + "versions": { + "1.1-r0": 1542924735 + }, + "origin": "bdftopcf", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bdftopcf" + ] + }, + "libmemcached-dev": { + "versions": { + "1.0.18-r3": 1543923891 + }, + "origin": "libmemcached", + "dependencies": [ + "cyrus-sasl-dev", + "libmemcached-libs=1.0.18-r3", + "pkgconfig" + ], + "provides": [ + "pc:libmemcached=1.0.18" + ] + }, + "perl-module-scandeps-doc": { + "versions": { + "1.25-r0": 1542893295 + }, + "origin": "perl-module-scandeps" + }, + "freetype-dev": { + "versions": { + "2.9.1-r2": 1542822031 + }, + "origin": "freetype", + "dependencies": [ + "freetype=2.9.1-r2", + "pc:libpng", + "pc:zlib", + "pkgconfig" + ], + "provides": [ + "pc:freetype2=22.1.16", + "cmd:freetype-config" + ] + }, + "perl-devel-symdump": { + "versions": { + "2.18-r0": 1542845658 + }, + "origin": "perl-devel-symdump" + }, + "libpcap-dev": { + "versions": { + "1.9.0-r1": 1545905178 + }, + "origin": "libpcap", + "dependencies": [ + "libpcap=1.9.0-r1", + "pkgconfig" + ], + "provides": [ + "cmd:pcap-config" + ] + }, + "acf-dnscache": { + "versions": { + "0.6.0-r2": 1545208952 + }, + "origin": "acf-dnscache", + "dependencies": [ + "acf-core", + "lua-posix", + "dnscache" + ] + }, + "py2-sphinx": { + "versions": { + "1.8.3-r0": 1548111929 + }, + "origin": "py-sphinx", + "dependencies": [ + "make", + "py2-docutils", + "py2-jinja2", + "py2-pygments", + "py2-six", + "py2-sphinx_rtd_theme", + "py2-alabaster<0.8", + "py2-babel", + "py2-snowballstemmer", + "py2-imagesize", + "py2-requests", + "py2-sphinxcontrib-websupport", + "py2-setuptools", + "py2-packaging", + "py2-typing", + "python2" + ], + "provides": [ + "cmd:sphinx-apidoc-2", + "cmd:sphinx-autogen-2", + "cmd:sphinx-build-2", + "cmd:sphinx-quickstart-2" + ] + }, + "opusfile": { + "versions": { + "0.11-r1": 1545209752 + }, + "origin": "opusfile", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libogg.so.0", + "so:libopus.so.0", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libopusfile.so.0=0.4.4", + "so:libopusurl.so.0=0.4.4" + ] + }, + "nss": { + "versions": { + "3.41-r0": 1545307911 + }, + "origin": "nss", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libnspr4.so", + "so:libplc4.so", + "so:libplds4.so", + "so:libsqlite3.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libfreebl3.so=41", + "so:libgtestutil.so.41=41", + "so:libnss3.so=41", + "so:libnssckbi.so=41", + "so:libnssdbm3.so=41", + "so:libnssutil3.so=41", + "so:libsmime3.so=41", + "so:libsoftokn3.so=41", + "so:libssl3.so=41" + ] + }, + "gdbm-doc": { + "versions": { + "1.13-r1": 1542303059 + }, + "origin": "gdbm" + }, + "libcdio-dev": { + "versions": { + "0.94-r2": 1543248376 + }, + "origin": "libcdio", + "dependencies": [ + "libcdio++=0.94-r2", + "libcdio=0.94-r2", + "pkgconfig" + ], + "provides": [ + "pc:libcdio++=0.94", + "pc:libcdio=0.94", + "pc:libiso9660++=0.94", + "pc:libiso9660=0.94", + "pc:libudf=0.94" + ] + }, + "libglade-dev": { + "versions": { + "2.6.4-r14": 1543927548 + }, + "origin": "libglade", + "dependencies": [ + "gtk+2.0-dev", + "libxml2-dev", + "libglade=2.6.4-r14", + "pc:gtk+-2.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libglade-2.0=2.6.4" + ] + }, + "iproute2-qos": { + "versions": { + "0.5-r1": 1544000678 + }, + "origin": "iproute2-qos", + "dependencies": [ + "iproute2" + ], + "provides": [ + "cmd:setup-qos" + ] + }, + "pinentry": { + "versions": { + "1.1.0-r0": 1543932178 + }, + "origin": "pinentry", + "dependencies": [ + "/bin/sh", + "so:libassuan.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libgpg-error.so.0", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:pinentry-curses" + ] + }, + "lua5.1-mosquitto": { + "versions": { + "0.2-r1": 1543932698 + }, + "origin": "lua-mosquitto", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ] + }, + "lighttpd-doc": { + "versions": { + "1.4.52-r0": 1545207628 + }, + "origin": "lighttpd" + }, + "lz4-dev": { + "versions": { + "1.8.3-r2": 1545154434 + }, + "origin": "lz4", + "dependencies": [ + "lz4-libs=1.8.3-r2", + "pkgconfig" + ], + "provides": [ + "pc:liblz4=1.8.3" + ] + }, + "rdesktop": { + "versions": { + "1.8.3-r5": 1545068564 + }, + "origin": "rdesktop", + "dependencies": [ + "so:libX11.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgssglue.so.1", + "so:libsamplerate.so.0", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:rdesktop" + ] + }, + "perl-package-stash": { + "versions": { + "0.37-r0": 1542845736 + }, + "origin": "perl-package-stash", + "dependencies": [ + "perl-dist-checkconflicts", + "perl-package-stash-xs", + "perl-module-implementation" + ], + "provides": [ + "cmd:package-stash-conflicts" + ] + }, + "mcpp": { + "versions": { + "2.7.2-r1": 1542883471 + }, + "origin": "mcpp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmcpp.so.0" + ], + "provides": [ + "cmd:mcpp" + ] + }, + "ulogd-doc": { + "versions": { + "2.0.7-r0": 1545300897 + }, + "origin": "ulogd" + }, + "py-crypto": { + "versions": { + "2.6.1-r2": 1543223473 + }, + "origin": "py-crypto" + }, + "py-django-pipeline": { + "versions": { + "1.3.25-r0": 1545207042 + }, + "origin": "py-django-pipeline", + "dependencies": [ + "python2" + ] + }, + "cksfv": { + "versions": { + "1.3.14-r4": 1545069445 + }, + "origin": "cksfv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cksfv" + ] + }, + "putty-doc": { + "versions": { + "0.71-r1": 1554726207 + }, + "origin": "putty" + }, + "json-glib-dev": { + "versions": { + "1.4.4-r0": 1545837175 + }, + "origin": "json-glib", + "dependencies": [ + "json-glib=1.4.4-r0", + "pc:gio-2.0>=2.44.0", + "pc:gobject-2.0>=2.44.0", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjson-glib-1.0.so.0" + ], + "provides": [ + "pc:json-glib-1.0=1.4.4", + "cmd:json-glib-format", + "cmd:json-glib-validate" + ] + }, + "atk": { + "versions": { + "2.30.0-r0": 1542823743 + }, + "origin": "atk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libatk-1.0.so.0=0.23009.1" + ] + }, + "perl-xml-namespacesupport": { + "versions": { + "1.12-r0": 1542893163 + }, + "origin": "perl-xml-namespacesupport", + "dependencies": [ + "perl" + ] + }, + "radvd-doc": { + "versions": { + "2.17-r2": 1545254216 + }, + "origin": "radvd" + }, + "zmap": { + "versions": { + "2.1.1-r3": 1545062099 + }, + "origin": "zmap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libjson-c.so.4", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:zblacklist", + "cmd:zmap", + "cmd:ztee" + ] + }, + "perl-clone-doc": { + "versions": { + "0.41-r1": 1546096794 + }, + "origin": "perl-clone" + }, + "poppler-doc": { + "versions": { + "0.56.0-r1": 1542824316 + }, + "origin": "poppler" + }, + "linux-firmware-myricom": { + "versions": { + "20190322-r0": 1554980650 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "libvirt-lxc": { + "versions": { + "4.10.0-r1": 1547051658 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-daemon=4.10.0-r1", + "libvirt-common-drivers=4.10.0-r1", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0", + "so:libfuse.so.2", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libtirpc.so.3", + "so:libvirt.so.0", + "so:libxml2.so.2" + ] + }, + "xf86-video-nv": { + "versions": { + "2.1.21-r3": 1545300604 + }, + "origin": "xf86-video-nv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "nagios-plugins-smtp": { + "versions": { + "2.2.1-r6": 1543933912 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "xtables-addons-vanilla": { + "versions": { + "4.19.41-r0": 1557400893 + }, + "origin": "xtables-addons-vanilla", + "dependencies": [ + "linux-vanilla=4.19.41-r0" + ] + }, + "gnuchess": { + "versions": { + "6.2.5-r1": 1545302332 + }, + "origin": "gnuchess", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:gnuchess", + "cmd:gnuchessu", + "cmd:gnuchessx" + ] + }, + "freeswitch-sounds-ru-RU-elena-8000": { + "versions": { + "1.0.12-r1": 1545249945 + }, + "origin": "freeswitch-sounds-ru-RU-elena-8000" + }, + "perl-params-classify-doc": { + "versions": { + "0.015-r0": 1542845687 + }, + "origin": "perl-params-classify" + }, + "weechat-aspell": { + "versions": { + "2.3-r0": 1545299932 + }, + "origin": "weechat", + "dependencies": [ + "weechat", + "so:libaspell.so.15", + "so:libc.musl-x86_64.so.1" + ] + }, + "libnetfilter_queue": { + "versions": { + "1.0.3-r0": 1546267693 + }, + "origin": "libnetfilter_queue", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0", + "so:libnfnetlink.so.0" + ], + "provides": [ + "so:libnetfilter_queue.so.1=1.4.0" + ] + }, + "dovecot-pigeonhole-plugin": { + "versions": { + "2.3.6-r0": 1557134098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libdovecot-login.so.0", + "so:libdovecot-storage.so.0", + "so:libdovecot.so.0" + ], + "provides": [ + "so:lib90_sieve_plugin.so=0", + "so:lib95_imap_filter_sieve_plugin.so=0", + "so:lib95_imap_sieve_plugin.so=0", + "so:libdovecot-sieve.so.0=0.0.0", + "cmd:sieve-dump", + "cmd:sieve-filter", + "cmd:sieve-test", + "cmd:sievec" + ] + }, + "perl-json": { + "versions": { + "2.97000-r0": 1542819174 + }, + "origin": "perl-json" + }, + "lua-asn1": { + "versions": { + "2.2.0-r0": 1545116591 + }, + "origin": "lua-asn1", + "dependencies": [ + "lua-stringy" + ] + }, + "libzdb": { + "versions": { + "3.1-r2": 1543221887 + }, + "origin": "libzdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3", + "so:libpq.so.5", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:libzdb.so.11=11.0.0" + ] + }, + "lua-stdlib-doc": { + "versions": { + "41.2.1-r0": 1545292922 + }, + "origin": "lua-stdlib" + }, + "netcf": { + "versions": { + "0.2.8-r5": 1543935401 + }, + "origin": "netcf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetcf.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:ncftool" + ] + }, + "openssh-keygen": { + "versions": { + "7.9_p1-r5": 1556034589 + }, + "origin": "openssh", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ], + "provides": [ + "cmd:ssh-keygen" + ] + }, + "libsamplerate": { + "versions": { + "0.1.9-r1": 1545068554 + }, + "origin": "libsamplerate", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsndfile.so.1" + ], + "provides": [ + "so:libsamplerate.so.0=0.1.8", + "cmd:sndfile-resample" + ] + }, + "perl-sub-quote": { + "versions": { + "2.004000-r0": 1542972964 + }, + "origin": "perl-sub-quote" + }, + "nload-doc": { + "versions": { + "0.7.4-r3": 1545214121 + }, + "origin": "nload" + }, + "binutils-gold": { + "versions": { + "2.31.1-r2": 1546441178 + }, + "origin": "binutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:ld.gold" + ] + }, + "perl-dbd-mysql": { + "versions": { + "4.050-r0": 1547124434 + }, + "origin": "perl-dbd-mysql", + "dependencies": [ + "perl", + "perl-dbi", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "libnl3": { + "versions": { + "3.4.0-r0": 1542965893 + }, + "origin": "libnl3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnl-3.so.200=200.26.0", + "so:libnl-genl-3.so.200=200.26.0", + "so:libnl-idiag-3.so.200=200.26.0", + "so:libnl-nf-3.so.200=200.26.0", + "so:libnl-route-3.so.200=200.26.0", + "so:libnl-xfrm-3.so.200=200.26.0" + ] + }, + "dkimproxy-doc": { + "versions": { + "1.4.1-r5": 1545213959 + }, + "origin": "dkimproxy" + }, + "bacula-doc": { + "versions": { + "9.4.1-r1": 1546944087 + }, + "origin": "bacula" + }, + "libidn-doc": { + "versions": { + "1.35-r0": 1542924920 + }, + "origin": "libidn" + }, + "libatasmart-dev": { + "versions": { + "0.19-r1": 1545062468 + }, + "origin": "libatasmart", + "dependencies": [ + "eudev-dev", + "libatasmart=0.19-r1", + "pkgconfig" + ], + "provides": [ + "pc:libatasmart=0.19" + ] + }, + "gc-doc": { + "versions": { + "7.6.4-r2": 1543226892 + }, + "origin": "gc" + }, + "gsm-dev": { + "versions": { + "1.0.18-r0": 1543927819 + }, + "origin": "gsm", + "dependencies": [ + "gsm=1.0.18-r0" + ] + }, + "qt-doc": { + "versions": { + "4.8.7-r11": 1545075086 + }, + "origin": "qt" + }, + "cyrus-sasl-dev": { + "versions": { + "2.1.27-r1": 1550353527 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "libsasl=2.1.27-r1", + "pkgconfig" + ], + "provides": [ + "pc:libsasl2=2.1.27" + ] + }, + "perl-xml-rss": { + "versions": { + "1.60-r0": 1545301036 + }, + "origin": "perl-xml-rss", + "dependencies": [ + "perl-xml-parser", + "perl-html-parser", + "perl-datetime-format-mail", + "perl-datetime-format-w3cdtf", + "perl-datetime" + ] + }, + "lua5.3-md5": { + "versions": { + "1.2-r3": 1542883866 + }, + "origin": "lua-md5", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-pip": { + "versions": { + "18.1-r0": 1545062533 + }, + "origin": "py2-pip", + "dependencies": [ + "python2", + "py-setuptools" + ], + "provides": [ + "py-pip=18.1-r0", + "cmd:pip", + "cmd:pip2", + "cmd:pip2.7" + ] + }, + "py3-requests": { + "versions": { + "2.19.1-r0": 1542825029 + }, + "origin": "py-requests", + "dependencies": [ + "py3-chardet", + "py3-idna", + "py3-certifi", + "py3-urllib3", + "python3" + ] + }, + "openrc": { + "versions": { + "0.39.2-r3": 1548592318 + }, + "origin": "openrc", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libeinfo.so.1=1", + "so:librc.so.1=1", + "cmd:openrc", + "cmd:openrc-init", + "cmd:openrc-run", + "cmd:openrc-shutdown", + "cmd:rc", + "cmd:rc-service", + "cmd:rc-sstat", + "cmd:rc-status", + "cmd:rc-update", + "cmd:runscript", + "cmd:service", + "cmd:start-stop-daemon", + "cmd:supervise-daemon" + ] + }, + "rsyslog-mmpstrucdata": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "nettle-dev": { + "versions": { + "3.4.1-r0": 1544791824 + }, + "origin": "nettle", + "dependencies": [ + "gmp-dev", + "nettle=3.4.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:hogweed=3.4.1", + "pc:nettle=3.4.1" + ] + }, + "ncurses5-widec-libs": { + "versions": { + "5.9-r1": 1545292734 + }, + "origin": "ncurses5", + "dependencies": [ + "ncurses-terminfo-base", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libformw.so.5=5.9", + "so:libmenuw.so.5=5.9", + "so:libncursesw.so.5=5.9", + "so:libpanelw.so.5=5.9" + ] + }, + "kamailio-mysql": { + "versions": { + "5.2.0-r1": 1546423170 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "kamailio-db", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3", + "so:libsrdb1.so.1", + "so:libsrdb2.so.1" + ] + }, + "gdb-doc": { + "versions": { + "8.2-r1": 1544798982 + }, + "origin": "gdb" + }, + "avahi-libs": { + "versions": { + "0.7-r1": 1543925311 + }, + "origin": "avahi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libintl.so.8" + ], + "provides": [ + "so:libavahi-client.so.3=3.2.9", + "so:libavahi-common.so.3=3.5.3" + ] + }, + "seavgabios-bin": { + "versions": { + "1.11.0-r0": 1543936026 + }, + "origin": "seabios" + }, + "libfdt": { + "versions": { + "1.4.7-r0": 1545300716 + }, + "origin": "dtc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfdt.so.1=0" + ] + }, + "at-spi2-core-lang": { + "versions": { + "2.28.0-r0": 1543241208 + }, + "origin": "at-spi2-core" + }, + "perl-sub-name": { + "versions": { + "0.21-r1": 1542845760 + }, + "origin": "perl-sub-name", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "fuse3": { + "versions": { + "3.2.6-r1": 1548943636 + }, + "origin": "fuse3", + "dependencies": [ + "fuse-common", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfuse3.so.3=3.2.6", + "cmd:fusermount3", + "cmd:mount.fuse3" + ] + }, + "lua5.1-lzmq": { + "versions": { + "0.4.4-r0": 1545076581 + }, + "origin": "lua-lzmq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5", + "so:libzmq.so.5" + ] + }, + "libjpeg-turbo": { + "versions": { + "1.5.3-r4": 1547027583 + }, + "origin": "libjpeg-turbo", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libjpeg.so.8=8.1.2", + "so:libturbojpeg.so.0=0.1.0" + ] + }, + "exiv2": { + "versions": { + "0.26-r0": 1543246769 + }, + "origin": "exiv2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libexiv2.so.26=26.0.0", + "cmd:exiv2" + ] + }, + "perl-universal-require-doc": { + "versions": { + "0.18-r0": 1545995649 + }, + "origin": "perl-universal-require" + }, + "linux-firmware-vicam": { + "versions": { + "20190322-r0": 1554980646 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "libxml2": { + "versions": { + "2.9.9-r1": 1551105514 + }, + "origin": "libxml2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libxml2.so.2=2.9.9" + ] + }, + "perl-test-requires-doc": { + "versions": { + "0.10-r0": 1542845644 + }, + "origin": "perl-test-requires" + }, + "py-urlgrabber": { + "versions": { + "3.10.1-r1": 1543935136 + }, + "origin": "py-urlgrabber", + "dependencies": [ + "python2", + "py-curl" + ], + "provides": [ + "cmd:urlgrabber" + ] + }, + "libogg": { + "versions": { + "1.3.3-r2": 1543925777 + }, + "origin": "libogg", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libogg.so.0=0.8.3" + ] + }, + "perl-dbd-sqlite-dev": { + "versions": { + "1.62-r0": 1546944025 + }, + "origin": "perl-dbd-sqlite" + }, + "zstd-static": { + "versions": { + "1.3.8-r0": 1546966443 + }, + "origin": "zstd" + }, + "openvswitch-dbg": { + "versions": { + "2.10.1-r0": 1544000344 + }, + "origin": "openvswitch" + }, + "perl-file-sharedir-install-doc": { + "versions": { + "0.13-r0": 1543238879 + }, + "origin": "perl-file-sharedir-install" + }, + "perl-test-failwarnings-doc": { + "versions": { + "0.008-r1": 1543238916 + }, + "origin": "perl-test-failwarnings" + }, + "darkstat-doc": { + "versions": { + "3.0.719-r1": 1543223079 + }, + "origin": "darkstat" + }, + "linux-firmware-ene-ub6250": { + "versions": { + "20190322-r0": 1554980652 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "py-atomicwrites": { + "versions": { + "1.1.5-r0": 1542824873 + }, + "origin": "py-atomicwrites" + }, + "perl-scalar-list-utils": { + "versions": { + "1.50-r0": 1543238903 + }, + "origin": "perl-scalar-list-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-sub-uplevel": { + "versions": { + "0.2800-r0": 1542883359 + }, + "origin": "perl-sub-uplevel", + "dependencies": [ + "perl" + ] + }, + "perl-x10-doc": { + "versions": { + "0.04-r1": 1545163612 + }, + "origin": "perl-x10" + }, + "cups-filters-libs": { + "versions": { + "1.21.6-r0": 1545820385 + }, + "origin": "cups-filters", + "dependencies": [ + "poppler-utils", + "bc", + "ttf-freefont", + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2", + "so:libdbus-1.so.3", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libtiff.so.5" + ], + "provides": [ + "so:libcupsfilters.so.1=1.0.0", + "so:libfontembed.so.1=1.0.0" + ] + }, + "wv": { + "versions": { + "1.2.9-r3": 1545300665 + }, + "origin": "wv", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgsf-1.so.114", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libwv-1.2.so.4=4.0.5", + "cmd:wvAbw", + "cmd:wvCleanLatex", + "cmd:wvConvert", + "cmd:wvDVI", + "cmd:wvDocBook", + "cmd:wvHtml", + "cmd:wvLatex", + "cmd:wvMime", + "cmd:wvPDF", + "cmd:wvPS", + "cmd:wvRTF", + "cmd:wvSummary", + "cmd:wvText", + "cmd:wvVersion", + "cmd:wvWare", + "cmd:wvWml" + ] + }, + "hunspell-lang": { + "versions": { + "1.6.2-r1": 1542883764 + }, + "origin": "hunspell" + }, + "rlog-dev": { + "versions": { + "1.4-r4": 1545061033 + }, + "origin": "rlog", + "dependencies": [ + "pkgconfig", + "rlog=1.4-r4" + ], + "provides": [ + "pc:librlog=1.4" + ] + }, + "snownews": { + "versions": { + "1.5.12-r8": 1543999424 + }, + "origin": "snownews", + "dependencies": [ + "ncurses", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:opml2snow", + "cmd:snow2opml", + "cmd:snownews" + ] + }, + "git-daemon": { + "versions": { + "2.20.1-r0": 1545214195 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0", + "so:libc.musl-x86_64.so.1", + "so:libpcre2-8.so.0", + "so:libz.so.1" + ] + }, + "exiv2-doc": { + "versions": { + "0.26-r0": 1543246769 + }, + "origin": "exiv2" + }, + "perl-xml-sax-base-doc": { + "versions": { + "1.09-r0": 1542893154 + }, + "origin": "perl-xml-sax-base" + }, + "perl-db": { + "versions": { + "0.55-r1": 1542985269 + }, + "origin": "perl-db", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so" + ] + }, + "lftp-doc": { + "versions": { + "4.8.4-r1": 1545073926 + }, + "origin": "lftp" + }, + "apr": { + "versions": { + "1.6.5-r0": 1544000273 + }, + "origin": "apr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libapr-1.so.0=0.6.5" + ] + }, + "postgresql-bdr-openrc": { + "versions": { + "9.4.14_p1-r1": 1543223165 + }, + "origin": "postgresql-bdr", + "dependencies": [ + "bash", + "libpq" + ] + }, + "gnu-efi-dev": { + "versions": { + "3.0.4-r1": 1543935645 + }, + "origin": "gnu-efi", + "dependencies": [ + "gnu-efi" + ] + }, + "s6-linux-init-doc": { + "versions": { + "0.4.0.0-r0": 1545292930 + }, + "origin": "s6-linux-init" + }, + "iperf3-doc": { + "versions": { + "3.6-r0": 1543932135 + }, + "origin": "iperf3" + }, + "libssh2-dbg": { + "versions": { + "1.8.2-r0": 1553681858 + }, + "origin": "libssh2" + }, + "giblib-dev": { + "versions": { + "1.2.4-r10": 1543226558 + }, + "origin": "giblib", + "dependencies": [ + "imlib2-dev", + "freetype-dev", + "zlib-dev", + "libx11-dev", + "libxext-dev", + "giblib=1.2.4-r10", + "pkgconfig" + ], + "provides": [ + "pc:giblib=1.2.4", + "cmd:giblib-config" + ] + }, + "libglade-doc": { + "versions": { + "2.6.4-r14": 1543927550 + }, + "origin": "libglade" + }, + "desktop-file-utils-doc": { + "versions": { + "0.23-r1": 1545116736 + }, + "origin": "desktop-file-utils" + }, + "flex": { + "versions": { + "2.6.4-r1": 1542300946 + }, + "origin": "flex", + "dependencies": [ + "m4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:flex", + "cmd:flex++", + "cmd:lex" + ] + }, + "unbound-dbg": { + "versions": { + "1.8.3-r1": 1555953584 + }, + "origin": "unbound", + "dependencies": [ + "dnssec-root" + ] + }, + "perl-css-minifier-xs-doc": { + "versions": { + "0.09-r3": 1545062654 + }, + "origin": "perl-css-minifier-xs" + }, + "libcap-dev": { + "versions": { + "2.26-r0": 1546585711 + }, + "origin": "libcap", + "dependencies": [ + "linux-headers", + "libcap=2.26-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcap=2.26" + ] + }, + "libmikmod": { + "versions": { + "3.3.11.1-r0": 1545162983 + }, + "origin": "libmikmod", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmikmod.so.3=3.3.0" + ] + }, + "nodejs": { + "versions": { + "10.14.2-r0": 1545345078 + }, + "origin": "nodejs", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libhttp_parser.so.2.8", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libuv.so.1", + "so:libz.so.1" + ], + "provides": [ + "nodejs-lts=10.14.2", + "cmd:node" + ] + }, + "gnome-doc-utils-lang": { + "versions": { + "0.20.10-r2": 1543254072 + }, + "origin": "gnome-doc-utils", + "dependencies": [ + "python2", + "docbook-xml", + "rarian", + "py2-libxml2", + "libxslt" + ] + }, + "mosquitto": { + "versions": { + "1.5.6-r0": 1549970968 + }, + "origin": "mosquitto", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libuuid.so.1", + "so:libwebsockets.so.14" + ], + "provides": [ + "cmd:mosquitto", + "cmd:mosquitto_passwd" + ] + }, + "mdocml-apropos": { + "versions": { + "1.14.3-r0": 1542304781 + }, + "origin": "mdocml", + "dependencies": [ + "/bin/sh", + "mdocml=1.14.3-r0" + ] + }, + "py-simplejson": { + "versions": { + "3.15.0-r0": 1543998767 + }, + "origin": "py-simplejson" + }, + "pacman-doc": { + "versions": { + "5.0.2-r4": 1543932339 + }, + "origin": "pacman" + }, + "perl-html-tree-doc": { + "versions": { + "5.07-r0": 1542893575 + }, + "origin": "perl-html-tree" + }, + "net-snmp-doc": { + "versions": { + "5.8-r0": 1543923349 + }, + "origin": "net-snmp" + }, + "perl-crypt-openssl-guess": { + "versions": { + "0.11-r0": 1543925838 + }, + "origin": "perl-crypt-openssl-guess" + }, + "scanelf": { + "versions": { + "1.2.3-r0": 1542304832 + }, + "origin": "pax-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:scanelf" + ] + }, + "cups-dev": { + "versions": { + "2.2.10-r0": 1545910846 + }, + "origin": "cups", + "dependencies": [ + "libgcrypt-dev", + "gnutls-dev", + "zlib-dev", + "cups-libs=2.2.10-r0" + ], + "provides": [ + "cmd:cups-config" + ] + }, + "zfs-scripts": { + "versions": { + "0.7.12-r1": 1552933679 + }, + "origin": "zfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libzfs.so.2" + ] + }, + "mcpp-doc": { + "versions": { + "2.7.2-r1": 1542883471 + }, + "origin": "mcpp" + }, + "gnokii-smsd-sqlite": { + "versions": { + "0.6.31-r8": 1545300480 + }, + "origin": "gnokii", + "dependencies": [ + "gnokii-smsd", + "so:libc.musl-x86_64.so.1" + ] + }, + "gross-dev": { + "versions": { + "1.0.2-r11": 1545062672 + }, + "origin": "gross" + }, + "libnotify-doc": { + "versions": { + "0.7.7-r2": 1544001120 + }, + "origin": "libnotify" + }, + "djbdns": { + "versions": { + "1.05-r47": 1545208951 + }, + "origin": "djbdns", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:axfr-get", + "cmd:axfrdns", + "cmd:axfrdns-conf", + "cmd:dnsfilter", + "cmd:dnsipq", + "cmd:dnsmx", + "cmd:dnsname", + "cmd:dnsq", + "cmd:dnstrace", + "cmd:dnstracesort", + "cmd:dnstxt", + "cmd:pickdns", + "cmd:pickdns-conf", + "cmd:pickdns-data", + "cmd:random-ip", + "cmd:rbldns", + "cmd:rbldns-conf", + "cmd:rbldns-data", + "cmd:walldns", + "cmd:walldns-conf" + ] + }, + "perl-class-accessor": { + "versions": { + "0.34-r0": 1543934265 + }, + "origin": "perl-class-accessor", + "dependencies": [ + "perl" + ] + }, + "libc-dev": { + "versions": { + "0.7.1-r0": 1542302755 + }, + "origin": "libc-dev", + "dependencies": [ + "musl-dev" + ] + }, + "perl-file-listing-doc": { + "versions": { + "6.04-r1": 1542821848 + }, + "origin": "perl-file-listing" + }, + "flashrom": { + "versions": { + "1.0-r3": 1543921877 + }, + "origin": "flashrom", + "dependencies": [ + "dmidecode", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libpci.so.3", + "so:libusb-0.1.so.4", + "so:libusb-1.0.so.0" + ], + "provides": [ + "cmd:flashrom" + ] + }, + "freeradius-sqlite": { + "versions": { + "3.0.17-r5": 1556201988 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius-sql=3.0.17-r5", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ], + "provides": [ + "freeradius3-sqlite=3.0.17-r5", + "so:rlm_sql_sqlite.so=0" + ] + }, + "mariadb-client": { + "versions": { + "10.3.13-r1": 1557431734 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common=10.3.13-r1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libncursesw.so.6", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:myisam_ftdump", + "cmd:mysql", + "cmd:mysql_find_rows", + "cmd:mysql_fix_extensions", + "cmd:mysql_waitpid", + "cmd:mysqlaccess", + "cmd:mysqladmin", + "cmd:mysqlcheck", + "cmd:mysqldump", + "cmd:mysqldumpslow", + "cmd:mysqlimport", + "cmd:mysqlshow" + ] + }, + "git": { + "versions": { + "2.20.1-r0": 1545214212 + }, + "origin": "git", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libexpat.so.1", + "so:libpcre2-8.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:git", + "cmd:git-receive-pack", + "cmd:git-shell", + "cmd:git-upload-archive", + "cmd:git-upload-pack" + ] + }, + "openntpd-doc": { + "versions": { + "6.2_p3-r2": 1543230955 + }, + "origin": "openntpd" + }, + "cairo-dbg": { + "versions": { + "1.16.0-r1": 1546948315 + }, + "origin": "cairo" + }, + "xev-doc": { + "versions": { + "1.2.2-r0": 1545301876 + }, + "origin": "xev" + }, + "less": { + "versions": { + "530-r0": 1542301312 + }, + "origin": "less", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:less", + "cmd:lessecho", + "cmd:lesskey" + ] + }, + "rsync": { + "versions": { + "3.1.3-r1": 1542820866 + }, + "origin": "rsync", + "dependencies": [ + "so:libacl.so.1", + "so:libattr.so.1", + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:rsync" + ] + }, + "nettle-utils": { + "versions": { + "3.4.1-r0": 1544791824 + }, + "origin": "nettle", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libhogweed.so.4", + "so:libnettle.so.6" + ], + "provides": [ + "cmd:nettle-hash", + "cmd:nettle-lfib-stream", + "cmd:nettle-pbkdf2", + "cmd:pkcs1-conv", + "cmd:sexp-conv" + ] + }, + "mc-doc": { + "versions": { + "4.8.22-r0": 1547126443 + }, + "origin": "mc" + }, + "poppler": { + "versions": { + "0.56.0-r1": 1542824320 + }, + "origin": "poppler", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libopenjp2.so.7", + "so:libpng16.so.16", + "so:libstdc++.so.6", + "so:libtiff.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libpoppler-cpp.so.0=0.3.0", + "so:libpoppler.so.67=67.0.0" + ] + }, + "redis": { + "versions": { + "4.0.12-r0": 1546005788 + }, + "origin": "redis", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:redis-benchmark", + "cmd:redis-check-aof", + "cmd:redis-check-rdb", + "cmd:redis-cli", + "cmd:redis-sentinel", + "cmd:redis-server" + ] + }, + "perl-test-requiresinternet-doc": { + "versions": { + "0.05-r1": 1542821874 + }, + "origin": "perl-test-requiresinternet" + }, + "postgresql-libs": { + "versions": { + "11.2-r0": 1554274176 + }, + "origin": "postgresql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "so:libecpg.so.6=6.11", + "so:libecpg_compat.so.3=3.11", + "so:libpgtypes.so.3=3.11" + ] + }, + "py-munkres": { + "versions": { + "1.0.12-r0": 1545292848 + }, + "origin": "py-munkres" + }, + "kamailio-unixodbc": { + "versions": { + "5.2.0-r1": 1546423170 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2", + "so:libsrdb1.so.1" + ] + }, + "font-vollkorn": { + "versions": { + "4.105-r0": 1545062649 + }, + "origin": "font-vollkorn" + }, + "py2-argparse": { + "versions": { + "1.4.0-r2": 1545060591 + }, + "origin": "py-argparse", + "dependencies": [ + "python2" + ] + }, + "boost-context": { + "versions": { + "1.67.0-r2": 1542823631 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libboost_context-mt.so.1.67.0=1.67.0" + ] + }, + "ansible": { + "versions": { + "2.7.0-r1": 1545223231 + }, + "origin": "ansible", + "dependencies": [ + "python3", + "py3-yaml", + "py3-paramiko", + "py3-jinja2", + "py3-markupsafe", + "py3-crypto" + ], + "provides": [ + "cmd:ansible", + "cmd:ansible-config", + "cmd:ansible-connection", + "cmd:ansible-console", + "cmd:ansible-doc", + "cmd:ansible-galaxy", + "cmd:ansible-inventory", + "cmd:ansible-playbook", + "cmd:ansible-pull", + "cmd:ansible-vault" + ] + }, + "libnice-doc": { + "versions": { + "0.1.14-r2": 1543928834 + }, + "origin": "libnice" + }, + "lua5.3-struct": { + "versions": { + "0.2-r2": 1545076455 + }, + "origin": "lua-struct", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libnetfilter_cttimeout": { + "versions": { + "1.0.0-r0": 1542883681 + }, + "origin": "libnetfilter_cttimeout", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0" + ], + "provides": [ + "so:libnetfilter_cttimeout.so.1=1.0.0" + ] + }, + "bluez": { + "versions": { + "5.50-r0": 1545822206 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:bluemoon", + "cmd:bluetoothctl", + "cmd:bluez-simple-agent", + "cmd:btattach", + "cmd:hex2hcd", + "cmd:l2ping", + "cmd:l2test", + "cmd:mpris-proxy", + "cmd:rctest" + ] + }, + "perl-probe-perl": { + "versions": { + "0.03-r0": 1545061093 + }, + "origin": "perl-probe-perl" + }, + "py-libxml2": { + "versions": { + "2.9.9-r1": 1551105514 + }, + "origin": "libxml2" + }, + "acf-tinydns": { + "versions": { + "0.11.0-r2": 1545214114 + }, + "origin": "acf-tinydns", + "dependencies": [ + "acf-core", + "tinydns" + ] + }, + "spamassassin-compiler": { + "versions": { + "3.4.2-r0": 1545061982 + }, + "origin": "spamassassin", + "dependencies": [ + "re2c", + "gcc", + "perl-dev", + "perl-mail-spamassassin" + ], + "provides": [ + "cmd:sa-compile" + ] + }, + "py3-pluggy": { + "versions": { + "0.7.1-r0": 1542824894 + }, + "origin": "py-pluggy", + "dependencies": [ + "python3" + ] + }, + "kmod-bash-completion": { + "versions": { + "24-r1": 1542845355 + }, + "origin": "kmod" + }, + "lua-xml": { + "versions": { + "130610-r5": 1543998790 + }, + "origin": "lua-xml" + }, + "a2ps-dev": { + "versions": { + "4.14-r7": 1545209604 + }, + "origin": "a2ps" + }, + "sparsehash": { + "versions": { + "2.0.3-r0": 1545249832 + }, + "origin": "sparsehash", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:libsparsehash=2.0.2" + ] + }, + "font-micro-misc": { + "versions": { + "1.0.3-r0": 1544000376 + }, + "origin": "font-micro-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "boost-python": { + "versions": { + "1.67.0-r2": 1542823633 + }, + "origin": "boost", + "dependencies": [ + "python", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "so:libboost_python27-mt.so.1.67.0=1.67.0", + "so:libboost_python27.so.1.67.0=1.67.0" + ] + }, + "jasper-dev": { + "versions": { + "2.0.14-r0": 1543932347 + }, + "origin": "jasper", + "dependencies": [ + "jasper-libs=2.0.14-r0", + "pkgconfig" + ], + "provides": [ + "pc:jasper=2.0.14" + ] + }, + "uwsgi-ping": { + "versions": { + "2.0.17.1-r0": 1545062204 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "sdl2_mixer": { + "versions": { + "2.0.4-r0": 1545208849 + }, + "origin": "sdl2_mixer", + "dependencies": [ + "so:libSDL2-2.0.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL2_mixer-2.0.so.0=0.2.2" + ] + }, + "openldap-back-sql": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2", + "so:libodbc.so.2" + ] + }, + "daq-dev": { + "versions": { + "2.0.6-r2": 1545163826 + }, + "origin": "daq", + "dependencies": [ + "daq=2.0.6-r2" + ], + "provides": [ + "cmd:daq-modules-config" + ] + }, + "lighttpd": { + "versions": { + "1.4.52-r0": 1545207630 + }, + "origin": "lighttpd", + "dependencies": [ + "/bin/sh", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libev.so.4", + "so:libfam.so.0", + "so:libldap-2.4.so.2", + "so:liblua-5.3.so.0", + "so:libpcre.so.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:lighttpd", + "cmd:lighttpd-angel" + ] + }, + "faad2": { + "versions": { + "2.7-r8": 1543998811 + }, + "origin": "faad2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfaad.so.2=2.0.0", + "cmd:faad" + ] + }, + "cryptsetup1-doc": { + "versions": { + "1.7.5-r4": 1545292765 + }, + "origin": "cryptsetup1" + }, + "libnih-doc": { + "versions": { + "1.0.3-r5": 1545062344 + }, + "origin": "libnih" + }, + "py-gnome": { + "versions": { + "2.28.1-r5": 1545301296 + }, + "origin": "py-gnome", + "dependencies": [ + "py-gnome-bonobo", + "py-gnome-gconf", + "py-gnome-libgnome", + "py-gnome-gnomevfs" + ] + }, + "collectd-apache": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "wayland-protocols": { + "versions": { + "1.17-r0": 1544791918 + }, + "origin": "wayland-protocols", + "provides": [ + "wayland-protocols-dev" + ] + }, + "acf-mdadm": { + "versions": { + "0.5.0-r2": 1545209219 + }, + "origin": "acf-mdadm", + "dependencies": [ + "acf-core", + "mdadm" + ] + }, + "lxc": { + "versions": { + "3.1.0-r1": 1549966162 + }, + "origin": "lxc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblxc.so.1" + ], + "provides": [ + "cmd:init.lxc", + "cmd:init.lxc.static", + "cmd:lxc-attach", + "cmd:lxc-autostart", + "cmd:lxc-cgroup", + "cmd:lxc-checkconfig", + "cmd:lxc-checkpoint", + "cmd:lxc-config", + "cmd:lxc-console", + "cmd:lxc-copy", + "cmd:lxc-create", + "cmd:lxc-destroy", + "cmd:lxc-device", + "cmd:lxc-execute", + "cmd:lxc-freeze", + "cmd:lxc-info", + "cmd:lxc-ls", + "cmd:lxc-monitor", + "cmd:lxc-snapshot", + "cmd:lxc-start", + "cmd:lxc-stop", + "cmd:lxc-top", + "cmd:lxc-unfreeze", + "cmd:lxc-unshare", + "cmd:lxc-update-config", + "cmd:lxc-usernsexec", + "cmd:lxc-wait" + ] + }, + "dovecot-dev": { + "versions": { + "2.3.6-r0": 1557134097 + }, + "origin": "dovecot" + }, + "hylafax": { + "versions": { + "6.0.7-r0": 1545746107 + }, + "origin": "hylafax", + "dependencies": [ + "ghostscript", + "bash", + "tiff-tools", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libstdc++.so.6", + "so:libtiff.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libhylafax-6.0.so.7=7", + "cmd:choptest", + "cmd:cqtest", + "cmd:dialtest", + "cmd:edit-faxcover", + "cmd:faxabort", + "cmd:faxaddmodem", + "cmd:faxadduser", + "cmd:faxalter", + "cmd:faxanswer", + "cmd:faxconfig", + "cmd:faxcover", + "cmd:faxcron", + "cmd:faxdeluser", + "cmd:faxgetty", + "cmd:faxinfo", + "cmd:faxlock", + "cmd:faxmail", + "cmd:faxmodem", + "cmd:faxmsg", + "cmd:faxq", + "cmd:faxqclean", + "cmd:faxquit", + "cmd:faxrm", + "cmd:faxsend", + "cmd:faxsetup", + "cmd:faxsetup.bsdi", + "cmd:faxsetup.irix", + "cmd:faxsetup.linux", + "cmd:faxstat", + "cmd:faxstate", + "cmd:faxwatch", + "cmd:hfaxd", + "cmd:hylafax", + "cmd:lockname", + "cmd:ondelay", + "cmd:pagesend", + "cmd:probemodem", + "cmd:recvstats", + "cmd:sendfax", + "cmd:sendpage", + "cmd:tagtest", + "cmd:textfmt", + "cmd:tiffcheck", + "cmd:tsitest", + "cmd:typetest", + "cmd:xferfaxstats" + ] + }, + "kmod-doc": { + "versions": { + "24-r1": 1542845353 + }, + "origin": "kmod" + }, + "perl-test-longstring": { + "versions": { + "0.17-r0": 1544000379 + }, + "origin": "perl-test-longstring", + "dependencies": [ + "perl" + ] + }, + "curl-dev": { + "versions": { + "7.64.0-r1": 1551205311 + }, + "origin": "curl", + "dependencies": [ + "openssl-dev", + "libssh2-dev", + "nghttp2-dev", + "zlib-dev", + "libcurl=7.64.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:libcurl=7.64.0", + "cmd:curl-config" + ] + }, + "xvfb": { + "versions": { + "1.20.3-r1": 1543928068 + }, + "origin": "xorg-server", + "dependencies": [ + "font-misc-misc", + "font-cursor-misc", + "xkeyboard-config", + "xkbcomp", + "xinit", + "so:libGL.so.1", + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpixman-1.so.0" + ], + "provides": [ + "cmd:Xvfb" + ] + }, + "py-tracing": { + "versions": { + "0.8-r1": 1545209173 + }, + "origin": "py-tracing", + "dependencies": [ + "python2" + ] + }, + "lua5.1-stdlib": { + "versions": { + "41.2.1-r0": 1545292923 + }, + "origin": "lua-stdlib" + }, + "perl-datetime-format-natural": { + "versions": { + "1.06-r0": 1545163113 + }, + "origin": "perl-datetime-format-natural", + "dependencies": [ + "perl-datetime-timezone", + "perl-clone", + "perl-params-validate", + "perl-list-moreutils", + "perl-datetime", + "perl-boolean" + ], + "provides": [ + "cmd:dateparse" + ] + }, + "sg3_utils-doc": { + "versions": { + "1.42-r1": 1544000667 + }, + "origin": "sg3_utils" + }, + "s6-linux-init": { + "versions": { + "0.4.0.0-r0": 1545292931 + }, + "origin": "s6-linux-init", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libskarnet.so.2.7" + ], + "provides": [ + "cmd:s6-halt", + "cmd:s6-linux-init-maker", + "cmd:s6-poweroff", + "cmd:s6-reboot" + ] + }, + "perl-fcgi-procmanager": { + "versions": { + "0.28-r0": 1543924484 + }, + "origin": "perl-fcgi-procmanager", + "dependencies": [ + "perl" + ] + }, + "ser2net": { + "versions": { + "3.4-r1": 1543999374 + }, + "origin": "ser2net", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ser2net" + ] + }, + "cogl": { + "versions": { + "1.22.2-r0": 1544000913 + }, + "origin": "cogl", + "dependencies": [ + "so:libEGL.so.1", + "so:libX11.so.6", + "so:libXdamage.so.1", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libdrm.so.2", + "so:libgbm.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0" + ], + "provides": [ + "so:libcogl-gles2.so.20=20.4.2", + "so:libcogl-pango.so.20=20.4.2", + "so:libcogl-path.so.20=20.4.2", + "so:libcogl.so.20=20.4.2" + ] + }, + "pigz-doc": { + "versions": { + "2.4-r0": 1542820956 + }, + "origin": "pigz" + }, + "rtapd": { + "versions": { + "1.7-r6": 1545301051 + }, + "origin": "rtapd", + "dependencies": [ + "rtnppd", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "cmd:rtapd", + "cmd:vsnppd" + ] + }, + "yajl": { + "versions": { + "2.1.0-r0": 1543935354 + }, + "origin": "yajl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libyajl.so.2=2.1.0" + ] + }, + "libxml2-dbg": { + "versions": { + "2.9.9-r1": 1551105514 + }, + "origin": "libxml2" + }, + "font-bh-lucidatypewriter-100dpi": { + "versions": { + "1.0.3-r0": 1545076403 + }, + "origin": "font-bh-lucidatypewriter-100dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-socket-doc": { + "versions": { + "2.027-r0": 1543922478 + }, + "origin": "perl-socket" + }, + "acf-awall": { + "versions": { + "0.4.1-r2": 1545300390 + }, + "origin": "acf-awall", + "dependencies": [ + "acf-core", + "awall" + ] + }, + "task": { + "versions": { + "2.5.1-r0": 1545254344 + }, + "origin": "task", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libstdc++.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:task" + ] + }, + "py3-babel": { + "versions": { + "2.6.0-r0": 1542824979 + }, + "origin": "py-babel", + "dependencies": [ + "py3-tz", + "python3" + ], + "provides": [ + "cmd:pybabel" + ] + }, + "xrdp": { + "versions": { + "0.9.9-r0": 1545859298 + }, + "origin": "xrdp", + "dependencies": [ + "so:libX11.so.6", + "so:libXfixes.so.3", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libfuse.so.2", + "so:libssl.so.1.1", + "so:libturbojpeg.so.0" + ], + "provides": [ + "so:libcommon.so.0=0.0.0", + "so:libmc.so=0", + "so:libpainter.so.0=0.0.0", + "so:librfxencode.so.0=0.0.0", + "so:libscp.so.0=0.0.0", + "so:libvnc.so=0", + "so:libxrdp.so.0=0.0.0", + "so:libxrdpapi.so.0=0.0.0", + "so:libxup.so=0", + "cmd:xrdp", + "cmd:xrdp-chansrv", + "cmd:xrdp-dis", + "cmd:xrdp-genkeymap", + "cmd:xrdp-keygen", + "cmd:xrdp-sesadmin", + "cmd:xrdp-sesman", + "cmd:xrdp-sesrun" + ] + }, + "pdnsd-doc": { + "versions": { + "1.2.9a-r5": 1543077303 + }, + "origin": "pdnsd" + }, + "lua5.3-lzlib": { + "versions": { + "0.4.3-r0": 1542845786 + }, + "origin": "lua-lzlib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ] + }, + "qemu-xtensaeb": { + "versions": { + "3.1.0-r3": 1551107306 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-xtensaeb" + ] + }, + "udisks-doc": { + "versions": { + "1.0.5-r3": 1545069134 + }, + "origin": "udisks" + }, + "wavpack": { + "versions": { + "5.1.0-r7": 1548939905 + }, + "origin": "wavpack", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libwavpack.so.1=1.2.0", + "cmd:wavpack", + "cmd:wvgain", + "cmd:wvtag", + "cmd:wvunpack" + ] + }, + "perl-extutils-cchecker-doc": { + "versions": { + "0.10-r0": 1542883381 + }, + "origin": "perl-extutils-cchecker" + }, + "perl-test-deep": { + "versions": { + "1.128-r0": 1542845599 + }, + "origin": "perl-test-deep", + "dependencies": [ + "perl", + "perl-test-tester", + "perl-test-nowarnings" + ] + }, + "imap": { + "versions": { + "2007f-r9": 1545062297 + }, + "origin": "imap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:imapd", + "cmd:ipop2d", + "cmd:ipop3d" + ] + }, + "xtrans-doc": { + "versions": { + "1.3.5-r1": 1542822665 + }, + "origin": "xtrans" + }, + "cython": { + "versions": { + "0.29.2-r0": 1545918551 + }, + "origin": "cython", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:cygdb", + "cmd:cython", + "cmd:cythonize" + ] + }, + "libmspack-utils": { + "versions": { + "0.8_alpha-r0": 1543925821 + }, + "origin": "libmspack", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmspack.so.0" + ], + "provides": [ + "cmd:cabrip", + "cmd:chmextract", + "cmd:msexpand", + "cmd:oabextract" + ] + }, + "libtirpc-doc": { + "versions": { + "1.0.3-r0": 1543223616 + }, + "origin": "libtirpc" + }, + "ruby-net-telnet": { + "versions": { + "2.5.5-r0": 1557164837 + }, + "origin": "ruby", + "dependencies": [ + "ruby-libs" + ] + }, + "nload": { + "versions": { + "0.7.4-r3": 1545214123 + }, + "origin": "nload", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libformw.so.6", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:nload" + ] + }, + "libdvdcss-dev": { + "versions": { + "1.4.2-r0": 1545838428 + }, + "origin": "libdvdcss", + "dependencies": [ + "libdvdcss=1.4.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libdvdcss=1.4.2" + ] + }, + "libmilter": { + "versions": { + "1.0.2-r6": 1543927485 + }, + "origin": "libmilter", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmilter.so.1.0.2=1.0.2" + ] + }, + "ruby-webrick": { + "versions": { + "2.5.5-r0": 1557164838 + }, + "origin": "ruby", + "dependencies": [ + "ruby" + ] + }, + "bash-completion": { + "versions": { + "2.8-r0": 1543935613 + }, + "origin": "bash-completion", + "dependencies": [ + "bash" + ] + }, + "boost-serialization": { + "versions": { + "1.67.0-r2": 1542823633 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_serialization-mt.so.1.67.0=1.67.0", + "so:libboost_serialization.so.1.67.0=1.67.0" + ] + }, + "xsetroot": { + "versions": { + "1.1.2-r0": 1545069361 + }, + "origin": "xsetroot", + "dependencies": [ + "so:libX11.so.6", + "so:libXcursor.so.1", + "so:libXmuu.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xsetroot" + ] + }, + "lua5.1-posixtz": { + "versions": { + "0.5-r1": 1543928315 + }, + "origin": "lua-posixtz", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "pax-utils-doc": { + "versions": { + "1.2.3-r0": 1542304831 + }, + "origin": "pax-utils" + }, + "cdw-doc": { + "versions": { + "0.8.1-r0": 1545302173 + }, + "origin": "cdw" + }, + "py3-httplib2": { + "versions": { + "0.12.0-r0": 1548112519 + }, + "origin": "py-httplib2", + "dependencies": [ + "python3" + ] + }, + "lua5.2-optarg": { + "versions": { + "0.2-r1": 1542820940 + }, + "origin": "lua-optarg", + "dependencies": [ + "lua5.2" + ] + }, + "libgsf": { + "versions": { + "1.14.44-r0": 1543926575 + }, + "origin": "libgsf", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libgsf-1.so.114=114.0.44", + "cmd:gsf", + "cmd:gsf-vba-dump" + ] + }, + "lua5.1-ldbus": { + "versions": { + "20150430-r2": 1545300019 + }, + "origin": "lua-ldbus", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3" + ] + }, + "luajit": { + "versions": { + "2.1.0_beta3-r4": 1542820929 + }, + "origin": "luajit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "lua", + "so:libluajit-5.1.so.2=2.1.0", + "cmd:luajit", + "cmd:luajit-2.1.0-beta3" + ] + }, + "xf86-video-s3virge": { + "versions": { + "1.10.7-r4": 1545069465 + }, + "origin": "xf86-video-s3virge", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "installkernel": { + "versions": { + "3.5-r0": 1543929099 + }, + "origin": "installkernel", + "provides": [ + "cmd:installkernel" + ] + }, + "gnupg1-doc": { + "versions": { + "1.4.23-r0": 1545302139 + }, + "origin": "gnupg1", + "provides": [ + "gnupg-doc=1.4.23-r0" + ] + }, + "lua5.2-cqueues": { + "versions": { + "20171014-r3": 1546520955 + }, + "origin": "lua-cqueues", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "bind-openrc": { + "versions": { + "9.12.4_p1-r1": 1556801608 + }, + "origin": "bind" + }, + "perl-net-telnet": { + "versions": { + "3.04-r0": 1543999441 + }, + "origin": "perl-net-telnet" + }, + "gdl-lang": { + "versions": { + "3.28.0-r0": 1545215938 + }, + "origin": "gdl" + }, + "lua5.3-expat": { + "versions": { + "1.3.0-r2": 1544000284 + }, + "origin": "lua-expat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ] + }, + "perl-future-doc": { + "versions": { + "0.39-r0": 1544799049 + }, + "origin": "perl-future" + }, + "mercurial": { + "versions": { + "4.9.1-r0": 1557160461 + }, + "origin": "mercurial", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:hg", + "cmd:hgeditor", + "cmd:hgk" + ] + }, + "ssmtp-doc": { + "versions": { + "2.64-r14": 1545215006 + }, + "origin": "ssmtp" + }, + "py2-cryptography": { + "versions": { + "2.4.2-r2": 1545067167 + }, + "origin": "py-cryptography", + "dependencies": [ + "py2-cffi", + "py2-idna", + "py2-asn1crypto", + "py2-six", + "py2-ipaddress", + "py-enum34", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpython2.7.so.1.0", + "so:libssl.so.1.1" + ] + }, + "gobject-introspection": { + "versions": { + "1.56.1-r0": 1542823016 + }, + "origin": "gobject-introspection", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libgirepository-1.0.so.1=1.0.0" + ] + }, + "netcat-openbsd": { + "versions": { + "1.130-r1": 1543935626 + }, + "origin": "netcat-openbsd", + "dependencies": [ + "so:libbsd.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:nc" + ] + }, + "libxaw": { + "versions": { + "1.0.13-r2": 1542894088 + }, + "origin": "libxaw", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXmu.so.6", + "so:libXpm.so.4", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXaw.so.7=7.0.0" + ] + }, + "perl-io-captureoutput": { + "versions": { + "1.1104-r0": 1543259614 + }, + "origin": "perl-io-captureoutput" + }, + "libotr3-tools": { + "versions": { + "3.2.1-r4": 1543230935 + }, + "origin": "libotr3", + "dependencies": [ + "libotr3", + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libotr.so.2" + ], + "provides": [ + "cmd:otr_mackey", + "cmd:otr_modify", + "cmd:otr_parse", + "cmd:otr_readforge", + "cmd:otr_remac", + "cmd:otr_sesskeys" + ] + }, + "xorg-cf-files": { + "versions": { + "1.0.6-r0": 1543927852 + }, + "origin": "xorg-cf-files" + }, + "wayland-libs-client": { + "versions": { + "1.16.0-r0": 1544001083 + }, + "origin": "wayland", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6" + ], + "provides": [ + "so:libwayland-client.so.0=0.3.0" + ] + }, + "icecast-openrc": { + "versions": { + "2.4.4-r1": 1543934499 + }, + "origin": "icecast" + }, + "freeradius": { + "versions": { + "3.0.17-r5": 1556201989 + }, + "origin": "freeradius", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libfreeradius-dhcp.so", + "so:libfreeradius-radius.so", + "so:libfreeradius-server.so", + "so:libgdbm.so.4", + "so:libpcap.so.1", + "so:libreadline.so.7", + "so:libssl.so.1.1", + "so:libtalloc.so.2" + ], + "provides": [ + "freeradius3=3.0.17-r5", + "so:proto_dhcp.so=0", + "so:proto_vmps.so=0", + "so:rlm_always.so=0", + "so:rlm_attr_filter.so=0", + "so:rlm_cache.so=0", + "so:rlm_cache_rbtree.so=0", + "so:rlm_chap.so=0", + "so:rlm_counter.so=0", + "so:rlm_cram.so=0", + "so:rlm_date.so=0", + "so:rlm_detail.so=0", + "so:rlm_dhcp.so=0", + "so:rlm_digest.so=0", + "so:rlm_dynamic_clients.so=0", + "so:rlm_exec.so=0", + "so:rlm_expiration.so=0", + "so:rlm_expr.so=0", + "so:rlm_files.so=0", + "so:rlm_ippool.so=0", + "so:rlm_linelog.so=0", + "so:rlm_logintime.so=0", + "so:rlm_mschap.so=0", + "so:rlm_otp.so=0", + "so:rlm_pap.so=0", + "so:rlm_passwd.so=0", + "so:rlm_preprocess.so=0", + "so:rlm_radutmp.so=0", + "so:rlm_realm.so=0", + "so:rlm_replicate.so=0", + "so:rlm_soh.so=0", + "so:rlm_sometimes.so=0", + "so:rlm_test.so=0", + "so:rlm_unix.so=0", + "so:rlm_unpack.so=0", + "so:rlm_utf8.so=0", + "so:rlm_wimax.so=0", + "cmd:dhcpclient", + "cmd:map_unit", + "cmd:rad_counter", + "cmd:radattr", + "cmd:radcrypt", + "cmd:raddebug", + "cmd:radiusd", + "cmd:radlast", + "cmd:radmin", + "cmd:radsniff", + "cmd:radsqlrelay", + "cmd:radtest", + "cmd:radwho", + "cmd:radzap", + "cmd:rlm_ippool_tool", + "cmd:smbencrypt" + ] + }, + "xl2tpd-doc": { + "versions": { + "1.3.10.1-r0": 1545300581 + }, + "origin": "xl2tpd" + }, + "perl-struct-dumb-doc": { + "versions": { + "0.09-r0": 1544799022 + }, + "origin": "perl-struct-dumb" + }, + "xmlto": { + "versions": { + "0.0.28-r2": 1542822500 + }, + "origin": "xmlto", + "dependencies": [ + "libxslt", + "perl-yaml-syck", + "perl-test-pod", + "bash", + "docbook-xsl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xmlif", + "cmd:xmlto" + ] + }, + "qpdf-dev": { + "versions": { + "8.3.0-r0": 1547117323 + }, + "origin": "qpdf", + "dependencies": [ + "pc:libjpeg", + "pc:zlib", + "pkgconfig", + "qpdf-libs=8.3.0-r0" + ], + "provides": [ + "pc:libqpdf=8.3.0" + ] + }, + "py-nose": { + "versions": { + "1.3.7-r2": 1544797228 + }, + "origin": "py-nose" + }, + "gtk+3.0-dev": { + "versions": { + "3.24.1-r0": 1543927434 + }, + "origin": "gtk+3.0", + "dependencies": [ + "at-spi2-atk-dev", + "atk-dev", + "cairo-dev", + "fontconfig-dev", + "gdk-pixbuf-dev", + "glib-dev", + "libepoxy-dev", + "libx11-dev", + "libxcomposite-dev", + "libxcursor-dev", + "libxdamage-dev", + "libxext-dev", + "libxfixes-dev", + "libxi-dev", + "libxinerama-dev", + "libxrandr-dev", + "pango-dev", + "wayland-protocols", + "wayland-libs-client", + "wayland-libs-cursor", + "wayland-dev", + "libxkbcommon-dev", + "gtk+3.0=3.24.1-r0", + "pc:atk", + "pc:atk-bridge-2.0", + "pc:atk>=2.15.1", + "pc:cairo", + "pc:cairo-gobject>=1.14.0", + "pc:cairo-xlib", + "pc:cairo>=1.14.0", + "pc:epoxy>=1.4", + "pc:fontconfig", + "pc:gdk-pixbuf-2.0>=2.30.0", + "pc:gio-2.0>=2.53.4", + "pc:gio-unix-2.0>=2.53.4", + "pc:pango", + "pc:pangocairo", + "pc:pangoft2", + "pc:wayland-client>=1.9.91", + "pc:wayland-cursor>=1.9.91", + "pc:wayland-egl", + "pc:x11", + "pc:xcomposite", + "pc:xcursor", + "pc:xdamage", + "pc:xext", + "pc:xfixes", + "pc:xi", + "pc:xinerama", + "pc:xkbcommon>=0.2.0", + "pc:xrandr", + "pkgconfig" + ], + "provides": [ + "pc:gail-3.0=3.24.1", + "pc:gdk-3.0=3.24.1", + "pc:gdk-wayland-3.0=3.24.1", + "pc:gdk-x11-3.0=3.24.1", + "pc:gtk+-3.0=3.24.1", + "pc:gtk+-unix-print-3.0=3.24.1", + "pc:gtk+-wayland-3.0=3.24.1", + "pc:gtk+-x11-3.0=3.24.1" + ] + }, + "perl-cache-simple-timedexpiry-doc": { + "versions": { + "0.27-r1": 1544798624 + }, + "origin": "perl-cache-simple-timedexpiry" + }, + "at-spi2-atk": { + "versions": { + "2.26.2-r0": 1543926534 + }, + "origin": "at-spi2-atk", + "dependencies": [ + "so:libatk-1.0.so.0", + "so:libatspi.so.0", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libatk-bridge-2.0.so.0=0.0.0" + ] + }, + "tree-doc": { + "versions": { + "1.8.0-r0": 1544791740 + }, + "origin": "tree" + }, + "perl-test-number-delta-doc": { + "versions": { + "1.06-r0": 1545062324 + }, + "origin": "perl-test-number-delta" + }, + "highlight-doc": { + "versions": { + "3.46-r0": 1542823672 + }, + "origin": "highlight" + }, + "py-phonenumbers": { + "versions": { + "8.10.4-r0": 1548490639 + }, + "origin": "py-phonenumbers" + }, + "gradm-doc": { + "versions": { + "3.1.201607172312-r0": 1545299889 + }, + "origin": "gradm" + }, + "perl-xml-parser": { + "versions": { + "2.44-r4": 1542821912 + }, + "origin": "perl-xml-parser", + "dependencies": [ + "perl-libwww", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ] + }, + "postfix-ldap": { + "versions": { + "3.3.2-r0": 1547130362 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ], + "provides": [ + "so:postfix-ldap.so=0" + ] + }, + "py-ediarpc": { + "versions": { + "0.3-r0": 1545214310 + }, + "origin": "py-ediarpc", + "dependencies": [ + "python2" + ] + }, + "libwebp-doc": { + "versions": { + "1.0.1-r0": 1545856968 + }, + "origin": "libwebp" + }, + "adwaita-icon-theme-dev": { + "versions": { + "3.31.1-r0": 1545820342 + }, + "origin": "adwaita-icon-theme", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:adwaita-icon-theme=3.31.1" + ] + }, + "java-common": { + "versions": { + "0.1-r0": 1545076633 + }, + "origin": "java-common", + "dependencies": [ + "/bin/sh" + ] + }, + "p7zip-doc": { + "versions": { + "16.02-r3": 1545118129 + }, + "origin": "p7zip" + }, + "sylpheed": { + "versions": { + "3.7.0-r2": 1544799190 + }, + "origin": "sylpheed", + "dependencies": [ + "pinentry-gtk", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcrypto.so.1.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgpgme.so.11", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libsylph-0.so.1=1.3.0", + "so:libsylpheed-plugin-0.so.1=1.3.0", + "cmd:sylpheed" + ] + }, + "newt-dev": { + "versions": { + "0.52.20-r0": 1543924699 + }, + "origin": "newt", + "dependencies": [ + "newt=0.52.20-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnewt=0.52.20" + ] + }, + "perl-mime-lite-doc": { + "versions": { + "3.030-r1": 1543226638 + }, + "origin": "perl-mime-lite" + }, + "vala-doc": { + "versions": { + "0.42.4-r0": 1545859420 + }, + "origin": "vala" + }, + "mariadb": { + "versions": { + "10.3.13-r1": 1557431745 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common", + "/bin/sh", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:liblzma.so.5", + "so:libncursesw.so.6", + "so:libpcre.so.1", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:aria_chk", + "cmd:aria_dump_log", + "cmd:aria_ftdump", + "cmd:aria_pack", + "cmd:aria_read_log", + "cmd:innochecksum", + "cmd:msql2mysql", + "cmd:my_print_defaults", + "cmd:myisamchk", + "cmd:myisamlog", + "cmd:myisampack", + "cmd:myrocks_hotbackup", + "cmd:mysql_client_test_embedded", + "cmd:mysql_embedded", + "cmd:mysql_install_db", + "cmd:mysql_ldb", + "cmd:mysql_plugin", + "cmd:mysql_secure_installation", + "cmd:mysql_tzinfo_to_sql", + "cmd:mysqlbinlog", + "cmd:mysqld", + "cmd:mysqld_safe", + "cmd:mysqlslap", + "cmd:replace", + "cmd:resolve_stack_dump", + "cmd:resolveip", + "cmd:sst_dump", + "cmd:test-connect-t", + "cmd:wsrep_sst_mariabackup", + "cmd:wsrep_sst_mysqldump", + "cmd:wsrep_sst_rsync", + "cmd:wsrep_sst_rsync_wan" + ] + }, + "rxvt-unicode-terminfo": { + "versions": { + "9.22-r4": 1543254026 + }, + "origin": "rxvt-unicode" + }, + "mdadm": { + "versions": { + "4.1-r0": 1545858163 + }, + "origin": "mdadm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mdadm", + "cmd:mdmon" + ] + }, + "bind-dev": { + "versions": { + "9.12.4_p1-r1": 1556801608 + }, + "origin": "bind", + "dependencies": [ + "bind-libs=9.12.4_p1-r1" + ], + "provides": [ + "cmd:bind9-config" + ] + }, + "libressl2.7-libssl": { + "versions": { + "2.7.5-r0": 1551116832 + }, + "origin": "libressl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.43" + ], + "provides": [ + "so:libssl.so.45=45.0.1" + ] + }, + "cups-client": { + "versions": { + "2.2.10-r0": 1545910846 + }, + "origin": "cups", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2", + "so:libcupsimage.so.2", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:accept", + "cmd:cancel", + "cmd:cupsaccept", + "cmd:cupsaddsmb", + "cmd:cupsctl", + "cmd:cupsdisable", + "cmd:cupsenable", + "cmd:cupstestdsc", + "cmd:cupstestppd", + "cmd:ippfind", + "cmd:lp", + "cmd:lpadmin", + "cmd:lpc", + "cmd:lpinfo", + "cmd:lpmove", + "cmd:lpoptions", + "cmd:lpq", + "cmd:lpr", + "cmd:lprm", + "cmd:lpstat", + "cmd:ppdc", + "cmd:ppdhtml", + "cmd:ppdi", + "cmd:ppdmerge", + "cmd:ppdpo", + "cmd:reject" + ] + }, + "qemu-armeb": { + "versions": { + "3.1.0-r3": 1551107300 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-armeb" + ] + }, + "cryptsetup1-libs": { + "versions": { + "1.7.5-r4": 1545292766 + }, + "origin": "cryptsetup1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libdevmapper.so.1.02", + "so:libuuid.so.1" + ], + "provides": [ + "so:libcryptsetup.so.4=4.7.0" + ] + }, + "libgphoto2": { + "versions": { + "2.5.16-r0": 1545116802 + }, + "origin": "libgphoto2", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libexif.so.12", + "so:libjpeg.so.8", + "so:libltdl.so.7", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libgphoto2.so.6=6.0.0", + "so:libgphoto2_port.so.12=12.0.0" + ] + }, + "freeradius-dbg": { + "versions": { + "3.0.17-r5": 1556201987 + }, + "origin": "freeradius" + }, + "libdvbcsa-dev": { + "versions": { + "1.1.0-r1": 1545293164 + }, + "origin": "libdvbcsa", + "dependencies": [ + "libdvbcsa=1.1.0-r1" + ] + }, + "lua5.3-dbi-mysql": { + "versions": { + "0.6-r3": 1545214224 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "libusb-dev": { + "versions": { + "1.0.22-r0": 1543223278 + }, + "origin": "libusb", + "dependencies": [ + "libusb=1.0.22-r0", + "pkgconfig" + ], + "provides": [ + "pc:libusb-1.0=1.0.22" + ] + }, + "poppler-qt4": { + "versions": { + "0.56.0-r0": 1545254184 + }, + "origin": "poppler-qt4", + "dependencies": [ + "so:libQtCore.so.4", + "so:libQtGui.so.4", + "so:libQtXml.so.4", + "so:libc.musl-x86_64.so.1", + "so:libpoppler.so.67", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libpoppler-qt4.so.4=4.11.0" + ] + }, + "perl-crypt-openssl-rsa-doc": { + "versions": { + "0.31-r1": 1545061168 + }, + "origin": "perl-crypt-openssl-rsa" + }, + "execline": { + "versions": { + "2.5.0.1-r0": 1543221686 + }, + "origin": "execline", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libskarnet.so.2.7" + ], + "provides": [ + "so:libexecline.so.2.5=2.5.0.1", + "cmd:background", + "cmd:backtick", + "cmd:cd", + "cmd:define", + "cmd:dollarat", + "cmd:elgetopt", + "cmd:elgetpositionals", + "cmd:elglob", + "cmd:emptyenv", + "cmd:exec", + "cmd:execlineb", + "cmd:exit", + "cmd:export", + "cmd:fdblock", + "cmd:fdclose", + "cmd:fdmove", + "cmd:fdreserve", + "cmd:fdswap", + "cmd:forbacktickx", + "cmd:foreground", + "cmd:forstdin", + "cmd:forx", + "cmd:getcwd", + "cmd:getpid", + "cmd:heredoc", + "cmd:homeof", + "cmd:if", + "cmd:ifelse", + "cmd:ifte", + "cmd:ifthenelse", + "cmd:importas", + "cmd:loopwhilex", + "cmd:multidefine", + "cmd:multisubstitute", + "cmd:pipeline", + "cmd:piperw", + "cmd:redirfd", + "cmd:runblock", + "cmd:shift", + "cmd:trap", + "cmd:tryexec", + "cmd:umask", + "cmd:unexport", + "cmd:wait", + "cmd:withstdinas" + ] + }, + "libotr3-dev": { + "versions": { + "3.2.1-r4": 1543230934 + }, + "origin": "libotr3", + "dependencies": [ + "libgcrypt-dev", + "libotr3=3.2.1-r4", + "pkgconfig" + ], + "provides": [ + "pc:libotr=3.1.0" + ] + }, + "x264-dev": { + "versions": { + "20180304-r1": 1545299801 + }, + "origin": "x264", + "dependencies": [ + "pkgconfig", + "x264-libs=20180304-r1" + ], + "provides": [ + "pc:x264=0.152.x" + ] + }, + "xf86-video-glint": { + "versions": { + "1.2.9-r3": 1545300614 + }, + "origin": "xf86-video-glint", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-doc": { + "versions": { + "5.26.3-r0": 1543998672 + }, + "origin": "perl" + }, + "antiword": { + "versions": { + "0.37-r3": 1545163687 + }, + "origin": "antiword", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:antiword" + ] + }, + "gtksourceview2-dev": { + "versions": { + "2.10.5-r0": 1545292663 + }, + "origin": "gtksourceview2", + "dependencies": [ + "gtk+2.0-dev", + "libxml2-dev", + "gtksourceview2=2.10.5-r0", + "pc:gtk+-2.0>=2.12.0", + "pc:libxml-2.0>=2.5.0", + "pkgconfig" + ], + "provides": [ + "pc:gtksourceview-2.0=2.10.5" + ] + }, + "skalibs": { + "versions": { + "2.7.0.0-r0": 1543221683 + }, + "origin": "skalibs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libskarnet.so.2.7=2.7.0.0" + ] + }, + "zsh": { + "versions": { + "5.6.2-r0": 1545308088 + }, + "origin": "zsh", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:zsh", + "cmd:zsh-5.6.2" + ] + }, + "perl-html-quoted": { + "versions": { + "0.04-r0": 1545214098 + }, + "origin": "perl-html-quoted", + "dependencies": [ + "perl", + "perl-html-parser" + ] + }, + "kamailio-authephemeral": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ] + }, + "openldap-overlay-rwm": { + "versions": { + "2.4.47-r2": 1546016480 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "freeradius-sql": { + "versions": { + "3.0.17-r5": 1556201988 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.17-r5", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "freeradius3-sql=3.0.17-r5", + "so:rlm_sql.so=0", + "so:rlm_sql_null.so=0", + "so:rlm_sqlcounter.so=0", + "so:rlm_sqlippool.so=0" + ] + }, + "perl-test-without-doc": { + "versions": { + "0.10-r0": 1545223239 + }, + "origin": "perl-test-without" + }, + "libcom_err": { + "versions": { + "1.44.5-r0": 1545745858 + }, + "origin": "e2fsprogs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcom_err.so.2=2.1" + ] + }, + "py2-pathlib2": { + "versions": { + "2.3.2-r0": 1542824911 + }, + "origin": "py-pathlib2", + "dependencies": [ + "py2-six", + "py2-scandir", + "python2" + ] + }, + "py-babel": { + "versions": { + "2.6.0-r0": 1542824980 + }, + "origin": "py-babel", + "dependencies": [ + "py-tz" + ] + }, + "bacula-client": { + "versions": { + "9.4.1-r1": 1546944087 + }, + "origin": "bacula", + "dependencies": [ + "so:libacl.so.1", + "so:libbac-9.4.1.so", + "so:libbaccfg-9.4.1.so", + "so:libbacfind-9.4.1.so", + "so:libc.musl-x86_64.so.1", + "so:liblzo2.so.2", + "so:libncursesw.so.6", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:bacula", + "cmd:bacula-fd", + "cmd:bconsole", + "cmd:btraceback" + ] + }, + "s6-linux-utils": { + "versions": { + "2.5.0.0-r0": 1545299630 + }, + "origin": "s6-linux-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libskarnet.so.2.7" + ], + "provides": [ + "cmd:s6-chroot", + "cmd:s6-fillurandompool", + "cmd:s6-freeramdisk", + "cmd:s6-hostname", + "cmd:s6-logwatch", + "cmd:s6-mount", + "cmd:s6-pivotchroot", + "cmd:s6-ps", + "cmd:s6-swapoff", + "cmd:s6-swapon", + "cmd:s6-umount" + ] + }, + "libobjc": { + "versions": { + "8.3.0-r0": 1554745498 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libobjc.so.4=4.0.0" + ] + }, + "dnscache": { + "versions": { + "1.05-r47": 1545208950 + }, + "origin": "djbdns", + "dependencies": [ + "djbdns-common", + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dnscache", + "cmd:dnscache-conf" + ] + }, + "shared-mime-info-lang": { + "versions": { + "1.10-r0": 1542821941 + }, + "origin": "shared-mime-info" + }, + "fuse": { + "versions": { + "2.9.8-r2": 1542972242 + }, + "origin": "fuse", + "dependencies": [ + "fuse-common", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfuse.so.2=2.9.8", + "so:libulockmgr.so.1=1.0.1", + "cmd:fusermount", + "cmd:mount.fuse", + "cmd:ulockmgr_server" + ] + }, + "nginx-doc": { + "versions": { + "1.14.2-r1": 1557179819 + }, + "origin": "nginx" + }, + "perl-net-smtp-ssl": { + "versions": { + "1.04-r0": 1545209114 + }, + "origin": "perl-net-smtp-ssl", + "dependencies": [ + "perl-io-socket-ssl", + "perl-net-ssleay" + ] + }, + "py3-cairo": { + "versions": { + "1.16.3-r0": 1543925201 + }, + "origin": "py-cairo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libpython3.6m.so.1.0" + ] + }, + "vte3-dev": { + "versions": { + "0.52.2-r0": 1545215297 + }, + "origin": "vte3", + "dependencies": [ + "pc:gio-2.0", + "pc:gio-unix-2.0", + "pc:glib-2.0>=2.40.0", + "pc:gnutls>=3.2.7", + "pc:gobject-2.0", + "pc:gtk+-3.0>=3.8.0", + "pc:libpcre2-8>=10.21", + "pc:pango>=1.22.0", + "pc:zlib", + "pkgconfig", + "vte3=0.52.2-r0" + ], + "provides": [ + "pc:vte-2.91=0.52.2" + ] + }, + "pam-pgsql-doc": { + "versions": { + "0.7.3.2-r0": 1545068588 + }, + "origin": "pam-pgsql" + }, + "gapk": { + "versions": { + "0.1-r1": 1545223041 + }, + "origin": "gapk", + "dependencies": [ + "apk-tools", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0" + ], + "provides": [ + "cmd:gapk" + ] + }, + "perl-cpanel-json-xs": { + "versions": { + "4.08-r0": 1544792332 + }, + "origin": "perl-cpanel-json-xs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cpanel_json_xs" + ] + }, + "libsndfile": { + "versions": { + "1.0.28-r8": 1555066604 + }, + "origin": "libsndfile", + "dependencies": [ + "so:libFLAC.so.8", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2" + ], + "provides": [ + "so:libsndfile.so.1=1.0.28", + "cmd:sndfile-cmp", + "cmd:sndfile-concat", + "cmd:sndfile-convert", + "cmd:sndfile-deinterleave", + "cmd:sndfile-info", + "cmd:sndfile-interleave", + "cmd:sndfile-metadata-get", + "cmd:sndfile-metadata-set", + "cmd:sndfile-play", + "cmd:sndfile-salvage" + ] + }, + "py2-wtforms": { + "versions": { + "2.1-r0": 1544000620 + }, + "origin": "py-wtforms", + "dependencies": [ + "python2" + ] + }, + "perl-data-uuid-doc": { + "versions": { + "1.221-r1": 1545163353 + }, + "origin": "perl-data-uuid" + }, + "perl-ldap-doc": { + "versions": { + "0.65-r1": 1543081710 + }, + "origin": "perl-ldap" + }, + "perl-class-data-inheritable": { + "versions": { + "0.08-r0": 1542845803 + }, + "origin": "perl-class-data-inheritable" + }, + "perl-socket-getaddrinfo-doc": { + "versions": { + "0.22-r0": 1542883394 + }, + "origin": "perl-socket-getaddrinfo" + }, + "uwsgi-emperor_pg": { + "versions": { + "2.0.17.1-r0": 1545062199 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "perl-class-load": { + "versions": { + "0.25-r0": 1543226708 + }, + "origin": "perl-class-load", + "dependencies": [ + "perl-data-optlist", + "perl-module-runtime", + "perl-module-implementation", + "perl-try-tiny", + "perl-namespace-clean", + "perl-package-stash" + ] + }, + "collectd-utils": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcollectdclient.so.1" + ], + "provides": [ + "cmd:collectd-nagios", + "cmd:collectd-tg", + "cmd:collectdctl" + ] + }, + "apache-mod-auth-kerb": { + "versions": { + "5.4-r5": 1545076830 + }, + "origin": "apache-mod-auth-kerb", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libgssapi_krb5.so.2", + "so:libkrb5.so.3" + ] + }, + "iwlwifi-5000-ucode": { + "versions": { + "8.83.5.1-r0": 1542845814 + }, + "origin": "iwlwifi-5000-ucode" + }, + "ragel-doc": { + "versions": { + "6.10-r0": 1545208962 + }, + "origin": "ragel" + }, + "sg3_utils-dev": { + "versions": { + "1.42-r1": 1544000665 + }, + "origin": "sg3_utils", + "dependencies": [ + "sg3_utils=1.42-r1" + ] + }, + "cciss_vol_status-doc": { + "versions": { + "1.12-r0": 1545073934 + }, + "origin": "cciss_vol_status" + }, + "audit-libs": { + "versions": { + "2.8.4-r0": 1543245856 + }, + "origin": "audit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0" + ], + "provides": [ + "so:libaudit.so.1=1.0.0", + "so:libauparse.so.0=0.0.0" + ] + }, + "git-svn": { + "versions": { + "2.20.1-r0": 1545214194 + }, + "origin": "git", + "dependencies": [ + "perl", + "perl-git-svn=2.20.1-r0", + "perl-subversion", + "perl-term-readkey", + "so:libc.musl-x86_64.so.1", + "so:libpcre2-8.so.0", + "so:libz.so.1" + ] + }, + "lua5.3-openrc": { + "versions": { + "0.2-r3": 1545066957 + }, + "origin": "lua-openrc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librc.so.1" + ] + }, + "irssi-dev": { + "versions": { + "1.1.2-r0": 1547738040 + }, + "origin": "irssi" + }, + "transmission-doc": { + "versions": { + "2.94-r1": 1545208749 + }, + "origin": "transmission" + }, + "kbd": { + "versions": { + "2.0.4-r3": 1545215192 + }, + "origin": "kbd", + "dependencies": [ + "kbd-misc", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:chvt", + "cmd:deallocvt", + "cmd:dumpkeys", + "cmd:fgconsole", + "cmd:getkeycodes", + "cmd:kbd_mode", + "cmd:kbdinfo", + "cmd:kbdrate", + "cmd:loadkeys", + "cmd:loadunimap", + "cmd:mapscrn", + "cmd:open", + "cmd:openvt", + "cmd:psfaddtable", + "cmd:psfgettable", + "cmd:psfstriptable", + "cmd:psfxtable", + "cmd:resizecons", + "cmd:setfont", + "cmd:setkeycodes", + "cmd:setleds", + "cmd:setmetamode", + "cmd:setvtrgb", + "cmd:showconsolefont", + "cmd:showkey", + "cmd:unicode_start", + "cmd:unicode_stop" + ] + }, + "libxrandr-dev": { + "versions": { + "1.5.1-r2": 1542972233 + }, + "origin": "libxrandr", + "dependencies": [ + "libxext-dev", + "libxrandr=1.5.1-r2", + "pc:randrproto>=1.5", + "pc:x11", + "pc:xext", + "pc:xproto", + "pc:xrender", + "pkgconfig" + ], + "provides": [ + "pc:xrandr=1.5.1" + ] + }, + "perl-class-method-modifiers": { + "versions": { + "2.12-r0": 1545073307 + }, + "origin": "perl-class-method-modifiers" + }, + "libavc1394-dev": { + "versions": { + "0.5.4-r2": 1543932110 + }, + "origin": "libavc1394", + "dependencies": [ + "libavc1394=0.5.4-r2", + "pc:libraw1394", + "pkgconfig" + ], + "provides": [ + "pc:libavc1394=0.5.4" + ] + }, + "libtls-standalone-doc": { + "versions": { + "2.7.4-r6": 1546784623 + }, + "origin": "libtls-standalone" + }, + "xbanish-doc": { + "versions": { + "1.6-r0": 1545290830 + }, + "origin": "xbanish" + }, + "perl-test-failwarnings": { + "versions": { + "0.008-r1": 1543238918 + }, + "origin": "perl-test-failwarnings" + }, + "perl-module-implementation-doc": { + "versions": { + "0.09-r0": 1542845723 + }, + "origin": "perl-module-implementation" + }, + "libsmbclient": { + "versions": { + "4.8.11-r1": 1555334973 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcli-smb-common-samba4.so", + "so:libdcerpc-samba-samba4.so", + "so:libgse-samba4.so", + "so:liblibcli-lsa3-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmsrpc3-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-security-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsecrets3-samba4.so", + "so:libsmbconf.so.0", + "so:libtalloc.so.2", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-cmdline-samba4.so" + ], + "provides": [ + "so:libsmbclient.so.0=0.3.1" + ] + }, + "asterisk-srtp": { + "versions": { + "15.7.1-r0": 1546247586 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ] + }, + "ntop-dev": { + "versions": { + "5.0.1-r14": 1545209735 + }, + "origin": "ntop", + "dependencies": [ + "ntop=5.0.1-r14" + ] + }, + "pixman-dbg": { + "versions": { + "0.34.0-r6": 1542822838 + }, + "origin": "pixman" + }, + "asterisk-speex": { + "versions": { + "15.7.1-r0": 1546247586 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0", + "so:libspeex.so.1", + "so:libspeexdsp.so.1" + ] + }, + "perl-scalar-list-utils-doc": { + "versions": { + "1.50-r0": 1543238901 + }, + "origin": "perl-scalar-list-utils" + }, + "qextserialport-dev": { + "versions": { + "1.2_rc1-r0": 1545300093 + }, + "origin": "qextserialport", + "dependencies": [ + "qt-dev", + "qextserialport=1.2_rc1-r0" + ] + }, + "qemu-mipsel": { + "versions": { + "3.1.0-r3": 1551107301 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mipsel" + ] + }, + "gphoto2-doc": { + "versions": { + "2.5.15-r0": 1545300679 + }, + "origin": "gphoto2" + }, + "libconfig-doc": { + "versions": { + "1.5-r3": 1543249698 + }, + "origin": "libconfig" + }, + "libxslt-dev": { + "versions": { + "1.1.33-r1": 1555485887 + }, + "origin": "libxslt", + "dependencies": [ + "libxslt=1.1.33-r1", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libexslt=0.8.20", + "pc:libxslt=1.1.33", + "cmd:xslt-config" + ] + }, + "collectd-lua": { + "versions": { + "5.8.0-r3": 1546422678 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ] + }, + "squid-lang-et": { + "versions": { + "4.4-r1": 1545216325 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-crypt-des": { + "versions": { + "2.07-r4": 1543923065 + }, + "origin": "perl-crypt-des", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.1-sql-mysql": { + "versions": { + "2.3.5-r2": 1543924396 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "iwlwifi-6000-ucode-doc": { + "versions": { + "9.221.4.1-r0": 1545256915 + }, + "origin": "iwlwifi-6000-ucode" + }, + "ca-certificates-doc": { + "versions": { + "20190108-r0": 1548779239 + }, + "origin": "ca-certificates" + }, + "collectd-ascent": { + "versions": { + "5.8.0-r3": 1546422677 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libxml2.so.2" + ] + }, + "spice-server": { + "versions": { + "0.14.1-r6": 1548919556 + }, + "origin": "spice", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcelt051.so.0", + "so:libcrypto.so.1.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libjpeg.so.8", + "so:libopus.so.0", + "so:libpixman-1.so.0", + "so:libsasl2.so.3", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:libspice-server.so.1=1.12.5" + ] + }, + "tcpdump": { + "versions": { + "4.9.2-r4": 1545300929 + }, + "origin": "tcpdump", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:tcpdump" + ] + }, + "encfs": { + "versions": { + "1.9.5-r3": 1545061083 + }, + "origin": "encfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libfuse.so.2", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libtinyxml2.so.7" + ], + "provides": [ + "so:libencfs.so.1.9=1.9.5", + "cmd:encfs", + "cmd:encfsctl", + "cmd:encfssh" + ] + }, + "xrdp-openrc": { + "versions": { + "0.9.9-r0": 1545859298 + }, + "origin": "xrdp" + }, + "multipath-tools": { + "versions": { + "0.7.9-r0": 1545224103 + }, + "origin": "multipath-tools", + "dependencies": [ + "eudev", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02", + "so:libjson-c.so.4", + "so:libreadline.so.7", + "so:libudev.so.1", + "so:liburcu.so.6" + ], + "provides": [ + "so:libdmmp.so.0.2.0=0.2.0", + "so:libmpathcmd.so.0=0", + "so:libmpathpersist.so.0=0", + "so:libmultipath.so.0=0", + "cmd:kpartx", + "cmd:mpathpersist", + "cmd:multipath", + "cmd:multipathd" + ] + }, + "libvorbis-dev": { + "versions": { + "1.3.6-r2": 1549615841 + }, + "origin": "libvorbis", + "dependencies": [ + "libvorbis=1.3.6-r2", + "pc:ogg", + "pkgconfig" + ], + "provides": [ + "pc:vorbis=1.3.6", + "pc:vorbisenc=1.3.6", + "pc:vorbisfile=1.3.6" + ] + }, + "libconfig-dev": { + "versions": { + "1.5-r3": 1543249699 + }, + "origin": "libconfig", + "dependencies": [ + "libconfig++=1.5-r3", + "libconfig=1.5-r3", + "pkgconfig" + ], + "provides": [ + "pc:libconfig++=1.5", + "pc:libconfig=1.5" + ] + }, + "curl-doc": { + "versions": { + "7.64.0-r1": 1551205311 + }, + "origin": "curl" + }, + "perl-xml-parser-doc": { + "versions": { + "2.44-r4": 1542821909 + }, + "origin": "perl-xml-parser" + }, + "dejagnu-dev": { + "versions": { + "1.6.2-r0": 1545820393 + }, + "origin": "dejagnu" + }, + "libsrtp-dev": { + "versions": { + "1.5.4-r1": 1544000953 + }, + "origin": "libsrtp", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:libsrtp=1.5.4" + ] + }, + "giblib-doc": { + "versions": { + "1.2.4-r10": 1543226558 + }, + "origin": "giblib" + }, + "graphite2-dev": { + "versions": { + "1.3.12-r1": 1542823993 + }, + "origin": "graphite2", + "dependencies": [ + "freetype-dev", + "graphite2=1.3.12-r1", + "pkgconfig" + ], + "provides": [ + "pc:graphite2=3.0.1" + ] + }, + "libid3tag-dev": { + "versions": { + "0.15.1b-r7": 1542972923 + }, + "origin": "libid3tag", + "dependencies": [ + "libid3tag=0.15.1b-r7", + "pkgconfig" + ], + "provides": [ + "pc:id3tag=0.15.1b" + ] + }, + "mcookie": { + "versions": { + "2.33-r0": 1545307437 + }, + "origin": "util-linux", + "dependencies": [ + "findmnt", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mcookie" + ] + }, + "gpsd": { + "versions": { + "3.18.1-r1": 1549881588 + }, + "origin": "gpsd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgps.so.24=24.0.0", + "cmd:gpsd", + "cmd:gpsdctl" + ] + }, + "llvm5-dev": { + "versions": { + "5.0.2-r0": 1546874980 + }, + "origin": "llvm5", + "dependencies": [ + "llvm5=5.0.2-r0", + "llvm5-libs=5.0.2-r0", + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "llvm-dev=5.0.2-r0", + "cmd:llvm-config" + ] + }, + "linux-firmware-amdgpu": { + "versions": { + "20190322-r0": 1554980654 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "curl": { + "versions": { + "7.64.0-r1": 1551205311 + }, + "origin": "curl", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libz.so.1" + ], + "provides": [ + "cmd:curl" + ] + }, + "libdbi": { + "versions": { + "0.9.0-r0": 1543932516 + }, + "origin": "libdbi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdbi.so.1=1.1.0" + ] + }, + "wv-doc": { + "versions": { + "1.2.9-r3": 1545300663 + }, + "origin": "wv" + }, + "iniparser": { + "versions": { + "4.1-r0": 1543928460 + }, + "origin": "iniparser", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libiniparser.so.1=1" + ] + }, + "ttf-droid-nonlatin": { + "versions": { + "20121017-r0": 1545300914 + }, + "origin": "ttf-droid", + "dependencies": [ + "fontconfig" + ] + }, + "xf86-video-i740": { + "versions": { + "1.3.6-r3": 1545299627 + }, + "origin": "xf86-video-i740", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gtksourceview2-doc": { + "versions": { + "2.10.5-r0": 1545292664 + }, + "origin": "gtksourceview2" + }, + "keyutils-dev": { + "versions": { + "1.6-r0": 1545837151 + }, + "origin": "keyutils", + "dependencies": [ + "keyutils-libs=1.6-r0" + ] + }, + "git-cvs": { + "versions": { + "2.20.1-r0": 1545214195 + }, + "origin": "git", + "dependencies": [ + "perl", + "perl-git=2.20.1-r0", + "cvs", + "perl-dbd-sqlite" + ], + "provides": [ + "cmd:git-cvsserver" + ] + }, + "daq": { + "versions": { + "2.0.6-r2": 1545163827 + }, + "origin": "daq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "so:libdaq.so.2=2.0.4", + "so:libsfbpf.so.0=0.0.1" + ] + }, + "librsvg": { + "versions": { + "2.40.20-r0": 1543926596 + }, + "origin": "librsvg", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcroco-0.6.so.3", + "so:libfontconfig.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpangoft2-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:librsvg-2.so.2=2.40.20", + "cmd:rsvg-convert" + ] + }, + "font-isas-misc": { + "versions": { + "1.0.3-r0": 1545163915 + }, + "origin": "font-isas-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "libraw1394": { + "versions": { + "2.1.2-r1": 1543932099 + }, + "origin": "libraw1394", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libraw1394.so.11=11.1.0" + ] + }, + "taglib": { + "versions": { + "1.11.1-r1": 1545062765 + }, + "origin": "taglib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libtag.so.1=1.17.0", + "so:libtag_c.so.0=0.0.0" + ] + }, + "py3-phonenumbers": { + "versions": { + "8.10.4-r0": 1548490639 + }, + "origin": "py-phonenumbers", + "dependencies": [ + "python3" + ] + }, + "xkbcomp-doc": { + "versions": { + "1.4.2-r0": 1542973208 + }, + "origin": "xkbcomp" + }, + "lua-dns": { + "versions": { + "20080404-r2": 1545292670 + }, + "origin": "lua-dns", + "dependencies": [ + "lua", + "lua-socket" + ] + }, + "xf86-video-sunleo-doc": { + "versions": { + "1.2.2-r3": 1545293001 + }, + "origin": "xf86-video-sunleo" + }, + "wireless-regdb": { + "versions": { + "2018.05.31-r1": 1550837521 + }, + "origin": "wireless-regdb" + }, + "grub-doc": { + "versions": { + "2.02-r14": 1548432370 + }, + "origin": "grub" + }, + "boost-wave": { + "versions": { + "1.67.0-r2": 1542823634 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_system-mt.so.1.67.0", + "so:libboost_thread-mt.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_wave-mt.so.1.67.0=1.67.0" + ] + }, + "busybox-static": { + "versions": { + "1.29.3-r10": 1548315956 + }, + "origin": "busybox", + "provides": [ + "cmd:busybox.static" + ] + }, + "sqlite-libs": { + "versions": { + "3.26.0-r3": 1546255353 + }, + "origin": "sqlite", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsqlite3.so.0=0.8.6" + ] + }, + "py2-flask": { + "versions": { + "1.0.2-r1": 1546978284 + }, + "origin": "py-flask", + "dependencies": [ + "py2-click", + "py2-itsdangerous", + "py2-jinja2", + "py2-werkzeug", + "python2" + ], + "provides": [ + "cmd:flask" + ] + }, + "qemu-nios2": { + "versions": { + "3.1.0-r3": 1551107301 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-nios2" + ] + }, + "ruby-fiddle": { + "versions": { + "2.5.5-r0": 1557164836 + }, + "origin": "ruby", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libruby.so.2.5" + ] + }, + "libgee": { + "versions": { + "0.20.1-r0": 1545060547 + }, + "origin": "libgee", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libgee-0.8.so.2=2.6.1" + ] + }, + "py-parsing": { + "versions": { + "2.2.0-r0": 1542825051 + }, + "origin": "py-parsing" + }, + "spawn-fcgi": { + "versions": { + "1.6.4-r3": 1542821586 + }, + "origin": "spawn-fcgi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:spawn-fcgi" + ] + }, + "perl-test-without": { + "versions": { + "0.10-r0": 1545223243 + }, + "origin": "perl-test-without" + }, + "libotr-doc": { + "versions": { + "4.1.1-r1": 1543248414 + }, + "origin": "libotr" + }, + "rhash-doc": { + "versions": { + "1.3.6-r2": 1542820448 + }, + "origin": "rhash" + }, + "unbound-dev": { + "versions": { + "1.8.3-r1": 1555953583 + }, + "origin": "unbound", + "dependencies": [ + "expat-dev", + "pc:libcrypto", + "pc:libevent", + "pc:libssl", + "pc:python2", + "pkgconfig", + "unbound-libs=1.8.3-r1" + ], + "provides": [ + "pc:libunbound=1.8.3" + ] + }, + "brlaser-doc": { + "versions": { + "3-r0": 1545214281 + }, + "origin": "brlaser" + }, + "bcache-tools": { + "versions": { + "1.0.8-r1": 1545060583 + }, + "origin": "bcache-tools", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:bcache-super-show", + "cmd:make-bcache" + ] + }, + "dhcpcd-dbus": { + "versions": { + "0.6.1-r3": 1543223071 + }, + "origin": "dhcpcd-dbus", + "dependencies": [ + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3" + ] + }, + "libxinerama-doc": { + "versions": { + "1.1.4-r1": 1543077318 + }, + "origin": "libxinerama" + }, + "which-doc": { + "versions": { + "2.21-r1": 1543929034 + }, + "origin": "which" + }, + "keybinder": { + "versions": { + "0.3.0-r1": 1545076730 + }, + "origin": "keybinder", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ], + "provides": [ + "so:libkeybinder.so.0=0.1.0" + ] + }, + "perl-file-copy-recursive": { + "versions": { + "0.44-r0": 1543238907 + }, + "origin": "perl-file-copy-recursive" + }, + "libvpx": { + "versions": { + "1.6.1-r1": 1545163814 + }, + "origin": "libvpx", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libvpx.so.4=4.1.0" + ] + }, + "hiredis-dev": { + "versions": { + "0.14.0-r0": 1545837197 + }, + "origin": "hiredis", + "dependencies": [ + "hiredis=0.14.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:hiredis=0.14.0" + ] + }, + "open-lldp-dev": { + "versions": { + "0.9.46-r3": 1545062127 + }, + "origin": "open-lldp", + "dependencies": [ + "open-lldp=0.9.46-r3", + "pkgconfig" + ], + "provides": [ + "pc:liblldp_clif=1.0.0", + "pc:lldpad=0.9.46" + ] + }, + "xf86-video-nouveau-doc": { + "versions": { + "1.0.15-r3": 1545299946 + }, + "origin": "xf86-video-nouveau" + }, + "valgrind-dev": { + "versions": { + "3.14.0-r0": 1545300865 + }, + "origin": "valgrind", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:valgrind=3.14.0" + ] + }, + "acf-opennhrp": { + "versions": { + "0.10.0-r2": 1545069407 + }, + "origin": "acf-opennhrp", + "dependencies": [ + "acf-core", + "lua-posix", + "opennhrp" + ] + }, + "lua5.3-posix": { + "versions": { + "33.4.0-r1": 1546010825 + }, + "origin": "lua-posix", + "dependencies": [ + "lua5.3-bit32<26", + "so:libc.musl-x86_64.so.1" + ] + }, + "py3-pbr": { + "versions": { + "5.1.1-r0": 1545235280 + }, + "origin": "py-pbr", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:pbr" + ] + }, + "subversion-bash-completion": { + "versions": { + "1.11.1-r0": 1548692375 + }, + "origin": "subversion" + }, + "bluez-deprecated": { + "versions": { + "5.50-r0": 1545822205 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libreadline.so.7", + "so:libudev.so.1" + ], + "provides": [ + "cmd:ciptool", + "cmd:gatttool", + "cmd:hciattach", + "cmd:hciconfig", + "cmd:hcidump", + "cmd:hcitool", + "cmd:rfcomm", + "cmd:sdptool" + ] + }, + "perl-protocol-websocket": { + "versions": { + "0.20-r0": 1545164022 + }, + "origin": "perl-protocol-websocket", + "dependencies": [ + "perl" + ] + }, + "lua5.2-file-magic": { + "versions": { + "0.2-r1": 1545116947 + }, + "origin": "lua-file-magic", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libmagic.so.1" + ] + }, + "jansson": { + "versions": { + "2.11-r0": 1542893195 + }, + "origin": "jansson", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libjansson.so.4=4.11.0" + ] + }, + "libunwind-dbg": { + "versions": { + "1.2.1-r3": 1545915765 + }, + "origin": "libunwind" + }, + "postgresql-dev": { + "versions": { + "11.2-r0": 1554274172 + }, + "origin": "postgresql", + "dependencies": [ + "openssl-dev", + "libpq=11.2-r0", + "pkgconfig", + "postgresql-libs=11.2-r0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "pc:libecpg=11.2", + "pc:libecpg_compat=11.2", + "pc:libpgtypes=11.2", + "pc:libpq=11.2", + "cmd:ecpg", + "cmd:pg_config" + ] + }, + "procmail-doc": { + "versions": { + "3.22-r4": 1545299910 + }, + "origin": "procmail" + }, + "py-cparser": { + "versions": { + "2.19-r0": 1546791397 + }, + "origin": "py-cparser" + }, + "openldap-dev": { + "versions": { + "2.4.47-r2": 1546016475 + }, + "origin": "openldap", + "dependencies": [ + "openssl-dev", + "cyrus-sasl-dev", + "util-linux-dev", + "libldap=2.4.47-r2" + ] + }, + "bc": { + "versions": { + "1.07.1-r0": 1542823693 + }, + "origin": "bc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:bc", + "cmd:dc" + ] + }, + "uwsgi-dummy": { + "versions": { + "2.0.17.1-r0": 1545062198 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "zlib-doc": { + "versions": { + "1.2.11-r1": 1542300122 + }, + "origin": "zlib" + }, + "libsm": { + "versions": { + "1.2.2-r2": 1542824342 + }, + "origin": "libsm", + "dependencies": [ + "so:libICE.so.6", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libSM.so.6=6.0.1" + ] + }, + "perl-mime-tools": { + "versions": { + "5.509-r1": 1544001507 + }, + "origin": "perl-mime-tools", + "dependencies": [ + "perl", + "perl-io-stringy", + "perl-mailtools", + "perl-convert-binhex" + ] + }, + "qt-assistant": { + "versions": { + "4.8.7-r11": 1545075086 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtGui.so.4", + "so:libQtHelp.so.4", + "so:libQtNetwork.so.4", + "so:libQtSql.so.4", + "so:libQtWebKit.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:assistant" + ] + }, + "ivykis-doc": { + "versions": { + "0.42.3-r0": 1548543106 + }, + "origin": "ivykis" + }, + "gpicview": { + "versions": { + "0.2.5-r0": 1543932505 + }, + "origin": "gpicview", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8" + ], + "provides": [ + "cmd:gpicview" + ] + }, + "memcached-doc": { + "versions": { + "1.5.12-r0": 1543923566 + }, + "origin": "memcached" + }, + "py-wtforms": { + "versions": { + "2.1-r0": 1544000626 + }, + "origin": "py-wtforms" + }, + "util-linux-bash-completion": { + "versions": { + "2.33-r0": 1545307436 + }, + "origin": "util-linux" + }, + "tolua++": { + "versions": { + "1.0.93-r2": 1545209663 + }, + "origin": "tolua++", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ], + "provides": [ + "cmd:tolua++" + ] + }, + "py-django-widget-tweaks": { + "versions": { + "1.4.3-r0": 1545163699 + }, + "origin": "py-django-widget-tweaks", + "dependencies": [ + "py-django" + ] + }, + "tinyproxy": { + "versions": { + "1.10.0-r1": 1551109287 + }, + "origin": "tinyproxy", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tinyproxy" + ] + }, + "aconf": { + "versions": { + "0.7.1-r0": 1553432498 + }, + "origin": "aconf", + "dependencies": [ + "lua5.3-augeas", + "lua5.3-b64", + "lua5.3-cjson", + "lua5.3-file-magic", + "lua5.3-openrc", + "lua5.3-ossl", + "lua5.3-posix", + "lua5.3-stringy", + "uwsgi", + "uwsgi-lua" + ], + "provides": [ + "cmd:aconfd" + ] + }, + "tcl-dev": { + "versions": { + "8.6.9-r0": 1545915062 + }, + "origin": "tcl", + "dependencies": [ + "tcl", + "pc:zlib>=1.2.3", + "pkgconfig" + ], + "provides": [ + "pc:tcl=8.6.9" + ] + }, + "collectd-network": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20" + ] + }, + "alsa-lib": { + "versions": { + "1.1.8-r0": 1547112801 + }, + "origin": "alsa-lib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libasound.so.2=2.0.0", + "cmd:aserver" + ] + }, + "ruby-full": { + "versions": { + "2.5.5-r0": 1557164838 + }, + "origin": "ruby", + "dependencies": [ + "ruby", + "ruby-bigdecimal", + "ruby-dbm", + "ruby-did_you_mean", + "ruby-etc", + "ruby-fiddle", + "ruby-gdbm", + "ruby-io-console", + "ruby-irb", + "ruby-json", + "ruby-minitest", + "ruby-net-telnet", + "ruby-power_assert", + "ruby-rake", + "ruby-rdoc", + "ruby-sdbm", + "ruby-test-unit", + "ruby-webrick", + "ruby-xmlrpc" + ] + }, + "sipsak-doc": { + "versions": { + "0.9.6-r11": 1543077292 + }, + "origin": "sipsak" + }, + "linux-firmware-acenic": { + "versions": { + "20190322-r0": 1554980655 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "ngrep-doc": { + "versions": { + "1.45-r4": 1543242183 + }, + "origin": "ngrep" + }, + "perl-dbix-dbschema-doc": { + "versions": { + "0.39-r0": 1543998794 + }, + "origin": "perl-dbix-dbschema" + }, + "sessreg-doc": { + "versions": { + "1.1.1-r1": 1542985395 + }, + "origin": "sessreg" + }, + "qemu-ppc64": { + "versions": { + "3.1.0-r3": 1551107301 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-ppc64" + ] + }, + "net-snmp": { + "versions": { + "5.8-r0": 1543923351 + }, + "origin": "net-snmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.35", + "so:libnetsnmpagent.so.35", + "so:libnetsnmpmibs.so.35", + "so:libnetsnmptrapd.so.35" + ], + "provides": [ + "cmd:snmpd", + "cmd:snmptrapd" + ] + }, + "hwdata": { + "versions": { + "0.318-r0": 1545746063 + }, + "origin": "hwdata", + "dependencies": [ + "hwdata-usb", + "hwdata-pci", + "hwdata-pnp", + "hwdata-oui" + ] + }, + "ntop-doc": { + "versions": { + "5.0.1-r14": 1545209735 + }, + "origin": "ntop" + }, + "nodejs-dev": { + "versions": { + "10.14.2-r0": 1545345076 + }, + "origin": "nodejs", + "dependencies": [ + "libuv" + ], + "provides": [ + "nodejs-lts-dev=10.14.2" + ] + }, + "perl-namespace-clean": { + "versions": { + "0.27-r0": 1542973127 + }, + "origin": "perl-namespace-clean", + "dependencies": [ + "perl-package-stash", + "perl-sub-name", + "perl-sub-identify", + "perl-b-hooks-endofscope" + ] + }, + "py3-sphinx": { + "versions": { + "1.8.3-r0": 1548111931 + }, + "origin": "py-sphinx", + "dependencies": [ + "make", + "py3-docutils", + "py3-jinja2", + "py3-pygments", + "py3-six", + "py3-sphinx_rtd_theme", + "py3-alabaster<0.8", + "py3-babel", + "py3-snowballstemmer", + "py3-imagesize", + "py3-requests", + "py3-sphinxcontrib-websupport", + "py3-setuptools", + "py3-packaging", + "python3" + ], + "provides": [ + "cmd:sphinx-apidoc-3", + "cmd:sphinx-autogen-3", + "cmd:sphinx-build-3", + "cmd:sphinx-quickstart-3" + ] + }, + "slang-doc": { + "versions": { + "2.3.2-r0": 1543924689 + }, + "origin": "slang" + }, + "lua-hashids": { + "versions": { + "1.0.6-r1": 1545209085 + }, + "origin": "lua-hashids" + }, + "gnome-mime-data": { + "versions": { + "2.18.0-r1": 1543934222 + }, + "origin": "gnome-mime-data" + }, + "perl-css-squish-doc": { + "versions": { + "0.10-r0": 1544000383 + }, + "origin": "perl-css-squish" + }, + "cups-filters": { + "versions": { + "1.21.6-r0": 1545820385 + }, + "origin": "cups-filters", + "dependencies": [ + "poppler-utils", + "bc", + "ttf-freefont", + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-glib.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2", + "so:libcupsfilters.so.1", + "so:libcupsimage.so.2", + "so:libfontconfig.so.1", + "so:libfontembed.so.1", + "so:libgcc_s.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:liblcms2.so.2", + "so:libpoppler.so.67", + "so:libqpdf.so.21", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:cups-browsed", + "cmd:driverless", + "cmd:foomatic-rip", + "cmd:ttfread" + ] + }, + "alsa-utils-dbg": { + "versions": { + "1.1.8-r0": 1547112822 + }, + "origin": "alsa-utils", + "dependencies": [ + "dialog" + ] + }, + "perl-config-inifiles-doc": { + "versions": { + "3.000001-r0": 1547708571 + }, + "origin": "perl-config-inifiles" + }, + "libdv-dev": { + "versions": { + "1.0.0-r3": 1543999340 + }, + "origin": "libdv", + "dependencies": [ + "libdv=1.0.0-r3", + "pkgconfig" + ], + "provides": [ + "pc:libdv=1.0.0" + ] + }, + "git-diff-highlight": { + "versions": { + "2.20.1-r0": 1545214200 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0", + "perl" + ], + "provides": [ + "cmd:diff-highlight" + ] + }, + "perl-datetime-timezone-doc": { + "versions": { + "2.19-r0": 1542973145 + }, + "origin": "perl-datetime-timezone" + }, + "tdb-dev": { + "versions": { + "1.3.16-r0": 1543933253 + }, + "origin": "tdb", + "dependencies": [ + "python2", + "pkgconfig", + "tdb-libs=1.3.16-r0" + ], + "provides": [ + "pc:tdb=1.3.16" + ] + }, + "db-c++": { + "versions": { + "5.3.28-r1": 1542819053 + }, + "origin": "db", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libdb_cxx-5.3.so=0" + ] + }, + "libexif": { + "versions": { + "0.6.21-r3": 1545116759 + }, + "origin": "libexif", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libexif.so.12=12.3.3" + ] + }, + "libpcre2-16": { + "versions": { + "10.32-r1": 1545069083 + }, + "origin": "pcre2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre2-16.so.0=0.7.1" + ] + }, + "xf86-video-rendition-doc": { + "versions": { + "4.2.7-r0": 1544000925 + }, + "origin": "xf86-video-rendition" + }, + "libnetfilter_conntrack-dev": { + "versions": { + "1.0.6-r0": 1545165062 + }, + "origin": "libnetfilter_conntrack", + "dependencies": [ + "libnetfilter_conntrack=1.0.6-r0", + "pc:libnfnetlink", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_conntrack=1.0.6" + ] + }, + "perl-canary-stability-doc": { + "versions": { + "2012-r0": 1543223266 + }, + "origin": "perl-canary-stability" + }, + "squid-lang-es": { + "versions": { + "4.4-r1": 1545216324 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "libcdio++": { + "versions": { + "0.94-r2": 1543248377 + }, + "origin": "libcdio", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcdio.so.16", + "so:libiso9660.so.10", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libcdio++.so.0=0.0.2", + "so:libiso9660++.so.0=0.0.0" + ] + }, + "libao-dev": { + "versions": { + "1.2.0-r3": 1545067193 + }, + "origin": "libao", + "dependencies": [ + "libao=1.2.0-r3", + "pkgconfig" + ], + "provides": [ + "pc:ao=1.2.0" + ] + }, + "py3-redis": { + "versions": { + "2.10.6-r0": 1545301345 + }, + "origin": "py-redis", + "dependencies": [ + "python3" + ] + }, + "openbox-lang": { + "versions": { + "3.6.1-r2": 1545207329 + }, + "origin": "openbox" + }, + "lua-stdlib-debug": { + "versions": { + "1.0.1-r0": 1545223086 + }, + "origin": "lua-stdlib-debug", + "dependencies": [ + "lua" + ], + "provides": [ + "lua5.1-stdlib-debug=1.0.1-r0", + "lua5.2-stdlib-debug=1.0.1-r0", + "lua5.3-stdlib-debug=1.0.1-r0" + ] + }, + "gdl-doc": { + "versions": { + "3.28.0-r0": 1545215937 + }, + "origin": "gdl" + }, + "py-cairo": { + "versions": { + "1.16.3-r0": 1543925202 + }, + "origin": "py-cairo" + }, + "db": { + "versions": { + "5.3.28-r1": 1542819053 + }, + "origin": "db", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdb-5.3.so=0" + ] + }, + "mercurial-doc": { + "versions": { + "4.9.1-r0": 1557160461 + }, + "origin": "mercurial" + }, + "py2-vobject": { + "versions": { + "0.9.6.1-r0": 1545165275 + }, + "origin": "py-vobject", + "dependencies": [ + "python2", + "py2-icu", + "py2-dateutil" + ] + }, + "lua5.3-ldap": { + "versions": { + "1.2.3-r5": 1543223253 + }, + "origin": "lua-ldap", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ] + }, + "lua5.3-curl": { + "versions": { + "0.3.8-r1": 1545224117 + }, + "origin": "lua-curl", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "uwsgi-rpc": { + "versions": { + "2.0.17.1-r0": 1545062209 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "s6-portable-utils": { + "versions": { + "2.2.1.2-r0": 1545069442 + }, + "origin": "s6-portable-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libskarnet.so.2.7" + ], + "provides": [ + "cmd:s6-basename", + "cmd:s6-cat", + "cmd:s6-chmod", + "cmd:s6-chown", + "cmd:s6-clock", + "cmd:s6-cut", + "cmd:s6-dirname", + "cmd:s6-dumpenv", + "cmd:s6-echo", + "cmd:s6-env", + "cmd:s6-expr", + "cmd:s6-false", + "cmd:s6-format-filter", + "cmd:s6-grep", + "cmd:s6-head", + "cmd:s6-hiercopy", + "cmd:s6-linkname", + "cmd:s6-ln", + "cmd:s6-ls", + "cmd:s6-maximumtime", + "cmd:s6-mkdir", + "cmd:s6-mkfifo", + "cmd:s6-nice", + "cmd:s6-nuke", + "cmd:s6-pause", + "cmd:s6-printenv", + "cmd:s6-quote", + "cmd:s6-quote-filter", + "cmd:s6-rename", + "cmd:s6-rmrf", + "cmd:s6-seq", + "cmd:s6-sleep", + "cmd:s6-sort", + "cmd:s6-sync", + "cmd:s6-tail", + "cmd:s6-test", + "cmd:s6-touch", + "cmd:s6-true", + "cmd:s6-uniquename", + "cmd:s6-unquote", + "cmd:s6-unquote-filter", + "cmd:s6-update-symlinks", + "cmd:seekablepipe" + ] + }, + "perl-test-without-module": { + "versions": { + "0.20-r0": 1542893310 + }, + "origin": "perl-test-without-module" + }, + "lua5.2-ldbus": { + "versions": { + "20150430-r2": 1545300022 + }, + "origin": "lua-ldbus", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3" + ] + }, + "lua-sec": { + "versions": { + "0.7-r1": 1545075389 + }, + "origin": "lua-sec" + }, + "perl-data-optlist": { + "versions": { + "0.110-r0": 1542883240 + }, + "origin": "perl-data-optlist", + "dependencies": [ + "perl", + "perl-params-util", + "perl-sub-install" + ] + }, + "thin-provisioning-tools": { + "versions": { + "0.7.1-r1": 1545208657 + }, + "origin": "thin-provisioning-tools", + "dependencies": [ + "expat", + "boost", + "libaio", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:cache_check", + "cmd:cache_dump", + "cmd:cache_metadata_size", + "cmd:cache_repair", + "cmd:cache_restore", + "cmd:cache_writeback", + "cmd:era_check", + "cmd:era_dump", + "cmd:era_invalidate", + "cmd:era_restore", + "cmd:pdata_tools", + "cmd:thin_check", + "cmd:thin_delta", + "cmd:thin_dump", + "cmd:thin_ls", + "cmd:thin_metadata_size", + "cmd:thin_repair", + "cmd:thin_restore", + "cmd:thin_rmap" + ] + }, + "ppp-passprompt": { + "versions": { + "2.4.7-r6": 1543999022 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-io-html": { + "versions": { + "1.001-r1": 1542821791 + }, + "origin": "perl-io-html" + }, + "libcddb": { + "versions": { + "1.3.2-r3": 1543248357 + }, + "origin": "libcddb", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcddb.so.2=2.2.3", + "cmd:cddb_query" + ] + }, + "libnice-gstreamer0.10": { + "versions": { + "0.1.14-r2": 1543928835 + }, + "origin": "libnice", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libnice.so.10" + ] + }, + "py-psycopg2": { + "versions": { + "2.7.5-r0": 1544000431 + }, + "origin": "py-psycopg2" + }, + "linux-firmware-bnx2x": { + "versions": { + "20190322-r0": 1554980654 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "py-flask-script": { + "versions": { + "2.0.6-r0": 1545207605 + }, + "origin": "py-flask-script", + "dependencies": [ + "python2", + "py-flask" + ] + }, + "dpkg": { + "versions": { + "1.19.2-r0": 1545820223 + }, + "origin": "dpkg", + "dependencies": [ + "xz", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:dpkg", + "cmd:dpkg-buildflags", + "cmd:dpkg-deb", + "cmd:dpkg-divert", + "cmd:dpkg-genbuildinfo", + "cmd:dpkg-maintscript-helper", + "cmd:dpkg-mergechangelogs", + "cmd:dpkg-parsechangelog", + "cmd:dpkg-query", + "cmd:dpkg-split", + "cmd:dpkg-statoverride", + "cmd:dpkg-trigger", + "cmd:update-alternatives" + ] + }, + "openresolv": { + "versions": { + "3.9.0-r0": 1545116956 + }, + "origin": "openresolv", + "provides": [ + "cmd:resolvconf" + ] + }, + "rsyslog-crypto": { + "versions": { + "8.40.0-r3": 1548686789 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20" + ], + "provides": [ + "rsyslog-lmcry_gcry=8.40.0-r3" + ] + }, + "djbdns-doc": { + "versions": { + "1.05-r47": 1545208951 + }, + "origin": "djbdns" + }, + "haserl-lua5.2": { + "versions": { + "0.9.35-r1": 1542883797 + }, + "origin": "haserl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua-5.2.so.0" + ], + "provides": [ + "cmd:haserl-lua5.2" + ] + }, + "pkgconf-dev": { + "versions": { + "1.6.0-r0": 1547496958 + }, + "origin": "pkgconf", + "dependencies": [ + "pkgconf=1.6.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libpkgconf=1.6.0" + ] + }, + "grub-xenhost": { + "versions": { + "2.02-r14": 1548432370 + }, + "origin": "grub", + "dependencies": [ + "mkinitfs" + ] + }, + "perl-data-page-pageset": { + "versions": { + "1.02-r1": 1545213964 + }, + "origin": "perl-data-page-pageset", + "dependencies": [ + "perl-data-page", + "perl-test-exception", + "perl-class-accessor" + ] + }, + "libxslt-doc": { + "versions": { + "1.1.33-r1": 1555485888 + }, + "origin": "libxslt" + }, + "farstream": { + "versions": { + "0.2.8-r2": 1543928859 + }, + "origin": "farstream", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-1.0.so.0", + "so:libgstnet-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgstrtp-1.0.so.0", + "so:libnice.so.10" + ], + "provides": [ + "so:libfarstream-0.2.so.5=5.1.0" + ] + }, + "freeswitch-timezones": { + "versions": { + "1.8.2-r1": 1545117892 + }, + "origin": "freeswitch" + }, + "subunit-libs": { + "versions": { + "1.2.0-r0": 1543933435 + }, + "origin": "subunit", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libcppunit_subunit.so.0=0.0.0", + "so:libsubunit.so.0=0.0.0" + ] + }, + "lua5.1-subprocess": { + "versions": { + "0.0.20141229-r2": 1542883781 + }, + "origin": "lua-subprocess", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-authen-sasl": { + "versions": { + "2.16-r1": 1545215986 + }, + "origin": "perl-authen-sasl", + "dependencies": [ + "perl", + "perl-digest-hmac" + ] + }, + "perl-class-load-doc": { + "versions": { + "0.25-r0": 1543226708 + }, + "origin": "perl-class-load" + }, + "samba-winbind": { + "versions": { + "4.8.11-r1": 1555334973 + }, + "origin": "samba", + "dependencies": [ + "samba-common=4.8.11-r1", + "so:libMESSAGING-samba4.so", + "so:libads-samba4.so", + "so:libasn1util-samba4.so", + "so:libauth-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-ldap-common-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcliauth-samba4.so", + "so:libcom_err.so.2", + "so:libcommon-auth-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libdcerpc-samba4.so", + "so:libflag-mapping-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libidmap-samba4.so", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldap-2.4.so.2", + "so:liblibcli-lsa3-samba4.so", + "so:liblibcli-netlogon3-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmsrpc3-samba4.so", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libnpa-tstream-samba4.so", + "so:libnss-info-samba4.so", + "so:libpopt-samba3-samba4.so", + "so:libpopt.so.0", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb-common-samba4.so", + "so:libsecrets3-samba4.so", + "so:libsmb-transport-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-shim-samba4.so", + "so:libsmbldap.so.2", + "so:libsocket-blocking-samba4.so", + "so:libsys-rw-samba4.so", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtime-basic-samba4.so", + "so:libtrusts-util-samba4.so", + "so:libutil-tdb-samba4.so" + ], + "provides": [ + "cmd:winbindd" + ] + }, + "sysfsutils-doc": { + "versions": { + "2.1.0-r8": 1542924684 + }, + "origin": "sysfsutils" + }, + "gmp-dev": { + "versions": { + "6.1.2-r1": 1542301440 + }, + "origin": "gmp", + "dependencies": [ + "gmp=6.1.2-r1", + "libgmpxx=6.1.2-r1" + ] + }, + "sdl2_image-dev": { + "versions": { + "2.0.4-r0": 1545062544 + }, + "origin": "sdl2_image", + "dependencies": [ + "pc:sdl2>=2.0.8", + "pkgconfig", + "sdl2_image=2.0.4-r0" + ], + "provides": [ + "pc:SDL2_image=2.0.4" + ] + }, + "xf86-video-ast-doc": { + "versions": { + "1.1.5-r4": 1545300633 + }, + "origin": "xf86-video-ast" + }, + "uwsgi-gevent": { + "versions": { + "2.0.17.1-r0": 1545062200 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libnetfilter_cthelper": { + "versions": { + "1.0.0-r0": 1545073849 + }, + "origin": "libnetfilter_cthelper", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0" + ], + "provides": [ + "so:libnetfilter_cthelper.so.0=0.0.0" + ] + }, + "qemu-system-sparc64": { + "versions": { + "3.1.0-r3": 1551107305 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-sparc64" + ] + }, + "perl-net-dns-resolver-mock": { + "versions": { + "1.20170814-r0": 1545061184 + }, + "origin": "perl-net-dns-resolver-mock", + "dependencies": [ + "perl-net-dns" + ] + }, + "py2-asn1crypto": { + "versions": { + "0.24.0-r0": 1543246739 + }, + "origin": "py-asn1crypto", + "dependencies": [ + "python2" + ] + }, + "itstool": { + "versions": { + "2.0.4-r3": 1545062596 + }, + "origin": "itstool", + "dependencies": [ + "py3-libxml2", + "python3" + ], + "provides": [ + "cmd:itstool" + ] + }, + "ddate-doc": { + "versions": { + "0.2.2-r0": 1545207007 + }, + "origin": "ddate" + }, + "apache2-icons": { + "versions": { + "2.4.39-r0": 1554306835 + }, + "origin": "apache2" + }, + "perl-io-compress": { + "versions": { + "2.084-r0": 1546944136 + }, + "origin": "perl-io-compress", + "dependencies": [ + "perl", + "perl-compress-raw-bzip2", + "perl-compress-raw-zlib" + ] + }, + "py3-itsdangerous": { + "versions": { + "0.24-r3": 1545164551 + }, + "origin": "py-itsdangerous", + "dependencies": [ + "python3" + ] + }, + "dmidecode": { + "versions": { + "3.2-r0": 1543921858 + }, + "origin": "dmidecode", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:biosdecode", + "cmd:dmidecode", + "cmd:ownership", + "cmd:vpddecode" + ] + }, + "clang-static": { + "versions": { + "5.0.2-r0": 1546873919 + }, + "origin": "clang" + }, + "heimdal-doc": { + "versions": { + "7.5.0-r2": 1542819372 + }, + "origin": "heimdal" + }, + "lame": { + "versions": { + "3.100-r0": 1545117087 + }, + "origin": "lame", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "so:libmp3lame.so.0=0.0.0", + "cmd:lame" + ] + }, + "alsa-lib-dbg": { + "versions": { + "1.1.8-r0": 1547112801 + }, + "origin": "alsa-lib" + }, + "syslog-ng-stomp": { + "versions": { + "3.19.1-r0": 1548543151 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "py2-funcsigs": { + "versions": { + "1.0.2-r1": 1542824897 + }, + "origin": "py-funcsigs", + "dependencies": [ + "python2" + ] + }, + "lua5.2": { + "versions": { + "5.2.4-r7": 1542304845 + }, + "origin": "lua5.2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua-5.2.so.0" + ], + "provides": [ + "lua", + "cmd:lua5.2", + "cmd:luac5.2" + ] + }, + "xrandr-doc": { + "versions": { + "1.5.0-r1": 1545207350 + }, + "origin": "xrandr" + }, + "aports-build": { + "versions": { + "1.5-r0": 1547042899 + }, + "origin": "aports-build", + "dependencies": [ + "abuild>2.20.0", + "mosquitto-clients", + "openssh-client", + "mqtt-exec", + "rsync", + "lua-aports>1.0.0", + "lua5.2-cjson", + "lua5.2-mqtt-publish", + "pigz", + "/bin/sh" + ], + "provides": [ + "cmd:aports-build" + ] + }, + "fluxbox": { + "versions": { + "1.3.7-r3": 1545165091 + }, + "origin": "fluxbox", + "dependencies": [ + "so:libImlib2.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libXpm.so.4", + "so:libXrandr.so.2", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:fbrun", + "cmd:fbsetbg", + "cmd:fbsetroot", + "cmd:fluxbox", + "cmd:fluxbox-generate_menu", + "cmd:fluxbox-remote", + "cmd:fluxbox-update_configs", + "cmd:startfluxbox" + ] + }, + "py-paramiko": { + "versions": { + "2.4.2-r0": 1545216487 + }, + "origin": "py-paramiko", + "dependencies": [ + "py-asn1", + "py-cryptography", + "py-bcrypt", + "py-pynacl" + ] + }, + "linux-firmware-amd-ucode": { + "versions": { + "20190322-r0": 1554980655 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "unixodbc": { + "versions": { + "2.3.7-r0": 1542845421 + }, + "origin": "unixodbc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "so:libodbc.so.2=2.0.0", + "so:libodbccr.so.2=2.0.0", + "so:libodbcinst.so.2=2.0.0", + "cmd:dltest", + "cmd:isql", + "cmd:iusql", + "cmd:odbc_config", + "cmd:odbcinst", + "cmd:slencheck" + ] + }, + "coova-chilli-doc": { + "versions": { + "1.4-r2": 1545163683 + }, + "origin": "coova-chilli" + }, + "libavc1394-doc": { + "versions": { + "0.5.4-r2": 1543932110 + }, + "origin": "libavc1394" + }, + "lua-alt-getopt": { + "versions": { + "0.7-r8": 1544799014 + }, + "origin": "lua-alt-getopt" + }, + "libnetfilter_cttimeout-dev": { + "versions": { + "1.0.0-r0": 1542883681 + }, + "origin": "libnetfilter_cttimeout", + "dependencies": [ + "libmnl-dev", + "libnetfilter_cttimeout=1.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_cttimeout=1.0.0" + ] + }, + "avahi-doc": { + "versions": { + "0.7-r1": 1543925309 + }, + "origin": "avahi" + }, + "py2-rsa": { + "versions": { + "3.4.2-r2": 1545116596 + }, + "origin": "py-rsa", + "dependencies": [ + "py2-asn1", + "python2" + ], + "provides": [ + "cmd:pyrsa-decrypt", + "cmd:pyrsa-decrypt-bigfile", + "cmd:pyrsa-encrypt", + "cmd:pyrsa-encrypt-bigfile", + "cmd:pyrsa-keygen", + "cmd:pyrsa-priv2pub", + "cmd:pyrsa-sign", + "cmd:pyrsa-verify" + ] + }, + "rtnppd-dbg": { + "versions": { + "1.7b-r8": 1543935345 + }, + "origin": "rtnppd" + }, + "font-bitstream-speedo": { + "versions": { + "1.0.2-r0": 1543932490 + }, + "origin": "font-bitstream-speedo", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-php-serialization-doc": { + "versions": { + "0.34-r1": 1545214145 + }, + "origin": "perl-php-serialization" + }, + "perl-gdgraph": { + "versions": { + "1.54-r0": 1545060789 + }, + "origin": "perl-gdgraph", + "dependencies": [ + "perl-gdtextutil", + "perl-gd" + ] + }, + "nfs-utils-openrc": { + "versions": { + "2.3.2-r1": 1543933422 + }, + "origin": "nfs-utils", + "dependencies": [ + "rpcbind" + ] + }, + "gdl-dev": { + "versions": { + "3.28.0-r0": 1545215936 + }, + "origin": "gdl", + "dependencies": [ + "gdl=3.28.0-r0", + "pc:gtk+-3.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gdl-3.0=3.28.0" + ] + }, + "opensmtpd": { + "versions": { + "6.0.3p1-r3": 1554301264 + }, + "origin": "opensmtpd", + "dependencies": [ + "!postfix", + "/bin/sh", + "so:libasr.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libdb-5.3.so", + "so:libevent-2.1.so.6", + "so:libfts.so.0", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:smtpctl", + "cmd:smtpd" + ] + }, + "libart-lgpl-dev": { + "versions": { + "2.3.21-r5": 1542924746 + }, + "origin": "libart-lgpl", + "dependencies": [ + "libart-lgpl=2.3.21-r5", + "pkgconfig" + ], + "provides": [ + "pc:libart-2.0=2.3.21", + "cmd:libart2-config" + ] + }, + "uwsgi-logcrypto": { + "versions": { + "2.0.17.1-r0": 1545062201 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "jasper": { + "versions": { + "2.0.14-r0": 1543932348 + }, + "origin": "jasper", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjasper.so.4" + ], + "provides": [ + "cmd:imgcmp", + "cmd:imginfo", + "cmd:jasper" + ] + }, + "qemu-system-alpha": { + "versions": { + "3.1.0-r3": 1551107302 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-alpha" + ] + }, + "freeradius-ldap": { + "versions": { + "3.0.17-r5": 1556201987 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.17-r5", + "so:libc.musl-x86_64.so.1", + "so:libldap-2.4.so.2" + ], + "provides": [ + "freeradius3-ldap=3.0.17-r5", + "so:rlm_ldap.so=0" + ] + }, + "libxml2-doc": { + "versions": { + "2.9.9-r1": 1551105514 + }, + "origin": "libxml2" + }, + "tlsdate-doc": { + "versions": { + "0.0.13-r7": 1545214008 + }, + "origin": "tlsdate" + }, + "perl-datetime-format-mail-doc": { + "versions": { + "0.403-r0": 1543927500 + }, + "origin": "perl-datetime-format-mail" + }, + "xdpyinfo": { + "versions": { + "1.3.2-r0": 1545069117 + }, + "origin": "xdpyinfo", + "dependencies": [ + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXi.so.6", + "so:libXtst.so.6", + "so:libc.musl-x86_64.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "cmd:xdpyinfo" + ] + }, + "xrefresh-doc": { + "versions": { + "1.0.6-r0": 1545290858 + }, + "origin": "xrefresh" + }, + "samba-dc": { + "versions": { + "4.8.11-r1": 1555334974 + }, + "origin": "samba", + "dependencies": [ + "samba-common=4.8.11-r1", + "samba-server=4.8.11-r1", + "samba-winbind=4.8.11-r1", + "py-samba=4.8.11-r1", + "tdb", + "so:libMESSAGING-samba4.so", + "so:libasn1util-samba4.so", + "so:libauth-samba4.so", + "so:libauth4-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-ldap-samba4.so", + "so:libcliauth-samba4.so", + "so:libcluster-samba4.so", + "so:libcmdline-credentials-samba4.so", + "so:libcom_err.so.2", + "so:libcommon-auth-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdsdb-module-samba4.so", + "so:libevents-samba4.so", + "so:libflag-mapping-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgnutls.so.30", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr.so.0", + "so:libnetif-samba4.so", + "so:libpopt.so.0", + "so:libposix-eadb-samba4.so", + "so:libprocess-model-samba4.so", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libsecrets3-samba4.so", + "so:libservice-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-base-samba4.so", + "so:libsmbd-shim-samba4.so", + "so:libsocket-blocking-samba4.so", + "so:libtalloc.so.2", + "so:libtdb-wrap-samba4.so", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtime-basic-samba4.so" + ], + "provides": [ + "so:libsmbpasswdparser-samba4.so=0", + "cmd:samba", + "cmd:samba-tool", + "cmd:samba_dnsupdate", + "cmd:samba_gpoupdate", + "cmd:samba_kcc", + "cmd:samba_spnupdate", + "cmd:samba_upgradedns" + ] + }, + "libmatroska": { + "versions": { + "1.4.9-r0": 1543928900 + }, + "origin": "libmatroska", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libebml.so.4", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libmatroska.so.6=6.0.0" + ] + }, + "libmad-dev": { + "versions": { + "0.15.1b-r8": 1543925792 + }, + "origin": "libmad", + "dependencies": [ + "libmad=0.15.1b-r8", + "pkgconfig" + ], + "provides": [ + "pc:mad=0.15.0b" + ] + }, + "netcat-openbsd-doc": { + "versions": { + "1.130-r1": 1543935624 + }, + "origin": "netcat-openbsd" + }, + "s6-dns-doc": { + "versions": { + "2.3.0.1-r0": 1545062684 + }, + "origin": "s6-dns" + }, + "linux-firmware": { + "versions": { + "20190322-r0": 1554980655 + }, + "origin": "linux-firmware", + "dependencies": [ + "linux-firmware-yamaha=20190322-r0", + "linux-firmware-yam=20190322-r0", + "linux-firmware-vxge=20190322-r0", + "linux-firmware-vicam=20190322-r0", + "linux-firmware-ueagle-atm=20190322-r0", + "linux-firmware-ttusb-budget=20190322-r0", + "linux-firmware-tigon=20190322-r0", + "linux-firmware-ti-keystone=20190322-r0", + "linux-firmware-ti-connectivity=20190322-r0", + "linux-firmware-tehuti=20190322-r0", + "linux-firmware-sxg=20190322-r0", + "linux-firmware-sun=20190322-r0", + "linux-firmware-slicoss=20190322-r0", + "linux-firmware-sb16=20190322-r0", + "linux-firmware-rtw88=20190322-r0", + "linux-firmware-rtlwifi=20190322-r0", + "linux-firmware-rtl_nic=20190322-r0", + "linux-firmware-rtl_bt=20190322-r0", + "linux-firmware-rtl8192e=20190322-r0", + "linux-firmware-rsi=20190322-r0", + "linux-firmware-rockchip=20190322-r0", + "linux-firmware-radeon=20190322-r0", + "linux-firmware-r128=20190322-r0", + "linux-firmware-qlogic=20190322-r0", + "linux-firmware-qed=20190322-r0", + "linux-firmware-qcom=20190322-r0", + "linux-firmware-qca=20190322-r0", + "linux-firmware-ositech=20190322-r0", + "linux-firmware-nvidia=20190322-r0", + "linux-firmware-netronome=20190322-r0", + "linux-firmware-myricom=20190322-r0", + "linux-firmware-mwlwifi=20190322-r0", + "linux-firmware-mwl8k=20190322-r0", + "linux-firmware-mrvl=20190322-r0", + "linux-firmware-moxa=20190322-r0", + "linux-firmware-microchip=20190322-r0", + "linux-firmware-mellanox=20190322-r0", + "linux-firmware-mediatek=20190322-r0", + "linux-firmware-matrox=20190322-r0", + "linux-firmware-liquidio=20190322-r0", + "linux-firmware-libertas=20190322-r0", + "linux-firmware-korg=20190322-r0", + "linux-firmware-keyspan_pda=20190322-r0", + "linux-firmware-keyspan=20190322-r0", + "linux-firmware-kaweth=20190322-r0", + "linux-firmware-isci=20190322-r0", + "linux-firmware-intel=20190322-r0", + "linux-firmware-imx=20190322-r0", + "linux-firmware-i915=20190322-r0", + "linux-firmware-go7007=20190322-r0", + "linux-firmware-ess=20190322-r0", + "linux-firmware-ene-ub6250=20190322-r0", + "linux-firmware-emi62=20190322-r0", + "linux-firmware-emi26=20190322-r0", + "linux-firmware-edgeport=20190322-r0", + "linux-firmware-e100=20190322-r0", + "linux-firmware-dsp56k=20190322-r0", + "linux-firmware-dpaa2=20190322-r0", + "linux-firmware-dabusb=20190322-r0", + "linux-firmware-cxgb4=20190322-r0", + "linux-firmware-cxgb3=20190322-r0", + "linux-firmware-cpia2=20190322-r0", + "linux-firmware-cis=20190322-r0", + "linux-firmware-cavium=20190322-r0", + "linux-firmware-cadence=20190322-r0", + "linux-firmware-brcm=20190322-r0", + "linux-firmware-bnx2x=20190322-r0", + "linux-firmware-bnx2=20190322-r0", + "linux-firmware-av7110=20190322-r0", + "linux-firmware-atusb=20190322-r0", + "linux-firmware-atmel=20190322-r0", + "linux-firmware-ath9k_htc=20190322-r0", + "linux-firmware-ath6k=20190322-r0", + "linux-firmware-ath10k=20190322-r0", + "linux-firmware-ar3k=20190322-r0", + "linux-firmware-amdgpu=20190322-r0", + "linux-firmware-amd-ucode=20190322-r0", + "linux-firmware-amd=20190322-r0", + "linux-firmware-advansys=20190322-r0", + "linux-firmware-adaptec=20190322-r0", + "linux-firmware-acenic=20190322-r0", + "linux-firmware-3com=20190322-r0", + "linux-firmware-other=20190322-r0" + ], + "provides": [ + "linux-firmware-any" + ] + }, + "libconfig": { + "versions": { + "1.5-r3": 1543249701 + }, + "origin": "libconfig", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libconfig.so.9=9.2.0" + ] + }, + "apache-mod-auth-ntlm-winbind": { + "versions": { + "0.1-r6": 1545258464 + }, + "origin": "apache-mod-auth-ntlm-winbind", + "dependencies": [ + "apache2", + "samba", + "samba-winbind", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-file-temp": { + "versions": { + "0.2308-r0": 1543226523 + }, + "origin": "perl-file-temp" + }, + "font-misc-ethiopic": { + "versions": { + "1.0.3-r0": 1545222989 + }, + "origin": "font-misc-ethiopic", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "sdl_mixer-dev": { + "versions": { + "1.2.12-r1": 1545162992 + }, + "origin": "sdl_mixer", + "dependencies": [ + "pc:sdl>=1.2.10", + "pkgconfig", + "sdl_mixer=1.2.12-r1" + ], + "provides": [ + "pc:SDL_mixer=1.2.12" + ] + }, + "llvm5-libs": { + "versions": { + "5.0.2-r0": 1546874980 + }, + "origin": "llvm5", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "llvm-libs=5.0.2-r0", + "so:libLLVM-5.0.so=0" + ] + }, + "libsecret": { + "versions": { + "0.18.6-r0": 1545291103 + }, + "origin": "libsecret", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libsecret-1.so.0=0.0.0", + "cmd:secret-tool" + ] + }, + "postgresql": { + "versions": { + "11.2-r0": 1554274177 + }, + "origin": "postgresql", + "dependencies": [ + "postgresql-client", + "tzdata", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libldap-2.4.so.2", + "so:libpq.so.5", + "so:libssl.so.1.1", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:initdb", + "cmd:pg_archivecleanup", + "cmd:pg_controldata", + "cmd:pg_ctl", + "cmd:pg_resetwal", + "cmd:pg_rewind", + "cmd:pg_test_fsync", + "cmd:pg_test_timing", + "cmd:pg_upgrade", + "cmd:pg_verify_checksums", + "cmd:pg_waldump", + "cmd:pgbench", + "cmd:postgres", + "cmd:postmaster" + ] + }, + "b43-fwcutter-doc": { + "versions": { + "019-r0": 1545163906 + }, + "origin": "b43-fwcutter" + }, + "perl-dbd-pg-doc": { + "versions": { + "3.7.4-r0": 1542972912 + }, + "origin": "perl-dbd-pg" + }, + "perl-test-manifest": { + "versions": { + "2.021-r0": 1543932508 + }, + "origin": "perl-test-manifest" + }, + "libmemcached-doc": { + "versions": { + "1.0.18-r3": 1543923892 + }, + "origin": "libmemcached" + }, + "npth": { + "versions": { + "1.6-r0": 1543932201 + }, + "origin": "npth", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnpth.so.0=0.1.2" + ] + }, + "qemu-block-ssh": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libssh2.so.1" + ] + }, + "libsecret-lang": { + "versions": { + "0.18.6-r0": 1545291103 + }, + "origin": "libsecret" + }, + "uwsgi-rsyslog": { + "versions": { + "2.0.17.1-r0": 1545062209 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "pcre2-dev": { + "versions": { + "10.32-r1": 1545069082 + }, + "origin": "pcre2", + "dependencies": [ + "libedit-dev", + "zlib-dev", + "libpcre2-16=10.32-r1", + "libpcre2-32=10.32-r1", + "pcre2=10.32-r1", + "pkgconfig" + ], + "provides": [ + "pc:libpcre2-16=10.32", + "pc:libpcre2-32=10.32", + "pc:libpcre2-8=10.32", + "pc:libpcre2-posix=10.32", + "cmd:pcre2-config" + ] + }, + "icecast": { + "versions": { + "2.4.4-r1": 1543934500 + }, + "origin": "icecast", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libogg.so.0", + "so:libssl.so.1.1", + "so:libtheora.so.0", + "so:libvorbis.so.0", + "so:libxml2.so.2", + "so:libxslt.so.1" + ], + "provides": [ + "cmd:icecast" + ] + }, + "libasyncns-dev": { + "versions": { + "0.8-r1": 1543226569 + }, + "origin": "libasyncns", + "dependencies": [ + "libasyncns=0.8-r1", + "pkgconfig" + ], + "provides": [ + "pc:libasyncns=0.8" + ] + }, + "mesa-gbm": { + "versions": { + "18.1.7-r2": 1554455971 + }, + "origin": "mesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libexpat.so.1", + "so:libwayland-server.so.0", + "so:libz.so.1" + ], + "provides": [ + "so:libgbm.so.1=1.0.0" + ] + }, + "libmodplug-dev": { + "versions": { + "0.8.9.0-r0": 1545208837 + }, + "origin": "libmodplug", + "dependencies": [ + "libmodplug=0.8.9.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmodplug=0.8.9.0" + ] + }, + "quagga-dbg": { + "versions": { + "1.2.4-r1": 1545068941 + }, + "origin": "quagga", + "dependencies": [ + "iproute2" + ] + }, + "pm-utils-doc": { + "versions": { + "1.4.1-r1": 1543935621 + }, + "origin": "pm-utils" + }, + "mariadb-backup": { + "versions": { + "10.3.13-r1": 1557431735 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:liblzma.so.5", + "so:libpcre.so.1", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:mariabackup", + "cmd:mbstream" + ] + }, + "dnsmasq": { + "versions": { + "2.80-r3": 1545223253 + }, + "origin": "dnsmasq", + "dependencies": [ + "!dnsmasq-dnssec", + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dnsmasq" + ] + }, + "openssl-doc": { + "versions": { + "1.1.1b-r1": 1552660155 + }, + "origin": "openssl" + }, + "enchant": { + "versions": { + "1.6.0-r13": 1542965846 + }, + "origin": "enchant", + "dependencies": [ + "so:libaspell.so.15", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libhunspell-1.6.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libenchant.so.1=1.6.0", + "cmd:enchant", + "cmd:enchant-lsmod" + ] + }, + "lcms": { + "versions": { + "1.19-r8": 1546249798 + }, + "origin": "lcms", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8", + "so:liblcms.so.1", + "so:libtiff.so.5" + ], + "provides": [ + "cmd:icc2ps", + "cmd:icclink", + "cmd:icctrans", + "cmd:jpegicc", + "cmd:tiffdiff", + "cmd:tifficc", + "cmd:wtpt" + ] + }, + "qt-sqlite": { + "versions": { + "4.8.7-r11": 1545075087 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtSql.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libsqlite3.so.0", + "so:libstdc++.so.6" + ] + }, + "aoetools": { + "versions": { + "37-r0": 1545073929 + }, + "origin": "aoetools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:aoe-discover", + "cmd:aoe-flush", + "cmd:aoe-interfaces", + "cmd:aoe-mkdevs", + "cmd:aoe-mkshelf", + "cmd:aoe-revalidate", + "cmd:aoe-sancheck", + "cmd:aoe-stat", + "cmd:aoe-version", + "cmd:aoecfg", + "cmd:aoeping", + "cmd:coraid-update" + ] + }, + "libvpx-dev": { + "versions": { + "1.6.1-r1": 1545163814 + }, + "origin": "libvpx", + "dependencies": [ + "libvpx=1.6.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:vpx=1.6.1" + ] + }, + "cunit-dev": { + "versions": { + "2.1.3-r1": 1542900197 + }, + "origin": "cunit", + "dependencies": [ + "cunit=2.1.3-r1", + "pkgconfig" + ] + }, + "perl-boolean": { + "versions": { + "0.46-r0": 1543928865 + }, + "origin": "perl-boolean" + }, + "collectd-lvm": { + "versions": { + "5.8.0-r3": 1546422678 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:liblvm2app.so.2.2" + ] + }, + "py3-zope-interface": { + "versions": { + "4.6.0-r0": 1548537850 + }, + "origin": "py-zope-interface", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "ppp-winbind": { + "versions": { + "2.4.7-r6": 1543999022 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "nftables-doc": { + "versions": { + "0.9.0-r0": 1542893220 + }, + "origin": "nftables" + }, + "clamav-daemon": { + "versions": { + "0.100.3-r0": 1555507335 + }, + "origin": "clamav", + "dependencies": [ + "freshclam", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libclamav.so.7", + "so:libcrypto.so.1.1", + "so:libfts.so.0", + "so:libncursesw.so.6", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:clamconf", + "cmd:clamd", + "cmd:clamdscan", + "cmd:clamdtop" + ] + }, + "mosh-client": { + "versions": { + "1.3.2-r7": 1543932000 + }, + "origin": "mosh", + "dependencies": [ + "openssh-client", + "perl-io-tty", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libprotobuf.so.17", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:mosh-client" + ] + }, + "screen": { + "versions": { + "4.6.2-r0": 1545290725 + }, + "origin": "screen", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:screen", + "cmd:screen-4.6.2" + ] + }, + "perl-text-wrapper": { + "versions": { + "1.05-r1": 1543927508 + }, + "origin": "perl-text-wrapper" + }, + "opensmtpd-doc": { + "versions": { + "6.0.3p1-r3": 1554301264 + }, + "origin": "opensmtpd" + }, + "py-openssl": { + "versions": { + "18.0.0-r0": 1545223367 + }, + "origin": "py-openssl", + "dependencies": [ + "py-cryptography", + "py-six" + ] + }, + "wpa_supplicant-doc": { + "versions": { + "2.7-r2": 1555501504 + }, + "origin": "wpa_supplicant" + }, + "nagios-plugins-disk_smb": { + "versions": { + "2.2.1-r6": 1543933903 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "samba-client", + "perl" + ] + }, + "py3-twitter": { + "versions": { + "3.5-r0": 1546524754 + }, + "origin": "py-twitter", + "dependencies": [ + "py3-future", + "py3-requests", + "py3-requests-oauthlib", + "python3" + ] + }, + "xf86-input-vmmouse": { + "versions": { + "13.1.0-r4": 1545207342 + }, + "origin": "xf86-input-vmmouse", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ], + "provides": [ + "cmd:vmmouse_detect" + ] + }, + "cairo": { + "versions": { + "1.16.0-r1": 1546948315 + }, + "origin": "cairo", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libxcb-render.so.0", + "so:libxcb-shm.so.0", + "so:libxcb.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libcairo-script-interpreter.so.2=2.11600.0", + "so:libcairo.so.2=2.11600.0" + ] + }, + "nginx-mod-http-upload-progress": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "libva-glx-dev": { + "versions": { + "2.2.0-r0": 1545300086 + }, + "origin": "libva-glx", + "dependencies": [ + "libva-dev", + "mesa-dev", + "libva-glx=2.2.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libva-glx=1.2.0" + ] + }, + "libsm-doc": { + "versions": { + "1.2.2-r2": 1542824341 + }, + "origin": "libsm" + }, + "xbacklight-doc": { + "versions": { + "1.2.2-r0": 1544792468 + }, + "origin": "xbacklight" + }, + "weechat-dev": { + "versions": { + "2.3-r0": 1545299932 + }, + "origin": "weechat", + "dependencies": [ + "cmake", + "libintl", + "ncurses-dev", + "gnutls-dev", + "libgcrypt-dev", + "curl-dev", + "aspell-dev", + "lua-dev", + "perl-dev", + "python2-dev", + "ruby-dev", + "zlib-dev", + "pkgconfig" + ], + "provides": [ + "pc:weechat=2.3" + ] + }, + "lua5.2-pc": { + "versions": { + "1.0.0-r9": 1543928870 + }, + "origin": "lua-pc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-list-allutils-doc": { + "versions": { + "0.14-r0": 1545214299 + }, + "origin": "perl-list-allutils" + }, + "augeas-tests": { + "versions": { + "1.11.0-r0": 1542924581 + }, + "origin": "augeas" + }, + "xz": { + "versions": { + "5.2.4-r0": 1542303286 + }, + "origin": "xz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblzma.so.5" + ], + "provides": [ + "cmd:lzcat", + "cmd:lzcmp", + "cmd:lzdiff", + "cmd:lzegrep", + "cmd:lzfgrep", + "cmd:lzgrep", + "cmd:lzless", + "cmd:lzma", + "cmd:lzmadec", + "cmd:lzmainfo", + "cmd:lzmore", + "cmd:unlzma", + "cmd:unxz", + "cmd:xz", + "cmd:xzcat", + "cmd:xzcmp", + "cmd:xzdec", + "cmd:xzdiff", + "cmd:xzegrep", + "cmd:xzfgrep", + "cmd:xzgrep", + "cmd:xzless", + "cmd:xzmore" + ] + }, + "linux-firmware-libertas": { + "versions": { + "20190322-r0": 1554980651 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "libgnome-doc": { + "versions": { + "2.32.1-r7": 1545299765 + }, + "origin": "libgnome" + }, + "perl-text-autoformat-doc": { + "versions": { + "1.74-r0": 1545118021 + }, + "origin": "perl-text-autoformat" + }, + "lua-lzmq": { + "versions": { + "0.4.4-r0": 1545076587 + }, + "origin": "lua-lzmq" + }, + "apcupsd": { + "versions": { + "3.14.14-r0": 1545293177 + }, + "origin": "apcupsd", + "dependencies": [ + "util-linux", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "cmd:apcaccess", + "cmd:apctest", + "cmd:apcupsd", + "cmd:smtp" + ] + }, + "vte": { + "versions": { + "0.28.2-r13": 1543927671 + }, + "origin": "vte", + "dependencies": [ + "so:libX11.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0" + ], + "provides": [ + "so:libvte.so.9=9.2800.2", + "cmd:vte" + ] + }, + "py2-olefile": { + "versions": { + "0.43-r2": 1542883723 + }, + "origin": "py-olefile" + }, + "clamav-daemon-openrc": { + "versions": { + "0.100.3-r0": 1555507335 + }, + "origin": "clamav", + "dependencies": [ + "freshclam-openrc" + ] + }, + "py-cliapp-doc": { + "versions": { + "1.20170823-r0": 1544798592 + }, + "origin": "py-cliapp" + }, + "uwsgi-notfound": { + "versions": { + "2.0.17.1-r0": 1545062204 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "vsftpd": { + "versions": { + "3.0.3-r6": 1544000085 + }, + "origin": "vsftpd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.1.1", + "so:libpam.so.0", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:vsftpd" + ] + }, + "qt-tds": { + "versions": { + "4.8.7-r11": 1545075088 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtSql.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libsybdb.so.5" + ] + }, + "linux-firmware-qca": { + "versions": { + "20190322-r0": 1554980649 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "tig": { + "versions": { + "2.4.1-r0": 1545216501 + }, + "origin": "tig", + "dependencies": [ + "git", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:tig" + ] + }, + "perl-test-refcount-doc": { + "versions": { + "0.08-r0": 1544799026 + }, + "origin": "perl-test-refcount" + }, + "py-virtualenv": { + "versions": { + "15.1.0-r0": 1545066964 + }, + "origin": "py-virtualenv" + }, + "alpine-git-mirror-syncd": { + "versions": { + "0.3.0-r0": 1543932706 + }, + "origin": "alpine-git-mirror-syncd", + "dependencies": [ + "ca-certificates", + "git", + "lua5.1", + "lua5.1-cjson", + "lua5.1-mosquitto" + ], + "provides": [ + "cmd:git-mirror-syncd" + ] + }, + "linux-firmware-rtl_nic": { + "versions": { + "20190322-r0": 1554980648 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "py2-werkzeug": { + "versions": { + "0.14.1-r0": 1549977320 + }, + "origin": "py-werkzeug", + "dependencies": [ + "python2" + ] + }, + "ckbcomp": { + "versions": { + "1.187-r0": 1545292994 + }, + "origin": "ckbcomp", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:ckbcomp" + ] + }, + "jsoncpp": { + "versions": { + "1.8.4-r0": 1545075440 + }, + "origin": "jsoncpp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libjsoncpp.so.20=20" + ] + }, + "uwsgi-ugreen": { + "versions": { + "2.0.17.1-r0": 1545062213 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "py3-bluez": { + "versions": { + "0.22-r1": 1545062450 + }, + "origin": "py-bluez", + "dependencies": [ + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "py3-purl": { + "versions": { + "1.4-r0": 1543925754 + }, + "origin": "py-purl", + "dependencies": [ + "py3-six", + "python3" + ] + }, + "openldap-backend-all": { + "versions": { + "2.4.47-r2": 1546016477 + }, + "origin": "openldap", + "dependencies": [ + "openldap-back-bdb", + "openldap-back-dnssrv", + "openldap-back-hdb", + "openldap-back-ldap", + "openldap-back-mdb", + "openldap-back-meta", + "openldap-back-monitor", + "openldap-back-null", + "openldap-back-passwd", + "openldap-back-relay", + "openldap-back-shell", + "openldap-back-sql", + "openldap-back-sock" + ] + }, + "openpgm-dev": { + "versions": { + "5.2.122-r1": 1543222717 + }, + "origin": "openpgm", + "dependencies": [ + "openpgm=5.2.122-r1", + "pkgconfig" + ], + "provides": [ + "pc:openpgm-5.2=5.2.122" + ] + }, + "py2-singledispatch": { + "versions": { + "3.4.0.3-r1": 1545300040 + }, + "origin": "py2-singledispatch", + "dependencies": [ + "python2" + ], + "provides": [ + "py-singledispatch" + ] + }, + "libvncserver": { + "versions": { + "0.9.11-r2": 1543248465 + }, + "origin": "libvncserver", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libz.so.1" + ], + "provides": [ + "so:libvncclient.so.1=1.0.0", + "so:libvncserver.so.1=1.0.0" + ] + }, + "libnetfilter_queue-dev": { + "versions": { + "1.0.3-r0": 1546267692 + }, + "origin": "libnetfilter_queue", + "dependencies": [ + "libnetfilter_queue=1.0.3-r0", + "pc:libnfnetlink", + "pkgconfig" + ], + "provides": [ + "pc:libnetfilter_queue=1.0.3" + ] + }, + "ruby-etc": { + "versions": { + "2.5.5-r0": 1557164836 + }, + "origin": "ruby", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.5" + ] + }, + "libmng-dev": { + "versions": { + "2.0.3-r1": 1545062629 + }, + "origin": "libmng", + "dependencies": [ + "libjpeg-turbo-dev", + "libmng=2.0.3-r1", + "pkgconfig" + ], + "provides": [ + "pc:libmng=2.0.2" + ] + }, + "linux-firmware-tigon": { + "versions": { + "20190322-r0": 1554980647 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "polkit-gnome": { + "versions": { + "0.105-r1": 1544798744 + }, + "origin": "polkit-gnome", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libpolkit-agent-1.so.0", + "so:libpolkit-gobject-1.so.0" + ] + }, + "perl-io-socket-ssl-doc": { + "versions": { + "2.060-r0": 1545164013 + }, + "origin": "perl-io-socket-ssl" + }, + "iso-codes-dev": { + "versions": { + "4.1-r0": 1545257105 + }, + "origin": "iso-codes", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:iso-codes=4.1" + ] + }, + "freeradius-perl": { + "versions": { + "3.0.17-r5": 1556201988 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.17-r5", + "perl", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ], + "provides": [ + "freeradius3-perl=3.0.17-r5", + "so:rlm_perl.so=0" + ] + }, + "libdvdnav-doc": { + "versions": { + "5.0.3-r1": 1543999001 + }, + "origin": "libdvdnav" + }, + "dejagnu": { + "versions": { + "1.6.2-r0": 1545820393 + }, + "origin": "dejagnu", + "dependencies": [ + "expect" + ], + "provides": [ + "cmd:runtest" + ] + }, + "dovecot-sqlite": { + "versions": { + "2.3.6-r0": 1557134098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot-sql=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:libdriver_sqlite.so=0" + ] + }, + "spl-vanilla-dev": { + "versions": { + "4.19.41-r0": 1557400331 + }, + "origin": "spl-vanilla", + "dependencies": [ + "linux-vanilla-dev=4.19.41-r0" + ] + }, + "lua5.2-rex-pcre": { + "versions": { + "2.9.0-r0": 1545209202 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ] + }, + "libxdg-basedir": { + "versions": { + "1.2.0-r0": 1545116744 + }, + "origin": "libxdg-basedir", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libxdg-basedir.so.1=1.2.0" + ] + }, + "fftw-single-libs": { + "versions": { + "3.3.8-r0": 1543926478 + }, + "origin": "fftw", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfftw3f.so.3=3.5.8", + "so:libfftw3f_threads.so.3=3.5.8" + ] + }, + "sshguard-doc": { + "versions": { + "2.2.0-r0": 1545300225 + }, + "origin": "sshguard" + }, + "skytraq-datalogger": { + "versions": { + "0.5.1-r1": 1543927504 + }, + "origin": "skytraq-datalogger", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ], + "provides": [ + "cmd:skytraq-datalogger" + ] + }, + "xcb-util-renderutil": { + "versions": { + "0.3.9-r1": 1543927922 + }, + "origin": "xcb-util-renderutil", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-render.so.0", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-render-util.so.0=0.0.0" + ] + }, + "py3-btrfs-progs": { + "versions": { + "4.19.1-r0": 1545665049 + }, + "origin": "btrfs-progs", + "dependencies": [ + "so:libbtrfsutil.so.1", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ], + "provides": [ + "py-btrfs-progs=4.19.1-r0" + ] + }, + "perl-http-date-doc": { + "versions": { + "6.02-r1": 1542821757 + }, + "origin": "perl-http-date" + }, + "py3-gpsd": { + "versions": { + "3.18.1-r1": 1549881587 + }, + "origin": "gpsd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:gpscat", + "cmd:gpsfake", + "cmd:gpsprof" + ] + }, + "tunnel": { + "versions": { + "1.0-r0": 1545076546 + }, + "origin": "tunnel" + }, + "libverto-glib": { + "versions": { + "0.3.0-r1": 1543223527 + }, + "origin": "libverto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libverto.so.1" + ], + "provides": [ + "so:libverto-glib.so.1=1.0.0" + ] + }, + "makekit": { + "versions": { + "0.2-r0": 1545223342 + }, + "origin": "makekit", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:makekit", + "cmd:mkdash" + ] + }, + "libpcre32": { + "versions": { + "8.42-r1": 1545067005 + }, + "origin": "pcre", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre32.so.0=0.0.10" + ] + }, + "libgpg-error-lisp": { + "versions": { + "1.33-r0": 1545838267 + }, + "origin": "libgpg-error" + }, + "libxft": { + "versions": { + "2.3.2-r3": 1542824036 + }, + "origin": "libxft", + "dependencies": [ + "so:libX11.so.6", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libfreetype.so.6" + ], + "provides": [ + "so:libXft.so.2=2.3.2" + ] + }, + "nginx-mod-rtmp": { + "versions": { + "1.14.2-r1": 1557179822 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "nginx-rtmp" + ] + }, + "libiptcdata": { + "versions": { + "1.0.4-r2": 1543932145 + }, + "origin": "libiptcdata", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libiptcdata.so.0=0.3.3", + "cmd:iptc" + ] + }, + "mesa-vulkan-ati": { + "versions": { + "18.1.7-r2": 1554455971 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libX11-xcb.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libelf.so.0", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libwayland-client.so.0", + "so:libxcb-dri3.so.0", + "so:libxcb-present.so.0", + "so:libxcb-sync.so.1", + "so:libxcb.so.1", + "so:libxshmfence.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libvulkan_radeon.so=0" + ] + }, + "glu": { + "versions": { + "9.0.0-r4": 1543927738 + }, + "origin": "glu", + "dependencies": [ + "so:libGL.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libGLU.so.1=1.3.1" + ] + }, + "xf86-video-ast": { + "versions": { + "1.1.5-r4": 1545300634 + }, + "origin": "xf86-video-ast", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libevent": { + "versions": { + "2.1.8-r6": 1543223505 + }, + "origin": "libevent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libevent-2.1.so.6=6.0.2", + "so:libevent_core-2.1.so.6=6.0.2", + "so:libevent_extra-2.1.so.6=6.0.2", + "so:libevent_openssl-2.1.so.6=6.0.2", + "so:libevent_pthreads-2.1.so.6=6.0.2" + ] + }, + "perl-symbol-global-name": { + "versions": { + "0.05-r1": 1545223348 + }, + "origin": "perl-symbol-global-name" + }, + "lua5.3-socket": { + "versions": { + "3.0_rc1_git20160306-r2": 1543934475 + }, + "origin": "lua-socket", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-test-leaktrace-doc": { + "versions": { + "0.16-r0": 1542821880 + }, + "origin": "perl-test-leaktrace" + }, + "diffutils-doc": { + "versions": { + "3.7-r0": 1546846562 + }, + "origin": "diffutils" + }, + "dovecot-submissiond": { + "versions": { + "2.3.6-r0": 1557134098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libdovecot-login.so.0", + "so:libdovecot-storage.so.0", + "so:libdovecot.so.0" + ] + }, + "cups-doc": { + "versions": { + "2.2.10-r0": 1545910846 + }, + "origin": "cups" + }, + "libunique-doc": { + "versions": { + "1.1.6-r6": 1545299841 + }, + "origin": "libunique" + }, + "gst-plugins-base": { + "versions": { + "1.14.4-r0": 1543928665 + }, + "origin": "gst-plugins-base", + "dependencies": [ + "so:libEGL.so.1", + "so:libGL.so.1", + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcdda_interface.so.0", + "so:libcdda_paranoia.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-1.0.so.0", + "so:libgstcontroller-1.0.so.0", + "so:libgstnet-1.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libintl.so.8", + "so:libogg.so.0", + "so:libopus.so.0", + "so:liborc-0.4.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpng16.so.16", + "so:libtheoradec.so.1", + "so:libtheoraenc.so.1", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2", + "so:libwayland-client.so.0", + "so:libwayland-egl.so.1", + "so:libxcb.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libgstadder.so=0", + "so:libgstallocators-1.0.so.0=0.1404.0", + "so:libgstalsa.so=0", + "so:libgstapp-1.0.so.0=0.1404.0", + "so:libgstapp.so=0", + "so:libgstaudio-1.0.so.0=0.1404.0", + "so:libgstaudioconvert.so=0", + "so:libgstaudiomixer.so=0", + "so:libgstaudiorate.so=0", + "so:libgstaudioresample.so=0", + "so:libgstaudiotestsrc.so=0", + "so:libgstcdparanoia.so=0", + "so:libgstencoding.so=0", + "so:libgstfft-1.0.so.0=0.1404.0", + "so:libgstgio.so=0", + "so:libgstgl-1.0.so.0=0.1404.0", + "so:libgstogg.so=0", + "so:libgstopengl.so=0", + "so:libgstopus.so=0", + "so:libgstpango.so=0", + "so:libgstpbtypes.so=0", + "so:libgstpbutils-1.0.so.0=0.1404.0", + "so:libgstplayback.so=0", + "so:libgstrawparse.so=0", + "so:libgstriff-1.0.so.0=0.1404.0", + "so:libgstrtp-1.0.so.0=0.1404.0", + "so:libgstrtsp-1.0.so.0=0.1404.0", + "so:libgstsdp-1.0.so.0=0.1404.0", + "so:libgstsubparse.so=0", + "so:libgsttag-1.0.so.0=0.1404.0", + "so:libgsttcp.so=0", + "so:libgsttheora.so=0", + "so:libgsttypefindfunctions.so=0", + "so:libgstvideo-1.0.so.0=0.1404.0", + "so:libgstvideoconvert.so=0", + "so:libgstvideorate.so=0", + "so:libgstvideoscale.so=0", + "so:libgstvideotestsrc.so=0", + "so:libgstvolume.so=0", + "so:libgstvorbis.so=0", + "so:libgstximagesink.so=0", + "so:libgstxvimagesink.so=0", + "cmd:gst-device-monitor-1.0", + "cmd:gst-discoverer-1.0", + "cmd:gst-play-1.0" + ] + }, + "py2-unidecode": { + "versions": { + "1.0.23-r0": 1545299542 + }, + "origin": "py-unidecode", + "dependencies": [ + "py3-unidecode", + "python2" + ], + "provides": [ + "cmd:unidecode-2" + ] + }, + "help2man": { + "versions": { + "1.47.8-r0": 1545745816 + }, + "origin": "help2man", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:help2man" + ] + }, + "perl-tree-dag_node": { + "versions": { + "1.31-r0": 1545213971 + }, + "origin": "perl-tree-dag_node", + "dependencies": [ + "perl-file-slurp-tiny" + ] + }, + "perl-php-serialization": { + "versions": { + "0.34-r1": 1545214147 + }, + "origin": "perl-php-serialization", + "dependencies": [ + "perl" + ] + }, + "py-django-registration": { + "versions": { + "1.0-r1": 1545207353 + }, + "origin": "py-django-registration", + "dependencies": [ + "python2" + ] + }, + "lua5.2-sec": { + "versions": { + "0.7-r1": 1545075388 + }, + "origin": "lua-sec", + "dependencies": [ + "lua5.2-socket", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "py3-ply": { + "versions": { + "3.11-r0": 1556798213 + }, + "origin": "py3-ply", + "dependencies": [ + "python3" + ] + }, + "perl-db_file-doc": { + "versions": { + "1.843-r0": 1545061210 + }, + "origin": "perl-db_file" + }, + "mupdf-dev": { + "versions": { + "1.13.0-r2": 1544000575 + }, + "origin": "mupdf", + "dependencies": [ + "mupdf=1.13.0-r2" + ] + }, + "opensp": { + "versions": { + "1.5.2-r0": 1542893110 + }, + "origin": "opensp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libosp.so.5=5.0.0", + "cmd:onsgmls", + "cmd:osgmlnorm", + "cmd:ospam", + "cmd:ospcat", + "cmd:ospent", + "cmd:osx" + ] + }, + "lcms2-doc": { + "versions": { + "2.9-r1": 1542824189 + }, + "origin": "lcms2" + }, + "gettext-libs": { + "versions": { + "0.19.8.1-r4": 1542304413 + }, + "origin": "gettext", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libunistring.so.2" + ], + "provides": [ + "so:libgettextpo.so.0=0.5.4" + ] + }, + "lua5.1-pty": { + "versions": { + "1.2.1-r1": 1545216358 + }, + "origin": "lua-pty", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua-uuid": { + "versions": { + "2012.05-r1": 1542883709 + }, + "origin": "lua-uuid", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ] + }, + "eudev-dev": { + "versions": { + "3.2.7-r0": 1542845384 + }, + "origin": "eudev", + "dependencies": [ + "eudev=3.2.7-r0", + "pkgconfig" + ], + "provides": [ + "pc:libudev=220", + "pc:udev=220" + ] + }, + "st": { + "versions": { + "0.8.1-r0": 1545292624 + }, + "origin": "st", + "dependencies": [ + "ncurses-terminfo", + "so:libX11.so.6", + "so:libXft.so.2", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1" + ], + "provides": [ + "cmd:st" + ] + }, + "mp3splt-gtk-lang": { + "versions": { + "0.9.2-r2": 1545069036 + }, + "origin": "mp3splt-gtk" + }, + "py-tz": { + "versions": { + "2018.9-r0": 1548109649 + }, + "origin": "py-tz" + }, + "libcap-ng-dev": { + "versions": { + "0.7.9-r1": 1545918386 + }, + "origin": "libcap-ng", + "dependencies": [ + "linux-headers", + "libcap-ng=0.7.9-r1", + "pkgconfig" + ], + "provides": [ + "pc:libcap-ng=0.7.9" + ] + }, + "gamin-dev": { + "versions": { + "0.1.10-r10": 1543928476 + }, + "origin": "gamin", + "dependencies": [ + "gamin=0.1.10-r10", + "pkgconfig" + ], + "provides": [ + "pc:gamin=0.1.10" + ] + }, + "msmtp": { + "versions": { + "1.8.1-r1": 1545062528 + }, + "origin": "msmtp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30", + "so:libintl.so.8" + ], + "provides": [ + "cmd:msmtp", + "cmd:msmtpd" + ] + }, + "zsh-calendar": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "py2-idna": { + "versions": { + "2.7-r0": 1542825009 + }, + "origin": "py-idna", + "dependencies": [ + "python2" + ] + }, + "postfix": { + "versions": { + "3.3.2-r0": 1547130363 + }, + "origin": "postfix", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libdb-5.3.so", + "so:libsasl2.so.3", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libpostfix-dns.so=0", + "so:libpostfix-global.so=0", + "so:libpostfix-master.so=0", + "so:libpostfix-tls.so=0", + "so:libpostfix-util.so=0", + "cmd:mailq", + "cmd:newaliases", + "cmd:postalias", + "cmd:postcat", + "cmd:postconf", + "cmd:postdrop", + "cmd:postfix", + "cmd:postkick", + "cmd:postlock", + "cmd:postlog", + "cmd:postmap", + "cmd:postmulti", + "cmd:postqueue", + "cmd:postsuper", + "cmd:sendmail" + ] + }, + "ebtables-doc": { + "versions": { + "2.0.10.4-r2": 1543935616 + }, + "origin": "ebtables" + }, + "oidentd-doc": { + "versions": { + "2.2.3-r0": 1545163555 + }, + "origin": "oidentd" + }, + "opensp-dev": { + "versions": { + "1.5.2-r0": 1542893109 + }, + "origin": "opensp", + "dependencies": [ + "opensp=1.5.2-r0" + ] + }, + "py3-werkzeug": { + "versions": { + "0.14.1-r0": 1549977321 + }, + "origin": "py-werkzeug", + "dependencies": [ + "python3" + ] + }, + "setpriv": { + "versions": { + "2.33-r0": 1545307437 + }, + "origin": "util-linux", + "dependencies": [ + "findmnt", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0" + ], + "provides": [ + "cmd:setpriv" + ] + }, + "hexchat": { + "versions": { + "2.14.2-r0": 1545746059 + }, + "origin": "hexchat", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libdbus-glib-1.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:liblua-5.3.so.0", + "so:libnotify.so.4", + "so:libpango-1.0.so.0", + "so:libproxy.so.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:hexchat" + ] + }, + "perl-module-scandeps": { + "versions": { + "1.25-r0": 1542893295 + }, + "origin": "perl-module-scandeps", + "provides": [ + "cmd:scandeps.pl" + ] + }, + "alpine-base": { + "versions": { + "3.9.4-r0": 1557434973 + }, + "origin": "alpine-base", + "dependencies": [ + "alpine-baselayout", + "alpine-conf", + "apk-tools", + "busybox", + "busybox-suid", + "busybox-initscripts", + "openrc", + "libc-utils", + "alpine-keys" + ] + }, + "pcmciautils": { + "versions": { + "018-r1": 1543077272 + }, + "origin": "pcmciautils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:lspcmcia", + "cmd:pccardctl" + ] + }, + "boost-random": { + "versions": { + "1.67.0-r2": 1542823633 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_system-mt.so.1.67.0", + "so:libboost_system.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_random-mt.so.1.67.0=1.67.0", + "so:libboost_random.so.1.67.0=1.67.0" + ] + }, + "lm_sensors-sensord-openrc": { + "versions": { + "3.4.0-r6": 1542924820 + }, + "origin": "lm_sensors", + "dependencies": [ + "bash", + "sysfsutils" + ] + }, + "perl-test2-plugin-nowarnings": { + "versions": { + "0.06-r0": 1542973053 + }, + "origin": "perl-test2-plugin-nowarnings", + "dependencies": [ + "perl-ipc-run3", + "perl-test2-suite", + "perl-test-simple" + ] + }, + "igmpproxy": { + "versions": { + "0.2.1-r0": 1545746723 + }, + "origin": "igmpproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:igmpproxy" + ] + }, + "nagios-plugins-overcr": { + "versions": { + "2.2.1-r6": 1543933910 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "linux-firmware-keyspan": { + "versions": { + "20190322-r0": 1554980651 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "duply": { + "versions": { + "2.1-r0": 1545300230 + }, + "origin": "duply", + "dependencies": [ + "duplicity", + "bash" + ], + "provides": [ + "cmd:duply" + ] + }, + "squid-lang-sr": { + "versions": { + "4.4-r1": 1545216329 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-error-doc": { + "versions": { + "0.17027-r0": 1545062330 + }, + "origin": "perl-error" + }, + "xscreensaver-doc": { + "versions": { + "5.40-r0": 1545224086 + }, + "origin": "xscreensaver" + }, + "perl-sub-exporter-progressive": { + "versions": { + "0.001013-r0": 1542883267 + }, + "origin": "perl-sub-exporter-progressive" + }, + "perl-devel-globaldestruction": { + "versions": { + "0.14-r0": 1545162998 + }, + "origin": "perl-devel-globaldestruction", + "dependencies": [ + "perl-sub-exporter-progressive" + ] + }, + "kamailio-ims": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libkamailio_ims.so.0", + "so:libsrdb1.so.1", + "so:libsrutils.so.1", + "so:libxml2.so.2" + ] + }, + "axel": { + "versions": { + "2.16.1-r2": 1545229308 + }, + "origin": "axel", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:axel" + ] + }, + "perl-libwww-doc": { + "versions": { + "6.36-r0": 1544791738 + }, + "origin": "perl-libwww" + }, + "libxcomposite-doc": { + "versions": { + "0.4.4-r2": 1543925370 + }, + "origin": "libxcomposite" + }, + "trac": { + "versions": { + "1.2.2-r0": 1543924853 + }, + "origin": "trac", + "dependencies": [ + "python2", + "py-setuptools", + "py-genshi", + "/bin/sh" + ], + "provides": [ + "cmd:trac-admin", + "cmd:tracd" + ] + }, + "openldap-back-bdb": { + "versions": { + "2.4.47-r2": 1546016477 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "py2-urllib3": { + "versions": { + "1.22-r0": 1542825019 + }, + "origin": "py-urllib3", + "dependencies": [ + "python2" + ] + }, + "unbound-libs": { + "versions": { + "1.8.3-r1": 1555953584 + }, + "origin": "unbound", + "dependencies": [ + "dnssec-root", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libevent-2.1.so.6", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libunbound.so.8=8.0.3" + ] + }, + "xauth-doc": { + "versions": { + "1.0.10-r1": 1542973224 + }, + "origin": "xauth" + }, + "perl-mro-compat": { + "versions": { + "0.13-r0": 1542972950 + }, + "origin": "perl-mro-compat" + }, + "augeas-dev": { + "versions": { + "1.11.0-r0": 1542924580 + }, + "origin": "augeas", + "dependencies": [ + "augeas-libs=1.11.0-r0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:augeas=1.11.0" + ] + }, + "sed": { + "versions": { + "4.5-r0": 1542822960 + }, + "origin": "sed", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sed" + ] + }, + "libassuan-dev": { + "versions": { + "2.5.1-r0": 1543932159 + }, + "origin": "libassuan", + "dependencies": [ + "libassuan=2.5.1-r0" + ], + "provides": [ + "cmd:libassuan-config" + ] + }, + "bluez-meshctl": { + "versions": { + "5.50-r0": 1545822205 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0", + "so:libjson-c.so.4", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:meshctl" + ] + }, + "perl-package-stash-doc": { + "versions": { + "0.37-r0": 1542845730 + }, + "origin": "perl-package-stash" + }, + "postfix-mysql": { + "versions": { + "3.3.2-r0": 1547130363 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ], + "provides": [ + "so:postfix-mysql.so=0" + ] + }, + "wxgtk2.8-media": { + "versions": { + "2.8.12.1-r4": 1545075287 + }, + "origin": "wxgtk2.8", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstinterfaces-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libstdc++.so.6", + "so:libwx_baseu-2.8.so.0", + "so:libwx_gtk2u_core-2.8.so.0" + ], + "provides": [ + "so:libwx_gtk2u_media-2.8.so.0=0.8.0" + ] + }, + "cpufrequtils-dev": { + "versions": { + "008-r4": 1543927814 + }, + "origin": "cpufrequtils", + "dependencies": [ + "cpufrequtils=008-r4" + ] + }, + "cpufreqd": { + "versions": { + "2.4.2-r4": 1543998859 + }, + "origin": "cpufreqd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcpufreq.so.0", + "so:libsysfs.so.2" + ], + "provides": [ + "so:cpufreqd_acpi.so=0", + "so:cpufreqd_apm.so=0", + "so:cpufreqd_cpu.so=0", + "so:cpufreqd_exec.so=0", + "so:cpufreqd_governor_parameters.so=0", + "so:cpufreqd_nforce2.so=0", + "so:cpufreqd_pmu.so=0", + "so:cpufreqd_programs.so=0", + "so:cpufreqd_tau.so=0", + "cmd:cpufreqd", + "cmd:cpufreqd-get", + "cmd:cpufreqd-set" + ] + }, + "shorewall": { + "versions": { + "5.2.2-r0": 1548095422 + }, + "origin": "shorewall", + "dependencies": [ + "shorewall-core", + "perl", + "iptables", + "iproute2" + ] + }, + "network-extras": { + "versions": { + "1.2-r0": 1545062256 + }, + "origin": "network-extras", + "dependencies": [ + "bridge", + "bonding", + "vlan", + "wpa_supplicant", + "wireless-tools", + "ppp-atm", + "ppp-chat", + "ppp-daemon", + "ppp-l2tp", + "ppp-minconn", + "ppp-passprompt", + "ppp-passwordfd", + "ppp-pppoe", + "ppp-radius", + "ppp-winbind", + "usb-modeswitch" + ] + }, + "lxterminal": { + "versions": { + "0.3.2-r0": 1543927689 + }, + "origin": "lxterminal", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libvte.so.9" + ], + "provides": [ + "cmd:lxterminal" + ] + }, + "py-werkzeug": { + "versions": { + "0.14.1-r0": 1549977321 + }, + "origin": "py-werkzeug" + }, + "lvm2-doc": { + "versions": { + "2.02.182-r0": 1543928933 + }, + "origin": "lvm2" + }, + "py2-crypto": { + "versions": { + "2.6.1-r2": 1543223470 + }, + "origin": "py-crypto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libpython2.7.so.1.0" + ] + }, + "perl-sub-identify": { + "versions": { + "0.14-r1": 1542973115 + }, + "origin": "perl-sub-identify", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "apache-mod-fcgid": { + "versions": { + "2.3.9-r2": 1545293007 + }, + "origin": "apache-mod-fcgid", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1" + ] + }, + "libxslt": { + "versions": { + "1.1.33-r1": 1555485888 + }, + "origin": "libxslt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libxml2.so.2" + ], + "provides": [ + "so:libexslt.so.0=0.8.20", + "so:libxslt.so.1=1.1.33", + "cmd:xsltproc" + ] + }, + "libusb-compat-dev": { + "versions": { + "0.1.5-r4": 1543921872 + }, + "origin": "libusb-compat", + "dependencies": [ + "libusb-compat=0.1.5-r4", + "pc:libusb-1.0", + "pkgconfig" + ], + "provides": [ + "pc:libusb=0.1.12", + "cmd:libusb-config" + ] + }, + "openldap-back-sock": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "perl-sub-exporter-doc": { + "versions": { + "0.987-r0": 1542883246 + }, + "origin": "perl-sub-exporter" + }, + "bash": { + "versions": { + "4.4.19-r1": 1542301270 + }, + "origin": "bash", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:bash" + ] + }, + "gphoto2-lang": { + "versions": { + "2.5.15-r0": 1545300678 + }, + "origin": "gphoto2" + }, + "perl-device-serialport-doc": { + "versions": { + "1.04-r10": 1545062309 + }, + "origin": "perl-device-serialport" + }, + "byacc": { + "versions": { + "20180609-r0": 1545062012 + }, + "origin": "byacc", + "dependencies": [ + "!bison", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:yacc" + ] + }, + "perl-data-guid": { + "versions": { + "0.049-r0": 1545163361 + }, + "origin": "perl-data-guid", + "dependencies": [ + "perl-sub-install", + "perl-sub-exporter", + "perl-data-uuid" + ] + }, + "py-argparse": { + "versions": { + "1.4.0-r2": 1545060593 + }, + "origin": "py-argparse" + }, + "ortp": { + "versions": { + "0.25.0-r0": 1543077282 + }, + "origin": "ortp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libortp.so.10=10.0.0" + ] + }, + "qemu-x86_64": { + "versions": { + "3.1.0-r3": 1551107306 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-x86_64" + ] + }, + "gnokii-dev": { + "versions": { + "0.6.31-r8": 1545300478 + }, + "origin": "gnokii", + "dependencies": [ + "gnokii-libs=0.6.31-r8", + "pkgconfig" + ], + "provides": [ + "pc:gnokii=0.6.31", + "pc:xgnokii=1.0" + ] + }, + "libmng-doc": { + "versions": { + "2.0.3-r1": 1545062629 + }, + "origin": "libmng" + }, + "nagios-plugins-tcp": { + "versions": { + "2.2.1-r6": 1543933914 + }, + "origin": "nagios-plugins", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "nagios-plugins-clamdnagios-plugins-ftpnagios-plugins-imapnagios-plugins-jabbernagios-plugins-nntpnagios-plugins-nntpsnagios-plugins-popnagios-plugins-simapnagios-plugins-spopnagios-plugins-ssmtpnagios-plugins-udp" + ] + }, + "squid": { + "versions": { + "4.4-r1": 1545216331 + }, + "origin": "squid", + "dependencies": [ + "logrotate", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.1.1", + "so:libdb-5.3.so", + "so:libgcc_s.so.1", + "so:libltdl.so.7", + "so:libssl.so.1.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:purge", + "cmd:squid", + "cmd:squidclient" + ] + }, + "cvechecker": { + "versions": { + "3.9-r1": 1543249725 + }, + "origin": "cvechecker", + "dependencies": [ + "gawk", + "wget", + "libxslt", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libconfig.so.9", + "so:libmariadb.so.3", + "so:libsqlite3.so.0" + ], + "provides": [ + "cmd:cvechecker", + "cmd:cvegenversdat", + "cmd:cvereport", + "cmd:cverules", + "cmd:pullcves" + ] + }, + "py2-markupsafe": { + "versions": { + "1.1.0-r0": 1544791904 + }, + "origin": "py-markupsafe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "lftp": { + "versions": { + "4.8.4-r1": 1545073926 + }, + "origin": "lftp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libncursesw.so.6", + "so:libreadline.so.7", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:cmd-mirror.so=0", + "so:cmd-sleep.so=0", + "so:cmd-torrent.so=0", + "so:liblftp-jobs.so.0=0.0.0", + "so:liblftp-network.so=0", + "so:liblftp-pty.so=0", + "so:liblftp-tasks.so.0=0.0.0", + "so:proto-file.so=0", + "so:proto-fish.so=0", + "so:proto-ftp.so=0", + "so:proto-http.so=0", + "so:proto-sftp.so=0", + "cmd:lftp", + "cmd:lftpget" + ] + }, + "mariadb-dev": { + "versions": { + "10.3.13-r1": 1557431734 + }, + "origin": "mariadb", + "dependencies": [ + "openssl-dev", + "zlib-dev", + "mariadb-connector-c-dev", + "mariadb-embedded=10.3.13-r1", + "pkgconfig" + ], + "provides": [ + "mysql-dev=10.3.13-r1" + ] + }, + "collectd-mysql": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "ntfs-3g-progs": { + "versions": { + "2017.3.23-r1": 1542973174 + }, + "origin": "ntfs-3g", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libntfs-3g.so.88", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:mkntfs", + "cmd:ntfs-3g.probe", + "cmd:ntfscat", + "cmd:ntfsclone", + "cmd:ntfscluster", + "cmd:ntfscmp", + "cmd:ntfscp", + "cmd:ntfsfix", + "cmd:ntfsinfo", + "cmd:ntfslabel", + "cmd:ntfsls", + "cmd:ntfsresize", + "cmd:ntfsundelete" + ] + }, + "py-gdbm": { + "versions": { + "2.7.16-r1": 1557171399 + }, + "origin": "python2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "python-gdbm=2.7.16-r1" + ] + }, + "clucene-contribs": { + "versions": { + "2.3.3.4-r5": 1543999111 + }, + "origin": "clucene", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libclucene-core.so.1", + "so:libclucene-shared.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libclucene-contribs-lib.so.1=2.3.3.4" + ] + }, + "sylpheed-lang": { + "versions": { + "3.7.0-r2": 1544799190 + }, + "origin": "sylpheed", + "dependencies": [ + "pinentry-gtk" + ] + }, + "cups-libs": { + "versions": { + "2.2.10-r0": 1545910846 + }, + "origin": "cups", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30", + "so:libz.so.1" + ], + "provides": [ + "so:libcups.so.2=2", + "so:libcupsimage.so.2=2" + ] + }, + "run-parts": { + "versions": { + "4.8.6-r0": 1543924425 + }, + "origin": "run-parts", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:run-parts" + ] + }, + "aspell-ru": { + "versions": { + "0.99f7-r0": 1543223264 + }, + "origin": "aspell-ru" + }, + "libxaw-doc": { + "versions": { + "1.0.13-r2": 1542894088 + }, + "origin": "libxaw" + }, + "vanessa_logger": { + "versions": { + "0.0.10-r0": 1542893600 + }, + "origin": "vanessa_logger", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libvanessa_logger.so.0=0.0.5", + "cmd:vanessa_logger_sample" + ] + }, + "perl-net-smtp-tls-butmaintained-doc": { + "versions": { + "0.24-r0": 1545235359 + }, + "origin": "perl-net-smtp-tls-butmaintained" + }, + "charybdis-openrc": { + "versions": { + "3.5.6-r1": 1542883316 + }, + "origin": "charybdis" + }, + "perl-tree-dag_node-doc": { + "versions": { + "1.31-r0": 1545213970 + }, + "origin": "perl-tree-dag_node" + }, + "altermime": { + "versions": { + "0.3.11-r0": 1543222722 + }, + "origin": "altermime", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:altermime" + ] + }, + "jpeg": { + "versions": { + "8-r6": 1542893530 + }, + "origin": "jpeg", + "dependencies": [ + "libjpeg-turbo-utils" + ] + }, + "qemu-system-hppa": { + "versions": { + "3.1.0-r3": 1551107303 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-hppa" + ] + }, + "font-misc-cyrillic": { + "versions": { + "1.0.3-r0": 1545207028 + }, + "origin": "font-misc-cyrillic", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "boost-coroutine": { + "versions": { + "1.67.0-r2": 1542823631 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_context-mt.so.1.67.0", + "so:libboost_system-mt.so.1.67.0", + "so:libboost_system.so.1.67.0", + "so:libboost_thread-mt.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_coroutine-mt.so.1.67.0=1.67.0", + "so:libboost_coroutine.so.1.67.0=1.67.0" + ] + }, + "gvpe": { + "versions": { + "3.1-r0": 1545214041 + }, + "origin": "gvpe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libgmp.so.10", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:gvpe", + "cmd:gvpectrl" + ] + }, + "linux-firmware-rtlwifi": { + "versions": { + "20190322-r0": 1554980648 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "xmlrpc-c-client++": { + "versions": { + "1.39.13-r0": 1545665215 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libxmlrpc++.so.8", + "so:libxmlrpc.so.3", + "so:libxmlrpc_client.so.3", + "so:libxmlrpc_packetsocket.so.8", + "so:libxmlrpc_util++.so.8", + "so:libxmlrpc_util.so.3" + ], + "provides": [ + "so:libxmlrpc_client++.so.8=8.39" + ] + }, + "nagios-plugins-dhcp": { + "versions": { + "2.2.1-r6": 1543933903 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-imagesize": { + "versions": { + "1.1.0-r0": 1542824997 + }, + "origin": "py-imagesize" + }, + "expect-dev": { + "versions": { + "5.45.4-r0": 1543246822 + }, + "origin": "expect" + }, + "consolekit2": { + "versions": { + "1.2.1-r1": 1545062381 + }, + "origin": "consolekit2", + "dependencies": [ + "polkit", + "eudev", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libpolkit-gobject-1.so.0", + "so:libudev.so.1", + "so:libz.so.1" + ], + "provides": [ + "consolekit=1.2.1", + "so:libck-connector.so.0=0.0.0", + "so:libconsolekit.so.1=1.0.0", + "cmd:ck-history", + "cmd:ck-launch-session", + "cmd:ck-list-sessions", + "cmd:ck-log-system-restart", + "cmd:ck-log-system-start", + "cmd:ck-log-system-stop", + "cmd:console-kit-daemon" + ] + }, + "bridge": { + "versions": { + "1.5-r3": 1545062228 + }, + "origin": "bridge" + }, + "linux-firmware-cpia2": { + "versions": { + "20190322-r0": 1554980653 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "figlet": { + "versions": { + "2.2.5-r0": 1545216341 + }, + "origin": "figlet", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:chkfont", + "cmd:figlet", + "cmd:figlist", + "cmd:showfigfonts" + ] + }, + "apache2-mod-wsgi-doc": { + "versions": { + "4.5.24-r0": 1545213946 + }, + "origin": "apache2-mod-wsgi" + }, + "xtables-addons-doc": { + "versions": { + "3.2-r0": 1545154536 + }, + "origin": "xtables-addons" + }, + "perl-net-libidn-doc": { + "versions": { + "0.12-r5": 1542924924 + }, + "origin": "perl-net-libidn" + }, + "libelf-dev": { + "versions": { + "0.8.13-r3": 1542900241 + }, + "origin": "libelf", + "dependencies": [ + "libelf=0.8.13-r3", + "pkgconfig" + ], + "provides": [ + "pc:libelf=0.8.13" + ] + }, + "uwsgi-stats_pusher_file": { + "versions": { + "2.0.17.1-r0": 1545062211 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-twitter": { + "versions": { + "3.5-r0": 1546524754 + }, + "origin": "py-twitter", + "dependencies": [ + "py2-future", + "py2-requests", + "py2-requests-oauthlib", + "python2" + ] + }, + "perl-text-csv": { + "versions": { + "1.97-r0": 1545163610 + }, + "origin": "perl-text-csv", + "dependencies": [ + "perl" + ] + }, + "http-parser": { + "versions": { + "2.8.1-r0": 1543934666 + }, + "origin": "http-parser", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libhttp_parser.so.2.8=2.8.1" + ] + }, + "syslog-ng-scl": { + "versions": { + "3.19.1-r0": 1548543149 + }, + "origin": "syslog-ng", + "dependencies": [ + "syslog-ng=3.19.1-r0" + ] + }, + "libotr": { + "versions": { + "4.1.1-r1": 1543248415 + }, + "origin": "libotr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20" + ], + "provides": [ + "so:libotr.so.5=5.1.1" + ] + }, + "openjade-doc": { + "versions": { + "1.3.2-r5": 1542893151 + }, + "origin": "openjade" + }, + "newsboat-doc": { + "versions": { + "2.13-r0": 1545075359 + }, + "origin": "newsboat" + }, + "rsyslog-mmrm1stspace": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "kbd-openrc": { + "versions": { + "2.0.4-r3": 1545215190 + }, + "origin": "kbd", + "dependencies": [ + "kbd-misc" + ] + }, + "lcms-dev": { + "versions": { + "1.19-r8": 1546249797 + }, + "origin": "lcms", + "dependencies": [ + "liblcms=1.19-r8", + "pkgconfig" + ], + "provides": [ + "pc:lcms=1.19" + ] + }, + "fcgiwrap-openrc": { + "versions": { + "1.1.0-r3": 1545665781 + }, + "origin": "fcgiwrap" + }, + "py2-sphinxcontrib-websupport": { + "versions": { + "1.0.1-r3": 1542825034 + }, + "origin": "py-sphinxcontrib-websupport", + "dependencies": [ + "python2" + ] + }, + "perl-test-output": { + "versions": { + "1.031-r0": 1545300174 + }, + "origin": "perl-test-output", + "dependencies": [ + "perl-capture-tiny", + "perl-sub-exporter", + "perl-test-simple" + ] + }, + "libmtp-examples": { + "versions": { + "1.1.15-r0": 1543924441 + }, + "origin": "libmtp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmtp.so.9" + ], + "provides": [ + "cmd:mtp-albumart", + "cmd:mtp-albums", + "cmd:mtp-connect", + "cmd:mtp-delfile", + "cmd:mtp-detect", + "cmd:mtp-emptyfolders", + "cmd:mtp-files", + "cmd:mtp-filetree", + "cmd:mtp-folders", + "cmd:mtp-format", + "cmd:mtp-getfile", + "cmd:mtp-getplaylist", + "cmd:mtp-hotplug", + "cmd:mtp-newfolder", + "cmd:mtp-newplaylist", + "cmd:mtp-playlists", + "cmd:mtp-reset", + "cmd:mtp-sendfile", + "cmd:mtp-sendtr", + "cmd:mtp-thumb", + "cmd:mtp-tracks", + "cmd:mtp-trexist" + ] + }, + "py2-requests-oauthlib": { + "versions": { + "0.8.0-r1": 1545062562 + }, + "origin": "py-requests-oauthlib", + "dependencies": [ + "py2-oauthlib", + "py2-requests", + "python2" + ] + }, + "perl-crypt-openssl-random-doc": { + "versions": { + "0.15-r1": 1543925841 + }, + "origin": "perl-crypt-openssl-random" + }, + "openldap-overlay-ppolicy": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2", + "so:libltdl.so.7" + ] + }, + "acf-skins": { + "versions": { + "0.6.0-r1": 1542883784 + }, + "origin": "acf-skins" + }, + "py2-incremental": { + "versions": { + "17.5.0-r0": 1546513617 + }, + "origin": "py-incremental", + "dependencies": [ + "python2" + ] + }, + "py-webassets": { + "versions": { + "0.12.1-r1": 1545214102 + }, + "origin": "py-webassets", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:webassets" + ] + }, + "wayland": { + "versions": { + "1.16.0-r0": 1544001084 + }, + "origin": "wayland", + "dependencies": [ + "wayland-libs-client", + "wayland-libs-cursor", + "wayland-libs-egl", + "wayland-libs-server" + ] + }, + "lua-posix": { + "versions": { + "33.4.0-r1": 1546010825 + }, + "origin": "lua-posix" + }, + "libmcrypt": { + "versions": { + "2.5.8-r7": 1545066949 + }, + "origin": "libmcrypt", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmcrypt.so.4=4.4.8" + ] + }, + "s6-doc": { + "versions": { + "2.7.2.0-r0": 1545062679 + }, + "origin": "s6" + }, + "syslinux-doc": { + "versions": { + "6.04_pre1-r2": 1543935715 + }, + "origin": "syslinux" + }, + "liblogging": { + "versions": { + "1.0.6-r0": 1545060630 + }, + "origin": "liblogging", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblogging-stdlog.so.0=0.1.0", + "cmd:stdlogctl" + ] + }, + "perl-www-robotrules": { + "versions": { + "6.02-r1": 1542821859 + }, + "origin": "perl-www-robotrules", + "dependencies": [ + "perl", + "perl-uri" + ] + }, + "flite-dev": { + "versions": { + "2.1-r0": 1545117048 + }, + "origin": "flite", + "dependencies": [ + "flite=2.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:flite=2.1" + ] + }, + "squid-lang-vi": { + "versions": { + "4.4-r1": 1545216331 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "open-iscsi-doc": { + "versions": { + "2.0.874-r2": 1547188207 + }, + "origin": "open-iscsi" + }, + "hdparm-doc": { + "versions": { + "9.58-r0": 1545745823 + }, + "origin": "hdparm" + }, + "libvdpau-doc": { + "versions": { + "1.1.1-r2": 1542900270 + }, + "origin": "libvdpau" + }, + "tcpflow-doc": { + "versions": { + "1.5.0-r0": 1545207691 + }, + "origin": "tcpflow" + }, + "libraw1394-doc": { + "versions": { + "2.1.2-r1": 1543932097 + }, + "origin": "libraw1394" + }, + "libconfig++": { + "versions": { + "1.5-r3": 1543249700 + }, + "origin": "libconfig", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libconfig++.so.9=9.2.0" + ] + }, + "ruby-dbm": { + "versions": { + "2.5.5-r0": 1557164836 + }, + "origin": "ruby", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libgdbm_compat.so.4", + "so:libruby.so.2.5" + ] + }, + "libvirt-lang": { + "versions": { + "4.10.0-r1": 1547051657 + }, + "origin": "libvirt", + "dependencies": [ + "lvm2" + ] + }, + "mysql": { + "versions": { + "10.3.13-r1": 1557431735 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb" + ] + }, + "collectd-redis": { + "versions": { + "5.8.0-r3": 1546422677 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.14" + ] + }, + "qemu-mips64": { + "versions": { + "3.1.0-r3": 1551107300 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mips64" + ] + }, + "uwsgi-stats_pusher_socket": { + "versions": { + "2.0.17.1-r0": 1545062211 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libsamplerate-dev": { + "versions": { + "0.1.9-r1": 1545068552 + }, + "origin": "libsamplerate", + "dependencies": [ + "libsamplerate=0.1.9-r1", + "pkgconfig" + ], + "provides": [ + "pc:samplerate=0.1.9" + ] + }, + "nano-syntax": { + "versions": { + "3.2-r0": 1545292880 + }, + "origin": "nano" + }, + "gsl-doc": { + "versions": { + "2.5-r0": 1545117968 + }, + "origin": "gsl" + }, + "perl-convert-tnef": { + "versions": { + "0.18-r1": 1544001412 + }, + "origin": "perl-convert-tnef", + "dependencies": [ + "perl" + ] + }, + "s6-linux-utils-doc": { + "versions": { + "2.5.0.0-r0": 1545299629 + }, + "origin": "s6-linux-utils" + }, + "tmux-doc": { + "versions": { + "2.8-r0": 1546590688 + }, + "origin": "tmux" + }, + "perl-convert-uulib-doc": { + "versions": { + "1.5-r2": 1544001423 + }, + "origin": "perl-convert-uulib" + }, + "lsyncd-openrc": { + "versions": { + "2.2.3-r1": 1545253929 + }, + "origin": "lsyncd", + "dependencies": [ + "rsync" + ] + }, + "linux-firmware-ueagle-atm": { + "versions": { + "20190322-r0": 1554980647 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "qemu-aarch64_be": { + "versions": { + "3.1.0-r3": 1551107299 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-aarch64_be" + ] + }, + "libdbi-doc": { + "versions": { + "0.9.0-r0": 1543932516 + }, + "origin": "libdbi" + }, + "mdadm-doc": { + "versions": { + "4.1-r0": 1545858162 + }, + "origin": "mdadm" + }, + "linux-firmware-keyspan_pda": { + "versions": { + "20190322-r0": 1554980651 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "openldap-back-hdb": { + "versions": { + "2.4.47-r2": 1546016477 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "perl-net-dns-resolver-programmable-doc": { + "versions": { + "0.009-r1": 1542925012 + }, + "origin": "perl-net-dns-resolver-programmable" + }, + "libnice-dev": { + "versions": { + "0.1.14-r2": 1543928834 + }, + "origin": "libnice", + "dependencies": [ + "libnice=0.1.14-r2", + "pc:gio-2.0>=2.44", + "pc:glib-2.0>=2.44", + "pc:gnutls>=2.12.0", + "pc:gobject-2.0>=2.44", + "pc:gthread-2.0", + "pkgconfig" + ], + "provides": [ + "pc:nice=0.1.14" + ] + }, + "samba-pidl": { + "versions": { + "4.8.11-r1": 1555334975 + }, + "origin": "samba", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:pidl" + ] + }, + "ssmtp": { + "versions": { + "2.64-r14": 1545215006 + }, + "origin": "ssmtp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:sendmail", + "cmd:ssmtp" + ] + }, + "xf86-video-savage-doc": { + "versions": { + "2.3.9-r3": 1545163475 + }, + "origin": "xf86-video-savage" + }, + "xscreensaver-gl-extras": { + "versions": { + "5.40-r0": 1545224087 + }, + "origin": "xscreensaver", + "dependencies": [ + "bc", + "so:libGL.so.1", + "so:libGLU.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXmu.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgdk_pixbuf_xlib-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "cmd:xscreensaver-gl-helper" + ] + }, + "nss-pam-ldapd-doc": { + "versions": { + "0.9.8-r0": 1542845538 + }, + "origin": "nss-pam-ldapd" + }, + "font-dec-misc": { + "versions": { + "1.0.3-r0": 1545302110 + }, + "origin": "font-dec-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "openvpn": { + "versions": { + "2.4.6-r4": 1543223245 + }, + "origin": "openvpn", + "dependencies": [ + "iproute2", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:liblzo2.so.2", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:openvpn" + ] + }, + "db-dev": { + "versions": { + "5.3.28-r1": 1542819052 + }, + "origin": "db", + "dependencies": [ + "db-c++=5.3.28-r1", + "db=5.3.28-r1" + ] + }, + "lua5.3-lub": { + "versions": { + "1.1.0-r1": 1544000684 + }, + "origin": "lua-lub", + "dependencies": [ + "lua5.3", + "lua5.3-filesystem" + ] + }, + "perl-crypt-rijndael": { + "versions": { + "1.13-r0": 1543923071 + }, + "origin": "perl-crypt-rijndael", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "aria2-doc": { + "versions": { + "1.34.0-r1": 1543998971 + }, + "origin": "aria2" + }, + "spl-doc": { + "versions": { + "0.7.8-r0": 1545117988 + }, + "origin": "spl" + }, + "libice-doc": { + "versions": { + "1.0.9-r3": 1542824333 + }, + "origin": "libice" + }, + "mailx-doc": { + "versions": { + "8.1.1-r1": 1545075408 + }, + "origin": "mailx" + }, + "fts": { + "versions": { + "1.2.7-r1": 1543933813 + }, + "origin": "fts", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfts.so.0=0.0.0" + ] + }, + "perl-date-extract": { + "versions": { + "0.06-r1": 1545163121 + }, + "origin": "perl-date-extract", + "dependencies": [ + "perl-datetime-format-natural", + "perl-class-data-inheritable", + "perl-test-mocktime" + ] + }, + "apache2-ssl": { + "versions": { + "2.4.39-r0": 1554306836 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "openssl", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "eggdrop-logs2html": { + "versions": { + "1.6.21-r2": 1545206947 + }, + "origin": "eggdrop", + "dependencies": [ + "tcl", + "so:libc.musl-x86_64.so.1" + ] + }, + "gptfdisk": { + "versions": { + "1.0.4-r0": 1543926506 + }, + "origin": "gptfdisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libstdc++.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:cgdisk", + "cmd:fixparts", + "cmd:gdisk" + ] + }, + "speedtest-cli": { + "versions": { + "2.0.2-r0": 1545209109 + }, + "origin": "speedtest-cli", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:speedtest", + "cmd:speedtest-cli" + ] + }, + "ksymoops-doc": { + "versions": { + "2.4.11-r7": 1545060802 + }, + "origin": "ksymoops" + }, + "pjproject-dev": { + "versions": { + "2.7.2-r4": 1545073632 + }, + "origin": "pjproject", + "dependencies": [ + "openssl-dev", + "alsa-lib-dev", + "gsm-dev", + "speex-dev", + "speexdsp-dev", + "portaudio-dev", + "libsrtp-dev", + "libsamplerate-dev", + "pjproject=2.7.2-r4", + "pkgconfig" + ], + "provides": [ + "pc:libpjproject=2.7.2" + ] + }, + "e2fsprogs": { + "versions": { + "1.44.5-r0": 1545745858 + }, + "origin": "e2fsprogs", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libe2p.so.2", + "so:libext2fs.so.2", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:e2fsck", + "cmd:fsck.ext2", + "cmd:fsck.ext3", + "cmd:fsck.ext4", + "cmd:mke2fs", + "cmd:mkfs.ext2", + "cmd:mkfs.ext3", + "cmd:mkfs.ext4" + ] + }, + "virglrenderer": { + "versions": { + "0.7.0-r1": 1547472270 + }, + "origin": "virglrenderer", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1" + ], + "provides": [ + "so:libvirglrenderer.so.0=0.2.0", + "cmd:virgl_test_server" + ] + }, + "lua5.3-yaml": { + "versions": { + "1.1.2-r1": 1544000071 + }, + "origin": "lua-yaml", + "dependencies": [ + "lua5.3", + "lua5.3-lub", + "so:libc.musl-x86_64.so.1" + ] + }, + "tslib-dev": { + "versions": { + "1.18-r0": 1545859355 + }, + "origin": "tslib", + "dependencies": [ + "pkgconfig", + "tslib=1.18-r0" + ], + "provides": [ + "pc:tslib=1.18" + ] + }, + "lua5.1-sec": { + "versions": { + "0.7-r1": 1545075388 + }, + "origin": "lua-sec", + "dependencies": [ + "lua5.1-socket", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "gst-plugins-base0.10-doc": { + "versions": { + "0.10.36-r4": 1543928810 + }, + "origin": "gst-plugins-base0.10" + }, + "py-jwt-cli": { + "versions": { + "1.6.4-r0": 1543249952 + }, + "origin": "py-jwt", + "dependencies": [ + "py3-jwt=1.6.4-r0" + ], + "provides": [ + "cmd:pyjwt" + ] + }, + "perl-test-mockobject-doc": { + "versions": { + "1.20180705-r0": 1545116825 + }, + "origin": "perl-test-mockobject" + }, + "libao-doc": { + "versions": { + "1.2.0-r3": 1545067193 + }, + "origin": "libao" + }, + "vte-dev": { + "versions": { + "0.28.2-r13": 1543927664 + }, + "origin": "vte", + "dependencies": [ + "pango-dev", + "gtk+2.0-dev", + "pc:cairo-xlib", + "pc:gio-2.0", + "pc:gio-unix-2.0", + "pc:glib-2.0>=2.26.0", + "pc:gobject-2.0", + "pc:gtk+-2.0>=2.20.0", + "pc:pango>=1.22.0", + "pc:x11", + "pkgconfig", + "vte=0.28.2-r13" + ], + "provides": [ + "pc:pyvte=0.28.2", + "pc:vte=0.28.2" + ] + }, + "perl-net-snmp": { + "versions": { + "6.0.1-r2": 1543923076 + }, + "origin": "perl-net-snmp", + "dependencies": [ + "perl-crypt-des", + "perl-crypt-rijndael", + "perl-digest-sha1" + ], + "provides": [ + "cmd:snmpkey" + ] + }, + "mariadb-connector-c-dev": { + "versions": { + "3.0.8-r0": 1547470538 + }, + "origin": "mariadb-connector-c", + "dependencies": [ + "openssl-dev", + "zlib-dev", + "mariadb-connector-c=3.0.8-r0", + "pkgconfig", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "pc:libmariadb=3.0.8", + "cmd:mariadb_config", + "cmd:mysql_config" + ] + }, + "btrfs-progs-doc": { + "versions": { + "4.19.1-r0": 1545665043 + }, + "origin": "btrfs-progs" + }, + "font-sony-misc": { + "versions": { + "1.0.3-r1": 1542924740 + }, + "origin": "font-sony-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "vte-lang": { + "versions": { + "0.28.2-r13": 1543927668 + }, + "origin": "vte" + }, + "perl-io-socket-inet6": { + "versions": { + "2.72-r0": 1543932733 + }, + "origin": "perl-io-socket-inet6", + "dependencies": [ + "perl-socket6" + ] + }, + "perl-list-someutils-xs-doc": { + "versions": { + "0.55-r0": 1543934252 + }, + "origin": "perl-list-someutils-xs" + }, + "bubblewrap-bash-completion": { + "versions": { + "0.3.1-r0": 1546247687 + }, + "origin": "bubblewrap" + }, + "cyrus-sasl": { + "versions": { + "2.1.27-r1": 1550353528 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:libkrb5.so.3", + "so:libsasl2.so.3" + ], + "provides": [ + "cmd:pluginviewer", + "cmd:saslauthd", + "cmd:sasldblistusers2", + "cmd:saslpasswd2", + "cmd:testsaslauthd" + ] + }, + "py3-psycopg2": { + "versions": { + "2.7.5-r0": 1544000430 + }, + "origin": "py-psycopg2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5", + "so:libpython3.6m.so.1.0" + ] + }, + "grub-bios": { + "versions": { + "2.02-r14": 1548432370 + }, + "origin": "grub", + "dependencies": [ + "grub" + ] + }, + "rabbitmq-c-utils": { + "versions": { + "0.8.0-r5": 1543923901 + }, + "origin": "rabbitmq-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0", + "so:librabbitmq.so.4" + ], + "provides": [ + "cmd:amqp-consume", + "cmd:amqp-declare-queue", + "cmd:amqp-delete-queue", + "cmd:amqp-get", + "cmd:amqp-publish" + ] + }, + "py-setuptools": { + "versions": { + "40.6.3-r0": 1547652242 + }, + "origin": "py-setuptools", + "dependencies": [ + "python2" + ], + "provides": [ + "py2-setuptools=40.6.3-r0", + "cmd:easy_install-2.7" + ] + }, + "uwsgi-router_static": { + "versions": { + "2.0.17.1-r0": 1545062208 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "bearssl-dev": { + "versions": { + "0.6-r0": 1546957733 + }, + "origin": "bearssl", + "dependencies": [ + "bearssl=0.6-r0" + ] + }, + "perl-time-modules-doc": { + "versions": { + "2013.0912-r0": 1545061985 + }, + "origin": "perl-time-modules" + }, + "libisoburn-dev": { + "versions": { + "1.4.8-r0": 1544797303 + }, + "origin": "libisoburn", + "dependencies": [ + "libisoburn=1.4.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:libisoburn-1=1.4.8" + ] + }, + "enca-doc": { + "versions": { + "1.19-r1": 1545076572 + }, + "origin": "enca" + }, + "msmtp-vim": { + "versions": { + "1.8.1-r1": 1545062528 + }, + "origin": "msmtp" + }, + "acf-snort": { + "versions": { + "0.8.0-r2": 1545163905 + }, + "origin": "acf-snort", + "dependencies": [ + "acf-core", + "snort" + ] + }, + "nagios-plugins-hpjd": { + "versions": { + "2.2.1-r6": 1543933905 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "libtirpc-dbg": { + "versions": { + "1.0.3-r0": 1543223617 + }, + "origin": "libtirpc" + }, + "devicemaster-linux-vanilla": { + "versions": { + "4.19.41-r0": 1557400832 + }, + "origin": "devicemaster-linux-vanilla", + "dependencies": [ + "linux-vanilla=4.19.41-r0" + ] + }, + "bacula": { + "versions": { + "9.4.1-r1": 1546944088 + }, + "origin": "bacula", + "dependencies": [ + "/bin/sh", + "so:libbac-9.4.1.so", + "so:libbaccfg-9.4.1.so", + "so:libbacfind-9.4.1.so", + "so:libbacsd-9.4.1.so", + "so:libbacsql-9.4.1.so", + "so:libc.musl-x86_64.so.1", + "so:liblzo2.so.2", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:bacula-dir", + "cmd:bacula-sd", + "cmd:bbconsjson", + "cmd:bcopy", + "cmd:bdirjson", + "cmd:bextract", + "cmd:bfdjson", + "cmd:bls", + "cmd:bregex", + "cmd:bscan", + "cmd:bsdjson", + "cmd:bsmtp", + "cmd:btape", + "cmd:bwild", + "cmd:dbcheck" + ] + }, + "dhcp-dev": { + "versions": { + "4.4.1-r1": 1543928451 + }, + "origin": "dhcp" + }, + "perl-sub-info-doc": { + "versions": { + "0.002-r0": 1542972994 + }, + "origin": "perl-sub-info" + }, + "linux-firmware-go7007": { + "versions": { + "20190322-r0": 1554980652 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "perl-net-cidr": { + "versions": { + "0.19-r0": 1544000981 + }, + "origin": "perl-net-cidr", + "dependencies": [ + "perl" + ] + }, + "dosfstools": { + "versions": { + "4.1-r1": 1545163465 + }, + "origin": "dosfstools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dosfsck", + "cmd:dosfslabel", + "cmd:fatlabel", + "cmd:fsck.fat", + "cmd:fsck.msdos", + "cmd:fsck.vfat", + "cmd:mkdosfs", + "cmd:mkfs.fat", + "cmd:mkfs.msdos", + "cmd:mkfs.vfat" + ] + }, + "perl-data-page-pageset-doc": { + "versions": { + "1.02-r1": 1545213962 + }, + "origin": "perl-data-page-pageset" + }, + "git-gitweb": { + "versions": { + "2.20.1-r0": 1545214195 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0", + "perl" + ] + }, + "perl-devel-stacktrace": { + "versions": { + "2.03-r0": 1542845798 + }, + "origin": "perl-devel-stacktrace" + }, + "userspace-rcu-doc": { + "versions": { + "0.10.1-r0": 1543249589 + }, + "origin": "userspace-rcu" + }, + "libmicrohttpd-dev": { + "versions": { + "0.9.62-r0": 1545300153 + }, + "origin": "libmicrohttpd", + "dependencies": [ + "libmicrohttpd=0.9.62-r0", + "pc:gnutls", + "pkgconfig" + ], + "provides": [ + "pc:libmicrohttpd=0.9.62" + ] + }, + "font-util-dev": { + "versions": { + "1.3.1-r2": 1542924710 + }, + "origin": "font-util", + "dependencies": [ + "font-util", + "pkgconfig" + ], + "provides": [ + "pc:fontutil=1.3.1" + ] + }, + "perl-date-format": { + "versions": { + "2.30-r0": 1543924842 + }, + "origin": "perl-date-format", + "dependencies": [ + "perl" + ] + }, + "perl-devel-stacktrace-doc": { + "versions": { + "2.03-r0": 1542845798 + }, + "origin": "perl-devel-stacktrace" + }, + "pcsc-lite-libs": { + "versions": { + "1.8.24-r1": 1545060761 + }, + "origin": "pcsc-lite", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcsclite.so.1=1.0.0", + "so:libpcscspy.so.0=0.0.0" + ] + }, + "libvdpau": { + "versions": { + "1.1.1-r2": 1542900270 + }, + "origin": "libvdpau", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libvdpau.so.1=1.0.0" + ] + }, + "py-gobject-doc": { + "versions": { + "2.28.7-r0": 1543927527 + }, + "origin": "py-gobject" + }, + "aspell-en": { + "versions": { + "2018.04.16-r0": 1545069363 + }, + "origin": "aspell-en" + }, + "ldapvi": { + "versions": { + "1.7-r10": 1545117995 + }, + "origin": "ldapvi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libglib-2.0.so.0", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libncursesw.so.6", + "so:libpopt.so.0", + "so:libreadline.so.7" + ], + "provides": [ + "cmd:ldapvi" + ] + }, + "libvirt-client": { + "versions": { + "4.10.0-r1": 1547051657 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt", + "pm-utils", + "gnutls-utils", + "netcat-openbsd", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libreadline.so.7", + "so:libtirpc.so.3", + "so:libvirt-admin.so.0", + "so:libvirt-lxc.so.0", + "so:libvirt-qemu.so.0", + "so:libvirt.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:virsh", + "cmd:virt-admin", + "cmd:virt-host-validate", + "cmd:virt-login-shell", + "cmd:virt-pki-validate", + "cmd:virt-xml-validate" + ] + }, + "libnet-dev": { + "versions": { + "1.1.6-r2": 1545060612 + }, + "origin": "libnet", + "dependencies": [ + "libnet=1.1.6-r2" + ], + "provides": [ + "cmd:libnet-config" + ] + }, + "py3-libvirt": { + "versions": { + "4.10.0-r0": 1545291074 + }, + "origin": "py-libvirt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0", + "so:libvirt-lxc.so.0", + "so:libvirt-qemu.so.0", + "so:libvirt.so.0" + ] + }, + "ruby-gdbm": { + "versions": { + "2.5.5-r0": 1557164836 + }, + "origin": "ruby", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libruby.so.2.5" + ] + }, + "py-idna": { + "versions": { + "2.7-r0": 1542825010 + }, + "origin": "py-idna" + }, + "openjpeg-tools": { + "versions": { + "2.3.0-r3": 1552584664 + }, + "origin": "openjpeg", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblcms2.so.2", + "so:libopenjp2.so.7", + "so:libpng16.so.16", + "so:libtiff.so.5" + ], + "provides": [ + "cmd:opj_compress", + "cmd:opj_decompress", + "cmd:opj_dump" + ] + }, + "qemu-system-arm": { + "versions": { + "3.1.0-r3": 1551107303 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libstdc++.so.6", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-arm" + ] + }, + "py3-tevent": { + "versions": { + "0.9.37-r1": 1543933087 + }, + "origin": "tevent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0", + "so:libtalloc.so.2", + "so:libtevent.so.0" + ] + }, + "py3-sphinxcontrib-websupport": { + "versions": { + "1.0.1-r3": 1542825037 + }, + "origin": "py-sphinxcontrib-websupport", + "dependencies": [ + "python3" + ] + }, + "nginx": { + "versions": { + "1.14.2-r1": 1557179822 + }, + "origin": "nginx", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpcre.so.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:nginx" + ] + }, + "collectd-postgresql": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "altermime-doc": { + "versions": { + "0.3.11-r0": 1543222722 + }, + "origin": "altermime" + }, + "openldap-mqtt": { + "versions": { + "2.4.47-r2": 1546016477 + }, + "origin": "openldap", + "dependencies": [ + "openldap", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libmosquitto.so.1" + ] + }, + "acf-weblog": { + "versions": { + "0.11.1-r1": 1545301038 + }, + "origin": "acf-weblog", + "dependencies": [ + "acf-core", + "lua-sql-postgres", + "wget", + "postgresql-client", + "lua-subprocess", + "/bin/sh" + ], + "provides": [ + "cmd:acf-weblog-update-schema" + ] + }, + "perl-carp-clan": { + "versions": { + "6.06-r1": 1545116510 + }, + "origin": "perl-carp-clan", + "dependencies": [ + "perl", + "perl-test-exception" + ] + }, + "libvirt-daemon": { + "versions": { + "4.10.0-r1": 1547051657 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-client", + "bridge-utils", + "dmidecode", + "dnsmasq", + "ebtables", + "ip6tables", + "iptables", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libintl.so.8", + "so:libtirpc.so.3", + "so:libvirt-lxc.so.0", + "so:libvirt-qemu.so.0", + "so:libvirt.so.0" + ], + "provides": [ + "cmd:libvirtd" + ] + }, + "logcheck-doc": { + "versions": { + "1.3.19-r0": 1545857328 + }, + "origin": "logcheck" + }, + "umix-doc": { + "versions": { + "1.0.2-r7": 1545235248 + }, + "origin": "umix" + }, + "acf-dnsmasq": { + "versions": { + "0.7.1-r0": 1545299894 + }, + "origin": "acf-dnsmasq", + "dependencies": [ + "acf-core", + "dnsmasq" + ] + }, + "perl-time-hires-doc": { + "versions": { + "1.9758-r0": 1545061217 + }, + "origin": "perl-time-hires" + }, + "sudo-doc": { + "versions": { + "1.8.25_p1-r2": 1542304826 + }, + "origin": "sudo" + }, + "uwsgi-logsocket": { + "versions": { + "2.0.17.1-r0": 1545062202 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "czmq": { + "versions": { + "4.1.1-r0": 1548683467 + }, + "origin": "czmq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1", + "so:libzmq.so.5" + ], + "provides": [ + "so:libczmq.so.4=4.1.1", + "cmd:zmakecert" + ] + }, + "strongswan-dbg": { + "versions": { + "5.7.2-r1": 1557161394 + }, + "origin": "strongswan", + "dependencies": [ + "iproute2" + ] + }, + "nginx-mod-http-fancyindex": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "ghi": { + "versions": { + "1.2.0-r3": 1544000928 + }, + "origin": "ghi", + "dependencies": [ + "ruby", + "ruby-json", + "ncurses", + "less" + ], + "provides": [ + "cmd:ghi" + ] + }, + "libqrencode-doc": { + "versions": { + "4.0.2-r0": 1543226702 + }, + "origin": "libqrencode" + }, + "openipmi-doc": { + "versions": { + "2.0.25-r1": 1545214270 + }, + "origin": "openipmi" + }, + "perl-test-nowarnings-doc": { + "versions": { + "1.04-r1": 1542845589 + }, + "origin": "perl-test-nowarnings" + }, + "uwsgi-zergpool": { + "versions": { + "2.0.17.1-r0": 1545062215 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libxres": { + "versions": { + "1.2.0-r1": 1545302056 + }, + "origin": "libxres", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXRes.so.1=1.0.0" + ] + }, + "lang": { + "versions": { + "0.1-r0": 1545293179 + }, + "origin": "lang" + }, + "qemu-system-lm32": { + "versions": { + "3.1.0-r3": 1551107303 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libX11.so.6", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-lm32" + ] + }, + "fuse-dev": { + "versions": { + "2.9.8-r2": 1542972242 + }, + "origin": "fuse", + "dependencies": [ + "fuse=2.9.8-r2", + "pkgconfig" + ], + "provides": [ + "pc:fuse=2.9.8" + ] + }, + "tftp-hpa": { + "versions": { + "5.2-r2": 1545207837 + }, + "origin": "tftp-hpa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:in.tftpd", + "cmd:tftp" + ] + }, + "xorg-server-xnest": { + "versions": { + "1.20.3-r1": 1543928069 + }, + "origin": "xorg-server", + "dependencies": [ + "so:libX11.so.6", + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXext.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpixman-1.so.0" + ], + "provides": [ + "cmd:Xnest" + ] + }, + "libxfixes": { + "versions": { + "5.0.3-r2": 1542823751 + }, + "origin": "libxfixes", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXfixes.so.3=3.1.0" + ] + }, + "mdocml-dev": { + "versions": { + "1.14.3-r0": 1542304778 + }, + "origin": "mdocml" + }, + "samba-initscript": { + "versions": { + "4.8.11-r1": 1555334972 + }, + "origin": "samba" + }, + "bacula-pgsql": { + "versions": { + "9.4.1-r1": 1546944087 + }, + "origin": "bacula", + "dependencies": [ + "bacula", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "so:libbaccats-postgresql-9.4.1.so=0" + ] + }, + "xf86-video-s3-doc": { + "versions": { + "0.6.5-r11": 1544792314 + }, + "origin": "xf86-video-s3" + }, + "font-cronyx-cyrillic": { + "versions": { + "1.0.3-r0": 1545165157 + }, + "origin": "font-cronyx-cyrillic", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "lua-file-magic": { + "versions": { + "0.2-r1": 1545116950 + }, + "origin": "lua-file-magic", + "dependencies": [ + "lua" + ] + }, + "perl-test-manifest-doc": { + "versions": { + "2.021-r0": 1543932508 + }, + "origin": "perl-test-manifest" + }, + "libdv-doc": { + "versions": { + "1.0.0-r3": 1543999340 + }, + "origin": "libdv" + }, + "sysklogd-doc": { + "versions": { + "1.5.1-r1": 1545213702 + }, + "origin": "sysklogd" + }, + "py3-openvswitch": { + "versions": { + "2.10.1-r0": 1544000345 + }, + "origin": "openvswitch", + "dependencies": [ + "py3-six" + ] + }, + "nmap-ncat": { + "versions": { + "7.70-r3": 1545163173 + }, + "origin": "nmap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:liblua-5.3.so.0", + "so:libpcap.so.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:ncat" + ] + }, + "perl-pathtools": { + "versions": { + "3.75-r0": 1545163545 + }, + "origin": "perl-pathtools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "lxc-dev": { + "versions": { + "3.1.0-r1": 1549966160 + }, + "origin": "lxc", + "dependencies": [ + "lxc-libs=3.1.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:lxc=3.1.0" + ] + }, + "lksctp-tools-doc": { + "versions": { + "1.0.17-r0": 1543241223 + }, + "origin": "lksctp-tools" + }, + "ghi-doc": { + "versions": { + "1.2.0-r3": 1544000928 + }, + "origin": "ghi" + }, + "perl-date-manip-doc": { + "versions": { + "6.75-r1": 1546098110 + }, + "origin": "perl-date-manip" + }, + "txt2man": { + "versions": { + "1.6.0-r0": 1545076644 + }, + "origin": "txt2man", + "dependencies": [ + "gawk" + ], + "provides": [ + "cmd:bookman", + "cmd:src2man", + "cmd:txt2man" + ] + }, + "qpdf-doc": { + "versions": { + "8.3.0-r0": 1547117323 + }, + "origin": "qpdf" + }, + "lvm2-dmeventd": { + "versions": { + "2.02.182-r0": 1543928933 + }, + "origin": "lvm2", + "dependencies": [ + "lvm2-libs=2.02.182-r0", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper-event.so.1.02", + "so:libdevmapper.so.1.02" + ], + "provides": [ + "cmd:dmeventd" + ] + }, + "perl-extutils-config-doc": { + "versions": { + "0.008-r0": 1542924640 + }, + "origin": "perl-extutils-config" + }, + "fetchmail-doc": { + "versions": { + "6.3.26-r14": 1544001391 + }, + "origin": "fetchmail" + }, + "perl-cgi-psgi": { + "versions": { + "0.15-r1": 1545208756 + }, + "origin": "perl-cgi-psgi", + "dependencies": [ + "perl-cgi" + ] + }, + "pciutils": { + "versions": { + "3.6.2-r0": 1543921867 + }, + "origin": "pciutils", + "dependencies": [ + "hwdata-pci", + "so:libc.musl-x86_64.so.1", + "so:libpci.so.3" + ], + "provides": [ + "cmd:lspci", + "cmd:setpci", + "cmd:update-pciids" + ] + }, + "celt051-dev": { + "versions": { + "0.5.1.3-r0": 1543935760 + }, + "origin": "celt051", + "dependencies": [ + "celt051=0.5.1.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:celt051=0.5.1.3" + ] + }, + "gstreamer0.10-dev": { + "versions": { + "0.10.36-r3": 1544000752 + }, + "origin": "gstreamer0.10", + "dependencies": [ + "glib-dev", + "libxml2-dev", + "gstreamer0.10=0.10.36-r3", + "pc:glib-2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gstreamer-0.10=0.10.36", + "pc:gstreamer-base-0.10=0.10.36", + "pc:gstreamer-check-0.10=0.10.36", + "pc:gstreamer-controller-0.10=0.10.36", + "pc:gstreamer-dataprotocol-0.10=0.10.36", + "pc:gstreamer-net-0.10=0.10.36" + ] + }, + "fsarchiver-doc": { + "versions": { + "0.8.5-r0": 1543226651 + }, + "origin": "fsarchiver" + }, + "dillo-doc": { + "versions": { + "3.0.5-r6": 1545302349 + }, + "origin": "dillo" + }, + "py3-sphinx_rtd_theme": { + "versions": { + "0.4.2-r0": 1544791909 + }, + "origin": "py-sphinx_rtd_theme", + "dependencies": [ + "python3" + ] + }, + "graphviz-graphs": { + "versions": { + "2.40.1-r1": 1543926741 + }, + "origin": "graphviz" + }, + "at-spi2-core-dbg": { + "versions": { + "2.28.0-r0": 1543241207 + }, + "origin": "at-spi2-core" + }, + "eventlog-dev": { + "versions": { + "0.2.12-r4": 1545118013 + }, + "origin": "eventlog", + "dependencies": [ + "eventlog=0.2.12-r4", + "pkgconfig" + ], + "provides": [ + "pc:eventlog=0.2.12" + ] + }, + "squid-lang-lt": { + "versions": { + "4.4-r1": 1545216327 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "iw": { + "versions": { + "4.14-r0": 1543249946 + }, + "origin": "iw", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnl-3.so.200", + "so:libnl-genl-3.so.200" + ], + "provides": [ + "cmd:iw" + ] + }, + "uwsgi-emperor_amqp": { + "versions": { + "2.0.17.1-r0": 1545062199 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.2-sql-odbc": { + "versions": { + "2.3.5-r2": 1543924402 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ] + }, + "mariadb-doc": { + "versions": { + "10.3.13-r1": 1557431733 + }, + "origin": "mariadb" + }, + "dbus-glib": { + "versions": { + "0.108-r1": 1542824780 + }, + "origin": "dbus-glib", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libexpat.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libdbus-glib-1.so.2=2.3.3", + "cmd:dbus-binding-tool" + ] + }, + "sparsehash-doc": { + "versions": { + "2.0.3-r0": 1545249827 + }, + "origin": "sparsehash" + }, + "kamailio-tls": { + "versions": { + "5.2.0-r1": 1546423170 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libssl.so.1.1" + ] + }, + "krb5-dev": { + "versions": { + "1.15.5-r0": 1548094457 + }, + "origin": "krb5", + "dependencies": [ + "e2fsprogs-dev", + "krb5-libs=1.15.5-r0", + "krb5-server-ldap=1.15.5-r0", + "pkgconfig" + ], + "provides": [ + "pc:gssrpc=1.15.5", + "pc:kadm-client=1.15.5", + "pc:kadm-server=1.15.5", + "pc:kdb=1.15.5", + "pc:krb5-gssapi=1.15.5", + "pc:krb5=1.15.5", + "pc:mit-krb5-gssapi=1.15.5", + "pc:mit-krb5=1.15.5", + "cmd:krb5-config" + ] + }, + "fprobe-doc": { + "versions": { + "1.1-r8": 1545213730 + }, + "origin": "fprobe" + }, + "gtk+3.0-dbg": { + "versions": { + "3.24.1-r0": 1543927436 + }, + "origin": "gtk+3.0", + "dependencies": [ + "shared-mime-info", + "gtk-update-icon-cache" + ] + }, + "freeswitch-pgsql": { + "versions": { + "1.8.2-r1": 1545117893 + }, + "origin": "freeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreeswitch.so.1", + "so:libpq.so.5" + ] + }, + "libxfont": { + "versions": { + "1.5.4-r1": 1542924729 + }, + "origin": "libxfont", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfontenc.so.1", + "so:libfreetype.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libXfont.so.1=1.4.1" + ] + }, + "py2-typing": { + "versions": { + "3.6.6-r0": 1543921909 + }, + "origin": "py-typing", + "dependencies": [ + "python2" + ] + }, + "wayland-libs-egl": { + "versions": { + "1.16.0-r0": 1544001083 + }, + "origin": "wayland", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libwayland-egl.so.1=1.0.0" + ] + }, + "lua5.1-stringy": { + "versions": { + "0.5.0-r1": 1545116589 + }, + "origin": "lua-stringy", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "pinentry-gtk": { + "versions": { + "1.1.0-r0": 1543932176 + }, + "origin": "pinentry", + "dependencies": [ + "/bin/sh", + "so:libassuan.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgpg-error.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:pinentry-gtk-2" + ] + }, + "rsync-doc": { + "versions": { + "3.1.3-r1": 1542820862 + }, + "origin": "rsync" + }, + "py2-curl": { + "versions": { + "7.43.0.2-r1": 1543935132 + }, + "origin": "py-curl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libpython2.7.so.1.0", + "so:libssl.so.1.1" + ] + }, + "uwsgi-pty": { + "versions": { + "2.0.17.1-r0": 1545062204 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "linux-firmware-advansys": { + "versions": { + "20190322-r0": 1554980655 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "acf-provisioning": { + "versions": { + "0.10.1-r0": 1545163176 + }, + "origin": "acf-provisioning", + "dependencies": [ + "acf-core", + "lua-sql-postgres", + "postgresql-client", + "lua-posixtz", + "lua-xml" + ] + }, + "perl-module-refresh": { + "versions": { + "0.17-r1": 1545292779 + }, + "origin": "perl-module-refresh" + }, + "cvs-doc": { + "versions": { + "1.11.23-r0": 1542893528 + }, + "origin": "cvs" + }, + "at-spi2-core-dev": { + "versions": { + "2.28.0-r0": 1543241208 + }, + "origin": "at-spi2-core", + "dependencies": [ + "libxtst-dev", + "at-spi2-core=2.28.0-r0", + "pc:dbus-1", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:atspi-2=2.28.0" + ] + }, + "perl-log-dispatch-doc": { + "versions": { + "2.68-r0": 1545209654 + }, + "origin": "perl-log-dispatch" + }, + "perl-test-mockrandom-doc": { + "versions": { + "1.01-r1": 1543238910 + }, + "origin": "perl-test-mockrandom" + }, + "perl-module-runtime": { + "versions": { + "0.016-r1": 1542845695 + }, + "origin": "perl-module-runtime", + "dependencies": [ + "perl-params-classify", + "perl-module-build" + ] + }, + "libunistring-doc": { + "versions": { + "0.9.10-r0": 1542304035 + }, + "origin": "libunistring" + }, + "brlaser": { + "versions": { + "3-r0": 1545214282 + }, + "origin": "brlaser", + "dependencies": [ + "cups-filters", + "so:libc.musl-x86_64.so.1", + "so:libcupsimage.so.2", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ] + }, + "bash-dbg": { + "versions": { + "4.4.19-r1": 1542301270 + }, + "origin": "bash" + }, + "the_silver_searcher-bash-completion": { + "versions": { + "2.1.0-r2": 1543998780 + }, + "origin": "the_silver_searcher" + }, + "directfb-dev": { + "versions": { + "1.7.7-r1": 1543934412 + }, + "origin": "directfb", + "dependencies": [ + "directfb=1.7.7-r1", + "pkgconfig" + ], + "provides": [ + "pc:++dfb=1.7.7", + "pc:direct=1.7.7", + "pc:directfb-internal=1.7.7", + "pc:directfb=1.7.7", + "pc:fusion=1.7.7", + "cmd:directfb-config" + ] + }, + "perl-params-util": { + "versions": { + "1.07-r5": 1542845640 + }, + "origin": "perl-params-util", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-jinja2": { + "versions": { + "2.10-r2": 1542824938 + }, + "origin": "py-jinja2", + "dependencies": [ + "py-markupsafe" + ] + }, + "apg": { + "versions": { + "2.2.3-r4": 1545207842 + }, + "origin": "apg", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:apg", + "cmd:apgbfm" + ] + }, + "libnjb": { + "versions": { + "2.2.7-r4": 1545300193 + }, + "origin": "libnjb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-0.1.so.4" + ], + "provides": [ + "so:libnjb.so.5=5.1.1" + ] + }, + "nmap-nselibs": { + "versions": { + "7.70-r3": 1545163173 + }, + "origin": "nmap" + }, + "perl-test-mockmodule-doc": { + "versions": { + "0.15-r0": 1543932008 + }, + "origin": "perl-test-mockmodule" + }, + "perl-test-warnings": { + "versions": { + "0.026-r0": 1542845612 + }, + "origin": "perl-test-warnings" + }, + "apr-dev": { + "versions": { + "1.6.5-r0": 1544000273 + }, + "origin": "apr", + "dependencies": [ + "apr", + "util-linux-dev", + "bash", + "pkgconfig" + ], + "provides": [ + "pc:apr-1=1.6.5", + "cmd:apr-1-config" + ] + }, + "libmilter-dev": { + "versions": { + "1.0.2-r6": 1543927483 + }, + "origin": "libmilter", + "dependencies": [ + "libmilter=1.0.2-r6" + ] + }, + "py2-roman": { + "versions": { + "2.0.0-r3": 1543998690 + }, + "origin": "py-roman", + "dependencies": [ + "python2" + ] + }, + "lua5.3-b64": { + "versions": { + "0.1-r2": 1545293186 + }, + "origin": "lua-b64", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "nghttp2-doc": { + "versions": { + "1.35.1-r0": 1545858450 + }, + "origin": "nghttp2" + }, + "libglade": { + "versions": { + "2.6.4-r14": 1543927551 + }, + "origin": "libglade", + "dependencies": [ + "libxml2-utils", + "/bin/sh", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libglade-2.0.so.0=0.0.7", + "cmd:libglade-convert" + ] + }, + "perl-term-readkey-doc": { + "versions": { + "2.37-r1": 1543922483 + }, + "origin": "perl-term-readkey" + }, + "perl-super-doc": { + "versions": { + "1.20141117-r0": 1543932004 + }, + "origin": "perl-super" + }, + "gtk-engines-dev": { + "versions": { + "2.21.0-r2": 1545289336 + }, + "origin": "gtk-engines", + "dependencies": [ + "pc:gtk+-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gtk-engines-2=2.21.0" + ] + }, + "perl-test-deep-doc": { + "versions": { + "1.128-r0": 1542845599 + }, + "origin": "perl-test-deep" + }, + "perl-string-shellquote-doc": { + "versions": { + "1.04-r0": 1545302145 + }, + "origin": "perl-string-shellquote" + }, + "portaudio": { + "versions": { + "190600.20161030-r0": 1545073464 + }, + "origin": "portaudio", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libjack.so.0" + ], + "provides": [ + "so:libportaudio.so.2=2.0.0" + ] + }, + "wavpack-doc": { + "versions": { + "5.1.0-r7": 1548939905 + }, + "origin": "wavpack" + }, + "dejagnu-doc": { + "versions": { + "1.6.2-r0": 1545820393 + }, + "origin": "dejagnu" + }, + "libxxf86vm-dev": { + "versions": { + "1.1.4-r2": 1542900277 + }, + "origin": "libxxf86vm", + "dependencies": [ + "libxxf86vm=1.1.4-r2", + "pc:x11", + "pc:xext", + "pc:xf86vidmodeproto", + "pkgconfig" + ], + "provides": [ + "pc:xxf86vm=1.1.4" + ] + }, + "perl-file-tail": { + "versions": { + "1.3-r1": 1545116502 + }, + "origin": "perl-file-tail" + }, + "iscsi-scst": { + "versions": { + "2.2.1-r2": 1545302142 + }, + "origin": "iscsi-scst", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:iscsi-scst-adm", + "cmd:iscsi-scstd" + ] + }, + "zfs-vanilla-dev": { + "versions": { + "4.19.41-r0": 1557400769 + }, + "origin": "zfs-vanilla", + "dependencies": [ + "glib-dev", + "e2fsprogs-dev", + "util-linux-dev", + "libtirpc-dev", + "linux-vanilla-dev=4.19.41-r0", + "spl-vanilla-dev" + ] + }, + "libpaper": { + "versions": { + "1.1.24-r4": 1542824770 + }, + "origin": "libpaper", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpaper.so.1=1.1.2", + "cmd:paperconf", + "cmd:paperconfig" + ] + }, + "librsync": { + "versions": { + "2.0.2-r1": 1543932724 + }, + "origin": "librsync", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0" + ], + "provides": [ + "so:librsync.so.2=2.0.2", + "cmd:rdiff" + ] + }, + "pango-dev": { + "versions": { + "1.42.4-r0": 1542824069 + }, + "origin": "pango", + "dependencies": [ + "pango=1.42.4-r0", + "pc:cairo", + "pc:fontconfig", + "pc:freetype2", + "pc:fribidi", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:harfbuzz", + "pc:xft", + "pkgconfig" + ], + "provides": [ + "pc:pango=1.42.4", + "pc:pangocairo=1.42.4", + "pc:pangoft2=1.42.4", + "pc:pangoxft=1.42.4" + ] + }, + "perl-dist-checkconflicts-doc": { + "versions": { + "0.11-r0": 1542845698 + }, + "origin": "perl-dist-checkconflicts" + }, + "py3-cffi": { + "versions": { + "1.11.5-r0": 1543924871 + }, + "origin": "py-cffi", + "dependencies": [ + "py3-cparser", + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libpython3.6m.so.1.0" + ] + }, + "py3-constantly": { + "versions": { + "15.1.0-r0": 1546513613 + }, + "origin": "py-constantly", + "dependencies": [ + "python3" + ] + }, + "atk-lang": { + "versions": { + "2.30.0-r0": 1542823743 + }, + "origin": "atk" + }, + "libmemcached": { + "versions": { + "1.0.18-r3": 1543923893 + }, + "origin": "libmemcached", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libmemcached.so.11", + "so:libmemcachedutil.so.2", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:memcapable", + "cmd:memcat", + "cmd:memcp", + "cmd:memdump", + "cmd:memerror", + "cmd:memexist", + "cmd:memflush", + "cmd:memparse", + "cmd:memping", + "cmd:memrm", + "cmd:memslap", + "cmd:memstat", + "cmd:memtouch" + ] + }, + "mrtg": { + "versions": { + "2.17.7-r0": 1545858156 + }, + "origin": "mrtg", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3" + ], + "provides": [ + "cmd:cfgmaker", + "cmd:indexmaker", + "cmd:mrtg", + "cmd:mrtg-traffic-sum", + "cmd:rateup" + ] + }, + "libnl3-doc": { + "versions": { + "3.4.0-r0": 1542965891 + }, + "origin": "libnl3" + }, + "libass-dev": { + "versions": { + "0.14.0-r0": 1545300164 + }, + "origin": "libass", + "dependencies": [ + "enca-dev", + "fontconfig-dev", + "fribidi-dev", + "freetype-dev", + "libass=0.14.0-r0", + "pc:fontconfig>=2.10.92", + "pc:freetype2>=9.10.3", + "pc:fribidi>=0.19.0", + "pkgconfig" + ], + "provides": [ + "pc:libass=0.14.0" + ] + }, + "libxfont2-dev": { + "versions": { + "2.0.3-r1": 1542924620 + }, + "origin": "libxfont2", + "dependencies": [ + "libxfont2=2.0.3-r1", + "pc:fontenc", + "pc:fontsproto", + "pc:freetype2", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xfont2=2.0.3" + ] + }, + "perl-email-address": { + "versions": { + "1.909-r0": 1542893278 + }, + "origin": "perl-email-address", + "dependencies": [ + "perl", + "perl-capture-tiny" + ] + }, + "dhcpcd-ui-doc": { + "versions": { + "0.7.5-r2": 1545060722 + }, + "origin": "dhcpcd-ui" + }, + "lua5.2-b64": { + "versions": { + "0.1-r2": 1545293185 + }, + "origin": "lua-b64", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "eggdrop": { + "versions": { + "1.6.21-r2": 1545206952 + }, + "origin": "eggdrop", + "dependencies": [ + "tcl", + "so:libc.musl-x86_64.so.1", + "so:libtcl8.6.so", + "so:libz.so.1" + ], + "provides": [ + "cmd:eggdrop" + ] + }, + "perl-crypt-x509": { + "versions": { + "0.51-r0": 1545207018 + }, + "origin": "perl-crypt-x509", + "dependencies": [ + "perl-convert-asn1" + ] + }, + "perl-params-validationcompiler-doc": { + "versions": { + "0.27-r0": 1542973092 + }, + "origin": "perl-params-validationcompiler" + }, + "linux-firmware-r128": { + "versions": { + "20190322-r0": 1554980649 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "font-adobe-75dpi": { + "versions": { + "1.0.3-r0": 1543241232 + }, + "origin": "font-adobe-75dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "varnish-libs": { + "versions": { + "6.1.1-r0": 1545300751 + }, + "origin": "varnish", + "dependencies": [ + "gcc", + "libc-dev", + "libgcc", + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ], + "provides": [ + "so:libvarnishapi.so.2=2.0.0" + ] + }, + "samba-dc-libs": { + "versions": { + "4.8.11-r1": 1555334975 + }, + "origin": "samba", + "dependencies": [ + "samba-server=4.8.11-r1", + "samba-client=4.8.11-r1", + "samba-common-tools=4.8.11-r1", + "so:libMESSAGING-SEND-samba4.so", + "so:libMESSAGING-samba4.so", + "so:libasn1-samba4.so.8", + "so:libasn1util-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-ldap-samba4.so", + "so:libcli-nbt-samba4.so", + "so:libcliauth-samba4.so", + "so:libcom_err.so.2", + "so:libcommon-auth-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libdcerpc-samba4.so", + "so:libdcerpc.so.0", + "so:libdsdb-garbage-collect-tombstones-samba4.so", + "so:libevents-samba4.so", + "so:libflag-mapping-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgnutls.so.30", + "so:libhcrypto-samba4.so.5", + "so:libhx509-samba4.so.5", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:libmessages-dgm-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libnetif-samba4.so", + "so:libnpa-tstream-samba4.so", + "so:libpam.so.0", + "so:libpopt.so.0", + "so:libpython2.7.so.1.0", + "so:libreplace-samba4.so", + "so:libroken-samba4.so.19", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-net-samba4.so", + "so:libsamba-python-samba4.so", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libsmb-transport-samba4.so", + "so:libsmbclient-raw-samba4.so", + "so:libsocket-blocking-samba4.so", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libwbclient.so.0" + ], + "provides": [ + "so:libHDB-SAMBA4-samba4.so=0", + "so:libLIBWBCLIENT-OLD-samba4.so=0", + "so:libauth-unix-token-samba4.so=0", + "so:libauth4-samba4.so=0", + "so:libcluster-samba4.so=0", + "so:libdb-glue-samba4.so=0", + "so:libdcerpc-samr.so.0=0.0.1", + "so:libdcerpc-server.so.0=0.0.1", + "so:libdnsserver-common-samba4.so=0", + "so:libdsdb-module-samba4.so=0", + "so:libhdb-samba4.so.11=11.0.2", + "so:libkdc-samba4.so.2=2.0.0", + "so:libpac-samba4.so=0", + "so:libposix-eadb-samba4.so=0", + "so:libprocess-model-samba4.so=0", + "so:libsamba-policy.so.0=0.0.1", + "so:libservice-samba4.so=0", + "so:libshares-samba4.so=0" + ] + }, + "knock": { + "versions": { + "0.7-r2": 1551279491 + }, + "origin": "knock", + "dependencies": [ + "iptables", + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:knock", + "cmd:knockd" + ] + }, + "asterisk-dbg": { + "versions": { + "15.7.1-r0": 1546247583 + }, + "origin": "asterisk" + }, + "nagios": { + "versions": { + "3.5.1-r4": 1543924481 + }, + "origin": "nagios", + "dependencies": [ + "perl", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3", + "so:libperl.so" + ], + "provides": [ + "cmd:nagios", + "cmd:nagiostats" + ] + }, + "lua-imlib2": { + "versions": { + "0.1-r2": 1543221707 + }, + "origin": "lua-imlib2", + "dependencies": [ + "so:libImlib2.so.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "mkfontscale-doc": { + "versions": { + "1.1.3-r1": 1542924691 + }, + "origin": "mkfontscale" + }, + "openldap-overlay-memberof": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "uwsgi-geoip": { + "versions": { + "2.0.17.1-r0": 1545062200 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "freeradius-radclient": { + "versions": { + "3.0.17-r5": 1556201988 + }, + "origin": "freeradius", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreeradius-radius.so", + "so:libtalloc.so.2" + ], + "provides": [ + "freeradius3-radclient=3.0.17-r5", + "cmd:radclient" + ] + }, + "libvncserver-dev": { + "versions": { + "0.9.11-r2": 1543248464 + }, + "origin": "libvncserver", + "dependencies": [ + "libgcrypt-dev", + "libjpeg-turbo-dev", + "gnutls-dev", + "libpng-dev", + "libice-dev", + "libx11-dev", + "libxdamage-dev", + "libxext-dev", + "libxfixes-dev", + "libxi-dev", + "libxinerama-dev", + "libxrandr-dev", + "libxtst-dev", + "libvncserver=0.9.11-r2", + "pc:zlib", + "pkgconfig" + ], + "provides": [ + "pc:libvncclient=0.9.11", + "pc:libvncserver=0.9.11", + "cmd:libvncserver-config" + ] + }, + "cunit-doc": { + "versions": { + "2.1.3-r1": 1542900198 + }, + "origin": "cunit" + }, + "openobex-doc": { + "versions": { + "1.7.2-r1": 1545069090 + }, + "origin": "openobex" + }, + "uwsgi-curl_cron": { + "versions": { + "2.0.17.1-r0": 1545062198 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4" + ] + }, + "libtheora-dev": { + "versions": { + "1.1.1-r13": 1543928528 + }, + "origin": "libtheora", + "dependencies": [ + "libogg-dev", + "libtheora=1.1.1-r13", + "pc:ogg>=1.1", + "pkgconfig" + ], + "provides": [ + "pc:theora=1.1.1", + "pc:theoradec=1.1.1", + "pc:theoraenc=1.1.1" + ] + }, + "gsm": { + "versions": { + "1.0.18-r0": 1543927819 + }, + "origin": "gsm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgsm.so.1=1.0.12" + ] + }, + "libatomic_ops-doc": { + "versions": { + "7.6.4-r0": 1543226876 + }, + "origin": "libatomic_ops" + }, + "python2": { + "versions": { + "2.7.16-r1": 1557171399 + }, + "origin": "python2", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libexpat.so.1", + "so:libffi.so.6", + "so:libgdbm_compat.so.4", + "so:libncursesw.so.6", + "so:libpanelw.so.6", + "so:libreadline.so.7", + "so:libsqlite3.so.0", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "python=2.7.16-r1", + "so:libpython2.7.so.1.0=1.0", + "cmd:idle", + "cmd:pydoc", + "cmd:python", + "cmd:python2", + "cmd:python2.7", + "cmd:smtpd.py" + ] + }, + "mariadb-embedded-dev": { + "versions": { + "10.3.13-r1": 1557431732 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common", + "mariadb-embedded=10.3.13-r1" + ] + }, + "tk-dev": { + "versions": { + "8.6.9-r0": 1545915089 + }, + "origin": "tk", + "dependencies": [ + "tcl-dev", + "libx11-dev", + "libxft-dev", + "fontconfig-dev", + "pc:tcl>=8.6", + "pkgconfig" + ], + "provides": [ + "pc:tk=8.6.9" + ] + }, + "gphoto2": { + "versions": { + "2.5.15-r0": 1545300680 + }, + "origin": "gphoto2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexif.so.12", + "so:libgphoto2.so.6", + "so:libgphoto2_port.so.12", + "so:libintl.so.8", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:gphoto2" + ] + }, + "geoip-doc": { + "versions": { + "1.6.12-r1": 1544799415 + }, + "origin": "geoip" + }, + "haserl": { + "versions": { + "0.9.35-r1": 1542883797 + }, + "origin": "haserl", + "dependencies": [ + "haserl-lua5.3", + "haserl-lua5.2", + "haserl-lua5.1", + "haserl-lua5.3=0.9.35-r1" + ] + }, + "tslib-doc": { + "versions": { + "1.18-r0": 1545859355 + }, + "origin": "tslib" + }, + "postfix-lmdb": { + "versions": { + "3.3.2-r0": 1547130363 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblmdb.so.0" + ], + "provides": [ + "so:postfix-lmdb.so=0" + ] + }, + "texinfo": { + "versions": { + "6.5-r1": 1542300392 + }, + "origin": "texinfo", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:info", + "cmd:install-info", + "cmd:makeinfo", + "cmd:pdftexi2dvi", + "cmd:pod2texi", + "cmd:texi2any", + "cmd:texi2dvi", + "cmd:texi2pdf", + "cmd:texindex" + ] + }, + "perl-switch-doc": { + "versions": { + "2.17-r0": 1543998982 + }, + "origin": "perl-switch" + }, + "libxdmcp": { + "versions": { + "1.1.2-r5": 1542822510 + }, + "origin": "libxdmcp", + "dependencies": [ + "so:libbsd.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXdmcp.so.6=6.0.0" + ] + }, + "openobex": { + "versions": { + "1.7.2-r1": 1545069091 + }, + "origin": "openobex", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libopenobex.so.2=1.7.2", + "cmd:obex-check-device" + ] + }, + "hexchat-doc": { + "versions": { + "2.14.2-r0": 1545746058 + }, + "origin": "hexchat" + }, + "cyrus-sasl-plain": { + "versions": { + "2.1.27-r1": 1550353528 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "apkbuild-cpan": { + "versions": { + "3.3.1-r0": 1551786761 + }, + "origin": "abuild", + "dependencies": [ + "perl", + "perl-libwww", + "perl-json", + "perl-module-build-tiny" + ], + "provides": [ + "cmd:apkbuild-cpan" + ] + }, + "perl-test-longstring-doc": { + "versions": { + "0.17-r0": 1544000379 + }, + "origin": "perl-test-longstring" + }, + "squid-lang-ko": { + "versions": { + "4.4-r1": 1545216327 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-mail-spamassassin-doc": { + "versions": { + "3.4.1-r2": 1545061231 + }, + "origin": "perl-mail-spamassassin" + }, + "py-alabaster": { + "versions": { + "0.7.12-r0": 1542824967 + }, + "origin": "py-alabaster" + }, + "perl-cgi-fast-doc": { + "versions": { + "2.13-r0": 1543242213 + }, + "origin": "perl-cgi-fast" + }, + "py3-asn1": { + "versions": { + "0.4.2-r0": 1542821563 + }, + "origin": "py-asn1", + "dependencies": [ + "python3" + ] + }, + "nginx-mod-http-js": { + "versions": { + "1.14.2-r1": 1557179820 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "ucarp": { + "versions": { + "1.5.2-r7": 1545076603 + }, + "origin": "ucarp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:ucarp" + ] + }, + "py-oauthlib": { + "versions": { + "2.0.6-r1": 1543249962 + }, + "origin": "py-oauthlib", + "dependencies": [ + "py-crypto", + "py-jwt" + ] + }, + "openldap-overlay-proxycache": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "ca-certificates-cacert": { + "versions": { + "20190108-r0": 1548779239 + }, + "origin": "ca-certificates" + }, + "unfs3": { + "versions": { + "0.9.22-r4": 1545222984 + }, + "origin": "unfs3", + "dependencies": [ + "rpcbind", + "so:libc.musl-x86_64.so.1", + "so:libfl.so.2", + "so:libtirpc.so.3" + ], + "provides": [ + "cmd:unfsd" + ] + }, + "perl-hash-multivalue-doc": { + "versions": { + "0.16-r0": 1543226610 + }, + "origin": "perl-hash-multivalue" + }, + "kamailio-lua": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ] + }, + "py2-tevent": { + "versions": { + "0.9.37-r1": 1543933086 + }, + "origin": "tevent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libtalloc.so.2", + "so:libtevent.so.0" + ], + "provides": [ + "py-tevent=0.9.37-r1" + ] + }, + "py3-snowballstemmer": { + "versions": { + "1.2.1-r1": 1542824987 + }, + "origin": "py-snowballstemmer", + "dependencies": [ + "python3" + ] + }, + "gettext": { + "versions": { + "0.19.8.1-r4": 1542304413 + }, + "origin": "gettext", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgomp.so.1", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libunistring.so.2", + "so:libxml2.so.2" + ], + "provides": [ + "so:libgettextlib-0.19.8.1.so=0", + "so:libgettextsrc-0.19.8.1.so=0", + "cmd:autopoint", + "cmd:envsubst", + "cmd:gettext", + "cmd:gettext.sh", + "cmd:gettextize", + "cmd:msgattrib", + "cmd:msgcat", + "cmd:msgcmp", + "cmd:msgcomm", + "cmd:msgconv", + "cmd:msgen", + "cmd:msgexec", + "cmd:msgfilter", + "cmd:msgfmt", + "cmd:msggrep", + "cmd:msginit", + "cmd:msgmerge", + "cmd:msgunfmt", + "cmd:msguniq", + "cmd:ngettext", + "cmd:recode-sr-latin", + "cmd:xgettext" + ] + }, + "py3-flake8": { + "versions": { + "3.4.1-r2": 1545223358 + }, + "origin": "py-flake8", + "dependencies": [ + "py3-mccabe", + "py3-pep8", + "py3-pyflakes", + "python3" + ], + "provides": [ + "cmd:flake8-3" + ] + }, + "perl-mail-domainkeys": { + "versions": { + "1.0-r2": 1545060517 + }, + "origin": "perl-mail-domainkeys", + "dependencies": [ + "perl" + ] + }, + "perl-exception-class": { + "versions": { + "1.44-r0": 1542845809 + }, + "origin": "perl-exception-class", + "dependencies": [ + "perl-devel-stacktrace", + "perl-class-data-inheritable" + ] + }, + "perl-scope-guard": { + "versions": { + "0.21-r0": 1542924586 + }, + "origin": "perl-scope-guard", + "dependencies": [ + "perl" + ] + }, + "fltk-dev": { + "versions": { + "1.3.4-r2": 1545076701 + }, + "origin": "fltk", + "dependencies": [ + "libx11-dev", + "libxext-dev", + "libxft-dev", + "mesa-dev", + "libxinerama-dev", + "fltk=1.3.4-r2" + ], + "provides": [ + "cmd:fltk-config" + ] + }, + "xset": { + "versions": { + "1.2.4-r0": 1545076640 + }, + "origin": "xset", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXmuu.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xset" + ] + }, + "perl-sub-quote-doc": { + "versions": { + "2.004000-r0": 1542972963 + }, + "origin": "perl-sub-quote" + }, + "expect-doc": { + "versions": { + "5.45.4-r0": 1543246822 + }, + "origin": "expect" + }, + "ldns-dev": { + "versions": { + "1.7.0-r2": 1545117110 + }, + "origin": "ldns", + "dependencies": [ + "openssl-dev", + "ldns=1.7.0-r2", + "pc:libcrypto", + "pkgconfig" + ], + "provides": [ + "pc:libldns=1.7.0", + "cmd:ldns-config" + ] + }, + "qemu-img": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libnettle.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-img", + "cmd:qemu-io", + "cmd:qemu-nbd" + ] + }, + "py3-wtforms": { + "versions": { + "2.1-r0": 1544000623 + }, + "origin": "py-wtforms", + "dependencies": [ + "python3" + ] + }, + "linux-firmware-sb16": { + "versions": { + "20190322-r0": 1554980648 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "check": { + "versions": { + "0.12.0-r1": 1542822525 + }, + "origin": "check", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcheck.so.0=0.0.0", + "cmd:checkmk" + ] + }, + "geeqie-doc": { + "versions": { + "1.4-r0": 1545292984 + }, + "origin": "geeqie" + }, + "p11-kit-trust": { + "versions": { + "0.23.14-r0": 1544791899 + }, + "origin": "p11-kit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libp11-kit.so.0", + "so:libtasn1.so.6" + ], + "provides": [ + "cmd:trust" + ] + }, + "apcupsd-doc": { + "versions": { + "3.14.14-r0": 1545293176 + }, + "origin": "apcupsd" + }, + "py3-flask-wtf": { + "versions": { + "0.14.2-r0": 1545213681 + }, + "origin": "py-flask-wtf", + "dependencies": [ + "py3-flask", + "py3-wtforms", + "python3" + ] + }, + "linux-firmware-liquidio": { + "versions": { + "20190322-r0": 1554980651 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "acf-dovecot": { + "versions": { + "0.6.0-r2": 1543999303 + }, + "origin": "acf-dovecot", + "dependencies": [ + "acf-core", + "dovecot" + ] + }, + "lm_sensors-sensord": { + "versions": { + "3.4.0-r6": 1542924819 + }, + "origin": "lm_sensors", + "dependencies": [ + "bash", + "sysfsutils", + "so:libc.musl-x86_64.so.1", + "so:librrd.so.8", + "so:libsensors.so.4" + ], + "provides": [ + "cmd:sensord" + ] + }, + "at-spi2-core": { + "versions": { + "2.28.0-r0": 1543241208 + }, + "origin": "at-spi2-core", + "dependencies": [ + "so:libX11.so.6", + "so:libXtst.so.6", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libatspi.so.0=0.0.1" + ] + }, + "cpufrequtils-doc": { + "versions": { + "008-r4": 1543927815 + }, + "origin": "cpufrequtils" + }, + "lua-feedparser": { + "versions": { + "0.71-r1": 1545299555 + }, + "origin": "lua-feedparser", + "dependencies": [ + "lua", + "lua-expat" + ], + "provides": [ + "lua-feedparser-common=0.71-r1", + "lua5.1-feedparser=0.71-r1", + "lua5.2-feedparser=0.71-r1", + "lua5.3-feedparser=0.71-r1" + ] + }, + "aspell-compat": { + "versions": { + "0.60.6.1-r13": 1542965829 + }, + "origin": "aspell", + "dependencies": [ + "aspell" + ], + "provides": [ + "cmd:ispell", + "cmd:spell" + ] + }, + "py-egenix-mx-base": { + "versions": { + "3.2.9-r0": 1542821605 + }, + "origin": "py-egenix-mx-base", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "py-django-extra-views": { + "versions": { + "0.11.0-r0": 1543249813 + }, + "origin": "py-django-extra-views", + "dependencies": [ + "py-django", + "py-six" + ] + }, + "libdrm": { + "versions": { + "2.4.96-r0": 1544791765 + }, + "origin": "libdrm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpciaccess.so.0" + ], + "provides": [ + "so:libdrm.so.2=2.4.0", + "so:libdrm_amdgpu.so.1=1.0.0", + "so:libdrm_freedreno.so.1=1.0.0", + "so:libdrm_intel.so.1=1.0.0", + "so:libdrm_nouveau.so.2=2.0.0", + "so:libdrm_radeon.so.1=1.0.1", + "so:libkms.so.1=1.0.0" + ] + }, + "py-gnome-bonobo": { + "versions": { + "2.28.1-r5": 1545301287 + }, + "origin": "py-gnome", + "dependencies": [ + "py-gtk", + "py-gnome-gnomecanvas" + ] + }, + "acf-lib": { + "versions": { + "0.10.1-r0": 1542883783 + }, + "origin": "acf-lib", + "dependencies": [ + "lua-subprocess" + ] + }, + "xf86-video-chips": { + "versions": { + "1.2.7-r3": 1545300415 + }, + "origin": "xf86-video-chips", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gstreamer0.10-lang": { + "versions": { + "0.10.36-r3": 1544000752 + }, + "origin": "gstreamer0.10" + }, + "lua-sec-doc": { + "versions": { + "0.7-r1": 1545075387 + }, + "origin": "lua-sec" + }, + "libdvbcsa": { + "versions": { + "1.1.0-r1": 1545293164 + }, + "origin": "libdvbcsa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdvbcsa.so.1=1.0.1" + ] + }, + "opusfile-dev": { + "versions": { + "0.11-r1": 1545209752 + }, + "origin": "opusfile", + "dependencies": [ + "openssl-dev", + "opusfile=0.11-r1", + "pc:ogg>=1.3", + "pc:opus>=1.0.1", + "pkgconfig" + ], + "provides": [ + "pc:opusfile=0.11", + "pc:opusurl=0.11" + ] + }, + "xf86-video-i128-doc": { + "versions": { + "1.3.6-r11": 1545069417 + }, + "origin": "xf86-video-i128" + }, + "beep": { + "versions": { + "1.3-r2": 1545214044 + }, + "origin": "beep", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:beep" + ] + }, + "rgb": { + "versions": { + "1.0.6-r2": 1545075162 + }, + "origin": "rgb", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:showrgb" + ] + }, + "perl-font-afm": { + "versions": { + "1.20-r0": 1542983922 + }, + "origin": "perl-font-afm" + }, + "logcheck": { + "versions": { + "1.3.19-r0": 1545857329 + }, + "origin": "logcheck", + "dependencies": [ + "lockfile-progs", + "/bin/sh" + ], + "provides": [ + "cmd:logcheck", + "cmd:logcheck-test", + "cmd:logtail", + "cmd:logtail2" + ] + }, + "perl-xml-simple-doc": { + "versions": { + "2.25-r0": 1545062590 + }, + "origin": "perl-xml-simple" + }, + "linux-firmware-rsi": { + "versions": { + "20190322-r0": 1554980648 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "autoconf-doc": { + "versions": { + "2.69-r2": 1542301299 + }, + "origin": "autoconf" + }, + "nagios-plugins-http": { + "versions": { + "2.2.1-r6": 1543933905 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "ngircd-doc": { + "versions": { + "24-r4": 1544001364 + }, + "origin": "ngircd" + }, + "perl-module-install-doc": { + "versions": { + "1.19-r0": 1542893355 + }, + "origin": "perl-module-install" + }, + "geoip-dev": { + "versions": { + "1.6.12-r1": 1544799414 + }, + "origin": "geoip", + "dependencies": [ + "geoip=1.6.12-r1", + "pkgconfig" + ], + "provides": [ + "pc:geoip=1.6.12" + ] + }, + "openvpn-doc": { + "versions": { + "2.4.6-r4": 1543223245 + }, + "origin": "openvpn" + }, + "linux-firmware-cadence": { + "versions": { + "20190322-r0": 1554980653 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "jwm": { + "versions": { + "2.3.7-r0": 1545292616 + }, + "origin": "jwm", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXft.so.2", + "so:libXinerama.so.1", + "so:libXmu.so.6", + "so:libXpm.so.4", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libfribidi.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:librsvg-2.so.2" + ], + "provides": [ + "cmd:jwm" + ] + }, + "tiff-tools": { + "versions": { + "4.0.10-r0": 1543921906 + }, + "origin": "tiff", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtiff.so.5" + ], + "provides": [ + "cmd:fax2ps", + "cmd:fax2tiff", + "cmd:pal2rgb", + "cmd:ppm2tiff", + "cmd:raw2tiff", + "cmd:tiff2bw", + "cmd:tiff2pdf", + "cmd:tiff2ps", + "cmd:tiff2rgba", + "cmd:tiffcmp", + "cmd:tiffcp", + "cmd:tiffcrop", + "cmd:tiffdither", + "cmd:tiffdump", + "cmd:tiffinfo", + "cmd:tiffmedian", + "cmd:tiffset", + "cmd:tiffsplit" + ] + }, + "coova-chilli-dev": { + "versions": { + "1.4-r2": 1545163684 + }, + "origin": "coova-chilli", + "dependencies": [ + "coova-chilli=1.4-r2" + ] + }, + "lua-sql-odbc": { + "versions": { + "2.3.5-r2": 1543924410 + }, + "origin": "lua-sql" + }, + "perl-devel-checkbin-doc": { + "versions": { + "0.02-r0": 1542845746 + }, + "origin": "perl-devel-checkbin" + }, + "perl-astro-suntime": { + "versions": { + "0.06-r0": 1545062002 + }, + "origin": "perl-astro-suntime", + "dependencies": [ + "perl" + ] + }, + "lua5.1-libs": { + "versions": { + "5.1.5-r7": 1542820877 + }, + "origin": "lua5.1", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "lua-libs", + "so:liblua.so.5=5.1.4" + ] + }, + "libmad": { + "versions": { + "0.15.1b-r8": 1543925793 + }, + "origin": "libmad", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmad.so.0=0.2.1" + ] + }, + "aria2-daemon": { + "versions": { + "1.34.0-r1": 1543998971 + }, + "origin": "aria2", + "dependencies": [ + "aria2=1.34.0-r1", + "openrc", + "/bin/sh" + ] + }, + "hypermail": { + "versions": { + "2.3.0-r4": 1545229738 + }, + "origin": "hypermail", + "dependencies": [ + "lua", + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libpcre.so.1" + ], + "provides": [ + "cmd:hypermail", + "cmd:mdir2mbox.lua", + "cmd:msgarchive", + "cmd:rdmsg" + ] + }, + "libxtst-dev": { + "versions": { + "1.2.3-r2": 1543241194 + }, + "origin": "libxtst", + "dependencies": [ + "xorgproto", + "libxtst=1.2.3-r2", + "pc:recordproto", + "pc:x11", + "pc:xext", + "pc:xextproto", + "pc:xi", + "pkgconfig" + ], + "provides": [ + "pc:xtst=1.2.3" + ] + }, + "freeswitch-sounds-fr-ca-june-8000": { + "versions": { + "1.0.51-r0": 1545075153 + }, + "origin": "freeswitch-sounds-fr-ca-june-8000" + }, + "parted-doc": { + "versions": { + "3.2-r7": 1543935530 + }, + "origin": "parted" + }, + "freeswitch-sounds-ru-RU-elena-32000": { + "versions": { + "1.0.12-r1": 1545254249 + }, + "origin": "freeswitch-sounds-ru-RU-elena-32000" + }, + "rrdcollect-doc": { + "versions": { + "0.2.10-r1": 1545207035 + }, + "origin": "rrdcollect" + }, + "sntpc": { + "versions": { + "0.9-r6": 1545215980 + }, + "origin": "sntpc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sntpc" + ] + }, + "perl-cpan-meta-check-doc": { + "versions": { + "0.014-r0": 1542845602 + }, + "origin": "perl-cpan-meta-check" + }, + "herbstluftwm-fish-completion": { + "versions": { + "0.7.1-r0": 1545300036 + }, + "origin": "herbstluftwm" + }, + "poppler-utils": { + "versions": { + "0.56.0-r1": 1542824318 + }, + "origin": "poppler", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libfreetype.so.6", + "so:liblcms2.so.2", + "so:libpoppler.so.67", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:pdfdetach", + "cmd:pdffonts", + "cmd:pdfimages", + "cmd:pdfinfo", + "cmd:pdfseparate", + "cmd:pdftocairo", + "cmd:pdftohtml", + "cmd:pdftoppm", + "cmd:pdftops", + "cmd:pdftotext", + "cmd:pdfunite" + ] + }, + "freeradius-mssql": { + "versions": { + "3.0.17-r5": 1556201988 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius-sql=3.0.17-r5" + ], + "provides": [ + "freeradius3-mssql=3.0.17-r5" + ] + }, + "st-dbg": { + "versions": { + "0.8.1-r0": 1545292622 + }, + "origin": "st", + "dependencies": [ + "ncurses-terminfo" + ] + }, + "py-enum34": { + "versions": { + "1.1.6-r2": 1545116813 + }, + "origin": "py-enum34" + }, + "pcre": { + "versions": { + "8.42-r1": 1545067005 + }, + "origin": "pcre", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre.so.1=1.2.10", + "so:libpcreposix.so.0=0.0.6" + ] + }, + "font-util-doc": { + "versions": { + "1.3.1-r2": 1542924710 + }, + "origin": "font-util" + }, + "syslog-ng": { + "versions": { + "3.19.1-r0": 1548543151 + }, + "origin": "syslog-ng", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libivykis.so.0", + "so:libpcre.so.1", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libevtlog-3.19.so.0=0.0.0", + "so:libloggen_helper-3.19.so.0=0.0.0", + "so:libloggen_plugin-3.19.so.0=0.0.0", + "so:libsecret-storage.so.0=0.0.0", + "so:libsyslog-ng-3.19.so.0=0.0.0", + "cmd:dqtool", + "cmd:loggen", + "cmd:pdbtool", + "cmd:syslog-ng", + "cmd:syslog-ng-ctl", + "cmd:syslog-ng-debun", + "cmd:update-patterndb" + ] + }, + "acf-freeswitch": { + "versions": { + "0.8.0-r2": 1545292839 + }, + "origin": "acf-freeswitch", + "dependencies": [ + "acf-core", + "freeswitch", + "lua-xml" + ] + }, + "perl-net-ip-doc": { + "versions": { + "1.26-r1": 1543924417 + }, + "origin": "perl-net-ip" + }, + "cifs-utils": { + "versions": { + "6.8-r0": 1545290852 + }, + "origin": "cifs-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libkeyutils.so.1", + "so:libkrb5.so.3", + "so:libtalloc.so.2", + "so:libwbclient.so.0" + ], + "provides": [ + "cmd:cifs.idmap", + "cmd:cifs.upcall", + "cmd:mount.cifs" + ] + }, + "pcre2-tools": { + "versions": { + "10.32-r1": 1545069083 + }, + "origin": "pcre2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libedit.so.0", + "so:libpcre2-16.so.0", + "so:libpcre2-32.so.0", + "so:libpcre2-8.so.0", + "so:libpcre2-posix.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:pcre2grep", + "cmd:pcre2test" + ] + }, + "p7zip": { + "versions": { + "16.02-r3": 1545118129 + }, + "origin": "p7zip", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:7z", + "cmd:7za", + "cmd:7zr", + "cmd:p7zip" + ] + }, + "libevdev-doc": { + "versions": { + "1.6.0-r0": 1545838440 + }, + "origin": "libevdev" + }, + "ircservices": { + "versions": { + "5.1.24-r4": 1545069549 + }, + "origin": "ircservices", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ircservices", + "cmd:ircservices-chk", + "cmd:ircservices-convert-db" + ] + }, + "ppp-l2tp": { + "versions": { + "2.4.7-r6": 1543999022 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "igmpproxy-doc": { + "versions": { + "0.2.1-r0": 1545746723 + }, + "origin": "igmpproxy" + }, + "vanessa_socket": { + "versions": { + "0.0.13-r0": 1545116981 + }, + "origin": "vanessa_socket", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0", + "so:libvanessa_logger.so.0" + ], + "provides": [ + "so:libvanessa_socket.so.2=2.1.0", + "cmd:vanessa_socket_pipe" + ] + }, + "lzo-doc": { + "versions": { + "2.10-r2": 1542985323 + }, + "origin": "lzo" + }, + "shorewall6": { + "versions": { + "5.2.2-r0": 1548095417 + }, + "origin": "shorewall6", + "dependencies": [ + "shorewall-core", + "perl", + "ip6tables", + "iproute2" + ] + }, + "perl-struct-dumb": { + "versions": { + "0.09-r0": 1544799022 + }, + "origin": "perl-struct-dumb" + }, + "lua-iconv": { + "versions": { + "7-r1": 1545062773 + }, + "origin": "lua-iconv" + }, + "attr": { + "versions": { + "2.4.47-r7": 1542301506 + }, + "origin": "attr", + "dependencies": [ + "so:libattr.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:attr", + "cmd:getfattr", + "cmd:setfattr" + ] + }, + "perl-dist-checkconflicts": { + "versions": { + "0.11-r0": 1542845704 + }, + "origin": "perl-dist-checkconflicts", + "dependencies": [ + "perl-list-moreutils", + "perl-module-runtime" + ] + }, + "openrc-doc": { + "versions": { + "0.39.2-r3": 1548592317 + }, + "origin": "openrc" + }, + "py2-cairo": { + "versions": { + "1.16.3-r0": 1543925200 + }, + "origin": "py-cairo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libpython2.7.so.1.0" + ] + }, + "libdnet": { + "versions": { + "1.12-r7": 1545076675 + }, + "origin": "libdnet", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdnet.so.1=1.0.1", + "cmd:dnet" + ] + }, + "py-samba": { + "versions": { + "4.8.11-r1": 1555334976 + }, + "origin": "samba", + "dependencies": [ + "py2-tdb", + "so:libMESSAGING-SEND-samba4.so", + "so:libMESSAGING-samba4.so", + "so:libads-samba4.so", + "so:libauth4-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-ldap-samba4.so", + "so:libcli-nbt-samba4.so", + "so:libcliauth-samba4.so", + "so:libcluster-samba4.so", + "so:libcmdline-credentials-samba4.so", + "so:libdb-glue-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libdcerpc-samba4.so", + "so:libdcerpc.so.0", + "so:libdnsserver-common-samba4.so", + "so:libdsdb-garbage-collect-tombstones-samba4.so", + "so:libevents-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgpext-samba4.so", + "so:libgse-samba4.so", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:liblibsmb-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libnetif-samba4.so", + "so:libposix-eadb-samba4.so", + "so:libpyldb-util.so.1", + "so:libpytalloc-util.so.2", + "so:libpython2.7.so.1.0", + "so:libregistry-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-net-samba4.so", + "so:libsamba-passdb.so.0", + "so:libsamba-policy.so.0", + "so:libsamba-python-samba4.so", + "so:libsamba-security-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libsecrets3-samba4.so", + "so:libserver-role-samba4.so", + "so:libsmbclient-raw-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-base-samba4.so", + "so:libsmbd-conn-samba4.so", + "so:libtalloc.so.2", + "so:libtdb-wrap-samba4.so", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-reg-samba4.so", + "so:libxattr-tdb-samba4.so" + ] + }, + "perl-datetime-format-mail": { + "versions": { + "0.403-r0": 1543927501 + }, + "origin": "perl-datetime-format-mail", + "dependencies": [ + "perl", + "perl-datetime", + "perl-params-validate" + ] + }, + "perl-eval-closure": { + "versions": { + "0.14-r0": 1542972943 + }, + "origin": "perl-eval-closure", + "dependencies": [ + "perl-try-tiny" + ] + }, + "ttf-droid": { + "versions": { + "20121017-r0": 1545300915 + }, + "origin": "ttf-droid", + "dependencies": [ + "fontconfig" + ] + }, + "py-jinja2-vim": { + "versions": { + "2.10-r2": 1542824938 + }, + "origin": "py-jinja2", + "dependencies": [ + "vim" + ] + }, + "lua5.2-ldap": { + "versions": { + "1.2.3-r5": 1543223251 + }, + "origin": "lua-ldap", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ] + }, + "parted-dev": { + "versions": { + "3.2-r7": 1543935530 + }, + "origin": "parted", + "dependencies": [ + "parted=3.2-r7", + "pkgconfig" + ], + "provides": [ + "pc:libparted=3.2" + ] + }, + "gst-plugins-base0.10-dev": { + "versions": { + "0.10.36-r4": 1543928809 + }, + "origin": "gst-plugins-base0.10", + "dependencies": [ + "gstreamer0.10-dev", + "gst-plugins-base0.10=0.10.36-r4", + "pc:glib-2.0", + "pc:gstreamer-0.10", + "pc:gstreamer-base-0.10", + "pkgconfig" + ], + "provides": [ + "pc:gstreamer-app-0.10=0.10.36", + "pc:gstreamer-audio-0.10=0.10.36", + "pc:gstreamer-cdda-0.10=0.10.36", + "pc:gstreamer-fft-0.10=0.10.36", + "pc:gstreamer-floatcast-0.10=0.10.36", + "pc:gstreamer-interfaces-0.10=0.10.36", + "pc:gstreamer-netbuffer-0.10=0.10.36", + "pc:gstreamer-pbutils-0.10=0.10.36", + "pc:gstreamer-plugins-base-0.10=0.10.36", + "pc:gstreamer-riff-0.10=0.10.36", + "pc:gstreamer-rtp-0.10=0.10.36", + "pc:gstreamer-rtsp-0.10=0.10.36", + "pc:gstreamer-sdp-0.10=0.10.36", + "pc:gstreamer-tag-0.10=0.10.36", + "pc:gstreamer-video-0.10=0.10.36" + ] + }, + "xrdp-doc": { + "versions": { + "0.9.9-r0": 1545859298 + }, + "origin": "xrdp" + }, + "mailcap-doc": { + "versions": { + "2.1.48-r0": 1545062135 + }, + "origin": "mailcap" + }, + "postgresql-bdr": { + "versions": { + "9.4.14_p1-r1": 1543223165 + }, + "origin": "postgresql-bdr", + "dependencies": [ + "bash", + "libpq", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpq.so.5", + "so:libssl.so.1.1" + ], + "provides": [ + "postgresql", + "so:postgresql-bdr:libecpg.so.6=6.6", + "so:postgresql-bdr:libecpg_compat.so.3=3.6", + "so:postgresql-bdr:libpgtypes.so.3=3.5", + "cmd:createlang", + "cmd:droplang", + "cmd:ecpg", + "cmd:initdb", + "cmd:pg_config", + "cmd:pg_controldata", + "cmd:pg_ctl", + "cmd:pg_receivexlog", + "cmd:pg_resetxlog", + "cmd:postgres", + "cmd:postmaster" + ] + }, + "qt-lang": { + "versions": { + "4.8.7-r11": 1545075090 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates" + ] + }, + "hostapd": { + "versions": { + "2.7-r0": 1547822982 + }, + "origin": "hostapd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libnl-3.so.200", + "so:libnl-genl-3.so.200", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:hostapd", + "cmd:hostapd_cli", + "cmd:nt_password_hash" + ] + }, + "net-tools-doc": { + "versions": { + "1.60_git20140218-r2": 1545216350 + }, + "origin": "net-tools" + }, + "xcb-util-keysyms": { + "versions": { + "0.4.0-r1": 1543927912 + }, + "origin": "xcb-util-keysyms", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-keysyms.so.1=1.0.0" + ] + }, + "btrfs-progs-dev": { + "versions": { + "4.19.1-r0": 1545665045 + }, + "origin": "btrfs-progs", + "dependencies": [ + "linux-headers", + "btrfs-progs-libs=4.19.1-r0" + ] + }, + "perl-yaml-syck-doc": { + "versions": { + "1.31-r0": 1545060526 + }, + "origin": "perl-yaml-syck" + }, + "procps-dev": { + "versions": { + "3.3.15-r0": 1543934060 + }, + "origin": "procps", + "dependencies": [ + "libproc=3.3.15-r0", + "pkgconfig" + ] + }, + "snappy-dbg": { + "versions": { + "1.1.7-r1": 1545208921 + }, + "origin": "snappy" + }, + "meson": { + "versions": { + "0.48.2-r0": 1544791717 + }, + "origin": "meson", + "dependencies": [ + "ninja", + "python3" + ], + "provides": [ + "cmd:meson" + ] + }, + "perl-subversion": { + "versions": { + "1.11.1-r0": 1548692375 + }, + "origin": "subversion", + "dependencies": [ + "so:libapr-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libsvn_client-1.so.0", + "so:libsvn_delta-1.so.0", + "so:libsvn_diff-1.so.0", + "so:libsvn_fs-1.so.0", + "so:libsvn_ra-1.so.0", + "so:libsvn_repos-1.so.0", + "so:libsvn_subr-1.so.0", + "so:libsvn_wc-1.so.0" + ], + "provides": [ + "so:libsvn_swig_perl-1.so.0=0.0.0" + ] + }, + "openldap-overlay-valsort": { + "versions": { + "2.4.47-r2": 1546016480 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "lmdb": { + "versions": { + "0.9.23-r0": 1547025175 + }, + "origin": "lmdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblmdb.so.0=0.0.0" + ] + }, + "umurmur": { + "versions": { + "0.2.17-r3": 1545207064 + }, + "origin": "umurmur", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libconfig.so.9", + "so:libcrypto.so.1.1", + "so:libprotobuf-c.so.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:umurmurd" + ] + }, + "squid-lang-th": { + "versions": { + "4.4-r1": 1545216330 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "net-snmp-tools": { + "versions": { + "5.8-r0": 1543923351 + }, + "origin": "net-snmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libnetsnmp.so.35", + "so:libnetsnmpagent.so.35" + ], + "provides": [ + "cmd:agentxtrap", + "cmd:checkbandwidth", + "cmd:encode_keychange", + "cmd:net-snmp-create-v3-user", + "cmd:snmpbulkget", + "cmd:snmpbulkwalk", + "cmd:snmpconf", + "cmd:snmpdelta", + "cmd:snmpdf", + "cmd:snmpget", + "cmd:snmpgetnext", + "cmd:snmpinform", + "cmd:snmpnetstat", + "cmd:snmpping", + "cmd:snmpps", + "cmd:snmpset", + "cmd:snmpstatus", + "cmd:snmptable", + "cmd:snmptest", + "cmd:snmptop", + "cmd:snmptranslate", + "cmd:snmptrap", + "cmd:snmpusm", + "cmd:snmpvacm", + "cmd:snmpwalk" + ] + }, + "lua5.1-sircbot": { + "versions": { + "0.4-r2": 1543924832 + }, + "origin": "sircbot", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-json-doc": { + "versions": { + "2.97000-r0": 1542819173 + }, + "origin": "perl-json" + }, + "perl-specio": { + "versions": { + "0.42-r0": 1542972981 + }, + "origin": "perl-specio", + "dependencies": [ + "perl-test-needs", + "perl-devel-stacktrace", + "perl-eval-closure", + "perl-mro-compat", + "perl-role-tiny", + "perl-test-fatal", + "perl-module-runtime", + "perl-sub-quote" + ] + }, + "tree": { + "versions": { + "1.8.0-r0": 1544791740 + }, + "origin": "tree", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tree" + ] + }, + "collectd-rrdtool": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:librrd.so.8" + ] + }, + "snownews-lang": { + "versions": { + "1.5.12-r8": 1543999423 + }, + "origin": "snownews", + "dependencies": [ + "ncurses" + ] + }, + "openipmi-lanserv": { + "versions": { + "2.0.25-r1": 1545214270 + }, + "origin": "openipmi", + "dependencies": [ + "so:libOpenIPMIposix.so.0", + "so:libOpenIPMIutils.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpopt.so.0" + ], + "provides": [ + "so:libIPMIlanserv.so.0=0.0.1", + "cmd:ipmi_sim", + "cmd:ipmilan", + "cmd:sdrcomp" + ] + }, + "squid-lang-hu": { + "versions": { + "4.4-r1": 1545216326 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "postgresql-bdr-contrib": { + "versions": { + "9.4.14_p1-r1": 1543223164 + }, + "origin": "postgresql-bdr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpq.so.5", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:oid2name", + "cmd:pg_archivecleanup", + "cmd:pg_standby", + "cmd:pg_test_fsync", + "cmd:pg_test_timing", + "cmd:pg_upgrade", + "cmd:pg_xlogdump", + "cmd:pgbench", + "cmd:vacuumlo" + ] + }, + "libdbi-dev": { + "versions": { + "0.9.0-r0": 1543932516 + }, + "origin": "libdbi", + "dependencies": [ + "libdbi=0.9.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:dbi=0.9.0" + ] + }, + "openldap-overlay-dds": { + "versions": { + "2.4.47-r2": 1546016479 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "libuv-dev": { + "versions": { + "1.23.2-r0": 1542820441 + }, + "origin": "libuv", + "dependencies": [ + "libuv=1.23.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libuv=1.23.2" + ] + }, + "ppp-openrc": { + "versions": { + "2.4.7-r6": 1543999024 + }, + "origin": "ppp", + "dependencies": [ + "ppp-chat", + "ppp-radius", + "ppp-atm", + "ppp-pppoe", + "ppp-l2tp", + "ppp-winbind", + "ppp-passprompt", + "ppp-passwordfd", + "ppp-minconn", + "ppp-daemon" + ] + }, + "font-adobe-100dpi": { + "versions": { + "1.0.3-r0": 1545062647 + }, + "origin": "font-adobe-100dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-test2-suite": { + "versions": { + "0.000114-r0": 1542973042 + }, + "origin": "perl-test2-suite", + "dependencies": [ + "perl-test-simple", + "perl-importer", + "perl-term-table", + "perl-sub-info", + "perl-scope-guard", + "perl-module-pluggable" + ] + }, + "squid-lang-bg": { + "versions": { + "4.4-r1": 1545216323 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "xf86-video-fbdev": { + "versions": { + "0.5.0-r2": 1545067180 + }, + "origin": "xf86-video-fbdev", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "kamailio-extras": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libhiredis.so.0.14", + "so:libsrdb1.so.1", + "so:libsrutils.so.1" + ] + }, + "libpng-doc": { + "versions": { + "1.6.37-r0": 1557129094 + }, + "origin": "libpng" + }, + "pcre2": { + "versions": { + "10.32-r1": 1545069083 + }, + "origin": "pcre2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcre2-8.so.0=0.7.1", + "so:libpcre2-posix.so.2=2.0.1" + ] + }, + "eventlog": { + "versions": { + "0.2.12-r4": 1545118013 + }, + "origin": "eventlog", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libevtlog.so.0=0.0.0" + ] + }, + "libjpeg": { + "versions": { + "8-r6": 1542893530 + }, + "origin": "jpeg", + "dependencies": [ + "libjpeg-turbo" + ] + }, + "sdl2_ttf": { + "versions": { + "2.0.14-r1": 1543934467 + }, + "origin": "sdl2_ttf", + "dependencies": [ + "so:libSDL2-2.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6" + ], + "provides": [ + "so:libSDL2_ttf-2.0.so.0=0.14.0" + ] + }, + "cifs-utils-dev": { + "versions": { + "6.8-r0": 1545290851 + }, + "origin": "cifs-utils" + }, + "gtk+3.0-lang": { + "versions": { + "3.24.1-r0": 1543927435 + }, + "origin": "gtk+3.0", + "dependencies": [ + "shared-mime-info", + "gtk-update-icon-cache" + ] + }, + "uwsgi-signal": { + "versions": { + "2.0.17.1-r0": 1545062210 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libbz2": { + "versions": { + "1.0.6-r6": 1542300119 + }, + "origin": "bzip2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libbz2.so.1=1.0.6" + ] + }, + "boost-program_options": { + "versions": { + "1.67.0-r2": 1542823632 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_program_options-mt.so.1.67.0=1.67.0", + "so:libboost_program_options.so.1.67.0=1.67.0" + ] + }, + "perl-mailtools-doc": { + "versions": { + "2.19-r0": 1543226634 + }, + "origin": "perl-mailtools" + }, + "cyrus-sasl-gssapiv2": { + "versions": { + "2.1.27-r1": 1550353527 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgssapi_krb5.so.2" + ] + }, + "squid-lang-da": { + "versions": { + "4.4-r1": 1545216324 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-class-accessor-chained-doc": { + "versions": { + "0.01-r0": 1544000361 + }, + "origin": "perl-class-accessor-chained" + }, + "linux-firmware-ar3k": { + "versions": { + "20190322-r0": 1554980654 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "qemu-mips64el": { + "versions": { + "3.1.0-r3": 1551107300 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mips64el" + ] + }, + "samba-common-libs": { + "versions": { + "4.8.11-r1": 1555334973 + }, + "origin": "samba", + "dependencies": [ + "so:libCHARSET3-samba4.so", + "so:libasn1util-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcliauth-samba4.so", + "so:libcom_err.so.2", + "so:libdbwrap-samba4.so", + "so:libevents-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgse-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:libmessages-dgm-samba4.so", + "so:libmessages-util-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libsecrets3-samba4.so", + "so:libserver-id-db-samba4.so", + "so:libsmb-transport-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-shim-samba4.so", + "so:libsocket-blocking-samba4.so", + "so:libtalloc.so.2", + "so:libtdb-wrap-samba4.so", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-cmdline-samba4.so", + "so:libutil-setid-samba4.so", + "so:libutil-tdb-samba4.so", + "so:libwbclient.so.0" + ], + "provides": [ + "so:libMESSAGING-SEND-samba4.so=0", + "so:libcli-spoolss-samba4.so=0", + "so:libdcerpc-binding.so.0=0.0.1", + "so:libdcerpc-samba-samba4.so=0", + "so:liblibcli-lsa3-samba4.so=0", + "so:liblibcli-netlogon3-samba4.so=0", + "so:liblibsmb-samba4.so=0", + "so:libmsrpc3-samba4.so=0", + "so:libndr-samba4.so=0", + "so:libsamba-passdb.so.0=0.27.2", + "so:libtrusts-util-samba4.so=0" + ] + }, + "libva-intel-driver": { + "versions": { + "2.2.0-r0": 1543245814 + }, + "origin": "libva-intel-driver", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_intel.so.1" + ] + }, + "yaml": { + "versions": { + "0.2.1-r0": 1542822092 + }, + "origin": "yaml", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libyaml-0.so.2=2.0.5" + ] + }, + "lua5.2-lzmq": { + "versions": { + "0.4.4-r0": 1545076583 + }, + "origin": "lua-lzmq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5", + "so:libzmq.so.5" + ] + }, + "debootstrap": { + "versions": { + "1.0.111-r0": 1545257152 + }, + "origin": "debootstrap", + "dependencies": [ + "debian-archive-keyring", + "dpkg", + "tar" + ], + "provides": [ + "cmd:debootstrap" + ] + }, + "weechat-lua": { + "versions": { + "2.3-r0": 1545299933 + }, + "origin": "weechat", + "dependencies": [ + "weechat", + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ] + }, + "uwsgi-transformation_gzip": { + "versions": { + "2.0.17.1-r0": 1545062212 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.1": { + "versions": { + "5.1.5-r7": 1542820878 + }, + "origin": "lua5.1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ], + "provides": [ + "lua", + "cmd:lua", + "cmd:lua5.1", + "cmd:luac", + "cmd:luac5.1" + ] + }, + "libatasmart": { + "versions": { + "0.19-r1": 1545062470 + }, + "origin": "libatasmart", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ], + "provides": [ + "so:libatasmart.so.4=4.0.5", + "cmd:skdump", + "cmd:sktest" + ] + }, + "perl-test-mocktime": { + "versions": { + "0.17-r0": 1544792417 + }, + "origin": "perl-test-mocktime" + }, + "xz-doc": { + "versions": { + "5.2.4-r0": 1542303284 + }, + "origin": "xz" + }, + "nagios-plugins-icmp": { + "versions": { + "2.2.1-r6": 1543933906 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-getopt-long-doc": { + "versions": { + "2.50-r0": 1543922466 + }, + "origin": "perl-getopt-long" + }, + "ruby-power_assert": { + "versions": { + "2.5.5-r0": 1557164837 + }, + "origin": "ruby", + "dependencies": [ + "ruby-libs" + ] + }, + "giflib": { + "versions": { + "5.1.4-r2": 1543077199 + }, + "origin": "giflib", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgif.so.7=7.0.0" + ] + }, + "mpc1": { + "versions": { + "1.0.3-r1": 1542301580 + }, + "origin": "mpc1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libmpfr.so.4" + ], + "provides": [ + "so:libmpc.so.3=3.0.0" + ] + }, + "kbd-misc": { + "versions": { + "2.0.4-r3": 1545215188 + }, + "origin": "kbd" + }, + "openvswitch": { + "versions": { + "2.10.1-r0": 1544000345 + }, + "origin": "openvswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:ovs-appctl", + "cmd:ovs-bugtool", + "cmd:ovs-docker", + "cmd:ovs-dpctl", + "cmd:ovs-dpctl-top", + "cmd:ovs-ofctl", + "cmd:ovs-parse-backtrace", + "cmd:ovs-pki", + "cmd:ovs-vlan-bug-workaround", + "cmd:ovs-vsctl", + "cmd:ovs-vswitchd", + "cmd:ovsdb-client", + "cmd:ovsdb-server", + "cmd:ovsdb-tool", + "cmd:vtep-ctl" + ] + }, + "tumbler-doc": { + "versions": { + "0.2.3-r0": 1545859329 + }, + "origin": "tumbler" + }, + "py-django-tables2": { + "versions": { + "1.21.2-r0": 1543925704 + }, + "origin": "py-django-tables2", + "dependencies": [ + "py-django", + "py-six" + ] + }, + "bacula-libs": { + "versions": { + "9.4.1-r1": 1546944087 + }, + "origin": "bacula", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:bpipe-fd.so=0", + "so:libbac-9.4.1.so=0", + "so:libbaccfg-9.4.1.so=0", + "so:libbacfind-9.4.1.so=0", + "so:libbacsd-9.4.1.so=0", + "so:libbacsql-9.4.1.so=0" + ] + }, + "xf86-video-openchrome-doc": { + "versions": { + "0.6.0-r4": 1545249765 + }, + "origin": "xf86-video-openchrome" + }, + "py2-click": { + "versions": { + "6.7-r2": 1545116987 + }, + "origin": "py-click", + "dependencies": [ + "python2" + ] + }, + "py2-bluez": { + "versions": { + "0.22-r1": 1545062448 + }, + "origin": "py-bluez", + "dependencies": [ + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "libnl-dev": { + "versions": { + "1.1.4-r0": 1543924490 + }, + "origin": "libnl", + "dependencies": [ + "libnl=1.1.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnl-1=1.1.4" + ] + }, + "py2-docutils": { + "versions": { + "0.14-r1": 1543925151 + }, + "origin": "py-docutils", + "dependencies": [ + "py2-pillow", + "py2-roman", + "python2" + ], + "provides": [ + "cmd:rst2html-2", + "cmd:rst2html4-2", + "cmd:rst2html5-2", + "cmd:rst2latex-2", + "cmd:rst2man-2", + "cmd:rst2odt-2", + "cmd:rst2odt_prepstyles-2", + "cmd:rst2pseudoxml-2", + "cmd:rst2s5-2", + "cmd:rst2xetex-2", + "cmd:rst2xml-2", + "cmd:rstpep2html-2" + ] + }, + "encfs-doc": { + "versions": { + "1.9.5-r3": 1545061083 + }, + "origin": "encfs" + }, + "clang-libs": { + "versions": { + "5.0.2-r0": 1546873920 + }, + "origin": "clang", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libclang.so.5.0=5.0" + ] + }, + "openldap-overlay-unique": { + "versions": { + "2.4.47-r2": 1546016480 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "lvm2-openrc": { + "versions": { + "2.02.182-r0": 1543928933 + }, + "origin": "lvm2", + "dependencies": [ + "lvm2-libs=2.02.182-r0" + ] + }, + "sudo": { + "versions": { + "1.8.25_p1-r2": 1542304827 + }, + "origin": "sudo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:group_file.so=0", + "so:libsudo_noexec.so=0", + "so:libsudo_util.so.0=0.0.0", + "so:sudoers.so=0", + "so:system_group.so=0", + "cmd:cvtsudoers", + "cmd:sudo", + "cmd:sudoedit", + "cmd:sudoreplay", + "cmd:visudo" + ] + }, + "py2-google-api-python-client": { + "versions": { + "1.7.7-r0": 1546936521 + }, + "origin": "py-google-api-python-client", + "dependencies": [ + "py2-httplib2", + "py2-oauth2client", + "py2-uritemplate", + "py2-six", + "python2" + ] + }, + "lockfile-progs-doc": { + "versions": { + "0.1.18-r0": 1546267694 + }, + "origin": "lockfile-progs" + }, + "perl-heap": { + "versions": { + "0.80-r1": 1545213985 + }, + "origin": "perl-heap" + }, + "busybox-initscripts": { + "versions": { + "3.1-r6": 1545665791 + }, + "origin": "busybox-initscripts", + "dependencies": [ + "busybox", + "openrc>=0.24.1-r6" + ] + }, + "s6-dns": { + "versions": { + "2.3.0.1-r0": 1545062685 + }, + "origin": "s6-dns", + "dependencies": [ + "skalibs", + "so:libc.musl-x86_64.so.1", + "so:libskarnet.so.2.7" + ], + "provides": [ + "so:libs6dns.so.2.3=2.3.0.1", + "so:libskadns.so.2.3=2.3.0.1", + "cmd:s6-dnsip4", + "cmd:s6-dnsip4-filter", + "cmd:s6-dnsip6", + "cmd:s6-dnsip6-filter", + "cmd:s6-dnsmx", + "cmd:s6-dnsname", + "cmd:s6-dnsname-filter", + "cmd:s6-dnsns", + "cmd:s6-dnsq", + "cmd:s6-dnsqr", + "cmd:s6-dnsqualify", + "cmd:s6-dnssoa", + "cmd:s6-dnssrv", + "cmd:s6-dnstxt", + "cmd:s6-randomip", + "cmd:skadnsd" + ] + }, + "libassuan-doc": { + "versions": { + "2.5.1-r0": 1543932160 + }, + "origin": "libassuan" + }, + "xf86-video-dummy-doc": { + "versions": { + "0.3.8-r3": 1545300622 + }, + "origin": "xf86-video-dummy" + }, + "argp-standalone": { + "versions": { + "1.3-r3": 1543249712 + }, + "origin": "argp-standalone" + }, + "gitolite": { + "versions": { + "3.6.11-r0": 1548271316 + }, + "origin": "gitolite", + "dependencies": [ + "git", + "perl", + "/bin/sh" + ] + }, + "xinit": { + "versions": { + "1.4.0-r1": 1542973236 + }, + "origin": "xinit", + "dependencies": [ + "xauth", + "mcookie", + "xmodmap", + "xrdb", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:startx", + "cmd:xinit" + ] + }, + "recode-doc": { + "versions": { + "3.6-r2": 1543245780 + }, + "origin": "recode" + }, + "xf86-video-intel": { + "versions": { + "2.99.917_git20170325-r4": 1545665365 + }, + "origin": "xf86-video-intel", + "dependencies": [ + "mesa-dri-intel", + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libXv.so.1", + "so:libXvMC.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_intel.so.1", + "so:libpciaccess.so.0", + "so:libpixman-1.so.0", + "so:libudev.so.1", + "so:libxcb-dri2.so.0", + "so:libxcb-util.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libI810XvMC.so.1=1.0.0", + "so:libIntelXvMC.so.1=1.0.0" + ] + }, + "alpine-conf": { + "versions": { + "3.8.1-r5": 1557161476 + }, + "origin": "alpine-conf", + "dependencies": [ + "openrc>=0.24.1-r6", + "busybox>=1.26.1-r3", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:lbu", + "cmd:lbu_commit", + "cmd:lbu_exclude", + "cmd:lbu_include", + "cmd:lbu_status", + "cmd:lbu_update", + "cmd:setup-acf", + "cmd:setup-alpine", + "cmd:setup-apkcache", + "cmd:setup-apkrepos", + "cmd:setup-bootable", + "cmd:setup-disk", + "cmd:setup-dns", + "cmd:setup-gparted-desktop", + "cmd:setup-hostname", + "cmd:setup-interfaces", + "cmd:setup-keymap", + "cmd:setup-lbu", + "cmd:setup-mta", + "cmd:setup-ntp", + "cmd:setup-proxy", + "cmd:setup-sshd", + "cmd:setup-timezone", + "cmd:setup-xen-dom0", + "cmd:setup-xorg-base", + "cmd:uniso", + "cmd:update-conf", + "cmd:update-kernel" + ] + }, + "postfix-doc": { + "versions": { + "3.3.2-r0": 1547130362 + }, + "origin": "postfix" + }, + "qemu-m68k": { + "versions": { + "3.1.0-r3": 1551107300 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-m68k" + ] + }, + "enca-dev": { + "versions": { + "1.19-r1": 1545076571 + }, + "origin": "enca", + "dependencies": [ + "enca=1.19-r1", + "pkgconfig" + ], + "provides": [ + "pc:enca=1.19" + ] + }, + "heimdal-libs": { + "versions": { + "7.5.0-r2": 1542819373 + }, + "origin": "heimdal", + "dependencies": [ + "krb5-conf", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libdb-5.3.so", + "so:libreadline.so.7", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:libasn1.so.8=8.0.0", + "so:libgssapi.so.3=3.0.0", + "so:libhcrypto.so.4=4.1.0", + "so:libhdb.so.9=9.2.0", + "so:libheimbase.so.1=1.0.0", + "so:libheimntlm.so.0=0.1.0", + "so:libhx509.so.5=5.0.0", + "so:libkadm5clnt.so.7=7.0.1", + "so:libkadm5srv.so.8=8.0.1", + "so:libkafs.so.0=0.5.1", + "so:libkdc.so.2=2.0.0", + "so:libkrb5.so.26=26.0.0", + "so:libotp.so.0=0.1.5", + "so:libroken.so.18=18.1.0", + "so:libsl.so.0=0.2.1", + "so:libwind.so.0=0.0.0", + "so:windc.so.0=0.0.0", + "cmd:digest-service", + "cmd:kdigest", + "cmd:string2key", + "cmd:verify_krb5_conf" + ] + }, + "strongswan": { + "versions": { + "5.7.2-r1": 1557161394 + }, + "origin": "strongswan", + "dependencies": [ + "iproute2", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libgmp.so.10", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:libcharon.so.0=0.0.0", + "so:libradius.so.0=0.0.0", + "so:libsimaka.so.0=0.0.0", + "so:libstrongswan.so.0=0.0.0", + "so:libtls.so.0=0.0.0", + "so:libvici.so.0=0.0.0", + "cmd:charon-cmd", + "cmd:ipsec", + "cmd:pki", + "cmd:swanctl" + ] + }, + "libical-dev": { + "versions": { + "2.0.0-r3": 1545062403 + }, + "origin": "libical", + "dependencies": [ + "libical=2.0.0-r3", + "pkgconfig" + ], + "provides": [ + "pc:libical=2.0" + ] + }, + "zstd-doc": { + "versions": { + "1.3.8-r0": 1546966444 + }, + "origin": "zstd" + }, + "hylafax-lang": { + "versions": { + "6.0.7-r0": 1545746106 + }, + "origin": "hylafax", + "dependencies": [ + "ghostscript", + "bash", + "tiff-tools" + ] + }, + "sshfs-doc": { + "versions": { + "3.5.1-r0": 1548094627 + }, + "origin": "sshfs" + }, + "farstream-doc": { + "versions": { + "0.2.8-r2": 1543928858 + }, + "origin": "farstream" + }, + "uwsgi-xslt": { + "versions": { + "2.0.17.1-r0": 1545062214 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2", + "so:libxslt.so.1" + ] + }, + "attr-dev": { + "versions": { + "2.4.47-r7": 1542301505 + }, + "origin": "attr", + "dependencies": [ + "libattr=2.4.47-r7" + ] + }, + "apache2-proxy": { + "versions": { + "2.4.39-r0": 1554306836 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1" + ] + }, + "fish-doc": { + "versions": { + "2.7.1-r0": 1545302079 + }, + "origin": "fish" + }, + "alpine-ipxe-ipxe_usb": { + "versions": { + "1.0_git20180825-r1": 1545986078 + }, + "origin": "alpine-ipxe" + }, + "libxvmc": { + "versions": { + "1.0.10-r2": 1542900306 + }, + "origin": "libxvmc", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXvMC.so.1=1.0.0", + "so:libXvMCW.so.1=1.0.0" + ] + }, + "kamailio-sqlang": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ] + }, + "orbit2": { + "versions": { + "2.14.19-r4": 1543223380 + }, + "origin": "orbit2", + "dependencies": [ + "so:libIDL-2.so.0", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libORBit-2.so.0=0.1.0", + "so:libORBit-imodule-2.so.0=0.0.0", + "so:libORBitCosNaming-2.so.0=0.1.0", + "cmd:ior-decode-2", + "cmd:linc-cleanup-sockets", + "cmd:orbit-idl-2", + "cmd:typelib-dump" + ] + }, + "libmp3splt-dev": { + "versions": { + "0.9.2-r0": 1545069016 + }, + "origin": "libmp3splt", + "dependencies": [ + "pcre-dev", + "libogg-dev", + "libmad-dev", + "libvorbis-dev", + "libid3tag-dev", + "libtool", + "libmp3splt=0.9.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmp3splt=0.9.2" + ] + }, + "hunspell-dev": { + "versions": { + "1.6.2-r1": 1542883763 + }, + "origin": "hunspell", + "dependencies": [ + "hunspell=1.6.2-r1", + "pkgconfig" + ], + "provides": [ + "pc:hunspell=1.6.2" + ] + }, + "perl-test-requiresinternet": { + "versions": { + "0.05-r1": 1542821875 + }, + "origin": "perl-test-requiresinternet" + }, + "sic-doc": { + "versions": { + "1.2-r1": 1543248488 + }, + "origin": "sic" + }, + "fts-dev": { + "versions": { + "1.2.7-r1": 1543933812 + }, + "origin": "fts", + "dependencies": [ + "fts=1.2.7-r1", + "pkgconfig" + ], + "provides": [ + "pc:libfts=1.2.7", + "pc:musl-fts=1.2.7" + ] + }, + "squid-lang-sv": { + "versions": { + "4.4-r1": 1545216330 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-sub-install": { + "versions": { + "0.928-r0": 1542845633 + }, + "origin": "perl-sub-install", + "dependencies": [ + "perl" + ] + }, + "py-urwid": { + "versions": { + "1.3.1-r2": 1542981240 + }, + "origin": "py-urwid" + }, + "quazip": { + "versions": { + "0.7.3-r0": 1545118030 + }, + "origin": "quazip", + "dependencies": [ + "so:libQtCore.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libquazip.so.1=1.0.0" + ] + }, + "openldap-back-null": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "freeglut-dev": { + "versions": { + "3.0.0-r0": 1545291113 + }, + "origin": "freeglut", + "dependencies": [ + "mesa-dev", + "libx11-dev", + "libice-dev", + "libxxf86vm-dev", + "libxi-dev", + "glu-dev", + "freeglut=3.0.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:freeglut=3.0.0" + ] + }, + "pam-winbind": { + "versions": { + "4.8.11-r1": 1555334974 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0", + "so:libtalloc.so.2", + "so:libwbclient.so.0" + ] + }, + "irssi-doc": { + "versions": { + "1.1.2-r0": 1547738040 + }, + "origin": "irssi" + }, + "lutok-doc": { + "versions": { + "0.4-r2": 1542304898 + }, + "origin": "lutok" + }, + "logrotate-doc": { + "versions": { + "3.15.0-r0": 1545857314 + }, + "origin": "logrotate" + }, + "open-lldp": { + "versions": { + "0.9.46-r3": 1545062132 + }, + "origin": "open-lldp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libconfig.so.9", + "so:libnl.so.1" + ], + "provides": [ + "so:liblldp_clif.so.1=1.0.0", + "cmd:dcbtool", + "cmd:lldpad", + "cmd:lldptool" + ] + }, + "wireless-tools": { + "versions": { + "30_pre9-r0": 1545062252 + }, + "origin": "wireless-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ifrename", + "cmd:iwconfig", + "cmd:iwevent", + "cmd:iwgetid", + "cmd:iwlist", + "cmd:iwpriv", + "cmd:iwspy" + ] + }, + "linux-firmware-adaptec": { + "versions": { + "20190322-r0": 1554980655 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "linux-firmware-mediatek": { + "versions": { + "20190322-r0": 1554980651 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "perl-devel-checklib": { + "versions": { + "1.13-r0": 1543922456 + }, + "origin": "perl-devel-checklib", + "provides": [ + "cmd:use-devel-checklib" + ] + }, + "qemu-riscv32": { + "versions": { + "3.1.0-r3": 1551107301 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-riscv32" + ] + }, + "lsyncd": { + "versions": { + "2.2.3-r1": 1545253932 + }, + "origin": "lsyncd", + "dependencies": [ + "rsync", + "so:libc.musl-x86_64.so.1", + "so:liblua-5.3.so.0" + ], + "provides": [ + "cmd:lsyncd" + ] + }, + "ipvsadm": { + "versions": { + "1.29-r0": 1545118133 + }, + "origin": "ipvsadm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnl.so.1", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:ipvsadm", + "cmd:ipvsadm-restore", + "cmd:ipvsadm-save" + ] + }, + "lcms2-dev": { + "versions": { + "2.9-r1": 1542824189 + }, + "origin": "lcms2", + "dependencies": [ + "libjpeg-turbo-dev", + "tiff-dev", + "zlib-dev", + "lcms2=2.9-r1", + "pkgconfig" + ], + "provides": [ + "pc:lcms2=2.9" + ] + }, + "perl-test-tcp": { + "versions": { + "2.19-r1": 1543249857 + }, + "origin": "perl-test-tcp", + "dependencies": [ + "perl-test-sharedfork" + ] + }, + "py3-alabaster": { + "versions": { + "0.7.12-r0": 1542824966 + }, + "origin": "py-alabaster", + "dependencies": [ + "python3" + ] + }, + "conntrack-tools": { + "versions": { + "1.4.4-r0": 1545301899 + }, + "origin": "conntrack-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0", + "so:libnetfilter_conntrack.so.3", + "so:libnetfilter_cthelper.so.0", + "so:libnetfilter_cttimeout.so.1", + "so:libnetfilter_queue.so.1", + "so:libnfnetlink.so.0" + ], + "provides": [ + "cmd:conntrack", + "cmd:conntrackd", + "cmd:nfct" + ] + }, + "lua-sql": { + "versions": { + "2.3.5-r2": 1543924411 + }, + "origin": "lua-sql" + }, + "xmlindent-doc": { + "versions": { + "0.2.17-r0": 1543223065 + }, + "origin": "xmlindent" + }, + "portaudio-dev": { + "versions": { + "190600.20161030-r0": 1545073462 + }, + "origin": "portaudio", + "dependencies": [ + "pc:alsa", + "pkgconfig", + "portaudio=190600.20161030-r0" + ], + "provides": [ + "pc:portaudio-2.0=19" + ] + }, + "lttng-ust-doc": { + "versions": { + "2.10.1-r0": 1543249617 + }, + "origin": "lttng-ust" + }, + "lua5.2-mosquitto": { + "versions": { + "0.2-r1": 1543932700 + }, + "origin": "lua-mosquitto", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ] + }, + "libva-dev": { + "versions": { + "2.2.0-r0": 1542900261 + }, + "origin": "libva", + "dependencies": [ + "libx11-dev", + "libxext-dev", + "libxfixes-dev", + "libdrm-dev", + "libva=2.2.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libva-drm=1.2.0", + "pc:libva-x11=1.2.0", + "pc:libva=1.2.0" + ] + }, + "ifupdown": { + "versions": { + "0.7.53.1-r1": 1545066951 + }, + "origin": "ifupdown", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ifdown", + "cmd:ifquery", + "cmd:ifup" + ] + }, + "lua-dbi": { + "versions": { + "0.6-r3": 1545214227 + }, + "origin": "lua-dbi" + }, + "chrony": { + "versions": { + "3.4-r1": 1545841267 + }, + "origin": "chrony", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2" + ], + "provides": [ + "cmd:chronyc", + "cmd:chronyd" + ] + }, + "sdl_mixer": { + "versions": { + "1.2.12-r1": 1545162992 + }, + "origin": "sdl_mixer", + "dependencies": [ + "so:libSDL-1.2.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL_mixer-1.2.so.0=0.12.0" + ] + }, + "squid-lang-ar": { + "versions": { + "4.4-r1": 1545216323 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "inotify-tools-doc": { + "versions": { + "3.20.1-r1": 1545302267 + }, + "origin": "inotify-tools" + }, + "openssh-sftp-server": { + "versions": { + "7.9_p1-r5": 1556034594 + }, + "origin": "openssh", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "consolekit2-doc": { + "versions": { + "1.2.1-r1": 1545062380 + }, + "origin": "consolekit2" + }, + "tar": { + "versions": { + "1.32-r0": 1551091781 + }, + "origin": "tar", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tar" + ] + }, + "perl-test-pod-doc": { + "versions": { + "1.52-r0": 1542822493 + }, + "origin": "perl-test-pod" + }, + "perl-compress-raw-zlib": { + "versions": { + "2.084-r0": 1546943961 + }, + "origin": "perl-compress-raw-zlib", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "squid-lang-lv": { + "versions": { + "4.4-r1": 1545216327 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "rsyslog-pgsql": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "rsyslog-ompgsql=8.40.0-r3" + ] + }, + "kbd-legacy": { + "versions": { + "2.0.4-r3": 1545215187 + }, + "origin": "kbd" + }, + "perl-html-quoted-doc": { + "versions": { + "0.04-r0": 1545214096 + }, + "origin": "perl-html-quoted" + }, + "tarsnap-doc": { + "versions": { + "1.0.39-r4": 1544000650 + }, + "origin": "tarsnap" + }, + "perl-cgi-doc": { + "versions": { + "4.40-r0": 1543242201 + }, + "origin": "perl-cgi" + }, + "uwsgi-cgi": { + "versions": { + "2.0.17.1-r0": 1545062197 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-gunicorn": { + "versions": { + "19.7.1-r1": 1545214072 + }, + "origin": "py-gunicorn" + }, + "rsyslog-tls": { + "versions": { + "8.40.0-r3": 1548686792 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30" + ], + "provides": [ + "rsyslog-lmnsd_gtls=8.40.0-r3" + ] + }, + "iaxmodem-doc": { + "versions": { + "1.3.0-r0": 1545214093 + }, + "origin": "iaxmodem" + }, + "nano-doc": { + "versions": { + "3.2-r0": 1545292879 + }, + "origin": "nano" + }, + "gcr-doc": { + "versions": { + "3.28.0-r0": 1545300011 + }, + "origin": "gcr" + }, + "apache2-lua": { + "versions": { + "2.4.39-r0": 1554306835 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ] + }, + "py3-incremental": { + "versions": { + "17.5.0-r0": 1546513617 + }, + "origin": "py-incremental", + "dependencies": [ + "python3" + ] + }, + "imagemagick-dev": { + "versions": { + "7.0.8.44-r0": 1557126217 + }, + "origin": "imagemagick", + "dependencies": [ + "imagemagick-c++=7.0.8.44-r0", + "imagemagick-libs=7.0.8.44-r0", + "pkgconfig" + ], + "provides": [ + "pc:ImageMagick-7.Q16HDRI=7.0.8", + "pc:ImageMagick=7.0.8", + "pc:Magick++-7.Q16HDRI=7.0.8", + "pc:Magick++=7.0.8", + "pc:MagickCore-7.Q16HDRI=7.0.8", + "pc:MagickCore=7.0.8", + "pc:MagickWand-7.Q16HDRI=7.0.8", + "pc:MagickWand=7.0.8", + "cmd:Magick++-config", + "cmd:MagickCore-config", + "cmd:MagickWand-config" + ] + }, + "perl-test-harness": { + "versions": { + "3.42-r0": 1543238858 + }, + "origin": "perl-test-harness" + }, + "rsyslog-mmdblookup": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libmaxminddb.so.0" + ] + }, + "policyd-spf-fs": { + "versions": { + "23-r3": 1543226491 + }, + "origin": "policyd-spf-fs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libspf2.so.2" + ], + "provides": [ + "cmd:policyd-spf-fs" + ] + }, + "protobuf-c-dev": { + "versions": { + "1.3.0-r6": 1545165151 + }, + "origin": "protobuf-c", + "dependencies": [ + "pkgconfig", + "protobuf-c=1.3.0-r6" + ], + "provides": [ + "pc:libprotobuf-c=1.3.0" + ] + }, + "gnokii-smsd-mysql": { + "versions": { + "0.6.31-r8": 1545300480 + }, + "origin": "gnokii", + "dependencies": [ + "gnokii-smsd", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "fftw-double-libs": { + "versions": { + "3.3.8-r0": 1543926479 + }, + "origin": "fftw", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libfftw3.so.3=3.5.8", + "so:libfftw3_threads.so.3=3.5.8" + ] + }, + "libuv-dbg": { + "versions": { + "1.23.2-r0": 1542820441 + }, + "origin": "libuv" + }, + "libgcrypt": { + "versions": { + "1.8.4-r0": 1545838380 + }, + "origin": "libgcrypt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgpg-error.so.0" + ], + "provides": [ + "so:libgcrypt.so.20=20.2.4", + "cmd:dumpsexp", + "cmd:hmac256", + "cmd:mpicalc" + ] + }, + "lua-soap": { + "versions": { + "3.0-r0": 1545207745 + }, + "origin": "lua-soap", + "dependencies": [ + "lua-expat", + "lua-socket" + ] + }, + "docs": { + "versions": { + "0.2-r0": 1545229743 + }, + "origin": "docs", + "dependencies": [ + "man" + ] + }, + "krb5-doc": { + "versions": { + "1.15.5-r0": 1548094457 + }, + "origin": "krb5" + }, + "bison": { + "versions": { + "3.0.5-r0": 1542300926 + }, + "origin": "bison", + "dependencies": [ + "m4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bison", + "cmd:yacc" + ] + }, + "grub-dev": { + "versions": { + "2.02-r14": 1548432369 + }, + "origin": "grub" + }, + "indent-lang": { + "versions": { + "2.2.12-r0": 1545299644 + }, + "origin": "indent" + }, + "perl-file-slurp": { + "versions": { + "9999.25-r0": 1546005797 + }, + "origin": "perl-file-slurp" + }, + "samba": { + "versions": { + "4.8.11-r1": 1555334977 + }, + "origin": "samba", + "dependencies": [ + "samba-server=4.8.11-r1", + "samba-client=4.8.11-r1", + "samba-common-tools=4.8.11-r1" + ] + }, + "libmpeg2-doc": { + "versions": { + "0.5.1-r8": 1544000975 + }, + "origin": "libmpeg2" + }, + "ncdu-doc": { + "versions": { + "1.13-r0": 1545154408 + }, + "origin": "ncdu" + }, + "libvpx-utils": { + "versions": { + "1.6.1-r1": 1545163814 + }, + "origin": "libvpx", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libvpx.so.4" + ], + "provides": [ + "cmd:vpxdec", + "cmd:vpxenc" + ] + }, + "libverto-dev": { + "versions": { + "0.3.0-r1": 1543223524 + }, + "origin": "libverto", + "dependencies": [ + "libverto-glib=0.3.0-r1", + "libverto-libev=0.3.0-r1", + "libverto-libevent=0.3.0-r1", + "libverto=0.3.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:libverto-glib=0.3.0", + "pc:libverto-libev=0.3.0", + "pc:libverto-libevent=0.3.0", + "pc:libverto=0.3.0" + ] + }, + "gcr-dev": { + "versions": { + "3.28.0-r0": 1545300010 + }, + "origin": "gcr", + "dependencies": [ + "glib-dev", + "gtk+3.0-dev", + "libgcrypt-dev", + "p11-kit-dev", + "gcr=3.28.0-r0", + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:gtk+-3.0", + "pc:p11-kit-1", + "pkgconfig" + ], + "provides": [ + "pc:gck-1=3.28.0", + "pc:gcr-3=3.28.0", + "pc:gcr-base-3=3.28.0", + "pc:gcr-ui-3=3.28.0" + ] + }, + "cdrkit": { + "versions": { + "1.1.11-r2": 1545075404 + }, + "origin": "cdrkit", + "dependencies": [ + "file", + "bzip2", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:cdda2mp3", + "cmd:cdda2ogg", + "cmd:cdda2wav", + "cmd:cdrecord", + "cmd:devdump", + "cmd:dirsplit", + "cmd:genisoimage", + "cmd:icedax", + "cmd:isodebug", + "cmd:isodump", + "cmd:isoinfo", + "cmd:isovfy", + "cmd:mkhybrid", + "cmd:mkisofs", + "cmd:netscsid", + "cmd:pitchplay", + "cmd:readcd", + "cmd:readmult", + "cmd:readom", + "cmd:wodim" + ] + }, + "opusfile-doc": { + "versions": { + "0.11-r1": 1545209752 + }, + "origin": "opusfile" + }, + "soxr-dev": { + "versions": { + "0.1.3-r0": 1545208987 + }, + "origin": "soxr", + "dependencies": [ + "cmake", + "pkgconfig", + "soxr=0.1.3-r0" + ], + "provides": [ + "pc:soxr-lsr=0.1.3", + "pc:soxr=0.1.3" + ] + }, + "irqbalance-openrc": { + "versions": { + "1.5.0-r0": 1545837194 + }, + "origin": "irqbalance" + }, + "xkeyboard-config": { + "versions": { + "2.22-r0": 1542973216 + }, + "origin": "xkeyboard-config" + }, + "perl-rrd": { + "versions": { + "1.7.0-r0": 1542924801 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librrd.so.8" + ] + }, + "postgresql-pllua-dev": { + "versions": { + "2.0-r0": 1544798727 + }, + "origin": "postgresql-pllua" + }, + "py2-ldb": { + "versions": { + "1.3.8-r0": 1555334661 + }, + "origin": "ldb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libldb.so.1", + "so:libpython2.7.so.1.0", + "so:libtalloc.so.2" + ], + "provides": [ + "py-ldb=1.3.8-r0", + "so:libpyldb-util.so.1=1.3.8" + ] + }, + "iftop": { + "versions": { + "0.17-r7": 1542981213 + }, + "origin": "iftop", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:iftop" + ] + }, + "glu-dev": { + "versions": { + "9.0.0-r4": 1543927738 + }, + "origin": "glu", + "dependencies": [ + "mesa-dev", + "glu=9.0.0-r4", + "pc:gl", + "pkgconfig" + ], + "provides": [ + "pc:glu=9.0.0" + ] + }, + "glib-dev": { + "versions": { + "2.58.1-r2": 1545290825 + }, + "origin": "glib", + "dependencies": [ + "python3", + "gettext-dev", + "zlib-dev", + "bzip2-dev", + "libffi-dev", + "util-linux-dev", + "libxml2-utils", + "libxslt", + "docbook-xml", + "docbook-xsl", + "glib=2.58.1-r2", + "pc:libpcre", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "pc:gio-2.0=2.58.1", + "pc:gio-unix-2.0=2.58.1", + "pc:glib-2.0=2.58.1", + "pc:gmodule-2.0=2.58.1", + "pc:gmodule-export-2.0=2.58.1", + "pc:gmodule-no-export-2.0=2.58.1", + "pc:gobject-2.0=2.58.1", + "pc:gthread-2.0=2.58.1", + "cmd:gdbus-codegen", + "cmd:glib-compile-resources", + "cmd:glib-genmarshal", + "cmd:glib-gettextize", + "cmd:glib-mkenums", + "cmd:gobject-query", + "cmd:gresource", + "cmd:gtester", + "cmd:gtester-report" + ] + }, + "file-doc": { + "versions": { + "5.36-r0": 1557160812 + }, + "origin": "file" + }, + "libshout": { + "versions": { + "2.4.1-r5": 1545117136 + }, + "origin": "libshout", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libogg.so.0", + "so:libspeex.so.1", + "so:libssl.so.1.1", + "so:libtheora.so.0", + "so:libvorbis.so.0" + ], + "provides": [ + "so:libshout.so.3=3.2.0" + ] + }, + "bind-dnssec-tools": { + "versions": { + "9.12.4_p1-r1": 1556801608 + }, + "origin": "bind", + "dependencies": [ + "py3-bind=9.12.4_p1-r1" + ], + "provides": [ + "cmd:dnssec-checkds", + "cmd:dnssec-coverage", + "cmd:dnssec-keymgr" + ] + }, + "libidn": { + "versions": { + "1.35-r0": 1542924921 + }, + "origin": "libidn", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libidn.so.12=12.6.0", + "cmd:idn" + ] + }, + "acf-iproute2-qos": { + "versions": { + "0.4.0-r2": 1545116953 + }, + "origin": "acf-iproute2-qos", + "dependencies": [ + "acf-core", + "iproute2-qos", + "acf-alpine-baselayout>=0.5.7" + ] + }, + "asterisk-odbc": { + "versions": { + "15.7.1-r0": 1546247584 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ] + }, + "py-pygments": { + "versions": { + "2.2.0-r0": 1542824953 + }, + "origin": "py-pygments", + "dependencies": [ + "py3-pygments", + "py3-pygments=2.2.0-r0" + ] + }, + "perl-convert-asn1-doc": { + "versions": { + "0.27-r0": 1543081682 + }, + "origin": "perl-convert-asn1" + }, + "perl-http-message": { + "versions": { + "6.18-r0": 1542821805 + }, + "origin": "perl-http-message", + "dependencies": [ + "perl-lwp-mediatypes", + "perl-encode-locale", + "perl-http-date", + "perl-uri", + "perl-io-html" + ] + }, + "perl-boolean-doc": { + "versions": { + "0.46-r0": 1543928862 + }, + "origin": "perl-boolean" + }, + "confuse-doc": { + "versions": { + "3.2.2-r0": 1545922117 + }, + "origin": "confuse" + }, + "halberd": { + "versions": { + "0.2.4-r1": 1545299951 + }, + "origin": "halberd", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:halberd" + ] + }, + "freeradius-client-dev": { + "versions": { + "1.1.7-r2": 1543922358 + }, + "origin": "freeradius-client", + "dependencies": [ + "freeradius-client=1.1.7-r2" + ] + }, + "binutils-dev": { + "versions": { + "2.31.1-r2": 1546441178 + }, + "origin": "binutils", + "dependencies": [ + "binutils=2.31.1-r2" + ] + }, + "vte3-lang": { + "versions": { + "0.52.2-r0": 1545215299 + }, + "origin": "vte3" + }, + "attr-doc": { + "versions": { + "2.4.47-r7": 1542301505 + }, + "origin": "attr" + }, + "qemu-system-tricore": { + "versions": { + "3.1.0-r3": 1551107306 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-tricore" + ] + }, + "snownews-doc": { + "versions": { + "1.5.12-r8": 1543999423 + }, + "origin": "snownews" + }, + "lxc-templates-legacy-alpine": { + "versions": { + "3.0.3-r0": 1546416504 + }, + "origin": "lxc-templates-legacy" + }, + "kamailio-perl": { + "versions": { + "5.2.0-r1": 1546423173 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "nginx-mod-http-headers-more": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "libmp3splt": { + "versions": { + "0.9.2-r0": 1545069016 + }, + "origin": "libmp3splt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libid3tag.so.0", + "so:libltdl.so.7", + "so:libmad.so.0", + "so:libogg.so.0", + "so:libpcre.so.1", + "so:libvorbis.so.0", + "so:libvorbisfile.so.3" + ], + "provides": [ + "so:libmp3splt.so.0=0.0.9" + ] + }, + "py-asn1": { + "versions": { + "0.4.2-r0": 1542821567 + }, + "origin": "py-asn1" + }, + "rsyslog-relp": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:librelp.so.0" + ], + "provides": [ + "rsyslog-imrelp=8.40.0-r3", + "rsyslog-omrelp=8.40.0-r3" + ] + }, + "pangomm": { + "versions": { + "2.40.2-r0": 1543925531 + }, + "origin": "pangomm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairomm-1.0.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libglibmm-2.4.so.1", + "so:libgobject-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libpangomm-1.4.so.1=1.0.30" + ] + }, + "cgit": { + "versions": { + "1.2.1-r1": 1544000809 + }, + "origin": "cgit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua-5.3.so.0", + "so:libz.so.1" + ] + }, + "transmission": { + "versions": { + "2.94-r1": 1545208750 + }, + "origin": "transmission", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libevent-2.1.so.6", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:transmission-gtk" + ] + }, + "libxp": { + "versions": { + "1.0.3-r2": 1545224135 + }, + "origin": "libxp", + "dependencies": [ + "so:libX11.so.6", + "so:libXau.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXp.so.6=6.2.0" + ] + }, + "sipp": { + "versions": { + "3.5.2-r0": 1545300214 + }, + "origin": "sipp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libpcap.so.1", + "so:libsctp.so.1", + "so:libssl.so.1.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:sipp" + ] + }, + "py3-pynacl": { + "versions": { + "1.3.0-r1": 1546125247 + }, + "origin": "py-pynacl", + "dependencies": [ + "py3-cffi", + "py3-six", + "python3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "perl-x10": { + "versions": { + "0.04-r1": 1545163614 + }, + "origin": "perl-x10", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:x10client", + "cmd:x10server" + ] + }, + "duplicity-lang": { + "versions": { + "0.7.18.2-r0": 1544000473 + }, + "origin": "duplicity", + "dependencies": [ + "python2", + "py-boto", + "gnupg", + "ncftp", + "py2-fasteners" + ] + }, + "wget-doc": { + "versions": { + "1.20.3-r0": 1554718236 + }, + "origin": "wget" + }, + "lua5.2-microlight": { + "versions": { + "1.1.1-r2": 1543249969 + }, + "origin": "lua-microlight" + }, + "logrotate": { + "versions": { + "3.15.0-r0": 1545857314 + }, + "origin": "logrotate", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpopt.so.0" + ], + "provides": [ + "cmd:logrotate" + ] + }, + "libmcrypt-doc": { + "versions": { + "2.5.8-r7": 1545066949 + }, + "origin": "libmcrypt" + }, + "py2-parsing": { + "versions": { + "2.2.0-r0": 1542825044 + }, + "origin": "py-parsing", + "dependencies": [ + "python2" + ] + }, + "perl-test-pod-coverage": { + "versions": { + "1.10-r0": 1542845675 + }, + "origin": "perl-test-pod-coverage", + "dependencies": [ + "perl", + "perl-pod-coverage", + "perl-test-pod", + "perl-devel-symdump" + ] + }, + "libnetfilter_acct": { + "versions": { + "1.0.3-r0": 1545209158 + }, + "origin": "libnetfilter_acct", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0" + ], + "provides": [ + "so:libnetfilter_acct.so.1=1.0.0" + ] + }, + "gnome-vfs-lang": { + "versions": { + "2.24.4-r6": 1545299691 + }, + "origin": "gnome-vfs", + "dependencies": [ + "gnome-mime-data" + ] + }, + "perl-test-mockmodule": { + "versions": { + "0.15-r0": 1543932008 + }, + "origin": "perl-test-mockmodule", + "dependencies": [ + "perl" + ] + }, + "mosquitto-clients": { + "versions": { + "1.5.6-r0": 1549970968 + }, + "origin": "mosquitto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ], + "provides": [ + "cmd:mosquitto_pub", + "cmd:mosquitto_sub" + ] + }, + "libvirt-doc": { + "versions": { + "4.10.0-r1": 1547051657 + }, + "origin": "libvirt" + }, + "nagios-plugins-openrc": { + "versions": { + "2.2.1-r6": 1543933914 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "openrc", + "sudo" + ] + }, + "pjsua": { + "versions": { + "2.7.2-r4": 1545073632 + }, + "origin": "pjproject", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpj.so.2", + "so:libpjlib-util.so.2", + "so:libpjmedia.so.2", + "so:libpjsip-simple.so.2", + "so:libpjsip.so.2", + "so:libpjsua.so.2" + ], + "provides": [ + "cmd:pjsua" + ] + }, + "cyrus-sasl-scram": { + "versions": { + "2.1.27-r1": 1550353528 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ] + }, + "fuse3-dev": { + "versions": { + "3.2.6-r1": 1548943636 + }, + "origin": "fuse3", + "dependencies": [ + "fuse3=3.2.6-r1", + "pkgconfig" + ], + "provides": [ + "pc:fuse3=3.2.6" + ] + }, + "oidentd": { + "versions": { + "2.2.3-r0": 1545163555 + }, + "origin": "oidentd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:oidentd" + ] + }, + "imlib2": { + "versions": { + "1.5.1-r0": 1543221705 + }, + "origin": "imlib2", + "dependencies": [ + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libXext.so.6", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libgif.so.7", + "so:libid3tag.so.0", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libtiff.so.5", + "so:libxcb-shm.so.0", + "so:libxcb.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libImlib2.so.1=1.5.1", + "cmd:imlib2_bumpmap", + "cmd:imlib2_colorspace", + "cmd:imlib2_conv", + "cmd:imlib2_grab", + "cmd:imlib2_poly", + "cmd:imlib2_show", + "cmd:imlib2_test", + "cmd:imlib2_view" + ] + }, + "perl-test-identity": { + "versions": { + "0.01-r0": 1544799040 + }, + "origin": "perl-test-identity" + }, + "p11-kit-doc": { + "versions": { + "0.23.14-r0": 1544791899 + }, + "origin": "p11-kit" + }, + "lua-lzlib": { + "versions": { + "0.4.3-r0": 1542845790 + }, + "origin": "lua-lzlib" + }, + "py3-parsing": { + "versions": { + "2.2.0-r0": 1542825048 + }, + "origin": "py-parsing", + "dependencies": [ + "python3" + ] + }, + "freeradius-checkrad": { + "versions": { + "3.0.17-r5": 1556201989 + }, + "origin": "freeradius", + "dependencies": [ + "perl", + "perl-net-telnet", + "perl-snmp-session", + "net-snmp-tools" + ], + "provides": [ + "cmd:checkrad" + ] + }, + "unbound-doc": { + "versions": { + "1.8.3-r1": 1555953584 + }, + "origin": "unbound" + }, + "xclip-doc": { + "versions": { + "0.13-r0": 1542883352 + }, + "origin": "xclip" + }, + "mpg123-dev": { + "versions": { + "1.25.10-r0": 1545117148 + }, + "origin": "mpg123", + "dependencies": [ + "mpg123=1.25.10-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmpg123=1.25.10", + "pc:libout123=1.25.10" + ] + }, + "mkfontdir-doc": { + "versions": { + "1.0.7-r1": 1542924704 + }, + "origin": "mkfontdir" + }, + "libgsf-dev": { + "versions": { + "1.14.44-r0": 1543926573 + }, + "origin": "libgsf", + "dependencies": [ + "bzip2-dev", + "libgsf=1.14.44-r0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:libxml-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libgsf-1=1.14.44" + ] + }, + "linux-virt": { + "versions": { + "4.19.41-r0": 1557399975 + }, + "origin": "linux-vanilla", + "dependencies": [ + "mkinitfs" + ] + }, + "perl-sub-info": { + "versions": { + "0.002-r0": 1542972997 + }, + "origin": "perl-sub-info", + "dependencies": [ + "perl-importer", + "perl-test-simple" + ] + }, + "amavisd-new": { + "versions": { + "2.11.1-r0": 1545229742 + }, + "origin": "amavisd-new", + "dependencies": [ + "sed", + "file", + "perl", + "perl-archive-zip", + "perl-carp", + "perl-convert-tnef", + "perl-compress-raw-zlib", + "perl-convert-uulib", + "perl-digest-md5", + "perl-io", + "perl-exporter", + "perl-io-compress", + "perl-io-stringy", + "perl-mime-tools", + "perl-mailtools", + "perl-socket", + "perl-net-libidn", + "perl-net-server", + "perl-time-hires", + "perl-unix-syslog", + "perl-db", + "perl-mail-dkim", + "perl-io-socket-inet6", + "/bin/sh" + ], + "provides": [ + "cmd:amavisd", + "cmd:amavisd-nanny", + "cmd:amavisd-release" + ] + }, + "perl-encode-utils": { + "versions": { + "2.98-r0": 1543935164 + }, + "origin": "perl-encode", + "dependencies": [ + "perl-encode", + "perl" + ], + "provides": [ + "perl-encode-piconv", + "cmd:encguess", + "cmd:piconv" + ] + }, + "lua5.2-sqlite": { + "versions": { + "0.9.5-r2": 1545214288 + }, + "origin": "lua-sqlite", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "bearssl": { + "versions": { + "0.6-r0": 1546957733 + }, + "origin": "bearssl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libbearssl.so.0=0.6", + "cmd:brssl" + ] + }, + "py-configshell": { + "versions": { + "1.1_p19-r3": 1542981244 + }, + "origin": "py-configshell", + "dependencies": [ + "python2", + "py-six", + "py-urwid", + "py-parsing" + ] + }, + "icon-naming-utils": { + "versions": { + "0.8.90-r2": 1545075429 + }, + "origin": "icon-naming-utils", + "dependencies": [ + "perl-xml-simple", + "pkgconfig" + ], + "provides": [ + "pc:icon-naming-utils=0.8.90" + ] + }, + "lua5.2-bit32": { + "versions": { + "5.3.0-r2": 1546011988 + }, + "origin": "lua-bit32", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "vimdiff": { + "versions": { + "8.1.0630-r0": 1545664993 + }, + "origin": "vim", + "dependencies": [ + "diffutils", + "vim=8.1.0630-r0" + ] + }, + "perl-mime-tools-doc": { + "versions": { + "5.509-r1": 1544001504 + }, + "origin": "perl-mime-tools" + }, + "mysql-bench": { + "versions": { + "10.3.13-r1": 1557431736 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-client" + ] + }, + "aspell-libs": { + "versions": { + "0.60.6.1-r13": 1542965830 + }, + "origin": "aspell", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libaspell.so.15=15.1.5", + "so:libpspell.so.15=15.1.5" + ] + }, + "nagios-plugins-ifoperstatus": { + "versions": { + "2.2.1-r6": 1543933906 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "perl" + ] + }, + "sdl2": { + "versions": { + "2.0.9-r0": 1543934457 + }, + "origin": "sdl2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL2-2.0.so.0=0.9.0" + ] + }, + "collectd-bind": { + "versions": { + "5.8.0-r3": 1546422677 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libxml2.so.2" + ] + }, + "gcalcli": { + "versions": { + "3.4.0-r1": 1545292692 + }, + "origin": "gcalcli", + "dependencies": [ + "python3", + "py-google-api-python-client", + "py-dateutil", + "py-gflags", + "py-vobject" + ], + "provides": [ + "cmd:gcalcli" + ] + }, + "mupdf-gl": { + "versions": { + "1.13.0-r2": 1544000576 + }, + "origin": "mupdf", + "dependencies": [ + "so:libGL.so.1", + "so:libX11.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1", + "so:libmupdf.so.0", + "so:libmupdfthird.so.0" + ], + "provides": [ + "cmd:mupdf-gl" + ] + }, + "lua5.3-optarg": { + "versions": { + "0.2-r1": 1542820941 + }, + "origin": "lua-optarg", + "dependencies": [ + "lua5.3" + ] + }, + "libburn-dev": { + "versions": { + "1.5.0-r0": 1545837389 + }, + "origin": "libburn", + "dependencies": [ + "libburn=1.5.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libburn-1=1.5.0" + ] + }, + "py2-cliapp": { + "versions": { + "1.20170823-r0": 1544798594 + }, + "origin": "py-cliapp", + "dependencies": [ + "python2", + "python3", + "python2" + ] + }, + "font-arabic-misc": { + "versions": { + "1.0.3-r0": 1545292771 + }, + "origin": "font-arabic-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "libproxy": { + "versions": { + "0.4.15-r1": 1544001160 + }, + "origin": "libproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libproxy.so.1=1.0.0" + ] + }, + "glamor-egl-dev": { + "versions": { + "0.6.0-r4": 1545060558 + }, + "origin": "glamor-egl", + "dependencies": [ + "mesa-dev", + "glamor-egl=0.6.0-r4", + "pkgconfig" + ], + "provides": [ + "pc:glamor-egl=0.6.0", + "pc:glamor=0.6.0" + ] + }, + "perl-html-rewriteattributes-doc": { + "versions": { + "0.05-r1": 1545289344 + }, + "origin": "perl-html-rewriteattributes" + }, + "mupdf-x11": { + "versions": { + "1.13.0-r2": 1544000575 + }, + "origin": "mupdf", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1", + "so:libmupdf.so.0", + "so:libmupdfthird.so.0" + ], + "provides": [ + "cmd:mupdf-x11" + ] + }, + "libproxy-dev": { + "versions": { + "0.4.15-r1": 1544001154 + }, + "origin": "libproxy", + "dependencies": [ + "zlib-dev", + "libproxy=0.4.15-r1", + "pkgconfig" + ], + "provides": [ + "pc:libproxy-1.0=0.4.15" + ] + }, + "py3-urwid": { + "versions": { + "1.3.1-r2": 1542981238 + }, + "origin": "py-urwid", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "libinput": { + "versions": { + "1.12.4-r0": 1545734494 + }, + "origin": "libinput", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libevdev.so.2", + "so:libinput.so.10", + "so:libudev.so.1" + ], + "provides": [ + "cmd:libinput" + ] + }, + "xwininfo": { + "versions": { + "1.1.4-r0": 1545249943 + }, + "origin": "xwininfo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-shape.so.0", + "so:libxcb.so.1" + ], + "provides": [ + "cmd:xwininfo" + ] + }, + "gstreamer0.10-doc": { + "versions": { + "0.10.36-r3": 1544000752 + }, + "origin": "gstreamer0.10" + }, + "perl-extutils-helpers-doc": { + "versions": { + "0.026-r0": 1543238832 + }, + "origin": "perl-extutils-helpers" + }, + "perl-locale-maketext-fuzzy": { + "versions": { + "0.11-r1": 1544000297 + }, + "origin": "perl-locale-maketext-fuzzy", + "dependencies": [ + "perl" + ] + }, + "linux-firmware-ath9k_htc": { + "versions": { + "20190322-r0": 1554980654 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "libical": { + "versions": { + "2.0.0-r3": 1545062404 + }, + "origin": "libical", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libical.so.2=2.0.0", + "so:libical_cxx.so.2=2.0.0", + "so:libicalss.so.2=2.0.0", + "so:libicalss_cxx.so.2=2.0.0", + "so:libicalvcal.so.2=2.0.0" + ] + }, + "upower-dev": { + "versions": { + "0.99.7-r0": 1545069384 + }, + "origin": "upower", + "dependencies": [ + "pc:gio-2.0", + "pc:glib-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pkgconfig", + "upower=0.99.7-r0" + ], + "provides": [ + "pc:upower-glib=0.99.7" + ] + }, + "perl-http-daemon-doc": { + "versions": { + "6.01-r1": 1542821822 + }, + "origin": "perl-http-daemon" + }, + "acf-gross": { + "versions": { + "0.6.0-r2": 1545062675 + }, + "origin": "acf-gross", + "dependencies": [ + "acf-core", + "gross" + ] + }, + "ncurses": { + "versions": { + "6.1_p20190105-r0": 1546948260 + }, + "origin": "ncurses", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:captoinfo", + "cmd:clear", + "cmd:infocmp", + "cmd:infotocap", + "cmd:reset", + "cmd:tabs", + "cmd:tic", + "cmd:toe", + "cmd:tput", + "cmd:tset" + ] + }, + "gdk-pixbuf-dbg": { + "versions": { + "2.36.11-r2": 1543253991 + }, + "origin": "gdk-pixbuf", + "dependencies": [ + "shared-mime-info" + ] + }, + "perl-text-reform-doc": { + "versions": { + "1.20-r0": 1545118015 + }, + "origin": "perl-text-reform" + }, + "py2-flake8": { + "versions": { + "3.4.1-r2": 1545223355 + }, + "origin": "py-flake8", + "dependencies": [ + "py2-mccabe", + "py2-pep8", + "py2-pyflakes", + "python2" + ], + "provides": [ + "flake8=3.4.1-r2", + "cmd:flake8-2" + ] + }, + "py2-pep8": { + "versions": { + "1.7.1-r0": 1542902304 + }, + "origin": "py-pep8", + "dependencies": [ + "python2" + ], + "provides": [ + "py-pep8=1.7.1-r0", + "cmd:pep8-2" + ] + }, + "nspr-dev": { + "versions": { + "4.20-r0": 1545207861 + }, + "origin": "nspr", + "dependencies": [ + "nspr", + "linux-headers", + "pkgconfig" + ], + "provides": [ + "pc:nspr=4.20.0", + "cmd:nspr-config" + ] + }, + "apache2-http2": { + "versions": { + "2.4.39-r0": 1554306835 + }, + "origin": "apache2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libnghttp2.so.14" + ] + }, + "graphviz": { + "versions": { + "2.40.1-r1": 1543926741 + }, + "origin": "graphviz", + "dependencies": [ + "/bin/sh", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libltdl.so.7", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpangoft2-1.0.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libcdt.so.5=5.0.0", + "so:libcgraph.so.6=6.0.0", + "so:libgvc.so.6=6.0.0", + "so:libgvplugin_core.so.6=6.0.0", + "so:libgvplugin_dot_layout.so.6=6.0.0", + "so:libgvplugin_neato_layout.so.6=6.0.0", + "so:libgvplugin_pango.so.6=6.0.0", + "so:libgvplugin_xlib.so.6=6.0.0", + "so:libgvpr.so.2=2.0.0", + "so:liblab_gamut.so.1=1.0.0", + "so:libpathplan.so.4=4.0.0", + "so:libxdot.so.4=4.0.0", + "cmd:acyclic", + "cmd:bcomps", + "cmd:ccomps", + "cmd:circo", + "cmd:cluster", + "cmd:dijkstra", + "cmd:dot", + "cmd:dot2gxl", + "cmd:dot_builtins", + "cmd:dotty", + "cmd:edgepaint", + "cmd:fdp", + "cmd:gc", + "cmd:gml2gv", + "cmd:graphml2gv", + "cmd:gv2gml", + "cmd:gv2gxl", + "cmd:gvcolor", + "cmd:gvgen", + "cmd:gvmap", + "cmd:gvmap.sh", + "cmd:gvpack", + "cmd:gvpr", + "cmd:gxl2dot", + "cmd:gxl2gv", + "cmd:lneato", + "cmd:mm2gv", + "cmd:neato", + "cmd:nop", + "cmd:osage", + "cmd:patchwork", + "cmd:prune", + "cmd:sccmap", + "cmd:sfdp", + "cmd:tred", + "cmd:twopi", + "cmd:unflatten", + "cmd:vimdot" + ] + }, + "libxxf86dga-doc": { + "versions": { + "1.1.4-r2": 1542845774 + }, + "origin": "libxxf86dga" + }, + "liblockfile-doc": { + "versions": { + "1.14-r0": 1545075167 + }, + "origin": "liblockfile" + }, + "cloog": { + "versions": { + "0.18.4-r2": 1543226589 + }, + "origin": "cloog", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libisl.so.15" + ], + "provides": [ + "so:libcloog-isl.so.4=4.0.0", + "cmd:cloog" + ] + }, + "sysfsutils": { + "versions": { + "2.1.0-r8": 1542924685 + }, + "origin": "sysfsutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsysfs.so.2=2.0.1", + "cmd:dlist_test", + "cmd:get_device", + "cmd:get_driver", + "cmd:get_module", + "cmd:systool" + ] + }, + "acf-openldap": { + "versions": { + "1.0.1-r4": 1545154436 + }, + "origin": "acf-openldap", + "dependencies": [ + "acf-core", + "openldap", + "openldap-back-bdb" + ] + }, + "qemu-system-sparc": { + "versions": { + "3.1.0-r3": 1551107305 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-sparc" + ] + }, + "lua5.1-maxminddb": { + "versions": { + "0.1-r1": 1543226669 + }, + "origin": "lua-maxminddb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmaxminddb.so.0" + ] + }, + "linux-firmware-dpaa2": { + "versions": { + "20190322-r0": 1554980653 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "xorg-server-doc": { + "versions": { + "1.20.3-r1": 1543928068 + }, + "origin": "xorg-server" + }, + "libnftnl-libs": { + "versions": { + "1.1.1-r0": 1542893203 + }, + "origin": "libnftnl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjansson.so.4", + "so:libmnl.so.0" + ], + "provides": [ + "so:libnftnl.so.7=7.3.0" + ] + }, + "ncurses-terminfo": { + "versions": { + "6.1_p20190105-r0": 1546948260 + }, + "origin": "ncurses", + "dependencies": [ + "ncurses-terminfo-base", + "ncurses-terminfo-base=6.1_p20190105-r0" + ] + }, + "ncurses5": { + "versions": { + "5.9-r1": 1545292735 + }, + "origin": "ncurses5" + }, + "libisoburn-doc": { + "versions": { + "1.4.8-r0": 1544797304 + }, + "origin": "libisoburn" + }, + "speex": { + "versions": { + "1.2.0-r0": 1544799252 + }, + "origin": "speex", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libspeex.so.1=1.5.1" + ] + }, + "varnish-dev": { + "versions": { + "6.1.1-r0": 1545300751 + }, + "origin": "varnish", + "dependencies": [ + "pkgconfig", + "varnish-libs=6.1.1-r0" + ], + "provides": [ + "pc:varnishapi=6.1.1" + ] + }, + "linux-firmware-ath10k": { + "versions": { + "20190322-r0": 1554980654 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "xf86-input-libinput": { + "versions": { + "0.28.1-r0": 1545207901 + }, + "origin": "xf86-input-libinput", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libinput.so.10" + ] + }, + "dbus-glib-doc": { + "versions": { + "0.108-r1": 1542824780 + }, + "origin": "dbus-glib" + }, + "openldap-back-monitor": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "bash-doc": { + "versions": { + "4.4.19-r1": 1542301270 + }, + "origin": "bash" + }, + "libmowgli": { + "versions": { + "2.1.3-r3": 1543998747 + }, + "origin": "libmowgli", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libmowgli-2.so.0=0.0.0" + ] + }, + "libvirt": { + "versions": { + "4.10.0-r1": 1547051658 + }, + "origin": "libvirt", + "dependencies": [ + "lvm2", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0", + "so:libdbus-1.so.3", + "so:libdevmapper.so.1.02", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libintl.so.8", + "so:libparted.so.2", + "so:libsasl2.so.3", + "so:libssh2.so.1", + "so:libtirpc.so.3", + "so:libvirt.so.0", + "so:libxml2.so.2", + "so:libyajl.so.2" + ], + "provides": [ + "cmd:virtlockd", + "cmd:virtlogd" + ] + }, + "eggdrop-gseen": { + "versions": { + "1.6.21-r2": 1545206950 + }, + "origin": "eggdrop", + "dependencies": [ + "tcl", + "so:libc.musl-x86_64.so.1", + "so:libtcl8.6.so" + ] + }, + "py-requests-oauthlib": { + "versions": { + "0.8.0-r1": 1545062567 + }, + "origin": "py-requests-oauthlib", + "dependencies": [ + "py-oauthlib", + "py-requests" + ] + }, + "rlog-doc": { + "versions": { + "1.4-r4": 1545061029 + }, + "origin": "rlog" + }, + "py3-mock": { + "versions": { + "2.0.0-r3": 1543925744 + }, + "origin": "py-mock", + "dependencies": [ + "py3-pbr", + "py3-six", + "python3" + ] + }, + "fetchmail": { + "versions": { + "6.3.26-r14": 1544001392 + }, + "origin": "fetchmail", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:fetchmail" + ] + }, + "perl-test-leaktrace": { + "versions": { + "0.16-r0": 1542821881 + }, + "origin": "perl-test-leaktrace", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "gtkman-lang": { + "versions": { + "1.0.1-r0": 1545075132 + }, + "origin": "gtkman", + "dependencies": [ + "py-gtk" + ] + }, + "font-misc-meltho": { + "versions": { + "1.0.3-r0": 1542981218 + }, + "origin": "font-misc-meltho", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "py3-newt": { + "versions": { + "0.52.20-r0": 1543924702 + }, + "origin": "newt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnewt.so.0.52" + ] + }, + "acf-nsd": { + "versions": { + "0.0.1-r0": 1545207717 + }, + "origin": "acf-nsd", + "dependencies": [ + "acf-core", + "nsd" + ] + }, + "quagga": { + "versions": { + "1.2.4-r1": 1545068941 + }, + "origin": "quagga", + "dependencies": [ + "iproute2", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libnetsnmp.so.35", + "so:libnetsnmpagent.so.35", + "so:libreadline.so.7" + ], + "provides": [ + "quagga-nhrp=1.2.4", + "so:libfpm_pb.so.0=0.0.0", + "so:libospf.so.0=0.0.0", + "so:libospfapiclient.so.0=0.0.0", + "so:libquagga_pb.so.0=0.0.0", + "so:libzebra.so.1=1.0.0", + "cmd:bgp_btoa", + "cmd:bgpd", + "cmd:isisd", + "cmd:nhrpd", + "cmd:ospf6d", + "cmd:ospfclient", + "cmd:ospfd", + "cmd:pimd", + "cmd:ripd", + "cmd:ripngd", + "cmd:test_igmpv3_join", + "cmd:vtysh", + "cmd:watchquagga", + "cmd:zebra" + ] + }, + "perl-net-openssh": { + "versions": { + "0.78-r0": 1545995475 + }, + "origin": "perl-net-openssh" + }, + "ircii": { + "versions": { + "20111115-r4": 1545213657 + }, + "origin": "ircii", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:irc", + "cmd:irc-20111115", + "cmd:ircbug", + "cmd:ircflush" + ] + }, + "apache2-utils": { + "versions": { + "2.4.39-r0": 1554306836 + }, + "origin": "apache2", + "dependencies": [ + "so:libapr-1.so.0", + "so:libaprutil-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:ab", + "cmd:checkgid", + "cmd:dbmmanage", + "cmd:htcacheclean", + "cmd:htdbm", + "cmd:htdigest", + "cmd:htpasswd", + "cmd:httxt2dbm", + "cmd:logresolve", + "cmd:rotatelogs" + ] + }, + "libwebp-tools": { + "versions": { + "1.0.1-r0": 1545856969 + }, + "origin": "libwebp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgif.so.7", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libtiff.so.5", + "so:libwebp.so.7", + "so:libwebpdemux.so.2", + "so:libwebpmux.so.3" + ], + "provides": [ + "cmd:cwebp", + "cmd:dwebp", + "cmd:gif2webp", + "cmd:img2webp", + "cmd:webpinfo", + "cmd:webpmux" + ] + }, + "db-doc": { + "versions": { + "5.3.28-r1": 1542819052 + }, + "origin": "db" + }, + "docbook-xsl": { + "versions": { + "1.79.1-r1": 1542304593 + }, + "origin": "docbook-xsl", + "dependencies": [ + "libxml2-utils", + "libxslt", + "docbook-xml", + "/bin/sh" + ] + }, + "zlib-dev": { + "versions": { + "1.2.11-r1": 1542300122 + }, + "origin": "zlib", + "dependencies": [ + "pkgconfig", + "zlib=1.2.11-r1" + ], + "provides": [ + "pc:zlib=1.2.11" + ] + }, + "perl-math-round-doc": { + "versions": { + "0.07-r0": 1545163052 + }, + "origin": "perl-math-round" + }, + "shorewall-core": { + "versions": { + "5.2.2-r0": 1548095414 + }, + "origin": "shorewall-core", + "provides": [ + "cmd:shorewall" + ] + }, + "lvm2-libs": { + "versions": { + "2.02.182-r0": 1543928934 + }, + "origin": "lvm2", + "dependencies": [ + "so:libaio.so.1", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper-event.so.1.02", + "so:libdevmapper.so.1.02" + ], + "provides": [ + "so:libdevmapper-event-lvm2.so.2.02=2.02", + "so:liblvm2app.so.2.2=2.2", + "so:liblvm2cmd.so.2.02=2.02" + ] + }, + "perl-mozilla-ca": { + "versions": { + "20160104-r0": 1545223048 + }, + "origin": "perl-mozilla-ca" + }, + "perl-b-hooks-endofscope-doc": { + "versions": { + "0.24-r0": 1542973108 + }, + "origin": "perl-b-hooks-endofscope" + }, + "iw-doc": { + "versions": { + "4.14-r0": 1543249945 + }, + "origin": "iw" + }, + "lua5.1-sql-postgres": { + "versions": { + "2.3.5-r2": 1543924397 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "razor-doc": { + "versions": { + "2.85-r7": 1545292685 + }, + "origin": "razor" + }, + "gtest": { + "versions": { + "1.8.1-r0": 1542822390 + }, + "origin": "gtest", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgtest.so=0", + "so:libgtest_main.so=0" + ] + }, + "vanessa_adt-dev": { + "versions": { + "0.0.9-r0": 1545223297 + }, + "origin": "vanessa_adt", + "dependencies": [ + "vanessa_logger-dev", + "vanessa_adt=0.0.9-r0" + ] + }, + "mariadb-test": { + "versions": { + "10.3.13-r1": 1557431727 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb-common", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:liblzma.so.5", + "so:libpcre.so.1", + "so:libpcreposix.so.0", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:my_safe_process", + "cmd:mysql_client_test", + "cmd:mysqltest", + "cmd:mysqltest_embedded" + ] + }, + "perl-html-formattext-withlinks-doc": { + "versions": { + "0.15-r0": 1545075963 + }, + "origin": "perl-html-formattext-withlinks" + }, + "perl-file-listing": { + "versions": { + "6.04-r1": 1542821849 + }, + "origin": "perl-file-listing", + "dependencies": [ + "perl", + "perl-http-date" + ] + }, + "libbonobo": { + "versions": { + "2.32.1-r6": 1545299732 + }, + "origin": "libbonobo", + "dependencies": [ + "so:libORBit-2.so.0", + "so:libORBitCosNaming-2.so.0", + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libintl.so.8", + "so:libxml2.so.2" + ], + "provides": [ + "so:libbonobo-2.so.0=0.0.0", + "so:libbonobo-activation.so.4=4.0.0", + "cmd:activation-client", + "cmd:bonobo-activation-sysconf", + "cmd:bonobo-slay" + ] + }, + "linux-firmware-mellanox": { + "versions": { + "20190322-r0": 1554980650 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "libxcomposite": { + "versions": { + "0.4.4-r2": 1543925371 + }, + "origin": "libxcomposite", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXcomposite.so.1=1.0.0" + ] + }, + "libsm-dev": { + "versions": { + "1.2.2-r2": 1542824341 + }, + "origin": "libsm", + "dependencies": [ + "libsm=1.2.2-r2", + "pc:ice", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:sm=1.2.2" + ] + }, + "lxc-download": { + "versions": { + "3.1.0-r1": 1549966161 + }, + "origin": "lxc", + "dependencies": [ + "lxc", + "gnupg1", + "tar", + "xz", + "wget" + ] + }, + "asterisk-alsa": { + "versions": { + "15.7.1-r0": 1546247586 + }, + "origin": "asterisk", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-gobject-dev": { + "versions": { + "2.28.7-r0": 1543927527 + }, + "origin": "py-gobject", + "dependencies": [ + "pc:gobject-2.0", + "pc:libffi", + "pkgconfig", + "py-gobject=2.28.7-r0" + ], + "provides": [ + "pc:pygobject-2.0=2.28.7" + ] + }, + "libxfixes-dev": { + "versions": { + "5.0.3-r2": 1542823751 + }, + "origin": "libxfixes", + "dependencies": [ + "libxfixes=5.0.3-r2", + "pc:fixesproto>=5.0", + "pc:x11", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xfixes=5.0.3" + ] + }, + "lua-cmsgpack": { + "versions": { + "0.4.0-r0": 1545163347 + }, + "origin": "lua-cmsgpack", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "acf-chrony": { + "versions": { + "0.8.0-r2": 1545299915 + }, + "origin": "acf-chrony", + "dependencies": [ + "acf-core", + "lua-posix", + "chrony" + ] + }, + "dmvpn": { + "versions": { + "1.0.2-r0": 1553425622 + }, + "origin": "dmvpn", + "dependencies": [ + "augeas", + "bind-tools", + "lua5.2", + "lua5.2-cqueues", + "lua5.2-lyaml", + "lua5.2-ossl", + "lua5.2-posix", + "lua5.2-struct", + "lua-dmvpn", + "quagga", + "strongswan", + "tunnel" + ], + "provides": [ + "cmd:nhrp-events", + "cmd:setup-dmvpn" + ] + }, + "cups": { + "versions": { + "2.2.10-r0": 1545910847 + }, + "origin": "cups", + "dependencies": [ + "cups-client", + "poppler-utils", + "openssl", + "dbus", + "/bin/sh", + "cups-client=2.2.10-r0", + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2", + "so:libcupsimage.so.2", + "so:libdbus-1.so.3", + "so:libgcc_s.so.1", + "so:libpaper.so.1", + "so:libstdc++.so.6", + "so:libusb-1.0.so.0" + ], + "provides": [ + "cmd:cupsd", + "cmd:cupsfilter" + ] + }, + "lua5.3-filesystem": { + "versions": { + "1.7.0.2-r0": 1542820935 + }, + "origin": "lua-filesystem", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1" + ] + }, + "protobuf-c": { + "versions": { + "1.3.0-r6": 1545165152 + }, + "origin": "protobuf-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libprotobuf.so.17", + "so:libprotoc.so.17", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libprotobuf-c.so.1=1.0.0", + "cmd:protoc-c", + "cmd:protoc-gen-c" + ] + }, + "libcurl": { + "versions": { + "7.64.0-r1": 1551205311 + }, + "origin": "curl", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libnghttp2.so.14", + "so:libssh2.so.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:libcurl.so.4=4.5.0" + ] + }, + "py3-pygments": { + "versions": { + "2.2.0-r0": 1542824946 + }, + "origin": "py-pygments", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:pygmentize-3" + ] + }, + "guile-doc": { + "versions": { + "2.0.14-r0": 1543227920 + }, + "origin": "guile" + }, + "ntfs-3g-libs": { + "versions": { + "2017.3.23-r1": 1542973172 + }, + "origin": "ntfs-3g", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libntfs-3g.so.88=88.0.0" + ] + }, + "py2-constantly": { + "versions": { + "15.1.0-r0": 1546513612 + }, + "origin": "py-constantly", + "dependencies": [ + "python2" + ] + }, + "menu-cache-dev": { + "versions": { + "0.5.1-r2": 1543999313 + }, + "origin": "menu-cache", + "dependencies": [ + "menu-cache=0.5.1-r2", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libmenu-cache=0.5.1" + ] + }, + "perl-dbd-mysql-doc": { + "versions": { + "4.050-r0": 1547124434 + }, + "origin": "perl-dbd-mysql" + }, + "xorg-server-xephyr": { + "versions": { + "1.20.3-r1": 1543928069 + }, + "origin": "xorg-server", + "dependencies": [ + "so:libGL.so.1", + "so:libX11-xcb.so.1", + "so:libX11.so.6", + "so:libXau.so.6", + "so:libXdmcp.so.6", + "so:libXfont2.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libepoxy.so.0", + "so:libpixman-1.so.0", + "so:libudev.so.1", + "so:libxcb-icccm.so.4", + "so:libxcb-image.so.0", + "so:libxcb-keysyms.so.1", + "so:libxcb-randr.so.0", + "so:libxcb-render-util.so.0", + "so:libxcb-render.so.0", + "so:libxcb-shape.so.0", + "so:libxcb-shm.so.0", + "so:libxcb-util.so.1", + "so:libxcb-xkb.so.1", + "so:libxcb-xv.so.0", + "so:libxcb.so.1", + "so:libxshmfence.so.1" + ], + "provides": [ + "cmd:Xephyr" + ] + }, + "lttng-ust-dev": { + "versions": { + "2.10.1-r0": 1543249618 + }, + "origin": "lttng-ust", + "dependencies": [ + "lttng-ust=2.10.1-r0", + "pc:liburcu-bp", + "pkgconfig" + ], + "provides": [ + "pc:lttng-ust=2.10.1" + ] + }, + "rsyslog-mmsnmptrapd": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "gtkmm-dev": { + "versions": { + "2.24.5-r0": 1543925600 + }, + "origin": "gtkmm", + "dependencies": [ + "atkmm-dev", + "gtk+2.0-dev", + "glibmm-dev", + "pangomm-dev", + "gtkmm=2.24.5-r0", + "pc:atkmm-1.6>=2.22.2", + "pc:giomm-2.4>=2.27.93", + "pc:gtk+-2.0>=2.24.0", + "pc:gtk+-unix-print-2.0", + "pc:pangomm-1.4>=2.27.1", + "pkgconfig" + ], + "provides": [ + "pc:gdkmm-2.4=2.24.5", + "pc:gtkmm-2.4=2.24.5" + ] + }, + "libexif-dev": { + "versions": { + "0.6.21-r3": 1545116758 + }, + "origin": "libexif", + "dependencies": [ + "libexif=0.6.21-r3", + "pkgconfig" + ], + "provides": [ + "pc:libexif=0.6.21" + ] + }, + "cabextract": { + "versions": { + "1.9-r0": 1545209003 + }, + "origin": "cabextract", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmspack.so.0" + ], + "provides": [ + "cmd:cabextract" + ] + }, + "mtools-doc": { + "versions": { + "4.0.23-r0": 1545858117 + }, + "origin": "mtools" + }, + "itstool-doc": { + "versions": { + "2.0.4-r3": 1545062595 + }, + "origin": "itstool" + }, + "lua5.3-libs": { + "versions": { + "5.3.5-r2": 1557162501 + }, + "origin": "lua5.3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblua-5.3.so.0=0.0.0" + ] + }, + "linux-vanilla-dev": { + "versions": { + "4.19.41-r0": 1557399963 + }, + "origin": "linux-vanilla", + "dependencies": [ + "perl", + "gmp-dev", + "elfutils-dev", + "bash", + "flex", + "bison", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libelf.so.1" + ] + }, + "ez-ipupdate": { + "versions": { + "3.0.10-r9": 1545116434 + }, + "origin": "ez-ipupdate", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ez-ipupdate" + ] + }, + "perl-xml-namespacesupport-doc": { + "versions": { + "1.12-r0": 1542893160 + }, + "origin": "perl-xml-namespacesupport" + }, + "gtk-xfce-engine": { + "versions": { + "3.2.0-r2": 1545292907 + }, + "origin": "gtk-xfce-engine" + }, + "py3-typing": { + "versions": { + "3.6.6-r0": 1543921910 + }, + "origin": "py-typing", + "dependencies": [ + "python3" + ] + }, + "neon": { + "versions": { + "0.30.2-r5": 1542883670 + }, + "origin": "neon", + "dependencies": [ + "ca-certificates", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libexpat.so.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:libneon.so.27=27.3.2" + ] + }, + "ppp-radius": { + "versions": { + "2.4.7-r6": 1543999020 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-asn1": { + "versions": { + "0.4.2-r0": 1542821565 + }, + "origin": "py-asn1", + "dependencies": [ + "python2" + ] + }, + "perl-ipc-run3": { + "versions": { + "0.048-r0": 1542973048 + }, + "origin": "perl-ipc-run3", + "dependencies": [ + "perl" + ] + }, + "dansguardian-doc": { + "versions": { + "2.12.0.3-r4": 1545207099 + }, + "origin": "dansguardian" + }, + "gpgmepp": { + "versions": { + "1.12.0-r3": 1543932321 + }, + "origin": "gpgme", + "dependencies": [ + "gnupg", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgpgme.so.11", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgpgmepp.so.6=6.8.0" + ] + }, + "rsnapshot-doc": { + "versions": { + "1.4.2-r0": 1544799298 + }, + "origin": "rsnapshot" + }, + "ruby": { + "versions": { + "2.5.5-r0": 1557164838 + }, + "origin": "ruby", + "dependencies": [ + "ca-certificates", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.5" + ], + "provides": [ + "cmd:erb", + "cmd:gem", + "cmd:ruby" + ] + }, + "perl-html-tree": { + "versions": { + "5.07-r0": 1542893578 + }, + "origin": "perl-html-tree", + "dependencies": [ + "perl-html-tagset", + "perl-html-parser" + ], + "provides": [ + "cmd:htmltree" + ] + }, + "orc-doc": { + "versions": { + "0.4.28-r0": 1542883449 + }, + "origin": "orc" + }, + "git-gui": { + "versions": { + "2.20.1-r0": 1545214200 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0", + "tcl", + "tk" + ] + }, + "the_silver_searcher-zsh-completion": { + "versions": { + "2.1.0-r2": 1543998779 + }, + "origin": "the_silver_searcher" + }, + "perl-file-rsync": { + "versions": { + "0.74-r1": 1545299967 + }, + "origin": "perl-file-rsync", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "xrdb": { + "versions": { + "1.1.1-r0": 1542883479 + }, + "origin": "xrdb", + "dependencies": [ + "mcpp", + "so:libX11.so.6", + "so:libXmuu.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xrdb" + ] + }, + "iasl": { + "versions": { + "20181213-r0": 1545995081 + }, + "origin": "acpica", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:iasl" + ] + }, + "lsof-doc": { + "versions": { + "4.91-r0": 1545235376 + }, + "origin": "lsof" + }, + "ruby-json": { + "versions": { + "2.5.5-r0": 1557164837 + }, + "origin": "ruby", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libruby.so.2.5" + ] + }, + "ruby-libs": { + "versions": { + "2.5.5-r0": 1557164838 + }, + "origin": "ruby", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgmp.so.10", + "so:libreadline.so.7", + "so:libssl.so.1.1", + "so:libyaml-0.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libruby.so.2.5=2.5.5" + ] + }, + "mariadb-server-utils": { + "versions": { + "10.3.13-r1": 1557431735 + }, + "origin": "mariadb", + "dependencies": [ + "perl", + "mariadb-common=10.3.13-r1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mysql_convert_table_format", + "cmd:mysql_setpermission", + "cmd:mysql_upgrade", + "cmd:mysqld_multi", + "cmd:mysqld_safe_helper", + "cmd:mysqlhotcopy", + "cmd:perror" + ] + }, + "dovecot-lmtpd": { + "versions": { + "2.3.6-r0": 1557134097 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libdovecot-lda.so.0", + "so:libdovecot-storage.so.0", + "so:libdovecot.so.0" + ] + }, + "apache-mod-auth-radius": { + "versions": { + "1.5.8-r3": 1545224139 + }, + "origin": "apache-mod-auth-radius", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1" + ] + }, + "linux-firmware-ti-connectivity": { + "versions": { + "20190322-r0": 1554980647 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "libatomic_ops": { + "versions": { + "7.6.4-r0": 1543226877 + }, + "origin": "libatomic_ops", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libatomic_ops.so.1=1.1.1", + "so:libatomic_ops_gpl.so.1=1.1.2" + ] + }, + "gstreamer-lang": { + "versions": { + "1.14.4-r0": 1543254159 + }, + "origin": "gstreamer" + }, + "xinit-doc": { + "versions": { + "1.4.0-r1": 1542973236 + }, + "origin": "xinit" + }, + "perl-mail-spamassassin": { + "versions": { + "3.4.1-r2": 1545061232, + "3.4.2-r0": 1545061982 + }, + "origin": "spamassassin", + "dependencies": [ + "perl", + "gnupg", + "perl-html-parser", + "perl-digest-sha1", + "perl-netaddr-ip", + "perl-net-dns", + "perl-mail-dkim", + "perl-mime-base64", + "perl-db_file", + "perl-time-hires", + "perl-libwww" + ], + "provides": [ + "cmd:sa-awl", + "cmd:sa-check_spamd", + "cmd:sa-compile", + "cmd:sa-learn", + "cmd:sa-update", + "cmd:spamassassin", + "cmd:spamc", + "cmd:spamd" + ] + }, + "ncftp": { + "versions": { + "3.2.6-r1": 1544000464 + }, + "origin": "ncftp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libncftp.so.3=3", + "cmd:ncftp", + "cmd:ncftpbatch", + "cmd:ncftpget", + "cmd:ncftpls", + "cmd:ncftpput", + "cmd:ncftpspooler" + ] + }, + "findutils": { + "versions": { + "4.6.0-r1": 1545073420 + }, + "origin": "findutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:find", + "cmd:locate", + "cmd:updatedb", + "cmd:xargs" + ] + }, + "nagios-plugins-ping": { + "versions": { + "2.2.1-r6": 1543933910 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "python3-wininst": { + "versions": { + "3.6.8-r2": 1554747639 + }, + "origin": "python3" + }, + "py-pylast": { + "versions": { + "2.4.0-r0": 1545301102 + }, + "origin": "py-pylast" + }, + "perl-canary-stability": { + "versions": { + "2012-r0": 1543223266 + }, + "origin": "perl-canary-stability" + }, + "nagios-plugins-sensors": { + "versions": { + "2.2.1-r6": 1543933911 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "grep", + "lm_sensors" + ] + }, + "gtk-engines-lang": { + "versions": { + "2.21.0-r2": 1545289335 + }, + "origin": "gtk-engines", + "dependencies": [ + "gtk-engines-clearlooks", + "gtk-engines-crux", + "gtk-engines-industrial", + "gtk-engines-mist", + "gtk-engines-redmond", + "gtk-engines-thinice" + ] + }, + "apache2-mod-wsgi": { + "versions": { + "4.5.24-r0": 1545213947 + }, + "origin": "apache2-mod-wsgi", + "dependencies": [ + "apache2", + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "apr-util-dbd_pgsql": { + "versions": { + "1.6.1-r5": 1545061009 + }, + "origin": "apr-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "font-bitstream-75dpi": { + "versions": { + "1.0.3-r0": 1545075927 + }, + "origin": "font-bitstream-75dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "zfs": { + "versions": { + "0.7.12-r1": 1552933680 + }, + "origin": "zfs", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libnvpair.so.1", + "so:libuuid.so.1", + "so:libuutil.so.1", + "so:libzfs.so.2", + "so:libzfs_core.so.1", + "so:libzpool.so.2" + ], + "provides": [ + "cmd:fsck.zfs", + "cmd:mount.zfs", + "cmd:raidz_test", + "cmd:zdb", + "cmd:zed", + "cmd:zfs", + "cmd:zgenhostid", + "cmd:zhack", + "cmd:zinject", + "cmd:zpios", + "cmd:zpool", + "cmd:zstreamdump", + "cmd:ztest" + ] + }, + "py-incremental": { + "versions": { + "17.5.0-r0": 1546513618 + }, + "origin": "py-incremental" + }, + "perl-package-anon": { + "versions": { + "0.05-r4": 1545116809 + }, + "origin": "perl-package-anon", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "mdadm-misc": { + "versions": { + "4.1-r0": 1545858163 + }, + "origin": "mdadm", + "dependencies": [ + "mdadm", + "bash" + ], + "provides": [ + "cmd:handle-mdadm-events", + "cmd:mdcheck" + ] + }, + "lua5.1-expat": { + "versions": { + "1.3.0-r2": 1544000275 + }, + "origin": "lua-expat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ] + }, + "libusb-compat": { + "versions": { + "0.1.5-r4": 1543921873 + }, + "origin": "libusb-compat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libusb-0.1.so.4=4.4.4" + ] + }, + "libxdg-basedir-dev": { + "versions": { + "1.2.0-r0": 1545116744 + }, + "origin": "libxdg-basedir", + "dependencies": [ + "libxdg-basedir=1.2.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libxdg-basedir=1.2.0" + ] + }, + "opus-dev": { + "versions": { + "1.3-r0": 1545069004 + }, + "origin": "opus", + "dependencies": [ + "opus=1.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:opus=1.3" + ] + }, + "udisks-lang": { + "versions": { + "1.0.5-r3": 1545069134 + }, + "origin": "udisks" + }, + "clutter-doc": { + "versions": { + "1.26.2-r2": 1545293147 + }, + "origin": "clutter" + }, + "tcl-tls": { + "versions": { + "1.7.16-r0": 1545165056 + }, + "origin": "tcl-tls", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "ulogd-pgsql": { + "versions": { + "2.0.7-r0": 1545300896 + }, + "origin": "ulogd", + "dependencies": [ + "ulogd", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "dahdi-tools-doc": { + "versions": { + "2.11.1-r0": 1543932614 + }, + "origin": "dahdi-tools" + }, + "py-pyflakes": { + "versions": { + "1.6.0-r3": 1545073867 + }, + "origin": "py-pyflakes", + "dependencies": [ + "py3-pyflakes=1.6.0-r3" + ] + }, + "email": { + "versions": { + "3.1.4-r7": 1543925854 + }, + "origin": "email", + "dependencies": [ + "openssl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:email" + ] + }, + "libnetfilter_log": { + "versions": { + "1.0.1-r2": 1543924455 + }, + "origin": "libnetfilter_log", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnfnetlink.so.0" + ], + "provides": [ + "so:libnetfilter_log.so.1=1.1.0", + "so:libnetfilter_log_libipulog.so.1=1.0.0" + ] + }, + "perl-xml-rss-doc": { + "versions": { + "1.60-r0": 1545301035 + }, + "origin": "perl-xml-rss" + }, + "smokeping-doc": { + "versions": { + "2.7.3-r3": 1545734482 + }, + "origin": "smokeping" + }, + "hexchat-lang": { + "versions": { + "2.14.2-r0": 1545746058 + }, + "origin": "hexchat" + }, + "scrot": { + "versions": { + "0.8.18-r0": 1545299550 + }, + "origin": "scrot", + "dependencies": [ + "so:libImlib2.so.1", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libgiblib.so.1" + ], + "provides": [ + "cmd:scrot" + ] + }, + "alpine-baselayout": { + "versions": { + "3.1.0-r3": 1548276389 + }, + "origin": "alpine-baselayout", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mkmntdirs" + ] + }, + "py-markupsafe": { + "versions": { + "1.1.0-r0": 1544791904 + }, + "origin": "py-markupsafe" + }, + "perl-git": { + "versions": { + "2.20.1-r0": 1545214194 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0", + "perl-error" + ] + }, + "gtkspell-lang": { + "versions": { + "2.0.16-r7": 1545215216 + }, + "origin": "gtkspell" + }, + "freeradius-pam": { + "versions": { + "3.0.17-r5": 1556201989 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.17-r5", + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0" + ], + "provides": [ + "freeradius3-pam=3.0.17-r5", + "so:rlm_pam.so=0" + ] + }, + "python2-dbg": { + "versions": { + "2.7.16-r1": 1557171398 + }, + "origin": "python2" + }, + "gst-plugins-base0.10-lang": { + "versions": { + "0.10.36-r4": 1543928811 + }, + "origin": "gst-plugins-base0.10" + }, + "openrc-dev": { + "versions": { + "0.39.2-r3": 1548592317 + }, + "origin": "openrc", + "dependencies": [ + "openrc=0.39.2-r3", + "pkgconfig" + ], + "provides": [ + "pc:einfo=0.39.2", + "pc:openrc=0.39.2" + ] + }, + "apk-tools": { + "versions": { + "2.10.3-r1": 1547112494 + }, + "origin": "apk-tools", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:apk" + ] + }, + "flashcache-utils": { + "versions": { + "3.1.3-r0": 1545209189 + }, + "origin": "flashcache-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:flashcache_create", + "cmd:flashcache_destroy", + "cmd:flashcache_load", + "cmd:flashcache_setioctl", + "cmd:get_agsize" + ] + }, + "pinentry-doc": { + "versions": { + "1.1.0-r0": 1543932175 + }, + "origin": "pinentry" + }, + "iwlwifi-6000-ucode": { + "versions": { + "9.221.4.1-r0": 1545256922 + }, + "origin": "iwlwifi-6000-ucode" + }, + "kamailio-sipdump": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1" + ] + }, + "cegui06": { + "versions": { + "0.6.2b-r15": 1543935898 + }, + "origin": "cegui06", + "dependencies": [ + "so:libGL.so.1", + "so:libGLEW.so.2.1", + "so:libGLU.so.1", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libfreetype.so.6", + "so:libgcc_s.so.1", + "so:liblua.so.5", + "so:libpcre.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libCEGUIBase-0.6.2.so=0", + "so:libCEGUIExpatParser-0.6.2.so=0", + "so:libCEGUIFalagardWRBase-0.6.2.so=0", + "so:libCEGUILuaScriptModule-0.6.2.so=0", + "so:libCEGUIOpenGLRenderer-0.6.2.so=0", + "so:libCEGUITGAImageCodec-0.6.2.so=0", + "so:libCEGUITinyXMLParser-0.6.2.so=0", + "so:libCEGUItoluapp-0.6.2.so=0" + ] + }, + "usbredir-server": { + "versions": { + "0.7.1-r0": 1543223286 + }, + "origin": "usbredir", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirhost.so.1" + ], + "provides": [ + "cmd:usbredirserver" + ] + }, + "beep-doc": { + "versions": { + "1.3-r2": 1545214043 + }, + "origin": "beep" + }, + "postfix-pcre": { + "versions": { + "3.3.2-r0": 1547130363 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ], + "provides": [ + "so:postfix-pcre.so=0" + ] + }, + "xgnokii": { + "versions": { + "0.6.31-r8": 1545300479 + }, + "origin": "gnokii", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgnokii.so.7", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:xgnokii" + ] + }, + "tmux-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308087 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "snort-doc": { + "versions": { + "2.9.12-r0": 1545301013 + }, + "origin": "snort" + }, + "hdparm": { + "versions": { + "9.58-r0": 1545745823 + }, + "origin": "hdparm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:hdparm", + "cmd:idectl", + "cmd:ultrabayd" + ] + }, + "perl-net-cidr-lite-doc": { + "versions": { + "0.21-r4": 1543924461 + }, + "origin": "perl-net-cidr-lite" + }, + "unionfs-fuse-doc": { + "versions": { + "1.0-r0": 1545163917 + }, + "origin": "unionfs-fuse" + }, + "gnupg1": { + "versions": { + "1.4.23-r0": 1545302139 + }, + "origin": "gnupg1", + "dependencies": [ + "pinentry", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libz.so.1" + ], + "provides": [ + "gnupg=1.4.23-r0", + "cmd:gpg", + "cmd:gpg-zip", + "cmd:gpgsplit", + "cmd:gpgv" + ] + }, + "git-bash-completion": { + "versions": { + "2.20.1-r0": 1545214194 + }, + "origin": "git" + }, + "lz4-libs": { + "versions": { + "1.8.3-r2": 1545154434 + }, + "origin": "lz4", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblz4.so.1=1.8.3" + ] + }, + "perl-io-socket-inet6-doc": { + "versions": { + "2.72-r0": 1543932732 + }, + "origin": "perl-io-socket-inet6" + }, + "openjpeg-dev": { + "versions": { + "2.3.0-r3": 1552584664 + }, + "origin": "openjpeg", + "dependencies": [ + "openjpeg=2.3.0-r3", + "pkgconfig" + ], + "provides": [ + "pc:libopenjp2=2.3.0" + ] + }, + "uwsgi-redislog": { + "versions": { + "2.0.17.1-r0": 1545062205 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "thin-provisioning-tools-doc": { + "versions": { + "0.7.1-r1": 1545208656 + }, + "origin": "thin-provisioning-tools" + }, + "mcabber": { + "versions": { + "1.1.0-r2": 1545214061 + }, + "origin": "mcabber", + "dependencies": [ + "beep", + "so:libc.musl-x86_64.so.1", + "so:libenchant.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgpgme.so.11", + "so:libidn.so.12", + "so:libloudmouth-1.so.0", + "so:libncursesw.so.6", + "so:libotr.so.2", + "so:libpanelw.so.6" + ], + "provides": [ + "cmd:mcabber" + ] + }, + "nasm-doc": { + "versions": { + "2.13.03-r0": 1542824085 + }, + "origin": "nasm" + }, + "lxc-openrc": { + "versions": { + "3.1.0-r1": 1549966161 + }, + "origin": "lxc" + }, + "perl-html-format-doc": { + "versions": { + "2.12-r0": 1545075957 + }, + "origin": "perl-html-format" + }, + "perl-test-needs-doc": { + "versions": { + "0.002005-r1": 1542821778 + }, + "origin": "perl-test-needs" + }, + "gsm-doc": { + "versions": { + "1.0.18-r0": 1543927819 + }, + "origin": "gsm" + }, + "kmod-openrc": { + "versions": { + "24-r1": 1542845354 + }, + "origin": "kmod" + }, + "perl-http-cookies": { + "versions": { + "6.04-r0": 1542821810 + }, + "origin": "perl-http-cookies", + "dependencies": [ + "perl", + "perl-http-date", + "perl-http-message" + ] + }, + "perl-xml-simple": { + "versions": { + "2.25-r0": 1545062591 + }, + "origin": "perl-xml-simple", + "dependencies": [ + "perl-xml-parser", + "perl" + ] + }, + "wxgtk2.8-dev": { + "versions": { + "2.8.12.1-r4": 1545075285 + }, + "origin": "wxgtk2.8", + "dependencies": [ + "gtk+2.0-dev", + "zlib-dev", + "tiff-dev", + "libpng-dev", + "libjpeg-turbo-dev", + "expat-dev", + "libsm-dev", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libwx_baseu-2.8.so.0", + "so:libwx_baseu_xml-2.8.so.0", + "wxgtk2.8-base=2.8.12.1-r4", + "wxgtk2.8-media=2.8.12.1-r4", + "wxgtk2.8=2.8.12.1-r4" + ], + "provides": [ + "cmd:wxrc", + "cmd:wxrc-2.8" + ] + }, + "lua5.1-lyaml": { + "versions": { + "6.2-r3": 1545076419 + }, + "origin": "lua-lyaml", + "dependencies": [ + "lua5.1", + "lua5.1-stdlib-normalize", + "so:libc.musl-x86_64.so.1", + "so:libyaml-0.so.2" + ] + }, + "perl-module-pluggable-doc": { + "versions": { + "5.2-r0": 1542973005 + }, + "origin": "perl-module-pluggable" + }, + "perl-date-calc": { + "versions": { + "6.4-r1": 1545060709 + }, + "origin": "perl-date-calc", + "dependencies": [ + "perl" + ] + }, + "command-not-found": { + "versions": { + "0.3-r0": 1545062749 + }, + "origin": "command-not-found" + }, + "perl-term-table": { + "versions": { + "0.012-r0": 1542972990 + }, + "origin": "perl-term-table", + "dependencies": [ + "perl-importer" + ] + }, + "websocket++": { + "versions": { + "0.8.1-r0": 1543222726 + }, + "origin": "websocket++" + }, + "libasr": { + "versions": { + "1.0.2-r9": 1543933802 + }, + "origin": "libasr", + "dependencies": [ + "so:libbsd.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libasr.so.0=0.0.2" + ] + }, + "lua-struct": { + "versions": { + "0.2-r2": 1545076457 + }, + "origin": "lua-struct" + }, + "uwsgi": { + "versions": { + "2.0.17.1-r0": 1545062215 + }, + "origin": "uwsgi", + "dependencies": [ + "mailcap", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.1.1", + "so:libjansson.so.4", + "so:libpcre.so.1", + "so:libssl.so.1.1", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:uwsgi" + ] + }, + "unzip": { + "versions": { + "6.0-r4": 1542301513 + }, + "origin": "unzip", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:funzip", + "cmd:unzip", + "cmd:unzipsfx", + "cmd:zipgrep", + "cmd:zipinfo" + ] + }, + "talloc-doc": { + "versions": { + "2.1.14-r0": 1543220639 + }, + "origin": "talloc" + }, + "py-asn1-modules": { + "versions": { + "0.2.2-r0": 1543928878 + }, + "origin": "py-asn1-modules" + }, + "fish-tools": { + "versions": { + "2.7.1-r0": 1545302079 + }, + "origin": "fish", + "dependencies": [ + "fish", + "python" + ] + }, + "hostapd-openrc": { + "versions": { + "2.7-r0": 1547822982 + }, + "origin": "hostapd" + }, + "perl-cgi": { + "versions": { + "4.40-r0": 1543242201 + }, + "origin": "perl-cgi", + "dependencies": [ + "perl-html-parser" + ] + }, + "perl-net-dns-resolver-mock-doc": { + "versions": { + "1.20170814-r0": 1545061182 + }, + "origin": "perl-net-dns-resolver-mock" + }, + "py-typing": { + "versions": { + "3.6.6-r0": 1543921910 + }, + "origin": "py-typing" + }, + "polkit-dev": { + "versions": { + "0.105-r9": 1547130960 + }, + "origin": "polkit", + "dependencies": [ + "eggdbus-dev", + "dbus-glib-dev", + "pc:gio-2.0>=2.18", + "pc:glib-2.0>=2.18", + "pkgconfig", + "polkit=0.105-r9" + ], + "provides": [ + "pc:polkit-agent-1=0.105", + "pc:polkit-backend-1=0.105", + "pc:polkit-gobject-1=0.105" + ] + }, + "lvm2-dev": { + "versions": { + "2.02.182-r0": 1543928933 + }, + "origin": "lvm2", + "dependencies": [ + "linux-headers", + "device-mapper-event-libs=2.02.182-r0", + "device-mapper-libs=2.02.182-r0", + "lvm2-libs=2.02.182-r0", + "pc:blkid", + "pkgconfig" + ], + "provides": [ + "pc:devmapper-event=1.02.152", + "pc:devmapper=1.02.152", + "pc:lvm2app=2.2" + ] + }, + "postgresql-doc": { + "versions": { + "11.2-r0": 1554274175 + }, + "origin": "postgresql" + }, + "openvpn-ad-check": { + "versions": { + "1.1-r2": 1543223258 + }, + "origin": "openvpn-ad-check", + "dependencies": [ + "openvpn", + "lua-ldap" + ] + }, + "net-snmp-gui": { + "versions": { + "5.8-r0": 1543923351 + }, + "origin": "net-snmp", + "dependencies": [ + "perl-net-snmp" + ], + "provides": [ + "cmd:tkmib" + ] + }, + "cyrus-sasl-digestmd5": { + "versions": { + "2.1.27-r1": 1550353528 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ] + }, + "nss-static": { + "versions": { + "3.41-r0": 1545307910 + }, + "origin": "nss" + }, + "libsexy": { + "versions": { + "0.1.11-r8": 1544001180 + }, + "origin": "libsexy", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "so:libsexy.so.2=2.0.4" + ] + }, + "lucene++-dev": { + "versions": { + "3.0.7-r8": 1545256825 + }, + "origin": "lucene++", + "dependencies": [ + "boost-dev", + "lucene++=3.0.7-r8", + "pkgconfig" + ], + "provides": [ + "pc:liblucene++-contrib=3.0.7", + "pc:liblucene++=3.0.7" + ] + }, + "libressl-dbg": { + "versions": { + "2.7.5-r0": 1551116832 + }, + "origin": "libressl" + }, + "py-egenix-mx-base-dev": { + "versions": { + "3.2.9-r0": 1542821603 + }, + "origin": "py-egenix-mx-base" + }, + "devicemaster-linux": { + "versions": { + "7.15-r0": 1543999030 + }, + "origin": "devicemaster-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:nslinkadmin", + "cmd:nslinkd", + "cmd:nslinkrelease", + "cmd:nslinktool" + ] + }, + "perl-extutils-installpaths-doc": { + "versions": { + "0.012-r0": 1542924654 + }, + "origin": "perl-extutils-installpaths" + }, + "perl-cpan-meta-check": { + "versions": { + "0.014-r0": 1542845603 + }, + "origin": "perl-cpan-meta-check", + "dependencies": [ + "perl-module-metadata>=1.000023" + ] + }, + "perl-carp": { + "versions": { + "1.38-r0": 1544001407 + }, + "origin": "perl-carp" + }, + "boost-prg_exec_monitor": { + "versions": { + "1.67.0-r2": 1542823632 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_prg_exec_monitor-mt.so.1.67.0=1.67.0", + "so:libboost_prg_exec_monitor.so.1.67.0=1.67.0" + ] + }, + "lua5.3-ossl": { + "versions": { + "20180708-r2": 1545076448 + }, + "origin": "lua-ossl", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "py-requests": { + "versions": { + "2.19.1-r0": 1542825031 + }, + "origin": "py-requests", + "dependencies": [ + "py-chardet", + "py-idna", + "py-certifi", + "py-urllib3" + ] + }, + "py3-jinja2": { + "versions": { + "2.10-r2": 1542824937 + }, + "origin": "py-jinja2", + "dependencies": [ + "py3-markupsafe", + "python3" + ] + }, + "snort-dev": { + "versions": { + "2.9.12-r0": 1545301013 + }, + "origin": "snort", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:snort=2.9.12", + "pc:snort_output=2.9.12", + "pc:snort_preproc=2.9.12" + ] + }, + "perl-test-warn": { + "versions": { + "0.36-r0": 1543242189 + }, + "origin": "perl-test-warn", + "dependencies": [ + "perl-sub-uplevel" + ] + }, + "apr-util-dbd_mysql": { + "versions": { + "1.6.1-r5": 1545061009 + }, + "origin": "apr-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "lua5.3-rex-pcre": { + "versions": { + "2.9.0-r0": 1545209205 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ] + }, + "perl-crypt-eksblowfish-doc": { + "versions": { + "0.009-r4": 1545213645 + }, + "origin": "perl-crypt-eksblowfish" + }, + "harfbuzz": { + "versions": { + "2.2.0-r0": 1545249743 + }, + "origin": "harfbuzz", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgraphite2.so.3" + ], + "provides": [ + "so:libharfbuzz-gobject.so.0=0.20200.0", + "so:libharfbuzz-subset.so.0=0.20200.0", + "so:libharfbuzz.so.0=0.20200.0" + ] + }, + "encfs-lang": { + "versions": { + "1.9.5-r3": 1545061083 + }, + "origin": "encfs" + }, + "pgtcl": { + "versions": { + "2.1.0-r0": 1545301848 + }, + "origin": "pgtcl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "libdvdnav-dev": { + "versions": { + "5.0.3-r1": 1543999001 + }, + "origin": "libdvdnav", + "dependencies": [ + "libdvdread-dev>=5.0.3", + "libdvdnav=5.0.3-r1", + "pc:dvdread>=4.1.2", + "pkgconfig" + ], + "provides": [ + "pc:dvdnav=5.0.3" + ] + }, + "libsexy-doc": { + "versions": { + "0.1.11-r8": 1544001179 + }, + "origin": "libsexy" + }, + "kamailio-xmpp": { + "versions": { + "5.2.0-r1": 1546423170 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1" + ] + }, + "swig-doc": { + "versions": { + "3.0.12-r4": 1543228065 + }, + "origin": "swig" + }, + "perl-io-multiplex": { + "versions": { + "1.16-r1": 1545163454 + }, + "origin": "perl-io-multiplex", + "dependencies": [ + "perl" + ] + }, + "perl-cache-cache-doc": { + "versions": { + "1.08-r0": 1543223056 + }, + "origin": "perl-cache-cache" + }, + "libidl-dev": { + "versions": { + "0.8.14-r2": 1543223345 + }, + "origin": "libidl", + "dependencies": [ + "libidl=0.8.14-r2", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:libIDL-2.0=0.8.14" + ] + }, + "rrdcollect": { + "versions": { + "0.2.10-r1": 1545207035 + }, + "origin": "rrdcollect", + "dependencies": [ + "rrdtool", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:rrdcollect" + ] + }, + "perl-params-validate-doc": { + "versions": { + "1.29-r0": 1543238893 + }, + "origin": "perl-params-validate" + }, + "libdaemon-dev": { + "versions": { + "0.14-r2": 1543925256 + }, + "origin": "libdaemon", + "dependencies": [ + "libdaemon=0.14-r2", + "pkgconfig" + ], + "provides": [ + "pc:libdaemon=0.14" + ] + }, + "perl-module-install": { + "versions": { + "1.19-r0": 1542893357 + }, + "origin": "perl-module-install" + }, + "ctags-doc": { + "versions": { + "5.8-r5": 1545301883 + }, + "origin": "ctags" + }, + "btrfs-progs-extra": { + "versions": { + "4.19.1-r0": 1545665053 + }, + "origin": "btrfs-progs", + "dependencies": [ + "btrfs-progs", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libext2fs.so.2", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:btrfs-convert", + "cmd:btrfs-find-root", + "cmd:btrfs-image", + "cmd:btrfs-map-logical", + "cmd:btrfs-select-super", + "cmd:btrfstune" + ] + }, + "squid-lang-sk": { + "versions": { + "4.4-r1": 1545216329 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "libnet-doc": { + "versions": { + "1.1.6-r2": 1545060612 + }, + "origin": "libnet" + }, + "mqtt-exec": { + "versions": { + "0.4-r4": 1545162970 + }, + "origin": "mqtt-exec", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ], + "provides": [ + "cmd:mqtt-exec" + ] + }, + "rsyslog-hiredis": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.14" + ], + "provides": [ + "rsyslog-omhiredis=8.40.0-r3" + ] + }, + "collectd-dns": { + "versions": { + "5.8.0-r3": 1546422678 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ] + }, + "perl-lwp-useragent-determined-doc": { + "versions": { + "1.07-r0": 1545062795 + }, + "origin": "perl-lwp-useragent-determined" + }, + "mosh-server": { + "versions": { + "1.3.2-r7": 1543932001 + }, + "origin": "mosh", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libprotobuf.so.17", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:mosh-server" + ] + }, + "lua-stdlib-normalize": { + "versions": { + "2.0.2-r0": 1542883720 + }, + "origin": "lua-stdlib-normalize", + "dependencies": [ + "lua", + "lua-stdlib-debug" + ], + "provides": [ + "lua5.1-stdlib-normalize=2.0.2-r0", + "lua5.2-stdlib-normalize=2.0.2-r0", + "lua5.3-stdlib-normalize=2.0.2-r0" + ] + }, + "spl": { + "versions": { + "0.7.8-r0": 1545117988 + }, + "origin": "spl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:splat", + "cmd:splslab.py" + ] + }, + "py-fuse": { + "versions": { + "0.2.1-r1": 1543935349 + }, + "origin": "py-fuse", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libfuse.so.2", + "so:libpython2.7.so.1.0" + ] + }, + "json-c0.12-dev": { + "versions": { + "0.12.1-r2": 1545301158 + }, + "origin": "json-c0.12", + "dependencies": [ + "json-c0.12=0.12.1-r2", + "pkgconfig" + ], + "provides": [ + "pc:json-c=0.12.1" + ] + }, + "py3-mccabe": { + "versions": { + "0.6.1-r1": 1545060943 + }, + "origin": "py-mccabe", + "dependencies": [ + "python3" + ] + }, + "linux-firmware-sxg": { + "versions": { + "20190322-r0": 1554980647 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "postfix-pgsql": { + "versions": { + "3.3.2-r0": 1547130363 + }, + "origin": "postfix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ], + "provides": [ + "so:postfix-pgsql.so=0" + ] + }, + "perl-html-tagset": { + "versions": { + "3.20-r1": 1542821829 + }, + "origin": "perl-html-tagset" + }, + "py3-crypto": { + "versions": { + "2.6.1-r2": 1543223466 + }, + "origin": "py-crypto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libpython3.6m.so.1.0" + ] + }, + "debian-archive-keyring": { + "versions": { + "2018.1-r0": 1543999031 + }, + "origin": "debian-archive-keyring", + "dependencies": [ + "gnupg" + ] + }, + "galculator-lang": { + "versions": { + "2.1.4-r0": 1545076847 + }, + "origin": "galculator" + }, + "pmacct": { + "versions": { + "1.7.0-r0": 1545116555 + }, + "origin": "pmacct", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjansson.so.4", + "so:libmariadb.so.3", + "so:libpcap.so.1", + "so:libpq.so.5", + "so:libsqlite3.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:nfacctd", + "cmd:pmacct", + "cmd:pmacctd", + "cmd:pmbgpd", + "cmd:pmbmpd", + "cmd:pmtelemetryd", + "cmd:sfacctd" + ] + }, + "libnftnl": { + "versions": { + "1.1.1-r0": 1542893203 + }, + "origin": "libnftnl" + }, + "py2-musicbrainzngs": { + "versions": { + "0.6-r2": 1543240635 + }, + "origin": "py-musicbrainzngs", + "dependencies": [ + "python2" + ] + }, + "py2-gobject3": { + "versions": { + "3.28.2-r0": 1543925241 + }, + "origin": "py-gobject3", + "dependencies": [ + "py2-cairo", + "so:libc.musl-x86_64.so.1", + "so:libcairo-gobject.so.2", + "so:libcairo.so.2", + "so:libffi.so.6", + "so:libgirepository-1.0.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ] + }, + "libiec61883-utils": { + "versions": { + "1.2.0-r2": 1543999349 + }, + "origin": "libiec61883", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libiec61883.so.0", + "so:libraw1394.so.11" + ], + "provides": [ + "cmd:plugctl", + "cmd:plugreport" + ] + }, + "perl-datetime-doc": { + "versions": { + "1.44-r0": 1543239027 + }, + "origin": "perl-datetime" + }, + "wayland-dev": { + "versions": { + "1.16.0-r0": 1544001083 + }, + "origin": "wayland", + "dependencies": [ + "libffi-dev", + "expat-dev", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libxml2.so.2", + "wayland-libs-client=1.16.0-r0", + "wayland-libs-cursor=1.16.0-r0", + "wayland-libs-egl=1.16.0-r0", + "wayland-libs-server=1.16.0-r0" + ], + "provides": [ + "pc:wayland-client=1.16.0", + "pc:wayland-cursor=1.16.0", + "pc:wayland-egl-backend=3", + "pc:wayland-egl=18.1.0", + "pc:wayland-scanner=1.16.0", + "pc:wayland-server=1.16.0", + "cmd:wayland-scanner" + ] + }, + "acf-pingu": { + "versions": { + "0.4.0-r2": 1545163694 + }, + "origin": "acf-pingu", + "dependencies": [ + "acf-core", + "pingu" + ] + }, + "py2-vte": { + "versions": { + "0.28.2-r13": 1543927670 + }, + "origin": "vte", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpango-1.0.so.0", + "so:libvte.so.9" + ] + }, + "uwsgi-pam": { + "versions": { + "2.0.17.1-r0": 1545062204 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0" + ] + }, + "haserl-doc": { + "versions": { + "0.9.35-r1": 1542883797 + }, + "origin": "haserl" + }, + "clutter": { + "versions": { + "1.26.2-r2": 1545293148 + }, + "origin": "clutter", + "dependencies": [ + "so:libX11.so.6", + "so:libXcomposite.so.1", + "so:libXdamage.so.1", + "so:libXi.so.6", + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo-gobject.so.2", + "so:libcairo.so.2", + "so:libcogl-pango.so.20", + "so:libcogl-path.so.20", + "so:libcogl.so.20", + "so:libfontconfig.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjson-glib-1.0.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpangoft2-1.0.so.0" + ], + "provides": [ + "so:libclutter-1.0.so.0=0.2600.2" + ] + }, + "qemu-system-x86_64": { + "versions": { + "3.1.0-r3": 1551107306 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-x86_64" + ] + }, + "py2-mock": { + "versions": { + "2.0.0-r3": 1543925735 + }, + "origin": "py-mock", + "dependencies": [ + "py2-pbr", + "py2-six", + "py2-funcsigs", + "python2" + ] + }, + "freetype": { + "versions": { + "2.9.1-r2": 1542822032 + }, + "origin": "freetype", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libpng16.so.16", + "so:libz.so.1" + ], + "provides": [ + "so:libfreetype.so.6=6.16.1" + ] + }, + "android-tools-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "xf86-video-xgixp-doc": { + "versions": { + "1.8.1-r11": 1545075375 + }, + "origin": "xf86-video-xgixp" + }, + "perl-text-soundex": { + "versions": { + "3.05-r0": 1543081699 + }, + "origin": "perl-text-soundex", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py-lockfile": { + "versions": { + "0.12.2-r0": 1544799214 + }, + "origin": "py-lockfile" + }, + "shorewall-core-doc": { + "versions": { + "5.2.2-r0": 1548095414 + }, + "origin": "shorewall-core" + }, + "pacman-dev": { + "versions": { + "5.0.2-r4": 1543932339 + }, + "origin": "pacman", + "dependencies": [ + "libarchive-dev", + "curl-dev", + "openssl-dev", + "gpgme-dev", + "gettext-dev", + "pacman=5.0.2-r4", + "pkgconfig" + ], + "provides": [ + "pc:libalpm=10.0.2" + ] + }, + "py3-scandir": { + "versions": { + "1.9.0-r1": 1542824907 + }, + "origin": "py-scandir", + "dependencies": [ + "python3", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "pm-utils-dev": { + "versions": { + "1.4.1-r1": 1543935620 + }, + "origin": "pm-utils", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:pm-utils=1.4.1" + ] + }, + "syslog-ng-dev": { + "versions": { + "3.19.1-r0": 1548543150 + }, + "origin": "syslog-ng", + "dependencies": [ + "pc:glib-2.0", + "pkgconfig", + "syslog-ng=3.19.1-r0" + ], + "provides": [ + "pc:syslog-ng-native-connector=0.1.0", + "pc:syslog-ng=3.19.1" + ] + }, + "sc-doc": { + "versions": { + "7.16-r5": 1545208853 + }, + "origin": "sc" + }, + "perl-config-inifiles": { + "versions": { + "3.000001-r0": 1547708572 + }, + "origin": "perl-config-inifiles", + "dependencies": [ + "perl-list-moreutils" + ] + }, + "perl-encode-dev": { + "versions": { + "2.98-r0": 1543935163 + }, + "origin": "perl-encode", + "dependencies": [ + "perl-encode-utils" + ], + "provides": [ + "cmd:enc2xs" + ] + }, + "cdparanoia-dev": { + "versions": { + "10.2-r7": 1543254198 + }, + "origin": "cdparanoia", + "dependencies": [ + "cdparanoia-libs=10.2-r7" + ] + }, + "rdiff-backup": { + "versions": { + "1.2.8-r2": 1545213661 + }, + "origin": "rdiff-backup", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:librsync.so.2" + ], + "provides": [ + "cmd:rdiff-backup", + "cmd:rdiff-backup-statistics" + ] + }, + "lua5.1-posix": { + "versions": { + "33.4.0-r1": 1546010825 + }, + "origin": "lua-posix", + "dependencies": [ + "lua5.1-bit32<26", + "so:libc.musl-x86_64.so.1" + ] + }, + "json-c-static": { + "versions": { + "0.13.1-r0": 1543923376 + }, + "origin": "json-c" + }, + "lua5.3-sql-odbc": { + "versions": { + "2.3.5-r2": 1543924406 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ] + }, + "libcanberra-gtk2": { + "versions": { + "0.30-r2": 1545118058 + }, + "origin": "libcanberra", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcanberra.so.0", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ], + "provides": [ + "so:libcanberra-gtk.so.0=0.1.9" + ] + }, + "perl-test-cpan-meta-doc": { + "versions": { + "0.25-r0": 1545075946 + }, + "origin": "perl-test-cpan-meta" + }, + "aspell-de": { + "versions": { + "20030222-r1": 1545256936 + }, + "origin": "aspell-de" + }, + "perl-mail-dkim": { + "versions": { + "0.54-r0": 1545061192 + }, + "origin": "perl-mail-dkim", + "dependencies": [ + "perl", + "perl-net-dns", + "perl-net-ip", + "perl-mailtools", + "perl-crypt-openssl-rsa", + "perl-yaml-libyaml", + "perl-net-dns-resolver-mock" + ] + }, + "perl-file-which": { + "versions": { + "1.22-r0": 1545214294 + }, + "origin": "perl-file-which" + }, + "py2-virtualenv": { + "versions": { + "15.1.0-r0": 1545066960 + }, + "origin": "py-virtualenv", + "dependencies": [ + "py2-pip", + "python2" + ], + "provides": [ + "cmd:virtualenv" + ] + }, + "py-hiredis": { + "versions": { + "0.1.4-r2": 1546423252 + }, + "origin": "py-hiredis", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "librevenge": { + "versions": { + "0.0.4-r2": 1543932070 + }, + "origin": "librevenge", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:librevenge-0.0.so.0=0.0.4", + "so:librevenge-generators-0.0.so.0=0.0.4", + "so:librevenge-stream-0.0.so.0=0.0.4" + ] + }, + "lxc-bridge": { + "versions": { + "3.1.0-r1": 1549966161 + }, + "origin": "lxc", + "dependencies": [ + "dnsmasq" + ] + }, + "uwsgi-legion_cache_fetch": { + "versions": { + "2.0.17.1-r0": 1545062201 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-datetime-format-w3cdtf": { + "versions": { + "0.07-r0": 1543239034 + }, + "origin": "perl-datetime-format-w3cdtf", + "dependencies": [ + "perl", + "perl-datetime" + ] + }, + "s6-dev": { + "versions": { + "2.7.2.0-r0": 1545062679 + }, + "origin": "s6", + "dependencies": [ + "s6=2.7.2.0-r0" + ] + }, + "ruby-bundler-doc": { + "versions": { + "1.17.1-r0": 1545301042 + }, + "origin": "ruby-bundler" + }, + "setup-box-doc": { + "versions": { + "1.0.1-r0": 1544000552 + }, + "origin": "setup-box", + "dependencies": [ + "jq" + ] + }, + "perl-http-daemon": { + "versions": { + "6.01-r1": 1542821824 + }, + "origin": "perl-http-daemon", + "dependencies": [ + "perl", + "perl-http-date", + "perl-http-message" + ] + }, + "lua5.2-dbi-sqlite3": { + "versions": { + "0.6-r3": 1545214223 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "kamailio-kazoo": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libjson-c.so.4", + "so:librabbitmq.so.4", + "so:libsrdb1.so.1", + "so:libuuid.so.1" + ] + }, + "geeqie": { + "versions": { + "1.4-r0": 1545292985 + }, + "origin": "geeqie", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libexiv2.so.26", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:liblua.so.5", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libstdc++.so.6", + "so:libtiff.so.5" + ], + "provides": [ + "cmd:geeqie" + ] + }, + "openssh-server": { + "versions": { + "7.9_p1-r5": 1556034597 + }, + "origin": "openssh", + "dependencies": [ + "openssh-keygen", + "openssh-server-common", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:sshd" + ] + }, + "kamailio": { + "versions": { + "5.2.0-r1": 1546423173 + }, + "origin": "kamailio", + "dependencies": [ + "gawk", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:libpcre.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libkamailio_ims.so.0=0.1", + "so:libprint.so.1=1.2", + "so:libsrdb1.so.1=1.0", + "so:libsrdb2.so.1=1.0", + "so:libsrutils.so.1=1.0", + "so:libtrie.so.1=1.0", + "cmd:kamailio", + "cmd:kambdb_recover", + "cmd:kamcmd", + "cmd:kamctl", + "cmd:kamdbctl" + ] + }, + "liblastfm-dev": { + "versions": { + "1.0.9-r1": 1545207823 + }, + "origin": "liblastfm", + "dependencies": [ + "qt-dev", + "libsamplerate-dev", + "fftw-dev", + "liblastfm=1.0.9-r1" + ] + }, + "linux-firmware-atusb": { + "versions": { + "20190322-r0": 1554980654 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "nagios-plugins-ssh": { + "versions": { + "2.2.1-r6": 1543933912 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "qt-mysql": { + "versions": { + "4.8.7-r11": 1545075088 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtSql.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libmariadb.so.3", + "so:libstdc++.so.6" + ] + }, + "mercurial-vim": { + "versions": { + "4.9.1-r0": 1557160461 + }, + "origin": "mercurial" + }, + "openldap-overlay-accesslog": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "zstd-dev": { + "versions": { + "1.3.8-r0": 1546966444 + }, + "origin": "zstd", + "dependencies": [ + "pkgconfig", + "zstd-libs=1.3.8-r0" + ], + "provides": [ + "pc:libzstd=1.3.8" + ] + }, + "aconf-doc": { + "versions": { + "0.7.1-r0": 1553432497 + }, + "origin": "aconf" + }, + "libxtst": { + "versions": { + "1.2.3-r2": 1543241194 + }, + "origin": "libxtst", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXtst.so.6=6.1.0" + ] + }, + "nghttp2": { + "versions": { + "1.35.1-r0": 1545858450 + }, + "origin": "nghttp2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libcrypto.so.1.1", + "so:libev.so.4", + "so:libgcc_s.so.1", + "so:libnghttp2.so.14", + "so:libssl.so.1.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:h2load", + "cmd:nghttp", + "cmd:nghttpd", + "cmd:nghttpx" + ] + }, + "smem": { + "versions": { + "1.4-r1": 1545060973 + }, + "origin": "smem", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:smem" + ] + }, + "xf86-video-rendition": { + "versions": { + "4.2.7-r0": 1544000926 + }, + "origin": "xf86-video-rendition", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "atkmm-dev": { + "versions": { + "2.24.2-r1": 1544799364 + }, + "origin": "atkmm", + "dependencies": [ + "atkmm=2.24.2-r1", + "pc:atk>=1.18", + "pc:glibmm-2.4>=2.46.2", + "pkgconfig" + ], + "provides": [ + "pc:atkmm-1.6=2.24.2" + ] + }, + "perl-universal-require": { + "versions": { + "0.18-r0": 1545995649 + }, + "origin": "perl-universal-require", + "dependencies": [ + "perl" + ] + }, + "gdb": { + "versions": { + "8.2-r1": 1544798982 + }, + "origin": "gdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libncursesw.so.6", + "so:libpython3.6m.so.1.0", + "so:libreadline.so.7", + "so:libz.so.1" + ], + "provides": [ + "cmd:gcore", + "cmd:gdb", + "cmd:gdb-add-index", + "cmd:gdbserver" + ] + }, + "libmagic": { + "versions": { + "5.36-r0": 1557160812 + }, + "origin": "file", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmagic.so.1=1.0.0" + ] + }, + "dovecot-mysql": { + "versions": { + "2.3.6-r0": 1557134098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot-sql=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ], + "provides": [ + "so:libdriver_mysql.so=0" + ] + }, + "uwsgi-fastrouter": { + "versions": { + "2.0.17.1-r0": 1545062200 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "uwsgi-corerouter", + "so:libc.musl-x86_64.so.1" + ] + }, + "zsh-vcs": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "librsync-dev": { + "versions": { + "2.0.2-r1": 1543932722 + }, + "origin": "librsync", + "dependencies": [ + "librsync=2.0.2-r1" + ] + }, + "lcms2-utils": { + "versions": { + "2.9-r1": 1542824189 + }, + "origin": "lcms2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libtiff.so.5" + ], + "provides": [ + "cmd:jpgicc", + "cmd:linkicc", + "cmd:psicc", + "cmd:tificc", + "cmd:transicc" + ] + }, + "perl-path-class": { + "versions": { + "0.37-r0": 1543246841 + }, + "origin": "perl-path-class" + }, + "faad2-dev": { + "versions": { + "2.7-r8": 1543998810 + }, + "origin": "faad2", + "dependencies": [ + "faad2=2.7-r8" + ] + }, + "zfs-udev": { + "versions": { + "0.7.12-r1": 1552933679 + }, + "origin": "zfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "mksh": { + "versions": { + "56c-r0": 1543925655 + }, + "origin": "mksh", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mksh" + ] + }, + "py-exifread": { + "versions": { + "2.1.2-r2": 1542821588 + }, + "origin": "py-exifread", + "dependencies": [ + "python3" + ], + "provides": [ + "cmd:EXIF.py" + ] + }, + "collectd-iptables": { + "versions": { + "5.8.0-r3": 1546422677 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libip4tc.so.0", + "so:libip6tc.so.0" + ] + }, + "wireless-tools-doc": { + "versions": { + "30_pre9-r0": 1545062250 + }, + "origin": "wireless-tools" + }, + "libdv": { + "versions": { + "1.0.0-r3": 1543999341 + }, + "origin": "libdv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdv.so.4=4.0.3" + ] + }, + "asterisk-sample-config": { + "versions": { + "15.7.1-r0": 1546247585 + }, + "origin": "asterisk" + }, + "perl-net-telnet-doc": { + "versions": { + "3.04-r0": 1543999436 + }, + "origin": "perl-net-telnet" + }, + "perl-crypt-openssl-random": { + "versions": { + "0.15-r1": 1543925841 + }, + "origin": "perl-crypt-openssl-random", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ] + }, + "lua5.2-cjson": { + "versions": { + "2.1.0-r8": 1542820883 + }, + "origin": "lua-cjson", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "procmail": { + "versions": { + "3.22-r4": 1545299912 + }, + "origin": "procmail", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:formail", + "cmd:lockfile", + "cmd:mailstat", + "cmd:procmail" + ] + }, + "perl-file-slurp-doc": { + "versions": { + "9999.25-r0": 1546005797 + }, + "origin": "perl-file-slurp" + }, + "gconf-lang": { + "versions": { + "3.2.6-r4": 1545075211 + }, + "origin": "gconf" + }, + "libnet": { + "versions": { + "1.1.6-r2": 1545060613 + }, + "origin": "libnet", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnet.so.1=1.7.0" + ] + }, + "readline-doc": { + "versions": { + "7.0.003-r1": 1542301065 + }, + "origin": "readline" + }, + "lua5.2-hashids": { + "versions": { + "1.0.6-r1": 1545209082 + }, + "origin": "lua-hashids", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1" + ] + }, + "bitlbee-dev": { + "versions": { + "3.5.1-r4": 1543248420 + }, + "origin": "bitlbee", + "dependencies": [ + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:bitlbee=3.5.1" + ] + }, + "py3-ldb": { + "versions": { + "1.3.8-r0": 1555334661 + }, + "origin": "ldb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libldb.so.1", + "so:libpython3.6m.so.1.0", + "so:libtalloc.so.2" + ], + "provides": [ + "so:libpyldb-util.cpython-36m-x86-64-linux-gnu.so.1=1.3.8" + ] + }, + "perl-list-someutils": { + "versions": { + "0.55-r0": 1543934260 + }, + "origin": "perl-list-someutils", + "dependencies": [ + "perl-module-implementation", + "perl-list-someutils-xs" + ] + }, + "uwsgi-router_metrics": { + "versions": { + "2.0.17.1-r0": 1545062207 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "poppler-glib": { + "versions": { + "0.56.0-r1": 1542824319 + }, + "origin": "poppler", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libfreetype.so.6", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpoppler.so.67", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libpoppler-glib.so.8=8.9.0" + ] + }, + "xset-doc": { + "versions": { + "1.2.4-r0": 1545076640 + }, + "origin": "xset" + }, + "py-musicbrainzngs": { + "versions": { + "0.6-r2": 1543240637 + }, + "origin": "py-musicbrainzngs" + }, + "indent": { + "versions": { + "2.2.12-r0": 1545299644 + }, + "origin": "indent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8" + ], + "provides": [ + "cmd:indent" + ] + }, + "sqsh-doc": { + "versions": { + "2.5.16.1-r2": 1545300947 + }, + "origin": "sqsh" + }, + "fprobe": { + "versions": { + "1.1-r8": 1545213731 + }, + "origin": "fprobe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:fprobe" + ] + }, + "bdftopcf-doc": { + "versions": { + "1.1-r0": 1542924734 + }, + "origin": "bdftopcf" + }, + "polkit-lang": { + "versions": { + "0.105-r9": 1547130960 + }, + "origin": "polkit" + }, + "lua5.2-openrc": { + "versions": { + "0.2-r3": 1545066957 + }, + "origin": "lua-openrc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librc.so.1" + ] + }, + "perl-text-wrapper-doc": { + "versions": { + "1.05-r1": 1543927508 + }, + "origin": "perl-text-wrapper" + }, + "gtk-engines-mist": { + "versions": { + "2.21.0-r2": 1545289337 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "lua-sqlite": { + "versions": { + "0.9.5-r2": 1545214289 + }, + "origin": "lua-sqlite" + }, + "libnfs": { + "versions": { + "3.0.0-r0": 1544799294 + }, + "origin": "libnfs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnfs.so.12=12.0.0", + "cmd:nfs-cat", + "cmd:nfs-cp", + "cmd:nfs-ls" + ] + }, + "gnokii-libs": { + "versions": { + "0.6.31-r8": 1545300479 + }, + "origin": "gnokii", + "dependencies": [ + "so:libXpm.so.4", + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libical.so.2", + "so:libintl.so.8", + "so:libusb-0.1.so.4" + ], + "provides": [ + "so:libgnokii.so.7=7.0.0" + ] + }, + "freeradius-eap": { + "versions": { + "3.0.17-r5": 1556201989 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius=3.0.17-r5", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libfreeradius-eap.so", + "so:libfreeradius-radius.so", + "so:libfreeradius-server.so", + "so:libssl.so.1.1", + "so:libtalloc.so.2" + ], + "provides": [ + "freeradius3-eap=3.0.17-r5", + "so:rlm_eap.so=0", + "so:rlm_eap_fast.so=0", + "so:rlm_eap_gtc.so=0", + "so:rlm_eap_leap.so=0", + "so:rlm_eap_md5.so=0", + "so:rlm_eap_mschapv2.so=0", + "so:rlm_eap_peap.so=0", + "so:rlm_eap_pwd.so=0", + "so:rlm_eap_sim.so=0", + "so:rlm_eap_tls.so=0", + "so:rlm_eap_ttls.so=0", + "cmd:radeapclient" + ] + }, + "glew-doc": { + "versions": { + "2.1.0-r0": 1543935773 + }, + "origin": "glew" + }, + "perl-test-cpan-meta": { + "versions": { + "0.25-r0": 1545075947 + }, + "origin": "perl-test-cpan-meta" + }, + "py3-oauthlib": { + "versions": { + "2.0.6-r1": 1543249960 + }, + "origin": "py-oauthlib", + "dependencies": [ + "py3-crypto", + "py3-jwt", + "python3" + ] + }, + "atkmm-doc": { + "versions": { + "2.24.2-r1": 1544799364 + }, + "origin": "atkmm" + }, + "iperf": { + "versions": { + "2.0.10-r1": 1543222923 + }, + "origin": "iperf", + "dependencies": [ + "!iperf3", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:iperf" + ] + }, + "joe-doc": { + "versions": { + "4.6-r0": 1543221866 + }, + "origin": "joe" + }, + "lua-rex": { + "versions": { + "2.9.0-r0": 1545209208 + }, + "origin": "lua-rex", + "dependencies": [ + "lua-rex-pcre", + "lua-rex-posix" + ] + }, + "popt": { + "versions": { + "1.16-r7": 1542820794 + }, + "origin": "popt", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpopt.so.0=0.0.0" + ] + }, + "lua-md5": { + "versions": { + "1.2-r3": 1542883871 + }, + "origin": "lua-md5" + }, + "jfsutils-doc": { + "versions": { + "1.1.15-r1": 1542883489 + }, + "origin": "jfsutils" + }, + "gtk-engines-crux": { + "versions": { + "2.21.0-r2": 1545289336 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "fontconfig": { + "versions": { + "2.13.1-r0": 1545745987 + }, + "origin": "fontconfig", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libexpat.so.1", + "so:libfreetype.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "so:libfontconfig.so.1=1.12.0", + "cmd:fc-cache", + "cmd:fc-cat", + "cmd:fc-conflist", + "cmd:fc-list", + "cmd:fc-match", + "cmd:fc-pattern", + "cmd:fc-query", + "cmd:fc-scan", + "cmd:fc-validate" + ] + }, + "gzip": { + "versions": { + "1.10-r0": 1546345202 + }, + "origin": "gzip", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:gunzip", + "cmd:gzexe", + "cmd:gzip", + "cmd:uncompress", + "cmd:zcat", + "cmd:zcmp", + "cmd:zdiff", + "cmd:zegrep", + "cmd:zfgrep", + "cmd:zforce", + "cmd:zgrep", + "cmd:zless", + "cmd:zmore", + "cmd:znew" + ] + }, + "gtk+-dev": { + "versions": { + "2.24.31-r0": 1543928744 + }, + "origin": "gtk+", + "dependencies": [ + "gtk+2.0-dev" + ] + }, + "lm_sensors": { + "versions": { + "3.4.0-r6": 1542924821 + }, + "origin": "lm_sensors", + "dependencies": [ + "bash", + "sysfsutils", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsensors.so.4=4.4.0", + "cmd:fancontrol", + "cmd:isadump", + "cmd:isaset", + "cmd:pwmconfig", + "cmd:sensors" + ] + }, + "libxmu": { + "versions": { + "1.1.2-r1": 1542883345 + }, + "origin": "libxmu", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXmu.so.6=6.2.0", + "so:libXmuu.so.1=1.0.0" + ] + }, + "openldap-back-relay": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2" + ] + }, + "libxxf86misc": { + "versions": { + "1.0.3-r2": 1543927842 + }, + "origin": "libxxf86misc", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXxf86misc.so.1=1.1.0" + ] + }, + "openldap-overlay-auditlog": { + "versions": { + "2.4.47-r2": 1546016478 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "perl-async-mergepoint": { + "versions": { + "0.04-r0": 1545075381 + }, + "origin": "perl-async-mergepoint" + }, + "qemu-system-nios2": { + "versions": { + "3.1.0-r3": 1551107304 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-nios2" + ] + }, + "perl-html-scrubber": { + "versions": { + "0.17-r0": 1545292679 + }, + "origin": "perl-html-scrubber", + "dependencies": [ + "perl-html-parser" + ] + }, + "krb5-server": { + "versions": { + "1.15.5-r0": 1548094457 + }, + "origin": "krb5", + "dependencies": [ + "libverto-libev", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libgssapi_krb5.so.2", + "so:libgssrpc.so.4", + "so:libk5crypto.so.3", + "so:libkadm5clnt_mit.so.11", + "so:libkadm5srv_mit.so.11", + "so:libkdb5.so.8", + "so:libkdb_ldap.so.1", + "so:libkrb5.so.3", + "so:libkrb5support.so.0", + "so:libss.so.2", + "so:libverto.so.1" + ], + "provides": [ + "cmd:gss-server", + "cmd:kadmin.local", + "cmd:kadmind", + "cmd:kdb5_ldap_util", + "cmd:kdb5_util", + "cmd:kprop", + "cmd:kpropd", + "cmd:kproplog", + "cmd:krb5-send-pr", + "cmd:krb5kdc", + "cmd:sclient", + "cmd:sim_server", + "cmd:sserver", + "cmd:uuserver" + ] + }, + "xf86-video-r128-doc": { + "versions": { + "6.11.0-r0": 1545060959 + }, + "origin": "xf86-video-r128" + }, + "libxv-doc": { + "versions": { + "1.0.11-r2": 1542900291 + }, + "origin": "libxv" + }, + "icu-libs": { + "versions": { + "62.1-r0": 1542823973 + }, + "origin": "icu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libicudata.so.62=62.1", + "so:libicui18n.so.62=62.1", + "so:libicuio.so.62=62.1", + "so:libicutest.so.62=62.1", + "so:libicutu.so.62=62.1", + "so:libicuuc.so.62=62.1" + ] + }, + "libechonest": { + "versions": { + "2.3.1-r0": 1545209022 + }, + "origin": "libechonest", + "dependencies": [ + "so:libQtCore.so.4", + "so:libQtNetwork.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libqjson.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libechonest.so.2.3=2.3.1" + ] + }, + "py2-itsdangerous": { + "versions": { + "0.24-r3": 1545164549 + }, + "origin": "py-itsdangerous", + "dependencies": [ + "python2" + ] + }, + "avfs": { + "versions": { + "1.0.6-r0": 1545665871 + }, + "origin": "avfs", + "dependencies": [ + "bash", + "so:libc.musl-x86_64.so.1", + "so:libfuse.so.2" + ], + "provides": [ + "so:libavfs.so.0=0.0.2", + "cmd:avfsd", + "cmd:davpass", + "cmd:ftppass", + "cmd:mountavfs", + "cmd:umountavfs" + ] + }, + "dpaste": { + "versions": { + "0.6-r0": 1545076747 + }, + "origin": "sprunge", + "dependencies": [ + "curl" + ], + "provides": [ + "cmd:dpaste" + ] + }, + "dconf": { + "versions": { + "0.26.0-r1": 1545302105 + }, + "origin": "dconf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libdconf.so.1=1.0.0", + "cmd:dconf" + ] + }, + "powertop": { + "versions": { + "2.9-r1": 1543927725 + }, + "origin": "powertop", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libnl.so.1", + "so:libpci.so.3", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:powertop" + ] + }, + "ipset-doc": { + "versions": { + "7.1-r0": 1545163450 + }, + "origin": "ipset" + }, + "gcc": { + "versions": { + "8.3.0-r0": 1554745500 + }, + "origin": "gcc", + "dependencies": [ + "binutils", + "isl", + "libgomp=8.3.0-r0", + "libatomic=8.3.0-r0", + "libgomp=8.3.0-r0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgmp.so.10", + "so:libisl.so.15", + "so:libmpc.so.3", + "so:libmpfr.so.4", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libcc1.so.0=0.0.0", + "so:libitm.so.1=1.0.0", + "cmd:c89", + "cmd:c99", + "cmd:cc", + "cmd:cpp", + "cmd:gcc", + "cmd:gcc-ar", + "cmd:gcc-nm", + "cmd:gcc-ranlib", + "cmd:gcov", + "cmd:gcov-dump", + "cmd:gcov-tool", + "cmd:x86_64-alpine-linux-musl-gcc", + "cmd:x86_64-alpine-linux-musl-gcc-8.3.0", + "cmd:x86_64-alpine-linux-musl-gcc-ar", + "cmd:x86_64-alpine-linux-musl-gcc-nm", + "cmd:x86_64-alpine-linux-musl-gcc-ranlib" + ] + }, + "xrandr": { + "versions": { + "1.5.0-r1": 1545207350 + }, + "origin": "xrandr", + "dependencies": [ + "so:libX11.so.6", + "so:libXrandr.so.2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xrandr" + ] + }, + "uwsgi-router_memcached": { + "versions": { + "2.0.17.1-r0": 1545062207 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "spamassassin-doc": { + "versions": { + "3.4.2-r0": 1545061981 + }, + "origin": "spamassassin" + }, + "mtdev-dev": { + "versions": { + "1.1.5-r2": 1545154597 + }, + "origin": "mtdev", + "dependencies": [ + "linux-headers", + "mtdev=1.1.5-r2", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libmtdev.so.1" + ], + "provides": [ + "pc:mtdev=1.1.5", + "cmd:mtdev-test" + ] + }, + "linux-firmware-isci": { + "versions": { + "20190322-r0": 1554980651 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "openipmi": { + "versions": { + "2.0.25-r1": 1545214271 + }, + "origin": "openipmi", + "dependencies": [ + "so:libOpenIPMI.so.0", + "so:libOpenIPMIcmdlang.so.0", + "so:libOpenIPMIglib.so.0", + "so:libOpenIPMIposix.so.0", + "so:libOpenIPMIui.so.1", + "so:libOpenIPMIutils.so.0", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libncursesw.so.6", + "so:libnetsnmp.so.35" + ], + "provides": [ + "cmd:ipmi_ui", + "cmd:ipmicmd", + "cmd:ipmish", + "cmd:openipmi_eventd", + "cmd:openipmicmd", + "cmd:openipmish", + "cmd:rmcp_ping", + "cmd:solterm" + ] + }, + "flashrom-doc": { + "versions": { + "1.0-r3": 1543921877 + }, + "origin": "flashrom" + }, + "samba-test": { + "versions": { + "4.8.11-r1": 1555334976 + }, + "origin": "samba", + "dependencies": [ + "so:libMESSAGING-SEND-samba4.so", + "so:libMESSAGING-samba4.so", + "so:libaddns-samba4.so", + "so:libasn1-samba4.so.8", + "so:libasn1util-samba4.so", + "so:libauthkrb5-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-ldap-common-samba4.so", + "so:libcli-ldap-samba4.so", + "so:libcli-nbt-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcliauth-samba4.so", + "so:libcluster-samba4.so", + "so:libcmdline-credentials-samba4.so", + "so:libcom_err.so.2", + "so:libcommon-auth-samba4.so", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libdcerpc-samba-samba4.so", + "so:libdcerpc.so.0", + "so:libdnsserver-common-samba4.so", + "so:libdsdb-module-samba4.so", + "so:libevents-samba4.so", + "so:libgenrand-samba4.so", + "so:libgensec-samba4.so", + "so:libgnutls.so.30", + "so:libkrb5-samba4.so.26", + "so:libkrb5samba-samba4.so", + "so:libldb.so.1", + "so:libldbsamba-samba4.so", + "so:liblibsmb-samba4.so", + "so:libndr-krb5pac.so.0", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libnetapi.so.0", + "so:libnetif-samba4.so", + "so:libpopt.so.0", + "so:libregistry-samba4.so", + "so:libreplace-samba4.so", + "so:libsamba-credentials.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-net-samba4.so", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamdb-common-samba4.so", + "so:libsamdb.so.0", + "so:libshares-samba4.so", + "so:libsmbclient-raw-samba4.so", + "so:libsmbclient.so.0", + "so:libsmbconf.so.0", + "so:libsmbpasswdparser-samba4.so", + "so:libsys-rw-samba4.so", + "so:libtalloc.so.2", + "so:libtdb-wrap-samba4.so", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libtime-basic-samba4.so", + "so:libutil-reg-samba4.so", + "so:libutil-tdb-samba4.so", + "so:libwbclient.so.0", + "so:libwinbind-client-samba4.so" + ], + "provides": [ + "so:libdlz-bind9-for-torture-samba4.so=0", + "so:libtorture-samba4.so=0", + "cmd:gentest", + "cmd:locktest", + "cmd:masktest", + "cmd:ndrdump", + "cmd:smbtorture" + ] + }, + "lua5.3-alt-getopt": { + "versions": { + "0.7-r8": 1544799010 + }, + "origin": "lua-alt-getopt" + }, + "dkimproxy": { + "versions": { + "1.4.1-r5": 1545213959 + }, + "origin": "dkimproxy", + "dependencies": [ + "perl-mail-dkim", + "perl-net-server", + "perl-error", + "/bin/sh" + ], + "provides": [ + "cmd:dkim_responder", + "cmd:dkimproxy.in", + "cmd:dkimproxy.out" + ] + }, + "linux-firmware-qed": { + "versions": { + "20190322-r0": 1554980649 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "mtdev": { + "versions": { + "1.1.5-r2": 1545154598 + }, + "origin": "mtdev", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmtdev.so.1=1.0.0" + ] + }, + "xf86-video-amdgpu": { + "versions": { + "18.0.1-r2": 1545215978 + }, + "origin": "xf86-video-amdgpu", + "dependencies": [ + "mesa-dri-ati", + "so:libc.musl-x86_64.so.1", + "so:libdrm_amdgpu.so.1", + "so:libgbm.so.1", + "so:libudev.so.1" + ] + }, + "py2-six": { + "versions": { + "1.11.0-r0": 1542823022 + }, + "origin": "py-six", + "dependencies": [ + "python2" + ] + }, + "libell-dev": { + "versions": { + "0.16-r0": 1545822164 + }, + "origin": "libell", + "dependencies": [ + "libell=0.16-r0", + "pkgconfig" + ], + "provides": [ + "pc:ell=0.16" + ] + }, + "pcre-dev": { + "versions": { + "8.42-r1": 1545067003 + }, + "origin": "pcre", + "dependencies": [ + "libpcre16=8.42-r1", + "libpcre32=8.42-r1", + "libpcrecpp=8.42-r1", + "pcre=8.42-r1", + "pkgconfig" + ], + "provides": [ + "pc:libpcre16=8.42", + "pc:libpcre32=8.42", + "pc:libpcre=8.42", + "pc:libpcrecpp=8.42", + "pc:libpcreposix=8.42", + "cmd:pcre-config" + ] + }, + "gpsd-clients": { + "versions": { + "3.18.1-r1": 1549881587 + }, + "origin": "gpsd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:cgps", + "cmd:gegps", + "cmd:gps2udp", + "cmd:gpsctl", + "cmd:gpsdecode", + "cmd:gpsmon", + "cmd:gpspipe", + "cmd:gpxlogger", + "cmd:lcdgps", + "cmd:ntpshmmon", + "cmd:ppscheck" + ] + }, + "ncurses-static": { + "versions": { + "6.1_p20190105-r0": 1546948252 + }, + "origin": "ncurses" + }, + "binutils": { + "versions": { + "2.31.1-r2": 1546441179 + }, + "origin": "binutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libbfd-2.31.1.so=0", + "so:libopcodes-2.31.1.so=0", + "cmd:addr2line", + "cmd:ar", + "cmd:as", + "cmd:c++filt", + "cmd:dwp", + "cmd:elfedit", + "cmd:gprof", + "cmd:ld", + "cmd:ld.bfd", + "cmd:nm", + "cmd:objcopy", + "cmd:objdump", + "cmd:ranlib", + "cmd:readelf", + "cmd:size", + "cmd:strings", + "cmd:strip" + ] + }, + "asterisk-dev": { + "versions": { + "15.7.1-r0": 1546247583 + }, + "origin": "asterisk", + "dependencies": [ + "asterisk" + ] + }, + "asterisk-sounds-moh": { + "versions": { + "15.7.1-r0": 1546247585 + }, + "origin": "asterisk" + }, + "iftop-doc": { + "versions": { + "0.17-r7": 1542981212 + }, + "origin": "iftop" + }, + "asterisk-fax": { + "versions": { + "15.7.1-r0": 1546247584 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libspandsp.so.2" + ] + }, + "rpcgen": { + "versions": { + "2.3.2-r1": 1543933422 + }, + "origin": "nfs-utils", + "dependencies": [ + "rpcbind", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:rpcgen" + ] + }, + "perl-devel-stacktrace-ashtml-doc": { + "versions": { + "0.15-r0": 1544792341 + }, + "origin": "perl-devel-stacktrace-ashtml" + }, + "lua-expat": { + "versions": { + "1.3.0-r2": 1544000289 + }, + "origin": "lua-expat" + }, + "duply-doc": { + "versions": { + "2.1-r0": 1545300230 + }, + "origin": "duply" + }, + "openldap": { + "versions": { + "2.4.47-r2": 1546016480 + }, + "origin": "openldap", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2", + "so:libltdl.so.7", + "so:libsasl2.so.3", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:slapacl", + "cmd:slapadd", + "cmd:slapauth", + "cmd:slapcat", + "cmd:slapd", + "cmd:slapdn", + "cmd:slapindex", + "cmd:slappasswd", + "cmd:slapschema", + "cmd:slaptest" + ] + }, + "libffi-dev": { + "versions": { + "3.2.1-r6": 1545154420 + }, + "origin": "libffi", + "dependencies": [ + "linux-headers", + "libffi=3.2.1-r6", + "pkgconfig" + ], + "provides": [ + "pc:libffi=3.2.1" + ] + }, + "smokeping": { + "versions": { + "2.7.3-r3": 1545734482 + }, + "origin": "smokeping", + "dependencies": [ + "fping", + "perl", + "perl-cgi", + "perl-cgi-fast", + "perl-cgi-session", + "perl-config-grammar", + "perl-data-hexdump", + "perl-digest-hmac", + "perl-fcgi", + "perl-io-socket-ssl", + "perl-io-tty", + "perl-ldap", + "perl-libwww", + "perl-mozilla-ca", + "perl-net-dns", + "perl-net-ip", + "perl-net-openssh", + "perl-net-snmp", + "perl-net-telnet", + "perl-text-soundex", + "perl-rrd", + "perl-snmp-session", + "perl-uri", + "rrdtool", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:smokeinfo", + "cmd:smokeping", + "cmd:smokeping_cgi", + "cmd:tSmoke" + ] + }, + "memcached-dev": { + "versions": { + "1.5.12-r0": 1543923566 + }, + "origin": "memcached", + "dependencies": [ + "memcached=1.5.12-r0" + ] + }, + "pcmciautils-doc": { + "versions": { + "018-r1": 1543077269 + }, + "origin": "pcmciautils" + }, + "umix": { + "versions": { + "1.0.2-r7": 1545235248 + }, + "origin": "umix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:umix" + ] + }, + "sc": { + "versions": { + "7.16-r5": 1545208854 + }, + "origin": "sc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:psc", + "cmd:sc", + "cmd:scqref" + ] + }, + "herbstluftwm": { + "versions": { + "0.7.1-r0": 1545300037 + }, + "origin": "herbstluftwm", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXinerama.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:dmenu_run_hlwm", + "cmd:herbstclient", + "cmd:herbstluftwm" + ] + }, + "cdparanoia-libs": { + "versions": { + "10.2-r7": 1543254199 + }, + "origin": "cdparanoia", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcdda_interface.so.0=0.10.2", + "so:libcdda_paranoia.so.0=0.10.2" + ] + }, + "lua5.2-iconv": { + "versions": { + "7-r1": 1545062769 + }, + "origin": "lua-iconv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libaio": { + "versions": { + "0.3.111-r0": 1542845872 + }, + "origin": "libaio", + "provides": [ + "so:libaio.so.1=1.0.1" + ] + }, + "linux-firmware-edgeport": { + "versions": { + "20190322-r0": 1554980652 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "collectd-curl": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libxml2.so.2", + "so:libyajl.so.2" + ] + }, + "run-parts-doc": { + "versions": { + "4.8.6-r0": 1543924424 + }, + "origin": "run-parts" + }, + "ntfs-3g-doc": { + "versions": { + "2017.3.23-r1": 1542973168 + }, + "origin": "ntfs-3g" + }, + "fontconfig-doc": { + "versions": { + "2.13.1-r0": 1545745987 + }, + "origin": "fontconfig" + }, + "qemu-ppc64abi32": { + "versions": { + "3.1.0-r3": 1551107301 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-ppc64abi32" + ] + }, + "lua5.1-hashids": { + "versions": { + "1.0.6-r1": 1545209081 + }, + "origin": "lua-hashids", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-packaging": { + "versions": { + "17.1-r0": 1542825055 + }, + "origin": "py-packaging", + "dependencies": [ + "py2-parsing", + "py2-six", + "python2" + ] + }, + "nagios-plugins-ircd": { + "versions": { + "2.2.1-r6": 1543933906 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "perl" + ] + }, + "libmaa": { + "versions": { + "1.3.2-r1": 1545209186 + }, + "origin": "libmaa", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmaa.so.3=3.0.0" + ] + }, + "ncurses-dev": { + "versions": { + "6.1_p20190105-r0": 1546948252 + }, + "origin": "ncurses", + "dependencies": [ + "ncurses-libs=6.1_p20190105-r0", + "pkgconfig" + ], + "provides": [ + "pc:form=6.1.20190105", + "pc:formw=6.1.20190105", + "pc:menu=6.1.20190105", + "pc:menuw=6.1.20190105", + "pc:ncurses=6.1.20190105", + "pc:ncursesw=6.1.20190105", + "pc:panel=6.1.20190105", + "pc:panelw=6.1.20190105", + "cmd:ncursesw6-config" + ] + }, + "enchant-dev": { + "versions": { + "1.6.0-r13": 1542965845 + }, + "origin": "enchant", + "dependencies": [ + "enchant=1.6.0-r13", + "pc:glib-2.0", + "pc:gmodule-no-export-2.0", + "pkgconfig" + ], + "provides": [ + "pc:enchant=1.6.0" + ] + }, + "qjson": { + "versions": { + "0.9.0-r0": 1545075113 + }, + "origin": "qjson", + "dependencies": [ + "so:libQtCore.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libqjson.so.0=0.9.0" + ] + }, + "perl-params-validationcompiler": { + "versions": { + "0.27-r0": 1542973093 + }, + "origin": "perl-params-validationcompiler", + "dependencies": [ + "perl-specio", + "perl-test2-suite", + "perl-test-simple", + "perl-test-without-module", + "perl-eval-closure", + "perl-exception-class", + "perl-test2-plugin-nowarnings", + "perl-role-tiny", + "perl-mro-compat" + ] + }, + "sharutils": { + "versions": { + "4.15.2-r0": 1543929028 + }, + "origin": "sharutils", + "dependencies": [ + "bzip2", + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8" + ], + "provides": [ + "cmd:shar", + "cmd:unshar", + "cmd:uudecode", + "cmd:uuencode" + ] + }, + "nagios-plugins-mrtg": { + "versions": { + "2.2.1-r6": 1543933908 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-django-djblets": { + "versions": { + "0.6.31-r0": 1545207048 + }, + "origin": "py-django-djblets", + "dependencies": [ + "python2", + "py-django-pipeline" + ] + }, + "lua-sql-mysql": { + "versions": { + "2.3.5-r2": 1543924407 + }, + "origin": "lua-sql" + }, + "syslog-ng-doc": { + "versions": { + "3.19.1-r0": 1548543150 + }, + "origin": "syslog-ng" + }, + "nginx-mod-http-image-filter": { + "versions": { + "1.14.2-r1": 1557179820 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1", + "so:libgd.so.3" + ] + }, + "libxscrnsaver": { + "versions": { + "1.2.2-r3": 1543932086 + }, + "origin": "libxscrnsaver", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXss.so.1=1.0.0" + ] + }, + "uwsgi-dumbloop": { + "versions": { + "2.0.17.1-r0": 1545062198 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "cabextract-doc": { + "versions": { + "1.9-r0": 1545209003 + }, + "origin": "cabextract" + }, + "py2-jwt": { + "versions": { + "1.6.4-r0": 1543249953 + }, + "origin": "py-jwt", + "dependencies": [ + "python2" + ] + }, + "collectd-ceph": { + "versions": { + "5.8.0-r3": 1546422678 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libyajl.so.2" + ] + }, + "perl-http-message-doc": { + "versions": { + "6.18-r0": 1542821805 + }, + "origin": "perl-http-message" + }, + "libmowgli-dev": { + "versions": { + "2.1.3-r3": 1543998747 + }, + "origin": "libmowgli", + "dependencies": [ + "libmowgli=2.1.3-r3", + "pkgconfig" + ], + "provides": [ + "pc:libmowgli-2=2.1.3" + ] + }, + "freshclam-openrc": { + "versions": { + "0.100.3-r0": 1555507335 + }, + "origin": "clamav" + }, + "krb5-conf": { + "versions": { + "1.0-r1": 1542819056 + }, + "origin": "krb5-conf" + }, + "lua5.1-dev": { + "versions": { + "5.1.5-r7": 1542820875 + }, + "origin": "lua5.1", + "dependencies": [ + "lua5.1", + "lua5.1-libs=5.1.5-r7", + "pkgconfig" + ], + "provides": [ + "lua-dev", + "pc:lua5.1=5.1.5", + "pc:lua=5.1.5" + ] + }, + "ncftp-bookmarks": { + "versions": { + "3.2.6-r1": 1544000464 + }, + "origin": "ncftp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncftp.so.3", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:ncftpbookmarks" + ] + }, + "py2-uritemplate": { + "versions": { + "3.0.0-r1": 1545207295 + }, + "origin": "py-uritemplate", + "dependencies": [ + "python2" + ], + "provides": [ + "py2-uritemplate.py" + ] + }, + "dropbear-dbclient": { + "versions": { + "2018.76-r2": 1545208976 + }, + "origin": "dropbear", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:dbclient" + ] + }, + "libedit-doc": { + "versions": { + "20181209.3.1-r0": 1545839273 + }, + "origin": "libedit" + }, + "sysstat": { + "versions": { + "11.6.0-r0": 1543922334 + }, + "origin": "sysstat", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:cifsiostat", + "cmd:iostat", + "cmd:mpstat", + "cmd:pidstat", + "cmd:sadf", + "cmd:sar", + "cmd:tapestat" + ] + }, + "graphviz-gtk": { + "versions": { + "2.40.1-r1": 1543926741 + }, + "origin": "graphviz", + "dependencies": [ + "so:libatk-1.0.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libgvc.so.6", + "so:librsvg-2.so.2" + ], + "provides": [ + "so:libgvplugin_gdk.so.6=6.0.0", + "so:libgvplugin_gtk.so.6=6.0.0", + "so:libgvplugin_rsvg.so.6=6.0.0" + ] + }, + "cryptsetup1-dev": { + "versions": { + "1.7.5-r4": 1545292765 + }, + "origin": "cryptsetup1", + "dependencies": [ + "cryptsetup1-libs=1.7.5-r4", + "pkgconfig" + ], + "provides": [ + "pc:libcryptsetup=1.7.5" + ] + }, + "pango": { + "versions": { + "1.42.4-r0": 1542824069 + }, + "origin": "pango", + "dependencies": [ + "/bin/sh", + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libfribidi.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libharfbuzz.so.0" + ], + "provides": [ + "so:libpango-1.0.so.0=0.4200.4", + "so:libpangocairo-1.0.so.0=0.4200.4", + "so:libpangoft2-1.0.so.0=0.4200.4", + "so:libpangoxft-1.0.so.0=0.4200.4", + "cmd:pango-list", + "cmd:pango-view" + ] + }, + "cairomm": { + "versions": { + "1.12.2-r0": 1543077183 + }, + "origin": "cairomm", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgcc_s.so.1", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libcairomm-1.0.so.1=1.4.0" + ] + }, + "lua-pingu": { + "versions": { + "1.5-r2": 1544000080 + }, + "origin": "pingu", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "nagios-plugins-dig": { + "versions": { + "2.2.1-r6": 1543933903 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "bind-tools", + "so:libc.musl-x86_64.so.1" + ] + }, + "rsnapshot": { + "versions": { + "1.4.2-r0": 1544799300 + }, + "origin": "rsnapshot", + "dependencies": [ + "perl", + "rsync", + "openssh-client" + ], + "provides": [ + "cmd:rsnapshot", + "cmd:rsnapshot-diff" + ] + }, + "acf-dhcp": { + "versions": { + "0.9.1-r0": 1545229760 + }, + "origin": "acf-dhcp", + "dependencies": [ + "acf-core", + "dhcp" + ] + }, + "expect": { + "versions": { + "5.45.4-r0": 1543246822 + }, + "origin": "expect", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtcl8.6.so" + ], + "provides": [ + "so:libexpect5.45.4.so=0", + "cmd:autoexpect", + "cmd:autopasswd", + "cmd:cryptdir", + "cmd:decryptdir", + "cmd:dislocate", + "cmd:expect", + "cmd:ftp-rfc", + "cmd:kibitz", + "cmd:lpunlock", + "cmd:mkpasswd", + "cmd:multixterm", + "cmd:passmass", + "cmd:rftp", + "cmd:rlogin-cwd", + "cmd:timed-read", + "cmd:timed-run", + "cmd:tknewsbiff", + "cmd:tkpasswd", + "cmd:unbuffer", + "cmd:weather", + "cmd:xkibitz", + "cmd:xpstat" + ] + }, + "perl-net-cidr-lite": { + "versions": { + "0.21-r4": 1543924461 + }, + "origin": "perl-net-cidr-lite", + "dependencies": [ + "perl" + ] + }, + "aumix": { + "versions": { + "2.9.1-r6": 1545069455 + }, + "origin": "aumix", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:aumix", + "cmd:mute", + "cmd:xaumix" + ] + }, + "libgcrypt-doc": { + "versions": { + "1.8.4-r0": 1545838380 + }, + "origin": "libgcrypt" + }, + "perl-encode-hanextra": { + "versions": { + "0.23-r2": 1545075149 + }, + "origin": "perl-encode-hanextra", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "pixman": { + "versions": { + "0.34.0-r6": 1542822838 + }, + "origin": "pixman", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpixman-1.so.0=0.34.0" + ] + }, + "cyrus-sasl-ntlm": { + "versions": { + "2.1.27-r1": 1550353528 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ] + }, + "py2-cffi": { + "versions": { + "1.11.5-r0": 1543924872 + }, + "origin": "py-cffi", + "dependencies": [ + "py2-cparser", + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libpython2.7.so.1.0" + ] + }, + "farstream0.1": { + "versions": { + "0.1.2-r2": 1545069353 + }, + "origin": "farstream0.1", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstnetbuffer-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgstrtp-0.10.so.0", + "so:libnice.so.10" + ], + "provides": [ + "so:libfarstream-0.1.so.0=0.0.1" + ] + }, + "gtkman": { + "versions": { + "1.0.1-r0": 1545075133 + }, + "origin": "gtkman", + "dependencies": [ + "py-gtk" + ], + "provides": [ + "cmd:gtkman" + ] + }, + "cairo-gobject": { + "versions": { + "1.16.0-r1": 1546948315 + }, + "origin": "cairo", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libcairo-gobject.so.2=2.11600.0" + ] + }, + "syslog-ng-add-contextual-data": { + "versions": { + "3.19.1-r0": 1548543150 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "gdk-pixbuf-doc": { + "versions": { + "2.36.11-r2": 1543253989 + }, + "origin": "gdk-pixbuf" + }, + "uwsgi-transformation_template": { + "versions": { + "2.0.17.1-r0": 1545062213 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "bacula-mysql": { + "versions": { + "9.4.1-r1": 1546944087 + }, + "origin": "bacula", + "dependencies": [ + "bacula", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ], + "provides": [ + "so:libbaccats-mysql-9.4.1.so=0" + ] + }, + "xf86-video-xgixp": { + "versions": { + "1.8.1-r11": 1545075376 + }, + "origin": "xf86-video-xgixp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "graphviz-doc": { + "versions": { + "2.40.1-r1": 1543926740 + }, + "origin": "graphviz" + }, + "py-docutils": { + "versions": { + "0.14-r1": 1543925153 + }, + "origin": "py-docutils", + "dependencies": [ + "py3-docutils", + "py3-docutils=0.14-r1" + ] + }, + "vblade-doc": { + "versions": { + "24-r0": 1543222854 + }, + "origin": "vblade" + }, + "redis-openrc": { + "versions": { + "4.0.12-r0": 1546005788 + }, + "origin": "redis" + }, + "acf-clamav": { + "versions": { + "0.8.0-r2": 1545209739 + }, + "origin": "acf-clamav", + "dependencies": [ + "acf-core", + "clamav" + ] + }, + "nagios-plugins-dummy": { + "versions": { + "2.2.1-r6": 1543933904 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "galculator": { + "versions": { + "2.1.4-r0": 1545076849 + }, + "origin": "galculator", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0" + ], + "provides": [ + "cmd:galculator" + ] + }, + "py-libvirt": { + "versions": { + "4.10.0-r0": 1545291084 + }, + "origin": "py-libvirt" + }, + "rhash-dev": { + "versions": { + "1.3.6-r2": 1542820449 + }, + "origin": "rhash", + "dependencies": [ + "rhash-libs=1.3.6-r2" + ] + }, + "openssh-server-pam": { + "versions": { + "7.9_p1-r5": 1556034599 + }, + "origin": "openssh", + "dependencies": [ + "openssh-keygen", + "openssh-server-common", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpam.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:sshd" + ] + }, + "git-gitk": { + "versions": { + "2.20.1-r0": 1545214200 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0", + "tcl", + "tk" + ], + "provides": [ + "cmd:gitk" + ] + }, + "xdriinfo": { + "versions": { + "1.0.5-r1": 1543927864 + }, + "origin": "xdriinfo", + "dependencies": [ + "so:libGL.so.1", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xdriinfo" + ] + }, + "nikto": { + "versions": { + "2.1.6-r0": 1543924830 + }, + "origin": "nikto", + "dependencies": [ + "perl", + "nmap", + "libressl" + ], + "provides": [ + "cmd:nikto.pl" + ] + }, + "pgpool-dev": { + "versions": { + "3.7.5-r0": 1545073842 + }, + "origin": "pgpool", + "dependencies": [ + "pgpool=3.7.5-r0" + ] + }, + "libxxf86dga-dev": { + "versions": { + "1.1.4-r2": 1542845774 + }, + "origin": "libxxf86dga", + "dependencies": [ + "libxxf86dga=1.1.4-r2", + "pc:x11", + "pc:xext", + "pc:xf86dgaproto", + "pkgconfig" + ], + "provides": [ + "pc:xxf86dga=1.1.4" + ] + }, + "weechat-python": { + "versions": { + "2.3-r0": 1545299933 + }, + "origin": "weechat", + "dependencies": [ + "weechat", + "so:libc.musl-x86_64.so.1" + ] + }, + "xz-libs": { + "versions": { + "5.2.4-r0": 1542303285 + }, + "origin": "xz", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblzma.so.5=5.2.4" + ] + }, + "gummiboot": { + "versions": { + "48.1-r0": 1545073320 + }, + "origin": "gummiboot", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:gummiboot" + ] + }, + "py3-talloc": { + "versions": { + "2.1.14-r0": 1543220637 + }, + "origin": "talloc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0", + "so:libtalloc.so.2" + ], + "provides": [ + "so:libpytalloc-util.cpython-36m-x86-64-linux-gnu.so.2=2.1.14" + ] + }, + "ortp-dev": { + "versions": { + "0.25.0-r0": 1543077281 + }, + "origin": "ortp", + "dependencies": [ + "ortp=0.25.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:ortp=0.25.0" + ] + }, + "uwsgi-forkptyrouter": { + "versions": { + "2.0.17.1-r0": 1545062200 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "uwsgi-corerouter", + "so:libc.musl-x86_64.so.1" + ] + }, + "lighttpd-mod_auth": { + "versions": { + "1.4.52-r0": 1545207630 + }, + "origin": "lighttpd", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libice": { + "versions": { + "1.0.9-r3": 1542824333 + }, + "origin": "libice", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libICE.so.6=6.3.0" + ] + }, + "perl-test2-plugin-nowarnings-doc": { + "versions": { + "0.06-r0": 1542973052 + }, + "origin": "perl-test2-plugin-nowarnings" + }, + "pixman-dev": { + "versions": { + "0.34.0-r6": 1542822838 + }, + "origin": "pixman", + "dependencies": [ + "pixman=0.34.0-r6", + "pkgconfig" + ], + "provides": [ + "pc:pixman-1=0.34.0" + ] + }, + "libiec61883": { + "versions": { + "1.2.0-r2": 1543999349 + }, + "origin": "libiec61883", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libraw1394.so.11" + ], + "provides": [ + "so:libiec61883.so.0=0.1.1" + ] + }, + "ip6tables": { + "versions": { + "1.6.2-r1": 1545062496 + }, + "origin": "iptables", + "dependencies": [ + "iptables", + "iptables=1.6.2-r1", + "so:libc.musl-x86_64.so.1", + "so:libxtables.so.12" + ] + }, + "tinyxml2-dev": { + "versions": { + "7.0.1-r0": 1545061051 + }, + "origin": "tinyxml2", + "dependencies": [ + "pkgconfig", + "tinyxml2=7.0.1-r0" + ], + "provides": [ + "pc:tinyxml2=7.0.1" + ] + }, + "py2-paramiko": { + "versions": { + "2.4.2-r0": 1545216487 + }, + "origin": "py-paramiko", + "dependencies": [ + "py2-asn1", + "py2-cryptography", + "py2-bcrypt", + "py2-pynacl", + "python2" + ] + }, + "neon-doc": { + "versions": { + "0.30.2-r5": 1542883669 + }, + "origin": "neon" + }, + "proxychains-ng": { + "versions": { + "4.13-r0": 1545116587 + }, + "origin": "proxychains-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libproxychains4.so=0", + "cmd:proxychains", + "cmd:proxychains4" + ] + }, + "asterisk-tds": { + "versions": { + "15.7.1-r0": 1546247584 + }, + "origin": "asterisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsybdb.so.5" + ] + }, + "libcrystalhd": { + "versions": { + "20130708-r2": 1543924960 + }, + "origin": "libcrystalhd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libcrystalhd.so.3=3.6" + ] + }, + "libisofs-dev": { + "versions": { + "1.4.8-r0": 1544797287 + }, + "origin": "libisofs", + "dependencies": [ + "libisofs=1.4.8-r0", + "pkgconfig" + ], + "provides": [ + "pc:libisofs-1=1.4.8" + ] + }, + "augeas-libs": { + "versions": { + "1.11.0-r0": 1542924581 + }, + "origin": "augeas", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "so:libaugeas.so.0=0.24.1", + "so:libfa.so.1=1.5.2" + ] + }, + "libsigc++-doc": { + "versions": { + "2.10.0-r1": 1543077170 + }, + "origin": "libsigc++" + }, + "gtk2-xfce-engine": { + "versions": { + "3.2.0-r2": 1545292906 + }, + "origin": "gtk-xfce-engine", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "perl-encode-locale-doc": { + "versions": { + "1.05-r1": 1542821769 + }, + "origin": "perl-encode-locale" + }, + "qt-config": { + "versions": { + "4.8.7-r11": 1545075087 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtGui.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:qtconfig" + ] + }, + "lua5.2-alt-getopt": { + "versions": { + "0.7-r8": 1544799006 + }, + "origin": "lua-alt-getopt" + }, + "seabios-bin": { + "versions": { + "1.11.0-r0": 1543936026 + }, + "origin": "seabios" + }, + "perl-javascript-minifier-xs-doc": { + "versions": { + "0.11-r3": 1543998975 + }, + "origin": "perl-javascript-minifier-xs" + }, + "haproxy-doc": { + "versions": { + "1.8.12-r1": 1545208999 + }, + "origin": "haproxy" + }, + "libsexy-dev": { + "versions": { + "0.1.11-r8": 1544001178 + }, + "origin": "libsexy", + "dependencies": [ + "libsexy=0.1.11-r8", + "pkgconfig" + ], + "provides": [ + "pc:libsexy=0.1.11" + ] + }, + "lua5.2-discount": { + "versions": { + "1.2.10.1-r4": 1545209121 + }, + "origin": "lua-discount", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "sysfsutils-dev": { + "versions": { + "2.1.0-r8": 1542924684 + }, + "origin": "sysfsutils", + "dependencies": [ + "sysfsutils=2.1.0-r8" + ] + }, + "perl-image-exiftool": { + "versions": { + "11.23-r0": 1545835015 + }, + "origin": "perl-image-exiftool", + "dependencies": [ + "perl" + ] + }, + "py3-rsa": { + "versions": { + "3.4.2-r2": 1545116593 + }, + "origin": "py-rsa", + "dependencies": [ + "py3-asn1", + "python3" + ], + "provides": [ + "cmd:pyrsa-decrypt", + "cmd:pyrsa-decrypt-bigfile", + "cmd:pyrsa-encrypt", + "cmd:pyrsa-encrypt-bigfile", + "cmd:pyrsa-keygen", + "cmd:pyrsa-priv2pub", + "cmd:pyrsa-sign", + "cmd:pyrsa-verify" + ] + }, + "libacl": { + "versions": { + "2.2.52-r5": 1542304043 + }, + "origin": "acl", + "dependencies": [ + "so:libattr.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libacl.so.1=1.1.0" + ] + }, + "libxshmfence-dev": { + "versions": { + "1.3-r0": 1542900235 + }, + "origin": "libxshmfence", + "dependencies": [ + "linux-headers", + "libxshmfence=1.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:xshmfence=1.3" + ] + }, + "lua5.2-doc": { + "versions": { + "5.2.4-r7": 1542304842 + }, + "origin": "lua5.2" + }, + "perl-test-warn-doc": { + "versions": { + "0.36-r0": 1543242188 + }, + "origin": "perl-test-warn" + }, + "squid-lang-ca": { + "versions": { + "4.4-r1": 1545216323 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "libvirt-qemu": { + "versions": { + "4.10.0-r1": 1547051658 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-daemon=4.10.0-r1", + "libvirt-common-drivers=4.10.0-r1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgnutls.so.30", + "so:libintl.so.8", + "so:libvirt.so.0", + "so:libxml2.so.2" + ] + }, + "ppp-atm": { + "versions": { + "2.4.7-r6": 1543999021 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "haproxy": { + "versions": { + "1.8.12-r1": 1545208999 + }, + "origin": "haproxy", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:liblua-5.3.so.0", + "so:libpcre.so.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:haproxy" + ] + }, + "libetpan-doc": { + "versions": { + "1.9.2-r0": 1548063823 + }, + "origin": "libetpan" + }, + "libsndfile-doc": { + "versions": { + "1.0.28-r8": 1555066604 + }, + "origin": "libsndfile" + }, + "darkice-doc": { + "versions": { + "1.3-r0": 1545302358 + }, + "origin": "darkice" + }, + "perl-class-data-inheritable-doc": { + "versions": { + "0.08-r0": 1542845801 + }, + "origin": "perl-class-data-inheritable" + }, + "py2-flask-oauthlib": { + "versions": { + "0.9.5-r0": 1545164577 + }, + "origin": "py-flask-oauthlib", + "dependencies": [ + "py2-flask", + "py2-requests-oauthlib", + "python2" + ] + }, + "py2-gflags": { + "versions": { + "3.1.1-r1": 1545208598 + }, + "origin": "py-gflags", + "dependencies": [ + "python2" + ] + }, + "perl-crypt-openssl-guess-doc": { + "versions": { + "0.11-r0": 1543925837 + }, + "origin": "perl-crypt-openssl-guess" + }, + "net-snmp-libs": { + "versions": { + "5.8-r0": 1543923350 + }, + "origin": "net-snmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ], + "provides": [ + "so:libnetsnmp.so.35=35.0.0" + ] + }, + "tk": { + "versions": { + "8.6.9-r0": 1545915089 + }, + "origin": "tk", + "dependencies": [ + "so:libX11.so.6", + "so:libXft.so.2", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libtcl8.6.so" + ], + "provides": [ + "so:libtk8.6.so=0", + "cmd:wish", + "cmd:wish8.6" + ] + }, + "py-certifi": { + "versions": { + "2018.4.16-r0": 1542825015 + }, + "origin": "py-certifi" + }, + "dosfstools-doc": { + "versions": { + "4.1-r1": 1545163464 + }, + "origin": "dosfstools" + }, + "graphite2": { + "versions": { + "1.3.12-r1": 1542823993 + }, + "origin": "graphite2", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgraphite2.so.3=3.2.1" + ] + }, + "nfdump-dbg": { + "versions": { + "1.6.17-r0": 1545223023 + }, + "origin": "nfdump" + }, + "libpri-dev": { + "versions": { + "1.6.0-r0": 1543932622 + }, + "origin": "libpri", + "dependencies": [ + "libpri=1.6.0-r0" + ] + }, + "libssh2-doc": { + "versions": { + "1.8.2-r0": 1553681860 + }, + "origin": "libssh2" + }, + "llvm5-test-utils": { + "versions": { + "5.0.2-r0": 1546874981 + }, + "origin": "llvm5", + "dependencies": [ + "python2", + "py-setuptools", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "llvm-test-utils=5.0.2-r0", + "lit=0.6.0-r0", + "cmd:lit" + ] + }, + "tig-doc": { + "versions": { + "2.4.1-r0": 1545216500 + }, + "origin": "tig" + }, + "iwlwifi-5150-ucode-doc": { + "versions": { + "8.24.2.2-r1": 1545208930 + }, + "origin": "iwlwifi-5150-ucode" + }, + "automake-doc": { + "versions": { + "1.16.1-r0": 1542301304 + }, + "origin": "automake" + }, + "libwebsockets-dev": { + "versions": { + "3.1.0-r0": 1545856933 + }, + "origin": "libwebsockets", + "dependencies": [ + "libwebsockets=3.1.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libwebsockets=3.1.0", + "pc:libwebsockets_static=3.1.0" + ] + }, + "py3-munkres": { + "versions": { + "1.0.12-r0": 1545292846 + }, + "origin": "py-munkres", + "dependencies": [ + "python3" + ] + }, + "mosquitto-doc": { + "versions": { + "1.5.6-r0": 1549970968 + }, + "origin": "mosquitto" + }, + "perl-dbd-odbc": { + "versions": { + "1.60-r0": 1545061020 + }, + "origin": "perl-dbd-odbc", + "dependencies": [ + "perl", + "perl-dbi", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ] + }, + "perl-net-cidr-doc": { + "versions": { + "0.19-r0": 1544000981 + }, + "origin": "perl-net-cidr" + }, + "openvpn-auth-ldap": { + "versions": { + "2.0.3-r6": 1545069395 + }, + "origin": "openvpn-auth-ldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libobjc.so.4" + ], + "provides": [ + "so:openvpn-auth-ldap.so=0" + ] + }, + "libsmartcols": { + "versions": { + "2.33-r0": 1545307436 + }, + "origin": "util-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsmartcols.so.1=1.1.0" + ] + }, + "sgdisk": { + "versions": { + "1.0.4-r0": 1543926506 + }, + "origin": "gptfdisk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpopt.so.0", + "so:libstdc++.so.6", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:sgdisk" + ] + }, + "perl-net-ssleay": { + "versions": { + "1.85-r4": 1545163948 + }, + "origin": "perl-net-ssleay", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "libxp-dev": { + "versions": { + "1.0.3-r2": 1545224134 + }, + "origin": "libxp", + "dependencies": [ + "libxp=1.0.3-r2", + "pc:printproto", + "pc:x11", + "pc:xau", + "pc:xext", + "pkgconfig" + ], + "provides": [ + "pc:xp=1.0.3" + ] + }, + "dmenu-doc": { + "versions": { + "4.8-r0": 1543222729 + }, + "origin": "dmenu" + }, + "checkbashisms": { + "versions": { + "2.18.11-r0": 1545292631 + }, + "origin": "checkbashisms", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:checkbashisms" + ] + }, + "perl-data-optlist-doc": { + "versions": { + "0.110-r0": 1542883239 + }, + "origin": "perl-data-optlist" + }, + "sshpass": { + "versions": { + "1.06-r0": 1542985470 + }, + "origin": "sshpass", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:sshpass" + ] + }, + "luajit-doc": { + "versions": { + "2.1.0_beta3-r4": 1542820928 + }, + "origin": "luajit" + }, + "squid-lang-oc": { + "versions": { + "4.4-r1": 1545216328 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "dpkg-doc": { + "versions": { + "1.19.2-r0": 1545820223 + }, + "origin": "dpkg" + }, + "py2-ipaddress": { + "versions": { + "1.0.22-r0": 1543926514 + }, + "origin": "py-ipaddress", + "dependencies": [ + "python2" + ] + }, + "perl-timedate-doc": { + "versions": { + "2.30-r1": 1543226625 + }, + "origin": "perl-timedate" + }, + "lz4-doc": { + "versions": { + "1.8.3-r2": 1545154434 + }, + "origin": "lz4" + }, + "gmp": { + "versions": { + "6.1.2-r1": 1542301442 + }, + "origin": "gmp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgmp.so.10=10.3.2" + ] + }, + "py3-yaml": { + "versions": { + "4.1-r0": 1544798589 + }, + "origin": "py-yaml" + }, + "uwsgi-python3": { + "versions": { + "2.0.17.1-r0": 1545062215 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "udev-init-scripts-openrc": { + "versions": { + "32-r2": 1542845335 + }, + "origin": "udev-init-scripts" + }, + "perl-http-date": { + "versions": { + "6.02-r1": 1542821759 + }, + "origin": "perl-http-date", + "dependencies": [ + "perl" + ] + }, + "rtpproxy": { + "versions": { + "2.0.0-r4": 1545301085 + }, + "origin": "rtpproxy", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:rtpproxy" + ] + }, + "sipcalc-doc": { + "versions": { + "1.1.6-r0": 1545164533 + }, + "origin": "sipcalc" + }, + "py2-mako": { + "versions": { + "1.0.7-r0": 1543220552 + }, + "origin": "py-mako", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:mako-render" + ] + }, + "py3-flask": { + "versions": { + "1.0.2-r1": 1546978285 + }, + "origin": "py-flask", + "dependencies": [ + "py3-click", + "py3-itsdangerous", + "py3-jinja2", + "py3-werkzeug", + "python3" + ], + "provides": [ + "cmd:flask" + ] + }, + "py-curl": { + "versions": { + "7.43.0.2-r1": 1543935133 + }, + "origin": "py-curl" + }, + "fping-doc": { + "versions": { + "4.1-r0": 1545746006 + }, + "origin": "fping" + }, + "postgrey": { + "versions": { + "1.37-r1": 1545214334 + }, + "origin": "postgrey", + "dependencies": [ + "perl", + "perl-db", + "perl-net-dns", + "perl-net-server", + "perl-io-multiplex", + "perl-net-rblclient", + "perl-parse-syslog", + "perl-netaddr-ip", + "/bin/sh" + ], + "provides": [ + "cmd:postgrey", + "cmd:postgreyreport" + ] + }, + "ttf-freefont": { + "versions": { + "20120503-r1": 1544000554 + }, + "origin": "ttf-freefont", + "dependencies": [ + "fontconfig", + "encodings", + "mkfontdir", + "mkfontscale" + ] + }, + "libebml": { + "versions": { + "1.3.6-r1": 1543226528 + }, + "origin": "libebml", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libebml.so.4=4.0.0" + ] + }, + "rsyslog-mmnormalize": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:liblognorm.so.5" + ] + }, + "py3-imagesize": { + "versions": { + "1.1.0-r0": 1542824997 + }, + "origin": "py-imagesize", + "dependencies": [ + "python3" + ] + }, + "lua5.1-sqlite": { + "versions": { + "0.9.5-r2": 1545214288 + }, + "origin": "lua-sqlite", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "iptraf-ng": { + "versions": { + "1.1.4-r4": 1545214013 + }, + "origin": "iptraf-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libpanelw.so.6" + ], + "provides": [ + "cmd:iptraf-ng", + "cmd:rvnamed-ng" + ] + }, + "perl-net-dns-doc": { + "versions": { + "1.19-r0": 1545061164 + }, + "origin": "perl-net-dns" + }, + "py2-oauthlib": { + "versions": { + "2.0.6-r1": 1543249957 + }, + "origin": "py-oauthlib", + "dependencies": [ + "py2-crypto", + "py2-jwt", + "python2" + ] + }, + "perl-dev": { + "versions": { + "5.26.3-r0": 1543998673 + }, + "origin": "perl", + "dependencies": [ + "perl-utils" + ], + "provides": [ + "cmd:enc2xs", + "cmd:h2xs", + "cmd:perlivp", + "cmd:xsubpp" + ] + }, + "perl-convert-color": { + "versions": { + "0.11-r0": 1545062511 + }, + "origin": "perl-convert-color", + "dependencies": [ + "perl-list-utilsby", + "perl-module-pluggable" + ] + }, + "subversion-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308087 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "cadaver-doc": { + "versions": { + "0.23.3-r4": 1545076658 + }, + "origin": "cadaver" + }, + "opennhrp-doc": { + "versions": { + "0.14.1-r6": 1545069405 + }, + "origin": "opennhrp" + }, + "gparted-lang": { + "versions": { + "0.33.0-r0": 1545746178 + }, + "origin": "gparted", + "dependencies": [ + "e2fsprogs", + "ntfs-3g-progs" + ] + }, + "nginx-mod-http-upstream-fair": { + "versions": { + "1.14.2-r1": 1557179822 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "bwm-ng-doc": { + "versions": { + "0.6.1-r4": 1545073954 + }, + "origin": "bwm-ng" + }, + "mupdf-tools": { + "versions": { + "1.13.0-r2": 1544000576 + }, + "origin": "mupdf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmupdf.so.0", + "so:libmupdfthird.so.0" + ], + "provides": [ + "cmd:mjsgen", + "cmd:mujstest", + "cmd:muraster", + "cmd:mutool" + ] + }, + "radvd-openrc": { + "versions": { + "2.17-r2": 1545254220 + }, + "origin": "radvd" + }, + "libiptcdata-dev": { + "versions": { + "1.0.4-r2": 1543932145 + }, + "origin": "libiptcdata", + "dependencies": [ + "libiptcdata=1.0.4-r2", + "pkgconfig" + ], + "provides": [ + "pc:libiptcdata=1.0.4" + ] + }, + "xz-dev": { + "versions": { + "5.2.4-r0": 1542303283 + }, + "origin": "xz", + "dependencies": [ + "pkgconfig", + "xz-libs=5.2.4-r0" + ], + "provides": [ + "pc:liblzma=5.2.4" + ] + }, + "lua5.1-sql-sqlite3": { + "versions": { + "2.3.5-r2": 1543924398 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "cups-filters-dev": { + "versions": { + "1.21.6-r0": 1545820385 + }, + "origin": "cups-filters", + "dependencies": [ + "cups-filters-libs=1.21.6-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcupsfilters=1.21.6", + "pc:libfontembed=1.21.6" + ] + }, + "xvinfo": { + "versions": { + "1.1.3-r0": 1545301889 + }, + "origin": "xvinfo", + "dependencies": [ + "so:libX11.so.6", + "so:libXv.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xvinfo" + ] + }, + "talloc-dev": { + "versions": { + "2.1.14-r0": 1543220634 + }, + "origin": "talloc", + "dependencies": [ + "pkgconfig", + "py2-talloc=2.1.14-r0", + "py3-talloc=2.1.14-r0", + "talloc=2.1.14-r0" + ], + "provides": [ + "pc:pytalloc-util.cpython-36m-x86_64-linux-gnu=2.1.14", + "pc:pytalloc-util=2.1.14", + "pc:talloc=2.1.14" + ] + }, + "xf86-video-savage": { + "versions": { + "2.3.9-r3": 1545163476 + }, + "origin": "xf86-video-savage", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "openssh-client": { + "versions": { + "7.9_p1-r5": 1556034591 + }, + "origin": "openssh", + "dependencies": [ + "openssh-keygen", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libedit.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:findssl.sh", + "cmd:scp", + "cmd:sftp", + "cmd:ssh", + "cmd:ssh-add", + "cmd:ssh-agent", + "cmd:ssh-copy-id", + "cmd:ssh-keyscan", + "cmd:ssh-pkcs11-helper" + ] + }, + "openbox-dev": { + "versions": { + "3.6.1-r2": 1545207327 + }, + "origin": "openbox", + "dependencies": [ + "libxcursor-dev", + "libxrandr-dev", + "libxinerama-dev", + "startup-notification-dev", + "openbox-libs=3.6.1-r2", + "pc:glib-2.0", + "pc:imlib2", + "pc:librsvg-2.0", + "pc:libxml-2.0", + "pc:pangoxft", + "pc:xft", + "pkgconfig" + ], + "provides": [ + "pc:obrender-3.5=3.6", + "pc:obt-3.5=3.6" + ] + }, + "fail2ban-openrc": { + "versions": { + "0.10.3.1-r2": 1545300388 + }, + "origin": "fail2ban", + "dependencies": [ + "python3", + "iptables", + "ip6tables", + "logrotate" + ] + }, + "libtirpc-dev": { + "versions": { + "1.0.3-r0": 1543223615 + }, + "origin": "libtirpc", + "dependencies": [ + "krb5-dev", + "bsd-compat-headers", + "libtirpc=1.0.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:libtirpc=1.0.3" + ] + }, + "lua5.3-mosquitto": { + "versions": { + "0.2-r1": 1543932701 + }, + "origin": "lua-mosquitto", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libmosquitto.so.1" + ] + }, + "uwsgi-rawrouter": { + "versions": { + "2.0.17.1-r0": 1545062205 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "uwsgi-corerouter", + "so:libc.musl-x86_64.so.1" + ] + }, + "dropbear-scp": { + "versions": { + "2018.76-r2": 1545208976 + }, + "origin": "dropbear", + "dependencies": [ + "!openssh-client", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:scp" + ] + }, + "mwm": { + "versions": { + "2.3.4-r2": 1545223192 + }, + "origin": "motif", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXm.so.4", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mwm", + "cmd:xmbind" + ] + }, + "pam-pgsql": { + "versions": { + "0.7.3.2-r0": 1545068588 + }, + "origin": "pam-pgsql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20", + "so:libpam.so.0", + "so:libpq.so.5" + ] + }, + "nss-tools": { + "versions": { + "3.41-r0": 1545307911 + }, + "origin": "nss", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnspr4.so", + "so:libnss3.so", + "so:libnssutil3.so", + "so:libplc4.so", + "so:libplds4.so", + "so:libsmime3.so", + "so:libssl3.so", + "so:libz.so.1" + ], + "provides": [ + "cmd:certutil", + "cmd:cmsutil", + "cmd:crlutil", + "cmd:modutil", + "cmd:pk12util", + "cmd:shlibsign", + "cmd:signtool", + "cmd:signver", + "cmd:ssltap" + ] + }, + "py2-attrs": { + "versions": { + "18.2.0-r0": 1542824881 + }, + "origin": "py-attrs", + "dependencies": [ + "python2" + ], + "provides": [ + "py-attrs-tools" + ] + }, + "linux-firmware-netronome": { + "versions": { + "20190322-r0": 1554980650 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "crconf": { + "versions": { + "0_pre2-r0": 1545069110 + }, + "origin": "crconf", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:crconf" + ] + }, + "linux-firmware-ositech": { + "versions": { + "20190322-r0": 1554980649 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "perl-io-stringy": { + "versions": { + "2.111-r1": 1542924627 + }, + "origin": "perl-io-stringy", + "dependencies": [ + "perl" + ] + }, + "flex-dev": { + "versions": { + "2.6.4-r1": 1542300945 + }, + "origin": "flex", + "dependencies": [ + "flex", + "flex-libs=2.6.4-r1" + ] + }, + "qemu-system-sh4eb": { + "versions": { + "3.1.0-r3": 1551107305 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-sh4eb" + ] + }, + "gtk-doc": { + "versions": { + "1.29-r0": 1542823716 + }, + "origin": "gtk-doc", + "dependencies": [ + "docbook-xsl", + "python3", + "py3-six", + "pkgconf", + "glib-dev", + "highlight" + ], + "provides": [ + "cmd:gtkdoc-check", + "cmd:gtkdoc-depscan", + "cmd:gtkdoc-fixxref", + "cmd:gtkdoc-mkdb", + "cmd:gtkdoc-mkhtml", + "cmd:gtkdoc-mkhtml2", + "cmd:gtkdoc-mkman", + "cmd:gtkdoc-mkpdf", + "cmd:gtkdoc-rebase", + "cmd:gtkdoc-scan", + "cmd:gtkdoc-scangobj", + "cmd:gtkdocize" + ] + }, + "xmlindent": { + "versions": { + "0.2.17-r0": 1543223068 + }, + "origin": "xmlindent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfl.so.2" + ], + "provides": [ + "cmd:xmlindent" + ] + }, + "mp3splt-gtk": { + "versions": { + "0.9.2-r2": 1545069037 + }, + "origin": "mp3splt-gtk", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstreamer-1.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libmp3splt.so.0" + ], + "provides": [ + "cmd:mp3splt-gtk" + ] + }, + "uwsgi-router_redirect": { + "versions": { + "2.0.17.1-r0": 1545062208 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "mkfontscale": { + "versions": { + "1.1.3-r1": 1542924691 + }, + "origin": "mkfontscale", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libfontenc.so.1", + "so:libfreetype.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:mkfontscale" + ] + }, + "stfl-dev": { + "versions": { + "0.24-r2": 1545075301 + }, + "origin": "stfl", + "dependencies": [ + "pkgconfig", + "stfl=0.24-r2" + ], + "provides": [ + "pc:stfl=0.24" + ] + }, + "gnome-mime-data-lang": { + "versions": { + "2.18.0-r1": 1543934221 + }, + "origin": "gnome-mime-data" + }, + "dbus-libs": { + "versions": { + "1.10.24-r1": 1542824378 + }, + "origin": "dbus", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdbus-1.so.3=3.14.14" + ] + }, + "flac-doc": { + "versions": { + "1.3.2-r2": 1545068441 + }, + "origin": "flac" + }, + "libvirt-uml": { + "versions": { + "4.10.0-r1": 1547051658 + }, + "origin": "libvirt", + "dependencies": [ + "libvirt-daemon=4.10.0-r1", + "libvirt-common-drivers=4.10.0-r1", + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libvirt.so.0" + ] + }, + "qemu-system-cris": { + "versions": { + "3.1.0-r3": 1551107303 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-cris" + ] + }, + "alsa-utils-doc": { + "versions": { + "1.1.8-r0": 1547112821 + }, + "origin": "alsa-utils" + }, + "perl-sys-mmap-doc": { + "versions": { + "0.19-r1": 1545075421 + }, + "origin": "perl-sys-mmap" + }, + "termrec-dev": { + "versions": { + "0.17-r1": 1545249955 + }, + "origin": "termrec", + "dependencies": [ + "termrec=0.17-r1" + ] + }, + "ldapvi-doc": { + "versions": { + "1.7-r10": 1545117995 + }, + "origin": "ldapvi" + }, + "perl-mime-types-doc": { + "versions": { + "2.17-r0": 1543226615 + }, + "origin": "perl-mime-types" + }, + "libasyncns": { + "versions": { + "0.8-r1": 1543226569 + }, + "origin": "libasyncns", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libasyncns.so.0=0.3.1" + ] + }, + "qemu-guest-agent": { + "versions": { + "3.1.0-r3": 1551107299 + }, + "origin": "qemu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0" + ], + "provides": [ + "cmd:qemu-ga" + ] + }, + "librelp-dev": { + "versions": { + "1.3.0-r0": 1548636902 + }, + "origin": "librelp", + "dependencies": [ + "librelp=1.3.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:relp=1.3.0" + ] + }, + "cloog-dev": { + "versions": { + "0.18.4-r2": 1543226589 + }, + "origin": "cloog", + "dependencies": [ + "gmp-dev", + "isl-dev", + "cloog=0.18.4-r2", + "pkgconfig" + ], + "provides": [ + "pc:cloog-isl=0.18.4" + ] + }, + "mpfr3": { + "versions": { + "3.1.5-r1": 1542301549 + }, + "origin": "mpfr3", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10" + ], + "provides": [ + "so:libmpfr.so.4=4.1.5" + ] + }, + "glade3-lang": { + "versions": { + "3.8.5-r5": 1543998726 + }, + "origin": "glade3" + }, + "perdition-doc": { + "versions": { + "2.2-r0": 1545223335 + }, + "origin": "perdition" + }, + "lua5.3-microlight": { + "versions": { + "1.1.1-r2": 1543249973 + }, + "origin": "lua-microlight" + }, + "f2fs-tools-doc": { + "versions": { + "1.12.0-r0": 1546344802 + }, + "origin": "f2fs-tools" + }, + "libpng": { + "versions": { + "1.6.37-r0": 1557129095 + }, + "origin": "libpng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libpng16.so.16=16.37.0" + ] + }, + "exiv2-dev": { + "versions": { + "0.26-r0": 1543246768 + }, + "origin": "exiv2", + "dependencies": [ + "expat-dev", + "zlib-dev", + "exiv2=0.26-r0", + "pkgconfig" + ], + "provides": [ + "pc:exiv2=0.26" + ] + }, + "ilbc": { + "versions": { + "0.0.1-r0": 1545117065 + }, + "origin": "ilbc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libilbc.so.0=0.0.2" + ] + }, + "ivykis": { + "versions": { + "0.42.3-r0": 1548543106 + }, + "origin": "ivykis", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libivykis.so.0=0.5.5" + ] + }, + "linux-firmware-cavium": { + "versions": { + "20190322-r0": 1554980653 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "lua-gversion": { + "versions": { + "0.2.0-r2": 1545292687 + }, + "origin": "lua-gversion", + "dependencies": [ + "lua" + ], + "provides": [ + "lua5.1-gversion=0.2.0-r2", + "lua5.2-gversion=0.2.0-r2", + "lua5.3-gversion=0.2.0-r2" + ] + }, + "gpgme-doc": { + "versions": { + "1.12.0-r3": 1543932321 + }, + "origin": "gpgme" + }, + "libnetfilter_conntrack": { + "versions": { + "1.0.6-r0": 1545165063 + }, + "origin": "libnetfilter_conntrack", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0", + "so:libnfnetlink.so.0" + ], + "provides": [ + "so:libnetfilter_conntrack.so.3=3.6.0" + ] + }, + "ipset-dev": { + "versions": { + "7.1-r0": 1545163449 + }, + "origin": "ipset", + "dependencies": [ + "libmnl-dev", + "ipset=7.1-r0", + "pc:libmnl>=1", + "pkgconfig" + ], + "provides": [ + "pc:libipset=7.1" + ] + }, + "bluez-cups": { + "versions": { + "5.50-r0": 1545822205 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0" + ] + }, + "perl-json-maybexs-doc": { + "versions": { + "1.004000-r0": 1542893316 + }, + "origin": "perl-json-maybexs" + }, + "nagios-plugins-procs": { + "versions": { + "2.2.1-r6": 1543933910 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "dansguardian": { + "versions": { + "2.12.0.3-r4": 1545207099 + }, + "origin": "dansguardian", + "dependencies": [ + "logrotate", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpcreposix.so.0", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:dansguardian" + ] + }, + "py-ecdsa": { + "versions": { + "0.13-r5": 1545301311 + }, + "origin": "py-ecdsa" + }, + "perl-gd-doc": { + "versions": { + "2.67-r0": 1543077251 + }, + "origin": "perl-gd" + }, + "coreutils": { + "versions": { + "8.30-r0": 1542304119 + }, + "origin": "coreutils", + "dependencies": [ + "/bin/sh", + "so:libacl.so.1", + "so:libattr.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:[", + "cmd:b2sum", + "cmd:base32", + "cmd:base64", + "cmd:basename", + "cmd:cat", + "cmd:chcon", + "cmd:chgrp", + "cmd:chmod", + "cmd:chown", + "cmd:chroot", + "cmd:cksum", + "cmd:comm", + "cmd:coreutils", + "cmd:cp", + "cmd:csplit", + "cmd:cut", + "cmd:date", + "cmd:dd", + "cmd:df", + "cmd:dir", + "cmd:dircolors", + "cmd:dirname", + "cmd:du", + "cmd:echo", + "cmd:env", + "cmd:expand", + "cmd:expr", + "cmd:factor", + "cmd:false", + "cmd:fmt", + "cmd:fold", + "cmd:groups", + "cmd:head", + "cmd:hostid", + "cmd:id", + "cmd:install", + "cmd:join", + "cmd:link", + "cmd:ln", + "cmd:logname", + "cmd:ls", + "cmd:md5sum", + "cmd:mkdir", + "cmd:mkfifo", + "cmd:mknod", + "cmd:mktemp", + "cmd:mv", + "cmd:nice", + "cmd:nl", + "cmd:nohup", + "cmd:nproc", + "cmd:numfmt", + "cmd:od", + "cmd:paste", + "cmd:pathchk", + "cmd:pinky", + "cmd:pr", + "cmd:printenv", + "cmd:printf", + "cmd:ptx", + "cmd:pwd", + "cmd:readlink", + "cmd:realpath", + "cmd:rm", + "cmd:rmdir", + "cmd:runcon", + "cmd:seq", + "cmd:sha1sum", + "cmd:sha224sum", + "cmd:sha256sum", + "cmd:sha384sum", + "cmd:sha512sum", + "cmd:shred", + "cmd:shuf", + "cmd:sleep", + "cmd:sort", + "cmd:split", + "cmd:stat", + "cmd:stdbuf", + "cmd:stty", + "cmd:sum", + "cmd:sync", + "cmd:tac", + "cmd:tail", + "cmd:tee", + "cmd:test", + "cmd:timeout", + "cmd:touch", + "cmd:tr", + "cmd:true", + "cmd:truncate", + "cmd:tsort", + "cmd:tty", + "cmd:uname", + "cmd:unexpand", + "cmd:uniq", + "cmd:unlink", + "cmd:users", + "cmd:vdir", + "cmd:wc", + "cmd:who", + "cmd:whoami", + "cmd:yes" + ] + }, + "py2-redis": { + "versions": { + "2.10.6-r0": 1545301343 + }, + "origin": "py-redis", + "dependencies": [ + "python2" + ] + }, + "cyrus-sasl-login": { + "versions": { + "2.1.27-r1": 1550353528 + }, + "origin": "cyrus-sasl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "ttf-ubuntu-font-family": { + "versions": { + "0.83-r0": 1545300911 + }, + "origin": "ttf-ubuntu-font-family" + }, + "tevent-dev": { + "versions": { + "0.9.37-r1": 1543933085 + }, + "origin": "tevent", + "dependencies": [ + "pc:talloc", + "pkgconfig", + "tevent=0.9.37-r1" + ], + "provides": [ + "pc:tevent=0.9.37" + ] + }, + "pangox-compat": { + "versions": { + "0.0.2-r0": 1544001260 + }, + "origin": "pangox-compat", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpango-1.0.so.0" + ], + "provides": [ + "so:libpangox-1.0.so.0=0.0.0" + ] + }, + "py2-tdb": { + "versions": { + "1.3.16-r0": 1543933253 + }, + "origin": "tdb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libtdb.so.1" + ], + "provides": [ + "py-tdb=1.3.16-r0" + ] + }, + "perl-netaddr-ip-doc": { + "versions": { + "4.079-r1": 1542925007 + }, + "origin": "perl-netaddr-ip" + }, + "py-urllib3": { + "versions": { + "1.22-r0": 1542825025 + }, + "origin": "py-urllib3" + }, + "py2-future": { + "versions": { + "0.17.0-r0": 1545213721 + }, + "origin": "py-future", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:futurize", + "cmd:pasteurize" + ] + }, + "mariadb-plugin-rocksdb": { + "versions": { + "10.3.13-r1": 1557431736 + }, + "origin": "mariadb", + "dependencies": [ + "mariadb=10.3.13-r1", + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ] + }, + "tmux": { + "versions": { + "2.8-r0": 1546590689 + }, + "origin": "tmux", + "dependencies": [ + "ncurses-terminfo", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:tmux" + ] + }, + "grub": { + "versions": { + "2.02-r14": 1548432370 + }, + "origin": "grub", + "dependencies": [ + "mkinitfs", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02", + "so:libfreetype.so.6", + "so:liblzma.so.5" + ], + "provides": [ + "cmd:grub-bios-setup", + "cmd:grub-editenv", + "cmd:grub-file", + "cmd:grub-fstest", + "cmd:grub-glue-efi", + "cmd:grub-install", + "cmd:grub-kbdcomp", + "cmd:grub-macbless", + "cmd:grub-menulst2cfg", + "cmd:grub-mkconfig", + "cmd:grub-mkfont", + "cmd:grub-mkimage", + "cmd:grub-mklayout", + "cmd:grub-mknetdir", + "cmd:grub-mkpasswd-pbkdf2", + "cmd:grub-mkrelpath", + "cmd:grub-mkrescue", + "cmd:grub-mkstandalone", + "cmd:grub-ofpathname", + "cmd:grub-probe", + "cmd:grub-reboot", + "cmd:grub-render-label", + "cmd:grub-script-check", + "cmd:grub-set-default", + "cmd:grub-sparc64-setup", + "cmd:grub-syslinux2cfg" + ] + }, + "libxi-dev": { + "versions": { + "1.7.9-r2": 1543228125 + }, + "origin": "libxi", + "dependencies": [ + "libxi=1.7.9-r2", + "pc:inputproto", + "pc:x11", + "pc:xext", + "pc:xfixes", + "pkgconfig" + ], + "provides": [ + "pc:xi=1.7.9" + ] + }, + "py-factory-boy": { + "versions": { + "2.11.1-r0": 1543253882 + }, + "origin": "py-factory-boy" + }, + "libmount": { + "versions": { + "2.33-r0": 1545307436 + }, + "origin": "util-linux", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmount.so.1=1.1.0" + ] + }, + "perl-test-sharedfork-doc": { + "versions": { + "0.35-r0": 1543249822 + }, + "origin": "perl-test-sharedfork" + }, + "acf-core": { + "versions": { + "0.21.1-r1": 1543246773 + }, + "origin": "acf-core", + "dependencies": [ + "acf-jquery", + "acf-lib", + "acf-skins", + "haserl-lua5.2", + "lua5.2", + "lua5.2-posix", + "lua5.2-md5", + "lua-json4", + "lua5.2-subprocess", + "/bin/sh" + ], + "provides": [ + "cmd:acf-cli", + "cmd:acfpasswd" + ] + }, + "lua5.2-ossl": { + "versions": { + "20180708-r2": 1545076448 + }, + "origin": "lua-ossl", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "libcanberra-gtk3": { + "versions": { + "0.30-r2": 1545118059 + }, + "origin": "libcanberra", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcanberra.so.0", + "so:libgdk-3.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0" + ], + "provides": [ + "so:libcanberra-gtk3.so.0=0.1.9", + "cmd:canberra-gtk-play" + ] + }, + "avahi-ui-gtk3": { + "versions": { + "0.6.31-r7": 1545163747 + }, + "origin": "avahi-ui", + "dependencies": [ + "so:libavahi-client.so.3", + "so:libavahi-common.so.3", + "so:libavahi-glib.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libgdk-3.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8" + ], + "provides": [ + "so:libavahi-ui-gtk3.so.0=0.1.4" + ] + }, + "aspell-lang": { + "versions": { + "0.60.6.1-r13": 1542965830 + }, + "origin": "aspell" + }, + "hexchat-python": { + "versions": { + "2.14.2-r0": 1545746059 + }, + "origin": "hexchat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libpython3.6m.so.1.0" + ] + }, + "libva": { + "versions": { + "2.2.0-r0": 1542900261 + }, + "origin": "libva", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXfixes.so.3", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2" + ], + "provides": [ + "so:libva-drm.so.2=2.200.0", + "so:libva-x11.so.2=2.200.0", + "so:libva.so.2=2.200.0" + ] + }, + "lynx-doc": { + "versions": { + "2.8.8_p2-r8": 1545154517 + }, + "origin": "lynx" + }, + "boost-contract": { + "versions": { + "1.67.0-r2": 1542823631 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_system-mt.so.1.67.0", + "so:libboost_system.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_contract-mt.so.1.67.0=1.67.0", + "so:libboost_contract.so.1.67.0=1.67.0" + ] + }, + "font-bh-ttf": { + "versions": { + "1.0.3-r0": 1545213976 + }, + "origin": "font-bh-ttf", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "uwsgi-router_hash": { + "versions": { + "2.0.17.1-r0": 1545062206 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "acct": { + "versions": { + "6.6.4-r0": 1545209152 + }, + "origin": "acct", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ac", + "cmd:accton", + "cmd:dump-acct", + "cmd:dump-utmp", + "cmd:last", + "cmd:lastcomm", + "cmd:sa" + ] + }, + "gamin": { + "versions": { + "0.1.10-r10": 1543928476 + }, + "origin": "gamin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "so:libfam.so.0=0.0.0", + "so:libgamin-1.so.0=0.1.10" + ] + }, + "ipptool": { + "versions": { + "2.2.10-r0": 1545910846 + }, + "origin": "cups", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2" + ], + "provides": [ + "cmd:ipptool" + ] + }, + "perl-encode-hanextra-doc": { + "versions": { + "0.23-r2": 1545075147 + }, + "origin": "perl-encode-hanextra" + }, + "snmptt": { + "versions": { + "1.4-r0": 1542924637 + }, + "origin": "snmptt", + "dependencies": [ + "perl", + "perl-config-inifiles", + "perl-list-moreutils", + "/bin/sh" + ], + "provides": [ + "cmd:snmptt", + "cmd:snmpttconvert", + "cmd:snmpttconvertmib", + "cmd:snmptthandler" + ] + }, + "mesa-xatracker": { + "versions": { + "18.1.7-r2": 1554455971 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_freedreno.so.1", + "so:libdrm_nouveau.so.2", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libxatracker.so.2=2.3.0" + ] + }, + "txt2man-doc": { + "versions": { + "1.6.0-r0": 1545076642 + }, + "origin": "txt2man" + }, + "nspr": { + "versions": { + "4.20-r0": 1545207862 + }, + "origin": "nspr", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnspr4.so=0", + "so:libplc4.so=0", + "so:libplds4.so=0" + ] + }, + "libpcap-doc": { + "versions": { + "1.9.0-r1": 1545905178 + }, + "origin": "libpcap" + }, + "iso-codes": { + "versions": { + "4.1-r0": 1545257107 + }, + "origin": "iso-codes" + }, + "ncurses5-libs": { + "versions": { + "5.9-r1": 1545292735 + }, + "origin": "ncurses5", + "dependencies": [ + "ncurses-terminfo-base", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libform.so.5=5.9", + "so:libmenu.so.5=5.9", + "so:libncurses.so.5=5.9", + "so:libpanel.so.5=5.9" + ] + }, + "icecast-doc": { + "versions": { + "2.4.4-r1": 1543934499 + }, + "origin": "icecast" + }, + "dovecot-pop3d": { + "versions": { + "2.3.6-r0": 1557134097 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libdovecot-login.so.0", + "so:libdovecot-storage.so.0", + "so:libdovecot.so.0" + ] + }, + "qemu-mipsn32": { + "versions": { + "3.1.0-r3": 1551107301 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-mipsn32" + ] + }, + "perl-importer": { + "versions": { + "0.025-r0": 1542972984 + }, + "origin": "perl-importer", + "dependencies": [ + "perl" + ] + }, + "ccache-doc": { + "versions": { + "3.5.1-r0": 1547294261 + }, + "origin": "ccache" + }, + "ncdu": { + "versions": { + "1.13-r0": 1545154410 + }, + "origin": "ncdu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:ncdu" + ] + }, + "libarchive-tools": { + "versions": { + "3.3.2-r4": 1542820311 + }, + "origin": "libarchive", + "dependencies": [ + "so:libacl.so.1", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libexpat.so.1", + "so:liblz4.so.1", + "so:liblzma.so.5", + "so:libz.so.1" + ], + "provides": [ + "cmd:bsdcat", + "cmd:bsdcpio", + "cmd:bsdtar" + ] + }, + "boost": { + "versions": { + "1.67.0-r2": 1542823634 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_chrono-mt.so.1.67.0", + "so:libboost_chrono.so.1.67.0", + "so:libboost_filesystem-mt.so.1.67.0", + "so:libboost_filesystem.so.1.67.0", + "so:libboost_regex-mt.so.1.67.0", + "so:libboost_regex.so.1.67.0", + "so:libboost_system-mt.so.1.67.0", + "so:libboost_system.so.1.67.0", + "so:libboost_thread-mt.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libboost_locale-mt.so.1.67.0=1.67.0", + "so:libboost_log-mt.so.1.67.0=1.67.0", + "so:libboost_log.so.1.67.0=1.67.0", + "so:libboost_log_setup-mt.so.1.67.0=1.67.0", + "so:libboost_log_setup.so.1.67.0=1.67.0", + "so:libboost_stacktrace_basic-mt.so.1.67.0=1.67.0", + "so:libboost_stacktrace_basic.so.1.67.0=1.67.0", + "so:libboost_stacktrace_noop-mt.so.1.67.0=1.67.0", + "so:libboost_stacktrace_noop.so.1.67.0=1.67.0", + "so:libboost_timer-mt.so.1.67.0=1.67.0", + "so:libboost_timer.so.1.67.0=1.67.0", + "so:libboost_type_erasure-mt.so.1.67.0=1.67.0", + "so:libboost_type_erasure.so.1.67.0=1.67.0", + "cmd:b2", + "cmd:bcp", + "cmd:bjam" + ] + }, + "x264": { + "versions": { + "20180304-r1": 1545299804 + }, + "origin": "x264", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:x264" + ] + }, + "py3-musicbrainzngs": { + "versions": { + "0.6-r2": 1543240636 + }, + "origin": "py-musicbrainzngs", + "dependencies": [ + "python3" + ] + }, + "alpine-ipxe": { + "versions": { + "1.0_git20180825-r1": 1545986078 + }, + "origin": "alpine-ipxe" + }, + "gtk-murrine-engine": { + "versions": { + "0.98.2-r2": 1543934246 + }, + "origin": "gtk-murrine-engine", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libpixman-1.so.0" + ] + }, + "krb5-pkinit": { + "versions": { + "1.15.5-r0": 1548094458 + }, + "origin": "krb5", + "dependencies": [ + "krb5-conf", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libcrypto.so.1.1", + "so:libk5crypto.so.3", + "so:libkrb5.so.3", + "so:libkrb5support.so.0" + ] + }, + "squid-lang-ru": { + "versions": { + "4.4-r1": 1545216329 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "boost-container": { + "versions": { + "1.67.0-r2": 1542823630 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_container-mt.so.1.67.0=1.67.0", + "so:libboost_container.so.1.67.0=1.67.0" + ] + }, + "ldb-tools": { + "versions": { + "1.3.8-r0": 1555334661 + }, + "origin": "ldb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libldb.so.1", + "so:libpopt.so.0", + "so:libtalloc.so.2" + ], + "provides": [ + "so:libldb-cmdline.so=0", + "cmd:ldbadd", + "cmd:ldbdel", + "cmd:ldbedit", + "cmd:ldbmodify", + "cmd:ldbrename", + "cmd:ldbsearch" + ] + }, + "json-glib-lang": { + "versions": { + "1.4.4-r0": 1545837175 + }, + "origin": "json-glib" + }, + "harfbuzz-dev": { + "versions": { + "2.2.0-r0": 1545249742 + }, + "origin": "harfbuzz", + "dependencies": [ + "harfbuzz-icu=2.2.0-r0", + "harfbuzz=2.2.0-r0", + "pc:glib-2.0", + "pc:glib-2.0>=2.19.1", + "pc:gobject-2.0", + "pc:graphite2>=1.2.0", + "pc:icu-uc", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libfreetype.so.6", + "so:libglib-2.0.so.0", + "so:libharfbuzz-subset.so.0", + "so:libharfbuzz.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "pc:harfbuzz-gobject=2.2.0", + "pc:harfbuzz-icu=2.2.0", + "pc:harfbuzz-subset=2.2.0", + "pc:harfbuzz=2.2.0", + "cmd:hb-ot-shape-closure", + "cmd:hb-shape", + "cmd:hb-subset", + "cmd:hb-view" + ] + }, + "libgsf-doc": { + "versions": { + "1.14.44-r0": 1543926574 + }, + "origin": "libgsf" + }, + "herbstluftwm-doc": { + "versions": { + "0.7.1-r0": 1545300035 + }, + "origin": "herbstluftwm" + }, + "rsyslog-udpspoof": { + "versions": { + "8.40.0-r3": 1548686792 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libnet.so.1" + ], + "provides": [ + "rsyslog-omudpspoof=8.40.0-r3" + ] + }, + "alpine-ipxe-ipxe_lkrn": { + "versions": { + "1.0_git20180825-r1": 1545986078 + }, + "origin": "alpine-ipxe" + }, + "kamailio-presence": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libsrdb1.so.1", + "so:libsrutils.so.1", + "so:libxml2.so.2" + ] + }, + "logtail": { + "versions": { + "3.21-r0": 1543999486 + }, + "origin": "logtail", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:logtail" + ] + }, + "the_silver_searcher-doc": { + "versions": { + "2.1.0-r2": 1543998777 + }, + "origin": "the_silver_searcher" + }, + "acf-lib-lua5.1": { + "versions": { + "0.10.1-r0": 1542883783 + }, + "origin": "acf-lib" + }, + "squid-lang-ms": { + "versions": { + "4.4-r1": 1545216328 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "gtk-engines": { + "versions": { + "2.21.0-r2": 1545289338 + }, + "origin": "gtk-engines", + "dependencies": [ + "gtk-engines-clearlooks", + "gtk-engines-crux", + "gtk-engines-industrial", + "gtk-engines-mist", + "gtk-engines-redmond", + "gtk-engines-thinice", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "collectd-ping": { + "versions": { + "5.8.0-r3": 1546422678 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:liboping.so.0" + ] + }, + "email2trac": { + "versions": { + "2.5.0-r1": 1543924858 + }, + "origin": "email2trac", + "dependencies": [ + "trac", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:delete_spam", + "cmd:email2trac", + "cmd:run_email2trac" + ] + }, + "xf86-input-mouse-dev": { + "versions": { + "1.9.3-r1": 1545300644 + }, + "origin": "xf86-input-mouse", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xorg-mouse=1.9.3" + ] + }, + "qemu-system-microblazeel": { + "versions": { + "3.1.0-r3": 1551107303 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-microblazeel" + ] + }, + "aumix-doc": { + "versions": { + "2.9.1-r6": 1545069455 + }, + "origin": "aumix" + }, + "libpthread-stubs": { + "versions": { + "0.3-r5": 1542822458 + }, + "origin": "libpthread-stubs", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:pthread-stubs=0.3" + ] + }, + "linux-pam-dev": { + "versions": { + "1.3.0-r0": 1542820658 + }, + "origin": "linux-pam", + "dependencies": [ + "gettext-dev", + "linux-pam=1.3.0-r0" + ] + }, + "perl-list-moreutils": { + "versions": { + "0.419-r1": 1542845626 + }, + "origin": "perl-list-moreutils", + "dependencies": [ + "perl-exporter-tiny" + ] + }, + "zfs-vanilla": { + "versions": { + "4.19.41-r0": 1557400769 + }, + "origin": "zfs-vanilla", + "dependencies": [ + "spl-vanilla", + "linux-vanilla=4.19.41-r0" + ] + }, + "linux-firmware-rockchip": { + "versions": { + "20190322-r0": 1554980649 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "intltool-doc": { + "versions": { + "0.51.0-r4": 1542821919 + }, + "origin": "intltool" + }, + "groff": { + "versions": { + "1.22.3-r2": 1542819580 + }, + "origin": "groff", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:addftinfo", + "cmd:afmtodit", + "cmd:chem", + "cmd:eqn", + "cmd:eqn2graph", + "cmd:gdiffmk", + "cmd:glilypond", + "cmd:gperl", + "cmd:gpinyin", + "cmd:grap2graph", + "cmd:grn", + "cmd:grodvi", + "cmd:groff", + "cmd:groffer", + "cmd:grog", + "cmd:grolbp", + "cmd:grolj4", + "cmd:gropdf", + "cmd:grops", + "cmd:grotty", + "cmd:hpftodit", + "cmd:indxbib", + "cmd:lkbib", + "cmd:lookbib", + "cmd:mmroff", + "cmd:neqn", + "cmd:nroff", + "cmd:pdfmom", + "cmd:pdfroff", + "cmd:pfbtops", + "cmd:pic", + "cmd:pic2graph", + "cmd:post-grohtml", + "cmd:pre-grohtml", + "cmd:preconv", + "cmd:refer", + "cmd:roff2dvi", + "cmd:roff2html", + "cmd:roff2pdf", + "cmd:roff2ps", + "cmd:roff2text", + "cmd:roff2x", + "cmd:soelim", + "cmd:tbl", + "cmd:tfmtodit", + "cmd:troff" + ] + }, + "py3-flask-oauthlib": { + "versions": { + "0.9.5-r0": 1545164578 + }, + "origin": "py-flask-oauthlib", + "dependencies": [ + "py3-flask", + "py3-requests-oauthlib", + "python3" + ] + }, + "perl-archive-zip-doc": { + "versions": { + "1.64-r0": 1544001401 + }, + "origin": "perl-archive-zip" + }, + "ciwiki-doc": { + "versions": { + "2.0.5-r1": 1545292637 + }, + "origin": "ciwiki" + }, + "py3-tz": { + "versions": { + "2018.9-r0": 1548109649 + }, + "origin": "py-tz", + "dependencies": [ + "python3" + ] + }, + "py-gnome-gconf": { + "versions": { + "2.28.1-r5": 1545301290 + }, + "origin": "py-gnome", + "dependencies": [ + "gconf", + "py-gtk", + "so:libc.musl-x86_64.so.1", + "so:libgconf-2.so.4", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libpython2.7.so.1.0" + ] + }, + "unbound": { + "versions": { + "1.8.3-r1": 1555953584 + }, + "origin": "unbound", + "dependencies": [ + "dnssec-root", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libevent-2.1.so.6", + "so:libexpat.so.1", + "so:libssl.so.1.1", + "so:libunbound.so.8" + ], + "provides": [ + "cmd:unbound", + "cmd:unbound-anchor", + "cmd:unbound-checkconf", + "cmd:unbound-control", + "cmd:unbound-control-setup", + "cmd:unbound-host" + ] + }, + "kamailio-db": { + "versions": { + "5.2.0-r1": 1546423169 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so", + "so:libsrdb1.so.1", + "so:libsrdb2.so.1", + "so:libsrutils.so.1", + "so:libtrie.so.1" + ] + }, + "gnome-vfs": { + "versions": { + "2.24.4-r6": 1545299692 + }, + "origin": "gnome-vfs", + "dependencies": [ + "gnome-mime-data", + "/bin/sh", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libdbus-glib-1.so.2", + "so:libfam.so.0", + "so:libgconf-2.so.4", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libgnomevfs-2.so.0=0.2400.4", + "cmd:gnomevfs-cat", + "cmd:gnomevfs-copy", + "cmd:gnomevfs-df", + "cmd:gnomevfs-info", + "cmd:gnomevfs-ls", + "cmd:gnomevfs-mkdir", + "cmd:gnomevfs-monitor", + "cmd:gnomevfs-mv", + "cmd:gnomevfs-rm" + ] + }, + "ruby-dev": { + "versions": { + "2.5.5-r0": 1557164836 + }, + "origin": "ruby", + "dependencies": [ + "ruby=2.5.5-r0", + "gmp-dev", + "pkgconfig", + "ruby-libs=2.5.5-r0" + ], + "provides": [ + "pc:ruby-2.5=2.5.0" + ] + }, + "perl-test-notabs-doc": { + "versions": { + "2.02-r0": 1545069438 + }, + "origin": "perl-test-notabs" + }, + "libxvmc-doc": { + "versions": { + "1.0.10-r2": 1542900306 + }, + "origin": "libxvmc" + }, + "xf86-video-ark": { + "versions": { + "0.7.5-r10": 1543928086 + }, + "origin": "xf86-video-ark", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "xmlrpc-c-client": { + "versions": { + "1.39.13-r0": 1545665213 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libxmlrpc.so.3", + "so:libxmlrpc_util.so.3" + ], + "provides": [ + "so:libxmlrpc_client.so.3=3.39" + ] + }, + "perl-net-ssleay-doc": { + "versions": { + "1.85-r4": 1545163948 + }, + "origin": "perl-net-ssleay" + }, + "libao": { + "versions": { + "1.2.0-r3": 1545067193 + }, + "origin": "libao", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libao.so.4=4.1.0" + ] + }, + "s6": { + "versions": { + "2.7.2.0-r0": 1545062681 + }, + "origin": "s6", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libexecline.so.2.5", + "so:libskarnet.so.2.7" + ], + "provides": [ + "so:libs6.so.2.7=2.7.2.0", + "cmd:s6-accessrules-cdb-from-fs", + "cmd:s6-accessrules-fs-from-cdb", + "cmd:s6-applyuidgid", + "cmd:s6-cleanfifodir", + "cmd:s6-connlimit", + "cmd:s6-envdir", + "cmd:s6-envuidgid", + "cmd:s6-fdholder-daemon", + "cmd:s6-fdholder-delete", + "cmd:s6-fdholder-deletec", + "cmd:s6-fdholder-getdump", + "cmd:s6-fdholder-getdumpc", + "cmd:s6-fdholder-list", + "cmd:s6-fdholder-listc", + "cmd:s6-fdholder-retrieve", + "cmd:s6-fdholder-retrievec", + "cmd:s6-fdholder-setdump", + "cmd:s6-fdholder-setdumpc", + "cmd:s6-fdholder-store", + "cmd:s6-fdholder-storec", + "cmd:s6-fdholder-transferdump", + "cmd:s6-fdholder-transferdumpc", + "cmd:s6-fdholderd", + "cmd:s6-fghack", + "cmd:s6-ftrig-listen", + "cmd:s6-ftrig-listen1", + "cmd:s6-ftrig-notify", + "cmd:s6-ftrig-wait", + "cmd:s6-ftrigrd", + "cmd:s6-ioconnect", + "cmd:s6-ipcclient", + "cmd:s6-ipcserver", + "cmd:s6-ipcserver-access", + "cmd:s6-ipcserver-socketbinder", + "cmd:s6-ipcserverd", + "cmd:s6-log", + "cmd:s6-mkfifodir", + "cmd:s6-notifyoncheck", + "cmd:s6-permafailon", + "cmd:s6-setlock", + "cmd:s6-setsid", + "cmd:s6-setuidgid", + "cmd:s6-softlimit", + "cmd:s6-sudo", + "cmd:s6-sudoc", + "cmd:s6-sudod", + "cmd:s6-supervise", + "cmd:s6-svc", + "cmd:s6-svdt", + "cmd:s6-svdt-clear", + "cmd:s6-svlisten", + "cmd:s6-svlisten1", + "cmd:s6-svok", + "cmd:s6-svscan", + "cmd:s6-svscanctl", + "cmd:s6-svstat", + "cmd:s6-svwait", + "cmd:s6-tai64n", + "cmd:s6-tai64nlocal", + "cmd:s6lockd", + "cmd:ucspilogd" + ] + }, + "directfb": { + "versions": { + "1.7.7-r1": 1543934413 + }, + "origin": "directfb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libfreetype.so.6", + "so:libgcc_s.so.1", + "so:libkms.so.1", + "so:libpng16.so.16", + "so:libstdc++.so.6", + "so:libts.so.0" + ], + "provides": [ + "so:lib++dfb-1.7.so.7=7.0.0", + "so:libdirect-1.7.so.7=7.0.0", + "so:libdirectfb-1.7.so.7=7.0.0", + "so:libfusion-1.7.so.7=7.0.0", + "cmd:dfbdump", + "cmd:dfbdumpinput", + "cmd:dfbfx", + "cmd:dfbg", + "cmd:dfbinfo", + "cmd:dfbinput", + "cmd:dfbinspector", + "cmd:dfblayer", + "cmd:dfbmaster", + "cmd:dfbpenmount", + "cmd:dfbplay", + "cmd:dfbscreen", + "cmd:dfbshow", + "cmd:dfbswitch", + "cmd:directfb-csource", + "cmd:mkdfiff", + "cmd:mkdgiff", + "cmd:mkdgifft" + ] + }, + "py3-unidecode": { + "versions": { + "1.0.23-r0": 1545299544 + }, + "origin": "py-unidecode", + "dependencies": [ + "py3-unidecode", + "python3" + ], + "provides": [ + "cmd:unidecode-3" + ] + }, + "zip-doc": { + "versions": { + "3.0-r7": 1542301519 + }, + "origin": "zip" + }, + "startup-notification": { + "versions": { + "0.12-r3": 1543254000 + }, + "origin": "startup-notification", + "dependencies": [ + "so:libX11-xcb.so.1", + "so:libc.musl-x86_64.so.1", + "so:libxcb-util.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libstartup-notification-1.so.0=0.0.0" + ] + }, + "stalonetray": { + "versions": { + "0.8.3-r0": 1545207381 + }, + "origin": "stalonetray", + "dependencies": [ + "so:libX11.so.6", + "so:libXpm.so.4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:stalonetray" + ] + }, + "lua-lub": { + "versions": { + "1.1.0-r1": 1544000686 + }, + "origin": "lua-lub" + }, + "libuv": { + "versions": { + "1.23.2-r0": 1542820441 + }, + "origin": "libuv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libuv.so.1=1.0.0" + ] + }, + "pytest": { + "versions": { + "4.1.0-r0": 1548112483 + }, + "origin": "pytest", + "dependencies": [ + "py3-pytest", + "py3-pytest=4.1.0-r0" + ] + }, + "stalonetray-doc": { + "versions": { + "0.8.3-r0": 1545207381 + }, + "origin": "stalonetray" + }, + "rrdtool-dev": { + "versions": { + "1.7.0-r0": 1542924799 + }, + "origin": "rrdtool", + "dependencies": [ + "librrd=1.7.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:librrd=1.7.0" + ] + }, + "libogg-dev": { + "versions": { + "1.3.3-r2": 1543925776 + }, + "origin": "libogg", + "dependencies": [ + "libogg=1.3.3-r2", + "pkgconfig" + ], + "provides": [ + "pc:ogg=1.3.3" + ] + }, + "pptpd-doc": { + "versions": { + "1.4.0-r1": 1544000992 + }, + "origin": "pptpd" + }, + "arpwatch": { + "versions": { + "2.1a15-r16": 1544000304 + }, + "origin": "arpwatch", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:arp2ethers", + "cmd:arpfetch", + "cmd:arpsnmp", + "cmd:arpwatch", + "cmd:bihourly.sh", + "cmd:massagevendor" + ] + }, + "apache-mod-fcgid-doc": { + "versions": { + "2.3.9-r2": 1545293006 + }, + "origin": "apache-mod-fcgid" + }, + "libmnl-dev": { + "versions": { + "1.0.4-r0": 1542883675 + }, + "origin": "libmnl", + "dependencies": [ + "linux-headers", + "libmnl=1.0.4-r0", + "pkgconfig" + ], + "provides": [ + "pc:libmnl=1.0.4" + ] + }, + "perl-text-vfile-asdata": { + "versions": { + "0.08-r0": 1545254277 + }, + "origin": "perl-text-vfile-asdata", + "dependencies": [ + "perl-class-accessor-chained" + ] + }, + "cmake": { + "versions": { + "3.13.0-r0": 1542820578 + }, + "origin": "cmake", + "dependencies": [ + "so:libarchive.so.13", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libexpat.so.1", + "so:libformw.so.6", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:librhash.so.0", + "so:libstdc++.so.6", + "so:libuv.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:ccmake", + "cmd:cmake", + "cmd:cpack", + "cmd:ctest" + ] + }, + "lua5.2-maxminddb": { + "versions": { + "0.1-r1": 1543226674 + }, + "origin": "lua-maxminddb", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmaxminddb.so.0" + ] + }, + "qemu-system-m68k": { + "versions": { + "3.1.0-r3": 1551107303 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-m68k" + ] + }, + "perl-test-number-delta": { + "versions": { + "1.06-r0": 1545062326 + }, + "origin": "perl-test-number-delta" + }, + "py2-chardet": { + "versions": { + "3.0.4-r0": 1542825000 + }, + "origin": "py-chardet", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:chardetect" + ] + }, + "py-irc": { + "versions": { + "8.5.1-r0": 1545060751 + }, + "origin": "py-irc" + }, + "perl-inline-c-doc": { + "versions": { + "0.78-r0": 1545067075 + }, + "origin": "perl-inline-c" + }, + "libotr-dev": { + "versions": { + "4.1.1-r1": 1543248414 + }, + "origin": "libotr", + "dependencies": [ + "libgcrypt-dev", + "libotr=4.1.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:libotr=4.1.1" + ] + }, + "py-django-contact-form": { + "versions": { + "1.6-r0": 1545301319 + }, + "origin": "py-django-contact-form", + "dependencies": [ + "py-django" + ] + }, + "xmlrpc-c-doc": { + "versions": { + "1.39.13-r0": 1545665211 + }, + "origin": "xmlrpc-c" + }, + "libiec61883-doc": { + "versions": { + "1.2.0-r2": 1543999348 + }, + "origin": "libiec61883" + }, + "rsync-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308087 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "libcdio": { + "versions": { + "0.94-r2": 1543248377 + }, + "origin": "libcdio", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcdio.so.16=16.0.0", + "so:libiso9660.so.10=10.0.0", + "so:libudf.so.0=0.0.0" + ] + }, + "perl-importer-doc": { + "versions": { + "0.025-r0": 1542972983 + }, + "origin": "perl-importer" + }, + "imagemagick-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "nagios-plugins-mailq": { + "versions": { + "2.2.1-r6": 1543933908 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "perl" + ] + }, + "openpgm": { + "versions": { + "5.2.122-r1": 1543222718 + }, + "origin": "openpgm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpgm-5.2.so.0=0.0.122" + ] + }, + "librtmp": { + "versions": { + "2.4_git20160909-r6": 1545215000 + }, + "origin": "rtmpdump", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libgnutls.so.30", + "so:libhogweed.so.4", + "so:libnettle.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:librtmp.so.1=1" + ] + }, + "perl-font-afm-doc": { + "versions": { + "1.20-r0": 1542983919 + }, + "origin": "perl-font-afm" + }, + "btrfs-progs-libs": { + "versions": { + "4.19.1-r0": 1545665051 + }, + "origin": "btrfs-progs", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ], + "provides": [ + "so:libbtrfs.so.0=0.1", + "so:libbtrfsutil.so.1=1.1.0" + ] + }, + "libxdmcp-doc": { + "versions": { + "1.1.2-r5": 1542822510 + }, + "origin": "libxdmcp" + }, + "collectd-disk": { + "versions": { + "5.8.0-r3": 1546422678 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1" + ] + }, + "libid3tag": { + "versions": { + "0.15.1b-r7": 1542972924 + }, + "origin": "libid3tag", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libid3tag.so.0=0.3.0" + ] + }, + "clamav-scanner": { + "versions": { + "0.100.3-r0": 1555507335 + }, + "origin": "clamav", + "dependencies": [ + "freshclam", + "so:libc.musl-x86_64.so.1", + "so:libclamav.so.7", + "so:libcrypto.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:clambc", + "cmd:clamscan", + "cmd:sigtool" + ] + }, + "nagios-plugins-nagios": { + "versions": { + "2.2.1-r6": 1543933909 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-imaging": { + "versions": { + "1.1.7-r4": 1542893551 + }, + "origin": "py-imaging", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libjpeg.so.8", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:pilconvert.py", + "cmd:pildriver.py", + "cmd:pilfile.py", + "cmd:pilfont.py", + "cmd:pilprint.py" + ] + }, + "ser2net-doc": { + "versions": { + "3.4-r1": 1543999372 + }, + "origin": "ser2net" + }, + "perl-log-any-doc": { + "versions": { + "1.707-r0": 1545208928 + }, + "origin": "perl-log-any" + }, + "lsof": { + "versions": { + "4.91-r0": 1545235377 + }, + "origin": "lsof", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:lsof" + ] + }, + "hwdata-oui": { + "versions": { + "0.318-r0": 1545746063 + }, + "origin": "hwdata" + }, + "fuse-doc": { + "versions": { + "2.9.8-r2": 1542972242 + }, + "origin": "fuse" + }, + "unrar-doc": { + "versions": { + "5.6.8-r0": 1545069402 + }, + "origin": "unrar" + }, + "cmph-dev": { + "versions": { + "2.0-r1": 1544000441 + }, + "origin": "cmph", + "dependencies": [ + "libcmph=2.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:cmph=2.0" + ] + }, + "perl-b-hooks-endofscope": { + "versions": { + "0.24-r0": 1542973108 + }, + "origin": "perl-b-hooks-endofscope", + "dependencies": [ + "perl-sub-exporter-progressive", + "perl-module-runtime", + "perl-module-implementation", + "perl-variable-magic" + ] + }, + "libbsd": { + "versions": { + "0.8.6-r2": 1542822480 + }, + "origin": "libbsd", + "dependencies": [ + "musl>=1.1.16-r22", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libbsd.so.0=0.8.6" + ] + }, + "py-simpleparse": { + "versions": { + "2.2.0-r0": 1545301094 + }, + "origin": "py-simpleparse", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "mc-lang": { + "versions": { + "4.8.22-r0": 1547126443 + }, + "origin": "mc" + }, + "libev-dev": { + "versions": { + "4.25-r0": 1545838252 + }, + "origin": "libev", + "dependencies": [ + "libev=4.25-r0", + "pkgconfig" + ], + "provides": [ + "pc:libev=4.25" + ] + }, + "font-bh-75dpi": { + "versions": { + "1.0.3-r0": 1545207070 + }, + "origin": "font-bh-75dpi", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-net-rblclient-doc": { + "versions": { + "0.5-r3": 1545214327 + }, + "origin": "perl-net-rblclient" + }, + "xcalib": { + "versions": { + "0.8-r0": 1543927704 + }, + "origin": "xcalib", + "dependencies": [ + "so:libX11.so.6", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xcalib" + ] + }, + "lmdb-doc": { + "versions": { + "0.9.23-r0": 1547025175 + }, + "origin": "lmdb" + }, + "py-templayer-doc": { + "versions": { + "1.5.1-r3": 1543935167 + }, + "origin": "py-templayer", + "dependencies": [ + "python2" + ] + }, + "libmemcached-libs": { + "versions": { + "1.0.18-r3": 1543923893 + }, + "origin": "libmemcached", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libsasl2.so.3", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libhashkit.so.2=2.0.0", + "so:libmemcached.so.11=11.0.0", + "so:libmemcachedprotocol.so.0=0.0.0", + "so:libmemcachedutil.so.2=2.0.0" + ] + }, + "openvswitch-dev": { + "versions": { + "2.10.1-r0": 1544000344 + }, + "origin": "openvswitch", + "dependencies": [ + "openssl-dev", + "pkgconfig" + ], + "provides": [ + "pc:libofproto=2.10.1", + "pc:libopenvswitch=2.10.1", + "pc:libovsdb=2.10.1", + "pc:libsflow=2.10.1" + ] + }, + "perl-class-singleton": { + "versions": { + "1.5-r0": 1542972932 + }, + "origin": "perl-class-singleton" + }, + "pax-utils": { + "versions": { + "1.2.3-r0": 1542304833 + }, + "origin": "pax-utils", + "dependencies": [ + "scanelf", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2" + ], + "provides": [ + "cmd:dumpelf", + "cmd:pspax", + "cmd:scanmacho", + "cmd:symtree" + ] + }, + "openresolv-doc": { + "versions": { + "3.9.0-r0": 1545116954 + }, + "origin": "openresolv" + }, + "libevent-dev": { + "versions": { + "2.1.8-r6": 1543223505 + }, + "origin": "libevent", + "dependencies": [ + "python2", + "libevent=2.1.8-r6", + "pkgconfig" + ], + "provides": [ + "pc:libevent=2.1.8-r6", + "pc:libevent_core=2.1.8-r6", + "pc:libevent_extra=2.1.8-r6", + "pc:libevent_openssl=2.1.8-r6", + "pc:libevent_pthreads=2.1.8-r6", + "cmd:event_rpcgen.py" + ] + }, + "xkbcomp-dev": { + "versions": { + "1.4.2-r0": 1542973207 + }, + "origin": "xkbcomp", + "dependencies": [ + "pc:x11", + "pc:xkbfile", + "pc:xproto>=7.0.17", + "pkgconfig" + ], + "provides": [ + "pc:xkbcomp=1.4.2" + ] + }, + "dbus-x11": { + "versions": { + "1.10.24-r1": 1542824378 + }, + "origin": "dbus", + "dependencies": [ + "dbus=1.10.24-r1", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3" + ], + "provides": [ + "cmd:dbus-launch" + ] + }, + "xgamma": { + "versions": { + "1.0.6-r0": 1545214308 + }, + "origin": "xgamma", + "dependencies": [ + "so:libX11.so.6", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xgamma" + ] + }, + "mosh-bash-completion": { + "versions": { + "1.3.2-r7": 1543932001 + }, + "origin": "mosh" + }, + "rsync-openrc": { + "versions": { + "3.1.3-r1": 1542820864 + }, + "origin": "rsync" + }, + "skalibs-dev": { + "versions": { + "2.7.0.0-r0": 1543221682 + }, + "origin": "skalibs", + "dependencies": [ + "skalibs=2.7.0.0-r0" + ] + }, + "libxcb-dev": { + "versions": { + "1.13-r2": 1542822547 + }, + "origin": "libxcb", + "dependencies": [ + "libxau-dev", + "xcb-proto", + "libxcb=1.13-r2", + "pc:pthread-stubs", + "pc:xau>=0.99.2", + "pc:xdmcp", + "pkgconfig" + ], + "provides": [ + "pc:xcb-composite=1.13", + "pc:xcb-damage=1.13", + "pc:xcb-dpms=1.13", + "pc:xcb-dri2=1.13", + "pc:xcb-dri3=1.13", + "pc:xcb-glx=1.13", + "pc:xcb-present=1.13", + "pc:xcb-randr=1.13", + "pc:xcb-record=1.13", + "pc:xcb-render=1.13", + "pc:xcb-res=1.13", + "pc:xcb-screensaver=1.13", + "pc:xcb-shape=1.13", + "pc:xcb-shm=1.13", + "pc:xcb-sync=1.13", + "pc:xcb-xf86dri=1.13", + "pc:xcb-xfixes=1.13", + "pc:xcb-xinerama=1.13", + "pc:xcb-xinput=1.13", + "pc:xcb-xkb=1.13", + "pc:xcb-xtest=1.13", + "pc:xcb-xv=1.13", + "pc:xcb-xvmc=1.13", + "pc:xcb=1.13" + ] + }, + "perl-www-robotrules-doc": { + "versions": { + "6.02-r1": 1542821857 + }, + "origin": "perl-www-robotrules" + }, + "avahi": { + "versions": { + "0.7-r1": 1543925313 + }, + "origin": "avahi", + "dependencies": [ + "/bin/sh", + "so:libavahi-common.so.3", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libdaemon.so.0", + "so:libdbus-1.so.3", + "so:libexpat.so.1" + ], + "provides": [ + "so:libavahi-core.so.7=7.0.2", + "cmd:avahi-daemon", + "cmd:avahi-discover", + "cmd:avahi-dnsconfd" + ] + }, + "uwsgi-zabbix": { + "versions": { + "2.0.17.1-r0": 1545062214 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "libtheora-doc": { + "versions": { + "1.1.1-r13": 1543928530 + }, + "origin": "libtheora" + }, + "dev86": { + "versions": { + "0.16.21-r0": 1543935746 + }, + "origin": "dev86", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ar86", + "cmd:as86", + "cmd:bcc", + "cmd:ld86", + "cmd:nm86", + "cmd:objdump86", + "cmd:size86" + ] + }, + "lua-posixtz": { + "versions": { + "0.5-r1": 1543928315 + }, + "origin": "lua-posixtz" + }, + "libgd": { + "versions": { + "2.2.5-r3": 1554727880 + }, + "origin": "gd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libwebp.so.7", + "so:libz.so.1" + ], + "provides": [ + "so:libgd.so.3=3.0.5" + ] + }, + "xscreensaver-lang": { + "versions": { + "5.40-r0": 1545224088 + }, + "origin": "xscreensaver", + "dependencies": [ + "bc" + ] + }, + "ghostscript-dbg": { + "versions": { + "9.26-r2": 1554362637 + }, + "origin": "ghostscript" + }, + "xf86-video-apm": { + "versions": { + "1.2.5-r11": 1545062640 + }, + "origin": "xf86-video-apm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py-urlgrabber-doc": { + "versions": { + "3.10.1-r1": 1543935134 + }, + "origin": "py-urlgrabber" + }, + "openvpn-auth-pam": { + "versions": { + "2.4.6-r4": 1543223245 + }, + "origin": "openvpn", + "dependencies": [ + "iproute2", + "so:libc.musl-x86_64.so.1", + "so:libpam.so.0" + ] + }, + "lua5.1-optarg": { + "versions": { + "0.2-r1": 1542820938 + }, + "origin": "lua-optarg", + "dependencies": [ + "lua5.1" + ] + }, + "libxvmc-dev": { + "versions": { + "1.0.10-r2": 1542900305 + }, + "origin": "libxvmc", + "dependencies": [ + "libxext-dev", + "libxvmc=1.0.10-r2", + "pc:videoproto", + "pc:x11", + "pc:xext", + "pc:xproto", + "pc:xv", + "pkgconfig" + ], + "provides": [ + "pc:xvmc=1.0.10" + ] + }, + "libunwind": { + "versions": { + "1.2.1-r3": 1545915765 + }, + "origin": "libunwind", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libunwind-coredump.so.0=0.0.0", + "so:libunwind-ptrace.so.0=0.0.0", + "so:libunwind-setjmp.so.0=0.0.0", + "so:libunwind-x86_64.so.8=8.0.1", + "so:libunwind.so.8=8.0.1" + ] + }, + "py-mccabe": { + "versions": { + "0.6.1-r1": 1545060945 + }, + "origin": "py-mccabe" + }, + "openssh-server-common": { + "versions": { + "7.9_p1-r5": 1556034596 + }, + "origin": "openssh" + }, + "truecrypt": { + "versions": { + "7.1a-r3": 1545300290 + }, + "origin": "truecrypt", + "dependencies": [ + "device-mapper", + "so:libc.musl-x86_64.so.1", + "so:libfuse.so.2", + "so:libgcc_s.so.1", + "so:libstdc++.so.6", + "so:libwx_baseu-2.8.so.0", + "so:libwx_gtk2u_adv-2.8.so.0", + "so:libwx_gtk2u_core-2.8.so.0" + ], + "provides": [ + "cmd:truecrypt" + ] + }, + "iptables-dev": { + "versions": { + "1.6.2-r1": 1545062496 + }, + "origin": "iptables", + "dependencies": [ + "linux-headers", + "iptables=1.6.2-r1", + "pkgconfig" + ], + "provides": [ + "pc:libip4tc=1.6.2", + "pc:libip6tc=1.6.2", + "pc:libipq=1.6.2", + "pc:libiptc=1.6.2", + "pc:xtables=1.6.2" + ] + }, + "dtc": { + "versions": { + "1.4.7-r0": 1545300716 + }, + "origin": "dtc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:convert-dtsv0", + "cmd:dtc", + "cmd:dtdiff", + "cmd:fdtdump", + "cmd:fdtget", + "cmd:fdtoverlay", + "cmd:fdtput" + ] + }, + "atk-doc": { + "versions": { + "2.30.0-r0": 1542823743 + }, + "origin": "atk" + }, + "xf86-video-nv-doc": { + "versions": { + "2.1.21-r3": 1545300603 + }, + "origin": "xf86-video-nv" + }, + "dconf-doc": { + "versions": { + "0.26.0-r1": 1545302104 + }, + "origin": "dconf" + }, + "xcb-util-dev": { + "versions": { + "0.4.0-r1": 1542822846 + }, + "origin": "xcb-util", + "dependencies": [ + "libxcb-dev", + "util-macros", + "pkgconfig", + "xcb-util=0.4.0-r1" + ], + "provides": [ + "pc:xcb-atom=0.4.0", + "pc:xcb-aux=0.4.0", + "pc:xcb-event=0.4.0", + "pc:xcb-util=0.4.0" + ] + }, + "lua-augeas": { + "versions": { + "0.1.2-r4": 1543246061 + }, + "origin": "lua-augeas" + }, + "rsyslog-mmanon": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "libepoxy-dev": { + "versions": { + "1.5.3-r0": 1545838421 + }, + "origin": "libepoxy", + "dependencies": [ + "libx11-dev", + "mesa-dev", + "libepoxy=1.5.3-r0", + "pc:egl", + "pc:gl", + "pkgconfig" + ], + "provides": [ + "pc:epoxy=1.5.3" + ] + }, + "nagios-plugins-radius": { + "versions": { + "2.2.1-r6": 1543933911 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libfreeradius-client.so.2" + ] + }, + "perl-file-slurp-tiny-doc": { + "versions": { + "0.004-r0": 1545213664 + }, + "origin": "perl-file-slurp-tiny" + }, + "py-gamin": { + "versions": { + "0.1.10-r10": 1543928476 + }, + "origin": "gamin", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgamin-1.so.0" + ] + }, + "postgresql-bdr-extension": { + "versions": { + "1.0.2-r4": 1543223174 + }, + "origin": "postgresql-bdr-extension", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:bdr_dump", + "cmd:bdr_init_copy", + "cmd:bdr_initial_load" + ] + }, + "x264-libs": { + "versions": { + "20180304-r1": 1545299803 + }, + "origin": "x264", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libx264.so.152=152" + ] + }, + "ferm-openrc": { + "versions": { + "2.4.1-r2": 1545292991 + }, + "origin": "ferm", + "dependencies": [ + "perl", + "iptables" + ] + }, + "gd-dev": { + "versions": { + "2.2.5-r3": 1554727880 + }, + "origin": "gd", + "dependencies": [ + "gd", + "perl", + "libgd=2.2.5-r3", + "pkgconfig" + ], + "provides": [ + "pc:gdlib=2.2.5", + "cmd:bdftogd", + "cmd:gdlib-config" + ] + }, + "apache2-ctl": { + "versions": { + "2.4.39-r0": 1554306834 + }, + "origin": "apache2", + "dependencies": [ + "lynx" + ], + "provides": [ + "cmd:apachectl" + ] + }, + "xf86-video-intel-doc": { + "versions": { + "2.99.917_git20170325-r4": 1545665362 + }, + "origin": "xf86-video-intel" + }, + "dbus-glib-dev": { + "versions": { + "0.108-r1": 1542824780 + }, + "origin": "dbus-glib", + "dependencies": [ + "dbus-glib=0.108-r1", + "pc:dbus-1", + "pc:glib-2.0", + "pc:gobject-2.0", + "pkgconfig" + ], + "provides": [ + "pc:dbus-glib-1=0.108" + ] + }, + "py2-more-itertools": { + "versions": { + "4.1.0-r0": 1542824885 + }, + "origin": "py-more-itertools", + "dependencies": [ + "python2" + ] + }, + "curl-dbg": { + "versions": { + "7.64.0-r1": 1551205305 + }, + "origin": "curl", + "dependencies": [ + "ca-certificates" + ] + }, + "python3-tests": { + "versions": { + "3.6.8-r2": 1554747638 + }, + "origin": "python3" + }, + "perl-regexp-common-doc": { + "versions": { + "2017060201-r0": 1543924763 + }, + "origin": "perl-regexp-common" + }, + "acf-lib-lua5.2": { + "versions": { + "0.10.1-r0": 1542883783 + }, + "origin": "acf-lib" + }, + "perl-html-scrubber-doc": { + "versions": { + "0.17-r0": 1545292678 + }, + "origin": "perl-html-scrubber" + }, + "gettext-dev": { + "versions": { + "0.19.8.1-r4": 1542304412 + }, + "origin": "gettext", + "dependencies": [ + "gettext-asprintf=0.19.8.1-r4", + "gettext-libs=0.19.8.1-r4", + "gettext=0.19.8.1-r4", + "libintl=0.19.8.1-r4" + ] + }, + "kamailio-snmpstats": { + "versions": { + "5.2.0-r1": 1546423170 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libnetsnmp.so.35", + "so:libnetsnmpagent.so.35" + ] + }, + "procps-doc": { + "versions": { + "3.3.15-r0": 1543934061 + }, + "origin": "procps" + }, + "gengetopt-doc": { + "versions": { + "2.22.6-r2": 1545062092 + }, + "origin": "gengetopt" + }, + "ttf-dejavu": { + "versions": { + "2.37-r1": 1545292697 + }, + "origin": "ttf-dejavu", + "dependencies": [ + "fontconfig", + "encodings", + "mkfontdir", + "mkfontscale" + ] + }, + "librelp": { + "versions": { + "1.3.0-r0": 1548636902 + }, + "origin": "librelp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgnutls.so.30" + ], + "provides": [ + "so:librelp.so.0=0.4.0" + ] + }, + "liblockfile": { + "versions": { + "1.14-r0": 1545075168 + }, + "origin": "liblockfile", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:dotlockfile" + ] + }, + "irqbalance-doc": { + "versions": { + "1.5.0-r0": 1545837194 + }, + "origin": "irqbalance" + }, + "libdc1394": { + "versions": { + "2.2.5-r1": 1545249933 + }, + "origin": "libdc1394", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libraw1394.so.11", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libdc1394.so.22=22.2.1", + "cmd:dc1394_reset_bus" + ] + }, + "libxaw-dev": { + "versions": { + "1.0.13-r2": 1542894088 + }, + "origin": "libxaw", + "dependencies": [ + "libxaw=1.0.13-r2", + "pc:x11", + "pc:xext", + "pc:xmu", + "pc:xpm", + "pc:xproto", + "pc:xt", + "pkgconfig" + ], + "provides": [ + "pc:xaw7=1.0.13" + ] + }, + "libart-lgpl": { + "versions": { + "2.3.21-r5": 1542924747 + }, + "origin": "libart-lgpl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libart_lgpl_2.so.2=2.3.21" + ] + }, + "pm-utils": { + "versions": { + "1.4.1-r1": 1543935621 + }, + "origin": "pm-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:on_ac_power", + "cmd:pm-is-supported", + "cmd:pm-powersave" + ] + }, + "py2-monotonic": { + "versions": { + "1.5-r0": 1544000466 + }, + "origin": "py2-monotonic", + "dependencies": [ + "python2" + ] + }, + "xf86-video-apm-doc": { + "versions": { + "1.2.5-r11": 1545062639 + }, + "origin": "xf86-video-apm" + }, + "libdvdcss-doc": { + "versions": { + "1.4.2-r0": 1545838429 + }, + "origin": "libdvdcss" + }, + "recordmydesktop-doc": { + "versions": { + "0.3.8.1-r2": 1545302038 + }, + "origin": "recordmydesktop" + }, + "ack-doc": { + "versions": { + "2.24-r0": 1545224031 + }, + "origin": "ack" + }, + "libxi-doc": { + "versions": { + "1.7.9-r2": 1543228126 + }, + "origin": "libxi" + }, + "py3-gpgme": { + "versions": { + "1.12.0-r3": 1543932322 + }, + "origin": "gpgme", + "dependencies": [ + "gnupg", + "gpgme", + "python3", + "so:libc.musl-x86_64.so.1", + "so:libgpgme.so.11", + "so:libpython3.6m.so.1.0" + ] + }, + "py-flask": { + "versions": { + "1.0.2-r1": 1546978285 + }, + "origin": "py-flask", + "dependencies": [ + "py-click", + "py-itsdangerous", + "py-jinja2", + "py-werkzeug" + ] + }, + "lua-openrc": { + "versions": { + "0.2-r3": 1545066957 + }, + "origin": "lua-openrc" + }, + "perl-module-build": { + "versions": { + "0.4224-r0": 1542304668 + }, + "origin": "perl-module-build", + "provides": [ + "cmd:config_data" + ] + }, + "fcgi-dev": { + "versions": { + "2.4.0-r8": 1543238825 + }, + "origin": "fcgi", + "dependencies": [ + "fcgi++=2.4.0-r8", + "fcgi=2.4.0-r8" + ] + }, + "unzip-doc": { + "versions": { + "6.0-r4": 1542301513 + }, + "origin": "unzip" + }, + "perl-text-vfile-asdata-doc": { + "versions": { + "0.08-r0": 1545254262 + }, + "origin": "perl-text-vfile-asdata" + }, + "perl-image-exiftool-doc": { + "versions": { + "11.23-r0": 1545835014 + }, + "origin": "perl-image-exiftool" + }, + "dhcp": { + "versions": { + "4.4.1-r1": 1543928454 + }, + "origin": "dhcp", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "cmd:dhcpd", + "cmd:omshell" + ] + }, + "xcb-util-image": { + "versions": { + "0.4.0-r1": 1543927901 + }, + "origin": "xcb-util-image", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-shm.so.0", + "so:libxcb-util.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "so:libxcb-image.so.0=0.0.0" + ] + }, + "cgdb": { + "versions": { + "0.7.0-r2": 1545214140 + }, + "origin": "cgdb", + "dependencies": [ + "gdb", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libncursesw.so.6", + "so:libreadline.so.7", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:cgdb" + ] + }, + "perl-test-without-module-doc": { + "versions": { + "0.20-r0": 1542893308 + }, + "origin": "perl-test-without-module" + }, + "perl-crypt-x509-doc": { + "versions": { + "0.51-r0": 1545207011 + }, + "origin": "perl-crypt-x509" + }, + "perl-net-openssh-doc": { + "versions": { + "0.78-r0": 1545995475 + }, + "origin": "perl-net-openssh" + }, + "libgudev": { + "versions": { + "230-r2": 1542845393 + }, + "origin": "libgudev", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libudev.so.1" + ], + "provides": [ + "so:libgudev-1.0.so.0=0.2.0" + ] + }, + "vsftpd-doc": { + "versions": { + "3.0.3-r6": 1544000084 + }, + "origin": "vsftpd" + }, + "imake-doc": { + "versions": { + "1.0.7-r2": 1543932076 + }, + "origin": "imake" + }, + "tzdata-doc": { + "versions": { + "2019a-r0": 1555329220 + }, + "origin": "tzdata" + }, + "vte-doc": { + "versions": { + "0.28.2-r13": 1543927666 + }, + "origin": "vte" + }, + "libfastjson-dbg": { + "versions": { + "0.99.8-r1": 1545060651 + }, + "origin": "libfastjson" + }, + "xf86-video-i128": { + "versions": { + "1.3.6-r11": 1545069417 + }, + "origin": "xf86-video-i128", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "arpwatch-ethercodes": { + "versions": { + "2.1a15-r16": 1544000303 + }, + "origin": "arpwatch" + }, + "wxgtk2.8": { + "versions": { + "2.8.12.1-r4": 1545075288 + }, + "origin": "wxgtk2.8", + "dependencies": [ + "so:libSDL-1.2.so.0", + "so:libSM.so.6", + "so:libXinerama.so.1", + "so:libXxf86vm.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libjpeg.so.8", + "so:libpango-1.0.so.0", + "so:libpng16.so.16", + "so:libstdc++.so.6", + "so:libtiff.so.5", + "so:libwx_baseu-2.8.so.0", + "so:libwx_baseu_xml-2.8.so.0" + ], + "provides": [ + "so:libwx_gtk2u_adv-2.8.so.0=0.8.0", + "so:libwx_gtk2u_aui-2.8.so.0=0.8.0", + "so:libwx_gtk2u_core-2.8.so.0=0.8.0", + "so:libwx_gtk2u_html-2.8.so.0=0.8.0", + "so:libwx_gtk2u_qa-2.8.so.0=0.8.0", + "so:libwx_gtk2u_richtext-2.8.so.0=0.8.0", + "so:libwx_gtk2u_xrc-2.8.so.0=0.8.0" + ] + }, + "clang-doc": { + "versions": { + "5.0.2-r0": 1546873920 + }, + "origin": "clang" + }, + "execline-dev": { + "versions": { + "2.5.0.1-r0": 1543221686 + }, + "origin": "execline", + "dependencies": [ + "execline=2.5.0.1-r0" + ] + }, + "freeradius-client": { + "versions": { + "1.1.7-r2": 1543922359 + }, + "origin": "freeradius-client", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnettle.so.6" + ], + "provides": [ + "so:libfreeradius-client.so.2=2.0.0", + "cmd:login.radius", + "cmd:radacct", + "cmd:radembedded", + "cmd:radexample", + "cmd:radiusclient", + "cmd:radlogin", + "cmd:radstatus" + ] + }, + "monit-doc": { + "versions": { + "5.25.2-r0": 1545858142 + }, + "origin": "monit" + }, + "libgcab": { + "versions": { + "0.7-r2": 1543249784 + }, + "origin": "libgcab", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libz.so.1" + ], + "provides": [ + "so:libgcab-1.0.so.0=0.0.0", + "cmd:gcab" + ] + }, + "acf-iptables": { + "versions": { + "0.7.1-r2": 1545222974 + }, + "origin": "acf-iptables", + "dependencies": [ + "acf-core", + "iptables" + ] + }, + "perl-http-cookies-doc": { + "versions": { + "6.04-r0": 1542821808 + }, + "origin": "perl-http-cookies" + }, + "usbutils": { + "versions": { + "010-r0": 1545075126 + }, + "origin": "usbutils", + "dependencies": [ + "hwdata-usb", + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "cmd:lsusb", + "cmd:lsusb.py", + "cmd:usb-devices", + "cmd:usbhid-dump" + ] + }, + "perl-dbix-searchbuilder": { + "versions": { + "1.67-r1": 1544798721 + }, + "origin": "perl-dbix-searchbuilder", + "dependencies": [ + "perl-class-returnvalue", + "perl-dbi", + "perl-cache-simple-timedexpiry", + "perl-class-accessor", + "perl-clone", + "perl-want", + "perl-dbix-dbschema" + ] + }, + "herbstluftwm-bash-completion": { + "versions": { + "0.7.1-r0": 1545300035 + }, + "origin": "herbstluftwm" + }, + "font-jis-misc": { + "versions": { + "1.0.3-r0": 1544000787 + }, + "origin": "font-jis-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "qemu-system-ppc64": { + "versions": { + "3.1.0-r3": 1551107305 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-ppc64" + ] + }, + "pkgconf": { + "versions": { + "1.6.0-r0": 1547496958 + }, + "origin": "pkgconf", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "pkgconfig=1", + "so:libpkgconf.so.3=3.0.0", + "cmd:pkg-config", + "cmd:pkgconf" + ] + }, + "perl-html-format": { + "versions": { + "2.12-r0": 1545075958 + }, + "origin": "perl-html-format", + "dependencies": [ + "perl-html-tree", + "perl-font-afm" + ] + }, + "rsyslog-mysql": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ], + "provides": [ + "rsyslog-ommysql=8.40.0-r3" + ] + }, + "openssh": { + "versions": { + "7.9_p1-r5": 1556034600 + }, + "origin": "openssh", + "dependencies": [ + "openssh-client", + "openssh-sftp-server", + "openssh-server", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ] + }, + "fossil": { + "versions": { + "2.7-r0": 1545746244 + }, + "origin": "fossil", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libsqlite3.so.0", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:fossil" + ] + }, + "spice-protocol": { + "versions": { + "0.12.14-r0": 1543935749 + }, + "origin": "spice-protocol", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:spice-protocol=0.12.14" + ] + }, + "libcrypto1.1": { + "versions": { + "1.1.1b-r1": 1552660176 + }, + "origin": "openssl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcrypto.so.1.1=1.1" + ] + }, + "open-isns": { + "versions": { + "0.97-r4": 1542883212 + }, + "origin": "open-isns", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libisns.so.0" + ], + "provides": [ + "cmd:isnsadm", + "cmd:isnsd", + "cmd:isnsdd" + ] + }, + "ncurses-doc": { + "versions": { + "6.1_p20190105-r0": 1546948257 + }, + "origin": "ncurses" + }, + "rsyslog-zmq": { + "versions": { + "8.40.0-r3": 1548686792 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1", + "so:libczmq.so.4" + ], + "provides": [ + "rsyslog-imczmq=8.40.0-r3", + "rsyslog-omczmq=8.40.0-r3" + ] + }, + "bluez-obexd": { + "versions": { + "5.50-r0": 1545822206 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libglib-2.0.so.0", + "so:libicalvcal.so.2" + ] + }, + "tiff-dev": { + "versions": { + "4.0.10-r0": 1543921905 + }, + "origin": "tiff", + "dependencies": [ + "zlib-dev", + "libjpeg-turbo-dev", + "libtiffxx=4.0.10-r0", + "pkgconfig", + "tiff=4.0.10-r0" + ], + "provides": [ + "pc:libtiff-4=4.0.10" + ] + }, + "acf-fetchmail": { + "versions": { + "0.9.0-r2": 1545257160 + }, + "origin": "acf-fetchmail", + "dependencies": [ + "acf-core", + "fetchmail" + ] + }, + "lua5.3-doc": { + "versions": { + "5.3.5-r2": 1557162501 + }, + "origin": "lua5.3" + }, + "perl-datetime-locale-doc": { + "versions": { + "1.22-r0": 1543238992 + }, + "origin": "perl-datetime-locale" + }, + "libsodium": { + "versions": { + "1.0.16-r0": 1544000836 + }, + "origin": "libsodium", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libsodium.so.23=23.1.0" + ] + }, + "perl-class-singleton-doc": { + "versions": { + "1.5-r0": 1542972927 + }, + "origin": "perl-class-singleton" + }, + "varnish-dbg": { + "versions": { + "6.1.1-r0": 1545300750 + }, + "origin": "varnish", + "dependencies": [ + "gcc", + "libc-dev", + "libgcc" + ] + }, + "qjson-dev": { + "versions": { + "0.9.0-r0": 1545075111 + }, + "origin": "qjson", + "dependencies": [ + "pc:QtCore", + "pkgconfig", + "qjson=0.9.0-r0" + ], + "provides": [ + "pc:QJson=0.9.0" + ] + }, + "darkstat": { + "versions": { + "3.0.719-r1": 1543223080 + }, + "origin": "darkstat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcap.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:darkstat" + ] + }, + "lua5.2-yaml": { + "versions": { + "1.1.2-r1": 1544000069 + }, + "origin": "lua-yaml", + "dependencies": [ + "lua5.2", + "lua5.2-lub", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-module-build-doc": { + "versions": { + "0.4224-r0": 1542304665 + }, + "origin": "perl-module-build" + }, + "py2-asn1-modules": { + "versions": { + "0.2.2-r0": 1543928877 + }, + "origin": "py-asn1-modules", + "dependencies": [ + "py2-openssl", + "py2-asn1" + ] + }, + "sysstat-doc": { + "versions": { + "11.6.0-r0": 1543922333 + }, + "origin": "sysstat" + }, + "perl-package-deprecationmanager": { + "versions": { + "0.17-r0": 1542845766 + }, + "origin": "perl-package-deprecationmanager", + "dependencies": [ + "perl-list-moreutils", + "perl-sub-install", + "perl-params-util", + "perl-test-fatal", + "perl-test-requires", + "perl-package-stash", + "perl-test-warnings", + "perl-sub-name" + ] + }, + "perl-async-mergepoint-doc": { + "versions": { + "0.04-r0": 1545075379 + }, + "origin": "perl-async-mergepoint" + }, + "boost-dev": { + "versions": { + "1.67.0-r2": 1542823626 + }, + "origin": "boost", + "dependencies": [ + "linux-headers", + "boost-atomic=1.67.0-r2", + "boost-chrono=1.67.0-r2", + "boost-container=1.67.0-r2", + "boost-context=1.67.0-r2", + "boost-contract=1.67.0-r2", + "boost-coroutine=1.67.0-r2", + "boost-date_time=1.67.0-r2", + "boost-fiber=1.67.0-r2", + "boost-filesystem=1.67.0-r2", + "boost-graph=1.67.0-r2", + "boost-iostreams=1.67.0-r2", + "boost-math=1.67.0-r2", + "boost-prg_exec_monitor=1.67.0-r2", + "boost-program_options=1.67.0-r2", + "boost-python3=1.67.0-r2", + "boost-python=1.67.0-r2", + "boost-random=1.67.0-r2", + "boost-regex=1.67.0-r2", + "boost-serialization=1.67.0-r2", + "boost-signals=1.67.0-r2", + "boost-system=1.67.0-r2", + "boost-thread=1.67.0-r2", + "boost-unit_test_framework=1.67.0-r2", + "boost-wave=1.67.0-r2", + "boost-wserialization=1.67.0-r2", + "boost=1.67.0-r2" + ] + }, + "darkhttpd-openrc": { + "versions": { + "1.12-r2": 1543223261 + }, + "origin": "darkhttpd" + }, + "czmq-dev": { + "versions": { + "4.1.1-r0": 1548683467 + }, + "origin": "czmq", + "dependencies": [ + "util-linux-dev", + "zeromq-dev", + "czmq=4.1.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libczmq=4.1.1" + ] + }, + "qemu-system-xtensa": { + "versions": { + "3.1.0-r3": 1551107306 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-xtensa" + ] + }, + "nasm": { + "versions": { + "2.13.03-r0": 1542824085 + }, + "origin": "nasm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:nasm", + "cmd:ndisasm" + ] + }, + "rrdtool-cgi": { + "versions": { + "1.7.0-r0": 1542924806 + }, + "origin": "rrdtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:librrd.so.8" + ], + "provides": [ + "cmd:rrdcgi" + ] + }, + "openldap-doc": { + "versions": { + "2.4.47-r2": 1546016476 + }, + "origin": "openldap" + }, + "lxc-bash-completion": { + "versions": { + "3.1.0-r1": 1549966161 + }, + "origin": "lxc" + }, + "py-country": { + "versions": { + "18.12.8-r0": 1548109642 + }, + "origin": "py-country" + }, + "pmacct-doc": { + "versions": { + "1.7.0-r0": 1545116554 + }, + "origin": "pmacct" + }, + "adwaita-icon-theme": { + "versions": { + "3.31.1-r0": 1545820343 + }, + "origin": "adwaita-icon-theme" + }, + "dovecot-fts-lucene": { + "versions": { + "2.3.6-r0": 1557134099 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libclucene-core.so.1", + "so:libclucene-shared.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:lib21_fts_lucene_plugin.so=0" + ] + }, + "fuse-common": { + "versions": { + "3.2.6-r1": 1548943636 + }, + "origin": "fuse3" + }, + "bonding": { + "versions": { + "2.6-r3": 1545062232 + }, + "origin": "bonding" + }, + "py-gst0.10": { + "versions": { + "0.10.22-r0": 1544000781 + }, + "origin": "py-gst0.10", + "dependencies": [ + "py-gtk", + "py-gobject", + "pc:gstreamer-0.10", + "pc:pygobject-2.0", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstaudio-0.10.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstcontroller-0.10.so.0", + "so:libgstdataprotocol-0.10.so.0", + "so:libgstinterfaces-0.10.so.0", + "so:libgstnet-0.10.so.0", + "so:libgstpbutils-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libgsttag-0.10.so.0", + "so:libgstvideo-0.10.so.0" + ], + "provides": [ + "pc:gst-python-0.10=0.10.22" + ] + }, + "eudev-netifnames": { + "versions": { + "3.2.7-r0": 1542845385 + }, + "origin": "eudev", + "dependencies": [ + "udev-init-scripts" + ] + }, + "py2-babel": { + "versions": { + "2.6.0-r0": 1542824977 + }, + "origin": "py-babel", + "dependencies": [ + "py2-tz", + "python2" + ], + "provides": [ + "cmd:pybabel" + ] + }, + "xf86-video-modesetting": { + "versions": { + "0.9.0-r6": 1544001371 + }, + "origin": "xf86-video-modesetting", + "dependencies": [ + "xorg-server" + ] + }, + "bubblewrap-doc": { + "versions": { + "0.3.1-r0": 1546247686 + }, + "origin": "bubblewrap" + }, + "strace": { + "versions": { + "4.24-r0": 1543928279 + }, + "origin": "strace", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:strace", + "cmd:strace-graph", + "cmd:strace-log-merge" + ] + }, + "openvswitch-test": { + "versions": { + "2.10.1-r0": 1544000344 + }, + "origin": "openvswitch", + "dependencies": [ + "py2-openvswitch=2.10.1-r0", + "py2-twisted", + "so:libc.musl-x86_64.so.1", + "so:libcap-ng.so.0", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:ovs-l3ping", + "cmd:ovs-pcap", + "cmd:ovs-tcpdump", + "cmd:ovs-tcpundump", + "cmd:ovs-test", + "cmd:ovs-testcontroller", + "cmd:ovs-vlan-test" + ] + }, + "py2-gunicorn": { + "versions": { + "19.7.1-r1": 1545214067 + }, + "origin": "py-gunicorn", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:gunicorn", + "cmd:gunicorn_paster" + ] + }, + "libmaxminddb": { + "versions": { + "1.3.2-r0": 1543226667 + }, + "origin": "libmaxminddb", + "dependencies": [ + "curl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmaxminddb.so.0=0.0.7", + "cmd:mmdblookup" + ] + }, + "docbook-xml": { + "versions": { + "4.5-r6": 1542304586 + }, + "origin": "docbook-xml", + "dependencies": [ + "libxml2-utils", + "/bin/sh" + ] + }, + "perl-digest-sha1": { + "versions": { + "2.13-r9": 1542924827 + }, + "origin": "perl-digest-sha1", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-module-build-tiny-doc": { + "versions": { + "0.039-r0": 1543238863 + }, + "origin": "perl-module-build-tiny" + }, + "rsyslog-pmaixforwardedfrom": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "clutter-dev": { + "versions": { + "1.26.2-r2": 1545293146 + }, + "origin": "clutter", + "dependencies": [ + "gdk-pixbuf-dev", + "json-glib-dev", + "atk-dev", + "pango-dev", + "mesa-dev", + "libxcomposite-dev", + "libxi-dev", + "cairo-dev", + "cogl-dev", + "clutter=1.26.2-r2", + "pc:atk", + "pc:atk>=2.5.3", + "pc:cairo-gobject>=1.14.0", + "pc:cogl-1.0>=1.21.2", + "pc:cogl-pango-1.0", + "pc:cogl-path-1.0", + "pc:gio-2.0>=2.44.0", + "pc:json-glib-1.0>=0.12.0", + "pc:pangocairo>=1.30", + "pc:pangoft2", + "pc:x11", + "pc:xcomposite>=0.4", + "pc:xdamage", + "pc:xext", + "pc:xi", + "pkgconfig" + ], + "provides": [ + "pc:cally-1.0=1.26.2", + "pc:clutter-1.0=1.26.2", + "pc:clutter-cogl-1.0=1.26.2", + "pc:clutter-egl-1.0=1.26.2", + "pc:clutter-glx-1.0=1.26.2", + "pc:clutter-x11-1.0=1.26.2" + ] + }, + "linux-firmware-rtl8192e": { + "versions": { + "20190322-r0": 1554980648 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "qemu-i386": { + "versions": { + "3.1.0-r3": 1551107300 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-i386" + ] + }, + "lua5.1-md5": { + "versions": { + "1.2-r3": 1542883857 + }, + "origin": "lua-md5", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libgit2-dev": { + "versions": { + "0.27.7-r0": 1545838464 + }, + "origin": "libgit2", + "dependencies": [ + "curl-dev", + "libssh2-dev", + "libgit2=0.27.7-r0", + "pc:openssl", + "pc:zlib", + "pkgconfig" + ], + "provides": [ + "pc:libgit2=0.27.7" + ] + }, + "rxvt-unicode": { + "versions": { + "9.22-r4": 1543254027 + }, + "origin": "rxvt-unicode", + "dependencies": [ + "rxvt-unicode-terminfo", + "so:libX11.so.6", + "so:libXft.so.2", + "so:libXrender.so.1", + "so:libc.musl-x86_64.so.1", + "so:libfontconfig.so.1", + "so:libgcc_s.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libperl.so", + "so:libstartup-notification-1.so.0" + ], + "provides": [ + "cmd:urxvt", + "cmd:urxvtc", + "cmd:urxvtd" + ] + }, + "kamailio-geoip2": { + "versions": { + "5.2.0-r1": 1546423172 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libmaxminddb.so.0" + ] + }, + "py2-yaml": { + "versions": { + "4.1-r0": 1544798588 + }, + "origin": "py-yaml" + }, + "lua5.2-sircbot": { + "versions": { + "0.4-r2": 1543924833 + }, + "origin": "sircbot", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py3-pillow": { + "versions": { + "5.4.1-r0": 1548112615 + }, + "origin": "py-pillow", + "dependencies": [ + "py3-olefile", + "so:libc.musl-x86_64.so.1", + "so:libfreetype.so.6", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libopenjp2.so.7", + "so:libpython3.6m.so.1.0", + "so:libtiff.so.5", + "so:libwebp.so.7", + "so:libwebpdemux.so.2", + "so:libwebpmux.so.3", + "so:libz.so.1" + ] + }, + "libnl3-dev": { + "versions": { + "3.4.0-r0": 1542965891 + }, + "origin": "libnl3", + "dependencies": [ + "libnl3-cli=3.4.0-r0", + "libnl3=3.4.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libnl-3.0=3.4.0", + "pc:libnl-cli-3.0=3.4.0", + "pc:libnl-genl-3.0=3.4.0", + "pc:libnl-idiag-3.0=3.4.0", + "pc:libnl-nf-3.0=3.4.0", + "pc:libnl-route-3.0=3.4.0", + "pc:libnl-xfrm-3.0=3.4.0" + ] + }, + "perl-io-socket-ssl": { + "versions": { + "2.060-r0": 1545164014 + }, + "origin": "perl-io-socket-ssl", + "dependencies": [ + "ca-certificates", + "perl-net-libidn", + "perl-net-ssleay" + ] + }, + "perl-sub-name-doc": { + "versions": { + "0.21-r1": 1542845757 + }, + "origin": "perl-sub-name" + }, + "sfdisk": { + "versions": { + "2.33-r0": 1545307437 + }, + "origin": "util-linux", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfdisk.so.1", + "so:libncursesw.so.6", + "so:libsmartcols.so.1" + ], + "provides": [ + "cmd:sfdisk" + ] + }, + "mosquitto-libs": { + "versions": { + "1.5.6-r0": 1549970968 + }, + "origin": "mosquitto", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcares.so.2", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libmosquitto.so.1=1" + ] + }, + "libjpeg-turbo-dev": { + "versions": { + "1.5.3-r4": 1547027583 + }, + "origin": "libjpeg-turbo", + "dependencies": [ + "libjpeg-turbo=1.5.3-r4", + "pkgconfig" + ], + "provides": [ + "pc:libjpeg=1.5.3", + "pc:libturbojpeg=1.5.3" + ] + }, + "gdbm": { + "versions": { + "1.13-r1": 1542303060 + }, + "origin": "gdbm", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgdbm.so.4=4.0.0", + "so:libgdbm_compat.so.4=4.0.0", + "cmd:gdbm_dump", + "cmd:gdbm_load", + "cmd:gdbmtool" + ] + }, + "fakeroot-doc": { + "versions": { + "1.23-r0": 1542304762 + }, + "origin": "fakeroot" + }, + "lua5.3-cqueues": { + "versions": { + "20171014-r3": 1546520956 + }, + "origin": "lua-cqueues", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "cmocka-dev": { + "versions": { + "1.1.1-r1": 1543933265 + }, + "origin": "cmocka", + "dependencies": [ + "cmocka=1.1.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:cmocka=1.1.1" + ] + }, + "nginx-mod-http-cache-purge": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1" + ] + }, + "rgb-doc": { + "versions": { + "1.0.6-r2": 1545075161 + }, + "origin": "rgb" + }, + "qpdf": { + "versions": { + "8.3.0-r0": 1547117323 + }, + "origin": "qpdf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libqpdf.so.21", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:qpdf", + "cmd:zlib-flate" + ] + }, + "p11-kit-server": { + "versions": { + "0.23.14-r0": 1544791899 + }, + "origin": "p11-kit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libp11-kit.so.0" + ] + }, + "libproc": { + "versions": { + "3.3.15-r0": 1543934063 + }, + "origin": "procps", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libprocps.so.7=7.1.0" + ] + }, + "perl-math-round": { + "versions": { + "0.07-r0": 1545163054 + }, + "origin": "perl-math-round", + "dependencies": [ + "perl" + ] + }, + "binutils-doc": { + "versions": { + "2.31.1-r2": 1546441178 + }, + "origin": "binutils" + }, + "xbacklight": { + "versions": { + "1.2.2-r0": 1544792468 + }, + "origin": "xbacklight", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxcb-randr.so.0", + "so:libxcb-util.so.1", + "so:libxcb.so.1" + ], + "provides": [ + "cmd:xbacklight" + ] + }, + "gettext-lang": { + "versions": { + "0.19.8.1-r4": 1542304412 + }, + "origin": "gettext" + }, + "ldns": { + "versions": { + "1.7.0-r2": 1545117118 + }, + "origin": "ldns", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ], + "provides": [ + "so:libldns.so.2=2.0.0" + ] + }, + "lua5.3-dbi-postgresql": { + "versions": { + "0.6-r3": 1545214225 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "openldap-back-dnssrv": { + "versions": { + "2.4.47-r2": 1546016477 + }, + "origin": "openldap", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap_r-2.4.so.2" + ] + }, + "clamav-libs": { + "versions": { + "0.100.3-r0": 1555507335 + }, + "origin": "clamav", + "dependencies": [ + "clamav-scanner", + "clamav-daemon", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libpcre.so.1", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:libclamav.so.7=7.1.2", + "so:libclammspack.so.0=0.1.0", + "so:libclamunrar.so.7=7.1.2", + "so:libclamunrar_iface.so.7=7.1.2" + ] + }, + "freeradius-mysql": { + "versions": { + "3.0.17-r5": 1556201988 + }, + "origin": "freeradius", + "dependencies": [ + "freeradius-sql=3.0.17-r5", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ], + "provides": [ + "freeradius3-mysql=3.0.17-r5", + "so:rlm_sql_mysql.so=0" + ] + }, + "clang-analyzer": { + "versions": { + "5.0.2-r0": 1546873920 + }, + "origin": "clang", + "dependencies": [ + "clang=5.0.2-r0", + "perl", + "python2" + ], + "provides": [ + "cmd:scan-build", + "cmd:scan-view" + ] + }, + "libevdev-dev": { + "versions": { + "1.6.0-r0": 1545838440 + }, + "origin": "libevdev", + "dependencies": [ + "libevdev=1.6.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libevdev=1.6.0" + ] + }, + "perl-dbix-searchbuilder-doc": { + "versions": { + "1.67-r1": 1544798719 + }, + "origin": "perl-dbix-searchbuilder" + }, + "opus-doc": { + "versions": { + "1.3-r0": 1545069004 + }, + "origin": "opus" + }, + "gtkmm": { + "versions": { + "2.24.5-r0": 1543925602 + }, + "origin": "gtkmm", + "dependencies": [ + "so:libatkmm-1.6.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcairomm-1.0.so.1", + "so:libgcc_s.so.1", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgiomm-2.4.so.1", + "so:libglib-2.0.so.0", + "so:libglibmm-2.4.so.1", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libpangomm-1.4.so.1", + "so:libsigc-2.0.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libgdkmm-2.4.so.1=1.1.0", + "so:libgtkmm-2.4.so.1=1.1.0" + ] + }, + "perl-module-util": { + "versions": { + "1.09-r0": 1545163062 + }, + "origin": "perl-module-util", + "provides": [ + "cmd:pm_which" + ] + }, + "lua5.3-lyaml": { + "versions": { + "6.2-r3": 1545076422 + }, + "origin": "lua-lyaml", + "dependencies": [ + "lua5.3", + "lua5.3-stdlib-normalize", + "so:libc.musl-x86_64.so.1", + "so:libyaml-0.so.2" + ] + }, + "libxcursor-doc": { + "versions": { + "1.1.15-r1": 1543925380 + }, + "origin": "libxcursor" + }, + "vanessa_logger-doc": { + "versions": { + "0.0.10-r0": 1542893598 + }, + "origin": "vanessa_logger" + }, + "perl-convert-uulib": { + "versions": { + "1.5-r2": 1544001424 + }, + "origin": "perl-convert-uulib", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ] + }, + "openbox-kde": { + "versions": { + "3.6.1-r2": 1545207328 + }, + "origin": "openbox", + "provides": [ + "cmd:openbox-kde-session" + ] + }, + "dmvpn-crl-dp": { + "versions": { + "1.0.2-r0": 1553425622 + }, + "origin": "dmvpn", + "dependencies": [ + "lighttpd" + ], + "provides": [ + "cmd:dmvpn-crl-update" + ] + }, + "ppp-passwordfd": { + "versions": { + "2.4.7-r6": 1543999023 + }, + "origin": "ppp", + "dependencies": [ + "ppp-daemon", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-class-accessor-chained": { + "versions": { + "0.01-r0": 1544000364 + }, + "origin": "perl-class-accessor-chained", + "dependencies": [ + "perl-class-accessor" + ] + }, + "yaml-dev": { + "versions": { + "0.2.1-r0": 1542822091 + }, + "origin": "yaml", + "dependencies": [ + "pkgconfig", + "yaml=0.2.1-r0" + ], + "provides": [ + "pc:yaml-0.1=0.2.1" + ] + }, + "perdition": { + "versions": { + "2.2-r0": 1545223336 + }, + "origin": "perdition", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libgdbm.so.4", + "so:libpopt.so.0", + "so:libssl.so.1.1", + "so:libvanessa_adt.so.1", + "so:libvanessa_logger.so.0", + "so:libvanessa_socket.so.2" + ], + "provides": [ + "so:libperditiondb_gdbm.so.0=0.0.0", + "cmd:makegdbm", + "cmd:perdition", + "cmd:perdition.imap4", + "cmd:perdition.imap4s", + "cmd:perdition.imaps", + "cmd:perdition.managesieve", + "cmd:perdition.pop3", + "cmd:perdition.pop3s" + ] + }, + "libbsd-doc": { + "versions": { + "0.8.6-r2": 1542822480 + }, + "origin": "libbsd" + }, + "kamailio-utils": { + "versions": { + "5.2.0-r1": 1546423170 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libsrdb1.so.1", + "so:libxml2.so.2" + ] + }, + "py-pbr": { + "versions": { + "5.1.1-r0": 1545235280 + }, + "origin": "py-pbr" + }, + "termrec": { + "versions": { + "0.17-r1": 1545249956 + }, + "origin": "termrec", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:liblzma.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libtty.so.0=0.0.0", + "cmd:proxyrec", + "cmd:termcat", + "cmd:termplay", + "cmd:termrec", + "cmd:termtime" + ] + }, + "lxc-templates-oci": { + "versions": { + "3.1.0-r1": 1549966161 + }, + "origin": "lxc", + "dependencies": [ + "bash", + "jq" + ] + }, + "cmocka": { + "versions": { + "1.1.1-r1": 1543933266 + }, + "origin": "cmocka", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libcmocka.so.0=0.4.1" + ] + }, + "wipe": { + "versions": { + "2.3.1-r0": 1543924496 + }, + "origin": "wipe", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:wipe" + ] + }, + "cgit-doc": { + "versions": { + "1.2.1-r1": 1544000809 + }, + "origin": "cgit" + }, + "gcc-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "perl-proc-wait3": { + "versions": { + "0.05-r1": 1543249818 + }, + "origin": "perl-proc-wait3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "cython-dev": { + "versions": { + "0.29.2-r0": 1545918551 + }, + "origin": "cython", + "dependencies": [ + "python2-dev", + "py-pgen", + "cython" + ] + }, + "libguess": { + "versions": { + "1.2-r0": 1543998753 + }, + "origin": "libguess", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libguess.so.1=1.0.0" + ] + }, + "e2fsprogs-extra": { + "versions": { + "1.44.5-r0": 1545745858 + }, + "origin": "e2fsprogs", + "dependencies": [ + "e2fsprogs", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libe2p.so.2", + "so:libext2fs.so.2", + "so:libss.so.2", + "so:libuuid.so.1" + ], + "provides": [ + "cmd:badblocks", + "cmd:chattr", + "cmd:debugfs", + "cmd:dumpe2fs", + "cmd:e2freefrag", + "cmd:e2image", + "cmd:e2label", + "cmd:e2mmpstatus", + "cmd:e2undo", + "cmd:e4crypt", + "cmd:e4defrag", + "cmd:filefrag", + "cmd:logsave", + "cmd:lsattr", + "cmd:mklost+found", + "cmd:resize2fs", + "cmd:tune2fs" + ] + }, + "kamailio-uuid": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libuuid.so.1" + ] + }, + "qt-private-dev": { + "versions": { + "4.8.7-r11": 1545075090 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates" + ] + }, + "py-backports_abc": { + "versions": { + "0.5-r0": 1545293194 + }, + "origin": "py-backports_abc" + }, + "libmcrypt-dev": { + "versions": { + "2.5.8-r7": 1545066949 + }, + "origin": "libmcrypt", + "dependencies": [ + "libmcrypt=2.5.8-r7" + ], + "provides": [ + "cmd:libmcrypt-config" + ] + }, + "udev-init-scripts": { + "versions": { + "32-r2": 1542845335 + }, + "origin": "udev-init-scripts", + "dependencies": [ + "/bin/sh" + ] + }, + "faac-doc": { + "versions": { + "1.28-r12": 1545165108 + }, + "origin": "faac" + }, + "perl-ldap": { + "versions": { + "0.65-r1": 1543081711 + }, + "origin": "perl-ldap", + "dependencies": [ + "perl-convert-asn1" + ], + "provides": [ + "perl-net-ldap" + ] + }, + "libdvbpsi": { + "versions": { + "1.3.2-r0": 1544792492 + }, + "origin": "libdvbpsi", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdvbpsi.so.10=10.0.0" + ] + }, + "nginx-mod-http-lua": { + "versions": { + "1.14.2-r1": 1557179821 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "nginx-mod-devel-kit", + "so:libc.musl-x86_64.so.1", + "so:libluajit-5.1.so.2" + ], + "provides": [ + "nginx-lua" + ] + }, + "farstream-dev": { + "versions": { + "0.2.8-r2": 1543928858 + }, + "origin": "farstream", + "dependencies": [ + "libnice-dev", + "gstreamer-dev", + "gst-plugins-base-dev", + "farstream=0.2.8-r2", + "pc:gstreamer-1.0", + "pc:gstreamer-base-1.0", + "pkgconfig" + ], + "provides": [ + "pc:farstream-0.2=0.2.8" + ] + }, + "ulogd-openrc": { + "versions": { + "2.0.7-r0": 1545300897 + }, + "origin": "ulogd" + }, + "open-lldp-doc": { + "versions": { + "0.9.46-r3": 1545062129 + }, + "origin": "open-lldp" + }, + "static-routing": { + "versions": { + "1.0-r0": 1545213722 + }, + "origin": "static-routing" + }, + "uwsgi-http": { + "versions": { + "2.0.17.1-r0": 1545062201 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "uwsgi-corerouter", + "so:libc.musl-x86_64.so.1" + ] + }, + "eggdrop-doc": { + "versions": { + "1.6.21-r2": 1545206945 + }, + "origin": "eggdrop" + }, + "py-libproxy": { + "versions": { + "0.4.15-r1": 1544001158 + }, + "origin": "libproxy" + }, + "perl-posix-strftime-compiler": { + "versions": { + "0.42-r0": 1544792384 + }, + "origin": "perl-posix-strftime-compiler", + "dependencies": [ + "tzdata" + ] + }, + "busybox-suid": { + "versions": { + "1.29.3-r10": 1548315956 + }, + "origin": "busybox", + "dependencies": [ + "busybox", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bbsuid" + ] + }, + "lighttpd-dbg": { + "versions": { + "1.4.52-r0": 1545207629 + }, + "origin": "lighttpd" + }, + "json-c": { + "versions": { + "0.13.1-r0": 1543923377 + }, + "origin": "json-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libjson-c.so.4=4.0.0" + ] + }, + "gnome-doc-utils": { + "versions": { + "0.20.10-r2": 1543254074 + }, + "origin": "gnome-doc-utils", + "dependencies": [ + "python2", + "docbook-xml", + "rarian", + "py2-libxml2", + "libxslt" + ], + "provides": [ + "cmd:gnome-doc-prepare", + "cmd:gnome-doc-tool", + "cmd:xml2po" + ] + }, + "postgresql-plpython2": { + "versions": { + "11.2-r0": 1554274176 + }, + "origin": "postgresql", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "clucene-dev": { + "versions": { + "2.3.3.4-r5": 1543999111 + }, + "origin": "clucene", + "dependencies": [ + "zlib-dev", + "boost-dev", + "clucene-contribs=2.3.3.4-r5", + "clucene=2.3.3.4-r5", + "pkgconfig" + ], + "provides": [ + "pc:libclucene-core=2.3.3.4" + ] + }, + "sdl-dev": { + "versions": { + "1.2.15-r9": 1545292835 + }, + "origin": "sdl", + "dependencies": [ + "libx11-dev", + "pkgconfig", + "sdl=1.2.15-r9" + ], + "provides": [ + "pc:sdl=1.2.15", + "cmd:sdl-config" + ] + }, + "linux-firmware-emi26": { + "versions": { + "20190322-r0": 1554980652 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "npm": { + "versions": { + "10.14.2-r0": 1545345077 + }, + "origin": "nodejs", + "dependencies": [ + "nodejs" + ], + "provides": [ + "nodejs-npm=10.14.2-r0", + "nodejs-current-npm=10.14.2-r0", + "cmd:npm", + "cmd:npx" + ] + }, + "zfs-dracut": { + "versions": { + "0.7.12-r1": 1552933679 + }, + "origin": "zfs" + }, + "lua5.3-iconv": { + "versions": { + "7-r1": 1545062771 + }, + "origin": "lua-iconv", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "dtc-dev": { + "versions": { + "1.4.7-r0": 1545300716 + }, + "origin": "dtc", + "dependencies": [ + "libfdt=1.4.7-r0" + ] + }, + "lua5.1-struct": { + "versions": { + "0.2-r2": 1545076451 + }, + "origin": "lua-struct", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "glibmm-doc": { + "versions": { + "2.56.0-r1": 1544799351 + }, + "origin": "glibmm" + }, + "perl-class-accessor-doc": { + "versions": { + "0.34-r0": 1543934263 + }, + "origin": "perl-class-accessor" + }, + "perl-term-readkey": { + "versions": { + "2.37-r1": 1543922485 + }, + "origin": "perl-term-readkey", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-purl": { + "versions": { + "1.4-r0": 1543925752 + }, + "origin": "py-purl", + "dependencies": [ + "py2-six", + "python2" + ] + }, + "scons": { + "versions": { + "3.0.1-r2": 1543245859 + }, + "origin": "scons", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:scons", + "cmd:scons-3.0.1", + "cmd:scons-configure-cache", + "cmd:scons-configure-cache-3.0.1", + "cmd:scons-time", + "cmd:scons-time-3.0.1", + "cmd:sconsign", + "cmd:sconsign-3.0.1" + ] + }, + "taglib-dev": { + "versions": { + "1.11.1-r1": 1545062764 + }, + "origin": "taglib", + "dependencies": [ + "pkgconfig", + "taglib=1.11.1-r1" + ], + "provides": [ + "pc:taglib=1.11.1", + "pc:taglib_c=1.11.1", + "cmd:taglib-config" + ] + }, + "sharutils-doc": { + "versions": { + "4.15.2-r0": 1543929026 + }, + "origin": "sharutils" + }, + "ethtool": { + "versions": { + "4.19-r0": 1545820353 + }, + "origin": "ethtool", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ethtool" + ] + }, + "perl-mro-compat-doc": { + "versions": { + "0.13-r0": 1542972948 + }, + "origin": "perl-mro-compat" + }, + "tinyxml2": { + "versions": { + "7.0.1-r0": 1545061051 + }, + "origin": "tinyxml2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libtinyxml2.so.7=7.0.1" + ] + }, + "goaccess": { + "versions": { + "1.2-r1": 1545116584 + }, + "origin": "goaccess", + "dependencies": [ + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:goaccess" + ] + }, + "font-daewoo-misc": { + "versions": { + "1.0.3-r0": 1544798553 + }, + "origin": "font-daewoo-misc", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "perl-text-wikiformat-doc": { + "versions": { + "0.81-r0": 1545076735 + }, + "origin": "perl-text-wikiformat" + }, + "glib-bash-completion": { + "versions": { + "2.58.1-r2": 1545290825 + }, + "origin": "glib" + }, + "man-pages": { + "versions": { + "4.16-r0": 1545256949 + }, + "origin": "man-pages" + }, + "irssi-perl": { + "versions": { + "1.1.2-r0": 1547738041 + }, + "origin": "irssi", + "dependencies": [ + "irssi", + "perl", + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "dvgrab-doc": { + "versions": { + "3.5-r3": 1543999358 + }, + "origin": "dvgrab" + }, + "acf-db-lib": { + "versions": { + "0.2.1-r2": 1543924387 + }, + "origin": "acf-db", + "dependencies": [ + "acf-core", + "lua", + "acf-lib" + ] + }, + "libpcap": { + "versions": { + "1.9.0-r1": 1545905178 + }, + "origin": "libpcap", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpcap.so.1=1.9.0" + ] + }, + "acf-db": { + "versions": { + "0.2.1-r2": 1543924387 + }, + "origin": "acf-db", + "dependencies": [ + "acf-core", + "acf-db-lib", + "acf-db-lib=0.2.1-r2" + ] + }, + "pangomm-dev": { + "versions": { + "2.40.2-r0": 1543925531 + }, + "origin": "pangomm", + "dependencies": [ + "pango-dev", + "glibmm-dev", + "cairomm-dev", + "pangomm=2.40.2-r0", + "pc:cairomm-1.0>=1.2.2", + "pc:glibmm-2.4>=2.48.0", + "pc:pangocairo>=1.38.0", + "pkgconfig" + ], + "provides": [ + "pc:pangomm-1.4=2.40.2" + ] + }, + "xf86-video-tdfx": { + "versions": { + "1.4.7-r3": 1545073943 + }, + "origin": "xf86-video-tdfx", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-openvswitch": { + "versions": { + "2.10.1-r0": 1544000344 + }, + "origin": "openvswitch", + "dependencies": [ + "py2-six" + ] + }, + "cifs-utils-doc": { + "versions": { + "6.8-r0": 1545290851 + }, + "origin": "cifs-utils" + }, + "lua5.1-dbi-postgresql": { + "versions": { + "0.6-r3": 1545214219 + }, + "origin": "lua-dbi", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "perl-switch": { + "versions": { + "2.17-r0": 1543998989 + }, + "origin": "perl-switch", + "dependencies": [ + "perl" + ] + }, + "ntop": { + "versions": { + "5.0.1-r14": 1545209736 + }, + "origin": "ntop", + "dependencies": [ + "/bin/sh", + "so:libGeoIP.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgdbm.so.4", + "so:libpcap.so.1", + "so:libpython2.7.so.1.0", + "so:librrd.so.8", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "so:libnetflowPlugin-5.0.1.so=0", + "so:libntop-5.0.1.so=0", + "so:libntopreport-5.0.1.so=0", + "so:librrdPlugin-5.0.1.so=0", + "so:libsflowPlugin-5.0.1.so=0", + "cmd:ntop", + "cmd:ntop-update-geoip-db" + ] + }, + "openvswitch-doc": { + "versions": { + "2.10.1-r0": 1544000341 + }, + "origin": "openvswitch" + }, + "fcgiwrap": { + "versions": { + "1.1.0-r3": 1545665782 + }, + "origin": "fcgiwrap", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libfcgi.so.0" + ], + "provides": [ + "cmd:fcgiwrap" + ] + }, + "netcf-doc": { + "versions": { + "0.2.8-r5": 1543935399 + }, + "origin": "netcf" + }, + "dovecot-gssapi": { + "versions": { + "2.3.6-r0": 1557134098 + }, + "origin": "dovecot", + "dependencies": [ + "dovecot=2.3.6-r0", + "so:libc.musl-x86_64.so.1", + "so:libgssapi.so.3" + ] + }, + "gtksourceview2-lang": { + "versions": { + "2.10.5-r0": 1545292665 + }, + "origin": "gtksourceview2" + }, + "collectd-libs": { + "versions": { + "5.8.0-r3": 1546422675 + }, + "origin": "collectd", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcrypt.so.20" + ], + "provides": [ + "so:libcollectdclient.so.1=1.1.0" + ] + }, + "bash-dev": { + "versions": { + "4.4.19-r1": 1542301270 + }, + "origin": "bash", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "cmd:bashbug" + ] + }, + "ruby-rdoc": { + "versions": { + "2.5.5-r0": 1557164837 + }, + "origin": "ruby", + "dependencies": [ + "ruby", + "ruby-json", + "ruby-io-console" + ], + "provides": [ + "cmd:rdoc", + "cmd:ri" + ] + }, + "libpri": { + "versions": { + "1.6.0-r0": 1543932624 + }, + "origin": "libpri", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libpri.so.1.4=1.4" + ] + }, + "bind-tools": { + "versions": { + "9.12.4_p1-r1": 1556801608 + }, + "origin": "bind", + "dependencies": [ + "so:libbind9.so.1201", + "so:libc.musl-x86_64.so.1", + "so:libdns.so.1208", + "so:libirs.so.1201", + "so:libisc.so.1204", + "so:libisccfg.so.1203", + "so:libkrb5.so.3" + ], + "provides": [ + "cmd:delv", + "cmd:dig", + "cmd:dnssec-cds", + "cmd:dnssec-dsfromkey", + "cmd:dnssec-importkey", + "cmd:dnssec-keyfromlabel", + "cmd:dnssec-keygen", + "cmd:dnssec-revoke", + "cmd:dnssec-settime", + "cmd:dnssec-signzone", + "cmd:dnssec-verify", + "cmd:host", + "cmd:nslookup", + "cmd:nsupdate" + ] + }, + "xvidcore": { + "versions": { + "1.3.4-r1": 1545302183 + }, + "origin": "xvidcore", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libxvidcore.so.4=4.3" + ] + }, + "openjade-libs": { + "versions": { + "1.3.2-r5": 1542893151 + }, + "origin": "openjade", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libogrove.so.0=0.0.1", + "so:libospgrove.so.0=0.0.1", + "so:libostyle.so.0=0.0.1" + ] + }, + "xsetmode-doc": { + "versions": { + "1.0.0-r4": 1545075298 + }, + "origin": "xsetmode" + }, + "cppunit": { + "versions": { + "1.14.0-r0": 1543932038 + }, + "origin": "cppunit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libcppunit-1.14.so.0=0.0.0", + "cmd:DllPlugInTester" + ] + }, + "py3-docutils": { + "versions": { + "0.14-r1": 1543925152 + }, + "origin": "py-docutils", + "dependencies": [ + "py3-pillow", + "py3-roman", + "python3" + ], + "provides": [ + "cmd:rst2html-3", + "cmd:rst2html4-3", + "cmd:rst2html5-3", + "cmd:rst2latex-3", + "cmd:rst2man-3", + "cmd:rst2odt-3", + "cmd:rst2odt_prepstyles-3", + "cmd:rst2pseudoxml-3", + "cmd:rst2s5-3", + "cmd:rst2xetex-3", + "cmd:rst2xml-3", + "cmd:rstpep2html-3" + ] + }, + "mkinitfs": { + "versions": { + "3.4.1-r0": 1551202041 + }, + "origin": "mkinitfs", + "dependencies": [ + "busybox>=1.28.2-r1", + "apk-tools>=2.9.1", + "lddtree>=1.25", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcryptsetup.so.12", + "so:libkmod.so.2" + ], + "provides": [ + "cmd:bootchartd", + "cmd:mkinitfs", + "cmd:nlplug-findfs" + ] + }, + "perl-test-identity-doc": { + "versions": { + "0.01-r0": 1544799038 + }, + "origin": "perl-test-identity" + }, + "util-linux-doc": { + "versions": { + "2.33-r0": 1545307436 + }, + "origin": "util-linux" + }, + "nginx-mod-http-vod": { + "versions": { + "1.14.2-r1": 1557179822 + }, + "origin": "nginx", + "dependencies": [ + "nginx", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libxml2.so.2", + "so:libz.so.1" + ] + }, + "squid-lang-uz": { + "versions": { + "4.4-r1": 1545216330 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "linux-firmware-radeon": { + "versions": { + "20190322-r0": 1554980649 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "gnutls": { + "versions": { + "3.6.7-r0": 1555049965 + }, + "origin": "gnutls", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libhogweed.so.4", + "so:libnettle.so.6", + "so:libp11-kit.so.0", + "so:libtasn1.so.6", + "so:libunistring.so.2" + ], + "provides": [ + "so:libgnutls.so.30=30.23.2" + ] + }, + "musl-utils": { + "versions": { + "1.1.20-r4": 1552989415 + }, + "origin": "musl", + "dependencies": [ + "scanelf", + "musl=1.1.20-r4", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:getconf", + "cmd:getent", + "cmd:iconv", + "cmd:ldconfig" + ] + }, + "libarchive-doc": { + "versions": { + "3.3.2-r4": 1542820310 + }, + "origin": "libarchive" + }, + "perl-html-tagset-doc": { + "versions": { + "3.20-r1": 1542821827 + }, + "origin": "perl-html-tagset" + }, + "nfdump": { + "versions": { + "1.6.17-r0": 1545223026 + }, + "origin": "nfdump", + "dependencies": [ + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnfdump-1.6.17.so=0", + "cmd:nfanon", + "cmd:nfcapd", + "cmd:nfdump", + "cmd:nfexpire", + "cmd:nfreplay" + ] + }, + "libebml-dev": { + "versions": { + "1.3.6-r1": 1543226528 + }, + "origin": "libebml", + "dependencies": [ + "libebml=1.3.6-r1", + "pkgconfig" + ], + "provides": [ + "pc:libebml=1.3.6" + ] + }, + "freeswitch-sounds-fr-fr-sibylle-8000": { + "versions": { + "0.1.3-r0": 1543999304 + }, + "origin": "freeswitch-sounds-fr-fr-sibylle-8000" + }, + "varnish-geoip": { + "versions": { + "6.1.1-r0": 1545300752 + }, + "origin": "varnish", + "dependencies": [ + "libmaxminddb-dev" + ] + }, + "libnl": { + "versions": { + "1.1.4-r0": 1543924491 + }, + "origin": "libnl", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnl.so.1=1.1.4" + ] + }, + "ghostscript": { + "versions": { + "9.26-r2": 1554362638 + }, + "origin": "ghostscript", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcups.so.2", + "so:libcupsimage.so.2", + "so:libfontconfig.so.1", + "so:libfreetype.so.6", + "so:libjbig2dec.so.0", + "so:libjpeg.so.8", + "so:liblcms2.so.2", + "so:libpng16.so.16", + "so:libtiff.so.5", + "so:libz.so.1" + ], + "provides": [ + "so:libgs.so.9=9.26", + "so:libijs-0.35.so=0", + "cmd:dvipdf", + "cmd:eps2eps", + "cmd:gs", + "cmd:gsbj", + "cmd:gsc", + "cmd:gsdj", + "cmd:gsdj500", + "cmd:gslj", + "cmd:gslp", + "cmd:gsnd", + "cmd:ijs_client_example", + "cmd:ijs_server_example", + "cmd:lprsetup.sh", + "cmd:pdf2dsc", + "cmd:pdf2ps", + "cmd:pf2afm", + "cmd:pfbtopfa", + "cmd:pphs", + "cmd:printafm", + "cmd:ps2ascii", + "cmd:ps2epsi", + "cmd:ps2pdf", + "cmd:ps2pdf12", + "cmd:ps2pdf13", + "cmd:ps2pdf14", + "cmd:ps2pdfwr", + "cmd:ps2ps", + "cmd:ps2ps2", + "cmd:unix-lpr.sh" + ] + }, + "lua5.2-struct": { + "versions": { + "0.2-r2": 1545076453 + }, + "origin": "lua-struct", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "lddtree": { + "versions": { + "1.26-r1": 1542845870 + }, + "origin": "lddtree", + "dependencies": [ + "scanelf" + ], + "provides": [ + "cmd:lddtree" + ] + }, + "libassuan": { + "versions": { + "2.5.1-r0": 1543932161 + }, + "origin": "libassuan", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgpg-error.so.0" + ], + "provides": [ + "so:libassuan.so.0=0.8.1" + ] + }, + "perl-test-mockobject": { + "versions": { + "1.20180705-r0": 1545116825 + }, + "origin": "perl-test-mockobject", + "dependencies": [ + "perl" + ] + }, + "mlmmj-doc": { + "versions": { + "1.3.0-r0": 1542883218 + }, + "origin": "mlmmj" + }, + "gcr": { + "versions": { + "3.28.0-r0": 1545300013 + }, + "origin": "gcr", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgcrypt.so.20", + "so:libgdk-3.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0", + "so:libintl.so.8", + "so:libp11-kit.so.0", + "so:libpango-1.0.so.0" + ], + "provides": [ + "so:libgck-1.so.0=0.0.0", + "so:libgcr-base-3.so.1=1.0.0", + "so:libgcr-ui-3.so.1=1.0.0", + "cmd:gcr-viewer" + ] + }, + "gettext-doc": { + "versions": { + "0.19.8.1-r4": 1542304411 + }, + "origin": "gettext" + }, + "fsarchiver": { + "versions": { + "0.8.5-r0": 1543226651 + }, + "origin": "fsarchiver", + "dependencies": [ + "so:libblkid.so.1", + "so:libbz2.so.1", + "so:libc.musl-x86_64.so.1", + "so:libe2p.so.2", + "so:libext2fs.so.2", + "so:libgcrypt.so.20", + "so:liblz4.so.1", + "so:liblzma.so.5", + "so:liblzo2.so.2", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:fsarchiver" + ] + }, + "lua5.1-pc": { + "versions": { + "1.0.0-r9": 1543928869 + }, + "origin": "lua-pc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-parse-syslog": { + "versions": { + "1.10-r3": 1545073947 + }, + "origin": "perl-parse-syslog", + "dependencies": [ + "perl" + ] + }, + "asterisk": { + "versions": { + "15.7.1-r0": 1546247586 + }, + "origin": "asterisk", + "dependencies": [ + "/bin/sh", + "so:libc-client.so.1", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libedit.so.0", + "so:libjansson.so.4", + "so:liblua.so.5", + "so:libpj.so.2", + "so:libpjlib-util.so.2", + "so:libpjmedia.so.2", + "so:libpjnath.so.2", + "so:libpjsip-simple.so.2", + "so:libpjsip-ua.so.2", + "so:libpjsip.so.2", + "so:libportaudio.so.2", + "so:libsqlite3.so.0", + "so:libssl.so.1.1", + "so:libuuid.so.1", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libasteriskssl.so.1=1", + "cmd:astcanary", + "cmd:astdb2bdb", + "cmd:astdb2sqlite3", + "cmd:asterisk", + "cmd:astgenkey", + "cmd:astversion", + "cmd:autosupport", + "cmd:rasterisk", + "cmd:safe_asterisk" + ] + }, + "libdvbpsi-dev": { + "versions": { + "1.3.2-r0": 1544792492 + }, + "origin": "libdvbpsi", + "dependencies": [ + "libdvbpsi=1.3.2-r0", + "pkgconfig" + ], + "provides": [ + "pc:libdvbpsi=1.3.2" + ] + }, + "lua-inspect": { + "versions": { + "3.1.1-r1": 1545075418 + }, + "origin": "lua-inspect", + "dependencies": [ + "lua" + ], + "provides": [ + "lua5.1-inspect=3.1.1-r1", + "lua5.2-inspect=3.1.1-r1", + "lua5.3-inspect=3.1.1-r1" + ] + }, + "py-scandir": { + "versions": { + "1.9.0-r1": 1542824907 + }, + "origin": "py-scandir" + }, + "dahdi-linux-dev": { + "versions": { + "3.0.0-r0": 1545911874 + }, + "origin": "dahdi-linux" + }, + "squid-lang-fi": { + "versions": { + "4.4-r1": 1545216325 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "qemu-ppc": { + "versions": { + "3.1.0-r3": 1551107301 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-ppc" + ] + }, + "perl-module-build-tiny": { + "versions": { + "0.039-r0": 1543238869 + }, + "origin": "perl-module-build-tiny", + "dependencies": [ + "perl-extutils-installpaths", + "perl-extutils-config", + "perl-extutils-helpers", + "perl-test-harness" + ] + }, + "qemu-riscv64": { + "versions": { + "3.1.0-r3": 1551107302 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-riscv64" + ] + }, + "openldap-passwd-pbkdf2": { + "versions": { + "2.4.47-r2": 1546016477 + }, + "origin": "openldap", + "dependencies": [ + "openldap", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ] + }, + "valgrind-doc": { + "versions": { + "3.14.0-r0": 1545300865 + }, + "origin": "valgrind" + }, + "iproute2-doc": { + "versions": { + "4.19.0-r0": 1543223211 + }, + "origin": "iproute2" + }, + "libseccomp-doc": { + "versions": { + "2.3.3-r1": 1543923553 + }, + "origin": "libseccomp" + }, + "po4a-doc": { + "versions": { + "0.52-r1": 1542304729 + }, + "origin": "po4a" + }, + "xf86-video-sis-doc": { + "versions": { + "0.10.9-r3": 1545253989 + }, + "origin": "xf86-video-sis" + }, + "syslog-ng-python2": { + "versions": { + "3.19.1-r0": 1548543150 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "perl-apache-logformat-compiler-doc": { + "versions": { + "0.32-r0": 1544792421 + }, + "origin": "perl-apache-logformat-compiler" + }, + "freshclam": { + "versions": { + "0.100.3-r0": 1555507335 + }, + "origin": "clamav", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libclamav.so.7", + "so:libcrypto.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:freshclam" + ] + }, + "squid-lang-tr": { + "versions": { + "4.4-r1": 1545216330 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "isl": { + "versions": { + "0.18-r0": 1542301497 + }, + "origin": "isl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10" + ], + "provides": [ + "so:libisl.so.15=15.3.0" + ] + }, + "openipmi-libs": { + "versions": { + "2.0.25-r1": 1545214271 + }, + "origin": "openipmi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "so:libOpenIPMI.so.0=0.0.5", + "so:libOpenIPMIcmdlang.so.0=0.0.5", + "so:libOpenIPMIglib.so.0=0.0.1", + "so:libOpenIPMIposix.so.0=0.0.1", + "so:libOpenIPMIpthread.so.0=0.0.1", + "so:libOpenIPMIui.so.1=1.0.1", + "so:libOpenIPMIutils.so.0=0.0.1" + ] + }, + "atf": { + "versions": { + "0.21-r1": 1542304875 + }, + "origin": "atf", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libatf-c++.so.2=2.0.0", + "so:libatf-c.so.1=1.0.0", + "cmd:atf-sh" + ] + }, + "openobex-dev": { + "versions": { + "1.7.2-r1": 1545069090 + }, + "origin": "openobex", + "dependencies": [ + "libusb-dev", + "openobex=1.7.2-r1", + "pc:libusb-1.0", + "pkgconfig" + ], + "provides": [ + "pc:openobex=1.7.2" + ] + }, + "perl-net-server": { + "versions": { + "2.009-r1": 1545213954 + }, + "origin": "perl-net-server", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:net-server" + ] + }, + "qemu-doc": { + "versions": { + "3.1.0-r3": 1551107299 + }, + "origin": "qemu" + }, + "xcb-proto": { + "versions": { + "1.13-r2": 1542822452 + }, + "origin": "xcb-proto", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xcb-proto=1.13" + ] + }, + "ivshmem-tools": { + "versions": { + "3.1.0-r3": 1551107299 + }, + "origin": "qemu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0" + ], + "provides": [ + "cmd:ivshmem-client", + "cmd:ivshmem-server" + ] + }, + "cdw": { + "versions": { + "0.8.1-r0": 1545302173 + }, + "origin": "cdw", + "dependencies": [ + "cdrkit", + "so:libburn.so.4", + "so:libc.musl-x86_64.so.1", + "so:libcdio.so.16", + "so:libformw.so.6", + "so:libintl.so.8", + "so:libiso9660.so.10", + "so:libmenuw.so.6", + "so:libncursesw.so.6", + "so:libpanelw.so.6" + ], + "provides": [ + "cmd:cdw" + ] + }, + "lua5.3-dev": { + "versions": { + "5.3.5-r2": 1557162501 + }, + "origin": "lua5.3", + "dependencies": [ + "lua5.3", + "lua5.3-libs=5.3.5-r2", + "pkgconfig" + ], + "provides": [ + "pc:lua5.3=5.3.5" + ] + }, + "usb-modeswitch": { + "versions": { + "2.5.2-r0": 1543932713 + }, + "origin": "usb-modeswitch", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "cmd:usb_modeswitch" + ] + }, + "bacula-sqlite": { + "versions": { + "9.4.1-r1": 1546944087 + }, + "origin": "bacula", + "dependencies": [ + "bacula", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ], + "provides": [ + "so:libbaccats-sqlite3-9.4.1.so=0" + ] + }, + "qemu-xtensa": { + "versions": { + "3.1.0-r3": 1551107306 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-xtensa" + ] + }, + "fping": { + "versions": { + "4.1-r0": 1545746006 + }, + "origin": "fping", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:fping" + ] + }, + "libzmq": { + "versions": { + "4.3.1-r0": 1548271359 + }, + "origin": "zeromq", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libsodium.so.23", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libzmq.so.5=5.2.1" + ] + }, + "dhcrelay": { + "versions": { + "4.4.1-r1": 1543928454 + }, + "origin": "dhcp", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "cmd:dhcrelay" + ] + }, + "setxkbmap-doc": { + "versions": { + "1.3.1-r1": 1545209626 + }, + "origin": "setxkbmap" + }, + "zfs-utils-py": { + "versions": { + "0.7.12-r1": 1552933680 + }, + "origin": "zfs", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:arc_summary.py", + "cmd:arcstat.py", + "cmd:dbufstat.py" + ] + }, + "kamailio-debugger": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libprint.so.1", + "so:libsrutils.so.1" + ] + }, + "libproxy-bin": { + "versions": { + "0.4.15-r1": 1544001156 + }, + "origin": "libproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libproxy.so.1" + ], + "provides": [ + "cmd:proxy" + ] + }, + "qt-postgresql": { + "versions": { + "4.8.7-r11": 1545075087 + }, + "origin": "qt", + "dependencies": [ + "ca-certificates", + "so:libQtCore.so.4", + "so:libQtSql.so.4", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpq.so.5", + "so:libstdc++.so.6" + ] + }, + "transmission-cli": { + "versions": { + "2.94-r1": 1545208748 + }, + "origin": "transmission", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libcurl.so.4", + "so:libevent-2.1.so.6", + "so:libintl.so.8", + "so:libz.so.1" + ], + "provides": [ + "cmd:transmission-cli", + "cmd:transmission-create", + "cmd:transmission-edit", + "cmd:transmission-remote", + "cmd:transmission-show" + ] + }, + "ldns-doc": { + "versions": { + "1.7.0-r2": 1545117117 + }, + "origin": "ldns" + }, + "iperf3": { + "versions": { + "3.6-r0": 1543932136 + }, + "origin": "iperf3", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libiperf.so.0=0.0.0", + "cmd:iperf3" + ] + }, + "tinc": { + "versions": { + "1.0.35-r1": 1545299617 + }, + "origin": "tinc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:liblzo2.so.2", + "so:libz.so.1" + ], + "provides": [ + "cmd:tincd" + ] + }, + "libfprint-dev": { + "versions": { + "0.7.0-r1": 1545207878 + }, + "origin": "libfprint", + "dependencies": [ + "libusb-dev", + "libfprint=0.7.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:libfprint=0.7.0" + ] + }, + "ppp-chat": { + "versions": { + "2.4.7-r6": 1543999020 + }, + "origin": "ppp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:chat" + ] + }, + "mini_httpd": { + "versions": { + "1.29-r2": 1544000654 + }, + "origin": "mini_httpd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:mini_htpasswd", + "cmd:mini_httpd" + ] + }, + "py2-gv": { + "versions": { + "2.40.1-r1": 1543926741 + }, + "origin": "graphviz", + "dependencies": [ + "python2", + "so:libc.musl-x86_64.so.1", + "so:libcgraph.so.6", + "so:libgcc_s.so.1", + "so:libgvc.so.6", + "so:libstdc++.so.6" + ], + "provides": [ + "py-graphviz=2.40.1-r1" + ] + }, + "apache2-proxy-html": { + "versions": { + "2.4.39-r0": 1554306835 + }, + "origin": "apache2", + "dependencies": [ + "apache2", + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2" + ] + }, + "lua5.1-cqueues": { + "versions": { + "20171014-r3": 1546520954 + }, + "origin": "lua-cqueues", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ] + }, + "xcb-util-cursor-dev": { + "versions": { + "0.1.3-r1": 1545163433 + }, + "origin": "xcb-util-cursor", + "dependencies": [ + "xcb-util-dev", + "pc:xcb-image", + "pc:xcb-render", + "pc:xcb-renderutil", + "pkgconfig", + "xcb-util-cursor=0.1.3-r1" + ], + "provides": [ + "pc:xcb-cursor=0.1.3" + ] + }, + "libdaemon-doc": { + "versions": { + "0.14-r2": 1543925257 + }, + "origin": "libdaemon" + }, + "py2-requests": { + "versions": { + "2.19.1-r0": 1542825031 + }, + "origin": "py-requests", + "dependencies": [ + "py2-chardet", + "py2-idna", + "py2-certifi", + "py2-urllib3", + "python2" + ] + }, + "ppp-doc": { + "versions": { + "2.4.7-r6": 1543999019 + }, + "origin": "ppp" + }, + "aspell-doc": { + "versions": { + "0.60.6.1-r13": 1542965830 + }, + "origin": "aspell" + }, + "perl-mime-base64": { + "versions": { + "3.15-r4": 1545061197 + }, + "origin": "perl-mime-base64", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "py2-pytest": { + "versions": { + "4.1.0-r0": 1548112483 + }, + "origin": "pytest", + "dependencies": [ + "py2-atomicwrites", + "py2-py", + "py2-six", + "py2-attrs", + "py2-more-itertools", + "py2-pluggy", + "py2-setuptools", + "py2-funcsigs", + "py2-pathlib2", + "python2" + ], + "provides": [ + "cmd:py.test-2", + "cmd:pytest-2" + ] + }, + "cppunit-doc": { + "versions": { + "1.14.0-r0": 1543932038 + }, + "origin": "cppunit" + }, + "python3-dbg": { + "versions": { + "3.6.8-r2": 1554747637 + }, + "origin": "python3" + }, + "rsyslog-pmsnare": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "font-screen-cyrillic": { + "versions": { + "1.0.4-r0": 1545207697 + }, + "origin": "font-screen-cyrillic", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "rtpproxy-debug": { + "versions": { + "2.0.0-r4": 1545301079 + }, + "origin": "rtpproxy", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:rtpproxy_debug" + ] + }, + "libnfsidmap": { + "versions": { + "2.3.2-r1": 1543933422 + }, + "origin": "nfs-utils", + "dependencies": [ + "rpcbind", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libnfsidmap.so.1=1.0.0" + ] + }, + "perl-json-maybexs": { + "versions": { + "1.004000-r0": 1542893316 + }, + "origin": "perl-json-maybexs", + "dependencies": [ + "perl-cpanel-json-xs" + ] + }, + "imlib2-dev": { + "versions": { + "1.5.1-r0": 1543221705 + }, + "origin": "imlib2", + "dependencies": [ + "freetype-dev", + "libxext-dev", + "libsm-dev", + "imlib2=1.5.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:imlib2=1.5.1", + "cmd:imlib2-config" + ] + }, + "ruby-xmlrpc": { + "versions": { + "2.5.5-r0": 1557164838 + }, + "origin": "ruby", + "dependencies": [ + "ruby-libs" + ] + }, + "qpdf-fix-qdf": { + "versions": { + "8.3.0-r0": 1547117323 + }, + "origin": "qpdf", + "dependencies": [ + "qpdf", + "perl" + ], + "provides": [ + "cmd:fix-qdf" + ] + }, + "py-constantly": { + "versions": { + "15.1.0-r0": 1546513613 + }, + "origin": "py-constantly" + }, + "mpg123": { + "versions": { + "1.25.10-r0": 1545117150 + }, + "origin": "mpg123", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmpg123.so.0=0.44.8", + "so:libout123.so.0=0.2.2", + "cmd:mpg123", + "cmd:mpg123-id3dump", + "cmd:mpg123-strip", + "cmd:out123" + ] + }, + "squark": { + "versions": { + "0.6.1-r1": 1545060929 + }, + "origin": "squark", + "dependencies": [ + "haserl", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcmph.so.0", + "so:liblua.so.5", + "so:libnetsnmp.so.35" + ], + "provides": [ + "cmd:sqdb-build.lua", + "cmd:squark-auth-ip", + "cmd:squark-auth-snmp", + "cmd:squark-filter" + ] + }, + "pacman-lang": { + "versions": { + "5.0.2-r4": 1543932339 + }, + "origin": "pacman", + "dependencies": [ + "libarchive-tools" + ] + }, + "xf86-input-keyboard-doc": { + "versions": { + "1.9.0-r1": 1545073857 + }, + "origin": "xf86-input-keyboard" + }, + "glfw-dev": { + "versions": { + "3.2.1-r2": 1545299813 + }, + "origin": "glfw", + "dependencies": [ + "libxinerama-dev", + "linux-headers", + "mesa-dev", + "glfw=3.2.1-r2", + "pc:x11", + "pc:xcursor", + "pc:xinerama", + "pc:xrandr", + "pc:xxf86vm", + "pkgconfig" + ], + "provides": [ + "pc:glfw3=3.2.1" + ] + }, + "mpc1-dev": { + "versions": { + "1.0.3-r1": 1542301580 + }, + "origin": "mpc1", + "dependencies": [ + "mpc1=1.0.3-r1" + ] + }, + "libvirt-common-drivers": { + "versions": { + "4.10.0-r1": 1547051658 + }, + "origin": "libvirt", + "dependencies": [ + "lvm2", + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdbus-1.so.3", + "so:libintl.so.8", + "so:libnetcf.so.1", + "so:libpcap.so.1", + "so:libpciaccess.so.0", + "so:libudev.so.1", + "so:libvirt.so.0", + "so:libxml2.so.2" + ] + }, + "mpc1-doc": { + "versions": { + "1.0.3-r1": 1542301580 + }, + "origin": "mpc1" + }, + "llvm5": { + "versions": { + "5.0.2-r0": 1546874981 + }, + "origin": "llvm5", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "llvm=5.0.2-r0", + "cmd:bugpoint", + "cmd:llc", + "cmd:lli", + "cmd:llvm-ar", + "cmd:llvm-as", + "cmd:llvm-bcanalyzer", + "cmd:llvm-c-test", + "cmd:llvm-cat", + "cmd:llvm-cov", + "cmd:llvm-cvtres", + "cmd:llvm-cxxdump", + "cmd:llvm-cxxfilt", + "cmd:llvm-diff", + "cmd:llvm-dis", + "cmd:llvm-dlltool", + "cmd:llvm-dsymutil", + "cmd:llvm-dwarfdump", + "cmd:llvm-dwp", + "cmd:llvm-extract", + "cmd:llvm-lib", + "cmd:llvm-link", + "cmd:llvm-lto", + "cmd:llvm-lto2", + "cmd:llvm-mc", + "cmd:llvm-mcmarkup", + "cmd:llvm-modextract", + "cmd:llvm-mt", + "cmd:llvm-nm", + "cmd:llvm-objdump", + "cmd:llvm-opt-report", + "cmd:llvm-pdbutil", + "cmd:llvm-profdata", + "cmd:llvm-ranlib", + "cmd:llvm-readelf", + "cmd:llvm-readobj", + "cmd:llvm-rtdyld", + "cmd:llvm-size", + "cmd:llvm-split", + "cmd:llvm-stress", + "cmd:llvm-strings", + "cmd:llvm-symbolizer", + "cmd:llvm-tblgen", + "cmd:llvm-xray", + "cmd:opt", + "cmd:sancov", + "cmd:sanstats", + "cmd:verify-uselistorder" + ] + }, + "lua5.1-ldap": { + "versions": { + "1.2.3-r5": 1543223250 + }, + "origin": "lua-ldap", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2" + ] + }, + "gnome-vfs-dev": { + "versions": { + "2.24.4-r6": 1545299689 + }, + "origin": "gnome-vfs", + "dependencies": [ + "gconf-dev", + "libxml2-dev", + "dbus-glib-dev", + "gamin-dev", + "gnome-vfs=2.24.4-r6", + "pc:gconf-2.0", + "pc:gmodule-no-export-2.0", + "pc:gobject-2.0", + "pc:gthread-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gnome-vfs-2.0=2.24.4", + "pc:gnome-vfs-module-2.0=2.24.4" + ] + }, + "jbig2dec-dev": { + "versions": { + "0.15-r0": 1545207929 + }, + "origin": "jbig2dec", + "dependencies": [ + "jbig2dec=0.15-r0" + ] + }, + "ipset": { + "versions": { + "7.1-r0": 1545163450 + }, + "origin": "ipset", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmnl.so.0" + ], + "provides": [ + "so:libipset.so.13=13.1.0", + "cmd:ipset" + ] + }, + "syslog-ng-redis": { + "versions": { + "3.19.1-r0": 1548543151 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libhiredis.so.0.14", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "xf86-input-evdev-dev": { + "versions": { + "2.10.6-r0": 1545229728 + }, + "origin": "xf86-input-evdev", + "dependencies": [ + "pkgconfig" + ], + "provides": [ + "pc:xorg-evdev=2.10.6" + ] + }, + "flac": { + "versions": { + "1.3.2-r2": 1545068441 + }, + "origin": "flac", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libogg.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libFLAC++.so.6=6.3.0", + "so:libFLAC.so.8=8.3.0", + "cmd:flac", + "cmd:metaflac" + ] + }, + "mpfr-dev": { + "versions": { + "3.1.5-r1": 1542301548 + }, + "origin": "mpfr3", + "dependencies": [ + "mpfr3=3.1.5-r1" + ] + }, + "py-redis": { + "versions": { + "2.10.6-r0": 1545301347 + }, + "origin": "py-redis" + }, + "liblcms": { + "versions": { + "1.19-r8": 1546249798 + }, + "origin": "lcms", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:liblcms.so.1=1.0.19" + ] + }, + "py-chardet": { + "versions": { + "3.0.4-r0": 1542825004 + }, + "origin": "py-chardet" + }, + "pciutils-doc": { + "versions": { + "3.6.2-r0": 1543921866 + }, + "origin": "pciutils" + }, + "linux-firmware-intel": { + "versions": { + "20190322-r0": 1554980651 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "xf86-video-ati-doc": { + "versions": { + "18.1.0-r0": 1545223268 + }, + "origin": "xf86-video-ati" + }, + "arpon": { + "versions": { + "3.0-r2": 1545224126 + }, + "origin": "arpon", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdnet.so.1", + "so:libnet.so.1", + "so:libpcap.so.1" + ], + "provides": [ + "cmd:arpon" + ] + }, + "perl-snmp-session": { + "versions": { + "1.13-r2": 1543999432 + }, + "origin": "perl-snmp-session", + "dependencies": [ + "perl" + ] + }, + "mpeg2dec": { + "versions": { + "0.5.1-r8": 1544000975 + }, + "origin": "libmpeg2", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libc.musl-x86_64.so.1", + "so:libmpeg2.so.0", + "so:libmpeg2convert.so.0" + ], + "provides": [ + "cmd:mpeg2dec" + ] + }, + "uwsgi-router_http": { + "versions": { + "2.0.17.1-r0": 1545062207 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "qemu-sparc64": { + "versions": { + "3.1.0-r3": 1551107302 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-sparc64" + ] + }, + "cd-discid-doc": { + "versions": { + "1.4-r2": 1545117997 + }, + "origin": "cd-discid" + }, + "libelf": { + "versions": { + "0.8.13-r3": 1542900243 + }, + "origin": "libelf", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libelf.so.0=0" + ] + }, + "patchutils": { + "versions": { + "0.3.4-r1": 1548427864 + }, + "origin": "patchutils", + "dependencies": [ + "perl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:combinediff", + "cmd:dehtmldiff", + "cmd:editdiff", + "cmd:espdiff", + "cmd:filterdiff", + "cmd:fixcvsdiff", + "cmd:flipdiff", + "cmd:grepdiff", + "cmd:interdiff", + "cmd:lsdiff", + "cmd:recountdiff", + "cmd:rediff", + "cmd:splitdiff", + "cmd:unwrapdiff" + ] + }, + "mqtt-exec-openrc": { + "versions": { + "0.4-r4": 1545162970 + }, + "origin": "mqtt-exec" + }, + "mcabber-doc": { + "versions": { + "1.1.0-r2": 1545214061 + }, + "origin": "mcabber" + }, + "nagios-plugins-breeze": { + "versions": { + "2.2.1-r6": 1543933902 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "perl" + ] + }, + "ghostscript-fonts": { + "versions": { + "8.11-r1": 1545062808 + }, + "origin": "ghostscript-fonts" + }, + "wireless-regdb-doc": { + "versions": { + "2018.05.31-r1": 1550837521 + }, + "origin": "wireless-regdb" + }, + "mpt-status-doc": { + "versions": { + "1.2.0-r0": 1545069138 + }, + "origin": "mpt-status" + }, + "py2-atomicwrites": { + "versions": { + "1.1.5-r0": 1542824872 + }, + "origin": "py-atomicwrites", + "dependencies": [ + "python2" + ] + }, + "a2ps": { + "versions": { + "4.14-r7": 1545209605 + }, + "origin": "a2ps", + "dependencies": [ + "ghostscript", + "imagemagick", + "perl", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:a2ps", + "cmd:card", + "cmd:composeglyphs", + "cmd:fixnt", + "cmd:fixps", + "cmd:ogonkify", + "cmd:pdiff", + "cmd:psmandup", + "cmd:psset", + "cmd:texi2dvi4a2ps" + ] + }, + "libxfont2": { + "versions": { + "2.0.3-r1": 1542924621 + }, + "origin": "libxfont2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfontenc.so.1", + "so:libfreetype.so.6", + "so:libz.so.1" + ], + "provides": [ + "so:libXfont2.so.2=2.0.0" + ] + }, + "tdb-doc": { + "versions": { + "1.3.16-r0": 1543933255 + }, + "origin": "tdb" + }, + "xdriinfo-doc": { + "versions": { + "1.0.5-r1": 1543927863 + }, + "origin": "xdriinfo" + }, + "mesa-dri-ati": { + "versions": { + "18.1.7-r2": 1554455969 + }, + "origin": "mesa", + "dependencies": [ + "so:libLLVM-5.0.so", + "so:libX11-xcb.so.1", + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libdrm_amdgpu.so.1", + "so:libdrm_freedreno.so.1", + "so:libdrm_intel.so.1", + "so:libdrm_nouveau.so.2", + "so:libdrm_radeon.so.1", + "so:libelf.so.0", + "so:libexpat.so.1", + "so:libgcc_s.so.1", + "so:libglapi.so.0", + "so:libstdc++.so.6", + "so:libxcb-dri2.so.0", + "so:libxcb-dri3.so.0", + "so:libxcb-present.so.0", + "so:libxcb-sync.so.1", + "so:libxcb-xfixes.so.0", + "so:libxcb.so.1", + "so:libxshmfence.so.1", + "so:libz.so.1" + ] + }, + "perl-proc-wait3-doc": { + "versions": { + "0.05-r1": 1543249816 + }, + "origin": "perl-proc-wait3" + }, + "lua5.1-yaml": { + "versions": { + "1.1.2-r1": 1544000067 + }, + "origin": "lua-yaml", + "dependencies": [ + "lua5.1", + "lua5.1-lub", + "so:libc.musl-x86_64.so.1" + ] + }, + "lxpolkit": { + "versions": { + "0.1.0-r1": 1545229284 + }, + "origin": "lxpolkit", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpolkit-agent-1.so.0", + "so:libpolkit-gobject-1.so.0" + ] + }, + "kamailio-python": { + "versions": { + "5.2.0-r1": 1546423173 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "spice-dev": { + "versions": { + "0.14.1-r6": 1548919556 + }, + "origin": "spice", + "dependencies": [ + "spice-protocol", + "pixman-dev", + "celt051-dev", + "libxinerama-dev", + "pc:gio-2.0>=2.32", + "pc:glib-2.0>=2.32", + "pc:gobject-2.0>=2.32", + "pc:openssl", + "pc:pixman-1>=0.17.7", + "pc:spice-protocol>=0.12.14", + "pkgconfig", + "spice-server=0.14.1-r6" + ], + "provides": [ + "pc:spice-server=0.14.1" + ] + }, + "xbanish": { + "versions": { + "1.6-r0": 1545290830 + }, + "origin": "xbanish", + "dependencies": [ + "so:libX11.so.6", + "so:libXfixes.so.3", + "so:libXi.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xbanish" + ] + }, + "boost-math": { + "versions": { + "1.67.0-r2": 1542823632 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_math_c99-mt.so.1.67.0=1.67.0", + "so:libboost_math_c99.so.1.67.0=1.67.0", + "so:libboost_math_c99f-mt.so.1.67.0=1.67.0", + "so:libboost_math_c99f.so.1.67.0=1.67.0", + "so:libboost_math_c99l-mt.so.1.67.0=1.67.0", + "so:libboost_math_c99l.so.1.67.0=1.67.0", + "so:libboost_math_tr1-mt.so.1.67.0=1.67.0", + "so:libboost_math_tr1.so.1.67.0=1.67.0", + "so:libboost_math_tr1f-mt.so.1.67.0=1.67.0", + "so:libboost_math_tr1f.so.1.67.0=1.67.0", + "so:libboost_math_tr1l-mt.so.1.67.0=1.67.0", + "so:libboost_math_tr1l.so.1.67.0=1.67.0" + ] + }, + "apr-util": { + "versions": { + "1.6.1-r5": 1545061010 + }, + "origin": "apr-util", + "dependencies": [ + "so:libapr-1.so.0", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libexpat.so.1" + ], + "provides": [ + "so:libaprutil-1.so.0=0.6.1" + ] + }, + "dhcpcd-ui": { + "versions": { + "0.7.5-r2": 1545060723 + }, + "origin": "dhcpcd-ui", + "dependencies": [ + "dhcpcd-dbus", + "hicolor-icon-theme", + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libnotify.so.4" + ], + "provides": [ + "cmd:dhcpcd-gtk", + "cmd:dhcpcd-online" + ] + }, + "powertop-doc": { + "versions": { + "2.9-r1": 1543927724 + }, + "origin": "powertop" + }, + "keyutils-libs": { + "versions": { + "1.6-r0": 1545837151 + }, + "origin": "keyutils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libkeyutils.so.1=1.8" + ] + }, + "bluez-btmon": { + "versions": { + "5.50-r0": 1545822205 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1", + "so:libudev.so.1" + ], + "provides": [ + "cmd:btmon" + ] + }, + "liboping": { + "versions": { + "1.10.0-r0": 1545214324 + }, + "origin": "liboping", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "so:liboping.so.0=0.3.0", + "cmd:noping", + "cmd:oping" + ] + }, + "rsyslog-mmjsonparse": { + "versions": { + "8.40.0-r3": 1548686790 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "avahi-ui-dev": { + "versions": { + "0.6.31-r7": 1545163746 + }, + "origin": "avahi-ui", + "dependencies": [ + "gtk+3.0-dev", + "gtk+2.0-dev", + "gdbm-dev", + "avahi-dev", + "avahi-ui-gtk3=0.6.31-r7", + "avahi-ui=0.6.31-r7", + "pc:avahi-client", + "pc:avahi-glib", + "pc:gtk+-2.0", + "pc:gtk+-3.0", + "pkgconfig" + ], + "provides": [ + "pc:avahi-ui-gtk3=0.6.31", + "pc:avahi-ui=0.6.31" + ] + }, + "libxau-dev": { + "versions": { + "1.0.8-r3": 1542822449 + }, + "origin": "libxau", + "dependencies": [ + "libxau=1.0.8-r3", + "pc:xproto", + "pkgconfig" + ], + "provides": [ + "pc:xau=1.0.8" + ] + }, + "libucontext": { + "versions": { + "0.1.3-r0": 1545207700 + }, + "origin": "libucontext", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libucontext.so.0=0" + ] + }, + "libgcc": { + "versions": { + "8.3.0-r0": 1554745497 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgcc_s.so.1=1" + ] + }, + "py-sphinx_rtd_theme": { + "versions": { + "0.4.2-r0": 1544791909 + }, + "origin": "py-sphinx_rtd_theme" + }, + "ninja": { + "versions": { + "1.8.2-r1": 1542822429 + }, + "origin": "ninja", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:ninja" + ] + }, + "apr-util-dbm_db": { + "versions": { + "1.6.1-r5": 1545061009 + }, + "origin": "apr-util", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so" + ] + }, + "unifont-doc": { + "versions": { + "11.0.01-r1": 1543077262 + }, + "origin": "unifont" + }, + "vanessa_socket-dev": { + "versions": { + "0.0.13-r0": 1545116977 + }, + "origin": "vanessa_socket", + "dependencies": [ + "popt-dev", + "vanessa_logger-dev", + "pkgconfig", + "vanessa_socket=0.0.13-r0" + ], + "provides": [ + "pc:vanessa-socket=0.0.13" + ] + }, + "perl-test-needs": { + "versions": { + "0.002005-r1": 1542821779 + }, + "origin": "perl-test-needs" + }, + "samba-server": { + "versions": { + "4.8.11-r1": 1555334975 + }, + "origin": "samba", + "dependencies": [ + "samba-common=4.8.11-r1", + "samba-initscript=4.8.11-r1", + "so:libCHARSET3-samba4.so", + "so:libauth-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libcli-cldap-samba4.so", + "so:libcli-nbt-samba4.so", + "so:libcli-smb-common-samba4.so", + "so:libcliauth-samba4.so", + "so:libcmdline-contexts-samba4.so", + "so:libdbwrap-samba4.so", + "so:libgenrand-samba4.so", + "so:libgse-samba4.so", + "so:liblibsmb-samba4.so", + "so:libmessages-dgm-samba4.so", + "so:libmsghdr-samba4.so", + "so:libndr-nbt.so.0", + "so:libndr-samba-samba4.so", + "so:libndr-standard.so.0", + "so:libndr.so.0", + "so:libpopt-samba3-samba4.so", + "so:libpopt.so.0", + "so:libreplace-samba4.so", + "so:libsamba-cluster-support-samba4.so", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-sockets-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsecrets3-samba4.so", + "so:libserver-id-db-samba4.so", + "so:libsmb-transport-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-base-samba4.so", + "so:libsmbd-shim-samba4.so", + "so:libsocket-blocking-samba4.so", + "so:libsys-rw-samba4.so", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-reg-samba4.so", + "so:libutil-tdb-samba4.so" + ], + "provides": [ + "so:libxattr-tdb-samba4.so=0", + "cmd:eventlogadm", + "cmd:nmbd", + "cmd:smbd", + "cmd:smbstatus" + ] + }, + "py2-libxslt": { + "versions": { + "1.1.33-r1": 1555485888 + }, + "origin": "libxslt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libexslt.so.0", + "so:libpython2.7.so.1.0", + "so:libxml2.so.2", + "so:libxslt.so.1" + ], + "provides": [ + "py-libxslt=1.1.33-r1" + ] + }, + "libiec61883-dev": { + "versions": { + "1.2.0-r2": 1543999348 + }, + "origin": "libiec61883", + "dependencies": [ + "libiec61883=1.2.0-r2", + "pc:libraw1394", + "pkgconfig" + ], + "provides": [ + "pc:libiec61883=1.2.0" + ] + }, + "wpa_supplicant": { + "versions": { + "2.7-r2": 1555501504 + }, + "origin": "wpa_supplicant", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libdbus-1.so.3", + "so:libnl-3.so.200", + "so:libnl-genl-3.so.200", + "so:libpcsclite.so.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:eapol_test", + "cmd:wpa_cli", + "cmd:wpa_passphrase", + "cmd:wpa_supplicant" + ] + }, + "boost-atomic": { + "versions": { + "1.67.0-r2": 1542823630 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libboost_atomic-mt.so.1.67.0=1.67.0" + ] + }, + "libpcrecpp": { + "versions": { + "8.42-r1": 1545067004 + }, + "origin": "pcre", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libpcre.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libpcrecpp.so.0=0.0.1" + ] + }, + "perl-class-method-modifiers-doc": { + "versions": { + "2.12-r0": 1545073304 + }, + "origin": "perl-class-method-modifiers" + }, + "taskd-doc": { + "versions": { + "1.1.0-r4": 1545163048 + }, + "origin": "taskd" + }, + "font-xfree86-type1": { + "versions": { + "1.0.4-r0": 1543242219 + }, + "origin": "font-xfree86-type1", + "dependencies": [ + "encodings", + "font-alias", + "mkfontscale", + "mkfontdir", + "fontconfig" + ] + }, + "libxkbfile-dev": { + "versions": { + "1.0.9-r2": 1542973200 + }, + "origin": "libxkbfile", + "dependencies": [ + "libxkbfile=1.0.9-r2", + "pc:kbproto", + "pc:x11", + "pkgconfig" + ], + "provides": [ + "pc:xkbfile=1.0.9" + ] + }, + "git-subtree-doc": { + "versions": { + "2.20.1-r0": 1545214197 + }, + "origin": "git" + }, + "libverto": { + "versions": { + "0.3.0-r1": 1543223528 + }, + "origin": "libverto", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libverto.so.1=1.0.0" + ] + }, + "pgtcl-doc": { + "versions": { + "2.1.0-r0": 1545301843 + }, + "origin": "pgtcl" + }, + "perl-net-ip": { + "versions": { + "1.26-r1": 1543924419 + }, + "origin": "perl-net-ip", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:ipcount", + "cmd:iptab" + ] + }, + "smem-doc": { + "versions": { + "1.4-r1": 1545060969 + }, + "origin": "smem" + }, + "py-unbound": { + "versions": { + "1.8.3-r1": 1555953584 + }, + "origin": "unbound", + "dependencies": [ + "dnssec-root", + "so:libc.musl-x86_64.so.1", + "so:libunbound.so.8" + ] + }, + "isl-dev": { + "versions": { + "0.18-r0": 1542301496 + }, + "origin": "isl", + "dependencies": [ + "gmp-dev", + "isl=0.18-r0", + "pkgconfig" + ], + "provides": [ + "pc:isl=0.18" + ] + }, + "libedit-dev": { + "versions": { + "20181209.3.1-r0": 1545839273 + }, + "origin": "libedit", + "dependencies": [ + "ncurses-dev", + "libedit=20181209.3.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:libedit=3.1" + ] + }, + "lxsession-doc": { + "versions": { + "0.5.3-r0": 1545299865 + }, + "origin": "lxsession" + }, + "cjdns-doc": { + "versions": { + "20.2-r1": 1546423249 + }, + "origin": "cjdns" + }, + "iniparser-dev": { + "versions": { + "4.1-r0": 1543928460 + }, + "origin": "iniparser", + "dependencies": [ + "iniparser=4.1-r0" + ] + }, + "libxrender": { + "versions": { + "0.9.10-r3": 1542822736 + }, + "origin": "libxrender", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXrender.so.1=1.3.0" + ] + }, + "nagios-plugins-pgsql": { + "versions": { + "2.2.1-r6": 1543933910 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5" + ] + }, + "mp3splt-doc": { + "versions": { + "2.6.2-r0": 1545207912 + }, + "origin": "mp3splt" + }, + "lua-mqtt-publish": { + "versions": { + "0.3-r0": 1542820949 + }, + "origin": "lua-mqtt-publish" + }, + "bluez-bccmd": { + "versions": { + "5.50-r0": 1545822205 + }, + "origin": "bluez", + "dependencies": [ + "consolekit2", + "dbus", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:bccmd" + ] + }, + "libgnome-lang": { + "versions": { + "2.32.1-r7": 1545299767 + }, + "origin": "libgnome" + }, + "libidl": { + "versions": { + "0.8.14-r2": 1543223347 + }, + "origin": "libidl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ], + "provides": [ + "so:libIDL-2.so.0=0.0.0", + "cmd:libIDL-config-2" + ] + }, + "sfcapd": { + "versions": { + "1.6.17-r0": 1545223025 + }, + "origin": "nfdump", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libnfdump-1.6.17.so" + ], + "provides": [ + "cmd:sfcapd" + ] + }, + "squid-lang-fr": { + "versions": { + "4.4-r1": 1545216325 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "nagios-plugins-real": { + "versions": { + "2.2.1-r6": 1543933911 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-io-stringy-doc": { + "versions": { + "2.111-r1": 1542924626 + }, + "origin": "perl-io-stringy" + }, + "snappy": { + "versions": { + "1.1.7-r1": 1545208922 + }, + "origin": "snappy", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libsnappy.so.1=1.1.7" + ] + }, + "nagios-plugins-ifstatus": { + "versions": { + "2.2.1-r6": 1543933906 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "perl" + ] + }, + "qemu-aarch64": { + "versions": { + "3.1.0-r3": 1551107299 + }, + "origin": "qemu", + "provides": [ + "cmd:qemu-aarch64" + ] + }, + "newsboat": { + "versions": { + "2.13-r0": 1545075360 + }, + "origin": "newsboat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libgcc_s.so.1", + "so:libintl.so.8", + "so:libjson-c.so.4", + "so:libncursesw.so.6", + "so:libsqlite3.so.0", + "so:libstdc++.so.6", + "so:libstfl.so.0", + "so:libxml2.so.2" + ], + "provides": [ + "newsbeuter=2.13-r0", + "cmd:newsboat", + "cmd:podboat" + ] + }, + "qemu-system-mipsel": { + "versions": { + "3.1.0-r3": 1551107304 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libstdc++.so.6", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-mipsel" + ] + }, + "py3-bcrypt": { + "versions": { + "3.1.4-r0": 1545216386 + }, + "origin": "py-bcrypt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "ppp-dev": { + "versions": { + "2.4.7-r6": 1543999019 + }, + "origin": "ppp" + }, + "mg": { + "versions": { + "20180824-r0": 1545208623 + }, + "origin": "mg", + "dependencies": [ + "so:libbsd.so.0", + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:mg" + ] + }, + "perl-test-harness-utils": { + "versions": { + "3.42-r0": 1543238857 + }, + "origin": "perl-test-harness", + "dependencies": [ + "perl-test-harness", + "perl" + ], + "provides": [ + "cmd:prove" + ] + }, + "libgcrypt-dev": { + "versions": { + "1.8.4-r0": 1545838380 + }, + "origin": "libgcrypt", + "dependencies": [ + "libgpg-error-dev", + "libgcrypt=1.8.4-r0" + ], + "provides": [ + "cmd:libgcrypt-config" + ] + }, + "micro-tetris": { + "versions": { + "1.2.1-r0": 1545209639 + }, + "origin": "micro-tetris", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:tetris" + ] + }, + "subunit": { + "versions": { + "1.2.0-r0": 1543933439 + }, + "origin": "subunit", + "dependencies": [ + "python2" + ], + "provides": [ + "cmd:subunit-1to2", + "cmd:subunit-2to1", + "cmd:subunit-diff", + "cmd:subunit-filter", + "cmd:subunit-ls", + "cmd:subunit-notify", + "cmd:subunit-output", + "cmd:subunit-stats", + "cmd:subunit-tags", + "cmd:subunit2csv", + "cmd:subunit2disk", + "cmd:subunit2gtk", + "cmd:subunit2junitxml", + "cmd:subunit2pyunit", + "cmd:tap2subunit" + ] + }, + "perl-want": { + "versions": { + "0.29-r1": 1543932635 + }, + "origin": "perl-want", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "libc6-compat": { + "versions": { + "1.1.20-r4": 1552989415 + }, + "origin": "musl", + "dependencies": [ + "musl=1.1.20-r4" + ] + }, + "py2-flask-wtf": { + "versions": { + "0.14.2-r0": 1545213678 + }, + "origin": "py-flask-wtf", + "dependencies": [ + "py2-flask", + "py2-wtforms", + "python2" + ] + }, + "samba-winbind-krb5-locator": { + "versions": { + "4.8.11-r1": 1555334974 + }, + "origin": "samba", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libwbclient.so.0" + ] + }, + "mtu": { + "versions": { + "1.5-r2": 1544000081 + }, + "origin": "pingu", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:mtu" + ] + }, + "ppp-daemon": { + "versions": { + "2.4.7-r6": 1543999024 + }, + "origin": "ppp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:poff", + "cmd:pon", + "cmd:pppd", + "cmd:pppdump", + "cmd:pppstats" + ] + }, + "py-flup": { + "versions": { + "1.0.2-r0": 1545163559 + }, + "origin": "py-flup", + "dependencies": [ + "python2" + ] + }, + "xmlrpc-c-abyss": { + "versions": { + "1.39.13-r0": 1545665219 + }, + "origin": "xmlrpc-c", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxmlrpc.so.3", + "so:libxmlrpc_server.so.3", + "so:libxmlrpc_util.so.3" + ], + "provides": [ + "so:libxmlrpc_abyss.so.3=3.39", + "so:libxmlrpc_server_abyss.so.3=3.39" + ] + }, + "smartmontools-openrc": { + "versions": { + "6.6-r1": 1545163012 + }, + "origin": "smartmontools" + }, + "email-doc": { + "versions": { + "3.1.4-r7": 1543925853 + }, + "origin": "email" + }, + "perl-yaml-syck": { + "versions": { + "1.31-r0": 1545060527 + }, + "origin": "perl-yaml-syck", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "jwm-lang": { + "versions": { + "2.3.7-r0": 1545292615 + }, + "origin": "jwm" + }, + "cadaver": { + "versions": { + "0.23.3-r4": 1545076658 + }, + "origin": "cadaver", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libneon.so.27" + ], + "provides": [ + "cmd:cadaver" + ] + }, + "logrotate-openrc": { + "versions": { + "3.15.0-r0": 1545857314 + }, + "origin": "logrotate" + }, + "texinfo-doc": { + "versions": { + "6.5-r1": 1542300391 + }, + "origin": "texinfo" + }, + "terminus-font": { + "versions": { + "4.47-r0": 1546434641 + }, + "origin": "terminus-font" + }, + "debian-archive-keyring-doc": { + "versions": { + "2018.1-r0": 1543999031 + }, + "origin": "debian-archive-keyring" + }, + "ltrace": { + "versions": { + "0.7.3-r1": 1544798548 + }, + "origin": "ltrace", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libelf.so.0" + ], + "provides": [ + "cmd:ltrace" + ] + }, + "fail2ban-doc": { + "versions": { + "0.10.3.1-r2": 1545300387 + }, + "origin": "fail2ban" + }, + "perl-digest-hmac": { + "versions": { + "1.03-r0": 1542924833 + }, + "origin": "perl-digest-hmac", + "dependencies": [ + "perl", + "perl-digest-sha1" + ] + }, + "gtk-engines-clearlooks": { + "versions": { + "2.21.0-r2": 1545289336 + }, + "origin": "gtk-engines", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ] + }, + "libxmu-dev": { + "versions": { + "1.1.2-r1": 1542883345 + }, + "origin": "libxmu", + "dependencies": [ + "util-linux-dev", + "libxmu=1.1.2-r1", + "pc:x11", + "pc:xext", + "pc:xproto", + "pc:xt", + "pkgconfig" + ], + "provides": [ + "pc:xmu=1.1.2", + "pc:xmuu=1.1.2" + ] + }, + "boost-thread": { + "versions": { + "1.67.0-r2": 1542823634 + }, + "origin": "boost", + "dependencies": [ + "so:libboost_system-mt.so.1.67.0", + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_thread-mt.so.1.67.0=1.67.0" + ] + }, + "lua5.3-sqlite": { + "versions": { + "0.9.5-r2": 1545214288 + }, + "origin": "lua-sqlite", + "dependencies": [ + "lua5.3", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "py2-cparser": { + "versions": { + "2.19-r0": 1546791397 + }, + "origin": "py-cparser", + "dependencies": [ + "python2" + ] + }, + "perl-module-pluggable": { + "versions": { + "5.2-r0": 1542973009 + }, + "origin": "perl-module-pluggable" + }, + "perl-test-script-doc": { + "versions": { + "1.25-r0": 1545061104 + }, + "origin": "perl-test-script" + }, + "libspf2-dev": { + "versions": { + "1.2.10-r3": 1546267711 + }, + "origin": "libspf2", + "dependencies": [ + "libspf2=1.2.10-r3" + ] + }, + "perl-socket6": { + "versions": { + "0.28-r0": 1543932730 + }, + "origin": "perl-socket6", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "awall-masquerade": { + "versions": { + "1.6.9-r0": 1548501700 + }, + "origin": "awall", + "dependencies": [ + "awall" + ] + }, + "ircservices-doc": { + "versions": { + "5.1.24-r4": 1545069548 + }, + "origin": "ircservices" + }, + "tevent": { + "versions": { + "0.9.37-r1": 1543933087 + }, + "origin": "tevent", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libtalloc.so.2" + ], + "provides": [ + "so:libtevent.so.0=0.9.37" + ] + }, + "farstream0.1-doc": { + "versions": { + "0.1.2-r2": 1545069353 + }, + "origin": "farstream0.1" + }, + "libssh-dev": { + "versions": { + "0.7.6-r1": 1545300250 + }, + "origin": "libssh", + "dependencies": [ + "libssh=0.7.6-r1", + "pkgconfig" + ], + "provides": [ + "pc:libssh=0.7.6", + "pc:libssh_threads=0.7.6" + ] + }, + "libsigc++-dev": { + "versions": { + "2.10.0-r1": 1543077169 + }, + "origin": "libsigc++", + "dependencies": [ + "libsigc++=2.10.0-r1", + "pkgconfig" + ], + "provides": [ + "pc:sigc++-2.0=2.10.0" + ] + }, + "perl-plack-doc": { + "versions": { + "1.0033-r0": 1544792449 + }, + "origin": "perl-plack" + }, + "qemu-system-moxie": { + "versions": { + "3.1.0-r3": 1551107304 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-moxie" + ] + }, + "perl-test-notabs": { + "versions": { + "2.02-r0": 1545069439 + }, + "origin": "perl-test-notabs" + }, + "py2-certifi": { + "versions": { + "2018.4.16-r0": 1542825013 + }, + "origin": "py-certifi", + "dependencies": [ + "python2" + ] + }, + "augeas-static": { + "versions": { + "1.11.0-r0": 1542924580 + }, + "origin": "augeas" + }, + "py-olefile": { + "versions": { + "0.43-r2": 1542883726 + }, + "origin": "py-olefile" + }, + "device-mapper": { + "versions": { + "2.02.182-r0": 1543928934 + }, + "origin": "lvm2", + "dependencies": [ + "lvm2-libs=2.02.182-r0", + "so:libc.musl-x86_64.so.1", + "so:libdevmapper.so.1.02" + ], + "provides": [ + "cmd:dmsetup", + "cmd:dmstats" + ] + }, + "npth-dev": { + "versions": { + "1.6-r0": 1543932200 + }, + "origin": "npth", + "dependencies": [ + "npth=1.6-r0" + ], + "provides": [ + "cmd:npth-config" + ] + }, + "xf86-video-fbdev-doc": { + "versions": { + "0.5.0-r2": 1545067179 + }, + "origin": "xf86-video-fbdev" + }, + "perl-package-stash-xs-doc": { + "versions": { + "0.28-r4": 1542845714 + }, + "origin": "perl-package-stash-xs" + }, + "py2-py": { + "versions": { + "1.5.3-r0": 1542824877 + }, + "origin": "py-py", + "dependencies": [ + "python2" + ] + }, + "imagemagick-doc": { + "versions": { + "7.0.8.44-r0": 1557126216 + }, + "origin": "imagemagick" + }, + "fluxbox-doc": { + "versions": { + "1.3.7-r3": 1545165090 + }, + "origin": "fluxbox" + }, + "mesa": { + "versions": { + "18.1.7-r2": 1554455972 + }, + "origin": "mesa" + }, + "aconf-mod-dns-zone": { + "versions": { + "0.7.1-r0": 1553432497 + }, + "origin": "aconf", + "dependencies": [ + "aconf" + ] + }, + "py-zope-interface": { + "versions": { + "4.6.0-r0": 1548537850 + }, + "origin": "py-zope-interface" + }, + "po4a-lang": { + "versions": { + "0.52-r1": 1542304730 + }, + "origin": "po4a", + "dependencies": [ + "perl", + "gettext" + ] + }, + "perl-package-anon-doc": { + "versions": { + "0.05-r4": 1545116806 + }, + "origin": "perl-package-anon" + }, + "pssh-doc": { + "versions": { + "2.3.1-r1": 1545302362 + }, + "origin": "pssh" + }, + "wavpack-dev": { + "versions": { + "5.1.0-r7": 1548939905 + }, + "origin": "wavpack", + "dependencies": [ + "pkgconfig", + "wavpack=5.1.0-r7" + ], + "provides": [ + "pc:wavpack=5.1.0" + ] + }, + "nagios-plugins-dns": { + "versions": { + "2.2.1-r6": 1543933904 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "bind-tools", + "so:libc.musl-x86_64.so.1" + ] + }, + "perl-future": { + "versions": { + "0.39-r0": 1544799049 + }, + "origin": "perl-future" + }, + "nrpe-plugin": { + "versions": { + "3.2.1-r0": 1545249892 + }, + "origin": "nrpe", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:nrpe-uninstall" + ] + }, + "liboping-dev": { + "versions": { + "1.10.0-r0": 1545214321 + }, + "origin": "liboping", + "dependencies": [ + "liboping=1.10.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:liboping=1.10.0" + ] + }, + "memcached": { + "versions": { + "1.5.12-r0": 1543923567 + }, + "origin": "memcached", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libsasl2.so.3", + "so:libseccomp.so.2" + ], + "provides": [ + "cmd:memcached" + ] + }, + "xorg-cf-files-doc": { + "versions": { + "1.0.6-r0": 1543927847 + }, + "origin": "xorg-cf-files" + }, + "elfutils-dev": { + "versions": { + "0.168-r2": 1546849158 + }, + "origin": "elfutils", + "dependencies": [ + "elfutils-libelf=0.168-r2" + ] + }, + "nagios-plugins-ide_smart": { + "versions": { + "2.2.1-r6": 1543933906 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "readline": { + "versions": { + "7.0.003-r1": 1542301065 + }, + "origin": "readline", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "so:libreadline.so.7=7.0" + ] + }, + "opus": { + "versions": { + "1.3-r0": 1545069004 + }, + "origin": "opus", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libopus.so.0=0.7.0" + ] + }, + "nodejs-doc": { + "versions": { + "10.14.2-r0": 1545345077 + }, + "origin": "nodejs" + }, + "cyrus-sasl-openrc": { + "versions": { + "2.1.27-r1": 1550353527 + }, + "origin": "cyrus-sasl" + }, + "perl-git-svn": { + "versions": { + "2.20.1-r0": 1545214194 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0" + ] + }, + "perl-dbix-dbschema": { + "versions": { + "0.39-r0": 1543998795 + }, + "origin": "perl-dbix-dbschema", + "dependencies": [ + "perl", + "perl-dbi" + ] + }, + "py2-pluggy": { + "versions": { + "0.7.1-r0": 1542824892 + }, + "origin": "py-pluggy", + "dependencies": [ + "python2" + ] + }, + "lxdm": { + "versions": { + "0.5.3-r1": 1545215914 + }, + "origin": "lxdm", + "dependencies": [ + "bash", + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libck-connector.so.0", + "so:libdbus-1.so.3", + "so:libgdk-x11-2.0.so.0", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0", + "so:libintl.so.8", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libxcb.so.1" + ], + "provides": [ + "cmd:lxdm", + "cmd:lxdm-binary", + "cmd:lxdm-config" + ] + }, + "uwsgi-python": { + "versions": { + "2.0.17.1-r0": 1545062205 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "perl-module-versions-report-doc": { + "versions": { + "1.06-r0": 1545067095 + }, + "origin": "perl-module-versions-report" + }, + "libressl2.7-libtls": { + "versions": { + "2.7.5-r0": 1551116832 + }, + "origin": "libressl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.43", + "so:libssl.so.45" + ], + "provides": [ + "so:libtls.so.17=17.0.1" + ] + }, + "libunique": { + "versions": { + "1.1.6-r6": 1545299842 + }, + "origin": "libunique", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libdbus-glib-1.so.2", + "so:libgdk-x11-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-x11-2.0.so.0" + ], + "provides": [ + "so:libunique-1.0.so.0=0.100.6" + ] + }, + "liblognorm-dev": { + "versions": { + "2.0.6-r1": 1548783946 + }, + "origin": "liblognorm", + "dependencies": [ + "libestr-dev", + "liblognorm=2.0.6-r1", + "pc:libfastjson", + "pkgconfig" + ], + "provides": [ + "pc:lognorm=2.0.6" + ] + }, + "fcgiwrap-doc": { + "versions": { + "1.1.0-r3": 1545665779 + }, + "origin": "fcgiwrap" + }, + "libstdc++": { + "versions": { + "8.3.0-r0": 1554745497 + }, + "origin": "gcc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libstdc++.so.6=6.0.25" + ] + }, + "abuild-rootbld": { + "versions": { + "3.3.1-r0": 1551786762 + }, + "origin": "abuild", + "dependencies": [ + "abuild", + "bubblewrap", + "gettext", + "git" + ] + }, + "iproute2": { + "versions": { + "4.19.0-r0": 1543223212 + }, + "origin": "iproute2", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libelf.so.0", + "so:libxtables.so.12" + ], + "provides": [ + "cmd:bridge", + "cmd:ctstat", + "cmd:genl", + "cmd:ifcfg", + "cmd:ifstat", + "cmd:ip", + "cmd:lnstat", + "cmd:nstat", + "cmd:routef", + "cmd:routel", + "cmd:rtacct", + "cmd:rtmon", + "cmd:rtpr", + "cmd:rtstat", + "cmd:ss", + "cmd:tc" + ] + }, + "pstree-doc": { + "versions": { + "2.39-r0": 1543926517 + }, + "origin": "pstree" + }, + "py3-pathlib2": { + "versions": { + "2.3.2-r0": 1542824911 + }, + "origin": "py-pathlib2", + "dependencies": [ + "py3-six", + "py3-scandir", + "python3" + ] + }, + "libcdio-paranoia-dev": { + "versions": { + "0.94_p1-r1": 1543248403 + }, + "origin": "libcdio-paranoia", + "dependencies": [ + "libcdio-paranoia=0.94_p1-r1", + "pc:libcdio", + "pkgconfig" + ] + }, + "sngtc_client-dev": { + "versions": { + "1.3.7-r1": 1545117158 + }, + "origin": "sngtc_client", + "dependencies": [ + "sngtc_client" + ] + }, + "postgresql-plperl": { + "versions": { + "11.2-r0": 1554274176 + }, + "origin": "postgresql", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libperl.so" + ] + }, + "perl-variable-magic-doc": { + "versions": { + "0.62-r0": 1542973100 + }, + "origin": "perl-variable-magic" + }, + "lua5.3-sircbot": { + "versions": { + "0.4-r2": 1543924833 + }, + "origin": "sircbot", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "galculator-doc": { + "versions": { + "2.1.4-r0": 1545076844 + }, + "origin": "galculator" + }, + "sdl_image": { + "versions": { + "1.2.12-r4": 1545302048 + }, + "origin": "sdl_image", + "dependencies": [ + "so:libSDL-1.2.so.0", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libSDL_image-1.2.so.0=0.8.4" + ] + }, + "espeak-dev": { + "versions": { + "1.48.04-r1": 1542973182 + }, + "origin": "espeak", + "dependencies": [ + "espeak=1.48.04-r1" + ] + }, + "squid-lang-de": { + "versions": { + "4.4-r1": 1545216324 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "perl-text-soundex-doc": { + "versions": { + "3.05-r0": 1543081697 + }, + "origin": "perl-text-soundex" + }, + "liblastfm": { + "versions": { + "1.0.9-r1": 1545207824 + }, + "origin": "liblastfm", + "dependencies": [ + "so:libQtCore.so.4", + "so:libQtDBus.so.4", + "so:libQtNetwork.so.4", + "so:libQtSql.so.4", + "so:libQtXml.so.4", + "so:libc.musl-x86_64.so.1", + "so:libfftw3f.so.3", + "so:libgcc_s.so.1", + "so:libsamplerate.so.0", + "so:libstdc++.so.6" + ], + "provides": [ + "so:liblastfm.so.1=1.0.9", + "so:liblastfm_fingerprint.so.1=1.0.9" + ] + }, + "haserl-lua5.1": { + "versions": { + "0.9.35-r1": 1542883797 + }, + "origin": "haserl", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblua.so.5" + ], + "provides": [ + "cmd:haserl-lua5.1" + ] + }, + "py3-click": { + "versions": { + "6.7-r2": 1545116990 + }, + "origin": "py-click", + "dependencies": [ + "python3" + ] + }, + "usbutils-doc": { + "versions": { + "010-r0": 1545075125 + }, + "origin": "usbutils" + }, + "libburn": { + "versions": { + "1.5.0-r0": 1545837389 + }, + "origin": "libburn", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libburn.so.4=4.103.0", + "cmd:cdrskin" + ] + }, + "gtk-update-icon-cache": { + "versions": { + "2.24.32-r1": 1543925514 + }, + "origin": "gtk+2.0", + "dependencies": [ + "hicolor-icon-theme", + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8" + ], + "provides": [ + "cmd:gtk-update-icon-cache" + ] + }, + "perl-extutils-cchecker": { + "versions": { + "0.10-r0": 1542883384 + }, + "origin": "perl-extutils-cchecker", + "dependencies": [ + "perl-test-exception" + ] + }, + "libfprint": { + "versions": { + "0.7.0-r1": 1545207879 + }, + "origin": "libfprint", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libnss3.so", + "so:libpixman-1.so.0", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libfprint.so.0=0.0.0" + ] + }, + "d-feet": { + "versions": { + "0.3.13-r1": 1545062610 + }, + "origin": "d-feet", + "dependencies": [ + "python2", + "py-gobject3" + ], + "provides": [ + "cmd:d-feet" + ] + }, + "perl-unix-syslog-doc": { + "versions": { + "1.1-r11": 1545067185 + }, + "origin": "perl-unix-syslog" + }, + "libwebsockets": { + "versions": { + "3.1.0-r0": 1545856933 + }, + "origin": "libwebsockets", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libssl.so.1.1" + ], + "provides": [ + "so:libwebsockets.so.14=14" + ] + }, + "bridge-utils": { + "versions": { + "1.6-r0": 1543935360 + }, + "origin": "bridge-utils", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:brctl" + ] + }, + "bluez-doc": { + "versions": { + "5.50-r0": 1545822205 + }, + "origin": "bluez" + }, + "gtk+3.0-doc": { + "versions": { + "3.24.1-r0": 1543927434 + }, + "origin": "gtk+3.0" + }, + "talloc": { + "versions": { + "2.1.14-r0": 1543220641 + }, + "origin": "talloc", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libtalloc.so.2=2.1.14" + ] + }, + "cairomm-dev": { + "versions": { + "1.12.2-r0": 1543077181 + }, + "origin": "cairomm", + "dependencies": [ + "libsigc++-dev", + "cairomm=1.12.2-r0", + "pc:cairo-ft", + "pc:cairo-pdf", + "pc:cairo-png", + "pc:cairo-ps", + "pc:cairo-svg", + "pc:cairo-xlib", + "pc:cairo-xlib-xrender", + "pc:cairo>=1.10.0", + "pc:sigc++-2.0>=2.5.1", + "pkgconfig" + ], + "provides": [ + "pc:cairomm-1.0=1.12.2", + "pc:cairomm-ft-1.0=1.12.2", + "pc:cairomm-pdf-1.0=1.12.2", + "pc:cairomm-png-1.0=1.12.2", + "pc:cairomm-ps-1.0=1.12.2", + "pc:cairomm-svg-1.0=1.12.2", + "pc:cairomm-xlib-1.0=1.12.2", + "pc:cairomm-xlib-xrender-1.0=1.12.2" + ] + }, + "aspell": { + "versions": { + "0.60.6.1-r13": 1542965830 + }, + "origin": "aspell", + "dependencies": [ + "so:libaspell.so.15", + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libncursesw.so.6", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:aspell" + ] + }, + "libcdio-doc": { + "versions": { + "0.94-r2": 1543248376 + }, + "origin": "libcdio" + }, + "py-unidecode": { + "versions": { + "1.0.23-r0": 1545299544 + }, + "origin": "py-unidecode", + "dependencies": [ + "py3-unidecode", + "py3-unidecode=1.0.23-r0" + ] + }, + "mcpp-libs": { + "versions": { + "2.7.2-r1": 1542883471 + }, + "origin": "mcpp", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libmcpp.so.0=0.3.0" + ] + }, + "perl-yaml-libyaml": { + "versions": { + "0.72-r0": 1545061179 + }, + "origin": "perl-yaml-libyaml", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "perl-yaml-xs=0.72-r0" + ] + }, + "lua5.1-bit32": { + "versions": { + "5.3.0-r2": 1546011988 + }, + "origin": "lua-bit32", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1" + ] + }, + "libmtp-dev": { + "versions": { + "1.1.15-r0": 1543924440 + }, + "origin": "libmtp", + "dependencies": [ + "libusb-compat-dev", + "libmtp=1.1.15-r0", + "pc:libusb-1.0", + "pkgconfig" + ], + "provides": [ + "pc:libmtp=1.1.15" + ] + }, + "lua5.1-rex-posix": { + "versions": { + "2.9.0-r0": 1545209201 + }, + "origin": "lua-rex", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "acf-mariadb": { + "versions": { + "0.2.0-r2": 1543998687 + }, + "origin": "acf-mariadb", + "dependencies": [ + "acf-core", + "mariadb", + "mariadb-client", + "lua-sql-mysql", + "acf-db-lib" + ] + }, + "sdl2-dev": { + "versions": { + "2.0.9-r0": 1543934457 + }, + "origin": "sdl2", + "dependencies": [ + "directfb-dev", + "pkgconfig", + "sdl2=2.0.9-r0" + ], + "provides": [ + "pc:sdl2=2.0.9", + "cmd:sdl2-config" + ] + }, + "py-funcsigs": { + "versions": { + "1.0.2-r1": 1542824900 + }, + "origin": "py-funcsigs" + }, + "xdg-utils-doc": { + "versions": { + "1.1.3-r0": 1545154524 + }, + "origin": "xdg-utils" + }, + "openssl-dbg": { + "versions": { + "1.1.1b-r1": 1552660099 + }, + "origin": "openssl" + }, + "qemu-system-ppc": { + "versions": { + "3.1.0-r3": 1551107304 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusb-1.0.so.0", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-ppc" + ] + }, + "samba-dev": { + "versions": { + "4.8.11-r1": 1555334971 + }, + "origin": "samba", + "dependencies": [ + "libsmbclient=4.8.11-r1", + "libwbclient=4.8.11-r1", + "pc:ldb", + "pc:talloc", + "pc:tevent", + "pkgconfig", + "samba-client-libs=4.8.11-r1", + "samba-common-libs=4.8.11-r1", + "samba-common-server-libs=4.8.11-r1", + "samba-dc-libs=4.8.11-r1", + "samba-libnss-winbind=4.8.11-r1", + "samba-libs=4.8.11-r1" + ], + "provides": [ + "pc:dcerpc=0.0.1", + "pc:dcerpc_samr=0.0.1", + "pc:dcerpc_server=0.0.1", + "pc:ndr=0.1.0", + "pc:ndr_krb5pac=0.0.1", + "pc:ndr_nbt=0.0.1", + "pc:ndr_standard=0.0.1", + "pc:netapi=0", + "pc:samba-credentials=0.0.1", + "pc:samba-hostconfig=0.0.1", + "pc:samba-policy=0.0.1", + "pc:samba-util=0.0.1", + "pc:samdb=0.0.1", + "pc:smbclient=0.3.1", + "pc:wbclient=0.14" + ] + }, + "rsyslog-mmsequence": { + "versions": { + "8.40.0-r3": 1548686791 + }, + "origin": "rsyslog", + "dependencies": [ + "rsyslog=8.40.0-r3", + "so:libc.musl-x86_64.so.1" + ] + }, + "radvd": { + "versions": { + "2.17-r2": 1545254223 + }, + "origin": "radvd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:radvd", + "cmd:radvdump" + ] + }, + "tumbler": { + "versions": { + "0.2.3-r0": 1545859329 + }, + "origin": "tumbler", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libcurl.so.4", + "so:libfreetype.so.6", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libintl.so.8", + "so:libjpeg.so.8", + "so:libpng16.so.16", + "so:libpoppler-glib.so.8" + ], + "provides": [ + "so:libtumbler-1.so.0=0.0.0" + ] + }, + "perl-log-dispatch": { + "versions": { + "2.68-r0": 1545209654 + }, + "origin": "perl-log-dispatch", + "dependencies": [ + "perl-module-runtime", + "perl-params-validate", + "perl-dist-checkconflicts", + "perl-devel-globaldestruction" + ] + }, + "qemu-system-or1k": { + "versions": { + "3.1.0-r3": 1551107304 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-or1k" + ] + }, + "nettle": { + "versions": { + "3.4.1-r0": 1544791824 + }, + "origin": "nettle", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10" + ], + "provides": [ + "so:libhogweed.so.4=4.5", + "so:libnettle.so.6=6.5" + ] + }, + "libtls-standalone-dev": { + "versions": { + "2.7.4-r6": 1546784623 + }, + "origin": "libtls-standalone", + "dependencies": [ + "openssl-dev", + "libtls-standalone=2.7.4-r6", + "pc:libcrypto", + "pc:libssl", + "pkgconfig" + ], + "provides": [ + "pc:libtls-standalone=2.7.4" + ] + }, + "rtnppd": { + "versions": { + "1.7b-r8": 1543935345 + }, + "origin": "rtnppd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:rtnppd", + "cmd:testq", + "cmd:tnpppage" + ] + }, + "mp3splt": { + "versions": { + "2.6.2-r0": 1545207912 + }, + "origin": "mp3splt", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libmp3splt.so.0" + ], + "provides": [ + "cmd:mp3splt", + "cmd:oggsplt" + ] + }, + "json-c-dev": { + "versions": { + "0.13.1-r0": 1543923376 + }, + "origin": "json-c", + "dependencies": [ + "json-c=0.13.1-r0", + "pkgconfig" + ], + "provides": [ + "pc:json-c=0.13.1" + ] + }, + "libogg-doc": { + "versions": { + "1.3.3-r2": 1543925775 + }, + "origin": "libogg" + }, + "kbd-doc": { + "versions": { + "2.0.4-r3": 1545215189 + }, + "origin": "kbd" + }, + "lua5.3-stdlib": { + "versions": { + "41.2.1-r0": 1545292925 + }, + "origin": "lua-stdlib" + }, + "libunwind-doc": { + "versions": { + "1.2.1-r3": 1545915765 + }, + "origin": "libunwind" + }, + "xprop-doc": { + "versions": { + "1.2.3-r0": 1543254086 + }, + "origin": "xprop" + }, + "perl-test-sharedfork": { + "versions": { + "0.35-r0": 1543249824 + }, + "origin": "perl-test-sharedfork", + "dependencies": [ + "perl", + "perl-test-requires" + ] + }, + "uwsgi-stats_pusher_statsd": { + "versions": { + "2.0.17.1-r0": 1545062211 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "cksfv-doc": { + "versions": { + "1.3.14-r4": 1545069444 + }, + "origin": "cksfv" + }, + "py-ttystatus": { + "versions": { + "0.23-r1": 1545060579 + }, + "origin": "py-ttystatus", + "dependencies": [ + "python2" + ] + }, + "tinyxml-dev": { + "versions": { + "2.6.2-r1": 1545292911 + }, + "origin": "tinyxml", + "dependencies": [ + "tinyxml=2.6.2-r1" + ] + }, + "libsecret-doc": { + "versions": { + "0.18.6-r0": 1545291103 + }, + "origin": "libsecret" + }, + "perl-string-shellquote": { + "versions": { + "1.04-r0": 1545302152 + }, + "origin": "perl-string-shellquote", + "provides": [ + "cmd:shell-quote" + ] + }, + "boost-doc": { + "versions": { + "1.67.0-r2": 1542823630 + }, + "origin": "boost" + }, + "linux-firmware-qcom": { + "versions": { + "20190322-r0": 1554980649 + }, + "origin": "linux-firmware", + "provides": [ + "linux-firmware-any" + ] + }, + "libexecinfo": { + "versions": { + "1.1-r0": 1545117123 + }, + "origin": "libexecinfo", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libexecinfo.so.1=1" + ] + }, + "xf86-video-vmware": { + "versions": { + "13.3.0-r0": 1545076717 + }, + "origin": "xf86-video-vmware", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdrm.so.2", + "so:libudev.so.1", + "so:libxatracker.so.2" + ] + }, + "sed-doc": { + "versions": { + "4.5-r0": 1542822960 + }, + "origin": "sed" + }, + "farstream0.1-dev": { + "versions": { + "0.1.2-r2": 1545069353 + }, + "origin": "farstream0.1", + "dependencies": [ + "libnice-dev", + "gstreamer0.10-dev", + "gst-plugins-base0.10-dev", + "farstream0.1=0.1.2-r2", + "pc:gstreamer-0.10", + "pc:gstreamer-base-0.10", + "pkgconfig" + ], + "provides": [ + "pc:farstream-0.1=0.1.2" + ] + }, + "py3-libxml2": { + "versions": { + "2.9.9-r1": 1551105514 + }, + "origin": "libxml2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0", + "so:libxml2.so.2" + ] + }, + "statserial": { + "versions": { + "1.1-r4": 1545293154 + }, + "origin": "statserial", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6" + ], + "provides": [ + "cmd:statserial" + ] + }, + "perl-type-tiny-doc": { + "versions": { + "1.000006-r0": 1542973080 + }, + "origin": "perl-type-tiny" + }, + "perl-class-inspector": { + "versions": { + "1.32-r0": 1543238829 + }, + "origin": "perl-class-inspector", + "dependencies": [ + "perl" + ] + }, + "lmdb-dev": { + "versions": { + "0.9.23-r0": 1547025175 + }, + "origin": "lmdb", + "dependencies": [ + "lmdb=0.9.23-r0", + "pkgconfig" + ] + }, + "libxml2-utils": { + "versions": { + "2.9.9-r1": 1551105514 + }, + "origin": "libxml2", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libxml2.so.2" + ], + "provides": [ + "cmd:xmlcatalog", + "cmd:xmllint" + ] + }, + "obex-data-server": { + "versions": { + "0.4.6-r4": 1545069104 + }, + "origin": "obex-data-server", + "dependencies": [ + "so:libbluetooth.so.3", + "so:libc.musl-x86_64.so.1", + "so:libdbus-glib-1.so.2", + "so:libgdk_pixbuf-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libopenobex.so.2", + "so:libusb-0.1.so.4" + ], + "provides": [ + "cmd:obex-data-server" + ] + }, + "libdvdread-dev": { + "versions": { + "6.0.0-r0": 1545838475 + }, + "origin": "libdvdread", + "dependencies": [ + "libdvdread=6.0.0-r0", + "pc:libdvdcss>=1.2", + "pkgconfig" + ], + "provides": [ + "pc:dvdread=6.0.0" + ] + }, + "lynx": { + "versions": { + "2.8.8_p2-r8": 1545154518 + }, + "origin": "lynx", + "dependencies": [ + "gzip", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1", + "so:libncursesw.so.6", + "so:libssl.so.1.1" + ], + "provides": [ + "cmd:lynx" + ] + }, + "clamav-dev": { + "versions": { + "0.100.3-r0": 1555507335 + }, + "origin": "clamav", + "dependencies": [ + "openssl-dev", + "clamav-libs=0.100.3-r0", + "pkgconfig" + ], + "provides": [ + "pc:libclamav=0.100.3", + "cmd:clamav-config" + ] + }, + "py-django": { + "versions": { + "1.11.20-r0": 1551267364 + }, + "origin": "py-django", + "dependencies": [ + "py-tz", + "py3-django=1.11.20-r0" + ] + }, + "libsrtp": { + "versions": { + "1.5.4-r1": 1544000954 + }, + "origin": "libsrtp" + }, + "krb5-libs": { + "versions": { + "1.15.5-r0": 1548094458 + }, + "origin": "krb5", + "dependencies": [ + "krb5-conf", + "so:libc.musl-x86_64.so.1", + "so:libcom_err.so.2", + "so:libcrypto.so.1.1", + "so:libkeyutils.so.1", + "so:libssl.so.1.1", + "so:libverto.so.1" + ], + "provides": [ + "so:libgssapi_krb5.so.2=2.2", + "so:libgssrpc.so.4=4.2", + "so:libk5crypto.so.3=3.1", + "so:libkadm5clnt_mit.so.11=11.0", + "so:libkadm5srv_mit.so.11=11.0", + "so:libkdb5.so.8=8.0", + "so:libkrad.so.0=0.0", + "so:libkrb5.so.3=3.3", + "so:libkrb5support.so.0=0.1" + ] + }, + "terminus-font-doc": { + "versions": { + "4.47-r0": 1546434641 + }, + "origin": "terminus-font" + }, + "avahi-lang": { + "versions": { + "0.7-r1": 1543925312 + }, + "origin": "avahi" + }, + "dhcp-dbg": { + "versions": { + "4.4.1-r1": 1543928453 + }, + "origin": "dhcp" + }, + "squid-lang-nl": { + "versions": { + "4.4-r1": 1545216328 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "uwsgi-tuntap": { + "versions": { + "2.0.17.1-r0": 1545062213 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "py-twisted-doc": { + "versions": { + "17.1.0-r1": 1546513851 + }, + "origin": "py-twisted" + }, + "rtapd-dbg": { + "versions": { + "1.7-r6": 1545301051 + }, + "origin": "rtapd", + "dependencies": [ + "rtnppd" + ] + }, + "db-utils": { + "versions": { + "5.3.28-r1": 1542819053 + }, + "origin": "db", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libdb-5.3.so" + ], + "provides": [ + "cmd:db_archive", + "cmd:db_checkpoint", + "cmd:db_deadlock", + "cmd:db_dump", + "cmd:db_hotbackup", + "cmd:db_load", + "cmd:db_log_verify", + "cmd:db_printlog", + "cmd:db_recover", + "cmd:db_replicate", + "cmd:db_stat", + "cmd:db_tuner", + "cmd:db_upgrade", + "cmd:db_verify" + ] + }, + "s6-linux-init-dev": { + "versions": { + "0.4.0.0-r0": 1545292929 + }, + "origin": "s6-linux-init" + }, + "libcddb-dev": { + "versions": { + "1.3.2-r3": 1543248357 + }, + "origin": "libcddb", + "dependencies": [ + "libcddb=1.3.2-r3", + "pkgconfig" + ], + "provides": [ + "pc:libcddb=1.3.2" + ] + }, + "py-dbus-dev": { + "versions": { + "1.2.8-r1": 1543925184 + }, + "origin": "py-dbus", + "dependencies": [ + "py-dbus", + "pc:dbus-1>=1.0", + "pkgconfig" + ], + "provides": [ + "pc:dbus-python=1.2.8" + ] + }, + "xfontsel": { + "versions": { + "1.0.6-r0": 1545215013 + }, + "origin": "xfontsel", + "dependencies": [ + "so:libX11.so.6", + "so:libXaw.so.7", + "so:libXmu.so.6", + "so:libXt.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xfontsel" + ] + }, + "postgresql-bdr-client": { + "versions": { + "9.4.14_p1-r1": 1543223164 + }, + "origin": "postgresql-bdr", + "dependencies": [ + "bash", + "libpq", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5", + "so:libreadline.so.7", + "so:libssl.so.1.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:clusterdb", + "cmd:createdb", + "cmd:createuser", + "cmd:dropdb", + "cmd:dropuser", + "cmd:pg_basebackup", + "cmd:pg_dump", + "cmd:pg_dumpall", + "cmd:pg_isready", + "cmd:pg_recvlogical", + "cmd:pg_restore", + "cmd:psql", + "cmd:reindexdb", + "cmd:vacuumdb" + ] + }, + "py2-pylast": { + "versions": { + "2.4.0-r0": 1545301101 + }, + "origin": "py-pylast", + "dependencies": [ + "python2" + ] + }, + "kamailio-json": { + "versions": { + "5.2.0-r1": 1546423171 + }, + "origin": "kamailio", + "dependencies": [ + "kamailio", + "so:libc.musl-x86_64.so.1", + "so:libevent-2.1.so.6", + "so:libjson-c.so.4", + "so:libsrutils.so.1" + ] + }, + "dbus-doc": { + "versions": { + "1.10.24-r1": 1542824378 + }, + "origin": "dbus" + }, + "pingu": { + "versions": { + "1.5-r2": 1544000081 + }, + "origin": "pingu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libev.so.4" + ], + "provides": [ + "cmd:pingu", + "cmd:pinguctl" + ] + }, + "librsync-doc": { + "versions": { + "2.0.2-r1": 1543932723 + }, + "origin": "librsync" + }, + "libvorbis-doc": { + "versions": { + "1.3.6-r2": 1549615842 + }, + "origin": "libvorbis" + }, + "libmpeg2-dev": { + "versions": { + "0.5.1-r8": 1544000975 + }, + "origin": "libmpeg2", + "dependencies": [ + "libmpeg2=0.5.1-r8", + "pkgconfig" + ], + "provides": [ + "pc:libmpeg2=0.5.1", + "pc:libmpeg2convert=0.5.1" + ] + }, + "libunistring-dev": { + "versions": { + "0.9.10-r0": 1542304035 + }, + "origin": "libunistring", + "dependencies": [ + "libunistring=0.9.10-r0" + ] + }, + "lua5.2-sql-sqlite3": { + "versions": { + "2.3.5-r2": 1543924402 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.2", + "so:libc.musl-x86_64.so.1", + "so:libsqlite3.so.0" + ] + }, + "perl-package-stash-xs": { + "versions": { + "0.28-r4": 1542845717 + }, + "origin": "perl-package-stash-xs", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ] + }, + "samba-server-libs": { + "versions": { + "4.8.11-r1": 1555334975 + }, + "origin": "samba", + "dependencies": [ + "so:libMESSAGING-SEND-samba4.so", + "so:libc.musl-x86_64.so.1", + "so:libdbwrap-samba4.so", + "so:libdcerpc-binding.so.0", + "so:libldap-2.4.so.2", + "so:libmessages-dgm-samba4.so", + "so:libmessages-util-samba4.so", + "so:libndr-samba-samba4.so", + "so:libndr-samba4.so", + "so:libndr.so.0", + "so:libsamba-debug-samba4.so", + "so:libsamba-errors.so.1", + "so:libsamba-hostconfig.so.0", + "so:libsamba-modules-samba4.so", + "so:libsamba-passdb.so.0", + "so:libsamba-security-samba4.so", + "so:libsamba-util.so.0", + "so:libsamba3-util-samba4.so", + "so:libsecrets3-samba4.so", + "so:libserver-id-db-samba4.so", + "so:libsmbconf.so.0", + "so:libsmbd-base-samba4.so", + "so:libsmbldap.so.2", + "so:libsmbldaphelper-samba4.so", + "so:libtalloc-report-samba4.so", + "so:libtalloc.so.2", + "so:libtevent-util.so.0", + "so:libtevent.so.0", + "so:libutil-tdb-samba4.so" + ], + "provides": [ + "so:libMESSAGING-samba4.so=0", + "so:libdcerpc-samba4.so=0", + "so:libidmap-samba4.so=0", + "so:libnon-posix-acls-samba4.so=0", + "so:libnss-info-samba4.so=0" + ] + }, + "meson-doc": { + "versions": { + "0.48.2-r0": 1544791717 + }, + "origin": "meson" + }, + "squid-lang-ka": { + "versions": { + "4.4-r1": 1545216327 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "libvorbis": { + "versions": { + "1.3.6-r2": 1549615842 + }, + "origin": "libvorbis", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libogg.so.0" + ], + "provides": [ + "so:libvorbis.so.0=0.4.8", + "so:libvorbisenc.so.2=2.0.11", + "so:libvorbisfile.so.3=3.3.7" + ] + }, + "dnstop-doc": { + "versions": { + "20140915-r3": 1545165119 + }, + "origin": "dnstop" + }, + "perl-ipc-system-simple": { + "versions": { + "1.25-r0": 1542924670 + }, + "origin": "perl-ipc-system-simple" + }, + "nagios-plugins-mysql": { + "versions": { + "2.2.1-r6": 1543933908 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "libc-utils": { + "versions": { + "0.7.1-r0": 1542302753 + }, + "origin": "libc-dev", + "dependencies": [ + "musl-utils" + ] + }, + "rabbitmq-c-doc": { + "versions": { + "0.8.0-r5": 1543923901 + }, + "origin": "rabbitmq-c" + }, + "boost-regex": { + "versions": { + "1.67.0-r2": 1542823633 + }, + "origin": "boost", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1" + ], + "provides": [ + "so:libboost_regex-mt.so.1.67.0=1.67.0", + "so:libboost_regex.so.1.67.0=1.67.0" + ] + }, + "nagios-plugins-disk": { + "versions": { + "2.2.1-r6": 1543933904 + }, + "origin": "nagios-plugins", + "dependencies": [ + "nagios-plugins", + "so:libc.musl-x86_64.so.1" + ] + }, + "graphviz-dev": { + "versions": { + "2.40.1-r1": 1543926740 + }, + "origin": "graphviz", + "dependencies": [ + "zlib-dev", + "libpng-dev", + "libjpeg-turbo-dev", + "expat-dev", + "fontconfig-dev", + "libsm-dev", + "libxext-dev", + "cairo-dev", + "pango-dev", + "librsvg-dev", + "gmp-dev", + "freetype-dev", + "graphviz=2.40.1-r1", + "pkgconfig" + ], + "provides": [ + "pc:libcdt=2.40.1", + "pc:libcgraph=2.40.1", + "pc:libgvc=2.40.1", + "pc:libgvpr=2.40.1", + "pc:liblab_gamut=2.40.1", + "pc:libpathplan=2.40.1", + "pc:libxdot=2.40.1" + ] + }, + "perl-inline-doc": { + "versions": { + "0.80-r1": 1545067070 + }, + "origin": "perl-inline" + }, + "perl-sub-exporter": { + "versions": { + "0.987-r0": 1542883253 + }, + "origin": "perl-sub-exporter", + "dependencies": [ + "perl-data-optlist", + "perl-sub-install", + "perl-params-util" + ] + }, + "lua-discount": { + "versions": { + "1.2.10.1-r4": 1545209125 + }, + "origin": "lua-discount" + }, + "mtools": { + "versions": { + "4.0.23-r0": 1545858117 + }, + "origin": "mtools", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:amuFormat.sh", + "cmd:lz", + "cmd:mattrib", + "cmd:mbadblocks", + "cmd:mcat", + "cmd:mcd", + "cmd:mcheck", + "cmd:mclasserase", + "cmd:mcomp", + "cmd:mcopy", + "cmd:mdel", + "cmd:mdeltree", + "cmd:mdir", + "cmd:mdu", + "cmd:mformat", + "cmd:minfo", + "cmd:mkmanifest", + "cmd:mlabel", + "cmd:mmd", + "cmd:mmount", + "cmd:mmove", + "cmd:mpartition", + "cmd:mrd", + "cmd:mren", + "cmd:mshortname", + "cmd:mshowfat", + "cmd:mtools", + "cmd:mtoolstest", + "cmd:mtype", + "cmd:mxtar", + "cmd:mzip", + "cmd:tgz", + "cmd:uz" + ] + }, + "libnotify": { + "versions": { + "0.7.7-r2": 1544001122 + }, + "origin": "libnotify", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgdk_pixbuf-2.0.so.0", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0" + ], + "provides": [ + "so:libnotify.so.4=4.0.0", + "cmd:notify-send" + ] + }, + "glib-doc": { + "versions": { + "2.58.1-r2": 1545290824 + }, + "origin": "glib" + }, + "libusb": { + "versions": { + "1.0.22-r0": 1543223279 + }, + "origin": "libusb", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libusb-1.0.so.0=0.1.0" + ] + }, + "py-gobject": { + "versions": { + "2.28.7-r0": 1543927528 + }, + "origin": "py-gobject", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libffi.so.6", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgthread-2.0.so.0" + ], + "provides": [ + "so:libpyglib-2.0-python.so.0=0.0.0", + "cmd:pygobject-codegen-2.0" + ] + }, + "slang": { + "versions": { + "2.3.2-r0": 1543924691 + }, + "origin": "slang", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpcre.so.1" + ], + "provides": [ + "so:libslang.so.2=2.3.2", + "cmd:slsh" + ] + }, + "cups-filters-doc": { + "versions": { + "1.21.6-r0": 1545820385 + }, + "origin": "cups-filters" + }, + "samba-libs": { + "versions": { + "4.8.11-r1": 1555334977 + }, + "origin": "samba", + "dependencies": [ + "so:libasn1-samba4.so.8", + "so:libc.musl-x86_64.so.1", + "so:libcap.so.2", + "so:libcom_err.so.2", + "so:libgnutls.so.30", + "so:libgssapi-samba4.so.2", + "so:libkrb5-samba4.so.26", + "so:liblber-2.4.so.2", + "so:libldap-2.4.so.2", + "so:libldb.so.1", + "so:libpopt.so.0", + "so:libreplace-samba4.so", + "so:libtalloc.so.2", + "so:libtdb.so.1", + "so:libtevent.so.0", + "so:libwbclient.so.0", + "so:libz.so.1" + ], + "provides": [ + "so:libCHARSET3-samba4.so=0", + "so:libaddns-samba4.so=0", + "so:libasn1util-samba4.so=0", + "so:libauthkrb5-samba4.so=0", + "so:libcli-cldap-samba4.so=0", + "so:libcli-ldap-common-samba4.so=0", + "so:libcli-nbt-samba4.so=0", + "so:libcli-smb-common-samba4.so=0", + "so:libcliauth-samba4.so=0", + "so:libcmocka-samba4.so=0", + "so:libcommon-auth-samba4.so=0", + "so:libdbwrap-samba4.so=0", + "so:libflag-mapping-samba4.so=0", + "so:libgenrand-samba4.so=0", + "so:libgensec-samba4.so=0", + "so:libgse-samba4.so=0", + "so:libinterfaces-samba4.so=0", + "so:libiov-buf-samba4.so=0", + "so:libkrb5samba-samba4.so=0", + "so:libldbsamba-samba4.so=0", + "so:libmessages-dgm-samba4.so=0", + "so:libmessages-util-samba4.so=0", + "so:libmsghdr-samba4.so=0", + "so:libndr-krb5pac.so.0=0.0.1", + "so:libndr-nbt.so.0=0.0.1", + "so:libndr-samba-samba4.so=0", + "so:libndr-standard.so.0=0.0.1", + "so:libndr.so.0=0.1.0", + "so:libpopt-samba3-samba4.so=0", + "so:libsamba-cluster-support-samba4.so=0", + "so:libsamba-credentials.so.0=0.0.1", + "so:libsamba-debug-samba4.so=0", + "so:libsamba-errors.so.1=1", + "so:libsamba-hostconfig.so.0=0.0.1", + "so:libsamba-modules-samba4.so=0", + "so:libsamba-security-samba4.so=0", + "so:libsamba-sockets-samba4.so=0", + "so:libsamba-util.so.0=0.0.1", + "so:libsamba3-util-samba4.so=0", + "so:libsamdb-common-samba4.so=0", + "so:libsamdb.so.0=0.0.1", + "so:libsecrets3-samba4.so=0", + "so:libserver-id-db-samba4.so=0", + "so:libserver-role-samba4.so=0", + "so:libsmb-transport-samba4.so=0", + "so:libsmbconf.so.0=0", + "so:libsmbd-shim-samba4.so=0", + "so:libsocket-blocking-samba4.so=0", + "so:libsys-rw-samba4.so=0", + "so:libtalloc-report-samba4.so=0", + "so:libtdb-wrap-samba4.so=0", + "so:libtevent-util.so.0=0.0.1", + "so:libtime-basic-samba4.so=0", + "so:libutil-cmdline-samba4.so=0", + "so:libutil-reg-samba4.so=0", + "so:libutil-setid-samba4.so=0", + "so:libutil-tdb-samba4.so=0" + ] + }, + "newt-lang": { + "versions": { + "0.52.20-r0": 1543924704 + }, + "origin": "newt" + }, + "pound-doc": { + "versions": { + "2.8-r0": 1545207004 + }, + "origin": "pound" + }, + "perl-apache-session": { + "versions": { + "1.93-r0": 1543927699 + }, + "origin": "perl-apache-session" + }, + "librsvg-doc": { + "versions": { + "2.40.20-r0": 1543926595 + }, + "origin": "librsvg" + }, + "py-twisted": { + "versions": { + "17.1.0-r1": 1546513851 + }, + "origin": "py-twisted", + "dependencies": [ + "py-crypto", + "py-zope-interface", + "py-constantly", + "py-incremental", + "so:libc.musl-x86_64.so.1", + "so:libpython2.7.so.1.0" + ], + "provides": [ + "cmd:cftp", + "cmd:ckeygen", + "cmd:conch", + "cmd:mailmail", + "cmd:pyhtmlizer", + "cmd:tkconch", + "cmd:trial", + "cmd:twist", + "cmd:twistd" + ] + }, + "lua5.3": { + "versions": { + "5.3.5-r2": 1557162502 + }, + "origin": "lua5.3", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblinenoise.so.0", + "so:liblua-5.3.so.0" + ], + "provides": [ + "lua", + "cmd:lua5.3", + "cmd:luac5.3" + ] + }, + "pstree": { + "versions": { + "2.39-r0": 1543926521 + }, + "origin": "pstree", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pstree" + ] + }, + "py-libmount": { + "versions": { + "2.33-r0": 1545307437 + }, + "origin": "util-linux", + "dependencies": [ + "findmnt", + "so:libc.musl-x86_64.so.1", + "so:libmount.so.1", + "so:libpython2.7.so.1.0" + ] + }, + "libffi-doc": { + "versions": { + "3.2.1-r6": 1545154420 + }, + "origin": "libffi" + }, + "api-sanity-checker": { + "versions": { + "1.98.7-r0": 1545069120 + }, + "origin": "api-sanity-checker", + "dependencies": [ + "perl", + "build-base" + ], + "provides": [ + "cmd:api-sanity-checker" + ] + }, + "zfs-doc": { + "versions": { + "0.7.12-r1": 1552933679 + }, + "origin": "zfs" + }, + "collectd-doc": { + "versions": { + "5.8.0-r3": 1546422675 + }, + "origin": "collectd" + }, + "lua-sql-sqlite3": { + "versions": { + "2.3.5-r2": 1543924409 + }, + "origin": "lua-sql" + }, + "fail2ban": { + "versions": { + "0.10.3.1-r2": 1545300388 + }, + "origin": "fail2ban", + "dependencies": [ + "python3", + "iptables", + "ip6tables", + "logrotate" + ], + "provides": [ + "cmd:fail2ban-client", + "cmd:fail2ban-python", + "cmd:fail2ban-regex", + "cmd:fail2ban-server", + "cmd:fail2ban-testcases" + ] + }, + "keybinder-dev": { + "versions": { + "0.3.0-r1": 1545076730 + }, + "origin": "keybinder", + "dependencies": [ + "gtk+2.0-dev", + "libxext-dev", + "keybinder=0.3.0-r1", + "pc:gtk+-2.0", + "pkgconfig" + ], + "provides": [ + "pc:keybinder=0.3.0" + ] + }, + "libxkbui": { + "versions": { + "1.0.2-r9": 1543927890 + }, + "origin": "libxkbui", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1", + "so:libxkbfile.so.1" + ], + "provides": [ + "so:libxkbui.so.1=1.0.0" + ] + }, + "uwsgi-cheaper_backlog2": { + "versions": { + "2.0.17.1-r0": 1545062197 + }, + "origin": "uwsgi", + "dependencies": [ + "uwsgi", + "so:libc.musl-x86_64.so.1" + ] + }, + "lua5.2-dev": { + "versions": { + "5.2.4-r7": 1542304841 + }, + "origin": "lua5.2", + "dependencies": [ + "lua5.2", + "lua5.2-libs=5.2.4-r7", + "pkgconfig" + ], + "provides": [ + "pc:lua5.2=5.2.4" + ] + }, + "py2-tz": { + "versions": { + "2018.9-r0": 1548109648 + }, + "origin": "py-tz", + "dependencies": [ + "python2" + ] + }, + "perl-error": { + "versions": { + "0.17027-r0": 1545062330 + }, + "origin": "perl-error", + "dependencies": [ + "perl" + ] + }, + "xmodmap": { + "versions": { + "1.0.9-r2": 1542973230 + }, + "origin": "xmodmap", + "dependencies": [ + "so:libX11.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:xmodmap" + ] + }, + "xdg-utils": { + "versions": { + "1.1.3-r0": 1545154525 + }, + "origin": "xdg-utils", + "dependencies": [ + "xset", + "xprop" + ], + "provides": [ + "cmd:xdg-desktop-icon", + "cmd:xdg-desktop-menu", + "cmd:xdg-email", + "cmd:xdg-icon-resource", + "cmd:xdg-mime", + "cmd:xdg-open", + "cmd:xdg-screensaver", + "cmd:xdg-settings" + ] + }, + "libfontenc": { + "versions": { + "1.1.3-r3": 1542924593 + }, + "origin": "libfontenc", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libfontenc.so.1=1.0.0" + ] + }, + "linux-headers": { + "versions": { + "4.18.13-r1": 1542301615 + }, + "origin": "linux-headers" + }, + "zfs-libs": { + "versions": { + "0.7.12-r1": 1552933679 + }, + "origin": "zfs", + "dependencies": [ + "so:libblkid.so.1", + "so:libc.musl-x86_64.so.1", + "so:libintl.so.8", + "so:libtirpc.so.3", + "so:libuuid.so.1", + "so:libz.so.1" + ], + "provides": [ + "so:libnvpair.so.1=1.0.1", + "so:libuutil.so.1=1.0.1", + "so:libzfs.so.2=2.0.0", + "so:libzfs_core.so.1=1.0.0", + "so:libzpool.so.2=2.0.0" + ] + }, + "collectd-write_http": { + "versions": { + "5.8.0-r3": 1546422676 + }, + "origin": "collectd", + "dependencies": [ + "collectd", + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libyajl.so.2" + ] + }, + "patch": { + "versions": { + "2.7.6-r4": 1548420016 + }, + "origin": "patch", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:patch" + ] + }, + "iwlwifi-1000-ucode": { + "versions": { + "39.31.5.1-r0": 1545076629 + }, + "origin": "iwlwifi-1000-ucode" + }, + "py2-psycopg2": { + "versions": { + "2.7.5-r0": 1544000428 + }, + "origin": "py-psycopg2", + "dependencies": [ + "py-egenix-mx-base", + "so:libc.musl-x86_64.so.1", + "so:libpq.so.5", + "so:libpython2.7.so.1.0" + ] + }, + "qemu-audio-alsa": { + "versions": { + "3.1.0-r3": 1551107306 + }, + "origin": "qemu", + "dependencies": [ + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0" + ] + }, + "cairomm-doc": { + "versions": { + "1.12.2-r0": 1543077182 + }, + "origin": "cairomm" + }, + "doxygen": { + "versions": { + "1.8.15-r0": 1545993768 + }, + "origin": "doxygen", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "cmd:doxygen" + ] + }, + "a52dec-doc": { + "versions": { + "0.7.4-r7": 1545214242 + }, + "origin": "a52dec" + }, + "glew-dev": { + "versions": { + "2.1.0-r0": 1543935773 + }, + "origin": "glew", + "dependencies": [ + "libxmu-dev", + "libxi-dev", + "mesa-dev", + "glu-dev", + "glew=2.1.0-r0", + "pc:glu", + "pkgconfig" + ], + "provides": [ + "pc:glew=2.1.0" + ] + }, + "gtk3-xfce-engine": { + "versions": { + "3.2.0-r2": 1545292906 + }, + "origin": "gtk-xfce-engine", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgdk-3.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgtk-3.so.0" + ] + }, + "nfprofile": { + "versions": { + "1.6.17-r0": 1545223024 + }, + "origin": "nfdump", + "dependencies": [ + "nfdump", + "so:libc.musl-x86_64.so.1", + "so:libnfdump-1.6.17.so", + "so:librrd.so.8" + ], + "provides": [ + "cmd:nfprofile" + ] + }, + "clamav-doc": { + "versions": { + "0.100.3-r0": 1555507335 + }, + "origin": "clamav" + }, + "perl-file-sharedir-install": { + "versions": { + "0.13-r0": 1543238880 + }, + "origin": "perl-file-sharedir-install" + }, + "py3-olefile": { + "versions": { + "0.43-r2": 1542883721 + }, + "origin": "py-olefile" + }, + "perl-cps": { + "versions": { + "0.18-r0": 1545289364 + }, + "origin": "perl-cps", + "dependencies": [ + "perl-future" + ] + }, + "pwgen": { + "versions": { + "2.08-r0": 1545290834 + }, + "origin": "pwgen", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pwgen" + ] + }, + "lua5.1-sql-odbc": { + "versions": { + "2.3.5-r2": 1543924399 + }, + "origin": "lua-sql", + "dependencies": [ + "lua5.1", + "so:libc.musl-x86_64.so.1", + "so:libodbc.so.2" + ] + }, + "perl-class-container": { + "versions": { + "0.13-r0": 1543254090 + }, + "origin": "perl-class-container", + "dependencies": [ + "perl-params-validate" + ] + }, + "ulogd-mysql": { + "versions": { + "2.0.7-r0": 1545300896 + }, + "origin": "ulogd", + "dependencies": [ + "ulogd", + "so:libc.musl-x86_64.so.1", + "so:libmariadb.so.3" + ] + }, + "squid-lang-cs": { + "versions": { + "4.4-r1": 1545216324 + }, + "origin": "squid", + "dependencies": [ + "logrotate" + ] + }, + "clamav": { + "versions": { + "0.100.3-r0": 1555507336 + }, + "origin": "clamav", + "dependencies": [ + "clamav-scanner", + "clamav-daemon" + ] + }, + "syslog-ng-openrc": { + "versions": { + "3.19.1-r0": 1548543150 + }, + "origin": "syslog-ng" + }, + "s6-networking": { + "versions": { + "2.3.0.3-r1": 1546957736 + }, + "origin": "s6-networking", + "dependencies": [ + "so:libbearssl.so.0", + "so:libc.musl-x86_64.so.1", + "so:libs6.so.2.7", + "so:libs6dns.so.2.3", + "so:libskarnet.so.2.7" + ], + "provides": [ + "so:libs6net.so.2.3=2.3.0.3", + "so:libsbearssl.so.2.3=2.3.0.3", + "cmd:minidentd", + "cmd:s6-clockadd", + "cmd:s6-clockview", + "cmd:s6-getservbyname", + "cmd:s6-ident-client", + "cmd:s6-sntpclock", + "cmd:s6-taiclock", + "cmd:s6-taiclockd", + "cmd:s6-tcpclient", + "cmd:s6-tcpserver", + "cmd:s6-tcpserver-access", + "cmd:s6-tcpserver4", + "cmd:s6-tcpserver4-socketbinder", + "cmd:s6-tcpserver4d", + "cmd:s6-tcpserver6", + "cmd:s6-tcpserver6-socketbinder", + "cmd:s6-tcpserver6d", + "cmd:s6-tlsc", + "cmd:s6-tlsclient", + "cmd:s6-tlsd", + "cmd:s6-tlsserver" + ] + }, + "lua-curl": { + "versions": { + "0.3.8-r1": 1545224117 + }, + "origin": "lua-curl" + }, + "py-mako": { + "versions": { + "1.0.7-r0": 1543220557 + }, + "origin": "py-mako" + }, + "perl-javascript-minifier-doc": { + "versions": { + "1.14-r0": 1545300393 + }, + "origin": "perl-javascript-minifier" + }, + "qemu-block-nfs": { + "versions": { + "3.1.0-r3": 1551107307 + }, + "origin": "qemu", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglib-2.0.so.0", + "so:libnfs.so.12" + ] + }, + "udns-dev": { + "versions": { + "0.4-r0": 1542924398 + }, + "origin": "udns", + "dependencies": [ + "udns=0.4-r0" + ] + }, + "freeswitch-sounds-music-8000": { + "versions": { + "1.0.8-r1": 1545116730 + }, + "origin": "freeswitch-sounds-music-8000" + }, + "iso-codes-lang": { + "versions": { + "4.1-r0": 1545257086 + }, + "origin": "iso-codes" + }, + "perl-net-dns": { + "versions": { + "1.19-r0": 1545061164 + }, + "origin": "perl-net-dns", + "dependencies": [ + "perl" + ] + }, + "bcache-tools-doc": { + "versions": { + "1.0.8-r1": 1545060582 + }, + "origin": "bcache-tools" + }, + "ciwiki": { + "versions": { + "2.0.5-r1": 1545292637 + }, + "origin": "ciwiki", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:ciwiki" + ] + }, + "mariadb-mytop": { + "versions": { + "10.3.13-r1": 1557431735 + }, + "origin": "mariadb", + "dependencies": [ + "perl", + "perl-dbi", + "perl-dbd-mysql", + "perl-getopt-long", + "perl-socket", + "perl-term-readkey" + ], + "provides": [ + "cmd:mytop" + ] + }, + "asciidoctor": { + "versions": { + "1.5.8-r0": 1545665243 + }, + "origin": "asciidoctor", + "dependencies": [ + "ruby" + ] + }, + "tiny-ec2-bootstrap": { + "versions": { + "1.2.0-r0": 1545300595 + }, + "origin": "tiny-ec2-bootstrap", + "dependencies": [ + "openrc", + "e2fsprogs-extra" + ] + }, + "gpicview-lang": { + "versions": { + "0.2.5-r0": 1543932504 + }, + "origin": "gpicview" + }, + "python3-doc": { + "versions": { + "3.6.8-r2": 1554747638 + }, + "origin": "python3" + }, + "bzr-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "libdvdcss": { + "versions": { + "1.4.2-r0": 1545838429 + }, + "origin": "libdvdcss", + "dependencies": [ + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libdvdcss.so.2=2.2.0" + ] + }, + "syslog-ng-tags-parser": { + "versions": { + "3.19.1-r0": 1548543151 + }, + "origin": "syslog-ng", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libsyslog-ng-3.19.so.0" + ] + }, + "perl-text-password-pronounceable": { + "versions": { + "0.30-r1": 1543928465 + }, + "origin": "perl-text-password-pronounceable", + "dependencies": [ + "perl" + ] + }, + "py3-vobject": { + "versions": { + "0.9.6.1-r0": 1545165275 + }, + "origin": "py-vobject", + "dependencies": [ + "python3", + "py3-icu", + "py3-dateutil" + ] + }, + "py3-oauth2client": { + "versions": { + "4.1.2-r2": 1545229292 + }, + "origin": "py-oauth2client", + "dependencies": [ + "py3-asn1", + "py3-httplib2", + "py3-asn1-modules", + "py3-rsa", + "py3-six", + "python3" + ] + }, + "c-ares-dev": { + "versions": { + "1.15.0-r0": 1544791695 + }, + "origin": "c-ares", + "dependencies": [ + "c-ares=1.15.0-r0", + "pkgconfig" + ], + "provides": [ + "pc:libcares=1.15.0" + ] + }, + "libgnome": { + "versions": { + "2.32.1-r7": 1545299768 + }, + "origin": "libgnome", + "dependencies": [ + "/bin/sh", + "so:libORBit-2.so.0", + "so:libbonobo-2.so.0", + "so:libbonobo-activation.so.4", + "so:libc.musl-x86_64.so.1", + "so:libcanberra.so.0", + "so:libgconf-2.so.4", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnomevfs-2.so.0", + "so:libgobject-2.0.so.0", + "so:libgthread-2.0.so.0", + "so:libintl.so.8", + "so:libpopt.so.0" + ], + "provides": [ + "so:libgnome-2.so.0=0.3200.1", + "cmd:gnome-open" + ] + }, + "qemu-system-s390x": { + "versions": { + "3.1.0-r3": 1551107305 + }, + "origin": "qemu", + "dependencies": [ + "qemu", + "so:libaio.so.1", + "so:libc.musl-x86_64.so.1", + "so:libepoxy.so.0", + "so:libgbm.so.1", + "so:libgcc_s.so.1", + "so:libglib-2.0.so.0", + "so:libgmodule-2.0.so.0", + "so:libgnutls.so.30", + "so:libjpeg.so.8", + "so:liblzo2.so.2", + "so:libnettle.so.6", + "so:libpixman-1.so.0", + "so:libpng16.so.16", + "so:libseccomp.so.2", + "so:libsnappy.so.1", + "so:libspice-server.so.1", + "so:libusbredirparser.so.1", + "so:libvdeplug.so.3", + "so:libvirglrenderer.so.0", + "so:libz.so.1" + ], + "provides": [ + "cmd:qemu-system-s390x" + ] + }, + "the_silver_searcher": { + "versions": { + "2.1.0-r2": 1543998782 + }, + "origin": "the_silver_searcher", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:liblzma.so.5", + "so:libpcre.so.1", + "so:libz.so.1" + ], + "provides": [ + "cmd:ag" + ] + }, + "gst-plugins-base0.10": { + "versions": { + "0.10.36-r4": 1543928811 + }, + "origin": "gst-plugins-base0.10", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libXv.so.1", + "so:libasound.so.2", + "so:libc.musl-x86_64.so.1", + "so:libcairo.so.2", + "so:libgio-2.0.so.0", + "so:libglib-2.0.so.0", + "so:libgobject-2.0.so.0", + "so:libgstbase-0.10.so.0", + "so:libgstcontroller-0.10.so.0", + "so:libgstdataprotocol-0.10.so.0", + "so:libgstreamer-0.10.so.0", + "so:libintl.so.8", + "so:libogg.so.0", + "so:liborc-0.4.so.0", + "so:liborc-test-0.4.so.0", + "so:libpango-1.0.so.0", + "so:libpangocairo-1.0.so.0", + "so:libtheoradec.so.1", + "so:libtheoraenc.so.1", + "so:libvorbis.so.0", + "so:libvorbisenc.so.2", + "so:libxml2.so.2", + "so:libz.so.1" + ], + "provides": [ + "so:libgstapp-0.10.so.0=0.25.0", + "so:libgstaudio-0.10.so.0=0.25.0", + "so:libgstcdda-0.10.so.0=0.25.0", + "so:libgstfft-0.10.so.0=0.25.0", + "so:libgstinterfaces-0.10.so.0=0.25.0", + "so:libgstnetbuffer-0.10.so.0=0.25.0", + "so:libgstpbutils-0.10.so.0=0.25.0", + "so:libgstriff-0.10.so.0=0.25.0", + "so:libgstrtp-0.10.so.0=0.25.0", + "so:libgstrtsp-0.10.so.0=0.25.0", + "so:libgstsdp-0.10.so.0=0.25.0", + "so:libgsttag-0.10.so.0=0.25.0", + "so:libgstvideo-0.10.so.0=0.25.0", + "cmd:gst-discoverer-0.10", + "cmd:gst-visualise-0.10" + ] + }, + "mdadm-udev": { + "versions": { + "4.1-r0": 1545858162 + }, + "origin": "mdadm" + }, + "yasm-doc": { + "versions": { + "1.3.0-r1": 1545117179 + }, + "origin": "yasm" + }, + "libxinerama": { + "versions": { + "1.1.4-r1": 1543077318 + }, + "origin": "libxinerama", + "dependencies": [ + "so:libX11.so.6", + "so:libXext.so.6", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libXinerama.so.1=1.0.0" + ] + }, + "newsboat-lang": { + "versions": { + "2.13-r0": 1545075359 + }, + "origin": "newsboat" + }, + "vde2-dev": { + "versions": { + "2.3.2-r10": 1545074024 + }, + "origin": "vde2", + "dependencies": [ + "pkgconfig", + "vde2-libs=2.3.2-r10" + ], + "provides": [ + "pc:vdehist=2.3.2", + "pc:vdemgmt=2.3.2", + "pc:vdeplug=2.3.2", + "pc:vdesnmp=2.3.2" + ] + }, + "virglrenderer-dev": { + "versions": { + "0.7.0-r1": 1547472270 + }, + "origin": "virglrenderer", + "dependencies": [ + "pkgconfig", + "virglrenderer=0.7.0-r1" + ], + "provides": [ + "pc:virglrenderer=0.7.0" + ] + }, + "perl-utils": { + "versions": { + "5.26.3-r0": 1543998673 + }, + "origin": "perl", + "dependencies": [ + "perl" + ], + "provides": [ + "cmd:corelist", + "cmd:cpan", + "cmd:encguess", + "cmd:h2ph", + "cmd:instmodsh", + "cmd:json_pp", + "cmd:libnetcfg", + "cmd:perlbug", + "cmd:perlthanks", + "cmd:piconv", + "cmd:pl2pm", + "cmd:prove", + "cmd:ptar", + "cmd:ptardiff", + "cmd:ptargrep", + "cmd:shasum", + "cmd:splain", + "cmd:zipdetails" + ] + }, + "freetype-static": { + "versions": { + "2.9.1-r2": 1542822031 + }, + "origin": "freetype" + }, + "perl-test-file-sharedir": { + "versions": { + "1.001002-r0": 1543238933 + }, + "origin": "perl-test-file-sharedir", + "dependencies": [ + "perl-class-tiny", + "perl-file-sharedir", + "perl-file-copy-recursive", + "perl-path-tiny", + "perl-scope-guard" + ] + }, + "glamor-egl": { + "versions": { + "0.6.0-r4": 1545060559 + }, + "origin": "glamor-egl", + "dependencies": [ + "so:libEGL.so.1", + "so:libGL.so.1", + "so:libc.musl-x86_64.so.1", + "so:libgbm.so.1" + ], + "provides": [ + "so:libglamor.so.0=0.0.0" + ] + }, + "gconf-dev": { + "versions": { + "3.2.6-r4": 1545075211 + }, + "origin": "gconf", + "dependencies": [ + "libxml2-dev", + "gtk+3.0-dev", + "polkit-dev", + "orbit2-dev", + "gconf=3.2.6-r4", + "pc:ORBit-2.0", + "pc:gio-2.0", + "pc:glib-2.0", + "pkgconfig" + ], + "provides": [ + "pc:gconf-2.0=3.2.6" + ] + }, + "perl-exception-class-doc": { + "versions": { + "1.44-r0": 1542845808 + }, + "origin": "perl-exception-class" + }, + "perl-exporter-doc": { + "versions": { + "5.73-r0": 1544001442 + }, + "origin": "perl-exporter" + }, + "py-docutils-doc": { + "versions": { + "0.14-r1": 1543925153 + }, + "origin": "py-docutils", + "dependencies": [ + "py3-docutils" + ] + }, + "py3-tornado": { + "versions": { + "4.5.2-r1": 1542845329 + }, + "origin": "py-tornado", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libpython3.6m.so.1.0" + ] + }, + "perl-stream-buffered-doc": { + "versions": { + "0.03-r0": 1544792335 + }, + "origin": "perl-stream-buffered" + }, + "s6-openrc": { + "versions": { + "2.7.2.0-r0": 1545062680 + }, + "origin": "s6" + }, + "libcap-ng-doc": { + "versions": { + "0.7.9-r1": 1545918386 + }, + "origin": "libcap-ng" + }, + "mesa-gles": { + "versions": { + "18.1.7-r2": 1554455971 + }, + "origin": "mesa", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libglapi.so.0" + ], + "provides": [ + "so:libGLESv1_CM.so.1=1.1.0", + "so:libGLESv2.so.2=2.0.0" + ] + }, + "lua-rex-pcre": { + "versions": { + "2.9.0-r0": 1545209197 + }, + "origin": "lua-rex" + }, + "py3-gflags": { + "versions": { + "3.1.1-r1": 1545208595 + }, + "origin": "py-gflags", + "dependencies": [ + "python3" + ] + }, + "fcgi++": { + "versions": { + "2.4.0-r8": 1543238825 + }, + "origin": "fcgi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libfcgi.so.0", + "so:libgcc_s.so.1", + "so:libstdc++.so.6" + ], + "provides": [ + "so:libfcgi++.so.0=0.0.0" + ] + }, + "alpine-keys": { + "versions": { + "2.1-r1": 1543248499 + }, + "origin": "alpine-keys" + }, + "perl-email-address-doc": { + "versions": { + "1.909-r0": 1542893277 + }, + "origin": "perl-email-address" + }, + "py2-backports.ssl_match_hostname": { + "versions": { + "3.5.0.1-r1": 1543223292 + }, + "origin": "py2-backports.ssl_match_hostname", + "dependencies": [ + "python2" + ], + "provides": [ + "py-backports.ssl_match_hostname" + ] + }, + "giblib": { + "versions": { + "1.2.4-r10": 1543226558 + }, + "origin": "giblib", + "dependencies": [ + "so:libImlib2.so.1", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "so:libgiblib.so.1=1.0.6" + ] + }, + "pdnsd": { + "versions": { + "1.2.9a-r5": 1543077306 + }, + "origin": "pdnsd", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1" + ], + "provides": [ + "cmd:pdnsd", + "cmd:pdnsd-ctl" + ] + }, + "lynx-zsh-completion": { + "versions": { + "5.6.2-r0": 1545308086 + }, + "origin": "zsh", + "dependencies": [ + "zsh" + ] + }, + "ca-certificates": { + "versions": { + "20190108-r0": 1548779239 + }, + "origin": "ca-certificates", + "dependencies": [ + "/bin/sh", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ], + "provides": [ + "cmd:c_rehash", + "cmd:update-ca-certificates" + ] + }, + "weechat": { + "versions": { + "2.3-r0": 1545299934 + }, + "origin": "weechat", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libcurl.so.4", + "so:libgcrypt.so.20", + "so:libgnutls.so.30", + "so:libncursesw.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:weechat", + "cmd:weechat-curses", + "cmd:weechat-headless" + ] + }, + "perl-crypt-openssl-rsa": { + "versions": { + "0.31-r1": 1545061169 + }, + "origin": "perl-crypt-openssl-rsa", + "dependencies": [ + "perl", + "perl-crypt-openssl-random", + "perl-crypt-openssl-guess", + "so:libc.musl-x86_64.so.1", + "so:libcrypto.so.1.1" + ] + }, + "perl-params-validate": { + "versions": { + "1.29-r0": 1543238894 + }, + "origin": "perl-params-validate", + "dependencies": [ + "perl-module-implementation", + "so:libc.musl-x86_64.so.1" + ] + }, + "nftables": { + "versions": { + "0.9.0-r0": 1542893221 + }, + "origin": "nftables", + "dependencies": [ + "/bin/sh", + "pkgconfig", + "so:libc.musl-x86_64.so.1", + "so:libgmp.so.10", + "so:libmnl.so.0", + "so:libnftnl.so.7", + "so:libreadline.so.7" + ], + "provides": [ + "so:libnftables.so.0=0.0.0", + "pc:libnftables=0.9.0", + "cmd:nft" + ] + }, + "perl-class-mix": { + "versions": { + "0.006-r0": 1545163344 + }, + "origin": "perl-class-mix", + "dependencies": [ + "perl-params-classify" + ] + }, + "xf86-video-tdfx-doc": { + "versions": { + "1.4.7-r3": 1545073943 + }, + "origin": "xf86-video-tdfx" + }, + "atop": { + "versions": { + "2.3.0-r1": 1543226695 + }, + "origin": "atop", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libncursesw.so.6", + "so:libz.so.1" + ], + "provides": [ + "cmd:atop", + "cmd:atop-2.3.0", + "cmd:atopacctd", + "cmd:atopsar", + "cmd:atopsar-2.3.0" + ] + }, + "open-iscsi": { + "versions": { + "2.0.874-r2": 1547188207 + }, + "origin": "open-iscsi", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libisns.so.0", + "so:libmount.so.1" + ], + "provides": [ + "cmd:iscsi-iname", + "cmd:iscsi_discovery", + "cmd:iscsiadm", + "cmd:iscsid", + "cmd:iscsistart", + "cmd:iscsiuio" + ] + }, + "usbredir": { + "versions": { + "0.7.1-r0": 1543223287 + }, + "origin": "usbredir", + "dependencies": [ + "so:libc.musl-x86_64.so.1", + "so:libusb-1.0.so.0" + ], + "provides": [ + "so:libusbredirhost.so.1=1.0.0", + "so:libusbredirparser.so.1=1.0.0" + ] + }, + "perl-test-eol-doc": { + "versions": { + "2.00-r0": 1545075951 + }, + "origin": "perl-test-eol" + }, + "git-perl": { + "versions": { + "2.20.1-r0": 1545214200 + }, + "origin": "git", + "dependencies": [ + "git=2.20.1-r0", + "perl-git=2.20.1-r0", + "perl" + ] + } + }, + "provide": { + "so": { + "libglut.so.3": { + "package": "freeglut", + "version": "3.10.0" + }, + "librabbitmq.so.4": { + "package": "rabbitmq-c", + "version": "4.2.0" + }, + "libgtksourceview-2.0.so.0": { + "package": "gtksourceview2", + "version": "0.0.0" + }, + "liba52.so.0": { + "package": "a52dec", + "version": "0.0.0" + }, + "cat.c32": { + "package": "syslinux", + "version": "0" + }, + "chain.c32": { + "package": "syslinux", + "version": "0" + }, + "cmd.c32": { + "package": "syslinux", + "version": "0" + }, + "cmenu.c32": { + "package": "syslinux", + "version": "0" + }, + "config.c32": { + "package": "syslinux", + "version": "0" + }, + "cptime.c32": { + "package": "syslinux", + "version": "0" + }, + "cpu.c32": { + "package": "syslinux", + "version": "0" + }, + "cpuid.c32": { + "package": "syslinux", + "version": "0" + }, + "cpuidtest.c32": { + "package": "syslinux", + "version": "0" + }, + "debug.c32": { + "package": "syslinux", + "version": "0" + }, + "dhcp.c32": { + "package": "syslinux", + "version": "0" + }, + "dir.c32": { + "package": "syslinux", + "version": "0" + }, + "disk.c32": { + "package": "syslinux", + "version": "0" + }, + "dmi.c32": { + "package": "syslinux", + "version": "0" + }, + "dmitest.c32": { + "package": "syslinux", + "version": "0" + }, + "elf.c32": { + "package": "syslinux", + "version": "0" + }, + "ethersel.c32": { + "package": "syslinux", + "version": "0" + }, + "gfxboot.c32": { + "package": "syslinux", + "version": "0" + }, + "gpxecmd.c32": { + "package": "syslinux", + "version": "0" + }, + "hdt.c32": { + "package": "syslinux", + "version": "0" + }, + "hexdump.c32": { + "package": "syslinux", + "version": "0" + }, + "host.c32": { + "package": "syslinux", + "version": "0" + }, + "ifcpu.c32": { + "package": "syslinux", + "version": "0" + }, + "ifcpu64.c32": { + "package": "syslinux", + "version": "0" + }, + "ifmemdsk.c32": { + "package": "syslinux", + "version": "0" + }, + "ifplop.c32": { + "package": "syslinux", + "version": "0" + }, + "kbdmap.c32": { + "package": "syslinux", + "version": "0" + }, + "kontron_wdt.c32": { + "package": "syslinux", + "version": "0" + }, + "ldlinux.c32": { + "package": "syslinux", + "version": "0" + }, + "lfs.c32": { + "package": "syslinux", + "version": "0" + }, + "libcom32.c32": { + "package": "syslinux", + "version": "0" + }, + "libgpl.c32": { + "package": "syslinux", + "version": "0" + }, + "liblua.c32": { + "package": "syslinux", + "version": "0" + }, + "libmenu.c32": { + "package": "syslinux", + "version": "0" + }, + "libutil.c32": { + "package": "syslinux", + "version": "0" + }, + "linux.c32": { + "package": "syslinux", + "version": "0" + }, + "ls.c32": { + "package": "syslinux", + "version": "0" + }, + "lua.c32": { + "package": "syslinux", + "version": "0" + }, + "mboot.c32": { + "package": "syslinux", + "version": "0" + }, + "meminfo.c32": { + "package": "syslinux", + "version": "0" + }, + "menu.c32": { + "package": "syslinux", + "version": "0" + }, + "pci.c32": { + "package": "syslinux", + "version": "0" + }, + "pcitest.c32": { + "package": "syslinux", + "version": "0" + }, + "pmload.c32": { + "package": "syslinux", + "version": "0" + }, + "poweroff.c32": { + "package": "syslinux", + "version": "0" + }, + "prdhcp.c32": { + "package": "syslinux", + "version": "0" + }, + "pwd.c32": { + "package": "syslinux", + "version": "0" + }, + "pxechn.c32": { + "package": "syslinux", + "version": "0" + }, + "reboot.c32": { + "package": "syslinux", + "version": "0" + }, + "rosh.c32": { + "package": "syslinux", + "version": "0" + }, + "sanboot.c32": { + "package": "syslinux", + "version": "0" + }, + "sdi.c32": { + "package": "syslinux", + "version": "0" + }, + "sysdump.c32": { + "package": "syslinux", + "version": "0" + }, + "syslinux.c32": { + "package": "syslinux", + "version": "0" + }, + "vesa.c32": { + "package": "syslinux", + "version": "0" + }, + "vesainfo.c32": { + "package": "syslinux", + "version": "0" + }, + "vesamenu.c32": { + "package": "syslinux", + "version": "0" + }, + "vpdtest.c32": { + "package": "syslinux", + "version": "0" + }, + "whichsys.c32": { + "package": "syslinux", + "version": "0" + }, + "zzjson.c32": { + "package": "syslinux", + "version": "0" + }, + "libfribidi.so.0": { + "package": "fribidi", + "version": "0.4.0" + }, + "libbluetooth.so.3": { + "package": "bluez-libs", + "version": "3.18.16" + }, + "libXdamage.so.1": { + "package": "libxdamage", + "version": "1.1.0" + }, + "libgmpxx.so.4": { + "package": "libgmpxx", + "version": "4.5.2" + }, + "libgailutil.so.18": { + "package": "gtk+2.0", + "version": "18.0.1" + }, + "libgdk-x11-2.0.so.0": { + "package": "gtk+2.0", + "version": "0.2400.32" + }, + "libgtk-x11-2.0.so.0": { + "package": "gtk+2.0", + "version": "0.2400.32" + }, + "libtiffxx.so.5": { + "package": "libtiffxx", + "version": "5.4.0" + }, + "libxcb-cursor.so.0": { + "package": "xcb-util-cursor", + "version": "0.0.0" + }, + "libetpan.so.20": { + "package": "libetpan", + "version": "20.3.0" + }, + "libnghttp2.so.14": { + "package": "nghttp2-libs", + "version": "14.17.1" + }, + "libmariadb.so.3": { + "package": "mariadb-connector-c", + "version": "3" + }, + "libalpm.so.10": { + "package": "pacman", + "version": "10.0.2" + }, + "libxmlrpc.so.3": { + "package": "xmlrpc-c", + "version": "3.39" + }, + "libxmlrpc_server.so.3": { + "package": "xmlrpc-c", + "version": "3.39" + }, + "libxmlrpc_util.so.3": { + "package": "xmlrpc-c", + "version": "3.39" + }, + "libxkbcommon-x11.so.0": { + "package": "libxkbcommon", + "version": "0.0.0" + }, + "libxkbcommon.so.0": { + "package": "libxkbcommon", + "version": "0.0.0" + }, + "libcli-ldap-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libcmdline-contexts-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libcmdline-credentials-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libdcerpc.so.0": { + "package": "samba-client-libs", + "version": "0.0.1" + }, + "libdsdb-garbage-collect-tombstones-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libevents-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libhttp-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libnetif-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libpopt-samba3-cmdline-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libregistry-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libsmbclient-raw-samba4.so": { + "package": "samba-client-libs", + "version": "0" + }, + "libpci.so.3": { + "package": "pciutils-libs", + "version": "3.6.2" + }, + "libMagick++-7.Q16HDRI.so.4": { + "package": "imagemagick-c++", + "version": "4.0.0" + }, + "libgdkglext-x11-1.0.so.0": { + "package": "gtkglext", + "version": "0.0.0" + }, + "libgtkglext-x11-1.0.so.0": { + "package": "gtkglext", + "version": "0.0.0" + }, + "lib01_acl_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib02_imap_acl_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib02_lazy_expunge_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib05_mail_crypt_acl_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib05_pop3_migration_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib05_snarf_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib10_last_login_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib10_mail_crypt_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib10_mail_filter_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib10_quota_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib11_imap_quota_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib11_trash_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib15_notify_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_autocreate_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_charset_alias_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_expire_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_fts_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_listescape_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_mail_log_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_mailbox_alias_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_notify_status_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_push_notification_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_quota_clone_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_replication_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_var_expand_crypt.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_virtual_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib20_zlib_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib21_fts_squat_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib30_imap_zlib_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib90_old_stats_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib95_imap_old_stats_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "lib99_welcome_plugin.so": { + "package": "dovecot", + "version": "0" + }, + "libdcrypt_openssl.so": { + "package": "dovecot", + "version": "0" + }, + "libdovecot-compression.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot-dsync.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot-fts.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot-lda.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot-login.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot-storage.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libdovecot.so.0": { + "package": "dovecot", + "version": "0.0.0" + }, + "libfs_compress.so": { + "package": "dovecot", + "version": "0" + }, + "libfs_crypt.so": { + "package": "dovecot", + "version": "0" + }, + "libfs_mail_crypt.so": { + "package": "dovecot", + "version": "0" + }, + "libold_stats_mail.so": { + "package": "dovecot", + "version": "0" + }, + "libssl_iostream_openssl.so": { + "package": "dovecot", + "version": "0" + }, + "libstats_auth.so": { + "package": "dovecot", + "version": "0" + }, + "libfdisk.so.1": { + "package": "libfdisk", + "version": "1.1.0" + }, + "lib21_fts_solr_plugin.so": { + "package": "dovecot-fts-solr", + "version": "0" + }, + "libgpgme.so.11": { + "package": "gpgme", + "version": "11.21.0" + }, + "libstfl.so.0": { + "package": "stfl", + "version": "0.24" + }, + "libquadmath.so.0": { + "package": "libquadmath", + "version": "0.0.0" + }, + "libenca.so.0": { + "package": "enca", + "version": "0.5.1" + }, + "libudev.so.1": { + "package": "eudev-libs", + "version": "1.6.3" + }, + "libtxc_dxtn.so": { + "package": "libtxc_dxtn", + "version": "0" + }, + "libsngtc_node.so": { + "package": "sngtc_client", + "version": "0" + }, + "libXpm.so.4": { + "package": "libxpm", + "version": "4.11.0" + }, + "libgmock.so": { + "package": "gmock", + "version": "0" + }, + "libgmock_main.so": { + "package": "gmock", + "version": "0" + }, + "libhunspell-1.6.so.0": { + "package": "hunspell", + "version": "0.0.1" + }, + "libee.so.0": { + "package": "libee", + "version": "0.0.0" + }, + "libfreetdm.so.1": { + "package": "freeswitch-freetdm", + "version": "1.0.0" + }, + "libchromeXvMC.so.1": { + "package": "xf86-video-openchrome", + "version": "1.0.0" + }, + "libchromeXvMCPro.so.1": { + "package": "xf86-video-openchrome", + "version": "1.0.0" + }, + "libMagickCore-7.Q16HDRI.so.6": { + "package": "imagemagick-libs", + "version": "6.0.0" + }, + "libMagickWand-7.Q16HDRI.so.6": { + "package": "imagemagick-libs", + "version": "6.0.0" + }, + "libhistory.so.7": { + "package": "libhistory", + "version": "7.0" + }, + "libdvdread.so.4": { + "package": "libdvdread", + "version": "4.2.0" + }, + "libdevmapper.so.1.02": { + "package": "device-mapper-libs", + "version": "1.02" + }, + "libhowl.so.0": { + "package": "avahi-compat-howl", + "version": "0.0.0" + }, + "libwayland-cursor.so.0": { + "package": "wayland-libs-cursor", + "version": "0.0.0" + }, + "libsctp.so.1": { + "package": "lksctp-tools", + "version": "1.0.17" + }, + "libmariadbd.so.19": { + "package": "mariadb-embedded", + "version": "19" + }, + "libcord.so.1": { + "package": "gc", + "version": "1.3.0" + }, + "libgc.so.1": { + "package": "gc", + "version": "1.3.2" + }, + "libjack.so.0": { + "package": "jack", + "version": "0.1.0" + }, + "libjacknet.so.0": { + "package": "jack", + "version": "0.1.0" + }, + "libjackserver.so.0": { + "package": "jack", + "version": "0.1.0" + }, + "libpython3.6m.so.1.0": { + "package": "python3", + "version": "1.0" + }, + "libpython3.so": { + "package": "python3", + "version": "0" + }, + "libldb.so.1": { + "package": "ldb", + "version": "1.3.8" + }, + "libnetcf.so.1": { + "package": "netcf-libs", + "version": "1.4.0" + }, + "libdvdnav.so.4": { + "package": "libdvdnav", + "version": "4.2.0" + }, + "libnewt.so.0.52": { + "package": "newt", + "version": "0.52.20" + }, + "libmenu-cache.so.3": { + "package": "menu-cache", + "version": "3.0.1" + }, + "libts.so.0": { + "package": "tslib", + "version": "0.10.0" + }, + "libudisks2.so.0": { + "package": "udisks2-libs", + "version": "0.0.0" + }, + "libglapi.so.0": { + "package": "mesa-glapi", + "version": "0.0.0" + }, + "libharfbuzz-icu.so.0": { + "package": "harfbuzz-icu", + "version": "0.20200.0" + }, + "libserf-1.so.1": { + "package": "serf", + "version": "1.3.0" + }, + "libXxf86vm.so.1": { + "package": "libxxf86vm", + "version": "1.0.0" + }, + "libconfuse.so.2": { + "package": "confuse", + "version": "2.0.0" + }, + "libattr.so.1": { + "package": "libattr", + "version": "1.1.0" + }, + "libqextserialport.so.1": { + "package": "qextserialport", + "version": "1.2.0" + }, + "libhiredis.so.0.14": { + "package": "hiredis", + "version": "0.14" + }, + "libpcre2-32.so.0": { + "package": "libpcre2-32", + "version": "0.7.1" + }, + "libtasn1.so.6": { + "package": "libtasn1", + "version": "6.5.5" + }, + "libcdio_cdda.so.2": { + "package": "libcdio-paranoia", + "version": "2.0.0" + }, + "libcdio_paranoia.so.2": { + "package": "libcdio-paranoia", + "version": "2.0.0" + }, + "libip4tc.so.0": { + "package": "iptables", + "version": "0.1.0" + }, + "libip6tc.so.0": { + "package": "iptables", + "version": "0.1.0" + }, + "libipq.so.0": { + "package": "iptables", + "version": "0.0.0" + }, + "libiptc.so.0": { + "package": "iptables", + "version": "0.0.0" + }, + "libxtables.so.12": { + "package": "iptables", + "version": "12.0.0" + }, + "libXv.so.1": { + "package": "libxv", + "version": "1.0.0" + }, + "libcrypto.so.43": { + "package": "libressl2.7-libcrypto", + "version": "43.0.1" + }, + "rlm_krb5.so": { + "package": "freeradius-krb5", + "version": "0" + }, + "libgstbase-1.0.so.0": { + "package": "gstreamer", + "version": "0.1404.0" + }, + "libgstcheck-1.0.so.0": { + "package": "gstreamer", + "version": "0.1404.0" + }, + "libgstcontroller-1.0.so.0": { + "package": "gstreamer", + "version": "0.1404.0" + }, + "libgstnet-1.0.so.0": { + "package": "gstreamer", + "version": "0.1404.0" + }, + "libgstreamer-1.0.so.0": { + "package": "gstreamer", + "version": "0.1404.0" + }, + "libgdl-3.so.5": { + "package": "gdl", + "version": "5.0.9" + }, + "libtls-standalone.so.1": { + "package": "libtls-standalone", + "version": "1.0.0" + }, + "libdevmapper-event.so.1.02": { + "package": "device-mapper-event-libs", + "version": "1.02" + }, + "libfreeradius-dhcp.so": { + "package": "freeradius-lib", + "version": "0" + }, + "libfreeradius-eap.so": { + "package": "freeradius-lib", + "version": "0" + }, + "libfreeradius-radius.so": { + "package": "freeradius-lib", + "version": "0" + }, + "libfreeradius-server.so": { + "package": "freeradius-lib", + "version": "0" + }, + "grosscheck.so": { + "package": "gross", + "version": "0" + }, + "libgconf-2.so.4": { + "package": "gconf", + "version": "4.1.5" + }, + "libxshmfence.so.1": { + "package": "libxshmfence", + "version": "1.0.0" + }, + "rlm_sql_postgresql.so": { + "package": "freeradius-postgresql", + "version": "0" + }, + "libloudmouth-1.so.0": { + "package": "loudmouth", + "version": "0.1.0" + }, + "libsasl2.so.3": { + "package": "libsasl", + "version": "3.0.0" + }, + "libfastjson.so.4": { + "package": "libfastjson", + "version": "4.2.0" + }, + "librhash.so.0": { + "package": "rhash-libs", + "version": "0" + }, + "libqrencode.so.4": { + "package": "libqrencode", + "version": "4.0.2" + }, + "libisofs.so.6": { + "package": "libisofs", + "version": "6.84.0" + }, + "libarchive.so.13": { + "package": "libarchive", + "version": "13.3.2" + }, + "libcpufreq.so.0": { + "package": "cpufrequtils", + "version": "0.0.0" + }, + "libunistring.so.2": { + "package": "libunistring", + "version": "2.1.0" + }, + "libbind9.so.1201": { + "package": "bind-libs", + "version": "1201.0.1" + }, + "libdns.so.1208": { + "package": "bind-libs", + "version": "1208.0.0" + }, + "libirs.so.1201": { + "package": "bind-libs", + "version": "1201.0.1" + }, + "libisc.so.1204": { + "package": "bind-libs", + "version": "1204.1.0" + }, + "libisccc.so.1201": { + "package": "bind-libs", + "version": "1201.0.1" + }, + "libisccfg.so.1203": { + "package": "bind-libs", + "version": "1203.0.1" + }, + "libns.so.1207": { + "package": "bind-libs", + "version": "1207.0.0" + }, + "libspeexdsp.so.1": { + "package": "speexdsp", + "version": "1.5.0" + }, + "libgnutlsxx.so.28": { + "package": "gnutls-c++", + "version": "28.1.0" + }, + "libgnarl-8.so": { + "package": "libgnat", + "version": "0" + }, + "libgnat-8.so": { + "package": "libgnat", + "version": "0" + }, + "libasn1-samba4.so.8": { + "package": "samba-heimdal-libs", + "version": "8.0.0" + }, + "libgssapi-samba4.so.2": { + "package": "samba-heimdal-libs", + "version": "2.0.0" + }, + "libhcrypto-samba4.so.5": { + "package": "samba-heimdal-libs", + "version": "5.0.1" + }, + "libheimbase-samba4.so.1": { + "package": "samba-heimdal-libs", + "version": "1.0.0" + }, + "libheimntlm-samba4.so.1": { + "package": "samba-heimdal-libs", + "version": "1.0.1" + }, + "libhx509-samba4.so.5": { + "package": "samba-heimdal-libs", + "version": "5.0.0" + }, + "libkrb5-samba4.so.26": { + "package": "samba-heimdal-libs", + "version": "26.0.0" + }, + "libroken-samba4.so.19": { + "package": "samba-heimdal-libs", + "version": "19.0.1" + }, + "libwind-samba4.so.0": { + "package": "samba-heimdal-libs", + "version": "0.0.0" + }, + "libdovecot-sql.so.0": { + "package": "dovecot-sql", + "version": "0.0.0" + }, + "libX11-xcb.so.1": { + "package": "libx11", + "version": "1.0.0" + }, + "libX11.so.6": { + "package": "libx11", + "version": "6.3.0" + }, + "libinput.so.10": { + "package": "libinput-libs", + "version": "10.13.0" + }, + "libfsimage.so.1.0": { + "package": "xen-libs", + "version": "1.0.0" + }, + "libxencall.so.1": { + "package": "xen-libs", + "version": "1.1" + }, + "libxenctrl.so.4.11": { + "package": "xen-libs", + "version": "4.11.0" + }, + "libxendevicemodel.so.1": { + "package": "xen-libs", + "version": "1.2" + }, + "libxenevtchn.so.1": { + "package": "xen-libs", + "version": "1.1" + }, + "libxenforeignmemory.so.1": { + "package": "xen-libs", + "version": "1.3" + }, + "libxengnttab.so.1": { + "package": "xen-libs", + "version": "1.1" + }, + "libxenguest.so.4.11": { + "package": "xen-libs", + "version": "4.11.0" + }, + "libxenlight.so.4.11": { + "package": "xen-libs", + "version": "4.11.0" + }, + "libxenstat.so.0": { + "package": "xen-libs", + "version": "0.0" + }, + "libxenstore.so.3.0": { + "package": "xen-libs", + "version": "3.0.3" + }, + "libxentoolcore.so.1": { + "package": "xen-libs", + "version": "1.0" + }, + "libxentoollog.so.1": { + "package": "xen-libs", + "version": "1.0" + }, + "libxenvchan.so.4.11": { + "package": "xen-libs", + "version": "4.11.0" + }, + "libxlutil.so.4.11": { + "package": "xen-libs", + "version": "4.11.0" + }, + "libpcp.so.1": { + "package": "pgpool", + "version": "1.0.0" + }, + "libfltk.so.1.3": { + "package": "fltk", + "version": "1.3" + }, + "libfltk_forms.so.1.3": { + "package": "fltk", + "version": "1.3" + }, + "libfltk_gl.so.1.3": { + "package": "fltk", + "version": "1.3" + }, + "libfltk_images.so.1.3": { + "package": "fltk", + "version": "1.3" + }, + "libpam.so.0": { + "package": "linux-pam", + "version": "0.84.2" + }, + "libpam_misc.so.0": { + "package": "linux-pam", + "version": "0.82.1" + }, + "libpamc.so.0": { + "package": "linux-pam", + "version": "0.82.1" + }, + "libcrack.so.2": { + "package": "cracklib", + "version": "2.9.0" + }, + "rlm_python.so": { + "package": "freeradius-python", + "version": "0" + }, + "libpytalloc-util.so.2": { + "package": "py2-talloc", + "version": "2.1.14" + }, + "libpq.so.5": { + "package": "libpq", + "version": "5.11" + }, + "libkdb_ldap.so.1": { + "package": "krb5-server-ldap", + "version": "1.0" + }, + "libminizip.so.1": { + "package": "minizip", + "version": "1.0.0" + }, + "libavahi-glib.so.1": { + "package": "avahi-glib", + "version": "1.0.2" + }, + "libavahi-gobject.so.0": { + "package": "avahi-glib", + "version": "0.0.5" + }, + "libdovecot-ldap.so.0": { + "package": "dovecot-ldap", + "version": "0.0.0" + }, + "libbstring.so.0": { + "package": "coova-chilli", + "version": "0.0.0" + }, + "libchilli.so.0": { + "package": "coova-chilli", + "version": "0.0.0" + }, + "libhandle.so.1": { + "package": "xfsprogs-libs", + "version": "1.0.3" + }, + "libxcb-util.so.1": { + "package": "xcb-util", + "version": "1.0.0" + }, + "libgladeui-1.so.11": { + "package": "glade3", + "version": "11.2.0" + }, + "libgccpp.so.1": { + "package": "libgc++", + "version": "1.3.1" + }, + "libdaemon.so.0": { + "package": "libdaemon", + "version": "0.5.0" + }, + "liborc-0.4.so.0": { + "package": "orc", + "version": "0.28.0" + }, + "liborc-test-0.4.so.0": { + "package": "orc", + "version": "0.28.0" + }, + "libQtWebKit.so.4": { + "package": "qt-webkit", + "version": "4.9.4" + }, + "libssh.so.4": { + "package": "libssh", + "version": "4.4.3" + }, + "libssh_threads.so.4": { + "package": "libssh", + "version": "4.4.3" + }, + "libasprintf.so.0": { + "package": "gettext-asprintf", + "version": "0.0.0" + }, + "libads-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libauth-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libdfs-server-ad-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libnetapi.so.0": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libnpa-tstream-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libprinting-migrate-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libsmbd-base-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libsmbd-conn-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libsmbldap.so.2": { + "package": "samba-common-server-libs", + "version": "2" + }, + "libsmbldaphelper-samba4.so": { + "package": "samba-common-server-libs", + "version": "0" + }, + "libg7221codec.so.2": { + "package": "pjproject", + "version": "2" + }, + "libilbccodec.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpj.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjlib-util.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjmedia-audiodev.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjmedia-codec.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjmedia-videodev.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjmedia.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjnath.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjsip-simple.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjsip-ua.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjsip.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjsua.so.2": { + "package": "pjproject", + "version": "2" + }, + "libpjsua2.so.2": { + "package": "pjproject", + "version": "2" + }, + "libyuv.so.2": { + "package": "pjproject", + "version": "2" + }, + "libGeoIP.so.1": { + "package": "geoip", + "version": "1.6.12" + }, + "liblber-2.4.so.2": { + "package": "libldap", + "version": "2.10.10" + }, + "libldap-2.4.so.2": { + "package": "libldap", + "version": "2.10.10" + }, + "libldap_r-2.4.so.2": { + "package": "libldap", + "version": "2.10.10" + }, + "libdriver_pgsql.so": { + "package": "dovecot-pgsql", + "version": "0" + }, + "libXau.so.6": { + "package": "libxau", + "version": "6.0.0" + }, + "libva-glx.so.2": { + "package": "libva-glx", + "version": "2.200.0" + }, + "libjson-c.so.2": { + "package": "json-c0.12", + "version": "2.0.2" + }, + "libQtCore.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtDBus.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtNetwork.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtScript.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtSql.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtTest.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtXml.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libQtXmlPatterns.so.4": { + "package": "qt", + "version": "4.8.7" + }, + "libnl-cli-3.so.200": { + "package": "libnl3-cli", + "version": "200.26.0" + }, + "libgstbase-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libgstcheck-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libgstcontroller-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libgstdataprotocol-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libgstnet-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libgstreamer-0.10.so.0": { + "package": "gstreamer0.10", + "version": "0.30.0" + }, + "libglfw.so.3": { + "package": "glfw", + "version": "3.2" + }, + "libsvn_client-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_delta-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_diff-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_fs-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_fs_base-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_fs_fs-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_fs_util-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_fs_x-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_ra-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_ra_local-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_ra_serf-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_ra_svn-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_repos-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_subr-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libsvn_wc-1.so.0": { + "package": "subversion-libs", + "version": "0.0.0" + }, + "libgailutil-3.so.0": { + "package": "gtk+3.0", + "version": "0.0.0" + }, + "libgdk-3.so.0": { + "package": "gtk+3.0", + "version": "0.2400.1" + }, + "libgtk-3.so.0": { + "package": "gtk+3.0", + "version": "0.2400.1" + }, + "libEGL.so.1": { + "package": "mesa-egl", + "version": "1.0.0" + }, + "libxklavier.so.16": { + "package": "libxklavier", + "version": "16.4.0" + }, + "libswish-e.so.2": { + "package": "swish-e", + "version": "2.0.0" + }, + "libevdev.so.2": { + "package": "libevdev", + "version": "2.2.0" + }, + "libslim.so.1.3.6": { + "package": "slim", + "version": "1.3.6" + }, + "libatkmm-1.6.so.1": { + "package": "atkmm", + "version": "1.1.0" + }, + "liblzo2.so.2": { + "package": "lzo", + "version": "2.0.0" + }, + "postfix-sqlite.so": { + "package": "postfix-sqlite", + "version": "0" + }, + "libell.so.0": { + "package": "libell", + "version": "0.0.2" + }, + "libmnl.so.0": { + "package": "libmnl", + "version": "0.2.0" + }, + "liblognorm.so.5": { + "package": "liblognorm", + "version": "5.1.0" + }, + "libxt_ACCOUNT_cl.so.0": { + "package": "xtables-addons", + "version": "0.0.0" + }, + "libboost_fiber-mt.so.1.67.0": { + "package": "boost-fiber", + "version": "1.67.0" + }, + "libpolkit-agent-1.so.0": { + "package": "polkit", + "version": "0.0.0" + }, + "libpolkit-backend-1.so.0": { + "package": "polkit", + "version": "0.0.0" + }, + "libpolkit-gobject-1.so.0": { + "package": "polkit", + "version": "0.0.0" + }, + "libupower-glib.so.3": { + "package": "upower", + "version": "3.0.1" + }, + "libboost_filesystem-mt.so.1.67.0": { + "package": "boost-filesystem", + "version": "1.67.0" + }, + "libboost_filesystem.so.1.67.0": { + "package": "boost-filesystem", + "version": "1.67.0" + }, + "libtdb.so.1": { + "package": "tdb-libs", + "version": "1.3.16" + }, + "libeggdbus-1.so.0": { + "package": "eggdbus", + "version": "0.0.0" + }, + "libnice.so.10": { + "package": "libnice", + "version": "10.7.0" + }, + "libopenjp2.so.7": { + "package": "openjpeg", + "version": "2.3.0" + }, + "libotr.so.2": { + "package": "libotr3", + "version": "2.2.1" + }, + "libMrm.so.4": { + "package": "motif", + "version": "4.0.4" + }, + "libUil.so.4": { + "package": "motif", + "version": "4.0.4" + }, + "libXm.so.4": { + "package": "motif", + "version": "4.0.4" + }, + "liblinenoise.so.0": { + "package": "linenoise", + "version": "0.0.0" + }, + "libjq.so.1": { + "package": "jq", + "version": "1.0.4" + }, + "libonig.so.5": { + "package": "oniguruma", + "version": "5.0.0" + }, + "libboost_date_time-mt.so.1.67.0": { + "package": "boost-date_time", + "version": "1.67.0" + }, + "libboost_date_time.so.1.67.0": { + "package": "boost-date_time", + "version": "1.67.0" + }, + "libcmph.so.0": { + "package": "libcmph", + "version": "0.0.0" + }, + "librlog.so.5": { + "package": "rlog", + "version": "5.0.0" + }, + "libperl.so": { + "package": "perl", + "version": "0" + }, + "libboost_python36-mt.so.1.67.0": { + "package": "boost-python3", + "version": "1.67.0" + }, + "libboost_python36.so.1.67.0": { + "package": "boost-python3", + "version": "1.67.0" + }, + "libgfortran.so.5": { + "package": "libgfortran", + "version": "5.0.0" + }, + "liblcms2.so.2": { + "package": "lcms2", + "version": "2.0.8" + }, + "liblxc.so.1": { + "package": "lxc-libs", + "version": "1.5.0" + }, + "libgpg-error.so.0": { + "package": "libgpg-error", + "version": "0.25.0" + }, + "libjbig2dec.so.0": { + "package": "jbig2dec", + "version": "0.0.0" + }, + "libzstd.so.1": { + "package": "zstd-libs", + "version": "1.3.8" + }, + "rlm_rest.so": { + "package": "freeradius-rest", + "version": "0" + }, + "libkmod.so.2": { + "package": "kmod", + "version": "2.3.2" + }, + "libtcl8.6.so": { + "package": "tcl", + "version": "0" + }, + "libwx_baseu-2.8.so.0": { + "package": "wxgtk2.8-base", + "version": "0.8.0" + }, + "libwx_baseu_net-2.8.so.0": { + "package": "wxgtk2.8-base", + "version": "0.8.0" + }, + "libwx_baseu_xml-2.8.so.0": { + "package": "wxgtk2.8-base", + "version": "0.8.0" + }, + "rlm_redis.so": { + "package": "freeradius-redis", + "version": "0" + }, + "rlm_rediswho.so": { + "package": "freeradius-redis", + "version": "0" + }, + "libavahi-ui.so.0": { + "package": "avahi-ui", + "version": "0.1.4" + }, + "liburiparser.so.1": { + "package": "uriparser", + "version": "1.0.24" + }, + "libunique-3.0.so.0": { + "package": "libunique3", + "version": "0.0.2" + }, + "liburcu-bp.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu-cds.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu-common.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu-mb.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu-qsbr.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu-signal.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "liburcu.so.6": { + "package": "userspace-rcu", + "version": "6.0.0" + }, + "libjson-glib-1.0.so.0": { + "package": "json-glib", + "version": "0.400.4" + }, + "libcap-ng.so.0": { + "package": "libcap-ng", + "version": "0.0.0" + }, + "libguile-2.0.so.22": { + "package": "guile-libs", + "version": "22.8.1" + }, + "libz.so.1": { + "package": "zlib", + "version": "1.2.11" + }, + "libuuid.so.1": { + "package": "libuuid", + "version": "1.3.0" + }, + "libSDL2_image-2.0.so.0": { + "package": "sdl2_image", + "version": "0.2.2" + }, + "librecode.so.0": { + "package": "recode", + "version": "0.0.0" + }, + "liblttng-ust-ctl.so.4": { + "package": "lttng-ust", + "version": "4.0.0" + }, + "liblttng-ust-cyg-profile-fast.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-cyg-profile.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-dl.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-fd.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-fork.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-libc-wrapper.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-pthread-wrapper.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust-tracepoint.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "liblttng-ust.so.0": { + "package": "lttng-ust", + "version": "0.0.0" + }, + "libgio-2.0.so.0": { + "package": "glib", + "version": "0.5800.1" + }, + "libglib-2.0.so.0": { + "package": "glib", + "version": "0.5800.1" + }, + "libgmodule-2.0.so.0": { + "package": "glib", + "version": "0.5800.1" + }, + "libgobject-2.0.so.0": { + "package": "glib", + "version": "0.5800.1" + }, + "libgthread-2.0.so.0": { + "package": "glib", + "version": "0.5800.1" + }, + "libp11-kit.so.0": { + "package": "p11-kit", + "version": "0.3.0" + }, + "libc.musl-x86_64.so.1": { + "package": "musl", + "version": "1" + }, + "libratbox.so": { + "package": "charybdis", + "version": "0" + }, + "libavc1394.so.0": { + "package": "libavc1394", + "version": "0.3.0" + }, + "librom1394.so.0": { + "package": "libavc1394", + "version": "0.3.0" + }, + "libwebp.so.7": { + "package": "libwebp", + "version": "7.0.3" + }, + "libwebpdecoder.so.3": { + "package": "libwebp", + "version": "3.0.3" + }, + "libwebpdemux.so.2": { + "package": "libwebp", + "version": "2.0.5" + }, + "libwebpmux.so.3": { + "package": "libwebp", + "version": "3.0.3" + }, + "libtheora.so.0": { + "package": "libtheora", + "version": "0.3.10" + }, + "libtheoradec.so.1": { + "package": "libtheora", + "version": "1.1.4" + }, + "libtheoraenc.so.1": { + "package": "libtheora", + "version": "1.1.2" + }, + "libboost_system-mt.so.1.67.0": { + "package": "boost-system", + "version": "1.67.0" + }, + "libboost_system.so.1.67.0": { + "package": "boost-system", + "version": "1.67.0" + }, + "libexpat.so.1": { + "package": "expat", + "version": "1.6.8" + }, + "libGLEW.so.2.1": { + "package": "glew", + "version": "2.1.0" + }, + "libprotobuf-lite.so.17": { + "package": "protobuf", + "version": "17.0.0" + }, + "libprotobuf.so.17": { + "package": "protobuf", + "version": "17.0.0" + }, + "libprotoc.so.17": { + "package": "protobuf", + "version": "17.0.0" + }, + "libflite.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmu_grapheme_lang.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmu_grapheme_lex.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmu_indic_lang.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmu_indic_lex.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmu_time_awb.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmu_us_awb.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmu_us_kal.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmu_us_kal16.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmu_us_rms.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmu_us_slt.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_cmulex.so.1": { + "package": "flite", + "version": "2.1" + }, + "libflite_usenglish.so.1": { + "package": "flite", + "version": "2.1" + }, + "libqpdf.so.21": { + "package": "qpdf-libs", + "version": "21.3.0" + }, + "libev.so.4": { + "package": "libev", + "version": "4.0.0" + }, + "libmspack.so.0": { + "package": "libmspack", + "version": "0.1.0" + }, + "libxkbfile.so.1": { + "package": "libxkbfile", + "version": "1.0.2" + }, + "libe2p.so.2": { + "package": "e2fsprogs-libs", + "version": "2.3" + }, + "libext2fs.so.2": { + "package": "e2fsprogs-libs", + "version": "2.4" + }, + "libss.so.2": { + "package": "e2fsprogs-libs", + "version": "2.0" + }, + "libcryptsetup.so.12": { + "package": "cryptsetup-libs", + "version": "12.3.0" + }, + "libQt3Support.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtCLucene.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtDeclarative.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtDesigner.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtDesignerComponents.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtGui.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtHelp.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtMultimedia.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtOpenGL.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtScriptTools.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "libQtSvg.so.4": { + "package": "qt-x11", + "version": "4.8.7" + }, + "psqlodbca.so": { + "package": "psqlodbc", + "version": "0" + }, + "psqlodbcw.so": { + "package": "psqlodbc", + "version": "0" + }, + "libinotifytools.so.0": { + "package": "inotify-tools", + "version": "0.4.1" + }, + "libmpeg2.so.0": { + "package": "libmpeg2", + "version": "0.1.0" + }, + "libmpeg2convert.so.0": { + "package": "libmpeg2", + "version": "0.0.0" + }, + "libmng.so.2": { + "package": "libmng", + "version": "2.0.2" + }, + "libmms.so.0": { + "package": "libmms", + "version": "0.0.2" + }, + "libfaac.so.0": { + "package": "faac", + "version": "0.0.0" + }, + "libedit.so.0": { + "package": "libedit", + "version": "0.0.59" + }, + "libcares.so.2": { + "package": "c-ares", + "version": "2.3.0" + }, + "libtinyxml.so.0": { + "package": "tinyxml", + "version": "0.2.6.2" + }, + "libmosquittopp.so.1": { + "package": "mosquitto-libs++", + "version": "1" + }, + "libs6rc.so.0.4": { + "package": "s6-rc", + "version": "0.4.1.0" + }, + "libparted-fs-resize.so.0": { + "package": "parted", + "version": "0.0.1" + }, + "libparted.so.2": { + "package": "parted", + "version": "2.0.1" + }, + "rlm_sql_unixodbc.so": { + "package": "freeradius-unixodbc", + "version": "0" + }, + "libepoxy.so.0": { + "package": "libepoxy", + "version": "0.0.0" + }, + "libnfnetlink.so.0": { + "package": "libnfnetlink", + "version": "0.2.0" + }, + "libatomic.so.1": { + "package": "libatomic", + "version": "1.2.0" + }, + "libintl.so.8": { + "package": "libintl", + "version": "8.1.5" + }, + "libltdl.so.7": { + "package": "libltdl", + "version": "7.3.1" + }, + "libgpext-samba4.so": { + "package": "samba-common-tools", + "version": "0" + }, + "libnet-keytab-samba4.so": { + "package": "samba-common-tools", + "version": "0" + }, + "libvdehist.so.0": { + "package": "vde2-libs", + "version": "0.0.1" + }, + "libvdemgmt.so.0": { + "package": "vde2-libs", + "version": "0.0.1" + }, + "libvdeplug.so.3": { + "package": "vde2-libs", + "version": "3.0.1" + }, + "libvdesnmp.so.0": { + "package": "vde2-libs", + "version": "0.0.1" + }, + "libfakeroot-0.so": { + "package": "fakeroot", + "version": "0" + }, + "libseccomp.so.2": { + "package": "libseccomp", + "version": "2.3.3" + }, + "libudns.so.0": { + "package": "udns", + "version": "0" + }, + "libjasper.so.4": { + "package": "jasper-libs", + "version": "4.0.0" + }, + "libXrandr.so.2": { + "package": "libxrandr", + "version": "2.2.0" + }, + "liblutok.so.3": { + "package": "lutok", + "version": "3.0.0" + }, + "libtirpc.so.3": { + "package": "libtirpc", + "version": "3.0.0" + }, + "libass.so.9": { + "package": "libass", + "version": "9.0.2" + }, + "libmodplug.so.1": { + "package": "libmodplug", + "version": "1.0.0" + }, + "libcroco-0.6.so.3": { + "package": "libcroco", + "version": "3.0.1" + }, + "libXext.so.6": { + "package": "libxext", + "version": "6.4.0" + }, + "libtiff.so.5": { + "package": "tiff", + "version": "5.4.0" + }, + "libfftw3l.so.3": { + "package": "fftw-long-double-libs", + "version": "3.5.8" + }, + "libfftw3l_threads.so.3": { + "package": "fftw-long-double-libs", + "version": "3.5.8" + }, + "libgsasl.so.7": { + "package": "libgsasl", + "version": "7.9.6" + }, + "libgtkspell.so.0": { + "package": "gtkspell", + "version": "0.0.0" + }, + "libformw.so.6": { + "package": "ncurses-libs", + "version": "6.1" + }, + "libmenuw.so.6": { + "package": "ncurses-libs", + "version": "6.1" + }, + "libncursesw.so.6": { + "package": "ncurses-libs", + "version": "6.1" + }, + "libpanelw.so.6": { + "package": "ncurses-libs", + "version": "6.1" + }, + "libgomp.so.1": { + "package": "libgomp", + "version": "1.0.0" + }, + "libnetsnmpagent.so.35": { + "package": "net-snmp-agent-libs", + "version": "35.0.0" + }, + "libnetsnmphelpers.so.35": { + "package": "net-snmp-agent-libs", + "version": "35.0.0" + }, + "libnetsnmpmibs.so.35": { + "package": "net-snmp-agent-libs", + "version": "35.0.0" + }, + "libnetsnmptrapd.so.35": { + "package": "net-snmp-agent-libs", + "version": "35.0.0" + }, + "libmicrohttpd.so.12": { + "package": "libmicrohttpd", + "version": "12.49.0" + }, + "libffi.so.6": { + "package": "libffi", + "version": "6.0.4" + }, + "libnih-dbus.so.1": { + "package": "libnih", + "version": "1.0.0" + }, + "libnih.so.1": { + "package": "libnih", + "version": "1.0.0" + }, + "libspandsp.so.2": { + "package": "spandsp", + "version": "2.0.0" + }, + "librarian.so.0": { + "package": "rarian", + "version": "0.0.0" + }, + "libboost_chrono-mt.so.1.67.0": { + "package": "boost-chrono", + "version": "1.67.0" + }, + "libboost_chrono.so.1.67.0": { + "package": "boost-chrono", + "version": "1.67.0" + }, + "libvanessa_adt.so.1": { + "package": "vanessa_adt", + "version": "1.0.0" + }, + "libclucene-core.so.1": { + "package": "clucene", + "version": "2.3.3.4" + }, + "libclucene-shared.so.1": { + "package": "clucene", + "version": "2.3.3.4" + }, + "libnss_winbind.so.2": { + "package": "samba-libnss-winbind", + "version": "2" + }, + "libnss_wins.so.2": { + "package": "samba-libnss-winbind", + "version": "2" + }, + "libgit2.so.27": { + "package": "libgit2", + "version": "0.27.7" + }, + "libvirt-admin.so.0": { + "package": "libvirt-libs", + "version": "0.4010.0" + }, + "libvirt-lxc.so.0": { + "package": "libvirt-libs", + "version": "0.4010.0" + }, + "libvirt-qemu.so.0": { + "package": "libvirt-libs", + "version": "0.4010.0" + }, + "libvirt.so.0": { + "package": "libvirt-libs", + "version": "0.4010.0" + }, + "libvala-0.42.so.0": { + "package": "vala", + "version": "0.0.0" + }, + "libvalaccodegen.so": { + "package": "vala", + "version": "0" + }, + "libvaladoc-0.42.so.0": { + "package": "vala", + "version": "0.0.0" + }, + "libverto-libev.so.1": { + "package": "libverto-libev", + "version": "1.0.0" + }, + "libsoxr-lsr.so.0": { + "package": "soxr", + "version": "0.1.9" + }, + "libsoxr.so.0": { + "package": "soxr", + "version": "0.1.2" + }, + "libobrender.so.32": { + "package": "openbox-libs", + "version": "32.0.0" + }, + "libobt.so.2": { + "package": "openbox-libs", + "version": "2.0.2" + }, + "libtonezone.so.2": { + "package": "dahdi-tools", + "version": "2.0.0" + }, + "libgtest1.so.41": { + "package": "nss-dev", + "version": "41" + }, + "libnsssysinit.so": { + "package": "nss-dev", + "version": "41" + }, + "libXxf86dga.so.1": { + "package": "libxxf86dga", + "version": "1.0.0" + }, + "libtsocks.so.1.8": { + "package": "tsocks", + "version": "1.8" + }, + "libsvn_swig_py-1.so.0": { + "package": "py-subversion", + "version": "0.0.0" + }, + "libssl.so.1.1": { + "package": "libssl1.1", + "version": "1.1" + }, + "libwayland-server.so.0": { + "package": "wayland-libs-server", + "version": "0.1.0" + }, + "libisns.so.0": { + "package": "open-isns-lib", + "version": "0" + }, + "libsamba-net-samba4.so": { + "package": "samba-libs-py", + "version": "0" + }, + "libsamba-python-samba4.so": { + "package": "samba-libs-py", + "version": "0" + }, + "liblua-5.2.so.0": { + "package": "lua5.2-libs", + "version": "0.0.0" + }, + "libxmlrpc++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_abyss++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_cpp.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_packetsocket.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_server++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_server_abyss++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_server_cgi++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_server_pstream++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libxmlrpc_util++.so.8": { + "package": "xmlrpc-c++", + "version": "8.39" + }, + "libgss.so.1": { + "package": "libgss", + "version": "1.1.0" + }, + "libcunit.so.1": { + "package": "cunit", + "version": "1.0.1" + }, + "libct.so.4": { + "package": "freetds", + "version": "4.0.0" + }, + "libsybdb.so.5": { + "package": "freetds", + "version": "5.1.0" + }, + "libtdsodbc.so.0": { + "package": "freetds", + "version": "0.0.0" + }, + "libSDL-1.2.so.0": { + "package": "sdl", + "version": "0.11.4" + }, + "liblucene++-contrib.so.0": { + "package": "lucene++", + "version": "3.0.7" + }, + "liblucene++.so.0": { + "package": "lucene++", + "version": "3.0.7" + }, + "libboost_iostreams-mt.so.1.67.0": { + "package": "boost-iostreams", + "version": "1.67.0" + }, + "libboost_iostreams.so.1.67.0": { + "package": "boost-iostreams", + "version": "1.67.0" + }, + "libelf.so.1": { + "package": "elfutils-libelf", + "version": "0" + }, + "libboost_graph-mt.so.1.67.0": { + "package": "boost-graph", + "version": "1.67.0" + }, + "libboost_graph.so.1.67.0": { + "package": "boost-graph", + "version": "1.67.0" + }, + "libpcre16.so.0": { + "package": "libpcre16", + "version": "0.2.10" + }, + "libgsl.so.23": { + "package": "gsl", + "version": "23.1.0" + }, + "libgslcblas.so.0": { + "package": "gsl", + "version": "0.0.0" + }, + "libf2fs.so.6": { + "package": "f2fs-tools-libs", + "version": "6.0.0" + }, + "libf2fs_format.so.5": { + "package": "f2fs-tools-libs", + "version": "5.0.0" + }, + "libc-client.so.1": { + "package": "c-client", + "version": "1.0.0" + }, + "libssh2.so.1": { + "package": "libssh2", + "version": "1.0.1" + }, + "libxcb-ewmh.so.2": { + "package": "xcb-util-wm", + "version": "2.0.0" + }, + "libxcb-icccm.so.4": { + "package": "xcb-util-wm", + "version": "4.0.0" + }, + "libsigc-2.0.so.0": { + "package": "libsigc++", + "version": "0.0.0" + }, + "libboost_wserialization-mt.so.1.67.0": { + "package": "boost-wserialization", + "version": "1.67.0" + }, + "libboost_wserialization.so.1.67.0": { + "package": "boost-wserialization", + "version": "1.67.0" + }, + "libcelt051.so.0": { + "package": "celt051", + "version": "0.0.0" + }, + "libfl.so.2": { + "package": "flex-libs", + "version": "2.0.0" + }, + "libmtp.so.9": { + "package": "libmtp", + "version": "9.4.0" + }, + "libblkid.so.1": { + "package": "libblkid", + "version": "1.1.0" + }, + "libvulkan_intel.so": { + "package": "mesa-vulkan-intel", + "version": "0" + }, + "libcanberra.so.0": { + "package": "libcanberra", + "version": "0.2.5" + }, + "libpciaccess.so.0": { + "package": "libpciaccess", + "version": "0.11.1" + }, + "libXcursor.so.1": { + "package": "libxcursor", + "version": "1.0.2" + }, + "libisoburn.so.1": { + "package": "libisoburn", + "version": "1.105.0" + }, + "libfreeswitch.so.1": { + "package": "freeswitch", + "version": "1.0.0" + }, + "libgiomm-2.4.so.1": { + "package": "glibmm", + "version": "1.3.0" + }, + "libglibmm-2.4.so.1": { + "package": "glibmm", + "version": "1.3.0" + }, + "libglibmm_generate_extra_defs-2.4.so.1": { + "package": "glibmm", + "version": "1.3.0" + }, + "libOSMesa.so.8": { + "package": "mesa-osmesa", + "version": "8.0.0" + }, + "libxcb-composite.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-damage.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-dpms.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-dri2.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-dri3.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-glx.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-present.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-randr.so.0": { + "package": "libxcb", + "version": "0.1.0" + }, + "libxcb-record.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-render.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-res.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-screensaver.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-shape.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-shm.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-sync.so.1": { + "package": "libxcb", + "version": "1.0.0" + }, + "libxcb-xf86dri.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-xfixes.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-xinerama.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-xinput.so.0": { + "package": "libxcb", + "version": "0.1.0" + }, + "libxcb-xkb.so.1": { + "package": "libxcb", + "version": "1.0.0" + }, + "libxcb-xtest.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-xv.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb-xvmc.so.0": { + "package": "libxcb", + "version": "0.0.0" + }, + "libxcb.so.1": { + "package": "libxcb", + "version": "1.1.0" + }, + "libXt.so.6": { + "package": "libxt", + "version": "6.0.0" + }, + "libebt_802_3.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_among.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_arp.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_arpreply.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_ip.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_ip6.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_limit.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_log.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_mark.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_mark_m.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_nat.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_nflog.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_pkttype.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_redirect.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_standard.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_stp.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_ulog.so": { + "package": "ebtables", + "version": "0" + }, + "libebt_vlan.so": { + "package": "ebtables", + "version": "0" + }, + "libebtable_broute.so": { + "package": "ebtables", + "version": "0" + }, + "libebtable_filter.so": { + "package": "ebtables", + "version": "0" + }, + "libebtable_nat.so": { + "package": "ebtables", + "version": "0" + }, + "libebtc.so": { + "package": "ebtables", + "version": "0" + }, + "libmandoc.so": { + "package": "mdocml", + "version": "0" + }, + "libsgutils2.so.2": { + "package": "sg3_utils", + "version": "2.0.0" + }, + "libmupdf.so.0": { + "package": "mupdf", + "version": "0" + }, + "libmupdfthird.so.0": { + "package": "mupdf", + "version": "0" + }, + "librrd.so.8": { + "package": "librrd", + "version": "8.1.0" + }, + "libdns_sd.so.1": { + "package": "avahi-compat-libdns_sd", + "version": "1.0.0" + }, + "libverto-libevent.so.1": { + "package": "libverto-libevent", + "version": "1.0.0" + }, + "libXi.so.6": { + "package": "libxi", + "version": "6.1.0" + }, + "libcap.so.2": { + "package": "libcap", + "version": "2.26" + }, + "libboost_signals-mt.so.1.67.0": { + "package": "boost-signals", + "version": "1.67.0" + }, + "libboost_signals.so.1.67.0": { + "package": "boost-signals", + "version": "1.67.0" + }, + "libhavege.so.1": { + "package": "haveged", + "version": "1.1.0" + }, + "libGL.so.1": { + "package": "mesa-gl", + "version": "1.2.0" + }, + "libgdk_pixbuf-2.0.so.0": { + "package": "gdk-pixbuf", + "version": "0.3611.0" + }, + "libgdk_pixbuf_xlib-2.0.so.0": { + "package": "gdk-pixbuf", + "version": "0.3611.0" + }, + "libksba.so.8": { + "package": "libksba", + "version": "8.11.6" + }, + "libespeak.so.1": { + "package": "espeak", + "version": "1.1.48" + }, + "libboost_unit_test_framework-mt.so.1.67.0": { + "package": "boost-unit_test_framework", + "version": "1.67.0" + }, + "libboost_unit_test_framework.so.1.67.0": { + "package": "boost-unit_test_framework", + "version": "1.67.0" + }, + "libgssglue.so.1": { + "package": "libgssglue", + "version": "1.0.0" + }, + "libestr.so.0": { + "package": "libestr", + "version": "0.0.0" + }, + "libargon2.so.1": { + "package": "argon2-libs", + "version": "1" + }, + "libvte-2.91.so.0": { + "package": "vte3", + "version": "0.5200.2" + }, + "libfcgi.so.0": { + "package": "fcgi", + "version": "0.0.0" + }, + "libspf2.so.2": { + "package": "libspf2", + "version": "2.1.0" + }, + "libreplace-samba4.so": { + "package": "libwbclient", + "version": "0" + }, + "libwbclient.so.0": { + "package": "libwbclient", + "version": "0.14" + }, + "libwinbind-client-samba4.so": { + "package": "libwbclient", + "version": "0" + }, + "libopusfile.so.0": { + "package": "opusfile", + "version": "0.4.4" + }, + "libopusurl.so.0": { + "package": "opusfile", + "version": "0.4.4" + }, + "libfreebl3.so": { + "package": "nss", + "version": "41" + }, + "libgtestutil.so.41": { + "package": "nss", + "version": "41" + }, + "libnss3.so": { + "package": "nss", + "version": "41" + }, + "libnssckbi.so": { + "package": "nss", + "version": "41" + }, + "libnssdbm3.so": { + "package": "nss", + "version": "41" + }, + "libnssutil3.so": { + "package": "nss", + "version": "41" + }, + "libsmime3.so": { + "package": "nss", + "version": "41" + }, + "libsoftokn3.so": { + "package": "nss", + "version": "41" + }, + "libssl3.so": { + "package": "nss", + "version": "41" + }, + "libatk-1.0.so.0": { + "package": "atk", + "version": "0.23009.1" + }, + "libnetfilter_queue.so.1": { + "package": "libnetfilter_queue", + "version": "1.4.0" + }, + "lib90_sieve_plugin.so": { + "package": "dovecot-pigeonhole-plugin", + "version": "0" + }, + "lib95_imap_filter_sieve_plugin.so": { + "package": "dovecot-pigeonhole-plugin", + "version": "0" + }, + "lib95_imap_sieve_plugin.so": { + "package": "dovecot-pigeonhole-plugin", + "version": "0" + }, + "libdovecot-sieve.so.0": { + "package": "dovecot-pigeonhole-plugin", + "version": "0.0.0" + }, + "libzdb.so.11": { + "package": "libzdb", + "version": "11.0.0" + }, + "libsamplerate.so.0": { + "package": "libsamplerate", + "version": "0.1.8" + }, + "libnl-3.so.200": { + "package": "libnl3", + "version": "200.26.0" + }, + "libnl-genl-3.so.200": { + "package": "libnl3", + "version": "200.26.0" + }, + "libnl-idiag-3.so.200": { + "package": "libnl3", + "version": "200.26.0" + }, + "libnl-nf-3.so.200": { + "package": "libnl3", + "version": "200.26.0" + }, + "libnl-route-3.so.200": { + "package": "libnl3", + "version": "200.26.0" + }, + "libnl-xfrm-3.so.200": { + "package": "libnl3", + "version": "200.26.0" + }, + "libeinfo.so.1": { + "package": "openrc", + "version": "1" + }, + "librc.so.1": { + "package": "openrc", + "version": "1" + }, + "libformw.so.5": { + "package": "ncurses5-widec-libs", + "version": "5.9" + }, + "libmenuw.so.5": { + "package": "ncurses5-widec-libs", + "version": "5.9" + }, + "libncursesw.so.5": { + "package": "ncurses5-widec-libs", + "version": "5.9" + }, + "libpanelw.so.5": { + "package": "ncurses5-widec-libs", + "version": "5.9" + }, + "libavahi-client.so.3": { + "package": "avahi-libs", + "version": "3.2.9" + }, + "libavahi-common.so.3": { + "package": "avahi-libs", + "version": "3.5.3" + }, + "libfdt.so.1": { + "package": "libfdt", + "version": "0" + }, + "libfuse3.so.3": { + "package": "fuse3", + "version": "3.2.6" + }, + "libjpeg.so.8": { + "package": "libjpeg-turbo", + "version": "8.1.2" + }, + "libturbojpeg.so.0": { + "package": "libjpeg-turbo", + "version": "0.1.0" + }, + "libexiv2.so.26": { + "package": "exiv2", + "version": "26.0.0" + }, + "libxml2.so.2": { + "package": "libxml2", + "version": "2.9.9" + }, + "libogg.so.0": { + "package": "libogg", + "version": "0.8.3" + }, + "libcupsfilters.so.1": { + "package": "cups-filters-libs", + "version": "1.0.0" + }, + "libfontembed.so.1": { + "package": "cups-filters-libs", + "version": "1.0.0" + }, + "libwv-1.2.so.4": { + "package": "wv", + "version": "4.0.5" + }, + "libapr-1.so.0": { + "package": "apr", + "version": "0.6.5" + }, + "libmikmod.so.3": { + "package": "libmikmod", + "version": "3.3.0" + }, + "rlm_sql_sqlite.so": { + "package": "freeradius-sqlite", + "version": "0" + }, + "libpoppler-cpp.so.0": { + "package": "poppler", + "version": "0.3.0" + }, + "libpoppler.so.67": { + "package": "poppler", + "version": "67.0.0" + }, + "libecpg.so.6": { + "package": "postgresql-libs", + "version": "6.11" + }, + "libecpg_compat.so.3": { + "package": "postgresql-libs", + "version": "3.11" + }, + "libpgtypes.so.3": { + "package": "postgresql-libs", + "version": "3.11" + }, + "libboost_context-mt.so.1.67.0": { + "package": "boost-context", + "version": "1.67.0" + }, + "libnetfilter_cttimeout.so.1": { + "package": "libnetfilter_cttimeout", + "version": "1.0.0" + }, + "libboost_python27-mt.so.1.67.0": { + "package": "boost-python", + "version": "1.67.0" + }, + "libboost_python27.so.1.67.0": { + "package": "boost-python", + "version": "1.67.0" + }, + "libSDL2_mixer-2.0.so.0": { + "package": "sdl2_mixer", + "version": "0.2.2" + }, + "libfaad.so.2": { + "package": "faad2", + "version": "2.0.0" + }, + "libhylafax-6.0.so.7": { + "package": "hylafax", + "version": "7" + }, + "libcogl-gles2.so.20": { + "package": "cogl", + "version": "20.4.2" + }, + "libcogl-pango.so.20": { + "package": "cogl", + "version": "20.4.2" + }, + "libcogl-path.so.20": { + "package": "cogl", + "version": "20.4.2" + }, + "libcogl.so.20": { + "package": "cogl", + "version": "20.4.2" + }, + "libyajl.so.2": { + "package": "yajl", + "version": "2.1.0" + }, + "libcommon.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "libmc.so": { + "package": "xrdp", + "version": "0" + }, + "libpainter.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "librfxencode.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "libscp.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "libvnc.so": { + "package": "xrdp", + "version": "0" + }, + "libxrdp.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "libxrdpapi.so.0": { + "package": "xrdp", + "version": "0.0.0" + }, + "libxup.so": { + "package": "xrdp", + "version": "0" + }, + "libwavpack.so.1": { + "package": "wavpack", + "version": "1.2.0" + }, + "libmilter.so.1.0.2": { + "package": "libmilter", + "version": "1.0.2" + }, + "libboost_serialization-mt.so.1.67.0": { + "package": "boost-serialization", + "version": "1.67.0" + }, + "libboost_serialization.so.1.67.0": { + "package": "boost-serialization", + "version": "1.67.0" + }, + "libgsf-1.so.114": { + "package": "libgsf", + "version": "114.0.44" + }, + "libluajit-5.1.so.2": { + "package": "luajit", + "version": "2.1.0" + }, + "libgirepository-1.0.so.1": { + "package": "gobject-introspection", + "version": "1.0.0" + }, + "libXaw.so.7": { + "package": "libxaw", + "version": "7.0.0" + }, + "libwayland-client.so.0": { + "package": "wayland-libs-client", + "version": "0.3.0" + }, + "proto_dhcp.so": { + "package": "freeradius", + "version": "0" + }, + "proto_vmps.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_always.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_attr_filter.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_cache.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_cache_rbtree.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_chap.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_counter.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_cram.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_date.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_detail.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_dhcp.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_digest.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_dynamic_clients.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_exec.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_expiration.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_expr.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_files.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_ippool.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_linelog.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_logintime.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_mschap.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_otp.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_pap.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_passwd.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_preprocess.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_radutmp.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_realm.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_replicate.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_soh.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_sometimes.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_test.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_unix.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_unpack.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_utf8.so": { + "package": "freeradius", + "version": "0" + }, + "rlm_wimax.so": { + "package": "freeradius", + "version": "0" + }, + "libatk-bridge-2.0.so.0": { + "package": "at-spi2-atk", + "version": "0.0.0" + }, + "postfix-ldap.so": { + "package": "postfix-ldap", + "version": "0" + }, + "libsylph-0.so.1": { + "package": "sylpheed", + "version": "1.3.0" + }, + "libsylpheed-plugin-0.so.1": { + "package": "sylpheed", + "version": "1.3.0" + }, + "libssl.so.45": { + "package": "libressl2.7-libssl", + "version": "45.0.1" + }, + "libcryptsetup.so.4": { + "package": "cryptsetup1-libs", + "version": "4.7.0" + }, + "libgphoto2.so.6": { + "package": "libgphoto2", + "version": "6.0.0" + }, + "libgphoto2_port.so.12": { + "package": "libgphoto2", + "version": "12.0.0" + }, + "libpoppler-qt4.so.4": { + "package": "poppler-qt4", + "version": "4.11.0" + }, + "libexecline.so.2.5": { + "package": "execline", + "version": "2.5.0.1" + }, + "libskarnet.so.2.7": { + "package": "skalibs", + "version": "2.7.0.0" + }, + "rlm_sql.so": { + "package": "freeradius-sql", + "version": "0" + }, + "rlm_sql_null.so": { + "package": "freeradius-sql", + "version": "0" + }, + "rlm_sqlcounter.so": { + "package": "freeradius-sql", + "version": "0" + }, + "rlm_sqlippool.so": { + "package": "freeradius-sql", + "version": "0" + }, + "libcom_err.so.2": { + "package": "libcom_err", + "version": "2.1" + }, + "libobjc.so.4": { + "package": "libobjc", + "version": "4.0.0" + }, + "libfuse.so.2": { + "package": "fuse", + "version": "2.9.8" + }, + "libulockmgr.so.1": { + "package": "fuse", + "version": "1.0.1" + }, + "libsndfile.so.1": { + "package": "libsndfile", + "version": "1.0.28" + }, + "libaudit.so.1": { + "package": "audit-libs", + "version": "1.0.0" + }, + "libauparse.so.0": { + "package": "audit-libs", + "version": "0.0.0" + }, + "libsmbclient.so.0": { + "package": "libsmbclient", + "version": "0.3.1" + }, + "libspice-server.so.1": { + "package": "spice-server", + "version": "1.12.5" + }, + "libencfs.so.1.9": { + "package": "encfs", + "version": "1.9.5" + }, + "libdmmp.so.0.2.0": { + "package": "multipath-tools", + "version": "0.2.0" + }, + "libmpathcmd.so.0": { + "package": "multipath-tools", + "version": "0" + }, + "libmpathpersist.so.0": { + "package": "multipath-tools", + "version": "0" + }, + "libmultipath.so.0": { + "package": "multipath-tools", + "version": "0" + }, + "libgps.so.24": { + "package": "gpsd", + "version": "24.0.0" + }, + "libdbi.so.1": { + "package": "libdbi", + "version": "1.1.0" + }, + "libiniparser.so.1": { + "package": "iniparser", + "version": "1" + }, + "libdaq.so.2": { + "package": "daq", + "version": "2.0.4" + }, + "libsfbpf.so.0": { + "package": "daq", + "version": "0.0.1" + }, + "librsvg-2.so.2": { + "package": "librsvg", + "version": "2.40.20" + }, + "libraw1394.so.11": { + "package": "libraw1394", + "version": "11.1.0" + }, + "libtag.so.1": { + "package": "taglib", + "version": "1.17.0" + }, + "libtag_c.so.0": { + "package": "taglib", + "version": "0.0.0" + }, + "libboost_wave-mt.so.1.67.0": { + "package": "boost-wave", + "version": "1.67.0" + }, + "libsqlite3.so.0": { + "package": "sqlite-libs", + "version": "0.8.6" + }, + "libgee-0.8.so.2": { + "package": "libgee", + "version": "2.6.1" + }, + "libkeybinder.so.0": { + "package": "keybinder", + "version": "0.1.0" + }, + "libvpx.so.4": { + "package": "libvpx", + "version": "4.1.0" + }, + "libjansson.so.4": { + "package": "jansson", + "version": "4.11.0" + }, + "libSM.so.6": { + "package": "libsm", + "version": "6.0.1" + }, + "libasound.so.2": { + "package": "alsa-lib", + "version": "2.0.0" + }, + "libdb_cxx-5.3.so": { + "package": "db-c++", + "version": "0" + }, + "libexif.so.12": { + "package": "libexif", + "version": "12.3.3" + }, + "libpcre2-16.so.0": { + "package": "libpcre2-16", + "version": "0.7.1" + }, + "libcdio++.so.0": { + "package": "libcdio++", + "version": "0.0.2" + }, + "libiso9660++.so.0": { + "package": "libcdio++", + "version": "0.0.0" + }, + "libdb-5.3.so": { + "package": "db", + "version": "0" + }, + "libcddb.so.2": { + "package": "libcddb", + "version": "2.2.3" + }, + "libfarstream-0.2.so.5": { + "package": "farstream", + "version": "5.1.0" + }, + "libcppunit_subunit.so.0": { + "package": "subunit-libs", + "version": "0.0.0" + }, + "libsubunit.so.0": { + "package": "subunit-libs", + "version": "0.0.0" + }, + "libnetfilter_cthelper.so.0": { + "package": "libnetfilter_cthelper", + "version": "0.0.0" + }, + "libmp3lame.so.0": { + "package": "lame", + "version": "0.0.0" + }, + "libodbc.so.2": { + "package": "unixodbc", + "version": "2.0.0" + }, + "libodbccr.so.2": { + "package": "unixodbc", + "version": "2.0.0" + }, + "libodbcinst.so.2": { + "package": "unixodbc", + "version": "2.0.0" + }, + "rlm_ldap.so": { + "package": "freeradius-ldap", + "version": "0" + }, + "libsmbpasswdparser-samba4.so": { + "package": "samba-dc", + "version": "0" + }, + "libmatroska.so.6": { + "package": "libmatroska", + "version": "6.0.0" + }, + "libconfig.so.9": { + "package": "libconfig", + "version": "9.2.0" + }, + "libLLVM-5.0.so": { + "package": "llvm5-libs", + "version": "0" + }, + "libsecret-1.so.0": { + "package": "libsecret", + "version": "0.0.0" + }, + "libnpth.so.0": { + "package": "npth", + "version": "0.1.2" + }, + "libgbm.so.1": { + "package": "mesa-gbm", + "version": "1.0.0" + }, + "libenchant.so.1": { + "package": "enchant", + "version": "1.6.0" + }, + "libcairo-script-interpreter.so.2": { + "package": "cairo", + "version": "2.11600.0" + }, + "libcairo.so.2": { + "package": "cairo", + "version": "2.11600.0" + }, + "libvte.so.9": { + "package": "vte", + "version": "9.2800.2" + }, + "libjsoncpp.so.20": { + "package": "jsoncpp", + "version": "20" + }, + "libvncclient.so.1": { + "package": "libvncserver", + "version": "1.0.0" + }, + "libvncserver.so.1": { + "package": "libvncserver", + "version": "1.0.0" + }, + "rlm_perl.so": { + "package": "freeradius-perl", + "version": "0" + }, + "libdriver_sqlite.so": { + "package": "dovecot-sqlite", + "version": "0" + }, + "libxdg-basedir.so.1": { + "package": "libxdg-basedir", + "version": "1.2.0" + }, + "libfftw3f.so.3": { + "package": "fftw-single-libs", + "version": "3.5.8" + }, + "libfftw3f_threads.so.3": { + "package": "fftw-single-libs", + "version": "3.5.8" + }, + "libxcb-render-util.so.0": { + "package": "xcb-util-renderutil", + "version": "0.0.0" + }, + "libverto-glib.so.1": { + "package": "libverto-glib", + "version": "1.0.0" + }, + "libpcre32.so.0": { + "package": "libpcre32", + "version": "0.0.10" + }, + "libXft.so.2": { + "package": "libxft", + "version": "2.3.2" + }, + "libiptcdata.so.0": { + "package": "libiptcdata", + "version": "0.3.3" + }, + "libvulkan_radeon.so": { + "package": "mesa-vulkan-ati", + "version": "0" + }, + "libGLU.so.1": { + "package": "glu", + "version": "1.3.1" + }, + "libevent-2.1.so.6": { + "package": "libevent", + "version": "6.0.2" + }, + "libevent_core-2.1.so.6": { + "package": "libevent", + "version": "6.0.2" + }, + "libevent_extra-2.1.so.6": { + "package": "libevent", + "version": "6.0.2" + }, + "libevent_openssl-2.1.so.6": { + "package": "libevent", + "version": "6.0.2" + }, + "libevent_pthreads-2.1.so.6": { + "package": "libevent", + "version": "6.0.2" + }, + "libgstadder.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstallocators-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstalsa.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstapp-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstapp.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstaudio-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstaudioconvert.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstaudiomixer.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstaudiorate.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstaudioresample.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstaudiotestsrc.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstcdparanoia.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstencoding.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstfft-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstgio.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstgl-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstogg.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstopengl.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstopus.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstpango.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstpbtypes.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstpbutils-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstplayback.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstrawparse.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstriff-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstrtp-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstrtsp-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstsdp-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstsubparse.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgsttag-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgsttcp.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgsttheora.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgsttypefindfunctions.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstvideo-1.0.so.0": { + "package": "gst-plugins-base", + "version": "0.1404.0" + }, + "libgstvideoconvert.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstvideorate.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstvideoscale.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstvideotestsrc.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstvolume.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstvorbis.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstximagesink.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libgstxvimagesink.so": { + "package": "gst-plugins-base", + "version": "0" + }, + "libosp.so.5": { + "package": "opensp", + "version": "5.0.0" + }, + "libgettextpo.so.0": { + "package": "gettext-libs", + "version": "0.5.4" + }, + "libpostfix-dns.so": { + "package": "postfix", + "version": "0" + }, + "libpostfix-global.so": { + "package": "postfix", + "version": "0" + }, + "libpostfix-master.so": { + "package": "postfix", + "version": "0" + }, + "libpostfix-tls.so": { + "package": "postfix", + "version": "0" + }, + "libpostfix-util.so": { + "package": "postfix", + "version": "0" + }, + "libboost_random-mt.so.1.67.0": { + "package": "boost-random", + "version": "1.67.0" + }, + "libboost_random.so.1.67.0": { + "package": "boost-random", + "version": "1.67.0" + }, + "libunbound.so.8": { + "package": "unbound-libs", + "version": "8.0.3" + }, + "postfix-mysql.so": { + "package": "postfix-mysql", + "version": "0" + }, + "libwx_gtk2u_media-2.8.so.0": { + "package": "wxgtk2.8-media", + "version": "0.8.0" + }, + "cpufreqd_acpi.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_apm.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_cpu.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_exec.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_governor_parameters.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_nforce2.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_pmu.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_programs.so": { + "package": "cpufreqd", + "version": "0" + }, + "cpufreqd_tau.so": { + "package": "cpufreqd", + "version": "0" + }, + "libexslt.so.0": { + "package": "libxslt", + "version": "0.8.20" + }, + "libxslt.so.1": { + "package": "libxslt", + "version": "1.1.33" + }, + "libortp.so.10": { + "package": "ortp", + "version": "10.0.0" + }, + "cmd-mirror.so": { + "package": "lftp", + "version": "0" + }, + "cmd-sleep.so": { + "package": "lftp", + "version": "0" + }, + "cmd-torrent.so": { + "package": "lftp", + "version": "0" + }, + "liblftp-jobs.so.0": { + "package": "lftp", + "version": "0.0.0" + }, + "liblftp-network.so": { + "package": "lftp", + "version": "0" + }, + "liblftp-pty.so": { + "package": "lftp", + "version": "0" + }, + "liblftp-tasks.so.0": { + "package": "lftp", + "version": "0.0.0" + }, + "proto-file.so": { + "package": "lftp", + "version": "0" + }, + "proto-fish.so": { + "package": "lftp", + "version": "0" + }, + "proto-ftp.so": { + "package": "lftp", + "version": "0" + }, + "proto-http.so": { + "package": "lftp", + "version": "0" + }, + "proto-sftp.so": { + "package": "lftp", + "version": "0" + }, + "libclucene-contribs-lib.so.1": { + "package": "clucene-contribs", + "version": "2.3.3.4" + }, + "libcups.so.2": { + "package": "cups-libs", + "version": "2" + }, + "libcupsimage.so.2": { + "package": "cups-libs", + "version": "2" + }, + "libvanessa_logger.so.0": { + "package": "vanessa_logger", + "version": "0.0.5" + }, + "libboost_coroutine-mt.so.1.67.0": { + "package": "boost-coroutine", + "version": "1.67.0" + }, + "libboost_coroutine.so.1.67.0": { + "package": "boost-coroutine", + "version": "1.67.0" + }, + "libxmlrpc_client++.so.8": { + "package": "xmlrpc-c-client++", + "version": "8.39" + }, + "libck-connector.so.0": { + "package": "consolekit2", + "version": "0.0.0" + }, + "libconsolekit.so.1": { + "package": "consolekit2", + "version": "1.0.0" + }, + "libhttp_parser.so.2.8": { + "package": "http-parser", + "version": "2.8.1" + }, + "libotr.so.5": { + "package": "libotr", + "version": "5.1.1" + }, + "libmcrypt.so.4": { + "package": "libmcrypt", + "version": "4.4.8" + }, + "liblogging-stdlog.so.0": { + "package": "liblogging", + "version": "0.1.0" + }, + "libconfig++.so.9": { + "package": "libconfig++", + "version": "9.2.0" + }, + "libfts.so.0": { + "package": "fts", + "version": "0.0.0" + }, + "libvirglrenderer.so.0": { + "package": "virglrenderer", + "version": "0.2.0" + }, + "libpcsclite.so.1": { + "package": "pcsc-lite-libs", + "version": "1.0.0" + }, + "libpcscspy.so.0": { + "package": "pcsc-lite-libs", + "version": "0.0.0" + }, + "libvdpau.so.1": { + "package": "libvdpau", + "version": "1.0.0" + }, + "libczmq.so.4": { + "package": "czmq", + "version": "4.1.1" + }, + "libXRes.so.1": { + "package": "libxres", + "version": "1.0.0" + }, + "libXfixes.so.3": { + "package": "libxfixes", + "version": "3.1.0" + }, + "libbaccats-postgresql-9.4.1.so": { + "package": "bacula-pgsql", + "version": "0" + }, + "libdbus-glib-1.so.2": { + "package": "dbus-glib", + "version": "2.3.3" + }, + "libXfont.so.1": { + "package": "libxfont", + "version": "1.4.1" + }, + "libwayland-egl.so.1": { + "package": "wayland-libs-egl", + "version": "1.0.0" + }, + "libnjb.so.5": { + "package": "libnjb", + "version": "5.1.1" + }, + "libglade-2.0.so.0": { + "package": "libglade", + "version": "0.0.7" + }, + "libportaudio.so.2": { + "package": "portaudio", + "version": "2.0.0" + }, + "libpaper.so.1": { + "package": "libpaper", + "version": "1.1.2" + }, + "librsync.so.2": { + "package": "librsync", + "version": "2.0.2" + }, + "libvarnishapi.so.2": { + "package": "varnish-libs", + "version": "2.0.0" + }, + "libHDB-SAMBA4-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libLIBWBCLIENT-OLD-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libauth-unix-token-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libauth4-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libcluster-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libdb-glue-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libdcerpc-samr.so.0": { + "package": "samba-dc-libs", + "version": "0.0.1" + }, + "libdcerpc-server.so.0": { + "package": "samba-dc-libs", + "version": "0.0.1" + }, + "libdnsserver-common-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libdsdb-module-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libhdb-samba4.so.11": { + "package": "samba-dc-libs", + "version": "11.0.2" + }, + "libkdc-samba4.so.2": { + "package": "samba-dc-libs", + "version": "2.0.0" + }, + "libpac-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libposix-eadb-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libprocess-model-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libsamba-policy.so.0": { + "package": "samba-dc-libs", + "version": "0.0.1" + }, + "libservice-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libshares-samba4.so": { + "package": "samba-dc-libs", + "version": "0" + }, + "libgsm.so.1": { + "package": "gsm", + "version": "1.0.12" + }, + "libpython2.7.so.1.0": { + "package": "python2", + "version": "1.0" + }, + "postfix-lmdb.so": { + "package": "postfix-lmdb", + "version": "0" + }, + "libXdmcp.so.6": { + "package": "libxdmcp", + "version": "6.0.0" + }, + "libopenobex.so.2": { + "package": "openobex", + "version": "1.7.2" + }, + "libgettextlib-0.19.8.1.so": { + "package": "gettext", + "version": "0" + }, + "libgettextsrc-0.19.8.1.so": { + "package": "gettext", + "version": "0" + }, + "libcheck.so.0": { + "package": "check", + "version": "0.0.0" + }, + "libatspi.so.0": { + "package": "at-spi2-core", + "version": "0.0.1" + }, + "libdrm.so.2": { + "package": "libdrm", + "version": "2.4.0" + }, + "libdrm_amdgpu.so.1": { + "package": "libdrm", + "version": "1.0.0" + }, + "libdrm_freedreno.so.1": { + "package": "libdrm", + "version": "1.0.0" + }, + "libdrm_intel.so.1": { + "package": "libdrm", + "version": "1.0.0" + }, + "libdrm_nouveau.so.2": { + "package": "libdrm", + "version": "2.0.0" + }, + "libdrm_radeon.so.1": { + "package": "libdrm", + "version": "1.0.1" + }, + "libkms.so.1": { + "package": "libdrm", + "version": "1.0.0" + }, + "libdvbcsa.so.1": { + "package": "libdvbcsa", + "version": "1.0.1" + }, + "liblua.so.5": { + "package": "lua5.1-libs", + "version": "5.1.4" + }, + "libmad.so.0": { + "package": "libmad", + "version": "0.2.1" + }, + "libpcre.so.1": { + "package": "pcre", + "version": "1.2.10" + }, + "libpcreposix.so.0": { + "package": "pcre", + "version": "0.0.6" + }, + "libevtlog-3.19.so.0": { + "package": "syslog-ng", + "version": "0.0.0" + }, + "libloggen_helper-3.19.so.0": { + "package": "syslog-ng", + "version": "0.0.0" + }, + "libloggen_plugin-3.19.so.0": { + "package": "syslog-ng", + "version": "0.0.0" + }, + "libsecret-storage.so.0": { + "package": "syslog-ng", + "version": "0.0.0" + }, + "libsyslog-ng-3.19.so.0": { + "package": "syslog-ng", + "version": "0.0.0" + }, + "libvanessa_socket.so.2": { + "package": "vanessa_socket", + "version": "2.1.0" + }, + "libdnet.so.1": { + "package": "libdnet", + "version": "1.0.1" + }, + "postgresql-bdr:libecpg.so.6": { + "package": "postgresql-bdr", + "version": "6.6" + }, + "postgresql-bdr:libecpg_compat.so.3": { + "package": "postgresql-bdr", + "version": "3.6" + }, + "postgresql-bdr:libpgtypes.so.3": { + "package": "postgresql-bdr", + "version": "3.5" + }, + "libxcb-keysyms.so.1": { + "package": "xcb-util-keysyms", + "version": "1.0.0" + }, + "libsvn_swig_perl-1.so.0": { + "package": "perl-subversion", + "version": "0.0.0" + }, + "liblmdb.so.0": { + "package": "lmdb", + "version": "0.0.0" + }, + "libIPMIlanserv.so.0": { + "package": "openipmi-lanserv", + "version": "0.0.1" + }, + "libpcre2-8.so.0": { + "package": "pcre2", + "version": "0.7.1" + }, + "libpcre2-posix.so.2": { + "package": "pcre2", + "version": "2.0.1" + }, + "libevtlog.so.0": { + "package": "eventlog", + "version": "0.0.0" + }, + "libSDL2_ttf-2.0.so.0": { + "package": "sdl2_ttf", + "version": "0.14.0" + }, + "libbz2.so.1": { + "package": "libbz2", + "version": "1.0.6" + }, + "libboost_program_options-mt.so.1.67.0": { + "package": "boost-program_options", + "version": "1.67.0" + }, + "libboost_program_options.so.1.67.0": { + "package": "boost-program_options", + "version": "1.67.0" + }, + "libMESSAGING-SEND-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libcli-spoolss-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libdcerpc-binding.so.0": { + "package": "samba-common-libs", + "version": "0.0.1" + }, + "libdcerpc-samba-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "liblibcli-lsa3-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "liblibcli-netlogon3-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "liblibsmb-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libmsrpc3-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libndr-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libsamba-passdb.so.0": { + "package": "samba-common-libs", + "version": "0.27.2" + }, + "libtrusts-util-samba4.so": { + "package": "samba-common-libs", + "version": "0" + }, + "libyaml-0.so.2": { + "package": "yaml", + "version": "2.0.5" + }, + "libatasmart.so.4": { + "package": "libatasmart", + "version": "4.0.5" + }, + "libgif.so.7": { + "package": "giflib", + "version": "7.0.0" + }, + "libmpc.so.3": { + "package": "mpc1", + "version": "3.0.0" + }, + "bpipe-fd.so": { + "package": "bacula-libs", + "version": "0" + }, + "libbac-9.4.1.so": { + "package": "bacula-libs", + "version": "0" + }, + "libbaccfg-9.4.1.so": { + "package": "bacula-libs", + "version": "0" + }, + "libbacfind-9.4.1.so": { + "package": "bacula-libs", + "version": "0" + }, + "libbacsd-9.4.1.so": { + "package": "bacula-libs", + "version": "0" + }, + "libbacsql-9.4.1.so": { + "package": "bacula-libs", + "version": "0" + }, + "libclang.so.5.0": { + "package": "clang-libs", + "version": "5.0" + }, + "group_file.so": { + "package": "sudo", + "version": "0" + }, + "libsudo_noexec.so": { + "package": "sudo", + "version": "0" + }, + "libsudo_util.so.0": { + "package": "sudo", + "version": "0.0.0" + }, + "sudoers.so": { + "package": "sudo", + "version": "0" + }, + "system_group.so": { + "package": "sudo", + "version": "0" + }, + "libs6dns.so.2.3": { + "package": "s6-dns", + "version": "2.3.0.1" + }, + "libskadns.so.2.3": { + "package": "s6-dns", + "version": "2.3.0.1" + }, + "libI810XvMC.so.1": { + "package": "xf86-video-intel", + "version": "1.0.0" + }, + "libIntelXvMC.so.1": { + "package": "xf86-video-intel", + "version": "1.0.0" + }, + "libasn1.so.8": { + "package": "heimdal-libs", + "version": "8.0.0" + }, + "libgssapi.so.3": { + "package": "heimdal-libs", + "version": "3.0.0" + }, + "libhcrypto.so.4": { + "package": "heimdal-libs", + "version": "4.1.0" + }, + "libhdb.so.9": { + "package": "heimdal-libs", + "version": "9.2.0" + }, + "libheimbase.so.1": { + "package": "heimdal-libs", + "version": "1.0.0" + }, + "libheimntlm.so.0": { + "package": "heimdal-libs", + "version": "0.1.0" + }, + "libhx509.so.5": { + "package": "heimdal-libs", + "version": "5.0.0" + }, + "libkadm5clnt.so.7": { + "package": "heimdal-libs", + "version": "7.0.1" + }, + "libkadm5srv.so.8": { + "package": "heimdal-libs", + "version": "8.0.1" + }, + "libkafs.so.0": { + "package": "heimdal-libs", + "version": "0.5.1" + }, + "libkdc.so.2": { + "package": "heimdal-libs", + "version": "2.0.0" + }, + "libkrb5.so.26": { + "package": "heimdal-libs", + "version": "26.0.0" + }, + "libotp.so.0": { + "package": "heimdal-libs", + "version": "0.1.5" + }, + "libroken.so.18": { + "package": "heimdal-libs", + "version": "18.1.0" + }, + "libsl.so.0": { + "package": "heimdal-libs", + "version": "0.2.1" + }, + "libwind.so.0": { + "package": "heimdal-libs", + "version": "0.0.0" + }, + "windc.so.0": { + "package": "heimdal-libs", + "version": "0.0.0" + }, + "libcharon.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libradius.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libsimaka.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libstrongswan.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libtls.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libvici.so.0": { + "package": "strongswan", + "version": "0.0.0" + }, + "libXvMC.so.1": { + "package": "libxvmc", + "version": "1.0.0" + }, + "libXvMCW.so.1": { + "package": "libxvmc", + "version": "1.0.0" + }, + "libORBit-2.so.0": { + "package": "orbit2", + "version": "0.1.0" + }, + "libORBit-imodule-2.so.0": { + "package": "orbit2", + "version": "0.0.0" + }, + "libORBitCosNaming-2.so.0": { + "package": "orbit2", + "version": "0.1.0" + }, + "libquazip.so.1": { + "package": "quazip", + "version": "1.0.0" + }, + "liblldp_clif.so.1": { + "package": "open-lldp", + "version": "1.0.0" + }, + "libSDL_mixer-1.2.so.0": { + "package": "sdl_mixer", + "version": "0.12.0" + }, + "libfftw3.so.3": { + "package": "fftw-double-libs", + "version": "3.5.8" + }, + "libfftw3_threads.so.3": { + "package": "fftw-double-libs", + "version": "3.5.8" + }, + "libgcrypt.so.20": { + "package": "libgcrypt", + "version": "20.2.4" + }, + "libpyldb-util.so.1": { + "package": "py2-ldb", + "version": "1.3.8" + }, + "libshout.so.3": { + "package": "libshout", + "version": "3.2.0" + }, + "libidn.so.12": { + "package": "libidn", + "version": "12.6.0" + }, + "libmp3splt.so.0": { + "package": "libmp3splt", + "version": "0.0.9" + }, + "libpangomm-1.4.so.1": { + "package": "pangomm", + "version": "1.0.30" + }, + "libXp.so.6": { + "package": "libxp", + "version": "6.2.0" + }, + "libnetfilter_acct.so.1": { + "package": "libnetfilter_acct", + "version": "1.0.0" + }, + "libImlib2.so.1": { + "package": "imlib2", + "version": "1.5.1" + }, + "libbearssl.so.0": { + "package": "bearssl", + "version": "0.6" + }, + "libaspell.so.15": { + "package": "aspell-libs", + "version": "15.1.5" + }, + "libpspell.so.15": { + "package": "aspell-libs", + "version": "15.1.5" + }, + "libSDL2-2.0.so.0": { + "package": "sdl2", + "version": "0.9.0" + }, + "libproxy.so.1": { + "package": "libproxy", + "version": "1.0.0" + }, + "libical.so.2": { + "package": "libical", + "version": "2.0.0" + }, + "libical_cxx.so.2": { + "package": "libical", + "version": "2.0.0" + }, + "libicalss.so.2": { + "package": "libical", + "version": "2.0.0" + }, + "libicalss_cxx.so.2": { + "package": "libical", + "version": "2.0.0" + }, + "libicalvcal.so.2": { + "package": "libical", + "version": "2.0.0" + }, + "libcdt.so.5": { + "package": "graphviz", + "version": "5.0.0" + }, + "libcgraph.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvc.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvplugin_core.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvplugin_dot_layout.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvplugin_neato_layout.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvplugin_pango.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvplugin_xlib.so.6": { + "package": "graphviz", + "version": "6.0.0" + }, + "libgvpr.so.2": { + "package": "graphviz", + "version": "2.0.0" + }, + "liblab_gamut.so.1": { + "package": "graphviz", + "version": "1.0.0" + }, + "libpathplan.so.4": { + "package": "graphviz", + "version": "4.0.0" + }, + "libxdot.so.4": { + "package": "graphviz", + "version": "4.0.0" + }, + "libcloog-isl.so.4": { + "package": "cloog", + "version": "4.0.0" + }, + "libsysfs.so.2": { + "package": "sysfsutils", + "version": "2.0.1" + }, + "libnftnl.so.7": { + "package": "libnftnl-libs", + "version": "7.3.0" + }, + "libspeex.so.1": { + "package": "speex", + "version": "1.5.1" + }, + "libmowgli-2.so.0": { + "package": "libmowgli", + "version": "0.0.0" + }, + "libfpm_pb.so.0": { + "package": "quagga", + "version": "0.0.0" + }, + "libospf.so.0": { + "package": "quagga", + "version": "0.0.0" + }, + "libospfapiclient.so.0": { + "package": "quagga", + "version": "0.0.0" + }, + "libquagga_pb.so.0": { + "package": "quagga", + "version": "0.0.0" + }, + "libzebra.so.1": { + "package": "quagga", + "version": "1.0.0" + }, + "libdevmapper-event-lvm2.so.2.02": { + "package": "lvm2-libs", + "version": "2.02" + }, + "liblvm2app.so.2.2": { + "package": "lvm2-libs", + "version": "2.2" + }, + "liblvm2cmd.so.2.02": { + "package": "lvm2-libs", + "version": "2.02" + }, + "libgtest.so": { + "package": "gtest", + "version": "0" + }, + "libgtest_main.so": { + "package": "gtest", + "version": "0" + }, + "libbonobo-2.so.0": { + "package": "libbonobo", + "version": "0.0.0" + }, + "libbonobo-activation.so.4": { + "package": "libbonobo", + "version": "4.0.0" + }, + "libXcomposite.so.1": { + "package": "libxcomposite", + "version": "1.0.0" + }, + "libprotobuf-c.so.1": { + "package": "protobuf-c", + "version": "1.0.0" + }, + "libcurl.so.4": { + "package": "libcurl", + "version": "4.5.0" + }, + "libntfs-3g.so.88": { + "package": "ntfs-3g-libs", + "version": "88.0.0" + }, + "liblua-5.3.so.0": { + "package": "lua5.3-libs", + "version": "0.0.0" + }, + "libneon.so.27": { + "package": "neon", + "version": "27.3.2" + }, + "libgpgmepp.so.6": { + "package": "gpgmepp", + "version": "6.8.0" + }, + "libruby.so.2.5": { + "package": "ruby-libs", + "version": "2.5.5" + }, + "libatomic_ops.so.1": { + "package": "libatomic_ops", + "version": "1.1.1" + }, + "libatomic_ops_gpl.so.1": { + "package": "libatomic_ops", + "version": "1.1.2" + }, + "libncftp.so.3": { + "package": "ncftp", + "version": "3" + }, + "libusb-0.1.so.4": { + "package": "libusb-compat", + "version": "4.4.4" + }, + "libnetfilter_log.so.1": { + "package": "libnetfilter_log", + "version": "1.1.0" + }, + "libnetfilter_log_libipulog.so.1": { + "package": "libnetfilter_log", + "version": "1.0.0" + }, + "rlm_pam.so": { + "package": "freeradius-pam", + "version": "0" + }, + "libCEGUIBase-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUIExpatParser-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUIFalagardWRBase-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUILuaScriptModule-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUIOpenGLRenderer-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUITGAImageCodec-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUITinyXMLParser-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "libCEGUItoluapp-0.6.2.so": { + "package": "cegui06", + "version": "0" + }, + "postfix-pcre.so": { + "package": "postfix-pcre", + "version": "0" + }, + "liblz4.so.1": { + "package": "lz4-libs", + "version": "1.8.3" + }, + "libasr.so.0": { + "package": "libasr", + "version": "0.0.2" + }, + "libsexy.so.2": { + "package": "libsexy", + "version": "2.0.4" + }, + "libboost_prg_exec_monitor-mt.so.1.67.0": { + "package": "boost-prg_exec_monitor", + "version": "1.67.0" + }, + "libboost_prg_exec_monitor.so.1.67.0": { + "package": "boost-prg_exec_monitor", + "version": "1.67.0" + }, + "libharfbuzz-gobject.so.0": { + "package": "harfbuzz", + "version": "0.20200.0" + }, + "libharfbuzz-subset.so.0": { + "package": "harfbuzz", + "version": "0.20200.0" + }, + "libharfbuzz.so.0": { + "package": "harfbuzz", + "version": "0.20200.0" + }, + "postfix-pgsql.so": { + "package": "postfix-pgsql", + "version": "0" + }, + "libclutter-1.0.so.0": { + "package": "clutter", + "version": "0.2600.2" + }, + "libfreetype.so.6": { + "package": "freetype", + "version": "6.16.1" + }, + "libcanberra-gtk.so.0": { + "package": "libcanberra-gtk2", + "version": "0.1.9" + }, + "librevenge-0.0.so.0": { + "package": "librevenge", + "version": "0.0.4" + }, + "librevenge-generators-0.0.so.0": { + "package": "librevenge", + "version": "0.0.4" + }, + "librevenge-stream-0.0.so.0": { + "package": "librevenge", + "version": "0.0.4" + }, + "libkamailio_ims.so.0": { + "package": "kamailio", + "version": "0.1" + }, + "libprint.so.1": { + "package": "kamailio", + "version": "1.2" + }, + "libsrdb1.so.1": { + "package": "kamailio", + "version": "1.0" + }, + "libsrdb2.so.1": { + "package": "kamailio", + "version": "1.0" + }, + "libsrutils.so.1": { + "package": "kamailio", + "version": "1.0" + }, + "libtrie.so.1": { + "package": "kamailio", + "version": "1.0" + }, + "libXtst.so.6": { + "package": "libxtst", + "version": "6.1.0" + }, + "libmagic.so.1": { + "package": "libmagic", + "version": "1.0.0" + }, + "libdriver_mysql.so": { + "package": "dovecot-mysql", + "version": "0" + }, + "libdv.so.4": { + "package": "libdv", + "version": "4.0.3" + }, + "libnet.so.1": { + "package": "libnet", + "version": "1.7.0" + }, + "libpyldb-util.cpython-36m-x86-64-linux-gnu.so.1": { + "package": "py3-ldb", + "version": "1.3.8" + }, + "libpoppler-glib.so.8": { + "package": "poppler-glib", + "version": "8.9.0" + }, + "libnfs.so.12": { + "package": "libnfs", + "version": "12.0.0" + }, + "libgnokii.so.7": { + "package": "gnokii-libs", + "version": "7.0.0" + }, + "rlm_eap.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_fast.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_gtc.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_leap.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_md5.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_mschapv2.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_peap.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_pwd.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_sim.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_tls.so": { + "package": "freeradius-eap", + "version": "0" + }, + "rlm_eap_ttls.so": { + "package": "freeradius-eap", + "version": "0" + }, + "libpopt.so.0": { + "package": "popt", + "version": "0.0.0" + }, + "libfontconfig.so.1": { + "package": "fontconfig", + "version": "1.12.0" + }, + "libsensors.so.4": { + "package": "lm_sensors", + "version": "4.4.0" + }, + "libXmu.so.6": { + "package": "libxmu", + "version": "6.2.0" + }, + "libXmuu.so.1": { + "package": "libxmu", + "version": "1.0.0" + }, + "libXxf86misc.so.1": { + "package": "libxxf86misc", + "version": "1.1.0" + }, + "libicudata.so.62": { + "package": "icu-libs", + "version": "62.1" + }, + "libicui18n.so.62": { + "package": "icu-libs", + "version": "62.1" + }, + "libicuio.so.62": { + "package": "icu-libs", + "version": "62.1" + }, + "libicutest.so.62": { + "package": "icu-libs", + "version": "62.1" + }, + "libicutu.so.62": { + "package": "icu-libs", + "version": "62.1" + }, + "libicuuc.so.62": { + "package": "icu-libs", + "version": "62.1" + }, + "libechonest.so.2.3": { + "package": "libechonest", + "version": "2.3.1" + }, + "libavfs.so.0": { + "package": "avfs", + "version": "0.0.2" + }, + "libdconf.so.1": { + "package": "dconf", + "version": "1.0.0" + }, + "libcc1.so.0": { + "package": "gcc", + "version": "0.0.0" + }, + "libitm.so.1": { + "package": "gcc", + "version": "1.0.0" + }, + "libdlz-bind9-for-torture-samba4.so": { + "package": "samba-test", + "version": "0" + }, + "libtorture-samba4.so": { + "package": "samba-test", + "version": "0" + }, + "libmtdev.so.1": { + "package": "mtdev", + "version": "1.0.0" + }, + "libbfd-2.31.1.so": { + "package": "binutils", + "version": "0" + }, + "libopcodes-2.31.1.so": { + "package": "binutils", + "version": "0" + }, + "libcdda_interface.so.0": { + "package": "cdparanoia-libs", + "version": "0.10.2" + }, + "libcdda_paranoia.so.0": { + "package": "cdparanoia-libs", + "version": "0.10.2" + }, + "libaio.so.1": { + "package": "libaio", + "version": "1.0.1" + }, + "libmaa.so.3": { + "package": "libmaa", + "version": "3.0.0" + }, + "libqjson.so.0": { + "package": "qjson", + "version": "0.9.0" + }, + "libXss.so.1": { + "package": "libxscrnsaver", + "version": "1.0.0" + }, + "libgvplugin_gdk.so.6": { + "package": "graphviz-gtk", + "version": "6.0.0" + }, + "libgvplugin_gtk.so.6": { + "package": "graphviz-gtk", + "version": "6.0.0" + }, + "libgvplugin_rsvg.so.6": { + "package": "graphviz-gtk", + "version": "6.0.0" + }, + "libpango-1.0.so.0": { + "package": "pango", + "version": "0.4200.4" + }, + "libpangocairo-1.0.so.0": { + "package": "pango", + "version": "0.4200.4" + }, + "libpangoft2-1.0.so.0": { + "package": "pango", + "version": "0.4200.4" + }, + "libpangoxft-1.0.so.0": { + "package": "pango", + "version": "0.4200.4" + }, + "libcairomm-1.0.so.1": { + "package": "cairomm", + "version": "1.4.0" + }, + "libexpect5.45.4.so": { + "package": "expect", + "version": "0" + }, + "libpixman-1.so.0": { + "package": "pixman", + "version": "0.34.0" + }, + "libfarstream-0.1.so.0": { + "package": "farstream0.1", + "version": "0.0.1" + }, + "libcairo-gobject.so.2": { + "package": "cairo-gobject", + "version": "2.11600.0" + }, + "libbaccats-mysql-9.4.1.so": { + "package": "bacula-mysql", + "version": "0" + }, + "liblzma.so.5": { + "package": "xz-libs", + "version": "5.2.4" + }, + "libpytalloc-util.cpython-36m-x86-64-linux-gnu.so.2": { + "package": "py3-talloc", + "version": "2.1.14" + }, + "libICE.so.6": { + "package": "libice", + "version": "6.3.0" + }, + "libiec61883.so.0": { + "package": "libiec61883", + "version": "0.1.1" + }, + "libproxychains4.so": { + "package": "proxychains-ng", + "version": "0" + }, + "libcrystalhd.so.3": { + "package": "libcrystalhd", + "version": "3.6" + }, + "libaugeas.so.0": { + "package": "augeas-libs", + "version": "0.24.1" + }, + "libfa.so.1": { + "package": "augeas-libs", + "version": "1.5.2" + }, + "libacl.so.1": { + "package": "libacl", + "version": "1.1.0" + }, + "libnetsnmp.so.35": { + "package": "net-snmp-libs", + "version": "35.0.0" + }, + "libtk8.6.so": { + "package": "tk", + "version": "0" + }, + "libgraphite2.so.3": { + "package": "graphite2", + "version": "3.2.1" + }, + "openvpn-auth-ldap.so": { + "package": "openvpn-auth-ldap", + "version": "0" + }, + "libsmartcols.so.1": { + "package": "libsmartcols", + "version": "1.1.0" + }, + "libgmp.so.10": { + "package": "gmp", + "version": "10.3.2" + }, + "libebml.so.4": { + "package": "libebml", + "version": "4.0.0" + }, + "libdbus-1.so.3": { + "package": "dbus-libs", + "version": "3.14.14" + }, + "libasyncns.so.0": { + "package": "libasyncns", + "version": "0.3.1" + }, + "libmpfr.so.4": { + "package": "mpfr3", + "version": "4.1.5" + }, + "libpng16.so.16": { + "package": "libpng", + "version": "16.37.0" + }, + "libilbc.so.0": { + "package": "ilbc", + "version": "0.0.2" + }, + "libivykis.so.0": { + "package": "ivykis", + "version": "0.5.5" + }, + "libnetfilter_conntrack.so.3": { + "package": "libnetfilter_conntrack", + "version": "3.6.0" + }, + "libpangox-1.0.so.0": { + "package": "pangox-compat", + "version": "0.0.0" + }, + "libmount.so.1": { + "package": "libmount", + "version": "1.1.0" + }, + "libcanberra-gtk3.so.0": { + "package": "libcanberra-gtk3", + "version": "0.1.9" + }, + "libavahi-ui-gtk3.so.0": { + "package": "avahi-ui-gtk3", + "version": "0.1.4" + }, + "libva-drm.so.2": { + "package": "libva", + "version": "2.200.0" + }, + "libva-x11.so.2": { + "package": "libva", + "version": "2.200.0" + }, + "libva.so.2": { + "package": "libva", + "version": "2.200.0" + }, + "libboost_contract-mt.so.1.67.0": { + "package": "boost-contract", + "version": "1.67.0" + }, + "libboost_contract.so.1.67.0": { + "package": "boost-contract", + "version": "1.67.0" + }, + "libfam.so.0": { + "package": "gamin", + "version": "0.0.0" + }, + "libgamin-1.so.0": { + "package": "gamin", + "version": "0.1.10" + }, + "libxatracker.so.2": { + "package": "mesa-xatracker", + "version": "2.3.0" + }, + "libnspr4.so": { + "package": "nspr", + "version": "0" + }, + "libplc4.so": { + "package": "nspr", + "version": "0" + }, + "libplds4.so": { + "package": "nspr", + "version": "0" + }, + "libform.so.5": { + "package": "ncurses5-libs", + "version": "5.9" + }, + "libmenu.so.5": { + "package": "ncurses5-libs", + "version": "5.9" + }, + "libncurses.so.5": { + "package": "ncurses5-libs", + "version": "5.9" + }, + "libpanel.so.5": { + "package": "ncurses5-libs", + "version": "5.9" + }, + "libboost_locale-mt.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_log-mt.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_log.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_log_setup-mt.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_log_setup.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_stacktrace_basic-mt.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_stacktrace_basic.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_stacktrace_noop-mt.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_stacktrace_noop.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_timer-mt.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_timer.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_type_erasure-mt.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_type_erasure.so.1.67.0": { + "package": "boost", + "version": "1.67.0" + }, + "libboost_container-mt.so.1.67.0": { + "package": "boost-container", + "version": "1.67.0" + }, + "libboost_container.so.1.67.0": { + "package": "boost-container", + "version": "1.67.0" + }, + "libldb-cmdline.so": { + "package": "ldb-tools", + "version": "0" + }, + "libgnomevfs-2.so.0": { + "package": "gnome-vfs", + "version": "0.2400.4" + }, + "libxmlrpc_client.so.3": { + "package": "xmlrpc-c-client", + "version": "3.39" + }, + "libao.so.4": { + "package": "libao", + "version": "4.1.0" + }, + "libs6.so.2.7": { + "package": "s6", + "version": "2.7.2.0" + }, + "lib++dfb-1.7.so.7": { + "package": "directfb", + "version": "7.0.0" + }, + "libdirect-1.7.so.7": { + "package": "directfb", + "version": "7.0.0" + }, + "libdirectfb-1.7.so.7": { + "package": "directfb", + "version": "7.0.0" + }, + "libfusion-1.7.so.7": { + "package": "directfb", + "version": "7.0.0" + }, + "libstartup-notification-1.so.0": { + "package": "startup-notification", + "version": "0.0.0" + }, + "libuv.so.1": { + "package": "libuv", + "version": "1.0.0" + }, + "libcdio.so.16": { + "package": "libcdio", + "version": "16.0.0" + }, + "libiso9660.so.10": { + "package": "libcdio", + "version": "10.0.0" + }, + "libudf.so.0": { + "package": "libcdio", + "version": "0.0.0" + }, + "libpgm-5.2.so.0": { + "package": "openpgm", + "version": "0.0.122" + }, + "librtmp.so.1": { + "package": "librtmp", + "version": "1" + }, + "libbtrfs.so.0": { + "package": "btrfs-progs-libs", + "version": "0.1" + }, + "libbtrfsutil.so.1": { + "package": "btrfs-progs-libs", + "version": "1.1.0" + }, + "libid3tag.so.0": { + "package": "libid3tag", + "version": "0.3.0" + }, + "libbsd.so.0": { + "package": "libbsd", + "version": "0.8.6" + }, + "libhashkit.so.2": { + "package": "libmemcached-libs", + "version": "2.0.0" + }, + "libmemcached.so.11": { + "package": "libmemcached-libs", + "version": "11.0.0" + }, + "libmemcachedprotocol.so.0": { + "package": "libmemcached-libs", + "version": "0.0.0" + }, + "libmemcachedutil.so.2": { + "package": "libmemcached-libs", + "version": "2.0.0" + }, + "libavahi-core.so.7": { + "package": "avahi", + "version": "7.0.2" + }, + "libgd.so.3": { + "package": "libgd", + "version": "3.0.5" + }, + "libunwind-coredump.so.0": { + "package": "libunwind", + "version": "0.0.0" + }, + "libunwind-ptrace.so.0": { + "package": "libunwind", + "version": "0.0.0" + }, + "libunwind-setjmp.so.0": { + "package": "libunwind", + "version": "0.0.0" + }, + "libunwind-x86_64.so.8": { + "package": "libunwind", + "version": "8.0.1" + }, + "libunwind.so.8": { + "package": "libunwind", + "version": "8.0.1" + }, + "libx264.so.152": { + "package": "x264-libs", + "version": "152" + }, + "librelp.so.0": { + "package": "librelp", + "version": "0.4.0" + }, + "libdc1394.so.22": { + "package": "libdc1394", + "version": "22.2.1" + }, + "libart_lgpl_2.so.2": { + "package": "libart-lgpl", + "version": "2.3.21" + }, + "libxcb-image.so.0": { + "package": "xcb-util-image", + "version": "0.0.0" + }, + "libgudev-1.0.so.0": { + "package": "libgudev", + "version": "0.2.0" + }, + "libwx_gtk2u_adv-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_aui-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_core-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_html-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_qa-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_richtext-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libwx_gtk2u_xrc-2.8.so.0": { + "package": "wxgtk2.8", + "version": "0.8.0" + }, + "libfreeradius-client.so.2": { + "package": "freeradius-client", + "version": "2.0.0" + }, + "libgcab-1.0.so.0": { + "package": "libgcab", + "version": "0.0.0" + }, + "libpkgconf.so.3": { + "package": "pkgconf", + "version": "3.0.0" + }, + "libcrypto.so.1.1": { + "package": "libcrypto1.1", + "version": "1.1" + }, + "libsodium.so.23": { + "package": "libsodium", + "version": "23.1.0" + }, + "lib21_fts_lucene_plugin.so": { + "package": "dovecot-fts-lucene", + "version": "0" + }, + "libmaxminddb.so.0": { + "package": "libmaxminddb", + "version": "0.0.7" + }, + "libmosquitto.so.1": { + "package": "mosquitto-libs", + "version": "1" + }, + "libgdbm.so.4": { + "package": "gdbm", + "version": "4.0.0" + }, + "libgdbm_compat.so.4": { + "package": "gdbm", + "version": "4.0.0" + }, + "libprocps.so.7": { + "package": "libproc", + "version": "7.1.0" + }, + "libldns.so.2": { + "package": "ldns", + "version": "2.0.0" + }, + "libclamav.so.7": { + "package": "clamav-libs", + "version": "7.1.2" + }, + "libclammspack.so.0": { + "package": "clamav-libs", + "version": "0.1.0" + }, + "libclamunrar.so.7": { + "package": "clamav-libs", + "version": "7.1.2" + }, + "libclamunrar_iface.so.7": { + "package": "clamav-libs", + "version": "7.1.2" + }, + "rlm_sql_mysql.so": { + "package": "freeradius-mysql", + "version": "0" + }, + "libgdkmm-2.4.so.1": { + "package": "gtkmm", + "version": "1.1.0" + }, + "libgtkmm-2.4.so.1": { + "package": "gtkmm", + "version": "1.1.0" + }, + "libperditiondb_gdbm.so.0": { + "package": "perdition", + "version": "0.0.0" + }, + "libtty.so.0": { + "package": "termrec", + "version": "0.0.0" + }, + "libcmocka.so.0": { + "package": "cmocka", + "version": "0.4.1" + }, + "libguess.so.1": { + "package": "libguess", + "version": "1.0.0" + }, + "libdvbpsi.so.10": { + "package": "libdvbpsi", + "version": "10.0.0" + }, + "libjson-c.so.4": { + "package": "json-c", + "version": "4.0.0" + }, + "libtinyxml2.so.7": { + "package": "tinyxml2", + "version": "7.0.1" + }, + "libpcap.so.1": { + "package": "libpcap", + "version": "1.9.0" + }, + "libnetflowPlugin-5.0.1.so": { + "package": "ntop", + "version": "0" + }, + "libntop-5.0.1.so": { + "package": "ntop", + "version": "0" + }, + "libntopreport-5.0.1.so": { + "package": "ntop", + "version": "0" + }, + "librrdPlugin-5.0.1.so": { + "package": "ntop", + "version": "0" + }, + "libsflowPlugin-5.0.1.so": { + "package": "ntop", + "version": "0" + }, + "libcollectdclient.so.1": { + "package": "collectd-libs", + "version": "1.1.0" + }, + "libpri.so.1.4": { + "package": "libpri", + "version": "1.4" + }, + "libxvidcore.so.4": { + "package": "xvidcore", + "version": "4.3" + }, + "libogrove.so.0": { + "package": "openjade-libs", + "version": "0.0.1" + }, + "libospgrove.so.0": { + "package": "openjade-libs", + "version": "0.0.1" + }, + "libostyle.so.0": { + "package": "openjade-libs", + "version": "0.0.1" + }, + "libcppunit-1.14.so.0": { + "package": "cppunit", + "version": "0.0.0" + }, + "libgnutls.so.30": { + "package": "gnutls", + "version": "30.23.2" + }, + "libnfdump-1.6.17.so": { + "package": "nfdump", + "version": "0" + }, + "libnl.so.1": { + "package": "libnl", + "version": "1.1.4" + }, + "libgs.so.9": { + "package": "ghostscript", + "version": "9.26" + }, + "libijs-0.35.so": { + "package": "ghostscript", + "version": "0" + }, + "libassuan.so.0": { + "package": "libassuan", + "version": "0.8.1" + }, + "libgck-1.so.0": { + "package": "gcr", + "version": "0.0.0" + }, + "libgcr-base-3.so.1": { + "package": "gcr", + "version": "1.0.0" + }, + "libgcr-ui-3.so.1": { + "package": "gcr", + "version": "1.0.0" + }, + "libasteriskssl.so.1": { + "package": "asterisk", + "version": "1" + }, + "libisl.so.15": { + "package": "isl", + "version": "15.3.0" + }, + "libOpenIPMI.so.0": { + "package": "openipmi-libs", + "version": "0.0.5" + }, + "libOpenIPMIcmdlang.so.0": { + "package": "openipmi-libs", + "version": "0.0.5" + }, + "libOpenIPMIglib.so.0": { + "package": "openipmi-libs", + "version": "0.0.1" + }, + "libOpenIPMIposix.so.0": { + "package": "openipmi-libs", + "version": "0.0.1" + }, + "libOpenIPMIpthread.so.0": { + "package": "openipmi-libs", + "version": "0.0.1" + }, + "libOpenIPMIui.so.1": { + "package": "openipmi-libs", + "version": "1.0.1" + }, + "libOpenIPMIutils.so.0": { + "package": "openipmi-libs", + "version": "0.0.1" + }, + "libatf-c++.so.2": { + "package": "atf", + "version": "2.0.0" + }, + "libatf-c.so.1": { + "package": "atf", + "version": "1.0.0" + }, + "libbaccats-sqlite3-9.4.1.so": { + "package": "bacula-sqlite", + "version": "0" + }, + "libzmq.so.5": { + "package": "libzmq", + "version": "5.2.1" + }, + "libiperf.so.0": { + "package": "iperf3", + "version": "0.0.0" + }, + "libnfsidmap.so.1": { + "package": "libnfsidmap", + "version": "1.0.0" + }, + "libmpg123.so.0": { + "package": "mpg123", + "version": "0.44.8" + }, + "libout123.so.0": { + "package": "mpg123", + "version": "0.2.2" + }, + "libipset.so.13": { + "package": "ipset", + "version": "13.1.0" + }, + "libFLAC++.so.6": { + "package": "flac", + "version": "6.3.0" + }, + "libFLAC.so.8": { + "package": "flac", + "version": "8.3.0" + }, + "liblcms.so.1": { + "package": "liblcms", + "version": "1.0.19" + }, + "libelf.so.0": { + "package": "libelf", + "version": "0" + }, + "libXfont2.so.2": { + "package": "libxfont2", + "version": "2.0.0" + }, + "libboost_math_c99-mt.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_c99.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_c99f-mt.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_c99f.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_c99l-mt.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_c99l.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_tr1-mt.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_tr1.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_tr1f-mt.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_tr1f.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_tr1l-mt.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libboost_math_tr1l.so.1.67.0": { + "package": "boost-math", + "version": "1.67.0" + }, + "libaprutil-1.so.0": { + "package": "apr-util", + "version": "0.6.1" + }, + "libkeyutils.so.1": { + "package": "keyutils-libs", + "version": "1.8" + }, + "liboping.so.0": { + "package": "liboping", + "version": "0.3.0" + }, + "libucontext.so.0": { + "package": "libucontext", + "version": "0" + }, + "libgcc_s.so.1": { + "package": "libgcc", + "version": "1" + }, + "libxattr-tdb-samba4.so": { + "package": "samba-server", + "version": "0" + }, + "libboost_atomic-mt.so.1.67.0": { + "package": "boost-atomic", + "version": "1.67.0" + }, + "libpcrecpp.so.0": { + "package": "libpcrecpp", + "version": "0.0.1" + }, + "libverto.so.1": { + "package": "libverto", + "version": "1.0.0" + }, + "libXrender.so.1": { + "package": "libxrender", + "version": "1.3.0" + }, + "libIDL-2.so.0": { + "package": "libidl", + "version": "0.0.0" + }, + "libsnappy.so.1": { + "package": "snappy", + "version": "1.1.7" + }, + "libxmlrpc_abyss.so.3": { + "package": "xmlrpc-c-abyss", + "version": "3.39" + }, + "libxmlrpc_server_abyss.so.3": { + "package": "xmlrpc-c-abyss", + "version": "3.39" + }, + "libboost_thread-mt.so.1.67.0": { + "package": "boost-thread", + "version": "1.67.0" + }, + "libtevent.so.0": { + "package": "tevent", + "version": "0.9.37" + }, + "libreadline.so.7": { + "package": "readline", + "version": "7.0" + }, + "libopus.so.0": { + "package": "opus", + "version": "0.7.0" + }, + "libtls.so.17": { + "package": "libressl2.7-libtls", + "version": "17.0.1" + }, + "libunique-1.0.so.0": { + "package": "libunique", + "version": "0.100.6" + }, + "libstdc++.so.6": { + "package": "libstdc++", + "version": "6.0.25" + }, + "libSDL_image-1.2.so.0": { + "package": "sdl_image", + "version": "0.8.4" + }, + "liblastfm.so.1": { + "package": "liblastfm", + "version": "1.0.9" + }, + "liblastfm_fingerprint.so.1": { + "package": "liblastfm", + "version": "1.0.9" + }, + "libburn.so.4": { + "package": "libburn", + "version": "4.103.0" + }, + "libfprint.so.0": { + "package": "libfprint", + "version": "0.0.0" + }, + "libwebsockets.so.14": { + "package": "libwebsockets", + "version": "14" + }, + "libtalloc.so.2": { + "package": "talloc", + "version": "2.1.14" + }, + "libmcpp.so.0": { + "package": "mcpp-libs", + "version": "0.3.0" + }, + "libtumbler-1.so.0": { + "package": "tumbler", + "version": "0.0.0" + }, + "libhogweed.so.4": { + "package": "nettle", + "version": "4.5" + }, + "libnettle.so.6": { + "package": "nettle", + "version": "6.5" + }, + "libexecinfo.so.1": { + "package": "libexecinfo", + "version": "1" + }, + "libgssapi_krb5.so.2": { + "package": "krb5-libs", + "version": "2.2" + }, + "libgssrpc.so.4": { + "package": "krb5-libs", + "version": "4.2" + }, + "libk5crypto.so.3": { + "package": "krb5-libs", + "version": "3.1" + }, + "libkadm5clnt_mit.so.11": { + "package": "krb5-libs", + "version": "11.0" + }, + "libkadm5srv_mit.so.11": { + "package": "krb5-libs", + "version": "11.0" + }, + "libkdb5.so.8": { + "package": "krb5-libs", + "version": "8.0" + }, + "libkrad.so.0": { + "package": "krb5-libs", + "version": "0.0" + }, + "libkrb5.so.3": { + "package": "krb5-libs", + "version": "3.3" + }, + "libkrb5support.so.0": { + "package": "krb5-libs", + "version": "0.1" + }, + "libMESSAGING-samba4.so": { + "package": "samba-server-libs", + "version": "0" + }, + "libdcerpc-samba4.so": { + "package": "samba-server-libs", + "version": "0" + }, + "libidmap-samba4.so": { + "package": "samba-server-libs", + "version": "0" + }, + "libnon-posix-acls-samba4.so": { + "package": "samba-server-libs", + "version": "0" + }, + "libnss-info-samba4.so": { + "package": "samba-server-libs", + "version": "0" + }, + "libvorbis.so.0": { + "package": "libvorbis", + "version": "0.4.8" + }, + "libvorbisenc.so.2": { + "package": "libvorbis", + "version": "2.0.11" + }, + "libvorbisfile.so.3": { + "package": "libvorbis", + "version": "3.3.7" + }, + "libboost_regex-mt.so.1.67.0": { + "package": "boost-regex", + "version": "1.67.0" + }, + "libboost_regex.so.1.67.0": { + "package": "boost-regex", + "version": "1.67.0" + }, + "libnotify.so.4": { + "package": "libnotify", + "version": "4.0.0" + }, + "libusb-1.0.so.0": { + "package": "libusb", + "version": "0.1.0" + }, + "libpyglib-2.0-python.so.0": { + "package": "py-gobject", + "version": "0.0.0" + }, + "libslang.so.2": { + "package": "slang", + "version": "2.3.2" + }, + "libCHARSET3-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libaddns-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libasn1util-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libauthkrb5-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcli-cldap-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcli-ldap-common-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcli-nbt-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcli-smb-common-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcliauth-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcmocka-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libcommon-auth-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libdbwrap-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libflag-mapping-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libgenrand-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libgensec-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libgse-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libinterfaces-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libiov-buf-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libkrb5samba-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libldbsamba-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libmessages-dgm-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libmessages-util-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libmsghdr-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libndr-krb5pac.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libndr-nbt.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libndr-samba-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libndr-standard.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libndr.so.0": { + "package": "samba-libs", + "version": "0.1.0" + }, + "libpopt-samba3-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-cluster-support-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-credentials.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libsamba-debug-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-errors.so.1": { + "package": "samba-libs", + "version": "1" + }, + "libsamba-hostconfig.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libsamba-modules-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-security-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-sockets-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamba-util.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libsamba3-util-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamdb-common-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsamdb.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libsecrets3-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libserver-id-db-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libserver-role-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsmb-transport-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsmbconf.so.0": { + "package": "samba-libs", + "version": "0" + }, + "libsmbd-shim-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsocket-blocking-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libsys-rw-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libtalloc-report-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libtdb-wrap-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libtevent-util.so.0": { + "package": "samba-libs", + "version": "0.0.1" + }, + "libtime-basic-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libutil-cmdline-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libutil-reg-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libutil-setid-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libutil-tdb-samba4.so": { + "package": "samba-libs", + "version": "0" + }, + "libxkbui.so.1": { + "package": "libxkbui", + "version": "1.0.0" + }, + "libfontenc.so.1": { + "package": "libfontenc", + "version": "1.0.0" + }, + "libnvpair.so.1": { + "package": "zfs-libs", + "version": "1.0.1" + }, + "libuutil.so.1": { + "package": "zfs-libs", + "version": "1.0.1" + }, + "libzfs.so.2": { + "package": "zfs-libs", + "version": "2.0.0" + }, + "libzfs_core.so.1": { + "package": "zfs-libs", + "version": "1.0.0" + }, + "libzpool.so.2": { + "package": "zfs-libs", + "version": "2.0.0" + }, + "libs6net.so.2.3": { + "package": "s6-networking", + "version": "2.3.0.3" + }, + "libsbearssl.so.2.3": { + "package": "s6-networking", + "version": "2.3.0.3" + }, + "libdvdcss.so.2": { + "package": "libdvdcss", + "version": "2.2.0" + }, + "libgnome-2.so.0": { + "package": "libgnome", + "version": "0.3200.1" + }, + "libgstapp-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstaudio-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstcdda-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstfft-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstinterfaces-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstnetbuffer-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstpbutils-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstriff-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstrtp-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstrtsp-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstsdp-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgsttag-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libgstvideo-0.10.so.0": { + "package": "gst-plugins-base0.10", + "version": "0.25.0" + }, + "libXinerama.so.1": { + "package": "libxinerama", + "version": "1.0.0" + }, + "libglamor.so.0": { + "package": "glamor-egl", + "version": "0.0.0" + }, + "libGLESv1_CM.so.1": { + "package": "mesa-gles", + "version": "1.1.0" + }, + "libGLESv2.so.2": { + "package": "mesa-gles", + "version": "2.0.0" + }, + "libfcgi++.so.0": { + "package": "fcgi++", + "version": "0.0.0" + }, + "libgiblib.so.1": { + "package": "giblib", + "version": "1.0.6" + }, + "libnftables.so.0": { + "package": "nftables", + "version": "0.0.0" + }, + "libusbredirhost.so.1": { + "package": "usbredir", + "version": "1.0.0" + }, + "libusbredirparser.so.1": { + "package": "usbredir", + "version": "1.0.0" + } + }, + "package": { + "linux-firmware-any": { + "package": "linux-firmware-qcom", + "version": 0 + }, + "py-avahi": { + "package": "py2-avahi", + "version": "0.7-r1" + }, + "rsyslog-omhttp": { + "package": "rsyslog-http", + "version": "8.40.0-r3" + }, + "rsyslog-fmhttp": { + "package": "rsyslog-http", + "version": "8.40.0-r3" + }, + "drbd": { + "package": "drbd-utils", + "version": "9.7.1-r0" + }, + "bigreqsproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "compositeproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "damageproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "dri2proto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "dri3proto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "fixesproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "fontsproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "glproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "inputproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "kbproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "presentproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "printproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "randrproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "recordproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "renderproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "resourceproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "scrnsaverproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "videoproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "xcmiscproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "xextproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "xf86bigfontproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "xf86dgaproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "xf86driproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "xf86miscproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "xf86vidmodeproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "xineramaproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "xproto": { + "package": "xorgproto", + "version": "2018.4-r0" + }, + "py3-pip": { + "package": "python3", + "version": 0 + }, + "rsyslog-lmgssutil": { + "package": "rsyslog-gssapi", + "version": "8.40.0-r3" + }, + "rsyslog-imgssapi": { + "package": "rsyslog-gssapi", + "version": "8.40.0-r3" + }, + "rsyslog-omgssapi": { + "package": "rsyslog-gssapi", + "version": "8.40.0-r3" + }, + "perl-test-tester": { + "package": "perl-test-simple", + "version": 0 + }, + "lua-graphviz": { + "package": "lua5.3-graphviz", + "version": "2.40.1-r1" + }, + "lua-penlight-shared": { + "package": "lua-penlight", + "version": "1.5.4-r2" + }, + "lua5.1-penlight": { + "package": "lua-penlight", + "version": "1.5.4-r2" + }, + "lua5.2-penlight": { + "package": "lua-penlight", + "version": "1.5.4-r2" + }, + "lua5.3-penlight": { + "package": "lua-penlight", + "version": "1.5.4-r2" + }, + "freeradius3-krb5": { + "package": "freeradius-krb5", + "version": "3.0.17-r5" + }, + "lua5.1-json4": { + "package": "lua-json4", + "version": "1.0.0-r3" + }, + "lua5.2-json4": { + "package": "lua-json4", + "version": "1.0.0-r3" + }, + "lua5.3-json4": { + "package": "lua-json4", + "version": "1.0.0-r3" + }, + "freeradius3-postgresql": { + "package": "freeradius-postgresql", + "version": "3.0.17-r5" + }, + "lua-lustache-common": { + "package": "lua-lustache", + "version": "1.3.1-r2" + }, + "lua5.1-lustache": { + "package": "lua-lustache", + "version": "1.3.1-r2" + }, + "lua5.2-lustache": { + "package": "lua-lustache", + "version": "1.3.1-r2" + }, + "lua5.3-lustache": { + "package": "lua-lustache", + "version": "1.3.1-r2" + }, + "python-tests": { + "package": "python2-tests", + "version": "2.7.16-r1" + }, + "lua-doc": { + "package": "lua5.1-doc", + "version": 0 + }, + "collectd-libvirt": { + "package": "collectd-virt", + "version": 0 + }, + "python-dev": { + "package": "python2-dev", + "version": "2.7.16-r1" + }, + "freeradius3-python": { + "package": "freeradius-python", + "version": "3.0.17-r5" + }, + "py-talloc": { + "package": "py2-talloc", + "version": "2.1.14-r0" + }, + "bkeymaps": { + "package": "kbd-bkeymaps", + "version": 0 + }, + "llvm-static": { + "package": "llvm5-static", + "version": "5.0.2-r0" + }, + "py3-uritemplate.py": { + "package": "py3-uritemplate", + "version": 0 + }, + "/bin/sh": { + "package": "busybox", + "version": 0 + }, + "rsyslog-omrabbitmq": { + "package": "rsyslog-rabbitmq", + "version": "8.40.0-r3" + }, + "rsyslog-omelasticsearch": { + "package": "rsyslog-elasticsearch", + "version": "8.40.0-r3" + }, + "rsyslog-omlibdbi": { + "package": "rsyslog-libdbi", + "version": "8.40.0-r3" + }, + "rsyslog-omtesting": { + "package": "rsyslog-testing", + "version": "8.40.0-r3" + }, + "pyflakes": { + "package": "py2-pyflakes", + "version": "1.6.0-r3" + }, + "perl-time-date": { + "package": "perl-timedate", + "version": "2.30" + }, + "freeradius3-unixodbc": { + "package": "freeradius-unixodbc", + "version": "3.0.17-r5" + }, + "py-django-tables2-r1.21.2": { + "package": "py2-django-tables2", + "version": 0 + }, + "pllua": { + "package": "postgresql-pllua", + "version": "2.0-r0" + }, + "rsyslog-omuxsock": { + "package": "rsyslog-uxsock", + "version": "8.40.0-r3" + }, + "ncurses-widec-libs": { + "package": "ncurses-libs", + "version": "6.1_p20190105-r0" + }, + "perl-mail-tools": { + "package": "perl-mailtools", + "version": "2.19" + }, + "libgit2-libs": { + "package": "libgit2", + "version": 0 + }, + "udev": { + "package": "eudev", + "version": "176" + }, + "rsyslog-omsnmp": { + "package": "rsyslog-snmp", + "version": "8.40.0-r3" + }, + "py-attrs-tools": { + "package": "py2-attrs", + "version": 0 + }, + "py-pip": { + "package": "py2-pip", + "version": "18.1-r0" + }, + "nodejs-lts": { + "package": "nodejs", + "version": "10.14.2" + }, + "freeradius3-sqlite": { + "package": "freeradius-sqlite", + "version": "3.0.17-r5" + }, + "wayland-protocols-dev": { + "package": "wayland-protocols", + "version": 0 + }, + "lua": { + "package": "lua5.3", + "version": 0 + }, + "gnupg-doc": { + "package": "gnupg1-doc", + "version": "1.4.23-r0" + }, + "freeradius3": { + "package": "freeradius", + "version": "3.0.17-r5" + }, + "freeradius3-sql": { + "package": "freeradius-sql", + "version": "3.0.17-r5" + }, + "llvm-dev": { + "package": "llvm5-dev", + "version": "5.0.2-r0" + }, + "nodejs-lts-dev": { + "package": "nodejs-dev", + "version": "10.14.2" + }, + "lua5.1-stdlib-debug": { + "package": "lua-stdlib-debug", + "version": "1.0.1-r0" + }, + "lua5.2-stdlib-debug": { + "package": "lua-stdlib-debug", + "version": "1.0.1-r0" + }, + "lua5.3-stdlib-debug": { + "package": "lua-stdlib-debug", + "version": "1.0.1-r0" + }, + "rsyslog-lmcry_gcry": { + "package": "rsyslog-crypto", + "version": "8.40.0-r3" + }, + "freeradius3-ldap": { + "package": "freeradius-ldap", + "version": "3.0.17-r5" + }, + "llvm-libs": { + "package": "llvm5-libs", + "version": "5.0.2-r0" + }, + "py-singledispatch": { + "package": "py2-singledispatch", + "version": 0 + }, + "freeradius3-perl": { + "package": "freeradius-perl", + "version": "3.0.17-r5" + }, + "py-btrfs-progs": { + "package": "py3-btrfs-progs", + "version": "4.19.1-r0" + }, + "nginx-rtmp": { + "package": "nginx-mod-rtmp", + "version": 0 + }, + "nagios-plugins-clamdnagios-plugins-ftpnagios-plugins-imapnagios-plugins-jabbernagios-plugins-nntpnagios-plugins-nntpsnagios-plugins-popnagios-plugins-simapnagios-plugins-spopnagios-plugins-ssmtpnagios-plugins-udp": { + "package": "nagios-plugins-tcp", + "version": 0 + }, + "mysql-dev": { + "package": "mariadb-dev", + "version": "10.3.13-r1" + }, + "python-gdbm": { + "package": "py-gdbm", + "version": "2.7.16-r1" + }, + "consolekit": { + "package": "consolekit2", + "version": "1.2.1" + }, + "py2-setuptools": { + "package": "py-setuptools", + "version": "40.6.3-r0" + }, + "freeradius3-radclient": { + "package": "freeradius-radclient", + "version": "3.0.17-r5" + }, + "python": { + "package": "python2", + "version": "2.7.16-r1" + }, + "py-tevent": { + "package": "py2-tevent", + "version": "0.9.37-r1" + }, + "lua-feedparser-common": { + "package": "lua-feedparser", + "version": "0.71-r1" + }, + "lua5.1-feedparser": { + "package": "lua-feedparser", + "version": "0.71-r1" + }, + "lua5.2-feedparser": { + "package": "lua-feedparser", + "version": "0.71-r1" + }, + "lua5.3-feedparser": { + "package": "lua-feedparser", + "version": "0.71-r1" + }, + "lua-libs": { + "package": "lua5.1-libs", + "version": 0 + }, + "freeradius3-mssql": { + "package": "freeradius-mssql", + "version": "3.0.17-r5" + }, + "postgresql": { + "package": "postgresql-bdr", + "version": 0 + }, + "rsyslog-ompgsql": { + "package": "rsyslog-pgsql", + "version": "8.40.0-r3" + }, + "rsyslog-lmnsd_gtls": { + "package": "rsyslog-tls", + "version": "8.40.0-r3" + }, + "py-ldb": { + "package": "py2-ldb", + "version": "1.3.8-r0" + }, + "rsyslog-imrelp": { + "package": "rsyslog-relp", + "version": "8.40.0-r3" + }, + "rsyslog-omrelp": { + "package": "rsyslog-relp", + "version": "8.40.0-r3" + }, + "perl-encode-piconv": { + "package": "perl-encode-utils", + "version": 0 + }, + "flake8": { + "package": "py2-flake8", + "version": "3.4.1-r2" + }, + "py-pep8": { + "package": "py2-pep8", + "version": "1.7.1-r0" + }, + "quagga-nhrp": { + "package": "quagga", + "version": "1.2.4" + }, + "freeradius3-pam": { + "package": "freeradius-pam", + "version": "3.0.17-r5" + }, + "gnupg": { + "package": "gnupg1", + "version": "1.4.23-r0" + }, + "rsyslog-omhiredis": { + "package": "rsyslog-hiredis", + "version": "8.40.0-r3" + }, + "lua5.1-stdlib-normalize": { + "package": "lua-stdlib-normalize", + "version": "2.0.2-r0" + }, + "lua5.2-stdlib-normalize": { + "package": "lua-stdlib-normalize", + "version": "2.0.2-r0" + }, + "lua5.3-stdlib-normalize": { + "package": "lua-stdlib-normalize", + "version": "2.0.2-r0" + }, + "freeradius3-eap": { + "package": "freeradius-eap", + "version": "3.0.17-r5" + }, + "lua-dev": { + "package": "lua5.1-dev", + "version": 0 + }, + "py2-uritemplate.py": { + "package": "py2-uritemplate", + "version": 0 + }, + "llvm-test-utils": { + "package": "llvm5-test-utils", + "version": "5.0.2-r0" + }, + "lit": { + "package": "llvm5-test-utils", + "version": "0.6.0-r0" + }, + "lua5.1-gversion": { + "package": "lua-gversion", + "version": "0.2.0-r2" + }, + "lua5.2-gversion": { + "package": "lua-gversion", + "version": "0.2.0-r2" + }, + "lua5.3-gversion": { + "package": "lua-gversion", + "version": "0.2.0-r2" + }, + "py-tdb": { + "package": "py2-tdb", + "version": "1.3.16-r0" + }, + "rsyslog-omudpspoof": { + "package": "rsyslog-udpspoof", + "version": "8.40.0-r3" + }, + "pkgconfig": { + "package": "pkgconf", + "version": "1" + }, + "rsyslog-ommysql": { + "package": "rsyslog-mysql", + "version": "8.40.0-r3" + }, + "rsyslog-imczmq": { + "package": "rsyslog-zmq", + "version": "8.40.0-r3" + }, + "rsyslog-omczmq": { + "package": "rsyslog-zmq", + "version": "8.40.0-r3" + }, + "freeradius3-mysql": { + "package": "freeradius-mysql", + "version": "3.0.17-r5" + }, + "perl-net-ldap": { + "package": "perl-ldap", + "version": 0 + }, + "nginx-lua": { + "package": "nginx-mod-http-lua", + "version": 0 + }, + "nodejs-npm": { + "package": "npm", + "version": "10.14.2-r0" + }, + "nodejs-current-npm": { + "package": "npm", + "version": "10.14.2-r0" + }, + "lua5.1-inspect": { + "package": "lua-inspect", + "version": "3.1.1-r1" + }, + "lua5.2-inspect": { + "package": "lua-inspect", + "version": "3.1.1-r1" + }, + "lua5.3-inspect": { + "package": "lua-inspect", + "version": "3.1.1-r1" + }, + "py-graphviz": { + "package": "py2-gv", + "version": "2.40.1-r1" + }, + "llvm": { + "package": "llvm5", + "version": "5.0.2-r0" + }, + "py-libxslt": { + "package": "py2-libxslt", + "version": "1.1.33-r1" + }, + "newsbeuter": { + "package": "newsboat", + "version": "2.13-r0" + }, + "perl-yaml-xs": { + "package": "perl-yaml-libyaml", + "version": "0.72-r0" + }, + "py-backports.ssl_match_hostname": { + "package": "py2-backports.ssl_match_hostname", + "version": 0 + } + } + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go new file mode 100644 index 000000000000..e0c70cc34cd0 --- /dev/null +++ b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go @@ -0,0 +1,125 @@ +package dockerfile + +import ( + "bytes" + "context" + "fmt" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/image" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/mapfs" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +const analyzerVersion = 1 + +func init() { + analyzer.RegisterConfigAnalyzer(analyzer.TypeHistoryDockerfile, newHistoryAnalyzer) +} + +type historyAnalyzer struct { + scanner *misconf.Scanner +} + +func newHistoryAnalyzer(opts analyzer.ConfigAnalyzerOptions) (analyzer.ConfigAnalyzer, error) { + s, err := misconf.NewDockerfileScanner(opts.FilePatterns, opts.MisconfScannerOption) + if err != nil { + return nil, xerrors.Errorf("misconfiguration scanner error: %w", err) + } + return &historyAnalyzer{ + scanner: s, + }, nil +} + +func (a *historyAnalyzer) Analyze(ctx context.Context, input analyzer.ConfigAnalysisInput) (*analyzer. + ConfigAnalysisResult, error) { + if input.Config == nil { + return nil, nil + } + dockerfile := new(bytes.Buffer) + var userFound bool + baseLayerIndex := image.GuessBaseImageIndex(input.Config.History) + for i := baseLayerIndex + 1; i < len(input.Config.History); i++ { + h := input.Config.History[i] + var createdBy string + switch { + case strings.HasPrefix(h.CreatedBy, "/bin/sh -c #(nop)"): + // Instruction other than RUN + createdBy = strings.TrimPrefix(h.CreatedBy, "/bin/sh -c #(nop)") + case strings.HasPrefix(h.CreatedBy, "/bin/sh -c"): + // RUN instruction + createdBy = strings.ReplaceAll(h.CreatedBy, "/bin/sh -c", "RUN") + case strings.HasSuffix(h.CreatedBy, "# buildkit"): + // buildkit instructions + // COPY ./foo /foo # buildkit + // ADD ./foo.txt /foo.txt # buildkit + // RUN /bin/sh -c ls -hl /foo # buildkit + createdBy = strings.TrimSuffix(h.CreatedBy, "# buildkit") + if strings.HasPrefix(h.CreatedBy, "RUN /bin/sh -c") { + createdBy = strings.ReplaceAll(createdBy, "RUN /bin/sh -c", "RUN") + } + case strings.HasPrefix(h.CreatedBy, "USER"): + // USER instruction + createdBy = h.CreatedBy + userFound = true + case strings.HasPrefix(h.CreatedBy, "HEALTHCHECK"): + // HEALTHCHECK instruction + var interval, timeout, startPeriod, retries, command string + if input.Config.Config.Healthcheck.Interval != 0 { + interval = fmt.Sprintf("--interval=%s ", input.Config.Config.Healthcheck.Interval) + } + if input.Config.Config.Healthcheck.Timeout != 0 { + timeout = fmt.Sprintf("--timeout=%s ", input.Config.Config.Healthcheck.Timeout) + } + if input.Config.Config.Healthcheck.StartPeriod != 0 { + startPeriod = fmt.Sprintf("--startPeriod=%s ", input.Config.Config.Healthcheck.StartPeriod) + } + if input.Config.Config.Healthcheck.Retries != 0 { + retries = fmt.Sprintf("--retries=%d ", input.Config.Config.Healthcheck.Retries) + } + command = strings.Join(input.Config.Config.Healthcheck.Test, " ") + command = strings.ReplaceAll(command, "CMD-SHELL", "CMD") + createdBy = fmt.Sprintf("HEALTHCHECK %s%s%s%s%s", interval, timeout, startPeriod, retries, command) + } + dockerfile.WriteString(strings.TrimSpace(createdBy) + "\n") + } + + if !userFound && input.Config.Config.User != "" { + user := fmt.Sprintf("USER %s", input.Config.Config.User) + dockerfile.WriteString(user) + } + + fsys := mapfs.New() + if err := fsys.WriteVirtualFile("Dockerfile", dockerfile.Bytes(), 0600); err != nil { + return nil, xerrors.Errorf("mapfs write error: %w", err) + } + + misconfs, err := a.scanner.Scan(ctx, fsys) + if err != nil { + return nil, xerrors.Errorf("history scan error: %w", err) + } + // The result should be a single element as it passes one Dockerfile. + if len(misconfs) != 1 { + return nil, nil + } + + return &analyzer.ConfigAnalysisResult{ + Misconfiguration: &misconfs[0], + }, nil +} + +func (a *historyAnalyzer) Required(_ types.OS) bool { + return true +} + +func (a *historyAnalyzer) Type() analyzer.Type { + return analyzer.TypeHistoryDockerfile +} + +func (a *historyAnalyzer) Version() int { + return analyzerVersion +} diff --git a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go new file mode 100644 index 000000000000..c23174d76540 --- /dev/null +++ b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go @@ -0,0 +1,304 @@ +package dockerfile + +import ( + "context" + "testing" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_historyAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + input analyzer.ConfigAnalysisInput + want *analyzer.ConfigAnalysisResult + wantErr bool + }{ + { + name: "happy path no policy failure", + input: analyzer.ConfigAnalysisInput{ + Config: &v1.ConfigFile{ + Config: v1.Config{ + Healthcheck: &v1.HealthConfig{ + Test: []string{"CMD-SHELL", "curl --fail http://localhost:3000 || exit 1"}, + Interval: time.Second * 10, + Timeout: time.Second * 3, + }, + }, + History: []v1.History{ + { + // this is fine, see https://github.com/aquasecurity/trivy-policies/pull/60 for details + CreatedBy: "/bin/sh -c #(nop) ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /", + EmptyLayer: false, + }, + { + CreatedBy: `HEALTHCHECK &{["CMD-SHELL" "curl --fail http://localhost:3000 || exit 1"] "10s" "3s" "0s" '\x00'}`, + EmptyLayer: false, + }, + { + CreatedBy: `USER user`, + EmptyLayer: true, + }, + { + CreatedBy: `/bin/sh -c #(nop) CMD [\"/bin/sh\"]`, + EmptyLayer: true, + }, + }, + }, + }, + want: &analyzer.ConfigAnalysisResult{ + Misconfiguration: &types.Misconfiguration{ + FileType: "dockerfile", + FilePath: "Dockerfile", + }, + }, + }, + { + name: "happy path with policy failure", + input: analyzer.ConfigAnalysisInput{ + Config: &v1.ConfigFile{ + Config: v1.Config{ + Healthcheck: &v1.HealthConfig{ + Test: []string{"CMD-SHELL", "curl --fail http://localhost:3000 || exit 1"}, + Interval: time.Second * 10, + Timeout: time.Second * 3, + }, + }, + History: []v1.History{ + { + CreatedBy: "/bin/sh -c #(nop) ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 /", + EmptyLayer: false, + }, + { + CreatedBy: `HEALTHCHECK &{["CMD-SHELL" "curl --fail http://localhost:3000 || exit 1"] "10s" "3s" "0s" '\x00'}`, + EmptyLayer: false, + }, + { + CreatedBy: `USER user`, + EmptyLayer: true, + }, + { + CreatedBy: `/bin/sh -c #(nop) CMD [\"/bin/sh\"]`, + EmptyLayer: true, + }, + }, + }, + }, + want: &analyzer.ConfigAnalysisResult{ + Misconfiguration: &types.Misconfiguration{ + FileType: "dockerfile", + FilePath: "Dockerfile", + Failures: types.MisconfResults{ + types.MisconfResult{ + Namespace: "builtin.dockerfile.DS005", + Query: "data.builtin.dockerfile.DS005.deny", + Message: "Consider using 'COPY file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 /' command instead of 'ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 /'", + PolicyMetadata: types.PolicyMetadata{ + ID: "DS005", + AVDID: "AVD-DS-0005", + Type: "Dockerfile Security Check", + Title: "ADD instead of COPY", + Description: "You should use COPY instead of ADD unless you want to extract a tar file. Note that an ADD command will extract a tar file, which adds the risk of Zip-based vulnerabilities. Accordingly, it is advised to use a COPY command, which does not extract tar files.", + Severity: "LOW", + RecommendedActions: "Use COPY instead of ADD", + References: []string{"https://docs.docker.com/engine/reference/builder/#add"}, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + StartLine: 1, + EndLine: 1, + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 /", + IsCause: true, + Truncated: false, + Highlighted: "\x1b[38;5;64mADD\x1b[0m file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 /", + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with buildkit instructions", + input: analyzer.ConfigAnalysisInput{ + Config: &v1.ConfigFile{ + Config: v1.Config{ + Healthcheck: &v1.HealthConfig{ + Test: []string{"CMD-SHELL", "curl --fail http://localhost:3000 || exit 1"}, + Interval: time.Second * 10, + Timeout: time.Second * 3, + }, + User: "1002", + }, + History: []v1.History{ + { + CreatedBy: "/bin/sh -c #(nop) ADD file:289c2fac17119508ced527225d445747cd177111b4a0018a6b04948ecb3b5e29 in / ", + EmptyLayer: false, + }, + { + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + EmptyLayer: true, + }, + { + CreatedBy: "ADD ./foo.txt /foo.txt # buildkit", + EmptyLayer: false, + }, + { + CreatedBy: "COPY ./foo /foo # buildkit", + EmptyLayer: false, + }, + { + CreatedBy: "RUN /bin/sh -c ls -hl /foo # buildkit", + EmptyLayer: false, + }, + { + CreatedBy: `HEALTHCHECK &{["CMD-SHELL" "curl -sS 127.0.0.1 || exit 1"] "10s" "3s" "0s" '\x00'}`, + EmptyLayer: true, + }, + }, + }, + }, + want: &analyzer.ConfigAnalysisResult{ + Misconfiguration: &types.Misconfiguration{ + FileType: "dockerfile", + FilePath: "Dockerfile", + Failures: types.MisconfResults{ + types.MisconfResult{ + Namespace: "builtin.dockerfile.DS005", + Query: "data.builtin.dockerfile.DS005.deny", + Message: "Consider using 'COPY ./foo.txt /foo.txt' command instead of 'ADD ./foo.txt /foo.txt'", + PolicyMetadata: types.PolicyMetadata{ + ID: "DS005", + AVDID: "AVD-DS-0005", + Type: "Dockerfile Security Check", + Title: "ADD instead of COPY", + Description: "You should use COPY instead of ADD unless you want to extract a tar file. Note that an ADD command will extract a tar file, which adds the risk of Zip-based vulnerabilities. Accordingly, it is advised to use a COPY command, which does not extract tar files.", + Severity: "LOW", + RecommendedActions: "Use COPY instead of ADD", + References: []string{"https://docs.docker.com/engine/reference/builder/#add"}, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + StartLine: 1, + EndLine: 1, + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "ADD ./foo.txt /foo.txt", + IsCause: true, + Truncated: false, + Highlighted: "\x1b[38;5;64mADD\x1b[0m ./foo.txt /foo.txt", + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path. Base layer is found", + input: analyzer.ConfigAnalysisInput{ + Config: &v1.ConfigFile{ + Config: v1.Config{ + Healthcheck: &v1.HealthConfig{ + Test: []string{"CMD-SHELL", "curl --fail http://localhost:3000 || exit 1"}, + Interval: time.Second * 10, + Timeout: time.Second * 3, + }, + }, + History: []v1.History{ + { + CreatedBy: "/bin/sh -c #(nop) ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /", + EmptyLayer: false, + }, + { + CreatedBy: `/bin/sh -c #(nop) CMD [\"/bin/sh\"]`, + EmptyLayer: true, + }, + { + CreatedBy: `HEALTHCHECK &{["CMD-SHELL" "curl --fail http://localhost:3000 || exit 1"] "10s" "3s" "0s" '\x00'}`, + EmptyLayer: false, + }, + { + CreatedBy: `/bin/sh -c #(nop) CMD [\"/bin/sh\"]`, + EmptyLayer: true, + }, + }, + }, + }, + want: &analyzer.ConfigAnalysisResult{ + Misconfiguration: &types.Misconfiguration{ + FileType: "dockerfile", + FilePath: "Dockerfile", + Failures: types.MisconfResults{ + types.MisconfResult{ + Namespace: "builtin.dockerfile.DS002", + Query: "data.builtin.dockerfile.DS002.deny", + Message: "Specify at least 1 USER command in Dockerfile with non-root user as argument", + PolicyMetadata: types.PolicyMetadata{ + ID: "DS002", + AVDID: "AVD-DS-0002", + Type: "Dockerfile Security Check", + Title: "Image user should not be 'root'", + Description: "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", + Severity: "HIGH", + RecommendedActions: "Add 'USER ' line to the Dockerfile", + References: []string{ + "https://docs.docker." + + "com/develop/develop-images/dockerfile_best-practices/", + }, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + }, + }, + }, + }, + }, + }, + { + name: "nil config", + input: analyzer.ConfigAnalysisInput{ + Config: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newHistoryAnalyzer(analyzer.ConfigAnalyzerOptions{}) + require.NoError(t, err) + got, err := a.Analyze(context.Background(), tt.input) + if tt.wantErr { + assert.Error(t, err) + return + } + if got != nil && got.Misconfiguration != nil { + got.Misconfiguration.Successes = nil // Not compare successes in this test + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/imgconf/secret/secret.go b/pkg/fanal/analyzer/imgconf/secret/secret.go new file mode 100644 index 000000000000..3d153e5f1b1f --- /dev/null +++ b/pkg/fanal/analyzer/imgconf/secret/secret.go @@ -0,0 +1,74 @@ +package secret + +import ( + "context" + "encoding/json" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/secret" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +const analyzerVersion = 1 + +func init() { + analyzer.RegisterConfigAnalyzer(analyzer.TypeImageConfigSecret, newSecretAnalyzer) +} + +// secretAnalyzer detects secrets in container image config. +type secretAnalyzer struct { + scanner secret.Scanner +} + +func newSecretAnalyzer(opts analyzer.ConfigAnalyzerOptions) (analyzer.ConfigAnalyzer, error) { + configPath := opts.SecretScannerOption.ConfigPath + c, err := secret.ParseConfig(configPath) + if err != nil { + return nil, xerrors.Errorf("secret config error: %w", err) + } + scanner := secret.NewScanner(c) + + return &secretAnalyzer{ + scanner: scanner, + }, nil +} + +func (a *secretAnalyzer) Analyze(_ context.Context, input analyzer.ConfigAnalysisInput) (*analyzer. + ConfigAnalysisResult, error) { + if input.Config == nil { + return nil, nil + } + b, err := json.MarshalIndent(input.Config, " ", "") + if err != nil { + return nil, xerrors.Errorf("json marshal error: %w", err) + } + + result := a.scanner.Scan(secret.ScanArgs{ + FilePath: "config.json", + Content: b, + }) + + if len(result.Findings) == 0 { + log.Logger.Debug("No secrets found in container image config") + return nil, nil + } + + return &analyzer.ConfigAnalysisResult{ + Secret: &result, + }, nil +} + +func (a *secretAnalyzer) Required(_ types.OS) bool { + return true +} + +func (a *secretAnalyzer) Type() analyzer.Type { + return analyzer.TypeImageConfigSecret +} + +func (a *secretAnalyzer) Version() int { + return analyzerVersion +} diff --git a/pkg/fanal/analyzer/imgconf/secret/secret_test.go b/pkg/fanal/analyzer/imgconf/secret/secret_test.go new file mode 100644 index 000000000000..8c28fd9c91ce --- /dev/null +++ b/pkg/fanal/analyzer/imgconf/secret/secret_test.go @@ -0,0 +1,109 @@ +package secret + +import ( + "context" + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_secretAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + config *v1.ConfigFile + want *analyzer.ConfigAnalysisResult + wantErr bool + }{ + { + name: "happy path", + config: &v1.ConfigFile{ + Config: v1.Config{ + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "secret=ghp_eifae6eigh3aeSah1shahd6oi1tague6vaey", // dummy token + }, + }, + }, + want: &analyzer.ConfigAnalysisResult{ + Secret: &types.Secret{ + FilePath: "config.json", + Findings: []types.SecretFinding{ + { + RuleID: "github-pat", + Category: "GitHub", + Severity: "CRITICAL", + Title: "GitHub Personal Access Token", + StartLine: 12, + EndLine: 12, + Code: types.Code{ + Lines: []types.Line{ + { + Number: 10, + Content: " \"Env\": [", + Highlighted: " \"Env\": [", + }, + { + Number: 11, + Content: " \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",", + Highlighted: " \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",", + }, + { + Number: 12, + Content: " \"secret=****************************************\"", + IsCause: true, + Highlighted: " \"secret=****************************************\"", + FirstCause: true, + LastCause: true, + }, + { + Number: 13, + Content: " ]", + Highlighted: " ]", + }, + }, + }, + Match: " \"secret=****************************************\"", + }, + }, + }, + }, + }, + { + name: "no secret", + config: &v1.ConfigFile{ + Config: v1.Config{ + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + }, + }, + }, + want: nil, + }, + { + name: "nil config", + config: nil, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newSecretAnalyzer(analyzer.ConfigAnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.Analyze(context.Background(), analyzer.ConfigAnalysisInput{ + Config: tt.config, + }) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/analyze.go b/pkg/fanal/analyzer/language/analyze.go new file mode 100644 index 000000000000..d31cbb2dfb0f --- /dev/null +++ b/pkg/fanal/analyzer/language/analyze.go @@ -0,0 +1,151 @@ +package language + +import ( + "io" + "strings" + + "golang.org/x/xerrors" + + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +// Analyze returns an analysis result of the lock file +func Analyze(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser godeptypes.Parser) (*analyzer.AnalysisResult, error) { + app, err := Parse(fileType, filePath, r, parser) + if err != nil { + return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err) + } + + if app == nil { + return nil, nil + } + + return &analyzer.AnalysisResult{Applications: []types.Application{*app}}, nil +} + +// AnalyzePackage returns an analysis result of the package file other than lock files +func AnalyzePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser godeptypes.Parser, checksum bool) (*analyzer.AnalysisResult, error) { + app, err := ParsePackage(fileType, filePath, r, parser, checksum) + if err != nil { + return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err) + } + + if app == nil { + return nil, nil + } + + return &analyzer.AnalysisResult{Applications: []types.Application{*app}}, nil +} + +// Parse returns a parsed result of the lock file +func Parse(fileType types.LangType, filePath string, r io.Reader, parser godeptypes.Parser) (*types.Application, error) { + rr, err := xio.NewReadSeekerAt(r) + if err != nil { + return nil, xerrors.Errorf("reader error: %w", err) + } + parsedLibs, parsedDependencies, err := parser.Parse(rr) + if err != nil { + return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err) + } + + // The file path of each library should be empty in case of dependency list such as lock file + // since they all will be the same path. + return toApplication(fileType, filePath, "", nil, parsedLibs, parsedDependencies), nil +} + +// ParsePackage returns a parsed result of the package file +func ParsePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser godeptypes.Parser, checksum bool) (*types.Application, error) { + parsedLibs, parsedDependencies, err := parser.Parse(r) + if err != nil { + return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err) + } + + // The reader is not passed if the checksum is not necessarily calculated. + if !checksum { + r = nil + } + + // The file path of each library should be empty in case of dependency list such as lock file + // since they all will be the same path. + return toApplication(fileType, filePath, filePath, r, parsedLibs, parsedDependencies), nil +} + +func toApplication(fileType types.LangType, filePath, libFilePath string, r xio.ReadSeekerAt, libs []godeptypes.Library, depGraph []godeptypes.Dependency) *types.Application { + if len(libs) == 0 { + return nil + } + + // Calculate the file digest when one of `spdx` formats is selected + d, err := calculateDigest(r) + if err != nil { + log.Logger.Warnf("Unable to get checksum for %s: %s", filePath, err) + } + + deps := make(map[string][]string) + for _, dep := range depGraph { + deps[dep.ID] = dep.DependsOn + } + + var pkgs []types.Package + for _, lib := range libs { + var licenses []string + if lib.License != "" { + licenses = licensing.SplitLicenses(lib.License) + for i, license := range licenses { + licenses[i] = licensing.Normalize(strings.TrimSpace(license)) + } + } + var locs []types.Location + for _, loc := range lib.Locations { + l := types.Location{ + StartLine: loc.StartLine, + EndLine: loc.EndLine, + } + locs = append(locs, l) + } + + // This file path is populated for virtual file paths within archives, such as nested JAR files. + libPath := libFilePath + if lib.FilePath != "" { + libPath = lib.FilePath + } + + newPkg := types.Package{ + ID: lib.ID, + Name: lib.Name, + Version: lib.Version, + Dev: lib.Dev, + FilePath: libPath, + Indirect: lib.Indirect, + Licenses: licenses, + DependsOn: deps[lib.ID], + Locations: locs, + Digest: d, + } + pkgs = append(pkgs, newPkg) + } + + return &types.Application{ + Type: fileType, + FilePath: filePath, + Libraries: pkgs, + } +} + +func calculateDigest(r xio.ReadSeekerAt) (digest.Digest, error) { + if r == nil { + return "", nil + } + // return reader to start after it has been read in analyzer + if _, err := r.Seek(0, io.SeekStart); err != nil { + return "", xerrors.Errorf("unable to seek: %w", err) + } + + return digest.CalcSHA1(r) +} diff --git a/pkg/fanal/analyzer/language/analyze_test.go b/pkg/fanal/analyzer/language/analyze_test.go new file mode 100644 index 000000000000..260c86b59ae8 --- /dev/null +++ b/pkg/fanal/analyzer/language/analyze_test.go @@ -0,0 +1,110 @@ +package language_test + +import ( + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" + + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type mockParser struct { + t *testing.T +} + +func (p *mockParser) Parse(r xio.ReadSeekerAt) ([]godeptypes.Library, []godeptypes.Dependency, error) { + b, err := io.ReadAll(r) + require.NoError(p.t, err) + + switch string(b) { + case "happy": + return []godeptypes.Library{ + { + Name: "test", + Version: "1.2.3", + }, + }, nil, nil + case "sad": + return nil, nil, xerrors.New("unexpected error") + } + + return nil, nil, nil +} + +func TestAnalyze(t *testing.T) { + type args struct { + fileType types.LangType + filePath string + content xio.ReadSeekerAt + } + tests := []struct { + name string + args args + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + args: args{ + fileType: types.GoBinary, + filePath: "app/myweb", + content: strings.NewReader("happy"), + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.GoBinary, + FilePath: "app/myweb", + Libraries: types.Packages{ + { + Name: "test", + Version: "1.2.3", + }, + }, + }, + }, + }, + }, + { + name: "empty", + args: args{ + fileType: types.GoBinary, + filePath: "app/myweb", + content: strings.NewReader(""), + }, + want: nil, + }, + { + name: "sad path", + args: args{ + fileType: types.Jar, + filePath: "app/myweb", + content: strings.NewReader("sad"), + }, + wantErr: "unexpected error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mp := &mockParser{t: t} + + got, err := language.Analyze(tt.args.fileType, tt.args.filePath, tt.args.content, mp) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/c/conan/conan.go b/pkg/fanal/analyzer/language/c/conan/conan.go new file mode 100644 index 000000000000..d06eb4cd260d --- /dev/null +++ b/pkg/fanal/analyzer/language/c/conan/conan.go @@ -0,0 +1,48 @@ +package conan + +import ( + "context" + "os" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/c/conan" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&conanLockAnalyzer{}) +} + +const ( + version = 1 +) + +// conanLockAnalyzer analyzes conan.lock +type conanLockAnalyzer struct{} + +func (a conanLockAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + p := conan.NewParser() + res, err := language.Analyze(types.Conan, input.FilePath, input.Content, p) + if err != nil { + return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err) + } + return res, nil +} + +func (a conanLockAnalyzer) Required(_ string, fileInfo os.FileInfo) bool { + // Lock file name can be anything + // cf. https://docs.conan.io/en/latest/versioning/lockfiles/introduction.html#locking-dependencies + // By default, we only check the default filename - `conan.lock`. + return fileInfo.Name() == types.ConanLock +} + +func (a conanLockAnalyzer) Type() analyzer.Type { + return analyzer.TypeConanLock +} + +func (a conanLockAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/c/conan/conan_test.go b/pkg/fanal/analyzer/language/c/conan/conan_test.go new file mode 100644 index 000000000000..fcb3237bfb61 --- /dev/null +++ b/pkg/fanal/analyzer/language/c/conan/conan_test.go @@ -0,0 +1,129 @@ +package conan + +import ( + "os" + "path/filepath" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_conanLockAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + inputFile: "testdata/happy.lock", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Conan, + FilePath: "testdata/happy.lock", + Libraries: types.Packages{ + { + ID: "openssl/3.0.5", + Name: "openssl", + Version: "3.0.5", + DependsOn: []string{ + "zlib/1.2.12", + }, + Locations: []types.Location{ + { + StartLine: 12, + EndLine: 21, + }, + }, + }, + { + ID: "zlib/1.2.12", + Name: "zlib", + Version: "1.2.12", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 28, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "empty file", + inputFile: "testdata/empty.lock", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := conanLockAnalyzer{} + got, err := a.Analyze(nil, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + if got != nil { + for _, app := range got.Applications { + sort.Sort(app.Libraries) + } + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_conanLockAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "default name", + filePath: "conan.lock", + want: true, + }, + { + name: "name with prefix", + filePath: "pkga_deps.lock", + want: false, + }, + { + name: "txt", + filePath: "test.txt", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + f, err := os.Create(filepath.Join(dir, tt.filePath)) + require.NoError(t, err) + defer f.Close() + + fi, err := f.Stat() + require.NoError(t, err) + + a := conanLockAnalyzer{} + got := a.Required("", fi) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/c/conan/testdata/empty.lock b/pkg/fanal/analyzer/language/c/conan/testdata/empty.lock new file mode 100644 index 000000000000..106ca0aa06b2 --- /dev/null +++ b/pkg/fanal/analyzer/language/c/conan/testdata/empty.lock @@ -0,0 +1,14 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "options": "o", + "path": "conanfile.txt", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=9\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/c/conan/testdata/happy.lock b/pkg/fanal/analyzer/language/c/conan/testdata/happy.lock new file mode 100644 index 000000000000..eb17995310e1 --- /dev/null +++ b/pkg/fanal/analyzer/language/c/conan/testdata/happy.lock @@ -0,0 +1,34 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "options": "o", + "requires": [ + "1" + ], + "path": "conanfile.txt", + "context": "host" + }, + "1": { + "ref": "openssl/3.0.5", + "options": "", + "requires": [ + "2" + ], + "package_id": "", + "prev": "0", + "context": "host" + }, + "2": { + "ref": "zlib/1.2.12", + "options": "", + "package_id": "", + "prev": "0", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=9\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/conda/meta/meta.go b/pkg/fanal/analyzer/language/conda/meta/meta.go new file mode 100644 index 000000000000..091587f3435c --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/meta/meta.go @@ -0,0 +1,39 @@ +package meta + +import ( + "context" + "os" + "path/filepath" + "regexp" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/meta" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&metaAnalyzer{}) +} + +const version = 1 + +var fileRegex = regexp.MustCompile(`.*/envs/.+/conda-meta/.+-.+-.+\.json`) + +type metaAnalyzer struct{} + +func (a metaAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + p := meta.NewParser() + return language.AnalyzePackage(types.CondaPkg, input.FilePath, input.Content, p, input.Options.FileChecksum) +} +func (a metaAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return fileRegex.MatchString(filepath.ToSlash(filePath)) +} + +func (a metaAnalyzer) Type() analyzer.Type { + return analyzer.TypeCondaPkg +} + +func (a metaAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/conda/meta/meta_test.go b/pkg/fanal/analyzer/language/conda/meta/meta_test.go new file mode 100644 index 000000000000..0b7a988e9ade --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/meta/meta_test.go @@ -0,0 +1,101 @@ +package meta + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_packagingAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "pip", + inputFile: "testdata/pip-22.2.2-py38h06a4308_0.json", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.CondaPkg, + FilePath: "testdata/pip-22.2.2-py38h06a4308_0.json", + Libraries: types.Packages{ + { + Name: "pip", + Version: "22.2.2", + Licenses: []string{"MIT"}, + FilePath: "testdata/pip-22.2.2-py38h06a4308_0.json", + }, + }, + }, + }, + }, + }, + { + name: "invalid", + inputFile: "testdata/invalid.json", + wantErr: "unable to parse conda package", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + stat, err := f.Stat() + require.NoError(t, err) + + a := metaAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Info: stat, + Content: f, + }) + + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } + +} + +func Test_packagingAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "pip", + filePath: "/home//miniconda3/envs//conda-meta/pip-22.2.2-py38h06a4308_0.json", + want: true, + }, + { + name: "invalid", + filePath: "/home//miniconda3/envs//conda-meta/invalid.json", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := metaAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/conda/meta/testdata/invalid.json b/pkg/fanal/analyzer/language/conda/meta/testdata/invalid.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/meta/testdata/invalid.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/conda/meta/testdata/pip-22.2.2-py38h06a4308_0.json b/pkg/fanal/analyzer/language/conda/meta/testdata/pip-22.2.2-py38h06a4308_0.json new file mode 100644 index 000000000000..4788d70e21aa --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/meta/testdata/pip-22.2.2-py38h06a4308_0.json @@ -0,0 +1,62 @@ +{ + "build": "py38h06a4308_0", + "build_number": 0, + "channel": "https://repo.anaconda.com/pkgs/main/linux-64", + "constrains": [], + "depends": [ + "python >=3.8,<3.9.0a0", + "setuptools", + "wheel" + ], + "extracted_package_dir": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0", + "features": "", + "files": [ + "bin/pip", + "", + "lib/python3.8/site-packages/pip/py.typed" + ], + "fn": "pip-22.2.2-py38h06a4308_0.conda", + "legacy_bz2_md5": "2ac9f1cfec65a1e4ef00cc0132ecd753", + "legacy_bz2_size": 2849993, + "license": "MIT", + "license_family": "MIT", + "link": { + "source": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0", + "type": 1 + }, + "md5": "ed3e0331e7c614b3148c9911e1fc15e3", + "name": "pip", + "package_tarball_full_path": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0.conda", + "paths_data": { + "paths": [ + { + "_path": "bin/pip", + "file_mode": "text", + "path_type": "hardlink", + "prefix_placeholder": "/opt/conda/conda-bld/pip_1664552683240/_h_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold", + "sha256": "1110c03ca2fb86e43e8b52a61cd9d7c722b2817fc893a613e1a947f5d7049008", + "sha256_in_prefix": "14ed5ba79d096035b83a469e863acbbaadd3ea6ca5f23f79eefa91fa857d3f19", + "size_in_bytes": 475 + }, + { + "_path": "" + }, + { + "_path": "lib/python3.8/site-packages/pip/py.typed", + "path_type": "hardlink", + "sha256": "10156fbcf4539ff788a73e5ee50ced48276b317ed0c1ded53fddd14a82256762", + "sha256_in_prefix": "10156fbcf4539ff788a73e5ee50ced48276b317ed0c1ded53fddd14a82256762", + "size_in_bytes": 286 + } + ], + "paths_version": 1 + }, + "requested_spec": "None", + "sha256": "3fb76b94cfa5ea9732bfb241b3d234ec0a5a48d16755c3c1ef3c94630f91eb26", + "size": 2417732, + "subdir": "linux-64", + "timestamp": 1664552878795, + "track_features": "", + "url": "https://repo.anaconda.com/pkgs/main/linux-64/pip-22.2.2-py38h06a4308_0.conda", + "version": "22.2.2" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec.go b/pkg/fanal/analyzer/language/dart/pub/pubspec.go new file mode 100644 index 000000000000..ab924cafd191 --- /dev/null +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec.go @@ -0,0 +1,182 @@ +package pub + +import ( + "context" + "io" + "io/fs" + "os" + "path/filepath" + "runtime" + "sort" + + "github.com/samber/lo" + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/parser/dart/pub" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypePubSpecLock, newPubSpecLockAnalyzer) +} + +const ( + version = 2 + pubSpecYamlFileName = "pubspec.yaml" +) + +// pubSpecLockAnalyzer analyzes `pubspec.lock` +type pubSpecLockAnalyzer struct { + parser godeptypes.Parser +} + +func newPubSpecLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return pubSpecLockAnalyzer{ + parser: pub.NewParser(), + }, nil +} + +func (a pubSpecLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + + // get all DependsOn from cache dir + // lib ID -> DependsOn names + allDependsOn, err := findDependsOn() + if err != nil { + log.Logger.Warnf("Unable to parse cache dir: %s", err) + } + + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == types.PubSpecLock + } + + err = fsutils.WalkDir(input.FS, ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { + app, err := language.Parse(types.Pub, path, r, a.parser) + if err != nil { + return xerrors.Errorf("unable to parse %q: %w", path, err) + } + + if app == nil { + return nil + } + + if allDependsOn != nil { + // Required to search for library versions for DependsOn. + libs := lo.SliceToMap(app.Libraries, func(lib types.Package) (string, string) { + return lib.Name, lib.ID + }) + + for i, lib := range app.Libraries { + var dependsOn []string + for _, depName := range allDependsOn[lib.ID] { + if depID, ok := libs[depName]; ok { + dependsOn = append(dependsOn, depID) + } + } + app.Libraries[i].DependsOn = dependsOn + } + } + + sort.Sort(app.Libraries) + apps = append(apps, *app) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func findDependsOn() (map[string][]string, error) { + dir := cacheDir() + if !fsutils.DirExists(dir) { + log.Logger.Debugf("Cache dir (%s) not found. Need 'dart pub get' to fill dependency relationships", dir) + return nil, nil + } + + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == pubSpecYamlFileName + } + + deps := make(map[string][]string) + if err := fsutils.WalkDir(os.DirFS(dir), ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + id, dependsOn, err := parsePubSpecYaml(r) + if err != nil { + log.Logger.Debugf("Unable to parse %q: %s", path, err) + return nil + } + if id != "" { + deps[id] = dependsOn + } + return nil + + }); err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + return deps, nil +} + +// https://dart.dev/tools/pub/glossary#system-cache +func cacheDir() string { + if dir := os.Getenv("PUB_CACHE"); dir != "" { + return dir + } + + // `%LOCALAPPDATA%\Pub\Cache` for Windows + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("LOCALAPPDATA"), "Pub", "Cache") + } + + // `~/.pub-cache` for Linux or Mac + return filepath.Join(os.Getenv("HOME"), ".pub_cache") +} + +type pubSpecYaml struct { + Name string `yaml:"name"` + Version string `yaml:"version,omitempty"` + Dependencies map[string]interface{} `yaml:"dependencies,omitempty"` +} + +func parsePubSpecYaml(r io.Reader) (string, []string, error) { + var spec pubSpecYaml + if err := yaml.NewDecoder(r).Decode(&spec); err != nil { + return "", nil, xerrors.Errorf("unable to decode: %w", err) + } + + // Version is a required field only for packages from pub.dev: + // https://dart.dev/tools/pub/pubspec#version + // We can skip packages without version, + // because we compare packages by ID (name+version) + if spec.Version == "" || len(spec.Dependencies) == 0 { + return "", nil, nil + } + + // pubspec.yaml uses version ranges + // save only dependencies names + dependsOn := maps.Keys(spec.Dependencies) + + return dependency.ID(types.Pub, spec.Name, spec.Version), dependsOn, nil +} + +func (a pubSpecLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Base(filePath) == types.PubSpecLock +} + +func (a pubSpecLockAnalyzer) Type() analyzer.Type { + return analyzer.TypePubSpecLock +} + +func (a pubSpecLockAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go b/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go new file mode 100644 index 000000000000..d8ecad82fb3f --- /dev/null +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go @@ -0,0 +1,214 @@ +package pub + +import ( + "context" + "os" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_pubSpecLockAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + pubCacheEnv string + want *analyzer.AnalysisResult + wantErr assert.ErrorAssertionFunc + }{ + { + // Supports only absolute paths for `rootUri` in package_config.json + // But for this test this field was changed + name: "happy path with cache", + dir: "testdata/happy", + pubCacheEnv: "testdata/happy/cache", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Pub, + FilePath: "pubspec.lock", + Libraries: types.Packages{ + { + ID: "collection@1.17.0", + Name: "collection", + Version: "1.17.0", + Indirect: true, + }, + { + ID: "crypto@3.0.3", + Name: "crypto", + Version: "3.0.3", + DependsOn: []string{ + "typed_data@1.3.2", + }, + }, + { + ID: "meta@1.11.0", + Name: "meta", + Version: "1.11.0", + }, + { + ID: "typed_data@1.3.2", + Name: "typed_data", + Version: "1.3.2", + Indirect: true, + DependsOn: []string{ + "collection@1.17.0", + }, + }, + }, + }, + }, + }, + wantErr: assert.NoError, + }, + { + name: "happy path without cache", + dir: "testdata/happy", + pubCacheEnv: "testdata/happy/empty", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Pub, + FilePath: "pubspec.lock", + Libraries: types.Packages{ + { + ID: "collection@1.17.0", + Name: "collection", + Version: "1.17.0", + Indirect: true, + }, + { + ID: "crypto@3.0.3", + Name: "crypto", + Version: "3.0.3", + }, + { + ID: "meta@1.11.0", + Name: "meta", + Version: "1.11.0", + }, + { + ID: "typed_data@1.3.2", + Name: "typed_data", + Version: "1.3.2", + Indirect: true, + }, + }, + }, + }, + }, + wantErr: assert.NoError, + }, + { + name: "empty file", + dir: "testdata/empty", + want: &analyzer.AnalysisResult{}, + wantErr: assert.NoError, + }, + { + name: "broken file", + dir: "testdata/broken", + want: &analyzer.AnalysisResult{}, + wantErr: assert.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("PUB_CACHE", tt.pubCacheEnv) + a, err := newPubSpecLockAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_pubSpecLockAnalyzer_cacheDir(t *testing.T) { + tests := []struct { + name string + pubCacheEnv string + localAppDataEnv string + windowsTest bool + wantDir string + }{ + { + name: "default cache dir for Linux/MacOS", + wantDir: "/root/.pub_cache", + }, + { + name: "default cache dir Windows", + windowsTest: true, + wantDir: "C:\\Users\\User\\AppData\\Local\\Pub\\Cache", + }, + { + name: "PUB_CACHE is used", + pubCacheEnv: "/root/cache", + wantDir: "/root/cache", + }, + { + name: "PUB_CACHE is used in Windows", + pubCacheEnv: "C:\\Cache", + windowsTest: true, + wantDir: "C:\\Cache", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if runtime.GOOS == "windows" { + if !tt.windowsTest { + t.Skipf("This test is not used for %s", runtime.GOOS) + } + t.Setenv("LOCALAPPDATA", "C:\\Users\\User\\AppData\\Local") + } else { + if tt.windowsTest { + t.Skipf("This test is not used for %s", runtime.GOOS) + } + t.Setenv("HOME", "/root") + } + + t.Setenv("PUB_CACHE", tt.pubCacheEnv) + + dir := cacheDir() + assert.Equal(t, tt.wantDir, dir) + }) + } +} + +func Test_pubSpecLockAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path", + filePath: "pubspec.lock", + want: true, + }, + { + name: "sad path", + filePath: "test.txt", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := pubSpecLockAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/dart/pub/testdata/broken/pubspec.lock b/pkg/fanal/analyzer/language/dart/pub/testdata/broken/pubspec.lock new file mode 100644 index 000000000000..66c0ff41b8d1 --- /dev/null +++ b/pkg/fanal/analyzer/language/dart/pub/testdata/broken/pubspec.lock @@ -0,0 +1 @@ +it is broken \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dart/pub/testdata/empty/pubspec.lock b/pkg/fanal/analyzer/language/dart/pub/testdata/empty/pubspec.lock new file mode 100644 index 000000000000..eb4cea8c2089 --- /dev/null +++ b/pkg/fanal/analyzer/language/dart/pub/testdata/empty/pubspec.lock @@ -0,0 +1,6 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: +sdks: + dart: ">=2.18.0 <3.0.0" + flutter: ">=3.3.0" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/collection-1.18.0/pubspec.yaml b/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/collection-1.18.0/pubspec.yaml new file mode 100644 index 000000000000..2e54e00344a6 --- /dev/null +++ b/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/collection-1.18.0/pubspec.yaml @@ -0,0 +1,16 @@ +name: collection +version: 1.18.0 +description: >- + Collections and utilities functions and classes related to collections. +repository: https://github.com/dart-lang/collection + +topics: + - data-structures + - collections + +environment: + sdk: ">=2.18.0 <4.0.0" + +dev_dependencies: + lints: ^2.0.1 + test: ^1.16.0 \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/crypto-3.0.3/pubspec.yaml b/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/crypto-3.0.3/pubspec.yaml new file mode 100644 index 000000000000..29cc438636a8 --- /dev/null +++ b/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/crypto-3.0.3/pubspec.yaml @@ -0,0 +1,17 @@ +name: crypto +version: 3.0.3 +description: Implementations of SHA, MD5, and HMAC cryptographic functions. +repository: https://github.com/dart-lang/crypto +topics: + - crypto + +environment: + sdk: '>=2.19.0 <3.0.0' + +dependencies: + typed_data: ^1.3.0 + +dev_dependencies: + convert: ^3.0.0 + dart_flutter_team_lints: ^1.0.0 + test: ^1.16.0 \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/meta-1.11.0/pubspec.yaml b/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/meta-1.11.0/pubspec.yaml new file mode 100644 index 000000000000..d4c6f0f86340 --- /dev/null +++ b/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/meta-1.11.0/pubspec.yaml @@ -0,0 +1,17 @@ +name: meta +# Note, because version `2.0.0` was mistakenly released, the next major version must be `3.x.y`. +version: 1.11.0 +description: >- + Annotations used to express developer intentions that can't otherwise be + deduced by statically analyzing source code. +repository: https://github.com/dart-lang/sdk/tree/main/pkg/meta + +environment: + sdk: ">=2.12.0 <4.0.0" + +# We use 'any' version constraints here as we get our package versions from +# the dart-lang/sdk repo's DEPS file. Note that this is a special case; the +# best practice for packages is to specify their compatible version ranges. +# See also https://dart.dev/tools/pub/dependencies. +dev_dependencies: + lints: any diff --git a/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/typed_data-1.3.2/pubspec.yaml b/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/typed_data-1.3.2/pubspec.yaml new file mode 100644 index 000000000000..642bf644115c --- /dev/null +++ b/pkg/fanal/analyzer/language/dart/pub/testdata/happy/cache/hosted/pub.dev/typed_data-1.3.2/pubspec.yaml @@ -0,0 +1,18 @@ +name: typed_data +version: 1.3.2 +description: >- + Utility functions and classes related to the dart:typed_data library. +repository: https://github.com/dart-lang/typed_data + +topics: + - data-structures + +environment: + sdk: '>=2.17.0 <4.0.0' + +dependencies: + collection: ^1.15.0 + +dev_dependencies: + dart_flutter_team_lints: ^0.1.0 + test: ^1.16.0 \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dart/pub/testdata/happy/pubspec.lock b/pkg/fanal/analyzer/language/dart/pub/testdata/happy/pubspec.lock new file mode 100644 index 000000000000..d3bd9670042c --- /dev/null +++ b/pkg/fanal/analyzer/language/dart/pub/testdata/happy/pubspec.lock @@ -0,0 +1,37 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + collection: + dependency: transitive + description: + name: collection + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" + source: hosted + version: "1.17.0" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + meta: + dependency: "direct dev" + description: + name: meta + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" +sdks: + dart: ">=3.1.0 <4.0.0" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dotnet/deps/deps.go b/pkg/fanal/analyzer/language/dotnet/deps/deps.go new file mode 100644 index 000000000000..035142b41708 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/deps/deps.go @@ -0,0 +1,47 @@ +package deps + +import ( + "context" + "os" + "strings" + + "golang.org/x/xerrors" + + core "github.com/aquasecurity/trivy/pkg/dependency/parser/dotnet/core_deps" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&depsLibraryAnalyzer{}) +} + +const ( + version = 1 + depsExtension = ".deps.json" +) + +type depsLibraryAnalyzer struct{} + +func (a depsLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + parser := core.NewParser() + res, err := language.Analyze(types.DotNetCore, input.FilePath, input.Content, parser) + if err != nil { + return nil, xerrors.Errorf(".Net Core dependencies analysis error: %w", err) + } + + return res, nil +} + +func (a depsLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return strings.HasSuffix(filePath, depsExtension) +} + +func (a depsLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypeDotNetCore +} + +func (a depsLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go b/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go new file mode 100644 index 000000000000..37f23d2774f2 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go @@ -0,0 +1,101 @@ +package deps + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_depsLibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/datacollector.deps.json", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.DotNetCore, + FilePath: "testdata/datacollector.deps.json", + Libraries: types.Packages{ + { + Name: "Newtonsoft.Json", + Version: "9.0.1", + Locations: []types.Location{ + { + StartLine: 8, + EndLine: 14, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "sad path", + inputFile: "testdata/invalid.txt", + wantErr: ".Net Core dependencies analysis error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := depsLibraryAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_depsLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "config", + filePath: "test/datacollector.deps.json", + want: true, + }, + { + name: "zip", + filePath: "test.zip", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := depsLibraryAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/dotnet/deps/testdata/datacollector.deps.json b/pkg/fanal/analyzer/language/dotnet/deps/testdata/datacollector.deps.json new file mode 100644 index 000000000000..08699fee4a7a --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/deps/testdata/datacollector.deps.json @@ -0,0 +1,21 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v2.1", + "signature": "" + }, + "compilationOptions": {}, + "libraries": { + "Newtonsoft.Json/9.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==", + "path": "newtonsoft.json/9.0.1", + "hashPath": "newtonsoft.json.9.0.1.nupkg.sha512" + }, + "Microsoft.VisualStudio.TestPlatform.Common/17.2.0-release-20220408-11": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dotnet/deps/testdata/invalid.txt b/pkg/fanal/analyzer/language/dotnet/deps/testdata/invalid.txt new file mode 100644 index 000000000000..9daeafb9864c --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/deps/testdata/invalid.txt @@ -0,0 +1 @@ +test diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go new file mode 100644 index 000000000000..6411f4d1bc8d --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go @@ -0,0 +1,116 @@ +package nuget + +import ( + "context" + "errors" + "io" + "io/fs" + "os" + "path/filepath" + "sort" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/config" + "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/lock" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeNuget, newNugetLibraryAnalyzer) +} + +const ( + version = 3 + lockFile = types.NuGetPkgsLock + configFile = types.NuGetPkgsConfig +) + +var requiredFiles = []string{lockFile, configFile} + +type nugetLibraryAnalyzer struct { + lockParser godeptypes.Parser + configParser godeptypes.Parser + licenseParser nuspecParser +} + +func newNugetLibraryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &nugetLibraryAnalyzer{ + lockParser: lock.NewParser(), + configParser: config.NewParser(), + licenseParser: newNuspecParser(), + }, nil +} + +func (a *nugetLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + foundLicenses := make(map[string][]string) + + // We saved only config and lock files in the FS, + // so we need to parse all saved files + required := func(path string, d fs.DirEntry) bool { + return true + } + + err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + // Set the default parser + parser := a.lockParser + + targetFile := filepath.Base(path) + if targetFile == configFile { + parser = a.configParser + } + + app, err := language.Parse(types.NuGet, path, r, parser) + if err != nil { + return xerrors.Errorf("NuGet parse error: %w", err) + } + + // nuget file doesn't contain dependencies + if app == nil { + return nil + } + + for i, lib := range app.Libraries { + license, ok := foundLicenses[lib.ID] + if !ok { + license, err = a.licenseParser.findLicense(lib.Name, lib.Version) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return xerrors.Errorf("license find error: %w", err) + } + foundLicenses[lib.ID] = license + } + + app.Libraries[i].Licenses = license + } + + sort.Sort(app.Libraries) + apps = append(apps, *app) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("NuGet walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a *nugetLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + return slices.Contains(requiredFiles, fileName) +} + +func (a *nugetLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypeNuget +} + +func (a *nugetLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go new file mode 100644 index 000000000000..3db74b377fc3 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go @@ -0,0 +1,233 @@ +package nuget + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + env map[string]string + want *analyzer.AnalysisResult + }{ + { + name: "happy path config file.", + dir: "testdata/config", + env: map[string]string{ + "HOME": "testdata/repository", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.NuGet, + FilePath: "packages.config", + Libraries: types.Packages{ + { + Name: "Microsoft.AspNet.WebApi", + Version: "5.2.2", + }, + { + Name: "Newtonsoft.Json", + Version: "6.0.4", + }, + }, + }, + }, + }, + }, + { + name: "happy path lock file.", + dir: "testdata/lock", + env: map[string]string{ + "HOME": "testdata/repository", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.NuGet, + FilePath: "packages.lock.json", + Libraries: types.Packages{ + { + ID: "Newtonsoft.Json@12.0.3", + Name: "Newtonsoft.Json", + Version: "12.0.3", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 10, + }, + }, + Licenses: []string{"MIT"}, + }, + { + ID: "NuGet.Frameworks@5.7.0", + Name: "NuGet.Frameworks", + Version: "5.7.0", + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 19, + }, + }, + DependsOn: []string{"Newtonsoft.Json@12.0.3"}, + }, + }, + }, + }, + }, + }, + { + name: "happy path lock file. `NUGET_PACKAGES` env is used", + dir: "testdata/lock", + env: map[string]string{ + "NUGET_PACKAGES": "testdata/repository/.nuget/packages", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.NuGet, + FilePath: "packages.lock.json", + Libraries: types.Packages{ + { + ID: "Newtonsoft.Json@12.0.3", + Name: "Newtonsoft.Json", + Version: "12.0.3", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 10, + }, + }, + Licenses: []string{"MIT"}, + }, + { + ID: "NuGet.Frameworks@5.7.0", + Name: "NuGet.Frameworks", + Version: "5.7.0", + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 19, + }, + }, + DependsOn: []string{"Newtonsoft.Json@12.0.3"}, + }, + }, + }, + }, + }, + }, + { + name: "happy path lock file. `.nuget` directory doesn't exist", + dir: "testdata/lock", + env: map[string]string{ + "HOME": "testdata/invalid", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.NuGet, + FilePath: "packages.lock.json", + Libraries: types.Packages{ + { + ID: "Newtonsoft.Json@12.0.3", + Name: "Newtonsoft.Json", + Version: "12.0.3", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 10, + }, + }, + }, + { + ID: "NuGet.Frameworks@5.7.0", + Name: "NuGet.Frameworks", + Version: "5.7.0", + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 19, + }, + }, + DependsOn: []string{"Newtonsoft.Json@12.0.3"}, + }, + }, + }, + }, + }, + }, + { + name: "happy path lock file without dependencies.", + dir: "testdata/lock-without-deps", + env: map[string]string{ + "HOME": "testdata/repository", + }, + want: &analyzer.AnalysisResult{}, + }, + { + name: "sad path", + dir: "testdata/sad", + env: map[string]string{ + "HOME": "testdata/repository", + }, + want: &analyzer.AnalysisResult{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for env, path := range tt.env { + t.Setenv(env, path) + } + a, err := newNugetLibraryAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_nugetLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "config", + filePath: "test/packages.config", + want: true, + }, + { + name: "lock", + filePath: "test/packages.lock.json", + want: true, + }, + { + name: "zip", + filePath: "test.zip", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := nugetLibraryAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuspec.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuspec.go new file mode 100644 index 000000000000..b42d1cec38d2 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuspec.go @@ -0,0 +1,82 @@ +package nuget + +import ( + "encoding/xml" + "fmt" + "os" + "path/filepath" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +const nuspecExt = "nuspec" + +// https://learn.microsoft.com/en-us/nuget/reference/nuspec +type Package struct { + Metadata Metadata `xml:"metadata"` +} + +type Metadata struct { + License License `xml:"license"` +} + +type License struct { + Text string `xml:",chardata"` + Type string `xml:"type,attr"` +} + +type nuspecParser struct { + packagesDir string // global packages folder - https: //learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders +} + +func newNuspecParser() nuspecParser { + // cf. https: //learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders + packagesDir := os.Getenv("NUGET_PACKAGES") + if packagesDir == "" { + packagesDir = filepath.Join(os.Getenv("HOME"), ".nuget", "packages") + } + + if !fsutils.DirExists(packagesDir) { + log.Logger.Debugf("The nuget packages directory couldn't be found. License search disabled") + return nuspecParser{} + } + + return nuspecParser{ + packagesDir: packagesDir, + } +} + +func (p nuspecParser) findLicense(name, version string) ([]string, error) { + if p.packagesDir == "" { + return nil, nil + } + + // package path uses lowercase letters only + // e.g. `$HOME/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.nuspec` + // for `Newtonsoft.Json` v13.0.3 + name = strings.ToLower(name) + version = strings.ToLower(version) + + nuspecFileName := fmt.Sprintf("%s.%s", name, nuspecExt) + path := filepath.Join(p.packagesDir, name, version, nuspecFileName) + + f, err := os.Open(path) + if err != nil { + return nil, xerrors.Errorf("unable to open %q file: %w", path, err) + } + defer func() { _ = f.Close() }() + + var pkg Package + if err = xml.NewDecoder(f).Decode(&pkg); err != nil { + return nil, xerrors.Errorf("unable to decode %q file: %w", path, err) + } + + if license := pkg.Metadata.License; license.Type != "expression" || license.Text == "" { + return nil, nil + } + return []string{pkg.Metadata.License.Text}, nil +} diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/testdata/config/packages.config b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/config/packages.config new file mode 100644 index 000000000000..51ea35d31a83 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/config/packages.config @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/testdata/lock-without-deps/packages.lock.json b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/lock-without-deps/packages.lock.json new file mode 100644 index 000000000000..2fca7fd98a29 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/lock-without-deps/packages.lock.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "dependencies": { + "net6.0": {} + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/testdata/lock/packages.lock.json b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/lock/packages.lock.json new file mode 100644 index 000000000000..7c06bd9d435f --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/lock/packages.lock.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "dependencies": { + ".NETCoreApp,Version=v5.0": { + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[12.0.3, )", + "resolved": "12.0.3", + "contentHash": "6mgjfnRB4jKMlzHSl+VD+oUc1IebOZabkbyWj2RiTgWwYPPuaK1H97G1sHqGwPlS5npiF5Q0OrxN1wni2n5QWg==" + }, + "NuGet.Frameworks": { + "type": "Direct", + "requested": "[5.7.0, )", + "resolved": "5.7.0", + "contentHash": "7Q/wUoB3jCBcq9zoBOBGHFhe78C13jViPmvjvzTwthVV8DAjMfpXnqAYtgwdaRLJMkTXrtdLxfPBIFFhmlsnIQ==", + "dependencies": { + "Newtonsoft.Json": "12.0.3" + } + } + } + } +} diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/testdata/repository/.nuget/packages/newtonsoft.json/12.0.3/newtonsoft.json.nuspec b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/repository/.nuget/packages/newtonsoft.json/12.0.3/newtonsoft.json.nuspec new file mode 100755 index 000000000000..c215566e4e41 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/repository/.nuget/packages/newtonsoft.json/12.0.3/newtonsoft.json.nuspec @@ -0,0 +1,42 @@ + + + + Newtonsoft.Json + 12.0.3 + Json.NET + James Newton-King + James Newton-King + false + MIT + https://licenses.nuget.org/MIT + packageIcon.png + https://www.newtonsoft.com/json + Json.NET is a popular high-performance JSON framework for .NET + Copyright © James Newton-King 2008 + json + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/testdata/repository/.nuget/packages/newtonsoft.json/6.0.4/newtonsoft.json.nuspec b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/repository/.nuget/packages/newtonsoft.json/6.0.4/newtonsoft.json.nuspec new file mode 100755 index 000000000000..4f361ab9c2d5 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/repository/.nuget/packages/newtonsoft.json/6.0.4/newtonsoft.json.nuspec @@ -0,0 +1,16 @@ + + + + Newtonsoft.Json + 6.0.4 + Json.NET + James Newton-King + James Newton-King + https://raw.github.com/JamesNK/Newtonsoft.Json/master/LICENSE.md + http://james.newtonking.com/json + false + Json.NET is a popular high-performance JSON framework for .NET + en-US + json + + \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/testdata/repository/.nuget/packages/nuget.frameworks/5.7.0/nuget.frameworks.nuspec b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/repository/.nuget/packages/nuget.frameworks/5.7.0/nuget.frameworks.nuspec new file mode 100755 index 000000000000..21ad4997758e --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/repository/.nuget/packages/nuget.frameworks/5.7.0/nuget.frameworks.nuspec @@ -0,0 +1,23 @@ + + + + NuGet.Frameworks + 5.7.0+b804bf4ba62c0b47c77bbf3e22e196b57cd7a556 + Microsoft + Microsoft + true + LICENSE.txt + https://aka.ms/nugetprj + https://raw.githubusercontent.com/NuGet/Media/master/Images/MainLogo/256x256/nuget_256.png + NuGet's understanding of target frameworks. + © Microsoft Corporation. All rights reserved. + nuget + true + + + + + + + + \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/testdata/sad/invalid.txt b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/sad/invalid.txt new file mode 100644 index 000000000000..9daeafb9864c --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/nuget/testdata/sad/invalid.txt @@ -0,0 +1 @@ +test diff --git a/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops.go b/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops.go new file mode 100644 index 000000000000..03bf27878e40 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops.go @@ -0,0 +1,49 @@ +package packagesprops + +import ( + "context" + "os" + "strings" + + "golang.org/x/xerrors" + + props "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/packagesprops" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&packagesPropsAnalyzer{}) +} + +const ( + version = 1 + packagesPropsSuffix = "packages.props" // https://github.com/dotnet/roslyn-tools/blob/b4c5220f5dfc4278847b6d38eff91cc1188f8066/src/RoslynInsertionTool/RoslynInsertionTool/CoreXT.cs#L39-L40 +) + +type packagesPropsAnalyzer struct{} + +func (a packagesPropsAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + parser := props.NewParser() + res, err := language.Analyze(types.PackagesProps, input.FilePath, input.Content, parser) + if err != nil { + return nil, xerrors.Errorf("*Packages.props dependencies analysis error: %w", err) + } + + return res, nil +} + +func (a packagesPropsAnalyzer) Required(filePath string, _ os.FileInfo) bool { + // There is no information about this in the documentation, + // but NuGet works correctly with lowercase filenames + return strings.HasSuffix(strings.ToLower(filePath), packagesPropsSuffix) +} + +func (a packagesPropsAnalyzer) Type() analyzer.Type { + return analyzer.TypePackagesProps +} + +func (a packagesPropsAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go b/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go new file mode 100644 index 000000000000..0fb12d4ba837 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go @@ -0,0 +1,134 @@ +package packagesprops + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_packagesPropsAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path packages props", + inputFile: "testdata/Packages.props", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PackagesProps, + FilePath: "testdata/Packages.props", + Libraries: types.Packages{ + { + ID: "Package1@22.1.4", + Name: "Package1", + Version: "22.1.4", + }, + { + ID: "Package2@2.3.0", + Name: "Package2", + Version: "2.3.0", + }, + }, + }, + }, + }, + }, + { + name: "happy path directory packages props", + inputFile: "testdata/Directory.Packages.props", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PackagesProps, + FilePath: "testdata/Directory.Packages.props", + Libraries: types.Packages{ + { + ID: "Package1@4.2.1", + Name: "Package1", + Version: "4.2.1", + }, + { + ID: "Package2@8.2.0", + Name: "Package2", + Version: "8.2.0", + }, + }, + }, + }, + }, + }, + { + name: "sad path", + inputFile: "testdata/invalid.txt", + wantErr: "*Packages.props dependencies analysis error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := packagesPropsAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_packagesPropsAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "directory packages props", + filePath: "test/Directory.Packages.props", + want: true, + }, + { + name: "packages props", + filePath: "test/Packages.props", + want: true, + }, + { + name: "packages props lower case", + filePath: "test/packages.props", + want: true, + }, + { + name: "zip", + filePath: "test.zip", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := packagesPropsAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/dotnet/packagesprops/testdata/Directory.Packages.props b/pkg/fanal/analyzer/language/dotnet/packagesprops/testdata/Directory.Packages.props new file mode 100644 index 000000000000..90adf042fde1 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/packagesprops/testdata/Directory.Packages.props @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dotnet/packagesprops/testdata/Packages.props b/pkg/fanal/analyzer/language/dotnet/packagesprops/testdata/Packages.props new file mode 100644 index 000000000000..2532bf1b1dc0 --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/packagesprops/testdata/Packages.props @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dotnet/packagesprops/testdata/invalid.txt b/pkg/fanal/analyzer/language/dotnet/packagesprops/testdata/invalid.txt new file mode 100644 index 000000000000..9daeafb9864c --- /dev/null +++ b/pkg/fanal/analyzer/language/dotnet/packagesprops/testdata/invalid.txt @@ -0,0 +1 @@ +test diff --git a/pkg/fanal/analyzer/language/elixir/mix/mix.go b/pkg/fanal/analyzer/language/elixir/mix/mix.go new file mode 100644 index 000000000000..24ad59ef94a3 --- /dev/null +++ b/pkg/fanal/analyzer/language/elixir/mix/mix.go @@ -0,0 +1,49 @@ +package mix + +import ( + "context" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/hex/mix" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&mixLockAnalyzer{}) +} + +const ( + version = 1 +) + +// mixLockAnalyzer analyzes 'mix.lock' +type mixLockAnalyzer struct{} + +func (a mixLockAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + p := mix.NewParser() + res, err := language.Analyze(types.Hex, input.FilePath, input.Content, p) + if err != nil { + return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err) + } + return res, nil +} + +func (a mixLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { + // Lock file name can be anything. + // cf. https://hexdocs.pm/mix/Mix.Project.html#module-configuration + // By default, we only check the default file name - `mix.lock`. + return filepath.Base(filePath) == types.MixLock +} + +func (a mixLockAnalyzer) Type() analyzer.Type { + return analyzer.TypeMixLock +} + +func (a mixLockAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/elixir/mix/mix_test.go b/pkg/fanal/analyzer/language/elixir/mix/mix_test.go new file mode 100644 index 000000000000..5c836c260555 --- /dev/null +++ b/pkg/fanal/analyzer/language/elixir/mix/mix_test.go @@ -0,0 +1,96 @@ +package mix + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_mixLockAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + inputFile: "testdata/happy.mix.lock", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Hex, + FilePath: "testdata/happy.mix.lock", + Libraries: types.Packages{ + { + ID: "bunt@0.2.0", + Name: "bunt", + Version: "0.2.0", + Locations: []types.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "empty file", + inputFile: "testdata/empty.mix.lock", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer func() { + err = f.Close() + assert.NoError(t, err) + }() + + a := mixLockAnalyzer{} + got, err := a.Analyze(nil, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_mixLockAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path", + filePath: "mix.lock", + want: true, + }, + { + name: "sad path", + filePath: "test.txt", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := mixLockAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/elixir/mix/testdata/empty.mix.lock b/pkg/fanal/analyzer/language/elixir/mix/testdata/empty.mix.lock new file mode 100644 index 000000000000..15081287722e --- /dev/null +++ b/pkg/fanal/analyzer/language/elixir/mix/testdata/empty.mix.lock @@ -0,0 +1,2 @@ +%{ +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/elixir/mix/testdata/happy.mix.lock b/pkg/fanal/analyzer/language/elixir/mix/testdata/happy.mix.lock new file mode 100644 index 000000000000..873a3a7fc7c8 --- /dev/null +++ b/pkg/fanal/analyzer/language/elixir/mix/testdata/happy.mix.lock @@ -0,0 +1,3 @@ +%{ + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/binary/binary.go b/pkg/fanal/analyzer/language/golang/binary/binary.go new file mode 100644 index 000000000000..a0a7a37c3c0e --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/binary/binary.go @@ -0,0 +1,47 @@ +package binary + +import ( + "context" + "errors" + "os" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/binary" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +func init() { + analyzer.RegisterAnalyzer(&gobinaryLibraryAnalyzer{}) +} + +const version = 1 + +type gobinaryLibraryAnalyzer struct{} + +func (a gobinaryLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + p := binary.NewParser() + res, err := language.Analyze(types.GoBinary, input.FilePath, input.Content, p) + if errors.Is(err, binary.ErrUnrecognizedExe) || errors.Is(err, binary.ErrNonGoBinary) { + return nil, nil + } else if err != nil { + return nil, xerrors.Errorf("go binary (filepath: %s) parse error: %w", input.FilePath, err) + } + + return res, nil +} + +func (a gobinaryLibraryAnalyzer) Required(_ string, fileInfo os.FileInfo) bool { + return utils.IsExecutable(fileInfo) +} + +func (a gobinaryLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypeGoBinary +} + +func (a gobinaryLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/golang/binary/binary_test.go b/pkg/fanal/analyzer/language/golang/binary/binary_test.go new file mode 100644 index 000000000000..ca8434298cbb --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/binary/binary_test.go @@ -0,0 +1,109 @@ +package binary + +import ( + "context" + "os" + "runtime" + "testing" + + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_gobinaryLibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + inputFile: "testdata/executable_gobinary", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.GoBinary, + FilePath: "testdata/executable_gobinary", + Libraries: types.Packages{ + { + Name: "github.com/aquasecurity/go-pep440-version", + Version: "v0.0.0-20210121094942-22b2f8951d46", + }, + { + Name: "github.com/aquasecurity/go-version", + Version: "v0.0.0-20210121072130-637058cfe492", + }, + { + Name: "golang.org/x/xerrors", + Version: "v0.0.0-20200804184101-5ec99f83aff1", + }, + }, + }, + }, + }, + }, + { + name: "not go binary", + inputFile: "testdata/executable_bash", + }, + { + name: "broken elf", + inputFile: "testdata/broken_elf", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := gobinaryLibraryAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_gobinaryLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "executable file", + filePath: lo.Ternary(runtime.GOOS == "windows", "testdata/binary.exe", "testdata/0755"), + want: true, + }, + { + name: "file perm 0644", + filePath: "testdata/0644", + want: false, + }, + { + name: "symlink", + filePath: "testdata/symlink", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := gobinaryLibraryAnalyzer{} + fileInfo, err := os.Lstat(tt.filePath) + require.NoError(t, err) + got := a.Required(tt.filePath, fileInfo) + assert.Equal(t, tt.want, got, fileInfo.Mode().Perm()) + }) + } + +} diff --git a/pkg/fanal/analyzer/language/golang/binary/testdata/0644 b/pkg/fanal/analyzer/language/golang/binary/testdata/0644 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/language/golang/binary/testdata/0755 b/pkg/fanal/analyzer/language/golang/binary/testdata/0755 new file mode 100755 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/language/golang/binary/testdata/binary.exe b/pkg/fanal/analyzer/language/golang/binary/testdata/binary.exe new file mode 100644 index 000000000000..f8e020bf1ebe --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/binary/testdata/binary.exe @@ -0,0 +1 @@ +exe \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/binary/testdata/broken_elf b/pkg/fanal/analyzer/language/golang/binary/testdata/broken_elf new file mode 100755 index 000000000000..1a736bf298d6 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/binary/testdata/broken_elf @@ -0,0 +1 @@ +ELF diff --git a/pkg/fanal/analyzer/language/golang/binary/testdata/executable_bash b/pkg/fanal/analyzer/language/golang/binary/testdata/executable_bash new file mode 100755 index 000000000000..ca3b9f0d2cda --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/binary/testdata/executable_bash @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "hello" diff --git a/pkg/fanal/analyzer/language/golang/binary/testdata/executable_gobinary b/pkg/fanal/analyzer/language/golang/binary/testdata/executable_gobinary new file mode 100755 index 000000000000..2890300b9205 Binary files /dev/null and b/pkg/fanal/analyzer/language/golang/binary/testdata/executable_gobinary differ diff --git a/pkg/fanal/analyzer/language/golang/binary/testdata/symlink b/pkg/fanal/analyzer/language/golang/binary/testdata/symlink new file mode 120000 index 000000000000..19102815663d --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/binary/testdata/symlink @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/mod/mod.go b/pkg/fanal/analyzer/language/golang/mod/mod.go new file mode 100644 index 000000000000..5aa0ae2293fe --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/mod.go @@ -0,0 +1,319 @@ +package mod + +import ( + "context" + "errors" + "fmt" + "go/build" + "io" + "io/fs" + "os" + "path/filepath" + "regexp" + "unicode" + + "github.com/samber/lo" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" + "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/sum" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeGoMod, newGoModAnalyzer) +} + +const version = 2 + +var ( + requiredFiles = []string{ + types.GoMod, + types.GoSum, + } + licenseRegexp = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING|README|NOTICE).*$`) +) + +type gomodAnalyzer struct { + // root go.mod/go.sum + modParser godeptypes.Parser + sumParser godeptypes.Parser + + // go.mod/go.sum in dependencies + leafModParser godeptypes.Parser + + licenseClassifierConfidenceLevel float64 +} + +func newGoModAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &gomodAnalyzer{ + modParser: mod.NewParser(true), // Only the root module should replace + sumParser: sum.NewParser(), + leafModParser: mod.NewParser(false), + licenseClassifierConfidenceLevel: opt.LicenseScannerOption.ClassifierConfidenceLevel, + }, nil +} + +func (a *gomodAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == types.GoMod + } + + err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, _ io.Reader) error { + // Parse go.mod + gomod, err := parse(input.FS, path, a.modParser) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if gomod == nil { + return nil + } + + if lessThanGo117(gomod) { + // e.g. /app/go.mod => /app/go.sum + sumPath := filepath.Join(filepath.Dir(path), types.GoSum) + gosum, err := parse(input.FS, sumPath, a.sumParser) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return xerrors.Errorf("parse error: %w", err) + } + mergeGoSum(gomod, gosum) + } + + apps = append(apps, *gomod) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + + if err = a.fillAdditionalData(apps); err != nil { + log.Logger.Warnf("Unable to collect additional info: %s", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a *gomodAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + return slices.Contains(requiredFiles, fileName) +} + +func (a *gomodAnalyzer) Type() analyzer.Type { + return analyzer.TypeGoMod +} + +func (a *gomodAnalyzer) Version() int { + return version +} + +// fillAdditionalData collects licenses and dependency relationships, then update applications. +func (a *gomodAnalyzer) fillAdditionalData(apps []types.Application) error { + gopath := os.Getenv("GOPATH") + if gopath == "" { + gopath = build.Default.GOPATH + } + + // $GOPATH/pkg/mod + modPath := filepath.Join(gopath, "pkg", "mod") + if !fsutils.DirExists(modPath) { + log.Logger.Debugf("GOPATH (%s) not found. Need 'go mod download' to fill licenses and dependency relationships", modPath) + return nil + } + + licenses := make(map[string][]string) + for i, app := range apps { + // Actually used dependencies + usedLibs := lo.SliceToMap(app.Libraries, func(pkg types.Package) (string, types.Package) { + return pkg.Name, pkg + }) + for j, lib := range app.Libraries { + if l, ok := licenses[lib.ID]; ok { + // Fill licenses + apps[i].Libraries[j].Licenses = l + continue + } + + // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v1.0.0 + modDir := filepath.Join(modPath, fmt.Sprintf("%s@v%s", normalizeModName(lib.Name), lib.Version)) + + // Collect licenses + if licenseNames, err := findLicense(modDir, a.licenseClassifierConfidenceLevel); err != nil { + return xerrors.Errorf("license error: %w", err) + } else { + // Cache the detected licenses + licenses[lib.ID] = licenseNames + + // Fill licenses + apps[i].Libraries[j].Licenses = licenseNames + } + + // Collect dependencies of the direct dependency + if dep, err := a.collectDeps(modDir, lib.ID); err != nil { + return xerrors.Errorf("dependency graph error: %w", err) + } else if dep.ID == "" { + // go.mod not found + continue + } else { + // Filter out unused dependencies and convert module names to module IDs + apps[i].Libraries[j].DependsOn = lo.FilterMap(dep.DependsOn, func(modName string, _ int) (string, bool) { + if m, ok := usedLibs[modName]; !ok { + return "", false + } else { + return m.ID, true + } + }) + } + } + } + return nil +} + +func (a *gomodAnalyzer) collectDeps(modDir, pkgID string) (godeptypes.Dependency, error) { + // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/go.mod + modPath := filepath.Join(modDir, "go.mod") + f, err := os.Open(modPath) + if errors.Is(err, fs.ErrNotExist) { + log.Logger.Debugf("Unable to identify dependencies of %s as it doesn't support Go modules", pkgID) + return godeptypes.Dependency{}, nil + } else if err != nil { + return godeptypes.Dependency{}, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + // Parse go.mod under $GOPATH/pkg/mod + libs, _, err := a.leafModParser.Parse(f) + if err != nil { + return godeptypes.Dependency{}, xerrors.Errorf("%s parse error: %w", modPath, err) + } + + // Filter out indirect dependencies + dependsOn := lo.FilterMap(libs, func(lib godeptypes.Library, index int) (string, bool) { + return lib.Name, !lib.Indirect + }) + + return godeptypes.Dependency{ + ID: pkgID, + DependsOn: dependsOn, + }, nil +} + +func parse(fsys fs.FS, path string, parser godeptypes.Parser) (*types.Application, error) { + f, err := fsys.Open(path) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + file, ok := f.(xio.ReadSeekCloserAt) + if !ok { + return nil, xerrors.Errorf("type assertion error: %w", err) + } + + // Parse go.mod or go.sum + return language.Parse(types.GoModule, path, file, parser) +} + +func lessThanGo117(gomod *types.Application) bool { + for _, lib := range gomod.Libraries { + // The indirect field is populated only in Go 1.17+ + if lib.Indirect { + return false + } + } + return true +} + +func mergeGoSum(gomod, gosum *types.Application) { + if gomod == nil || gosum == nil { + return + } + uniq := make(map[string]types.Package) + for _, lib := range gomod.Libraries { + // It will be used for merging go.sum. + uniq[lib.Name] = lib + } + + // For Go 1.16 or less, we need to merge go.sum into go.mod. + for _, lib := range gosum.Libraries { + // Skip dependencies in go.mod so that go.mod should be preferred. + if _, ok := uniq[lib.Name]; ok { + continue + } + + // This dependency doesn't exist in go.mod, so it must be an indirect dependency. + lib.Indirect = true + uniq[lib.Name] = lib + } + + gomod.Libraries = maps.Values(uniq) +} + +func findLicense(dir string, classifierConfidenceLevel float64) ([]string, error) { + var license *types.LicenseFile + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } else if !d.Type().IsRegular() { + return nil + } + if !licenseRegexp.MatchString(filepath.Base(path)) { + return nil + } + // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/LICENSE + f, err := os.Open(path) + if err != nil { + return xerrors.Errorf("file (%s) open error: %w", path, err) + } + defer f.Close() + + l, err := licensing.Classify(path, f, classifierConfidenceLevel) + if err != nil { + return xerrors.Errorf("license classify error: %w", err) + } + // License found + if l != nil && len(l.Findings) > 0 { + license = l + return io.EOF + } + return nil + }) + + switch { + // The module path may not exist + case errors.Is(err, os.ErrNotExist): + return nil, nil + case err != nil && !errors.Is(err, io.EOF): + return nil, fmt.Errorf("finding a known open source license: %w", err) + case license == nil || len(license.Findings) == 0: + return nil, nil + } + + return license.Findings.Names(), nil +} + +// normalizeModName escapes upper characters +// e.g. 'github.com/BurntSushi/toml' => 'github.com/!burnt!sushi' +func normalizeModName(name string) string { + var newName []rune + for _, c := range name { + if unicode.IsUpper(c) { + // 'A' => '!a' + newName = append(newName, '!', unicode.ToLower(c)) + } else { + newName = append(newName, c) + } + } + return string(newName) +} diff --git a/pkg/fanal/analyzer/language/golang/mod/mod_test.go b/pkg/fanal/analyzer/language/golang/mod/mod_test.go new file mode 100644 index 000000000000..25137172cbc2 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/mod_test.go @@ -0,0 +1,203 @@ +package mod + +import ( + "context" + "path/filepath" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/mapfs" +) + +func Test_gomodAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + files []string + want *analyzer.AnalysisResult + }{ + { + name: "happy", + files: []string{ + "testdata/happy/mod", + "testdata/happy/sum", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.GoModule, + FilePath: "go.mod", + Libraries: types.Packages{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20220406074731-71021a481237", + Licenses: []string{ + "MIT", + }, + DependsOn: []string{ + "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + }, + }, + { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Indirect: true, + }, + }, + }, + }, + }, + }, + { + name: "wrong go.mod from `pkg`", + files: []string{ + "testdata/wrong-gomod-in-pkg/mod", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.GoModule, + FilePath: "go.mod", + Libraries: types.Packages{ + { + ID: "github.com/sad/sad@v0.0.1", + Name: "github.com/sad/sad", + Version: "0.0.1", + }, + }, + }, + }, + }, + }, + { + name: "less than 1.17", + files: []string{ + "testdata/merge/mod", + "testdata/merge/sum", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.GoModule, + FilePath: "go.mod", + Libraries: types.Packages{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20230219131432-590b1dfb6edd", + DependsOn: []string{ + "github.com/BurntSushi/toml@v0.3.1", + }, + }, + { + ID: "github.com/BurntSushi/toml@v0.3.1", + Name: "github.com/BurntSushi/toml", + Version: "0.3.1", + Indirect: true, + Licenses: []string{ + "MIT", + }, + }, + }, + }, + }, + }, + }, + { + name: "no go.sum", + files: []string{ + "testdata/merge/mod", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.GoModule, + FilePath: "go.mod", + Libraries: types.Packages{ + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20230219131432-590b1dfb6edd", + DependsOn: []string{}, + }, + }, + }, + }, + }, + }, + { + name: "sad go.mod", + files: []string{ + "testdata/sad/mod", + }, + want: &analyzer.AnalysisResult{}, + }, + } + for _, tt := range tests { + t.Setenv("GOPATH", "testdata") + t.Run(tt.name, func(t *testing.T) { + a, err := newGoModAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + mfs := mapfs.New() + for _, file := range tt.files { + // Since broken go.mod files bothers IDE, we should use other file names than "go.mod" and "go.sum". + if filepath.Base(file) == "mod" { + require.NoError(t, mfs.WriteFile("go.mod", file)) + } else if filepath.Base(file) == "sum" { + require.NoError(t, mfs.WriteFile("go.sum", file)) + } + } + + ctx := context.Background() + got, err := a.PostAnalyze(ctx, analyzer.PostAnalysisInput{ + FS: mfs, + }) + assert.NoError(t, err) + + if len(got.Applications) > 0 { + sort.Sort(got.Applications[0].Libraries) + sort.Sort(tt.want.Applications[0].Libraries) + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_gomodAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "go.mod", + filePath: "test/go.mod", + want: true, + }, + { + name: "go.sum", + filePath: "test/foo/go.sum", + want: true, + }, + { + name: "sad", + filePath: "a/b/c/d/test.sum", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := gomodAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/happy/mod b/pkg/fanal/analyzer/language/golang/mod/testdata/happy/mod new file mode 100644 index 000000000000..0b4e07634bc5 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/happy/mod @@ -0,0 +1,9 @@ +module github.com/org/repo + +go 1.17 + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211110174639-8257534ffed3 + +require golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + +replace github.com/aquasecurity/go-dep-parser => github.com/aquasecurity/go-dep-parser v0.0.0-20220406074731-71021a481237 \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/happy/sum b/pkg/fanal/analyzer/language/golang/mod/testdata/happy/sum new file mode 100644 index 000000000000..24e1c479b8e8 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/happy/sum @@ -0,0 +1,24 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= +github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/merge/mod b/pkg/fanal/analyzer/language/golang/mod/testdata/merge/mod new file mode 100644 index 000000000000..4257ff26da4d --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/merge/mod @@ -0,0 +1,7 @@ +module github.com/org/repo + +go 1.15 + +require github.com/aquasecurity/go-dep-parser v0.0.0-20211110174639-8257534ffed3 + +replace github.com/aquasecurity/go-dep-parser => github.com/aquasecurity/go-dep-parser v0.0.0-20230219131432-590b1dfb6edd \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/merge/sum b/pkg/fanal/analyzer/language/golang/mod/testdata/merge/sum new file mode 100644 index 000000000000..d64fdb02cbd7 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/merge/sum @@ -0,0 +1,4 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aquasecurity/go-dep-parser v0.0.0-20230219131432-590b1dfb6edd h1:H9IR14rR3+Z13ZH7ay9bs2hHBL7WAqdEJLLr8nhx/Rs= +github.com/aquasecurity/go-dep-parser v0.0.0-20230219131432-590b1dfb6edd/go.mod h1:4dZHU2Ntsh9EopNVdTKf8UjSGDNTMVoyB5B34RjD75g= \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/!burnt!sushi/toml@v0.3.1/COPYING b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/!burnt!sushi/toml@v0.3.1/COPYING new file mode 100644 index 000000000000..01b5743200b8 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/!burnt!sushi/toml@v0.3.1/COPYING @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 TOML authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/LICENSE b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/LICENSE new file mode 100644 index 000000000000..7ce066ea3308 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Teppei Fukuda + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/go.mod b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/go.mod new file mode 100644 index 000000000000..9c840195c345 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/go.mod @@ -0,0 +1,31 @@ +module github.com/aquasecurity/go-dep-parser + +go 1.18 + +require ( + github.com/BurntSushi/toml v1.2.1 + github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 + github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/go-retryablehttp v0.7.2 + github.com/liamg/jfather v0.0.7 + github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 + github.com/samber/lo v1.37.0 + github.com/stretchr/testify v1.8.1 + go.uber.org/zap v1.24.0 + golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4 + golang.org/x/mod v0.8.0 + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/text v0.3.8 // indirect +) diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd/go.mod b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd/go.mod new file mode 100644 index 000000000000..9c840195c345 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd/go.mod @@ -0,0 +1,31 @@ +module github.com/aquasecurity/go-dep-parser + +go 1.18 + +require ( + github.com/BurntSushi/toml v1.2.1 + github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 + github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/go-retryablehttp v0.7.2 + github.com/liamg/jfather v0.0.7 + github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 + github.com/samber/lo v1.37.0 + github.com/stretchr/testify v1.8.1 + go.uber.org/zap v1.24.0 + golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4 + golang.org/x/mod v0.8.0 + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/text v0.3.8 // indirect +) diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/sad/sad@v0.0.1/go.mod b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/sad/sad@v0.0.1/go.mod new file mode 100644 index 000000000000..688dd9e29ac4 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/sad/sad@v0.0.1/go.mod @@ -0,0 +1 @@ +wrong \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/sad/mod b/pkg/fanal/analyzer/language/golang/mod/testdata/sad/mod new file mode 100644 index 000000000000..e466dcbd8e8f --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/sad/mod @@ -0,0 +1 @@ +invalid \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/wrong-gomod-in-pkg/mod b/pkg/fanal/analyzer/language/golang/mod/testdata/wrong-gomod-in-pkg/mod new file mode 100644 index 000000000000..eb68112644ef --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/wrong-gomod-in-pkg/mod @@ -0,0 +1,5 @@ +module github.com/org/repo + +go 1.17 + +require github.com/sad/sad v0.0.1 \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/java/gradle/lockfile.go b/pkg/fanal/analyzer/language/java/gradle/lockfile.go new file mode 100644 index 000000000000..5dddb0b49c3c --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/lockfile.go @@ -0,0 +1,119 @@ +package gradle + +import ( + "context" + "fmt" + "io" + "io/fs" + "os" + "sort" + "strings" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/gradle/lockfile" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeGradleLock, newGradleLockAnalyzer) +} + +const ( + version = 2 + fileNameSuffix = "gradle.lockfile" +) + +// gradleLockAnalyzer analyzes '*gradle.lockfile' +type gradleLockAnalyzer struct { + parser godeptypes.Parser +} + +func newGradleLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &gradleLockAnalyzer{ + parser: lockfile.NewParser(), + }, nil +} + +func (a gradleLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + poms, err := parsePoms() + if err != nil { + log.Logger.Warnf("Unable to get licenses and dependsOn: %s", err) + } + + required := func(path string, d fs.DirEntry) bool { + return a.Required(path, nil) + } + + var apps []types.Application + err = fsutils.WalkDir(input.FS, ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { + var app *types.Application + app, err = language.Parse(types.Gradle, filePath, r, a.parser) + if err != nil { + return xerrors.Errorf("%s parse error: %w", filePath, err) + } + + if app == nil { + return nil + } + + libs := lo.SliceToMap(app.Libraries, func(lib types.Package) (string, struct{}) { + return lib.ID, struct{}{} + }) + + for i, lib := range app.Libraries { + pom := poms[lib.ID] + + // Fill licenses from pom file + if len(pom.Licenses.License) > 0 { + app.Libraries[i].Licenses = lo.Map(pom.Licenses.License, func(license License, _ int) string { + return license.Name + }) + } + + // File child deps from pom file + var deps []string + for _, dep := range pom.Dependencies.Dependency { + id := packageID(dep.GroupID, dep.ArtifactID, dep.Version) + if _, ok := libs[id]; ok { + deps = append(deps, id) + } + } + sort.Strings(deps) + app.Libraries[i].DependsOn = deps + } + + sort.Sort(app.Libraries) + apps = append(apps, *app) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a gradleLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return strings.HasSuffix(filePath, fileNameSuffix) +} + +func (a gradleLockAnalyzer) Type() analyzer.Type { + return analyzer.TypeGradleLock +} + +func (a gradleLockAnalyzer) Version() int { + return version +} + +func packageID(groupId, artifactId, ver string) string { + return fmt.Sprintf("%s:%s:%s", groupId, artifactId, ver) +} diff --git a/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go b/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go new file mode 100644 index 000000000000..b1868fecb936 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go @@ -0,0 +1,160 @@ +package gradle + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_gradleLockAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + cacheDir string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + dir: "testdata/lockfiles/happy", + cacheDir: "testdata/cache", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Gradle, + FilePath: "gradle.lockfile", + Libraries: types.Packages{ + { + ID: "junit:junit:4.13", + Name: "junit:junit", + Version: "4.13", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + Licenses: []string{ + "Eclipse Public License 1.0", + }, + DependsOn: []string{ + "org.hamcrest:hamcrest-core:1.3", + }, + }, + { + ID: "org.hamcrest:hamcrest-core:1.3", + Name: "org.hamcrest:hamcrest-core", + Version: "1.3", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path without cache", + dir: "testdata/lockfiles/happy", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Gradle, + FilePath: "gradle.lockfile", + Libraries: types.Packages{ + { + ID: "junit:junit:4.13", + Name: "junit:junit", + Version: "4.13", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + { + ID: "org.hamcrest:hamcrest-core:1.3", + Name: "org.hamcrest:hamcrest-core", + Version: "1.3", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "empty file", + dir: "testdata/lockfiles/empty", + want: &analyzer.AnalysisResult{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.cacheDir != "" { + t.Setenv("GRADLE_USER_HOME", tt.cacheDir) + } + + a, err := newGradleLockAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_nugetLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "default name", + filePath: "test/gradle.lockfile", + want: true, + }, + { + name: "name with prefix", + filePath: "test/settings-gradle.lockfile", + want: true, + }, + { + name: "txt", + filePath: "test/test.txt", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := gradleLockAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/java/gradle/pom.go b/pkg/fanal/analyzer/language/java/gradle/pom.go new file mode 100644 index 000000000000..638b5c9fd61b --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/pom.go @@ -0,0 +1,166 @@ +package gradle + +import ( + "encoding/xml" + "io" + "io/fs" + "os" + "path/filepath" + "runtime" + "strings" + + "golang.org/x/net/html/charset" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +type pomXML struct { + GroupId string `xml:"groupId"` + ArtifactId string `xml:"artifactId"` + Version string `xml:"version"` + Properties Properties `xml:"properties"` + Dependencies Dependencies `xml:"dependencies"` + Licenses Licenses `xml:"licenses"` +} +type Dependencies struct { + Dependency []Dependency `xml:"dependency"` +} + +type Dependency struct { + GroupID string `xml:"groupId"` + ArtifactID string `xml:"artifactId"` + Version string `xml:"version"` +} + +type Licenses struct { + License []License `xml:"license"` +} + +type License struct { + Name string `xml:"name"` +} + +type Properties map[string]string + +type property struct { + XMLName xml.Name + Value string `xml:",chardata"` +} + +func (props *Properties) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error { + *props = Properties{} + for { + var p property + err := d.Decode(&p) + if err == io.EOF { + break + } else if err != nil { + return xerrors.Errorf("XML decode error: %w", err) + } + + (*props)[p.XMLName.Local] = p.Value + } + return nil +} + +func parsePoms() (map[string]pomXML, error) { + cacheDir := detectCacheDir() + // Cache dir is not found + if cacheDir == "" { + return nil, nil + } + + required := func(path string, d fs.DirEntry) bool { + return filepath.Ext(path) == ".pom" + } + + var poms = make(map[string]pomXML) + err := fsutils.WalkDir(os.DirFS(cacheDir), ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { + pom, err := parsePom(r, path) + if err != nil { + log.Logger.Debugf("Unable to parse %q: %s", path, err) + return nil + } + + if pom.ArtifactId != "" { + poms[packageID(pom.GroupId, pom.ArtifactId, pom.Version)] = pom + } + return nil + }) + if err != nil { + return nil, xerrors.Errorf("gradle licenses walk error: %w", err) + } + + return poms, nil +} + +func parsePom(r io.Reader, path string) (pomXML, error) { + pom := pomXML{} + decoder := xml.NewDecoder(r) + decoder.CharsetReader = charset.NewReaderLabel + if err := decoder.Decode(&pom); err != nil { + return pomXML{}, xerrors.Errorf("xml decode error: %w", err) + } + + // We only need pom's with licenses or dependencies + if len(pom.Licenses.License) == 0 && len(pom.Dependencies.Dependency) == 0 { + return pomXML{}, nil + } + + // If pom file doesn't contain GroupID or Version: + // find these values from filepath + // e.g. caches/modules-2/files-2.1/com.google.code.gson/gson/2.9.1/f0cf3edcef8dcb74d27cb427544a309eb718d772/gson-2.9.1.pom + dirs := strings.Split(filepath.ToSlash(path), "/") + if pom.GroupId == "" { + pom.GroupId = dirs[len(dirs)-5] + } + if pom.Version == "" { + pom.Version = dirs[len(dirs)-3] + } + + if err := pom.resolveDependencyVersions(); err != nil { + return pomXML{}, xerrors.Errorf("unable to resolve dependency version: %w", err) + } + + return pom, nil +} + +// resolveDependencyVersions resolves versions from properties +func (pom *pomXML) resolveDependencyVersions() error { + for i, dep := range pom.Dependencies.Dependency { + if strings.HasPrefix(dep.Version, "${") && strings.HasSuffix(dep.Version, "}") { + dep.Version = strings.TrimPrefix(strings.TrimSuffix(dep.Version, "}"), "${") + if resolvedVer, ok := pom.Properties[dep.Version]; ok { + pom.Dependencies.Dependency[i].Version = resolvedVer + } else if dep.Version == "${project.version}" { + pom.Dependencies.Dependency[i].Version = dep.Version + } else { + // We use simplified logic to resolve properties. + // If necessary, update and use the logic for maven pom's + return xerrors.Errorf("Unable to resolve %q version. Please open a new discussion to update the Trivy logic.", dep.Version) + } + } + } + return nil +} + +func detectCacheDir() string { + // https://docs.gradle.org/current/userguide/directory_layout.html + dir := os.Getenv("GRADLE_USER_HOME") + if dir == "" { + if runtime.GOOS == "windows" { + dir = filepath.Join(os.Getenv("%HOMEPATH%"), ".gradle") + } else { + dir = filepath.Join(os.Getenv("HOME"), ".gradle") + } + } + dir = filepath.Join(dir, "caches") + + if !fsutils.DirExists(dir) { + log.Logger.Debug("Unable to get licenses and dependsOn. Gradle cache dir doesn't exist.") + return "" + } + return dir +} diff --git a/pkg/fanal/analyzer/language/java/gradle/pom_test.go b/pkg/fanal/analyzer/language/java/gradle/pom_test.go new file mode 100644 index 000000000000..4ca85c647e2e --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/pom_test.go @@ -0,0 +1,101 @@ +package gradle + +import ( + "github.com/stretchr/testify/require" + "os" + "path/filepath" + "testing" +) + +func Test_parsePom(t *testing.T) { + tests := []struct { + name string + inputFile string + inputPath string + want pomXML + }{ + { + name: "happy path", + inputFile: filepath.Join("testdata", "poms", "happy.pom"), + inputPath: "cache/caches/modules-2/files-2.1/org.example/example-core/1.0/872e413497b906e7c9fa85ccc96046c5d1ef7ece/example-core-1.0.pom", + want: pomXML{ + GroupId: "org.example", + ArtifactId: "example-core", + Version: "1.0.0", + Licenses: Licenses{ + License: []License{ + { + Name: "Apache License, Version 2.0", + }, + }, + }, + Dependencies: Dependencies{ + Dependency: []Dependency{ + { + GroupID: "org.example", + ArtifactID: "example-api", + Version: "2.0.0", + }, + }, + }, + }, + }, + { + name: "happy path. Take GroupID and Version from path", + inputFile: filepath.Join("testdata", "poms", "without-groupid-and-version.pom"), + inputPath: "cache/caches/modules-2/files-2.1/org.example/example-core/1.0.0/872e413497b906e7c9fa85ccc96046c5d1ef7ece/example-core-1.0.pom", + want: pomXML{ + GroupId: "org.example", + ArtifactId: "example-core", + Version: "1.0.0", + Licenses: Licenses{ + License: []License{ + { + Name: "Apache License, Version 2.0", + }, + }, + }, + }, + }, + { + name: "happy path. Dependency version as property.", + inputFile: filepath.Join("testdata", "poms", "dep-version-as-property.pom"), + inputPath: "cache/caches/modules-2/files-2.1/org.example/example-core/1.0.0/872e413497b906e7c9fa85ccc96046c5d1ef7ece/example-core-1.0.pom", + want: pomXML{ + GroupId: "org.example", + ArtifactId: "example-core", + Version: "1.0.0", + Properties: Properties{ + "coreVersion": "2.0.1", + }, + Dependencies: Dependencies{ + Dependency: []Dependency{ + { + GroupID: "org.example", + ArtifactID: "example-api", + Version: "2.0.1", + }, + }, + }, + }, + }, + { + name: "happy path. Dependency version as property.", + inputFile: filepath.Join("testdata", "poms", "without-licenses-and-deps.pom"), + inputPath: "cache/caches/modules-2/files-2.1/org.example/example-core/1.0.0/872e413497b906e7c9fa85ccc96046c5d1ef7ece/example-core-1.0.pom", + want: pomXML{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + + pom, err := parsePom(f, tt.inputPath) + require.NoError(t, err) + + require.Equal(t, tt.want, pom) + }) + } +} diff --git a/pkg/fanal/analyzer/language/java/gradle/testdata/cache/caches/modules-2/files-2.1/junit/junit/4.13/5c17760663fae422643fc859fd352c68a1d91bfc/junit-4.13.pom b/pkg/fanal/analyzer/language/java/gradle/testdata/cache/caches/modules-2/files-2.1/junit/junit/4.13/5c17760663fae422643fc859fd352c68a1d91bfc/junit-4.13.pom new file mode 100644 index 000000000000..40d49278c416 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/testdata/cache/caches/modules-2/files-2.1/junit/junit/4.13/5c17760663fae422643fc859fd352c68a1d91bfc/junit-4.13.pom @@ -0,0 +1,587 @@ + + + 4.0.0 + + junit + junit + 4.13 + + JUnit + JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck. + http://junit.org + 2002 + + JUnit + http://www.junit.org + + + + Eclipse Public License 1.0 + http://www.eclipse.org/legal/epl-v10.html + repo + + + + + + dsaff + David Saff + david@saff.net + + + kcooney + Kevin Cooney + kcooney@google.com + + + stefanbirkner + Stefan Birkner + mail@stefan-birkner.de + + + marcphilipp + Marc Philipp + mail@marcphilipp.de + + + + + JUnit contributors + JUnit + team@junit.org + https://github.com/junit-team/junit4/graphs/contributors + + developers + + + + + + 3.0.4 + + + + scm:git:git://github.com/junit-team/junit4.git + scm:git:git@github.com:junit-team/junit4.git + http://github.com/junit-team/junit4/tree/master + r4.13 + + + github + https://github.com/junit-team/junit4/issues + + + travis + https://travis-ci.org/junit-team/junit4 + + + https://github.com/junit-team/junit4/wiki/Download-and-Install + + junit-snapshot-repo + Nexus Snapshot Repository + https://oss.sonatype.org/content/repositories/snapshots/ + + + junit-releases-repo + Nexus Release Repository + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + junit.github.io + gitsite:git@github.com/junit-team/junit4.git + + + + + 1.5 + 2.19.1 + 1.3 + ISO-8859-1 + + 67893CC4 + + + + + org.hamcrest + hamcrest-core + ${hamcrestVersion} + + + + org.hamcrest + hamcrest-library + ${hamcrestVersion} + test + + + + + + + ${project.basedir}/src/main/resources + + + ${project.basedir} + + LICENSE-junit.txt + + + + + + + + maven-enforcer-plugin + 1.4 + + + enforce-versions + initialize + + enforce + + + true + + + + Current version of Maven ${maven.version} required to build the project + should be ${project.prerequisites.maven}, or higher! + + [${project.prerequisites.maven},) + + + Current JDK version ${java.version} should be ${jdkVersion}, or higher! + + ${jdkVersion} + + + Best Practice is to never define repositories in pom.xml (use a repository + manager instead). + + + + No Snapshots Dependencies Allowed! + + + + + + + + + com.google.code.maven-replacer-plugin + replacer + 1.5.3 + + + process-sources + + replace + + + + + false + ${project.build.sourceDirectory}/junit/runner/Version.java.template + ${project.build.sourceDirectory}/junit/runner/Version.java + false + @version@ + ${project.version} + + + + + maven-compiler-plugin + 3.3 + + ${project.build.sourceEncoding} + ${jdkVersion} + ${jdkVersion} + ${jdkVersion} + ${jdkVersion} + 1.5 + true + true + true + true + + -Xlint:unchecked + + 128m + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.14 + + + signature-check + test + + check + + + + org.codehaus.mojo.signature + java15 + 1.0 + + + + + + + + maven-surefire-plugin + ${surefireVersion} + + org/junit/tests/AllTests.java + true + false + + + + org.apache.maven.surefire + surefire-junit47 + ${surefireVersion} + + + + + + maven-source-plugin + 2.4 + + + + maven-javadoc-plugin + 2.10.3 + + ${basedir}/src/main/javadoc/stylesheet.css + protected + false + false + false + true + true + true + JUnit API + UTF-8 + en + ${jdkVersion} + + + api_${jdkVersion} + http://docs.oracle.com/javase/${jdkVersion}.0/docs/api/ + + + *.internal.* + true + 32m + 128m + true + true + + org.hamcrest:hamcrest-core:* + + + + + maven-release-plugin + 2.5.2 + + forked-path + false + -Pgenerate-docs,junit-release ${arguments} + r@{project.version} + + + + maven-site-plugin + 3.4 + + + com.github.stephenc.wagon + wagon-gitsite + 0.4.1 + + + org.apache.maven.doxia + doxia-module-markdown + 1.5 + + + + + maven-jar-plugin + 2.6 + + + false + + true + + + junit + + + + + + maven-clean-plugin + 2.6.1 + + + maven-deploy-plugin + 2.8.2 + + + maven-install-plugin + 2.5.2 + + + maven-resources-plugin + 2.7 + + + + + + + + maven-project-info-reports-plugin + 2.8 + + false + + + + + + index + dependency-info + modules + license + project-team + scm + issue-tracking + mailing-list + dependency-management + dependencies + dependency-convergence + cim + distribution-management + + + + + + maven-javadoc-plugin + 2.10.3 + + javadoc/latest + ${basedir}/src/main/javadoc/stylesheet.css + protected + false + false + false + true + true + true + JUnit API + UTF-8 + en + ${jdkVersion} + + + api_${jdkVersion} + http://docs.oracle.com/javase/${jdkVersion}.0/docs/api/ + + + junit.*,*.internal.* + true + 32m + 128m + true + true + + org.hamcrest:hamcrest-core:* + + + + + + javadoc + + + + + + + + + + junit-release + + + + + + maven-gpg-plugin + 1.6 + + + gpg-sign + verify + + sign + + + + + + + + + generate-docs + + + + + maven-source-plugin + + + attach-sources + prepare-package + + jar-no-fork + + + + + + maven-javadoc-plugin + + + attach-javadoc + package + + jar + + + + + + + + + restrict-doclint + + + [1.8,) + + + + + maven-compiler-plugin + + + -Xlint:unchecked + -Xdoclint:accessibility,reference,syntax + + + + + maven-javadoc-plugin + + -Xdoclint:accessibility -Xdoclint:reference + + + + + + + + maven-javadoc-plugin + + -Xdoclint:accessibility -Xdoclint:reference + + + + + + + java9 + + [1.9,) + + + + 1.6 + + + + + maven-javadoc-plugin + + 1.6 + + + + + + + + maven-javadoc-plugin + + 1.6 + + + + + + + diff --git a/pkg/fanal/analyzer/language/java/gradle/testdata/cache/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/872e413497b906e7c9fa85ccc96046c5d1ef7ece/hamcrest-core-1.3.pom b/pkg/fanal/analyzer/language/java/gradle/testdata/cache/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/872e413497b906e7c9fa85ccc96046c5d1ef7ece/hamcrest-core-1.3.pom new file mode 100644 index 000000000000..0721781c99a0 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/testdata/cache/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/872e413497b906e7c9fa85ccc96046c5d1ef7ece/hamcrest-core-1.3.pom @@ -0,0 +1,18 @@ + + + 4.0.0 + + + org.hamcrest + hamcrest-parent + 1.3 + + + hamcrest-core + jar + Hamcrest Core + + This is the core API of hamcrest matcher framework to be used by third-party framework providers. This includes the a foundation set of matcher implementations for common operations. + + diff --git a/pkg/fanal/analyzer/language/java/gradle/testdata/lockfiles/empty/gradle.lockfile b/pkg/fanal/analyzer/language/java/gradle/testdata/lockfiles/empty/gradle.lockfile new file mode 100644 index 000000000000..138b79c580c2 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/testdata/lockfiles/empty/gradle.lockfile @@ -0,0 +1,4 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +empty= \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/java/gradle/testdata/lockfiles/happy/gradle.lockfile b/pkg/fanal/analyzer/language/java/gradle/testdata/lockfiles/happy/gradle.lockfile new file mode 100644 index 000000000000..957bb968cc8f --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/testdata/lockfiles/happy/gradle.lockfile @@ -0,0 +1,6 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +junit:junit:4.13=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hamcrest:hamcrest-core:1.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +empty=annotationProcessor,testAnnotationProcessor \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/java/gradle/testdata/poms/dep-version-as-property.pom b/pkg/fanal/analyzer/language/java/gradle/testdata/poms/dep-version-as-property.pom new file mode 100644 index 000000000000..7b2cd75b39f1 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/testdata/poms/dep-version-as-property.pom @@ -0,0 +1,21 @@ + + + 4.0.0 + + org.example + example-core + 1.0.0 + + + 2.0.1 + + + + + org.example + example-api + ${coreVersion} + + + diff --git a/pkg/fanal/analyzer/language/java/gradle/testdata/poms/happy.pom b/pkg/fanal/analyzer/language/java/gradle/testdata/poms/happy.pom new file mode 100644 index 000000000000..896fb1df5981 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/testdata/poms/happy.pom @@ -0,0 +1,23 @@ + + + 4.0.0 + + org.example + example-core + 1.0.0 + + + + Apache License, Version 2.0 + + + + + + org.example + example-api + 2.0.0 + + + diff --git a/pkg/fanal/analyzer/language/java/gradle/testdata/poms/without-groupid-and-version.pom b/pkg/fanal/analyzer/language/java/gradle/testdata/poms/without-groupid-and-version.pom new file mode 100644 index 000000000000..e94fcbaaaca2 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/testdata/poms/without-groupid-and-version.pom @@ -0,0 +1,19 @@ + + + 4.0.0 + + + org.example + example-parent + 1.3 + + + example-core + + + + Apache License, Version 2.0 + + + diff --git a/pkg/fanal/analyzer/language/java/gradle/testdata/poms/without-licenses-and-deps.pom b/pkg/fanal/analyzer/language/java/gradle/testdata/poms/without-licenses-and-deps.pom new file mode 100644 index 000000000000..5c83a401353d --- /dev/null +++ b/pkg/fanal/analyzer/language/java/gradle/testdata/poms/without-licenses-and-deps.pom @@ -0,0 +1,10 @@ + + + 4.0.0 + + org.example + example-core + 1.0.0 + + diff --git a/pkg/fanal/analyzer/language/java/jar/jar.go b/pkg/fanal/analyzer/language/java/jar/jar.go new file mode 100644 index 000000000000..42d1004a2f44 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/jar/jar.go @@ -0,0 +1,98 @@ +package jar + +import ( + "context" + "io/fs" + "os" + "path/filepath" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/javadb" + "github.com/aquasecurity/trivy/pkg/parallel" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeJar, newJavaLibraryAnalyzer) +} + +const version = 1 + +var requiredExtensions = []string{ + ".jar", + ".war", + ".ear", + ".par", +} + +// javaLibraryAnalyzer analyzes jar/war/ear/par files +type javaLibraryAnalyzer struct { + parallel int +} + +func newJavaLibraryAnalyzer(options analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &javaLibraryAnalyzer{ + parallel: options.Parallel, + }, nil +} + +func (a *javaLibraryAnalyzer) PostAnalyze(ctx context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + // TODO: think about the sonatype API and "--offline" + client, err := javadb.NewClient() + if err != nil { + return nil, xerrors.Errorf("Unable to initialize the Java DB: %s", err) + } + defer func() { _ = client.Close() }() + + // Skip analyzing JAR files as the nil client means the Java DB was not downloaded successfully. + if client == nil { + return nil, nil + } + + // It will be called on each JAR file + onFile := func(path string, info fs.FileInfo, r xio.ReadSeekerAt) (*types.Application, error) { + p := jar.NewParser(client, jar.WithSize(info.Size()), jar.WithFilePath(path)) + return language.ParsePackage(types.Jar, path, r, p, input.Options.FileChecksum) + } + + var apps []types.Application + onResult := func(app *types.Application) error { + if app == nil { + return nil + } + apps = append(apps, *app) + return nil + } + + if err = parallel.WalkDir(ctx, input.FS, ".", a.parallel, onFile, onResult); err != nil { + return nil, xerrors.Errorf("walk dir error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a *javaLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + ext := filepath.Ext(filePath) + for _, required := range requiredExtensions { + if strings.EqualFold(ext, required) { + return true + } + } + return false +} + +func (a *javaLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypeJar +} + +func (a *javaLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/java/jar/jar_test.go b/pkg/fanal/analyzer/language/java/jar/jar_test.go new file mode 100644 index 000000000000..3988dc27daf5 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/jar/jar_test.go @@ -0,0 +1,198 @@ +package jar + +import ( + "context" + "github.com/google/go-containerregistry/pkg/name" + "github.com/stretchr/testify/require" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/javadb" + "github.com/aquasecurity/trivy/pkg/mapfs" + + _ "modernc.org/sqlite" +) + +const ( + defaultJavaDBRepository = "ghcr.io/aquasecurity/trivy-java-db" +) + +func Test_javaLibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + includeChecksum bool + want *analyzer.AnalysisResult + }{ + { + name: "happy path (WAR file)", + inputFile: "testdata/test.war", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Jar, + FilePath: "testdata/test.war", + Libraries: types.Packages{ + { + Name: "org.glassfish:javax.el", + FilePath: "testdata/test.war/WEB-INF/lib/javax.el-3.0.0.jar", + Version: "3.0.0", + }, + { + Name: "com.fasterxml.jackson.core:jackson-databind", + FilePath: "testdata/test.war/WEB-INF/lib/jackson-databind-2.9.10.6.jar", + Version: "2.9.10.6", + }, + { + Name: "com.fasterxml.jackson.core:jackson-annotations", + FilePath: "testdata/test.war/WEB-INF/lib/jackson-annotations-2.9.10.jar", + Version: "2.9.10", + }, + { + Name: "com.fasterxml.jackson.core:jackson-core", + FilePath: "testdata/test.war/WEB-INF/lib/jackson-core-2.9.10.jar", + Version: "2.9.10", + }, + { + Name: "org.slf4j:slf4j-api", + FilePath: "testdata/test.war/WEB-INF/lib/slf4j-api-1.7.30.jar", + Version: "1.7.30", + }, + { + Name: "com.cronutils:cron-utils", + FilePath: "testdata/test.war/WEB-INF/lib/cron-utils-9.1.2.jar", + Version: "9.1.2", + }, + { + Name: "org.apache.commons:commons-lang3", + FilePath: "testdata/test.war/WEB-INF/lib/commons-lang3-3.11.jar", + Version: "3.11", + }, + { + Name: "com.example:web-app", + FilePath: "testdata/test.war", + Version: "1.0-SNAPSHOT", + }, + }, + }, + }, + }, + }, + { + name: "happy path (PAR file)", + inputFile: "testdata/test.par", + includeChecksum: true, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Jar, + FilePath: "testdata/test.par", + Libraries: types.Packages{ + { + Name: "com.fasterxml.jackson.core:jackson-core", + FilePath: "testdata/test.par/lib/jackson-core-2.9.10.jar", + Version: "2.9.10", + Digest: "sha1:d40913470259cfba6dcc90f96bcaa9bcff1b72e0", + }, + }, + }, + }, + }, + }, + { + name: "happy path (package found in trivy-java-db by sha1)", + inputFile: "testdata/test.jar", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Jar, + FilePath: "testdata/test.jar", + Libraries: types.Packages{ + { + Name: "org.apache.tomcat.embed:tomcat-embed-websocket", + FilePath: "testdata/test.jar", + Version: "9.0.65", + }, + }, + }, + }, + }, + }, + { + name: "sad path", + inputFile: "testdata/test.txt", + want: &analyzer.AnalysisResult{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // init java-trivy-db with skip update + repo, err := name.NewTag(javadb.DefaultRepository) + require.NoError(t, err) + javadb.Init("testdata", repo, true, false, types.RegistryOptions{Insecure: false}) + + a := javaLibraryAnalyzer{} + ctx := context.Background() + + mfs := mapfs.New() + err = mfs.MkdirAll(filepath.Dir(tt.inputFile), os.ModePerm) + assert.NoError(t, err) + err = mfs.WriteFile(tt.inputFile, tt.inputFile) + assert.NoError(t, err) + + got, err := a.PostAnalyze(ctx, analyzer.PostAnalysisInput{ + FS: mfs, + Options: analyzer.AnalysisOptions{FileChecksum: tt.includeChecksum}, + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_javaLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "war", + filePath: "test/test.war", + want: true, + }, + { + name: "jar", + filePath: "test.jar", + want: true, + }, + { + name: "ear", + filePath: "a/b/c/d/test.ear", + want: true, + }, + { + name: "capital jar", + filePath: "a/b/c/d/test.JAR", + want: true, + }, + { + name: "zip", + filePath: "test.zip", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := javaLibraryAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/java/jar/testdata/java-db/metadata.json b/pkg/fanal/analyzer/language/java/jar/testdata/java-db/metadata.json new file mode 100644 index 000000000000..d810cb77937f --- /dev/null +++ b/pkg/fanal/analyzer/language/java/jar/testdata/java-db/metadata.json @@ -0,0 +1 @@ +{"Version":1,"NextUpdate":"0001-01-01T00:00:00Z","UpdatedAt":"0001-01-01T00:00:00Z","DownloadedAt":"0001-01-01T00:00:00Z"} diff --git a/pkg/fanal/analyzer/language/java/jar/testdata/java-db/trivy-java.db b/pkg/fanal/analyzer/language/java/jar/testdata/java-db/trivy-java.db new file mode 100644 index 000000000000..eb415104625d Binary files /dev/null and b/pkg/fanal/analyzer/language/java/jar/testdata/java-db/trivy-java.db differ diff --git a/pkg/fanal/analyzer/language/java/jar/testdata/test.jar b/pkg/fanal/analyzer/language/java/jar/testdata/test.jar new file mode 100644 index 000000000000..f2bd484e20d4 Binary files /dev/null and b/pkg/fanal/analyzer/language/java/jar/testdata/test.jar differ diff --git a/pkg/fanal/analyzer/language/java/jar/testdata/test.par b/pkg/fanal/analyzer/language/java/jar/testdata/test.par new file mode 100644 index 000000000000..a6d1e0122c2c Binary files /dev/null and b/pkg/fanal/analyzer/language/java/jar/testdata/test.par differ diff --git a/pkg/fanal/analyzer/language/java/jar/testdata/test.txt b/pkg/fanal/analyzer/language/java/jar/testdata/test.txt new file mode 100644 index 000000000000..9daeafb9864c --- /dev/null +++ b/pkg/fanal/analyzer/language/java/jar/testdata/test.txt @@ -0,0 +1 @@ +test diff --git a/pkg/fanal/analyzer/language/java/jar/testdata/test.war b/pkg/fanal/analyzer/language/java/jar/testdata/test.war new file mode 100644 index 000000000000..787d517174b7 Binary files /dev/null and b/pkg/fanal/analyzer/language/java/jar/testdata/test.war differ diff --git a/pkg/fanal/analyzer/language/java/pom/pom.go b/pkg/fanal/analyzer/language/java/pom/pom.go new file mode 100644 index 000000000000..d192d69dfc07 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/pom/pom.go @@ -0,0 +1,67 @@ +package pom + +import ( + "context" + "os" + "path/filepath" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/java/pom" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&pomAnalyzer{}) +} + +const version = 1 + +// pomAnalyzer analyzes pom.xml +type pomAnalyzer struct{} + +func (a pomAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + filePath := filepath.Join(input.Dir, input.FilePath) + p := pom.NewParser(filePath, pom.WithOffline(input.Options.Offline)) + res, err := language.Analyze(types.Pom, input.FilePath, input.Content, p) + if err != nil { + return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err) + } + + // Mark integration test pom files for `maven-invoker-plugin` as Dev to skip them by default. + if isIntegrationTestDir(filePath) { + for i := range res.Applications { + for j := range res.Applications[i].Libraries { + res.Applications[i].Libraries[j].Dev = true + } + } + } + + return res, nil +} + +func (a pomAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Base(filePath) == types.MavenPom +} + +func (a pomAnalyzer) Type() analyzer.Type { + return analyzer.TypePom +} + +func (a pomAnalyzer) Version() int { + return version +} + +// isIntegrationTestDir checks that pom file is in directory with integration tests of `maven-invoker-plugin` +// https://maven.apache.org/plugins/maven-invoker-plugin/usage.html +func isIntegrationTestDir(filePath string) bool { + dirs := strings.Split(filepath.ToSlash(filePath), "/") + // filepath pattern: `**/[src|target]/it/*/pom.xml` + if len(dirs) < 4 { + return false + } + return (dirs[len(dirs)-4] == "src" || dirs[len(dirs)-4] == "target") && dirs[len(dirs)-3] == "it" +} diff --git a/pkg/fanal/analyzer/language/java/pom/pom_test.go b/pkg/fanal/analyzer/language/java/pom/pom_test.go new file mode 100644 index 000000000000..6df1ea2274fc --- /dev/null +++ b/pkg/fanal/analyzer/language/java/pom/pom_test.go @@ -0,0 +1,215 @@ +package pom + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_pomAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputDir string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/happy/pom.xml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Pom, + FilePath: "testdata/happy/pom.xml", + Libraries: types.Packages{ + { + ID: "com.example:example-api:2.0.0", + Name: "com.example:example-api", + Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 28, + EndLine: 32, + }, + }, + }, + { + ID: "com.example:example:1.0.0", + Name: "com.example:example", + Version: "1.0.0", + Licenses: []string{"Apache-2.0"}, + DependsOn: []string{ + "com.example:example-api:2.0.0", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy dir path", + inputDir: "testdata/happy", + inputFile: "pom.xml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Pom, + FilePath: "pom.xml", + Libraries: types.Packages{ + { + ID: "com.example:example-api:2.0.0", + Name: "com.example:example-api", + Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 28, + EndLine: 32, + }, + }, + }, + { + ID: "com.example:example:1.0.0", + Name: "com.example:example", + Version: "1.0.0", + Licenses: []string{"Apache-2.0"}, + DependsOn: []string{ + "com.example:example-api:2.0.0", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path for maven-invoker-plugin integration tests", + inputFile: "testdata/mark-as-dev/src/it/example/pom.xml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Pom, + FilePath: "testdata/mark-as-dev/src/it/example/pom.xml", + Libraries: types.Packages{ + { + ID: "com.example:example-api:@example.version@", + Name: "com.example:example-api", + Version: "@example.version@", + Locations: []types.Location{ + { + StartLine: 28, + EndLine: 32, + }, + }, + Dev: true, + }, + { + ID: "com.example:example:1.0.0", + Name: "com.example:example", + Version: "1.0.0", + Licenses: []string{"Apache-2.0"}, + DependsOn: []string{ + "com.example:example-api:@example.version@", + }, + Dev: true, + }, + }, + }, + }, + }, + }, + { + name: "unsupported requirement", + inputFile: "testdata/requirements/pom.xml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Pom, + FilePath: "testdata/requirements/pom.xml", + Libraries: types.Packages{ + { + ID: "com.example:example:2.0.0", + Name: "com.example:example", + Version: "2.0.0", + Licenses: []string{"Apache-2.0"}, + }, + }, + }, + }, + }, + }, + { + name: "sad path", + inputFile: "testdata/broken/pom.xml", + wantErr: "xml decode error", + }, + { + name: "sad dir path", + inputDir: "testdata/broken", + inputFile: "pom.xml", + wantErr: "xml decode error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(filepath.Join(tt.inputDir, tt.inputFile)) + require.NoError(t, err) + defer f.Close() + + a := pomAnalyzer{} + got, err := a.Analyze(nil, analyzer.AnalysisInput{ + Dir: tt.inputDir, + FilePath: tt.inputFile, + Content: f, + Options: analyzer.AnalysisOptions{ + Offline: true, + }, + }) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_pomAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy", + filePath: "test/pom.xml", + want: true, + }, + { + name: "no extension", + filePath: "test/pom", + want: false, + }, + { + name: "json", + filePath: "test/pom.json", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := pomAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/java/pom/testdata/broken/pom.xml b/pkg/fanal/analyzer/language/java/pom/testdata/broken/pom.xml new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/pom/testdata/broken/pom.xml @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/java/pom/testdata/happy/pom.xml b/pkg/fanal/analyzer/language/java/pom/testdata/happy/pom.xml new file mode 100644 index 000000000000..aa5f1066295c --- /dev/null +++ b/pkg/fanal/analyzer/language/java/pom/testdata/happy/pom.xml @@ -0,0 +1,34 @@ + + 4.0.0 + + com.example + example + 1.0.0 + + example + Example + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + knqyf263 + https://github.com/knqyf263 + + + + + + com.example + example-api + 2.0.0 + + + diff --git a/pkg/fanal/analyzer/language/java/pom/testdata/mark-as-dev/src/it/example/pom.xml b/pkg/fanal/analyzer/language/java/pom/testdata/mark-as-dev/src/it/example/pom.xml new file mode 100644 index 000000000000..3717c917a1f2 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/pom/testdata/mark-as-dev/src/it/example/pom.xml @@ -0,0 +1,34 @@ + + 4.0.0 + + com.example + example + 1.0.0 + + example + Example + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + knqyf263 + https://github.com/knqyf263 + + + + + + com.example + example-api + @example.version@ + + + diff --git a/pkg/fanal/analyzer/language/java/pom/testdata/requirements/pom.xml b/pkg/fanal/analyzer/language/java/pom/testdata/requirements/pom.xml new file mode 100644 index 000000000000..dc6d6ebc2240 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/pom/testdata/requirements/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + com.example + example + 2.0.0 + + example + Example + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + org.example + example-api + (,1.0] + + + diff --git a/pkg/fanal/analyzer/language/nodejs/license/license.go b/pkg/fanal/analyzer/language/nodejs/license/license.go new file mode 100644 index 000000000000..7889a2b4e9a8 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/license.go @@ -0,0 +1,105 @@ +package license + +import ( + "errors" + "io" + "io/fs" + "path" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +type License struct { + parser *packagejson.Parser + classifierConfidenceLevel float64 +} + +func NewLicense(classifierConfidenceLevel float64) *License { + return &License{ + parser: packagejson.NewParser(), + classifierConfidenceLevel: classifierConfidenceLevel, + } +} + +func (l *License) Traverse(fsys fs.FS, root string) (map[string][]string, error) { + licenses := make(map[string][]string) + walkDirFunc := func(pkgJSONPath string, d fs.DirEntry, r io.Reader) error { + pkg, err := l.parser.Parse(r) + if err != nil { + return xerrors.Errorf("unable to parse %q: %w", pkgJSONPath, err) + } + + ok, licenseFileName := IsLicenseRefToFile(pkg.License) + if !ok { + licenses[pkg.ID] = []string{pkg.License} + return nil + } + + log.Logger.Debugf("License names are missing in %q, an attempt to find them in the %q file", pkgJSONPath, licenseFileName) + licenseFilePath := path.Join(path.Dir(pkgJSONPath), licenseFileName) + + if findings, err := classifyLicense(licenseFilePath, l.classifierConfidenceLevel, fsys); err != nil { + return xerrors.Errorf("unable to classify the license: %w", err) + } else if len(findings) > 0 { + // License found + licenses[pkg.ID] = findings.Names() + } else { + log.Logger.Debugf("The license file %q was not found or the license could not be classified", licenseFilePath) + } + return nil + } + if err := fsutils.WalkDir(fsys, root, fsutils.RequiredFile(types.NpmPkg), walkDirFunc); err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + + return licenses, nil +} + +// IsLicenseRefToFile The license field can refer to a file +// https://docs.npmjs.com/cli/v9/configuring-npm/package-json +func IsLicenseRefToFile(maybeLicense string) (bool, string) { + if maybeLicense == "" { + // trying to find at least the LICENSE file + return true, "LICENSE" + } + + var licenseFileName string + if strings.HasPrefix(maybeLicense, "LicenseRef-") { + // LicenseRef- + licenseFileName = strings.Split(maybeLicense, "-")[1] + } else if strings.HasPrefix(maybeLicense, "SEE LICENSE IN ") { + // SEE LICENSE IN + parts := strings.Split(maybeLicense, " ") + licenseFileName = parts[len(parts)-1] + } + + return licenseFileName != "", licenseFileName +} + +func classifyLicense(filePath string, classifierConfidenceLevel float64, fsys fs.FS) (types.LicenseFindings, error) { + f, err := fsys.Open(filePath) + if errors.Is(err, fs.ErrNotExist) { + return nil, nil + } else if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + l, err := licensing.Classify(filePath, f, classifierConfidenceLevel) + if err != nil { + return nil, xerrors.Errorf("license classify error: %w", err) + } + + if l == nil { + return nil, nil + } + + return l.Findings, nil +} diff --git a/pkg/fanal/analyzer/language/nodejs/license/license_test.go b/pkg/fanal/analyzer/language/nodejs/license/license_test.go new file mode 100644 index 000000000000..a282bc4bf6de --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/license_test.go @@ -0,0 +1,98 @@ +package license_test + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/license" + "github.com/aquasecurity/trivy/pkg/mapfs" +) + +func Test_ParseLicenses(t *testing.T) { + tests := []struct { + name string + dir string + want map[string][]string + wantErr string + }{ + { + name: "happy", + dir: filepath.Join("testdata", "happy"), + want: map[string][]string{ + "package-a@0.0.1": {"CC-BY-SA-4.0"}, + "package-b@0.0.1": {"MIT"}, + "package-c@0.0.1": {"BSD-3-Clause"}, + "package-d@0.0.1": {"BSD-3-Clause"}, + "package-e@0.0.1": {"(GPL-3.0 OR LGPL-3.0 OR MPL-1.1 OR SEE LICENSE IN LICENSE)"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsys := mapfs.New() + require.NoError(t, fsys.CopyFilesUnder(tt.dir)) + + l := license.NewLicense(0.9) + licenses, err := l.Traverse(fsys, ".") + if tt.wantErr != "" { + assert.ErrorContainsf(t, err, tt.wantErr, tt.name) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, licenses) + }) + } +} + +func Test_IsLicenseRefToFile(t *testing.T) { + tests := []struct { + name string + input string + wantOk bool + wantFileName string + }{ + { + name: "no ref to file", + input: "MIT", + }, + { + name: "empty input", + wantOk: true, + wantFileName: "LICENSE", + }, + { + name: "happy `SEE LICENSE IN`", + input: "SEE LICENSE IN LICENSE.md", + wantOk: true, + wantFileName: "LICENSE.md", + }, + { + name: "sad `SEE LICENSE IN`", + input: "SEE LICENSE IN ", + wantOk: false, + }, + { + name: "happy `LicenseRef-`", + input: "LicenseRef-LICENSE.txt", + wantOk: true, + wantFileName: "LICENSE.txt", + }, + { + name: "sad `LicenseRef-`", + input: "LicenseRef-", + wantOk: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ok, licenseFileName := license.IsLicenseRefToFile(tt.input) + assert.Equal(t, ok, tt.wantOk) + assert.Equal(t, licenseFileName, tt.wantFileName) + }) + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-default-license-file/LICENSE b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-default-license-file/LICENSE new file mode 100644 index 000000000000..45cf32fb3848 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-default-license-file/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright 2023 test +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-default-license-file/package.json b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-default-license-file/package.json new file mode 100644 index 000000000000..2475c8237a41 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-default-license-file/package.json @@ -0,0 +1,4 @@ +{ + "name": "package-d", + "version": "0.0.1" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-as-is/package.json b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-as-is/package.json new file mode 100644 index 000000000000..9ba9baf3c11a --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-as-is/package.json @@ -0,0 +1,5 @@ +{ + "name": "package-e", + "version": "0.0.1", + "license": "(GPL-3.0 OR LGPL-3.0 OR MPL-1.1 OR SEE LICENSE IN LICENSE)" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-name/package.json b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-name/package.json new file mode 100644 index 000000000000..2070fead3b74 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-name/package.json @@ -0,0 +1,5 @@ +{ + "name": "package-a", + "version": "0.0.1", + "license": "CC-BY-SA-4.0" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-ref/LICENSE.txt b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-ref/LICENSE.txt new file mode 100644 index 000000000000..edc19225c4cc --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-ref/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright 2023 test + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-ref/package.json b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-ref/package.json new file mode 100644 index 000000000000..20bb37bcf3d3 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-license-ref/package.json @@ -0,0 +1,5 @@ +{ + "name": "package-b", + "version": "0.0.1", + "license": "LicenseRef-LICENSE.txt" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-no-licenses/package.json b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-no-licenses/package.json new file mode 100644 index 000000000000..d597b4b31aeb --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-no-licenses/package.json @@ -0,0 +1,4 @@ +{ + "name": "package-f", + "version": "0.0.1" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-see-license/LICENSE.md b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-see-license/LICENSE.md new file mode 100644 index 000000000000..45cf32fb3848 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-see-license/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright 2023 test +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-see-license/package.json b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-see-license/package.json new file mode 100644 index 000000000000..0e425322da0b --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/license/testdata/happy/node_modules/package-see-license/package.json @@ -0,0 +1,5 @@ +{ + "name": "package-c", + "version": "0.0.1", + "license": "SEE LICENSE IN LICENSE.md" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/npm/npm.go b/pkg/fanal/analyzer/language/nodejs/npm/npm.go new file mode 100644 index 000000000000..c5dd5d26eed0 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/npm.go @@ -0,0 +1,156 @@ +package npm + +import ( + "context" + "errors" + "io" + "io/fs" + "os" + "path" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/npm" + "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + xio "github.com/aquasecurity/trivy/pkg/x/io" + xpath "github.com/aquasecurity/trivy/pkg/x/path" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeNpmPkgLock, newNpmLibraryAnalyzer) +} + +const ( + version = 1 +) + +type npmLibraryAnalyzer struct { + lockParser godeptypes.Parser + packageParser *packagejson.Parser +} + +func newNpmLibraryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &npmLibraryAnalyzer{ + lockParser: npm.NewParser(), + packageParser: packagejson.NewParser(), + }, nil +} + +func (a npmLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + // Parse package-lock.json + required := func(path string, _ fs.DirEntry) bool { + return filepath.Base(path) == types.NpmPkgLock + } + + var apps []types.Application + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { + // Find all licenses from package.json files under node_modules dirs + licenses, err := a.findLicenses(input.FS, filePath) + if err != nil { + log.Logger.Errorf("Unable to collect licenses: %s", err) + licenses = make(map[string]string) + } + + app, err := a.parseNpmPkgLock(input.FS, filePath) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil + } + + // Fill licenses + for i, lib := range app.Libraries { + if license, ok := licenses[lib.ID]; ok { + app.Libraries[i].Licenses = []string{license} + } + } + + apps = append(apps, *app) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("package-lock.json/package.json walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a npmLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + // Don't save package-lock.json from the `node_modules` directory to avoid duplication and mistakes. + if fileName == types.NpmPkgLock && !xpath.Contains(filePath, "node_modules") { + return true + } + + // Save package.json files only from the `node_modules` directory. + // Required to search for licenses. + if fileName == types.NpmPkg && xpath.Contains(filePath, "node_modules") { + return true + } + return false +} + +func (a npmLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypeNpmPkgLock +} + +func (a npmLibraryAnalyzer) Version() int { + return version +} + +func (a npmLibraryAnalyzer) parseNpmPkgLock(fsys fs.FS, filePath string) (*types.Application, error) { + f, err := fsys.Open(filePath) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer func() { _ = f.Close() }() + + file, ok := f.(xio.ReadSeekCloserAt) + if !ok { + return nil, xerrors.Errorf("type assertion error: %w", err) + } + + // parse package-lock.json file + return language.Parse(types.Npm, filePath, file, a.lockParser) +} + +func (a npmLibraryAnalyzer) findLicenses(fsys fs.FS, lockPath string) (map[string]string, error) { + dir := path.Dir(lockPath) + root := path.Join(dir, "node_modules") + if _, err := fs.Stat(fsys, root); errors.Is(err, fs.ErrNotExist) { + log.Logger.Infof(`To collect the license information of packages in %q, "npm install" needs to be performed beforehand`, lockPath) + return nil, nil + } + + // Parse package.json + required := func(path string, _ fs.DirEntry) bool { + return filepath.Base(path) == types.NpmPkg + } + + // Traverse node_modules dir and find licenses + // Note that fs.FS is always slashed regardless of the platform, + // and path.Join should be used rather than filepath.Join. + licenses := make(map[string]string) + err := fsutils.WalkDir(fsys, root, required, func(filePath string, d fs.DirEntry, r io.Reader) error { + pkg, err := a.packageParser.Parse(r) + if err != nil { + return xerrors.Errorf("unable to parse %q: %w", filePath, err) + } + + licenses[pkg.ID] = pkg.License + return nil + }) + if err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + return licenses, nil +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go b/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go new file mode 100644 index 000000000000..7635e0266729 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go @@ -0,0 +1,245 @@ +package npm + +import ( + "context" + "os" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +func TestMain(m *testing.M) { + _ = log.InitLogger(false, true) + os.Exit(m.Run()) +} + +func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + want *analyzer.AnalysisResult + }{ + { + name: "with node_modules", + dir: "testdata/happy", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Npm, + FilePath: "package-lock.json", + Libraries: types.Packages{ + { + ID: "@babel/parser@7.23.6", + Name: "@babel/parser", + Version: "7.23.6", + Indirect: true, + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 6, + EndLine: 10, + }, + }, + }, + { + ID: "ansi-colors@3.2.3", + Name: "ansi-colors", + Version: "3.2.3", + Dev: true, + Indirect: true, + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 16, + }, + }, + }, + { + ID: "array-flatten@1.1.1", + Name: "array-flatten", + Version: "1.1.1", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 17, + EndLine: 21, + }, + }, + }, + { + ID: "body-parser@1.18.3", + Name: "body-parser", + Version: "1.18.3", + Indirect: true, + DependsOn: []string{"debug@2.6.9"}, + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 44, + }, + }, + }, + { + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Indirect: true, + DependsOn: []string{"ms@2.0.0"}, + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 30, + EndLine: 37, + }, + { + StartLine: 53, + EndLine: 60, + }, + }, + }, + { + ID: "express@4.16.4", + Name: "express", + Version: "4.16.4", + Indirect: true, + DependsOn: []string{"debug@2.6.9"}, + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 45, + EndLine: 67, + }, + }, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Indirect: true, + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 38, + EndLine: 42, + }, + { + StartLine: 61, + EndLine: 65, + }, + }, + }, + { + ID: "ms@2.1.1", + Name: "ms", + Version: "2.1.1", + Indirect: true, + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 68, + EndLine: 72, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "without node_modules", + dir: "testdata/no-node_modules", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Npm, + FilePath: "package-lock.json", + Libraries: types.Packages{ + { + ID: "ms@2.1.1", + Name: "ms", + Version: "2.1.1", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 6, + EndLine: 10, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "sad path", + dir: "testdata/sad", + want: &analyzer.AnalysisResult{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newNpmLibraryAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + assert.NoError(t, err) + if len(got.Applications) > 0 { + sort.Sort(got.Applications[0].Libraries) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_nodePkgLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "lock file", + filePath: "npm/package-lock.json", + want: true, + }, + { + name: "package.json", + filePath: "npm/node_modules/ms/package.json", + want: true, + }, + { + name: "package.json with `/` in name", + filePath: "npm/node_modules/@babel/parser/package.json", + want: true, + }, + { + name: "sad path", + filePath: "npm/package.json", + want: false, + }, + { + name: "lock file in node_modules", + filePath: "npm/node_modules/html2canvas/package-lock.json", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := npmLibraryAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/@babel/parser/package.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/@babel/parser/package.json new file mode 100644 index 000000000000..5dc69d812815 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/@babel/parser/package.json @@ -0,0 +1,46 @@ +{ + "name": "@babel/parser", + "version": "7.23.6", + "description": "A JavaScript parser", + "author": "The Babel Team (https://babel.dev/team)", + "homepage": "https://babel.dev/docs/en/next/babel-parser", + "bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A+parser+%28babylon%29%22+is%3Aopen", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "keywords": [ + "babel", + "javascript", + "parser", + "tc39", + "ecmascript", + "@babel/parser" + ], + "repository": { + "type": "git", + "url": "https://github.com/babel/babel.git", + "directory": "packages/babel-parser" + }, + "main": "./lib/index.js", + "types": "./typings/babel-parser.d.ts", + "files": [ + "bin", + "lib", + "typings/babel-parser.d.ts", + "index.cjs" + ], + "engines": { + "node": ">=6.0.0" + }, + "devDependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/helper-check-duplicate-nodes": "^7.22.5", + "@babel/helper-fixtures": "^7.23.4", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "charcodes": "^0.2.0" + }, + "bin": "./bin/babel-parser.js", + "type": "commonjs" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/body-parser/node_modules/debug/package.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/body-parser/node_modules/debug/package.json new file mode 100644 index 000000000000..dc787ba76781 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/body-parser/node_modules/debug/package.json @@ -0,0 +1,49 @@ +{ + "name": "debug", + "version": "2.6.9", + "repository": { + "type": "git", + "url": "git://github.com/visionmedia/debug.git" + }, + "description": "small debugging utility", + "keywords": [ + "debug", + "log", + "debugger" + ], + "author": "TJ Holowaychuk ", + "contributors": [ + "Nathan Rajlich (http://n8.io)", + "Andrew Rhyne " + ], + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + }, + "devDependencies": { + "browserify": "9.0.3", + "chai": "^3.5.0", + "concurrently": "^3.1.0", + "coveralls": "^2.11.15", + "eslint": "^3.12.1", + "istanbul": "^0.4.5", + "karma": "^1.3.0", + "karma-chai": "^0.1.0", + "karma-mocha": "^1.3.0", + "karma-phantomjs-launcher": "^1.0.2", + "karma-sinon": "^1.0.5", + "mocha": "^3.2.0", + "mocha-lcov-reporter": "^1.2.0", + "rimraf": "^2.5.4", + "sinon": "^1.17.6", + "sinon-chai": "^2.8.0" + }, + "main": "./src/index.js", + "browser": "./src/browser.js", + "component": { + "scripts": { + "debug/index.js": "browser.js", + "debug/debug.js": "debug.js" + } + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/body-parser/node_modules/ms/package.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/body-parser/node_modules/ms/package.json new file mode 100644 index 000000000000..6a31c81fac43 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/body-parser/node_modules/ms/package.json @@ -0,0 +1,37 @@ +{ + "name": "ms", + "version": "2.0.0", + "description": "Tiny milisecond conversion utility", + "repository": "zeit/ms", + "main": "./index", + "files": [ + "index.js" + ], + "scripts": { + "precommit": "lint-staged", + "lint": "eslint lib/* bin/*", + "test": "mocha tests.js" + }, + "eslintConfig": { + "extends": "eslint:recommended", + "env": { + "node": true, + "es6": true + } + }, + "lint-staged": { + "*.js": [ + "npm run lint", + "prettier --single-quote --write", + "git add" + ] + }, + "license": "MIT", + "devDependencies": { + "eslint": "3.19.0", + "expect.js": "0.3.1", + "husky": "0.13.3", + "lint-staged": "3.4.1", + "mocha": "3.4.1" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/body-parser/package.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/body-parser/package.json new file mode 100644 index 000000000000..db78b735baee --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/body-parser/package.json @@ -0,0 +1,52 @@ +{ + "name": "body-parser", + "description": "Node.js body parsing middleware", + "version": "1.18.3", + "contributors": [ + "Douglas Christopher Wilson ", + "Jonathan Ong (http://jongleberry.com)" + ], + "license": "MIT", + "repository": "expressjs/body-parser", + "dependencies": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "devDependencies": { + "eslint": "4.19.1", + "eslint-config-standard": "11.0.0", + "eslint-plugin-import": "2.11.0", + "eslint-plugin-markdown": "1.0.0-beta.6", + "eslint-plugin-node": "6.0.1", + "eslint-plugin-promise": "3.7.0", + "eslint-plugin-standard": "3.1.0", + "istanbul": "0.4.5", + "methods": "1.1.2", + "mocha": "2.5.3", + "safe-buffer": "5.1.2", + "supertest": "1.1.0" + }, + "files": [ + "lib/", + "LICENSE", + "HISTORY.md", + "index.js" + ], + "engines": { + "node": ">= 0.8" + }, + "scripts": { + "lint": "eslint --plugin markdown --ext js,md .", + "test": "mocha --require test/support/env --reporter spec --check-leaks --bail test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/express/node_modules/debug/package.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/express/node_modules/debug/package.json new file mode 100644 index 000000000000..dc787ba76781 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/express/node_modules/debug/package.json @@ -0,0 +1,49 @@ +{ + "name": "debug", + "version": "2.6.9", + "repository": { + "type": "git", + "url": "git://github.com/visionmedia/debug.git" + }, + "description": "small debugging utility", + "keywords": [ + "debug", + "log", + "debugger" + ], + "author": "TJ Holowaychuk ", + "contributors": [ + "Nathan Rajlich (http://n8.io)", + "Andrew Rhyne " + ], + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + }, + "devDependencies": { + "browserify": "9.0.3", + "chai": "^3.5.0", + "concurrently": "^3.1.0", + "coveralls": "^2.11.15", + "eslint": "^3.12.1", + "istanbul": "^0.4.5", + "karma": "^1.3.0", + "karma-chai": "^0.1.0", + "karma-mocha": "^1.3.0", + "karma-phantomjs-launcher": "^1.0.2", + "karma-sinon": "^1.0.5", + "mocha": "^3.2.0", + "mocha-lcov-reporter": "^1.2.0", + "rimraf": "^2.5.4", + "sinon": "^1.17.6", + "sinon-chai": "^2.8.0" + }, + "main": "./src/index.js", + "browser": "./src/browser.js", + "component": { + "scripts": { + "debug/index.js": "browser.js", + "debug/debug.js": "debug.js" + } + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/express/node_modules/ms/package.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/express/node_modules/ms/package.json new file mode 100644 index 000000000000..6a31c81fac43 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/express/node_modules/ms/package.json @@ -0,0 +1,37 @@ +{ + "name": "ms", + "version": "2.0.0", + "description": "Tiny milisecond conversion utility", + "repository": "zeit/ms", + "main": "./index", + "files": [ + "index.js" + ], + "scripts": { + "precommit": "lint-staged", + "lint": "eslint lib/* bin/*", + "test": "mocha tests.js" + }, + "eslintConfig": { + "extends": "eslint:recommended", + "env": { + "node": true, + "es6": true + } + }, + "lint-staged": { + "*.js": [ + "npm run lint", + "prettier --single-quote --write", + "git add" + ] + }, + "license": "MIT", + "devDependencies": { + "eslint": "3.19.0", + "expect.js": "0.3.1", + "husky": "0.13.3", + "lint-staged": "3.4.1", + "mocha": "3.4.1" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/express/package.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/express/package.json new file mode 100644 index 000000000000..74196ad68e24 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/express/package.json @@ -0,0 +1,98 @@ +{ + "name": "express", + "description": "Fast, unopinionated, minimalist web framework", + "version": "4.16.4", + "author": "TJ Holowaychuk ", + "contributors": [ + "Aaron Heckmann ", + "Ciaran Jessup ", + "Douglas Christopher Wilson ", + "Guillermo Rauch ", + "Jonathan Ong ", + "Roman Shtylman ", + "Young Jae Sim " + ], + "license": "MIT", + "repository": "expressjs/express", + "homepage": "http://expressjs.com/", + "keywords": [ + "express", + "framework", + "sinatra", + "web", + "rest", + "restful", + "router", + "app", + "api" + ], + "dependencies": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "devDependencies": { + "after": "0.8.2", + "connect-redis": "3.4.0", + "cookie-parser": "~1.4.3", + "cookie-session": "1.3.2", + "ejs": "2.6.1", + "eslint": "2.13.1", + "express-session": "1.15.6", + "hbs": "4.0.1", + "istanbul": "0.4.5", + "marked": "0.5.1", + "method-override": "3.0.0", + "mocha": "5.2.0", + "morgan": "1.9.1", + "multiparty": "4.2.1", + "pbkdf2-password": "1.2.1", + "should": "13.2.3", + "supertest": "3.3.0", + "vhost": "~3.0.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "files": [ + "LICENSE", + "History.md", + "Readme.md", + "index.js", + "lib/" + ], + "scripts": { + "lint": "eslint .", + "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/", + "test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/", + "test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/ms/package.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/ms/package.json new file mode 100644 index 000000000000..fc28cb39d38a --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/node_modules/ms/package.json @@ -0,0 +1,37 @@ +{ + "name": "ms", + "version": "2.1.1", + "description": "Tiny millisecond conversion utility", + "repository": "zeit/ms", + "main": "./index", + "files": [ + "index.js" + ], + "scripts": { + "precommit": "lint-staged", + "lint": "eslint lib/* bin/*", + "test": "mocha tests.js" + }, + "eslintConfig": { + "extends": "eslint:recommended", + "env": { + "node": true, + "es6": true + } + }, + "lint-staged": { + "*.js": [ + "npm run lint", + "prettier --single-quote --write", + "git add" + ] + }, + "license": "MIT", + "devDependencies": { + "eslint": "4.12.1", + "expect.js": "0.3.1", + "husky": "0.14.3", + "lint-staged": "5.0.0", + "mocha": "4.0.1" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/package-lock.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/package-lock.json new file mode 100644 index 000000000000..1a7779fc21bb --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/happy/package-lock.json @@ -0,0 +1,74 @@ +{ + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/parser": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==" + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "requires": { + "debug": "2.6.9" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "requires": { + "debug": "2.6.9" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/no-node_modules/package-lock.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/no-node_modules/package-lock.json new file mode 100644 index 000000000000..78bd8378678f --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/no-node_modules/package-lock.json @@ -0,0 +1,12 @@ +{ + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/npm/testdata/sad/package-lock.json b/pkg/fanal/analyzer/language/nodejs/npm/testdata/sad/package-lock.json new file mode 100644 index 000000000000..80c4ed2eb6fa --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/npm/testdata/sad/package-lock.json @@ -0,0 +1 @@ +{broken} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go b/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go new file mode 100644 index 000000000000..aabc4da6b6a2 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go @@ -0,0 +1,60 @@ +package pkg + +import ( + "context" + "os" + "path/filepath" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +func init() { + analyzer.RegisterAnalyzer(&nodePkgLibraryAnalyzer{}) +} + +const ( + version = 1 + requiredFile = "package.json" +) + +type parser struct{} + +func (*parser) Parse(r xio.ReadSeekerAt) ([]godeptypes.Library, []godeptypes.Dependency, error) { + p := packagejson.NewParser() + pkg, err := p.Parse(r) + if err != nil { + return nil, nil, err + } + // skip packages without name/version + if pkg.Library.ID == "" { + return nil, nil, nil + } + // package.json may contain version range in `dependencies` fields + // e.g. "devDependencies": { "mocha": "^5.2.0", } + // so we get only information about project + return []godeptypes.Library{pkg.Library}, nil, nil +} + +type nodePkgLibraryAnalyzer struct{} + +// Analyze analyzes package.json for node packages +func (a nodePkgLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + return language.AnalyzePackage(types.NodePkg, input.FilePath, input.Content, &parser{}, input.Options.FileChecksum) +} + +func (a nodePkgLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return requiredFile == filepath.Base(filePath) +} + +func (a nodePkgLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypeNodePkg +} + +func (a nodePkgLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go b/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go new file mode 100644 index 000000000000..91a6bb6d708d --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go @@ -0,0 +1,127 @@ +package pkg + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_nodePkgLibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + includeChecksum bool + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/package.json", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.NodePkg, + FilePath: "testdata/package.json", + Libraries: types.Packages{ + { + ID: "lodash@5.0.0", + Name: "lodash", + Version: "5.0.0", + Licenses: []string{"MIT"}, + FilePath: "testdata/package.json", + }, + }, + }, + }, + }, + }, + { + name: "happy path with checksum", + inputFile: "testdata/package.json", + includeChecksum: true, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.NodePkg, + FilePath: "testdata/package.json", + Libraries: types.Packages{ + { + ID: "lodash@5.0.0", + Name: "lodash", + Version: "5.0.0", + Licenses: []string{"MIT"}, + FilePath: "testdata/package.json", + Digest: "sha1:901a7b55410321c4d35543506cff2a8613ef5aa2", + }, + }, + }, + }, + }, + }, + { + name: "happy path without name", + inputFile: "testdata/noname.json", + }, + { + name: "sad path", + inputFile: "testdata/sad.json", + wantErr: "JSON decode error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := nodePkgLibraryAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + Options: analyzer.AnalysisOptions{FileChecksum: tt.includeChecksum}, + }) + + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_nodePkgLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path", + filePath: "nodejs/package.json", + want: true, + }, + { + name: "sad path", + filePath: "nodejs/package-lock.json", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := nodePkgLibraryAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/pkg/testdata/noname.json b/pkg/fanal/analyzer/language/nodejs/pkg/testdata/noname.json new file mode 100644 index 000000000000..6efcbfe1b3e5 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pkg/testdata/noname.json @@ -0,0 +1,22 @@ +{ + "version": "5.0.0", + "license": "MIT", + "private": true, + "main": "lodash.js", + "engines": { + "node": ">=4.0.0" + }, + "sideEffects": false, + "scripts": { + "style": "eslint *.js .internal/**/*.js", + "test": "mocha -r esm test/*.test.js", + "validate": "npm run style && npm run test" + }, + "devDependencies": { + "mocha": "^5.2.0", + "eslint": "^7.16.0", + "eslint-plugin-import": "^2.22.1", + "lodash": "4.17.20", + "esm": "^3.2.25" + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/pkg/testdata/package.json b/pkg/fanal/analyzer/language/nodejs/pkg/testdata/package.json new file mode 100644 index 000000000000..26d216257252 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pkg/testdata/package.json @@ -0,0 +1,23 @@ +{ + "name": "lodash", + "version": "5.0.0", + "license": "MIT", + "private": true, + "main": "lodash.js", + "engines": { + "node": ">=4.0.0" + }, + "sideEffects": false, + "scripts": { + "style": "eslint *.js .internal/**/*.js", + "test": "mocha -r esm test/*.test.js", + "validate": "npm run style && npm run test" + }, + "devDependencies": { + "mocha": "^5.2.0", + "eslint": "^7.16.0", + "eslint-plugin-import": "^2.22.1", + "lodash": "4.17.20", + "esm": "^3.2.25" + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/pkg/testdata/sad.json b/pkg/fanal/analyzer/language/nodejs/pkg/testdata/sad.json new file mode 100644 index 000000000000..81750b96f9d8 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pkg/testdata/sad.json @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go new file mode 100644 index 000000000000..594e5e02a82e --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go @@ -0,0 +1,46 @@ +package pnpm + +import ( + "context" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/pnpm" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +func init() { + analyzer.RegisterAnalyzer(&pnpmLibraryAnalyzer{}) +} + +const version = 1 + +var requiredFiles = []string{types.PnpmLock} + +type pnpmLibraryAnalyzer struct{} + +func (a pnpmLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + res, err := language.Analyze(types.Pnpm, input.FilePath, input.Content, pnpm.NewParser()) + if err != nil { + return nil, xerrors.Errorf("unable to parse %s: %w", input.FilePath, err) + } + return res, nil +} + +func (a pnpmLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + return utils.StringInSlice(fileName, requiredFiles) +} + +func (a pnpmLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypePnpm +} + +func (a pnpmLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go new file mode 100644 index 000000000000..3351de538b3b --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go @@ -0,0 +1,65 @@ +package pnpm + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_pnpmPkgLibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/pnpm-lock.yaml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Pnpm, + FilePath: "testdata/pnpm-lock.yaml", + Libraries: types.Packages{ + { + ID: "lodash@4.17.21", + Name: "lodash", + Version: "4.17.21", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := pnpmLibraryAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/pnpm-lock.yaml b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/pnpm-lock.yaml new file mode 100644 index 000000000000..a319ff8e7d4f --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/pnpm-lock.yaml @@ -0,0 +1,13 @@ +lockfileVersion: 5.4 + +specifiers: + lodash: ^4.17.21 + +dependencies: + lodash: 4.17.21 + +packages: + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/alias/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/alias/package.json new file mode 100644 index 000000000000..caa6e0f3f637 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/alias/package.json @@ -0,0 +1,14 @@ +{ + "name": "test", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "devDependencies": { + "foo-json": "npm:@types/jsonstream@0.8.33", + "foo-uuid": "npm:@types/uuid" + }, + "dependencies": { + "foo-debug": "npm:debug@^4.3", + "foo-ms": "npm:ms" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/alias/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/alias/yarn.lock new file mode 100644 index 000000000000..cb68abb657f9 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/alias/yarn.lock @@ -0,0 +1,44 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@*": + version "20.10.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2" + integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== + dependencies: + undici-types "~5.26.4" + +"foo-debug@npm:debug@^4.3": + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +"foo-json@npm:@types/jsonstream@0.8.33": + version "0.8.33" + resolved "https://registry.yarnpkg.com/@types/jsonstream/-/jsonstream-0.8.33.tgz#7d37a16a78cf68a67858110dc1767023436fca23" + integrity sha512-yhg1SNOgJ8y2nOkvAQ1zZ1Z2xibxgFs7984+EeBPuWgo/TbuYo79+rj2wUVch3KF4GhhcwAi/AlJcehmLCXb3g== + dependencies: + "@types/node" "*" + +"foo-ms@npm:ms": + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +"foo-uuid@npm:@types/uuid": + version "9.0.7" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.7.tgz#b14cebc75455eeeb160d5fe23c2fcc0c64f724d8" + integrity sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/happy/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/happy/package.json new file mode 100644 index 000000000000..ce1af9d253e8 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/happy/package.json @@ -0,0 +1,13 @@ +{ + "name": "90", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "js-tokens": "^2.0.0", + "scheduler": "0.13.6" + }, + "devDependencies": { + "prop-types": "15.7.2" + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/happy/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/happy/yarn.lock new file mode 100644 index 000000000000..ae9f0ec16b89 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/happy/yarn.lock @@ -0,0 +1,47 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +js-tokens@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5" + integrity sha512-kk4RjoztHFeAlCNPdTr/uF04oN5z53L18LJfADBQ7YHkO3iR2rP2ZwOH0n33r2FRw0psRmjTDEkOWPE//zmujg== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +prop-types@15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +scheduler@0.13.6: + version "0.13.6" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" + integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/c/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/c/package.json new file mode 100644 index 000000000000..7fc842765891 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/c/package.json @@ -0,0 +1,7 @@ +{ + "name": "c", + "version": "0.0.0", + "dependencies": { + "is-number": "^7.0.0" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/package.json new file mode 100644 index 000000000000..ce55f5435751 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/package.json @@ -0,0 +1,13 @@ +{ + "name": "yarn-workspace-test", + "version": "1.0.0", + "packageManager": "yarn@3.4.1", + "private": true, + "workspaces": [ + "packages/**", + "c" + ], + "devDependencies": { + "prettier": "^2.8.8" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package1/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package1/package.json new file mode 100644 index 000000000000..0983a7733942 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package1/package.json @@ -0,0 +1,8 @@ +{ + "name": "package1", + "version": "0.0.0", + "private": true, + "dependencies": { + "scheduler": "^0.23.0" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package2/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package2/package.json new file mode 100644 index 000000000000..29b60ba87602 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package2/package.json @@ -0,0 +1,9 @@ +{ + "name": "package2", + "private": true, + "version": "0.0.0", + "type": "module", + "dependencies": { + "is-odd": "^3.0.1" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/utils/util1/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/utils/util1/package.json new file mode 100644 index 000000000000..7bcf16729401 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/utils/util1/package.json @@ -0,0 +1,10 @@ +{ + "name": "util1", + "version": "0.0.0", + "dependencies": { + "js-tokens": "^8.0.1" + }, + "devDependencies": { + "prop-types": "^15.8.1" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/yarn.lock new file mode 100644 index 000000000000..ff1ff18ef57c --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/yarn.lock @@ -0,0 +1,138 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"c@workspace:c": + version: 0.0.0-use.local + resolution: "c@workspace:c" + dependencies: + is-number: ^7.0.0 + languageName: unknown + linkType: soft + +"is-number@npm:^6.0.0": + version: 6.0.0 + resolution: "is-number@npm:6.0.0" + checksum: f73bfced022128b5684bf77e0266a74e5222522bbc40f81cc1e949170c774a3c14b59a208be025d2d97a9c6b79c7c45fe351ab1c2c780872464fdedde0ae067a + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 456ac6f8e0f3111ed34668a624e45315201dff921e5ac181f8ec24923b99e9f32ca1a194912dc79d539c97d33dba17dc635202ff0b2cf98326f608323276d27a + languageName: node + linkType: hard + +"is-odd@npm:^3.0.1": + version: 3.0.1 + resolution: "is-odd@npm:3.0.1" + dependencies: + is-number: ^6.0.0 + checksum: 4e2b20764dd2296bafe44823d127f281c7039b37d2feaf5caffc1bf162502ef2920bcd4ad171490f371d3f15f52232c763a8ffc0b3633d4c83385fe20f3493af + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 8a95213a5a77deb6cbe94d86340e8d9ace2b93bc367790b260101d2f36a2eaf4e4e22d9fa9cf459b38af3a32fb4190e638024cf82ec95ef708680e405ea7cc78 + languageName: node + linkType: hard + +"js-tokens@npm:^8.0.1": + version: 8.0.1 + resolution: "js-tokens@npm:8.0.1" + checksum: fb7bcd476c5b902ffb766382ca85aecb86ec66a607e419377026293b5877774e465f6cbe4229c8d85db3776ccc91c3aee518a0e04a005e260e57353f6f9278a8 + languageName: node + linkType: hard + +"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: ^3.0.0 || ^4.0.0 + bin: + loose-envify: cli.js + checksum: 6517e24e0cad87ec9888f500c5b5947032cdfe6ef65e1c1936a0c48a524b81e65542c9c3edc91c97d5bddc806ee2a985dbc79be89215d613b1de5db6d1cfe6f4 + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f + languageName: node + linkType: hard + +"package1@workspace:packages/package1": + version: 0.0.0-use.local + resolution: "package1@workspace:packages/package1" + dependencies: + scheduler: ^0.23.0 + languageName: unknown + linkType: soft + +"package2@workspace:packages/package2": + version: 0.0.0-use.local + resolution: "package2@workspace:packages/package2" + dependencies: + is-odd: ^3.0.1 + languageName: unknown + linkType: soft + +"prettier@npm:^2.8.8": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: b49e409431bf129dd89238d64299ba80717b57ff5a6d1c1a8b1a28b590d998a34e083fa13573bc732bb8d2305becb4c9a4407f8486c81fa7d55100eb08263cf8 + languageName: node + linkType: hard + +"prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: ^1.4.0 + object-assign: ^4.1.1 + react-is: ^16.13.1 + checksum: c056d3f1c057cb7ff8344c645450e14f088a915d078dcda795041765047fa080d38e5d626560ccaac94a4e16e3aa15f3557c1a9a8d1174530955e992c675e459 + languageName: node + linkType: hard + +"react-is@npm:^16.13.1": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f + languageName: node + linkType: hard + +"scheduler@npm:^0.23.0": + version: 0.23.0 + resolution: "scheduler@npm:0.23.0" + dependencies: + loose-envify: ^1.1.0 + checksum: d79192eeaa12abef860c195ea45d37cbf2bbf5f66e3c4dcd16f54a7da53b17788a70d109ee3d3dde1a0fd50e6a8fc171f4300356c5aee4fc0171de526bf35f8a + languageName: node + linkType: hard + +"util1@workspace:packages/utils/util1": + version: 0.0.0-use.local + resolution: "util1@workspace:packages/utils/util1" + dependencies: + js-tokens: ^8.0.1 + prop-types: ^15.8.1 + languageName: unknown + linkType: soft + +"yarn-workspace-test@workspace:.": + version: 0.0.0-use.local + resolution: "yarn-workspace-test@workspace:." + dependencies: + prettier: ^2.8.8 + languageName: unknown + linkType: soft diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/no-packagejson/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/no-packagejson/yarn.lock new file mode 100644 index 000000000000..ae9f0ec16b89 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/no-packagejson/yarn.lock @@ -0,0 +1,47 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +js-tokens@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5" + integrity sha512-kk4RjoztHFeAlCNPdTr/uF04oN5z53L18LJfADBQ7YHkO3iR2rP2ZwOH0n33r2FRw0psRmjTDEkOWPE//zmujg== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +prop-types@15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +scheduler@0.13.6: + version "0.13.6" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" + integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/project-with-workspace-in-subdir/foo/bar/generators/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/project-with-workspace-in-subdir/foo/bar/generators/package.json new file mode 100644 index 000000000000..1a1f420b0888 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/project-with-workspace-in-subdir/foo/bar/generators/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/bar-generators", + "version": "0.0.1", + "dependencies": { + "hoek": "6.1.3" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/project-with-workspace-in-subdir/foo/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/project-with-workspace-in-subdir/foo/package.json new file mode 100644 index 000000000000..bc453af12531 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/project-with-workspace-in-subdir/foo/package.json @@ -0,0 +1,10 @@ +{ + "name": "@test/foo", + "version": "1.0.0", + "repository": "ssh://git@test.com/test.git/foo", + "workspaces": [ + "bar/*" + ], + "main": "index.js", + "license": "MIT" +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/project-with-workspace-in-subdir/foo/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/project-with-workspace-in-subdir/foo/yarn.lock new file mode 100644 index 000000000000..0074e5c84d84 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/project-with-workspace-in-subdir/foo/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +hoek@6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c" + integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ== diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/unsupported_protocol/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/unsupported_protocol/yarn.lock new file mode 100644 index 000000000000..b66e215e825e --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/unsupported_protocol/yarn.lock @@ -0,0 +1,2 @@ + asap@unsupported:~2.0.6: + version "2.0.6" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/wrong-packagejson/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/wrong-packagejson/package.json new file mode 100644 index 000000000000..81750b96f9d8 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/wrong-packagejson/package.json @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/wrong-packagejson/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/wrong-packagejson/yarn.lock new file mode 100644 index 000000000000..a56ac5d16517 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/wrong-packagejson/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +js-tokens@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5" + integrity sha512-kk4RjoztHFeAlCNPdTr/uF04oN5z53L18LJfADBQ7YHkO3iR2rP2ZwOH0n33r2FRw0psRmjTDEkOWPE//zmujg== \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@babel/parser/LICENSE b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@babel/parser/LICENSE new file mode 100644 index 000000000000..d4c7fc583804 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@babel/parser/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2012-2014 by various contributors (see AUTHORS) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@babel/parser/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@babel/parser/package.json new file mode 100644 index 000000000000..627a683bd652 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@babel/parser/package.json @@ -0,0 +1,46 @@ +{ + "name": "@babel/parser", + "version": "7.22.7", + "description": "A JavaScript parser", + "author": "The Babel Team (https://babel.dev/team)", + "homepage": "https://babel.dev/docs/en/next/babel-parser", + "bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A+parser+%28babylon%29%22+is%3Aopen", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "keywords": [ + "babel", + "javascript", + "parser", + "tc39", + "ecmascript", + "@babel/parser" + ], + "repository": { + "type": "git", + "url": "https://github.com/babel/babel.git", + "directory": "packages/babel-parser" + }, + "main": "./lib/index.js", + "types": "./typings/babel-parser.d.ts", + "files": [ + "bin", + "lib", + "typings/babel-parser.d.ts", + "index.cjs" + ], + "engines": { + "node": ">=6.0.0" + }, + "devDependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/helper-check-duplicate-nodes": "^7.22.5", + "@babel/helper-fixtures": "^7.22.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "charcodes": "^0.2.0" + }, + "bin": "./bin/babel-parser.js", + "type": "commonjs" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@vue/compiler-sfc/LICENSE b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@vue/compiler-sfc/LICENSE new file mode 100644 index 000000000000..b65dd9e62e21 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@vue/compiler-sfc/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@vue/compiler-sfc/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@vue/compiler-sfc/package.json new file mode 100644 index 000000000000..e02b3934fd82 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/@vue/compiler-sfc/package.json @@ -0,0 +1,34 @@ +{ + "name": "@vue/compiler-sfc", + "version": "2.7.14", + "description": "compiler-sfc for Vue 2", + "main": "dist/compiler-sfc.js", + "types": "dist/compiler-sfc.d.ts", + "files": [ + "dist" + ], + "dependencies": { + "@babel/parser": "^7.18.4", + "postcss": "^8.4.14", + "source-map": "^0.6.1" + }, + "devDependencies": { + "@babel/types": "^7.19.4", + "@types/estree": "^0.0.48", + "@types/hash-sum": "^1.0.0", + "@types/lru-cache": "^5.1.1", + "@vue/consolidate": "^0.17.3", + "de-indent": "^1.0.2", + "estree-walker": "^2.0.2", + "hash-sum": "^2.0.0", + "less": "^4.1.3", + "lru-cache": "^5.1.1", + "magic-string": "^0.25.9", + "merge-source-map": "^1.1.0", + "postcss-modules": "^4.3.1", + "postcss-selector-parser": "^6.0.10", + "pug": "^3.0.2", + "sass": "^1.52.3", + "stylus": "^0.58.1" + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/nanoid/LICENSE b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/nanoid/LICENSE new file mode 100644 index 000000000000..37f56aa49f8e --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/nanoid/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright 2017 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/nanoid/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/nanoid/package.json new file mode 100644 index 000000000000..19d7d7a5e9f1 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/nanoid/package.json @@ -0,0 +1,66 @@ +{ + "name": "nanoid", + "version": "3.3.6", + "description": "A tiny (116 bytes), secure URL-friendly unique string ID generator", + "keywords": [ + "uuid", + "random", + "id", + "url" + ], + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "author": "Andrey Sitnik ", + "license": "MIT", + "repository": "ai/nanoid", + "browser": { + "./index.js": "./index.browser.js", + "./async/index.js": "./async/index.browser.js", + "./async/index.cjs": "./async/index.browser.cjs", + "./index.cjs": "./index.browser.cjs" + }, + "react-native": "index.js", + "bin": "./bin/nanoid.cjs", + "sideEffects": false, + "types": "./index.d.ts", + "type": "module", + "main": "index.cjs", + "module": "index.js", + "exports": { + ".": { + "types": "./index.d.ts", + "browser": "./index.browser.js", + "require": "./index.cjs", + "import": "./index.js", + "default": "./index.js" + }, + "./index.d.ts": "./index.d.ts", + "./package.json": "./package.json", + "./async/package.json": "./async/package.json", + "./async": { + "browser": "./async/index.browser.js", + "require": "./async/index.cjs", + "import": "./async/index.js", + "default": "./async/index.js" + }, + "./non-secure/package.json": "./non-secure/package.json", + "./non-secure": { + "require": "./non-secure/index.cjs", + "import": "./non-secure/index.js", + "default": "./non-secure/index.js" + }, + "./url-alphabet/package.json": "./url-alphabet/package.json", + "./url-alphabet": { + "require": "./url-alphabet/index.cjs", + "import": "./url-alphabet/index.js", + "default": "./url-alphabet/index.js" + } + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/picocolors/LICENSE b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/picocolors/LICENSE new file mode 100644 index 000000000000..496098c6e895 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/picocolors/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2021 Alexey Raspopov, Kostiantyn Denysov, Anton Verinov + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/picocolors/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/picocolors/package.json new file mode 100644 index 000000000000..85a12d523497 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/picocolors/package.json @@ -0,0 +1,25 @@ +{ + "name": "picocolors", + "version": "1.0.0", + "main": "./picocolors.js", + "types": "./picocolors.d.ts", + "browser": { + "./picocolors.js": "./picocolors.browser.js" + }, + "sideEffects": false, + "description": "The tiniest and the fastest library for terminal output formatting with ANSI colors", + "files": [ + "picocolors.*", + "types.ts" + ], + "keywords": [ + "terminal", + "colors", + "formatting", + "cli", + "console" + ], + "author": "Alexey Raspopov", + "repository": "alexeyraspopov/picocolors", + "license": "ISC" +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/postcss/LICENSE b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/postcss/LICENSE new file mode 100644 index 000000000000..da057b4562a8 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/postcss/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright 2013 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/postcss/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/postcss/package.json new file mode 100755 index 000000000000..cfee014cefa3 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/postcss/package.json @@ -0,0 +1,88 @@ +{ + "name": "postcss", + "version": "8.4.27", + "description": "Tool for transforming styles with JS plugins", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "exports": { + ".": { + "require": "./lib/postcss.js", + "import": "./lib/postcss.mjs" + }, + "./lib/at-rule": "./lib/at-rule.js", + "./lib/comment": "./lib/comment.js", + "./lib/container": "./lib/container.js", + "./lib/css-syntax-error": "./lib/css-syntax-error.js", + "./lib/declaration": "./lib/declaration.js", + "./lib/fromJSON": "./lib/fromJSON.js", + "./lib/input": "./lib/input.js", + "./lib/lazy-result": "./lib/lazy-result.js", + "./lib/no-work-result": "./lib/no-work-result.js", + "./lib/list": "./lib/list.js", + "./lib/map-generator": "./lib/map-generator.js", + "./lib/node": "./lib/node.js", + "./lib/parse": "./lib/parse.js", + "./lib/parser": "./lib/parser.js", + "./lib/postcss": "./lib/postcss.js", + "./lib/previous-map": "./lib/previous-map.js", + "./lib/processor": "./lib/processor.js", + "./lib/result": "./lib/result.js", + "./lib/root": "./lib/root.js", + "./lib/rule": "./lib/rule.js", + "./lib/stringifier": "./lib/stringifier.js", + "./lib/stringify": "./lib/stringify.js", + "./lib/symbols": "./lib/symbols.js", + "./lib/terminal-highlight": "./lib/terminal-highlight.js", + "./lib/tokenize": "./lib/tokenize.js", + "./lib/warn-once": "./lib/warn-once.js", + "./lib/warning": "./lib/warning.js", + "./package.json": "./package.json" + }, + "main": "./lib/postcss.js", + "types": "./lib/postcss.d.ts", + "keywords": [ + "css", + "postcss", + "rework", + "preprocessor", + "parser", + "source map", + "transform", + "manipulation", + "transpiler" + ], + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "author": "Andrey Sitnik ", + "license": "MIT", + "homepage": "https://postcss.org/", + "repository": "postcss/postcss", + "bugs": { + "url": "https://github.com/postcss/postcss/issues" + }, + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "browser": { + "./lib/terminal-highlight": false, + "source-map-js": false, + "path": false, + "url": false, + "fs": false + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/source-map-js/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/source-map-js/package.json new file mode 100644 index 000000000000..501fafe16159 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/source-map-js/package.json @@ -0,0 +1,71 @@ +{ + "name": "source-map-js", + "description": "Generates and consumes source maps", + "version": "1.0.2", + "homepage": "https://github.com/7rulnik/source-map-js", + "author": "Valentin 7rulnik Semirulnik ", + "contributors": [ + "Nick Fitzgerald ", + "Tobias Koppers ", + "Duncan Beevers ", + "Stephen Crane ", + "Ryan Seddon ", + "Miles Elam ", + "Mihai Bazon ", + "Michael Ficarra ", + "Todd Wolfson ", + "Alexander Solovyov ", + "Felix Gnass ", + "Conrad Irwin ", + "usrbincc ", + "David Glasser ", + "Chase Douglas ", + "Evan Wallace ", + "Heather Arthur ", + "Hugh Kennedy ", + "David Glasser ", + "Simon Lydell ", + "Jmeas Smith ", + "Michael Z Goddard ", + "azu ", + "John Gozde ", + "Adam Kirkton ", + "Chris Montgomery ", + "J. Ryan Stinnett ", + "Jack Herrington ", + "Chris Truter ", + "Daniel Espeset ", + "Jamie Wong ", + "Eddy Bruël ", + "Hawken Rives ", + "Gilad Peleg ", + "djchie ", + "Gary Ye ", + "Nicolas LaleveÌe " + ], + "repository": "7rulnik/source-map-js", + "main": "./source-map.js", + "files": [ + "source-map.js", + "source-map.d.ts", + "lib/" + ], + "engines": { + "node": ">=0.10.0" + }, + "license": "BSD-3-Clause", + "scripts": { + "test": "npm run build && node test/run-tests.js", + "build": "webpack --color", + "toc": "doctoc --title '## Table of Contents' README.md && doctoc --title '## Table of Contents' CONTRIBUTING.md" + }, + "devDependencies": { + "clean-publish": "^3.1.0", + "doctoc": "^0.15.0", + "webpack": "^1.12.0" + }, + "clean-publish": { + "cleanDocs": true + }, + "typings": "source-map.d.ts" +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/source-map/LICENSE b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/source-map/LICENSE new file mode 100644 index 000000000000..ed1b7cf27e97 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/source-map/LICENSE @@ -0,0 +1,28 @@ + +Copyright (c) 2009-2011, Mozilla Foundation and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the names of the Mozilla Foundation nor the names of project + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/source-map/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/source-map/package.json new file mode 100644 index 000000000000..24663417e7ac --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/node_modules/source-map/package.json @@ -0,0 +1,73 @@ +{ + "name": "source-map", + "description": "Generates and consumes source maps", + "version": "0.6.1", + "homepage": "https://github.com/mozilla/source-map", + "author": "Nick Fitzgerald ", + "contributors": [ + "Tobias Koppers ", + "Duncan Beevers ", + "Stephen Crane ", + "Ryan Seddon ", + "Miles Elam ", + "Mihai Bazon ", + "Michael Ficarra ", + "Todd Wolfson ", + "Alexander Solovyov ", + "Felix Gnass ", + "Conrad Irwin ", + "usrbincc ", + "David Glasser ", + "Chase Douglas ", + "Evan Wallace ", + "Heather Arthur ", + "Hugh Kennedy ", + "David Glasser ", + "Simon Lydell ", + "Jmeas Smith ", + "Michael Z Goddard ", + "azu ", + "John Gozde ", + "Adam Kirkton ", + "Chris Montgomery ", + "J. Ryan Stinnett ", + "Jack Herrington ", + "Chris Truter ", + "Daniel Espeset ", + "Jamie Wong ", + "Eddy Bruël ", + "Hawken Rives ", + "Gilad Peleg ", + "djchie ", + "Gary Ye ", + "Nicolas LaleveÌe " + ], + "repository": { + "type": "git", + "url": "http://github.com/mozilla/source-map.git" + }, + "main": "./source-map.js", + "files": [ + "source-map.js", + "source-map.d.ts", + "lib/", + "dist/source-map.debug.js", + "dist/source-map.js", + "dist/source-map.min.js", + "dist/source-map.min.js.map" + ], + "engines": { + "node": ">=0.10.0" + }, + "license": "BSD-3-Clause", + "scripts": { + "test": "npm run build && node test/run-tests.js", + "build": "webpack --color", + "toc": "doctoc --title '## Table of Contents' README.md && doctoc --title '## Table of Contents' CONTRIBUTING.md" + }, + "devDependencies": { + "doctoc": "^0.15.0", + "webpack": "^1.12.0" + }, + "typings": "source-map" +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/package.json new file mode 100644 index 000000000000..395444cb6b18 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/package.json @@ -0,0 +1,6 @@ +{ + "packageManager": "yarn@1.22.19", + "dependencies": { + "@vue/compiler-sfc": "2.7.14" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/yarn.lock new file mode 100644 index 000000000000..59692dac7cad --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-classic-licenses/yarn.lock @@ -0,0 +1,46 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/parser@^7.18.4": + version "7.22.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" + integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== + +"@vue/compiler-sfc@2.7.14": + version "2.7.14" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz#3446fd2fbb670d709277fc3ffa88efc5e10284fd" + integrity sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA== + dependencies: + "@babel/parser" "^7.18.4" + postcss "^8.4.14" + source-map "^0.6.1" + +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +postcss@^8.4.14: + version "8.4.27" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057" + integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/.yarn/cache/is-number-npm-6.0.0-30881e83e6-f73bfced02.zip b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/.yarn/cache/is-number-npm-6.0.0-30881e83e6-f73bfced02.zip new file mode 100644 index 000000000000..3f2dc0150d3b Binary files /dev/null and b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/.yarn/cache/is-number-npm-6.0.0-30881e83e6-f73bfced02.zip differ diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/.yarn/cache/is-odd-npm-3.0.1-93c3c3f41b-4e2b20764d.zip b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/.yarn/cache/is-odd-npm-3.0.1-93c3c3f41b-4e2b20764d.zip new file mode 100644 index 000000000000..14718cba0519 Binary files /dev/null and b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/.yarn/cache/is-odd-npm-3.0.1-93c3c3f41b-4e2b20764d.zip differ diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/.yarn/unplugged/is-callable-npm-1.2.7-808a303e61/node_modules/is-callable/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/.yarn/unplugged/is-callable-npm-1.2.7-808a303e61/node_modules/is-callable/package.json new file mode 100644 index 000000000000..3255bf0efceb --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/.yarn/unplugged/is-callable-npm-1.2.7-808a303e61/node_modules/is-callable/package.json @@ -0,0 +1,25 @@ +{ + "name": "is-callable", + "version": "1.2.7", + "license": "MIT", + "devDependencies": { + "@ljharb/eslint-config": "^21.0.0", + "aud": "^2.0.0", + "auto-changelog": "^2.4.0", + "available-typed-arrays": "^1.0.5", + "eclint": "^2.8.1", + "es-value-fixtures": "^1.4.2", + "eslint": "=8.8.0", + "for-each": "^0.3.3", + "has-tostringtag": "^1.0.0", + "make-arrow-function": "^1.2.0", + "make-async-function": "^1.0.0", + "make-generator-function": "^2.0.0", + "npmignore": "^0.3.0", + "nyc": "^10.3.2", + "object-inspect": "^1.12.2", + "rimraf": "^2.7.1", + "safe-publish-latest": "^2.0.0", + "tape": "^5.6.0" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/package.json new file mode 100644 index 000000000000..a955ffc0c193 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/package.json @@ -0,0 +1,9 @@ +{ + "name": "yarn-3-licenses", + "version": "1.0.0", + "packageManager": "yarn@3.4.1", + "dependencies": { + "is-callable": "1.2.7", + "is-odd": "^3.0.1" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/yarn.lock new file mode 100644 index 000000000000..e4b2313250db --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/yarn-licenses/yarn.lock @@ -0,0 +1,41 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"is-callable@npm:1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac + languageName: node + linkType: hard + +"is-number@npm:^6.0.0": + version: 6.0.0 + resolution: "is-number@npm:6.0.0" + checksum: f73bfced022128b5684bf77e0266a74e5222522bbc40f81cc1e949170c774a3c14b59a208be025d2d97a9c6b79c7c45fe351ab1c2c780872464fdedde0ae067a + languageName: node + linkType: hard + +"is-odd@npm:3.0.1": + version: 3.0.1 + resolution: "is-odd@npm:3.0.1" + dependencies: + is-number: ^6.0.0 + checksum: 4e2b20764dd2296bafe44823d127f281c7039b37d2feaf5caffc1bf162502ef2920bcd4ad171490f371d3f15f52232c763a8ffc0b3633d4c83385fe20f3493af + languageName: node + linkType: hard + +"root-workspace-0b6124@workspace:.": + version: 0.0.0-use.local + resolution: "root-workspace-0b6124@workspace:." + dependencies: + is-callable: 1.2.7 + is-odd: 3.0.1 + dependenciesMeta: + is-callable@1.2.7: + unplugged: true + languageName: unknown + linkType: soft \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go new file mode 100644 index 000000000000..1cbd6b4896aa --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go @@ -0,0 +1,413 @@ +package yarn + +import ( + "archive/zip" + "context" + "errors" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "regexp" + "sort" + "strings" + + "github.com/hashicorp/go-multierror" + "github.com/samber/lo" + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" + "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/yarn" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare/npm" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/license" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeYarn, newYarnAnalyzer) +} + +const version = 2 + +// Taken from Yarn +// cf. https://github.com/yarnpkg/yarn/blob/328fd596de935acc6c3e134741748fcc62ec3739/src/resolvers/exotics/registry-resolver.js#L12 +var fragmentRegexp = regexp.MustCompile(`(\S+):(@?.*?)(@(.*?)|)$`) + +type yarnAnalyzer struct { + packageJsonParser *packagejson.Parser + lockParser godeptypes.Parser + comparer npm.Comparer + license *license.License +} + +func newYarnAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &yarnAnalyzer{ + packageJsonParser: packagejson.NewParser(), + lockParser: yarn.NewParser(), + comparer: npm.Comparer{}, + license: license.NewLicense(opt.LicenseScannerOption.ClassifierConfidenceLevel), + }, nil +} + +func (a yarnAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == types.YarnLock + } + + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { + // Parse yarn.lock + app, err := a.parseYarnLock(filePath, r) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil + } + + licenses, err := a.traverseLicenses(input.FS, filePath) + if err != nil { + log.Logger.Debugf("Unable to traverse licenses: %s", err) + } + + // Parse package.json alongside yarn.lock to find direct deps and mark dev deps + if err = a.analyzeDependencies(input.FS, path.Dir(filePath), app); err != nil { + log.Logger.Warnf("Unable to parse %q to remove dev dependencies: %s", path.Join(path.Dir(filePath), types.NpmPkg), err) + } + + // Fill licenses + for i, lib := range app.Libraries { + if l, ok := licenses[lib.ID]; ok { + app.Libraries[i].Licenses = l + } + } + + apps = append(apps, *app) + + return nil + }) + if err != nil { + return nil, xerrors.Errorf("yarn walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a yarnAnalyzer) Required(filePath string, _ os.FileInfo) bool { + dirs, fileName := splitPath(filePath) + + if fileName == types.YarnLock && + // skipping yarn.lock in cache folders + lo.Some(dirs, []string{ + "node_modules", + ".yarn", + }) { + return false + } + + if fileName == types.YarnLock || + fileName == types.NpmPkg || + strings.HasPrefix(strings.ToLower(fileName), "license") { + return true + } + + // The path is slashed in analyzers. + l := len(dirs) + // Valid path to the zip file - **/.yarn/cache/*.zip + if l > 1 && dirs[l-2] == ".yarn" && dirs[l-1] == "cache" && path.Ext(fileName) == ".zip" { + return true + } + + return false +} + +func splitPath(filePath string) (dirs []string, fileName string) { + fileName = filepath.Base(filePath) + // The path is slashed in analyzers. + dirs = strings.Split(path.Dir(filePath), "/") + return dirs, fileName +} + +func (a yarnAnalyzer) Type() analyzer.Type { + return analyzer.TypeYarn +} + +func (a yarnAnalyzer) Version() int { + return version +} + +func (a yarnAnalyzer) parseYarnLock(filePath string, r io.Reader) (*types.Application, error) { + return language.Parse(types.Yarn, filePath, r, a.lockParser) +} + +// analyzeDependencies analyzes the package.json file next to yarn.lock, +// distinguishing between direct and transitive dependencies as well as production and development dependencies. +func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.Application) error { + packageJsonPath := path.Join(dir, types.NpmPkg) + directDeps, directDevDeps, err := a.parsePackageJsonDependencies(fsys, packageJsonPath) + if errors.Is(err, fs.ErrNotExist) { + log.Logger.Debugf("Yarn: %s not found", packageJsonPath) + return nil + } else if err != nil { + return xerrors.Errorf("unable to parse %s: %w", dir, err) + } + + // yarn.lock file can contain same libraries with different versions + // save versions separately for version comparison by comparator + pkgIDs := lo.SliceToMap(app.Libraries, func(pkg types.Package) (string, types.Package) { + return pkg.ID, pkg + }) + + // Walk prod dependencies + pkgs, err := a.walkDependencies(app.Libraries, pkgIDs, directDeps, false) + if err != nil { + return xerrors.Errorf("unable to walk dependencies: %w", err) + } + + // Walk dev dependencies + devPkgs, err := a.walkDependencies(app.Libraries, pkgIDs, directDevDeps, true) + if err != nil { + return xerrors.Errorf("unable to walk dependencies: %w", err) + } + + // Merge prod and dev dependencies. + // If the same package is found in both prod and dev dependencies, use the one in prod. + pkgs = lo.Assign(devPkgs, pkgs) + + pkgSlice := maps.Values(pkgs) + sort.Sort(types.Packages(pkgSlice)) + + // Save libraries + app.Libraries = pkgSlice + return nil +} + +func (a yarnAnalyzer) walkDependencies(libs []types.Package, pkgIDs map[string]types.Package, + directDeps map[string]string, dev bool) (map[string]types.Package, error) { + + // Identify direct dependencies + pkgs := make(map[string]types.Package) + for _, pkg := range libs { + constraint, ok := directDeps[pkg.Name] + if !ok { + continue + } + + // Handle aliases + // cf. https://classic.yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add-alias + if m := fragmentRegexp.FindStringSubmatch(constraint); len(m) == 5 { + pkg.Name = m[2] // original name + constraint = m[4] + } + + // npm has own comparer to compare versions + if match, err := a.comparer.MatchVersion(pkg.Version, constraint); err != nil { + return nil, xerrors.Errorf("unable to match version for %s", pkg.Name) + } else if !match { + continue + } + + // Mark as a direct dependency + pkg.Indirect = false + pkg.Dev = dev + pkgs[pkg.ID] = pkg + + } + + // Walk indirect dependencies + for _, pkg := range pkgs { + a.walkIndirectDependencies(pkg, pkgIDs, pkgs) + } + + return pkgs, nil +} + +func (a yarnAnalyzer) walkIndirectDependencies(pkg types.Package, pkgIDs, deps map[string]types.Package) { + for _, pkgID := range pkg.DependsOn { + if _, ok := deps[pkgID]; ok { + continue + } + + dep, ok := pkgIDs[pkgID] + if !ok { + continue + } + + dep.Indirect = true + dep.Dev = pkg.Dev + deps[dep.ID] = dep + a.walkIndirectDependencies(dep, pkgIDs, deps) + } +} + +func (a yarnAnalyzer) parsePackageJsonDependencies(fsys fs.FS, filePath string) (map[string]string, map[string]string, error) { + // Parse package.json + f, err := fsys.Open(filePath) + if err != nil { + return nil, nil, xerrors.Errorf("file open error: %w", err) + } + defer func() { _ = f.Close() }() + + rootPkg, err := a.packageJsonParser.Parse(f) + if err != nil { + return nil, nil, xerrors.Errorf("parse error: %w", err) + } + + // Merge dependencies and optionalDependencies + dependencies := lo.Assign(rootPkg.Dependencies, rootPkg.OptionalDependencies) + devDependencies := rootPkg.DevDependencies + + if len(rootPkg.Workspaces) > 0 { + pkgs, err := a.traverseWorkspaces(fsys, path.Dir(filePath), rootPkg.Workspaces) + if err != nil { + return nil, nil, xerrors.Errorf("traverse workspaces error: %w", err) + } + for _, pkg := range pkgs { + dependencies = lo.Assign(dependencies, pkg.Dependencies, pkg.OptionalDependencies) + devDependencies = lo.Assign(devDependencies, pkg.DevDependencies) + } + } + + return dependencies, devDependencies, nil +} + +func (a yarnAnalyzer) traverseWorkspaces(fsys fs.FS, dir string, workspaces []string) ([]packagejson.Package, error) { + var pkgs []packagejson.Package + + required := func(path string, _ fs.DirEntry) bool { + return filepath.Base(path) == types.NpmPkg + } + + walkDirFunc := func(path string, d fs.DirEntry, r io.Reader) error { + pkg, err := a.packageJsonParser.Parse(r) + if err != nil { + return xerrors.Errorf("unable to parse %q: %w", path, err) + } + pkgs = append(pkgs, pkg) + return nil + } + + for _, workspace := range workspaces { + // We need to add the path to the `package.json` file + // to properly get the pattern to search in `fs` + workspace = path.Join(dir, workspace) + matches, err := fs.Glob(fsys, workspace) + if err != nil { + return nil, err + } + for _, match := range matches { + if err := fsutils.WalkDir(fsys, match, required, walkDirFunc); err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + } + + } + return pkgs, nil +} + +func (a yarnAnalyzer) traverseLicenses(fsys fs.FS, lockPath string) (map[string][]string, error) { + sub, err := fs.Sub(fsys, path.Dir(lockPath)) + if err != nil { + return nil, xerrors.Errorf("fs error: %w", err) + } + var errs error + + // Yarn v1 + licenses, err := a.traverseYarnClassicPkgs(sub) + if err == nil { + return licenses, nil + } + errs = multierror.Append(errs, err) + + // Yarn v2+ + licenses, err = a.traverseYarnModernPkgs(sub) + if err == nil { + return licenses, nil + } + errs = multierror.Append(errs, err) + + return nil, errs +} + +func (a yarnAnalyzer) traverseYarnClassicPkgs(fsys fs.FS) (map[string][]string, error) { + return a.license.Traverse(fsys, "node_modules") +} + +func (a yarnAnalyzer) traverseYarnModernPkgs(fsys fs.FS) (map[string][]string, error) { + sub, err := fs.Sub(fsys, ".yarn") + if err != nil { + return nil, xerrors.Errorf("fs error: %w", err) + } + + var errs error + licenses := make(map[string][]string) + + if ll, err := a.traverseUnpluggedDir(sub); err != nil { + errs = multierror.Append(errs, err) + } else { + licenses = lo.Assign(licenses, ll) + } + + if ll, err := a.traverseCacheDir(sub); err != nil { + errs = multierror.Append(errs, err) + } else { + licenses = lo.Assign(licenses, ll) + } + + if len(licenses) == 0 { + return nil, errs + } + + return licenses, nil +} + +func (a yarnAnalyzer) traverseUnpluggedDir(fsys fs.FS) (map[string][]string, error) { + // `unplugged` hold machine-specific build artifacts + // Traverse .yarn/unplugged dir + return a.license.Traverse(fsys, "unplugged") +} + +func (a yarnAnalyzer) traverseCacheDir(fsys fs.FS) (map[string][]string, error) { + // Traverse .yarn/cache dir + licenses := make(map[string][]string) + err := fsutils.WalkDir(fsys, "cache", fsutils.RequiredExt(".zip"), + func(filePath string, d fs.DirEntry, r io.Reader) error { + fi, err := d.Info() + if err != nil { + return xerrors.Errorf("file stat error: %w", err) + } + + rr, err := xio.NewReadSeekerAt(r) + if err != nil { + return xerrors.Errorf("reader error: %w", err) + } + + zr, err := zip.NewReader(rr, fi.Size()) + if err != nil { + return xerrors.Errorf("zip reader error: %w", err) + } + + if l, err := a.license.Traverse(zr, "node_modules"); err != nil { + return xerrors.Errorf("license traverse error: %w", err) + } else { + licenses = lo.Assign(licenses, l) + } + return nil + }) + + if err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + + return licenses, nil +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go new file mode 100644 index 000000000000..57d8ca835d65 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go @@ -0,0 +1,803 @@ +package yarn + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + dir: "testdata/happy", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Yarn, + FilePath: "yarn.lock", + Libraries: types.Packages{ + { + ID: "js-tokens@2.0.0", + Name: "js-tokens", + Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 8, + }, + }, + }, + { + ID: "js-tokens@4.0.0", + Name: "js-tokens", + Version: "4.0.0", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 10, + EndLine: 13, + }, + }, + }, + { + ID: "loose-envify@1.4.0", + Name: "loose-envify", + Version: "1.4.0", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 15, + EndLine: 20, + }, + }, + DependsOn: []string{ + "js-tokens@4.0.0", + }, + }, + { + ID: "object-assign@4.1.1", + Name: "object-assign", + Version: "4.1.1", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 25, + }, + }, + }, + { + ID: "prop-types@15.7.2", + Name: "prop-types", + Version: "15.7.2", + Dev: true, + Locations: []types.Location{ + { + StartLine: 27, + EndLine: 34, + }, + }, + DependsOn: []string{ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "react-is@16.13.1", + }, + }, + { + ID: "react-is@16.13.1", + Name: "react-is", + Version: "16.13.1", + Dev: true, + Indirect: true, + Locations: []types.Location{ + { + StartLine: 36, + EndLine: 39, + }, + }, + }, + { + ID: "scheduler@0.13.6", + Name: "scheduler", + Version: "0.13.6", + Locations: []types.Location{ + { + StartLine: 41, + EndLine: 47, + }, + }, + DependsOn: []string{ + "loose-envify@1.4.0", + "object-assign@4.1.1", + }, + }, + }, + }, + }, + }, + }, + { + name: "Project with workspace placed in sub dir", + dir: "testdata/project-with-workspace-in-subdir", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Yarn, + FilePath: "foo/yarn.lock", + Libraries: types.Packages{ + { + ID: "hoek@6.1.3", + Name: "hoek", + Version: "6.1.3", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 8, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "no package.json", + dir: "testdata/no-packagejson", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Yarn, + FilePath: "yarn.lock", + Libraries: types.Packages{ + { + ID: "js-tokens@2.0.0", + Name: "js-tokens", + Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 8, + }, + }, + }, + { + ID: "js-tokens@4.0.0", + Name: "js-tokens", + Version: "4.0.0", + Locations: []types.Location{ + { + StartLine: 10, + EndLine: 13, + }, + }, + }, + { + ID: "loose-envify@1.4.0", + Name: "loose-envify", + Version: "1.4.0", + Locations: []types.Location{ + { + StartLine: 15, + EndLine: 20, + }, + }, + DependsOn: []string{ + "js-tokens@4.0.0", + }, + }, + { + ID: "object-assign@4.1.1", + Name: "object-assign", + Version: "4.1.1", + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 25, + }, + }, + }, + { + ID: "prop-types@15.7.2", + Name: "prop-types", + Version: "15.7.2", + Locations: []types.Location{ + { + StartLine: 27, + EndLine: 34, + }, + }, + DependsOn: []string{ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "react-is@16.13.1", + }, + }, + { + ID: "react-is@16.13.1", + Name: "react-is", + Version: "16.13.1", + Locations: []types.Location{ + { + StartLine: 36, + EndLine: 39, + }, + }, + }, + { + ID: "scheduler@0.13.6", + Name: "scheduler", + Version: "0.13.6", + Locations: []types.Location{ + { + StartLine: 41, + EndLine: 47, + }, + }, + DependsOn: []string{ + "loose-envify@1.4.0", + "object-assign@4.1.1", + }, + }, + }, + }, + }, + }, + }, + { + name: "wrong package.json", + dir: "testdata/wrong-packagejson", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Yarn, + FilePath: "yarn.lock", + Libraries: types.Packages{ + { + ID: "js-tokens@2.0.0", + Name: "js-tokens", + Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 8, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "unsupported_protocol", + dir: "testdata/unsupported_protocol", + want: &analyzer.AnalysisResult{}, + }, + // docker run --rm -it node@sha256:2d5e8a8a51bc341fd5f2eed6d91455c3a3d147e91a14298fc564b5dc519c1666 sh + // mkdir test && cd "$_" + // yarn set version 3.4.1 + // yarn add is-callable@1.2.7 is-odd@3.0.1 + // yarn unplug is-callable@1.2.7 + // rm .yarn/cache/is-callable-npm* + { + name: "parse licenses (yarn v2+)", + dir: "testdata/yarn-licenses", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Yarn, + FilePath: "yarn.lock", + Libraries: []types.Package{ + { + ID: "is-callable@1.2.7", + Name: "is-callable", + Version: "1.2.7", + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 8, + EndLine: 13, + }, + }, + }, + { + ID: "is-number@6.0.0", + Name: "is-number", + Version: "6.0.0", + Licenses: []string{"MIT"}, + Indirect: true, + Locations: []types.Location{ + { + StartLine: 15, + EndLine: 20, + }, + }, + }, + { + ID: "is-odd@3.0.1", + Name: "is-odd", + Version: "3.0.1", + Licenses: []string{"MIT"}, + DependsOn: []string{"is-number@6.0.0"}, + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 29, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with alias rewrite", + dir: "testdata/alias", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Yarn, + FilePath: "yarn.lock", + Libraries: types.Packages{ + { + ID: "foo-json@0.8.33", + Name: "@types/jsonstream", + Version: "0.8.33", + Indirect: false, + Dev: true, + Locations: []types.Location{ + { + StartLine: 19, + EndLine: 24, + }, + }, + DependsOn: []string{ + "@types/node@20.10.5", + }, + }, + { + ID: "@types/node@20.10.5", + Name: "@types/node", + Version: "20.10.5", + Indirect: true, + Dev: true, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 10, + }, + }, + DependsOn: []string{ + "undici-types@5.26.5", + }, + }, + { + ID: "foo-uuid@9.0.7", + Name: "@types/uuid", + Version: "9.0.7", + Indirect: false, + Dev: true, + Locations: []types.Location{ + { + StartLine: 31, + EndLine: 34, + }, + }, + }, + { + ID: "foo-debug@4.3.4", + Name: "debug", + Version: "4.3.4", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 12, + EndLine: 17, + }, + }, + DependsOn: []string{ + "ms@2.1.2", + }, + }, + { + ID: "ms@2.1.2", + Name: "ms", + Version: "2.1.2", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 36, + EndLine: 39, + }, + }, + }, + { + ID: "foo-ms@2.1.3", + Name: "ms", + Version: "2.1.3", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 26, + EndLine: 29, + }, + }, + }, + { + ID: "undici-types@5.26.5", + Name: "undici-types", + Version: "5.26.5", + Indirect: true, + Dev: true, + Locations: []types.Location{ + { + StartLine: 41, + EndLine: 44, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "monorepo", + dir: "testdata/monorepo", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Yarn, + FilePath: "yarn.lock", + Libraries: types.Packages{ + { + ID: "is-number@6.0.0", + Name: "is-number", + Version: "6.0.0", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 16, + EndLine: 21, + }, + }, + }, + { + ID: "is-number@7.0.0", + Name: "is-number", + Version: "7.0.0", + Locations: []types.Location{ + { + StartLine: 23, + EndLine: 28, + }, + }, + }, + { + ID: "is-odd@3.0.1", + Name: "is-odd", + Version: "3.0.1", + DependsOn: []string{"is-number@6.0.0"}, + Locations: []types.Location{ + { + StartLine: 30, + EndLine: 37, + }, + }, + }, + { + ID: "js-tokens@4.0.0", + Name: "js-tokens", + Version: "4.0.0", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 39, + EndLine: 44, + }, + }, + }, + { + ID: "js-tokens@8.0.1", + Name: "js-tokens", + Version: "8.0.1", + Locations: []types.Location{ + { + StartLine: 46, + EndLine: 51, + }, + }, + }, + { + ID: "loose-envify@1.4.0", + Name: "loose-envify", + Version: "1.4.0", + Indirect: true, + DependsOn: []string{"js-tokens@4.0.0"}, + Locations: []types.Location{ + { + StartLine: 53, + EndLine: 62, + }, + }, + }, + { + ID: "object-assign@4.1.1", + Name: "object-assign", + Version: "4.1.1", + Indirect: true, + Dev: true, + Locations: []types.Location{ + { + StartLine: 64, + EndLine: 69, + }, + }, + }, + { + ID: "prettier@2.8.8", + Name: "prettier", + Version: "2.8.8", + Dev: true, + Locations: []types.Location{ + { + StartLine: 87, + EndLine: 94, + }, + }, + }, + { + ID: "prop-types@15.8.1", + Name: "prop-types", + Version: "15.8.1", + Dev: true, + Locations: []types.Location{ + { + StartLine: 96, + EndLine: 105, + }, + }, + DependsOn: []string{ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "react-is@16.13.1", + }, + }, + { + ID: "react-is@16.13.1", + Name: "react-is", + Version: "16.13.1", + Dev: true, + Indirect: true, + Locations: []types.Location{ + { + StartLine: 107, + EndLine: 112, + }, + }, + }, + { + ID: "scheduler@0.23.0", + Name: "scheduler", + Version: "0.23.0", + DependsOn: []string{"loose-envify@1.4.0"}, + Locations: []types.Location{ + { + StartLine: 114, + EndLine: 121, + }, + }, + }, + }, + }, + }, + }, + }, + // docker run --rm -it node@sha256:2d5e8a8a51bc341fd5f2eed6d91455c3a3d147e91a14298fc564b5dc519c1666 sh + // mkdir test && cd "$_" + // yarn set version 1.22.19 + // yarn add @vue/compiler-sfc@2.7.14 + { + name: "parse licenses (yarn classic)", + dir: "testdata/yarn-classic-licenses", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Yarn, + FilePath: "yarn.lock", + Libraries: []types.Package{ + { + ID: "@babel/parser@7.22.7", + Name: "@babel/parser", + Version: "7.22.7", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 8, + }, + }, + Licenses: []string{"MIT"}, + }, + { + ID: "@vue/compiler-sfc@2.7.14", + Name: "@vue/compiler-sfc", + Version: "2.7.14", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 10, + EndLine: 17, + }, + }, + Licenses: []string{"MIT"}, + DependsOn: []string{ + "@babel/parser@7.22.7", + "postcss@8.4.27", + "source-map@0.6.1", + }, + }, + { + ID: "nanoid@3.3.6", + Name: "nanoid", + Version: "3.3.6", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 19, + EndLine: 22, + }, + }, + Licenses: []string{"MIT"}, + }, + { + ID: "picocolors@1.0.0", + Name: "picocolors", + Version: "1.0.0", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 24, + EndLine: 27, + }, + }, + Licenses: []string{"ISC"}, + }, + { + ID: "postcss@8.4.27", + Name: "postcss", + Version: "8.4.27", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 29, + EndLine: 36, + }, + }, + Licenses: []string{"MIT"}, + DependsOn: []string{ + "nanoid@3.3.6", + "picocolors@1.0.0", + "source-map-js@1.0.2", + }, + }, + { + ID: "source-map@0.6.1", + Name: "source-map", + Version: "0.6.1", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 43, + EndLine: 46, + }, + }, + Licenses: []string{"BSD-3-Clause"}, + }, + { + ID: "source-map-js@1.0.2", + Name: "source-map-js", + Version: "1.0.2", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 38, + EndLine: 41, + }, + }, + Licenses: []string{"BSD-3-Clause"}, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newYarnAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_yarnLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path yarn.lock", + filePath: "test/yarn.lock", + want: true, + }, + { + name: "sad path", + filePath: "test/package-lock.json", + want: false, + }, + { + name: "yarn cache", + filePath: ".yarn/cache/websocket-driver-npm-0.7.4-a72739da70-fffe5a33fe.zip", + want: true, + }, + { + name: "not a yarn cache", + filePath: "cache/is-number-npm-6.0.0-30881e83e6-f73bfced03.zip", + want: false, + }, + { + name: "yarn.lock in node_modules", + filePath: "somedir/node_modules/uri-js/yarn.lock", + want: false, + }, + { + name: "yarn.lock in unplugged", + filePath: "somedir/.yarn/unplugged/uri-js/yarn.lock", + want: false, + }, + { + name: "deep package.json", + filePath: "somedir/node_modules/canvg/node_modules/parse5/package.json", + want: true, + }, + { + name: "license file", + filePath: "node_modules/@vue/compiler-sfc/LICENSE", + want: true, + }, + { + name: "txt license file", + filePath: "node_modules/@vue/compiler-sfc/LICENSE.txt", + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := yarnAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/php/composer/composer.go b/pkg/fanal/analyzer/language/php/composer/composer.go new file mode 100644 index 000000000000..d0244514a0e7 --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/composer.go @@ -0,0 +1,146 @@ +package composer + +import ( + "context" + "encoding/json" + "errors" + "io" + "io/fs" + "os" + "path/filepath" + "sort" + "strings" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/php/composer" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeComposer, newComposerAnalyzer) +} + +const version = 1 + +var requiredFiles = []string{ + types.ComposerLock, + types.ComposerJson, +} + +type composerAnalyzer struct { + lockParser godeptypes.Parser +} + +func newComposerAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &composerAnalyzer{ + lockParser: composer.NewParser(), + }, nil +} + +func (a composerAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == types.ComposerLock + } + + err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + // Parse composer.lock + app, err := a.parseComposerLock(path, r) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil + } + + // Parse composer.json alongside composer.lock to identify the direct dependencies + if err = a.mergeComposerJson(input.FS, filepath.Dir(path), app); err != nil { + log.Logger.Warnf("Unable to parse %q to identify direct dependencies: %s", filepath.Join(filepath.Dir(path), types.ComposerJson), err) + } + sort.Sort(app.Libraries) + apps = append(apps, *app) + + return nil + }) + if err != nil { + return nil, xerrors.Errorf("composer walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a composerAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + if !slices.Contains(requiredFiles, fileName) { + return false + } + + // Skip `composer.lock` inside `vendor` folder + if slices.Contains(strings.Split(filePath, "/"), "vendor") { + return false + } + return true +} + +func (a composerAnalyzer) Type() analyzer.Type { + return analyzer.TypeComposer +} + +func (a composerAnalyzer) Version() int { + return version +} + +func (a composerAnalyzer) parseComposerLock(path string, r io.Reader) (*types.Application, error) { + return language.Parse(types.Composer, path, r, a.lockParser) +} + +func (a composerAnalyzer) mergeComposerJson(fsys fs.FS, dir string, app *types.Application) error { + // Parse composer.json to identify the direct dependencies + path := filepath.Join(dir, types.ComposerJson) + p, err := a.parseComposerJson(fsys, path) + if errors.Is(err, fs.ErrNotExist) { + // Assume all the packages are direct dependencies as it cannot identify them from composer.lock + log.Logger.Debugf("Unable to determine the direct dependencies: %s not found", path) + return nil + } else if err != nil { + return xerrors.Errorf("unable to parse %s: %w", path, err) + } + + for i, lib := range app.Libraries { + // Identify the direct/transitive dependencies + if _, ok := p[lib.Name]; !ok { + app.Libraries[i].Indirect = true + } + } + + return nil +} + +type composerJson struct { + Require map[string]string `json:"require"` +} + +func (a composerAnalyzer) parseComposerJson(fsys fs.FS, path string) (map[string]string, error) { + // Parse composer.json + f, err := fsys.Open(path) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer func() { _ = f.Close() }() + + jsonFile := composerJson{} + err = json.NewDecoder(f).Decode(&jsonFile) + if err != nil { + return nil, xerrors.Errorf("json decode error: %w", err) + } + return jsonFile.Require, nil +} diff --git a/pkg/fanal/analyzer/language/php/composer/composer_test.go b/pkg/fanal/analyzer/language/php/composer/composer_test.go new file mode 100644 index 000000000000..e420375dbaea --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/composer_test.go @@ -0,0 +1,164 @@ +package composer + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_composerAnalyzer_PostAnalyze(t *testing.T) { + tests := []struct { + name string + dir string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + dir: "testdata/happy", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Composer, + FilePath: "composer.lock", + Libraries: types.Packages{ + { + ID: "pear/log@1.13.3", + Name: "pear/log", + Version: "1.13.3", + Indirect: false, + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 9, + EndLine: 68, + }, + }, + DependsOn: []string{"pear/pear_exception@v1.0.2"}, + }, + { + ID: "pear/pear_exception@v1.0.2", + Name: "pear/pear_exception", + Version: "v1.0.2", + Indirect: true, + Licenses: []string{"BSD-2-Clause"}, + Locations: []types.Location{ + { + StartLine: 69, + EndLine: 127, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "no composer.json", + dir: "testdata/no-composer-json", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Composer, + FilePath: "composer.lock", + Libraries: types.Packages{ + { + ID: "pear/log@1.13.3", + Name: "pear/log", + Version: "1.13.3", + Indirect: false, + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 9, + EndLine: 68, + }, + }, + DependsOn: []string{"pear/pear_exception@v1.0.2"}, + }, + { + ID: "pear/pear_exception@v1.0.2", + Name: "pear/pear_exception", + Version: "v1.0.2", + Indirect: false, + Licenses: []string{"BSD-2-Clause"}, + Locations: []types.Location{ + { + StartLine: 69, + EndLine: 127, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "wrong composer.json", + dir: "testdata/wrong-composer-json", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Composer, + FilePath: "composer.lock", + Libraries: types.Packages{ + { + ID: "pear/log@1.13.3", + Name: "pear/log", + Version: "1.13.3", + Indirect: false, + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 9, + EndLine: 68, + }, + }, + DependsOn: []string{"pear/pear_exception@v1.0.2"}, + }, + { + ID: "pear/pear_exception@v1.0.2", + Name: "pear/pear_exception", + Version: "v1.0.2", + Indirect: false, + Licenses: []string{"BSD-2-Clause"}, + Locations: []types.Location{ + { + StartLine: 69, + EndLine: 127, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "broken composer.lock", + dir: "testdata/sad", + want: &analyzer.AnalysisResult{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newComposerAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/happy/composer.json b/pkg/fanal/analyzer/language/php/composer/testdata/happy/composer.json new file mode 100644 index 000000000000..b2311cac15d4 --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/testdata/happy/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "pear/log": "^1.13" + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/happy/composer.lock b/pkg/fanal/analyzer/language/php/composer/testdata/happy/composer.lock new file mode 100644 index 000000000000..d842a30b9231 --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/testdata/happy/composer.lock @@ -0,0 +1,138 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "2450af46c78f9ecbca6976876bdd04c2", + "packages": [ + { + "name": "pear/log", + "version": "1.13.3", + "source": { + "type": "git", + "url": "https://github.com/pear/Log.git", + "reference": "21af0be11669194d72d88b5ee9d5f176dc75d9a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Log/zipball/21af0be11669194d72d88b5ee9d5f176dc75d9a3", + "reference": "21af0be11669194d72d88b5ee9d5f176dc75d9a3", + "shasum": "" + }, + "require": { + "pear/pear_exception": "1.0.1 || 1.0.2", + "php": ">5.2" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "pear/db": "Install optionally via your project's composer.json" + }, + "type": "library", + "autoload": { + "psr-0": { + "Log": "./" + }, + "exclude-from-classmap": [ + "/examples/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jon Parise", + "email": "jon@php.net", + "homepage": "http://www.indelible.org", + "role": "Developer" + } + ], + "description": "PEAR Logging Framework", + "homepage": "http://pear.github.io/Log/", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/pear/Log/issues", + "source": "https://github.com/pear/Log" + }, + "time": "2021-05-04T23:51:30+00:00" + }, + { + "name": "pear/pear_exception", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/pear/PEAR_Exception.git", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "<9" + }, + "type": "class", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "PEAR/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "." + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Helgi Thormar", + "email": "dufuz@php.net" + }, + { + "name": "Greg Beaver", + "email": "cellog@php.net" + } + ], + "description": "The PEAR Exception base class.", + "homepage": "https://github.com/pear/PEAR_Exception", + "keywords": [ + "exception" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception", + "source": "https://github.com/pear/PEAR_Exception" + }, + "time": "2021-03-21T15:43:46+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/no-composer-json/composer.lock b/pkg/fanal/analyzer/language/php/composer/testdata/no-composer-json/composer.lock new file mode 100644 index 000000000000..d842a30b9231 --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/testdata/no-composer-json/composer.lock @@ -0,0 +1,138 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "2450af46c78f9ecbca6976876bdd04c2", + "packages": [ + { + "name": "pear/log", + "version": "1.13.3", + "source": { + "type": "git", + "url": "https://github.com/pear/Log.git", + "reference": "21af0be11669194d72d88b5ee9d5f176dc75d9a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Log/zipball/21af0be11669194d72d88b5ee9d5f176dc75d9a3", + "reference": "21af0be11669194d72d88b5ee9d5f176dc75d9a3", + "shasum": "" + }, + "require": { + "pear/pear_exception": "1.0.1 || 1.0.2", + "php": ">5.2" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "pear/db": "Install optionally via your project's composer.json" + }, + "type": "library", + "autoload": { + "psr-0": { + "Log": "./" + }, + "exclude-from-classmap": [ + "/examples/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jon Parise", + "email": "jon@php.net", + "homepage": "http://www.indelible.org", + "role": "Developer" + } + ], + "description": "PEAR Logging Framework", + "homepage": "http://pear.github.io/Log/", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/pear/Log/issues", + "source": "https://github.com/pear/Log" + }, + "time": "2021-05-04T23:51:30+00:00" + }, + { + "name": "pear/pear_exception", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/pear/PEAR_Exception.git", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "<9" + }, + "type": "class", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "PEAR/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "." + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Helgi Thormar", + "email": "dufuz@php.net" + }, + { + "name": "Greg Beaver", + "email": "cellog@php.net" + } + ], + "description": "The PEAR Exception base class.", + "homepage": "https://github.com/pear/PEAR_Exception", + "keywords": [ + "exception" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception", + "source": "https://github.com/pear/PEAR_Exception" + }, + "time": "2021-03-21T15:43:46+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/sad/composer.lock b/pkg/fanal/analyzer/language/php/composer/testdata/sad/composer.lock new file mode 100644 index 000000000000..81750b96f9d8 --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/testdata/sad/composer.lock @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/wrong-composer-json/composer.json b/pkg/fanal/analyzer/language/php/composer/testdata/wrong-composer-json/composer.json new file mode 100644 index 000000000000..81750b96f9d8 --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/testdata/wrong-composer-json/composer.json @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/wrong-composer-json/composer.lock b/pkg/fanal/analyzer/language/php/composer/testdata/wrong-composer-json/composer.lock new file mode 100644 index 000000000000..d842a30b9231 --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/testdata/wrong-composer-json/composer.lock @@ -0,0 +1,138 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "2450af46c78f9ecbca6976876bdd04c2", + "packages": [ + { + "name": "pear/log", + "version": "1.13.3", + "source": { + "type": "git", + "url": "https://github.com/pear/Log.git", + "reference": "21af0be11669194d72d88b5ee9d5f176dc75d9a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Log/zipball/21af0be11669194d72d88b5ee9d5f176dc75d9a3", + "reference": "21af0be11669194d72d88b5ee9d5f176dc75d9a3", + "shasum": "" + }, + "require": { + "pear/pear_exception": "1.0.1 || 1.0.2", + "php": ">5.2" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "pear/db": "Install optionally via your project's composer.json" + }, + "type": "library", + "autoload": { + "psr-0": { + "Log": "./" + }, + "exclude-from-classmap": [ + "/examples/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jon Parise", + "email": "jon@php.net", + "homepage": "http://www.indelible.org", + "role": "Developer" + } + ], + "description": "PEAR Logging Framework", + "homepage": "http://pear.github.io/Log/", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/pear/Log/issues", + "source": "https://github.com/pear/Log" + }, + "time": "2021-05-04T23:51:30+00:00" + }, + { + "name": "pear/pear_exception", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/pear/PEAR_Exception.git", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "<9" + }, + "type": "class", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "PEAR/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "." + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Helgi Thormar", + "email": "dufuz@php.net" + }, + { + "name": "Greg Beaver", + "email": "cellog@php.net" + } + ], + "description": "The PEAR Exception base class.", + "homepage": "https://github.com/pear/PEAR_Exception", + "keywords": [ + "exception" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception", + "source": "https://github.com/pear/PEAR_Exception" + }, + "time": "2021-03-21T15:43:46+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging.go b/pkg/fanal/analyzer/language/python/packaging/packaging.go new file mode 100644 index 000000000000..c45a4f5aef0f --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/packaging.go @@ -0,0 +1,221 @@ +package packaging + +import ( + "archive/zip" + "bytes" + "context" + "errors" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "strings" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/packaging" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypePythonPkg, newPackagingAnalyzer) +} + +const version = 1 + +func newPackagingAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &packagingAnalyzer{ + pkgParser: packaging.NewParser(), + licenseClassifierConfidenceLevel: opt.LicenseScannerOption.ClassifierConfidenceLevel, + }, nil +} + +var ( + eggFiles = []string{ + // .egg format + // https://setuptools.readthedocs.io/en/latest/deprecated/python_eggs.html#eggs-and-their-formats + ".egg", // zip format + "EGG-INFO/PKG-INFO", + + // .egg-info format: .egg-info can be a file or directory + // https://setuptools.readthedocs.io/en/latest/deprecated/python_eggs.html#eggs-and-their-formats + ".egg-info", + ".egg-info/PKG-INFO", + } +) + +type packagingAnalyzer struct { + pkgParser godeptypes.Parser + licenseClassifierConfidenceLevel float64 +} + +// PostAnalyze analyzes egg and wheel files. +func (a packagingAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + + var apps []types.Application + + required := func(path string, _ fs.DirEntry) bool { + return filepath.Base(path) == "METADATA" || isEggFile(path) + } + + err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + rsa, ok := r.(xio.ReadSeekerAt) + if !ok { + return xerrors.New("invalid reader") + } + + // .egg file is zip format and PKG-INFO needs to be extracted from the zip file. + if strings.HasSuffix(path, ".egg") { + info, err := d.Info() + if err != nil { + return xerrors.Errorf("egg file error: %w", err) + } + pkginfoInZip, err := a.analyzeEggZip(rsa, info.Size()) + if err != nil { + return xerrors.Errorf("egg analysis error: %w", err) + } + + // Egg archive may not contain required files, then we will get nil. Skip this archives + if pkginfoInZip == nil { + return nil + } + rsa = pkginfoInZip + } + + app, err := a.parse(path, rsa, input.Options.FileChecksum) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil + } + + if err := a.fillAdditionalData(input.FS, app); err != nil { + log.Logger.Warnf("Unable to collect additional info: %s", err) + } + + apps = append(apps, *app) + return nil + }) + + if err != nil { + return nil, xerrors.Errorf("python package walk error: %w", err) + } + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a packagingAnalyzer) fillAdditionalData(fsys fs.FS, app *types.Application) error { + for i, lib := range app.Libraries { + var licenses []string + for _, lic := range lib.Licenses { + // Parser adds `file://` prefix to filepath from `License-File` field + // We need to read this file to find licenses + // Otherwise, this is the name of the license + if !strings.HasPrefix(lic, "file://") { + licenses = append(licenses, lic) + continue + } + licenseFilePath := path.Base(strings.TrimPrefix(lic, "file://")) + + findings, err := classifyLicense(app.FilePath, licenseFilePath, a.licenseClassifierConfidenceLevel, fsys) + if err != nil { + return err + } else if len(findings) == 0 { + continue + } + + // License found + foundLicenses := lo.Map(findings, func(finding types.LicenseFinding, _ int) string { + return finding.Name + }) + licenses = append(licenses, foundLicenses...) + } + app.Libraries[i].Licenses = licenses + } + + return nil +} + +func classifyLicense(dir, licPath string, classifierConfidenceLevel float64, fsys fs.FS) (types.LicenseFindings, error) { + // Note that fs.FS is always slashed regardless of the platform, + // and path.Join should be used rather than filepath.Join. + f, err := fsys.Open(path.Join(path.Dir(dir), licPath)) + if errors.Is(err, fs.ErrNotExist) { + return nil, nil + } else if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + l, err := licensing.Classify(licPath, f, classifierConfidenceLevel) + if err != nil { + return nil, xerrors.Errorf("license classify error: %w", err) + } else if l == nil { + return nil, nil + } + + return l.Findings, nil +} + +func (a packagingAnalyzer) parse(filePath string, r xio.ReadSeekerAt, checksum bool) (*types.Application, error) { + return language.ParsePackage(types.PythonPkg, filePath, r, a.pkgParser, checksum) +} + +func (a packagingAnalyzer) analyzeEggZip(r io.ReaderAt, size int64) (xio.ReadSeekerAt, error) { + zr, err := zip.NewReader(r, size) + if err != nil { + return nil, xerrors.Errorf("zip reader error: %w", err) + } + + found, ok := lo.Find(zr.File, func(f *zip.File) bool { + return isEggFile(f.Name) + }) + if !ok { + return nil, nil + } + return a.open(found) +} + +// open reads the file content in the zip archive to make it seekable. +func (a packagingAnalyzer) open(file *zip.File) (xio.ReadSeekerAt, error) { + f, err := file.Open() + if err != nil { + return nil, err + } + defer f.Close() + + b, err := io.ReadAll(f) + if err != nil { + return nil, xerrors.Errorf("file %s open error: %w", file.Name, err) + } + + return bytes.NewReader(b), nil +} + +func (a packagingAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return strings.Contains(filePath, ".dist-info") || isEggFile(filePath) +} + +func isEggFile(filePath string) bool { + return lo.SomeBy(eggFiles, func(fileName string) bool { + return strings.HasSuffix(filePath, fileName) + }) +} + +func (a packagingAnalyzer) Type() analyzer.Type { + return analyzer.TypePythonPkg +} + +func (a packagingAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go new file mode 100644 index 000000000000..2dbfd603e92b --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go @@ -0,0 +1,221 @@ +package packaging + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_packagingAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + includeChecksum bool + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "egg zip", + dir: "testdata/egg-zip", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "kitchen-1.2.6-py2.7.egg", + Libraries: types.Packages{ + { + Name: "kitchen", + Version: "1.2.6", + Licenses: []string{ + "GNU Library or Lesser General Public License (LGPL)", + }, + FilePath: "kitchen-1.2.6-py2.7.egg", + }, + }, + }, + }, + }, + }, + { + name: "egg-info", + dir: "testdata/happy-egg", + includeChecksum: true, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "distlib-0.3.1.egg-info/PKG-INFO", + Libraries: types.Packages{ + { + Name: "distlib", + Version: "0.3.1", + Licenses: []string{"Python license"}, + FilePath: "distlib-0.3.1.egg-info/PKG-INFO", + Digest: "sha1:d9d89d8ed3b2b683767c96814c9c5d3e57ef2e1b", + }, + }, + }, + }, + }, + }, + { + name: "egg-info license classifiers", + dir: "testdata/classifier-license-egg", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "setuptools-51.3.3.egg-info/PKG-INFO", + Libraries: types.Packages{ + { + Name: "setuptools", + Version: "51.3.3", + Licenses: []string{"MIT License"}, + FilePath: "setuptools-51.3.3.egg-info/PKG-INFO", + }, + }, + }, + }, + }, + }, + { + name: "dist-info license classifiers", + dir: "testdata/classifier-license-dist", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "setuptools-51.3.3.dist-info/METADATA", + Libraries: types.Packages{ + { + Name: "setuptools", + Version: "51.3.3", + Licenses: []string{"MIT License"}, + FilePath: "setuptools-51.3.3.dist-info/METADATA", + }, + }, + }, + }, + }, + }, + { + name: "wheel", + dir: "testdata/happy-dist", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "distlib-0.3.1.dist-info/METADATA", + Libraries: types.Packages{ + { + Name: "distlib", + Version: "0.3.1", + Licenses: []string{"Python license"}, + FilePath: "distlib-0.3.1.dist-info/METADATA", + }, + }, + }, + }, + }, + }, + { + name: "egg zip doesn't contain required files", + dir: "testdata/no-req-files", + want: &analyzer.AnalysisResult{}, + }, + { + name: "license file in dist.info", + dir: "testdata/license-file-dist", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "typing_extensions-4.4.0.dist-info/METADATA", + Libraries: []types.Package{ + { + Name: "typing_extensions", + Version: "4.4.0", + Licenses: []string{"BeOpen", "CNRI-Python-GPL-Compatible", "LicenseRef-MIT-Lucent", "Python-2.0"}, + FilePath: "typing_extensions-4.4.0.dist-info/METADATA", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + a, err := newPackagingAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + Options: analyzer.AnalysisOptions{ + FileChecksum: tt.includeChecksum, + }, + }) + + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } + +} + +func Test_packagingAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "egg", + filePath: "python2.7/site-packages/cssutils-1.0-py2.7.egg/EGG-INFO/PKG-INFO", + want: true, + }, + { + name: "egg-info", + filePath: "python3.8/site-packages/wrapt-1.12.1.egg-info", + want: true, + }, + { + name: "egg-info PKG-INFO", + filePath: "python3.8/site-packages/wrapt-1.12.1.egg-info/PKG-INFO", + want: true, + }, + { + name: "wheel", + filePath: "python3.8/site-packages/wrapt-1.12.1.dist-info/METADATA", + want: true, + }, + { + name: "wheel license", + filePath: "python3.8/site-packages/wrapt-1.12.1.dist-info/LICENSE", + want: true, + }, + { + name: "sad", + filePath: "random/PKG-INFO", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := packagingAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/python/packaging/testdata/classifier-license-dist/setuptools-51.3.3.dist-info/METADATA b/pkg/fanal/analyzer/language/python/packaging/testdata/classifier-license-dist/setuptools-51.3.3.dist-info/METADATA new file mode 100644 index 000000000000..fa35ca3301a7 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/testdata/classifier-license-dist/setuptools-51.3.3.dist-info/METADATA @@ -0,0 +1,90 @@ +Metadata-Version: 2.1 +Name: setuptools +Version: 51.3.3 +Summary: Easily download, build, install, upgrade, and uninstall Python packages +Home-page: https://github.com/pypa/setuptools +Author: Python Packaging Authority +Author-email: distutils-sig@python.org +Project-URL: Documentation, https://setuptools.readthedocs.io/ +Keywords: CPAN PyPI distutils eggs package management +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Requires-Python: >=3.6 +Provides-Extra: testing +Provides-Extra: docs +Provides-Extra: ssl +Provides-Extra: certs + +.. image:: https://img.shields.io/pypi/v/setuptools.svg + :target: https://pypi.org/project/setuptools + +.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg + +.. image:: https://github.com/pypa/setuptools/workflows/tests/badge.svg + :target: https://github.com/pypa/setuptools/actions?query=workflow%3A%22tests%22 + :alt: tests + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: Black + +.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg + :target: https://setuptools.pypa.io + +.. image:: https://img.shields.io/badge/skeleton-2023-informational + :target: https://blog.jaraco.com/skeleton + +.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white + :target: https://codecov.io/gh/pypa/setuptools + +.. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat + :target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme + +.. image:: https://img.shields.io/discord/803025117553754132 + :target: https://discord.com/channels/803025117553754132/815945031150993468 + :alt: Discord + +See the `Installation Instructions +`_ in the Python Packaging +User's Guide for instructions on installing, upgrading, and uninstalling +Setuptools. + +Questions and comments should be directed to `GitHub Discussions +`_. +Bug reports and especially tested patches may be +submitted directly to the `bug tracker +`_. + + +Code of Conduct +=============== + +Everyone interacting in the setuptools project's codebases, issue trackers, +chat rooms, and fora is expected to follow the +`PSF Code of Conduct `_. + + +For Enterprise +============== + +Available as part of the Tidelift Subscription. + +Setuptools and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. + +`Learn more `_. + + +Security Contact +================ + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure. diff --git a/pkg/fanal/analyzer/language/python/packaging/testdata/classifier-license-egg/setuptools-51.3.3.egg-info/PKG-INFO b/pkg/fanal/analyzer/language/python/packaging/testdata/classifier-license-egg/setuptools-51.3.3.egg-info/PKG-INFO new file mode 100644 index 000000000000..3c775e5909e8 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/testdata/classifier-license-egg/setuptools-51.3.3.egg-info/PKG-INFO @@ -0,0 +1,89 @@ +Metadata-Version: 2.1 +Name: setuptools +Version: 51.3.3 +Summary: Easily download, build, install, upgrade, and uninstall Python packages +Home-page: https://github.com/pypa/setuptools +Author: Python Packaging Authority +Author-email: distutils-sig@python.org +Project-URL: Documentation, https://setuptools.readthedocs.io/ +Keywords: CPAN PyPI distutils eggs package management +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Requires-Python: >=3.6 +Provides-Extra: testing +Provides-Extra: docs +Provides-Extra: ssl +Provides-Extra: certs +.. image:: https://img.shields.io/pypi/v/setuptools.svg + :target: https://pypi.org/project/setuptools + +.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg + +.. image:: https://github.com/pypa/setuptools/workflows/tests/badge.svg + :target: https://github.com/pypa/setuptools/actions?query=workflow%3A%22tests%22 + :alt: tests + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: Black + +.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg + :target: https://setuptools.pypa.io + +.. image:: https://img.shields.io/badge/skeleton-2023-informational + :target: https://blog.jaraco.com/skeleton + +.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white + :target: https://codecov.io/gh/pypa/setuptools + +.. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat + :target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme + +.. image:: https://img.shields.io/discord/803025117553754132 + :target: https://discord.com/channels/803025117553754132/815945031150993468 + :alt: Discord + +See the `Installation Instructions +`_ in the Python Packaging +User's Guide for instructions on installing, upgrading, and uninstalling +Setuptools. + +Questions and comments should be directed to `GitHub Discussions +`_. +Bug reports and especially tested patches may be +submitted directly to the `bug tracker +`_. + + +Code of Conduct +=============== + +Everyone interacting in the setuptools project's codebases, issue trackers, +chat rooms, and fora is expected to follow the +`PSF Code of Conduct `_. + + +For Enterprise +============== + +Available as part of the Tidelift Subscription. + +Setuptools and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. + +`Learn more `_. + + +Security Contact +================ + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure. diff --git a/pkg/fanal/analyzer/language/python/packaging/testdata/egg-zip/kitchen-1.2.6-py2.7.egg b/pkg/fanal/analyzer/language/python/packaging/testdata/egg-zip/kitchen-1.2.6-py2.7.egg new file mode 100644 index 000000000000..b40d7822f7b6 Binary files /dev/null and b/pkg/fanal/analyzer/language/python/packaging/testdata/egg-zip/kitchen-1.2.6-py2.7.egg differ diff --git a/pkg/fanal/analyzer/language/python/packaging/testdata/happy-dist/distlib-0.3.1.dist-info/METADATA b/pkg/fanal/analyzer/language/python/packaging/testdata/happy-dist/distlib-0.3.1.dist-info/METADATA new file mode 100644 index 000000000000..95c76f43e0dd --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/testdata/happy-dist/distlib-0.3.1.dist-info/METADATA @@ -0,0 +1,30 @@ +Metadata-Version: 1.1 +Name: distlib +Version: 0.3.1 +Summary: Distribution utilities +Home-page: https://bitbucket.org/pypa/distlib +Author: Vinay Sajip +Author-email: vinay_sajip@red-dove.com +License: Python license +Download-URL: https://bitbucket.org/pypa/distlib/downloads/distlib-0.3.1.zip +Description: Low-level components of distutils2/packaging, augmented with higher-level APIs for making packaging easier. + +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Topic :: Software Development +Classifier: Topic :: Utilities diff --git a/pkg/fanal/analyzer/language/python/packaging/testdata/happy-egg/distlib-0.3.1.egg-info/PKG-INFO b/pkg/fanal/analyzer/language/python/packaging/testdata/happy-egg/distlib-0.3.1.egg-info/PKG-INFO new file mode 100644 index 000000000000..95c76f43e0dd --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/testdata/happy-egg/distlib-0.3.1.egg-info/PKG-INFO @@ -0,0 +1,30 @@ +Metadata-Version: 1.1 +Name: distlib +Version: 0.3.1 +Summary: Distribution utilities +Home-page: https://bitbucket.org/pypa/distlib +Author: Vinay Sajip +Author-email: vinay_sajip@red-dove.com +License: Python license +Download-URL: https://bitbucket.org/pypa/distlib/downloads/distlib-0.3.1.zip +Description: Low-level components of distutils2/packaging, augmented with higher-level APIs for making packaging easier. + +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Topic :: Software Development +Classifier: Topic :: Utilities diff --git a/pkg/fanal/analyzer/language/python/packaging/testdata/license-file-dist/typing_extensions-4.4.0.dist-info/LICENSE.txt b/pkg/fanal/analyzer/language/python/packaging/testdata/license-file-dist/typing_extensions-4.4.0.dist-info/LICENSE.txt new file mode 100644 index 000000000000..1df6b3b8de0e --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/testdata/license-file-dist/typing_extensions-4.4.0.dist-info/LICENSE.txt @@ -0,0 +1,254 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/pkg/fanal/analyzer/language/python/packaging/testdata/license-file-dist/typing_extensions-4.4.0.dist-info/METADATA b/pkg/fanal/analyzer/language/python/packaging/testdata/license-file-dist/typing_extensions-4.4.0.dist-info/METADATA new file mode 100644 index 000000000000..e4fc961591e5 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/testdata/license-file-dist/typing_extensions-4.4.0.dist-info/METADATA @@ -0,0 +1,189 @@ +Metadata-Version: 2.1 +Name: typing_extensions +Version: 4.4.0 +Summary: Backported and Experimental Type Hints for Python 3.7+ +Keywords: annotations,backport,checker,checking,function,hinting,hints,type,typechecking,typehinting,typehints,typing +Author-email: "Guido van Rossum, Jukka Lehtosalo, Åukasz Langa, Michael Lee" +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +License-File: licenses/LICENSE.txt +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Topic :: Software Development +Project-URL: Bug Tracker, https://github.com/python/typing_extensions/issues +Project-URL: Changes, https://github.com/python/typing_extensions/blob/main/CHANGELOG.md +Project-URL: Documentation, https://typing.readthedocs.io/ +Project-URL: Home, https://github.com/python/typing_extensions +Project-URL: Q & A, https://github.com/python/typing/discussions +Project-URL: Repository, https://github.com/python/typing_extensions + +# Typing Extensions + +[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing) + +## Overview + +The `typing_extensions` module serves two related purposes: + +- Enable use of new type system features on older Python versions. For example, + `typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows + users on previous Python versions to use it too. +- Enable experimentation with new type system PEPs before they are accepted and + added to the `typing` module. + +New features may be added to `typing_extensions` as soon as they are specified +in a PEP that has been added to the [python/peps](https://github.com/python/peps) +repository. If the PEP is accepted, the feature will then be added to `typing` +for the next CPython release. No typing PEP has been rejected so far, so we +haven't yet figured out how to deal with that possibility. + +Starting with version 4.0.0, `typing_extensions` uses +[Semantic Versioning](https://semver.org/). The +major version is incremented for all backwards-incompatible changes. +Therefore, it's safe to depend +on `typing_extensions` like this: `typing_extensions >=x.y, <(x+1)`, +where `x.y` is the first version that includes all features you need. + +`typing_extensions` supports Python versions 3.7 and higher. In the future, +support for older Python versions will be dropped some time after that version +reaches end of life. + +## Included items + +This module currently contains the following: + +- Experimental features + + - `override` (see PEP 698) + - The `default=` argument to `TypeVar`, `ParamSpec`, and `TypeVarTuple` (see PEP 696) + - The `infer_variance=` argument to `TypeVar` (see PEP 695) + +- In `typing` since Python 3.11 + + - `assert_never` + - `assert_type` + - `clear_overloads` + - `@dataclass_transform()` (see PEP 681) + - `get_overloads` + - `LiteralString` (see PEP 675) + - `Never` + - `NotRequired` (see PEP 655) + - `reveal_type` + - `Required` (see PEP 655) + - `Self` (see PEP 673) + - `TypeVarTuple` (see PEP 646; the `typing_extensions` version supports the `default=` argument from PEP 696) + - `Unpack` (see PEP 646) + +- In `typing` since Python 3.10 + + - `Concatenate` (see PEP 612) + - `ParamSpec` (see PEP 612; the `typing_extensions` version supports the `default=` argument from PEP 696) + - `ParamSpecArgs` (see PEP 612) + - `ParamSpecKwargs` (see PEP 612) + - `TypeAlias` (see PEP 613) + - `TypeGuard` (see PEP 647) + - `is_typeddict` + +- In `typing` since Python 3.9 + + - `Annotated` (see PEP 593) + +- In `typing` since Python 3.8 + + - `final` (see PEP 591) + - `Final` (see PEP 591) + - `Literal` (see PEP 586) + - `Protocol` (see PEP 544) + - `runtime_checkable` (see PEP 544) + - `TypedDict` (see PEP 589) + - `get_origin` (`typing_extensions` provides this function only in Python 3.7+) + - `get_args` (`typing_extensions` provides this function only in Python 3.7+) + +- In `typing` since Python 3.7 + + - `OrderedDict` + +- In `typing` since Python 3.5 or 3.6 (see [the typing documentation](https://docs.python.org/3.10/library/typing.html) for details) + + - `AsyncContextManager` + - `AsyncGenerator` + - `AsyncIterable` + - `AsyncIterator` + - `Awaitable` + - `ChainMap` + - `ClassVar` (see PEP 526) + - `ContextManager` + - `Coroutine` + - `Counter` + - `DefaultDict` + - `Deque` + - `NewType` + - `NoReturn` + - `overload` + - `Text` + - `Type` + - `TYPE_CHECKING` + - `get_type_hints` + +- The following have always been present in `typing`, but the `typing_extensions` versions provide + additional features: + + - `Any` (supports inheritance since Python 3.11) + - `NamedTuple` (supports multiple inheritance with `Generic` since Python 3.11) + - `TypeVar` (see PEPs 695 and 696) + +# Other Notes and Limitations + +Certain objects were changed after they were added to `typing`, and +`typing_extensions` provides a backport even on newer Python versions: + +- `TypedDict` does not store runtime information + about which (if any) keys are non-required in Python 3.8, and does not + honor the `total` keyword with old-style `TypedDict()` in Python + 3.9.0 and 3.9.1. `TypedDict` also does not support multiple inheritance + with `typing.Generic` on Python <3.11. +- `get_origin` and `get_args` lack support for `Annotated` in + Python 3.8 and lack support for `ParamSpecArgs` and `ParamSpecKwargs` + in 3.9. +- `@final` was changed in Python 3.11 to set the `.__final__` attribute. +- `@overload` was changed in Python 3.11 to make function overloads + introspectable at runtime. In order to access overloads with + `typing_extensions.get_overloads()`, you must use + `@typing_extensions.overload`. +- `NamedTuple` was changed in Python 3.11 to allow for multiple inheritance + with `typing.Generic`. +- Since Python 3.11, it has been possible to inherit from `Any` at + runtime. `typing_extensions.Any` also provides this capability. +- `TypeVar` gains two additional parameters, `default=` and `infer_variance=`, + in the draft PEPs 695 and 696, which are being considered for inclusion + in Python 3.12. + +There are a few types whose interface was modified between different +versions of typing. For example, `typing.Sequence` was modified to +subclass `typing.Reversible` as of Python 3.5.3. + +These changes are _not_ backported to prevent subtle compatibility +issues when mixing the differing implementations of modified classes. + +Certain types have incorrect runtime behavior due to limitations of older +versions of the typing module: + +- `ParamSpec` and `Concatenate` will not work with `get_args` and + `get_origin`. Certain PEP 612 special cases in user-defined + `Generic`s are also not available. + +These types are only guaranteed to work for static type checking. + +## Running tests + +To run tests, navigate into the appropriate source directory and run +`test_typing_extensions.py`. + diff --git a/pkg/fanal/analyzer/language/python/packaging/testdata/no-req-files/no-required-files.egg b/pkg/fanal/analyzer/language/python/packaging/testdata/no-req-files/no-required-files.egg new file mode 100644 index 000000000000..8cd2acb25451 Binary files /dev/null and b/pkg/fanal/analyzer/language/python/packaging/testdata/no-req-files/no-required-files.egg differ diff --git a/pkg/fanal/analyzer/language/python/pip/pip.go b/pkg/fanal/analyzer/language/python/pip/pip.go new file mode 100644 index 000000000000..380fcbf4936c --- /dev/null +++ b/pkg/fanal/analyzer/language/python/pip/pip.go @@ -0,0 +1,43 @@ +package pip + +import ( + "context" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/pip" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&pipLibraryAnalyzer{}) +} + +const version = 1 + +type pipLibraryAnalyzer struct{} + +func (a pipLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + res, err := language.Analyze(types.Pip, input.FilePath, input.Content, pip.NewParser()) + if err != nil { + return nil, xerrors.Errorf("unable to parse requirements.txt: %w", err) + } + return res, nil +} + +func (a pipLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + return fileName == types.PipRequirements +} + +func (a pipLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypePip +} + +func (a pipLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/python/pip/pip_test.go b/pkg/fanal/analyzer/language/python/pip/pip_test.go new file mode 100644 index 000000000000..b023648ae5d0 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/pip/pip_test.go @@ -0,0 +1,102 @@ +package pip + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_pipAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/requirements.txt", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Pip, + FilePath: "testdata/requirements.txt", + Libraries: types.Packages{ + { + Name: "click", + Version: "8.0.0", + }, + { + Name: "Flask", + Version: "2.0.0", + }, + { + Name: "itsdangerous", + Version: "2.0.0", + }, + }, + }, + }, + }, + }, + { + name: "happy path with not related filename", + inputFile: "testdata/not-related.txt", + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := pipLibraryAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_pipAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy", + filePath: "test/requirements.txt", + want: true, + }, + { + name: "sad", + filePath: "a/b/c/d/test.sum", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := pipLibraryAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/python/pip/testdata/not-related.txt b/pkg/fanal/analyzer/language/python/pip/testdata/not-related.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/language/python/pip/testdata/requirements.txt b/pkg/fanal/analyzer/language/python/pip/testdata/requirements.txt new file mode 100644 index 000000000000..acb00401b7d6 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/pip/testdata/requirements.txt @@ -0,0 +1,6 @@ +click==8.0.0 +Flask==2.0.0 +itsdangerous==2.0.0 +Jinja2<3.0.0 +MarkupSafe>2.0.0 +Werkzeug diff --git a/pkg/fanal/analyzer/language/python/pipenv/pipenv.go b/pkg/fanal/analyzer/language/python/pipenv/pipenv.go new file mode 100644 index 000000000000..06fc1f35dccf --- /dev/null +++ b/pkg/fanal/analyzer/language/python/pipenv/pipenv.go @@ -0,0 +1,46 @@ +package pipenv + +import ( + "context" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/pipenv" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +func init() { + analyzer.RegisterAnalyzer(&pipenvLibraryAnalyzer{}) +} + +const version = 1 + +var requiredFiles = []string{types.PipfileLock} + +type pipenvLibraryAnalyzer struct{} + +func (a pipenvLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + res, err := language.Analyze(types.Pipenv, input.FilePath, input.Content, pipenv.NewParser()) + if err != nil { + return nil, xerrors.Errorf("unable to parse Pipfile.lock: %w", err) + } + return res, nil +} + +func (a pipenvLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + return utils.StringInSlice(fileName, requiredFiles) +} + +func (a pipenvLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypePipenv +} + +func (a pipenvLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry.go b/pkg/fanal/analyzer/language/python/poetry/poetry.go new file mode 100644 index 000000000000..90897e8f12ba --- /dev/null +++ b/pkg/fanal/analyzer/language/python/poetry/poetry.go @@ -0,0 +1,126 @@ +package poetry + +import ( + "context" + "errors" + "io" + "io/fs" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/poetry" + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/pyproject" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypePoetry, newPoetryAnalyzer) +} + +const version = 1 + +type poetryAnalyzer struct { + pyprojectParser *pyproject.Parser + lockParser godeptypes.Parser +} + +func newPoetryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &poetryAnalyzer{ + pyprojectParser: pyproject.NewParser(), + lockParser: poetry.NewParser(), + }, nil +} + +func (a poetryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == types.PoetryLock + } + + err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + // Parse poetry.lock + app, err := a.parsePoetryLock(path, r) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil + } + + // Parse pyproject.toml alongside poetry.lock to identify the direct dependencies + if err = a.mergePyProject(input.FS, filepath.Dir(path), app); err != nil { + log.Logger.Warnf("Unable to parse %q to identify direct dependencies: %s", filepath.Join(filepath.Dir(path), types.PyProject), err) + } + apps = append(apps, *app) + + return nil + }) + if err != nil { + return nil, xerrors.Errorf("poetry walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a poetryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + return fileName == types.PoetryLock || fileName == types.PyProject +} + +func (a poetryAnalyzer) Type() analyzer.Type { + return analyzer.TypePoetry +} + +func (a poetryAnalyzer) Version() int { + return version +} + +func (a poetryAnalyzer) parsePoetryLock(path string, r io.Reader) (*types.Application, error) { + return language.Parse(types.Poetry, path, r, a.lockParser) +} + +func (a poetryAnalyzer) mergePyProject(fsys fs.FS, dir string, app *types.Application) error { + // Parse pyproject.toml to identify the direct dependencies + path := filepath.Join(dir, types.PyProject) + p, err := a.parsePyProject(fsys, path) + if errors.Is(err, fs.ErrNotExist) { + // Assume all the packages are direct dependencies as it cannot identify them from poetry.lock + log.Logger.Debugf("Poetry: %s not found", path) + return nil + } else if err != nil { + return xerrors.Errorf("unable to parse %s: %w", path, err) + } + + for i, lib := range app.Libraries { + // Identify the direct/transitive dependencies + if _, ok := p[lib.Name]; !ok { + app.Libraries[i].Indirect = true + } + } + + return nil +} + +func (a poetryAnalyzer) parsePyProject(fsys fs.FS, path string) (map[string]interface{}, error) { + // Parse pyproject.toml + f, err := fsys.Open(path) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + parsed, err := a.pyprojectParser.Parse(f) + if err != nil { + return nil, err + } + return parsed, nil +} diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry_test.go b/pkg/fanal/analyzer/language/python/poetry/poetry_test.go new file mode 100644 index 000000000000..f4becb554cec --- /dev/null +++ b/pkg/fanal/analyzer/language/python/poetry/poetry_test.go @@ -0,0 +1,188 @@ +package poetry + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_poetryLibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + dir: "testdata/happy", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Poetry, + FilePath: "poetry.lock", + Libraries: types.Packages{ + { + ID: "certifi@2022.12.7", + Name: "certifi", + Version: "2022.12.7", + Indirect: true, + }, + { + ID: "charset-normalizer@2.1.1", + Name: "charset-normalizer", + Version: "2.1.1", + Indirect: true, + }, + { + ID: "click@7.1.2", + Name: "click", + Version: "7.1.2", + Indirect: true, + }, + { + ID: "flask@1.1.4", + Name: "flask", + Version: "1.1.4", + DependsOn: []string{ + "click@7.1.2", + "itsdangerous@1.1.0", + "jinja2@2.11.3", + "werkzeug@1.0.1", + }, + }, + { + ID: "idna@3.4", + Name: "idna", + Version: "3.4", + Indirect: true, + }, + { + ID: "itsdangerous@1.1.0", + Name: "itsdangerous", + Version: "1.1.0", + Indirect: true, + }, + { + ID: "jinja2@2.11.3", + Name: "jinja2", + Version: "2.11.3", + Indirect: true, + DependsOn: []string{ + "markupsafe@2.1.2", + }, + }, + { + ID: "markupsafe@2.1.2", + Name: "markupsafe", + Version: "2.1.2", + Indirect: true, + }, + { + ID: "requests@2.28.1", + Name: "requests", + Version: "2.28.1", + DependsOn: []string{ + "certifi@2022.12.7", + "charset-normalizer@2.1.1", + "idna@3.4", + "urllib3@1.26.14", + }, + }, + { + ID: "urllib3@1.26.14", + Name: "urllib3", + Version: "1.26.14", + Indirect: true, + }, + { + ID: "werkzeug@1.0.1", + Name: "werkzeug", + Version: "1.0.1", + Indirect: true, + }, + }, + }, + }, + }, + }, + { + name: "no pyproject.toml", + dir: "testdata/no-pyproject", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Poetry, + FilePath: "poetry.lock", + Libraries: types.Packages{ + { + ID: "click@8.1.3", + Name: "click", + Version: "8.1.3", + DependsOn: []string{ + "colorama@0.4.6", + }, + }, + { + ID: "colorama@0.4.6", + Name: "colorama", + Version: "0.4.6", + }, + }, + }, + }, + }, + }, + { + name: "wrong pyproject.toml", + dir: "testdata/wrong-pyproject", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Poetry, + FilePath: "poetry.lock", + Libraries: types.Packages{ + { + ID: "click@8.1.3", + Name: "click", + Version: "8.1.3", + DependsOn: []string{ + "colorama@0.4.6", + }, + }, + { + ID: "colorama@0.4.6", + Name: "colorama", + Version: "0.4.6", + }, + }, + }, + }, + }, + }, + { + name: "broken poetry.lock", + dir: "testdata/sad", + want: &analyzer.AnalysisResult{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newPoetryAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/python/poetry/testdata/happy/poetry.lock b/pkg/fanal/analyzer/language/python/poetry/testdata/happy/poetry.lock new file mode 100644 index 000000000000..39fcaab78167 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/poetry/testdata/happy/poetry.lock @@ -0,0 +1,225 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = true +python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = true +python-versions = ">=3.6.0" +files = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] + +[[package]] +name = "flask" +version = "1.1.4" +description = "A simple framework for building complex web applications." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "Flask-1.1.4-py2.py3-none-any.whl", hash = "sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22"}, + {file = "Flask-1.1.4.tar.gz", hash = "sha256:0fbeb6180d383a9186d0d6ed954e0042ad9f18e0e8de088b2b419d526927d196"}, +] + +[package.dependencies] +click = ">=5.1,<8.0" +itsdangerous = ">=0.24,<2.0" +Jinja2 = ">=2.10.1,<3.0" +Werkzeug = ">=0.15,<2.0" + +[package.extras] +dev = ["coverage", "pallets-sphinx-themes", "pytest", "sphinx", "sphinx-issues", "sphinxcontrib-log-cabinet", "tox"] +docs = ["pallets-sphinx-themes", "sphinx", "sphinx-issues", "sphinxcontrib-log-cabinet"] +dotenv = ["python-dotenv"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = true +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "itsdangerous" +version = "1.1.0" +description = "Various helpers to pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, +] + +[[package]] +name = "jinja2" +version = "2.11.3" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, + {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, +] + +[package.dependencies] +MarkupSafe = ">=0.23" + +[package.extras] +i18n = ["Babel (>=0.8)"] + +[[package]] +name = "markupsafe" +version = "2.1.2" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, +] + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = true +python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "urllib3" +version = "1.26.14" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, + {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "werkzeug" +version = "1.0.1" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, + {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, +] + +[package.extras] +dev = ["coverage", "pallets-sphinx-themes", "pytest", "pytest-timeout", "sphinx", "sphinx-issues", "tox"] +watchdog = ["watchdog"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "0ee6cb4bc2d84091d5dcb0a0110a65f244987ed427933b2f49949195e3ef69c7" diff --git a/pkg/fanal/analyzer/language/python/poetry/testdata/happy/pyproject.toml b/pkg/fanal/analyzer/language/python/poetry/testdata/happy/pyproject.toml new file mode 100644 index 000000000000..2df330973ff2 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/poetry/testdata/happy/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "example" +version = "0.1.0" +description = "My Hello World Example" +authors = ["Trivy"] + +[tool.poetry.dependencies] +python = "^3.9" +flask = "^1.0" +requests = {version = "2.28.1", optional = true} + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/pkg/fanal/analyzer/language/python/poetry/testdata/no-pyproject/poetry.lock b/pkg/fanal/analyzer/language/python/poetry/testdata/no-pyproject/poetry.lock new file mode 100644 index 000000000000..232e8289fa3a --- /dev/null +++ b/pkg/fanal/analyzer/language/python/poetry/testdata/no-pyproject/poetry.lock @@ -0,0 +1,33 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "c84861cc8679600635c65a32b5079dbfdf0c615c25a7db3d94c23156df8c56e9" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/python/poetry/testdata/sad/poetry.lock b/pkg/fanal/analyzer/language/python/poetry/testdata/sad/poetry.lock new file mode 100644 index 000000000000..8e2f0bef135b --- /dev/null +++ b/pkg/fanal/analyzer/language/python/poetry/testdata/sad/poetry.lock @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/python/poetry/testdata/wrong-pyproject/poetry.lock b/pkg/fanal/analyzer/language/python/poetry/testdata/wrong-pyproject/poetry.lock new file mode 100644 index 000000000000..232e8289fa3a --- /dev/null +++ b/pkg/fanal/analyzer/language/python/poetry/testdata/wrong-pyproject/poetry.lock @@ -0,0 +1,33 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "c84861cc8679600635c65a32b5079dbfdf0c615c25a7db3d94c23156df8c56e9" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/python/poetry/testdata/wrong-pyproject/pyproject.toml b/pkg/fanal/analyzer/language/python/poetry/testdata/wrong-pyproject/pyproject.toml new file mode 100644 index 000000000000..558ed37d93c5 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/poetry/testdata/wrong-pyproject/pyproject.toml @@ -0,0 +1 @@ +[ diff --git a/pkg/fanal/analyzer/language/ruby/bundler/bundler.go b/pkg/fanal/analyzer/language/ruby/bundler/bundler.go new file mode 100644 index 000000000000..49f35bf3a3d1 --- /dev/null +++ b/pkg/fanal/analyzer/language/ruby/bundler/bundler.go @@ -0,0 +1,43 @@ +package bundler + +import ( + "context" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/ruby/bundler" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&bundlerLibraryAnalyzer{}) +} + +const version = 1 + +type bundlerLibraryAnalyzer struct{} + +func (a bundlerLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + res, err := language.Analyze(types.Bundler, input.FilePath, input.Content, bundler.NewParser()) + if err != nil { + return nil, xerrors.Errorf("unable to parse Gemfile.lock: %w", err) + } + return res, nil +} + +func (a bundlerLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + return fileName == types.GemfileLock +} + +func (a bundlerLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypeBundler +} + +func (a bundlerLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/ruby/gemspec/gemspec.go b/pkg/fanal/analyzer/language/ruby/gemspec/gemspec.go new file mode 100644 index 000000000000..e422771bc565 --- /dev/null +++ b/pkg/fanal/analyzer/language/ruby/gemspec/gemspec.go @@ -0,0 +1,40 @@ +package gemspec + +import ( + "context" + "os" + "path/filepath" + "regexp" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/ruby/gemspec" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&gemspecLibraryAnalyzer{}) +} + +const version = 1 + +var fileRegex = regexp.MustCompile(`.*/specifications/.+\.gemspec`) + +type gemspecLibraryAnalyzer struct{} + +func (a gemspecLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + return language.AnalyzePackage(types.GemSpec, input.FilePath, input.Content, + gemspec.NewParser(), input.Options.FileChecksum) +} + +func (a gemspecLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return fileRegex.MatchString(filepath.ToSlash(filePath)) +} + +func (a gemspecLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypeGemSpec +} + +func (a gemspecLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go b/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go new file mode 100644 index 000000000000..4bd48d379f8b --- /dev/null +++ b/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go @@ -0,0 +1,139 @@ +package gemspec + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_gemspecLibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + includeChecksum bool + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/multiple_licenses.gemspec", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.GemSpec, + FilePath: "testdata/multiple_licenses.gemspec", + Libraries: types.Packages{ + { + Name: "test-unit", + Version: "3.3.7", + Licenses: []string{ + "Ruby", + "BSDL", + "PSFL", + }, + FilePath: "testdata/multiple_licenses.gemspec", + }, + }, + }, + }, + }, + }, + { + name: "happy path with checksum", + inputFile: "testdata/multiple_licenses.gemspec", + includeChecksum: true, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.GemSpec, + FilePath: "testdata/multiple_licenses.gemspec", + Libraries: types.Packages{ + { + Name: "test-unit", + Version: "3.3.7", + Licenses: []string{ + "Ruby", + "BSDL", + "PSFL", + }, + FilePath: "testdata/multiple_licenses.gemspec", + Digest: "sha1:6ba7904180fad7e09f224cd3e4d449ea53401fb9", + }, + }, + }, + }, + }, + }, + { + name: "empty name", + inputFile: "testdata/empty_name.gemspec", + want: nil, + wantErr: "failed to parse", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := gemspecLibraryAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + Options: analyzer.AnalysisOptions{FileChecksum: tt.includeChecksum}, + }) + + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_gemspecLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "with default", + filePath: "usr/ank/specifications/default/ank.gemspec", + want: true, + }, + { + name: "without default", + filePath: "usr/ank/specifications/ank.gemspec", + want: true, + }, + { + name: "without dot", + filePath: "usr/ank/specifications/ankgemspec", + want: false, + }, + { + name: "source gemspec", + filePath: "/localtRepo/default/ank.gemspec", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := gemspecLibraryAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/ruby/gemspec/testdata/empty_name.gemspec b/pkg/fanal/analyzer/language/ruby/gemspec/testdata/empty_name.gemspec new file mode 100644 index 000000000000..c4b5921d736c --- /dev/null +++ b/pkg/fanal/analyzer/language/ruby/gemspec/testdata/empty_name.gemspec @@ -0,0 +1,41 @@ +# -*- encoding: utf-8 -*- +# stub: test-unit 3.3.7 ruby lib + +Gem::Specification.new do |s| + s.name = "".freeze + s.version = "3.3.7" + + s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= + s.metadata = { "source_code_uri" => "https://github.com/test-unit/test-unit" } if s.respond_to? :metadata= + s.require_paths = ["lib".freeze] + s.authors = ["Kouhei Sutou".freeze, "Haruka Yoshihara".freeze] + s.date = "2020-11-18" + s.description = "test-unit (Test::Unit) is unit testing framework for Ruby, based on xUnit\nprinciples. These were originally designed by Kent Beck, creator of extreme\nprogramming software development methodology, for Smalltalk's SUnit. It allows\nwriting tests, checking results and automated testing in Ruby.\n".freeze + s.email = ["kou@cozmixng.org".freeze, "yoshihara@clear-code.com".freeze] + s.homepage = "http://test-unit.github.io/".freeze + s.licenses = ["Ruby".freeze, "BSDL".freeze, "PSFL".freeze] + s.rubygems_version = "3.2.22".freeze + s.summary = "An xUnit family unit testing framework for Ruby.".freeze + + s.installed_by_version = "3.2.22" if s.respond_to? :installed_by_version + + if s.respond_to? :specification_version then + s.specification_version = 4 + end + + if s.respond_to? :add_runtime_dependency then + s.add_runtime_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + else + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + end +end diff --git a/pkg/fanal/analyzer/language/ruby/gemspec/testdata/multiple_licenses.gemspec b/pkg/fanal/analyzer/language/ruby/gemspec/testdata/multiple_licenses.gemspec new file mode 100644 index 000000000000..d8b8b7d090fa --- /dev/null +++ b/pkg/fanal/analyzer/language/ruby/gemspec/testdata/multiple_licenses.gemspec @@ -0,0 +1,41 @@ +# -*- encoding: utf-8 -*- +# stub: test-unit 3.3.7 ruby lib + +Gem::Specification.new do |s| + s.name = "test-unit".freeze + s.version = "3.3.7" + + s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= + s.metadata = { "source_code_uri" => "https://github.com/test-unit/test-unit" } if s.respond_to? :metadata= + s.require_paths = ["lib".freeze] + s.authors = ["Kouhei Sutou".freeze, "Haruka Yoshihara".freeze] + s.date = "2020-11-18" + s.description = "test-unit (Test::Unit) is unit testing framework for Ruby, based on xUnit\nprinciples. These were originally designed by Kent Beck, creator of extreme\nprogramming software development methodology, for Smalltalk's SUnit. It allows\nwriting tests, checking results and automated testing in Ruby.\n".freeze + s.email = ["kou@cozmixng.org".freeze, "yoshihara@clear-code.com".freeze] + s.homepage = "http://test-unit.github.io/".freeze + s.licenses = ["Ruby".freeze, "BSDL".freeze, "PSFL".freeze] + s.rubygems_version = "3.2.22".freeze + s.summary = "An xUnit family unit testing framework for Ruby.".freeze + + s.installed_by_version = "3.2.22" if s.respond_to? :installed_by_version + + if s.respond_to? :specification_version then + s.specification_version = 4 + end + + if s.respond_to? :add_runtime_dependency then + s.add_runtime_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + else + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, [">= 0"]) + end +end diff --git a/pkg/fanal/analyzer/language/rust/binary/binary.go b/pkg/fanal/analyzer/language/rust/binary/binary.go new file mode 100644 index 000000000000..941baacbbee9 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/binary/binary.go @@ -0,0 +1,45 @@ +package binary + +import ( + "context" + "errors" + "os" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/rust/binary" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +func init() { + analyzer.RegisterAnalyzer(&rustBinaryLibraryAnalyzer{}) +} + +const version = 1 + +type rustBinaryLibraryAnalyzer struct{} + +func (a rustBinaryLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + res, err := language.Analyze(types.RustBinary, input.FilePath, input.Content, binary.NewParser()) + if errors.Is(err, binary.ErrUnrecognizedExe) || errors.Is(err, binary.ErrNonRustBinary) { + return nil, nil + } else if err != nil { + return nil, xerrors.Errorf("rust binary parse error: %w", err) + } + return res, nil +} + +func (a rustBinaryLibraryAnalyzer) Required(_ string, fileInfo os.FileInfo) bool { + return utils.IsExecutable(fileInfo) +} + +func (a rustBinaryLibraryAnalyzer) Type() analyzer.Type { + return analyzer.TypeRustBinary +} + +func (a rustBinaryLibraryAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/rust/binary/binary_test.go b/pkg/fanal/analyzer/language/rust/binary/binary_test.go new file mode 100644 index 000000000000..19b339670559 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/binary/binary_test.go @@ -0,0 +1,108 @@ +package binary + +import ( + "context" + "os" + "runtime" + "testing" + + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_rustBinaryLibraryAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + inputFile: "testdata/executable_rust", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.RustBinary, + FilePath: "testdata/executable_rust", + Libraries: types.Packages{ + { + ID: "crate_with_features@0.1.0", + Name: "crate_with_features", + Version: "0.1.0", + DependsOn: []string{"library_crate@0.1.0"}, + }, + { + ID: "library_crate@0.1.0", + Name: "library_crate", + Version: "0.1.0", + Indirect: true, + }, + }, + }, + }, + }, + }, + { + name: "not rust binary", + inputFile: "testdata/executable_bash", + }, + { + name: "broken elf", + inputFile: "testdata/broken_elf", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := rustBinaryLibraryAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_rustBinaryLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "executable file", + filePath: lo.Ternary(runtime.GOOS == "windows", "testdata/binary.exe", "testdata/0755"), + want: true, + }, + { + name: "file perm 0644", + filePath: "testdata/0644", + want: false, + }, + { + name: "symlink", + filePath: "testdata/symlink", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := rustBinaryLibraryAnalyzer{} + fileInfo, err := os.Lstat(tt.filePath) + require.NoError(t, err) + got := a.Required(tt.filePath, fileInfo) + assert.Equal(t, tt.want, got, fileInfo.Mode().Perm()) + }) + } +} diff --git a/pkg/fanal/analyzer/language/rust/binary/testdata/0644 b/pkg/fanal/analyzer/language/rust/binary/testdata/0644 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/language/rust/binary/testdata/0755 b/pkg/fanal/analyzer/language/rust/binary/testdata/0755 new file mode 100755 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/language/rust/binary/testdata/binary.exe b/pkg/fanal/analyzer/language/rust/binary/testdata/binary.exe new file mode 100644 index 000000000000..f8e020bf1ebe --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/binary/testdata/binary.exe @@ -0,0 +1 @@ +exe \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/binary/testdata/broken_elf b/pkg/fanal/analyzer/language/rust/binary/testdata/broken_elf new file mode 100755 index 000000000000..1a736bf298d6 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/binary/testdata/broken_elf @@ -0,0 +1 @@ +ELF diff --git a/pkg/fanal/analyzer/language/rust/binary/testdata/executable_bash b/pkg/fanal/analyzer/language/rust/binary/testdata/executable_bash new file mode 100755 index 000000000000..ca3b9f0d2cda --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/binary/testdata/executable_bash @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "hello" diff --git a/pkg/fanal/analyzer/language/rust/binary/testdata/executable_rust b/pkg/fanal/analyzer/language/rust/binary/testdata/executable_rust new file mode 100755 index 000000000000..cadfee87b008 Binary files /dev/null and b/pkg/fanal/analyzer/language/rust/binary/testdata/executable_rust differ diff --git a/pkg/fanal/analyzer/language/rust/binary/testdata/foo b/pkg/fanal/analyzer/language/rust/binary/testdata/foo new file mode 100755 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/language/rust/binary/testdata/symlink b/pkg/fanal/analyzer/language/rust/binary/testdata/symlink new file mode 120000 index 000000000000..19102815663d --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/binary/testdata/symlink @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo.go b/pkg/fanal/analyzer/language/rust/cargo/cargo.go new file mode 100644 index 000000000000..f487ba0c46e8 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo.go @@ -0,0 +1,281 @@ +package cargo + +import ( + "context" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "sort" + + "github.com/BurntSushi/toml" + "github.com/samber/lo" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/go-version/pkg/semver" + goversion "github.com/aquasecurity/go-version/pkg/version" + "github.com/aquasecurity/trivy/pkg/dependency/parser/rust/cargo" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeCargo, newCargoAnalyzer) +} + +const version = 1 + +var requiredFiles = []string{ + types.CargoLock, + types.CargoToml, +} + +type cargoAnalyzer struct { + lockParser godeptypes.Parser + comparer compare.GenericComparer +} + +func newCargoAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &cargoAnalyzer{ + lockParser: cargo.NewParser(), + comparer: compare.GenericComparer{}, + }, nil +} + +func (a cargoAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == types.CargoLock + } + + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { + // Parse Cargo.lock + app, err := a.parseCargoLock(filePath, r) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil + } + + // Parse Cargo.toml alongside Cargo.lock to identify the direct dependencies + if err = a.removeDevDependencies(input.FS, path.Dir(filePath), app); err != nil { + log.Logger.Warnf("Unable to parse %q to identify direct dependencies: %s", path.Join(path.Dir(filePath), types.CargoToml), err) + } + sort.Sort(app.Libraries) + apps = append(apps, *app) + + return nil + }) + if err != nil { + return nil, xerrors.Errorf("cargo walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a cargoAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + return slices.Contains(requiredFiles, fileName) +} + +func (a cargoAnalyzer) Type() analyzer.Type { + return analyzer.TypeCargo +} + +func (a cargoAnalyzer) Version() int { + return version +} + +func (a cargoAnalyzer) parseCargoLock(filePath string, r io.Reader) (*types.Application, error) { + return language.Parse(types.Cargo, filePath, r, a.lockParser) +} + +func (a cargoAnalyzer) removeDevDependencies(fsys fs.FS, dir string, app *types.Application) error { + cargoTOMLPath := path.Join(dir, types.CargoToml) + directDeps, err := a.parseRootCargoTOML(fsys, cargoTOMLPath) + if errors.Is(err, fs.ErrNotExist) { + log.Logger.Debugf("Cargo: %s not found", cargoTOMLPath) + return nil + } else if err != nil { + return xerrors.Errorf("unable to parse %s: %w", cargoTOMLPath, err) + } + + // Cargo.toml file can contain same libraries with different versions. + // Save versions separately for version comparison by comparator + pkgIDs := lo.SliceToMap(app.Libraries, func(pkg types.Package) (string, types.Package) { + return pkg.ID, pkg + }) + + // Identify direct dependencies + pkgs := make(map[string]types.Package) + for name, constraint := range directDeps { + for _, pkg := range app.Libraries { + if pkg.Name != name { + continue + } + + if match, err := a.matchVersion(pkg.Version, constraint); err != nil { + log.Logger.Warnf("Unable to match Cargo version: package: %s, error: %s", pkg.ID, err) + continue + } else if match { + // Mark as a direct dependency + pkg.Indirect = false + pkgs[pkg.ID] = pkg + break + } + } + } + + // Walk indirect dependencies + // Since it starts from direct dependencies, devDependencies will not appear in this walk. + for _, pkg := range pkgs { + a.walkIndirectDependencies(pkg, pkgIDs, pkgs) + } + + pkgSlice := maps.Values(pkgs) + sort.Sort(types.Packages(pkgSlice)) + + // Save only prod libraries + app.Libraries = pkgSlice + return nil +} + +type cargoToml struct { + Dependencies Dependencies `toml:"dependencies"` + Target map[string]map[string]Dependencies `toml:"target"` + Workspace cargoTomlWorkspace `toml:"workspace"` +} + +type cargoTomlWorkspace struct { + Dependencies Dependencies `toml:"dependencies"` + Members []string `toml:"members"` +} + +type Dependencies map[string]interface{} + +// parseRootCargoTOML parses top-level Cargo.toml and returns dependencies. +// It also parses workspace members and their dependencies. +func (a cargoAnalyzer) parseRootCargoTOML(fsys fs.FS, filePath string) (map[string]string, error) { + dependencies, members, err := parseCargoTOML(fsys, filePath) + if err != nil { + return nil, xerrors.Errorf("unable to parse %s: %w", filePath, err) + } + // According to Cargo workspace RFC, workspaces can't be nested: + // https://github.com/nox/rust-rfcs/blob/master/text/1525-cargo-workspace.md#validating-a-workspace + for _, member := range members { + memberPath := path.Join(path.Dir(filePath), member, types.CargoToml) + memberDeps, _, err := parseCargoTOML(fsys, memberPath) + if err != nil { + log.Logger.Warnf("Unable to parse %q: %s", memberPath, err) + continue + } + // Member dependencies shouldn't overwrite dependencies from root cargo.toml file + maps.Copy(memberDeps, dependencies) + dependencies = memberDeps + } + + deps := make(map[string]string) + for name, value := range dependencies { + switch ver := value.(type) { + case string: + // e.g. regex = "1.5" + deps[name] = ver + case map[string]interface{}: + // e.g. serde = { version = "1.0", features = ["derive"] } + for k, v := range ver { + if k == "version" { + if vv, ok := v.(string); ok { + deps[name] = vv + } + break + } + } + } + } + + return deps, nil +} + +func (a cargoAnalyzer) walkIndirectDependencies(pkg types.Package, pkgIDs, deps map[string]types.Package) { + for _, pkgID := range pkg.DependsOn { + if _, ok := deps[pkgID]; ok { + continue + } + + dep, ok := pkgIDs[pkgID] + if !ok { + continue + } + + dep.Indirect = true + deps[dep.ID] = dep + a.walkIndirectDependencies(dep, pkgIDs, deps) + } +} + +// cf. https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html +func (a cargoAnalyzer) matchVersion(currentVersion, constraint string) (bool, error) { + // `` == `^` - https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#caret-requirements + // Add `^` for correct version comparison + // - 1.2.3 -> ^1.2.3 + // - 1.2.* -> 1.2.* + // - ^1.2 -> ^1.2 + if _, err := goversion.Parse(constraint); err == nil { + constraint = fmt.Sprintf("^%s", constraint) + } + + ver, err := semver.Parse(currentVersion) + if err != nil { + return false, xerrors.Errorf("version error (%s): %s", currentVersion, err) + } + + c, err := semver.NewConstraints(constraint) + if err != nil { + return false, xerrors.Errorf("constraint error (%s): %s", currentVersion, err) + } + + return c.Check(ver), nil +} + +func parseCargoTOML(fsys fs.FS, filePath string) (Dependencies, []string, error) { + // Parse Cargo.toml + f, err := fsys.Open(filePath) + if err != nil { + return nil, nil, xerrors.Errorf("file open error: %w", err) + } + defer func() { _ = f.Close() }() + + var tomlFile cargoToml + // There are cases when toml file doesn't include `Dependencies` field (then map will be nil). + // e.g. when only `workspace.Dependencies` are used + // declare `dependencies` to avoid panic + dependencies := Dependencies{} + if _, err = toml.NewDecoder(f).Decode(&tomlFile); err != nil { + return nil, nil, xerrors.Errorf("toml decode error: %w", err) + } + + maps.Copy(dependencies, tomlFile.Dependencies) + + // https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies + for _, target := range tomlFile.Target { + maps.Copy(dependencies, target["dependencies"]) + } + + // https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#inheriting-a-dependency-from-a-workspace + maps.Copy(dependencies, tomlFile.Workspace.Dependencies) + // https://doc.rust-lang.org/cargo/reference/workspaces.html#the-members-and-exclude-fields + return dependencies, tomlFile.Workspace.Members, nil +} diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go new file mode 100644 index 000000000000..ef699f4eff72 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go @@ -0,0 +1,606 @@ +package cargo + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/detector/library/compare" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_cargoAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + dir: "testdata/happy", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Cargo, + FilePath: "Cargo.lock", + Libraries: types.Packages{ + { + ID: "aho-corasick@0.7.20", + Name: "aho-corasick", + Version: "0.7.20", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 11, + }, + }, + DependsOn: []string{"memchr@2.5.0"}, + }, + { + ID: "libc@0.2.140", + Name: "libc", + Version: "0.2.140", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 26, + }, + }, + }, + { + ID: "memchr@1.0.2", + Name: "memchr", + Version: "1.0.2", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 28, + EndLine: 35, + }, + }, + DependsOn: []string{"libc@0.2.140"}, + }, + { + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 37, + EndLine: 41, + }, + }, + }, + { + ID: "regex@1.7.3", + Name: "regex", + Version: "1.7.3", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 43, + EndLine: 52, + }, + }, + DependsOn: []string{ + "aho-corasick@0.7.20", + "memchr@2.5.0", + "regex-syntax@0.6.29", + }, + }, + { + ID: "regex-syntax@0.5.6", + Name: "regex-syntax", + Version: "0.5.6", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 54, + EndLine: 61, + }, + }, + DependsOn: []string{"ucd-util@0.1.10"}, + }, + { + ID: "regex-syntax@0.6.29", + Name: "regex-syntax", + Version: "0.6.29", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 63, + EndLine: 67, + }, + }, + }, + { + ID: "ucd-util@0.1.10", + Name: "ucd-util", + Version: "0.1.10", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 69, + EndLine: 73, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Cargo.toml doesn't include `Dependencies` field", + dir: "testdata/toml-only-workspace-deps", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Cargo, + FilePath: "Cargo.lock", + Libraries: types.Packages{ + { + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 15, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "no Cargo.toml", + dir: "testdata/no-cargo-toml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Cargo, + FilePath: "Cargo.lock", + Libraries: types.Packages{ + { + ID: "aho-corasick@0.7.20", + Name: "aho-corasick", + Version: "0.7.20", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 11, + }, + }, + DependsOn: []string{"memchr@2.5.0"}, + }, + { + ID: "app@0.1.0", + Name: "app", + Version: "0.1.0", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 13, + EndLine: 20, + }, + }, + DependsOn: []string{ + "memchr@1.0.2", + "regex-syntax@0.5.6", + "regex@1.7.3", + }, + }, + { + ID: "libc@0.2.140", + Name: "libc", + Version: "0.2.140", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 26, + }, + }, + }, + { + ID: "memchr@1.0.2", + Name: "memchr", + Version: "1.0.2", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 28, + EndLine: 35, + }, + }, + DependsOn: []string{"libc@0.2.140"}, + }, + { + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 37, + EndLine: 41, + }, + }, + }, + { + ID: "regex@1.7.3", + Name: "regex", + Version: "1.7.3", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 43, + EndLine: 52, + }, + }, + DependsOn: []string{ + "aho-corasick@0.7.20", + "memchr@2.5.0", + "regex-syntax@0.6.29", + }, + }, + { + ID: "regex-syntax@0.5.6", + Name: "regex-syntax", + Version: "0.5.6", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 54, + EndLine: 61, + }, + }, + DependsOn: []string{"ucd-util@0.1.10"}, + }, + { + ID: "regex-syntax@0.6.29", + Name: "regex-syntax", + Version: "0.6.29", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 63, + EndLine: 67, + }, + }, + }, + { + ID: "ucd-util@0.1.10", + Name: "ucd-util", + Version: "0.1.10", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 69, + EndLine: 73, + }, + }, + }, + { + ID: "winapi@0.3.9", + Name: "winapi", + Version: "0.3.9", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 75, + EndLine: 83, + }, + }, + DependsOn: []string{ + "winapi-i686-pc-windows-gnu@0.4.0", + "winapi-x86_64-pc-windows-gnu@0.4.0", + }, + }, + { + ID: "winapi-i686-pc-windows-gnu@0.4.0", + Name: "winapi-i686-pc-windows-gnu", + Version: "0.4.0", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 85, + EndLine: 89, + }, + }, + }, + { + ID: "winapi-x86_64-pc-windows-gnu@0.4.0", + Name: "winapi-x86_64-pc-windows-gnu", + Version: "0.4.0", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 91, + EndLine: 95, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "wrong Cargo.toml", + dir: "testdata/wrong-cargo-toml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Cargo, + FilePath: "Cargo.lock", + Libraries: types.Packages{ + { + ID: "app@0.1.0", + Name: "app", + Version: "0.1.0", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 10, + }, + }, + DependsOn: []string{"memchr@2.5.0"}, + }, + { + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 12, + EndLine: 16, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "broken Cargo.lock", + dir: "testdata/sad", + want: &analyzer.AnalysisResult{}, + }, + { + name: "workspace members", + dir: "testdata/toml-workspace-members", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Cargo, + FilePath: "Cargo.lock", + Libraries: types.Packages{ + { + ID: "aho-corasick@1.1.2", + Name: "aho-corasick", + Version: "1.1.2", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 12, + }, + }, + DependsOn: []string{"memchr@2.6.4"}, + }, + { + ID: "gdb-command@0.7.6", + Name: "gdb-command", + Version: "0.7.6", + Indirect: false, + Locations: []types.Location{ + { + StartLine: 14, + EndLine: 22, + }, + }, + DependsOn: []string{ + "regex@1.10.2", + "wait-timeout@0.2.0", + }, + }, + { + ID: "libc@0.2.150", + Name: "libc", + Version: "0.2.150", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 24, + EndLine: 28, + }, + }, + }, + { + ID: "memchr@2.6.4", + Name: "memchr", + Version: "2.6.4", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 44, + EndLine: 48, + }, + }, + }, + { + ID: "regex@1.10.2", + Name: "regex", + Version: "1.10.2", + Locations: []types.Location{ + { + StartLine: 50, + EndLine: 60, + }, + }, + DependsOn: []string{ + "aho-corasick@1.1.2", + "memchr@2.6.4", + "regex-automata@0.4.3", + "regex-syntax@0.8.2", + }, + }, + { + ID: "regex-automata@0.4.3", + Name: "regex-automata", + Version: "0.4.3", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 62, + EndLine: 71, + }, + }, + DependsOn: []string{ + "aho-corasick@1.1.2", + "memchr@2.6.4", + "regex-syntax@0.8.2", + }, + }, + { + ID: "regex-syntax@0.8.2", + Name: "regex-syntax", + Version: "0.8.2", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 73, + EndLine: 77, + }, + }, + }, + { + ID: "wait-timeout@0.2.0", + Name: "wait-timeout", + Version: "0.2.0", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 79, + EndLine: 86, + }, + }, + DependsOn: []string{"libc@0.2.150"}, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newCargoAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestMatchVersion(t *testing.T) { + tests := []struct { + name string + version string // version from Cargo.lock + constraint string // version from Cargo.toml + want bool + }{ + { + name: "major version == 0.", + version: "0.0.4", + constraint: "0.0.3", + want: false, + }, + { + name: "major version > 0.", + version: "1.2.4", + constraint: "1.2.3", + want: true, + }, + { + name: "Caret prefix", + version: "1.5.0", + constraint: "^1.2", + want: true, + }, + { + name: "Tilde prefix. Minor version", + version: "1.3.4", + constraint: "~ 1.2", + want: false, + }, + { + name: "Tilde prefix. Patch version", + version: "1.2.4", + constraint: "~ 1.2.3", + want: true, + }, + { + name: "Comparison prefix", + version: "2.5.0", + constraint: "< 2.5.0", + want: false, + }, + { + name: "Multiple prefixes", + version: "2.5.0", + constraint: ">= 2.5, < 2.5.1", + want: true, + }, + { + name: "= prefix", + version: "2.5.0", + constraint: "= 2.5", + want: true, + }, + { + name: "`*` constraint", + version: "2.5.0", + constraint: "*", + want: true, + }, + { + name: "constraint with `.*`", + version: "2.5.0", + constraint: "2.5.*", + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := cargoAnalyzer{ + comparer: compare.GenericComparer{}, + } + match, _ := a.matchVersion(tt.version, tt.constraint) + assert.Equal(t, tt.want, match) + }) + } +} diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.lock b/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.lock new file mode 100644 index 000000000000..af569f245848 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.lock @@ -0,0 +1,95 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +dependencies = [ + "libc 0.2.140 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +dependencies = [ + "ucd-util 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "ucd-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.toml b/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.toml new file mode 100644 index 000000000000..26b080c2575d --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "app" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +regex = "=1.7.3" + +[target.'cfg(not(target_os = "windows"))'.dependencies] +memchr = { version = "1.*", optional = true } + +[workspace.dependencies] +regex-syntax = { version = "<0.6"} + +[dev-dependencies] +winapi = "*" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/no-cargo-toml/Cargo.lock b/pkg/fanal/analyzer/language/rust/cargo/testdata/no-cargo-toml/Cargo.lock new file mode 100644 index 000000000000..af569f245848 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/no-cargo-toml/Cargo.lock @@ -0,0 +1,95 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +dependencies = [ + "libc 0.2.140 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +dependencies = [ + "ucd-util 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "ucd-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/sad/Cargo.lock b/pkg/fanal/analyzer/language/rust/cargo/testdata/sad/Cargo.lock new file mode 100644 index 000000000000..8e2f0bef135b --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/sad/Cargo.lock @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-only-workspace-deps/Cargo.lock b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-only-workspace-deps/Cargo.lock new file mode 100644 index 000000000000..1dcc60734a75 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-only-workspace-deps/Cargo.lock @@ -0,0 +1,15 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-only-workspace-deps/Cargo.toml b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-only-workspace-deps/Cargo.toml new file mode 100644 index 000000000000..a5ad9ebb04d4 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-only-workspace-deps/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "app" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[workspace.dependencies] +memchr = "2.5" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/Cargo.lock b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/Cargo.lock new file mode 100644 index 000000000000..66afdd45c106 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/Cargo.lock @@ -0,0 +1,86 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "gdb-command" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a067c49eb3dfc7c2357d3d37536d2f57c5250ac377672164286b8d5ea94c5d" +dependencies = [ + "regex", + "wait-timeout", +] + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "member" +version = "0.1.0" +dependencies = [ + "gdb-command", +] + +[[package]] +name = "member2" +version = "0.1.0" +dependencies = [ + "regex", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/Cargo.toml b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/Cargo.toml new file mode 100644 index 000000000000..0a0b822e61d4 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/Cargo.toml @@ -0,0 +1,10 @@ +[workspace.package] +name = "toml-workspace-members" +version = "0.1.0" + +[workspace] +resolver = "2" +members = ["member", "member2"] + +[workspace.dependencies] +regex = "1" diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/member/Cargo.toml b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/member/Cargo.toml new file mode 100644 index 000000000000..3be79eac93e1 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/member/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "member" +version = "0.1.0" +edition = "2021" + +[dependencies] +gdb-command = "0.7" diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/member2/Cargo.toml b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/member2/Cargo.toml new file mode 100644 index 000000000000..297d3a8659e9 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/toml-workspace-members/member2/Cargo.toml @@ -0,0 +1,7 @@ +[package] +edition = "2021" +name = "member2" +version = "0.1.0" + +[dependencies] +regex = { workspace = true} diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/wrong-cargo-toml/Cargo.lock b/pkg/fanal/analyzer/language/rust/cargo/testdata/wrong-cargo-toml/Cargo.lock new file mode 100644 index 000000000000..981df5958d78 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/wrong-cargo-toml/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/wrong-cargo-toml/Cargo.toml b/pkg/fanal/analyzer/language/rust/cargo/testdata/wrong-cargo-toml/Cargo.toml new file mode 100644 index 000000000000..8e2f0bef135b --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/wrong-cargo-toml/Cargo.toml @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods.go b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods.go new file mode 100644 index 000000000000..9a10d42f8870 --- /dev/null +++ b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods.go @@ -0,0 +1,45 @@ +package cocoapods + +import ( + "context" + "os" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/swift/cocoapods" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&cocoaPodsLockAnalyzer{}) +} + +const ( + version = 1 +) + +// cocoaPodsLockAnalyzer analyzes Podfile.lock +type cocoaPodsLockAnalyzer struct{} + +func (a cocoaPodsLockAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + p := cocoapods.NewParser() + res, err := language.Analyze(types.Cocoapods, input.FilePath, input.Content, p) + if err != nil { + return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err) + } + return res, nil +} + +func (a cocoaPodsLockAnalyzer) Required(_ string, fileInfo os.FileInfo) bool { + return fileInfo.Name() == types.CocoaPodsLock +} + +func (a cocoaPodsLockAnalyzer) Type() analyzer.Type { + return analyzer.TypeCocoaPods +} + +func (a cocoaPodsLockAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go new file mode 100644 index 000000000000..fcf2e7254f59 --- /dev/null +++ b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go @@ -0,0 +1,98 @@ +package cocoapods + +import ( + "os" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_cocoaPodsLockAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + inputFile: "testdata/happy.lock", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Cocoapods, + FilePath: "testdata/happy.lock", + Libraries: types.Packages{ + { + ID: "AppCenter@4.2.0", + Name: "AppCenter", + Version: "4.2.0", + DependsOn: []string{ + "AppCenter/Analytics@4.2.0", + "AppCenter/Crashes@4.2.0", + }, + }, + { + ID: "AppCenter/Analytics@4.2.0", + Name: "AppCenter/Analytics", + Version: "4.2.0", + DependsOn: []string{ + "AppCenter/Core@4.2.0", + }, + }, + { + ID: "AppCenter/Core@4.2.0", + Name: "AppCenter/Core", + Version: "4.2.0", + }, + { + ID: "AppCenter/Crashes@4.2.0", + Name: "AppCenter/Crashes", + Version: "4.2.0", + DependsOn: []string{ + "AppCenter/Core@4.2.0", + }, + }, + { + ID: "KeychainAccess@4.2.1", + Name: "KeychainAccess", + Version: "4.2.1", + }, + }, + }, + }, + }, + }, + { + name: "empty file", + inputFile: "testdata/empty.lock", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := cocoaPodsLockAnalyzer{} + got, err := a.Analyze(nil, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + if got != nil { + for _, app := range got.Applications { + sort.Sort(app.Libraries) + } + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/swift/cocoapods/testdata/empty.lock b/pkg/fanal/analyzer/language/swift/cocoapods/testdata/empty.lock new file mode 100644 index 000000000000..142864b62d23 --- /dev/null +++ b/pkg/fanal/analyzer/language/swift/cocoapods/testdata/empty.lock @@ -0,0 +1 @@ +COCOAPODS: 1.11.2 \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/swift/cocoapods/testdata/happy.lock b/pkg/fanal/analyzer/language/swift/cocoapods/testdata/happy.lock new file mode 100644 index 000000000000..65600c35476c --- /dev/null +++ b/pkg/fanal/analyzer/language/swift/cocoapods/testdata/happy.lock @@ -0,0 +1,12 @@ +PODS: + - AppCenter (4.2.0): + - AppCenter/Analytics (= 4.2.0) + - AppCenter/Crashes (= 4.2.0) + - AppCenter/Analytics (4.2.0): + - AppCenter/Core + - AppCenter/Core (4.2.0) + - AppCenter/Crashes (4.2.0): + - AppCenter/Core + - KeychainAccess (4.2.1) + +COCOAPODS: 1.11.2 \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/swift/swift/swift.go b/pkg/fanal/analyzer/language/swift/swift/swift.go new file mode 100644 index 000000000000..2b140545b2fb --- /dev/null +++ b/pkg/fanal/analyzer/language/swift/swift/swift.go @@ -0,0 +1,46 @@ +package swift + +import ( + "context" + "os" + "path" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/swift/swift" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&swiftLockAnalyzer{}) +} + +const ( + version = 1 +) + +// swiftLockAnalyzer analyzes Package.resolved files +type swiftLockAnalyzer struct{} + +func (a swiftLockAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + p := swift.NewParser() + res, err := language.Analyze(types.Swift, input.FilePath, input.Content, p) + if err != nil { + return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err) + } + return res, nil +} + +func (a swiftLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return path.Base(filePath) == types.SwiftResolved +} + +func (a swiftLockAnalyzer) Type() analyzer.Type { + return analyzer.TypeSwift +} + +func (a swiftLockAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/swift/swift/swift_test.go b/pkg/fanal/analyzer/language/swift/swift/swift_test.go new file mode 100644 index 000000000000..9a7fc981c1fc --- /dev/null +++ b/pkg/fanal/analyzer/language/swift/swift/swift_test.go @@ -0,0 +1,90 @@ +package swift + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_swiftLockAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + inputFile: "testdata/happy/Package.resolved", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Swift, + FilePath: "testdata/happy/Package.resolved", + Libraries: types.Packages{ + + { + ID: "github.com/Quick/Nimble@9.2.1", + Name: "github.com/Quick/Nimble", + Version: "9.2.1", + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 12, + }, + }, + }, + { + ID: "github.com/Quick/Quick@7.0.0", + Name: "github.com/Quick/Quick", + Version: "7.0.0", + Locations: []types.Location{ + { + StartLine: 13, + EndLine: 21, + }, + }, + }, + { + ID: "github.com/ReactiveCocoa/ReactiveSwift@7.1.1", + Name: "github.com/ReactiveCocoa/ReactiveSwift", + Version: "7.1.1", + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 30, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "empty file", + inputFile: "testdata/empty/Package.resolved", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := swiftLockAnalyzer{} + got, err := a.Analyze(nil, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/swift/swift/testdata/empty/Package.resolved b/pkg/fanal/analyzer/language/swift/swift/testdata/empty/Package.resolved new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/pkg/fanal/analyzer/language/swift/swift/testdata/empty/Package.resolved @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/swift/swift/testdata/happy/Package.resolved b/pkg/fanal/analyzer/language/swift/swift/testdata/happy/Package.resolved new file mode 100644 index 000000000000..73b088823abc --- /dev/null +++ b/pkg/fanal/analyzer/language/swift/swift/testdata/happy/Package.resolved @@ -0,0 +1,34 @@ +{ + "object": { + "pins": [ + { + "package": "Nimble", + "repositoryURL": "https://github.com/Quick/Nimble.git", + "state": { + "branch": null, + "revision": "c93f16c25af5770f0d3e6af27c9634640946b068", + "version": "9.2.1" + } + }, + { + "package": "Quick", + "repositoryURL": "https://github.com/Quick/Quick.git", + "state": { + "branch": null, + "revision": "e206b8deba0d01fce70388a6d9dc66cba5603958", + "version": "7.0.0" + } + }, + { + "package": "ReactiveSwift", + "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift", + "state": { + "branch": null, + "revision": "40c465af19b993344e84355c00669ba2022ca3cd", + "version": "7.1.1" + } + } + ] + }, + "version": 1 +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/licensing/license.go b/pkg/fanal/analyzer/licensing/license.go new file mode 100644 index 000000000000..3e3986d732f8 --- /dev/null +++ b/pkg/fanal/analyzer/licensing/license.go @@ -0,0 +1,125 @@ +package licensing + +import ( + "context" + "io" + "math" + "os" + "path/filepath" + "strings" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/log" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +const version = 1 + +var ( + skipDirs = []string{ + "node_modules/", // node scan will pick these up + "usr/share/doc/", // dpkg will pick these up + + // Some heuristic exclusion + "usr/lib", + "usr/local/include", + "usr/include", + "usr/lib/python", + "usr/local/go", + "opt/yarn", + "usr/lib/gems", + "usr/src/wordpress", + } + + acceptedExtensions = []string{ + ".asp", ".aspx", ".bas", ".bat", ".b", ".c", ".cue", ".cgi", ".cs", ".css", ".fish", ".html", ".h", ".ini", + ".java", ".js", ".jsx", ".markdown", ".md", ".py", ".php", ".pl", ".r", ".rb", ".sh", ".sql", ".ts", + ".tsx", ".txt", ".vue", ".zsh", + } + + acceptedFileNames = []string{ + "license", "licence", "copyright", + } +) + +func init() { + analyzer.RegisterAnalyzer(&licenseFileAnalyzer{}) +} + +// licenseFileAnalyzer is an analyzer for file headers and license files +type licenseFileAnalyzer struct { + classifierConfidenceLevel float64 +} + +func (a licenseFileAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + log.Logger.Debugf("License scanning: %s", input.FilePath) + + // need files to be text based, readable files + readable, err := isHumanReadable(input.Content, input.Info.Size()) + if err != nil || !readable { + return nil, nil + } + lf, err := licensing.Classify(input.FilePath, input.Content, a.classifierConfidenceLevel) + if err != nil { + return nil, xerrors.Errorf("license classification error: %w", err) + } else if len(lf.Findings) == 0 { + return nil, nil + } + + return &analyzer.AnalysisResult{ + Licenses: []types.LicenseFile{*lf}, + }, nil +} + +func (a *licenseFileAnalyzer) Init(opt analyzer.AnalyzerOptions) error { + a.classifierConfidenceLevel = opt.LicenseScannerOption.ClassifierConfidenceLevel + return nil +} + +func (a licenseFileAnalyzer) Required(filePath string, _ os.FileInfo) bool { + for _, skipDir := range skipDirs { + if strings.Contains(filePath, skipDir) { + return false + } + } + ext := strings.ToLower(filepath.Ext(filePath)) + if slices.Contains(acceptedExtensions, ext) { + return true + } + + baseName := strings.ToLower(filepath.Base(filePath)) + return slices.Contains(acceptedFileNames, baseName) +} + +func isHumanReadable(content xio.ReadSeekerAt, fileSize int64) (bool, error) { + headSize := int(math.Min(float64(fileSize), 300)) + head := make([]byte, headSize) + if _, err := content.Read(head); err != nil { + return false, err + } + if _, err := content.Seek(0, io.SeekStart); err != nil { + return false, err + } + + // cf. https://github.com/file/file/blob/f2a6e7cb7db9b5fd86100403df6b2f830c7f22ba/src/encoding.c#L151-L228 + for _, b := range head { + if b < 7 || b == 11 || (13 < b && b < 27) || (27 < b && b < 0x20) || b == 0x7f { + return false, nil + } + } + + return true, nil +} + +func (a licenseFileAnalyzer) Type() analyzer.Type { + return analyzer.TypeLicenseFile +} + +func (a licenseFileAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/licensing/license_test.go b/pkg/fanal/analyzer/licensing/license_test.go new file mode 100644 index 000000000000..fd04029873d9 --- /dev/null +++ b/pkg/fanal/analyzer/licensing/license_test.go @@ -0,0 +1,98 @@ +package licensing + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_licenseAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + filePath string + want *analyzer.AnalysisResult + }{ + { + name: "Licensed C file", + filePath: "testdata/licensed.c", + want: &analyzer.AnalysisResult{ + Licenses: []types.LicenseFile{ + { + Type: types.LicenseTypeHeader, + FilePath: "testdata/licensed.c", + Findings: []types.LicenseFinding{ + { + Name: "AGPL-3.0", + Confidence: 1, + Link: "https://spdx.org/licenses/AGPL-3.0.html", + }, + }, + }, + }, + }, + }, + { + name: "Non human readable binary file", + filePath: "testdata/binaryfile", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.filePath) + require.NoError(t, err) + defer f.Close() + + fi, err := f.Stat() + require.NoError(t, err) + + a := licenseFileAnalyzer{} + got, err := a.Analyze(context.TODO(), analyzer.AnalysisInput{ + FilePath: tt.filePath, + Content: f, + Info: fi, + }) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } + +} + +func Test_licenseAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "C file with license", + filePath: "testdata/licensed.c", + want: true, + }, + { + name: "C file without license", + filePath: "testdata/unlicensed.c", + want: true, + }, + { + name: "Unreadable file", + filePath: "testdata/binaryfile", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := licenseFileAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/licensing/testdata/binaryfile b/pkg/fanal/analyzer/licensing/testdata/binaryfile new file mode 100644 index 000000000000..df93f5f3f724 Binary files /dev/null and b/pkg/fanal/analyzer/licensing/testdata/binaryfile differ diff --git a/pkg/fanal/analyzer/licensing/testdata/licensed.c b/pkg/fanal/analyzer/licensing/testdata/licensed.c new file mode 100644 index 000000000000..f66b373b7e68 --- /dev/null +++ b/pkg/fanal/analyzer/licensing/testdata/licensed.c @@ -0,0 +1,24 @@ +/* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. + +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ + +int func1() { + /* implementation */ + return 0; +} + +int func2() { + /* implementation */ + return 0; +} diff --git a/pkg/fanal/analyzer/os/alpine/alpine.go b/pkg/fanal/analyzer/os/alpine/alpine.go new file mode 100644 index 000000000000..0caa5189b8f5 --- /dev/null +++ b/pkg/fanal/analyzer/os/alpine/alpine.go @@ -0,0 +1,50 @@ +package alpine + +import ( + "bufio" + "context" + "os" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&alpineOSAnalyzer{}) +} + +const version = 1 + +var requiredFiles = []string{"etc/alpine-release"} + +type alpineOSAnalyzer struct{} + +func (a alpineOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + scanner := bufio.NewScanner(input.Content) + for scanner.Scan() { + line := scanner.Text() + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Alpine, + Name: line, + }, + }, nil + } + return nil, xerrors.Errorf("alpine: %w", fos.AnalyzeOSError) +} + +func (a alpineOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return slices.Contains(requiredFiles, filePath) +} + +func (a alpineOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeAlpine +} + +func (a alpineOSAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/os/alpine/alpine_test.go b/pkg/fanal/analyzer/os/alpine/alpine_test.go new file mode 100644 index 000000000000..29a2496ba556 --- /dev/null +++ b/pkg/fanal/analyzer/os/alpine/alpine_test.go @@ -0,0 +1,50 @@ +package alpine + +import ( + "context" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestAlpineReleaseOSAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + input analyzer.AnalysisInput + wantResult *analyzer.AnalysisResult + wantError string + }{ + { + name: "happy path", + input: analyzer.AnalysisInput{ + FilePath: "/etc/alpine-release", + Content: strings.NewReader("3.15.4"), + }, + wantResult: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Alpine, + Name: "3.15.4", + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + a := alpineOSAnalyzer{} + res, err := a.Analyze(context.Background(), test.input) + + if test.wantError != "" { + assert.NotNil(t, err) + assert.Equal(t, test.wantError, err.Error()) + } else { + assert.Nil(t, err) + assert.Equal(t, test.wantResult, res) + } + }) + } +} diff --git a/pkg/fanal/analyzer/os/amazonlinux/amazonlinux.go b/pkg/fanal/analyzer/os/amazonlinux/amazonlinux.go new file mode 100644 index 000000000000..2dc96646719f --- /dev/null +++ b/pkg/fanal/analyzer/os/amazonlinux/amazonlinux.go @@ -0,0 +1,75 @@ +package amazonlinux + +import ( + "bufio" + "context" + "io" + "os" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +func init() { + analyzer.RegisterAnalyzer(&amazonlinuxOSAnalyzer{}) +} + +const version = 1 + +var requiredFiles = []string{ + "etc/system-release", // for 1 and 2 versions + "usr/lib/system-release", // for 2022, 2023 version +} + +type amazonlinuxOSAnalyzer struct{} + +func (a amazonlinuxOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + foundOS, err := a.parseRelease(input.Content) + if err != nil { + return nil, err + } + return &analyzer.AnalysisResult{ + OS: foundOS, + }, nil +} + +func (a amazonlinuxOSAnalyzer) parseRelease(r io.Reader) (types.OS, error) { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + // Only Amazon Linux Prefix + if strings.HasPrefix(line, "Amazon Linux release 2") { + if len(fields) < 5 { + continue + } + return types.OS{ + Family: types.Amazon, + Name: strings.Join(fields[3:], " "), + }, nil + } else if strings.HasPrefix(line, "Amazon Linux") { + return types.OS{ + Family: types.Amazon, + Name: strings.Join(fields[2:], " "), + }, nil + } + } + return types.OS{}, xerrors.Errorf("amazon: %w", fos.AnalyzeOSError) +} + +func (a amazonlinuxOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return utils.StringInSlice(filePath, requiredFiles) +} + +func (a amazonlinuxOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeAmazon +} + +func (a amazonlinuxOSAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go b/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go new file mode 100644 index 000000000000..83fcd25fe76b --- /dev/null +++ b/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go @@ -0,0 +1,108 @@ +package amazonlinux + +import ( + "context" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_amazonlinuxOSAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + input analyzer.AnalysisInput + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path amazon linux 1", + input: analyzer.AnalysisInput{ + FilePath: "etc/system-release", + Content: strings.NewReader(`Amazon Linux AMI release 2018.03`), + }, + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Amazon, + Name: "AMI release 2018.03", + }, + }, + }, + { + name: "happy path amazon linux 2", + input: analyzer.AnalysisInput{ + FilePath: "etc/system-release", + Content: strings.NewReader(`Amazon Linux release 2 (Karoo)`), + }, + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Amazon, + Name: "2 (Karoo)", + }, + }, + }, + { + name: "happy path amazon linux 2022", + input: analyzer.AnalysisInput{ + FilePath: "usr/lib/system-release", + Content: strings.NewReader(`Amazon Linux release 2022 (Amazon Linux)`), + }, + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Amazon, + Name: "2022 (Amazon Linux)", + }, + }, + }, + { + name: "happy path amazon linux 2023", + input: analyzer.AnalysisInput{ + FilePath: "usr/lib/system-release", + Content: strings.NewReader(`Amazon Linux release 2023 (Amazon Linux)`), + }, + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Amazon, + Name: "2023 (Amazon Linux)", + }, + }, + }, + { + name: "sad path amazon linux 2 without code name", + input: analyzer.AnalysisInput{ + FilePath: "etc/system-release", + Content: strings.NewReader(`Amazon Linux release 2`), + }, + wantErr: fos.AnalyzeOSError.Error(), + }, + { + name: "sad path", + input: analyzer.AnalysisInput{ + FilePath: "etc/system-release", + Content: strings.NewReader(`foo bar`), + }, + wantErr: fos.AnalyzeOSError.Error(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := amazonlinuxOSAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, tt.input) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + require.NoError(t, err) + } + + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/os/const.go b/pkg/fanal/analyzer/os/const.go new file mode 100644 index 000000000000..82f908979b66 --- /dev/null +++ b/pkg/fanal/analyzer/os/const.go @@ -0,0 +1,5 @@ +package os + +import "golang.org/x/xerrors" + +var AnalyzeOSError = xerrors.New("unable to analyze OS information") diff --git a/pkg/fanal/analyzer/os/debian/debian.go b/pkg/fanal/analyzer/os/debian/debian.go new file mode 100644 index 000000000000..5c5f3c766229 --- /dev/null +++ b/pkg/fanal/analyzer/os/debian/debian.go @@ -0,0 +1,50 @@ +package debian + +import ( + "bufio" + "context" + "os" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +func init() { + analyzer.RegisterAnalyzer(&debianOSAnalyzer{}) +} + +const version = 1 + +var requiredFiles = []string{"etc/debian_version"} + +type debianOSAnalyzer struct{} + +func (a debianOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + scanner := bufio.NewScanner(input.Content) + for scanner.Scan() { + line := scanner.Text() + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Debian, + Name: line, + }, + }, nil + } + return nil, xerrors.Errorf("debian: %w", fos.AnalyzeOSError) +} + +func (a debianOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return utils.StringInSlice(filePath, requiredFiles) +} + +func (a debianOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeDebian +} + +func (a debianOSAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/os/debian/debian_test.go b/pkg/fanal/analyzer/os/debian/debian_test.go new file mode 100644 index 000000000000..0366e87693e4 --- /dev/null +++ b/pkg/fanal/analyzer/os/debian/debian_test.go @@ -0,0 +1,71 @@ +package debian + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_debianOSAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path with debian 9", + inputFile: "testdata/debian_9/etc/debian_version", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Debian, + Name: "9.8", + }, + }, + }, + { + name: "happy path with debian sid", + inputFile: "testdata/debian_sid/etc/debian_version", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Debian, + Name: "buster/sid", + }, + }, + }, + { + name: "sad path with empty file", + inputFile: "testdata/empty", + wantErr: "debian: unable to analyze OS information", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := debianOSAnalyzer{} + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + ctx := context.Background() + + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: "etc/debian_version", + Content: f, + }) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/os/debian/testdata/debian_9/etc/debian_version b/pkg/fanal/analyzer/os/debian/testdata/debian_9/etc/debian_version new file mode 100644 index 000000000000..021debdfd4a0 --- /dev/null +++ b/pkg/fanal/analyzer/os/debian/testdata/debian_9/etc/debian_version @@ -0,0 +1 @@ +9.8 diff --git a/pkg/fanal/analyzer/os/debian/testdata/debian_sid/etc/debian_version b/pkg/fanal/analyzer/os/debian/testdata/debian_sid/etc/debian_version new file mode 100644 index 000000000000..2834e5822e25 --- /dev/null +++ b/pkg/fanal/analyzer/os/debian/testdata/debian_sid/etc/debian_version @@ -0,0 +1 @@ +buster/sid diff --git a/pkg/fanal/analyzer/os/debian/testdata/empty b/pkg/fanal/analyzer/os/debian/testdata/empty new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/os/mariner/mariner.go b/pkg/fanal/analyzer/os/mariner/mariner.go new file mode 100644 index 000000000000..f24a8b1886b3 --- /dev/null +++ b/pkg/fanal/analyzer/os/mariner/mariner.go @@ -0,0 +1,67 @@ +package mariner + +import ( + "bufio" + "context" + "io" + "os" + "path/filepath" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&marinerOSAnalyzer{}) +} + +const ( + version = 1 + requiredFile = "etc/mariner-release" +) + +type marinerOSAnalyzer struct{} + +func (a marinerOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + foundOS, err := a.parseRelease(input.Content) + if err != nil { + return nil, xerrors.Errorf("release parse error: %w", err) + } + return &analyzer.AnalysisResult{ + OS: foundOS, + }, nil +} + +func (a marinerOSAnalyzer) parseRelease(r io.Reader) (types.OS, error) { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) != 2 { + continue + } + if strings.EqualFold(fields[0], "cbl-mariner") { + return types.OS{ + Family: types.CBLMariner, + Name: fields[1], + }, nil + } + } + return types.OS{}, xerrors.Errorf("cbl-mariner: %w", fos.AnalyzeOSError) +} + +func (a marinerOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.ToSlash(filePath) == requiredFile +} + +func (a marinerOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeCBLMariner +} + +func (a marinerOSAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/os/mariner/mariner_test.go b/pkg/fanal/analyzer/os/mariner/mariner_test.go new file mode 100644 index 000000000000..e13730a021cb --- /dev/null +++ b/pkg/fanal/analyzer/os/mariner/mariner_test.go @@ -0,0 +1,60 @@ +package mariner + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_marinerOSAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path with CBL Mariner 1.0", + inputFile: "testdata/1.0/mariner-release", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.CBLMariner, + Name: "1.0.20220122", + }, + }, + }, + { + name: "sad path", + inputFile: "testdata/sad/mariner-release", + wantErr: "cbl-mariner: unable to analyze OS information", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := marinerOSAnalyzer{} + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: "etc/mariner-release", + Content: f, + }) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/os/mariner/testdata/1.0/mariner-release b/pkg/fanal/analyzer/os/mariner/testdata/1.0/mariner-release new file mode 100644 index 000000000000..1a8769674acf --- /dev/null +++ b/pkg/fanal/analyzer/os/mariner/testdata/1.0/mariner-release @@ -0,0 +1,2 @@ +CBL-Mariner 1.0.20220122 +MARINER_BUILD_NUMBER=7da4f23 diff --git a/pkg/fanal/analyzer/os/mariner/testdata/sad/mariner-release b/pkg/fanal/analyzer/os/mariner/testdata/sad/mariner-release new file mode 100644 index 000000000000..4fda2bc57d30 --- /dev/null +++ b/pkg/fanal/analyzer/os/mariner/testdata/sad/mariner-release @@ -0,0 +1 @@ +MARINER_BUILD_NUMBER=7da4f23 diff --git a/pkg/fanal/analyzer/os/redhatbase/alma.go b/pkg/fanal/analyzer/os/redhatbase/alma.go new file mode 100644 index 000000000000..eddf7d82f80b --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/alma.go @@ -0,0 +1,62 @@ +package redhatbase + +import ( + "bufio" + "context" + "os" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +const almaAnalyzerVersion = 1 + +func init() { + analyzer.RegisterAnalyzer(&almaOSAnalyzer{}) +} + +type almaOSAnalyzer struct{} + +func (a almaOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + scanner := bufio.NewScanner(input.Content) + for scanner.Scan() { + line := scanner.Text() + result := redhatRe.FindStringSubmatch(strings.TrimSpace(line)) + if len(result) != 3 { + return nil, xerrors.New("alma: invalid almalinux-release") + } + + switch strings.ToLower(result[1]) { + case "alma", "almalinux", "alma linux": + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Alma, + Name: result[2], + }, + }, nil + } + } + + return nil, xerrors.Errorf("alma: %w", fos.AnalyzeOSError) +} + +func (a almaOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return utils.StringInSlice(filePath, a.requiredFiles()) +} + +func (a almaOSAnalyzer) requiredFiles() []string { + return []string{"etc/almalinux-release"} +} + +func (a almaOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeAlma +} + +func (a almaOSAnalyzer) Version() int { + return almaAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/os/redhatbase/alma_test.go b/pkg/fanal/analyzer/os/redhatbase/alma_test.go new file mode 100644 index 000000000000..7356652a8234 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/alma_test.go @@ -0,0 +1,57 @@ +package redhatbase + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_almaOSAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/alma/almalinux-release", + want: &analyzer.AnalysisResult{ + OS: types.OS{Family: "alma", Name: "8.4"}, + }, + }, + { + name: "sad path", + inputFile: "testdata/not_redhatbase/empty", + wantErr: "alma: unable to analyze OS information", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := almaOSAnalyzer{} + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + ctx := context.Background() + + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: "etc/almalinux-release", + Content: f, + }) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/os/redhatbase/centos.go b/pkg/fanal/analyzer/os/redhatbase/centos.go new file mode 100644 index 000000000000..4a57e3e9eac1 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/centos.go @@ -0,0 +1,62 @@ +package redhatbase + +import ( + "bufio" + "context" + "os" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +const centosAnalyzerVersion = 1 + +func init() { + analyzer.RegisterAnalyzer(¢OSAnalyzer{}) +} + +type centOSAnalyzer struct{} + +func (a centOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + scanner := bufio.NewScanner(input.Content) + for scanner.Scan() { + line := scanner.Text() + result := redhatRe.FindStringSubmatch(strings.TrimSpace(line)) + if len(result) != 3 { + return nil, xerrors.New("centos: invalid centos-release") + } + + switch strings.ToLower(result[1]) { + case "centos", "centos linux": + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.CentOS, + Name: result[2], + }, + }, nil + } + } + + return nil, xerrors.Errorf("centos: %w", fos.AnalyzeOSError) +} + +func (a centOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return utils.StringInSlice(filePath, a.requiredFiles()) +} + +func (a centOSAnalyzer) requiredFiles() []string { + return []string{"etc/centos-release"} +} + +func (a centOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeCentOS +} + +func (a centOSAnalyzer) Version() int { + return centosAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/os/redhatbase/centos_test.go b/pkg/fanal/analyzer/os/redhatbase/centos_test.go new file mode 100644 index 000000000000..d85ea2494a89 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/centos_test.go @@ -0,0 +1,57 @@ +package redhatbase + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_centosOSAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/centos/centos-release", + want: &analyzer.AnalysisResult{ + OS: types.OS{Family: "centos", Name: "7.6.1810"}, + }, + }, + { + name: "sad path", + inputFile: "testdata/not_redhatbase/empty", + wantErr: "centos: unable to analyze OS information", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := centOSAnalyzer{} + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + ctx := context.Background() + + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: "etc/centos-release", + Content: f, + }) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/os/redhatbase/fedora.go b/pkg/fanal/analyzer/os/redhatbase/fedora.go new file mode 100644 index 000000000000..d5b2458e1c5b --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/fedora.go @@ -0,0 +1,64 @@ +package redhatbase + +import ( + "bufio" + "context" + "os" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +const fedoraAnalyzerVersion = 1 + +func init() { + analyzer.RegisterAnalyzer(&fedoraOSAnalyzer{}) +} + +type fedoraOSAnalyzer struct{} + +func (a fedoraOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + scanner := bufio.NewScanner(input.Content) + for scanner.Scan() { + line := scanner.Text() + result := redhatRe.FindStringSubmatch(strings.TrimSpace(line)) + if len(result) != 3 { + return nil, xerrors.New("fedora: Invalid fedora-release") + } + + switch strings.ToLower(result[1]) { + case "fedora", "fedora linux": + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Fedora, + Name: result[2], + }, + }, nil + } + } + return nil, xerrors.Errorf("fedora: %w", fos.AnalyzeOSError) +} + +func (a fedoraOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return utils.StringInSlice(filePath, a.requiredFiles()) +} + +func (a fedoraOSAnalyzer) requiredFiles() []string { + return []string{ + "etc/fedora-release", + "usr/lib/fedora-release", + } +} + +func (a fedoraOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeFedora +} + +func (a fedoraOSAnalyzer) Version() int { + return fedoraAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/os/redhatbase/fedora_test.go b/pkg/fanal/analyzer/os/redhatbase/fedora_test.go new file mode 100644 index 000000000000..48bbb3b42755 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/fedora_test.go @@ -0,0 +1,57 @@ +package redhatbase + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_fedoraOSAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/fedora_29/fedora-release", + want: &analyzer.AnalysisResult{ + OS: types.OS{Family: "fedora", Name: "29"}, + }, + }, + { + name: "sad path", + inputFile: "testdata/not_redhatbase/empty", + wantErr: "fedora: unable to analyze OS information", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := fedoraOSAnalyzer{} + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: "etc/fedora-release", + Content: f, + }) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/os/redhatbase/oracle.go b/pkg/fanal/analyzer/os/redhatbase/oracle.go new file mode 100644 index 000000000000..90864aefd848 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/oracle.go @@ -0,0 +1,58 @@ +package redhatbase + +import ( + "bufio" + "context" + "os" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +const oracleAnalyzerVersion = 1 + +func init() { + analyzer.RegisterAnalyzer(&oracleOSAnalyzer{}) +} + +type oracleOSAnalyzer struct{} + +func (a oracleOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + scanner := bufio.NewScanner(input.Content) + for scanner.Scan() { + line := scanner.Text() + result := redhatRe.FindStringSubmatch(strings.TrimSpace(line)) + if len(result) != 3 { + return nil, xerrors.New("oracle: invalid oracle-release") + } + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Oracle, + Name: result[2], + }, + }, nil + } + + return nil, xerrors.Errorf("oracle: %w", fos.AnalyzeOSError) +} + +func (a oracleOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return utils.StringInSlice(filePath, a.requiredFiles()) +} + +func (a oracleOSAnalyzer) requiredFiles() []string { + return []string{"etc/oracle-release"} +} + +func (a oracleOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeOracle +} + +func (a oracleOSAnalyzer) Version() int { + return oracleAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/os/redhatbase/oracle_test.go b/pkg/fanal/analyzer/os/redhatbase/oracle_test.go new file mode 100644 index 000000000000..c30498481a23 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/oracle_test.go @@ -0,0 +1,57 @@ +package redhatbase + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_oracleOSAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/oracle_7/oracle-release", + want: &analyzer.AnalysisResult{ + OS: types.OS{Family: "oracle", Name: "7.6"}, + }, + }, + { + name: "sad path", + inputFile: "testdata/not_redhatbase/empty", + wantErr: "oracle: unable to analyze OS information", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := oracleOSAnalyzer{} + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: "etc/oracle-release", + Content: f, + }) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/os/redhatbase/redhatbase.go b/pkg/fanal/analyzer/os/redhatbase/redhatbase.go new file mode 100644 index 000000000000..9fc69a8753ca --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/redhatbase.go @@ -0,0 +1,99 @@ +package redhatbase + +import ( + "bufio" + "context" + "io" + "os" + "regexp" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +const redhatAnalyzerVersion = 1 + +func init() { + analyzer.RegisterAnalyzer(&redhatOSAnalyzer{}) +} + +var redhatRe = regexp.MustCompile(`(.*) release (\d[\d\.]*)`) + +type redhatOSAnalyzer struct{} + +func (a redhatOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + foundOS, err := a.parseRelease(input.Content) + if err != nil { + return nil, err + } + return &analyzer.AnalysisResult{ + OS: foundOS, + }, nil + +} + +func (a redhatOSAnalyzer) parseRelease(r io.Reader) (types.OS, error) { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + result := redhatRe.FindStringSubmatch(strings.TrimSpace(line)) + if len(result) != 3 { + return types.OS{}, xerrors.New("redhat: invalid redhat-release") + } + + switch strings.ToLower(result[1]) { + case "centos", "centos linux": + return types.OS{ + Family: types.CentOS, + Name: result[2], + }, nil + case "rocky", "rocky linux": + return types.OS{ + Family: types.Rocky, + Name: result[2], + }, nil + case "alma", "almalinux", "alma linux": + return types.OS{ + Family: types.Alma, + Name: result[2], + }, nil + case "oracle", "oracle linux", "oracle linux server": + return types.OS{ + Family: types.Oracle, + Name: result[2], + }, nil + case "fedora", "fedora linux": + return types.OS{ + Family: types.Fedora, + Name: result[2], + }, nil + default: + return types.OS{ + Family: types.RedHat, + Name: result[2], + }, nil + } + } + return types.OS{}, xerrors.Errorf("redhatbase: %w", fos.AnalyzeOSError) +} + +func (a redhatOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return utils.StringInSlice(filePath, a.requiredFiles()) +} + +func (a redhatOSAnalyzer) requiredFiles() []string { + return []string{"etc/redhat-release"} +} + +func (a redhatOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeRedHatBase +} + +func (a redhatOSAnalyzer) Version() int { + return redhatAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go b/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go new file mode 100644 index 000000000000..8d1688ef1b8a --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go @@ -0,0 +1,57 @@ +package redhatbase + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_redhatOSAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/redhat_6/redhat-release", + want: &analyzer.AnalysisResult{ + OS: types.OS{Family: "redhat", Name: "6.2"}, + }, + }, + { + name: "sad path", + inputFile: "testdata/not_redhatbase/empty", + wantErr: "redhatbase: unable to analyze OS information", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := redhatOSAnalyzer{} + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: "etc/redhat-release", + Content: f, + }) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/os/redhatbase/rocky.go b/pkg/fanal/analyzer/os/redhatbase/rocky.go new file mode 100644 index 000000000000..ac443d29ae83 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/rocky.go @@ -0,0 +1,62 @@ +package redhatbase + +import ( + "bufio" + "context" + "os" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +const rockyAnalyzerVersion = 1 + +func init() { + analyzer.RegisterAnalyzer(&rockyOSAnalyzer{}) +} + +type rockyOSAnalyzer struct{} + +func (a rockyOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + scanner := bufio.NewScanner(input.Content) + for scanner.Scan() { + line := scanner.Text() + result := redhatRe.FindStringSubmatch(strings.TrimSpace(line)) + if len(result) != 3 { + return nil, xerrors.New("rocky: invalid rocky-release") + } + + switch strings.ToLower(result[1]) { + case "rocky", "rocky linux": + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Rocky, + Name: result[2], + }, + }, nil + } + } + + return nil, xerrors.Errorf("rocky: %w", fos.AnalyzeOSError) +} + +func (a rockyOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return utils.StringInSlice(filePath, a.requiredFiles()) +} + +func (a rockyOSAnalyzer) requiredFiles() []string { + return []string{"etc/rocky-release"} +} + +func (a rockyOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeRocky +} + +func (a rockyOSAnalyzer) Version() int { + return rockyAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/os/redhatbase/rocky_test.go b/pkg/fanal/analyzer/os/redhatbase/rocky_test.go new file mode 100644 index 000000000000..7b9f3152ea41 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/rocky_test.go @@ -0,0 +1,56 @@ +package redhatbase + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_rockyOSAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/rocky/rocky-release", + want: &analyzer.AnalysisResult{ + OS: types.OS{Family: "rocky", Name: "8.4"}, + }, + }, + { + name: "sad path", + inputFile: "testdata/not_redhatbase/empty", + wantErr: "rocky: unable to analyze OS information", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := rockyOSAnalyzer{} + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: "etc/rocky-release", + Content: f, + }) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/os/redhatbase/testdata/alma/almalinux-release b/pkg/fanal/analyzer/os/redhatbase/testdata/alma/almalinux-release new file mode 100644 index 000000000000..078ddf097550 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/testdata/alma/almalinux-release @@ -0,0 +1 @@ +AlmaLinux release 8.4 (Electric Cheetah) \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/redhatbase/testdata/centos/centos-release b/pkg/fanal/analyzer/os/redhatbase/testdata/centos/centos-release new file mode 100644 index 000000000000..aad37b610e3b --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/testdata/centos/centos-release @@ -0,0 +1 @@ +CentOS Linux release 7.6.1810 (Core) diff --git a/pkg/fanal/analyzer/os/redhatbase/testdata/fedora_29/fedora-release b/pkg/fanal/analyzer/os/redhatbase/testdata/fedora_29/fedora-release new file mode 100644 index 000000000000..b84fe96c8f4c --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/testdata/fedora_29/fedora-release @@ -0,0 +1 @@ +Fedora release 29 (Twenty Nine) diff --git a/pkg/fanal/analyzer/os/redhatbase/testdata/not_redhatbase/empty b/pkg/fanal/analyzer/os/redhatbase/testdata/not_redhatbase/empty new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/os/redhatbase/testdata/oracle_7/oracle-release b/pkg/fanal/analyzer/os/redhatbase/testdata/oracle_7/oracle-release new file mode 100644 index 000000000000..49ada11583a7 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/testdata/oracle_7/oracle-release @@ -0,0 +1 @@ +Oracle Linux Server release 7.6 diff --git a/pkg/fanal/analyzer/os/redhatbase/testdata/redhat_6/redhat-release b/pkg/fanal/analyzer/os/redhatbase/testdata/redhat_6/redhat-release new file mode 100644 index 000000000000..574c7bd1ede5 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/testdata/redhat_6/redhat-release @@ -0,0 +1 @@ +Red Hat Linux release 6.2 (Zoot) diff --git a/pkg/fanal/analyzer/os/redhatbase/testdata/rocky/rocky-release b/pkg/fanal/analyzer/os/redhatbase/testdata/rocky/rocky-release new file mode 100644 index 000000000000..e79c566541d6 --- /dev/null +++ b/pkg/fanal/analyzer/os/redhatbase/testdata/rocky/rocky-release @@ -0,0 +1 @@ +Rocky Linux release 8.4 (Green Obsidian) \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/release/release.go b/pkg/fanal/analyzer/os/release/release.go new file mode 100644 index 000000000000..d4b959c3a9b3 --- /dev/null +++ b/pkg/fanal/analyzer/os/release/release.go @@ -0,0 +1,90 @@ +package release + +import ( + "bufio" + "context" + "os" + "strings" + + "golang.org/x/exp/slices" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&osReleaseAnalyzer{}) +} + +const version = 1 + +var requiredFiles = []string{ + "etc/os-release", + "usr/lib/os-release", +} + +type osReleaseAnalyzer struct{} + +func (a osReleaseAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + var id, versionID string + scanner := bufio.NewScanner(input.Content) + for scanner.Scan() { + line := scanner.Text() + + ss := strings.SplitN(line, "=", 2) + if len(ss) != 2 { + continue + } + key, value := strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1]) + + switch key { + case "ID": + id = strings.Trim(value, `"'`) + case "VERSION_ID": + versionID = strings.Trim(value, `"'`) + default: + continue + } + + var family types.OSType + switch id { + case "alpine": + family = types.Alpine + case "opensuse-tumbleweed": + family = types.OpenSUSETumbleweed + case "opensuse-leap", "opensuse": // opensuse for leap:42, opensuse-leap for leap:15 + family = types.OpenSUSELeap + case "sles": + family = types.SLES + case "photon": + family = types.Photon + case "wolfi": + family = types.Wolfi + case "chainguard": + family = types.Chainguard + } + + if family != "" && versionID != "" { + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: family, + Name: versionID, + }, + }, nil + } + } + + return nil, nil +} + +func (a osReleaseAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return slices.Contains(requiredFiles, filePath) +} + +func (a osReleaseAnalyzer) Type() analyzer.Type { + return analyzer.TypeOSRelease +} + +func (a osReleaseAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/os/release/release_test.go b/pkg/fanal/analyzer/os/release/release_test.go new file mode 100644 index 000000000000..615324c200fd --- /dev/null +++ b/pkg/fanal/analyzer/os/release/release_test.go @@ -0,0 +1,132 @@ +package release + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_osReleaseAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + input analyzer.AnalysisInput + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "alpine", + inputFile: "testdata/alpine", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Alpine, + Name: "3.15.4", + }, + }, + }, + { + name: "openSUSE-leap 15.2.1", + inputFile: "testdata/opensuseleap-15.2.1", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.OpenSUSELeap, + Name: "15.2.1", + }, + }, + }, + { + name: "openSUSE-leap 42.3", + inputFile: "testdata/opensuseleap-42.3", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.OpenSUSELeap, + Name: "42.3", + }, + }, + }, + { + name: "openSUSE-tumbleweed", + inputFile: "testdata/opensusetumbleweed", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.OpenSUSETumbleweed, + Name: "20220412", + }, + }, + }, + { + name: "SUSE Linux Enterprise Server", + inputFile: "testdata/sles", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.SLES, + Name: "15.3", + }, + }, + }, + { + name: "Photon OS", + inputFile: "testdata/photon", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Photon, + Name: "4.0", + }, + }, + }, + { + name: "Photon OS", + inputFile: "testdata/photon", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Photon, + Name: "4.0", + }, + }, + }, + { + name: "Unknown OS", + inputFile: "testdata/unknown", + want: nil, + }, + { + name: "No 'ID' field", + inputFile: "testdata/no-id", + want: nil, + }, + { + name: "No 'VERSION_ID' field", + inputFile: "testdata/no-version", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := osReleaseAnalyzer{} + res, err := a.Analyze(context.Background(), analyzer.AnalysisInput{ + FilePath: "etc/os-release", + Content: f, + }) + + if tt.wantErr != "" { + assert.Error(t, err) + assert.Equal(t, tt.wantErr, err.Error()) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, res) + }) + } +} diff --git a/pkg/fanal/analyzer/os/release/testdata/alpine b/pkg/fanal/analyzer/os/release/testdata/alpine new file mode 100644 index 000000000000..f53b6e704bb6 --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/alpine @@ -0,0 +1,6 @@ +NAME="Alpine Linux" +ID=alpine +VERSION_ID=3.15.4 +PRETTY_NAME="Alpine Linux v3.15" +HOME_URL="https://alpinelinux.org/" +BUG_REPORT_URL="https://bugs.alpinelinux.org/" \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/release/testdata/no-id b/pkg/fanal/analyzer/os/release/testdata/no-id new file mode 100644 index 000000000000..dc088d5994c9 --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/no-id @@ -0,0 +1,5 @@ +NAME="Alpine Linux" +VERSION_ID=3.15.4 +PRETTY_NAME="Alpine Linux v3.15" +HOME_URL="https://alpinelinux.org/" +BUG_REPORT_URL="https://bugs.alpinelinux.org/" \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/release/testdata/no-version b/pkg/fanal/analyzer/os/release/testdata/no-version new file mode 100644 index 000000000000..075d0208d18e --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/no-version @@ -0,0 +1,5 @@ +NAME="Alpine Linux" +ID=alpine +PRETTY_NAME="Alpine Linux v3.15" +HOME_URL="https://alpinelinux.org/" +BUG_REPORT_URL="https://bugs.alpinelinux.org/" \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/release/testdata/opensuseleap-15.2.1 b/pkg/fanal/analyzer/os/release/testdata/opensuseleap-15.2.1 new file mode 100644 index 000000000000..cc1ee6bcb755 --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/opensuseleap-15.2.1 @@ -0,0 +1,10 @@ +NAME="openSUSE Leap" +VERSION="15.2.1 Beta" +ID="opensuse-leap" +ID_LIKE="suse opensuse" +VERSION_ID="15.2.1" +PRETTY_NAME="openSUSE Leap 15.2.1 Beta" +ANSI_COLOR="0;32" +CPE_NAME="cpe:/o:opensuse:leap:15.2.1" +BUG_REPORT_URL="https://bugs.opensuse.org" +HOME_URL="https://www.opensuse.org/" \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/release/testdata/opensuseleap-42.3 b/pkg/fanal/analyzer/os/release/testdata/opensuseleap-42.3 new file mode 100644 index 000000000000..a449c2ce1415 --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/opensuseleap-42.3 @@ -0,0 +1,10 @@ +NAME="openSUSE Leap" +VERSION="42.3" +ID=opensuse +ID_LIKE="suse" +VERSION_ID="42.3" +PRETTY_NAME="openSUSE Leap 42.3" +ANSI_COLOR="0;32" +CPE_NAME="cpe:/o:opensuse:leap:42.3" +BUG_REPORT_URL="https://bugs.opensuse.org" +HOME_URL="https://www.opensuse.org/" diff --git a/pkg/fanal/analyzer/os/release/testdata/opensusetumbleweed b/pkg/fanal/analyzer/os/release/testdata/opensusetumbleweed new file mode 100644 index 000000000000..1929741767f0 --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/opensusetumbleweed @@ -0,0 +1,12 @@ +NAME="openSUSE Tumbleweed" +# VERSION="20220412" +ID="opensuse-tumbleweed" +ID_LIKE="opensuse suse" +VERSION_ID="20220412" +PRETTY_NAME="openSUSE Tumbleweed" +ANSI_COLOR="0;32" +CPE_NAME="cpe:/o:opensuse:tumbleweed:20220412" +BUG_REPORT_URL="https://bugs.opensuse.org" +HOME_URL="https://www.opensuse.org/" +DOCUMENTATION_URL="https://en.opensuse.org/Portal:Tumbleweed" +LOGO="distributor-logo-Tumbleweed" \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/release/testdata/photon b/pkg/fanal/analyzer/os/release/testdata/photon new file mode 100644 index 000000000000..abf0ba3fecea --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/photon @@ -0,0 +1,8 @@ +NAME="VMware Photon OS" +VERSION="4.0" +ID=photon +VERSION_ID=4.0 +PRETTY_NAME="VMware Photon OS/Linux" +ANSI_COLOR="1;34" +HOME_URL="https://vmware.github.io/photon/" +BUG_REPORT_URL="https://github.com/vmware/photon/issues" \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/release/testdata/sles b/pkg/fanal/analyzer/os/release/testdata/sles new file mode 100644 index 000000000000..e41d21049bfa --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/sles @@ -0,0 +1,9 @@ +NAME="SLES" +VERSION="15-SP3" +VERSION_ID="15.3" +PRETTY_NAME="SUSE Linux Enterprise Server 15 SP3" +ID="sles" +ID_LIKE="suse" +ANSI_COLOR="0;32" +CPE_NAME="cpe:/o:suse:sles:15:sp3" +DOCUMENTATION_URL="https://documentation.suse.com/" \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/release/testdata/unknown b/pkg/fanal/analyzer/os/release/testdata/unknown new file mode 100644 index 000000000000..20658f0975a5 --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/unknown @@ -0,0 +1,2 @@ +ID=unknown +VERSION_ID=4.3.2 \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/ubuntu/esm.go b/pkg/fanal/analyzer/os/ubuntu/esm.go new file mode 100644 index 000000000000..9f1dd08f9c6d --- /dev/null +++ b/pkg/fanal/analyzer/os/ubuntu/esm.go @@ -0,0 +1,79 @@ +package ubuntu + +import ( + "context" + "encoding/json" + "os" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&ubuntuESMAnalyzer{}) +} + +const ( + ESMAnalyzerVersion = 1 + esmConfFilePath = "var/lib/ubuntu-advantage/status.json" + esmServiceName = "esm-infra" + esmStatusEnabled = "enabled" +) + +var ESMRequiredFiles = []string{ + esmConfFilePath, +} + +type ubuntuESMAnalyzer struct{} + +func (a ubuntuESMAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + st := status{} + err := json.NewDecoder(input.Content).Decode(&st) + if err != nil { + return nil, xerrors.Errorf("ubuntu ESM analyze error: %w", err) + } + if esmEnabled(st) { + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Ubuntu, + Extended: true, + }, + }, nil + } + // if ESM is disabled - return nil to reduce the amount of logic in the OS.Merge function + return nil, nil +} + +func (a ubuntuESMAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return slices.Contains(ESMRequiredFiles, filePath) +} + +func (a ubuntuESMAnalyzer) Type() analyzer.Type { + return analyzer.TypeUbuntuESM +} + +func (a ubuntuESMAnalyzer) Version() int { + return ESMAnalyzerVersion +} + +// structs to parse ESM status +type status struct { + Services []service `json:"services"` +} + +type service struct { + Name string `json:"name"` + Status string `json:"status"` +} + +func esmEnabled(st status) bool { + for _, s := range st.Services { // Find ESM Service + if s.Name == esmServiceName && s.Status == esmStatusEnabled { + return true + } + } + return false +} diff --git a/pkg/fanal/analyzer/os/ubuntu/esm_test.go b/pkg/fanal/analyzer/os/ubuntu/esm_test.go new file mode 100644 index 000000000000..1923e4ab8c7f --- /dev/null +++ b/pkg/fanal/analyzer/os/ubuntu/esm_test.go @@ -0,0 +1,90 @@ +package ubuntu + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_ubuntuESMAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + filePath string + testFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path. Parse status.json file(ESM enabled)", + filePath: "var/lib/ubuntu-advantage/status.json", + testFile: "testdata/esm_enabled_status.json", + want: &analyzer.AnalysisResult{ + OS: types.OS{Family: "ubuntu", Extended: true}, + }, + }, + { + name: "happy path. Parse status.json file(ESM disabled)", + filePath: "var/lib/ubuntu-advantage/status.json", + testFile: "testdata/esm_disabled_status.json", + want: nil, + }, + { + name: "sad path", + filePath: "var/lib/ubuntu-advantage/status.json", + testFile: "testdata/invalid", + wantErr: "ubuntu ESM analyze error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := ubuntuESMAnalyzer{} + f, err := os.Open(tt.testFile) + require.NoError(t, err) + defer f.Close() + + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.filePath, + Content: f, + }) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_ubuntuESMAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path status.json", + filePath: "var/lib/ubuntu-advantage/status.json", + want: true, + }, + { + name: "sad path", + filePath: "etc/invalid", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := ubuntuESMAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/os/ubuntu/testdata/esm_disabled_status.json b/pkg/fanal/analyzer/os/ubuntu/testdata/esm_disabled_status.json new file mode 100644 index 000000000000..dcc5e54dd4df --- /dev/null +++ b/pkg/fanal/analyzer/os/ubuntu/testdata/esm_disabled_status.json @@ -0,0 +1 @@ +{"machine_id": "1", "version": "27.6~16.04.1", "notices": [["", "Operation in progress: ua attach"]], "account": {"external_account_ids": [], "name": "trivy", "id": "11", "created_at": "2002-06-11T05:31:56+00:00"}, "execution_details": "Operation in progress: ua attach (pid:7)", "services": [{"status_details": "CC EAL2 is not configured", "name": "cc-eal", "description_override": null, "status": "disabled", "entitled": "yes", "description": "Common Criteria EAL2 Provisioning Packages", "available": "yes"}, {"status_details": "Ubuntu Security Guide is not configured", "name": "cis", "description_override": null, "status": "disabled", "entitled": "yes", "description": "Security compliance and audit tools", "available": "yes"}, {"status_details": "UA Infra: ESM is active", "name": "esm-infra", "description_override": null, "status": "disabled", "entitled": "yes", "description": "UA Infra: Extended Security Maintenance (ESM)", "available": "yes"}, {"status_details": "Cannot install FIPS on a container.", "name": "fips", "description_override": null, "status": "n/a", "entitled": "yes", "description": "NIST-certified core packages", "available": "yes"}, {"status_details": "Cannot install FIPS Updates on a container.", "name": "fips-updates", "description_override": null, "status": "n/a", "entitled": "yes", "description": "NIST-certified core packages with priority security updates", "available": "yes"}, {"status_details": "Cannot install Livepatch on a container.", "name": "livepatch", "description_override": null, "status": "n/a", "entitled": "yes", "description": "Canonical Livepatch service", "available": "yes"}], "_doc": "Content provided in json response is currently considered Experimental and may change", "contract": {"tech_support_level": "n/a", "name": "trivy", "id": "11", "created_at": "2022-03-11T05:31:57+00:00", "products": ["free"]}, "config": {"ua_config": {"update_status_timer": 43200, "https_proxy": null, "http_proxy": null, "apt_http_proxy": null, "metering_timer": 14400, "update_messaging_timer": 21600, "apt_https_proxy": null}, "data_dir": "/var/lib/ubuntu-advantage", "log_file": "/var/log/ubuntu-advantage.log", "security_url": "https://ubuntu.com/security", "log_level": "debug", "timer_log_file": "/var/log/ubuntu-advantage-timer.log", "license_check_log_file": "/var/log/ubuntu-advantage-license-check.log", "contract_url": "https://contracts.canonical.com"}, "_schema_version": "0.1", "attached": true, "execution_status": "active", "effective": "2022-03-11T05:31:57+00:00", "config_path": "/etc/ubuntu-advantage/uaclient.conf", "origin": "free", "simulated": false, "expires": "9999-12-31T00:00:00+00:00"} \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/ubuntu/testdata/esm_enabled_status.json b/pkg/fanal/analyzer/os/ubuntu/testdata/esm_enabled_status.json new file mode 100644 index 000000000000..5d292414bdf4 --- /dev/null +++ b/pkg/fanal/analyzer/os/ubuntu/testdata/esm_enabled_status.json @@ -0,0 +1 @@ +{"machine_id": "1", "version": "27.6~16.04.1", "notices": [["", "Operation in progress: ua attach"]], "account": {"external_account_ids": [], "name": "trivy", "id": "11", "created_at": "2002-06-11T05:31:56+00:00"}, "execution_details": "Operation in progress: ua attach (pid:7)", "services": [{"status_details": "CC EAL2 is not configured", "name": "cc-eal", "description_override": null, "status": "disabled", "entitled": "yes", "description": "Common Criteria EAL2 Provisioning Packages", "available": "yes"}, {"status_details": "Ubuntu Security Guide is not configured", "name": "cis", "description_override": null, "status": "disabled", "entitled": "yes", "description": "Security compliance and audit tools", "available": "yes"}, {"status_details": "UA Infra: ESM is active", "name": "esm-infra", "description_override": null, "status": "enabled", "entitled": "yes", "description": "UA Infra: Extended Security Maintenance (ESM)", "available": "yes"}, {"status_details": "Cannot install FIPS on a container.", "name": "fips", "description_override": null, "status": "n/a", "entitled": "yes", "description": "NIST-certified core packages", "available": "yes"}, {"status_details": "Cannot install FIPS Updates on a container.", "name": "fips-updates", "description_override": null, "status": "n/a", "entitled": "yes", "description": "NIST-certified core packages with priority security updates", "available": "yes"}, {"status_details": "Cannot install Livepatch on a container.", "name": "livepatch", "description_override": null, "status": "n/a", "entitled": "yes", "description": "Canonical Livepatch service", "available": "yes"}], "_doc": "Content provided in json response is currently considered Experimental and may change", "contract": {"tech_support_level": "n/a", "name": "trivy", "id": "11", "created_at": "2022-03-11T05:31:57+00:00", "products": ["free"]}, "config": {"ua_config": {"update_status_timer": 43200, "https_proxy": null, "http_proxy": null, "apt_http_proxy": null, "metering_timer": 14400, "update_messaging_timer": 21600, "apt_https_proxy": null}, "data_dir": "/var/lib/ubuntu-advantage", "log_file": "/var/log/ubuntu-advantage.log", "security_url": "https://ubuntu.com/security", "log_level": "debug", "timer_log_file": "/var/log/ubuntu-advantage-timer.log", "license_check_log_file": "/var/log/ubuntu-advantage-license-check.log", "contract_url": "https://contracts.canonical.com"}, "_schema_version": "0.1", "attached": true, "execution_status": "active", "effective": "2022-03-11T05:31:57+00:00", "config_path": "/etc/ubuntu-advantage/uaclient.conf", "origin": "free", "simulated": false, "expires": "9999-12-31T00:00:00+00:00"} \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/ubuntu/testdata/invalid b/pkg/fanal/analyzer/os/ubuntu/testdata/invalid new file mode 100644 index 000000000000..e466dcbd8e8f --- /dev/null +++ b/pkg/fanal/analyzer/os/ubuntu/testdata/invalid @@ -0,0 +1 @@ +invalid \ No newline at end of file diff --git a/pkg/fanal/analyzer/os/ubuntu/testdata/lsb-release b/pkg/fanal/analyzer/os/ubuntu/testdata/lsb-release new file mode 100644 index 000000000000..2cb44ac6a582 --- /dev/null +++ b/pkg/fanal/analyzer/os/ubuntu/testdata/lsb-release @@ -0,0 +1,4 @@ +DISTRIB_ID=Ubuntu +DISTRIB_RELEASE=18.04 +DISTRIB_CODENAME=bionic +DISTRIB_DESCRIPTION="Ubuntu 18.04.2 LTS" diff --git a/pkg/fanal/analyzer/os/ubuntu/ubuntu.go b/pkg/fanal/analyzer/os/ubuntu/ubuntu.go new file mode 100644 index 000000000000..2fff3ac8339f --- /dev/null +++ b/pkg/fanal/analyzer/os/ubuntu/ubuntu.go @@ -0,0 +1,64 @@ +package ubuntu + +import ( + "bufio" + "context" + "os" + "strings" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&ubuntuOSAnalyzer{}) +} + +const ( + version = 1 + ubuntuConfFilePath = "etc/lsb-release" +) + +var requiredFiles = []string{ + ubuntuConfFilePath, +} + +type ubuntuOSAnalyzer struct{} + +func (a ubuntuOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + isUbuntu := false + scanner := bufio.NewScanner(input.Content) + for scanner.Scan() { + line := scanner.Text() + if line == "DISTRIB_ID=Ubuntu" { + isUbuntu = true + continue + } + + if isUbuntu && strings.HasPrefix(line, "DISTRIB_RELEASE=") { + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Ubuntu, + Name: strings.TrimSpace(line[16:]), + }, + }, nil + } + } + return nil, xerrors.Errorf("ubuntu: %w", fos.AnalyzeOSError) +} + +func (a ubuntuOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return slices.Contains(requiredFiles, filePath) +} + +func (a ubuntuOSAnalyzer) Type() analyzer.Type { + return analyzer.TypeUbuntu +} + +func (a ubuntuOSAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go b/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go new file mode 100644 index 000000000000..042d28af9924 --- /dev/null +++ b/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go @@ -0,0 +1,83 @@ +package ubuntu + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_ubuntuOSAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/lsb-release", + want: &analyzer.AnalysisResult{ + OS: types.OS{Family: "ubuntu", Name: "18.04"}, + }, + }, + { + name: "sad path", + inputFile: "testdata/invalid", + wantErr: "ubuntu: unable to analyze OS information", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := ubuntuOSAnalyzer{} + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: "etc/lsb-release", + Content: f, + }) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_ubuntuOSAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path", + filePath: "etc/lsb-release", + want: true, + }, + { + name: "sad path", + filePath: "etc/invalid", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := ubuntuOSAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/pkg/apk/apk.go b/pkg/fanal/analyzer/pkg/apk/apk.go new file mode 100644 index 000000000000..5f3b82dbbe76 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/apk/apk.go @@ -0,0 +1,247 @@ +package apk + +import ( + "bufio" + "context" + "encoding/base64" + "encoding/hex" + "fmt" + "os" + "path" + "sort" + "strings" + + apkVersion "github.com/knqyf263/go-apk-version" + "github.com/samber/lo" + "golang.org/x/exp/slices" + + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/log" +) + +func init() { + analyzer.RegisterAnalyzer(&alpinePkgAnalyzer{}) +} + +const analyzerVersion = 2 + +var requiredFiles = []string{"lib/apk/db/installed"} + +type alpinePkgAnalyzer struct{} + +func (a alpinePkgAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + scanner := bufio.NewScanner(input.Content) + parsedPkgs, installedFiles := a.parseApkInfo(scanner) + + return &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: input.FilePath, + Packages: parsedPkgs, + }, + }, + SystemInstalledFiles: installedFiles, + }, nil +} + +func (a alpinePkgAnalyzer) parseApkInfo(scanner *bufio.Scanner) ([]types.Package, []string) { + var ( + pkgs []types.Package + pkg types.Package + version string + dir string + installedFiles []string + provides = make(map[string]string) // for dependency graph + ) + + for scanner.Scan() { + line := scanner.Text() + + // check package if paragraph end + if len(line) < 2 { + if !pkg.Empty() { + pkgs = append(pkgs, pkg) + } + pkg = types.Package{} + continue + } + + // ref. https://wiki.alpinelinux.org/wiki/Apk_spec + switch line[:2] { + case "P:": + pkg.Name = line[2:] + case "V:": + version = line[2:] + if !apkVersion.Valid(version) { + log.Logger.Warnf("Invalid Version Found : OS %s, Package %s, Version %s", "alpine", pkg.Name, version) + continue + } + pkg.Version = version + case "o:": + origin := line[2:] + pkg.SrcName = origin + pkg.SrcVersion = version + case "L:": + pkg.Licenses = a.parseLicense(line) + case "F:": + dir = line[2:] + case "R:": + absPath := path.Join(dir, line[2:]) + pkg.InstalledFiles = append(pkg.InstalledFiles, absPath) + installedFiles = append(installedFiles, absPath) + case "p:": // provides (corresponds to provides in PKGINFO, concatenated by spaces into a single line) + a.parseProvides(line, pkg.ID, provides) + case "D:": // dependencies (corresponds to depend in PKGINFO, concatenated by spaces into a single line) + pkg.DependsOn = a.parseDependencies(line) + case "A:": + pkg.Arch = line[2:] + case "C:": + d := decodeChecksumLine(line) + if d != "" { + pkg.Digest = d + } + } + + if pkg.Name != "" && pkg.Version != "" { + pkg.ID = fmt.Sprintf("%s@%s", pkg.Name, pkg.Version) + + // Dependencies could be package names or provides, so package names are stored as provides here. + // e.g. D:scanelf so:libc.musl-x86_64.so.1 + provides[pkg.Name] = pkg.ID + } + } + // in case of last paragraph + if !pkg.Empty() { + pkgs = append(pkgs, pkg) + } + + pkgs = a.uniquePkgs(pkgs) + + // Replace dependencies with package IDs + a.consolidateDependencies(pkgs, provides) + + return pkgs, installedFiles +} + +func (a alpinePkgAnalyzer) trimRequirement(s string) string { + // Trim version requirements + // e.g. + // so:libssl.so.1.1=1.1 => so:libssl.so.1.1 + // musl>=1.2 => musl + if strings.ContainsAny(s, "<>=") { + s = s[:strings.IndexAny(s, "><=")] + } + return s +} + +func (a alpinePkgAnalyzer) parseLicense(line string) []string { + line = line[2:] // Remove "L:" + if line == "" { + return nil + } + var licenses []string + // e.g. MPL 2.0 GPL2+ => {"MPL2.0", "GPL2+"} + for i, s := range strings.Fields(line) { + s = strings.Trim(s, "()") + switch { + case s == "": + continue + case s == "AND" || s == "OR": + continue + case i > 0 && (s == "1.0" || s == "2.0" || s == "3.0"): + licenses[i-1] = licensing.Normalize(licenses[i-1] + s) + default: + licenses = append(licenses, licensing.Normalize(s)) + } + } + return licenses +} + +func (a alpinePkgAnalyzer) parseProvides(line, pkgID string, provides map[string]string) { + for _, p := range strings.Fields(line[2:]) { + p = a.trimRequirement(p) + + // Assume name ("P:") and version ("V:") are defined before provides ("p:") + provides[p] = pkgID + } +} + +func (a alpinePkgAnalyzer) parseDependencies(line string) []string { + line = line[2:] // Remove "D:" + return lo.FilterMap(strings.Fields(line), func(d string, _ int) (string, bool) { + // e.g. D:!uclibc-utils scanelf musl=1.1.14-r10 so:libc.musl-x86_64.so.1 + if strings.HasPrefix(d, "!") { + return "", false + } + return a.trimRequirement(d), true + }) +} + +func (a alpinePkgAnalyzer) consolidateDependencies(pkgs []types.Package, provides map[string]string) { + for i := range pkgs { + // e.g. libc6 => libc6@2.31-13+deb11u4 + pkgs[i].DependsOn = lo.FilterMap(pkgs[i].DependsOn, func(d string, _ int) (string, bool) { + if pkgID, ok := provides[d]; ok { + return pkgID, true + } + return "", false + }) + sort.Strings(pkgs[i].DependsOn) + pkgs[i].DependsOn = slices.Compact(pkgs[i].DependsOn) + + if len(pkgs[i].DependsOn) == 0 { + pkgs[i].DependsOn = nil + } + } +} + +func (a alpinePkgAnalyzer) uniquePkgs(pkgs []types.Package) (uniqPkgs []types.Package) { + uniq := make(map[string]struct{}) + for _, pkg := range pkgs { + if _, ok := uniq[pkg.Name]; ok { + continue + } + uniqPkgs = append(uniqPkgs, pkg) + uniq[pkg.Name] = struct{}{} + } + return uniqPkgs +} + +func (a alpinePkgAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return slices.Contains(requiredFiles, filePath) +} + +func (a alpinePkgAnalyzer) Type() analyzer.Type { + return analyzer.TypeApk +} + +func (a alpinePkgAnalyzer) Version() int { + return analyzerVersion +} + +// decodeChecksumLine decodes checksum line +func decodeChecksumLine(line string) digest.Digest { + if len(line) < 2 { + log.Logger.Debugf("Unable to decode checksum line of apk package: %s", line) + return "" + } + // https://wiki.alpinelinux.org/wiki/Apk_spec#Package_Checksum_Field + // https://stackoverflow.com/a/71712569 + alg := digest.MD5 + d := line[2:] + if strings.HasPrefix(d, "Q1") { + alg = digest.SHA1 + d = d[2:] // remove `Q1` prefix + } + + decodedDigestString, err := base64.StdEncoding.DecodeString(d) + if err != nil { + log.Logger.Debugf("unable to decode digest: %s", err) + return "" + } + h := hex.EncodeToString(decodedDigestString) + return digest.NewDigestFromString(alg, h) +} diff --git a/pkg/fanal/analyzer/pkg/apk/apk_test.go b/pkg/fanal/analyzer/pkg/apk/apk_test.go new file mode 100644 index 000000000000..948cd1775da0 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/apk/apk_test.go @@ -0,0 +1,440 @@ +package apk + +import ( + "bufio" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var pkgs = []types.Package{ + { + ID: "musl@1.1.14-r10", + Name: "musl", + Version: "1.1.14-r10", + SrcName: "musl", + SrcVersion: "1.1.14-r10", + Licenses: []string{"MIT"}, + Arch: "x86_64", + Digest: "sha1:d68b402f35f57750f49156b0cb4e886a2ad35d2d", + InstalledFiles: []string{ + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1", + }, + }, + { + ID: "busybox@1.24.2-r9", + Name: "busybox", + Version: "1.24.2-r9", + SrcName: "busybox", + SrcVersion: "1.24.2-r9", + Licenses: []string{"GPL-2.0"}, + DependsOn: []string{"musl@1.1.14-r10"}, + Arch: "x86_64", + Digest: "sha1:ca124719267cd0bedc2f4cb850a286ac13f0ad44", + InstalledFiles: []string{ + "bin/busybox", + "bin/sh", + "etc/securetty", + "etc/udhcpd.conf", + "etc/logrotate.d/acpid", + }, + }, + { + ID: "alpine-baselayout@3.0.3-r0", + Name: "alpine-baselayout", + Version: "3.0.3-r0", + SrcName: "alpine-baselayout", + SrcVersion: "3.0.3-r0", + Licenses: []string{"GPL-2.0"}, + DependsOn: []string{"busybox@1.24.2-r9", "musl@1.1.14-r10"}, + Arch: "x86_64", + Digest: "sha1:a214896150411d72dd1fafdb32d1c6c4855cccfa", + InstalledFiles: []string{ + "etc/hosts", + "etc/sysctl.conf", + "etc/group", + "etc/protocols", + "etc/fstab", + "etc/mtab", + "etc/profile", + "etc/TZ", + "etc/shells", + "etc/motd", + "etc/inittab", + "etc/hostname", + "etc/modules", + "etc/services", + "etc/shadow", + "etc/passwd", + "etc/profile.d/color_prompt", + "etc/sysctl.d/00-alpine.conf", + "etc/modprobe.d/i386.conf", + "etc/modprobe.d/blacklist.conf", + "etc/modprobe.d/aliases.conf", + "etc/modprobe.d/kms.conf", + "etc/crontabs/root", + "sbin/mkmntdirs", + "var/spool/cron/crontabs", + }, + }, + { + ID: "alpine-keys@1.1-r0", + Name: "alpine-keys", + Version: "1.1-r0", + SrcName: "alpine-keys", + SrcVersion: "1.1-r0", + Licenses: []string{"GPL-3.0"}, + Arch: "x86_64", + Digest: "sha1:4def7ffaee6aeba700c1d62570326f75cbb8fa25", + InstalledFiles: []string{ + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4d07755e.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + }, + }, + { + ID: "zlib@1.2.8-r2", + Name: "zlib", + Version: "1.2.8-r2", + SrcName: "zlib", + SrcVersion: "1.2.8-r2", + Licenses: []string{"Zlib"}, + DependsOn: []string{"musl@1.1.14-r10"}, + Arch: "x86_64", + Digest: "sha1:efd04d34d40aa8eb331480127364c27a8ba760ef", + InstalledFiles: []string{ + "lib/libz.so.1.2.8", + "lib/libz.so.1", + }, + }, + { + ID: "libcrypto1.0@1.0.2h-r1", + Name: "libcrypto1.0", + Version: "1.0.2h-r1", + SrcName: "openssl", + SrcVersion: "1.0.2h-r1", + Licenses: []string{"openssl"}, + DependsOn: []string{"musl@1.1.14-r10", "zlib@1.2.8-r2"}, + Arch: "x86_64", + Digest: "sha1:65c860ff8f103b664f40ba849a3f5a51c69c8beb", + InstalledFiles: []string{ + "lib/libcrypto.so.1.0.0", + "usr/bin/c_rehash", + "usr/lib/libcrypto.so.1.0.0", + "usr/lib/engines/libubsec.so", + "usr/lib/engines/libatalla.so", + "usr/lib/engines/libcapi.so", + "usr/lib/engines/libgost.so", + "usr/lib/engines/libcswift.so", + "usr/lib/engines/libchil.so", + "usr/lib/engines/libgmp.so", + "usr/lib/engines/libnuron.so", + "usr/lib/engines/lib4758cca.so", + "usr/lib/engines/libsureware.so", + "usr/lib/engines/libpadlock.so", + "usr/lib/engines/libaep.so", + }, + }, + { + ID: "libssl1.0@1.0.2h-r1", + Name: "libssl1.0", + Version: "1.0.2h-r1", + SrcName: "openssl", + SrcVersion: "1.0.2h-r1", + Licenses: []string{"openssl"}, + Digest: "sha1:7120f337e93b2b4c44e0f5f31a15b60dc678ca14", + DependsOn: []string{ + "libcrypto1.0@1.0.2h-r1", + "musl@1.1.14-r10", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "lib/libssl.so.1.0.0", + "usr/lib/libssl.so.1.0.0", + }, + }, + { + ID: "apk-tools@2.6.7-r0", + Name: "apk-tools", + Version: "2.6.7-r0", + SrcName: "apk-tools", + SrcVersion: "2.6.7-r0", + Licenses: []string{"GPL-2.0"}, + Digest: "sha1:0990c0acd62b4175818c3a4cc60ed11f14e23bd8", + DependsOn: []string{ + "libcrypto1.0@1.0.2h-r1", + "libssl1.0@1.0.2h-r1", + "musl@1.1.14-r10", + "zlib@1.2.8-r2", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "sbin/apk", + }, + }, + { + ID: "scanelf@1.1.6-r0", + Name: "scanelf", + Version: "1.1.6-r0", + SrcName: "pax-utils", + SrcVersion: "1.1.6-r0", + Licenses: []string{"GPL-2.0"}, + Digest: "sha1:f9bab817c5ad93e92a6218bc0f7596b657c02d90", + DependsOn: []string{"musl@1.1.14-r10"}, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/bin/scanelf", + }, + }, + { + ID: "musl-utils@1.1.14-r10", + Name: "musl-utils", + Version: "1.1.14-r10", + SrcName: "musl", + SrcVersion: "1.1.14-r10", + Licenses: []string{"MIT", "BSD-3-Clause", "GPL-2.0"}, + Digest: "sha1:608aa1dd39eff7bc6615d3e5e33383750f8f5ecc", + DependsOn: []string{ + "musl@1.1.14-r10", + "scanelf@1.1.6-r0", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "sbin/ldconfig", + "usr/bin/iconv", + "usr/bin/ldd", + "usr/bin/getconf", + "usr/bin/getent", + }, + }, + { + ID: "libc-utils@0.7-r0", + Name: "libc-utils", + Version: "0.7-r0", + SrcName: "libc-dev", + SrcVersion: "0.7-r0", + Licenses: []string{"GPL-3.0"}, + Digest: "sha1:9055bc7afd76cf2672198042f72fc4a5ed4fa961", + DependsOn: []string{"musl-utils@1.1.14-r10"}, + Arch: "x86_64", + //InstalledFiles: []string{}, + }, + { + ID: "pkgconf@1.6.0-r0", + Name: "pkgconf", + Version: "1.6.0-r0", + SrcName: "pkgconf", + SrcVersion: "1.6.0-r0", + Licenses: []string{"ISC"}, + Digest: "sha1:e6242ac29589c8a84a4b179b491ea7c29fce66a9", + DependsOn: []string{"musl@1.1.14-r10"}, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/bin/pkgconf", + "usr/bin/pkg-config", + "usr/lib/libpkgconf.so.3.0.0", + "usr/lib/libpkgconf.so.3", + "usr/share/aclocal/pkg.m4", + }, + }, + + { + ID: "sqlite-libs@3.26.0-r3", + Name: "sqlite-libs", + Version: "3.26.0-r3", + SrcName: "sqlite", + SrcVersion: "3.26.0-r3", + Licenses: []string{"Public-Domain"}, + Digest: "sha1:1464946c3a5f0dd5a67ca1af930fc17af7a74474", + DependsOn: []string{"musl@1.1.14-r10"}, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/lib/libsqlite3.so.0", + "usr/lib/libsqlite3.so.0.8.6", + }, + }, + + { + ID: "test@2.9.11_pre20061021-r2", + Name: "test", + Version: "2.9.11_pre20061021-r2", + SrcName: "test-parent", + SrcVersion: "2.9.11_pre20061021-r2", + Licenses: []string{"Public-Domain"}, + Digest: "sha1:f0bf315ec54828188910e4a665c00bc48bdbdd7d", + DependsOn: []string{ + "pkgconf@1.6.0-r0", + "sqlite-libs@3.26.0-r3", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/lib/libsqlite3.so", + "usr/lib/pkgconfig/sqlite3.pc", + "usr/include/sqlite3ext.h", + "usr/include/sqlite3.h", + }, + }, + + { + ID: "ada-libs@2.7.4-r0", + Name: "ada-libs", + Version: "2.7.4-r0", + SrcName: "ada", + SrcVersion: "2.7.4-r0", + Licenses: []string{"Apache-2.0", "MIT", "MPL-2.0"}, + Digest: "sha1:593154f80c440685448e0f52479725d7bc9b678d", + DependsOn: []string{ + "musl@1.1.14-r10", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/lib/libada.so.2", + "usr/lib/libada.so.2.7.4", + }, + }, +} + +var files = []string{ + // musl-1.1.14-r10 + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1", + + // busybox-1.24.2-r9 + "bin/busybox", + "bin/sh", + "etc/securetty", + "etc/udhcpd.conf", + "etc/logrotate.d/acpid", + + // alpine-baselayout-3.0.3-r0 + "etc/hosts", + "etc/sysctl.conf", + "etc/group", + "etc/protocols", + "etc/fstab", + "etc/mtab", + "etc/profile", + "etc/TZ", + "etc/shells", + "etc/motd", + "etc/inittab", + "etc/hostname", + "etc/modules", + "etc/services", + "etc/shadow", + "etc/passwd", + "etc/profile.d/color_prompt", + "etc/sysctl.d/00-alpine.conf", + "etc/modprobe.d/i386.conf", + "etc/modprobe.d/blacklist.conf", + "etc/modprobe.d/aliases.conf", + "etc/modprobe.d/kms.conf", + "etc/crontabs/root", + "sbin/mkmntdirs", + "var/spool/cron/crontabs", + + // alpine-keys-1.1-r0 + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4d07755e.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + + // zlib-1.2.8-r2 + "lib/libz.so.1.2.8", + "lib/libz.so.1", + + // libcrypto1.0-1.0.2h-r1 + "lib/libcrypto.so.1.0.0", + "usr/bin/c_rehash", + "usr/lib/libcrypto.so.1.0.0", + "usr/lib/engines/libubsec.so", + "usr/lib/engines/libatalla.so", + "usr/lib/engines/libcapi.so", + "usr/lib/engines/libgost.so", + "usr/lib/engines/libcswift.so", + "usr/lib/engines/libchil.so", + "usr/lib/engines/libgmp.so", + "usr/lib/engines/libnuron.so", + "usr/lib/engines/lib4758cca.so", + "usr/lib/engines/libsureware.so", + "usr/lib/engines/libpadlock.so", + "usr/lib/engines/libaep.so", + + // libssl1.0-1.0.2h-r1 + "lib/libssl.so.1.0.0", + "usr/lib/libssl.so.1.0.0", + + // apk-tools-2.6.7-r0 + "sbin/apk", + + // scanelf-1.1.6-r0 + "usr/bin/scanelf", + + // musl-utils-1.1.14-r10 + "sbin/ldconfig", + "usr/bin/iconv", + "usr/bin/ldd", + "usr/bin/getconf", + "usr/bin/getent", + + // libc-utils-0.7-r0 + + // pkgconf-1.6.0-r0 + "usr/bin/pkgconf", + "usr/bin/pkg-config", + "usr/lib/libpkgconf.so.3.0.0", + "usr/lib/libpkgconf.so.3", + "usr/share/aclocal/pkg.m4", + + // sqlite-libs-3.26.0-r3 + "usr/lib/libsqlite3.so.0", + "usr/lib/libsqlite3.so.0.8.6", + + // test-2.9.11_pre20061021-r2 + "usr/lib/libsqlite3.so", + "usr/lib/pkgconfig/sqlite3.pc", + "usr/include/sqlite3ext.h", + "usr/include/sqlite3.h", + + // ada-libs@2.5.1-r0 + "usr/lib/libada.so.2", + "usr/lib/libada.so.2.7.4", +} + +func TestParseApkInfo(t *testing.T) { + var tests = []struct { + name string + path string + wantPkgs []types.Package + wantFiles []string + }{ + { + name: "happy path", + path: "./testdata/apk", + wantPkgs: pkgs, + wantFiles: files, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := alpinePkgAnalyzer{} + f, err := os.Open(tt.path) + require.NoError(t, err) + defer f.Close() + scanner := bufio.NewScanner(f) + gotPkgs, gotFiles := a.parseApkInfo(scanner) + + assert.Equal(t, tt.wantPkgs, gotPkgs) + assert.Equal(t, tt.wantFiles, gotFiles) + }) + } +} diff --git a/pkg/fanal/analyzer/pkg/apk/testdata/apk b/pkg/fanal/analyzer/pkg/apk/testdata/apk new file mode 100644 index 000000000000..633fe29aa012 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/apk/testdata/apk @@ -0,0 +1,564 @@ +C:Q11otALzX1d1D0kVawy06IairTXS0= +P:musl +V:1.1.14-r10 +A:x86_64 +S:445080 +I:569344 +T:the musl c library (libc) implementation +U:http://www.musl-libc.org/ +L:MIT +o:musl +m:Timo Teräs +t:1466181580 +c:e6c226fbe17bb8f856dce5f0d9bc088b333e6225 +p:so:libc.musl-x86_64.so.1=1 +F:lib +R:libc.musl-x86_64.so.1 +a:0:0:777 +Z:Q17yJ3JFNypA4mxhJJr0ou6CzsJVI= +R:ld-musl-x86_64.so.1 +a:0:0:755 +Z:Q1KUwsFGLHn/enpN9+QIpK/FmixtQ= + +P:invalidPackageWithoutAVersion + +C:Q1yhJHGSZ80L7cL0y4UKKGrBPwrUQ= +P:busybox +V:1.24.2-r9 +A:x86_64 +S:642121 +I:909312 +T:Size optimized toolbox of many common UNIX utilities +U:http://busybox.net +L:GPL2 +o:busybox +m:Natanael Copa +t:1466671780 +c:386b639b3917a9d1b8588dd87f09ed446501cddf +D:so:libc.musl-x86_64.so.1 +F:bin +R:busybox +a:0:0:755 +Z:Q1xOlCsdvx4O0gnKWoFCNKjz2quRE= +R:sh +a:0:0:777 +Z:Q1pcfTfDNEbNKQc2s1tia7da05M8Q= +F:etc +R:securetty +Z:Q14VDshgWFleuDbp4jqXk+UNES65Q= +R:udhcpd.conf +Z:Q1PWhOJ+TaEzAXw+XC6kkz/FXI/KA= +F:etc/logrotate.d +R:acpid +Z:Q1bPM2hPZy1LntZ/YdI4ZFJzVl1Y8= +F:etc/network +F:etc/network/if-up.d +F:etc/network/if-post-up.d +F:etc/network/if-pre-up.d +F:etc/network/if-post-down.d +F:etc/network/if-pre-down.d +F:etc/network/if-down.d +F:sbin +F:tmp +M:0:0:1777 +F:usr +F:usr/sbin +F:usr/bin +F:var +F:var/lib +F:var/lib/udhcpd +F:var/cache +F:var/cache/misc + +C:Q1ohSJYVBBHXLdH6/bMtHGxIVczPo= +P:alpine-baselayout +V:3.0.3-r0 +A:x86_64 +S:74697 +I:401408 +T:Alpine base dir structure and init scripts +U:http://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout +L:GPL2 +o:alpine-baselayout +m:Natanael Copa +t:1466181584 +c:d0bef446b94220475c60e78f2e081f38390b89ca +D:busybox so:libc.musl-x86_64.so.1 +F:dev +F:dev/shm +F:dev/pts +F:etc +R:hosts +Z:Q1S93L8EsQ/7zGSnfGDfj5I7bjCS4= +R:sysctl.conf +Z:Q14upz3tfnNxZkIEsUhWn7Xoiw96g= +R:group +Z:Q1zNuxdqs1x+nJO8WucpZfQrdiapA= +R:protocols +Z:Q13FqXUnvuOpMDrH/6rehxuYAEE34= +R:fstab +Z:Q11Q7hNe8QpDS531guqCdrXBzoA/o= +R:mtab +a:0:0:777 +Z:Q1kiljhXXH1LlQroHsEJIkPZg2eiw= +R:profile +Z:Q1FrM1yy3WJbQTc9LgnKTn5tRovlE= +R:TZ +Z:Q1uHH18uOLEzp56qFUP843WSoKM9E= +R:shells +Z:Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA= +R:motd +Z:Q1MaUHN/Rf32Lf67Owrq1BXQU7usE= +R:inittab +Z:Q1TsthbhW7QzWRe1E/NKwTOuD4pHc= +R:hostname +Z:Q16nVwYVXP/tChvUPdukVD2ifXOmc= +R:modules +Z:Q1C7P4uPQo8B6P6V+O78ybHl0AHhA= +R:services +Z:Q1NBe0rrC8HMzNmVf4ybSENcsdey0= +R:shadow +a:0:42:640 +Z:Q1LG0ii8vP4gQgDmSnK0WBtjtovlg= +R:passwd +Z:Q11HpI0rBp2zsun4+LIIBENg8JQUE= +F:etc/profile.d +R:color_prompt +Z:Q10wL23GuSCVfumMRgakabUI6EsSk= +F:etc/init.d +F:etc/apk +F:etc/sysctl.d +R:00-alpine.conf +Z:Q1kZy9KEvjykp1vCw1kWgdvBuEXvg= +F:etc/modprobe.d +R:i386.conf +Z:Q1pnay/njn6ol9cCssL7KiZZ8etlc= +R:blacklist.conf +Z:Q1+TdC1pulajuYy0ebcos8V/aMeqk= +R:aliases.conf +Z:Q1udaZLaeaalyuCcnBgCKPIybDO08= +R:kms.conf +Z:Q1yH/c6fBvCWn0Huny5Rf/GET2Jbs= +F:etc/modules-load.d +F:etc/opt +F:etc/conf.d +F:etc/crontabs +R:root +a:0:0:600 +Z:Q1vfk1apUWI4yLJGhhNRd0kJixfvY= +F:etc/periodic +F:etc/periodic/hourly +F:etc/periodic/weekly +F:etc/periodic/monthly +F:etc/periodic/15min +F:etc/periodic/daily +F:etc/network +F:etc/network/if-up.d +F:etc/network/if-pre-up.d +F:etc/network/if-post-down.d +F:etc/network/if-down.d +F:home +F:lib +F:lib/mdev +F:lib/firmware +F:media +F:media/floppy +F:media/cdrom +F:media/usb +F:mnt +F:proc +F:root +M:0:0:700 +F:run +F:sbin +R:mkmntdirs +a:0:0:755 +Z:Q1lGBnGMsnB3SEZL/oHeN99F1/ie8= +F:srv +F:sys +F:tmp +M:0:0:1777 +F:usr +F:usr/sbin +F:usr/bin +F:usr/local +F:usr/local/bin +F:usr/local/lib +F:usr/local/share +F:usr/share +F:usr/share/man +F:usr/share/misc +F:var +F:var/lock +F:var/lock/subsys +F:var/tmp +M:0:0:1777 +F:var/log +F:var/lib +F:var/lib/misc +F:var/local +F:var/opt +F:var/cache +F:var/cache/misc +F:var/spool +F:var/spool/cron +R:crontabs +a:0:0:777 +Z:Q1OFZt+ZMp7j0Gny0rqSKuWJyqYmA= +F:var/empty +F:var/run + +C:Q1Te9/+u5q66cAwdYlcDJvdcu4+iU= +P:alpine-keys +V:1.1-r0 +A:x86_64 +S:7787 +I:36864 +T:Public keys for Alpine Linux packages +U:http://alpinelinux.org +L:GPL +o:alpine-keys +m:Natanael Copa +t:1461964035 +c:d0b08b1e17d40d21196df7709fdb95f37165615d +r:alpine-base +F:etc +F:etc/apk +F:etc/apk/keys +R:alpine-devel@lists.alpinelinux.org-4d07755e.rsa.pub +Z:Q1XfH9IG0ZFgbOIssIhpiWqDlSspY= +R:alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub +Z:Q1BTqS+H/UUyhQuzHwiBl47+BTKuU= +R:alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub +Z:Q1v7YWZYzAWoclaLDI45jEguI7YN0= +R:alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub +Z:Q1NnGuDsdQOx4ZNYfB3N97eLyGPkI= +R:alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub +Z:Q1OvCFSO94z97c80mIDCxqGkh2Og4= + +C:Q179BNNNQKqOszFIASc2TCeounYO8= +P:zlib +V:1.2.8-r2 +A:x86_64 +S:71733 +I:98304 +T:A compression/decompression Library +U:http://zlib.net +L:zlib +o:zlib +m:Natanael Copa +t:1461931151 +c:a7329a4de4d10d99206c78c229dc3742880cd042 +D:so:libc.musl-x86_64.so.1 +p:so:libz.so.1=1.2.8 +F:lib +R:libz.so.1.2.8 +a:0:0:755 +Z:Q1bh8VMdNZcPKyIQTtLMtz/0VL2H0= +R:libz.so.1 +a:0:0:777 +Z:Q1x1VqGi0rW5lxmvPQOjnqNapz/OU= + +C:Q1Zchg/48QO2ZPQLqEmj9aUcaci+s= +P:libcrypto1.0 +V:1.0.2h-r1 +A:x86_64 +S:1714530 +I:2527232 +T:Crypto library from openssl +U:http://openssl.org +L:openssl +o:openssl +m:Timo Teras +t:1466620012 +c:38c6e1fd86f4d9cba4c146b8bdcd71f84e1a4ee7 +D:so:libc.musl-x86_64.so.1 so:libz.so.1 +p:so:libcrypto.so.1.0.0=1.0.0 +F:lib +R:libcrypto.so.1.0.0 +a:0:0:555 +Z:Q1DgSoxP0AWq64XJSPXT0yRTjSSBk= +F:usr +F:usr/bin +R:c_rehash +a:0:0:755 +Z:Q1XZt0LbTr44grnBtK+Yihjynh2EE= +F:usr/lib +R:libcrypto.so.1.0.0 +a:0:0:777 +Z:Q1jLDKGBtunzKi5FKmK/QTAqfh6uI= +F:usr/lib/engines +R:libubsec.so +a:0:0:555 +Z:Q1SaoP91RpISCN8KO3AxuB8Tzyc/A= +R:libatalla.so +a:0:0:555 +Z:Q1W31yRhAZE9X5Z5zy9l6mkn1tbcQ= +R:libcapi.so +a:0:0:555 +Z:Q1iriyqA2XunZb8pxmsOeRML2ZsRg= +R:libgost.so +a:0:0:555 +Z:Q1zwS6yHAzzdnrHb9BV8pIsE2yIgg= +R:libcswift.so +a:0:0:555 +Z:Q16/TBTN+WkIFQeFlCPTDc5Xs33bU= +R:libchil.so +a:0:0:555 +Z:Q1fMssNRSfAgg4nZVYm0IzYG2gTLk= +R:libgmp.so +a:0:0:555 +Z:Q1onZiPB/LsF3Xqn8rwH5FcYLkuf4= +R:libnuron.so +a:0:0:555 +Z:Q189GVmg2NFt2nTCqfcSl7Wtoym1o= +R:lib4758cca.so +a:0:0:555 +Z:Q1aNXgEbvxfvZdZpa4frZ9p6eq2Y4= +R:libsureware.so +a:0:0:555 +Z:Q14Z9HkfG+WqvGRIb42iugBuBKOag= +R:libpadlock.so +a:0:0:555 +Z:Q1OAwUUirl7Q1OiqTjB96lKBQYbMc= +R:libaep.so +a:0:0:555 +Z:Q1nrvE4qxl4AXEC/psF1Eh9n6E7Pg= + +C:Q1cSDzN+k7K0xE4PXzGhW2DcZ4yhQ= +P:libssl1.0 +V:1.0.2h-r1 +A:x86_64 +S:274743 +I:442368 +T:SSL shared libraries +U:http://openssl.org +L:openssl +o:openssl +m:Timo Teras +t:1466620012 +c:38c6e1fd86f4d9cba4c146b8bdcd71f84e1a4ee7 +D:so:libc.musl-x86_64.so.1 so:libcrypto.so.1.0.0 +p:so:libssl.so.1.0.0=1.0.0 +F:lib +R:libssl.so.1.0.0 +a:0:0:555 +Z:Q1wdqn4897nQP5l/f3SV4DWf9QOkQ= +F:usr +F:usr/lib +R:libssl.so.1.0.0 +a:0:0:777 +Z:Q1ke5dnHGVWcEyRpOe0/lKEqizHHQ= + +C:Q1CZDArNYrQXWBjDpMxg7RHxTiO9g= +P:apk-tools +V:2.6.7-r0 +A:x86_64 +S:146592 +I:253952 +T:Alpine Package Keeper - package manager for alpine +U:http://git.alpinelinux.org/cgit/apk-tools/ +L:GPL2 +o:apk-tools +m:Natanael Copa +t:1464341138 +c:08b6e2ae43356fbb24ef5c262fb08bfe09d675b0 +D:so:libc.musl-x86_64.so.1 so:libcrypto.so.1.0.0 so:libssl.so.1.0.0 so:libz.so.1 +F:etc +F:etc/apk +F:etc/apk/keys +F:etc/apk/protected_paths.d +F:sbin +R:apk +a:0:0:755 +Z:Q1ozOGvVOspzXfX1bUFjrjZ6ygEB0= +F:usr +F:var +F:var/lib +F:var/lib/apk +F:var/cache +F:var/cache/misc + +C:Q1+bq4F8Wtk+kqYhi8D3WWtlfALZA= +P:scanelf +V:1.1.6-r0 +A:x86_64 +S:53666 +I:90112 +T:Scan ELF binaries for stuff +U:https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities +L:GPL2 +o:pax-utils +m:Natanael Copa +t:1461934341 +c:891f6254fb6e1f11f62ee2c9fd35784fd313b9d1 +D:so:libc.musl-x86_64.so.1 +r:pax-utils +F:usr +F:usr/bin +R:scanelf +a:0:0:755 +Z:Q12S+aDBkRA63GvTmx45HqqbOKBz0= + +C:Q1YIqh3Tnv97xmFdPl4zODdQ+PXsw= +P:musl-utils +V:1.1.14-r10 +A:x86_64 +S:50427 +I:110592 +T:the musl c library (libc) implementation +U:http://www.musl-libc.org/ +L:MIT BSD GPL2+ +o:musl +m:Timo Teräs +t:1466181579 +c:e6c226fbe17bb8f856dce5f0d9bc088b333e6225 +D:!uclibc-utils scanelf musl=1.1.14-r10 so:libc.musl-x86_64.so.1 +r:libiconv uclibc-utils +F:sbin +R:ldconfig +a:0:0:755 +Z:Q1Kja2+POZKxEkUOZqwSjC6kmaED4= +F:usr +F:usr/bin +R:iconv +a:0:0:755 +Z:Q1DmLsMEsBDtwb8S0z1pl0MYH29+o= +R:ldd +a:0:0:777 +Z:Q1a/NMxsyXbxLcmrTyGQtovas5gJk= +R:getconf +a:0:0:755 +Z:Q1iub9vwJRjlaCnu21SWjb504fUqk= +R:getent +a:0:0:755 +Z:Q1Z5dnRfQ7nvRbRRSpM1k0J7UMdng= + +C:Q1kFW8ev12zyZyGYBC9y/Epe1PqWE= +P:libc-utils +V:0.7-r0 +A:x86_64 +S:2804 +I:4096 +T:Meta package to pull in correct libc +U:http://alpinelinux.org +L:GPL +o:libc-dev +m:Natanael Copa +t:1461934274 +c:e3725c0af137717d6883265a92db3838900b5cee +D:musl-utils + +C:Q15iQqwpWJyKhKSxebSR6nwp/OZqk= +P:pkgconf +V:1.6.0-r0 +A:x86_64 +S:43001 +I:143360 +T:development framework configuration tools +U:https://git.dereferenced.org/pkgconf/pkgconf +L:ISC +o:pkgconf +m:William Pitcock +t:1547496958 +c:810b2b4cab3aad63cb338988d9d976012b6ad062 +D:so:libc.musl-x86_64.so.1 +p:pkgconfig=1 so:libpkgconf.so.3=3.0.0 cmd:pkg-config cmd:pkgconf +r:pkgconfig +F:usr +F:usr/bin +R:pkgconf +a:0:0:755 +Z:Q1h2xvgvvUejQFBbqDvOMhcdazFGI= +R:pkg-config +a:0:0:777 +Z:Q18oszBu4K1Rwi+tYKAA91sDfWTzE= +F:usr/lib +R:libpkgconf.so.3.0.0 +a:0:0:755 +Z:Q1HQLOx86sW3RRtphIYcWcBBmJo6M= +R:libpkgconf.so.3 +a:0:0:777 +Z:Q1NQYO0TsKR3JRQxUrYhF/izzKEOU= +F:usr/share +F:usr/share/aclocal +R:pkg.m4 +Z:Q1pVlmIMTTArohUPZPu3OCLGaH+e0= + +C:Q1FGSUbDpfDdWmfKGvkw/BevenRHQ= +P:sqlite-libs +V:3.26.0-r3 +A:x86_64 +S:481468 +I:917504 +T:Sqlite3 library +U:http://www.sqlite.org +L:Public-Domain +o:sqlite +m:Carlo Landmeter +t:1546255353 +c:856c64b1dc1f9b8176c60e28808482f8503c4e98 +D:so:libc.musl-x86_64.so.1 +p:so:libsqlite3.so.0=0.8.6 +r:sqlite +F:usr +F:usr/lib +R:libsqlite3.so.0 +a:0:0:777 +Z:Q1MZwGMEis9uc78EhxmxyozT2ZxZM= +R:libsqlite3.so.0.8.6 +a:0:0:755 +Z:Q1BOGonxBB2SdxbKENvJNl9ifknaE= + +C:Q18L8xXsVIKBiJEOSmZcALxIvb3X0= +P:test +V:2.9.11_pre20061021-r2 +A:x86_64 +S:151185 +I:618496 +T:C library that implements an SQL database engine (development files) +U:http://www.sqlite.org +L:Public-Domain +o:test-parent +m:Carlo Landmeter +t:1546255353 +c:856c64b1dc1f9b8176c60e28808482f8503c4e98 +D:pkgconfig sqlite-libs=3.26.0-r3 +p:pc:sqlite3=3.26.0 +F:usr +F:usr/lib +R:libsqlite3.so +a:0:0:777 +Z:Q1MZwGMEis9uc78EhxmxyozT2ZxZM= +F:usr/lib/pkgconfig +R:sqlite3.pc +Z:Q1Gu7uz+QOw0X+WZ8MKc8iNVSnsBA= +F:usr/include +R:sqlite3ext.h +Z:Q1riWNHq9ufQzhyMXm7raBW+ZL9z0= +R:sqlite3.h +Z:Q11MT2xE8JuMfBRYu6BDTz2PY95Vw= + +C:Q1WTFU+AxEBoVEjg9SR5cl17ybZ40= +P:ada-libs +V:2.7.4-r0 +A:x86_64 +S:166703 +I:491520 +T:WHATWG-compliant and fast URL parser written in modern C++ (libraries) +U:https://ada-url.github.io/ada +L:( Apache-2.0 OR MIT ) AND MPL-2.0 +o:ada +m:Jakub Jirutka +t:1701726025 +c:fa40f3454f9c60870d54115aac4161f9ab7c667f +D:so:libc.musl-x86_64.so.1 +p:so:libada.so.2=2.7.4 +F:usr +F:usr/lib +R:libada.so.2 +a:0:0:777 +Z:Q1qDM97tDEseDtdayU2yr3eJjOl5I= +R:libada.so.2.7.4 +a:0:0:755 +Z:Q1LUjWSS3wH8zDBHd0pYxED/hWBhk= \ No newline at end of file diff --git a/pkg/fanal/analyzer/pkg/dpkg/copyright.go b/pkg/fanal/analyzer/pkg/dpkg/copyright.go new file mode 100644 index 000000000000..193fd5efe8d6 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/copyright.go @@ -0,0 +1,151 @@ +package dpkg + +import ( + "bufio" + "context" + "io" + "os" + "path" + "regexp" + "strings" + + "github.com/samber/lo" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +func init() { + analyzer.RegisterAnalyzer(&dpkgLicenseAnalyzer{}) +} + +var ( + dpkgLicenseAnalyzerVersion = 1 + + commonLicenseReferenceRegexp = regexp.MustCompile(`/?usr/share/common-licenses/([0-9A-Za-z_.+-]+[0-9A-Za-z+])`) +) + +// dpkgLicenseAnalyzer parses copyright files and detect licenses +type dpkgLicenseAnalyzer struct { + licenseFull bool + classifierConfidenceLevel float64 +} + +// Analyze parses /usr/share/doc/*/copyright files +func (a *dpkgLicenseAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + findings, err := a.parseCopyright(input.Content) + if err != nil { + return nil, xerrors.Errorf("parse copyright %s: %w", input.FilePath, err) + } + + // If licenses are not found, fallback to the classifier + if len(findings) == 0 && a.licenseFull { + // Rewind the reader to the beginning of the stream after saving + if _, err = input.Content.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("seek error: %w", err) + } + licenseFile, err := licensing.Classify(input.FilePath, input.Content, a.classifierConfidenceLevel) + if err != nil { + return nil, xerrors.Errorf("license classification error: %w", err) + } + findings = licenseFile.Findings + } + + if len(findings) == 0 { + return nil, nil + } + + // e.g. "usr/share/doc/zlib1g/copyright" => "zlib1g" + pkgName := strings.Split(input.FilePath, "/")[3] + + return &analyzer.AnalysisResult{ + Licenses: []types.LicenseFile{ + { + Type: types.LicenseTypeDpkg, + FilePath: input.FilePath, + Findings: findings, + PkgName: pkgName, + }, + }, + }, nil +} + +// parseCopyright parses /usr/share/doc/*/copyright files +func (a *dpkgLicenseAnalyzer) parseCopyright(r xio.ReadSeekerAt) ([]types.LicenseFinding, error) { + scanner := bufio.NewScanner(r) + var licenses []string + for scanner.Scan() { + line := scanner.Text() + + switch { + case strings.HasPrefix(line, "License:"): + // Machine-readable format + // cf. https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#:~:text=The%20debian%2Fcopyright%20file%20must,in%20the%20Debian%20Policy%20Manual. + l := strings.TrimSpace(line[8:]) + + l = normalizeLicense(l) + if len(l) > 0 { + for _, lic := range licensing.SplitLicenses(l) { + lic = licensing.Normalize(lic) + if !slices.Contains(licenses, lic) { + licenses = append(licenses, lic) + } + } + } + case strings.Contains(line, "/usr/share/common-licenses/"): + // Common license pattern + license := commonLicenseReferenceRegexp.FindStringSubmatch(line) + if len(license) == 2 { + l := licensing.Normalize(license[1]) + if !slices.Contains(licenses, l) { + licenses = append(licenses, l) + } + } + } + } + + return lo.Map(licenses, func(license string, _ int) types.LicenseFinding { + return types.LicenseFinding{Name: license} + }), nil + +} + +func (a *dpkgLicenseAnalyzer) Init(opt analyzer.AnalyzerOptions) error { + a.licenseFull = opt.LicenseScannerOption.Full + a.classifierConfidenceLevel = opt.LicenseScannerOption.ClassifierConfidenceLevel + return nil +} + +func (a *dpkgLicenseAnalyzer) Required(filePath string, _ os.FileInfo) bool { + // To exclude files from subfolders + // e.g. usr/share/doc/ca-certificates/examples/ca-certificates-local/debian/copyright + match, err := path.Match("usr/share/doc/*/copyright", filePath) + if err != nil { + return false + } + return match +} + +func (a *dpkgLicenseAnalyzer) Type() analyzer.Type { + return analyzer.TypeDpkgLicense +} + +func (a *dpkgLicenseAnalyzer) Version() int { + return dpkgLicenseAnalyzerVersion +} + +// normalizeLicense returns a normalized license identifier in a heuristic way +func normalizeLicense(s string) string { + // "The MIT License (MIT)" => "The MIT License" + s, _, _ = strings.Cut(s, "(") + + // Very rarely has below phrases + s = strings.TrimPrefix(s, "The main library is licensed under ") + s = strings.TrimSuffix(s, " license") + + return strings.TrimSpace(s) +} diff --git a/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go b/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go new file mode 100644 index 000000000000..d44899e4362e --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go @@ -0,0 +1,135 @@ +package dpkg + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_dpkgLicenseAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + filePath string + testFile string + want *analyzer.AnalysisResult + }{ + { + name: "machine-readable format", + filePath: "usr/share/doc/zlib1g/copyright", + testFile: "testdata/license-pattern-and-classifier-copyright", + want: &analyzer.AnalysisResult{ + Licenses: []types.LicenseFile{ + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/zlib1g/copyright", + Findings: []types.LicenseFinding{ + {Name: "GPL-1.0"}, + {Name: "Artistic"}, + {Name: "BSD-4-clause-POWERDOG"}, + {Name: "Zlib"}, + }, + PkgName: "zlib1g", + }, + }, + }, + }, + { + name: "common-licenses format", + filePath: "usr/share/doc/adduser/copyright", + testFile: "testdata/common-license-copyright", + want: &analyzer.AnalysisResult{ + Licenses: []types.LicenseFile{ + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/adduser/copyright", + Findings: []types.LicenseFinding{ + {Name: "GPL-2.0"}, + }, + PkgName: "adduser", + }, + }, + }, + }, + { + name: "machine-readable and common-licenses format", + filePath: "usr/share/doc/apt/copyright", + testFile: "testdata/all-patterns-copyright", + want: &analyzer.AnalysisResult{ + Licenses: []types.LicenseFile{ + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/apt/copyright", + Findings: []types.LicenseFinding{ + {Name: "GPL-2.0"}, + }, + PkgName: "apt", + }, + }, + }, + }, + { + name: "no license found", + filePath: "usr/share/doc/tzdata/copyright", + testFile: "testdata/no-license-copyright", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.testFile) + require.NoError(t, err) + + input := analyzer.AnalysisInput{ + Content: f, + FilePath: tt.filePath, + } + a := dpkgLicenseAnalyzer{} + + license, err := a.Analyze(context.Background(), input) + require.NoError(t, err) + assert.Equal(t, tt.want, license) + }) + } +} + +func Test_dpkgLicenseAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path", + filePath: "usr/share/doc/eject/copyright", + want: true, + }, + { + name: "copyright files in subfolder", + filePath: "usr/share/doc/ca-certificates/examples/ca-certificates-local/debian/copyright", + want: false, + }, + { + name: "bad prefix", + filePath: "usr/share/doc/eject/copyright/file", + want: false, + }, + { + name: "bad file name", + filePath: "usr/share/doc/eject/copyright/foo", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := dpkgLicenseAnalyzer{} + assert.Equal(t, tt.want, a.Required(tt.filePath, nil)) + }) + } +} diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go new file mode 100644 index 000000000000..74fd4d82ef7b --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go @@ -0,0 +1,360 @@ +package dpkg + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "io/fs" + "net/textproto" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + + debVersion "github.com/knqyf263/go-deb-version" + "github.com/samber/lo" + "go.uber.org/zap" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeDpkg, newDpkgAnalyzer) +} + +type dpkgAnalyzer struct{} + +func newDpkgAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &dpkgAnalyzer{}, nil +} + +const ( + analyzerVersion = 5 + + statusFile = "var/lib/dpkg/status" + statusDir = "var/lib/dpkg/status.d/" + infoDir = "var/lib/dpkg/info/" + availableFile = "var/lib/dpkg/available" +) + +var ( + dpkgSrcCaptureRegexp = regexp.MustCompile(`(?P[^\s]*)( \((?P.*)\))?`) + dpkgSrcCaptureRegexpNames = dpkgSrcCaptureRegexp.SubexpNames() +) + +func (a dpkgAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var systemInstalledFiles []string + var packageInfos []types.PackageInfo + + // parse `available` file to get digest for packages + digests, err := a.parseDpkgAvailable(input.FS) + if err != nil { + log.Logger.Debugf("Unable to parse %q file: %s", availableFile, err) + } + + required := func(path string, d fs.DirEntry) bool { + return path != availableFile + } + + packageFiles := make(map[string][]string) + + // parse other files + err = fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + // parse list files + if a.isListFile(filepath.Split(path)) { + scanner := bufio.NewScanner(r) + systemFiles, err := a.parseDpkgInfoList(scanner) + if err != nil { + return err + } + packageFiles[strings.TrimSuffix(filepath.Base(path), ".list")] = systemFiles + systemInstalledFiles = append(systemInstalledFiles, systemFiles...) + return nil + } + // parse status files + infos, err := a.parseDpkgStatus(path, r, digests) + if err != nil { + return err + } + packageInfos = append(packageInfos, infos...) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("dpkg walk error: %w", err) + } + + // map the packages to their respective files + for i, pkgInfo := range packageInfos { + for j, pkg := range pkgInfo.Packages { + installedFiles, found := packageFiles[pkg.Name] + if !found { + installedFiles = packageFiles[pkg.Name+":"+pkg.Arch] + } + packageInfos[i].Packages[j].InstalledFiles = installedFiles + } + } + + return &analyzer.AnalysisResult{ + PackageInfos: packageInfos, + SystemInstalledFiles: systemInstalledFiles, + }, nil + +} + +// parseDpkgInfoList parses /var/lib/dpkg/info/*.list +func (a dpkgAnalyzer) parseDpkgInfoList(scanner *bufio.Scanner) ([]string, error) { + var installedFiles []string + var previous string + for scanner.Scan() { + current := scanner.Text() + if current == "/." { + continue + } + + // Add the file if it is not directory. + // e.g. + // /usr/sbin + // /usr/sbin/tarcat + // + // In the above case, we should take only /usr/sbin/tarcat since /usr/sbin is a directory + if !strings.HasPrefix(current, previous+"/") { + installedFiles = append(installedFiles, previous) + } + previous = current + } + + // Add the last file + installedFiles = append(installedFiles, previous) + + if err := scanner.Err(); err != nil { + return nil, xerrors.Errorf("scan error: %w", err) + } + + return installedFiles, nil +} + +// parseDpkgAvailable parses /var/lib/dpkg/available +func (a dpkgAnalyzer) parseDpkgAvailable(fsys fs.FS) (map[string]digest.Digest, error) { + f, err := fsys.Open(availableFile) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + pkgs := make(map[string]digest.Digest) + scanner := NewScanner(f) + for scanner.Scan() { + header, err := scanner.Header() + if !errors.Is(err, io.EOF) && err != nil { + log.Logger.Warnw("Parse error", zap.String("file", availableFile), zap.Error(err)) + continue + } + name, version, checksum := header.Get("Package"), header.Get("Version"), header.Get("SHA256") + pkgID := a.pkgID(name, version) + if pkgID != "" && checksum != "" { + pkgs[pkgID] = digest.NewDigestFromString(digest.SHA256, checksum) + } + } + if err = scanner.Err(); err != nil { + return nil, xerrors.Errorf("scan error: %w", err) + } + + return pkgs, nil +} + +// parseDpkgStatus parses /var/lib/dpkg/status or /var/lib/dpkg/status/* +func (a dpkgAnalyzer) parseDpkgStatus(filePath string, r io.Reader, digests map[string]digest.Digest) ([]types.PackageInfo, error) { + var pkg *types.Package + pkgs := make(map[string]*types.Package) + pkgIDs := make(map[string]string) + + scanner := NewScanner(r) + for scanner.Scan() { + header, err := scanner.Header() + if !errors.Is(err, io.EOF) && err != nil { + log.Logger.Warnw("Parse error", zap.String("file", filePath), zap.Error(err)) + continue + } + + pkg = a.parseDpkgPkg(header) + if pkg != nil { + pkg.Digest = digests[pkg.ID] + pkgs[pkg.ID] = pkg + pkgIDs[pkg.Name] = pkg.ID + } + } + + if err := scanner.Err(); err != nil { + return nil, xerrors.Errorf("scan error: %w", err) + } + + a.consolidateDependencies(pkgs, pkgIDs) + + return []types.PackageInfo{ + { + FilePath: filePath, + Packages: lo.MapToSlice(pkgs, func(_ string, p *types.Package) types.Package { + return *p + }), + }, + }, nil +} + +func (a dpkgAnalyzer) parseDpkgPkg(header textproto.MIMEHeader) *types.Package { + if isInstalled := a.parseStatus(header.Get("Status")); !isInstalled { + return nil + } + + pkg := &types.Package{ + Name: header.Get("Package"), + Version: header.Get("Version"), // Will be parsed later + DependsOn: a.parseDepends(header.Get("Depends")), // Will be updated later + Maintainer: header.Get("Maintainer"), + Arch: header.Get("Architecture"), + } + if pkg.Name == "" || pkg.Version == "" { + return nil + } + + // Source line (Optional) + // Gives the name of the source package + // May also specifies a version + if src := header.Get("Source"); src != "" { + srcCapture := dpkgSrcCaptureRegexp.FindAllStringSubmatch(src, -1)[0] + md := make(map[string]string) + for i, n := range srcCapture { + md[dpkgSrcCaptureRegexpNames[i]] = strings.TrimSpace(n) + } + pkg.SrcName = md["name"] + pkg.SrcVersion = md["version"] + } + + // Source version and names are computed from binary package names and versions in dpkg. + // Source package name: + // https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/pkg-format.c#n338 + // Source package version: + // https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/pkg-format.c#n355 + if pkg.SrcName == "" { + pkg.SrcName = pkg.Name + } + if pkg.SrcVersion == "" { + pkg.SrcVersion = pkg.Version + } + + if v, err := debVersion.NewVersion(pkg.Version); err != nil { + log.Logger.Warnw("Invalid version", zap.String("OS", "debian"), + zap.String("package", pkg.Name), zap.String("version", pkg.Version)) + return nil + } else { + pkg.ID = a.pkgID(pkg.Name, pkg.Version) + pkg.Version = v.Version() + pkg.Epoch = v.Epoch() + pkg.Release = v.Revision() + } + + if v, err := debVersion.NewVersion(pkg.SrcVersion); err != nil { + log.Logger.Warnw("Invalid source version", zap.String("OS", "debian"), + zap.String("package", pkg.Name), zap.String("version", pkg.SrcVersion)) + return nil + } else { + pkg.SrcVersion = v.Version() + pkg.SrcEpoch = v.Epoch() + pkg.SrcRelease = v.Revision() + } + + return pkg +} + +func (a dpkgAnalyzer) Required(filePath string, _ os.FileInfo) bool { + dir, fileName := filepath.Split(filePath) + if a.isListFile(dir, fileName) || filePath == statusFile || filePath == availableFile { + return true + } + + // skip `*.md5sums` files from `status.d` directory + if dir == statusDir && filepath.Ext(fileName) != ".md5sums" { + return true + } + return false +} + +func (a dpkgAnalyzer) pkgID(name, version string) string { + return fmt.Sprintf("%s@%s", name, version) +} + +func (a dpkgAnalyzer) parseStatus(s string) bool { + for _, ss := range strings.Fields(s) { + if ss == "deinstall" || ss == "purge" { + return false + } + } + return true +} + +func (a dpkgAnalyzer) parseDepends(s string) []string { + // e.g. passwd, debconf (>= 0.5) | debconf-2.0 + var dependencies []string + depends := strings.Split(s, ",") + for _, dep := range depends { + // e.g. gpgv | gpgv2 | gpgv1 + for _, d := range strings.Split(dep, "|") { + d = a.trimVersionRequirement(d) + + // Store only uniq package names here + d = strings.TrimSpace(d) + if !slices.Contains(dependencies, d) { + dependencies = append(dependencies, d) + } + } + } + return dependencies +} + +func (a dpkgAnalyzer) trimVersionRequirement(s string) string { + // e.g. + // libapt-pkg6.0 (>= 2.2.4) => libapt-pkg6.0 + // adduser => adduser + s, _, _ = strings.Cut(s, "(") + return s +} + +func (a dpkgAnalyzer) consolidateDependencies(pkgs map[string]*types.Package, pkgIDs map[string]string) { + for _, pkg := range pkgs { + // e.g. libc6 => libc6@2.31-13+deb11u4 + pkg.DependsOn = lo.FilterMap(pkg.DependsOn, func(d string, _ int) (string, bool) { + if pkgID, ok := pkgIDs[d]; ok { + return pkgID, true + } + return "", false + }) + sort.Strings(pkg.DependsOn) + if len(pkg.DependsOn) == 0 { + pkg.DependsOn = nil + } + } +} + +func (a dpkgAnalyzer) isListFile(dir, fileName string) bool { + if dir != infoDir { + return false + } + + return strings.HasSuffix(fileName, ".list") +} + +func (a dpkgAnalyzer) Type() analyzer.Type { + return analyzer.TypeDpkg +} + +func (a dpkgAnalyzer) Version() int { + return analyzerVersion +} diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go new file mode 100644 index 000000000000..4dc627823200 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go @@ -0,0 +1,1518 @@ +package dpkg + +import ( + "context" + "os" + "path/filepath" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/mapfs" +) + +func Test_dpkgAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + // testFiles contains path in testdata and path in OS + // e.g. tar.list => var/lib/dpkg/info/tar.list + testFiles map[string]string + want *analyzer.AnalysisResult + wantErr bool + }{ + { + name: "valid", + testFiles: map[string]string{"./testdata/dpkg": "var/lib/dpkg/status"}, + want: &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: []types.Package{ + { + ID: "adduser@3.116ubuntu1", + Name: "adduser", + Version: "3.116ubuntu1", + SrcName: "adduser", + SrcVersion: "3.116ubuntu1", + DependsOn: []string{ + "debconf@1.5.66", + "passwd@1:4.5-1ubuntu1", + }, + Maintainer: "Ubuntu Core Developers ", + Arch: "all", + }, + { + ID: "apt@1.6.3ubuntu0.1", + Name: "apt", + Version: "1.6.3ubuntu0.1", + SrcName: "apt", + SrcVersion: "1.6.3ubuntu0.1", + DependsOn: []string{ + "adduser@3.116ubuntu1", + "gpgv@2.2.4-1ubuntu1.1", + "libapt-pkg5.0@1.6.3ubuntu0.1", + "libc6@2.27-3ubuntu1", + "libgcc1@1:8-20180414-1ubuntu2", + "libgnutls30@3.5.18-1ubuntu1", + "libseccomp2@2.3.1-2.1ubuntu4", + "libstdc++6@8-20180414-1ubuntu2", + "ubuntu-keyring@2018.02.28", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "base-files@10.1ubuntu2.2", + Name: "base-files", + Version: "10.1ubuntu2.2", + SrcName: "base-files", + SrcVersion: "10.1ubuntu2.2", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "base-passwd@3.5.44", + Name: "base-passwd", + Version: "3.5.44", + SrcName: "base-passwd", + SrcVersion: "3.5.44", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libdebconfclient0@0.213ubuntu1", + }, + Maintainer: "Colin Watson ", + Arch: "amd64", + }, + { + ID: "bash@4.4.18-2ubuntu1", + Name: "bash", + Version: "4.4.18", + Release: "2ubuntu1", + SrcName: "bash", + SrcVersion: "4.4.18", + SrcRelease: "2ubuntu1", + DependsOn: []string{ + "base-files@10.1ubuntu2.2", + "debianutils@4.8.4", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "bsdutils@1:2.31.1-0.4ubuntu3.1", + Name: "bsdutils", + Version: "2.31.1", + Epoch: 1, + Release: "0.4ubuntu3.1", + SrcName: "util-linux", + SrcVersion: "2.31.1", + SrcRelease: "0.4ubuntu3.1", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "bzip2@1.0.6-8.1", + Name: "bzip2", + Version: "1.0.6", + Release: "8.1", + SrcName: "bzip2", + SrcVersion: "1.0.6", + SrcRelease: "8.1", + DependsOn: []string{ + "libbz2-1.0@1.0.6-8.1", + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "coreutils@8.28-1ubuntu1", + Name: "coreutils", + Version: "8.28", + Release: "1ubuntu1", + SrcName: "coreutils", + SrcVersion: "8.28", + SrcRelease: "1ubuntu1", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "dash@0.5.8-2.10", + Name: "dash", + Version: "0.5.8", + Release: "2.10", + SrcName: "dash", + SrcVersion: "0.5.8", + SrcRelease: "2.10", + DependsOn: []string{ + "debianutils@4.8.4", + "dpkg@1.19.0.5ubuntu2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "debconf@1.5.66", + Name: "debconf", + Version: "1.5.66", + SrcName: "debconf", + SrcVersion: "1.5.66", + Maintainer: "Ubuntu Developers ", + Arch: "all", + }, + { + ID: "debianutils@4.8.4", + Name: "debianutils", + Version: "4.8.4", + SrcName: "debianutils", + SrcVersion: "4.8.4", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "diffutils@1:3.6-1", + Name: "diffutils", + Epoch: 1, + Version: "3.6", + Release: "1", + SrcName: "diffutils", + SrcEpoch: 1, + SrcVersion: "3.6", + SrcRelease: "1", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "dpkg@1.19.0.5ubuntu2", + Name: "dpkg", + Version: "1.19.0.5ubuntu2", + SrcName: "dpkg", + SrcVersion: "1.19.0.5ubuntu2", + DependsOn: []string{ + "tar@1.29b-2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "e2fsprogs@1.44.1-1", + Name: "e2fsprogs", + Version: "1.44.1", + Release: "1", + SrcName: "e2fsprogs", + SrcVersion: "1.44.1", + SrcRelease: "1", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "fdisk@2.31.1-0.4ubuntu3.1", + Name: "fdisk", + Version: "2.31.1", + Release: "0.4ubuntu3.1", + SrcName: "util-linux", + SrcVersion: "2.31.1", + SrcRelease: "0.4ubuntu3.1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libfdisk1@2.31.1-0.4ubuntu3.1", + "libmount1@2.31.1-0.4ubuntu3.1", + "libncursesw5@6.1-1ubuntu1.18.04", + "libsmartcols1@2.31.1-0.4ubuntu3.1", + "libtinfo5@6.1-1ubuntu1.18.04", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "findutils@4.6.0+git+20170828-2", + Name: "findutils", + Version: "4.6.0+git+20170828", + Release: "2", + SrcName: "findutils", + SrcVersion: "4.6.0+git+20170828", + SrcRelease: "2", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "gcc-8-base@8-20180414-1ubuntu2", + Name: "gcc-8-base", + Version: "8-20180414", + Release: "1ubuntu2", + SrcName: "gcc-8", + SrcVersion: "8-20180414", + SrcRelease: "1ubuntu2", + Maintainer: "Ubuntu Core developers ", + Arch: "amd64", + }, + { + ID: "gpgv@2.2.4-1ubuntu1.1", + Name: "gpgv", + Version: "2.2.4", + Release: "1ubuntu1.1", + SrcName: "gnupg2", + SrcVersion: "2.2.4", + SrcRelease: "1ubuntu1.1", + DependsOn: []string{ + "libbz2-1.0@1.0.6-8.1", + "libc6@2.27-3ubuntu1", + "libgcrypt20@1.8.1-4ubuntu1.1", + "libgpg-error0@1.27-6", + "zlib1g@1:1.2.11.dfsg-0ubuntu2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "grep@3.1-2", + Name: "grep", + Version: "3.1", + Release: "2", + SrcName: "grep", + SrcVersion: "3.1", + SrcRelease: "2", + DependsOn: []string{ + "dpkg@1.19.0.5ubuntu2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "gzip@1.6-5ubuntu1", + Name: "gzip", + Version: "1.6", + Release: "5ubuntu1", + SrcName: "gzip", + SrcVersion: "1.6", + SrcRelease: "5ubuntu1", + DependsOn: []string{ + "dpkg@1.19.0.5ubuntu2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "hostname@3.20", + Name: "hostname", + Version: "3.20", + SrcName: "hostname", + SrcVersion: "3.20", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "init-system-helpers@1.51", + Name: "init-system-helpers", + Version: "1.51", + SrcName: "init-system-helpers", + SrcVersion: "1.51", + DependsOn: []string{ + "perl-base@5.26.1-6ubuntu0.2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "all", + }, + { + ID: "libacl1@2.2.52-3build1", + Name: "libacl1", + Version: "2.2.52", + Release: "3build1", + SrcName: "acl", + SrcVersion: "2.2.52", + SrcRelease: "3build1", + DependsOn: []string{ + "libattr1@1:2.4.47-2build1", + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libapt-pkg5.0@1.6.3ubuntu0.1", + Name: "libapt-pkg5.0", + Version: "1.6.3ubuntu0.1", + SrcName: "apt", + SrcVersion: "1.6.3ubuntu0.1", + DependsOn: []string{ + "libbz2-1.0@1.0.6-8.1", + "libc6@2.27-3ubuntu1", + "libgcc1@1:8-20180414-1ubuntu2", + "liblz4-1@0.0~r131-2ubuntu3", + "liblzma5@5.1.1alpha+20120614-2+b3", + "libstdc++6@8-20180414-1ubuntu2", + "libudev1@237-3ubuntu10.3", + "libzstd1@1.3.3+dfsg-2ubuntu1", + "zlib1g@1:1.2.11.dfsg-0ubuntu2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libattr1@1:2.4.47-2build1", + Name: "libattr1", + Epoch: 1, + Version: "2.4.47", + Release: "2build1", + SrcName: "attr", + SrcEpoch: 1, + SrcVersion: "2.4.47", + SrcRelease: "2build1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libaudit-common@1:2.8.2-1ubuntu1", + Name: "libaudit-common", + Version: "2.8.2", + Epoch: 1, + Release: "1ubuntu1", + SrcName: "audit", + SrcVersion: "2.8.2", + SrcEpoch: 1, + SrcRelease: "1ubuntu1", + Maintainer: "Ubuntu Developers ", + Arch: "all", + }, + { + ID: "libaudit1@1:2.8.2-1ubuntu1", + Name: "libaudit1", + Epoch: 1, + Version: "2.8.2", + Release: "1ubuntu1", + SrcName: "audit", + SrcEpoch: 1, + SrcVersion: "2.8.2", + SrcRelease: "1ubuntu1", + DependsOn: []string{ + "libaudit-common@1:2.8.2-1ubuntu1", + "libc6@2.27-3ubuntu1", + "libcap-ng0@0.7.7-3.1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libblkid1@2.31.1-0.4ubuntu3.1", + Name: "libblkid1", + Version: "2.31.1", + Release: "0.4ubuntu3.1", + SrcName: "util-linux", + SrcVersion: "2.31.1", + SrcRelease: "0.4ubuntu3.1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libuuid1@2.31.1-0.4ubuntu3.1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libbz2-1.0@1.0.6-8.1", + Name: "libbz2-1.0", + Version: "1.0.6", + Release: "8.1", + SrcName: "bzip2", + SrcVersion: "1.0.6", + SrcRelease: "8.1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libc-bin@2.27-3ubuntu1", + Name: "libc-bin", + Version: "2.27", + Release: "3ubuntu1", + SrcName: "glibc", + SrcVersion: "2.27", + SrcRelease: "3ubuntu1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libc6@2.27-3ubuntu1", + Name: "libc6", + Version: "2.27", + Release: "3ubuntu1", + SrcName: "glibc", + SrcVersion: "2.27", + SrcRelease: "3ubuntu1", + DependsOn: []string{ + "libgcc1@1:8-20180414-1ubuntu2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libcap-ng0@0.7.7-3.1", + Name: "libcap-ng0", + Version: "0.7.7", + Release: "3.1", + SrcName: "libcap-ng", + SrcVersion: "0.7.7", + SrcRelease: "3.1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libcom-err2@1.44.1-1", + Name: "libcom-err2", + Version: "1.44.1", + Release: "1", + SrcName: "e2fsprogs", + SrcVersion: "1.44.1", + SrcRelease: "1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libdb5.3@5.3.28-13.1ubuntu1", + Name: "libdb5.3", + Version: "5.3.28", + Release: "13.1ubuntu1", + SrcName: "db5.3", + SrcVersion: "5.3.28", + SrcRelease: "13.1ubuntu1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libdebconfclient0@0.213ubuntu1", + Name: "libdebconfclient0", + Version: "0.213ubuntu1", + SrcName: "cdebconf", + SrcVersion: "0.213ubuntu1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libext2fs2@1.44.1-1", + Name: "libext2fs2", + Version: "1.44.1", + Release: "1", + SrcName: "e2fsprogs", + SrcVersion: "1.44.1", + SrcRelease: "1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libfdisk1@2.31.1-0.4ubuntu3.1", + Name: "libfdisk1", + Version: "2.31.1", + Release: "0.4ubuntu3.1", + SrcName: "util-linux", + SrcVersion: "2.31.1", + SrcRelease: "0.4ubuntu3.1", + DependsOn: []string{ + "libblkid1@2.31.1-0.4ubuntu3.1", + "libc6@2.27-3ubuntu1", + "libuuid1@2.31.1-0.4ubuntu3.1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libffi6@3.2.1-8", + Name: "libffi6", + Version: "3.2.1", + Release: "8", + SrcName: "libffi", + SrcVersion: "3.2.1", + SrcRelease: "8", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libgcc1@1:8-20180414-1ubuntu2", + Name: "libgcc1", + Epoch: 1, + Version: "8-20180414", + Release: "1ubuntu2", + SrcName: "gcc-8", + SrcRelease: "1ubuntu2", + SrcVersion: "8-20180414", + DependsOn: []string{ + "gcc-8-base@8-20180414-1ubuntu2", + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Core developers ", + Arch: "amd64", + }, + { + ID: "libgcrypt20@1.8.1-4ubuntu1.1", + Name: "libgcrypt20", + Version: "1.8.1", + Release: "4ubuntu1.1", + SrcName: "libgcrypt20", + SrcVersion: "1.8.1", + SrcRelease: "4ubuntu1.1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libgpg-error0@1.27-6", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libgmp10@2:6.1.2+dfsg-2", + Name: "libgmp10", + Epoch: 2, + Version: "6.1.2+dfsg", + Release: "2", + SrcName: "gmp", + SrcEpoch: 2, + SrcVersion: "6.1.2+dfsg", + SrcRelease: "2", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libgnutls30@3.5.18-1ubuntu1", + Name: "libgnutls30", + Version: "3.5.18", + Release: "1ubuntu1", + SrcName: "gnutls28", + SrcVersion: "3.5.18", + SrcRelease: "1ubuntu1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libgmp10@2:6.1.2+dfsg-2", + "libhogweed4@3.4-1", + "libidn2-0@2.0.4-1.1build2", + "libnettle6@3.4-1", + "libp11-kit0@0.23.9-2", + "libtasn1-6@4.13-2", + "libunistring2@0.9.9-0ubuntu1", + "zlib1g@1:1.2.11.dfsg-0ubuntu2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libgpg-error0@1.27-6", + Name: "libgpg-error0", + + Version: "1.27", + Release: "6", + SrcName: "libgpg-error", + SrcVersion: "1.27", + SrcRelease: "6", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libhogweed4@3.4-1", + Name: "libhogweed4", + Version: "3.4", + Release: "1", + SrcName: "nettle", + SrcVersion: "3.4", + SrcRelease: "1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libgmp10@2:6.1.2+dfsg-2", + "libnettle6@3.4-1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libidn2-0@2.0.4-1.1build2", + Name: "libidn2-0", + Version: "2.0.4", + Release: "1.1build2", + SrcName: "libidn2", + SrcVersion: "2.0.4", + SrcRelease: "1.1build2", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libunistring2@0.9.9-0ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "liblz4-1@0.0~r131-2ubuntu3", + Name: "liblz4-1", + Version: "0.0~r131", + Release: "2ubuntu3", + SrcName: "lz4", + SrcVersion: "0.0~r131", + SrcRelease: "2ubuntu3", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "liblzma5@5.1.1alpha+20120614-2+b3", + Name: "liblzma5", + Version: "5.1.1alpha+20120614", + Release: "2+b3", + SrcName: "xz-utils", + SrcVersion: "5.1.1alpha+20120614", + SrcRelease: "2", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Jonathan Nieder ", + Arch: "amd64", + }, + { + ID: "libmount1@2.31.1-0.4ubuntu3.1", + Name: "libmount1", + Version: "2.31.1", + Release: "0.4ubuntu3.1", + SrcName: "util-linux", + SrcVersion: "2.31.1", + SrcRelease: "0.4ubuntu3.1", + DependsOn: []string{ + "libblkid1@2.31.1-0.4ubuntu3.1", + "libc6@2.27-3ubuntu1", + "libselinux1@2.7-2build2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libncurses5@6.1-1ubuntu1.18.04", + Name: "libncurses5", + Version: "6.1", + Release: "1ubuntu1.18.04", + SrcName: "ncurses", + SrcVersion: "6.1", + SrcRelease: "1ubuntu1.18.04", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libtinfo5@6.1-1ubuntu1.18.04", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libncursesw5@6.1-1ubuntu1.18.04", + Name: "libncursesw5", + Version: "6.1", + Release: "1ubuntu1.18.04", + SrcName: "ncurses", + SrcVersion: "6.1", + SrcRelease: "1ubuntu1.18.04", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libtinfo5@6.1-1ubuntu1.18.04", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libnettle6@3.4-1", + Name: "libnettle6", + Version: "3.4", + Release: "1", + SrcName: "nettle", + SrcVersion: "3.4", + SrcRelease: "1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libp11-kit0@0.23.9-2", + Name: "libp11-kit0", + Version: "0.23.9", + Release: "2", + SrcName: "p11-kit", + SrcVersion: "0.23.9", + SrcRelease: "2", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libffi6@3.2.1-8", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libpam-modules@1.1.8-3.6ubuntu2", + Name: "libpam-modules", + Version: "1.1.8", + Release: "3.6ubuntu2", + SrcName: "pam", + SrcVersion: "1.1.8", + SrcRelease: "3.6ubuntu2", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libpam-modules-bin@1.1.8-3.6ubuntu2", + Name: "libpam-modules-bin", + Version: "1.1.8", + Release: "3.6ubuntu2", + SrcName: "pam", + SrcVersion: "1.1.8", + SrcRelease: "3.6ubuntu2", + DependsOn: []string{ + "libaudit1@1:2.8.2-1ubuntu1", + "libc6@2.27-3ubuntu1", + "libpam0g@1.1.8-3.6ubuntu2", + "libselinux1@2.7-2build2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libpam-runtime@1.1.8-3.6ubuntu2", + Name: "libpam-runtime", + Version: "1.1.8", + Release: "3.6ubuntu2", + SrcName: "pam", + SrcVersion: "1.1.8", + SrcRelease: "3.6ubuntu2", + DependsOn: []string{ + "debconf@1.5.66", + "libpam-modules@1.1.8-3.6ubuntu2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "all", + }, + { + ID: "libpam0g@1.1.8-3.6ubuntu2", + Name: "libpam0g", + Version: "1.1.8", + Release: "3.6ubuntu2", + SrcName: "pam", + SrcVersion: "1.1.8", + SrcRelease: "3.6ubuntu2", + DependsOn: []string{ + "debconf@1.5.66", + "libaudit1@1:2.8.2-1ubuntu1", + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libpcre3@2:8.39-9", + Name: "libpcre3", + Version: "8.39", + Epoch: 2, + Release: "9", + SrcName: "pcre3", + SrcVersion: "8.39", + SrcEpoch: 2, + SrcRelease: "9", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libprocps6@2:3.3.12-3ubuntu1.1", + Name: "libprocps6", + Version: "3.3.12", + Epoch: 2, + Release: "3ubuntu1.1", + SrcName: "procps", + SrcVersion: "3.3.12", + SrcRelease: "3ubuntu1.1", + SrcEpoch: 2, + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libsystemd0@237-3ubuntu10.3", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libseccomp2@2.3.1-2.1ubuntu4", + Name: "libseccomp2", + Version: "2.3.1", + Release: "2.1ubuntu4", + SrcName: "libseccomp", + SrcVersion: "2.3.1", + SrcRelease: "2.1ubuntu4", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libselinux1@2.7-2build2", + Name: "libselinux1", + Version: "2.7", + Release: "2build2", + SrcName: "libselinux", + SrcVersion: "2.7", + SrcRelease: "2build2", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libpcre3@2:8.39-9", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libsemanage-common@2.7-2build2", + Name: "libsemanage-common", + Version: "2.7", + Release: "2build2", + SrcName: "libsemanage", + SrcVersion: "2.7", + SrcRelease: "2build2", + Maintainer: "Ubuntu Developers ", + Arch: "all", + }, + { + ID: "libsemanage1@2.7-2build2", + Name: "libsemanage1", + Version: "2.7", + Release: "2build2", + SrcName: "libsemanage", + SrcVersion: "2.7", + SrcRelease: "2build2", + DependsOn: []string{ + "libaudit1@1:2.8.2-1ubuntu1", + "libbz2-1.0@1.0.6-8.1", + "libc6@2.27-3ubuntu1", + "libselinux1@2.7-2build2", + "libsemanage-common@2.7-2build2", + "libsepol1@2.7-1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libsepol1@2.7-1", + Name: "libsepol1", + Version: "2.7", + Release: "1", + SrcName: "libsepol", + SrcVersion: "2.7", + SrcRelease: "1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libsmartcols1@2.31.1-0.4ubuntu3.1", + Name: "libsmartcols1", + Version: "2.31.1", + Release: "0.4ubuntu3.1", + SrcName: "util-linux", + SrcVersion: "2.31.1", + SrcRelease: "0.4ubuntu3.1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libss2@1.44.1-1", + Name: "libss2", + Version: "1.44.1", + Release: "1", + SrcName: "e2fsprogs", + SrcVersion: "1.44.1", + SrcRelease: "1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + "libcom-err2@1.44.1-1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libstdc++6@8-20180414-1ubuntu2", + Name: "libstdc++6", + Version: "8-20180414", + Release: "1ubuntu2", + SrcName: "gcc-8", + SrcVersion: "8-20180414", + SrcRelease: "1ubuntu2", + DependsOn: []string{ + "gcc-8-base@8-20180414-1ubuntu2", + "libc6@2.27-3ubuntu1", + "libgcc1@1:8-20180414-1ubuntu2", + }, + Maintainer: "Ubuntu Core developers ", + Arch: "amd64", + }, + { + ID: "libsystemd0@237-3ubuntu10.3", + Name: "libsystemd0", + Version: "237", + Release: "3ubuntu10.3", + SrcName: "systemd", + SrcVersion: "237", + SrcRelease: "3ubuntu10.3", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libtasn1-6@4.13-2", + Name: "libtasn1-6", + Version: "4.13", + Release: "2", + SrcName: "libtasn1-6", + SrcVersion: "4.13", + SrcRelease: "2", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libtinfo5@6.1-1ubuntu1.18.04", + Name: "libtinfo5", + Version: "6.1", + Release: "1ubuntu1.18.04", + SrcName: "ncurses", + SrcVersion: "6.1", + SrcRelease: "1ubuntu1.18.04", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libudev1@237-3ubuntu10.3", + Name: "libudev1", + Version: "237", + Release: "3ubuntu10.3", + SrcName: "systemd", + SrcVersion: "237", + SrcRelease: "3ubuntu10.3", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libunistring2@0.9.9-0ubuntu1", + Name: "libunistring2", + Version: "0.9.9", + Release: "0ubuntu1", + SrcName: "libunistring", + SrcVersion: "0.9.9", + SrcRelease: "0ubuntu1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libustr-1.0-1@1.0.4-3+b2", + Name: "libustr-1.0-1", + Version: "1.0.4", + Release: "3+b2", + SrcName: "ustr", + SrcVersion: "1.0.4", + SrcRelease: "3", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Vaclav Ovsik ", + Arch: "amd64", + }, + { + ID: "libuuid1@2.31.1-0.4ubuntu3.1", + Name: "libuuid1", + Version: "2.31.1", + Release: "0.4ubuntu3.1", + SrcName: "util-linux", + SrcVersion: "2.31.1", + SrcRelease: "0.4ubuntu3.1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libzstd1@1.3.3+dfsg-2ubuntu1", + Name: "libzstd1", + Version: "1.3.3+dfsg", + Release: "2ubuntu1", + SrcName: "libzstd", + SrcVersion: "1.3.3+dfsg", + SrcRelease: "2ubuntu1", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "login@1:4.5-1ubuntu1", + Name: "login", + Version: "4.5", + Epoch: 1, + Release: "1ubuntu1", + SrcName: "shadow", + SrcEpoch: 1, + SrcVersion: "4.5", + SrcRelease: "1ubuntu1", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "lsb-base@9.20170808ubuntu1", + Name: "lsb-base", + Version: "9.20170808ubuntu1", + SrcName: "lsb", + SrcVersion: "9.20170808ubuntu1", + Maintainer: "Ubuntu Developers ", + Arch: "all", + }, + { + ID: "mawk@1.3.3-17ubuntu3", + Name: "mawk", + Version: "1.3.3", + Release: "17ubuntu3", + SrcName: "mawk", + SrcVersion: "1.3.3", + SrcRelease: "17ubuntu3", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "mount@2.31.1-0.4ubuntu3.1", + Name: "mount", + Version: "2.31.1", + Release: "0.4ubuntu3.1", + SrcName: "util-linux", + SrcVersion: "2.31.1", + SrcRelease: "0.4ubuntu3.1", + DependsOn: []string{ + "util-linux@2.31.1-0.4ubuntu3.1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "ncurses-base@6.1-1ubuntu1.18.04", + Name: "ncurses-base", + Version: "6.1", + Release: "1ubuntu1.18.04", + SrcName: "ncurses", + SrcVersion: "6.1", + SrcRelease: "1ubuntu1.18.04", + Maintainer: "Ubuntu Developers ", + Arch: "all", + }, + { + ID: "ncurses-bin@6.1-1ubuntu1.18.04", + Name: "ncurses-bin", + Version: "6.1", + Release: "1ubuntu1.18.04", + SrcName: "ncurses", + SrcVersion: "6.1", + SrcRelease: "1ubuntu1.18.04", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "passwd@1:4.5-1ubuntu1", + Name: "passwd", + Epoch: 1, + Version: "4.5", + Release: "1ubuntu1", + SrcName: "shadow", + SrcEpoch: 1, + SrcVersion: "4.5", + SrcRelease: "1ubuntu1", + DependsOn: []string{ + "libaudit1@1:2.8.2-1ubuntu1", + "libc6@2.27-3ubuntu1", + "libpam-modules@1.1.8-3.6ubuntu2", + "libpam0g@1.1.8-3.6ubuntu2", + "libselinux1@2.7-2build2", + "libsemanage1@2.7-2build2", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "perl-base@5.26.1-6ubuntu0.2", + Name: "perl-base", + Version: "5.26.1", + Release: "6ubuntu0.2", + SrcName: "perl", + SrcVersion: "5.26.1", + SrcRelease: "6ubuntu0.2", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "procps@2:3.3.12-3ubuntu1.1", + Name: "procps", + Epoch: 2, + Version: "3.3.12", + Release: "3ubuntu1.1", + SrcName: "procps", + SrcEpoch: 2, + SrcVersion: "3.3.12", + SrcRelease: "3ubuntu1.1", + DependsOn: []string{ + "init-system-helpers@1.51", + "libc6@2.27-3ubuntu1", + "libncurses5@6.1-1ubuntu1.18.04", + "libncursesw5@6.1-1ubuntu1.18.04", + "libprocps6@2:3.3.12-3ubuntu1.1", + "libtinfo5@6.1-1ubuntu1.18.04", + "lsb-base@9.20170808ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "sed@4.4-2", + Name: "sed", + Version: "4.4", + Release: "2", + SrcName: "sed", + SrcVersion: "4.4", + SrcRelease: "2", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "sensible-utils@0.0.12", + Name: "sensible-utils", + Version: "0.0.12", + SrcName: "sensible-utils", + SrcVersion: "0.0.12", + Maintainer: "Ubuntu Developers ", + Arch: "all", + }, + { + ID: "sysvinit-utils@2.88dsf-59.10ubuntu1", + Name: "sysvinit-utils", + Version: "2.88dsf", + Release: "59.10ubuntu1", + SrcName: "sysvinit", + SrcVersion: "2.88dsf", + SrcRelease: "59.10ubuntu1", + DependsOn: []string{ + "init-system-helpers@1.51", + "libc6@2.27-3ubuntu1", + "util-linux@2.31.1-0.4ubuntu3.1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "tar@1.29b-2", + Name: "tar", + Version: "1.29b", + Release: "2", + SrcName: "tar", + SrcVersion: "1.29b", + SrcRelease: "2", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "ubuntu-keyring@2018.02.28", + Name: "ubuntu-keyring", + Version: "2018.02.28", + SrcName: "ubuntu-keyring", + SrcVersion: "2018.02.28", + Maintainer: "Dimitri John Ledkov ", + Arch: "all", + }, + { + ID: "util-linux@2.31.1-0.4ubuntu3.1", + Name: "util-linux", + Version: "2.31.1", + Release: "0.4ubuntu3.1", + SrcName: "util-linux", + SrcVersion: "2.31.1", + SrcRelease: "0.4ubuntu3.1", + DependsOn: []string{ + "fdisk@2.31.1-0.4ubuntu3.1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "zlib1g@1:1.2.11.dfsg-0ubuntu2", + Name: "zlib1g", + Epoch: 1, + Version: "1.2.11.dfsg", + Release: "0ubuntu2", + SrcName: "zlib", + SrcEpoch: 1, + SrcVersion: "1.2.11.dfsg", + SrcRelease: "0ubuntu2", + DependsOn: []string{ + "libc6@2.27-3ubuntu1", + }, + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + }, + }, + }, + }, + }, + { + name: "corrupsed", + testFiles: map[string]string{"./testdata/corrupsed": "var/lib/dpkg/status"}, + want: &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: []types.Package{ + { + ID: "libgcc1@1:5.1.1-12ubuntu1", + Name: "libgcc1", + Version: "5.1.1", + Epoch: 1, + Release: "12ubuntu1", + SrcName: "gcc-5", + SrcVersion: "5.1.1", + SrcRelease: "12ubuntu1", + Maintainer: "Ubuntu Core developers ", + Arch: "amd64", + }, + { + ID: "libpam-modules-bin@1.1.8-3.1ubuntu3", + Name: "libpam-modules-bin", + Version: "1.1.8", + Release: "3.1ubuntu3", + SrcName: "pam", + SrcVersion: "1.1.8", + SrcRelease: "3.1ubuntu3", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "libpam-runtime@1.1.8-3.1ubuntu3", + Name: "libpam-runtime", + Version: "1.1.8", + Release: "3.1ubuntu3", + SrcName: "pam", + SrcVersion: "1.1.8", + SrcRelease: "3.1ubuntu3", + Maintainer: "Ubuntu Developers ", + Arch: "all", + }, + { + ID: "makedev@2.3.1-93ubuntu1", + Name: "makedev", + Version: "2.3.1", + Release: "93ubuntu1", + SrcName: "makedev", + SrcVersion: "2.3.1", + SrcRelease: "93ubuntu1", + Maintainer: "Ubuntu Developers ", + Arch: "all", + }, + }, + }, + }, + }, + }, + { + name: "only apt", + testFiles: map[string]string{"./testdata/dpkg_apt": "var/lib/dpkg/status"}, + want: &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: []types.Package{ + { + ID: "apt@1.6.3ubuntu0.1", Name: "apt", Version: "1.6.3ubuntu0.1", + SrcName: "apt", SrcVersion: "1.6.3ubuntu0.1", Maintainer: "Ubuntu Developers ", Arch: "amd64"}, + }, + }, + }, + }, + }, + { + name: "happy path with digests", + testFiles: map[string]string{ + "./testdata/digest-status": "var/lib/dpkg/status", + "./testdata/digest-available": "var/lib/dpkg/available", + }, + want: &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: []types.Package{ + { + ID: "sed@4.4-2", + Name: "sed", + Version: "4.4", + Release: "2", + SrcName: "sed", + SrcVersion: "4.4", + SrcRelease: "2", + Maintainer: "Ubuntu Developers ", + Arch: "amd64", + }, + { + ID: "tar@1.34+dfsg-1", + Name: "tar", + Version: "1.34+dfsg", + Release: "1", + SrcName: "tar", + SrcVersion: "1.34+dfsg", + SrcRelease: "1", + Maintainer: "Janos Lenart ", + Arch: "amd64", + Digest: "sha256:bd8e963c6edcf1c806df97cd73560794c347aa94b9aaaf3b88eea585bb2d2f3c", + }, + }, + }, + }, + }, + }, + { + name: "info list", + testFiles: map[string]string{"./testdata/tar.list": "var/lib/dpkg/info/tar.list"}, + want: &analyzer.AnalysisResult{ + SystemInstalledFiles: []string{ + "/bin/tar", + "/etc", + "/usr/lib/mime/packages/tar", + "/usr/sbin/rmt-tar", + "/usr/sbin/tarcat", + "/usr/share/doc/tar/AUTHORS", + "/usr/share/doc/tar/NEWS.gz", + "/usr/share/doc/tar/README.Debian", + "/usr/share/doc/tar/THANKS.gz", + "/usr/share/doc/tar/changelog.Debian.gz", + "/usr/share/doc/tar/copyright", + "/usr/share/man/man1/tar.1.gz", + "/usr/share/man/man1/tarcat.1.gz", + "/usr/share/man/man8/rmt-tar.8.gz", + "/etc/rmt", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newDpkgAnalyzer(analyzer.AnalyzerOptions{}) + assert.NoError(t, err) + ctx := context.Background() + + mfs := mapfs.New() + for testPath, osPath := range tt.testFiles { + err = mfs.MkdirAll(filepath.Dir(osPath), os.ModePerm) + assert.NoError(t, err) + err = mfs.WriteFile(osPath, testPath) + assert.NoError(t, err) + } + + got, err := a.PostAnalyze(ctx, analyzer.PostAnalysisInput{ + FS: mfs, + }) + assert.NoError(t, err) + + // Sort the result for consistency + for i := range got.PackageInfos { + sort.Sort(got.PackageInfos[i].Packages) + } + + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_dpkgAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "status", + filePath: "var/lib/dpkg/status", + want: true, + }, + { + name: "status dir", + filePath: "var/lib/dpkg/status.d/gcc", + want: true, + }, + { + name: "*.md5sums file in status dir", + filePath: "var/lib/dpkg/status.d/base-files.md5sums", + want: false, + }, + { + name: "list file", + filePath: "var/lib/dpkg/info/bash.list", + want: true, + }, + { + name: "available file", + filePath: "var/lib/dpkg/available", + want: true, + }, + { + name: "sad path", + filePath: "var/lib/dpkg/status/bash.list", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newDpkgAnalyzer(analyzer.AnalyzerOptions{}) + assert.NoError(t, err) + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/pkg/dpkg/scanner.go b/pkg/fanal/analyzer/pkg/dpkg/scanner.go new file mode 100644 index 000000000000..2e38f06b0cf7 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/scanner.go @@ -0,0 +1,56 @@ +package dpkg + +import ( + "bufio" + "bytes" + "io" + "net/textproto" + "strings" +) + +type dpkgScanner struct { + *bufio.Scanner +} + +// NewScanner returns a new scanner that splits on empty lines. +func NewScanner(r io.Reader) *dpkgScanner { + s := bufio.NewScanner(r) + // Package data may exceed default buffer size + // Increase the buffer default size by 2 times + buf := make([]byte, 0, 128*1024) + s.Buffer(buf, 128*1024) + + s.Split(emptyLineSplit) + return &dpkgScanner{Scanner: s} +} + +// Scan advances the scanner to the next token. +func (s *dpkgScanner) Scan() bool { + return s.Scanner.Scan() +} + +// Header returns the MIME header of the current scan. +func (s *dpkgScanner) Header() (textproto.MIMEHeader, error) { + b := s.Bytes() + reader := textproto.NewReader(bufio.NewReader(bytes.NewReader(b))) + return reader.ReadMIMEHeader() +} + +// emptyLineSplit is a bufio.SplitFunc that splits on empty lines. +func emptyLineSplit(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + + if i := strings.Index(string(data), "\n\n"); i >= 0 { + // We have a full empty line terminated block. + return i + 2, data[0:i], nil + } + + if atEOF { + // Return the rest of the data if we're at EOF. + return len(data), data, nil + } + + return +} diff --git a/pkg/fanal/analyzer/pkg/dpkg/testdata/all-patterns-copyright b/pkg/fanal/analyzer/pkg/dpkg/testdata/all-patterns-copyright new file mode 100644 index 000000000000..2f9ab10e8f81 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/testdata/all-patterns-copyright @@ -0,0 +1,22 @@ +Apt is copyright 1997, 1998, 1999 Jason Gunthorpe and others. +Apt is currently developed by APT Development Team . + +License: GPLv2+ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +See /usr/share/common-licenses/GPL-2, or + for the terms of the latest version +of the GNU General Public License. diff --git a/pkg/fanal/analyzer/pkg/dpkg/testdata/common-license-copyright b/pkg/fanal/analyzer/pkg/dpkg/testdata/common-license-copyright new file mode 100644 index 000000000000..1e82b8d57e6f --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/testdata/common-license-copyright @@ -0,0 +1,47 @@ +This package was first put together by Ian Murdock + and was maintained by Steve Phillips + from sources written for the Debian Project by Ian +Murdock, Ted Hajek , and Sven Rudolph +. + +Since Nov 27 1996, it was maintained by Guy Maor . He +rewrote most of it. + +Since May 20 2000, it is maintained by Roland Bauerschmidt +. + +Since March 24 2004, it is maintained by Roland Bauerschmidt +, and co-maintained by Marc Haber + + +Since 23 Oct 2005, it has been maintained by Joerg Hoh + +Since June 2006, it has been maintained by Stephen Gran + +deluser is Copyright (C) 2000 Roland Bauerschmidt +and based on the source code of adduser. + +adduser is Copyright (C) 1997, 1998, 1999 Guy Maor . +adduser is Copyright (C) 1995 Ted Hajek +with portions Copyright (C) 1994 Debian Association, Inc. + +The examples directory has been contributed by John Zaitseff, and is +GPL V2 as well. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +On Debian GNU/Linux systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL-2'. diff --git a/pkg/fanal/analyzer/pkg/dpkg/testdata/corrupsed b/pkg/fanal/analyzer/pkg/dpkg/testdata/corrupsed new file mode 100644 index 000000000000..c86c583d58b4 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/testdata/corrupsed @@ -0,0 +1,87 @@ +Package: libpam-runtime +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 300 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Source: pam +Version: 1.1.8-3.1ubuntu3 +Replaces: libpam0g-dev, libpam0g-util +Depends: debconf (>= 0.5) | debconf-2.0, debconf (>= 1.5.19) | cdebconf, libpam-modules (>= 1.0.1-6) +Conflicts: libpam0g-util +Conffiles: + /etc/pam.conf 87fc76f18e98ee7d3848f6b81b3391e5 + /etc/pam.d/other 31aa7f2181889ffb00b87df4126d1701 +Description: Runtime support for the PAM library + Contains configuration files and directories required for + authentication to work on Debian systems. This package is required + on almost all installations. +Homepage: http://pam.sourceforge.net/ +Original-Maintainer: Steve Langasek + +Package: libpam-modules-bin +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 299 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: pam +Version: 1.1.8-3.1ubuntu3 +Replaces: libpam-modules (<< 1.1.3-8) +Depends: libaudit1 (>= 1:2.2.1), libc6 (>= 2.14), libpam0g (>= 0.99.7.1), libselinux1 (>= 1.32) +Description: Pluggable Authentication Modules for PAM - helper binaries + This package contains helper binaries used by the standard set of PAM + modules in the libpam-modules package. +Homepage: http://pam.sourceforge.net/ +Original-Maintainer: Steve Langasek + +Package: makedev +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 125 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Version: 2.3.1-93ubuntu1 +Depends: base-passwd (>= 3.0.4) +Conflicts: udev (<= 0.024-7) +Description: creates device files in /dev + The MAKEDEV executable is used to create device files, often in /dev. + . + Device files are special files through which applications can interact + with hardware. + . + This package is not necessary for most modern Linux systems, where the udev + subsystem provides a more dynamic mechanism for device file management. +Original-Maintainer: Debian QA Group + +Package: brokenPackageWithNoVersionThatShouldGetThrownOut + +Package: libgcc1 +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 131 +Maintainer: Ubuntu Core developers +Architecture: amd64 +Multi-Arch: same +Source: gcc-5 (5.1.1-12ubuntu1) +Version: 1:5.1.1-12ubuntu1 +Depends: gcc-5-base (= 5.1.1-12ubuntu1), libc6 (>= 2.14) +Pre-Depends: multiarch-support +Breaks: gcc-4.3 (<< 4.3.6-1), gcc-4.4 (<< 4.4.6-4), gcc-4.5 (<< 4.5.3-2) +Description: GCC support library + Shared version of the support library, a library of internal subroutines + that GCC uses to overcome shortcomings of particular machines, or + special needs for some languages. +Homepage: http://gcc.gnu.org/ +Original-Maintainer: Debian GCC Maintainers + +Package: invalidpkg +Source: invalidpkg-5 (5.#) +Version: 1:5.# diff --git a/pkg/fanal/analyzer/pkg/dpkg/testdata/digest-available b/pkg/fanal/analyzer/pkg/dpkg/testdata/digest-available new file mode 100644 index 000000000000..d2f062a871b4 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/testdata/digest-available @@ -0,0 +1,46 @@ +Package: tar +Version: 1.34+dfsg-1 +Essential: yes +Installed-Size: 3152 +Maintainer: Janos Lenart +Architecture: amd64 +Replaces: cpio (<< 2.4.2-39) +Pre-Depends: libacl1 (>= 2.2.23), libc6 (>= 2.28), libselinux1 (>= 3.1~) +Suggests: bzip2, ncompress, xz-utils, tar-scripts, tar-doc +Conflicts: cpio (<= 2.4.2-38) +Breaks: dpkg-dev (<< 1.14.26) +Description: GNU version of the tar archiving utility +Multi-Arch: foreign +Homepage: https://www.gnu.org/software/tar/ +Description-md5: 48033bf96442788d1f697785773ad9bb +Tag: admin::backup, admin::file-distribution, devel::packaging, + implemented-in::c, interface::commandline, role::program, + scope::utility, suite::gnu, use::storing, works-with-format::tar, + works-with::archive, works-with::file +Section: utils +Priority: required +Filename: pool/main/t/tar/tar_1.34+dfsg-1_amd64.deb +Size: 846756 +MD5sum: 483cd0fa2cc7139193af5fdfb9e1d7c1 +SHA256: bd8e963c6edcf1c806df97cd73560794c347aa94b9aaaf3b88eea585bb2d2f3c + +Package: sed +Version: 4.7-1 +Essential: yes +Installed-Size: 883 +Maintainer: Clint Adams +Architecture: amd64 +Pre-Depends: libacl1 (>= 2.2.51-8), libc6 (>= 2.14), libselinux1 (>= 1.32) +Description: GNU stream editor for filtering/transforming text +Multi-Arch: foreign +Homepage: https://www.gnu.org/software/sed/ +Description-md5: 2ed71305ee7a49ce4438c58140980d2f +Tag: implemented-in::c, interface::commandline, role::program, + scope::utility, suite::gnu, use::editing, works-with::file, + works-with::text +Section: utils +Priority: required +Filename: pool/main/s/sed/sed_4.7-1_amd64.deb +Size: 309632 +MD5sum: dee55bfb08935c00028641671bcaffeb +SHA256: 37917c63d062cd924ba5084820353b256377d88371fbb89537ff06f7a0b5a6c7 \ No newline at end of file diff --git a/pkg/fanal/analyzer/pkg/dpkg/testdata/digest-status b/pkg/fanal/analyzer/pkg/dpkg/testdata/digest-status new file mode 100644 index 000000000000..2ae0da89c888 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/testdata/digest-status @@ -0,0 +1,41 @@ +Package: sed +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 320 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 4.4-2 +Pre-Depends: libc6 (>= 2.14), libselinux1 (>= 1.32) +Description: GNU stream editor for filtering/transforming text + sed reads the specified files or the standard input if no + files are specified, makes editing changes according to a + list of commands, and writes the results to the standard + output. +Original-Maintainer: Clint Adams +Homepage: https://www.gnu.org/software/sed/ + +Package: tar +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 3152 +Maintainer: Janos Lenart +Architecture: amd64 +Multi-Arch: foreign +Version: 1.34+dfsg-1 +Replaces: cpio (<< 2.4.2-39) +Pre-Depends: libacl1 (>= 2.2.23), libc6 (>= 2.28), libselinux1 (>= 3.1~) +Suggests: bzip2, ncompress, xz-utils, tar-scripts, tar-doc +Breaks: dpkg-dev (<< 1.14.26) +Conflicts: cpio (<= 2.4.2-38) +Description: GNU version of the tar archiving utility + Tar is a program for packaging a set of files as a single archive in tar + format. The function it performs is conceptually similar to cpio, and to + things like PKZIP in the DOS world. It is heavily used by the Debian package + management system, and is useful for performing system backups and exchanging + sets of files with others. +Homepage: https://www.gnu.org/software/tar/ \ No newline at end of file diff --git a/pkg/fanal/analyzer/pkg/dpkg/testdata/dpkg b/pkg/fanal/analyzer/pkg/dpkg/testdata/dpkg new file mode 100644 index 000000000000..48f61708024d --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/testdata/dpkg @@ -0,0 +1,2145 @@ +Package: fdisk +Status: install ok installed +Priority: important +Section: utils +Installed-Size: 427 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: util-linux +Version: 2.31.1-0.4ubuntu3.1 +Replaces: util-linux (<< 2.30.1-0ubuntu4~) +Depends: libc6 (>= 2.14), libfdisk1 (>= 2.31.1), libmount1 (>= 2.24.2), libncursesw5 (>= 6), libsmartcols1 (>= 2.28~rc1), libtinfo5 (>= 6) +Breaks: util-linux (<< 2.30.1-0ubuntu4~) +Description: collection of partitioning utilities + This package contains the classic fdisk, sfdisk and cfdisk partitioning + utilities from the util-linux suite. + . + The utilities included in this package allow you to partition + your hard disk. The utilities supports both modern and legacy + partition tables (eg. GPT, MBR, etc). + . + The fdisk utility is the classical text-mode utility. + The cfdisk utilitity gives a more userfriendly curses based interface. + The sfdisk utility is mostly for automation and scripting uses. +Important: yes +Original-Maintainer: LaMont Jones + +Package: libpam-runtime +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 300 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Source: pam +Version: 1.1.8-3.6ubuntu2 +Replaces: libpam0g-dev, libpam0g-util +Depends: debconf (>= 0.5) | debconf-2.0, debconf (>= 1.5.19) | cdebconf, libpam-modules (>= 1.0.1-6) +Conflicts: libpam0g-util +Conffiles: + /etc/pam.conf 87fc76f18e98ee7d3848f6b81b3391e5 + /etc/pam.d/other 31aa7f2181889ffb00b87df4126d1701 +Description: Runtime support for the PAM library + Contains configuration files and directories required for + authentication to work on Debian systems. This package is required + on almost all installations. +Homepage: http://www.linux-pam.org/ +Original-Maintainer: Steve Langasek + +Package: libncurses5 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 283 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: ncurses +Version: 6.1-1ubuntu1.18.04 +Depends: libtinfo5 (= 6.1-1ubuntu1.18.04), libc6 (>= 2.14) +Recommends: libgpm2 +Description: shared libraries for terminal handling + The ncurses library routines are a terminal-independent method of + updating character screens with reasonable optimization. + . + This package contains the shared libraries necessary to run programs + compiled with ncurses. +Homepage: https://invisible-island.net/ncurses/ +Original-Maintainer: Craig Small + +Package: libcom-err2 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 86 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: e2fsprogs +Version: 1.44.1-1 +Replaces: libcomerr2 (<< 1.43.9-1~) +Provides: libcomerr2 (= 1.44.1-1) +Depends: libc6 (>= 2.17) +Breaks: libcomerr2 (<< 1.43.9-1~) +Description: common error description library + libcomerr is an attempt to present a common error-handling mechanism to + manipulate the most common form of error code in a fashion that does not + have the problems identified with mechanisms commonly in use. +Original-Maintainer: Theodore Y. Ts'o +Homepage: http://e2fsprogs.sourceforge.net + +Package: libapt-pkg5.0 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 3108 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: apt +Version: 1.6.3ubuntu0.1 +Provides: libapt-pkg (= 1.6.3ubuntu0.1) +Depends: libbz2-1.0, libc6 (>= 2.27), libgcc1 (>= 1:3.0), liblz4-1 (>= 0.0~r127), liblzma5 (>= 5.1.1alpha+20120614), libstdc++6 (>= 5.2), libudev1 (>= 183), libzstd1 (>= 1.3.2), zlib1g (>= 1:1.2.2.3) +Recommends: apt (>= 1.6.3ubuntu0.1) +Breaks: appstream (<< 0.9.0-3~), apt (<< 1.1~exp14), libapt-inst1.5 (<< 0.9.9~) +Description: package management runtime library + This library provides the common functionality for searching and + managing packages as well as information about packages. + Higher-level package managers can depend upon this library. + . + This includes: + * retrieval of information about packages from multiple sources + * retrieval of packages and all dependent packages + needed to satisfy a request either through an internal + solver or by interfacing with an external one + * authenticating the sources and validating the retrieved data + * installation and removal of packages in the system + * providing different transports to retrieve data over cdrom, ftp, + http(s), rsh as well as an interface to add more transports like + tor+http(s) (apt-transport-tor). +Original-Maintainer: APT Development Team + +Package: libaudit1 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 147 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: audit +Version: 1:2.8.2-1ubuntu1 +Depends: libaudit-common (>= 1:2.8.2-1ubuntu1), libc6 (>= 2.14), libcap-ng0 +Description: Dynamic library for security auditing + The audit-libs package contains the dynamic libraries needed for + applications to use the audit framework. It is used to monitor systems for + security related events. +Homepage: https://people.redhat.com/sgrubb/audit/ +Original-Maintainer: Laurent Bigonville + +Package: libtinfo5 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 497 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: ncurses +Version: 6.1-1ubuntu1.18.04 +Replaces: libncurses5 (<< 5.9-3) +Depends: libc6 (>= 2.16) +Breaks: dialog (<< 1.2-20130523) +Description: shared low-level terminfo library for terminal handling + The ncurses library routines are a terminal-independent method of + updating character screens with reasonable optimization. + . + This package contains the shared low-level terminfo library. +Homepage: https://invisible-island.net/ncurses/ +Original-Maintainer: Craig Small + +Package: perl-base +Essential: yes +Status: install ok installed +Priority: required +Section: perl +Installed-Size: 7818 +Maintainer: Ubuntu Developers +Architecture: amd64 +Source: perl +Version: 5.26.1-6ubuntu0.2 +Replaces: libfile-path-perl (<< 2.12.01), libfile-temp-perl (<< 0.2304), libio-socket-ip-perl (<< 0.38), libscalar-list-utils-perl (<< 1:1.46.02), libsocket-perl (<< 2.020.03), libxsloader-perl (<< 0.27), perl (<< 5.10.1-12), perl-modules (<< 5.20.1-3) +Provides: libfile-path-perl, libfile-temp-perl, libio-socket-ip-perl, libscalar-list-utils-perl, libsocket-perl, libxsloader-perl, perlapi-5.26.0, perlapi-5.26.1 +Pre-Depends: libc6 (>= 2.23), dpkg (>= 1.17.17) +Suggests: perl +Breaks: amanda-common (<< 1:3.3.9-2), autoconf2.13 (<< 2.13-45), backuppc (<< 3.3.1-2), debconf (<< 1.5.61), dh-haskell (<< 0.3), intltool (<< 0.51.0-4), libalien-wxwidgets-perl (<< 0.65+dfsg-2), libanyevent-perl (<< 7.070-2), libcommon-sense-perl (<< 3.72-2~), libexception-class-perl (<< 1.42), libfile-path-perl (<< 2.12.01), libfile-spec-perl (<< 3.6700), libfile-temp-perl (<< 0.2304), libgtk2-perl-doc (<< 2:1.2491-4), libio-socket-ip-perl (<< 0.38), libjcode-perl (<< 2.13-3), libmarc-charset-perl (<< 1.2), libsbuild-perl (<< 0.67.0-1), libscalar-list-utils-perl (<< 1:1.46.02), libsocket-perl (<< 2.020.03), libxsloader-perl (<< 0.27), mailagent (<< 1:3.1-81-2), pdl (<< 1:2.007-4), perl (<< 5.26.1~), perl-modules (<< 5.26.1~), slic3r (<< 1.2.9+dfsg-6.1), slic3r-prusa (<< 1.37.0+dfsg-1.1), texinfo (<< 6.1.0.dfsg.1-8) +Conflicts: defoma (<< 0.11.12), doc-base (<< 0.10.3), mono-gac (<< 2.10.8.1-3), safe-rm (<< 0.8), update-inetd (<< 4.41) +Description: minimal Perl system + Perl is a scripting language used in many system scripts and utilities. + . + This package provides a Perl interpreter and the small subset of the + standard run-time library required to perform basic tasks. For a full + Perl installation, install "perl" (and its dependencies, "perl-modules-5.26" + and "perl-doc"). +Homepage: http://dev.perl.org/perl5/ +Original-Maintainer: Niko Tyni + +Package: libudev1 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 225 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: systemd +Version: 237-3ubuntu10.3 +Depends: libc6 (>= 2.25) +Description: libudev shared library + This library provides access to udev device information. +Homepage: https://www.freedesktop.org/wiki/Software/systemd +Original-Maintainer: Debian systemd Maintainers + +Package: libunistring2 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 1569 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: libunistring +Version: 0.9.9-0ubuntu1 +Depends: libc6 (>= 2.14) +Description: Unicode string library for C + The 'libunistring' library implements Unicode strings (in the UTF-8, + UTF-16, and UTF-32 encodings), together with functions for Unicode + characters (character names, classifications, properties) and + functions for string processing (formatted output, width, word + breaks, line breaks, normalization, case folding, regular + expressions). + . + This package contains the shared library. +Original-Maintainer: Jörg Frings-Fürst +Homepage: http://www.gnu.org/software/libunistring/ + +Package: libnettle6 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 373 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: nettle +Version: 3.4-1 +Depends: libc6 (>= 2.14) +Description: low level cryptographic library (symmetric and one-way cryptos) + Nettle is a cryptographic library that is designed to fit easily in more or + less any context: In crypto toolkits for object-oriented languages (C++, + Python, Pike, ...), in applications like LSH or GNUPG, or even in kernel + space. + . + It tries to solve a problem of providing a common set of cryptographic + algorithms for higher-level applications by implementing a + context-independent set of cryptographic algorithms. In that light, Nettle + doesn't do any memory allocation or I/O, it simply provides the + cryptographic algorithms for the application to use in any environment and + in any way it needs. + . + This package contains the symmetric and one-way cryptographic + algorithms. To avoid having this package depend on libgmp, the + asymmetric cryptos reside in a separate library, libhogweed. +Original-Maintainer: Magnus Holmgren +Homepage: http://www.lysator.liu.se/~nisse/nettle/ + +Package: libattr1 +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 36 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: attr +Version: 1:2.4.47-2build1 +Depends: libc6 (>= 2.4) +Conflicts: attr (<< 2.0.0) +Description: Extended attribute shared library + Contains the runtime environment required by programs that make use + of extended attributes. +Original-Maintainer: Anibal Monsalve Salazar +Homepage: http://savannah.nongnu.org/projects/attr/ + +Package: libss2 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 98 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: e2fsprogs +Version: 1.44.1-1 +Replaces: e2fsprogs (<< 1.34-1) +Depends: libcom-err2, libc6 (>= 2.17) +Description: command-line interface parsing library + libss provides a simple command-line interface parser which will + accept input from the user, parse the command into an argv argument + vector, and then dispatch it to a handler function. + . + It was originally inspired by the Multics SubSystem library. +Original-Maintainer: Theodore Y. Ts'o +Homepage: http://e2fsprogs.sourceforge.net + +Package: liblzma5 +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 309 +Maintainer: Jonathan Nieder +Architecture: amd64 +Multi-Arch: same +Source: xz-utils (5.1.1alpha+20120614-2) +Version: 5.1.1alpha+20120614-2+b3 +Depends: libc6 (>= 2.14) +Pre-Depends: multiarch-support +Description: XZ-format compression library + XZ is the successor to the Lempel-Ziv/Markov-chain Algorithm + compression format, which provides memory-hungry but powerful + compression (often better than bzip2) and fast, easy decompression. + . + The native format of liblzma is XZ; it also supports raw (headerless) + streams and the older LZMA format used by lzma. (For 7-Zip's related + format, use the p7zip package instead.) +Homepage: http://tukaani.org/xz/ + +Package: libidn2-0 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 146 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: libidn2 +Version: 2.0.4-1.1build2 +Depends: libc6 (>= 2.14), libunistring2 (>= 0.9.7) +Description: Internationalized domain names (IDNA2008/TR46) library + Libidn2 implements the revised algorithm for internationalized domain + names called IDNA2008/TR46. + . + This package contains runtime libraries. +Original-Maintainer: Debian Libidn team +Homepage: https://www.gnu.org/software/libidn/#libidn2 + +Package: libpam-modules-bin +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 267 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: pam +Version: 1.1.8-3.6ubuntu2 +Replaces: libpam-modules (<< 1.1.3-8) +Depends: libaudit1 (>= 1:2.2.1), libc6 (>= 2.14), libpam0g (>= 0.99.7.1), libselinux1 (>= 1.32) +Description: Pluggable Authentication Modules for PAM - helper binaries + This package contains helper binaries used by the standard set of PAM + modules in the libpam-modules package. +Homepage: http://www.linux-pam.org/ +Original-Maintainer: Steve Langasek + +Package: grep +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 508 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 3.1-2 +Provides: rgrep +Depends: dpkg (>= 1.15.4) | install-info +Pre-Depends: libc6 (>= 2.14), libpcre3 +Suggests: libpcre3 (>= 7.7) +Conflicts: rgrep +Description: GNU grep, egrep and fgrep + 'grep' is a utility to search for text in files; it can be used from the + command line or in scripts. Even if you don't want to use it, other packages + on your system probably will. + . + The GNU family of grep utilities may be the "fastest grep in the west". + GNU grep is based on a fast lazy-state deterministic matcher (about + twice as fast as stock Unix egrep) hybridized with a Boyer-Moore-Gosper + search for a fixed string that eliminates impossible text from being + considered by the full regexp matcher without necessarily having to + look at every character. The result is typically many times faster + than Unix grep or egrep. (Regular expressions containing backreferencing + will run more slowly, however.) +Original-Maintainer: Anibal Monsalve Salazar +Homepage: http://www.gnu.org/software/grep/ + +Package: base-passwd +Essential: yes +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 228 +Maintainer: Colin Watson +Architecture: amd64 +Multi-Arch: foreign +Version: 3.5.44 +Replaces: base +Depends: libc6 (>= 2.8), libdebconfclient0 (>= 0.145) +Recommends: debconf (>= 0.5) | debconf-2.0 +Description: Debian base system master password and group files + These are the canonical master copies of the user database files + (/etc/passwd and /etc/group), containing the Debian-allocated user and + group IDs. The update-passwd tool is provided to keep the system databases + synchronized with these master files. + +Package: liblz4-1 +Status: install ok installed +Priority: extra +Section: libs +Installed-Size: 132 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: lz4 +Version: 0.0~r131-2ubuntu3 +Depends: libc6 (>= 2.14) +Description: Fast LZ compression algorithm library - runtime + LZ4 is a very fast lossless compression algorithm, providing compression speed + at 400 MB/s per core, scalable with multi-cores CPU. It also features an + extremely fast decoder, with speed in multiple GB/s per core, typically + reaching RAM speed limits on multi-core systems. + . + This package includes the shared library. +Homepage: https://github.com/Cyan4973/lz4 +Original-Maintainer: Nobuhiro Iwamatsu + +Package: debianutils +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 212 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 4.8.4 +Pre-Depends: libc6 (>= 2.15) +Description: Miscellaneous utilities specific to Debian + This package provides a number of small utilities which are used + primarily by the installation scripts of Debian packages, although + you may use them directly. + . + The specific utilities included are: + add-shell installkernel ischroot remove-shell run-parts savelog + tempfile which +Original-Maintainer: Clint Adams + +Package: libgcrypt20 +Status: install ok installed +Priority: standard +Section: libs +Installed-Size: 1209 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Version: 1.8.1-4ubuntu1.1 +Depends: libc6 (>= 2.15), libgpg-error0 (>= 1.25) +Suggests: rng-tools +Description: LGPL Crypto library - runtime library + libgcrypt contains cryptographic functions. Many important free + ciphers, hash algorithms and public key signing algorithms have been + implemented: + . + Arcfour, Blowfish, CAST5, DES, AES, Twofish, Serpent, rfc2268 (rc2), SEED, + Poly1305, Camellia, ChaCha20, IDEA, Salsa, Blake-2, CRC, MD2, MD4, MD5, + RIPE-MD160, SHA-1, SHA-256, SHA-512, SHA3-224, SHA3-256, SHA3-384, SHA3-512, + SHAKE128, SHAKE256, Tiger, Whirlpool, DSA, DSA2, ElGamal, RSA, ECC + (Curve25519, sec256k1, GOST R 34.10-2001 and GOST R 34.10-2012, etc.) +Homepage: http://directory.fsf.org/project/libgcrypt/ +Original-Maintainer: Debian GnuTLS Maintainers + +Package: libncursesw5 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 343 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: ncurses +Version: 6.1-1ubuntu1.18.04 +Depends: libtinfo5 (= 6.1-1ubuntu1.18.04), libc6 (>= 2.14) +Recommends: libgpm2 +Description: shared libraries for terminal handling (wide character support) + The ncurses library routines are a terminal-independent method of + updating character screens with reasonable optimization. + . + This package contains the shared libraries necessary to run programs + compiled with ncursesw, which includes support for wide characters. +Homepage: https://invisible-island.net/ncurses/ +Original-Maintainer: Craig Small + +Package: bash +Essential: yes +Status: install ok installed +Priority: required +Section: shells +Installed-Size: 1588 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 4.4.18-2ubuntu1 +Replaces: bash-completion (<< 20060301-0), bash-doc (<= 2.05-1) +Depends: base-files (>= 2.1.12), debianutils (>= 2.15) +Pre-Depends: libc6 (>= 2.15), libtinfo5 (>= 6) +Recommends: bash-completion (>= 20060301-0) +Suggests: bash-doc +Conflicts: bash-completion (<< 20060301-0) +Conffiles: + /etc/bash.bashrc 3aa8b92d1dd6ddf4daaedc019662f1dc + /etc/skel/.bash_logout 22bfb8c1dd94b5f3813a2b25da67463f + /etc/skel/.bashrc 1f98b8f3f3c8f8927eca945d59dcc1c6 + /etc/skel/.profile f4e81ade7d6f9fb342541152d08e7a97 +Description: GNU Bourne Again SHell + Bash is an sh-compatible command language interpreter that executes + commands read from the standard input or from a file. Bash also + incorporates useful features from the Korn and C shells (ksh and csh). + . + Bash is ultimately intended to be a conformant implementation of the + IEEE POSIX Shell and Tools specification (IEEE Working Group 1003.2). + . + The Programmable Completion Code, by Ian Macdonald, is now found in + the bash-completion package. +Homepage: http://tiswww.case.edu/php/chet/bash/bashtop.html +Original-Maintainer: Matthias Klose + +Package: libuuid1 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 116 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: util-linux +Version: 2.31.1-0.4ubuntu3.1 +Replaces: e2fsprogs (<< 1.34-1) +Depends: libc6 (>= 2.25) +Recommends: uuid-runtime +Description: Universally Unique ID library + The libuuid library generates and parses 128-bit Universally Unique + IDs (UUIDs). A UUID is an identifier that is unique within the space + of all such identifiers across both space and time. It can be used for + multiple purposes, from tagging objects with an extremely short lifetime + to reliably identifying very persistent objects across a network. + . + See RFC 4122 for more information. +Original-Maintainer: LaMont Jones + +Package: libdb5.3 +Status: install ok installed +Priority: standard +Section: libs +Installed-Size: 1729 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: db5.3 +Version: 5.3.28-13.1ubuntu1 +Depends: libc6 (>= 2.17) +Description: Berkeley v5.3 Database Libraries [runtime] + This is the runtime package for programs that use the v5.3 Berkeley + database library. +Homepage: http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/overview/index.html +Original-Maintainer: Debian Berkeley DB Group + +Package: debconf +Status: install ok installed +Priority: important +Section: admin +Installed-Size: 545 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Version: 1.5.66 +Replaces: debconf-tiny +Provides: debconf-2.0 +Pre-Depends: perl-base (>= 5.20.1-3~) +Recommends: apt-utils (>= 0.5.1), debconf-i18n +Suggests: debconf-doc, debconf-utils, whiptail | dialog, libterm-readline-gnu-perl, libgtk3-perl, libnet-ldap-perl, perl, libqtgui4-perl, libqtcore4-perl +Breaks: apt-listchanges (<< 3.14), ubiquity (<< 17.10.2), update-notifier-common (<< 3.187~) +Conflicts: apt (<< 0.3.12.1), cdebconf (<< 0.96), debconf-tiny, debconf-utils (<< 1.3.22), dialog (<< 0.9b-20020814-1), menu (<= 2.1.3-1), whiptail (<< 0.51.4-11), whiptail-utf8 (<= 0.50.17-13) +Conffiles: + /etc/apt/apt.conf.d/70debconf 7e9d09d5801a42b4926b736b8eeabb73 + /etc/debconf.conf 8c0619be413824f1fc7698cee0f23811 +Description: Debian configuration management system + Debconf is a configuration management system for debian packages. Packages + use Debconf to ask questions when they are installed. +Original-Maintainer: Debconf Developers + +Package: zlib1g +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 169 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: zlib +Version: 1:1.2.11.dfsg-0ubuntu2 +Provides: libz1 +Depends: libc6 (>= 2.14) +Breaks: libxml2 (<< 2.7.6.dfsg-2), texlive-binaries (<< 2009-12) +Conflicts: zlib1 (<= 1:1.0.4-7) +Description: compression library - runtime + zlib is a library implementing the deflate compression method found + in gzip and PKZIP. This package includes the shared library. +Homepage: http://zlib.net/ +Original-Maintainer: Mark Brown + +Package: hostname +Essential: yes +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 46 +Maintainer: Ubuntu Developers +Architecture: amd64 +Version: 3.20 +Replaces: nis (<< 3.17-30) +Pre-Depends: libc6 (>= 2.4) +Breaks: nis (<< 3.17-30) +Description: utility to set/show the host name or domain name + This package provides commands which can be used to display the system's + DNS name, and to display or set its hostname or NIS domain name. +Original-Maintainer: Michael Meskes + +Package: mawk +Status: install ok installed +Priority: required +Section: interpreters +Installed-Size: 184 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 1.3.3-17ubuntu3 +Provides: awk +Pre-Depends: libc6 (>= 2.14) +Description: a pattern scanning and text processing language + Mawk is an interpreter for the AWK Programming Language. The AWK + language is useful for manipulation of data files, text retrieval and + processing, and for prototyping and experimenting with algorithms. Mawk + is a new awk meaning it implements the AWK language as defined in Aho, + Kernighan and Weinberger, The AWK Programming Language, Addison-Wesley + Publishing, 1988. (Hereafter referred to as the AWK book.) Mawk conforms + to the POSIX 1003.2 (draft 11.3) definition of the AWK language + which contains a few features not described in the AWK book, and mawk + provides a small number of extensions. + . + Mawk is smaller and much faster than gawk. It has some compile-time + limits such as NF = 32767 and sprintf buffer = 1020. +Original-Maintainer: Steve Langasek + +Package: gzip +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 229 +Maintainer: Ubuntu Developers +Architecture: amd64 +Version: 1.6-5ubuntu1 +Depends: dpkg (>= 1.15.4) | install-info +Pre-Depends: libc6 (>= 2.17) +Suggests: less +Description: GNU compression utilities + This package provides the standard GNU file compression utilities, which + are also the default compression tools for Debian. They typically operate + on files with names ending in '.gz', but can also decompress files ending + in '.Z' created with 'compress'. +Original-Maintainer: Bdale Garbee + +Package: gpgv +Status: install ok installed +Priority: important +Section: utils +Installed-Size: 475 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: gnupg2 +Version: 2.2.4-1ubuntu1.1 +Replaces: gnupg2 (<< 2.0.21-2), gpgv2 (<< 2.1.11-7+exp1) +Depends: libbz2-1.0, libc6 (>= 2.14), libgcrypt20 (>= 1.8.0), libgpg-error0 (>= 1.14), zlib1g (>= 1:1.1.4) +Suggests: gnupg +Breaks: gnupg2 (<< 2.0.21-2), gpgv2 (<< 2.1.11-7+exp1), python-debian (<< 0.1.29) +Description: GNU privacy guard - signature verification tool + GnuPG is GNU's tool for secure communication and data storage. + . + gpgv is actually a stripped-down version of gpg which is only able + to check signatures. It is somewhat smaller than the fully-blown gpg + and uses a different (and simpler) way to check that the public keys + used to make the signature are valid. There are no configuration + files and only a few options are implemented. +Homepage: https://www.gnupg.org/ +Original-Maintainer: Debian GnuPG Maintainers + +Package: bsdutils +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 264 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: util-linux (2.31.1-0.4ubuntu3.1) +Version: 1:2.31.1-0.4ubuntu3.1 +Replaces: bash-completion (<< 1:2.1-4.1~) +Pre-Depends: libc6 (>= 2.14), libsystemd0 +Recommends: bsdmainutils +Breaks: bash-completion (<< 1:2.1-4.1~) +Description: basic utilities from 4.4BSD-Lite + This package contains the bare minimum of BSD utilities needed for a + Debian system: logger, renice, script, scriptreplay, and wall. The + remaining standard BSD utilities are provided by bsdmainutils. +Original-Maintainer: LaMont Jones + +Package: dash +Essential: yes +Status: install ok installed +Priority: required +Section: shells +Installed-Size: 214 +Maintainer: Ubuntu Developers +Architecture: amd64 +Version: 0.5.8-2.10 +Depends: debianutils (>= 2.15), dpkg (>= 1.15.0) +Pre-Depends: libc6 (>= 2.14) +Description: POSIX-compliant shell + The Debian Almquist Shell (dash) is a POSIX-compliant shell derived + from ash. + . + Since it executes scripts faster than bash, and has fewer library + dependencies (making it more robust against software or hardware + failures), it is used as the default system shell on Debian systems. +Original-Maintainer: Gerrit Pape +Homepage: http://gondor.apana.org.au/~herbert/dash/ + +Package: mount +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 370 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: util-linux +Version: 2.31.1-0.4ubuntu3.1 +Replaces: bash-completion (<< 1:2.1-4.3~) +Depends: util-linux (>= 2.30.1-0ubuntu4~) +Pre-Depends: libblkid1 (>= 2.17.2), libc6 (>= 2.17), libmount1 (>= 2.30.2), libsmartcols1 (>= 2.27~rc1) +Suggests: nfs-common (>= 1:1.1.0-13) +Breaks: bash-completion (<< 1:2.1-4.3~) +Description: tools for mounting and manipulating filesystems + This package provides the mount(8), umount(8), swapon(8), + swapoff(8), and losetup(8) commands. +Important: yes +Original-Maintainer: LaMont Jones + +Package: libgnutls30 +Status: install ok installed +Priority: standard +Section: libs +Installed-Size: 1708 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: gnutls28 +Version: 3.5.18-1ubuntu1 +Depends: libc6 (>= 2.25), libgmp10 (>= 2:6), libhogweed4, libidn2-0 (>= 0.6), libnettle6, libp11-kit0 (>= 0.23.1), libtasn1-6 (>= 4.12), libunistring2 (>= 0.9.7), zlib1g (>= 1:1.1.4) +Suggests: gnutls-bin +Description: GNU TLS library - main runtime library + GnuTLS is a portable library which implements the Transport Layer + Security (TLS 1.0, 1.1, 1.2) and Secure Sockets Layer (SSL) 3.0 and Datagram + Transport Layer Security (DTLS 1.0, 1.2) protocols. + . + GnuTLS features support for: + - TLS extensions: server name indication, max record size, opaque PRF + input, etc. + - authentication using the SRP protocol. + - authentication using both X.509 certificates and OpenPGP keys. + - TLS Pre-Shared-Keys (PSK) extension. + - Inner Application (TLS/IA) extension. + - X.509 and OpenPGP certificate handling. + - X.509 Proxy Certificates (RFC 3820). + - all the strong encryption algorithms (including SHA-256/384/512 and + Camellia (RFC 4132)). + . + This package contains the main runtime library. +Homepage: http://www.gnutls.org/ +Original-Maintainer: Debian GnuTLS Maintainers + +Package: libsystemd0 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 646 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: systemd +Version: 237-3ubuntu10.3 +Pre-Depends: libc6 (>= 2.27), libgcrypt20 (>= 1.8.0), liblz4-1 (>= 0.0~r113), liblzma5 (>= 5.1.1alpha+20120614) +Description: systemd utility library + The libsystemd0 library provides interfaces to various systemd components. +Homepage: https://www.freedesktop.org/wiki/Software/systemd +Original-Maintainer: Debian systemd Maintainers + +Package: libzstd1 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 519 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: libzstd +Version: 1.3.3+dfsg-2ubuntu1 +Depends: libc6 (>= 2.14) +Description: fast lossless compression algorithm + Zstd, short for Zstandard, is a fast lossless compression algorithm, targeting + real-time compression scenarios at zlib-level compression ratio. + . + This package contains the shared library. +Homepage: https://github.com/facebook/zstd +Original-Maintainer: Debian Med Packaging Team + +Package: libc6 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 11877 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: glibc +Version: 2.27-3ubuntu1 +Replaces: libc6-amd64 +Depends: libgcc1 +Suggests: glibc-doc, debconf | debconf-2.0, locales +Breaks: hurd (<< 1:0.5.git20140203-1), libtirpc1 (<< 0.2.3), locales (<< 2.27), locales-all (<< 2.27), nscd (<< 2.27) +Conflicts: openrc (<< 0.27-2~) +Conffiles: + /etc/ld.so.conf.d/x86_64-linux-gnu.conf d4e7a7b88a71b5ffd9e2644e71a0cfab +Description: GNU C Library: Shared libraries + Contains the standard libraries that are used by nearly all programs on + the system. This package includes shared versions of the standard C library + and the standard math library, as well as many others. +Homepage: https://www.gnu.org/software/libc/libc.html +Original-Maintainer: GNU Libc Maintainers + +Package: libfdisk1 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 508 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: util-linux +Version: 2.31.1-0.4ubuntu3.1 +Depends: libblkid1 (>= 2.24.2), libc6 (>= 2.25), libuuid1 (>= 2.16) +Description: fdisk partitioning library + The libfdisk library is used for manipulating partition tables. It is + the core of the fdisk, cfdisk, and sfdisk tools. +Original-Maintainer: LaMont Jones + +Package: libpcre3 +Status: install ok installed +Priority: important +Section: libs +Installed-Size: 665 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: pcre3 +Version: 2:8.39-9 +Depends: libc6 (>= 2.14) +Breaks: approx (<< 4.4-1~), cduce (<< 0.5.3-2~), cmigrep (<< 1.5-7~), galax (<< 1.1-7~), libpcre-ocaml (<< 6.0.1~), liquidsoap (<< 0.9.2-3~), ocsigen (<< 1.3.3-1~) +Conflicts: libpcre3-dev (<= 4.3-3) +Description: Old Perl 5 Compatible Regular Expression Library - runtime files + This is a library of functions to support regular expressions whose syntax + and semantics are as close as possible to those of the Perl 5 language. + . + New packages should use the newer pcre2 packages, and existing + packages should migrate to pcre2. + . + This package contains the runtime libraries. +Original-Maintainer: Matthew Vernon + +Package: coreutils +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 6560 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 8.28-1ubuntu1 +Pre-Depends: libacl1 (>= 2.2.51-8), libattr1 (>= 1:2.4.46-8), libc6 (>= 2.25), libselinux1 (>= 2.1.13) +Description: GNU core utilities + This package contains the basic file, shell and text manipulation + utilities which are expected to exist on every operating system. + . + Specifically, this package includes: + arch base64 basename cat chcon chgrp chmod chown chroot cksum comm cp + csplit cut date dd df dir dircolors dirname du echo env expand expr + factor false flock fmt fold groups head hostid id install join link ln + logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup nproc numfmt + od paste pathchk pinky pr printenv printf ptx pwd readlink realpath rm + rmdir runcon sha*sum seq shred sleep sort split stat stty sum sync tac + tail tee test timeout touch tr true truncate tsort tty uname unexpand + uniq unlink users vdir wc who whoami yes +Homepage: http://gnu.org/software/coreutils +Original-Maintainer: Michael Stone + +Package: e2fsprogs +Essential: yes +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 1222 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 1.44.1-1 +Pre-Depends: libblkid1 (>= 2.17.2), libc6 (>= 2.14), libcom-err2 (>= 1.42~WIP-2011-10-05-1), libext2fs2 (= 1.44.1-1), libss2 (>= 1.34-1), libuuid1 (>= 2.16) +Recommends: e2fsprogs-l10n +Suggests: gpart, parted, fuse2fs, e2fsck-static +Conffiles: + /etc/mke2fs.conf 72b349d890a9b5cca06c7804cd0c8d1d +Description: ext2/ext3/ext4 file system utilities + The ext2, ext3 and ext4 file systems are successors of the original ext + ("extended") file system. They are the main file system types used for + hard disks on Debian and other Linux systems. + . + This package contains programs for creating, checking, and maintaining + ext2/3/4-based file systems. It also includes the "badblocks" program, + which can be used to scan for bad blocks on a disk or other storage device. +Original-Maintainer: Theodore Y. Ts'o +Homepage: http://e2fsprogs.sourceforge.net + +Package: tar +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 864 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 1.29b-2 +Replaces: cpio (<< 2.4.2-39) +Pre-Depends: libacl1 (>= 2.2.51-8), libc6 (>= 2.17), libselinux1 (>= 1.32) +Suggests: bzip2, ncompress, xz-utils, tar-scripts, tar-doc +Breaks: dpkg-dev (<< 1.14.26) +Conflicts: cpio (<= 2.4.2-38) +Conffiles: + /etc/rmt 3c58b7cd13da1085eff0acc6a00f43c7 +Description: GNU version of the tar archiving utility + Tar is a program for packaging a set of files as a single archive in tar + format. The function it performs is conceptually similar to cpio, and to + things like PKZIP in the DOS world. It is heavily used by the Debian package + management system, and is useful for performing system backups and exchanging + sets of files with others. +Original-Maintainer: Bdale Garbee + +Package: libprocps6 +Status: install ok installed +Priority: important +Section: libs +Installed-Size: 118 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: procps +Version: 2:3.3.12-3ubuntu1.1 +Replaces: procps (<< 1:3.3.2-1) +Depends: libc6 (>= 2.14), libsystemd0 (>= 209) +Description: library for accessing process information from /proc + The libprocps library is a way of accessing information out of the /proc + filesystem. + . + This package contains the shared libraries necessary to run programs + compiled with libprocps. +Homepage: https://gitlab.com/procps-ng/procps +Original-Maintainer: Craig Small + +Package: libbz2-1.0 +Status: install ok installed +Priority: important +Section: libs +Installed-Size: 90 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: bzip2 +Version: 1.0.6-8.1 +Depends: libc6 (>= 2.4) +Description: high-quality block-sorting file compressor library - runtime + This package contains libbzip2 which is used by the bzip2 compressor. + . + bzip2 is a freely available, patent free, high-quality data compressor. + It typically compresses files to within 10% to 15% of the best available + techniques, whilst being around twice as fast at compression and six + times faster at decompression. + . + bzip2 compresses files using the Burrows-Wheeler block-sorting text + compression algorithm, and Huffman coding. Compression is generally + considerably better than that achieved by more conventional + LZ77/LZ78-based compressors, and approaches the performance of the PPM + family of statistical compressors. + . + The archive file format of bzip2 (.bz2) is incompatible with that of its + predecessor, bzip (.bz). +Original-Maintainer: Anibal Monsalve Salazar +Homepage: http://www.bzip.org/ + +Package: libblkid1 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 398 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: util-linux +Version: 2.31.1-0.4ubuntu3.1 +Depends: libc6 (>= 2.25), libuuid1 (>= 2.16) +Description: block device ID library + The blkid library allows system programs such as fsck and mount to + quickly and easily find block devices by filesystem UUID or label. + This allows system administrators to avoid specifying filesystems by + hard-coded device names and use a logical naming system instead. +Original-Maintainer: LaMont Jones + +Package: libtasn1-6 +Status: install ok installed +Priority: standard +Section: libs +Installed-Size: 112 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Version: 4.13-2 +Depends: libc6 (>= 2.14) +Description: Manage ASN.1 structures (runtime) + Manage ASN1 (Abstract Syntax Notation One) structures. + The main features of this library are: + * on-line ASN1 structure management that doesn't require any C code + file generation. + * off-line ASN1 structure management with C code file generation + containing an array. + * DER (Distinguish Encoding Rules) encoding + * no limits for INTEGER and ENUMERATED values + . + This package contains runtime libraries. +Original-Maintainer: Debian GnuTLS Maintainers +Homepage: http://www.gnu.org/software/libtasn1/ + +Package: bzip2 +Status: install ok installed +Priority: standard +Section: utils +Installed-Size: 177 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 1.0.6-8.1 +Replaces: libbz2 (<< 0.9.5d-3) +Depends: libbz2-1.0 (= 1.0.6-8.1), libc6 (>= 2.14) +Suggests: bzip2-doc +Description: high-quality block-sorting file compressor - utilities + bzip2 is a freely available, patent free, high-quality data compressor. + It typically compresses files to within 10% to 15% of the best available + techniques, whilst being around twice as fast at compression and six + times faster at decompression. + . + bzip2 compresses files using the Burrows-Wheeler block-sorting text + compression algorithm, and Huffman coding. Compression is generally + considerably better than that achieved by more conventional + LZ77/LZ78-based compressors, and approaches the performance of the PPM + family of statistical compressors. + . + The archive file format of bzip2 (.bz2) is incompatible with that of its + predecessor, bzip (.bz). +Original-Maintainer: Anibal Monsalve Salazar +Homepage: http://www.bzip.org/ + +Package: libhogweed4 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 228 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: nettle +Version: 3.4-1 +Depends: libc6 (>= 2.14), libgmp10 (>= 2:6.0.0), libnettle6 (= 3.4-1) +Description: low level cryptographic library (public-key cryptos) + Nettle is a cryptographic library that is designed to fit easily in more or + less any context: In crypto toolkits for object-oriented languages (C++, + Python, Pike, ...), in applications like LSH or GNUPG, or even in kernel + space. + . + It tries to solve a problem of providing a common set of cryptographic + algorithms for higher-level applications by implementing a + context-independent set of cryptographic algorithms. In that light, Nettle + doesn't do any memory allocation or I/O, it simply provides the + cryptographic algorithms for the application to use in any environment and + in any way it needs. + . + This package contains the asymmetric cryptographic algorithms, which, + require the GNU multiple precision arithmetic library (libgmp) for + their large integer computations. +Original-Maintainer: Magnus Holmgren +Homepage: http://www.lysator.liu.se/~nisse/nettle/ + +Package: lsb-base +Status: install ok installed +Priority: required +Section: misc +Installed-Size: 58 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Source: lsb +Version: 9.20170808ubuntu1 +Description: Linux Standard Base init script functionality + The Linux Standard Base (http://www.linuxbase.org/) is a standard + core system that third-party applications written for Linux can + depend upon. + . + This package only includes the init-functions shell library, which + may be used by other packages' initialization scripts for console + logging and other purposes. +Homepage: https://wiki.linuxfoundation.org/lsb/start +Original-Maintainer: Debian LSB Team + +Package: procps +Status: install ok installed +Priority: important +Section: admin +Installed-Size: 709 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 2:3.3.12-3ubuntu1.1 +Provides: watch +Depends: libc6 (>= 2.27), libncurses5 (>= 6), libncursesw5 (>= 6), libprocps6, libtinfo5 (>= 6), lsb-base (>= 3.0-10), init-system-helpers (>= 1.29~) +Recommends: psmisc +Breaks: guymager (<= 0.5.9-1), open-vm-tools (<= 2011.12.20-562307-1) +Conflicts: pgrep (<< 3.3-5), w-bassman (<< 1.0-3) +Conffiles: + /etc/init.d/procps 49fbfd237be2a2f09576f1f9374580be + /etc/sysctl.conf 36547fde818f251846b0198564060927 + /etc/sysctl.d/10-console-messages.conf 154f6f5c5810d10bb303fb6a8e907c6a + /etc/sysctl.d/10-ipv6-privacy.conf e9473d12b4a7069d6a3ca8b694511ddf + /etc/sysctl.d/10-kernel-hardening.conf 5c1388f00011a287cdeba60208c674e1 + /etc/sysctl.d/10-link-restrictions.conf 8568316f2baa8db06554dab91f93a161 + /etc/sysctl.d/10-magic-sysrq.conf b3059f2835f17c97265433fdfdee358f + /etc/sysctl.d/10-network-security.conf 4ac7258f5336e7eeaf448c05ab668d3c + /etc/sysctl.d/10-ptrace.conf 47f40494b2fc698e15549e0a4a79e81c + /etc/sysctl.d/10-zeropage.conf 8d7193abcc4dfedaf519dd03016a5e59 + /etc/sysctl.d/README c20074b9b11a5202758c69d7bcb6996f +Description: /proc file system utilities + This package provides command line and full screen utilities for browsing + procfs, a "pseudo" file system dynamically generated by the kernel to + provide information about the status of entries in its process table + (such as whether the process is running, stopped, or a "zombie"). + . + It contains free, kill, pkill, pgrep, pmap, ps, pwdx, skill, slabtop, + snice, sysctl, tload, top, uptime, vmstat, w, and watch. +Homepage: https://gitlab.com/procps-ng/procps +Original-Maintainer: Craig Small + +Package: libgpg-error0 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 164 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: libgpg-error +Version: 1.27-6 +Depends: libc6 (>= 2.15) +Description: library for common error values and messages in GnuPG components + Library that defines common error values for all GnuPG + components. Among these are GPG, GPGSM, GPGME, GPG-Agent, libgcrypt, + pinentry, SmartCard Daemon and possibly more in the future. +Original-Maintainer: Debian GnuPG Maintainers +Homepage: https://www.gnupg.org/related_software/libgpg-error/ + +Package: base-files +Essential: yes +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 375 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 10.1ubuntu2.2 +Replaces: base, dpkg (<= 1.15.0), miscutils +Provides: base +Pre-Depends: awk +Breaks: initscripts (<< 2.88dsf-13.3), sendfile (<< 2.1b.20080616-5.2~) +Conffiles: + /etc/debian_version 71f3b31c52b52a91784e496ddf94b019 + /etc/default/motd-news c08a329a603b640095da5ffe4e73491c + /etc/dpkg/origins/debian 731423fa8ba067262f8ef37882d1e742 + /etc/dpkg/origins/ubuntu ea35901c45553c3451f60476be94d2d8 + /etc/host.conf 89408008f2585c957c031716600d5a80 + /etc/issue 38103f9a76bb14e2abbc051b65722cff + /etc/issue.net 4c773f83594f2a3f47059a348a0af0a5 + /etc/legal 0110925f6e068836ef2e09356e3651d9 + /etc/lsb-release d1b7cd24192250d97c4699cbe7a47cfa + /etc/update-motd.d/00-header 4a1e6eed7a59f200b4267085721750a3 + /etc/update-motd.d/10-help-text d95d18b11ac12cf6582d08a1643034f3 + /etc/update-motd.d/50-motd-news c1d89e86b0eed1ffb1835bcdd78dfe32 +Description: Debian base system miscellaneous files + This package contains the basic filesystem hierarchy of a Debian system, and + several important miscellaneous files, such as /etc/debian_version, + /etc/host.conf, /etc/issue, /etc/motd, /etc/profile, and others, + and the text of several common licenses in use on Debian systems. +Original-Maintainer: Santiago Vila + +Package: libgmp10 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 558 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: gmp +Version: 2:6.1.2+dfsg-2 +Depends: libc6 (>= 2.14) +Description: Multiprecision arithmetic library + GNU MP is a programmer's library for arbitrary precision + arithmetic (ie, a bignum package). It can operate on signed + integer, rational, and floating point numeric types. + . + It has a rich set of functions, and the functions have a regular + interface. +Original-Maintainer: Debian Science Team +Homepage: http://gmplib.org/ + +Package: sensible-utils +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 62 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Version: 0.0.12 +Replaces: debianutils (<= 2.32.3), manpages-pl (<= 20060617-3~) +Description: Utilities for sensible alternative selection + This package provides a number of small utilities which are used + by programs to sensibly select and spawn an appropriate browser, + editor, or pager. + . + The specific utilities included are: sensible-browser sensible-editor + sensible-pager +Original-Maintainer: Anibal Monsalve Salazar + +Package: passwd +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 2541 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: shadow +Version: 1:4.5-1ubuntu1 +Replaces: manpages-tr (<< 1.0.5), manpages-zh (<< 1.5.1-1) +Depends: libaudit1 (>= 1:2.2.1), libc6 (>= 2.14), libpam0g (>= 0.99.7.1), libselinux1 (>= 1.32), libsemanage1 (>= 2.0.3), libpam-modules +Conffiles: + /etc/cron.daily/passwd db990990933b6f56322725223f13c2bc + /etc/default/useradd cc9f9a7713ab62a32cd38363d958f396 + /etc/pam.d/chfn 4d466e00a348ba426130664d795e8afa + /etc/pam.d/chpasswd 9900720564cb4ee98b7da29e2d183cb2 + /etc/pam.d/chsh a6e9b589e90009334ffd030d819290a6 + /etc/pam.d/newusers 1454e29bfa9f2a10836563e76936cea5 + /etc/pam.d/passwd eaf2ad85b5ccd06cceb19a3e75f40c63 +Description: change and administer password and group data + This package includes passwd, chsh, chfn, and many other programs to + maintain password and group data. + . + Shadow passwords are supported. See /usr/share/doc/passwd/README.Debian +Homepage: https://github.com/shadow-maint/shadow +Original-Maintainer: Shadow package maintainers + +Package: init-system-helpers +Essential: yes +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 129 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Version: 1.51 +Replaces: sysv-rc (<< 2.88dsf-59.3~), sysvinit-utils (<< 2.88dsf-59.3) +Depends: perl-base (>= 5.20.1-3) +Breaks: systemd (<< 44-12), sysvinit-utils (<< 2.88dsf-59.3~) +Conflicts: file-rc (<< 0.8.17~), openrc (<= 0.18.3-1) +Description: helper tools for all init systems + This package contains helper tools that are necessary for switching between + the various init systems that Debian contains (e. g. sysvinit or + systemd). An example is deb-systemd-helper, a script that enables systemd unit + files without depending on a running systemd. + . + It also includes the "service", "invoke-rc.d", and "update-rc.d" scripts which + provide an abstraction for enabling, disabling, starting, and stopping + services for all supported Debian init systems as specified by the policy. + . + While this package is maintained by pkg-systemd-maintainers, it is NOT + specific to systemd at all. Maintainers of other init systems are welcome to + include their helpers in this package. +Original-Maintainer: Debian systemd Maintainers + +Package: ncurses-base +Essential: yes +Status: install ok installed +Priority: required +Section: misc +Installed-Size: 364 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Source: ncurses +Version: 6.1-1ubuntu1.18.04 +Provides: ncurses-runtime +Breaks: ncurses-term (<< 5.7+20100313-3) +Conffiles: + /etc/terminfo/README 45b6df19fb5e21f55717482fa7a30171 +Description: basic terminal type definitions + The ncurses library routines are a terminal-independent method of + updating character screens with reasonable optimization. + . + This package contains terminfo data files to support the most common types of + terminal, including ansi, dumb, linux, rxvt, screen, sun, vt100, vt102, vt220, + vt52, and xterm. +Homepage: https://invisible-island.net/ncurses/ +Original-Maintainer: Craig Small + +Package: libc-bin +Essential: yes +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 3631 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: glibc +Version: 2.27-3ubuntu1 +Depends: libc6 (>> 2.27), libc6 (<< 2.28) +Suggests: manpages +Conffiles: + /etc/bindresvport.blacklist 4c09213317e4e3dd3c71d74404e503c5 + /etc/default/nss d6d5d6f621fb3ead2548076ce81e309c + /etc/gai.conf 28fa76ff5a9e0566eaa1e11f1ce51f09 + /etc/ld.so.conf 4317c6de8564b68d628c21efa96b37e4 + /etc/ld.so.conf.d/libc.conf d4d833fd095fb7b90e1bb4a547f16de6 +Description: GNU C Library: Binaries + This package contains utility programs related to the GNU C Library. + . + * catchsegv: catch segmentation faults in programs + * getconf: query system configuration variables + * getent: get entries from administrative databases + * iconv, iconvconfig: convert between character encodings + * ldd, ldconfig: print/configure shared library dependencies + * locale, localedef: show/generate locale definitions + * tzselect, zdump, zic: select/dump/compile time zones +Homepage: https://www.gnu.org/software/libc/libc.html +Original-Maintainer: GNU Libc Maintainers + +Package: libsemanage1 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 292 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: libsemanage +Version: 2.7-2build2 +Depends: libsemanage-common (= 2.7-2build2), libaudit1 (>= 1:2.2.1), libbz2-1.0, libc6 (>= 2.14), libselinux1 (>= 2.7), libsepol1 (>= 2.7) +Breaks: policycoreutils (<< 2.4), selinux-policy-default (<< 2:2.20140421-10~), selinux-policy-mls (<< 2:2.20140421-10~) +Description: SELinux policy management library + This package provides the shared libraries for SELinux policy management. + It uses libsepol for binary policy manipulation and libselinux for + interacting with the SELinux system. It also exec's helper programs + for loading policy and for checking whether the file_contexts + configuration is valid (load_policy and setfiles from + policycoreutils) presently, although this may change at least for the + bootstrapping case + . + Security-enhanced Linux is a patch of the Linux kernel and a + number of utilities with enhanced security functionality designed to + add mandatory access controls to Linux. The Security-enhanced Linux + kernel contains new architectural components originally developed to + improve the security of the Flask operating system. These + architectural components provide general support for the enforcement + of many kinds of mandatory access control policies, including those + based on the concepts of Type Enforcement, Role-based Access + Control, and Multi-level Security. +Homepage: http://userspace.selinuxproject.org/ +Original-Maintainer: Debian SELinux maintainers + +Package: libseccomp2 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 293 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: libseccomp +Version: 2.3.1-2.1ubuntu4 +Depends: libc6 (>= 2.14) +Description: high level interface to Linux seccomp filter + This library provides a high level interface to constructing, analyzing + and installing seccomp filters via a BPF passed to the Linux Kernel's + prctl() syscall. +Homepage: https://github.com/seccomp/libseccomp +Original-Maintainer: Kees Cook + +Package: sysvinit-utils +Essential: yes +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 60 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: sysvinit +Version: 2.88dsf-59.10ubuntu1 +Replaces: initscripts (<< 2.88dsf-59.5) +Depends: libc6 (>= 2.14), init-system-helpers (>= 1.25~), util-linux (>> 2.28-2~) +Breaks: systemd (<< 215) +Description: System-V-like utilities + This package contains the important System-V-like utilities. + . + Specifically, this package includes: + killall5, pidof +Homepage: http://savannah.nongnu.org/projects/sysvinit +Original-Maintainer: Debian sysvinit maintainers + +Package: libsemanage-common +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 29 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Source: libsemanage +Version: 2.7-2build2 +Replaces: libsemanage1 (<= 2.0.41-1), libsemanage1-dev (<< 2.1.6-3~) +Breaks: libsemanage1 (<= 2.0.41-1), libsemanage1-dev (<< 2.1.6-3~) +Conffiles: + /etc/selinux/semanage.conf f6f9b97af233c90ca127f406fb6f0932 +Description: Common files for SELinux policy management libraries + This package provides the common files used by the shared libraries + for SELinux policy management. + . + Security-enhanced Linux is a patch of the Linux kernel and a + number of utilities with enhanced security functionality designed to + add mandatory access controls to Linux. The Security-enhanced Linux + kernel contains new architectural components originally developed to + improve the security of the Flask operating system. These + architectural components provide general support for the enforcement + of many kinds of mandatory access control policies, including those + based on the concepts of Type Enforcement, Role-based Access + Control, and Multi-level Security. +Homepage: http://userspace.selinuxproject.org/ +Original-Maintainer: Debian SELinux maintainers + +Package: libp11-kit0 +Status: install ok installed +Priority: standard +Section: libs +Installed-Size: 1246 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: p11-kit +Version: 0.23.9-2 +Depends: libc6 (>= 2.26), libffi6 (>= 3.0.4) +Breaks: opencryptoki (<= 3.6.1+dfsg-1) +Description: library for loading and coordinating access to PKCS#11 modules - runtime + The p11-kit library provides a way to load and enumerate Public-Key + Cryptography Standard #11 modules, along with a standard configuration + setup for installing PKCS#11 modules so that they're discoverable. It + also solves problems with coordinating the use of PKCS#11 by different + components or libraries living in the same process. + . + This package contains the shared library required for applications loading + and accessing PKCS#11 modules. +Original-Maintainer: Debian GnuTLS Maintainers +Homepage: http://p11-glue.freedesktop.org/p11-kit.html + +Package: libdebconfclient0 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 68 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: cdebconf +Version: 0.213ubuntu1 +Depends: libc6 (>= 2.4) +Description: Debian Configuration Management System (C-implementation library) + Debconf is a configuration management system for Debian packages. It is + used by some packages to prompt you for information before they are + installed. cdebconf is a reimplementation of the original debconf in C. + . + This library allows C programs to interface with cdebconf. +Original-Maintainer: Debian Install System Team + +Package: libselinux1 +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 193 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: libselinux +Version: 2.7-2build2 +Depends: libc6 (>= 2.14), libpcre3 +Description: SELinux runtime shared libraries + This package provides the shared libraries for Security-enhanced + Linux that provides interfaces (e.g. library functions for the + SELinux kernel APIs like getcon(), other support functions like + getseuserbyname()) to SELinux-aware applications. Security-enhanced + Linux is a patch of the Linux kernel and a number of utilities with + enhanced security functionality designed to add mandatory access + controls to Linux. The Security-enhanced Linux kernel contains new + architectural components originally developed to improve the security + of the Flask operating system. These architectural components provide + general support for the enforcement of many kinds of mandatory access + control policies, including those based on the concepts of Type + Enforcement, Role-based Access Control, and Multi-level Security. + . + libselinux1 provides an API for SELinux applications to get and set + process and file security contexts and to obtain security policy + decisions. Required for any applications that use the SELinux + API. libselinux may use the shared libsepol to manipulate the binary + policy if necessary (e.g. to downgrade the policy format to an older + version supported by the kernel) when loading policy. +Homepage: http://userspace.selinuxproject.org/ +Original-Maintainer: Debian SELinux maintainers + +Package: dpkg +Essential: yes +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 6773 +Origin: debian +Maintainer: Ubuntu Developers +Bugs: debbugs://bugs.debian.org +Architecture: amd64 +Multi-Arch: foreign +Version: 1.19.0.5ubuntu2 +Depends: tar (>= 1.28-1) +Pre-Depends: libbz2-1.0, libc6 (>= 2.14), liblzma5 (>= 5.2.2), libselinux1 (>= 2.3), libzstd1 (>= 1.3.2), zlib1g (>= 1:1.1.4) +Suggests: apt, debsig-verify +Breaks: acidbase (<= 1.4.5-4), amule (<< 2.3.1+git1a369e47-3), beep (<< 1.3-4), im (<< 1:151-4), libdpkg-perl (<< 1.18.11), netselect (<< 0.3.ds1-27), pconsole (<< 1.0-12), phpgacl (<< 3.3.7-7.3), pure-ftpd (<< 1.0.43-1), systemtap (<< 2.8-1), terminatorx (<< 4.0.1-1), xvt (<= 2.1-20.1) +Conffiles: + /etc/alternatives/README 7be88b21f7e386c8d5a8790c2461c92b + /etc/cron.daily/dpkg 4a75e177a3662e2efd8d477ae8e8533b + /etc/dpkg/dpkg.cfg f4413ffb515f8f753624ae3bb365b81b + /etc/logrotate.d/alternatives 5fe0af6ce1505fefdc158d9e5dbf6286 + /etc/logrotate.d/dpkg 9e25c8505966b5829785f34a548ae11f +Description: Debian package management system + This package provides the low-level infrastructure for handling the + installation and removal of Debian software packages. + . + For Debian package development tools, install dpkg-dev. +Homepage: https://wiki.debian.org/Teams/Dpkg +Original-Maintainer: Dpkg Developers + +Package: gcc-8-base +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 106 +Maintainer: Ubuntu Core developers +Architecture: amd64 +Multi-Arch: same +Source: gcc-8 +Version: 8-20180414-1ubuntu2 +Breaks: gcc-4.4-base (<< 4.4.7), gcc-4.7-base (<< 4.7.3), gcj-4.4-base (<< 4.4.6-9~), gcj-4.6-base (<< 4.6.1-4~), gnat-4.4-base (<< 4.4.6-3~), gnat-4.6 (<< 4.6.1-5~) +Description: GCC, the GNU Compiler Collection (base package) + This package contains files common to all languages and libraries + contained in the GNU Compiler Collection (GCC). +Homepage: http://gcc.gnu.org/ +Original-Maintainer: Debian GCC Maintainers + +Package: apt +Status: install ok installed +Priority: important +Section: admin +Installed-Size: 3806 +Maintainer: Ubuntu Developers +Architecture: amd64 +Version: 1.6.3ubuntu0.1 +Replaces: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~) +Provides: apt-transport-https (= 1.6.3ubuntu0.1) +Depends: adduser, gpgv | gpgv2 | gpgv1, ubuntu-keyring, libapt-pkg5.0 (>= 1.6.3ubuntu0.1), libc6 (>= 2.15), libgcc1 (>= 1:3.0), libgnutls30 (>= 3.5.6), libseccomp2 (>= 1.0.1), libstdc++6 (>= 5.2) +Recommends: ca-certificates +Suggests: apt-doc, aptitude | synaptic | wajig, dpkg-dev (>= 1.17.2), gnupg | gnupg2 | gnupg1, powermgmt-base +Breaks: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~), aptitude (<< 0.8.10) +Conffiles: + /etc/apt/apt.conf.d/01-vendor-ubuntu 5232396660502461fc834c0a1229dbe4 + /etc/apt/apt.conf.d/01autoremove 735050108d38dbf99e933a9f647f6892 + /etc/cron.daily/apt-compat 49e9b2cfa17849700d4db735d04244f3 + /etc/kernel/postinst.d/apt-auto-removal 4ad976a68f045517cf4696cec7b8aa3a + /etc/logrotate.d/apt 179f2ed4f85cbaca12fa3d69c2a4a1c3 +Description: commandline package manager + This package provides commandline tools for searching and + managing as well as querying information about packages + as a low-level access to all features of the libapt-pkg library. + . + These include: + * apt-get for retrieval of packages and information about them + from authenticated sources and for installation, upgrade and + removal of packages together with their dependencies + * apt-cache for querying available information about installed + as well as installable packages + * apt-cdrom to use removable media as a source for packages + * apt-config as an interface to the configuration settings + * apt-key as an interface to manage authentication keys +Original-Maintainer: APT Development Team + +Package: diffutils +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 452 +Maintainer: Ubuntu Developers +Architecture: amd64 +Version: 1:3.6-1 +Replaces: diff +Pre-Depends: libc6 (>= 2.17) +Suggests: diffutils-doc, wdiff +Description: File comparison utilities + The diffutils package provides the diff, diff3, sdiff, and cmp programs. + . + `diff' shows differences between two files, or each corresponding file + in two directories. `cmp' shows the offsets and line numbers where + two files differ. `cmp' can also show all the characters that + differ between the two files, side by side. `diff3' shows differences + among three files. `sdiff' merges two files interactively. + . + The set of differences produced by `diff' can be used to distribute + updates to text files (such as program source code) to other people. + This method is especially useful when the differences are small compared + to the complete files. Given `diff' output, the `patch' program can + update, or "patch", a copy of the file. +Original-Maintainer: Santiago Vila +Homepage: http://www.gnu.org/software/diffutils/ + +Package: libpam-modules +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 912 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: pam +Version: 1.1.8-3.6ubuntu2 +Replaces: libpam-umask, libpam0g-util +Provides: libpam-mkhomedir, libpam-motd, libpam-umask +Pre-Depends: libaudit1 (>= 1:2.2.1), libc6 (>= 2.27), libdb5.3, libpam0g (>= 1.1.3-2), libselinux1 (>= 2.1.9), debconf (>= 0.5) | debconf-2.0, libpam-modules-bin (= 1.1.8-3.6ubuntu2) +Recommends: update-motd +Conflicts: libpam-mkhomedir, libpam-motd, libpam-umask +Conffiles: + /etc/security/access.conf 13ec4d189f0ed9acf3433977a53d446b + /etc/security/group.conf f1e26e8db6f7abd2d697d7dad3422c36 + /etc/security/limits.conf 11c27ba00b7bd6a255f33126f75c5005 + /etc/security/namespace.conf 6424c99a62ddf4b7d3ca713bb06ded89 + /etc/security/namespace.init d9e6a7c85e966427ef23a04ec6c7000f + /etc/security/pam_env.conf ddee4a931170dc21b4e0b9bb28e02a7b + /etc/security/sepermit.conf d41c74654734a5c069a37bfc02f0a6d4 + /etc/security/time.conf 06e05c6079e839c8833ac7c3abfde192 +Description: Pluggable Authentication Modules for PAM + This package completes the set of modules for PAM. It includes the + pam_unix.so module as well as some specialty modules. +Homepage: http://www.linux-pam.org/ +Original-Maintainer: Steve Langasek + +Package: libstdc++6 +Status: install ok installed +Priority: important +Section: libs +Installed-Size: 2060 +Maintainer: Ubuntu Core developers +Architecture: amd64 +Multi-Arch: same +Source: gcc-8 +Version: 8-20180414-1ubuntu2 +Replaces: libstdc++6-8-dbg (<< 4.9.0-3) +Depends: gcc-8-base (= 8-20180414-1ubuntu2), libc6 (>= 2.18), libgcc1 (>= 1:4.2) +Breaks: blockattack (<= 1.4.1+ds1-2.1build2), boo (<= 0.9.5~git20110729.r1.202a430-2), c++-annotations (<= 10.2.0-1), chromium-browser (<= 43.0.2357.130-0ubuntu2), clustalx (<= 2.1+lgpl-2), dff (<= 1.3.0+dfsg.1-4.1build2), emscripten (<= 1.22.1-1), ergo (<= 3.4.0-1), fceux (<= 2.2.2+dfsg0-1), flush (<= 0.9.12-3.1ubuntu1), freeorion (<= 0.4.4+git20150327-2), fslview (<= 4.0.1-4), fwbuilder (<= 5.1.0-4), gcc-4.3 (<< 4.3.6-1), gcc-4.4 (<< 4.4.6-4), gcc-4.5 (<< 4.5.3-2), gnote (<= 3.16.2-1), gnudatalanguage (<= 0.9.5-2build1), innoextract (<= 1.4-1build1), libantlr-dev (<= 2.7.7+dfsg-6), libapache2-mod-passenger (<= 4.0.53-1), libaqsis1 (<= 1.8.2-1), libassimp3 (<= 3.0~dfsg-4), libboost-date-time1.55.0, libcpprest2.2 (<= 2.2.0-1), libdap17 (<= 3.14.0-2), libdapclient6 (<= 3.14.0-2), libdapserver7 (<= 3.14.0-2), libdavix0 (<= 0.4.0-1build1), libdballe6 (<= 6.8-1), libdiet-admin2.8 (<= 2.8.0-1build3), libdiet-client2.8 (<= 2.8.0-1build3), libdiet-sed2.8 (<= 2.8.0-1build3), libfreefem++ (<= 3.37.1-1), libgazebo5 (<= 5.0.1+dfsg-2.1), libgetfem4++ (<= 4.2.1~beta1~svn4482~dfsg-3ubuntu3), libgmsh2 (<= 2.8.5+dfsg-1.1ubuntu1), libinsighttoolkit4.6 (<= 4.6.0-3ubuntu3), libkgeomap2 (<= 4:15.04.2-0ubuntu1), libkolabxml1 (<= 1.1.0-3), libkvkontakte1 (<= 1.0~digikam4.10.0-0ubuntu2), libmarisa0 (<= 0.2.4-8build1), libmediawiki1 (<= 1.0~digikam4.10.0-0ubuntu2), libogre-1.8.0 (<= 1.8.1+dfsg-0ubuntu5), libogre-1.9.0 (<= 1.9.0+dfsg1-4), libopenwalnut1 (<= 1.4.0~rc1+hg3a3147463ee2-1ubuntu2), libpqxx-4.0 (<= 4.0.1+dfsg-3ubuntu1), libreoffice-core (<= 1:4.4.4~rc3-0ubuntu1), librime1 (<= 1.2+dfsg-2), libwibble-dev (<= 1.1-1), libwreport2 (<= 2.14-1), libxmltooling6 (<= 1.5.3-2.1), lightspark (<= 0.7.2+git20150512-2), mira-assembler (<= 4.9.5-1), mongodb (<= 1:2.6.3-0ubuntu7), mongodb-server (<= 1:2.6.3-0ubuntu7), ncbi-blast+ (<= 2.2.30-4), openscad (<= 2014.03+dfsg-1build1), passepartout (<= 0.7.1-1.1), pdf2djvu (<= 0.7.19-1ubuntu2), photoprint (<= 0.4.2~pre2-2.3), plastimatch (<= 1.6.2+dfsg-1), plee-the-bear (<= 0.6.0-3.1), povray (<= 1:3.7.0.0-8), powertop (<= 2.6.1-1), printer-driver-brlaser (<= 3-3), psi4 (<= 4.0~beta5+dfsg-2build1), python-healpy (<= 1.8.1-1), python3-taglib (<= 0.3.6+dfsg-2build2), realtimebattle (<= 1.0.8-14), ruby-passenger (<= 4.0.53-1), sqlitebrowser (<= 3.5.1-3), tecnoballz (<= 0.93.1-6), wesnoth-1.12-core (<= 1:1.12.4-1), widelands (<= 1:18-3build1), xflr5 (<= 6.09.06-2) +Conflicts: scim (<< 1.4.2-1) +Description: GNU Standard C++ Library v3 + This package contains an additional runtime library for C++ programs + built with the GNU compiler. + . + libstdc++-v3 is a complete rewrite from the previous libstdc++-v2, which + was included up to g++-2.95. The first version of libstdc++-v3 appeared + in g++-3.0. +Homepage: http://gcc.gnu.org/ +Original-Maintainer: Debian GCC Maintainers + +Package: libffi6 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 52 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: libffi +Version: 3.2.1-8 +Depends: libc6 (>= 2.14) +Description: Foreign Function Interface library runtime + A foreign function interface is the popular name for the interface that + allows code written in one language to call code written in another + language. +Original-Maintainer: Debian GCC Maintainers + +Package: libaudit-common +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 23 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Source: audit +Version: 1:2.8.2-1ubuntu1 +Replaces: libaudit0, libaudit1 (<< 1:2.2.1-2) +Breaks: libaudit0, libaudit1 (<< 1:2.2.1-2) +Conffiles: + /etc/libaudit.conf cdc703f9d27f0d980271a9e95d0f18b2 +Description: Dynamic library for security auditing - common files + The audit-libs package contains the dynamic libraries needed for + applications to use the audit framework. It is used to monitor systems for + security related events. + . + This package contains the libaudit.conf configuration file and the associated + manpage. +Homepage: https://people.redhat.com/sgrubb/audit/ +Original-Maintainer: Laurent Bigonville + +Package: findutils +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 580 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 4.6.0+git+20170828-2 +Pre-Depends: libc6 (>= 2.17), libselinux1 (>= 1.32) +Suggests: mlocate | locate +Breaks: binstats (<< 1.08-8.1), guilt (<< 0.36-0.2), libpython3.4-minimal (<< 3.4.4-2), libpython3.5-minimal (<< 3.5.1-3), lsat (<< 0.9.7.1-2.1), mc (<< 3:4.8.11-1), switchconf (<< 0.0.9-2.1) +Description: utilities for finding files--find, xargs + GNU findutils provides utilities to find files meeting specified + criteria and perform various actions on the files which are found. + This package contains 'find' and 'xargs'; however, 'locate' has + been split off into a separate package. +Original-Maintainer: Andreas Metzler +Homepage: https://savannah.gnu.org/projects/findutils/ + +Package: libpam0g +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 212 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: pam +Version: 1.1.8-3.6ubuntu2 +Replaces: libpam0g-util +Depends: libaudit1 (>= 1:2.2.1), libc6 (>= 2.14), debconf (>= 0.5) | debconf-2.0 +Suggests: libpam-doc +Description: Pluggable Authentication Modules library + Contains the shared library for Linux-PAM, a library that enables the + local system administrator to choose how applications authenticate users. + In other words, without rewriting or recompiling a PAM-aware application, + it is possible to switch between the authentication mechanism(s) it uses. + One may entirely upgrade the local authentication system without touching + the applications themselves. +Homepage: http://www.linux-pam.org/ +Original-Maintainer: Steve Langasek + +Package: libcap-ng0 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 36 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: libcap-ng +Version: 0.7.7-3.1 +Depends: libc6 (>= 2.8) +Description: An alternate POSIX capabilities library + This library implements the user-space interfaces to the POSIX + 1003.1e capabilities available in Linux kernels. These capabilities are + a partitioning of the all powerful root privilege into a set of distinct + privileges. + . + The libcap-ng library is intended to make programming with POSIX + capabilities much easier than the traditional libcap library. + . + This package contains header files and libraries for libcap-ng. +Original-Maintainer: Pierre Chifflier +Homepage: http://people.redhat.com/sgrubb/libcap-ng + +Package: libmount1 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 433 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: util-linux +Version: 2.31.1-0.4ubuntu3.1 +Depends: libblkid1 (>= 2.17.2), libc6 (>= 2.25), libselinux1 (>= 2.6-3~) +Description: device mounting library + This device mounting library is used by mount and umount helpers. +Original-Maintainer: LaMont Jones + +Package: login +Essential: yes +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 1212 +Maintainer: Ubuntu Developers +Architecture: amd64 +Source: shadow +Version: 1:4.5-1ubuntu1 +Replaces: manpages-de (<< 0.5-3), manpages-tr (<< 1.0.5), manpages-zh (<< 1.5.1-1) +Pre-Depends: libaudit1 (>= 1:2.2.1), libc6 (>= 2.14), libpam0g (>= 0.99.7.1), libpam-runtime, libpam-modules (>= 1.1.8-1) +Conflicts: amavisd-new (<< 2.3.3-8), backupninja (<< 0.9.3-5), echolot (<< 2.1.8-4), gnunet (<< 0.7.0c-2), python-4suite (<< 0.99cvs20060405-1) +Conffiles: + /etc/login.defs 2a8f6cd8e00f54df72dc345a23f9db20 + /etc/pam.d/login 1fd6cb4d4267a68148ee9973510a9d3e + /etc/pam.d/su ce6dcfda3b190a27a455bb38a45ff34a + /etc/securetty d0124b1d2fb22d4ac9a91aa02ae6d6db +Description: system login tools + These tools are required to be able to login and use your system. The + login program invokes your user shell and enables command execution. The + newgrp program is used to change your effective group ID (useful for + workgroup type situations). The su program allows changing your effective + user ID (useful being able to execute commands as another user). +Homepage: https://github.com/shadow-maint/shadow +Original-Maintainer: Shadow package maintainers + +Package: adduser +Status: install ok installed +Priority: important +Section: admin +Installed-Size: 624 +Maintainer: Ubuntu Core Developers +Architecture: all +Multi-Arch: foreign +Version: 3.116ubuntu1 +Depends: passwd, debconf (>= 0.5) | debconf-2.0 +Suggests: liblocale-gettext-perl, perl, ecryptfs-utils (>= 67-1) +Conffiles: + /etc/deluser.conf 773fb95e98a27947de4a95abb3d3f2a2 +Description: add and remove users and groups + This package includes the 'adduser' and 'deluser' commands for creating + and removing users. + . + - 'adduser' creates new users and groups and adds existing users to + existing groups; + - 'deluser' removes users and groups and removes users from a given + group. + . + Adding users with 'adduser' is much easier than adding them manually. + Adduser will choose appropriate UID and GID values, create a home + directory, copy skeletal user configuration, and automate setting + initial values for the user's password, real name and so on. + . + Deluser can back up and remove users' home directories + and mail spool or all the files they own on the system. + . + A custom script can be executed after each of the commands. + . + Development mailing list: + http://lists.alioth.debian.org/mailman/listinfo/adduser-devel/ +Homepage: http://alioth.debian.org/projects/adduser/ +Original-Maintainer: Debian Adduser Developers + +Package: libext2fs2 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 451 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: e2fsprogs +Version: 1.44.1-1 +Replaces: e2fslibs (<< 1.43.9-1~) +Provides: e2fslibs (= 1.44.1-1) +Depends: libc6 (>= 2.17) +Breaks: e2fslibs (<< 1.43.9-1~) +Description: ext2/ext3/ext4 file system libraries + The ext2, ext3 and ext4 file systems are successors of the original ext + ("extended") file system. They are the main file system types used for + hard disks on Debian and other Linux systems. + . + This package provides the ext2fs and e2p libraries, for userspace software + that directly accesses extended file systems. Programs that use libext2fs + include e2fsck, mke2fs, and tune2fs. Programs that use libe2p include + dumpe2fs, chattr, and lsattr. +Original-Maintainer: Theodore Y. Ts'o +Homepage: http://e2fsprogs.sourceforge.net + +Package: libacl1 +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 57 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: acl +Version: 2.2.52-3build1 +Depends: libattr1 (>= 1:2.4.46-8), libc6 (>= 2.14) +Conflicts: acl (<< 2.0.0), libacl1-kerberos4kth +Description: Access control list shared library + This package contains the libacl.so dynamic library containing + the POSIX 1003.1e draft standard 17 functions for manipulating + access control lists. +Original-Maintainer: Anibal Monsalve Salazar +Homepage: http://savannah.nongnu.org/projects/acl/ + +Package: ncurses-bin +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 576 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: ncurses +Version: 6.1-1ubuntu1.18.04 +Pre-Depends: libc6 (>= 2.14), libtinfo5 (>= 6.1) +Description: terminal-related programs and man pages + The ncurses library routines are a terminal-independent method of + updating character screens with reasonable optimization. + . + This package contains the programs used for manipulating the terminfo + database and individual terminfo entries, as well as some programs for + resetting terminals and such. +Homepage: https://invisible-island.net/ncurses/ +Original-Maintainer: Craig Small + +Package: libsepol1 +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 720 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: libsepol +Version: 2.7-1 +Depends: libc6 (>= 2.14) +Description: SELinux library for manipulating binary security policies + Security-enhanced Linux is a patch of the Linux kernel and a number + of utilities with enhanced security functionality designed to add + mandatory access controls to Linux. The Security-enhanced Linux + kernel contains new architectural components originally developed to + improve the security of the Flask operating system. These + architectural components provide general support for the enforcement + of many kinds of mandatory access control policies, including those + based on the concepts of Type Enforcement®, Role-based Access + Control, and Multi-level Security. + . + libsepol provides an API for the manipulation of SELinux binary policies. + It is used by checkpolicy (the policy compiler) and similar tools, as well + as by programs like load_policy that need to perform specific transformations + on binary policies such as customizing policy boolean settings. +Original-Maintainer: Debian SELinux maintainers +Homepage: http://userspace.selinuxproject.org/ + +Package: ubuntu-keyring +Status: install ok installed +Priority: important +Section: misc +Installed-Size: 42 +Maintainer: Dimitri John Ledkov +Architecture: all +Multi-Arch: foreign +Version: 2018.02.28 +Replaces: ubuntu-cloudimage-keyring (<< 2018.02.05) +Breaks: ubuntu-cloudimage-keyring (<< 2018.02.05) +Description: GnuPG keys of the Ubuntu archive + The Ubuntu project digitally signs its Release files. This package + contains the archive keys used for that. + +Package: libgcc1 +Status: install ok installed +Priority: required +Section: libs +Installed-Size: 112 +Maintainer: Ubuntu Core developers +Architecture: amd64 +Multi-Arch: same +Source: gcc-8 (8-20180414-1ubuntu2) +Version: 1:8-20180414-1ubuntu2 +Depends: gcc-8-base (= 8-20180414-1ubuntu2), libc6 (>= 2.14) +Breaks: gcc-4.3 (<< 4.3.6-1), gcc-4.4 (<< 4.4.6-4), gcc-4.5 (<< 4.5.3-2) +Description: GCC support library + Shared version of the support library, a library of internal subroutines + that GCC uses to overcome shortcomings of particular machines, or + special needs for some languages. +Homepage: http://gcc.gnu.org/ +Original-Maintainer: Debian GCC Maintainers + +Package: util-linux +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 3374 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 2.31.1-0.4ubuntu3.1 +Replaces: bash-completion (<< 1:2.1-4.1~), initscripts (<< 2.88dsf-59.2~), mount (<< 2.29.2-3~), s390-tools (<< 1.37.0-0ubuntu7~), sysvinit-utils (<< 2.88dsf-59.1~) +Depends: fdisk +Pre-Depends: libaudit1 (>= 1:2.2.1), libblkid1 (>= 2.31.1), libc6 (>= 2.25), libmount1 (>= 2.25), libpam0g (>= 0.99.7.1), libselinux1 (>= 2.6-3~), libsmartcols1 (>= 2.30.2), libsystemd0, libtinfo5 (>= 6), libudev1 (>= 183), libuuid1 (>= 2.16), zlib1g (>= 1:1.1.4) +Suggests: dosfstools, kbd | console-tools, util-linux-locales +Breaks: bash-completion (<< 1:2.1-4.1~), grml-debootstrap (<< 0.68), mount (<< 2.29.2-3~), s390-tools (<< 1.37.0-0ubuntu7~), sysvinit-utils (<< 2.88dsf-59.4~) +Conffiles: + /etc/init.d/hwclock.sh 1ca5c0743fa797ffa364db95bb8d8d8e + /etc/pam.d/runuser b8b44b045259525e0fae9e38fdb2aeeb + /etc/pam.d/runuser-l 2106ea05877e8913f34b2c77fa02be45 +Description: miscellaneous system utilities + This package contains a number of important utilities, most of which + are oriented towards maintenance of your system. Some of the more + important utilities included in this package allow you to view kernel + messages, create new filesystems, view block device information, + interface with real time clock, etc. +Original-Maintainer: LaMont Jones + +Package: sed +Essential: yes +Status: install ok installed +Priority: required +Section: utils +Installed-Size: 320 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 4.4-2 +Pre-Depends: libc6 (>= 2.14), libselinux1 (>= 1.32) +Description: GNU stream editor for filtering/transforming text + sed reads the specified files or the standard input if no + files are specified, makes editing changes according to a + list of commands, and writes the results to the standard + output. +Original-Maintainer: Clint Adams +Homepage: https://www.gnu.org/software/sed/ + +Package: libsmartcols1 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 287 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: same +Source: util-linux +Version: 2.31.1-0.4ubuntu3.1 +Depends: libc6 (>= 2.25) +Description: smart column output alignment library + This smart column output alignment library is used by fdisk utilities. +Original-Maintainer: LaMont Jones + +Package: libustr-1.0-1 +Status: install ok installed +Priority: optional +Section: libs +Installed-Size: 287 +Maintainer: Vaclav Ovsik +Architecture: amd64 +Multi-Arch: same +Source: ustr (1.0.4-3) +Version: 1.0.4-3+b2 +Depends: libc6 (>= 2.14) +Pre-Depends: multiarch-support +Description: Micro string library: shared library + ustr (Micro string library) is a string API for C. It has tiny overhead over + just plain strdup(), is much safer, is easier to use, is faster for many + operations, can be used with read-only or automatically allocated data. You + don't even need to link to the library to use it (so there are no + dependencies). + . + This package contains the shared library for ustr. +Homepage: http://www.and.org/ustr/ + +Package: git +Status: deinstall ok config-files +Priority: optional +Section: vcs +Installed-Size: 35396 +Maintainer: Gerrit Pape +Architecture: amd64 +Multi-Arch: foreign +Version: 1:2.20.1-2+deb10u3 +Config-Version: 1:2.20.1-2+deb10u3 +Replaces: git-core (<< 1:1.7.0.4-1.), gitweb (<< 1:1.7.4~rc1) + + diff --git a/pkg/fanal/analyzer/pkg/dpkg/testdata/dpkg_apt b/pkg/fanal/analyzer/pkg/dpkg/testdata/dpkg_apt new file mode 100644 index 000000000000..6cf7d5e92486 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/testdata/dpkg_apt @@ -0,0 +1,35 @@ +Package: apt +Status: install ok installed +Priority: important +Section: admin +Installed-Size: 3806 +Maintainer: Ubuntu Developers +Architecture: amd64 +Version: 1.6.3ubuntu0.1 +Replaces: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~) +Provides: apt-transport-https (= 1.6.3ubuntu0.1) +Depends: adduser, gpgv | gpgv2 | gpgv1, ubuntu-keyring, libapt-pkg5.0 (>= 1.6.3ubuntu0.1), libc6 (>= 2.15), libgcc1 (>= 1:3.0), libgnutls30 (>= 3.5.6), libseccomp2 (>= 1.0.1), libstdc++6 (>= 5.2) +Recommends: ca-certificates +Suggests: apt-doc, aptitude | synaptic | wajig, dpkg-dev (>= 1.17.2), gnupg | gnupg2 | gnupg1, powermgmt-base +Breaks: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~), aptitude (<< 0.8.10) +Conffiles: + /etc/apt/apt.conf.d/01-vendor-ubuntu 5232396660502461fc834c0a1229dbe4 + /etc/apt/apt.conf.d/01autoremove 735050108d38dbf99e933a9f647f6892 + /etc/cron.daily/apt-compat 49e9b2cfa17849700d4db735d04244f3 + /etc/kernel/postinst.d/apt-auto-removal 4ad976a68f045517cf4696cec7b8aa3a + /etc/logrotate.d/apt 179f2ed4f85cbaca12fa3d69c2a4a1c3 +Description: commandline package manager + This package provides commandline tools for searching and + managing as well as querying information about packages + as a low-level access to all features of the libapt-pkg library. + . + These include: + * apt-get for retrieval of packages and information about them + from authenticated sources and for installation, upgrade and + removal of packages together with their dependencies + * apt-cache for querying available information about installed + as well as installable packages + * apt-cdrom to use removable media as a source for packages + * apt-config as an interface to the configuration settings + * apt-key as an interface to manage authentication keys +Original-Maintainer: APT Development Team diff --git a/pkg/fanal/analyzer/pkg/dpkg/testdata/license-pattern-and-classifier-copyright b/pkg/fanal/analyzer/pkg/dpkg/testdata/license-pattern-and-classifier-copyright new file mode 100644 index 000000000000..80c06f2461d7 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/testdata/license-pattern-and-classifier-copyright @@ -0,0 +1,83 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: zlib +Upstream-Contact: zlib@gzip.org +Source: http://zlib.net/ +Comment: This is the pre-packaged Debian Linux version of the zlib compression + library. It was packaged by Michael Alan Dorman + from sources originally retrieved from ftp.uu.net in the directory + /pub/archiving/zip/zlib as the file zlib-1.0.4.tar.gz. + . + The deflate format used by zlib was defined by Phil Katz. The deflate + and zlib specifications were written by Peter Deutsch. Thanks to all the + people who reported problems and suggested various improvements in zlib; + they are too numerous to cite here. +Files-Excluded: + contrib/ada + contrib/amd64 + contrib/asm686 + contrib/blast + contrib/delphi + contrib/dotzlib + contrib/gcc_gvmat64 + contrib/infback9 + contrib/inflate86 + contrib/iostream + contrib/iostream2 + contrib/iostream3 + contrib/masmx64 + contrib/masmx86 + contrib/pascal + contrib/puff + contrib/testzlib + contrib/untgz + contrib/vstudio + doc/rfc1950.txt + doc/rfc1951.txt + doc/rfc1952.txt + +Files: * +Copyright: 1995-2013 Jean-loup Gailly and Mark Adler +License: GPL-1+ or Artistic, and BSD-4-clause-POWERDOG + +Files: amiga/Makefile.pup +Copyright: 1998 by Andreas R. Kleinert +License: Zlib + +Files: contrib/minizip/* +Copyright: 1998-2010 Gilles Vollant + 2007-2008 Even Rouault + 2009-2010 Mathias Svensson +License: Zlib + +Files: debian/* +Copyright: 2000-2017 Mark Brown +License: Zlib + +License: Zlib + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + . + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + . + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + . + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + . + If you use the zlib library in a product, we would appreciate *not* receiving + lengthy legal documents to sign. The sources are provided for free but without + warranty of any kind. The library has been entirely written by Jean-loup + Gailly and Mark Adler; it does not include third-party code. + . + If you redistribute modified sources, we would appreciate that you include in + the file ChangeLog history information documenting your changes. Please read + the FAQ for more information on the distribution of modified source versions. diff --git a/pkg/fanal/analyzer/pkg/dpkg/testdata/no-license-copyright b/pkg/fanal/analyzer/pkg/dpkg/testdata/no-license-copyright new file mode 100644 index 000000000000..c536ab78f3b6 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/testdata/no-license-copyright @@ -0,0 +1,10 @@ +This is the Debian prepackaged version of the Time Zone and Daylight +Saving Time Data. + +It was downloaded from http://www.iana.org/time-zones + +Upstream Author: The Internet Assigned Numbers Authority (IANA) +Commentary should be addressed to tz@iana.org + +Copyright: This database is in the public domain. + diff --git a/pkg/fanal/analyzer/pkg/dpkg/testdata/tar.list b/pkg/fanal/analyzer/pkg/dpkg/testdata/tar.list new file mode 100644 index 000000000000..e83a49eec87a --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/testdata/tar.list @@ -0,0 +1,28 @@ +/. +/bin +/bin/tar +/etc +/usr +/usr/lib +/usr/lib/mime +/usr/lib/mime/packages +/usr/lib/mime/packages/tar +/usr/sbin +/usr/sbin/rmt-tar +/usr/sbin/tarcat +/usr/share +/usr/share/doc +/usr/share/doc/tar +/usr/share/doc/tar/AUTHORS +/usr/share/doc/tar/NEWS.gz +/usr/share/doc/tar/README.Debian +/usr/share/doc/tar/THANKS.gz +/usr/share/doc/tar/changelog.Debian.gz +/usr/share/doc/tar/copyright +/usr/share/man +/usr/share/man/man1 +/usr/share/man/man1/tar.1.gz +/usr/share/man/man1/tarcat.1.gz +/usr/share/man/man8 +/usr/share/man/man8/rmt-tar.8.gz +/etc/rmt \ No newline at end of file diff --git a/pkg/fanal/analyzer/pkg/rpm/rpm.go b/pkg/fanal/analyzer/pkg/rpm/rpm.go new file mode 100644 index 000000000000..f3a52286e578 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/rpm/rpm.go @@ -0,0 +1,294 @@ +package rpm + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + + rpmdb "github.com/knqyf263/go-rpmdb/pkg" + "github.com/samber/lo" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/log" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +func init() { + analyzer.RegisterAnalyzer(&rpmPkgAnalyzer{}) +} + +const version = 3 + +var ( + requiredFiles = []string{ + // Berkeley DB + "usr/lib/sysimage/rpm/Packages", + "var/lib/rpm/Packages", + + // NDB + "usr/lib/sysimage/rpm/Packages.db", + "var/lib/rpm/Packages.db", + + // SQLite3 + "usr/lib/sysimage/rpm/rpmdb.sqlite", + "var/lib/rpm/rpmdb.sqlite", + } + + errUnexpectedNameFormat = xerrors.New("unexpected name format") +) + +var osVendors = []string{ + "Amazon Linux", // Amazon Linux 1 + "Amazon.com", // Amazon Linux 2 + "CentOS", // CentOS + "Fedora Project", // Fedora + "Oracle America", // Oracle Linux + "Red Hat", // Red Hat + "AlmaLinux", // AlmaLinux + "CloudLinux", // AlmaLinux + "VMware", // Photon OS + "SUSE", // SUSE Linux Enterprise + "openSUSE", // openSUSE + "Microsoft Corporation", // CBL-Mariner + "Rocky", // Rocky Linux +} + +type rpmPkgAnalyzer struct{} + +type RPMDB interface { + ListPackages() ([]*rpmdb.PackageInfo, error) +} + +func (a rpmPkgAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + parsedPkgs, installedFiles, err := a.parsePkgInfo(input.Content) + if err != nil { + return nil, xerrors.Errorf("failed to parse rpmdb: %w", err) + } + + return &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: input.FilePath, + Packages: parsedPkgs, + }, + }, + SystemInstalledFiles: installedFiles, + }, nil +} + +func (a rpmPkgAnalyzer) parsePkgInfo(rc io.Reader) (types.Packages, []string, error) { + filePath, err := writeToTempFile(rc) + if err != nil { + return nil, nil, xerrors.Errorf("temp file error: %w", err) + } + defer os.RemoveAll(filepath.Dir(filePath)) // Remove the temp dir + + // rpm-python 4.11.3 rpm-4.11.3-35.el7.src.rpm + // Extract binary package names because RHSA refers to binary package names. + db, err := rpmdb.Open(filePath) + if err != nil { + return nil, nil, xerrors.Errorf("failed to open RPM DB: %w", err) + } + defer db.Close() + + return a.listPkgs(db) +} + +func (a rpmPkgAnalyzer) listPkgs(db RPMDB) (types.Packages, []string, error) { + // equivalent: + // new version: rpm -qa --qf "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{SOURCERPM} %{ARCH}\n" + // old version: rpm -qa --qf "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{SOURCERPM} %{ARCH}\n" + pkgList, err := db.ListPackages() + if err != nil { + return nil, nil, xerrors.Errorf("failed to list packages: %w", err) + } + + var pkgs []types.Package + var installedFiles []string + provides := make(map[string]string) + for _, pkg := range pkgList { + arch := pkg.Arch + if arch == "" { + arch = "None" + } + + // parse source rpm + var srcName, srcVer, srcRel string + if pkg.SourceRpm != "(none)" && pkg.SourceRpm != "" { + // source epoch is not included in SOURCERPM + srcName, srcVer, srcRel, err = splitFileName(pkg.SourceRpm) + if err != nil { + log.Logger.Debugf("Invalid Source RPM Found: %s", pkg.SourceRpm) + } + } + + // Check if the package is vendor-provided. + // If the package is not provided by vendor, the installed files should not be skipped. + var files []string + if packageProvidedByVendor(pkg) { + files, err = pkg.InstalledFileNames() + if err != nil { + return nil, nil, xerrors.Errorf("unable to get installed files: %w", err) + } + + for i, file := range files { + files[i] = filepath.ToSlash(file) + } + } + + // RPM DB uses MD5 digest + // https://rpm-software-management.github.io/rpm/manual/tags.html#signatures-and-digests + var d digest.Digest + if pkg.SigMD5 != "" { + d = digest.NewDigestFromString(digest.MD5, pkg.SigMD5) + } + + var licenses []string + if pkg.License != "" { + licenses = []string{pkg.License} + } + + p := types.Package{ + ID: fmt.Sprintf("%s@%s-%s.%s", pkg.Name, pkg.Version, pkg.Release, pkg.Arch), + Name: pkg.Name, + Epoch: pkg.EpochNum(), + Version: pkg.Version, + Release: pkg.Release, + Arch: arch, + SrcName: srcName, + SrcEpoch: pkg.EpochNum(), // NOTE: use epoch of binary package as epoch of src package + SrcVersion: srcVer, + SrcRelease: srcRel, + Modularitylabel: pkg.Modularitylabel, + Licenses: licenses, + DependsOn: pkg.Requires, // Will be replaced with package IDs + Maintainer: pkg.Vendor, + Digest: d, + InstalledFiles: files, + } + pkgs = append(pkgs, p) + installedFiles = append(installedFiles, files...) + + // It contains mappings between package-providing files and package IDs + // e.g. + // "libc.so.6()(64bit)" => "glibc-2.12-1.212.el6.x86_64" + // "rtld(GNU_HASH)" => "glibc-2.12-1.212.el6.x86_64" + for _, provide := range pkg.Provides { + provides[provide] = p.ID + } + } + + // Replace required files with package IDs + consolidateDependencies(pkgs, provides) + + return pkgs, installedFiles, nil +} + +func (a rpmPkgAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return utils.StringInSlice(filePath, requiredFiles) +} + +func (a rpmPkgAnalyzer) Type() analyzer.Type { + return analyzer.TypeRpm +} + +func (a rpmPkgAnalyzer) Version() int { + return version +} + +// splitFileName returns a name, version, release, epoch, arch: +// +// e.g. +// foo-1.0-1.i386.rpm => foo, 1.0, 1, i386 +// 1:bar-9-123a.ia64.rpm => bar, 9, 123a, 1, ia64 +// +// https://github.com/rpm-software-management/yum/blob/043e869b08126c1b24e392f809c9f6871344c60d/rpmUtils/miscutils.py#L301 +func splitFileName(filename string) (name, ver, rel string, err error) { + filename = strings.TrimSuffix(filename, ".rpm") + + archIndex := strings.LastIndex(filename, ".") + if archIndex == -1 { + return "", "", "", errUnexpectedNameFormat + } + + relIndex := strings.LastIndex(filename[:archIndex], "-") + if relIndex == -1 { + return "", "", "", errUnexpectedNameFormat + } + rel = filename[relIndex+1 : archIndex] + + verIndex := strings.LastIndex(filename[:relIndex], "-") + if verIndex == -1 { + return "", "", "", errUnexpectedNameFormat + } + ver = filename[verIndex+1 : relIndex] + + name = filename[:verIndex] + return name, ver, rel, nil +} + +func packageProvidedByVendor(pkg *rpmdb.PackageInfo) bool { + if pkg.Vendor == "" { + // Official Amazon packages may not contain `Vendor` field: + // https://github.com/aquasecurity/trivy/issues/5887 + return strings.Contains(pkg.Release, "amzn") + } + + for _, vendor := range osVendors { + if strings.HasPrefix(pkg.Vendor, vendor) { + return true + } + } + + return false +} + +func writeToTempFile(rc io.Reader) (string, error) { + tmpDir, err := os.MkdirTemp("", "rpm") + if err != nil { + return "", xerrors.Errorf("failed to create a temp dir: %w", err) + } + + filePath := filepath.Join(tmpDir, "Packages") + f, err := os.Create(filePath) + if err != nil { + return "", xerrors.Errorf("failed to create a package file: %w", err) + } + + if _, err = io.Copy(f, rc); err != nil { + return "", xerrors.Errorf("failed to copy a package file: %w", err) + } + + // The temp file must be closed before being opened as Berkeley DB. + if err = f.Close(); err != nil { + return "", xerrors.Errorf("failed to close a temp file: %w", err) + } + + return filePath, nil +} + +func consolidateDependencies(pkgs []types.Package, provides map[string]string) { + for i := range pkgs { + // e.g. "libc.so.6()(64bit)" => "glibc-2.12-1.212.el6.x86_64" + pkgs[i].DependsOn = lo.FilterMap(pkgs[i].DependsOn, func(d string, _ int) (string, bool) { + if pkgID, ok := provides[d]; ok && pkgs[i].ID != pkgID { + return pkgID, true + } + return "", false + }) + sort.Strings(pkgs[i].DependsOn) + pkgs[i].DependsOn = slices.Compact(pkgs[i].DependsOn) + + if len(pkgs[i].DependsOn) == 0 { + pkgs[i].DependsOn = nil + } + } +} diff --git a/pkg/fanal/analyzer/pkg/rpm/rpm_test.go b/pkg/fanal/analyzer/pkg/rpm/rpm_test.go new file mode 100644 index 000000000000..2ef2a0b46219 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/rpm/rpm_test.go @@ -0,0 +1,272 @@ +package rpm + +import ( + "context" + "errors" + "os" + "strings" + "testing" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + rpmdb "github.com/knqyf263/go-rpmdb/pkg" + "github.com/samber/lo" + "github.com/stretchr/testify/require" + + "github.com/stretchr/testify/assert" +) + +type mockRPMDB struct { + packages []*rpmdb.PackageInfo + err error +} + +func (m *mockRPMDB) ListPackages() ([]*rpmdb.PackageInfo, error) { + if m.err != nil { + return nil, m.err + } + return m.packages, nil +} + +func Test_rpmPkgAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + input analyzer.AnalysisInput + want int + wantErr string + }{ + { + name: "valid", + input: analyzer.AnalysisInput{ + FilePath: "testdata/valid", + Content: lo.Must(os.Open("testdata/valid")), + }, + want: 1, + }, + { + name: "broken", + input: analyzer.AnalysisInput{ + FilePath: "testdata/valid", + Content: strings.NewReader("broken"), + }, + want: 0, + wantErr: "unexpected EOF", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := rpmPkgAnalyzer{} + got, err := a.Analyze(context.Background(), tt.input) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + assert.Len(t, got.PackageInfos, tt.want) + }) + } +} + +func Test_splitFileName(t *testing.T) { + tests := []struct { + name string + filename string + wantName string + wantVer string + wantRel string + wantErr bool + }{ + { + name: "valid name", + filename: "glibc-2.17-307.el7.1.src.rpm", + wantName: "glibc", + wantVer: "2.17", + wantRel: "307.el7.1", + wantErr: false, + }, + { + name: "invalid name", + filename: "elasticsearch-5.6.16-1-src.rpm", + wantName: "", + wantVer: "", + wantRel: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotName, gotVer, gotRel, err := splitFileName(tt.filename) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.wantName, gotName) + assert.Equal(t, tt.wantVer, gotVer) + assert.Equal(t, tt.wantRel, gotRel) + }) + } +} + +func Test_rpmPkgAnalyzer_listPkgs(t *testing.T) { + type mock struct { + packages []*rpmdb.PackageInfo + err error + } + tests := []struct { + name string + mock mock + wantPkgs types.Packages + wantFiles []string + wantErr string + }{ + { + name: "normal", + mock: mock{ + packages: []*rpmdb.PackageInfo{ + { + Name: "glibc", + Version: "2.17", + Release: "307.el7.1", + Arch: "x86_64", + SourceRpm: "glibc-2.17-317.el7.src.rpm", + DirNames: []string{"/etc", "/lib64"}, + DirIndexes: []int32{0, 0, 1}, + BaseNames: []string{ + "ld.so.conf", + "rpc", + "libm-2.27.so", + }, + Vendor: "Red Hat", + }, + }, + }, + wantPkgs: types.Packages{ + { + ID: "glibc@2.17-307.el7.1.x86_64", + Name: "glibc", + Version: "2.17", + Release: "307.el7.1", + Arch: "x86_64", + SrcName: "glibc", + SrcVersion: "2.17", + SrcRelease: "317.el7", + Maintainer: "Red Hat", + InstalledFiles: []string{ + "/etc/ld.so.conf", + "/etc/rpc", + "/lib64/libm-2.27.so", + }, + }, + }, + wantFiles: []string{ + "/etc/ld.so.conf", + "/etc/rpc", + "/lib64/libm-2.27.so", + }, + }, + { + name: "Amazon official package without `Vendor` field", + mock: mock{ + packages: []*rpmdb.PackageInfo{ + { + Name: "curl-minimal", + Version: "8.3.0", + Release: "1.amzn2023.0.2", + Arch: "aarch64", + SourceRpm: "curl-8.3.0-1.amzn2023.0.2.src.rpm", + DirNames: []string{ + "/usr/bin/", + "/usr/lib/", + "/usr/lib/.build-id/", + "/usr/lib/.build-id/aa/", + "/usr/share/man/man1/", + }, + DirIndexes: []int32{0, 1, 2, 3, 4}, + BaseNames: []string{ + "curl", + ".build-id", + "aa", + "d987ea9bc1c73706d12c7a143ee792117851ff", + "curl.1.gz", + }, + Vendor: "", + }, + }, + }, + wantPkgs: types.Packages{ + { + ID: "curl-minimal@8.3.0-1.amzn2023.0.2.aarch64", + Name: "curl-minimal", + Version: "8.3.0", + Release: "1.amzn2023.0.2", + Arch: "aarch64", + SrcName: "curl", + SrcVersion: "8.3.0", + SrcRelease: "1.amzn2023.0.2", + InstalledFiles: []string{ + "/usr/bin/curl", + "/usr/lib/.build-id", + "/usr/lib/.build-id/aa", + "/usr/lib/.build-id/aa/d987ea9bc1c73706d12c7a143ee792117851ff", + "/usr/share/man/man1/curl.1.gz", + }, + }, + }, + wantFiles: []string{ + "/usr/bin/curl", + "/usr/lib/.build-id", + "/usr/lib/.build-id/aa", + "/usr/lib/.build-id/aa/d987ea9bc1c73706d12c7a143ee792117851ff", + "/usr/share/man/man1/curl.1.gz", + }, + }, + { + name: "invalid source rpm", + mock: mock{ + packages: []*rpmdb.PackageInfo{ + { + Name: "glibc", + Version: "2.17", + Release: "307.el7.1", + Arch: "x86_64", + SourceRpm: "invalid", + }, + }, + }, + wantPkgs: types.Packages{ + { + ID: "glibc@2.17-307.el7.1.x86_64", + Name: "glibc", + Version: "2.17", + Release: "307.el7.1", + Arch: "x86_64", + }, + }, + }, + { + name: "sad path", + mock: mock{ + err: errors.New("unexpected error"), + }, + wantErr: "unexpected error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &mockRPMDB{ + packages: tt.mock.packages, + err: tt.mock.err, + } + + a := rpmPkgAnalyzer{} + gotPkgs, gotFiles, err := a.listPkgs(m) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + assert.Equal(t, tt.wantPkgs, gotPkgs) + assert.Equal(t, tt.wantFiles, gotFiles) + }) + } +} diff --git a/pkg/fanal/analyzer/pkg/rpm/rpmqa.go b/pkg/fanal/analyzer/pkg/rpm/rpmqa.go new file mode 100644 index 000000000000..83b06f16823b --- /dev/null +++ b/pkg/fanal/analyzer/pkg/rpm/rpmqa.go @@ -0,0 +1,94 @@ +package rpm + +import ( + "bufio" + "context" + "os" + "strings" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +func init() { + analyzer.RegisterAnalyzer(&rpmqaPkgAnalyzer{}) +} + +const versionRpmqa = 1 + +var ( + // For CBL-Mariner Distroless + requiredRpmqaFiles = []string{"var/lib/rpmmanifest/container-manifest-2"} +) + +// rpmqaPkgAnalyzer parses the output of +// "rpm -qa --qf %{NAME}\t%{VERSION}-%{RELEASE}\t%{INSTALLTIME}\t%{BUILDTIME}\t%{VENDOR}\t(none)\t%{SIZE}\t%{ARCH}\t%{EPOCHNUM}\t%{SOURCERPM}". +type rpmqaPkgAnalyzer struct{} + +func (a rpmqaPkgAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + pkgs, err := a.parseRpmqaManifest(input.Content) + if err != nil { + return nil, xerrors.Errorf("failed to parse rpmqa manifest: %w", err) + } + return &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: input.FilePath, + Packages: pkgs, + }, + }, + }, nil +} + +func (a rpmqaPkgAnalyzer) parseRpmqaManifest(r xio.ReadSeekerAt) ([]types.Package, error) { + var pkgs []types.Package + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + var name, ver, rel, sourceRpm, arch string + // %{NAME}\t%{VERSION}-%{RELEASE}\t%{INSTALLTIME}\t%{BUILDTIME}\t%{VENDOR}\t(none)\t%{SIZE}\t%{ARCH}\t%{EPOCHNUM}\t%{SOURCERPM} + s := strings.Split(line, "\t") + if len(s) != 10 { + return nil, xerrors.Errorf("failed to parse a line (%s)", line) + } + name = s[0] + arch = s[7] + sourceRpm = s[9] + if verRel := strings.Split(s[1], "-"); len(verRel) == 2 { + ver = verRel[0] + rel = verRel[1] + } else { + return nil, xerrors.Errorf("failed to split a version (%s)", s[1]) + } + srcName, srcVer, srcRel, err := splitFileName(sourceRpm) + if err != nil { + return nil, xerrors.Errorf("failed to split source rpm: %w", err) + } + pkgs = append(pkgs, types.Package{ + Name: name, + Version: ver, + Release: rel, + Arch: arch, + SrcName: srcName, + SrcVersion: srcVer, + SrcRelease: srcRel, + }) + } + return pkgs, nil +} + +func (a rpmqaPkgAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return slices.Contains(requiredRpmqaFiles, filePath) +} + +func (a rpmqaPkgAnalyzer) Type() analyzer.Type { + return analyzer.TypeRpmqa +} + +func (a rpmqaPkgAnalyzer) Version() int { + return versionRpmqa +} diff --git a/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go b/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go new file mode 100644 index 000000000000..1e01535db900 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go @@ -0,0 +1,74 @@ +package rpm + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestParseMarinerDistrolessManifest(t *testing.T) { + tests := []struct { + name string + content string + wantPkgs []types.Package + wantErr string + }{ + { + name: "happy path", + content: `mariner-release 2.0-12.cm2 1653816591 1653753130 Microsoft Corporation (none) 580 noarch 0 mariner-release-2.0-12.cm2.src.rpm +filesystem 1.1-9.cm2 1653816591 1653628924 Microsoft Corporation (none) 7596 x86_64 0 filesystem-1.1-9.cm2.src.rpm +glibc 2.35-2.cm2 1653816591 1653628955 Microsoft Corporation (none) 10855265 x86_64 0 glibc-2.35-2.cm2.src.rpm`, + wantPkgs: []types.Package{ + { + Name: "mariner-release", + Version: "2.0", + Release: "12.cm2", + Arch: "noarch", + SrcName: "mariner-release", + SrcVersion: "2.0", + SrcRelease: "12.cm2", + }, + { + Name: "filesystem", + Version: "1.1", + Release: "9.cm2", + Arch: "x86_64", + SrcName: "filesystem", + SrcVersion: "1.1", + SrcRelease: "9.cm2", + }, + { + Name: "glibc", + Version: "2.35", + Release: "2.cm2", + Arch: "x86_64", + SrcName: "glibc", + SrcVersion: "2.35", + SrcRelease: "2.cm2", + }, + }, + }, + { + name: "sab path", + content: "filesystem\t1.1-7.cm1\t1653164283\t1599428094", + wantErr: "failed to parse a line (filesystem\t1.1-7.cm1\t1653164283\t1599428094)", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + a := rpmqaPkgAnalyzer{} + result, err := a.parseRpmqaManifest(strings.NewReader(test.content)) + if test.wantErr != "" { + assert.NotNil(t, err) + assert.Equal(t, test.wantErr, err.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, test.wantPkgs, result) + } + }) + } +} diff --git a/pkg/fanal/analyzer/pkg/rpm/testdata/valid b/pkg/fanal/analyzer/pkg/rpm/testdata/valid new file mode 100644 index 000000000000..35a61a826c70 Binary files /dev/null and b/pkg/fanal/analyzer/pkg/rpm/testdata/valid differ diff --git a/pkg/fanal/analyzer/repo/apk/apk.go b/pkg/fanal/analyzer/repo/apk/apk.go new file mode 100644 index 000000000000..454710d6841b --- /dev/null +++ b/pkg/fanal/analyzer/repo/apk/apk.go @@ -0,0 +1,98 @@ +package apk + +import ( + "bufio" + "context" + "os" + "regexp" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + ver "github.com/aquasecurity/go-version/pkg/version" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&apkRepoAnalyzer{}) +} + +const version = 1 +const edgeVersion = "edge" + +var ( + requiredFiles = []string{"etc/apk/repositories"} + urlParseRegexp = regexp.MustCompile(`(https*|ftp)://[0-9A-Za-z.-]+/([A-Za-z]+)/v?([0-9A-Za-z_.-]+)/`) +) + +type apkRepoAnalyzer struct{} + +func (a apkRepoAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + scanner := bufio.NewScanner(input.Content) + var osFamily types.OSType + var repoVer string + for scanner.Scan() { + line := scanner.Text() + + m := urlParseRegexp.FindStringSubmatch(line) + if len(m) != 4 { + continue + } + + newOSFamily := types.OSType(m[2]) + newVersion := m[3] + + // Find OS Family + if osFamily != "" && osFamily != newOSFamily { + return nil, xerrors.Errorf("mixing different distributions in etc/apk/repositories: %s != %s", osFamily, newOSFamily) + } + osFamily = newOSFamily + + // Find max Release version + switch { + case repoVer == "": + repoVer = newVersion + case repoVer == edgeVersion || newVersion == edgeVersion: + repoVer = edgeVersion + default: + oldVer, err := ver.Parse(repoVer) + if err != nil { + continue + } + newVer, err := ver.Parse(newVersion) + if err != nil { + continue + } + + // Take the maximum version in apk repositories + if newVer.GreaterThan(oldVer) { + repoVer = newVersion + } + } + } + + // Currently, we support only Alpine Linux in apk repositories. + if osFamily != types.Alpine || repoVer == "" { + return nil, nil + } + + return &analyzer.AnalysisResult{ + Repository: &types.Repository{ + Family: osFamily, + Release: repoVer, + }, + }, nil +} + +func (a apkRepoAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return slices.Contains(requiredFiles, filePath) +} + +func (a apkRepoAnalyzer) Type() analyzer.Type { + return analyzer.TypeApkRepo +} + +func (a apkRepoAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/repo/apk/apk_test.go b/pkg/fanal/analyzer/repo/apk/apk_test.go new file mode 100644 index 000000000000..daf60febe639 --- /dev/null +++ b/pkg/fanal/analyzer/repo/apk/apk_test.go @@ -0,0 +1,163 @@ +package apk + +import ( + "context" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_apkRepoAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + input analyzer.AnalysisInput + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "alpine", + input: analyzer.AnalysisInput{ + FilePath: "/etc/apk/repositories", + Content: strings.NewReader("http://nl.alpinelinux.org/alpine/v3.7/main"), + }, + want: &analyzer.AnalysisResult{ + Repository: &types.Repository{ + Family: types.Alpine, + Release: "3.7", + }, + }, + }, + { + name: "adelie", + input: analyzer.AnalysisInput{ + FilePath: "/etc/apk/repositories", + Content: strings.NewReader("https://distfiles.adelielinux.org/adelie/1.0-beta4/system/"), + }, + want: nil, + }, + { + name: "repository has 'http' schema", + input: analyzer.AnalysisInput{ + FilePath: "/etc/apk/repositories", + Content: strings.NewReader("http://nl.alpinelinux.org/alpine/v3.7/main"), + }, + want: &analyzer.AnalysisResult{ + Repository: &types.Repository{ + Family: types.Alpine, + Release: "3.7", + }, + }, + }, + { + name: "repository has 'https' schema", + input: analyzer.AnalysisInput{ + FilePath: "/etc/apk/repositories", + Content: strings.NewReader("https://dl-cdn.alpinelinux.org/alpine/v3.15/main"), + }, + want: &analyzer.AnalysisResult{ + Repository: &types.Repository{ + Family: types.Alpine, + Release: "3.15", + }, + }, + }, + { + name: "repository has 'ftp' schema", + input: analyzer.AnalysisInput{ + FilePath: "/etc/apk/repositories", + Content: strings.NewReader("ftp://dl-3.alpinelinux.org/alpine/v2.6/main"), + }, + want: &analyzer.AnalysisResult{ + Repository: &types.Repository{ + Family: types.Alpine, + Release: "2.6", + }, + }, + }, + { + name: "edge version", + input: analyzer.AnalysisInput{ + FilePath: "/etc/apk/repositories", + Content: strings.NewReader("https://dl-cdn.alpinelinux.org/alpine/edge/main"), + }, + want: &analyzer.AnalysisResult{ + Repository: &types.Repository{ + Family: types.Alpine, + Release: "edge", + }, + }, + }, + { + name: "happy path. 'etc/apk/repositories' contains some line with v* versions", + input: analyzer.AnalysisInput{ + FilePath: "/etc/apk/repositories", + Content: strings.NewReader(`https://dl-cdn.alpinelinux.org/alpine/v3.1/main + +https://dl-cdn.alpinelinux.org/alpine/v3.10/main +`), + }, + want: &analyzer.AnalysisResult{ + Repository: &types.Repository{ + Family: types.Alpine, + Release: "3.10", + }, + }, + }, + { + name: "multiple v* versions", + input: analyzer.AnalysisInput{ + FilePath: "/etc/apk/repositories", + Content: strings.NewReader(`https://dl-cdn.alpinelinux.org/alpine/v3.10/main +https://dl-cdn.alpinelinux.org/alpine/v3.1/main +`), + }, + want: &analyzer.AnalysisResult{ + Repository: &types.Repository{ + Family: types.Alpine, + Release: "3.10", + }, + }, + }, + { + name: "multiple v* and edge versions", + input: analyzer.AnalysisInput{ + FilePath: "/etc/apk/repositories", + Content: strings.NewReader(`https://dl-cdn.alpinelinux.org/alpine/edge/main +https://dl-cdn.alpinelinux.org/alpine/v3.10/main +`), + }, + want: &analyzer.AnalysisResult{ + Repository: &types.Repository{ + Family: types.Alpine, + Release: "edge", + }, + }, + }, + { + name: "sad path", + input: analyzer.AnalysisInput{ + FilePath: "/etc/apk/repositories", + Content: strings.NewReader("https://dl-cdn.alpinelinux.org/alpine//edge/main"), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + a := apkRepoAnalyzer{} + got, err := a.Analyze(context.Background(), test.input) + + if test.wantErr != "" { + assert.Error(t, err) + assert.Equal(t, test.wantErr, err.Error()) + return + } + assert.NoError(t, err) + assert.Equal(t, test.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/sbom/sbom.go b/pkg/fanal/analyzer/sbom/sbom.go new file mode 100644 index 000000000000..efb9829a1593 --- /dev/null +++ b/pkg/fanal/analyzer/sbom/sbom.go @@ -0,0 +1,93 @@ +package sbom + +import ( + "context" + "os" + "path" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/sbom" + "github.com/aquasecurity/trivy/pkg/types" +) + +func init() { + analyzer.RegisterAnalyzer(&sbomAnalyzer{}) +} + +const version = 1 + +var requiredSuffixes = []string{ + ".spdx", + ".spdx.json", + ".cdx", + ".cdx.json", +} + +type sbomAnalyzer struct{} + +func (a sbomAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + // Format auto-detection + format, err := sbom.DetectFormat(input.Content) + if err != nil { + return nil, xerrors.Errorf("failed to detect SBOM format: %w", err) + } + + bom, err := sbom.Decode(input.Content, format) + if err != nil { + return nil, xerrors.Errorf("SBOM decode error: %w", err) + } + + // Bitnami images + // SPDX files are located under the /opt/bitnami/ directory + // and named with the pattern .spdx-.spdx + // ref: https://github.com/bitnami/vulndb#how-to-consume-this-cve-feed + if strings.HasPrefix(input.FilePath, "opt/bitnami/") { + handleBitnamiImages(path.Dir(input.FilePath), bom) + } + + return &analyzer.AnalysisResult{ + PackageInfos: bom.Packages, + Applications: bom.Applications, + }, nil +} + +func (a sbomAnalyzer) Required(filePath string, _ os.FileInfo) bool { + for _, suffix := range requiredSuffixes { + if strings.HasSuffix(filePath, suffix) { + return true + } + } + return false +} + +func (a sbomAnalyzer) Type() analyzer.Type { + return analyzer.TypeSBOM +} + +func (a sbomAnalyzer) Version() int { + return version +} + +func handleBitnamiImages(componentPath string, bom types.SBOM) { + for i, app := range bom.Applications { + if app.Type == ftypes.Bitnami { + // Set the component dir path to the application + bom.Applications[i].FilePath = componentPath + // Either Application.FilePath or Application.Libraries[].FilePath should be set + continue + } + + for j, pkg := range app.Libraries { + // Set the absolute path since SBOM in Bitnami images contain a relative path + // e.g. modules/apm/elastic-apm-agent-1.36.0.jar + // => opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar + // If the file path is empty, the file path will be set to the component dir path. + filePath := path.Join(componentPath, pkg.FilePath) + bom.Applications[i].Libraries[j].FilePath = filePath + } + } +} diff --git a/pkg/fanal/analyzer/sbom/sbom_test.go b/pkg/fanal/analyzer/sbom/sbom_test.go new file mode 100644 index 000000000000..3bcb619d402b --- /dev/null +++ b/pkg/fanal/analyzer/sbom/sbom_test.go @@ -0,0 +1,307 @@ +package sbom + +import ( + "context" + "github.com/package-url/packageurl-go" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_sbomAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + file string + filePath string + want *analyzer.AnalysisResult + wantErr require.ErrorAssertionFunc + }{ + { + name: "valid elasticsearch spdx file", + file: "testdata/elasticsearch.spdx.json", + filePath: "opt/bitnami/elasticsearch/.spdx-elasticsearch.spdx", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Jar, + Libraries: types.Packages{ + { + ID: "co.elastic.apm:apm-agent:1.36.0", + Name: "co.elastic.apm:apm-agent", + Version: "1.36.0", + FilePath: "opt/bitnami/elasticsearch", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "co.elastic.apm", + Name: "apm-agent", + Version: "1.36.0", + }, + }, + }, + { + ID: "co.elastic.apm:apm-agent-cached-lookup-key:1.36.0", + Name: "co.elastic.apm:apm-agent-cached-lookup-key", + Version: "1.36.0", + FilePath: "opt/bitnami/elasticsearch", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "co.elastic.apm", + Name: "apm-agent-cached-lookup-key", + Version: "1.36.0", + }, + }, + }, + { + ID: "co.elastic.apm:apm-agent-common:1.36.0", + Name: "co.elastic.apm:apm-agent-common", + Version: "1.36.0", + FilePath: "opt/bitnami/elasticsearch", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "co.elastic.apm", + Name: "apm-agent-common", + Version: "1.36.0", + }, + }, + }, + { + ID: "co.elastic.apm:apm-agent-core:1.36.0", + Name: "co.elastic.apm:apm-agent-core", + Version: "1.36.0", + FilePath: "opt/bitnami/elasticsearch", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "co.elastic.apm", + Name: "apm-agent-core", + Version: "1.36.0", + }, + }, + }, + }, + }, + { + Type: types.Bitnami, + FilePath: "opt/bitnami/elasticsearch", + Libraries: types.Packages{ + { + ID: "Elasticsearch@8.9.1", + Name: "Elasticsearch", + Version: "8.9.1", + Arch: "arm64", + Licenses: []string{"Elastic-2.0"}, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeBitnami, + Name: "elasticsearch", + Version: "8.9.1", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "arm64", + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: require.NoError, + }, + { + name: "valid elasticsearch cdx file", + file: "testdata/cdx.json", + filePath: "opt/bitnami/elasticsearch/.spdx-elasticsearch.cdx", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Jar, + Libraries: types.Packages{ + { + FilePath: "opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar", + ID: "co.elastic.apm:apm-agent:1.36.0", + Name: "co.elastic.apm:apm-agent", + Version: "1.36.0", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "co.elastic.apm", + Name: "apm-agent", + Version: "1.36.0", + }, + BOMRef: "pkg:maven/co.elastic.apm/apm-agent@1.36.0", + }, + }, + { + FilePath: "opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar", + ID: "co.elastic.apm:apm-agent-cached-lookup-key:1.36.0", + Name: "co.elastic.apm:apm-agent-cached-lookup-key", + Version: "1.36.0", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "co.elastic.apm", + Name: "apm-agent-cached-lookup-key", + Version: "1.36.0", + }, + BOMRef: "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0", + }, + }, + }, + }, + }, + }, + wantErr: require.NoError, + }, + { + name: "valid postgresql spdx file", + file: "testdata/postgresql.spdx.json", + filePath: "opt/bitnami/postgresql/.spdx-postgresql.spdx", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Bitnami, + FilePath: "opt/bitnami/postgresql", + Libraries: types.Packages{ + { + ID: "GDAL@3.7.1", + Name: "GDAL", + Version: "3.7.1", + Licenses: []string{"MIT"}, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeBitnami, + Name: "gdal", + Version: "3.7.1", + }, + }, + }, + { + ID: "GEOS@3.8.3", + Name: "GEOS", + Version: "3.8.3", + Licenses: []string{"LGPL-2.1-only"}, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeBitnami, + Name: "geos", + Version: "3.8.3", + }, + }, + }, + { + ID: "PostgreSQL@15.3.0", + Name: "PostgreSQL", + Version: "15.3.0", + Licenses: []string{"PostgreSQL"}, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeBitnami, + Name: "postgresql", + Version: "15.3.0", + }, + }, + DependsOn: []string{ + "GEOS@3.8.3", + "Proj@6.3.2", + "GDAL@3.7.1", + }, + }, + { + ID: "Proj@6.3.2", + Name: "Proj", + Version: "6.3.2", + Licenses: []string{"MIT"}, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeBitnami, + Name: "proj", + Version: "6.3.2", + }, + }, + }, + }, + }, + }, + }, + wantErr: require.NoError, + }, + { + name: "invalid spdx file", + file: "testdata/invalid_spdx.json", + filePath: "opt/bitnami/elasticsearch/.spdx-elasticsearch.spdx", + want: nil, + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + defer f.Close() + + a := sbomAnalyzer{} + got, err := a.Analyze(context.Background(), analyzer.AnalysisInput{ + FilePath: tt.filePath, + Content: f, + }) + tt.wantErr(t, err) + + if got != nil { + got.Sort() + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_packagingAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "cdx", + filePath: "/test/result.cdx", + want: true, + }, + { + name: "spdx", + filePath: "/test/result.spdx", + want: true, + }, + { + name: "cdx.json", + filePath: "/test/result.cdx.json", + want: true, + }, + { + name: "spdx.json", + filePath: "/test/result.spdx.json", + want: true, + }, + { + name: "json", + filePath: "/test/result.json", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := sbomAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/sbom/testdata/cdx.json b/pkg/fanal/analyzer/sbom/testdata/cdx.json new file mode 100644 index 000000000000..a006bf7053e2 --- /dev/null +++ b/pkg/fanal/analyzer/sbom/testdata/cdx.json @@ -0,0 +1,61 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:73f26314-e86a-4f5a-befc-f853a15b64e7", + "version": 1, + "metadata": { + "timestamp": "2023-06-01T13:10:23+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "0.41.0-80-g1c03982fe" + } + ] + }, + "component": { + "bom-ref": "pkg:oci/elasticsearch@sha256:d4b68b602eb3d92ea3256886761752ae1159dc01fd391f4c4a87ebf6ba9d3895?repository_url=index.docker.io%2Fbitnami%2Felasticsearch\u0026arch=arm64", + "type": "container", + "name": "bitnami/elasticsearch:8.7.1-debian-11-r7", + "purl": "pkg:oci/elasticsearch@sha256:d4b68b602eb3d92ea3256886761752ae1159dc01fd391f4c4a87ebf6ba9d3895?repository_url=index.docker.io%2Fbitnami%2Felasticsearch\u0026arch=arm64" + } + }, + "components": [ + { + "bom-ref": "pkg:maven/co.elastic.apm/apm-agent@1.36.0", + "type": "library", + "name": "co.elastic.apm:apm-agent", + "version": "1.36.0", + "purl": "pkg:maven/co.elastic.apm/apm-agent@1.36.0", + "properties": [ + { + "name": "aquasecurity:trivy:PkgType", + "value": "jar" + }, + { + "name": "aquasecurity:trivy:FilePath", + "value": "modules/apm/elastic-apm-agent-1.36.0.jar" + } + ] + }, + { + "bom-ref": "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0", + "type": "library", + "name": "co.elastic.apm:apm-agent-cached-lookup-key", + "version": "1.36.0", + "purl": "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0", + "properties": [ + { + "name": "aquasecurity:trivy:PkgType", + "value": "jar" + }, + { + "name": "aquasecurity:trivy:FilePath", + "value": "modules/apm/elastic-apm-agent-1.36.0.jar" + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/sbom/testdata/elasticsearch.spdx.json b/pkg/fanal/analyzer/sbom/testdata/elasticsearch.spdx.json new file mode 100644 index 000000000000..155d7de903f2 --- /dev/null +++ b/pkg/fanal/analyzer/sbom/testdata/elasticsearch.spdx.json @@ -0,0 +1,150 @@ +{ + "SPDXID": "SPDXRef-elasticsearch", + "spdxVersion": "SPDX-2.3", + "creationInfo": { + "created": "2023-08-18T20:09:40.708Z", + "creators": [ + "Organization: VMware, Inc." + ] + }, + "name": "SPDX document for Elasticsearch 8.9.1", + "dataLicense": "CC0-1.0", + "documentDescribes": [ + "SPDXRef-elasticsearch" + ], + "documentNamespace": "elasticsearch-8.9.1", + "packages": [ + { + "SPDXID": "SPDXRef-elasticsearch", + "name": "Elasticsearch", + "versionInfo": "8.9.1", + "downloadLocation": "https://github.com/elastic/elasticsearch/archive/v8.9.1.tar.gz", + "licenseConcluded": "Elastic-2.0", + "licenseDeclared": "Elastic-2.0", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:*:elastic:elasticsearch:8.9.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:bitnami/elasticsearch@8.9.1?arch=arm64" + } + ], + "copyrightText": "NOASSERTION" + }, + { + "name": "jar", + "SPDXID": "SPDXRef-Application-150e605f5f17224d", + "downloadLocation": "NONE", + "sourceInfo": "Java", + "copyrightText": "NOASSERTION", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "filesAnalyzed": false + }, + { + "name": "co.elastic.apm:apm-agent", + "SPDXID": "SPDXRef-Package-f0db45781e6813a1", + "versionInfo": "1.36.0", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/co.elastic.apm/apm-agent@1.36.0" + } + ], + "filesAnalyzed": false + }, + { + "name": "co.elastic.apm:apm-agent-cached-lookup-key", + "SPDXID": "SPDXRef-Package-efe22bf5916f985f", + "versionInfo": "1.36.0", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0" + } + ], + "filesAnalyzed": false + }, + { + "name": "co.elastic.apm:apm-agent-common", + "SPDXID": "SPDXRef-Package-33d86d2d11abe114", + "versionInfo": "1.36.0", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/co.elastic.apm/apm-agent-common@1.36.0" + } + ], + "filesAnalyzed": false + }, + { + "name": "co.elastic.apm:apm-agent-core", + "SPDXID": "SPDXRef-Package-b905fcf69ca61281", + "versionInfo": "1.36.0", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/co.elastic.apm/apm-agent-core@1.36.0" + } + ], + "filesAnalyzed": false + } + ], + "files": [], + "relationships": [ + { + "spdxElementId": "SPDXRef-elasticsearch", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-Application-150e605f5f17224d" + }, + { + "spdxElementId": "SPDXRef-Application-150e605f5f17224d", + "relatedSpdxElement": "SPDXRef-Package-f0db45781e6813a1", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Application-150e605f5f17224d", + "relatedSpdxElement": "SPDXRef-Package-efe22bf5916f985f", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Application-150e605f5f17224d", + "relatedSpdxElement": "SPDXRef-Package-33d86d2d11abe114", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Application-150e605f5f17224d", + "relatedSpdxElement": "SPDXRef-Package-b905fcf69ca61281", + "relationshipType": "CONTAINS" + } + ] +} diff --git a/pkg/fanal/analyzer/sbom/testdata/invalid_spdx.json b/pkg/fanal/analyzer/sbom/testdata/invalid_spdx.json new file mode 100644 index 000000000000..dc9255659ea7 --- /dev/null +++ b/pkg/fanal/analyzer/sbom/testdata/invalid_spdx.json @@ -0,0 +1,3 @@ +{ + "invalid": "data" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/sbom/testdata/postgresql.spdx.json b/pkg/fanal/analyzer/sbom/testdata/postgresql.spdx.json new file mode 100644 index 000000000000..ec9860bb0109 --- /dev/null +++ b/pkg/fanal/analyzer/sbom/testdata/postgresql.spdx.json @@ -0,0 +1,120 @@ +{ + "SPDXID": "SPDXRef-postgresql", + "spdxVersion": "SPDX-2.3", + "creationInfo": { + "created": "2023-07-13T19:24:23.609Z", + "creators": [ + "Organization: VMware, Inc." + ] + }, + "name": "SPDX document for PostgreSQL 15.3.0", + "dataLicense": "CC0-1.0", + "documentDescribes": [ + "SPDXRef-postgresql" + ], + "documentNamespace": "postgresql-15.3.0", + "packages": [ + { + "SPDXID": "SPDXRef-postgresql", + "name": "PostgreSQL", + "versionInfo": "15.3.0", + "downloadLocation": "https://ftp.postgresql.org/pub/source/v15.3/postgresql-15.3.tar.gz", + "licenseConcluded": "PostgreSQL", + "licenseDeclared": "PostgreSQL", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:*:postgresql:postgresql:15.3.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:bitnami/postgresql@15.3.0" + } + ] + }, + { + "SPDXID": "SPDXRef-geos", + "name": "GEOS", + "versionInfo": "3.8.3", + "downloadLocation": "https://github.com/libgeos/geos/archive/3.8.3.tar.gz", + "licenseConcluded": "LGPL-2.1-only", + "licenseDeclared": "LGPL-2.1-only", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:*:libgeos:geos:3.8.3:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:bitnami/geos@3.8.3" + } + ] + }, + { + "SPDXID": "SPDXRef-proj", + "name": "Proj", + "versionInfo": "6.3.2", + "downloadLocation": "https://github.com/OSGeo/PROJ/archive/6.3.2.tar.gz", + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:*:proj:proj:6.3.2:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:bitnami/proj@6.3.2" + } + ] + }, + { + "SPDXID": "SPDXRef-gdal", + "name": "GDAL", + "versionInfo": "3.7.1", + "downloadLocation": "https://github.com/OSGeo/gdal/releases/download/v3.7.1/gdal-3.7.1.tar.gz", + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:*:osgeo:gdal:3.7.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:bitnami/gdal@3.7.1" + } + ] + } + ], + "files": [], + "relationships": [ + { + "spdxElementId": "SPDXRef-postgresql", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-geos" + }, + { + "spdxElementId": "SPDXRef-postgresql", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-proj" + }, + { + "spdxElementId": "SPDXRef-postgresql", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-gdal" + } + ] +} diff --git a/pkg/fanal/analyzer/secret/secret.go b/pkg/fanal/analyzer/secret/secret.go new file mode 100644 index 000000000000..bbce32af326e --- /dev/null +++ b/pkg/fanal/analyzer/secret/secret.go @@ -0,0 +1,161 @@ +package secret + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/samber/lo" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/secret" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +// To make sure SecretAnalyzer implements analyzer.Initializer +var _ analyzer.Initializer = &SecretAnalyzer{} + +const version = 1 + +var ( + skipFiles = []string{ + "go.mod", + "go.sum", + "package-lock.json", + "yarn.lock", + "pnpm-lock.yaml", + "Pipfile.lock", + "Gemfile.lock", + } + skipDirs = []string{".git", "node_modules"} + skipExts = []string{ + ".jpg", ".png", ".gif", ".doc", ".pdf", ".bin", ".svg", ".socket", ".deb", ".rpm", + ".zip", ".gz", ".gzip", ".tar", ".pyc", + } +) + +func init() { + // The scanner will be initialized later via InitScanner() + analyzer.RegisterAnalyzer(NewSecretAnalyzer(secret.Scanner{}, "")) +} + +// SecretAnalyzer is an analyzer for secrets +type SecretAnalyzer struct { + scanner secret.Scanner + configPath string +} + +func NewSecretAnalyzer(s secret.Scanner, configPath string) *SecretAnalyzer { + return &SecretAnalyzer{ + scanner: s, + configPath: configPath, + } +} + +// Init initializes and sets a secret scanner +func (a *SecretAnalyzer) Init(opt analyzer.AnalyzerOptions) error { + if opt.SecretScannerOption.ConfigPath == a.configPath && !lo.IsEmpty(a.scanner) { + // This check is for tools importing Trivy and customize analyzers + // Never reach here in Trivy OSS + return nil + } + configPath := opt.SecretScannerOption.ConfigPath + c, err := secret.ParseConfig(configPath) + if err != nil { + return xerrors.Errorf("secret config error: %w", err) + } + a.scanner = secret.NewScanner(c) + a.configPath = configPath + return nil +} + +func (a *SecretAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + // Do not scan binaries + binary, err := utils.IsBinary(input.Content, input.Info.Size()) + if binary || err != nil { + return nil, nil + } + + content, err := io.ReadAll(input.Content) + if err != nil { + return nil, xerrors.Errorf("read error %s: %w", input.FilePath, err) + } + + content = bytes.ReplaceAll(content, []byte("\r"), []byte("")) + + filePath := input.FilePath + // Files extracted from the image have an empty input.Dir. + // Also, paths to these files do not have "/" prefix. + // We need to add a "/" prefix to properly filter paths from the config file. + if input.Dir == "" { // add leading `/` for files extracted from image + filePath = fmt.Sprintf("/%s", filePath) + } + + result := a.scanner.Scan(secret.ScanArgs{ + FilePath: filePath, + Content: content, + }) + + if len(result.Findings) == 0 { + return nil, nil + } + + return &analyzer.AnalysisResult{ + Secrets: []types.Secret{result}, + }, nil +} + +func (a *SecretAnalyzer) Required(filePath string, fi os.FileInfo) bool { + // Skip small files + if fi.Size() < 10 { + return false + } + + dir, fileName := filepath.Split(filePath) + dir = filepath.ToSlash(dir) + dirs := strings.Split(dir, "/") + + // Check if the directory should be skipped + for _, skipDir := range skipDirs { + if slices.Contains(dirs, skipDir) { + return false + } + } + + // Check if the file should be skipped + if slices.Contains(skipFiles, fileName) { + return false + } + + // Skip the config file for secret scanning + if filepath.Base(a.configPath) == filePath { + return false + } + + // Check if the file extension should be skipped + ext := filepath.Ext(fileName) + if slices.Contains(skipExts, ext) { + return false + } + + if a.scanner.AllowPath(filePath) { + return false + } + + return true +} + +func (a *SecretAnalyzer) Type() analyzer.Type { + return analyzer.TypeSecret +} + +func (a *SecretAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/secret/secret_test.go b/pkg/fanal/analyzer/secret/secret_test.go new file mode 100644 index 000000000000..4f499c4d59c3 --- /dev/null +++ b/pkg/fanal/analyzer/secret/secret_test.go @@ -0,0 +1,227 @@ +package secret_test + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestSecretAnalyzer(t *testing.T) { + wantFinding1 := types.SecretFinding{ + RuleID: "rule1", + Category: "general", + Title: "Generic Rule", + Severity: "HIGH", + StartLine: 2, + EndLine: 2, + Match: "generic secret line secret=\"*********\"", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "--- ignore block start ---", + IsCause: false, + Annotation: "", + Truncated: false, + Highlighted: "--- ignore block start ---", + FirstCause: false, + LastCause: false, + }, + { + Number: 2, + Content: "generic secret line secret=\"*********\"", + IsCause: true, + Annotation: "", + Truncated: false, + Highlighted: "generic secret line secret=\"*********\"", + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + Content: "--- ignore block stop ---", + IsCause: false, + Annotation: "", + Truncated: false, + Highlighted: "--- ignore block stop ---", + FirstCause: false, + LastCause: false, + }, + }, + }, + } + wantFinding2 := types.SecretFinding{ + RuleID: "rule1", + Category: "general", + Title: "Generic Rule", + Severity: "HIGH", + StartLine: 4, + EndLine: 4, + Match: "secret=\"**********\"", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 2, + Content: "generic secret line secret=\"*********\"", + IsCause: false, + Highlighted: "generic secret line secret=\"*********\"", + }, + { + Number: 3, + Content: "--- ignore block stop ---", + IsCause: false, + Highlighted: "--- ignore block stop ---", + }, + { + Number: 4, + Content: "secret=\"**********\"", + IsCause: true, + Highlighted: "secret=\"**********\"", + FirstCause: true, + LastCause: true, + }, + { + Number: 5, + Content: "credentials: { user: \"username\" password: \"123456789\" }", + Highlighted: "credentials: { user: \"username\" password: \"123456789\" }", + }, + }, + }, + } + tests := []struct { + name string + configPath string + filePath string + dir string + want *analyzer.AnalysisResult + }{ + { + name: "return results", + configPath: "testdata/config.yaml", + filePath: "testdata/secret.txt", + dir: ".", + want: &analyzer.AnalysisResult{ + Secrets: []types.Secret{ + { + FilePath: "testdata/secret.txt", + Findings: []types.SecretFinding{wantFinding1, wantFinding2}, + }, + }, + }, + }, + { + name: "image scan return result", + configPath: "testdata/image-config.yaml", + filePath: "testdata/secret.txt", + want: &analyzer.AnalysisResult{ + Secrets: []types.Secret{ + { + FilePath: "/testdata/secret.txt", + Findings: []types.SecretFinding{wantFinding1, wantFinding2}, + }, + }, + }, + }, + { + name: "image scan return nil", + configPath: "testdata/image-config.yaml", + filePath: "testdata/secret.doc", + want: nil, + }, + { + name: "return nil when no results", + configPath: "", + filePath: "testdata/secret.txt", + want: nil, + }, + { + name: "skip binary file", + configPath: "", + filePath: "testdata/binaryfile", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &secret.SecretAnalyzer{} + err := a.Init(analyzer.AnalyzerOptions{ + SecretScannerOption: analyzer.SecretScannerOption{ConfigPath: tt.configPath}, + }) + require.NoError(t, err) + content, err := os.Open(tt.filePath) + require.NoError(t, err) + fi, err := content.Stat() + require.NoError(t, err) + + got, err := a.Analyze(context.TODO(), analyzer.AnalysisInput{ + FilePath: tt.filePath, + Dir: tt.dir, + Content: content, + Info: fi, + }) + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestSecretRequire(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "pass regular file", + filePath: "testdata/secret.txt", + want: true, + }, + { + name: "skip small file", + filePath: "testdata/emptyfile", + want: false, + }, + { + name: "skip folder", + filePath: "testdata/node_modules/secret.txt", + want: false, + }, + { + name: "skip file", + filePath: "testdata/package-lock.json", + want: false, + }, + { + name: "skip extension", + filePath: "testdata/secret.doc", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := secret.SecretAnalyzer{} + err := a.Init(analyzer.AnalyzerOptions{ + SecretScannerOption: analyzer.SecretScannerOption{ + ConfigPath: "testdata/skip-tests-config.yaml", + }, + }) + require.NoError(t, err) + + fi, err := os.Stat(tt.filePath) + require.NoError(t, err) + + got := a.Required(tt.filePath, fi) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/secret/testdata/binaryfile b/pkg/fanal/analyzer/secret/testdata/binaryfile new file mode 100644 index 000000000000..df93f5f3f724 Binary files /dev/null and b/pkg/fanal/analyzer/secret/testdata/binaryfile differ diff --git a/pkg/fanal/analyzer/secret/testdata/config.yaml b/pkg/fanal/analyzer/secret/testdata/config.yaml new file mode 100644 index 000000000000..d656427e6b0c --- /dev/null +++ b/pkg/fanal/analyzer/secret/testdata/config.yaml @@ -0,0 +1,10 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret + +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/analyzer/secret/testdata/emptyfile b/pkg/fanal/analyzer/secret/testdata/emptyfile new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/secret/testdata/image-config.yaml b/pkg/fanal/analyzer/secret/testdata/image-config.yaml new file mode 100644 index 000000000000..b5640c223ad0 --- /dev/null +++ b/pkg/fanal/analyzer/secret/testdata/image-config.yaml @@ -0,0 +1,10 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + path: /testdata/secret.txt + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/analyzer/secret/testdata/node_modules/secret.txt b/pkg/fanal/analyzer/secret/testdata/node_modules/secret.txt new file mode 100644 index 000000000000..f0bad73a2d29 --- /dev/null +++ b/pkg/fanal/analyzer/secret/testdata/node_modules/secret.txt @@ -0,0 +1,5 @@ +--- ignore block start --- +generic secret line secret="somevalue" +--- ignore block stop --- +secret="othervalue" +credentials: { user: "username" password: "123456789" } \ No newline at end of file diff --git a/pkg/fanal/analyzer/secret/testdata/package-lock.json b/pkg/fanal/analyzer/secret/testdata/package-lock.json new file mode 100644 index 000000000000..6480ec74d711 --- /dev/null +++ b/pkg/fanal/analyzer/secret/testdata/package-lock.json @@ -0,0 +1 @@ +{"name": "just a field to check file skip"} \ No newline at end of file diff --git a/pkg/fanal/analyzer/secret/testdata/secret.doc b/pkg/fanal/analyzer/secret/testdata/secret.doc new file mode 100644 index 000000000000..a52b5e7228ca --- /dev/null +++ b/pkg/fanal/analyzer/secret/testdata/secret.doc @@ -0,0 +1 @@ +mock doc file \ No newline at end of file diff --git a/pkg/fanal/analyzer/secret/testdata/secret.txt b/pkg/fanal/analyzer/secret/testdata/secret.txt new file mode 100644 index 000000000000..f0bad73a2d29 --- /dev/null +++ b/pkg/fanal/analyzer/secret/testdata/secret.txt @@ -0,0 +1,5 @@ +--- ignore block start --- +generic secret line secret="somevalue" +--- ignore block stop --- +secret="othervalue" +credentials: { user: "username" password: "123456789" } \ No newline at end of file diff --git a/pkg/fanal/analyzer/secret/testdata/skip-tests-config.yaml b/pkg/fanal/analyzer/secret/testdata/skip-tests-config.yaml new file mode 100644 index 000000000000..48cb99db27f0 --- /dev/null +++ b/pkg/fanal/analyzer/secret/testdata/skip-tests-config.yaml @@ -0,0 +1,2 @@ +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/analyzer/testdata/app/Gemfile.lock b/pkg/fanal/analyzer/testdata/app/Gemfile.lock new file mode 100644 index 000000000000..f4515a6b4cc5 --- /dev/null +++ b/pkg/fanal/analyzer/testdata/app/Gemfile.lock @@ -0,0 +1,15 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.3) + actionpack (= 5.2.3) + actionpack (5.2.3) + +PLATFORMS + ruby + +DEPENDENCIES + actioncable (~> 5.0) + +BUNDLED WITH + 1.17.2 \ No newline at end of file diff --git a/pkg/fanal/analyzer/testdata/error b/pkg/fanal/analyzer/testdata/error new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/testdata/etc/alpine-release b/pkg/fanal/analyzer/testdata/etc/alpine-release new file mode 100644 index 000000000000..375f5cabfe6c --- /dev/null +++ b/pkg/fanal/analyzer/testdata/etc/alpine-release @@ -0,0 +1 @@ +3.11.6 diff --git a/pkg/fanal/analyzer/testdata/etc/hostname b/pkg/fanal/analyzer/testdata/etc/hostname new file mode 100644 index 000000000000..c70dc2dfaf07 --- /dev/null +++ b/pkg/fanal/analyzer/testdata/etc/hostname @@ -0,0 +1 @@ +host diff --git a/pkg/fanal/analyzer/testdata/lib/apk/db/installed b/pkg/fanal/analyzer/testdata/lib/apk/db/installed new file mode 100644 index 000000000000..dc23126b6a1b --- /dev/null +++ b/pkg/fanal/analyzer/testdata/lib/apk/db/installed @@ -0,0 +1,21 @@ +C:Q1yyMWoYnr7lKCxKm9mHlMwkd6dMY= +P:musl +V:1.1.24-r2 +A:x86_64 +S:377123 +I:614400 +T:the musl c library (libc) implementation +U:https://musl.libc.org/ +L:MIT +o:musl +m:Timo Teräs +t:1584790550 +c:4024cc3b29ad4c65544ad068b8f59172b5494306 +p:so:libc.musl-x86_64.so.1=1 +F:lib +R:libc.musl-x86_64.so.1 +a:0:0:777 +Z:Q17yJ3JFNypA4mxhJJr0ou6CzsJVI= +R:ld-musl-x86_64.so.1 +a:0:0:755 +Z:Q19mQZaYKY6yTQWQm0hkvsrh39O7Y= diff --git a/pkg/fanal/analyzer/testdata/no-permission b/pkg/fanal/analyzer/testdata/no-permission new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/analyzer/testdata/post-apps/jar/invalid.jar b/pkg/fanal/analyzer/testdata/post-apps/jar/invalid.jar new file mode 100644 index 000000000000..d800886d9c86 --- /dev/null +++ b/pkg/fanal/analyzer/testdata/post-apps/jar/invalid.jar @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/pkg/fanal/analyzer/testdata/post-apps/jar/jackson-annotations-2.15.0-rc2.jar b/pkg/fanal/analyzer/testdata/post-apps/jar/jackson-annotations-2.15.0-rc2.jar new file mode 100644 index 000000000000..b56a46744a66 Binary files /dev/null and b/pkg/fanal/analyzer/testdata/post-apps/jar/jackson-annotations-2.15.0-rc2.jar differ diff --git a/pkg/fanal/analyzer/testdata/post-apps/poetry/happy/poetry.lock b/pkg/fanal/analyzer/testdata/post-apps/poetry/happy/poetry.lock new file mode 100644 index 000000000000..221c62aef3e4 --- /dev/null +++ b/pkg/fanal/analyzer/testdata/post-apps/poetry/happy/poetry.lock @@ -0,0 +1,18 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = true +python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "0ee6cb4bc2d84091d5dcb0a0110a65f244987ed427933b2f49949195e3ef69c7" diff --git a/pkg/fanal/analyzer/testdata/post-apps/poetry/sad/poetry.lock b/pkg/fanal/analyzer/testdata/post-apps/poetry/sad/poetry.lock new file mode 100644 index 000000000000..8e2f0bef135b --- /dev/null +++ b/pkg/fanal/analyzer/testdata/post-apps/poetry/sad/poetry.lock @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/pkg/fanal/applier/applier.go b/pkg/fanal/applier/applier.go new file mode 100644 index 000000000000..192c9d2184f2 --- /dev/null +++ b/pkg/fanal/applier/applier.go @@ -0,0 +1,50 @@ +package applier + +import ( + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// Applier defines operation to scan image layers +type Applier interface { + ApplyLayers(artifactID string, blobIDs []string) (detail ftypes.ArtifactDetail, err error) +} + +type applier struct { + cache cache.LocalArtifactCache +} + +func NewApplier(c cache.LocalArtifactCache) Applier { + return &applier{cache: c} +} + +func (a *applier) ApplyLayers(imageID string, layerKeys []string) (ftypes.ArtifactDetail, error) { + var layers []ftypes.BlobInfo + for _, key := range layerKeys { + blob, _ := a.cache.GetBlob(key) // nolint + if blob.SchemaVersion == 0 { + return ftypes.ArtifactDetail{}, xerrors.Errorf("layer cache missing: %s", key) + } + layers = append(layers, blob) + } + + mergedLayer := ApplyLayers(layers) + + imageInfo, _ := a.cache.GetArtifact(imageID) // nolint + mergedLayer.ImageConfig = ftypes.ImageConfigDetail{ + Packages: imageInfo.HistoryPackages, + Misconfiguration: imageInfo.Misconfiguration, + Secret: imageInfo.Secret, + } + + if !mergedLayer.OS.Detected() { + return mergedLayer, analyzer.ErrUnknownOS // send back package and apps info regardless + } else if mergedLayer.Packages == nil { + return mergedLayer, analyzer.ErrNoPkgsDetected // send back package and apps info regardless + } + + return mergedLayer, nil +} diff --git a/pkg/fanal/applier/applier_test.go b/pkg/fanal/applier/applier_test.go new file mode 100644 index 000000000000..8037e0bee922 --- /dev/null +++ b/pkg/fanal/applier/applier_test.go @@ -0,0 +1,975 @@ +package applier_test + +import ( + "github.com/package-url/packageurl-go" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/applier" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type dummyData struct { + data string +} + +func TestApplier_ApplyLayers(t *testing.T) { + type args struct { + imageID string + layerIDs []string + } + tests := []struct { + name string + args args + getLayerExpectations []cache.LocalArtifactCacheGetBlobExpectation + getArtifactExpectations []cache.LocalArtifactCacheGetArtifactExpectation + want types.ArtifactDetail + wantErr string + }{ + { + name: "happy path", + args: args{ + imageID: "sha256:4791503518dff090d6a82f7a5c1fd71c41146920e2562fb64308e17ab6834b7e", + layerIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + }, + getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{ + BlobInfo: types.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + OS: types.OS{ + Family: "debian", + Name: "9.9", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/tzdata", + Packages: types.Packages{ + { + Name: "tzdata", + Version: "2019a-0+deb9u1", + SrcName: "tzdata", + SrcVersion: "2019a-0+deb9u1", + }, + }, + }, + }, + }, + }, + }, + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{ + BlobInfo: types.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/libc6", + Packages: types.Packages{ + { + Name: "libc6", + Version: "2.24-11+deb9u4", + SrcName: "glibc", + SrcVersion: "2.24-11+deb9u4", + }, + }, + }, + }, + Applications: nil, + OpaqueDirs: nil, + WhiteoutFiles: nil, + }, + }, + }, + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{ + BlobInfo: types.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + Applications: []types.Application{ + { + Type: "composer", + FilePath: "php-app/composer.lock", + Libraries: types.Packages{ + { + Name: "guzzlehttp/guzzle", + Version: "6.2.0", + }, + { + Name: "symfony/process", + Version: "v4.2.7", + }, + }, + }, + }, + }, + }, + }, + }, + getArtifactExpectations: []cache.LocalArtifactCacheGetArtifactExpectation{ + { + Args: cache.LocalArtifactCacheGetArtifactArgs{ + ArtifactID: "sha256:4791503518dff090d6a82f7a5c1fd71c41146920e2562fb64308e17ab6834b7e", + }, + Returns: cache.LocalArtifactCacheGetArtifactReturns{ + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: 1, + }, + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "debian", + Name: "9.9", + }, + Packages: types.Packages{ + { + Name: "libc6", + Version: "2.24-11+deb9u4", + SrcName: "glibc", + SrcVersion: "2.24-11+deb9u4", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "debian", + Name: "libc6", + Version: "2.24-11+deb9u4", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "debian-9.9", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + }, + { + Name: "tzdata", + Version: "2019a-0+deb9u1", + SrcName: "tzdata", + SrcVersion: "2019a-0+deb9u1", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "debian", + Name: "tzdata", + Version: "2019a-0+deb9u1", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "debian-9.9", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + }, + }, + Applications: []types.Application{ + { + Type: "composer", + FilePath: "php-app/composer.lock", + Libraries: types.Packages{ + { + Name: "guzzlehttp/guzzle", + Version: "6.2.0", + Layer: types.Layer{ + Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "guzzlehttp", + Name: "guzzle", + Version: "6.2.0", + }, + }, + }, + { + Name: "symfony/process", + Version: "v4.2.7", + Layer: types.Layer{ + Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "symfony", + Name: "process", + Version: "v4.2.7", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with history packages", + args: args{ + imageID: "sha256:3bb70bd5fb37e05b8ecaaace5d6a6b5ec7834037c07ecb5907355c23ab70352d", + layerIDs: []string{ + "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }, + }, + getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{ + BlobInfo: types.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + DiffID: "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + OS: types.OS{ + Family: "alpine", + Name: "3.10.4", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: types.Packages{ + { + Name: "musl", + Version: "1.1.22-r3", + }, + { + Name: "busybox", + Version: "1.30.1-r3", + }, + { + Name: "openssl", + Version: "1.1.1d-r2", + }, + { + Name: "libcrypto1.1", + Version: "1.1.1d-r2", + }, + { + Name: "libssl1.1", + Version: "1.1.1d-r2", + }, + }, + }, + }, + }, + }, + }, + }, + getArtifactExpectations: []cache.LocalArtifactCacheGetArtifactExpectation{ + { + Args: cache.LocalArtifactCacheGetArtifactArgs{ + ArtifactID: "sha256:3bb70bd5fb37e05b8ecaaace5d6a6b5ec7834037c07ecb5907355c23ab70352d", + }, + Returns: cache.LocalArtifactCacheGetArtifactReturns{ + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: 1, + HistoryPackages: types.Packages{ + { + Name: "musl", + Version: "1.1.23", + }, + { + Name: "busybox", + Version: "1.31", + }, + { + Name: "ncurses-libs", + Version: "6.1_p20190518-r0", + }, + { + Name: "ncurses-terminfo-base", + Version: "6.1_p20190518-r0", + }, + { + Name: "ncurses", + Version: "6.1_p20190518-r0", + }, + { + Name: "ncurses-terminfo", + Version: "6.1_p20190518-r0", + }, + { + Name: "bash", + Version: "5.0.0-r0", + }, + { + Name: "readline", + Version: "8.0.0-r0", + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "alpine", + Name: "3.10.4", + }, + Packages: types.Packages{ + { + Name: "busybox", + Version: "1.30.1-r3", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "busybox", + Version: "1.30.1-r3", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.10.4", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + DiffID: "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }, + }, + { + Name: "libcrypto1.1", + Version: "1.1.1d-r2", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "libcrypto1.1", + Version: "1.1.1d-r2", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.10.4", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + DiffID: "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }, + }, + { + Name: "libssl1.1", + Version: "1.1.1d-r2", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "libssl1.1", + Version: "1.1.1d-r2", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.10.4", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + DiffID: "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }, + }, + { + Name: "musl", + Version: "1.1.22-r3", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "musl", + Version: "1.1.22-r3", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.10.4", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + DiffID: "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }, + }, + { + Name: "openssl", + Version: "1.1.1d-r2", + Identifier: types.PkgIdentifier{ + //PURL: "pkg:apk/alpine/openssl@1.1.1d-r2?distro=3.10.4", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "openssl", + Version: "1.1.1d-r2", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.10.4", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + DiffID: "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }, + }, + }, + ImageConfig: types.ImageConfigDetail{ + Packages: types.Packages{ + { + Name: "musl", + Version: "1.1.23", + }, + { + Name: "busybox", + Version: "1.31", + }, + { + Name: "ncurses-libs", + Version: "6.1_p20190518-r0", + }, + { + Name: "ncurses-terminfo-base", + Version: "6.1_p20190518-r0", + }, + { + Name: "ncurses", + Version: "6.1_p20190518-r0", + }, + { + Name: "ncurses-terminfo", + Version: "6.1_p20190518-r0", + }, + { + Name: "bash", + Version: "5.0.0-r0", + }, + { + Name: "readline", + Version: "8.0.0-r0", + }, + }, + }, + }, + }, + { + name: "sad path GetBlob returns an error", + args: args{ + layerIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{BlobInfo: types.BlobInfo{}}, + }, + }, + wantErr: "layer cache missing", + }, + { + name: "sad path GetBlob returns empty layer info", + args: args{ + layerIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{BlobInfo: types.BlobInfo{}}, + }, + }, + wantErr: "layer cache missing", + }, + { + name: "happy path with some packages but unknown OS", + args: args{ + imageID: "sha256:4791503518dff090d6a82f7a5c1fd71c41146920e2562fb64308e17ab6834b7e", + layerIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + }, + getArtifactExpectations: []cache.LocalArtifactCacheGetArtifactExpectation{ + { + Args: cache.LocalArtifactCacheGetArtifactArgs{ + ArtifactID: "sha256:4791503518dff090d6a82f7a5c1fd71c41146920e2562fb64308e17ab6834b7e", + }, + Returns: cache.LocalArtifactCacheGetArtifactReturns{ + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: 1, + }, + }, + }, + }, + getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{ + BlobInfo: types.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/tzdata", + Packages: types.Packages{ + { + Name: "tzdata", + Version: "2019a-0+deb9u1", + SrcName: "tzdata", + SrcVersion: "2019a-0+deb9u1", + }, + }, + }, + }, + }, + }, + }, + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{ + BlobInfo: types.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/libc6", + Packages: types.Packages{ + { + Name: "libc6", + Version: "2.24-11+deb9u4", + SrcName: "glibc", + SrcVersion: "2.24-11+deb9u4", + }, + }, + }, + }, + Applications: nil, + OpaqueDirs: nil, + WhiteoutFiles: nil, + }, + }, + }, + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{ + BlobInfo: types.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + Applications: []types.Application{ + { + Type: "composer", + FilePath: "php-app/composer.lock", + Libraries: types.Packages{ + { + Name: "guzzlehttp/guzzle", + Version: "6.2.0", + }, + { + Name: "symfony/process", + Version: "v4.2.7", + }, + }, + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + Packages: types.Packages{ + { + Name: "libc6", + Version: "2.24-11+deb9u4", + SrcName: "glibc", + SrcVersion: "2.24-11+deb9u4", + Layer: types.Layer{ + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + }, + { + Name: "tzdata", + Version: "2019a-0+deb9u1", + SrcName: "tzdata", + SrcVersion: "2019a-0+deb9u1", + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + }, + }, + Applications: []types.Application{ + { + Type: "composer", + FilePath: "php-app/composer.lock", + Libraries: types.Packages{ + { + Name: "guzzlehttp/guzzle", + Version: "6.2.0", + Layer: types.Layer{ + Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "guzzlehttp", + Name: "guzzle", + Version: "6.2.0", + }, + }, + }, + { + Name: "symfony/process", + Version: "v4.2.7", + Layer: types.Layer{ + Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "symfony", + Name: "process", + Version: "v4.2.7", + }, + }, + }, + }, + }, + }, + }, + wantErr: "unknown OS", + }, + { + name: "sad path no package detected", + args: args{ + imageID: "sha256:4791503518dff090d6a82f7a5c1fd71c41146920e2562fb64308e17ab6834b7e", + layerIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + getArtifactExpectations: []cache.LocalArtifactCacheGetArtifactExpectation{ + { + Args: cache.LocalArtifactCacheGetArtifactArgs{ + ArtifactID: "sha256:4791503518dff090d6a82f7a5c1fd71c41146920e2562fb64308e17ab6834b7e", + }, + Returns: cache.LocalArtifactCacheGetArtifactReturns{ + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: 1, + }, + }, + }, + }, + getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{ + BlobInfo: types.BlobInfo{ + SchemaVersion: 1, + OS: types.OS{ + Family: "debian", + Name: "9.9", + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "debian", + Name: "9.9", + }, + }, + wantErr: "no packages detected", + }, + { + name: "happy path with custom resources", + args: args{ + imageID: "sha256:4791503518dff090d6a82f7a5c1fd71c41146920e2562fb64308e17ab6834b7e", + layerIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + }, + getArtifactExpectations: []cache.LocalArtifactCacheGetArtifactExpectation{ + { + Args: cache.LocalArtifactCacheGetArtifactArgs{ + ArtifactID: "sha256:4791503518dff090d6a82f7a5c1fd71c41146920e2562fb64308e17ab6834b7e", + }, + Returns: cache.LocalArtifactCacheGetArtifactReturns{ + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: 1, + }, + }, + }, + }, + getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{ + BlobInfo: types.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/tzdata", + Packages: types.Packages{ + { + Name: "tzdata", + Version: "2019a-0+deb9u1", + SrcName: "tzdata", + SrcVersion: "2019a-0+deb9u1", + }, + }, + }, + }, + CustomResources: []types.CustomResource{ + { + Type: "type-A", + FilePath: "var/lib/dpkg/status.d/tzdata", + Data: dummyData{ + data: "Common Package type-A var/lib/dpkg/status.d/tzdata", + }, + }, + { + Type: "type-B", + FilePath: "var/lib/dpkg/status.d/tzdata", + Data: dummyData{ + data: "Common Package type-B, overidden in next layer", + }, + }, + }, + }, + }, + }, + { + Args: cache.LocalArtifactCacheGetBlobArgs{ + BlobID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + Returns: cache.LocalArtifactCacheGetBlobReturns{ + BlobInfo: types.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + Applications: []types.Application{ + { + Type: "composer", + FilePath: "php-app/composer.lock", + Libraries: types.Packages{ + { + Name: "guzzlehttp/guzzle", + Version: "6.2.0", + }, + { + Name: "symfony/process", + Version: "v4.2.7", + }, + }, + }, + }, + CustomResources: []types.CustomResource{ + { + Type: "type-A", + FilePath: "php-app/composer.lock", + Data: dummyData{ + data: "Common Application type-A php-app/composer.lock", + }, + }, + { + Type: "type-B", + FilePath: "var/lib/dpkg/status.d/tzdata", + Data: dummyData{ + data: "Type B application which replaces earlier detected resource", + }, + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + Packages: types.Packages{ + { + Name: "tzdata", + Version: "2019a-0+deb9u1", + SrcName: "tzdata", + SrcVersion: "2019a-0+deb9u1", + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + }, + }, + Applications: []types.Application{ + { + Type: "composer", + FilePath: "php-app/composer.lock", + Libraries: types.Packages{ + { + Name: "guzzlehttp/guzzle", + Version: "6.2.0", + Layer: types.Layer{ + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "guzzlehttp", + Name: "guzzle", + Version: "6.2.0", + }, + }, + }, + { + Name: "symfony/process", + Version: "v4.2.7", + Layer: types.Layer{ + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "symfony", + Name: "process", + Version: "v4.2.7", + }, + }, + }, + }, + }, + }, + CustomResources: []types.CustomResource{ + { + Type: "type-A", + FilePath: "php-app/composer.lock", + Layer: types.Layer{ + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + Data: dummyData{ + data: "Common Application type-A php-app/composer.lock", + }, + }, + { + Type: "type-A", + FilePath: "var/lib/dpkg/status.d/tzdata", + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + Data: dummyData{ + data: "Common Package type-A var/lib/dpkg/status.d/tzdata", + }, + }, + { + Type: "type-B", + FilePath: "var/lib/dpkg/status.d/tzdata", + Layer: types.Layer{ + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + Data: dummyData{ + data: "Type B application which replaces earlier detected resource", + }, + }, + }, + }, + wantErr: "unknown OS", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockLocalArtifactCache) + c.ApplyGetBlobExpectations(tt.getLayerExpectations) + c.ApplyGetArtifactExpectations(tt.getArtifactExpectations) + + a := applier.NewApplier(c) + + got, err := a.ApplyLayers(tt.args.imageID, tt.args.layerIDs) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + } else { + require.NoError(t, err, tt.name) + } + + sort.Sort(got.Packages) + for _, app := range got.Applications { + sort.Sort(app.Libraries) + } + + sort.Slice(got.CustomResources, func(i, j int) bool { + if got.CustomResources[i].FilePath == got.CustomResources[j].FilePath { + return got.CustomResources[i].Type < got.CustomResources[j].Type + } + return got.CustomResources[i].FilePath < got.CustomResources[j].FilePath + }) + + assert.Equal(t, tt.want, got) + c.AssertExpectations(t) + }) + } +} diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go new file mode 100644 index 000000000000..abcc1ce51958 --- /dev/null +++ b/pkg/fanal/applier/docker.go @@ -0,0 +1,320 @@ +package applier + +import ( + "fmt" + "strings" + "time" + + "github.com/knqyf263/nested" + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/types" +) + +type Config struct { + ContainerConfig containerConfig `json:"container_config"` + History []History +} + +type containerConfig struct { + Env []string +} + +type History struct { + Created time.Time + CreatedBy string `json:"created_by"` +} + +func containsPackage(e ftypes.Package, s []ftypes.Package) bool { + for _, a := range s { + if a.Name == e.Name && a.Version == e.Version && a.Release == e.Release { + return true + } + } + return false +} + +func lookupOriginLayerForPkg(pkg ftypes.Package, layers []ftypes.BlobInfo) (string, string, *ftypes.BuildInfo) { + for i, layer := range layers { + for _, info := range layer.PackageInfos { + if containsPackage(pkg, info.Packages) { + return layer.Digest, layer.DiffID, lookupBuildInfo(i, layers) + } + } + } + return "", "", nil +} + +// lookupBuildInfo looks up Red Hat content sets from all layers +func lookupBuildInfo(index int, layers []ftypes.BlobInfo) *ftypes.BuildInfo { + if layers[index].BuildInfo != nil { + return layers[index].BuildInfo + } + + // Base layer (layers[0]) is missing content sets + // - it needs to be shared from layers[1] + if index == 0 { + if len(layers) > 1 { + return layers[1].BuildInfo + } + return nil + } + + // Customer's layers build on top of Red Hat image are also missing content sets + // - it needs to be shared from the last Red Hat's layers which contains content sets + for i := index - 1; i >= 1; i-- { + if layers[i].BuildInfo != nil { + return layers[i].BuildInfo + } + } + return nil +} + +func lookupOriginLayerForLib(filePath string, lib ftypes.Package, layers []ftypes.BlobInfo) (string, string) { + for _, layer := range layers { + for _, layerApp := range layer.Applications { + if filePath != layerApp.FilePath { + continue + } + if containsPackage(lib, layerApp.Libraries) { + return layer.Digest, layer.DiffID + } + } + } + return "", "" +} + +// ApplyLayers returns the merged layer +// nolint: gocyclo +func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { + sep := "/" + nestedMap := nested.Nested{} + secretsMap := make(map[string]ftypes.Secret) + var mergedLayer ftypes.ArtifactDetail + + for _, layer := range layers { + for _, opqDir := range layer.OpaqueDirs { + opqDir = strings.TrimSuffix(opqDir, sep) // this is necessary so that an empty element is not contribute into the array of the DeleteByString function + _ = nestedMap.DeleteByString(opqDir, sep) // nolint + } + for _, whFile := range layer.WhiteoutFiles { + _ = nestedMap.DeleteByString(whFile, sep) // nolint + } + + mergedLayer.OS.Merge(layer.OS) + + if layer.Repository != nil { + mergedLayer.Repository = layer.Repository + } + + // Apply OS packages + for _, pkgInfo := range layer.PackageInfos { + key := fmt.Sprintf("%s/type:ospkg", pkgInfo.FilePath) + nestedMap.SetByString(key, sep, pkgInfo) + } + + // Apply language-specific packages + for _, app := range layer.Applications { + key := fmt.Sprintf("%s/type:%s", app.FilePath, app.Type) + nestedMap.SetByString(key, sep, app) + } + + // Apply misconfigurations + for _, config := range layer.Misconfigurations { + config.Layer = ftypes.Layer{ + Digest: layer.Digest, + DiffID: layer.DiffID, + } + key := fmt.Sprintf("%s/type:config", config.FilePath) + nestedMap.SetByString(key, sep, config) + } + + // Apply secrets + for _, secret := range layer.Secrets { + l := ftypes.Layer{ + Digest: layer.Digest, + DiffID: layer.DiffID, + CreatedBy: layer.CreatedBy, + } + secretsMap = mergeSecrets(secretsMap, secret, l) + } + + // Apply license files + for _, license := range layer.Licenses { + license.Layer = ftypes.Layer{ + Digest: layer.Digest, + DiffID: layer.DiffID, + } + key := fmt.Sprintf("%s/type:license,%s", license.FilePath, license.Type) + nestedMap.SetByString(key, sep, license) + } + + // Apply custom resources + for _, customResource := range layer.CustomResources { + key := fmt.Sprintf("%s/custom:%s", customResource.FilePath, customResource.Type) + customResource.Layer = ftypes.Layer{ + Digest: layer.Digest, + DiffID: layer.DiffID, + } + nestedMap.SetByString(key, sep, customResource) + } + } + + // nolint + _ = nestedMap.Walk(func(keys []string, value interface{}) error { + switch v := value.(type) { + case ftypes.PackageInfo: + mergedLayer.Packages = append(mergedLayer.Packages, v.Packages...) + case ftypes.Application: + mergedLayer.Applications = append(mergedLayer.Applications, v) + case ftypes.Misconfiguration: + mergedLayer.Misconfigurations = append(mergedLayer.Misconfigurations, v) + case ftypes.LicenseFile: + mergedLayer.Licenses = append(mergedLayer.Licenses, v) + case ftypes.CustomResource: + mergedLayer.CustomResources = append(mergedLayer.CustomResources, v) + } + return nil + }) + + for _, s := range secretsMap { + mergedLayer.Secrets = append(mergedLayer.Secrets, s) + } + + // Extract dpkg licenses + // The license information is not stored in the dpkg database and in a separate file, + // so we have to merge the license information into the package. + dpkgLicenses := make(map[string][]string) + mergedLayer.Licenses = lo.Reject(mergedLayer.Licenses, func(license ftypes.LicenseFile, _ int) bool { + if license.Type != ftypes.LicenseTypeDpkg { + return false + } + // e.g. + // "adduser" => {"GPL-2"} + // "openssl" => {"MIT", "BSD"} + dpkgLicenses[license.PkgName] = lo.Map(license.Findings, func(finding ftypes.LicenseFinding, _ int) string { + return finding.Name + }) + // Remove this license in the merged result as it is merged into the package information. + return true + }) + if len(mergedLayer.Licenses) == 0 { + mergedLayer.Licenses = nil + } + + for i, pkg := range mergedLayer.Packages { + // Skip lookup for SBOM + if !lo.IsEmpty(pkg.Layer) { + continue + } + originLayerDigest, originLayerDiffID, buildInfo := lookupOriginLayerForPkg(pkg, layers) + mergedLayer.Packages[i].Layer = ftypes.Layer{ + Digest: originLayerDigest, + DiffID: originLayerDiffID, + } + mergedLayer.Packages[i].BuildInfo = buildInfo + if mergedLayer.OS.Family != "" { + mergedLayer.Packages[i].Identifier.PURL = newPURL(mergedLayer.OS.Family, types.Metadata{OS: &mergedLayer.OS}, pkg) + } + + // Only debian packages + if licenses, ok := dpkgLicenses[pkg.Name]; ok { + mergedLayer.Packages[i].Licenses = licenses + } + } + + for _, app := range mergedLayer.Applications { + for i, lib := range app.Libraries { + // Skip lookup for SBOM + if !lo.IsEmpty(lib.Layer) { + continue + } + originLayerDigest, originLayerDiffID := lookupOriginLayerForLib(app.FilePath, lib, layers) + app.Libraries[i].Layer = ftypes.Layer{ + Digest: originLayerDigest, + DiffID: originLayerDiffID, + } + if lib.Identifier.PURL == nil { + app.Libraries[i].Identifier.PURL = newPURL(app.Type, types.Metadata{}, lib) + } + } + } + + // Aggregate python/ruby/node.js packages and JAR files + aggregate(&mergedLayer) + + return mergedLayer +} + +func newPURL(pkgType ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) *packageurl.PackageURL { + p, err := purl.New(pkgType, metadata, pkg) + if err != nil { + log.Logger.Errorf("Failed to create PackageURL: %s", err) + return nil + } + return p.Unwrap() +} + +// aggregate merges all packages installed by pip/gem/npm/jar/conda into each application +func aggregate(detail *ftypes.ArtifactDetail) { + var apps []ftypes.Application + + aggregatedApps := make(map[ftypes.LangType]*ftypes.Application) + for _, t := range ftypes.AggregatingTypes { + aggregatedApps[t] = &ftypes.Application{Type: t} + } + + for _, app := range detail.Applications { + a, ok := aggregatedApps[app.Type] + if !ok { + apps = append(apps, app) + continue + } + a.Libraries = append(a.Libraries, app.Libraries...) + } + + for _, app := range aggregatedApps { + if len(app.Libraries) > 0 { + apps = append(apps, *app) + } + } + + // Overwrite Applications + detail.Applications = apps +} + +// We must save secrets from all layers even though they are removed in the uppler layer. +// If the secret was changed at the top level, we need to overwrite it. +func mergeSecrets(secretsMap map[string]ftypes.Secret, newSecret ftypes.Secret, layer ftypes.Layer) map[string]ftypes.Secret { + for i := range newSecret.Findings { // add layer to the Findings from the new secret + newSecret.Findings[i].Layer = layer + } + + secret, ok := secretsMap[newSecret.FilePath] + if !ok { + // Add the new finding if its file doesn't exist before + secretsMap[newSecret.FilePath] = newSecret + } else { + // If the new finding has the same `RuleID` as the finding in the previous layers - use the new finding + for _, previousFinding := range secret.Findings { // secrets from previous layers + if !secretFindingsContains(newSecret.Findings, previousFinding) { + newSecret.Findings = append(newSecret.Findings, previousFinding) + } + } + secretsMap[newSecret.FilePath] = newSecret + } + return secretsMap +} + +func secretFindingsContains(findings []ftypes.SecretFinding, finding ftypes.SecretFinding) bool { + for _, f := range findings { + if f.RuleID == finding.RuleID { + return true + } + } + return false +} diff --git a/pkg/fanal/applier/docker_test.go b/pkg/fanal/applier/docker_test.go new file mode 100644 index 000000000000..425930b9ba2e --- /dev/null +++ b/pkg/fanal/applier/docker_test.go @@ -0,0 +1,1036 @@ +package applier_test + +import ( + "sort" + "testing" + + "github.com/package-url/packageurl-go" + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/applier" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestApplyLayers(t *testing.T) { + tests := []struct { + name string + inputLayers []types.BlobInfo + want types.ArtifactDetail + }{ + { + name: "happy path", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 1, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + OS: types.OS{ + Family: "alpine", + Name: "3.10", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: types.Packages{ + { + Name: "openssl", + Version: "1.2.3", + Release: "4.5.6", + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: types.Bundler, + FilePath: "app/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "gemlibrary1", + Version: "1.2.3", + }, + }, + }, + { + Type: types.Composer, + FilePath: "app/composer.lock", + Libraries: types.Packages{ + { + Name: "phplibrary1", + Version: "6.6.6", + }, + }, + }, + { + Type: types.GemSpec, + FilePath: "usr/local/bundle/specifications/gon-6.3.2.gemspec", + Libraries: types.Packages{ + { + Name: "gon", + Version: "6.3.2", + FilePath: "usr/local/bundle/specifications/gon-6.3.2.gemspec", + }, + }, + }, + }, + }, + { + SchemaVersion: 1, + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: types.Packages{ + { + Name: "openssl", + Version: "1.2.3", + Release: "4.5.6", + }, + { + // added + Name: "musl", + Version: "1.2.4", + Release: "4.5.7", + }, + }, + }, + }, + WhiteoutFiles: []string{"app/composer.lock"}, + }, + { + SchemaVersion: 1, + Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: types.Packages{ + { + Name: "openssl", + Version: "1.2.3", + Release: "4.5.6", + }, + { + Name: "musl", + Version: "1.2.4", + Release: "4.5.8", // updated + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: types.GemSpec, + FilePath: "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", + Libraries: types.Packages{ + { + Name: "activesupport", + Version: "6.0.2.1", + FilePath: "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "alpine", + Name: "3.10", + }, + Packages: types.Packages{ + { + Name: "musl", + Version: "1.2.4", + Release: "4.5.8", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "musl", + Version: "1.2.4-4.5.8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.10", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + }, + { + Name: "openssl", + Version: "1.2.3", + Release: "4.5.6", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "openssl", + Version: "1.2.3-4.5.6", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.10", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + }, + }, + Applications: []types.Application{ + { + Type: types.GemSpec, + Libraries: types.Packages{ + { + Name: "activesupport", + Version: "6.0.2.1", + FilePath: "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", + Layer: types.Layer{ + Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "activesupport", + Version: "6.0.2.1", + }, + }, + }, + { + Name: "gon", + Version: "6.3.2", + FilePath: "usr/local/bundle/specifications/gon-6.3.2.gemspec", + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "gon", + Version: "6.3.2", + }, + }, + }, + }, + }, + { + Type: types.Bundler, + FilePath: "app/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "gemlibrary1", + Version: "1.2.3", + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "gemlibrary1", + Version: "1.2.3", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with digests in libs/packages (as for SBOM)", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 2, + OS: types.OS{ + Family: "debian", + Name: "11.8", + }, + PackageInfos: []types.PackageInfo{ + { + Packages: types.Packages{ + { + ID: "adduser@3.118+deb11u1", + Name: "adduser", + Version: "3.118+deb11u1", + Arch: "all", + SrcName: "adduser", + SrcVersion: "3.118+deb11u1", + Layer: types.Layer{ + Digest: "sha256:e67fdae3559346105027c63e7fb032bba57e62b1fe9f2da23e6fdfb56384e00b", + DiffID: "sha256:633f5bf471f7595b236a21e62dc60beef321db45916363a02ad5af02d794d497", + }, + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: types.PythonPkg, + Libraries: types.Packages{ + { + Name: "pip", + Version: "23.0.1", + Layer: types.Layer{ + DiffID: "sha256:1def056a3160854c9395aa76282dd62172ec08c18a5fa03bb7d50a777c15ba99", + }, + FilePath: "usr/local/lib/python3.9/site-packages/pip-23.0.1.dist-info/METADATA", + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "debian", + Name: "11.8", + }, + Packages: types.Packages{ + { + ID: "adduser@3.118+deb11u1", + Name: "adduser", + Version: "3.118+deb11u1", + Arch: "all", + SrcName: "adduser", + SrcVersion: "3.118+deb11u1", + Layer: types.Layer{ + Digest: "sha256:e67fdae3559346105027c63e7fb032bba57e62b1fe9f2da23e6fdfb56384e00b", + DiffID: "sha256:633f5bf471f7595b236a21e62dc60beef321db45916363a02ad5af02d794d497", + }, + }, + }, + Applications: []types.Application{ + { + Type: types.PythonPkg, + Libraries: types.Packages{ + { + Name: "pip", + Version: "23.0.1", + FilePath: "usr/local/lib/python3.9/site-packages/pip-23.0.1.dist-info/METADATA", + Layer: types.Layer{ + DiffID: "sha256:1def056a3160854c9395aa76282dd62172ec08c18a5fa03bb7d50a777c15ba99", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with merging ubuntu version and ESM", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 1, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + OS: types.OS{ + Family: "ubuntu", + Extended: true, + }, + }, + { + SchemaVersion: 1, + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + OS: types.OS{ + Family: "ubuntu", + Name: "16.04", + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "ubuntu", + Name: "16.04", + Extended: true, + }, + }, + }, + { + name: "happy path with removed and updated lockfile", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 1, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + OS: types.OS{ + Family: "alpine", + Name: "3.10", + }, + Applications: []types.Application{ + { + Type: types.Bundler, + FilePath: "app/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "rails", + Version: "5.0.0", + }, + { + Name: "rack", + Version: "4.0.0", + }, + }, + }, + { + Type: types.Composer, + FilePath: "app/composer.lock", + Libraries: types.Packages{ + { + Name: "phplibrary1", + Version: "6.6.6", + }, + }, + }, + { + Type: types.GemSpec, + FilePath: "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", + Libraries: types.Packages{ + { + Name: "activesupport", + Version: "6.0.2.1", + FilePath: "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", + }, + }, + }, + }, + }, + { + SchemaVersion: 1, + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + Applications: []types.Application{ + { + Type: types.Bundler, + FilePath: "app/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "rails", + Version: "6.0.0", + }, + { + Name: "rack", + Version: "4.0.0", + }, + }, + }, + { + Type: "composer", + FilePath: "app/composer2.lock", + Libraries: types.Packages{ + { + Name: "phplibrary1", + Version: "6.6.6", + }, + }, + }, + }, + WhiteoutFiles: []string{ + "app/composer.lock", + "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "alpine", + Name: "3.10", + }, + Applications: []types.Application{ + { + Type: types.Bundler, + FilePath: "app/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "rack", + Version: "4.0.0", + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "rack", + Version: "4.0.0", + }, + }, + }, + { + Name: "rails", + Version: "6.0.0", + Layer: types.Layer{ + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "rails", + Version: "6.0.0", + }, + }, + }, + }, + }, + { + Type: types.Composer, + FilePath: "app/composer2.lock", + Libraries: types.Packages{ + { + Name: "phplibrary1", + Version: "6.6.6", + Layer: types.Layer{ + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Name: "phplibrary1", + Version: "6.6.6", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with removed and updated secret", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 2, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + CreatedBy: "Line_1", + Secrets: []types.Secret{ + { + FilePath: "usr/secret.txt", + Findings: []types.SecretFinding{ + { + RuleID: "aws-access-key-id", + Category: "AWS", + Severity: "CRITICAL", + Title: "AWS Access Key ID", + StartLine: 1, + EndLine: 1, + Match: "AWS_ACCESS_KEY_ID=********************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "AWS_ACCESS_KEY_ID=********************", + IsCause: true, + Highlighted: "AWS_ACCESS_KEY_ID=********************", + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + }, + }, + }, + }, + { + SchemaVersion: 2, + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + CreatedBy: "Line_2", + Secrets: []types.Secret{ + { + FilePath: "usr/secret.txt", + Findings: []types.SecretFinding{ + { + RuleID: "github-pat", + Category: "GitHub", + Severity: "CRITICAL", + Title: "GitHub Personal Access Token", + StartLine: 1, + EndLine: 1, + Match: "GITHUB_PAT=****************************************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "GITHUB_PAT=****************************************", + IsCause: true, + Highlighted: "GITHUB_PAT=****************************************", + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + { + RuleID: "aws-access-key-id", + Category: "AWS", + Severity: "CRITICAL", + Title: "AWS Access Key ID", + StartLine: 2, + EndLine: 2, + Match: "AWS_ACCESS_KEY_ID=********************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "AWS_ACCESS_KEY_ID=********************", + IsCause: true, + Highlighted: "AWS_ACCESS_KEY_ID=********************", + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + }, + }, + }, + }, + { + SchemaVersion: 2, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + CreatedBy: "Line_3", + WhiteoutFiles: []string{ + "usr/secret.txt", + }, + }, + }, + want: types.ArtifactDetail{ + Secrets: []types.Secret{ + { + FilePath: "usr/secret.txt", + Findings: []types.SecretFinding{ + { + RuleID: "github-pat", + Category: "GitHub", + Severity: "CRITICAL", + Title: "GitHub Personal Access Token", + StartLine: 1, + EndLine: 1, + Match: "GITHUB_PAT=****************************************", + Layer: types.Layer{ + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + CreatedBy: "Line_2", + }, + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "GITHUB_PAT=****************************************", + IsCause: true, + Highlighted: "GITHUB_PAT=****************************************", + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + { + RuleID: "aws-access-key-id", + Category: "AWS", + Severity: "CRITICAL", + Title: "AWS Access Key ID", + StartLine: 2, + EndLine: 2, + Match: "AWS_ACCESS_KEY_ID=********************", + Layer: types.Layer{ + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + CreatedBy: "Line_2", + }, + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "AWS_ACCESS_KEY_ID=********************", + IsCause: true, + Highlighted: "AWS_ACCESS_KEY_ID=********************", + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with status.d and opaque dirs without the trailing slash", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 1, + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + OS: types.OS{ + Family: "debian", + Name: "8", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/openssl", + Packages: types.Packages{ + { + Name: "openssl", + Version: "1.2.3", + Release: "4.5.6", + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: "composer", + FilePath: "app/composer.lock", + Libraries: types.Packages{ + { + Name: "phplibrary1", + Version: "6.6.6", + }, + }, + }, + }, + Licenses: []types.LicenseFile{ + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/openssl/copyright", + Findings: []types.LicenseFinding{ + {Name: "OpenSSL"}, + }, + PkgName: "openssl", + }, + }, + }, + { + SchemaVersion: 1, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/libc", + Packages: types.Packages{ + { + Name: "libc", + Version: "1.2.4", + Release: "4.5.7", + }, + }, + }, + }, + Licenses: []types.LicenseFile{ + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/libc/copyright", + Findings: []types.LicenseFinding{ + {Name: "GPL-2"}, + }, + PkgName: "libc", + }, + }, + OpaqueDirs: []string{"app"}, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "debian", + Name: "8", + }, + Packages: types.Packages{ + { + Name: "libc", + Version: "1.2.4", + Release: "4.5.7", + Licenses: []string{"GPL-2"}, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "debian", + Name: "libc", + Version: "1.2.4-4.5.7", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "debian-8", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + }, + { + Name: "openssl", + Version: "1.2.3", + Release: "4.5.6", + Licenses: []string{"OpenSSL"}, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "debian", + Name: "openssl", + Version: "1.2.3-4.5.6", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "debian-8", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + }, + }, + }, + }, + { + name: "happy path, opaque dirs with the trailing slash", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 1, + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + Applications: []types.Application{ + { + Type: "composer", + FilePath: "app/composer.lock", + Libraries: types.Packages{ + { + Name: "phplibrary1", + Version: "6.6.6", + }, + }, + }, + }, + }, + { + SchemaVersion: 1, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + OpaqueDirs: []string{"app/"}, + }, + }, + want: types.ArtifactDetail{}, + }, + { + name: "happy path with Red Hat content sets", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 1, + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + OS: types.OS{ + Family: "redhat", + Name: "8", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/rpm/Packages", + Packages: types.Packages{ + { + Name: "openssl", + Version: "1.2.3", + Release: "4", + }, + }, + }, + }, + }, + { + SchemaVersion: 1, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + BuildInfo: &types.BuildInfo{ + ContentSets: []string{ + "rhel-8-for-x86_64-baseos-rpms", + "rhel-8-for-x86_64-appstream-rpms", + }, + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/rpm/Packages", + Packages: types.Packages{ + { + Name: "openssl", + Version: "1.2.3", + Release: "4", + }, + { + Name: "libc", + Version: "1.2.4", + Release: "5", + }, + }, + }, + }, + }, + { + SchemaVersion: 1, + Digest: "sha256:a64e5f34c33ed4c5121498e721e24d95dae2c9599bee4aa6d07850702b401406", + DiffID: "sha256:0abd3f2c73de6f02e033f410590111f9339b9500dc07270234f283f2d9a2694b", + BuildInfo: &types.BuildInfo{ + Nvr: "3scale-amp-apicast-gateway-container-1.11-1", + Arch: "x86_64", + }, + }, + { + SchemaVersion: 1, + Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/rpm/Packages", + Packages: types.Packages{ + { + Name: "openssl", + Version: "1.2.3", + Release: "4", + }, + { + Name: "libc", + Version: "1.2.4", + Release: "5", + }, + { + Name: "bash", + Version: "5.6.7", + Release: "8", + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "redhat", + Name: "8", + }, + Packages: types.Packages{ + { + Name: "bash", + Version: "5.6.7", + Release: "8", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "bash", + Version: "5.6.7-8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "redhat-8", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + BuildInfo: &types.BuildInfo{ + Nvr: "3scale-amp-apicast-gateway-container-1.11-1", + Arch: "x86_64", + }, + }, + { + Name: "libc", + Version: "1.2.4", + Release: "5", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "libc", + Version: "1.2.4-5", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "redhat-8", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + BuildInfo: &types.BuildInfo{ + ContentSets: []string{ + "rhel-8-for-x86_64-baseos-rpms", + "rhel-8-for-x86_64-appstream-rpms", + }, + }, + }, + { + Name: "openssl", + Version: "1.2.3", + Release: "4", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "openssl", + Version: "1.2.3-4", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "redhat-8", + }, + }, + }, + }, + Layer: types.Layer{ + Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + BuildInfo: &types.BuildInfo{ + ContentSets: []string{ + "rhel-8-for-x86_64-baseos-rpms", + "rhel-8-for-x86_64-appstream-rpms", + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := applier.ApplyLayers(tt.inputLayers) + sort.Sort(got.Packages) + sort.Slice(got.Applications, func(i, j int) bool { + return got.Applications[i].FilePath < got.Applications[j].FilePath + }) + for _, app := range got.Applications { + sort.Sort(app.Libraries) + } + assert.Equal(t, tt.want, got, tt.name) + }) + } +} diff --git a/pkg/fanal/artifact/artifact.go b/pkg/fanal/artifact/artifact.go new file mode 100644 index 000000000000..bcb3250f8b7a --- /dev/null +++ b/pkg/fanal/artifact/artifact.go @@ -0,0 +1,86 @@ +package artifact + +import ( + "context" + "sort" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/walker" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +type Option struct { + AnalyzerGroup analyzer.Group // It is empty in OSS + DisabledAnalyzers []analyzer.Type + DisabledHandlers []types.HandlerType + SkipFiles []string + SkipDirs []string + FilePatterns []string + NoProgress bool + Insecure bool + Offline bool + AppDirs []string + SBOMSources []string + RekorURL string + Parallel int + AWSRegion string + AWSEndpoint string + FileChecksum bool // For SPDX + + // Git repositories + RepoBranch string + RepoCommit string + RepoTag string + + // For image scanning + ImageOption types.ImageOptions + + MisconfScannerOption misconf.ScannerOption + SecretScannerOption analyzer.SecretScannerOption + LicenseScannerOption analyzer.LicenseScannerOption + + // File walk + WalkOption WalkOption +} + +// WalkOption is a struct that allows users to define a custom walking behavior. +// This option is only available when using Trivy as an imported library and not through CLI flags. +type WalkOption struct { + ErrorCallback walker.ErrorCallback +} + +func (o *Option) AnalyzerOptions() analyzer.AnalyzerOptions { + return analyzer.AnalyzerOptions{ + Group: o.AnalyzerGroup, + FilePatterns: o.FilePatterns, + Parallel: o.Parallel, + DisabledAnalyzers: o.DisabledAnalyzers, + MisconfScannerOption: o.MisconfScannerOption, + SecretScannerOption: o.SecretScannerOption, + LicenseScannerOption: o.LicenseScannerOption, + } +} + +func (o *Option) ConfigAnalyzerOptions() analyzer.ConfigAnalyzerOptions { + return analyzer.ConfigAnalyzerOptions{ + FilePatterns: o.FilePatterns, + DisabledAnalyzers: o.DisabledAnalyzers, + MisconfScannerOption: o.MisconfScannerOption, + SecretScannerOption: o.SecretScannerOption, + } +} + +func (o *Option) Sort() { + sort.Slice(o.DisabledAnalyzers, func(i, j int) bool { + return o.DisabledAnalyzers[i] < o.DisabledAnalyzers[j] + }) + sort.Strings(o.SkipFiles) + sort.Strings(o.SkipDirs) + sort.Strings(o.FilePatterns) +} + +type Artifact interface { + Inspect(ctx context.Context) (reference types.ArtifactReference, err error) + Clean(reference types.ArtifactReference) error +} diff --git a/pkg/fanal/artifact/image/image.go b/pkg/fanal/artifact/image/image.go new file mode 100644 index 000000000000..782d13a86097 --- /dev/null +++ b/pkg/fanal/artifact/image/image.go @@ -0,0 +1,426 @@ +package image + +import ( + "context" + "errors" + "io" + "os" + "reflect" + "strings" + "sync" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/samber/lo" + "golang.org/x/exp/slices" + "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/image" + "github.com/aquasecurity/trivy/pkg/fanal/log" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/walker" + "github.com/aquasecurity/trivy/pkg/parallel" + "github.com/aquasecurity/trivy/pkg/semaphore" +) + +type Artifact struct { + image types.Image + cache cache.ArtifactCache + walker walker.LayerTar + analyzer analyzer.AnalyzerGroup // analyzer for files in container image + configAnalyzer analyzer.ConfigAnalyzerGroup // analyzer for container image config + handlerManager handler.Manager + + artifactOption artifact.Option +} + +type LayerInfo struct { + DiffID string + CreatedBy string // can be empty +} + +func NewArtifact(img types.Image, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) { + // Initialize handlers + handlerManager, err := handler.NewManager(opt) + if err != nil { + return nil, xerrors.Errorf("handler init error: %w", err) + } + + a, err := analyzer.NewAnalyzerGroup(opt.AnalyzerOptions()) + if err != nil { + return nil, xerrors.Errorf("analyzer group error: %w", err) + } + + ca, err := analyzer.NewConfigAnalyzerGroup(opt.ConfigAnalyzerOptions()) + if err != nil { + return nil, xerrors.Errorf("config analyzer group error: %w", err) + } + + return Artifact{ + image: img, + cache: c, + walker: walker.NewLayerTar(opt.SkipFiles, opt.SkipDirs), + analyzer: a, + configAnalyzer: ca, + handlerManager: handlerManager, + + artifactOption: opt, + }, nil +} + +func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { + imageID, err := a.image.ID() + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("unable to get the image ID: %w", err) + } + + configFile, err := a.image.ConfigFile() + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("unable to get the image's config file: %w", err) + } + + diffIDs := a.diffIDs(configFile) + + // Debug + log.Logger.Debugf("Image ID: %s", imageID) + log.Logger.Debugf("Diff IDs: %v", diffIDs) + + // Try retrieving a remote SBOM document + if res, err := a.retrieveRemoteSBOM(ctx); err == nil { + // Found SBOM + return res, nil + } else if !errors.Is(err, errNoSBOMFound) { + // Fail on unexpected error, otherwise it falls into the usual scanning. + return types.ArtifactReference{}, xerrors.Errorf("remote SBOM fetching error: %w", err) + } + + // Try to detect base layers. + baseDiffIDs := a.guessBaseLayers(diffIDs, configFile) + log.Logger.Debugf("Base Layers: %v", baseDiffIDs) + + // Convert image ID and layer IDs to cache keys + imageKey, layerKeys, err := a.calcCacheKeys(imageID, diffIDs) + if err != nil { + return types.ArtifactReference{}, err + } + + // Parse histories and extract a list of "created_by" + layerKeyMap := a.consolidateCreatedBy(diffIDs, layerKeys, configFile) + + missingImage, missingLayers, err := a.cache.MissingBlobs(imageKey, layerKeys) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("unable to get missing layers: %w", err) + } + + missingImageKey := imageKey + if missingImage { + log.Logger.Debugf("Missing image ID in cache: %s", imageID) + } else { + missingImageKey = "" + } + + if err = a.inspect(ctx, missingImageKey, missingLayers, baseDiffIDs, layerKeyMap, configFile); err != nil { + return types.ArtifactReference{}, xerrors.Errorf("analyze error: %w", err) + } + + return types.ArtifactReference{ + Name: a.image.Name(), + Type: types.ArtifactContainerImage, + ID: imageKey, + BlobIDs: layerKeys, + ImageMetadata: types.ImageMetadata{ + ID: imageID, + DiffIDs: diffIDs, + RepoTags: a.image.RepoTags(), + RepoDigests: a.image.RepoDigests(), + ConfigFile: *configFile, + }, + }, nil +} + +func (Artifact) Clean(_ types.ArtifactReference) error { + return nil +} + +func (a Artifact) calcCacheKeys(imageID string, diffIDs []string) (string, []string, error) { + // Pass an empty config scanner option so that the cache key can be the same, even when policies are updated. + imageKey, err := cache.CalcKey(imageID, a.configAnalyzer.AnalyzerVersions(), nil, artifact.Option{}) + if err != nil { + return "", nil, err + } + + hookVersions := a.handlerManager.Versions() + var layerKeys []string + for _, diffID := range diffIDs { + blobKey, err := cache.CalcKey(diffID, a.analyzer.AnalyzerVersions(), hookVersions, a.artifactOption) + if err != nil { + return "", nil, err + } + layerKeys = append(layerKeys, blobKey) + } + return imageKey, layerKeys, nil +} + +func (a Artifact) consolidateCreatedBy(diffIDs, layerKeys []string, configFile *v1.ConfigFile) map[string]LayerInfo { + // save createdBy fields in order of layers + var createdBy []string + for _, h := range configFile.History { + // skip histories for empty layers + if h.EmptyLayer { + continue + } + c := strings.TrimPrefix(h.CreatedBy, "/bin/sh -c ") + c = strings.TrimPrefix(c, "#(nop) ") + createdBy = append(createdBy, c) + } + + // If history detected incorrect - use only diffID + // TODO: our current logic may not detect empty layers correctly in rare cases. + validCreatedBy := len(diffIDs) == len(createdBy) + + layerKeyMap := make(map[string]LayerInfo) + for i, diffID := range diffIDs { + + c := "" + if validCreatedBy { + c = createdBy[i] + } + + layerKey := layerKeys[i] + layerKeyMap[layerKey] = LayerInfo{ + DiffID: diffID, + CreatedBy: c, + } + } + return layerKeyMap +} + +func (a Artifact) inspect(ctx context.Context, missingImage string, layerKeys, baseDiffIDs []string, + layerKeyMap map[string]LayerInfo, configFile *v1.ConfigFile) error { + + var osFound types.OS + p := parallel.NewPipeline(a.artifactOption.Parallel, false, layerKeys, func(ctx context.Context, layerKey string) (any, error) { + layer := layerKeyMap[layerKey] + + // If it is a base layer, secret scanning should not be performed. + var disabledAnalyzers []analyzer.Type + if slices.Contains(baseDiffIDs, layer.DiffID) { + disabledAnalyzers = append(disabledAnalyzers, analyzer.TypeSecret) + } + + layerInfo, err := a.inspectLayer(ctx, layer, disabledAnalyzers) + if err != nil { + return nil, xerrors.Errorf("failed to analyze layer (%s): %w", layer.DiffID, err) + } + if err = a.cache.PutBlob(layerKey, layerInfo); err != nil { + return nil, xerrors.Errorf("failed to store layer: %s in cache: %w", layerKey, err) + } + if lo.IsNotEmpty(layerInfo.OS) { + osFound = layerInfo.OS + } + return nil, nil + + }, nil) + + if err := p.Do(ctx); err != nil { + return xerrors.Errorf("pipeline error: %w", err) + } + + if missingImage != "" { + if err := a.inspectConfig(ctx, missingImage, osFound, configFile); err != nil { + return xerrors.Errorf("unable to analyze config: %w", err) + } + } + + return nil +} + +func (a Artifact) inspectLayer(ctx context.Context, layerInfo LayerInfo, disabled []analyzer.Type) (types.BlobInfo, error) { + log.Logger.Debugf("Missing diff ID in cache: %s", layerInfo.DiffID) + + layerDigest, rc, err := a.uncompressedLayer(layerInfo.DiffID) + if err != nil { + return types.BlobInfo{}, xerrors.Errorf("unable to get uncompressed layer %s: %w", layerInfo.DiffID, err) + } + defer rc.Close() + + // Prepare variables + var wg sync.WaitGroup + opts := analyzer.AnalysisOptions{ + Offline: a.artifactOption.Offline, + FileChecksum: a.artifactOption.FileChecksum, + } + result := analyzer.NewAnalysisResult() + limit := semaphore.New(a.artifactOption.Parallel) + + // Prepare filesystem for post analysis + composite, err := a.analyzer.PostAnalyzerFS() + if err != nil { + return types.BlobInfo{}, xerrors.Errorf("unable to get post analysis filesystem: %w", err) + } + defer composite.Cleanup() + + // Walk a tar layer + opqDirs, whFiles, err := a.walker.Walk(rc, func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + if err = a.analyzer.AnalyzeFile(ctx, &wg, limit, result, "", filePath, info, opener, disabled, opts); err != nil { + return xerrors.Errorf("failed to analyze %s: %w", filePath, err) + } + + // Skip post analysis if the file is not required + analyzerTypes := a.analyzer.RequiredPostAnalyzers(filePath, info) + if len(analyzerTypes) == 0 { + return nil + } + + // Build filesystem for post analysis + tmpFilePath, err := composite.CopyFileToTemp(opener, info) + if err != nil { + return xerrors.Errorf("failed to copy file to temp: %w", err) + } + if err = composite.CreateLink(analyzerTypes, "", filePath, tmpFilePath); err != nil { + return xerrors.Errorf("failed to write a file: %w", err) + } + + return nil + }) + if err != nil { + return types.BlobInfo{}, xerrors.Errorf("walk error: %w", err) + } + + // Wait for all the goroutine to finish. + wg.Wait() + + // Post-analysis + if err = a.analyzer.PostAnalyze(ctx, composite, result, opts); err != nil { + return types.BlobInfo{}, xerrors.Errorf("post analysis error: %w", err) + } + + // Sort the analysis result for consistent results + result.Sort() + + blobInfo := types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: layerDigest, + DiffID: layerInfo.DiffID, + CreatedBy: layerInfo.CreatedBy, + OpaqueDirs: opqDirs, + WhiteoutFiles: whFiles, + OS: result.OS, + Repository: result.Repository, + PackageInfos: result.PackageInfos, + Applications: result.Applications, + Misconfigurations: result.Misconfigurations, + Secrets: result.Secrets, + Licenses: result.Licenses, + CustomResources: result.CustomResources, + + // For Red Hat + BuildInfo: result.BuildInfo, + } + + // Call post handlers to modify blob info + if err = a.handlerManager.PostHandle(ctx, result, &blobInfo); err != nil { + return types.BlobInfo{}, xerrors.Errorf("post handler error: %w", err) + } + + return blobInfo, nil +} + +func (a Artifact) diffIDs(configFile *v1.ConfigFile) []string { + if configFile == nil { + return nil + } + return lo.Map(configFile.RootFS.DiffIDs, func(diffID v1.Hash, _ int) string { + return diffID.String() + }) +} + +func (a Artifact) uncompressedLayer(diffID string) (string, io.ReadCloser, error) { + // diffID is a hash of the uncompressed layer + h, err := v1.NewHash(diffID) + if err != nil { + return "", nil, xerrors.Errorf("invalid layer ID (%s): %w", diffID, err) + } + + layer, err := a.image.LayerByDiffID(h) + if err != nil { + return "", nil, xerrors.Errorf("failed to get the layer (%s): %w", diffID, err) + } + + // digest is a hash of the compressed layer + var digest string + if a.isCompressed(layer) { + d, err := layer.Digest() + if err != nil { + return "", nil, xerrors.Errorf("failed to get the digest (%s): %w", diffID, err) + } + digest = d.String() + } + + rc, err := layer.Uncompressed() + if err != nil { + return "", nil, xerrors.Errorf("failed to get the layer content (%s): %w", diffID, err) + } + return digest, rc, nil +} + +// ref. https://github.com/google/go-containerregistry/issues/701 +func (a Artifact) isCompressed(l v1.Layer) bool { + _, uncompressed := reflect.TypeOf(l).Elem().FieldByName("UncompressedLayer") + return !uncompressed +} + +func (a Artifact) inspectConfig(ctx context.Context, imageID string, osFound types.OS, config *v1.ConfigFile) error { + result := lo.FromPtr(a.configAnalyzer.AnalyzeImageConfig(ctx, osFound, config)) + + info := types.ArtifactInfo{ + SchemaVersion: types.ArtifactJSONSchemaVersion, + Architecture: config.Architecture, + Created: config.Created.Time, + DockerVersion: config.DockerVersion, + OS: config.OS, + Misconfiguration: result.Misconfiguration, + Secret: result.Secret, + HistoryPackages: result.HistoryPackages, + } + + if err := a.cache.PutArtifact(imageID, info); err != nil { + return xerrors.Errorf("failed to put image info into the cache: %w", err) + } + + return nil +} + +// guessBaseLayers guesses layers in base image (call base layers). +func (a Artifact) guessBaseLayers(diffIDs []string, configFile *v1.ConfigFile) []string { + if configFile == nil { + return nil + } + + baseImageIndex := image.GuessBaseImageIndex(configFile.History) + + // Diff IDs don't include empty layers, so the index is different from histories + var diffIDIndex int + var baseDiffIDs []string + for i, h := range configFile.History { + // It is no longer base layer. + if i > baseImageIndex { + break + } + // Empty layers are not included in diff IDs. + if h.EmptyLayer { + continue + } + + if diffIDIndex >= len(diffIDs) { + // something wrong... + return nil + } + baseDiffIDs = append(baseDiffIDs, diffIDs[diffIDIndex]) + diffIDIndex++ + } + return baseDiffIDs +} diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go new file mode 100644 index 000000000000..a99823dde2c0 --- /dev/null +++ b/pkg/fanal/artifact/image/image_test.go @@ -0,0 +1,2214 @@ +package image_test + +import ( + "context" + "errors" + "testing" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + image2 "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/image" + "github.com/aquasecurity/trivy/pkg/fanal/types" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/imgconf/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/php/composer" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/bundler" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/licensing" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/debian" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/dpkg" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/repo/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" + _ "github.com/aquasecurity/trivy/pkg/fanal/handler/sysfile" +) + +func TestArtifact_Inspect(t *testing.T) { + alpinePkgs := types.Packages{ + { + ID: "alpine-baselayout@3.2.0-r3", + Name: "alpine-baselayout", + Version: "3.2.0-r3", + SrcName: "alpine-baselayout", + SrcVersion: "3.2.0-r3", + Licenses: []string{"GPL-2.0"}, + Digest: "sha1:8f373f5b329c3aaf136eb30c63a387661ee0f3d0", + DependsOn: []string{ + "busybox@1.31.1-r9", + "musl@1.1.24-r2", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "etc/hosts", + "etc/sysctl.conf", + "etc/group", + "etc/protocols", + "etc/fstab", + "etc/mtab", + "etc/profile", + "etc/shells", + "etc/motd", + "etc/inittab", + "etc/hostname", + "etc/modules", + "etc/services", + "etc/shadow", + "etc/passwd", + "etc/profile.d/locale", + "etc/profile.d/color_prompt", + "etc/sysctl.d/00-alpine.conf", + "etc/modprobe.d/i386.conf", + "etc/modprobe.d/blacklist.conf", + "etc/modprobe.d/aliases.conf", + "etc/modprobe.d/kms.conf", + "etc/crontabs/root", + "sbin/mkmntdirs", + "var/run", + "var/spool/mail", + "var/spool/cron/crontabs", + }, + }, + { + ID: "alpine-keys@2.1-r2", + Name: "alpine-keys", + Version: "2.1-r2", + SrcName: "alpine-keys", + SrcVersion: "2.1-r2", + Licenses: []string{"MIT"}, + Arch: "x86_64", + Digest: "sha1:64929f85b7f8b4adbb664d905410312936b79d9b", + InstalledFiles: []string{ + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", + "usr/share/apk/keys/aarch64/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "usr/share/apk/keys/ppc64le/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/s390x/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", + "usr/share/apk/keys/armhf/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + }, + }, + { + ID: "apk-tools@2.10.4-r3", + Name: "apk-tools", + Version: "2.10.4-r3", + SrcName: "apk-tools", + SrcVersion: "2.10.4-r3", + Licenses: []string{"GPL-2.0"}, + Digest: "sha1:b15ad0c90e4493dfdc948d6b90a8e020da8936ef", + DependsOn: []string{ + "libcrypto1.1@1.1.1d-r3", + "libssl1.1@1.1.1d-r3", + "musl@1.1.24-r2", + "zlib@1.2.11-r3", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "sbin/apk", + }, + }, + { + ID: "busybox@1.31.1-r9", + Name: "busybox", + Version: "1.31.1-r9", + SrcName: "busybox", + SrcVersion: "1.31.1-r9", + Licenses: []string{"GPL-2.0"}, + Digest: "sha1:a457703d71654811ea28d8d27a5cfc49ece27b34", + DependsOn: []string{ + "musl@1.1.24-r2", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "bin/busybox", + "bin/sh", + "etc/securetty", + "etc/udhcpd.conf", + "etc/logrotate.d/acpid", + "etc/network/if-up.d/dad", + "usr/share/udhcpc/default.script", + }, + }, + { + ID: "ca-certificates-cacert@20191127-r1", + Name: "ca-certificates-cacert", + Version: "20191127-r1", + SrcName: "ca-certificates", + SrcVersion: "20191127-r1", + Licenses: []string{ + "MPL-2.0", + "GPL-2.0", + }, + Arch: "x86_64", + Digest: "sha1:3aeb8a90d7179d2a187782e980a964494e08c5fb", + InstalledFiles: []string{ + "etc/ssl/cert.pem", + }, + }, + { + ID: "libc-utils@0.7.2-r0", + Name: "libc-utils", + Version: "0.7.2-r0", + SrcName: "libc-dev", + SrcVersion: "0.7.2-r0", + Licenses: []string{"BSD-3-Clause"}, + Digest: "sha1:a7bf32bd32c6d3de2d1c4d7e753a0919b998cd01", + DependsOn: []string{ + "musl-utils@1.1.24-r2", + }, + Arch: "x86_64", + }, + { + ID: "libcrypto1.1@1.1.1d-r3", + Name: "libcrypto1.1", + Version: "1.1.1d-r3", + SrcName: "openssl", + SrcVersion: "1.1.1d-r3", + Licenses: []string{"OpenSSL"}, + Digest: "sha1:dd8fb9a3cce7b2bcf954271da62fb85dac2b106a", + DependsOn: []string{ + "musl@1.1.24-r2", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "etc/ssl/openssl.cnf.dist", + "etc/ssl/ct_log_list.cnf", + "etc/ssl/ct_log_list.cnf.dist", + "etc/ssl/openssl.cnf", + "etc/ssl/misc/CA.pl", + "etc/ssl/misc/tsget.pl", + "etc/ssl/misc/tsget", + "lib/libcrypto.so.1.1", + "usr/lib/libcrypto.so.1.1", + "usr/lib/engines-1.1/capi.so", + "usr/lib/engines-1.1/padlock.so", + "usr/lib/engines-1.1/afalg.so", + }, + }, + { + ID: "libssl1.1@1.1.1d-r3", + Name: "libssl1.1", + Version: "1.1.1d-r3", + SrcName: "openssl", + SrcVersion: "1.1.1d-r3", + Licenses: []string{"OpenSSL"}, + Digest: "sha1:938d46e41b3e56b339a3aeb2d02fad3d75728f35", + DependsOn: []string{ + "libcrypto1.1@1.1.1d-r3", + "musl@1.1.24-r2", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "lib/libssl.so.1.1", + "usr/lib/libssl.so.1.1", + }, + }, + { + ID: "libtls-standalone@2.9.1-r0", + Name: "libtls-standalone", + Version: "2.9.1-r0", + SrcName: "libtls-standalone", + SrcVersion: "2.9.1-r0", + Licenses: []string{"ISC"}, + Digest: "sha1:b2e5627a56378ea6eeb962a8f33722df9393c1c5", + DependsOn: []string{ + "ca-certificates-cacert@20191127-r1", + "libcrypto1.1@1.1.1d-r3", + "libssl1.1@1.1.1d-r3", + "musl@1.1.24-r2", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/lib/libtls-standalone.so.1.0.0", + "usr/lib/libtls-standalone.so.1", + }, + }, + { + ID: "musl@1.1.24-r2", + Name: "musl", + Version: "1.1.24-r2", + SrcName: "musl", + SrcVersion: "1.1.24-r2", + Licenses: []string{"MIT"}, + Arch: "x86_64", + Digest: "sha1:cb2316a189ebee5282c4a9bd98794cc2477a74c6", + InstalledFiles: []string{ + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1", + }, + }, + { + ID: "musl-utils@1.1.24-r2", + Name: "musl-utils", + Version: "1.1.24-r2", + SrcName: "musl", + SrcVersion: "1.1.24-r2", + Licenses: []string{ + "MIT", + "BSD-3-Clause", + "GPL-2.0", + }, + Digest: "sha1:6d3b45e79dbab444ca7cbfa59e2833203be6fb6a", + DependsOn: []string{ + "musl@1.1.24-r2", + "scanelf@1.2.4-r0", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "sbin/ldconfig", + "usr/bin/iconv", + "usr/bin/ldd", + "usr/bin/getconf", + "usr/bin/getent", + }, + }, + { + ID: "scanelf@1.2.4-r0", + Name: "scanelf", + Version: "1.2.4-r0", + SrcName: "pax-utils", + SrcVersion: "1.2.4-r0", + Licenses: []string{"GPL-2.0"}, + Digest: "sha1:d6147beb32bff803b5d9f83a3bec7ab319087185", + DependsOn: []string{ + "musl@1.1.24-r2", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/bin/scanelf", + }, + }, + { + ID: "ssl_client@1.31.1-r9", + Name: "ssl_client", + Version: "1.31.1-r9", + SrcName: "busybox", + SrcVersion: "1.31.1-r9", + Licenses: []string{"GPL-2.0"}, + Digest: "sha1:3b685152af320120ae8941c740d3376b54e43c10", + DependsOn: []string{ + "libtls-standalone@2.9.1-r0", + "musl@1.1.24-r2", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/bin/ssl_client", + }, + }, + { + ID: "zlib@1.2.11-r3", + Name: "zlib", + Version: "1.2.11-r3", + SrcName: "zlib", + SrcVersion: "1.2.11-r3", + Licenses: []string{"Zlib"}, + Digest: "sha1:acca078ee8baa93e005f57b2fae359c1efd443cd", + DependsOn: []string{ + "musl@1.1.24-r2", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "lib/libz.so.1.2.11", + "lib/libz.so.1", + }, + }, + } + + tests := []struct { + name string + imagePath string + artifactOpt artifact.Option + missingBlobsExpectation cache.ArtifactCacheMissingBlobsExpectation + putBlobExpectations []cache.ArtifactCachePutBlobExpectation + putArtifactExpectations []cache.ArtifactCachePutArtifactExpectation + want types.ArtifactReference + wantErr string + }{ + { + name: "happy path", + imagePath: "../../test/testdata/alpine-311.tar.gz", + artifactOpt: artifact.Option{ + LicenseScannerOption: analyzer.LicenseScannerOption{Full: true}, + }, + missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ + Args: cache.ArtifactCacheMissingBlobsArgs{ + ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", + BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + }, + Returns: cache.ArtifactCacheMissingBlobsReturns{ + MissingArtifact: true, + MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + }, + }, + putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + CreatedBy: "ADD file:0c4555f363c2672e350001f1293e689875a3760afe7b3f9146886afe67121cba in / ", + OS: types.OS{ + Family: "alpine", + Name: "3.11.5", + }, + Repository: &types.Repository{ + Family: "alpine", + Release: "3.11", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: alpinePkgs, + }, + }, + Licenses: []types.LicenseFile{ + { + Type: "header", + FilePath: "etc/ssl/misc/CA.pl", + Findings: []types.LicenseFinding{ + { + Name: "OpenSSL", + Confidence: 1, + Link: "https://spdx.org/licenses/OpenSSL.html", + }, + }, + }, + { + Type: "header", + FilePath: "etc/ssl/misc/tsget.pl", + Findings: []types.LicenseFinding{ + { + Name: "OpenSSL", + Confidence: 1, + Link: "https://spdx.org/licenses/OpenSSL.html", + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + }, + putArtifactExpectations: []cache.ArtifactCachePutArtifactExpectation{ + { + Args: cache.ArtifactCachePutArtifactArgs{ + ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: types.ArtifactJSONSchemaVersion, + Architecture: "amd64", + Created: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC), + DockerVersion: "18.09.7", + OS: "linux", + }, + }, + }, + }, + want: types.ArtifactReference{ + Name: "../../test/testdata/alpine-311.tar.gz", + Type: types.ArtifactContainerImage, + ID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", + BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + ImageMetadata: types.ImageMetadata{ + ID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + DiffIDs: []string{ + "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + }, + ConfigFile: v1.ConfigFile{ + Architecture: "amd64", + Author: "", + Container: "fb71ddde5f6411a82eb056a9190f0cc1c80d7f77a8509ee90a2054428edb0024", + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC)}, + DockerVersion: "18.09.7", + History: []v1.History{ + { + Author: "", + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 27725872, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:0c4555f363c2672e350001f1293e689875a3760afe7b3f9146886afe67121cba in / ", + Comment: "", + EmptyLayer: false, + }, + { + Author: "", + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + Comment: "", + EmptyLayer: true, + }, + }, + OS: "linux", + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + }, + }, + }, + Config: v1.Config{ + Cmd: []string{"/bin/sh"}, + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + Hostname: "", + Image: "sha256:74df73bb19fbfc7fb5ab9a8234b3d98ee2fb92df5b824496679802685205ab8c", + ArgsEscaped: true, + }, + }, + }, + }, + }, + { + name: "happy path: include lock files", + imagePath: "../../test/testdata/vuln-image.tar.gz", + artifactOpt: artifact.Option{ + LicenseScannerOption: analyzer.LicenseScannerOption{Full: true}, + }, + missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ + Args: cache.ArtifactCacheMissingBlobsArgs{ + ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", + BlobIDs: []string{ + "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + }, + }, + Returns: cache.ArtifactCacheMissingBlobsReturns{ + MissingBlobIDs: []string{ + "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + }, + }, + }, + putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + CreatedBy: "bazel build ...", + OS: types.OS{ + Family: "debian", + Name: "9.9", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/base", + Packages: types.Packages{ + { + ID: "base-files@9.9+deb9u9", + Name: "base-files", + Version: "9.9+deb9u9", + SrcName: "base-files", + SrcVersion: "9.9+deb9u9", + Maintainer: "Santiago Vila ", + Arch: "amd64", + }, + }, + }, + { + FilePath: "var/lib/dpkg/status.d/netbase", + Packages: types.Packages{ + { + ID: "netbase@5.4", + Name: "netbase", + Version: "5.4", + SrcName: "netbase", + SrcVersion: "5.4", + Maintainer: "Marco d'Itri ", + Arch: "all", + }, + }, + }, + { + FilePath: "var/lib/dpkg/status.d/tzdata", + Packages: types.Packages{ + { + ID: "tzdata@2019a-0+deb9u1", + Name: "tzdata", + Version: "2019a", + SrcName: "tzdata", + Release: "0+deb9u1", + SrcVersion: "2019a", + SrcRelease: "0+deb9u1", + Maintainer: "GNU Libc Maintainers ", + Arch: "all", + }, + }, + }, + }, + Licenses: []types.LicenseFile{ + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/base-files/copyright", + Findings: []types.LicenseFinding{ + {Name: "GPL-3.0"}, + }, + PkgName: "base-files", + }, + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/ca-certificates/copyright", + Findings: []types.LicenseFinding{ + {Name: "GPL-2.0"}, + {Name: "MPL-2.0"}, + }, + PkgName: "ca-certificates", + }, + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/netbase/copyright", + Findings: []types.LicenseFinding{ + {Name: "GPL-2.0"}, + }, + PkgName: "netbase", + }, + }, + }, + }, + }, + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + CreatedBy: "bazel build ...", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/libc6", + Packages: types.Packages{ + { + ID: "libc6@2.24-11+deb9u4", + Name: "libc6", + Version: "2.24", + Release: "11+deb9u4", + SrcName: "glibc", + SrcVersion: "2.24", + SrcRelease: "11+deb9u4", + Maintainer: "GNU Libc Maintainers ", + Arch: "amd64", + }, + }, + }, + { + FilePath: "var/lib/dpkg/status.d/libssl1", + Packages: types.Packages{ + { + ID: "libssl1.1@1.1.0k-1~deb9u1", + Name: "libssl1.1", + Version: "1.1.0k", + SrcName: "openssl", + Release: "1~deb9u1", + SrcVersion: "1.1.0k", + SrcRelease: "1~deb9u1", + Maintainer: "Debian OpenSSL Team ", + Arch: "amd64", + }, + }, + }, + { + FilePath: "var/lib/dpkg/status.d/openssl", + Packages: types.Packages{ + { + ID: "openssl@1.1.0k-1~deb9u1", + Name: "openssl", + Version: "1.1.0k", + SrcName: "openssl", + Release: "1~deb9u1", + SrcVersion: "1.1.0k", + SrcRelease: "1~deb9u1", + Maintainer: "Debian OpenSSL Team ", + Arch: "amd64", + }, + }, + }, + }, + Licenses: []types.LicenseFile{ + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/libc6/copyright", + Findings: []types.LicenseFinding{ + {Name: "LGPL-2.1"}, + {Name: "GPL-2.0"}, + }, + PkgName: "libc6", + }, + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/libssl1.1/copyright", + Findings: []types.LicenseFinding{ + { + Name: "OpenSSL", + Confidence: 0.9960474308300395, + Link: "https://spdx.org/licenses/OpenSSL.html", + }, + }, + PkgName: "libssl1.1", + }, + { + Type: types.LicenseTypeDpkg, + FilePath: "usr/share/doc/openssl/copyright", + Findings: []types.LicenseFinding{ + { + Name: "OpenSSL", + Confidence: 0.9960474308300395, + Link: "https://spdx.org/licenses/OpenSSL.html", + }, + }, + PkgName: "openssl", + }, + }, + }, + }, + }, + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + CreatedBy: "COPY file:842584685f26edb24dc305d76894f51cfda2bad0c24a05e727f9d4905d184a70 in /php-app/composer.lock ", + Applications: []types.Application{ + { + Type: "composer", + FilePath: "php-app/composer.lock", + Libraries: types.Packages{ + { + ID: "guzzlehttp/guzzle@6.2.0", + Name: "guzzlehttp/guzzle", + Version: "6.2.0", + Licenses: []string{"MIT"}, + DependsOn: []string{ + "guzzlehttp/promises@v1.3.1", + "guzzlehttp/psr7@1.5.2", + }, + Locations: []types.Location{ + { + StartLine: 9, + EndLine: 73, + }, + }, + }, + { + ID: "guzzlehttp/promises@v1.3.1", + Name: "guzzlehttp/promises", + Version: "v1.3.1", + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 74, + EndLine: 124, + }, + }, + }, + { + ID: "guzzlehttp/psr7@1.5.2", + Name: "guzzlehttp/psr7", + Version: "1.5.2", + Licenses: []string{"MIT"}, + DependsOn: []string{ + "psr/http-message@1.0.1", + "ralouphie/getallheaders@2.0.5", + }, + Locations: []types.Location{ + { + StartLine: 125, + EndLine: 191, + }, + }, + }, + { + ID: "laravel/installer@v2.0.1", + Name: "laravel/installer", + Version: "v2.0.1", + Licenses: []string{"MIT"}, + DependsOn: []string{ + "guzzlehttp/guzzle@6.2.0", + "symfony/console@v4.2.7", + "symfony/filesystem@v4.2.7", + "symfony/process@v4.2.7", + }, + Locations: []types.Location{ + { + StartLine: 192, + EndLine: 237, + }, + }, + }, + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Licenses: []string{"MIT"}, + DependsOn: []string{"pear/pear_exception@v1.0.0"}, + Locations: []types.Location{ + { + StartLine: 238, + EndLine: 290, + }, + }, + }, + { + ID: "pear/pear_exception@v1.0.0", + Name: "pear/pear_exception", + Version: "v1.0.0", + Licenses: []string{"BSD-2-Clause"}, + Locations: []types.Location{ + { + StartLine: 291, + EndLine: 345, + }, + }, + }, + { + ID: "psr/http-message@1.0.1", + Name: "psr/http-message", + Version: "1.0.1", + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 346, + EndLine: 395, + }, + }, + }, + { + ID: "ralouphie/getallheaders@2.0.5", + Name: "ralouphie/getallheaders", + Version: "2.0.5", + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 396, + EndLine: 435, + }, + }, + }, + { + ID: "symfony/console@v4.2.7", + Name: "symfony/console", + Version: "v4.2.7", + Licenses: []string{"MIT"}, + DependsOn: []string{ + "symfony/contracts@v1.0.2", + "symfony/polyfill-mbstring@v1.11.0", + }, + Locations: []types.Location{ + { + StartLine: 436, + EndLine: 507, + }, + }, + }, + { + ID: "symfony/contracts@v1.0.2", + Name: "symfony/contracts", + Version: "v1.0.2", + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 508, + EndLine: 575, + }, + }, + }, + { + ID: "symfony/filesystem@v4.2.7", + Name: "symfony/filesystem", + Version: "v4.2.7", + Licenses: []string{"MIT"}, + DependsOn: []string{"symfony/polyfill-ctype@v1.11.0"}, + Locations: []types.Location{ + { + StartLine: 576, + EndLine: 625, + }, + }, + }, + { + ID: "symfony/polyfill-ctype@v1.11.0", + Name: "symfony/polyfill-ctype", + Version: "v1.11.0", + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 626, + EndLine: 683, + }, + }, + }, + { + ID: "symfony/polyfill-mbstring@v1.11.0", + Name: "symfony/polyfill-mbstring", + Version: "v1.11.0", + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 684, + EndLine: 742, + }, + }, + }, + { + ID: "symfony/process@v4.2.7", + Name: "symfony/process", + Version: "v4.2.7", + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 743, + EndLine: 791, + }, + }, + }, + }, + }, + }, + OpaqueDirs: []string{"php-app/"}, + }, + }, + }, + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:a4595c43a874856bf95f3bfc4fbf78bbaa04c92c726276d4f64193a47ced0566", + CreatedBy: "COPY file:c6d0373d380252b91829a5bb3c81d5b1afa574c91cef7752d18170a231c31f6d in /ruby-app/Gemfile.lock ", + Applications: []types.Application{ + { + Type: "bundler", + FilePath: "ruby-app/Gemfile.lock", + Libraries: types.Packages{ + { + ID: "actioncable@5.2.3", + Name: "actioncable", + Version: "5.2.3", + Indirect: true, + DependsOn: []string{ + "actionpack@5.2.3", + "nio4r@2.3.1", + "websocket-driver@0.7.0", + }, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + { + ID: "actionmailer@5.2.3", + Name: "actionmailer", + Version: "5.2.3", + Indirect: true, + DependsOn: []string{ + "actionpack@5.2.3", + "actionview@5.2.3", + "activejob@5.2.3", + "mail@2.7.1", + "rails-dom-testing@2.0.3", + }, + Locations: []types.Location{ + { + StartLine: 8, + EndLine: 8, + }, + }, + }, + { + ID: "actionpack@5.2.3", + Name: "actionpack", + Version: "5.2.3", + Indirect: true, + DependsOn: []string{ + "actionview@5.2.3", + "activesupport@5.2.3", + "rack@2.0.7", + "rack-test@1.1.0", + "rails-dom-testing@2.0.3", + "rails-html-sanitizer@1.0.3", + }, + Locations: []types.Location{ + { + StartLine: 14, + EndLine: 14, + }, + }, + }, + { + ID: "actionview@5.2.3", + Name: "actionview", + Version: "5.2.3", + Indirect: true, + DependsOn: []string{ + "activesupport@5.2.3", + "builder@3.2.3", + "erubi@1.8.0", + "rails-dom-testing@2.0.3", + "rails-html-sanitizer@1.0.3", + }, + Locations: []types.Location{ + { + StartLine: 21, + EndLine: 21, + }, + }, + }, + { + ID: "activejob@5.2.3", + Name: "activejob", + Version: "5.2.3", + Indirect: true, + DependsOn: []string{ + "activesupport@5.2.3", + "globalid@0.4.2", + }, + Locations: []types.Location{ + { + StartLine: 27, + EndLine: 27, + }, + }, + }, + { + ID: "activemodel@5.2.3", + Name: "activemodel", + Version: "5.2.3", + Indirect: true, + DependsOn: []string{"activesupport@5.2.3"}, + Locations: []types.Location{ + { + StartLine: 30, + EndLine: 30, + }, + }, + }, + { + ID: "activerecord@5.2.3", + Name: "activerecord", + Version: "5.2.3", + Indirect: true, + DependsOn: []string{ + "activemodel@5.2.3", + "activesupport@5.2.3", + "arel@9.0.0", + }, + Locations: []types.Location{ + { + StartLine: 32, + EndLine: 32, + }, + }, + }, + { + ID: "activestorage@5.2.3", + Name: "activestorage", + Version: "5.2.3", + Indirect: true, + DependsOn: []string{ + "actionpack@5.2.3", + "activerecord@5.2.3", + "marcel@0.3.3", + }, + Locations: []types.Location{ + { + StartLine: 36, + EndLine: 36, + }, + }, + }, + { + ID: "activesupport@5.2.3", + Name: "activesupport", + Version: "5.2.3", + Indirect: true, + DependsOn: []string{ + "concurrent-ruby@1.1.5", + "i18n@1.6.0", + "minitest@5.11.3", + "tzinfo@1.2.5", + }, + Locations: []types.Location{ + { + StartLine: 40, + EndLine: 40, + }, + }, + }, + { + ID: "arel@9.0.0", + Name: "arel", + Version: "9.0.0", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 45, + EndLine: 45, + }, + }, + }, + { + ID: "ast@2.4.0", + Name: "ast", + Version: "2.4.0", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 46, + EndLine: 46, + }, + }, + }, + { + ID: "builder@3.2.3", + Name: "builder", + Version: "3.2.3", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 47, + EndLine: 47, + }, + }, + }, + { + ID: "coderay@1.1.2", + Name: "coderay", + Version: "1.1.2", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 48, + EndLine: 48, + }, + }, + }, + { + ID: "concurrent-ruby@1.1.5", + Name: "concurrent-ruby", + Version: "1.1.5", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 49, + EndLine: 49, + }, + }, + }, + { + ID: "crass@1.0.4", + Name: "crass", + Version: "1.0.4", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 50, + EndLine: 50, + }, + }, + }, + { + ID: "dotenv@2.7.2", + Name: "dotenv", + Version: "2.7.2", + Indirect: false, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 51, + EndLine: 51, + }, + }, + }, + { + ID: "erubi@1.8.0", + Name: "erubi", + Version: "1.8.0", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 52, + EndLine: 52, + }, + }, + }, + { + ID: "faker@1.9.3", + Name: "faker", + Version: "1.9.3", + Indirect: false, + DependsOn: []string{"i18n@1.6.0"}, + Locations: []types.Location{ + { + StartLine: 53, + EndLine: 53, + }, + }, + }, + { + ID: "globalid@0.4.2", + Name: "globalid", + Version: "0.4.2", + Indirect: true, + DependsOn: []string{"activesupport@5.2.3"}, + Locations: []types.Location{ + { + StartLine: 55, + EndLine: 55, + }, + }, + }, + { + ID: "i18n@1.6.0", + Name: "i18n", + Version: "1.6.0", + Indirect: true, + DependsOn: []string{"concurrent-ruby@1.1.5"}, + Locations: []types.Location{ + { + StartLine: 57, + EndLine: 57, + }, + }, + }, + { + ID: "jaro_winkler@1.5.2", + Name: "jaro_winkler", + Version: "1.5.2", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 59, + EndLine: 59, + }, + }, + }, + { + ID: "json@2.2.0", + Name: "json", + Version: "2.2.0", + Indirect: false, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 60, + EndLine: 60, + }, + }, + }, + { + ID: "loofah@2.2.3", + Name: "loofah", + Version: "2.2.3", + Indirect: true, + DependsOn: []string{ + "crass@1.0.4", + "nokogiri@1.10.3", + }, + Locations: []types.Location{ + { + StartLine: 61, + EndLine: 61, + }, + }, + }, + { + ID: "mail@2.7.1", + Name: "mail", + Version: "2.7.1", + Indirect: true, + DependsOn: []string{"mini_mime@1.0.1"}, + Locations: []types.Location{ + { + StartLine: 64, + EndLine: 64, + }, + }, + }, + { + ID: "marcel@0.3.3", + Name: "marcel", + Version: "0.3.3", + Indirect: true, + DependsOn: []string{"mimemagic@0.3.3"}, + Locations: []types.Location{ + { + StartLine: 66, + EndLine: 66, + }, + }, + }, + { + ID: "method_source@0.9.2", + Name: "method_source", + Version: "0.9.2", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 68, + EndLine: 68, + }, + }, + }, + { + ID: "mimemagic@0.3.3", + Name: "mimemagic", + Version: "0.3.3", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 69, + EndLine: 69, + }, + }, + }, + { + ID: "mini_mime@1.0.1", + Name: "mini_mime", + Version: "1.0.1", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 70, + EndLine: 70, + }, + }, + }, + { + ID: "mini_portile2@2.4.0", + Name: "mini_portile2", + Version: "2.4.0", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 71, + EndLine: 71, + }, + }, + }, + { + ID: "minitest@5.11.3", + Name: "minitest", + Version: "5.11.3", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 72, + EndLine: 72, + }, + }, + }, + { + ID: "nio4r@2.3.1", + Name: "nio4r", + Version: "2.3.1", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 73, + EndLine: 73, + }, + }, + }, + { + ID: "nokogiri@1.10.3", + Name: "nokogiri", + Version: "1.10.3", + Indirect: true, + DependsOn: []string{"mini_portile2@2.4.0"}, + Locations: []types.Location{ + { + StartLine: 74, + EndLine: 74, + }, + }, + }, + { + ID: "parallel@1.17.0", + Name: "parallel", + Version: "1.17.0", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 76, + EndLine: 76, + }, + }, + }, + { + ID: "parser@2.6.3.0", + Name: "parser", + Version: "2.6.3.0", + Indirect: true, + DependsOn: []string{"ast@2.4.0"}, + Locations: []types.Location{ + { + StartLine: 77, + EndLine: 77, + }, + }, + }, + { + ID: "pry@0.12.2", + Name: "pry", + Version: "0.12.2", + Indirect: false, + DependsOn: []string{ + "coderay@1.1.2", + "method_source@0.9.2", + }, + Locations: []types.Location{ + { + StartLine: 79, + EndLine: 79, + }, + }, + }, + { + ID: "psych@3.1.0", + Name: "psych", + Version: "3.1.0", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 82, + EndLine: 82, + }, + }, + }, + { + ID: "rack@2.0.7", + Name: "rack", + Version: "2.0.7", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 83, + EndLine: 83, + }, + }, + }, + { + ID: "rack-test@1.1.0", + Name: "rack-test", + Version: "1.1.0", + Indirect: true, + DependsOn: []string{"rack@2.0.7"}, + Locations: []types.Location{ + { + StartLine: 84, + EndLine: 84, + }, + }, + }, + { + ID: "rails@5.2.0", + Name: "rails", + Version: "5.2.0", + Indirect: false, + DependsOn: []string{ + "actioncable@5.2.3", + "actionmailer@5.2.3", + "actionpack@5.2.3", + "actionview@5.2.3", + "activejob@5.2.3", + "activemodel@5.2.3", + "activerecord@5.2.3", + "activestorage@5.2.3", + "activesupport@5.2.3", + "railties@5.2.3", + "sprockets-rails@3.2.1", + }, + Locations: []types.Location{ + { + StartLine: 86, + EndLine: 86, + }, + }, + }, + { + ID: "rails-dom-testing@2.0.3", + Name: "rails-dom-testing", + Version: "2.0.3", + Indirect: true, + DependsOn: []string{ + "activesupport@5.2.3", + "nokogiri@1.10.3", + }, + Locations: []types.Location{ + { + StartLine: 99, + EndLine: 99, + }, + }, + }, + { + ID: "rails-html-sanitizer@1.0.3", + Name: "rails-html-sanitizer", + Version: "1.0.3", + Indirect: true, + DependsOn: []string{"loofah@2.2.3"}, + Locations: []types.Location{ + { + StartLine: 102, + EndLine: 102, + }, + }, + }, + { + ID: "railties@5.2.3", + Name: "railties", + Version: "5.2.3", + Indirect: true, + DependsOn: []string{ + "actionpack@5.2.3", + "activesupport@5.2.3", + "method_source@0.9.2", + "rake@12.3.2", + "thor@0.20.3", + }, + Locations: []types.Location{ + { + StartLine: 104, + EndLine: 104, + }, + }, + }, + { + ID: "rainbow@3.0.0", + Name: "rainbow", + Version: "3.0.0", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 110, + EndLine: 110, + }, + }, + }, + { + ID: "rake@12.3.2", + Name: "rake", + Version: "12.3.2", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 111, + EndLine: 111, + }, + }, + }, + { + ID: "rubocop@0.67.2", + Name: "rubocop", + Version: "0.67.2", + Indirect: false, + DependsOn: []string{ + "jaro_winkler@1.5.2", + "parallel@1.17.0", + "parser@2.6.3.0", + "psych@3.1.0", + "rainbow@3.0.0", + "ruby-progressbar@1.10.0", + "unicode-display_width@1.5.0", + }, + Locations: []types.Location{ + { + StartLine: 112, + EndLine: 112, + }, + }, + }, + { + ID: "ruby-progressbar@1.10.0", + Name: "ruby-progressbar", + Version: "1.10.0", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 120, + EndLine: 120, + }, + }, + }, + { + ID: "sprockets@3.7.2", + Name: "sprockets", + Version: "3.7.2", + Indirect: true, + DependsOn: []string{ + "concurrent-ruby@1.1.5", + "rack@2.0.7", + }, + Locations: []types.Location{ + { + StartLine: 121, + EndLine: 121, + }, + }, + }, + { + ID: "sprockets-rails@3.2.1", + Name: "sprockets-rails", + Version: "3.2.1", + Indirect: true, + DependsOn: []string{ + "actionpack@5.2.3", + "activesupport@5.2.3", + "sprockets@3.7.2", + }, + Locations: []types.Location{ + { + StartLine: 124, + EndLine: 124, + }, + }, + }, + { + ID: "thor@0.20.3", + Name: "thor", + Version: "0.20.3", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 128, + EndLine: 128, + }, + }, + }, + { + ID: "thread_safe@0.3.6", + Name: "thread_safe", + Version: "0.3.6", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 129, + EndLine: 129, + }, + }, + }, + { + ID: "tzinfo@1.2.5", + Name: "tzinfo", + Version: "1.2.5", + Indirect: true, + DependsOn: []string{"thread_safe@0.3.6"}, + Locations: []types.Location{ + { + StartLine: 130, + EndLine: 130, + }, + }, + }, + { + ID: "unicode-display_width@1.5.0", + Name: "unicode-display_width", + Version: "1.5.0", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 132, + EndLine: 132, + }, + }, + }, + { + ID: "websocket-driver@0.7.0", + Name: "websocket-driver", + Version: "0.7.0", + Indirect: true, + DependsOn: []string{"websocket-extensions@0.1.3"}, + Locations: []types.Location{ + { + StartLine: 133, + EndLine: 133, + }, + }, + }, + { + ID: "websocket-extensions@0.1.3", + Name: "websocket-extensions", + Version: "0.1.3", + Indirect: true, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 135, + EndLine: 135, + }, + }, + }, + }, + }, + }, + OpaqueDirs: []string{ + "ruby-app/", + }, + }, + }, + }, + }, + want: types.ArtifactReference{ + Name: "../../test/testdata/vuln-image.tar.gz", + Type: types.ArtifactContainerImage, + ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", + BlobIDs: []string{ + "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + }, + ImageMetadata: types.ImageMetadata{ + ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", + DiffIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + "sha256:a4595c43a874856bf95f3bfc4fbf78bbaa04c92c726276d4f64193a47ced0566", + }, + ConfigFile: v1.ConfigFile{ + Architecture: "amd64", + Author: "", + Created: v1.Time{Time: time.Date(2020, 2, 16, 10, 38, 41, 114114788, time.UTC)}, + DockerVersion: "19.03.5", + History: []v1.History{ + { + Author: "Bazel", + Created: v1.Time{Time: time.Date(1970, 01, 01, 0, 0, 0, 0, time.UTC)}, + CreatedBy: "bazel build ...", + EmptyLayer: false, + }, + { + Author: "Bazel", + Created: v1.Time{Time: time.Date(1970, 01, 01, 0, 0, 0, 0, time.UTC)}, + CreatedBy: "bazel build ...", + EmptyLayer: false, + }, + { + Author: "", + Created: v1.Time{Time: time.Date(2020, 2, 16, 10, 38, 40, 976530082, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) COPY file:842584685f26edb24dc305d76894f51cfda2bad0c24a05e727f9d4905d184a70 in /php-app/composer.lock ", + Comment: "", + EmptyLayer: false, + }, + { + Author: "", + Created: v1.Time{Time: time.Date(2020, 2, 16, 10, 38, 41, 114114788, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) COPY file:c6d0373d380252b91829a5bb3c81d5b1afa574c91cef7752d18170a231c31f6d in /ruby-app/Gemfile.lock ", + Comment: "", + EmptyLayer: false, + }, + }, + OS: "linux", + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + { + Algorithm: "sha256", + Hex: "dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + { + Algorithm: "sha256", + Hex: "24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + { + Algorithm: "sha256", + Hex: "a4595c43a874856bf95f3bfc4fbf78bbaa04c92c726276d4f64193a47ced0566", + }, + }, + }, + Config: v1.Config{ + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt", + }, + Image: "sha256:916390dcf84a1c7852e298f24fb5389a6e7801102086924e55eb08cd58d6a741", + }, + }, + }, + }, + }, + { + name: "happy path: disable analyzers", + imagePath: "../../test/testdata/vuln-image.tar.gz", + artifactOpt: artifact.Option{ + DisabledAnalyzers: []analyzer.Type{ + analyzer.TypeDebian, + analyzer.TypeDpkg, + analyzer.TypeDpkgLicense, + analyzer.TypeComposer, + analyzer.TypeBundler, + analyzer.TypeLicenseFile, + }, + LicenseScannerOption: analyzer.LicenseScannerOption{Full: true}, + }, + missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ + Args: cache.ArtifactCacheMissingBlobsArgs{ + ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", + BlobIDs: []string{ + "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", + "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", + "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", + "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", + }, + }, + Returns: cache.ArtifactCacheMissingBlobsReturns{ + MissingBlobIDs: []string{ + "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", + "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", + "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", + "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", + }, + }, + }, + putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + CreatedBy: "bazel build ...", + }, + }, + }, + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + CreatedBy: "bazel build ...", + }, + }, + }, + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + CreatedBy: "COPY file:842584685f26edb24dc305d76894f51cfda2bad0c24a05e727f9d4905d184a70 in /php-app/composer.lock ", + OpaqueDirs: []string{"php-app/"}, + }, + }, + }, + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:a4595c43a874856bf95f3bfc4fbf78bbaa04c92c726276d4f64193a47ced0566", + CreatedBy: "COPY file:c6d0373d380252b91829a5bb3c81d5b1afa574c91cef7752d18170a231c31f6d in /ruby-app/Gemfile.lock ", + OpaqueDirs: []string{"ruby-app/"}, + }, + }, + }, + }, + want: types.ArtifactReference{ + Name: "../../test/testdata/vuln-image.tar.gz", + Type: types.ArtifactContainerImage, + ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", + BlobIDs: []string{ + "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", + "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", + "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", + "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", + }, + ImageMetadata: types.ImageMetadata{ + ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", + DiffIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + "sha256:a4595c43a874856bf95f3bfc4fbf78bbaa04c92c726276d4f64193a47ced0566", + }, + ConfigFile: v1.ConfigFile{ + Architecture: "amd64", + Author: "", + Created: v1.Time{Time: time.Date(2020, 2, 16, 10, 38, 41, 114114788, time.UTC)}, + DockerVersion: "19.03.5", + History: []v1.History{ + { + Author: "Bazel", + Created: v1.Time{Time: time.Date(1970, 01, 01, 0, 0, 0, 0, time.UTC)}, + CreatedBy: "bazel build ...", + Comment: "", + EmptyLayer: false, + }, + { + Author: "Bazel", + Created: v1.Time{Time: time.Date(1970, 01, 01, 0, 0, 0, 0, time.UTC)}, + CreatedBy: "bazel build ...", + Comment: "", + EmptyLayer: false, + }, + { + Created: v1.Time{Time: time.Date(2020, 2, 16, 10, 38, 40, 976530082, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) COPY file:842584685f26edb24dc305d76894f51cfda2bad0c24a05e727f9d4905d184a70 in /php-app/composer.lock ", + Comment: "", + EmptyLayer: false, + }, + { + Created: v1.Time{Time: time.Date(2020, 2, 16, 10, 38, 41, 114114788, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) COPY file:c6d0373d380252b91829a5bb3c81d5b1afa574c91cef7752d18170a231c31f6d in /ruby-app/Gemfile.lock ", + Comment: "", + EmptyLayer: false, + }, + }, + OS: "linux", + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + { + Algorithm: "sha256", + Hex: "dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + { + Algorithm: "sha256", + Hex: "24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + { + Algorithm: "sha256", + Hex: "a4595c43a874856bf95f3bfc4fbf78bbaa04c92c726276d4f64193a47ced0566", + }, + }, + }, + Config: v1.Config{ + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt", + }, + Hostname: "", + Image: "sha256:916390dcf84a1c7852e298f24fb5389a6e7801102086924e55eb08cd58d6a741", + }, + }, + }, + }, + }, + { + name: "sad path, MissingBlobs returns an error", + imagePath: "../../test/testdata/alpine-311.tar.gz", + missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ + Args: cache.ArtifactCacheMissingBlobsArgs{ + ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", + BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + }, + Returns: cache.ArtifactCacheMissingBlobsReturns{ + Err: xerrors.New("MissingBlobs failed"), + }, + }, + wantErr: "MissingBlobs failed", + }, + { + name: "sad path, PutBlob returns an error", + imagePath: "../../test/testdata/alpine-311.tar.gz", + missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ + Args: cache.ArtifactCacheMissingBlobsArgs{ + ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", + BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + }, + Returns: cache.ArtifactCacheMissingBlobsReturns{ + MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + }, + }, + putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + CreatedBy: "ADD file:0c4555f363c2672e350001f1293e689875a3760afe7b3f9146886afe67121cba in / ", + OS: types.OS{ + Family: "alpine", + Name: "3.11.5", + }, + Repository: &types.Repository{ + Family: "alpine", + Release: "3.11", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: alpinePkgs, + }, + }, + Licenses: []types.LicenseFile{ + { + Type: "header", + FilePath: "etc/ssl/misc/CA.pl", + Findings: []types.LicenseFinding{ + { + Name: "OpenSSL", + Confidence: 1, + Link: "https://spdx.org/licenses/OpenSSL.html", + }, + }, + }, + { + Type: "header", + FilePath: "etc/ssl/misc/tsget.pl", + Findings: []types.LicenseFinding{ + { + Name: "OpenSSL", + Confidence: 1, + Link: "https://spdx.org/licenses/OpenSSL.html", + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{ + Err: errors.New("put layer failed"), + }, + }, + }, + wantErr: "put layer failed", + }, + { + name: "sad path, PutBlob returns an error with multiple layers", + imagePath: "../../test/testdata/vuln-image.tar.gz", + missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ + Args: cache.ArtifactCacheMissingBlobsArgs{ + ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", + BlobIDs: []string{ + "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + }, + }, + Returns: cache.ArtifactCacheMissingBlobsReturns{ + MissingBlobIDs: []string{ + "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + }, + }, + }, + putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ + { + + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + BlobInfoAnything: true, + }, + + Returns: cache.ArtifactCachePutBlobReturns{ + Err: errors.New("put layer failed"), + }, + }, + { + + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + BlobInfoAnything: true, + }, + + Returns: cache.ArtifactCachePutBlobReturns{ + Err: errors.New("put layer failed"), + }, + }, + { + + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + BlobInfoAnything: true, + }, + + Returns: cache.ArtifactCachePutBlobReturns{ + Err: errors.New("put layer failed"), + }, + }, + { + + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + BlobInfoAnything: true, + }, + + Returns: cache.ArtifactCachePutBlobReturns{ + Err: errors.New("put layer failed"), + }, + }, + }, + wantErr: "put layer failed", + }, + { + name: "sad path, PutArtifact returns an error", + imagePath: "../../test/testdata/alpine-311.tar.gz", + missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ + Args: cache.ArtifactCacheMissingBlobsArgs{ + ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", + BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + }, + Returns: cache.ArtifactCacheMissingBlobsReturns{ + MissingArtifact: true, + MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + }, + }, + putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Digest: "", + DiffID: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + CreatedBy: "ADD file:0c4555f363c2672e350001f1293e689875a3760afe7b3f9146886afe67121cba in / ", + OS: types.OS{ + Family: "alpine", + Name: "3.11.5", + }, + Repository: &types.Repository{ + Family: "alpine", + Release: "3.11", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: alpinePkgs, + }, + }, + Licenses: []types.LicenseFile{ + { + Type: "header", + FilePath: "etc/ssl/misc/CA.pl", + Findings: []types.LicenseFinding{ + { + Name: "OpenSSL", + Confidence: 1, + Link: "https://spdx.org/licenses/OpenSSL.html", + }, + }, + }, + { + Type: "header", + FilePath: "etc/ssl/misc/tsget.pl", + Findings: []types.LicenseFinding{ + { + Name: "OpenSSL", + Confidence: 1, + Link: "https://spdx.org/licenses/OpenSSL.html", + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + }, + putArtifactExpectations: []cache.ArtifactCachePutArtifactExpectation{ + { + Args: cache.ArtifactCachePutArtifactArgs{ + ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: types.ArtifactJSONSchemaVersion, + Architecture: "amd64", + Created: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC), + DockerVersion: "18.09.7", + OS: "linux", + }, + }, + Returns: cache.ArtifactCachePutArtifactReturns{ + Err: errors.New("put artifact failed"), + }, + }, + }, + wantErr: "put artifact failed", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCache := new(cache.MockArtifactCache) + mockCache.ApplyMissingBlobsExpectation(tt.missingBlobsExpectation) + mockCache.ApplyPutBlobExpectations(tt.putBlobExpectations) + mockCache.ApplyPutArtifactExpectations(tt.putArtifactExpectations) + + img, err := image.NewArchiveImage(tt.imagePath) + require.NoError(t, err) + + a, err := image2.NewArtifact(img, mockCache, tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr, tt.name) + return + } + require.NoError(t, err, tt.name) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/artifact/image/remote_sbom.go b/pkg/fanal/artifact/image/remote_sbom.go new file mode 100644 index 000000000000..f0c9ae26bfeb --- /dev/null +++ b/pkg/fanal/artifact/image/remote_sbom.go @@ -0,0 +1,192 @@ +package image + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/samber/lo" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + sbomatt "github.com/aquasecurity/trivy/pkg/attestation/sbom" + "github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom" + "github.com/aquasecurity/trivy/pkg/fanal/log" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/oci" + "github.com/aquasecurity/trivy/pkg/remote" + "github.com/aquasecurity/trivy/pkg/types" +) + +var errNoSBOMFound = xerrors.New("remote SBOM not found") + +type inspectRemoteSBOM func(context.Context) (ftypes.ArtifactReference, error) + +func (a Artifact) retrieveRemoteSBOM(ctx context.Context) (ftypes.ArtifactReference, error) { + for _, sbomSource := range a.artifactOption.SBOMSources { + var inspect inspectRemoteSBOM + switch sbomSource { + case types.SBOMSourceOCI: + inspect = a.inspectOCIReferrerSBOM + case types.SBOMSourceRekor: + inspect = a.inspectRekorSBOMAttestation + default: + // Never reach here as the "--sbom-sources" values are validated beforehand + continue + } + + ref, err := inspect(ctx) + if errors.Is(err, errNoSBOMFound) { + // Try the next SBOM source + log.Logger.Debugf("No SBOM found in the source: %s", sbomSource) + continue + } else if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("SBOM searching error: %w", err) + } + return ref, nil + } + return ftypes.ArtifactReference{}, errNoSBOMFound +} + +func (a Artifact) inspectOCIReferrerSBOM(ctx context.Context) (ftypes.ArtifactReference, error) { + digest, err := repoDigest(a.image, a.artifactOption.Insecure) + if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("repo digest error: %w", err) + } + + // Fetch referrers + index, err := remote.Referrers(ctx, digest, a.artifactOption.ImageOption.RegistryOptions) + if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("unable to fetch referrers: %w", err) + } + manifest, err := index.IndexManifest() + if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("unable to get manifest: %w", err) + } + for _, m := range lo.FromPtr(manifest).Manifests { + // Unsupported artifact type + if !slices.Contains(oci.SupportedSBOMArtifactTypes, m.ArtifactType) { + continue + } + res, err := a.parseReferrer(ctx, digest.Context().String(), m) + if err != nil { + log.Logger.Warnf("Error with SBOM via OCI referrers (%s): %s", m.Digest.String(), err) + continue + } + return res, nil + } + return ftypes.ArtifactReference{}, errNoSBOMFound +} + +func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descriptor) (ftypes.ArtifactReference, error) { + const fileName string = "referrer.sbom" + repoName := fmt.Sprintf("%s@%s", repo, desc.Digest) + referrer, err := oci.NewArtifact(repoName, true, a.artifactOption.ImageOption.RegistryOptions) + if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("OCI error: %w", err) + } + + tmpDir, err := os.MkdirTemp("", "trivy-sbom-*") + if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("mkdir temp error: %w", err) + } + defer os.RemoveAll(tmpDir) + + // Download SBOM to local filesystem + if err = referrer.Download(ctx, tmpDir, oci.DownloadOption{ + MediaType: desc.ArtifactType, + Filename: fileName, + }); err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("SBOM download error: %w", err) + } + + res, err := a.inspectSBOMFile(ctx, filepath.Join(tmpDir, fileName)) + if err != nil { + return res, xerrors.Errorf("SBOM error: %w", err) + } + + // Found SBOM + log.Logger.Infof("Found SBOM (%s) in the OCI referrers", res.Type) + + return res, nil +} + +func (a Artifact) inspectRekorSBOMAttestation(ctx context.Context) (ftypes.ArtifactReference, error) { + digest, err := repoDigest(a.image, a.artifactOption.Insecure) + if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("repo digest error: %w", err) + } + + client, err := sbomatt.NewRekor(a.artifactOption.RekorURL) + if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("failed to create rekor client: %w", err) + } + + raw, err := client.RetrieveSBOM(ctx, digest.DigestStr()) + if errors.Is(err, sbomatt.ErrNoSBOMAttestation) { + return ftypes.ArtifactReference{}, errNoSBOMFound + } else if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("failed to retrieve SBOM attestation: %w", err) + } + + f, err := os.CreateTemp("", "sbom-*") + if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("failed to create a temporary file: %w", err) + } + defer os.Remove(f.Name()) + + if _, err = f.Write(raw); err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("copy error: %w", err) + } + if err = f.Close(); err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("failed to close %s: %w", f.Name(), err) + } + res, err := a.inspectSBOMFile(ctx, f.Name()) + if err != nil { + return res, xerrors.Errorf("SBOM error: %w", err) + } + + // Found SBOM + log.Logger.Infof("Found SBOM (%s) in Rekor (%s)", res.Type, a.artifactOption.RekorURL) + + return res, nil +} + +func (a Artifact) inspectSBOMFile(ctx context.Context, filePath string) (ftypes.ArtifactReference, error) { + ar, err := sbom.NewArtifact(filePath, a.cache, a.artifactOption) + if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("failed to new artifact: %w", err) + } + + results, err := ar.Inspect(ctx) + if err != nil { + return ftypes.ArtifactReference{}, xerrors.Errorf("failed to inspect: %w", err) + } + results.Name = a.image.Name() + + return results, nil +} + +func repoDigest(img ftypes.Image, insecure bool) (name.Digest, error) { + repoNameFull := img.Name() + ref, err := name.ParseReference(repoNameFull) + if err != nil { + return name.Digest{}, xerrors.Errorf("image name parse error: %w", err) + } + + for _, rd := range img.RepoDigests() { + opts := lo.Ternary(insecure, []name.Option{name.Insecure}, nil) + digest, err := name.NewDigest(rd, opts...) + if err != nil { + continue + } + if ref.Context().String() == digest.Context().String() { + return digest, nil + } + } + return name.Digest{}, xerrors.Errorf("no repo digest found: %w", errNoSBOMFound) +} diff --git a/pkg/fanal/artifact/image/remote_sbom_test.go b/pkg/fanal/artifact/image/remote_sbom_test.go new file mode 100644 index 000000000000..c9255c2057bd --- /dev/null +++ b/pkg/fanal/artifact/image/remote_sbom_test.go @@ -0,0 +1,319 @@ +package image_test + +import ( + "context" + "github.com/package-url/packageurl-go" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + fakei "github.com/google/go-containerregistry/pkg/v1/fake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + image2 "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/rekortest" +) + +func TestMain(m *testing.M) { + log.InitLogger(false, true) + os.Exit(m.Run()) +} + +type fakeImage struct { + name string + repoDigests []string + *fakei.FakeImage + types.ImageExtension +} + +func (f fakeImage) ID() (string, error) { + return "", nil +} + +func (f fakeImage) Name() string { + return f.name +} + +func (f fakeImage) RepoDigests() []string { + return f.repoDigests +} + +func TestArtifact_InspectRekorAttestation(t *testing.T) { + type fields struct { + imageName string + repoDigests []string + } + tests := []struct { + name string + fields fields + artifactOpt artifact.Option + putBlobExpectations []cache.ArtifactCachePutBlobExpectation + want types.ArtifactReference + wantErr string + }{ + { + name: "happy path", + fields: fields{ + imageName: "test/image:10", + repoDigests: []string{ + "test/image@sha256:782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02", + }, + }, + putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:066b9998617ffb7dfe0a3219ac5c3efc1008a6223606fcf474e7d5c965e4e8da", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: types.OS{ + Family: "alpine", + Name: "3.16.2", + }, + PackageInfos: []types.PackageInfo{ + { + Packages: types.Packages{ + { + ID: "musl@1.2.3-r0", + Name: "musl", + Version: "1.2.3-r0", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "musl", + Version: "1.2.3-r0", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.16.2", + }, + }, + }, + BOMRef: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.2", + }, + SrcName: "musl", + SrcVersion: "1.2.3-r0", + Licenses: []string{"MIT"}, + Layer: types.Layer{ + DiffID: "sha256:994393dc58e7931862558d06e46aa2bb17487044f670f310dffe1d24e4d1eec7", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + }, + artifactOpt: artifact.Option{ + SBOMSources: []string{"rekor"}, + }, + want: types.ArtifactReference{ + Name: "test/image:10", + Type: types.ArtifactCycloneDX, + ID: "sha256:066b9998617ffb7dfe0a3219ac5c3efc1008a6223606fcf474e7d5c965e4e8da", + BlobIDs: []string{ + "sha256:066b9998617ffb7dfe0a3219ac5c3efc1008a6223606fcf474e7d5c965e4e8da", + }, + }, + }, + { + name: "error", + fields: fields{ + imageName: "test/image:10", + repoDigests: []string{ + "test/image@sha256:123456e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02", + }, + }, + artifactOpt: artifact.Option{ + SBOMSources: []string{"rekor"}, + }, + wantErr: "remote SBOM fetching error", + }, + } + + require.NoError(t, log.InitLogger(false, true)) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := rekortest.NewServer(t) + defer ts.Close() + + // Set the testing URL + tt.artifactOpt.RekorURL = ts.URL() + + mockCache := new(cache.MockArtifactCache) + mockCache.ApplyPutBlobExpectations(tt.putBlobExpectations) + + fi := &fakei.FakeImage{} + fi.ConfigFileReturns(&v1.ConfigFile{}, nil) + + img := &fakeImage{ + name: tt.fields.imageName, + repoDigests: tt.fields.repoDigests, + FakeImage: fi, + } + a, err := image2.NewArtifact(img, mockCache, tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err, tt.name) + got.BOM = nil + assert.Equal(t, tt.want, got) + }) + } +} + +func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v2": + _, err := w.Write([]byte("ok")) + require.NoError(t, err) + case "/v2/test/image/referrers/sha256:782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02": + http.ServeFile(w, r, "testdata/index.json") + case "/v2/test/image/manifests/sha256:37c89af4907fa0af078aeba12d6f18dc0c63937c010030baaaa88e958f0719a5": + http.ServeFile(w, r, "testdata/manifest.json") + case "/v2/test/image/blobs/sha256:9e05dda2a2dcdd526c9204be8645ae48742861c27f093bf496a6397834acecf2": + http.ServeFile(w, r, "testdata/cyclonedx.json") + } + })) + defer ts.Close() + + u, err := url.Parse(ts.URL) + require.NoError(t, err) + registry := u.Host + + type fields struct { + imageName string + repoDigests []string + } + + tests := []struct { + name string + fields fields + artifactOpt artifact.Option + putBlobExpectations []cache.ArtifactCachePutBlobExpectation + want types.ArtifactReference + wantErr string + }{ + { + name: "happy path", + fields: fields{ + imageName: registry + "/test/image:10", + repoDigests: []string{ + registry + "/test/image@sha256:782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02", + }, + }, + artifactOpt: artifact.Option{ + SBOMSources: []string{"oci"}, + }, + putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ + { + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:7e0b5476a5ff5a10594ad1ed7566220fcc43ecff29b831236cb2e98e574a1d05", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Applications: []types.Application{ + { + Type: types.GoBinary, + Libraries: types.Packages{ + { + ID: "github.com/opencontainers/go-digest@v1.0.0", + Name: "github.com/opencontainers/go-digest", + Version: "v1.0.0", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/opencontainers", + Name: "go-digest", + Version: "v1.0.0", + }, + BOMRef: "pkg:golang/github.com/opencontainers/go-digest@v1.0.0", + }, + }, + { + ID: "golang.org/x/sync@v0.1.0", + Name: "golang.org/x/sync", + Version: "v0.1.0", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "golang.org/x", + Name: "sync", + Version: "v0.1.0", + }, + BOMRef: "pkg:golang/golang.org/x/sync@v0.1.0", + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: types.ArtifactReference{ + Name: registry + "/test/image:10", + Type: types.ArtifactCycloneDX, + ID: "sha256:7e0b5476a5ff5a10594ad1ed7566220fcc43ecff29b831236cb2e98e574a1d05", + BlobIDs: []string{ + "sha256:7e0b5476a5ff5a10594ad1ed7566220fcc43ecff29b831236cb2e98e574a1d05", + }, + }, + }, + { + name: "404", + fields: fields{ + imageName: registry + "/test/image:unknown", + repoDigests: []string{ + registry + "/test/image@sha256:123456e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02", + }, + }, + artifactOpt: artifact.Option{ + SBOMSources: []string{"oci"}, + }, + wantErr: "unable to get manifest", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCache := new(cache.MockArtifactCache) + mockCache.ApplyPutBlobExpectations(tt.putBlobExpectations) + + fi := &fakei.FakeImage{} + fi.ConfigFileReturns(&v1.ConfigFile{}, nil) + + img := &fakeImage{ + name: tt.fields.imageName, + repoDigests: tt.fields.repoDigests, + FakeImage: fi, + } + a, err := image2.NewArtifact(img, mockCache, tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + require.NoError(t, err, tt.name) + got.BOM = nil + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/artifact/image/testdata/cyclonedx.json b/pkg/fanal/artifact/image/testdata/cyclonedx.json new file mode 100644 index 000000000000..dc6165ec226f --- /dev/null +++ b/pkg/fanal/artifact/image/testdata/cyclonedx.json @@ -0,0 +1,72 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "serialNumber": "urn:uuid:2e3c684c-1f99-466c-ad6b-b783942eb9d1", + "version": 1, + "metadata": { + "timestamp": "2023-03-22T11:35:38+00:00", + "tools": [ + { + "vendor": "aquasecurity", + "name": "trivy", + "version": "0.39.0" + } + ], + "component": { + "bom-ref": "pkg:oci/demo-referrers-2023@sha256:e76a13475c6b4a713a0e4a7a8574ce450274d340357a2c40b8221cfcfedf8b19?repository_url=localhost:5001%2Fdemo-referrers-2023\u0026arch=arm64", + "type": "container", + "name": "localhost:5001/demo-referrers-2023:app", + "purl": "pkg:oci/demo-referrers-2023@sha256:e76a13475c6b4a713a0e4a7a8574ce450274d340357a2c40b8221cfcfedf8b19?repository_url=localhost:5001%2Fdemo-referrers-2023\u0026arch=arm64", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:golang/github.com/opencontainers/go-digest@v1.0.0", + "type": "library", + "name": "github.com/opencontainers/go-digest", + "version": "v1.0.0", + "purl": "pkg:golang/github.com/opencontainers/go-digest@v1.0.0", + "properties": [ + { + "name": "aquasecurity:trivy:PkgType", + "value": "gobinary" + } + ] + }, + { + "bom-ref": "pkg:golang/golang.org/x/sync@v0.1.0", + "type": "library", + "name": "golang.org/x/sync", + "version": "v0.1.0", + "purl": "pkg:golang/golang.org/x/sync@v0.1.0", + "properties": [ + { + "name": "aquasecurity:trivy:PkgType", + "value": "gobinary" + } + ] + } + ], + "dependencies": [ + { + "ref": "c6309d4a-f6db-4acc-8e44-f0d271492b65", + "dependsOn": [ + "pkg:golang/github.com/opencontainers/go-digest@v1.0.0", + "pkg:golang/golang.org/x/sync@v0.1.0" + ] + }, + { + "ref": "pkg:oci/demo-referrers-2023@sha256:e76a13475c6b4a713a0e4a7a8574ce450274d340357a2c40b8221cfcfedf8b19?repository_url=localhost:5001%2Fdemo-referrers-2023\u0026arch=arm64", + "dependsOn": [ + "c6309d4a-f6db-4acc-8e44-f0d271492b65" + ] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/fanal/artifact/image/testdata/index.json b/pkg/fanal/artifact/image/testdata/index.json new file mode 100644 index 000000000000..21cb0d38021b --- /dev/null +++ b/pkg/fanal/artifact/image/testdata/index.json @@ -0,0 +1,15 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 621, + "digest": "sha256:37c89af4907fa0af078aeba12d6f18dc0c63937c010030baaaa88e958f0719a5", + "annotations": { + "org.opencontainers.artifact.description": "CycloneDX JSON SBOM" + }, + "artifactType": "application/vnd.cyclonedx+json" + } + ] +} \ No newline at end of file diff --git a/pkg/fanal/artifact/image/testdata/manifest.json b/pkg/fanal/artifact/image/testdata/manifest.json new file mode 100644 index 000000000000..bbb680cb408a --- /dev/null +++ b/pkg/fanal/artifact/image/testdata/manifest.json @@ -0,0 +1,24 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.cyclonedx+json", + "size": 2, + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" + }, + "layers": [ + { + "mediaType": "application/vnd.cyclonedx+json", + "size": 2215, + "digest": "sha256:9e05dda2a2dcdd526c9204be8645ae48742861c27f093bf496a6397834acecf2" + } + ], + "annotations": { + "org.opencontainers.artifact.description": "CycloneDX JSON SBOM" + }, + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 1024, + "digest": "sha256:782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02" + } +} \ No newline at end of file diff --git a/pkg/fanal/artifact/image/testdata/valid.rego b/pkg/fanal/artifact/image/testdata/valid.rego new file mode 100644 index 000000000000..57f95ac2f878 --- /dev/null +++ b/pkg/fanal/artifact/image/testdata/valid.rego @@ -0,0 +1,14 @@ +package testdata.kubernetes.id_100 + +__rego_metadata__ := { + "id": "ID-100", + "title": "Bad Deployment", + "version": "v1.0.0", + "severity": "HIGH", + "type": "Kubernetes Security Check", +} + +deny[res] { + input.kind == "Deployment" + res := {"type": "Kubernetes Check", "id": "ID-100", "msg": "deny", "severity": "CRITICAL"} +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/fs.go b/pkg/fanal/artifact/local/fs.go new file mode 100644 index 000000000000..8d7409cdfae3 --- /dev/null +++ b/pkg/fanal/artifact/local/fs.go @@ -0,0 +1,231 @@ +package local + +import ( + "context" + "crypto/sha256" + "encoding/json" + "os" + "path" + "path/filepath" + "strings" + "sync" + + "github.com/opencontainers/go-digest" + "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/log" + "github.com/aquasecurity/trivy/pkg/semaphore" +) + +type Artifact struct { + rootPath string + cache cache.ArtifactCache + walker walker.FS + analyzer analyzer.AnalyzerGroup + handlerManager handler.Manager + + artifactOption artifact.Option +} + +func NewArtifact(rootPath string, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) { + handlerManager, err := handler.NewManager(opt) + if err != nil { + return nil, xerrors.Errorf("handler initialize error: %w", err) + } + + a, err := analyzer.NewAnalyzerGroup(opt.AnalyzerOptions()) + if err != nil { + return nil, xerrors.Errorf("analyzer group error: %w", err) + } + + return Artifact{ + rootPath: filepath.ToSlash(filepath.Clean(rootPath)), + cache: c, + walker: walker.NewFS(buildPathsToSkip(rootPath, opt.SkipFiles), buildPathsToSkip(rootPath, opt.SkipDirs), + opt.Parallel, opt.WalkOption.ErrorCallback), + analyzer: a, + handlerManager: handlerManager, + + artifactOption: opt, + }, nil +} + +// buildPathsToSkip builds correct patch for skipDirs and skipFiles +func buildPathsToSkip(base string, paths []string) []string { + var relativePaths []string + absBase, err := filepath.Abs(base) + if err != nil { + log.Logger.Warnf("Failed to get an absolute path of %s: %s", base, err) + return nil + } + for _, path := range paths { + // Supports three types of flag specification. + // All of them are converted into the relative path from the root directory. + // 1. Relative skip dirs/files from the root directory + // The specified dirs and files will be used as is. + // e.g. $ trivy fs --skip-dirs bar ./foo + // The skip dir from the root directory will be `bar/`. + // 2. Relative skip dirs/files from the working directory + // The specified dirs and files wll be converted to the relative path from the root directory. + // e.g. $ trivy fs --skip-dirs ./foo/bar ./foo + // The skip dir will be converted to `bar/`. + // 3. Absolute skip dirs/files + // The specified dirs and files wll be converted to the relative path from the root directory. + // e.g. $ trivy fs --skip-dirs /bar/foo/baz ./foo + // When the working directory is + // 3.1 /bar: the skip dir will be converted to `baz/`. + // 3.2 /hoge : the skip dir will be converted to `../../bar/foo/baz/`. + + absSkipPath, err := filepath.Abs(path) + if err != nil { + log.Logger.Warnf("Failed to get an absolute path of %s: %s", base, err) + continue + } + rel, err := filepath.Rel(absBase, absSkipPath) + if err != nil { + log.Logger.Warnf("Failed to get a relative path from %s to %s: %s", base, path, err) + continue + } + + var relPath string + switch { + case !filepath.IsAbs(path) && strings.HasPrefix(rel, ".."): + // #1: Use the path as is + relPath = path + case !filepath.IsAbs(path) && !strings.HasPrefix(rel, ".."): + // #2: Use the relative path from the root directory + relPath = rel + case filepath.IsAbs(path): + // #3: Use the relative path from the root directory + relPath = rel + } + relPath = filepath.ToSlash(relPath) + relativePaths = append(relativePaths, relPath) + } + return relativePaths +} + +func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { + var wg sync.WaitGroup + result := analyzer.NewAnalysisResult() + limit := semaphore.New(a.artifactOption.Parallel) + opts := analyzer.AnalysisOptions{ + Offline: a.artifactOption.Offline, + FileChecksum: a.artifactOption.FileChecksum, + } + + // Prepare filesystem for post analysis + composite, err := a.analyzer.PostAnalyzerFS() + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("failed to prepare filesystem for post analysis: %w", err) + } + + err = a.walker.Walk(a.rootPath, func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + dir := a.rootPath + + // When the directory is the same as the filePath, a file was given + // instead of a directory, rewrite the file path and directory in this case. + if filePath == "." { + dir, filePath = path.Split(a.rootPath) + } + + if err := a.analyzer.AnalyzeFile(ctx, &wg, limit, result, dir, filePath, info, opener, nil, opts); err != nil { + return xerrors.Errorf("analyze file (%s): %w", filePath, err) + } + + // Skip post analysis if the file is not required + analyzerTypes := a.analyzer.RequiredPostAnalyzers(filePath, info) + if len(analyzerTypes) == 0 { + return nil + } + + // Build filesystem for post analysis + if err := composite.CreateLink(analyzerTypes, dir, filePath, filepath.Join(dir, filePath)); err != nil { + return xerrors.Errorf("failed to create link: %w", err) + } + + return nil + }) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("walk filesystem: %w", err) + } + + // Wait for all the goroutine to finish. + wg.Wait() + + // Post-analysis + if err = a.analyzer.PostAnalyze(ctx, composite, result, opts); err != nil { + return types.ArtifactReference{}, xerrors.Errorf("post analysis error: %w", err) + } + + // Sort the analysis result for consistent results + result.Sort() + + blobInfo := types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: result.OS, + Repository: result.Repository, + PackageInfos: result.PackageInfos, + Applications: result.Applications, + Misconfigurations: result.Misconfigurations, + Secrets: result.Secrets, + Licenses: result.Licenses, + CustomResources: result.CustomResources, + } + + if err = a.handlerManager.PostHandle(ctx, result, &blobInfo); err != nil { + return types.ArtifactReference{}, xerrors.Errorf("failed to call hooks: %w", err) + } + + cacheKey, err := a.calcCacheKey(blobInfo) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("failed to calculate a cache key: %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) + } + + // get hostname + var hostName string + b, err := os.ReadFile(filepath.Join(a.rootPath, "etc", "hostname")) + if err == nil && len(b) != 0 { + hostName = strings.TrimSpace(string(b)) + } else { + // To slash for Windows + hostName = filepath.ToSlash(a.rootPath) + } + + return types.ArtifactReference{ + Name: hostName, + Type: types.ArtifactFilesystem, + ID: cacheKey, // use a cache key as pseudo artifact ID + BlobIDs: []string{cacheKey}, + }, nil +} + +func (a Artifact) Clean(reference types.ArtifactReference) error { + return a.cache.DeleteBlobs(reference.BlobIDs) +} + +func (a Artifact) 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 +} diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go new file mode 100644 index 000000000000..bbaf684552ae --- /dev/null +++ b/pkg/fanal/artifact/local/fs_test.go @@ -0,0 +1,2286 @@ +package local + +import ( + "context" + "errors" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" + + "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/types" + "github.com/aquasecurity/trivy/pkg/misconf" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/pip" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" + _ "github.com/aquasecurity/trivy/pkg/fanal/handler/sysfile" +) + +func TestArtifact_Inspect(t *testing.T) { + type fields struct { + dir string + } + tests := []struct { + name string + fields fields + artifactOpt artifact.Option + scannerOpt misconf.ScannerOption + disabledAnalyzers []analyzer.Type + disabledHandlers []types.HandlerType + putBlobExpectation cache.ArtifactCachePutBlobExpectation + want types.ArtifactReference + wantErr string + }{ + { + name: "happy path", + fields: fields{ + dir: "./testdata/alpine", + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: types.OS{ + Family: "alpine", + Name: "3.11.6", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: types.Packages{ + { + ID: "musl@1.1.24-r2", + Name: "musl", + Version: "1.1.24-r2", + SrcName: "musl", + SrcVersion: "1.1.24-r2", + Licenses: []string{"MIT"}, + Arch: "x86_64", + Digest: "sha1:cb2316a189ebee5282c4a9bd98794cc2477a74c6", + InstalledFiles: []string{ + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "host", + Type: types.ArtifactFilesystem, + ID: "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", + BlobIDs: []string{ + "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", + }, + }, + }, + { + name: "disable analyzers", + fields: fields{ + dir: "./testdata/alpine", + }, + artifactOpt: artifact.Option{ + DisabledAnalyzers: []analyzer.Type{ + analyzer.TypeAlpine, + analyzer.TypeApk, + analyzer.TypePip, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "host", + Type: types.ArtifactFilesystem, + ID: "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", + BlobIDs: []string{ + "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", + }, + }, + }, + { + name: "sad path PutBlob returns an error", + fields: fields{ + dir: "./testdata/alpine", + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: types.OS{ + Family: "alpine", + Name: "3.11.6", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: types.Packages{ + { + ID: "musl@1.1.24-r2", + Name: "musl", + Version: "1.1.24-r2", + SrcName: "musl", + SrcVersion: "1.1.24-r2", + Licenses: []string{"MIT"}, + Arch: "x86_64", + Digest: "sha1:cb2316a189ebee5282c4a9bd98794cc2477a74c6", + InstalledFiles: []string{ + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{ + Err: errors.New("error"), + }, + }, + wantErr: "failed to store blob", + }, + { + name: "sad path with no such directory", + fields: fields{ + dir: "./testdata/unknown", + }, + wantErr: "walk dir error", + }, + { + name: "happy path with single file", + fields: fields{ + dir: "testdata/requirements.txt", + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Applications: []types.Application{ + { + Type: "pip", + FilePath: "requirements.txt", + Libraries: types.Packages{ + { + Name: "Flask", + Version: "2.0.0", + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/requirements.txt", + Type: types.ArtifactFilesystem, + ID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + BlobIDs: []string{ + "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + }, + }, + }, + { + name: "happy path with single file using relative path", + fields: fields{ + dir: "./testdata/requirements.txt", + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Applications: []types.Application{ + { + Type: "pip", + FilePath: "requirements.txt", + Libraries: types.Packages{ + { + Name: "Flask", + Version: "2.0.0", + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/requirements.txt", + Type: types.ArtifactFilesystem, + ID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + BlobIDs: []string{ + "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + + a, err := NewArtifact(tt.fields.dir, c, tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestBuildPathsToSkip(t *testing.T) { + tests := []struct { + name string + oses []string + paths []string + base string + want []string + }{ + // Linux/macOS + { + name: "path - abs, base - abs, not joining paths", + oses: []string{ + "linux", + "darwin", + }, + base: "/foo", + paths: []string{"/foo/bar"}, + want: []string{"bar"}, + }, + { + name: "path - abs, base - rel", + oses: []string{ + "linux", + "darwin", + }, + base: "foo", + paths: func() []string { + abs, err := filepath.Abs("foo/bar") + require.NoError(t, err) + return []string{abs} + }(), + want: []string{"bar"}, + }, + { + name: "path - rel, base - rel, joining paths", + oses: []string{ + "linux", + "darwin", + }, + base: "foo", + paths: []string{"bar"}, + want: []string{"bar"}, + }, + { + name: "path - rel, base - rel, not joining paths", + oses: []string{ + "linux", + "darwin", + }, + base: "foo", + paths: []string{"foo/bar/bar"}, + want: []string{"bar/bar"}, + }, + { + name: "path - rel with dot, base - rel, removing the leading dot and not joining paths", + oses: []string{ + "linux", + "darwin", + }, + base: "foo", + paths: []string{"./foo/bar"}, + want: []string{"bar"}, + }, + { + name: "path - rel, base - dot", + oses: []string{ + "linux", + "darwin", + }, + base: ".", + paths: []string{"foo/bar"}, + want: []string{"foo/bar"}, + }, + // Windows + { + name: "path - rel, base - rel. Skip common prefix", + oses: []string{"windows"}, + base: "foo", + paths: []string{"foo\\bar\\bar"}, + want: []string{"bar/bar"}, + }, + { + name: "path - rel, base - dot, windows", + oses: []string{"windows"}, + base: ".", + paths: []string{"foo\\bar"}, + want: []string{"foo/bar"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !slices.Contains(tt.oses, runtime.GOOS) { + t.Skipf("Skip path tests for %q", tt.oses) + } + got := buildPathsToSkip(tt.base, tt.paths) + assert.Equal(t, tt.want, got) + }) + } +} + +var terraformPolicyMetadata = types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Terraform Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, +} + +func TestTerraformMisconfigurationScan(t *testing.T) { + type fields struct { + dir string + } + tests := []struct { + name string + fields fields + putBlobExpectation cache.ArtifactCachePutBlobExpectation + artifactOpt artifact.Option + want types.ArtifactReference + }{ + { + name: "single failure", + fields: fields{ + dir: "./testdata/misconfig/terraform/single-failure", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "terraform", + FilePath: "main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.asd", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraform/single-failure", + Type: types.ArtifactFilesystem, + ID: "sha256:1a6ce0acc3b57eb6c830c96fcd868fec1eb4d3b57ad51e481c76d85f22870a65", + BlobIDs: []string{ + "sha256:1a6ce0acc3b57eb6c830c96fcd868fec1eb4d3b57ad51e481c76d85f22870a65", + }, + }, + }, + { + name: "multiple failures", + fields: fields{ + dir: "./testdata/misconfig/terraform/multiple-failures", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "terraform", + FilePath: "main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.one", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.two", + Provider: "Generic", + Service: "general", + StartLine: 5, + EndLine: 7, + }, + }, + }, + }, + { + FileType: "terraform", + FilePath: "more.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.three", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraform/multiple-failures", + Type: types.ArtifactFilesystem, + ID: "sha256:afc20cf0fc99c62bbc79b00cb9fbc70ba7ee76c946a6d560639ba9279344787d", + BlobIDs: []string{ + "sha256:afc20cf0fc99c62bbc79b00cb9fbc70ba7ee76c946a6d560639ba9279344787d", + }, + }, + }, + { + name: "no results", + fields: fields{ + dir: "./testdata/misconfig/terraform/no-results", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraform/no-results", + Type: types.ArtifactFilesystem, + ID: "sha256:1827b6a0b0a17e0d623a2045e9d9c331ef613390eda2fed823969ee0dd730257", + BlobIDs: []string{ + "sha256:1827b6a0b0a17e0d623a2045e9d9c331ef613390eda2fed823969ee0dd730257", + }, + }, + }, + { + name: "passed", + fields: fields{ + dir: "./testdata/misconfig/terraform/passed", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "terraform", + FilePath: ".", + Successes: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraform/passed", + Type: types.ArtifactFilesystem, + ID: "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", + BlobIDs: []string{ + "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", + }, + }, + }, + { + name: "multiple failures busted relative paths", + fields: fields{ + dir: "./testdata/misconfig/terraform/busted-relative-paths/child/main.tf", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "terraform", + FilePath: "main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.one", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.two", + Provider: "Generic", + Service: "general", + StartLine: 5, + EndLine: 7, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraform/busted-relative-paths/child/main.tf", + Type: types.ArtifactFilesystem, + ID: "sha256:8db34d644bfb98077180616caeab0b41a26d9029a47a23d4b36e1d6e45584919", + BlobIDs: []string{ + "sha256:8db34d644bfb98077180616caeab0b41a26d9029a47a23d4b36e1d6e45584919", + }, + }, + }, + { + name: "tfvars outside the scan folder", + fields: fields{ + dir: "./testdata/misconfig/terraform/tfvar-outside/tf", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, + TerraformTFVars: []string{"./testdata/misconfig/terraform/tfvar-outside/main.tfvars"}, + TfExcludeDownloaded: true, + DisableEmbeddedPolicies: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.Terraform, + FilePath: ".", + Successes: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraform/tfvar-outside/tf", + Type: types.ArtifactFilesystem, + ID: "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", + BlobIDs: []string{ + "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", + }, + }, + }, + { + name: "relative paths", + fields: fields{ + dir: "./testdata/misconfig/terraform/relative-paths/child", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, + DisableEmbeddedPolicies: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.Terraform, + FilePath: "../parent/main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.three", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + }, + }, + { + FileType: types.Terraform, + FilePath: "main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.one", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + }, + }, + { + FileType: types.Terraform, + FilePath: "nested/main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.two", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraform/relative-paths/child", + Type: types.ArtifactFilesystem, + ID: "sha256:f13b89447db61be1c1e4099ef18aec7272091f8f2d3581643a9d1fabc74eda83", + BlobIDs: []string{ + "sha256:f13b89447db61be1c1e4099ef18aec7272091f8f2d3581643a9d1fabc74eda83", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + tt.artifactOpt.DisabledHandlers = []types.HandlerType{ + types.SystemFileFilteringPostHandler, + } + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true + a, err := NewArtifact(tt.fields.dir, c, tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +const emptyBucketCheck = `package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "empty-bucket-name", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "" + res := result("Empty bucket name!", bucket) +}` + +var terraformPlanPolicyMetadata = types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Terraform Plan Snapshot Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, +} + +func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { + type fields struct { + dir string + } + tests := []struct { + name string + fields fields + putBlobExpectation cache.ArtifactCachePutBlobExpectation + want types.ArtifactReference + }{ + { + name: "single failure", + fields: fields{ + + dir: "./testdata/misconfig/terraformplan/snapshots/single-failure", + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.TerraformPlanSnapshot, + FilePath: "main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPlanPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.this", + Provider: "Generic", + Service: "general", + StartLine: 10, + EndLine: 12, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraformplan/snapshots/single-failure", + Type: types.ArtifactFilesystem, + ID: "sha256:732c38451bde877a94d1ff5b6f2019655bed04fd24b1169de195eeee1199045e", + BlobIDs: []string{ + "sha256:732c38451bde877a94d1ff5b6f2019655bed04fd24b1169de195eeee1199045e", + }, + }, + }, + { + name: "multiple failures", + fields: fields{ + dir: "./testdata/misconfig/terraformplan/snapshots/multiple-failures", + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.TerraformPlanSnapshot, + FilePath: "main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPlanPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.one", + Provider: "Generic", + Service: "general", + StartLine: 10, + EndLine: 12, + }, + }, + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPlanPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.two", + Provider: "Generic", + Service: "general", + StartLine: 14, + EndLine: 16, + }, + }, + }, + }, + { + FileType: types.TerraformPlanSnapshot, + FilePath: "more.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPlanPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.three", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraformplan/snapshots/multiple-failures", + Type: types.ArtifactFilesystem, + ID: "sha256:752c0b470adfcfe7e21892cf4c6fc3bc28dba873c9c1696f40b71b7a51ad7231", + BlobIDs: []string{ + "sha256:752c0b470adfcfe7e21892cf4c6fc3bc28dba873c9c1696f40b71b7a51ad7231", + }, + }, + }, + { + name: "passed", + fields: fields{ + dir: "./testdata/misconfig/terraformplan/snapshots/passed", + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.TerraformPlanSnapshot, + FilePath: ".", + Successes: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: terraformPlanPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraformplan/snapshots/passed", + Type: types.ArtifactFilesystem, + ID: "sha256:8947704a08f54ab1df32cd905d6bca72edf1785a42702968bafa331172da7176", + BlobIDs: []string{ + "sha256:8947704a08f54ab1df32cd905d6bca72edf1785a42702968bafa331172da7176", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + tmpDir := t.TempDir() + f, err := os.Create(filepath.Join(tmpDir, "policy.rego")) + require.NoError(t, err) + defer f.Close() + + f.WriteString(emptyBucketCheck) + + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + + opt := artifact.Option{ + DisabledHandlers: []types.HandlerType{ + types.SystemFileFilteringPostHandler, + }, + SkipFiles: []string{"*.tf"}, + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + DisableEmbeddedPolicies: true, + + DisableEmbeddedLibraries: false, + Namespaces: []string{"user"}, + PolicyPaths: []string{tmpDir}, + }, + } + + a, err := NewArtifact(tt.fields.dir, c, opt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestCloudFormationMisconfigurationScan(t *testing.T) { + type fields struct { + dir string + } + tests := []struct { + name string + fields fields + putBlobExpectation cache.ArtifactCachePutBlobExpectation + artifactOpt artifact.Option + want types.ArtifactReference + }{ + { + name: "single failure", + fields: fields{ + dir: "./testdata/misconfig/cloudformation/single-failure/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/cloudformation/single-failure/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "cloudformation", + FilePath: "main.yaml", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No buckets allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "CloudFormation Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Resource: "main.yaml:3-6", + Provider: "Generic", + Service: "general", + StartLine: 3, + EndLine: 6, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/cloudformation/single-failure/src", + Type: types.ArtifactFilesystem, + ID: "sha256:889a94522970c6e55f1f7543914b2f0131f79f9c4526445fb95309f64a9947d7", + BlobIDs: []string{ + "sha256:889a94522970c6e55f1f7543914b2f0131f79f9c4526445fb95309f64a9947d7", + }, + }, + }, + { + name: "multiple failures", + fields: fields{ + dir: "./testdata/misconfig/cloudformation/multiple-failures/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/cloudformation/multiple-failures/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "cloudformation", + FilePath: "main.yaml", + Failures: types.MisconfResults{ + types.MisconfResult{ + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No buckets allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "CloudFormation Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Resource: "main.yaml:2-5", + Provider: "Generic", + Service: "general", + StartLine: 2, + EndLine: 5, + }, + }, + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No buckets allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "CloudFormation Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Resource: "main.yaml:6-9", + Provider: "Generic", + Service: "general", + StartLine: 6, + EndLine: 9, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/cloudformation/multiple-failures/src", + Type: types.ArtifactFilesystem, + ID: "sha256:17c9c72a759856445e6d3847b2d5ed90c3bad3e4ee50cea0c812ef53c179f8ca", + BlobIDs: []string{ + "sha256:17c9c72a759856445e6d3847b2d5ed90c3bad3e4ee50cea0c812ef53c179f8ca", + }, + }, + }, + { + name: "no results", + fields: fields{ + dir: "./testdata/misconfig/cloudformation/no-results/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/cloudformation/no-results/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/cloudformation/no-results/src", + Type: types.ArtifactFilesystem, + ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + BlobIDs: []string{ + "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + }, + }, + }, + { + name: "CloudFormation parameters outside the scan directory", + fields: fields{ + dir: "./testdata/misconfig/cloudformation/params/code/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/cloudformation/params/code/rego"}, + CloudFormationParamVars: []string{"./testdata/misconfig/cloudformation/params/cfparams.json"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "cloudformation", + FilePath: "main.yaml", + Successes: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "CloudFormation Security Check", + Title: "Bad stuff is bad", + Description: "Its not good!", + Severity: "HIGH", + RecommendedActions: "Remove bad stuff", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "AWS", + Service: "sqs", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/cloudformation/params/code/src", + Type: types.ArtifactFilesystem, + ID: "sha256:267b572211115db6a2a4484a02317fbb6d4f050da0e95b1db4243d49889483de", + BlobIDs: []string{ + "sha256:267b572211115db6a2a4484a02317fbb6d4f050da0e95b1db4243d49889483de", + }, + }, + }, + { + name: "passed", + fields: fields{ + dir: "./testdata/misconfig/cloudformation/passed/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/cloudformation/passed/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "cloudformation", + FilePath: "main.yaml", + Successes: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "CloudFormation Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/cloudformation/passed/src", + Type: types.ArtifactFilesystem, + ID: "sha256:8ca92725ce2f47b7ffb1b0a9e0359d59ac2b3b3f517ba42f66a859436057e54a", + BlobIDs: []string{ + "sha256:8ca92725ce2f47b7ffb1b0a9e0359d59ac2b3b3f517ba42f66a859436057e54a", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + tt.artifactOpt.DisabledHandlers = []types.HandlerType{ + types.SystemFileFilteringPostHandler, + } + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true + a, err := NewArtifact(tt.fields.dir, c, tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestDockerfileMisconfigurationScan(t *testing.T) { + type fields struct { + dir string + } + tests := []struct { + name string + fields fields + putBlobExpectation cache.ArtifactCachePutBlobExpectation + artifactOpt artifact.Option + want types.ArtifactReference + }{ + { + name: "single failure", + fields: fields{ + dir: "./testdata/misconfig/dockerfile/single-failure/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/dockerfile/single-failure/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "dockerfile", + FilePath: "Dockerfile", + Successes: types.MisconfResults{ + types.MisconfResult{ + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Dockerfile Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/dockerfile/single-failure/src", + Type: types.ArtifactFilesystem, + ID: "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", + BlobIDs: []string{ + "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", + }, + }, + }, + { + name: "multiple failures", + fields: fields{ + dir: "./testdata/misconfig/dockerfile/multiple-failures/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/dockerfile/multiple-failures/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "dockerfile", + FilePath: "Dockerfile", + Successes: types.MisconfResults{ + types.MisconfResult{ + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Dockerfile Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/dockerfile/multiple-failures/src", + Type: types.ArtifactFilesystem, + ID: "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", + BlobIDs: []string{ + "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", + }, + }, + }, + { + name: "no results", + fields: fields{ + dir: "./testdata/misconfig/dockerfile/no-results/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/dockerfile/no-results/rego"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/dockerfile/no-results/src", + Type: types.ArtifactFilesystem, + ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + BlobIDs: []string{ + "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + }, + }, + }, + { + name: "passed", + fields: fields{ + dir: "./testdata/misconfig/dockerfile/passed/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/dockerfile/passed/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "dockerfile", + FilePath: "Dockerfile", + Successes: []types.MisconfResult{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Dockerfile Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{ + "https://trivy.dev/", + }, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/dockerfile/passed/src", + Type: types.ArtifactFilesystem, + ID: "sha256:4cc7f6bba417cc65c5391bc9c07fd1e205e21bdec87b271889433af18be1e454", + BlobIDs: []string{ + "sha256:4cc7f6bba417cc65c5391bc9c07fd1e205e21bdec87b271889433af18be1e454", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + tt.artifactOpt.DisabledHandlers = []types.HandlerType{ + types.SystemFileFilteringPostHandler, + } + a, err := NewArtifact(tt.fields.dir, c, tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestKubernetesMisconfigurationScan(t *testing.T) { + type fields struct { + dir string + } + tests := []struct { + name string + fields fields + putBlobExpectation cache.ArtifactCachePutBlobExpectation + artifactOpt artifact.Option + want types.ArtifactReference + }{ + { + name: "single failure", + fields: fields{ + dir: "./testdata/misconfig/kubernetes/single-failure/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/kubernetes/single-failure/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "kubernetes", + FilePath: "test.yaml", + Failures: []types.MisconfResult{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No evil containers allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Kubernetes Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{ + "https://trivy.dev/", + }, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + StartLine: 7, + EndLine: 9, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/kubernetes/single-failure/src", + Type: types.ArtifactFilesystem, + ID: "sha256:d5ca0b4e96aaaeafa424a2250db6297a5182cb6ca5db49bc1ff11790f6cdbee9", + BlobIDs: []string{ + "sha256:d5ca0b4e96aaaeafa424a2250db6297a5182cb6ca5db49bc1ff11790f6cdbee9", + }, + }, + }, + { + name: "multiple failures", + fields: fields{ + dir: "./testdata/misconfig/kubernetes/multiple-failures/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/kubernetes/multiple-failures/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "kubernetes", + FilePath: "test.yaml", + Failures: []types.MisconfResult{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No evil containers allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Kubernetes Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{ + "https://trivy.dev/", + }, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + StartLine: 7, + EndLine: 9, + }, + }, + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No evil containers allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Kubernetes Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{ + "https://trivy.dev/", + }, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + StartLine: 10, + EndLine: 12, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/kubernetes/multiple-failures/src", + Type: types.ArtifactFilesystem, + ID: "sha256:eef9fff2fe8f5c4a123c018b4f91db25d9676e7d171a3a683c2fbfbbbe82fa54", + BlobIDs: []string{ + "sha256:eef9fff2fe8f5c4a123c018b4f91db25d9676e7d171a3a683c2fbfbbbe82fa54", + }, + }, + }, + { + name: "no results", + fields: fields{ + dir: "./testdata/misconfig/kubernetes/no-results/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/kubernetes/no-results/rego"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/kubernetes/no-results/src", + Type: types.ArtifactFilesystem, + ID: "sha256:2b54cf33feaa1fe1f5bf223f873ca6c3f7c3693b0bb3b0ce9e2e7fd79cd37b5a", + BlobIDs: []string{ + "sha256:2b54cf33feaa1fe1f5bf223f873ca6c3f7c3693b0bb3b0ce9e2e7fd79cd37b5a", + }, + }, + }, + { + name: "passed", + fields: fields{ + dir: "./testdata/misconfig/kubernetes/passed/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/kubernetes/passed/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "kubernetes", + FilePath: "test.yaml", + Successes: []types.MisconfResult{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Kubernetes Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{ + "https://trivy.dev/", + }, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/kubernetes/passed/src", + Type: types.ArtifactFilesystem, + ID: "sha256:dc7a0fd3ea2f13b0ea05f4e517a16e602b0fc17fbd72aa5e34107ef12a91a30b", + BlobIDs: []string{ + "sha256:dc7a0fd3ea2f13b0ea05f4e517a16e602b0fc17fbd72aa5e34107ef12a91a30b", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + tt.artifactOpt.DisabledHandlers = []types.HandlerType{ + types.SystemFileFilteringPostHandler, + } + a, err := NewArtifact(tt.fields.dir, c, tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestAzureARMMisconfigurationScan(t *testing.T) { + type fields struct { + dir string + } + tests := []struct { + name string + fields fields + putBlobExpectation cache.ArtifactCachePutBlobExpectation + artifactOpt artifact.Option + want types.ArtifactReference + }{ + { + name: "single failure", + fields: fields{ + dir: "./testdata/misconfig/azurearm/single-failure/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/azurearm/single-failure/rego"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "azure-arm", + FilePath: "deploy.json", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No account allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Azure ARM Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Resource: "resources[0]", + Provider: "Generic", + Service: "general", + StartLine: 30, + EndLine: 40, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/azurearm/single-failure/src", + Type: types.ArtifactFilesystem, + ID: "sha256:c1a8bfd544b9041ad194382cc42b54289f70966d061ef501b267aec8fd07c5df", + BlobIDs: []string{ + "sha256:c1a8bfd544b9041ad194382cc42b54289f70966d061ef501b267aec8fd07c5df", + }, + }, + }, + { + name: "multiple failures", + fields: fields{ + dir: "./testdata/misconfig/azurearm/multiple-failures/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/azurearm/multiple-failures/rego"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "azure-arm", + FilePath: "deploy.json", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No account allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Azure ARM Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Resource: "resources[0]", + Provider: "Generic", + Service: "general", + StartLine: 30, + EndLine: 40, + }, + }, + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No account allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Azure ARM Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Resource: "resources[1]", + Provider: "Generic", + Service: "general", + StartLine: 41, + EndLine: 51, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/azurearm/multiple-failures/src", + Type: types.ArtifactFilesystem, + ID: "sha256:75bf0e88f8d2857be90fb8d10a350c04c1532ba7f510e1eb404a8bae30ce97d8", + BlobIDs: []string{ + "sha256:75bf0e88f8d2857be90fb8d10a350c04c1532ba7f510e1eb404a8bae30ce97d8", + }, + }, + }, + { + name: "no results", + fields: fields{ + dir: "./testdata/misconfig/azurearm/no-results/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/azurearm/no-results/rego"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/azurearm/no-results/src", + Type: types.ArtifactFilesystem, + ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + BlobIDs: []string{ + "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + }, + }, + }, + { + name: "passed", + fields: fields{ + dir: "./testdata/misconfig/azurearm/passed/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/azurearm/passed/rego"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "azure-arm", + FilePath: "deploy.json", + Successes: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Azure ARM Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/azurearm/passed/src", + Type: types.ArtifactFilesystem, + ID: "sha256:b9ba7c4eafec405c8b6998dbb98ee1c7f7830caf8487fd1461433ff82d8779e9", + BlobIDs: []string{ + "sha256:b9ba7c4eafec405c8b6998dbb98ee1c7f7830caf8487fd1461433ff82d8779e9", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + tt.artifactOpt.DisabledHandlers = []types.HandlerType{ + types.SystemFileFilteringPostHandler, + } + a, err := NewArtifact(tt.fields.dir, c, tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestMixedConfigurationScan(t *testing.T) { + type fields struct { + dir string + } + tests := []struct { + name string + fields fields + putBlobExpectation cache.ArtifactCachePutBlobExpectation + artifactOpt artifact.Option + want types.ArtifactReference + }{ + { + name: "single failure each within terraform and cloudformation", + fields: fields{ + dir: "./testdata/misconfig/mixed/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/mixed/rego"}, + DisableEmbeddedPolicies: true, + DisableEmbeddedLibraries: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "terraform", + FilePath: "main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No buckets allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Terraform Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.asd", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + }, + }, + { + FileType: "cloudformation", + FilePath: "main.yaml", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "No buckets allowed!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "CloudFormation Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, + }, + CauseMetadata: types.CauseMetadata{ + Resource: "main.yaml:3-6", + Provider: "Generic", + Service: "general", + StartLine: 3, + EndLine: 6, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/mixed/src", + Type: types.ArtifactFilesystem, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + tt.artifactOpt.DisabledHandlers = []types.HandlerType{ + types.SystemFileFilteringPostHandler, + } + a, err := NewArtifact(tt.fields.dir, c, tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + require.NotNil(t, got) + + assert.Equal(t, tt.want.Name, got.Name) + assert.Equal(t, tt.want.Type, got.Type) + }) + } + +} diff --git a/pkg/fanal/artifact/local/testdata/alpine/etc/alpine-release b/pkg/fanal/artifact/local/testdata/alpine/etc/alpine-release new file mode 100644 index 000000000000..375f5cabfe6c --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/alpine/etc/alpine-release @@ -0,0 +1 @@ +3.11.6 diff --git a/pkg/fanal/artifact/local/testdata/alpine/etc/hostname b/pkg/fanal/artifact/local/testdata/alpine/etc/hostname new file mode 100644 index 000000000000..c70dc2dfaf07 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/alpine/etc/hostname @@ -0,0 +1 @@ +host diff --git a/pkg/fanal/artifact/local/testdata/alpine/lib/apk/db/installed b/pkg/fanal/artifact/local/testdata/alpine/lib/apk/db/installed new file mode 100644 index 000000000000..dc23126b6a1b --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/alpine/lib/apk/db/installed @@ -0,0 +1,21 @@ +C:Q1yyMWoYnr7lKCxKm9mHlMwkd6dMY= +P:musl +V:1.1.24-r2 +A:x86_64 +S:377123 +I:614400 +T:the musl c library (libc) implementation +U:https://musl.libc.org/ +L:MIT +o:musl +m:Timo Teräs +t:1584790550 +c:4024cc3b29ad4c65544ad068b8f59172b5494306 +p:so:libc.musl-x86_64.so.1=1 +F:lib +R:libc.musl-x86_64.so.1 +a:0:0:777 +Z:Q17yJ3JFNypA4mxhJJr0ou6CzsJVI= +R:ld-musl-x86_64.so.1 +a:0:0:755 +Z:Q19mQZaYKY6yTQWQm0hkvsrh39O7Y= diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego new file mode 100644 index 000000000000..fca807d18e9d --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + account := input.azure.storage.accounts[_] + res := result("No account allowed!", account) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/src/deploy.json b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/src/deploy.json new file mode 100644 index 000000000000..8978cca2f43c --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/src/deploy.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "storageAccountType": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS", + "Standard_ZRS", + "Premium_LRS" + ], + "metadata": { + "description": "Storage Account type" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + } + }, + "variables": { + "storageAccountName": "[concat('store', uniquestring(resourceGroup().id))]" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "location": "[parameters('location')]", + "apiVersion": "2018-07-01", + "sku": { + "name": "[parameters('storageAccountType')]" + }, + "kind": "StorageV2", + "properties": {} + }, + { + "type": "Microsoft.Storage/storageAccounts", + "name": "bucket2", + "location": "[parameters('location')]", + "apiVersion": "2018-07-01", + "sku": { + "name": "[parameters('storageAccountType')]" + }, + "kind": "StorageV2", + "properties": {} + } + ], + "outputs": { + "storageAccountName": { + "type": "string", + "value": "[variables('storageAccountName')]" + } + } +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego new file mode 100644 index 000000000000..ecf4506727a3 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + bucket := input.aws.s3.buckets[_] + res := result("No buckets allowed!", bucket) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/src/.gitkeep b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/src/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego new file mode 100644 index 000000000000..efae52a8f62c --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego @@ -0,0 +1,33 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + account := input.azure.storage.accounts[_] + true == false + res := result("No accounts allowed!", account) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/src/deploy.json b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/src/deploy.json new file mode 100644 index 000000000000..fbe9fa898e95 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/src/deploy.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "storageAccountType": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS", + "Standard_ZRS", + "Premium_LRS" + ], + "metadata": { + "description": "Storage Account type" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + } + }, + "variables": { + "storageAccountName": "[concat('store', uniquestring(resourceGroup().id))]" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "location": "[parameters('location')]", + "apiVersion": "2018-07-01", + "sku": { + "name": "[parameters('storageAccountType')]" + }, + "kind": "StorageV2", + "properties": {} + } + ], + "outputs": { + "storageAccountName": { + "type": "string", + "value": "[variables('storageAccountName')]" + } + } +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego new file mode 100644 index 000000000000..fca807d18e9d --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + account := input.azure.storage.accounts[_] + res := result("No account allowed!", account) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/src/deploy.json b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/src/deploy.json new file mode 100644 index 000000000000..fbe9fa898e95 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/src/deploy.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "storageAccountType": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS", + "Standard_ZRS", + "Premium_LRS" + ], + "metadata": { + "description": "Storage Account type" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + } + }, + "variables": { + "storageAccountName": "[concat('store', uniquestring(resourceGroup().id))]" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "location": "[parameters('location')]", + "apiVersion": "2018-07-01", + "sku": { + "name": "[parameters('storageAccountType')]" + }, + "kind": "StorageV2", + "properties": {} + } + ], + "outputs": { + "storageAccountName": { + "type": "string", + "value": "[variables('storageAccountName')]" + } + } +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego new file mode 100644 index 000000000000..ecf4506727a3 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + bucket := input.aws.s3.buckets[_] + res := result("No buckets allowed!", bucket) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/src/main.yaml b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/src/main.yaml new file mode 100644 index 000000000000..f07fec238b87 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/src/main.yaml @@ -0,0 +1,9 @@ +Resources: + S3BucketOne: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: bucket-one + S3BucketTwo: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: bucket-two diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego new file mode 100644 index 000000000000..ecf4506727a3 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + bucket := input.aws.s3.buckets[_] + res := result("No buckets allowed!", bucket) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/src/.gitkeep b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/src/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/cfparams.json b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/cfparams.json new file mode 100644 index 000000000000..b9276cb36eb2 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/cfparams.json @@ -0,0 +1,6 @@ +[ + { + "ParameterKey": "KmsMasterKeyId", + "ParameterValue": "some_id" + } +] \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego new file mode 100644 index 000000000000..1a94609aaa8b --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego @@ -0,0 +1,22 @@ +# METADATA +# title: "Bad stuff is bad" +# description: "Its not good!" +# scope: package +# schemas: +# - input: schema["cloud"] +# custom: +# avd_id: AVD-TEST-0001 +# id: TEST001 +# provider: aws +# service: sqs +# severity: HIGH +# short_code: foo-bar-baz +# recommended_action: "Remove bad stuff" + +package user.something + +deny[res] { + qs := input.aws.sqs.queues[_] + qs.encryption.kmskeyid.value == "" + res := "No unencrypted queues allowed!" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/src/main.yaml b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/src/main.yaml new file mode 100644 index 000000000000..45047944d8c9 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/src/main.yaml @@ -0,0 +1,10 @@ +AWSTemplateFormatVersion: 2010-09-09 +Parameters: + KmsMasterKeyId: + Type: String +Resources: + TestQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: worst-possible-queue + KmsMasterKeyId: !Ref KmsMasterKeyId \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego new file mode 100644 index 000000000000..d844d6cd2e75 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego @@ -0,0 +1,33 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + bucket := input.aws.s3.buckets[_] + true == false + res := result("No buckets allowed!", bucket) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/src/main.yaml b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/src/main.yaml new file mode 100644 index 000000000000..5406b2c9f2a1 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/src/main.yaml @@ -0,0 +1,5 @@ +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: public-bucket diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego new file mode 100644 index 000000000000..ecf4506727a3 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + bucket := input.aws.s3.buckets[_] + res := result("No buckets allowed!", bucket) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/src/main.yaml b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/src/main.yaml new file mode 100644 index 000000000000..2135e1c51922 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/src/main.yaml @@ -0,0 +1,6 @@ +--- +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: public-bucket diff --git a/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/multiple-failures/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/multiple-failures/rego/policy.rego new file mode 100644 index 000000000000..ab4daa223c8b --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/multiple-failures/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", object.get(metadata, "StartLine", 0)), + "endline": object.get(metadata, "endline", object.get(metadata, "EndLine", 0)), + "filepath": object.get(metadata, "filepath", object.get(metadata, "Path", "")), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + cmd := input.stages[_][_] + res := result("No commands allowed!", cmd) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/multiple-failures/src/Dockerfile b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/multiple-failures/src/Dockerfile new file mode 100644 index 000000000000..200e856568eb --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/multiple-failures/src/Dockerfile @@ -0,0 +1,3 @@ +FROM ubuntu + +FROM alpine \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/no-results/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/no-results/rego/policy.rego new file mode 100644 index 000000000000..ecf4506727a3 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/no-results/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + bucket := input.aws.s3.buckets[_] + res := result("No buckets allowed!", bucket) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/no-results/src/.gitkeep b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/no-results/src/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/passed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/passed/rego/policy.rego new file mode 100644 index 000000000000..4ea9908bff21 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/passed/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + true == false + res := result("No nothing allowed!", {}) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/passed/src/Dockerfile b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/passed/src/Dockerfile new file mode 100644 index 000000000000..7078ce2076df --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/passed/src/Dockerfile @@ -0,0 +1 @@ +FROM nowhere \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/single-failure/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/single-failure/rego/policy.rego new file mode 100644 index 000000000000..ab4daa223c8b --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/single-failure/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", object.get(metadata, "StartLine", 0)), + "endline": object.get(metadata, "endline", object.get(metadata, "EndLine", 0)), + "filepath": object.get(metadata, "filepath", object.get(metadata, "Path", "")), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + cmd := input.stages[_][_] + res := result("No commands allowed!", cmd) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/single-failure/src/Dockerfile b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/single-failure/src/Dockerfile new file mode 100644 index 000000000000..6d4b4c49ea2e --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/dockerfile/single-failure/src/Dockerfile @@ -0,0 +1 @@ +FROM ubuntu \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego new file mode 100644 index 000000000000..6acb90f8b852 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego @@ -0,0 +1,33 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", object.get(metadata, "StartLine", 0)), + "endline": object.get(metadata, "endline", object.get(metadata, "EndLine", 0)), + "filepath": object.get(metadata, "filepath", object.get(metadata, "Path", "")), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + container := input.spec.containers[_] + container.image == "evil" + res := result("No evil containers allowed!", container) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/src/test.yaml b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/src/test.yaml new file mode 100644 index 000000000000..b8eb522009ce --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/src/test.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: evil-pod +spec: + containers: + - command: [ "sh", "-c", "echo 'Hello' && sleep 1h" ] + image: evil + name: evil1 + - command: [ "sh", "-c", "echo 'Hello' && sleep 1h" ] + image: evil + name: evil2 diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego new file mode 100644 index 000000000000..87c2e8f830b8 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego @@ -0,0 +1,33 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + container := input.spec.containers[_] + container.image == "evil" + res := result("No evil containers allowed!", container) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/src/.gitkeep b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/src/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego new file mode 100644 index 000000000000..c46411777481 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego @@ -0,0 +1,34 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + true == false + container := input.spec.containers[_] + container.image == "evil" + res := result("No evil containers allowed!", container) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/src/test.yaml b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/src/test.yaml new file mode 100644 index 000000000000..e50c03986d23 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/src/test.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: good-pod +spec: + containers: + - command: [ "sh", "-c", "echo 'Hello' && sleep 1h" ] + image: good + name: good diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego new file mode 100644 index 000000000000..de8ae68d4d5d --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego @@ -0,0 +1,33 @@ +package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-evil", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", object.get(metadata, "StartLine", 0)), + "endline": object.get(metadata, "endline", object.get(metadata, "EndLine", 0)), + "filepath": object.get(metadata, "filepath", object.get(metadata, "Path", "")), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + container := input.spec.containers[_] + container.image == "evil" + res := result("No evil containers allowed!", container) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/src/test.yaml b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/src/test.yaml new file mode 100644 index 000000000000..1874c10c48a4 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/src/test.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: evil-pod +spec: + containers: + - command: [ "sh", "-c", "echo 'Hello' && sleep 1h" ] + image: evil + name: evil diff --git a/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego new file mode 100644 index 000000000000..a9399362d285 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego @@ -0,0 +1,32 @@ +package user.something + + __rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "no-buckets", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", + } + + # taken from defsec rego lib to mimic behaviour + result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } + } + + deny[res] { + bucket := input.aws.s3.buckets[_] + res := result("No buckets allowed!", bucket) + } \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/mixed/src/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/mixed/src/main.tf new file mode 100644 index 000000000000..62697f2ab0bc --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/mixed/src/main.tf @@ -0,0 +1,3 @@ +resource "aws_s3_bucket" "asd" { + + } \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/mixed/src/main.yaml b/pkg/fanal/artifact/local/testdata/misconfig/mixed/src/main.yaml new file mode 100644 index 000000000000..c3e9adcc2fd9 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/mixed/src/main.yaml @@ -0,0 +1,6 @@ +--- +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: public-bucket \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/busted-relative-paths/child/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraform/busted-relative-paths/child/main.tf new file mode 100644 index 000000000000..192ffbe802a3 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/busted-relative-paths/child/main.tf @@ -0,0 +1,11 @@ +resource "aws_s3_bucket" "one" { + + } + + resource "aws_s3_bucket" "two" { + + } + + module "module_in_parent_dir" { + source = "../does not exist anywhere/" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/busted-relative-paths/parent/more.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraform/busted-relative-paths/parent/more.tf new file mode 100644 index 000000000000..b06a4528a493 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/busted-relative-paths/parent/more.tf @@ -0,0 +1,3 @@ +resource "aws_s3_bucket" "three" { + + } \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/multiple-failures/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraform/multiple-failures/main.tf new file mode 100644 index 000000000000..6d6aa02ece44 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/multiple-failures/main.tf @@ -0,0 +1,7 @@ +resource "aws_s3_bucket" "one" { + +} + +resource "aws_s3_bucket" "two" { + +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/multiple-failures/more.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraform/multiple-failures/more.tf new file mode 100644 index 000000000000..e5cc0c1ea195 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/multiple-failures/more.tf @@ -0,0 +1,3 @@ +resource "aws_s3_bucket" "three" { + +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/no-results/.gitkeep b/pkg/fanal/artifact/local/testdata/misconfig/terraform/no-results/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/passed/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraform/passed/main.tf new file mode 100644 index 000000000000..28149d8b5608 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/passed/main.tf @@ -0,0 +1,3 @@ +resource "aws_s3_bucket" "asd" { + bucket = "asd" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/terraform/rego/policy.rego new file mode 100644 index 000000000000..b1cd80249c9e --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/rego/policy.rego @@ -0,0 +1,33 @@ +package user.something + + __rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "empty-bucket-name", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", + } + + # taken from defsec rego lib to mimic behaviour + result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } + } + + deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "" + res := result("Empty bucket name!", bucket) + } \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/relative-paths/child/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraform/relative-paths/child/main.tf new file mode 100644 index 000000000000..4e7522b5c20f --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/relative-paths/child/main.tf @@ -0,0 +1,7 @@ +resource "aws_s3_bucket" "one" { + +} + +module "module_in_nested_dir" { + source = "./nested/" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/relative-paths/child/nested/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraform/relative-paths/child/nested/main.tf new file mode 100644 index 000000000000..45aab16e2bda --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/relative-paths/child/nested/main.tf @@ -0,0 +1,7 @@ +resource "aws_s3_bucket" "two" { + +} + +module "module_in_parent_dir" { + source = "../../parent/" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/relative-paths/parent/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraform/relative-paths/parent/main.tf new file mode 100644 index 000000000000..7d6126ed3c8e --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/relative-paths/parent/main.tf @@ -0,0 +1,3 @@ +resource "aws_s3_bucket" "three" { + +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/single-failure/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraform/single-failure/main.tf new file mode 100644 index 000000000000..4a0a3a1fcd29 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/single-failure/main.tf @@ -0,0 +1,3 @@ +resource "aws_s3_bucket" "asd" { + +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/tfvar-outside/main.tfvars b/pkg/fanal/artifact/local/testdata/misconfig/terraform/tfvar-outside/main.tfvars new file mode 100644 index 000000000000..6eb0b00dcbd0 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/tfvar-outside/main.tfvars @@ -0,0 +1 @@ +bucket_name = "test" diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/tfvar-outside/tf/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraform/tfvar-outside/tf/main.tf new file mode 100644 index 000000000000..d9e4a4745359 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/tfvar-outside/tf/main.tf @@ -0,0 +1,7 @@ +resource "aws_s3_bucket" "this" { + bucket = var.bucket_name +} + +variable "bucket_name" { + type = string +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/main.tf new file mode 100644 index 000000000000..9eb5363beea5 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/main.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + aws = { + source = "aws" + version = "5.35.0" + } + } +} + +resource "aws_s3_bucket" "one" { + +} + +resource "aws_s3_bucket" "two" { + +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/more.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/more.tf new file mode 100644 index 000000000000..e5cc0c1ea195 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/more.tf @@ -0,0 +1,3 @@ +resource "aws_s3_bucket" "three" { + +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/tfplan b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/tfplan new file mode 100644 index 000000000000..80b80a786b3d Binary files /dev/null and b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/tfplan differ diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/main.tf new file mode 100644 index 000000000000..64e9f0d2b965 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/main.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + aws = { + source = "aws" + version = "5.35.0" + } + } +} + +resource "aws_s3_bucket" "this" { + bucket = "test-bucket" +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/tfplan b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/tfplan new file mode 100644 index 000000000000..ee00c44a36b3 Binary files /dev/null and b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/tfplan differ diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/main.tf new file mode 100644 index 000000000000..156495110856 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/main.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + aws = { + source = "aws" + version = "5.35.0" + } + } +} + +resource "aws_s3_bucket" "this" { + +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/tfplan b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/tfplan new file mode 100644 index 000000000000..c07b8d1757f5 Binary files /dev/null and b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/tfplan differ diff --git a/pkg/fanal/artifact/local/testdata/requirements.txt b/pkg/fanal/artifact/local/testdata/requirements.txt new file mode 100644 index 000000000000..af266d21cd34 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/requirements.txt @@ -0,0 +1 @@ +Flask==3.1.3 diff --git a/pkg/fanal/artifact/mock_artifact.go b/pkg/fanal/artifact/mock_artifact.go new file mode 100644 index 000000000000..d17a15d46089 --- /dev/null +++ b/pkg/fanal/artifact/mock_artifact.go @@ -0,0 +1,103 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package artifact + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + types "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// MockArtifact is an autogenerated mock type for the Artifact type +type MockArtifact struct { + mock.Mock +} + +type ArtifactCleanArgs struct { + Reference types.ArtifactReference + ReferenceAnything bool +} + +type ArtifactCleanReturns struct { + _a0 error +} + +type ArtifactCleanExpectation struct { + Args ArtifactCleanArgs + Returns ArtifactCleanReturns +} + +func (_m *MockArtifact) ApplyCleanExpectation(e ArtifactCleanExpectation) { + var args []interface{} + if e.Args.ReferenceAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.Reference) + } + _m.On("Clean", args...).Return(e.Returns._a0) +} + +func (_m *MockArtifact) ApplyCleanExpectations(expectations []ArtifactCleanExpectation) { + for _, e := range expectations { + _m.ApplyCleanExpectation(e) + } +} + +// Clean provides a mock function with given fields: reference +func (_m *MockArtifact) Clean(reference types.ArtifactReference) error { + return nil +} + +type ArtifactInspectArgs struct { + Ctx context.Context + CtxAnything bool +} + +type ArtifactInspectReturns struct { + Reference types.ArtifactReference + Err error +} + +type ArtifactInspectExpectation struct { + Args ArtifactInspectArgs + Returns ArtifactInspectReturns +} + +func (_m *MockArtifact) ApplyInspectExpectation(e ArtifactInspectExpectation) { + var args []interface{} + if e.Args.CtxAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.Ctx) + } + _m.On("Inspect", args...).Return(e.Returns.Reference, e.Returns.Err) +} + +func (_m *MockArtifact) ApplyInspectExpectations(expectations []ArtifactInspectExpectation) { + for _, e := range expectations { + _m.ApplyInspectExpectation(e) + } +} + +// Inspect provides a mock function with given fields: ctx +func (_m *MockArtifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { + ret := _m.Called(ctx) + + var r0 types.ArtifactReference + if rf, ok := ret.Get(0).(func(context.Context) types.ArtifactReference); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(types.ArtifactReference) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/fanal/artifact/repo/git.go b/pkg/fanal/artifact/repo/git.go new file mode 100644 index 000000000000..de1a528f65dc --- /dev/null +++ b/pkg/fanal/artifact/repo/git.go @@ -0,0 +1,207 @@ +package repo + +import ( + "context" + "net/url" + "os" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/hashicorp/go-multierror" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type Artifact struct { + url string + local artifact.Artifact +} + +func NewArtifact(target string, c cache.ArtifactCache, artifactOpt artifact.Option) ( + artifact.Artifact, func(), error) { + + var cleanup func() + var errs error + + // Try the local repository + art, err := tryLocalRepo(target, c, artifactOpt) + if err == nil { + return art, func() {}, nil + } + errs = multierror.Append(errs, err) + + // Try the remote git repository + art, cleanup, err = tryRemoteRepo(target, c, artifactOpt) + if err == nil { + return art, cleanup, nil + } + errs = multierror.Append(errs, err) + + // Return errors + return nil, cleanup, errs +} + +func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { + ref, err := a.local.Inspect(ctx) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("remote repository error: %w", err) + } + + if a.url != "" { + ref.Name = a.url + } + ref.Type = types.ArtifactRepository + + return ref, nil +} + +func (Artifact) Clean(_ types.ArtifactReference) error { + return nil +} + +func tryLocalRepo(target string, c cache.ArtifactCache, artifactOpt artifact.Option) (artifact.Artifact, error) { + if _, err := os.Stat(target); err != nil { + return nil, xerrors.Errorf("no such path: %w", err) + } + + art, err := local.NewArtifact(target, c, artifactOpt) + if err != nil { + return nil, xerrors.Errorf("local repo artifact error: %w", err) + } + return Artifact{ + local: art, + }, nil +} + +func tryRemoteRepo(target string, c cache.ArtifactCache, artifactOpt artifact.Option) (artifact.Artifact, func(), error) { + cleanup := func() {} + u, err := newURL(target) + if err != nil { + return nil, cleanup, err + } + + tmpDir, err := cloneRepo(u, artifactOpt) + if err != nil { + return nil, cleanup, xerrors.Errorf("repository clone error: %w", err) + } + + cleanup = func() { _ = os.RemoveAll(tmpDir) } + + art, err := local.NewArtifact(tmpDir, c, artifactOpt) + if err != nil { + return nil, cleanup, xerrors.Errorf("fs artifact: %w", err) + } + + return Artifact{ + url: target, + local: art, + }, cleanup, nil + +} + +func cloneRepo(u *url.URL, artifactOpt artifact.Option) (string, error) { + tmpDir, err := os.MkdirTemp("", "trivy-remote-repo") + if err != nil { + return "", xerrors.Errorf("failed to create a temp dir: %w", err) + } + + cloneOptions := git.CloneOptions{ + URL: u.String(), + Auth: gitAuth(), + Progress: os.Stdout, + InsecureSkipTLS: artifactOpt.Insecure, + } + + // suppress clone output if noProgress + if artifactOpt.NoProgress { + cloneOptions.Progress = nil + } + + if artifactOpt.RepoCommit == "" { + cloneOptions.Depth = 1 + } + + if artifactOpt.RepoBranch != "" { + cloneOptions.ReferenceName = plumbing.NewBranchReferenceName(artifactOpt.RepoBranch) + cloneOptions.SingleBranch = true + } + + if artifactOpt.RepoTag != "" { + cloneOptions.ReferenceName = plumbing.NewTagReferenceName(artifactOpt.RepoTag) + cloneOptions.SingleBranch = true + } + + r, err := git.PlainClone(tmpDir, false, &cloneOptions) + if err != nil { + return "", xerrors.Errorf("git clone error: %w", err) + } + + if artifactOpt.RepoCommit != "" { + w, err := r.Worktree() + if err != nil { + return "", xerrors.Errorf("git worktree error: %w", err) + } + + err = w.Checkout(&git.CheckoutOptions{ + Hash: plumbing.NewHash(artifactOpt.RepoCommit), + }) + if err != nil { + return "", xerrors.Errorf("git checkout error: %w", err) + } + } + + return tmpDir, nil +} + +func newURL(rawurl string) (*url.URL, error) { + u, err := url.Parse(rawurl) + if err != nil { + return nil, xerrors.Errorf("url parse error: %w", err) + } + // "https://" can be omitted + // e.g. github.com/aquasecurity/trivy + if u.Scheme == "" { + u.Scheme = "https" + } + + return u, nil +} + +// Helper function to check for a GitHub/GitLab token from env vars in order to +// make authenticated requests to access private repos +func gitAuth() *http.BasicAuth { + var auth *http.BasicAuth + + // The username can be anything for HTTPS Git operations + gitUsername := "fanal-aquasecurity-scan" + + // We first check if a GitHub token was provided + githubToken := os.Getenv("GITHUB_TOKEN") + if githubToken != "" { + auth = &http.BasicAuth{ + Username: gitUsername, + Password: githubToken, + } + return auth + } + + // Otherwise we check if a GitLab token was provided + gitlabToken := os.Getenv("GITLAB_TOKEN") + if gitlabToken != "" { + auth = &http.BasicAuth{ + Username: gitUsername, + Password: gitlabToken, + } + return auth + } + + // If no token was provided, we simply return a nil, + // which will make the request to be unauthenticated + return nil + +} diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go new file mode 100644 index 000000000000..ab8221c2f9e8 --- /dev/null +++ b/pkg/fanal/artifact/repo/git_test.go @@ -0,0 +1,267 @@ +//go:build unix + +package repo + +import ( + "context" + "net/http/httptest" + "testing" + + "github.com/sosedoff/gitkit" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" +) + +func setupGitServer() (*httptest.Server, error) { + service := gitkit.New(gitkit.Config{ + Dir: "./testdata", + AutoCreate: false, + }) + + if err := service.Setup(); err != nil { + return nil, err + } + + ts := httptest.NewServer(service) + + return ts, nil +} + +func TestNewArtifact(t *testing.T) { + ts, err := setupGitServer() + require.NoError(t, err) + defer ts.Close() + + type args struct { + target string + c cache.ArtifactCache + noProgress bool + repoBranch string + repoTag string + repoCommit string + } + tests := []struct { + name string + args args + assertion assert.ErrorAssertionFunc + }{ + { + name: "remote repo", + args: args{ + target: ts.URL + "/test.git", + c: nil, + noProgress: false, + }, + assertion: assert.NoError, + }, + { + name: "local repo", + args: args{ + target: "testdata", + c: nil, + noProgress: false, + }, + assertion: assert.NoError, + }, + { + name: "happy noProgress", + args: args{ + target: ts.URL + "/test.git", + c: nil, + noProgress: true, + }, + assertion: assert.NoError, + }, + { + name: "branch", + args: args{ + target: ts.URL + "/test.git", + c: nil, + repoBranch: "valid-branch", + }, + assertion: assert.NoError, + }, + { + name: "tag", + args: args{ + target: ts.URL + "/test.git", + c: nil, + repoTag: "v1.0.0", + }, + assertion: assert.NoError, + }, + { + name: "commit", + args: args{ + target: ts.URL + "/test.git", + c: nil, + repoCommit: "6ac152fe2b87cb5e243414df71790a32912e778d", + }, + assertion: assert.NoError, + }, + { + name: "sad path", + args: args{ + target: ts.URL + "/unknown.git", + c: nil, + noProgress: false, + }, + assertion: func(t assert.TestingT, err error, args ...interface{}) bool { + return assert.ErrorContains(t, err, "repository not found") + }, + }, + { + name: "invalid url", + args: args{ + target: "ht tp://foo.com", + c: nil, + noProgress: false, + }, + assertion: func(t assert.TestingT, err error, args ...interface{}) bool { + return assert.ErrorContains(t, err, "url parse error") + }, + }, + { + name: "invalid branch", + args: args{ + target: ts.URL + "/test.git", + c: nil, + repoBranch: "invalid-branch", + }, + assertion: func(t assert.TestingT, err error, args ...interface{}) bool { + return assert.ErrorContains(t, err, `couldn't find remote ref "refs/heads/invalid-branch"`) + }, + }, + { + name: "invalid tag", + args: args{ + target: ts.URL + "/test.git", + c: nil, + repoTag: "v1.0.9", + }, + assertion: func(t assert.TestingT, err error, args ...interface{}) bool { + return assert.ErrorContains(t, err, `couldn't find remote ref "refs/tags/v1.0.9"`) + }, + }, + { + name: "invalid commit", + args: args{ + target: ts.URL + "/test.git", + c: nil, + repoCommit: "6ac152fe2b87cb5e243414df71790a32912e778e", + }, + assertion: func(t assert.TestingT, err error, args ...interface{}) bool { + return assert.ErrorContains(t, err, "git checkout error: object not found") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, cleanup, err := NewArtifact(tt.args.target, tt.args.c, artifact.Option{ + NoProgress: tt.args.noProgress, + RepoBranch: tt.args.repoBranch, + RepoTag: tt.args.repoTag, + RepoCommit: tt.args.repoCommit, + }) + tt.assertion(t, err) + defer cleanup() + }) + } +} + +func TestArtifact_Inspect(t *testing.T) { + ts, err := setupGitServer() + require.NoError(t, err) + defer ts.Close() + + tests := []struct { + name string + rawurl string + want types.ArtifactReference + wantErr bool + }{ + { + name: "happy path", + rawurl: ts.URL + "/test.git", + want: types.ArtifactReference{ + Name: ts.URL + "/test.git", + Type: types.ArtifactRepository, + ID: "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", + BlobIDs: []string{ + "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsCache, err := cache.NewFSCache(t.TempDir()) + require.NoError(t, err) + + art, cleanup, err := NewArtifact(tt.rawurl, fsCache, artifact.Option{}) + require.NoError(t, err) + defer cleanup() + + ref, err := art.Inspect(context.Background()) + assert.NoError(t, err) + assert.Equal(t, tt.want, ref) + }) + } +} + +func Test_newURL(t *testing.T) { + type args struct { + rawurl string + } + tests := []struct { + name string + args args + want string + wantErr string + }{ + { + name: "happy path", + args: args{ + rawurl: "https://github.com/aquasecurity/fanal", + }, + want: "https://github.com/aquasecurity/fanal", + }, + { + name: "happy path: no scheme", + args: args{ + rawurl: "github.com/aquasecurity/fanal", + }, + want: "https://github.com/aquasecurity/fanal", + }, + { + name: "sad path: invalid url", + args: args{ + rawurl: "ht tp://foo.com", + }, + wantErr: "first path segment in URL cannot contain colon", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := newURL(tt.args.rawurl) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + require.NoError(t, err) + } + + assert.Equal(t, tt.want, got.String()) + }) + } +} diff --git a/pkg/fanal/artifact/repo/testdata/test.git/HEAD b/pkg/fanal/artifact/repo/testdata/test.git/HEAD new file mode 100644 index 000000000000..cb089cd89a7d --- /dev/null +++ b/pkg/fanal/artifact/repo/testdata/test.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/pkg/fanal/artifact/repo/testdata/test.git/config b/pkg/fanal/artifact/repo/testdata/test.git/config new file mode 100644 index 000000000000..e6da231579bc --- /dev/null +++ b/pkg/fanal/artifact/repo/testdata/test.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/0d/8bf3f07c3970b3e38c2cfb1c619cb86fae76d2 b/pkg/fanal/artifact/repo/testdata/test.git/objects/0d/8bf3f07c3970b3e38c2cfb1c619cb86fae76d2 new file mode 100644 index 000000000000..9baa2c164c9e --- /dev/null +++ b/pkg/fanal/artifact/repo/testdata/test.git/objects/0d/8bf3f07c3970b3e38c2cfb1c619cb86fae76d2 @@ -0,0 +1,2 @@ +x¥ŽAjÄ0sÖ+æ’ÆÒXË²ä ¼`,"µ^¼c’çG—¼ Ї¦¡ŠÎ{ïMÁGû¢‡xb–°$nZÓ„3Ìd±¸˜VŸ—¬ul|È]!rvÁñËLy â'œWÈQ²Œ>9/DójøÔºðYÏ¥rÿàíhß¼1\Ÿ¿~¾unÛ[Þû \ 4[ŠáÕ´f¬ã©Ê¦ë´¶'Œð}×:„*? +¥mb~ÂÜT‰ \ No newline at end of file diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/1c/1d7deed649fbecd66fab423ccd9d001bf9ff91 b/pkg/fanal/artifact/repo/testdata/test.git/objects/1c/1d7deed649fbecd66fab423ccd9d001bf9ff91 new file mode 100644 index 000000000000..1c1c8f2a4f50 Binary files /dev/null and b/pkg/fanal/artifact/repo/testdata/test.git/objects/1c/1d7deed649fbecd66fab423ccd9d001bf9ff91 differ diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/27/aaec53f92314d9438a53c703f169d2cbf5001a b/pkg/fanal/artifact/repo/testdata/test.git/objects/27/aaec53f92314d9438a53c703f169d2cbf5001a new file mode 100644 index 000000000000..1e1970b9b3e2 Binary files /dev/null and b/pkg/fanal/artifact/repo/testdata/test.git/objects/27/aaec53f92314d9438a53c703f169d2cbf5001a differ diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/5e/fb9bc29c482e023e40e0a2b3b7e49cec842034 b/pkg/fanal/artifact/repo/testdata/test.git/objects/5e/fb9bc29c482e023e40e0a2b3b7e49cec842034 new file mode 100644 index 000000000000..d615c02a6b36 Binary files /dev/null and b/pkg/fanal/artifact/repo/testdata/test.git/objects/5e/fb9bc29c482e023e40e0a2b3b7e49cec842034 differ diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/6a/c152fe2b87cb5e243414df71790a32912e778d b/pkg/fanal/artifact/repo/testdata/test.git/objects/6a/c152fe2b87cb5e243414df71790a32912e778d new file mode 100644 index 000000000000..f40c82e685b2 --- /dev/null +++ b/pkg/fanal/artifact/repo/testdata/test.git/objects/6a/c152fe2b87cb5e243414df71790a32912e778d @@ -0,0 +1,3 @@ +x¥ŽKŠÃ0Dg­Sô>¬¿BÈr‚¶º5XqpÚ$Çmr‚@-ªÞâQym­ +ï~dc6.…@š¬&*…&Ë”‹O–<ñäl +äµóêßò8„’Ž.SšcŽ:õ­%ØñÈ9*Üe^7¸Íû4cûÅe«/\ÎÏǧï׿†u9æµ]@ÓÐ¥#oÕi*üCF9Ìõ =ÂoRVÿœ‡Rò \ No newline at end of file diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/c0/42cd14d2b999cade090785af47e9f8b8e342ff b/pkg/fanal/artifact/repo/testdata/test.git/objects/c0/42cd14d2b999cade090785af47e9f8b8e342ff new file mode 100644 index 000000000000..8320beb6f186 Binary files /dev/null and b/pkg/fanal/artifact/repo/testdata/test.git/objects/c0/42cd14d2b999cade090785af47e9f8b8e342ff differ diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/c9/06fc4a94762f8a2c77c718947143d16e4e9ec7 b/pkg/fanal/artifact/repo/testdata/test.git/objects/c9/06fc4a94762f8a2c77c718947143d16e4e9ec7 new file mode 100644 index 000000000000..1751baa5dbc3 Binary files /dev/null and b/pkg/fanal/artifact/repo/testdata/test.git/objects/c9/06fc4a94762f8a2c77c718947143d16e4e9ec7 differ diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/d7/937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b b/pkg/fanal/artifact/repo/testdata/test.git/objects/d7/937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b new file mode 100644 index 000000000000..c948dc6ea8d0 Binary files /dev/null and b/pkg/fanal/artifact/repo/testdata/test.git/objects/d7/937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b differ diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/e2/4866d1d31ddffdb27fbcf583d5deb4386d5145 b/pkg/fanal/artifact/repo/testdata/test.git/objects/e2/4866d1d31ddffdb27fbcf583d5deb4386d5145 new file mode 100644 index 000000000000..5f1213f2ea4c Binary files /dev/null and b/pkg/fanal/artifact/repo/testdata/test.git/objects/e2/4866d1d31ddffdb27fbcf583d5deb4386d5145 differ diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/pkg/fanal/artifact/repo/testdata/test.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 000000000000..711223894375 Binary files /dev/null and b/pkg/fanal/artifact/repo/testdata/test.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/f4/836be6497e83e13dc0cfbce7e6b973b1ea511d b/pkg/fanal/artifact/repo/testdata/test.git/objects/f4/836be6497e83e13dc0cfbce7e6b973b1ea511d new file mode 100644 index 000000000000..bec9b64a3358 Binary files /dev/null and b/pkg/fanal/artifact/repo/testdata/test.git/objects/f4/836be6497e83e13dc0cfbce7e6b973b1ea511d differ diff --git a/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/master b/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/master new file mode 100644 index 000000000000..21969947cb30 --- /dev/null +++ b/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/master @@ -0,0 +1 @@ +0d8bf3f07c3970b3e38c2cfb1c619cb86fae76d2 diff --git a/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/valid-branch b/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/valid-branch new file mode 100644 index 000000000000..22301d650ca1 --- /dev/null +++ b/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/valid-branch @@ -0,0 +1 @@ +d7937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b diff --git a/pkg/fanal/artifact/repo/testdata/test.git/refs/tags/v1.0.0 b/pkg/fanal/artifact/repo/testdata/test.git/refs/tags/v1.0.0 new file mode 100644 index 000000000000..bff68b69688e --- /dev/null +++ b/pkg/fanal/artifact/repo/testdata/test.git/refs/tags/v1.0.0 @@ -0,0 +1 @@ +c906fc4a94762f8a2c77c718947143d16e4e9ec7 diff --git a/pkg/fanal/artifact/sbom/sbom.go b/pkg/fanal/artifact/sbom/sbom.go new file mode 100644 index 000000000000..2be0ce31e6a4 --- /dev/null +++ b/pkg/fanal/artifact/sbom/sbom.go @@ -0,0 +1,113 @@ +package sbom + +import ( + "context" + "crypto/sha256" + "encoding/json" + "os" + "path/filepath" + + "github.com/opencontainers/go-digest" + "github.com/samber/lo" + "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/log" + "github.com/aquasecurity/trivy/pkg/sbom" +) + +type Artifact struct { + filePath string + cache cache.ArtifactCache + analyzer analyzer.AnalyzerGroup + handlerManager handler.Manager + + artifactOption artifact.Option +} + +func NewArtifact(filePath string, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) { + return Artifact{ + filePath: filepath.Clean(filePath), + cache: c, + artifactOption: opt, + }, nil +} + +func (a Artifact) Inspect(_ context.Context) (types.ArtifactReference, error) { + f, err := os.Open(a.filePath) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("failed to open sbom file error: %w", err) + } + defer f.Close() + + // Format auto-detection + format, err := sbom.DetectFormat(f) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("failed to detect SBOM format: %w", err) + } + log.Logger.Infof("Detected SBOM format: %s", format) + + bom, err := sbom.Decode(f, format) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("SBOM decode error: %w", err) + } + + blobInfo := types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: lo.FromPtr(bom.Metadata.OS), + PackageInfos: bom.Packages, + Applications: bom.Applications, + } + + cacheKey, err := a.calcCacheKey(blobInfo) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("failed to calculate a cache key: %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) + } + + var artifactType types.ArtifactType + switch format { + case sbom.FormatCycloneDXJSON, sbom.FormatCycloneDXXML, sbom.FormatAttestCycloneDXJSON, sbom.FormatLegacyCosignAttestCycloneDXJSON: + artifactType = types.ArtifactCycloneDX + case sbom.FormatSPDXTV, sbom.FormatSPDXJSON: + artifactType = types.ArtifactSPDX + + } + + return types.ArtifactReference{ + Name: a.filePath, + Type: artifactType, + ID: cacheKey, // use a cache key as pseudo artifact ID + BlobIDs: []string{cacheKey}, + + // Keep an original report + BOM: bom.BOM, + }, nil +} + +func (a Artifact) Clean(reference types.ArtifactReference) error { + return a.cache.DeleteBlobs(reference.BlobIDs) +} + +func (a Artifact) 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 +} diff --git a/pkg/fanal/artifact/sbom/sbom_test.go b/pkg/fanal/artifact/sbom/sbom_test.go new file mode 100644 index 000000000000..3b26194cfa55 --- /dev/null +++ b/pkg/fanal/artifact/sbom/sbom_test.go @@ -0,0 +1,430 @@ +package sbom_test + +import ( + "context" + "errors" + "github.com/package-url/packageurl-go" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestArtifact_Inspect(t *testing.T) { + tests := []struct { + name string + filePath string + putBlobExpectation cache.ArtifactCachePutBlobExpectation + want types.ArtifactReference + wantErr []string + }{ + { + name: "happy path", + filePath: filepath.Join("testdata", "bom.json"), + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: types.OS{ + Family: "alpine", + Name: "3.16.0", + }, + PackageInfos: []types.PackageInfo{ + { + Packages: types.Packages{ + { + ID: "musl@1.2.3-r0", + Name: "musl", + Version: "1.2.3-r0", + SrcName: "musl", + SrcVersion: "1.2.3-r0", + Licenses: []string{"MIT"}, + Layer: types.Layer{ + DiffID: "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "musl", + Version: "1.2.3-r0", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.16.0", + }, + }, + }, + BOMRef: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + }, + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: "composer", + FilePath: "app/composer/composer.lock", + Libraries: types.Packages{ + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "log", + Version: "1.13.1", + }, + BOMRef: "pkg:composer/pear/log@1.13.1", + }, + }, + { + ID: "pear/pear_exception@v1.0.0", + Name: "pear/pear_exception", + Version: "v1.0.0", + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "pear_exception", + Version: "v1.0.0", + }, + BOMRef: "pkg:composer/pear/pear_exception@v1.0.0", + }, + }, + }, + }, + { + Type: "gobinary", + FilePath: "app/gobinary/gobinary", + Libraries: types.Packages{ + { + ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + Name: "github.com/package-url/packageurl-go", + Version: "v0.1.1-0.20220203205134-d70459300c8a", + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/package-url", + Name: "packageurl-go", + Version: "v0.1.1-0.20220203205134-d70459300c8a", + }, + BOMRef: "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + }, + }, + }, + }, + { + Type: "jar", + FilePath: "", + Libraries: types.Packages{ + { + ID: "org.codehaus.mojo:child-project:1.0", + Name: "org.codehaus.mojo:child-project", + Version: "1.0", + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + FilePath: "app/maven/target/child-project-1.0.jar", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.codehaus.mojo", + Name: "child-project", + Version: "1.0", + }, + // Keep the original value + BOMRef: "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", + }, + }, + }, + }, + { + Type: "node-pkg", + FilePath: "", + Libraries: types.Packages{ + { + ID: "bootstrap@5.0.2", + Name: "bootstrap", + Version: "5.0.2", + Licenses: []string{"MIT"}, + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + FilePath: "app/app/package.json", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "bootstrap", + Version: "5.0.2", + }, + // Keep the original value + BOMRef: "pkg:npm/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: filepath.Join("testdata", "bom.json"), + Type: types.ArtifactCycloneDX, + ID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + BlobIDs: []string{ + "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + }, + }, + }, + { + name: "happy path for sbom attestation", + filePath: filepath.Join("testdata", "sbom.cdx.intoto.jsonl"), + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: types.OS{ + Family: "alpine", + Name: "3.16.0", + }, + PackageInfos: []types.PackageInfo{ + { + Packages: types.Packages{ + { + ID: "musl@1.2.3-r0", + Name: "musl", + Version: "1.2.3-r0", + SrcName: "musl", + SrcVersion: "1.2.3-r0", + Licenses: []string{"MIT"}, + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "musl", + Version: "1.2.3-r0", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.16.0", + }, + }, + }, + BOMRef: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + }, + Layer: types.Layer{ + DiffID: "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: "composer", + FilePath: "app/composer/composer.lock", + Libraries: types.Packages{ + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "log", + Version: "1.13.1", + }, + BOMRef: "pkg:composer/pear/log@1.13.1", + }, + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + { + ID: "pear/pear_exception@v1.0.0", + Name: "pear/pear_exception", + Version: "v1.0.0", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "pear_exception", + Version: "v1.0.0", + }, + BOMRef: "pkg:composer/pear/pear_exception@v1.0.0", + }, + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + }, + }, + { + Type: "gobinary", + FilePath: "app/gobinary/gobinary", + Libraries: types.Packages{ + { + ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + Name: "github.com/package-url/packageurl-go", + Version: "v0.1.1-0.20220203205134-d70459300c8a", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/package-url", + Name: "packageurl-go", + Version: "v0.1.1-0.20220203205134-d70459300c8a", + }, + BOMRef: "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + }, + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + }, + }, + { + Type: "jar", + FilePath: "", + Libraries: types.Packages{ + { + ID: "org.codehaus.mojo:child-project:1.0", + Name: "org.codehaus.mojo:child-project", + Version: "1.0", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.codehaus.mojo", + Name: "child-project", + Version: "1.0", + }, + // Keep the original value + BOMRef: "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", + }, + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + FilePath: "app/maven/target/child-project-1.0.jar", + }, + }, + }, + { + Type: "node-pkg", + FilePath: "", + Libraries: types.Packages{ + { + ID: "bootstrap@5.0.2", + Name: "bootstrap", + Version: "5.0.2", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "bootstrap", + Version: "5.0.2", + }, + // Keep the original value + BOMRef: "pkg:npm/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", + }, + Licenses: []string{"MIT"}, + Layer: types.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + FilePath: "app/app/package.json", + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: filepath.Join("testdata", "sbom.cdx.intoto.jsonl"), + Type: types.ArtifactCycloneDX, + ID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + BlobIDs: []string{ + "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + }, + }, + }, + { + name: "sad path with no such directory", + filePath: filepath.Join("testdata", "unknown.json"), + wantErr: []string{ + "no such file or directory", + "The system cannot find the file specified", + }, + }, + { + name: "sad path PutBlob returns an error", + filePath: filepath.Join("testdata", "os-only-bom.json"), + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:911a6c875617315c51971dddf19fa2d47d6132cd14e9c6a87deb074afaf07818", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: types.OS{ + Family: "alpine", + Name: "3.16.0", + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{ + Err: errors.New("error"), + }, + }, + wantErr: []string{"failed to store blob"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + + a, err := sbom.NewArtifact(tt.filePath, c, artifact.Option{}) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + if len(tt.wantErr) > 0 { + require.NotNil(t, err) + found := false + for _, wantErr := range tt.wantErr { + if strings.Contains(err.Error(), wantErr) { + found = true + break + } + } + assert.True(t, found) + return + } + + // Not compare the original CycloneDX report + got.BOM = nil + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/artifact/sbom/testdata/bom.json b/pkg/fanal/artifact/sbom/testdata/bom.json new file mode 100644 index 000000000000..f8fd55ea6add --- /dev/null +++ b/pkg/fanal/artifact/sbom/testdata/bom.json @@ -0,0 +1,238 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "container", + "name": "maven-test-project", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "maven-test-project:latest" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + "type": "library", + "name": "musl", + "version": "1.2.3-r0", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + "properties": [ + { + "name": "aquasecurity:trivy:SrcName", + "value": "musl" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.2.3-r0" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + } + ] + }, + { + "bom-ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "type": "operating-system", + "name": "alpine", + "version": "3.16.0", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "alpine" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + } + ] + }, + { + "bom-ref": "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", + "type": "library", + "name": "org.codehaus.mojo:child-project", + "version": "1.0", + "purl": "pkg:maven/org.codehaus.mojo/child-project@1.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "app/maven/target/child-project-1.0.jar" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "jar" + } + ] + }, + { + "bom-ref": "pkg:npm/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", + "type": "library", + "name": "bootstrap", + "version": "5.0.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:npm/bootstrap@5.0.2", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "app/app/package.json" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "node-pkg" + } + ] + }, + { + "bom-ref": "pkg:composer/pear/log@1.13.1", + "type": "library", + "name": "pear/log", + "version": "1.13.1", + "purl": "pkg:composer/pear/log@1.13.1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } + ] + }, + { + "bom-ref": "pkg:composer/pear/pear_exception@v1.0.0", + "type": "library", + "name": "pear/pear_exception", + "version": "v1.0.0", + "purl": "pkg:composer/pear/pear_exception@v1.0.0", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } + ] + }, + { + "bom-ref": "100925ff-7c0a-470f-a725-8fb973b40e7b", + "type": "application", + "name": "app/composer/composer.lock", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "composer" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + } + ] + }, + { + "bom-ref": "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + "type": "library", + "name": "github.com/package-url/packageurl-go", + "version": "v0.1.1-0.20220203205134-d70459300c8a", + "purl": "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } + ] + }, + { + "bom-ref": "1a111e6b-a682-470e-8b0e-aaa49d93cd39", + "type": "application", + "name": "app/gobinary/gobinary", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "gobinary" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + } + ] + } + ], + "dependencies": [ + { + "ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "dependsOn": [ + "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0" + ] + }, + { + "ref": "100925ff-7c0a-470f-a725-8fb973b40e7b", + "dependsOn": [ + "pkg:composer/pear/log@1.13.1", + "pkg:composer/pear/pear_exception@v1.0.0" + ] + }, + { + "ref": "1a111e6b-a682-470e-8b0e-aaa49d93cd39", + "dependsOn": [ + "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a" + ] + }, + { + "ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "dependsOn": [ + "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", + "pkg:npm/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", + "100925ff-7c0a-470f-a725-8fb973b40e7b", + "1a111e6b-a682-470e-8b0e-aaa49d93cd39" + ] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/fanal/artifact/sbom/testdata/os-only-bom.json b/pkg/fanal/artifact/sbom/testdata/os-only-bom.json new file mode 100644 index 000000000000..837c16754211 --- /dev/null +++ b/pkg/fanal/artifact/sbom/testdata/os-only-bom.json @@ -0,0 +1,77 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "container", + "name": "maven-test-project", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "maven-test-project:latest" + } + ] + } + }, + "components": [ + { + "bom-ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "type": "operating-system", + "name": "alpine", + "version": "3.16.0", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "alpine" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + } + ] + } + ], + "dependencies": [ + { + "ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "dependsOn": [] + }, + { + "ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "dependsOn": [ + "60e9f57b-d4a6-4f71-ad14-0893ac609182" + ] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/fanal/artifact/sbom/testdata/sbom.cdx.intoto.jsonl b/pkg/fanal/artifact/sbom/testdata/sbom.cdx.intoto.jsonl new file mode 100644 index 000000000000..3f2b64b667a3 --- /dev/null +++ b/pkg/fanal/artifact/sbom/testdata/sbom.cdx.intoto.jsonl @@ -0,0 +1,10 @@ +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2N5Y2xvbmVkeC5vcmcvYm9tIiwic3ViamVjdCI6W3sibmFtZSI6ImluZGV4LmRvY2tlci5pby9vdG1zNjEvaGVsbG8tMSIsImRpZ2VzdCI6eyJzaGEyNTYiOiIyMGQzZjY5M2RjZmZhNDRkNmIyNGVhZTg4NzgzMzI0ZDI1Y2MxMzJjMjIwODlmNzBlNGZiZmI4NTg2MjViMDYyIn19XSwicHJlZGljYXRlIjp7IkRhdGEiOnsiYm9tRm9ybWF0IjoiQ3ljbG9uZURYIiwiY29tcG9uZW50cyI6W3siYm9tLXJlZiI6InBrZzphcGsvYWxwaW5lL211c2xAMS4yLjMtcjA/ZGlzdHJvPTMuMTYuMCIsImxpY2Vuc2VzIjpbeyJleHByZXNzaW9uIjoiTUlUIn1dLCJuYW1lIjoibXVzbCIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjTmFtZSIsInZhbHVlIjoibXVzbCJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNWZXJzaW9uIiwidmFsdWUiOiIxLjIuMy1yMCJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpMYXllckRpZmZJRCIsInZhbHVlIjoic2hhMjU2OmRkNTY1ZmY4NTBlNzAwMzM1NmUyYjI1Mjc1OGY5YmRjMWZmMjgwM2Y2MWU5OTVlMjRjNzg0NGY2Mjk3ZjhmYzMifV0sInB1cmwiOiJwa2c6YXBrL2FscGluZS9tdXNsQDEuMi4zLXIwP2Rpc3Rybz0zLjE2LjAiLCJ0eXBlIjoibGlicmFyeSIsInZlcnNpb24iOiIxLjIuMy1yMCJ9LHsiYm9tLXJlZiI6IjYwZTlmNTdiLWQ0YTYtNGY3MS1hZDE0LTA4OTNhYzYwOTE4MiIsIm5hbWUiOiJhbHBpbmUiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlR5cGUiLCJ2YWx1ZSI6ImFscGluZSJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpDbGFzcyIsInZhbHVlIjoib3MtcGtncyJ9XSwidHlwZSI6Im9wZXJhdGluZy1zeXN0ZW0iLCJ2ZXJzaW9uIjoiMy4xNi4wIn0seyJib20tcmVmIjoicGtnOm1hdmVuL29yZy5jb2RlaGF1cy5tb2pvL2NoaWxkLXByb2plY3RAMS4wP2ZpbGVfcGF0aD1hcHAlMkZtYXZlbiUyRnRhcmdldCUyRmNoaWxkLXByb2plY3QtMS4wLmphciIsIm5hbWUiOiJvcmcuY29kZWhhdXMubW9qbzpjaGlsZC1wcm9qZWN0IiwicHJvcGVydGllcyI6W3sibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpGaWxlUGF0aCIsInZhbHVlIjoiYXBwL21hdmVuL3RhcmdldC9jaGlsZC1wcm9qZWN0LTEuMC5qYXIifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWZmSUQiLCJ2YWx1ZSI6InNoYTI1NjozYzc5ZTgzMmIxYjQ4OTFhMWNiNGEzMjZlZjg1MjRlMGJkMTRhMjUzNzE1MGFjMGUyMDNhNTY3NzE3NmMxY2ExIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlR5cGUiLCJ2YWx1ZSI6ImphciJ9XSwicHVybCI6InBrZzptYXZlbi9vcmcuY29kZWhhdXMubW9qby9jaGlsZC1wcm9qZWN0QDEuMCIsInR5cGUiOiJsaWJyYXJ5IiwidmVyc2lvbiI6IjEuMCJ9LHsiYm9tLXJlZiI6InBrZzpucG0vYm9vdHN0cmFwQDUuMC4yP2ZpbGVfcGF0aD1hcHAlMkZhcHAlMkZwYWNrYWdlLmpzb24iLCJsaWNlbnNlcyI6W3siZXhwcmVzc2lvbiI6Ik1JVCJ9XSwibmFtZSI6ImJvb3RzdHJhcCIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6RmlsZVBhdGgiLCJ2YWx1ZSI6ImFwcC9hcHAvcGFja2FnZS5qc29uIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlmZklEIiwidmFsdWUiOiJzaGEyNTY6M2M3OWU4MzJiMWI0ODkxYTFjYjRhMzI2ZWY4NTI0ZTBiZDE0YTI1MzcxNTBhYzBlMjAzYTU2NzcxNzZjMWNhMSJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpUeXBlIiwidmFsdWUiOiJub2RlLXBrZyJ9XSwicHVybCI6InBrZzpucG0vYm9vdHN0cmFwQDUuMC4yIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiNS4wLjIifSx7ImJvbS1yZWYiOiJwa2c6Y29tcG9zZXIvcGVhci9sb2dAMS4xMy4xIiwibmFtZSI6InBlYXIvbG9nIiwicHJvcGVydGllcyI6W3sibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpMYXllckRpZmZJRCIsInZhbHVlIjoic2hhMjU2OjNjNzllODMyYjFiNDg5MWExY2I0YTMyNmVmODUyNGUwYmQxNGEyNTM3MTUwYWMwZTIwM2E1Njc3MTc2YzFjYTEifV0sInB1cmwiOiJwa2c6Y29tcG9zZXIvcGVhci9sb2dAMS4xMy4xIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiMS4xMy4xIn0seyJib20tcmVmIjoicGtnOmNvbXBvc2VyL3BlYXIvcGVhcl9leGNlcHRpb25AdjEuMC4wIiwibmFtZSI6InBlYXIvcGVhcl9leGNlcHRpb24iLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlmZklEIiwidmFsdWUiOiJzaGEyNTY6M2M3OWU4MzJiMWI0ODkxYTFjYjRhMzI2ZWY4NTI0ZTBiZDE0YTI1MzcxNTBhYzBlMjAzYTU2NzcxNzZjMWNhMSJ9XSwicHVybCI6InBrZzpjb21wb3Nlci9wZWFyL3BlYXJfZXhjZXB0aW9uQHYxLjAuMCIsInR5cGUiOiJsaWJyYXJ5IiwidmVyc2lvbiI6InYxLjAuMCJ9LHsiYm9tLXJlZiI6IjEwMDkyNWZmLTdjMGEtNDcwZi1hNzI1LThmYjk3M2I0MGU3YiIsIm5hbWUiOiJhcHAvY29tcG9zZXIvY29tcG9zZXIubG9jayIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6VHlwZSIsInZhbHVlIjoiY29tcG9zZXIifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6Q2xhc3MiLCJ2YWx1ZSI6ImxhbmctcGtncyJ9XSwidHlwZSI6ImFwcGxpY2F0aW9uIn0seyJib20tcmVmIjoicGtnOmdvbGFuZy9naXRodWIuY29tL3BhY2thZ2UtdXJsL3BhY2thZ2V1cmwtZ29AdjAuMS4xLTAuMjAyMjAyMDMyMDUxMzQtZDcwNDU5MzAwYzhhIiwibmFtZSI6ImdpdGh1Yi5jb20vcGFja2FnZS11cmwvcGFja2FnZXVybC1nbyIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWZmSUQiLCJ2YWx1ZSI6InNoYTI1NjozYzc5ZTgzMmIxYjQ4OTFhMWNiNGEzMjZlZjg1MjRlMGJkMTRhMjUzNzE1MGFjMGUyMDNhNTY3NzE3NmMxY2ExIn1dLCJwdXJsIjoicGtnOmdvbGFuZy9naXRodWIuY29tL3BhY2thZ2UtdXJsL3BhY2thZ2V1cmwtZ29AdjAuMS4xLTAuMjAyMjAyMDMyMDUxMzQtZDcwNDU5MzAwYzhhIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoidjAuMS4xLTAuMjAyMjAyMDMyMDUxMzQtZDcwNDU5MzAwYzhhIn0seyJib20tcmVmIjoiMWExMTFlNmItYTY4Mi00NzBlLThiMGUtYWFhNDlkOTNjZDM5IiwibmFtZSI6ImFwcC9nb2JpbmFyeS9nb2JpbmFyeSIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6VHlwZSIsInZhbHVlIjoiZ29iaW5hcnkifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6Q2xhc3MiLCJ2YWx1ZSI6ImxhbmctcGtncyJ9XSwidHlwZSI6ImFwcGxpY2F0aW9uIn1dLCJkZXBlbmRlbmNpZXMiOlt7ImRlcGVuZHNPbiI6WyJwa2c6YXBrL2FscGluZS9tdXNsQDEuMi4zLXIwP2Rpc3Rybz0zLjE2LjAiXSwicmVmIjoiNjBlOWY1N2ItZDRhNi00ZjcxLWFkMTQtMDg5M2FjNjA5MTgyIn0seyJkZXBlbmRzT24iOlsicGtnOmNvbXBvc2VyL3BlYXIvbG9nQDEuMTMuMSIsInBrZzpjb21wb3Nlci9wZWFyL3BlYXJfZXhjZXB0aW9uQHYxLjAuMCJdLCJyZWYiOiIxMDA5MjVmZi03YzBhLTQ3MGYtYTcyNS04ZmI5NzNiNDBlN2IifSx7ImRlcGVuZHNPbiI6WyJwa2c6Z29sYW5nL2dpdGh1Yi5jb20vcGFja2FnZS11cmwvcGFja2FnZXVybC1nb0B2MC4xLjEtMC4yMDIyMDIwMzIwNTEzNC1kNzA0NTkzMDBjOGEiXSwicmVmIjoiMWExMTFlNmItYTY4Mi00NzBlLThiMGUtYWFhNDlkOTNjZDM5In0seyJkZXBlbmRzT24iOlsiNjBlOWY1N2ItZDRhNi00ZjcxLWFkMTQtMDg5M2FjNjA5MTgyIiwicGtnOm1hdmVuL29yZy5jb2RlaGF1cy5tb2pvL2NoaWxkLXByb2plY3RAMS4wP2ZpbGVfcGF0aD1hcHAlMkZtYXZlbiUyRnRhcmdldCUyRmNoaWxkLXByb2plY3QtMS4wLmphciIsInBrZzpucG0vYm9vdHN0cmFwQDUuMC4yP2ZpbGVfcGF0aD1hcHAlMkZhcHAlMkZwYWNrYWdlLmpzb24iLCIxMDA5MjVmZi03YzBhLTQ3MGYtYTcyNS04ZmI5NzNiNDBlN2IiLCIxYTExMWU2Yi1hNjgyLTQ3MGUtOGIwZS1hYWE0OWQ5M2NkMzkiXSwicmVmIjoiMGY1ODVkNjQtNDgxNS00YjcyLTkyYzUtOTdkYWUxOTFmYTRhIn1dLCJtZXRhZGF0YSI6eyJjb21wb25lbnQiOnsiYm9tLXJlZiI6IjBmNTg1ZDY0LTQ4MTUtNGI3Mi05MmM1LTk3ZGFlMTkxZmE0YSIsIm5hbWUiOiJtYXZlbi10ZXN0LXByb2plY3QiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNjaGVtYVZlcnNpb24iLCJ2YWx1ZSI6IjIifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6SW1hZ2VJRCIsInZhbHVlIjoic2hhMjU2OjQ5MTkzYTIzMTBkYmFkNGMwMjM4MmRhODdhYzYyNGE4MGE5MjM4N2E0Zjc1MzYyMzVmOWJhNTkwZTViY2Q3YjUifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6RGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ZGQ1NjVmZjg1MGU3MDAzMzU2ZTJiMjUyNzU4ZjliZGMxZmYyODAzZjYxZTk5NWUyNGM3ODQ0ZjYyOTdmOGZjMyJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpEaWZmSUQiLCJ2YWx1ZSI6InNoYTI1NjozYzc5ZTgzMmIxYjQ4OTFhMWNiNGEzMjZlZjg1MjRlMGJkMTRhMjUzNzE1MGFjMGUyMDNhNTY3NzE3NmMxY2ExIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlJlcG9UYWciLCJ2YWx1ZSI6Im1hdmVuLXRlc3QtcHJvamVjdDpsYXRlc3QifV0sInR5cGUiOiJjb250YWluZXIifSwidGltZXN0YW1wIjoiMjAyMi0wNS0yOFQxMDoyMDowMy43OTUyN1oiLCJ0b29scyI6W3sibmFtZSI6InRyaXZ5IiwidmVuZG9yIjoiYXF1YXNlY3VyaXR5IiwidmVyc2lvbiI6ImRldiJ9XX0sInNlcmlhbE51bWJlciI6InVybjp1dWlkOmM5ODZiYTk0LWUzN2QtNDljOC05ZTMwLTk2ZGFjY2QwNDE1YiIsInNwZWNWZXJzaW9uIjoiMS40IiwidmVyc2lvbiI6MSwidnVsbmVyYWJpbGl0aWVzIjpbXX0sIlRpbWVzdGFtcCI6IiJ9fQ==", + "signatures": [ + { + "keyid": "", + "sig": "MEUCIQD57kCtdvc8gCX9CU3yMO/areJwRf7U52SatgsY5+Z+uAIgU3EQv/qAC+oj2bp3HusTrOAMjMN9RajsuFmifbahZeo=" + } + ] +} diff --git a/pkg/fanal/artifact/vm/ami.go b/pkg/fanal/artifact/vm/ami.go new file mode 100644 index 000000000000..6ce2dbd4ef30 --- /dev/null +++ b/pkg/fanal/artifact/vm/ami.go @@ -0,0 +1,65 @@ +package vm + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/cloud/aws/config" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +type AMI struct { + *EBS + + imageID string +} + +func newAMI(imageID string, storage Storage, region, endpoint string) (*AMI, error) { + // TODO: propagate context + ctx := context.TODO() + cfg, err := config.LoadDefaultAWSConfig(ctx, region, endpoint) + if err != nil { + return nil, 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, region, endpoint) + 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 000000000000..879d5e9b424c --- /dev/null +++ b/pkg/fanal/artifact/vm/ebs.go @@ -0,0 +1,121 @@ +package vm + +import ( + "context" + "io" + + lru "github.com/hashicorp/golang-lru/v2" + ebsfile "github.com/masahiro331/go-ebs-file" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/cloud/aws/config" + "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, region, endpoint string) (*EBS, error) { + ebs, err := ebsfile.New(context.TODO(), config.MakeAWSOptions(region, endpoint)...) + 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[string, []byte](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 000000000000..cecddf57e472 --- /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/v2" + "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[string, []byte](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/alpine/etc/alpine-release b/pkg/fanal/artifact/vm/testdata/alpine/etc/alpine-release new file mode 100644 index 000000000000..0f84b16be2f5 --- /dev/null +++ b/pkg/fanal/artifact/vm/testdata/alpine/etc/alpine-release @@ -0,0 +1 @@ +3.17.5 \ No newline at end of file diff --git a/pkg/fanal/artifact/vm/testdata/alpine/lib/apk/db/installed b/pkg/fanal/artifact/vm/testdata/alpine/lib/apk/db/installed new file mode 100644 index 000000000000..9b91634fd159 --- /dev/null +++ b/pkg/fanal/artifact/vm/testdata/alpine/lib/apk/db/installed @@ -0,0 +1,21 @@ +C:Q1dCsKJvMnxtpg1CoCw+thiaWORo8= +P:musl +V:1.2.3-r5 +A:aarch64 +S:397125 +I:675840 +T:the musl c library (libc) implementation +U:https://musl.libc.org/ +L:MIT +o:musl +m:Timo Teräs +t:1684510151 +c:b12380f8608f8cdd44347db413e8937ac4a5565b +p:so:libc.musl-aarch64.so.1=1 +F:lib +R:ld-musl-aarch64.so.1 +a:0:0:755 +Z:Q17HWoHxeSxkUYleHiHuyks+G3edE= +R:libc.musl-aarch64.so.1 +a:0:0:777 +Z:Q14RpiCEfZIqcg1XDcVqp8QEpc9ks= \ No newline at end of file diff --git a/pkg/fanal/artifact/vm/testdata/mock.img b/pkg/fanal/artifact/vm/testdata/mock.img new file mode 100644 index 000000000000..23ee03b20946 --- /dev/null +++ b/pkg/fanal/artifact/vm/testdata/mock.img @@ -0,0 +1 @@ +mock \ No newline at end of file diff --git a/pkg/fanal/artifact/vm/testdata/monolithicSparse.vmdk b/pkg/fanal/artifact/vm/testdata/monolithicSparse.vmdk new file mode 100644 index 000000000000..b64f95dbdedf Binary files /dev/null and b/pkg/fanal/artifact/vm/testdata/monolithicSparse.vmdk differ diff --git a/pkg/fanal/artifact/vm/vm.go b/pkg/fanal/artifact/vm/vm.go new file mode 100644 index 000000000000..5f0d1f5622cc --- /dev/null +++ b/pkg/fanal/artifact/vm/vm.go @@ -0,0 +1,169 @@ +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 Walker interface { + Walk(*io.SectionReader, string, walker.WalkFunc) error +} + +func NewArtifact(target string, c cache.ArtifactCache, w Walker, 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(opt.AnalyzerOptions()) + if err != nil { + return nil, xerrors.Errorf("analyzer group error: %w", err) + } + + storage := Storage{ + cache: c, + analyzer: a, + handlerManager: handlerManager, + walker: w, + artifactOption: opt, + } + + targetType := detectType(target) + switch targetType { + case TypeAMI: + target = strings.TrimPrefix(target, TypeAMI.Prefix()) + return newAMI(target, storage, opt.AWSRegion, opt.AWSEndpoint) + case TypeEBS: + target = strings.TrimPrefix(target, TypeEBS.Prefix()) + e, err := newEBS(target, storage, opt.AWSRegion, opt.AWSEndpoint) + 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") +} + +type Storage struct { + cache cache.ArtifactCache + analyzer analyzer.AnalyzerGroup + handlerManager handler.Manager + walker Walker + + 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.Parallel) + result := analyzer.NewAnalysisResult() + + opts := analyzer.AnalysisOptions{ + Offline: a.artifactOption.Offline, + FileChecksum: a.artifactOption.FileChecksum, + } + + // Prepare filesystem for post analysis + composite, err := a.analyzer.PostAnalyzerFS() + if err != nil { + return types.BlobInfo{}, xerrors.Errorf("unable to get post analysis filesystem: %w", err) + } + defer composite.Cleanup() + + // 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 { + 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) + } + + // Skip post analysis if the file is not required + analyzerTypes := a.analyzer.RequiredPostAnalyzers(path, info) + if len(analyzerTypes) == 0 { + return nil + } + + // Build filesystem for post analysis + tmpFilePath, err := composite.CopyFileToTemp(opener, info) + if err != nil { + return xerrors.Errorf("failed to copy file to temp: %w", err) + } + + if err = composite.CreateLink(analyzerTypes, "", path, tmpFilePath); err != nil { + return xerrors.Errorf("failed to write a file: %w", 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) + } + + // Post-analysis + if err = a.analyzer.PostAnalyze(ctx, composite, result, opts); err != nil { + return types.BlobInfo{}, xerrors.Errorf("post analysis 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 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 000000000000..2c47c756e6e5 --- /dev/null +++ b/pkg/fanal/artifact/vm/vm_test.go @@ -0,0 +1,252 @@ +package vm_test + +import ( + "context" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + "testing" + + ebsfile "github.com/masahiro331/go-ebs-file" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + xio "github.com/aquasecurity/trivy/pkg/x/io" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "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" + "github.com/aquasecurity/trivy/pkg/fanal/walker" + "github.com/aquasecurity/trivy/pkg/misconf" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk" +) + +const ( + ebsPrefix = string(vm.TypeEBS) + ":" + filePrefix = string(vm.TypeFile) + ":" +) + +type mockWalker struct { + root string +} + +func (m *mockWalker) Walk(_ *io.SectionReader, _ string, fn walker.WalkFunc) error { + return filepath.WalkDir(m.root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } else if d.IsDir() { + return nil + } + + info, err := d.Info() + if err != nil { + return err + } + opener := func() (xio.ReadSeekCloserAt, error) { + return os.Open(path) + } + relPath, err := filepath.Rel(m.root, path) + if err != nil { + return err + } + relPath = filepath.ToSlash(relPath) + return fn(relPath, info, opener) + }) +} + +func TestNewArtifact(t *testing.T) { + tests := []struct { + name string + target string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "happy path for file", + target: "testdata/mock.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, "file open error") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &mockWalker{root: "testdata"} + _, err := vm.NewArtifact(tt.target, nil, w, artifact.Option{}) + tt.wantErr(t, err, fmt.Sprintf("NewArtifact(%v, nil, nil)", tt.target)) + }) + } +} + +func TestArtifact_Inspect(t *testing.T) { + tests := []struct { + name string + target string + rootDir string + artifactOpt artifact.Option + scannerOpt misconf.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", + target: "testdata/mock.img", + rootDir: "testdata/alpine", + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:84a726d23c36d0e1857101969b257c1199de5432489d44581750d54ea8eff8cd", + BlobInfo: expectedBlobInfo, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + putArtifactExpectations: []cache.ArtifactCachePutArtifactExpectation{ + { + Args: cache.ArtifactCachePutArtifactArgs{ + ArtifactID: "sha256:84a726d23c36d0e1857101969b257c1199de5432489d44581750d54ea8eff8cd", + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: types.ArtifactJSONSchemaVersion, + }, + }, + }, + }, + want: types.ArtifactReference{ + Name: "rawdata.img", + Type: types.ArtifactVM, + ID: "sha256:84a726d23c36d0e1857101969b257c1199de5432489d44581750d54ea8eff8cd", + BlobIDs: []string{ + "sha256:84a726d23c36d0e1857101969b257c1199de5432489d44581750d54ea8eff8cd", + }, + }, + }, + { + name: "happy path for ebs", + target: "ebs:ebs-012345", + rootDir: "testdata/alpine", + missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ + Args: cache.ArtifactCacheMissingBlobsArgs{ + ArtifactID: "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", + BlobIDs: []string{"sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", + BlobInfo: expectedBlobInfo, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + putArtifactExpectations: []cache.ArtifactCachePutArtifactExpectation{ + { + Args: cache.ArtifactCachePutArtifactArgs{ + ArtifactID: "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: types.ArtifactJSONSchemaVersion, + }, + }, + }, + }, + want: types.ArtifactReference{ + Name: "ebs-012345", + Type: types.ArtifactVM, + ID: "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", + BlobIDs: []string{ + "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", + }, + }, + }, + } + 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) + c.ApplyDeleteBlobsExpectation(cache.ArtifactCacheDeleteBlobsExpectation{ + Args: cache.ArtifactCacheDeleteBlobsArgs{BlobIDsAnything: true}, + }) + + m := &mockWalker{root: tt.rootDir} + + a, err := vm.NewArtifact(tt.target, c, m, tt.artifactOpt) + require.NoError(t, err) + + if aa, ok := a.(*vm.EBS); ok { + ebs := ebsfile.NewMockEBS("testdata/mock.img", 1, 2) + aa.SetEBS(ebs) + } + + got, err := a.Inspect(context.Background()) + defer a.Clean(got) + if tt.wantErr != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.wantErr) + return + } + tt.want.Name = trimPrefix(tt.target) + 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 +} + +var expectedBlobInfo = types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: types.OS{ + Family: "alpine", + Name: "3.17.5", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: types.Packages{ + { + ID: "musl@1.2.3-r5", + Name: "musl", + Version: "1.2.3-r5", + SrcName: "musl", + SrcVersion: "1.2.3-r5", + Licenses: []string{"MIT"}, + Arch: "aarch64", + Digest: "sha1:742b0a26f327c6da60d42a02c3eb6189a58e468f", + InstalledFiles: []string{ + "lib/ld-musl-aarch64.so.1", + "lib/libc.musl-aarch64.so.1", + }, + }, + }, + }, + }, +} diff --git a/pkg/fanal/cache/cache.go b/pkg/fanal/cache/cache.go new file mode 100644 index 000000000000..b2f5fa704ae7 --- /dev/null +++ b/pkg/fanal/cache/cache.go @@ -0,0 +1,49 @@ +package cache + +import ( + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +const ( + cacheDirName = "fanal" + + // artifactBucket stores artifact information with artifact ID such as image ID + artifactBucket = "artifact" + // blobBucket stores os, package and library information per blob ID such as layer ID + blobBucket = "blob" +) + +type Cache interface { + ArtifactCache + LocalArtifactCache +} + +// ArtifactCache uses local or remote cache +type ArtifactCache interface { + // MissingBlobs returns missing blob IDs such as layer IDs in cache + MissingBlobs(artifactID string, blobIDs []string) (missingArtifact bool, missingBlobIDs []string, err error) + + // PutArtifact stores artifact information such as image metadata in cache + PutArtifact(artifactID string, artifactInfo types.ArtifactInfo) (err error) + + // PutBlob stores blob information such as layer information in local cache + PutBlob(blobID string, blobInfo types.BlobInfo) (err error) + + // DeleteBlobs removes blobs by IDs + DeleteBlobs(blobIDs []string) error +} + +// LocalArtifactCache always uses local cache +type LocalArtifactCache interface { + // GetArtifact gets artifact information such as image metadata from local cache + GetArtifact(artifactID string) (artifactInfo types.ArtifactInfo, err error) + + // GetBlob gets blob information such as layer data from local cache + GetBlob(blobID string) (blobInfo types.BlobInfo, err error) + + // Close closes the local database + Close() (err error) + + // Clear deletes the local database + Clear() (err error) +} diff --git a/pkg/fanal/cache/fs.go b/pkg/fanal/cache/fs.go new file mode 100644 index 000000000000..08fa696e6555 --- /dev/null +++ b/pkg/fanal/cache/fs.go @@ -0,0 +1,209 @@ +package cache + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/hashicorp/go-multierror" + bolt "go.etcd.io/bbolt" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var _ Cache = &FSCache{} + +type FSCache struct { + db *bolt.DB + directory string +} + +func NewFSCache(cacheDir string) (FSCache, error) { + dir := filepath.Join(cacheDir, cacheDirName) + if err := os.MkdirAll(dir, 0700); err != nil { + return FSCache{}, xerrors.Errorf("failed to create cache dir: %w", err) + } + + db, err := bolt.Open(filepath.Join(dir, "fanal.db"), 0600, nil) + if err != nil { + return FSCache{}, xerrors.Errorf("unable to open DB: %w", err) + } + + err = db.Update(func(tx *bolt.Tx) error { + for _, bucket := range []string{artifactBucket, blobBucket} { + if _, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil { + return xerrors.Errorf("unable to create %s bucket: %w", bucket, err) + } + } + return nil + }) + if err != nil { + return FSCache{}, xerrors.Errorf("DB error: %w", err) + } + + return FSCache{ + db: db, + directory: dir, + }, nil +} + +// GetBlob gets blob information such as layer data from local cache +func (fs FSCache) GetBlob(blobID string) (types.BlobInfo, error) { + var blobInfo types.BlobInfo + err := fs.db.View(func(tx *bolt.Tx) error { + var err error + blobBucket := tx.Bucket([]byte(blobBucket)) + blobInfo, err = fs.getBlob(blobBucket, blobID) + if err != nil { + return xerrors.Errorf("failed to get blob from the cache: %w", err) + } + return nil + }) + if err != nil { + return types.BlobInfo{}, xerrors.Errorf("DB error: %w", err) + } + return blobInfo, nil +} + +func (fs FSCache) getBlob(blobBucket *bolt.Bucket, diffID string) (types.BlobInfo, error) { + b := blobBucket.Get([]byte(diffID)) + + var l types.BlobInfo + if err := json.Unmarshal(b, &l); err != nil { + return types.BlobInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err) + } + return l, nil +} + +// PutBlob stores blob information such as layer information in local cache +func (fs FSCache) PutBlob(blobID string, blobInfo types.BlobInfo) error { + b, err := json.Marshal(blobInfo) + if err != nil { + return xerrors.Errorf("unable to marshal blob JSON (%s): %w", blobID, err) + } + err = fs.db.Update(func(tx *bolt.Tx) error { + blobBucket := tx.Bucket([]byte(blobBucket)) + err = blobBucket.Put([]byte(blobID), b) + if err != nil { + return xerrors.Errorf("unable to store blob information in cache (%s): %w", blobID, err) + } + return nil + }) + if err != nil { + return xerrors.Errorf("DB update error: %w", err) + } + return nil +} + +// GetArtifact gets artifact information such as image metadata from local cache +func (fs FSCache) GetArtifact(artifactID string) (types.ArtifactInfo, error) { + var blob []byte + err := fs.db.View(func(tx *bolt.Tx) error { + artifactBucket := tx.Bucket([]byte(artifactBucket)) + blob = artifactBucket.Get([]byte(artifactID)) + return nil + }) + if err != nil { + return types.ArtifactInfo{}, xerrors.Errorf("DB error: %w", err) + } + + var info types.ArtifactInfo + if err := json.Unmarshal(blob, &info); err != nil { + return types.ArtifactInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err) + } + return info, nil +} + +// DeleteBlobs removes blobs by IDs +func (fs FSCache) DeleteBlobs(blobIDs []string) error { + var errs error + err := fs.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(blobBucket)) + for _, blobID := range blobIDs { + if err := bucket.Delete([]byte(blobID)); err != nil { + errs = multierror.Append(errs, err) + } + } + return nil + }) + if err != nil { + return xerrors.Errorf("DB delete error: %w", err) + } + return errs +} + +// PutArtifact stores artifact information such as image metadata in local cache +func (fs FSCache) PutArtifact(artifactID string, artifactInfo types.ArtifactInfo) (err error) { + b, err := json.Marshal(artifactInfo) + if err != nil { + return xerrors.Errorf("unable to marshal artifact JSON (%s): %w", artifactID, err) + } + + err = fs.db.Update(func(tx *bolt.Tx) error { + artifactBucket := tx.Bucket([]byte(artifactBucket)) + err = artifactBucket.Put([]byte(artifactID), b) + if err != nil { + return xerrors.Errorf("unable to store artifact information in cache (%s): %w", artifactID, err) + } + return nil + }) + if err != nil { + return xerrors.Errorf("DB update error: %w", err) + } + return nil +} + +// MissingBlobs returns missing blob IDs such as layer IDs +func (fs FSCache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) { + var missingArtifact bool + var missingBlobIDs []string + err := fs.db.View(func(tx *bolt.Tx) error { + blobBucket := tx.Bucket([]byte(blobBucket)) + for _, blobID := range blobIDs { + blobInfo, err := fs.getBlob(blobBucket, blobID) + if err != nil { + // error means cache missed blob info + missingBlobIDs = append(missingBlobIDs, blobID) + continue + } + if blobInfo.SchemaVersion != types.BlobJSONSchemaVersion { + missingBlobIDs = append(missingBlobIDs, blobID) + } + } + return nil + }) + if err != nil { + return false, nil, xerrors.Errorf("DB error: %w", err) + } + + // get artifact info + artifactInfo, err := fs.GetArtifact(artifactID) + if err != nil { + // error means cache missed artifact info + return true, missingBlobIDs, nil + } + if artifactInfo.SchemaVersion != types.ArtifactJSONSchemaVersion { + missingArtifact = true + } + return missingArtifact, missingBlobIDs, nil +} + +// Close closes the database +func (fs FSCache) Close() error { + if err := fs.db.Close(); err != nil { + return xerrors.Errorf("unable to close DB: %w", err) + } + return nil +} + +// Clear removes the database +func (fs FSCache) Clear() error { + if err := fs.Close(); err != nil { + return err + } + if err := os.RemoveAll(fs.directory); err != nil { + return xerrors.Errorf("failed to remove cache: %w", err) + } + return nil +} diff --git a/pkg/fanal/cache/fs_test.go b/pkg/fanal/cache/fs_test.go new file mode 100644 index 000000000000..eaa050096d24 --- /dev/null +++ b/pkg/fanal/cache/fs_test.go @@ -0,0 +1,497 @@ +package cache + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + bolt "go.etcd.io/bbolt" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func newTempDB(t *testing.T, dbPath string) (string, error) { + dir := t.TempDir() + if dbPath != "" { + d := filepath.Join(dir, "fanal") + if err := os.MkdirAll(d, 0700); err != nil { + return "", err + } + + dst := filepath.Join(d, "fanal.db") + if _, err := copyFile(dbPath, dst); err != nil { + return "", err + } + } + + return dir, nil +} + +func copyFile(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + defer destination.Close() + n, err := io.Copy(destination, source) + return n, err +} + +func TestFSCache_GetBlob(t *testing.T) { + type args struct { + layerID string + } + tests := []struct { + name string + dbPath string + args args + want types.BlobInfo + wantErr bool + }{ + { + name: "happy path", + dbPath: "testdata/fanal.db", + args: args{ + layerID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7/11101", + }, + want: types.BlobInfo{ + SchemaVersion: 2, + OS: types.OS{ + Family: "alpine", + Name: "3.10", + }, + }, + }, + { + name: "sad path", + dbPath: "testdata/fanal.db", + args: args{ + layerID: "sha256:unknown", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir, err := newTempDB(t, tt.dbPath) + require.NoError(t, err) + + fs, err := NewFSCache(tmpDir) + require.NoError(t, err) + defer func() { + _ = fs.Clear() + _ = fs.Close() + }() + + got, err := fs.GetBlob(tt.args.layerID) + assert.Equal(t, tt.wantErr, err != nil, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestFSCache_PutBlob(t *testing.T) { + type fields struct { + db *bolt.DB + directory string + } + type args struct { + diffID string + layerInfo types.BlobInfo + } + tests := []struct { + name string + fields fields + args args + want string + wantLayerID string + wantErr string + }{ + { + name: "happy path", + args: args{ + diffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + layerInfo: types.BlobInfo{ + SchemaVersion: 1, + OS: types.OS{ + Family: "alpine", + Name: "3.10", + }, + Repository: &types.Repository{ + Family: "alpine", + Release: "3.10", + }, + }, + }, + want: ` + { + "SchemaVersion": 1, + "OS": { + "Family": "alpine", + "Name": "3.10" + }, + "Repository": { + "Family": "alpine", + "Release": "3.10" + } + }`, + wantLayerID: "", + }, + { + name: "happy path: different decompressed layer ID", + args: args{ + diffID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + layerInfo: types.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:dab15cac9ebd43beceeeda3ce95c574d6714ed3d3969071caead678c065813ec", + OS: types.OS{ + Family: "alpine", + Name: "3.10", + }, + Repository: &types.Repository{ + Family: "alpine", + Release: "3.10", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: types.Packages{ + { + Name: "musl", + Version: "1.1.22-r3", + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: "composer", + FilePath: "php-app/composer.lock", + Libraries: types.Packages{ + { + Name: "guzzlehttp/guzzle", + Version: "6.2.0", + }, + { + Name: "guzzlehttp/promises", + Version: "v1.3.1", + }, + }, + }, + }, + OpaqueDirs: []string{"php-app/"}, + WhiteoutFiles: []string{"etc/foobar"}, + }, + }, + want: ` + { + "SchemaVersion": 1, + "Digest": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + "DiffID": "sha256:dab15cac9ebd43beceeeda3ce95c574d6714ed3d3969071caead678c065813ec", + "OS": { + "Family": "alpine", + "Name": "3.10" + }, + "Repository": { + "Family": "alpine", + "Release": "3.10" + }, + "PackageInfos": [ + { + "FilePath": "lib/apk/db/installed", + "Packages": [ + { + "Name": "musl", + "Version": "1.1.22-r3", + "Identifier": {}, + "Layer": {} + } + ] + } + ], + "Applications": [ + { + "Type": "composer", + "FilePath": "php-app/composer.lock", + "Libraries": [ + { + "Name":"guzzlehttp/guzzle", + "Version":"6.2.0", + "Identifier": {}, + "Layer": {} + }, + { + "Name":"guzzlehttp/promises", + "Version":"v1.3.1", + "Identifier": {}, + "Layer": {} + } + ] + } + ], + "OpaqueDirs": [ + "php-app/" + ], + "WhiteoutFiles": [ + "etc/foobar" + ] + }`, + wantLayerID: "sha256:dab15cac9ebd43beceeeda3ce95c574d6714ed3d3969071caead678c065813ec", + }, + { + name: "sad path closed DB", + args: args{ + diffID: "sha256:dab15cac9ebd43beceeeda3ce95c574d6714ed3d3969071caead678c065813ec/11111", + }, + wantErr: "database not open", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir, err := newTempDB(t, "") + require.NoError(t, err) + + fs, err := NewFSCache(tmpDir) + require.NoError(t, err) + defer func() { + _ = fs.Clear() + _ = fs.Close() + }() + + if strings.HasPrefix(tt.name, "sad") { + require.NoError(t, fs.Close()) + } + + err = fs.PutBlob(tt.args.diffID, tt.args.layerInfo) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } else { + require.NoError(t, err, tt.name) + } + + fs.db.View(func(tx *bolt.Tx) error { + layerBucket := tx.Bucket([]byte(blobBucket)) + b := layerBucket.Get([]byte(tt.args.diffID)) + assert.JSONEq(t, tt.want, string(b)) + + return nil + }) + }) + } +} + +func TestFSCache_PutArtifact(t *testing.T) { + type args struct { + imageID string + imageConfig types.ArtifactInfo + } + tests := []struct { + name string + args args + want string + wantErr string + }{ + { + name: "happy path", + args: args{ + imageID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", + imageConfig: types.ArtifactInfo{ + SchemaVersion: 1, + Architecture: "amd64", + Created: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC), + DockerVersion: "18.06.1-ce", + OS: "linux", + HistoryPackages: types.Packages{ + { + Name: "musl", + Version: "1.2.3", + }, + }, + }, + }, + want: ` + { + "SchemaVersion": 1, + "Architecture": "amd64", + "Created": "2020-01-02T03:04:05Z", + "DockerVersion": "18.06.1-ce", + "OS": "linux", + "HistoryPackages": [ + { + "Name": "musl", + "Version": "1.2.3", + "Identifier": {}, + "Layer": {} + } + ] + } + `, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir, err := newTempDB(t, "") + require.NoError(t, err) + + fs, err := NewFSCache(tmpDir) + require.NoError(t, err) + defer func() { + _ = fs.Clear() + _ = fs.Close() + }() + + err = fs.PutArtifact(tt.args.imageID, tt.args.imageConfig) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } else { + require.NoError(t, err, tt.name) + } + + fs.db.View(func(tx *bolt.Tx) error { + // check decompressedDigestBucket + imageBucket := tx.Bucket([]byte(artifactBucket)) + b := imageBucket.Get([]byte(tt.args.imageID)) + assert.JSONEq(t, tt.want, string(b)) + + return nil + }) + }) + } +} + +func TestFSCache_MissingBlobs(t *testing.T) { + type args struct { + imageID string + layerIDs []string + } + tests := []struct { + name string + dbPath string + args args + wantMissingImage bool + wantMissingLayerIDs []string + wantErr string + }{ + { + name: "happy path", + dbPath: "testdata/fanal.db", + args: args{ + imageID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4/1", + layerIDs: []string{ + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7/11101", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5/11101", + "sha256:dab15cac9ebd43beceeeda3ce95c574d6714ed3d3969071caead678c065813ec/11101", + }, + }, + wantMissingImage: false, + wantMissingLayerIDs: []string{ + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5/11101", + "sha256:dab15cac9ebd43beceeeda3ce95c574d6714ed3d3969071caead678c065813ec/11101", + }, + }, + { + name: "happy path: broken layer JSON", + dbPath: "testdata/broken-layer.db", + args: args{ + imageID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", + layerIDs: []string{ + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + }, + wantMissingImage: true, + wantMissingLayerIDs: []string{ + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + }, + { + name: "happy path: broken image JSON", + dbPath: "testdata/broken-image.db", + args: args{ + imageID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", + layerIDs: []string{ + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + }, + wantMissingImage: true, + wantMissingLayerIDs: []string{ + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + }, + { + name: "happy path: the schema version of image JSON doesn't match", + dbPath: "testdata/different-image-schema.db", + args: args{ + imageID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", + layerIDs: []string{ + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + }, + wantMissingImage: true, + wantMissingLayerIDs: []string{ + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + }, + { + name: "happy path: new config analyzer", + dbPath: "testdata/fanal.db", + args: args{ + imageID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4/2", + layerIDs: []string{ + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7/11102", + }, + }, + wantMissingImage: true, + wantMissingLayerIDs: []string{ + "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7/11102", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir, err := newTempDB(t, tt.dbPath) + require.NoError(t, err) + + fs, err := NewFSCache(tmpDir) + require.NoError(t, err) + defer func() { + _ = fs.Clear() + _ = fs.Close() + }() + + gotMissingImage, gotMissingLayerIDs, err := fs.MissingBlobs(tt.args.imageID, tt.args.layerIDs) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr, tt.name) + return + } + require.NoError(t, err, tt.name) + assert.Equal(t, tt.wantMissingImage, gotMissingImage, tt.name) + assert.Equal(t, tt.wantMissingLayerIDs, gotMissingLayerIDs, tt.name) + }) + } +} diff --git a/pkg/fanal/cache/key.go b/pkg/fanal/cache/key.go new file mode 100644 index 000000000000..f9258caa866b --- /dev/null +++ b/pkg/fanal/cache/key.go @@ -0,0 +1,83 @@ +package cache + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + + "golang.org/x/mod/sumdb/dirhash" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" +) + +func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[string]int, artifactOpt artifact.Option) (string, error) { + // Sort options for consistent results + artifactOpt.Sort() + artifactOpt.MisconfScannerOption.Sort() + + h := sha256.New() + + // Write ID, analyzer/handler versions, skipped files/dirs and file patterns + keyBase := struct { + ID string + AnalyzerVersions analyzer.Versions + HookVersions map[string]int + SkipFiles []string + SkipDirs []string + FilePatterns []string `json:",omitempty"` + }{id, analyzerVersions, hookVersions, artifactOpt.SkipFiles, artifactOpt.SkipDirs, artifactOpt.FilePatterns} + + if err := json.NewEncoder(h).Encode(keyBase); err != nil { + return "", xerrors.Errorf("json encode error: %w", err) + } + + // Write policy, data contents and secret config file + paths := append(artifactOpt.MisconfScannerOption.PolicyPaths, artifactOpt.MisconfScannerOption.DataPaths...) + + // Check if the secret config exists. + if _, err := os.Stat(artifactOpt.SecretScannerOption.ConfigPath); err == nil { + paths = append(paths, artifactOpt.SecretScannerOption.ConfigPath) + } + + for _, p := range paths { + hash, err := hashContents(p) + if err != nil { + return "", err + } + + if _, err := h.Write([]byte(hash)); err != nil { + return "", xerrors.Errorf("sha256 write error: %w", err) + } + } + + return fmt.Sprintf("sha256:%x", h.Sum(nil)), nil +} + +func hashContents(path string) (string, error) { + fi, err := os.Stat(path) + if err != nil { + return "", xerrors.Errorf("file %q stat error: %w", path, err) + } + + var hash string + + if fi.IsDir() { + hash, err = dirhash.HashDir(path, "", dirhash.DefaultHash) + if err != nil { + return "", xerrors.Errorf("hash dir error (%s): %w", path, err) + } + } else { + hash, err = dirhash.DefaultHash([]string{filepath.Base(path)}, func(_ string) (io.ReadCloser, error) { + return os.Open(path) + }) + if err != nil { + return "", xerrors.Errorf("hash file error (%s): %w", path, err) + } + } + return hash, nil +} diff --git a/pkg/fanal/cache/key_test.go b/pkg/fanal/cache/key_test.go new file mode 100644 index 000000000000..94828e5d485e --- /dev/null +++ b/pkg/fanal/cache/key_test.go @@ -0,0 +1,256 @@ +package cache + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +func TestCalcKey(t *testing.T) { + type args struct { + key string + analyzerVersions analyzer.Versions + hookVersions map[string]int + skipFiles []string + skipDirs []string + patterns []string + policy []string + data []string + secretConfigPath string + } + tests := []struct { + name string + args args + want string + wantErr string + }{ + { + name: "happy path", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + hookVersions: map[string]int{ + "python-pkg": 1, + }, + }, + want: "sha256:c720b502991465ea11929cfefc71cf4b5aeaa9a8c0ae59fdaf597f957f5cdb18", + }, + { + name: "with disabled analyzer", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 0, + "redhat": 2, + }, + }, + hookVersions: map[string]int{ + "python-pkg": 1, + }, + }, + want: "sha256:d63724cc72729edd3c81205739d64fcb414a4e6345dd4dde7f0fe6bdd56bedf9", + }, + { + name: "with empty slice file patterns", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + patterns: []string{}, + }, + want: "sha256:9f7afa4d27c4c4f371dc6bb47bcc09e7a4a00b1d870e8156f126e35d8f6522e6", + }, + { + name: "with single empty string in file patterns", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + patterns: []string{""}, + }, + want: "sha256:bcfc5da13ef9bf0b85e719584800a010063474546f1051a781b78bd83de01102", + }, + { + name: "with single non empty string in file patterns", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + patterns: []string{"test"}, + }, + want: "sha256:8c9750b8eca507628417f21d7db707a7876d2e22c3e75b13f31a795af4051c57", + }, + { + name: "with non empty followed by empty string in file patterns", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + patterns: []string{"test", ""}, + }, + want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103", + }, + { + name: "with non empty preceded by empty string in file patterns", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + patterns: []string{"", "test"}, + }, + want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103", + }, + { + name: "with policy", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + policy: []string{"testdata/policy"}, + }, + want: "sha256:9602d5ef5af086112cc9fae8310390ed3fb79f4b309d8881b9807e379c8dfa57", + }, + { + name: "with policy file", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + policy: []string{"testdata/policy/test.rego"}, + }, + want: "sha256:9602d5ef5af086112cc9fae8310390ed3fb79f4b309d8881b9807e379c8dfa57", + }, + { + name: "skip files and dirs", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + skipFiles: []string{"app/deployment.yaml"}, + skipDirs: []string{"usr/java"}, + policy: []string{"testdata/policy"}, + }, + want: "sha256:363f70f4ee795f250873caea11c2fc94ef12945444327e7e2f8a99e3884695e0", + }, + { + + name: "secret config", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + hookVersions: map[string]int{ + "python-pkg": 1, + }, + secretConfigPath: "testdata/trivy-secret.yaml", + }, + want: "sha256:d3fb9503f2851ae9bdb250b7ef55c00c0bfa1250b19d4d398a9219c2c0e20958", + }, + { + + name: "secret config file doesn't exist", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + hookVersions: map[string]int{ + "python-pkg": 1, + }, + secretConfigPath: "trivy-secret.yaml", + }, + want: "sha256:c720b502991465ea11929cfefc71cf4b5aeaa9a8c0ae59fdaf597f957f5cdb18", + }, + { + name: "with policy/non-existent dir", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + policy: []string{"policydir"}, + }, + wantErr: "file \"policydir\" stat error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + artifactOpt := artifact.Option{ + SkipFiles: tt.args.skipFiles, + SkipDirs: tt.args.skipDirs, + FilePatterns: tt.args.patterns, + + MisconfScannerOption: misconf.ScannerOption{ + PolicyPaths: tt.args.policy, + DataPaths: tt.args.data, + }, + + SecretScannerOption: analyzer.SecretScannerOption{ + ConfigPath: tt.args.secretConfigPath, + }, + } + got, err := CalcKey(tt.args.key, tt.args.analyzerVersions, tt.args.hookVersions, artifactOpt) + if tt.wantErr != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/cache/mock_artifact_cache.go b/pkg/fanal/cache/mock_artifact_cache.go new file mode 100644 index 000000000000..e0b1430337e4 --- /dev/null +++ b/pkg/fanal/cache/mock_artifact_cache.go @@ -0,0 +1,247 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package cache + +import ( + mock "github.com/stretchr/testify/mock" + + types "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// MockArtifactCache is an autogenerated mock type for the ArtifactCache type +type MockArtifactCache struct { + mock.Mock +} + +type ArtifactCacheDeleteBlobsArgs struct { + BlobIDs []string + BlobIDsAnything bool +} + +type ArtifactCacheDeleteBlobsReturns struct { + _a0 error +} + +type ArtifactCacheDeleteBlobsExpectation struct { + Args ArtifactCacheDeleteBlobsArgs + Returns ArtifactCacheDeleteBlobsReturns +} + +func (_m *MockArtifactCache) ApplyDeleteBlobsExpectation(e ArtifactCacheDeleteBlobsExpectation) { + var args []interface{} + if e.Args.BlobIDsAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobIDs) + } + _m.On("DeleteBlobs", args...).Return(e.Returns._a0) +} + +func (_m *MockArtifactCache) ApplyDeleteBlobsExpectations(expectations []ArtifactCacheDeleteBlobsExpectation) { + for _, e := range expectations { + _m.ApplyDeleteBlobsExpectation(e) + } +} + +// DeleteBlobs provides a mock function with given fields: blobIDs +func (_m *MockArtifactCache) DeleteBlobs(blobIDs []string) error { + ret := _m.Called(blobIDs) + + var r0 error + if rf, ok := ret.Get(0).(func([]string) error); ok { + r0 = rf(blobIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type ArtifactCacheMissingBlobsArgs struct { + ArtifactID string + ArtifactIDAnything bool + BlobIDs []string + BlobIDsAnything bool +} + +type ArtifactCacheMissingBlobsReturns struct { + MissingArtifact bool + MissingBlobIDs []string + Err error +} + +type ArtifactCacheMissingBlobsExpectation struct { + Args ArtifactCacheMissingBlobsArgs + Returns ArtifactCacheMissingBlobsReturns +} + +func (_m *MockArtifactCache) ApplyMissingBlobsExpectation(e ArtifactCacheMissingBlobsExpectation) { + var args []interface{} + if e.Args.ArtifactIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.ArtifactID) + } + if e.Args.BlobIDsAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobIDs) + } + _m.On("MissingBlobs", args...).Return(e.Returns.MissingArtifact, e.Returns.MissingBlobIDs, e.Returns.Err) +} + +func (_m *MockArtifactCache) ApplyMissingBlobsExpectations(expectations []ArtifactCacheMissingBlobsExpectation) { + for _, e := range expectations { + _m.ApplyMissingBlobsExpectation(e) + } +} + +// MissingBlobs provides a mock function with given fields: artifactID, blobIDs +func (_m *MockArtifactCache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) { + ret := _m.Called(artifactID, blobIDs) + + var r0 bool + if rf, ok := ret.Get(0).(func(string, []string) bool); ok { + r0 = rf(artifactID, blobIDs) + } else { + r0 = ret.Get(0).(bool) + } + + var r1 []string + if rf, ok := ret.Get(1).(func(string, []string) []string); ok { + r1 = rf(artifactID, blobIDs) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]string) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(string, []string) error); ok { + r2 = rf(artifactID, blobIDs) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +type ArtifactCachePutArtifactArgs struct { + ArtifactID string + ArtifactIDAnything bool + ArtifactInfo types.ArtifactInfo + ArtifactInfoAnything bool +} + +type ArtifactCachePutArtifactReturns struct { + Err error +} + +type ArtifactCachePutArtifactExpectation struct { + Args ArtifactCachePutArtifactArgs + Returns ArtifactCachePutArtifactReturns +} + +func (_m *MockArtifactCache) ApplyPutArtifactExpectation(e ArtifactCachePutArtifactExpectation) { + var args []interface{} + if e.Args.ArtifactIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.ArtifactID) + } + if e.Args.ArtifactInfoAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.ArtifactInfo) + } + _m.On("PutArtifact", args...).Return(e.Returns.Err) +} + +func (_m *MockArtifactCache) ApplyPutArtifactExpectations(expectations []ArtifactCachePutArtifactExpectation) { + for _, e := range expectations { + _m.ApplyPutArtifactExpectation(e) + } +} + +// PutArtifact provides a mock function with given fields: artifactID, artifactInfo +func (_m *MockArtifactCache) PutArtifact(artifactID string, artifactInfo types.ArtifactInfo) error { + ret := _m.Called(artifactID, artifactInfo) + + var r0 error + if rf, ok := ret.Get(0).(func(string, types.ArtifactInfo) error); ok { + r0 = rf(artifactID, artifactInfo) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type ArtifactCachePutBlobArgs struct { + BlobID string + BlobIDAnything bool + BlobInfo types.BlobInfo + BlobInfoAnything bool +} + +type ArtifactCachePutBlobReturns struct { + Err error +} + +type ArtifactCachePutBlobExpectation struct { + Args ArtifactCachePutBlobArgs + Returns ArtifactCachePutBlobReturns +} + +func (_m *MockArtifactCache) ApplyPutBlobExpectation(e ArtifactCachePutBlobExpectation) *mock.Call { + var args []interface{} + if e.Args.BlobIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobID) + } + if e.Args.BlobInfoAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobInfo) + } + return _m.On("PutBlob", args...).Return(e.Returns.Err) + //return _m.On("PutBlob", mock.AnythingOfType("string"), mock.Anything).Return(e.Returns.Err) +} + +func (_m *MockArtifactCache) ApplyPutBlobExpectations(expectations []ArtifactCachePutBlobExpectation) { + for _, e := range expectations { + _m.ApplyPutBlobExpectation(e) + } +} + +// PutBlob provides a mock function with given fields: blobID, blobInfo +func (_m *MockArtifactCache) PutBlob(blobID string, blobInfo types.BlobInfo) error { + + for i := range blobInfo.Misconfigurations { + // suppress misconfiguration code block + for j := range blobInfo.Misconfigurations[i].Failures { + blobInfo.Misconfigurations[i].Failures[j].Code = types.Code{} + } + for j := range blobInfo.Misconfigurations[i].Successes { + blobInfo.Misconfigurations[i].Successes[j].Code = types.Code{} + } + for j := range blobInfo.Misconfigurations[i].Warnings { + blobInfo.Misconfigurations[i].Warnings[j].Code = types.Code{} + } + for j := range blobInfo.Misconfigurations[i].Exceptions { + blobInfo.Misconfigurations[i].Exceptions[j].Code = types.Code{} + } + } + + ret := _m.Called(blobID, blobInfo) + + var r0 error + if rf, ok := ret.Get(0).(func(string, types.BlobInfo) error); ok { + r0 = rf(blobID, blobInfo) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/pkg/fanal/cache/mock_cache.go b/pkg/fanal/cache/mock_cache.go new file mode 100644 index 000000000000..bb7623b0e6f7 --- /dev/null +++ b/pkg/fanal/cache/mock_cache.go @@ -0,0 +1,399 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package cache + +import ( + mock "github.com/stretchr/testify/mock" + + types "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// MockCache is an autogenerated mock type for the Cache type +type MockCache struct { + mock.Mock +} + +type CacheClearReturns struct { + Err error +} + +type CacheClearExpectation struct { + Returns CacheClearReturns +} + +func (_m *MockCache) ApplyClearExpectation(e CacheClearExpectation) { + var args []interface{} + _m.On("Clear", args...).Return(e.Returns.Err) +} + +func (_m *MockCache) ApplyClearExpectations(expectations []CacheClearExpectation) { + for _, e := range expectations { + _m.ApplyClearExpectation(e) + } +} + +// Clear provides a mock function with given fields: +func (_m *MockCache) Clear() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type CacheCloseReturns struct { + Err error +} + +type CacheCloseExpectation struct { + Returns CacheCloseReturns +} + +func (_m *MockCache) ApplyCloseExpectation(e CacheCloseExpectation) { + var args []interface{} + _m.On("Close", args...).Return(e.Returns.Err) +} + +func (_m *MockCache) ApplyCloseExpectations(expectations []CacheCloseExpectation) { + for _, e := range expectations { + _m.ApplyCloseExpectation(e) + } +} + +// Close provides a mock function with given fields: +func (_m *MockCache) Close() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type CacheDeleteBlobArgs struct { + BlobID string + BlobIDAnything bool +} + +type CacheDeleteBlobReturns struct { + _a0 error +} + +type CacheDeleteBlobExpectation struct { + Args CacheDeleteBlobArgs + Returns CacheDeleteBlobReturns +} + +func (_m *MockCache) ApplyDeleteBlobExpectation(e CacheDeleteBlobExpectation) { + var args []interface{} + if e.Args.BlobIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobID) + } + _m.On("DeleteBlob", args...).Return(e.Returns._a0) +} + +func (_m *MockCache) ApplyDeleteBlobExpectations(expectations []CacheDeleteBlobExpectation) { + for _, e := range expectations { + _m.ApplyDeleteBlobExpectation(e) + } +} + +// DeleteBlob provides a mock function with given fields: blobID +func (_m *MockCache) DeleteBlob(blobID string) error { + ret := _m.Called(blobID) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(blobID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type CacheGetArtifactArgs struct { + ArtifactID string + ArtifactIDAnything bool +} + +type CacheGetArtifactReturns struct { + ArtifactInfo types.ArtifactInfo + Err error +} + +type CacheGetArtifactExpectation struct { + Args CacheGetArtifactArgs + Returns CacheGetArtifactReturns +} + +func (_m *MockCache) ApplyGetArtifactExpectation(e CacheGetArtifactExpectation) { + var args []interface{} + if e.Args.ArtifactIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.ArtifactID) + } + _m.On("GetArtifact", args...).Return(e.Returns.ArtifactInfo, e.Returns.Err) +} + +func (_m *MockCache) ApplyGetArtifactExpectations(expectations []CacheGetArtifactExpectation) { + for _, e := range expectations { + _m.ApplyGetArtifactExpectation(e) + } +} + +// GetArtifact provides a mock function with given fields: artifactID +func (_m *MockCache) GetArtifact(artifactID string) (types.ArtifactInfo, error) { + ret := _m.Called(artifactID) + + var r0 types.ArtifactInfo + if rf, ok := ret.Get(0).(func(string) types.ArtifactInfo); ok { + r0 = rf(artifactID) + } else { + r0 = ret.Get(0).(types.ArtifactInfo) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(artifactID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type CacheGetBlobArgs struct { + BlobID string + BlobIDAnything bool +} + +type CacheGetBlobReturns struct { + BlobInfo types.BlobInfo + Err error +} + +type CacheGetBlobExpectation struct { + Args CacheGetBlobArgs + Returns CacheGetBlobReturns +} + +func (_m *MockCache) ApplyGetBlobExpectation(e CacheGetBlobExpectation) { + var args []interface{} + if e.Args.BlobIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobID) + } + _m.On("GetBlob", args...).Return(e.Returns.BlobInfo, e.Returns.Err) +} + +func (_m *MockCache) ApplyGetBlobExpectations(expectations []CacheGetBlobExpectation) { + for _, e := range expectations { + _m.ApplyGetBlobExpectation(e) + } +} + +// GetBlob provides a mock function with given fields: blobID +func (_m *MockCache) GetBlob(blobID string) (types.BlobInfo, error) { + ret := _m.Called(blobID) + + var r0 types.BlobInfo + if rf, ok := ret.Get(0).(func(string) types.BlobInfo); ok { + r0 = rf(blobID) + } else { + r0 = ret.Get(0).(types.BlobInfo) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(blobID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type CacheMissingBlobsArgs struct { + ArtifactID string + ArtifactIDAnything bool + BlobIDs []string + BlobIDsAnything bool +} + +type CacheMissingBlobsReturns struct { + MissingArtifact bool + MissingBlobIDs []string + Err error +} + +type CacheMissingBlobsExpectation struct { + Args CacheMissingBlobsArgs + Returns CacheMissingBlobsReturns +} + +func (_m *MockCache) ApplyMissingBlobsExpectation(e CacheMissingBlobsExpectation) { + var args []interface{} + if e.Args.ArtifactIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.ArtifactID) + } + if e.Args.BlobIDsAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobIDs) + } + _m.On("MissingBlobs", args...).Return(e.Returns.MissingArtifact, e.Returns.MissingBlobIDs, e.Returns.Err) +} + +func (_m *MockCache) ApplyMissingBlobsExpectations(expectations []CacheMissingBlobsExpectation) { + for _, e := range expectations { + _m.ApplyMissingBlobsExpectation(e) + } +} + +// MissingBlobs provides a mock function with given fields: artifactID, blobIDs +func (_m *MockCache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) { + ret := _m.Called(artifactID, blobIDs) + + var r0 bool + if rf, ok := ret.Get(0).(func(string, []string) bool); ok { + r0 = rf(artifactID, blobIDs) + } else { + r0 = ret.Get(0).(bool) + } + + var r1 []string + if rf, ok := ret.Get(1).(func(string, []string) []string); ok { + r1 = rf(artifactID, blobIDs) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]string) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(string, []string) error); ok { + r2 = rf(artifactID, blobIDs) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +type CachePutArtifactArgs struct { + ArtifactID string + ArtifactIDAnything bool + ArtifactInfo types.ArtifactInfo + ArtifactInfoAnything bool +} + +type CachePutArtifactReturns struct { + Err error +} + +type CachePutArtifactExpectation struct { + Args CachePutArtifactArgs + Returns CachePutArtifactReturns +} + +func (_m *MockCache) ApplyPutArtifactExpectation(e CachePutArtifactExpectation) { + var args []interface{} + if e.Args.ArtifactIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.ArtifactID) + } + if e.Args.ArtifactInfoAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.ArtifactInfo) + } + _m.On("PutArtifact", args...).Return(e.Returns.Err) +} + +func (_m *MockCache) ApplyPutArtifactExpectations(expectations []CachePutArtifactExpectation) { + for _, e := range expectations { + _m.ApplyPutArtifactExpectation(e) + } +} + +// PutArtifact provides a mock function with given fields: artifactID, artifactInfo +func (_m *MockCache) PutArtifact(artifactID string, artifactInfo types.ArtifactInfo) error { + ret := _m.Called(artifactID, artifactInfo) + + var r0 error + if rf, ok := ret.Get(0).(func(string, types.ArtifactInfo) error); ok { + r0 = rf(artifactID, artifactInfo) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type CachePutBlobArgs struct { + BlobID string + BlobIDAnything bool + BlobInfo types.BlobInfo + BlobInfoAnything bool +} + +type CachePutBlobReturns struct { + Err error +} + +type CachePutBlobExpectation struct { + Args CachePutBlobArgs + Returns CachePutBlobReturns +} + +func (_m *MockCache) ApplyPutBlobExpectation(e CachePutBlobExpectation) { + var args []interface{} + if e.Args.BlobIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobID) + } + if e.Args.BlobInfoAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobInfo) + } + _m.On("PutBlob", args...).Return(e.Returns.Err) +} + +func (_m *MockCache) ApplyPutBlobExpectations(expectations []CachePutBlobExpectation) { + for _, e := range expectations { + _m.ApplyPutBlobExpectation(e) + } +} + +// PutBlob provides a mock function with given fields: blobID, blobInfo +func (_m *MockCache) PutBlob(blobID string, blobInfo types.BlobInfo) error { + ret := _m.Called(blobID, blobInfo) + + var r0 error + if rf, ok := ret.Get(0).(func(string, types.BlobInfo) error); ok { + r0 = rf(blobID, blobInfo) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/pkg/fanal/cache/mock_local_artifact_cache.go b/pkg/fanal/cache/mock_local_artifact_cache.go new file mode 100644 index 000000000000..9b7266708cad --- /dev/null +++ b/pkg/fanal/cache/mock_local_artifact_cache.go @@ -0,0 +1,184 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package cache + +import ( + mock "github.com/stretchr/testify/mock" + + types "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// MockLocalArtifactCache is an autogenerated mock type for the LocalArtifactCache type +type MockLocalArtifactCache struct { + mock.Mock +} + +type LocalArtifactCacheClearReturns struct { + Err error +} + +type LocalArtifactCacheClearExpectation struct { + Returns LocalArtifactCacheClearReturns +} + +func (_m *MockLocalArtifactCache) ApplyClearExpectation(e LocalArtifactCacheClearExpectation) { + var args []interface{} + _m.On("Clear", args...).Return(e.Returns.Err) +} + +func (_m *MockLocalArtifactCache) ApplyClearExpectations(expectations []LocalArtifactCacheClearExpectation) { + for _, e := range expectations { + _m.ApplyClearExpectation(e) + } +} + +// Clear provides a mock function with given fields: +func (_m *MockLocalArtifactCache) Clear() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type LocalArtifactCacheCloseReturns struct { + Err error +} + +type LocalArtifactCacheCloseExpectation struct { + Returns LocalArtifactCacheCloseReturns +} + +func (_m *MockLocalArtifactCache) ApplyCloseExpectation(e LocalArtifactCacheCloseExpectation) { + var args []interface{} + _m.On("Close", args...).Return(e.Returns.Err) +} + +func (_m *MockLocalArtifactCache) ApplyCloseExpectations(expectations []LocalArtifactCacheCloseExpectation) { + for _, e := range expectations { + _m.ApplyCloseExpectation(e) + } +} + +// Close provides a mock function with given fields: +func (_m *MockLocalArtifactCache) Close() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type LocalArtifactCacheGetArtifactArgs struct { + ArtifactID string + ArtifactIDAnything bool +} + +type LocalArtifactCacheGetArtifactReturns struct { + ArtifactInfo types.ArtifactInfo + Err error +} + +type LocalArtifactCacheGetArtifactExpectation struct { + Args LocalArtifactCacheGetArtifactArgs + Returns LocalArtifactCacheGetArtifactReturns +} + +func (_m *MockLocalArtifactCache) ApplyGetArtifactExpectation(e LocalArtifactCacheGetArtifactExpectation) { + var args []interface{} + if e.Args.ArtifactIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.ArtifactID) + } + _m.On("GetArtifact", args...).Return(e.Returns.ArtifactInfo, e.Returns.Err) +} + +func (_m *MockLocalArtifactCache) ApplyGetArtifactExpectations(expectations []LocalArtifactCacheGetArtifactExpectation) { + for _, e := range expectations { + _m.ApplyGetArtifactExpectation(e) + } +} + +// GetArtifact provides a mock function with given fields: artifactID +func (_m *MockLocalArtifactCache) GetArtifact(artifactID string) (types.ArtifactInfo, error) { + ret := _m.Called(artifactID) + + var r0 types.ArtifactInfo + if rf, ok := ret.Get(0).(func(string) types.ArtifactInfo); ok { + r0 = rf(artifactID) + } else { + r0 = ret.Get(0).(types.ArtifactInfo) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(artifactID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type LocalArtifactCacheGetBlobArgs struct { + BlobID string + BlobIDAnything bool +} + +type LocalArtifactCacheGetBlobReturns struct { + BlobInfo types.BlobInfo + Err error +} + +type LocalArtifactCacheGetBlobExpectation struct { + Args LocalArtifactCacheGetBlobArgs + Returns LocalArtifactCacheGetBlobReturns +} + +func (_m *MockLocalArtifactCache) ApplyGetBlobExpectation(e LocalArtifactCacheGetBlobExpectation) { + var args []interface{} + if e.Args.BlobIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobID) + } + _m.On("GetBlob", args...).Return(e.Returns.BlobInfo, e.Returns.Err) +} + +func (_m *MockLocalArtifactCache) ApplyGetBlobExpectations(expectations []LocalArtifactCacheGetBlobExpectation) { + for _, e := range expectations { + _m.ApplyGetBlobExpectation(e) + } +} + +// GetBlob provides a mock function with given fields: blobID +func (_m *MockLocalArtifactCache) GetBlob(blobID string) (types.BlobInfo, error) { + ret := _m.Called(blobID) + + var r0 types.BlobInfo + if rf, ok := ret.Get(0).(func(string) types.BlobInfo); ok { + r0 = rf(blobID) + } else { + r0 = ret.Get(0).(types.BlobInfo) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(blobID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/fanal/cache/redis.go b/pkg/fanal/cache/redis.go new file mode 100644 index 000000000000..af9d2622b531 --- /dev/null +++ b/pkg/fanal/cache/redis.go @@ -0,0 +1,147 @@ +package cache + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/go-redis/redis/v8" + "github.com/hashicorp/go-multierror" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var _ Cache = &RedisCache{} + +const ( + redisPrefix = "fanal" +) + +type RedisCache struct { + client *redis.Client + expiration time.Duration +} + +func NewRedisCache(options *redis.Options, expiration time.Duration) RedisCache { + return RedisCache{ + client: redis.NewClient(options), + expiration: expiration, + } +} + +func (c RedisCache) PutArtifact(artifactID string, artifactConfig types.ArtifactInfo) error { + key := fmt.Sprintf("%s::%s::%s", redisPrefix, artifactBucket, artifactID) + b, err := json.Marshal(artifactConfig) + if err != nil { + return xerrors.Errorf("failed to marshal artifact JSON: %w", err) + } + if err := c.client.Set(context.TODO(), key, string(b), c.expiration).Err(); err != nil { + return xerrors.Errorf("unable to store artifact information in Redis cache (%s): %w", artifactID, err) + } + return nil +} + +func (c RedisCache) PutBlob(blobID string, blobInfo types.BlobInfo) error { + b, err := json.Marshal(blobInfo) + if err != nil { + return xerrors.Errorf("failed to marshal blob JSON: %w", err) + } + key := fmt.Sprintf("%s::%s::%s", redisPrefix, blobBucket, blobID) + if err := c.client.Set(context.TODO(), key, string(b), c.expiration).Err(); err != nil { + return xerrors.Errorf("unable to store blob information in Redis cache (%s): %w", blobID, err) + } + return nil +} +func (c RedisCache) DeleteBlobs(blobIDs []string) error { + var errs error + for _, blobID := range blobIDs { + key := fmt.Sprintf("%s::%s::%s", redisPrefix, artifactBucket, blobID) + if err := c.client.Del(context.TODO(), key).Err(); err != nil { + errs = multierror.Append(errs, xerrors.Errorf("unable to delete blob %s: %w", blobID, err)) + } + } + return errs +} + +func (c RedisCache) GetArtifact(artifactID string) (types.ArtifactInfo, error) { + key := fmt.Sprintf("%s::%s::%s", redisPrefix, artifactBucket, artifactID) + val, err := c.client.Get(context.TODO(), key).Bytes() + if err == redis.Nil { + return types.ArtifactInfo{}, xerrors.Errorf("artifact (%s) is missing in Redis cache", artifactID) + } else if err != nil { + return types.ArtifactInfo{}, xerrors.Errorf("failed to get artifact from the Redis cache: %w", err) + } + + var info types.ArtifactInfo + err = json.Unmarshal(val, &info) + if err != nil { + return types.ArtifactInfo{}, xerrors.Errorf("failed to unmarshal artifact (%s) from Redis value: %w", artifactID, err) + } + return info, nil +} + +func (c RedisCache) GetBlob(blobID string) (types.BlobInfo, error) { + key := fmt.Sprintf("%s::%s::%s", redisPrefix, blobBucket, blobID) + val, err := c.client.Get(context.TODO(), key).Bytes() + if err == redis.Nil { + return types.BlobInfo{}, xerrors.Errorf("blob (%s) is missing in Redis cache", blobID) + } else if err != nil { + return types.BlobInfo{}, xerrors.Errorf("failed to get blob from the Redis cache: %w", err) + } + + var blobInfo types.BlobInfo + if err = json.Unmarshal(val, &blobInfo); err != nil { + return types.BlobInfo{}, xerrors.Errorf("failed to unmarshal blob (%s) from Redis value: %w", blobID, err) + } + return blobInfo, nil +} + +func (c RedisCache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) { + var missingArtifact bool + var missingBlobIDs []string + for _, blobID := range blobIDs { + blobInfo, err := c.GetBlob(blobID) + if err != nil { + // error means cache missed blob info + missingBlobIDs = append(missingBlobIDs, blobID) + continue + } + if blobInfo.SchemaVersion != types.BlobJSONSchemaVersion { + missingBlobIDs = append(missingBlobIDs, blobID) + } + } + // get artifact info + artifactInfo, err := c.GetArtifact(artifactID) + // error means cache missed artifact info + if err != nil { + return true, missingBlobIDs, nil + } + if artifactInfo.SchemaVersion != types.ArtifactJSONSchemaVersion { + missingArtifact = true + } + return missingArtifact, missingBlobIDs, nil +} + +func (c RedisCache) Close() error { + return c.client.Close() +} + +func (c RedisCache) Clear() error { + ctx := context.Background() + + for { + keys, cursor, err := c.client.Scan(ctx, 0, redisPrefix+"::*", 100).Result() + if err != nil { + return xerrors.Errorf("failed to perform prefix scanning: %w", err) + } + if err = c.client.Unlink(ctx, keys...).Err(); err != nil { + return xerrors.Errorf("failed to unlink redis keys: %w", err) + } + if cursor == 0 { // We cleared all keys + break + } + } + return nil +} diff --git a/pkg/fanal/cache/redis_test.go b/pkg/fanal/cache/redis_test.go new file mode 100644 index 000000000000..028860c4dfc2 --- /dev/null +++ b/pkg/fanal/cache/redis_test.go @@ -0,0 +1,562 @@ +package cache_test + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/alicebob/miniredis/v2" + "github.com/go-redis/redis/v8" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +const correctHash = "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7" + +func TestRedisCache_PutArtifact(t *testing.T) { + type args struct { + artifactID string + artifactConfig types.ArtifactInfo + } + tests := []struct { + name string + setupRedis bool + args args + wantKey string + wantErr string + }{ + { + name: "happy path", + setupRedis: true, + args: args{ + artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", + artifactConfig: types.ArtifactInfo{ + SchemaVersion: 2, + Architecture: "amd64", + Created: time.Date(2020, 11, 14, 0, 20, 4, 0, time.UTC), + DockerVersion: "19.03.12", + OS: "linux", + }, + }, + wantKey: "fanal::artifact::sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", + }, + { + name: "no such host", + setupRedis: false, + args: args{ + artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", + artifactConfig: types.ArtifactInfo{}, + }, + wantErr: "unable to store artifact information in Redis cache", + }, + } + + // Set up Redis test server + s, err := miniredis.Run() + require.NoError(t, err) + defer s.Close() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addr := s.Addr() + if !tt.setupRedis { + addr = "dummy:16379" + } + + c := cache.NewRedisCache(&redis.Options{ + Addr: addr, + }, 0) + + err = c.PutArtifact(tt.args.artifactID, tt.args.artifactConfig) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + assert.NoError(t, err) + } + + got, err := s.Get(tt.wantKey) + require.NoError(t, err) + + want, err := json.Marshal(tt.args.artifactConfig) + require.NoError(t, err) + + assert.JSONEq(t, string(want), got) + }) + } +} + +func TestRedisCache_PutBlob(t *testing.T) { + type args struct { + blobID string + blobConfig types.BlobInfo + } + tests := []struct { + name string + setupRedis bool + args args + wantKey string + wantErr string + }{ + { + name: "happy path", + setupRedis: true, + args: args{ + blobID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + blobConfig: types.BlobInfo{ + SchemaVersion: 2, + Digest: "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + DiffID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + OS: types.OS{ + Family: "alpine", + Name: "3.10.2", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: []types.Package{ + { + Name: "musl", + Version: "1.1.22-r3", + SrcName: "musl", + SrcVersion: "1.1.22-r3", + }, + }, + }, + }, + }, + }, + wantKey: "fanal::blob::sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + }, + { + name: "no such host", + setupRedis: false, + args: args{ + blobID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + blobConfig: types.BlobInfo{}, + }, + wantErr: "unable to store blob information in Redis cache", + }, + } + + // Set up Redis test server + s, err := miniredis.Run() + require.NoError(t, err) + defer s.Close() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addr := s.Addr() + if !tt.setupRedis { + addr = "dummy:16379" + } + + c := cache.NewRedisCache(&redis.Options{ + Addr: addr, + }, 0) + + err = c.PutBlob(tt.args.blobID, tt.args.blobConfig) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + assert.NoError(t, err) + } + + got, err := s.Get(tt.wantKey) + require.NoError(t, err) + + want, err := json.Marshal(tt.args.blobConfig) + require.NoError(t, err) + + assert.JSONEq(t, string(want), got) + }) + } +} + +func TestRedisCache_GetArtifact(t *testing.T) { + info := types.ArtifactInfo{ + SchemaVersion: 2, + Architecture: "amd64", + Created: time.Date(2020, 11, 14, 0, 20, 4, 0, time.UTC), + DockerVersion: "19.03.12", + OS: "linux", + } + + tests := []struct { + name string + setupRedis bool + artifactID string + want types.ArtifactInfo + wantErr string + }{ + { + name: "happy path", + setupRedis: true, + artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", + want: info, + }, + { + name: "malformed JSON", + setupRedis: true, + artifactID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", + wantErr: "failed to unmarshal artifact", + }, + { + name: "no such host", + setupRedis: false, + artifactID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", + wantErr: "failed to get artifact from the Redis cache", + }, + { + name: "nonexistent key", + setupRedis: true, + artifactID: "sha256:foo", + wantErr: "artifact (sha256:foo) is missing in Redis cache", + }, + } + + // Set up Redis test server + s, err := miniredis.Run() + require.NoError(t, err) + defer s.Close() + + // Set key/value pairs + b, err := json.Marshal(info) + require.NoError(t, err) + + s.Set("fanal::artifact::sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", string(b)) + s.Set("fanal::artifact::sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", "foobar") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addr := s.Addr() + if !tt.setupRedis { + addr = "dummy:16379" + } + + c := cache.NewRedisCache(&redis.Options{ + Addr: addr, + }, 0) + + got, err := c.GetArtifact(tt.artifactID) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRedisCache_GetBlob(t *testing.T) { + blobInfo := types.BlobInfo{ + SchemaVersion: 2, + Digest: "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + DiffID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + OS: types.OS{ + Family: "alpine", + Name: "3.10.2", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: []types.Package{ + { + Name: "musl", + Version: "1.1.22-r3", + SrcName: "musl", + SrcVersion: "1.1.22-r3", + }, + }, + }, + }, + } + + tests := []struct { + name string + setupRedis bool + blobID string + want types.BlobInfo + wantErr string + }{ + { + name: "happy path", + setupRedis: true, + blobID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + want: blobInfo, + }, + { + name: "malformed JSON", + setupRedis: true, + blobID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", + wantErr: "failed to unmarshal blob", + }, + { + name: "no such host", + setupRedis: false, + blobID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", + wantErr: "failed to get blob from the Redis cache", + }, + { + name: "nonexistent key", + setupRedis: true, + blobID: "sha256:foo", + wantErr: "blob (sha256:foo) is missing in Redis cache", + }, + } + + // Set up Redis test server + s, err := miniredis.Run() + require.NoError(t, err) + defer s.Close() + + // Set key/value pairs + b, err := json.Marshal(blobInfo) + require.NoError(t, err) + s.Set("fanal::blob::sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", string(b)) + s.Set("fanal::blob::sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", "foobar") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addr := s.Addr() + if !tt.setupRedis { + addr = "dummy:16379" + } + + c := cache.NewRedisCache(&redis.Options{ + Addr: addr, + }, 0) + + got, err := c.GetBlob(tt.blobID) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRedisCache_MissingBlobs(t *testing.T) { + type args struct { + artifactID string + blobIDs []string + } + tests := []struct { + name string + setupRedis bool + args args + wantMissingArtifact bool + wantMissingBlobIDs []string + wantErr string + }{ + { + name: "missing both", + setupRedis: true, + args: args{ + artifactID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4/1", + blobIDs: []string{"sha256:1b3ee35aacca9866b01dd96e870136266bde18006ac2f0d6eb706c798d1fa3c3/11111"}, + }, + wantMissingArtifact: true, + wantMissingBlobIDs: []string{"sha256:1b3ee35aacca9866b01dd96e870136266bde18006ac2f0d6eb706c798d1fa3c3/11111"}, + }, + { + name: "missing artifact", + setupRedis: true, + args: args{ + artifactID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4/1", + blobIDs: []string{"sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0/11111"}, + }, + wantMissingArtifact: true, + }, + { + name: "missing blobs", + setupRedis: true, + args: args{ + artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf/1", + blobIDs: []string{"sha256:1b3ee35aacca9866b01dd96e870136266bde18006ac2f0d6eb706c798d1fa3c3/11111"}, + }, + wantMissingArtifact: false, + wantMissingBlobIDs: []string{"sha256:1b3ee35aacca9866b01dd96e870136266bde18006ac2f0d6eb706c798d1fa3c3/11111"}, + }, + { + name: "missing artifact with different schema version", + setupRedis: true, + args: args{ + artifactID: "sha256:be4e4bea2c2e15b403bb321562e78ea84b501fb41497472e91ecb41504e8a27c/1", + blobIDs: []string{"sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0/11111"}, + }, + wantMissingArtifact: true, + }, + { + name: "missing blobs with different schema version", + setupRedis: true, + args: args{ + artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf/1", + blobIDs: []string{"sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02/11111"}, + }, + wantMissingArtifact: false, + wantMissingBlobIDs: []string{"sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02/11111"}, + }, + { + name: "different analyzer versions", + setupRedis: true, + args: args{ + artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf/0", + blobIDs: []string{"sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0/11012"}, + }, + wantMissingArtifact: true, + wantMissingBlobIDs: []string{"sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0/11012"}, + }, + } + + // Set up Redis test server + s, err := miniredis.Run() + require.NoError(t, err) + defer s.Close() + + s.Set("fanal::artifact::sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf/1", + fmt.Sprintf("{\"SchemaVersion\": %d}", types.ArtifactJSONSchemaVersion)) + s.Set("fanal::artifact::sha256:be4e4bea2c2e15b403bb321562e78ea84b501fb41497472e91ecb41504e8a27c/1", + `{"SchemaVersion": 999999}`) // This version should not match the current version + s.Set("fanal::blob::sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0/11111", + fmt.Sprintf("{\"SchemaVersion\": %d}", types.BlobJSONSchemaVersion)) + s.Set("fanal::blob::sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02/11111", + `{"SchemaVersion": 999999}`) // This version should not match the current version + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addr := s.Addr() + if !tt.setupRedis { + addr = "dummy:6379" + } + + c := cache.NewRedisCache(&redis.Options{ + Addr: addr, + }, 0) + + missingArtifact, missingBlobIDs, err := c.MissingBlobs(tt.args.artifactID, tt.args.blobIDs) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wantMissingArtifact, missingArtifact) + assert.Equal(t, tt.wantMissingBlobIDs, missingBlobIDs) + }) + } +} + +func TestRedisCache_Close(t *testing.T) { + // Set up Redis test server + s, err := miniredis.Run() + require.NoError(t, err) + defer s.Close() + + t.Run("close", func(t *testing.T) { + c := cache.NewRedisCache(&redis.Options{ + Addr: s.Addr(), + }, 0) + closeErr := c.Close() + require.NoError(t, closeErr) + time.Sleep(3 * time.Second) // give it some time + assert.Equal(t, 0, s.CurrentConnectionCount(), "The client is disconnected") + }) +} + +func TestRedisCache_Clear(t *testing.T) { + // Set up Redis test server + s, err := miniredis.Run() + require.NoError(t, err) + defer s.Close() + + for i := 0; i < 200; i++ { + s.Set(fmt.Sprintf("fanal::key%d", i), "value") + } + s.Set("foo", "bar") + + t.Run("clear", func(t *testing.T) { + c := cache.NewRedisCache(&redis.Options{ + Addr: s.Addr(), + }, 0) + require.NoError(t, c.Clear()) + for i := 0; i < 200; i++ { + assert.False(t, s.Exists(fmt.Sprintf("fanal::key%d", i))) + } + assert.True(t, s.Exists("foo")) + }) +} + +func TestRedisCache_DeleteBlobs(t *testing.T) { + type args struct { + blobIDs []string + } + tests := []struct { + name string + setupRedis bool + args args + wantKey string + wantErr string + }{ + { + name: "happy path", + setupRedis: true, + args: args{ + blobIDs: []string{correctHash}, + }, + wantKey: "fanal::blob::" + correctHash, + }, + { + name: "no such host", + setupRedis: false, + args: args{ + blobIDs: []string{"sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae800"}, + }, + wantErr: "unable to delete blob", + }, + } + + // Set up Redis test server + s, err := miniredis.Run() + require.NoError(t, err) + defer s.Close() + + s.Set(correctHash, "any string") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addr := s.Addr() + if !tt.setupRedis { + addr = "dummy:16379" + } + + c := cache.NewRedisCache(&redis.Options{ + Addr: addr, + }, 0) + + err = c.DeleteBlobs(tt.args.blobIDs) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + }) + } +} diff --git a/pkg/fanal/cache/s3.go b/pkg/fanal/cache/s3.go new file mode 100644 index 000000000000..b11aed42a476 --- /dev/null +++ b/pkg/fanal/cache/s3.go @@ -0,0 +1,180 @@ +package cache + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/feature/s3/manager" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/hashicorp/go-multierror" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var _ Cache = &S3Cache{} + +type s3API interface { + HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error) + PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) + DeleteBucket(ctx context.Context, params *s3.DeleteBucketInput, optFns ...func(*s3.Options)) (*s3.DeleteBucketOutput, error) +} + +type S3Cache struct { + s3Client s3API + downloader *manager.Downloader + bucketName string + prefix string +} + +func NewS3Cache(bucketName, prefix string, api s3API, downloaderAPI *manager.Downloader) S3Cache { + return S3Cache{ + s3Client: api, + downloader: downloaderAPI, + bucketName: bucketName, + prefix: prefix, + } +} + +func (c S3Cache) PutArtifact(artifactID string, artifactConfig types.ArtifactInfo) (err error) { + key := fmt.Sprintf("%s/%s/%s", artifactBucket, c.prefix, artifactID) + if err := c.put(key, artifactConfig); err != nil { + return xerrors.Errorf("unable to store artifact information in cache (%s): %w", artifactID, err) + } + return nil +} + +func (c S3Cache) DeleteBlobs(blobIDs []string) error { + var errs error + for _, blobID := range blobIDs { + key := fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID) + input := &s3.DeleteBucketInput{Bucket: aws.String(key)} + if _, err := c.s3Client.DeleteBucket(context.TODO(), input); err != nil { + errs = multierror.Append(errs, err) + } + } + return errs +} + +func (c S3Cache) PutBlob(blobID string, blobInfo types.BlobInfo) error { + key := fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID) + if err := c.put(key, blobInfo); err != nil { + return xerrors.Errorf("unable to store blob information in cache (%s): %w", blobID, err) + } + return nil +} + +func (c S3Cache) put(key string, body interface{}) (err error) { + b, err := json.Marshal(body) + if err != nil { + return err + } + params := &s3.PutObjectInput{ + Bucket: aws.String(c.bucketName), + Key: aws.String(key), + Body: bytes.NewReader(b), + } + _, err = c.s3Client.PutObject(context.TODO(), params) + if err != nil { + return xerrors.Errorf("unable to put object: %w", err) + } + // Index file due S3 caveat read after write consistency + _, err = c.s3Client.PutObject(context.TODO(), &s3.PutObjectInput{ + Bucket: aws.String(c.bucketName), + Key: aws.String(fmt.Sprintf("%s.index", key)), + }) + if err != nil { + return xerrors.Errorf("unable to put index object: %w", err) + } + return nil +} + +func (c S3Cache) GetBlob(blobID string) (types.BlobInfo, error) { + var blobInfo types.BlobInfo + buf := manager.NewWriteAtBuffer([]byte{}) + _, err := c.downloader.Download(context.TODO(), buf, &s3.GetObjectInput{ + Bucket: aws.String(c.bucketName), + Key: aws.String(fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID)), + }) + if err != nil { + return types.BlobInfo{}, xerrors.Errorf("failed to get blob from the cache: %w", err) + } + err = json.Unmarshal(buf.Bytes(), &blobInfo) + if err != nil { + return types.BlobInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err) + } + return blobInfo, nil +} + +func (c S3Cache) GetArtifact(artifactID string) (types.ArtifactInfo, error) { + var info types.ArtifactInfo + buf := manager.NewWriteAtBuffer([]byte{}) + _, err := c.downloader.Download(context.TODO(), buf, &s3.GetObjectInput{ + Bucket: aws.String(c.bucketName), + Key: aws.String(fmt.Sprintf("%s/%s/%s", artifactBucket, c.prefix, artifactID)), + }) + if err != nil { + return types.ArtifactInfo{}, xerrors.Errorf("failed to get artifact from the cache: %w", err) + } + err = json.Unmarshal(buf.Bytes(), &info) + if err != nil { + return types.ArtifactInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err) + } + return info, nil +} + +func (c S3Cache) getIndex(key, keyType string) error { + _, err := c.s3Client.HeadObject(context.TODO(), &s3.HeadObjectInput{ + Key: aws.String(fmt.Sprintf("%s/%s/%s.index", keyType, c.prefix, key)), + Bucket: &c.bucketName, + }) + if err != nil { + return xerrors.Errorf("failed to get index from the cache: %w", err) + } + return nil +} + +func (c S3Cache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) { + var missingArtifact bool + var missingBlobIDs []string + for _, blobID := range blobIDs { + err := c.getIndex(blobID, blobBucket) + if err != nil { + // error means cache missed blob info + missingBlobIDs = append(missingBlobIDs, blobID) + continue + } + blobInfo, err := c.GetBlob(blobID) + if err != nil { + return true, missingBlobIDs, xerrors.Errorf("the blob object (%s) doesn't exist in S3 even though the index file exists: %w", blobID, err) + } + if blobInfo.SchemaVersion != types.BlobJSONSchemaVersion { + missingBlobIDs = append(missingBlobIDs, blobID) + } + } + // get artifact info + err := c.getIndex(artifactID, artifactBucket) + // error means cache missed artifact info + if err != nil { + return true, missingBlobIDs, nil + } + artifactInfo, err := c.GetArtifact(artifactID) + if err != nil { + return true, missingBlobIDs, xerrors.Errorf("the artifact object (%s) doesn't exist in S3 even though the index file exists: %w", artifactID, err) + } + if artifactInfo.SchemaVersion != types.ArtifactJSONSchemaVersion { + missingArtifact = true + } + return missingArtifact, missingBlobIDs, nil +} + +func (c S3Cache) Close() error { + return nil +} + +func (c S3Cache) Clear() error { + return nil +} diff --git a/pkg/fanal/cache/s3_test.go b/pkg/fanal/cache/s3_test.go new file mode 100644 index 000000000000..ed3da27b974f --- /dev/null +++ b/pkg/fanal/cache/s3_test.go @@ -0,0 +1,312 @@ +package cache + +import ( + "context" + "errors" + "reflect" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/feature/s3/manager" + "github.com/aws/aws-sdk-go-v2/service/s3" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type mockS3Client struct { + s3API +} + +const ( + correctHash = "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7" +) + +func (m *mockS3Client) PutObject(ctx context.Context, in *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) { + return &s3.PutObjectOutput{}, nil +} + +func (m *mockS3Client) HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error) { + return &s3.HeadObjectOutput{}, nil +} + +func (m *mockS3Client) DeleteBucket(ctx context.Context, in *s3.DeleteBucketInput, optFns ...func(*s3.Options)) (*s3.DeleteBucketOutput, error) { + if in != nil && *in.Bucket == blobBucket+"/prefix/"+correctHash { + return &s3.DeleteBucketOutput{}, nil + } + return nil, errors.New("unknown bucket") +} + +func TestS3Cache_PutBlob(t *testing.T) { + mockSvc := &mockS3Client{} + + type fields struct { + S3 s3API + Downloader *manager.Downloader + BucketName string + Prefix string + } + type args struct { + blobID string + blobInfo types.BlobInfo + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + S3: mockSvc, + BucketName: "test", + Prefix: "prefix", + }, + args: args{ + blobID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + blobInfo: types.BlobInfo{ + SchemaVersion: 1, + OS: types.OS{ + Family: "alpine", + Name: "3.10", + }, + }}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := NewS3Cache(tt.fields.BucketName, tt.fields.Prefix, tt.fields.S3, tt.fields.Downloader) + if err := c.PutBlob(tt.args.blobID, tt.args.blobInfo); (err != nil) != tt.wantErr { + t.Errorf("S3Cache.PutBlob() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestS3Cache_PutArtifact(t *testing.T) { + mockSvc := &mockS3Client{} + + type fields struct { + S3 s3API + Downloader *manager.Downloader + BucketName string + Prefix string + } + type args struct { + artifactID string + artifactConfig types.ArtifactInfo + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + S3: mockSvc, + BucketName: "test", + Prefix: "prefix", + }, + args: args{ + artifactID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", + artifactConfig: types.ArtifactInfo{ + SchemaVersion: 1, + Architecture: "amd64", + Created: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC), + DockerVersion: "18.06.1-ce", + OS: "linux", + HistoryPackages: []types.Package{ + { + Name: "musl", + Version: "1.2.3", + }, + }, + }}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := NewS3Cache(tt.fields.BucketName, tt.fields.Prefix, tt.fields.S3, tt.fields.Downloader) + if err := c.PutArtifact(tt.args.artifactID, tt.args.artifactConfig); (err != nil) != tt.wantErr { + t.Errorf("S3Cache.PutArtifact() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestS3Cache_getIndex(t *testing.T) { + mockSvc := &mockS3Client{} + + type fields struct { + S3 s3API + Downloader *manager.Downloader + BucketName string + Prefix string + } + type args struct { + key string + keyType string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + S3: mockSvc, + BucketName: "test", + Prefix: "prefix", + }, + args: args{ + key: "key", + keyType: "artifactBucket", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := NewS3Cache(tt.fields.BucketName, tt.fields.Prefix, tt.fields.S3, tt.fields.Downloader) + if err := c.getIndex(tt.args.key, tt.args.keyType); (err != nil) != tt.wantErr { + t.Errorf("S3Cache.getIndex() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +type mockS3ClientMissingBlobs struct { + s3API +} + +func (m *mockS3ClientMissingBlobs) PutObject(ctx context.Context, in *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) { + return &s3.PutObjectOutput{}, nil +} + +func (m *mockS3ClientMissingBlobs) HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error) { + return &s3.HeadObjectOutput{}, xerrors.Errorf("the object doesn't exist in S3") +} + +func TestS3Cache_MissingBlobs(t *testing.T) { + mockSvc := &mockS3ClientMissingBlobs{} + + type fields struct { + S3 s3API + Downloader *manager.Downloader + BucketName string + Prefix string + } + type args struct { + artifactID string + blobIDs []string + analyzerVersions map[string]int + configAnalyzerVersions map[string]int + } + tests := []struct { + name string + fields fields + args args + want bool + wantStringSlice []string + wantErr bool + }{{ + name: "happy path", + fields: fields{ + S3: mockSvc, + BucketName: "test", + Prefix: "prefix", + }, + args: args{ + artifactID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4/1", + blobIDs: []string{"sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7/10011"}, + }, + want: true, + wantStringSlice: []string{"sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7/10011"}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := NewS3Cache(tt.fields.BucketName, tt.fields.Prefix, tt.fields.S3, tt.fields.Downloader) + got, got1, err := c.MissingBlobs(tt.args.artifactID, tt.args.blobIDs) + if (err != nil) != tt.wantErr { + t.Errorf("S3Cache.MissingBlobs() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("S3Cache.MissingBlobs() got = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got1, tt.wantStringSlice) { + t.Errorf("S3Cache.MissingBlobs() got1 = %v, want %v", got1, tt.wantStringSlice) + } + }) + } +} + +func TestS3Cache_DeleteBlobs(t *testing.T) { + mockSvc := &mockS3Client{} + + type fields struct { + S3 s3API + Downloader *manager.Downloader + BucketName string + Prefix string + } + type args struct { + blobIDs []string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + S3: mockSvc, + BucketName: "test", + Prefix: "prefix", + }, + args: args{ + blobIDs: []string{correctHash}, + }, + }, + { + name: "delete blob with bad ID", + fields: fields{ + S3: mockSvc, + BucketName: "test", + Prefix: "prefix", + }, + args: args{ + blobIDs: []string{"unde"}, + }, + wantErr: true, + }, + { + name: "delete blobs with bad ID", + fields: fields{ + S3: mockSvc, + BucketName: "test", + Prefix: "prefix", + }, + args: args{ + blobIDs: []string{correctHash}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := NewS3Cache(tt.fields.BucketName, tt.fields.Prefix, tt.fields.S3, tt.fields.Downloader) + if err := c.DeleteBlobs(tt.args.blobIDs); (err != nil) != tt.wantErr { + t.Errorf("S3Cache.PutBlob() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/fanal/cache/testdata/broken-image.db b/pkg/fanal/cache/testdata/broken-image.db new file mode 100644 index 000000000000..c27eb16b6454 Binary files /dev/null and b/pkg/fanal/cache/testdata/broken-image.db differ diff --git a/pkg/fanal/cache/testdata/broken-layer.db b/pkg/fanal/cache/testdata/broken-layer.db new file mode 100644 index 000000000000..434a88d2811f Binary files /dev/null and b/pkg/fanal/cache/testdata/broken-layer.db differ diff --git a/pkg/fanal/cache/testdata/different-image-schema.db b/pkg/fanal/cache/testdata/different-image-schema.db new file mode 100644 index 000000000000..0a205256e605 Binary files /dev/null and b/pkg/fanal/cache/testdata/different-image-schema.db differ diff --git a/pkg/fanal/cache/testdata/fanal.db b/pkg/fanal/cache/testdata/fanal.db new file mode 100644 index 000000000000..7fb522be223e Binary files /dev/null and b/pkg/fanal/cache/testdata/fanal.db differ diff --git a/pkg/fanal/cache/testdata/policy/test.rego b/pkg/fanal/cache/testdata/policy/test.rego new file mode 100644 index 000000000000..b27c498ab823 --- /dev/null +++ b/pkg/fanal/cache/testdata/policy/test.rego @@ -0,0 +1 @@ +package foo \ No newline at end of file diff --git a/pkg/fanal/cache/testdata/trivy-secret.yaml b/pkg/fanal/cache/testdata/trivy-secret.yaml new file mode 100644 index 000000000000..8ce416eaba9d --- /dev/null +++ b/pkg/fanal/cache/testdata/trivy-secret.yaml @@ -0,0 +1,4 @@ +disable-allow-rules: + - usr-dirs + - examples + diff --git a/pkg/fanal/external/config_scan.go b/pkg/fanal/external/config_scan.go new file mode 100644 index 000000000000..43f9ede430f4 --- /dev/null +++ b/pkg/fanal/external/config_scan.go @@ -0,0 +1,79 @@ +package external + +import ( + "context" + "errors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/applier" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/misconf" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" +) + +type ConfigScanner struct { + cache cache.FSCache + policyPaths []string + dataPaths []string + namespaces []string + allowEmbedded bool +} + +func NewConfigScanner(cacheDir string, policyPaths, dataPaths, namespaces []string, allowEmbedded bool) (*ConfigScanner, error) { + // Initialize local cache + cacheClient, err := cache.NewFSCache(cacheDir) + if err != nil { + return nil, err + } + + return &ConfigScanner{ + cache: cacheClient, + policyPaths: policyPaths, + dataPaths: dataPaths, + namespaces: namespaces, + allowEmbedded: allowEmbedded, + }, nil +} + +func (s ConfigScanner) Scan(dir string) ([]types.Misconfiguration, error) { + art, err := local.NewArtifact(dir, s.cache, artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + PolicyPaths: s.policyPaths, + DataPaths: s.dataPaths, + Namespaces: s.namespaces, + DisableEmbeddedPolicies: !s.allowEmbedded, + DisableEmbeddedLibraries: !s.allowEmbedded, + }, + }) + if err != nil { + return nil, err + } + + // Scan config files + result, err := art.Inspect(context.Background()) + if err != nil { + return nil, err + } + + // Merge layers + a := applier.NewApplier(s.cache) + mergedLayer, err := a.ApplyLayers(result.ID, result.BlobIDs) + if !errors.Is(err, analyzer.ErrUnknownOS) && !errors.Is(err, analyzer.ErrNoPkgsDetected) { + return nil, err + } + + // Do not assert successes and layer + for i := range mergedLayer.Misconfigurations { + mergedLayer.Misconfigurations[i].Layer = types.Layer{} + } + + return mergedLayer.Misconfigurations, nil +} + +func (s ConfigScanner) Close() error { + return s.cache.Close() +} diff --git a/pkg/fanal/external/config_scan_test.go b/pkg/fanal/external/config_scan_test.go new file mode 100644 index 000000000000..094fdf41accf --- /dev/null +++ b/pkg/fanal/external/config_scan_test.go @@ -0,0 +1,143 @@ +package external_test + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/external" + "github.com/aquasecurity/trivy/pkg/fanal/types" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" +) + +func TestConfigScanner_Scan(t *testing.T) { + type fields struct { + policyPaths []string + dataPaths []string + namespaces []string + } + tests := []struct { + name string + fields fields + inputDir string + want []types.Misconfiguration + }{ + { + name: "deny", + fields: fields{ + policyPaths: []string{filepath.Join("testdata", "deny")}, + namespaces: []string{"testdata"}, + }, + inputDir: filepath.Join("testdata", "deny"), + want: []types.Misconfiguration{ + { + FileType: "dockerfile", + FilePath: "Dockerfile", + Failures: types.MisconfResults{ + types.MisconfResult{ + Namespace: "testdata.xyz_200", + Query: "data.testdata.xyz_200.deny", + Message: "Old image", + PolicyMetadata: types.PolicyMetadata{ + ID: "XYZ-200", + Type: "Dockerfile Security Check", + Title: "Old FROM", + Description: "Rego module: data.testdata.xyz_200", + Severity: "LOW", + RecommendedActions: "", + References: []string(nil), + }, + CauseMetadata: types.CauseMetadata{ + Resource: "", + Provider: "Dockerfile", + Service: "general", + StartLine: 1, + EndLine: 2, + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "FROM alpine:3.10", + Highlighted: "\x1b[38;5;64mFROM\x1b[0m\x1b[38;5;37m alpine:3.10", + IsCause: true, + Annotation: "", + Truncated: false, + FirstCause: true, + LastCause: false, + }, + { + Number: 2, + Content: "", + Highlighted: "\x1b[0m", + IsCause: true, + Annotation: "", + Truncated: false, + FirstCause: false, + LastCause: true, + }, + }, + }, + }, Traces: []string(nil), + }, + }, Warnings: types.MisconfResults(nil), + Successes: types.MisconfResults(nil), + Exceptions: types.MisconfResults(nil), + Layer: types.Layer{ + Digest: "", + DiffID: "", + }, + }, + }, + }, + { + name: "allow", + fields: fields{ + policyPaths: []string{filepath.Join("testdata", "allow")}, + namespaces: []string{"testdata"}, + }, + inputDir: filepath.Join("testdata", "allow"), + want: []types.Misconfiguration{ + { + FileType: "dockerfile", + FilePath: "Dockerfile", + Successes: types.MisconfResults{ + { + Namespace: "testdata.xyz_200", + Query: "data.testdata.xyz_200.deny", + PolicyMetadata: types.PolicyMetadata{ + ID: "XYZ-200", + Type: "Dockerfile Security Check", + Title: "Old FROM", + Description: "Rego module: data.testdata.xyz_200", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Resource: "", + Provider: "Dockerfile", + Service: "general", + StartLine: 0, + EndLine: 0, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, err := external.NewConfigScanner(t.TempDir(), + tt.fields.policyPaths, tt.fields.dataPaths, tt.fields.namespaces, false) + require.NoError(t, err) + + defer func() { _ = s.Close() }() + + got, err := s.Scan(tt.inputDir) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/external/testdata/allow/Dockerfile b/pkg/fanal/external/testdata/allow/Dockerfile new file mode 100644 index 000000000000..fb7daf440fc6 --- /dev/null +++ b/pkg/fanal/external/testdata/allow/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine:3.14 + +ADD foo.txt . \ No newline at end of file diff --git a/pkg/fanal/external/testdata/allow/docker.rego b/pkg/fanal/external/testdata/allow/docker.rego new file mode 100644 index 000000000000..0ef57f138813 --- /dev/null +++ b/pkg/fanal/external/testdata/allow/docker.rego @@ -0,0 +1,20 @@ +package testdata.xyz_200 + +__rego_metadata__ := { + "id": "XYZ-200", + "title": "Old FROM", + "version": "v1.0.0", + "severity": "LOW", + "type": "Docker Security Check", +} + +__rego_input__ := { + "combine": false, + "selector": [{"type": "dockerfile"}], +} + +deny[msg] { + input.stages[from] + from == "alpine:3.10" + msg := "Old image" +} diff --git a/pkg/fanal/external/testdata/deny/Dockerfile b/pkg/fanal/external/testdata/deny/Dockerfile new file mode 100644 index 000000000000..b3be16e28eac --- /dev/null +++ b/pkg/fanal/external/testdata/deny/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine:3.10 + +ADD foo.txt . \ No newline at end of file diff --git a/pkg/fanal/external/testdata/deny/docker.rego b/pkg/fanal/external/testdata/deny/docker.rego new file mode 100644 index 000000000000..6be4af8ee57e --- /dev/null +++ b/pkg/fanal/external/testdata/deny/docker.rego @@ -0,0 +1,25 @@ +package testdata.xyz_200 + +__rego_metadata__ := { + "id": "XYZ-200", + "title": "Old FROM", + "version": "v1.0.0", + "severity": "LOW", + "type": "Docker Security Check", +} + +__rego_input__ := { + "combine": false, + "selector": [{"type": "dockerfile"}], +} + +deny[res] { + stage := input.Stages[_] + stage.Name == "alpine:3.10" + msg := "Old image" + res := { + "msg": msg, + "startline": 1, + "endline": 2, + } +} diff --git a/pkg/fanal/handler/all/import.go b/pkg/fanal/handler/all/import.go new file mode 100644 index 000000000000..37c7c5545708 --- /dev/null +++ b/pkg/fanal/handler/all/import.go @@ -0,0 +1,6 @@ +package all + +import ( + _ "github.com/aquasecurity/trivy/pkg/fanal/handler/sysfile" + _ "github.com/aquasecurity/trivy/pkg/fanal/handler/unpackaged" +) diff --git a/pkg/fanal/handler/handler.go b/pkg/fanal/handler/handler.go new file mode 100644 index 000000000000..10ce085b3547 --- /dev/null +++ b/pkg/fanal/handler/handler.go @@ -0,0 +1,79 @@ +package handler + +import ( + "context" + "sort" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var ( + postHandlerInits = make(map[types.HandlerType]postHandlerInit) +) + +type postHandlerInit func(artifact.Option) (PostHandler, error) + +type PostHandler interface { + Type() types.HandlerType + Version() int + Handle(context.Context, *analyzer.AnalysisResult, *types.BlobInfo) error + Priority() int +} + +// RegisterPostHandlerInit adds a constructor of post handler +func RegisterPostHandlerInit(t types.HandlerType, init postHandlerInit) { + postHandlerInits[t] = init +} + +func DeregisterPostHandler(t types.HandlerType) { + delete(postHandlerInits, t) +} + +type Manager struct { + postHandlers []PostHandler +} + +func NewManager(artifactOpt artifact.Option) (Manager, error) { + var m Manager + for t, handlerInit := range postHandlerInits { + // Skip the handler if it is disabled + if slices.Contains(artifactOpt.DisabledHandlers, t) { + continue + } + handler, err := handlerInit(artifactOpt) + if err != nil { + return Manager{}, xerrors.Errorf("post handler %s initialize error: %w", t, err) + } + + m.postHandlers = append(m.postHandlers, handler) + } + + // Sort post handlers by priority + sort.Slice(m.postHandlers, func(i, j int) bool { + return m.postHandlers[i].Priority() > m.postHandlers[j].Priority() + }) + + return m, nil +} + +func (m Manager) Versions() map[string]int { + versions := make(map[string]int) + for _, h := range m.postHandlers { + versions[string(h.Type())] = h.Version() + } + return versions +} + +func (m Manager) PostHandle(ctx context.Context, result *analyzer.AnalysisResult, blob *types.BlobInfo) error { + for _, h := range m.postHandlers { + if err := h.Handle(ctx, result, blob); err != nil { + return xerrors.Errorf("post handler error: %w", err) + } + } + return nil +} diff --git a/pkg/fanal/handler/handler_test.go b/pkg/fanal/handler/handler_test.go new file mode 100644 index 000000000000..b53f885e24e0 --- /dev/null +++ b/pkg/fanal/handler/handler_test.go @@ -0,0 +1,107 @@ +package handler_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/handler" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type fakeHook struct{} + +func (h fakeHook) Handle(ctx context.Context, result *analyzer.AnalysisResult, info *types.BlobInfo) error { + info.DiffID = "fake" + return nil +} + +func (h fakeHook) Priority() int { + return 1 +} + +func (h fakeHook) Version() int { return 1 } + +func (h fakeHook) Type() types.HandlerType { return "fake" } + +func fakehookInit(_ artifact.Option) (handler.PostHandler, error) { + return fakeHook{}, nil +} + +func TestManager_Versions(t *testing.T) { + tests := []struct { + name string + disable []types.HandlerType + want map[string]int + }{ + { + name: "happy path", + want: map[string]int{ + "fake": 1, + }, + }, + { + name: "disable hooks", + disable: []types.HandlerType{"fake"}, + want: map[string]int{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + handler.RegisterPostHandlerInit("fake", fakehookInit) + defer handler.DeregisterPostHandler("fake") + m, err := handler.NewManager(artifact.Option{ + DisabledHandlers: tt.disable, + }) + require.NoError(t, err) + got := m.Versions() + assert.Equal(t, tt.want, got) + }) + } +} + +func TestManager_CallHooks(t *testing.T) { + tests := []struct { + name string + disable []types.HandlerType + want types.BlobInfo + }{ + { + name: "happy path", + want: types.BlobInfo{ + Digest: "digest", + DiffID: "fake", + }, + }, + { + name: "disable hooks", + disable: []types.HandlerType{"fake"}, + want: types.BlobInfo{ + Digest: "digest", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + handler.RegisterPostHandlerInit("fake", fakehookInit) + defer handler.DeregisterPostHandler("fake") + blob := types.BlobInfo{ + Digest: "digest", + } + m, err := handler.NewManager(artifact.Option{ + DisabledHandlers: tt.disable, + }) + require.NoError(t, err) + + err = m.PostHandle(context.TODO(), nil, &blob) + require.NoError(t, err) + assert.Equal(t, tt.want, blob) + }) + } +} diff --git a/pkg/fanal/handler/sysfile/filter.go b/pkg/fanal/handler/sysfile/filter.go new file mode 100644 index 000000000000..09525aebc1e7 --- /dev/null +++ b/pkg/fanal/handler/sysfile/filter.go @@ -0,0 +1,117 @@ +package nodejs + +import ( + "context" + "strings" + + "golang.org/x/exp/slices" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/handler" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + handler.RegisterPostHandlerInit(types.SystemFileFilteringPostHandler, newSystemFileFilteringPostHandler) +} + +const version = 1 + +var ( + defaultSystemFiles = []string{ + // TODO: Google Distroless removes /var/lib/dpkg/info/*.list, so we cannot know which files are installed by dpkg. + // We have to hardcode these files at the moment, but should look for the better way. + "/usr/lib/python2.7/argparse.egg-info", + "/usr/lib/python2.7/lib-dynload/Python-2.7.egg-info", + "/usr/lib/python2.7/wsgiref.egg-info", + } + + affectedTypes = []types.LangType{ + // ruby + types.GemSpec, + + // python + types.PythonPkg, + + // conda + types.CondaPkg, + + // node.js + types.NodePkg, + + // Go binaries + types.GoBinary, + } +) + +type systemFileFilteringPostHandler struct{} + +func newSystemFileFilteringPostHandler(artifact.Option) (handler.PostHandler, error) { + return systemFileFilteringPostHandler{}, nil +} + +// Handle removes files installed by OS package manager such as yum. +func (h systemFileFilteringPostHandler) Handle(_ context.Context, result *analyzer.AnalysisResult, blob *types.BlobInfo) error { + var systemFiles []string + for _, file := range append(result.SystemInstalledFiles, defaultSystemFiles...) { + // Trim leading slashes to be the same format as the path in container images. + systemFile := strings.TrimPrefix(file, "/") + // We should check the root filepath ("/") and ignore it. + // Otherwise libraries with an empty filePath will be removed. + if systemFile != "" { + systemFiles = append(systemFiles, systemFile) + } + } + + var apps []types.Application + for _, app := range blob.Applications { + // If the lang-specific package was installed by OS package manager, it should not be taken. + // Otherwise, the package version will be wrong, then it will lead to false positive. + if slices.Contains(systemFiles, app.FilePath) && slices.Contains(affectedTypes, app.Type) { + continue + } + + var pkgs []types.Package + for _, lib := range app.Libraries { + // If the lang-specific package was installed by OS package manager, it should not be taken. + // Otherwise, the package version will be wrong, then it will lead to false positive. + if slices.Contains(systemFiles, lib.FilePath) { + continue + } + pkgs = append(pkgs, lib) + } + + // Overwrite Libraries + app.Libraries = pkgs + apps = append(apps, app) + } + + // Iterate and delete unnecessary customResource + i := 0 + for _, res := range blob.CustomResources { + if slices.Contains(systemFiles, res.FilePath) { + continue + } + blob.CustomResources[i] = res + i++ + } + blob.CustomResources = blob.CustomResources[:i] + + // Overwrite Applications + blob.Applications = apps + + return nil +} + +func (h systemFileFilteringPostHandler) Version() int { + return version +} + +func (h systemFileFilteringPostHandler) Type() types.HandlerType { + return types.SystemFileFilteringPostHandler +} + +func (h systemFileFilteringPostHandler) Priority() int { + return types.SystemFileFilteringPostHandlerPriority +} diff --git a/pkg/fanal/handler/sysfile/filter_test.go b/pkg/fanal/handler/sysfile/filter_test.go new file mode 100644 index 000000000000..1b987fd5823f --- /dev/null +++ b/pkg/fanal/handler/sysfile/filter_test.go @@ -0,0 +1,268 @@ +package nodejs + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_systemFileFilterHook_Hook(t *testing.T) { + tests := []struct { + name string + result *analyzer.AnalysisResult + blob *types.BlobInfo + want *types.BlobInfo + }{ + { + name: "happy path", + result: &analyzer.AnalysisResult{ + SystemInstalledFiles: []string{ + "/", + "/usr/bin/pydoc", + "/usr/bin/python", + "/usr/bin/python2", + "/usr/bin/python2.7", + "/usr/libexec/platform-python", + "/usr/share/doc/python-2.7.5", + "/usr/share/doc/python-2.7.5/LICENSE", + "/usr/share/doc/python-2.7.5/README", + "/usr/share/man/man1/python.1.gz", + "/usr/share/man/man1/python2.1.gz", + "/usr/share/man/man1/python2.7.1.gz", + "/usr/lib64/python2.7/distutils/command/install_egg_info.py", + "/usr/lib64/python2.7/distutils/command/install_egg_info.pyc", + "/usr/lib64/python2.7/distutils/command/install_egg_info.pyo", + "/usr/lib64/python2.7/lib-dynload/Python-2.7.5-py2.7.egg-info", + "usr/lib64/python2.7/wsgiref.egg-info", // without the leading slash + }, + }, + blob: &types.BlobInfo{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/rpm/Packages", + Packages: types.Packages{ + { + Name: "python", + Version: "2.7.5", + Release: "89.el7", + }, + { + Name: "python-libs", + Version: "2.7.5", + Release: "89.el7", + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: types.Pipenv, + FilePath: "app/Pipfile.lock", + Libraries: types.Packages{ + { + Name: "django", + Version: "3.1.2", + }, + }, + }, + { + Type: types.PythonPkg, + Libraries: types.Packages{ + { + Name: "python", + Version: "2.7.5", + FilePath: "usr/lib64/python2.7/lib-dynload/Python-2.7.5-py2.7.egg-info", + }, + { + Name: "pycurl", + Version: "7.19.0", + FilePath: "usr/lib64/python2.7/site-packages/pycurl-7.19.0-py2.7.egg-info", + }, + }, + }, + { + Type: types.PythonPkg, + FilePath: "usr/lib64/python2.7/wsgiref.egg-info", + Libraries: types.Packages{ + { + Name: "wsgiref", + Version: "0.1.2", + }, + }, + }, + { + Type: types.GoBinary, + FilePath: "usr/local/bin/goBinariryFile", + Libraries: types.Packages{ + { + Name: "cloud.google.com/go", + Version: "v0.81.0", + FilePath: "", + }, + }, + }, + }, + CustomResources: []types.CustomResource{ + { + FilePath: "usr/bin/pydoc", + Data: "remove", + }, + { + FilePath: "usr/bin/pydoc/needed", + Data: "shouldNotRemove", + }, + }, + }, + want: &types.BlobInfo{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/rpm/Packages", + Packages: types.Packages{ + { + Name: "python", + Version: "2.7.5", + Release: "89.el7", + }, + { + Name: "python-libs", + Version: "2.7.5", + Release: "89.el7", + }, + }, + }, + }, + Applications: []types.Application{ + { + Type: types.Pipenv, + FilePath: "app/Pipfile.lock", + Libraries: types.Packages{ + { + Name: "django", + Version: "3.1.2", + }, + }, + }, + { + Type: types.PythonPkg, + Libraries: types.Packages{ + { + Name: "pycurl", + Version: "7.19.0", + FilePath: "usr/lib64/python2.7/site-packages/pycurl-7.19.0-py2.7.egg-info", + }, + }, + }, + { + Type: types.GoBinary, + FilePath: "usr/local/bin/goBinariryFile", + Libraries: types.Packages{ + { + Name: "cloud.google.com/go", + Version: "v0.81.0", + }, + }, + }, + }, + CustomResources: []types.CustomResource{ + { + FilePath: "usr/bin/pydoc/needed", + Data: "shouldNotRemove", + Layer: types.Layer{}, + }, + }, + }, + }, + { + name: "distroless", + result: &analyzer.AnalysisResult{}, + blob: &types.BlobInfo{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "usr/lib/python2.7/lib-dynload/Python-2.7.egg-info", + Libraries: types.Packages{ + { + Name: "python", + Version: "2.7.14", + FilePath: "usr/lib/python2.7/lib-dynload/Python-2.7.egg-info", + }, + }, + }, + }, + }, + want: &types.BlobInfo{}, + }, + { + name: "go binaries", + result: &analyzer.AnalysisResult{ + SystemInstalledFiles: []string{ + "usr/local/bin/goreleaser", + }, + }, + blob: &types.BlobInfo{ + Applications: []types.Application{ + { + Type: types.GoBinary, + FilePath: "usr/local/bin/goreleaser", + Libraries: types.Packages{ + { + Name: "github.com/sassoftware/go-rpmutils", + Version: "v0.0.0-20190420191620-a8f1baeba37b", + }, + }, + }, + }, + }, + want: &types.BlobInfo{}, + }, + { + name: "Rust will not be skipped", + result: &analyzer.AnalysisResult{ + SystemInstalledFiles: []string{ + "app/Cargo.lock", + }, + }, + blob: &types.BlobInfo{ + Applications: []types.Application{ + { + Type: types.Cargo, + FilePath: "app/Cargo.lock", + Libraries: types.Packages{ + { + Name: "ghash", + Version: "0.4.4", + }, + }, + }, + }, + }, + want: &types.BlobInfo{ + Applications: []types.Application{ + { + Type: types.Cargo, + FilePath: "app/Cargo.lock", + Libraries: types.Packages{ + { + Name: "ghash", + Version: "0.4.4", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := systemFileFilteringPostHandler{} + err := h.Handle(context.TODO(), tt.result, tt.blob) + require.NoError(t, err) + assert.Equal(t, tt.want, tt.blob) + }) + } +} diff --git a/pkg/fanal/handler/unpackaged/unpackaged.go b/pkg/fanal/handler/unpackaged/unpackaged.go new file mode 100644 index 000000000000..5f450c7923cb --- /dev/null +++ b/pkg/fanal/handler/unpackaged/unpackaged.go @@ -0,0 +1,92 @@ +package unpackaged + +import ( + "bytes" + "context" + "errors" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + sbomatt "github.com/aquasecurity/trivy/pkg/attestation/sbom" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/handler" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/sbom" +) + +func init() { + handler.RegisterPostHandlerInit(types.UnpackagedPostHandler, NewUnpackagedHandler) +} + +const version = 1 + +type unpackagedHook struct { + client sbomatt.Rekor +} + +func NewUnpackagedHandler(opt artifact.Option) (handler.PostHandler, error) { + c, err := sbomatt.NewRekor(opt.RekorURL) + if err != nil { + return nil, xerrors.Errorf("rekor client error: %w", err) + } + return unpackagedHook{ + client: c, + }, nil +} + +// Handle retrieves SBOM of unpackaged executable files in Rekor. +func (h unpackagedHook) Handle(ctx context.Context, res *analyzer.AnalysisResult, blob *types.BlobInfo) error { + for filePath, digest := range res.Digests { + // Skip files installed by OS package managers. + if slices.Contains(res.SystemInstalledFiles, filePath) { + continue + } + + // Retrieve SBOM from Rekor according to the file digest. + raw, err := h.client.RetrieveSBOM(ctx, digest) + if errors.Is(err, sbomatt.ErrNoSBOMAttestation) { + continue + } else if err != nil { + return err + } + + r := bytes.NewReader(raw) + + // Detect the SBOM format like CycloneDX, SPDX, etc. + format, err := sbom.DetectFormat(r) + if err != nil { + return err + } + + // Parse the fetched SBOM + bom, err := sbom.Decode(bytes.NewReader(raw), format) + if err != nil { + return err + } + + if len(bom.Applications) > 0 { + log.Logger.Infof("Found SBOM attestation in Rekor: %s", filePath) + // Take the first app since this SBOM should contain a single application. + app := bom.Applications[0] + app.FilePath = filePath // Use the original file path rather than the one in the SBOM. + blob.Applications = append(blob.Applications, app) + } + } + + return nil +} + +func (h unpackagedHook) Version() int { + return version +} + +func (h unpackagedHook) Type() types.HandlerType { + return types.UnpackagedPostHandler +} + +func (h unpackagedHook) Priority() int { + return types.UnpackagedPostHandlerPriority +} diff --git a/pkg/fanal/handler/unpackaged/unpackaged_test.go b/pkg/fanal/handler/unpackaged/unpackaged_test.go new file mode 100644 index 000000000000..b466aa83282a --- /dev/null +++ b/pkg/fanal/handler/unpackaged/unpackaged_test.go @@ -0,0 +1,101 @@ +package unpackaged_test + +import ( + "context" + "github.com/package-url/packageurl-go" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/handler/unpackaged" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/rekortest" +) + +func Test_unpackagedHook_Handle(t *testing.T) { + type args struct { + res *analyzer.AnalysisResult + blob *types.BlobInfo + } + tests := []struct { + name string + args args + want *types.BlobInfo + wantErr string + }{ + { + name: "happy path", + args: args{ + res: &analyzer.AnalysisResult{ + Digests: map[string]string{ + "go.mod": "sha256:23f4e10c43c7654e33a3c9570913c8c9c528292762f1a5c4a97253e9e4e4b238", + }, + }, + }, + want: &types.BlobInfo{ + Applications: []types.Application{ + { + Type: types.GoModule, + FilePath: "go.mod", + Libraries: types.Packages{ + { + ID: "github.com/spf13/cobra@v1.5.0", + Name: "github.com/spf13/cobra", + Version: "1.5.0", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/spf13", + Name: "cobra", + Version: "1.5.0", + }, + BOMRef: "pkg:golang/github.com/spf13/cobra@1.5.0", + }, + }, + }, + }, + }, + }, + }, + { + name: "404", + args: args{ + res: &analyzer.AnalysisResult{ + Digests: map[string]string{ + "go.mod": "sha256:unknown", + }, + }, + }, + wantErr: "failed to search", + }, + } + + require.NoError(t, log.InitLogger(false, true)) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := rekortest.NewServer(t) + defer ts.Close() + + // Set the testing URL + opt := artifact.Option{ + RekorURL: ts.URL(), + } + + got := &types.BlobInfo{} + h, err := unpackaged.NewUnpackagedHandler(opt) + require.NoError(t, err) + + err = h.Handle(context.Background(), tt.args.res, got) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err, tt.name) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/image/archive.go b/pkg/fanal/image/archive.go new file mode 100644 index 000000000000..2b81e92d0d68 --- /dev/null +++ b/pkg/fanal/image/archive.go @@ -0,0 +1,64 @@ +package image + +import ( + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/hashicorp/go-multierror" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func NewArchiveImage(fileName string) (types.Image, error) { + img, err := newImage(fileName) + if err != nil { + return nil, err + } + return archiveImage{ + name: fileName, + Image: img, + }, nil +} + +func newImage(fileName string) (v1.Image, error) { + var errs error + + // Docker archive + img, err := tryDockerArchive(fileName) + if err == nil { + // Return v1.Image if the file can be opened as Docker archive + return img, nil + } + errs = multierror.Append(errs, err) + + // OCI layout + img, err = tryOCI(fileName) + if err == nil { + // Return v1.Image if the directory can be opened as OCI Image Format + return img, nil + } + errs = multierror.Append(errs, err) + + return nil, errs +} + +type archiveImage struct { + v1.Image + name string +} + +func (img archiveImage) Name() string { + return img.name +} + +func (img archiveImage) ID() (string, error) { + return ID(img) +} + +// RepoTags returns empty as an archive doesn't support RepoTags +func (archiveImage) RepoTags() []string { + return nil +} + +// RepoDigests returns empty as an archive doesn't support RepoDigests +func (archiveImage) RepoDigests() []string { + return nil +} diff --git a/pkg/fanal/image/daemon.go b/pkg/fanal/image/daemon.go new file mode 100644 index 000000000000..6b9be09c4437 --- /dev/null +++ b/pkg/fanal/image/daemon.go @@ -0,0 +1,58 @@ +package image + +import ( + "context" + + "github.com/google/go-containerregistry/pkg/name" + + "github.com/aquasecurity/trivy/pkg/fanal/image/daemon" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func tryDockerDaemon(_ context.Context, imageName string, ref name.Reference, opt types.ImageOptions) (types.Image, func(), error) { + img, cleanup, err := daemon.DockerImage(ref, opt.DockerOptions.Host) + if err != nil { + return nil, nil, err + } + return daemonImage{ + Image: img, + name: imageName, + }, cleanup, nil + +} + +func tryPodmanDaemon(_ context.Context, imageName string, _ name.Reference, opts types.ImageOptions) (types.Image, func(), error) { + img, cleanup, err := daemon.PodmanImage(imageName, opts.PodmanOptions.Host) + if err != nil { + return nil, nil, err + } + return daemonImage{ + Image: img, + name: imageName, + }, cleanup, nil +} + +func tryContainerdDaemon(ctx context.Context, imageName string, _ name.Reference, opts types.ImageOptions) (types.Image, func(), error) { + img, cleanup, err := daemon.ContainerdImage(ctx, imageName, opts) + if err != nil { + return nil, cleanup, err + } + + return daemonImage{ + Image: img, + name: imageName, + }, cleanup, nil +} + +type daemonImage struct { + daemon.Image + name string +} + +func (d daemonImage) Name() string { + return d.name +} + +func (d daemonImage) ID() (string, error) { + return ID(d) +} diff --git a/pkg/fanal/image/daemon/containerd.go b/pkg/fanal/image/daemon/containerd.go new file mode 100644 index 000000000000..109081d5bae5 --- /dev/null +++ b/pkg/fanal/image/daemon/containerd.go @@ -0,0 +1,286 @@ +package daemon + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/images/archive" + "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/platforms" + refdocker "github.com/containerd/containerd/reference/docker" + api "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/go-connections/nat" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +const ( + defaultContainerdSocket = "/run/containerd/containerd.sock" + defaultContainerdNamespace = "default" +) + +type familiarNamed string + +func (n familiarNamed) Name() string { + return strings.Split(string(n), ":")[0] +} + +func (n familiarNamed) Tag() string { + s := strings.Split(string(n), ":") + if len(s) < 2 { + return "" + } + + return s[1] +} + +func (n familiarNamed) String() string { + return string(n) +} + +func imageWriter(client *containerd.Client, img containerd.Image, platform types.Platform) imageSave { + return func(ctx context.Context, ref []string) (io.ReadCloser, error) { + if len(ref) < 1 { + return nil, xerrors.New("no image reference") + } + imgOpts := archive.WithImage(client.ImageService(), ref[0]) + manifestOpts := archive.WithManifest(img.Target()) + + var platformMatchComparer platforms.MatchComparer + if platform.Platform == nil { + platformMatchComparer = platforms.DefaultStrict() + } else { + platformMatchComparer = img.Platform() + } + platOpts := archive.WithPlatform(platformMatchComparer) + pr, pw := io.Pipe() + go func() { + pw.CloseWithError(archive.Export(ctx, client.ContentStore(), pw, imgOpts, manifestOpts, platOpts)) + }() + return pr, nil + } +} + +// ContainerdImage implements v1.Image +func ContainerdImage(ctx context.Context, imageName string, opts types.ImageOptions) (Image, func(), error) { + cleanup := func() {} + + addr := os.Getenv("CONTAINERD_ADDRESS") + if addr == "" { + // TODO: support rootless + addr = defaultContainerdSocket + } + + if _, err := os.Stat(addr); errors.Is(err, os.ErrNotExist) { + return nil, cleanup, xerrors.Errorf("containerd socket not found: %s", addr) + } + + ref, searchFilters, err := parseReference(imageName) + if err != nil { + return nil, cleanup, err + } + + var options []containerd.ClientOpt + if opts.RegistryOptions.Platform.Platform != nil { + ociPlatform, err := platforms.Parse(opts.RegistryOptions.Platform.String()) + if err != nil { + return nil, cleanup, err + } + + options = append(options, containerd.WithDefaultPlatform(platforms.OnlyStrict(ociPlatform))) + } + + client, err := containerd.New(addr, options...) + if err != nil { + return nil, cleanup, xerrors.Errorf("failed to initialize a containerd client: %w", err) + } + + namespace := os.Getenv("CONTAINERD_NAMESPACE") + if namespace == "" { + namespace = defaultContainerdNamespace + } + + ctx = namespaces.WithNamespace(ctx, namespace) + + imgs, err := client.ListImages(ctx, searchFilters...) + if err != nil { + return nil, cleanup, xerrors.Errorf("failed to list images from containerd client: %w", err) + } + + if len(imgs) < 1 { + return nil, cleanup, xerrors.Errorf("image not found in containerd store: %s", imageName) + } + + img := imgs[0] + + f, err := os.CreateTemp("", "fanal-containerd-*") + if err != nil { + return nil, cleanup, xerrors.Errorf("failed to create a temporary file: %w", err) + } + + cleanup = func() { + _ = client.Close() + _ = f.Close() + _ = os.Remove(f.Name()) + } + + insp, history, ref, err := inspect(ctx, img, ref) + if err != nil { + return nil, cleanup, xerrors.Errorf("inspect error: %w", err) + } + + return &image{ + opener: imageOpener(ctx, ref.String(), f, imageWriter(client, img, opts.RegistryOptions.Platform)), + inspect: insp, + history: history, + }, cleanup, nil +} + +func parseReference(imageName string) (refdocker.Reference, []string, error) { + ref, err := refdocker.ParseAnyReference(imageName) + if err != nil { + return nil, nil, xerrors.Errorf("parse error: %w", err) + } + + d, isDigested := ref.(refdocker.Digested) + n, isNamed := ref.(refdocker.Named) + nt, isNamedAndTagged := ref.(refdocker.NamedTagged) + + // a name plus a digest + // example: name@sha256:41adb3ef... + if isDigested && isNamed { + dgst := d.Digest() + // for the filters, each slice entry is logically or'd. each + // comma-separated filter is logically anded + return ref, []string{ + fmt.Sprintf(`name~="^%s(:|@).*",target.digest==%q`, n.Name(), dgst), + fmt.Sprintf(`name~="^%s(:|@).*",target.digest==%q`, refdocker.FamiliarName(n), dgst), + }, nil + } + + // digested, but not named. i.e. a plain digest + // example: sha256:41adb3ef... + if isDigested { + return ref, []string{fmt.Sprintf(`target.digest==%q`, d.Digest())}, nil + } + + // a name plus a tag + // example: name:tag + if isNamedAndTagged { + tag := nt.Tag() + return familiarNamed(imageName), []string{ + fmt.Sprintf(`name=="%s:%s"`, nt.Name(), tag), + fmt.Sprintf(`name=="%s:%s"`, refdocker.FamiliarName(nt), tag), + }, nil + } + + return nil, nil, xerrors.Errorf("failed to parse image reference: %s", imageName) +} + +// readImageConfig reads the config spec (`application/vnd.oci.image.config.v1+json`) for img.platform from content store. +// ported from https://github.com/containerd/nerdctl/blob/7dfbaa2122628921febeb097e7a8a86074dc931d/pkg/imgutil/imgutil.go#L377-L393 +func readImageConfig(ctx context.Context, img containerd.Image) (ocispec.Image, ocispec.Descriptor, error) { + var config ocispec.Image + + configDesc, err := img.Config(ctx) // aware of img.platform + if err != nil { + return config, configDesc, err + } + p, err := content.ReadBlob(ctx, img.ContentStore(), configDesc) + if err != nil { + return config, configDesc, err + } + if err = json.Unmarshal(p, &config); err != nil { + return config, configDesc, err + } + return config, configDesc, nil +} + +// ported from https://github.com/containerd/nerdctl/blob/d110fea18018f13c3f798fa6565e482f3ff03591/pkg/inspecttypes/dockercompat/dockercompat.go#L279-L321 +func inspect(ctx context.Context, img containerd.Image, ref refdocker.Reference) (api.ImageInspect, []v1.History, refdocker.Reference, error) { + if _, ok := ref.(refdocker.Digested); ok { + ref = familiarNamed(img.Name()) + } + + var tag string + if tagged, ok := ref.(refdocker.Tagged); ok { + tag = tagged.Tag() + } + + var repository string + if n, isNamed := ref.(refdocker.Named); isNamed { + repository = refdocker.FamiliarName(n) + } + + imgConfig, imgConfigDesc, err := readImageConfig(ctx, img) + if err != nil { + return api.ImageInspect{}, nil, nil, err + } + + var lastHistory ocispec.History + if len(imgConfig.History) > 0 { + lastHistory = imgConfig.History[len(imgConfig.History)-1] + } + + var history []v1.History + for _, h := range imgConfig.History { + history = append(history, v1.History{ + Author: h.Author, + Created: v1.Time{Time: *h.Created}, + CreatedBy: h.CreatedBy, + Comment: h.Comment, + EmptyLayer: h.EmptyLayer, + }) + } + + portSet := make(nat.PortSet) + for k := range imgConfig.Config.ExposedPorts { + portSet[nat.Port(k)] = struct{}{} + } + + created := "" + if lastHistory.Created != nil { + created = lastHistory.Created.Format(time.RFC3339Nano) + } + + return api.ImageInspect{ + ID: imgConfigDesc.Digest.String(), + RepoTags: []string{fmt.Sprintf("%s:%s", repository, tag)}, + RepoDigests: []string{fmt.Sprintf("%s@%s", repository, img.Target().Digest)}, + Comment: lastHistory.Comment, + Created: created, + Author: lastHistory.Author, + Config: &container.Config{ + User: imgConfig.Config.User, + ExposedPorts: portSet, + Env: imgConfig.Config.Env, + Cmd: imgConfig.Config.Cmd, + Volumes: imgConfig.Config.Volumes, + WorkingDir: imgConfig.Config.WorkingDir, + Entrypoint: imgConfig.Config.Entrypoint, + Labels: imgConfig.Config.Labels, + }, + Architecture: imgConfig.Architecture, + Os: imgConfig.OS, + RootFS: api.RootFS{ + Type: imgConfig.RootFS.Type, + Layers: lo.Map(imgConfig.RootFS.DiffIDs, func(d digest.Digest, _ int) string { + return d.String() + }), + }, + }, history, ref, nil +} diff --git a/pkg/fanal/image/daemon/docker.go b/pkg/fanal/image/daemon/docker.go new file mode 100644 index 000000000000..45581c99b3c8 --- /dev/null +++ b/pkg/fanal/image/daemon/docker.go @@ -0,0 +1,70 @@ +package daemon + +import ( + "context" + "os" + + "github.com/docker/docker/client" + "github.com/google/go-containerregistry/pkg/name" + "golang.org/x/xerrors" +) + +// DockerImage implements v1.Image by extending daemon.Image. +// The caller must call cleanup() to remove a temporary file. +func DockerImage(ref name.Reference, host string) (Image, func(), error) { + cleanup := func() {} + + opts := []client.Opt{ + client.FromEnv, + client.WithAPIVersionNegotiation(), + } + if host != "" { + // adding host parameter to the last assuming it will pick up more preference + opts = append(opts, client.WithHost(host)) + } + c, err := client.NewClientWithOpts(opts...) + + if err != nil { + return nil, cleanup, xerrors.Errorf("failed to initialize a docker client: %w", err) + } + defer func() { + if err != nil { + _ = c.Close() + } + }() + + // : pattern like "alpine:3.15" + // or + // @ pattern like "alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300" + imageID := ref.Name() + inspect, _, err := c.ImageInspectWithRaw(context.Background(), imageID) + if err != nil { + imageID = ref.String() // pattern like `5ac716b05a9c` + inspect, _, err = c.ImageInspectWithRaw(context.Background(), imageID) + if err != nil { + return nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", imageID, err) + } + } + + history, err := c.ImageHistory(context.Background(), imageID) + if err != nil { + return nil, cleanup, xerrors.Errorf("unable to get history (%s): %w", imageID, err) + } + + f, err := os.CreateTemp("", "fanal-*") + if err != nil { + return nil, cleanup, xerrors.Errorf("failed to create a temporary file: %w", err) + } + + cleanup = func() { + _ = c.Close() + _ = f.Close() + _ = os.Remove(f.Name()) + } + + return &image{ + opener: imageOpener(context.Background(), imageID, f, c.ImageSave), + inspect: inspect, + history: configHistory(history), + }, cleanup, nil +} diff --git a/pkg/fanal/image/daemon/docker_test.go b/pkg/fanal/image/daemon/docker_test.go new file mode 100644 index 000000000000..3ae519bdf938 --- /dev/null +++ b/pkg/fanal/image/daemon/docker_test.go @@ -0,0 +1,51 @@ +package daemon + +import ( + "testing" + + "github.com/docker/docker/api/types" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDockerImage(t *testing.T) { + type fields struct { + Image v1.Image + opener opener + inspect types.ImageInspect + } + tests := []struct { + name string + imageName string + fields fields + want *v1.ConfigFile + wantErr bool + }{ + { + name: "happy path", + imageName: "alpine:3.11", + wantErr: false, + }, + { + name: "unknown image", + imageName: "alpine:unknown", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ref, err := name.ParseReference(tt.imageName) + require.NoError(t, err) + + _, cleanup, err := DockerImage(ref, "") + assert.Equal(t, tt.wantErr, err != nil, err) + defer func() { + if cleanup != nil { + cleanup() + } + }() + }) + } +} diff --git a/pkg/fanal/image/daemon/image.go b/pkg/fanal/image/daemon/image.go new file mode 100644 index 000000000000..23adf966f84b --- /dev/null +++ b/pkg/fanal/image/daemon/image.go @@ -0,0 +1,285 @@ +package daemon + +import ( + "context" + "io" + "os" + "strings" + "sync" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + dimage "github.com/docker/docker/api/types/image" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" +) + +type Image interface { + v1.Image + RepoTags() []string + RepoDigests() []string +} + +var mu sync.Mutex + +type opener func() (v1.Image, error) + +type imageSave func(context.Context, []string) (io.ReadCloser, error) + +func imageOpener(ctx context.Context, ref string, f *os.File, imageSave imageSave) opener { + return func() (v1.Image, error) { + // Store the tarball in local filesystem and return a new reader into the bytes each time we need to access something. + rc, err := imageSave(ctx, []string{ref}) + if err != nil { + return nil, xerrors.Errorf("unable to export the image: %w", err) + } + defer rc.Close() + + if _, err = io.Copy(f, rc); err != nil { + return nil, xerrors.Errorf("failed to copy the image: %w", err) + } + defer f.Close() + + img, err := tarball.ImageFromPath(f.Name(), nil) + if err != nil { + return nil, xerrors.Errorf("failed to initialize the struct from the temporary file: %w", err) + } + + return img, nil + } +} + +// image is a wrapper for github.com/google/go-containerregistry/pkg/v1/daemon.Image +// daemon.Image loads the entire image into the memory at first, +// but it doesn't need to load it if the information is already in the cache, +// To avoid entire loading, this wrapper uses ImageInspectWithRaw and checks image ID and layer IDs. +type image struct { + v1.Image + opener opener + inspect types.ImageInspect + history []v1.History +} + +// populateImage initializes an "image" struct. +// This method is called by some goroutines at the same time. +// To prevent multiple heavy initializations, the lock is necessary. +func (img *image) populateImage() (err error) { + mu.Lock() + defer mu.Unlock() + + // img.Image is already initialized, so we don't have to do it again. + if img.Image != nil { + return nil + } + + img.Image, err = img.opener() + if err != nil { + return xerrors.Errorf("unable to open: %w", err) + } + + return nil +} + +func (img *image) ConfigName() (v1.Hash, error) { + return v1.NewHash(img.inspect.ID) +} + +func (img *image) ConfigFile() (*v1.ConfigFile, error) { + if len(img.inspect.RootFS.Layers) == 0 { + // Podman doesn't return RootFS... + return img.configFile() + } + + nonEmptyLayerCount := lo.CountBy(img.history, func(history v1.History) bool { + return !history.EmptyLayer + }) + + if len(img.inspect.RootFS.Layers) != nonEmptyLayerCount { + // In cases where empty layers are not correctly determined from the history API. + // There are some edge cases where we cannot guess empty layers well. + return img.configFile() + } + + diffIDs, err := img.diffIDs() + if err != nil { + return nil, xerrors.Errorf("unable to get diff IDs: %w", err) + } + + created, err := time.Parse(time.RFC3339Nano, img.inspect.Created) + if err != nil { + return nil, xerrors.Errorf("failed parsing created %s: %w", img.inspect.Created, err) + } + + return &v1.ConfigFile{ + Architecture: img.inspect.Architecture, + Author: img.inspect.Author, + Container: img.inspect.Container, + Created: v1.Time{Time: created}, + DockerVersion: img.inspect.DockerVersion, + Config: img.imageConfig(img.inspect.Config), + History: img.history, + OS: img.inspect.Os, + RootFS: v1.RootFS{ + Type: img.inspect.RootFS.Type, + DiffIDs: diffIDs, + }, + }, nil +} + +func (img *image) configFile() (*v1.ConfigFile, error) { + log.Logger.Debug("Saving the container image to a local file to obtain the image config...") + + // Need to fall back into expensive operations like "docker save" + // because the config file cannot be generated properly from container engine API for some reason. + if err := img.populateImage(); err != nil { + return nil, xerrors.Errorf("unable to populate: %w", err) + } + return img.Image.ConfigFile() +} + +func (img *image) LayerByDiffID(h v1.Hash) (v1.Layer, error) { + if err := img.populateImage(); err != nil { + return nil, xerrors.Errorf("unable to populate: %w", err) + } + return img.Image.LayerByDiffID(h) +} + +func (img *image) RawConfigFile() ([]byte, error) { + if err := img.populateImage(); err != nil { + return nil, xerrors.Errorf("unable to populate: %w", err) + } + return img.Image.RawConfigFile() +} + +func (img *image) RepoTags() []string { + return img.inspect.RepoTags +} + +func (img *image) RepoDigests() []string { + return img.inspect.RepoDigests +} + +func (img *image) diffIDs() ([]v1.Hash, error) { + var diffIDs []v1.Hash + for _, l := range img.inspect.RootFS.Layers { + h, err := v1.NewHash(l) + if err != nil { + return nil, xerrors.Errorf("invalid hash %s: %w", l, err) + } + diffIDs = append(diffIDs, h) + } + return diffIDs, nil +} + +func (img *image) imageConfig(config *container.Config) v1.Config { + if config == nil { + return v1.Config{} + } + + c := v1.Config{ + AttachStderr: config.AttachStderr, + AttachStdin: config.AttachStdin, + AttachStdout: config.AttachStdout, + Cmd: config.Cmd, + Domainname: config.Domainname, + Entrypoint: config.Entrypoint, + Env: config.Env, + Hostname: config.Hostname, + Image: config.Image, + Labels: config.Labels, + OnBuild: config.OnBuild, + OpenStdin: config.OpenStdin, + StdinOnce: config.StdinOnce, + Tty: config.Tty, + User: config.User, + Volumes: config.Volumes, + WorkingDir: config.WorkingDir, + ArgsEscaped: config.ArgsEscaped, + NetworkDisabled: config.NetworkDisabled, + MacAddress: config.MacAddress, + StopSignal: config.StopSignal, + Shell: config.Shell, + } + + if config.Healthcheck != nil { + c.Healthcheck = &v1.HealthConfig{ + Test: config.Healthcheck.Test, + Interval: config.Healthcheck.Interval, + Timeout: config.Healthcheck.Timeout, + StartPeriod: config.Healthcheck.StartPeriod, + Retries: config.Healthcheck.Retries, + } + } + + if len(config.ExposedPorts) > 0 { + c.ExposedPorts = make(map[string]struct{}) + for port := range c.ExposedPorts { + c.ExposedPorts[port] = struct{}{} + } + } + + return c +} + +func configHistory(dhistory []dimage.HistoryResponseItem) []v1.History { + // Fill only required metadata + var history []v1.History + + for i := len(dhistory) - 1; i >= 0; i-- { + h := dhistory[i] + history = append(history, v1.History{ + Created: v1.Time{ + Time: time.Unix(h.Created, 0).UTC(), + }, + CreatedBy: h.CreatedBy, + Comment: h.Comment, + EmptyLayer: emptyLayer(h), + }) + } + return history +} + +// emptyLayer tries to determine if the layer is empty from the history API, but may return a wrong result. +// The non-empty layers will be compared to diffIDs later so that results can be validated. +func emptyLayer(history dimage.HistoryResponseItem) bool { + if history.Size != 0 { + return false + } + createdBy := strings.TrimSpace(strings.TrimLeft(history.CreatedBy, "/bin/sh -c #(nop)")) + // This logic is taken from https://github.com/moby/buildkit/blob/2942d13ff489a2a49082c99e6104517e357e53ad/frontend/dockerfile/dockerfile2llb/convert.go + if strings.HasPrefix(createdBy, "ENV") || + strings.HasPrefix(createdBy, "MAINTAINER") || + strings.HasPrefix(createdBy, "LABEL") || + strings.HasPrefix(createdBy, "CMD") || + strings.HasPrefix(createdBy, "ENTRYPOINT") || + strings.HasPrefix(createdBy, "HEALTHCHECK") || + strings.HasPrefix(createdBy, "EXPOSE") || + strings.HasPrefix(createdBy, "USER") || + strings.HasPrefix(createdBy, "VOLUME") || + strings.HasPrefix(createdBy, "STOPSIGNAL") || + strings.HasPrefix(createdBy, "SHELL") || + strings.HasPrefix(createdBy, "ARG") { + return true + } + // buildkit layers with "WORKDIR /" command are empty, + if strings.HasPrefix(history.Comment, "buildkit.dockerfile") { + if createdBy == "WORKDIR /" { + return true + } + } else if strings.HasPrefix(createdBy, "WORKDIR") { // layers build with docker and podman, WORKDIR command is always empty layer. + return true + } + // The following instructions could reach here: + // - "ADD" + // - "COPY" + // - "RUN" + // - "RUN" may not include even 'RUN' prefix + // e.g. '/bin/sh -c mkdir test ' + // - "WORKDIR", which doesn't meet the above conditions + return false +} diff --git a/pkg/fanal/image/daemon/image_test.go b/pkg/fanal/image/daemon/image_test.go new file mode 100644 index 000000000000..a8462200e479 --- /dev/null +++ b/pkg/fanal/image/daemon/image_test.go @@ -0,0 +1,424 @@ +package daemon + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" + "time" + + dimage "github.com/docker/docker/api/types/image" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/testdocker/engine" +) + +var imagePaths = map[string]string{ + "alpine:3.10": "../../test/testdata/alpine-310.tar.gz", + "alpine:3.11": "../../test/testdata/alpine-311.tar.gz", + "gcr.io/distroless/base": "../../test/testdata/distroless.tar.gz", +} + +// for Docker +var opt = engine.Option{ + APIVersion: "1.38", + ImagePaths: imagePaths, +} + +func TestMain(m *testing.M) { + te := engine.NewDockerEngine(opt) + defer te.Close() + + os.Setenv("DOCKER_HOST", fmt.Sprintf("tcp://%s", te.Listener.Addr().String())) + + os.Exit(m.Run()) +} + +func Test_image_ConfigName(t *testing.T) { + tests := []struct { + name string + imageName string + want v1.Hash + wantErr bool + }{ + { + name: "happy path", + imageName: "alpine:3.11", + want: v1.Hash{ + Algorithm: "sha256", + Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ref, err := name.ParseReference(tt.imageName) + require.NoError(t, err) + + img, cleanup, err := DockerImage(ref, "") + require.NoError(t, err) + defer cleanup() + + conf, err := img.ConfigName() + assert.Equal(t, tt.want, conf) + assert.Equal(t, tt.wantErr, err != nil) + }) + } +} + +func Test_image_ConfigNameWithCustomDockerHost(t *testing.T) { + + ref, err := name.ParseReference("alpine:3.11") + require.NoError(t, err) + + eo := engine.Option{ + APIVersion: opt.APIVersion, + ImagePaths: opt.ImagePaths, + } + + var dockerHostParam string + + if runtime.GOOS != "windows" { + runtimeDir, err := os.MkdirTemp("", "daemon") + require.NoError(t, err) + + dir := filepath.Join(runtimeDir, "image") + err = os.MkdirAll(dir, os.ModePerm) + require.NoError(t, err) + + customDockerHost := filepath.Join(dir, "image-test-unix-socket.sock") + eo.UnixDomainSocket = customDockerHost + dockerHostParam = "unix://" + customDockerHost + } + + te := engine.NewDockerEngine(eo) + defer te.Close() + + if runtime.GOOS == "windows" { + dockerHostParam = te.Listener.Addr().Network() + "://" + te.Listener.Addr().String() + } + + img, cleanup, err := DockerImage(ref, dockerHostParam) + require.NoError(t, err) + defer cleanup() + + conf, err := img.ConfigName() + assert.Equal(t, v1.Hash{ + Algorithm: "sha256", + Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, conf) + assert.Nil(t, err) +} + +func Test_image_ConfigNameWithCustomPodmanHost(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("podman.sock is not available for Windows CI") + } + + ref, err := name.ParseReference("alpine:3.11") + require.NoError(t, err) + + eo := engine.Option{ + APIVersion: opt.APIVersion, + ImagePaths: map[string]string{ + "index.docker.io/library/alpine:3.11": "../../test/testdata/alpine-311.tar.gz", + }, + } + + runtimeDir, err := os.MkdirTemp("", "daemon") + require.NoError(t, err) + + dir := filepath.Join(runtimeDir, "image") + err = os.MkdirAll(dir, os.ModePerm) + require.NoError(t, err) + + podmanSocket := filepath.Join(dir, "image-test-podman-socket.sock") + eo.UnixDomainSocket = podmanSocket + + te := engine.NewDockerEngine(eo) + defer te.Close() + + img, cleanup, err := PodmanImage(ref.Name(), podmanSocket) + require.NoError(t, err) + defer cleanup() + + conf, err := img.ConfigName() + assert.Equal(t, v1.Hash{ + Algorithm: "sha256", + Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, conf) + assert.Nil(t, err) +} + +func Test_image_ConfigFile(t *testing.T) { + tests := []struct { + name string + imageName string + want *v1.ConfigFile + wantErr bool + }{ + { + name: "one diff_id", + imageName: "alpine:3.11", + want: &v1.ConfigFile{ + Architecture: "amd64", + Container: "fb71ddde5f6411a82eb056a9190f0cc1c80d7f77a8509ee90a2054428edb0024", + OS: "linux", + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC)}, + DockerVersion: "18.09.7", + History: []v1.History{ + { + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 0, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + Comment: "", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 0, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:0c4555f363c2672e350001f1293e689875a3760afe7b3f9146886afe67121cba in / ", + EmptyLayer: false, + }, + }, + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + }, + }, + }, + Config: v1.Config{ + Cmd: []string{"/bin/sh"}, + Image: "sha256:74df73bb19fbfc7fb5ab9a8234b3d98ee2fb92df5b824496679802685205ab8c", + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + ArgsEscaped: true, + }, + OSVersion: "", + }, + wantErr: false, + }, + { + name: "multiple diff_ids", + imageName: "gcr.io/distroless/base", + want: &v1.ConfigFile{ + Architecture: "amd64", + OS: "linux", + Author: "Bazel", + Created: v1.Time{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, + History: []v1.History{ + { + Created: v1.Time{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, + CreatedBy: "bazel build ...", + EmptyLayer: false, + }, + { + Created: v1.Time{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, + CreatedBy: "bazel build ...", + EmptyLayer: false, + }, + }, + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + {Algorithm: "sha256", Hex: "42a3027eaac150d2b8f516100921f4bd83b3dbc20bfe64124f686c072b49c602"}, + {Algorithm: "sha256", Hex: "f47163e8de57e3e3ccfe89d5dfbd9c252d9eca53dc7906b8db60eddcb876c592"}, + }, + }, + Config: v1.Config{Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt"}}, + OSVersion: "", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ref, err := name.ParseReference(tt.imageName) + require.NoError(t, err) + + img, cleanup, err := DockerImage(ref, "") + require.NoError(t, err) + defer cleanup() + + conf, err := img.ConfigFile() + require.Equal(t, tt.wantErr, err != nil, err) + assert.Equal(t, tt.want, conf) + }) + } +} + +func Test_image_LayerByDiffID(t *testing.T) { + type args struct { + h v1.Hash + } + tests := []struct { + name string + imageName string + args args + wantErr bool + }{ + { + name: "happy path", + imageName: "alpine:3.10", + args: args{h: v1.Hash{ + Algorithm: "sha256", + Hex: "531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }}, + wantErr: false, + }, + { + name: "ImageSave returns 404", + imageName: "alpine:3.11", + args: args{h: v1.Hash{ + Algorithm: "sha256", + Hex: "531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ref, err := name.ParseReference(tt.imageName) + require.NoError(t, err) + + img, cleanup, err := DockerImage(ref, "") + require.NoError(t, err) + defer cleanup() + + _, err = img.LayerByDiffID(tt.args.h) + assert.Equal(t, tt.wantErr, err != nil, err) + }) + } +} + +func Test_image_RawConfigFile(t *testing.T) { + tests := []struct { + name string + imageName string + goldenFile string + wantErr bool + }{ + { + name: "happy path", + imageName: "alpine:3.10", + goldenFile: "testdata/golden/config-alpine310.json", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ref, err := name.ParseReference(tt.imageName) + require.NoError(t, err) + + img, cleanup, err := DockerImage(ref, "") + require.NoError(t, err) + defer cleanup() + + got, err := img.RawConfigFile() + assert.Equal(t, tt.wantErr, err != nil, err) + + if err != nil { + return + } + + want, err := os.ReadFile(tt.goldenFile) + require.NoError(t, err) + + require.JSONEq(t, string(want), string(got)) + }) + } +} + +func Test_image_emptyLayer(t *testing.T) { + tests := []struct { + name string + history dimage.HistoryResponseItem + want bool + }{ + { + name: "size != 0", + history: dimage.HistoryResponseItem{ + Size: 10, + }, + want: false, + }, + { + name: "ENV", + history: dimage.HistoryResponseItem{ + CreatedBy: "/bin/sh -c #(nop) ENV TESTENV=TEST", + }, + want: true, + }, + { + name: "ENV created with buildkit", + history: dimage.HistoryResponseItem{ + CreatedBy: "ENV BUILDKIT_ENV=TEST", + Comment: "buildkit.dockerfile.v0", + }, + want: true, + }, + { + name: "ENV", + history: dimage.HistoryResponseItem{ + CreatedBy: "/bin/sh -c #(nop) ENV TESTENV=TEST", + }, + want: true, + }, + { + name: "CMD", + history: dimage.HistoryResponseItem{ + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + }, + want: true, + }, + { + name: "WORKDIR == '/'", + history: dimage.HistoryResponseItem{ + CreatedBy: "/bin/sh -c #(nop) WORKDIR /", + }, + want: true, + }, + { + name: "WORKDIR != '/'", + history: dimage.HistoryResponseItem{ + CreatedBy: "/bin/sh -c #(nop) WORKDIR /app", + }, + want: true, + }, + { + name: "WORKDIR =='/' buildkit", + history: dimage.HistoryResponseItem{ + CreatedBy: "/bin/sh -c #(nop) WORKDIR /", + Comment: "buildkit.dockerfile.v0", + }, + want: true, + }, + { + name: "WORKDIR == '/app' buildkit", + history: dimage.HistoryResponseItem{ + CreatedBy: "/bin/sh -c #(nop) WORKDIR /app", + Comment: "buildkit.dockerfile.v0", + }, + want: false, + }, + { + name: "without command", + history: dimage.HistoryResponseItem{ + CreatedBy: "/bin/sh -c mkdir test", + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + empty := emptyLayer(tt.history) + assert.Equal(t, tt.want, empty) + }) + } +} diff --git a/pkg/fanal/image/daemon/podman.go b/pkg/fanal/image/daemon/podman.go new file mode 100644 index 000000000000..51766ae01822 --- /dev/null +++ b/pkg/fanal/image/daemon/podman.go @@ -0,0 +1,147 @@ +package daemon + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "os" + "path/filepath" + + api "github.com/docker/docker/api/types" + dimage "github.com/docker/docker/api/types/image" + "golang.org/x/xerrors" +) + +var ( + inspectURL = "http://podman/images/%s/json" + historyURL = "http://podman/images/%s/history" + saveURL = "http://podman/images/%s/get" +) + +type podmanClient struct { + c http.Client +} + +func newPodmanClient(host string) (podmanClient, error) { + // Get Podman socket location + sockDir := os.Getenv("XDG_RUNTIME_DIR") + socket := filepath.Join(sockDir, "podman", "podman.sock") + if host != "" { + socket = host + } + + if _, err := os.Stat(socket); err != nil { + return podmanClient{}, xerrors.Errorf("no podman socket found: %w", err) + } + + return podmanClient{ + c: http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", socket) + }, + }, + }, + }, nil +} + +type errResponse struct { + Message string +} + +func (p podmanClient) imageInspect(imageName string) (api.ImageInspect, error) { + url := fmt.Sprintf(inspectURL, imageName) + resp, err := p.c.Get(url) + if err != nil { + return api.ImageInspect{}, xerrors.Errorf("http error: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var res errResponse + if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { + return api.ImageInspect{}, xerrors.Errorf("unknown status code from Podman: %d", resp.StatusCode) + } + return api.ImageInspect{}, xerrors.New(res.Message) + } + + var inspect api.ImageInspect + if err = json.NewDecoder(resp.Body).Decode(&inspect); err != nil { + return api.ImageInspect{}, xerrors.Errorf("unable to decode JSON: %w", err) + } + return inspect, nil +} + +func (p podmanClient) imageHistoryInspect(imageName string) ([]dimage.HistoryResponseItem, error) { + url := fmt.Sprintf(historyURL, imageName) + resp, err := p.c.Get(url) + if err != nil { + return []dimage.HistoryResponseItem{}, xerrors.Errorf("http error: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var res errResponse + if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { + return []dimage.HistoryResponseItem{}, xerrors.Errorf("unknown status code from Podman: %d", resp.StatusCode) + } + return []dimage.HistoryResponseItem{}, xerrors.New(res.Message) + } + + var history []dimage.HistoryResponseItem + if err = json.NewDecoder(resp.Body).Decode(&history); err != nil { + return []dimage.HistoryResponseItem{}, xerrors.Errorf("unable to decode JSON: %w", err) + } + return history, nil +} + +func (p podmanClient) imageSave(_ context.Context, imageNames []string) (io.ReadCloser, error) { + if len(imageNames) < 1 { + return nil, xerrors.Errorf("no specified image") + } + url := fmt.Sprintf(saveURL, imageNames[0]) + resp, err := p.c.Get(url) + if err != nil { + return nil, xerrors.Errorf("http error: %w", err) + } + return resp.Body, nil +} + +// PodmanImage implements v1.Image by extending daemon.Image. +// The caller must call cleanup() to remove a temporary file. +func PodmanImage(ref, host string) (Image, func(), error) { + cleanup := func() {} + + c, err := newPodmanClient(host) + if err != nil { + return nil, cleanup, xerrors.Errorf("unable to initialize Podman client: %w", err) + } + inspect, err := c.imageInspect(ref) + if err != nil { + return nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", ref, err) + } + + history, err := c.imageHistoryInspect(ref) + if err != nil { + return nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", ref, err) + } + + f, err := os.CreateTemp("", "fanal-*") + if err != nil { + return nil, cleanup, xerrors.Errorf("failed to create a temporary file: %w", err) + } + + cleanup = func() { + _ = f.Close() + _ = os.Remove(f.Name()) + } + + return &image{ + opener: imageOpener(context.Background(), ref, f, c.imageSave), + inspect: inspect, + history: configHistory(history), + }, cleanup, nil +} diff --git a/pkg/fanal/image/daemon/podman_test.go b/pkg/fanal/image/daemon/podman_test.go new file mode 100644 index 000000000000..d408def16e87 --- /dev/null +++ b/pkg/fanal/image/daemon/podman_test.go @@ -0,0 +1,109 @@ +package daemon + +import ( + "net/http/httptest" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/docker/docker/api/types" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/testdocker/engine" +) + +func setupPodmanSock(t *testing.T) *httptest.Server { + t.Helper() + + runtimeDir, err := os.MkdirTemp("", "daemon") + require.NoError(t, err) + + os.Setenv("XDG_RUNTIME_DIR", runtimeDir) + + dir := filepath.Join(runtimeDir, "podman") + err = os.MkdirAll(dir, os.ModePerm) + require.NoError(t, err) + + sockPath := filepath.Join(dir, "podman.sock") + + opt := engine.Option{ + APIVersion: "1.40", + ImagePaths: map[string]string{ + "index.docker.io/library/alpine:3.11": "../../test/testdata/alpine-311.tar.gz", + }, + UnixDomainSocket: sockPath, + } + te := engine.NewDockerEngine(opt) + return te +} + +func TestPodmanImage(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("podman.sock is not available for Windows CI") + } + + type fields struct { + Image v1.Image + opener opener + inspect types.ImageInspect + } + tests := []struct { + name string + imageName string + fields fields + wantConfigName string + wantCreateBy []string + wantErr bool + }{ + { + name: "happy path", + imageName: "alpine:3.11", + wantConfigName: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + wantCreateBy: []string{ + "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + "/bin/sh -c #(nop) ADD file:0c4555f363c2672e350001f1293e689875a3760afe7b3f9146886afe67121cba in / ", + }, + wantErr: false, + }, + { + name: "unknown image", + imageName: "alpine:unknown", + wantErr: true, + }, + } + + te := setupPodmanSock(t) + defer te.Close() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ref, err := name.ParseReference(tt.imageName) + require.NoError(t, err) + + img, cleanup, err := PodmanImage(ref.Name(), "") + defer cleanup() + + if tt.wantErr { + assert.NotNil(t, err) + return + } + assert.NoError(t, err) + + confName, err := img.ConfigName() + require.NoError(t, err) + assert.Equal(t, tt.wantConfigName, confName.String()) + + confFile, err := img.ConfigFile() + require.NoError(t, err) + + assert.Equal(t, len(confFile.History), len(tt.wantCreateBy)) + for _, h := range confFile.History { + assert.Contains(t, tt.wantCreateBy, h.CreatedBy) + } + }) + } +} diff --git a/pkg/fanal/image/daemon/testdata/golden/config-alpine310.json b/pkg/fanal/image/daemon/testdata/golden/config-alpine310.json new file mode 100644 index 000000000000..6565cd403075 --- /dev/null +++ b/pkg/fanal/image/daemon/testdata/golden/config-alpine310.json @@ -0,0 +1,75 @@ +{ + "architecture": "amd64", + "config": { + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cmd": [ + "/bin/sh" + ], + "ArgsEscaped": true, + "Image": "sha256:7c41e139ba64dd2eba852a2e963ee86f2e8da3a5bbfaf10cf4349535dbf0ff08", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": null, + "OnBuild": null, + "Labels": null + }, + "container": "7f4a36a667d138b079b5ff059485ff65bfbb5ebc48f24a89f983b918e73f4f28", + "container_config": { + "Hostname": "7f4a36a667d1", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cmd": [ + "/bin/sh", + "-c", + "#(nop) ", + "CMD [\"/bin/sh\"]" + ], + "ArgsEscaped": true, + "Image": "sha256:7c41e139ba64dd2eba852a2e963ee86f2e8da3a5bbfaf10cf4349535dbf0ff08", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": null, + "OnBuild": null, + "Labels": {} + }, + "created": "2020-01-23T16:53:06.686519038Z", + "docker_version": "18.06.1-ce", + "history": [ + { + "created": "2020-01-23T16:53:06.551172402Z", + "created_by": "/bin/sh -c #(nop) ADD file:d48cac34fac385cbc1de6adfdd88300f76f9bbe346cd17e64fd834d042a98326 in / " + }, + { + "created": "2020-01-23T16:53:06.686519038Z", + "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028" + ] + } +} diff --git a/pkg/fanal/image/docker.go b/pkg/fanal/image/docker.go new file mode 100644 index 000000000000..54096099761f --- /dev/null +++ b/pkg/fanal/image/docker.go @@ -0,0 +1,43 @@ +package image + +import ( + "bufio" + "compress/gzip" + "io" + "os" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +func tryDockerArchive(fileName string) (v1.Image, error) { + img, err := tarball.Image(fileOpener(fileName), nil) + if err != nil { + return nil, xerrors.Errorf("unable to open %s as a Docker image: %w", fileName, err) + } + return img, nil +} + +func fileOpener(fileName string) func() (io.ReadCloser, error) { + return func() (io.ReadCloser, error) { + f, err := os.Open(fileName) + if err != nil { + return nil, xerrors.Errorf("unable to open the file: %w", err) + } + + var r io.Reader + br := bufio.NewReader(f) + r = br + + if utils.IsGzip(br) { + r, err = gzip.NewReader(br) + if err != nil { + return nil, xerrors.Errorf("failed to open gzip: %w", err) + } + } + return io.NopCloser(r), nil + } +} diff --git a/pkg/fanal/image/image.go b/pkg/fanal/image/image.go new file mode 100644 index 000000000000..fc8e14ab37e1 --- /dev/null +++ b/pkg/fanal/image/image.go @@ -0,0 +1,137 @@ +package image + +import ( + "context" + "fmt" + "strings" + + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + multierror "github.com/hashicorp/go-multierror" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +type imageSourceFunc func(ctx context.Context, imageName string, ref name.Reference, option types.ImageOptions) (types.Image, func(), error) + +var imageSourceFuncs = map[types.ImageSource]imageSourceFunc{ + types.ContainerdImageSource: tryContainerdDaemon, + types.PodmanImageSource: tryPodmanDaemon, + types.DockerImageSource: tryDockerDaemon, + types.RemoteImageSource: tryRemote, +} + +func NewContainerImage(ctx context.Context, imageName string, opt types.ImageOptions) (types.Image, func(), error) { + if len(opt.ImageSources) == 0 { + return nil, func() {}, xerrors.New("no image sources supplied") + } + + var errs error + var nameOpts []name.Option + if opt.RegistryOptions.Insecure { + nameOpts = append(nameOpts, name.Insecure) + } + + ref, err := name.ParseReference(imageName, nameOpts...) + if err != nil { + return nil, func() {}, xerrors.Errorf("failed to parse the image name: %w", err) + } + + for _, src := range opt.ImageSources { + trySrc, ok := imageSourceFuncs[src] + if !ok { + log.Logger.Warnf("Unknown image source: '%s'", src) + continue + } + + img, cleanup, err := trySrc(ctx, imageName, ref, opt) + if err == nil { + // Return v1.Image if the image is found + return img, cleanup, nil + } + err = multierror.Prefix(err, fmt.Sprintf("%s error:", src)) + errs = multierror.Append(errs, err) + } + + return nil, func() {}, errs +} + +func ID(img v1.Image) (string, error) { + h, err := img.ConfigName() + if err != nil { + return "", xerrors.Errorf("unable to get the image ID: %w", err) + } + return h.String(), nil +} + +func LayerIDs(img v1.Image) ([]string, error) { + conf, err := img.ConfigFile() + if err != nil { + return nil, xerrors.Errorf("unable to get the config file: %w", err) + } + + var layerIDs []string + for _, d := range conf.RootFS.DiffIDs { + layerIDs = append(layerIDs, d.String()) + } + return layerIDs, nil +} + +// GuessBaseImageIndex tries to guess index of base layer +// +// e.g. In the following example, we should detect layers in debian:8. +// +// FROM debian:8 +// RUN apt-get update +// COPY mysecret / +// ENTRYPOINT ["entrypoint.sh"] +// CMD ["somecmd"] +// +// debian:8 may be like +// +// ADD file:5d673d25da3a14ce1f6cf66e4c7fd4f4b85a3759a9d93efb3fd9ff852b5b56e4 in / +// CMD ["/bin/sh"] +// +// In total, it would be like: +// +// ADD file:5d673d25da3a14ce1f6cf66e4c7fd4f4b85a3759a9d93efb3fd9ff852b5b56e4 in / +// CMD ["/bin/sh"] # empty layer (detected) +// RUN apt-get update +// COPY mysecret / +// ENTRYPOINT ["entrypoint.sh"] # empty layer (skipped) +// CMD ["somecmd"] # empty layer (skipped) +// +// This method tries to detect CMD in the second line and assume the first line is a base layer. +// 1. Iterate histories from the bottom. +// 2. Skip all the empty layers at the bottom. In the above example, "entrypoint.sh" and "somecmd" will be skipped +// 3. If it finds CMD, it assumes that it is the end of base layers. +// 4. It gets all the layers as base layers above the CMD found in #3. +func GuessBaseImageIndex(histories []v1.History) int { + baseImageIndex := -1 + var foundNonEmpty bool + for i := len(histories) - 1; i >= 0; i-- { + h := histories[i] + + // Skip the last CMD, ENTRYPOINT, etc. + if !foundNonEmpty { + if h.EmptyLayer { + continue + } + foundNonEmpty = true + } + + if !h.EmptyLayer { + continue + } + + // Detect CMD instruction in base image + if strings.HasPrefix(h.CreatedBy, "/bin/sh -c #(nop) CMD") || + strings.HasPrefix(h.CreatedBy, "CMD") { // BuildKit + baseImageIndex = i + break + } + } + return baseImageIndex +} diff --git a/pkg/fanal/image/image_test.go b/pkg/fanal/image/image_test.go new file mode 100644 index 000000000000..551fbd6d682a --- /dev/null +++ b/pkg/fanal/image/image_test.go @@ -0,0 +1,559 @@ +package image + +import ( + "context" + "fmt" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/golang-jwt/jwt" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/testdocker/auth" + "github.com/aquasecurity/testdocker/engine" + "github.com/aquasecurity/testdocker/registry" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func setupEngineAndRegistry() (*httptest.Server, *httptest.Server) { + imagePaths := map[string]string{ + "alpine:3.10": "../test/testdata/alpine-310.tar.gz", + "alpine:3.11": "../test/testdata/alpine-311.tar.gz", + "a187dde48cd2": "../test/testdata/alpine-311.tar.gz", + } + opt := engine.Option{ + APIVersion: "1.38", + ImagePaths: imagePaths, + } + te := engine.NewDockerEngine(opt) + + imagePaths = map[string]string{ + "v2/library/alpine:3.10": "../test/testdata/alpine-310.tar.gz", + } + tr := registry.NewDockerRegistry(registry.Option{ + Images: imagePaths, + Auth: auth.Auth{}, + }) + + os.Setenv("DOCKER_HOST", fmt.Sprintf("tcp://%s", te.Listener.Addr().String())) + + return te, tr +} + +func TestNewDockerImage(t *testing.T) { + te, tr := setupEngineAndRegistry() + defer func() { + te.Close() + tr.Close() + }() + serverAddr := tr.Listener.Addr().String() + + type args struct { + imageName string + option types.ImageOptions + } + tests := []struct { + name string + args args + wantID string + wantConfigFile *v1.ConfigFile + wantRepoTags []string + wantRepoDigests []string + wantErr bool + }{ + { + name: "happy path with Docker Engine (use pattern : for image name)", + args: args{ + imageName: "alpine:3.11", + }, + wantID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + wantRepoTags: []string{"alpine:3.11"}, + wantConfigFile: &v1.ConfigFile{ + Architecture: "amd64", + Container: "fb71ddde5f6411a82eb056a9190f0cc1c80d7f77a8509ee90a2054428edb0024", + OS: "linux", + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC)}, + DockerVersion: "18.09.7", + History: []v1.History{ + { + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 0, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + Comment: "", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 0, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:0c4555f363c2672e350001f1293e689875a3760afe7b3f9146886afe67121cba in / ", + EmptyLayer: false, + }, + }, + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + }, + }, + }, + Config: v1.Config{ + Cmd: []string{"/bin/sh"}, + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + Image: "sha256:74df73bb19fbfc7fb5ab9a8234b3d98ee2fb92df5b824496679802685205ab8c", + ArgsEscaped: true, + }, + OSVersion: "", + }, + }, + { + name: "happy path with Docker Engine (use pattern for image name)", + args: args{ + imageName: "a187dde48cd2", + }, + wantID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + wantRepoTags: []string{"alpine:3.11"}, + wantConfigFile: &v1.ConfigFile{ + Architecture: "amd64", + Container: "fb71ddde5f6411a82eb056a9190f0cc1c80d7f77a8509ee90a2054428edb0024", + OS: "linux", + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC)}, + DockerVersion: "18.09.7", + History: []v1.History{ + { + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 0, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + Comment: "", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 0, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:0c4555f363c2672e350001f1293e689875a3760afe7b3f9146886afe67121cba in / ", + EmptyLayer: false, + }, + }, + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + }, + }, + }, + Config: v1.Config{ + Cmd: []string{"/bin/sh"}, + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + Image: "sha256:74df73bb19fbfc7fb5ab9a8234b3d98ee2fb92df5b824496679802685205ab8c", + ArgsEscaped: true, + }, + OSVersion: "", + }, + }, + { + name: "happy path with Docker Registry", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + }, + wantID: "sha256:af341ccd2df8b0e2d67cf8dd32e087bfda4e5756ebd1c76bbf3efa0dc246590e", + wantRepoTags: []string{serverAddr + "/library/alpine:3.10"}, + wantRepoDigests: []string{ + serverAddr + "/library/alpine@sha256:e10ea963554297215478627d985466ada334ed15c56d3d6bb808ceab98374d91", + }, + wantConfigFile: &v1.ConfigFile{ + Architecture: "amd64", + Container: "7f4a36a667d138b079b5ff059485ff65bfbb5ebc48f24a89f983b918e73f4f28", + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 686519038, time.UTC)}, + DockerVersion: "18.06.1-ce", + History: []v1.History{ + { + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 551172402, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:d48cac34fac385cbc1de6adfdd88300f76f9bbe346cd17e64fd834d042a98326 in / ", + EmptyLayer: false, + }, + { + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 686519038, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + Comment: "", + EmptyLayer: true, + }, + }, + OS: "linux", + + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }, + }, + }, + Config: v1.Config{ + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + Cmd: []string{"/bin/sh"}, + Image: "sha256:7c41e139ba64dd2eba852a2e963ee86f2e8da3a5bbfaf10cf4349535dbf0ff08", + ArgsEscaped: true, + }, + OSVersion: "", + }, + }, + { + name: "happy path with insecure Docker Registry", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + option: types.ImageOptions{ + RegistryOptions: types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: "test", + Password: "test", + }, + }, + Insecure: true, + }, + }, + }, + wantID: "sha256:af341ccd2df8b0e2d67cf8dd32e087bfda4e5756ebd1c76bbf3efa0dc246590e", + wantRepoTags: []string{serverAddr + "/library/alpine:3.10"}, + wantRepoDigests: []string{ + serverAddr + "/library/alpine@sha256:e10ea963554297215478627d985466ada334ed15c56d3d6bb808ceab98374d91", + }, + wantConfigFile: &v1.ConfigFile{ + Architecture: "amd64", + Container: "7f4a36a667d138b079b5ff059485ff65bfbb5ebc48f24a89f983b918e73f4f28", + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 686519038, time.UTC)}, + DockerVersion: "18.06.1-ce", + History: []v1.History{ + { + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 551172402, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:d48cac34fac385cbc1de6adfdd88300f76f9bbe346cd17e64fd834d042a98326 in / ", + EmptyLayer: false, + }, + { + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 686519038, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + Comment: "", + EmptyLayer: true, + }, + }, + OS: "linux", + + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }, + }, + }, + Config: v1.Config{ + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + Cmd: []string{"/bin/sh"}, + Image: "sha256:7c41e139ba64dd2eba852a2e963ee86f2e8da3a5bbfaf10cf4349535dbf0ff08", + ArgsEscaped: true, + }, + }, + }, + { + name: "sad path with invalid tag", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.11!!!", serverAddr), + }, + wantErr: true, + }, + { + name: "sad path with non-exist image", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:100", serverAddr), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.args.option.ImageSources = types.AllImageSources + img, cleanup, err := NewContainerImage(context.Background(), tt.args.imageName, tt.args.option) + defer cleanup() + + if tt.wantErr { + assert.NotNil(t, err) + return + } + assert.NoError(t, err) + + gotID, err := img.ID() + require.NoError(t, err) + assert.Equal(t, tt.wantID, gotID) + + gotConfigFile, err := img.ConfigFile() + require.NoError(t, err) + assert.Equal(t, tt.wantConfigFile, gotConfigFile) + + gotRepoTags := img.RepoTags() + assert.Equal(t, tt.wantRepoTags, gotRepoTags) + + gotRepoDigests := img.RepoDigests() + assert.Equal(t, tt.wantRepoDigests, gotRepoDigests) + }) + } +} + +func setupPrivateRegistry() *httptest.Server { + imagePaths := map[string]string{ + "v2/library/alpine:3.10": "../test/testdata/alpine-310.tar.gz", + } + tr := registry.NewDockerRegistry(registry.Option{ + Images: imagePaths, + Auth: auth.Auth{ + User: "test", + Password: "testpass", + Secret: "secret", + }, + }) + + return tr +} + +func TestNewDockerImageWithPrivateRegistry(t *testing.T) { + tr := setupPrivateRegistry() + defer tr.Close() + + serverAddr := tr.Listener.Addr().String() + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "iss": "testdocker", + }) + + registryToken, err := token.SignedString([]byte("secret")) + require.NoError(t, err) + + type args struct { + imageName string + option types.ImageOptions + } + tests := []struct { + name string + args args + want v1.Image + wantErr string + }{ + { + name: "happy path with private Docker Registry", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + option: types.ImageOptions{ + RegistryOptions: types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: "test", + Password: "testpass", + }, + }, + Insecure: true, + }, + }, + }, + }, + { + name: "happy path with registry token", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + option: types.ImageOptions{ + RegistryOptions: types.RegistryOptions{ + RegistryToken: registryToken, + Insecure: true, + }, + }, + }, + }, + { + name: "sad path without a credential", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.11", serverAddr), + }, + wantErr: "unexpected status code 401", + }, + { + name: "sad path with invalid registry token", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.11", serverAddr), + option: types.ImageOptions{ + RegistryOptions: types.RegistryOptions{ + RegistryToken: registryToken + "invalid", + Insecure: true, + }, + }, + }, + wantErr: "signature is invalid", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.args.option.ImageSources = types.AllImageSources + _, cleanup, err := NewContainerImage(context.Background(), tt.args.imageName, tt.args.option) + defer cleanup() + + if tt.wantErr != "" { + assert.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestNewArchiveImage(t *testing.T) { + type args struct { + fileName string + } + tests := []struct { + name string + args args + want v1.Image + wantErr string + }{ + { + name: "happy path", + args: args{ + fileName: "../test/testdata/alpine-310.tar.gz", + }, + }, + { + name: "happy path with OCI Image Format", + args: args{ + fileName: "../test/testdata/test.oci", + }, + }, + { + name: "happy path with OCI Image and tag Format", + args: args{ + fileName: "../test/testdata/test_image_tag.oci:0.0.1", + }, + }, + { + name: "happy path with OCI Image only", + args: args{ + fileName: "../test/testdata/test_image_tag.oci", + }, + }, + { + name: "sad path with OCI Image and invalid tagFormat", + args: args{ + fileName: "../test/testdata/test_image_tag.oci:0.0.0", + }, + wantErr: "invalid OCI image ref", + }, + { + name: "sad path, oci image not found", + args: args{ + fileName: "../test/testdata/invalid.tar.gz", + }, + wantErr: "unable to open", + }, + { + name: "sad path with OCI Image Format index.json directory", + args: args{ + fileName: "../test/testdata/test_index_json_dir.oci", + }, + wantErr: "unable to retrieve index.json", + }, + { + name: "sad path with OCI Image Format invalid index.json", + args: args{ + fileName: "../test/testdata/test_bad_index_json.oci", + }, + wantErr: "invalid index.json", + }, + { + name: "sad path with OCI Image Format no valid manifests", + args: args{ + fileName: "../test/testdata/test_no_valid_manifests.oci", + }, + wantErr: "no valid manifest", + }, + { + name: "sad path with OCI Image Format with invalid oci image digest", + args: args{ + fileName: "../test/testdata/test_invalid_oci_image.oci", + }, + wantErr: "invalid OCI image", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + img, err := NewArchiveImage(tt.args.fileName) + switch { + case tt.wantErr != "": + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + default: + assert.NoError(t, err, tt.name) + } + + // archive doesn't support RepoTags and RepoDigests + assert.Empty(t, img.RepoTags()) + assert.Empty(t, img.RepoDigests()) + }) + } +} + +func TestDockerPlatformArguments(t *testing.T) { + tr := setupPrivateRegistry() + defer tr.Close() + + serverAddr := tr.Listener.Addr().String() + + type args struct { + option types.ImageOptions + } + tests := []struct { + name string + args args + want v1.Image + wantErr string + }{ + { + name: "happy path with valid platform", + args: args{ + option: types.ImageOptions{ + RegistryOptions: types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: "test", + Password: "testpass", + }, + }, + Insecure: true, + Platform: types.Platform{ + Platform: &v1.Platform{ + Architecture: "arm", + OS: "linux", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + imageName := fmt.Sprintf("%s/library/alpine:3.10", serverAddr) + tt.args.option.ImageSources = types.AllImageSources + _, cleanup, err := NewContainerImage(context.Background(), imageName, tt.args.option) + defer cleanup() + + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/fanal/image/oci.go b/pkg/fanal/image/oci.go new file mode 100644 index 000000000000..3031810a059b --- /dev/null +++ b/pkg/fanal/image/oci.go @@ -0,0 +1,82 @@ +package image + +import ( + "strings" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/layout" + ispec "github.com/opencontainers/image-spec/specs-go/v1" + "golang.org/x/xerrors" +) + +func tryOCI(fileName string) (v1.Image, error) { + var inputRef, inputFileName string + + // Check if tag is specified in input + // e.g. /path/to/oci:0.0.1 + + inputFileName, inputRef, found := strings.Cut(fileName, "@") + + if !found { + inputFileName, inputRef, found = strings.Cut(fileName, ":") + } + + if !found { + inputFileName = fileName + inputRef = "" + } + + lp, err := layout.FromPath(inputFileName) + if err != nil { + return nil, xerrors.Errorf("unable to open %s as an OCI Image: %w", fileName, err) + } + + index, err := lp.ImageIndex() + if err != nil { + return nil, xerrors.Errorf("unable to retrieve index.json: %w", err) + } + + m, err := index.IndexManifest() + if err != nil { + return nil, xerrors.Errorf("invalid index.json: %w", err) + } + + if len(m.Manifests) == 0 { + return nil, xerrors.New("no valid manifest") + } + + // Support image having tag separated by : , otherwise support first image + return getOCIImage(m, index, inputRef) +} + +func getOCIImage(m *v1.IndexManifest, index v1.ImageIndex, inputRef string) (v1.Image, error) { + for _, manifest := range m.Manifests { + annotation := manifest.Annotations + tag := annotation[ispec.AnnotationRefName] + if inputRef == "" || // always select the first digest + tag == inputRef || + manifest.Digest.String() == inputRef { + h := manifest.Digest + if manifest.MediaType.IsIndex() { + childIndex, err := index.ImageIndex(h) + if err != nil { + return nil, xerrors.Errorf("unable to retrieve a child image %q: %w", h.String(), err) + } + childManifest, err := childIndex.IndexManifest() + if err != nil { + return nil, xerrors.Errorf("invalid a child manifest for %q: %w", h.String(), err) + } + return getOCIImage(childManifest, childIndex, "") + } + + img, err := index.Image(h) + if err != nil { + return nil, xerrors.Errorf("invalid OCI image: %w", err) + } + + return img, nil + } + } + + return nil, xerrors.New("invalid OCI image ref") +} diff --git a/pkg/fanal/image/oci_test.go b/pkg/fanal/image/oci_test.go new file mode 100644 index 000000000000..b7bcf4b8f640 --- /dev/null +++ b/pkg/fanal/image/oci_test.go @@ -0,0 +1,71 @@ +package image + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTryOCI(t *testing.T) { + tests := []struct { + name string + ociImagePath string + wantErr string + }{ + { + name: "correct path to index without tag", + ociImagePath: filepath.Join("testdata", "multi"), + wantErr: "", + }, + { + name: "correct path to index with correct tag", + ociImagePath: filepath.Join("testdata", "multi:tg11"), + wantErr: "", + }, + { + name: "correct path to index with incorrect tag", + ociImagePath: filepath.Join("testdata", "multi:tg12"), + wantErr: "invalid OCI image ref", + }, + { + name: "correct path to manifest without tag", + ociImagePath: filepath.Join("testdata", "single"), + wantErr: "", + }, + { + name: "correct path to manifest with correct tag", + ociImagePath: filepath.Join("testdata", "single:3.14"), + wantErr: "", + }, + { + name: "correct path to manifest with incorrect tag", + ociImagePath: filepath.Join("testdata", "single:3.11"), + wantErr: "invalid OCI image ref", + }, + { + name: "correct path to manifest with correct digest", + ociImagePath: filepath.Join("testdata", + "single@sha256:56ae38f2f5c54b98311b8b2463d4861368c451ac17098f4227d84946b42ab96d"), + wantErr: "", + }, + { + name: "correct path to manifest with incorrect digest", + ociImagePath: filepath.Join("testdata", + "single@sha256:1111111111111111111111111111111111111111111111111111111111111111"), + wantErr: "invalid OCI image ref", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := tryOCI(test.ociImagePath) + if test.wantErr != "" { + assert.NotNil(t, err) + assert.Contains(t, err.Error(), test.wantErr, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/fanal/image/registry/azure/azure.go b/pkg/fanal/image/registry/azure/azure.go new file mode 100644 index 000000000000..491fce4e27c3 --- /dev/null +++ b/pkg/fanal/image/registry/azure/azure.go @@ -0,0 +1,59 @@ +package azure + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + + "github.com/Azure/azure-sdk-for-go/profiles/preview/preview/containerregistry/runtime/containerregistry" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type Registry struct { + domain string +} + +const ( + azureURL = "azurecr.io" + scope = "https://management.azure.com/.default" + scheme = "https" +) + +func (r *Registry) CheckOptions(domain string, _ types.RegistryOptions) error { + if !strings.HasSuffix(domain, azureURL) { + return xerrors.Errorf("Azure registry: %w", types.InvalidURLPattern) + } + r.domain = domain + return nil +} + +func (r *Registry) GetCredential(ctx context.Context) (string, string, error) { + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return "", "", xerrors.Errorf("unable to generate acr credential error: %w", err) + } + aadToken, err := cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: []string{scope}}) + if err != nil { + return "", "", xerrors.Errorf("unable to get an access token: %w", err) + } + rt, err := refreshToken(ctx, aadToken.Token, r.domain) + if err != nil { + return "", "", xerrors.Errorf("unable to refresh token: %w", err) + } + return "00000000-0000-0000-0000-000000000000", *rt.RefreshToken, err +} + +func refreshToken(ctx context.Context, accessToken, domain string) (containerregistry.RefreshToken, error) { + tenantID := os.Getenv("AZURE_TENANT_ID") + if tenantID == "" { + return containerregistry.RefreshToken{}, errors.New("missing environment variable AZURE_TENANT_ID") + } + repoClient := containerregistry.NewRefreshTokensClient(fmt.Sprintf("%s://%s", scheme, domain)) + return repoClient.GetFromExchange(ctx, "access_token", domain, tenantID, "", accessToken) +} diff --git a/pkg/fanal/image/registry/azure/azure_test.go b/pkg/fanal/image/registry/azure/azure_test.go new file mode 100644 index 000000000000..ae823b82a65a --- /dev/null +++ b/pkg/fanal/image/registry/azure/azure_test.go @@ -0,0 +1,39 @@ +package azure_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/azure" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestRegistry_CheckOptions(t *testing.T) { + tests := []struct { + name string + domain string + wantErr string + }{ + { + name: "happy path", + domain: "test.azurecr.io", + }, + { + name: "invalidURL", + domain: "alpine:3.9", + wantErr: "Azure registry: invalid url pattern", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := azure.Registry{} + err := r.CheckOptions(tt.domain, types.RegistryOptions{}) + if tt.wantErr != "" { + assert.EqualError(t, err, tt.wantErr) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/fanal/image/registry/ecr/ecr.go b/pkg/fanal/image/registry/ecr/ecr.go new file mode 100644 index 000000000000..e675ed47afaf --- /dev/null +++ b/pkg/fanal/image/registry/ecr/ecr.go @@ -0,0 +1,72 @@ +package ecr + +import ( + "context" + "encoding/base64" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/ecr" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +const ecrURL = "amazonaws.com" + +type ecrAPI interface { + GetAuthorizationToken(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) +} + +type ECR struct { + Client ecrAPI +} + +func getSession(option types.RegistryOptions) (aws.Config, error) { + // create custom credential information if option is valid + if option.AWSSecretKey != "" && option.AWSAccessKey != "" && option.AWSRegion != "" { + return config.LoadDefaultConfig( + context.TODO(), + config.WithRegion(option.AWSRegion), + config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(option.AWSAccessKey, option.AWSSecretKey, option.AWSSessionToken)), + ) + } + return config.LoadDefaultConfig(context.TODO()) +} + +func (e *ECR) CheckOptions(domain string, option types.RegistryOptions) error { + if !strings.HasSuffix(domain, ecrURL) { + return xerrors.Errorf("ECR : %w", types.InvalidURLPattern) + } + + cfg, err := getSession(option) + if err != nil { + return err + } + + svc := ecr.NewFromConfig(cfg) + e.Client = svc + return nil +} + +func (e *ECR) GetCredential(ctx context.Context) (username, password string, err error) { + input := &ecr.GetAuthorizationTokenInput{} + result, err := e.Client.GetAuthorizationToken(ctx, input) + if err != nil { + return "", "", xerrors.Errorf("failed to get authorization token: %w", err) + } + for _, data := range result.AuthorizationData { + b, err := base64.StdEncoding.DecodeString(*data.AuthorizationToken) + if err != nil { + return "", "", xerrors.Errorf("base64 decode failed: %w", err) + } + // e.g. AWS:eyJwYXlsb2... + split := strings.SplitN(string(b), ":", 2) + if len(split) == 2 { + return split[0], split[1], nil + } + } + return "", "", nil +} diff --git a/pkg/fanal/image/registry/ecr/ecr_test.go b/pkg/fanal/image/registry/ecr/ecr_test.go new file mode 100644 index 000000000000..63ae1858114c --- /dev/null +++ b/pkg/fanal/image/registry/ecr/ecr_test.go @@ -0,0 +1,99 @@ +package ecr + +import ( + "context" + "errors" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ecr" + awstypes "github.com/aws/aws-sdk-go-v2/service/ecr/types" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestCheckOptions(t *testing.T) { + var tests = map[string]struct { + domain string + wantErr error + }{ + "InvalidURL": { + domain: "alpine:3.9", + wantErr: types.InvalidURLPattern, + }, + "NoOption": { + domain: "xxx.ecr.ap-northeast-1.amazonaws.com", + }, + } + + for testname, v := range tests { + a := &ECR{} + err := a.CheckOptions(v.domain, types.RegistryOptions{}) + if err != nil { + if !errors.Is(err, v.wantErr) { + t.Errorf("[%s]\nexpected error based on %v\nactual : %v", testname, v.wantErr, err) + } + continue + } + } +} + +type mockedECR struct { + Resp ecr.GetAuthorizationTokenOutput +} + +func (m mockedECR) GetAuthorizationToken(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) { + return &m.Resp, nil +} + +func TestECRGetCredential(t *testing.T) { + cases := []struct { + Resp ecr.GetAuthorizationTokenOutput + expectedUser string + expectedPassword string + }{ + { + Resp: ecr.GetAuthorizationTokenOutput{ + AuthorizationData: []awstypes.AuthorizationData{ + {AuthorizationToken: aws.String("YXdzOnBhc3N3b3Jk")}, + }, + }, + expectedUser: "aws", + expectedPassword: "password", + }, + { + Resp: ecr.GetAuthorizationTokenOutput{ + AuthorizationData: []awstypes.AuthorizationData{ + {AuthorizationToken: aws.String("YXdzOnBhc3N3b3JkOmJhZA==")}, + }, + }, + expectedUser: "aws", + expectedPassword: "password:bad", + }, + { + Resp: ecr.GetAuthorizationTokenOutput{ + AuthorizationData: []awstypes.AuthorizationData{ + {AuthorizationToken: aws.String("YXdzcGFzc3dvcmQ=")}, + }, + }, + expectedUser: "", + expectedPassword: "", + }, + } + + for i, c := range cases { + e := ECR{ + Client: mockedECR{Resp: c.Resp}, + } + username, password, err := e.GetCredential(context.Background()) + if err != nil { + t.Fatalf("%d, unexpected error", err) + } + if username != c.expectedUser { + t.Fatalf("%d, username: expected %s, got %s", i, c.expectedUser, username) + } + if password != c.expectedPassword { + t.Fatalf("%d, password: expected %s, got %s", i, c.expectedPassword, password) + } + } +} diff --git a/pkg/fanal/image/registry/google/google.go b/pkg/fanal/image/registry/google/google.go new file mode 100644 index 000000000000..f4e7a7414260 --- /dev/null +++ b/pkg/fanal/image/registry/google/google.go @@ -0,0 +1,53 @@ +package google + +import ( + "context" + "strings" + + "github.com/GoogleCloudPlatform/docker-credential-gcr/config" + "github.com/GoogleCloudPlatform/docker-credential-gcr/credhelper" + "github.com/GoogleCloudPlatform/docker-credential-gcr/store" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type Registry struct { + Store store.GCRCredStore + domain string +} + +// Google container registry +const gcrURL = "gcr.io" + +// Google artifact registry +const garURL = "docker.pkg.dev" + +func (g *Registry) CheckOptions(domain string, option types.RegistryOptions) error { + if !strings.HasSuffix(domain, gcrURL) && !strings.HasSuffix(domain, garURL) { + return xerrors.Errorf("Google registry: %w", types.InvalidURLPattern) + } + g.domain = domain + if option.GCPCredPath != "" { + g.Store = store.NewGCRCredStore(option.GCPCredPath) + } + return nil +} + +func (g *Registry) GetCredential(_ context.Context) (username, password string, err error) { + var credStore store.GCRCredStore + if g.Store == nil { + credStore, err = store.DefaultGCRCredStore() + if err != nil { + return "", "", xerrors.Errorf("failed to get GCRCredStore: %w", err) + } + } else { + credStore = g.Store + } + userCfg, err := config.LoadUserConfig() + if err != nil { + return "", "", xerrors.Errorf("failed to load user config: %w", err) + } + helper := credhelper.NewGCRCredentialHelper(credStore, userCfg) + return helper.Get(g.domain) +} diff --git a/pkg/fanal/image/registry/google/google_test.go b/pkg/fanal/image/registry/google/google_test.go new file mode 100644 index 000000000000..62a2b1f57627 --- /dev/null +++ b/pkg/fanal/image/registry/google/google_test.go @@ -0,0 +1,55 @@ +package google + +import ( + "errors" + "reflect" + "testing" + + "github.com/GoogleCloudPlatform/docker-credential-gcr/store" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestCheckOptions(t *testing.T) { + var tests = map[string]struct { + domain string + opt types.RegistryOptions + gcr *Registry + wantErr error + }{ + "InvalidURL": { + domain: "alpine:3.9", + wantErr: types.InvalidURLPattern, + }, + "NoOption": { + domain: "gcr.io", + gcr: &Registry{domain: "gcr.io"}, + }, + "CredOption": { + domain: "gcr.io", + opt: types.RegistryOptions{GCPCredPath: "/path/to/file.json"}, + gcr: &Registry{ + domain: "gcr.io", + Store: store.NewGCRCredStore("/path/to/file.json"), + }, + }, + } + + for testname, v := range tests { + g := &Registry{} + err := g.CheckOptions(v.domain, v.opt) + if v.wantErr != nil { + if err == nil { + t.Errorf("%s : expected error but no error", testname) + continue + } + if !errors.Is(err, v.wantErr) { + t.Errorf("[%s]\nexpected error based on %v\nactual : %v", testname, v.wantErr, err) + } + continue + } + if !reflect.DeepEqual(v.gcr, g) { + t.Errorf("[%s]\nexpected : %v\nactual : %v", testname, v.gcr, g) + } + } +} diff --git a/pkg/fanal/image/registry/token.go b/pkg/fanal/image/registry/token.go new file mode 100644 index 000000000000..1e51b0fd31a4 --- /dev/null +++ b/pkg/fanal/image/registry/token.go @@ -0,0 +1,53 @@ +package registry + +import ( + "context" + + "github.com/google/go-containerregistry/pkg/authn" + + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/azure" + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/ecr" + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/google" + "github.com/aquasecurity/trivy/pkg/fanal/log" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var ( + registries []Registry +) + +func init() { + RegisterRegistry(&google.Registry{}) + RegisterRegistry(&ecr.ECR{}) + RegisterRegistry(&azure.Registry{}) +} + +type Registry interface { + CheckOptions(domain string, option types.RegistryOptions) error + GetCredential(ctx context.Context) (string, string, error) +} + +func RegisterRegistry(registry Registry) { + registries = append(registries, registry) +} + +func GetToken(ctx context.Context, domain string, opt types.RegistryOptions) (auth authn.Basic) { + // check registry which particular to get credential + for _, registry := range registries { + err := registry.CheckOptions(domain, opt) + if err != nil { + continue + } + username, password, err := registry.GetCredential(ctx) + if err != nil { + // only skip check registry if error occurred + log.Logger.Debug(err) + break + } + return authn.Basic{ + Username: username, + Password: password, + } + } + return authn.Basic{} +} diff --git a/pkg/fanal/image/registry/token_test.go b/pkg/fanal/image/registry/token_test.go new file mode 100644 index 000000000000..52bf78d84b9f --- /dev/null +++ b/pkg/fanal/image/registry/token_test.go @@ -0,0 +1,37 @@ +package registry + +import ( + "context" + "testing" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestGetToken(t *testing.T) { + type args struct { + domain string + opt types.RegistryOptions + } + tests := []struct { + name string + args args + wantAuth authn.Basic + }{ + { + name: "happy path", + args: args{ + domain: "docker.io", + }, + wantAuth: authn.Basic{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotAuth := GetToken(context.Background(), tt.args.domain, tt.args.opt) + assert.Equal(t, tt.wantAuth, gotAuth) + }) + } +} diff --git a/pkg/fanal/image/remote.go b/pkg/fanal/image/remote.go new file mode 100644 index 000000000000..3927d1177e1b --- /dev/null +++ b/pkg/fanal/image/remote.go @@ -0,0 +1,90 @@ +package image + +import ( + "context" + "fmt" + "strings" + + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/remote" +) + +func tryRemote(ctx context.Context, imageName string, ref name.Reference, option types.ImageOptions) (types.Image, func(), error) { + // This function doesn't need cleanup + cleanup := func() {} + + desc, err := remote.Get(ctx, ref, option.RegistryOptions) + if err != nil { + return nil, cleanup, err + } + img, err := desc.Image() + if err != nil { + return nil, cleanup, err + } + + // Return v1.Image if the image is found in Docker Registry + return remoteImage{ + name: imageName, + Image: img, + ref: implicitReference{ref: ref}, + descriptor: desc, + }, cleanup, nil + +} + +type remoteImage struct { + name string + ref implicitReference + descriptor *remote.Descriptor + v1.Image +} + +func (img remoteImage) Name() string { + return img.name +} + +func (img remoteImage) ID() (string, error) { + return ID(img) +} + +func (img remoteImage) RepoTags() []string { + tag := img.ref.TagName() + if tag == "" { + return []string{} + } + return []string{fmt.Sprintf("%s:%s", img.ref.RepositoryName(), tag)} +} + +func (img remoteImage) RepoDigests() []string { + repoDigest := fmt.Sprintf("%s@%s", img.ref.RepositoryName(), img.descriptor.Digest.String()) + return []string{repoDigest} +} + +type implicitReference struct { + ref name.Reference +} + +func (r implicitReference) TagName() string { + if t, ok := r.ref.(name.Tag); ok { + return t.TagStr() + } + return "" +} + +func (r implicitReference) RepositoryName() string { + ctx := r.ref.Context() + reg := ctx.RegistryStr() + repo := ctx.RepositoryStr() + + // Default registry + if reg != name.DefaultRegistry { + return fmt.Sprintf("%s/%s", reg, repo) + } + + // Trim default namespace + // See https://docs.docker.com/docker-hub/official_repos + return strings.TrimPrefix(repo, "library/") +} diff --git a/pkg/fanal/image/remote_test.go b/pkg/fanal/image/remote_test.go new file mode 100644 index 000000000000..ededa04da271 --- /dev/null +++ b/pkg/fanal/image/remote_test.go @@ -0,0 +1,84 @@ +package image + +import ( + "testing" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_implicitReference_TagName(t *testing.T) { + tests := []struct { + name string + image string + want string + }{ + { + name: "explicit tag", + image: "aquasec/trivy:0.15.0", + want: "0.15.0", + }, + { + name: "implicit tag", + image: "aquasec/trivy", + want: "latest", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r, err := name.ParseReference(tt.image) + require.NoError(t, err) + + ref := implicitReference{ref: r} + + got := ref.TagName() + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_implicitReference_RepositoryName(t *testing.T) { + tests := []struct { + name string + image string + want string + }{ + { + name: "explicit default registry", + image: "index.docker.io/aquasec/trivy:0.15.0", + want: "aquasec/trivy", + }, + { + name: "explicit default namespace", + image: "library/alpine:3.12", + want: "alpine", + }, + { + name: "implicit registry", + image: "aquasec/trivy:latest", + want: "aquasec/trivy", + }, + { + name: "implicit namespace", + image: "alpine:latest", + want: "alpine", + }, + { + name: "non-default registry", + image: "gcr.io/library/alpine:3.12", + want: "gcr.io/library/alpine", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r, err := name.ParseReference(tt.image) + require.NoError(t, err) + + ref := implicitReference{ref: r} + + got := ref.RepositoryName() + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/image/testdata/manifest.json b/pkg/fanal/image/testdata/manifest.json new file mode 100644 index 000000000000..58fc349934c9 --- /dev/null +++ b/pkg/fanal/image/testdata/manifest.json @@ -0,0 +1,26 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 6666, + "digest": "sha256:2073e0bcb60ee98548d313ead5eacbfe16d9054f8800a32bedd859922a99a6e1" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 27092260, + "digest": "sha256:bc51dd8edc1b1132bb97827ed6bd16aac332b03fb03d4c02d2088067a5fbb499" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 23882249, + "digest": "sha256:66ba67045f57970205d6b5a6956fe5225565cbbcbf1900fc81612e66d30180ef" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 204, + "digest": "sha256:bf317aa10aa501e00f87c2ff5a414079c4f3083ab086c91f57878dbbffa70507" + } + ] +} \ No newline at end of file diff --git a/pkg/fanal/image/testdata/multi/blobs/sha256/56f658ee7c94c1a65099c680916c12f6b81ae4c586c662a8146791054fa466ab b/pkg/fanal/image/testdata/multi/blobs/sha256/56f658ee7c94c1a65099c680916c12f6b81ae4c586c662a8146791054fa466ab new file mode 100644 index 000000000000..4db0c4521917 --- /dev/null +++ b/pkg/fanal/image/testdata/multi/blobs/sha256/56f658ee7c94c1a65099c680916c12f6b81ae4c586c662a8146791054fa466ab @@ -0,0 +1 @@ +{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:56ae38f2f5c54b98311b8b2463d4861368c451ac17098f4227d84946b42ab96d","size":348,"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:d4797ba2a3f15e3fe4c13398104f2199cc3fe22da004c9d382a60b74990136ad","size":348,"platform":{"architecture":"arm","os":"linux"}}]} \ No newline at end of file diff --git a/pkg/fanal/image/testdata/multi/index.json b/pkg/fanal/image/testdata/multi/index.json new file mode 100644 index 000000000000..26373522f9ba --- /dev/null +++ b/pkg/fanal/image/testdata/multi/index.json @@ -0,0 +1 @@ +{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:56f658ee7c94c1a65099c680916c12f6b81ae4c586c662a8146791054fa466ab","size":435,"annotations":{"org.opencontainers.image.ref.name":"tg11"}}]} \ No newline at end of file diff --git a/pkg/fanal/image/testdata/single/index.json b/pkg/fanal/image/testdata/single/index.json new file mode 100644 index 000000000000..aa32a93f3903 --- /dev/null +++ b/pkg/fanal/image/testdata/single/index.json @@ -0,0 +1 @@ +{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:56ae38f2f5c54b98311b8b2463d4861368c451ac17098f4227d84946b42ab96d","size":348,"annotations":{"org.opencontainers.image.ref.name":"3.14"}}]} \ No newline at end of file diff --git a/pkg/fanal/log/log.go b/pkg/fanal/log/log.go new file mode 100644 index 000000000000..93344390d03e --- /dev/null +++ b/pkg/fanal/log/log.go @@ -0,0 +1,17 @@ +package log + +import ( + "go.uber.org/zap" +) + +var Logger *zap.SugaredLogger + +func init() { + if logger, err := zap.NewProduction(); err == nil { + Logger = logger.Sugar() + } +} + +func SetLogger(l *zap.SugaredLogger) { + Logger = l +} diff --git a/pkg/fanal/secret/builtin-allow-rules.go b/pkg/fanal/secret/builtin-allow-rules.go new file mode 100644 index 000000000000..5e02f3241a04 --- /dev/null +++ b/pkg/fanal/secret/builtin-allow-rules.go @@ -0,0 +1,65 @@ +package secret + +var builtinAllowRules = []AllowRule{ + { + ID: "tests", + Description: "Avoid test files and paths", + Path: MustCompile(`(^test|\/test|-test|_test|\.test)`), + }, + { + ID: "examples", + Description: "Avoid example files and paths", // e.g. https://github.com/boto/botocore/blob/develop/botocore/data/organizations/2016-11-28/examples-1.json + Path: MustCompile(`example`), + Regex: MustCompile("(?i)example"), + }, + { + ID: "vendor", + Description: "Vendor dirs", + Path: MustCompile(`\/vendor\/`), + }, + { + ID: "usr-dirs", + Description: "System dirs", + Path: MustCompile(`^usr\/(?:share|include|lib)\/`), + }, + { + ID: "locale-dir", + Description: "Locales directory contains locales file", + Path: MustCompile(`\/locales?\/`), + }, + { + ID: "markdown", + Description: "Markdown files", + Path: MustCompile(`\.md$`), + }, + { + ID: "node.js", + Description: "Node container images", + Path: MustCompile(`^opt\/yarn-v[\d.]+\/`), + }, + { + ID: "golang", + Description: "Go container images", + Path: MustCompile(`^usr\/local\/go\/`), + }, + { + ID: "python", + Description: "Python container images", + Path: MustCompile(`^usr\/local\/lib\/python[\d.]+\/`), + }, + { + ID: "rubygems", + Description: "Ruby container images", + Path: MustCompile(`^usr\/lib\/gems\/`), + }, + { + ID: "wordpress", + Description: "Wordpress container images", + Path: MustCompile(`^usr\/src\/wordpress\/`), + }, + { + ID: "anaconda-log", + Description: "Anaconda CI Logs in container images", + Path: MustCompile(`^var\/log\/anaconda\/`), + }, +} diff --git a/pkg/fanal/secret/builtin-rules.go b/pkg/fanal/secret/builtin-rules.go new file mode 100644 index 000000000000..ae08f494ad7f --- /dev/null +++ b/pkg/fanal/secret/builtin-rules.go @@ -0,0 +1,823 @@ +package secret + +import ( + "fmt" + + "github.com/samber/lo" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + iacRules "github.com/aquasecurity/trivy/pkg/iac/rules" +) + +var ( + CategoryAWS = types.SecretRuleCategory("AWS") + CategoryGitHub = types.SecretRuleCategory("GitHub") + CategoryGitLab = types.SecretRuleCategory("GitLab") + CategoryAsymmetricPrivateKey = types.SecretRuleCategory("AsymmetricPrivateKey") + CategoryShopify = types.SecretRuleCategory("Shopify") + CategorySlack = types.SecretRuleCategory("Slack") + CategoryGoogle = types.SecretRuleCategory("Google") + CategoryStripe = types.SecretRuleCategory("Stripe") + CategoryPyPI = types.SecretRuleCategory("PyPI") + CategoryHeroku = types.SecretRuleCategory("Heroku") + CategoryTwilio = types.SecretRuleCategory("Twilio") + CategoryAge = types.SecretRuleCategory("Age") + CategoryFacebook = types.SecretRuleCategory("Facebook") + CategoryTwitter = types.SecretRuleCategory("Twitter") + CategoryAdobe = types.SecretRuleCategory("Adobe") + CategoryAlibaba = types.SecretRuleCategory("Alibaba") + CategoryAsana = types.SecretRuleCategory("Asana") + CategoryAtlassian = types.SecretRuleCategory("Atlassian") + CategoryBitbucket = types.SecretRuleCategory("Bitbucket") + CategoryBeamer = types.SecretRuleCategory("Beamer") + CategoryClojars = types.SecretRuleCategory("Clojars") + CategoryContentfulDelivery = types.SecretRuleCategory("ContentfulDelivery") + CategoryDatabricks = types.SecretRuleCategory("Databricks") + CategoryDiscord = types.SecretRuleCategory("Discord") + CategoryDoppler = types.SecretRuleCategory("Doppler") + CategoryDropbox = types.SecretRuleCategory("Dropbox") + CategoryDuffel = types.SecretRuleCategory("Duffel") + CategoryDynatrace = types.SecretRuleCategory("Dynatrace") + CategoryEasypost = types.SecretRuleCategory("Easypost") + CategoryFastly = types.SecretRuleCategory("Fastly") + CategoryFinicity = types.SecretRuleCategory("Finicity") + CategoryFlutterwave = types.SecretRuleCategory("Flutterwave") + CategoryFrameio = types.SecretRuleCategory("Frameio") + CategoryGoCardless = types.SecretRuleCategory("GoCardless") + CategoryGrafana = types.SecretRuleCategory("Grafana") + CategoryHashiCorp = types.SecretRuleCategory("HashiCorp") + CategoryHubSpot = types.SecretRuleCategory("HubSpot") + CategoryIntercom = types.SecretRuleCategory("Intercom") + CategoryIonic = types.SecretRuleCategory("Ionic") + CategoryJWT = types.SecretRuleCategory("JWT") + CategoryLinear = types.SecretRuleCategory("Linear") + CategoryLob = types.SecretRuleCategory("Lob") + CategoryMailchimp = types.SecretRuleCategory("Mailchimp") + CategoryMailgun = types.SecretRuleCategory("Mailgun") + CategoryMapbox = types.SecretRuleCategory("Mapbox") + CategoryMessageBird = types.SecretRuleCategory("MessageBird") + CategoryNewRelic = types.SecretRuleCategory("NewRelic") + CategoryNpm = types.SecretRuleCategory("Npm") + CategoryPlanetscale = types.SecretRuleCategory("Planetscale") + CategoryPostman = types.SecretRuleCategory("Postman") + CategoryPulumi = types.SecretRuleCategory("Pulumi") + CategoryRubyGems = types.SecretRuleCategory("RubyGems") + CategorySendGrid = types.SecretRuleCategory("SendGrid") + CategorySendinblue = types.SecretRuleCategory("Sendinblue") + CategoryShippo = types.SecretRuleCategory("Shippo") + CategoryLinkedIn = types.SecretRuleCategory("LinkedIn") + CategoryTwitch = types.SecretRuleCategory("Twitch") + CategoryTypeform = types.SecretRuleCategory("Typeform") + CategoryDocker = types.SecretRuleCategory("Docker") + CategoryHuggingFace = types.SecretRuleCategory("HuggingFace") +) + +// Reusable regex patterns +const ( + quote = `["']?` + connect = `\s*(:|=>|=)?\s*` + startSecret = `(^|\s+)` + endSecret = `[.,]?(\s+|$)` + + aws = `aws_?` +) + +// This function is exported for trivy-plugin-aqua purposes only +func GetSecretRulesMetadata() []iacRules.Check { + return lo.Map(builtinRules, func(rule Rule, i int) iacRules.Check { + return iacRules.Check{ + Name: rule.ID, + Description: rule.Title, + } + }) +} + +var builtinRules = []Rule{ + { + ID: "aws-access-key-id", + Category: CategoryAWS, + Severity: "CRITICAL", + Title: "AWS Access Key ID", + Regex: MustCompile(fmt.Sprintf(`%s(?P(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16})%s%s`, quote, quote, endSecret)), + SecretGroupName: "secret", + Keywords: []string{"AKIA", "AGPA", "AIDA", "AROA", "AIPA", "ANPA", "ANVA", "ASIA"}, + }, + { + ID: "aws-secret-access-key", + Category: CategoryAWS, + Severity: "CRITICAL", + Title: "AWS Secret Access Key", + Regex: MustCompile(fmt.Sprintf(`(?i)%s%s%s(sec(ret)?)?_?(access)?_?key%s%s%s(?P[A-Za-z0-9\/\+=]{40})%s%s`, startSecret, quote, aws, quote, connect, quote, quote, endSecret)), + SecretGroupName: "secret", + Keywords: []string{"key"}, + }, + { + ID: "github-pat", + Category: CategoryGitHub, + Title: "GitHub Personal Access Token", + Severity: "CRITICAL", + Regex: MustCompile(`ghp_[0-9a-zA-Z]{36}`), + Keywords: []string{"ghp_"}, + }, + { + ID: "github-oauth", + Category: CategoryGitHub, + Title: "GitHub OAuth Access Token", + Severity: "CRITICAL", + Regex: MustCompile(`gho_[0-9a-zA-Z]{36}`), + Keywords: []string{"gho_"}, + }, + { + ID: "github-app-token", + Category: CategoryGitHub, + Title: "GitHub App Token", + Severity: "CRITICAL", + Regex: MustCompile(`(ghu|ghs)_[0-9a-zA-Z]{36}`), + Keywords: []string{"ghu_", "ghs_"}, + }, + { + ID: "github-refresh-token", + Category: CategoryGitHub, + Title: "GitHub Refresh Token", + Severity: "CRITICAL", + Regex: MustCompile(`ghr_[0-9a-zA-Z]{76}`), + Keywords: []string{"ghr_"}, + }, + { + ID: "github-fine-grained-pat", + Category: CategoryGitHub, + Title: "GitHub Fine-grained personal access tokens", + Severity: "CRITICAL", + Regex: MustCompile(`github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}`), + Keywords: []string{"github_pat_"}, + }, + { + ID: "gitlab-pat", + Category: CategoryGitLab, + Title: "GitLab Personal Access Token", + Severity: "CRITICAL", + Regex: MustCompile(`glpat-[0-9a-zA-Z\-\_]{20}`), + Keywords: []string{"glpat-"}, + }, + { + // cf. https://huggingface.co/docs/hub/en/security-tokens + ID: "hugging-face-access-token", + Category: CategoryHuggingFace, + Severity: "CRITICAL", + Title: "Hugging Face Access Token", + Regex: MustCompile(`hf_[A-Za-z0-9]{39}`), + Keywords: []string{"hf_"}, + }, + { + ID: "private-key", + Category: CategoryAsymmetricPrivateKey, + Title: "Asymmetric Private Key", + Severity: "HIGH", + Regex: MustCompile(`(?i)-----\s*?BEGIN[ A-Z0-9_-]*?PRIVATE KEY( BLOCK)?\s*?-----[\s]*?(?P[\sA-Za-z0-9=+/\\\r\n]+)[\s]*?-----\s*?END[ A-Z0-9_-]*? PRIVATE KEY( BLOCK)?\s*?-----`), + SecretGroupName: "secret", + Keywords: []string{"-----"}, + }, + { + ID: "shopify-token", + Category: CategoryShopify, + Title: "Shopify token", + Severity: "HIGH", + Regex: MustCompile(`shp(ss|at|ca|pa)_[a-fA-F0-9]{32}`), + Keywords: []string{"shpss_", "shpat_", "shpca_", "shppa_"}, + }, + { + ID: "slack-access-token", + Category: CategorySlack, + Title: "Slack token", + Severity: "HIGH", + Regex: MustCompile(`xox[baprs]-([0-9a-zA-Z]{10,48})`), + Keywords: []string{"xoxb-", "xoxa-", "xoxp-", "xoxr-", "xoxs-"}, + }, + { + ID: "stripe-publishable-token", + Category: CategoryStripe, + Title: "Stripe Publishable Key", + Severity: "LOW", + Regex: MustCompile(`(?i)pk_(test|live)_[0-9a-z]{10,32}`), + Keywords: []string{"pk_test_", "pk_live_"}, + }, + { + ID: "stripe-secret-token", + Category: CategoryStripe, + Title: "Stripe Secret Key", + Severity: "CRITICAL", + Regex: MustCompile(`(?i)sk_(test|live)_[0-9a-z]{10,32}`), + Keywords: []string{"sk_test_", "sk_live_"}, + }, + { + ID: "pypi-upload-token", + Category: CategoryPyPI, + Title: "PyPI upload token", + Severity: "HIGH", + Regex: MustCompile(`pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\-_]{50,1000}`), + Keywords: []string{"pypi-AgEIcHlwaS5vcmc"}, + }, + { + ID: "gcp-service-account", + Category: CategoryGoogle, + Title: "Google (GCP) Service-account", + Severity: "CRITICAL", + Regex: MustCompile(`\"type\": \"service_account\"`), + Keywords: []string{"\"type\": \"service_account\""}, + }, + { + ID: "heroku-api-key", + Category: CategoryHeroku, + Title: "Heroku API Key", + Severity: "HIGH", + Regex: MustCompile(` (?i)(?Pheroku[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"heroku"}, + }, + { + ID: "slack-web-hook", + Category: CategorySlack, + Title: "Slack Webhook", + Severity: "MEDIUM", + Regex: MustCompile(`https:\/\/hooks.slack.com\/services\/[A-Za-z0-9+\/]{44,48}`), + Keywords: []string{"hooks.slack.com"}, + }, + { + ID: "twilio-api-key", + Category: CategoryTwilio, + Title: "Twilio API Key", + Severity: "MEDIUM", + Regex: MustCompile(`SK[0-9a-fA-F]{32}`), + Keywords: []string{"SK"}, + }, + { + ID: "age-secret-key", + Category: CategoryAge, + Title: "Age secret key", + Severity: "MEDIUM", + Regex: MustCompile(`AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}`), + Keywords: []string{"AGE-SECRET-KEY-1"}, + }, + { + ID: "facebook-token", + Category: CategoryFacebook, + Title: "Facebook token", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Pfacebook[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-f0-9]{32})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"facebook"}, + }, + { + ID: "twitter-token", + Category: CategoryTwitter, + Title: "Twitter token", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Ptwitter[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-f0-9]{35,44})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"twitter"}, + }, + { + ID: "adobe-client-id", + Category: CategoryAdobe, + Title: "Adobe Client ID (Oauth Web)", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Padobe[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-f0-9]{32})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"adobe"}, + }, + { + ID: "adobe-client-secret", + Category: CategoryAdobe, + Title: "Adobe Client Secret", + Severity: "LOW", + Regex: MustCompile(`(p8e-)(?i)[a-z0-9]{32}`), + Keywords: []string{"p8e-"}, + }, + { + ID: "alibaba-access-key-id", + Category: CategoryAlibaba, + Title: "Alibaba AccessKey ID", + Severity: "HIGH", + Regex: MustCompile(`([^0-9A-Za-z]|^)(?P(LTAI)(?i)[a-z0-9]{20})([^0-9A-Za-z]|$)`), + SecretGroupName: "secret", + Keywords: []string{"LTAI"}, + }, + { + ID: "alibaba-secret-key", + Category: CategoryAlibaba, + Title: "Alibaba Secret Key", + Severity: "HIGH", + Regex: MustCompile(`(?i)(?Palibaba[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9]{30})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"alibaba"}, + }, + { + ID: "asana-client-id", + Category: CategoryAsana, + Title: "Asana Client ID", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pasana[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[0-9]{16})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"asana"}, + }, + { + ID: "asana-client-secret", + Category: CategoryAsana, + Title: "Asana Client Secret", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pasana[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9]{32})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"asana"}, + }, + { + ID: "atlassian-api-token", + Category: CategoryAtlassian, + Title: "Atlassian API token", + Severity: "HIGH", + Regex: MustCompile(`(?i)(?Patlassian[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9]{24})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"atlassian"}, + }, + { + ID: "bitbucket-client-id", + Category: CategoryBitbucket, + Title: "Bitbucket client ID", + Severity: "HIGH", + Regex: MustCompile(`(?i)(?Pbitbucket[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9]{32})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"bitbucket"}, + }, + { + ID: "bitbucket-client-secret", + Category: CategoryBitbucket, + Title: "Bitbucket client secret", + Severity: "HIGH", + Regex: MustCompile(`(?i)(?Pbitbucket[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9_\-]{64})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"bitbucket"}, + }, + { + ID: "beamer-api-token", + Category: CategoryBeamer, + Title: "Beamer API token", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Pbeamer[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?Pb_[a-z0-9=_\-]{44})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"beamer"}, + }, + { + ID: "clojars-api-token", + Category: CategoryClojars, + Title: "Clojars API token", + Severity: "MEDIUM", + Regex: MustCompile(`(CLOJARS_)(?i)[a-z0-9]{60}`), + Keywords: []string{"CLOJARS_"}, + }, + { + ID: "contentful-delivery-api-token", + Category: CategoryContentfulDelivery, + Title: "Contentful delivery API token", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Pcontentful[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9\-=_]{43})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"contentful"}, + }, + { + ID: "databricks-api-token", + Category: CategoryDatabricks, + Title: "Databricks API token", + Severity: "MEDIUM", + Regex: MustCompile(`dapi[a-h0-9]{32}`), + Keywords: []string{"dapi"}, + }, + { + ID: "discord-api-token", + Category: CategoryDiscord, + Title: "Discord API key", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pdiscord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-h0-9]{64})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"discord"}, + }, + { + ID: "discord-client-id", + Category: CategoryDiscord, + Title: "Discord client ID", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pdiscord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[0-9]{18})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"discord"}, + }, + { + ID: "discord-client-secret", + Category: CategoryDiscord, + Title: "Discord client secret", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pdiscord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9=_\-]{32})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"discord"}, + }, + { + ID: "doppler-api-token", + Category: CategoryDoppler, + Title: "Doppler API token", + Severity: "MEDIUM", + Regex: MustCompile(`['\"](dp\.pt\.)(?i)[a-z0-9]{43}['\"]`), + Keywords: []string{"dp.pt."}, + }, + { + ID: "dropbox-api-secret", + Category: CategoryDropbox, + Title: "Dropbox API secret/key", + Severity: "HIGH", + Regex: MustCompile(`(?i)(dropbox[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{15})['\"]`), + Keywords: []string{"dropbox"}, + }, + { + ID: "dropbox-short-lived-api-token", + Category: CategoryDropbox, + Title: "Dropbox short lived API token", + Severity: "HIGH", + Regex: MustCompile(`(?i)(dropbox[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](sl\.[a-z0-9\-=_]{135})['\"]`), + Keywords: []string{"dropbox"}, + }, + { + ID: "dropbox-long-lived-api-token", + Category: CategoryDropbox, + Title: "Dropbox long lived API token", + Severity: "HIGH", + Regex: MustCompile(`(?i)(dropbox[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"][a-z0-9]{11}(AAAAAAAAAA)[a-z0-9\-_=]{43}['\"]`), + Keywords: []string{"dropbox"}, + }, + { + ID: "duffel-api-token", + Category: CategoryDuffel, + Title: "Duffel API token", + Severity: "LOW", + Regex: MustCompile(`['\"]duffel_(test|live)_(?i)[a-z0-9_-]{43}['\"]`), + Keywords: []string{"duffel_test_", "duffel_live_"}, + }, + { + ID: "dynatrace-api-token", + Category: CategoryDynatrace, + Title: "Dynatrace API token", + Severity: "MEDIUM", + Regex: MustCompile(`['\"]dt0c01\.(?i)[a-z0-9]{24}\.[a-z0-9]{64}['\"]`), + Keywords: []string{"dt0c01."}, + }, + { + ID: "easypost-api-token", + Category: CategoryEasypost, + Title: "EasyPost API token", + Severity: "LOW", + Regex: MustCompile(`['\"]EZ[AT]K(?i)[a-z0-9]{54}['\"]`), + Keywords: []string{"EZAK", "EZAT"}, + }, + { + ID: "fastly-api-token", + Category: CategoryFastly, + Title: "Fastly API token", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pfastly[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9\-=_]{32})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"fastly"}, + }, + { + ID: "finicity-client-secret", + Category: CategoryFinicity, + Title: "Finicity client secret", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pfinicity[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9]{20})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"finicity"}, + }, + { + ID: "finicity-api-token", + Category: CategoryFinicity, + Title: "Finicity API token", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pfinicity[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-f0-9]{32})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"finicity"}, + }, + { + ID: "flutterwave-public-key", + Category: CategoryFlutterwave, + Title: "Flutterwave public/secret key", + Severity: "MEDIUM", + Regex: MustCompile(`FLW(PUB|SEC)K_TEST-(?i)[a-h0-9]{32}-X`), + Keywords: []string{"FLWSECK_TEST-", "FLWPUBK_TEST-"}, + }, + { + ID: "flutterwave-enc-key", + Category: CategoryFlutterwave, + Title: "Flutterwave encrypted key", + Severity: "MEDIUM", + Regex: MustCompile(`FLWSECK_TEST[a-h0-9]{12}`), + Keywords: []string{"FLWSECK_TEST"}, + }, + { + ID: "frameio-api-token", + Category: CategoryFrameio, + Title: "Frame.io API token", + Severity: "LOW", + Regex: MustCompile(`fio-u-(?i)[a-z0-9\-_=]{64}`), + Keywords: []string{"fio-u-"}, + }, + { + ID: "gocardless-api-token", + Category: CategoryGoCardless, + Title: "GoCardless API token", + Severity: "MEDIUM", + Regex: MustCompile(`['\"]live_(?i)[a-z0-9\-_=]{40}['\"]`), + Keywords: []string{"live_"}, + }, + { + ID: "grafana-api-token", + Category: CategoryGrafana, + Title: "Grafana API token", + Severity: "MEDIUM", + Regex: MustCompile(`['\"]eyJrIjoi(?i)[a-z0-9\-_=]{72,92}['\"]`), + Keywords: []string{"eyJrIjoi"}, + }, + { + ID: "hashicorp-tf-api-token", + Category: CategoryHashiCorp, + Title: "HashiCorp Terraform user/org API token", + Severity: "MEDIUM", + Regex: MustCompile(`['\"](?i)[a-z0-9]{14}\.atlasv1\.[a-z0-9\-_=]{60,70}['\"]`), + Keywords: []string{"atlasv1."}, + }, + { + ID: "hubspot-api-token", + Title: "HubSpot API token", + Category: CategoryHubSpot, + Severity: "LOW", + Regex: MustCompile(`(?i)(?Phubspot[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-h0-9]{8}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{12})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"hubspot"}, + }, + { + ID: "intercom-api-token", + Category: CategoryIntercom, + Title: "Intercom API token", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Pintercom[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9=_]{60})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"intercom"}, + }, + { + ID: "intercom-client-secret", + Category: CategoryIntercom, + Title: "Intercom client secret/ID", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Pintercom[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-h0-9]{8}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{12})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"intercom"}, + }, + { + ID: "ionic-api-token", + Category: CategoryIonic, + Title: "Ionic API token", + Regex: MustCompile(`(?i)(ionic[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](ion_[a-z0-9]{42})['\"]`), + Keywords: []string{"ionic"}, + }, + { + ID: "jwt-token", + Category: CategoryJWT, + Title: "JWT token", + Severity: "MEDIUM", + Regex: MustCompile(`ey[a-zA-Z0-9]{17,}\.ey[a-zA-Z0-9\/\\_-]{17,}\.(?:[a-zA-Z0-9\/\\_-]{10,}={0,2})?`), + Keywords: []string{"jwt"}, + }, + { + ID: "linear-api-token", + Category: CategoryLinear, + Title: "Linear API token", + Severity: "MEDIUM", + Regex: MustCompile(`lin_api_(?i)[a-z0-9]{40}`), + Keywords: []string{"lin_api_"}, + }, + { + ID: "linear-client-secret", + Category: CategoryLinear, + Title: "Linear client secret/ID", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Plinear[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-f0-9]{32})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"linear"}, + }, + { + ID: "lob-api-key", + Category: CategoryLob, + Title: "Lob API Key", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Plob[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P(live|test)_[a-f0-9]{35})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"lob"}, + }, + { + ID: "lob-pub-api-key", + Category: CategoryLob, + Title: "Lob Publishable API Key", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Plob[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P(test|live)_pub_[a-f0-9]{31})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"lob"}, + }, + { + ID: "mailchimp-api-key", + Category: CategoryMailchimp, + Title: "Mailchimp API key", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pmailchimp[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-f0-9]{32}-us20)['\"]`), + SecretGroupName: "secret", + Keywords: []string{"mailchimp"}, + }, + { + ID: "mailgun-token", + Category: CategoryMailgun, + Title: "Mailgun private API token", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pmailgun[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P(pub)?key-[a-f0-9]{32})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"mailgun"}, + }, + { + ID: "mailgun-signing-key", + Category: CategoryMailgun, + Title: "Mailgun webhook signing key", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pmailgun[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-h0-9]{32}-[a-h0-9]{8}-[a-h0-9]{8})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"mailgun"}, + }, + { + ID: "mapbox-api-token", + Category: CategoryMapbox, + Title: "Mapbox API token", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(pk\.[a-z0-9]{60}\.[a-z0-9]{22})`), + Keywords: []string{"pk."}, + }, + { + ID: "messagebird-api-token", + Category: CategoryMessageBird, + Title: "MessageBird API token", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pmessagebird[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9]{25})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"messagebird"}, + }, + { + ID: "messagebird-client-id", + Category: CategoryMessageBird, + Title: "MessageBird API client ID", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pmessagebird[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-h0-9]{8}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{12})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"messagebird"}, + }, + { + ID: "new-relic-user-api-key", + Category: CategoryNewRelic, + Title: "New Relic user API Key", + Severity: "MEDIUM", + Regex: MustCompile(`['\"](NRAK-[A-Z0-9]{27})['\"]`), + Keywords: []string{"NRAK-"}, + }, + { + ID: "new-relic-user-api-id", + Category: CategoryNewRelic, + Title: "New Relic user API ID", + Severity: "MEDIUM", + Regex: MustCompile(`(?i)(?Pnewrelic[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[A-Z0-9]{64})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"newrelic"}, + }, + { + ID: "new-relic-browser-api-token", + Category: CategoryNewRelic, + Title: "New Relic ingest browser API token", + Severity: "MEDIUM", + Regex: MustCompile(`['\"](NRJS-[a-f0-9]{19})['\"]`), + Keywords: []string{"NRJS-"}, + }, + { + ID: "npm-access-token", + Category: CategoryNpm, + Title: "npm access token", + Severity: "CRITICAL", + Regex: MustCompile(`['\"](npm_(?i)[a-z0-9]{36})['\"]`), + Keywords: []string{"npm_"}, + }, + { + ID: "planetscale-password", + Category: CategoryPlanetscale, + Title: "PlanetScale password", + Severity: "MEDIUM", + Regex: MustCompile(`pscale_pw_(?i)[a-z0-9\-_\.]{43}`), + Keywords: []string{"pscale_pw_"}, + }, + { + ID: "planetscale-api-token", + Category: CategoryPlanetscale, + Title: "PlanetScale API token", + Severity: "MEDIUM", + Regex: MustCompile(`pscale_tkn_(?i)[a-z0-9\-_\.]{43}`), + Keywords: []string{"pscale_tkn_"}, + }, + { + ID: "postman-api-token", + Category: CategoryPostman, + Title: "Postman API token", + Severity: "MEDIUM", + Regex: MustCompile(`PMAK-(?i)[a-f0-9]{24}\-[a-f0-9]{34}`), + Keywords: []string{"PMAK-"}, + }, + { + ID: "pulumi-api-token", + Category: CategoryPulumi, + Title: "Pulumi API token", + Severity: "HIGH", + Regex: MustCompile(`pul-[a-f0-9]{40}`), + Keywords: []string{"pul-"}, + }, + { + ID: "rubygems-api-token", + Category: CategoryRubyGems, + Title: "Rubygem API token", + Severity: "MEDIUM", + Regex: MustCompile(`rubygems_[a-f0-9]{48}`), + Keywords: []string{"rubygems_"}, + }, + { + ID: "sendgrid-api-token", + Category: CategorySendGrid, + Title: "SendGrid API token", + Severity: "MEDIUM", + Regex: MustCompile(`SG\.(?i)[a-z0-9_\-\.]{66}`), + Keywords: []string{"SG."}, + }, + { + ID: "sendinblue-api-token", + Category: CategorySendinblue, + Title: "Sendinblue API token", + Severity: "LOW", + Regex: MustCompile(`xkeysib-[a-f0-9]{64}\-(?i)[a-z0-9]{16}`), + Keywords: []string{"xkeysib-"}, + }, + { + ID: "shippo-api-token", + Category: CategoryShippo, + Title: "Shippo API token", + Severity: "LOW", + Regex: MustCompile(`shippo_(live|test)_[a-f0-9]{40}`), + Keywords: []string{"shippo_live_", "shippo_test_"}, + }, + { + ID: "linkedin-client-secret", + Category: CategoryLinkedIn, + Title: "LinkedIn Client secret", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Plinkedin[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z]{16})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"linkedin"}, + }, + { + ID: "linkedin-client-id", + Category: CategoryLinkedIn, + Title: "LinkedIn Client ID", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Plinkedin[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9]{14})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"linkedin"}, + }, + { + ID: "twitch-api-token", + Category: CategoryTwitch, + Title: "Twitch API token", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Ptwitch[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](?P[a-z0-9]{30})['\"]`), + SecretGroupName: "secret", + Keywords: []string{"twitch"}, + }, + { + ID: "typeform-api-token", + Category: CategoryTypeform, + Title: "Typeform API token", + Severity: "LOW", + Regex: MustCompile(`(?i)(?Ptypeform[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}(?Ptfp_[a-z0-9\-_\.=]{59})`), + SecretGroupName: "secret", + Keywords: []string{"typeform"}, + }, + { + ID: "dockerconfig-secret", + Category: CategoryDocker, + Title: "Dockerconfig secret exposed", + Severity: "HIGH", + Regex: MustCompile(`(?i)(\.(dockerconfigjson|dockercfg):\s*\|*\s*(?P(ey|ew)+[A-Za-z0-9\/\+=]+))`), + SecretGroupName: "secret", + Keywords: []string{"dockerc"}, + }, +} diff --git a/pkg/fanal/secret/scanner.go b/pkg/fanal/secret/scanner.go new file mode 100644 index 000000000000..c773b9707ae3 --- /dev/null +++ b/pkg/fanal/secret/scanner.go @@ -0,0 +1,504 @@ +package secret + +import ( + "bytes" + "errors" + "os" + "regexp" + "sort" + "strings" + "sync" + + "github.com/samber/lo" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/fanal/log" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var lineSep = []byte{'\n'} + +type Scanner struct { + *Global +} + +type Config struct { + // Enable only specified built-in rules. If only one ID is specified, all other rules are disabled. + // All the built-in rules are enabled if this field is not specified. It doesn't affect custom rules. + EnableBuiltinRuleIDs []string `yaml:"enable-builtin-rules"` + + // Disable rules. It is applied to enabled IDs. + DisableRuleIDs []string `yaml:"disable-rules"` + + // Disable allow rules. + DisableAllowRuleIDs []string `yaml:"disable-allow-rules"` + + CustomRules []Rule `yaml:"rules"` + CustomAllowRules AllowRules `yaml:"allow-rules"` + ExcludeBlock ExcludeBlock `yaml:"exclude-block"` +} + +type Global struct { + Rules []Rule + AllowRules AllowRules + ExcludeBlock ExcludeBlock +} + +// Allow checks if the match is allowed +func (g Global) Allow(match string) bool { + return g.AllowRules.Allow(match) +} + +// AllowPath checks if the path is allowed +func (g Global) AllowPath(path string) bool { + return g.AllowRules.AllowPath(path) +} + +// Regexp adds unmarshalling from YAML for regexp.Regexp +type Regexp struct { + *regexp.Regexp +} + +func MustCompile(str string) *Regexp { + return &Regexp{regexp.MustCompile(str)} +} + +// UnmarshalYAML unmarshals YAML into a regexp.Regexp +func (r *Regexp) UnmarshalYAML(value *yaml.Node) error { + var v string + if err := value.Decode(&v); err != nil { + return err + } + regex, err := regexp.Compile(v) + if err != nil { + return xerrors.Errorf("regexp compile error: %w", err) + } + + r.Regexp = regex + return nil +} + +type Rule struct { + ID string `yaml:"id"` + Category types.SecretRuleCategory `yaml:"category"` + Title string `yaml:"title"` + Severity string `yaml:"severity"` + Regex *Regexp `yaml:"regex"` + Keywords []string `yaml:"keywords"` + Path *Regexp `yaml:"path"` + AllowRules AllowRules `yaml:"allow-rules"` + ExcludeBlock ExcludeBlock `yaml:"exclude-block"` + SecretGroupName string `yaml:"secret-group-name"` +} + +func (s *Scanner) FindLocations(r Rule, content []byte) []Location { + if r.Regex == nil { + return nil + } + + if r.SecretGroupName != "" { + return s.FindSubmatchLocations(r, content) + } + + var locs []Location + indices := r.Regex.FindAllIndex(content, -1) + for _, index := range indices { + loc := Location{ + Start: index[0], + End: index[1], + } + + if s.AllowLocation(r, content, loc) { + continue + } + + locs = append(locs, loc) + } + return locs +} + +func (s *Scanner) FindSubmatchLocations(r Rule, content []byte) []Location { + var submatchLocations []Location + matchsIndices := r.Regex.FindAllSubmatchIndex(content, -1) + for _, matchIndices := range matchsIndices { + matchLocation := Location{ // first two indexes are always start and end of the whole match + Start: matchIndices[0], + End: matchIndices[1], + } + + if s.AllowLocation(r, content, matchLocation) { + continue + } + + matchSubgroupsLocations := r.getMatchSubgroupsLocations(matchIndices) + if len(matchSubgroupsLocations) > 0 { + submatchLocations = append(submatchLocations, matchSubgroupsLocations...) + } + } + return submatchLocations +} + +func (s *Scanner) AllowLocation(r Rule, content []byte, loc Location) bool { + match := string(content[loc.Start:loc.End]) + return s.Allow(match) || r.Allow(match) +} + +func (r *Rule) getMatchSubgroupsLocations(matchLocs []int) []Location { + var locations []Location + for i, name := range r.Regex.SubexpNames() { + if name == r.SecretGroupName { + startLocIndex := 2 * i + endLocIndex := startLocIndex + 1 + locations = append(locations, Location{Start: matchLocs[startLocIndex], End: matchLocs[endLocIndex]}) + } + } + return locations +} + +func (r *Rule) MatchPath(path string) bool { + return r.Path == nil || r.Path.MatchString(path) +} + +func (r *Rule) MatchKeywords(content []byte) bool { + if len(r.Keywords) == 0 { + return true + } + + for _, kw := range r.Keywords { + if bytes.Contains(bytes.ToLower(content), []byte(strings.ToLower(kw))) { + return true + } + } + + return false +} + +func (r *Rule) AllowPath(path string) bool { + return r.AllowRules.AllowPath(path) +} + +func (r *Rule) Allow(match string) bool { + return r.AllowRules.Allow(match) +} + +type AllowRule struct { + ID string `yaml:"id"` + Description string `yaml:"description"` + Regex *Regexp `yaml:"regex"` + Path *Regexp `yaml:"path"` +} + +type AllowRules []AllowRule + +func (rules AllowRules) AllowPath(path string) bool { + for _, rule := range rules { + if rule.Path != nil && rule.Path.MatchString(path) { + return true + } + } + return false +} + +func (rules AllowRules) Allow(match string) bool { + for _, rule := range rules { + if rule.Regex != nil && rule.Regex.MatchString(match) { + return true + } + } + return false +} + +type ExcludeBlock struct { + Description string `yaml:"description"` + Regexes []*Regexp `yaml:"regexes"` +} + +type Location struct { + Start int + End int +} + +func (l Location) Match(loc Location) bool { + return l.Start <= loc.Start && loc.End <= l.End +} + +type Blocks struct { + content []byte + regexes []*Regexp + locs []Location + once *sync.Once +} + +func newBlocks(content []byte, regexes []*Regexp) Blocks { + return Blocks{ + content: content, + regexes: regexes, + once: new(sync.Once), + } +} + +func (b *Blocks) Match(block Location) bool { + b.once.Do(b.find) + for _, loc := range b.locs { + if loc.Match(block) { + return true + } + } + return false +} + +func (b *Blocks) find() { + for _, regex := range b.regexes { + results := regex.FindAllIndex(b.content, -1) + if len(results) == 0 { + continue + } + for _, r := range results { + b.locs = append(b.locs, Location{ + Start: r[0], + End: r[1], + }) + } + } +} + +func ParseConfig(configPath string) (*Config, error) { + // If no config is passed, use built-in rules and allow rules. + if configPath == "" { + return nil, nil + } + + f, err := os.Open(configPath) + if errors.Is(err, os.ErrNotExist) { + // If the specified file doesn't exist, it just uses built-in rules and allow rules. + log.Logger.Debugf("No secret config detected: %s", configPath) + return nil, nil + } else if err != nil { + return nil, xerrors.Errorf("file open error %s: %w", configPath, err) + } + defer f.Close() + + log.Logger.Infof("Loading %s for secret scanning...", configPath) + + var config Config + if err = yaml.NewDecoder(f).Decode(&config); err != nil { + return nil, xerrors.Errorf("secrets config decode error: %w", err) + } + + return &config, nil +} + +func NewScanner(config *Config) Scanner { + // Use the default rules + if config == nil { + return Scanner{Global: &Global{ + Rules: builtinRules, + AllowRules: builtinAllowRules, + }} + } + + enabledRules := builtinRules + if len(config.EnableBuiltinRuleIDs) != 0 { + // Enable only specified built-in rules + enabledRules = lo.Filter(builtinRules, func(v Rule, _ int) bool { + return slices.Contains(config.EnableBuiltinRuleIDs, v.ID) + }) + } + + // Custom rules are enabled regardless of "enable-builtin-rules". + enabledRules = append(enabledRules, config.CustomRules...) + + // Disable specified rules + rules := lo.Filter(enabledRules, func(v Rule, _ int) bool { + return !slices.Contains(config.DisableRuleIDs, v.ID) + }) + + // Disable specified allow rules + allowRules := append(builtinAllowRules, config.CustomAllowRules...) + allowRules = lo.Filter(allowRules, func(v AllowRule, _ int) bool { + return !slices.Contains(config.DisableAllowRuleIDs, v.ID) + }) + + return Scanner{Global: &Global{ + Rules: rules, + AllowRules: allowRules, + ExcludeBlock: config.ExcludeBlock, + }} +} + +type ScanArgs struct { + FilePath string + Content []byte +} + +type Match struct { + Rule Rule + Location Location +} + +func (s *Scanner) Scan(args ScanArgs) types.Secret { + // Global allowed paths + if s.AllowPath(args.FilePath) { + log.Logger.Debugf("Skipped secret scanning on %q matching allowed paths", args.FilePath) + return types.Secret{ + FilePath: args.FilePath, + } + } + + var censored []byte + var copyCensored sync.Once + var matched []Match + + var findings []types.SecretFinding + globalExcludedBlocks := newBlocks(args.Content, s.ExcludeBlock.Regexes) + for _, rule := range s.Rules { + // Check if the file path should be scanned by this rule + if !rule.MatchPath(args.FilePath) { + log.Logger.Debugf("Skipped secret scanning on %q as non-compliant to the rule %q", args.FilePath, rule.ID) + continue + } + + // Check if the file path should be allowed + if rule.AllowPath(args.FilePath) { + log.Logger.Debugf("Skipped secret scanning on %q as allowed", args.FilePath) + continue + } + + // Check if the file content contains keywords and should be scanned + if !rule.MatchKeywords(args.Content) { + continue + } + + // Detect secrets + locs := s.FindLocations(rule, args.Content) + if len(locs) == 0 { + continue + } + + localExcludedBlocks := newBlocks(args.Content, rule.ExcludeBlock.Regexes) + + for _, loc := range locs { + // Skip the secret if it is within excluded blocks. + if globalExcludedBlocks.Match(loc) || localExcludedBlocks.Match(loc) { + continue + } + + matched = append(matched, Match{ + Rule: rule, + Location: loc, + }) + copyCensored.Do(func() { + censored = make([]byte, len(args.Content)) + copy(censored, args.Content) + }) + censored = censorLocation(loc, censored) + } + } + + for _, match := range matched { + findings = append(findings, toFinding(match.Rule, match.Location, censored)) + } + + if len(findings) == 0 { + return types.Secret{} + } + + sort.Slice(findings, func(i, j int) bool { + if findings[i].RuleID != findings[j].RuleID { + return findings[i].RuleID < findings[j].RuleID + } + return findings[i].Match < findings[j].Match + }) + + return types.Secret{ + FilePath: args.FilePath, + Findings: findings, + } +} + +func censorLocation(loc Location, input []byte) []byte { + return append( + input[:loc.Start], + append( + bytes.Repeat([]byte("*"), loc.End-loc.Start), + input[loc.End:]..., + )..., + ) +} + +func toFinding(rule Rule, loc Location, content []byte) types.SecretFinding { + startLine, endLine, code, matchLine := findLocation(loc.Start, loc.End, content) + + return types.SecretFinding{ + RuleID: rule.ID, + Category: rule.Category, + Severity: lo.Ternary(rule.Severity == "", "UNKNOWN", rule.Severity), + Title: rule.Title, + Match: matchLine, + StartLine: startLine, + EndLine: endLine, + Code: code, + } +} + +const secretHighlightRadius = 2 // number of lines above + below each secret to include in code output + +func findLocation(start, end int, content []byte) (int, int, types.Code, string) { + startLineNum := bytes.Count(content[:start], lineSep) + + lineStart := bytes.LastIndex(content[:start], lineSep) + if lineStart == -1 { + lineStart = 0 + } else { + lineStart += 1 + } + + lineEnd := bytes.Index(content[start:], lineSep) + if lineEnd == -1 { + lineEnd = len(content) + } else { + lineEnd += start + } + + if lineEnd-lineStart > 100 { + lineStart = lo.Ternary(start-30 < 0, 0, start-30) + lineEnd = lo.Ternary(end+20 > len(content), len(content), end+20) + } + matchLine := string(content[lineStart:lineEnd]) + endLineNum := startLineNum + bytes.Count(content[start:end], lineSep) + + var code types.Code + + lines := bytes.Split(content, lineSep) + codeStart := lo.Ternary(startLineNum-secretHighlightRadius < 0, 0, startLineNum-secretHighlightRadius) + codeEnd := lo.Ternary(endLineNum+secretHighlightRadius > len(lines), len(lines), endLineNum+secretHighlightRadius) + + rawLines := lines[codeStart:codeEnd] + var foundFirst bool + for i, rawLine := range rawLines { + strRawLine := string(rawLine) + realLine := codeStart + i + inCause := realLine >= startLineNum && realLine <= endLineNum + code.Lines = append(code.Lines, types.Line{ + Number: codeStart + i + 1, + Content: strRawLine, + IsCause: inCause, + Highlighted: strRawLine, + FirstCause: !foundFirst && inCause, + LastCause: false, + }) + foundFirst = foundFirst || inCause + } + if len(code.Lines) > 0 { + for i := len(code.Lines) - 1; i >= 0; i-- { + if code.Lines[i].IsCause { + code.Lines[i].LastCause = true + break + } + } + } + + return startLineNum + 1, endLineNum + 1, code, matchLine +} diff --git a/pkg/fanal/secret/scanner_test.go b/pkg/fanal/secret/scanner_test.go new file mode 100644 index 000000000000..2e43047f9d53 --- /dev/null +++ b/pkg/fanal/secret/scanner_test.go @@ -0,0 +1,952 @@ +package secret_test + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/aquasecurity/trivy/pkg/fanal/log" + "github.com/aquasecurity/trivy/pkg/fanal/secret" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestMain(m *testing.M) { + logger, _ := zap.NewDevelopment(zap.IncreaseLevel(zapcore.FatalLevel)) + log.SetLogger(logger.Sugar()) + os.Exit(m.Run()) +} + +func TestSecretScanner(t *testing.T) { + wantFinding1 := types.SecretFinding{ + RuleID: "rule1", + Category: "general", + Title: "Generic Rule", + Severity: "HIGH", + StartLine: 2, + EndLine: 2, + Match: "generic secret line secret=\"*********\"", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "--- ignore block start ---", + Highlighted: "--- ignore block start ---", + }, + { + Number: 2, + Content: "generic secret line secret=\"*********\"", + Highlighted: "generic secret line secret=\"*********\"", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + Content: "--- ignore block stop ---", + Highlighted: "--- ignore block stop ---", + }, + }, + }, + } + wantFinding2 := types.SecretFinding{ + RuleID: "rule1", + Category: "general", + Title: "Generic Rule", + Severity: "HIGH", + StartLine: 4, + EndLine: 4, + Match: "secret=\"**********\"", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 2, + Content: "generic secret line secret=\"*********\"", + Highlighted: "generic secret line secret=\"*********\"", + }, + { + Number: 3, + Content: "--- ignore block stop ---", + Highlighted: "--- ignore block stop ---", + }, + { + Number: 4, + Content: "secret=\"**********\"", + Highlighted: "secret=\"**********\"", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 5, + Content: "credentials: { user: \"username\" password: \"123456789\" }", + Highlighted: "credentials: { user: \"username\" password: \"123456789\" }", + }, + }, + }, + } + wantFindingRegexDisabled := types.SecretFinding{ + RuleID: "rule1", + Category: "general", + Title: "Generic Rule", + Severity: "HIGH", + StartLine: 4, + EndLine: 4, + Match: "secret=\"**********\"", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 2, + Content: "generic secret line secret=\"somevalue\"", + Highlighted: "generic secret line secret=\"somevalue\"", + }, + { + Number: 3, + Content: "--- ignore block stop ---", + Highlighted: "--- ignore block stop ---", + }, + { + Number: 4, + Content: "secret=\"**********\"", + Highlighted: "secret=\"**********\"", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 5, + Content: "credentials: { user: \"username\" password: \"123456789\" }", + Highlighted: "credentials: { user: \"username\" password: \"123456789\" }", + }, + }, + }, + } + wantFinding3 := types.SecretFinding{ + RuleID: "rule1", + Category: "general", + Title: "Generic Rule", + Severity: "HIGH", + StartLine: 5, + EndLine: 5, + Match: "credentials: { user: \"********\" password: \"*********\" }", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 3, + Content: "--- ignore block stop ---", + Highlighted: "--- ignore block stop ---", + }, + { + Number: 4, + Content: "secret=\"othervalue\"", + Highlighted: "secret=\"othervalue\"", + }, + { + Number: 5, + Content: "credentials: { user: \"********\" password: \"*********\" }", + Highlighted: "credentials: { user: \"********\" password: \"*********\" }", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFinding4 := types.SecretFinding{ + RuleID: "rule1", + Category: "general", + Title: "Generic Rule", + Severity: "HIGH", + StartLine: 5, + EndLine: 5, + Match: "credentials: { user: \"********\" password: \"*********\" }", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 3, + Content: "--- ignore block stop ---", + Highlighted: "--- ignore block stop ---", + }, + { + Number: 4, + Content: "secret=\"othervalue\"", + Highlighted: "secret=\"othervalue\"", + }, + { + Number: 5, + Content: "credentials: { user: \"********\" password: \"*********\" }", + Highlighted: "credentials: { user: \"********\" password: \"*********\" }", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFinding5 := types.SecretFinding{ + RuleID: "aws-access-key-id", + Category: secret.CategoryAWS, + Title: "AWS Access Key ID", + Severity: "CRITICAL", + StartLine: 2, + EndLine: 2, + Match: "AWS_ACCESS_KEY_ID=********************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "'AWS_secret_KEY'=\"****************************************\"", + Highlighted: "'AWS_secret_KEY'=\"****************************************\"", + }, + { + Number: 2, + Content: "AWS_ACCESS_KEY_ID=********************", + Highlighted: "AWS_ACCESS_KEY_ID=********************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + Content: "\"aws_account_ID\":'1234-5678-9123'", + Highlighted: "\"aws_account_ID\":'1234-5678-9123'", + }, + }, + }, + } + wantFinding5a := types.SecretFinding{ + RuleID: "aws-access-key-id", + Category: secret.CategoryAWS, + Title: "AWS Access Key ID", + Severity: "CRITICAL", + StartLine: 2, + EndLine: 2, + Match: "AWS_ACCESS_KEY_ID=********************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "GITHUB_PAT=****************************************", + Highlighted: "GITHUB_PAT=****************************************", + }, + { + Number: 2, + Content: "AWS_ACCESS_KEY_ID=********************", + Highlighted: "AWS_ACCESS_KEY_ID=********************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFindingPATDisabled := types.SecretFinding{ + RuleID: "aws-access-key-id", + Category: secret.CategoryAWS, + Title: "AWS Access Key ID", + Severity: "CRITICAL", + StartLine: 2, + EndLine: 2, + Match: "AWS_ACCESS_KEY_ID=********************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "GITHUB_PAT=ghp_012345678901234567890123456789abcdef", + Highlighted: "GITHUB_PAT=ghp_012345678901234567890123456789abcdef", + }, + { + Number: 2, + Content: "AWS_ACCESS_KEY_ID=********************", + Highlighted: "AWS_ACCESS_KEY_ID=********************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFinding6 := types.SecretFinding{ + RuleID: "github-pat", + Category: secret.CategoryGitHub, + Title: "GitHub Personal Access Token", + Severity: "CRITICAL", + StartLine: 1, + EndLine: 1, + Match: "GITHUB_PAT=****************************************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "GITHUB_PAT=****************************************", + Highlighted: "GITHUB_PAT=****************************************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 2, + Content: "AWS_ACCESS_KEY_ID=********************", + Highlighted: "AWS_ACCESS_KEY_ID=********************", + }, + }, + }, + } + wantFindingGitHubPAT := types.SecretFinding{ + RuleID: "github-fine-grained-pat", + Category: secret.CategoryGitHub, + Title: "GitHub Fine-grained personal access tokens", + Severity: "CRITICAL", + StartLine: 1, + EndLine: 1, + Match: "GITHUB_TOKEN=*********************************************************************************************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "GITHUB_TOKEN=*********************************************************************************************", + Highlighted: "GITHUB_TOKEN=*********************************************************************************************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFindingGHButDisableAWS := types.SecretFinding{ + RuleID: "github-pat", + Category: secret.CategoryGitHub, + Title: "GitHub Personal Access Token", + Severity: "CRITICAL", + StartLine: 1, + EndLine: 1, + Match: "GITHUB_PAT=****************************************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "GITHUB_PAT=****************************************", + Highlighted: "GITHUB_PAT=****************************************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 2, + Content: "AWS_ACCESS_KEY_ID=AKIA0123456789ABCDEF", + Highlighted: "AWS_ACCESS_KEY_ID=AKIA0123456789ABCDEF", + }, + }, + }, + } + wantFinding7 := types.SecretFinding{ + RuleID: "github-pat", + Category: secret.CategoryGitHub, + Title: "GitHub Personal Access Token", + Severity: "CRITICAL", + StartLine: 1, + EndLine: 1, + Match: "aaaaaaaaaaaaaaaaaa GITHUB_PAT=**************************************** bbbbbbbbbbbbbbbbbbb", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa GITHUB_PAT=**************************************** bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + Highlighted: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa GITHUB_PAT=**************************************** bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFinding8 := types.SecretFinding{ + RuleID: "rule1", + Category: "general", + Title: "Generic Rule", + Severity: "UNKNOWN", + StartLine: 2, + EndLine: 2, + Match: "generic secret line secret=\"*********\"", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "--- ignore block start ---", + Highlighted: "--- ignore block start ---", + }, + { + Number: 2, + Content: "generic secret line secret=\"*********\"", + Highlighted: "generic secret line secret=\"*********\"", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + Content: "--- ignore block stop ---", + Highlighted: "--- ignore block stop ---", + }, + }, + }, + } + wantFinding9 := types.SecretFinding{ + RuleID: "aws-secret-access-key", + Category: secret.CategoryAWS, + Title: "AWS Secret Access Key", + Severity: "CRITICAL", + StartLine: 1, + EndLine: 1, + Match: `'AWS_secret_KEY'="****************************************"`, + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "'AWS_secret_KEY'=\"****************************************\"", + Highlighted: "'AWS_secret_KEY'=\"****************************************\"", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 2, + Content: "AWS_ACCESS_KEY_ID=********************", + Highlighted: "AWS_ACCESS_KEY_ID=********************", + }, + }, + }, + } + wantFinding10 := types.SecretFinding{ + RuleID: "aws-secret-access-key", + Category: secret.CategoryAWS, + Title: "AWS Secret Access Key", + Severity: "CRITICAL", + StartLine: 5, + EndLine: 5, + Match: ` "created_by": "ENV aws_sec_key "****************************************",`, + Code: types.Code{ + Lines: []types.Line{ + { + Number: 3, + Content: "\"aws_account_ID\":'1234-5678-9123'", + Highlighted: "\"aws_account_ID\":'1234-5678-9123'", + }, + { + Number: 4, + Content: "AWS_example=AKIAIOSFODNN7EXAMPLE", + Highlighted: "AWS_example=AKIAIOSFODNN7EXAMPLE", + }, + { + Number: 5, + Content: " \"created_by\": \"ENV aws_sec_key \"****************************************\",", + Highlighted: " \"created_by\": \"ENV aws_sec_key \"****************************************\",", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFindingAsymmetricPrivateKeyJson := types.SecretFinding{ + RuleID: "private-key", + Category: secret.CategoryAsymmetricPrivateKey, + Title: "Asymmetric Private Key", + Severity: "HIGH", + StartLine: 1, + EndLine: 1, + Match: "----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************-----END RSA PRIVATE", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "{\"key\": \"-----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************-----END RSA PRIVATE KEY-----\\n\"}", + Highlighted: "{\"key\": \"-----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************-----END RSA PRIVATE KEY-----\\n\"}", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFindingAsymmetricPrivateKey := types.SecretFinding{ + RuleID: "private-key", + Category: secret.CategoryAsymmetricPrivateKey, + Title: "Asymmetric Private Key", + Severity: "HIGH", + StartLine: 1, + EndLine: 1, + Match: "----BEGIN RSA PRIVATE KEY-----****************************************************************************************************************************************************************************************-----END RSA PRIVATE", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "-----BEGIN RSA PRIVATE KEY-----****************************************************************************************************************************************************************************************-----END RSA PRIVATE KEY-----", + Highlighted: "-----BEGIN RSA PRIVATE KEY-----****************************************************************************************************************************************************************************************-----END RSA PRIVATE KEY-----", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFindingAsymmSecretKey := types.SecretFinding{ + RuleID: "private-key", + Category: secret.CategoryAsymmetricPrivateKey, + Title: "Asymmetric Private Key", + Severity: "HIGH", + StartLine: 1, + EndLine: 1, + Match: "----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************-----END RSA PRIVATE", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "-----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************-----END RSA PRIVATE KEY-----", + Highlighted: "-----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************-----END RSA PRIVATE KEY-----", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFindingAlibabaAccessKeyId := types.SecretFinding{ + RuleID: "alibaba-access-key-id", + Category: secret.CategoryAlibaba, + Title: "Alibaba AccessKey ID", + Severity: "HIGH", + StartLine: 2, + EndLine: 2, + Match: "key = ************************,", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "key : LTAI1234567890ABCDEFG123asd", + Highlighted: "key : LTAI1234567890ABCDEFG123asd", + }, + { + Number: 2, + Content: "key = ************************,", + Highlighted: "key = ************************,", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + Content: "asdLTAI1234567890ABCDEFG123", + Highlighted: "asdLTAI1234567890ABCDEFG123", + }, + }, + }, + } + wantFindingDockerKey1 := types.SecretFinding{ + RuleID: "dockerconfig-secret", + Category: secret.CategoryDocker, + Title: "Dockerconfig secret exposed", + Severity: "HIGH", + StartLine: 4, + EndLine: 4, + Match: " .dockercfg: ************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 2, + Content: " .dockerconfigjson: ************", + Highlighted: " .dockerconfigjson: ************", + }, + { + Number: 3, + Content: "data2:", + Highlighted: "data2:", + }, + { + Number: 4, + Content: " .dockercfg: ************", + Highlighted: " .dockercfg: ************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + wantFindingDockerKey2 := types.SecretFinding{ + RuleID: "dockerconfig-secret", + Category: secret.CategoryDocker, + Title: "Dockerconfig secret exposed", + Severity: "HIGH", + StartLine: 2, + EndLine: 2, + Match: " .dockerconfigjson: ************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "data1:", + Highlighted: "data1:", + }, + { + Number: 2, + Content: " .dockerconfigjson: ************", + Highlighted: " .dockerconfigjson: ************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + Content: "data2:", + Highlighted: "data2:", + }, + }, + }, + } + wantFindingHuggingFace := types.SecretFinding{ + RuleID: "hugging-face-access-token", + Category: secret.CategoryHuggingFace, + Title: "Hugging Face Access Token", + Severity: "CRITICAL", + StartLine: 1, + EndLine: 1, + Match: "HF_example_token: ******************************************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "HF_example_token: ******************************************", + Highlighted: "HF_example_token: ******************************************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } + + wantMultiLine := types.SecretFinding{ + RuleID: "multi-line-secret", + Category: "general", + Title: "Generic Rule", + Severity: "HIGH", + StartLine: 2, + EndLine: 2, + Match: "***************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "123", + Highlighted: "123", + }, + { + Number: 2, + Content: "***************", + Highlighted: "***************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + Content: "123", + Highlighted: "123", + }, + }, + }, + } + + tests := []struct { + name string + configPath string + inputFilePath string + want types.Secret + }{ + { + name: "find match", + configPath: filepath.Join("testdata", "config.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.txt"), + Findings: []types.SecretFinding{wantFinding1, wantFinding2}, + }, + }, + { + name: "find aws secrets", + configPath: filepath.Join("testdata", "config.yaml"), + inputFilePath: filepath.Join("testdata", "aws-secrets.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "aws-secrets.txt"), + Findings: []types.SecretFinding{wantFinding5, wantFinding10, wantFinding9}, + }, + }, + { + name: "find Asymmetric Private Key secrets", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: filepath.Join("testdata", "asymmetric-private-secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "asymmetric-private-secret.txt"), + Findings: []types.SecretFinding{wantFindingAsymmetricPrivateKey}, + }, + }, + { + name: "find Alibaba AccessKey ID txt", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: "testdata/alibaba-access-key-id.txt", + want: types.Secret{ + FilePath: "testdata/alibaba-access-key-id.txt", + Findings: []types.SecretFinding{wantFindingAlibabaAccessKeyId}, + }, + }, + { + name: "find Asymmetric Private Key secrets json", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: filepath.Join("testdata", "asymmetric-private-secret.json"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "asymmetric-private-secret.json"), + Findings: []types.SecretFinding{wantFindingAsymmetricPrivateKeyJson}, + }, + }, + { + name: "find Docker registry credentials", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: filepath.Join("testdata", "docker-secrets.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "docker-secrets.txt"), + Findings: []types.SecretFinding{wantFindingDockerKey1, wantFindingDockerKey2}, + }, + }, + { + name: "find Hugging face secret", + configPath: filepath.Join("testdata", "config.yaml"), + inputFilePath: filepath.Join("testdata", "hugging-face-secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "hugging-face-secret.txt"), + Findings: []types.SecretFinding{wantFindingHuggingFace}, + }, + }, + { + name: "include when keyword found", + configPath: filepath.Join("testdata", "config-happy-keywords.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.txt"), + Findings: []types.SecretFinding{wantFinding1, wantFinding2}, + }, + }, + { + name: "exclude when no keyword found", + configPath: filepath.Join("testdata", "config-sad-keywords.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{}, + }, + { + name: "should ignore .md files by default", + configPath: filepath.Join("testdata", "config.yaml"), + inputFilePath: filepath.Join("testdata", "secret.md"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.md"), + }, + }, + { + name: "should disable .md allow rule", + configPath: filepath.Join("testdata", "config-disable-allow-rule-md.yaml"), + inputFilePath: filepath.Join("testdata", "secret.md"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.md"), + Findings: []types.SecretFinding{wantFinding1, wantFinding2}, + }, + }, + { + name: "should find ghp builtin secret", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: filepath.Join("testdata", "builtin-rule-secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "builtin-rule-secret.txt"), + Findings: []types.SecretFinding{wantFinding5a, wantFinding6}, + }, + }, + { + name: "should find GitHub Personal Access Token (classic)", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: "testdata/github-token.txt", + want: types.Secret{ + FilePath: "testdata/github-token.txt", + Findings: []types.SecretFinding{wantFindingGitHubPAT}, + }, + }, + { + name: "should enable github-pat builtin rule, but disable aws-access-key-id rule", + configPath: filepath.Join("testdata", "config-enable-ghp.yaml"), + inputFilePath: filepath.Join("testdata", "builtin-rule-secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "builtin-rule-secret.txt"), + Findings: []types.SecretFinding{wantFindingGHButDisableAWS}, + }, + }, + { + name: "should disable github-pat builtin rule", + configPath: filepath.Join("testdata", "config-disable-ghp.yaml"), + inputFilePath: filepath.Join("testdata", "builtin-rule-secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "builtin-rule-secret.txt"), + Findings: []types.SecretFinding{wantFindingPATDisabled}, + }, + }, + { + name: "should disable custom rule", + configPath: filepath.Join("testdata", "config-disable-rule1.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{}, + }, + { + name: "allow-rule path", + configPath: filepath.Join("testdata", "allow-path.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{}, + }, + { + name: "allow-rule regex inside group", + configPath: filepath.Join("testdata", "allow-regex.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.txt"), + Findings: []types.SecretFinding{wantFinding1}, + }, + }, + { + name: "allow-rule regex outside group", + configPath: filepath.Join("testdata", "allow-regex-outside-group.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{}, + }, + { + name: "exclude-block regexes", + configPath: filepath.Join("testdata", "exclude-block.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.txt"), + Findings: []types.SecretFinding{wantFindingRegexDisabled}, + }, + }, + { + name: "skip examples file", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: filepath.Join("testdata", "example-secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "example-secret.txt"), + }, + }, + { + name: "global allow-rule path", + configPath: filepath.Join("testdata", "global-allow-path.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.txt"), + Findings: nil, + }, + }, + { + name: "global allow-rule regex", + configPath: filepath.Join("testdata", "global-allow-regex.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.txt"), + Findings: []types.SecretFinding{wantFinding1}, + }, + }, + { + name: "global exclude-block regexes", + configPath: filepath.Join("testdata", "global-exclude-block.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.txt"), + Findings: []types.SecretFinding{wantFindingRegexDisabled}, + }, + }, + { + name: "multiple secret groups", + configPath: filepath.Join("testdata", "multiple-secret-groups.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.txt"), + Findings: []types.SecretFinding{wantFinding3, wantFinding4}, + }, + }, + { + name: "truncate long line", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: filepath.Join("testdata", "long-line-secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "long-line-secret.txt"), + Findings: []types.SecretFinding{wantFinding7}, + }, + }, + { + name: "add unknown severity when rule has no severity", + configPath: filepath.Join("testdata", "config-without-severity.yaml"), + inputFilePath: filepath.Join("testdata", "secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "secret.txt"), + Findings: []types.SecretFinding{wantFinding8}, + }, + }, + { + name: "invalid aws secrets", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: filepath.Join("testdata", "invalid-aws-secrets.txt"), + want: types.Secret{}, + }, + { + name: "asymmetric file", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: "testdata/asymmetric-private-key.txt", + want: types.Secret{ + FilePath: "testdata/asymmetric-private-key.txt", + Findings: []types.SecretFinding{wantFindingAsymmSecretKey}, + }, + }, + { + name: "begin/end line symbols without multi-line mode", + configPath: filepath.Join("testdata", "multi-line-off.yaml"), + inputFilePath: "testdata/multi-line.txt", + want: types.Secret{}, + }, + { + name: "begin/end line symbols with multi-line mode", + configPath: filepath.Join("testdata", "multi-line-on.yaml"), + inputFilePath: "testdata/multi-line.txt", + want: types.Secret{ + FilePath: "testdata/multi-line.txt", + Findings: []types.SecretFinding{wantMultiLine}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + content, err := os.ReadFile(tt.inputFilePath) + require.NoError(t, err) + + content = bytes.ReplaceAll(content, []byte("\r"), []byte("")) + + c, err := secret.ParseConfig(tt.configPath) + require.NoError(t, err) + + s := secret.NewScanner(c) + got := s.Scan(secret.ScanArgs{ + FilePath: tt.inputFilePath, + Content: content, + }, + ) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/secret/testdata/alibaba-access-key-id.txt b/pkg/fanal/secret/testdata/alibaba-access-key-id.txt new file mode 100644 index 000000000000..f7d92c446ea9 --- /dev/null +++ b/pkg/fanal/secret/testdata/alibaba-access-key-id.txt @@ -0,0 +1,6 @@ +key : LTAI1234567890ABCDEFG123asd +key = LTAI1234567890ABCDEFG123, +asdLTAI1234567890ABCDEFG123 +asDLTAI1234567890ABCDEFG123 +as1LTAI1234567890ABCDEFG123 +key : LTAI1234567890ABCDEFG123Asd \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/allow-path.yaml b/pkg/fanal/secret/testdata/allow-path.yaml new file mode 100644 index 000000000000..520b028a30a7 --- /dev/null +++ b/pkg/fanal/secret/testdata/allow-path.yaml @@ -0,0 +1,12 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret + allow-rules: + - description: skip text files + path: .*\.txt +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/allow-regex-outside-group.yaml b/pkg/fanal/secret/testdata/allow-regex-outside-group.yaml new file mode 100644 index 000000000000..25162d8254e0 --- /dev/null +++ b/pkg/fanal/secret/testdata/allow-regex-outside-group.yaml @@ -0,0 +1,12 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)line secret(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret + allow-rules: + - description: skip line + regex: line +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/allow-regex.yaml b/pkg/fanal/secret/testdata/allow-regex.yaml new file mode 100644 index 000000000000..cad82e86b0c6 --- /dev/null +++ b/pkg/fanal/secret/testdata/allow-regex.yaml @@ -0,0 +1,12 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret + allow-rules: + - description: skip other + regex: other +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/asymmetric-private-key.txt b/pkg/fanal/secret/testdata/asymmetric-private-key.txt new file mode 100644 index 000000000000..926230bd92f0 --- /dev/null +++ b/pkg/fanal/secret/testdata/asymmetric-private-key.txt @@ -0,0 +1,4 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAu/Nua0/1y08gkbnBfKd6VDHia8Na0ATgMQqZ4YEbi/t73g84IEPQPkLbPF3X De++JA1QzqTZbbePLsW44DbbgMX/5jj+Sh2SvGe5IXNeNwyyMox+DKQccJUPxbCxnhF/gSnF76cM BXJC63nDnGawz4g3qwU1+0sfyKG0ixFI3e3992fk6QnF49Cv5iqwjgKPIZjgfoM70r71XDKJjVTC rJdSBfyQwX2TU0CncglvJSzhhcuTeQZWldbk/BHjxINrqQIxKaG2OfBgkupPjnrImzSAUE9a/gIS REUVSamc69qqQnXER3Jmoy8HXiAQdPI+CpVVkI7FCCq4qD7fVqsNhwIDAQABAoIBAQC5707zNr1Q jk0IHR3+9agdFuSJ+08hr1Ei8vvcjN71kqqtuZyqvquKjJVamPMhRGV0QQAKDidTVV5+xPfqSBrK wBYyaXuXUr5RSMNrBjjUeOjo/PfOBaRk8/IQfoaYe3MKEotQVI+d67WsQl9zoFuWU4nO1G7c1Sry TpbPZSAS+6J7fUClUgT9pvg+EpoboXs+voeWTh9r9eracxUmlclVAdS3tP7xMv5R29EBYtjGKbF6 r0Ku/hXJjPu5Eck4/BeciEinVWn/yqSsqd5XKOUwTuLlUyAGWhJKcn/zWgaBYUvknzSmwePvW/W8 iwrEhP4GNHBEHisJHdWPtbCDdOVxAoGBAPqyR/9ocwZ3GhHz3dI53Z6UjKUPtRnxJb19ZS8UVN57 P7yCXpH+L6KhIxo9yx0D5Z4bdNSYTyjl6eFnv0FZA3UXsM2tyY+Ylih1LOqcttehJkK2JaFmuefx d6bcpPJG00EKFDZoTH5bbnrB3uGKUVJ5TMFlUbOgkATJL652VTNTAoGBAL/tVWwlO5ET80BSheJ/ V88rSF4AxK48ZNt5EG7RHph46KukwywPUnWRoFLxRtVP/udZf9Qq164IPGgDrn4E6VTpZwmp7HDv 6P8sSLwJj/YW3y9c57lA4SMoowO2ik09fbBJVvWLeev4n6taDNwgCZ4fuLUtMf/mUU3r80okeUp9 AoGAIySIyTn4HejmQ6v+5XBtK8TBLoZUKc3PL4/7di0QdJusZJ2V6jtKrC6QgCY3adrY/l/08bRk LGSGc62aduume2yVwU9iWPnX2tYKNN1BGFsjxOhJwCVpXCVSU5bMnJXnGU/zY2kdh/0DMLwqpU1B dyE/7EBqwpZ4eeNGBtvZt7cCgYB8jaZJJ6SPkzXiwWtXwTKYJMuzDaaWOGVvtRKACEBlzNmaQrPS jSMDX31/Nku0tVSEiSWW6DLOI1QoYHNGHyPZ0hrnP5pM9LTtnKybM0109ATlNNLA+6Tf70hTaYw5 cjV2STIg6eI2zEO6rRb5Z+U18/onwevX2X1cJ0rdC+yW9QKBgH0xSLUGFZwFDCPE+jKGgqJQme5Q 8oxHs1CTkV4SxeLFNldA9c6uESMppSUwO7wx+NaFAJw9m2Q9Ifmo57pncAx2iVXOA9Jxaa7YFIsL +vKftqLPCPbAPPxkaqQi0Ico/1fzD10znRy66aosPBrbleduiynubgk+GVm9y/R6bDYhR +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/asymmetric-private-secret.json b/pkg/fanal/secret/testdata/asymmetric-private-secret.json new file mode 100644 index 000000000000..9bb6975245d4 --- /dev/null +++ b/pkg/fanal/secret/testdata/asymmetric-private-secret.json @@ -0,0 +1 @@ +{"key": "-----BEGIN RSA PRIVATE KEY-----\n7eUHc9npsdql24xsVK8huo9VKPu/mrDStv8JYRWP4cSUXWWBx5oGpPBP7uaMgQCx\nuks3gKrUL/3LFndE0egKslgGSkoyYb3iHP1X3IFsOtTQz6ZPmzc=\n-----END RSA PRIVATE KEY-----\n"} \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/asymmetric-private-secret.txt b/pkg/fanal/secret/testdata/asymmetric-private-secret.txt new file mode 100644 index 000000000000..4ad9b5cc29e0 --- /dev/null +++ b/pkg/fanal/secret/testdata/asymmetric-private-secret.txt @@ -0,0 +1,5 @@ +-----BEGIN RSA PRIVATE KEY----- +YObVAoGAGsWtK3K2Q4OSf6Z1az3AuDfo8xj9/UBuBQe1Op9RFTXps84foAqEgFwj +7eUHc9npsdql24xsVK8huo9VKPu/mrDStv8JYRWP4cSUXWWBx5oGpPBP7uaMgQCx +uks3gKrUL/3LFndE0egKslgGSkoyYb3iHP1X3IFsOtTQz6ZPmzc= +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/aws-secrets.txt b/pkg/fanal/secret/testdata/aws-secrets.txt new file mode 100644 index 000000000000..737708284303 --- /dev/null +++ b/pkg/fanal/secret/testdata/aws-secrets.txt @@ -0,0 +1,5 @@ +'AWS_secret_KEY'="12ASD34qwe56CXZ78tyH10Tna543VBokN85RHCas" +AWS_ACCESS_KEY_ID=AKIA0123456789ABCDEF +"aws_account_ID":'1234-5678-9123' +AWS_example=AKIAIOSFODNN7EXAMPLE + "created_by": "ENV aws_sec_key "KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYK", \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/builtin-rule-secret.txt b/pkg/fanal/secret/testdata/builtin-rule-secret.txt new file mode 100644 index 000000000000..a8172ee8628d --- /dev/null +++ b/pkg/fanal/secret/testdata/builtin-rule-secret.txt @@ -0,0 +1,2 @@ +GITHUB_PAT=ghp_012345678901234567890123456789abcdef +AWS_ACCESS_KEY_ID=AKIA0123456789ABCDEF \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/config-disable-allow-rule-md.yaml b/pkg/fanal/secret/testdata/config-disable-allow-rule-md.yaml new file mode 100644 index 000000000000..df54e72b1704 --- /dev/null +++ b/pkg/fanal/secret/testdata/config-disable-allow-rule-md.yaml @@ -0,0 +1,12 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret +disable-allow-rules: + - markdown + - tests + + diff --git a/pkg/fanal/secret/testdata/config-disable-ghp.yaml b/pkg/fanal/secret/testdata/config-disable-ghp.yaml new file mode 100644 index 000000000000..cd3c5009ced1 --- /dev/null +++ b/pkg/fanal/secret/testdata/config-disable-ghp.yaml @@ -0,0 +1,5 @@ +disable-rules: + - github-pat + +disable-allow-rules: + - tests diff --git a/pkg/fanal/secret/testdata/config-disable-rule1.yaml b/pkg/fanal/secret/testdata/config-disable-rule1.yaml new file mode 100644 index 000000000000..d1287510229e --- /dev/null +++ b/pkg/fanal/secret/testdata/config-disable-rule1.yaml @@ -0,0 +1,12 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret +disable-rules: + - rule1 + +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/config-enable-ghp.yaml b/pkg/fanal/secret/testdata/config-enable-ghp.yaml new file mode 100644 index 000000000000..ad2390e9dafe --- /dev/null +++ b/pkg/fanal/secret/testdata/config-enable-ghp.yaml @@ -0,0 +1,5 @@ +enable-builtin-rules: + - github-pat + +disable-allow-rules: + - tests diff --git a/pkg/fanal/secret/testdata/config-happy-keywords.yaml b/pkg/fanal/secret/testdata/config-happy-keywords.yaml new file mode 100644 index 000000000000..b430410fb801 --- /dev/null +++ b/pkg/fanal/secret/testdata/config-happy-keywords.yaml @@ -0,0 +1,11 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret + keywords: [secret] + +disable-allow-rules: + - tests diff --git a/pkg/fanal/secret/testdata/config-sad-keywords.yaml b/pkg/fanal/secret/testdata/config-sad-keywords.yaml new file mode 100644 index 000000000000..d0320e910f1d --- /dev/null +++ b/pkg/fanal/secret/testdata/config-sad-keywords.yaml @@ -0,0 +1,11 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret + keywords: [bla] + +disable-allow-rules: + - tests diff --git a/pkg/fanal/secret/testdata/config-without-severity.yaml b/pkg/fanal/secret/testdata/config-without-severity.yaml new file mode 100644 index 000000000000..38443f858442 --- /dev/null +++ b/pkg/fanal/secret/testdata/config-without-severity.yaml @@ -0,0 +1,8 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + regex: (?i)(?P(secret))(=|:).{0,5}['"](?Psomevalue)['"] + secret-group-name: secret +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/config.yaml b/pkg/fanal/secret/testdata/config.yaml new file mode 100644 index 000000000000..d14f45b97512 --- /dev/null +++ b/pkg/fanal/secret/testdata/config.yaml @@ -0,0 +1,10 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret + +disable-allow-rules: + - tests diff --git a/pkg/fanal/secret/testdata/docker-secrets.txt b/pkg/fanal/secret/testdata/docker-secrets.txt new file mode 100644 index 000000000000..9bd5b12cfd38 --- /dev/null +++ b/pkg/fanal/secret/testdata/docker-secrets.txt @@ -0,0 +1,4 @@ +data1: + .dockerconfigjson: eyE1x2a3MpLe +data2: + .dockercfg: ewE1x2a3MpLe \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/example-secret.txt b/pkg/fanal/secret/testdata/example-secret.txt new file mode 100644 index 000000000000..05dd97aa1046 --- /dev/null +++ b/pkg/fanal/secret/testdata/example-secret.txt @@ -0,0 +1 @@ +'AWS_secret_KEY'="12ASD34qwe56CXZ78tyH10Tna543VBokN85RHCas" \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/exclude-block.yaml b/pkg/fanal/secret/testdata/exclude-block.yaml new file mode 100644 index 000000000000..26ba2ac28e9d --- /dev/null +++ b/pkg/fanal/secret/testdata/exclude-block.yaml @@ -0,0 +1,13 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret + exclude-block: + description: exclude blocks + regexes: + - --- ignore block start ---(.|\s)*--- ignore block stop --- +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/github-token.txt b/pkg/fanal/secret/testdata/github-token.txt new file mode 100644 index 000000000000..6c7026200bbb --- /dev/null +++ b/pkg/fanal/secret/testdata/github-token.txt @@ -0,0 +1 @@ +GITHUB_TOKEN=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 \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/global-allow-path.yaml b/pkg/fanal/secret/testdata/global-allow-path.yaml new file mode 100644 index 000000000000..a5e630ae8577 --- /dev/null +++ b/pkg/fanal/secret/testdata/global-allow-path.yaml @@ -0,0 +1,10 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret +allow-rules: + - description: skip text files + path: .*\.txt \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/global-allow-regex.yaml b/pkg/fanal/secret/testdata/global-allow-regex.yaml new file mode 100644 index 000000000000..dd44e2460d37 --- /dev/null +++ b/pkg/fanal/secret/testdata/global-allow-regex.yaml @@ -0,0 +1,12 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret +allow-rules: + - description: skip other + regex: other +disable-allow-rules: + - tests diff --git a/pkg/fanal/secret/testdata/global-exclude-block.yaml b/pkg/fanal/secret/testdata/global-exclude-block.yaml new file mode 100644 index 000000000000..0c9e5f09c914 --- /dev/null +++ b/pkg/fanal/secret/testdata/global-exclude-block.yaml @@ -0,0 +1,13 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)(?P(secret))(=|:).{0,5}['"](?P[0-9a-zA-Z\-_=]{8,64})['"] + secret-group-name: secret +exclude-block: + description: exclude blocks + regexes: + - --- ignore block start ---(.|\s)*--- ignore block stop --- +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/hugging-face-secret.txt b/pkg/fanal/secret/testdata/hugging-face-secret.txt new file mode 100644 index 000000000000..99a9d3a45bc0 --- /dev/null +++ b/pkg/fanal/secret/testdata/hugging-face-secret.txt @@ -0,0 +1 @@ +HF_example_token: hf_Testpoiqazwsxedcrfvtgbyhn12345ujmik6789 \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/invalid-aws-secrets.txt b/pkg/fanal/secret/testdata/invalid-aws-secrets.txt new file mode 100644 index 000000000000..4549e696d792 --- /dev/null +++ b/pkg/fanal/secret/testdata/invalid-aws-secrets.txt @@ -0,0 +1,6 @@ +base64: +YXdzLWFjY2AIPA1rZXktaWQgdGVzdCBzdHJpbmc= +web.config file in windows containers: +publicKey="F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293" +length is too long: +"AWS_key":HGM724ngha9785NGKbbar6jk76mnLL80BHJnabyhdngha9785NGKbb6jk76mnLL8 \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/long-line-secret.txt b/pkg/fanal/secret/testdata/long-line-secret.txt new file mode 100644 index 000000000000..5349ada402d4 --- /dev/null +++ b/pkg/fanal/secret/testdata/long-line-secret.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa GITHUB_PAT=ghp_012345678901234567890123456789abcdef bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/multi-line-off.yaml b/pkg/fanal/secret/testdata/multi-line-off.yaml new file mode 100644 index 000000000000..9cde681d92d0 --- /dev/null +++ b/pkg/fanal/secret/testdata/multi-line-off.yaml @@ -0,0 +1,8 @@ +rules: + - id: multi-line-sect + category: general + title: Generic Rule + severity: HIGH + regex: '^multi-line: \d+$' +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/multi-line-on.yaml b/pkg/fanal/secret/testdata/multi-line-on.yaml new file mode 100644 index 000000000000..cf27a9faebe4 --- /dev/null +++ b/pkg/fanal/secret/testdata/multi-line-on.yaml @@ -0,0 +1,8 @@ +rules: + - id: multi-line-secret + category: general + title: Generic Rule + severity: HIGH + regex: '(?m)^multi-line: \d+$' +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/multi-line.txt b/pkg/fanal/secret/testdata/multi-line.txt new file mode 100644 index 000000000000..0b710dd6d4bd --- /dev/null +++ b/pkg/fanal/secret/testdata/multi-line.txt @@ -0,0 +1,3 @@ +123 +multi-line: 123 +123 \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/multiple-secret-groups.yaml b/pkg/fanal/secret/testdata/multiple-secret-groups.yaml new file mode 100644 index 000000000000..bd246ae24c1d --- /dev/null +++ b/pkg/fanal/secret/testdata/multiple-secret-groups.yaml @@ -0,0 +1,9 @@ +rules: + - id: rule1 + category: general + title: Generic Rule + severity: HIGH + regex: (?i)credentials:\s*\{\s*user:\s*['"](?P[0-9a-zA-Z\-_=]{8,64})['"]\s*password:\s*['"](?P[0-9a-zA-Z\-_=]{8,64})['"]\s*\} + secret-group-name: secret +disable-allow-rules: + - tests \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/secret.md b/pkg/fanal/secret/testdata/secret.md new file mode 100644 index 000000000000..f0bad73a2d29 --- /dev/null +++ b/pkg/fanal/secret/testdata/secret.md @@ -0,0 +1,5 @@ +--- ignore block start --- +generic secret line secret="somevalue" +--- ignore block stop --- +secret="othervalue" +credentials: { user: "username" password: "123456789" } \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/secret.txt b/pkg/fanal/secret/testdata/secret.txt new file mode 100644 index 000000000000..f0bad73a2d29 --- /dev/null +++ b/pkg/fanal/secret/testdata/secret.txt @@ -0,0 +1,5 @@ +--- ignore block start --- +generic secret line secret="somevalue" +--- ignore block stop --- +secret="othervalue" +credentials: { user: "username" password: "123456789" } \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/skip-test.yaml b/pkg/fanal/secret/testdata/skip-test.yaml new file mode 100644 index 000000000000..deb523b387fb --- /dev/null +++ b/pkg/fanal/secret/testdata/skip-test.yaml @@ -0,0 +1,2 @@ +disable-allow-rules: + - tests diff --git a/pkg/fanal/test/README.md b/pkg/fanal/test/README.md new file mode 100644 index 000000000000..180c75a73521 --- /dev/null +++ b/pkg/fanal/test/README.md @@ -0,0 +1,7 @@ +# test + +## Directory structure +- integration + - Integration test +- testdata + - This directory contains test images that are used in multiple unit tests \ No newline at end of file diff --git a/pkg/fanal/test/integration/containerd_test.go b/pkg/fanal/test/integration/containerd_test.go new file mode 100644 index 000000000000..9ca993ec0627 --- /dev/null +++ b/pkg/fanal/test/integration/containerd_test.go @@ -0,0 +1,856 @@ +//go:build integration && linux + +package integration + +import ( + "compress/gzip" + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/samber/lo" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/namespaces" + dockercontainer "github.com/docker/docker/api/types/container" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/applier" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + aimage "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/image" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func setupContainerd(t *testing.T, ctx context.Context, namespace string) *containerd.Client { + t.Helper() + tmpDir := t.TempDir() + + containerdDir := filepath.Join(tmpDir, "containerd") + err := os.MkdirAll(containerdDir, os.ModePerm) + require.NoError(t, err) + + socketPath := filepath.Join(containerdDir, "containerd.sock") + + // Set a containerd socket + t.Setenv("CONTAINERD_ADDRESS", socketPath) + t.Setenv("CONTAINERD_NAMESPACE", namespace) + + startContainerd(t, ctx, tmpDir) + + // Retry up to 3 times until containerd is ready + var client *containerd.Client + iteration, _, err := lo.AttemptWhileWithDelay(3, 1*time.Second, func(int, time.Duration) (error, bool) { + client, err = containerd.New(socketPath) + if err != nil { + if !errors.Is(err, os.ErrPermission) { + return err, false // unexpected error + } + return err, true + } + t.Cleanup(func() { + assert.NoError(t, client.Close()) + }) + return nil, false + }) + require.NoErrorf(t, err, "attempted %d times ", iteration) + + return client +} + +func startContainerd(t *testing.T, ctx context.Context, hostPath string) { + t.Helper() + t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") + req := testcontainers.ContainerRequest{ + Name: "containerd", + Image: "ghcr.io/aquasecurity/trivy-test-images/containerd:latest", + Entrypoint: []string{ + "/bin/sh", + "-c", + "/usr/local/bin/containerd", + }, + Mounts: testcontainers.Mounts( + testcontainers.BindMount(hostPath, "/run"), + ), + HostConfigModifier: func(hostConfig *dockercontainer.HostConfig) { + hostConfig.AutoRemove = true + }, + WaitingFor: wait.ForLog("containerd successfully booted"), + } + containerdC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + require.NoError(t, err) + + _, _, err = containerdC.Exec(ctx, []string{ + "chmod", + "666", + "/run/containerd/containerd.sock", + }) + require.NoError(t, err) + + t.Cleanup(func() { + assert.NoError(t, containerdC.Terminate(ctx)) + }) +} + +// Each of these tests imports an image and tags it with the name found in the +// `imageName` field. Then, the containerd store is searched by the reference +// provided in the `searchName` field. +func TestContainerd_SearchLocalStoreByNameOrDigest(t *testing.T) { + // Each architecture needs different images and test cases. + // Currently only amd64 architecture is supported + if runtime.GOARCH != "amd64" { + t.Skip("'Containerd' test only supports amd64 architecture") + } + + digest := "sha256:f12582b2f2190f350e3904462c1c23aaf366b4f76705e97b199f9bbded1d816a" + basename := "hello" + tag := "world" + importedImageOriginalName := "ghcr.io/aquasecurity/trivy-test-images:alpine-310" + + tests := []struct { + name string + imageName string + searchName string + wantErr bool + }{ + { + name: "familiarName:tag", + imageName: fmt.Sprintf("%s:%s", basename, tag), + searchName: fmt.Sprintf("%s:%s", basename, tag), + }, + { + name: "compound/name:tag", + imageName: fmt.Sprintf("say/%s:%s", basename, tag), + searchName: fmt.Sprintf("say/%s:%s", basename, tag), + }, + { + name: "docker.io/library/name:tag", + imageName: fmt.Sprintf("docker.io/library/%s:%s", basename, tag), + searchName: fmt.Sprintf("docker.io/library/%s:%s", basename, tag), + }, + { + name: "other-registry.io/library/name:tag", + imageName: fmt.Sprintf("other-registry.io/library/%s:%s", basename, tag), + searchName: fmt.Sprintf("other-registry.io/library/%s:%s", basename, tag), + }, + { + name: "other-registry.io/library/name:wrongTag should fail", + imageName: fmt.Sprintf("other-registry.io/library/%s:%s", basename, tag), + searchName: fmt.Sprintf("other-registry.io/library/%s:badtag", basename), + wantErr: true, + }, + { + name: "other-registry.io/library/wrongName:tag should fail", + imageName: fmt.Sprintf("other-registry.io/library/%s:%s", basename, tag), + searchName: fmt.Sprintf("other-registry.io/library/badname:%s", tag), + wantErr: true, + }, + { + name: "digest should succeed", + imageName: "", + searchName: digest, + }, + { + name: "wrong digest should fail", + imageName: "", + searchName: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + wantErr: true, + }, + { + name: "name@digest", + imageName: fmt.Sprintf("%s:%s", basename, tag), + searchName: fmt.Sprintf("hello@%s", digest), + }, + { + name: "compound/name@digest", + imageName: fmt.Sprintf("compound/%s:%s", basename, tag), + searchName: fmt.Sprintf("compound/%s@%s", basename, digest), + }, + { + name: "docker.io/library/name@digest", + imageName: fmt.Sprintf("docker.io/library/%s:%s", basename, tag), + searchName: fmt.Sprintf("docker.io/library/%s@%s", basename, digest), + }, + { + name: "otherdomain.io/name@digest", + imageName: fmt.Sprintf("otherdomain.io/%s:%s", basename, tag), + searchName: fmt.Sprintf("otherdomain.io/%s@%s", basename, digest), + }, + { + name: "wrongName@digest should fail", + imageName: fmt.Sprintf("%s:%s", basename, tag), + searchName: fmt.Sprintf("badname@%s", digest), + wantErr: true, + }, + { + name: "compound/wrongName@digest should fail", + imageName: fmt.Sprintf("compound/%s:%s", basename, tag), + searchName: fmt.Sprintf("compound/badname@%s", digest), + wantErr: true, + }, + } + + namespace := "default" + ctx := namespaces.WithNamespace(context.Background(), namespace) + client := setupContainerd(t, ctx, namespace) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + archive, err := os.Open("../../../../integration/testdata/fixtures/images/alpine-310.tar.gz") + require.NoError(t, err) + + uncompressedArchive, err := gzip.NewReader(archive) + require.NoError(t, err) + defer archive.Close() + + cacheDir := t.TempDir() + c, err := cache.NewFSCache(cacheDir) + require.NoError(t, err) + + imgs, err := client.Import(ctx, uncompressedArchive) + require.NoError(t, err) + _ = imgs + + digestTest := tt.imageName == "" + + if !digestTest { + // Tag the image, taken from the code in `ctr image tag...` + imgs[0].Name = tt.imageName + _, err = client.ImageService().Create(ctx, imgs[0]) + require.NoError(t, err) + + // Remove the image by its original name, to ensure the image + // is known only by the tag we have given it. + err = client.ImageService().Delete(ctx, importedImageOriginalName, images.SynchronousDelete()) + require.NoError(t, err) + } + + t.Cleanup(func() { + for _, img := range imgs { + err = client.ImageService().Delete(ctx, img.Name, images.SynchronousDelete()) + require.NoError(t, err) + } + }) + + // enable only containerd + img, cleanup, err := image.NewContainerImage(ctx, tt.searchName, + types.ImageOptions{ImageSources: types.ImageSources{types.ContainerdImageSource}}) + defer cleanup() + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + ar, err := aimage.NewArtifact(img, c, artifact.Option{}) + require.NoError(t, err) + + ref, err := ar.Inspect(ctx) + require.NoError(t, err) + + if digestTest { + actualDigest := strings.Split(ref.ImageMetadata.RepoDigests[0], "@")[1] + require.Equal(t, tt.searchName, actualDigest) + return + } + + require.Equal(t, tt.searchName, ref.Name) + }) + } +} + +func TestContainerd_LocalImage(t *testing.T) { + localImageTestWithNamespace(t, "default") +} + +func TestContainerd_LocalImage_Alternative_Namespace(t *testing.T) { + localImageTestWithNamespace(t, "test") +} + +func localImageTestWithNamespace(t *testing.T, namespace string) { + // Each architecture needs different images and test cases. + // Currently only amd64 architecture is supported + if runtime.GOARCH != "amd64" { + t.Skip("'Containerd' test only supports amd64 architecture") + } + + tests := []struct { + name string + imageName string + tarArchive string + wantMetadata types.ImageMetadata + }{ + { + name: "alpine 3.10", + imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", + tarArchive: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", + wantMetadata: types.ImageMetadata{ + ID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", + DiffIDs: []string{ + "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + }, + RepoTags: []string{"ghcr.io/aquasecurity/trivy-test-images:alpine-310"}, + RepoDigests: []string{"ghcr.io/aquasecurity/trivy-test-images@sha256:f12582b2f2190f350e3904462c1c23aaf366b4f76705e97b199f9bbded1d816a"}, + ConfigFile: v1.ConfigFile{ + Architecture: "amd64", + Created: v1.Time{ + Time: time.Date(2019, 8, 20, 20, 19, 55, 211423266, time.UTC), + }, + OS: "linux", + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + }, + }, + }, + Config: v1.Config{ + Cmd: []string{ + "/bin/sh", + }, + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + }, + }, + History: []v1.History{ + { + Created: v1.Time{Time: time.Date(2019, 8, 20, 20, 19, 55, 62606894, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:fe64057fbb83dccb960efabbf1cd8777920ef279a7fa8dbca0a8801c651bdf7c in / ", + }, + { + Created: v1.Time{Time: time.Date(2019, 8, 20, 20, 19, 55, 211423266, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + EmptyLayer: true, + }, + }, + }, + }, + }, + { + name: "vulnimage", + imageName: "ghcr.io/aquasecurity/trivy-test-images:vulnimage", + tarArchive: "../../../../integration/testdata/fixtures/images/vulnimage.tar.gz", + wantMetadata: types.ImageMetadata{ + ID: "sha256:c17083664da903e13e9092fa3a3a1aeee2431aa2728298e3dbcec72f26369c41", + DiffIDs: []string{ + "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + "sha256:dc00fbef458ad3204bbb548e2d766813f593d857b845a940a0de76aed94c94d1", + "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", + "sha256:9bdb2c849099a99c8ab35f6fd7469c623635e8f4479a0a5a3df61e22bae509f6", + "sha256:6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013", + "sha256:83abef706f5ae199af65d1c13d737d0eb36219f0d18e36c6d8ff06159df39a63", + "sha256:c03283c257abd289a30b4f5e9e1345da0e9bfdc6ca398ee7e8fac6d2c1456227", + "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4", + "sha256:82c59ac8ee582542648e634ca5aff9a464c68ff8a054f105a58689fb52209e34", + "sha256:2f4a5c9187c249834ebc28783bd3c65bdcbacaa8baa6620ddaa27846dd3ef708", + "sha256:6ca56f561e677ae06c3bc87a70792642d671a4416becb9a101577c1a6e090e36", + "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + "sha256:4d116f47cb2cc77a88d609b9805f2b011a5d42339b67300166654b3922685ac9", + "sha256:9b1326af1cf81505fd8e596b7f622b679ae5d290e46b25214ba26e4f7c661d60", + "sha256:a66245f885f2a210071e415f0f8ac4f21f5e4eab6c0435b4082e5c3637c411cb", + "sha256:ba17950e91742d6ac7055ea3a053fe764486658ca1ce8188f1e427b1fe2bc4da", + "sha256:6ef42db7800507577383edf1937cb203b9b85f619feed6046594208748ceb52c", + }, + RepoTags: []string{"ghcr.io/aquasecurity/trivy-test-images:vulnimage"}, + RepoDigests: []string{"ghcr.io/aquasecurity/trivy-test-images@sha256:e74abbfd81e00baaf464cf9e09f8b24926e5255171e3150a60aa341ce064688f"}, + ConfigFile: v1.ConfigFile{ + Architecture: "amd64", + Created: v1.Time{ + Time: time.Date(2019, 8, 7, 7, 25, 58, 651649800, time.UTC), + }, + OS: "linux", + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + { + Algorithm: "sha256", + Hex: "0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + { + Algorithm: "sha256", + Hex: "9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + { + Algorithm: "sha256", + Hex: "dc00fbef458ad3204bbb548e2d766813f593d857b845a940a0de76aed94c94d1", + }, + { + Algorithm: "sha256", + Hex: "5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", + }, + { + Algorithm: "sha256", + Hex: "9bdb2c849099a99c8ab35f6fd7469c623635e8f4479a0a5a3df61e22bae509f6", + }, + { + Algorithm: "sha256", + Hex: "6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013", + }, + { + Algorithm: "sha256", + Hex: "83abef706f5ae199af65d1c13d737d0eb36219f0d18e36c6d8ff06159df39a63", + }, + { + Algorithm: "sha256", + Hex: "c03283c257abd289a30b4f5e9e1345da0e9bfdc6ca398ee7e8fac6d2c1456227", + }, + { + Algorithm: "sha256", + Hex: "2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4", + }, + { + Algorithm: "sha256", + Hex: "82c59ac8ee582542648e634ca5aff9a464c68ff8a054f105a58689fb52209e34", + }, + { + Algorithm: "sha256", + Hex: "2f4a5c9187c249834ebc28783bd3c65bdcbacaa8baa6620ddaa27846dd3ef708", + }, + { + Algorithm: "sha256", + Hex: "6ca56f561e677ae06c3bc87a70792642d671a4416becb9a101577c1a6e090e36", + }, + { + Algorithm: "sha256", + Hex: "154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + }, + { + Algorithm: "sha256", + Hex: "b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + { + Algorithm: "sha256", + Hex: "4d116f47cb2cc77a88d609b9805f2b011a5d42339b67300166654b3922685ac9", + }, + { + Algorithm: "sha256", + Hex: "9b1326af1cf81505fd8e596b7f622b679ae5d290e46b25214ba26e4f7c661d60", + }, + { + Algorithm: "sha256", + Hex: "a66245f885f2a210071e415f0f8ac4f21f5e4eab6c0435b4082e5c3637c411cb", + }, + { + Algorithm: "sha256", + Hex: "ba17950e91742d6ac7055ea3a053fe764486658ca1ce8188f1e427b1fe2bc4da", + }, + { + Algorithm: "sha256", + Hex: "6ef42db7800507577383edf1937cb203b9b85f619feed6046594208748ceb52c", + }, + }, + }, + Config: v1.Config{ + Cmd: []string{"composer"}, + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "PHPIZE_DEPS=autoconf \t\tdpkg-dev dpkg \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkgconf \t\tre2c", + "PHP_INI_DIR=/usr/local/etc/php", + "PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2", + "PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2", + "PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie", + "GPG_KEYS=1729F83938DA44E27BA0F4D3DBDB397470D12172 B1B44D8F021E4E2D6021E995DC9FF8D3EE5AF27F", + "PHP_VERSION=7.2.11", + "PHP_URL=https://secure.php.net/get/php-7.2.11.tar.xz/from/this/mirror", + "PHP_ASC_URL=https://secure.php.net/get/php-7.2.11.tar.xz.asc/from/this/mirror", + "PHP_SHA256=da1a705c0bc46410e330fc6baa967666c8cd2985378fb9707c01a8e33b01d985", + "PHP_MD5=", + "COMPOSER_ALLOW_SUPERUSER=1", + "COMPOSER_HOME=/tmp", + "COMPOSER_VERSION=1.7.2", + }, + WorkingDir: "/app", + Entrypoint: []string{ + "/bin/sh", + "/docker-entrypoint.sh", + }, + }, + History: []v1.History{ + { + Created: v1.Time{Time: time.Date(2018, 9, 11, 22, 19, 38, 885299940, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:49f9e47e678d868d5b023482aa8dded71276a241a665c4f8b55ca77269321b34 in / ", + }, + { + Created: v1.Time{Time: time.Date(2018, 9, 11, 22, 19, 39, 58628442, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 26, 59, 951316015, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHPIZE_DEPS=autoconf \t\tdpkg-dev dpkg \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkgconf \t\tre2c", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 1, 470388635, time.UTC)}, + CreatedBy: "/bin/sh -c apk add --no-cache --virtual .persistent-deps \t\tca-certificates \t\tcurl \t\ttar \t\txz \t\tlibressl", + }, + { + Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 2, 432381785, time.UTC)}, + CreatedBy: "/bin/sh -c set -x \t&& addgroup -g 82 -S www-data \t&& adduser -u 82 -D -S -G www-data www-data", + }, + { + Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 2, 715120309, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_INI_DIR=/usr/local/etc/php", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 3, 655421341, time.UTC)}, + CreatedBy: "/bin/sh -c mkdir -p $PHP_INI_DIR/conf.d", + }, + { + Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 3, 931799562, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 4, 210945499, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 4, 523116501, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 4, 795176159, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV GPG_KEYS=1729F83938DA44E27BA0F4D3DBDB397470D12172 B1B44D8F021E4E2D6021E995DC9FF8D3EE5AF27F", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 2, 18, 415761689, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_VERSION=7.2.11", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 2, 18, 599097853, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_URL=https://secure.php.net/get/php-7.2.11.tar.xz/from/this/mirror PHP_ASC_URL=https://secure.php.net/get/php-7.2.11.tar.xz.asc/from/this/mirror", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 2, 18, 782890412, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV PHP_SHA256=da1a705c0bc46410e330fc6baa967666c8cd2985378fb9707c01a8e33b01d985 PHP_MD5=", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 2, 22, 795846753, time.UTC)}, + CreatedBy: "/bin/sh -c set -xe; \t\tapk add --no-cache --virtual .fetch-deps \t\tgnupg \t\twget \t; \t\tmkdir -p /usr/src; \tcd /usr/src; \t\twget -O php.tar.xz \"$PHP_URL\"; \t\tif [ -n \"$PHP_SHA256\" ]; then \t\techo \"$PHP_SHA256 *php.tar.xz\" | sha256sum -c -; \tfi; \tif [ -n \"$PHP_MD5\" ]; then \t\techo \"$PHP_MD5 *php.tar.xz\" | md5sum -c -; \tfi; \t\tif [ -n \"$PHP_ASC_URL\" ]; then \t\twget -O php.tar.xz.asc \"$PHP_ASC_URL\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \t\tfor key in $GPG_KEYS; do \t\t\tgpg --keyserver ha.pool.sks-keyservers.net --recv-keys \"$key\"; \t\tdone; \t\tgpg --batch --verify php.tar.xz.asc php.tar.xz; \t\tcommand -v gpgconf > /dev/null && gpgconf --kill all; \t\trm -rf \"$GNUPGHOME\"; \tfi; \t\tapk del .fetch-deps", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 2, 23, 71406376, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) COPY file:207c686e3fed4f71f8a7b245d8dcae9c9048d276a326d82b553c12a90af0c0ca in /usr/local/bin/ ", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 7, 13, 93396680, time.UTC)}, + CreatedBy: "/bin/sh -c set -xe \t&& apk add --no-cache --virtual .build-deps \t\t$PHPIZE_DEPS \t\tcoreutils \t\tcurl-dev \t\tlibedit-dev \t\tlibressl-dev \t\tlibsodium-dev \t\tlibxml2-dev \t\tsqlite-dev \t\t&& export CFLAGS=\"$PHP_CFLAGS\" \t\tCPPFLAGS=\"$PHP_CPPFLAGS\" \t\tLDFLAGS=\"$PHP_LDFLAGS\" \t&& docker-php-source extract \t&& cd /usr/src/php \t&& gnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\" \t&& ./configure \t\t--build=\"$gnuArch\" \t\t--with-config-file-path=\"$PHP_INI_DIR\" \t\t--with-config-file-scan-dir=\"$PHP_INI_DIR/conf.d\" \t\t\t\t--enable-option-checking=fatal \t\t\t\t--with-mhash \t\t\t\t--enable-ftp \t\t--enable-mbstring \t\t--enable-mysqlnd \t\t--with-sodium=shared \t\t\t\t--with-curl \t\t--with-libedit \t\t--with-openssl \t\t--with-zlib \t\t\t\t$(test \"$gnuArch\" = 's390x-linux-gnu' && echo '--without-pcre-jit') \t\t\t\t$PHP_EXTRA_CONFIGURE_ARGS \t&& make -j \"$(nproc)\" \t&& make install \t&& { find /usr/local/bin /usr/local/sbin -type f -perm +0111 -exec strip --strip-all '{}' + || true; } \t&& make clean \t\t&& cp -v php.ini-* \"$PHP_INI_DIR/\" \t\t&& cd / \t&& docker-php-source delete \t\t&& runDeps=\"$( \t\tscanelf --needed --nobanner --format '%n#p' --recursive /usr/local \t\t\t| tr ',' '\\n' \t\t\t| sort -u \t\t\t| awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' \t)\" \t&& apk add --no-cache --virtual .php-rundeps $runDeps \t\t&& apk del .build-deps \t\t&& pecl update-channels \t&& rm -rf /tmp/pear ~/.pearrc", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 7, 13, 722586262, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) COPY multi:2cdcedabcf5a3b9ae610fab7848e94bc2f64b4d85710d55fd6f79e44dacf73d8 in /usr/local/bin/ ", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 7, 14, 618087104, time.UTC)}, + CreatedBy: "/bin/sh -c docker-php-ext-enable sodium", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 7, 14, 826981756, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENTRYPOINT [\"docker-php-entrypoint\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 7, 15, 10831572, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"php\" \"-a\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 21, 919735971, time.UTC)}, + CreatedBy: "/bin/sh -c apk --no-cache add git subversion openssh mercurial tini bash patch", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 22, 611763893, time.UTC)}, + CreatedBy: "/bin/sh -c echo \"memory_limit=-1\" > \"$PHP_INI_DIR/conf.d/memory-limit.ini\" && echo \"date.timezone=${PHP_TIMEZONE:-UTC}\" > \"$PHP_INI_DIR/conf.d/date_timezone.ini\"", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 50, 224278478, time.UTC)}, + CreatedBy: "/bin/sh -c apk add --no-cache --virtual .build-deps zlib-dev && docker-php-ext-install zip && runDeps=\"$( scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions | tr ',' '\\n' | sort -u | awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' )\" && apk add --virtual .composer-phpext-rundeps $runDeps && apk del .build-deps", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 50, 503010161, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_ALLOW_SUPERUSER=1", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 50, 775378559, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_HOME=/tmp", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 51, 35012363, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_VERSION=1.7.2", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 52, 491402624, time.UTC)}, + CreatedBy: "/bin/sh -c curl --silent --fail --location --retry 3 --output /tmp/installer.php --url https://raw.githubusercontent.com/composer/getcomposer.org/b107d959a5924af895807021fcef4ffec5a76aa9/web/installer && php -r \" \\$signature = '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061'; \\$hash = hash('SHA384', file_get_contents('/tmp/installer.php')); if (!hash_equals(\\$signature, \\$hash)) { unlink('/tmp/installer.php'); echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; exit(1); }\" && php /tmp/installer.php --no-ansi --install-dir=/usr/bin --filename=composer --version=${COMPOSER_VERSION} && composer --ansi --version --no-interaction && rm -rf /tmp/* /tmp/.htaccess", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 52, 948859545, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) COPY file:295943a303e8f27de4302b6aa3687bce4b1d1392335efaaab9ecd37bec5ab4c5 in /docker-entrypoint.sh ", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 53, 295399872, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) WORKDIR /app", + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 53, 582920705, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ENTRYPOINT [\"/bin/sh\" \"/docker-entrypoint.sh\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 53, 798628678, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"composer\"]", + EmptyLayer: true, + }, + { + Created: v1.Time{Time: time.Date(2019, 8, 7, 7, 25, 57, 211142800, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:842584685f26edb24dc305d76894f51cfda2bad0c24a05e727f9d4905d184a70 in /php-app/composer.lock ", + }, + { + Created: v1.Time{Time: time.Date(2019, 8, 7, 7, 25, 57, 583779000, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:c6d0373d380252b91829a5bb3c81d5b1afa574c91cef7752d18170a231c31f6d in /ruby-app/Gemfile.lock ", + }, + { + Created: v1.Time{Time: time.Date(2019, 8, 7, 7, 25, 57, 921730100, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:54a1c52556a5ebe98fd124f51c25d071f9e29e2714c72c80d6d3d254b9e83386 in /node-app/package-lock.json ", + }, + { + Created: v1.Time{Time: time.Date(2019, 8, 7, 7, 25, 58, 311593100, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:097d32f46acde76c4da9e55f17110d69d02cc6d16c86da907980da335fc0fc5f in /python-app/Pipfile.lock ", + }, + { + Created: v1.Time{Time: time.Date(2019, 8, 7, 7, 25, 58, 651649800, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:7f147d85de19bfb905c260a0c175f227a433259022c163017b96d0efacdcd105 in /rust-app/Cargo.lock ", + }, + }, + }, + }, + }, + } + + t.Helper() + ctx := namespaces.WithNamespace(context.Background(), namespace) + client := setupContainerd(t, ctx, namespace) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cacheDir := t.TempDir() + c, err := cache.NewFSCache(cacheDir) + require.NoError(t, err) + + defer func() { + c.Clear() + c.Close() + }() + + archive, err := os.Open(tt.tarArchive) + require.NoError(t, err) + + uncompressedArchive, err := gzip.NewReader(archive) + require.NoError(t, err) + defer archive.Close() + + _, err = client.Import(ctx, uncompressedArchive) + require.NoError(t, err) + + // Enable only containerd + img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.ImageOptions{ + ImageSources: types.ImageSources{types.ContainerdImageSource}, + }) + require.NoError(t, err) + defer cleanup() + + ar, err := aimage.NewArtifact(img, c, artifact.Option{ + DisabledAnalyzers: []analyzer.Type{ + analyzer.TypeExecutable, + analyzer.TypeLicenseFile, + }, + }) + require.NoError(t, err) + + ref, err := ar.Inspect(ctx) + require.NoError(t, err) + require.Equal(t, tt.wantMetadata, ref.ImageMetadata) + + a := applier.NewApplier(c) + got, err := a.ApplyLayers(ref.ID, ref.BlobIDs) + require.NoError(t, err) + + tag := strings.Split(tt.imageName, ":")[1] + goldenFile := fmt.Sprintf("testdata/goldens/packages/%s.json.golden", tag) + + if *update { + b, err := json.MarshalIndent(got.Packages, "", " ") + require.NoError(t, err) + err = os.WriteFile(goldenFile, b, 0666) + require.NoError(t, err) + } + + // Parse a golden file + golden, err := os.Open(goldenFile) + require.NoError(t, err) + defer golden.Close() + + var wantPkgs types.Packages + err = json.NewDecoder(golden).Decode(&wantPkgs) + require.NoError(t, err) + + // Assert + assert.Equal(t, wantPkgs, got.Packages) + }) + } +} + +func TestContainerd_PullImage(t *testing.T) { + // Each architecture needs different images and test cases. + // Currently only amd64 architecture is supported + if runtime.GOARCH != "amd64" { + t.Skip("'Containerd' test only supports amd64 architecture") + } + + tests := []struct { + name string + imageName string + wantMetadata types.ImageMetadata + }{ + { + name: "remote alpine 3.10", + imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", + wantMetadata: types.ImageMetadata{ + ID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", + DiffIDs: []string{ + "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + }, + RepoTags: []string{"ghcr.io/aquasecurity/trivy-test-images:alpine-310"}, + RepoDigests: []string{"ghcr.io/aquasecurity/trivy-test-images@sha256:72c42ed48c3a2db31b7dafe17d275b634664a708d901ec9fd57b1529280f01fb"}, + ConfigFile: v1.ConfigFile{ + Architecture: "amd64", + Created: v1.Time{ + Time: time.Date(2019, 8, 20, 20, 19, 55, 211423266, time.UTC), + }, + OS: "linux", + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []v1.Hash{ + { + Algorithm: "sha256", + Hex: "03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + }, + }, + }, + Config: v1.Config{ + Cmd: []string{ + "/bin/sh", + }, + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + }, + ArgsEscaped: false, + }, + History: []v1.History{ + { + Created: v1.Time{time.Date(2019, 8, 20, 20, 19, 55, 62606894, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) ADD file:fe64057fbb83dccb960efabbf1cd8777920ef279a7fa8dbca0a8801c651bdf7c in / ", + }, + { + Created: v1.Time{time.Date(2019, 8, 20, 20, 19, 55, 211423266, time.UTC)}, + CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", + EmptyLayer: true, + }, + }, + }, + }, + }, + } + + namespace := "default" + ctx := namespaces.WithNamespace(context.Background(), namespace) + client := setupContainerd(t, ctx, namespace) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cacheDir := t.TempDir() + c, err := cache.NewFSCache(cacheDir) + require.NoError(t, err) + + defer func() { + c.Clear() + c.Close() + }() + + _, err = client.Pull(ctx, tt.imageName) + require.NoError(t, err) + + // Enable only containerd + img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.ImageOptions{ + ImageSources: types.ImageSources{types.ContainerdImageSource}, + }) + require.NoError(t, err) + defer cleanup() + + art, err := aimage.NewArtifact(img, c, artifact.Option{ + DisabledAnalyzers: []analyzer.Type{ + analyzer.TypeExecutable, + analyzer.TypeLicenseFile, + }, + }) + require.NoError(t, err) + require.NotNil(t, art) + + ref, err := art.Inspect(context.Background()) + require.NoError(t, err) + require.Equal(t, tt.wantMetadata, ref.ImageMetadata) + + a := applier.NewApplier(c) + got, err := a.ApplyLayers(ref.ID, ref.BlobIDs) + require.NoError(t, err) + + // Parse a golden file + tag := strings.Split(tt.imageName, ":")[1] + golden, err := os.Open(fmt.Sprintf("testdata/goldens/packages/%s.json.golden", tag)) + require.NoError(t, err) + + var wantPkgs types.Packages + err = json.NewDecoder(golden).Decode(&wantPkgs) + require.NoError(t, err) + + // Assert + assert.Equal(t, wantPkgs, got.Packages) + }) + } +} diff --git a/pkg/fanal/test/integration/data/registry/auth/htpasswd b/pkg/fanal/test/integration/data/registry/auth/htpasswd new file mode 100644 index 000000000000..87fc507be943 --- /dev/null +++ b/pkg/fanal/test/integration/data/registry/auth/htpasswd @@ -0,0 +1,2 @@ +testuser:$2y$05$6PfNBO51NB6RIVOmy14WSed7tiHV/pETiYldZ/cT78KLPpFnSUWTi + diff --git a/pkg/fanal/test/integration/data/registry/certs/cert.pem b/pkg/fanal/test/integration/data/registry/certs/cert.pem new file mode 100644 index 000000000000..66d2421c2159 --- /dev/null +++ b/pkg/fanal/test/integration/data/registry/certs/cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+DCCAeCgAwIBAgIQdRfi50B0buW3v2MxX5WIlDANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMB4XDTE5MTAxMzE3MjIxMFoXDTIwMTAxMjE3MjIx +MFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAKtXwwgUzfBtf1Hj41cUBkHbwR2hnlGfAXH00LxO9EQdP8zvA1u98aEJ +5zDSSo6g3O+at4izkWC9Niik9kUJiqDJ6DYcN/mZe1UZ6QclsdZrqmLY3C6qD8Zx +M+LyEA67b2OHYKACk9jXk+8XmVhFFI1svZNMo6Ci3soXv32tR2Ydb4OTBhfLk2i5 +w/MNqzoQIanF9zyXAiPnX8SdbrdLV9DAYlOABBDAtSabhffgZSUD9ThYtvS/Sx2M +sT5TAbqbAVRhfBxCK23+6+HebISYUygkdCtvfSSuzdp4gSH3yet/3oaxGiXawcrS +TKJna0MMiWJC769OsdvUgNpELUnoRZ8CAwEAAaNKMEgwDgYDVR0PAQH/BAQDAgWg +MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwEwYDVR0RBAwwCoII +cmVnaXN0cnkwDQYJKoZIhvcNAQELBQADggEBACOR2nkmztD3bZh9nx2MVRE1tkHt +06k5kDPnvYo89Q4R0Qx6fJ6g+aTXEOTbHkwPMR/ZRAswZg6ZMkpo4hq0qf5ueL0G +qHJT6GH0YYr2SqVyxbNHZZqs7nuz5lJLJTQYF95OwEo1qKEfwdA1BU6+p1S95COK +tZngyuQGWp6qGgnzpOhrkC4O6tpXA2lXbgpR87HyzD2GVIt5WXZJX99FXiUp2ZhQ +0yPJwnDUGCbTui1UpBRrM62PwwFNKGg9+pGF+0A6gUWX4ofFApcxKDSdcynT9duO ++4yh/t1KTalzv7W0KtBIGeJWHQAskn2EBFjRrhgmPAlXjP85RVVHHrM2FWc= +-----END CERTIFICATE----- diff --git a/pkg/fanal/test/integration/data/registry/certs/key.pem b/pkg/fanal/test/integration/data/registry/certs/key.pem new file mode 100644 index 000000000000..d8f95ed3f520 --- /dev/null +++ b/pkg/fanal/test/integration/data/registry/certs/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrV8MIFM3wbX9R +4+NXFAZB28EdoZ5RnwFx9NC8TvREHT/M7wNbvfGhCecw0kqOoNzvmreIs5FgvTYo +pPZFCYqgyeg2HDf5mXtVGekHJbHWa6pi2Nwuqg/GcTPi8hAOu29jh2CgApPY15Pv +F5lYRRSNbL2TTKOgot7KF799rUdmHW+DkwYXy5NoucPzDas6ECGpxfc8lwIj51/E +nW63S1fQwGJTgAQQwLUmm4X34GUlA/U4WLb0v0sdjLE+UwG6mwFUYXwcQitt/uvh +3myEmFMoJHQrb30krs3aeIEh98nrf96GsRol2sHK0kyiZ2tDDIliQu+vTrHb1IDa +RC1J6EWfAgMBAAECggEAPnjeNFkZiTrzPBFh94LmkSalr78YsVvPNBR18uK6rwcX +FwpiaBXGPtgUgjl6U6yVQYtgtaf7LpFuaL1UyXEkKWHUl74MLqZmUG4FB8UXpT1W +P3ogNadt3wWxTGLDtgtjHp/ifBTUgTCA0KEDGSr+xl3KdSVxV4Vqw7aK7DC8op8q +CkqHMHW/tbmC/52sbo8rPCDoPcdBiotJuGWrBy3pvvinv4PegPjbdgMskneII7JB +7fyN9O543ViASO6OPQeKXHCefDVU+Bu6jCobZSHyd9ofP9ODYNh2eehWDrLcxUiI +Fv2k9/OYKTkiAZt4n6uOeBIjADQPW3O+pFhIzyh10QKBgQDaLC1ndASTzxjPhCxk +F6KzZKgTfZTESYmcZRWjOdPN+uLZmQ0vUDP8ww1BBKsNWiT1DG+b5t0AcV8GhtRH +SDHfsyUk4+SmX+DSbq7d08SPbXuBw9qj5e4TwLbuhRpEzC6Y/FJowdcIJBDA/Bdl +eFGLKGK4uSEmIf3HMHwuE22sSQKBgQDJDP79U32hW9dkrXCaYwuLGNCTp7OUUvhr +xZ2XpiAd4xTiHArS5oD+uYOtl89gLcC3rl04ONR2czXo1wbUzoGxfc5mMFfDwbu4 +flpc2Unf+OC7MAYI8tweNvPaCh9yM162kA/HBpCmXnU7AvrFxCv8varUD7JouuM6 +GJ0Tq8HSpwKBgAwXRqzlERvf7lAR0yP461tlyKrL2uLF8IpqT7isC8DuloPYp5Jn +r3nilWl1CMXEqSereMP7F2Re5BVIg5svtRPgqb5RoupVhfAjki1Y/xuzxZR7djJ/ +G6Kcm4a8eag2aJ6x3R9RHsmktgj775AACwTbSB1UpTGn0JipZzhmez2BAoGBALNG +om20MFddNPEyczFH1ng/NWYk6U3+ukQnoAIlAYiFAA8roD81iZsaQ5/cue9yfOGN +WtSSYGoLC+xHKXFDf4SMwSqAOxJ0w3adPOiEXYqZO5PPG+KI5N0QdVtdIXNM+Uzj +ox3+7adcnT50xrbo2axyWEzmfllDJPGeFxr+UkQLAoGAU+e8RnpTzSabcCSLI5lN +XZ/YZzylwyFQtuwP4CkWY5ZamRI4xbAGgUrQhJLdALJqa6gY48MjLyiMeREPpmPB +Qy2kGvbvRn6twQApxgwbqDKzHH33nx9PSuZhTR/GQyONGIcpXptrqoDCFChQs7hK +js5gSQbPU3clpXeP3Oq7IAY= +-----END PRIVATE KEY----- diff --git a/pkg/fanal/test/integration/docker/docker.go b/pkg/fanal/test/integration/docker/docker.go new file mode 100644 index 000000000000..13ba250d4444 --- /dev/null +++ b/pkg/fanal/test/integration/docker/docker.go @@ -0,0 +1,126 @@ +package docker + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/url" + "os" + "os/exec" + + "github.com/docker/docker/api/types" + apiregistry "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/client" +) + +type RegistryConfig struct { + URL *url.URL + Username string + Password string +} + +func (c RegistryConfig) GetAuthConfig() apiregistry.AuthConfig { + return apiregistry.AuthConfig{ + Username: c.Username, + Password: c.Password, + ServerAddress: c.URL.Host, + } +} + +func (c RegistryConfig) GetRegistryAuth() (string, error) { + authConfig := apiregistry.AuthConfig{ + Username: c.Username, + Password: c.Password, + } + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(encodedJSON), nil +} + +func (c RegistryConfig) GetBasicAuthorization() string { + return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.Username, c.Password)))) +} + +type Docker struct { + cli *client.Client +} + +func New() (Docker, error) { + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return Docker{}, err + } + return Docker{ + cli: cli, + }, nil +} + +func (d Docker) Login(conf RegistryConfig) error { + auth := conf.GetAuthConfig() + return exec.Command("docker", "login", "-u", auth.Username, "-p", auth.Password, auth.ServerAddress).Run() +} + +func (d Docker) Logout(conf RegistryConfig) error { + auth := conf.GetAuthConfig() + return exec.Command("docker", "logout", auth.ServerAddress).Run() +} + +// ReplicateImage tags the given imagePath and pushes it to the given dest registry. +func (d Docker) ReplicateImage(ctx context.Context, imageRef, imagePath string, dest RegistryConfig) error { + // remove existing Image if any + _, _ = d.cli.ImageRemove(ctx, imageRef, types.ImageRemoveOptions{ + Force: true, + PruneChildren: true, + }) + + testfile, err := os.Open(imagePath) + if err != nil { + return err + } + + // load image into docker engine + resp, err := d.cli.ImageLoad(ctx, testfile, true) + if err != nil { + return err + } + if _, err := io.Copy(io.Discard, resp.Body); err != nil { + return err + } + defer resp.Body.Close() + + targetImageRef := fmt.Sprintf("%s/%s", dest.URL.Host, imageRef) + + if err = d.cli.ImageTag(ctx, imageRef, targetImageRef); err != nil { + return err + } + defer func() { + _, _ = d.cli.ImageRemove(ctx, imageRef, types.ImageRemoveOptions{ + Force: true, + PruneChildren: true, + }) + _, _ = d.cli.ImageRemove(ctx, targetImageRef, types.ImageRemoveOptions{ + Force: true, + PruneChildren: true, + }) + }() + + auth, err := dest.GetRegistryAuth() + if err != nil { + return err + } + + pushOut, err := d.cli.ImagePush(ctx, targetImageRef, types.ImagePushOptions{RegistryAuth: auth}) + if err != nil { + return err + } + defer pushOut.Close() + + if _, err = io.Copy(io.Discard, pushOut); err != nil { + return err + } + return nil +} diff --git a/pkg/fanal/test/integration/library_test.go b/pkg/fanal/test/integration/library_test.go new file mode 100644 index 000000000000..b802084ba314 --- /dev/null +++ b/pkg/fanal/test/integration/library_test.go @@ -0,0 +1,382 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "io" + "os" + "sort" + "strings" + "testing" + + dtypes "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/all" + "github.com/aquasecurity/trivy/pkg/fanal/applier" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + aimage "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + _ "github.com/aquasecurity/trivy/pkg/fanal/handler/all" + "github.com/aquasecurity/trivy/pkg/fanal/image" + "github.com/aquasecurity/trivy/pkg/fanal/types" + + _ "modernc.org/sqlite" +) + +var update = flag.Bool("update", false, "update golden files") + +type testCase struct { + name string + remoteImageName string + imageFile string + wantOS types.OS + wantPkgsFromCmds string + wantApplicationFile string +} + +var tests = []testCase{ + { + name: "happy path, alpine:3.10", + remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", + imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", + wantOS: types.OS{ + Name: "3.10.2", + Family: "alpine", + }, + }, + { + name: "happy path, amazonlinux:2", + remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:amazon-2", + imageFile: "../../../../integration/testdata/fixtures/images/amazon-2.tar.gz", + wantOS: types.OS{ + Name: "2 (Karoo)", + Family: "amazon", + }, + }, + { + name: "happy path, debian:buster", + remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:debian-buster", + imageFile: "../../../../integration/testdata/fixtures/images/debian-buster.tar.gz", + wantOS: types.OS{ + Name: "10.1", + Family: "debian", + }, + }, + { + name: "happy path, photon:3.0", + remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:photon-30", + imageFile: "../../../../integration/testdata/fixtures/images/photon-30.tar.gz", + wantOS: types.OS{ + Name: "3.0", + Family: "photon", + }, + }, + { + name: "happy path, registry.redhat.io/ubi7", + remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:ubi-7", + imageFile: "../../../../integration/testdata/fixtures/images/ubi-7.tar.gz", + wantOS: types.OS{ + Name: "7.7", + Family: "redhat", + }, + }, + { + name: "happy path, opensuse leap 15.1", + remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:opensuse-leap-151", + imageFile: "../../../../integration/testdata/fixtures/images/opensuse-leap-151.tar.gz", + wantOS: types.OS{ + Name: "15.1", + Family: "opensuse.leap", + }, + }, + { + // from registry.suse.com/suse/sle15:15.3.17.8.16 + name: "happy path, suse 15.3 (NDB)", + remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:suse-15.3_ndb", + imageFile: "../../../../integration/testdata/fixtures/images/suse-15.3_ndb.tar.gz", + wantOS: types.OS{ + Name: "15.3", + Family: "suse linux enterprise server", + }, + }, + { + name: "happy path, Fedora 35", + remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:fedora-35", + imageFile: "../../../../integration/testdata/fixtures/images/fedora-35.tar.gz", + wantOS: types.OS{ + Name: "35", + Family: "fedora", + }, + }, + { + name: "happy path, vulnimage with lock files", + remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:vulnimage", + imageFile: "../../../../integration/testdata/fixtures/images/vulnimage.tar.gz", + wantOS: types.OS{ + Name: "3.7.1", + Family: "alpine", + }, + wantApplicationFile: "testdata/goldens/vuln-image1.2.3.expectedlibs.golden", + wantPkgsFromCmds: "testdata/goldens/vuln-image1.2.3.expectedpkgsfromcmds.golden", + }, +} + +func TestFanal_Library_DockerLessMode(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + d := t.TempDir() + + c, err := cache.NewFSCache(d) + require.NoError(t, err, tt.name) + + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + require.NoError(t, err) + + // remove existing Image if any + _, _ = cli.ImageRemove(ctx, tt.remoteImageName, dtypes.ImageRemoveOptions{ + Force: true, + PruneChildren: true, + }) + + // Enable only registry scanning + img, cleanup, err := image.NewContainerImage(ctx, tt.remoteImageName, types.ImageOptions{ + ImageSources: types.ImageSources{types.RemoteImageSource}, + }) + require.NoError(t, err) + defer cleanup() + + // don't scan licenses in the test - in parallel it will fail + ar, err := aimage.NewArtifact(img, c, artifact.Option{ + DisabledAnalyzers: []analyzer.Type{ + analyzer.TypeExecutable, + analyzer.TypeLicenseFile, + }, + }) + require.NoError(t, err) + + applier := applier.NewApplier(c) + + // run tests twice, one without cache and with cache + for i := 1; i <= 2; i++ { + runChecks(t, ctx, ar, applier, tt) + } + + // clear Cache + require.NoError(t, c.Clear()) + }) + } +} + +func TestFanal_Library_DockerMode(t *testing.T) { + // Disable updating golden files because local images don't have compressed layer digests, + // and updating golden files in this function results in incomplete files. + if *update { + t.Skipf("This test creates wrong golden file") + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + d := t.TempDir() + + c, err := cache.NewFSCache(d) + require.NoError(t, err) + + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + require.NoError(t, err, tt.name) + + testfile, err := os.Open(tt.imageFile) + require.NoError(t, err) + + // load image into docker engine + resp, err := cli.ImageLoad(ctx, testfile, true) + require.NoError(t, err, tt.name) + _, err = io.Copy(io.Discard, resp.Body) + require.NoError(t, err, tt.name) + + // Enable only dockerd scanning + img, cleanup, err := image.NewContainerImage(ctx, tt.remoteImageName, types.ImageOptions{ + ImageSources: types.ImageSources{types.DockerImageSource}, + }) + require.NoError(t, err, tt.name) + defer cleanup() + + ar, err := aimage.NewArtifact(img, c, artifact.Option{ + // disable license checking in the test - in parallel it will fail because of resource requirement + DisabledAnalyzers: []analyzer.Type{ + analyzer.TypeExecutable, + analyzer.TypeLicenseFile, + }, + }) + require.NoError(t, err) + + applier := applier.NewApplier(c) + + // run tests twice, one without cache and with cache + for i := 1; i <= 2; i++ { + runChecks(t, ctx, ar, applier, tt) + } + + // clear Cache + require.NoError(t, c.Clear(), tt.name) + + _, _ = cli.ImageRemove(ctx, tt.remoteImageName, dtypes.ImageRemoveOptions{ + Force: true, + PruneChildren: true, + }) + }) + } +} + +func TestFanal_Library_TarMode(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + d := t.TempDir() + + c, err := cache.NewFSCache(d) + require.NoError(t, err) + + img, err := image.NewArchiveImage(tt.imageFile) + require.NoError(t, err, tt.name) + + ar, err := aimage.NewArtifact(img, c, artifact.Option{ + DisabledAnalyzers: []analyzer.Type{ + analyzer.TypeExecutable, + analyzer.TypeLicenseFile, + }, + }) + require.NoError(t, err) + + applier := applier.NewApplier(c) + + runChecks(t, ctx, ar, applier, tt) + + // clear Cache + require.NoError(t, c.Clear(), tt.name) + }) + } +} + +func runChecks(t *testing.T, ctx context.Context, ar artifact.Artifact, applier applier.Applier, tc testCase) { + imageInfo, err := ar.Inspect(ctx) + require.NoError(t, err, tc.name) + imageDetail, err := applier.ApplyLayers(imageInfo.ID, imageInfo.BlobIDs) + require.NoError(t, err, tc.name) + commonChecks(t, imageDetail, tc) +} + +func commonChecks(t *testing.T, detail types.ArtifactDetail, tc testCase) { + assert.Equal(t, tc.wantOS, detail.OS, tc.name) + checkOSPackages(t, detail, tc) + checkPackageFromCommands(t, detail, tc) + checkLangPkgs(detail, t, tc) +} + +func checkOSPackages(t *testing.T, detail types.ArtifactDetail, tc testCase) { + // Sort OS packages for consistency + sort.Sort(detail.Packages) + + splitted := strings.Split(tc.remoteImageName, ":") + goldenFile := fmt.Sprintf("testdata/goldens/packages/%s.json.golden", splitted[len(splitted)-1]) + + if *update { + b, err := json.MarshalIndent(detail.Packages, "", " ") + require.NoError(t, err) + err = os.WriteFile(goldenFile, b, 0666) + require.NoError(t, err) + return + } + data, err := os.ReadFile(goldenFile) + require.NoError(t, err, tc.name) + + var expectedPkgs []types.Package + err = json.Unmarshal(data, &expectedPkgs) + require.NoError(t, err) + + require.Equal(t, len(expectedPkgs), len(detail.Packages), tc.name) + sort.Slice(expectedPkgs, func(i, j int) bool { return expectedPkgs[i].Name < expectedPkgs[j].Name }) + sort.Sort(detail.Packages) + + for i := 0; i < len(expectedPkgs); i++ { + require.Equal(t, expectedPkgs[i].Name, detail.Packages[i].Name, tc.name) + require.Equal(t, expectedPkgs[i].Version, detail.Packages[i].Version, tc.name) + } +} + +func checkLangPkgs(detail types.ArtifactDetail, t *testing.T, tc testCase) { + if tc.wantApplicationFile != "" { + // Sort applications for consistency + sort.Slice(detail.Applications, func(i, j int) bool { + if detail.Applications[i].Type != detail.Applications[j].Type { + return detail.Applications[i].Type < detail.Applications[j].Type + } + return detail.Applications[i].FilePath < detail.Applications[j].FilePath + }) + + for _, app := range detail.Applications { + sort.Sort(app.Libraries) + for i := range app.Libraries { + sort.Strings(app.Libraries[i].DependsOn) + } + } + + // Do not compare layers + for _, app := range detail.Applications { + for i := range app.Libraries { + app.Libraries[i].Layer = types.Layer{} + } + } + + if *update { + b, err := json.MarshalIndent(detail.Applications, "", " ") + require.NoError(t, err) + err = os.WriteFile(tc.wantApplicationFile, b, 0666) + require.NoError(t, err) + return + } + + var wantApps []types.Application + data, err := os.ReadFile(tc.wantApplicationFile) + require.NoError(t, err) + err = json.Unmarshal(data, &wantApps) + require.NoError(t, err) + + require.Equal(t, wantApps, detail.Applications, tc.name) + } else { + assert.Nil(t, detail.Applications, tc.name) + } +} + +func checkPackageFromCommands(t *testing.T, detail types.ArtifactDetail, tc testCase) { + if tc.wantPkgsFromCmds != "" { + if *update { + sort.Sort(types.Packages(detail.ImageConfig.Packages)) + b, err := json.MarshalIndent(detail.ImageConfig.Packages, "", " ") + require.NoError(t, err) + err = os.WriteFile(tc.wantPkgsFromCmds, b, 0666) + require.NoError(t, err) + return + } + data, _ := os.ReadFile(tc.wantPkgsFromCmds) + var expectedPkgsFromCmds []types.Package + + err := json.Unmarshal(data, &expectedPkgsFromCmds) + require.NoError(t, err) + assert.ElementsMatch(t, expectedPkgsFromCmds, detail.ImageConfig.Packages, tc.name) + } else { + assert.Equal(t, []types.Package(nil), detail.ImageConfig.Packages, tc.name) + } +} diff --git a/pkg/fanal/test/integration/registry_test.go b/pkg/fanal/test/integration/registry_test.go new file mode 100644 index 000000000000..081f9df20d95 --- /dev/null +++ b/pkg/fanal/test/integration/registry_test.go @@ -0,0 +1,264 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "fmt" + "io/ioutil" + "net/url" + "os" + "path/filepath" + "testing" + + dockercontainer "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + testcontainers "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/all" + "github.com/aquasecurity/trivy/pkg/fanal/applier" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + aimage "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/image" + testdocker "github.com/aquasecurity/trivy/pkg/fanal/test/integration/docker" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +const ( + registryImage = "registry:2" + registryPort = "5443/tcp" + registryUsername = "testuser" + registryPassword = "testpassword" +) + +func TestTLSRegistry(t *testing.T) { + ctx := context.Background() + + baseDir, err := filepath.Abs(".") + require.NoError(t, err) + + t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") + req := testcontainers.ContainerRequest{ + Name: "registry", + Image: registryImage, + ExposedPorts: []string{registryPort}, + Env: map[string]string{ + "REGISTRY_HTTP_ADDR": "0.0.0.0:5443", + "REGISTRY_HTTP_TLS_CERTIFICATE": "/certs/cert.pem", + "REGISTRY_HTTP_TLS_KEY": "/certs/key.pem", + "REGISTRY_AUTH": "htpasswd", + "REGISTRY_AUTH_HTPASSWD_PATH": "/auth/htpasswd", + "REGISTRY_AUTH_HTPASSWD_REALM": "Registry Realm", + }, + Mounts: testcontainers.Mounts( + testcontainers.BindMount(filepath.Join(baseDir, "data", "registry", "certs"), "/certs"), + testcontainers.BindMount(filepath.Join(baseDir, "data", "registry", "auth"), "/auth"), + ), + HostConfigModifier: func(hostConfig *dockercontainer.HostConfig) { + hostConfig.AutoRemove = true + }, + WaitingFor: wait.ForLog("listening on [::]:5443"), + } + + registryC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + require.NoError(t, err) + defer registryC.Terminate(ctx) + + registryURL, err := getRegistryURL(ctx, registryC, registryPort) + require.NoError(t, err) + + config := testdocker.RegistryConfig{ + URL: registryURL, + Username: registryUsername, + Password: registryPassword, + } + + testCases := []struct { + name string + imageName string + imageFile string + option types.ImageOptions + login bool + expectedOS types.OS + expectedRepo types.Repository + wantErr bool + }{ + { + name: "happy path", + imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", + imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", + option: types.ImageOptions{ + RegistryOptions: types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: registryUsername, + Password: registryPassword, + }, + }, + Insecure: true, + }, + }, + expectedOS: types.OS{ + Name: "3.10.2", + Family: "alpine", + }, + expectedRepo: types.Repository{ + Family: "alpine", + Release: "3.10", + }, + wantErr: false, + }, + { + name: "happy path with docker login", + imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", + imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", + option: types.ImageOptions{ + RegistryOptions: types.RegistryOptions{ + Insecure: true, + }, + }, + login: true, + expectedOS: types.OS{ + Name: "3.10.2", + Family: "alpine", + }, + expectedRepo: types.Repository{ + Family: "alpine", + Release: "3.10", + }, + wantErr: false, + }, + { + name: "sad path: tls verify", + imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", + imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", + option: types.ImageOptions{ + RegistryOptions: types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: registryUsername, + Password: registryPassword, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "sad path: no credential", + imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", + imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", + option: types.ImageOptions{ + RegistryOptions: types.RegistryOptions{ + Insecure: true, + }, + }, + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + d, err := testdocker.New() + require.NoError(t, err) + + // 0. Set the image source to remote + tc.option.ImageSources = types.ImageSources{types.RemoteImageSource} + + // 1. Load a test image from the tar file, tag it and push to the test registry. + err = d.ReplicateImage(ctx, tc.imageName, tc.imageFile, config) + require.NoError(t, err) + + if tc.login { + err = d.Login(config) + require.NoError(t, err) + + defer d.Logout(config) + } + + // 2. Analyze it + imageRef := fmt.Sprintf("%s/%s", registryURL.Host, tc.imageName) + imageDetail, err := analyze(ctx, imageRef, tc.option) + require.Equal(t, tc.wantErr, err != nil, err) + if err != nil { + return + } + + assert.Equal(t, tc.expectedOS, imageDetail.OS) + assert.Equal(t, &tc.expectedRepo, imageDetail.Repository) + }) + } +} + +func getRegistryURL(ctx context.Context, registryC testcontainers.Container, exposedPort nat.Port) (*url.URL, error) { + ip, err := registryC.Host(ctx) + if err != nil { + return nil, err + } + + port, err := registryC.MappedPort(ctx, exposedPort) + if err != nil { + return nil, err + } + + urlStr := fmt.Sprintf("https://%s:%s", ip, port.Port()) + return url.Parse(urlStr) +} + +func analyze(ctx context.Context, imageRef string, opt types.ImageOptions) (*types.ArtifactDetail, error) { + d, err := ioutil.TempDir("", "TestRegistry-*") + if err != nil { + return nil, err + } + defer os.RemoveAll(d) + + c, err := cache.NewFSCache(d) + if err != nil { + return nil, err + } + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return nil, err + } + cli.NegotiateAPIVersion(ctx) + + img, cleanup, err := image.NewContainerImage(ctx, imageRef, opt) + if err != nil { + return nil, err + } + defer cleanup() + + ar, err := aimage.NewArtifact(img, c, artifact.Option{ + DisabledAnalyzers: []analyzer.Type{ + analyzer.TypeExecutable, + analyzer.TypeLicenseFile, + }, + }) + if err != nil { + return nil, err + } + + ap := applier.NewApplier(c) + + imageInfo, err := ar.Inspect(ctx) + if err != nil { + return nil, err + } + + imageDetail, err := ap.ApplyLayers(imageInfo.ID, imageInfo.BlobIDs) + if err != nil { + return nil, err + } + return &imageDetail, nil +} diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden new file mode 100644 index 000000000000..ff6de7ec239e --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden @@ -0,0 +1,418 @@ +[ + { + "ID": "alpine-baselayout@3.1.2-r0", + "Name": "alpine-baselayout", + "Identifier": { + "PURL": "pkg:apk/alpine/alpine-baselayout@3.1.2-r0?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "3.1.2-r0", + "Arch": "x86_64", + "SrcName": "alpine-baselayout", + "SrcVersion": "3.1.2-r0", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "busybox@1.30.1-r2", + "musl@1.1.22-r3" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:574d490311b68db01c0a3e44f5491be0cdc79250", + "InstalledFiles": [ + "etc/hosts", + "etc/sysctl.conf", + "etc/group", + "etc/protocols", + "etc/fstab", + "etc/mtab", + "etc/profile", + "etc/shells", + "etc/motd", + "etc/inittab", + "etc/hostname", + "etc/modules", + "etc/services", + "etc/shadow", + "etc/passwd", + "etc/profile.d/color_prompt", + "etc/sysctl.d/00-alpine.conf", + "etc/modprobe.d/i386.conf", + "etc/modprobe.d/blacklist.conf", + "etc/modprobe.d/aliases.conf", + "etc/modprobe.d/kms.conf", + "etc/crontabs/root", + "sbin/mkmntdirs", + "var/run", + "var/spool/cron/crontabs" + ] + }, + { + "ID": "alpine-keys@2.1-r2", + "Name": "alpine-keys", + "Identifier": { + "PURL": "pkg:apk/alpine/alpine-keys@2.1-r2?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "2.1-r2", + "Arch": "x86_64", + "SrcName": "alpine-keys", + "SrcVersion": "2.1-r2", + "Licenses": [ + "MIT" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:6dd672e2dabc14aa324cf9cd4553e98b69a769c1", + "InstalledFiles": [ + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", + "usr/share/apk/keys/aarch64/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "usr/share/apk/keys/ppc64le/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/s390x/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", + "usr/share/apk/keys/armhf/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub" + ] + }, + { + "ID": "apk-tools@2.10.4-r2", + "Name": "apk-tools", + "Identifier": { + "PURL": "pkg:apk/alpine/apk-tools@2.10.4-r2?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "2.10.4-r2", + "Arch": "x86_64", + "SrcName": "apk-tools", + "SrcVersion": "2.10.4-r2", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "libcrypto1.1@1.1.1c-r0", + "libssl1.1@1.1.1c-r0", + "musl@1.1.22-r3", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:e6393c9419776955346cddd70ad4ec66082a2705", + "InstalledFiles": [ + "sbin/apk" + ] + }, + { + "ID": "busybox@1.30.1-r2", + "Name": "busybox", + "Identifier": { + "PURL": "pkg:apk/alpine/busybox@1.30.1-r2?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "1.30.1-r2", + "Arch": "x86_64", + "SrcName": "busybox", + "SrcVersion": "1.30.1-r2", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "musl@1.1.22-r3" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:9c244d7f4909bffcef4c67380b6aed145c41a232", + "InstalledFiles": [ + "bin/busybox", + "bin/sh", + "etc/securetty", + "etc/udhcpd.conf", + "etc/logrotate.d/acpid", + "etc/network/if-up.d/dad" + ] + }, + { + "ID": "ca-certificates-cacert@20190108-r0", + "Name": "ca-certificates-cacert", + "Identifier": { + "PURL": "pkg:apk/alpine/ca-certificates-cacert@20190108-r0?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "20190108-r0", + "Arch": "x86_64", + "SrcName": "ca-certificates", + "SrcVersion": "20190108-r0", + "Licenses": [ + "MPL-2.0", + "GPL-2.0" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:0d69933c7cd071e82acb24c6e4268e4752f7a3f7", + "InstalledFiles": [ + "etc/ssl/cert.pem" + ] + }, + { + "ID": "libc-utils@0.7.1-r0", + "Name": "libc-utils", + "Identifier": { + "PURL": "pkg:apk/alpine/libc-utils@0.7.1-r0?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "0.7.1-r0", + "Arch": "x86_64", + "SrcName": "libc-dev", + "SrcVersion": "0.7.1-r0", + "Licenses": [ + "BSD-3-Clause" + ], + "DependsOn": [ + "musl-utils@1.1.22-r3" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:1ff2cf7a0a53c1e83c8649f27b95aaa07bd4a83e" + }, + { + "ID": "libcrypto1.1@1.1.1c-r0", + "Name": "libcrypto1.1", + "Identifier": { + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "1.1.1c-r0", + "Arch": "x86_64", + "SrcName": "openssl", + "SrcVersion": "1.1.1c-r0", + "Licenses": [ + "OpenSSL" + ], + "DependsOn": [ + "musl@1.1.22-r3" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:547053af84ac3667548b11b990d7b80ef23b9a3f", + "InstalledFiles": [ + "etc/ssl/openssl.cnf.dist", + "etc/ssl/ct_log_list.cnf", + "etc/ssl/ct_log_list.cnf.dist", + "etc/ssl/openssl.cnf", + "etc/ssl/misc/CA.pl", + "etc/ssl/misc/tsget.pl", + "etc/ssl/misc/tsget", + "lib/libcrypto.so.1.1", + "usr/lib/libcrypto.so.1.1", + "usr/lib/engines-1.1/capi.so", + "usr/lib/engines-1.1/padlock.so", + "usr/lib/engines-1.1/afalg.so" + ] + }, + { + "ID": "libssl1.1@1.1.1c-r0", + "Name": "libssl1.1", + "Identifier": { + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "1.1.1c-r0", + "Arch": "x86_64", + "SrcName": "openssl", + "SrcVersion": "1.1.1c-r0", + "Licenses": [ + "OpenSSL" + ], + "DependsOn": [ + "libcrypto1.1@1.1.1c-r0", + "musl@1.1.22-r3" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:6f37a428d6f8de036a523d42e6b0c99288153c38", + "InstalledFiles": [ + "lib/libssl.so.1.1", + "usr/lib/libssl.so.1.1" + ] + }, + { + "ID": "libtls-standalone@2.9.1-r0", + "Name": "libtls-standalone", + "Identifier": { + "PURL": "pkg:apk/alpine/libtls-standalone@2.9.1-r0?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "2.9.1-r0", + "Arch": "x86_64", + "SrcName": "libtls-standalone", + "SrcVersion": "2.9.1-r0", + "Licenses": [ + "ISC" + ], + "DependsOn": [ + "ca-certificates-cacert@20190108-r0", + "libcrypto1.1@1.1.1c-r0", + "libssl1.1@1.1.1c-r0", + "musl@1.1.22-r3" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:f149b608e1e33cfad61fbcf9e3fdb6494f7d691a", + "InstalledFiles": [ + "usr/lib/libtls-standalone.so.1.0.0", + "usr/lib/libtls-standalone.so.1" + ] + }, + { + "ID": "musl@1.1.22-r3", + "Name": "musl", + "Identifier": { + "PURL": "pkg:apk/alpine/musl@1.1.22-r3?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "1.1.22-r3", + "Arch": "x86_64", + "SrcName": "musl", + "SrcVersion": "1.1.22-r3", + "Licenses": [ + "MIT" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:d489e0f3fbb5548758f5ccd2c5a0cef70260ef62", + "InstalledFiles": [ + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1" + ] + }, + { + "ID": "musl-utils@1.1.22-r3", + "Name": "musl-utils", + "Identifier": { + "PURL": "pkg:apk/alpine/musl-utils@1.1.22-r3?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "1.1.22-r3", + "Arch": "x86_64", + "SrcName": "musl", + "SrcVersion": "1.1.22-r3", + "Licenses": [ + "MIT", + "BSD-3-Clause", + "GPL-2.0" + ], + "DependsOn": [ + "musl@1.1.22-r3", + "scanelf@1.2.3-r0" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:8bb14c727be819d07a1e9d8b998dc94bde989205", + "InstalledFiles": [ + "sbin/ldconfig", + "usr/bin/iconv", + "usr/bin/ldd", + "usr/bin/getconf", + "usr/bin/getent" + ] + }, + { + "ID": "scanelf@1.2.3-r0", + "Name": "scanelf", + "Identifier": { + "PURL": "pkg:apk/alpine/scanelf@1.2.3-r0?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "1.2.3-r0", + "Arch": "x86_64", + "SrcName": "pax-utils", + "SrcVersion": "1.2.3-r0", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "musl@1.1.22-r3" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:cb3059ce358cea0f5f78a0220a2980e6c4916a94", + "InstalledFiles": [ + "usr/bin/scanelf" + ] + }, + { + "ID": "ssl_client@1.30.1-r2", + "Name": "ssl_client", + "Identifier": { + "PURL": "pkg:apk/alpine/ssl_client@1.30.1-r2?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "1.30.1-r2", + "Arch": "x86_64", + "SrcName": "busybox", + "SrcVersion": "1.30.1-r2", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "libtls-standalone@2.9.1-r0", + "musl@1.1.22-r3" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:1f7afca8301f00cef8a9797124721d9f8c16f586", + "InstalledFiles": [ + "usr/bin/ssl_client" + ] + }, + { + "ID": "zlib@1.2.11-r1", + "Name": "zlib", + "Identifier": { + "PURL": "pkg:apk/alpine/zlib@1.2.11-r1?arch=x86_64\u0026distro=3.10.2" + }, + "Version": "1.2.11-r1", + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.2.11-r1", + "Licenses": [ + "Zlib" + ], + "DependsOn": [ + "musl@1.1.22-r3" + ], + "Layer": { + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + }, + "Digest": "sha1:bacb380dfa6f2f5e8dc366144f09b3181001cf76", + "InstalledFiles": [ + "lib/libz.so.1.2.11", + "lib/libz.so.1" + ] + } +] \ No newline at end of file diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/amazon-2.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/amazon-2.json.golden new file mode 100644 index 000000000000..906be44312ee --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/amazon-2.json.golden @@ -0,0 +1,1363 @@ +[ + { + "Name": "amazon-linux-extras", + "Version": "1.6.7", + "Release": "1.amzn2", + "Arch": "noarch", + "SrcName": "amazon-linux-extras", + "SrcVersion": "1.6.7", + "SrcRelease": "1.amzn2", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "basesystem", + "Version": "10.0", + "Release": "7.amzn2.0.1", + "Arch": "noarch", + "SrcName": "basesystem", + "SrcVersion": "10.0", + "SrcRelease": "7.amzn2.0.1", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "bash", + "Version": "4.2.46", + "Release": "30.amzn2", + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "4.2.46", + "SrcRelease": "30.amzn2", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "BSD", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "ca-certificates", + "Version": "2018.2.22", + "Release": "70.0.amzn2", + "Arch": "noarch", + "SrcName": "ca-certificates", + "SrcVersion": "2018.2.22", + "SrcRelease": "70.0.amzn2", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "coreutils", + "Version": "8.22", + "Release": "21.amzn2", + "Arch": "x86_64", + "SrcName": "coreutils", + "SrcVersion": "8.22", + "SrcRelease": "21.amzn2", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "cpio", + "Version": "2.11", + "Release": "27.amzn2", + "Arch": "x86_64", + "SrcName": "cpio", + "SrcVersion": "2.11", + "SrcRelease": "27.amzn2", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "cyrus-sasl-lib", + "Version": "2.1.26", + "Release": "23.amzn2", + "Arch": "x86_64", + "SrcName": "cyrus-sasl", + "SrcVersion": "2.1.26", + "SrcRelease": "23.amzn2", + "License": "BSD with advertising", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "diffutils", + "Version": "3.3", + "Release": "4.amzn2.0.2", + "Arch": "x86_64", + "SrcName": "diffutils", + "SrcVersion": "3.3", + "SrcRelease": "4.amzn2.0.2", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "elfutils-libelf", + "Version": "0.170", + "Release": "4.amzn2", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.170", + "SrcRelease": "4.amzn2", + "License": "GPLv2+ or LGPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "file-libs", + "Version": "5.11", + "Release": "33.amzn2.0.2", + "Arch": "x86_64", + "SrcName": "file", + "SrcVersion": "5.11", + "SrcRelease": "33.amzn2.0.2", + "License": "BSD", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "filesystem", + "Version": "3.2", + "Release": "25.amzn2.0.4", + "Arch": "x86_64", + "SrcName": "filesystem", + "SrcVersion": "3.2", + "SrcRelease": "25.amzn2.0.4", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "findutils", + "Version": "4.5.11", + "Release": "5.amzn2.0.2", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "findutils", + "SrcVersion": "4.5.11", + "SrcRelease": "5.amzn2.0.2", + "SrcEpoch": 1, + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv3+ and GPL and LGPLv3+ and LGPL and BSD", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "gdbm", + "Version": "1.13", + "Release": "6.amzn2.0.2", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "gdbm", + "SrcVersion": "1.13", + "SrcRelease": "6.amzn2.0.2", + "SrcEpoch": 1, + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "glib2", + "Version": "2.54.2", + "Release": "2.amzn2", + "Arch": "x86_64", + "SrcName": "glib2", + "SrcVersion": "2.54.2", + "SrcRelease": "2.amzn2", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "glibc", + "Version": "2.26", + "Release": "32.amzn2.0.1", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.26", + "SrcRelease": "32.amzn2.0.1", + "License": "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "glibc-common", + "Version": "2.26", + "Release": "32.amzn2.0.1", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.26", + "SrcRelease": "32.amzn2.0.1", + "License": "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "gmp", + "Version": "6.0.0", + "Release": "15.amzn2.0.2", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "gmp", + "SrcVersion": "6.0.0", + "SrcRelease": "15.amzn2.0.2", + "SrcEpoch": 1, + "License": "LGPLv3+ or GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "gpg-pubkey", + "Version": "c87f5b1a", + "Release": "593863f8", + "Arch": "None", + "License": "pubkey", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "grep", + "Version": "2.20", + "Release": "3.amzn2.0.2", + "Arch": "x86_64", + "SrcName": "grep", + "SrcVersion": "2.20", + "SrcRelease": "3.amzn2.0.2", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "info", + "Version": "5.1", + "Release": "5.amzn2", + "Arch": "x86_64", + "SrcName": "texinfo", + "SrcVersion": "5.1", + "SrcRelease": "5.amzn2", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv2+ and LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "libacl", + "Version": "2.2.51", + "Release": "14.amzn2", + "Arch": "x86_64", + "SrcName": "acl", + "SrcVersion": "2.2.51", + "SrcRelease": "14.amzn2", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+ and GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "libcap", + "Version": "2.22", + "Release": "9.amzn2.0.2", + "Arch": "x86_64", + "SrcName": "libcap", + "SrcVersion": "2.22", + "SrcRelease": "9.amzn2.0.2", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "libcrypt", + "Version": "2.26", + "Release": "32.amzn2.0.1", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.26", + "SrcRelease": "32.amzn2.0.1", + "License": "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "BSD and LGPLv2 and Sleepycat", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "BSD and LGPLv2 and Sleepycat", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT and Public Domain", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "(GPLv2+ or LGPLv3+) and GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "libselinux", + "Version": "2.5", + "Release": "12.amzn2.0.2", + "Arch": "x86_64", + "SrcName": "libselinux", + "SrcVersion": "2.5", + "SrcRelease": "12.amzn2.0.2", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "libssh2", + "Version": "1.4.3", + "Release": "12.amzn2.2", + "Arch": "x86_64", + "SrcName": "libssh2", + "SrcVersion": "1.4.3", + "SrcRelease": "12.amzn2.2", + "License": "BSD", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "libtasn1", + "Version": "4.10", + "Release": "1.amzn2.0.2", + "Arch": "x86_64", + "SrcName": "libtasn1", + "SrcVersion": "4.10", + "SrcRelease": "1.amzn2.0.2", + "License": "GPLv3+ and LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "BSD", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "nspr", + "Version": "4.19.0", + "Release": "1.amzn2", + "Arch": "x86_64", + "SrcName": "nspr", + "SrcVersion": "4.19.0", + "SrcRelease": "1.amzn2", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "nss", + "Version": "3.36.0", + "Release": "7.amzn2", + "Arch": "x86_64", + "SrcName": "nss", + "SrcVersion": "3.36.0", + "SrcRelease": "7.amzn2", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "nss-pem", + "Version": "1.0.3", + "Release": "5.amzn2", + "Arch": "x86_64", + "SrcName": "nss-pem", + "SrcVersion": "1.0.3", + "SrcRelease": "5.amzn2", + "License": "MPLv1.1", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "nss-softokn", + "Version": "3.36.0", + "Release": "5.amzn2", + "Arch": "x86_64", + "SrcName": "nss-softokn", + "SrcVersion": "3.36.0", + "SrcRelease": "5.amzn2", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "nss-softokn-freebl", + "Version": "3.36.0", + "Release": "5.amzn2", + "Arch": "x86_64", + "SrcName": "nss-softokn", + "SrcVersion": "3.36.0", + "SrcRelease": "5.amzn2", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "nss-sysinit", + "Version": "3.36.0", + "Release": "7.amzn2", + "Arch": "x86_64", + "SrcName": "nss", + "SrcVersion": "3.36.0", + "SrcRelease": "7.amzn2", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "nss-tools", + "Version": "3.36.0", + "Release": "7.amzn2", + "Arch": "x86_64", + "SrcName": "nss", + "SrcVersion": "3.36.0", + "SrcRelease": "7.amzn2", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "nss-util", + "Version": "3.36.0", + "Release": "1.amzn2", + "Arch": "x86_64", + "SrcName": "nss-util", + "SrcVersion": "3.36.0", + "SrcRelease": "1.amzn2", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "openldap", + "Version": "2.4.44", + "Release": "15.amzn2", + "Arch": "x86_64", + "SrcName": "openldap", + "SrcVersion": "2.4.44", + "SrcRelease": "15.amzn2", + "License": "OpenLDAP", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "openssl-libs", + "Version": "1.0.2k", + "Release": "16.amzn2.1.1", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "openssl", + "SrcVersion": "1.0.2k", + "SrcRelease": "16.amzn2.1.1", + "SrcEpoch": 1, + "License": "OpenSSL", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "BSD", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "BSD", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "pcre", + "Version": "8.32", + "Release": "17.amzn2.0.2", + "Arch": "x86_64", + "SrcName": "pcre", + "SrcVersion": "8.32", + "SrcRelease": "17.amzn2.0.2", + "License": "BSD", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "popt", + "Version": "1.13", + "Release": "16.amzn2.0.2", + "Arch": "x86_64", + "SrcName": "popt", + "SrcVersion": "1.13", + "SrcRelease": "16.amzn2.0.2", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "pygpgme", + "Version": "0.3", + "Release": "9.amzn2.0.2", + "Arch": "x86_64", + "SrcName": "pygpgme", + "SrcVersion": "0.3", + "SrcRelease": "9.amzn2.0.2", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "Python", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "python-iniparse", + "Version": "0.4", + "Release": "9.amzn2", + "Arch": "noarch", + "SrcName": "python-iniparse", + "SrcVersion": "0.4", + "SrcRelease": "9.amzn2", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "Python", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+ or MIT", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "python-urlgrabber", + "Version": "3.10", + "Release": "8.amzn2", + "Arch": "noarch", + "SrcName": "python-urlgrabber", + "SrcVersion": "3.10", + "SrcRelease": "8.amzn2", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "readline", + "Version": "6.2", + "Release": "10.amzn2.0.2", + "Arch": "x86_64", + "SrcName": "readline", + "SrcVersion": "6.2", + "SrcRelease": "10.amzn2.0.2", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv2+ and LGPLv2+ with exceptions", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv2+ and LGPLv2+ with exceptions", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "setup", + "Version": "2.8.71", + "Release": "10.amzn2", + "Arch": "noarch", + "SrcName": "setup", + "SrcVersion": "2.8.71", + "SrcRelease": "10.amzn2", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "shared-mime-info", + "Version": "1.8", + "Release": "4.amzn2", + "Arch": "x86_64", + "SrcName": "shared-mime-info", + "SrcVersion": "1.8", + "SrcRelease": "4.amzn2", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "system-release", + "Version": "2", + "Release": "10.amzn2", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "system-release", + "SrcVersion": "2", + "SrcRelease": "10.amzn2", + "SrcEpoch": 1, + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "tzdata", + "Version": "2018i", + "Release": "1.amzn2", + "Arch": "noarch", + "SrcName": "tzdata", + "SrcVersion": "2018i", + "SrcRelease": "1.amzn2", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "vim-minimal", + "Version": "7.4.160", + "Release": "4.amzn2.0.16", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "vim", + "SrcVersion": "7.4.160", + "SrcRelease": "4.amzn2.0.16", + "SrcEpoch": 2, + "License": "Vim", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "Name": "yum", + "Version": "3.4.3", + "Release": "158.amzn2.0.2", + "Arch": "noarch", + "SrcName": "yum", + "SrcVersion": "3.4.3", + "SrcRelease": "158.amzn2.0.2", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + }, + { + "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", + "License": "zlib and Boost", + "Layer": { + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + } +] \ No newline at end of file diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/centos-6.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/centos-6.json.golden new file mode 100644 index 000000000000..9fe3c3bb8b3b --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/centos-6.json.golden @@ -0,0 +1,1550 @@ +[ + { + "Name": "setup", + "Version": "2.8.14", + "Release": "23.el6", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "setup", + "SrcVersion": "2.8.14", + "SrcRelease": "23.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "basesystem", + "Version": "10.0", + "Release": "4.el6", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "basesystem", + "SrcVersion": "10.0", + "SrcRelease": "4.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "tzdata", + "Version": "2018e", + "Release": "3.el6", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "tzdata", + "SrcVersion": "2018e", + "SrcRelease": "3.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "glibc-common", + "Version": "2.12", + "Release": "1.212.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.12", + "SrcRelease": "1.212.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "glibc", + "Version": "2.12", + "Release": "1.212.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.12", + "SrcRelease": "1.212.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "bash", + "Version": "4.1.2", + "Release": "48.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "4.1.2", + "SrcRelease": "48.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libcap", + "Version": "2.16", + "Release": "5.5.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libcap", + "SrcVersion": "2.16", + "SrcRelease": "5.5.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "info", + "Version": "4.13a", + "Release": "8.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "texinfo", + "SrcVersion": "4.13a", + "SrcRelease": "8.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libacl", + "Version": "2.2.49", + "Release": "7.el6_9.1", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "acl", + "SrcVersion": "2.2.49", + "SrcRelease": "7.el6_9.1", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "nspr", + "Version": "4.19.0", + "Release": "1.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "nspr", + "SrcVersion": "4.19.0", + "SrcRelease": "1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libcom_err", + "Version": "1.41.12", + "Release": "24.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "e2fsprogs", + "SrcVersion": "1.41.12", + "SrcRelease": "24.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libsepol", + "Version": "2.0.41", + "Release": "4.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libsepol", + "SrcVersion": "2.0.41", + "SrcRelease": "4.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "chkconfig", + "Version": "1.3.49.5", + "Release": "1.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "chkconfig", + "SrcVersion": "1.3.49.5", + "SrcRelease": "1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "audit-libs", + "Version": "2.4.5", + "Release": "6.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "audit", + "SrcVersion": "2.4.5", + "SrcRelease": "6.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "readline", + "Version": "6.0", + "Release": "4.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "readline", + "SrcVersion": "6.0", + "SrcRelease": "4.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "file-libs", + "Version": "5.04", + "Release": "30.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "file", + "SrcVersion": "5.04", + "SrcRelease": "30.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "dbus-libs", + "Version": "1.2.24", + "Release": "9.el6", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "dbus", + "SrcVersion": "1.2.24", + "SrcRelease": "9.el6", + "SrcEpoch":1, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "sqlite", + "Version": "3.6.20", + "Release": "1.el6_7.2", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "sqlite", + "SrcVersion": "3.6.20", + "SrcRelease": "1.el6_7.2", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libuuid", + "Version": "2.17.2", + "Release": "12.28.el6_9.2", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "util-linux-ng", + "SrcVersion": "2.17.2", + "SrcRelease": "12.28.el6_9.2", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "xz-libs", + "Version": "4.999.9", + "Release": "0.5.beta.20091007git.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "4.999.9", + "SrcRelease": "0.5.beta.20091007git.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libgpg-error", + "Version": "1.7", + "Release": "4.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libgpg-error", + "SrcVersion": "1.7", + "SrcRelease": "4.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "pcre", + "Version": "7.8", + "Release": "7.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "pcre", + "SrcVersion": "7.8", + "SrcRelease": "7.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "findutils", + "Version": "4.4.2", + "Release": "9.el6", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "findutils", + "SrcVersion": "4.4.2", + "SrcRelease": "9.el6", + "SrcEpoch":1, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "expat", + "Version": "2.0.1", + "Release": "13.el6_8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "expat", + "SrcVersion": "2.0.1", + "SrcRelease": "13.el6_8", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "p11-kit", + "Version": "0.18.5", + "Release": "2.el6_5.2", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.18.5", + "SrcRelease": "2.el6_5.2", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libgcrypt", + "Version": "1.4.5", + "Release": "12.el6_8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libgcrypt", + "SrcVersion": "1.4.5", + "SrcRelease": "12.el6_8", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libusb", + "Version": "0.1.12", + "Release": "23.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libusb", + "SrcVersion": "0.1.12", + "SrcRelease": "23.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "pinentry", + "Version": "0.7.6", + "Release": "8.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "pinentry", + "SrcVersion": "0.7.6", + "SrcRelease": "8.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "procps", + "Version": "3.2.8", + "Release": "45.el6_9.3", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "procps", + "SrcVersion": "3.2.8", + "SrcRelease": "45.el6_9.3", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "tar", + "Version": "1.23", + "Release": "15.el6_8", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "tar", + "SrcVersion": "1.23", + "SrcRelease": "15.el6_8", + "SrcEpoch": 2, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "checkpolicy", + "Version": "2.0.22", + "Release": "1.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "checkpolicy", + "SrcVersion": "2.0.22", + "SrcRelease": "1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "db4-utils", + "Version": "4.7.25", + "Release": "22.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "db4", + "SrcVersion": "4.7.25", + "SrcRelease": "22.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "binutils", + "Version": "2.20.51.0.2", + "Release": "5.48.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "binutils", + "SrcVersion": "2.20.51.0.2", + "SrcRelease": "5.48.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "which", + "Version": "2.19", + "Release": "6.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "which", + "SrcVersion": "2.19", + "SrcRelease": "6.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "dash", + "Version": "0.5.5.1", + "Release": "4.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "dash", + "SrcVersion": "0.5.5.1", + "SrcRelease": "4.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "groff", + "Version": "1.18.1.4", + "Release": "21.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "groff", + "SrcVersion": "1.18.1.4", + "SrcRelease": "21.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "coreutils-libs", + "Version": "8.4", + "Release": "47.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "coreutils", + "SrcVersion": "8.4", + "SrcRelease": "47.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "cracklib", + "Version": "2.8.16", + "Release": "4.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.8.16", + "SrcRelease": "4.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "coreutils", + "Version": "8.4", + "Release": "47.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "coreutils", + "SrcVersion": "8.4", + "SrcRelease": "47.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "module-init-tools", + "Version": "3.9", + "Release": "26.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "module-init-tools", + "SrcVersion": "3.9", + "SrcRelease": "26.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "nss", + "Version": "3.36.0", + "Release": "8.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "nss", + "SrcVersion": "3.36.0", + "SrcRelease": "8.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "nss-tools", + "Version": "3.36.0", + "Release": "8.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "nss", + "SrcVersion": "3.36.0", + "SrcRelease": "8.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "mingetty", + "Version": "1.08", + "Release": "5.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "mingetty", + "SrcVersion": "1.08", + "SrcRelease": "5.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "krb5-libs", + "Version": "1.10.3", + "Release": "65.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "krb5", + "SrcVersion": "1.10.3", + "SrcRelease": "65.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libssh2", + "Version": "1.4.2", + "Release": "2.el6_7.1", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libssh2", + "SrcVersion": "1.4.2", + "SrcRelease": "2.el6_7.1", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "rpm-libs", + "Version": "4.8.0", + "Release": "59.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.8.0", + "SrcRelease": "59.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "rpm", + "Version": "4.8.0", + "Release": "59.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.8.0", + "SrcRelease": "59.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "gnupg2", + "Version": "2.0.14", + "Release": "9.el6_10", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gnupg2", + "SrcVersion": "2.0.14", + "SrcRelease": "9.el6_10", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "bind-libs", + "Version": "9.8.2", + "Release": "0.68.rc1.el6_10.1", + "Epoch": 32, + "Arch": "x86_64", + "SrcName": "bind", + "SrcVersion": "9.8.2", + "SrcRelease": "0.68.rc1.el6_10.1", + "SrcEpoch": 32, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libsemanage", + "Version": "2.0.43", + "Release": "5.1.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libsemanage", + "SrcVersion": "2.0.43", + "SrcRelease": "5.1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libutempter", + "Version": "1.1.5", + "Release": "4.1.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libutempter", + "SrcVersion": "1.1.5", + "SrcRelease": "4.1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "plymouth-core-libs", + "Version": "0.8.3", + "Release": "29.el6.centos", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "plymouth", + "SrcVersion": "0.8.3", + "SrcRelease": "29.el6.centos", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libffi", + "Version": "3.0.5", + "Release": "3.2.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libffi", + "SrcVersion": "3.0.5", + "SrcRelease": "3.2.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "python-libs", + "Version": "2.6.6", + "Release": "66.el6_8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "python", + "SrcVersion": "2.6.6", + "SrcRelease": "66.el6_8", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "pygpgme", + "Version": "0.1", + "Release": "18.20090824bzr68.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "pygpgme", + "SrcVersion": "0.1", + "SrcRelease": "18.20090824bzr68.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "python-urlgrabber", + "Version": "3.9.1", + "Release": "11.el6", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "python-urlgrabber", + "SrcVersion": "3.9.1", + "SrcRelease": "11.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "pkgconfig", + "Version": "0.23", + "Release": "9.1.el6", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "pkgconfig", + "SrcVersion": "0.23", + "SrcRelease": "9.1.el6", + "SrcEpoch":1, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "glib2", + "Version": "2.28.8", + "Release": "10.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "glib2", + "SrcVersion": "2.28.8", + "SrcRelease": "10.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "yum-metadata-parser", + "Version": "1.1.2", + "Release": "16.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "yum-metadata-parser", + "SrcVersion": "1.1.2", + "SrcRelease": "16.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "yum", + "Version": "3.2.29", + "Release": "81.el6.centos", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "yum", + "SrcVersion": "3.2.29", + "SrcRelease": "81.el6.centos", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "passwd", + "Version": "0.77", + "Release": "7.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "passwd", + "SrcVersion": "0.77", + "SrcRelease": "7.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "bind-utils", + "Version": "9.8.2", + "Release": "0.68.rc1.el6_10.1", + "Epoch": 32, + "Arch": "x86_64", + "SrcName": "bind", + "SrcVersion": "9.8.2", + "SrcRelease": "0.68.rc1.el6_10.1", + "SrcEpoch": 32, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "vim-minimal", + "Version": "7.4.629", + "Release": "5.el6_8.1", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "vim", + "SrcVersion": "7.4.629", + "SrcRelease": "5.el6_8.1", + "SrcEpoch": 2, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libgcc", + "Version": "4.4.7", + "Release": "23.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gcc", + "SrcVersion": "4.4.7", + "SrcRelease": "23.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "filesystem", + "Version": "2.4.30", + "Release": "3.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "filesystem", + "SrcVersion": "2.4.30", + "SrcRelease": "3.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "ncurses-base", + "Version": "5.7", + "Release": "4.20090207.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "5.7", + "SrcRelease": "4.20090207.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "nss-softokn-freebl", + "Version": "3.14.3", + "Release": "23.3.el6_8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "nss-softokn", + "SrcVersion": "3.14.3", + "SrcRelease": "23.3.el6_8", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "ncurses-libs", + "Version": "5.7", + "Release": "4.20090207.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "5.7", + "SrcRelease": "4.20090207.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libattr", + "Version": "2.4.44", + "Release": "7.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "attr", + "SrcVersion": "2.4.44", + "SrcRelease": "7.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "zlib", + "Version": "1.2.3", + "Release": "29.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.2.3", + "SrcRelease": "29.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "popt", + "Version": "1.13", + "Release": "7.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "popt", + "SrcVersion": "1.13", + "SrcRelease": "7.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "db4", + "Version": "4.7.25", + "Release": "22.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "db4", + "SrcVersion": "4.7.25", + "SrcRelease": "22.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "nss-util", + "Version": "3.36.0", + "Release": "1.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "nss-util", + "SrcVersion": "3.36.0", + "SrcRelease": "1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "bzip2-libs", + "Version": "1.0.5", + "Release": "7.el6_0", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.5", + "SrcRelease": "7.el6_0", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libselinux", + "Version": "2.0.94", + "Release": "7.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libselinux", + "SrcVersion": "2.0.94", + "SrcRelease": "7.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "sed", + "Version": "4.2.1", + "Release": "10.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "sed", + "SrcVersion": "4.2.1", + "SrcRelease": "10.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libidn", + "Version": "1.18", + "Release": "2.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libidn", + "SrcVersion": "1.18", + "SrcRelease": "2.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libxml2", + "Version": "2.7.6", + "Release": "21.el6_8.1", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libxml2", + "SrcVersion": "2.7.6", + "SrcRelease": "21.el6_8.1", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libstdc++", + "Version": "4.4.7", + "Release": "23.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gcc", + "SrcVersion": "4.4.7", + "SrcRelease": "23.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "lua", + "Version": "5.1.4", + "Release": "4.1.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "lua", + "SrcVersion": "5.1.4", + "SrcRelease": "4.1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "gawk", + "Version": "3.1.7", + "Release": "10.el6_7.3", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gawk", + "SrcVersion": "3.1.7", + "SrcRelease": "10.el6_7.3", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libblkid", + "Version": "2.17.2", + "Release": "12.28.el6_9.2", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "util-linux-ng", + "SrcVersion": "2.17.2", + "SrcRelease": "12.28.el6_9.2", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "elfutils-libelf", + "Version": "0.164", + "Release": "2.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.164", + "SrcRelease": "2.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "nss-softokn", + "Version": "3.14.3", + "Release": "23.3.el6_8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "nss-softokn", + "SrcVersion": "3.14.3", + "SrcRelease": "23.3.el6_8", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "grep", + "Version": "2.20", + "Release": "6.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "grep", + "SrcVersion": "2.20", + "SrcRelease": "6.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "cpio", + "Version": "2.10", + "Release": "13.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "cpio", + "SrcVersion": "2.10", + "SrcRelease": "13.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "pth", + "Version": "2.0.7", + "Release": "9.3.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "pth", + "SrcVersion": "2.0.7", + "SrcRelease": "9.3.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libtasn1", + "Version": "2.3", + "Release": "6.el6_5", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libtasn1", + "SrcVersion": "2.3", + "SrcRelease": "6.el6_5", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "p11-kit-trust", + "Version": "0.18.5", + "Release": "2.el6_5.2", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.18.5", + "SrcRelease": "2.el6_5.2", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libnih", + "Version": "1.0.1", + "Release": "8.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libnih", + "SrcVersion": "1.0.1", + "SrcRelease": "8.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "gmp", + "Version": "4.3.1", + "Release": "13.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gmp", + "SrcVersion": "4.3.1", + "SrcRelease": "13.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "file", + "Version": "5.04", + "Release": "30.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "file", + "SrcVersion": "5.04", + "SrcRelease": "30.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "net-tools", + "Version": "1.60", + "Release": "114.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "net-tools", + "SrcVersion": "1.60", + "SrcRelease": "114.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "psmisc", + "Version": "22.6", + "Release": "24.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "psmisc", + "SrcVersion": "22.6", + "SrcRelease": "24.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libselinux-utils", + "Version": "2.0.94", + "Release": "7.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libselinux", + "SrcVersion": "2.0.94", + "SrcRelease": "7.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "bzip2", + "Version": "1.0.5", + "Release": "7.el6_0", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.5", + "SrcRelease": "7.el6_0", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "cyrus-sasl-lib", + "Version": "2.1.23", + "Release": "15.el6_6.2", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "cyrus-sasl", + "SrcVersion": "2.1.23", + "SrcRelease": "15.el6_6.2", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "make", + "Version": "3.81", + "Release": "23.el6", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "make", + "SrcVersion": "3.81", + "SrcRelease": "23.el6", + "SrcEpoch":1, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "diffutils", + "Version": "2.8.1", + "Release": "28.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "diffutils", + "SrcVersion": "2.8.1", + "SrcRelease": "28.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "ncurses", + "Version": "5.7", + "Release": "4.20090207.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "5.7", + "SrcRelease": "4.20090207.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "less", + "Version": "436", + "Release": "13.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "less", + "SrcVersion": "436", + "SrcRelease": "13.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "gzip", + "Version": "1.3.12", + "Release": "24.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gzip", + "SrcVersion": "1.3.12", + "SrcRelease": "24.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "cracklib-dicts", + "Version": "2.8.16", + "Release": "4.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.8.16", + "SrcRelease": "4.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "pam", + "Version": "1.1.1", + "Release": "24.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "pam", + "SrcVersion": "1.1.1", + "SrcRelease": "24.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "plymouth-scripts", + "Version": "0.8.3", + "Release": "29.el6.centos", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "plymouth", + "SrcVersion": "0.8.3", + "SrcRelease": "29.el6.centos", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "ca-certificates", + "Version": "2018.2.22", + "Release": "65.1.el6", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "ca-certificates", + "SrcVersion": "2018.2.22", + "SrcRelease": "65.1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "nss-sysinit", + "Version": "3.36.0", + "Release": "8.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "nss", + "SrcVersion": "3.36.0", + "SrcRelease": "8.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "ethtool", + "Version": "3.5", + "Release": "6.el6", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "ethtool", + "SrcVersion": "3.5", + "SrcRelease": "6.el6", + "SrcEpoch": 2, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "keyutils-libs", + "Version": "1.4", + "Release": "5.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "keyutils", + "SrcVersion": "1.4", + "SrcRelease": "5.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "openssl", + "Version": "1.0.1e", + "Release": "57.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "openssl", + "SrcVersion": "1.0.1e", + "SrcRelease": "57.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libcurl", + "Version": "7.19.7", + "Release": "53.el6_9", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.19.7", + "SrcRelease": "53.el6_9", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "curl", + "Version": "7.19.7", + "Release": "53.el6_9", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.19.7", + "SrcRelease": "53.el6_9", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "openldap", + "Version": "2.4.40", + "Release": "16.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "openldap", + "SrcVersion": "2.4.40", + "SrcRelease": "16.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "gpgme", + "Version": "1.1.8", + "Release": "3.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gpgme", + "SrcVersion": "1.1.8", + "SrcRelease": "3.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "ustr", + "Version": "1.0.4", + "Release": "9.1.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "ustr", + "SrcVersion": "1.0.4", + "SrcRelease": "9.1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "shadow-utils", + "Version": "4.1.5.1", + "Release": "5.el6", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "shadow-utils", + "SrcVersion": "4.1.5.1", + "SrcRelease": "5.el6", + "SrcEpoch": 2, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "MAKEDEV", + "Version": "3.24", + "Release": "6.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "MAKEDEV", + "SrcVersion": "3.24", + "SrcRelease": "6.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "gdbm", + "Version": "1.8.0", + "Release": "39.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gdbm", + "SrcVersion": "1.8.0", + "SrcRelease": "39.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "python", + "Version": "2.6.6", + "Release": "66.el6_8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "python", + "SrcVersion": "2.6.6", + "SrcRelease": "66.el6_8", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "rpm-python", + "Version": "4.8.0", + "Release": "59.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.8.0", + "SrcRelease": "59.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "python-pycurl", + "Version": "7.19.0", + "Release": "9.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "python-pycurl", + "SrcVersion": "7.19.0", + "SrcRelease": "9.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "python-iniparse", + "Version": "0.3.1", + "Release": "2.1.el6", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "python-iniparse", + "SrcVersion": "0.3.1", + "SrcRelease": "2.1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "gamin", + "Version": "0.1.10", + "Release": "9.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gamin", + "SrcVersion": "0.1.10", + "SrcRelease": "9.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "shared-mime-info", + "Version": "0.70", + "Release": "6.el6", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "shared-mime-info", + "SrcVersion": "0.70", + "SrcRelease": "6.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "libuser", + "Version": "0.56.13", + "Release": "8.el6_7", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libuser", + "SrcVersion": "0.56.13", + "SrcRelease": "8.el6_7", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "yum-plugin-fastestmirror", + "Version": "1.1.30", + "Release": "42.el6_10", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "yum-utils", + "SrcVersion": "1.1.30", + "SrcRelease": "42.el6_10", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "centos-release", + "Version": "6", + "Release": "10.el6.centos.12.3", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "centos-release", + "SrcVersion": "6", + "SrcRelease": "10.el6.centos.12.3", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "yum-plugin-ovl", + "Version": "1.1.30", + "Release": "42.el6_10", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "yum-utils", + "SrcVersion": "1.1.30", + "SrcRelease": "42.el6_10", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + }, + { + "Name": "rootfiles", + "Version": "8.1", + "Release": "6.1.el6", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "rootfiles", + "SrcVersion": "8.1", + "SrcRelease": "6.1.el6", + "SrcEpoch":0, + "LayerID":"sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + } +] diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/debian-buster.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/debian-buster.json.golden new file mode 100644 index 000000000000..c00b7cf51073 --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/debian-buster.json.golden @@ -0,0 +1,1906 @@ +[ + { + "ID": "adduser@3.118", + "Name": "adduser", + "Version": "3.118", + "SrcName": "adduser", + "SrcVersion": "3.118", + "Licenses": [ + "GPL-2.0" + ], + "Maintainer": "Debian Adduser Developers \u003cadduser@packages.debian.org\u003e", + "DependsOn": [ + "debconf@1.5.71", + "passwd@1:4.5-1.1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "apt@1.8.2", + "Name": "apt", + "Version": "1.8.2", + "SrcName": "apt", + "SrcVersion": "1.8.2", + "Licenses": [ + "GPL-2.0" + ], + "Maintainer": "APT Development Team \u003cdeity@lists.debian.org\u003e", + "DependsOn": [ + "adduser@3.118", + "debian-archive-keyring@2019.1", + "gpgv@2.2.12-1+deb10u1", + "libapt-pkg5.0@1.8.2", + "libc6@2.28-10", + "libgcc1@1:8.3.0-6", + "libgnutls30@3.6.7-4", + "libseccomp2@2.3.3-4", + "libstdc++6@8.3.0-6" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "base-files@10.3+deb10u1", + "Name": "base-files", + "Version": "10.3+deb10u1", + "SrcName": "base-files", + "SrcVersion": "10.3+deb10u1", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Santiago Vila \u003csanvila@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "base-passwd@3.5.46", + "Name": "base-passwd", + "Version": "3.5.46", + "SrcName": "base-passwd", + "SrcVersion": "3.5.46", + "Licenses": [ + "GPL-2.0", + "PD" + ], + "Maintainer": "Colin Watson \u003ccjwatson@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libdebconfclient0@0.249" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "bash@5.0-4", + "Name": "bash", + "Version": "5.0", + "Release": "4", + "SrcName": "bash", + "SrcVersion": "5.0", + "SrcRelease": "4", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Matthias Klose \u003cdoko@debian.org\u003e", + "DependsOn": [ + "base-files@10.3+deb10u1", + "debianutils@4.8.6.1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "bsdutils@1:2.33.1-0.1", + "Name": "bsdutils", + "Version": "2.33.1", + "Release": "0.1", + "Epoch": 1, + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "0.1", + "Licenses": [ + "GPL-2.0", + "public-domain", + "BSD-4-Clause", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "LGPL-2.0", + "LGPL-2.1", + "GPL-3.0", + "LGPL-3.0" + ], + "Maintainer": "LaMont Jones \u003clamont@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "coreutils@8.30-3", + "Name": "coreutils", + "Version": "8.30", + "Release": "3", + "SrcName": "coreutils", + "SrcVersion": "8.30", + "SrcRelease": "3", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Michael Stone \u003cmstone@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "dash@0.5.10.2-5", + "Name": "dash", + "Version": "0.5.10.2", + "Release": "5", + "SrcName": "dash", + "SrcVersion": "0.5.10.2", + "SrcRelease": "5", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Andrej Shadura \u003candrewsh@debian.org\u003e", + "DependsOn": [ + "debconf@1.5.71", + "debianutils@4.8.6.1", + "dpkg@1.19.7" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "debconf@1.5.71", + "Name": "debconf", + "Version": "1.5.71", + "SrcName": "debconf", + "SrcVersion": "1.5.71", + "Licenses": [ + "BSD-2-Clause" + ], + "Maintainer": "Debconf Developers \u003cdebconf-devel@lists.alioth.debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "debian-archive-keyring@2019.1", + "Name": "debian-archive-keyring", + "Version": "2019.1", + "SrcName": "debian-archive-keyring", + "SrcVersion": "2019.1", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Debian Release Team \u003cpackages@release.debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "debianutils@4.8.6.1", + "Name": "debianutils", + "Version": "4.8.6.1", + "SrcName": "debianutils", + "SrcVersion": "4.8.6.1", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Clint Adams \u003cclint@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "diffutils@1:3.7-3", + "Name": "diffutils", + "Version": "3.7", + "Release": "3", + "Epoch": 1, + "SrcName": "diffutils", + "SrcVersion": "3.7", + "SrcRelease": "3", + "SrcEpoch": 1, + "Licenses": [ + "GPL-3.0", + "GFDL" + ], + "Maintainer": "Santiago Vila \u003csanvila@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "dpkg@1.19.7", + "Name": "dpkg", + "Version": "1.19.7", + "SrcName": "dpkg", + "SrcVersion": "1.19.7", + "Licenses": [ + "GPL-2.0", + "BSD-2-Clause", + "public-domain-s-s-d", + "public-domain-md5" + ], + "Maintainer": "Dpkg Developers \u003cdebian-dpkg@lists.debian.org\u003e", + "DependsOn": [ + "tar@1.30+dfsg-6" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "e2fsprogs@1.44.5-1+deb10u1", + "Name": "e2fsprogs", + "Version": "1.44.5", + "Release": "1+deb10u1", + "SrcName": "e2fsprogs", + "SrcVersion": "1.44.5", + "SrcRelease": "1+deb10u1", + "Licenses": [ + "GPL-2.0", + "LGPL-2.0" + ], + "Maintainer": "Theodore Y. Ts'o \u003ctytso@mit.edu\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "fdisk@2.33.1-0.1", + "Name": "fdisk", + "Version": "2.33.1", + "Release": "0.1", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "0.1", + "Licenses": [ + "GPL-2.0", + "public-domain", + "BSD-4-Clause", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "LGPL-2.0", + "LGPL-2.1", + "GPL-3.0", + "LGPL-3.0" + ], + "Maintainer": "LaMont Jones \u003clamont@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libfdisk1@2.33.1-0.1", + "libmount1@2.33.1-0.1", + "libncursesw6@6.1+20181013-2+deb10u1", + "libsmartcols1@2.33.1-0.1", + "libtinfo6@6.1+20181013-2+deb10u1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "findutils@4.6.0+git+20190209-2", + "Name": "findutils", + "Version": "4.6.0+git+20190209", + "Release": "2", + "SrcName": "findutils", + "SrcVersion": "4.6.0+git+20190209", + "SrcRelease": "2", + "Licenses": [ + "GPL-3.0", + "GFDL-1.3" + ], + "Maintainer": "Andreas Metzler \u003cametzler@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "gcc-8-base@8.3.0-6", + "Name": "gcc-8-base", + "Version": "8.3.0", + "Release": "6", + "SrcName": "gcc-8", + "SrcVersion": "8.3.0", + "SrcRelease": "6", + "Licenses": [ + "GPL-3.0", + "GFDL-1.2", + "GPL-2.0", + "Artistic", + "LGPL-3.0" + ], + "Maintainer": "Debian GCC Maintainers \u003cdebian-gcc@lists.debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "gpgv@2.2.12-1+deb10u1", + "Name": "gpgv", + "Version": "2.2.12", + "Release": "1+deb10u1", + "SrcName": "gnupg2", + "SrcVersion": "2.2.12", + "SrcRelease": "1+deb10u1", + "Licenses": [ + "GPL-3.0", + "permissive", + "LGPL-2.1", + "Expat", + "BSD-3-Clause", + "LGPL-3.0", + "RFC-Reference", + "TinySCHEME", + "CC0-1.0" + ], + "Maintainer": "Debian GnuPG Maintainers \u003cpkg-gnupg-maint@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libbz2-1.0@1.0.6-9.2~deb10u1", + "libc6@2.28-10", + "libgcrypt20@1.8.4-5", + "libgpg-error0@1.35-1", + "zlib1g@1:1.2.11.dfsg-1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "grep@3.3-1", + "Name": "grep", + "Version": "3.3", + "Release": "1", + "SrcName": "grep", + "SrcVersion": "3.3", + "SrcRelease": "1", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Anibal Monsalve Salazar \u003canibal@debian.org\u003e", + "DependsOn": [ + "dpkg@1.19.7" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "gzip@1.9-3", + "Name": "gzip", + "Version": "1.9", + "Release": "3", + "SrcName": "gzip", + "SrcVersion": "1.9", + "SrcRelease": "3", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Bdale Garbee \u003cbdale@gag.com\u003e", + "DependsOn": [ + "dpkg@1.19.7" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "hostname@3.21", + "Name": "hostname", + "Version": "3.21", + "SrcName": "hostname", + "SrcVersion": "3.21", + "Licenses": [ + "GPL-2.0" + ], + "Maintainer": "Michael Meskes \u003cmeskes@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "init-system-helpers@1.56+nmu1", + "Name": "init-system-helpers", + "Version": "1.56+nmu1", + "SrcName": "init-system-helpers", + "SrcVersion": "1.56+nmu1", + "Licenses": [ + "BSD-3-Clause", + "GPL-2.0" + ], + "Maintainer": "Debian systemd Maintainers \u003cpkg-systemd-maintainers@lists.alioth.debian.org\u003e", + "DependsOn": [ + "perl-base@5.28.1-6" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "iproute2@4.20.0-2", + "Name": "iproute2", + "Version": "4.20.0", + "Release": "2", + "SrcName": "iproute2", + "SrcVersion": "4.20.0", + "SrcRelease": "2", + "Licenses": [ + "GPL-2.0" + ], + "Maintainer": "Alexander Wirt \u003cformorer@debian.org\u003e", + "DependsOn": [ + "debconf@1.5.71", + "libc6@2.28-10", + "libcap2-bin@1:2.25-2", + "libcap2@1:2.25-2", + "libdb5.3@5.3.28+dfsg1-0.5", + "libelf1@0.176-1.1", + "libmnl0@1.0.4-2", + "libselinux1@2.8-1+b1", + "libxtables12@1.8.2-4" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "iputils-ping@3:20180629-2", + "Name": "iputils-ping", + "Version": "20180629", + "Release": "2", + "Epoch": 3, + "SrcName": "iputils", + "SrcVersion": "20180629", + "SrcRelease": "2", + "SrcEpoch": 3, + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Noah Meyerhans \u003cnoahm@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libcap2@1:2.25-2", + "libidn2-0@2.0.5-1", + "libnettle6@3.4.1-1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libacl1@2.2.53-4", + "Name": "libacl1", + "Version": "2.2.53", + "Release": "4", + "SrcName": "acl", + "SrcVersion": "2.2.53", + "SrcRelease": "4", + "Licenses": [ + "GPL-2.0", + "LGPL-2.0", + "LGPL-2.1" + ], + "Maintainer": "Guillem Jover \u003cguillem@debian.org\u003e", + "DependsOn": [ + "libattr1@1:2.4.48-4", + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libapt-pkg5.0@1.8.2", + "Name": "libapt-pkg5.0", + "Version": "1.8.2", + "SrcName": "apt", + "SrcVersion": "1.8.2", + "Licenses": [ + "GPL-2.0" + ], + "Maintainer": "APT Development Team \u003cdeity@lists.debian.org\u003e", + "DependsOn": [ + "libbz2-1.0@1.0.6-9.2~deb10u1", + "libc6@2.28-10", + "libgcc1@1:8.3.0-6", + "liblz4-1@1.8.3-1", + "liblzma5@5.2.4-1", + "libstdc++6@8.3.0-6", + "libsystemd0@241-7~deb10u1", + "libudev1@241-7~deb10u1", + "libzstd1@1.3.8+dfsg-3", + "zlib1g@1:1.2.11.dfsg-1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libattr1@1:2.4.48-4", + "Name": "libattr1", + "Version": "2.4.48", + "Release": "4", + "Epoch": 1, + "SrcName": "attr", + "SrcVersion": "2.4.48", + "SrcRelease": "4", + "SrcEpoch": 1, + "Licenses": [ + "GPL-2.0", + "LGPL-2.0", + "LGPL-2.1" + ], + "Maintainer": "Guillem Jover \u003cguillem@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libaudit-common@1:2.8.4-3", + "Name": "libaudit-common", + "Version": "2.8.4", + "Release": "3", + "Epoch": 1, + "SrcName": "audit", + "SrcVersion": "2.8.4", + "SrcRelease": "3", + "SrcEpoch": 1, + "Licenses": [ + "GPL-2.0", + "LGPL-2.1", + "GPL-1.0" + ], + "Maintainer": "Laurent Bigonville \u003cbigon@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libaudit1@1:2.8.4-3", + "Name": "libaudit1", + "Version": "2.8.4", + "Release": "3", + "Epoch": 1, + "SrcName": "audit", + "SrcVersion": "2.8.4", + "SrcRelease": "3", + "SrcEpoch": 1, + "Licenses": [ + "GPL-2.0", + "LGPL-2.1", + "GPL-1.0" + ], + "Maintainer": "Laurent Bigonville \u003cbigon@debian.org\u003e", + "DependsOn": [ + "libaudit-common@1:2.8.4-3", + "libc6@2.28-10", + "libcap-ng0@0.7.9-2" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libblkid1@2.33.1-0.1", + "Name": "libblkid1", + "Version": "2.33.1", + "Release": "0.1", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "0.1", + "Licenses": [ + "GPL-2.0", + "public-domain", + "BSD-4-Clause", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "LGPL-2.0", + "LGPL-2.1", + "GPL-3.0", + "LGPL-3.0" + ], + "Maintainer": "LaMont Jones \u003clamont@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libuuid1@2.33.1-0.1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libbz2-1.0@1.0.6-9.2~deb10u1", + "Name": "libbz2-1.0", + "Version": "1.0.6", + "Release": "9.2~deb10u1", + "SrcName": "bzip2", + "SrcVersion": "1.0.6", + "SrcRelease": "9.2~deb10u1", + "Licenses": [ + "BSD-variant", + "GPL-2.0" + ], + "Maintainer": "Anibal Monsalve Salazar \u003canibal@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libc-bin@2.28-10", + "Name": "libc-bin", + "Version": "2.28", + "Release": "10", + "SrcName": "glibc", + "SrcVersion": "2.28", + "SrcRelease": "10", + "Licenses": [ + "LGPL-2.1", + "GPL-2.0" + ], + "Maintainer": "GNU Libc Maintainers \u003cdebian-glibc@lists.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libc6@2.28-10", + "Name": "libc6", + "Version": "2.28", + "Release": "10", + "SrcName": "glibc", + "SrcVersion": "2.28", + "SrcRelease": "10", + "Licenses": [ + "LGPL-2.1", + "GPL-2.0" + ], + "Maintainer": "GNU Libc Maintainers \u003cdebian-glibc@lists.debian.org\u003e", + "DependsOn": [ + "libgcc1@1:8.3.0-6" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libcap-ng0@0.7.9-2", + "Name": "libcap-ng0", + "Version": "0.7.9", + "Release": "2", + "SrcName": "libcap-ng", + "SrcVersion": "0.7.9", + "SrcRelease": "2", + "Licenses": [ + "LGPL-2.1", + "GPL-2.0", + "GPL-3.0" + ], + "Maintainer": "Pierre Chifflier \u003cpollux@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libcap2@1:2.25-2", + "Name": "libcap2", + "Version": "2.25", + "Release": "2", + "Epoch": 1, + "SrcName": "libcap2", + "SrcVersion": "2.25", + "SrcRelease": "2", + "SrcEpoch": 1, + "Licenses": [ + "BSD-3-Clause", + "GPL-2.0" + ], + "Maintainer": "Christian Kastner \u003cckk@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libcap2-bin@1:2.25-2", + "Name": "libcap2-bin", + "Version": "2.25", + "Release": "2", + "Epoch": 1, + "SrcName": "libcap2", + "SrcVersion": "2.25", + "SrcRelease": "2", + "SrcEpoch": 1, + "Licenses": [ + "BSD-3-Clause", + "GPL-2.0" + ], + "Maintainer": "Christian Kastner \u003cckk@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libcap2@1:2.25-2" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libcom-err2@1.44.5-1+deb10u1", + "Name": "libcom-err2", + "Version": "1.44.5", + "Release": "1+deb10u1", + "SrcName": "e2fsprogs", + "SrcVersion": "1.44.5", + "SrcRelease": "1+deb10u1", + "Maintainer": "Theodore Y. Ts'o \u003ctytso@mit.edu\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libdb5.3@5.3.28+dfsg1-0.5", + "Name": "libdb5.3", + "Version": "5.3.28+dfsg1", + "Release": "0.5", + "SrcName": "db5.3", + "SrcVersion": "5.3.28+dfsg1", + "SrcRelease": "0.5", + "Maintainer": "Debian Berkeley DB Team \u003cteam+bdb@tracker.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libdebconfclient0@0.249", + "Name": "libdebconfclient0", + "Version": "0.249", + "SrcName": "cdebconf", + "SrcVersion": "0.249", + "Maintainer": "Debian Install System Team \u003cdebian-boot@lists.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libelf1@0.176-1.1", + "Name": "libelf1", + "Version": "0.176", + "Release": "1.1", + "SrcName": "elfutils", + "SrcVersion": "0.176", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-2.0", + "GPL-3.0", + "LGPL-3.0" + ], + "Maintainer": "Kurt Roeckx \u003ckurt@roeckx.be\u003e", + "DependsOn": [ + "libc6@2.28-10", + "zlib1g@1:1.2.11.dfsg-1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libext2fs2@1.44.5-1+deb10u1", + "Name": "libext2fs2", + "Version": "1.44.5", + "Release": "1+deb10u1", + "SrcName": "e2fsprogs", + "SrcVersion": "1.44.5", + "SrcRelease": "1+deb10u1", + "Licenses": [ + "GPL-2.0", + "LGPL-2.0" + ], + "Maintainer": "Theodore Y. Ts'o \u003ctytso@mit.edu\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libfdisk1@2.33.1-0.1", + "Name": "libfdisk1", + "Version": "2.33.1", + "Release": "0.1", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "0.1", + "Licenses": [ + "GPL-2.0", + "public-domain", + "BSD-4-Clause", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "LGPL-2.0", + "LGPL-2.1", + "GPL-3.0", + "LGPL-3.0" + ], + "Maintainer": "LaMont Jones \u003clamont@debian.org\u003e", + "DependsOn": [ + "libblkid1@2.33.1-0.1", + "libc6@2.28-10", + "libuuid1@2.33.1-0.1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libffi6@3.2.1-9", + "Name": "libffi6", + "Version": "3.2.1", + "Release": "9", + "SrcName": "libffi", + "SrcVersion": "3.2.1", + "SrcRelease": "9", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Debian GCC Maintainers \u003cdebian-gcc@lists.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libgcc1@1:8.3.0-6", + "Name": "libgcc1", + "Version": "8.3.0", + "Release": "6", + "Epoch": 1, + "SrcName": "gcc-8", + "SrcVersion": "8.3.0", + "SrcRelease": "6", + "Maintainer": "Debian GCC Maintainers \u003cdebian-gcc@lists.debian.org\u003e", + "DependsOn": [ + "gcc-8-base@8.3.0-6", + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libgcrypt20@1.8.4-5", + "Name": "libgcrypt20", + "Version": "1.8.4", + "Release": "5", + "SrcName": "libgcrypt20", + "SrcVersion": "1.8.4", + "SrcRelease": "5", + "Licenses": [ + "LGPL-3.0", + "GPL-2.0" + ], + "Maintainer": "Debian GnuTLS Maintainers \u003cpkg-gnutls-maint@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libgpg-error0@1.35-1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libgmp10@2:6.1.2+dfsg-4", + "Name": "libgmp10", + "Version": "6.1.2+dfsg", + "Release": "4", + "Epoch": 2, + "SrcName": "gmp", + "SrcVersion": "6.1.2+dfsg", + "SrcRelease": "4", + "SrcEpoch": 2, + "Licenses": [ + "LGPL-3.0", + "GPL-2.0", + "GPL-3.0" + ], + "Maintainer": "Debian Science Team \u003cdebian-science-maintainers@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libgnutls30@3.6.7-4", + "Name": "libgnutls30", + "Version": "3.6.7", + "Release": "4", + "SrcName": "gnutls28", + "SrcVersion": "3.6.7", + "SrcRelease": "4", + "Licenses": [ + "The main library is licensed under GNU Lesser", + "LGPL-3.0", + "GPL-3.0", + "GFDL-1.3", + "CC0 license", + "The MIT License (MIT)", + "LGPLv3+", + "GPL-2.0", + "Apache-2.0" + ], + "Maintainer": "Debian GnuTLS Maintainers \u003cpkg-gnutls-maint@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libgmp10@2:6.1.2+dfsg-4", + "libhogweed4@3.4.1-1", + "libidn2-0@2.0.5-1", + "libnettle6@3.4.1-1", + "libp11-kit0@0.23.15-2", + "libtasn1-6@4.13-3", + "libunistring2@0.9.10-1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libgpg-error0@1.35-1", + "Name": "libgpg-error0", + "Version": "1.35", + "Release": "1", + "SrcName": "libgpg-error", + "SrcVersion": "1.35", + "SrcRelease": "1", + "Licenses": [ + "LGPL-2.1", + "BSD-3-Clause", + "g10-permissive", + "GPL-3.0" + ], + "Maintainer": "Debian GnuPG Maintainers \u003cpkg-gnupg-maint@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libhogweed4@3.4.1-1", + "Name": "libhogweed4", + "Version": "3.4.1", + "Release": "1", + "SrcName": "nettle", + "SrcVersion": "3.4.1", + "SrcRelease": "1", + "Maintainer": "Magnus Holmgren \u003cholmgren@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libgmp10@2:6.1.2+dfsg-4", + "libnettle6@3.4.1-1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libidn2-0@2.0.5-1", + "Name": "libidn2-0", + "Version": "2.0.5", + "Release": "1", + "SrcName": "libidn2", + "SrcVersion": "2.0.5", + "SrcRelease": "1", + "Licenses": [ + "GPL-3.0", + "LGPL-3.0", + "GPL-2.0", + "Unicode" + ], + "Maintainer": "Debian Libidn team \u003chelp-libidn@gnu.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libunistring2@0.9.10-1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "liblz4-1@1.8.3-1", + "Name": "liblz4-1", + "Version": "1.8.3", + "Release": "1", + "SrcName": "lz4", + "SrcVersion": "1.8.3", + "SrcRelease": "1", + "Licenses": [ + "BSD-2-Clause", + "GPL-2.0" + ], + "Maintainer": "Nobuhiro Iwamatsu \u003ciwamatsu@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "liblzma5@5.2.4-1", + "Name": "liblzma5", + "Version": "5.2.4", + "Release": "1", + "SrcName": "xz-utils", + "SrcVersion": "5.2.4", + "SrcRelease": "1", + "Licenses": [ + "PD", + "probably-PD", + "GPL-2.0", + "LGPL-2.1", + "permissive-fsf", + "Autoconf", + "permissive-nowarranty", + "none", + "config-h", + "LGPL-2.0", + "noderivs", + "PD-debian", + "GPL-3.0" + ], + "Maintainer": "Jonathan Nieder \u003cjrnieder@gmail.com\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libmnl0@1.0.4-2", + "Name": "libmnl0", + "Version": "1.0.4", + "Release": "2", + "SrcName": "libmnl", + "SrcVersion": "1.0.4", + "SrcRelease": "2", + "Licenses": [ + "LGPL-2.1", + "GPL-2.0" + ], + "Maintainer": "Anibal Monsalve Salazar \u003canibal@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libmount1@2.33.1-0.1", + "Name": "libmount1", + "Version": "2.33.1", + "Release": "0.1", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "0.1", + "Licenses": [ + "GPL-2.0", + "public-domain", + "BSD-4-Clause", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "LGPL-2.0", + "LGPL-2.1", + "GPL-3.0", + "LGPL-3.0" + ], + "Maintainer": "LaMont Jones \u003clamont@debian.org\u003e", + "DependsOn": [ + "libblkid1@2.33.1-0.1", + "libc6@2.28-10", + "libselinux1@2.8-1+b1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libncursesw6@6.1+20181013-2+deb10u1", + "Name": "libncursesw6", + "Version": "6.1+20181013", + "Release": "2+deb10u1", + "SrcName": "ncurses", + "SrcVersion": "6.1+20181013", + "SrcRelease": "2+deb10u1", + "Maintainer": "Craig Small \u003ccsmall@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libtinfo6@6.1+20181013-2+deb10u1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libnettle6@3.4.1-1", + "Name": "libnettle6", + "Version": "3.4.1", + "Release": "1", + "SrcName": "nettle", + "SrcVersion": "3.4.1", + "SrcRelease": "1", + "Licenses": [ + "LGPL-2.1", + "LGPL-2.0", + "other", + "GPL-2.0", + "GPL-2.0-with-autoconf-exception", + "public-domain", + "GAP", + "LGPL-3.0", + "GPL-3.0" + ], + "Maintainer": "Magnus Holmgren \u003cholmgren@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libp11-kit0@0.23.15-2", + "Name": "libp11-kit0", + "Version": "0.23.15", + "Release": "2", + "SrcName": "p11-kit", + "SrcVersion": "0.23.15", + "SrcRelease": "2", + "Licenses": [ + "BSD-3-Clause", + "permissive-like-automake-output", + "ISC", + "ISC+IBM", + "same-as-rest-of-p11kit" + ], + "Maintainer": "Debian GnuTLS Maintainers \u003cpkg-gnutls-maint@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libffi6@3.2.1-9" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libpam-modules@1.3.1-5", + "Name": "libpam-modules", + "Version": "1.3.1", + "Release": "5", + "SrcName": "pam", + "SrcVersion": "1.3.1", + "SrcRelease": "5", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Steve Langasek \u003cvorlon@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libpam-modules-bin@1.3.1-5", + "Name": "libpam-modules-bin", + "Version": "1.3.1", + "Release": "5", + "SrcName": "pam", + "SrcVersion": "1.3.1", + "SrcRelease": "5", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Steve Langasek \u003cvorlon@debian.org\u003e", + "DependsOn": [ + "libaudit1@1:2.8.4-3", + "libc6@2.28-10", + "libpam0g@1.3.1-5", + "libselinux1@2.8-1+b1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libpam-runtime@1.3.1-5", + "Name": "libpam-runtime", + "Version": "1.3.1", + "Release": "5", + "SrcName": "pam", + "SrcVersion": "1.3.1", + "SrcRelease": "5", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Steve Langasek \u003cvorlon@debian.org\u003e", + "DependsOn": [ + "debconf@1.5.71", + "debconf@1.5.71", + "libpam-modules@1.3.1-5" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libpam0g@1.3.1-5", + "Name": "libpam0g", + "Version": "1.3.1", + "Release": "5", + "SrcName": "pam", + "SrcVersion": "1.3.1", + "SrcRelease": "5", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Steve Langasek \u003cvorlon@debian.org\u003e", + "DependsOn": [ + "debconf@1.5.71", + "libaudit1@1:2.8.4-3", + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libpcre3@2:8.39-12", + "Name": "libpcre3", + "Version": "8.39", + "Release": "12", + "Epoch": 2, + "SrcName": "pcre3", + "SrcVersion": "8.39", + "SrcRelease": "12", + "SrcEpoch": 2, + "Maintainer": "Matthew Vernon \u003cmatthew@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libseccomp2@2.3.3-4", + "Name": "libseccomp2", + "Version": "2.3.3", + "Release": "4", + "SrcName": "libseccomp", + "SrcVersion": "2.3.3", + "SrcRelease": "4", + "Licenses": [ + "LGPL-2.1" + ], + "Maintainer": "Kees Cook \u003ckees@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libselinux1@2.8-1+b1", + "Name": "libselinux1", + "Version": "2.8", + "Release": "1+b1", + "SrcName": "libselinux", + "SrcVersion": "2.8", + "SrcRelease": "1", + "Licenses": [ + "LGPL-2.1", + "GPL-2.0" + ], + "Maintainer": "Debian SELinux maintainers \u003cselinux-devel@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libpcre3@2:8.39-12" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libsemanage-common@2.8-2", + "Name": "libsemanage-common", + "Version": "2.8", + "Release": "2", + "SrcName": "libsemanage", + "SrcVersion": "2.8", + "SrcRelease": "2", + "Licenses": [ + "LGPL-3.0", + "GPL-3.0" + ], + "Maintainer": "Debian SELinux maintainers \u003cselinux-devel@lists.alioth.debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libsemanage1@2.8-2", + "Name": "libsemanage1", + "Version": "2.8", + "Release": "2", + "SrcName": "libsemanage", + "SrcVersion": "2.8", + "SrcRelease": "2", + "Licenses": [ + "LGPL-3.0", + "GPL-3.0" + ], + "Maintainer": "Debian SELinux maintainers \u003cselinux-devel@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libaudit1@1:2.8.4-3", + "libbz2-1.0@1.0.6-9.2~deb10u1", + "libc6@2.28-10", + "libselinux1@2.8-1+b1", + "libsemanage-common@2.8-2", + "libsepol1@2.8-1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libsepol1@2.8-1", + "Name": "libsepol1", + "Version": "2.8", + "Release": "1", + "SrcName": "libsepol", + "SrcVersion": "2.8", + "SrcRelease": "1", + "Licenses": [ + "LGPL-3.0", + "GPL-3.0" + ], + "Maintainer": "Debian SELinux maintainers \u003cselinux-devel@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libsmartcols1@2.33.1-0.1", + "Name": "libsmartcols1", + "Version": "2.33.1", + "Release": "0.1", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "0.1", + "Licenses": [ + "GPL-2.0", + "public-domain", + "BSD-4-Clause", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "LGPL-2.0", + "LGPL-2.1", + "GPL-3.0", + "LGPL-3.0" + ], + "Maintainer": "LaMont Jones \u003clamont@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libss2@1.44.5-1+deb10u1", + "Name": "libss2", + "Version": "1.44.5", + "Release": "1+deb10u1", + "SrcName": "e2fsprogs", + "SrcVersion": "1.44.5", + "SrcRelease": "1+deb10u1", + "Maintainer": "Theodore Y. Ts'o \u003ctytso@mit.edu\u003e", + "DependsOn": [ + "libc6@2.28-10", + "libcom-err2@1.44.5-1+deb10u1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libstdc++6@8.3.0-6", + "Name": "libstdc++6", + "Version": "8.3.0", + "Release": "6", + "SrcName": "gcc-8", + "SrcVersion": "8.3.0", + "SrcRelease": "6", + "Maintainer": "Debian GCC Maintainers \u003cdebian-gcc@lists.debian.org\u003e", + "DependsOn": [ + "gcc-8-base@8.3.0-6", + "libc6@2.28-10", + "libgcc1@1:8.3.0-6" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libsystemd0@241-7~deb10u1", + "Name": "libsystemd0", + "Version": "241", + "Release": "7~deb10u1", + "SrcName": "systemd", + "SrcVersion": "241", + "SrcRelease": "7~deb10u1", + "Licenses": [ + "LGPL-2.1", + "CC0-1.0", + "GPL-2.0", + "Expat", + "public-domain" + ], + "Maintainer": "Debian systemd Maintainers \u003cpkg-systemd-maintainers@lists.alioth.debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libtasn1-6@4.13-3", + "Name": "libtasn1-6", + "Version": "4.13", + "Release": "3", + "SrcName": "libtasn1-6", + "SrcVersion": "4.13", + "SrcRelease": "3", + "Licenses": [ + "LGPL-3.0", + "LGPL-2.1", + "GPL-3.0", + "GFDL-1.3" + ], + "Maintainer": "Debian GnuTLS Maintainers \u003cpkg-gnutls-maint@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libtinfo6@6.1+20181013-2+deb10u1", + "Name": "libtinfo6", + "Version": "6.1+20181013", + "Release": "2+deb10u1", + "SrcName": "ncurses", + "SrcVersion": "6.1+20181013", + "SrcRelease": "2+deb10u1", + "Maintainer": "Craig Small \u003ccsmall@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libudev1@241-7~deb10u1", + "Name": "libudev1", + "Version": "241", + "Release": "7~deb10u1", + "SrcName": "systemd", + "SrcVersion": "241", + "SrcRelease": "7~deb10u1", + "Licenses": [ + "LGPL-2.1", + "CC0-1.0", + "GPL-2.0", + "Expat", + "public-domain" + ], + "Maintainer": "Debian systemd Maintainers \u003cpkg-systemd-maintainers@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libunistring2@0.9.10-1", + "Name": "libunistring2", + "Version": "0.9.10", + "Release": "1", + "SrcName": "libunistring", + "SrcVersion": "0.9.10", + "SrcRelease": "1", + "Licenses": [ + "LGPL-3.0", + "GPL-2.0", + "FreeSoftware", + "GPL-2+ with distribution exception", + "GPL-3.0", + "GFDL-1.2+", + "MIT", + "GFDL-1.2" + ], + "Maintainer": "Jörg Frings-Fürst \u003cdebian@jff.email\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libuuid1@2.33.1-0.1", + "Name": "libuuid1", + "Version": "2.33.1", + "Release": "0.1", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "0.1", + "Licenses": [ + "GPL-2.0", + "public-domain", + "BSD-4-Clause", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "LGPL-2.0", + "LGPL-2.1", + "GPL-3.0", + "LGPL-3.0" + ], + "Maintainer": "LaMont Jones \u003clamont@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libxtables12@1.8.2-4", + "Name": "libxtables12", + "Version": "1.8.2", + "Release": "4", + "SrcName": "iptables", + "SrcVersion": "1.8.2", + "SrcRelease": "4", + "Licenses": [ + "GPL-2.0", + "Artistic-2", + "custom" + ], + "Maintainer": "Debian Netfilter Packaging Team \u003cpkg-netfilter-team@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "libzstd1@1.3.8+dfsg-3", + "Name": "libzstd1", + "Version": "1.3.8+dfsg", + "Release": "3", + "SrcName": "libzstd", + "SrcVersion": "1.3.8+dfsg", + "SrcRelease": "3", + "Licenses": [ + "BSD-3-Clause", + "GPL-2.0", + "Zlib", + "Expat" + ], + "Maintainer": "Debian Med Packaging Team \u003cdebian-med-packaging@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "login@1:4.5-1.1", + "Name": "login", + "Version": "4.5", + "Release": "1.1", + "Epoch": 1, + "SrcName": "shadow", + "SrcVersion": "4.5", + "SrcRelease": "1.1", + "SrcEpoch": 1, + "Licenses": [ + "GPL-2.0" + ], + "Maintainer": "Shadow package maintainers \u003cpkg-shadow-devel@lists.alioth.debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "mawk@1.3.3-17+b3", + "Name": "mawk", + "Version": "1.3.3", + "Release": "17+b3", + "SrcName": "mawk", + "SrcVersion": "1.3.3", + "SrcRelease": "17", + "Licenses": [ + "GPL-2.0" + ], + "Maintainer": "Steve Langasek \u003cvorlon@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "mount@2.33.1-0.1", + "Name": "mount", + "Version": "2.33.1", + "Release": "0.1", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "0.1", + "Licenses": [ + "GPL-2.0", + "public-domain", + "BSD-4-Clause", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "LGPL-2.0", + "LGPL-2.1", + "GPL-3.0", + "LGPL-3.0" + ], + "Maintainer": "LaMont Jones \u003clamont@debian.org\u003e", + "DependsOn": [ + "util-linux@2.33.1-0.1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "ncurses-base@6.1+20181013-2+deb10u1", + "Name": "ncurses-base", + "Version": "6.1+20181013", + "Release": "2+deb10u1", + "SrcName": "ncurses", + "SrcVersion": "6.1+20181013", + "SrcRelease": "2+deb10u1", + "Maintainer": "Craig Small \u003ccsmall@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "ncurses-bin@6.1+20181013-2+deb10u1", + "Name": "ncurses-bin", + "Version": "6.1+20181013", + "Release": "2+deb10u1", + "SrcName": "ncurses", + "SrcVersion": "6.1+20181013", + "SrcRelease": "2+deb10u1", + "Maintainer": "Craig Small \u003ccsmall@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "passwd@1:4.5-1.1", + "Name": "passwd", + "Version": "4.5", + "Release": "1.1", + "Epoch": 1, + "SrcName": "shadow", + "SrcVersion": "4.5", + "SrcRelease": "1.1", + "SrcEpoch": 1, + "Licenses": [ + "GPL-2.0" + ], + "Maintainer": "Shadow package maintainers \u003cpkg-shadow-devel@lists.alioth.debian.org\u003e", + "DependsOn": [ + "libaudit1@1:2.8.4-3", + "libc6@2.28-10", + "libpam-modules@1.3.1-5", + "libpam0g@1.3.1-5", + "libselinux1@2.8-1+b1", + "libsemanage1@2.8-2" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "perl-base@5.28.1-6", + "Name": "perl-base", + "Version": "5.28.1", + "Release": "6", + "SrcName": "perl", + "SrcVersion": "5.28.1", + "SrcRelease": "6", + "Maintainer": "Niko Tyni \u003cntyni@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "sed@4.7-1", + "Name": "sed", + "Version": "4.7", + "Release": "1", + "SrcName": "sed", + "SrcVersion": "4.7", + "SrcRelease": "1", + "Licenses": [ + "GPL-3.0" + ], + "Maintainer": "Clint Adams \u003cclint@debian.org\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "sysvinit-utils@2.93-8", + "Name": "sysvinit-utils", + "Version": "2.93", + "Release": "8", + "SrcName": "sysvinit", + "SrcVersion": "2.93", + "SrcRelease": "8", + "Licenses": [ + "GPL-2.0" + ], + "Maintainer": "Debian sysvinit maintainers \u003cdebian-init-diversity@chiark.greenend.org.uk\u003e", + "DependsOn": [ + "init-system-helpers@1.56+nmu1", + "libc6@2.28-10", + "util-linux@2.33.1-0.1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "tar@1.30+dfsg-6", + "Name": "tar", + "Version": "1.30+dfsg", + "Release": "6", + "SrcName": "tar", + "SrcVersion": "1.30+dfsg", + "SrcRelease": "6", + "Licenses": [ + "GPL-3.0", + "GPL-2.0" + ], + "Maintainer": "Bdale Garbee \u003cbdale@gag.com\u003e", + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "tzdata@2019b-0+deb10u1", + "Name": "tzdata", + "Version": "2019b", + "Release": "0+deb10u1", + "SrcName": "tzdata", + "SrcVersion": "2019b", + "SrcRelease": "0+deb10u1", + "Maintainer": "GNU Libc Maintainers \u003cdebian-glibc@lists.debian.org\u003e", + "DependsOn": [ + "debconf@1.5.71" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "util-linux@2.33.1-0.1", + "Name": "util-linux", + "Version": "2.33.1", + "Release": "0.1", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "0.1", + "Licenses": [ + "GPL-2.0", + "public-domain", + "BSD-4-Clause", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "LGPL-2.0", + "LGPL-2.1", + "GPL-3.0", + "LGPL-3.0" + ], + "Maintainer": "LaMont Jones \u003clamont@debian.org\u003e", + "DependsOn": [ + "fdisk@2.33.1-0.1", + "login@1:4.5-1.1" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + }, + { + "ID": "zlib1g@1:1.2.11.dfsg-1", + "Name": "zlib1g", + "Version": "1.2.11.dfsg", + "Release": "1", + "Epoch": 1, + "SrcName": "zlib", + "SrcVersion": "1.2.11.dfsg", + "SrcRelease": "1", + "SrcEpoch": 1, + "Licenses": [ + "Zlib" + ], + "Maintainer": "Mark Brown \u003cbroonie@debian.org\u003e", + "DependsOn": [ + "libc6@2.28-10" + ], + "Layer": { + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + } +] \ No newline at end of file diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/fedora-35.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/fedora-35.json.golden new file mode 100644 index 000000000000..0cd3289afa05 --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/fedora-35.json.golden @@ -0,0 +1,1959 @@ +[ + { + "Name": "alternatives", + "Version": "1.19", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "chkconfig", + "SrcVersion": "1.19", + "SrcRelease": "1.fc35", + "License": "GPLv2", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "audit-libs", + "Version": "3.0.7", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "audit", + "SrcVersion": "3.0.7", + "SrcRelease": "2.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "basesystem", + "Version": "11", + "Release": "12.fc35", + "Arch": "noarch", + "SrcName": "basesystem", + "SrcVersion": "11", + "SrcRelease": "12.fc35", + "License": "Public Domain", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "bash", + "Version": "5.1.8", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "5.1.8", + "SrcRelease": "2.fc35", + "License": "GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "bzip2-libs", + "Version": "1.0.8", + "Release": "9.fc35", + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.8", + "SrcRelease": "9.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "ca-certificates", + "Version": "2021.2.52", + "Release": "1.0.fc35", + "Arch": "noarch", + "SrcName": "ca-certificates", + "SrcVersion": "2021.2.52", + "SrcRelease": "1.0.fc35", + "License": "Public Domain", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "coreutils", + "Version": "8.32", + "Release": "31.fc35", + "Arch": "x86_64", + "SrcName": "coreutils", + "SrcVersion": "8.32", + "SrcRelease": "31.fc35", + "License": "GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "coreutils-common", + "Version": "8.32", + "Release": "31.fc35", + "Arch": "x86_64", + "SrcName": "coreutils", + "SrcVersion": "8.32", + "SrcRelease": "31.fc35", + "License": "GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "cracklib", + "Version": "2.9.6", + "Release": "27.fc35", + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.9.6", + "SrcRelease": "27.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "crypto-policies", + "Version": "20210819", + "Release": "1.gitd0fdcfb.fc35", + "Arch": "noarch", + "SrcName": "crypto-policies", + "SrcVersion": "20210819", + "SrcRelease": "1.gitd0fdcfb.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "curl", + "Version": "7.79.1", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.79.1", + "SrcRelease": "1.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "cyrus-sasl-lib", + "Version": "2.1.27", + "Release": "13.fc35", + "Arch": "x86_64", + "SrcName": "cyrus-sasl", + "SrcVersion": "2.1.27", + "SrcRelease": "13.fc35", + "License": "BSD with advertising", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "dnf", + "Version": "4.9.0", + "Release": "1.fc35", + "Arch": "noarch", + "SrcName": "dnf", + "SrcVersion": "4.9.0", + "SrcRelease": "1.fc35", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "dnf-data", + "Version": "4.9.0", + "Release": "1.fc35", + "Arch": "noarch", + "SrcName": "dnf", + "SrcVersion": "4.9.0", + "SrcRelease": "1.fc35", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "elfutils-default-yama-scope", + "Version": "0.186", + "Release": "1.fc35", + "Arch": "noarch", + "SrcName": "elfutils", + "SrcVersion": "0.186", + "SrcRelease": "1.fc35", + "License": "GPLv2+ or LGPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "elfutils-libelf", + "Version": "0.186", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.186", + "SrcRelease": "1.fc35", + "License": "GPLv2+ or LGPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "elfutils-libs", + "Version": "0.186", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.186", + "SrcRelease": "1.fc35", + "License": "GPLv2+ or LGPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "expat", + "Version": "2.4.4", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "expat", + "SrcVersion": "2.4.4", + "SrcRelease": "1.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "fedora-gpg-keys", + "Version": "35", + "Release": "1", + "Arch": "noarch", + "SrcName": "fedora-repos", + "SrcVersion": "35", + "SrcRelease": "1", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "fedora-release-common", + "Version": "35", + "Release": "36", + "Arch": "noarch", + "SrcName": "fedora-release", + "SrcVersion": "35", + "SrcRelease": "36", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "fedora-release-container", + "Version": "35", + "Release": "36", + "Arch": "noarch", + "SrcName": "fedora-release", + "SrcVersion": "35", + "SrcRelease": "36", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "fedora-release-identity-container", + "Version": "35", + "Release": "36", + "Arch": "noarch", + "SrcName": "fedora-release", + "SrcVersion": "35", + "SrcRelease": "36", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "fedora-repos", + "Version": "35", + "Release": "1", + "Arch": "noarch", + "SrcName": "fedora-repos", + "SrcVersion": "35", + "SrcRelease": "1", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "fedora-repos-modular", + "Version": "35", + "Release": "1", + "Arch": "noarch", + "SrcName": "fedora-repos", + "SrcVersion": "35", + "SrcRelease": "1", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "file-libs", + "Version": "5.40", + "Release": "9.fc35", + "Arch": "x86_64", + "SrcName": "file", + "SrcVersion": "5.40", + "SrcRelease": "9.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "filesystem", + "Version": "3.14", + "Release": "7.fc35", + "Arch": "x86_64", + "SrcName": "filesystem", + "SrcVersion": "3.14", + "SrcRelease": "7.fc35", + "License": "Public Domain", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "gawk", + "Version": "5.1.0", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "gawk", + "SrcVersion": "5.1.0", + "SrcRelease": "4.fc35", + "License": "GPLv3+ and GPLv2+ and LGPLv2+ and BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "gdbm-libs", + "Version": "1.22", + "Release": "1.fc35", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "gdbm", + "SrcVersion": "1.22", + "SrcRelease": "1.fc35", + "SrcEpoch": 1, + "License": "GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "glib2", + "Version": "2.70.4", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "glib2", + "SrcVersion": "2.70.4", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "glibc", + "Version": "2.34", + "Release": "25.fc35", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.34", + "SrcRelease": "25.fc35", + "License": "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+ and GPLv2+ with exceptions and BSD and Inner-Net and ISC and Public Domain and GFDL", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "glibc-common", + "Version": "2.34", + "Release": "25.fc35", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.34", + "SrcRelease": "25.fc35", + "License": "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+ and GPLv2+ with exceptions and BSD and Inner-Net and ISC and Public Domain and GFDL", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "glibc-minimal-langpack", + "Version": "2.34", + "Release": "25.fc35", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.34", + "SrcRelease": "25.fc35", + "License": "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+ and GPLv2+ with exceptions and BSD and Inner-Net and ISC and Public Domain and GFDL", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "gmp", + "Version": "6.2.0", + "Release": "7.fc35", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "gmp", + "SrcVersion": "6.2.0", + "SrcRelease": "7.fc35", + "SrcEpoch": 1, + "License": "LGPLv3+ or GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "gnupg2", + "Version": "2.3.4", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "gnupg2", + "SrcVersion": "2.3.4", + "SrcRelease": "1.fc35", + "License": "GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "gnutls", + "Version": "3.7.2", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "gnutls", + "SrcVersion": "3.7.2", + "SrcRelease": "2.fc35", + "License": "GPLv3+ and LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "gpg-pubkey", + "Version": "9867c58f", + "Release": "601c49ca", + "Arch": "None", + "License": "pubkey", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "gpgme", + "Version": "1.15.1", + "Release": "6.fc35", + "Arch": "x86_64", + "SrcName": "gpgme", + "SrcVersion": "1.15.1", + "SrcRelease": "6.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "grep", + "Version": "3.6", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "grep", + "SrcVersion": "3.6", + "SrcRelease": "4.fc35", + "License": "GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "gzip", + "Version": "1.10", + "Release": "5.fc35", + "Arch": "x86_64", + "SrcName": "gzip", + "SrcVersion": "1.10", + "SrcRelease": "5.fc35", + "License": "GPLv3+ and GFDL", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "ima-evm-utils", + "Version": "1.3.2", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "ima-evm-utils", + "SrcVersion": "1.3.2", + "SrcRelease": "3.fc35", + "License": "GPLv2", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "json-c", + "Version": "0.15", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "json-c", + "SrcVersion": "0.15", + "SrcRelease": "2.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "keyutils-libs", + "Version": "1.6.1", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "keyutils", + "SrcVersion": "1.6.1", + "SrcRelease": "3.fc35", + "License": "GPLv2+ and LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "krb5-libs", + "Version": "1.19.2", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "krb5", + "SrcVersion": "1.19.2", + "SrcRelease": "2.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libacl", + "Version": "2.3.1", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "acl", + "SrcVersion": "2.3.1", + "SrcRelease": "2.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libarchive", + "Version": "3.5.2", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "libarchive", + "SrcVersion": "3.5.2", + "SrcRelease": "2.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libassuan", + "Version": "2.5.5", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "libassuan", + "SrcVersion": "2.5.5", + "SrcRelease": "3.fc35", + "License": "LGPLv2+ and GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libattr", + "Version": "2.5.1", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "attr", + "SrcVersion": "2.5.1", + "SrcRelease": "3.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libblkid", + "Version": "2.37.4", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.37.4", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libbrotli", + "Version": "1.0.9", + "Release": "6.fc35", + "Arch": "x86_64", + "SrcName": "brotli", + "SrcVersion": "1.0.9", + "SrcRelease": "6.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libcap", + "Version": "2.48", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "libcap", + "SrcVersion": "2.48", + "SrcRelease": "3.fc35", + "License": "BSD or GPLv2", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libcap-ng", + "Version": "0.8.2", + "Release": "8.fc35", + "Arch": "x86_64", + "SrcName": "libcap-ng", + "SrcVersion": "0.8.2", + "SrcRelease": "8.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libcom_err", + "Version": "1.46.3", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "e2fsprogs", + "SrcVersion": "1.46.3", + "SrcRelease": "1.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libcomps", + "Version": "0.1.18", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libcomps", + "SrcVersion": "0.1.18", + "SrcRelease": "1.fc35", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libcurl", + "Version": "7.79.1", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.79.1", + "SrcRelease": "1.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libdb", + "Version": "5.3.28", + "Release": "50.fc35", + "Arch": "x86_64", + "SrcName": "libdb", + "SrcVersion": "5.3.28", + "SrcRelease": "50.fc35", + "License": "BSD and LGPLv2 and Sleepycat", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libdnf", + "Version": "0.64.0", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libdnf", + "SrcVersion": "0.64.0", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libeconf", + "Version": "0.4.0", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "libeconf", + "SrcVersion": "0.4.0", + "SrcRelease": "2.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libffi", + "Version": "3.1", + "Release": "29.fc35", + "Arch": "x86_64", + "SrcName": "libffi", + "SrcVersion": "3.1", + "SrcRelease": "29.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libfsverity", + "Version": "1.4", + "Release": "6.fc35", + "Arch": "x86_64", + "SrcName": "fsverity-utils", + "SrcVersion": "1.4", + "SrcRelease": "6.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libgcc", + "Version": "11.2.1", + "Release": "9.fc35", + "Arch": "x86_64", + "SrcName": "gcc", + "SrcVersion": "11.2.1", + "SrcRelease": "9.fc35", + "License": "GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libgcrypt", + "Version": "1.9.4", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libgcrypt", + "SrcVersion": "1.9.4", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libgomp", + "Version": "11.2.1", + "Release": "9.fc35", + "Arch": "x86_64", + "SrcName": "gcc", + "SrcVersion": "11.2.1", + "SrcRelease": "9.fc35", + "License": "GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libgpg-error", + "Version": "1.43", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libgpg-error", + "SrcVersion": "1.43", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libidn2", + "Version": "2.3.2", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "libidn2", + "SrcVersion": "2.3.2", + "SrcRelease": "3.fc35", + "License": "(GPLv2+ or LGPLv3+) and GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libksba", + "Version": "1.6.0", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "libksba", + "SrcVersion": "1.6.0", + "SrcRelease": "2.fc35", + "License": "(LGPLv3+ or GPLv2+) and GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libmodulemd", + "Version": "2.14.0", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libmodulemd", + "SrcVersion": "2.14.0", + "SrcRelease": "1.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libmount", + "Version": "2.37.4", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.37.4", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libnghttp2", + "Version": "1.45.1", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "nghttp2", + "SrcVersion": "1.45.1", + "SrcRelease": "1.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libnsl2", + "Version": "1.3.0", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "libnsl2", + "SrcVersion": "1.3.0", + "SrcRelease": "4.fc35", + "License": "BSD and LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libpsl", + "Version": "0.21.1", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "libpsl", + "SrcVersion": "0.21.1", + "SrcRelease": "4.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libpwquality", + "Version": "1.4.4", + "Release": "6.fc35", + "Arch": "x86_64", + "SrcName": "libpwquality", + "SrcVersion": "1.4.4", + "SrcRelease": "6.fc35", + "License": "BSD or GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "librepo", + "Version": "1.14.2", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "librepo", + "SrcVersion": "1.14.2", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libreport-filesystem", + "Version": "2.15.2", + "Release": "6.fc35", + "Arch": "noarch", + "SrcName": "libreport", + "SrcVersion": "2.15.2", + "SrcRelease": "6.fc35", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libselinux", + "Version": "3.3", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libselinux", + "SrcVersion": "3.3", + "SrcRelease": "1.fc35", + "License": "Public Domain", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libsemanage", + "Version": "3.3", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libsemanage", + "SrcVersion": "3.3", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libsepol", + "Version": "3.3", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "libsepol", + "SrcVersion": "3.3", + "SrcRelease": "2.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libsigsegv", + "Version": "2.13", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "libsigsegv", + "SrcVersion": "2.13", + "SrcRelease": "3.fc35", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libsmartcols", + "Version": "2.37.4", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.37.4", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libsolv", + "Version": "0.7.19", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "libsolv", + "SrcVersion": "0.7.19", + "SrcRelease": "3.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libssh", + "Version": "0.9.6", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libssh", + "SrcVersion": "0.9.6", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libssh-config", + "Version": "0.9.6", + "Release": "1.fc35", + "Arch": "noarch", + "SrcName": "libssh", + "SrcVersion": "0.9.6", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libstdc++", + "Version": "11.2.1", + "Release": "9.fc35", + "Arch": "x86_64", + "SrcName": "gcc", + "SrcVersion": "11.2.1", + "SrcRelease": "9.fc35", + "License": "GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libtasn1", + "Version": "4.16.0", + "Release": "6.fc35", + "Arch": "x86_64", + "SrcName": "libtasn1", + "SrcVersion": "4.16.0", + "SrcRelease": "6.fc35", + "License": "GPLv3+ and LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libtirpc", + "Version": "1.3.2", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libtirpc", + "SrcVersion": "1.3.2", + "SrcRelease": "1.fc35", + "License": "SISSL and BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libunistring", + "Version": "0.9.10", + "Release": "14.fc35", + "Arch": "x86_64", + "SrcName": "libunistring", + "SrcVersion": "0.9.10", + "SrcRelease": "14.fc35", + "License": "GPLv2+ or LGPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libuuid", + "Version": "2.37.4", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.37.4", + "SrcRelease": "1.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libverto", + "Version": "0.3.2", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "libverto", + "SrcVersion": "0.3.2", + "SrcRelease": "2.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libxcrypt", + "Version": "4.4.28", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libxcrypt", + "SrcVersion": "4.4.28", + "SrcRelease": "1.fc35", + "License": "LGPLv2+ and BSD and Public Domain", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libxml2", + "Version": "2.9.12", + "Release": "6.fc35", + "Arch": "x86_64", + "SrcName": "libxml2", + "SrcVersion": "2.9.12", + "SrcRelease": "6.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libyaml", + "Version": "0.2.5", + "Release": "6.fc35", + "Arch": "x86_64", + "SrcName": "libyaml", + "SrcVersion": "0.2.5", + "SrcRelease": "6.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "libzstd", + "Version": "1.5.2", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "zstd", + "SrcVersion": "1.5.2", + "SrcRelease": "1.fc35", + "License": "BSD and GPLv2", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "lua-libs", + "Version": "5.4.4", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "lua", + "SrcVersion": "5.4.4", + "SrcRelease": "1.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "lz4-libs", + "Version": "1.9.3", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "lz4", + "SrcVersion": "1.9.3", + "SrcRelease": "3.fc35", + "License": "GPLv2+ and BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "mpdecimal", + "Version": "2.5.1", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "mpdecimal", + "SrcVersion": "2.5.1", + "SrcRelease": "2.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "mpfr", + "Version": "4.1.0", + "Release": "8.fc35", + "Arch": "x86_64", + "SrcName": "mpfr", + "SrcVersion": "4.1.0", + "SrcRelease": "8.fc35", + "License": "LGPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "ncurses-base", + "Version": "6.2", + "Release": "8.20210508.fc35", + "Arch": "noarch", + "SrcName": "ncurses", + "SrcVersion": "6.2", + "SrcRelease": "8.20210508.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "ncurses-libs", + "Version": "6.2", + "Release": "8.20210508.fc35", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.2", + "SrcRelease": "8.20210508.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "nettle", + "Version": "3.7.3", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "nettle", + "SrcVersion": "3.7.3", + "SrcRelease": "2.fc35", + "License": "LGPLv3+ or GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "npth", + "Version": "1.6", + "Release": "7.fc35", + "Arch": "x86_64", + "SrcName": "npth", + "SrcVersion": "1.6", + "SrcRelease": "7.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "openldap", + "Version": "2.4.59", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "openldap", + "SrcVersion": "2.4.59", + "SrcRelease": "3.fc35", + "License": "OpenLDAP", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "openssl-libs", + "Version": "1.1.1l", + "Release": "2.fc35", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "openssl", + "SrcVersion": "1.1.1l", + "SrcRelease": "2.fc35", + "SrcEpoch": 1, + "License": "OpenSSL and ASL 2.0", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "p11-kit", + "Version": "0.23.22", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.22", + "SrcRelease": "4.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "p11-kit-trust", + "Version": "0.23.22", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.22", + "SrcRelease": "4.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "pam", + "Version": "1.5.2", + "Release": "7.fc35", + "Arch": "x86_64", + "SrcName": "pam", + "SrcVersion": "1.5.2", + "SrcRelease": "7.fc35", + "License": "BSD and GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "pcre", + "Version": "8.45", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "pcre", + "SrcVersion": "8.45", + "SrcRelease": "1.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "pcre2", + "Version": "10.39", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "pcre2", + "SrcVersion": "10.39", + "SrcRelease": "1.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "pcre2-syntax", + "Version": "10.39", + "Release": "1.fc35", + "Arch": "noarch", + "SrcName": "pcre2", + "SrcVersion": "10.39", + "SrcRelease": "1.fc35", + "License": "BSD", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "popt", + "Version": "1.18", + "Release": "6.fc35", + "Arch": "x86_64", + "SrcName": "popt", + "SrcVersion": "1.18", + "SrcRelease": "6.fc35", + "License": "MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "publicsuffix-list-dafsa", + "Version": "20210518", + "Release": "2.fc35", + "Arch": "noarch", + "SrcName": "publicsuffix-list", + "SrcVersion": "20210518", + "SrcRelease": "2.fc35", + "License": "MPLv2.0", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "python-pip-wheel", + "Version": "21.2.3", + "Release": "4.fc35", + "Arch": "noarch", + "SrcName": "python-pip", + "SrcVersion": "21.2.3", + "SrcRelease": "4.fc35", + "License": "MIT and Python and ASL 2.0 and BSD and ISC and LGPLv2 and MPLv2.0 and (ASL 2.0 or BSD)", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "python-setuptools-wheel", + "Version": "57.4.0", + "Release": "1.fc35", + "Arch": "noarch", + "SrcName": "python-setuptools", + "SrcVersion": "57.4.0", + "SrcRelease": "1.fc35", + "License": "MIT and (BSD or ASL 2.0)", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "python3", + "Version": "3.10.2", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "python3.10", + "SrcVersion": "3.10.2", + "SrcRelease": "1.fc35", + "License": "Python", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "python3-dnf", + "Version": "4.9.0", + "Release": "1.fc35", + "Arch": "noarch", + "SrcName": "dnf", + "SrcVersion": "4.9.0", + "SrcRelease": "1.fc35", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "python3-gpg", + "Version": "1.15.1", + "Release": "6.fc35", + "Arch": "x86_64", + "SrcName": "gpgme", + "SrcVersion": "1.15.1", + "SrcRelease": "6.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "python3-hawkey", + "Version": "0.64.0", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libdnf", + "SrcVersion": "0.64.0", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "python3-libcomps", + "Version": "0.1.18", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libcomps", + "SrcVersion": "0.1.18", + "SrcRelease": "1.fc35", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "python3-libdnf", + "Version": "0.64.0", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "libdnf", + "SrcVersion": "0.64.0", + "SrcRelease": "1.fc35", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "python3-libs", + "Version": "3.10.2", + "Release": "1.fc35", + "Arch": "x86_64", + "SrcName": "python3.10", + "SrcVersion": "3.10.2", + "SrcRelease": "1.fc35", + "License": "Python", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "python3-rpm", + "Version": "4.17.0", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.17.0", + "SrcRelease": "4.fc35", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "readline", + "Version": "8.1", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "readline", + "SrcVersion": "8.1", + "SrcRelease": "3.fc35", + "License": "GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "rootfiles", + "Version": "8.1", + "Release": "30.fc35", + "Arch": "noarch", + "SrcName": "rootfiles", + "SrcVersion": "8.1", + "SrcRelease": "30.fc35", + "License": "Public Domain", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "rpm", + "Version": "4.17.0", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.17.0", + "SrcRelease": "4.fc35", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "rpm-build-libs", + "Version": "4.17.0", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.17.0", + "SrcRelease": "4.fc35", + "License": "GPLv2+ and LGPLv2+ with exceptions", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "rpm-libs", + "Version": "4.17.0", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.17.0", + "SrcRelease": "4.fc35", + "License": "GPLv2+ and LGPLv2+ with exceptions", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "rpm-sign-libs", + "Version": "4.17.0", + "Release": "4.fc35", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.17.0", + "SrcRelease": "4.fc35", + "License": "GPLv2+ and LGPLv2+ with exceptions", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "sed", + "Version": "4.8", + "Release": "8.fc35", + "Arch": "x86_64", + "SrcName": "sed", + "SrcVersion": "4.8", + "SrcRelease": "8.fc35", + "License": "GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "setup", + "Version": "2.13.9.1", + "Release": "2.fc35", + "Arch": "noarch", + "SrcName": "setup", + "SrcVersion": "2.13.9.1", + "SrcRelease": "2.fc35", + "License": "Public Domain", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "shadow-utils", + "Version": "4.9", + "Release": "9.fc35", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "shadow-utils", + "SrcVersion": "4.9", + "SrcRelease": "9.fc35", + "SrcEpoch": 2, + "License": "BSD and GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "sqlite-libs", + "Version": "3.36.0", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "sqlite", + "SrcVersion": "3.36.0", + "SrcRelease": "3.fc35", + "License": "Public Domain", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "sudo", + "Version": "1.9.7p2", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "sudo", + "SrcVersion": "1.9.7p2", + "SrcRelease": "2.fc35", + "License": "ISC", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "tar", + "Version": "1.34", + "Release": "2.fc35", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "tar", + "SrcVersion": "1.34", + "SrcRelease": "2.fc35", + "SrcEpoch": 2, + "License": "GPLv3+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "tpm2-tss", + "Version": "3.1.0", + "Release": "3.fc35", + "Arch": "x86_64", + "SrcName": "tpm2-tss", + "SrcVersion": "3.1.0", + "SrcRelease": "3.fc35", + "License": "BSD and TCGL", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "tzdata", + "Version": "2021e", + "Release": "1.fc35", + "Arch": "noarch", + "SrcName": "tzdata", + "SrcVersion": "2021e", + "SrcRelease": "1.fc35", + "License": "Public Domain", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "vim-data", + "Version": "8.2.4386", + "Release": "1.fc35", + "Epoch": 2, + "Arch": "noarch", + "SrcName": "vim", + "SrcVersion": "8.2.4386", + "SrcRelease": "1.fc35", + "SrcEpoch": 2, + "License": "Vim and MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "vim-minimal", + "Version": "8.2.4386", + "Release": "1.fc35", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "vim", + "SrcVersion": "8.2.4386", + "SrcRelease": "1.fc35", + "SrcEpoch": 2, + "License": "Vim and MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "xz-libs", + "Version": "5.2.5", + "Release": "7.fc35", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.2.5", + "SrcRelease": "7.fc35", + "License": "Public Domain", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "yum", + "Version": "4.9.0", + "Release": "1.fc35", + "Arch": "noarch", + "SrcName": "dnf", + "SrcVersion": "4.9.0", + "SrcRelease": "1.fc35", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "zchunk-libs", + "Version": "1.1.15", + "Release": "2.fc35", + "Arch": "x86_64", + "SrcName": "zchunk", + "SrcVersion": "1.1.15", + "SrcRelease": "2.fc35", + "License": "BSD and MIT", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + }, + { + "Name": "zlib", + "Version": "1.2.11", + "Release": "30.fc35", + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.2.11", + "SrcRelease": "30.fc35", + "License": "zlib and Boost", + "Layer": { + "Digest": "sha256:054e3e802ba7bde57dcc19df7b12ac4fecb92bc0c3e7b591210bcea96f993b5d", + "DiffID": "sha256:5b86cbe1caa031a8d90f13401b67620c51b1f2c6d8e9ed17a074cd8bbe50d837" + } + } +] \ No newline at end of file diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/gcr.io-distroless-base-latest.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/gcr.io-distroless-base-latest.json.golden new file mode 100644 index 000000000000..55b5e30880c2 --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/gcr.io-distroless-base-latest.json.golden @@ -0,0 +1,74 @@ +[ + { + "Name": "tzdata", + "Version": "2019a-0+deb9u1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "tzdata", + "SrcVersion": "2019a-0+deb9u1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02" + }, + { + "Name": "base-files", + "Version": "9.9+deb9u9", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "base-files", + "SrcVersion": "9.9+deb9u9", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02" + }, + { + "Name": "netbase", + "Version": "5.4", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "netbase", + "SrcVersion": "5.4", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02" + }, + { + "Name": "libc6", + "Version": "2.24-11+deb9u4", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "glibc", + "SrcVersion": "2.24-11+deb9u4", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + { + "Name": "libssl1.1", + "Version": "1.1.0k-1~deb9u1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "openssl", + "SrcVersion": "1.1.0k-1~deb9u1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + { + "Name": "openssl", + "Version": "1.1.0k-1~deb9u1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "openssl", + "SrcVersion": "1.1.0k-1~deb9u1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + } +] diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/opensuse-leap-151.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/opensuse-leap-151.json.golden new file mode 100644 index 000000000000..f86b539200c0 --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/opensuse-leap-151.json.golden @@ -0,0 +1,1579 @@ +[ + { + "Name": "aaa_base", + "Version": "84.87+git20180409.04c9dae", + "Release": "lp151.5.12.1", + "Arch": "x86_64", + "SrcName": "aaa_base", + "SrcVersion": "84.87+git20180409.04c9dae", + "SrcRelease": "lp151.5.12.1", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "bash", + "Version": "4.4", + "Release": "lp151.10.3.1", + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "4.4", + "SrcRelease": "lp151.10.3.1", + "License": "GPL-3.0-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "boost-license1_66_0", + "Version": "1.66.0", + "Release": "lp151.4.5", + "Arch": "noarch", + "SrcName": "boost-base", + "SrcVersion": "1.66.0", + "SrcRelease": "lp151.4.5", + "License": "BSL-1.0", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "ca-certificates", + "Version": "2+git20170807.10b2785", + "Release": "lp151.7.1", + "Arch": "noarch", + "SrcName": "ca-certificates", + "SrcVersion": "2+git20170807.10b2785", + "SrcRelease": "lp151.7.1", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "ca-certificates-mozilla", + "Version": "2.34", + "Release": "lp151.2.3.1", + "Arch": "noarch", + "SrcName": "ca-certificates-mozilla", + "SrcVersion": "2.34", + "SrcRelease": "lp151.2.3.1", + "License": "MPL-2.0", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "coreutils", + "Version": "8.29", + "Release": "lp151.3.3", + "Arch": "x86_64", + "SrcName": "coreutils", + "SrcVersion": "8.29", + "SrcRelease": "lp151.3.3", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "cpio", + "Version": "2.12", + "Release": "lp151.2.68", + "Arch": "x86_64", + "SrcName": "cpio", + "SrcVersion": "2.12", + "SrcRelease": "lp151.2.68", + "License": "GPL-3.0", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "cracklib", + "Version": "2.9.6", + "Release": "lp151.3.69", + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.9.6", + "SrcRelease": "lp151.3.69", + "License": "LGPL-2.1", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "cracklib-dict-small", + "Version": "2.9.6", + "Release": "lp151.3.69", + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.9.6", + "SrcRelease": "lp151.3.69", + "License": "LGPL-2.1", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "diffutils", + "Version": "3.6", + "Release": "lp151.3.3", + "Arch": "x86_64", + "SrcName": "diffutils", + "SrcVersion": "3.6", + "SrcRelease": "lp151.3.3", + "License": "GFDL-1.2 and GPL-3.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "file-magic", + "Version": "5.32", + "Release": "lp151.7.22", + "Arch": "noarch", + "SrcName": "file", + "SrcVersion": "5.32", + "SrcRelease": "lp151.7.22", + "License": "BSD-2-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "filesystem", + "Version": "15.0", + "Release": "lp151.9.2", + "Arch": "x86_64", + "SrcName": "filesystem", + "SrcVersion": "15.0", + "SrcRelease": "lp151.9.2", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "fillup", + "Version": "1.42", + "Release": "lp151.3.2", + "Arch": "x86_64", + "SrcName": "fillup", + "SrcVersion": "1.42", + "SrcRelease": "lp151.3.2", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "findutils", + "Version": "4.6.0", + "Release": "lp151.3.71", + "Arch": "x86_64", + "SrcName": "findutils", + "SrcVersion": "4.6.0", + "SrcRelease": "lp151.3.71", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "gawk", + "Version": "4.2.1", + "Release": "lp151.2.70", + "Arch": "x86_64", + "SrcName": "gawk", + "SrcVersion": "4.2.1", + "SrcRelease": "lp151.2.70", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "glibc", + "Version": "2.26", + "Release": "lp151.18.7", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.26", + "SrcRelease": "lp151.18.7", + "License": "LGPL-2.1+ AND SUSE-LGPL-2.1+-with-GCC-exception AND GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "gpg-pubkey", + "Version": "307e3d54", + "Release": "5aaa90a5", + "Arch": "None", + "License": "pubkey", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "gpg-pubkey", + "Version": "39db7c82", + "Release": "5847eb1f", + "Arch": "None", + "License": "pubkey", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "gpg-pubkey", + "Version": "3dbdc284", + "Release": "53674dd4", + "Arch": "None", + "License": "pubkey", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "gpg2", + "Version": "2.2.5", + "Release": "lp151.6.3.1", + "Arch": "x86_64", + "SrcName": "gpg2", + "SrcVersion": "2.2.5", + "SrcRelease": "lp151.6.3.1", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "grep", + "Version": "3.1", + "Release": "lp151.3.29", + "Arch": "x86_64", + "SrcName": "grep", + "SrcVersion": "3.1", + "SrcRelease": "lp151.3.29", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "info", + "Version": "6.5", + "Release": "lp151.5.69", + "Arch": "x86_64", + "SrcName": "texinfo", + "SrcVersion": "6.5", + "SrcRelease": "lp151.5.69", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "krb5", + "Version": "1.16.3", + "Release": "lp151.2.6.1", + "Arch": "x86_64", + "SrcName": "krb5", + "SrcVersion": "1.16.3", + "SrcRelease": "lp151.2.6.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "kubic-locale-archive", + "Version": "2.26", + "Release": "lp151.4.1", + "Arch": "noarch", + "SrcName": "kubic-locale-archive", + "SrcVersion": "2.26", + "SrcRelease": "lp151.4.1", + "License": "GPL-2.0+ AND MIT AND LGPL-2.1+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libacl1", + "Version": "2.2.52", + "Release": "lp151.4.29", + "Arch": "x86_64", + "SrcName": "acl", + "SrcVersion": "2.2.52", + "SrcRelease": "lp151.4.29", + "License": "GPL-2.0+ and LGPL-2.1+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libassuan0", + "Version": "2.5.1", + "Release": "lp151.3.3", + "Arch": "x86_64", + "SrcName": "libassuan", + "SrcVersion": "2.5.1", + "SrcRelease": "lp151.3.3", + "License": "GPL-3.0+ and LGPL-2.1+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libattr1", + "Version": "2.4.47", + "Release": "lp151.3.70", + "Arch": "x86_64", + "SrcName": "attr", + "SrcVersion": "2.4.47", + "SrcRelease": "lp151.3.70", + "License": "GPL-2.0-or-later AND LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libaudit1", + "Version": "2.8.1", + "Release": "lp151.4.66", + "Arch": "x86_64", + "SrcName": "audit", + "SrcVersion": "2.8.1", + "SrcRelease": "lp151.4.66", + "License": "LGPL-2.1+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libaugeas0", + "Version": "1.10.1", + "Release": "lp151.2.3", + "Arch": "x86_64", + "SrcName": "augeas", + "SrcVersion": "1.10.1", + "SrcRelease": "lp151.2.3", + "License": "GPL-3.0-or-later AND LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libblkid1", + "Version": "2.33.1", + "Release": "lp151.3.3.2", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "lp151.3.3.2", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libboost_system1_66_0", + "Version": "1.66.0", + "Release": "lp151.4.5", + "Arch": "x86_64", + "SrcName": "boost-base", + "SrcVersion": "1.66.0", + "SrcRelease": "lp151.4.5", + "License": "BSL-1.0", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libboost_thread1_66_0", + "Version": "1.66.0", + "Release": "lp151.4.5", + "Arch": "x86_64", + "SrcName": "boost-base", + "SrcVersion": "1.66.0", + "SrcRelease": "lp151.4.5", + "License": "BSL-1.0", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libbz2-1", + "Version": "1.0.6", + "Release": "lp151.5.9.1", + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.6", + "SrcRelease": "lp151.5.9.1", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libcap-ng0", + "Version": "0.7.9", + "Release": "lp151.3.39", + "Arch": "x86_64", + "SrcName": "libcap-ng", + "SrcVersion": "0.7.9", + "SrcRelease": "lp151.3.39", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libcap2", + "Version": "2.25", + "Release": "lp151.3.70", + "Arch": "x86_64", + "SrcName": "libcap", + "SrcVersion": "2.25", + "SrcRelease": "lp151.3.70", + "License": "BSD-3-Clause and GPL-2.0", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libcom_err2", + "Version": "1.43.8", + "Release": "lp151.5.6.1", + "Arch": "x86_64", + "SrcName": "e2fsprogs", + "SrcVersion": "1.43.8", + "SrcRelease": "lp151.5.6.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libcrack2", + "Version": "2.9.6", + "Release": "lp151.3.69", + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.9.6", + "SrcRelease": "lp151.3.69", + "License": "LGPL-2.1", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libcurl4", + "Version": "7.60.0", + "Release": "lp151.5.6.1", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.60.0", + "SrcRelease": "lp151.5.6.1", + "License": "curl", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libdw1", + "Version": "0.168", + "Release": "lp151.4.3.1", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.168", + "SrcRelease": "lp151.4.3.1", + "License": "SUSE-GPL-2.0-with-OSI-exception", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libebl-plugins", + "Version": "0.168", + "Release": "lp151.4.3.1", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.168", + "SrcRelease": "lp151.4.3.1", + "License": "SUSE-GPL-2.0-with-OSI-exception", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libelf1", + "Version": "0.168", + "Release": "lp151.4.3.1", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.168", + "SrcRelease": "lp151.4.3.1", + "License": "SUSE-GPL-2.0-with-OSI-exception", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libfdisk1", + "Version": "2.33.1", + "Release": "lp151.3.3.2", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "lp151.3.3.2", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libffi7", + "Version": "3.2.1.git259", + "Release": "lp151.5.2", + "Arch": "x86_64", + "SrcName": "libffi", + "SrcVersion": "3.2.1.git259", + "SrcRelease": "lp151.5.2", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libgcc_s1", + "Version": "8.2.1+r264010", + "Release": "lp151.1.33", + "Arch": "x86_64", + "SrcName": "gcc8", + "SrcVersion": "8.2.1+r264010", + "SrcRelease": "lp151.1.33", + "License": "GPL-3.0-with-GCC-exception", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libgcrypt20", + "Version": "1.8.2", + "Release": "lp151.9.4.1", + "Arch": "x86_64", + "SrcName": "libgcrypt", + "SrcVersion": "1.8.2", + "SrcRelease": "lp151.9.4.1", + "License": "GPL-2.0+ AND LGPL-2.1+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libgmp10", + "Version": "6.1.2", + "Release": "lp151.3.70", + "Arch": "x86_64", + "SrcName": "gmp", + "SrcVersion": "6.1.2", + "SrcRelease": "lp151.3.70", + "License": "GPL-3.0+ and LGPL-3.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libgnutls30", + "Version": "3.6.7", + "Release": "lp151.2.3.1", + "Arch": "x86_64", + "SrcName": "gnutls", + "SrcVersion": "3.6.7", + "SrcRelease": "lp151.2.3.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libgpg-error0", + "Version": "1.29", + "Release": "lp151.2.4", + "Arch": "x86_64", + "SrcName": "libgpg-error", + "SrcVersion": "1.29", + "SrcRelease": "lp151.2.4", + "License": "GPL-2.0-or-later AND LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libgpgme11", + "Version": "1.10.0", + "Release": "lp151.4.1", + "Arch": "x86_64", + "SrcName": "gpgme", + "SrcVersion": "1.10.0", + "SrcRelease": "lp151.4.1", + "License": "LGPL-2.1-or-later AND GPL-3.0-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libhogweed4", + "Version": "3.4.1", + "Release": "lp151.1.1", + "Arch": "x86_64", + "SrcName": "libnettle", + "SrcVersion": "3.4.1", + "SrcRelease": "lp151.1.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libidn2-0", + "Version": "2.0.4", + "Release": "lp151.2.3", + "Arch": "x86_64", + "SrcName": "libidn2", + "SrcVersion": "2.0.4", + "SrcRelease": "lp151.2.3", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libkeyutils1", + "Version": "1.5.10", + "Release": "lp151.4.70", + "Arch": "x86_64", + "SrcName": "keyutils", + "SrcVersion": "1.5.10", + "SrcRelease": "lp151.4.70", + "License": "GPL-2.0+ and LGPL-2.1+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libksba8", + "Version": "1.3.5", + "Release": "lp151.3.3", + "Arch": "x86_64", + "SrcName": "libksba", + "SrcVersion": "1.3.5", + "SrcRelease": "lp151.3.3", + "License": "(LGPL-3.0+ or GPL-2.0+) and GPL-3.0+ and MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libldap-2_4-2", + "Version": "2.4.46", + "Release": "lp151.10.3.1", + "Arch": "x86_64", + "SrcName": "openldap2", + "SrcVersion": "2.4.46", + "SrcRelease": "lp151.10.3.1", + "License": "OLDAP-2.8", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "liblua5_3-5", + "Version": "5.3.4", + "Release": "lp151.3.25", + "Arch": "x86_64", + "SrcName": "lua53", + "SrcVersion": "5.3.4", + "SrcRelease": "lp151.3.25", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "liblz4-1", + "Version": "1.8.0", + "Release": "lp151.3.3.1", + "Arch": "x86_64", + "SrcName": "lz4", + "SrcVersion": "1.8.0", + "SrcRelease": "lp151.3.3.1", + "License": "BSD-2-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "liblzma5", + "Version": "5.2.3", + "Release": "lp151.4.3.1", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.2.3", + "SrcRelease": "lp151.4.3.1", + "License": "SUSE-Public-Domain", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libmagic1", + "Version": "5.32", + "Release": "lp151.7.22", + "Arch": "x86_64", + "SrcName": "file", + "SrcVersion": "5.32", + "SrcRelease": "lp151.7.22", + "License": "BSD-2-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libmodman1", + "Version": "2.0.1", + "Release": "lp151.2.3", + "Arch": "x86_64", + "SrcName": "libmodman", + "SrcVersion": "2.0.1", + "SrcRelease": "lp151.2.3", + "License": "LGPL-2.1+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libmount1", + "Version": "2.33.1", + "Release": "lp151.3.3.2", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "lp151.3.3.2", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libncurses6", + "Version": "6.1", + "Release": "lp151.5.41", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.1", + "SrcRelease": "lp151.5.41", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libnettle6", + "Version": "3.4.1", + "Release": "lp151.1.1", + "Arch": "x86_64", + "SrcName": "libnettle", + "SrcVersion": "3.4.1", + "SrcRelease": "lp151.1.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libnghttp2-14", + "Version": "1.39.2", + "Release": "lp151.3.3.1", + "Arch": "x86_64", + "SrcName": "nghttp2", + "SrcVersion": "1.39.2", + "SrcRelease": "lp151.3.3.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libnpth0", + "Version": "1.5", + "Release": "lp151.3.2", + "Arch": "x86_64", + "SrcName": "npth", + "SrcVersion": "1.5", + "SrcRelease": "lp151.3.2", + "License": "LGPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libnsl2", + "Version": "1.2.0", + "Release": "lp151.3.67", + "Arch": "x86_64", + "SrcName": "libnsl", + "SrcVersion": "1.2.0", + "SrcRelease": "lp151.3.67", + "License": "LGPL-2.1-only", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libopenssl1_1", + "Version": "1.1.0i", + "Release": "lp151.8.3.1", + "Arch": "x86_64", + "SrcName": "openssl-1_1", + "SrcVersion": "1.1.0i", + "SrcRelease": "lp151.8.3.1", + "License": "OpenSSL", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libp11-kit0", + "Version": "0.23.2", + "Release": "lp151.3.3", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.2", + "SrcRelease": "lp151.3.3", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libpcre1", + "Version": "8.41", + "Release": "lp151.5.67", + "Arch": "x86_64", + "SrcName": "pcre", + "SrcVersion": "8.41", + "SrcRelease": "lp151.5.67", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libpopt0", + "Version": "1.16", + "Release": "lp151.3.67", + "Arch": "x86_64", + "SrcName": "popt", + "SrcVersion": "1.16", + "SrcRelease": "lp151.3.67", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libprocps7", + "Version": "3.3.15", + "Release": "lp151.6.3.1", + "Arch": "x86_64", + "SrcName": "procps", + "SrcVersion": "3.3.15", + "SrcRelease": "lp151.6.3.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libproxy1", + "Version": "0.4.15", + "Release": "lp151.3.3", + "Arch": "x86_64", + "SrcName": "libproxy", + "SrcVersion": "0.4.15", + "SrcRelease": "lp151.3.3", + "License": "GPL-2.0+ AND LGPL-2.1+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libpsl5", + "Version": "0.20.1", + "Release": "lp151.2.43", + "Arch": "x86_64", + "SrcName": "libpsl", + "SrcVersion": "0.20.1", + "SrcRelease": "lp151.2.43", + "License": "MIT AND MPL-2.0", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libreadline7", + "Version": "7.0", + "Release": "lp151.10.3.1", + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "4.4", + "SrcRelease": "lp151.10.3.1", + "License": "GPL-3.0-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libsasl2-3", + "Version": "2.1.26", + "Release": "lp151.5.1", + "Arch": "x86_64", + "SrcName": "cyrus-sasl", + "SrcVersion": "2.1.26", + "SrcRelease": "lp151.5.1", + "License": "BSD-4-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libselinux1", + "Version": "2.8", + "Release": "lp151.1.30", + "Arch": "x86_64", + "SrcName": "libselinux", + "SrcVersion": "2.8", + "SrcRelease": "lp151.1.30", + "License": "GPL-2.0-only AND SUSE-Public-Domain", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libsemanage1", + "Version": "2.8", + "Release": "lp151.1.32", + "Arch": "x86_64", + "SrcName": "libsemanage", + "SrcVersion": "2.8", + "SrcRelease": "lp151.1.32", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libsepol1", + "Version": "2.8", + "Release": "lp151.1.36", + "Arch": "x86_64", + "SrcName": "libsepol", + "SrcVersion": "2.8", + "SrcRelease": "lp151.1.36", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libsmartcols1", + "Version": "2.33.1", + "Release": "lp151.3.3.2", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "lp151.3.3.2", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libsolv-tools", + "Version": "0.7.6", + "Release": "lp151.2.3.2", + "Arch": "x86_64", + "SrcName": "libsolv", + "SrcVersion": "0.7.6", + "SrcRelease": "lp151.2.3.2", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libsqlite3-0", + "Version": "3.28.0", + "Release": "lp151.2.3.1", + "Arch": "x86_64", + "SrcName": "sqlite3", + "SrcVersion": "3.28.0", + "SrcRelease": "lp151.2.3.1", + "License": "SUSE-Public-Domain", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libssh4", + "Version": "0.8.7", + "Release": "lp151.2.3.1", + "Arch": "x86_64", + "SrcName": "libssh", + "SrcVersion": "0.8.7", + "SrcRelease": "lp151.2.3.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libstdc++6", + "Version": "8.2.1+r264010", + "Release": "lp151.1.33", + "Arch": "x86_64", + "SrcName": "gcc8", + "SrcVersion": "8.2.1+r264010", + "SrcRelease": "lp151.1.33", + "License": "GPL-3.0-with-GCC-exception", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libsystemd0", + "Version": "234", + "Release": "lp151.26.4.1", + "Arch": "x86_64", + "SrcName": "systemd", + "SrcVersion": "234", + "SrcRelease": "lp151.26.4.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libtasn1", + "Version": "4.13", + "Release": "lp151.4.3.1", + "Arch": "x86_64", + "SrcName": "libtasn1", + "SrcVersion": "4.13", + "SrcRelease": "lp151.4.3.1", + "License": "LGPL-2.1-or-later AND GPL-3.0-only", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libtasn1-6", + "Version": "4.13", + "Release": "lp151.4.3.1", + "Arch": "x86_64", + "SrcName": "libtasn1", + "SrcVersion": "4.13", + "SrcRelease": "lp151.4.3.1", + "License": "LGPL-2.1-or-later AND GPL-3.0-only", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libtirpc-netconfig", + "Version": "1.0.2", + "Release": "lp151.4.8", + "Arch": "x86_64", + "SrcName": "libtirpc", + "SrcVersion": "1.0.2", + "SrcRelease": "lp151.4.8", + "License": "BSD-3-Clause AND GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libtirpc3", + "Version": "1.0.2", + "Release": "lp151.4.8", + "Arch": "x86_64", + "SrcName": "libtirpc", + "SrcVersion": "1.0.2", + "SrcRelease": "lp151.4.8", + "License": "BSD-3-Clause AND GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libudev1", + "Version": "234", + "Release": "lp151.26.4.1", + "Arch": "x86_64", + "SrcName": "systemd", + "SrcVersion": "234", + "SrcRelease": "lp151.26.4.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libunistring2", + "Version": "0.9.9", + "Release": "lp151.2.3", + "Arch": "x86_64", + "SrcName": "libunistring", + "SrcVersion": "0.9.9", + "SrcRelease": "lp151.2.3", + "License": "LGPL-3.0-or-later OR GPL-2.0-only", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libusb-1_0-0", + "Version": "1.0.21", + "Release": "lp151.2.3", + "Arch": "x86_64", + "SrcName": "libusb-1_0", + "SrcVersion": "1.0.21", + "SrcRelease": "lp151.2.3", + "License": "LGPL-2.1+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libutempter0", + "Version": "1.1.6", + "Release": "lp151.4.70", + "Arch": "x86_64", + "SrcName": "utempter", + "SrcVersion": "1.1.6", + "SrcRelease": "lp151.4.70", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libuuid1", + "Version": "2.33.1", + "Release": "lp151.3.3.2", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "lp151.3.3.2", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libverto1", + "Version": "0.2.6", + "Release": "lp151.4.70", + "Arch": "x86_64", + "SrcName": "libverto", + "SrcVersion": "0.2.6", + "SrcRelease": "lp151.4.70", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libxml2-2", + "Version": "2.9.7", + "Release": "lp151.5.3.1", + "Arch": "x86_64", + "SrcName": "libxml2", + "SrcVersion": "2.9.7", + "SrcRelease": "lp151.5.3.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libz1", + "Version": "1.2.11", + "Release": "lp151.5.3.1", + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.2.11", + "SrcRelease": "lp151.5.3.1", + "License": "Zlib", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libzio1", + "Version": "1.06", + "Release": "lp151.3.71", + "Arch": "x86_64", + "SrcName": "libzio", + "SrcVersion": "1.06", + "SrcRelease": "lp151.3.71", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libzstd1", + "Version": "1.4.2", + "Release": "lp151.3.3.1", + "Arch": "x86_64", + "SrcName": "zstd", + "SrcVersion": "1.4.2", + "SrcRelease": "lp151.3.3.1", + "License": "BSD-3-Clause AND GPL-2.0-only", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "libzypp", + "Version": "17.15.0", + "Release": "lp151.2.3.2", + "Arch": "x86_64", + "SrcName": "libzypp", + "SrcVersion": "17.15.0", + "SrcRelease": "lp151.2.3.2", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "ncurses-utils", + "Version": "6.1", + "Release": "lp151.5.41", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.1", + "SrcRelease": "lp151.5.41", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "netcfg", + "Version": "11.6", + "Release": "lp151.2.2", + "Arch": "noarch", + "SrcName": "netcfg", + "SrcVersion": "11.6", + "SrcRelease": "lp151.2.2", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "openSUSE-build-key", + "Version": "1.0", + "Release": "lp151.3.1", + "Arch": "noarch", + "SrcName": "openSUSE-build-key", + "SrcVersion": "1.0", + "SrcRelease": "lp151.3.1", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "openSUSE-release", + "Version": "15.1", + "Release": "lp151.301.1", + "Arch": "x86_64", + "SrcName": "openSUSE-release", + "SrcVersion": "15.1", + "SrcRelease": "lp151.301.1", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "openSUSE-release-appliance-docker", + "Version": "15.1", + "Release": "lp151.301.1", + "Arch": "x86_64", + "SrcName": "openSUSE-release", + "SrcVersion": "15.1", + "SrcRelease": "lp151.301.1", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "openssl", + "Version": "1.1.0i", + "Release": "lp151.1.1", + "Arch": "noarch", + "SrcName": "openssl", + "SrcVersion": "1.1.0i", + "SrcRelease": "lp151.1.1", + "License": "OpenSSL", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "openssl-1_1", + "Version": "1.1.0i", + "Release": "lp151.8.3.1", + "Arch": "x86_64", + "SrcName": "openssl-1_1", + "SrcVersion": "1.1.0i", + "SrcRelease": "lp151.8.3.1", + "License": "OpenSSL", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "p11-kit", + "Version": "0.23.2", + "Release": "lp151.3.3", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.2", + "SrcRelease": "lp151.3.3", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "p11-kit-tools", + "Version": "0.23.2", + "Release": "lp151.3.3", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.2", + "SrcRelease": "lp151.3.3", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "pam", + "Version": "1.3.0", + "Release": "lp151.7.38", + "Arch": "x86_64", + "SrcName": "pam", + "SrcVersion": "1.3.0", + "SrcRelease": "lp151.7.38", + "License": "GPL-2.0+ or BSD-3-Clause", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "perl-base", + "Version": "5.26.1", + "Release": "lp151.8.37", + "Arch": "x86_64", + "SrcName": "perl", + "SrcVersion": "5.26.1", + "SrcRelease": "lp151.8.37", + "License": "Artistic-1.0 or GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "permissions", + "Version": "20181116", + "Release": "lp151.4.6.1", + "Arch": "x86_64", + "SrcName": "permissions", + "SrcVersion": "20181116", + "SrcRelease": "lp151.4.6.1", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "pinentry", + "Version": "1.1.0", + "Release": "lp151.4.3.1", + "Arch": "x86_64", + "SrcName": "pinentry", + "SrcVersion": "1.1.0", + "SrcRelease": "lp151.4.3.1", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "procps", + "Version": "3.3.15", + "Release": "lp151.6.3.1", + "Arch": "x86_64", + "SrcName": "procps", + "SrcVersion": "3.3.15", + "SrcRelease": "lp151.6.3.1", + "License": "GPL-2.0-or-later AND LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "rpm", + "Version": "4.14.1", + "Release": "lp151.13.10", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.14.1", + "SrcRelease": "lp151.13.10", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "sed", + "Version": "4.4", + "Release": "lp151.3.4", + "Arch": "x86_64", + "SrcName": "sed", + "SrcVersion": "4.4", + "SrcRelease": "lp151.3.4", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "shadow", + "Version": "4.6", + "Release": "lp151.2.3.2", + "Arch": "x86_64", + "SrcName": "shadow", + "SrcVersion": "4.6", + "SrcRelease": "lp151.2.3.2", + "License": "BSD-3-Clause AND GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "system-group-hardware", + "Version": "20170617", + "Release": "lp151.4.70", + "Arch": "noarch", + "SrcName": "system-users", + "SrcVersion": "20170617", + "SrcRelease": "lp151.4.70", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "system-user-root", + "Version": "20190513", + "Release": "lp151.3.3.1", + "Arch": "noarch", + "SrcName": "system-user-root", + "SrcVersion": "20190513", + "SrcRelease": "lp151.3.3.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "sysuser-shadow", + "Version": "2.0", + "Release": "lp151.3.70", + "Arch": "noarch", + "SrcName": "sysuser-tools", + "SrcVersion": "2.0", + "SrcRelease": "lp151.3.70", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "terminfo-base", + "Version": "6.1", + "Release": "lp151.5.41", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.1", + "SrcRelease": "lp151.5.41", + "License": "MIT", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "update-alternatives", + "Version": "1.19.0.4", + "Release": "lp151.3.67", + "Arch": "x86_64", + "SrcName": "update-alternatives", + "SrcVersion": "1.19.0.4", + "SrcRelease": "lp151.3.67", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "util-linux", + "Version": "2.33.1", + "Release": "lp151.3.3.2", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.33.1", + "SrcRelease": "lp151.3.3.2", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + }, + { + "Name": "zypper", + "Version": "1.14.30", + "Release": "lp151.2.3.1", + "Arch": "x86_64", + "SrcName": "zypper", + "SrcVersion": "1.14.30", + "SrcRelease": "lp151.2.3.1", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + } +] \ No newline at end of file diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/oraclelinux-8-slim.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/oraclelinux-8-slim.json.golden new file mode 100644 index 000000000000..0780d41d550d --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/oraclelinux-8-slim.json.golden @@ -0,0 +1,1190 @@ +[ + { + "Name": "ncurses-base", + "Version": "6.1", + "Release": "7.20180224.el8", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "ncurses", + "SrcVersion": "6.1", + "SrcRelease": "7.20180224.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "publicsuffix-list-dafsa", + "Version": "20180723", + "Release": "1.el8", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "publicsuffix-list", + "SrcVersion": "20180723", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "pcre2", + "Version": "10.32", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "pcre2", + "SrcVersion": "10.32", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "setup", + "Version": "2.12.2", + "Release": "2.el8", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "setup", + "SrcVersion": "2.12.2", + "SrcRelease": "2.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "basesystem", + "Version": "11", + "Release": "5.el8", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "basesystem", + "SrcVersion": "11", + "SrcRelease": "5.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libselinux", + "Version": "2.8", + "Release": "6.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libselinux", + "SrcVersion": "2.8", + "SrcRelease": "6.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "bash", + "Version": "4.4.19", + "Release": "8.el8_0", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "4.4.19", + "SrcRelease": "8.el8_0", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "glibc", + "Version": "2.28", + "Release": "42.0.1.el8_0.1", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.28", + "SrcRelease": "42.0.1.el8_0.1", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "zlib", + "Version": "1.2.11", + "Release": "10.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.2.11", + "SrcRelease": "10.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "info", + "Version": "6.5", + "Release": "4.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "texinfo", + "SrcVersion": "6.5", + "SrcRelease": "4.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "bzip2-libs", + "Version": "1.0.6", + "Release": "26.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.6", + "SrcRelease": "26.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libcom_err", + "Version": "1.44.3", + "Release": "2.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "e2fsprogs", + "SrcVersion": "1.44.3", + "SrcRelease": "2.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libffi", + "Version": "3.1", + "Release": "18.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libffi", + "SrcVersion": "3.1", + "SrcRelease": "18.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libxml2", + "Version": "2.9.7", + "Release": "5.0.1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libxml2", + "SrcVersion": "2.9.7", + "SrcRelease": "5.0.1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libidn2", + "Version": "2.0.5", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libidn2", + "SrcVersion": "2.0.5", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "readline", + "Version": "7.0", + "Release": "10.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "readline", + "SrcVersion": "7.0", + "SrcRelease": "10.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libassuan", + "Version": "2.5.1", + "Release": "3.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libassuan", + "SrcVersion": "2.5.1", + "SrcRelease": "3.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "elfutils-libelf", + "Version": "0.174", + "Release": "6.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.174", + "SrcRelease": "6.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "lua-libs", + "Version": "5.3.4", + "Release": "10.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "lua", + "SrcVersion": "5.3.4", + "SrcRelease": "10.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libacl", + "Version": "2.2.53", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "acl", + "SrcVersion": "2.2.53", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "sed", + "Version": "4.5", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "sed", + "SrcVersion": "4.5", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "grep", + "Version": "3.1", + "Release": "6.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "grep", + "SrcVersion": "3.1", + "SrcRelease": "6.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libtasn1", + "Version": "4.13", + "Release": "3.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libtasn1", + "SrcVersion": "4.13", + "SrcRelease": "3.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "lz4-libs", + "Version": "1.8.1.2", + "Release": "4.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "lz4", + "SrcVersion": "1.8.1.2", + "SrcRelease": "4.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libmount", + "Version": "2.32.1", + "Release": "8.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.32.1", + "SrcRelease": "8.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "gobject-introspection", + "Version": "1.56.1", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gobject-introspection", + "SrcVersion": "1.56.1", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "systemd-libs", + "Version": "239", + "Release": "13.0.1.el8_0.5", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "systemd", + "SrcVersion": "239", + "SrcRelease": "13.0.1.el8_0.5", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "chkconfig", + "Version": "1.11", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "chkconfig", + "SrcVersion": "1.11", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "ca-certificates", + "Version": "2018.2.24", + "Release": "6.el8", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "ca-certificates", + "SrcVersion": "2018.2.24", + "SrcRelease": "6.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libdb", + "Version": "5.3.28", + "Release": "36.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libdb", + "SrcVersion": "5.3.28", + "SrcRelease": "36.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libarchive", + "Version": "3.3.2", + "Release": "3.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libarchive", + "SrcVersion": "3.3.2", + "SrcRelease": "3.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "mpfr", + "Version": "3.1.6", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "mpfr", + "SrcVersion": "3.1.6", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "gnutls", + "Version": "3.6.5", + "Release": "2.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gnutls", + "SrcVersion": "3.6.5", + "SrcRelease": "2.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "expat", + "Version": "2.2.5", + "Release": "3.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "expat", + "SrcVersion": "2.2.5", + "SrcRelease": "3.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "json-c", + "Version": "0.13.1", + "Release": "0.2.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "json-c", + "SrcVersion": "0.13.1", + "SrcRelease": "0.2.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "brotli", + "Version": "1.0.6", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "brotli", + "SrcVersion": "1.0.6", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "npth", + "Version": "1.5", + "Release": "4.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "npth", + "SrcVersion": "1.5", + "SrcRelease": "4.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libnghttp2", + "Version": "1.33.0", + "Release": "1.el8_0.1", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "nghttp2", + "SrcVersion": "1.33.0", + "SrcRelease": "1.el8_0.1", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libyaml", + "Version": "0.1.7", + "Release": "5.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libyaml", + "SrcVersion": "0.1.7", + "SrcRelease": "5.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libsigsegv", + "Version": "2.11", + "Release": "5.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libsigsegv", + "SrcVersion": "2.11", + "SrcRelease": "5.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "krb5-libs", + "Version": "1.16.1", + "Release": "22.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "krb5", + "SrcVersion": "1.16.1", + "SrcRelease": "22.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "cyrus-sasl-lib", + "Version": "2.1.27", + "Release": "0.3rc7.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "cyrus-sasl", + "SrcVersion": "2.1.27", + "SrcRelease": "0.3rc7.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libcurl", + "Version": "7.61.1", + "Release": "8.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.61.1", + "SrcRelease": "8.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "gnupg2", + "Version": "2.2.9", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gnupg2", + "SrcVersion": "2.2.9", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "librepo", + "Version": "1.9.2", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "librepo", + "SrcVersion": "1.9.2", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "audit-libs", + "Version": "3.0", + "Release": "0.10.20180831git0047a6c.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "audit", + "SrcVersion": "3.0", + "SrcRelease": "0.10.20180831git0047a6c.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "rpm", + "Version": "4.14.2", + "Release": "11.el8_0", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.14.2", + "SrcRelease": "11.el8_0", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libdnf", + "Version": "0.22.5", + "Release": "5.0.2.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libdnf", + "SrcVersion": "0.22.5", + "SrcRelease": "5.0.2.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "oraclelinux-release-el8", + "Version": "1.0", + "Release": "4.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "oraclelinux-release-el8", + "SrcVersion": "1.0", + "SrcRelease": "4.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libgcc", + "Version": "8.2.1", + "Release": "3.5.0.1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gcc", + "SrcVersion": "8.2.1", + "SrcRelease": "3.5.0.1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "tzdata", + "Version": "2019c", + "Release": "1.el8", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "tzdata", + "SrcVersion": "2019c", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "redhat-release", + "Version": "8.0", + "Release": "0.44.0.1.el8", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "redhat-release", + "SrcVersion": "8.0", + "SrcRelease": "0.44.0.1.el8", + "SrcEpoch": 2, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "oraclelinux-release", + "Version": "8.0", + "Release": "1.0.14.el8", + "Epoch": 8, + "Arch": "x86_64", + "SrcName": "oraclelinux-release", + "SrcVersion": "8.0", + "SrcRelease": "1.0.14.el8", + "SrcEpoch": 8, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "filesystem", + "Version": "3.8", + "Release": "2.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "filesystem", + "SrcVersion": "3.8", + "SrcRelease": "2.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "ncurses-libs", + "Version": "6.1", + "Release": "7.20180224.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.1", + "SrcRelease": "7.20180224.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "glibc-minimal-langpack", + "Version": "2.28", + "Release": "42.0.1.el8_0.1", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.28", + "SrcRelease": "42.0.1.el8_0.1", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "glibc-common", + "Version": "2.28", + "Release": "42.0.1.el8_0.1", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.28", + "SrcRelease": "42.0.1.el8_0.1", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libsepol", + "Version": "2.8", + "Release": "2.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libsepol", + "SrcVersion": "2.8", + "SrcRelease": "2.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libgpg-error", + "Version": "1.31", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libgpg-error", + "SrcVersion": "1.31", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "xz-libs", + "Version": "5.2.4", + "Release": "3.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.2.4", + "SrcRelease": "3.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "gmp", + "Version": "6.1.2", + "Release": "8.el8", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "gmp", + "SrcVersion": "6.1.2", + "SrcRelease": "8.el8", + "SrcEpoch":1, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libcap", + "Version": "2.25", + "Release": "9.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libcap", + "SrcVersion": "2.25", + "SrcRelease": "9.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "p11-kit", + "Version": "0.23.14", + "Release": "5.el8_0", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.14", + "SrcRelease": "5.el8_0", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libunistring", + "Version": "0.9.9", + "Release": "3.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libunistring", + "SrcVersion": "0.9.9", + "SrcRelease": "3.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "popt", + "Version": "1.16", + "Release": "14.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "popt", + "SrcVersion": "1.16", + "SrcRelease": "14.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libgcrypt", + "Version": "1.8.3", + "Release": "2.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libgcrypt", + "SrcVersion": "1.8.3", + "SrcRelease": "2.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "sqlite-libs", + "Version": "3.26.0", + "Release": "3.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "sqlite", + "SrcVersion": "3.26.0", + "SrcRelease": "3.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libuuid", + "Version": "2.32.1", + "Release": "8.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.32.1", + "SrcRelease": "8.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libattr", + "Version": "2.4.48", + "Release": "3.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "attr", + "SrcVersion": "2.4.48", + "SrcRelease": "3.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "coreutils-single", + "Version": "8.30", + "Release": "6.0.1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "coreutils", + "SrcVersion": "8.30", + "SrcRelease": "6.0.1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "pcre", + "Version": "8.42", + "Release": "4.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "pcre", + "SrcVersion": "8.42", + "SrcRelease": "4.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "crypto-policies", + "Version": "20181217", + "Release": "6.git9a35207.el8", + "Epoch": 0, + "Arch": "noarch", + "SrcName": "crypto-policies", + "SrcVersion": "20181217", + "SrcRelease": "6.git9a35207.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libsmartcols", + "Version": "2.32.1", + "Release": "8.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.32.1", + "SrcRelease": "8.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libblkid", + "Version": "2.32.1", + "Release": "8.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.32.1", + "SrcRelease": "8.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "glib2", + "Version": "2.56.4", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "glib2", + "SrcVersion": "2.56.4", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libpeas", + "Version": "1.22.0", + "Release": "6.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libpeas", + "SrcVersion": "1.22.0", + "SrcRelease": "6.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libusbx", + "Version": "1.0.22", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libusbx", + "SrcVersion": "1.0.22", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "p11-kit-trust", + "Version": "0.23.14", + "Release": "5.el8_0", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.14", + "SrcRelease": "5.el8_0", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "openssl-libs", + "Version": "1.1.1", + "Release": "8.0.1.el8", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "openssl", + "SrcVersion": "1.1.1", + "SrcRelease": "8.0.1.el8", + "SrcEpoch":1, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libdb-utils", + "Version": "5.3.28", + "Release": "36.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libdb", + "SrcVersion": "5.3.28", + "SrcRelease": "36.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libpsl", + "Version": "0.20.2", + "Release": "5.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libpsl", + "SrcVersion": "0.20.2", + "SrcRelease": "5.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "nettle", + "Version": "3.4.1", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "nettle", + "SrcVersion": "3.4.1", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libksba", + "Version": "1.3.5", + "Release": "7.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libksba", + "SrcVersion": "1.3.5", + "SrcRelease": "7.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libmetalink", + "Version": "0.1.3", + "Release": "7.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libmetalink", + "SrcVersion": "0.1.3", + "SrcRelease": "7.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "keyutils-libs", + "Version": "1.5.10", + "Release": "6.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "keyutils", + "SrcVersion": "1.5.10", + "SrcRelease": "6.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libstdc++", + "Version": "8.2.1", + "Release": "3.5.0.1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gcc", + "SrcVersion": "8.2.1", + "SrcRelease": "3.5.0.1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libverto", + "Version": "0.3.0", + "Release": "5.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libverto", + "SrcVersion": "0.3.0", + "SrcRelease": "5.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libxcrypt", + "Version": "4.1.1", + "Release": "4.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libxcrypt", + "SrcVersion": "4.1.1", + "SrcRelease": "4.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libmodulemd1", + "Version": "1.8.0", + "Release": "5.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libmodulemd", + "SrcVersion": "2.0.0", + "SrcRelease": "5.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "gawk", + "Version": "4.2.1", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gawk", + "SrcVersion": "4.2.1", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libssh", + "Version": "0.8.5", + "Release": "2.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libssh", + "SrcVersion": "0.8.5", + "SrcRelease": "2.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "openldap", + "Version": "2.4.46", + "Release": "9.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "openldap", + "SrcVersion": "2.4.46", + "SrcRelease": "9.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "curl", + "Version": "7.61.1", + "Release": "8.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.61.1", + "SrcRelease": "8.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "gpgme", + "Version": "1.10.0", + "Release": "6.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "gpgme", + "SrcVersion": "1.10.0", + "SrcRelease": "6.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libcap-ng", + "Version": "0.7.9", + "Release": "4.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libcap-ng", + "SrcVersion": "0.7.9", + "SrcRelease": "4.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "rpm-libs", + "Version": "4.14.2", + "Release": "11.el8_0", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.14.2", + "SrcRelease": "11.el8_0", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "libsolv", + "Version": "0.6.35", + "Release": "6.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "libsolv", + "SrcVersion": "0.6.35", + "SrcRelease": "6.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "microdnf", + "Version": "3.0.1", + "Release": "1.el8", + "Epoch": 0, + "Arch": "x86_64", + "SrcName": "microdnf", + "SrcVersion": "3.0.1", + "SrcRelease": "1.el8", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + }, + { + "Name": "gpg-pubkey", + "Version": "ad986da3", + "Release": "5cabf60d", + "Epoch": 0, + "Arch": "(none)", + "SrcName": "", + "SrcVersion": "", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:e3196b7450602f5547c52d197255dfa96a006ea9c52c19bf3ba2d5412a4b161e" + } +] diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/photon-30.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/photon-30.json.golden new file mode 100644 index 000000000000..294b3a07c419 --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/photon-30.json.golden @@ -0,0 +1,461 @@ +[ + { + "Name": "bash", + "Version": "4.4.18", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "4.4.18", + "SrcRelease": "1.ph3", + "License": "GPLv3", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "bzip2-libs", + "Version": "1.0.6", + "Release": "10.ph3", + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.6", + "SrcRelease": "10.ph3", + "License": "BSD", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "ca-certificates", + "Version": "20190521", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "ca-certificates", + "SrcVersion": "20190521", + "SrcRelease": "1.ph3", + "License": "Custom", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "ca-certificates-pki", + "Version": "20190521", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "ca-certificates", + "SrcVersion": "20190521", + "SrcRelease": "1.ph3", + "License": "Custom", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "curl", + "Version": "7.61.1", + "Release": "4.ph3", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.61.1", + "SrcRelease": "4.ph3", + "License": "MIT", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "curl-libs", + "Version": "7.61.1", + "Release": "4.ph3", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.61.1", + "SrcRelease": "4.ph3", + "License": "MIT", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "e2fsprogs-libs", + "Version": "1.44.3", + "Release": "2.ph3", + "Arch": "x86_64", + "SrcName": "e2fsprogs", + "SrcVersion": "1.44.3", + "SrcRelease": "2.ph3", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "elfutils-libelf", + "Version": "0.176", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.176", + "SrcRelease": "1.ph3", + "License": "GPLv2+ or LGPLv3+", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "expat-libs", + "Version": "2.2.6", + "Release": "2.ph3", + "Arch": "x86_64", + "SrcName": "expat", + "SrcVersion": "2.2.6", + "SrcRelease": "2.ph3", + "License": "MIT", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "filesystem", + "Version": "1.1", + "Release": "4.ph3", + "Arch": "x86_64", + "SrcName": "filesystem", + "SrcVersion": "1.1", + "SrcRelease": "4.ph3", + "License": "GPLv3", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "glibc", + "Version": "2.28", + "Release": "3.ph3", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.28", + "SrcRelease": "3.ph3", + "License": "LGPLv2+", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "gpg-pubkey", + "Version": "66fd4949", + "Release": "4803fe57", + "Arch": "None", + "License": "pubkey", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "krb5", + "Version": "1.17", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "krb5", + "SrcVersion": "1.17", + "SrcRelease": "1.ph3", + "License": "MIT", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "libcap", + "Version": "2.25", + "Release": "8.ph3", + "Arch": "x86_64", + "SrcName": "libcap", + "SrcVersion": "2.25", + "SrcRelease": "8.ph3", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "libdb", + "Version": "5.3.28", + "Release": "2.ph3", + "Arch": "x86_64", + "SrcName": "libdb", + "SrcVersion": "5.3.28", + "SrcRelease": "2.ph3", + "License": "BSD and LGPLv2 and Sleepycat", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "libgcc", + "Version": "7.3.0", + "Release": "4.ph3", + "Arch": "x86_64", + "SrcName": "gcc", + "SrcVersion": "7.3.0", + "SrcRelease": "4.ph3", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "libsolv", + "Version": "0.6.26", + "Release": "5.ph3", + "Arch": "x86_64", + "SrcName": "libsolv", + "SrcVersion": "0.6.26", + "SrcRelease": "5.ph3", + "License": "BSD", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "libssh2", + "Version": "1.9.0", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "libssh2", + "SrcVersion": "1.9.0", + "SrcRelease": "1.ph3", + "License": "BSD", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "ncurses-libs", + "Version": "6.1", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.1", + "SrcRelease": "1.ph3", + "License": "MIT", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "nspr", + "Version": "4.21", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "nspr", + "SrcVersion": "4.21", + "SrcRelease": "1.ph3", + "License": "MPLv2.0", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "nss-libs", + "Version": "3.44", + "Release": "2.ph3", + "Arch": "x86_64", + "SrcName": "nss", + "SrcVersion": "3.44", + "SrcRelease": "2.ph3", + "License": "MPLv2.0", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "openssl", + "Version": "1.0.2s", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "openssl", + "SrcVersion": "1.0.2s", + "SrcRelease": "1.ph3", + "License": "OpenSSL", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "photon-release", + "Version": "3.0", + "Release": "3.ph3", + "Arch": "noarch", + "SrcName": "photon-release", + "SrcVersion": "3.0", + "SrcRelease": "3.ph3", + "License": "Apache License", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "photon-repos", + "Version": "3.0", + "Release": "3.ph3", + "Arch": "noarch", + "SrcName": "photon-repos", + "SrcVersion": "3.0", + "SrcRelease": "3.ph3", + "License": "Apache License", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "popt", + "Version": "1.16", + "Release": "5.ph3", + "Arch": "x86_64", + "SrcName": "popt", + "SrcVersion": "1.16", + "SrcRelease": "5.ph3", + "License": "MIT", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "readline", + "Version": "7.0", + "Release": "2.ph3", + "Arch": "x86_64", + "SrcName": "readline", + "SrcVersion": "7.0", + "SrcRelease": "2.ph3", + "License": "GPLv3+", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "rpm-libs", + "Version": "4.14.2", + "Release": "4.ph3", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.14.2", + "SrcRelease": "4.ph3", + "License": "GPLv2+", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "sqlite-libs", + "Version": "3.27.2", + "Release": "3.ph3", + "Arch": "x86_64", + "SrcName": "sqlite", + "SrcVersion": "3.27.2", + "SrcRelease": "3.ph3", + "License": "Public Domain", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "tdnf", + "Version": "2.0.0", + "Release": "10.ph3", + "Arch": "x86_64", + "SrcName": "tdnf", + "SrcVersion": "2.0.0", + "SrcRelease": "10.ph3", + "License": "LGPLv2.1,GPLv2", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "tdnf-cli-libs", + "Version": "2.0.0", + "Release": "10.ph3", + "Arch": "x86_64", + "SrcName": "tdnf", + "SrcVersion": "2.0.0", + "SrcRelease": "10.ph3", + "License": "LGPLv2.1,GPLv2", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "toybox", + "Version": "0.7.7", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "toybox", + "SrcVersion": "0.7.7", + "SrcRelease": "1.ph3", + "License": "BSD", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "xz-libs", + "Version": "5.2.4", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.2.4", + "SrcRelease": "1.ph3", + "License": "GPLv2+ and GPLv3+ and LGPLv2+", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + }, + { + "Name": "zlib", + "Version": "1.2.11", + "Release": "1.ph3", + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.2.11", + "SrcRelease": "1.ph3", + "License": "zlib", + "Layer": { + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + } +] \ No newline at end of file diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/suse-15.3_ndb.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/suse-15.3_ndb.json.golden new file mode 100644 index 000000000000..0b3f4f1acd2d --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/suse-15.3_ndb.json.golden @@ -0,0 +1,1631 @@ +[ + { + "Name": "aaa_base", + "Version": "84.87+git20180409.04c9dae", + "Release": "3.45.1", + "Arch": "x86_64", + "SrcName": "aaa_base", + "SrcVersion": "84.87+git20180409.04c9dae", + "SrcRelease": "3.45.1", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "bash", + "Version": "4.4", + "Release": "19.6.1", + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "4.4", + "SrcRelease": "19.6.1", + "License": "GPL-3.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "boost-license1_66_0", + "Version": "1.66.0", + "Release": "10.1", + "Arch": "noarch", + "SrcName": "boost-base", + "SrcVersion": "1.66.0", + "SrcRelease": "10.1", + "License": "BSL-1.0", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "ca-certificates", + "Version": "2+git20210309.21162a6", + "Release": "2.1", + "Arch": "noarch", + "SrcName": "ca-certificates", + "SrcVersion": "2+git20210309.21162a6", + "SrcRelease": "2.1", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "ca-certificates-mozilla", + "Version": "2.44", + "Release": "21.1", + "Arch": "noarch", + "SrcName": "ca-certificates-mozilla", + "SrcVersion": "2.44", + "SrcRelease": "21.1", + "License": "MPL-2.0", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "container-suseconnect", + "Version": "2.3.0", + "Release": "4.15.2", + "Arch": "x86_64", + "SrcName": "container-suseconnect", + "SrcVersion": "2.3.0", + "SrcRelease": "4.15.2", + "License": "Apache-2.0", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "coreutils", + "Version": "8.32", + "Release": "3.2.1", + "Arch": "x86_64", + "SrcName": "coreutils", + "SrcVersion": "8.32", + "SrcRelease": "3.2.1", + "License": "GPL-3.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "cpio", + "Version": "2.12", + "Release": "3.9.1", + "Arch": "x86_64", + "SrcName": "cpio", + "SrcVersion": "2.12", + "SrcRelease": "3.9.1", + "License": "GPL-3.0", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "cracklib", + "Version": "2.9.7", + "Release": "11.3.1", + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.9.7", + "SrcRelease": "11.3.1", + "License": "LGPL-2.1-only", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "cracklib-dict-small", + "Version": "2.9.7", + "Release": "11.3.1", + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.9.7", + "SrcRelease": "11.3.1", + "License": "LGPL-2.1-only", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "diffutils", + "Version": "3.6", + "Release": "4.3.1", + "Arch": "x86_64", + "SrcName": "diffutils", + "SrcVersion": "3.6", + "SrcRelease": "4.3.1", + "License": "GFDL-1.2 and GPL-3.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "file-magic", + "Version": "5.32", + "Release": "7.14.1", + "Arch": "noarch", + "SrcName": "file", + "SrcVersion": "5.32", + "SrcRelease": "7.14.1", + "License": "BSD-2-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "filesystem", + "Version": "15.0", + "Release": "11.3.2", + "Arch": "x86_64", + "SrcName": "filesystem", + "SrcVersion": "15.0", + "SrcRelease": "11.3.2", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "fillup", + "Version": "1.42", + "Release": "2.18", + "Arch": "x86_64", + "SrcName": "fillup", + "SrcVersion": "1.42", + "SrcRelease": "2.18", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "findutils", + "Version": "4.8.0", + "Release": "1.20", + "Arch": "x86_64", + "SrcName": "findutils", + "SrcVersion": "4.8.0", + "SrcRelease": "1.20", + "License": "GPL-3.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "glibc", + "Version": "2.31", + "Release": "9.3.2", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.31", + "SrcRelease": "9.3.2", + "License": "LGPL-2.1-or-later AND LGPL-2.1-or-later WITH GCC-exception-2.0 AND GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "gpg-pubkey", + "Version": "307e3d54", + "Release": "5aaa90a5", + "Arch": "None", + "License": "pubkey", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "gpg-pubkey", + "Version": "39db7c82", + "Release": "5f68629b", + "Arch": "None", + "License": "pubkey", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "gpg-pubkey", + "Version": "50a3dd1c", + "Release": "50f35137", + "Arch": "None", + "License": "pubkey", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "gpg2", + "Version": "2.2.27", + "Release": "1.2", + "Arch": "x86_64", + "SrcName": "gpg2", + "SrcVersion": "2.2.27", + "SrcRelease": "1.2", + "License": "GPL-3.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "grep", + "Version": "3.1", + "Release": "4.3.12", + "Arch": "x86_64", + "SrcName": "grep", + "SrcVersion": "3.1", + "SrcRelease": "4.3.12", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "info", + "Version": "6.5", + "Release": "4.17", + "Arch": "x86_64", + "SrcName": "texinfo", + "SrcVersion": "6.5", + "SrcRelease": "4.17", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "krb5", + "Version": "1.16.3", + "Release": "3.24.1", + "Arch": "x86_64", + "SrcName": "krb5", + "SrcVersion": "1.16.3", + "SrcRelease": "3.24.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "kubic-locale-archive", + "Version": "2.31", + "Release": "10.36", + "Arch": "noarch", + "SrcName": "kubic-locale-archive", + "SrcVersion": "2.31", + "SrcRelease": "10.36", + "License": "GPL-2.0+ AND MIT AND LGPL-2.1+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libacl1", + "Version": "2.2.52", + "Release": "4.3.1", + "Arch": "x86_64", + "SrcName": "acl", + "SrcVersion": "2.2.52", + "SrcRelease": "4.3.1", + "License": "GPL-2.0+ and LGPL-2.1+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libassuan0", + "Version": "2.5.1", + "Release": "2.14", + "Arch": "x86_64", + "SrcName": "libassuan", + "SrcVersion": "2.5.1", + "SrcRelease": "2.14", + "License": "GPL-3.0+ and LGPL-2.1+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libattr1", + "Version": "2.4.47", + "Release": "2.19", + "Arch": "x86_64", + "SrcName": "attr", + "SrcVersion": "2.4.47", + "SrcRelease": "2.19", + "License": "GPL-2.0-or-later AND LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libaudit1", + "Version": "2.8.5", + "Release": "3.43", + "Arch": "x86_64", + "SrcName": "audit", + "SrcVersion": "2.8.5", + "SrcRelease": "3.43", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libaugeas0", + "Version": "1.10.1", + "Release": "1.11", + "Arch": "x86_64", + "SrcName": "augeas", + "SrcVersion": "1.10.1", + "SrcRelease": "1.11", + "License": "GPL-3.0-or-later AND LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libblkid1", + "Version": "2.36.2", + "Release": "4.5.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.36.2", + "SrcRelease": "4.5.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libboost_system1_66_0", + "Version": "1.66.0", + "Release": "10.1", + "Arch": "x86_64", + "SrcName": "boost-base", + "SrcVersion": "1.66.0", + "SrcRelease": "10.1", + "License": "BSL-1.0", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libboost_thread1_66_0", + "Version": "1.66.0", + "Release": "10.1", + "Arch": "x86_64", + "SrcName": "boost-base", + "SrcVersion": "1.66.0", + "SrcRelease": "10.1", + "License": "BSL-1.0", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libbz2-1", + "Version": "1.0.6", + "Release": "5.11.1", + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.6", + "SrcRelease": "5.11.1", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libcap-ng0", + "Version": "0.7.9", + "Release": "4.37", + "Arch": "x86_64", + "SrcName": "libcap-ng", + "SrcVersion": "0.7.9", + "SrcRelease": "4.37", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libcap2", + "Version": "2.26", + "Release": "4.6.1", + "Arch": "x86_64", + "SrcName": "libcap", + "SrcVersion": "2.26", + "SrcRelease": "4.6.1", + "License": "BSD-3-Clause or GPL-2.0", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libcom_err2", + "Version": "1.43.8", + "Release": "4.26.1", + "Arch": "x86_64", + "SrcName": "e2fsprogs", + "SrcVersion": "1.43.8", + "SrcRelease": "4.26.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libcrack2", + "Version": "2.9.7", + "Release": "11.3.1", + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.9.7", + "SrcRelease": "11.3.1", + "License": "LGPL-2.1-only", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libcrypt1", + "Version": "4.4.15", + "Release": "2.51", + "Arch": "x86_64", + "SrcName": "libxcrypt", + "SrcVersion": "4.4.15", + "SrcRelease": "2.51", + "License": "LGPL-2.1-or-later AND BSD-2-Clause AND BSD-3-Clause AND SUSE-Public-Domain", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libcurl4", + "Version": "7.66.0", + "Release": "4.27.1", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.66.0", + "SrcRelease": "4.27.1", + "License": "curl", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libdw1", + "Version": "0.168", + "Release": "4.5.3", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.168", + "SrcRelease": "4.5.3", + "License": "SUSE-GPL-2.0-with-OSI-exception", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libebl-plugins", + "Version": "0.168", + "Release": "4.5.3", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.168", + "SrcRelease": "4.5.3", + "License": "SUSE-GPL-2.0-with-OSI-exception", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libelf1", + "Version": "0.168", + "Release": "4.5.3", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.168", + "SrcRelease": "4.5.3", + "License": "SUSE-GPL-2.0-with-OSI-exception", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libfdisk1", + "Version": "2.36.2", + "Release": "4.5.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.36.2", + "SrcRelease": "4.5.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libffi7", + "Version": "3.2.1.git259", + "Release": "10.8", + "Arch": "x86_64", + "SrcName": "libffi", + "SrcVersion": "3.2.1.git259", + "SrcRelease": "10.8", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libgcc_s1", + "Version": "10.3.0+git1587", + "Release": "1.6.4", + "Arch": "x86_64", + "SrcName": "gcc10", + "SrcVersion": "10.3.0+git1587", + "SrcRelease": "1.6.4", + "License": "GPL-3.0 WITH GCC-exception-3.1", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libgcrypt20", + "Version": "1.8.2", + "Release": "8.39.1", + "Arch": "x86_64", + "SrcName": "libgcrypt", + "SrcVersion": "1.8.2", + "SrcRelease": "8.39.1", + "License": "GPL-2.0+ AND LGPL-2.1+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libgcrypt20-hmac", + "Version": "1.8.2", + "Release": "8.39.1", + "Arch": "x86_64", + "SrcName": "libgcrypt", + "SrcVersion": "1.8.2", + "SrcRelease": "8.39.1", + "License": "GPL-2.0+ AND LGPL-2.1+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libglib-2_0-0", + "Version": "2.62.6", + "Release": "3.6.1", + "Arch": "x86_64", + "SrcName": "glib2", + "SrcVersion": "2.62.6", + "SrcRelease": "3.6.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libgmp10", + "Version": "6.1.2", + "Release": "4.6.1", + "Arch": "x86_64", + "SrcName": "gmp", + "SrcVersion": "6.1.2", + "SrcRelease": "4.6.1", + "License": "LGPL-3.0-or-later OR GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libgpg-error0", + "Version": "1.29", + "Release": "1.8", + "Arch": "x86_64", + "SrcName": "libgpg-error", + "SrcVersion": "1.29", + "SrcRelease": "1.8", + "License": "GPL-2.0-or-later AND LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libgpgme11", + "Version": "1.13.1", + "Release": "4.3.1", + "Arch": "x86_64", + "SrcName": "gpgme", + "SrcVersion": "1.13.1", + "SrcRelease": "4.3.1", + "License": "LGPL-2.1-or-later AND GPL-3.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libidn2-0", + "Version": "2.2.0", + "Release": "3.6.1", + "Arch": "x86_64", + "SrcName": "libidn2", + "SrcVersion": "2.2.0", + "SrcRelease": "3.6.1", + "License": "GPL-2.0-or-later OR LGPL-3.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libkeyutils1", + "Version": "1.5.10", + "Release": "5.3.1", + "Arch": "x86_64", + "SrcName": "keyutils", + "SrcVersion": "1.5.10", + "SrcRelease": "5.3.1", + "License": "LGPL-2.1+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libksba8", + "Version": "1.3.5", + "Release": "2.14", + "Arch": "x86_64", + "SrcName": "libksba", + "SrcVersion": "1.3.5", + "SrcRelease": "2.14", + "License": "(LGPL-3.0+ or GPL-2.0+) and GPL-3.0+ and MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libldap-2_4-2", + "Version": "2.4.46", + "Release": "9.58.1", + "Arch": "x86_64", + "SrcName": "openldap2", + "SrcVersion": "2.4.46", + "SrcRelease": "9.58.1", + "License": "OLDAP-2.8", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libldap-data", + "Version": "2.4.46", + "Release": "9.58.1", + "Arch": "noarch", + "SrcName": "openldap2", + "SrcVersion": "2.4.46", + "SrcRelease": "9.58.1", + "License": "OLDAP-2.8", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "liblua5_3-5", + "Version": "5.3.6", + "Release": "3.6.1", + "Arch": "x86_64", + "SrcName": "lua53", + "SrcVersion": "5.3.6", + "SrcRelease": "3.6.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "liblz4-1", + "Version": "1.9.2", + "Release": "3.3.1", + "Arch": "x86_64", + "SrcName": "lz4", + "SrcVersion": "1.9.2", + "SrcRelease": "3.3.1", + "License": "BSD-2-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "liblzma5", + "Version": "5.2.3", + "Release": "4.3.1", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.2.3", + "SrcRelease": "4.3.1", + "License": "SUSE-Public-Domain", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libmagic1", + "Version": "5.32", + "Release": "7.14.1", + "Arch": "x86_64", + "SrcName": "file", + "SrcVersion": "5.32", + "SrcRelease": "7.14.1", + "License": "BSD-2-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libmodman1", + "Version": "2.0.1", + "Release": "1.27", + "Arch": "x86_64", + "SrcName": "libmodman", + "SrcVersion": "2.0.1", + "SrcRelease": "1.27", + "License": "LGPL-2.1+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libmount1", + "Version": "2.36.2", + "Release": "4.5.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.36.2", + "SrcRelease": "4.5.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libncurses6", + "Version": "6.1", + "Release": "5.6.2", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.1", + "SrcRelease": "5.6.2", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libnghttp2-14", + "Version": "1.40.0", + "Release": "6.1", + "Arch": "x86_64", + "SrcName": "nghttp2", + "SrcVersion": "1.40.0", + "SrcRelease": "6.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libnpth0", + "Version": "1.5", + "Release": "2.11", + "Arch": "x86_64", + "SrcName": "npth", + "SrcVersion": "1.5", + "SrcRelease": "2.11", + "License": "LGPL-2.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libnsl2", + "Version": "1.2.0", + "Release": "2.44", + "Arch": "x86_64", + "SrcName": "libnsl", + "SrcVersion": "1.2.0", + "SrcRelease": "2.44", + "License": "LGPL-2.1-only", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libopenssl1_1", + "Version": "1.1.1d", + "Release": "11.30.1", + "Arch": "x86_64", + "SrcName": "openssl-1_1", + "SrcVersion": "1.1.1d", + "SrcRelease": "11.30.1", + "License": "OpenSSL", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libopenssl1_1-hmac", + "Version": "1.1.1d", + "Release": "11.30.1", + "Arch": "x86_64", + "SrcName": "openssl-1_1", + "SrcVersion": "1.1.1d", + "SrcRelease": "11.30.1", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libp11-kit0", + "Version": "0.23.2", + "Release": "4.8.3", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.2", + "SrcRelease": "4.8.3", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libpcre1", + "Version": "8.41", + "Release": "6.4.2", + "Arch": "x86_64", + "SrcName": "pcre", + "SrcVersion": "8.41", + "SrcRelease": "6.4.2", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libpopt0", + "Version": "1.16", + "Release": "3.22", + "Arch": "x86_64", + "SrcName": "popt", + "SrcVersion": "1.16", + "SrcRelease": "3.22", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libprocps7", + "Version": "3.3.15", + "Release": "7.19.1", + "Arch": "x86_64", + "SrcName": "procps", + "SrcVersion": "3.3.15", + "SrcRelease": "7.19.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libproxy1", + "Version": "0.4.15", + "Release": "12.41", + "Arch": "x86_64", + "SrcName": "libproxy", + "SrcVersion": "0.4.15", + "SrcRelease": "12.41", + "License": "GPL-2.0-or-later AND LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libpsl5", + "Version": "0.20.1", + "Release": "1.20", + "Arch": "x86_64", + "SrcName": "libpsl", + "SrcVersion": "0.20.1", + "SrcRelease": "1.20", + "License": "MIT AND MPL-2.0", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libreadline7", + "Version": "7.0", + "Release": "19.6.1", + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "4.4", + "SrcRelease": "19.6.1", + "License": "GPL-3.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libsasl2-3", + "Version": "2.1.27", + "Release": "2.2", + "Arch": "x86_64", + "SrcName": "cyrus-sasl", + "SrcVersion": "2.1.27", + "SrcRelease": "2.2", + "License": "BSD-4-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libselinux1", + "Version": "3.0", + "Release": "1.31", + "Arch": "x86_64", + "SrcName": "libselinux", + "SrcVersion": "3.0", + "SrcRelease": "1.31", + "License": "SUSE-Public-Domain", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libsemanage1", + "Version": "3.0", + "Release": "1.27", + "Arch": "x86_64", + "SrcName": "libsemanage", + "SrcVersion": "3.0", + "SrcRelease": "1.27", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libsepol1", + "Version": "3.0", + "Release": "1.31", + "Arch": "x86_64", + "SrcName": "libsepol", + "SrcVersion": "3.0", + "SrcRelease": "1.31", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libsigc-2_0-0", + "Version": "2.10.2", + "Release": "1.18", + "Arch": "x86_64", + "SrcName": "libsigc++2", + "SrcVersion": "2.10.2", + "SrcRelease": "1.18", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libsmartcols1", + "Version": "2.36.2", + "Release": "4.5.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.36.2", + "SrcRelease": "4.5.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libsolv-tools", + "Version": "0.7.19", + "Release": "6.1", + "Arch": "x86_64", + "SrcName": "libsolv", + "SrcVersion": "0.7.19", + "SrcRelease": "6.1", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libsqlite3-0", + "Version": "3.36.0", + "Release": "3.12.1", + "Arch": "x86_64", + "SrcName": "sqlite3", + "SrcVersion": "3.36.0", + "SrcRelease": "3.12.1", + "License": "SUSE-Public-Domain", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libssh4", + "Version": "0.8.7", + "Release": "10.12.1", + "Arch": "x86_64", + "SrcName": "libssh", + "SrcVersion": "0.8.7", + "SrcRelease": "10.12.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libstdc++6", + "Version": "10.3.0+git1587", + "Release": "1.6.4", + "Arch": "x86_64", + "SrcName": "gcc10", + "SrcVersion": "10.3.0+git1587", + "SrcRelease": "1.6.4", + "License": "GPL-3.0 WITH GCC-exception-3.1", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libsystemd0", + "Version": "246.16", + "Release": "7.14.1", + "Arch": "x86_64", + "SrcName": "systemd", + "SrcVersion": "246.16", + "SrcRelease": "7.14.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libtasn1", + "Version": "4.13", + "Release": "4.5.1", + "Arch": "x86_64", + "SrcName": "libtasn1", + "SrcVersion": "4.13", + "SrcRelease": "4.5.1", + "License": "LGPL-2.1-or-later AND GPL-3.0-only", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libtasn1-6", + "Version": "4.13", + "Release": "4.5.1", + "Arch": "x86_64", + "SrcName": "libtasn1", + "SrcVersion": "4.13", + "SrcRelease": "4.5.1", + "License": "LGPL-2.1-or-later AND GPL-3.0-only", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libtirpc-netconfig", + "Version": "1.2.6", + "Release": "1.131", + "Arch": "x86_64", + "SrcName": "libtirpc", + "SrcVersion": "1.2.6", + "SrcRelease": "1.131", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libtirpc3", + "Version": "1.2.6", + "Release": "1.131", + "Arch": "x86_64", + "SrcName": "libtirpc", + "SrcVersion": "1.2.6", + "SrcRelease": "1.131", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libudev1", + "Version": "246.16", + "Release": "7.14.1", + "Arch": "x86_64", + "SrcName": "systemd", + "SrcVersion": "246.16", + "SrcRelease": "7.14.1", + "License": "LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libunistring2", + "Version": "0.9.10", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libunistring", + "SrcVersion": "0.9.10", + "SrcRelease": "1.1", + "License": "LGPL-3.0-or-later OR GPL-2.0-only", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libusb-1_0-0", + "Version": "1.0.21", + "Release": "3.3.1", + "Arch": "x86_64", + "SrcName": "libusb-1_0", + "SrcVersion": "1.0.21", + "SrcRelease": "3.3.1", + "License": "LGPL-2.1+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libutempter0", + "Version": "1.1.6", + "Release": "3.42", + "Arch": "x86_64", + "SrcName": "utempter", + "SrcVersion": "1.1.6", + "SrcRelease": "3.42", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libuuid1", + "Version": "2.36.2", + "Release": "4.5.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.36.2", + "SrcRelease": "4.5.1", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libverto1", + "Version": "0.2.6", + "Release": "3.20", + "Arch": "x86_64", + "SrcName": "libverto", + "SrcVersion": "0.2.6", + "SrcRelease": "3.20", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libxml2-2", + "Version": "2.9.7", + "Release": "3.37.1", + "Arch": "x86_64", + "SrcName": "libxml2", + "SrcVersion": "2.9.7", + "SrcRelease": "3.37.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libyaml-cpp0_6", + "Version": "0.6.1", + "Release": "4.2.1", + "Arch": "x86_64", + "SrcName": "yaml-cpp", + "SrcVersion": "0.6.1", + "SrcRelease": "4.2.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libz1", + "Version": "1.2.11", + "Release": "3.21.1", + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.2.11", + "SrcRelease": "3.21.1", + "License": "Zlib", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libzio1", + "Version": "1.06", + "Release": "2.20", + "Arch": "x86_64", + "SrcName": "libzio", + "SrcVersion": "1.06", + "SrcRelease": "2.20", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libzstd1", + "Version": "1.4.4", + "Release": "1.6.1", + "Arch": "x86_64", + "SrcName": "zstd", + "SrcVersion": "1.4.4", + "SrcRelease": "1.6.1", + "License": "BSD-3-Clause AND GPL-2.0-only", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "libzypp", + "Version": "17.27.0", + "Release": "12.1", + "Arch": "x86_64", + "SrcName": "libzypp", + "SrcVersion": "17.27.0", + "SrcRelease": "12.1", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "login_defs", + "Version": "4.8.1", + "Release": "2.43", + "Arch": "noarch", + "SrcName": "shadow", + "SrcVersion": "4.8.1", + "SrcRelease": "2.43", + "License": "BSD-3-Clause AND GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "ncurses-utils", + "Version": "6.1", + "Release": "5.6.2", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.1", + "SrcRelease": "5.6.2", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "netcfg", + "Version": "11.6", + "Release": "3.3.1", + "Arch": "noarch", + "SrcName": "netcfg", + "SrcVersion": "11.6", + "SrcRelease": "3.3.1", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "openssl-1_1", + "Version": "1.1.1d", + "Release": "11.30.1", + "Arch": "x86_64", + "SrcName": "openssl-1_1", + "SrcVersion": "1.1.1d", + "SrcRelease": "11.30.1", + "License": "OpenSSL", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "p11-kit", + "Version": "0.23.2", + "Release": "4.8.3", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.2", + "SrcRelease": "4.8.3", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "p11-kit-tools", + "Version": "0.23.2", + "Release": "4.8.3", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.2", + "SrcRelease": "4.8.3", + "License": "BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "pam", + "Version": "1.3.0", + "Release": "6.38.1", + "Arch": "x86_64", + "SrcName": "pam", + "SrcVersion": "1.3.0", + "SrcRelease": "6.38.1", + "License": "GPL-2.0+ or BSD-3-Clause", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "patterns-base-fips", + "Version": "20200124", + "Release": "10.5.1", + "Arch": "x86_64", + "SrcName": "patterns-base", + "SrcVersion": "20200124", + "SrcRelease": "10.5.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "perl-base", + "Version": "5.26.1", + "Release": "15.87", + "Arch": "x86_64", + "SrcName": "perl", + "SrcVersion": "5.26.1", + "SrcRelease": "15.87", + "License": "Artistic-1.0 or GPL-2.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "permissions", + "Version": "20181225", + "Release": "23.6.1", + "Arch": "x86_64", + "SrcName": "permissions", + "SrcVersion": "20181225", + "SrcRelease": "23.6.1", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "pinentry", + "Version": "1.1.0", + "Release": "4.3.1", + "Arch": "x86_64", + "SrcName": "pinentry", + "SrcVersion": "1.1.0", + "SrcRelease": "4.3.1", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "procps", + "Version": "3.3.15", + "Release": "7.19.1", + "Arch": "x86_64", + "SrcName": "procps", + "SrcVersion": "3.3.15", + "SrcRelease": "7.19.1", + "License": "GPL-2.0-or-later AND LGPL-2.1-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "rpm-config-SUSE", + "Version": "1", + "Release": "3.61", + "Arch": "noarch", + "SrcName": "rpm-config-SUSE", + "SrcVersion": "1", + "SrcRelease": "3.61", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "rpm-ndb", + "Version": "4.14.3", + "Release": "40.1", + "Arch": "x86_64", + "SrcName": "rpm-ndb", + "SrcVersion": "4.14.3", + "SrcRelease": "40.1", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "sed", + "Version": "4.4", + "Release": "11.6", + "Arch": "x86_64", + "SrcName": "sed", + "SrcVersion": "4.4", + "SrcRelease": "11.6", + "License": "GPL-3.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "shadow", + "Version": "4.8.1", + "Release": "2.43", + "Arch": "x86_64", + "SrcName": "shadow", + "SrcVersion": "4.8.1", + "SrcRelease": "2.43", + "License": "BSD-3-Clause AND GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "sles-release", + "Version": "15.3", + "Release": "55.4.1", + "Arch": "x86_64", + "SrcName": "sles-release", + "SrcVersion": "15.3", + "SrcRelease": "55.4.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "suse-build-key", + "Version": "12.0", + "Release": "8.16.1", + "Arch": "noarch", + "SrcName": "suse-build-key", + "SrcVersion": "12.0", + "SrcRelease": "8.16.1", + "License": "GPL-2.0+", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "system-group-hardware", + "Version": "20170617", + "Release": "15.86", + "Arch": "noarch", + "SrcName": "system-users", + "SrcVersion": "20170617", + "SrcRelease": "15.86", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "system-user-root", + "Version": "20190513", + "Release": "3.3.1", + "Arch": "noarch", + "SrcName": "system-user-root", + "SrcVersion": "20190513", + "SrcRelease": "3.3.1", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "sysuser-shadow", + "Version": "2.0", + "Release": "4.2.8", + "Arch": "noarch", + "SrcName": "sysuser-tools", + "SrcVersion": "2.0", + "SrcRelease": "4.2.8", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "terminfo-base", + "Version": "6.1", + "Release": "5.6.2", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.1", + "SrcRelease": "5.6.2", + "License": "MIT", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "util-linux", + "Version": "2.36.2", + "Release": "4.5.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.36.2", + "SrcRelease": "4.5.1", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + }, + { + "Name": "zypper", + "Version": "1.14.46", + "Release": "13.1", + "Arch": "x86_64", + "SrcName": "zypper", + "SrcVersion": "1.14.46", + "SrcRelease": "13.1", + "License": "GPL-2.0-or-later", + "Layer": { + "DiffID": "sha256:df03f5ac688c255988c08ee67da08cae2f2697eb2dbbaba3afb0bf2da2ff69d7" + } + } +] \ No newline at end of file diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/ubi-7.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/ubi-7.json.golden new file mode 100644 index 000000000000..fb71282921cd --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/ubi-7.json.golden @@ -0,0 +1,2134 @@ +[ + { + "Name": "acl", + "Version": "2.2.51", + "Release": "14.el7", + "Arch": "x86_64", + "SrcName": "acl", + "SrcVersion": "2.2.51", + "SrcRelease": "14.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "audit-libs", + "Version": "2.8.5", + "Release": "4.el7", + "Arch": "x86_64", + "SrcName": "audit", + "SrcVersion": "2.8.5", + "SrcRelease": "4.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "basesystem", + "Version": "10.0", + "Release": "7.el7", + "Arch": "noarch", + "SrcName": "basesystem", + "SrcVersion": "10.0", + "SrcRelease": "7.el7", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "bash", + "Version": "4.2.46", + "Release": "33.el7", + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "4.2.46", + "SrcRelease": "33.el7", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "binutils", + "Version": "2.27", + "Release": "41.base.el7", + "Arch": "x86_64", + "SrcName": "binutils", + "SrcVersion": "2.27", + "SrcRelease": "41.base.el7", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "bzip2-libs", + "Version": "1.0.6", + "Release": "13.el7", + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.6", + "SrcRelease": "13.el7", + "License": "BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "ca-certificates", + "Version": "2018.2.22", + "Release": "70.0.el7_5", + "Arch": "noarch", + "SrcName": "ca-certificates", + "SrcVersion": "2018.2.22", + "SrcRelease": "70.0.el7_5", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "chkconfig", + "Version": "1.7.4", + "Release": "1.el7", + "Arch": "x86_64", + "SrcName": "chkconfig", + "SrcVersion": "1.7.4", + "SrcRelease": "1.el7", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "coreutils", + "Version": "8.22", + "Release": "24.el7", + "Arch": "x86_64", + "SrcName": "coreutils", + "SrcVersion": "8.22", + "SrcRelease": "24.el7", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "cpio", + "Version": "2.11", + "Release": "27.el7", + "Arch": "x86_64", + "SrcName": "cpio", + "SrcVersion": "2.11", + "SrcRelease": "27.el7", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "cracklib", + "Version": "2.9.0", + "Release": "11.el7", + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.9.0", + "SrcRelease": "11.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "cracklib-dicts", + "Version": "2.9.0", + "Release": "11.el7", + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.9.0", + "SrcRelease": "11.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "cryptsetup-libs", + "Version": "2.0.3", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "cryptsetup", + "SrcVersion": "2.0.3", + "SrcRelease": "5.el7", + "License": "GPLv2+ and LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "curl", + "Version": "7.29.0", + "Release": "54.el7", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.29.0", + "SrcRelease": "54.el7", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "cyrus-sasl-lib", + "Version": "2.1.26", + "Release": "23.el7", + "Arch": "x86_64", + "SrcName": "cyrus-sasl", + "SrcVersion": "2.1.26", + "SrcRelease": "23.el7", + "License": "BSD with advertising", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "dbus", + "Version": "1.10.24", + "Release": "13.el7_6", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "dbus", + "SrcVersion": "1.10.24", + "SrcRelease": "13.el7_6", + "SrcEpoch": 1, + "License": "(GPLv2+ or AFL) and GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "dbus-glib", + "Version": "0.100", + "Release": "7.el7", + "Arch": "x86_64", + "SrcName": "dbus-glib", + "SrcVersion": "0.100", + "SrcRelease": "7.el7", + "License": "AFL and GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "dbus-libs", + "Version": "1.10.24", + "Release": "13.el7_6", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "dbus", + "SrcVersion": "1.10.24", + "SrcRelease": "13.el7_6", + "SrcEpoch": 1, + "License": "(GPLv2+ or AFL) and GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "dbus-python", + "Version": "1.1.1", + "Release": "9.el7", + "Arch": "x86_64", + "SrcName": "dbus-python", + "SrcVersion": "1.1.1", + "SrcRelease": "9.el7", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "device-mapper", + "Version": "1.02.158", + "Release": "2.el7", + "Epoch": 7, + "Arch": "x86_64", + "SrcName": "lvm2", + "SrcVersion": "2.02.185", + "SrcRelease": "2.el7", + "SrcEpoch": 7, + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "device-mapper-libs", + "Version": "1.02.158", + "Release": "2.el7", + "Epoch": 7, + "Arch": "x86_64", + "SrcName": "lvm2", + "SrcVersion": "2.02.185", + "SrcRelease": "2.el7", + "SrcEpoch": 7, + "License": "LGPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "diffutils", + "Version": "3.3", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "diffutils", + "SrcVersion": "3.3", + "SrcRelease": "5.el7", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "dmidecode", + "Version": "3.2", + "Release": "3.el7", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "dmidecode", + "SrcVersion": "3.2", + "SrcRelease": "3.el7", + "SrcEpoch": 1, + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "dracut", + "Version": "033", + "Release": "564.el7", + "Arch": "x86_64", + "SrcName": "dracut", + "SrcVersion": "033", + "SrcRelease": "564.el7", + "License": "GPLv2+ and LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "elfutils-default-yama-scope", + "Version": "0.176", + "Release": "2.el7", + "Arch": "noarch", + "SrcName": "elfutils", + "SrcVersion": "0.176", + "SrcRelease": "2.el7", + "License": "GPLv2+ or LGPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "elfutils-libelf", + "Version": "0.176", + "Release": "2.el7", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.176", + "SrcRelease": "2.el7", + "License": "GPLv2+ or LGPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "elfutils-libs", + "Version": "0.176", + "Release": "2.el7", + "Arch": "x86_64", + "SrcName": "elfutils", + "SrcVersion": "0.176", + "SrcRelease": "2.el7", + "License": "GPLv2+ or LGPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "expat", + "Version": "2.1.0", + "Release": "10.el7_3", + "Arch": "x86_64", + "SrcName": "expat", + "SrcVersion": "2.1.0", + "SrcRelease": "10.el7_3", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "file-libs", + "Version": "5.11", + "Release": "35.el7", + "Arch": "x86_64", + "SrcName": "file", + "SrcVersion": "5.11", + "SrcRelease": "35.el7", + "License": "BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "filesystem", + "Version": "3.2", + "Release": "25.el7", + "Arch": "x86_64", + "SrcName": "filesystem", + "SrcVersion": "3.2", + "SrcRelease": "25.el7", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "findutils", + "Version": "4.5.11", + "Release": "6.el7", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "findutils", + "SrcVersion": "4.5.11", + "SrcRelease": "6.el7", + "SrcEpoch": 1, + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "gawk", + "Version": "4.0.2", + "Release": "4.el7_3.1", + "Arch": "x86_64", + "SrcName": "gawk", + "SrcVersion": "4.0.2", + "SrcRelease": "4.el7_3.1", + "License": "GPLv3+ and GPL and LGPLv3+ and LGPL and BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "gdb-gdbserver", + "Version": "7.6.1", + "Release": "115.el7", + "Arch": "x86_64", + "SrcName": "gdb", + "SrcVersion": "7.6.1", + "SrcRelease": "115.el7", + "License": "GPLv3+ and GPLv3+ with exceptions and GPLv2+ and GPLv2+ with exceptions and GPL+ and LGPLv2+ and BSD and Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "gdbm", + "Version": "1.10", + "Release": "8.el7", + "Arch": "x86_64", + "SrcName": "gdbm", + "SrcVersion": "1.10", + "SrcRelease": "8.el7", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "glib2", + "Version": "2.56.1", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "glib2", + "SrcVersion": "2.56.1", + "SrcRelease": "5.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "glibc", + "Version": "2.17", + "Release": "292.el7", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.17", + "SrcRelease": "292.el7", + "License": "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "glibc-common", + "Version": "2.17", + "Release": "292.el7", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.17", + "SrcRelease": "292.el7", + "License": "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "gmp", + "Version": "6.0.0", + "Release": "15.el7", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "gmp", + "SrcVersion": "6.0.0", + "SrcRelease": "15.el7", + "SrcEpoch": 1, + "License": "LGPLv3+ or GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "gnupg2", + "Version": "2.0.22", + "Release": "5.el7_5", + "Arch": "x86_64", + "SrcName": "gnupg2", + "SrcVersion": "2.0.22", + "SrcRelease": "5.el7_5", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "gobject-introspection", + "Version": "1.56.1", + "Release": "1.el7", + "Arch": "x86_64", + "SrcName": "gobject-introspection", + "SrcVersion": "1.56.1", + "SrcRelease": "1.el7", + "License": "GPLv2+, LGPLv2+, MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "gpgme", + "Version": "1.3.2", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "gpgme", + "SrcVersion": "1.3.2", + "SrcRelease": "5.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "grep", + "Version": "2.20", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "grep", + "SrcVersion": "2.20", + "SrcRelease": "3.el7", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "gzip", + "Version": "1.5", + "Release": "10.el7", + "Arch": "x86_64", + "SrcName": "gzip", + "SrcVersion": "1.5", + "SrcRelease": "10.el7", + "License": "GPLv3+ and GFDL", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "hardlink", + "Version": "1.0", + "Release": "19.el7", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "hardlink", + "SrcVersion": "1.0", + "SrcRelease": "19.el7", + "SrcEpoch": 1, + "License": "GPL+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "info", + "Version": "5.1", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "texinfo", + "SrcVersion": "5.1", + "SrcRelease": "5.el7", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "json-c", + "Version": "0.11", + "Release": "4.el7_0", + "Arch": "x86_64", + "SrcName": "json-c", + "SrcVersion": "0.11", + "SrcRelease": "4.el7_0", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "keyutils-libs", + "Version": "1.5.8", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "keyutils", + "SrcVersion": "1.5.8", + "SrcRelease": "3.el7", + "License": "GPLv2+ and LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "kmod", + "Version": "20", + "Release": "25.el7", + "Arch": "x86_64", + "SrcName": "kmod", + "SrcVersion": "20", + "SrcRelease": "25.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "kmod-libs", + "Version": "20", + "Release": "25.el7", + "Arch": "x86_64", + "SrcName": "kmod", + "SrcVersion": "20", + "SrcRelease": "25.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "kpartx", + "Version": "0.4.9", + "Release": "127.el7", + "Arch": "x86_64", + "SrcName": "device-mapper-multipath", + "SrcVersion": "0.4.9", + "SrcRelease": "127.el7", + "License": "GPL+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "krb5-libs", + "Version": "1.15.1", + "Release": "37.el7_7.2", + "Arch": "x86_64", + "SrcName": "krb5", + "SrcVersion": "1.15.1", + "SrcRelease": "37.el7_7.2", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libacl", + "Version": "2.2.51", + "Release": "14.el7", + "Arch": "x86_64", + "SrcName": "acl", + "SrcVersion": "2.2.51", + "SrcRelease": "14.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libassuan", + "Version": "2.1.0", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "libassuan", + "SrcVersion": "2.1.0", + "SrcRelease": "3.el7", + "License": "LGPLv2+ and GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libattr", + "Version": "2.4.46", + "Release": "13.el7", + "Arch": "x86_64", + "SrcName": "attr", + "SrcVersion": "2.4.46", + "SrcRelease": "13.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libblkid", + "Version": "2.23.2", + "Release": "61.el7", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.23.2", + "SrcRelease": "61.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libcap", + "Version": "2.22", + "Release": "10.el7", + "Arch": "x86_64", + "SrcName": "libcap", + "SrcVersion": "2.22", + "SrcRelease": "10.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libcap-ng", + "Version": "0.7.5", + "Release": "4.el7", + "Arch": "x86_64", + "SrcName": "libcap-ng", + "SrcVersion": "0.7.5", + "SrcRelease": "4.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libcom_err", + "Version": "1.42.9", + "Release": "16.el7", + "Arch": "x86_64", + "SrcName": "e2fsprogs", + "SrcVersion": "1.42.9", + "SrcRelease": "16.el7", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libcurl", + "Version": "7.29.0", + "Release": "54.el7", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.29.0", + "SrcRelease": "54.el7", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libdb", + "Version": "5.3.21", + "Release": "25.el7", + "Arch": "x86_64", + "SrcName": "libdb", + "SrcVersion": "5.3.21", + "SrcRelease": "25.el7", + "License": "BSD and LGPLv2 and Sleepycat", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libdb-utils", + "Version": "5.3.21", + "Release": "25.el7", + "Arch": "x86_64", + "SrcName": "libdb", + "SrcVersion": "5.3.21", + "SrcRelease": "25.el7", + "License": "BSD and LGPLv2 and Sleepycat", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libffi", + "Version": "3.0.13", + "Release": "18.el7", + "Arch": "x86_64", + "SrcName": "libffi", + "SrcVersion": "3.0.13", + "SrcRelease": "18.el7", + "License": "MIT and Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libgcc", + "Version": "4.8.5", + "Release": "39.el7", + "Arch": "x86_64", + "SrcName": "gcc", + "SrcVersion": "4.8.5", + "SrcRelease": "39.el7", + "License": "GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libgcrypt", + "Version": "1.5.3", + "Release": "14.el7", + "Arch": "x86_64", + "SrcName": "libgcrypt", + "SrcVersion": "1.5.3", + "SrcRelease": "14.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libgpg-error", + "Version": "1.12", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "libgpg-error", + "SrcVersion": "1.12", + "SrcRelease": "3.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libidn", + "Version": "1.28", + "Release": "4.el7", + "Arch": "x86_64", + "SrcName": "libidn", + "SrcVersion": "1.28", + "SrcRelease": "4.el7", + "License": "LGPLv2+ and GPLv3+ and GFDL", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libmount", + "Version": "2.23.2", + "Release": "61.el7", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.23.2", + "SrcRelease": "61.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libnl", + "Version": "1.1.4", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "libnl", + "SrcVersion": "1.1.4", + "SrcRelease": "3.el7", + "License": "LGPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libpwquality", + "Version": "1.2.3", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "libpwquality", + "SrcVersion": "1.2.3", + "SrcRelease": "5.el7", + "License": "BSD or GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libselinux", + "Version": "2.5", + "Release": "14.1.el7", + "Arch": "x86_64", + "SrcName": "libselinux", + "SrcVersion": "2.5", + "SrcRelease": "14.1.el7", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libsemanage", + "Version": "2.5", + "Release": "14.el7", + "Arch": "x86_64", + "SrcName": "libsemanage", + "SrcVersion": "2.5", + "SrcRelease": "14.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libsepol", + "Version": "2.5", + "Release": "10.el7", + "Arch": "x86_64", + "SrcName": "libsepol", + "SrcVersion": "2.5", + "SrcRelease": "10.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libsmartcols", + "Version": "2.23.2", + "Release": "61.el7", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.23.2", + "SrcRelease": "61.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libssh2", + "Version": "1.8.0", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "libssh2", + "SrcVersion": "1.8.0", + "SrcRelease": "3.el7", + "License": "BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libstdc++", + "Version": "4.8.5", + "Release": "39.el7", + "Arch": "x86_64", + "SrcName": "gcc", + "SrcVersion": "4.8.5", + "SrcRelease": "39.el7", + "License": "GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libtasn1", + "Version": "4.10", + "Release": "1.el7", + "Arch": "x86_64", + "SrcName": "libtasn1", + "SrcVersion": "4.10", + "SrcRelease": "1.el7", + "License": "GPLv3+ and LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libuser", + "Version": "0.60", + "Release": "9.el7", + "Arch": "x86_64", + "SrcName": "libuser", + "SrcVersion": "0.60", + "SrcRelease": "9.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libutempter", + "Version": "1.1.6", + "Release": "4.el7", + "Arch": "x86_64", + "SrcName": "libutempter", + "SrcVersion": "1.1.6", + "SrcRelease": "4.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libuuid", + "Version": "2.23.2", + "Release": "61.el7", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.23.2", + "SrcRelease": "61.el7", + "License": "BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libverto", + "Version": "0.2.5", + "Release": "4.el7", + "Arch": "x86_64", + "SrcName": "libverto", + "SrcVersion": "0.2.5", + "SrcRelease": "4.el7", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libxml2", + "Version": "2.9.1", + "Release": "6.el7_2.3", + "Arch": "x86_64", + "SrcName": "libxml2", + "SrcVersion": "2.9.1", + "SrcRelease": "6.el7_2.3", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "libxml2-python", + "Version": "2.9.1", + "Release": "6.el7_2.3", + "Arch": "x86_64", + "SrcName": "libxml2", + "SrcVersion": "2.9.1", + "SrcRelease": "6.el7_2.3", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "lua", + "Version": "5.1.4", + "Release": "15.el7", + "Arch": "x86_64", + "SrcName": "lua", + "SrcVersion": "5.1.4", + "SrcRelease": "15.el7", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "lz4", + "Version": "1.7.5", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "lz4", + "SrcVersion": "1.7.5", + "SrcRelease": "3.el7", + "License": "GPLv2+ and BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "ncurses", + "Version": "5.9", + "Release": "14.20130511.el7_4", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "5.9", + "SrcRelease": "14.20130511.el7_4", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "ncurses-base", + "Version": "5.9", + "Release": "14.20130511.el7_4", + "Arch": "noarch", + "SrcName": "ncurses", + "SrcVersion": "5.9", + "SrcRelease": "14.20130511.el7_4", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "ncurses-libs", + "Version": "5.9", + "Release": "14.20130511.el7_4", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "5.9", + "SrcRelease": "14.20130511.el7_4", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "nspr", + "Version": "4.21.0", + "Release": "1.el7", + "Arch": "x86_64", + "SrcName": "nspr", + "SrcVersion": "4.21.0", + "SrcRelease": "1.el7", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "nss", + "Version": "3.44.0", + "Release": "4.el7", + "Arch": "x86_64", + "SrcName": "nss", + "SrcVersion": "3.44.0", + "SrcRelease": "4.el7", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "nss-pem", + "Version": "1.0.3", + "Release": "7.el7", + "Arch": "x86_64", + "SrcName": "nss-pem", + "SrcVersion": "1.0.3", + "SrcRelease": "7.el7", + "License": "MPLv1.1", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "nss-softokn", + "Version": "3.44.0", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "nss-softokn", + "SrcVersion": "3.44.0", + "SrcRelease": "5.el7", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "nss-softokn-freebl", + "Version": "3.44.0", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "nss-softokn", + "SrcVersion": "3.44.0", + "SrcRelease": "5.el7", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "nss-sysinit", + "Version": "3.44.0", + "Release": "4.el7", + "Arch": "x86_64", + "SrcName": "nss", + "SrcVersion": "3.44.0", + "SrcRelease": "4.el7", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "nss-tools", + "Version": "3.44.0", + "Release": "4.el7", + "Arch": "x86_64", + "SrcName": "nss", + "SrcVersion": "3.44.0", + "SrcRelease": "4.el7", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "nss-util", + "Version": "3.44.0", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "nss-util", + "SrcVersion": "3.44.0", + "SrcRelease": "3.el7", + "License": "MPLv2.0", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "openldap", + "Version": "2.4.44", + "Release": "21.el7_6", + "Arch": "x86_64", + "SrcName": "openldap", + "SrcVersion": "2.4.44", + "SrcRelease": "21.el7_6", + "License": "OpenLDAP", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "openssl-libs", + "Version": "1.0.2k", + "Release": "19.el7", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "openssl", + "SrcVersion": "1.0.2k", + "SrcRelease": "19.el7", + "SrcEpoch": 1, + "License": "OpenSSL", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "p11-kit", + "Version": "0.23.5", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.5", + "SrcRelease": "3.el7", + "License": "BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "p11-kit-trust", + "Version": "0.23.5", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.23.5", + "SrcRelease": "3.el7", + "License": "BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "pam", + "Version": "1.1.8", + "Release": "22.el7", + "Arch": "x86_64", + "SrcName": "pam", + "SrcVersion": "1.1.8", + "SrcRelease": "22.el7", + "License": "BSD and GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "passwd", + "Version": "0.79", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "passwd", + "SrcVersion": "0.79", + "SrcRelease": "5.el7", + "License": "BSD or GPL+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "pcre", + "Version": "8.32", + "Release": "17.el7", + "Arch": "x86_64", + "SrcName": "pcre", + "SrcVersion": "8.32", + "SrcRelease": "17.el7", + "License": "BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "pinentry", + "Version": "0.8.1", + "Release": "17.el7", + "Arch": "x86_64", + "SrcName": "pinentry", + "SrcVersion": "0.8.1", + "SrcRelease": "17.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "pkgconfig", + "Version": "0.27.1", + "Release": "4.el7", + "Epoch": 1, + "Arch": "x86_64", + "SrcName": "pkgconfig", + "SrcVersion": "0.27.1", + "SrcRelease": "4.el7", + "SrcEpoch": 1, + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "popt", + "Version": "1.13", + "Release": "16.el7", + "Arch": "x86_64", + "SrcName": "popt", + "SrcVersion": "1.13", + "SrcRelease": "16.el7", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "procps-ng", + "Version": "3.3.10", + "Release": "26.el7", + "Arch": "x86_64", + "SrcName": "procps-ng", + "SrcVersion": "3.3.10", + "SrcRelease": "26.el7", + "License": "GPL+ and GPLv2 and GPLv2+ and GPLv3+ and LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "pth", + "Version": "2.0.7", + "Release": "23.el7", + "Arch": "x86_64", + "SrcName": "pth", + "SrcVersion": "2.0.7", + "SrcRelease": "23.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "pygpgme", + "Version": "0.3", + "Release": "9.el7", + "Arch": "x86_64", + "SrcName": "pygpgme", + "SrcVersion": "0.3", + "SrcRelease": "9.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "pyliblzma", + "Version": "0.5.3", + "Release": "11.el7", + "Arch": "x86_64", + "SrcName": "pyliblzma", + "SrcVersion": "0.5.3", + "SrcRelease": "11.el7", + "License": "LGPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python", + "Version": "2.7.5", + "Release": "86.el7", + "Arch": "x86_64", + "SrcName": "python", + "SrcVersion": "2.7.5", + "SrcRelease": "86.el7", + "License": "Python", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-backports", + "Version": "1.0", + "Release": "8.el7", + "Arch": "x86_64", + "SrcName": "python-backports", + "SrcVersion": "1.0", + "SrcRelease": "8.el7", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-backports-ssl_match_hostname", + "Version": "3.5.0.1", + "Release": "1.el7", + "Arch": "noarch", + "SrcName": "python-backports-ssl_match_hostname", + "SrcVersion": "3.5.0.1", + "SrcRelease": "1.el7", + "License": "Python", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-chardet", + "Version": "2.2.1", + "Release": "3.el7", + "Arch": "noarch", + "SrcName": "python-chardet", + "SrcVersion": "2.2.1", + "SrcRelease": "3.el7", + "License": "LGPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-dateutil", + "Version": "1.5", + "Release": "7.el7", + "Arch": "noarch", + "SrcName": "python-dateutil", + "SrcVersion": "1.5", + "SrcRelease": "7.el7", + "License": "Python", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-decorator", + "Version": "3.4.0", + "Release": "3.el7", + "Arch": "noarch", + "SrcName": "python-decorator", + "SrcVersion": "3.4.0", + "SrcRelease": "3.el7", + "License": "BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-dmidecode", + "Version": "3.12.2", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "python-dmidecode", + "SrcVersion": "3.12.2", + "SrcRelease": "3.el7", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-ethtool", + "Version": "0.8", + "Release": "8.el7", + "Arch": "x86_64", + "SrcName": "python-ethtool", + "SrcVersion": "0.8", + "SrcRelease": "8.el7", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-gobject-base", + "Version": "3.22.0", + "Release": "1.el7_4.1", + "Arch": "x86_64", + "SrcName": "pygobject3", + "SrcVersion": "3.22.0", + "SrcRelease": "1.el7_4.1", + "License": "LGPLv2+ and MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-iniparse", + "Version": "0.4", + "Release": "9.el7", + "Arch": "noarch", + "SrcName": "python-iniparse", + "SrcVersion": "0.4", + "SrcRelease": "9.el7", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-inotify", + "Version": "0.9.4", + "Release": "4.el7", + "Arch": "noarch", + "SrcName": "python-inotify", + "SrcVersion": "0.9.4", + "SrcRelease": "4.el7", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-ipaddress", + "Version": "1.0.16", + "Release": "2.el7", + "Arch": "noarch", + "SrcName": "python-ipaddress", + "SrcVersion": "1.0.16", + "SrcRelease": "2.el7", + "License": "Python", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-kitchen", + "Version": "1.1.1", + "Release": "5.el7", + "Arch": "noarch", + "SrcName": "python-kitchen", + "SrcVersion": "1.1.1", + "SrcRelease": "5.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-libs", + "Version": "2.7.5", + "Release": "86.el7", + "Arch": "x86_64", + "SrcName": "python", + "SrcVersion": "2.7.5", + "SrcRelease": "86.el7", + "License": "Python", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-pycurl", + "Version": "7.19.0", + "Release": "19.el7", + "Arch": "x86_64", + "SrcName": "python-pycurl", + "SrcVersion": "7.19.0", + "SrcRelease": "19.el7", + "License": "LGPLv2+ or MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-setuptools", + "Version": "0.9.8", + "Release": "7.el7", + "Arch": "noarch", + "SrcName": "python-setuptools", + "SrcVersion": "0.9.8", + "SrcRelease": "7.el7", + "License": "Python or ZPLv2.0", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-six", + "Version": "1.9.0", + "Release": "2.el7", + "Arch": "noarch", + "SrcName": "python-six", + "SrcVersion": "1.9.0", + "SrcRelease": "2.el7", + "License": "MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-syspurpose", + "Version": "1.24.13", + "Release": "3.el7_7", + "Arch": "x86_64", + "SrcName": "subscription-manager", + "SrcVersion": "1.24.13", + "SrcRelease": "3.el7_7", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "python-urlgrabber", + "Version": "3.10", + "Release": "9.el7", + "Arch": "noarch", + "SrcName": "python-urlgrabber", + "SrcVersion": "3.10", + "SrcRelease": "9.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "pyxattr", + "Version": "0.5.1", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "pyxattr", + "SrcVersion": "0.5.1", + "SrcRelease": "5.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "qrencode-libs", + "Version": "3.4.1", + "Release": "3.el7", + "Arch": "x86_64", + "SrcName": "qrencode", + "SrcVersion": "3.4.1", + "SrcRelease": "3.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "readline", + "Version": "6.2", + "Release": "11.el7", + "Arch": "x86_64", + "SrcName": "readline", + "SrcVersion": "6.2", + "SrcRelease": "11.el7", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "redhat-release-server", + "Version": "7.7", + "Release": "10.el7", + "Arch": "x86_64", + "SrcName": "redhat-release-server", + "SrcVersion": "7.7", + "SrcRelease": "10.el7", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "rootfiles", + "Version": "8.1", + "Release": "11.el7", + "Arch": "noarch", + "SrcName": "rootfiles", + "SrcVersion": "8.1", + "SrcRelease": "11.el7", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "rpm", + "Version": "4.11.3", + "Release": "40.el7", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.11.3", + "SrcRelease": "40.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "rpm-build-libs", + "Version": "4.11.3", + "Release": "40.el7", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.11.3", + "SrcRelease": "40.el7", + "License": "GPLv2+ and LGPLv2+ with exceptions", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "rpm-libs", + "Version": "4.11.3", + "Release": "40.el7", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.11.3", + "SrcRelease": "40.el7", + "License": "GPLv2+ and LGPLv2+ with exceptions", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "rpm-python", + "Version": "4.11.3", + "Release": "40.el7", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.11.3", + "SrcRelease": "40.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "sed", + "Version": "4.2.2", + "Release": "5.el7", + "Arch": "x86_64", + "SrcName": "sed", + "SrcVersion": "4.2.2", + "SrcRelease": "5.el7", + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "setup", + "Version": "2.8.71", + "Release": "10.el7", + "Arch": "noarch", + "SrcName": "setup", + "SrcVersion": "2.8.71", + "SrcRelease": "10.el7", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "shadow-utils", + "Version": "4.6", + "Release": "5.el7", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "shadow-utils", + "SrcVersion": "4.6", + "SrcRelease": "5.el7", + "SrcEpoch": 2, + "License": "BSD and GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "shared-mime-info", + "Version": "1.8", + "Release": "4.el7", + "Arch": "x86_64", + "SrcName": "shared-mime-info", + "SrcVersion": "1.8", + "SrcRelease": "4.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "sqlite", + "Version": "3.7.17", + "Release": "8.el7", + "Arch": "x86_64", + "SrcName": "sqlite", + "SrcVersion": "3.7.17", + "SrcRelease": "8.el7", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "subscription-manager", + "Version": "1.24.13", + "Release": "3.el7_7", + "Arch": "x86_64", + "SrcName": "subscription-manager", + "SrcVersion": "1.24.13", + "SrcRelease": "3.el7_7", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "subscription-manager-rhsm", + "Version": "1.24.13", + "Release": "3.el7_7", + "Arch": "x86_64", + "SrcName": "subscription-manager", + "SrcVersion": "1.24.13", + "SrcRelease": "3.el7_7", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "subscription-manager-rhsm-certificates", + "Version": "1.24.13", + "Release": "3.el7_7", + "Arch": "x86_64", + "SrcName": "subscription-manager", + "SrcVersion": "1.24.13", + "SrcRelease": "3.el7_7", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "systemd", + "Version": "219", + "Release": "67.el7_7.1", + "Arch": "x86_64", + "SrcName": "systemd", + "SrcVersion": "219", + "SrcRelease": "67.el7_7.1", + "License": "LGPLv2+ and MIT and GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "systemd-libs", + "Version": "219", + "Release": "67.el7_7.1", + "Arch": "x86_64", + "SrcName": "systemd", + "SrcVersion": "219", + "SrcRelease": "67.el7_7.1", + "License": "LGPLv2+ and MIT", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "tar", + "Version": "1.26", + "Release": "35.el7", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "tar", + "SrcVersion": "1.26", + "SrcRelease": "35.el7", + "SrcEpoch": 2, + "License": "GPLv3+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "tzdata", + "Version": "2019b", + "Release": "1.el7", + "Arch": "noarch", + "SrcName": "tzdata", + "SrcVersion": "2019b", + "SrcRelease": "1.el7", + "License": "Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "usermode", + "Version": "1.111", + "Release": "6.el7", + "Arch": "x86_64", + "SrcName": "usermode", + "SrcVersion": "1.111", + "SrcRelease": "6.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "ustr", + "Version": "1.0.4", + "Release": "16.el7", + "Arch": "x86_64", + "SrcName": "ustr", + "SrcVersion": "1.0.4", + "SrcRelease": "16.el7", + "License": "MIT or LGPLv2+ or BSD", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "util-linux", + "Version": "2.23.2", + "Release": "61.el7", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.23.2", + "SrcRelease": "61.el7", + "License": "GPLv2 and GPLv2+ and LGPLv2+ and BSD with advertising and Public Domain", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "vim-minimal", + "Version": "7.4.629", + "Release": "6.el7", + "Epoch": 2, + "Arch": "x86_64", + "SrcName": "vim", + "SrcVersion": "7.4.629", + "SrcRelease": "6.el7", + "SrcEpoch": 2, + "License": "Vim", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "virt-what", + "Version": "1.18", + "Release": "4.el7", + "Arch": "x86_64", + "SrcName": "virt-what", + "SrcVersion": "1.18", + "SrcRelease": "4.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "which", + "Version": "2.20", + "Release": "7.el7", + "Arch": "x86_64", + "SrcName": "which", + "SrcVersion": "2.20", + "SrcRelease": "7.el7", + "License": "GPLv3", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "xz", + "Version": "5.2.2", + "Release": "1.el7", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.2.2", + "SrcRelease": "1.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "xz-libs", + "Version": "5.2.2", + "Release": "1.el7", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.2.2", + "SrcRelease": "1.el7", + "License": "LGPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "yum", + "Version": "3.4.3", + "Release": "163.el7", + "Arch": "noarch", + "SrcName": "yum", + "SrcVersion": "3.4.3", + "SrcRelease": "163.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "yum-metadata-parser", + "Version": "1.1.4", + "Release": "10.el7", + "Arch": "x86_64", + "SrcName": "yum-metadata-parser", + "SrcVersion": "1.1.4", + "SrcRelease": "10.el7", + "License": "GPLv2", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "yum-plugin-ovl", + "Version": "1.1.31", + "Release": "52.el7", + "Arch": "noarch", + "SrcName": "yum-utils", + "SrcVersion": "1.1.31", + "SrcRelease": "52.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "yum-utils", + "Version": "1.1.31", + "Release": "52.el7", + "Arch": "noarch", + "SrcName": "yum-utils", + "SrcVersion": "1.1.31", + "SrcRelease": "52.el7", + "License": "GPLv2+", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + }, + { + "Name": "zlib", + "Version": "1.2.7", + "Release": "18.el7", + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.2.7", + "SrcRelease": "18.el7", + "License": "zlib and Boost", + "Layer": { + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + } + } +] \ No newline at end of file diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/ubuntu-18.04.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/ubuntu-18.04.json.golden new file mode 100644 index 000000000000..ecf13d0f996b --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/ubuntu-18.04.json.golden @@ -0,0 +1,1070 @@ +[ + { + "Name": "dash", + "Version": "0.5.8-2.10", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "dash", + "SrcVersion": "0.5.8-2.10", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "passwd", + "Version": "1:4.5-1ubuntu2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "shadow", + "SrcVersion": "1:4.5-1ubuntu2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "liblz4-1", + "Version": "0.0~r131-2ubuntu3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "lz4", + "SrcVersion": "0.0~r131-2ubuntu3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libgcrypt20", + "Version": "1.8.1-4ubuntu1.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libgcrypt20", + "SrcVersion": "1.8.1-4ubuntu1.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libfdisk1", + "Version": "2.31.1-0.4ubuntu3.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "util-linux", + "SrcVersion": "2.31.1-0.4ubuntu3.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "coreutils", + "Version": "8.28-1ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "coreutils", + "SrcVersion": "8.28-1ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "tar", + "Version": "1.29b-2ubuntu0.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "tar", + "SrcVersion": "1.29b-2ubuntu0.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "ncurses-base", + "Version": "6.1-1ubuntu1.18.04", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "ncurses", + "SrcVersion": "6.1-1ubuntu1.18.04", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libapt-pkg5.0", + "Version": "1.6.11", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "apt", + "SrcVersion": "1.6.11", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "bash", + "Version": "4.4.18-2ubuntu1.2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "bash", + "SrcVersion": "4.4.18-2ubuntu1.2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "zlib1g", + "Version": "1:1.2.11.dfsg-0ubuntu2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "zlib", + "SrcVersion": "1:1.2.11.dfsg-0ubuntu2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "gzip", + "Version": "1.6-5ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "gzip", + "SrcVersion": "1.6-5ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "bzip2", + "Version": "1.0.6-8.1ubuntu0.2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "bzip2", + "SrcVersion": "1.0.6-8.1ubuntu0.2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libhogweed4", + "Version": "3.4-1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "nettle", + "SrcVersion": "3.4-1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "diffutils", + "Version": "1:3.6-1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "diffutils", + "SrcVersion": "1:3.6-1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libncursesw5", + "Version": "6.1-1ubuntu1.18.04", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "ncurses", + "SrcVersion": "6.1-1ubuntu1.18.04", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "hostname", + "Version": "3.20", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "hostname", + "SrcVersion": "3.20", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libselinux1", + "Version": "2.7-2build2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libselinux", + "SrcVersion": "2.7-2build2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libpam0g", + "Version": "1.1.8-3.6ubuntu2.18.04.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "pam", + "SrcVersion": "1.1.8-3.6ubuntu2.18.04.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libnettle6", + "Version": "3.4-1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "nettle", + "SrcVersion": "3.4-1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libgnutls30", + "Version": "3.5.18-1ubuntu1.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "gnutls28", + "SrcVersion": "3.5.18-1ubuntu1.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libc6", + "Version": "2.27-3ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "glibc", + "SrcVersion": "2.27-3ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libc-bin", + "Version": "2.27-3ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "glibc", + "SrcVersion": "2.27-3ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "dpkg", + "Version": "1.19.0.5ubuntu2.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "dpkg", + "SrcVersion": "1.19.0.5ubuntu2.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libgcc1", + "Version": "1:8.3.0-6ubuntu1~18.04.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "gcc-8", + "SrcVersion": "8.3.0-6ubuntu1~18.04.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "util-linux", + "Version": "2.31.1-0.4ubuntu3.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "util-linux", + "SrcVersion": "2.31.1-0.4ubuntu3.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "base-passwd", + "Version": "3.5.44", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "base-passwd", + "SrcVersion": "3.5.44", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "bsdutils", + "Version": "1:2.31.1-0.4ubuntu3.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "util-linux", + "SrcVersion": "2.31.1-0.4ubuntu3.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libgpg-error0", + "Version": "1.27-6", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libgpg-error", + "SrcVersion": "1.27-6", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "gcc-8-base", + "Version": "8.3.0-6ubuntu1~18.04.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "gcc-8", + "SrcVersion": "8.3.0-6ubuntu1~18.04.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "ncurses-bin", + "Version": "6.1-1ubuntu1.18.04", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "ncurses", + "SrcVersion": "6.1-1ubuntu1.18.04", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libudev1", + "Version": "237-3ubuntu10.25", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "systemd", + "SrcVersion": "237-3ubuntu10.25", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libuuid1", + "Version": "2.31.1-0.4ubuntu3.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "util-linux", + "SrcVersion": "2.31.1-0.4ubuntu3.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libsystemd0", + "Version": "237-3ubuntu10.25", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "systemd", + "SrcVersion": "237-3ubuntu10.25", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libbz2-1.0", + "Version": "1.0.6-8.1ubuntu0.2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "bzip2", + "SrcVersion": "1.0.6-8.1ubuntu0.2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "init-system-helpers", + "Version": "1.51", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "init-system-helpers", + "SrcVersion": "1.51", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "apt", + "Version": "1.6.11", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "apt", + "SrcVersion": "1.6.11", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "ubuntu-keyring", + "Version": "2018.09.18.1~18.04.0", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "ubuntu-keyring", + "SrcVersion": "2018.09.18.1~18.04.0", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "grep", + "Version": "3.1-2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "grep", + "SrcVersion": "3.1-2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "debianutils", + "Version": "4.8.4", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "debianutils", + "SrcVersion": "4.8.4", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libdb5.3", + "Version": "5.3.28-13.1ubuntu1.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "db5.3", + "SrcVersion": "5.3.28-13.1ubuntu1.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "gpgv", + "Version": "2.2.4-1ubuntu1.2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "gnupg2", + "SrcVersion": "2.2.4-1ubuntu1.2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "mount", + "Version": "2.31.1-0.4ubuntu3.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "util-linux", + "SrcVersion": "2.31.1-0.4ubuntu3.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libtasn1-6", + "Version": "4.13-2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libtasn1-6", + "SrcVersion": "4.13-2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "adduser", + "Version": "3.116ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "adduser", + "SrcVersion": "3.116ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libcom-err2", + "Version": "1.44.1-1ubuntu1.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "e2fsprogs", + "SrcVersion": "1.44.1-1ubuntu1.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libaudit1", + "Version": "1:2.8.2-1ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "audit", + "SrcVersion": "1:2.8.2-1ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libpcre3", + "Version": "2:8.39-9", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "pcre3", + "SrcVersion": "2:8.39-9", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "base-files", + "Version": "10.1ubuntu2.6", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "base-files", + "SrcVersion": "10.1ubuntu2.6", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libss2", + "Version": "1.44.1-1ubuntu1.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "e2fsprogs", + "SrcVersion": "1.44.1-1ubuntu1.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "debconf", + "Version": "1.5.66ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "debconf", + "SrcVersion": "1.5.66ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libzstd1", + "Version": "1.3.3+dfsg-2ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libzstd", + "SrcVersion": "1.3.3+dfsg-2ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "procps", + "Version": "2:3.3.12-3ubuntu1.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "procps", + "SrcVersion": "2:3.3.12-3ubuntu1.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libffi6", + "Version": "3.2.1-8", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libffi", + "SrcVersion": "3.2.1-8", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libacl1", + "Version": "2.2.52-3build1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "acl", + "SrcVersion": "2.2.52-3build1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libsemanage1", + "Version": "2.7-2build2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libsemanage", + "SrcVersion": "2.7-2build2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libdebconfclient0", + "Version": "0.213ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "cdebconf", + "SrcVersion": "0.213ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libstdc++6", + "Version": "8.3.0-6ubuntu1~18.04.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "gcc-8", + "SrcVersion": "8.3.0-6ubuntu1~18.04.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libmount1", + "Version": "2.31.1-0.4ubuntu3.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "util-linux", + "SrcVersion": "2.31.1-0.4ubuntu3.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "login", + "Version": "1:4.5-1ubuntu2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "shadow", + "SrcVersion": "1:4.5-1ubuntu2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libncurses5", + "Version": "6.1-1ubuntu1.18.04", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "ncurses", + "SrcVersion": "6.1-1ubuntu1.18.04", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libattr1", + "Version": "1:2.4.47-2build1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "attr", + "SrcVersion": "1:2.4.47-2build1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "e2fsprogs", + "Version": "1.44.1-1ubuntu1.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "e2fsprogs", + "SrcVersion": "1.44.1-1ubuntu1.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libsepol1", + "Version": "2.7-1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libsepol", + "SrcVersion": "2.7-1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libpam-runtime", + "Version": "1.1.8-3.6ubuntu2.18.04.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "pam", + "SrcVersion": "1.1.8-3.6ubuntu2.18.04.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libunistring2", + "Version": "0.9.9-0ubuntu2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libunistring", + "SrcVersion": "0.9.9-0ubuntu2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libprocps6", + "Version": "2:3.3.12-3ubuntu1.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "procps", + "SrcVersion": "2:3.3.12-3ubuntu1.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "sensible-utils", + "Version": "0.0.12", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "sensible-utils", + "SrcVersion": "0.0.12", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libsemanage-common", + "Version": "2.7-2build2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libsemanage", + "SrcVersion": "2.7-2build2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "sed", + "Version": "4.4-2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "sed", + "SrcVersion": "4.4-2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libsmartcols1", + "Version": "2.31.1-0.4ubuntu3.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "util-linux", + "SrcVersion": "2.31.1-0.4ubuntu3.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "perl-base", + "Version": "5.26.1-6ubuntu0.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "perl", + "SrcVersion": "5.26.1-6ubuntu0.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "liblzma5", + "Version": "5.2.2-1.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "xz-utils", + "SrcVersion": "5.2.2-1.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "lsb-base", + "Version": "9.20170808ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "lsb", + "SrcVersion": "9.20170808ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libpam-modules", + "Version": "1.1.8-3.6ubuntu2.18.04.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "pam", + "SrcVersion": "1.1.8-3.6ubuntu2.18.04.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libaudit-common", + "Version": "1:2.8.2-1ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "audit", + "SrcVersion": "1:2.8.2-1ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "findutils", + "Version": "4.6.0+git+20170828-2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "findutils", + "SrcVersion": "4.6.0+git+20170828-2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libcap-ng0", + "Version": "0.7.7-3.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libcap-ng", + "SrcVersion": "0.7.7-3.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "fdisk", + "Version": "2.31.1-0.4ubuntu3.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "util-linux", + "SrcVersion": "2.31.1-0.4ubuntu3.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libidn2-0", + "Version": "2.0.4-1.1build2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libidn2", + "SrcVersion": "2.0.4-1.1build2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libblkid1", + "Version": "2.31.1-0.4ubuntu3.3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "util-linux", + "SrcVersion": "2.31.1-0.4ubuntu3.3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libgmp10", + "Version": "2:6.1.2+dfsg-2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "gmp", + "SrcVersion": "2:6.1.2+dfsg-2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libseccomp2", + "Version": "2.4.1-0ubuntu0.18.04.2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "libseccomp", + "SrcVersion": "2.4.1-0ubuntu0.18.04.2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libtinfo5", + "Version": "6.1-1ubuntu1.18.04", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "ncurses", + "SrcVersion": "6.1-1ubuntu1.18.04", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libpam-modules-bin", + "Version": "1.1.8-3.6ubuntu2.18.04.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "pam", + "SrcVersion": "1.1.8-3.6ubuntu2.18.04.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "mawk", + "Version": "1.3.3-17ubuntu3", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "mawk", + "SrcVersion": "1.3.3-17ubuntu3", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "sysvinit-utils", + "Version": "2.88dsf-59.10ubuntu1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "sysvinit", + "SrcVersion": "2.88dsf-59.10ubuntu1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libp11-kit0", + "Version": "0.23.9-2", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "p11-kit", + "SrcVersion": "0.23.9-2", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Name": "libext2fs2", + "Version": "1.44.1-1ubuntu1.1", + "Release": "", + "Epoch": 0, + "Arch": "", + "SrcName": "e2fsprogs", + "SrcVersion": "1.44.1-1ubuntu1.1", + "SrcRelease": "", + "SrcEpoch":0, + "LayerID":"sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + } +] diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden new file mode 100644 index 000000000000..d6e7df5676d6 --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden @@ -0,0 +1,8027 @@ +[ + { + "ID": ".composer-phpext-rundeps@0", + "Name": ".composer-phpext-rundeps", + "Identifier": { + "PURL": "pkg:apk/alpine/.composer-phpext-rundeps@0?arch=noarch\u0026distro=3.7.1" + }, + "Version": "0", + "Arch": "noarch", + "DependsOn": [ + "libsodium@1.0.15-r0", + "musl@1.1.18-r3", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:1eea04acda91d39256def9ea27cb33007e243a1b4f7f8908bcfd9d3de376905d", + "DiffID": "sha256:2f4a5c9187c249834ebc28783bd3c65bdcbacaa8baa6620ddaa27846dd3ef708" + }, + "Digest": "sha1:6120632b2e7d0a3ceb27984e667f1601ef3f6f61" + }, + { + "ID": ".persistent-deps@0", + "Name": ".persistent-deps", + "Identifier": { + "PURL": "pkg:apk/alpine/.persistent-deps@0?arch=noarch\u0026distro=3.7.1" + }, + "Version": "0", + "Arch": "noarch", + "DependsOn": [ + "ca-certificates@20171114-r0", + "curl@7.61.0-r0", + "libressl@2.6.5-r0", + "tar@1.29-r1", + "xz@5.2.3-r1" + ], + "Layer": { + "Digest": "sha256:88777455d910410652665cec0149a02db3584d6dc26e306788a3532d480b00ae", + "DiffID": "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33" + }, + "Digest": "sha1:fdb428017a69c58edbe2c141cf5277c76381d88a" + }, + { + "ID": ".php-rundeps@0", + "Name": ".php-rundeps", + "Identifier": { + "PURL": "pkg:apk/alpine/.php-rundeps@0?arch=noarch\u0026distro=3.7.1" + }, + "Version": "0", + "Arch": "noarch", + "DependsOn": [ + "libcurl@7.61.1-r0", + "libedit@20170329.3.1-r3", + "libressl2.6-libcrypto@2.6.5-r0", + "libressl2.6-libssl@2.6.5-r0", + "libsodium@1.0.15-r0", + "libxml2@2.9.7-r0", + "musl@1.1.18-r3", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:3d6152f6ac208640f9fb494d1c379fe508db1fc5754cd08fefec200bddd13e0e", + "DiffID": "sha256:6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013" + }, + "Digest": "sha1:b55c2c60d52012cbfbd1b40d236e76490b1d2f46" + }, + { + "ID": "alpine-baselayout@3.0.5-r2", + "Name": "alpine-baselayout", + "Identifier": { + "PURL": "pkg:apk/alpine/alpine-baselayout@3.0.5-r2?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "3.0.5-r2", + "Arch": "x86_64", + "SrcName": "alpine-baselayout", + "SrcVersion": "3.0.5-r2", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "busybox@1.27.2-r11", + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:6e1c83d84d2369825ec44b335e246cc812ee21e8", + "InstalledFiles": [ + "etc/hosts", + "etc/sysctl.conf", + "etc/group", + "etc/protocols", + "etc/fstab", + "etc/mtab", + "etc/profile", + "etc/TZ", + "etc/shells", + "etc/motd", + "etc/inittab", + "etc/hostname", + "etc/modules", + "etc/services", + "etc/shadow", + "etc/passwd", + "etc/profile.d/color_prompt", + "etc/sysctl.d/00-alpine.conf", + "etc/modprobe.d/i386.conf", + "etc/modprobe.d/blacklist.conf", + "etc/modprobe.d/aliases.conf", + "etc/modprobe.d/kms.conf", + "etc/crontabs/root", + "sbin/mkmntdirs", + "var/run", + "var/spool/cron/crontabs" + ] + }, + { + "ID": "alpine-keys@2.1-r1", + "Name": "alpine-keys", + "Identifier": { + "PURL": "pkg:apk/alpine/alpine-keys@2.1-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.1-r1", + "Arch": "x86_64", + "SrcName": "alpine-keys", + "SrcVersion": "2.1-r1", + "Licenses": [ + "MIT" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:3458e5b2ab168697409f61d2afd35dea6287fbd2", + "InstalledFiles": [ + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", + "usr/share/apk/keys/aarch64/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "usr/share/apk/keys/ppc64le/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/s390x/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", + "usr/share/apk/keys/armhf/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub" + ] + }, + { + "ID": "apk-tools@2.10.1-r0", + "Name": "apk-tools", + "Identifier": { + "PURL": "pkg:apk/alpine/apk-tools@2.10.1-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.10.1-r0", + "Arch": "x86_64", + "SrcName": "apk-tools", + "SrcVersion": "2.10.1-r0", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "libressl2.6-libcrypto@2.6.5-r0", + "libressl2.6-libssl@2.6.5-r0", + "musl@1.1.18-r3", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:599116788034fe9c0f9486edd4f4a5bf589f4806", + "InstalledFiles": [ + "sbin/apk" + ] + }, + { + "ID": "apr@1.6.3-r0", + "Name": "apr", + "Identifier": { + "PURL": "pkg:apk/alpine/apr@1.6.3-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.6.3-r0", + "Arch": "x86_64", + "SrcName": "apr", + "SrcVersion": "1.6.3-r0", + "Licenses": [ + "ASL2.0" + ], + "DependsOn": [ + "libuuid@2.31-r0", + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:ef13fd7436617f0d1664d824b0e89e516d57dbf6", + "InstalledFiles": [ + "usr/lib/libapr-1.so", + "usr/lib/libapr-1.so.0.6.3", + "usr/lib/libapr-1.so.0" + ] + }, + { + "ID": "apr-util@1.6.1-r1", + "Name": "apr-util", + "Identifier": { + "PURL": "pkg:apk/alpine/apr-util@1.6.1-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.6.1-r1", + "Arch": "x86_64", + "SrcName": "apr-util", + "SrcVersion": "1.6.1-r1", + "Licenses": [ + "ASL2.0" + ], + "DependsOn": [ + "apr@1.6.3-r0", + "expat@2.2.5-r0", + "libressl2.6-libcrypto@2.6.5-r0", + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:bafbf610de141db01418fa88e91b05c69efeb608", + "InstalledFiles": [ + "usr/lib/libaprutil-1.so.0.6.1", + "usr/lib/libaprutil-1.so.0", + "usr/lib/apr-util-1/apr_crypto_openssl.so", + "usr/lib/apr-util-1/apr_crypto_openssl-1.so" + ] + }, + { + "ID": "bash@4.4.19-r1", + "Name": "bash", + "Identifier": { + "PURL": "pkg:apk/alpine/bash@4.4.19-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "4.4.19-r1", + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "4.4.19-r1", + "Licenses": [ + "GPL-3.0" + ], + "DependsOn": [ + "busybox@1.27.2-r11", + "musl@1.1.18-r3", + "pkgconf@1.3.10-r0", + "readline@7.0.003-r0" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:2a2bb204890224c98f6327d5c4da7ea291ad370f", + "InstalledFiles": [ + "bin/bashbug", + "bin/bash", + "usr/lib/pkgconfig/bash.pc", + "usr/lib/bash/unlink", + "usr/lib/bash/tty", + "usr/lib/bash/pathchk", + "usr/lib/bash/mypid", + "usr/lib/bash/basename", + "usr/lib/bash/sync", + "usr/lib/bash/mkdir", + "usr/lib/bash/logname", + "usr/lib/bash/sleep", + "usr/lib/bash/whoami", + "usr/lib/bash/head", + "usr/lib/bash/uname", + "usr/lib/bash/setpgid", + "usr/lib/bash/tee", + "usr/lib/bash/id", + "usr/lib/bash/strftime", + "usr/lib/bash/realpath", + "usr/lib/bash/truefalse", + "usr/lib/bash/finfo", + "usr/lib/bash/dirname", + "usr/lib/bash/ln", + "usr/lib/bash/print", + "usr/lib/bash/Makefile.inc", + "usr/lib/bash/printenv", + "usr/lib/bash/rmdir", + "usr/lib/bash/push", + "usr/include/bash/unwind_prot.h", + "usr/include/bash/config-bot.h", + "usr/include/bash/hashlib.h", + "usr/include/bash/siglist.h", + "usr/include/bash/make_cmd.h", + "usr/include/bash/version.h", + "usr/include/bash/bashjmp.h", + "usr/include/bash/error.h", + "usr/include/bash/config.h", + "usr/include/bash/dispose_cmd.h", + "usr/include/bash/xmalloc.h", + "usr/include/bash/general.h", + "usr/include/bash/y.tab.h", + "usr/include/bash/arrayfunc.h", + "usr/include/bash/alias.h", + "usr/include/bash/quit.h", + "usr/include/bash/externs.h", + "usr/include/bash/bashansi.h", + "usr/include/bash/signames.h", + "usr/include/bash/config-top.h", + "usr/include/bash/bashintl.h", + "usr/include/bash/syntax.h", + "usr/include/bash/shell.h", + "usr/include/bash/builtins.h", + "usr/include/bash/bashtypes.h", + "usr/include/bash/variables.h", + "usr/include/bash/pathnames.h", + "usr/include/bash/sig.h", + "usr/include/bash/array.h", + "usr/include/bash/conftypes.h", + "usr/include/bash/command.h", + "usr/include/bash/jobs.h", + "usr/include/bash/assoc.h", + "usr/include/bash/subst.h", + "usr/include/bash/builtins/bashgetopt.h", + "usr/include/bash/builtins/getopt.h", + "usr/include/bash/builtins/common.h", + "usr/include/bash/builtins/builtext.h", + "usr/include/bash/include/posixdir.h", + "usr/include/bash/include/maxpath.h", + "usr/include/bash/include/stat-time.h", + "usr/include/bash/include/filecntl.h", + "usr/include/bash/include/posixwait.h", + "usr/include/bash/include/typemax.h", + "usr/include/bash/include/posixtime.h", + "usr/include/bash/include/ocache.h", + "usr/include/bash/include/posixjmp.h", + "usr/include/bash/include/chartypes.h", + "usr/include/bash/include/shmbchar.h", + "usr/include/bash/include/shmbutil.h", + "usr/include/bash/include/stdc.h", + "usr/include/bash/include/unionwait.h", + "usr/include/bash/include/posixstat.h", + "usr/include/bash/include/ansi_stdlib.h", + "usr/include/bash/include/memalloc.h", + "usr/include/bash/include/shtty.h", + "usr/include/bash/include/systimes.h", + "usr/include/bash/include/gettext.h" + ] + }, + { + "ID": "busybox@1.27.2-r11", + "Name": "busybox", + "Identifier": { + "PURL": "pkg:apk/alpine/busybox@1.27.2-r11?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.27.2-r11", + "Arch": "x86_64", + "SrcName": "busybox", + "SrcVersion": "1.27.2-r11", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:7f9617f0e3abb6430216d33201e6d5b30d049fc5", + "InstalledFiles": [ + "bin/busybox", + "bin/sh", + "etc/securetty", + "etc/udhcpd.conf", + "etc/logrotate.d/acpid", + "etc/network/if-up.d/dad" + ] + }, + { + "ID": "ca-certificates@20171114-r0", + "Name": "ca-certificates", + "Identifier": { + "PURL": "pkg:apk/alpine/ca-certificates@20171114-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "20171114-r0", + "Arch": "x86_64", + "SrcName": "ca-certificates", + "SrcVersion": "20171114-r0", + "Licenses": [ + "MPL-2.0", + "GPL-2.0" + ], + "DependsOn": [ + "busybox@1.27.2-r11", + "libressl2.6-libcrypto@2.6.5-r0", + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:88777455d910410652665cec0149a02db3584d6dc26e306788a3532d480b00ae", + "DiffID": "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33" + }, + "Digest": "sha1:5aa669d28ef467b64451bd754af97b4f68cdd884", + "InstalledFiles": [ + "etc/ca-certificates.conf", + "etc/ca-certificates/update.d/certhash", + "etc/apk/protected_paths.d/ca-certificates.list", + "usr/sbin/update-ca-certificates", + "usr/bin/c_rehash", + "usr/share/ca-certificates/mozilla/Taiwan_GRCA.crt", + "usr/share/ca-certificates/mozilla/LuxTrust_Global_Root_2.crt", + "usr/share/ca-certificates/mozilla/Starfield_Class_2_CA.crt", + "usr/share/ca-certificates/mozilla/UTN_USERFirst_Email_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Izenpe.com.crt", + "usr/share/ca-certificates/mozilla/Certinomis_-_Autorité_Racine.crt", + "usr/share/ca-certificates/mozilla/SecureTrust_CA.crt", + "usr/share/ca-certificates/mozilla/Hellenic_Academic_and_Research_Institutions_RootCA_2011.crt", + "usr/share/ca-certificates/mozilla/Go_Daddy_Root_Certificate_Authority_-_G2.crt", + "usr/share/ca-certificates/mozilla/OpenTrust_Root_CA_G3.crt", + "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_2_G3.crt", + "usr/share/ca-certificates/mozilla/Security_Communication_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.crt", + "usr/share/ca-certificates/mozilla/CA_Disig_Root_R2.crt", + "usr/share/ca-certificates/mozilla/Amazon_Root_CA_1.crt", + "usr/share/ca-certificates/mozilla/Verisign_Class_1_Public_Primary_Certification_Authority_-_G3.crt", + "usr/share/ca-certificates/mozilla/ComSign_CA.crt", + "usr/share/ca-certificates/mozilla/Deutsche_Telekom_Root_CA_2.crt", + "usr/share/ca-certificates/mozilla/Amazon_Root_CA_3.crt", + "usr/share/ca-certificates/mozilla/ISRG_Root_X1.crt", + "usr/share/ca-certificates/mozilla/Comodo_AAA_Services_root.crt", + "usr/share/ca-certificates/mozilla/CFCA_EV_ROOT.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Trusted_Root_G4.crt", + "usr/share/ca-certificates/mozilla/TÜRKTRUST_Elektronik_Sertifika_Hizmet_Sağlayıcısı_H5.crt", + "usr/share/ca-certificates/mozilla/DST_ACES_CA_X6.crt", + "usr/share/ca-certificates/mozilla/OISTE_WISeKey_Global_Root_GA_CA.crt", + "usr/share/ca-certificates/mozilla/OpenTrust_Root_CA_G2.crt", + "usr/share/ca-certificates/mozilla/SZAFIR_ROOT_CA2.crt", + "usr/share/ca-certificates/mozilla/AddTrust_Low-Value_Services_Root.crt", + "usr/share/ca-certificates/mozilla/T-TeleSec_GlobalRoot_Class_2.crt", + "usr/share/ca-certificates/mozilla/TURKTRUST_Certificate_Services_Provider_Root_2007.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_Root_CA_-_R3.crt", + "usr/share/ca-certificates/mozilla/Global_Chambersign_Root_-_2008.crt", + "usr/share/ca-certificates/mozilla/AffirmTrust_Commercial.crt", + "usr/share/ca-certificates/mozilla/Symantec_Class_2_Public_Primary_Certification_Authority_-_G4.crt", + "usr/share/ca-certificates/mozilla/ACEDICOM_Root.crt", + "usr/share/ca-certificates/mozilla/SwissSign_Gold_CA_-_G2.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_Root_CA.crt", + "usr/share/ca-certificates/mozilla/AC_RAIZ_FNMT-RCM.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Assured_ID_Root_G3.crt", + "usr/share/ca-certificates/mozilla/Symantec_Class_1_Public_Primary_Certification_Authority_-_G6.crt", + "usr/share/ca-certificates/mozilla/Atos_TrustedRoot_2011.crt", + "usr/share/ca-certificates/mozilla/GeoTrust_Primary_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/certSIGN_ROOT_CA.crt", + "usr/share/ca-certificates/mozilla/thawte_Primary_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Hongkong_Post_Root_CA_1.crt", + "usr/share/ca-certificates/mozilla/Certinomis_-_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Cybertrust_Global_Root.crt", + "usr/share/ca-certificates/mozilla/Starfield_Services_Root_Certificate_Authority_-_G2.crt", + "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_2.crt", + "usr/share/ca-certificates/mozilla/GeoTrust_Global_CA.crt", + "usr/share/ca-certificates/mozilla/AddTrust_External_Root.crt", + "usr/share/ca-certificates/mozilla/SecureSign_RootCA11.crt", + "usr/share/ca-certificates/mozilla/Go_Daddy_Class_2_CA.crt", + "usr/share/ca-certificates/mozilla/IdenTrust_Commercial_Root_CA_1.crt", + "usr/share/ca-certificates/mozilla/TWCA_Root_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/Staat_der_Nederlanden_EV_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Visa_eCommerce_Root.crt", + "usr/share/ca-certificates/mozilla/OISTE_WISeKey_Global_Root_GB_CA.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Assured_ID_Root_G2.crt", + "usr/share/ca-certificates/mozilla/Starfield_Root_Certificate_Authority_-_G2.crt", + "usr/share/ca-certificates/mozilla/EE_Certification_Centre_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Buypass_Class_2_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Certum_Root_CA.crt", + "usr/share/ca-certificates/mozilla/TeliaSonera_Root_CA_v1.crt", + "usr/share/ca-certificates/mozilla/AffirmTrust_Networking.crt", + "usr/share/ca-certificates/mozilla/AffirmTrust_Premium_ECC.crt", + "usr/share/ca-certificates/mozilla/ACCVRAIZ1.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Assured_ID_Root_CA.crt", + "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_3.crt", + "usr/share/ca-certificates/mozilla/SwissSign_Silver_CA_-_G2.crt", + "usr/share/ca-certificates/mozilla/TUBITAK_Kamu_SM_SSL_Kok_Sertifikasi_-_Surum_1.crt", + "usr/share/ca-certificates/mozilla/GeoTrust_Primary_Certification_Authority_-_G3.crt", + "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_1_G3.crt", + "usr/share/ca-certificates/mozilla/Entrust_Root_Certification_Authority_-_G2.crt", + "usr/share/ca-certificates/mozilla/Staat_der_Nederlanden_Root_CA_-_G3.crt", + "usr/share/ca-certificates/mozilla/COMODO_ECC_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/COMODO_RSA_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/Certplus_Class_2_Primary_CA.crt", + "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA.crt", + "usr/share/ca-certificates/mozilla/D-TRUST_Root_Class_3_CA_2_EV_2009.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Global_Root_G2.crt", + "usr/share/ca-certificates/mozilla/Entrust_Root_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/EC-ACC.crt", + "usr/share/ca-certificates/mozilla/XRamp_Global_CA_Root.crt", + "usr/share/ca-certificates/mozilla/Trustis_FPS_Root_CA.crt", + "usr/share/ca-certificates/mozilla/GeoTrust_Primary_Certification_Authority_-_G2.crt", + "usr/share/ca-certificates/mozilla/thawte_Primary_Root_CA_-_G3.crt", + "usr/share/ca-certificates/mozilla/Verisign_Class_2_Public_Primary_Certification_Authority_-_G3.crt", + "usr/share/ca-certificates/mozilla/NetLock_Arany_=Class_Gold=_Főtanúsítvány.crt", + "usr/share/ca-certificates/mozilla/Amazon_Root_CA_2.crt", + "usr/share/ca-certificates/mozilla/TC_TrustCenter_Class_3_CA_II.crt", + "usr/share/ca-certificates/mozilla/Camerfirma_Global_Chambersign_Root.crt", + "usr/share/ca-certificates/mozilla/AC_Raíz_Certicámara_S.A..crt", + "usr/share/ca-certificates/mozilla/Swisscom_Root_CA_2.crt", + "usr/share/ca-certificates/mozilla/Amazon_Root_CA_4.crt", + "usr/share/ca-certificates/mozilla/Symantec_Class_2_Public_Primary_Certification_Authority_-_G6.crt", + "usr/share/ca-certificates/mozilla/Security_Communication_RootCA2.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Global_Root_CA.crt", + "usr/share/ca-certificates/mozilla/ePKI_Root_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/TÜBİTAK_UEKAE_Kök_Sertifika_Hizmet_Sağlayıcısı_-_Sürüm_3.crt", + "usr/share/ca-certificates/mozilla/D-TRUST_Root_CA_3_2013.crt", + "usr/share/ca-certificates/mozilla/VeriSign_Universal_Root_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/Certum_Trusted_Network_CA.crt", + "usr/share/ca-certificates/mozilla/COMODO_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/S-TRUST_Universal_Root_CA.crt", + "usr/share/ca-certificates/mozilla/SwissSign_Platinum_CA_-_G2.crt", + "usr/share/ca-certificates/mozilla/Camerfirma_Chambers_of_Commerce_Root.crt", + "usr/share/ca-certificates/mozilla/T-TeleSec_GlobalRoot_Class_3.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Global_Root_G3.crt", + "usr/share/ca-certificates/mozilla/Symantec_Class_1_Public_Primary_Certification_Authority_-_G4.crt", + "usr/share/ca-certificates/mozilla/VeriSign_Class_3_Public_Primary_Certification_Authority_-_G4.crt", + "usr/share/ca-certificates/mozilla/Secure_Global_CA.crt", + "usr/share/ca-certificates/mozilla/Actalis_Authentication_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Hellenic_Academic_and_Research_Institutions_RootCA_2015.crt", + "usr/share/ca-certificates/mozilla/USERTrust_RSA_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/D-TRUST_Root_Class_3_CA_2_2009.crt", + "usr/share/ca-certificates/mozilla/AffirmTrust_Premium.crt", + "usr/share/ca-certificates/mozilla/Certigna.crt", + "usr/share/ca-certificates/mozilla/Certplus_Root_CA_G2.crt", + "usr/share/ca-certificates/mozilla/TWCA_Global_Root_CA.crt", + "usr/share/ca-certificates/mozilla/IdenTrust_Public_Sector_Root_CA_1.crt", + "usr/share/ca-certificates/mozilla/OpenTrust_Root_CA_G1.crt", + "usr/share/ca-certificates/mozilla/Staat_der_Nederlanden_Root_CA_-_G2.crt", + "usr/share/ca-certificates/mozilla/Hellenic_Academic_and_Research_Institutions_ECC_RootCA_2015.crt", + "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_3_G3.crt", + "usr/share/ca-certificates/mozilla/Chambers_of_Commerce_Root_-_2008.crt", + "usr/share/ca-certificates/mozilla/DST_Root_CA_X3.crt", + "usr/share/ca-certificates/mozilla/Entrust.net_Premium_2048_Secure_Server_CA.crt", + "usr/share/ca-certificates/mozilla/Certplus_Root_CA_G1.crt", + "usr/share/ca-certificates/mozilla/Verisign_Class_3_Public_Primary_Certification_Authority_-_G3.crt", + "usr/share/ca-certificates/mozilla/USERTrust_ECC_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/DigiCert_High_Assurance_EV_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Security_Communication_EV_RootCA1.crt", + "usr/share/ca-certificates/mozilla/PSCProcert.crt", + "usr/share/ca-certificates/mozilla/thawte_Primary_Root_CA_-_G2.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_Root_CA_-_R2.crt", + "usr/share/ca-certificates/mozilla/GeoTrust_Universal_CA.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_ECC_Root_CA_-_R4.crt", + "usr/share/ca-certificates/mozilla/Baltimore_CyberTrust_Root.crt", + "usr/share/ca-certificates/mozilla/Sonera_Class_2_Root_CA.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_ECC_Root_CA_-_R5.crt", + "usr/share/ca-certificates/mozilla/Microsec_e-Szigno_Root_CA_2009.crt", + "usr/share/ca-certificates/mozilla/GeoTrust_Universal_CA_2.crt", + "usr/share/ca-certificates/mozilla/VeriSign_Class_3_Public_Primary_Certification_Authority_-_G5.crt", + "usr/share/ca-certificates/mozilla/CA_Disig_Root_R1.crt", + "usr/share/ca-certificates/mozilla/Entrust_Root_Certification_Authority_-_EC1.crt", + "usr/share/ca-certificates/mozilla/E-Tugra_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/Buypass_Class_3_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Network_Solutions_Certificate_Authority.crt", + "usr/share/ca-certificates/mozilla/Certum_Trusted_Network_CA_2.crt" + ] + }, + { + "ID": "curl@7.61.0-r0", + "Name": "curl", + "Identifier": { + "PURL": "pkg:apk/alpine/curl@7.61.0-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "7.61.0-r0", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.61.0-r0", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "ca-certificates@20171114-r0", + "libcurl@7.61.1-r0", + "musl@1.1.18-r3", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:88777455d910410652665cec0149a02db3584d6dc26e306788a3532d480b00ae", + "DiffID": "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33" + }, + "Digest": "sha1:016d483d30cfea67c126965281160db475b6ca65", + "InstalledFiles": [ + "usr/bin/curl" + ] + }, + { + "ID": "db@5.3.28-r0", + "Name": "db", + "Identifier": { + "PURL": "pkg:apk/alpine/db@5.3.28-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "5.3.28-r0", + "Arch": "x86_64", + "SrcName": "db", + "SrcVersion": "5.3.28-r0", + "Licenses": [ + "custom" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:6a4d69ccb4da10b07ede9649b08aa323a8902790", + "InstalledFiles": [ + "usr/lib/libdb-5.3.so" + ] + }, + { + "ID": "expat@2.2.5-r0", + "Name": "expat", + "Identifier": { + "PURL": "pkg:apk/alpine/expat@2.2.5-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.2.5-r0", + "Arch": "x86_64", + "SrcName": "expat", + "SrcVersion": "2.2.5-r0", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:5096efd90f782b0e54927539aa30f4134181d477", + "InstalledFiles": [ + "usr/bin/xmlwf", + "usr/lib/libexpat.so.1.6.7", + "usr/lib/libexpat.so.1" + ] + }, + { + "ID": "gdbm@1.13-r1", + "Name": "gdbm", + "Identifier": { + "PURL": "pkg:apk/alpine/gdbm@1.13-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.13-r1", + "Arch": "x86_64", + "SrcName": "gdbm", + "SrcVersion": "1.13-r1", + "Licenses": [ + "GPL-3.0" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:331bccf0688c10645a51523ef5590f3f2bebcbf9", + "InstalledFiles": [ + "usr/bin/gdbm_dump", + "usr/bin/gdbm_load", + "usr/bin/gdbmtool", + "usr/lib/libgdbm.so.4.0.0", + "usr/lib/libgdbm_compat.so.4.0.0", + "usr/lib/libgdbm.so.4", + "usr/lib/libgdbm_compat.so.4" + ] + }, + { + "ID": "git@2.15.2-r0", + "Name": "git", + "Identifier": { + "PURL": "pkg:apk/alpine/git@2.15.2-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.15.2-r0", + "Arch": "x86_64", + "SrcName": "git", + "SrcVersion": "2.15.2-r0", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "expat@2.2.5-r0", + "libcurl@7.61.1-r0", + "musl@1.1.18-r3", + "pcre2@10.30-r0", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:acfbed348e588d90838c4a308428a16042ee9f30", + "InstalledFiles": [ + "usr/libexec/git-core/git-fmt-merge-msg", + "usr/libexec/git-core/git-parse-remote", + "usr/libexec/git-core/git-describe", + "usr/libexec/git-core/git-repack", + "usr/libexec/git-core/git-bisect--helper", + "usr/libexec/git-core/git-whatchanged", + "usr/libexec/git-core/git-revert", + "usr/libexec/git-core/git-cat-file", + "usr/libexec/git-core/git-merge-tree", + "usr/libexec/git-core/git-merge-subtree", + "usr/libexec/git-core/git-read-tree", + "usr/libexec/git-core/git-verify-tag", + "usr/libexec/git-core/git-commit", + "usr/libexec/git-core/git-var", + "usr/libexec/git-core/git-rebase--helper", + "usr/libexec/git-core/git-reset", + "usr/libexec/git-core/git-diff", + "usr/libexec/git-core/git-clean", + "usr/libexec/git-core/git-verify-commit", + "usr/libexec/git-core/git-push", + "usr/libexec/git-core/git-remote-ftps", + "usr/libexec/git-core/git-credential-cache--daemon", + "usr/libexec/git-core/git-merge-base", + "usr/libexec/git-core/git-branch", + "usr/libexec/git-core/git-bisect", + "usr/libexec/git-core/git-pack-redundant", + "usr/libexec/git-core/git-interpret-trailers", + "usr/libexec/git-core/git-prune-packed", + "usr/libexec/git-core/git", + "usr/libexec/git-core/git-diff-index", + "usr/libexec/git-core/git-show-branch", + "usr/libexec/git-core/git-rebase--interactive", + "usr/libexec/git-core/git-check-mailmap", + "usr/libexec/git-core/git-cherry-pick", + "usr/libexec/git-core/git-worktree", + "usr/libexec/git-core/git-fetch-pack", + "usr/libexec/git-core/git-mailinfo", + "usr/libexec/git-core/git-format-patch", + "usr/libexec/git-core/git-tag", + "usr/libexec/git-core/git-add", + "usr/libexec/git-core/git-column", + "usr/libexec/git-core/git-mailsplit", + "usr/libexec/git-core/git-filter-branch", + "usr/libexec/git-core/git-stash", + "usr/libexec/git-core/git-name-rev", + "usr/libexec/git-core/git-rebase--am", + "usr/libexec/git-core/git-rev-list", + "usr/libexec/git-core/git-notes", + "usr/libexec/git-core/git-init", + "usr/libexec/git-core/git-init-db", + "usr/libexec/git-core/git-shortlog", + "usr/libexec/git-core/git-rerere", + "usr/libexec/git-core/git-fsck-objects", + "usr/libexec/git-core/git-mv", + "usr/libexec/git-core/git-fetch", + "usr/libexec/git-core/git-for-each-ref", + "usr/libexec/git-core/git-difftool--helper", + "usr/libexec/git-core/git-stage", + "usr/libexec/git-core/git-pull", + "usr/libexec/git-core/git-diff-tree", + "usr/libexec/git-core/git-rev-parse", + "usr/libexec/git-core/git-check-attr", + "usr/libexec/git-core/git-credential-store", + "usr/libexec/git-core/git-remote-fd", + "usr/libexec/git-core/git-annotate", + "usr/libexec/git-core/git-apply", + "usr/libexec/git-core/git-checkout-index", + "usr/libexec/git-core/git-pack-objects", + "usr/libexec/git-core/git-http-push", + "usr/libexec/git-core/git-mergetool", + "usr/libexec/git-core/git-update-ref", + "usr/libexec/git-core/git-merge-octopus", + "usr/libexec/git-core/git-blame", + "usr/libexec/git-core/git-merge-one-file", + "usr/libexec/git-core/git-symbolic-ref", + "usr/libexec/git-core/git-ls-remote", + "usr/libexec/git-core/git-commit-tree", + "usr/libexec/git-core/git-merge-recursive", + "usr/libexec/git-core/git-check-ref-format", + "usr/libexec/git-core/git-grep", + "usr/libexec/git-core/git-merge-ours", + "usr/libexec/git-core/git-bundle", + "usr/libexec/git-core/git-show-index", + "usr/libexec/git-core/git-mergetool--lib", + "usr/libexec/git-core/git-upload-pack", + "usr/libexec/git-core/git-merge-resolve", + "usr/libexec/git-core/git-update-index", + "usr/libexec/git-core/git-sh-i18n--envsubst", + "usr/libexec/git-core/git-mktag", + "usr/libexec/git-core/git-write-tree", + "usr/libexec/git-core/git-credential", + "usr/libexec/git-core/git-remote-http", + "usr/libexec/git-core/git-quiltimport", + "usr/libexec/git-core/git-cherry", + "usr/libexec/git-core/git-archive", + "usr/libexec/git-core/git-get-tar-commit-id", + "usr/libexec/git-core/git-send-pack", + "usr/libexec/git-core/git-fsck", + "usr/libexec/git-core/git-difftool", + "usr/libexec/git-core/git-gc", + "usr/libexec/git-core/git-fast-export", + "usr/libexec/git-core/git-check-ignore", + "usr/libexec/git-core/git-reflog", + "usr/libexec/git-core/git-remote-ext", + "usr/libexec/git-core/git-merge-file", + "usr/libexec/git-core/git-mktree", + "usr/libexec/git-core/git-hash-object", + "usr/libexec/git-core/git-web--browse", + "usr/libexec/git-core/git-submodule--helper", + "usr/libexec/git-core/git-receive-pack", + "usr/libexec/git-core/git-pack-refs", + "usr/libexec/git-core/git-help", + "usr/libexec/git-core/git-stripspace", + "usr/libexec/git-core/git-sh-setup", + "usr/libexec/git-core/git-archimport", + "usr/libexec/git-core/git-merge", + "usr/libexec/git-core/git-add--interactive", + "usr/libexec/git-core/git-verify-pack", + "usr/libexec/git-core/git-rebase--merge", + "usr/libexec/git-core/git-rebase", + "usr/libexec/git-core/git-am", + "usr/libexec/git-core/git-request-pull", + "usr/libexec/git-core/git-log", + "usr/libexec/git-core/git-unpack-file", + "usr/libexec/git-core/git-checkout", + "usr/libexec/git-core/git-status", + "usr/libexec/git-core/git-remote-https", + "usr/libexec/git-core/git-http-fetch", + "usr/libexec/git-core/git-index-pack", + "usr/libexec/git-core/git-upload-archive", + "usr/libexec/git-core/git-rm", + "usr/libexec/git-core/git-remote-ftp", + "usr/libexec/git-core/git-count-objects", + "usr/libexec/git-core/git-unpack-objects", + "usr/libexec/git-core/git-ls-files", + "usr/libexec/git-core/git-merge-index", + "usr/libexec/git-core/git-show-ref", + "usr/libexec/git-core/git-sh-i18n", + "usr/libexec/git-core/git-diff-files", + "usr/libexec/git-core/git-patch-id", + "usr/libexec/git-core/git-show", + "usr/libexec/git-core/git-remote", + "usr/libexec/git-core/git-submodule", + "usr/libexec/git-core/git-prune", + "usr/libexec/git-core/git-update-server-info", + "usr/libexec/git-core/git-ls-tree", + "usr/libexec/git-core/git-credential-cache", + "usr/libexec/git-core/git-clone", + "usr/libexec/git-core/git-config", + "usr/libexec/git-core/git-replace", + "usr/libexec/git-core/mergetools/meld", + "usr/libexec/git-core/mergetools/examdiff", + "usr/libexec/git-core/mergetools/opendiff", + "usr/libexec/git-core/mergetools/gvimdiff", + "usr/libexec/git-core/mergetools/bc", + "usr/libexec/git-core/mergetools/araxis", + "usr/libexec/git-core/mergetools/emerge", + "usr/libexec/git-core/mergetools/vimdiff", + "usr/libexec/git-core/mergetools/vimdiff3", + "usr/libexec/git-core/mergetools/tkdiff", + "usr/libexec/git-core/mergetools/codecompare", + "usr/libexec/git-core/mergetools/kdiff3", + "usr/libexec/git-core/mergetools/vimdiff2", + "usr/libexec/git-core/mergetools/kompare", + "usr/libexec/git-core/mergetools/ecmerge", + "usr/libexec/git-core/mergetools/diffmerge", + "usr/libexec/git-core/mergetools/xxdiff", + "usr/libexec/git-core/mergetools/winmerge", + "usr/libexec/git-core/mergetools/gvimdiff3", + "usr/libexec/git-core/mergetools/tortoisemerge", + "usr/libexec/git-core/mergetools/diffuse", + "usr/libexec/git-core/mergetools/deltawalker", + "usr/libexec/git-core/mergetools/gvimdiff2", + "usr/libexec/git-core/mergetools/bc3", + "usr/bin/git", + "usr/bin/git-upload-pack", + "usr/bin/git-receive-pack", + "usr/bin/git-upload-archive", + "usr/bin/git-shell", + "usr/share/git-core/templates/description", + "usr/share/git-core/templates/info/exclude", + "usr/share/git-core/templates/hooks/pre-push.sample", + "usr/share/git-core/templates/hooks/commit-msg.sample", + "usr/share/git-core/templates/hooks/prepare-commit-msg.sample", + "usr/share/git-core/templates/hooks/pre-applypatch.sample", + "usr/share/git-core/templates/hooks/pre-receive.sample", + "usr/share/git-core/templates/hooks/update.sample", + "usr/share/git-core/templates/hooks/pre-commit.sample", + "usr/share/git-core/templates/hooks/pre-rebase.sample", + "usr/share/git-core/templates/hooks/applypatch-msg.sample", + "usr/share/git-core/templates/hooks/post-update.sample" + ] + }, + { + "ID": "libbz2@1.0.6-r6", + "Name": "libbz2", + "Identifier": { + "PURL": "pkg:apk/alpine/libbz2@1.0.6-r6?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.0.6-r6", + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.6-r6", + "Licenses": [ + "BSD-3-Clause" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:132242e396d99c2d06ee14c7ea4c3f3d02be102a", + "InstalledFiles": [ + "usr/lib/libbz2.so.1.0.6", + "usr/lib/libbz2.so.1" + ] + }, + { + "ID": "libc-utils@0.7.1-r0", + "Name": "libc-utils", + "Identifier": { + "PURL": "pkg:apk/alpine/libc-utils@0.7.1-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "0.7.1-r0", + "Arch": "x86_64", + "SrcName": "libc-dev", + "SrcVersion": "0.7.1-r0", + "Licenses": [ + "BSD-3-Clause" + ], + "DependsOn": [ + "musl-utils@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:b149e07fcf3cd345e38023edca9b0c9ce7e821bc" + }, + { + "ID": "libcurl@7.61.1-r0", + "Name": "libcurl", + "Identifier": { + "PURL": "pkg:apk/alpine/libcurl@7.61.1-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "7.61.1-r0", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "7.61.1-r0", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "ca-certificates@20171114-r0", + "libressl2.6-libcrypto@2.6.5-r0", + "libressl2.6-libssl@2.6.5-r0", + "libssh2@1.8.0-r2", + "musl@1.1.18-r3", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:3d6152f6ac208640f9fb494d1c379fe508db1fc5754cd08fefec200bddd13e0e", + "DiffID": "sha256:6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013" + }, + "Digest": "sha1:3dbd4ffbb43027efba686b75e4418d7e94ad8b46", + "InstalledFiles": [ + "usr/lib/libcurl.so.4", + "usr/lib/libcurl.so.4.5.0" + ] + }, + { + "ID": "libedit@20170329.3.1-r3", + "Name": "libedit", + "Identifier": { + "PURL": "pkg:apk/alpine/libedit@20170329.3.1-r3?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "20170329.3.1-r3", + "Arch": "x86_64", + "SrcName": "libedit", + "SrcVersion": "20170329.3.1-r3", + "Licenses": [ + "BSD-3-Clause" + ], + "DependsOn": [ + "musl@1.1.18-r3", + "ncurses-libs@6.0_p20171125-r1" + ], + "Layer": { + "Digest": "sha256:3d6152f6ac208640f9fb494d1c379fe508db1fc5754cd08fefec200bddd13e0e", + "DiffID": "sha256:6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013" + }, + "Digest": "sha1:4d393236e43554c60c5aaaffbcca394f45adcfb8", + "InstalledFiles": [ + "usr/lib/libedit.so.0.0.56", + "usr/lib/libedit.so.0" + ] + }, + { + "ID": "libffi@3.2.1-r4", + "Name": "libffi", + "Identifier": { + "PURL": "pkg:apk/alpine/libffi@3.2.1-r4?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "3.2.1-r4", + "Arch": "x86_64", + "SrcName": "libffi", + "SrcVersion": "3.2.1-r4", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:9b28ec06ab670657cd47582bf4fe4b803cd93e28", + "InstalledFiles": [ + "usr/lib/libffi.so.6.0.4", + "usr/lib/libffi.so.6" + ] + }, + { + "ID": "libressl@2.6.5-r0", + "Name": "libressl", + "Identifier": { + "PURL": "pkg:apk/alpine/libressl@2.6.5-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.6.5-r0", + "Arch": "x86_64", + "SrcName": "libressl", + "SrcVersion": "2.6.5-r0", + "Licenses": [ + "custom" + ], + "DependsOn": [ + "libressl2.6-libcrypto@2.6.5-r0", + "libressl2.6-libssl@2.6.5-r0", + "libressl2.6-libtls@2.6.5-r0", + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:88777455d910410652665cec0149a02db3584d6dc26e306788a3532d480b00ae", + "DiffID": "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33" + }, + "Digest": "sha1:684940a8557dc097623cdc6a551b2baad7a08b06", + "InstalledFiles": [ + "usr/bin/openssl", + "usr/bin/ocspcheck" + ] + }, + { + "ID": "libressl2.6-libcrypto@2.6.5-r0", + "Name": "libressl2.6-libcrypto", + "Identifier": { + "PURL": "pkg:apk/alpine/libressl2.6-libcrypto@2.6.5-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.6.5-r0", + "Arch": "x86_64", + "SrcName": "libressl", + "SrcVersion": "2.6.5-r0", + "Licenses": [ + "custom" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:2718dc7d5a9a34ac650614600e16118e466317f7", + "InstalledFiles": [ + "etc/ssl/cert.pem", + "etc/ssl/x509v3.cnf", + "etc/ssl/openssl.cnf", + "lib/libcrypto.so.42", + "lib/libcrypto.so.42.0.0", + "usr/lib/libcrypto.so.42", + "usr/lib/libcrypto.so.42.0.0" + ] + }, + { + "ID": "libressl2.6-libssl@2.6.5-r0", + "Name": "libressl2.6-libssl", + "Identifier": { + "PURL": "pkg:apk/alpine/libressl2.6-libssl@2.6.5-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.6.5-r0", + "Arch": "x86_64", + "SrcName": "libressl", + "SrcVersion": "2.6.5-r0", + "Licenses": [ + "custom" + ], + "DependsOn": [ + "libressl2.6-libcrypto@2.6.5-r0", + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:df902e38eab7ecbdf3d143a56c3caa511522f524", + "InstalledFiles": [ + "lib/libssl.so.44.0.1", + "lib/libssl.so.44", + "usr/lib/libssl.so.44.0.1", + "usr/lib/libssl.so.44" + ] + }, + { + "ID": "libressl2.6-libtls@2.6.5-r0", + "Name": "libressl2.6-libtls", + "Identifier": { + "PURL": "pkg:apk/alpine/libressl2.6-libtls@2.6.5-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.6.5-r0", + "Arch": "x86_64", + "SrcName": "libressl", + "SrcVersion": "2.6.5-r0", + "Licenses": [ + "custom" + ], + "DependsOn": [ + "libressl2.6-libcrypto@2.6.5-r0", + "libressl2.6-libssl@2.6.5-r0", + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:7740cbc6dee93683e6893edbe89715a2a99aa9b4", + "InstalledFiles": [ + "lib/libtls.so.16.0.1", + "lib/libtls.so.16", + "usr/lib/libtls.so.16.0.1", + "usr/lib/libtls.so.16" + ] + }, + { + "ID": "libsasl@2.1.26-r11", + "Name": "libsasl", + "Identifier": { + "PURL": "pkg:apk/alpine/libsasl@2.1.26-r11?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.1.26-r11", + "Arch": "x86_64", + "SrcName": "cyrus-sasl", + "SrcVersion": "2.1.26-r11", + "Licenses": [ + "custom" + ], + "DependsOn": [ + "db@5.3.28-r0", + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:1f14d6b5e172b3b4643106f49a4dc85c9c517060", + "InstalledFiles": [ + "usr/lib/libsasl2.so.3.0.0", + "usr/lib/libsasl2.so.3", + "usr/lib/sasl2/liblogin.so", + "usr/lib/sasl2/liblogin.so.3", + "usr/lib/sasl2/libsasldb.so.3", + "usr/lib/sasl2/libsasldb.so.3.0.0", + "usr/lib/sasl2/liblogin.so.3.0.0", + "usr/lib/sasl2/libplain.so", + "usr/lib/sasl2/libplain.so.3", + "usr/lib/sasl2/libplain.so.3.0.0", + "usr/lib/sasl2/libsasldb.so" + ] + }, + { + "ID": "libsodium@1.0.15-r0", + "Name": "libsodium", + "Identifier": { + "PURL": "pkg:apk/alpine/libsodium@1.0.15-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.0.15-r0", + "Arch": "x86_64", + "SrcName": "libsodium", + "SrcVersion": "1.0.15-r0", + "Licenses": [ + "ISC" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:3d6152f6ac208640f9fb494d1c379fe508db1fc5754cd08fefec200bddd13e0e", + "DiffID": "sha256:6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013" + }, + "Digest": "sha1:7fbed17399609f0613bede5c686d7a02169da7ee", + "InstalledFiles": [ + "usr/lib/libsodium.so.23", + "usr/lib/libsodium.so.23.0.0" + ] + }, + { + "ID": "libssh2@1.8.0-r2", + "Name": "libssh2", + "Identifier": { + "PURL": "pkg:apk/alpine/libssh2@1.8.0-r2?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.8.0-r2", + "Arch": "x86_64", + "SrcName": "libssh2", + "SrcVersion": "1.8.0-r2", + "Licenses": [ + "BSD-3-Clause" + ], + "DependsOn": [ + "libressl2.6-libcrypto@2.6.5-r0", + "musl@1.1.18-r3", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:88777455d910410652665cec0149a02db3584d6dc26e306788a3532d480b00ae", + "DiffID": "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33" + }, + "Digest": "sha1:953a992e9e05e10134651f8826403854240e739b", + "InstalledFiles": [ + "usr/lib/libssh2.so.1", + "usr/lib/libssh2.so.1.0.1" + ] + }, + { + "ID": "libuuid@2.31-r0", + "Name": "libuuid", + "Identifier": { + "PURL": "pkg:apk/alpine/libuuid@2.31-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.31-r0", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.31-r0", + "Licenses": [ + "GPL-2.0", + "GPL-2.0", + "LGPL-2.0", + "BSD-3-Clause", + "Public", + "Domain" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:d95c3db24000cb62331d3892fc1e44292799b4b2", + "InstalledFiles": [ + "lib/libuuid.so.1.3.0", + "lib/libuuid.so.1" + ] + }, + { + "ID": "libxml2@2.9.7-r0", + "Name": "libxml2", + "Identifier": { + "PURL": "pkg:apk/alpine/libxml2@2.9.7-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.9.7-r0", + "Arch": "x86_64", + "SrcName": "libxml2", + "SrcVersion": "2.9.7-r0", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "musl@1.1.18-r3", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:3d6152f6ac208640f9fb494d1c379fe508db1fc5754cd08fefec200bddd13e0e", + "DiffID": "sha256:6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013" + }, + "Digest": "sha1:990af059bbdb87a870c12f0959bb1b9b91bd57ba", + "InstalledFiles": [ + "usr/lib/libxml2.so.2.9.7", + "usr/lib/libxml2.so.2" + ] + }, + { + "ID": "mercurial@4.5.2-r0", + "Name": "mercurial", + "Identifier": { + "PURL": "pkg:apk/alpine/mercurial@4.5.2-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "4.5.2-r0", + "Arch": "x86_64", + "SrcName": "mercurial", + "SrcVersion": "4.5.2-r0", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "musl@1.1.18-r3", + "python2@2.7.15-r2" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:42cc6d472044ac2936f00c412af0ae4e08fd599e", + "InstalledFiles": [ + "usr/bin/hgk", + "usr/bin/hg", + "usr/bin/hgeditor", + "usr/lib/python2.7/site-packages/mercurial-4.5.2-py2.7.egg-info", + "usr/lib/python2.7/site-packages/hgext/journal.py", + "usr/lib/python2.7/site-packages/hgext/acl.pyc", + "usr/lib/python2.7/site-packages/hgext/clonebundles.py", + "usr/lib/python2.7/site-packages/hgext/releasenotes.py", + "usr/lib/python2.7/site-packages/hgext/__init__.pyc", + "usr/lib/python2.7/site-packages/hgext/rebase.pyc", + "usr/lib/python2.7/site-packages/hgext/churn.pyc", + "usr/lib/python2.7/site-packages/hgext/bugzilla.pyc", + "usr/lib/python2.7/site-packages/hgext/uncommit.pyc", + "usr/lib/python2.7/site-packages/hgext/censor.py", + "usr/lib/python2.7/site-packages/hgext/mq.pyc", + "usr/lib/python2.7/site-packages/hgext/notify.py", + "usr/lib/python2.7/site-packages/hgext/histedit.py", + "usr/lib/python2.7/site-packages/hgext/blackbox.py", + "usr/lib/python2.7/site-packages/hgext/hgk.py", + "usr/lib/python2.7/site-packages/hgext/children.pyc", + "usr/lib/python2.7/site-packages/hgext/acl.py", + "usr/lib/python2.7/site-packages/hgext/githelp.pyc", + "usr/lib/python2.7/site-packages/hgext/censor.pyc", + "usr/lib/python2.7/site-packages/hgext/record.pyc", + "usr/lib/python2.7/site-packages/hgext/pager.pyc", + "usr/lib/python2.7/site-packages/hgext/strip.py", + "usr/lib/python2.7/site-packages/hgext/eol.pyc", + "usr/lib/python2.7/site-packages/hgext/purge.pyc", + "usr/lib/python2.7/site-packages/hgext/patchbomb.py", + "usr/lib/python2.7/site-packages/hgext/hgk.pyc", + "usr/lib/python2.7/site-packages/hgext/split.py", + "usr/lib/python2.7/site-packages/hgext/patchbomb.pyc", + "usr/lib/python2.7/site-packages/hgext/sparse.py", + "usr/lib/python2.7/site-packages/hgext/__init__.py", + "usr/lib/python2.7/site-packages/hgext/rebase.py", + "usr/lib/python2.7/site-packages/hgext/win32text.py", + "usr/lib/python2.7/site-packages/hgext/factotum.py", + "usr/lib/python2.7/site-packages/hgext/clonebundles.pyc", + "usr/lib/python2.7/site-packages/hgext/split.pyc", + "usr/lib/python2.7/site-packages/hgext/journal.pyc", + "usr/lib/python2.7/site-packages/hgext/relink.pyc", + "usr/lib/python2.7/site-packages/hgext/gpg.py", + "usr/lib/python2.7/site-packages/hgext/schemes.pyc", + "usr/lib/python2.7/site-packages/hgext/logtoprocess.py", + "usr/lib/python2.7/site-packages/hgext/extdiff.pyc", + "usr/lib/python2.7/site-packages/hgext/children.py", + "usr/lib/python2.7/site-packages/hgext/fetch.pyc", + "usr/lib/python2.7/site-packages/hgext/churn.py", + "usr/lib/python2.7/site-packages/hgext/share.py", + "usr/lib/python2.7/site-packages/hgext/win32text.pyc", + "usr/lib/python2.7/site-packages/hgext/notify.pyc", + "usr/lib/python2.7/site-packages/hgext/relink.py", + "usr/lib/python2.7/site-packages/hgext/keyword.py", + "usr/lib/python2.7/site-packages/hgext/factotum.pyc", + "usr/lib/python2.7/site-packages/hgext/show.pyc", + "usr/lib/python2.7/site-packages/hgext/shelve.pyc", + "usr/lib/python2.7/site-packages/hgext/record.py", + "usr/lib/python2.7/site-packages/hgext/extdiff.py", + "usr/lib/python2.7/site-packages/hgext/keyword.pyc", + "usr/lib/python2.7/site-packages/hgext/automv.pyc", + "usr/lib/python2.7/site-packages/hgext/eol.py", + "usr/lib/python2.7/site-packages/hgext/transplant.pyc", + "usr/lib/python2.7/site-packages/hgext/sparse.pyc", + "usr/lib/python2.7/site-packages/hgext/schemes.py", + "usr/lib/python2.7/site-packages/hgext/purge.py", + "usr/lib/python2.7/site-packages/hgext/releasenotes.pyc", + "usr/lib/python2.7/site-packages/hgext/fetch.py", + "usr/lib/python2.7/site-packages/hgext/commitextras.pyc", + "usr/lib/python2.7/site-packages/hgext/graphlog.py", + "usr/lib/python2.7/site-packages/hgext/gpg.pyc", + "usr/lib/python2.7/site-packages/hgext/uncommit.py", + "usr/lib/python2.7/site-packages/hgext/mq.py", + "usr/lib/python2.7/site-packages/hgext/show.py", + "usr/lib/python2.7/site-packages/hgext/graphlog.pyc", + "usr/lib/python2.7/site-packages/hgext/amend.pyc", + "usr/lib/python2.7/site-packages/hgext/pager.py", + "usr/lib/python2.7/site-packages/hgext/bugzilla.py", + "usr/lib/python2.7/site-packages/hgext/automv.py", + "usr/lib/python2.7/site-packages/hgext/win32mbcs.py", + "usr/lib/python2.7/site-packages/hgext/win32mbcs.pyc", + "usr/lib/python2.7/site-packages/hgext/blackbox.pyc", + "usr/lib/python2.7/site-packages/hgext/amend.py", + "usr/lib/python2.7/site-packages/hgext/transplant.py", + "usr/lib/python2.7/site-packages/hgext/commitextras.py", + "usr/lib/python2.7/site-packages/hgext/githelp.py", + "usr/lib/python2.7/site-packages/hgext/histedit.pyc", + "usr/lib/python2.7/site-packages/hgext/logtoprocess.pyc", + "usr/lib/python2.7/site-packages/hgext/shelve.py", + "usr/lib/python2.7/site-packages/hgext/strip.pyc", + "usr/lib/python2.7/site-packages/hgext/share.pyc", + "usr/lib/python2.7/site-packages/hgext/zeroconf/__init__.pyc", + "usr/lib/python2.7/site-packages/hgext/zeroconf/Zeroconf.py", + "usr/lib/python2.7/site-packages/hgext/zeroconf/__init__.py", + "usr/lib/python2.7/site-packages/hgext/zeroconf/Zeroconf.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/__init__.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/wirestore.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/storefactory.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/localstore.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/lfutil.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/proto.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/lfutil.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/proto.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/__init__.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/overrides.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/reposetup.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/lfcommands.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/overrides.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/uisetup.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/basestore.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/basestore.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/wirestore.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/remotestore.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/storefactory.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/remotestore.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/localstore.py", + "usr/lib/python2.7/site-packages/hgext/largefiles/uisetup.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/reposetup.pyc", + "usr/lib/python2.7/site-packages/hgext/largefiles/lfcommands.py", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/__init__.pyc", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/watchmanclient.pyc", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/__init__.py", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/watchmanclient.py", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/state.py", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/state.pyc", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/__init__.pyc", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/encoding.pyc", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/load.py", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/pybser.pyc", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/__init__.py", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/compat.py", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/pybser.py", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/capabilities.pyc", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/capabilities.py", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/load.pyc", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/compat.pyc", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/bser.so", + "usr/lib/python2.7/site-packages/hgext/fsmonitor/pywatchman/encoding.py", + "usr/lib/python2.7/site-packages/hgext/convert/monotone.py", + "usr/lib/python2.7/site-packages/hgext/convert/__init__.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/subversion.py", + "usr/lib/python2.7/site-packages/hgext/convert/common.py", + "usr/lib/python2.7/site-packages/hgext/convert/hg.py", + "usr/lib/python2.7/site-packages/hgext/convert/darcs.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/gnuarch.py", + "usr/lib/python2.7/site-packages/hgext/convert/cvs.py", + "usr/lib/python2.7/site-packages/hgext/convert/bzr.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/monotone.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/convcmd.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/convcmd.py", + "usr/lib/python2.7/site-packages/hgext/convert/__init__.py", + "usr/lib/python2.7/site-packages/hgext/convert/darcs.py", + "usr/lib/python2.7/site-packages/hgext/convert/cvs.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/subversion.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/git.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/transport.py", + "usr/lib/python2.7/site-packages/hgext/convert/cvsps.py", + "usr/lib/python2.7/site-packages/hgext/convert/git.py", + "usr/lib/python2.7/site-packages/hgext/convert/filemap.py", + "usr/lib/python2.7/site-packages/hgext/convert/p4.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/transport.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/p4.py", + "usr/lib/python2.7/site-packages/hgext/convert/common.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/hg.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/filemap.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/bzr.py", + "usr/lib/python2.7/site-packages/hgext/convert/gnuarch.pyc", + "usr/lib/python2.7/site-packages/hgext/convert/cvsps.pyc", + "usr/lib/python2.7/site-packages/hgext/lfs/__init__.pyc", + "usr/lib/python2.7/site-packages/hgext/lfs/__init__.py", + "usr/lib/python2.7/site-packages/hgext/lfs/pointer.py", + "usr/lib/python2.7/site-packages/hgext/lfs/wrapper.py", + "usr/lib/python2.7/site-packages/hgext/lfs/blobstore.py", + "usr/lib/python2.7/site-packages/hgext/lfs/blobstore.pyc", + "usr/lib/python2.7/site-packages/hgext/lfs/wrapper.pyc", + "usr/lib/python2.7/site-packages/hgext/lfs/pointer.pyc", + "usr/lib/python2.7/site-packages/hgext/highlight/__init__.pyc", + "usr/lib/python2.7/site-packages/hgext/highlight/highlight.py", + "usr/lib/python2.7/site-packages/hgext/highlight/__init__.py", + "usr/lib/python2.7/site-packages/hgext/highlight/highlight.pyc", + "usr/lib/python2.7/site-packages/mercurial/lock.py", + "usr/lib/python2.7/site-packages/mercurial/hook.pyc", + "usr/lib/python2.7/site-packages/mercurial/progress.py", + "usr/lib/python2.7/site-packages/mercurial/repoview.py", + "usr/lib/python2.7/site-packages/mercurial/manifest.py", + "usr/lib/python2.7/site-packages/mercurial/sslutil.py", + "usr/lib/python2.7/site-packages/mercurial/revset.pyc", + "usr/lib/python2.7/site-packages/mercurial/policy.pyc", + "usr/lib/python2.7/site-packages/mercurial/sshpeer.py", + "usr/lib/python2.7/site-packages/mercurial/byterange.pyc", + "usr/lib/python2.7/site-packages/mercurial/__init__.pyc", + "usr/lib/python2.7/site-packages/mercurial/streamclone.pyc", + "usr/lib/python2.7/site-packages/mercurial/color.py", + "usr/lib/python2.7/site-packages/mercurial/mergeutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/scmposix.pyc", + "usr/lib/python2.7/site-packages/mercurial/treediscovery.pyc", + "usr/lib/python2.7/site-packages/mercurial/dirstate.py", + "usr/lib/python2.7/site-packages/mercurial/manifest.pyc", + "usr/lib/python2.7/site-packages/mercurial/profiling.pyc", + "usr/lib/python2.7/site-packages/mercurial/upgrade.py", + "usr/lib/python2.7/site-packages/mercurial/context.py", + "usr/lib/python2.7/site-packages/mercurial/statichttprepo.pyc", + "usr/lib/python2.7/site-packages/mercurial/tags.pyc", + "usr/lib/python2.7/site-packages/mercurial/help.py", + "usr/lib/python2.7/site-packages/mercurial/server.pyc", + "usr/lib/python2.7/site-packages/mercurial/encoding.pyc", + "usr/lib/python2.7/site-packages/mercurial/dirstateguard.pyc", + "usr/lib/python2.7/site-packages/mercurial/minifileset.py", + "usr/lib/python2.7/site-packages/mercurial/mdiff.pyc", + "usr/lib/python2.7/site-packages/mercurial/discovery.py", + "usr/lib/python2.7/site-packages/mercurial/hg.py", + "usr/lib/python2.7/site-packages/mercurial/pathutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/store.pyc", + "usr/lib/python2.7/site-packages/mercurial/archival.py", + "usr/lib/python2.7/site-packages/mercurial/revsetlang.py", + "usr/lib/python2.7/site-packages/mercurial/win32.py", + "usr/lib/python2.7/site-packages/mercurial/__modulepolicy__.pyc", + "usr/lib/python2.7/site-packages/mercurial/mail.py", + "usr/lib/python2.7/site-packages/mercurial/merge.py", + "usr/lib/python2.7/site-packages/mercurial/treediscovery.py", + "usr/lib/python2.7/site-packages/mercurial/pvec.py", + "usr/lib/python2.7/site-packages/mercurial/progress.pyc", + "usr/lib/python2.7/site-packages/mercurial/dagparser.pyc", + "usr/lib/python2.7/site-packages/mercurial/copies.py", + "usr/lib/python2.7/site-packages/mercurial/lsprofcalltree.py", + "usr/lib/python2.7/site-packages/mercurial/vfs.py", + "usr/lib/python2.7/site-packages/mercurial/unionrepo.py", + "usr/lib/python2.7/site-packages/mercurial/repair.py", + "usr/lib/python2.7/site-packages/mercurial/cmdutil.py", + "usr/lib/python2.7/site-packages/mercurial/upgrade.pyc", + "usr/lib/python2.7/site-packages/mercurial/simplemerge.pyc", + "usr/lib/python2.7/site-packages/mercurial/templatekw.pyc", + "usr/lib/python2.7/site-packages/mercurial/localrepo.py", + "usr/lib/python2.7/site-packages/mercurial/merge.pyc", + "usr/lib/python2.7/site-packages/mercurial/pycompat.py", + "usr/lib/python2.7/site-packages/mercurial/rcutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/templatefilters.pyc", + "usr/lib/python2.7/site-packages/mercurial/dagutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/revset.py", + "usr/lib/python2.7/site-packages/mercurial/copies.pyc", + "usr/lib/python2.7/site-packages/mercurial/urllibcompat.py", + "usr/lib/python2.7/site-packages/mercurial/subrepo.pyc", + "usr/lib/python2.7/site-packages/mercurial/windows.py", + "usr/lib/python2.7/site-packages/mercurial/error.pyc", + "usr/lib/python2.7/site-packages/mercurial/transaction.py", + "usr/lib/python2.7/site-packages/mercurial/tagmerge.pyc", + "usr/lib/python2.7/site-packages/mercurial/logexchange.py", + "usr/lib/python2.7/site-packages/mercurial/repair.pyc", + "usr/lib/python2.7/site-packages/mercurial/rewriteutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/formatter.pyc", + "usr/lib/python2.7/site-packages/mercurial/dagutil.py", + "usr/lib/python2.7/site-packages/mercurial/dispatch.py", + "usr/lib/python2.7/site-packages/mercurial/sparse.py", + "usr/lib/python2.7/site-packages/mercurial/graphmod.pyc", + "usr/lib/python2.7/site-packages/mercurial/__init__.py", + "usr/lib/python2.7/site-packages/mercurial/pycompat.pyc", + "usr/lib/python2.7/site-packages/mercurial/fileset.py", + "usr/lib/python2.7/site-packages/mercurial/scmutil.py", + "usr/lib/python2.7/site-packages/mercurial/error.py", + "usr/lib/python2.7/site-packages/mercurial/repository.py", + "usr/lib/python2.7/site-packages/mercurial/obsutil.py", + "usr/lib/python2.7/site-packages/mercurial/lsprof.pyc", + "usr/lib/python2.7/site-packages/mercurial/subrepo.py", + "usr/lib/python2.7/site-packages/mercurial/dagop.pyc", + "usr/lib/python2.7/site-packages/mercurial/unionrepo.pyc", + "usr/lib/python2.7/site-packages/mercurial/bundle2.py", + "usr/lib/python2.7/site-packages/mercurial/server.py", + "usr/lib/python2.7/site-packages/mercurial/repoview.pyc", + "usr/lib/python2.7/site-packages/mercurial/util.py", + "usr/lib/python2.7/site-packages/mercurial/rcutil.py", + "usr/lib/python2.7/site-packages/mercurial/registrar.pyc", + "usr/lib/python2.7/site-packages/mercurial/crecord.pyc", + "usr/lib/python2.7/site-packages/mercurial/store.py", + "usr/lib/python2.7/site-packages/mercurial/phases.py", + "usr/lib/python2.7/site-packages/mercurial/profiling.py", + "usr/lib/python2.7/site-packages/mercurial/txnutil.py", + "usr/lib/python2.7/site-packages/mercurial/httpconnection.pyc", + "usr/lib/python2.7/site-packages/mercurial/worker.py", + "usr/lib/python2.7/site-packages/mercurial/revlog.py", + "usr/lib/python2.7/site-packages/mercurial/lsprof.py", + "usr/lib/python2.7/site-packages/mercurial/ui.pyc", + "usr/lib/python2.7/site-packages/mercurial/streamclone.py", + "usr/lib/python2.7/site-packages/mercurial/crecord.py", + "usr/lib/python2.7/site-packages/mercurial/filelog.py", + "usr/lib/python2.7/site-packages/mercurial/repository.pyc", + "usr/lib/python2.7/site-packages/mercurial/__version__.py", + "usr/lib/python2.7/site-packages/mercurial/registrar.py", + "usr/lib/python2.7/site-packages/mercurial/patch.py", + "usr/lib/python2.7/site-packages/mercurial/pvec.pyc", + "usr/lib/python2.7/site-packages/mercurial/simplemerge.py", + "usr/lib/python2.7/site-packages/mercurial/setdiscovery.pyc", + "usr/lib/python2.7/site-packages/mercurial/commands.py", + "usr/lib/python2.7/site-packages/mercurial/revsetlang.pyc", + "usr/lib/python2.7/site-packages/mercurial/scmutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/ancestor.pyc", + "usr/lib/python2.7/site-packages/mercurial/hook.py", + "usr/lib/python2.7/site-packages/mercurial/node.py", + "usr/lib/python2.7/site-packages/mercurial/chgserver.pyc", + "usr/lib/python2.7/site-packages/mercurial/wireproto.py", + "usr/lib/python2.7/site-packages/mercurial/namespaces.pyc", + "usr/lib/python2.7/site-packages/mercurial/i18n.pyc", + "usr/lib/python2.7/site-packages/mercurial/changegroup.pyc", + "usr/lib/python2.7/site-packages/mercurial/cacheutil.py", + "usr/lib/python2.7/site-packages/mercurial/hbisect.py", + "usr/lib/python2.7/site-packages/mercurial/revlog.pyc", + "usr/lib/python2.7/site-packages/mercurial/sshserver.pyc", + "usr/lib/python2.7/site-packages/mercurial/lock.pyc", + "usr/lib/python2.7/site-packages/mercurial/worker.pyc", + "usr/lib/python2.7/site-packages/mercurial/config.py", + "usr/lib/python2.7/site-packages/mercurial/exchange.py", + "usr/lib/python2.7/site-packages/mercurial/tagmerge.py", + "usr/lib/python2.7/site-packages/mercurial/dummycert.pem", + "usr/lib/python2.7/site-packages/mercurial/ancestor.py", + "usr/lib/python2.7/site-packages/mercurial/url.pyc", + "usr/lib/python2.7/site-packages/mercurial/graphmod.py", + "usr/lib/python2.7/site-packages/mercurial/peer.py", + "usr/lib/python2.7/site-packages/mercurial/wireproto.pyc", + "usr/lib/python2.7/site-packages/mercurial/configitems.py", + "usr/lib/python2.7/site-packages/mercurial/pushkey.pyc", + "usr/lib/python2.7/site-packages/mercurial/rewriteutil.py", + "usr/lib/python2.7/site-packages/mercurial/fileset.pyc", + "usr/lib/python2.7/site-packages/mercurial/similar.py", + "usr/lib/python2.7/site-packages/mercurial/obsutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/verify.pyc", + "usr/lib/python2.7/site-packages/mercurial/bookmarks.py", + "usr/lib/python2.7/site-packages/mercurial/setdiscovery.py", + "usr/lib/python2.7/site-packages/mercurial/smartset.pyc", + "usr/lib/python2.7/site-packages/mercurial/sshserver.py", + "usr/lib/python2.7/site-packages/mercurial/fancyopts.pyc", + "usr/lib/python2.7/site-packages/mercurial/commandserver.pyc", + "usr/lib/python2.7/site-packages/mercurial/debugcommands.py", + "usr/lib/python2.7/site-packages/mercurial/match.pyc", + "usr/lib/python2.7/site-packages/mercurial/minirst.py", + "usr/lib/python2.7/site-packages/mercurial/sparse.pyc", + "usr/lib/python2.7/site-packages/mercurial/mail.pyc", + "usr/lib/python2.7/site-packages/mercurial/filemerge.pyc", + "usr/lib/python2.7/site-packages/mercurial/util.pyc", + "usr/lib/python2.7/site-packages/mercurial/keepalive.py", + "usr/lib/python2.7/site-packages/mercurial/templatekw.py", + "usr/lib/python2.7/site-packages/mercurial/bundlerepo.py", + "usr/lib/python2.7/site-packages/mercurial/verify.py", + "usr/lib/python2.7/site-packages/mercurial/__version__.pyc", + "usr/lib/python2.7/site-packages/mercurial/bundlerepo.pyc", + "usr/lib/python2.7/site-packages/mercurial/url.py", + "usr/lib/python2.7/site-packages/mercurial/destutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/byterange.py", + "usr/lib/python2.7/site-packages/mercurial/logexchange.pyc", + "usr/lib/python2.7/site-packages/mercurial/httppeer.py", + "usr/lib/python2.7/site-packages/mercurial/scmwindows.pyc", + "usr/lib/python2.7/site-packages/mercurial/phases.pyc", + "usr/lib/python2.7/site-packages/mercurial/txnutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/destutil.py", + "usr/lib/python2.7/site-packages/mercurial/context.pyc", + "usr/lib/python2.7/site-packages/mercurial/minifileset.pyc", + "usr/lib/python2.7/site-packages/mercurial/templater.pyc", + "usr/lib/python2.7/site-packages/mercurial/mdiff.py", + "usr/lib/python2.7/site-packages/mercurial/dirstate.pyc", + "usr/lib/python2.7/site-packages/mercurial/extensions.py", + "usr/lib/python2.7/site-packages/mercurial/commands.pyc", + "usr/lib/python2.7/site-packages/mercurial/httpconnection.py", + "usr/lib/python2.7/site-packages/mercurial/dagop.py", + "usr/lib/python2.7/site-packages/mercurial/localrepo.pyc", + "usr/lib/python2.7/site-packages/mercurial/help.pyc", + "usr/lib/python2.7/site-packages/mercurial/chgserver.py", + "usr/lib/python2.7/site-packages/mercurial/keepalive.pyc", + "usr/lib/python2.7/site-packages/mercurial/changegroup.py", + "usr/lib/python2.7/site-packages/mercurial/bookmarks.pyc", + "usr/lib/python2.7/site-packages/mercurial/branchmap.pyc", + "usr/lib/python2.7/site-packages/mercurial/color.pyc", + "usr/lib/python2.7/site-packages/mercurial/extensions.pyc", + "usr/lib/python2.7/site-packages/mercurial/vfs.pyc", + "usr/lib/python2.7/site-packages/mercurial/sslutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/dirstateguard.py", + "usr/lib/python2.7/site-packages/mercurial/dispatch.pyc", + "usr/lib/python2.7/site-packages/mercurial/dagparser.py", + "usr/lib/python2.7/site-packages/mercurial/cmdutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/parser.py", + "usr/lib/python2.7/site-packages/mercurial/windows.pyc", + "usr/lib/python2.7/site-packages/mercurial/peer.pyc", + "usr/lib/python2.7/site-packages/mercurial/discovery.pyc", + "usr/lib/python2.7/site-packages/mercurial/statprof.pyc", + "usr/lib/python2.7/site-packages/mercurial/fancyopts.py", + "usr/lib/python2.7/site-packages/mercurial/scmposix.py", + "usr/lib/python2.7/site-packages/mercurial/branchmap.py", + "usr/lib/python2.7/site-packages/mercurial/configitems.pyc", + "usr/lib/python2.7/site-packages/mercurial/win32.pyc", + "usr/lib/python2.7/site-packages/mercurial/filemerge.py", + "usr/lib/python2.7/site-packages/mercurial/similar.pyc", + "usr/lib/python2.7/site-packages/mercurial/obsolete.py", + "usr/lib/python2.7/site-packages/mercurial/encoding.py", + "usr/lib/python2.7/site-packages/mercurial/debugcommands.pyc", + "usr/lib/python2.7/site-packages/mercurial/commandserver.py", + "usr/lib/python2.7/site-packages/mercurial/transaction.pyc", + "usr/lib/python2.7/site-packages/mercurial/changelog.py", + "usr/lib/python2.7/site-packages/mercurial/httppeer.pyc", + "usr/lib/python2.7/site-packages/mercurial/scmwindows.py", + "usr/lib/python2.7/site-packages/mercurial/mergeutil.py", + "usr/lib/python2.7/site-packages/mercurial/patch.pyc", + "usr/lib/python2.7/site-packages/mercurial/hg.pyc", + "usr/lib/python2.7/site-packages/mercurial/urllibcompat.pyc", + "usr/lib/python2.7/site-packages/mercurial/sshpeer.pyc", + "usr/lib/python2.7/site-packages/mercurial/namespaces.py", + "usr/lib/python2.7/site-packages/mercurial/minirst.pyc", + "usr/lib/python2.7/site-packages/mercurial/hbisect.pyc", + "usr/lib/python2.7/site-packages/mercurial/changelog.pyc", + "usr/lib/python2.7/site-packages/mercurial/exchange.pyc", + "usr/lib/python2.7/site-packages/mercurial/posix.py", + "usr/lib/python2.7/site-packages/mercurial/i18n.py", + "usr/lib/python2.7/site-packages/mercurial/tags.py", + "usr/lib/python2.7/site-packages/mercurial/filelog.pyc", + "usr/lib/python2.7/site-packages/mercurial/archival.pyc", + "usr/lib/python2.7/site-packages/mercurial/pushkey.py", + "usr/lib/python2.7/site-packages/mercurial/zstd.so", + "usr/lib/python2.7/site-packages/mercurial/templatefilters.py", + "usr/lib/python2.7/site-packages/mercurial/__modulepolicy__.py", + "usr/lib/python2.7/site-packages/mercurial/ui.py", + "usr/lib/python2.7/site-packages/mercurial/statprof.py", + "usr/lib/python2.7/site-packages/mercurial/match.py", + "usr/lib/python2.7/site-packages/mercurial/statichttprepo.py", + "usr/lib/python2.7/site-packages/mercurial/policy.py", + "usr/lib/python2.7/site-packages/mercurial/parser.pyc", + "usr/lib/python2.7/site-packages/mercurial/lsprofcalltree.pyc", + "usr/lib/python2.7/site-packages/mercurial/templater.py", + "usr/lib/python2.7/site-packages/mercurial/pathutil.py", + "usr/lib/python2.7/site-packages/mercurial/node.pyc", + "usr/lib/python2.7/site-packages/mercurial/formatter.py", + "usr/lib/python2.7/site-packages/mercurial/posix.pyc", + "usr/lib/python2.7/site-packages/mercurial/obsolete.pyc", + "usr/lib/python2.7/site-packages/mercurial/config.pyc", + "usr/lib/python2.7/site-packages/mercurial/bundle2.pyc", + "usr/lib/python2.7/site-packages/mercurial/cacheutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/smartset.py", + "usr/lib/python2.7/site-packages/mercurial/cffi/osutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/cffi/__init__.pyc", + "usr/lib/python2.7/site-packages/mercurial/cffi/mpatchbuild.pyc", + "usr/lib/python2.7/site-packages/mercurial/cffi/mpatch.pyc", + "usr/lib/python2.7/site-packages/mercurial/cffi/osutilbuild.py", + "usr/lib/python2.7/site-packages/mercurial/cffi/bdiffbuild.pyc", + "usr/lib/python2.7/site-packages/mercurial/cffi/bdiff.py", + "usr/lib/python2.7/site-packages/mercurial/cffi/__init__.py", + "usr/lib/python2.7/site-packages/mercurial/cffi/mpatchbuild.py", + "usr/lib/python2.7/site-packages/mercurial/cffi/osutilbuild.pyc", + "usr/lib/python2.7/site-packages/mercurial/cffi/bdiff.pyc", + "usr/lib/python2.7/site-packages/mercurial/cffi/osutil.py", + "usr/lib/python2.7/site-packages/mercurial/cffi/bdiffbuild.py", + "usr/lib/python2.7/site-packages/mercurial/cffi/mpatch.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/__init__.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/__init__.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/selectors2.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/selectors2.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/__init__.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/_compat.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/_make.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/_config.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/exceptions.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/__init__.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/_make.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/converters.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/_config.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/_compat.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/exceptions.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/filters.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/validators.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/filters.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/validators.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/_funcs.pyc", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/converters.py", + "usr/lib/python2.7/site-packages/mercurial/thirdparty/attr/_funcs.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/__init__.pyc", + "usr/lib/python2.7/site-packages/mercurial/hgweb/server.pyc", + "usr/lib/python2.7/site-packages/mercurial/hgweb/hgweb_mod.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/request.pyc", + "usr/lib/python2.7/site-packages/mercurial/hgweb/common.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/protocol.pyc", + "usr/lib/python2.7/site-packages/mercurial/hgweb/webcommands.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/webcommands.pyc", + "usr/lib/python2.7/site-packages/mercurial/hgweb/__init__.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/server.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/protocol.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/wsgicgi.pyc", + "usr/lib/python2.7/site-packages/mercurial/hgweb/request.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/hgwebdir_mod.pyc", + "usr/lib/python2.7/site-packages/mercurial/hgweb/webutil.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/webutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/hgweb/common.pyc", + "usr/lib/python2.7/site-packages/mercurial/hgweb/wsgicgi.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/hgwebdir_mod.py", + "usr/lib/python2.7/site-packages/mercurial/hgweb/hgweb_mod.pyc", + "usr/lib/python2.7/site-packages/mercurial/cext/__init__.pyc", + "usr/lib/python2.7/site-packages/mercurial/cext/base85.so", + "usr/lib/python2.7/site-packages/mercurial/cext/__init__.py", + "usr/lib/python2.7/site-packages/mercurial/cext/mpatch.so", + "usr/lib/python2.7/site-packages/mercurial/cext/diffhelpers.so", + "usr/lib/python2.7/site-packages/mercurial/cext/bdiff.so", + "usr/lib/python2.7/site-packages/mercurial/cext/parsers.so", + "usr/lib/python2.7/site-packages/mercurial/cext/osutil.so", + "usr/lib/python2.7/site-packages/mercurial/pure/osutil.pyc", + "usr/lib/python2.7/site-packages/mercurial/pure/__init__.pyc", + "usr/lib/python2.7/site-packages/mercurial/pure/diffhelpers.py", + "usr/lib/python2.7/site-packages/mercurial/pure/diffhelpers.pyc", + "usr/lib/python2.7/site-packages/mercurial/pure/charencode.py", + "usr/lib/python2.7/site-packages/mercurial/pure/mpatch.pyc", + "usr/lib/python2.7/site-packages/mercurial/pure/charencode.pyc", + "usr/lib/python2.7/site-packages/mercurial/pure/bdiff.py", + "usr/lib/python2.7/site-packages/mercurial/pure/parsers.py", + "usr/lib/python2.7/site-packages/mercurial/pure/__init__.py", + "usr/lib/python2.7/site-packages/mercurial/pure/base85.pyc", + "usr/lib/python2.7/site-packages/mercurial/pure/parsers.pyc", + "usr/lib/python2.7/site-packages/mercurial/pure/bdiff.pyc", + "usr/lib/python2.7/site-packages/mercurial/pure/osutil.py", + "usr/lib/python2.7/site-packages/mercurial/pure/base85.py", + "usr/lib/python2.7/site-packages/mercurial/pure/mpatch.py", + "usr/lib/python2.7/site-packages/mercurial/templates/map-cmdline.status", + "usr/lib/python2.7/site-packages/mercurial/templates/map-cmdline.changelog", + "usr/lib/python2.7/site-packages/mercurial/templates/map-cmdline.default", + "usr/lib/python2.7/site-packages/mercurial/templates/map-cmdline.xml", + "usr/lib/python2.7/site-packages/mercurial/templates/map-cmdline.phases", + "usr/lib/python2.7/site-packages/mercurial/templates/map-cmdline.bisect", + "usr/lib/python2.7/site-packages/mercurial/templates/map-cmdline.show", + "usr/lib/python2.7/site-packages/mercurial/templates/map-cmdline.compact", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/branches.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/filecomparison.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/help.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/changelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/notfound.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/footer.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/manifest.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/summary.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/changeset.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/helptopics.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/error.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/tags.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/filelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/filerevision.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/filediff.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/graph.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/fileannotate.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/changelogentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/graphentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/header.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/index.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/search.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/map", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/bookmarks.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/monoblue/shortlog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/filelogentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/branches.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/shortlogentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/changelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/notfound.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/footer.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/manifest.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/changeset.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/error.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/tags.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/filelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/filerevision.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/filediff.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/graph.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/fileannotate.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/changelogentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/graphentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/header.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/index.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/search.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/map", + "usr/lib/python2.7/site-packages/mercurial/templates/spartan/shortlog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/json/changelist.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/json/graph.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/json/map", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/filelogentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/branches.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/shortlogentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/filecomparison.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/help.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/notfound.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/diffstat.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/footer.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/manifest.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/changeset.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/helptopics.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/error.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/tags.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/filelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/filerevision.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/filediff.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/graph.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/fileannotate.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/graphentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/header.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/index.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/search.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/map", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/bookmarks.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/paper/shortlog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/graphedge.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/changelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/notfound.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/graphnode.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/manifest.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/changeset.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/error.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/logentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/filediff.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/graph.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/fileannotate.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/index.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/search.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/raw/map", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/filelogentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/branches.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/changelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/error.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/tags.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/filelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/changelogentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/header.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/bookmarkentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/map", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/bookmarks.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/tagentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/rss/branchentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/static/background.png", + "usr/lib/python2.7/site-packages/mercurial/templates/static/hglogo.png", + "usr/lib/python2.7/site-packages/mercurial/templates/static/coal-folder.png", + "usr/lib/python2.7/site-packages/mercurial/templates/static/style-extra-coal.css", + "usr/lib/python2.7/site-packages/mercurial/templates/static/style-gitweb.css", + "usr/lib/python2.7/site-packages/mercurial/templates/static/style-monoblue.css", + "usr/lib/python2.7/site-packages/mercurial/templates/static/style-paper.css", + "usr/lib/python2.7/site-packages/mercurial/templates/static/followlines.js", + "usr/lib/python2.7/site-packages/mercurial/templates/static/feed-icon-14x14.png", + "usr/lib/python2.7/site-packages/mercurial/templates/static/coal-file.png", + "usr/lib/python2.7/site-packages/mercurial/templates/static/mercurial.js", + "usr/lib/python2.7/site-packages/mercurial/templates/static/hgicon.png", + "usr/lib/python2.7/site-packages/mercurial/templates/static/style.css", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/branches.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/filecomparison.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/help.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/changelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/notfound.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/footer.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/manifest.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/summary.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/changeset.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/helptopics.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/error.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/tags.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/filelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/filerevision.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/filediff.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/graph.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/fileannotate.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/changelogentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/graphentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/header.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/index.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/search.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/map", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/bookmarks.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/gitweb/shortlog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/coal/header.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/coal/map", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/branches.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/changelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/error.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/tags.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/filelog.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/changelogentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/header.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/bookmarkentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/map", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/bookmarks.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/tagentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/templates/atom/branchentry.tmpl", + "usr/lib/python2.7/site-packages/mercurial/help/common.txt", + "usr/lib/python2.7/site-packages/mercurial/help/hgignore.5.txt", + "usr/lib/python2.7/site-packages/mercurial/help/dates.txt", + "usr/lib/python2.7/site-packages/mercurial/help/hgweb.txt", + "usr/lib/python2.7/site-packages/mercurial/help/filesets.txt", + "usr/lib/python2.7/site-packages/mercurial/help/urls.txt", + "usr/lib/python2.7/site-packages/mercurial/help/extensions.txt", + "usr/lib/python2.7/site-packages/mercurial/help/diffs.txt", + "usr/lib/python2.7/site-packages/mercurial/help/flags.txt", + "usr/lib/python2.7/site-packages/mercurial/help/hg.1.txt", + "usr/lib/python2.7/site-packages/mercurial/help/merge-tools.txt", + "usr/lib/python2.7/site-packages/mercurial/help/pager.txt", + "usr/lib/python2.7/site-packages/mercurial/help/hgignore.txt", + "usr/lib/python2.7/site-packages/mercurial/help/templates.txt", + "usr/lib/python2.7/site-packages/mercurial/help/patterns.txt", + "usr/lib/python2.7/site-packages/mercurial/help/subrepos.txt", + "usr/lib/python2.7/site-packages/mercurial/help/glossary.txt", + "usr/lib/python2.7/site-packages/mercurial/help/config.txt", + "usr/lib/python2.7/site-packages/mercurial/help/bundlespec.txt", + "usr/lib/python2.7/site-packages/mercurial/help/hg-ssh.8.txt", + "usr/lib/python2.7/site-packages/mercurial/help/color.txt", + "usr/lib/python2.7/site-packages/mercurial/help/hgrc.5.txt", + "usr/lib/python2.7/site-packages/mercurial/help/scripting.txt", + "usr/lib/python2.7/site-packages/mercurial/help/environment.txt", + "usr/lib/python2.7/site-packages/mercurial/help/revisions.txt", + "usr/lib/python2.7/site-packages/mercurial/help/phases.txt", + "usr/lib/python2.7/site-packages/mercurial/help/internals/changegroups.txt", + "usr/lib/python2.7/site-packages/mercurial/help/internals/config.txt", + "usr/lib/python2.7/site-packages/mercurial/help/internals/requirements.txt", + "usr/lib/python2.7/site-packages/mercurial/help/internals/censor.txt", + "usr/lib/python2.7/site-packages/mercurial/help/internals/revlogs.txt", + "usr/lib/python2.7/site-packages/mercurial/help/internals/bundles.txt", + "usr/lib/python2.7/site-packages/mercurial/help/internals/wireprotocol.txt", + "usr/lib/python2.7/site-packages/mercurial/httpclient/__init__.pyc", + "usr/lib/python2.7/site-packages/mercurial/httpclient/_readers.pyc", + "usr/lib/python2.7/site-packages/mercurial/httpclient/__init__.py", + "usr/lib/python2.7/site-packages/mercurial/httpclient/_readers.py", + "usr/lib/python2.7/site-packages/mercurial/default.d/mergetools.rc", + "usr/lib/python2.7/site-packages/hgdemandimport/__init__.pyc", + "usr/lib/python2.7/site-packages/hgdemandimport/__init__.py", + "usr/lib/python2.7/site-packages/hgdemandimport/demandimportpy3.py", + "usr/lib/python2.7/site-packages/hgdemandimport/demandimportpy2.py", + "usr/lib/python2.7/site-packages/hgdemandimport/demandimportpy2.pyc", + "usr/lib/python2.7/site-packages/hgdemandimport/demandimportpy3.pyc", + "usr/lib/python2.7/site-packages/hgext3rd/__init__.pyc", + "usr/lib/python2.7/site-packages/hgext3rd/__init__.py" + ] + }, + { + "ID": "musl@1.1.18-r3", + "Name": "musl", + "Identifier": { + "PURL": "pkg:apk/alpine/musl@1.1.18-r3?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.1.18-r3", + "Arch": "x86_64", + "SrcName": "musl", + "SrcVersion": "1.1.18-r3", + "Licenses": [ + "MIT" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:5595b2575962e133096977497ef1582bcc76429e", + "InstalledFiles": [ + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1" + ] + }, + { + "ID": "musl-utils@1.1.18-r3", + "Name": "musl-utils", + "Identifier": { + "PURL": "pkg:apk/alpine/musl-utils@1.1.18-r3?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.1.18-r3", + "Arch": "x86_64", + "SrcName": "musl", + "SrcVersion": "1.1.18-r3", + "Licenses": [ + "MIT", + "BSD-3-Clause", + "GPL-2.0" + ], + "DependsOn": [ + "musl@1.1.18-r3", + "scanelf@1.2.2-r1" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:87d95e8fc8f4792b3e544a3576cb73f0a89bfa41", + "InstalledFiles": [ + "sbin/ldconfig", + "usr/bin/iconv", + "usr/bin/ldd", + "usr/bin/getconf", + "usr/bin/getent" + ] + }, + { + "ID": "ncurses-libs@6.0_p20171125-r1", + "Name": "ncurses-libs", + "Identifier": { + "PURL": "pkg:apk/alpine/ncurses-libs@6.0_p20171125-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "6.0_p20171125-r1", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.0_p20171125-r1", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "musl@1.1.18-r3", + "ncurses-terminfo-base@6.0_p20171125-r1", + "ncurses-terminfo@6.0_p20171125-r1" + ], + "Layer": { + "Digest": "sha256:3d6152f6ac208640f9fb494d1c379fe508db1fc5754cd08fefec200bddd13e0e", + "DiffID": "sha256:6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013" + }, + "Digest": "sha1:f784fc9499dda6c6d13da34507efa7546368fff3", + "InstalledFiles": [ + "usr/lib/terminfo", + "usr/lib/libncursesw.so.6", + "usr/lib/libmenuw.so.6", + "usr/lib/libformw.so.6", + "usr/lib/libncursesw.so.6.0", + "usr/lib/libformw.so.6.0", + "usr/lib/libpanelw.so.6.0", + "usr/lib/libmenuw.so.6.0", + "usr/lib/libpanelw.so.6" + ] + }, + { + "ID": "ncurses-terminfo@6.0_p20171125-r1", + "Name": "ncurses-terminfo", + "Identifier": { + "PURL": "pkg:apk/alpine/ncurses-terminfo@6.0_p20171125-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "6.0_p20171125-r1", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.0_p20171125-r1", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "ncurses-terminfo-base@6.0_p20171125-r1" + ], + "Layer": { + "Digest": "sha256:3d6152f6ac208640f9fb494d1c379fe508db1fc5754cd08fefec200bddd13e0e", + "DiffID": "sha256:6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013" + }, + "Digest": "sha1:113144a8d7eeb534dc692b66d45ec8ccdd4b972d", + "InstalledFiles": [ + "usr/share/terminfo/a/att4425", + "usr/share/terminfo/a/avt-w-rv", + "usr/share/terminfo/a/aaa-60", + "usr/share/terminfo/a/avt-rv-ns", + "usr/share/terminfo/a/ampex80", + "usr/share/terminfo/a/atari-color", + "usr/share/terminfo/a/alto-heath", + "usr/share/terminfo/a/apple-videx2", + "usr/share/terminfo/a/att6386", + "usr/share/terminfo/a/apple-80", + "usr/share/terminfo/a/adm2", + "usr/share/terminfo/a/adm5", + "usr/share/terminfo/a/ansi80x30", + "usr/share/terminfo/a/alto-h19", + "usr/share/terminfo/a/aaa-60-rv", + "usr/share/terminfo/a/att2350", + "usr/share/terminfo/a/a80", + "usr/share/terminfo/a/att510a", + "usr/share/terminfo/a/aaa-s", + "usr/share/terminfo/a/att7300", + "usr/share/terminfo/a/amp219", + "usr/share/terminfo/a/att620-103k-w", + "usr/share/terminfo/a/avt-w-ns", + "usr/share/terminfo/a/aj510", + "usr/share/terminfo/a/altos-2", + "usr/share/terminfo/a/avt", + "usr/share/terminfo/a/ansi+sgrul", + "usr/share/terminfo/a/att630", + "usr/share/terminfo/a/att5410", + "usr/share/terminfo/a/att5620-34", + "usr/share/terminfo/a/apple-ae", + "usr/share/terminfo/a/att615", + "usr/share/terminfo/a/abm85e", + "usr/share/terminfo/a/apple-videx3", + "usr/share/terminfo/a/alt7pc", + "usr/share/terminfo/a/ansil-mono", + "usr/share/terminfo/a/addrinfo", + "usr/share/terminfo/a/att5420+nl", + "usr/share/terminfo/a/att620-103k", + "usr/share/terminfo/a/altos5", + "usr/share/terminfo/a/ansi+idl1", + "usr/share/terminfo/a/arm100-am", + "usr/share/terminfo/a/aaa-48", + "usr/share/terminfo/a/abm80", + "usr/share/terminfo/a/adm20", + "usr/share/terminfo/a/avatar1", + "usr/share/terminfo/a/annarbor4080", + "usr/share/terminfo/a/avatar", + "usr/share/terminfo/a/ap-vm80", + "usr/share/terminfo/a/ansi+idl", + "usr/share/terminfo/a/ansi+rep", + "usr/share/terminfo/a/aaa-rv-unk", + "usr/share/terminfo/a/aaa-60-s-rv", + "usr/share/terminfo/a/att615-103k-w", + "usr/share/terminfo/a/ansis-mono", + "usr/share/terminfo/a/att5410v1-w", + "usr/share/terminfo/a/appleIIc", + "usr/share/terminfo/a/apple2e-p", + "usr/share/terminfo/a/adm1", + "usr/share/terminfo/a/aaa-rv", + "usr/share/terminfo/a/ansi-emx", + "usr/share/terminfo/a/aixterm-m-old", + "usr/share/terminfo/a/altos7pc", + "usr/share/terminfo/a/att610", + "usr/share/terminfo/a/att4426", + "usr/share/terminfo/a/avt-w-rv-s", + "usr/share/terminfo/a/att4415+nl", + "usr/share/terminfo/a/ambas", + "usr/share/terminfo/a/aaa-60-s", + "usr/share/terminfo/a/att5320", + "usr/share/terminfo/a/att4425-w", + "usr/share/terminfo/a/at-m", + "usr/share/terminfo/a/att5420-rv-nl", + "usr/share/terminfo/a/att610-w", + "usr/share/terminfo/a/att5620", + "usr/share/terminfo/a/altos2", + "usr/share/terminfo/a/att4415-rv-nl", + "usr/share/terminfo/a/aaa-30-s", + "usr/share/terminfo/a/altos3", + "usr/share/terminfo/a/att5420-nl", + "usr/share/terminfo/a/ansi80x25-raw", + "usr/share/terminfo/a/ampex175-b", + "usr/share/terminfo/a/apple80p", + "usr/share/terminfo/a/att5620-1", + "usr/share/terminfo/a/att5420-w", + "usr/share/terminfo/a/att4418", + "usr/share/terminfo/a/ansi-nt", + "usr/share/terminfo/a/att5420-w-rv-n", + "usr/share/terminfo/a/att5418", + "usr/share/terminfo/a/abm85h", + "usr/share/terminfo/a/amiga", + "usr/share/terminfo/a/att5310", + "usr/share/terminfo/a/aas1901", + "usr/share/terminfo/a/atari_st", + "usr/share/terminfo/a/altos4", + "usr/share/terminfo/a/ansiw", + "usr/share/terminfo/a/ansi+rca", + "usr/share/terminfo/a/att730-41", + "usr/share/terminfo/a/aaa-40-rv", + "usr/share/terminfo/a/att4424", + "usr/share/terminfo/a/att2300", + "usr/share/terminfo/a/att5410v1", + "usr/share/terminfo/a/arm100", + "usr/share/terminfo/a/ansi+local", + "usr/share/terminfo/a/att4415-nl", + "usr/share/terminfo/a/ansi+sgrdim", + "usr/share/terminfo/a/addsvp60", + "usr/share/terminfo/a/ampex-219w", + "usr/share/terminfo/a/act5", + "usr/share/terminfo/a/aaa-60-dec-rv", + "usr/share/terminfo/a/att610-103k-w", + "usr/share/terminfo/a/act4", + "usr/share/terminfo/a/a980", + "usr/share/terminfo/a/ampex219", + "usr/share/terminfo/a/atari_st-color", + "usr/share/terminfo/a/ansi+erase", + "usr/share/terminfo/a/att4424m", + "usr/share/terminfo/a/aaa-30-s-rv", + "usr/share/terminfo/a/att4418-w", + "usr/share/terminfo/a/att615-103k", + "usr/share/terminfo/a/att700", + "usr/share/terminfo/a/ansi-color-3-emx", + "usr/share/terminfo/a/avatar0+", + "usr/share/terminfo/a/atari-old", + "usr/share/terminfo/a/att4425-nl", + "usr/share/terminfo/a/a210", + "usr/share/terminfo/a/ansi", + "usr/share/terminfo/a/ansi+sgrbold", + "usr/share/terminfo/a/altos-4", + "usr/share/terminfo/a/adm12", + "usr/share/terminfo/a/apollo", + "usr/share/terminfo/a/ambassador", + "usr/share/terminfo/a/ansi80x43", + "usr/share/terminfo/a/aaa-22", + "usr/share/terminfo/a/att5420-w-nl", + "usr/share/terminfo/a/att730-24", + "usr/share/terminfo/a/ansi-color-2-emx", + "usr/share/terminfo/a/aaa-30", + "usr/share/terminfo/a/appleII", + "usr/share/terminfo/a/ansi80x60-mono", + "usr/share/terminfo/a/avt-s", + "usr/share/terminfo/a/apple-uterm-vb", + "usr/share/terminfo/a/ampex219w", + "usr/share/terminfo/a/att605-w", + "usr/share/terminfo/a/avatar0", + "usr/share/terminfo/a/aaa-24", + "usr/share/terminfo/a/ampex210", + "usr/share/terminfo/a/att5420_2-w", + "usr/share/terminfo/a/aaa-30-rv", + "usr/share/terminfo/a/ansi-m", + "usr/share/terminfo/a/aa4080", + "usr/share/terminfo/a/at-color", + "usr/share/terminfo/a/att4410v1-w", + "usr/share/terminfo/a/aaa-18-rv", + "usr/share/terminfo/a/apple-soroc", + "usr/share/terminfo/a/altos-3", + "usr/share/terminfo/a/amiga-8bit", + "usr/share/terminfo/a/ansi+pp", + "usr/share/terminfo/a/att510d", + "usr/share/terminfo/a/aaa-30-s-rv-ct", + "usr/share/terminfo/a/att5420-w-rv", + "usr/share/terminfo/a/avt-rv", + "usr/share/terminfo/a/att730", + "usr/share/terminfo/a/adm3a+", + "usr/share/terminfo/a/apple-vm80", + "usr/share/terminfo/a/adm3a", + "usr/share/terminfo/a/ansil", + "usr/share/terminfo/a/att4415-w", + "usr/share/terminfo/a/ansis", + "usr/share/terminfo/a/ansi77", + "usr/share/terminfo/a/aaa-30-ctxt", + "usr/share/terminfo/a/apple-videx", + "usr/share/terminfo/a/aaa+unk", + "usr/share/terminfo/a/alt3", + "usr/share/terminfo/a/adm1178", + "usr/share/terminfo/a/arm100-wam", + "usr/share/terminfo/a/appleIIgs", + "usr/share/terminfo/a/adm21", + "usr/share/terminfo/a/att5430", + "usr/share/terminfo/a/adm42", + "usr/share/terminfo/a/ansi.sys", + "usr/share/terminfo/a/ansi80x25-mono", + "usr/share/terminfo/a/aaa-18", + "usr/share/terminfo/a/att730r-41", + "usr/share/terminfo/a/ansi-mono", + "usr/share/terminfo/a/adm31-old", + "usr/share/terminfo/a/att4415-w-nl", + "usr/share/terminfo/a/altos7", + "usr/share/terminfo/a/apple2e", + "usr/share/terminfo/a/aaa-db", + "usr/share/terminfo/a/ansiterm", + "usr/share/terminfo/a/att620", + "usr/share/terminfo/a/adds980", + "usr/share/terminfo/a/ansi80x50-mono", + "usr/share/terminfo/a/amiga-h", + "usr/share/terminfo/a/aaa-30-rv-ctxt", + "usr/share/terminfo/a/altoheath", + "usr/share/terminfo/a/ansi80x30-mono", + "usr/share/terminfo/a/adm22", + "usr/share/terminfo/a/apollo_color", + "usr/share/terminfo/a/att5420", + "usr/share/terminfo/a/aaa-s-rv-ctxt", + "usr/share/terminfo/a/att5425", + "usr/share/terminfo/a/att4410v1", + "usr/share/terminfo/a/aaa-36-rv", + "usr/share/terminfo/a/avt-w", + "usr/share/terminfo/a/avt-w-s", + "usr/share/terminfo/a/att630-24", + "usr/share/terminfo/a/aaa-s-ctxt", + "usr/share/terminfo/a/att5425-nl", + "usr/share/terminfo/a/aaa-40", + "usr/share/terminfo/a/aj", + "usr/share/terminfo/a/ampex175", + "usr/share/terminfo/a/aj832", + "usr/share/terminfo/a/ansi+sgr", + "usr/share/terminfo/a/aixterm-m", + "usr/share/terminfo/a/adm3", + "usr/share/terminfo/a/att505-24", + "usr/share/terminfo/a/atarist-m", + "usr/share/terminfo/a/ansi-mr", + "usr/share/terminfo/a/aixterm-16color", + "usr/share/terminfo/a/aaa-48-rv", + "usr/share/terminfo/a/aaa+dec", + "usr/share/terminfo/a/ansi-generic", + "usr/share/terminfo/a/avt-ns", + "usr/share/terminfo/a/ansi+csr", + "usr/share/terminfo/a/att4420", + "usr/share/terminfo/a/ansi43m", + "usr/share/terminfo/a/ansi+tabs", + "usr/share/terminfo/a/att5620-24", + "usr/share/terminfo/a/ansi80x25", + "usr/share/terminfo/a/alt2", + "usr/share/terminfo/a/ansi+enq", + "usr/share/terminfo/a/att4424-1", + "usr/share/terminfo/a/att4415", + "usr/share/terminfo/a/att4415-w-rv-n", + "usr/share/terminfo/a/att5420-rv", + "usr/share/terminfo/a/avt-w-rv-ns", + "usr/share/terminfo/a/avt+s", + "usr/share/terminfo/a/aaa-30-s-ctxt", + "usr/share/terminfo/a/addsviewpoint", + "usr/share/terminfo/a/att5410-w", + "usr/share/terminfo/a/ansi80x50", + "usr/share/terminfo/a/aaa-20", + "usr/share/terminfo/a/aaa-rv-ctxt", + "usr/share/terminfo/a/apollo_19L", + "usr/share/terminfo/a/altos-5", + "usr/share/terminfo/a/aj830", + "usr/share/terminfo/a/att4415-w-rv", + "usr/share/terminfo/a/ansisysk", + "usr/share/terminfo/a/att605", + "usr/share/terminfo/a/adm42-ns", + "usr/share/terminfo/a/altoh19", + "usr/share/terminfo/a/ansi.sysk", + "usr/share/terminfo/a/at", + "usr/share/terminfo/a/att5418-w", + "usr/share/terminfo/a/aixterm", + "usr/share/terminfo/a/aepro", + "usr/share/terminfo/a/att615-w", + "usr/share/terminfo/a/appleIIe", + "usr/share/terminfo/a/att500", + "usr/share/terminfo/a/abm85h-old", + "usr/share/terminfo/a/ampex232w", + "usr/share/terminfo/a/adm+sgr", + "usr/share/terminfo/a/aaa-36", + "usr/share/terminfo/a/adm1a", + "usr/share/terminfo/a/ansi+inittabs", + "usr/share/terminfo/a/ansi+idc", + "usr/share/terminfo/a/att730r-24", + "usr/share/terminfo/a/ansi+cup", + "usr/share/terminfo/a/atari-m", + "usr/share/terminfo/a/att4410-w", + "usr/share/terminfo/a/aaa-26", + "usr/share/terminfo/a/ampex-219", + "usr/share/terminfo/a/ampex232", + "usr/share/terminfo/a/ansi+arrows", + "usr/share/terminfo/a/apl", + "usr/share/terminfo/a/ansi+local1", + "usr/share/terminfo/a/ampex-232", + "usr/share/terminfo/a/att513", + "usr/share/terminfo/a/att5425-w", + "usr/share/terminfo/a/att5420_2", + "usr/share/terminfo/a/att730r", + "usr/share/terminfo/a/adm11", + "usr/share/terminfo/a/aaa-24-rv", + "usr/share/terminfo/a/aaa-ctxt", + "usr/share/terminfo/a/att4415-rv", + "usr/share/terminfo/a/aterm", + "usr/share/terminfo/a/ansi+sgrso", + "usr/share/terminfo/a/att620-w", + "usr/share/terminfo/a/amp219w", + "usr/share/terminfo/a/ansi-mini", + "usr/share/terminfo/a/arm100-w", + "usr/share/terminfo/a/aaa-unk", + "usr/share/terminfo/a/att505", + "usr/share/terminfo/a/alt4", + "usr/share/terminfo/a/att610-103k", + "usr/share/terminfo/a/att5620-s", + "usr/share/terminfo/a/at386", + "usr/share/terminfo/a/alt7", + "usr/share/terminfo/a/aaa-28", + "usr/share/terminfo/a/apollo_15P", + "usr/share/terminfo/a/ansi-mtabs", + "usr/share/terminfo/a/awsc", + "usr/share/terminfo/a/ansi.sys-old", + "usr/share/terminfo/a/ansi80x60", + "usr/share/terminfo/a/aaa-s-rv", + "usr/share/terminfo/a/atari", + "usr/share/terminfo/a/att4410", + "usr/share/terminfo/a/aaa", + "usr/share/terminfo/a/aws", + "usr/share/terminfo/a/adm31", + "usr/share/terminfo/a/amiga-vnc", + "usr/share/terminfo/a/aaa+rv", + "usr/share/terminfo/a/abm85", + "usr/share/terminfo/a/apple-uterm", + "usr/share/terminfo/a/adm36", + "usr/share/terminfo/a/ansi80x43-mono", + "usr/share/terminfo/a/alt5", + "usr/share/terminfo/a/avt-rv-s", + "usr/share/terminfo/a/att605-pc", + "usr/share/terminfo/r/regent40", + "usr/share/terminfo/r/rt6221-w", + "usr/share/terminfo/r/rebus3180", + "usr/share/terminfo/r/rbcomm", + "usr/share/terminfo/r/rcons-color", + "usr/share/terminfo/r/rxvt+pcfkeys", + "usr/share/terminfo/r/rxvt-16color", + "usr/share/terminfo/r/rxvt-cygwin-native", + "usr/share/terminfo/r/rt6221", + "usr/share/terminfo/r/rcons", + "usr/share/terminfo/r/rxvt-color", + "usr/share/terminfo/r/regent25", + "usr/share/terminfo/r/regent", + "usr/share/terminfo/r/rxvt-cygwin", + "usr/share/terminfo/r/regent100", + "usr/share/terminfo/r/regent40+", + "usr/share/terminfo/r/rxvt", + "usr/share/terminfo/r/rca", + "usr/share/terminfo/r/rxvt-basic", + "usr/share/terminfo/r/rtpc", + "usr/share/terminfo/r/rbcomm-w", + "usr/share/terminfo/r/rbcomm-nam", + "usr/share/terminfo/r/rxvt-88color", + "usr/share/terminfo/r/rxvt-256color", + "usr/share/terminfo/r/regent60", + "usr/share/terminfo/r/regent20", + "usr/share/terminfo/r/rxvt-xpm", + "usr/share/terminfo/r/regent200", + "usr/share/terminfo/d/d211-7b", + "usr/share/terminfo/d/d413-dg", + "usr/share/terminfo/d/d555-7b-w", + "usr/share/terminfo/d/d430-dg-ccc", + "usr/share/terminfo/d/djgpp204", + "usr/share/terminfo/d/d412+w", + "usr/share/terminfo/d/d462-unix", + "usr/share/terminfo/d/d210-dg", + "usr/share/terminfo/d/d555-7b", + "usr/share/terminfo/d/darwin-90x30", + "usr/share/terminfo/d/dt100w", + "usr/share/terminfo/d/diablo1740-lm", + "usr/share/terminfo/d/d217-unix-25", + "usr/share/terminfo/d/dialogue80", + "usr/share/terminfo/d/d216-unix-25", + "usr/share/terminfo/d/darwin-128x48-m", + "usr/share/terminfo/d/dgmode+color", + "usr/share/terminfo/d/d461-dg", + "usr/share/terminfo/d/d410", + "usr/share/terminfo/d/djgpp203", + "usr/share/terminfo/d/dumb-emacs-ansi", + "usr/share/terminfo/d/dg211", + "usr/share/terminfo/d/d430-unix-sr-ccc", + "usr/share/terminfo/d/dg210", + "usr/share/terminfo/d/d470c-7b", + "usr/share/terminfo/d/d470-dg", + "usr/share/terminfo/d/dw4", + "usr/share/terminfo/d/d411-7b", + "usr/share/terminfo/d/diablo1640-m8", + "usr/share/terminfo/d/darwin-200x64-m", + "usr/share/terminfo/d/dvtm", + "usr/share/terminfo/d/datagraphix", + "usr/share/terminfo/d/darwin-128x40", + "usr/share/terminfo/d/d220", + "usr/share/terminfo/d/d463-unix-w", + "usr/share/terminfo/d/diablo450", + "usr/share/terminfo/d/diablo1620", + "usr/share/terminfo/d/d400", + "usr/share/terminfo/d/d430-unix-25-ccc", + "usr/share/terminfo/d/d412+sr", + "usr/share/terminfo/d/d410-7b-w", + "usr/share/terminfo/d/datamedia2500", + "usr/share/terminfo/d/dg-ansi", + "usr/share/terminfo/d/diablo630", + "usr/share/terminfo/d/dku7202", + "usr/share/terminfo/d/d412-unix-sr", + "usr/share/terminfo/d/dec+pp", + "usr/share/terminfo/d/d216e-dg", + "usr/share/terminfo/d/dg605x", + "usr/share/terminfo/d/darwin-200x75", + "usr/share/terminfo/d/dm3045", + "usr/share/terminfo/d/dg100", + "usr/share/terminfo/d/dmd", + "usr/share/terminfo/d/ddr3180", + "usr/share/terminfo/d/darwin-100x37-m", + "usr/share/terminfo/d/dgunix+ccc", + "usr/share/terminfo/d/d577-7b-w", + "usr/share/terminfo/d/dec-vt330", + "usr/share/terminfo/d/darwin", + "usr/share/terminfo/d/d462+25", + "usr/share/terminfo/d/darwin-256x96-m", + "usr/share/terminfo/d/dgkeys+11", + "usr/share/terminfo/d/d430c-unix-ccc", + "usr/share/terminfo/d/darwin-144x48-m", + "usr/share/terminfo/d/djgpp", + "usr/share/terminfo/d/d413-unix-s", + "usr/share/terminfo/d/d410-w", + "usr/share/terminfo/d/dg200", + "usr/share/terminfo/d/dp3360", + "usr/share/terminfo/d/d216e+dg", + "usr/share/terminfo/d/d412+s", + "usr/share/terminfo/d/dg450", + "usr/share/terminfo/d/d578-7b", + "usr/share/terminfo/d/dg+fixed", + "usr/share/terminfo/d/d217-unix", + "usr/share/terminfo/d/darwin-100x37", + "usr/share/terminfo/d/d464-unix-s", + "usr/share/terminfo/d/dm2500", + "usr/share/terminfo/d/d430c-unix-w-ccc", + "usr/share/terminfo/d/d200-dg", + "usr/share/terminfo/d/d215-dg", + "usr/share/terminfo/d/d217-dg", + "usr/share/terminfo/d/darwin-80x30-m", + "usr/share/terminfo/d/d413-unix", + "usr/share/terminfo/d/darwin-m-b", + "usr/share/terminfo/d/d430c-unix-s-ccc", + "usr/share/terminfo/d/d464-unix-sr", + "usr/share/terminfo/d/darwin-80x25", + "usr/share/terminfo/d/d211-dg", + "usr/share/terminfo/d/dw3", + "usr/share/terminfo/d/d464-unix", + "usr/share/terminfo/d/d413-unix-25", + "usr/share/terminfo/d/darwin-160x64-m", + "usr/share/terminfo/d/dmchat", + "usr/share/terminfo/d/dm3025", + "usr/share/terminfo/d/dtterm", + "usr/share/terminfo/d/d132", + "usr/share/terminfo/d/d430-dg", + "usr/share/terminfo/d/d470c-dg", + "usr/share/terminfo/d/d400-dg", + "usr/share/terminfo/d/d220-7b", + "usr/share/terminfo/d/d410-7b", + "usr/share/terminfo/d/dg6053", + "usr/share/terminfo/d/d230", + "usr/share/terminfo/d/d412-unix-25", + "usr/share/terminfo/d/dmd1", + "usr/share/terminfo/d/d462+dg", + "usr/share/terminfo/d/dp8242", + "usr/share/terminfo/d/dw2", + "usr/share/terminfo/d/darwin-160x64", + "usr/share/terminfo/d/dec+sl", + "usr/share/terminfo/d/dku7103-sna", + "usr/share/terminfo/d/d463-unix", + "usr/share/terminfo/d/dgkeys+7b", + "usr/share/terminfo/d/diablo1740", + "usr/share/terminfo/d/ds40-2", + "usr/share/terminfo/d/dwk-vt", + "usr/share/terminfo/d/d462-unix-s", + "usr/share/terminfo/d/d414-unix-w", + "usr/share/terminfo/d/d430-unix", + "usr/share/terminfo/d/diablo1640", + "usr/share/terminfo/d/d577-w", + "usr/share/terminfo/d/dt80-sas", + "usr/share/terminfo/d/diablo1620-m8", + "usr/share/terminfo/d/darwin-90x30-m", + "usr/share/terminfo/d/d200", + "usr/share/terminfo/d/ds40", + "usr/share/terminfo/d/darwin-200x64", + "usr/share/terminfo/d/d450", + "usr/share/terminfo/d/dg460-ansi", + "usr/share/terminfo/d/dgunix+fixed", + "usr/share/terminfo/d/d80", + "usr/share/terminfo/d/d461-w", + "usr/share/terminfo/d/d470", + "usr/share/terminfo/d/darwin-f2", + "usr/share/terminfo/d/darwin-128x40-m", + "usr/share/terminfo/d/d216-unix", + "usr/share/terminfo/d/d577-7b", + "usr/share/terminfo/d/d412+25", + "usr/share/terminfo/d/dt80", + "usr/share/terminfo/d/d463-unix-s", + "usr/share/terminfo/d/d216+25", + "usr/share/terminfo/d/d462e-dg", + "usr/share/terminfo/d/d214-dg", + "usr/share/terminfo/d/d430c-unix-25", + "usr/share/terminfo/d/darwin-m-f", + "usr/share/terminfo/d/dec-vt400", + "usr/share/terminfo/d/dwk", + "usr/share/terminfo/d/d430c-dg-ccc", + "usr/share/terminfo/d/d462-unix-w", + "usr/share/terminfo/d/d411-w", + "usr/share/terminfo/d/d460-7b", + "usr/share/terminfo/d/d414-unix-25", + "usr/share/terminfo/d/darwin-144x48", + "usr/share/terminfo/d/dku7102-sna", + "usr/share/terminfo/d/d460-dg", + "usr/share/terminfo/d/d430c-unix-w", + "usr/share/terminfo/d/d470-7b", + "usr/share/terminfo/d/diablo1720", + "usr/share/terminfo/d/dm1521", + "usr/share/terminfo/d/d411", + "usr/share/terminfo/d/dgkeys+15", + "usr/share/terminfo/d/d578-dg", + "usr/share/terminfo/d/d413-unix-w", + "usr/share/terminfo/d/darwin-f", + "usr/share/terminfo/d/d410-dg", + "usr/share/terminfo/d/d577", + "usr/share/terminfo/d/d220-dg", + "usr/share/terminfo/d/decpro", + "usr/share/terminfo/d/d555-w", + "usr/share/terminfo/d/dg6134", + "usr/share/terminfo/d/d430c-unix-sr", + "usr/share/terminfo/d/d461-7b", + "usr/share/terminfo/d/d2", + "usr/share/terminfo/d/d414-unix-sr", + "usr/share/terminfo/d/d462+s", + "usr/share/terminfo/d/diablo", + "usr/share/terminfo/d/d216-dg", + "usr/share/terminfo/d/dg+color", + "usr/share/terminfo/d/decansi", + "usr/share/terminfo/d/d450-dg", + "usr/share/terminfo/d/dm80w", + "usr/share/terminfo/d/ddr", + "usr/share/terminfo/d/diablo1640-lm", + "usr/share/terminfo/d/diablo-lm", + "usr/share/terminfo/d/d430-unix-s", + "usr/share/terminfo/d/d412+dg", + "usr/share/terminfo/d/d216e-unix", + "usr/share/terminfo/d/dmdt80", + "usr/share/terminfo/d/dgmode+color8", + "usr/share/terminfo/d/dw", + "usr/share/terminfo/d/dtc382", + "usr/share/terminfo/d/d464-unix-25", + "usr/share/terminfo/d/darwin-112x37", + "usr/share/terminfo/d/darwin-80x25-m", + "usr/share/terminfo/d/d430-unix-25", + "usr/share/terminfo/d/d578", + "usr/share/terminfo/d/dmterm", + "usr/share/terminfo/d/d800", + "usr/share/terminfo/d/dec-vt100", + "usr/share/terminfo/d/darwin-128x48", + "usr/share/terminfo/d/d414-unix", + "usr/share/terminfo/d/dt-100", + "usr/share/terminfo/d/decwriter", + "usr/share/terminfo/d/d210", + "usr/share/terminfo/d/dt80w", + "usr/share/terminfo/d/d460", + "usr/share/terminfo/d/d462+sr", + "usr/share/terminfo/d/d216+", + "usr/share/terminfo/d/d430-unix-w", + "usr/share/terminfo/d/dd5000", + "usr/share/terminfo/d/dumb", + "usr/share/terminfo/d/dialogue", + "usr/share/terminfo/d/dm1520", + "usr/share/terminfo/d/d430c-unix-s", + "usr/share/terminfo/d/d411-dg", + "usr/share/terminfo/d/d230-dg", + "usr/share/terminfo/d/dw1", + "usr/share/terminfo/d/darwin-256x96", + "usr/share/terminfo/d/d462-dg", + "usr/share/terminfo/d/d555", + "usr/share/terminfo/d/datapoint", + "usr/share/terminfo/d/dvtm-256color", + "usr/share/terminfo/d/d460-w", + "usr/share/terminfo/d/d430c-unix", + "usr/share/terminfo/d/d215", + "usr/share/terminfo/d/d463-unix-sr", + "usr/share/terminfo/d/dku7003-dumb", + "usr/share/terminfo/d/d463-unix-25", + "usr/share/terminfo/d/d430c-dg", + "usr/share/terminfo/d/d214", + "usr/share/terminfo/d/dm80", + "usr/share/terminfo/d/dataspeed40", + "usr/share/terminfo/d/d462+w", + "usr/share/terminfo/d/delta", + "usr/share/terminfo/d/darwin-80x30", + "usr/share/terminfo/d/d463-dg", + "usr/share/terminfo/d/d230c-dg", + "usr/share/terminfo/d/dku7003", + "usr/share/terminfo/d/dec-vt340", + "usr/share/terminfo/d/diablo1730", + "usr/share/terminfo/d/d462-unix-25", + "usr/share/terminfo/d/darwin-112x37-m", + "usr/share/terminfo/d/d460-7b-w", + "usr/share/terminfo/d/dec-vt220", + "usr/share/terminfo/d/d211", + "usr/share/terminfo/d/digilog", + "usr/share/terminfo/d/d462+", + "usr/share/terminfo/d/d412-dg", + "usr/share/terminfo/d/dku7102", + "usr/share/terminfo/d/dg6053-old", + "usr/share/terminfo/d/d216+dg", + "usr/share/terminfo/d/dmdt80w", + "usr/share/terminfo/d/d430-unix-sr", + "usr/share/terminfo/d/d412-unix", + "usr/share/terminfo/d/dt-100w", + "usr/share/terminfo/d/d216e+", + "usr/share/terminfo/d/darwin-m", + "usr/share/terminfo/d/d430-unix-ccc", + "usr/share/terminfo/d/dmd-34", + "usr/share/terminfo/d/d461", + "usr/share/terminfo/d/d2-dg", + "usr/share/terminfo/d/d430c-unix-sr-ccc", + "usr/share/terminfo/d/d555-dg", + "usr/share/terminfo/d/d464-unix-w", + "usr/share/terminfo/d/d412-unix-w", + "usr/share/terminfo/d/d430-unix-w-ccc", + "usr/share/terminfo/d/d414-unix-s", + "usr/share/terminfo/d/dt100", + "usr/share/terminfo/d/dg+color8", + "usr/share/terminfo/d/dmd-24", + "usr/share/terminfo/d/d430-unix-s-ccc", + "usr/share/terminfo/d/d470c", + "usr/share/terminfo/d/dgkeys+8b", + "usr/share/terminfo/d/darwin-200x75-m", + "usr/share/terminfo/d/d411-7b-w", + "usr/share/terminfo/d/dg+ccc", + "usr/share/terminfo/d/darwin-b", + "usr/share/terminfo/d/d412+", + "usr/share/terminfo/d/dg-generic", + "usr/share/terminfo/d/d577-dg", + "usr/share/terminfo/d/dt110", + "usr/share/terminfo/d/d430c-unix-25-ccc", + "usr/share/terminfo/d/dtc300s", + "usr/share/terminfo/d/d412-unix-s", + "usr/share/terminfo/d/d413-unix-sr", + "usr/share/terminfo/d/d215-7b", + "usr/share/terminfo/d/d230c", + "usr/share/terminfo/d/d462-unix-sr", + "usr/share/terminfo/d/d461-7b-w", + "usr/share/terminfo/d/darwin-m-f2", + "usr/share/terminfo/d/dku7102-old", + "usr/share/terminfo/b/b-128", + "usr/share/terminfo/b/bq300-8-pc-w-rv", + "usr/share/terminfo/b/basis", + "usr/share/terminfo/b/bsdos-pc", + "usr/share/terminfo/b/bh4", + "usr/share/terminfo/b/bq300-8", + "usr/share/terminfo/b/bg3.10", + "usr/share/terminfo/b/beehive", + "usr/share/terminfo/b/bg2.0rv", + "usr/share/terminfo/b/basic4", + "usr/share/terminfo/b/bq300", + "usr/share/terminfo/b/bsdos-pc-m", + "usr/share/terminfo/b/bq300-pc-w", + "usr/share/terminfo/b/beehiveIIIm", + "usr/share/terminfo/b/bq300-rv", + "usr/share/terminfo/b/bg3.10nv", + "usr/share/terminfo/b/bq300-pc-rv", + "usr/share/terminfo/b/bobcat", + "usr/share/terminfo/b/bee", + "usr/share/terminfo/b/bg1.25rv", + "usr/share/terminfo/b/bq300-8rv", + "usr/share/terminfo/b/bct510a", + "usr/share/terminfo/b/bsdos-pc-mono", + "usr/share/terminfo/b/bg1.25nv", + "usr/share/terminfo/b/bh3m", + "usr/share/terminfo/b/bsdos-ppc", + "usr/share/terminfo/b/beehive4", + "usr/share/terminfo/b/bg3.10rv", + "usr/share/terminfo/b/bg1.25", + "usr/share/terminfo/b/bq300-8-pc-w", + "usr/share/terminfo/b/bq300-w", + "usr/share/terminfo/b/bg2.0", + "usr/share/terminfo/b/bq300-8-pc", + "usr/share/terminfo/b/bct510d", + "usr/share/terminfo/b/bg2.0nv", + "usr/share/terminfo/b/bq300-8w", + "usr/share/terminfo/b/beterm", + "usr/share/terminfo/b/bq300-8-pc-rv", + "usr/share/terminfo/b/bq300-w-8rv", + "usr/share/terminfo/b/beacon", + "usr/share/terminfo/b/bsdos-sparc", + "usr/share/terminfo/b/bantam", + "usr/share/terminfo/b/bterm", + "usr/share/terminfo/b/bq300-pc", + "usr/share/terminfo/b/blit", + "usr/share/terminfo/b/bsdos-pc-nobold", + "usr/share/terminfo/b/beehive3", + "usr/share/terminfo/b/bitgraph", + "usr/share/terminfo/b/bq300-w-rv", + "usr/share/terminfo/b/bq300-pc-w-rv", + "usr/share/terminfo/w/wy160-wvb", + "usr/share/terminfo/w/wyse370", + "usr/share/terminfo/w/wyse185-wvb", + "usr/share/terminfo/w/wy520-48pc", + "usr/share/terminfo/w/wy30-vb", + "usr/share/terminfo/w/wy520-48wpc", + "usr/share/terminfo/w/wyse60-25", + "usr/share/terminfo/w/wyse150", + "usr/share/terminfo/w/wy30-mc", + "usr/share/terminfo/w/wyse325-25", + "usr/share/terminfo/w/wy60-wvb", + "usr/share/terminfo/w/wyse520-48", + "usr/share/terminfo/w/wy75-wvb", + "usr/share/terminfo/w/wy120-w-vb", + "usr/share/terminfo/w/wy160-25-w", + "usr/share/terminfo/w/wyse99gt", + "usr/share/terminfo/w/wyse185-vb", + "usr/share/terminfo/w/wyse350-w", + "usr/share/terminfo/w/wyse520-36pc", + "usr/share/terminfo/w/wy325-w-vb", + "usr/share/terminfo/w/wy99fgt", + "usr/share/terminfo/w/wy75ap", + "usr/share/terminfo/w/wyse99gt-25", + "usr/share/terminfo/w/wyse350-wvb", + "usr/share/terminfo/w/wy85", + "usr/share/terminfo/w/wy370-101k", + "usr/share/terminfo/w/wy50-wvb", + "usr/share/terminfo/w/wyse60-42", + "usr/share/terminfo/w/wy99a-ansi", + "usr/share/terminfo/w/wyse520-36", + "usr/share/terminfo/w/wyse85-8bit", + "usr/share/terminfo/w/wyse520-48wpc", + "usr/share/terminfo/w/wy60-vb", + "usr/share/terminfo/w/wyse60-43", + "usr/share/terminfo/w/wy99fgta", + "usr/share/terminfo/w/wy325", + "usr/share/terminfo/w/wy-99fgta", + "usr/share/terminfo/w/wyse520-36wpc", + "usr/share/terminfo/w/wy60-43", + "usr/share/terminfo/w/wyse50-w", + "usr/share/terminfo/w/wyse60-25-w", + "usr/share/terminfo/w/wyse160-43", + "usr/share/terminfo/w/wyse325", + "usr/share/terminfo/w/wy350", + "usr/share/terminfo/w/wyse99gt-25-w", + "usr/share/terminfo/w/wyse120-wvb", + "usr/share/terminfo/w/wy85-w", + "usr/share/terminfo/w/wy85-vb", + "usr/share/terminfo/w/wyse120-25-w", + "usr/share/terminfo/w/wyse520-p-wvb", + "usr/share/terminfo/w/wsvt25m", + "usr/share/terminfo/w/wy370-wvb", + "usr/share/terminfo/w/wy120-vb", + "usr/share/terminfo/w/wyse520-vb", + "usr/share/terminfo/w/wyse75-wvb", + "usr/share/terminfo/w/wy99fa", + "usr/share/terminfo/w/wy99gt-wvb", + "usr/share/terminfo/w/wyse520-24", + "usr/share/terminfo/w/wy99gt-25", + "usr/share/terminfo/w/wyse-325", + "usr/share/terminfo/w/wyse99gt-wvb", + "usr/share/terminfo/w/wy99f", + "usr/share/terminfo/w/wy75-mc", + "usr/share/terminfo/w/wy60-w-vb", + "usr/share/terminfo/w/wy75-w", + "usr/share/terminfo/w/wy520-36w", + "usr/share/terminfo/w/wy99gt-w-vb", + "usr/share/terminfo/w/wyse99gt-vb", + "usr/share/terminfo/w/wy520-epc-vb", + "usr/share/terminfo/w/wyse85", + "usr/share/terminfo/w/wy160-w-vb", + "usr/share/terminfo/w/wy520-36pc", + "usr/share/terminfo/w/wyse185", + "usr/share/terminfo/w/wyse120", + "usr/share/terminfo/w/wy325-80", + "usr/share/terminfo/w/wyse325-w", + "usr/share/terminfo/w/wy120-25-w", + "usr/share/terminfo/w/wy350-vb", + "usr/share/terminfo/w/wy99gt-w", + "usr/share/terminfo/w/wyse325-wvb", + "usr/share/terminfo/w/wy160-43-w", + "usr/share/terminfo/w/wyse150-w", + "usr/share/terminfo/w/wy99gt", + "usr/share/terminfo/w/wy160", + "usr/share/terminfo/w/wyse85-w", + "usr/share/terminfo/w/wyse60-wvb", + "usr/share/terminfo/w/wyse520-36w", + "usr/share/terminfo/w/wyse30-mc", + "usr/share/terminfo/w/wy60-42", + "usr/share/terminfo/w/wyse50-wvb", + "usr/share/terminfo/w/wyse150-vb", + "usr/share/terminfo/w/wyse85-wvb", + "usr/share/terminfo/w/wyse160-vb", + "usr/share/terminfo/w/wyse185-24", + "usr/share/terminfo/w/wy325-43wvb", + "usr/share/terminfo/w/wyse99gt-w", + "usr/share/terminfo/w/wyse60-PC", + "usr/share/terminfo/w/wy520-epc-w", + "usr/share/terminfo/w/wy50-vb", + "usr/share/terminfo/w/wy520-vb", + "usr/share/terminfo/w/wyse325-vb", + "usr/share/terminfo/w/wy370-vb", + "usr/share/terminfo/w/wy60-AT", + "usr/share/terminfo/w/wy325-42w-vb", + "usr/share/terminfo/w/wyse325-42", + "usr/share/terminfo/w/wyse325-43w", + "usr/share/terminfo/w/wyse30", + "usr/share/terminfo/w/wy30", + "usr/share/terminfo/w/wy60-43-w", + "usr/share/terminfo/w/wy-75ap", + "usr/share/terminfo/w/wy350-w", + "usr/share/terminfo/w/wy85-8bit", + "usr/share/terminfo/w/wy325w-24", + "usr/share/terminfo/w/wy100q", + "usr/share/terminfo/w/wyse520-w", + "usr/share/terminfo/w/wy370-tek", + "usr/share/terminfo/w/wy150-25-w", + "usr/share/terminfo/w/wy325-43", + "usr/share/terminfo/w/wy520-epc", + "usr/share/terminfo/w/wyse60", + "usr/share/terminfo/w/wyse160-25-w", + "usr/share/terminfo/w/wyse60-42-w", + "usr/share/terminfo/w/wyse520-48pc", + "usr/share/terminfo/w/wyse120-vb", + "usr/share/terminfo/w/wyse60-43-w", + "usr/share/terminfo/w/wyse60-vb", + "usr/share/terminfo/w/wyse85-vb", + "usr/share/terminfo/w/wsvt25", + "usr/share/terminfo/w/wyse75ap", + "usr/share/terminfo/w/wy99gt-tek", + "usr/share/terminfo/w/wy60", + "usr/share/terminfo/w/wyse150-25-w", + "usr/share/terminfo/w/wy160-43", + "usr/share/terminfo/w/wyse520-pc-24", + "usr/share/terminfo/w/wy520-36wpc", + "usr/share/terminfo/w/wy325-w", + "usr/share/terminfo/w/wyse325-25w", + "usr/share/terminfo/w/wrenw", + "usr/share/terminfo/w/wy50-w", + "usr/share/terminfo/w/wy185-24", + "usr/share/terminfo/w/wyse160", + "usr/share/terminfo/w/wyse75-mc", + "usr/share/terminfo/w/wy60-42-w", + "usr/share/terminfo/w/wy120-wvb", + "usr/share/terminfo/w/wy520", + "usr/share/terminfo/w/wy-99fgt", + "usr/share/terminfo/w/wyse75-w", + "usr/share/terminfo/w/wy350-wvb", + "usr/share/terminfo/w/wy325-25w", + "usr/share/terminfo/w/wyse-75ap", + "usr/share/terminfo/w/wy60-25", + "usr/share/terminfo/w/wy520-wvb", + "usr/share/terminfo/w/wy325-vb", + "usr/share/terminfo/w/wy325-43w", + "usr/share/terminfo/w/wyse75", + "usr/share/terminfo/w/wy160-42", + "usr/share/terminfo/w/wsiris", + "usr/share/terminfo/w/wyse50-vb", + "usr/share/terminfo/w/wy520-epc-wvb", + "usr/share/terminfo/w/wy150-w", + "usr/share/terminfo/w/wyse185-w", + "usr/share/terminfo/w/wy370", + "usr/share/terminfo/w/wy120", + "usr/share/terminfo/w/wyse160-43-w", + "usr/share/terminfo/w/wy100", + "usr/share/terminfo/w/wyse325-42w", + "usr/share/terminfo/w/wy520-24", + "usr/share/terminfo/w/wyse350-vb", + "usr/share/terminfo/w/wyse120-w", + "usr/share/terminfo/w/wy370-nk", + "usr/share/terminfo/w/wy325-43w-vb", + "usr/share/terminfo/w/wy370-EPC", + "usr/share/terminfo/w/wy99gt-25-w", + "usr/share/terminfo/w/wyse30-vb", + "usr/share/terminfo/w/wy160-tek", + "usr/share/terminfo/w/wy75-vb", + "usr/share/terminfo/w/wy325-42w", + "usr/share/terminfo/w/wyse60-AT", + "usr/share/terminfo/w/wyse60-w", + "usr/share/terminfo/w/wyse520-epc", + "usr/share/terminfo/w/wyse-vp", + "usr/share/terminfo/w/wy85-wvb", + "usr/share/terminfo/w/wy150-w-vb", + "usr/share/terminfo/w/wy520-48w", + "usr/share/terminfo/w/wyse120-25", + "usr/share/terminfo/w/wyse75-vb", + "usr/share/terminfo/w/wyse50-mc", + "usr/share/terminfo/w/wyse160-w", + "usr/share/terminfo/w/wy325-42wvb", + "usr/share/terminfo/w/wyse520-pc-vb", + "usr/share/terminfo/w/wy325-25", + "usr/share/terminfo/w/wy370-w", + "usr/share/terminfo/w/wyse150-25", + "usr/share/terminfo/w/wyse520-wvb", + "usr/share/terminfo/w/wyse520-48w", + "usr/share/terminfo/w/wy50-mc", + "usr/share/terminfo/w/wyse150-w-vb", + "usr/share/terminfo/w/wy185-wvb", + "usr/share/terminfo/w/wy160-vb", + "usr/share/terminfo/w/wy120-w", + "usr/share/terminfo/w/wyse520", + "usr/share/terminfo/w/wy185-vb", + "usr/share/terminfo/w/wy520-36", + "usr/share/terminfo/w/wy60-25-w", + "usr/share/terminfo/w/wy150-vb", + "usr/share/terminfo/w/wyse160-42-w", + "usr/share/terminfo/w/wy160-w", + "usr/share/terminfo/w/wyse520-epc-w", + "usr/share/terminfo/w/wyse160-25", + "usr/share/terminfo/w/wy120-25", + "usr/share/terminfo/w/wyse50", + "usr/share/terminfo/w/wren", + "usr/share/terminfo/w/wy50", + "usr/share/terminfo/w/wyse160-42", + "usr/share/terminfo/w/wy150", + "usr/share/terminfo/w/wy99gt-vb", + "usr/share/terminfo/w/wy60-316X", + "usr/share/terminfo/w/wy370-105k", + "usr/share/terminfo/w/wyse160-wvb", + "usr/share/terminfo/w/wy160-25", + "usr/share/terminfo/w/wy185", + "usr/share/terminfo/w/wy370-rv", + "usr/share/terminfo/w/wy520-epc-24", + "usr/share/terminfo/w/wy60-PC", + "usr/share/terminfo/w/wyse60-316X", + "usr/share/terminfo/w/wy520-48", + "usr/share/terminfo/w/wy99-ansi", + "usr/share/terminfo/w/wy150-25", + "usr/share/terminfo/w/wy185-w", + "usr/share/terminfo/w/wy60-w", + "usr/share/terminfo/w/wy160-42-w", + "usr/share/terminfo/w/wy325-42", + "usr/share/terminfo/w/wyse350", + "usr/share/terminfo/w/wy325-wvb", + "usr/share/terminfo/w/wyse325-43", + "usr/share/terminfo/w/wy75", + "usr/share/terminfo/w/wy520-w", + "usr/share/terminfo/E/Eterm-color", + "usr/share/terminfo/E/Eterm-256color", + "usr/share/terminfo/E/Eterm", + "usr/share/terminfo/E/Eterm-88color", + "usr/share/terminfo/t/tek4014", + "usr/share/terminfo/t/tvi912b-vb-unk", + "usr/share/terminfo/t/tvi920", + "usr/share/terminfo/t/tvi912c-vb-unk", + "usr/share/terminfo/t/tek4112-5", + "usr/share/terminfo/t/tvi912b-p", + "usr/share/terminfo/t/tty5410", + "usr/share/terminfo/t/tandem6510", + "usr/share/terminfo/t/ti928", + "usr/share/terminfo/t/tek4025-17-ws", + "usr/share/terminfo/t/ti916-8-132", + "usr/share/terminfo/t/tt505-22", + "usr/share/terminfo/t/tmux-256color", + "usr/share/terminfo/t/tvi912c-unk", + "usr/share/terminfo/t/tvi955-hb", + "usr/share/terminfo/t/tty5410v1-w", + "usr/share/terminfo/t/tvi92D", + "usr/share/terminfo/t/tvi912c-unk-vb", + "usr/share/terminfo/t/tvi925-hi", + "usr/share/terminfo/t/tek4025ex", + "usr/share/terminfo/t/tek4015-sm", + "usr/share/terminfo/t/tty4420", + "usr/share/terminfo/t/ts100-ctxt", + "usr/share/terminfo/t/tek4106brl", + "usr/share/terminfo/t/tvi912b+2p", + "usr/share/terminfo/t/tvi920b-unk-vb", + "usr/share/terminfo/t/tty5420-w", + "usr/share/terminfo/t/tty5420-w-nl", + "usr/share/terminfo/t/tvi920c-vb-p", + "usr/share/terminfo/t/tvi910+", + "usr/share/terminfo/t/tty5620-1", + "usr/share/terminfo/t/ti926", + "usr/share/terminfo/t/tvi912b", + "usr/share/terminfo/t/tty5420-w-rv", + "usr/share/terminfo/t/tvi920b-mc-2p", + "usr/share/terminfo/t/tvi912b-p-vb", + "usr/share/terminfo/t/tek", + "usr/share/terminfo/t/tvi920b-unk", + "usr/share/terminfo/t/tvi803", + "usr/share/terminfo/t/tvi912c", + "usr/share/terminfo/t/tvi912c-mc-vb", + "usr/share/terminfo/t/teraterm", + "usr/share/terminfo/t/tab132-15", + "usr/share/terminfo/t/ts1", + "usr/share/terminfo/t/tek4025a", + "usr/share/terminfo/t/tty37", + "usr/share/terminfo/t/tek4014-sm", + "usr/share/terminfo/t/ti916-8", + "usr/share/terminfo/t/ti924w", + "usr/share/terminfo/t/tek4112-nd", + "usr/share/terminfo/t/tvi912c-unk-2p", + "usr/share/terminfo/t/tvi9065", + "usr/share/terminfo/t/tvi950-rv-4p", + "usr/share/terminfo/t/tvi970-2p", + "usr/share/terminfo/t/ti_ansi", + "usr/share/terminfo/t/tandem653", + "usr/share/terminfo/t/tvi920c-2p-unk", + "usr/share/terminfo/t/tvi912c-mc", + "usr/share/terminfo/t/tvi912b-vb-p", + "usr/share/terminfo/t/tty5410v1", + "usr/share/terminfo/t/tty5620-34", + "usr/share/terminfo/t/tty5420-nl", + "usr/share/terminfo/t/tek4113-34", + "usr/share/terminfo/t/tty4424", + "usr/share/terminfo/t/tek4113", + "usr/share/terminfo/t/tn1200", + "usr/share/terminfo/t/tab132-w-rv", + "usr/share/terminfo/t/terminet300", + "usr/share/terminfo/t/tvi950-rv", + "usr/share/terminfo/t/ti800", + "usr/share/terminfo/t/ts1p", + "usr/share/terminfo/t/ti924", + "usr/share/terminfo/t/tws2103", + "usr/share/terminfo/t/tw52-m", + "usr/share/terminfo/t/tvi920c-p", + "usr/share/terminfo/t/tek4207-s", + "usr/share/terminfo/t/tvi920c-mc-2p", + "usr/share/terminfo/t/teken", + "usr/share/terminfo/t/ti745", + "usr/share/terminfo/t/tvi920b-2p-p", + "usr/share/terminfo/t/tty5420+nl", + "usr/share/terminfo/t/tvi955", + "usr/share/terminfo/t/tvi912b-p-2p", + "usr/share/terminfo/t/tek4025", + "usr/share/terminfo/t/tvi912b-2p-mc", + "usr/share/terminfo/t/tvi970", + "usr/share/terminfo/t/tek4013", + "usr/share/terminfo/t/ti931", + "usr/share/terminfo/t/teraterm4.59", + "usr/share/terminfo/t/tt52", + "usr/share/terminfo/t/tvi950-rv-2p", + "usr/share/terminfo/t/tty35", + "usr/share/terminfo/t/trs16", + "usr/share/terminfo/t/tvi955-w", + "usr/share/terminfo/t/ts100-sp", + "usr/share/terminfo/t/tvi912b-unk-vb", + "usr/share/terminfo/t/tvi920b+fn", + "usr/share/terminfo/t/tty43", + "usr/share/terminfo/t/ti924-8", + "usr/share/terminfo/t/tvi920b-unk-2p", + "usr/share/terminfo/t/terminet1200", + "usr/share/terminfo/t/tvi912c-vb-mc", + "usr/share/terminfo/t/tek4025-cr", + "usr/share/terminfo/t/tvi912c-2p", + "usr/share/terminfo/t/tvi920b-2p-mc", + "usr/share/terminfo/t/tvi970-vb", + "usr/share/terminfo/t/tek4112", + "usr/share/terminfo/t/ts-1", + "usr/share/terminfo/t/tws2102-sna", + "usr/share/terminfo/t/tty5425-nl", + "usr/share/terminfo/t/tek4107", + "usr/share/terminfo/t/terminology-0.6.1", + "usr/share/terminfo/t/tvi920c", + "usr/share/terminfo/t/ti928-8", + "usr/share/terminfo/t/tty4424m", + "usr/share/terminfo/t/tvi920c-p-2p", + "usr/share/terminfo/t/tws2103-sna", + "usr/share/terminfo/t/tek4105", + "usr/share/terminfo/t/ti735", + "usr/share/terminfo/t/tvi912c-mc-2p", + "usr/share/terminfo/t/tvi912b+printer", + "usr/share/terminfo/t/tty5425-w", + "usr/share/terminfo/t/ti916-132", + "usr/share/terminfo/t/tek4205", + "usr/share/terminfo/t/tn300", + "usr/share/terminfo/t/t10", + "usr/share/terminfo/t/tvi920b-2p-unk", + "usr/share/terminfo/t/tvi920c-2p-mc", + "usr/share/terminfo/t/tek4015", + "usr/share/terminfo/t/ti916-220-8", + "usr/share/terminfo/t/tvi925", + "usr/share/terminfo/t/tek4107brl", + "usr/share/terminfo/t/t3800", + "usr/share/terminfo/t/tab132", + "usr/share/terminfo/t/tvi912c-vb-p", + "usr/share/terminfo/t/tty5420", + "usr/share/terminfo/t/tvi912b-2p", + "usr/share/terminfo/t/tab132-w", + "usr/share/terminfo/t/tty4424-1", + "usr/share/terminfo/t/tek4115", + "usr/share/terminfo/t/tvi912b-2p-p", + "usr/share/terminfo/t/t16", + "usr/share/terminfo/t/tvi920b-p-vb", + "usr/share/terminfo/t/tty5420-rv-nl", + "usr/share/terminfo/t/tvi912cc", + "usr/share/terminfo/t/tek4105-30", + "usr/share/terminfo/t/tvi920b-vb-unk", + "usr/share/terminfo/t/terminator", + "usr/share/terminfo/t/tab", + "usr/share/terminfo/t/tvi912c-p", + "usr/share/terminfo/t/tvi920b-vb", + "usr/share/terminfo/t/tty4426", + "usr/share/terminfo/t/trs80II", + "usr/share/terminfo/t/tvi912c-2p-p", + "usr/share/terminfo/t/tvi914", + "usr/share/terminfo/t/tvi912b-vb", + "usr/share/terminfo/t/ti924-8w", + "usr/share/terminfo/t/tty33", + "usr/share/terminfo/t/tvi920c-vb-unk", + "usr/share/terminfo/t/tvi920c-mc-vb", + "usr/share/terminfo/t/tek4025-ex", + "usr/share/terminfo/t/ttydmd", + "usr/share/terminfo/t/tvi920c-2p", + "usr/share/terminfo/t/tvi920c-unk-vb", + "usr/share/terminfo/t/tek4114", + "usr/share/terminfo/t/tek4125", + "usr/share/terminfo/t/t1061f", + "usr/share/terminfo/t/tek4109brl", + "usr/share/terminfo/t/tvi912c-p-2p", + "usr/share/terminfo/t/tvi920b-p", + "usr/share/terminfo/t/tek4023", + "usr/share/terminfo/t/tab132-rv", + "usr/share/terminfo/t/tvi912b-mc-vb", + "usr/share/terminfo/t/tvi920b-vb-p", + "usr/share/terminfo/t/teletec", + "usr/share/terminfo/t/teleray", + "usr/share/terminfo/t/tvi910", + "usr/share/terminfo/t/tvi920b-2p", + "usr/share/terminfo/t/tty5620-s", + "usr/share/terminfo/t/tvi920b-mc", + "usr/share/terminfo/t/tvi912b+dim", + "usr/share/terminfo/t/tvi950-4p", + "usr/share/terminfo/t/tty40", + "usr/share/terminfo/t/tvi920c-vb-mc", + "usr/share/terminfo/t/tvi920b", + "usr/share/terminfo/t/tvi912c-2p-unk", + "usr/share/terminfo/t/tvi920c-unk", + "usr/share/terminfo/t/tek4113-nd", + "usr/share/terminfo/t/terminology-1.0.0", + "usr/share/terminfo/t/ts100", + "usr/share/terminfo/t/tek4012", + "usr/share/terminfo/t/ts-1p", + "usr/share/terminfo/t/t653x", + "usr/share/terminfo/t/tek4024", + "usr/share/terminfo/t/tkterm", + "usr/share/terminfo/t/tvi912b+vb", + "usr/share/terminfo/t/tw52", + "usr/share/terminfo/t/teraterm2.3", + "usr/share/terminfo/t/tty5425", + "usr/share/terminfo/t/tvi912c-vb", + "usr/share/terminfo/t/tek4027-ex", + "usr/share/terminfo/t/ti916-220-7", + "usr/share/terminfo/t/tvi912b-mc", + "usr/share/terminfo/t/tvi912b-mc-2p", + "usr/share/terminfo/t/tw100", + "usr/share/terminfo/t/trs2", + "usr/share/terminfo/t/tvi912c-2p-mc", + "usr/share/terminfo/t/terminology", + "usr/share/terminfo/t/tek4404", + "usr/share/terminfo/t/tws-generic", + "usr/share/terminfo/t/tty5620-24", + "usr/share/terminfo/t/tvi912b-unk-2p", + "usr/share/terminfo/t/trsII", + "usr/share/terminfo/t/tty5620", + "usr/share/terminfo/t/tvi920c-unk-2p", + "usr/share/terminfo/t/tvi912b+mc", + "usr/share/terminfo/t/tvi912b-vb-mc", + "usr/share/terminfo/t/ti926-8", + "usr/share/terminfo/t/tvi920c-p-vb", + "usr/share/terminfo/t/terminet", + "usr/share/terminfo/t/ti700", + "usr/share/terminfo/t/tvi912c-p-vb", + "usr/share/terminfo/t/tmux", + "usr/share/terminfo/t/tvi912", + "usr/share/terminfo/t/tek4207", + "usr/share/terminfo/t/tvi950", + "usr/share/terminfo/t/tvi920c-2p-p", + "usr/share/terminfo/t/tek4027", + "usr/share/terminfo/t/tvi920c-mc", + "usr/share/terminfo/t/tvi920b-p-2p", + "usr/share/terminfo/t/t3700", + "usr/share/terminfo/t/tvi924", + "usr/share/terminfo/t/ti916", + "usr/share/terminfo/t/tty5420-w-rv-n", + "usr/share/terminfo/t/ti733", + "usr/share/terminfo/t/tvi920b-vb-mc", + "usr/share/terminfo/t/tek4025-17", + "usr/share/terminfo/t/tvi920c-vb", + "usr/share/terminfo/t/tvi920b-mc-vb", + "usr/share/terminfo/t/tvi912b-2p-unk", + "usr/share/terminfo/t/tt", + "usr/share/terminfo/t/tvipt", + "usr/share/terminfo/t/tvi921", + "usr/share/terminfo/t/tty5410-w", + "usr/share/terminfo/t/tek4105a", + "usr/share/terminfo/t/tw52-color", + "usr/share/terminfo/t/tvi912b-unk", + "usr/share/terminfo/t/t1061", + "usr/share/terminfo/t/tvi950-2p", + "usr/share/terminfo/t/tgtelnet", + "usr/share/terminfo/t/tvi92B", + "usr/share/terminfo/t/tty5420-rv", + "usr/share/terminfo/t/tek4109", + "usr/share/terminfo/l/luna", + "usr/share/terminfo/l/linux-koi8", + "usr/share/terminfo/l/linux-16color", + "usr/share/terminfo/l/ln03", + "usr/share/terminfo/l/linux-c-nc", + "usr/share/terminfo/l/linux-koi8r", + "usr/share/terminfo/l/liswb", + "usr/share/terminfo/l/linux2.2", + "usr/share/terminfo/l/linux-lat", + "usr/share/terminfo/l/lisaterm-w", + "usr/share/terminfo/l/linux-m", + "usr/share/terminfo/l/lft", + "usr/share/terminfo/l/linux-m2", + "usr/share/terminfo/l/lft-pc850", + "usr/share/terminfo/l/ln03-w", + "usr/share/terminfo/l/linux3.0", + "usr/share/terminfo/l/lisa", + "usr/share/terminfo/l/lpr", + "usr/share/terminfo/l/linux-basic", + "usr/share/terminfo/l/linux-m1b", + "usr/share/terminfo/l/lisaterm", + "usr/share/terminfo/l/linux", + "usr/share/terminfo/l/luna68k", + "usr/share/terminfo/l/linux-vt", + "usr/share/terminfo/l/linux-nic", + "usr/share/terminfo/l/linux-c", + "usr/share/terminfo/l/la120", + "usr/share/terminfo/l/layer", + "usr/share/terminfo/l/linux2.6", + "usr/share/terminfo/l/linux2.6.26", + "usr/share/terminfo/l/linux-m1", + "usr/share/terminfo/p/pccon0", + "usr/share/terminfo/p/pro350", + "usr/share/terminfo/p/prism14-m", + "usr/share/terminfo/p/pcix", + "usr/share/terminfo/p/pcansi-33", + "usr/share/terminfo/p/pt505-24", + "usr/share/terminfo/p/prism8gl", + "usr/share/terminfo/p/pc3-bold", + "usr/share/terminfo/p/pcvt25", + "usr/share/terminfo/p/p12-m", + "usr/share/terminfo/p/putty-m1", + "usr/share/terminfo/p/putty-noapp", + "usr/share/terminfo/p/pcansi-mono", + "usr/share/terminfo/p/pcz19", + "usr/share/terminfo/p/pccon+colors", + "usr/share/terminfo/p/pcvt43", + "usr/share/terminfo/p/pcansi25m", + "usr/share/terminfo/p/pcvt28", + "usr/share/terminfo/p/pcplot", + "usr/share/terminfo/p/pty", + "usr/share/terminfo/p/pckermit120", + "usr/share/terminfo/p/p19", + "usr/share/terminfo/p/p9", + "usr/share/terminfo/p/pcansi-25", + "usr/share/terminfo/p/prism12", + "usr/share/terminfo/p/prism12-m", + "usr/share/terminfo/p/pccon+sgr+acs", + "usr/share/terminfo/p/psterm-96x48", + "usr/share/terminfo/p/pt250", + "usr/share/terminfo/p/pcvt43w", + "usr/share/terminfo/p/pe6312", + "usr/share/terminfo/p/pccon", + "usr/share/terminfo/p/pcansi43", + "usr/share/terminfo/p/pt505", + "usr/share/terminfo/p/prism9-8-w", + "usr/share/terminfo/p/p4", + "usr/share/terminfo/p/p12-w", + "usr/share/terminfo/p/pe7000m", + "usr/share/terminfo/p/pcansi-43", + "usr/share/terminfo/p/pcvt25-color", + "usr/share/terminfo/p/pccon0-m", + "usr/share/terminfo/p/prism12-w", + "usr/share/terminfo/p/pccons", + "usr/share/terminfo/p/putty+fnkeys+esc", + "usr/share/terminfo/p/p12-m-w", + "usr/share/terminfo/p/pcvt40w", + "usr/share/terminfo/p/pcvt40", + "usr/share/terminfo/p/pcansi25", + "usr/share/terminfo/p/pt100", + "usr/share/terminfo/p/pe1100", + "usr/share/terminfo/p/p9-8-w", + "usr/share/terminfo/p/ps300", + "usr/share/terminfo/p/p9-8", + "usr/share/terminfo/p/psterm-80x24", + "usr/share/terminfo/p/putty", + "usr/share/terminfo/p/putty-vt100", + "usr/share/terminfo/p/prism14-w", + "usr/share/terminfo/p/psterm", + "usr/share/terminfo/p/pccon-m", + "usr/share/terminfo/p/pcansi-m", + "usr/share/terminfo/p/psterm-90x28", + "usr/share/terminfo/p/pcansi-33-m", + "usr/share/terminfo/p/p7", + "usr/share/terminfo/p/pc3", + "usr/share/terminfo/p/pt200w", + "usr/share/terminfo/p/putty+fnkeys+sco", + "usr/share/terminfo/p/putty+fnkeys+linux", + "usr/share/terminfo/p/pcmw", + "usr/share/terminfo/p/pmcons", + "usr/share/terminfo/p/prism14", + "usr/share/terminfo/p/pccon+keys", + "usr/share/terminfo/p/pt100w", + "usr/share/terminfo/p/putty+fnkeys+vt400", + "usr/share/terminfo/p/pcconsole", + "usr/share/terminfo/p/pmconsole", + "usr/share/terminfo/p/psterm-fast", + "usr/share/terminfo/p/prism12-m-w", + "usr/share/terminfo/p/pc3r-m", + "usr/share/terminfo/p/prism8-w", + "usr/share/terminfo/p/putty+fnkeys+xterm", + "usr/share/terminfo/p/pilot", + "usr/share/terminfo/p/pcvt50w", + "usr/share/terminfo/p/pe1200", + "usr/share/terminfo/p/p14-w", + "usr/share/terminfo/p/pcvt25w", + "usr/share/terminfo/p/pe550", + "usr/share/terminfo/p/pe1251", + "usr/share/terminfo/p/pc-coherent", + "usr/share/terminfo/p/pckermit12", + "usr/share/terminfo/p/psterm-basic", + "usr/share/terminfo/p/pc-venix", + "usr/share/terminfo/p/p12", + "usr/share/terminfo/p/pcvt35w", + "usr/share/terminfo/p/prism5", + "usr/share/terminfo/p/pt210", + "usr/share/terminfo/p/printer", + "usr/share/terminfo/p/pcansi-43-m", + "usr/share/terminfo/p/pe7000c", + "usr/share/terminfo/p/pt505-22", + "usr/share/terminfo/p/pcansi33m", + "usr/share/terminfo/p/pc7300", + "usr/share/terminfo/p/prism14-m-w", + "usr/share/terminfo/p/prism9-8", + "usr/share/terminfo/p/p14-m", + "usr/share/terminfo/p/pccon+sgr+acs0", + "usr/share/terminfo/p/prism9-w", + "usr/share/terminfo/p/pcansi", + "usr/share/terminfo/p/putty-m2", + "usr/share/terminfo/p/pcansi-25-m", + "usr/share/terminfo/p/putty-256color", + "usr/share/terminfo/p/p5", + "usr/share/terminfo/p/prism7", + "usr/share/terminfo/p/pcvt28w", + "usr/share/terminfo/p/putty-sco", + "usr/share/terminfo/p/psx_ansi", + "usr/share/terminfo/p/pcvt50", + "usr/share/terminfo/p/pt200", + "usr/share/terminfo/p/pckermit", + "usr/share/terminfo/p/p8-w", + "usr/share/terminfo/p/putty+fnkeys+vt100", + "usr/share/terminfo/p/pc-minix", + "usr/share/terminfo/p/prism8", + "usr/share/terminfo/p/pcansi33", + "usr/share/terminfo/p/putty+fnkeys", + "usr/share/terminfo/p/pc6300plus", + "usr/share/terminfo/p/p14", + "usr/share/terminfo/p/prism9", + "usr/share/terminfo/p/pc3r", + "usr/share/terminfo/p/pe6300", + "usr/share/terminfo/p/prism4", + "usr/share/terminfo/p/pccon+base", + "usr/share/terminfo/p/p14-m-w", + "usr/share/terminfo/p/p8", + "usr/share/terminfo/p/pt250w", + "usr/share/terminfo/p/pe6100", + "usr/share/terminfo/p/putty-m1b", + "usr/share/terminfo/p/p8gl", + "usr/share/terminfo/p/pcvt35", + "usr/share/terminfo/p/pcvtXX", + "usr/share/terminfo/p/prism2", + "usr/share/terminfo/p/p9-w", + "usr/share/terminfo/9/955-hb", + "usr/share/terminfo/9/9term", + "usr/share/terminfo/9/955-w", + "usr/share/terminfo/6/605x", + "usr/share/terminfo/6/605x-dg", + "usr/share/terminfo/6/630-lm", + "usr/share/terminfo/6/6053-dg", + "usr/share/terminfo/6/630MTG-24", + "usr/share/terminfo/6/6053", + "usr/share/terminfo/Q/Q306-8-pc", + "usr/share/terminfo/Q/Q310-vip-w", + "usr/share/terminfo/Q/Q310-vip-H", + "usr/share/terminfo/Q/Q310-vip-w-am", + "usr/share/terminfo/Q/Q310-vip-Hw", + "usr/share/terminfo/Q/Q310-vip-H-am", + "usr/share/terminfo/x/xnuppc+80x25", + "usr/share/terminfo/x/xterm-sun", + "usr/share/terminfo/x/xterm+sm+1006", + "usr/share/terminfo/x/xterm-1005", + "usr/share/terminfo/x/xnuppc-90x30-m", + "usr/share/terminfo/x/xterm-24", + "usr/share/terminfo/x/xterm-noapp", + "usr/share/terminfo/x/xterm-xf86-v40", + "usr/share/terminfo/x/xterm+r6f2", + "usr/share/terminfo/x/xtermm", + "usr/share/terminfo/x/xnuppc+90x30", + "usr/share/terminfo/x/xnuppc-200x75-m", + "usr/share/terminfo/x/xtalk", + "usr/share/terminfo/x/xnuppc+128x40", + "usr/share/terminfo/x/xterm-xf86-v333", + "usr/share/terminfo/x/xterm+x11hilite", + "usr/share/terminfo/x/xterm+pcc0", + "usr/share/terminfo/x/xfce", + "usr/share/terminfo/x/xterm-x11hilite", + "usr/share/terminfo/x/x68k", + "usr/share/terminfo/x/x10term", + "usr/share/terminfo/x/xterm+vt+edit", + "usr/share/terminfo/x/xterm+88color", + "usr/share/terminfo/x/xnuppc-128x40", + "usr/share/terminfo/x/xterm+pcc3", + "usr/share/terminfo/x/xterm-pcolor", + "usr/share/terminfo/x/xnuppc-b", + "usr/share/terminfo/x/xterm-256color", + "usr/share/terminfo/x/xterm-xf86-v43", + "usr/share/terminfo/x/x1700", + "usr/share/terminfo/x/xtermc", + "usr/share/terminfo/x/xerox1720", + "usr/share/terminfo/x/xterms", + "usr/share/terminfo/x/xl83", + "usr/share/terminfo/x/xterm+x10mouse", + "usr/share/terminfo/x/xnuppc-m-f", + "usr/share/terminfo/x/xterm-vt52", + "usr/share/terminfo/x/xterm-r6", + "usr/share/terminfo/x/xterm-1006", + "usr/share/terminfo/x/xnuppc-f", + "usr/share/terminfo/x/xnuppc-128x48", + "usr/share/terminfo/x/xterm-hp", + "usr/share/terminfo/x/xnuppc-80x25", + "usr/share/terminfo/x/xerox", + "usr/share/terminfo/x/xterm+pcfkeys", + "usr/share/terminfo/x/xnuppc-128x40-m", + "usr/share/terminfo/x/xnuppc+160x64", + "usr/share/terminfo/x/xterm-x11mouse", + "usr/share/terminfo/x/xterm-r5", + "usr/share/terminfo/x/xterm-1003", + "usr/share/terminfo/x/xterm+pcf0", + "usr/share/terminfo/x/xterm", + "usr/share/terminfo/x/xterm+pce2", + "usr/share/terminfo/x/xterm+noapp", + "usr/share/terminfo/x/xnuppc", + "usr/share/terminfo/x/xnuppc+basic", + "usr/share/terminfo/x/xnuppc+b", + "usr/share/terminfo/x/x68k-ite", + "usr/share/terminfo/x/xnuppc+200x64", + "usr/share/terminfo/x/xterm+sl", + "usr/share/terminfo/x/xnuppc-m-f2", + "usr/share/terminfo/x/xterm-xf86-v33", + "usr/share/terminfo/x/xterm+256color", + "usr/share/terminfo/x/xnuppc+256x96", + "usr/share/terminfo/x/x1750", + "usr/share/terminfo/x/xdku", + "usr/share/terminfo/x/xnuppc-200x75", + "usr/share/terminfo/x/xterm+kbs", + "usr/share/terminfo/x/xterm+sm+1002", + "usr/share/terminfo/x/xerox-lm", + "usr/share/terminfo/x/xnuppc-90x30", + "usr/share/terminfo/x/xterm-xf86-v44", + "usr/share/terminfo/x/xnuppc-m-b", + "usr/share/terminfo/x/xterm+sm+1003", + "usr/share/terminfo/x/xnuppc+100x37", + "usr/share/terminfo/x/xnuppc+80x30", + "usr/share/terminfo/x/xnuppc-128x48-m", + "usr/share/terminfo/x/xterm+edit", + "usr/share/terminfo/x/xterm-basic", + "usr/share/terminfo/x/xnuppc+112x37", + "usr/share/terminfo/x/xterm-color", + "usr/share/terminfo/x/xiterm", + "usr/share/terminfo/x/xnuppc-100x37", + "usr/share/terminfo/x/xnuppc+c", + "usr/share/terminfo/x/xnuppc-256x96-m", + "usr/share/terminfo/x/xterm+pc+edit", + "usr/share/terminfo/x/xterm+app", + "usr/share/terminfo/x/x1700-lm", + "usr/share/terminfo/x/xterm+pcc1", + "usr/share/terminfo/x/xterm+pcc2", + "usr/share/terminfo/x/xterm-xi", + "usr/share/terminfo/x/xnuppc-144x48", + "usr/share/terminfo/x/xterm-x10mouse", + "usr/share/terminfo/x/xnuppc+144x48", + "usr/share/terminfo/x/xwsh", + "usr/share/terminfo/x/xterm1", + "usr/share/terminfo/x/xterm-xfree86", + "usr/share/terminfo/x/xnuppc-200x64", + "usr/share/terminfo/x/xterm-utf8", + "usr/share/terminfo/x/xnuppc-144x48-m", + "usr/share/terminfo/x/xterm-xf86-v32", + "usr/share/terminfo/x/xenix", + "usr/share/terminfo/x/xnuppc-160x64-m", + "usr/share/terminfo/x/xnuppc-112x37-m", + "usr/share/terminfo/x/x820", + "usr/share/terminfo/x/xterm-new", + "usr/share/terminfo/x/xnuppc-112x37", + "usr/share/terminfo/x/xnuppc+128x48", + "usr/share/terminfo/x/xterm-sco", + "usr/share/terminfo/x/xnuppc-160x64", + "usr/share/terminfo/x/xterm-8bit", + "usr/share/terminfo/x/xterm+tmux", + "usr/share/terminfo/x/xterm-nic", + "usr/share/terminfo/x/xnuppc-80x25-m", + "usr/share/terminfo/x/xnuppc+f", + "usr/share/terminfo/x/xterm+sl-twm", + "usr/share/terminfo/x/x1720", + "usr/share/terminfo/x/xterm+x11mouse", + "usr/share/terminfo/x/xnuppc+200x75", + "usr/share/terminfo/x/xerox820", + "usr/share/terminfo/x/xterms-sun", + "usr/share/terminfo/x/xterm-16color", + "usr/share/terminfo/x/xnuppc-100x37-m", + "usr/share/terminfo/x/xnuppc-200x64-m", + "usr/share/terminfo/x/xnuppc-f2", + "usr/share/terminfo/x/xnuppc-256x96", + "usr/share/terminfo/x/xterm-bold", + "usr/share/terminfo/x/xterm-88color", + "usr/share/terminfo/x/xnuppc+f2", + "usr/share/terminfo/x/xterm-old", + "usr/share/terminfo/x/xterm+pcf2", + "usr/share/terminfo/x/xnuppc-80x30-m", + "usr/share/terminfo/x/xnuppc-m", + "usr/share/terminfo/x/xnuppc-80x30", + "usr/share/terminfo/x/xterm-vt220", + "usr/share/terminfo/x/xterm+256setaf", + "usr/share/terminfo/x/xterm+sm+1005", + "usr/share/terminfo/x/xterm-1002", + "usr/share/terminfo/j/jfbterm", + "usr/share/terminfo/j/jaixterm-m", + "usr/share/terminfo/j/jaixterm", + "usr/share/terminfo/j/jerq", + "usr/share/terminfo/n/ncr260vt100wan", + "usr/share/terminfo/n/news28-a", + "usr/share/terminfo/n/nwe501-o", + "usr/share/terminfo/n/nwp512-a", + "usr/share/terminfo/n/ncrvt100pp", + "usr/share/terminfo/n/news", + "usr/share/terminfo/n/nsterm-7-s", + "usr/share/terminfo/n/nsterm-c", + "usr/share/terminfo/n/nsterm-s-7", + "usr/share/terminfo/n/news-unk", + "usr/share/terminfo/n/news-42", + "usr/share/terminfo/n/nansi.sysk", + "usr/share/terminfo/n/ntconsole-25-w-vt", + "usr/share/terminfo/n/ncr260vt300pp", + "usr/share/terminfo/n/ncr260vt200wpp", + "usr/share/terminfo/n/news-33", + "usr/share/terminfo/n/nwp512", + "usr/share/terminfo/n/ntconsole-25", + "usr/share/terminfo/n/nsterm-m-7", + "usr/share/terminfo/n/nec", + "usr/share/terminfo/n/nwp513-a", + "usr/share/terminfo/n/news-a", + "usr/share/terminfo/n/ntconsole-w", + "usr/share/terminfo/n/ncsa-vt220-8", + "usr/share/terminfo/n/nwp517-w", + "usr/share/terminfo/n/news31", + "usr/share/terminfo/n/ncr7901", + "usr/share/terminfo/n/ncr260intpp", + "usr/share/terminfo/n/news-29-euc", + "usr/share/terminfo/n/nwp518-o", + "usr/share/terminfo/n/ncr160vt200wan", + "usr/share/terminfo/n/ncr7900iv", + "usr/share/terminfo/n/ndr9500-mc-nl", + "usr/share/terminfo/n/ncr260intwpp", + "usr/share/terminfo/n/nwp251-o", + "usr/share/terminfo/n/nsterm-build361", + "usr/share/terminfo/n/netbsd6", + "usr/share/terminfo/n/nwp513-o", + "usr/share/terminfo/n/nsterm-c-s", + "usr/share/terminfo/n/ncr260wy325wpp", + "usr/share/terminfo/n/news-o", + "usr/share/terminfo/n/nsterm-acs-c", + "usr/share/terminfo/n/ncr260vpwpp", + "usr/share/terminfo/n/nsterm-m-acs", + "usr/share/terminfo/n/nsterm-256color", + "usr/share/terminfo/n/ndr9500-25-mc", + "usr/share/terminfo/n/nsterm-7-m", + "usr/share/terminfo/n/nsterm-acs-m", + "usr/share/terminfo/n/nwp-517", + "usr/share/terminfo/n/newscbm-o", + "usr/share/terminfo/n/ntconsole-35-nti", + "usr/share/terminfo/n/ncsa", + "usr/share/terminfo/n/nwp518-a", + "usr/share/terminfo/n/ncr160vppp", + "usr/share/terminfo/n/ntconsole-60-nti", + "usr/share/terminfo/n/ncr260wy60pp", + "usr/share/terminfo/n/news-33-euc", + "usr/share/terminfo/n/ntconsole-35-w", + "usr/share/terminfo/n/nsterm-7-c", + "usr/share/terminfo/n/nextshell", + "usr/share/terminfo/n/ncr160wy50+wpp", + "usr/share/terminfo/n/nsterm-s", + "usr/share/terminfo/n/nsterm-bce", + "usr/share/terminfo/n/news-33-sjis", + "usr/share/terminfo/n/ndr9500-25-nl", + "usr/share/terminfo/n/news-29-sjis", + "usr/share/terminfo/n/news-42-sjis", + "usr/share/terminfo/n/ntconsole-60-w", + "usr/share/terminfo/n/news-29", + "usr/share/terminfo/n/nwp514", + "usr/share/terminfo/n/northstar", + "usr/share/terminfo/n/nsterm-7-c-s", + "usr/share/terminfo/n/nsterm-acs-m-s", + "usr/share/terminfo/n/nxterm", + "usr/share/terminfo/n/ncr260wy350pp", + "usr/share/terminfo/n/nansisysk", + "usr/share/terminfo/n/newhpkeyboard", + "usr/share/terminfo/n/newscbm33", + "usr/share/terminfo/n/nwe501-a", + "usr/share/terminfo/n/ncr260wy50+pp", + "usr/share/terminfo/n/nec5520", + "usr/share/terminfo/n/news40-o", + "usr/share/terminfo/n/nansisys", + "usr/share/terminfo/n/nsterm-7", + "usr/share/terminfo/n/news31-o", + "usr/share/terminfo/n/ncr160wy60wpp", + "usr/share/terminfo/n/ncr260wy325pp", + "usr/share/terminfo/n/newhp", + "usr/share/terminfo/n/ntconsole-35", + "usr/share/terminfo/n/nwp513", + "usr/share/terminfo/n/ncr260vt100wpp", + "usr/share/terminfo/n/nwp-517-w", + "usr/share/terminfo/n/ntconsole-50-nti", + "usr/share/terminfo/n/nsterm", + "usr/share/terminfo/n/nwp511", + "usr/share/terminfo/n/ncr160vt300an", + "usr/share/terminfo/n/ncrvt100wpp", + "usr/share/terminfo/n/ncsa-m", + "usr/share/terminfo/n/ncr7900i", + "usr/share/terminfo/n/ntconsole-25-w", + "usr/share/terminfo/n/ncr260intan", + "usr/share/terminfo/n/ncr260vt100pp", + "usr/share/terminfo/n/nwp512-o", + "usr/share/terminfo/n/ncr260wy350wpp", + "usr/share/terminfo/n/ncr160vt300wpp", + "usr/share/terminfo/n/nsterm-c-s-7", + "usr/share/terminfo/n/ncr260vt300wpp", + "usr/share/terminfo/n/ncr260vt100an", + "usr/share/terminfo/n/ndr9500-25", + "usr/share/terminfo/n/nsterm-16color", + "usr/share/terminfo/n/ncr160vt200wpp", + "usr/share/terminfo/n/ncr160vt200pp", + "usr/share/terminfo/n/nwe501", + "usr/share/terminfo/n/next", + "usr/share/terminfo/n/nsterm-acs-c-s", + "usr/share/terminfo/n/ncrvt100an", + "usr/share/terminfo/n/news28", + "usr/share/terminfo/n/news40-a", + "usr/share/terminfo/n/ncr260vt300an", + "usr/share/terminfo/n/ncr260vt200wan", + "usr/share/terminfo/n/nsterm+mac", + "usr/share/terminfo/n/nsterm-m", + "usr/share/terminfo/n/ncr260vt200an", + "usr/share/terminfo/n/ncr260intwan", + "usr/share/terminfo/n/nsterm-build343", + "usr/share/terminfo/n/nsterm-c-s-acs", + "usr/share/terminfo/n/news42", + "usr/share/terminfo/n/newscbm", + "usr/share/terminfo/n/ncr7900", + "usr/share/terminfo/n/news29", + "usr/share/terminfo/n/ncsa-ns", + "usr/share/terminfo/n/news33", + "usr/share/terminfo/n/nsterm-m-s-acs", + "usr/share/terminfo/n/ntconsole", + "usr/share/terminfo/n/nsterm-acs", + "usr/share/terminfo/n/nsterm+c", + "usr/share/terminfo/n/ndr9500", + "usr/share/terminfo/n/ncr160vpwpp", + "usr/share/terminfo/n/ndr9500-nl", + "usr/share/terminfo/n/nsterm-m-s-7", + "usr/share/terminfo/n/nsterm+7", + "usr/share/terminfo/n/nwp514-a", + "usr/share/terminfo/n/ncsa-vt220", + "usr/share/terminfo/n/ndr9500-mc", + "usr/share/terminfo/n/ncr160vt100pp", + "usr/share/terminfo/n/ntconsole-w-vt", + "usr/share/terminfo/n/ncr160vt300pp", + "usr/share/terminfo/n/nansi.sys", + "usr/share/terminfo/n/ntconsole-60", + "usr/share/terminfo/n/newscbm-a", + "usr/share/terminfo/n/ndr9500-25-mc-nl", + "usr/share/terminfo/n/nsterm-c-acs", + "usr/share/terminfo/n/nsterm+s", + "usr/share/terminfo/n/nsterm+acs", + "usr/share/terminfo/n/nsterm-old", + "usr/share/terminfo/n/ncr160vt100wan", + "usr/share/terminfo/n/ncr260vt300wan", + "usr/share/terminfo/n/news40", + "usr/share/terminfo/n/ntconsole-50-w", + "usr/share/terminfo/n/ncr260wy50+wpp", + "usr/share/terminfo/n/nwp251-a", + "usr/share/terminfo/n/ncr160wy50+pp", + "usr/share/terminfo/n/ntconsole-25-nti", + "usr/share/terminfo/n/ncr160vt100wpp", + "usr/share/terminfo/n/nsterm-7-m-s", + "usr/share/terminfo/n/ncr160vt200an", + "usr/share/terminfo/n/ncsa-m-ns", + "usr/share/terminfo/n/ncr260vppp", + "usr/share/terminfo/n/nwp-511", + "usr/share/terminfo/n/nsterm-m-s", + "usr/share/terminfo/n/nsterm+c41", + "usr/share/terminfo/n/nsterm-c-7", + "usr/share/terminfo/n/ncr160vt100an", + "usr/share/terminfo/n/nwp518", + "usr/share/terminfo/n/nsterm-build326", + "usr/share/terminfo/n/ncrvt100wan", + "usr/share/terminfo/n/ncr160vt300wan", + "usr/share/terminfo/n/news31-a", + "usr/share/terminfo/n/ncr260wy60wpp", + "usr/share/terminfo/n/ncr260vt200pp", + "usr/share/terminfo/n/nwp517", + "usr/share/terminfo/n/ntconsole-50", + "usr/share/terminfo/n/news-old-unk", + "usr/share/terminfo/n/news-42-euc", + "usr/share/terminfo/n/nsterm-s-acs", + "usr/share/terminfo/n/ncr160wy60pp", + "usr/share/terminfo/n/nd9500", + "usr/share/terminfo/n/ntconsole-100-nti", + "usr/share/terminfo/n/ntconsole-100", + "usr/share/terminfo/n/nwp514-o", + "usr/share/terminfo/n/nsterm-acs-s", + "usr/share/terminfo/c/cbunix", + "usr/share/terminfo/c/cops10", + "usr/share/terminfo/c/cs10", + "usr/share/terminfo/c/cbblit", + "usr/share/terminfo/c/crt", + "usr/share/terminfo/c/cons25r", + "usr/share/terminfo/c/cons25l1", + "usr/share/terminfo/c/c301", + "usr/share/terminfo/c/concept", + "usr/share/terminfo/c/cons60-iso-m", + "usr/share/terminfo/c/c321", + "usr/share/terminfo/c/cons50r", + "usr/share/terminfo/c/c100-4p", + "usr/share/terminfo/c/c108-rv", + "usr/share/terminfo/c/cons60-iso", + "usr/share/terminfo/c/c300", + "usr/share/terminfo/c/cdc456", + "usr/share/terminfo/c/cit101e-132", + "usr/share/terminfo/c/citoh-pica", + "usr/share/terminfo/c/cons25-koi8-r", + "usr/share/terminfo/c/cons30-m", + "usr/share/terminfo/c/cx", + "usr/share/terminfo/c/cons43", + "usr/share/terminfo/c/cygwin", + "usr/share/terminfo/c/c108-4p", + "usr/share/terminfo/c/cons25r-m", + "usr/share/terminfo/c/c108-w", + "usr/share/terminfo/c/citc", + "usr/share/terminfo/c/cdc721", + "usr/share/terminfo/c/cit101", + "usr/share/terminfo/c/citoh-8lpi", + "usr/share/terminfo/c/cs10-w", + "usr/share/terminfo/c/c100-rv", + "usr/share/terminfo/c/cons60", + "usr/share/terminfo/c/contel320", + "usr/share/terminfo/c/cons25w", + "usr/share/terminfo/c/cit80", + "usr/share/terminfo/c/cit-80", + "usr/share/terminfo/c/cit500", + "usr/share/terminfo/c/concept108-8p", + "usr/share/terminfo/c/cons25-iso8859", + "usr/share/terminfo/c/cons60-m", + "usr/share/terminfo/c/cons50l1-m", + "usr/share/terminfo/c/cons60r-m", + "usr/share/terminfo/c/concept100-rv", + "usr/share/terminfo/c/cons60-koi8r", + "usr/share/terminfo/c/c108-rv-4p", + "usr/share/terminfo/c/ct8500", + "usr/share/terminfo/c/colorscan", + "usr/share/terminfo/c/cons43-m", + "usr/share/terminfo/c/concept108-4p", + "usr/share/terminfo/c/cygwinDBG", + "usr/share/terminfo/c/contel300", + "usr/share/terminfo/c/cops-10", + "usr/share/terminfo/c/crt-vt220", + "usr/share/terminfo/c/coherent", + "usr/share/terminfo/c/cygwinB19", + "usr/share/terminfo/c/c100-1p", + "usr/share/terminfo/c/ci8510", + "usr/share/terminfo/c/cit101e", + "usr/share/terminfo/c/cons50-m", + "usr/share/terminfo/c/cons50-koi8r", + "usr/share/terminfo/c/contel301", + "usr/share/terminfo/c/cons60r", + "usr/share/terminfo/c/cyb83", + "usr/share/terminfo/c/concept100", + "usr/share/terminfo/c/cons25-m", + "usr/share/terminfo/c/cad68-2", + "usr/share/terminfo/c/cons60l1-m", + "usr/share/terminfo/c/color_xterm", + "usr/share/terminfo/c/cci1", + "usr/share/terminfo/c/cit101e-n", + "usr/share/terminfo/c/cdc721ll", + "usr/share/terminfo/c/cons50r-m", + "usr/share/terminfo/c/c104", + "usr/share/terminfo/c/c108", + "usr/share/terminfo/c/cons25l1-m", + "usr/share/terminfo/c/cyb110", + "usr/share/terminfo/c/citoh-comp", + "usr/share/terminfo/c/cci", + "usr/share/terminfo/c/cg7900", + "usr/share/terminfo/c/cons50-iso8859", + "usr/share/terminfo/c/concept108-w-8", + "usr/share/terminfo/c/cgc3", + "usr/share/terminfo/c/cons50-koi8r-m", + "usr/share/terminfo/c/c108-8p", + "usr/share/terminfo/c/citoh-6lpi", + "usr/share/terminfo/c/cons50-iso-m", + "usr/share/terminfo/c/citoh-elite", + "usr/share/terminfo/c/coco3", + "usr/share/terminfo/c/cdc752", + "usr/share/terminfo/c/cons25-iso-m", + "usr/share/terminfo/c/cons50l1", + "usr/share/terminfo/c/cons60l1", + "usr/share/terminfo/c/cdc756", + "usr/share/terminfo/c/cons30", + "usr/share/terminfo/c/cops", + "usr/share/terminfo/c/cons25-debian", + "usr/share/terminfo/c/cgc2", + "usr/share/terminfo/c/c100", + "usr/share/terminfo/c/cad68-3", + "usr/share/terminfo/c/citoh", + "usr/share/terminfo/c/cdc721-esc", + "usr/share/terminfo/c/ct82", + "usr/share/terminfo/c/cons60-koi8r-m", + "usr/share/terminfo/c/c108-w-8p", + "usr/share/terminfo/c/c100-rv-4p", + "usr/share/terminfo/c/concept108-w8p", + "usr/share/terminfo/c/citoh-prop", + "usr/share/terminfo/c/commodore", + "usr/share/terminfo/c/chromatics", + "usr/share/terminfo/c/concept108", + "usr/share/terminfo/c/contel321", + "usr/share/terminfo/c/cons50", + "usr/share/terminfo/c/cons25-koi8r-m", + "usr/share/terminfo/c/ctrm", + "usr/share/terminfo/c/c108-rv-8p", + "usr/share/terminfo/c/citoh-ps", + "usr/share/terminfo/c/cx100", + "usr/share/terminfo/c/ca22851", + "usr/share/terminfo/c/concept108rv4p", + "usr/share/terminfo/c/concept-avt", + "usr/share/terminfo/c/cit101e-rv", + "usr/share/terminfo/c/cons25", + "usr/share/terminfo/c/cit101e-n132", + "usr/share/terminfo/v/vt52", + "usr/share/terminfo/v/vip7800-H", + "usr/share/terminfo/v/vi50adm", + "usr/share/terminfo/v/vt100-s", + "usr/share/terminfo/v/vt200", + "usr/share/terminfo/v/vt220", + "usr/share/terminfo/v/vte-2007", + "usr/share/terminfo/v/vt400", + "usr/share/terminfo/v/v320n", + "usr/share/terminfo/v/vte+pcfkeys", + "usr/share/terminfo/v/vt50h", + "usr/share/terminfo/v/vte-2012", + "usr/share/terminfo/v/vt200-8", + "usr/share/terminfo/v/vip7800-w", + "usr/share/terminfo/v/vt100-nav", + "usr/share/terminfo/v/vt125", + "usr/share/terminfo/v/vt-utf8", + "usr/share/terminfo/v/vt300-w-nam", + "usr/share/terminfo/v/vt330", + "usr/share/terminfo/v/vt320-k311", + "usr/share/terminfo/v/vt510pcdos", + "usr/share/terminfo/v/vt420f", + "usr/share/terminfo/v/vc414h", + "usr/share/terminfo/v/vt200-w", + "usr/share/terminfo/v/vt100-top-s", + "usr/share/terminfo/v/vt61.5", + "usr/share/terminfo/v/vt420pcdos", + "usr/share/terminfo/v/vitty", + "usr/share/terminfo/v/vt300-nam", + "usr/share/terminfo/v/vi50", + "usr/share/terminfo/v/vc303a", + "usr/share/terminfo/v/vt100-bm", + "usr/share/terminfo/v/vp90", + "usr/share/terminfo/v/vt100-nam", + "usr/share/terminfo/v/vte-2014", + "usr/share/terminfo/v/vt320-w", + "usr/share/terminfo/v/v200-nam", + "usr/share/terminfo/v/vanilla", + "usr/share/terminfo/v/vt220-js", + "usr/share/terminfo/v/vt102+enq", + "usr/share/terminfo/v/vt100+", + "usr/share/terminfo/v/vt100-bot-s", + "usr/share/terminfo/v/vt200-8bit", + "usr/share/terminfo/v/vt100-putty", + "usr/share/terminfo/v/vc404", + "usr/share/terminfo/v/vremote", + "usr/share/terminfo/v/vv100", + "usr/share/terminfo/v/vt100+pfkeys", + "usr/share/terminfo/v/viewpoint60", + "usr/share/terminfo/v/vt132", + "usr/share/terminfo/v/vip-Hw", + "usr/share/terminfo/v/vt200-old", + "usr/share/terminfo/v/vapple", + "usr/share/terminfo/v/vt100-nam-w", + "usr/share/terminfo/v/vc404-s", + "usr/share/terminfo/v/vt100-bm-o", + "usr/share/terminfo/v/vt100-s-top", + "usr/share/terminfo/v/viewdata-rv", + "usr/share/terminfo/v/vt102-w", + "usr/share/terminfo/v/vt400-24", + "usr/share/terminfo/v/vt100-s-bot", + "usr/share/terminfo/v/vt100-w-nav", + "usr/share/terminfo/v/vk100", + "usr/share/terminfo/v/vt340", + "usr/share/terminfo/v/viewdata", + "usr/share/terminfo/v/vt320-w-nam", + "usr/share/terminfo/v/viewpoint90", + "usr/share/terminfo/v/vt61", + "usr/share/terminfo/v/vt100-w-am", + "usr/share/terminfo/v/vi300-old", + "usr/share/terminfo/v/vt420", + "usr/share/terminfo/v/vt100-am", + "usr/share/terminfo/v/viewdata-o", + "usr/share/terminfo/v/vt100-w-nam", + "usr/share/terminfo/v/vt525", + "usr/share/terminfo/v/vc103", + "usr/share/terminfo/v/vt100-w", + "usr/share/terminfo/v/vc414", + "usr/share/terminfo/v/vsc", + "usr/share/terminfo/v/vi603", + "usr/share/terminfo/v/vip-H", + "usr/share/terminfo/v/vt102", + "usr/share/terminfo/v/vs100", + "usr/share/terminfo/v/vi200-f", + "usr/share/terminfo/v/vt320-k3", + "usr/share/terminfo/v/vt420pc", + "usr/share/terminfo/v/vt50", + "usr/share/terminfo/v/vt220-8", + "usr/share/terminfo/v/vte", + "usr/share/terminfo/v/vc203", + "usr/share/terminfo/v/vc403a", + "usr/share/terminfo/v/vt100-nav-w", + "usr/share/terminfo/v/vp60", + "usr/share/terminfo/v/vt220-old", + "usr/share/terminfo/v/vwmterm", + "usr/share/terminfo/v/vt220-nam", + "usr/share/terminfo/v/vt102-nsgr", + "usr/share/terminfo/v/venix", + "usr/share/terminfo/v/vip-w", + "usr/share/terminfo/v/vt100+4bsd", + "usr/share/terminfo/v/vt100+fnkeys", + "usr/share/terminfo/v/visual603", + "usr/share/terminfo/v/vt100nam", + "usr/share/terminfo/v/vt100+enq", + "usr/share/terminfo/v/vi500", + "usr/share/terminfo/v/vc303", + "usr/share/terminfo/v/vt220-w", + "usr/share/terminfo/v/vp3a+", + "usr/share/terminfo/v/v5410", + "usr/share/terminfo/v/vt220-8bit", + "usr/share/terminfo/v/vtnt", + "usr/share/terminfo/v/vt320", + "usr/share/terminfo/v/vi550", + "usr/share/terminfo/v/vt220+keypad", + "usr/share/terminfo/v/vt-61", + "usr/share/terminfo/v/vi200-rv", + "usr/share/terminfo/v/vt320-nam", + "usr/share/terminfo/v/vt100-vb", + "usr/share/terminfo/v/vi55", + "usr/share/terminfo/v/vt320nam", + "usr/share/terminfo/v/vt220d", + "usr/share/terminfo/v/visa50", + "usr/share/terminfo/v/viewpoint3a+", + "usr/share/terminfo/v/vt510", + "usr/share/terminfo/v/vt100", + "usr/share/terminfo/v/v3220", + "usr/share/terminfo/v/vt131", + "usr/share/terminfo/v/vte-256color", + "usr/share/terminfo/v/vc415", + "usr/share/terminfo/v/vt300-w", + "usr/share/terminfo/v/vi200", + "usr/share/terminfo/v/vt300", + "usr/share/terminfo/v/vt100+keypad", + "usr/share/terminfo/v/viewpoint", + "usr/share/terminfo/v/vip7800-Hw", + "usr/share/terminfo/v/vs100-x10", + "usr/share/terminfo/v/versaterm", + "usr/share/terminfo/v/vt520", + "usr/share/terminfo/v/vt510pc", + "usr/share/terminfo/v/vip", + "usr/share/terminfo/v/vte-2008", + "usr/share/terminfo/v/vt200-js", + "usr/share/terminfo/v/vt520ansi", + "usr/share/terminfo/v/vi300", + "usr/share/terminfo/g/guru-76-wm", + "usr/share/terminfo/g/guru-76-w-s", + "usr/share/terminfo/g/gigi", + "usr/share/terminfo/g/guru+rv", + "usr/share/terminfo/g/go-225", + "usr/share/terminfo/g/gnome-rh62", + "usr/share/terminfo/g/gnome-fc5", + "usr/share/terminfo/g/gs6300", + "usr/share/terminfo/g/go225", + "usr/share/terminfo/g/gator", + "usr/share/terminfo/g/guru+unk", + "usr/share/terminfo/g/guru-33", + "usr/share/terminfo/g/guru+s", + "usr/share/terminfo/g/gnome", + "usr/share/terminfo/g/guru-76-s", + "usr/share/terminfo/g/guru-76-lp", + "usr/share/terminfo/g/gator-52t", + "usr/share/terminfo/g/gnome-256color", + "usr/share/terminfo/g/gt42", + "usr/share/terminfo/g/gnome-rh72", + "usr/share/terminfo/g/gnome-2007", + "usr/share/terminfo/g/guru-76", + "usr/share/terminfo/g/guru-44-s", + "usr/share/terminfo/g/gnome+pcfkeys", + "usr/share/terminfo/g/gs5430-24", + "usr/share/terminfo/g/glasstty", + "usr/share/terminfo/g/graphos", + "usr/share/terminfo/g/guru-76-w", + "usr/share/terminfo/g/gsi", + "usr/share/terminfo/g/guru-s", + "usr/share/terminfo/g/guru-33-rv", + "usr/share/terminfo/g/gnome-2008", + "usr/share/terminfo/g/guru-24", + "usr/share/terminfo/g/gt100a", + "usr/share/terminfo/g/guru-nctxt", + "usr/share/terminfo/g/gator-t", + "usr/share/terminfo/g/gnome-2012", + "usr/share/terminfo/g/graphos-30", + "usr/share/terminfo/g/guru", + "usr/share/terminfo/g/gs5430-22", + "usr/share/terminfo/g/guru-rv", + "usr/share/terminfo/g/gnome-rh80", + "usr/share/terminfo/g/guru-33-s", + "usr/share/terminfo/g/go140w", + "usr/share/terminfo/g/gnome-rh90", + "usr/share/terminfo/g/guru-lp", + "usr/share/terminfo/g/gator-52", + "usr/share/terminfo/g/go140", + "usr/share/terminfo/g/gt100", + "usr/share/terminfo/g/gs5430", + "usr/share/terminfo/g/gt40", + "usr/share/terminfo/g/guru-44", + "usr/share/terminfo/A/Apple_Terminal", + "usr/share/terminfo/k/konsole-256color", + "usr/share/terminfo/k/kermit", + "usr/share/terminfo/k/kds7372-w", + "usr/share/terminfo/k/konsole-solaris", + "usr/share/terminfo/k/k45", + "usr/share/terminfo/k/kt7", + "usr/share/terminfo/k/kds6402", + "usr/share/terminfo/k/kon2", + "usr/share/terminfo/k/ktm", + "usr/share/terminfo/k/klone+color", + "usr/share/terminfo/k/konsole-xf3x", + "usr/share/terminfo/k/konsole-16color", + "usr/share/terminfo/k/konsole-xf4x", + "usr/share/terminfo/k/kaypro2", + "usr/share/terminfo/k/konsole-base", + "usr/share/terminfo/k/konsole", + "usr/share/terminfo/k/kterm-color", + "usr/share/terminfo/k/kvt", + "usr/share/terminfo/k/kterm", + "usr/share/terminfo/k/klone+koi8acs", + "usr/share/terminfo/k/klone+sgr-dumb", + "usr/share/terminfo/k/kon", + "usr/share/terminfo/k/klone+sgr", + "usr/share/terminfo/k/klone+acs", + "usr/share/terminfo/k/konsole-linux", + "usr/share/terminfo/k/kterm-co", + "usr/share/terminfo/k/konsole-vt100", + "usr/share/terminfo/k/kaypro", + "usr/share/terminfo/k/kds7372", + "usr/share/terminfo/k/kt7ix", + "usr/share/terminfo/k/konsole+pcfkeys", + "usr/share/terminfo/k/klone+sgr8", + "usr/share/terminfo/k/kermit-am", + "usr/share/terminfo/k/konsole-vt420pc", + "usr/share/terminfo/f/fixterm", + "usr/share/terminfo/f/f200vi", + "usr/share/terminfo/f/freedom110", + "usr/share/terminfo/f/fenixw", + "usr/share/terminfo/f/f100", + "usr/share/terminfo/f/fos", + "usr/share/terminfo/f/freedom200", + "usr/share/terminfo/f/fox", + "usr/share/terminfo/f/freedom100", + "usr/share/terminfo/f/f1720", + "usr/share/terminfo/f/f200-w", + "usr/share/terminfo/f/freedom-rv", + "usr/share/terminfo/f/f200", + "usr/share/terminfo/f/fortune", + "usr/share/terminfo/f/f110-14", + "usr/share/terminfo/f/freedom", + "usr/share/terminfo/f/f110", + "usr/share/terminfo/f/f1720a", + "usr/share/terminfo/f/fenix", + "usr/share/terminfo/f/f200vi-w", + "usr/share/terminfo/f/falco", + "usr/share/terminfo/f/f100-rv", + "usr/share/terminfo/f/fbterm", + "usr/share/terminfo/f/f110-w", + "usr/share/terminfo/f/f110-14w", + "usr/share/terminfo/f/falco-p", + "usr/share/terminfo/z/z110", + "usr/share/terminfo/z/ztx11", + "usr/share/terminfo/z/z39a", + "usr/share/terminfo/z/zen8001", + "usr/share/terminfo/z/z30", + "usr/share/terminfo/z/zenith29", + "usr/share/terminfo/z/zenith", + "usr/share/terminfo/z/z29a", + "usr/share/terminfo/z/ztx", + "usr/share/terminfo/z/z-100", + "usr/share/terminfo/z/z39-a", + "usr/share/terminfo/z/z110bw", + "usr/share/terminfo/z/z29", + "usr/share/terminfo/z/zenith39-ansi", + "usr/share/terminfo/z/z8001", + "usr/share/terminfo/z/z340", + "usr/share/terminfo/z/ztx-1-a", + "usr/share/terminfo/z/zen30", + "usr/share/terminfo/z/zenith39-a", + "usr/share/terminfo/z/zen50", + "usr/share/terminfo/z/z340-nam", + "usr/share/terminfo/z/z29a-kc-bc", + "usr/share/terminfo/z/z-100bw", + "usr/share/terminfo/z/zt-1", + "usr/share/terminfo/z/z100bw", + "usr/share/terminfo/z/z100", + "usr/share/terminfo/z/z50", + "usr/share/terminfo/z/z29a-nkc-uc", + "usr/share/terminfo/z/z29a-nkc-bc", + "usr/share/terminfo/z/z19", + "usr/share/terminfo/z/z29a-kc-uc", + "usr/share/terminfo/z/z29b", + "usr/share/terminfo/M/MtxOrb162", + "usr/share/terminfo/M/MtxOrb", + "usr/share/terminfo/M/MtxOrb204", + "usr/share/terminfo/L/LFT-PC850", + "usr/share/terminfo/1/1730-lm", + "usr/share/terminfo/1/1178", + "usr/share/terminfo/s/scoansi", + "usr/share/terminfo/s/sun-ss5", + "usr/share/terminfo/s/screen-bce.mrxvt", + "usr/share/terminfo/s/screen-bce.gnome", + "usr/share/terminfo/s/spinwriter", + "usr/share/terminfo/s/sb1", + "usr/share/terminfo/s/screen-256color-bce", + "usr/share/terminfo/s/sun-e", + "usr/share/terminfo/s/sun-color", + "usr/share/terminfo/s/st-256color", + "usr/share/terminfo/s/sun-c", + "usr/share/terminfo/s/screen.minitel1b", + "usr/share/terminfo/s/simpleterm", + "usr/share/terminfo/s/simterm", + "usr/share/terminfo/s/st52-m", + "usr/share/terminfo/s/screen-16color-s", + "usr/share/terminfo/s/screen", + "usr/share/terminfo/s/sun-48", + "usr/share/terminfo/s/st", + "usr/share/terminfo/s/swtp", + "usr/share/terminfo/s/screen+italics", + "usr/share/terminfo/s/screen.teraterm", + "usr/share/terminfo/s/sun-il", + "usr/share/terminfo/s/soroc120", + "usr/share/terminfo/s/screen-16color-bce", + "usr/share/terminfo/s/screen.minitel2-80", + "usr/share/terminfo/s/soroc140", + "usr/share/terminfo/s/sun", + "usr/share/terminfo/s/sibo", + "usr/share/terminfo/s/stv52", + "usr/share/terminfo/s/screen.xterm-r6", + "usr/share/terminfo/s/sbobcat", + "usr/share/terminfo/s/superbrain", + "usr/share/terminfo/s/sb2", + "usr/share/terminfo/s/stterm-256color", + "usr/share/terminfo/s/screen3", + "usr/share/terminfo/s/screen.konsole-256color", + "usr/share/terminfo/s/screen.minitel1b-nb", + "usr/share/terminfo/s/sv80", + "usr/share/terminfo/s/screen.minitel12-80", + "usr/share/terminfo/s/screen.mlterm", + "usr/share/terminfo/s/screen.minitel1", + "usr/share/terminfo/s/st52-old", + "usr/share/terminfo/s/synertek380", + "usr/share/terminfo/s/superbeeic", + "usr/share/terminfo/s/sun-s", + "usr/share/terminfo/s/screen.linux-m1b", + "usr/share/terminfo/s/sc415", + "usr/share/terminfo/s/screwpoint", + "usr/share/terminfo/s/sbi", + "usr/share/terminfo/s/screen.vte-256color", + "usr/share/terminfo/s/scoansi-old", + "usr/share/terminfo/s/screen.gnome", + "usr/share/terminfo/s/screen+fkeys", + "usr/share/terminfo/s/st-0.7", + "usr/share/terminfo/s/screen.mlterm-256color", + "usr/share/terminfo/s/sun-24", + "usr/share/terminfo/s/st52", + "usr/share/terminfo/s/screen.Eterm", + "usr/share/terminfo/s/screen.putty-m1", + "usr/share/terminfo/s/screen.minitel1-nb", + "usr/share/terminfo/s/screen-bce.Eterm", + "usr/share/terminfo/s/screen-s", + "usr/share/terminfo/s/system1", + "usr/share/terminfo/s/sun-s-e", + "usr/share/terminfo/s/superbee-xsb", + "usr/share/terminfo/s/screen-bce.konsole", + "usr/share/terminfo/s/scanset", + "usr/share/terminfo/s/screen.putty", + "usr/share/terminfo/s/st52-color", + "usr/share/terminfo/s/sun2", + "usr/share/terminfo/s/screen-bce", + "usr/share/terminfo/s/screen.rxvt", + "usr/share/terminfo/s/screen.mrxvt", + "usr/share/terminfo/s/stv52pc", + "usr/share/terminfo/s/sun+sl", + "usr/share/terminfo/s/screen.putty-256color", + "usr/share/terminfo/s/sc410", + "usr/share/terminfo/s/sun-cmd", + "usr/share/terminfo/s/sun-nic", + "usr/share/terminfo/s/stterm", + "usr/share/terminfo/s/sun-17", + "usr/share/terminfo/s/st-16color", + "usr/share/terminfo/s/soroc", + "usr/share/terminfo/s/screen.linux-m1", + "usr/share/terminfo/s/screen-256color", + "usr/share/terminfo/s/scoansi-new", + "usr/share/terminfo/s/screen.konsole", + "usr/share/terminfo/s/screen-16color", + "usr/share/terminfo/s/s4", + "usr/share/terminfo/s/screen-bce.xterm-new", + "usr/share/terminfo/s/screen.xterm-256color", + "usr/share/terminfo/s/screen.putty-m2", + "usr/share/terminfo/s/synertek", + "usr/share/terminfo/s/sb3", + "usr/share/terminfo/s/screen-256color-s", + "usr/share/terminfo/s/screen-w", + "usr/share/terminfo/s/screen-bce.linux", + "usr/share/terminfo/s/screen-16color-bce-s", + "usr/share/terminfo/s/scrhp", + "usr/share/terminfo/s/screen.putty-m1b", + "usr/share/terminfo/s/sun-12", + "usr/share/terminfo/s/st-0.6", + "usr/share/terminfo/s/stterm-16color", + "usr/share/terminfo/s/screen.xterm-new", + "usr/share/terminfo/s/screen.linux", + "usr/share/terminfo/s/sun1", + "usr/share/terminfo/s/screen-bce.rxvt", + "usr/share/terminfo/s/sun-1", + "usr/share/terminfo/s/sune", + "usr/share/terminfo/s/sun-type4", + "usr/share/terminfo/s/screen.minitel1b-80", + "usr/share/terminfo/s/sun-e-s", + "usr/share/terminfo/s/screen.vte", + "usr/share/terminfo/s/screen-256color-bce-s", + "usr/share/terminfo/s/superbee", + "usr/share/terminfo/s/sun-cgsix", + "usr/share/terminfo/s/screen.linux-m2", + "usr/share/terminfo/s/sun-34", + "usr/share/terminfo/s/screen2", + "usr/share/terminfo/s/screen.xterm-xfree86", + "usr/share/terminfo/3/3b1", + "usr/share/terminfo/3/386at", + "usr/share/terminfo/e/envision230", + "usr/share/terminfo/e/elks", + "usr/share/terminfo/e/ecma+color", + "usr/share/terminfo/e/excel64-w", + "usr/share/terminfo/e/ecma+strikeout", + "usr/share/terminfo/e/excel62", + "usr/share/terminfo/e/eterm", + "usr/share/terminfo/e/env230", + "usr/share/terminfo/e/elks-vt52", + "usr/share/terminfo/e/emu-220", + "usr/share/terminfo/e/emu", + "usr/share/terminfo/e/ep40", + "usr/share/terminfo/e/ep48", + "usr/share/terminfo/e/excel64", + "usr/share/terminfo/e/esprit", + "usr/share/terminfo/e/elks-glasstty", + "usr/share/terminfo/e/ecma+italics", + "usr/share/terminfo/e/esprit-am", + "usr/share/terminfo/e/eterm-color", + "usr/share/terminfo/e/emots", + "usr/share/terminfo/e/excel62-rv", + "usr/share/terminfo/e/ergo4000", + "usr/share/terminfo/e/excel62-w", + "usr/share/terminfo/e/emx-base", + "usr/share/terminfo/e/excel64-rv", + "usr/share/terminfo/e/ep4000", + "usr/share/terminfo/e/ecma+sgr", + "usr/share/terminfo/e/ex155", + "usr/share/terminfo/e/elks-ansi", + "usr/share/terminfo/e/exec80", + "usr/share/terminfo/e/ep4080", + "usr/share/terminfo/8/8510", + "usr/share/terminfo/2/2621-wl", + "usr/share/terminfo/2/2621A", + "usr/share/terminfo/2/2621", + "usr/share/terminfo/2/2621a", + "usr/share/terminfo/h/hp98550", + "usr/share/terminfo/h/hp2621-wl", + "usr/share/terminfo/h/h100", + "usr/share/terminfo/h/hp2640a", + "usr/share/terminfo/h/hft-old", + "usr/share/terminfo/h/hpsub", + "usr/share/terminfo/h/hp2624a-10p", + "usr/share/terminfo/h/hirez100-w", + "usr/share/terminfo/h/hp2626-s", + "usr/share/terminfo/h/h19-b", + "usr/share/terminfo/h/heath-19", + "usr/share/terminfo/h/hurd", + "usr/share/terminfo/h/h19-u", + "usr/share/terminfo/h/hp2621p-a", + "usr/share/terminfo/h/hp2621b-kx-p", + "usr/share/terminfo/h/hz1510", + "usr/share/terminfo/h/hft-c", + "usr/share/terminfo/h/hp+pfk+cr", + "usr/share/terminfo/h/hp2627c", + "usr/share/terminfo/h/h19-us", + "usr/share/terminfo/h/hpex", + "usr/share/terminfo/h/ha8675", + "usr/share/terminfo/h/hp98721", + "usr/share/terminfo/h/hp2382a", + "usr/share/terminfo/h/hp2624-10p", + "usr/share/terminfo/h/hp2621-48", + "usr/share/terminfo/h/h19-g", + "usr/share/terminfo/h/hp2626-ns", + "usr/share/terminfo/h/hp262x", + "usr/share/terminfo/h/hp2624b", + "usr/share/terminfo/h/hp700", + "usr/share/terminfo/h/hp+pfk+arrows", + "usr/share/terminfo/h/hp70092", + "usr/share/terminfo/h/hp2626a", + "usr/share/terminfo/h/hp2621A", + "usr/share/terminfo/h/hp2627a-rev", + "usr/share/terminfo/h/hp2621-nl", + "usr/share/terminfo/h/hp+pfk-cr", + "usr/share/terminfo/h/hp2645a", + "usr/share/terminfo/h/hp2626p", + "usr/share/terminfo/h/hp2644a", + "usr/share/terminfo/h/he80", + "usr/share/terminfo/h/h19kermit", + "usr/share/terminfo/h/hp2626-12-s", + "usr/share/terminfo/h/hpterm-color", + "usr/share/terminfo/h/hp2647a", + "usr/share/terminfo/h/hz1000", + "usr/share/terminfo/h/hmod1", + "usr/share/terminfo/h/hp2624b-10p", + "usr/share/terminfo/h/hp+printer", + "usr/share/terminfo/h/hz1552", + "usr/share/terminfo/h/hp2621a-a", + "usr/share/terminfo/h/hp2626-12x40", + "usr/share/terminfo/h/h19g", + "usr/share/terminfo/h/hp236", + "usr/share/terminfo/h/hp2621b-kx", + "usr/share/terminfo/h/hp2623a", + "usr/share/terminfo/h/hp2624b-4p-p", + "usr/share/terminfo/h/hp2621a", + "usr/share/terminfo/h/hp45", + "usr/share/terminfo/h/hp2645", + "usr/share/terminfo/h/h29a-nkc-bc", + "usr/share/terminfo/h/hds200", + "usr/share/terminfo/h/h19a", + "usr/share/terminfo/h/hp2621-nt", + "usr/share/terminfo/h/hp2624b-p", + "usr/share/terminfo/h/hp700-wy", + "usr/share/terminfo/h/hp2648a", + "usr/share/terminfo/h/hz2000", + "usr/share/terminfo/h/hz1552-rv", + "usr/share/terminfo/h/hp98550a", + "usr/share/terminfo/h/hp2623", + "usr/share/terminfo/h/hz1500", + "usr/share/terminfo/h/hp+color", + "usr/share/terminfo/h/hz1420", + "usr/share/terminfo/h/hp2392", + "usr/share/terminfo/h/h29a-nkc-uc", + "usr/share/terminfo/h/hp2382", + "usr/share/terminfo/h/hpex2", + "usr/share/terminfo/h/hp2626-x40", + "usr/share/terminfo/h/hazel", + "usr/share/terminfo/h/h19k", + "usr/share/terminfo/h/hp150", + "usr/share/terminfo/h/ha8686", + "usr/share/terminfo/h/hp2621b", + "usr/share/terminfo/h/hp2", + "usr/share/terminfo/h/hp2641a", + "usr/share/terminfo/h/hpgeneric", + "usr/share/terminfo/h/hp2621-a", + "usr/share/terminfo/h/hp2621", + "usr/share/terminfo/h/hp2622", + "usr/share/terminfo/h/h-100bw", + "usr/share/terminfo/h/h19-a", + "usr/share/terminfo/h/hz1520", + "usr/share/terminfo/h/h29a-kc-uc", + "usr/share/terminfo/h/hp2626-12", + "usr/share/terminfo/h/hp2626", + "usr/share/terminfo/h/hp2397a", + "usr/share/terminfo/h/heath-ansi", + "usr/share/terminfo/h/hp110", + "usr/share/terminfo/h/hp2624", + "usr/share/terminfo/h/hp2621p", + "usr/share/terminfo/h/htx11", + "usr/share/terminfo/h/heathkit-a", + "usr/share/terminfo/h/hp2624a", + "usr/share/terminfo/h/h100bw", + "usr/share/terminfo/h/hp9837", + "usr/share/terminfo/h/h19us", + "usr/share/terminfo/h/hp2627a", + "usr/share/terminfo/h/hirez100", + "usr/share/terminfo/h/hp2640b", + "usr/share/terminfo/h/h80", + "usr/share/terminfo/h/hp2621-fl", + "usr/share/terminfo/h/hp2621-ba", + "usr/share/terminfo/h/hp2624b-4p", + "usr/share/terminfo/h/hp70092a", + "usr/share/terminfo/h/hp98720", + "usr/share/terminfo/h/hz1520-noesc", + "usr/share/terminfo/h/h29a-kc-bc", + "usr/share/terminfo/h/h-100", + "usr/share/terminfo/h/hp2621-k45", + "usr/share/terminfo/h/h19-bs", + "usr/share/terminfo/h/heathkit", + "usr/share/terminfo/h/hp70092A", + "usr/share/terminfo/h/h19", + "usr/share/terminfo/h/hp9845", + "usr/share/terminfo/h/hp2621b-p", + "usr/share/terminfo/h/hp", + "usr/share/terminfo/h/hp2648", + "usr/share/terminfo/h/hp+arrows", + "usr/share/terminfo/h/hp+labels", + "usr/share/terminfo/h/h19-smul", + "usr/share/terminfo/h/hft", + "usr/share/terminfo/h/hp2624b-10p-p", + "usr/share/terminfo/h/hpansi", + "usr/share/terminfo/h/hp2622a", + "usr/share/terminfo/h/hpterm", + "usr/share/terminfo/h/heath", + "usr/share/terminfo/h/hp2397", + "usr/share/terminfo/h/hp2621k45", + "usr/share/terminfo/h/hft-c-old", + "usr/share/terminfo/h/hp300h", + "usr/share/terminfo/P/P9-8-W", + "usr/share/terminfo/P/P8", + "usr/share/terminfo/P/P14-M", + "usr/share/terminfo/P/P14-W", + "usr/share/terminfo/P/P14", + "usr/share/terminfo/P/P9-W", + "usr/share/terminfo/P/P4", + "usr/share/terminfo/P/P12-M-W", + "usr/share/terminfo/P/P8-W", + "usr/share/terminfo/P/P12", + "usr/share/terminfo/P/P12-M", + "usr/share/terminfo/P/P9", + "usr/share/terminfo/P/P12-W", + "usr/share/terminfo/P/P7", + "usr/share/terminfo/P/P5", + "usr/share/terminfo/P/P9-8", + "usr/share/terminfo/P/P14-M-W", + "usr/share/terminfo/u/uniterm", + "usr/share/terminfo/u/ultima2", + "usr/share/terminfo/u/ultimaII", + "usr/share/terminfo/u/unknown", + "usr/share/terminfo/u/uwin", + "usr/share/terminfo/u/unixpc", + "usr/share/terminfo/u/uts30", + "usr/share/terminfo/u/uniterm49", + "usr/share/terminfo/7/730MTG-41r", + "usr/share/terminfo/7/730MTG-41", + "usr/share/terminfo/7/730MTGr-24", + "usr/share/terminfo/7/730MTGr", + "usr/share/terminfo/7/730MTG-24", + "usr/share/terminfo/N/NCRVT100WPP", + "usr/share/terminfo/N/NCR260VT300WPP", + "usr/share/terminfo/5/5630DMD-24", + "usr/share/terminfo/5/5410-w", + "usr/share/terminfo/5/5630-24", + "usr/share/terminfo/5/5620", + "usr/share/terminfo/5/5051", + "usr/share/terminfo/o/opennt", + "usr/share/terminfo/o/o85h", + "usr/share/terminfo/o/opennt-100", + "usr/share/terminfo/o/opennt-25", + "usr/share/terminfo/o/oldibmpc3", + "usr/share/terminfo/o/os9LII", + "usr/share/terminfo/o/opennt-60-w", + "usr/share/terminfo/o/opennt-60", + "usr/share/terminfo/o/osborne1", + "usr/share/terminfo/o/opennt-50-w", + "usr/share/terminfo/o/otek4112", + "usr/share/terminfo/o/otek4113", + "usr/share/terminfo/o/osexec", + "usr/share/terminfo/o/origpc3", + "usr/share/terminfo/o/o4112-nd", + "usr/share/terminfo/o/opennt-nti", + "usr/share/terminfo/o/oldsun", + "usr/share/terminfo/o/opennt-25-nti", + "usr/share/terminfo/o/opennt-35-w", + "usr/share/terminfo/o/opennt-w-vt", + "usr/share/terminfo/o/opennt-100-nti", + "usr/share/terminfo/o/opennt-60-nti", + "usr/share/terminfo/o/osborne-w", + "usr/share/terminfo/o/omron", + "usr/share/terminfo/o/origibmpc3", + "usr/share/terminfo/o/opennt-25-w", + "usr/share/terminfo/o/opennt-w", + "usr/share/terminfo/o/ofcons", + "usr/share/terminfo/o/opennt-35-nti", + "usr/share/terminfo/o/old-st", + "usr/share/terminfo/o/opennt-25-w-vt", + "usr/share/terminfo/o/oldpc3", + "usr/share/terminfo/o/owl", + "usr/share/terminfo/o/oabm85h", + "usr/share/terminfo/o/oblit", + "usr/share/terminfo/o/opennt-35", + "usr/share/terminfo/o/osborne", + "usr/share/terminfo/o/otek4114", + "usr/share/terminfo/o/opus3n1+", + "usr/share/terminfo/o/osborne1-w", + "usr/share/terminfo/o/otek4115", + "usr/share/terminfo/o/opennt-50-nti", + "usr/share/terminfo/o/opennt-50", + "usr/share/terminfo/o/oconcept", + "usr/share/terminfo/o/ojerq", + "usr/share/terminfo/o/o31", + "usr/share/terminfo/o/oc100", + "usr/share/terminfo/i/ibmapa16", + "usr/share/terminfo/i/ibm3101", + "usr/share/terminfo/i/ibmpc3r", + "usr/share/terminfo/i/ibm+16color", + "usr/share/terminfo/i/ibm5154-c", + "usr/share/terminfo/i/ibm8503", + "usr/share/terminfo/i/i100", + "usr/share/terminfo/i/ibcs2", + "usr/share/terminfo/i/iris-ansi-net", + "usr/share/terminfo/i/iris40", + "usr/share/terminfo/i/infoton", + "usr/share/terminfo/i/i3101", + "usr/share/terminfo/i/ibm8514", + "usr/share/terminfo/i/ipsi", + "usr/share/terminfo/i/ibm3161", + "usr/share/terminfo/i/ibmapa8c", + "usr/share/terminfo/i/i400", + "usr/share/terminfo/i/intext2", + "usr/share/terminfo/i/ibmpc3r-mono", + "usr/share/terminfo/i/ibm+color", + "usr/share/terminfo/i/iq140", + "usr/share/terminfo/i/ibm6153-90", + "usr/share/terminfo/i/ibm3162", + "usr/share/terminfo/i/ibm-system1", + "usr/share/terminfo/i/ibm5081", + "usr/share/terminfo/i/ibm3164", + "usr/share/terminfo/i/ibm6155", + "usr/share/terminfo/i/ibm8514-c", + "usr/share/terminfo/i/ibm327x", + "usr/share/terminfo/i/ibm8604", + "usr/share/terminfo/i/ibmpc", + "usr/share/terminfo/i/icl6402", + "usr/share/terminfo/i/i3164", + "usr/share/terminfo/i/iTerm2.app", + "usr/share/terminfo/i/ibm5151", + "usr/share/terminfo/i/ibm-pc", + "usr/share/terminfo/i/ibm6154-c", + "usr/share/terminfo/i/intertube", + "usr/share/terminfo/i/ims950-b", + "usr/share/terminfo/i/intertube2", + "usr/share/terminfo/i/ibmmono", + "usr/share/terminfo/i/ibm8507", + "usr/share/terminfo/i/iterm2", + "usr/share/terminfo/i/ibm5051", + "usr/share/terminfo/i/ibmaed", + "usr/share/terminfo/i/interix", + "usr/share/terminfo/i/interix-nti", + "usr/share/terminfo/i/ibmega-c", + "usr/share/terminfo/i/ibmmpel-c", + "usr/share/terminfo/i/intextii", + "usr/share/terminfo/i/iq120", + "usr/share/terminfo/i/ibmega", + "usr/share/terminfo/i/ibm3163", + "usr/share/terminfo/i/intext", + "usr/share/terminfo/i/ips", + "usr/share/terminfo/i/ibmapa8", + "usr/share/terminfo/i/ims-ansi", + "usr/share/terminfo/i/ibm5154", + "usr/share/terminfo/i/ibmpcx", + "usr/share/terminfo/i/ibmapa8c-c", + "usr/share/terminfo/i/ibm3161-C", + "usr/share/terminfo/i/intertec", + "usr/share/terminfo/i/iterm", + "usr/share/terminfo/i/ibm6154", + "usr/share/terminfo/i/iTerm.app", + "usr/share/terminfo/i/ibm8512", + "usr/share/terminfo/i/ifmr", + "usr/share/terminfo/i/ibm6153-40", + "usr/share/terminfo/i/iris-color", + "usr/share/terminfo/i/ibmx", + "usr/share/terminfo/i/ibm8513", + "usr/share/terminfo/i/ibmpc3", + "usr/share/terminfo/i/ibmvga-c", + "usr/share/terminfo/i/iris-ansi-ap", + "usr/share/terminfo/i/iris-ansi", + "usr/share/terminfo/i/ibm-apl", + "usr/share/terminfo/i/icl6404-w", + "usr/share/terminfo/i/ims950", + "usr/share/terminfo/i/ibm5081-c", + "usr/share/terminfo/i/ibm3151", + "usr/share/terminfo/i/ims950-rv", + "usr/share/terminfo/i/ibm6153", + "usr/share/terminfo/i/ibmvga", + "usr/share/terminfo/i/icl6404", + "usr/share/terminfo/m/mskermit227", + "usr/share/terminfo/m/mod", + "usr/share/terminfo/m/minitel1b-nb", + "usr/share/terminfo/m/masscomp", + "usr/share/terminfo/m/minitel", + "usr/share/terminfo/m/mime340", + "usr/share/terminfo/m/mime314", + "usr/share/terminfo/m/mai", + "usr/share/terminfo/m/mime-hb", + "usr/share/terminfo/m/morphos", + "usr/share/terminfo/m/mrxvt", + "usr/share/terminfo/m/modgraph48", + "usr/share/terminfo/m/mach", + "usr/share/terminfo/m/mach-gnu-color", + "usr/share/terminfo/m/msk22714", + "usr/share/terminfo/m/mod24", + "usr/share/terminfo/m/mime2a-v", + "usr/share/terminfo/m/macintosh", + "usr/share/terminfo/m/mlterm-256color", + "usr/share/terminfo/m/microbee", + "usr/share/terminfo/m/mlterm", + "usr/share/terminfo/m/mvterm", + "usr/share/terminfo/m/mdl110", + "usr/share/terminfo/m/minix-3.0", + "usr/share/terminfo/m/mime", + "usr/share/terminfo/m/macterminal-w", + "usr/share/terminfo/m/ms-vt100-color", + "usr/share/terminfo/m/masscomp2", + "usr/share/terminfo/m/mskermit227am", + "usr/share/terminfo/m/microterm", + "usr/share/terminfo/m/mouse-sun", + "usr/share/terminfo/m/ms-vt100", + "usr/share/terminfo/m/mlterm3", + "usr/share/terminfo/m/mime-fb", + "usr/share/terminfo/m/minitel12-80", + "usr/share/terminfo/m/memhp", + "usr/share/terminfo/m/mgr-linux", + "usr/share/terminfo/m/minix", + "usr/share/terminfo/m/minitel1", + "usr/share/terminfo/m/minitel-2", + "usr/share/terminfo/m/mime2a-s", + "usr/share/terminfo/m/msk227am", + "usr/share/terminfo/m/mterm", + "usr/share/terminfo/m/mime3ax", + "usr/share/terminfo/m/microterm5", + "usr/share/terminfo/m/mm340", + "usr/share/terminfo/m/minitel-2-nam", + "usr/share/terminfo/m/mrxvt-256color", + "usr/share/terminfo/m/mac", + "usr/share/terminfo/m/mime2", + "usr/share/terminfo/m/megatek", + "usr/share/terminfo/m/mm314", + "usr/share/terminfo/m/mterm-ansi", + "usr/share/terminfo/m/minitel2-80", + "usr/share/terminfo/m/mime2a", + "usr/share/terminfo/m/mac-w", + "usr/share/terminfo/m/mach-color", + "usr/share/terminfo/m/mach-gnu", + "usr/share/terminfo/m/modgraph2", + "usr/share/terminfo/m/minitel1b-80", + "usr/share/terminfo/m/masscomp1", + "usr/share/terminfo/m/mime1", + "usr/share/terminfo/m/msk227", + "usr/share/terminfo/m/m2-nam", + "usr/share/terminfo/m/mgt", + "usr/share/terminfo/m/minix-old-am", + "usr/share/terminfo/m/mime-3ax", + "usr/share/terminfo/m/ms-vt-utf8", + "usr/share/terminfo/m/mgterm", + "usr/share/terminfo/m/mgr-sun", + "usr/share/terminfo/m/minitel1b", + "usr/share/terminfo/m/mlterm+pcfkeys", + "usr/share/terminfo/m/mime3a", + "usr/share/terminfo/m/minix-old", + "usr/share/terminfo/m/minix-1.5", + "usr/share/terminfo/m/minix-1.7", + "usr/share/terminfo/m/modgraph", + "usr/share/terminfo/m/mimeii", + "usr/share/terminfo/m/mgr", + "usr/share/terminfo/m/mt70", + "usr/share/terminfo/m/mono-emx", + "usr/share/terminfo/m/mt4520-rv", + "usr/share/terminfo/m/mskermit22714", + "usr/share/terminfo/m/ms-vt100+", + "usr/share/terminfo/m/mimei", + "usr/share/terminfo/m/mt-70", + "usr/share/terminfo/m/microb", + "usr/share/terminfo/m/mach-bold", + "usr/share/terminfo/m/minitel1-nb", + "usr/share/terminfo/m/mlterm2", + "usr/share/terminfo/4/4025ex", + "usr/share/terminfo/4/4410-w", + "usr/share/terminfo/4/4027ex", + "usr/share/terminfo/X/X-hpterm", + "usr/share/terminfo/q/qvt119-w", + "usr/share/terminfo/q/qvt203-25-w", + "usr/share/terminfo/q/qume", + "usr/share/terminfo/q/qvt108", + "usr/share/terminfo/q/qdcons", + "usr/share/terminfo/q/qnxm", + "usr/share/terminfo/q/qvt203+", + "usr/share/terminfo/q/qvt119+-w", + "usr/share/terminfo/q/qvt203-w", + "usr/share/terminfo/q/qvt119p-25-w", + "usr/share/terminfo/q/qvt101+", + "usr/share/terminfo/q/qvt119+-25-w", + "usr/share/terminfo/q/qvt102", + "usr/share/terminfo/q/qansi-t", + "usr/share/terminfo/q/qnxtmono", + "usr/share/terminfo/q/qnxt2", + "usr/share/terminfo/q/qvt103-w", + "usr/share/terminfo/q/qansi", + "usr/share/terminfo/q/qvt203-w-am", + "usr/share/terminfo/q/qvt119+-25", + "usr/share/terminfo/q/qvt119p-w", + "usr/share/terminfo/q/qvt103", + "usr/share/terminfo/q/qdss", + "usr/share/terminfo/q/qvt119-25-w", + "usr/share/terminfo/q/qvt203-25", + "usr/share/terminfo/q/qvt119", + "usr/share/terminfo/q/qnxw", + "usr/share/terminfo/q/qansi-g", + "usr/share/terminfo/q/qvt119p", + "usr/share/terminfo/q/qnxt4", + "usr/share/terminfo/q/qvt101p", + "usr/share/terminfo/q/qvt203", + "usr/share/terminfo/q/qvt119p-25", + "usr/share/terminfo/q/qnx", + "usr/share/terminfo/q/qume5", + "usr/share/terminfo/q/qansi-m", + "usr/share/terminfo/q/qvt101", + "usr/share/terminfo/q/qvt119+", + "usr/share/terminfo/q/qnx4", + "usr/share/terminfo/q/qansi-w", + "usr/share/terminfo/q/qnxt" + ] + }, + { + "ID": "ncurses-terminfo-base@6.0_p20171125-r1", + "Name": "ncurses-terminfo-base", + "Identifier": { + "PURL": "pkg:apk/alpine/ncurses-terminfo-base@6.0_p20171125-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "6.0_p20171125-r1", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.0_p20171125-r1", + "Licenses": [ + "MIT" + ], + "Layer": { + "Digest": "sha256:3d6152f6ac208640f9fb494d1c379fe508db1fc5754cd08fefec200bddd13e0e", + "DiffID": "sha256:6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013" + }, + "Digest": "sha1:967549060a1cf0ef40e1f1d2fff56b8e45e999c2", + "InstalledFiles": [ + "etc/terminfo/a/ansi", + "etc/terminfo/r/rxvt", + "etc/terminfo/d/dumb", + "etc/terminfo/l/linux", + "etc/terminfo/x/xterm", + "etc/terminfo/x/xterm-color", + "etc/terminfo/x/xterm-xfree86", + "etc/terminfo/v/vt52", + "etc/terminfo/v/vt200", + "etc/terminfo/v/vt220", + "etc/terminfo/v/vt102", + "etc/terminfo/v/vt100", + "etc/terminfo/s/screen", + "etc/terminfo/s/sun" + ] + }, + { + "ID": "openssh@7.5_p1-r9", + "Name": "openssh", + "Identifier": { + "PURL": "pkg:apk/alpine/openssh@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "7.5_p1-r9", + "Arch": "x86_64", + "SrcName": "openssh", + "SrcVersion": "7.5_p1-r9", + "Licenses": [ + "as-is" + ], + "DependsOn": [ + "libressl2.6-libcrypto@2.6.5-r0", + "musl@1.1.18-r3", + "openssh-client@7.5_p1-r9", + "openssh-server@7.5_p1-r9", + "openssh-sftp-server@7.5_p1-r9" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:f1d37d2f20528e68bc78028934fa749abd56fcd9", + "InstalledFiles": [ + "usr/lib/ssh/ssh-pkcs11-helper" + ] + }, + { + "ID": "openssh-client@7.5_p1-r9", + "Name": "openssh-client", + "Identifier": { + "PURL": "pkg:apk/alpine/openssh-client@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "7.5_p1-r9", + "Arch": "x86_64", + "SrcName": "openssh", + "SrcVersion": "7.5_p1-r9", + "Licenses": [ + "as-is" + ], + "DependsOn": [ + "libressl2.6-libcrypto@2.6.5-r0", + "musl@1.1.18-r3", + "openssh-keygen@7.5_p1-r9", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:8cfbbee8796be1acaa03e8ee58f431dad21ef3c5", + "InstalledFiles": [ + "etc/ssh/moduli", + "etc/ssh/ssh_config", + "usr/bin/ssh-pkcs11-helper", + "usr/bin/ssh", + "usr/bin/sftp", + "usr/bin/scp", + "usr/bin/ssh-keyscan", + "usr/bin/ssh-copy-id", + "usr/bin/ssh-add", + "usr/bin/ssh-agent", + "usr/bin/findssl.sh" + ] + }, + { + "ID": "openssh-keygen@7.5_p1-r9", + "Name": "openssh-keygen", + "Identifier": { + "PURL": "pkg:apk/alpine/openssh-keygen@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "7.5_p1-r9", + "Arch": "x86_64", + "SrcName": "openssh", + "SrcVersion": "7.5_p1-r9", + "Licenses": [ + "as-is" + ], + "DependsOn": [ + "libressl2.6-libcrypto@2.6.5-r0", + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:c610fb52fb90da0e25eea69c1e55d02cf44ba05d", + "InstalledFiles": [ + "usr/bin/ssh-keygen" + ] + }, + { + "ID": "openssh-server@7.5_p1-r9", + "Name": "openssh-server", + "Identifier": { + "PURL": "pkg:apk/alpine/openssh-server@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "7.5_p1-r9", + "Arch": "x86_64", + "SrcName": "openssh", + "SrcVersion": "7.5_p1-r9", + "Licenses": [ + "as-is" + ], + "DependsOn": [ + "libressl2.6-libcrypto@2.6.5-r0", + "musl@1.1.18-r3", + "openssh-keygen@7.5_p1-r9", + "openssh-server-common@7.5_p1-r9", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:326edd676b254aefe048536473c540b28fb335ce", + "InstalledFiles": [ + "usr/sbin/sshd" + ] + }, + { + "ID": "openssh-server-common@7.5_p1-r9", + "Name": "openssh-server-common", + "Identifier": { + "PURL": "pkg:apk/alpine/openssh-server-common@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "7.5_p1-r9", + "Arch": "x86_64", + "SrcName": "openssh", + "SrcVersion": "7.5_p1-r9", + "Licenses": [ + "as-is" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:f055712504ebbe781cf4c6a6987a878067dd65fd", + "InstalledFiles": [ + "etc/ssh/sshd_config", + "etc/init.d/sshd", + "etc/conf.d/sshd" + ] + }, + { + "ID": "openssh-sftp-server@7.5_p1-r9", + "Name": "openssh-sftp-server", + "Identifier": { + "PURL": "pkg:apk/alpine/openssh-sftp-server@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "7.5_p1-r9", + "Arch": "x86_64", + "SrcName": "openssh", + "SrcVersion": "7.5_p1-r9", + "Licenses": [ + "as-is" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:6c1dcaaba744742843ca4766f21ca546e7813930", + "InstalledFiles": [ + "usr/lib/ssh/sftp-server" + ] + }, + { + "ID": "patch@2.7.5-r2", + "Name": "patch", + "Identifier": { + "PURL": "pkg:apk/alpine/patch@2.7.5-r2?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.7.5-r2", + "Arch": "x86_64", + "SrcName": "patch", + "SrcVersion": "2.7.5-r2", + "Licenses": [ + "GPL-3.0" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:8ab18877f726014de8dff1368937414ae8929abd", + "InstalledFiles": [ + "usr/bin/patch" + ] + }, + { + "ID": "pcre2@10.30-r0", + "Name": "pcre2", + "Identifier": { + "PURL": "pkg:apk/alpine/pcre2@10.30-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "10.30-r0", + "Arch": "x86_64", + "SrcName": "pcre2", + "SrcVersion": "10.30-r0", + "Licenses": [ + "BSD-3-Clause" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:5d9f8804b6343a6dd1df0d131b4f342294a423af", + "InstalledFiles": [ + "usr/lib/libpcre2-8.so.0.6.0", + "usr/lib/libpcre2-8.so.0", + "usr/lib/libpcre2-posix.so.2", + "usr/lib/libpcre2-posix.so.2.0.0" + ] + }, + { + "ID": "pkgconf@1.3.10-r0", + "Name": "pkgconf", + "Identifier": { + "PURL": "pkg:apk/alpine/pkgconf@1.3.10-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.3.10-r0", + "Arch": "x86_64", + "SrcName": "pkgconf", + "SrcVersion": "1.3.10-r0", + "Licenses": [ + "ISC" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:bcdf2647b07bae9fffd76433d0defcc6e8dbea21", + "InstalledFiles": [ + "usr/bin/pkgconf", + "usr/bin/pkg-config", + "usr/lib/libpkgconf.so.2", + "usr/lib/libpkgconf.so.2.0.0", + "usr/share/aclocal/pkg.m4" + ] + }, + { + "ID": "python2@2.7.15-r2", + "Name": "python2", + "Identifier": { + "PURL": "pkg:apk/alpine/python2@2.7.15-r2?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "2.7.15-r2", + "Arch": "x86_64", + "SrcName": "python2", + "SrcVersion": "2.7.15-r2", + "Licenses": [ + "custom" + ], + "DependsOn": [ + "expat@2.2.5-r0", + "gdbm@1.13-r1", + "libbz2@1.0.6-r6", + "libffi@3.2.1-r4", + "libressl2.6-libcrypto@2.6.5-r0", + "libressl2.6-libssl@2.6.5-r0", + "musl@1.1.18-r3", + "ncurses-libs@6.0_p20171125-r1", + "readline@7.0.003-r0", + "sqlite-libs@3.21.0-r1", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:e535f131326fc346ffe23083b79fb688a220796a", + "InstalledFiles": [ + "usr/bin/pydoc", + "usr/bin/python", + "usr/bin/python2.7", + "usr/bin/python2", + "usr/bin/idle", + "usr/bin/smtpd.py", + "usr/lib/libpython2.7.so.1.0", + "usr/lib/python2.7/_sysconfigdata.pyc", + "usr/lib/python2.7/DocXMLRPCServer.pyo", + "usr/lib/python2.7/new.py", + "usr/lib/python2.7/profile.pyc", + "usr/lib/python2.7/pprint.pyo", + "usr/lib/python2.7/smtpd.pyc", + "usr/lib/python2.7/pdb.doc", + "usr/lib/python2.7/UserDict.pyc", + "usr/lib/python2.7/_abcoll.py", + "usr/lib/python2.7/_pyio.pyo", + "usr/lib/python2.7/ihooks.pyc", + "usr/lib/python2.7/chunk.pyc", + "usr/lib/python2.7/_threading_local.pyo", + "usr/lib/python2.7/wave.pyc", + "usr/lib/python2.7/doctest.pyo", + "usr/lib/python2.7/weakref.pyo", + "usr/lib/python2.7/pkgutil.pyc", + "usr/lib/python2.7/sre_parse.py", + "usr/lib/python2.7/macurl2path.pyo", + "usr/lib/python2.7/quopri.py", + "usr/lib/python2.7/numbers.py", + "usr/lib/python2.7/_threading_local.py", + "usr/lib/python2.7/telnetlib.pyo", + "usr/lib/python2.7/abc.pyc", + "usr/lib/python2.7/pkgutil.pyo", + "usr/lib/python2.7/poplib.pyc", + "usr/lib/python2.7/__future__.pyo", + "usr/lib/python2.7/symtable.pyo", + "usr/lib/python2.7/mimetypes.pyc", + "usr/lib/python2.7/_MozillaCookieJar.py", + "usr/lib/python2.7/_osx_support.pyc", + "usr/lib/python2.7/tokenize.py", + "usr/lib/python2.7/dircache.pyc", + "usr/lib/python2.7/rexec.pyo", + "usr/lib/python2.7/CGIHTTPServer.pyc", + "usr/lib/python2.7/this.pyo", + "usr/lib/python2.7/timeit.pyo", + "usr/lib/python2.7/rlcompleter.py", + "usr/lib/python2.7/urlparse.pyc", + "usr/lib/python2.7/__phello__.foo.pyc", + "usr/lib/python2.7/Cookie.py", + "usr/lib/python2.7/pickletools.py", + "usr/lib/python2.7/stringold.pyo", + "usr/lib/python2.7/pstats.pyc", + "usr/lib/python2.7/filecmp.py", + "usr/lib/python2.7/difflib.pyo", + "usr/lib/python2.7/getpass.pyo", + "usr/lib/python2.7/linecache.py", + "usr/lib/python2.7/rlcompleter.pyo", + "usr/lib/python2.7/mailcap.py", + "usr/lib/python2.7/uuid.py", + "usr/lib/python2.7/xmlrpclib.pyc", + "usr/lib/python2.7/calendar.pyo", + "usr/lib/python2.7/struct.py", + "usr/lib/python2.7/MimeWriter.py", + "usr/lib/python2.7/zipfile.pyc", + "usr/lib/python2.7/platform.py", + "usr/lib/python2.7/inspect.pyc", + "usr/lib/python2.7/warnings.pyo", + "usr/lib/python2.7/tty.pyo", + "usr/lib/python2.7/shelve.pyo", + "usr/lib/python2.7/whichdb.py", + "usr/lib/python2.7/asynchat.py", + "usr/lib/python2.7/plistlib.py", + "usr/lib/python2.7/runpy.py", + "usr/lib/python2.7/anydbm.py", + "usr/lib/python2.7/os.py", + "usr/lib/python2.7/cookielib.py", + "usr/lib/python2.7/_LWPCookieJar.pyo", + "usr/lib/python2.7/urllib.pyc", + "usr/lib/python2.7/SimpleHTTPServer.pyo", + "usr/lib/python2.7/cgi.pyo", + "usr/lib/python2.7/cProfile.pyo", + "usr/lib/python2.7/smtpd.pyo", + "usr/lib/python2.7/multifile.pyc", + "usr/lib/python2.7/mailcap.pyo", + "usr/lib/python2.7/repr.pyo", + "usr/lib/python2.7/calendar.pyc", + "usr/lib/python2.7/functools.pyo", + "usr/lib/python2.7/macurl2path.pyc", + "usr/lib/python2.7/getopt.py", + "usr/lib/python2.7/mimify.py", + "usr/lib/python2.7/functools.py", + "usr/lib/python2.7/dbhash.py", + "usr/lib/python2.7/_MozillaCookieJar.pyc", + "usr/lib/python2.7/struct.pyc", + "usr/lib/python2.7/pty.pyo", + "usr/lib/python2.7/_MozillaCookieJar.pyo", + "usr/lib/python2.7/urlparse.pyo", + "usr/lib/python2.7/this.py", + "usr/lib/python2.7/user.py", + "usr/lib/python2.7/threading.pyc", + "usr/lib/python2.7/timeit.pyc", + "usr/lib/python2.7/stringprep.pyo", + "usr/lib/python2.7/argparse.pyo", + "usr/lib/python2.7/pkgutil.py", + "usr/lib/python2.7/wave.py", + "usr/lib/python2.7/aifc.pyo", + "usr/lib/python2.7/atexit.pyo", + "usr/lib/python2.7/HTMLParser.pyo", + "usr/lib/python2.7/nntplib.py", + "usr/lib/python2.7/toaiff.py", + "usr/lib/python2.7/fpformat.pyc", + "usr/lib/python2.7/antigravity.pyc", + "usr/lib/python2.7/asyncore.pyo", + "usr/lib/python2.7/fnmatch.py", + "usr/lib/python2.7/mailbox.py", + "usr/lib/python2.7/uuid.pyc", + "usr/lib/python2.7/shutil.py", + "usr/lib/python2.7/multifile.py", + "usr/lib/python2.7/poplib.py", + "usr/lib/python2.7/SimpleHTTPServer.py", + "usr/lib/python2.7/UserString.pyo", + "usr/lib/python2.7/sysconfig.pyo", + "usr/lib/python2.7/mimify.pyc", + "usr/lib/python2.7/sre_constants.py", + "usr/lib/python2.7/token.pyo", + "usr/lib/python2.7/audiodev.pyo", + "usr/lib/python2.7/os.pyo", + "usr/lib/python2.7/quopri.pyc", + "usr/lib/python2.7/shutil.pyc", + "usr/lib/python2.7/glob.pyo", + "usr/lib/python2.7/glob.pyc", + "usr/lib/python2.7/sndhdr.pyo", + "usr/lib/python2.7/urllib.py", + "usr/lib/python2.7/uuid.pyo", + "usr/lib/python2.7/base64.py", + "usr/lib/python2.7/Queue.pyc", + "usr/lib/python2.7/pyclbr.pyc", + "usr/lib/python2.7/traceback.pyo", + "usr/lib/python2.7/telnetlib.pyc", + "usr/lib/python2.7/dis.py", + "usr/lib/python2.7/_threading_local.pyc", + "usr/lib/python2.7/runpy.pyo", + "usr/lib/python2.7/subprocess.pyo", + "usr/lib/python2.7/xmllib.py", + "usr/lib/python2.7/stringold.pyc", + "usr/lib/python2.7/sre_compile.py", + "usr/lib/python2.7/webbrowser.pyo", + "usr/lib/python2.7/pickle.py", + "usr/lib/python2.7/hmac.pyo", + "usr/lib/python2.7/base64.pyo", + "usr/lib/python2.7/codeop.pyo", + "usr/lib/python2.7/locale.py", + "usr/lib/python2.7/posixpath.pyc", + "usr/lib/python2.7/popen2.pyc", + "usr/lib/python2.7/binhex.py", + "usr/lib/python2.7/this.pyc", + "usr/lib/python2.7/cmd.pyc", + "usr/lib/python2.7/pickle.pyo", + "usr/lib/python2.7/shutil.pyo", + "usr/lib/python2.7/profile.py", + "usr/lib/python2.7/SimpleHTTPServer.pyc", + "usr/lib/python2.7/textwrap.pyc", + "usr/lib/python2.7/sets.pyo", + "usr/lib/python2.7/site.pyo", + "usr/lib/python2.7/genericpath.py", + "usr/lib/python2.7/timeit.py", + "usr/lib/python2.7/uu.pyo", + "usr/lib/python2.7/nntplib.pyc", + "usr/lib/python2.7/colorsys.pyo", + "usr/lib/python2.7/imputil.pyc", + "usr/lib/python2.7/sched.py", + "usr/lib/python2.7/base64.pyc", + "usr/lib/python2.7/xdrlib.pyo", + "usr/lib/python2.7/imaplib.pyo", + "usr/lib/python2.7/tabnanny.pyo", + "usr/lib/python2.7/sched.pyc", + "usr/lib/python2.7/macpath.py", + "usr/lib/python2.7/abc.py", + "usr/lib/python2.7/htmllib.pyc", + "usr/lib/python2.7/tempfile.pyo", + "usr/lib/python2.7/wave.pyo", + "usr/lib/python2.7/compileall.pyo", + "usr/lib/python2.7/HTMLParser.py", + "usr/lib/python2.7/bisect.py", + "usr/lib/python2.7/shlex.pyo", + "usr/lib/python2.7/tty.pyc", + "usr/lib/python2.7/stat.pyc", + "usr/lib/python2.7/mhlib.pyc", + "usr/lib/python2.7/mutex.pyo", + "usr/lib/python2.7/nturl2path.py", + "usr/lib/python2.7/rlcompleter.pyc", + "usr/lib/python2.7/formatter.pyc", + "usr/lib/python2.7/poplib.pyo", + "usr/lib/python2.7/antigravity.py", + "usr/lib/python2.7/tokenize.pyo", + "usr/lib/python2.7/pickletools.pyc", + "usr/lib/python2.7/asyncore.py", + "usr/lib/python2.7/decimal.pyc", + "usr/lib/python2.7/ftplib.py", + "usr/lib/python2.7/fileinput.pyc", + "usr/lib/python2.7/smtplib.pyo", + "usr/lib/python2.7/pipes.pyc", + "usr/lib/python2.7/hmac.pyc", + "usr/lib/python2.7/ast.pyc", + "usr/lib/python2.7/dircache.py", + "usr/lib/python2.7/cmd.pyo", + "usr/lib/python2.7/stat.pyo", + "usr/lib/python2.7/tempfile.py", + "usr/lib/python2.7/traceback.py", + "usr/lib/python2.7/binhex.pyc", + "usr/lib/python2.7/_osx_support.pyo", + "usr/lib/python2.7/toaiff.pyo", + "usr/lib/python2.7/xdrlib.py", + "usr/lib/python2.7/locale.pyc", + "usr/lib/python2.7/types.py", + "usr/lib/python2.7/difflib.py", + "usr/lib/python2.7/CGIHTTPServer.pyo", + "usr/lib/python2.7/codecs.pyo", + "usr/lib/python2.7/opcode.pyc", + "usr/lib/python2.7/cgitb.pyc", + "usr/lib/python2.7/mhlib.py", + "usr/lib/python2.7/gettext.pyo", + "usr/lib/python2.7/macpath.pyo", + "usr/lib/python2.7/pdb.pyo", + "usr/lib/python2.7/quopri.pyo", + "usr/lib/python2.7/subprocess.py", + "usr/lib/python2.7/gzip.pyc", + "usr/lib/python2.7/getopt.pyo", + "usr/lib/python2.7/multifile.pyo", + "usr/lib/python2.7/tarfile.pyo", + "usr/lib/python2.7/ihooks.pyo", + "usr/lib/python2.7/dummy_threading.py", + "usr/lib/python2.7/_sysconfigdata.pyo", + "usr/lib/python2.7/pickle.pyc", + "usr/lib/python2.7/_LWPCookieJar.py", + "usr/lib/python2.7/xdrlib.pyc", + "usr/lib/python2.7/bisect.pyc", + "usr/lib/python2.7/LICENSE.txt", + "usr/lib/python2.7/sunaudio.py", + "usr/lib/python2.7/_abcoll.pyc", + "usr/lib/python2.7/string.pyc", + "usr/lib/python2.7/sunaudio.pyo", + "usr/lib/python2.7/getopt.pyc", + "usr/lib/python2.7/symtable.py", + "usr/lib/python2.7/codecs.py", + "usr/lib/python2.7/pdb.py", + "usr/lib/python2.7/hashlib.py", + "usr/lib/python2.7/code.pyo", + "usr/lib/python2.7/mailcap.pyc", + "usr/lib/python2.7/dis.pyo", + "usr/lib/python2.7/sets.py", + "usr/lib/python2.7/pydoc.pyo", + "usr/lib/python2.7/optparse.pyo", + "usr/lib/python2.7/hmac.py", + "usr/lib/python2.7/UserList.pyc", + "usr/lib/python2.7/fractions.pyc", + "usr/lib/python2.7/io.pyo", + "usr/lib/python2.7/py_compile.pyo", + "usr/lib/python2.7/popen2.pyo", + "usr/lib/python2.7/pty.py", + "usr/lib/python2.7/sunau.pyo", + "usr/lib/python2.7/atexit.py", + "usr/lib/python2.7/heapq.py", + "usr/lib/python2.7/sha.pyo", + "usr/lib/python2.7/sunaudio.pyc", + "usr/lib/python2.7/webbrowser.pyc", + "usr/lib/python2.7/pty.pyc", + "usr/lib/python2.7/urllib.pyo", + "usr/lib/python2.7/posixfile.py", + "usr/lib/python2.7/colorsys.py", + "usr/lib/python2.7/threading.pyo", + "usr/lib/python2.7/contextlib.pyc", + "usr/lib/python2.7/robotparser.pyo", + "usr/lib/python2.7/commands.py", + "usr/lib/python2.7/ssl.pyc", + "usr/lib/python2.7/commands.pyo", + "usr/lib/python2.7/binhex.pyo", + "usr/lib/python2.7/_weakrefset.pyo", + "usr/lib/python2.7/traceback.pyc", + "usr/lib/python2.7/SocketServer.pyc", + "usr/lib/python2.7/new.pyo", + "usr/lib/python2.7/cgi.pyc", + "usr/lib/python2.7/ihooks.py", + "usr/lib/python2.7/random.pyc", + "usr/lib/python2.7/MimeWriter.pyc", + "usr/lib/python2.7/Queue.pyo", + "usr/lib/python2.7/mimetypes.py", + "usr/lib/python2.7/htmllib.pyo", + "usr/lib/python2.7/_strptime.pyc", + "usr/lib/python2.7/ConfigParser.pyo", + "usr/lib/python2.7/modulefinder.py", + "usr/lib/python2.7/doctest.pyc", + "usr/lib/python2.7/xmlrpclib.pyo", + "usr/lib/python2.7/ftplib.pyo", + "usr/lib/python2.7/csv.pyo", + "usr/lib/python2.7/abc.pyo", + "usr/lib/python2.7/tty.py", + "usr/lib/python2.7/asynchat.pyo", + "usr/lib/python2.7/robotparser.pyc", + "usr/lib/python2.7/mimetools.pyc", + "usr/lib/python2.7/pstats.pyo", + "usr/lib/python2.7/HTMLParser.pyc", + "usr/lib/python2.7/DocXMLRPCServer.py", + "usr/lib/python2.7/_strptime.py", + "usr/lib/python2.7/re.pyo", + "usr/lib/python2.7/chunk.py", + "usr/lib/python2.7/DocXMLRPCServer.pyc", + "usr/lib/python2.7/mailbox.pyc", + "usr/lib/python2.7/argparse.pyc", + "usr/lib/python2.7/filecmp.pyo", + "usr/lib/python2.7/tempfile.pyc", + "usr/lib/python2.7/_pyio.py", + "usr/lib/python2.7/contextlib.py", + "usr/lib/python2.7/keyword.py", + "usr/lib/python2.7/ftplib.pyc", + "usr/lib/python2.7/codeop.py", + "usr/lib/python2.7/sre_parse.pyo", + "usr/lib/python2.7/StringIO.pyc", + "usr/lib/python2.7/numbers.pyo", + "usr/lib/python2.7/compileall.py", + "usr/lib/python2.7/BaseHTTPServer.pyo", + "usr/lib/python2.7/ntpath.pyo", + "usr/lib/python2.7/collections.pyo", + "usr/lib/python2.7/rexec.pyc", + "usr/lib/python2.7/optparse.py", + "usr/lib/python2.7/anydbm.pyc", + "usr/lib/python2.7/cgitb.py", + "usr/lib/python2.7/copy_reg.py", + "usr/lib/python2.7/Cookie.pyc", + "usr/lib/python2.7/cProfile.py", + "usr/lib/python2.7/_strptime.pyo", + "usr/lib/python2.7/calendar.py", + "usr/lib/python2.7/sndhdr.pyc", + "usr/lib/python2.7/fpformat.pyo", + "usr/lib/python2.7/_pyio.pyc", + "usr/lib/python2.7/shelve.pyc", + "usr/lib/python2.7/sre_parse.pyc", + "usr/lib/python2.7/pyclbr.py", + "usr/lib/python2.7/statvfs.pyo", + "usr/lib/python2.7/trace.pyc", + "usr/lib/python2.7/codeop.pyc", + "usr/lib/python2.7/StringIO.py", + "usr/lib/python2.7/ssl.py", + "usr/lib/python2.7/sre_constants.pyc", + "usr/lib/python2.7/htmlentitydefs.py", + "usr/lib/python2.7/symbol.py", + "usr/lib/python2.7/argparse.py", + "usr/lib/python2.7/dumbdbm.py", + "usr/lib/python2.7/sre_compile.pyc", + "usr/lib/python2.7/keyword.pyc", + "usr/lib/python2.7/os.pyc", + "usr/lib/python2.7/mutex.py", + "usr/lib/python2.7/mutex.pyc", + "usr/lib/python2.7/Bastion.pyc", + "usr/lib/python2.7/stat.py", + "usr/lib/python2.7/textwrap.pyo", + "usr/lib/python2.7/imghdr.py", + "usr/lib/python2.7/getpass.pyc", + "usr/lib/python2.7/cookielib.pyo", + "usr/lib/python2.7/uu.py", + "usr/lib/python2.7/__future__.py", + "usr/lib/python2.7/Bastion.pyo", + "usr/lib/python2.7/os2emxpath.pyo", + "usr/lib/python2.7/subprocess.pyc", + "usr/lib/python2.7/symbol.pyo", + "usr/lib/python2.7/bdb.py", + "usr/lib/python2.7/stringold.py", + "usr/lib/python2.7/inspect.pyo", + "usr/lib/python2.7/opcode.py", + "usr/lib/python2.7/sgmllib.pyc", + "usr/lib/python2.7/popen2.py", + "usr/lib/python2.7/runpy.pyc", + "usr/lib/python2.7/md5.py", + "usr/lib/python2.7/anydbm.pyo", + "usr/lib/python2.7/contextlib.pyo", + "usr/lib/python2.7/genericpath.pyo", + "usr/lib/python2.7/tabnanny.py", + "usr/lib/python2.7/io.pyc", + "usr/lib/python2.7/ssl.pyo", + "usr/lib/python2.7/smtplib.pyc", + "usr/lib/python2.7/decimal.py", + "usr/lib/python2.7/cProfile.pyc", + "usr/lib/python2.7/genericpath.pyc", + "usr/lib/python2.7/posixfile.pyo", + "usr/lib/python2.7/rfc822.pyc", + "usr/lib/python2.7/rexec.py", + "usr/lib/python2.7/chunk.pyo", + "usr/lib/python2.7/pprint.pyc", + "usr/lib/python2.7/ntpath.pyc", + "usr/lib/python2.7/netrc.pyc", + "usr/lib/python2.7/markupbase.py", + "usr/lib/python2.7/dummy_thread.pyc", + "usr/lib/python2.7/profile.pyo", + "usr/lib/python2.7/sha.pyc", + "usr/lib/python2.7/SocketServer.py", + "usr/lib/python2.7/robotparser.py", + "usr/lib/python2.7/csv.py", + "usr/lib/python2.7/functools.pyc", + "usr/lib/python2.7/io.py", + "usr/lib/python2.7/imaplib.pyc", + "usr/lib/python2.7/os2emxpath.pyc", + "usr/lib/python2.7/SimpleXMLRPCServer.pyc", + "usr/lib/python2.7/htmllib.py", + "usr/lib/python2.7/tabnanny.pyc", + "usr/lib/python2.7/site.py", + "usr/lib/python2.7/posixpath.py", + "usr/lib/python2.7/urllib2.py", + "usr/lib/python2.7/pstats.py", + "usr/lib/python2.7/mailbox.pyo", + "usr/lib/python2.7/plistlib.pyo", + "usr/lib/python2.7/commands.pyc", + "usr/lib/python2.7/code.py", + "usr/lib/python2.7/copy_reg.pyo", + "usr/lib/python2.7/dumbdbm.pyo", + "usr/lib/python2.7/symbol.pyc", + "usr/lib/python2.7/fnmatch.pyo", + "usr/lib/python2.7/random.py", + "usr/lib/python2.7/webbrowser.py", + "usr/lib/python2.7/md5.pyc", + "usr/lib/python2.7/keyword.pyo", + "usr/lib/python2.7/Cookie.pyo", + "usr/lib/python2.7/_LWPCookieJar.pyc", + "usr/lib/python2.7/imghdr.pyc", + "usr/lib/python2.7/warnings.pyc", + "usr/lib/python2.7/__phello__.foo.py", + "usr/lib/python2.7/os2emxpath.py", + "usr/lib/python2.7/site.pyc", + "usr/lib/python2.7/aifc.py", + "usr/lib/python2.7/getpass.py", + "usr/lib/python2.7/heapq.pyo", + "usr/lib/python2.7/cmd.py", + "usr/lib/python2.7/netrc.py", + "usr/lib/python2.7/imputil.pyo", + "usr/lib/python2.7/compileall.pyc", + "usr/lib/python2.7/tarfile.pyc", + "usr/lib/python2.7/dbhash.pyo", + "usr/lib/python2.7/bdb.pyc", + "usr/lib/python2.7/gzip.py", + "usr/lib/python2.7/csv.pyc", + "usr/lib/python2.7/UserDict.py", + "usr/lib/python2.7/heapq.pyc", + "usr/lib/python2.7/smtpd.py", + "usr/lib/python2.7/sched.pyo", + "usr/lib/python2.7/numbers.pyc", + "usr/lib/python2.7/asynchat.pyc", + "usr/lib/python2.7/aifc.pyc", + "usr/lib/python2.7/audiodev.pyc", + "usr/lib/python2.7/fractions.py", + "usr/lib/python2.7/random.pyo", + "usr/lib/python2.7/string.pyo", + "usr/lib/python2.7/trace.pyo", + "usr/lib/python2.7/xmllib.pyc", + "usr/lib/python2.7/Bastion.py", + "usr/lib/python2.7/socket.pyc", + "usr/lib/python2.7/copy.py", + "usr/lib/python2.7/shlex.pyc", + "usr/lib/python2.7/whichdb.pyo", + "usr/lib/python2.7/sre_compile.pyo", + "usr/lib/python2.7/markupbase.pyc", + "usr/lib/python2.7/mhlib.pyo", + "usr/lib/python2.7/codecs.pyc", + "usr/lib/python2.7/sre_constants.pyo", + "usr/lib/python2.7/zipfile.pyo", + "usr/lib/python2.7/types.pyo", + "usr/lib/python2.7/plistlib.pyc", + "usr/lib/python2.7/tarfile.py", + "usr/lib/python2.7/pyclbr.pyo", + "usr/lib/python2.7/netrc.pyo", + "usr/lib/python2.7/MimeWriter.pyo", + "usr/lib/python2.7/platform.pyo", + "usr/lib/python2.7/copy.pyo", + "usr/lib/python2.7/rfc822.pyo", + "usr/lib/python2.7/_sysconfigdata.py", + "usr/lib/python2.7/pdb.pyc", + "usr/lib/python2.7/weakref.py", + "usr/lib/python2.7/sysconfig.pyc", + "usr/lib/python2.7/pydoc.pyc", + "usr/lib/python2.7/token.py", + "usr/lib/python2.7/pipes.py", + "usr/lib/python2.7/threading.py", + "usr/lib/python2.7/_weakrefset.py", + "usr/lib/python2.7/ast.pyo", + "usr/lib/python2.7/dbhash.pyc", + "usr/lib/python2.7/optparse.pyc", + "usr/lib/python2.7/sha.py", + "usr/lib/python2.7/macpath.pyc", + "usr/lib/python2.7/weakref.pyc", + "usr/lib/python2.7/UserList.py", + "usr/lib/python2.7/SocketServer.pyo", + "usr/lib/python2.7/atexit.pyc", + "usr/lib/python2.7/uu.pyc", + "usr/lib/python2.7/cgi.py", + "usr/lib/python2.7/whichdb.pyc", + "usr/lib/python2.7/modulefinder.pyo", + "usr/lib/python2.7/copy_reg.pyc", + "usr/lib/python2.7/socket.pyo", + "usr/lib/python2.7/nturl2path.pyo", + "usr/lib/python2.7/md5.pyo", + "usr/lib/python2.7/Queue.py", + "usr/lib/python2.7/dircache.pyo", + "usr/lib/python2.7/xmlrpclib.py", + "usr/lib/python2.7/hashlib.pyo", + "usr/lib/python2.7/token.pyc", + "usr/lib/python2.7/socket.py", + "usr/lib/python2.7/fractions.pyo", + "usr/lib/python2.7/ConfigParser.py", + "usr/lib/python2.7/CGIHTTPServer.py", + "usr/lib/python2.7/trace.py", + "usr/lib/python2.7/__phello__.foo.pyo", + "usr/lib/python2.7/imaplib.py", + "usr/lib/python2.7/filecmp.pyc", + "usr/lib/python2.7/markupbase.pyo", + "usr/lib/python2.7/nturl2path.pyc", + "usr/lib/python2.7/toaiff.pyc", + "usr/lib/python2.7/copy.pyc", + "usr/lib/python2.7/asyncore.pyc", + "usr/lib/python2.7/linecache.pyc", + "usr/lib/python2.7/locale.pyo", + "usr/lib/python2.7/warnings.py", + "usr/lib/python2.7/sre.py", + "usr/lib/python2.7/dummy_threading.pyo", + "usr/lib/python2.7/py_compile.py", + "usr/lib/python2.7/gzip.pyo", + "usr/lib/python2.7/bdb.pyo", + "usr/lib/python2.7/imghdr.pyo", + "usr/lib/python2.7/decimal.pyo", + "usr/lib/python2.7/dis.pyc", + "usr/lib/python2.7/rfc822.py", + "usr/lib/python2.7/shlex.py", + "usr/lib/python2.7/cgitb.pyo", + "usr/lib/python2.7/sgmllib.py", + "usr/lib/python2.7/platform.pyc", + "usr/lib/python2.7/UserList.pyo", + "usr/lib/python2.7/dummy_threading.pyc", + "usr/lib/python2.7/colorsys.pyc", + "usr/lib/python2.7/difflib.pyc", + "usr/lib/python2.7/nntplib.pyo", + "usr/lib/python2.7/pprint.py", + "usr/lib/python2.7/statvfs.py", + "usr/lib/python2.7/posixfile.pyc", + "usr/lib/python2.7/linecache.pyo", + "usr/lib/python2.7/re.pyc", + "usr/lib/python2.7/fileinput.py", + "usr/lib/python2.7/user.pyc", + "usr/lib/python2.7/posixpath.pyo", + "usr/lib/python2.7/mimetools.py", + "usr/lib/python2.7/glob.py", + "usr/lib/python2.7/pydoc.py", + "usr/lib/python2.7/mimetypes.pyo", + "usr/lib/python2.7/BaseHTTPServer.py", + "usr/lib/python2.7/UserDict.pyo", + "usr/lib/python2.7/xmllib.pyo", + "usr/lib/python2.7/wsgiref.egg-info", + "usr/lib/python2.7/formatter.pyo", + "usr/lib/python2.7/fpformat.py", + "usr/lib/python2.7/fnmatch.pyc", + "usr/lib/python2.7/sre.pyo", + "usr/lib/python2.7/urllib2.pyo", + "usr/lib/python2.7/repr.py", + "usr/lib/python2.7/imputil.py", + "usr/lib/python2.7/cookielib.pyc", + "usr/lib/python2.7/mimify.pyo", + "usr/lib/python2.7/sunau.py", + "usr/lib/python2.7/fileinput.pyo", + "usr/lib/python2.7/re.py", + "usr/lib/python2.7/sysconfig.py", + "usr/lib/python2.7/zipfile.py", + "usr/lib/python2.7/types.pyc", + "usr/lib/python2.7/tokenize.pyc", + "usr/lib/python2.7/hashlib.pyc", + "usr/lib/python2.7/stringprep.py", + "usr/lib/python2.7/htmlentitydefs.pyc", + "usr/lib/python2.7/sgmllib.pyo", + "usr/lib/python2.7/htmlentitydefs.pyo", + "usr/lib/python2.7/ast.py", + "usr/lib/python2.7/code.pyc", + "usr/lib/python2.7/smtplib.py", + "usr/lib/python2.7/stringprep.pyc", + "usr/lib/python2.7/pipes.pyo", + "usr/lib/python2.7/sndhdr.py", + "usr/lib/python2.7/BaseHTTPServer.pyc", + "usr/lib/python2.7/gettext.pyc", + "usr/lib/python2.7/urllib2.pyc", + "usr/lib/python2.7/collections.pyc", + "usr/lib/python2.7/inspect.py", + "usr/lib/python2.7/_abcoll.pyo", + "usr/lib/python2.7/StringIO.pyo", + "usr/lib/python2.7/ConfigParser.pyc", + "usr/lib/python2.7/py_compile.pyc", + "usr/lib/python2.7/struct.pyo", + "usr/lib/python2.7/bisect.pyo", + "usr/lib/python2.7/macurl2path.py", + "usr/lib/python2.7/httplib.py", + "usr/lib/python2.7/repr.pyc", + "usr/lib/python2.7/dummy_thread.py", + "usr/lib/python2.7/gettext.py", + "usr/lib/python2.7/sre.pyc", + "usr/lib/python2.7/telnetlib.py", + "usr/lib/python2.7/UserString.py", + "usr/lib/python2.7/httplib.pyo", + "usr/lib/python2.7/shelve.py", + "usr/lib/python2.7/antigravity.pyo", + "usr/lib/python2.7/opcode.pyo", + "usr/lib/python2.7/doctest.py", + "usr/lib/python2.7/dummy_thread.pyo", + "usr/lib/python2.7/ntpath.py", + "usr/lib/python2.7/urlparse.py", + "usr/lib/python2.7/dumbdbm.pyc", + "usr/lib/python2.7/_weakrefset.pyc", + "usr/lib/python2.7/sunau.pyc", + "usr/lib/python2.7/new.pyc", + "usr/lib/python2.7/_osx_support.py", + "usr/lib/python2.7/SimpleXMLRPCServer.py", + "usr/lib/python2.7/pickletools.pyo", + "usr/lib/python2.7/formatter.py", + "usr/lib/python2.7/__future__.pyc", + "usr/lib/python2.7/collections.py", + "usr/lib/python2.7/modulefinder.pyc", + "usr/lib/python2.7/user.pyo", + "usr/lib/python2.7/symtable.pyc", + "usr/lib/python2.7/UserString.pyc", + "usr/lib/python2.7/mimetools.pyo", + "usr/lib/python2.7/httplib.pyc", + "usr/lib/python2.7/textwrap.py", + "usr/lib/python2.7/SimpleXMLRPCServer.pyo", + "usr/lib/python2.7/statvfs.pyc", + "usr/lib/python2.7/sets.pyc", + "usr/lib/python2.7/string.py", + "usr/lib/python2.7/audiodev.py", + "usr/lib/python2.7/distutils/archive_util.pyc", + "usr/lib/python2.7/distutils/cygwinccompiler.py", + "usr/lib/python2.7/distutils/file_util.py", + "usr/lib/python2.7/distutils/dep_util.py", + "usr/lib/python2.7/distutils/__init__.pyc", + "usr/lib/python2.7/distutils/bcppcompiler.pyo", + "usr/lib/python2.7/distutils/versionpredicate.pyo", + "usr/lib/python2.7/distutils/archive_util.pyo", + "usr/lib/python2.7/distutils/dep_util.pyo", + "usr/lib/python2.7/distutils/core.py", + "usr/lib/python2.7/distutils/msvccompiler.pyo", + "usr/lib/python2.7/distutils/unixccompiler.py", + "usr/lib/python2.7/distutils/sysconfig.pyo", + "usr/lib/python2.7/distutils/cygwinccompiler.pyc", + "usr/lib/python2.7/distutils/text_file.pyo", + "usr/lib/python2.7/distutils/fancy_getopt.py", + "usr/lib/python2.7/distutils/cmd.pyc", + "usr/lib/python2.7/distutils/util.pyo", + "usr/lib/python2.7/distutils/msvccompiler.py", + "usr/lib/python2.7/distutils/msvccompiler.pyc", + "usr/lib/python2.7/distutils/emxccompiler.pyc", + "usr/lib/python2.7/distutils/versionpredicate.pyc", + "usr/lib/python2.7/distutils/debug.py", + "usr/lib/python2.7/distutils/log.py", + "usr/lib/python2.7/distutils/version.py", + "usr/lib/python2.7/distutils/__init__.py", + "usr/lib/python2.7/distutils/cmd.pyo", + "usr/lib/python2.7/distutils/util.py", + "usr/lib/python2.7/distutils/fancy_getopt.pyc", + "usr/lib/python2.7/distutils/cygwinccompiler.pyo", + "usr/lib/python2.7/distutils/extension.py", + "usr/lib/python2.7/distutils/ccompiler.pyc", + "usr/lib/python2.7/distutils/filelist.py", + "usr/lib/python2.7/distutils/core.pyc", + "usr/lib/python2.7/distutils/spawn.py", + "usr/lib/python2.7/distutils/dist.pyc", + "usr/lib/python2.7/distutils/filelist.pyc", + "usr/lib/python2.7/distutils/versionpredicate.py", + "usr/lib/python2.7/distutils/config.py", + "usr/lib/python2.7/distutils/debug.pyo", + "usr/lib/python2.7/distutils/version.pyc", + "usr/lib/python2.7/distutils/spawn.pyc", + "usr/lib/python2.7/distutils/errors.pyc", + "usr/lib/python2.7/distutils/log.pyc", + "usr/lib/python2.7/distutils/msvc9compiler.py", + "usr/lib/python2.7/distutils/errors.pyo", + "usr/lib/python2.7/distutils/dist.pyo", + "usr/lib/python2.7/distutils/extension.pyc", + "usr/lib/python2.7/distutils/dist.py", + "usr/lib/python2.7/distutils/msvc9compiler.pyc", + "usr/lib/python2.7/distutils/unixccompiler.pyc", + "usr/lib/python2.7/distutils/dep_util.pyc", + "usr/lib/python2.7/distutils/util.pyc", + "usr/lib/python2.7/distutils/ccompiler.pyo", + "usr/lib/python2.7/distutils/emxccompiler.py", + "usr/lib/python2.7/distutils/text_file.pyc", + "usr/lib/python2.7/distutils/config.pyo", + "usr/lib/python2.7/distutils/version.pyo", + "usr/lib/python2.7/distutils/debug.pyc", + "usr/lib/python2.7/distutils/core.pyo", + "usr/lib/python2.7/distutils/dir_util.py", + "usr/lib/python2.7/distutils/cmd.py", + "usr/lib/python2.7/distutils/text_file.py", + "usr/lib/python2.7/distutils/spawn.pyo", + "usr/lib/python2.7/distutils/file_util.pyc", + "usr/lib/python2.7/distutils/README", + "usr/lib/python2.7/distutils/archive_util.py", + "usr/lib/python2.7/distutils/ccompiler.py", + "usr/lib/python2.7/distutils/extension.pyo", + "usr/lib/python2.7/distutils/sysconfig.pyc", + "usr/lib/python2.7/distutils/errors.py", + "usr/lib/python2.7/distutils/dir_util.pyo", + "usr/lib/python2.7/distutils/__init__.pyo", + "usr/lib/python2.7/distutils/msvc9compiler.pyo", + "usr/lib/python2.7/distutils/bcppcompiler.py", + "usr/lib/python2.7/distutils/filelist.pyo", + "usr/lib/python2.7/distutils/fancy_getopt.pyo", + "usr/lib/python2.7/distutils/sysconfig.py", + "usr/lib/python2.7/distutils/dir_util.pyc", + "usr/lib/python2.7/distutils/emxccompiler.pyo", + "usr/lib/python2.7/distutils/bcppcompiler.pyc", + "usr/lib/python2.7/distutils/unixccompiler.pyo", + "usr/lib/python2.7/distutils/file_util.pyo", + "usr/lib/python2.7/distutils/log.pyo", + "usr/lib/python2.7/distutils/config.pyc", + "usr/lib/python2.7/distutils/command/wininst-6.0.exe", + "usr/lib/python2.7/distutils/command/wininst-9.0.exe", + "usr/lib/python2.7/distutils/command/__init__.pyc", + "usr/lib/python2.7/distutils/command/command_template", + "usr/lib/python2.7/distutils/command/install_egg_info.py", + "usr/lib/python2.7/distutils/command/install.py", + "usr/lib/python2.7/distutils/command/bdist_rpm.py", + "usr/lib/python2.7/distutils/command/install.pyo", + "usr/lib/python2.7/distutils/command/install_data.pyc", + "usr/lib/python2.7/distutils/command/clean.pyc", + "usr/lib/python2.7/distutils/command/build_clib.pyo", + "usr/lib/python2.7/distutils/command/check.pyc", + "usr/lib/python2.7/distutils/command/upload.pyo", + "usr/lib/python2.7/distutils/command/bdist_msi.py", + "usr/lib/python2.7/distutils/command/build_clib.py", + "usr/lib/python2.7/distutils/command/bdist_dumb.pyc", + "usr/lib/python2.7/distutils/command/build_scripts.py", + "usr/lib/python2.7/distutils/command/upload.py", + "usr/lib/python2.7/distutils/command/build_py.py", + "usr/lib/python2.7/distutils/command/register.pyc", + "usr/lib/python2.7/distutils/command/build_ext.pyo", + "usr/lib/python2.7/distutils/command/build_ext.py", + "usr/lib/python2.7/distutils/command/install_headers.py", + "usr/lib/python2.7/distutils/command/__init__.py", + "usr/lib/python2.7/distutils/command/install.pyc", + "usr/lib/python2.7/distutils/command/bdist.pyc", + "usr/lib/python2.7/distutils/command/check.pyo", + "usr/lib/python2.7/distutils/command/bdist_wininst.pyc", + "usr/lib/python2.7/distutils/command/bdist_rpm.pyo", + "usr/lib/python2.7/distutils/command/bdist_dumb.pyo", + "usr/lib/python2.7/distutils/command/install_lib.pyo", + "usr/lib/python2.7/distutils/command/install_lib.pyc", + "usr/lib/python2.7/distutils/command/install_scripts.py", + "usr/lib/python2.7/distutils/command/upload.pyc", + "usr/lib/python2.7/distutils/command/check.py", + "usr/lib/python2.7/distutils/command/register.py", + "usr/lib/python2.7/distutils/command/config.py", + "usr/lib/python2.7/distutils/command/install_headers.pyo", + "usr/lib/python2.7/distutils/command/bdist_wininst.py", + "usr/lib/python2.7/distutils/command/build_scripts.pyc", + "usr/lib/python2.7/distutils/command/register.pyo", + "usr/lib/python2.7/distutils/command/build_py.pyo", + "usr/lib/python2.7/distutils/command/bdist_msi.pyc", + "usr/lib/python2.7/distutils/command/sdist.pyo", + "usr/lib/python2.7/distutils/command/install_data.pyo", + "usr/lib/python2.7/distutils/command/install_scripts.pyc", + "usr/lib/python2.7/distutils/command/build_scripts.pyo", + "usr/lib/python2.7/distutils/command/install_lib.py", + "usr/lib/python2.7/distutils/command/sdist.py", + "usr/lib/python2.7/distutils/command/install_egg_info.pyo", + "usr/lib/python2.7/distutils/command/config.pyo", + "usr/lib/python2.7/distutils/command/bdist_rpm.pyc", + "usr/lib/python2.7/distutils/command/build.py", + "usr/lib/python2.7/distutils/command/install_headers.pyc", + "usr/lib/python2.7/distutils/command/wininst-8.0.exe", + "usr/lib/python2.7/distutils/command/bdist_msi.pyo", + "usr/lib/python2.7/distutils/command/wininst-7.1.exe", + "usr/lib/python2.7/distutils/command/bdist.py", + "usr/lib/python2.7/distutils/command/bdist.pyo", + "usr/lib/python2.7/distutils/command/__init__.pyo", + "usr/lib/python2.7/distutils/command/bdist_dumb.py", + "usr/lib/python2.7/distutils/command/install_data.py", + "usr/lib/python2.7/distutils/command/clean.py", + "usr/lib/python2.7/distutils/command/build_py.pyc", + "usr/lib/python2.7/distutils/command/install_scripts.pyo", + "usr/lib/python2.7/distutils/command/wininst-9.0-amd64.exe", + "usr/lib/python2.7/distutils/command/clean.pyo", + "usr/lib/python2.7/distutils/command/install_egg_info.pyc", + "usr/lib/python2.7/distutils/command/build.pyc", + "usr/lib/python2.7/distutils/command/sdist.pyc", + "usr/lib/python2.7/distutils/command/config.pyc", + "usr/lib/python2.7/distutils/command/build_ext.pyc", + "usr/lib/python2.7/distutils/command/bdist_wininst.pyo", + "usr/lib/python2.7/distutils/command/build_clib.pyc", + "usr/lib/python2.7/distutils/command/build.pyo", + "usr/lib/python2.7/distutils/tests/test_text_file.pyc", + "usr/lib/python2.7/distutils/tests/test_bdist_wininst.pyo", + "usr/lib/python2.7/distutils/tests/__init__.pyc", + "usr/lib/python2.7/distutils/tests/test_register.py", + "usr/lib/python2.7/distutils/tests/test_sysconfig.pyo", + "usr/lib/python2.7/distutils/tests/test_dep_util.pyo", + "usr/lib/python2.7/distutils/tests/test_build_clib.pyc", + "usr/lib/python2.7/distutils/tests/test_bdist_msi.pyo", + "usr/lib/python2.7/distutils/tests/test_msvc9compiler.pyc", + "usr/lib/python2.7/distutils/tests/test_util.pyo", + "usr/lib/python2.7/distutils/tests/test_unixccompiler.pyc", + "usr/lib/python2.7/distutils/tests/test_register.pyo", + "usr/lib/python2.7/distutils/tests/test_build_scripts.py", + "usr/lib/python2.7/distutils/tests/test_bdist.pyo", + "usr/lib/python2.7/distutils/tests/test_bdist_dumb.py", + "usr/lib/python2.7/distutils/tests/test_spawn.pyo", + "usr/lib/python2.7/distutils/tests/test_install_lib.pyo", + "usr/lib/python2.7/distutils/tests/test_build_py.pyc", + "usr/lib/python2.7/distutils/tests/test_bdist_msi.pyc", + "usr/lib/python2.7/distutils/tests/test_msvc9compiler.py", + "usr/lib/python2.7/distutils/tests/test_file_util.pyo", + "usr/lib/python2.7/distutils/tests/test_dir_util.pyo", + "usr/lib/python2.7/distutils/tests/test_bdist_dumb.pyo", + "usr/lib/python2.7/distutils/tests/test_build_clib.py", + "usr/lib/python2.7/distutils/tests/test_bdist_rpm.pyc", + "usr/lib/python2.7/distutils/tests/test_spawn.py", + "usr/lib/python2.7/distutils/tests/test_clean.pyo", + "usr/lib/python2.7/distutils/tests/setuptools_extension.pyo", + "usr/lib/python2.7/distutils/tests/Setup.sample", + "usr/lib/python2.7/distutils/tests/test_build.pyc", + "usr/lib/python2.7/distutils/tests/test_register.pyc", + "usr/lib/python2.7/distutils/tests/test_bdist.pyc", + "usr/lib/python2.7/distutils/tests/test_build_py.pyo", + "usr/lib/python2.7/distutils/tests/test_msvc9compiler.pyo", + "usr/lib/python2.7/distutils/tests/test_ccompiler.pyc", + "usr/lib/python2.7/distutils/tests/test_versionpredicate.pyo", + "usr/lib/python2.7/distutils/tests/setuptools_build_ext.pyo", + "usr/lib/python2.7/distutils/tests/test_dir_util.pyc", + "usr/lib/python2.7/distutils/tests/test_filelist.pyo", + "usr/lib/python2.7/distutils/tests/test_filelist.py", + "usr/lib/python2.7/distutils/tests/__init__.py", + "usr/lib/python2.7/distutils/tests/test_util.pyc", + "usr/lib/python2.7/distutils/tests/test_build_clib.pyo", + "usr/lib/python2.7/distutils/tests/setuptools_build_ext.pyc", + "usr/lib/python2.7/distutils/tests/test_install_scripts.pyo", + "usr/lib/python2.7/distutils/tests/support.py", + "usr/lib/python2.7/distutils/tests/support.pyc", + "usr/lib/python2.7/distutils/tests/test_text_file.pyo", + "usr/lib/python2.7/distutils/tests/test_install.py", + "usr/lib/python2.7/distutils/tests/test_clean.py", + "usr/lib/python2.7/distutils/tests/test_bdist_rpm.py", + "usr/lib/python2.7/distutils/tests/test_core.pyc", + "usr/lib/python2.7/distutils/tests/test_bdist_wininst.pyc", + "usr/lib/python2.7/distutils/tests/test_dist.py", + "usr/lib/python2.7/distutils/tests/test_dir_util.py", + "usr/lib/python2.7/distutils/tests/test_version.py", + "usr/lib/python2.7/distutils/tests/test_sdist.pyc", + "usr/lib/python2.7/distutils/tests/test_ccompiler.pyo", + "usr/lib/python2.7/distutils/tests/test_build.pyo", + "usr/lib/python2.7/distutils/tests/test_bdist_wininst.py", + "usr/lib/python2.7/distutils/tests/test_build_py.py", + "usr/lib/python2.7/distutils/tests/test_check.pyc", + "usr/lib/python2.7/distutils/tests/test_install_headers.pyc", + "usr/lib/python2.7/distutils/tests/test_core.pyo", + "usr/lib/python2.7/distutils/tests/test_dep_util.pyc", + "usr/lib/python2.7/distutils/tests/test_unixccompiler.pyo", + "usr/lib/python2.7/distutils/tests/test_bdist_rpm.pyo", + "usr/lib/python2.7/distutils/tests/test_sysconfig.py", + "usr/lib/python2.7/distutils/tests/test_text_file.py", + "usr/lib/python2.7/distutils/tests/test_bdist_dumb.pyc", + "usr/lib/python2.7/distutils/tests/test_check.pyo", + "usr/lib/python2.7/distutils/tests/test_build.py", + "usr/lib/python2.7/distutils/tests/test_install.pyc", + "usr/lib/python2.7/distutils/tests/test_archive_util.pyc", + "usr/lib/python2.7/distutils/tests/support.pyo", + "usr/lib/python2.7/distutils/tests/test_upload.py", + "usr/lib/python2.7/distutils/tests/test_versionpredicate.pyc", + "usr/lib/python2.7/distutils/tests/test_util.py", + "usr/lib/python2.7/distutils/tests/test_bdist_msi.py", + "usr/lib/python2.7/distutils/tests/test_install_scripts.pyc", + "usr/lib/python2.7/distutils/tests/test_config.pyc", + "usr/lib/python2.7/distutils/tests/test_config.py", + "usr/lib/python2.7/distutils/tests/test_archive_util.py", + "usr/lib/python2.7/distutils/tests/test_sdist.py", + "usr/lib/python2.7/distutils/tests/test_bdist.py", + "usr/lib/python2.7/distutils/tests/test_spawn.pyc", + "usr/lib/python2.7/distutils/tests/setuptools_build_ext.py", + "usr/lib/python2.7/distutils/tests/test_file_util.py", + "usr/lib/python2.7/distutils/tests/test_install_lib.pyc", + "usr/lib/python2.7/distutils/tests/test_dep_util.py", + "usr/lib/python2.7/distutils/tests/test_sdist.pyo", + "usr/lib/python2.7/distutils/tests/test_config_cmd.pyc", + "usr/lib/python2.7/distutils/tests/test_install_data.py", + "usr/lib/python2.7/distutils/tests/test_dist.pyo", + "usr/lib/python2.7/distutils/tests/test_unixccompiler.py", + "usr/lib/python2.7/distutils/tests/test_versionpredicate.py", + "usr/lib/python2.7/distutils/tests/test_cmd.py", + "usr/lib/python2.7/distutils/tests/test_build_ext.pyo", + "usr/lib/python2.7/distutils/tests/test_ccompiler.py", + "usr/lib/python2.7/distutils/tests/test_upload.pyo", + "usr/lib/python2.7/distutils/tests/test_upload.pyc", + "usr/lib/python2.7/distutils/tests/test_build_ext.pyc", + "usr/lib/python2.7/distutils/tests/test_install_headers.py", + "usr/lib/python2.7/distutils/tests/test_file_util.pyc", + "usr/lib/python2.7/distutils/tests/test_check.py", + "usr/lib/python2.7/distutils/tests/test_config_cmd.py", + "usr/lib/python2.7/distutils/tests/setuptools_extension.pyc", + "usr/lib/python2.7/distutils/tests/test_build_scripts.pyo", + "usr/lib/python2.7/distutils/tests/test_config_cmd.pyo", + "usr/lib/python2.7/distutils/tests/test_install_data.pyo", + "usr/lib/python2.7/distutils/tests/__init__.pyo", + "usr/lib/python2.7/distutils/tests/test_cmd.pyo", + "usr/lib/python2.7/distutils/tests/test_core.py", + "usr/lib/python2.7/distutils/tests/test_install_scripts.py", + "usr/lib/python2.7/distutils/tests/test_version.pyo", + "usr/lib/python2.7/distutils/tests/test_install_data.pyc", + "usr/lib/python2.7/distutils/tests/test_build_scripts.pyc", + "usr/lib/python2.7/distutils/tests/test_dist.pyc", + "usr/lib/python2.7/distutils/tests/test_install_headers.pyo", + "usr/lib/python2.7/distutils/tests/test_version.pyc", + "usr/lib/python2.7/distutils/tests/test_config.pyo", + "usr/lib/python2.7/distutils/tests/test_install_lib.py", + "usr/lib/python2.7/distutils/tests/test_sysconfig.pyc", + "usr/lib/python2.7/distutils/tests/test_cmd.pyc", + "usr/lib/python2.7/distutils/tests/test_filelist.pyc", + "usr/lib/python2.7/distutils/tests/setuptools_extension.py", + "usr/lib/python2.7/distutils/tests/test_clean.pyc", + "usr/lib/python2.7/distutils/tests/test_archive_util.pyo", + "usr/lib/python2.7/distutils/tests/test_build_ext.py", + "usr/lib/python2.7/distutils/tests/test_install.pyo", + "usr/lib/python2.7/curses/__init__.pyc", + "usr/lib/python2.7/curses/has_key.pyc", + "usr/lib/python2.7/curses/textpad.pyc", + "usr/lib/python2.7/curses/ascii.pyc", + "usr/lib/python2.7/curses/has_key.pyo", + "usr/lib/python2.7/curses/__init__.py", + "usr/lib/python2.7/curses/wrapper.py", + "usr/lib/python2.7/curses/textpad.py", + "usr/lib/python2.7/curses/wrapper.pyo", + "usr/lib/python2.7/curses/panel.pyc", + "usr/lib/python2.7/curses/ascii.py", + "usr/lib/python2.7/curses/__init__.pyo", + "usr/lib/python2.7/curses/has_key.py", + "usr/lib/python2.7/curses/wrapper.pyc", + "usr/lib/python2.7/curses/ascii.pyo", + "usr/lib/python2.7/curses/panel.py", + "usr/lib/python2.7/curses/textpad.pyo", + "usr/lib/python2.7/curses/panel.pyo", + "usr/lib/python2.7/wsgiref/__init__.pyc", + "usr/lib/python2.7/wsgiref/handlers.py", + "usr/lib/python2.7/wsgiref/util.pyo", + "usr/lib/python2.7/wsgiref/__init__.py", + "usr/lib/python2.7/wsgiref/validate.pyc", + "usr/lib/python2.7/wsgiref/handlers.pyo", + "usr/lib/python2.7/wsgiref/util.py", + "usr/lib/python2.7/wsgiref/validate.py", + "usr/lib/python2.7/wsgiref/handlers.pyc", + "usr/lib/python2.7/wsgiref/headers.pyc", + "usr/lib/python2.7/wsgiref/util.pyc", + "usr/lib/python2.7/wsgiref/simple_server.py", + "usr/lib/python2.7/wsgiref/headers.pyo", + "usr/lib/python2.7/wsgiref/simple_server.pyo", + "usr/lib/python2.7/wsgiref/__init__.pyo", + "usr/lib/python2.7/wsgiref/headers.py", + "usr/lib/python2.7/wsgiref/simple_server.pyc", + "usr/lib/python2.7/wsgiref/validate.pyo", + "usr/lib/python2.7/email/charset.py", + "usr/lib/python2.7/email/__init__.pyc", + "usr/lib/python2.7/email/parser.pyo", + "usr/lib/python2.7/email/utils.pyo", + "usr/lib/python2.7/email/iterators.pyc", + "usr/lib/python2.7/email/utils.py", + "usr/lib/python2.7/email/base64mime.pyc", + "usr/lib/python2.7/email/_parseaddr.pyc", + "usr/lib/python2.7/email/quoprimime.pyc", + "usr/lib/python2.7/email/message.pyo", + "usr/lib/python2.7/email/__init__.py", + "usr/lib/python2.7/email/header.py", + "usr/lib/python2.7/email/header.pyc", + "usr/lib/python2.7/email/feedparser.py", + "usr/lib/python2.7/email/message.py", + "usr/lib/python2.7/email/quoprimime.py", + "usr/lib/python2.7/email/errors.pyc", + "usr/lib/python2.7/email/errors.pyo", + "usr/lib/python2.7/email/charset.pyo", + "usr/lib/python2.7/email/quoprimime.pyo", + "usr/lib/python2.7/email/iterators.py", + "usr/lib/python2.7/email/message.pyc", + "usr/lib/python2.7/email/feedparser.pyc", + "usr/lib/python2.7/email/base64mime.py", + "usr/lib/python2.7/email/charset.pyc", + "usr/lib/python2.7/email/generator.py", + "usr/lib/python2.7/email/encoders.py", + "usr/lib/python2.7/email/header.pyo", + "usr/lib/python2.7/email/feedparser.pyo", + "usr/lib/python2.7/email/generator.pyc", + "usr/lib/python2.7/email/parser.py", + "usr/lib/python2.7/email/errors.py", + "usr/lib/python2.7/email/generator.pyo", + "usr/lib/python2.7/email/__init__.pyo", + "usr/lib/python2.7/email/_parseaddr.py", + "usr/lib/python2.7/email/encoders.pyc", + "usr/lib/python2.7/email/base64mime.pyo", + "usr/lib/python2.7/email/encoders.pyo", + "usr/lib/python2.7/email/utils.pyc", + "usr/lib/python2.7/email/parser.pyc", + "usr/lib/python2.7/email/_parseaddr.pyo", + "usr/lib/python2.7/email/iterators.pyo", + "usr/lib/python2.7/email/mime/__init__.pyc", + "usr/lib/python2.7/email/mime/audio.pyc", + "usr/lib/python2.7/email/mime/text.pyo", + "usr/lib/python2.7/email/mime/base.pyc", + "usr/lib/python2.7/email/mime/multipart.py", + "usr/lib/python2.7/email/mime/text.py", + "usr/lib/python2.7/email/mime/application.py", + "usr/lib/python2.7/email/mime/base.py", + "usr/lib/python2.7/email/mime/multipart.pyo", + "usr/lib/python2.7/email/mime/image.py", + "usr/lib/python2.7/email/mime/audio.pyo", + "usr/lib/python2.7/email/mime/message.pyo", + "usr/lib/python2.7/email/mime/text.pyc", + "usr/lib/python2.7/email/mime/nonmultipart.pyc", + "usr/lib/python2.7/email/mime/__init__.py", + "usr/lib/python2.7/email/mime/base.pyo", + "usr/lib/python2.7/email/mime/message.py", + "usr/lib/python2.7/email/mime/application.pyc", + "usr/lib/python2.7/email/mime/nonmultipart.pyo", + "usr/lib/python2.7/email/mime/nonmultipart.py", + "usr/lib/python2.7/email/mime/message.pyc", + "usr/lib/python2.7/email/mime/image.pyc", + "usr/lib/python2.7/email/mime/image.pyo", + "usr/lib/python2.7/email/mime/multipart.pyc", + "usr/lib/python2.7/email/mime/application.pyo", + "usr/lib/python2.7/email/mime/__init__.pyo", + "usr/lib/python2.7/email/mime/audio.py", + "usr/lib/python2.7/plat-linux2/IN.py", + "usr/lib/python2.7/plat-linux2/DLFCN.py", + "usr/lib/python2.7/plat-linux2/IN.pyo", + "usr/lib/python2.7/plat-linux2/DLFCN.pyc", + "usr/lib/python2.7/plat-linux2/DLFCN.pyo", + "usr/lib/python2.7/plat-linux2/TYPES.py", + "usr/lib/python2.7/plat-linux2/TYPES.pyo", + "usr/lib/python2.7/plat-linux2/TYPES.pyc", + "usr/lib/python2.7/plat-linux2/regen", + "usr/lib/python2.7/plat-linux2/IN.pyc", + "usr/lib/python2.7/plat-linux2/CDROM.py", + "usr/lib/python2.7/plat-linux2/CDROM.pyc", + "usr/lib/python2.7/plat-linux2/CDROM.pyo", + "usr/lib/python2.7/lib2to3/btm_matcher.pyc", + "usr/lib/python2.7/lib2to3/pytree.pyc", + "usr/lib/python2.7/lib2to3/__init__.pyc", + "usr/lib/python2.7/lib2to3/__main__.pyc", + "usr/lib/python2.7/lib2to3/patcomp.pyo", + "usr/lib/python2.7/lib2to3/fixer_base.pyc", + "usr/lib/python2.7/lib2to3/btm_utils.py", + "usr/lib/python2.7/lib2to3/fixer_util.py", + "usr/lib/python2.7/lib2to3/patcomp.pyc", + "usr/lib/python2.7/lib2to3/fixer_base.py", + "usr/lib/python2.7/lib2to3/pygram.pyo", + "usr/lib/python2.7/lib2to3/__init__.py", + "usr/lib/python2.7/lib2to3/fixer_util.pyo", + "usr/lib/python2.7/lib2to3/main.pyc", + "usr/lib/python2.7/lib2to3/PatternGrammar.txt", + "usr/lib/python2.7/lib2to3/__main__.pyo", + "usr/lib/python2.7/lib2to3/pygram.py", + "usr/lib/python2.7/lib2to3/btm_matcher.py", + "usr/lib/python2.7/lib2to3/btm_utils.pyo", + "usr/lib/python2.7/lib2to3/pytree.pyo", + "usr/lib/python2.7/lib2to3/Grammar2.7.15.final.0.pickle", + "usr/lib/python2.7/lib2to3/refactor.pyo", + "usr/lib/python2.7/lib2to3/main.pyo", + "usr/lib/python2.7/lib2to3/fixer_util.pyc", + "usr/lib/python2.7/lib2to3/Grammar.txt", + "usr/lib/python2.7/lib2to3/refactor.py", + "usr/lib/python2.7/lib2to3/main.py", + "usr/lib/python2.7/lib2to3/__init__.pyo", + "usr/lib/python2.7/lib2to3/pytree.py", + "usr/lib/python2.7/lib2to3/btm_utils.pyc", + "usr/lib/python2.7/lib2to3/btm_matcher.pyo", + "usr/lib/python2.7/lib2to3/__main__.py", + "usr/lib/python2.7/lib2to3/patcomp.py", + "usr/lib/python2.7/lib2to3/fixer_base.pyo", + "usr/lib/python2.7/lib2to3/PatternGrammar2.7.15.final.0.pickle", + "usr/lib/python2.7/lib2to3/pygram.pyc", + "usr/lib/python2.7/lib2to3/refactor.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_unicode.py", + "usr/lib/python2.7/lib2to3/fixes/fix_imports2.py", + "usr/lib/python2.7/lib2to3/fixes/fix_repr.py", + "usr/lib/python2.7/lib2to3/fixes/fix_imports.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_urllib.pyo", + "usr/lib/python2.7/lib2to3/fixes/__init__.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_funcattrs.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_reduce.py", + "usr/lib/python2.7/lib2to3/fixes/fix_ne.py", + "usr/lib/python2.7/lib2to3/fixes/fix_intern.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_execfile.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_set_literal.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_dict.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_repr.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_numliterals.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_standarderror.py", + "usr/lib/python2.7/lib2to3/fixes/fix_map.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_xrange.py", + "usr/lib/python2.7/lib2to3/fixes/fix_throw.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_idioms.py", + "usr/lib/python2.7/lib2to3/fixes/fix_raise.py", + "usr/lib/python2.7/lib2to3/fixes/fix_types.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_input.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_paren.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_types.py", + "usr/lib/python2.7/lib2to3/fixes/fix_ne.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_urllib.py", + "usr/lib/python2.7/lib2to3/fixes/fix_print.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_asserts.py", + "usr/lib/python2.7/lib2to3/fixes/fix_itertools.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_raw_input.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_input.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_isinstance.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_print.py", + "usr/lib/python2.7/lib2to3/fixes/fix_imports.py", + "usr/lib/python2.7/lib2to3/fixes/fix_exitfunc.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_exec.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_filter.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_itertools_imports.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_long.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_raise.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_raise.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_dict.py", + "usr/lib/python2.7/lib2to3/fixes/fix_getcwdu.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_reduce.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_except.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_next.py", + "usr/lib/python2.7/lib2to3/fixes/fix_has_key.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_buffer.pyo", + "usr/lib/python2.7/lib2to3/fixes/__init__.py", + "usr/lib/python2.7/lib2to3/fixes/fix_imports.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_funcattrs.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_next.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_basestring.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_urllib.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_zip.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_funcattrs.py", + "usr/lib/python2.7/lib2to3/fixes/fix_metaclass.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_getcwdu.py", + "usr/lib/python2.7/lib2to3/fixes/fix_itertools_imports.py", + "usr/lib/python2.7/lib2to3/fixes/fix_sys_exc.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_itertools.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_asserts.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_apply.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_dict.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_imports2.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_import.py", + "usr/lib/python2.7/lib2to3/fixes/fix_raw_input.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_types.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_set_literal.py", + "usr/lib/python2.7/lib2to3/fixes/fix_isinstance.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_buffer.py", + "usr/lib/python2.7/lib2to3/fixes/fix_set_literal.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_standarderror.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_tuple_params.py", + "usr/lib/python2.7/lib2to3/fixes/fix_xreadlines.py", + "usr/lib/python2.7/lib2to3/fixes/fix_filter.py", + "usr/lib/python2.7/lib2to3/fixes/fix_exitfunc.py", + "usr/lib/python2.7/lib2to3/fixes/fix_paren.py", + "usr/lib/python2.7/lib2to3/fixes/fix_execfile.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_apply.py", + "usr/lib/python2.7/lib2to3/fixes/fix_methodattrs.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_nonzero.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_next.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_import.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_getcwdu.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_unicode.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_execfile.py", + "usr/lib/python2.7/lib2to3/fixes/fix_long.py", + "usr/lib/python2.7/lib2to3/fixes/fix_renames.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_xreadlines.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_methodattrs.py", + "usr/lib/python2.7/lib2to3/fixes/fix_idioms.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_ws_comma.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_imports2.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_paren.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_has_key.py", + "usr/lib/python2.7/lib2to3/fixes/fix_unicode.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_metaclass.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_sys_exc.py", + "usr/lib/python2.7/lib2to3/fixes/fix_nonzero.py", + "usr/lib/python2.7/lib2to3/fixes/fix_exitfunc.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_operator.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_reduce.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_basestring.py", + "usr/lib/python2.7/lib2to3/fixes/fix_filter.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_intern.py", + "usr/lib/python2.7/lib2to3/fixes/fix_ne.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_numliterals.py", + "usr/lib/python2.7/lib2to3/fixes/fix_sys_exc.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_zip.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_xrange.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_intern.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_xreadlines.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_long.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_nonzero.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_operator.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_itertools_imports.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_standarderror.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_throw.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_repr.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_methodattrs.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_future.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_operator.py", + "usr/lib/python2.7/lib2to3/fixes/fix_basestring.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_isinstance.py", + "usr/lib/python2.7/lib2to3/fixes/fix_asserts.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_exec.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_future.pyc", + "usr/lib/python2.7/lib2to3/fixes/__init__.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_xrange.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_numliterals.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_renames.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_zip.py", + "usr/lib/python2.7/lib2to3/fixes/fix_except.py", + "usr/lib/python2.7/lib2to3/fixes/fix_raw_input.py", + "usr/lib/python2.7/lib2to3/fixes/fix_input.py", + "usr/lib/python2.7/lib2to3/fixes/fix_tuple_params.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_exec.py", + "usr/lib/python2.7/lib2to3/fixes/fix_future.py", + "usr/lib/python2.7/lib2to3/fixes/fix_has_key.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_map.py", + "usr/lib/python2.7/lib2to3/fixes/fix_print.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_apply.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_ws_comma.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_metaclass.py", + "usr/lib/python2.7/lib2to3/fixes/fix_ws_comma.py", + "usr/lib/python2.7/lib2to3/fixes/fix_map.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_throw.py", + "usr/lib/python2.7/lib2to3/fixes/fix_idioms.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_tuple_params.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_import.pyo", + "usr/lib/python2.7/lib2to3/fixes/fix_itertools.py", + "usr/lib/python2.7/lib2to3/fixes/fix_buffer.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_except.pyc", + "usr/lib/python2.7/lib2to3/fixes/fix_renames.py", + "usr/lib/python2.7/lib2to3/tests/pytree_idempotency.py", + "usr/lib/python2.7/lib2to3/tests/__init__.pyc", + "usr/lib/python2.7/lib2to3/tests/test_util.pyo", + "usr/lib/python2.7/lib2to3/tests/pytree_idempotency.pyc", + "usr/lib/python2.7/lib2to3/tests/test_all_fixers.pyo", + "usr/lib/python2.7/lib2to3/tests/test_all_fixers.pyc", + "usr/lib/python2.7/lib2to3/tests/test_fixers.pyo", + "usr/lib/python2.7/lib2to3/tests/__init__.py", + "usr/lib/python2.7/lib2to3/tests/test_util.pyc", + "usr/lib/python2.7/lib2to3/tests/support.py", + "usr/lib/python2.7/lib2to3/tests/test_pytree.pyo", + "usr/lib/python2.7/lib2to3/tests/support.pyc", + "usr/lib/python2.7/lib2to3/tests/test_main.pyo", + "usr/lib/python2.7/lib2to3/tests/pytree_idempotency.pyo", + "usr/lib/python2.7/lib2to3/tests/test_parser.py", + "usr/lib/python2.7/lib2to3/tests/test_fixers.pyc", + "usr/lib/python2.7/lib2to3/tests/test_fixers.py", + "usr/lib/python2.7/lib2to3/tests/test_main.py", + "usr/lib/python2.7/lib2to3/tests/support.pyo", + "usr/lib/python2.7/lib2to3/tests/test_util.py", + "usr/lib/python2.7/lib2to3/tests/test_parser.pyo", + "usr/lib/python2.7/lib2to3/tests/test_all_fixers.py", + "usr/lib/python2.7/lib2to3/tests/test_parser.pyc", + "usr/lib/python2.7/lib2to3/tests/test_pytree.pyc", + "usr/lib/python2.7/lib2to3/tests/__init__.pyo", + "usr/lib/python2.7/lib2to3/tests/test_refactor.py", + "usr/lib/python2.7/lib2to3/tests/test_refactor.pyc", + "usr/lib/python2.7/lib2to3/tests/test_main.pyc", + "usr/lib/python2.7/lib2to3/tests/test_pytree.py", + "usr/lib/python2.7/lib2to3/tests/test_refactor.pyo", + "usr/lib/python2.7/lib2to3/tests/data/infinite_recursion.py", + "usr/lib/python2.7/lib2to3/tests/data/bom.py", + "usr/lib/python2.7/lib2to3/tests/data/py2_test_grammar.py", + "usr/lib/python2.7/lib2to3/tests/data/crlf.py", + "usr/lib/python2.7/lib2to3/tests/data/different_encoding.py", + "usr/lib/python2.7/lib2to3/tests/data/py3_test_grammar.py", + "usr/lib/python2.7/lib2to3/tests/data/README", + "usr/lib/python2.7/lib2to3/tests/data/false_encoding.py", + "usr/lib/python2.7/lib2to3/tests/data/fixers/bad_order.py", + "usr/lib/python2.7/lib2to3/tests/data/fixers/no_fixer_cls.py", + "usr/lib/python2.7/lib2to3/tests/data/fixers/parrot_example.py", + "usr/lib/python2.7/lib2to3/tests/data/fixers/myfixes/fix_parrot.py", + "usr/lib/python2.7/lib2to3/tests/data/fixers/myfixes/fix_first.py", + "usr/lib/python2.7/lib2to3/tests/data/fixers/myfixes/__init__.py", + "usr/lib/python2.7/lib2to3/tests/data/fixers/myfixes/fix_last.py", + "usr/lib/python2.7/lib2to3/tests/data/fixers/myfixes/fix_explicit.py", + "usr/lib/python2.7/lib2to3/tests/data/fixers/myfixes/fix_preorder.py", + "usr/lib/python2.7/lib2to3/pgen2/__init__.pyc", + "usr/lib/python2.7/lib2to3/pgen2/tokenize.py", + "usr/lib/python2.7/lib2to3/pgen2/literals.pyo", + "usr/lib/python2.7/lib2to3/pgen2/driver.pyo", + "usr/lib/python2.7/lib2to3/pgen2/pgen.py", + "usr/lib/python2.7/lib2to3/pgen2/token.pyo", + "usr/lib/python2.7/lib2to3/pgen2/parse.pyc", + "usr/lib/python2.7/lib2to3/pgen2/tokenize.pyo", + "usr/lib/python2.7/lib2to3/pgen2/__init__.py", + "usr/lib/python2.7/lib2to3/pgen2/parse.py", + "usr/lib/python2.7/lib2to3/pgen2/pgen.pyo", + "usr/lib/python2.7/lib2to3/pgen2/conv.pyc", + "usr/lib/python2.7/lib2to3/pgen2/grammar.py", + "usr/lib/python2.7/lib2to3/pgen2/grammar.pyo", + "usr/lib/python2.7/lib2to3/pgen2/grammar.pyc", + "usr/lib/python2.7/lib2to3/pgen2/driver.pyc", + "usr/lib/python2.7/lib2to3/pgen2/literals.py", + "usr/lib/python2.7/lib2to3/pgen2/conv.py", + "usr/lib/python2.7/lib2to3/pgen2/conv.pyo", + "usr/lib/python2.7/lib2to3/pgen2/token.py", + "usr/lib/python2.7/lib2to3/pgen2/token.pyc", + "usr/lib/python2.7/lib2to3/pgen2/__init__.pyo", + "usr/lib/python2.7/lib2to3/pgen2/literals.pyc", + "usr/lib/python2.7/lib2to3/pgen2/pgen.pyc", + "usr/lib/python2.7/lib2to3/pgen2/tokenize.pyc", + "usr/lib/python2.7/lib2to3/pgen2/driver.py", + "usr/lib/python2.7/lib2to3/pgen2/parse.pyo", + "usr/lib/python2.7/site-packages/README", + "usr/lib/python2.7/sqlite3/__init__.pyc", + "usr/lib/python2.7/sqlite3/dbapi2.pyo", + "usr/lib/python2.7/sqlite3/__init__.py", + "usr/lib/python2.7/sqlite3/dump.py", + "usr/lib/python2.7/sqlite3/dbapi2.pyc", + "usr/lib/python2.7/sqlite3/dbapi2.py", + "usr/lib/python2.7/sqlite3/dump.pyo", + "usr/lib/python2.7/sqlite3/dump.pyc", + "usr/lib/python2.7/sqlite3/__init__.pyo", + "usr/lib/python2.7/json/__init__.pyc", + "usr/lib/python2.7/json/scanner.pyo", + "usr/lib/python2.7/json/scanner.pyc", + "usr/lib/python2.7/json/encoder.py", + "usr/lib/python2.7/json/decoder.py", + "usr/lib/python2.7/json/__init__.py", + "usr/lib/python2.7/json/encoder.pyo", + "usr/lib/python2.7/json/encoder.pyc", + "usr/lib/python2.7/json/decoder.pyc", + "usr/lib/python2.7/json/scanner.py", + "usr/lib/python2.7/json/tool.pyc", + "usr/lib/python2.7/json/tool.py", + "usr/lib/python2.7/json/__init__.pyo", + "usr/lib/python2.7/json/tool.pyo", + "usr/lib/python2.7/json/decoder.pyo", + "usr/lib/python2.7/json/tests/test_dump.py", + "usr/lib/python2.7/json/tests/__init__.pyc", + "usr/lib/python2.7/json/tests/test_check_circular.pyc", + "usr/lib/python2.7/json/tests/test_float.pyo", + "usr/lib/python2.7/json/tests/test_encode_basestring_ascii.pyo", + "usr/lib/python2.7/json/tests/test_recursion.py", + "usr/lib/python2.7/json/tests/test_pass2.py", + "usr/lib/python2.7/json/tests/test_pass3.pyo", + "usr/lib/python2.7/json/tests/test_fail.pyo", + "usr/lib/python2.7/json/tests/test_decode.py", + "usr/lib/python2.7/json/tests/test_unicode.py", + "usr/lib/python2.7/json/tests/test_unicode.pyc", + "usr/lib/python2.7/json/tests/test_separators.pyc", + "usr/lib/python2.7/json/tests/test_indent.py", + "usr/lib/python2.7/json/tests/test_pass3.pyc", + "usr/lib/python2.7/json/tests/test_speedups.pyo", + "usr/lib/python2.7/json/tests/test_pass3.py", + "usr/lib/python2.7/json/tests/test_scanstring.pyo", + "usr/lib/python2.7/json/tests/__init__.py", + "usr/lib/python2.7/json/tests/test_encode_basestring_ascii.pyc", + "usr/lib/python2.7/json/tests/test_scanstring.pyc", + "usr/lib/python2.7/json/tests/test_fail.pyc", + "usr/lib/python2.7/json/tests/test_check_circular.py", + "usr/lib/python2.7/json/tests/test_unicode.pyo", + "usr/lib/python2.7/json/tests/test_pass2.pyc", + "usr/lib/python2.7/json/tests/test_default.py", + "usr/lib/python2.7/json/tests/test_scanstring.py", + "usr/lib/python2.7/json/tests/test_default.pyo", + "usr/lib/python2.7/json/tests/test_tool.py", + "usr/lib/python2.7/json/tests/test_separators.py", + "usr/lib/python2.7/json/tests/test_separators.pyo", + "usr/lib/python2.7/json/tests/test_speedups.pyc", + "usr/lib/python2.7/json/tests/test_dump.pyo", + "usr/lib/python2.7/json/tests/test_speedups.py", + "usr/lib/python2.7/json/tests/test_fail.py", + "usr/lib/python2.7/json/tests/test_default.pyc", + "usr/lib/python2.7/json/tests/test_indent.pyc", + "usr/lib/python2.7/json/tests/test_pass1.pyc", + "usr/lib/python2.7/json/tests/test_tool.pyo", + "usr/lib/python2.7/json/tests/test_pass1.py", + "usr/lib/python2.7/json/tests/test_recursion.pyo", + "usr/lib/python2.7/json/tests/test_float.pyc", + "usr/lib/python2.7/json/tests/test_pass1.pyo", + "usr/lib/python2.7/json/tests/test_check_circular.pyo", + "usr/lib/python2.7/json/tests/test_decode.pyo", + "usr/lib/python2.7/json/tests/test_tool.pyc", + "usr/lib/python2.7/json/tests/__init__.pyo", + "usr/lib/python2.7/json/tests/test_pass2.pyo", + "usr/lib/python2.7/json/tests/test_dump.pyc", + "usr/lib/python2.7/json/tests/test_float.py", + "usr/lib/python2.7/json/tests/test_recursion.pyc", + "usr/lib/python2.7/json/tests/test_indent.pyo", + "usr/lib/python2.7/json/tests/test_decode.pyc", + "usr/lib/python2.7/json/tests/test_encode_basestring_ascii.py", + "usr/lib/python2.7/ensurepip/__init__.pyc", + "usr/lib/python2.7/ensurepip/__main__.pyc", + "usr/lib/python2.7/ensurepip/_uninstall.py", + "usr/lib/python2.7/ensurepip/_uninstall.pyo", + "usr/lib/python2.7/ensurepip/__init__.py", + "usr/lib/python2.7/ensurepip/__main__.pyo", + "usr/lib/python2.7/ensurepip/_uninstall.pyc", + "usr/lib/python2.7/ensurepip/__init__.pyo", + "usr/lib/python2.7/ensurepip/__main__.py", + "usr/lib/python2.7/ensurepip/_bundled/pip-9.0.3-py2.py3-none-any.whl", + "usr/lib/python2.7/ensurepip/_bundled/setuptools-39.0.1-py2.py3-none-any.whl", + "usr/lib/python2.7/hotshot/__init__.pyc", + "usr/lib/python2.7/hotshot/stones.py", + "usr/lib/python2.7/hotshot/stones.pyc", + "usr/lib/python2.7/hotshot/log.py", + "usr/lib/python2.7/hotshot/__init__.py", + "usr/lib/python2.7/hotshot/stats.py", + "usr/lib/python2.7/hotshot/log.pyc", + "usr/lib/python2.7/hotshot/stones.pyo", + "usr/lib/python2.7/hotshot/stats.pyc", + "usr/lib/python2.7/hotshot/__init__.pyo", + "usr/lib/python2.7/hotshot/stats.pyo", + "usr/lib/python2.7/hotshot/log.pyo", + "usr/lib/python2.7/logging/__init__.pyc", + "usr/lib/python2.7/logging/handlers.py", + "usr/lib/python2.7/logging/__init__.py", + "usr/lib/python2.7/logging/handlers.pyo", + "usr/lib/python2.7/logging/handlers.pyc", + "usr/lib/python2.7/logging/config.py", + "usr/lib/python2.7/logging/config.pyo", + "usr/lib/python2.7/logging/__init__.pyo", + "usr/lib/python2.7/logging/config.pyc", + "usr/lib/python2.7/multiprocessing/__init__.pyc", + "usr/lib/python2.7/multiprocessing/heap.pyc", + "usr/lib/python2.7/multiprocessing/pool.pyc", + "usr/lib/python2.7/multiprocessing/forking.pyc", + "usr/lib/python2.7/multiprocessing/util.pyo", + "usr/lib/python2.7/multiprocessing/heap.py", + "usr/lib/python2.7/multiprocessing/__init__.py", + "usr/lib/python2.7/multiprocessing/sharedctypes.pyc", + "usr/lib/python2.7/multiprocessing/queues.py", + "usr/lib/python2.7/multiprocessing/util.py", + "usr/lib/python2.7/multiprocessing/managers.pyo", + "usr/lib/python2.7/multiprocessing/forking.pyo", + "usr/lib/python2.7/multiprocessing/managers.pyc", + "usr/lib/python2.7/multiprocessing/heap.pyo", + "usr/lib/python2.7/multiprocessing/synchronize.py", + "usr/lib/python2.7/multiprocessing/synchronize.pyc", + "usr/lib/python2.7/multiprocessing/pool.py", + "usr/lib/python2.7/multiprocessing/forking.py", + "usr/lib/python2.7/multiprocessing/util.pyc", + "usr/lib/python2.7/multiprocessing/synchronize.pyo", + "usr/lib/python2.7/multiprocessing/sharedctypes.pyo", + "usr/lib/python2.7/multiprocessing/process.pyo", + "usr/lib/python2.7/multiprocessing/connection.pyo", + "usr/lib/python2.7/multiprocessing/reduction.pyc", + "usr/lib/python2.7/multiprocessing/queues.pyc", + "usr/lib/python2.7/multiprocessing/connection.pyc", + "usr/lib/python2.7/multiprocessing/connection.py", + "usr/lib/python2.7/multiprocessing/process.pyc", + "usr/lib/python2.7/multiprocessing/__init__.pyo", + "usr/lib/python2.7/multiprocessing/managers.py", + "usr/lib/python2.7/multiprocessing/sharedctypes.py", + "usr/lib/python2.7/multiprocessing/reduction.py", + "usr/lib/python2.7/multiprocessing/process.py", + "usr/lib/python2.7/multiprocessing/reduction.pyo", + "usr/lib/python2.7/multiprocessing/pool.pyo", + "usr/lib/python2.7/multiprocessing/queues.pyo", + "usr/lib/python2.7/multiprocessing/dummy/__init__.pyc", + "usr/lib/python2.7/multiprocessing/dummy/__init__.py", + "usr/lib/python2.7/multiprocessing/dummy/connection.pyo", + "usr/lib/python2.7/multiprocessing/dummy/connection.pyc", + "usr/lib/python2.7/multiprocessing/dummy/connection.py", + "usr/lib/python2.7/multiprocessing/dummy/__init__.pyo", + "usr/lib/python2.7/pydoc_data/__init__.pyc", + "usr/lib/python2.7/pydoc_data/topics.pyo", + "usr/lib/python2.7/pydoc_data/topics.pyc", + "usr/lib/python2.7/pydoc_data/__init__.py", + "usr/lib/python2.7/pydoc_data/__init__.pyo", + "usr/lib/python2.7/pydoc_data/topics.py", + "usr/lib/python2.7/lib-dynload/bz2.so", + "usr/lib/python2.7/lib-dynload/binascii.so", + "usr/lib/python2.7/lib-dynload/_functools.so", + "usr/lib/python2.7/lib-dynload/_ctypes.so", + "usr/lib/python2.7/lib-dynload/_testcapi.so", + "usr/lib/python2.7/lib-dynload/_codecs_iso2022.so", + "usr/lib/python2.7/lib-dynload/cPickle.so", + "usr/lib/python2.7/lib-dynload/select.so", + "usr/lib/python2.7/lib-dynload/fcntl.so", + "usr/lib/python2.7/lib-dynload/time.so", + "usr/lib/python2.7/lib-dynload/dbm.so", + "usr/lib/python2.7/lib-dynload/_collections.so", + "usr/lib/python2.7/lib-dynload/parser.so", + "usr/lib/python2.7/lib-dynload/_csv.so", + "usr/lib/python2.7/lib-dynload/_hashlib.so", + "usr/lib/python2.7/lib-dynload/_codecs_cn.so", + "usr/lib/python2.7/lib-dynload/_codecs_jp.so", + "usr/lib/python2.7/lib-dynload/_lsprof.so", + "usr/lib/python2.7/lib-dynload/mmap.so", + "usr/lib/python2.7/lib-dynload/_ctypes_test.so", + "usr/lib/python2.7/lib-dynload/_json.so", + "usr/lib/python2.7/lib-dynload/zlib.so", + "usr/lib/python2.7/lib-dynload/grp.so", + "usr/lib/python2.7/lib-dynload/future_builtins.so", + "usr/lib/python2.7/lib-dynload/itertools.so", + "usr/lib/python2.7/lib-dynload/Python-2.7.15-py2.7.egg-info", + "usr/lib/python2.7/lib-dynload/datetime.so", + "usr/lib/python2.7/lib-dynload/resource.so", + "usr/lib/python2.7/lib-dynload/_curses_panel.so", + "usr/lib/python2.7/lib-dynload/syslog.so", + "usr/lib/python2.7/lib-dynload/audioop.so", + "usr/lib/python2.7/lib-dynload/_heapq.so", + "usr/lib/python2.7/lib-dynload/termios.so", + "usr/lib/python2.7/lib-dynload/pyexpat.so", + "usr/lib/python2.7/lib-dynload/_codecs_tw.so", + "usr/lib/python2.7/lib-dynload/_elementtree.so", + "usr/lib/python2.7/lib-dynload/_io.so", + "usr/lib/python2.7/lib-dynload/_codecs_kr.so", + "usr/lib/python2.7/lib-dynload/_bisect.so", + "usr/lib/python2.7/lib-dynload/linuxaudiodev.so", + "usr/lib/python2.7/lib-dynload/spwd.so", + "usr/lib/python2.7/lib-dynload/cmath.so", + "usr/lib/python2.7/lib-dynload/_random.so", + "usr/lib/python2.7/lib-dynload/strop.so", + "usr/lib/python2.7/lib-dynload/ossaudiodev.so", + "usr/lib/python2.7/lib-dynload/math.so", + "usr/lib/python2.7/lib-dynload/_hotshot.so", + "usr/lib/python2.7/lib-dynload/unicodedata.so", + "usr/lib/python2.7/lib-dynload/_multiprocessing.so", + "usr/lib/python2.7/lib-dynload/_locale.so", + "usr/lib/python2.7/lib-dynload/_socket.so", + "usr/lib/python2.7/lib-dynload/_multibytecodec.so", + "usr/lib/python2.7/lib-dynload/operator.so", + "usr/lib/python2.7/lib-dynload/_struct.so", + "usr/lib/python2.7/lib-dynload/cStringIO.so", + "usr/lib/python2.7/lib-dynload/readline.so", + "usr/lib/python2.7/lib-dynload/_curses.so", + "usr/lib/python2.7/lib-dynload/_sqlite3.so", + "usr/lib/python2.7/lib-dynload/_codecs_hk.so", + "usr/lib/python2.7/lib-dynload/_ssl.so", + "usr/lib/python2.7/lib-dynload/crypt.so", + "usr/lib/python2.7/lib-dynload/array.so", + "usr/lib/python2.7/lib-tk/Tkconstants.pyo", + "usr/lib/python2.7/lib-tk/Tkconstants.pyc", + "usr/lib/python2.7/lib-tk/Tkdnd.pyc", + "usr/lib/python2.7/lib-tk/Tix.py", + "usr/lib/python2.7/lib-tk/tkColorChooser.py", + "usr/lib/python2.7/lib-tk/ScrolledText.pyo", + "usr/lib/python2.7/lib-tk/SimpleDialog.pyo", + "usr/lib/python2.7/lib-tk/Tix.pyo", + "usr/lib/python2.7/lib-tk/Dialog.pyo", + "usr/lib/python2.7/lib-tk/ttk.pyo", + "usr/lib/python2.7/lib-tk/tkFileDialog.py", + "usr/lib/python2.7/lib-tk/Tkinter.pyc", + "usr/lib/python2.7/lib-tk/tkMessageBox.pyc", + "usr/lib/python2.7/lib-tk/tkSimpleDialog.pyo", + "usr/lib/python2.7/lib-tk/Tix.pyc", + "usr/lib/python2.7/lib-tk/tkMessageBox.pyo", + "usr/lib/python2.7/lib-tk/FileDialog.py", + "usr/lib/python2.7/lib-tk/tkFileDialog.pyc", + "usr/lib/python2.7/lib-tk/SimpleDialog.pyc", + "usr/lib/python2.7/lib-tk/Dialog.py", + "usr/lib/python2.7/lib-tk/turtle.py", + "usr/lib/python2.7/lib-tk/FixTk.pyc", + "usr/lib/python2.7/lib-tk/tkMessageBox.py", + "usr/lib/python2.7/lib-tk/tkColorChooser.pyc", + "usr/lib/python2.7/lib-tk/FixTk.py", + "usr/lib/python2.7/lib-tk/ttk.py", + "usr/lib/python2.7/lib-tk/SimpleDialog.py", + "usr/lib/python2.7/lib-tk/ttk.pyc", + "usr/lib/python2.7/lib-tk/tkCommonDialog.pyc", + "usr/lib/python2.7/lib-tk/ScrolledText.pyc", + "usr/lib/python2.7/lib-tk/tkColorChooser.pyo", + "usr/lib/python2.7/lib-tk/Canvas.py", + "usr/lib/python2.7/lib-tk/tkSimpleDialog.pyc", + "usr/lib/python2.7/lib-tk/ScrolledText.py", + "usr/lib/python2.7/lib-tk/Dialog.pyc", + "usr/lib/python2.7/lib-tk/FixTk.pyo", + "usr/lib/python2.7/lib-tk/tkFont.pyc", + "usr/lib/python2.7/lib-tk/turtle.pyc", + "usr/lib/python2.7/lib-tk/Tkinter.pyo", + "usr/lib/python2.7/lib-tk/Tkinter.py", + "usr/lib/python2.7/lib-tk/FileDialog.pyc", + "usr/lib/python2.7/lib-tk/Tkdnd.py", + "usr/lib/python2.7/lib-tk/FileDialog.pyo", + "usr/lib/python2.7/lib-tk/tkFileDialog.pyo", + "usr/lib/python2.7/lib-tk/turtle.pyo", + "usr/lib/python2.7/lib-tk/tkFont.pyo", + "usr/lib/python2.7/lib-tk/Canvas.pyo", + "usr/lib/python2.7/lib-tk/Canvas.pyc", + "usr/lib/python2.7/lib-tk/tkSimpleDialog.py", + "usr/lib/python2.7/lib-tk/tkCommonDialog.pyo", + "usr/lib/python2.7/lib-tk/tkCommonDialog.py", + "usr/lib/python2.7/lib-tk/Tkconstants.py", + "usr/lib/python2.7/lib-tk/tkFont.py", + "usr/lib/python2.7/lib-tk/Tkdnd.pyo", + "usr/lib/python2.7/unittest/__init__.pyc", + "usr/lib/python2.7/unittest/__main__.pyc", + "usr/lib/python2.7/unittest/runner.pyc", + "usr/lib/python2.7/unittest/runner.pyo", + "usr/lib/python2.7/unittest/util.pyo", + "usr/lib/python2.7/unittest/signals.pyc", + "usr/lib/python2.7/unittest/suite.pyc", + "usr/lib/python2.7/unittest/__init__.py", + "usr/lib/python2.7/unittest/case.pyo", + "usr/lib/python2.7/unittest/util.py", + "usr/lib/python2.7/unittest/main.pyc", + "usr/lib/python2.7/unittest/__main__.pyo", + "usr/lib/python2.7/unittest/suite.pyo", + "usr/lib/python2.7/unittest/signals.pyo", + "usr/lib/python2.7/unittest/case.pyc", + "usr/lib/python2.7/unittest/result.pyo", + "usr/lib/python2.7/unittest/runner.py", + "usr/lib/python2.7/unittest/util.pyc", + "usr/lib/python2.7/unittest/main.pyo", + "usr/lib/python2.7/unittest/case.py", + "usr/lib/python2.7/unittest/loader.py", + "usr/lib/python2.7/unittest/loader.pyc", + "usr/lib/python2.7/unittest/suite.py", + "usr/lib/python2.7/unittest/main.py", + "usr/lib/python2.7/unittest/__init__.pyo", + "usr/lib/python2.7/unittest/__main__.py", + "usr/lib/python2.7/unittest/loader.pyo", + "usr/lib/python2.7/unittest/signals.py", + "usr/lib/python2.7/unittest/result.pyc", + "usr/lib/python2.7/unittest/result.py", + "usr/lib/python2.7/importlib/__init__.pyc", + "usr/lib/python2.7/importlib/__init__.py", + "usr/lib/python2.7/importlib/__init__.pyo", + "usr/lib/python2.7/compiler/__init__.pyc", + "usr/lib/python2.7/compiler/syntax.pyo", + "usr/lib/python2.7/compiler/pycodegen.pyo", + "usr/lib/python2.7/compiler/symbols.pyo", + "usr/lib/python2.7/compiler/misc.pyo", + "usr/lib/python2.7/compiler/future.pyo", + "usr/lib/python2.7/compiler/consts.py", + "usr/lib/python2.7/compiler/pycodegen.pyc", + "usr/lib/python2.7/compiler/ast.pyc", + "usr/lib/python2.7/compiler/__init__.py", + "usr/lib/python2.7/compiler/pyassem.pyc", + "usr/lib/python2.7/compiler/transformer.py", + "usr/lib/python2.7/compiler/pyassem.py", + "usr/lib/python2.7/compiler/syntax.pyc", + "usr/lib/python2.7/compiler/transformer.pyc", + "usr/lib/python2.7/compiler/transformer.pyo", + "usr/lib/python2.7/compiler/consts.pyo", + "usr/lib/python2.7/compiler/symbols.pyc", + "usr/lib/python2.7/compiler/pyassem.pyo", + "usr/lib/python2.7/compiler/future.pyc", + "usr/lib/python2.7/compiler/misc.pyc", + "usr/lib/python2.7/compiler/visitor.py", + "usr/lib/python2.7/compiler/syntax.py", + "usr/lib/python2.7/compiler/ast.pyo", + "usr/lib/python2.7/compiler/visitor.pyo", + "usr/lib/python2.7/compiler/visitor.pyc", + "usr/lib/python2.7/compiler/__init__.pyo", + "usr/lib/python2.7/compiler/consts.pyc", + "usr/lib/python2.7/compiler/ast.py", + "usr/lib/python2.7/compiler/misc.py", + "usr/lib/python2.7/compiler/future.py", + "usr/lib/python2.7/compiler/pycodegen.py", + "usr/lib/python2.7/compiler/symbols.py", + "usr/lib/python2.7/xml/__init__.pyc", + "usr/lib/python2.7/xml/__init__.py", + "usr/lib/python2.7/xml/__init__.pyo", + "usr/lib/python2.7/xml/dom/__init__.pyc", + "usr/lib/python2.7/xml/dom/expatbuilder.py", + "usr/lib/python2.7/xml/dom/NodeFilter.pyc", + "usr/lib/python2.7/xml/dom/xmlbuilder.pyo", + "usr/lib/python2.7/xml/dom/minidom.py", + "usr/lib/python2.7/xml/dom/minicompat.py", + "usr/lib/python2.7/xml/dom/__init__.py", + "usr/lib/python2.7/xml/dom/minicompat.pyo", + "usr/lib/python2.7/xml/dom/expatbuilder.pyo", + "usr/lib/python2.7/xml/dom/minidom.pyo", + "usr/lib/python2.7/xml/dom/expatbuilder.pyc", + "usr/lib/python2.7/xml/dom/NodeFilter.pyo", + "usr/lib/python2.7/xml/dom/xmlbuilder.pyc", + "usr/lib/python2.7/xml/dom/domreg.pyo", + "usr/lib/python2.7/xml/dom/NodeFilter.py", + "usr/lib/python2.7/xml/dom/domreg.pyc", + "usr/lib/python2.7/xml/dom/xmlbuilder.py", + "usr/lib/python2.7/xml/dom/minidom.pyc", + "usr/lib/python2.7/xml/dom/__init__.pyo", + "usr/lib/python2.7/xml/dom/pulldom.py", + "usr/lib/python2.7/xml/dom/minicompat.pyc", + "usr/lib/python2.7/xml/dom/pulldom.pyo", + "usr/lib/python2.7/xml/dom/domreg.py", + "usr/lib/python2.7/xml/dom/pulldom.pyc", + "usr/lib/python2.7/xml/parsers/__init__.pyc", + "usr/lib/python2.7/xml/parsers/expat.pyc", + "usr/lib/python2.7/xml/parsers/expat.py", + "usr/lib/python2.7/xml/parsers/__init__.py", + "usr/lib/python2.7/xml/parsers/expat.pyo", + "usr/lib/python2.7/xml/parsers/__init__.pyo", + "usr/lib/python2.7/xml/sax/__init__.pyc", + "usr/lib/python2.7/xml/sax/expatreader.pyc", + "usr/lib/python2.7/xml/sax/xmlreader.pyo", + "usr/lib/python2.7/xml/sax/_exceptions.pyo", + "usr/lib/python2.7/xml/sax/handler.pyo", + "usr/lib/python2.7/xml/sax/xmlreader.pyc", + "usr/lib/python2.7/xml/sax/__init__.py", + "usr/lib/python2.7/xml/sax/_exceptions.pyc", + "usr/lib/python2.7/xml/sax/handler.py", + "usr/lib/python2.7/xml/sax/expatreader.pyo", + "usr/lib/python2.7/xml/sax/_exceptions.py", + "usr/lib/python2.7/xml/sax/saxutils.py", + "usr/lib/python2.7/xml/sax/expatreader.py", + "usr/lib/python2.7/xml/sax/xmlreader.py", + "usr/lib/python2.7/xml/sax/__init__.pyo", + "usr/lib/python2.7/xml/sax/handler.pyc", + "usr/lib/python2.7/xml/sax/saxutils.pyo", + "usr/lib/python2.7/xml/sax/saxutils.pyc", + "usr/lib/python2.7/xml/etree/__init__.pyc", + "usr/lib/python2.7/xml/etree/cElementTree.py", + "usr/lib/python2.7/xml/etree/__init__.py", + "usr/lib/python2.7/xml/etree/ElementPath.py", + "usr/lib/python2.7/xml/etree/cElementTree.pyc", + "usr/lib/python2.7/xml/etree/ElementPath.pyc", + "usr/lib/python2.7/xml/etree/ElementTree.pyc", + "usr/lib/python2.7/xml/etree/ElementInclude.pyc", + "usr/lib/python2.7/xml/etree/cElementTree.pyo", + "usr/lib/python2.7/xml/etree/ElementTree.pyo", + "usr/lib/python2.7/xml/etree/ElementInclude.py", + "usr/lib/python2.7/xml/etree/__init__.pyo", + "usr/lib/python2.7/xml/etree/ElementTree.py", + "usr/lib/python2.7/xml/etree/ElementInclude.pyo", + "usr/lib/python2.7/xml/etree/ElementPath.pyo", + "usr/lib/python2.7/bsddb/db.py", + "usr/lib/python2.7/bsddb/__init__.pyc", + "usr/lib/python2.7/bsddb/dbtables.py", + "usr/lib/python2.7/bsddb/dbshelve.py", + "usr/lib/python2.7/bsddb/dbshelve.pyc", + "usr/lib/python2.7/bsddb/dbobj.py", + "usr/lib/python2.7/bsddb/__init__.py", + "usr/lib/python2.7/bsddb/dbrecio.pyo", + "usr/lib/python2.7/bsddb/dbtables.pyo", + "usr/lib/python2.7/bsddb/dbutils.pyo", + "usr/lib/python2.7/bsddb/dbrecio.py", + "usr/lib/python2.7/bsddb/dbutils.pyc", + "usr/lib/python2.7/bsddb/db.pyo", + "usr/lib/python2.7/bsddb/dbtables.pyc", + "usr/lib/python2.7/bsddb/dbobj.pyo", + "usr/lib/python2.7/bsddb/dbobj.pyc", + "usr/lib/python2.7/bsddb/dbshelve.pyo", + "usr/lib/python2.7/bsddb/dbutils.py", + "usr/lib/python2.7/bsddb/__init__.pyo", + "usr/lib/python2.7/bsddb/dbrecio.pyc", + "usr/lib/python2.7/bsddb/db.pyc", + "usr/lib/python2.7/config/Setup.local", + "usr/lib/python2.7/config/makesetup", + "usr/lib/python2.7/config/Makefile", + "usr/lib/python2.7/config/config.c.in", + "usr/lib/python2.7/config/install-sh", + "usr/lib/python2.7/config/Setup.config", + "usr/lib/python2.7/config/Setup", + "usr/lib/python2.7/idlelib/ScrolledList.pyo", + "usr/lib/python2.7/idlelib/ReplaceDialog.pyc", + "usr/lib/python2.7/idlelib/idle.py", + "usr/lib/python2.7/idlelib/IOBinding.pyc", + "usr/lib/python2.7/idlelib/PyParse.py", + "usr/lib/python2.7/idlelib/RemoteObjectBrowser.pyo", + "usr/lib/python2.7/idlelib/__init__.pyc", + "usr/lib/python2.7/idlelib/UndoDelegator.py", + "usr/lib/python2.7/idlelib/CodeContext.pyc", + "usr/lib/python2.7/idlelib/AutoExpand.py", + "usr/lib/python2.7/idlelib/OutputWindow.pyc", + "usr/lib/python2.7/idlelib/help.py", + "usr/lib/python2.7/idlelib/StackViewer.pyo", + "usr/lib/python2.7/idlelib/EditorWindow.pyo", + "usr/lib/python2.7/idlelib/Delegator.py", + "usr/lib/python2.7/idlelib/SearchDialog.pyc", + "usr/lib/python2.7/idlelib/Debugger.py", + "usr/lib/python2.7/idlelib/RstripExtension.pyc", + "usr/lib/python2.7/idlelib/CallTips.py", + "usr/lib/python2.7/idlelib/aboutDialog.pyo", + "usr/lib/python2.7/idlelib/Bindings.py", + "usr/lib/python2.7/idlelib/AutoCompleteWindow.pyo", + "usr/lib/python2.7/idlelib/Debugger.pyo", + "usr/lib/python2.7/idlelib/ColorDelegator.pyo", + "usr/lib/python2.7/idlelib/idle.pyw", + "usr/lib/python2.7/idlelib/PyShell.pyc", + "usr/lib/python2.7/idlelib/ColorDelegator.pyc", + "usr/lib/python2.7/idlelib/CodeContext.pyo", + "usr/lib/python2.7/idlelib/idle.bat", + "usr/lib/python2.7/idlelib/ZoomHeight.py", + "usr/lib/python2.7/idlelib/RemoteObjectBrowser.pyc", + "usr/lib/python2.7/idlelib/CallTipWindow.py", + "usr/lib/python2.7/idlelib/CallTips.pyc", + "usr/lib/python2.7/idlelib/OutputWindow.pyo", + "usr/lib/python2.7/idlelib/ToolTip.py", + "usr/lib/python2.7/idlelib/ScriptBinding.pyc", + "usr/lib/python2.7/idlelib/ReplaceDialog.py", + "usr/lib/python2.7/idlelib/NEWS.txt", + "usr/lib/python2.7/idlelib/ChangeLog", + "usr/lib/python2.7/idlelib/configHelpSourceEdit.pyc", + "usr/lib/python2.7/idlelib/idle.pyo", + "usr/lib/python2.7/idlelib/UndoDelegator.pyc", + "usr/lib/python2.7/idlelib/SearchDialogBase.pyc", + "usr/lib/python2.7/idlelib/macosxSupport.pyo", + "usr/lib/python2.7/idlelib/ClassBrowser.pyc", + "usr/lib/python2.7/idlelib/run.py", + "usr/lib/python2.7/idlelib/macosxSupport.pyc", + "usr/lib/python2.7/idlelib/Bindings.pyc", + "usr/lib/python2.7/idlelib/MultiStatusBar.pyo", + "usr/lib/python2.7/idlelib/Debugger.pyc", + "usr/lib/python2.7/idlelib/WidgetRedirector.pyo", + "usr/lib/python2.7/idlelib/dynOptionMenuWidget.pyo", + "usr/lib/python2.7/idlelib/EditorWindow.py", + "usr/lib/python2.7/idlelib/FormatParagraph.pyc", + "usr/lib/python2.7/idlelib/Delegator.pyc", + "usr/lib/python2.7/idlelib/Percolator.py", + "usr/lib/python2.7/idlelib/HyperParser.pyo", + "usr/lib/python2.7/idlelib/ClassBrowser.py", + "usr/lib/python2.7/idlelib/configHandler.pyo", + "usr/lib/python2.7/idlelib/__init__.py", + "usr/lib/python2.7/idlelib/AutoComplete.pyo", + "usr/lib/python2.7/idlelib/MultiCall.pyo", + "usr/lib/python2.7/idlelib/WidgetRedirector.py", + "usr/lib/python2.7/idlelib/WidgetRedirector.pyc", + "usr/lib/python2.7/idlelib/AutoExpand.pyo", + "usr/lib/python2.7/idlelib/TODO.txt", + "usr/lib/python2.7/idlelib/RemoteDebugger.py", + "usr/lib/python2.7/idlelib/HyperParser.py", + "usr/lib/python2.7/idlelib/keybindingDialog.pyo", + "usr/lib/python2.7/idlelib/MultiCall.pyc", + "usr/lib/python2.7/idlelib/SearchDialogBase.py", + "usr/lib/python2.7/idlelib/SearchEngine.pyo", + "usr/lib/python2.7/idlelib/FileList.pyc", + "usr/lib/python2.7/idlelib/StackViewer.pyc", + "usr/lib/python2.7/idlelib/AutoCompleteWindow.py", + "usr/lib/python2.7/idlelib/RemoteObjectBrowser.py", + "usr/lib/python2.7/idlelib/config-extensions.def", + "usr/lib/python2.7/idlelib/Percolator.pyc", + "usr/lib/python2.7/idlelib/HISTORY.txt", + "usr/lib/python2.7/idlelib/Bindings.pyo", + "usr/lib/python2.7/idlelib/rpc.pyc", + "usr/lib/python2.7/idlelib/idlever.pyc", + "usr/lib/python2.7/idlelib/MultiStatusBar.py", + "usr/lib/python2.7/idlelib/FileList.py", + "usr/lib/python2.7/idlelib/SearchDialog.pyo", + "usr/lib/python2.7/idlelib/AutoComplete.py", + "usr/lib/python2.7/idlelib/textView.pyo", + "usr/lib/python2.7/idlelib/IOBinding.pyo", + "usr/lib/python2.7/idlelib/config-main.def", + "usr/lib/python2.7/idlelib/IdleHistory.py", + "usr/lib/python2.7/idlelib/RstripExtension.pyo", + "usr/lib/python2.7/idlelib/PyParse.pyo", + "usr/lib/python2.7/idlelib/RstripExtension.py", + "usr/lib/python2.7/idlelib/CREDITS.txt", + "usr/lib/python2.7/idlelib/WindowList.pyc", + "usr/lib/python2.7/idlelib/AutoExpand.pyc", + "usr/lib/python2.7/idlelib/rpc.py", + "usr/lib/python2.7/idlelib/textView.pyc", + "usr/lib/python2.7/idlelib/configDialog.pyo", + "usr/lib/python2.7/idlelib/dynOptionMenuWidget.py", + "usr/lib/python2.7/idlelib/keybindingDialog.pyc", + "usr/lib/python2.7/idlelib/textView.py", + "usr/lib/python2.7/idlelib/PyParse.pyc", + "usr/lib/python2.7/idlelib/extend.txt", + "usr/lib/python2.7/idlelib/StackViewer.py", + "usr/lib/python2.7/idlelib/ZoomHeight.pyo", + "usr/lib/python2.7/idlelib/SearchEngine.py", + "usr/lib/python2.7/idlelib/ParenMatch.pyc", + "usr/lib/python2.7/idlelib/ScrolledList.pyc", + "usr/lib/python2.7/idlelib/MultiCall.py", + "usr/lib/python2.7/idlelib/WindowList.py", + "usr/lib/python2.7/idlelib/ToolTip.pyc", + "usr/lib/python2.7/idlelib/ScrolledList.py", + "usr/lib/python2.7/idlelib/ScriptBinding.pyo", + "usr/lib/python2.7/idlelib/PathBrowser.pyc", + "usr/lib/python2.7/idlelib/idlever.py", + "usr/lib/python2.7/idlelib/PyShell.pyo", + "usr/lib/python2.7/idlelib/OutputWindow.py", + "usr/lib/python2.7/idlelib/TreeWidget.pyc", + "usr/lib/python2.7/idlelib/dynOptionMenuWidget.pyc", + "usr/lib/python2.7/idlelib/config-keys.def", + "usr/lib/python2.7/idlelib/PathBrowser.pyo", + "usr/lib/python2.7/idlelib/idlever.pyo", + "usr/lib/python2.7/idlelib/PathBrowser.py", + "usr/lib/python2.7/idlelib/RemoteDebugger.pyo", + "usr/lib/python2.7/idlelib/config-highlight.def", + "usr/lib/python2.7/idlelib/FormatParagraph.py", + "usr/lib/python2.7/idlelib/Percolator.pyo", + "usr/lib/python2.7/idlelib/IdleHistory.pyo", + "usr/lib/python2.7/idlelib/macosxSupport.py", + "usr/lib/python2.7/idlelib/CodeContext.py", + "usr/lib/python2.7/idlelib/ObjectBrowser.pyc", + "usr/lib/python2.7/idlelib/rpc.pyo", + "usr/lib/python2.7/idlelib/tabbedpages.pyo", + "usr/lib/python2.7/idlelib/RemoteDebugger.pyc", + "usr/lib/python2.7/idlelib/SearchDialogBase.pyo", + "usr/lib/python2.7/idlelib/UndoDelegator.pyo", + "usr/lib/python2.7/idlelib/configSectionNameDialog.py", + "usr/lib/python2.7/idlelib/ClassBrowser.pyo", + "usr/lib/python2.7/idlelib/keybindingDialog.py", + "usr/lib/python2.7/idlelib/configHelpSourceEdit.pyo", + "usr/lib/python2.7/idlelib/configHandler.pyc", + "usr/lib/python2.7/idlelib/Delegator.pyo", + "usr/lib/python2.7/idlelib/GrepDialog.pyo", + "usr/lib/python2.7/idlelib/configHelpSourceEdit.py", + "usr/lib/python2.7/idlelib/help.pyo", + "usr/lib/python2.7/idlelib/SearchDialog.py", + "usr/lib/python2.7/idlelib/help.pyc", + "usr/lib/python2.7/idlelib/ParenMatch.pyo", + "usr/lib/python2.7/idlelib/AutoComplete.pyc", + "usr/lib/python2.7/idlelib/GrepDialog.py", + "usr/lib/python2.7/idlelib/CallTipWindow.pyc", + "usr/lib/python2.7/idlelib/aboutDialog.py", + "usr/lib/python2.7/idlelib/ColorDelegator.py", + "usr/lib/python2.7/idlelib/ZoomHeight.pyc", + "usr/lib/python2.7/idlelib/HyperParser.pyc", + "usr/lib/python2.7/idlelib/help.html", + "usr/lib/python2.7/idlelib/README.txt", + "usr/lib/python2.7/idlelib/WindowList.pyo", + "usr/lib/python2.7/idlelib/ParenMatch.py", + "usr/lib/python2.7/idlelib/IdleHistory.pyc", + "usr/lib/python2.7/idlelib/ScriptBinding.py", + "usr/lib/python2.7/idlelib/FileList.pyo", + "usr/lib/python2.7/idlelib/__init__.pyo", + "usr/lib/python2.7/idlelib/help.txt", + "usr/lib/python2.7/idlelib/ToolTip.pyo", + "usr/lib/python2.7/idlelib/FormatParagraph.pyo", + "usr/lib/python2.7/idlelib/tabbedpages.py", + "usr/lib/python2.7/idlelib/ReplaceDialog.pyo", + "usr/lib/python2.7/idlelib/run.pyc", + "usr/lib/python2.7/idlelib/configSectionNameDialog.pyc", + "usr/lib/python2.7/idlelib/run.pyo", + "usr/lib/python2.7/idlelib/configDialog.py", + "usr/lib/python2.7/idlelib/tabbedpages.pyc", + "usr/lib/python2.7/idlelib/configSectionNameDialog.pyo", + "usr/lib/python2.7/idlelib/SearchEngine.pyc", + "usr/lib/python2.7/idlelib/ObjectBrowser.py", + "usr/lib/python2.7/idlelib/configHandler.py", + "usr/lib/python2.7/idlelib/AutoCompleteWindow.pyc", + "usr/lib/python2.7/idlelib/idle.pyc", + "usr/lib/python2.7/idlelib/GrepDialog.pyc", + "usr/lib/python2.7/idlelib/CallTips.pyo", + "usr/lib/python2.7/idlelib/PyShell.py", + "usr/lib/python2.7/idlelib/TreeWidget.py", + "usr/lib/python2.7/idlelib/ObjectBrowser.pyo", + "usr/lib/python2.7/idlelib/configDialog.pyc", + "usr/lib/python2.7/idlelib/aboutDialog.pyc", + "usr/lib/python2.7/idlelib/IOBinding.py", + "usr/lib/python2.7/idlelib/CallTipWindow.pyo", + "usr/lib/python2.7/idlelib/TreeWidget.pyo", + "usr/lib/python2.7/idlelib/MultiStatusBar.pyc", + "usr/lib/python2.7/idlelib/EditorWindow.pyc", + "usr/lib/python2.7/idlelib/Icons/idle_32.gif", + "usr/lib/python2.7/idlelib/Icons/idle.icns", + "usr/lib/python2.7/idlelib/Icons/tk.gif", + "usr/lib/python2.7/idlelib/Icons/plusnode.gif", + "usr/lib/python2.7/idlelib/Icons/idle_32.png", + "usr/lib/python2.7/idlelib/Icons/idle.ico", + "usr/lib/python2.7/idlelib/Icons/openfolder.gif", + "usr/lib/python2.7/idlelib/Icons/idle_16.png", + "usr/lib/python2.7/idlelib/Icons/idle_48.gif", + "usr/lib/python2.7/idlelib/Icons/python.gif", + "usr/lib/python2.7/idlelib/Icons/idle_16.gif", + "usr/lib/python2.7/idlelib/Icons/minusnode.gif", + "usr/lib/python2.7/idlelib/Icons/idle_48.png", + "usr/lib/python2.7/idlelib/Icons/folder.gif", + "usr/lib/python2.7/idlelib/idle_test/test_autocomplete.pyo", + "usr/lib/python2.7/idlelib/idle_test/mock_tk.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_pathbrowser.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_parenmatch.pyo", + "usr/lib/python2.7/idlelib/idle_test/__init__.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_io.py", + "usr/lib/python2.7/idlelib/idle_test/test_calltips.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_autoexpand.py", + "usr/lib/python2.7/idlelib/idle_test/test_helpabout.py", + "usr/lib/python2.7/idlelib/idle_test/test_grep.py", + "usr/lib/python2.7/idlelib/idle_test/mock_idle.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_hyperparser.py", + "usr/lib/python2.7/idlelib/idle_test/test_delegator.py", + "usr/lib/python2.7/idlelib/idle_test/test_warning.py", + "usr/lib/python2.7/idlelib/idle_test/test_textview.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_rstrip.py", + "usr/lib/python2.7/idlelib/idle_test/test_calltips.py", + "usr/lib/python2.7/idlelib/idle_test/test_pathbrowser.pyo", + "usr/lib/python2.7/idlelib/idle_test/htest.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_searchengine.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_searchdialogbase.py", + "usr/lib/python2.7/idlelib/idle_test/test_idlehistory.py", + "usr/lib/python2.7/idlelib/idle_test/test_autocomplete.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_configdialog.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_grep.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_text.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_config_name.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_widgetredir.py", + "usr/lib/python2.7/idlelib/idle_test/test_grep.pyc", + "usr/lib/python2.7/idlelib/idle_test/__init__.py", + "usr/lib/python2.7/idlelib/idle_test/test_helpabout.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_idlehistory.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_text.py", + "usr/lib/python2.7/idlelib/idle_test/test_formatparagraph.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_parenmatch.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_config_name.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_rstrip.pyo", + "usr/lib/python2.7/idlelib/idle_test/mock_tk.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_configdialog.py", + "usr/lib/python2.7/idlelib/idle_test/test_widgetredir.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_configdialog.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_formatparagraph.py", + "usr/lib/python2.7/idlelib/idle_test/test_editmenu.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_config_name.py", + "usr/lib/python2.7/idlelib/idle_test/mock_idle.py", + "usr/lib/python2.7/idlelib/idle_test/htest.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_textview.pyc", + "usr/lib/python2.7/idlelib/idle_test/mock_idle.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_delegator.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_warning.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_delegator.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_textview.py", + "usr/lib/python2.7/idlelib/idle_test/test_rstrip.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_searchengine.py", + "usr/lib/python2.7/idlelib/idle_test/mock_tk.py", + "usr/lib/python2.7/idlelib/idle_test/test_searchdialogbase.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_autoexpand.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_hyperparser.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_formatparagraph.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_searchengine.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_autoexpand.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_editmenu.py", + "usr/lib/python2.7/idlelib/idle_test/README.txt", + "usr/lib/python2.7/idlelib/idle_test/__init__.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_warning.pyc", + "usr/lib/python2.7/idlelib/idle_test/htest.py", + "usr/lib/python2.7/idlelib/idle_test/test_widgetredir.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_text.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_helpabout.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_io.pyc", + "usr/lib/python2.7/idlelib/idle_test/test_calltips.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_searchdialogbase.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_io.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_idlehistory.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_parenmatch.py", + "usr/lib/python2.7/idlelib/idle_test/test_autocomplete.py", + "usr/lib/python2.7/idlelib/idle_test/test_hyperparser.pyo", + "usr/lib/python2.7/idlelib/idle_test/test_pathbrowser.py", + "usr/lib/python2.7/idlelib/idle_test/test_editmenu.pyo", + "usr/lib/python2.7/encodings/cp037.py", + "usr/lib/python2.7/encodings/utf_32_be.py", + "usr/lib/python2.7/encodings/utf_16.pyc", + "usr/lib/python2.7/encodings/koi8_u.pyc", + "usr/lib/python2.7/encodings/cp437.py", + "usr/lib/python2.7/encodings/latin_1.py", + "usr/lib/python2.7/encodings/euc_jisx0213.py", + "usr/lib/python2.7/encodings/euc_jis_2004.py", + "usr/lib/python2.7/encodings/cp720.pyo", + "usr/lib/python2.7/encodings/big5hkscs.py", + "usr/lib/python2.7/encodings/big5.py", + "usr/lib/python2.7/encodings/__init__.pyc", + "usr/lib/python2.7/encodings/charmap.py", + "usr/lib/python2.7/encodings/cp874.pyc", + "usr/lib/python2.7/encodings/cp875.pyo", + "usr/lib/python2.7/encodings/cp424.pyo", + "usr/lib/python2.7/encodings/cp437.pyc", + "usr/lib/python2.7/encodings/iso2022_jp_3.py", + "usr/lib/python2.7/encodings/cp932.pyc", + "usr/lib/python2.7/encodings/utf_32.pyc", + "usr/lib/python2.7/encodings/cp1250.py", + "usr/lib/python2.7/encodings/euc_jis_2004.pyc", + "usr/lib/python2.7/encodings/hz.pyo", + "usr/lib/python2.7/encodings/cp037.pyc", + "usr/lib/python2.7/encodings/cp857.pyc", + "usr/lib/python2.7/encodings/mac_turkish.pyc", + "usr/lib/python2.7/encodings/string_escape.pyc", + "usr/lib/python2.7/encodings/cp865.pyc", + "usr/lib/python2.7/encodings/unicode_internal.py", + "usr/lib/python2.7/encodings/utf_8_sig.pyo", + "usr/lib/python2.7/encodings/iso8859_5.pyc", + "usr/lib/python2.7/encodings/mac_roman.py", + "usr/lib/python2.7/encodings/cp863.py", + "usr/lib/python2.7/encodings/tis_620.pyc", + "usr/lib/python2.7/encodings/mac_romanian.py", + "usr/lib/python2.7/encodings/cp1258.py", + "usr/lib/python2.7/encodings/unicode_escape.py", + "usr/lib/python2.7/encodings/cp1253.pyo", + "usr/lib/python2.7/encodings/utf_8_sig.pyc", + "usr/lib/python2.7/encodings/hex_codec.pyo", + "usr/lib/python2.7/encodings/utf_32.py", + "usr/lib/python2.7/encodings/charmap.pyo", + "usr/lib/python2.7/encodings/cp862.py", + "usr/lib/python2.7/encodings/cp866.py", + "usr/lib/python2.7/encodings/cp1251.pyo", + "usr/lib/python2.7/encodings/iso8859_13.py", + "usr/lib/python2.7/encodings/cp1252.py", + "usr/lib/python2.7/encodings/cp437.pyo", + "usr/lib/python2.7/encodings/cp860.pyo", + "usr/lib/python2.7/encodings/mac_latin2.py", + "usr/lib/python2.7/encodings/mac_romanian.pyo", + "usr/lib/python2.7/encodings/rot_13.pyo", + "usr/lib/python2.7/encodings/aliases.pyo", + "usr/lib/python2.7/encodings/utf_8.py", + "usr/lib/python2.7/encodings/cp875.py", + "usr/lib/python2.7/encodings/mac_farsi.py", + "usr/lib/python2.7/encodings/cp1257.pyo", + "usr/lib/python2.7/encodings/cp424.py", + "usr/lib/python2.7/encodings/cp863.pyc", + "usr/lib/python2.7/encodings/mac_latin2.pyc", + "usr/lib/python2.7/encodings/rot_13.py", + "usr/lib/python2.7/encodings/base64_codec.py", + "usr/lib/python2.7/encodings/bz2_codec.py", + "usr/lib/python2.7/encodings/shift_jis_2004.pyo", + "usr/lib/python2.7/encodings/iso8859_3.py", + "usr/lib/python2.7/encodings/koi8_u.py", + "usr/lib/python2.7/encodings/gbk.pyo", + "usr/lib/python2.7/encodings/utf_16_be.py", + "usr/lib/python2.7/encodings/iso2022_jp_2004.pyo", + "usr/lib/python2.7/encodings/mac_greek.pyc", + "usr/lib/python2.7/encodings/cp864.pyo", + "usr/lib/python2.7/encodings/cp862.pyo", + "usr/lib/python2.7/encodings/utf_16_le.py", + "usr/lib/python2.7/encodings/cp950.py", + "usr/lib/python2.7/encodings/iso8859_14.py", + "usr/lib/python2.7/encodings/cp864.pyc", + "usr/lib/python2.7/encodings/iso8859_15.py", + "usr/lib/python2.7/encodings/cp857.pyo", + "usr/lib/python2.7/encodings/iso8859_15.pyo", + "usr/lib/python2.7/encodings/mac_croatian.py", + "usr/lib/python2.7/encodings/uu_codec.pyo", + "usr/lib/python2.7/encodings/cp1252.pyo", + "usr/lib/python2.7/encodings/cp1006.pyc", + "usr/lib/python2.7/encodings/quopri_codec.py", + "usr/lib/python2.7/encodings/iso2022_jp_1.pyc", + "usr/lib/python2.7/encodings/cp932.py", + "usr/lib/python2.7/encodings/undefined.pyo", + "usr/lib/python2.7/encodings/johab.pyo", + "usr/lib/python2.7/encodings/aliases.py", + "usr/lib/python2.7/encodings/iso2022_jp_ext.pyo", + "usr/lib/python2.7/encodings/shift_jisx0213.pyc", + "usr/lib/python2.7/encodings/cp1140.py", + "usr/lib/python2.7/encodings/utf_16_le.pyo", + "usr/lib/python2.7/encodings/iso8859_9.pyc", + "usr/lib/python2.7/encodings/punycode.pyc", + "usr/lib/python2.7/encodings/ascii.pyc", + "usr/lib/python2.7/encodings/mac_romanian.pyc", + "usr/lib/python2.7/encodings/hex_codec.py", + "usr/lib/python2.7/encodings/shift_jis.pyo", + "usr/lib/python2.7/encodings/mac_cyrillic.pyo", + "usr/lib/python2.7/encodings/iso2022_jp.pyc", + "usr/lib/python2.7/encodings/cp852.py", + "usr/lib/python2.7/encodings/euc_jp.py", + "usr/lib/python2.7/encodings/palmos.pyo", + "usr/lib/python2.7/encodings/cp720.py", + "usr/lib/python2.7/encodings/undefined.pyc", + "usr/lib/python2.7/encodings/cp866.pyc", + "usr/lib/python2.7/encodings/unicode_internal.pyo", + "usr/lib/python2.7/encodings/iso8859_4.py", + "usr/lib/python2.7/encodings/cp775.py", + "usr/lib/python2.7/encodings/cp869.pyo", + "usr/lib/python2.7/encodings/cp862.pyc", + "usr/lib/python2.7/encodings/zlib_codec.pyo", + "usr/lib/python2.7/encodings/iso8859_16.pyo", + "usr/lib/python2.7/encodings/iso8859_9.py", + "usr/lib/python2.7/encodings/iso8859_6.pyo", + "usr/lib/python2.7/encodings/iso2022_jp_3.pyo", + "usr/lib/python2.7/encodings/cp1254.pyo", + "usr/lib/python2.7/encodings/cp1006.py", + "usr/lib/python2.7/encodings/__init__.py", + "usr/lib/python2.7/encodings/cp869.pyc", + "usr/lib/python2.7/encodings/utf_16_be.pyo", + "usr/lib/python2.7/encodings/bz2_codec.pyc", + "usr/lib/python2.7/encodings/cp037.pyo", + "usr/lib/python2.7/encodings/cp1253.pyc", + "usr/lib/python2.7/encodings/iso2022_jp_2.pyo", + "usr/lib/python2.7/encodings/charmap.pyc", + "usr/lib/python2.7/encodings/zlib_codec.pyc", + "usr/lib/python2.7/encodings/mbcs.pyc", + "usr/lib/python2.7/encodings/iso8859_10.pyo", + "usr/lib/python2.7/encodings/iso2022_jp_3.pyc", + "usr/lib/python2.7/encodings/hex_codec.pyc", + "usr/lib/python2.7/encodings/cp737.py", + "usr/lib/python2.7/encodings/iso2022_jp_2004.pyc", + "usr/lib/python2.7/encodings/iso8859_2.pyc", + "usr/lib/python2.7/encodings/cp1026.pyc", + "usr/lib/python2.7/encodings/gb18030.pyo", + "usr/lib/python2.7/encodings/euc_kr.pyo", + "usr/lib/python2.7/encodings/iso8859_7.py", + "usr/lib/python2.7/encodings/ptcp154.pyc", + "usr/lib/python2.7/encodings/cp856.py", + "usr/lib/python2.7/encodings/utf_32_le.pyo", + "usr/lib/python2.7/encodings/shift_jis_2004.pyc", + "usr/lib/python2.7/encodings/cp737.pyc", + "usr/lib/python2.7/encodings/cp737.pyo", + "usr/lib/python2.7/encodings/cp865.py", + "usr/lib/python2.7/encodings/iso8859_11.pyo", + "usr/lib/python2.7/encodings/iso8859_3.pyo", + "usr/lib/python2.7/encodings/iso2022_jp.py", + "usr/lib/python2.7/encodings/iso8859_6.py", + "usr/lib/python2.7/encodings/zlib_codec.py", + "usr/lib/python2.7/encodings/cp855.pyc", + "usr/lib/python2.7/encodings/gb2312.pyo", + "usr/lib/python2.7/encodings/mac_arabic.pyo", + "usr/lib/python2.7/encodings/big5hkscs.pyo", + "usr/lib/python2.7/encodings/ptcp154.pyo", + "usr/lib/python2.7/encodings/iso8859_10.py", + "usr/lib/python2.7/encodings/koi8_r.pyc", + "usr/lib/python2.7/encodings/iso8859_16.py", + "usr/lib/python2.7/encodings/gb18030.pyc", + "usr/lib/python2.7/encodings/iso2022_jp_2.py", + "usr/lib/python2.7/encodings/gb18030.py", + "usr/lib/python2.7/encodings/cp1256.pyo", + "usr/lib/python2.7/encodings/shift_jis_2004.py", + "usr/lib/python2.7/encodings/utf_8.pyc", + "usr/lib/python2.7/encodings/cp1255.pyc", + "usr/lib/python2.7/encodings/cp775.pyo", + "usr/lib/python2.7/encodings/latin_1.pyo", + "usr/lib/python2.7/encodings/johab.py", + "usr/lib/python2.7/encodings/mac_croatian.pyc", + "usr/lib/python2.7/encodings/utf_16_be.pyc", + "usr/lib/python2.7/encodings/cp1006.pyo", + "usr/lib/python2.7/encodings/cp874.pyo", + "usr/lib/python2.7/encodings/cp1256.py", + "usr/lib/python2.7/encodings/cp850.pyc", + "usr/lib/python2.7/encodings/base64_codec.pyc", + "usr/lib/python2.7/encodings/iso8859_11.pyc", + "usr/lib/python2.7/encodings/cp424.pyc", + "usr/lib/python2.7/encodings/mac_arabic.pyc", + "usr/lib/python2.7/encodings/cp861.pyo", + "usr/lib/python2.7/encodings/cp950.pyc", + "usr/lib/python2.7/encodings/iso2022_jp_ext.pyc", + "usr/lib/python2.7/encodings/cp1253.py", + "usr/lib/python2.7/encodings/cp861.py", + "usr/lib/python2.7/encodings/cp1250.pyc", + "usr/lib/python2.7/encodings/mac_centeuro.pyc", + "usr/lib/python2.7/encodings/utf_16.pyo", + "usr/lib/python2.7/encodings/mac_farsi.pyc", + "usr/lib/python2.7/encodings/iso8859_2.pyo", + "usr/lib/python2.7/encodings/iso2022_kr.py", + "usr/lib/python2.7/encodings/mac_centeuro.pyo", + "usr/lib/python2.7/encodings/iso8859_4.pyc", + "usr/lib/python2.7/encodings/bz2_codec.pyo", + "usr/lib/python2.7/encodings/cp858.pyo", + "usr/lib/python2.7/encodings/hp_roman8.pyo", + "usr/lib/python2.7/encodings/iso2022_jp_1.py", + "usr/lib/python2.7/encodings/iso8859_1.pyo", + "usr/lib/python2.7/encodings/shift_jisx0213.py", + "usr/lib/python2.7/encodings/cp1254.py", + "usr/lib/python2.7/encodings/cp1140.pyc", + "usr/lib/python2.7/encodings/mac_iceland.py", + "usr/lib/python2.7/encodings/mac_greek.pyo", + "usr/lib/python2.7/encodings/cp869.py", + "usr/lib/python2.7/encodings/raw_unicode_escape.pyc", + "usr/lib/python2.7/encodings/utf_32_be.pyc", + "usr/lib/python2.7/encodings/rot_13.pyc", + "usr/lib/python2.7/encodings/hz.pyc", + "usr/lib/python2.7/encodings/iso2022_jp_2.pyc", + "usr/lib/python2.7/encodings/mac_iceland.pyc", + "usr/lib/python2.7/encodings/cp949.pyc", + "usr/lib/python2.7/encodings/iso2022_kr.pyc", + "usr/lib/python2.7/encodings/utf_16_le.pyc", + "usr/lib/python2.7/encodings/iso8859_1.py", + "usr/lib/python2.7/encodings/hp_roman8.pyc", + "usr/lib/python2.7/encodings/iso8859_8.pyo", + "usr/lib/python2.7/encodings/iso8859_7.pyo", + "usr/lib/python2.7/encodings/utf_7.py", + "usr/lib/python2.7/encodings/euc_jisx0213.pyc", + "usr/lib/python2.7/encodings/iso8859_14.pyc", + "usr/lib/python2.7/encodings/iso8859_11.py", + "usr/lib/python2.7/encodings/mac_farsi.pyo", + "usr/lib/python2.7/encodings/iso8859_9.pyo", + "usr/lib/python2.7/encodings/gbk.pyc", + "usr/lib/python2.7/encodings/iso8859_13.pyo", + "usr/lib/python2.7/encodings/euc_jis_2004.pyo", + "usr/lib/python2.7/encodings/iso8859_2.py", + "usr/lib/python2.7/encodings/punycode.py", + "usr/lib/python2.7/encodings/big5hkscs.pyc", + "usr/lib/python2.7/encodings/aliases.pyc", + "usr/lib/python2.7/encodings/cp858.py", + "usr/lib/python2.7/encodings/utf_7.pyc", + "usr/lib/python2.7/encodings/cp720.pyc", + "usr/lib/python2.7/encodings/utf_8_sig.py", + "usr/lib/python2.7/encodings/uu_codec.py", + "usr/lib/python2.7/encodings/cp1258.pyc", + "usr/lib/python2.7/encodings/iso8859_14.pyo", + "usr/lib/python2.7/encodings/iso8859_16.pyc", + "usr/lib/python2.7/encodings/cp875.pyc", + "usr/lib/python2.7/encodings/raw_unicode_escape.py", + "usr/lib/python2.7/encodings/tis_620.pyo", + "usr/lib/python2.7/encodings/euc_jisx0213.pyo", + "usr/lib/python2.7/encodings/punycode.pyo", + "usr/lib/python2.7/encodings/quopri_codec.pyc", + "usr/lib/python2.7/encodings/string_escape.pyo", + "usr/lib/python2.7/encodings/cp1251.pyc", + "usr/lib/python2.7/encodings/gb2312.pyc", + "usr/lib/python2.7/encodings/mac_cyrillic.py", + "usr/lib/python2.7/encodings/palmos.py", + "usr/lib/python2.7/encodings/iso2022_jp_1.pyo", + "usr/lib/python2.7/encodings/cp1254.pyc", + "usr/lib/python2.7/encodings/euc_jp.pyo", + "usr/lib/python2.7/encodings/koi8_r.py", + "usr/lib/python2.7/encodings/cp1251.py", + "usr/lib/python2.7/encodings/shift_jis.py", + "usr/lib/python2.7/encodings/iso8859_8.py", + "usr/lib/python2.7/encodings/mac_greek.py", + "usr/lib/python2.7/encodings/mac_turkish.pyo", + "usr/lib/python2.7/encodings/cp1252.pyc", + "usr/lib/python2.7/encodings/hz.py", + "usr/lib/python2.7/encodings/cp1255.pyo", + "usr/lib/python2.7/encodings/utf_32.pyo", + "usr/lib/python2.7/encodings/utf_7.pyo", + "usr/lib/python2.7/encodings/cp858.pyc", + "usr/lib/python2.7/encodings/big5.pyo", + "usr/lib/python2.7/encodings/cp949.pyo", + "usr/lib/python2.7/encodings/iso2022_kr.pyo", + "usr/lib/python2.7/encodings/iso8859_4.pyo", + "usr/lib/python2.7/encodings/iso8859_13.pyc", + "usr/lib/python2.7/encodings/ascii.py", + "usr/lib/python2.7/encodings/iso8859_5.pyo", + "usr/lib/python2.7/encodings/iso8859_1.pyc", + "usr/lib/python2.7/encodings/shift_jisx0213.pyo", + "usr/lib/python2.7/encodings/utf_32_le.py", + "usr/lib/python2.7/encodings/cp856.pyc", + "usr/lib/python2.7/encodings/cp775.pyc", + "usr/lib/python2.7/encodings/ptcp154.py", + "usr/lib/python2.7/encodings/uu_codec.pyc", + "usr/lib/python2.7/encodings/utf_8.pyo", + "usr/lib/python2.7/encodings/cp865.pyo", + "usr/lib/python2.7/encodings/cp861.pyc", + "usr/lib/python2.7/encodings/idna.pyo", + "usr/lib/python2.7/encodings/hp_roman8.py", + "usr/lib/python2.7/encodings/gbk.py", + "usr/lib/python2.7/encodings/unicode_internal.pyc", + "usr/lib/python2.7/encodings/raw_unicode_escape.pyo", + "usr/lib/python2.7/encodings/utf_16.py", + "usr/lib/python2.7/encodings/koi8_u.pyo", + "usr/lib/python2.7/encodings/cp1140.pyo", + "usr/lib/python2.7/encodings/mac_turkish.py", + "usr/lib/python2.7/encodings/iso2022_jp_2004.py", + "usr/lib/python2.7/encodings/cp500.pyc", + "usr/lib/python2.7/encodings/iso8859_6.pyc", + "usr/lib/python2.7/encodings/mac_roman.pyo", + "usr/lib/python2.7/encodings/euc_kr.py", + "usr/lib/python2.7/encodings/big5.pyc", + "usr/lib/python2.7/encodings/cp866.pyo", + "usr/lib/python2.7/encodings/cp850.pyo", + "usr/lib/python2.7/encodings/string_escape.py", + "usr/lib/python2.7/encodings/__init__.pyo", + "usr/lib/python2.7/encodings/cp1026.py", + "usr/lib/python2.7/encodings/mbcs.py", + "usr/lib/python2.7/encodings/cp864.py", + "usr/lib/python2.7/encodings/unicode_escape.pyo", + "usr/lib/python2.7/encodings/cp855.pyo", + "usr/lib/python2.7/encodings/latin_1.pyc", + "usr/lib/python2.7/encodings/cp863.pyo", + "usr/lib/python2.7/encodings/cp852.pyo", + "usr/lib/python2.7/encodings/undefined.py", + "usr/lib/python2.7/encodings/cp860.py", + "usr/lib/python2.7/encodings/cp1258.pyo", + "usr/lib/python2.7/encodings/cp857.py", + "usr/lib/python2.7/encodings/cp1255.py", + "usr/lib/python2.7/encodings/cp932.pyo", + "usr/lib/python2.7/encodings/euc_jp.pyc", + "usr/lib/python2.7/encodings/koi8_r.pyo", + "usr/lib/python2.7/encodings/cp1256.pyc", + "usr/lib/python2.7/encodings/utf_32_be.pyo", + "usr/lib/python2.7/encodings/cp1257.pyc", + "usr/lib/python2.7/encodings/johab.pyc", + "usr/lib/python2.7/encodings/mbcs.pyo", + "usr/lib/python2.7/encodings/tis_620.py", + "usr/lib/python2.7/encodings/iso8859_15.pyc", + "usr/lib/python2.7/encodings/cp949.py", + "usr/lib/python2.7/encodings/base64_codec.pyo", + "usr/lib/python2.7/encodings/cp1250.pyo", + "usr/lib/python2.7/encodings/quopri_codec.pyo", + "usr/lib/python2.7/encodings/mac_roman.pyc", + "usr/lib/python2.7/encodings/iso8859_10.pyc", + "usr/lib/python2.7/encodings/cp856.pyo", + "usr/lib/python2.7/encodings/ascii.pyo", + "usr/lib/python2.7/encodings/cp852.pyc", + "usr/lib/python2.7/encodings/idna.py", + "usr/lib/python2.7/encodings/cp1026.pyo", + "usr/lib/python2.7/encodings/cp500.py", + "usr/lib/python2.7/encodings/cp874.py", + "usr/lib/python2.7/encodings/cp1257.py", + "usr/lib/python2.7/encodings/mac_latin2.pyo", + "usr/lib/python2.7/encodings/idna.pyc", + "usr/lib/python2.7/encodings/iso8859_5.py", + "usr/lib/python2.7/encodings/utf_32_le.pyc", + "usr/lib/python2.7/encodings/cp850.py", + "usr/lib/python2.7/encodings/cp855.py", + "usr/lib/python2.7/encodings/mac_arabic.py", + "usr/lib/python2.7/encodings/shift_jis.pyc", + "usr/lib/python2.7/encodings/iso2022_jp.pyo", + "usr/lib/python2.7/encodings/mac_centeuro.py", + "usr/lib/python2.7/encodings/iso8859_8.pyc", + "usr/lib/python2.7/encodings/iso8859_3.pyc", + "usr/lib/python2.7/encodings/unicode_escape.pyc", + "usr/lib/python2.7/encodings/iso8859_7.pyc", + "usr/lib/python2.7/encodings/cp860.pyc", + "usr/lib/python2.7/encodings/mac_croatian.pyo", + "usr/lib/python2.7/encodings/euc_kr.pyc", + "usr/lib/python2.7/encodings/mac_iceland.pyo", + "usr/lib/python2.7/encodings/gb2312.py", + "usr/lib/python2.7/encodings/cp950.pyo", + "usr/lib/python2.7/encodings/iso2022_jp_ext.py", + "usr/lib/python2.7/encodings/mac_cyrillic.pyc", + "usr/lib/python2.7/encodings/cp500.pyo", + "usr/lib/python2.7/encodings/palmos.pyc", + "usr/lib/python2.7/ctypes/__init__.pyc", + "usr/lib/python2.7/ctypes/util.pyo", + "usr/lib/python2.7/ctypes/__init__.py", + "usr/lib/python2.7/ctypes/util.py", + "usr/lib/python2.7/ctypes/wintypes.py", + "usr/lib/python2.7/ctypes/_endian.py", + "usr/lib/python2.7/ctypes/util.pyc", + "usr/lib/python2.7/ctypes/_endian.pyo", + "usr/lib/python2.7/ctypes/__init__.pyo", + "usr/lib/python2.7/ctypes/_endian.pyc", + "usr/lib/python2.7/ctypes/wintypes.pyo", + "usr/lib/python2.7/ctypes/wintypes.pyc", + "usr/lib/python2.7/ctypes/macholib/dylib.pyo", + "usr/lib/python2.7/ctypes/macholib/__init__.pyc", + "usr/lib/python2.7/ctypes/macholib/framework.pyc", + "usr/lib/python2.7/ctypes/macholib/__init__.py", + "usr/lib/python2.7/ctypes/macholib/dyld.pyc", + "usr/lib/python2.7/ctypes/macholib/dyld.pyo", + "usr/lib/python2.7/ctypes/macholib/framework.py", + "usr/lib/python2.7/ctypes/macholib/README.ctypes", + "usr/lib/python2.7/ctypes/macholib/dylib.pyc", + "usr/lib/python2.7/ctypes/macholib/__init__.pyo", + "usr/lib/python2.7/ctypes/macholib/dylib.py", + "usr/lib/python2.7/ctypes/macholib/dyld.py", + "usr/lib/python2.7/ctypes/macholib/fetch_macholib.bat", + "usr/lib/python2.7/ctypes/macholib/fetch_macholib", + "usr/lib/python2.7/ctypes/macholib/framework.pyo", + "usr/include/python2.7/pyconfig.h" + ] + }, + { + "ID": "readline@7.0.003-r0", + "Name": "readline", + "Identifier": { + "PURL": "pkg:apk/alpine/readline@7.0.003-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "7.0.003-r0", + "Arch": "x86_64", + "SrcName": "readline", + "SrcVersion": "7.0.003-r0", + "Licenses": [ + "GPL-3.0" + ], + "DependsOn": [ + "musl@1.1.18-r3", + "ncurses-libs@6.0_p20171125-r1" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:6796e379c3acec5edda98243e506a363d54c2854", + "InstalledFiles": [ + "usr/lib/libreadline.so.7.0", + "usr/lib/libreadline.so.7" + ] + }, + { + "ID": "scanelf@1.2.2-r1", + "Name": "scanelf", + "Identifier": { + "PURL": "pkg:apk/alpine/scanelf@1.2.2-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.2.2-r1", + "Arch": "x86_64", + "SrcName": "pax-utils", + "SrcVersion": "1.2.2-r1", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:673d3ba729ab198f0120e3d1f37e993ee2f93c88", + "InstalledFiles": [ + "usr/bin/scanelf" + ] + }, + { + "ID": "serf@1.3.9-r3", + "Name": "serf", + "Identifier": { + "PURL": "pkg:apk/alpine/serf@1.3.9-r3?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.3.9-r3", + "Arch": "x86_64", + "SrcName": "serf", + "SrcVersion": "1.3.9-r3", + "Licenses": [ + "ASL2.0" + ], + "DependsOn": [ + "apr-util@1.6.1-r1", + "apr@1.6.3-r0", + "libressl2.6-libcrypto@2.6.5-r0", + "libressl2.6-libssl@2.6.5-r0", + "musl@1.1.18-r3", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:7f3acf89cc921a20b621f297018caed2e939d873", + "InstalledFiles": [ + "usr/lib/libserf-1.so.1", + "usr/lib/libserf-1.so.1.3.0" + ] + }, + { + "ID": "sqlite-libs@3.21.0-r1", + "Name": "sqlite-libs", + "Identifier": { + "PURL": "pkg:apk/alpine/sqlite-libs@3.21.0-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "3.21.0-r1", + "Arch": "x86_64", + "SrcName": "sqlite", + "SrcVersion": "3.21.0-r1", + "Licenses": [ + "custom" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:963204681a8d8922da42739756caca5d0a4290f0", + "InstalledFiles": [ + "usr/lib/libsqlite3.so.0", + "usr/lib/libsqlite3.so.0.8.6" + ] + }, + { + "ID": "ssl_client@1.27.2-r11", + "Name": "ssl_client", + "Identifier": { + "PURL": "pkg:apk/alpine/ssl_client@1.27.2-r11?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.27.2-r11", + "Arch": "x86_64", + "SrcName": "busybox", + "SrcVersion": "1.27.2-r11", + "Licenses": [ + "GPL-2.0" + ], + "DependsOn": [ + "libressl2.6-libtls@2.6.5-r0", + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:be991cfc32528659450b6dd21e855d638b5865a6", + "InstalledFiles": [ + "usr/bin/ssl_client" + ] + }, + { + "ID": "subversion@1.9.7-r0", + "Name": "subversion", + "Identifier": { + "PURL": "pkg:apk/alpine/subversion@1.9.7-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.9.7-r0", + "Arch": "x86_64", + "SrcName": "subversion", + "SrcVersion": "1.9.7-r0", + "Licenses": [ + "Apache-2.0", + "BSD-3-Clause" + ], + "DependsOn": [ + "apr-util@1.6.1-r1", + "apr@1.6.3-r0", + "busybox@1.27.2-r11", + "libsasl@2.1.26-r11", + "musl@1.1.18-r3", + "subversion-libs@1.9.7-r0" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:b3f0936eb24b3f61099cedfac29195dc04a08362", + "InstalledFiles": [ + "etc/init.d/svnserve", + "etc/conf.d/svnserve", + "usr/bin/svn", + "usr/bin/svnbench", + "usr/bin/svnadmin", + "usr/bin/svnversion", + "usr/bin/svnrdump", + "usr/bin/svnlook", + "usr/bin/svndumpfilter", + "usr/bin/svnsync", + "usr/bin/svnmucc", + "usr/bin/svnserve", + "usr/bin/svnfsfs", + "usr/share/pkgconfig/libsvn_fs_fs.pc", + "usr/share/pkgconfig/libsvn_diff.pc", + "usr/share/pkgconfig/libsvn_fs.pc", + "usr/share/pkgconfig/libsvn_fs_util.pc", + "usr/share/pkgconfig/libsvn_ra_svn.pc", + "usr/share/pkgconfig/libsvn_delta.pc", + "usr/share/pkgconfig/libsvn_ra_serf.pc", + "usr/share/pkgconfig/libsvn_fs_base.pc", + "usr/share/pkgconfig/libsvn_ra.pc", + "usr/share/pkgconfig/libsvn_client.pc", + "usr/share/pkgconfig/libsvn_fs_x.pc", + "usr/share/pkgconfig/libsvn_repos.pc", + "usr/share/pkgconfig/libsvn_subr.pc", + "usr/share/pkgconfig/libsvn_ra_local.pc", + "usr/share/pkgconfig/libsvn_wc.pc" + ] + }, + { + "ID": "subversion-libs@1.9.7-r0", + "Name": "subversion-libs", + "Identifier": { + "PURL": "pkg:apk/alpine/subversion-libs@1.9.7-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.9.7-r0", + "Arch": "x86_64", + "SrcName": "subversion", + "SrcVersion": "1.9.7-r0", + "Licenses": [ + "Apache-2.0", + "BSD-3-Clause" + ], + "DependsOn": [ + "apr-util@1.6.1-r1", + "apr@1.6.3-r0", + "db@5.3.28-r0", + "expat@2.2.5-r0", + "libsasl@2.1.26-r11", + "musl@1.1.18-r3", + "serf@1.3.9-r3", + "sqlite-libs@3.21.0-r1", + "zlib@1.2.11-r1" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:159042d5715f6e501e73569ffd9b86530758b6d4", + "InstalledFiles": [ + "usr/lib/libsvn_client-1.so.0.0.0", + "usr/lib/libsvn_ra_local-1.so.0.0.0", + "usr/lib/libsvn_fs_util-1.so.0", + "usr/lib/libsvn_ra_svn-1.so.0.0.0", + "usr/lib/libsvn_ra_local-1.so.0", + "usr/lib/libsvn_fs_x-1.so.0", + "usr/lib/libsvn_fs-1.so.0", + "usr/lib/libsvn_repos-1.so.0.0.0", + "usr/lib/libsvn_fs_fs-1.so.0", + "usr/lib/libsvn_subr-1.so.0", + "usr/lib/libsvn_ra_serf-1.so.0.0.0", + "usr/lib/libsvn_delta-1.so.0", + "usr/lib/libsvn_wc-1.so.0.0.0", + "usr/lib/libsvn_subr-1.so.0.0.0", + "usr/lib/libsvn_ra_serf-1.so.0", + "usr/lib/libsvn_diff-1.so.0", + "usr/lib/libsvn_fs_fs-1.so.0.0.0", + "usr/lib/libsvn_diff-1.so.0.0.0", + "usr/lib/libsvn_fs_base-1.so.0.0.0", + "usr/lib/libsvn_ra-1.so.0.0.0", + "usr/lib/libsvn_client-1.so.0", + "usr/lib/libsvn_ra-1.so.0", + "usr/lib/libsvn_delta-1.so.0.0.0", + "usr/lib/libsvn_fs_util-1.so.0.0.0", + "usr/lib/libsvn_fs_x-1.so.0.0.0", + "usr/lib/libsvn_repos-1.so.0", + "usr/lib/libsvn_wc-1.so.0", + "usr/lib/libsvn_fs_base-1.so.0", + "usr/lib/libsvn_fs-1.so.0.0.0", + "usr/lib/libsvn_ra_svn-1.so.0" + ] + }, + { + "ID": "tar@1.29-r1", + "Name": "tar", + "Identifier": { + "PURL": "pkg:apk/alpine/tar@1.29-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.29-r1", + "Arch": "x86_64", + "SrcName": "tar", + "SrcVersion": "1.29-r1", + "Licenses": [ + "GPL-3.0" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:88777455d910410652665cec0149a02db3584d6dc26e306788a3532d480b00ae", + "DiffID": "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33" + }, + "Digest": "sha1:32ed0dea10cb4fc68f32e7466513230e09679d09", + "InstalledFiles": [ + "bin/tar", + "usr/libexec/rmt", + "usr/bin/tar" + ] + }, + { + "ID": "tini@0.16.1-r0", + "Name": "tini", + "Identifier": { + "PURL": "pkg:apk/alpine/tini@0.16.1-r0?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "0.16.1-r0", + "Arch": "x86_64", + "SrcName": "tini", + "SrcVersion": "0.16.1-r0", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c191915691a422a1b0230c9010165ff655204a9fd95e3b43151132bcb237826b", + "DiffID": "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4" + }, + "Digest": "sha1:f88c6efbdbb698ba8a2db15ee88fa2b474002fb3", + "InstalledFiles": [ + "sbin/tini" + ] + }, + { + "ID": "xz@5.2.3-r1", + "Name": "xz", + "Identifier": { + "PURL": "pkg:apk/alpine/xz@5.2.3-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "5.2.3-r1", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.2.3-r1", + "Licenses": [ + "custom" + ], + "DependsOn": [ + "musl@1.1.18-r3", + "xz-libs@5.2.3-r1" + ], + "Layer": { + "Digest": "sha256:88777455d910410652665cec0149a02db3584d6dc26e306788a3532d480b00ae", + "DiffID": "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33" + }, + "Digest": "sha1:fe745c838ec20816bc5c4d9e4fceb01dc866caac", + "InstalledFiles": [ + "usr/bin/lzdiff", + "usr/bin/xzegrep", + "usr/bin/lzless", + "usr/bin/xzdiff", + "usr/bin/lzgrep", + "usr/bin/xzless", + "usr/bin/xzmore", + "usr/bin/lzcat", + "usr/bin/xz", + "usr/bin/xzfgrep", + "usr/bin/lzcmp", + "usr/bin/unlzma", + "usr/bin/lzfgrep", + "usr/bin/lzmainfo", + "usr/bin/lzegrep", + "usr/bin/unxz", + "usr/bin/xzgrep", + "usr/bin/xzcmp", + "usr/bin/lzmadec", + "usr/bin/xzcat", + "usr/bin/xzdec", + "usr/bin/lzma", + "usr/bin/lzmore" + ] + }, + { + "ID": "xz-libs@5.2.3-r1", + "Name": "xz-libs", + "Identifier": { + "PURL": "pkg:apk/alpine/xz-libs@5.2.3-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "5.2.3-r1", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.2.3-r1", + "Licenses": [ + "custom" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:88777455d910410652665cec0149a02db3584d6dc26e306788a3532d480b00ae", + "DiffID": "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33" + }, + "Digest": "sha1:7aeb306958a1cb330a7bb8f7ce24ab6fe85c8b1f", + "InstalledFiles": [ + "usr/lib/liblzma.so.5", + "usr/lib/liblzma.so.5.2.3" + ] + }, + { + "ID": "zlib@1.2.11-r1", + "Name": "zlib", + "Identifier": { + "PURL": "pkg:apk/alpine/zlib@1.2.11-r1?arch=x86_64\u0026distro=3.7.1" + }, + "Version": "1.2.11-r1", + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.2.11-r1", + "Licenses": [ + "Zlib" + ], + "DependsOn": [ + "musl@1.1.18-r3" + ], + "Layer": { + "Digest": "sha256:c67f3896b22c1378881cbbb9c9d1edfe881fd07f713371835ef46d93c649684d", + "DiffID": "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888" + }, + "Digest": "sha1:4869b83137ea8eab47c9f0b0e1085a5cc6ae2bb3", + "InstalledFiles": [ + "lib/libz.so.1.2.11", + "lib/libz.so.1" + ] + } +] \ No newline at end of file diff --git a/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden b/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden new file mode 100644 index 000000000000..fdd72301280c --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden @@ -0,0 +1,3633 @@ +[ + { + "Type": "bundler", + "FilePath": "ruby-app/Gemfile.lock", + "Libraries": [ + { + "ID": "actioncable@5.2.3", + "Name": "actioncable", + "Identifier": { + "PURL": "pkg:gem/actioncable@5.2.3" + }, + "Version": "5.2.3", + "Indirect": true, + "DependsOn": [ + "actionpack@5.2.3", + "nio4r@2.3.1", + "websocket-driver@0.7.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 4, + "EndLine": 4 + } + ] + }, + { + "ID": "actionmailer@5.2.3", + "Name": "actionmailer", + "Identifier": { + "PURL": "pkg:gem/actionmailer@5.2.3" + }, + "Version": "5.2.3", + "Indirect": true, + "DependsOn": [ + "actionpack@5.2.3", + "actionview@5.2.3", + "activejob@5.2.3", + "mail@2.7.1", + "rails-dom-testing@2.0.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 8, + "EndLine": 8 + } + ] + }, + { + "ID": "actionpack@5.2.3", + "Name": "actionpack", + "Identifier": { + "PURL": "pkg:gem/actionpack@5.2.3" + }, + "Version": "5.2.3", + "Indirect": true, + "DependsOn": [ + "actionview@5.2.3", + "activesupport@5.2.3", + "rack-test@1.1.0", + "rack@2.0.7", + "rails-dom-testing@2.0.3", + "rails-html-sanitizer@1.0.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 14, + "EndLine": 14 + } + ] + }, + { + "ID": "actionview@5.2.3", + "Name": "actionview", + "Identifier": { + "PURL": "pkg:gem/actionview@5.2.3" + }, + "Version": "5.2.3", + "Indirect": true, + "DependsOn": [ + "activesupport@5.2.3", + "builder@3.2.3", + "erubi@1.8.0", + "rails-dom-testing@2.0.3", + "rails-html-sanitizer@1.0.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 21, + "EndLine": 21 + } + ] + }, + { + "ID": "activejob@5.2.3", + "Name": "activejob", + "Identifier": { + "PURL": "pkg:gem/activejob@5.2.3" + }, + "Version": "5.2.3", + "Indirect": true, + "DependsOn": [ + "activesupport@5.2.3", + "globalid@0.4.2" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 27, + "EndLine": 27 + } + ] + }, + { + "ID": "activemodel@5.2.3", + "Name": "activemodel", + "Identifier": { + "PURL": "pkg:gem/activemodel@5.2.3" + }, + "Version": "5.2.3", + "Indirect": true, + "DependsOn": [ + "activesupport@5.2.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 30, + "EndLine": 30 + } + ] + }, + { + "ID": "activerecord@5.2.3", + "Name": "activerecord", + "Identifier": { + "PURL": "pkg:gem/activerecord@5.2.3" + }, + "Version": "5.2.3", + "Indirect": true, + "DependsOn": [ + "activemodel@5.2.3", + "activesupport@5.2.3", + "arel@9.0.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 32, + "EndLine": 32 + } + ] + }, + { + "ID": "activestorage@5.2.3", + "Name": "activestorage", + "Identifier": { + "PURL": "pkg:gem/activestorage@5.2.3" + }, + "Version": "5.2.3", + "Indirect": true, + "DependsOn": [ + "actionpack@5.2.3", + "activerecord@5.2.3", + "marcel@0.3.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 36, + "EndLine": 36 + } + ] + }, + { + "ID": "activesupport@5.2.3", + "Name": "activesupport", + "Identifier": { + "PURL": "pkg:gem/activesupport@5.2.3" + }, + "Version": "5.2.3", + "Indirect": true, + "DependsOn": [ + "concurrent-ruby@1.1.5", + "i18n@1.6.0", + "minitest@5.11.3", + "tzinfo@1.2.5" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 40, + "EndLine": 40 + } + ] + }, + { + "ID": "arel@9.0.0", + "Name": "arel", + "Identifier": { + "PURL": "pkg:gem/arel@9.0.0" + }, + "Version": "9.0.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 45, + "EndLine": 45 + } + ] + }, + { + "ID": "ast@2.4.0", + "Name": "ast", + "Identifier": { + "PURL": "pkg:gem/ast@2.4.0" + }, + "Version": "2.4.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 46, + "EndLine": 46 + } + ] + }, + { + "ID": "builder@3.2.3", + "Name": "builder", + "Identifier": { + "PURL": "pkg:gem/builder@3.2.3" + }, + "Version": "3.2.3", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 47, + "EndLine": 47 + } + ] + }, + { + "ID": "coderay@1.1.2", + "Name": "coderay", + "Identifier": { + "PURL": "pkg:gem/coderay@1.1.2" + }, + "Version": "1.1.2", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 48, + "EndLine": 48 + } + ] + }, + { + "ID": "concurrent-ruby@1.1.5", + "Name": "concurrent-ruby", + "Identifier": { + "PURL": "pkg:gem/concurrent-ruby@1.1.5" + }, + "Version": "1.1.5", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 49, + "EndLine": 49 + } + ] + }, + { + "ID": "crass@1.0.4", + "Name": "crass", + "Identifier": { + "PURL": "pkg:gem/crass@1.0.4" + }, + "Version": "1.0.4", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 50, + "EndLine": 50 + } + ] + }, + { + "ID": "dotenv@2.7.2", + "Name": "dotenv", + "Identifier": { + "PURL": "pkg:gem/dotenv@2.7.2" + }, + "Version": "2.7.2", + "Layer": {}, + "Locations": [ + { + "StartLine": 51, + "EndLine": 51 + } + ] + }, + { + "ID": "erubi@1.8.0", + "Name": "erubi", + "Identifier": { + "PURL": "pkg:gem/erubi@1.8.0" + }, + "Version": "1.8.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 52, + "EndLine": 52 + } + ] + }, + { + "ID": "faker@1.9.3", + "Name": "faker", + "Identifier": { + "PURL": "pkg:gem/faker@1.9.3" + }, + "Version": "1.9.3", + "DependsOn": [ + "i18n@1.6.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 53, + "EndLine": 53 + } + ] + }, + { + "ID": "globalid@0.4.2", + "Name": "globalid", + "Identifier": { + "PURL": "pkg:gem/globalid@0.4.2" + }, + "Version": "0.4.2", + "Indirect": true, + "DependsOn": [ + "activesupport@5.2.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 55, + "EndLine": 55 + } + ] + }, + { + "ID": "i18n@1.6.0", + "Name": "i18n", + "Identifier": { + "PURL": "pkg:gem/i18n@1.6.0" + }, + "Version": "1.6.0", + "Indirect": true, + "DependsOn": [ + "concurrent-ruby@1.1.5" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 57, + "EndLine": 57 + } + ] + }, + { + "ID": "jaro_winkler@1.5.2", + "Name": "jaro_winkler", + "Identifier": { + "PURL": "pkg:gem/jaro_winkler@1.5.2" + }, + "Version": "1.5.2", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 59, + "EndLine": 59 + } + ] + }, + { + "ID": "json@2.2.0", + "Name": "json", + "Identifier": { + "PURL": "pkg:gem/json@2.2.0" + }, + "Version": "2.2.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 60, + "EndLine": 60 + } + ] + }, + { + "ID": "loofah@2.2.3", + "Name": "loofah", + "Identifier": { + "PURL": "pkg:gem/loofah@2.2.3" + }, + "Version": "2.2.3", + "Indirect": true, + "DependsOn": [ + "crass@1.0.4", + "nokogiri@1.10.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 61, + "EndLine": 61 + } + ] + }, + { + "ID": "mail@2.7.1", + "Name": "mail", + "Identifier": { + "PURL": "pkg:gem/mail@2.7.1" + }, + "Version": "2.7.1", + "Indirect": true, + "DependsOn": [ + "mini_mime@1.0.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 64, + "EndLine": 64 + } + ] + }, + { + "ID": "marcel@0.3.3", + "Name": "marcel", + "Identifier": { + "PURL": "pkg:gem/marcel@0.3.3" + }, + "Version": "0.3.3", + "Indirect": true, + "DependsOn": [ + "mimemagic@0.3.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 66, + "EndLine": 66 + } + ] + }, + { + "ID": "method_source@0.9.2", + "Name": "method_source", + "Identifier": { + "PURL": "pkg:gem/method_source@0.9.2" + }, + "Version": "0.9.2", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 68, + "EndLine": 68 + } + ] + }, + { + "ID": "mimemagic@0.3.3", + "Name": "mimemagic", + "Identifier": { + "PURL": "pkg:gem/mimemagic@0.3.3" + }, + "Version": "0.3.3", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 69, + "EndLine": 69 + } + ] + }, + { + "ID": "mini_mime@1.0.1", + "Name": "mini_mime", + "Identifier": { + "PURL": "pkg:gem/mini_mime@1.0.1" + }, + "Version": "1.0.1", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 70, + "EndLine": 70 + } + ] + }, + { + "ID": "mini_portile2@2.4.0", + "Name": "mini_portile2", + "Identifier": { + "PURL": "pkg:gem/mini_portile2@2.4.0" + }, + "Version": "2.4.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 71, + "EndLine": 71 + } + ] + }, + { + "ID": "minitest@5.11.3", + "Name": "minitest", + "Identifier": { + "PURL": "pkg:gem/minitest@5.11.3" + }, + "Version": "5.11.3", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 72, + "EndLine": 72 + } + ] + }, + { + "ID": "nio4r@2.3.1", + "Name": "nio4r", + "Identifier": { + "PURL": "pkg:gem/nio4r@2.3.1" + }, + "Version": "2.3.1", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 73, + "EndLine": 73 + } + ] + }, + { + "ID": "nokogiri@1.10.3", + "Name": "nokogiri", + "Identifier": { + "PURL": "pkg:gem/nokogiri@1.10.3" + }, + "Version": "1.10.3", + "Indirect": true, + "DependsOn": [ + "mini_portile2@2.4.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 74, + "EndLine": 74 + } + ] + }, + { + "ID": "parallel@1.17.0", + "Name": "parallel", + "Identifier": { + "PURL": "pkg:gem/parallel@1.17.0" + }, + "Version": "1.17.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 76, + "EndLine": 76 + } + ] + }, + { + "ID": "parser@2.6.3.0", + "Name": "parser", + "Identifier": { + "PURL": "pkg:gem/parser@2.6.3.0" + }, + "Version": "2.6.3.0", + "Indirect": true, + "DependsOn": [ + "ast@2.4.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 77, + "EndLine": 77 + } + ] + }, + { + "ID": "pry@0.12.2", + "Name": "pry", + "Identifier": { + "PURL": "pkg:gem/pry@0.12.2" + }, + "Version": "0.12.2", + "DependsOn": [ + "coderay@1.1.2", + "method_source@0.9.2" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 79, + "EndLine": 79 + } + ] + }, + { + "ID": "psych@3.1.0", + "Name": "psych", + "Identifier": { + "PURL": "pkg:gem/psych@3.1.0" + }, + "Version": "3.1.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 82, + "EndLine": 82 + } + ] + }, + { + "ID": "rack@2.0.7", + "Name": "rack", + "Identifier": { + "PURL": "pkg:gem/rack@2.0.7" + }, + "Version": "2.0.7", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 83, + "EndLine": 83 + } + ] + }, + { + "ID": "rack-test@1.1.0", + "Name": "rack-test", + "Identifier": { + "PURL": "pkg:gem/rack-test@1.1.0" + }, + "Version": "1.1.0", + "Indirect": true, + "DependsOn": [ + "rack@2.0.7" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 84, + "EndLine": 84 + } + ] + }, + { + "ID": "rails@5.2.0", + "Name": "rails", + "Identifier": { + "PURL": "pkg:gem/rails@5.2.0" + }, + "Version": "5.2.0", + "DependsOn": [ + "actioncable@5.2.3", + "actionmailer@5.2.3", + "actionpack@5.2.3", + "actionview@5.2.3", + "activejob@5.2.3", + "activemodel@5.2.3", + "activerecord@5.2.3", + "activestorage@5.2.3", + "activesupport@5.2.3", + "railties@5.2.3", + "sprockets-rails@3.2.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 86, + "EndLine": 86 + } + ] + }, + { + "ID": "rails-dom-testing@2.0.3", + "Name": "rails-dom-testing", + "Identifier": { + "PURL": "pkg:gem/rails-dom-testing@2.0.3" + }, + "Version": "2.0.3", + "Indirect": true, + "DependsOn": [ + "activesupport@5.2.3", + "nokogiri@1.10.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 99, + "EndLine": 99 + } + ] + }, + { + "ID": "rails-html-sanitizer@1.0.3", + "Name": "rails-html-sanitizer", + "Identifier": { + "PURL": "pkg:gem/rails-html-sanitizer@1.0.3" + }, + "Version": "1.0.3", + "Indirect": true, + "DependsOn": [ + "loofah@2.2.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 102, + "EndLine": 102 + } + ] + }, + { + "ID": "railties@5.2.3", + "Name": "railties", + "Identifier": { + "PURL": "pkg:gem/railties@5.2.3" + }, + "Version": "5.2.3", + "Indirect": true, + "DependsOn": [ + "actionpack@5.2.3", + "activesupport@5.2.3", + "method_source@0.9.2", + "rake@12.3.2", + "thor@0.20.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 104, + "EndLine": 104 + } + ] + }, + { + "ID": "rainbow@3.0.0", + "Name": "rainbow", + "Identifier": { + "PURL": "pkg:gem/rainbow@3.0.0" + }, + "Version": "3.0.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 110, + "EndLine": 110 + } + ] + }, + { + "ID": "rake@12.3.2", + "Name": "rake", + "Identifier": { + "PURL": "pkg:gem/rake@12.3.2" + }, + "Version": "12.3.2", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 111, + "EndLine": 111 + } + ] + }, + { + "ID": "rubocop@0.67.2", + "Name": "rubocop", + "Identifier": { + "PURL": "pkg:gem/rubocop@0.67.2" + }, + "Version": "0.67.2", + "DependsOn": [ + "jaro_winkler@1.5.2", + "parallel@1.17.0", + "parser@2.6.3.0", + "psych@3.1.0", + "rainbow@3.0.0", + "ruby-progressbar@1.10.0", + "unicode-display_width@1.5.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 112, + "EndLine": 112 + } + ] + }, + { + "ID": "ruby-progressbar@1.10.0", + "Name": "ruby-progressbar", + "Identifier": { + "PURL": "pkg:gem/ruby-progressbar@1.10.0" + }, + "Version": "1.10.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 120, + "EndLine": 120 + } + ] + }, + { + "ID": "sprockets@3.7.2", + "Name": "sprockets", + "Identifier": { + "PURL": "pkg:gem/sprockets@3.7.2" + }, + "Version": "3.7.2", + "Indirect": true, + "DependsOn": [ + "concurrent-ruby@1.1.5", + "rack@2.0.7" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 121, + "EndLine": 121 + } + ] + }, + { + "ID": "sprockets-rails@3.2.1", + "Name": "sprockets-rails", + "Identifier": { + "PURL": "pkg:gem/sprockets-rails@3.2.1" + }, + "Version": "3.2.1", + "Indirect": true, + "DependsOn": [ + "actionpack@5.2.3", + "activesupport@5.2.3", + "sprockets@3.7.2" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 124, + "EndLine": 124 + } + ] + }, + { + "ID": "thor@0.20.3", + "Name": "thor", + "Identifier": { + "PURL": "pkg:gem/thor@0.20.3" + }, + "Version": "0.20.3", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 128, + "EndLine": 128 + } + ] + }, + { + "ID": "thread_safe@0.3.6", + "Name": "thread_safe", + "Identifier": { + "PURL": "pkg:gem/thread_safe@0.3.6" + }, + "Version": "0.3.6", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 129, + "EndLine": 129 + } + ] + }, + { + "ID": "tzinfo@1.2.5", + "Name": "tzinfo", + "Identifier": { + "PURL": "pkg:gem/tzinfo@1.2.5" + }, + "Version": "1.2.5", + "Indirect": true, + "DependsOn": [ + "thread_safe@0.3.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 130, + "EndLine": 130 + } + ] + }, + { + "ID": "unicode-display_width@1.5.0", + "Name": "unicode-display_width", + "Identifier": { + "PURL": "pkg:gem/unicode-display_width@1.5.0" + }, + "Version": "1.5.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 132, + "EndLine": 132 + } + ] + }, + { + "ID": "websocket-driver@0.7.0", + "Name": "websocket-driver", + "Identifier": { + "PURL": "pkg:gem/websocket-driver@0.7.0" + }, + "Version": "0.7.0", + "Indirect": true, + "DependsOn": [ + "websocket-extensions@0.1.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 133, + "EndLine": 133 + } + ] + }, + { + "ID": "websocket-extensions@0.1.3", + "Name": "websocket-extensions", + "Identifier": { + "PURL": "pkg:gem/websocket-extensions@0.1.3" + }, + "Version": "0.1.3", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 135, + "EndLine": 135 + } + ] + } + ] + }, + { + "Type": "cargo", + "FilePath": "rust-app/Cargo.lock", + "Libraries": [ + { + "ID": "ammonia@1.9.0", + "Name": "ammonia", + "Identifier": { + "PURL": "pkg:cargo/ammonia@1.9.0" + }, + "Version": "1.9.0", + "DependsOn": [ + "html5ever@0.23.0", + "lazy_static@1.3.0", + "maplit@1.0.1", + "matches@0.1.8", + "tendril@0.4.1", + "url@1.7.2" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 2, + "EndLine": 13 + } + ] + }, + { + "ID": "autocfg@0.1.2", + "Name": "autocfg", + "Identifier": { + "PURL": "pkg:cargo/autocfg@0.1.2" + }, + "Version": "0.1.2", + "Layer": {}, + "Locations": [ + { + "StartLine": 15, + "EndLine": 18 + } + ] + }, + { + "ID": "bitflags@0.7.0", + "Name": "bitflags", + "Identifier": { + "PURL": "pkg:cargo/bitflags@0.7.0" + }, + "Version": "0.7.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 20, + "EndLine": 23 + } + ] + }, + { + "ID": "bitflags@1.0.4", + "Name": "bitflags", + "Identifier": { + "PURL": "pkg:cargo/bitflags@1.0.4" + }, + "Version": "1.0.4", + "Layer": {}, + "Locations": [ + { + "StartLine": 25, + "EndLine": 28 + } + ] + }, + { + "ID": "cfg-if@0.1.7", + "Name": "cfg-if", + "Identifier": { + "PURL": "pkg:cargo/cfg-if@0.1.7" + }, + "Version": "0.1.7", + "Layer": {}, + "Locations": [ + { + "StartLine": 30, + "EndLine": 33 + } + ] + }, + { + "ID": "cloudabi@0.0.3", + "Name": "cloudabi", + "Identifier": { + "PURL": "pkg:cargo/cloudabi@0.0.3" + }, + "Version": "0.0.3", + "DependsOn": [ + "bitflags@1.0.4" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 35, + "EndLine": 41 + } + ] + }, + { + "ID": "fuchsia-cprng@0.1.1", + "Name": "fuchsia-cprng", + "Identifier": { + "PURL": "pkg:cargo/fuchsia-cprng@0.1.1" + }, + "Version": "0.1.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 43, + "EndLine": 46 + } + ] + }, + { + "ID": "futf@0.1.4", + "Name": "futf", + "Identifier": { + "PURL": "pkg:cargo/futf@0.1.4" + }, + "Version": "0.1.4", + "DependsOn": [ + "mac@0.1.1", + "new_debug_unreachable@1.0.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 48, + "EndLine": 55 + } + ] + }, + { + "ID": "gdi32-sys@0.2.0", + "Name": "gdi32-sys", + "Identifier": { + "PURL": "pkg:cargo/gdi32-sys@0.2.0" + }, + "Version": "0.2.0", + "DependsOn": [ + "winapi-build@0.1.1", + "winapi@0.2.8" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 57, + "EndLine": 64 + } + ] + }, + { + "ID": "html5ever@0.23.0", + "Name": "html5ever", + "Identifier": { + "PURL": "pkg:cargo/html5ever@0.23.0" + }, + "Version": "0.23.0", + "DependsOn": [ + "log@0.4.6", + "mac@0.1.1", + "markup5ever@0.8.1", + "proc-macro2@0.4.30", + "quote@0.6.12", + "syn@0.15.34" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 66, + "EndLine": 77 + } + ] + }, + { + "ID": "idna@0.1.5", + "Name": "idna", + "Identifier": { + "PURL": "pkg:cargo/idna@0.1.5" + }, + "Version": "0.1.5", + "DependsOn": [ + "matches@0.1.8", + "unicode-bidi@0.3.4", + "unicode-normalization@0.1.8" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 79, + "EndLine": 87 + } + ] + }, + { + "ID": "itoa@0.4.4", + "Name": "itoa", + "Identifier": { + "PURL": "pkg:cargo/itoa@0.4.4" + }, + "Version": "0.4.4", + "Layer": {}, + "Locations": [ + { + "StartLine": 89, + "EndLine": 92 + } + ] + }, + { + "ID": "kernel32-sys@0.2.2", + "Name": "kernel32-sys", + "Identifier": { + "PURL": "pkg:cargo/kernel32-sys@0.2.2" + }, + "Version": "0.2.2", + "DependsOn": [ + "winapi-build@0.1.1", + "winapi@0.2.8" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 94, + "EndLine": 101 + } + ] + }, + { + "ID": "lazy_static@0.2.11", + "Name": "lazy_static", + "Identifier": { + "PURL": "pkg:cargo/lazy_static@0.2.11" + }, + "Version": "0.2.11", + "Layer": {}, + "Locations": [ + { + "StartLine": 103, + "EndLine": 106 + } + ] + }, + { + "ID": "lazy_static@1.3.0", + "Name": "lazy_static", + "Identifier": { + "PURL": "pkg:cargo/lazy_static@1.3.0" + }, + "Version": "1.3.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 108, + "EndLine": 111 + } + ] + }, + { + "ID": "libc@0.2.54", + "Name": "libc", + "Identifier": { + "PURL": "pkg:cargo/libc@0.2.54" + }, + "Version": "0.2.54", + "Layer": {}, + "Locations": [ + { + "StartLine": 113, + "EndLine": 116 + } + ] + }, + { + "ID": "libressl-pnacl-sys@2.1.6", + "Name": "libressl-pnacl-sys", + "Identifier": { + "PURL": "pkg:cargo/libressl-pnacl-sys@2.1.6" + }, + "Version": "2.1.6", + "DependsOn": [ + "pnacl-build-helper@1.4.11" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 118, + "EndLine": 124 + } + ] + }, + { + "ID": "log@0.4.6", + "Name": "log", + "Identifier": { + "PURL": "pkg:cargo/log@0.4.6" + }, + "Version": "0.4.6", + "DependsOn": [ + "cfg-if@0.1.7" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 126, + "EndLine": 132 + } + ] + }, + { + "ID": "mac@0.1.1", + "Name": "mac", + "Identifier": { + "PURL": "pkg:cargo/mac@0.1.1" + }, + "Version": "0.1.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 134, + "EndLine": 137 + } + ] + }, + { + "ID": "maplit@1.0.1", + "Name": "maplit", + "Identifier": { + "PURL": "pkg:cargo/maplit@1.0.1" + }, + "Version": "1.0.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 139, + "EndLine": 142 + } + ] + }, + { + "ID": "markup5ever@0.8.1", + "Name": "markup5ever", + "Identifier": { + "PURL": "pkg:cargo/markup5ever@0.8.1" + }, + "Version": "0.8.1", + "DependsOn": [ + "log@0.4.6", + "phf@0.7.24", + "phf_codegen@0.7.24", + "serde@1.0.91", + "serde_derive@1.0.91", + "serde_json@1.0.39", + "string_cache@0.7.3", + "string_cache_codegen@0.4.2", + "tendril@0.4.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 144, + "EndLine": 158 + } + ] + }, + { + "ID": "matches@0.1.8", + "Name": "matches", + "Identifier": { + "PURL": "pkg:cargo/matches@0.1.8" + }, + "Version": "0.1.8", + "Layer": {}, + "Locations": [ + { + "StartLine": 160, + "EndLine": 163 + } + ] + }, + { + "ID": "new_debug_unreachable@1.0.3", + "Name": "new_debug_unreachable", + "Identifier": { + "PURL": "pkg:cargo/new_debug_unreachable@1.0.3" + }, + "Version": "1.0.3", + "Layer": {}, + "Locations": [ + { + "StartLine": 165, + "EndLine": 168 + } + ] + }, + { + "ID": "normal@0.1.0", + "Name": "normal", + "Identifier": { + "PURL": "pkg:cargo/normal@0.1.0" + }, + "Version": "0.1.0", + "DependsOn": [ + "ammonia@2.0.0", + "libc@0.2.54", + "openssl@0.8.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 170, + "EndLine": 177 + } + ] + }, + { + "ID": "openssl@0.8.3", + "Name": "openssl", + "Identifier": { + "PURL": "pkg:cargo/openssl@0.8.3" + }, + "Version": "0.8.3", + "DependsOn": [ + "bitflags@0.7.0", + "lazy_static@0.2.11", + "libc@0.2.54", + "openssl-sys@0.7.17" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 179, + "EndLine": 188 + } + ] + }, + { + "ID": "openssl-sys@0.7.17", + "Name": "openssl-sys", + "Identifier": { + "PURL": "pkg:cargo/openssl-sys@0.7.17" + }, + "Version": "0.7.17", + "DependsOn": [ + "gdi32-sys@0.2.0", + "libc@0.2.54", + "libressl-pnacl-sys@2.1.6", + "pkg-config@0.3.14", + "user32-sys@0.2.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 190, + "EndLine": 200 + } + ] + }, + { + "ID": "percent-encoding@1.0.1", + "Name": "percent-encoding", + "Identifier": { + "PURL": "pkg:cargo/percent-encoding@1.0.1" + }, + "Version": "1.0.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 202, + "EndLine": 205 + } + ] + }, + { + "ID": "phf@0.7.24", + "Name": "phf", + "Identifier": { + "PURL": "pkg:cargo/phf@0.7.24" + }, + "Version": "0.7.24", + "DependsOn": [ + "phf_shared@0.7.24" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 207, + "EndLine": 213 + } + ] + }, + { + "ID": "phf_codegen@0.7.24", + "Name": "phf_codegen", + "Identifier": { + "PURL": "pkg:cargo/phf_codegen@0.7.24" + }, + "Version": "0.7.24", + "DependsOn": [ + "phf_generator@0.7.24", + "phf_shared@0.7.24" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 215, + "EndLine": 222 + } + ] + }, + { + "ID": "phf_generator@0.7.24", + "Name": "phf_generator", + "Identifier": { + "PURL": "pkg:cargo/phf_generator@0.7.24" + }, + "Version": "0.7.24", + "DependsOn": [ + "phf_shared@0.7.24", + "rand@0.6.5" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 224, + "EndLine": 231 + } + ] + }, + { + "ID": "phf_shared@0.7.24", + "Name": "phf_shared", + "Identifier": { + "PURL": "pkg:cargo/phf_shared@0.7.24" + }, + "Version": "0.7.24", + "DependsOn": [ + "siphasher@0.2.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 233, + "EndLine": 239 + } + ] + }, + { + "ID": "pkg-config@0.3.14", + "Name": "pkg-config", + "Identifier": { + "PURL": "pkg:cargo/pkg-config@0.3.14" + }, + "Version": "0.3.14", + "Layer": {}, + "Locations": [ + { + "StartLine": 241, + "EndLine": 244 + } + ] + }, + { + "ID": "pnacl-build-helper@1.4.11", + "Name": "pnacl-build-helper", + "Identifier": { + "PURL": "pkg:cargo/pnacl-build-helper@1.4.11" + }, + "Version": "1.4.11", + "DependsOn": [ + "tempdir@0.3.7", + "walkdir@1.0.7" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 246, + "EndLine": 253 + } + ] + }, + { + "ID": "precomputed-hash@0.1.1", + "Name": "precomputed-hash", + "Identifier": { + "PURL": "pkg:cargo/precomputed-hash@0.1.1" + }, + "Version": "0.1.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 255, + "EndLine": 258 + } + ] + }, + { + "ID": "proc-macro2@0.4.30", + "Name": "proc-macro2", + "Identifier": { + "PURL": "pkg:cargo/proc-macro2@0.4.30" + }, + "Version": "0.4.30", + "DependsOn": [ + "unicode-xid@0.1.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 260, + "EndLine": 266 + } + ] + }, + { + "ID": "quote@0.6.12", + "Name": "quote", + "Identifier": { + "PURL": "pkg:cargo/quote@0.6.12" + }, + "Version": "0.6.12", + "DependsOn": [ + "proc-macro2@0.4.30" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 268, + "EndLine": 274 + } + ] + }, + { + "ID": "rand@0.4.6", + "Name": "rand", + "Identifier": { + "PURL": "pkg:cargo/rand@0.4.6" + }, + "Version": "0.4.6", + "DependsOn": [ + "fuchsia-cprng@0.1.1", + "libc@0.2.54", + "rand_core@0.3.1", + "rdrand@0.4.0", + "winapi@0.3.7" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 276, + "EndLine": 286 + } + ] + }, + { + "ID": "rand@0.6.5", + "Name": "rand", + "Identifier": { + "PURL": "pkg:cargo/rand@0.6.5" + }, + "Version": "0.6.5", + "DependsOn": [ + "autocfg@0.1.2", + "libc@0.2.54", + "rand_chacha@0.1.1", + "rand_core@0.4.0", + "rand_hc@0.1.0", + "rand_isaac@0.1.1", + "rand_jitter@0.1.4", + "rand_os@0.1.3", + "rand_pcg@0.1.2", + "rand_xorshift@0.1.1", + "winapi@0.3.7" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 288, + "EndLine": 304 + } + ] + }, + { + "ID": "rand_chacha@0.1.1", + "Name": "rand_chacha", + "Identifier": { + "PURL": "pkg:cargo/rand_chacha@0.1.1" + }, + "Version": "0.1.1", + "DependsOn": [ + "autocfg@0.1.2", + "rand_core@0.3.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 306, + "EndLine": 313 + } + ] + }, + { + "ID": "rand_core@0.3.1", + "Name": "rand_core", + "Identifier": { + "PURL": "pkg:cargo/rand_core@0.3.1" + }, + "Version": "0.3.1", + "DependsOn": [ + "rand_core@0.4.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 315, + "EndLine": 321 + } + ] + }, + { + "ID": "rand_core@0.4.0", + "Name": "rand_core", + "Identifier": { + "PURL": "pkg:cargo/rand_core@0.4.0" + }, + "Version": "0.4.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 323, + "EndLine": 326 + } + ] + }, + { + "ID": "rand_hc@0.1.0", + "Name": "rand_hc", + "Identifier": { + "PURL": "pkg:cargo/rand_hc@0.1.0" + }, + "Version": "0.1.0", + "DependsOn": [ + "rand_core@0.3.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 328, + "EndLine": 334 + } + ] + }, + { + "ID": "rand_isaac@0.1.1", + "Name": "rand_isaac", + "Identifier": { + "PURL": "pkg:cargo/rand_isaac@0.1.1" + }, + "Version": "0.1.1", + "DependsOn": [ + "rand_core@0.3.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 336, + "EndLine": 342 + } + ] + }, + { + "ID": "rand_jitter@0.1.4", + "Name": "rand_jitter", + "Identifier": { + "PURL": "pkg:cargo/rand_jitter@0.1.4" + }, + "Version": "0.1.4", + "DependsOn": [ + "libc@0.2.54", + "rand_core@0.4.0", + "winapi@0.3.7" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 344, + "EndLine": 352 + } + ] + }, + { + "ID": "rand_os@0.1.3", + "Name": "rand_os", + "Identifier": { + "PURL": "pkg:cargo/rand_os@0.1.3" + }, + "Version": "0.1.3", + "DependsOn": [ + "cloudabi@0.0.3", + "fuchsia-cprng@0.1.1", + "libc@0.2.54", + "rand_core@0.4.0", + "rdrand@0.4.0", + "winapi@0.3.7" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 354, + "EndLine": 365 + } + ] + }, + { + "ID": "rand_pcg@0.1.2", + "Name": "rand_pcg", + "Identifier": { + "PURL": "pkg:cargo/rand_pcg@0.1.2" + }, + "Version": "0.1.2", + "DependsOn": [ + "autocfg@0.1.2", + "rand_core@0.4.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 367, + "EndLine": 374 + } + ] + }, + { + "ID": "rand_xorshift@0.1.1", + "Name": "rand_xorshift", + "Identifier": { + "PURL": "pkg:cargo/rand_xorshift@0.1.1" + }, + "Version": "0.1.1", + "DependsOn": [ + "rand_core@0.3.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 376, + "EndLine": 382 + } + ] + }, + { + "ID": "rdrand@0.4.0", + "Name": "rdrand", + "Identifier": { + "PURL": "pkg:cargo/rdrand@0.4.0" + }, + "Version": "0.4.0", + "DependsOn": [ + "rand_core@0.3.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 384, + "EndLine": 390 + } + ] + }, + { + "ID": "remove_dir_all@0.5.1", + "Name": "remove_dir_all", + "Identifier": { + "PURL": "pkg:cargo/remove_dir_all@0.5.1" + }, + "Version": "0.5.1", + "DependsOn": [ + "winapi@0.3.7" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 392, + "EndLine": 398 + } + ] + }, + { + "ID": "ryu@0.2.8", + "Name": "ryu", + "Identifier": { + "PURL": "pkg:cargo/ryu@0.2.8" + }, + "Version": "0.2.8", + "Layer": {}, + "Locations": [ + { + "StartLine": 400, + "EndLine": 403 + } + ] + }, + { + "ID": "same-file@0.1.3", + "Name": "same-file", + "Identifier": { + "PURL": "pkg:cargo/same-file@0.1.3" + }, + "Version": "0.1.3", + "DependsOn": [ + "kernel32-sys@0.2.2", + "winapi@0.2.8" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 405, + "EndLine": 412 + } + ] + }, + { + "ID": "serde@1.0.91", + "Name": "serde", + "Identifier": { + "PURL": "pkg:cargo/serde@1.0.91" + }, + "Version": "1.0.91", + "Layer": {}, + "Locations": [ + { + "StartLine": 414, + "EndLine": 417 + } + ] + }, + { + "ID": "serde_derive@1.0.91", + "Name": "serde_derive", + "Identifier": { + "PURL": "pkg:cargo/serde_derive@1.0.91" + }, + "Version": "1.0.91", + "DependsOn": [ + "proc-macro2@0.4.30", + "quote@0.6.12", + "syn@0.15.34" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 419, + "EndLine": 427 + } + ] + }, + { + "ID": "serde_json@1.0.39", + "Name": "serde_json", + "Identifier": { + "PURL": "pkg:cargo/serde_json@1.0.39" + }, + "Version": "1.0.39", + "DependsOn": [ + "itoa@0.4.4", + "ryu@0.2.8", + "serde@1.0.91" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 429, + "EndLine": 437 + } + ] + }, + { + "ID": "siphasher@0.2.3", + "Name": "siphasher", + "Identifier": { + "PURL": "pkg:cargo/siphasher@0.2.3" + }, + "Version": "0.2.3", + "Layer": {}, + "Locations": [ + { + "StartLine": 439, + "EndLine": 442 + } + ] + }, + { + "ID": "smallvec@0.6.9", + "Name": "smallvec", + "Identifier": { + "PURL": "pkg:cargo/smallvec@0.6.9" + }, + "Version": "0.6.9", + "Layer": {}, + "Locations": [ + { + "StartLine": 444, + "EndLine": 447 + } + ] + }, + { + "ID": "string_cache@0.7.3", + "Name": "string_cache", + "Identifier": { + "PURL": "pkg:cargo/string_cache@0.7.3" + }, + "Version": "0.7.3", + "DependsOn": [ + "lazy_static@1.3.0", + "new_debug_unreachable@1.0.3", + "phf_shared@0.7.24", + "precomputed-hash@0.1.1", + "serde@1.0.91", + "string_cache_codegen@0.4.2", + "string_cache_shared@0.3.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 449, + "EndLine": 461 + } + ] + }, + { + "ID": "string_cache_codegen@0.4.2", + "Name": "string_cache_codegen", + "Identifier": { + "PURL": "pkg:cargo/string_cache_codegen@0.4.2" + }, + "Version": "0.4.2", + "DependsOn": [ + "phf_generator@0.7.24", + "phf_shared@0.7.24", + "proc-macro2@0.4.30", + "quote@0.6.12", + "string_cache_shared@0.3.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 463, + "EndLine": 473 + } + ] + }, + { + "ID": "string_cache_shared@0.3.0", + "Name": "string_cache_shared", + "Identifier": { + "PURL": "pkg:cargo/string_cache_shared@0.3.0" + }, + "Version": "0.3.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 475, + "EndLine": 478 + } + ] + }, + { + "ID": "syn@0.15.34", + "Name": "syn", + "Identifier": { + "PURL": "pkg:cargo/syn@0.15.34" + }, + "Version": "0.15.34", + "DependsOn": [ + "proc-macro2@0.4.30", + "quote@0.6.12", + "unicode-xid@0.1.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 480, + "EndLine": 488 + } + ] + }, + { + "ID": "tempdir@0.3.7", + "Name": "tempdir", + "Identifier": { + "PURL": "pkg:cargo/tempdir@0.3.7" + }, + "Version": "0.3.7", + "DependsOn": [ + "rand@0.4.6", + "remove_dir_all@0.5.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 490, + "EndLine": 497 + } + ] + }, + { + "ID": "tendril@0.4.1", + "Name": "tendril", + "Identifier": { + "PURL": "pkg:cargo/tendril@0.4.1" + }, + "Version": "0.4.1", + "DependsOn": [ + "futf@0.1.4", + "mac@0.1.1", + "utf-8@0.7.5" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 499, + "EndLine": 507 + } + ] + }, + { + "ID": "unicode-bidi@0.3.4", + "Name": "unicode-bidi", + "Identifier": { + "PURL": "pkg:cargo/unicode-bidi@0.3.4" + }, + "Version": "0.3.4", + "DependsOn": [ + "matches@0.1.8" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 509, + "EndLine": 515 + } + ] + }, + { + "ID": "unicode-normalization@0.1.8", + "Name": "unicode-normalization", + "Identifier": { + "PURL": "pkg:cargo/unicode-normalization@0.1.8" + }, + "Version": "0.1.8", + "DependsOn": [ + "smallvec@0.6.9" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 517, + "EndLine": 523 + } + ] + }, + { + "ID": "unicode-xid@0.1.0", + "Name": "unicode-xid", + "Identifier": { + "PURL": "pkg:cargo/unicode-xid@0.1.0" + }, + "Version": "0.1.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 525, + "EndLine": 528 + } + ] + }, + { + "ID": "url@1.7.2", + "Name": "url", + "Identifier": { + "PURL": "pkg:cargo/url@1.7.2" + }, + "Version": "1.7.2", + "DependsOn": [ + "idna@0.1.5", + "matches@0.1.8", + "percent-encoding@1.0.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 530, + "EndLine": 538 + } + ] + }, + { + "ID": "user32-sys@0.2.0", + "Name": "user32-sys", + "Identifier": { + "PURL": "pkg:cargo/user32-sys@0.2.0" + }, + "Version": "0.2.0", + "DependsOn": [ + "winapi-build@0.1.1", + "winapi@0.2.8" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 540, + "EndLine": 547 + } + ] + }, + { + "ID": "utf-8@0.7.5", + "Name": "utf-8", + "Identifier": { + "PURL": "pkg:cargo/utf-8@0.7.5" + }, + "Version": "0.7.5", + "Layer": {}, + "Locations": [ + { + "StartLine": 549, + "EndLine": 552 + } + ] + }, + { + "ID": "walkdir@1.0.7", + "Name": "walkdir", + "Identifier": { + "PURL": "pkg:cargo/walkdir@1.0.7" + }, + "Version": "1.0.7", + "DependsOn": [ + "kernel32-sys@0.2.2", + "same-file@0.1.3", + "winapi@0.2.8" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 554, + "EndLine": 562 + } + ] + }, + { + "ID": "winapi@0.2.8", + "Name": "winapi", + "Identifier": { + "PURL": "pkg:cargo/winapi@0.2.8" + }, + "Version": "0.2.8", + "Layer": {}, + "Locations": [ + { + "StartLine": 564, + "EndLine": 567 + } + ] + }, + { + "ID": "winapi@0.3.7", + "Name": "winapi", + "Identifier": { + "PURL": "pkg:cargo/winapi@0.3.7" + }, + "Version": "0.3.7", + "DependsOn": [ + "winapi-i686-pc-windows-gnu@0.4.0", + "winapi-x86_64-pc-windows-gnu@0.4.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 569, + "EndLine": 576 + } + ] + }, + { + "ID": "winapi-build@0.1.1", + "Name": "winapi-build", + "Identifier": { + "PURL": "pkg:cargo/winapi-build@0.1.1" + }, + "Version": "0.1.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 578, + "EndLine": 581 + } + ] + }, + { + "ID": "winapi-i686-pc-windows-gnu@0.4.0", + "Name": "winapi-i686-pc-windows-gnu", + "Identifier": { + "PURL": "pkg:cargo/winapi-i686-pc-windows-gnu@0.4.0" + }, + "Version": "0.4.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 583, + "EndLine": 586 + } + ] + }, + { + "ID": "winapi-x86_64-pc-windows-gnu@0.4.0", + "Name": "winapi-x86_64-pc-windows-gnu", + "Identifier": { + "PURL": "pkg:cargo/winapi-x86_64-pc-windows-gnu@0.4.0" + }, + "Version": "0.4.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 588, + "EndLine": 591 + } + ] + } + ] + }, + { + "Type": "composer", + "FilePath": "php-app/composer.lock", + "Libraries": [ + { + "ID": "guzzlehttp/guzzle@6.2.0", + "Name": "guzzlehttp/guzzle", + "Identifier": { + "PURL": "pkg:composer/guzzlehttp/guzzle@6.2.0" + }, + "Version": "6.2.0", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "guzzlehttp/promises@v1.3.1", + "guzzlehttp/psr7@1.5.2" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 9, + "EndLine": 73 + } + ] + }, + { + "ID": "guzzlehttp/promises@v1.3.1", + "Name": "guzzlehttp/promises", + "Identifier": { + "PURL": "pkg:composer/guzzlehttp/promises@v1.3.1" + }, + "Version": "v1.3.1", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 74, + "EndLine": 124 + } + ] + }, + { + "ID": "guzzlehttp/psr7@1.5.2", + "Name": "guzzlehttp/psr7", + "Identifier": { + "PURL": "pkg:composer/guzzlehttp/psr7@1.5.2" + }, + "Version": "1.5.2", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "psr/http-message@1.0.1", + "ralouphie/getallheaders@2.0.5" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 125, + "EndLine": 191 + } + ] + }, + { + "ID": "laravel/installer@v2.0.1", + "Name": "laravel/installer", + "Identifier": { + "PURL": "pkg:composer/laravel/installer@v2.0.1" + }, + "Version": "v2.0.1", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "guzzlehttp/guzzle@6.2.0", + "symfony/console@v4.2.7", + "symfony/filesystem@v4.2.7", + "symfony/process@v4.2.7" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 192, + "EndLine": 237 + } + ] + }, + { + "ID": "pear/log@1.13.1", + "Name": "pear/log", + "Identifier": { + "PURL": "pkg:composer/pear/log@1.13.1" + }, + "Version": "1.13.1", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "pear/pear_exception@v1.0.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 238, + "EndLine": 290 + } + ] + }, + { + "ID": "pear/pear_exception@v1.0.0", + "Name": "pear/pear_exception", + "Identifier": { + "PURL": "pkg:composer/pear/pear_exception@v1.0.0" + }, + "Version": "v1.0.0", + "Licenses": [ + "BSD-2-Clause" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 291, + "EndLine": 345 + } + ] + }, + { + "ID": "psr/http-message@1.0.1", + "Name": "psr/http-message", + "Identifier": { + "PURL": "pkg:composer/psr/http-message@1.0.1" + }, + "Version": "1.0.1", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 346, + "EndLine": 395 + } + ] + }, + { + "ID": "ralouphie/getallheaders@2.0.5", + "Name": "ralouphie/getallheaders", + "Identifier": { + "PURL": "pkg:composer/ralouphie/getallheaders@2.0.5" + }, + "Version": "2.0.5", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 396, + "EndLine": 435 + } + ] + }, + { + "ID": "symfony/console@v4.2.7", + "Name": "symfony/console", + "Identifier": { + "PURL": "pkg:composer/symfony/console@v4.2.7" + }, + "Version": "v4.2.7", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "symfony/contracts@v1.0.2", + "symfony/polyfill-mbstring@v1.11.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 436, + "EndLine": 507 + } + ] + }, + { + "ID": "symfony/contracts@v1.0.2", + "Name": "symfony/contracts", + "Identifier": { + "PURL": "pkg:composer/symfony/contracts@v1.0.2" + }, + "Version": "v1.0.2", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 508, + "EndLine": 575 + } + ] + }, + { + "ID": "symfony/filesystem@v4.2.7", + "Name": "symfony/filesystem", + "Identifier": { + "PURL": "pkg:composer/symfony/filesystem@v4.2.7" + }, + "Version": "v4.2.7", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "symfony/polyfill-ctype@v1.11.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 576, + "EndLine": 625 + } + ] + }, + { + "ID": "symfony/polyfill-ctype@v1.11.0", + "Name": "symfony/polyfill-ctype", + "Identifier": { + "PURL": "pkg:composer/symfony/polyfill-ctype@v1.11.0" + }, + "Version": "v1.11.0", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 626, + "EndLine": 683 + } + ] + }, + { + "ID": "symfony/polyfill-mbstring@v1.11.0", + "Name": "symfony/polyfill-mbstring", + "Identifier": { + "PURL": "pkg:composer/symfony/polyfill-mbstring@v1.11.0" + }, + "Version": "v1.11.0", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 684, + "EndLine": 742 + } + ] + }, + { + "ID": "symfony/process@v4.2.7", + "Name": "symfony/process", + "Identifier": { + "PURL": "pkg:composer/symfony/process@v4.2.7" + }, + "Version": "v4.2.7", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 743, + "EndLine": 791 + } + ] + } + ] + }, + { + "Type": "npm", + "FilePath": "node-app/package-lock.json", + "Libraries": [ + { + "ID": "asap@2.0.6", + "Name": "asap", + "Identifier": { + "PURL": "pkg:npm/asap@2.0.6" + }, + "Version": "2.0.6", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 6, + "EndLine": 10 + } + ] + }, + { + "ID": "jquery@3.3.9", + "Name": "jquery", + "Identifier": { + "PURL": "pkg:npm/jquery@3.3.9" + }, + "Version": "3.3.9", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 16, + "EndLine": 20 + } + ] + }, + { + "ID": "js-tokens@4.0.0", + "Name": "js-tokens", + "Identifier": { + "PURL": "pkg:npm/js-tokens@4.0.0" + }, + "Version": "4.0.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 21, + "EndLine": 25 + } + ] + }, + { + "ID": "lodash@4.17.4", + "Name": "lodash", + "Identifier": { + "PURL": "pkg:npm/lodash@4.17.4" + }, + "Version": "4.17.4", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 11, + "EndLine": 15 + } + ] + }, + { + "ID": "loose-envify@1.4.0", + "Name": "loose-envify", + "Identifier": { + "PURL": "pkg:npm/loose-envify@1.4.0" + }, + "Version": "1.4.0", + "Indirect": true, + "DependsOn": [ + "js-tokens@4.0.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 26, + "EndLine": 33 + } + ] + }, + { + "ID": "object-assign@4.1.1", + "Name": "object-assign", + "Identifier": { + "PURL": "pkg:npm/object-assign@4.1.1" + }, + "Version": "4.1.1", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 34, + "EndLine": 38 + } + ] + }, + { + "ID": "promise@8.0.3", + "Name": "promise", + "Identifier": { + "PURL": "pkg:npm/promise@8.0.3" + }, + "Version": "8.0.3", + "Indirect": true, + "DependsOn": [ + "asap@2.0.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 39, + "EndLine": 46 + } + ] + }, + { + "ID": "prop-types@15.7.2", + "Name": "prop-types", + "Identifier": { + "PURL": "pkg:npm/prop-types@15.7.2" + }, + "Version": "15.7.2", + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "react-is@16.8.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 47, + "EndLine": 56 + } + ] + }, + { + "ID": "react@16.8.6", + "Name": "react", + "Identifier": { + "PURL": "pkg:npm/react@16.8.6" + }, + "Version": "16.8.6", + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "prop-types@15.7.2", + "scheduler@0.13.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 57, + "EndLine": 67 + } + ] + }, + { + "ID": "react-is@16.8.6", + "Name": "react-is", + "Identifier": { + "PURL": "pkg:npm/react-is@16.8.6" + }, + "Version": "16.8.6", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 68, + "EndLine": 72 + } + ] + }, + { + "ID": "redux@4.0.1", + "Name": "redux", + "Identifier": { + "PURL": "pkg:npm/redux@4.0.1" + }, + "Version": "4.0.1", + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "symbol-observable@1.2.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 73, + "EndLine": 81 + } + ] + }, + { + "ID": "scheduler@0.13.6", + "Name": "scheduler", + "Identifier": { + "PURL": "pkg:npm/scheduler@0.13.6" + }, + "Version": "0.13.6", + "Indirect": true, + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 82, + "EndLine": 90 + } + ] + }, + { + "ID": "symbol-observable@1.2.0", + "Name": "symbol-observable", + "Identifier": { + "PURL": "pkg:npm/symbol-observable@1.2.0" + }, + "Version": "1.2.0", + "Indirect": true, + "Layer": {}, + "Locations": [ + { + "StartLine": 91, + "EndLine": 95 + } + ] + } + ] + }, + { + "Type": "pipenv", + "FilePath": "python-app/Pipfile.lock", + "Libraries": [ + { + "Name": "amqp", + "Identifier": { + "PURL": "pkg:pypi/amqp@2.4.2" + }, + "Version": "2.4.2", + "Layer": {}, + "Locations": [ + { + "StartLine": 19, + "EndLine": 25 + } + ] + }, + { + "Name": "autopep8", + "Identifier": { + "PURL": "pkg:pypi/autopep8@1.4.3" + }, + "Version": "1.4.3", + "Layer": {}, + "Locations": [ + { + "StartLine": 26, + "EndLine": 31 + } + ] + }, + { + "Name": "babel", + "Identifier": { + "PURL": "pkg:pypi/babel@2.6.0" + }, + "Version": "2.6.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 32, + "EndLine": 38 + } + ] + }, + { + "Name": "billiard", + "Identifier": { + "PURL": "pkg:pypi/billiard@3.6.0.0" + }, + "Version": "3.6.0.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 39, + "EndLine": 44 + } + ] + }, + { + "Name": "boto3", + "Identifier": { + "PURL": "pkg:pypi/boto3@1.9.130" + }, + "Version": "1.9.130", + "Layer": {}, + "Locations": [ + { + "StartLine": 45, + "EndLine": 52 + } + ] + }, + { + "Name": "botocore", + "Identifier": { + "PURL": "pkg:pypi/botocore@1.12.130" + }, + "Version": "1.12.130", + "Layer": {}, + "Locations": [ + { + "StartLine": 53, + "EndLine": 59 + } + ] + }, + { + "Name": "celery", + "Identifier": { + "PURL": "pkg:pypi/celery@4.3.0" + }, + "Version": "4.3.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 60, + "EndLine": 70 + } + ] + }, + { + "Name": "certifi", + "Identifier": { + "PURL": "pkg:pypi/certifi@2019.3.9" + }, + "Version": "2019.3.9", + "Layer": {}, + "Locations": [ + { + "StartLine": 71, + "EndLine": 77 + } + ] + }, + { + "Name": "chardet", + "Identifier": { + "PURL": "pkg:pypi/chardet@3.0.4" + }, + "Version": "3.0.4", + "Layer": {}, + "Locations": [ + { + "StartLine": 78, + "EndLine": 84 + } + ] + }, + { + "Name": "decorator", + "Identifier": { + "PURL": "pkg:pypi/decorator@4.4.0" + }, + "Version": "4.4.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 85, + "EndLine": 91 + } + ] + }, + { + "Name": "django", + "Identifier": { + "PURL": "pkg:pypi/django@2.0.9" + }, + "Version": "2.0.9", + "Layer": {}, + "Locations": [ + { + "StartLine": 92, + "EndLine": 99 + } + ] + }, + { + "Name": "django-celery-beat", + "Identifier": { + "PURL": "pkg:pypi/django-celery-beat@1.4.0" + }, + "Version": "1.4.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 100, + "EndLine": 107 + } + ] + }, + { + "Name": "django-cors-headers", + "Identifier": { + "PURL": "pkg:pypi/django-cors-headers@2.5.2" + }, + "Version": "2.5.2", + "Layer": {}, + "Locations": [ + { + "StartLine": 108, + "EndLine": 115 + } + ] + }, + { + "Name": "django-extensions", + "Identifier": { + "PURL": "pkg:pypi/django-extensions@2.1.6" + }, + "Version": "2.1.6", + "Layer": {}, + "Locations": [ + { + "StartLine": 116, + "EndLine": 123 + } + ] + }, + { + "Name": "django-postgres-extra", + "Identifier": { + "PURL": "pkg:pypi/django-postgres-extra" + }, + "Layer": {}, + "Locations": [ + { + "StartLine": 124, + "EndLine": 128 + } + ] + }, + { + "Name": "django-redis-cache", + "Identifier": { + "PURL": "pkg:pypi/django-redis-cache@2.0.0" + }, + "Version": "2.0.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 129, + "EndLine": 135 + } + ] + }, + { + "Name": "django-silk", + "Identifier": { + "PURL": "pkg:pypi/django-silk@3.0.1" + }, + "Version": "3.0.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 136, + "EndLine": 143 + } + ] + }, + { + "Name": "django-timezone-field", + "Identifier": { + "PURL": "pkg:pypi/django-timezone-field@3.0" + }, + "Version": "3.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 144, + "EndLine": 150 + } + ] + }, + { + "Name": "djangorestframework", + "Identifier": { + "PURL": "pkg:pypi/djangorestframework@3.9.2" + }, + "Version": "3.9.2", + "Layer": {}, + "Locations": [ + { + "StartLine": 151, + "EndLine": 158 + } + ] + }, + { + "Name": "djangorestframework-jwt", + "Identifier": { + "PURL": "pkg:pypi/djangorestframework-jwt@1.11.0" + }, + "Version": "1.11.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 159, + "EndLine": 166 + } + ] + }, + { + "Name": "docutils", + "Identifier": { + "PURL": "pkg:pypi/docutils@0.14" + }, + "Version": "0.14", + "Layer": {}, + "Locations": [ + { + "StartLine": 167, + "EndLine": 174 + } + ] + }, + { + "Name": "flower", + "Identifier": { + "PURL": "pkg:pypi/flower@0.9.3" + }, + "Version": "0.9.3", + "Layer": {}, + "Locations": [ + { + "StartLine": 175, + "EndLine": 181 + } + ] + }, + { + "Name": "gprof2dot", + "Identifier": { + "PURL": "pkg:pypi/gprof2dot@2016.10.13" + }, + "Version": "2016.10.13", + "Layer": {}, + "Locations": [ + { + "StartLine": 182, + "EndLine": 187 + } + ] + }, + { + "Name": "gunicorn", + "Identifier": { + "PURL": "pkg:pypi/gunicorn@19.9.0" + }, + "Version": "19.9.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 188, + "EndLine": 195 + } + ] + }, + { + "Name": "hiredis", + "Identifier": { + "PURL": "pkg:pypi/hiredis@1.0.0" + }, + "Version": "1.0.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 196, + "EndLine": 229 + } + ] + }, + { + "Name": "httplib2", + "Identifier": { + "PURL": "pkg:pypi/httplib2@0.12.1" + }, + "Version": "0.12.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 230, + "EndLine": 235 + } + ] + }, + { + "Name": "idna", + "Identifier": { + "PURL": "pkg:pypi/idna@2.8" + }, + "Version": "2.8", + "Layer": {}, + "Locations": [ + { + "StartLine": 236, + "EndLine": 242 + } + ] + }, + { + "Name": "jinja2", + "Identifier": { + "PURL": "pkg:pypi/jinja2@2.10.1" + }, + "Version": "2.10.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 243, + "EndLine": 249 + } + ] + }, + { + "Name": "jmespath", + "Identifier": { + "PURL": "pkg:pypi/jmespath@0.9.4" + }, + "Version": "0.9.4", + "Layer": {}, + "Locations": [ + { + "StartLine": 250, + "EndLine": 256 + } + ] + }, + { + "Name": "kombu", + "Identifier": { + "PURL": "pkg:pypi/kombu@4.5.0" + }, + "Version": "4.5.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 257, + "EndLine": 263 + } + ] + }, + { + "Name": "markupsafe", + "Identifier": { + "PURL": "pkg:pypi/markupsafe@1.1.1" + }, + "Version": "1.1.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 264, + "EndLine": 296 + } + ] + }, + { + "Name": "oauth2", + "Identifier": { + "PURL": "pkg:pypi/oauth2@1.9.0.post1" + }, + "Version": "1.9.0.post1", + "Layer": {}, + "Locations": [ + { + "StartLine": 297, + "EndLine": 304 + } + ] + }, + { + "Name": "psycopg2-binary", + "Identifier": { + "PURL": "pkg:pypi/psycopg2-binary@2.8.1" + }, + "Version": "2.8.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 305, + "EndLine": 338 + } + ] + }, + { + "Name": "py", + "Identifier": { + "PURL": "pkg:pypi/py@1.8.0" + }, + "Version": "1.8.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 339, + "EndLine": 345 + } + ] + }, + { + "Name": "pycodestyle", + "Identifier": { + "PURL": "pkg:pypi/pycodestyle@2.5.0" + }, + "Version": "2.5.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 346, + "EndLine": 352 + } + ] + }, + { + "Name": "pycurl", + "Identifier": { + "PURL": "pkg:pypi/pycurl@7.43.0.2" + }, + "Version": "7.43.0.2", + "Layer": {}, + "Locations": [ + { + "StartLine": 353, + "EndLine": 365 + } + ] + }, + { + "Name": "pygments", + "Identifier": { + "PURL": "pkg:pypi/pygments@2.3.1" + }, + "Version": "2.3.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 366, + "EndLine": 372 + } + ] + }, + { + "Name": "pyjwt", + "Identifier": { + "PURL": "pkg:pypi/pyjwt@1.7.1" + }, + "Version": "1.7.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 373, + "EndLine": 379 + } + ] + }, + { + "Name": "python-crontab", + "Identifier": { + "PURL": "pkg:pypi/python-crontab@2.3.6" + }, + "Version": "2.3.6", + "Layer": {}, + "Locations": [ + { + "StartLine": 380, + "EndLine": 385 + } + ] + }, + { + "Name": "python-dateutil", + "Identifier": { + "PURL": "pkg:pypi/python-dateutil@2.8.0" + }, + "Version": "2.8.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 386, + "EndLine": 393 + } + ] + }, + { + "Name": "python-http-client", + "Identifier": { + "PURL": "pkg:pypi/python-http-client@3.1.0" + }, + "Version": "3.1.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 394, + "EndLine": 399 + } + ] + }, + { + "Name": "pytz", + "Identifier": { + "PURL": "pkg:pypi/pytz@2019.1" + }, + "Version": "2019.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 400, + "EndLine": 406 + } + ] + }, + { + "Name": "pyyaml", + "Identifier": { + "PURL": "pkg:pypi/pyyaml@5.1" + }, + "Version": "5.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 407, + "EndLine": 423 + } + ] + }, + { + "Name": "redis", + "Identifier": { + "PURL": "pkg:pypi/redis@3.2.1" + }, + "Version": "3.2.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 424, + "EndLine": 430 + } + ] + }, + { + "Name": "requests", + "Identifier": { + "PURL": "pkg:pypi/requests@2.21.0" + }, + "Version": "2.21.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 431, + "EndLine": 438 + } + ] + }, + { + "Name": "retry", + "Identifier": { + "PURL": "pkg:pypi/retry@0.9.2" + }, + "Version": "0.9.2", + "Layer": {}, + "Locations": [ + { + "StartLine": 439, + "EndLine": 446 + } + ] + }, + { + "Name": "s3transfer", + "Identifier": { + "PURL": "pkg:pypi/s3transfer@0.2.0" + }, + "Version": "0.2.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 447, + "EndLine": 453 + } + ] + }, + { + "Name": "sendgrid", + "Identifier": { + "PURL": "pkg:pypi/sendgrid@6.0.4" + }, + "Version": "6.0.4", + "Layer": {}, + "Locations": [ + { + "StartLine": 454, + "EndLine": 461 + } + ] + }, + { + "Name": "sentry-sdk", + "Identifier": { + "PURL": "pkg:pypi/sentry-sdk@0.7.10" + }, + "Version": "0.7.10", + "Layer": {}, + "Locations": [ + { + "StartLine": 462, + "EndLine": 469 + } + ] + }, + { + "Name": "six", + "Identifier": { + "PURL": "pkg:pypi/six@1.12.0" + }, + "Version": "1.12.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 470, + "EndLine": 476 + } + ] + }, + { + "Name": "sqlparse", + "Identifier": { + "PURL": "pkg:pypi/sqlparse@0.3.0" + }, + "Version": "0.3.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 477, + "EndLine": 483 + } + ] + }, + { + "Name": "tornado", + "Identifier": { + "PURL": "pkg:pypi/tornado@5.1.1" + }, + "Version": "5.1.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 484, + "EndLine": 495 + } + ] + }, + { + "Name": "urllib3", + "Identifier": { + "PURL": "pkg:pypi/urllib3@1.24.1" + }, + "Version": "1.24.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 496, + "EndLine": 503 + } + ] + }, + { + "Name": "vine", + "Identifier": { + "PURL": "pkg:pypi/vine@1.3.0" + }, + "Version": "1.3.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 504, + "EndLine": 510 + } + ] + }, + { + "Name": "xmltodict", + "Identifier": { + "PURL": "pkg:pypi/xmltodict@0.12.0" + }, + "Version": "0.12.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 511, + "EndLine": 518 + } + ] + } + ] + } +] \ No newline at end of file diff --git a/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedpkgsfromcmds.golden b/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedpkgsfromcmds.golden new file mode 100644 index 000000000000..818db8d5c29d --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedpkgsfromcmds.golden @@ -0,0 +1,500 @@ +[ + { + "Name": "acl", + "Identifier": {}, + "Version": "2.2.52-r3", + "Layer": {} + }, + { + "Name": "apr", + "Identifier": {}, + "Version": "1.6.3-r0", + "Layer": {} + }, + { + "Name": "apr-util", + "Identifier": {}, + "Version": "1.6.1-r1", + "Layer": {} + }, + { + "Name": "attr", + "Identifier": {}, + "Version": "2.4.47-r6", + "Layer": {} + }, + { + "Name": "autoconf", + "Identifier": {}, + "Version": "2.69-r0", + "Layer": {} + }, + { + "Name": "bash", + "Identifier": {}, + "Version": "4.4.19-r1", + "Layer": {} + }, + { + "Name": "binutils", + "Identifier": {}, + "Version": "2.30-r1", + "Layer": {} + }, + { + "Name": "binutils-libs", + "Identifier": {}, + "Version": "2.30-r1", + "Layer": {} + }, + { + "Name": "busybox", + "Identifier": {}, + "Version": "1.27.2-r11", + "Layer": {} + }, + { + "Name": "bzip2", + "Identifier": {}, + "Version": "1.0.6-r6", + "Layer": {} + }, + { + "Name": "ca-certificates", + "Identifier": {}, + "Version": "20171114-r0", + "Layer": {} + }, + { + "Name": "coreutils", + "Identifier": {}, + "Version": "8.28-r0", + "Layer": {} + }, + { + "Name": "cyrus-sasl", + "Identifier": {}, + "Version": "2.1.26-r11", + "Layer": {} + }, + { + "Name": "db", + "Identifier": {}, + "Version": "5.3.28-r0", + "Layer": {} + }, + { + "Name": "dpkg", + "Identifier": {}, + "Version": "1.18.24-r0", + "Layer": {} + }, + { + "Name": "dpkg-dev", + "Identifier": {}, + "Version": "1.18.24-r0", + "Layer": {} + }, + { + "Name": "expat", + "Identifier": {}, + "Version": "2.2.5-r0", + "Layer": {} + }, + { + "Name": "file", + "Identifier": {}, + "Version": "5.32-r0", + "Layer": {} + }, + { + "Name": "g++", + "Identifier": {}, + "Version": "6.4.0-r5", + "Layer": {} + }, + { + "Name": "gcc", + "Identifier": {}, + "Version": "6.4.0-r5", + "Layer": {} + }, + { + "Name": "gdbm", + "Identifier": {}, + "Version": "1.13-r1", + "Layer": {} + }, + { + "Name": "gmp", + "Identifier": {}, + "Version": "6.1.2-r1", + "Layer": {} + }, + { + "Name": "gnupg", + "Identifier": {}, + "Version": "2.2.3-r1", + "Layer": {} + }, + { + "Name": "gnutls", + "Identifier": {}, + "Version": "3.6.1-r0", + "Layer": {} + }, + { + "Name": "isl", + "Identifier": {}, + "Version": "0.18-r0", + "Layer": {} + }, + { + "Name": "libacl", + "Identifier": {}, + "Version": "2.2.52-r3", + "Layer": {} + }, + { + "Name": "libassuan", + "Identifier": {}, + "Version": "2.4.4-r0", + "Layer": {} + }, + { + "Name": "libatomic", + "Identifier": {}, + "Version": "6.4.0-r5", + "Layer": {} + }, + { + "Name": "libattr", + "Identifier": {}, + "Version": "2.4.47-r6", + "Layer": {} + }, + { + "Name": "libbz2", + "Identifier": {}, + "Version": "1.0.6-r6", + "Layer": {} + }, + { + "Name": "libc-dev", + "Identifier": {}, + "Version": "0.7.1-r0", + "Layer": {} + }, + { + "Name": "libcap", + "Identifier": {}, + "Version": "2.25-r1", + "Layer": {} + }, + { + "Name": "libedit", + "Identifier": {}, + "Version": "20170329.3.1-r3", + "Layer": {} + }, + { + "Name": "libedit-dev", + "Identifier": {}, + "Version": "20170329.3.1-r3", + "Layer": {} + }, + { + "Name": "libffi", + "Identifier": {}, + "Version": "3.2.1-r4", + "Layer": {} + }, + { + "Name": "libgcc", + "Identifier": {}, + "Version": "6.4.0-r5", + "Layer": {} + }, + { + "Name": "libgcrypt", + "Identifier": {}, + "Version": "1.8.3-r0", + "Layer": {} + }, + { + "Name": "libgomp", + "Identifier": {}, + "Version": "6.4.0-r5", + "Layer": {} + }, + { + "Name": "libgpg-error", + "Identifier": {}, + "Version": "1.27-r1", + "Layer": {} + }, + { + "Name": "libksba", + "Identifier": {}, + "Version": "1.3.5-r0", + "Layer": {} + }, + { + "Name": "libldap", + "Identifier": {}, + "Version": "2.4.45-r3", + "Layer": {} + }, + { + "Name": "libmagic", + "Identifier": {}, + "Version": "5.32-r0", + "Layer": {} + }, + { + "Name": "libressl", + "Identifier": {}, + "Version": "2.6.5-r0", + "Layer": {} + }, + { + "Name": "libressl-dev", + "Identifier": {}, + "Version": "2.6.5-r0", + "Layer": {} + }, + { + "Name": "libressl2.6-libcrypto", + "Identifier": {}, + "Version": "2.6.5-r0", + "Layer": {} + }, + { + "Name": "libressl2.6-libssl", + "Identifier": {}, + "Version": "2.6.5-r0", + "Layer": {} + }, + { + "Name": "libressl2.6-libtls", + "Identifier": {}, + "Version": "2.6.5-r0", + "Layer": {} + }, + { + "Name": "libsasl", + "Identifier": {}, + "Version": "2.1.26-r11", + "Layer": {} + }, + { + "Name": "libsodium", + "Identifier": {}, + "Version": "1.0.15-r0", + "Layer": {} + }, + { + "Name": "libsodium-dev", + "Identifier": {}, + "Version": "1.0.15-r0", + "Layer": {} + }, + { + "Name": "libstdc++", + "Identifier": {}, + "Version": "6.4.0-r5", + "Layer": {} + }, + { + "Name": "libtasn1", + "Identifier": {}, + "Version": "4.12-r3", + "Layer": {} + }, + { + "Name": "libunistring", + "Identifier": {}, + "Version": "0.9.7-r0", + "Layer": {} + }, + { + "Name": "m4", + "Identifier": {}, + "Version": "1.4.18-r0", + "Layer": {} + }, + { + "Name": "make", + "Identifier": {}, + "Version": "4.2.1-r0", + "Layer": {} + }, + { + "Name": "mercurial", + "Identifier": {}, + "Version": "4.5.2-r0", + "Layer": {} + }, + { + "Name": "mpc1", + "Identifier": {}, + "Version": "1.0.3-r1", + "Layer": {} + }, + { + "Name": "mpfr3", + "Identifier": {}, + "Version": "3.1.5-r1", + "Layer": {} + }, + { + "Name": "musl", + "Identifier": {}, + "Version": "1.1.18-r3", + "Layer": {} + }, + { + "Name": "musl-dev", + "Identifier": {}, + "Version": "1.1.18-r3", + "Layer": {} + }, + { + "Name": "ncurses", + "Identifier": {}, + "Version": "6.0_p20171125-r1", + "Layer": {} + }, + { + "Name": "ncurses-dev", + "Identifier": {}, + "Version": "6.0_p20171125-r1", + "Layer": {} + }, + { + "Name": "ncurses-libs", + "Identifier": {}, + "Version": "6.0_p20171125-r1", + "Layer": {} + }, + { + "Name": "ncurses-terminfo", + "Identifier": {}, + "Version": "6.0_p20171125-r1", + "Layer": {} + }, + { + "Name": "ncurses-terminfo-base", + "Identifier": {}, + "Version": "6.0_p20171125-r1", + "Layer": {} + }, + { + "Name": "nettle", + "Identifier": {}, + "Version": "3.3-r0", + "Layer": {} + }, + { + "Name": "npth", + "Identifier": {}, + "Version": "1.5-r1", + "Layer": {} + }, + { + "Name": "openldap", + "Identifier": {}, + "Version": "2.4.45-r3", + "Layer": {} + }, + { + "Name": "p11-kit", + "Identifier": {}, + "Version": "0.23.2-r2", + "Layer": {} + }, + { + "Name": "patch", + "Identifier": {}, + "Version": "2.7.5-r2", + "Layer": {} + }, + { + "Name": "pcre2", + "Identifier": {}, + "Version": "10.30-r0", + "Layer": {} + }, + { + "Name": "pinentry", + "Identifier": {}, + "Version": "1.0.0-r0", + "Layer": {} + }, + { + "Name": "pkgconf", + "Identifier": {}, + "Version": "1.3.10-r0", + "Layer": {} + }, + { + "Name": "python2", + "Identifier": {}, + "Version": "2.7.15-r2", + "Layer": {} + }, + { + "Name": "re2c", + "Identifier": {}, + "Version": "1.0.2-r0", + "Layer": {} + }, + { + "Name": "readline", + "Identifier": {}, + "Version": "7.0.003-r0", + "Layer": {} + }, + { + "Name": "serf", + "Identifier": {}, + "Version": "1.3.9-r3", + "Layer": {} + }, + { + "Name": "subversion", + "Identifier": {}, + "Version": "1.9.7-r0", + "Layer": {} + }, + { + "Name": "subversion-libs", + "Identifier": {}, + "Version": "1.9.7-r0", + "Layer": {} + }, + { + "Name": "xz", + "Identifier": {}, + "Version": "5.2.3-r1", + "Layer": {} + }, + { + "Name": "xz-libs", + "Identifier": {}, + "Version": "5.2.3-r1", + "Layer": {} + }, + { + "Name": "zlib", + "Identifier": {}, + "Version": "1.2.11-r1", + "Layer": {} + }, + { + "Name": "zlib-dev", + "Identifier": {}, + "Version": "1.2.11-r1", + "Layer": {} + } +] \ No newline at end of file diff --git a/pkg/fanal/test/testdata/alpine-310.tar.gz b/pkg/fanal/test/testdata/alpine-310.tar.gz new file mode 100644 index 000000000000..5a1750b24751 Binary files /dev/null and b/pkg/fanal/test/testdata/alpine-310.tar.gz differ diff --git a/pkg/fanal/test/testdata/alpine-311.tar.gz b/pkg/fanal/test/testdata/alpine-311.tar.gz new file mode 100644 index 000000000000..381d2bbb84dd Binary files /dev/null and b/pkg/fanal/test/testdata/alpine-311.tar.gz differ diff --git a/pkg/fanal/test/testdata/distroless.tar.gz b/pkg/fanal/test/testdata/distroless.tar.gz new file mode 100644 index 000000000000..594568b1e898 Binary files /dev/null and b/pkg/fanal/test/testdata/distroless.tar.gz differ diff --git a/pkg/fanal/test/testdata/image1.tar b/pkg/fanal/test/testdata/image1.tar new file mode 100644 index 000000000000..5e2aedf33636 Binary files /dev/null and b/pkg/fanal/test/testdata/image1.tar differ diff --git a/pkg/fanal/test/testdata/test.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab b/pkg/fanal/test/testdata/test.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab new file mode 100644 index 000000000000..8967e307376a --- /dev/null +++ b/pkg/fanal/test/testdata/test.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab @@ -0,0 +1 @@ +{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604","size":584},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c","size":2610}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 b/pkg/fanal/test/testdata/test.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 new file mode 100644 index 000000000000..1156328a5b74 --- /dev/null +++ b/pkg/fanal/test/testdata/test.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 @@ -0,0 +1 @@ +{"created":"2020-01-03T01:21:37.263809283Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"rootfs":{"type":"layers","diff_ids":["sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63"]},"history":[{"created":"2020-01-03T01:21:37.132606296Z","created_by":"/bin/sh -c #(nop) COPY file:7bf12aab75c3867a023fe3b8bd6d113d43a4fcc415f3cc27cbcf0fff37b65a02 in / "},{"created":"2020-01-03T01:21:37.263809283Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c b/pkg/fanal/test/testdata/test.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c new file mode 100644 index 000000000000..097c1a433865 Binary files /dev/null and b/pkg/fanal/test/testdata/test.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c differ diff --git a/pkg/fanal/test/testdata/test.oci/index.json b/pkg/fanal/test/testdata/test.oci/index.json new file mode 100644 index 000000000000..f76e19c9274c --- /dev/null +++ b/pkg/fanal/test/testdata/test.oci/index.json @@ -0,0 +1 @@ +{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab","size":345}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test.oci/oci-layout b/pkg/fanal/test/testdata/test.oci/oci-layout new file mode 100644 index 000000000000..21b1439d1c6d --- /dev/null +++ b/pkg/fanal/test/testdata/test.oci/oci-layout @@ -0,0 +1 @@ +{"imageLayoutVersion": "1.0.0"} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_bad_index_json.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab b/pkg/fanal/test/testdata/test_bad_index_json.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab new file mode 100644 index 000000000000..8967e307376a --- /dev/null +++ b/pkg/fanal/test/testdata/test_bad_index_json.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab @@ -0,0 +1 @@ +{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604","size":584},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c","size":2610}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_bad_index_json.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 b/pkg/fanal/test/testdata/test_bad_index_json.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 new file mode 100644 index 000000000000..1156328a5b74 --- /dev/null +++ b/pkg/fanal/test/testdata/test_bad_index_json.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 @@ -0,0 +1 @@ +{"created":"2020-01-03T01:21:37.263809283Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"rootfs":{"type":"layers","diff_ids":["sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63"]},"history":[{"created":"2020-01-03T01:21:37.132606296Z","created_by":"/bin/sh -c #(nop) COPY file:7bf12aab75c3867a023fe3b8bd6d113d43a4fcc415f3cc27cbcf0fff37b65a02 in / "},{"created":"2020-01-03T01:21:37.263809283Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_bad_index_json.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c b/pkg/fanal/test/testdata/test_bad_index_json.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c new file mode 100644 index 000000000000..097c1a433865 Binary files /dev/null and b/pkg/fanal/test/testdata/test_bad_index_json.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c differ diff --git a/pkg/fanal/test/testdata/test_bad_index_json.oci/index.json b/pkg/fanal/test/testdata/test_bad_index_json.oci/index.json new file mode 100644 index 000000000000..f6ea04951876 --- /dev/null +++ b/pkg/fanal/test/testdata/test_bad_index_json.oci/index.json @@ -0,0 +1 @@ +foobar \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_bad_index_json.oci/oci-layout b/pkg/fanal/test/testdata/test_bad_index_json.oci/oci-layout new file mode 100644 index 000000000000..21b1439d1c6d --- /dev/null +++ b/pkg/fanal/test/testdata/test_bad_index_json.oci/oci-layout @@ -0,0 +1 @@ +{"imageLayoutVersion": "1.0.0"} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_image_tag.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab b/pkg/fanal/test/testdata/test_image_tag.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab new file mode 100644 index 000000000000..8967e307376a --- /dev/null +++ b/pkg/fanal/test/testdata/test_image_tag.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab @@ -0,0 +1 @@ +{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604","size":584},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c","size":2610}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_image_tag.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 b/pkg/fanal/test/testdata/test_image_tag.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 new file mode 100644 index 000000000000..1156328a5b74 --- /dev/null +++ b/pkg/fanal/test/testdata/test_image_tag.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 @@ -0,0 +1 @@ +{"created":"2020-01-03T01:21:37.263809283Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"rootfs":{"type":"layers","diff_ids":["sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63"]},"history":[{"created":"2020-01-03T01:21:37.132606296Z","created_by":"/bin/sh -c #(nop) COPY file:7bf12aab75c3867a023fe3b8bd6d113d43a4fcc415f3cc27cbcf0fff37b65a02 in / "},{"created":"2020-01-03T01:21:37.263809283Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_image_tag.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c b/pkg/fanal/test/testdata/test_image_tag.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c new file mode 100644 index 000000000000..097c1a433865 Binary files /dev/null and b/pkg/fanal/test/testdata/test_image_tag.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c differ diff --git a/pkg/fanal/test/testdata/test_image_tag.oci/index.json b/pkg/fanal/test/testdata/test_image_tag.oci/index.json new file mode 100644 index 000000000000..1655f80927d0 --- /dev/null +++ b/pkg/fanal/test/testdata/test_image_tag.oci/index.json @@ -0,0 +1,13 @@ +{ + "schemaVersion": 2, + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab", + "size": 345, + "annotations": { + "org.opencontainers.image.ref.name": "0.0.1" + } + } + ] +} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_image_tag.oci/oci-layout b/pkg/fanal/test/testdata/test_image_tag.oci/oci-layout new file mode 100644 index 000000000000..21b1439d1c6d --- /dev/null +++ b/pkg/fanal/test/testdata/test_image_tag.oci/oci-layout @@ -0,0 +1 @@ +{"imageLayoutVersion": "1.0.0"} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_index_json_dir.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab b/pkg/fanal/test/testdata/test_index_json_dir.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab new file mode 100644 index 000000000000..8967e307376a --- /dev/null +++ b/pkg/fanal/test/testdata/test_index_json_dir.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab @@ -0,0 +1 @@ +{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604","size":584},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c","size":2610}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_index_json_dir.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 b/pkg/fanal/test/testdata/test_index_json_dir.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 new file mode 100644 index 000000000000..1156328a5b74 --- /dev/null +++ b/pkg/fanal/test/testdata/test_index_json_dir.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 @@ -0,0 +1 @@ +{"created":"2020-01-03T01:21:37.263809283Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"rootfs":{"type":"layers","diff_ids":["sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63"]},"history":[{"created":"2020-01-03T01:21:37.132606296Z","created_by":"/bin/sh -c #(nop) COPY file:7bf12aab75c3867a023fe3b8bd6d113d43a4fcc415f3cc27cbcf0fff37b65a02 in / "},{"created":"2020-01-03T01:21:37.263809283Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_index_json_dir.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c b/pkg/fanal/test/testdata/test_index_json_dir.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c new file mode 100644 index 000000000000..097c1a433865 Binary files /dev/null and b/pkg/fanal/test/testdata/test_index_json_dir.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c differ diff --git a/pkg/fanal/test/testdata/test_index_json_dir.oci/index.json/.keep b/pkg/fanal/test/testdata/test_index_json_dir.oci/index.json/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/test/testdata/test_index_json_dir.oci/oci-layout b/pkg/fanal/test/testdata/test_index_json_dir.oci/oci-layout new file mode 100644 index 000000000000..21b1439d1c6d --- /dev/null +++ b/pkg/fanal/test/testdata/test_index_json_dir.oci/oci-layout @@ -0,0 +1 @@ +{"imageLayoutVersion": "1.0.0"} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab b/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab new file mode 100644 index 000000000000..8967e307376a --- /dev/null +++ b/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab @@ -0,0 +1 @@ +{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604","size":584},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c","size":2610}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 b/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 new file mode 100644 index 000000000000..1156328a5b74 --- /dev/null +++ b/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 @@ -0,0 +1 @@ +{"created":"2020-01-03T01:21:37.263809283Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"rootfs":{"type":"layers","diff_ids":["sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63"]},"history":[{"created":"2020-01-03T01:21:37.132606296Z","created_by":"/bin/sh -c #(nop) COPY file:7bf12aab75c3867a023fe3b8bd6d113d43a4fcc415f3cc27cbcf0fff37b65a02 in / "},{"created":"2020-01-03T01:21:37.263809283Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c b/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c new file mode 100644 index 000000000000..097c1a433865 Binary files /dev/null and b/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c differ diff --git a/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/index.json b/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/index.json new file mode 100644 index 000000000000..f6ea04951876 --- /dev/null +++ b/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/index.json @@ -0,0 +1 @@ +foobar \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/oci-layout b/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/oci-layout new file mode 100644 index 000000000000..21b1439d1c6d --- /dev/null +++ b/pkg/fanal/test/testdata/test_index_json_dir.oci/test_bad_index_json.oci/oci-layout @@ -0,0 +1 @@ +{"imageLayoutVersion": "1.0.0"} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_invalid_oci_image.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab b/pkg/fanal/test/testdata/test_invalid_oci_image.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab new file mode 100644 index 000000000000..8967e307376a --- /dev/null +++ b/pkg/fanal/test/testdata/test_invalid_oci_image.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab @@ -0,0 +1 @@ +{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604","size":584},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c","size":2610}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_invalid_oci_image.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 b/pkg/fanal/test/testdata/test_invalid_oci_image.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 new file mode 100644 index 000000000000..1156328a5b74 --- /dev/null +++ b/pkg/fanal/test/testdata/test_invalid_oci_image.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 @@ -0,0 +1 @@ +{"created":"2020-01-03T01:21:37.263809283Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"rootfs":{"type":"layers","diff_ids":["sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63"]},"history":[{"created":"2020-01-03T01:21:37.132606296Z","created_by":"/bin/sh -c #(nop) COPY file:7bf12aab75c3867a023fe3b8bd6d113d43a4fcc415f3cc27cbcf0fff37b65a02 in / "},{"created":"2020-01-03T01:21:37.263809283Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_invalid_oci_image.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c b/pkg/fanal/test/testdata/test_invalid_oci_image.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c new file mode 100644 index 000000000000..097c1a433865 Binary files /dev/null and b/pkg/fanal/test/testdata/test_invalid_oci_image.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c differ diff --git a/pkg/fanal/test/testdata/test_invalid_oci_image.oci/index.json b/pkg/fanal/test/testdata/test_invalid_oci_image.oci/index.json new file mode 100644 index 000000000000..8fa56d9857ad --- /dev/null +++ b/pkg/fanal/test/testdata/test_invalid_oci_image.oci/index.json @@ -0,0 +1 @@ +{"schemaVersion":2,"manifests":[{"mediaType":"application/badmedia+json","digest":"sha256:bad744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab","size":345}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_invalid_oci_image.oci/oci-layout b/pkg/fanal/test/testdata/test_invalid_oci_image.oci/oci-layout new file mode 100644 index 000000000000..21b1439d1c6d --- /dev/null +++ b/pkg/fanal/test/testdata/test_invalid_oci_image.oci/oci-layout @@ -0,0 +1 @@ +{"imageLayoutVersion": "1.0.0"} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_no_valid_manifests.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab b/pkg/fanal/test/testdata/test_no_valid_manifests.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab new file mode 100644 index 000000000000..8967e307376a --- /dev/null +++ b/pkg/fanal/test/testdata/test_no_valid_manifests.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab @@ -0,0 +1 @@ +{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604","size":584},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c","size":2610}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_no_valid_manifests.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 b/pkg/fanal/test/testdata/test_no_valid_manifests.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 new file mode 100644 index 000000000000..1156328a5b74 --- /dev/null +++ b/pkg/fanal/test/testdata/test_no_valid_manifests.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 @@ -0,0 +1 @@ +{"created":"2020-01-03T01:21:37.263809283Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"rootfs":{"type":"layers","diff_ids":["sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63"]},"history":[{"created":"2020-01-03T01:21:37.132606296Z","created_by":"/bin/sh -c #(nop) COPY file:7bf12aab75c3867a023fe3b8bd6d113d43a4fcc415f3cc27cbcf0fff37b65a02 in / "},{"created":"2020-01-03T01:21:37.263809283Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}]} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_no_valid_manifests.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c b/pkg/fanal/test/testdata/test_no_valid_manifests.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c new file mode 100644 index 000000000000..097c1a433865 Binary files /dev/null and b/pkg/fanal/test/testdata/test_no_valid_manifests.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c differ diff --git a/pkg/fanal/test/testdata/test_no_valid_manifests.oci/index.json b/pkg/fanal/test/testdata/test_no_valid_manifests.oci/index.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/pkg/fanal/test/testdata/test_no_valid_manifests.oci/index.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/test_no_valid_manifests.oci/oci-layout b/pkg/fanal/test/testdata/test_no_valid_manifests.oci/oci-layout new file mode 100644 index 000000000000..21b1439d1c6d --- /dev/null +++ b/pkg/fanal/test/testdata/test_no_valid_manifests.oci/oci-layout @@ -0,0 +1 @@ +{"imageLayoutVersion": "1.0.0"} \ No newline at end of file diff --git a/pkg/fanal/test/testdata/vuln-image.tar.gz b/pkg/fanal/test/testdata/vuln-image.tar.gz new file mode 100644 index 000000000000..4603140c6469 Binary files /dev/null and b/pkg/fanal/test/testdata/vuln-image.tar.gz differ diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go new file mode 100644 index 000000000000..86870d52ee40 --- /dev/null +++ b/pkg/fanal/types/artifact.go @@ -0,0 +1,397 @@ +package types + +import ( + "encoding/json" + "strings" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/sbom/core" +) + +type OS struct { + Family OSType + Name string + Eosl bool `json:"EOSL,omitempty"` + + // This field is used for enhanced security maintenance programs such as Ubuntu ESM, Debian Extended LTS. + Extended bool `json:"extended,omitempty"` +} + +func (o *OS) Detected() bool { + return o.Family != "" +} + +// Merge merges OS version and enhanced security maintenance programs +func (o *OS) Merge(newOS OS) { + if lo.IsEmpty(newOS) { + return + } + + switch { + // OLE also has /etc/redhat-release and it detects OLE as RHEL by mistake. + // In that case, OS must be overwritten with the content of /etc/oracle-release. + // There is the same problem between Debian and Ubuntu. + case o.Family == RedHat, o.Family == Debian: + *o = newOS + default: + if o.Family == "" { + o.Family = newOS.Family + } + if o.Name == "" { + o.Name = newOS.Name + } + // Ubuntu has ESM program: https://ubuntu.com/security/esm + // OS version and esm status are stored in different files. + // We have to merge OS version after parsing these files. + if o.Extended || newOS.Extended { + o.Extended = true + } + } +} + +type Repository struct { + Family OSType `json:",omitempty"` + Release string `json:",omitempty"` +} + +type Layer struct { + Digest string `json:",omitempty"` + DiffID string `json:",omitempty"` + CreatedBy string `json:",omitempty"` +} + +type Package struct { + ID string `json:",omitempty"` + Name string `json:",omitempty"` + Identifier PkgIdentifier `json:",omitempty"` + Version string `json:",omitempty"` + Release string `json:",omitempty"` + Epoch int `json:",omitempty"` + Arch string `json:",omitempty"` + Dev bool `json:",omitempty"` + SrcName string `json:",omitempty"` + SrcVersion string `json:",omitempty"` + SrcRelease string `json:",omitempty"` + SrcEpoch int `json:",omitempty"` + Licenses []string `json:",omitempty"` + Maintainer string `json:",omitempty"` + + Modularitylabel string `json:",omitempty"` // only for Red Hat based distributions + BuildInfo *BuildInfo `json:",omitempty"` // only for Red Hat + Indirect bool `json:",omitempty"` // this package is direct dependency of the project or not + + // Dependencies of this package + // Note: it may have interdependencies, which may lead to infinite loops. + DependsOn []string `json:",omitempty"` + + Layer Layer `json:",omitempty"` + + // Each package metadata have the file path, while the package from lock files does not have. + FilePath string `json:",omitempty"` + + // This is required when using SPDX formats. Otherwise, it will be empty. + Digest digest.Digest `json:",omitempty"` + + // lines from the lock file where the dependency is written + Locations []Location `json:",omitempty"` + + // Files installed by the package + InstalledFiles []string `json:",omitempty"` +} + +// PkgIdentifier represents a software identifiers in one of more of the supported formats. +type PkgIdentifier struct { + PURL *packageurl.PackageURL `json:"-"` + BOMRef string `json:",omitempty"` // For CycloneDX +} + +// MarshalJSON customizes the JSON encoding of PkgIdentifier. +func (id *PkgIdentifier) MarshalJSON() ([]byte, error) { + var p string + if id.PURL != nil { + p = id.PURL.String() + } + + type Alias PkgIdentifier + return json.Marshal(&struct { + PURL string `json:",omitempty"` + *Alias + }{ + PURL: p, + Alias: (*Alias)(id), + }) +} + +// UnmarshalJSON customizes the JSON decoding of PkgIdentifier. +func (id *PkgIdentifier) UnmarshalJSON(data []byte) error { + type Alias PkgIdentifier + aux := &struct { + PURL string `json:",omitempty"` + *Alias + }{ + Alias: (*Alias)(id), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + if aux.PURL != "" { + p, err := packageurl.FromString(aux.PURL) + if err != nil { + return err + } else if len(p.Qualifiers) == 0 { + p.Qualifiers = nil + } + id.PURL = &p + } + + return nil +} + +func (id *PkgIdentifier) Empty() bool { + return id.PURL == nil && id.BOMRef == "" +} + +func (id *PkgIdentifier) Match(s string) bool { + // Encode string as PURL + if strings.HasPrefix(s, "pkg:") { + if p, err := packageurl.FromString(s); err == nil { + s = p.String() + } + } + + switch { + case id.BOMRef == s: + return true + case id.PURL != nil && id.PURL.String() == s: + return true + } + return false +} + +type Location struct { + StartLine int `json:",omitempty"` + EndLine int `json:",omitempty"` +} + +// BuildInfo represents information under /root/buildinfo in RHEL +type BuildInfo struct { + ContentSets []string `json:",omitempty"` + Nvr string `json:",omitempty"` + Arch string `json:",omitempty"` +} + +func (pkg *Package) Empty() bool { + return pkg.Name == "" || pkg.Version == "" +} + +type Packages []Package + +func (pkgs Packages) Len() int { + return len(pkgs) +} + +func (pkgs Packages) Swap(i, j int) { + pkgs[i], pkgs[j] = pkgs[j], pkgs[i] +} + +func (pkgs Packages) Less(i, j int) bool { + switch { + case pkgs[i].Name != pkgs[j].Name: + return pkgs[i].Name < pkgs[j].Name + case pkgs[i].Version != pkgs[j].Version: + return pkgs[i].Version < pkgs[j].Version + } + return pkgs[i].FilePath < pkgs[j].FilePath +} + +// ParentDeps returns a map where the keys are package IDs and the values are the packages +// that depend on the respective package ID (parent dependencies). +func (pkgs Packages) ParentDeps() map[string]Packages { + parents := make(map[string]Packages) + for _, pkg := range pkgs { + for _, dependOn := range pkg.DependsOn { + parents[dependOn] = append(parents[dependOn], pkg) + } + } + + for k, v := range parents { + parents[k] = lo.UniqBy(v, func(pkg Package) string { + return pkg.ID + }) + } + return parents +} + +type SrcPackage struct { + Name string `json:"name"` + Version string `json:"version"` + BinaryNames []string `json:"binaryNames"` +} + +type PackageInfo struct { + FilePath string + Packages Packages +} + +type Application struct { + // e.g. bundler and pipenv + Type LangType + + // Lock files have the file path here, while each package metadata do not have + FilePath string `json:",omitempty"` + + // Libraries is a list of lang-specific packages + Libraries Packages +} + +type File struct { + Type string + Path string + Content []byte +} + +// ArtifactType represents a type of artifact +type ArtifactType string + +const ( + ArtifactContainerImage ArtifactType = "container_image" + ArtifactFilesystem ArtifactType = "filesystem" + ArtifactRepository ArtifactType = "repository" + ArtifactCycloneDX ArtifactType = "cyclonedx" + ArtifactSPDX ArtifactType = "spdx" + ArtifactAWSAccount ArtifactType = "aws_account" + ArtifactVM ArtifactType = "vm" +) + +// ArtifactReference represents a reference of container image, local filesystem and repository +type ArtifactReference struct { + Name string // image name, tar file name, directory or repository name + Type ArtifactType + ID string + BlobIDs []string + ImageMetadata ImageMetadata + + // SBOM + BOM *core.BOM +} + +type ImageMetadata struct { + ID string // image ID + DiffIDs []string // uncompressed layer IDs + RepoTags []string + RepoDigests []string + ConfigFile v1.ConfigFile +} + +// ArtifactInfo is stored in cache +type ArtifactInfo struct { + SchemaVersion int + Architecture string + Created time.Time + DockerVersion string + OS string + + // Misconfiguration holds misconfiguration in container image config + Misconfiguration *Misconfiguration `json:",omitempty"` + + // Secret holds secrets in container image config such as environment variables + Secret *Secret `json:",omitempty"` + + // HistoryPackages are packages extracted from RUN instructions + HistoryPackages Packages `json:",omitempty"` +} + +// BlobInfo is stored in cache +type BlobInfo struct { + SchemaVersion int + + // Layer information + Digest string `json:",omitempty"` + DiffID string `json:",omitempty"` + CreatedBy string `json:",omitempty"` + OpaqueDirs []string `json:",omitempty"` + WhiteoutFiles []string `json:",omitempty"` + + // Analysis result + OS OS `json:",omitempty"` + Repository *Repository `json:",omitempty"` + PackageInfos []PackageInfo `json:",omitempty"` + Applications []Application `json:",omitempty"` + Misconfigurations []Misconfiguration `json:",omitempty"` + Secrets []Secret `json:",omitempty"` + Licenses []LicenseFile `json:",omitempty"` + + // Red Hat distributions have build info per layer. + // This information will be embedded into packages when applying layers. + // ref. https://redhat-connect.gitbook.io/partner-guide-for-adopting-red-hat-oval-v2/determining-common-platform-enumeration-cpe + BuildInfo *BuildInfo `json:",omitempty"` + + // CustomResources hold analysis results from custom analyzers. + // It is for extensibility and not used in OSS. + CustomResources []CustomResource `json:",omitempty"` +} + +// ArtifactDetail represents the analysis result. +type ArtifactDetail struct { + OS OS `json:",omitempty"` + Repository *Repository `json:",omitempty"` + Packages Packages `json:",omitempty"` + Applications []Application `json:",omitempty"` + Misconfigurations []Misconfiguration `json:",omitempty"` + Secrets []Secret `json:",omitempty"` + Licenses []LicenseFile `json:",omitempty"` + + // ImageConfig has information from container image config + ImageConfig ImageConfigDetail + + // CustomResources hold analysis results from custom analyzers. + // It is for extensibility and not used in OSS. + CustomResources []CustomResource `json:",omitempty"` +} + +// ImageConfigDetail has information from container image config +type ImageConfigDetail struct { + // Packages are packages extracted from RUN instructions in history + Packages []Package `json:",omitempty"` + + // Misconfiguration holds misconfigurations in container image config + Misconfiguration *Misconfiguration `json:",omitempty"` + + // Secret holds secrets in container image config + Secret *Secret `json:",omitempty"` +} + +// ToBlobInfo is used to store a merged layer in cache. +func (a *ArtifactDetail) ToBlobInfo() BlobInfo { + return BlobInfo{ + SchemaVersion: BlobJSONSchemaVersion, + OS: a.OS, + Repository: a.Repository, + PackageInfos: []PackageInfo{ + { + FilePath: "merged", // Set a dummy file path + Packages: a.Packages, + }, + }, + Applications: a.Applications, + Misconfigurations: a.Misconfigurations, + Secrets: a.Secrets, + Licenses: a.Licenses, + CustomResources: a.CustomResources, + } +} + +// CustomResource holds the analysis result from a custom analyzer. +// It is for extensibility and not used in OSS. +type CustomResource struct { + Type string + FilePath string + Layer Layer + Data interface{} +} diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go new file mode 100644 index 000000000000..b46b36a8d425 --- /dev/null +++ b/pkg/fanal/types/const.go @@ -0,0 +1,142 @@ +package types + +type ( + // TargetType represents the type of target + TargetType string + + // OSType is an alias of TargetType for operating systems + OSType = TargetType + + // LangType is an alias of TargetType for programming languages + LangType = TargetType + + // ConfigType is an alias of TargetType for configuration files + ConfigType = TargetType +) + +const ( + ArtifactJSONSchemaVersion = 1 + BlobJSONSchemaVersion = 2 +) + +// Operating systems +const ( + Alma OSType = "alma" + Alpine OSType = "alpine" + Amazon OSType = "amazon" + CBLMariner OSType = "cbl-mariner" + CentOS OSType = "centos" + Chainguard OSType = "chainguard" + Debian OSType = "debian" + Fedora OSType = "fedora" + OpenSUSE OSType = "opensuse" + OpenSUSELeap OSType = "opensuse.leap" + OpenSUSETumbleweed OSType = "opensuse.tumbleweed" + Oracle OSType = "oracle" + Photon OSType = "photon" + RedHat OSType = "redhat" + Rocky OSType = "rocky" + SLES OSType = "suse linux enterprise server" + Ubuntu OSType = "ubuntu" + Wolfi OSType = "wolfi" +) + +// Programming language dependencies +const ( + Bundler LangType = "bundler" + GemSpec LangType = "gemspec" + Cargo LangType = "cargo" + Composer LangType = "composer" + Npm LangType = "npm" + NuGet LangType = "nuget" + DotNetCore LangType = "dotnet-core" + PackagesProps LangType = "packages-props" + Pip LangType = "pip" + Pipenv LangType = "pipenv" + Poetry LangType = "poetry" + CondaPkg LangType = "conda-pkg" + PythonPkg LangType = "python-pkg" + NodePkg LangType = "node-pkg" + Yarn LangType = "yarn" + Pnpm LangType = "pnpm" + Jar LangType = "jar" + Pom LangType = "pom" + Gradle LangType = "gradle" + GoBinary LangType = "gobinary" + GoModule LangType = "gomod" + JavaScript LangType = "javascript" + RustBinary LangType = "rustbinary" + Conan LangType = "conan" + Cocoapods LangType = "cocoapods" + Swift LangType = "swift" + Pub LangType = "pub" + Hex LangType = "hex" + Bitnami LangType = "bitnami" + + K8sUpstream LangType = "kubernetes" + EKS LangType = "eks" // Amazon Elastic Kubernetes Service + GKE LangType = "gke" // Google Kubernetes Engine + AKS LangType = "aks" // Azure Kubernetes Service + RKE LangType = "rke" // Rancher Kubernetes Engine + OCP LangType = "ocp" // Red Hat OpenShift Container Platform +) + +var AggregatingTypes = []LangType{ + PythonPkg, + CondaPkg, + GemSpec, + NodePkg, + Jar, +} + +// Config files +const ( + JSON ConfigType = "json" + Dockerfile ConfigType = "dockerfile" + Terraform ConfigType = "terraform" + TerraformPlanJSON ConfigType = "terraformplan" + TerraformPlanSnapshot ConfigType = "terraformplan-snapshot" + CloudFormation ConfigType = "cloudformation" + Kubernetes ConfigType = "kubernetes" + Helm ConfigType = "helm" + Cloud ConfigType = "cloud" + AzureARM ConfigType = "azure-arm" +) + +// Language-specific file names +const ( + NuGetPkgsLock = "packages.lock.json" + NuGetPkgsConfig = "packages.config" + + GoMod = "go.mod" + GoSum = "go.sum" + + MavenPom = "pom.xml" + + NpmPkg = "package.json" + NpmPkgLock = "package-lock.json" + YarnLock = "yarn.lock" + PnpmLock = "pnpm-lock.yaml" + + ComposerLock = "composer.lock" + ComposerJson = "composer.json" + + PyProject = "pyproject.toml" + PipRequirements = "requirements.txt" + PipfileLock = "Pipfile.lock" + PoetryLock = "poetry.lock" + + GemfileLock = "Gemfile.lock" + + CargoLock = "Cargo.lock" + CargoToml = "Cargo.toml" + + ConanLock = "conan.lock" + + CocoaPodsLock = "Podfile.lock" + SwiftResolved = "Package.resolved" + + PubSpecLock = "pubspec.lock" + + MixLock = "mix.lock" +) diff --git a/pkg/fanal/types/error.go b/pkg/fanal/types/error.go new file mode 100644 index 000000000000..02272d6fbd5d --- /dev/null +++ b/pkg/fanal/types/error.go @@ -0,0 +1,8 @@ +package types + +import "golang.org/x/xerrors" + +var ( + InvalidURLPattern = xerrors.New("invalid url pattern") + ErrNoRpmCmd = xerrors.New("no rpm command") +) diff --git a/pkg/fanal/types/handler.go b/pkg/fanal/types/handler.go new file mode 100644 index 000000000000..d53847c896c4 --- /dev/null +++ b/pkg/fanal/types/handler.go @@ -0,0 +1,13 @@ +package types + +type HandlerType string + +const ( + SystemFileFilteringPostHandler HandlerType = "system-file-filter" + UnpackagedPostHandler HandlerType = "unpackaged" + + // SystemFileFilteringPostHandlerPriority should be higher than other handlers. + // Otherwise, other handlers need to process unnecessary files. + SystemFileFilteringPostHandlerPriority = 100 + UnpackagedPostHandlerPriority = 50 +) diff --git a/pkg/fanal/types/image.go b/pkg/fanal/types/image.go new file mode 100644 index 000000000000..91cdfb44b60c --- /dev/null +++ b/pkg/fanal/types/image.go @@ -0,0 +1,107 @@ +package types + +import ( + v1 "github.com/google/go-containerregistry/pkg/v1" +) + +const ( + // DockerImageSource is the docker runtime + DockerImageSource ImageSource = "docker" + + // ContainerdImageSource is the containerd runtime + ContainerdImageSource ImageSource = "containerd" + + // PodmanImageSource is the podman runtime + PodmanImageSource ImageSource = "podman" + + // RemoteImageSource represents a remote scan + RemoteImageSource ImageSource = "remote" +) + +var ( + AllImageSources = ImageSources{ + DockerImageSource, + ContainerdImageSource, + PodmanImageSource, + RemoteImageSource, + } +) + +type Platform struct { + *v1.Platform + + // Force returns an error if the specified platform is not found. + // This option is for Aqua, and cannot be configured via Trivy CLI. + Force bool +} + +type Image interface { + v1.Image + ImageExtension +} + +type ImageExtension interface { + Name() string + ID() (string, error) + RepoTags() []string + RepoDigests() []string +} + +type ImageOptions struct { + RegistryOptions RegistryOptions + DockerOptions DockerOptions + PodmanOptions PodmanOptions + ContainerdOptions ContainerdOptions + ImageSources ImageSources +} + +type DockerOptions struct { + Host string +} + +type PodmanOptions struct { + Host string +} + +type ContainerdOptions struct { + // Add Containerd-specific options +} + +// ImageSource represents the source of an image. It can be a string that identifies +// the container registry or a type of container runtime. +type ImageSource string + +// ImageSources is a slice of image sources +type ImageSources []ImageSource + +type RegistryOptions struct { + // Auth for registries + Credentials []Credential + + // RegistryToken is a bearer token to be sent to a registry + RegistryToken string + + // SSL/TLS + Insecure bool + + // For internal use. Needed for mTLS authentication. + ClientCert []byte + ClientKey []byte + + // Architecture + Platform Platform + + // ECR + AWSAccessKey string + AWSSecretKey string + AWSSessionToken string + AWSRegion string + + // GCP + GCPCredPath string +} + +type Credential struct { + Username string + Password string +} diff --git a/pkg/fanal/types/license.go b/pkg/fanal/types/license.go new file mode 100644 index 000000000000..a168b1983b5b --- /dev/null +++ b/pkg/fanal/types/license.go @@ -0,0 +1,58 @@ +package types + +import "github.com/samber/lo" + +type LicenseType string + +const ( + LicenseTypeDpkg LicenseType = "dpkg" // From /usr/share/doc/*/copyright + LicenseTypeHeader LicenseType = "header" // From file headers + LicenseTypeFile LicenseType = "license-file" // From LICENSE, COPYRIGHT, etc. +) + +type LicenseCategory string + +const ( + CategoryForbidden LicenseCategory = "forbidden" + CategoryRestricted LicenseCategory = "restricted" + CategoryReciprocal LicenseCategory = "reciprocal" + CategoryNotice LicenseCategory = "notice" + CategoryPermissive LicenseCategory = "permissive" + CategoryUnencumbered LicenseCategory = "unencumbered" + CategoryUnknown LicenseCategory = "unknown" +) + +type LicenseFile struct { + Type LicenseType + FilePath string + PkgName string + Findings LicenseFindings + Layer Layer `json:",omitempty"` +} + +type LicenseFindings []LicenseFinding + +func (findings LicenseFindings) Len() int { + return len(findings) +} + +func (findings LicenseFindings) Swap(i, j int) { + findings[i], findings[j] = findings[j], findings[i] +} + +func (findings LicenseFindings) Less(i, j int) bool { + return findings[i].Name < findings[j].Name +} + +func (findings LicenseFindings) Names() []string { + return lo.Map(findings, func(finding LicenseFinding, _ int) string { + return finding.Name + }) +} + +type LicenseFinding struct { + Category LicenseCategory // such as "forbidden" + Name string + Confidence float64 + Link string +} diff --git a/pkg/fanal/types/misconf.go b/pkg/fanal/types/misconf.go new file mode 100644 index 000000000000..3b4e9b447895 --- /dev/null +++ b/pkg/fanal/types/misconf.go @@ -0,0 +1,144 @@ +package types + +import ( + "fmt" + "sort" + + "github.com/samber/lo" +) + +type Misconfiguration struct { + FileType ConfigType `json:",omitempty"` + FilePath string `json:",omitempty"` + Successes MisconfResults `json:",omitempty"` + Warnings MisconfResults `json:",omitempty"` + Failures MisconfResults `json:",omitempty"` + Exceptions MisconfResults `json:",omitempty"` + Layer Layer `json:",omitempty"` +} + +type MisconfResult struct { + Namespace string `json:",omitempty"` + Query string `json:",omitempty"` + Message string `json:",omitempty"` + PolicyMetadata `json:",omitempty"` + CauseMetadata `json:",omitempty"` + + // For debugging + Traces []string `json:",omitempty"` +} + +type MisconfResults []MisconfResult + +type CauseMetadata struct { + Resource string `json:",omitempty"` + Provider string `json:",omitempty"` + Service string `json:",omitempty"` + StartLine int `json:",omitempty"` + EndLine int `json:",omitempty"` + Code Code `json:",omitempty"` + Occurrences []Occurrence `json:",omitempty"` +} + +type Occurrence struct { + Resource string `json:",omitempty"` + Filename string `json:",omitempty"` + Location Location +} + +type Code struct { + Lines []Line +} + +type Line struct { + Number int `json:"Number"` + Content string `json:"Content"` + IsCause bool `json:"IsCause"` + Annotation string `json:"Annotation"` + Truncated bool `json:"Truncated"` + Highlighted string `json:"Highlighted,omitempty"` + FirstCause bool `json:"FirstCause"` + LastCause bool `json:"LastCause"` +} + +type PolicyMetadata struct { + ID string `json:",omitempty"` + AVDID string `json:",omitempty"` + Type string `json:",omitempty"` + Title string `json:",omitempty"` + Description string `json:",omitempty"` + Severity string `json:",omitempty"` + RecommendedActions string `json:",omitempty" mapstructure:"recommended_actions"` + References []string `json:",omitempty"` +} + +type PolicyInputOption struct { + Combine bool `mapstructure:"combine"` + Selectors []PolicyInputSelector `mapstructure:"selector"` +} + +type PolicyInputSelector struct { + Type string `mapstructure:"type"` +} + +func (r MisconfResults) Len() int { + return len(r) +} + +func (r MisconfResults) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +func (r MisconfResults) Less(i, j int) bool { + switch { + case r[i].Type != r[j].Type: + return r[i].Type < r[j].Type + case r[i].AVDID != r[j].AVDID: + return r[i].AVDID < r[j].AVDID + case r[i].ID != r[j].ID: + return r[i].ID < r[j].ID + case r[i].Severity != r[j].Severity: + return r[i].Severity < r[j].Severity + case r[i].Resource != r[j].Resource: + return r[i].Resource < r[j].Resource + } + return r[i].Message < r[j].Message +} + +func ToMisconfigurations(misconfs map[string]Misconfiguration) []Misconfiguration { + var results []Misconfiguration + for _, misconf := range misconfs { + // Remove duplicates + misconf.Successes = uniqueResults(misconf.Successes) + misconf.Warnings = uniqueResults(misconf.Warnings) + misconf.Failures = uniqueResults(misconf.Failures) + + // Sort results + sort.Sort(misconf.Successes) + sort.Sort(misconf.Warnings) + sort.Sort(misconf.Failures) + sort.Sort(misconf.Exceptions) + + results = append(results, misconf) + } + + // Sort misconfigurations + sort.Slice(results, func(i, j int) bool { + if results[i].FileType != results[j].FileType { + return results[i].FileType < results[j].FileType + } + return results[i].FilePath < results[j].FilePath + }) + + return results +} + +func uniqueResults(results []MisconfResult) []MisconfResult { + if len(results) == 0 { + return results + } + return lo.UniqBy(results, func(result MisconfResult) string { + return fmt.Sprintf("ID: %s, Namespace: %s, Messsage: %s, Cause: %v", + result.ID, result.Namespace, result.Message, result.CauseMetadata) + }) +} diff --git a/pkg/fanal/types/secret.go b/pkg/fanal/types/secret.go new file mode 100644 index 000000000000..a2747cad75f5 --- /dev/null +++ b/pkg/fanal/types/secret.go @@ -0,0 +1,20 @@ +package types + +type SecretRuleCategory string + +type Secret struct { + FilePath string + Findings []SecretFinding +} + +type SecretFinding struct { + RuleID string + Category SecretRuleCategory + Severity string + Title string + StartLine int + EndLine int + Code Code + Match string + Layer Layer `json:",omitempty"` +} diff --git a/pkg/fanal/utils/testdata/aqua.png b/pkg/fanal/utils/testdata/aqua.png new file mode 100644 index 000000000000..80c2f5fded03 Binary files /dev/null and b/pkg/fanal/utils/testdata/aqua.png differ diff --git a/pkg/fanal/utils/testdata/test.tar.gz b/pkg/fanal/utils/testdata/test.tar.gz new file mode 100644 index 000000000000..ad92b40a48c4 Binary files /dev/null and b/pkg/fanal/utils/testdata/test.tar.gz differ diff --git a/pkg/fanal/utils/testdata/test.txt b/pkg/fanal/utils/testdata/test.txt new file mode 100644 index 000000000000..06d740502001 Binary files /dev/null and b/pkg/fanal/utils/testdata/test.txt differ diff --git a/pkg/fanal/utils/testdata/test.txt.gz b/pkg/fanal/utils/testdata/test.txt.gz new file mode 100644 index 000000000000..ec1e1784b1e3 Binary files /dev/null and b/pkg/fanal/utils/testdata/test.txt.gz differ diff --git a/pkg/fanal/utils/testdata/test.txt.zst b/pkg/fanal/utils/testdata/test.txt.zst new file mode 100644 index 000000000000..2981873cdf95 Binary files /dev/null and b/pkg/fanal/utils/testdata/test.txt.zst differ diff --git a/pkg/fanal/utils/testdata/testdir.tar.gz b/pkg/fanal/utils/testdata/testdir.tar.gz new file mode 100644 index 000000000000..d6ae289abac0 Binary files /dev/null and b/pkg/fanal/utils/testdata/testdir.tar.gz differ diff --git a/pkg/fanal/utils/utils.go b/pkg/fanal/utils/utils.go new file mode 100644 index 000000000000..1c5ea54b2612 --- /dev/null +++ b/pkg/fanal/utils/utils.go @@ -0,0 +1,95 @@ +package utils + +import ( + "bufio" + "fmt" + "io" + "math" + "os" + "os/exec" + "path/filepath" + + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +var ( + PathSeparator = fmt.Sprintf("%c", os.PathSeparator) +) + +func CacheDir() string { + cacheDir, err := os.UserCacheDir() + if err != nil { + cacheDir = os.TempDir() + } + return cacheDir +} + +func StringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +func IsCommandAvailable(name string) bool { + if _, err := exec.LookPath(name); err != nil { + return false + } + return true +} + +func IsGzip(f *bufio.Reader) bool { + buf, err := f.Peek(3) + if err != nil { + return false + } + return buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x8 +} + +func Keys(m map[string]struct{}) []string { + var keys []string + for k := range m { + keys = append(keys, k) + } + return keys +} + +func IsExecutable(fileInfo os.FileInfo) bool { + // For Windows + if filepath.Ext(fileInfo.Name()) == ".exe" { + return true + } + + mode := fileInfo.Mode() + if !mode.IsRegular() { + return false + } + + // Check unpackaged file + if mode.Perm()&0111 != 0 { + return true + } + return false +} + +func IsBinary(content xio.ReadSeekerAt, fileSize int64) (bool, error) { + headSize := int(math.Min(float64(fileSize), 300)) + head := make([]byte, headSize) + if _, err := content.Read(head); err != nil { + return false, err + } + if _, err := content.Seek(0, io.SeekStart); err != nil { + return false, err + } + + // cf. https://github.com/file/file/blob/f2a6e7cb7db9b5fd86100403df6b2f830c7f22ba/src/encoding.c#L151-L228 + for _, b := range head { + if b < 7 || b == 11 || (13 < b && b < 27) || (27 < b && b < 0x20) || b == 0x7f { + return true, nil + } + } + + return false, nil +} diff --git a/pkg/fanal/utils/utils_test.go b/pkg/fanal/utils/utils_test.go new file mode 100644 index 000000000000..0b497369db71 --- /dev/null +++ b/pkg/fanal/utils/utils_test.go @@ -0,0 +1,34 @@ +package utils + +import ( + "bufio" + "os" + "path/filepath" + "testing" +) + +func TestIsGzip(t *testing.T) { + var tests = []struct { + in string + want bool + }{ + {filepath.Join("testdata", "test.txt.gz"), true}, + {filepath.Join("testdata", "test.tar.gz"), true}, + {filepath.Join("testdata", "test.txt"), false}, + {filepath.Join("testdata", "test.txt.zst"), false}, + {filepath.Join("testdata", "aqua.png"), false}, + } + for _, tt := range tests { + t.Run(tt.in, func(t *testing.T) { + f, err := os.Open(tt.in) + if err != nil { + t.Fatalf("unknown error: %s", err) + } + + got := IsGzip(bufio.NewReader(f)) + if got != tt.want { + t.Errorf("got %t, want %t", got, tt.want) + } + }) + } +} diff --git a/pkg/fanal/vm/disk/disk.go b/pkg/fanal/vm/disk/disk.go new file mode 100644 index 000000000000..bf39707e2eb5 --- /dev/null +++ b/pkg/fanal/vm/disk/disk.go @@ -0,0 +1,37 @@ +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(io.ReadSeeker, vm.Cache[string, []byte]) (*io.SectionReader, error) +} + +func New(rs io.ReadSeeker, cache vm.Cache[string, []byte]) (*io.SectionReader, error) { + + for _, vmdisk := range vmDisks { + var vreader, err = vmdisk.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 000000000000..df6f906bf812 --- /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[string, []byte] + } + 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 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/vm/disk/vmdk.go b/pkg/fanal/vm/disk/vmdk.go new file mode 100644 index 000000000000..3be79780e43f --- /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 (VMDK) NewReader(rs io.ReadSeeker, cache vm.Cache[string, []byte]) (*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 000000000000..4c62a202b423 --- /dev/null +++ b/pkg/fanal/vm/disk/vmdk_test.go @@ -0,0 +1,41 @@ +package disk_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/vm/disk" +) + +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 000000000000..f2476988450a --- /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[string, any]) (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 000000000000..ae77610579c4 --- /dev/null +++ b/pkg/fanal/vm/filesystem/filesystem.go @@ -0,0 +1,50 @@ +package filesystem + +import ( + "errors" + "io" + "io/fs" + + lru "github.com/hashicorp/golang-lru/v2" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/vm" +) + +const cacheSize = 2048 + +var ( + ErrInvalidHeader = xerrors.New("invalid Header error") + filesystems = []Filesystem{ + EXT4{}, + XFS{}, + } +) + +type Filesystem interface { + New(io.SectionReader, vm.Cache[string, any]) (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[string, any](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 000000000000..d1344e846354 --- /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[string, any]) (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 000000000000..bfb414263712 --- /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[K comparable, V any] interface { + // Add stores data in the cache + Add(key K, value V) (evicted bool) + + // Get returns key's value from the cache + Get(key K) (value V, ok bool) +} diff --git a/pkg/fanal/walker/cached_file.go b/pkg/fanal/walker/cached_file.go new file mode 100644 index 000000000000..05cfa1443c39 --- /dev/null +++ b/pkg/fanal/walker/cached_file.go @@ -0,0 +1,85 @@ +package walker + +import ( + "bytes" + "io" + "os" + "sync" + + "golang.org/x/xerrors" + + xio "github.com/aquasecurity/trivy/pkg/x/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() (xio.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() (xio.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 xio.NopCloser(bytes.NewReader(o.content)), nil +} + +func (o *cachedFile) Clean() error { + return os.Remove(o.filePath) +} diff --git a/pkg/fanal/walker/fs.go b/pkg/fanal/walker/fs.go new file mode 100644 index 000000000000..5397c1bdfc10 --- /dev/null +++ b/pkg/fanal/walker/fs.go @@ -0,0 +1,121 @@ +package walker + +import ( + "io/fs" + "os" + "path/filepath" + + swalker "github.com/saracen/walker" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type ErrorCallback func(pathname string, err error) error + +type FS struct { + walker + parallel int + errCallback ErrorCallback +} + +func NewFS(skipFiles, skipDirs []string, parallel int, errCallback ErrorCallback) FS { + if errCallback == nil { + errCallback = func(pathname string, err error) error { + // ignore permission errors + if os.IsPermission(err) { + return nil + } + // halt traversal on any other error + return xerrors.Errorf("unknown error with %s: %w", pathname, err) + } + } + + return FS{ + walker: newWalker(skipFiles, skipDirs), + parallel: parallel, + errCallback: errCallback, + } +} + +// Walk walks the file tree rooted at root, calling WalkFunc for each file or +// directory in the tree, including root, but a directory to be ignored will be skipped. +func (w FS) Walk(root string, fn WalkFunc) error { + // walk function called for every path found + walkFn := func(pathname string, fi os.FileInfo) error { + pathname = filepath.Clean(pathname) + + // For exported rootfs (e.g. images/alpine/etc/alpine-release) + relPath, err := filepath.Rel(root, pathname) + if err != nil { + return xerrors.Errorf("filepath rel (%s): %w", relPath, err) + } + relPath = filepath.ToSlash(relPath) + + switch { + case fi.IsDir(): + if w.shouldSkipDir(relPath) { + return filepath.SkipDir + } + return nil + case !fi.Mode().IsRegular(): + return nil + case w.shouldSkipFile(relPath): + return nil + } + + if err = fn(relPath, fi, w.fileOpener(pathname)); err != nil { + return xerrors.Errorf("failed to analyze file: %w", err) + } + return nil + } + + if w.parallel <= 1 { + // In series: fast, with higher CPU/memory + return w.walkSlow(root, walkFn) + } + + // In parallel: slow, with lower CPU/memory + return w.walkFast(root, walkFn) +} + +type fastWalkFunc func(pathname string, fi os.FileInfo) error + +func (w FS) walkFast(root string, walkFn fastWalkFunc) error { + // error function called for every error encountered + errorCallbackOption := swalker.WithErrorCallback(w.errCallback) + + // Multiple goroutines stat the filesystem concurrently. The provided + // walkFn must be safe for concurrent use. + log.Logger.Debugf("Walk the file tree rooted at '%s' in parallel", root) + if err := swalker.Walk(root, walkFn, errorCallbackOption); err != nil { + return xerrors.Errorf("walk error: %w", err) + } + return nil +} + +func (w FS) walkSlow(root string, walkFn fastWalkFunc) error { + log.Logger.Debugf("Walk the file tree rooted at '%s' in series", root) + err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return w.errCallback(path, err) + } + info, err := d.Info() + if err != nil { + return xerrors.Errorf("file info error: %w", err) + } + return walkFn(path, info) + }) + if err != nil { + return xerrors.Errorf("walk dir error: %w", err) + } + return nil +} + +// fileOpener returns a function opening a file. +func (w *walker) fileOpener(pathname string) func() (xio.ReadSeekCloserAt, error) { + return func() (xio.ReadSeekCloserAt, error) { + return os.Open(pathname) + } +} diff --git a/pkg/fanal/walker/fs_test.go b/pkg/fanal/walker/fs_test.go new file mode 100644 index 000000000000..aa84f6d23503 --- /dev/null +++ b/pkg/fanal/walker/fs_test.go @@ -0,0 +1,107 @@ +package walker_test + +import ( + "errors" + "io" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/walker" +) + +func TestDir_Walk(t *testing.T) { + type fields struct { + skipFiles []string + skipDirs []string + errCallback walker.ErrorCallback + } + tests := []struct { + name string + fields fields + rootDir string + analyzeFn walker.WalkFunc + wantErr string + }{ + { + name: "happy path", + rootDir: "testdata/fs", + analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + if filePath == "testdata/fs/bar" { + got, err := opener() + require.NoError(t, err) + + b, err := io.ReadAll(got) + require.NoError(t, err) + + assert.Equal(t, "bar", string(b)) + } + return nil + }, + }, + { + name: "skip file", + rootDir: "testdata/fs", + fields: fields{ + skipFiles: []string{"testdata/fs/bar"}, + }, + analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + if filePath == "testdata/fs/bar" { + assert.Fail(t, "skip files error", "%s should be skipped", filePath) + } + return nil + }, + }, + { + name: "skip dir", + rootDir: "testdata/fs/", + fields: fields{ + skipDirs: []string{"/testdata/fs/app"}, + }, + analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + if strings.HasPrefix(filePath, "testdata/fs/app") { + assert.Fail(t, "skip dirs error", "%s should be skipped", filePath) + } + return nil + }, + }, + { + name: "ignore all errors", + rootDir: "testdata/fs/nosuch", + fields: fields{ + errCallback: func(pathname string, err error) error { + return nil + }, + }, + analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + // Ignore errors + return nil + }, + }, + { + name: "sad path", + rootDir: "testdata/fs", + analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + return errors.New("error") + }, + wantErr: "failed to analyze file", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := walker.NewFS(tt.fields.skipFiles, tt.fields.skipDirs, 1, tt.fields.errCallback) + + err := w.Walk(tt.rootDir, tt.analyzeFn) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + }) + } +} diff --git a/pkg/fanal/walker/tar.go b/pkg/fanal/walker/tar.go new file mode 100644 index 000000000000..d548d06f9c0a --- /dev/null +++ b/pkg/fanal/walker/tar.go @@ -0,0 +1,117 @@ +package walker + +import ( + "archive/tar" + "io" + "io/fs" + "path" + "path/filepath" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/utils" +) + +const ( + opq string = ".wh..wh..opq" + wh string = ".wh." +) + +var parentDir = ".." + utils.PathSeparator + +type LayerTar struct { + walker + threshold int64 +} + +func NewLayerTar(skipFiles, skipDirs []string) LayerTar { + threshold := defaultSizeThreshold + return LayerTar{ + walker: newWalker(skipFiles, skipDirs), + threshold: threshold, + } +} + +func (w LayerTar) Walk(layer io.Reader, analyzeFn WalkFunc) ([]string, []string, error) { + var opqDirs, whFiles, skipDirs []string + tr := tar.NewReader(layer) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } else if err != nil { + return nil, nil, xerrors.Errorf("failed to extract the archive: %w", err) + } + + // filepath.Clean cannot be used since tar file paths should be OS-agnostic. + filePath := path.Clean(hdr.Name) + filePath = strings.TrimLeft(filePath, "/") + fileDir, fileName := path.Split(filePath) + + // e.g. etc/.wh..wh..opq + if opq == fileName { + opqDirs = append(opqDirs, fileDir) + continue + } + // etc/.wh.hostname + if strings.HasPrefix(fileName, wh) { + name := strings.TrimPrefix(fileName, wh) + fpath := path.Join(fileDir, name) + whFiles = append(whFiles, fpath) + continue + } + + switch hdr.Typeflag { + case tar.TypeDir: + if w.shouldSkipDir(filePath) { + skipDirs = append(skipDirs, filePath) + continue + } + case tar.TypeReg: + if w.shouldSkipFile(filePath) { + continue + } + // symlinks and hardlinks have no content in reader, skip them + default: + continue + } + + if underSkippedDir(filePath, skipDirs) { + continue + } + + // A regular file will reach here. + if err = w.processFile(filePath, tr, hdr.FileInfo(), analyzeFn); err != nil { + return nil, nil, xerrors.Errorf("failed to process the file: %w", err) + } + } + return opqDirs, whFiles, nil +} + +func (w LayerTar) processFile(filePath string, tr *tar.Reader, fi fs.FileInfo, analyzeFn WalkFunc) error { + cf := newCachedFile(fi.Size(), tr, w.threshold) + defer func() { + // nolint + _ = cf.Clean() + }() + + if err := analyzeFn(filePath, fi, cf.Open); err != nil { + return xerrors.Errorf("failed to analyze file: %w", err) + } + + return nil +} + +func underSkippedDir(filePath string, skipDirs []string) bool { + for _, skipDir := range skipDirs { + rel, err := filepath.Rel(skipDir, filePath) + if err != nil { + return false + } + if !strings.HasPrefix(rel, parentDir) { + return true + } + } + return false +} diff --git a/pkg/fanal/walker/tar_test.go b/pkg/fanal/walker/tar_test.go new file mode 100644 index 000000000000..fed2f9efea7d --- /dev/null +++ b/pkg/fanal/walker/tar_test.go @@ -0,0 +1,97 @@ +package walker_test + +import ( + "errors" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/walker" +) + +func TestLayerTar_Walk(t *testing.T) { + type fields struct { + skipFiles []string + skipDirs []string + } + tests := []struct { + name string + fields fields + inputFile string + analyzeFn walker.WalkFunc + wantOpqDirs []string + wantWhFiles []string + wantErr string + }{ + { + name: "happy path", + inputFile: filepath.Join("testdata", "test.tar"), + analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + return nil + }, + wantOpqDirs: []string{"etc/"}, + wantWhFiles: []string{"foo/foo"}, + }, + { + name: "skip file", + inputFile: filepath.Join("testdata", "test.tar"), + fields: fields{ + skipFiles: []string{"/app/myweb/index.html"}, + }, + analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + if filePath == "app/myweb/index.html" { + assert.Fail(t, "skip files error", "%s should be skipped", filePath) + } + return nil + }, + wantOpqDirs: []string{"etc/"}, + wantWhFiles: []string{"foo/foo"}, + }, + { + name: "skip dir", + inputFile: filepath.Join("testdata", "test.tar"), + fields: fields{ + skipDirs: []string{"/app"}, + }, + analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + if strings.HasPrefix(filePath, "app") { + assert.Fail(t, "skip dirs error", "%s should be skipped", filePath) + } + return nil + }, + wantOpqDirs: []string{"etc/"}, + wantWhFiles: []string{"foo/foo"}, + }, + { + name: "sad path", + inputFile: "testdata/test.tar", + analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + return errors.New("error") + }, + wantErr: "failed to analyze file", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open("testdata/test.tar") + require.NoError(t, err) + + w := walker.NewLayerTar(tt.fields.skipFiles, tt.fields.skipDirs) + + gotOpqDirs, gotWhFiles, err := w.Walk(f, tt.analyzeFn) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.wantOpqDirs, gotOpqDirs) + assert.Equal(t, tt.wantWhFiles, gotWhFiles) + }) + } +} diff --git a/pkg/fanal/walker/testdata/fs/app/myweb/test.txt b/pkg/fanal/walker/testdata/fs/app/myweb/test.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/fanal/walker/testdata/fs/bar b/pkg/fanal/walker/testdata/fs/bar new file mode 100644 index 000000000000..ba0e162e1c47 --- /dev/null +++ b/pkg/fanal/walker/testdata/fs/bar @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/pkg/fanal/walker/testdata/fs/sym.txt b/pkg/fanal/walker/testdata/fs/sym.txt new file mode 120000 index 000000000000..ba0e162e1c47 --- /dev/null +++ b/pkg/fanal/walker/testdata/fs/sym.txt @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/pkg/fanal/walker/testdata/test.tar b/pkg/fanal/walker/testdata/test.tar new file mode 100644 index 000000000000..72d5e6fce956 Binary files /dev/null and b/pkg/fanal/walker/testdata/test.tar differ diff --git a/pkg/fanal/walker/vm.go b/pkg/fanal/walker/vm.go new file mode 100644 index 000000000000..8d13e0aee57c --- /dev/null +++ b/pkg/fanal/walker/vm.go @@ -0,0 +1,230 @@ +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" + + "github.com/aquasecurity/trivy/pkg/fanal/vm/filesystem" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +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) *VM { + threshold := defaultSizeThreshold + return &VM{ + walker: newWalker(skipFiles, skipDirs), + 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), "/") + switch { + case fi.IsDir(): + if w.shouldSkipDir(pathName) { + return filepath.SkipDir + } + return nil + case !fi.Mode().IsRegular(): + return nil + case w.shouldSkipFile(pathName): + return nil + case 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() (xio.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/fanal/walker/walk.go b/pkg/fanal/walker/walk.go new file mode 100644 index 000000000000..cebc86ee76d7 --- /dev/null +++ b/pkg/fanal/walker/walk.go @@ -0,0 +1,90 @@ +package walker + +import ( + "os" + "path/filepath" + "strings" + + "github.com/bmatcuk/doublestar/v4" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/utils" + "github.com/aquasecurity/trivy/pkg/log" +) + +var ( + // These variables are exported so that a tool importing Trivy as a library can override these values. + AppDirs = []string{".git"} + SystemDirs = []string{ + "proc", + "sys", + "dev", + } +) + +const defaultSizeThreshold = int64(200) << 20 // 200MB + +type WalkFunc func(filePath string, info os.FileInfo, opener analyzer.Opener) error + +type walker struct { + skipFiles []string + skipDirs []string +} + +func newWalker(skipFiles, skipDirs []string) walker { + var cleanSkipFiles, cleanSkipDirs []string + for _, skipFile := range skipFiles { + skipFile = filepath.ToSlash(filepath.Clean(skipFile)) + skipFile = strings.TrimLeft(skipFile, "/") + cleanSkipFiles = append(cleanSkipFiles, skipFile) + } + + for _, skipDir := range append(skipDirs, SystemDirs...) { + skipDir = filepath.ToSlash(filepath.Clean(skipDir)) + skipDir = strings.TrimLeft(skipDir, "/") + cleanSkipDirs = append(cleanSkipDirs, skipDir) + } + + return walker{ + skipFiles: cleanSkipFiles, + skipDirs: cleanSkipDirs, + } +} + +func (w *walker) shouldSkipFile(filePath string) bool { + filePath = strings.TrimLeft(filePath, "/") + + // skip files + for _, pattern := range w.skipFiles { + match, err := doublestar.Match(pattern, filePath) + if err != nil { + return false // return early if bad pattern + } else if match { + log.Logger.Debugf("Skipping file: %s", filePath) + return true + } + } + return false +} + +func (w *walker) shouldSkipDir(dir string) bool { + dir = strings.TrimLeft(dir, "/") + + // Skip application dirs (relative path) + base := filepath.Base(dir) + if utils.StringInSlice(base, AppDirs) { + return true + } + + // Skip system dirs and specified dirs (absolute path) + for _, pattern := range w.skipDirs { + if match, err := doublestar.Match(pattern, dir); err != nil { + return false // return early if bad pattern + } else if match { + log.Logger.Debugf("Skipping directory: %s", dir) + return true + } + } + + return false +} diff --git a/pkg/fanal/walker/walk_test.go b/pkg/fanal/walker/walk_test.go new file mode 100644 index 000000000000..4d52117aa1ce --- /dev/null +++ b/pkg/fanal/walker/walk_test.go @@ -0,0 +1,124 @@ +package walker + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_shouldSkipFile(t *testing.T) { + testCases := []struct { + skipFiles []string + skipMap map[string]bool + }{ + { + skipFiles: []string{filepath.Join("/etc/*")}, + skipMap: map[string]bool{ + filepath.Join("/etc/foo"): true, + filepath.Join("/etc/foo/bar"): false, + }, + }, + { + skipFiles: []string{filepath.Join("/etc/*/*")}, + skipMap: map[string]bool{ + filepath.Join("/etc/foo"): false, + filepath.Join("/etc/foo/bar"): true, + }, + }, + { + skipFiles: []string{filepath.Join("**/*.txt")}, + skipMap: map[string]bool{ + filepath.Join("/etc/foo"): false, + filepath.Join("/etc/foo/bar"): false, + filepath.Join("/var/log/bar.txt"): true, + }, + }, + { + skipFiles: []string{filepath.Join("/etc/*/*"), filepath.Join("/var/log/*.txt")}, + skipMap: map[string]bool{ + filepath.Join("/etc/foo"): false, + filepath.Join("/etc/foo/bar"): true, + filepath.Join("/var/log/bar.txt"): true, + }, + }, + { + skipFiles: []string{filepath.Join(`[^etc`)}, // filepath.Match returns ErrBadPattern + skipMap: map[string]bool{ + filepath.Join("/etc/foo"): false, + filepath.Join("/etc/foo/bar"): false, + }, + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprint(i), func(t *testing.T) { + w := newWalker(tc.skipFiles, nil) + for file, skipResult := range tc.skipMap { + assert.Equal(t, skipResult, w.shouldSkipFile(filepath.ToSlash(filepath.Clean(file))), fmt.Sprintf("skipFiles: %s, file: %s", tc.skipFiles, file)) + } + }) + } +} + +func Test_shouldSkipDir(t *testing.T) { + testCases := []struct { + skipDirs []string + skipMap map[string]bool + }{ + { + skipDirs: nil, + skipMap: map[string]bool{ + ".git": true, // AppDir + "proc": true, // SystemDir + "foo.bar": false, // random directory + }, + }, + { + skipDirs: []string{filepath.Join("/*")}, + skipMap: map[string]bool{ + filepath.Join("/etc"): true, + filepath.Join("/etc/foo/bar"): false, + }, + }, + { + skipDirs: []string{filepath.Join("/etc/*/*")}, + skipMap: map[string]bool{ + filepath.Join("/etc/foo"): false, + filepath.Join("/etc/foo/bar"): true, + }, + }, + { + skipDirs: []string{filepath.Join("/etc/*/*"), filepath.Join("/var/log/*")}, + skipMap: map[string]bool{ + filepath.Join("/etc/foo"): false, + filepath.Join("/etc/foo/bar"): true, + filepath.Join("/var/log/bar"): true, + }, + }, + { + skipDirs: []string{filepath.Join(`[^etc`)}, // filepath.Match returns ErrBadPattern + skipMap: map[string]bool{ + filepath.Join("/etc/foo"): false, + filepath.Join("/etc/foo/bar"): false, + }, + }, + { + skipDirs: []string{"**/.terraform"}, + skipMap: map[string]bool{ + ".terraform": true, + "test/foo/bar/.terraform": true, + }, + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprint(i), func(t *testing.T) { + w := newWalker(nil, tc.skipDirs) + for dir, skipResult := range tc.skipMap { + assert.Equal(t, skipResult, w.shouldSkipDir(filepath.ToSlash(filepath.Clean(dir))), fmt.Sprintf("skipDirs: %s, dir: %s", tc.skipDirs, dir)) + } + }) + } +} diff --git a/pkg/flag/aws_flags.go b/pkg/flag/aws_flags.go new file mode 100644 index 000000000000..6801d8b1ae5a --- /dev/null +++ b/pkg/flag/aws_flags.go @@ -0,0 +1,92 @@ +package flag + +var ( + awsRegionFlag = Flag[string]{ + Name: "region", + ConfigName: "cloud.aws.region", + Usage: "AWS Region to scan", + } + awsEndpointFlag = Flag[string]{ + Name: "endpoint", + ConfigName: "cloud.aws.endpoint", + Usage: "AWS Endpoint override", + } + awsServiceFlag = Flag[[]string]{ + Name: "service", + ConfigName: "cloud.aws.service", + Usage: "Only scan AWS Service(s) specified with this flag. Can specify multiple services using --service A --service B etc.", + } + awsSkipServicesFlag = Flag[[]string]{ + Name: "skip-service", + ConfigName: "cloud.aws.skip-service", + Usage: "Skip selected AWS Service(s) specified with this flag. Can specify multiple services using --skip-service A --skip-service B etc.", + } + awsAccountFlag = Flag[string]{ + Name: "account", + ConfigName: "cloud.aws.account", + Usage: "The AWS account to scan. It's useful to specify this when reviewing cached results for multiple accounts.", + } + awsARNFlag = Flag[string]{ + Name: "arn", + ConfigName: "cloud.aws.arn", + Usage: "The AWS ARN to show results for. Useful to filter results once a scan is cached.", + } +) + +type AWSFlagGroup struct { + Region *Flag[string] + Endpoint *Flag[string] + Services *Flag[[]string] + SkipServices *Flag[[]string] + Account *Flag[string] + ARN *Flag[string] +} + +type AWSOptions struct { + Region string + Endpoint string + Services []string + SkipServices []string + Account string + ARN string +} + +func NewAWSFlagGroup() *AWSFlagGroup { + return &AWSFlagGroup{ + Region: awsRegionFlag.Clone(), + Endpoint: awsEndpointFlag.Clone(), + Services: awsServiceFlag.Clone(), + SkipServices: awsSkipServicesFlag.Clone(), + Account: awsAccountFlag.Clone(), + ARN: awsARNFlag.Clone(), + } +} + +func (f *AWSFlagGroup) Name() string { + return "AWS" +} + +func (f *AWSFlagGroup) Flags() []Flagger { + return []Flagger{ + f.Region, + f.Endpoint, + f.Services, + f.SkipServices, + f.Account, + f.ARN, + } +} + +func (f *AWSFlagGroup) ToOptions() (AWSOptions, error) { + if err := parseFlags(f); err != nil { + return AWSOptions{}, err + } + return AWSOptions{ + Region: f.Region.Value(), + Endpoint: f.Endpoint.Value(), + Services: f.Services.Value(), + SkipServices: f.SkipServices.Value(), + Account: f.Account.Value(), + ARN: f.ARN.Value(), + }, nil +} diff --git a/pkg/flag/cache_flags.go b/pkg/flag/cache_flags.go new file mode 100644 index 000000000000..c259d5b9e963 --- /dev/null +++ b/pkg/flag/cache_flags.go @@ -0,0 +1,160 @@ +package flag + +import ( + "fmt" + "strings" + "time" + + "github.com/samber/lo" + "golang.org/x/xerrors" +) + +// e.g. config yaml: +// +// cache: +// clear: true +// backend: "redis://localhost:6379" +// redis: +// ca: ca-cert.pem +// cert: cert.pem +// key: key.pem +var ( + ClearCacheFlag = Flag[bool]{ + Name: "clear-cache", + ConfigName: "cache.clear", + Usage: "clear image caches without scanning", + } + CacheBackendFlag = Flag[string]{ + Name: "cache-backend", + ConfigName: "cache.backend", + Default: "fs", + Usage: "cache backend (e.g. redis://localhost:6379)", + } + CacheTTLFlag = Flag[time.Duration]{ + Name: "cache-ttl", + ConfigName: "cache.ttl", + Usage: "cache TTL when using redis as cache backend", + } + RedisTLSFlag = Flag[bool]{ + Name: "redis-tls", + ConfigName: "cache.redis.tls", + Usage: "enable redis TLS with public certificates, if using redis as cache backend", + } + RedisCACertFlag = Flag[string]{ + Name: "redis-ca", + ConfigName: "cache.redis.ca", + Usage: "redis ca file location, if using redis as cache backend", + } + RedisCertFlag = Flag[string]{ + Name: "redis-cert", + ConfigName: "cache.redis.cert", + Usage: "redis certificate file location, if using redis as cache backend", + } + RedisKeyFlag = Flag[string]{ + Name: "redis-key", + ConfigName: "cache.redis.key", + Usage: "redis key file location, if using redis as cache backend", + } +) + +// CacheFlagGroup composes common printer flag structs used for commands requiring cache logic. +type CacheFlagGroup struct { + ClearCache *Flag[bool] + CacheBackend *Flag[string] + CacheTTL *Flag[time.Duration] + + RedisTLS *Flag[bool] + RedisCACert *Flag[string] + RedisCert *Flag[string] + RedisKey *Flag[string] +} + +type CacheOptions struct { + ClearCache bool + CacheBackend string + CacheTTL time.Duration + RedisTLS bool + RedisOptions +} + +// RedisOptions holds the options for redis cache +type RedisOptions struct { + RedisCACert string + RedisCert string + RedisKey string +} + +// NewCacheFlagGroup returns a default CacheFlagGroup +func NewCacheFlagGroup() *CacheFlagGroup { + return &CacheFlagGroup{ + ClearCache: ClearCacheFlag.Clone(), + CacheBackend: CacheBackendFlag.Clone(), + CacheTTL: CacheTTLFlag.Clone(), + RedisTLS: RedisTLSFlag.Clone(), + RedisCACert: RedisCACertFlag.Clone(), + RedisCert: RedisCertFlag.Clone(), + RedisKey: RedisKeyFlag.Clone(), + } +} + +func (fg *CacheFlagGroup) Name() string { + return "Cache" +} + +func (fg *CacheFlagGroup) Flags() []Flagger { + return []Flagger{ + fg.ClearCache, + fg.CacheBackend, + fg.CacheTTL, + fg.RedisTLS, + fg.RedisCACert, + fg.RedisCert, + fg.RedisKey, + } +} + +func (fg *CacheFlagGroup) ToOptions() (CacheOptions, error) { + if err := parseFlags(fg); err != nil { + return CacheOptions{}, err + } + + cacheBackend := fg.CacheBackend.Value() + redisOptions := RedisOptions{ + RedisCACert: fg.RedisCACert.Value(), + RedisCert: fg.RedisCert.Value(), + RedisKey: fg.RedisKey.Value(), + } + + // "redis://" or "fs" are allowed for now + // An empty value is also allowed for testability + if !strings.HasPrefix(cacheBackend, "redis://") && + cacheBackend != "fs" && cacheBackend != "" { + return CacheOptions{}, xerrors.Errorf("unsupported cache backend: %s", cacheBackend) + } + // if one of redis option not nil, make sure CA, cert, and key provided + if !lo.IsEmpty(redisOptions) { + if redisOptions.RedisCACert == "" || redisOptions.RedisCert == "" || redisOptions.RedisKey == "" { + return CacheOptions{}, xerrors.Errorf("you must provide Redis CA, cert and key file path when using TLS") + } + } + + return CacheOptions{ + ClearCache: fg.ClearCache.Value(), + CacheBackend: cacheBackend, + CacheTTL: fg.CacheTTL.Value(), + RedisTLS: fg.RedisTLS.Value(), + RedisOptions: redisOptions, + }, nil +} + +// CacheBackendMasked returns the redis connection string masking credentials +func (o *CacheOptions) CacheBackendMasked() string { + endIndex := strings.Index(o.CacheBackend, "@") + if endIndex == -1 { + return o.CacheBackend + } + + startIndex := strings.Index(o.CacheBackend, "//") + + return fmt.Sprintf("%s****%s", o.CacheBackend[:startIndex+2], o.CacheBackend[endIndex:]) +} diff --git a/pkg/flag/cache_flags_test.go b/pkg/flag/cache_flags_test.go new file mode 100644 index 000000000000..db54c75b13e8 --- /dev/null +++ b/pkg/flag/cache_flags_test.go @@ -0,0 +1,160 @@ +package flag_test + +import ( + "testing" + "time" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/flag" +) + +func TestCacheFlagGroup_ToOptions(t *testing.T) { + type fields struct { + ClearCache bool + CacheBackend string + CacheTTL time.Duration + RedisTLS bool + RedisCACert string + RedisCert string + RedisKey string + } + tests := []struct { + name string + fields fields + want flag.CacheOptions + assertion require.ErrorAssertionFunc + }{ + { + name: "fs", + fields: fields{ + CacheBackend: "fs", + }, + want: flag.CacheOptions{ + CacheBackend: "fs", + }, + assertion: require.NoError, + }, + { + name: "redis", + fields: fields{ + CacheBackend: "redis://localhost:6379", + }, + want: flag.CacheOptions{ + CacheBackend: "redis://localhost:6379", + }, + assertion: require.NoError, + }, + { + name: "redis tls", + fields: fields{ + CacheBackend: "redis://localhost:6379", + RedisCACert: "ca-cert.pem", + RedisCert: "cert.pem", + RedisKey: "key.pem", + }, + want: flag.CacheOptions{ + CacheBackend: "redis://localhost:6379", + RedisOptions: flag.RedisOptions{ + RedisCACert: "ca-cert.pem", + RedisCert: "cert.pem", + RedisKey: "key.pem", + }, + }, + assertion: require.NoError, + }, + { + name: "redis tls with public certificates", + fields: fields{ + CacheBackend: "redis://localhost:6379", + RedisTLS: true, + }, + want: flag.CacheOptions{ + CacheBackend: "redis://localhost:6379", + RedisTLS: true, + }, + assertion: require.NoError, + }, + { + name: "unknown backend", + fields: fields{ + CacheBackend: "unknown", + }, + assertion: func(t require.TestingT, err error, msgs ...interface{}) { + require.ErrorContains(t, err, "unsupported cache backend") + }, + }, + { + name: "sad redis tls", + fields: fields{ + CacheBackend: "redis://localhost:6379", + RedisCACert: "ca-cert.pem", + }, + assertion: func(t require.TestingT, err error, msgs ...interface{}) { + require.ErrorContains(t, err, "you must provide Redis CA") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + viper.Set(flag.ClearCacheFlag.ConfigName, tt.fields.ClearCache) + viper.Set(flag.CacheBackendFlag.ConfigName, tt.fields.CacheBackend) + viper.Set(flag.CacheTTLFlag.ConfigName, tt.fields.CacheTTL) + viper.Set(flag.RedisTLSFlag.ConfigName, tt.fields.RedisTLS) + viper.Set(flag.RedisCACertFlag.ConfigName, tt.fields.RedisCACert) + viper.Set(flag.RedisCertFlag.ConfigName, tt.fields.RedisCert) + viper.Set(flag.RedisKeyFlag.ConfigName, tt.fields.RedisKey) + + f := &flag.CacheFlagGroup{ + ClearCache: flag.ClearCacheFlag.Clone(), + CacheBackend: flag.CacheBackendFlag.Clone(), + CacheTTL: flag.CacheTTLFlag.Clone(), + RedisTLS: flag.RedisTLSFlag.Clone(), + RedisCACert: flag.RedisCACertFlag.Clone(), + RedisCert: flag.RedisCertFlag.Clone(), + RedisKey: flag.RedisKeyFlag.Clone(), + } + + got, err := f.ToOptions() + tt.assertion(t, err) + assert.Equalf(t, tt.want, got, "ToOptions()") + }) + } +} + +func TestCacheOptions_CacheBackendMasked(t *testing.T) { + type fields struct { + backend string + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "redis cache backend masked", + fields: fields{ + backend: "redis://root:password@localhost:6379", + }, + want: "redis://****@localhost:6379", + }, + { + name: "redis cache backend masked does nothing", + fields: fields{ + backend: "redis://localhost:6379", + }, + want: "redis://localhost:6379", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &flag.CacheOptions{ + CacheBackend: tt.fields.backend, + } + + assert.Equal(t, tt.want, c.CacheBackendMasked()) + }) + } +} diff --git a/pkg/flag/cloud_flags.go b/pkg/flag/cloud_flags.go new file mode 100644 index 000000000000..dfff5a997f7a --- /dev/null +++ b/pkg/flag/cloud_flags.go @@ -0,0 +1,55 @@ +package flag + +import "time" + +var ( + cloudUpdateCacheFlag = Flag[bool]{ + Name: "update-cache", + ConfigName: "cloud.update-cache", + Usage: "Update the cache for the applicable cloud provider instead of using cached results.", + } + cloudMaxCacheAgeFlag = Flag[time.Duration]{ + Name: "max-cache-age", + ConfigName: "cloud.max-cache-age", + Default: time.Hour * 24, + Usage: "The maximum age of the cloud cache. Cached data will be requeried from the cloud provider if it is older than this.", + } +) + +type CloudFlagGroup struct { + UpdateCache *Flag[bool] + MaxCacheAge *Flag[time.Duration] +} + +type CloudOptions struct { + MaxCacheAge time.Duration + UpdateCache bool +} + +func NewCloudFlagGroup() *CloudFlagGroup { + return &CloudFlagGroup{ + UpdateCache: cloudUpdateCacheFlag.Clone(), + MaxCacheAge: cloudMaxCacheAgeFlag.Clone(), + } +} + +func (f *CloudFlagGroup) Name() string { + return "Cloud" +} + +func (f *CloudFlagGroup) Flags() []Flagger { + return []Flagger{ + f.UpdateCache, + f.MaxCacheAge, + } +} + +func (f *CloudFlagGroup) ToOptions() (CloudOptions, error) { + if err := parseFlags(f); err != nil { + return CloudOptions{}, err + } + return CloudOptions{ + UpdateCache: f.UpdateCache.Value(), + MaxCacheAge: f.MaxCacheAge.Value(), + }, nil +} diff --git a/pkg/flag/db_flags.go b/pkg/flag/db_flags.go new file mode 100644 index 000000000000..58e7809a2152 --- /dev/null +++ b/pkg/flag/db_flags.go @@ -0,0 +1,188 @@ +package flag + +import ( + "fmt" + + "github.com/google/go-containerregistry/pkg/name" + "go.uber.org/zap" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/db" + "github.com/aquasecurity/trivy/pkg/javadb" + "github.com/aquasecurity/trivy/pkg/log" +) + +var ( + ResetFlag = Flag[bool]{ + Name: "reset", + ConfigName: "reset", + Usage: "remove all caches and database", + } + DownloadDBOnlyFlag = Flag[bool]{ + Name: "download-db-only", + ConfigName: "db.download-only", + Usage: "download/update vulnerability database but don't run a scan", + } + SkipDBUpdateFlag = Flag[bool]{ + Name: "skip-db-update", + ConfigName: "db.skip-update", + Usage: "skip updating vulnerability database", + Aliases: []Alias{ + { + Name: "skip-update", + Deprecated: true, // --skip-update was renamed to --skip-db-update + }, + }, + } + DownloadJavaDBOnlyFlag = Flag[bool]{ + Name: "download-java-db-only", + ConfigName: "db.download-java-only", + Usage: "download/update Java index database but don't run a scan", + } + SkipJavaDBUpdateFlag = Flag[bool]{ + Name: "skip-java-db-update", + ConfigName: "db.java-skip-update", + Usage: "skip updating Java index database", + } + NoProgressFlag = Flag[bool]{ + Name: "no-progress", + ConfigName: "db.no-progress", + Usage: "suppress progress bar", + } + DBRepositoryFlag = Flag[string]{ + Name: "db-repository", + ConfigName: "db.repository", + Default: db.DefaultRepository, + Usage: "OCI repository to retrieve trivy-db from", + } + JavaDBRepositoryFlag = Flag[string]{ + Name: "java-db-repository", + ConfigName: "db.java-repository", + Default: javadb.DefaultRepository, + Usage: "OCI repository to retrieve trivy-java-db from", + } + LightFlag = Flag[bool]{ + Name: "light", + ConfigName: "db.light", + Usage: "deprecated", + Deprecated: true, + } +) + +// DBFlagGroup composes common printer flag structs used for commands requiring DB logic. +type DBFlagGroup struct { + Reset *Flag[bool] + DownloadDBOnly *Flag[bool] + SkipDBUpdate *Flag[bool] + DownloadJavaDBOnly *Flag[bool] + SkipJavaDBUpdate *Flag[bool] + NoProgress *Flag[bool] + DBRepository *Flag[string] + JavaDBRepository *Flag[string] + Light *Flag[bool] // deprecated +} + +type DBOptions struct { + Reset bool + DownloadDBOnly bool + SkipDBUpdate bool + DownloadJavaDBOnly bool + SkipJavaDBUpdate bool + NoProgress bool + DBRepository name.Reference + JavaDBRepository name.Reference + Light bool // deprecated +} + +// NewDBFlagGroup returns a default DBFlagGroup +func NewDBFlagGroup() *DBFlagGroup { + return &DBFlagGroup{ + Reset: ResetFlag.Clone(), + DownloadDBOnly: DownloadDBOnlyFlag.Clone(), + SkipDBUpdate: SkipDBUpdateFlag.Clone(), + DownloadJavaDBOnly: DownloadJavaDBOnlyFlag.Clone(), + SkipJavaDBUpdate: SkipJavaDBUpdateFlag.Clone(), + Light: LightFlag.Clone(), + NoProgress: NoProgressFlag.Clone(), + DBRepository: DBRepositoryFlag.Clone(), + JavaDBRepository: JavaDBRepositoryFlag.Clone(), + } +} + +func (f *DBFlagGroup) Name() string { + return "DB" +} + +func (f *DBFlagGroup) Flags() []Flagger { + return []Flagger{ + f.Reset, + f.DownloadDBOnly, + f.SkipDBUpdate, + f.DownloadJavaDBOnly, + f.SkipJavaDBUpdate, + f.NoProgress, + f.DBRepository, + f.JavaDBRepository, + f.Light, + } +} + +func (f *DBFlagGroup) ToOptions() (DBOptions, error) { + if err := parseFlags(f); err != nil { + return DBOptions{}, err + } + + skipDBUpdate := f.SkipDBUpdate.Value() + skipJavaDBUpdate := f.SkipJavaDBUpdate.Value() + downloadDBOnly := f.DownloadDBOnly.Value() + downloadJavaDBOnly := f.DownloadJavaDBOnly.Value() + light := f.Light.Value() + + if downloadDBOnly && skipDBUpdate { + return DBOptions{}, xerrors.New("--skip-db-update and --download-db-only options can not be specified both") + } + if downloadJavaDBOnly && skipJavaDBUpdate { + return DBOptions{}, xerrors.New("--skip-java-db-update and --download-java-db-only options can not be specified both") + } + if light { + log.Logger.Warn("'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649") + } + + var dbRepository, javaDBRepository name.Reference + var err error + if f.DBRepository != nil { + if dbRepository, err = name.ParseReference(f.DBRepository.Value(), name.WithDefaultTag("")); err != nil { + return DBOptions{}, xerrors.Errorf("invalid db repository: %w", err) + } + // Add the schema version if the tag is not specified for backward compatibility. + if t, ok := dbRepository.(name.Tag); ok && t.TagStr() == "" { + dbRepository = t.Tag(fmt.Sprint(db.SchemaVersion)) + log.Logger.Infow("Adding schema version to the DB repository for backward compatibility", + zap.String("repository", dbRepository.String())) + } + } + + if f.JavaDBRepository != nil { + if javaDBRepository, err = name.ParseReference(f.JavaDBRepository.Value(), name.WithDefaultTag("")); err != nil { + return DBOptions{}, xerrors.Errorf("invalid javadb repository: %w", err) + } + // Add the schema version if the tag is not specified for backward compatibility. + if t, ok := javaDBRepository.(name.Tag); ok && t.TagStr() == "" { + javaDBRepository = t.Tag(fmt.Sprint(javadb.SchemaVersion)) + log.Logger.Infow("Adding schema version to the Java DB repository for backward compatibility", + zap.String("repository", javaDBRepository.String())) + } + } + + return DBOptions{ + Reset: f.Reset.Value(), + DownloadDBOnly: downloadDBOnly, + SkipDBUpdate: skipDBUpdate, + DownloadJavaDBOnly: downloadJavaDBOnly, + SkipJavaDBUpdate: skipJavaDBUpdate, + Light: light, + NoProgress: f.NoProgress.Value(), + DBRepository: dbRepository, + JavaDBRepository: javaDBRepository, + }, nil +} diff --git a/pkg/flag/db_flags_test.go b/pkg/flag/db_flags_test.go new file mode 100644 index 000000000000..b53f29135d74 --- /dev/null +++ b/pkg/flag/db_flags_test.go @@ -0,0 +1,119 @@ +package flag_test + +import ( + "github.com/google/go-containerregistry/pkg/name" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" + + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" +) + +func TestDBFlagGroup_ToOptions(t *testing.T) { + type fields struct { + SkipDBUpdate bool + DownloadDBOnly bool + Light bool + DBRepository string + JavaDBRepository string + } + tests := []struct { + name string + fields fields + want flag.DBOptions + wantLogs []string + assertion require.ErrorAssertionFunc + }{ + { + name: "happy", + fields: fields{ + SkipDBUpdate: true, + DownloadDBOnly: false, + DBRepository: "ghcr.io/aquasecurity/trivy-db", + JavaDBRepository: "ghcr.io/aquasecurity/trivy-java-db", + }, + want: flag.DBOptions{ + SkipDBUpdate: true, + DownloadDBOnly: false, + DBRepository: name.Tag{}, // All fields are unexported + JavaDBRepository: name.Tag{}, // All fields are unexported + }, + assertion: require.NoError, + }, + { + name: "light", + fields: fields{ + Light: true, + DBRepository: "ghcr.io/aquasecurity/trivy-db", + JavaDBRepository: "ghcr.io/aquasecurity/trivy-java-db", + }, + want: flag.DBOptions{ + Light: true, + DBRepository: name.Tag{}, // All fields are unexported + JavaDBRepository: name.Tag{}, // All fields are unexported + }, + wantLogs: []string{ + "'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649", + }, + assertion: require.NoError, + }, + { + name: "sad", + fields: fields{ + SkipDBUpdate: true, + DownloadDBOnly: true, + }, + assertion: func(t require.TestingT, err error, msgs ...interface{}) { + require.ErrorContains(t, err, "--skip-db-update and --download-db-only options can not be specified both") + }, + }, + { + name: "invalid repo", + fields: fields{ + SkipDBUpdate: true, + DownloadDBOnly: false, + DBRepository: "foo:bar:baz", + }, + assertion: func(t require.TestingT, err error, msgs ...interface{}) { + require.ErrorContains(t, err, "invalid db repository") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + level := zap.WarnLevel + core, obs := observer.New(level) + log.Logger = zap.New(core).Sugar() + + viper.Set(flag.SkipDBUpdateFlag.ConfigName, tt.fields.SkipDBUpdate) + viper.Set(flag.DownloadDBOnlyFlag.ConfigName, tt.fields.DownloadDBOnly) + viper.Set(flag.LightFlag.ConfigName, tt.fields.Light) + viper.Set(flag.DBRepositoryFlag.ConfigName, tt.fields.DBRepository) + viper.Set(flag.JavaDBRepositoryFlag.ConfigName, tt.fields.JavaDBRepository) + + // Assert options + f := &flag.DBFlagGroup{ + DownloadDBOnly: flag.DownloadDBOnlyFlag.Clone(), + SkipDBUpdate: flag.SkipDBUpdateFlag.Clone(), + Light: flag.LightFlag.Clone(), + DBRepository: flag.DBRepositoryFlag.Clone(), + JavaDBRepository: flag.JavaDBRepositoryFlag.Clone(), + } + got, err := f.ToOptions() + tt.assertion(t, err) + assert.EqualExportedValues(t, tt.want, got) + + // Assert log messages + var gotMessages []string + for _, entry := range obs.AllUntimed() { + gotMessages = append(gotMessages, entry.Message) + } + assert.Equal(t, tt.wantLogs, gotMessages, tt.name) + }) + } +} diff --git a/pkg/flag/global_flags.go b/pkg/flag/global_flags.go new file mode 100644 index 000000000000..aa4851c657f0 --- /dev/null +++ b/pkg/flag/global_flags.go @@ -0,0 +1,157 @@ +package flag + +import ( + "os" + "time" + + "github.com/spf13/cobra" + + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +var ( + ConfigFileFlag = Flag[string]{ + Name: "config", + ConfigName: "config", + Shorthand: "c", + Default: "trivy.yaml", + Usage: "config path", + Persistent: true, + } + ShowVersionFlag = Flag[bool]{ + Name: "version", + ConfigName: "version", + Shorthand: "v", + Usage: "show version", + Persistent: true, + } + QuietFlag = Flag[bool]{ + Name: "quiet", + ConfigName: "quiet", + Shorthand: "q", + Usage: "suppress progress bar and log output", + Persistent: true, + } + DebugFlag = Flag[bool]{ + Name: "debug", + ConfigName: "debug", + Shorthand: "d", + Usage: "debug mode", + Persistent: true, + } + InsecureFlag = Flag[bool]{ + Name: "insecure", + ConfigName: "insecure", + Usage: "allow insecure server connections", + Persistent: true, + } + TimeoutFlag = Flag[time.Duration]{ + Name: "timeout", + ConfigName: "timeout", + Default: time.Second * 300, // 5 mins + Usage: "timeout", + Persistent: true, + } + CacheDirFlag = Flag[string]{ + Name: "cache-dir", + ConfigName: "cache.dir", + Default: fsutils.CacheDir(), + Usage: "cache directory", + Persistent: true, + } + GenerateDefaultConfigFlag = Flag[bool]{ + Name: "generate-default-config", + ConfigName: "generate-default-config", + Usage: "write the default config to trivy-default.yaml", + Persistent: true, + } +) + +// GlobalFlagGroup composes global flags +type GlobalFlagGroup struct { + ConfigFile *Flag[string] + ShowVersion *Flag[bool] // spf13/cobra can't override the logic of version printing like VersionPrinter in urfave/cli. -v needs to be defined ourselves. + Quiet *Flag[bool] + Debug *Flag[bool] + Insecure *Flag[bool] + Timeout *Flag[time.Duration] + CacheDir *Flag[string] + GenerateDefaultConfig *Flag[bool] +} + +// GlobalOptions defines flags and other configuration parameters for all the subcommands +type GlobalOptions struct { + ConfigFile string + ShowVersion bool + Quiet bool + Debug bool + Insecure bool + Timeout time.Duration + CacheDir string + GenerateDefaultConfig bool +} + +func NewGlobalFlagGroup() *GlobalFlagGroup { + return &GlobalFlagGroup{ + ConfigFile: ConfigFileFlag.Clone(), + ShowVersion: ShowVersionFlag.Clone(), + Quiet: QuietFlag.Clone(), + Debug: DebugFlag.Clone(), + Insecure: InsecureFlag.Clone(), + Timeout: TimeoutFlag.Clone(), + CacheDir: CacheDirFlag.Clone(), + GenerateDefaultConfig: GenerateDefaultConfigFlag.Clone(), + } +} + +func (f *GlobalFlagGroup) Name() string { + return "global" +} + +func (f *GlobalFlagGroup) Flags() []Flagger { + return []Flagger{ + f.ConfigFile, + f.ShowVersion, + f.Quiet, + f.Debug, + f.Insecure, + f.Timeout, + f.CacheDir, + f.GenerateDefaultConfig, + } +} + +func (f *GlobalFlagGroup) AddFlags(cmd *cobra.Command) { + for _, flag := range f.Flags() { + flag.Add(cmd) + } +} + +func (f *GlobalFlagGroup) Bind(cmd *cobra.Command) error { + for _, flag := range f.Flags() { + if err := flag.Bind(cmd); err != nil { + return err + } + } + return nil +} + +func (f *GlobalFlagGroup) ToOptions() (GlobalOptions, error) { + if err := parseFlags(f); err != nil { + return GlobalOptions{}, err + } + + // Keep TRIVY_NON_SSL for backward compatibility + insecure := f.Insecure.Value() || os.Getenv("TRIVY_NON_SSL") != "" + + return GlobalOptions{ + ConfigFile: f.ConfigFile.Value(), + ShowVersion: f.ShowVersion.Value(), + Quiet: f.Quiet.Value(), + Debug: f.Debug.Value(), + Insecure: insecure, + Timeout: f.Timeout.Value(), + CacheDir: f.CacheDir.Value(), + GenerateDefaultConfig: f.GenerateDefaultConfig.Value(), + }, nil +} diff --git a/pkg/flag/image_flags.go b/pkg/flag/image_flags.go new file mode 100644 index 000000000000..aaa3fc65b930 --- /dev/null +++ b/pkg/flag/image_flags.go @@ -0,0 +1,137 @@ +package flag + +import ( + v1 "github.com/google/go-containerregistry/pkg/v1" + "golang.org/x/xerrors" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" +) + +// e.g. config yaml +// image: +// removed-pkgs: true +// input: "/path/to/alpine" + +var ( + ImageConfigScannersFlag = Flag[[]string]{ + Name: "image-config-scanners", + ConfigName: "image.image-config-scanners", + Values: xstrings.ToStringSlice(types.Scanners{ + types.MisconfigScanner, + types.SecretScanner, + }), + Usage: "comma-separated list of what security issues to detect on container image configurations", + } + ScanRemovedPkgsFlag = Flag[bool]{ + Name: "removed-pkgs", + ConfigName: "image.removed-pkgs", + Usage: "detect vulnerabilities of removed packages (only for Alpine)", + } + InputFlag = Flag[string]{ + Name: "input", + ConfigName: "image.input", + Usage: "input file path instead of image name", + } + PlatformFlag = Flag[string]{ + Name: "platform", + ConfigName: "image.platform", + Usage: "set platform in the form os/arch if image is multi-platform capable", + } + DockerHostFlag = Flag[string]{ + Name: "docker-host", + ConfigName: "image.docker.host", + Default: "", + Usage: "unix domain socket path to use for docker scanning", + } + PodmanHostFlag = Flag[string]{ + Name: "podman-host", + ConfigName: "image.podman.host", + Default: "", + Usage: "unix podman socket path to use for podman scanning", + } + SourceFlag = Flag[[]string]{ + Name: "image-src", + ConfigName: "image.source", + Default: xstrings.ToStringSlice(ftypes.AllImageSources), + Values: xstrings.ToStringSlice(ftypes.AllImageSources), + Usage: "image source(s) to use, in priority order", + } +) + +type ImageFlagGroup struct { + Input *Flag[string] // local image archive + ImageConfigScanners *Flag[[]string] + ScanRemovedPkgs *Flag[bool] + Platform *Flag[string] + DockerHost *Flag[string] + PodmanHost *Flag[string] + ImageSources *Flag[[]string] +} + +type ImageOptions struct { + Input string + ImageConfigScanners types.Scanners + ScanRemovedPkgs bool + Platform ftypes.Platform + DockerHost string + PodmanHost string + ImageSources ftypes.ImageSources +} + +func NewImageFlagGroup() *ImageFlagGroup { + return &ImageFlagGroup{ + Input: InputFlag.Clone(), + ImageConfigScanners: ImageConfigScannersFlag.Clone(), + ScanRemovedPkgs: ScanRemovedPkgsFlag.Clone(), + Platform: PlatformFlag.Clone(), + DockerHost: DockerHostFlag.Clone(), + PodmanHost: PodmanHostFlag.Clone(), + ImageSources: SourceFlag.Clone(), + } +} + +func (f *ImageFlagGroup) Name() string { + return "Image" +} + +func (f *ImageFlagGroup) Flags() []Flagger { + return []Flagger{ + f.Input, + f.ImageConfigScanners, + f.ScanRemovedPkgs, + f.Platform, + f.DockerHost, + f.PodmanHost, + f.ImageSources, + } +} + +func (f *ImageFlagGroup) ToOptions() (ImageOptions, error) { + if err := parseFlags(f); err != nil { + return ImageOptions{}, err + } + + var platform ftypes.Platform + if p := f.Platform.Value(); p != "" { + pl, err := v1.ParsePlatform(p) + if err != nil { + return ImageOptions{}, xerrors.Errorf("unable to parse platform: %w", err) + } + if pl.OS == "*" { + pl.OS = "" // Empty OS means any OS + } + platform = ftypes.Platform{Platform: pl} + } + + return ImageOptions{ + Input: f.Input.Value(), + ImageConfigScanners: xstrings.ToTSlice[types.Scanner](f.ImageConfigScanners.Value()), + ScanRemovedPkgs: f.ScanRemovedPkgs.Value(), + Platform: platform, + DockerHost: f.DockerHost.Value(), + PodmanHost: f.PodmanHost.Value(), + ImageSources: xstrings.ToTSlice[ftypes.ImageSource](f.ImageSources.Value()), + }, nil +} diff --git a/pkg/flag/kubernetes_flags.go b/pkg/flag/kubernetes_flags.go new file mode 100644 index 000000000000..7a87040ba698 --- /dev/null +++ b/pkg/flag/kubernetes_flags.go @@ -0,0 +1,241 @@ +package flag + +import ( + "fmt" + "strconv" + "strings" + + "github.com/samber/lo" + corev1 "k8s.io/api/core/v1" +) + +var ( + ClusterContextFlag = Flag[string]{ + Name: "context", + ConfigName: "kubernetes.context", + Usage: "specify a context to scan", + Aliases: []Alias{ + {Name: "ctx"}, + }, + } + K8sNamespaceFlag = Flag[string]{ + Name: "namespace", + ConfigName: "kubernetes.namespace", + Shorthand: "n", + Usage: "specify a namespace to scan", + } + KubeConfigFlag = Flag[string]{ + Name: "kubeconfig", + ConfigName: "kubernetes.kubeconfig", + Usage: "specify the kubeconfig file path to use", + } + ComponentsFlag = Flag[[]string]{ + Name: "components", + ConfigName: "kubernetes.components", + Default: []string{ + "workload", + "infra", + }, + Values: []string{ + "workload", + "infra", + }, + Usage: "specify which components to scan", + } + K8sVersionFlag = Flag[string]{ + Name: "k8s-version", + ConfigName: "kubernetes.k8s.version", + Usage: "specify k8s version to validate outdated api by it (example: 1.21.0)", + } + TolerationsFlag = Flag[[]string]{ + Name: "tolerations", + ConfigName: "kubernetes.tolerations", + Usage: "specify node-collector job tolerations (example: key1=value1:NoExecute,key2=value2:NoSchedule)", + } + AllNamespaces = Flag[bool]{ + Name: "all-namespaces", + ConfigName: "kubernetes.all.namespaces", + Shorthand: "A", + Usage: "fetch resources from all cluster namespaces", + } + NodeCollectorNamespace = Flag[string]{ + Name: "node-collector-namespace", + ConfigName: "node.collector.namespace", + Default: "trivy-temp", + Usage: "specify the namespace in which the node-collector job should be deployed", + } + ExcludeOwned = Flag[bool]{ + Name: "exclude-owned", + ConfigName: "kubernetes.exclude.owned", + Usage: "exclude resources that have an owner reference", + } + ExcludeNodes = Flag[[]string]{ + Name: "exclude-nodes", + ConfigName: "kubernetes.exclude.nodes", + Usage: "indicate the node labels that the node-collector job should exclude from scanning (example: kubernetes.io/arch:arm64,team:dev)", + } + NodeCollectorImageRef = Flag[string]{ + Name: "node-collector-imageref", + ConfigName: "kubernetes.node.collector.imageref", + Default: "ghcr.io/aquasecurity/node-collector:0.0.9", + Usage: "indicate the image reference for the node-collector scan job", + } + QPS = Flag[float64]{ + Name: "qps", + ConfigName: "kubernetes.qps", + Default: 5.0, + Usage: "specify the maximum QPS to the master from this client", + } + Burst = Flag[int]{ + Name: "burst", + ConfigName: "kubernetes.burst", + Default: 10, + Usage: "specify the maximum burst for throttle", + } +) + +type K8sFlagGroup struct { + ClusterContext *Flag[string] + Namespace *Flag[string] + KubeConfig *Flag[string] + Components *Flag[[]string] + K8sVersion *Flag[string] + Tolerations *Flag[[]string] + NodeCollectorImageRef *Flag[string] + AllNamespaces *Flag[bool] + NodeCollectorNamespace *Flag[string] + ExcludeOwned *Flag[bool] + ExcludeNodes *Flag[[]string] + QPS *Flag[float64] + Burst *Flag[int] +} + +type K8sOptions struct { + ClusterContext string + Namespace string + KubeConfig string + Components []string + K8sVersion string + Tolerations []corev1.Toleration + NodeCollectorImageRef string + AllNamespaces bool + NodeCollectorNamespace string + ExcludeOwned bool + ExcludeNodes map[string]string + QPS float32 + Burst int +} + +func NewK8sFlagGroup() *K8sFlagGroup { + return &K8sFlagGroup{ + ClusterContext: ClusterContextFlag.Clone(), + Namespace: K8sNamespaceFlag.Clone(), + KubeConfig: KubeConfigFlag.Clone(), + Components: ComponentsFlag.Clone(), + K8sVersion: K8sVersionFlag.Clone(), + Tolerations: TolerationsFlag.Clone(), + AllNamespaces: AllNamespaces.Clone(), + NodeCollectorNamespace: NodeCollectorNamespace.Clone(), + ExcludeOwned: ExcludeOwned.Clone(), + ExcludeNodes: ExcludeNodes.Clone(), + NodeCollectorImageRef: NodeCollectorImageRef.Clone(), + QPS: QPS.Clone(), + Burst: Burst.Clone(), + } +} + +func (f *K8sFlagGroup) Name() string { + return "Kubernetes" +} + +func (f *K8sFlagGroup) Flags() []Flagger { + return []Flagger{ + f.ClusterContext, + f.Namespace, + f.KubeConfig, + f.Components, + f.K8sVersion, + f.Tolerations, + f.AllNamespaces, + f.NodeCollectorNamespace, + f.ExcludeOwned, + f.ExcludeNodes, + f.NodeCollectorImageRef, + f.QPS, + f.Burst, + } +} + +func (f *K8sFlagGroup) ToOptions() (K8sOptions, error) { + if err := parseFlags(f); err != nil { + return K8sOptions{}, err + } + + tolerations, err := optionToTolerations(f.Tolerations.Value()) + if err != nil { + return K8sOptions{}, err + } + + exludeNodeLabels := make(map[string]string) + exludeNodes := f.ExcludeNodes.Value() + for _, exludeNodeValue := range exludeNodes { + excludeNodeParts := strings.Split(exludeNodeValue, ":") + if len(excludeNodeParts) != 2 { + return K8sOptions{}, fmt.Errorf("exclude node %s must be a key:value", exludeNodeValue) + } + exludeNodeLabels[excludeNodeParts[0]] = excludeNodeParts[1] + } + + return K8sOptions{ + ClusterContext: f.ClusterContext.Value(), + Namespace: f.Namespace.Value(), + KubeConfig: f.KubeConfig.Value(), + Components: f.Components.Value(), + K8sVersion: f.K8sVersion.Value(), + Tolerations: tolerations, + AllNamespaces: f.AllNamespaces.Value(), + NodeCollectorNamespace: f.NodeCollectorNamespace.Value(), + ExcludeOwned: f.ExcludeOwned.Value(), + ExcludeNodes: exludeNodeLabels, + NodeCollectorImageRef: f.NodeCollectorImageRef.Value(), + QPS: float32(f.QPS.Value()), + Burst: f.Burst.Value(), + }, nil +} + +func optionToTolerations(tolerationsOptions []string) ([]corev1.Toleration, error) { + var tolerations []corev1.Toleration + for _, toleration := range tolerationsOptions { + tolerationParts := strings.Split(toleration, ":") + if len(tolerationParts) < 2 { + return []corev1.Toleration{}, fmt.Errorf("toleration must include key and effect") + } + if corev1.TaintEffect(tolerationParts[1]) != corev1.TaintEffectNoSchedule && + corev1.TaintEffect(tolerationParts[1]) != corev1.TaintEffectPreferNoSchedule && + corev1.TaintEffect(tolerationParts[1]) != corev1.TaintEffectNoExecute { + return []corev1.Toleration{}, fmt.Errorf("toleration effect must be a valid value") + } + keyValue := strings.Split(tolerationParts[0], "=") + operator := corev1.TolerationOpEqual + if keyValue[1] == "" { + operator = corev1.TolerationOpExists + } + toleration := corev1.Toleration{ + Key: keyValue[0], + Value: keyValue[1], + Operator: operator, + Effect: corev1.TaintEffect(tolerationParts[1]), + } + var tolerationSec int + var err error + if len(tolerationParts) == 3 { + tolerationSec, err = strconv.Atoi(tolerationParts[2]) + if err != nil { + return nil, fmt.Errorf("TolerationSeconds must must be a number") + } + toleration.TolerationSeconds = lo.ToPtr(int64(tolerationSec)) + } + tolerations = append(tolerations, toleration) + } + return tolerations, nil +} diff --git a/pkg/flag/kubernetes_flags_test.go b/pkg/flag/kubernetes_flags_test.go new file mode 100644 index 000000000000..50c0b4f793bf --- /dev/null +++ b/pkg/flag/kubernetes_flags_test.go @@ -0,0 +1,51 @@ +package flag + +import ( + "testing" + + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" +) + +func TestOptionToToleration(t *testing.T) { + + tests := []struct { + name string + tolerationsOptions []string + want []corev1.Toleration + }{ + { + name: "no execute", + tolerationsOptions: []string{"key1=CriticalAddonsOnly:NoExecute:3600"}, + want: []corev1.Toleration{ + { + Key: "key1", + Operator: "Equal", + Value: "CriticalAddonsOnly", + Effect: "NoExecute", + TolerationSeconds: lo.ToPtr(int64(3600)), + }, + }, + }, + { + name: "no schedule", + tolerationsOptions: []string{"key1=CriticalAddonsOnly:NoSchedule"}, + want: []corev1.Toleration{ + { + Key: "key1", + Operator: "Equal", + Value: "CriticalAddonsOnly", + Effect: "NoSchedule", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := optionToTolerations(tt.tolerationsOptions) + assert.NoError(t, err) + assert.Equal(t, got, tt.want) + }) + } +} diff --git a/pkg/flag/license_flags.go b/pkg/flag/license_flags.go new file mode 100644 index 000000000000..5f4e148af5b2 --- /dev/null +++ b/pkg/flag/license_flags.go @@ -0,0 +1,137 @@ +package flag + +import ( + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" +) + +var ( + LicenseFull = Flag[bool]{ + Name: "license-full", + ConfigName: "license.full", + Usage: "eagerly look for licenses in source code headers and license files", + } + IgnoredLicenses = Flag[[]string]{ + Name: "ignored-licenses", + ConfigName: "license.ignored", + Usage: "specify a list of license to ignore", + } + LicenseConfidenceLevel = Flag[float64]{ + Name: "license-confidence-level", + ConfigName: "license.confidenceLevel", + Default: 0.9, + Usage: "specify license classifier's confidence level", + } + + // LicenseForbidden is an option only in a config file + LicenseForbidden = Flag[[]string]{ + ConfigName: "license.forbidden", + Default: licensing.ForbiddenLicenses, + Usage: "forbidden licenses", + } + // LicenseRestricted is an option only in a config file + LicenseRestricted = Flag[[]string]{ + ConfigName: "license.restricted", + Default: licensing.RestrictedLicenses, + Usage: "restricted licenses", + } + // LicenseReciprocal is an option only in a config file + LicenseReciprocal = Flag[[]string]{ + ConfigName: "license.reciprocal", + Default: licensing.ReciprocalLicenses, + Usage: "reciprocal licenses", + } + // LicenseNotice is an option only in a config file + LicenseNotice = Flag[[]string]{ + ConfigName: "license.notice", + Default: licensing.NoticeLicenses, + Usage: "notice licenses", + } + // LicensePermissive is an option only in a config file + LicensePermissive = Flag[[]string]{ + ConfigName: "license.permissive", + Default: licensing.PermissiveLicenses, + Usage: "permissive licenses", + } + // LicenseUnencumbered is an option only in a config file + LicenseUnencumbered = Flag[[]string]{ + ConfigName: "license.unencumbered", + Default: licensing.UnencumberedLicenses, + Usage: "unencumbered licenses", + } +) + +type LicenseFlagGroup struct { + LicenseFull *Flag[bool] + IgnoredLicenses *Flag[[]string] + LicenseConfidenceLevel *Flag[float64] + + // License Categories + LicenseForbidden *Flag[[]string] // mapped to CRITICAL + LicenseRestricted *Flag[[]string] // mapped to HIGH + LicenseReciprocal *Flag[[]string] // mapped to MEDIUM + LicenseNotice *Flag[[]string] // mapped to LOW + LicensePermissive *Flag[[]string] // mapped to LOW + LicenseUnencumbered *Flag[[]string] // mapped to LOW +} + +type LicenseOptions struct { + LicenseFull bool + IgnoredLicenses []string + LicenseConfidenceLevel float64 + LicenseRiskThreshold int + LicenseCategories map[types.LicenseCategory][]string +} + +func NewLicenseFlagGroup() *LicenseFlagGroup { + return &LicenseFlagGroup{ + LicenseFull: LicenseFull.Clone(), + IgnoredLicenses: IgnoredLicenses.Clone(), + LicenseConfidenceLevel: LicenseConfidenceLevel.Clone(), + LicenseForbidden: LicenseForbidden.Clone(), + LicenseRestricted: LicenseRestricted.Clone(), + LicenseReciprocal: LicenseReciprocal.Clone(), + LicenseNotice: LicenseNotice.Clone(), + LicensePermissive: LicensePermissive.Clone(), + LicenseUnencumbered: LicenseUnencumbered.Clone(), + } +} + +func (f *LicenseFlagGroup) Name() string { + return "License" +} + +func (f *LicenseFlagGroup) Flags() []Flagger { + return []Flagger{ + f.LicenseFull, + f.IgnoredLicenses, + f.LicenseForbidden, + f.LicenseRestricted, + f.LicenseReciprocal, + f.LicenseNotice, + f.LicensePermissive, + f.LicenseUnencumbered, + f.LicenseConfidenceLevel, + } +} + +func (f *LicenseFlagGroup) ToOptions() (LicenseOptions, error) { + if err := parseFlags(f); err != nil { + return LicenseOptions{}, err + } + + licenseCategories := make(map[types.LicenseCategory][]string) + licenseCategories[types.CategoryForbidden] = f.LicenseForbidden.Value() + licenseCategories[types.CategoryRestricted] = f.LicenseRestricted.Value() + licenseCategories[types.CategoryReciprocal] = f.LicenseReciprocal.Value() + licenseCategories[types.CategoryNotice] = f.LicenseNotice.Value() + licenseCategories[types.CategoryPermissive] = f.LicensePermissive.Value() + licenseCategories[types.CategoryUnencumbered] = f.LicenseUnencumbered.Value() + + return LicenseOptions{ + LicenseFull: f.LicenseFull.Value(), + IgnoredLicenses: f.IgnoredLicenses.Value(), + LicenseConfidenceLevel: f.LicenseConfidenceLevel.Value(), + LicenseCategories: licenseCategories, + }, nil +} diff --git a/pkg/flag/misconf_flags.go b/pkg/flag/misconf_flags.go new file mode 100644 index 000000000000..57a91820c60d --- /dev/null +++ b/pkg/flag/misconf_flags.go @@ -0,0 +1,186 @@ +package flag + +import ( + "fmt" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/policy" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" +) + +// e.g. config yaml: +// +// misconfiguration: +// trace: true +// config-policy: "custom-policy/policy" +// policy-namespaces: "user" +var ( + ResetPolicyBundleFlag = Flag[bool]{ + Name: "reset-policy-bundle", + ConfigName: "misconfiguration.reset-policy-bundle", + Usage: "remove policy bundle", + } + IncludeNonFailuresFlag = Flag[bool]{ + Name: "include-non-failures", + ConfigName: "misconfiguration.include-non-failures", + Usage: "include successes and exceptions, available with '--scanners misconfig'", + } + HelmValuesFileFlag = Flag[[]string]{ + Name: "helm-values", + ConfigName: "misconfiguration.helm.values", + Usage: "specify paths to override the Helm values.yaml files", + } + HelmSetFlag = Flag[[]string]{ + Name: "helm-set", + ConfigName: "misconfiguration.helm.set", + Usage: "specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)", + } + HelmSetFileFlag = Flag[[]string]{ + Name: "helm-set-file", + ConfigName: "misconfiguration.helm.set-file", + Usage: "specify Helm values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)", + } + HelmSetStringFlag = Flag[[]string]{ + Name: "helm-set-string", + ConfigName: "misconfiguration.helm.set-string", + Usage: "specify Helm string values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)", + } + HelmAPIVersionsFlag = Flag[[]string]{ + Name: "helm-api-versions", + ConfigName: "misconfiguration.helm.api-versions", + Usage: "Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment)", + } + HelmKubeVersionFlag = Flag[string]{ + Name: "helm-kube-version", + ConfigName: "misconfiguration.helm.kube-version", + Usage: "Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command.", + } + TfVarsFlag = Flag[[]string]{ + Name: "tf-vars", + ConfigName: "misconfiguration.terraform.vars", + Usage: "specify paths to override the Terraform tfvars files", + } + CfParamsFlag = Flag[[]string]{ + Name: "cf-params", + ConfigName: "misconfiguration.cloudformation.params", + Default: []string{}, + Usage: "specify paths to override the CloudFormation parameters files", + } + TerraformExcludeDownloaded = Flag[bool]{ + Name: "tf-exclude-downloaded-modules", + ConfigName: "misconfiguration.terraform.exclude-downloaded-modules", + Usage: "exclude misconfigurations for downloaded terraform modules", + } + PolicyBundleRepositoryFlag = Flag[string]{ + Name: "policy-bundle-repository", + ConfigName: "misconfiguration.policy-bundle-repository", + Default: fmt.Sprintf("%s:%d", policy.BundleRepository, policy.BundleVersion), + Usage: "OCI registry URL to retrieve policy bundle from", + } + MisconfigScannersFlag = Flag[[]string]{ + Name: "misconfig-scanners", + ConfigName: "misconfiguration.scanners", + Default: xstrings.ToStringSlice(analyzer.TypeConfigFiles), + Usage: "comma-separated list of misconfig scanners to use for misconfiguration scanning", + } +) + +// MisconfFlagGroup composes common printer flag structs used for commands providing misconfiguration scanning. +type MisconfFlagGroup struct { + IncludeNonFailures *Flag[bool] + ResetPolicyBundle *Flag[bool] + PolicyBundleRepository *Flag[string] + + // Values Files + HelmValues *Flag[[]string] + HelmValueFiles *Flag[[]string] + HelmFileValues *Flag[[]string] + HelmStringValues *Flag[[]string] + HelmAPIVersions *Flag[[]string] + HelmKubeVersion *Flag[string] + TerraformTFVars *Flag[[]string] + CloudformationParamVars *Flag[[]string] + TerraformExcludeDownloaded *Flag[bool] + MisconfigScanners *Flag[[]string] +} + +type MisconfOptions struct { + IncludeNonFailures bool + ResetPolicyBundle bool + PolicyBundleRepository string + + // Values Files + HelmValues []string + HelmValueFiles []string + HelmFileValues []string + HelmStringValues []string + HelmAPIVersions []string + HelmKubeVersion string + TerraformTFVars []string + CloudFormationParamVars []string + TfExcludeDownloaded bool + MisconfigScanners []analyzer.Type +} + +func NewMisconfFlagGroup() *MisconfFlagGroup { + return &MisconfFlagGroup{ + IncludeNonFailures: IncludeNonFailuresFlag.Clone(), + ResetPolicyBundle: ResetPolicyBundleFlag.Clone(), + PolicyBundleRepository: PolicyBundleRepositoryFlag.Clone(), + + HelmValues: HelmSetFlag.Clone(), + HelmFileValues: HelmSetFileFlag.Clone(), + HelmStringValues: HelmSetStringFlag.Clone(), + HelmValueFiles: HelmValuesFileFlag.Clone(), + HelmAPIVersions: HelmAPIVersionsFlag.Clone(), + HelmKubeVersion: HelmKubeVersionFlag.Clone(), + TerraformTFVars: TfVarsFlag.Clone(), + CloudformationParamVars: CfParamsFlag.Clone(), + TerraformExcludeDownloaded: TerraformExcludeDownloaded.Clone(), + MisconfigScanners: MisconfigScannersFlag.Clone(), + } +} + +func (f *MisconfFlagGroup) Name() string { + return "Misconfiguration" +} + +func (f *MisconfFlagGroup) Flags() []Flagger { + return []Flagger{ + f.IncludeNonFailures, + f.ResetPolicyBundle, + f.PolicyBundleRepository, + f.HelmValues, + f.HelmValueFiles, + f.HelmFileValues, + f.HelmStringValues, + f.HelmAPIVersions, + f.HelmKubeVersion, + f.TerraformTFVars, + f.TerraformExcludeDownloaded, + f.CloudformationParamVars, + f.MisconfigScanners, + } +} + +func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) { + if err := parseFlags(f); err != nil { + return MisconfOptions{}, err + } + + return MisconfOptions{ + IncludeNonFailures: f.IncludeNonFailures.Value(), + ResetPolicyBundle: f.ResetPolicyBundle.Value(), + PolicyBundleRepository: f.PolicyBundleRepository.Value(), + HelmValues: f.HelmValues.Value(), + HelmValueFiles: f.HelmValueFiles.Value(), + HelmFileValues: f.HelmFileValues.Value(), + HelmStringValues: f.HelmStringValues.Value(), + HelmAPIVersions: f.HelmAPIVersions.Value(), + HelmKubeVersion: f.HelmKubeVersion.Value(), + TerraformTFVars: f.TerraformTFVars.Value(), + CloudFormationParamVars: f.CloudformationParamVars.Value(), + TfExcludeDownloaded: f.TerraformExcludeDownloaded.Value(), + MisconfigScanners: xstrings.ToTSlice[analyzer.Type](f.MisconfigScanners.Value()), + }, nil +} diff --git a/pkg/flag/module_flags.go b/pkg/flag/module_flags.go new file mode 100644 index 000000000000..a3fdca308208 --- /dev/null +++ b/pkg/flag/module_flags.go @@ -0,0 +1,68 @@ +package flag + +import ( + "github.com/aquasecurity/trivy/pkg/module" +) + +// e.g. config yaml +// module: +// dir: "/path/to/my_modules" +// enable-modules: +// - spring4shell + +var ( + ModuleDirFlag = Flag[string]{ + Name: "module-dir", + ConfigName: "module.dir", + Default: module.DefaultDir, + Usage: "specify directory to the wasm modules that will be loaded", + Persistent: true, + } + EnableModulesFlag = Flag[[]string]{ + Name: "enable-modules", + ConfigName: "module.enable-modules", + Default: []string{}, + Usage: "[EXPERIMENTAL] module names to enable", + Persistent: true, + } +) + +// ModuleFlagGroup defines flags for modules +type ModuleFlagGroup struct { + Dir *Flag[string] + EnabledModules *Flag[[]string] +} + +type ModuleOptions struct { + ModuleDir string + EnabledModules []string +} + +func NewModuleFlagGroup() *ModuleFlagGroup { + return &ModuleFlagGroup{ + Dir: ModuleDirFlag.Clone(), + EnabledModules: EnableModulesFlag.Clone(), + } +} + +func (f *ModuleFlagGroup) Name() string { + return "Module" +} + +func (f *ModuleFlagGroup) Flags() []Flagger { + return []Flagger{ + f.Dir, + f.EnabledModules, + } +} + +func (f *ModuleFlagGroup) ToOptions() (ModuleOptions, error) { + if err := parseFlags(f); err != nil { + return ModuleOptions{}, err + } + + return ModuleOptions{ + ModuleDir: f.Dir.Value(), + EnabledModules: f.EnabledModules.Value(), + }, nil +} diff --git a/pkg/flag/options.go b/pkg/flag/options.go new file mode 100644 index 000000000000..cfdce46e2240 --- /dev/null +++ b/pkg/flag/options.go @@ -0,0 +1,746 @@ +package flag + +import ( + "context" + "fmt" + "io" + "os" + "strings" + "sync" + "time" + + "github.com/samber/lo" + "github.com/spf13/cast" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/plugin" + "github.com/aquasecurity/trivy/pkg/result" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/version" +) + +type FlagType interface { + int | string | []string | bool | time.Duration | float64 +} + +type Flag[T FlagType] struct { + // Name is for CLI flag and environment variable. + // If this field is empty, it will be available only in config file. + Name string + + // ConfigName is a key in config file. It is also used as a key of viper. + ConfigName string + + // Shorthand is a shorthand letter. + Shorthand string + + // Default is the default value. It should be defined when the value is different from the zero value. + Default T + + // Values is a list of allowed values. + // It currently supports string flags and string slice flags only. + Values []string + + // ValueNormalize is a function to normalize the value. + // It can be used for aliases, etc. + ValueNormalize func(T) T + + // Usage explains how to use the flag. + Usage string + + // Persistent represents if the flag is persistent + Persistent bool + + // Deprecated represents if the flag is deprecated + Deprecated bool + + // Aliases represents aliases + Aliases []Alias + + // value is the value passed through CLI flag, env, or config file. + // It is populated after flag.Parse() is called. + value T +} + +type Alias struct { + Name string + ConfigName string + Deprecated bool +} + +func (f *Flag[T]) Clone() *Flag[T] { + var t T + ff := *f + ff.value = t + fff := &ff + return fff +} + +func (f *Flag[T]) Parse() error { + if f == nil { + return nil + } + + v := f.parse() + if v == nil { + f.value = lo.Empty[T]() + return nil + } + + value, ok := f.cast(v).(T) + if !ok { + return xerrors.Errorf("failed to parse flag %s", f.Name) + } + + if f.ValueNormalize != nil { + value = f.ValueNormalize(value) + } + + if f.isSet() && !f.allowedValue(value) { + return xerrors.Errorf(`invalid argument "%s" for "--%s" flag: must be one of %q`, value, f.Name, f.Values) + } + + f.value = value + return nil +} + +func (f *Flag[T]) parse() any { + // First, looks for aliases in config file (trivy.yaml). + // Note that viper.RegisterAlias cannot be used for this purpose. + var v any + for _, alias := range f.Aliases { + if alias.ConfigName == "" { + continue + } + v = viper.Get(alias.ConfigName) + if v != nil { + log.Logger.Warnf("'%s' in config file is deprecated. Use '%s' instead.", alias.ConfigName, f.ConfigName) + return v + } + } + return viper.Get(f.ConfigName) +} + +// cast converts the value to the type of the flag. +func (f *Flag[T]) cast(val any) any { + switch any(f.Default).(type) { + case bool: + return cast.ToBool(val) + case string: + return cast.ToString(val) + case int: + return cast.ToInt(val) + case float64, float32: + return cast.ToFloat64(val) + case time.Duration: + return cast.ToDuration(val) + case []string: + if s, ok := val.(string); ok && strings.Contains(s, ",") { + // Split environmental variables by comma as it is not done by viper. + // cf. https://github.com/spf13/viper/issues/380 + // It is split by spaces only. + // https://github.com/spf13/cast/blob/48ddde5701366ade1d3aba346e09bb58430d37c6/caste.go#L1296-L1297 + val = strings.Split(s, ",") + } + return cast.ToStringSlice(val) + } + return val +} + +func (f *Flag[T]) isSet() bool { + configNames := lo.FilterMap(f.Aliases, func(alias Alias, _ int) (string, bool) { + return alias.ConfigName, alias.ConfigName != "" + }) + configNames = append(configNames, f.ConfigName) + + return lo.SomeBy(configNames, viper.IsSet) +} + +func (f *Flag[T]) allowedValue(v any) bool { + if len(f.Values) == 0 { + return true + } + switch value := v.(type) { + case string: + return slices.Contains(f.Values, value) + case []string: + for _, v := range value { + if !slices.Contains(f.Values, v) { + return false + } + } + } + return true +} + +func (f *Flag[T]) GetName() string { + return f.Name +} + +func (f *Flag[T]) GetAliases() []Alias { + return f.Aliases +} + +func (f *Flag[T]) Value() (t T) { + if f == nil { + return t + } + return f.value +} + +func (f *Flag[T]) Add(cmd *cobra.Command) { + if f == nil || f.Name == "" { + return + } + var flags *pflag.FlagSet + if f.Persistent { + flags = cmd.PersistentFlags() + } else { + flags = cmd.Flags() + } + + switch v := any(f.Default).(type) { + case int: + flags.IntP(f.Name, f.Shorthand, v, f.Usage) + case string: + usage := f.Usage + if len(f.Values) > 0 { + usage += fmt.Sprintf(" (%s)", strings.Join(f.Values, ",")) + } + flags.StringP(f.Name, f.Shorthand, v, usage) + case []string: + usage := f.Usage + if len(f.Values) > 0 { + usage += fmt.Sprintf(" (%s)", strings.Join(f.Values, ",")) + } + flags.StringSliceP(f.Name, f.Shorthand, v, usage) + case bool: + flags.BoolP(f.Name, f.Shorthand, v, f.Usage) + case time.Duration: + flags.DurationP(f.Name, f.Shorthand, v, f.Usage) + case float64: + flags.Float64P(f.Name, f.Shorthand, v, f.Usage) + } + + if f.Deprecated { + flags.MarkHidden(f.Name) // nolint: gosec + } +} + +func (f *Flag[T]) Bind(cmd *cobra.Command) error { + if f == nil { + return nil + } else if f.Name == "" { + // This flag is available only in trivy.yaml + viper.SetDefault(f.ConfigName, f.Default) + return nil + } + + // Bind CLI flags + flag := cmd.Flags().Lookup(f.Name) + if f == nil { + // Lookup local persistent flags + flag = cmd.PersistentFlags().Lookup(f.Name) + } + if err := viper.BindPFlag(f.ConfigName, flag); err != nil { + return xerrors.Errorf("bind flag error: %w", err) + } + + // Bind environmental variable + if err := f.BindEnv(); err != nil { + return err + } + + return nil +} + +func (f *Flag[T]) BindEnv() error { + // We don't use viper.AutomaticEnv, so we need to add a prefix manually here. + envName := strings.ToUpper("trivy_" + strings.ReplaceAll(f.Name, "-", "_")) + if err := viper.BindEnv(f.ConfigName, envName); err != nil { + return xerrors.Errorf("bind env error: %w", err) + } + + // Bind env aliases + for _, alias := range f.Aliases { + envAlias := strings.ToUpper("trivy_" + strings.ReplaceAll(alias.Name, "-", "_")) + if err := viper.BindEnv(f.ConfigName, envAlias); err != nil { + return xerrors.Errorf("bind env error: %w", err) + } + if alias.Deprecated { + if _, ok := os.LookupEnv(envAlias); ok { + log.Logger.Warnf("'%s' is deprecated. Use '%s' instead.", envAlias, envName) + } + } + } + return nil +} + +type FlagGroup interface { + Name() string + Flags() []Flagger +} + +type Flagger interface { + GetName() string + GetAliases() []Alias + + Parse() error + Add(cmd *cobra.Command) + Bind(cmd *cobra.Command) error +} + +type Flags struct { + GlobalFlagGroup *GlobalFlagGroup + AWSFlagGroup *AWSFlagGroup + CacheFlagGroup *CacheFlagGroup + CloudFlagGroup *CloudFlagGroup + DBFlagGroup *DBFlagGroup + ImageFlagGroup *ImageFlagGroup + K8sFlagGroup *K8sFlagGroup + LicenseFlagGroup *LicenseFlagGroup + MisconfFlagGroup *MisconfFlagGroup + ModuleFlagGroup *ModuleFlagGroup + RemoteFlagGroup *RemoteFlagGroup + RegistryFlagGroup *RegistryFlagGroup + RegoFlagGroup *RegoFlagGroup + RepoFlagGroup *RepoFlagGroup + ReportFlagGroup *ReportFlagGroup + SBOMFlagGroup *SBOMFlagGroup + ScanFlagGroup *ScanFlagGroup + SecretFlagGroup *SecretFlagGroup + VulnerabilityFlagGroup *VulnerabilityFlagGroup +} + +// Options holds all the runtime configuration +type Options struct { + GlobalOptions + AWSOptions + CacheOptions + CloudOptions + DBOptions + ImageOptions + K8sOptions + LicenseOptions + MisconfOptions + ModuleOptions + RegistryOptions + RegoOptions + RemoteOptions + RepoOptions + ReportOptions + SBOMOptions + ScanOptions + SecretOptions + VulnerabilityOptions + + // Trivy's version, not populated via CLI flags + AppVersion string + + // We don't want to allow disabled analyzers to be passed by users, but it is necessary for internal use. + DisabledAnalyzers []analyzer.Type + + // outputWriter is not initialized via the CLI. + // It is mainly used for testing purposes or by tools that use Trivy as a library. + outputWriter io.Writer +} + +// Align takes consistency of options +func (o *Options) Align() { + if o.Format == types.FormatSPDX || o.Format == types.FormatSPDXJSON { + log.Logger.Info(`"--format spdx" and "--format spdx-json" disable security scanning`) + o.Scanners = nil + } + + // Vulnerability scanning is disabled by default for CycloneDX. + if o.Format == types.FormatCycloneDX && !viper.IsSet(ScannersFlag.ConfigName) && len(o.K8sOptions.Components) == 0 { // remove K8sOptions.Components validation check when vuln scan is supported for k8s report with cycloneDX + log.Logger.Info(`"--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the CycloneDX report.`) + o.Scanners = nil + } + + if o.Format == types.FormatCycloneDX && len(o.K8sOptions.Components) > 0 { + log.Logger.Info(`"k8s with --format cyclonedx" disable security scanning`) + o.Scanners = nil + } +} + +// RegistryOpts returns options for OCI registries +func (o *Options) RegistryOpts() ftypes.RegistryOptions { + return ftypes.RegistryOptions{ + Credentials: o.Credentials, + RegistryToken: o.RegistryToken, + Insecure: o.Insecure, + Platform: o.Platform, + AWSRegion: o.AWSOptions.Region, + } +} + +// FilterOpts returns options for filtering +func (o *Options) FilterOpts() result.FilterOption { + return result.FilterOption{ + Severities: o.Severities, + IgnoreStatuses: o.IgnoreStatuses, + IncludeNonFailures: o.IncludeNonFailures, + IgnoreFile: o.IgnoreFile, + PolicyFile: o.IgnorePolicy, + IgnoreLicenses: o.IgnoredLicenses, + VEXPath: o.VEXPath, + } +} + +// SetOutputWriter sets an output writer. +func (o *Options) SetOutputWriter(w io.Writer) { + o.outputWriter = w +} + +// OutputWriter returns an output writer. +// If the output file is not specified, it returns os.Stdout. +func (o *Options) OutputWriter(ctx context.Context) (io.Writer, func() error, error) { + cleanup := func() error { return nil } + switch { + case o.outputWriter != nil: + return o.outputWriter, cleanup, nil + case o.Output == "": + return os.Stdout, cleanup, nil + case strings.HasPrefix(o.Output, "plugin="): + return o.outputPluginWriter(ctx) + } + + f, err := os.Create(o.Output) + if err != nil { + return nil, nil, xerrors.Errorf("failed to create output file: %w", err) + } + return f, f.Close, nil +} + +func (o *Options) outputPluginWriter(ctx context.Context) (io.Writer, func() error, error) { + pluginName := strings.TrimPrefix(o.Output, "plugin=") + + pr, pw := io.Pipe() + wait, err := plugin.Start(ctx, pluginName, plugin.RunOptions{ + Args: o.OutputPluginArgs, + Stdin: pr, + }) + if err != nil { + return nil, nil, xerrors.Errorf("plugin start: %w", err) + } + + cleanup := func() error { + if err = pw.Close(); err != nil { + return xerrors.Errorf("failed to close pipe: %w", err) + } + if err = wait(); err != nil { + return xerrors.Errorf("plugin error: %w", err) + } + return nil + } + return pw, cleanup, nil +} + +// groups returns all the flag groups other than global flags +func (f *Flags) groups() []FlagGroup { + var groups []FlagGroup + // This order affects the usage message, so they are sorted by frequency of use. + if f.ScanFlagGroup != nil { + groups = append(groups, f.ScanFlagGroup) + } + if f.ReportFlagGroup != nil { + groups = append(groups, f.ReportFlagGroup) + } + if f.CacheFlagGroup != nil { + groups = append(groups, f.CacheFlagGroup) + } + if f.DBFlagGroup != nil { + groups = append(groups, f.DBFlagGroup) + } + if f.RegistryFlagGroup != nil { + groups = append(groups, f.RegistryFlagGroup) + } + if f.ImageFlagGroup != nil { + groups = append(groups, f.ImageFlagGroup) + } + if f.SBOMFlagGroup != nil { + groups = append(groups, f.SBOMFlagGroup) + } + if f.VulnerabilityFlagGroup != nil { + groups = append(groups, f.VulnerabilityFlagGroup) + } + if f.MisconfFlagGroup != nil { + groups = append(groups, f.MisconfFlagGroup) + } + if f.ModuleFlagGroup != nil { + groups = append(groups, f.ModuleFlagGroup) + } + if f.SecretFlagGroup != nil { + groups = append(groups, f.SecretFlagGroup) + } + if f.LicenseFlagGroup != nil { + groups = append(groups, f.LicenseFlagGroup) + } + if f.RegoFlagGroup != nil { + groups = append(groups, f.RegoFlagGroup) + } + if f.CloudFlagGroup != nil { + groups = append(groups, f.CloudFlagGroup) + } + if f.AWSFlagGroup != nil { + groups = append(groups, f.AWSFlagGroup) + } + if f.K8sFlagGroup != nil { + groups = append(groups, f.K8sFlagGroup) + } + if f.RemoteFlagGroup != nil { + groups = append(groups, f.RemoteFlagGroup) + } + if f.RepoFlagGroup != nil { + groups = append(groups, f.RepoFlagGroup) + } + return groups +} + +func (f *Flags) AddFlags(cmd *cobra.Command) { + aliases := make(flagAliases) + for _, group := range f.groups() { + for _, flag := range group.Flags() { + if lo.IsNil(flag) || flag.GetName() == "" { + continue + } + // Register the CLI flag + flag.Add(cmd) + + // Register flag aliases + aliases.Add(flag) + } + } + + cmd.Flags().SetNormalizeFunc(aliases.NormalizeFunc()) +} + +func (f *Flags) Usages(cmd *cobra.Command) string { + var usages string + for _, group := range f.groups() { + flags := pflag.NewFlagSet(cmd.Name(), pflag.ContinueOnError) + lflags := cmd.LocalFlags() + for _, flag := range group.Flags() { + if lo.IsNil(flag) || flag.GetName() == "" { + continue + } + flags.AddFlag(lflags.Lookup(flag.GetName())) + } + if !flags.HasAvailableFlags() { + continue + } + + usages += fmt.Sprintf("%s Flags\n", group.Name()) + usages += flags.FlagUsages() + "\n" + } + return strings.TrimSpace(usages) +} + +func (f *Flags) Bind(cmd *cobra.Command) error { + for _, group := range f.groups() { + if group == nil { + continue + } + for _, flag := range group.Flags() { + if err := flag.Bind(cmd); err != nil { + return xerrors.Errorf("flag groups: %w", err) + } + } + } + return nil +} + +// nolint: gocyclo +func (f *Flags) ToOptions(args []string) (Options, error) { + var err error + opts := Options{ + AppVersion: version.AppVersion(), + } + + if f.GlobalFlagGroup != nil { + opts.GlobalOptions, err = f.GlobalFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("global flag error: %w", err) + } + } + + if f.AWSFlagGroup != nil { + opts.AWSOptions, err = f.AWSFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("aws flag error: %w", err) + } + } + + if f.CloudFlagGroup != nil { + opts.CloudOptions, err = f.CloudFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("cloud flag error: %w", err) + } + } + + if f.CacheFlagGroup != nil { + opts.CacheOptions, err = f.CacheFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("cache flag error: %w", err) + } + } + + if f.DBFlagGroup != nil { + opts.DBOptions, err = f.DBFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("db flag error: %w", err) + } + } + + if f.ImageFlagGroup != nil { + opts.ImageOptions, err = f.ImageFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("image flag error: %w", err) + } + } + + if f.K8sFlagGroup != nil { + opts.K8sOptions, err = f.K8sFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("k8s flag error: %w", err) + } + } + + if f.LicenseFlagGroup != nil { + opts.LicenseOptions, err = f.LicenseFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("license flag error: %w", err) + } + } + + if f.MisconfFlagGroup != nil { + opts.MisconfOptions, err = f.MisconfFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("misconfiguration flag error: %w", err) + } + } + + if f.ModuleFlagGroup != nil { + opts.ModuleOptions, err = f.ModuleFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("module flag error: %w", err) + } + } + + if f.RegoFlagGroup != nil { + opts.RegoOptions, err = f.RegoFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("rego flag error: %w", err) + } + } + + if f.RemoteFlagGroup != nil { + opts.RemoteOptions, err = f.RemoteFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("remote flag error: %w", err) + } + } + + if f.RegistryFlagGroup != nil { + opts.RegistryOptions, err = f.RegistryFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("registry flag error: %w", err) + } + } + + if f.RepoFlagGroup != nil { + opts.RepoOptions, err = f.RepoFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("rego flag error: %w", err) + } + } + + if f.ReportFlagGroup != nil { + opts.ReportOptions, err = f.ReportFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("report flag error: %w", err) + } + } + + if f.SBOMFlagGroup != nil { + opts.SBOMOptions, err = f.SBOMFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("sbom flag error: %w", err) + } + } + + if f.ScanFlagGroup != nil { + opts.ScanOptions, err = f.ScanFlagGroup.ToOptions(args) + if err != nil { + return Options{}, xerrors.Errorf("scan flag error: %w", err) + } + } + + if f.SecretFlagGroup != nil { + opts.SecretOptions, err = f.SecretFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("secret flag error: %w", err) + } + } + + if f.VulnerabilityFlagGroup != nil { + opts.VulnerabilityOptions, err = f.VulnerabilityFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("vulnerability flag error: %w", err) + } + } + + opts.Align() + + return opts, nil +} + +func parseFlags(fg FlagGroup) error { + for _, flag := range fg.Flags() { + if err := flag.Parse(); err != nil { + return xerrors.Errorf("unable to parse flag: %w", err) + } + } + return nil +} + +type flagAlias struct { + formalName string + deprecated bool + once sync.Once +} + +// flagAliases have aliases for CLI flags +type flagAliases map[string]*flagAlias + +func (a flagAliases) Add(flag Flagger) { + for _, alias := range flag.GetAliases() { + a[alias.Name] = &flagAlias{ + formalName: flag.GetName(), + deprecated: alias.Deprecated, + } + } +} + +func (a flagAliases) NormalizeFunc() func(*pflag.FlagSet, string) pflag.NormalizedName { + return func(_ *pflag.FlagSet, name string) pflag.NormalizedName { + if alias, ok := a[name]; ok { + if alias.deprecated { + // NormalizeFunc is called several times + alias.once.Do(func() { + log.Logger.Warnf("'--%s' is deprecated. Use '--%s' instead.", name, alias.formalName) + }) + } + name = alias.formalName + } + return pflag.NormalizedName(name) + } +} diff --git a/pkg/flag/options_test.go b/pkg/flag/options_test.go new file mode 100644 index 000000000000..092e09d7b411 --- /dev/null +++ b/pkg/flag/options_test.go @@ -0,0 +1,127 @@ +package flag_test + +import ( + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/samber/lo" + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" + "testing" + + "github.com/spf13/viper" +) + +func TestFlag_Parse(t *testing.T) { + type kv struct { + key string + value any + } + tests := []struct { + name string + flag *kv + env *kv + want []string + wantErr string + }{ + { + name: "flag, string slice", + flag: &kv{ + key: "scan.scanners", + value: []string{ + "vuln", + "misconfig", + }, + }, + want: []string{ + string(types.VulnerabilityScanner), + string(types.MisconfigScanner), + }, + }, + { + name: "env, string", + env: &kv{ + key: "TRIVY_SCANNERS", + value: "vuln,misconfig", + }, + want: []string{ + string(types.VulnerabilityScanner), + string(types.MisconfigScanner), + }, + }, + { + name: "flag, alias", + flag: &kv{ + key: "scan.security-checks", + value: "vulnerability,config", + }, + want: []string{ + string(types.VulnerabilityScanner), + string(types.MisconfigScanner), + }, + }, + { + name: "env, alias", + env: &kv{ + key: "TRIVY_SECURITY_CHECKS", + value: "vulnerability,config", + }, + want: []string{ + string(types.VulnerabilityScanner), + string(types.MisconfigScanner), + }, + }, + { + name: "flag, invalid value", + flag: &kv{ + key: "scan.scanners", + value: "vuln,invalid", + }, + wantErr: `invalid argument "[vuln invalid]" for "--scanners" flag`, + }, + { + name: "env, invalid value", + env: &kv{ + key: "TRIVY_SCANNERS", + value: "vuln,invalid", + }, + wantErr: `invalid argument "[vuln invalid]" for "--scanners" flag`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Cleanup(viper.Reset) + + if tt.flag != nil { + viper.Set(tt.flag.key, tt.flag.value) + } else { + t.Setenv(tt.env.key, tt.env.value.(string)) + } + + app := &cobra.Command{} + f := flag.ScannersFlag.Clone() + f.Add(app) + require.NoError(t, f.Bind(app)) + + err := f.Parse() + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, f.Value()) + }) + } +} + +func setValue[T comparable](key string, value T) { + if !lo.IsEmpty(value) { + viper.Set(key, value) + } +} + +func setSliceValue[T any](key string, value []T) { + if len(value) > 0 { + viper.Set(key, value) + } +} diff --git a/pkg/flag/registry_flags.go b/pkg/flag/registry_flags.go new file mode 100644 index 000000000000..552eaf4276d6 --- /dev/null +++ b/pkg/flag/registry_flags.go @@ -0,0 +1,82 @@ +package flag + +import ( + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var ( + UsernameFlag = Flag[[]string]{ + Name: "username", + ConfigName: "registry.username", + Usage: "username. Comma-separated usernames allowed.", + } + PasswordFlag = Flag[[]string]{ + Name: "password", + ConfigName: "registry.password", + Usage: "password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons.", + } + RegistryTokenFlag = Flag[string]{ + Name: "registry-token", + ConfigName: "registry.token", + Usage: "registry token", + } +) + +type RegistryFlagGroup struct { + Username *Flag[[]string] + Password *Flag[[]string] + RegistryToken *Flag[string] +} + +type RegistryOptions struct { + Credentials []types.Credential + RegistryToken string +} + +func NewRegistryFlagGroup() *RegistryFlagGroup { + return &RegistryFlagGroup{ + Username: UsernameFlag.Clone(), + Password: PasswordFlag.Clone(), + RegistryToken: RegistryTokenFlag.Clone(), + } +} + +func (f *RegistryFlagGroup) Name() string { + return "Registry" +} + +func (f *RegistryFlagGroup) Flags() []Flagger { + return []Flagger{ + f.Username, + f.Password, + f.RegistryToken, + } +} + +func (f *RegistryFlagGroup) ToOptions() (RegistryOptions, error) { + if err := parseFlags(f); err != nil { + return RegistryOptions{}, err + } + + var credentials []types.Credential + users := f.Username.Value() + passwords := f.Password.Value() + if len(users) != len(passwords) { + return RegistryOptions{}, xerrors.New("the length of usernames and passwords must match") + } + for i, user := range users { + credentials = append(credentials, types.Credential{ + Username: strings.TrimSpace(user), + Password: strings.TrimSpace(passwords[i]), + }) + } + + return RegistryOptions{ + Credentials: credentials, + RegistryToken: f.RegistryToken.Value(), + }, nil +} diff --git a/pkg/flag/rego_flags.go b/pkg/flag/rego_flags.go new file mode 100644 index 000000000000..e0b21f73030b --- /dev/null +++ b/pkg/flag/rego_flags.go @@ -0,0 +1,99 @@ +package flag + +// e.g. config yaml: +// +// rego: +// trace: true +// config-policy: "custom-policy/policy" +// policy-namespaces: "user" +var ( + SkipPolicyUpdateFlag = Flag[bool]{ + Name: "skip-policy-update", + ConfigName: "rego.skip-policy-update", + Usage: "skip fetching rego policy updates", + } + TraceFlag = Flag[bool]{ + Name: "trace", + ConfigName: "rego.trace", + Usage: "enable more verbose trace output for custom queries", + } + ConfigPolicyFlag = Flag[[]string]{ + Name: "config-policy", + ConfigName: "rego.policy", + Usage: "specify the paths to the Rego policy files or to the directories containing them, applying config files", + Aliases: []Alias{ + {Name: "policy"}, + }, + } + ConfigDataFlag = Flag[[]string]{ + Name: "config-data", + ConfigName: "rego.data", + Usage: "specify paths from which data for the Rego policies will be recursively loaded", + Aliases: []Alias{ + {Name: "data"}, + }, + } + PolicyNamespaceFlag = Flag[[]string]{ + Name: "policy-namespaces", + ConfigName: "rego.namespaces", + Usage: "Rego namespaces", + Aliases: []Alias{ + {Name: "namespaces"}, + }, + } +) + +// RegoFlagGroup composes common printer flag structs used for commands providing misconfinguration scanning. +type RegoFlagGroup struct { + SkipPolicyUpdate *Flag[bool] + Trace *Flag[bool] + PolicyPaths *Flag[[]string] + DataPaths *Flag[[]string] + PolicyNamespaces *Flag[[]string] +} + +type RegoOptions struct { + SkipPolicyUpdate bool + Trace bool + PolicyPaths []string + DataPaths []string + PolicyNamespaces []string +} + +func NewRegoFlagGroup() *RegoFlagGroup { + return &RegoFlagGroup{ + SkipPolicyUpdate: SkipPolicyUpdateFlag.Clone(), + Trace: TraceFlag.Clone(), + PolicyPaths: ConfigPolicyFlag.Clone(), + DataPaths: ConfigDataFlag.Clone(), + PolicyNamespaces: PolicyNamespaceFlag.Clone(), + } +} + +func (f *RegoFlagGroup) Name() string { + return "Rego" +} + +func (f *RegoFlagGroup) Flags() []Flagger { + return []Flagger{ + f.SkipPolicyUpdate, + f.Trace, + f.PolicyPaths, + f.DataPaths, + f.PolicyNamespaces, + } +} + +func (f *RegoFlagGroup) ToOptions() (RegoOptions, error) { + if err := parseFlags(f); err != nil { + return RegoOptions{}, err + } + + return RegoOptions{ + SkipPolicyUpdate: f.SkipPolicyUpdate.Value(), + Trace: f.Trace.Value(), + PolicyPaths: f.PolicyPaths.Value(), + DataPaths: f.DataPaths.Value(), + PolicyNamespaces: f.PolicyNamespaces.Value(), + }, nil +} diff --git a/pkg/flag/remote_flags.go b/pkg/flag/remote_flags.go new file mode 100644 index 000000000000..9277f2db908f --- /dev/null +++ b/pkg/flag/remote_flags.go @@ -0,0 +1,149 @@ +package flag + +import ( + "net/http" + "strings" + + "github.com/aquasecurity/trivy/pkg/log" +) + +const ( + DefaultTokenHeader = "Trivy-Token" +) + +var ( + ServerTokenFlag = Flag[string]{ + Name: "token", + ConfigName: "server.token", + Usage: "for authentication in client/server mode", + } + ServerTokenHeaderFlag = Flag[string]{ + Name: "token-header", + ConfigName: "server.token-header", + Default: DefaultTokenHeader, + Usage: "specify a header name for token in client/server mode", + } + ServerAddrFlag = Flag[string]{ + Name: "server", + ConfigName: "server.addr", + Usage: "server address in client mode", + } + ServerCustomHeadersFlag = Flag[[]string]{ + Name: "custom-headers", + ConfigName: "server.custom-headers", + Usage: "custom headers in client mode", + } + ServerListenFlag = Flag[string]{ + Name: "listen", + ConfigName: "server.listen", + Default: "localhost:4954", + Usage: "listen address in server mode", + } +) + +// RemoteFlagGroup composes common printer flag structs +// used for commands requiring reporting logic. +type RemoteFlagGroup struct { + // for client/server + Token *Flag[string] + TokenHeader *Flag[string] + + // for client + ServerAddr *Flag[string] + CustomHeaders *Flag[[]string] + + // for server + Listen *Flag[string] +} + +type RemoteOptions struct { + Token string + TokenHeader string + + ServerAddr string + Listen string + CustomHeaders http.Header +} + +func NewClientFlags() *RemoteFlagGroup { + return &RemoteFlagGroup{ + Token: ServerTokenFlag.Clone(), + TokenHeader: ServerTokenHeaderFlag.Clone(), + ServerAddr: ServerAddrFlag.Clone(), + CustomHeaders: ServerCustomHeadersFlag.Clone(), + } +} + +func NewServerFlags() *RemoteFlagGroup { + return &RemoteFlagGroup{ + Token: &ServerTokenFlag, + TokenHeader: &ServerTokenHeaderFlag, + Listen: &ServerListenFlag, + } +} + +func (f *RemoteFlagGroup) Name() string { + return "Client/Server" +} + +func (f *RemoteFlagGroup) Flags() []Flagger { + return []Flagger{ + f.Token, + f.TokenHeader, + f.ServerAddr, + f.CustomHeaders, + f.Listen, + } +} + +func (f *RemoteFlagGroup) ToOptions() (RemoteOptions, error) { + if err := parseFlags(f); err != nil { + return RemoteOptions{}, err + } + + serverAddr := f.ServerAddr.Value() + customHeaders := splitCustomHeaders(f.CustomHeaders.Value()) + listen := f.Listen.Value() + token := f.Token.Value() + tokenHeader := f.TokenHeader.Value() + + if serverAddr == "" && listen == "" { + switch { + case len(customHeaders) > 0: + log.Logger.Warn(`"--custom-header" can be used only with "--server"`) + case token != "": + log.Logger.Warn(`"--token" can be used only with "--server"`) + case tokenHeader != "" && tokenHeader != DefaultTokenHeader: + log.Logger.Warn(`"--token-header" can be used only with "--server"`) + } + } + + if token == "" && tokenHeader != DefaultTokenHeader { + log.Logger.Warn(`"--token-header" should be used with "--token"`) + } + + if token != "" && tokenHeader != "" { + customHeaders.Set(tokenHeader, token) + } + + return RemoteOptions{ + Token: token, + TokenHeader: tokenHeader, + ServerAddr: serverAddr, + CustomHeaders: customHeaders, + Listen: listen, + }, nil +} + +func splitCustomHeaders(headers []string) http.Header { + result := make(http.Header) + for _, header := range headers { + // e.g. x-api-token:XXX + s := strings.SplitN(header, ":", 2) + if len(s) != 2 { + continue + } + result.Set(s[0], s[1]) + } + return result +} diff --git a/pkg/flag/remote_flags_test.go b/pkg/flag/remote_flags_test.go new file mode 100644 index 000000000000..4500b0bb5ca9 --- /dev/null +++ b/pkg/flag/remote_flags_test.go @@ -0,0 +1,129 @@ +package flag_test + +import ( + "github.com/stretchr/testify/require" + "net/http" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" + + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" +) + +func TestRemoteFlagGroup_ToOptions(t *testing.T) { + type fields struct { + Server string + CustomHeaders []string + Token string + TokenHeader string + } + tests := []struct { + name string + fields fields + want flag.RemoteOptions + wantLogs []string + }{ + { + name: "happy", + fields: fields{ + Server: "http://localhost:4954", + CustomHeaders: []string{ + "x-api-token:foo bar", + "Authorization:user:password", + }, + Token: "token", + TokenHeader: "Trivy-Token", + }, + want: flag.RemoteOptions{ + ServerAddr: "http://localhost:4954", + CustomHeaders: http.Header{ + "X-Api-Token": []string{"foo bar"}, + "Authorization": []string{"user:password"}, + "Trivy-Token": []string{"token"}, + }, + Token: "token", + TokenHeader: "Trivy-Token", + }, + }, + { + name: "custom headers and no server", + fields: fields{ + CustomHeaders: []string{ + "Authorization:user:password", + }, + TokenHeader: "Trivy-Token", + }, + want: flag.RemoteOptions{ + CustomHeaders: http.Header{ + "Authorization": []string{"user:password"}, + }, + TokenHeader: "Trivy-Token", + }, + wantLogs: []string{ + `"--custom-header" can be used only with "--server"`, + }, + }, + { + name: "token and no server", + fields: fields{ + Token: "token", + }, + want: flag.RemoteOptions{ + CustomHeaders: http.Header{}, + Token: "token", + }, + wantLogs: []string{ + `"--token" can be used only with "--server"`, + }, + }, + { + name: "token header and no token", + fields: fields{ + Server: "http://localhost:4954", + TokenHeader: "Non-Default", + }, + want: flag.RemoteOptions{ + CustomHeaders: http.Header{}, + ServerAddr: "http://localhost:4954", + TokenHeader: "Non-Default", + }, + wantLogs: []string{ + `"--token-header" should be used with "--token"`, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + level := zap.WarnLevel + core, obs := observer.New(level) + log.Logger = zap.New(core).Sugar() + + viper.Set(flag.ServerAddrFlag.ConfigName, tt.fields.Server) + viper.Set(flag.ServerCustomHeadersFlag.ConfigName, tt.fields.CustomHeaders) + viper.Set(flag.ServerTokenFlag.ConfigName, tt.fields.Token) + viper.Set(flag.ServerTokenHeaderFlag.ConfigName, tt.fields.TokenHeader) + + // Assert options + f := &flag.RemoteFlagGroup{ + ServerAddr: flag.ServerAddrFlag.Clone(), + CustomHeaders: flag.ServerCustomHeadersFlag.Clone(), + Token: flag.ServerTokenFlag.Clone(), + TokenHeader: flag.ServerTokenHeaderFlag.Clone(), + } + got, err := f.ToOptions() + require.NoError(t, err) + assert.Equalf(t, tt.want, got, "ToOptions()") + + // Assert log messages + var gotMessages []string + for _, entry := range obs.AllUntimed() { + gotMessages = append(gotMessages, entry.Message) + } + assert.Equal(t, tt.wantLogs, gotMessages, tt.name) + }) + } +} diff --git a/pkg/flag/repo_flags.go b/pkg/flag/repo_flags.go new file mode 100644 index 000000000000..31ac0b634a3e --- /dev/null +++ b/pkg/flag/repo_flags.go @@ -0,0 +1,63 @@ +package flag + +var ( + FetchBranchFlag = Flag[string]{ + Name: "branch", + ConfigName: "repository.branch", + Usage: "pass the branch name to be scanned", + } + FetchCommitFlag = Flag[string]{ + Name: "commit", + ConfigName: "repository.commit", + Usage: "pass the commit hash to be scanned", + } + FetchTagFlag = Flag[string]{ + Name: "tag", + ConfigName: "repository.tag", + Usage: "pass the tag name to be scanned", + } +) + +type RepoFlagGroup struct { + Branch *Flag[string] + Commit *Flag[string] + Tag *Flag[string] +} + +type RepoOptions struct { + RepoBranch string + RepoCommit string + RepoTag string +} + +func NewRepoFlagGroup() *RepoFlagGroup { + return &RepoFlagGroup{ + Branch: FetchBranchFlag.Clone(), + Commit: FetchCommitFlag.Clone(), + Tag: FetchTagFlag.Clone(), + } +} + +func (f *RepoFlagGroup) Name() string { + return "Repository" +} + +func (f *RepoFlagGroup) Flags() []Flagger { + return []Flagger{ + f.Branch, + f.Commit, + f.Tag, + } +} + +func (f *RepoFlagGroup) ToOptions() (RepoOptions, error) { + if err := parseFlags(f); err != nil { + return RepoOptions{}, err + } + + return RepoOptions{ + RepoBranch: f.Branch.Value(), + RepoCommit: f.Commit.Value(), + RepoTag: f.Tag.Value(), + }, nil +} diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go new file mode 100644 index 000000000000..94b8c2ff689d --- /dev/null +++ b/pkg/flag/report_flags.go @@ -0,0 +1,305 @@ +package flag + +import ( + "strings" + + "github.com/mattn/go-shellwords" + "github.com/samber/lo" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/compliance/spec" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/result" + "github.com/aquasecurity/trivy/pkg/types" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" +) + +// e.g. config yaml: +// +// format: table +// dependency-tree: true +// severity: HIGH,CRITICAL +var ( + FormatFlag = Flag[string]{ + Name: "format", + ConfigName: "format", + Shorthand: "f", + Default: string(types.FormatTable), + Values: xstrings.ToStringSlice(types.SupportedFormats), + Usage: "format", + } + ReportFormatFlag = Flag[string]{ + Name: "report", + ConfigName: "report", + Default: "all", + Values: []string{ + "all", + "summary", + }, + Usage: "specify a report format for the output", + } + TemplateFlag = Flag[string]{ + Name: "template", + ConfigName: "template", + Shorthand: "t", + Usage: "output template", + } + DependencyTreeFlag = Flag[bool]{ + Name: "dependency-tree", + ConfigName: "dependency-tree", + Usage: "[EXPERIMENTAL] show dependency origin tree of vulnerable packages", + } + ListAllPkgsFlag = Flag[bool]{ + Name: "list-all-pkgs", + ConfigName: "list-all-pkgs", + Usage: "enabling the option will output all packages regardless of vulnerability", + } + IgnoreFileFlag = Flag[string]{ + Name: "ignorefile", + ConfigName: "ignorefile", + Default: result.DefaultIgnoreFile, + Usage: "specify .trivyignore file", + } + IgnorePolicyFlag = Flag[string]{ + Name: "ignore-policy", + ConfigName: "ignore-policy", + Usage: "specify the Rego file path to evaluate each vulnerability", + } + ExitCodeFlag = Flag[int]{ + Name: "exit-code", + ConfigName: "exit-code", + Usage: "specify exit code when any security issues are found", + } + ExitOnEOLFlag = Flag[int]{ + Name: "exit-on-eol", + ConfigName: "exit-on-eol", + Usage: "exit with the specified code when the OS reaches end of service/life", + } + OutputFlag = Flag[string]{ + Name: "output", + ConfigName: "output", + Shorthand: "o", + Usage: "output file name", + } + OutputPluginArgFlag = Flag[string]{ + Name: "output-plugin-arg", + ConfigName: "output-plugin-arg", + Usage: "[EXPERIMENTAL] output plugin arguments", + } + SeverityFlag = Flag[[]string]{ + Name: "severity", + ConfigName: "severity", + Shorthand: "s", + Default: dbTypes.SeverityNames, + Values: dbTypes.SeverityNames, + Usage: "severities of security issues to be displayed", + } + ComplianceFlag = Flag[string]{ + Name: "compliance", + ConfigName: "scan.compliance", + Usage: "compliance report to generate", + } + ShowSuppressedFlag = Flag[bool]{ + Name: "show-suppressed", + ConfigName: "scan.show-suppressed", + Usage: "[EXPERIMENTAL] show suppressed vulnerabilities", + } +) + +// ReportFlagGroup composes common printer flag structs +// used for commands requiring reporting logic. +type ReportFlagGroup struct { + Format *Flag[string] + ReportFormat *Flag[string] + Template *Flag[string] + DependencyTree *Flag[bool] + ListAllPkgs *Flag[bool] + IgnoreFile *Flag[string] + IgnorePolicy *Flag[string] + ExitCode *Flag[int] + ExitOnEOL *Flag[int] + Output *Flag[string] + OutputPluginArg *Flag[string] + Severity *Flag[[]string] + Compliance *Flag[string] + ShowSuppressed *Flag[bool] +} + +type ReportOptions struct { + Format types.Format + ReportFormat string + Template string + DependencyTree bool + ListAllPkgs bool + IgnoreFile string + ExitCode int + ExitOnEOL int + IgnorePolicy string + Output string + OutputPluginArgs []string + Severities []dbTypes.Severity + Compliance spec.ComplianceSpec + ShowSuppressed bool +} + +func NewReportFlagGroup() *ReportFlagGroup { + return &ReportFlagGroup{ + Format: FormatFlag.Clone(), + ReportFormat: ReportFormatFlag.Clone(), + Template: TemplateFlag.Clone(), + DependencyTree: DependencyTreeFlag.Clone(), + ListAllPkgs: ListAllPkgsFlag.Clone(), + IgnoreFile: IgnoreFileFlag.Clone(), + IgnorePolicy: IgnorePolicyFlag.Clone(), + ExitCode: ExitCodeFlag.Clone(), + ExitOnEOL: ExitOnEOLFlag.Clone(), + Output: OutputFlag.Clone(), + OutputPluginArg: OutputPluginArgFlag.Clone(), + Severity: SeverityFlag.Clone(), + Compliance: ComplianceFlag.Clone(), + ShowSuppressed: ShowSuppressedFlag.Clone(), + } +} + +func (f *ReportFlagGroup) Name() string { + return "Report" +} + +func (f *ReportFlagGroup) Flags() []Flagger { + return []Flagger{ + f.Format, + f.ReportFormat, + f.Template, + f.DependencyTree, + f.ListAllPkgs, + f.IgnoreFile, + f.IgnorePolicy, + f.ExitCode, + f.ExitOnEOL, + f.Output, + f.OutputPluginArg, + f.Severity, + f.Compliance, + f.ShowSuppressed, + } +} + +func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { + if err := parseFlags(f); err != nil { + return ReportOptions{}, err + } + + format := types.Format(f.Format.Value()) + template := f.Template.Value() + dependencyTree := f.DependencyTree.Value() + listAllPkgs := f.ListAllPkgs.Value() + + if template != "" { + if format == "" { + log.Logger.Warn("'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.") + } else if format != "template" { + log.Logger.Warnf("'--template' is ignored because '--format %s' is specified. Use '--template' option with '--format template' option.", format) + } + } else { + if format == types.FormatTemplate { + log.Logger.Warn("'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.") + } + } + + // "--list-all-pkgs" option is unavailable with "--format table". + // If user specifies "--list-all-pkgs" with "--format table", we should warn it. + if listAllPkgs && format == types.FormatTable { + log.Logger.Warn(`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`) + } + + // "--dependency-tree" option is available only with "--format table". + if dependencyTree { + log.Logger.Infof(`"--dependency-tree" only shows the dependents of vulnerable packages. ` + + `Note that it is the reverse of the usual dependency tree, which shows the packages that depend on the vulnerable package. ` + + `It supports limited package managers. Please see the document for the detail.`) + if format != types.FormatTable { + log.Logger.Warn(`"--dependency-tree" can be used only with "--format table".`) + } + } + + // Enable '--list-all-pkgs' if needed + if f.forceListAllPkgs(format, listAllPkgs, dependencyTree) { + listAllPkgs = true + } + + cs, err := loadComplianceTypes(f.Compliance.Value()) + if err != nil { + return ReportOptions{}, xerrors.Errorf("unable to load compliance spec: %w", err) + } + + var outputPluginArgs []string + if arg := f.OutputPluginArg.Value(); arg != "" { + outputPluginArgs, err = shellwords.Parse(arg) + if err != nil { + return ReportOptions{}, xerrors.Errorf("unable to parse output plugin argument: %w", err) + } + } + + return ReportOptions{ + Format: format, + ReportFormat: f.ReportFormat.Value(), + Template: template, + DependencyTree: dependencyTree, + ListAllPkgs: listAllPkgs, + IgnoreFile: f.IgnoreFile.Value(), + ExitCode: f.ExitCode.Value(), + ExitOnEOL: f.ExitOnEOL.Value(), + IgnorePolicy: f.IgnorePolicy.Value(), + Output: f.Output.Value(), + OutputPluginArgs: outputPluginArgs, + Severities: toSeverity(f.Severity.Value()), + Compliance: cs, + ShowSuppressed: f.ShowSuppressed.Value(), + }, nil +} + +func loadComplianceTypes(compliance string) (spec.ComplianceSpec, error) { + if len(compliance) > 0 && !slices.Contains(types.SupportedCompliances, compliance) && !strings.HasPrefix(compliance, "@") { + return spec.ComplianceSpec{}, xerrors.Errorf("unknown compliance : %v", compliance) + } + + cs, err := spec.GetComplianceSpec(compliance) + if err != nil { + return spec.ComplianceSpec{}, xerrors.Errorf("spec loading from file system error: %w", err) + } + + return cs, nil +} + +func (f *ReportFlagGroup) forceListAllPkgs(format types.Format, listAllPkgs, dependencyTree bool) bool { + if slices.Contains(types.SupportedSBOMFormats, format) && !listAllPkgs { + log.Logger.Debugf("%q automatically enables '--list-all-pkgs'.", types.SupportedSBOMFormats) + return true + } + // We need this flag to insert dependency locations into Sarif('Package' struct contains 'Locations') + if format == types.FormatSarif && !listAllPkgs { + log.Logger.Debugf("Sarif format automatically enables '--list-all-pkgs' to get locations") + return true + } + if dependencyTree && !listAllPkgs { + log.Logger.Debugf("'--dependency-tree' enables '--list-all-pkgs'.") + return true + } + return false +} + +func toSeverity(severity []string) []dbTypes.Severity { + if len(severity) == 0 { + return nil + } + severities := lo.Map(severity, func(s string, _ int) dbTypes.Severity { + // Note that there is no need to check the error here + // since the severity value is already validated in the flag parser. + sev, _ := dbTypes.NewSeverity(s) + return sev + }) + log.Logger.Debugf("Severities: %q", severities) + return severities +} diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go new file mode 100644 index 000000000000..1d230398d5c7 --- /dev/null +++ b/pkg/flag/report_flags_test.go @@ -0,0 +1,239 @@ +package flag_test + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/compliance/spec" + "github.com/aquasecurity/trivy/pkg/flag" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestReportFlagGroup_ToOptions(t *testing.T) { + type fields struct { + format types.Format + template string + dependencyTree bool + listAllPkgs bool + ignoreUnfixed bool + ignoreFile string + exitCode int + exitOnEOSL bool + ignorePolicy string + output string + outputPluginArgs string + severities string + compliance string + debug bool + } + tests := []struct { + name string + fields fields + want flag.ReportOptions + wantLogs []string + }{ + { + name: "happy default (without flags)", + fields: fields{}, + want: flag.ReportOptions{}, + }, + { + name: "happy path with an cyclonedx", + fields: fields{ + severities: "CRITICAL", + format: "cyclonedx", + listAllPkgs: true, + }, + want: flag.ReportOptions{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + Format: types.FormatCycloneDX, + ListAllPkgs: true, + }, + }, + { + name: "happy path with an cyclonedx option list-all-pkgs is false", + fields: fields{ + severities: "CRITICAL", + format: "cyclonedx", + listAllPkgs: false, + debug: true, + }, + wantLogs: []string{ + `["cyclonedx" "spdx" "spdx-json" "github"] automatically enables '--list-all-pkgs'.`, + `Severities: ["CRITICAL"]`, + }, + want: flag.ReportOptions{ + Severities: []dbTypes.Severity{ + dbTypes.SeverityCritical, + }, + Format: types.FormatCycloneDX, + ListAllPkgs: true, + }, + }, + { + name: "invalid option combination: --template enabled without --format", + fields: fields{ + template: "@contrib/gitlab.tpl", + severities: "LOW", + }, + wantLogs: []string{ + "'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.", + }, + want: flag.ReportOptions{ + Severities: []dbTypes.Severity{dbTypes.SeverityLow}, + Template: "@contrib/gitlab.tpl", + }, + }, + { + name: "invalid option combination: --template and --format json", + fields: fields{ + format: "json", + template: "@contrib/gitlab.tpl", + severities: "LOW", + }, + wantLogs: []string{ + "'--template' is ignored because '--format json' is specified. Use '--template' option with '--format template' option.", + }, + want: flag.ReportOptions{ + Format: "json", + Severities: []dbTypes.Severity{dbTypes.SeverityLow}, + Template: "@contrib/gitlab.tpl", + }, + }, + { + name: "invalid option combination: --format template without --template", + fields: fields{ + format: "template", + severities: "LOW", + }, + wantLogs: []string{ + "'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.", + }, + want: flag.ReportOptions{ + Format: "template", + Severities: []dbTypes.Severity{dbTypes.SeverityLow}, + }, + }, + { + name: "invalid option combination: --list-all-pkgs with --format table", + fields: fields{ + format: "table", + severities: "LOW", + listAllPkgs: true, + }, + wantLogs: []string{ + `"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`, + }, + want: flag.ReportOptions{ + Format: "table", + Severities: []dbTypes.Severity{dbTypes.SeverityLow}, + ListAllPkgs: true, + }, + }, + { + name: "happy path with output plugin args", + fields: fields{ + output: "plugin=count", + outputPluginArgs: "--publish-after 2023-10-01 --publish-before 2023-10-02", + }, + want: flag.ReportOptions{ + Output: "plugin=count", + OutputPluginArgs: []string{ + "--publish-after", + "2023-10-01", + "--publish-before", + "2023-10-02", + }, + }, + }, + { + name: "happy path with compliance", + fields: fields{ + compliance: "@testdata/example-spec.yaml", + severities: dbTypes.SeverityLow.String(), + }, + want: flag.ReportOptions{ + Compliance: spec.ComplianceSpec{ + Spec: iacTypes.Spec{ + ID: "0001", + Title: "my-custom-spec", + Description: "My fancy spec", + Version: "1.2", + Controls: []iacTypes.Control{ + { + ID: "1.1", + Name: "Unencrypted S3 bucket", + Description: "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-AWS-0088"}, + }, + Severity: "HIGH", + }, + }, + }, + }, + Severities: []dbTypes.Severity{dbTypes.SeverityLow}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Cleanup(viper.Reset) + + level := zap.WarnLevel + if tt.fields.debug { + level = zap.DebugLevel + } + core, obs := observer.New(level) + log.Logger = zap.New(core).Sugar() + + setValue(flag.FormatFlag.ConfigName, string(tt.fields.format)) + setValue(flag.TemplateFlag.ConfigName, tt.fields.template) + setValue(flag.DependencyTreeFlag.ConfigName, tt.fields.dependencyTree) + setValue(flag.ListAllPkgsFlag.ConfigName, tt.fields.listAllPkgs) + setValue(flag.IgnoreFileFlag.ConfigName, tt.fields.ignoreFile) + setValue(flag.IgnoreUnfixedFlag.ConfigName, tt.fields.ignoreUnfixed) + setValue(flag.IgnorePolicyFlag.ConfigName, tt.fields.ignorePolicy) + setValue(flag.ExitCodeFlag.ConfigName, tt.fields.exitCode) + setValue(flag.ExitOnEOLFlag.ConfigName, tt.fields.exitOnEOSL) + setValue(flag.OutputFlag.ConfigName, tt.fields.output) + setValue(flag.OutputPluginArgFlag.ConfigName, tt.fields.outputPluginArgs) + setValue(flag.SeverityFlag.ConfigName, tt.fields.severities) + setValue(flag.ComplianceFlag.ConfigName, tt.fields.compliance) + + // Assert options + f := &flag.ReportFlagGroup{ + Format: flag.FormatFlag.Clone(), + Template: flag.TemplateFlag.Clone(), + DependencyTree: flag.DependencyTreeFlag.Clone(), + ListAllPkgs: flag.ListAllPkgsFlag.Clone(), + IgnoreFile: flag.IgnoreFileFlag.Clone(), + IgnorePolicy: flag.IgnorePolicyFlag.Clone(), + ExitCode: flag.ExitCodeFlag.Clone(), + ExitOnEOL: flag.ExitOnEOLFlag.Clone(), + Output: flag.OutputFlag.Clone(), + OutputPluginArg: flag.OutputPluginArgFlag.Clone(), + Severity: flag.SeverityFlag.Clone(), + Compliance: flag.ComplianceFlag.Clone(), + } + + got, err := f.ToOptions() + assert.NoError(t, err) + assert.Equalf(t, tt.want, got, "ToOptions()") + + // Assert log messages + var gotMessages []string + for _, entry := range obs.AllUntimed() { + gotMessages = append(gotMessages, entry.Message) + } + assert.Equal(t, tt.wantLogs, gotMessages, tt.name) + }) + } +} diff --git a/pkg/flag/sbom_flags.go b/pkg/flag/sbom_flags.go new file mode 100644 index 000000000000..f5ab1aff3189 --- /dev/null +++ b/pkg/flag/sbom_flags.go @@ -0,0 +1,65 @@ +package flag + +import ( + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" +) + +var ( + ArtifactTypeFlag = Flag[string]{ + Name: "artifact-type", + ConfigName: "sbom.artifact-type", + Usage: "deprecated", + Deprecated: true, + } + SBOMFormatFlag = Flag[string]{ + Name: "sbom-format", + ConfigName: "sbom.format", + Usage: "deprecated", + Deprecated: true, + } +) + +type SBOMFlagGroup struct { + ArtifactType *Flag[string] // deprecated + SBOMFormat *Flag[string] // deprecated +} + +type SBOMOptions struct { +} + +func NewSBOMFlagGroup() *SBOMFlagGroup { + return &SBOMFlagGroup{ + ArtifactType: ArtifactTypeFlag.Clone(), + SBOMFormat: SBOMFormatFlag.Clone(), + } +} + +func (f *SBOMFlagGroup) Name() string { + return "SBOM" +} + +func (f *SBOMFlagGroup) Flags() []Flagger { + return []Flagger{ + f.ArtifactType, + f.SBOMFormat, + } +} + +func (f *SBOMFlagGroup) ToOptions() (SBOMOptions, error) { + if err := parseFlags(f); err != nil { + return SBOMOptions{}, err + } + + artifactType := f.ArtifactType.Value() + sbomFormat := f.SBOMFormat.Value() + + if artifactType != "" || sbomFormat != "" { + log.Logger.Error("'trivy sbom' is now for scanning SBOM. " + + "See https://github.com/aquasecurity/trivy/discussions/2407 for the detail") + return SBOMOptions{}, xerrors.New("'--artifact-type' and '--sbom-format' are no longer available") + } + + return SBOMOptions{}, nil +} diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go new file mode 100644 index 000000000000..aba3961b0243 --- /dev/null +++ b/pkg/flag/scan_flags.go @@ -0,0 +1,194 @@ +package flag + +import ( + "runtime" + + "github.com/samber/lo" + + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" +) + +var ( + SkipDirsFlag = Flag[[]string]{ + Name: "skip-dirs", + ConfigName: "scan.skip-dirs", + Usage: "specify the directories or glob patterns to skip", + } + SkipFilesFlag = Flag[[]string]{ + Name: "skip-files", + ConfigName: "scan.skip-files", + Default: []string{}, + Usage: "specify the files or glob patterns to skip", + } + OfflineScanFlag = Flag[bool]{ + Name: "offline-scan", + ConfigName: "scan.offline", + Usage: "do not issue API requests to identify dependencies", + } + ScannersFlag = Flag[[]string]{ + Name: "scanners", + ConfigName: "scan.scanners", + Default: xstrings.ToStringSlice(types.Scanners{ + types.VulnerabilityScanner, + types.SecretScanner, + }), + Values: xstrings.ToStringSlice(types.Scanners{ + types.VulnerabilityScanner, + types.MisconfigScanner, + types.SecretScanner, + types.LicenseScanner, + }), + ValueNormalize: func(ss []string) []string { + return lo.Map(ss, func(s string, _ int) string { + switch s { + case "vulnerability": + return string(types.VulnerabilityScanner) + case "misconf", "misconfiguration": + return string(types.MisconfigScanner) + case "config": + log.Logger.Warn("'--scanners config' is deprecated. Use '--scanners misconfig' instead. See https://github.com/aquasecurity/trivy/discussions/5586 for the detail.") + return string(types.MisconfigScanner) + } + return s + }) + }, + Aliases: []Alias{ + { + Name: "security-checks", + ConfigName: "scan.security-checks", + Deprecated: true, // --security-checks was renamed to --scanners + }, + }, + Usage: "comma-separated list of what security issues to detect", + } + FilePatternsFlag = Flag[[]string]{ + Name: "file-patterns", + ConfigName: "scan.file-patterns", + Usage: "specify config file patterns", + } + SlowFlag = Flag[bool]{ + Name: "slow", + ConfigName: "scan.slow", + Default: false, + Usage: "scan over time with lower CPU and memory utilization", + Deprecated: true, + } + ParallelFlag = Flag[int]{ + Name: "parallel", + ConfigName: "scan.parallel", + Default: 5, + Usage: "number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism", + } + SBOMSourcesFlag = Flag[[]string]{ + Name: "sbom-sources", + ConfigName: "scan.sbom-sources", + Values: []string{ + "oci", + "rekor", + }, + Usage: "[EXPERIMENTAL] try to retrieve SBOM from the specified sources", + } + RekorURLFlag = Flag[string]{ + Name: "rekor-url", + ConfigName: "scan.rekor-url", + Default: "https://rekor.sigstore.dev", + Usage: "[EXPERIMENTAL] address of rekor STL server", + } + IncludeDevDepsFlag = Flag[bool]{ + Name: "include-dev-deps", + ConfigName: "include-dev-deps", + Usage: "include development dependencies in the report (supported: npm, yarn)", + } +) + +type ScanFlagGroup struct { + SkipDirs *Flag[[]string] + SkipFiles *Flag[[]string] + OfflineScan *Flag[bool] + Scanners *Flag[[]string] + FilePatterns *Flag[[]string] + Slow *Flag[bool] // deprecated + Parallel *Flag[int] + SBOMSources *Flag[[]string] + RekorURL *Flag[string] + IncludeDevDeps *Flag[bool] +} + +type ScanOptions struct { + Target string + SkipDirs []string + SkipFiles []string + OfflineScan bool + Scanners types.Scanners + FilePatterns []string + Parallel int + SBOMSources []string + RekorURL string + IncludeDevDeps bool +} + +func NewScanFlagGroup() *ScanFlagGroup { + return &ScanFlagGroup{ + SkipDirs: SkipDirsFlag.Clone(), + SkipFiles: SkipFilesFlag.Clone(), + OfflineScan: OfflineScanFlag.Clone(), + Scanners: ScannersFlag.Clone(), + FilePatterns: FilePatternsFlag.Clone(), + Parallel: ParallelFlag.Clone(), + SBOMSources: SBOMSourcesFlag.Clone(), + RekorURL: RekorURLFlag.Clone(), + IncludeDevDeps: IncludeDevDepsFlag.Clone(), + Slow: SlowFlag.Clone(), + } +} + +func (f *ScanFlagGroup) Name() string { + return "Scan" +} + +func (f *ScanFlagGroup) Flags() []Flagger { + return []Flagger{ + f.SkipDirs, + f.SkipFiles, + f.OfflineScan, + f.Scanners, + f.FilePatterns, + f.Slow, + f.Parallel, + f.SBOMSources, + f.RekorURL, + f.IncludeDevDeps, + } +} + +func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { + if err := parseFlags(f); err != nil { + return ScanOptions{}, err + } + + var target string + if len(args) == 1 { + target = args[0] + } + + parallel := f.Parallel.Value() + if f.Parallel != nil && parallel == 0 { + log.Logger.Infof("Set '--parallel' to the number of CPUs (%d)", runtime.NumCPU()) + parallel = runtime.NumCPU() + } + + return ScanOptions{ + Target: target, + SkipDirs: f.SkipDirs.Value(), + SkipFiles: f.SkipFiles.Value(), + OfflineScan: f.OfflineScan.Value(), + Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()), + FilePatterns: f.FilePatterns.Value(), + Parallel: parallel, + SBOMSources: f.SBOMSources.Value(), + RekorURL: f.RekorURL.Value(), + IncludeDevDeps: f.IncludeDevDeps.Value(), + }, nil +} diff --git a/pkg/flag/scan_flags_test.go b/pkg/flag/scan_flags_test.go new file mode 100644 index 000000000000..2d5cb718b0d1 --- /dev/null +++ b/pkg/flag/scan_flags_test.go @@ -0,0 +1,131 @@ +package flag_test + +import ( + "github.com/spf13/viper" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestScanFlagGroup_ToOptions(t *testing.T) { + type fields struct { + skipDirs []string + skipFiles []string + offlineScan bool + scanners string + } + tests := []struct { + name string + args []string + fields fields + want flag.ScanOptions + assertion require.ErrorAssertionFunc + }{ + { + name: "happy path", + args: []string{"alpine:latest"}, + fields: fields{}, + want: flag.ScanOptions{ + Target: "alpine:latest", + }, + assertion: require.NoError, + }, + { + name: "happy path for configs", + args: []string{"alpine:latest"}, + fields: fields{ + scanners: "misconfig", + }, + want: flag.ScanOptions{ + Target: "alpine:latest", + Scanners: types.Scanners{types.MisconfigScanner}, + }, + assertion: require.NoError, + }, + { + name: "without target (args)", + args: []string{}, + fields: fields{}, + want: flag.ScanOptions{}, + assertion: require.NoError, + }, + { + name: "with two or more targets (args)", + args: []string{ + "alpine:latest", + "nginx:latest", + }, + fields: fields{}, + want: flag.ScanOptions{}, + assertion: require.NoError, + }, + { + name: "skip two files", + fields: fields{ + skipFiles: []string{ + "file1", + "file2", + }, + }, + want: flag.ScanOptions{ + SkipFiles: []string{ + "file1", + "file2", + }, + }, + assertion: require.NoError, + }, + { + name: "skip two folders", + fields: fields{ + skipDirs: []string{ + "dir1", + "dir2", + }, + }, + want: flag.ScanOptions{ + SkipDirs: []string{ + "dir1", + "dir2", + }, + }, + assertion: require.NoError, + }, + { + name: "offline scan", + fields: fields{ + offlineScan: true, + }, + want: flag.ScanOptions{ + OfflineScan: true, + }, + assertion: require.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Cleanup(viper.Reset) + setSliceValue(flag.SkipDirsFlag.ConfigName, tt.fields.skipDirs) + setSliceValue(flag.SkipFilesFlag.ConfigName, tt.fields.skipFiles) + setValue(flag.OfflineScanFlag.ConfigName, tt.fields.offlineScan) + setValue(flag.ScannersFlag.ConfigName, tt.fields.scanners) + + // Assert options + f := &flag.ScanFlagGroup{ + SkipDirs: flag.SkipDirsFlag.Clone(), + SkipFiles: flag.SkipFilesFlag.Clone(), + OfflineScan: flag.OfflineScanFlag.Clone(), + Scanners: flag.ScannersFlag.Clone(), + } + + got, err := f.ToOptions(tt.args) + tt.assertion(t, err) + assert.Equalf(t, tt.want, got, "ToOptions()") + }) + } +} diff --git a/pkg/flag/secret_flags.go b/pkg/flag/secret_flags.go new file mode 100644 index 000000000000..295753c48cc5 --- /dev/null +++ b/pkg/flag/secret_flags.go @@ -0,0 +1,42 @@ +package flag + +var ( + SecretConfigFlag = Flag[string]{ + Name: "secret-config", + ConfigName: "secret.config", + Default: "trivy-secret.yaml", + Usage: "specify a path to config file for secret scanning", + } +) + +type SecretFlagGroup struct { + SecretConfig *Flag[string] +} + +type SecretOptions struct { + SecretConfigPath string +} + +func NewSecretFlagGroup() *SecretFlagGroup { + return &SecretFlagGroup{ + SecretConfig: SecretConfigFlag.Clone(), + } +} + +func (f *SecretFlagGroup) Name() string { + return "Secret" +} + +func (f *SecretFlagGroup) Flags() []Flagger { + return []Flagger{f.SecretConfig} +} + +func (f *SecretFlagGroup) ToOptions() (SecretOptions, error) { + if err := parseFlags(f); err != nil { + return SecretOptions{}, err + } + + return SecretOptions{ + SecretConfigPath: f.SecretConfig.Value(), + }, nil +} diff --git a/pkg/flag/testdata/example-spec.yaml b/pkg/flag/testdata/example-spec.yaml new file mode 100644 index 000000000000..19fbf0a3bf31 --- /dev/null +++ b/pkg/flag/testdata/example-spec.yaml @@ -0,0 +1,13 @@ +spec: + id: "0001" + title: my-custom-spec + description: My fancy spec + version: "1.2" + controls: + - id: "1.1" + name: Unencrypted S3 bucket + description: |- + S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised. + checks: + - id: AVD-AWS-0088 + severity: HIGH \ No newline at end of file diff --git a/pkg/flag/vulnerability_flags.go b/pkg/flag/vulnerability_flags.go new file mode 100644 index 000000000000..3989fbfa1c51 --- /dev/null +++ b/pkg/flag/vulnerability_flags.go @@ -0,0 +1,112 @@ +package flag + +import ( + "github.com/samber/lo" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + VulnTypeFlag = Flag[[]string]{ + Name: "vuln-type", + ConfigName: "vulnerability.type", + Default: []string{ + types.VulnTypeOS, + types.VulnTypeLibrary, + }, + Values: []string{ + types.VulnTypeOS, + types.VulnTypeLibrary, + }, + Usage: "comma-separated list of vulnerability types", + } + IgnoreUnfixedFlag = Flag[bool]{ + Name: "ignore-unfixed", + ConfigName: "vulnerability.ignore-unfixed", + Usage: "display only fixed vulnerabilities", + } + IgnoreStatusFlag = Flag[[]string]{ + Name: "ignore-status", + ConfigName: "vulnerability.ignore-status", + Values: dbTypes.Statuses, + Usage: "comma-separated list of vulnerability status to ignore", + } + VEXFlag = Flag[string]{ + Name: "vex", + ConfigName: "vulnerability.vex", + Default: "", + Usage: "[EXPERIMENTAL] file path to VEX", + } +) + +type VulnerabilityFlagGroup struct { + VulnType *Flag[[]string] + IgnoreUnfixed *Flag[bool] + IgnoreStatus *Flag[[]string] + VEXPath *Flag[string] +} + +type VulnerabilityOptions struct { + VulnType []string + IgnoreStatuses []dbTypes.Status + VEXPath string +} + +func NewVulnerabilityFlagGroup() *VulnerabilityFlagGroup { + return &VulnerabilityFlagGroup{ + VulnType: VulnTypeFlag.Clone(), + IgnoreUnfixed: IgnoreUnfixedFlag.Clone(), + IgnoreStatus: IgnoreStatusFlag.Clone(), + VEXPath: VEXFlag.Clone(), + } +} + +func (f *VulnerabilityFlagGroup) Name() string { + return "Vulnerability" +} + +func (f *VulnerabilityFlagGroup) Flags() []Flagger { + return []Flagger{ + f.VulnType, + f.IgnoreUnfixed, + f.IgnoreStatus, + f.VEXPath, + } +} + +func (f *VulnerabilityFlagGroup) ToOptions() (VulnerabilityOptions, error) { + if err := parseFlags(f); err != nil { + return VulnerabilityOptions{}, err + } + + // Just convert string to dbTypes.Status as the validated values are passed here. + ignoreStatuses := lo.Map(f.IgnoreStatus.Value(), func(s string, _ int) dbTypes.Status { + return dbTypes.NewStatus(s) + }) + ignoreUnfixed := f.IgnoreUnfixed.Value() + + switch { + case ignoreUnfixed && len(ignoreStatuses) > 0: + log.Logger.Warn("'--ignore-unfixed' is ignored because '--ignore-status' is specified") + case ignoreUnfixed: + // '--ignore-unfixed' is a shorthand of '--ignore-status'. + ignoreStatuses = lo.FilterMap(dbTypes.Statuses, func(s string, _ int) (dbTypes.Status, bool) { + fixed := dbTypes.StatusFixed + if s == fixed.String() { + return 0, false + } + return dbTypes.NewStatus(s), true + }) + case len(ignoreStatuses) == 0: + ignoreStatuses = nil + } + log.Logger.Debugw("Ignore statuses", "statuses", ignoreStatuses) + + return VulnerabilityOptions{ + VulnType: f.VulnType.Value(), + IgnoreStatuses: ignoreStatuses, + VEXPath: f.VEXPath.Value(), + }, nil +} diff --git a/pkg/flag/vulnerability_flags_test.go b/pkg/flag/vulnerability_flags_test.go new file mode 100644 index 000000000000..02ee3c8d9605 --- /dev/null +++ b/pkg/flag/vulnerability_flags_test.go @@ -0,0 +1,77 @@ +package flag_test + +import ( + "github.com/stretchr/testify/require" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" + + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestVulnerabilityFlagGroup_ToOptions(t *testing.T) { + type fields struct { + vulnType string + } + tests := []struct { + name string + args []string + fields fields + want flag.VulnerabilityOptions + wantLogs []string + }{ + { + name: "happy path for OS vulnerabilities", + args: []string{"alpine:latest"}, + fields: fields{ + vulnType: "os", + }, + want: flag.VulnerabilityOptions{ + VulnType: []string{types.VulnTypeOS}, + }, + }, + { + name: "happy path for library vulnerabilities", + args: []string{"alpine:latest"}, + fields: fields{ + vulnType: "library", + }, + want: flag.VulnerabilityOptions{ + VulnType: []string{types.VulnTypeLibrary}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + level := zap.WarnLevel + + core, obs := observer.New(level) + log.Logger = zap.New(core).Sugar() + + viper.Set(flag.VulnTypeFlag.ConfigName, tt.fields.vulnType) + + // Assert options + f := &flag.VulnerabilityFlagGroup{ + VulnType: flag.VulnTypeFlag.Clone(), + } + + got, err := f.ToOptions() + require.NoError(t, err) + assert.Equalf(t, tt.want, got, "ToOptions()") + + // Assert log messages + var gotMessages []string + for _, entry := range obs.AllUntimed() { + gotMessages = append(gotMessages, entry.Message) + } + assert.Equal(t, tt.wantLogs, gotMessages, tt.name) + }) + + } +} diff --git a/pkg/git/git.go b/pkg/git/git.go deleted file mode 100644 index d2ade7a950cc..000000000000 --- a/pkg/git/git.go +++ /dev/null @@ -1,194 +0,0 @@ -package git - -import ( - "os" - "path/filepath" - "strings" - - "github.com/knqyf263/trivy/pkg/db" - - "github.com/knqyf263/trivy/pkg/log" - "github.com/knqyf263/trivy/pkg/utils" - "golang.org/x/xerrors" - git "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing/object" - "gopkg.in/src-d/go-git.v4/plumbing/storer" -) - -func CloneOrPull(url, repoPath string) (map[string]struct{}, error) { - exists, err := utils.Exists(filepath.Join(repoPath, ".git")) - if err != nil { - return nil, xerrors.Errorf("failed to check if a file exists: %w", err) - } - - updatedFiles := map[string]struct{}{} - if exists { - log.Logger.Debug("git pull") - files, err := pull(repoPath) - if err != nil { - return nil, xerrors.Errorf("failed to pull repository: %w", err) - } - - for _, filename := range files { - updatedFiles[strings.TrimSpace(filename)] = struct{}{} - } - } else { - if !utils.IsCommandAvailable("git") { - log.Logger.Warn("Recommend installing git (if not, DB update is very slow)") - } - log.Logger.Debug("remove an existed directory") - - suffix := " The first time will take a while..." - s := utils.NewSpinner(suffix) - s.Start() - defer s.Stop() - - if err = os.RemoveAll(repoPath); err != nil { - return nil, xerrors.Errorf("failed to remove an existed directory: %w", err) - } - - if err = os.MkdirAll(repoPath, 0700); err != nil { - return nil, xerrors.Errorf("failed to mkdir: %w", err) - } - if err := clone(url, repoPath); err != nil { - return nil, xerrors.Errorf("failed to clone repository: %w", err) - } - - } - - // Need to refresh all vulnerabilities - if db.GetVersion() == "" { - err = filepath.Walk(repoPath, func(path string, info os.FileInfo, err error) error { - if info.IsDir() { - return nil - } - rel, err := filepath.Rel(repoPath, path) - if err != nil { - return xerrors.Errorf("failed to get a relative path: %w", err) - } - updatedFiles[rel] = struct{}{} - return nil - }) - if err != nil { - return nil, xerrors.Errorf("error in file walk: %w", err) - } - } - - return updatedFiles, nil -} - -func clone(url, repoPath string) error { - if utils.IsCommandAvailable("git") { - return cloneByOSCommand(url, repoPath) - } - - _, err := git.PlainClone(repoPath, false, &git.CloneOptions{ - URL: url, - }) - if err != nil && err != git.ErrRepositoryAlreadyExists { - return xerrors.Errorf("unexpected error in git clone: %w", err) - } - return nil -} - -func cloneByOSCommand(url, repoPath string) error { - commandAndArgs := []string{"clone", url, repoPath} - _, err := utils.Exec("git", commandAndArgs) - if err != nil { - return xerrors.Errorf("error in git clone: %w", err) - } - return nil -} - -func pull(repoPath string) ([]string, error) { - if utils.IsCommandAvailable("git") { - return pullByOSCommand(repoPath) - } - - r, err := git.PlainOpen(repoPath) - if err != nil { - return nil, xerrors.Errorf("failed to open repository: %w", err) - } - - log.Logger.Debug("Retrieve the branch being pointed by HEAD") - ref, err := r.Head() - if err != nil { - return nil, xerrors.Errorf("failed to get HEAD: %w", err) - } - - log.Logger.Debug("Get the working directory for the repository") - w, err := r.Worktree() - if err != nil { - return nil, xerrors.Errorf("failed to get the working directory: %w", err) - } - - log.Logger.Debug("Pull the latest changes from the origin remote and merge into the current branch") - err = w.Pull(&git.PullOptions{RemoteName: "origin"}) - if err != nil && err != git.NoErrAlreadyUpToDate { - return nil, err - } else if err == git.NoErrAlreadyUpToDate { - return []string{}, nil - } - - log.Logger.Debug("Retrieve the commit history") - commits, err := r.Log(&git.LogOptions{}) - if err != nil { - return nil, xerrors.Errorf("error in git log: %w", err) - } - - log.Logger.Debug("Detect the updated files") - var prevCommit *object.Commit - var updatedFiles []string - err = commits.ForEach(func(commit *object.Commit) error { - if prevCommit == nil { - prevCommit = commit - return nil - } - - patch, err := commit.Patch(prevCommit) - if err != nil { - return xerrors.Errorf("error in patch: %w", err) - } - for _, stat := range patch.Stats() { - updatedFiles = append(updatedFiles, stat.Name) - } - - if commit.Hash == ref.Hash() { - return storer.ErrStop - } - - prevCommit = commit - return nil - }) - if err != nil { - return nil, xerrors.Errorf("error in commit foreach: %w", err) - } - - return updatedFiles, nil -} - -func pullByOSCommand(repoPath string) ([]string, error) { - gitDir := filepath.Join(repoPath, ".git") - commandArgs := []string{"--git-dir", gitDir, "--work-tree", repoPath} - - revParseCmd := []string{"rev-parse", "HEAD"} - output, err := utils.Exec("git", append(commandArgs, revParseCmd...)) - if err != nil { - return nil, xerrors.Errorf("error in git rev-parse: %w", err) - } - commitHash := strings.TrimSpace(output) - - pullCmd := []string{"pull", "origin", "master"} - _, err = utils.Exec("git", append(commandArgs, pullCmd...)) - if err != nil { - return nil, xerrors.Errorf("error in git pull: %w", err) - } - - diffCmd := []string{"diff", commitHash, "HEAD", "--name-only"} - output, err = utils.Exec("git", append(commandArgs, diffCmd...)) - if err != nil { - return nil, xerrors.Errorf("error in git diff: %w", err) - } - updatedFiles := strings.Split(strings.TrimSpace(output), "\n") - return updatedFiles, nil -} diff --git a/pkg/iac/adapters/arm/adapt.go b/pkg/iac/adapters/arm/adapt.go new file mode 100644 index 000000000000..a6bcbec2a42e --- /dev/null +++ b/pkg/iac/adapters/arm/adapt.go @@ -0,0 +1,49 @@ +package arm + +import ( + "context" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/appservice" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/authorization" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/compute" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/container" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/database" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/datafactory" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/datalake" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/keyvault" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/monitor" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/network" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/securitycenter" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/storage" + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm/synapse" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure" + scanner "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/state" +) + +// Adapt adapts an azure arm instance +func Adapt(ctx context.Context, deployment scanner.Deployment) *state.State { + return &state.State{ + Azure: adaptAzure(deployment), + } +} + +func adaptAzure(deployment scanner.Deployment) azure.Azure { + + return azure.Azure{ + AppService: appservice.Adapt(deployment), + Authorization: authorization.Adapt(deployment), + Compute: compute.Adapt(deployment), + Container: container.Adapt(deployment), + Database: database.Adapt(deployment), + DataFactory: datafactory.Adapt(deployment), + DataLake: datalake.Adapt(deployment), + KeyVault: keyvault.Adapt(deployment), + Monitor: monitor.Adapt(deployment), + Network: network.Adapt(deployment), + SecurityCenter: securitycenter.Adapt(deployment), + Storage: storage.Adapt(deployment), + Synapse: synapse.Adapt(deployment), + } + +} diff --git a/pkg/iac/adapters/arm/appservice/adapt.go b/pkg/iac/adapters/arm/appservice/adapt.go new file mode 100644 index 000000000000..d5512cc5d3b4 --- /dev/null +++ b/pkg/iac/adapters/arm/appservice/adapt.go @@ -0,0 +1,58 @@ +package appservice + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/appservice" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(deployment azure.Deployment) appservice.AppService { + return appservice.AppService{ + Services: adaptServices(deployment), + FunctionApps: adaptFunctionApps(deployment), + } +} + +func adaptFunctionApps(deployment azure.Deployment) []appservice.FunctionApp { + var functionApps []appservice.FunctionApp + + for _, resource := range deployment.GetResourcesByType("Microsoft.Web/sites") { + functionApps = append(functionApps, adaptFunctionApp(resource)) + } + return functionApps +} + +func adaptServices(deployment azure.Deployment) []appservice.Service { + var services []appservice.Service + for _, resource := range deployment.GetResourcesByType("Microsoft.Web/sites") { + services = append(services, adaptService(resource)) + } + return services +} + +func adaptFunctionApp(resource azure.Resource) appservice.FunctionApp { + return appservice.FunctionApp{ + Metadata: resource.Metadata, + HTTPSOnly: resource.Properties.GetMapValue("httpsOnly").AsBoolValue(false, resource.Properties.GetMetadata()), + } +} + +func adaptService(resource azure.Resource) appservice.Service { + return appservice.Service{ + Metadata: resource.Metadata, + EnableClientCert: resource.Properties.GetMapValue("clientCertEnabled").AsBoolValue(false, resource.Properties.GetMetadata()), + Identity: struct{ Type iacTypes.StringValue }{ + Type: resource.Properties.GetMapValue("identity").GetMapValue("type").AsStringValue("", resource.Properties.GetMetadata()), + }, + Authentication: struct{ Enabled iacTypes.BoolValue }{ + Enabled: resource.Properties.GetMapValue("siteAuthSettings").GetMapValue("enabled").AsBoolValue(false, resource.Properties.GetMetadata()), + }, + Site: struct { + EnableHTTP2 iacTypes.BoolValue + MinimumTLSVersion iacTypes.StringValue + }{ + EnableHTTP2: resource.Properties.GetMapValue("httpsOnly").AsBoolValue(false, resource.Properties.GetMetadata()), + MinimumTLSVersion: resource.Properties.GetMapValue("minTlsVersion").AsStringValue("", resource.Properties.GetMetadata()), + }, + } +} diff --git a/pkg/iac/adapters/arm/authorization/adapt.go b/pkg/iac/adapters/arm/authorization/adapt.go new file mode 100644 index 000000000000..08798665eaab --- /dev/null +++ b/pkg/iac/adapters/arm/authorization/adapt.go @@ -0,0 +1,38 @@ +package authorization + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/authorization" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" +) + +func Adapt(deployment azure.Deployment) authorization.Authorization { + return authorization.Authorization{ + RoleDefinitions: adaptRoleDefinitions(deployment), + } +} + +func adaptRoleDefinitions(deployment azure.Deployment) (roleDefinitions []authorization.RoleDefinition) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Authorization/roleDefinitions") { + roleDefinitions = append(roleDefinitions, adaptRoleDefinition(resource)) + } + return roleDefinitions +} + +func adaptRoleDefinition(resource azure.Resource) authorization.RoleDefinition { + + return authorization.RoleDefinition{ + Metadata: resource.Metadata, + Permissions: adaptPermissions(resource), + AssignableScopes: resource.Properties.GetMapValue("assignableScopes").AsStringValuesList(""), + } +} + +func adaptPermissions(resource azure.Resource) (permissions []authorization.Permission) { + for _, permission := range resource.Properties.GetMapValue("permissions").AsList() { + permissions = append(permissions, authorization.Permission{ + Metadata: resource.Metadata, + Actions: permission.GetMapValue("actions").AsStringValuesList(""), + }) + } + return permissions +} diff --git a/pkg/iac/adapters/arm/compute/adapt.go b/pkg/iac/adapters/arm/compute/adapt.go new file mode 100644 index 000000000000..bdcbfac500fd --- /dev/null +++ b/pkg/iac/adapters/arm/compute/adapt.go @@ -0,0 +1,85 @@ +package compute + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/compute" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(deployment azure.Deployment) compute.Compute { + return compute.Compute{ + LinuxVirtualMachines: adaptLinuxVirtualMachines(deployment), + WindowsVirtualMachines: adaptWindowsVirtualMachines(deployment), + ManagedDisks: adaptManagedDisks(deployment), + } +} + +func adaptManagedDisks(deployment azure.Deployment) (managedDisks []compute.ManagedDisk) { + + for _, resource := range deployment.GetResourcesByType("Microsoft.Compute/disks") { + managedDisks = append(managedDisks, adaptManagedDisk(resource)) + } + + return managedDisks +} + +func adaptManagedDisk(resource azure.Resource) compute.ManagedDisk { + hasEncryption := resource.Properties.HasKey("encryption") + + return compute.ManagedDisk{ + Metadata: resource.Metadata, + Encryption: compute.Encryption{ + Metadata: resource.Metadata, + Enabled: iacTypes.Bool(hasEncryption, resource.Metadata), + }, + } +} + +func adaptWindowsVirtualMachines(deployment azure.Deployment) (windowsVirtualMachines []compute.WindowsVirtualMachine) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Compute/virtualMachines") { + if resource.Properties.GetMapValue("osProfile").GetMapValue("windowsConfiguration").AsMap() != nil { + windowsVirtualMachines = append(windowsVirtualMachines, adaptWindowsVirtualMachine(resource)) + } + } + + return windowsVirtualMachines +} + +func adaptWindowsVirtualMachine(resource azure.Resource) compute.WindowsVirtualMachine { + return compute.WindowsVirtualMachine{ + Metadata: resource.Metadata, + VirtualMachine: compute.VirtualMachine{ + Metadata: resource.Metadata, + CustomData: resource.Properties.GetMapValue("osProfile"). + GetMapValue("customData").AsStringValue("", resource.Metadata), + }, + } +} + +func adaptLinuxVirtualMachines(deployment azure.Deployment) (linuxVirtualMachines []compute.LinuxVirtualMachine) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Compute/virtualMachines") { + if resource.Properties.GetMapValue("osProfile").GetMapValue("linuxConfiguration").AsMap() != nil { + linuxVirtualMachines = append(linuxVirtualMachines, adaptLinuxVirtualMachine(resource)) + } + } + + return linuxVirtualMachines +} + +func adaptLinuxVirtualMachine(resource azure.Resource) compute.LinuxVirtualMachine { + return compute.LinuxVirtualMachine{ + Metadata: resource.Metadata, + VirtualMachine: compute.VirtualMachine{ + Metadata: resource.Metadata, + CustomData: resource.Properties.GetMapValue("osProfile"). + GetMapValue("customData").AsStringValue("", resource.Metadata), + }, + OSProfileLinuxConfig: compute.OSProfileLinuxConfig{ + Metadata: resource.Metadata, + DisablePasswordAuthentication: resource.Properties.GetMapValue("osProfile"). + GetMapValue("linuxConfiguration"). + GetMapValue("disablePasswordAuthentication").AsBoolValue(false, resource.Metadata), + }, + } + +} diff --git a/pkg/iac/adapters/arm/compute/adapt_test.go b/pkg/iac/adapters/arm/compute/adapt_test.go new file mode 100644 index 000000000000..2021e08b95a8 --- /dev/null +++ b/pkg/iac/adapters/arm/compute/adapt_test.go @@ -0,0 +1,59 @@ +package compute + +import ( + "testing" + + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" + + "github.com/stretchr/testify/require" +) + +func Test_AdaptLinuxVM(t *testing.T) { + + input := azure2.Deployment{ + Resources: []azure2.Resource{ + { + Type: azure2.NewValue("Microsoft.Compute/virtualMachines", types.NewTestMetadata()), + Properties: azure2.NewValue(map[string]azure2.Value{ + "osProfile": azure2.NewValue(map[string]azure2.Value{ + "linuxConfiguration": azure2.NewValue(map[string]azure2.Value{ + "disablePasswordAuthentication": azure2.NewValue(true, types.NewTestMetadata()), + }, types.NewTestMetadata()), + }, types.NewTestMetadata()), + }, types.NewTestMetadata()), + }, + }, + } + + output := Adapt(input) + + require.Len(t, output.LinuxVirtualMachines, 1) + require.Len(t, output.WindowsVirtualMachines, 0) + + linuxVM := output.LinuxVirtualMachines[0] + assert.True(t, linuxVM.OSProfileLinuxConfig.DisablePasswordAuthentication.IsTrue()) + +} + +func Test_AdaptWindowsVM(t *testing.T) { + + input := azure2.Deployment{ + Resources: []azure2.Resource{ + { + Type: azure2.NewValue("Microsoft.Compute/virtualMachines", types.NewTestMetadata()), + Properties: azure2.NewValue(map[string]azure2.Value{ + "osProfile": azure2.NewValue(map[string]azure2.Value{ + "windowsConfiguration": azure2.NewValue(map[string]azure2.Value{}, types.NewTestMetadata()), + }, types.NewTestMetadata()), + }, types.NewTestMetadata()), + }, + }, + } + + output := Adapt(input) + + require.Len(t, output.LinuxVirtualMachines, 0) + require.Len(t, output.WindowsVirtualMachines, 1) +} diff --git a/pkg/iac/adapters/arm/container/adapt.go b/pkg/iac/adapters/arm/container/adapt.go new file mode 100644 index 000000000000..2172553e4cfb --- /dev/null +++ b/pkg/iac/adapters/arm/container/adapt.go @@ -0,0 +1,17 @@ +package container + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/container" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" +) + +func Adapt(deployment azure.Deployment) container.Container { + return container.Container{ + KubernetesClusters: adaptKubernetesClusters(deployment), + } +} + +func adaptKubernetesClusters(deployment azure.Deployment) []container.KubernetesCluster { + + return nil +} diff --git a/pkg/iac/adapters/arm/database/adapt.go b/pkg/iac/adapters/arm/database/adapt.go new file mode 100644 index 000000000000..834865fc22d0 --- /dev/null +++ b/pkg/iac/adapters/arm/database/adapt.go @@ -0,0 +1,35 @@ +package database + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/database" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" +) + +func Adapt(deployment azure.Deployment) database.Database { + return database.Database{ + MSSQLServers: adaptMSSQLServers(deployment), + MariaDBServers: adaptMariaDBServers(deployment), + MySQLServers: adaptMySQLServers(deployment), + PostgreSQLServers: adaptPostgreSQLServers(deployment), + } +} + +func adaptMySQLServers(deployment azure.Deployment) (mysqlDbServers []database.MySQLServer) { + for _, resource := range deployment.GetResourcesByType("Microsoft.DBforMySQL/servers") { + mysqlDbServers = append(mysqlDbServers, adaptMySQLServer(resource, deployment)) + } + return mysqlDbServers +} + +func adaptMySQLServer(resource azure.Resource, deployment azure.Deployment) database.MySQLServer { + return database.MySQLServer{ + Metadata: resource.Metadata, + Server: database.Server{ + Metadata: resource.Metadata, + EnableSSLEnforcement: resource.Properties.GetMapValue("sslEnforcement").AsBoolValue(false, resource.Metadata), + MinimumTLSVersion: resource.Properties.GetMapValue("minimalTlsVersion").AsStringValue("TLSEnforcementDisabled", resource.Metadata), + EnablePublicNetworkAccess: resource.Properties.GetMapValue("publicNetworkAccess").AsBoolValue(false, resource.Metadata), + FirewallRules: addFirewallRule(resource), + }, + } +} diff --git a/pkg/iac/adapters/arm/database/firewall.go b/pkg/iac/adapters/arm/database/firewall.go new file mode 100644 index 000000000000..ff6ff5a56cd3 --- /dev/null +++ b/pkg/iac/adapters/arm/database/firewall.go @@ -0,0 +1,18 @@ +package database + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/database" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" +) + +func addFirewallRule(resource azure.Resource) []database.FirewallRule { + var rules []database.FirewallRule + for _, rule := range resource.Properties.GetMapValue("firewallRules").AsMap() { + rules = append(rules, database.FirewallRule{ + Metadata: rule.Metadata, + StartIP: rule.GetMapValue("startIpAddress").AsStringValue("", rule.Metadata), + EndIP: rule.GetMapValue("endIpAddress").AsStringValue("", rule.Metadata), + }) + } + return rules +} diff --git a/pkg/iac/adapters/arm/database/maria.go b/pkg/iac/adapters/arm/database/maria.go new file mode 100644 index 000000000000..083c3b0540e5 --- /dev/null +++ b/pkg/iac/adapters/arm/database/maria.go @@ -0,0 +1,27 @@ +package database + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/database" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" +) + +func adaptMariaDBServers(deployment azure.Deployment) (mariaDbServers []database.MariaDBServer) { + for _, resource := range deployment.GetResourcesByType("Microsoft.DBforMariaDB/servers") { + mariaDbServers = append(mariaDbServers, adaptMariaDBServer(resource, deployment)) + } + return mariaDbServers + +} + +func adaptMariaDBServer(resource azure.Resource, deployment azure.Deployment) database.MariaDBServer { + return database.MariaDBServer{ + Metadata: resource.Metadata, + Server: database.Server{ + Metadata: resource.Metadata, + EnableSSLEnforcement: resource.Properties.GetMapValue("sslEnforcement").AsBoolValue(false, resource.Metadata), + MinimumTLSVersion: resource.Properties.GetMapValue("minimalTlsVersion").AsStringValue("TLSEnforcementDisabled", resource.Metadata), + EnablePublicNetworkAccess: resource.Properties.GetMapValue("publicNetworkAccess").AsBoolValue(false, resource.Metadata), + FirewallRules: addFirewallRule(resource), + }, + } +} diff --git a/pkg/iac/adapters/arm/database/mssql.go b/pkg/iac/adapters/arm/database/mssql.go new file mode 100644 index 000000000000..95cc41ed6b25 --- /dev/null +++ b/pkg/iac/adapters/arm/database/mssql.go @@ -0,0 +1,61 @@ +package database + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/database" + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func adaptMSSQLServers(deployment azure2.Deployment) (msSQlServers []database.MSSQLServer) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Sql/servers") { + msSQlServers = append(msSQlServers, adaptMSSQLServer(resource, deployment)) + } + return msSQlServers +} + +func adaptMSSQLServer(resource azure2.Resource, deployment azure2.Deployment) database.MSSQLServer { + return database.MSSQLServer{ + Metadata: resource.Metadata, + Server: database.Server{ + Metadata: resource.Metadata, + EnableSSLEnforcement: resource.Properties.GetMapValue("sslEnforcement").AsBoolValue(false, resource.Metadata), + MinimumTLSVersion: resource.Properties.GetMapValue("minimalTlsVersion").AsStringValue("TLSEnforcementDisabled", resource.Metadata), + EnablePublicNetworkAccess: resource.Properties.GetMapValue("publicNetworkAccess").AsBoolValue(false, resource.Metadata), + FirewallRules: addFirewallRule(resource), + }, + ExtendedAuditingPolicies: adaptExtendedAuditingPolicies(resource, deployment), + SecurityAlertPolicies: adaptSecurityAlertPolicies(resource, deployment), + } +} + +func adaptExtendedAuditingPolicies(resource azure2.Resource, deployment azure2.Deployment) (policies []database.ExtendedAuditingPolicy) { + + for _, policy := range deployment.GetResourcesByType("Microsoft.Sql/servers/extendedAuditingSettings") { + policies = append(policies, database.ExtendedAuditingPolicy{ + Metadata: policy.Metadata, + RetentionInDays: policy.Properties.GetMapValue("retentionDays").AsIntValue(0, policy.Metadata), + }) + } + + return policies +} + +func adaptSecurityAlertPolicies(resource azure2.Resource, deployment azure2.Deployment) (policies []database.SecurityAlertPolicy) { + for _, policy := range deployment.GetResourcesByType("Microsoft.Sql/servers/securityAlertPolicies") { + policies = append(policies, database.SecurityAlertPolicy{ + Metadata: policy.Metadata, + EmailAddresses: adaptStringList(policy.Properties.GetMapValue("emailAddresses")), + DisabledAlerts: adaptStringList(policy.Properties.GetMapValue("disabledAlerts")), + EmailAccountAdmins: policy.Properties.GetMapValue("emailAccountAdmins").AsBoolValue(false, policy.Metadata), + }) + } + return policies +} + +func adaptStringList(value azure2.Value) []iacTypes.StringValue { + var list []iacTypes.StringValue + for _, v := range value.AsList() { + list = append(list, v.AsStringValue("", value.Metadata)) + } + return list +} diff --git a/pkg/iac/adapters/arm/database/postgresql.go b/pkg/iac/adapters/arm/database/postgresql.go new file mode 100644 index 000000000000..155667c65f39 --- /dev/null +++ b/pkg/iac/adapters/arm/database/postgresql.go @@ -0,0 +1,64 @@ +package database + +import ( + "fmt" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/database" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func adaptPostgreSQLServers(deployment azure.Deployment) (databases []database.PostgreSQLServer) { + for _, resource := range deployment.GetResourcesByType("Microsoft.DBforPostgreSQL/servers") { + databases = append(databases, adaptPostgreSQLServer(resource, deployment)) + } + + return databases +} + +func adaptPostgreSQLServer(resource azure.Resource, deployment azure.Deployment) database.PostgreSQLServer { + return database.PostgreSQLServer{ + Metadata: resource.Metadata, + Server: database.Server{ + Metadata: resource.Metadata, + EnableSSLEnforcement: resource.Properties.GetMapValue("sslEnforcement").AsBoolValue(false, resource.Metadata), + MinimumTLSVersion: resource.Properties.GetMapValue("minimalTlsVersion").AsStringValue("TLSEnforcementDisabled", resource.Metadata), + EnablePublicNetworkAccess: resource.Properties.GetMapValue("publicNetworkAccess").AsBoolValue(false, resource.Metadata), + FirewallRules: addFirewallRule(resource), + }, + Config: adaptPostgreSQLConfiguration(resource, deployment), + } +} + +func adaptPostgreSQLConfiguration(resource azure.Resource, deployment azure.Deployment) database.PostgresSQLConfig { + + parent := fmt.Sprintf("%s/", resource.Name.AsString()) + + config := database.PostgresSQLConfig{ + Metadata: resource.Metadata, + LogCheckpoints: iacTypes.BoolDefault(false, resource.Metadata), + ConnectionThrottling: iacTypes.BoolDefault(false, resource.Metadata), + LogConnections: iacTypes.BoolDefault(false, resource.Metadata), + } + + for _, configuration := range deployment.GetResourcesByType("Microsoft.DBforPostgreSQL/servers/configurations") { + if strings.HasPrefix(configuration.Name.AsString(), parent) { + val := configuration.Properties.GetMapValue("value") + if strings.HasSuffix(configuration.Name.AsString(), "log_checkpoints") { + config.LogCheckpoints = val.AsBoolValue(false, configuration.Metadata) + continue + } + if strings.HasSuffix(configuration.Name.AsString(), "log_connections") { + config.LogConnections = val.AsBoolValue(false, configuration.Metadata) + continue + } + if strings.HasSuffix(configuration.Name.AsString(), "connection_throttling") { + config.ConnectionThrottling = val.AsBoolValue(false, configuration.Metadata) + continue + } + } + } + + return config +} diff --git a/pkg/iac/adapters/arm/datafactory/adapt.go b/pkg/iac/adapters/arm/datafactory/adapt.go new file mode 100644 index 000000000000..3dbb44ecd0b0 --- /dev/null +++ b/pkg/iac/adapters/arm/datafactory/adapt.go @@ -0,0 +1,27 @@ +package datafactory + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/datafactory" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" +) + +func Adapt(deployment azure.Deployment) datafactory.DataFactory { + + return datafactory.DataFactory{ + DataFactories: adaptDataFactories(deployment), + } +} + +func adaptDataFactories(deployment azure.Deployment) (factories []datafactory.Factory) { + for _, resource := range deployment.GetResourcesByType("Microsoft.DataFactory/factories") { + factories = append(factories, adaptDataFactory(resource)) + } + return factories +} + +func adaptDataFactory(resource azure.Resource) datafactory.Factory { + return datafactory.Factory{ + Metadata: resource.Metadata, + EnablePublicNetwork: resource.Properties.GetMapValue("publicNetworkAccess").AsBoolValue(true, resource.Metadata), + } +} diff --git a/pkg/iac/adapters/arm/datalake/adapt.go b/pkg/iac/adapters/arm/datalake/adapt.go new file mode 100644 index 000000000000..301650d50114 --- /dev/null +++ b/pkg/iac/adapters/arm/datalake/adapt.go @@ -0,0 +1,28 @@ +package datalake + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/datalake" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" +) + +func Adapt(deployment azure.Deployment) datalake.DataLake { + + return datalake.DataLake{ + Stores: adaptStores(deployment), + } +} + +func adaptStores(deployment azure.Deployment) (stores []datalake.Store) { + for _, resource := range deployment.GetResourcesByType("Microsoft.DataLakeStore/accounts") { + stores = append(stores, adaptStore(resource)) + } + + return stores +} + +func adaptStore(resource azure.Resource) datalake.Store { + return datalake.Store{ + Metadata: resource.Metadata, + EnableEncryption: resource.Properties.GetMapValue("encryptionState").AsBoolValue(false, resource.Metadata), + } +} diff --git a/pkg/iac/adapters/arm/keyvault/adapt.go b/pkg/iac/adapters/arm/keyvault/adapt.go new file mode 100644 index 000000000000..71a1fbd54f33 --- /dev/null +++ b/pkg/iac/adapters/arm/keyvault/adapt.go @@ -0,0 +1,64 @@ +package keyvault + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/keyvault" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" +) + +func Adapt(deployment azure.Deployment) keyvault.KeyVault { + return keyvault.KeyVault{ + Vaults: adaptVaults(deployment), + } +} + +func adaptVaults(deployment azure.Deployment) (vaults []keyvault.Vault) { + for _, resource := range deployment.GetResourcesByType("Microsoft.KeyVault/vaults") { + vaults = append(vaults, adaptVault(resource, deployment)) + } + + return vaults +} + +func adaptVault(resource azure.Resource, deployment azure.Deployment) keyvault.Vault { + return keyvault.Vault{ + Metadata: resource.Metadata, + Secrets: adaptSecrets(resource, deployment), + Keys: adaptKeys(resource, deployment), + EnablePurgeProtection: resource.Properties.GetMapValue("enablePurgeProtection").AsBoolValue(false, resource.Metadata), + SoftDeleteRetentionDays: resource.Properties.GetMapValue("softDeleteRetentionInDays").AsIntValue(7, resource.Metadata), + NetworkACLs: keyvault.NetworkACLs{ + Metadata: resource.Metadata, + DefaultAction: resource.Properties.GetMapValue("properties").GetMapValue("networkAcls").GetMapValue("defaultAction").AsStringValue("", resource.Metadata), + }, + } +} + +func adaptKeys(resource azure.Resource, deployment azure.Deployment) (keys []keyvault.Key) { + for _, resource := range deployment.GetResourcesByType("Microsoft.KeyVault/vaults/keys") { + keys = append(keys, adaptKey(resource)) + } + + return keys +} + +func adaptKey(resource azure.Resource) keyvault.Key { + return keyvault.Key{ + Metadata: resource.Metadata, + ExpiryDate: resource.Properties.GetMapValue("attributes").GetMapValue("exp").AsTimeValue(resource.Metadata), + } +} + +func adaptSecrets(resource azure.Resource, deployment azure.Deployment) (secrets []keyvault.Secret) { + for _, resource := range deployment.GetResourcesByType("Microsoft.KeyVault/vaults/secrets") { + secrets = append(secrets, adaptSecret(resource)) + } + return secrets +} + +func adaptSecret(resource azure.Resource) keyvault.Secret { + return keyvault.Secret{ + Metadata: resource.Metadata, + ContentType: resource.Properties.GetMapValue("contentType").AsStringValue("", resource.Metadata), + ExpiryDate: resource.Properties.GetMapValue("attributes").GetMapValue("exp").AsTimeValue(resource.Metadata), + } +} diff --git a/pkg/iac/adapters/arm/monitor/adapt.go b/pkg/iac/adapters/arm/monitor/adapt.go new file mode 100644 index 000000000000..e821ec256c92 --- /dev/null +++ b/pkg/iac/adapters/arm/monitor/adapt.go @@ -0,0 +1,45 @@ +package monitor + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/monitor" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(deployment azure.Deployment) monitor.Monitor { + return monitor.Monitor{ + LogProfiles: adaptLogProfiles(deployment), + } +} + +func adaptLogProfiles(deployment azure.Deployment) (logProfiles []monitor.LogProfile) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Insights/logProfiles") { + logProfiles = append(logProfiles, adaptLogProfile(resource)) + } + return logProfiles +} + +func adaptLogProfile(resource azure.Resource) monitor.LogProfile { + categories := resource.Properties.GetMapValue("categories").AsList() + var categoriesList []types.StringValue + for _, category := range categories { + categoriesList = append(categoriesList, category.AsStringValue("", category.Metadata)) + } + + locations := resource.Properties.GetMapValue("locations").AsList() + var locationsList []types.StringValue + for _, location := range locations { + locationsList = append(locationsList, location.AsStringValue("", location.Metadata)) + } + + return monitor.LogProfile{ + Metadata: resource.Metadata, + RetentionPolicy: monitor.RetentionPolicy{ + Metadata: resource.Metadata, + Enabled: resource.Properties.GetMapValue("retentionPolicy").GetMapValue("enabled").AsBoolValue(false, resource.Metadata), + Days: resource.Properties.GetMapValue("retentionPolicy").GetMapValue("days").AsIntValue(0, resource.Metadata), + }, + Categories: categoriesList, + Locations: locationsList, + } +} diff --git a/pkg/iac/adapters/arm/network/adapt.go b/pkg/iac/adapters/arm/network/adapt.go new file mode 100644 index 000000000000..5201b84761e7 --- /dev/null +++ b/pkg/iac/adapters/arm/network/adapt.go @@ -0,0 +1,126 @@ +package network + +import ( + "strconv" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/network" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(deployment azure.Deployment) network.Network { + return network.Network{ + SecurityGroups: adaptSecurityGroups(deployment), + NetworkWatcherFlowLogs: adaptNetworkWatcherFlowLogs(deployment), + } +} + +func adaptSecurityGroups(deployment azure.Deployment) (sgs []network.SecurityGroup) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Network/networkSecurityGroups") { + sgs = append(sgs, adaptSecurityGroup(resource, deployment)) + } + return sgs + +} + +func adaptSecurityGroup(resource azure.Resource, deployment azure.Deployment) network.SecurityGroup { + return network.SecurityGroup{ + Metadata: resource.Metadata, + Rules: adaptSecurityGroupRules(resource, deployment), + } +} + +func adaptSecurityGroupRules(resource azure.Resource, deployment azure.Deployment) (rules []network.SecurityGroupRule) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Network/networkSecurityGroups/securityRules") { + rules = append(rules, adaptSecurityGroupRule(resource)) + } + return rules +} + +func adaptSecurityGroupRule(resource azure.Resource) network.SecurityGroupRule { + sourceAddressPrefixes := resource.Properties.GetMapValue("sourceAddressPrefixes").AsStringValuesList("") + sourceAddressPrefixes = append(sourceAddressPrefixes, resource.Properties.GetMapValue("sourceAddressPrefix").AsStringValue("", resource.Metadata)) + + var sourcePortRanges []network.PortRange + for _, portRange := range resource.Properties.GetMapValue("sourcePortRanges").AsList() { + sourcePortRanges = append(sourcePortRanges, expandRange(portRange.AsString(), resource.Metadata)) + } + sourcePortRanges = append(sourcePortRanges, expandRange(resource.Properties.GetMapValue("sourcePortRange").AsString(), resource.Metadata)) + + destinationAddressPrefixes := resource.Properties.GetMapValue("destinationAddressPrefixes").AsStringValuesList("") + destinationAddressPrefixes = append(destinationAddressPrefixes, resource.Properties.GetMapValue("destinationAddressPrefix").AsStringValue("", resource.Metadata)) + + var destinationPortRanges []network.PortRange + for _, portRange := range resource.Properties.GetMapValue("destinationPortRanges").AsList() { + destinationPortRanges = append(destinationPortRanges, expandRange(portRange.AsString(), resource.Metadata)) + } + destinationPortRanges = append(destinationPortRanges, expandRange(resource.Properties.GetMapValue("destinationPortRange").AsString(), resource.Metadata)) + + allow := iacTypes.BoolDefault(false, resource.Metadata) + if resource.Properties.GetMapValue("access").AsString() == "Allow" { + allow = iacTypes.Bool(true, resource.Metadata) + } + + outbound := iacTypes.BoolDefault(false, resource.Metadata) + if resource.Properties.GetMapValue("direction").AsString() == "Outbound" { + outbound = iacTypes.Bool(true, resource.Metadata) + } + + return network.SecurityGroupRule{ + Metadata: resource.Metadata, + Outbound: outbound, + Allow: allow, + SourceAddresses: sourceAddressPrefixes, + SourcePorts: sourcePortRanges, + DestinationAddresses: destinationAddressPrefixes, + DestinationPorts: destinationPortRanges, + Protocol: resource.Properties.GetMapValue("protocol").AsStringValue("", resource.Metadata), + } +} + +func adaptNetworkWatcherFlowLogs(deployment azure.Deployment) (flowLogs []network.NetworkWatcherFlowLog) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Network/networkWatchers/flowLogs") { + flowLogs = append(flowLogs, adaptNetworkWatcherFlowLog(resource)) + } + return flowLogs +} + +func adaptNetworkWatcherFlowLog(resource azure.Resource) network.NetworkWatcherFlowLog { + return network.NetworkWatcherFlowLog{ + Metadata: resource.Metadata, + RetentionPolicy: network.RetentionPolicy{ + Metadata: resource.Metadata, + Enabled: resource.Properties.GetMapValue("retentionPolicy").GetMapValue("enabled").AsBoolValue(false, resource.Metadata), + Days: resource.Properties.GetMapValue("retentionPolicy").GetMapValue("days").AsIntValue(0, resource.Metadata), + }, + } +} + +func expandRange(r string, m iacTypes.Metadata) network.PortRange { + start := 0 + end := 65535 + switch { + case r == "*": + case strings.Contains(r, "-"): + if parts := strings.Split(r, "-"); len(parts) == 2 { + if p1, err := strconv.ParseInt(parts[0], 10, 32); err == nil { + start = int(p1) + } + if p2, err := strconv.ParseInt(parts[1], 10, 32); err == nil { + end = int(p2) + } + } + default: + if val, err := strconv.ParseInt(r, 10, 32); err == nil { + start = int(val) + end = int(val) + } + } + + return network.PortRange{ + Metadata: m, + Start: start, + End: end, + } +} diff --git a/pkg/iac/adapters/arm/securitycenter/adapt.go b/pkg/iac/adapters/arm/securitycenter/adapt.go new file mode 100644 index 000000000000..69a5de5b8d79 --- /dev/null +++ b/pkg/iac/adapters/arm/securitycenter/adapt.go @@ -0,0 +1,43 @@ +package securitycenter + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" +) + +func Adapt(deployment azure.Deployment) securitycenter.SecurityCenter { + return securitycenter.SecurityCenter{ + Contacts: adaptContacts(deployment), + Subscriptions: adaptSubscriptions(deployment), + } +} + +func adaptContacts(deployment azure.Deployment) (contacts []securitycenter.Contact) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Security/securityContacts") { + contacts = append(contacts, adaptContact(resource)) + } + + return contacts +} + +func adaptContact(resource azure.Resource) securitycenter.Contact { + return securitycenter.Contact{ + Metadata: resource.Metadata, + EnableAlertNotifications: resource.Properties.GetMapValue("email").AsBoolValue(false, resource.Metadata), + Phone: resource.Properties.GetMapValue("phone").AsStringValue("", resource.Metadata), + } +} + +func adaptSubscriptions(deployment azure.Deployment) (subscriptions []securitycenter.SubscriptionPricing) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Security/pricings") { + subscriptions = append(subscriptions, adaptSubscription(resource)) + } + return subscriptions +} + +func adaptSubscription(resource azure.Resource) securitycenter.SubscriptionPricing { + return securitycenter.SubscriptionPricing{ + Metadata: resource.Metadata, + Tier: resource.Properties.GetMapValue("pricingTier").AsStringValue("Free", resource.Metadata), + } +} diff --git a/pkg/iac/adapters/arm/storage/adapt.go b/pkg/iac/adapters/arm/storage/adapt.go new file mode 100644 index 000000000000..1b10ebbe9ad8 --- /dev/null +++ b/pkg/iac/adapters/arm/storage/adapt.go @@ -0,0 +1,68 @@ +package storage + +import ( + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/storage" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(deployment azure.Deployment) storage.Storage { + return storage.Storage{ + Accounts: adaptAccounts(deployment), + } +} + +func adaptAccounts(deployment azure.Deployment) []storage.Account { + var accounts []storage.Account + for _, resource := range deployment.GetResourcesByType("Microsoft.Storage/storageAccounts") { + + var networkRules []storage.NetworkRule + for _, acl := range resource.Properties.GetMapValue("networkAcls").AsList() { + + var bypasses []types.StringValue + bypassProp := acl.GetMapValue("bypass") + for _, bypass := range strings.Split(bypassProp.AsString(), ",") { + bypasses = append(bypasses, types.String(bypass, bypassProp.GetMetadata())) + } + + networkRules = append(networkRules, storage.NetworkRule{ + Metadata: acl.GetMetadata(), + Bypass: bypasses, + AllowByDefault: types.Bool(acl.GetMapValue("defaultAction").EqualTo("Allow"), acl.GetMetadata()), + }) + } + + var queues []storage.Queue + for _, queueResource := range resource.GetResourcesByType("queueServices/queues") { + queues = append(queues, storage.Queue{ + Metadata: queueResource.Metadata, + Name: queueResource.Name.AsStringValue("", queueResource.Metadata), + }) + } + + var containers []storage.Container + for _, containerResource := range resource.GetResourcesByType("containerServices/containers") { + containers = append(containers, storage.Container{ + Metadata: containerResource.Metadata, + PublicAccess: containerResource.Properties.GetMapValue("publicAccess").AsStringValue("None", containerResource.Metadata), + }) + } + + account := storage.Account{ + Metadata: resource.Metadata, + NetworkRules: networkRules, + EnforceHTTPS: resource.Properties.GetMapValue("supportsHttpsTrafficOnly").AsBoolValue(false, resource.Properties.GetMetadata()), + Containers: containers, + QueueProperties: storage.QueueProperties{ + Metadata: resource.Properties.GetMetadata(), + EnableLogging: types.BoolDefault(false, resource.Properties.GetMetadata()), + }, + MinimumTLSVersion: resource.Properties.GetMapValue("minimumTlsVersion").AsStringValue("TLS1_0", resource.Properties.GetMetadata()), + Queues: queues, + } + accounts = append(accounts, account) + } + return accounts +} diff --git a/pkg/iac/adapters/arm/storage/adapt_test.go b/pkg/iac/adapters/arm/storage/adapt_test.go new file mode 100644 index 000000000000..31d95e814d6e --- /dev/null +++ b/pkg/iac/adapters/arm/storage/adapt_test.go @@ -0,0 +1,58 @@ +package storage + +import ( + "testing" + + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/require" +) + +func Test_AdaptStorageDefaults(t *testing.T) { + + input := azure2.Deployment{ + Resources: []azure2.Resource{ + { + Type: azure2.NewValue("Microsoft.Storage/storageAccounts", types.NewTestMetadata()), + Properties: azure2.NewValue(map[string]azure2.Value{}, types.NewTestMetadata()), + }, + }, + } + + output := Adapt(input) + + require.Len(t, output.Accounts, 1) + + account := output.Accounts[0] + assert.Equal(t, "TLS1_0", account.MinimumTLSVersion.Value()) + assert.Equal(t, false, account.EnforceHTTPS.Value()) + +} + +func Test_AdaptStorage(t *testing.T) { + + input := azure2.Deployment{ + Resources: []azure2.Resource{ + { + Type: azure2.NewValue("Microsoft.Storage/storageAccounts", types.NewTestMetadata()), + Name: azure2.Value{}, + Properties: azure2.NewValue(map[string]azure2.Value{ + "minimumTlsVersion": azure2.NewValue("TLS1_2", types.NewTestMetadata()), + "supportsHttpsTrafficOnly": azure2.NewValue(true, types.NewTestMetadata()), + }, types.NewTestMetadata()), + }, + }, + } + + output := Adapt(input) + + require.Len(t, output.Accounts, 1) + + account := output.Accounts[0] + assert.Equal(t, "TLS1_2", account.MinimumTLSVersion.Value()) + assert.Equal(t, true, account.EnforceHTTPS.Value()) + +} diff --git a/pkg/iac/adapters/arm/synapse/adapt.go b/pkg/iac/adapters/arm/synapse/adapt.go new file mode 100644 index 000000000000..649831e16a9a --- /dev/null +++ b/pkg/iac/adapters/arm/synapse/adapt.go @@ -0,0 +1,34 @@ +package synapse + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/synapse" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(deployment azure.Deployment) synapse.Synapse { + return synapse.Synapse{ + Workspaces: adaptWorkspaces(deployment), + } +} + +func adaptWorkspaces(deployment azure.Deployment) (workspaces []synapse.Workspace) { + for _, resource := range deployment.GetResourcesByType("Microsoft.Synapse/workspaces") { + workspaces = append(workspaces, adaptWorkspace(resource)) + } + return workspaces +} + +func adaptWorkspace(resource azure.Resource) synapse.Workspace { + + managedVirtualNetwork := resource.Properties.GetMapValue("managedVirtualNetwork").AsString() + enableManagedVirtualNetwork := types.BoolDefault(false, resource.Metadata) + if managedVirtualNetwork == "default" { + enableManagedVirtualNetwork = types.Bool(true, resource.Metadata) + } + + return synapse.Workspace{ + Metadata: resource.Metadata, + EnableManagedVirtualNetwork: enableManagedVirtualNetwork, + } +} diff --git a/pkg/iac/adapters/cloudformation/adapt.go b/pkg/iac/adapters/cloudformation/adapt.go new file mode 100644 index 000000000000..da2d2595892d --- /dev/null +++ b/pkg/iac/adapters/cloudformation/adapt.go @@ -0,0 +1,14 @@ +package cloudformation + +import ( + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/state" +) + +// Adapt adapts the Cloudformation instance +func Adapt(cfFile parser.FileContext) *state.State { + return &state.State{ + AWS: aws.Adapt(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/accessanalyzer/accessanalyzer.go b/pkg/iac/adapters/cloudformation/aws/accessanalyzer/accessanalyzer.go new file mode 100644 index 000000000000..e6f7031a58d5 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/accessanalyzer/accessanalyzer.go @@ -0,0 +1,13 @@ +package accessanalyzer + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/accessanalyzer" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an AccessAnalyzer instance +func Adapt(cfFile parser.FileContext) accessanalyzer.AccessAnalyzer { + return accessanalyzer.AccessAnalyzer{ + Analyzers: getAccessAnalyzer(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/accessanalyzer/accessanalyzer_test.go b/pkg/iac/adapters/cloudformation/aws/accessanalyzer/accessanalyzer_test.go new file mode 100644 index 000000000000..04e67c2b6818 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/accessanalyzer/accessanalyzer_test.go @@ -0,0 +1,54 @@ +package accessanalyzer + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/accessanalyzer" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected accessanalyzer.AccessAnalyzer + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Analyzer: + Type: 'AWS::AccessAnalyzer::Analyzer' + Properties: + AnalyzerName: MyAccountAnalyzer +`, + expected: accessanalyzer.AccessAnalyzer{ + Analyzers: []accessanalyzer.Analyzer{ + { + Name: types.StringTest("MyAccountAnalyzer"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Analyzer: + Type: 'AWS::AccessAnalyzer::Analyzer' +`, + expected: accessanalyzer.AccessAnalyzer{ + Analyzers: []accessanalyzer.Analyzer{ + {}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/accessanalyzer/analyzer.go b/pkg/iac/adapters/cloudformation/aws/accessanalyzer/analyzer.go new file mode 100644 index 000000000000..c81802d60e0e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/accessanalyzer/analyzer.go @@ -0,0 +1,24 @@ +package accessanalyzer + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/accessanalyzer" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getAccessAnalyzer(ctx parser.FileContext) (analyzers []accessanalyzer.Analyzer) { + + analyzersList := ctx.GetResourcesByType("AWS::AccessAnalyzer::Analyzer") + + for _, r := range analyzersList { + aa := accessanalyzer.Analyzer{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("AnalyzerName"), + ARN: r.StringDefault(""), + Active: types.BoolDefault(false, r.Metadata()), + } + + analyzers = append(analyzers, aa) + } + return analyzers +} diff --git a/pkg/iac/adapters/cloudformation/aws/adapt.go b/pkg/iac/adapters/cloudformation/aws/adapt.go new file mode 100644 index 000000000000..e8c10edf0855 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/adapt.go @@ -0,0 +1,74 @@ +package aws + +import ( + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/apigateway" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/athena" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/cloudfront" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/cloudtrail" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/cloudwatch" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/codebuild" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/config" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/documentdb" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/dynamodb" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/ecr" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/ecs" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/efs" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/eks" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/elasticache" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/elasticsearch" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/elb" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/kinesis" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/lambda" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/mq" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/msk" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/neptune" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/rds" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/redshift" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/s3" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/sam" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/sns" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/sqs" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/ssm" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/aws/workspaces" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a Cloudformation AWS instance +func Adapt(cfFile parser.FileContext) aws.AWS { + return aws.AWS{ + APIGateway: apigateway.Adapt(cfFile), + Athena: athena.Adapt(cfFile), + Cloudfront: cloudfront.Adapt(cfFile), + CloudTrail: cloudtrail.Adapt(cfFile), + CloudWatch: cloudwatch.Adapt(cfFile), + CodeBuild: codebuild.Adapt(cfFile), + Config: config.Adapt(cfFile), + DocumentDB: documentdb.Adapt(cfFile), + DynamoDB: dynamodb.Adapt(cfFile), + EC2: ec2.Adapt(cfFile), + ECR: ecr.Adapt(cfFile), + ECS: ecs.Adapt(cfFile), + EFS: efs.Adapt(cfFile), + IAM: iam.Adapt(cfFile), + EKS: eks.Adapt(cfFile), + ElastiCache: elasticache.Adapt(cfFile), + Elasticsearch: elasticsearch.Adapt(cfFile), + ELB: elb.Adapt(cfFile), + MSK: msk.Adapt(cfFile), + MQ: mq.Adapt(cfFile), + Kinesis: kinesis.Adapt(cfFile), + Lambda: lambda.Adapt(cfFile), + Neptune: neptune.Adapt(cfFile), + RDS: rds.Adapt(cfFile), + Redshift: redshift.Adapt(cfFile), + S3: s3.Adapt(cfFile), + SAM: sam.Adapt(cfFile), + SNS: sns.Adapt(cfFile), + SQS: sqs.Adapt(cfFile), + SSM: ssm.Adapt(cfFile), + WorkSpaces: workspaces.Adapt(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway.go b/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway.go new file mode 100644 index 000000000000..bbc79623a6c4 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway.go @@ -0,0 +1,21 @@ +package apigateway + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway" + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an APIGateway instance +func Adapt(cfFile parser.FileContext) apigateway.APIGateway { + return apigateway.APIGateway{ + V1: v1.APIGateway{ + APIs: nil, + DomainNames: nil, + }, + V2: v2.APIGateway{ + APIs: getApis(cfFile), + }, + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go b/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go new file mode 100644 index 000000000000..8f9e55ef8abd --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go @@ -0,0 +1,84 @@ +package apigateway + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway" + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected apigateway.APIGateway + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyApi: + Type: 'AWS::ApiGatewayV2::Api' + Properties: + Name: MyApi + ProtocolType: WEBSOCKET + MyStage: + Type: 'AWS::ApiGatewayV2::Stage' + Properties: + StageName: Prod + ApiId: !Ref MyApi + AccessLogSettings: + DestinationArn: some-arn +`, + expected: apigateway.APIGateway{ + V2: v2.APIGateway{ + APIs: []v2.API{ + { + Name: types.StringTest("MyApi"), + ProtocolType: types.StringTest("WEBSOCKET"), + Stages: []v2.Stage{ + { + Name: types.StringTest("Prod"), + AccessLogging: v2.AccessLogging{ + CloudwatchLogGroupARN: types.StringTest("some-arn"), + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyApi: + Type: 'AWS::ApiGatewayV2::Api' + MyStage: + Type: 'AWS::ApiGatewayV2::Stage' + MyStage2: + Type: 'AWS::ApiGatewayV2::Stage' + Properties: + ApiId: !Ref MyApi +`, + expected: apigateway.APIGateway{ + V2: v2.APIGateway{ + APIs: []v2.API{ + { + Stages: []v2.Stage{{}}, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/apigateway/stage.go b/pkg/iac/adapters/cloudformation/aws/apigateway/stage.go new file mode 100644 index 000000000000..8e9497a91ec3 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/apigateway/stage.go @@ -0,0 +1,68 @@ +package apigateway + +import ( + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getApis(cfFile parser.FileContext) (apis []v2.API) { + + apiResources := cfFile.GetResourcesByType("AWS::ApiGatewayV2::Api") + for _, apiRes := range apiResources { + api := v2.API{ + Metadata: apiRes.Metadata(), + Name: apiRes.GetStringProperty("Name"), + ProtocolType: apiRes.GetStringProperty("ProtocolType"), + Stages: getStages(apiRes.ID(), cfFile), + } + apis = append(apis, api) + } + + return apis +} + +func getStages(apiId string, cfFile parser.FileContext) []v2.Stage { + var apiStages []v2.Stage + + stageResources := cfFile.GetResourcesByType("AWS::ApiGatewayV2::Stage") + for _, r := range stageResources { + stageApiId := r.GetStringProperty("ApiId") + if stageApiId.Value() != apiId { + continue + } + + s := v2.Stage{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("StageName"), + AccessLogging: getAccessLogging(r), + } + apiStages = append(apiStages, s) + } + + return apiStages +} + +func getAccessLogging(r *parser.Resource) v2.AccessLogging { + + loggingProp := r.GetProperty("AccessLogSettings") + if loggingProp.IsNil() { + return v2.AccessLogging{ + Metadata: r.Metadata(), + CloudwatchLogGroupARN: types.StringDefault("", r.Metadata()), + } + } + + destinationProp := r.GetProperty("AccessLogSettings.DestinationArn") + + if destinationProp.IsNil() { + return v2.AccessLogging{ + Metadata: loggingProp.Metadata(), + CloudwatchLogGroupARN: types.StringDefault("", r.Metadata()), + } + } + return v2.AccessLogging{ + Metadata: destinationProp.Metadata(), + CloudwatchLogGroupARN: destinationProp.AsStringValue(), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/athena/athena.go b/pkg/iac/adapters/cloudformation/aws/athena/athena.go new file mode 100644 index 000000000000..b23a1936c179 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/athena/athena.go @@ -0,0 +1,14 @@ +package athena + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/athena" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an Athena instance +func Adapt(cfFile parser.FileContext) athena.Athena { + return athena.Athena{ + Databases: nil, + Workgroups: getWorkGroups(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/athena/athena_test.go b/pkg/iac/adapters/cloudformation/aws/athena/athena_test.go new file mode 100644 index 000000000000..097de6fa303d --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/athena/athena_test.go @@ -0,0 +1,61 @@ +package athena + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/athena" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected athena.Athena + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyAthenaWorkGroup: + Type: AWS::Athena::WorkGroup + Properties: + Name: MyCustomWorkGroup + WorkGroupConfiguration: + EnforceWorkGroupConfiguration: true + ResultConfiguration: + EncryptionOption: SSE_KMS +`, + expected: athena.Athena{ + Workgroups: []athena.Workgroup{ + { + Name: types.StringTest("MyCustomWorkGroup"), + EnforceConfiguration: types.BoolTest(true), + Encryption: athena.EncryptionConfiguration{ + Type: types.StringTest("SSE_KMS"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyAthenaWorkGroup: + Type: AWS::Athena::WorkGroup +`, + expected: athena.Athena{ + Workgroups: []athena.Workgroup{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } + +} diff --git a/pkg/iac/adapters/cloudformation/aws/athena/workgroup.go b/pkg/iac/adapters/cloudformation/aws/athena/workgroup.go new file mode 100644 index 000000000000..916e2ac4b6f8 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/athena/workgroup.go @@ -0,0 +1,30 @@ +package athena + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/athena" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getWorkGroups(cfFile parser.FileContext) []athena.Workgroup { + + var workgroups []athena.Workgroup + + workgroupResources := cfFile.GetResourcesByType("AWS::Athena::WorkGroup") + + for _, r := range workgroupResources { + + wg := athena.Workgroup{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("Name"), + Encryption: athena.EncryptionConfiguration{ + Metadata: r.Metadata(), + Type: r.GetStringProperty("WorkGroupConfiguration.ResultConfiguration.EncryptionConfiguration.EncryptionOption"), + }, + EnforceConfiguration: r.GetBoolProperty("WorkGroupConfiguration.EnforceWorkGroupConfiguration"), + } + + workgroups = append(workgroups, wg) + } + + return workgroups +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront.go b/pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront.go new file mode 100644 index 000000000000..0cfe00145907 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront.go @@ -0,0 +1,13 @@ +package cloudfront + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a CloudFront instance +func Adapt(cfFile parser.FileContext) cloudfront.Cloudfront { + return cloudfront.Cloudfront{ + Distributions: getDistributions(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront_test.go b/pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront_test.go new file mode 100644 index 000000000000..6c0ec7348b33 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront_test.go @@ -0,0 +1,68 @@ +package cloudfront + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected cloudfront.Cloudfront + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + cloudfrontdistribution: + Type: AWS::CloudFront::Distribution + Properties: + DistributionConfig: + WebACLId: "a1b2c3d4-5678-90ab-cdef-EXAMPLE11111" + Logging: + Bucket: "myawslogbucket.s3.amazonaws.com" + ViewerCertificate: + MinimumProtocolVersion: SSLv3 + DefaultCacheBehavior: + ViewerProtocolPolicy: "redirect-to-https" +`, + expected: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + WAFID: types.StringTest("a1b2c3d4-5678-90ab-cdef-EXAMPLE11111"), + Logging: cloudfront.Logging{ + Bucket: types.StringTest("myawslogbucket.s3.amazonaws.com"), + }, + ViewerCertificate: cloudfront.ViewerCertificate{ + MinimumProtocolVersion: types.StringTest("SSLv3"), + }, + DefaultCacheBehaviour: cloudfront.CacheBehaviour{ + ViewerProtocolPolicy: types.StringTest("redirect-to-https"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + cloudfrontdistribution: + Type: AWS::CloudFront::Distribution +`, + expected: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudfront/distribution.go b/pkg/iac/adapters/cloudformation/aws/cloudfront/distribution.go new file mode 100644 index 000000000000..70c5052bcd55 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudfront/distribution.go @@ -0,0 +1,45 @@ +package cloudfront + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getDistributions(ctx parser.FileContext) (distributions []cloudfront.Distribution) { + + distributionResources := ctx.GetResourcesByType("AWS::CloudFront::Distribution") + + for _, r := range distributionResources { + distribution := cloudfront.Distribution{ + Metadata: r.Metadata(), + WAFID: r.GetStringProperty("DistributionConfig.WebACLId"), + Logging: cloudfront.Logging{ + Metadata: r.Metadata(), + Bucket: r.GetStringProperty("DistributionConfig.Logging.Bucket"), + }, + DefaultCacheBehaviour: getDefaultCacheBehaviour(r), + OrdererCacheBehaviours: nil, + ViewerCertificate: cloudfront.ViewerCertificate{ + Metadata: r.Metadata(), + MinimumProtocolVersion: r.GetStringProperty("DistributionConfig.ViewerCertificate.MinimumProtocolVersion"), + }, + } + + distributions = append(distributions, distribution) + } + + return distributions +} + +func getDefaultCacheBehaviour(r *parser.Resource) cloudfront.CacheBehaviour { + defaultCache := r.GetProperty("DistributionConfig.DefaultCacheBehavior") + if defaultCache.IsNil() { + return cloudfront.CacheBehaviour{ + Metadata: r.Metadata(), + } + } + return cloudfront.CacheBehaviour{ + Metadata: defaultCache.Metadata(), + ViewerProtocolPolicy: defaultCache.GetStringProperty("ViewerProtocolPolicy"), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudtrail/cloudtrail.go b/pkg/iac/adapters/cloudformation/aws/cloudtrail/cloudtrail.go new file mode 100644 index 000000000000..04270579dbcd --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudtrail/cloudtrail.go @@ -0,0 +1,13 @@ +package cloudtrail + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a CloudTrail instance +func Adapt(cfFile parser.FileContext) cloudtrail.CloudTrail { + return cloudtrail.CloudTrail{ + Trails: getCloudTrails(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudtrail/cloudtrail_test.go b/pkg/iac/adapters/cloudformation/aws/cloudtrail/cloudtrail_test.go new file mode 100644 index 000000000000..5dcebb291035 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudtrail/cloudtrail_test.go @@ -0,0 +1,64 @@ +package cloudtrail + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected cloudtrail.CloudTrail + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Trail: + Type: AWS::CloudTrail::Trail + Properties: + S3BucketName: MyBucket + IsLogging: true + TrailName: MyTrail + EnableLogFileValidation: true + IsMultiRegionTrail: true + CloudWatchLogsLogGroupArn: cw-arn + KmsKeyId: my-kms-key +`, + expected: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Name: types.StringTest("MyTrail"), + BucketName: types.StringTest("MyBucket"), + IsLogging: types.BoolTest(true), + IsMultiRegion: types.BoolTest(true), + EnableLogFileValidation: types.BoolTest(true), + CloudWatchLogsLogGroupArn: types.StringTest("cw-arn"), + KMSKeyID: types.StringTest("my-kms-key"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Trail: + Type: AWS::CloudTrail::Trail + `, + expected: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudtrail/trails.go b/pkg/iac/adapters/cloudformation/aws/cloudtrail/trails.go new file mode 100644 index 000000000000..fc9c3871cbba --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudtrail/trails.go @@ -0,0 +1,27 @@ +package cloudtrail + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getCloudTrails(ctx parser.FileContext) (trails []cloudtrail.Trail) { + + cloudtrailResources := ctx.GetResourcesByType("AWS::CloudTrail::Trail") + + for _, r := range cloudtrailResources { + ct := cloudtrail.Trail{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("TrailName"), + EnableLogFileValidation: r.GetBoolProperty("EnableLogFileValidation"), + IsMultiRegion: r.GetBoolProperty("IsMultiRegionTrail"), + KMSKeyID: r.GetStringProperty("KmsKeyId"), + CloudWatchLogsLogGroupArn: r.GetStringProperty("CloudWatchLogsLogGroupArn"), + IsLogging: r.GetBoolProperty("IsLogging"), + BucketName: r.GetStringProperty("S3BucketName"), + } + + trails = append(trails, ct) + } + return trails +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch.go b/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch.go new file mode 100644 index 000000000000..0c4a59e43189 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch.go @@ -0,0 +1,13 @@ +package cloudwatch + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a Cloudwatch instance +func Adapt(cfFile parser.FileContext) cloudwatch.CloudWatch { + return cloudwatch.CloudWatch{ + LogGroups: getLogGroups(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch_test.go b/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch_test.go new file mode 100644 index 000000000000..c8a7bd95c9a3 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch_test.go @@ -0,0 +1,57 @@ +package cloudwatch + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected cloudwatch.CloudWatch + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + myLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: my-log-group + RetentionInDays: 7 + KmsKeyId: my-kms + +`, + expected: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Name: types.StringTest("my-log-group"), + RetentionInDays: types.IntTest(7), + KMSKeyID: types.StringTest("my-kms"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + myLogGroup: + Type: AWS::Logs::LogGroup + `, + expected: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudwatch/log_group.go b/pkg/iac/adapters/cloudformation/aws/cloudwatch/log_group.go new file mode 100644 index 000000000000..09e039129781 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudwatch/log_group.go @@ -0,0 +1,23 @@ +package cloudwatch + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getLogGroups(ctx parser.FileContext) (logGroups []cloudwatch.LogGroup) { + + logGroupResources := ctx.GetResourcesByType("AWS::Logs::LogGroup") + + for _, r := range logGroupResources { + group := cloudwatch.LogGroup{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("LogGroupName"), + KMSKeyID: r.GetStringProperty("KmsKeyId"), + RetentionInDays: r.GetIntProperty("RetentionInDays"), + } + logGroups = append(logGroups, group) + } + + return logGroups +} diff --git a/pkg/iac/adapters/cloudformation/aws/codebuild/codebuild.go b/pkg/iac/adapters/cloudformation/aws/codebuild/codebuild.go new file mode 100644 index 000000000000..8951f230ee98 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/codebuild/codebuild.go @@ -0,0 +1,13 @@ +package codebuild + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/codebuild" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a CodeBuild instance +func Adapt(cfFile parser.FileContext) codebuild.CodeBuild { + return codebuild.CodeBuild{ + Projects: getProjects(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/codebuild/codebuild_test.go b/pkg/iac/adapters/cloudformation/aws/codebuild/codebuild_test.go new file mode 100644 index 000000000000..06eaa19402e6 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/codebuild/codebuild_test.go @@ -0,0 +1,68 @@ +package codebuild + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/codebuild" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected codebuild.CodeBuild + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Project: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + EncryptionDisabled: true + SecondaryArtifacts: + - EncryptionDisabled: true +`, + expected: codebuild.CodeBuild{ + Projects: []codebuild.Project{ + { + ArtifactSettings: codebuild.ArtifactSettings{ + EncryptionEnabled: types.BoolTest(false), + }, + SecondaryArtifactSettings: []codebuild.ArtifactSettings{ + { + EncryptionEnabled: types.BoolTest(false), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Project: + Type: AWS::CodeBuild::Project + `, + expected: codebuild.CodeBuild{ + Projects: []codebuild.Project{ + { + ArtifactSettings: codebuild.ArtifactSettings{ + EncryptionEnabled: types.BoolTest(true), + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/codebuild/project.go b/pkg/iac/adapters/cloudformation/aws/codebuild/project.go new file mode 100644 index 000000000000..554fc8afecea --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/codebuild/project.go @@ -0,0 +1,63 @@ +package codebuild + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/codebuild" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getProjects(ctx parser.FileContext) (projects []codebuild.Project) { + + projectResources := ctx.GetResourcesByType("AWS::CodeBuild::Project") + + for _, r := range projectResources { + project := codebuild.Project{ + Metadata: r.Metadata(), + ArtifactSettings: getArtifactSettings(r), + SecondaryArtifactSettings: getSecondaryArtifactSettings(r), + } + + projects = append(projects, project) + } + + return projects +} + +func getSecondaryArtifactSettings(r *parser.Resource) (secondaryArtifacts []codebuild.ArtifactSettings) { + secondaryArtifactsList := r.GetProperty("SecondaryArtifacts") + if secondaryArtifactsList.IsNil() || !secondaryArtifactsList.IsList() { + return + } + + for _, a := range secondaryArtifactsList.AsList() { + settings := codebuild.ArtifactSettings{ + Metadata: secondaryArtifactsList.Metadata(), + EncryptionEnabled: types.BoolDefault(true, secondaryArtifactsList.Metadata()), + } + encryptionDisabled := a.GetProperty("EncryptionDisabled") + if encryptionDisabled.IsBool() { + settings.EncryptionEnabled = types.Bool(!encryptionDisabled.AsBool(), encryptionDisabled.Metadata()) + } + secondaryArtifacts = append(secondaryArtifacts, settings) + } + + return secondaryArtifacts +} + +func getArtifactSettings(r *parser.Resource) codebuild.ArtifactSettings { + + settings := codebuild.ArtifactSettings{ + Metadata: r.Metadata(), + EncryptionEnabled: types.BoolDefault(true, r.Metadata()), + } + + artifactsProperty := r.GetProperty("Artifacts") + if artifactsProperty.IsNotNil() { + encryptionDisabled := artifactsProperty.GetProperty("EncryptionDisabled") + if encryptionDisabled.IsBool() { + settings.EncryptionEnabled = types.Bool(!encryptionDisabled.AsBool(), encryptionDisabled.Metadata()) + } + } + + return settings +} diff --git a/pkg/iac/adapters/cloudformation/aws/config/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/config/adapt_test.go new file mode 100644 index 000000000000..e6dc652da7b1 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/config/adapt_test.go @@ -0,0 +1,57 @@ +package config + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/config" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected config.Config + }{ + { + name: "Config aggregator with AccountAggregationSources", + source: `AWSTemplateFormatVersion: "2010-09-09" +Resources: + ConfigurationAggregator: + Type: AWS::Config::ConfigurationAggregator + Properties: + AccountAggregationSources: + - AllAwsRegions: "true" +`, + expected: config.Config{ + ConfigurationAggregrator: config.ConfigurationAggregrator{ + SourceAllRegions: types.BoolTest(true), + }, + }, + }, + { + name: "Config aggregator with OrganizationAggregationSource", + source: `AWSTemplateFormatVersion: "2010-09-09" +Resources: + ConfigurationAggregator: + Type: AWS::Config::ConfigurationAggregator + Properties: + OrganizationAggregationSource: + AllAwsRegions: "true" +`, + expected: config.Config{ + ConfigurationAggregrator: config.ConfigurationAggregrator{ + SourceAllRegions: types.BoolTest(true), + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } + +} diff --git a/pkg/iac/adapters/cloudformation/aws/config/aggregator.go b/pkg/iac/adapters/cloudformation/aws/config/aggregator.go new file mode 100644 index 000000000000..72447398b80f --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/config/aggregator.go @@ -0,0 +1,41 @@ +package config + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/config" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getConfigurationAggregator(ctx parser.FileContext) config.ConfigurationAggregrator { + + aggregator := config.ConfigurationAggregrator{ + Metadata: iacTypes.NewUnmanagedMetadata(), + SourceAllRegions: iacTypes.BoolDefault(false, ctx.Metadata()), + } + + aggregatorResources := ctx.GetResourcesByType("AWS::Config::ConfigurationAggregator") + + if len(aggregatorResources) == 0 { + return aggregator + } + + return config.ConfigurationAggregrator{ + Metadata: aggregatorResources[0].Metadata(), + SourceAllRegions: isSourcingAllRegions(aggregatorResources[0]), + } +} + +func isSourcingAllRegions(r *parser.Resource) iacTypes.BoolValue { + accountProp := r.GetProperty("AccountAggregationSources") + + if accountProp.IsNotNil() && accountProp.IsList() { + for _, a := range accountProp.AsList() { + regionsProp := a.GetProperty("AllAwsRegions") + if regionsProp.IsNotNil() { + return a.GetBoolProperty("AllAwsRegions") + } + } + } + + return r.GetBoolProperty("OrganizationAggregationSource.AllAwsRegions") +} diff --git a/pkg/iac/adapters/cloudformation/aws/config/config.go b/pkg/iac/adapters/cloudformation/aws/config/config.go new file mode 100644 index 000000000000..a9634252694e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/config/config.go @@ -0,0 +1,13 @@ +package config + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/config" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a configurationaggregator instance +func Adapt(cfFile parser.FileContext) config.Config { + return config.Config{ + ConfigurationAggregrator: getConfigurationAggregator(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/documentdb/cluster.go b/pkg/iac/adapters/cloudformation/aws/documentdb/cluster.go new file mode 100644 index 000000000000..f37467dc4100 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/documentdb/cluster.go @@ -0,0 +1,58 @@ +package documentdb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/documentdb" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getClusters(ctx parser.FileContext) (clusters []documentdb.Cluster) { + + clusterResources := ctx.GetResourcesByType("AWS::DocDB::DBCluster") + + for _, r := range clusterResources { + cluster := documentdb.Cluster{ + Metadata: r.Metadata(), + Identifier: r.GetStringProperty("DBClusterIdentifier"), + EnabledLogExports: getLogExports(r), + Instances: nil, + BackupRetentionPeriod: r.GetIntProperty("BackupRetentionPeriod", 1), + StorageEncrypted: r.GetBoolProperty("StorageEncrypted"), + KMSKeyID: r.GetStringProperty("KmsKeyId"), + } + + updateInstancesOnCluster(&cluster, ctx) + + clusters = append(clusters, cluster) + } + return clusters +} + +func updateInstancesOnCluster(cluster *documentdb.Cluster, ctx parser.FileContext) { + + instanceResources := ctx.GetResourcesByType("AWS::DocDB::DBInstance") + + for _, r := range instanceResources { + clusterIdentifier := r.GetStringProperty("DBClusterIdentifier") + if cluster.Identifier.EqualTo(clusterIdentifier.Value()) { + cluster.Instances = append(cluster.Instances, documentdb.Instance{ + Metadata: r.Metadata(), + KMSKeyID: cluster.KMSKeyID, + }) + } + } +} + +func getLogExports(r *parser.Resource) (logExports []types.StringValue) { + + exportsList := r.GetProperty("EnableCloudwatchLogsExports") + + if exportsList.IsNil() || exportsList.IsNotList() { + return logExports + } + + for _, export := range exportsList.AsList() { + logExports = append(logExports, export.AsStringValue()) + } + return logExports +} diff --git a/pkg/iac/adapters/cloudformation/aws/documentdb/documentdb.go b/pkg/iac/adapters/cloudformation/aws/documentdb/documentdb.go new file mode 100644 index 000000000000..40b0c6ffae44 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/documentdb/documentdb.go @@ -0,0 +1,13 @@ +package documentdb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/documentdb" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adaps a documentDB instance +func Adapt(cfFile parser.FileContext) documentdb.DocumentDB { + return documentdb.DocumentDB{ + Clusters: getClusters(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/documentdb/documentdb_test.go b/pkg/iac/adapters/cloudformation/aws/documentdb/documentdb_test.go new file mode 100644 index 000000000000..3e60155e9dfb --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/documentdb/documentdb_test.go @@ -0,0 +1,79 @@ +package documentdb + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/documentdb" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected documentdb.DocumentDB + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + myDBCluster: + Type: 'AWS::DocDB::DBCluster' + Properties: + BackupRetentionPeriod: 8 + DBClusterIdentifier: sample-cluster + KmsKeyId: your-kms-key-id + StorageEncrypted: true + EnableCloudwatchLogsExports: + - audit + - general + myDBInstance: + Type: 'AWS::DocDB::DBInstance' + Properties: + DBClusterIdentifier: sample-cluster + KmsKeyId: your-kms-key-id +`, + expected: documentdb.DocumentDB{ + Clusters: []documentdb.Cluster{ + { + Identifier: types.StringTest("sample-cluster"), + BackupRetentionPeriod: types.IntTest(8), + KMSKeyID: types.StringTest("your-kms-key-id"), + StorageEncrypted: types.BoolTest(true), + EnabledLogExports: []types.StringValue{ + types.StringTest("audit"), + types.StringTest("general"), + }, + Instances: []documentdb.Instance{ + { + KMSKeyID: types.StringTest("your-kms-key-id"), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + myDBCluster: + Type: 'AWS::DocDB::DBCluster' + `, + expected: documentdb.DocumentDB{ + Clusters: []documentdb.Cluster{ + { + BackupRetentionPeriod: types.IntTest(1), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/dynamodb/cluster.go b/pkg/iac/adapters/cloudformation/aws/dynamodb/cluster.go new file mode 100644 index 000000000000..8a350236134c --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/dynamodb/cluster.go @@ -0,0 +1,36 @@ +package dynamodb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/dynamodb" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getClusters(file parser.FileContext) (clusters []dynamodb.DAXCluster) { + + clusterResources := file.GetResourcesByType("AWS::DAX::Cluster") + + for _, r := range clusterResources { + cluster := dynamodb.DAXCluster{ + Metadata: r.Metadata(), + ServerSideEncryption: dynamodb.ServerSideEncryption{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + KMSKeyID: iacTypes.StringDefault("", r.Metadata()), + }, + PointInTimeRecovery: iacTypes.BoolUnresolvable(r.Metadata()), + } + + if sseProp := r.GetProperty("SSESpecification"); sseProp.IsNotNil() { + cluster.ServerSideEncryption = dynamodb.ServerSideEncryption{ + Metadata: sseProp.Metadata(), + Enabled: r.GetBoolProperty("SSESpecification.SSEEnabled"), + KMSKeyID: iacTypes.StringUnresolvable(sseProp.Metadata()), + } + } + + clusters = append(clusters, cluster) + } + + return clusters +} diff --git a/pkg/iac/adapters/cloudformation/aws/dynamodb/dynamodb.go b/pkg/iac/adapters/cloudformation/aws/dynamodb/dynamodb.go new file mode 100644 index 000000000000..c841e0671520 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/dynamodb/dynamodb.go @@ -0,0 +1,13 @@ +package dynamodb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/dynamodb" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a dynamodb instance +func Adapt(cfFile parser.FileContext) dynamodb.DynamoDB { + return dynamodb.DynamoDB{ + DAXClusters: getClusters(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/dynamodb/dynamodb_test.go b/pkg/iac/adapters/cloudformation/aws/dynamodb/dynamodb_test.go new file mode 100644 index 000000000000..ce62e85cde5e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/dynamodb/dynamodb_test.go @@ -0,0 +1,55 @@ +package dynamodb + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/dynamodb" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected dynamodb.DynamoDB + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + daxCluster: + Type: AWS::DAX::Cluster + Properties: + SSESpecification: + SSEEnabled: true +`, + expected: dynamodb.DynamoDB{ + DAXClusters: []dynamodb.DAXCluster{ + { + ServerSideEncryption: dynamodb.ServerSideEncryption{ + Enabled: types.BoolTest(true), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + daxCluster: + Type: AWS::DAX::Cluster + `, + expected: dynamodb.DynamoDB{ + DAXClusters: []dynamodb.DAXCluster{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go new file mode 100644 index 000000000000..ac05f8f7b263 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go @@ -0,0 +1,283 @@ +package ec2 + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected ec2.EC2 + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyEC2Instance: + Type: AWS::EC2::Instance + Properties: + ImageId: "ami-79fd7eee" + KeyName: "testkey" + BlockDeviceMappings: + - DeviceName: "/dev/sdm" + Ebs: + VolumeType: "io1" + Iops: "200" + DeleteOnTermination: "false" + VolumeSize: "20" + Encrypted: true + - DeviceName: "/dev/sdk" + NoDevice: {} + NewVolume: + Type: AWS::EC2::Volume + Properties: + KmsKeyId: alias/my_cmk + Encrypted: true + mySubnet: + Type: AWS::EC2::Subnet + Properties: + MapPublicIpOnLaunch: true + InstanceSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupName: default + GroupDescription: Allow http to client host + VpcId: vpc-id + SecurityGroupIngress: + - IpProtocol: tcp + Description: ingress + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + SecurityGroupEgress: + - IpProtocol: tcp + Description: egress + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + myNetworkAcl: + Type: AWS::EC2::NetworkAcl + Properties: + VpcId: vpc-1122334455aabbccd + InboundRule: + Type: AWS::EC2::NetworkAclEntry + Properties: + NetworkAclId: + Ref: myNetworkAcl + Egress: true + Protocol: 6 + RuleAction: allow + CidrBlock: 172.16.0.0/24 + myLaunchConfig: + Type: AWS::AutoScaling::LaunchConfiguration + Properties: + LaunchConfigurationName: test-cfg + InstanceId: !Ref MyEC2Instance + AssociatePublicIpAddress: true + SecurityGroups: + - !Ref InstanceSecurityGroup + UserData: test + BlockDeviceMappings: + - DeviceName: /dev/sda1 + Ebs: + VolumeSize: '30' + VolumeType: gp3 + Encrypted: true + - DeviceName: /dev/sdm + Ebs: + VolumeSize: '100' + DeleteOnTermination: false + MetadataOptions: + HttpTokens: required + HttpEndpoint: disabled +`, + expected: ec2.EC2{ + Instances: []ec2.Instance{ + { + MetadataOptions: ec2.MetadataOptions{ + HttpEndpoint: types.StringDefault("enabled", types.NewTestMetadata()), + HttpTokens: types.StringDefault("optional", types.NewTestMetadata()), + }, + RootBlockDevice: &ec2.BlockDevice{ + Encrypted: types.BoolDefault(true, types.NewTestMetadata()), + }, + EBSBlockDevices: []*ec2.BlockDevice{ + { + Encrypted: types.BoolDefault(false, types.NewTestMetadata()), + }, + }, + }, + }, + Volumes: []ec2.Volume{ + { + Encryption: ec2.Encryption{ + KMSKeyID: types.StringTest("alias/my_cmk"), + Enabled: types.BoolTest(true), + }, + }, + }, + Subnets: []ec2.Subnet{ + { + MapPublicIpOnLaunch: types.BoolTest(true), + }, + }, + SecurityGroups: []ec2.SecurityGroup{ + { + IsDefault: types.BoolTest(true), + Description: types.StringTest("Allow http to client host"), + VPCID: types.StringTest("vpc-id"), + IngressRules: []ec2.SecurityGroupRule{ + { + Description: types.StringTest("ingress"), + CIDRs: []types.StringValue{ + types.StringTest("0.0.0.0/0"), + }, + }, + }, + EgressRules: []ec2.SecurityGroupRule{ + { + Description: types.StringTest("egress"), + CIDRs: []types.StringValue{ + types.StringTest("0.0.0.0/0"), + }, + }, + }, + }, + }, + NetworkACLs: []ec2.NetworkACL{ + { + Rules: []ec2.NetworkACLRule{ + { + Type: types.StringTest(ec2.TypeEgress), + Action: types.StringTest(ec2.ActionAllow), + Protocol: types.StringTest("6"), + CIDRs: []types.StringValue{ + types.StringTest("172.16.0.0/24"), + }, + }, + }, + }, + }, + LaunchConfigurations: []ec2.LaunchConfiguration{ + { + Name: types.StringTest("test-cfg"), + AssociatePublicIP: types.BoolTest(true), + RootBlockDevice: &ec2.BlockDevice{ + Encrypted: types.BoolTest(true), + }, + EBSBlockDevices: []*ec2.BlockDevice{ + { + Encrypted: types.BoolTest(false), + }, + }, + UserData: types.StringTest("test"), + MetadataOptions: ec2.MetadataOptions{ + HttpTokens: types.StringTest("required"), + HttpEndpoint: types.StringTest("disabled"), + }, + }, + }, + }, + }, + { + name: "ec2 instance with launch template, ref to name", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyLaunchTemplate: + Type: AWS::EC2::LaunchTemplate + Properties: + LaunchTemplateName: MyTemplate + LaunchTemplateData: + MetadataOptions: + HttpEndpoint: enabled + HttpTokens: required + MyEC2Instance: + Type: AWS::EC2::Instance + Properties: + ImageId: "ami-79fd7eee" + LaunchTemplate: + LaunchTemplateName: MyTemplate +`, + expected: ec2.EC2{ + LaunchTemplates: []ec2.LaunchTemplate{ + { + Name: types.StringTest("MyTemplate"), + Instance: ec2.Instance{ + MetadataOptions: ec2.MetadataOptions{ + HttpEndpoint: types.StringTest("enabled"), + HttpTokens: types.StringTest("required"), + }, + }, + }, + }, + Instances: []ec2.Instance{ + { + MetadataOptions: ec2.MetadataOptions{ + HttpEndpoint: types.StringTest("enabled"), + HttpTokens: types.StringTest("required"), + }, + RootBlockDevice: &ec2.BlockDevice{ + Encrypted: types.BoolTest(false), + }, + }, + }, + }, + }, + { + name: "ec2 instance with launch template, ref to id", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyLaunchTemplate: + Type: AWS::EC2::LaunchTemplate + Properties: + LaunchTemplateName: MyTemplate + LaunchTemplateData: + MetadataOptions: + HttpEndpoint: enabled + HttpTokens: required + MyEC2Instance: + Type: AWS::EC2::Instance + Properties: + ImageId: "ami-79fd7eee" + LaunchTemplate: + LaunchTemplateId: !Ref MyLaunchTemplate +`, + expected: ec2.EC2{ + LaunchTemplates: []ec2.LaunchTemplate{ + { + Name: types.StringTest("MyTemplate"), + Instance: ec2.Instance{ + MetadataOptions: ec2.MetadataOptions{ + HttpEndpoint: types.StringTest("enabled"), + HttpTokens: types.StringTest("required"), + }, + }, + }, + }, + Instances: []ec2.Instance{ + { + MetadataOptions: ec2.MetadataOptions{ + HttpEndpoint: types.StringTest("enabled"), + HttpTokens: types.StringTest("required"), + }, + RootBlockDevice: &ec2.BlockDevice{ + Encrypted: types.BoolTest(false), + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } + +} diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/ec2.go b/pkg/iac/adapters/cloudformation/aws/ec2/ec2.go new file mode 100644 index 000000000000..93056580c9b4 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ec2/ec2.go @@ -0,0 +1,20 @@ +package ec2 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an EC2 instance +func Adapt(cfFile parser.FileContext) ec2.EC2 { + return ec2.EC2{ + LaunchConfigurations: getLaunchConfigurations(cfFile), + LaunchTemplates: getLaunchTemplates(cfFile), + Instances: getInstances(cfFile), + VPCs: nil, + NetworkACLs: getNetworkACLs(cfFile), + SecurityGroups: getSecurityGroups(cfFile), + Subnets: getSubnets(cfFile), + Volumes: getVolumes(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/instance.go b/pkg/iac/adapters/cloudformation/aws/ec2/instance.go new file mode 100644 index 000000000000..7b6f149e0168 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ec2/instance.go @@ -0,0 +1,106 @@ +package ec2 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getInstances(ctx parser.FileContext) (instances []ec2.Instance) { + instanceResources := ctx.GetResourcesByType("AWS::EC2::Instance") + + for _, r := range instanceResources { + instance := ec2.Instance{ + Metadata: r.Metadata(), + // metadata not supported by CloudFormation at the moment - + // https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/655 + MetadataOptions: ec2.MetadataOptions{ + Metadata: r.Metadata(), + HttpTokens: iacTypes.StringDefault("optional", r.Metadata()), + HttpEndpoint: iacTypes.StringDefault("enabled", r.Metadata()), + }, + UserData: r.GetStringProperty("UserData"), + } + + if launchTemplate, ok := findRelatedLaunchTemplate(ctx, r); ok { + instance = launchTemplate.Instance + } + + if instance.RootBlockDevice == nil { + instance.RootBlockDevice = &ec2.BlockDevice{ + Metadata: r.Metadata(), + Encrypted: iacTypes.BoolDefault(false, r.Metadata()), + } + } + + blockDevices := getBlockDevices(r) + for i, device := range blockDevices { + copyDevice := device + if i == 0 { + instance.RootBlockDevice = copyDevice + continue + } + instance.EBSBlockDevices = append(instance.EBSBlockDevices, device) + } + instances = append(instances, instance) + } + + return instances +} + +func findRelatedLaunchTemplate(fctx parser.FileContext, r *parser.Resource) (ec2.LaunchTemplate, bool) { + launchTemplateRef := r.GetProperty("LaunchTemplate.LaunchTemplateName") + if launchTemplateRef.IsString() { + res := findLaunchTemplateByName(fctx, launchTemplateRef) + if res != nil { + return adaptLaunchTemplate(res), true + } + } + + launchTemplateRef = r.GetProperty("LaunchTemplate.LaunchTemplateId") + if !launchTemplateRef.IsString() { + return ec2.LaunchTemplate{}, false + } + + resource := fctx.GetResourceByLogicalID(launchTemplateRef.AsString()) + if resource == nil { + return ec2.LaunchTemplate{}, false + } + return adaptLaunchTemplate(resource), true +} + +func findLaunchTemplateByName(fctx parser.FileContext, prop *parser.Property) *parser.Resource { + for _, res := range fctx.GetResourcesByType("AWS::EC2::LaunchTemplate") { + templateName := res.GetProperty("LaunchTemplateName") + if templateName.IsNotString() { + continue + } + + if prop.EqualTo(templateName.AsString()) { + return res + } + } + + return nil +} + +func getBlockDevices(r *parser.Resource) []*ec2.BlockDevice { + var blockDevices []*ec2.BlockDevice + + devicesProp := r.GetProperty("BlockDeviceMappings") + + if devicesProp.IsNil() { + return blockDevices + } + + for _, d := range devicesProp.AsList() { + device := &ec2.BlockDevice{ + Metadata: d.Metadata(), + Encrypted: d.GetBoolProperty("Ebs.Encrypted"), + } + + blockDevices = append(blockDevices, device) + } + + return blockDevices +} diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/launch_configuration.go b/pkg/iac/adapters/cloudformation/aws/ec2/launch_configuration.go new file mode 100644 index 000000000000..e99459b5d4f0 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ec2/launch_configuration.go @@ -0,0 +1,48 @@ +package ec2 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getLaunchConfigurations(file parser.FileContext) (launchConfigurations []ec2.LaunchConfiguration) { + launchConfigResources := file.GetResourcesByType("AWS::AutoScaling::LaunchConfiguration") + + for _, r := range launchConfigResources { + + launchConfig := ec2.LaunchConfiguration{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("LaunchConfigurationName"), + AssociatePublicIP: r.GetBoolProperty("AssociatePublicIpAddress"), + MetadataOptions: ec2.MetadataOptions{ + Metadata: r.Metadata(), + HttpTokens: types.StringDefault("optional", r.Metadata()), + HttpEndpoint: types.StringDefault("enabled", r.Metadata()), + }, + UserData: r.GetStringProperty("UserData"), + } + + if opts := r.GetProperty("MetadataOptions"); opts.IsNotNil() { + launchConfig.MetadataOptions = ec2.MetadataOptions{ + Metadata: opts.Metadata(), + HttpTokens: opts.GetStringProperty("HttpTokens", "optional"), + HttpEndpoint: opts.GetStringProperty("HttpEndpoint", "enabled"), + } + } + + blockDevices := getBlockDevices(r) + for i, device := range blockDevices { + copyDevice := device + if i == 0 { + launchConfig.RootBlockDevice = copyDevice + continue + } + launchConfig.EBSBlockDevices = append(launchConfig.EBSBlockDevices, device) + } + + launchConfigurations = append(launchConfigurations, launchConfig) + + } + return launchConfigurations +} diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/launch_template.go b/pkg/iac/adapters/cloudformation/aws/ec2/launch_template.go new file mode 100644 index 000000000000..c138ed3284e1 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ec2/launch_template.go @@ -0,0 +1,56 @@ +package ec2 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getLaunchTemplates(file parser.FileContext) (templates []ec2.LaunchTemplate) { + launchConfigResources := file.GetResourcesByType("AWS::EC2::LaunchTemplate") + + for _, r := range launchConfigResources { + templates = append(templates, adaptLaunchTemplate(r)) + } + return templates +} + +func adaptLaunchTemplate(r *parser.Resource) ec2.LaunchTemplate { + launchTemplate := ec2.LaunchTemplate{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("LaunchTemplateName", ""), + Instance: ec2.Instance{ + Metadata: r.Metadata(), + MetadataOptions: ec2.MetadataOptions{ + Metadata: r.Metadata(), + HttpTokens: types.StringDefault("optional", r.Metadata()), + HttpEndpoint: types.StringDefault("enabled", r.Metadata()), + }, + UserData: types.StringDefault("", r.Metadata()), + }, + } + + if data := r.GetProperty("LaunchTemplateData"); data.IsNotNil() { + if opts := data.GetProperty("MetadataOptions"); opts.IsNotNil() { + launchTemplate.MetadataOptions = ec2.MetadataOptions{ + Metadata: opts.Metadata(), + HttpTokens: opts.GetStringProperty("HttpTokens", "optional"), + HttpEndpoint: opts.GetStringProperty("HttpEndpoint", "enabled"), + } + } + + launchTemplate.Instance.UserData = data.GetStringProperty("UserData", "") + + blockDevices := getBlockDevices(r) + for i, device := range blockDevices { + copyDevice := device + if i == 0 { + launchTemplate.RootBlockDevice = copyDevice + } else { + launchTemplate.EBSBlockDevices = append(launchTemplate.EBSBlockDevices, device) + } + } + } + + return launchTemplate +} diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go b/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go new file mode 100644 index 000000000000..0f908f78ae08 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go @@ -0,0 +1,69 @@ +package ec2 + +import ( + "strconv" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getNetworkACLs(ctx parser.FileContext) (acls []ec2.NetworkACL) { + for _, aclResource := range ctx.GetResourcesByType("AWS::EC2::NetworkAcl") { + acl := ec2.NetworkACL{ + Metadata: aclResource.Metadata(), + Rules: getRules(aclResource.ID(), ctx), + IsDefaultRule: iacTypes.BoolDefault(false, aclResource.Metadata()), + } + acls = append(acls, acl) + } + return acls +} + +func getRules(id string, ctx parser.FileContext) (rules []ec2.NetworkACLRule) { + for _, ruleResource := range ctx.GetResourcesByType("AWS::EC2::NetworkAclEntry") { + aclID := ruleResource.GetProperty("NetworkAclId") + if aclID.IsString() && aclID.AsString() == id { + + rule := ec2.NetworkACLRule{ + Metadata: ruleResource.Metadata(), + Type: iacTypes.StringDefault(ec2.TypeIngress, ruleResource.Metadata()), + Action: iacTypes.StringDefault(ec2.ActionAllow, ruleResource.Metadata()), + Protocol: iacTypes.String("-1", ruleResource.Metadata()), + CIDRs: nil, + } + + if egressProperty := ruleResource.GetProperty("Egress"); egressProperty.IsBool() { + if egressProperty.AsBool() { + rule.Type = iacTypes.String(ec2.TypeEgress, egressProperty.Metadata()) + } else { + rule.Type = iacTypes.String(ec2.TypeIngress, egressProperty.Metadata()) + } + } + + if actionProperty := ruleResource.GetProperty("RuleAction"); actionProperty.IsString() { + if actionProperty.AsString() == ec2.ActionAllow { + rule.Action = iacTypes.String(ec2.ActionAllow, actionProperty.Metadata()) + } else { + rule.Action = iacTypes.String(ec2.ActionDeny, actionProperty.Metadata()) + } + } + + if protocolProperty := ruleResource.GetProperty("Protocol"); protocolProperty.IsInt() { + protocol := protocolProperty.AsIntValue().Value() + rule.Protocol = iacTypes.String(strconv.Itoa(protocol), protocolProperty.Metadata()) + } + + if ipv4Cidr := ruleResource.GetProperty("CidrBlock"); ipv4Cidr.IsString() { + rule.CIDRs = append(rule.CIDRs, ipv4Cidr.AsStringValue()) + } + + if ipv6Cidr := ruleResource.GetProperty("Ipv6CidrBlock"); ipv6Cidr.IsString() { + rule.CIDRs = append(rule.CIDRs, ipv6Cidr.AsStringValue()) + } + + rules = append(rules, rule) + } + } + return rules +} diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go new file mode 100644 index 000000000000..72546aa116e0 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go @@ -0,0 +1,68 @@ +package ec2 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getSecurityGroups(ctx parser.FileContext) (groups []ec2.SecurityGroup) { + for _, r := range ctx.GetResourcesByType("AWS::EC2::SecurityGroup") { + group := ec2.SecurityGroup{ + Metadata: r.Metadata(), + Description: r.GetStringProperty("GroupDescription"), + IngressRules: getIngressRules(r), + EgressRules: getEgressRules(r), + IsDefault: types.Bool(r.GetStringProperty("GroupName").EqualTo("default"), r.Metadata()), + VPCID: r.GetStringProperty("VpcId"), + } + + groups = append(groups, group) + } + return groups +} + +func getIngressRules(r *parser.Resource) (sgRules []ec2.SecurityGroupRule) { + if ingressProp := r.GetProperty("SecurityGroupIngress"); ingressProp.IsList() { + for _, ingress := range ingressProp.AsList() { + rule := ec2.SecurityGroupRule{ + Metadata: ingress.Metadata(), + Description: ingress.GetStringProperty("Description"), + CIDRs: nil, + } + v4Cidr := ingress.GetProperty("CidrIp") + if v4Cidr.IsString() && v4Cidr.AsStringValue().IsNotEmpty() { + rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v4Cidr.AsString(), v4Cidr.Metadata())) + } + v6Cidr := ingress.GetProperty("CidrIpv6") + if v6Cidr.IsString() && v6Cidr.AsStringValue().IsNotEmpty() { + rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v6Cidr.AsString(), v6Cidr.Metadata())) + } + + sgRules = append(sgRules, rule) + } + } + return sgRules +} + +func getEgressRules(r *parser.Resource) (sgRules []ec2.SecurityGroupRule) { + if egressProp := r.GetProperty("SecurityGroupEgress"); egressProp.IsList() { + for _, egress := range egressProp.AsList() { + rule := ec2.SecurityGroupRule{ + Metadata: egress.Metadata(), + Description: egress.GetStringProperty("Description"), + } + v4Cidr := egress.GetProperty("CidrIp") + if v4Cidr.IsString() && v4Cidr.AsStringValue().IsNotEmpty() { + rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v4Cidr.AsString(), v4Cidr.Metadata())) + } + v6Cidr := egress.GetProperty("CidrIpv6") + if v6Cidr.IsString() && v6Cidr.AsStringValue().IsNotEmpty() { + rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v6Cidr.AsString(), v6Cidr.Metadata())) + } + + sgRules = append(sgRules, rule) + } + } + return sgRules +} diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/subnet.go b/pkg/iac/adapters/cloudformation/aws/ec2/subnet.go new file mode 100644 index 000000000000..725a441429e1 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ec2/subnet.go @@ -0,0 +1,21 @@ +package ec2 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getSubnets(ctx parser.FileContext) (subnets []ec2.Subnet) { + + subnetResources := ctx.GetResourcesByType("AWS::EC2::Subnet") + for _, r := range subnetResources { + + subnet := ec2.Subnet{ + Metadata: r.Metadata(), + MapPublicIpOnLaunch: r.GetBoolProperty("MapPublicIpOnLaunch"), + } + + subnets = append(subnets, subnet) + } + return subnets +} diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/volume.go b/pkg/iac/adapters/cloudformation/aws/ec2/volume.go new file mode 100644 index 000000000000..35c22e053cfc --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ec2/volume.go @@ -0,0 +1,25 @@ +package ec2 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getVolumes(ctx parser.FileContext) (volumes []ec2.Volume) { + + volumeResources := ctx.GetResourcesByType("AWS::EC2::Volume") + for _, r := range volumeResources { + + volume := ec2.Volume{ + Metadata: r.Metadata(), + Encryption: ec2.Encryption{ + Metadata: r.Metadata(), + Enabled: r.GetBoolProperty("Encrypted"), + KMSKeyID: r.GetStringProperty("KmsKeyId"), + }, + } + + volumes = append(volumes, volume) + } + return volumes +} diff --git a/pkg/iac/adapters/cloudformation/aws/ecr/ecr.go b/pkg/iac/adapters/cloudformation/aws/ecr/ecr.go new file mode 100644 index 000000000000..c7552132d862 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ecr/ecr.go @@ -0,0 +1,13 @@ +package ecr + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecr" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an ECR instance +func Adapt(cfFile parser.FileContext) ecr.ECR { + return ecr.ECR{ + Repositories: getRepositories(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go b/pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go new file mode 100644 index 000000000000..cb3e4b6b4b8d --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go @@ -0,0 +1,102 @@ +package ecr + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecr" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/liamg/iamgo" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected ecr.ECR + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + +`, + expected: ecr.ECR{}, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyRepository: + Type: AWS::ECR::Repository + Properties: + RepositoryName: "test-repository" + ImageScanningConfiguration: + ScanOnPush: true + EncryptionConfiguration: + EncryptionType: KMS + KmsKey: mykey + ImageTagMutability: IMMUTABLE + RepositoryPolicyText: + Version: "2012-10-17" + Statement: + - + Sid: AllowPushPull + Effect: Allow + Principal: + AWS: + - "arn:aws:iam::123456789012:user/Alice" + Action: + - "ecr:GetDownloadUrlForLayer" + - "ecr:BatchGetImage" + `, + expected: ecr.ECR{ + Repositories: []ecr.Repository{ + { + ImageTagsImmutable: types.BoolTest(true), + ImageScanning: ecr.ImageScanning{ + ScanOnPush: types.BoolTest(true), + }, + Encryption: ecr.Encryption{ + Type: types.StringTest("KMS"), + KMSKeyID: types.StringTest("mykey"), + }, + Policies: []iam.Policy{ + { + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithSid("AllowPushPull"). + WithEffect("Allow"). + WithAWSPrincipals( + []string{"arn:aws:iam::123456789012:user/Alice"}, + ). + WithActions( + []string{ + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + }, + ). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ecr/repository.go b/pkg/iac/adapters/cloudformation/aws/ecr/repository.go new file mode 100644 index 000000000000..2c08d57a29c6 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ecr/repository.go @@ -0,0 +1,88 @@ +package ecr + +import ( + "fmt" + + "github.com/liamg/iamgo" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecr" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getRepositories(ctx parser.FileContext) (repositories []ecr.Repository) { + + repositoryResources := ctx.GetResourcesByType("AWS::ECR::Repository") + + for _, r := range repositoryResources { + + repository := ecr.Repository{ + Metadata: r.Metadata(), + ImageScanning: ecr.ImageScanning{ + Metadata: r.Metadata(), + ScanOnPush: iacTypes.BoolDefault(false, r.Metadata()), + }, + ImageTagsImmutable: hasImmutableImageTags(r), + Policies: nil, + Encryption: ecr.Encryption{ + Metadata: r.Metadata(), + Type: iacTypes.StringDefault(ecr.EncryptionTypeAES256, r.Metadata()), + KMSKeyID: iacTypes.StringDefault("", r.Metadata()), + }, + } + + if imageScanningProp := r.GetProperty("ImageScanningConfiguration"); imageScanningProp.IsNotNil() { + repository.ImageScanning = ecr.ImageScanning{ + Metadata: imageScanningProp.Metadata(), + ScanOnPush: imageScanningProp.GetBoolProperty("ScanOnPush", false), + } + } + + if encProp := r.GetProperty("EncryptionConfiguration"); encProp.IsNotNil() { + repository.Encryption = ecr.Encryption{ + Metadata: encProp.Metadata(), + Type: encProp.GetStringProperty("EncryptionType", ecr.EncryptionTypeAES256), + KMSKeyID: encProp.GetStringProperty("KmsKey", ""), + } + } + + if policy, err := getPolicy(r); err == nil { + repository.Policies = append(repository.Policies, *policy) + } + + repositories = append(repositories, repository) + } + + return repositories +} + +func getPolicy(r *parser.Resource) (*iam.Policy, error) { + policyProp := r.GetProperty("RepositoryPolicyText") + if policyProp.IsNil() { + return nil, fmt.Errorf("missing policy") + } + + parsed, err := iamgo.Parse(policyProp.GetJsonBytes()) + if err != nil { + return nil, err + } + + return &iam.Policy{ + Metadata: policyProp.Metadata(), + Name: iacTypes.StringDefault("", policyProp.Metadata()), + Document: iam.Document{ + Metadata: policyProp.Metadata(), + Parsed: *parsed, + }, + Builtin: iacTypes.Bool(false, policyProp.Metadata()), + }, nil +} + +func hasImmutableImageTags(r *parser.Resource) iacTypes.BoolValue { + mutabilityProp := r.GetProperty("ImageTagMutability") + if mutabilityProp.IsNil() { + return iacTypes.BoolDefault(false, r.Metadata()) + } + return iacTypes.Bool(mutabilityProp.EqualTo("IMMUTABLE"), mutabilityProp.Metadata()) +} diff --git a/pkg/iac/adapters/cloudformation/aws/ecs/cluster.go b/pkg/iac/adapters/cloudformation/aws/ecs/cluster.go new file mode 100644 index 000000000000..e3964076d25e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ecs/cluster.go @@ -0,0 +1,57 @@ +package ecs + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecs" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getClusters(ctx parser.FileContext) (clusters []ecs.Cluster) { + + clusterResources := ctx.GetResourcesByType("AWS::ECS::Cluster") + + for _, r := range clusterResources { + + cluster := ecs.Cluster{ + Metadata: r.Metadata(), + Settings: getClusterSettings(r), + } + + clusters = append(clusters, cluster) + + } + + return clusters +} + +func getClusterSettings(r *parser.Resource) ecs.ClusterSettings { + + clusterSettings := ecs.ClusterSettings{ + Metadata: r.Metadata(), + ContainerInsightsEnabled: types.BoolDefault(false, r.Metadata()), + } + + clusterSettingMap := r.GetProperty("ClusterSettings") + if clusterSettingMap.IsNil() || clusterSettingMap.IsNotList() { + return clusterSettings + } + + clusterSettings.Metadata = clusterSettingMap.Metadata() + + for _, setting := range clusterSettingMap.AsList() { + checkProperty(setting, &clusterSettings) + } + + return clusterSettings +} + +func checkProperty(setting *parser.Property, clusterSettings *ecs.ClusterSettings) { + settingMap := setting.AsMap() + name := settingMap["Name"] + if name.IsNotNil() && name.EqualTo("containerInsights") { + value := settingMap["Value"] + if value.IsNotNil() && value.EqualTo("enabled") { + clusterSettings.ContainerInsightsEnabled = types.Bool(true, value.Metadata()) + } + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ecs/ecs.go b/pkg/iac/adapters/cloudformation/aws/ecs/ecs.go new file mode 100644 index 000000000000..5707cdddcd8e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ecs/ecs.go @@ -0,0 +1,14 @@ +package ecs + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecs" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an ECS instance +func Adapt(cfFile parser.FileContext) ecs.ECS { + return ecs.ECS{ + Clusters: getClusters(cfFile), + TaskDefinitions: getTaskDefinitions(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ecs/ecs_test.go b/pkg/iac/adapters/cloudformation/aws/ecs/ecs_test.go new file mode 100644 index 000000000000..c6323a1df926 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ecs/ecs_test.go @@ -0,0 +1,108 @@ +package ecs + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecs" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected ecs.ECS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + ECSCluster: + Type: 'AWS::ECS::Cluster' + Properties: + ClusterName: MyFargateCluster + ClusterSettings: + - Name: containerInsights + Value: enabled + taskdefinition: + Type: AWS::ECS::TaskDefinition + Properties: + ContainerDefinitions: + - + Name: "busybox" + Image: "busybox" + Cpu: 256 + Memory: 512 + Essential: true + Privileged: true + Environment: + - Name: entryPoint + Value: 'sh, -c' + Volumes: + - + Host: + SourcePath: "/var/lib/docker/vfs/dir/" + Name: "my-vol" + EFSVolumeConfiguration: + TransitEncryption: enabled +`, + expected: ecs.ECS{ + Clusters: []ecs.Cluster{ + { + Settings: ecs.ClusterSettings{ + ContainerInsightsEnabled: types.BoolTest(true), + }, + }, + }, + TaskDefinitions: []ecs.TaskDefinition{ + { + Volumes: []ecs.Volume{ + { + EFSVolumeConfiguration: ecs.EFSVolumeConfiguration{ + TransitEncryptionEnabled: types.BoolTest(true), + }, + }, + }, + ContainerDefinitions: []ecs.ContainerDefinition{ + { + Name: types.StringTest("busybox"), + Image: types.StringTest("busybox"), + CPU: types.IntTest(256), + Memory: types.IntTest(512), + Essential: types.BoolTest(true), + Privileged: types.BoolTest(true), + Environment: []ecs.EnvVar{ + { + Name: "entryPoint", + Value: "sh, -c", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + ECSCluster: + Type: 'AWS::ECS::Cluster' + taskdefinition: + Type: AWS::ECS::TaskDefinition + `, + expected: ecs.ECS{ + Clusters: []ecs.Cluster{{}}, + TaskDefinitions: []ecs.TaskDefinition{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ecs/task_definition.go b/pkg/iac/adapters/cloudformation/aws/ecs/task_definition.go new file mode 100644 index 000000000000..9c2e342bb6f3 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ecs/task_definition.go @@ -0,0 +1,86 @@ +package ecs + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecs" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getTaskDefinitions(ctx parser.FileContext) (taskDefinitions []ecs.TaskDefinition) { + + taskDefResources := ctx.GetResourcesByType("AWS::ECS::TaskDefinition") + + for _, r := range taskDefResources { + definitions, _ := getContainerDefinitions(r) + taskDef := ecs.TaskDefinition{ + Metadata: r.Metadata(), + Volumes: getVolumes(r), + ContainerDefinitions: definitions, + } + taskDefinitions = append(taskDefinitions, taskDef) + } + + return taskDefinitions +} + +func getContainerDefinitions(r *parser.Resource) ([]ecs.ContainerDefinition, error) { + var definitions []ecs.ContainerDefinition + containerDefs := r.GetProperty("ContainerDefinitions") + if containerDefs.IsNil() || containerDefs.IsNotList() { + return definitions, nil + } + for _, containerDef := range containerDefs.AsList() { + + var envVars []ecs.EnvVar + envVarsList := containerDef.GetProperty("Environment") + if envVarsList.IsNotNil() && envVarsList.IsList() { + for _, envVar := range envVarsList.AsList() { + envVars = append(envVars, ecs.EnvVar{ + Name: envVar.GetStringProperty("Name").Value(), + Value: envVar.GetStringProperty("Value").Value(), + }) + } + } + definition := ecs.ContainerDefinition{ + Metadata: containerDef.Metadata(), + Name: containerDef.GetStringProperty("Name"), + Image: containerDef.GetStringProperty("Image"), + CPU: containerDef.GetIntProperty("Cpu"), + Memory: containerDef.GetIntProperty("Memory"), + Essential: containerDef.GetBoolProperty("Essential"), + Privileged: containerDef.GetBoolProperty("Privileged"), + Environment: envVars, + PortMappings: nil, + } + definitions = append(definitions, definition) + } + if containerDefs.IsNotNil() && containerDefs.IsString() { + return ecs.CreateDefinitionsFromString(r.Metadata(), containerDefs.AsString()) + } + return definitions, nil +} + +func getVolumes(r *parser.Resource) (volumes []ecs.Volume) { + + volumesList := r.GetProperty("Volumes") + if volumesList.IsNil() || volumesList.IsNotList() { + return volumes + } + + for _, v := range volumesList.AsList() { + volume := ecs.Volume{ + Metadata: r.Metadata(), + EFSVolumeConfiguration: ecs.EFSVolumeConfiguration{ + Metadata: r.Metadata(), + TransitEncryptionEnabled: types.BoolDefault(false, r.Metadata()), + }, + } + transitProp := v.GetProperty("EFSVolumeConfiguration.TransitEncryption") + if transitProp.IsNotNil() && transitProp.EqualTo("enabled", parser.IgnoreCase) { + volume.EFSVolumeConfiguration.TransitEncryptionEnabled = types.Bool(true, transitProp.Metadata()) + } + + volumes = append(volumes, volume) + } + return volumes +} diff --git a/pkg/iac/adapters/cloudformation/aws/efs/efs.go b/pkg/iac/adapters/cloudformation/aws/efs/efs.go new file mode 100644 index 000000000000..bda1f15ffbfb --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/efs/efs.go @@ -0,0 +1,13 @@ +package efs + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/efs" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an EFS instance +func Adapt(cfFile parser.FileContext) efs.EFS { + return efs.EFS{ + FileSystems: getFileSystems(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/efs/efs_test.go b/pkg/iac/adapters/cloudformation/aws/efs/efs_test.go new file mode 100644 index 000000000000..a22d769020b6 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/efs/efs_test.go @@ -0,0 +1,52 @@ +package efs + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/efs" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected efs.EFS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + FileSystemResource: + Type: 'AWS::EFS::FileSystem' + Properties: + Encrypted: true +`, + expected: efs.EFS{ + FileSystems: []efs.FileSystem{ + { + Encrypted: types.BoolTest(true), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + FileSystemResource: + Type: 'AWS::EFS::FileSystem' + `, + expected: efs.EFS{ + FileSystems: []efs.FileSystem{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/efs/filesystem.go b/pkg/iac/adapters/cloudformation/aws/efs/filesystem.go new file mode 100644 index 000000000000..728b09079f2a --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/efs/filesystem.go @@ -0,0 +1,23 @@ +package efs + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/efs" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getFileSystems(ctx parser.FileContext) (filesystems []efs.FileSystem) { + + filesystemResources := ctx.GetResourcesByType("AWS::EFS::FileSystem") + + for _, r := range filesystemResources { + + filesystem := efs.FileSystem{ + Metadata: r.Metadata(), + Encrypted: r.GetBoolProperty("Encrypted"), + } + + filesystems = append(filesystems, filesystem) + } + + return filesystems +} diff --git a/pkg/iac/adapters/cloudformation/aws/eks/cluster.go b/pkg/iac/adapters/cloudformation/aws/eks/cluster.go new file mode 100644 index 000000000000..c960924e33d4 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/eks/cluster.go @@ -0,0 +1,94 @@ +package eks + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/eks" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getClusters(ctx parser.FileContext) (clusters []eks.Cluster) { + + clusterResources := ctx.GetResourcesByType("AWS::EKS::Cluster") + + for _, r := range clusterResources { + cluster := eks.Cluster{ + Metadata: r.Metadata(), + Logging: getLogging(r), + Encryption: getEncryptionConfig(r), + PublicAccessEnabled: r.GetBoolProperty("ResourcesVpcConfig.EndpointPublicAccess"), + PublicAccessCIDRs: getPublicCIDRs(r), + } + + clusters = append(clusters, cluster) + } + return clusters +} + +func getPublicCIDRs(r *parser.Resource) []iacTypes.StringValue { + publicAccessCidrs := r.GetProperty("ResourcesVpcConfig.PublicAccessCidrs") + if publicAccessCidrs.IsNotList() { + return nil + } + + var cidrs []iacTypes.StringValue + for _, el := range publicAccessCidrs.AsList() { + cidrs = append(cidrs, el.AsStringValue()) + } + + return cidrs +} + +func getEncryptionConfig(r *parser.Resource) eks.Encryption { + + encryptionConfigs := r.GetProperty("EncryptionConfig") + if encryptionConfigs.IsNotList() { + return eks.Encryption{ + Metadata: r.Metadata(), + } + } + + for _, encryptionConfig := range encryptionConfigs.AsList() { + resources := encryptionConfig.GetProperty("Resources") + hasSecrets := resources.IsList() && resources.Contains("secrets") + return eks.Encryption{ + Metadata: encryptionConfig.Metadata(), + KMSKeyID: encryptionConfig.GetStringProperty("Provider.KeyArn"), + Secrets: iacTypes.Bool(hasSecrets, resources.Metadata()), + } + } + + return eks.Encryption{ + Metadata: r.Metadata(), + } +} + +func getLogging(r *parser.Resource) eks.Logging { + enabledTypes := r.GetProperty("Logging.ClusterLogging.EnabledTypes") + if enabledTypes.IsNotList() { + return eks.Logging{ + Metadata: r.Metadata(), + } + } + + logging := eks.Logging{ + Metadata: enabledTypes.Metadata(), + } + + for _, typeConf := range enabledTypes.AsList() { + switch typ := typeConf.GetProperty("Type"); typ.AsString() { + case "api": + logging.API = iacTypes.Bool(true, typ.Metadata()) + case "audit": + logging.Audit = iacTypes.Bool(true, typ.Metadata()) + case "authenticator": + logging.Authenticator = iacTypes.Bool(true, typ.Metadata()) + case "controllerManager": + logging.ControllerManager = iacTypes.Bool(true, typ.Metadata()) + case "scheduler": + logging.Scheduler = iacTypes.Bool(true, typ.Metadata()) + } + + } + + return logging +} diff --git a/pkg/iac/adapters/cloudformation/aws/eks/eks.go b/pkg/iac/adapters/cloudformation/aws/eks/eks.go new file mode 100644 index 000000000000..fe6933c19a8e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/eks/eks.go @@ -0,0 +1,13 @@ +package eks + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/eks" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an EKS instance +func Adapt(cfFile parser.FileContext) eks.EKS { + return eks.EKS{ + Clusters: getClusters(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/eks/eks_test.go b/pkg/iac/adapters/cloudformation/aws/eks/eks_test.go new file mode 100644 index 000000000000..36981f6bf544 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/eks/eks_test.go @@ -0,0 +1,81 @@ +package eks + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/eks" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected eks.EKS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + EKSCluster: + Type: AWS::EKS::Cluster + Properties: + Logging: + ClusterLogging: + EnabledTypes: + - Type: api + - Type: audit + - Type: authenticator + - Type: controllerManager + - Type: scheduler + EncryptionConfig: + - Provider: + KeyArn: alias/mykey + Resources: [secrets] + ResourcesVpcConfig: + EndpointPublicAccess: True + PublicAccessCidrs: + - 0.0.0.0/0 +`, + expected: eks.EKS{ + Clusters: []eks.Cluster{ + { + Logging: eks.Logging{ + API: types.BoolTest(true), + Audit: types.BoolTest(true), + Authenticator: types.BoolTest(true), + ControllerManager: types.BoolTest(true), + Scheduler: types.BoolTest(true), + }, + Encryption: eks.Encryption{ + KMSKeyID: types.StringTest("alias/mykey"), + Secrets: types.BoolTest(true), + }, + PublicAccessEnabled: types.BoolTest(true), + PublicAccessCIDRs: []types.StringValue{ + types.StringTest("0.0.0.0/0"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + EKSCluster: + Type: AWS::EKS::Cluster + `, + expected: eks.EKS{ + Clusters: []eks.Cluster{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/elasticache/cluster.go b/pkg/iac/adapters/cloudformation/aws/elasticache/cluster.go new file mode 100644 index 000000000000..bdbefbbbb6c2 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elasticache/cluster.go @@ -0,0 +1,24 @@ +package elasticache + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getClusterGroups(ctx parser.FileContext) (clusters []elasticache.Cluster) { + + clusterResources := ctx.GetResourcesByType("AWS::ElastiCache::CacheCluster") + + for _, r := range clusterResources { + cluster := elasticache.Cluster{ + Metadata: r.Metadata(), + Engine: r.GetStringProperty("Engine"), + NodeType: r.GetStringProperty("CacheNodeType"), + SnapshotRetentionLimit: r.GetIntProperty("SnapshotRetentionLimit"), + } + + clusters = append(clusters, cluster) + } + + return clusters +} diff --git a/pkg/iac/adapters/cloudformation/aws/elasticache/elasticache.go b/pkg/iac/adapters/cloudformation/aws/elasticache/elasticache.go new file mode 100644 index 000000000000..1d69000ff9ea --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elasticache/elasticache.go @@ -0,0 +1,15 @@ +package elasticache + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an ElasticCache instance +func Adapt(cfFile parser.FileContext) elasticache.ElastiCache { + return elasticache.ElastiCache{ + Clusters: getClusterGroups(cfFile), + ReplicationGroups: getReplicationGroups(cfFile), + SecurityGroups: getSecurityGroups(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/elasticache/elasticache_test.go b/pkg/iac/adapters/cloudformation/aws/elasticache/elasticache_test.go new file mode 100644 index 000000000000..e7e3d018b14c --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elasticache/elasticache_test.go @@ -0,0 +1,82 @@ +package elasticache + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected elasticache.ElastiCache + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + ElasticacheCluster: + Type: 'AWS::ElastiCache::CacheCluster' + Properties: + Engine: memcached + CacheNodeType: cache.t2.micro + SnapshotRetentionLimit: 5 + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + TransitEncryptionEnabled: true + AtRestEncryptionEnabled: true + mySecGroup: + Type: AWS::ElastiCache::SecurityGroup + Properties: + Description: test +`, + expected: elasticache.ElastiCache{ + Clusters: []elasticache.Cluster{ + { + Engine: types.StringTest("memcached"), + NodeType: types.StringTest("cache.t2.micro"), + SnapshotRetentionLimit: types.IntTest(5), + }, + }, + ReplicationGroups: []elasticache.ReplicationGroup{ + { + TransitEncryptionEnabled: types.BoolTest(true), + AtRestEncryptionEnabled: types.BoolTest(true), + }, + }, + SecurityGroups: []elasticache.SecurityGroup{ + { + Description: types.StringTest("test"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + ElasticacheCluster: + Type: 'AWS::ElastiCache::CacheCluster' + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + mySecGroup: + Type: AWS::ElastiCache::SecurityGroup + `, + expected: elasticache.ElastiCache{ + Clusters: []elasticache.Cluster{{}}, + ReplicationGroups: []elasticache.ReplicationGroup{{}}, + SecurityGroups: []elasticache.SecurityGroup{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/elasticache/replication_group.go b/pkg/iac/adapters/cloudformation/aws/elasticache/replication_group.go new file mode 100644 index 000000000000..95b6a7916370 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elasticache/replication_group.go @@ -0,0 +1,23 @@ +package elasticache + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getReplicationGroups(ctx parser.FileContext) (replicationGroups []elasticache.ReplicationGroup) { + + replicationGroupResources := ctx.GetResourcesByType("AWS::ElastiCache::ReplicationGroup") + + for _, r := range replicationGroupResources { + replicationGroup := elasticache.ReplicationGroup{ + Metadata: r.Metadata(), + TransitEncryptionEnabled: r.GetBoolProperty("TransitEncryptionEnabled"), + AtRestEncryptionEnabled: r.GetBoolProperty("AtRestEncryptionEnabled"), + } + + replicationGroups = append(replicationGroups, replicationGroup) + } + + return replicationGroups +} diff --git a/pkg/iac/adapters/cloudformation/aws/elasticache/security_group.go b/pkg/iac/adapters/cloudformation/aws/elasticache/security_group.go new file mode 100644 index 000000000000..8be6e68f6903 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elasticache/security_group.go @@ -0,0 +1,22 @@ +package elasticache + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getSecurityGroups(ctx parser.FileContext) (securityGroups []elasticache.SecurityGroup) { + + sgResources := ctx.GetResourcesByType("AWS::ElastiCache::SecurityGroup") + + for _, r := range sgResources { + + sg := elasticache.SecurityGroup{ + Metadata: r.Metadata(), + Description: r.GetStringProperty("Description"), + } + securityGroups = append(securityGroups, sg) + } + + return securityGroups +} diff --git a/pkg/iac/adapters/cloudformation/aws/elasticsearch/domain.go b/pkg/iac/adapters/cloudformation/aws/elasticsearch/domain.go new file mode 100644 index 000000000000..2ca77a5d7448 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elasticsearch/domain.go @@ -0,0 +1,88 @@ +package elasticsearch + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getDomains(ctx parser.FileContext) (domains []elasticsearch.Domain) { + + domainResources := ctx.GetResourcesByType("AWS::Elasticsearch::Domain", "AWS::OpenSearchService::Domain") + + for _, r := range domainResources { + + domain := elasticsearch.Domain{ + Metadata: r.Metadata(), + DomainName: r.GetStringProperty("DomainName"), + AccessPolicies: r.GetStringProperty("AccessPolicies"), + VpcId: iacTypes.String("", r.Metadata()), + LogPublishing: elasticsearch.LogPublishing{ + Metadata: r.Metadata(), + AuditEnabled: iacTypes.BoolDefault(false, r.Metadata()), + CloudWatchLogGroupArn: iacTypes.String("", r.Metadata()), + }, + TransitEncryption: elasticsearch.TransitEncryption{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + }, + AtRestEncryption: elasticsearch.AtRestEncryption{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + KmsKeyId: iacTypes.String("", r.Metadata()), + }, + Endpoint: elasticsearch.Endpoint{ + Metadata: r.Metadata(), + EnforceHTTPS: iacTypes.BoolDefault(false, r.Metadata()), + }, + ServiceSoftwareOptions: elasticsearch.ServiceSoftwareOptions{ + Metadata: r.Metadata(), + CurrentVersion: iacTypes.String("", r.Metadata()), + NewVersion: iacTypes.String("", r.Metadata()), + UpdateStatus: iacTypes.String("", r.Metadata()), + UpdateAvailable: iacTypes.Bool(false, r.Metadata()), + }, + } + + if r.Type() == "AWS::OpenSearchService::Domain" { + domain.DedicatedMasterEnabled = r.GetBoolProperty("ClusterConfig.DedicatedMasterEnabled") + } else { + domain.DedicatedMasterEnabled = r.GetBoolProperty("ElasticsearchClusterConfig.DedicatedMasterEnabled") + } + + if prop := r.GetProperty("LogPublishingOptions"); prop.IsNotNil() { + domain.LogPublishing = elasticsearch.LogPublishing{ + Metadata: prop.Metadata(), + AuditEnabled: prop.GetBoolProperty("AUDIT_LOGS.Enabled"), + CloudWatchLogGroupArn: prop.GetStringProperty("AUDIT_LOGS.CloudWatchLogsLogGroupArn"), + } + } + + if prop := r.GetProperty("NodeToNodeEncryptionOptions"); prop.IsNotNil() { + domain.TransitEncryption = elasticsearch.TransitEncryption{ + Metadata: prop.Metadata(), + Enabled: prop.GetBoolProperty("Enabled"), + } + } + + if prop := r.GetProperty("EncryptionAtRestOptions"); prop.IsNotNil() { + domain.AtRestEncryption = elasticsearch.AtRestEncryption{ + Metadata: prop.Metadata(), + Enabled: prop.GetBoolProperty("Enabled"), + KmsKeyId: prop.GetStringProperty("KmsKeyId"), + } + } + + if prop := r.GetProperty("DomainEndpointOptions"); prop.IsNotNil() { + domain.Endpoint = elasticsearch.Endpoint{ + Metadata: prop.Metadata(), + EnforceHTTPS: prop.GetBoolProperty("EnforceHTTPS"), + TLSPolicy: prop.GetStringProperty("TLSSecurityPolicy"), + } + } + + domains = append(domains, domain) + } + + return domains +} diff --git a/pkg/iac/adapters/cloudformation/aws/elasticsearch/elasticsearch.go b/pkg/iac/adapters/cloudformation/aws/elasticsearch/elasticsearch.go new file mode 100644 index 000000000000..b3e230b0435b --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elasticsearch/elasticsearch.go @@ -0,0 +1,13 @@ +package elasticsearch + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an ElasticSearch instance +func Adapt(cfFile parser.FileContext) elasticsearch.Elasticsearch { + return elasticsearch.Elasticsearch{ + Domains: getDomains(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/elasticsearch/elasticsearch_test.go b/pkg/iac/adapters/cloudformation/aws/elasticsearch/elasticsearch_test.go new file mode 100644 index 000000000000..afb9c3a81e22 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elasticsearch/elasticsearch_test.go @@ -0,0 +1,109 @@ +package elasticsearch + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected elasticsearch.Elasticsearch + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + OpenSearchServiceDomain: + Type: AWS::OpenSearchService::Domain + Properties: + DomainName: 'test' + ClusterConfig: + DedicatedMasterEnabled: true + NodeToNodeEncryptionOptions: + Enabled: true + EncryptionAtRestOptions: + Enabled: true + KmsKeyId: mykey + DomainEndpointOptions: + EnforceHTTPS: true + TLSSecurityPolicy: Policy-Min-TLS-1-0-2019-07 + AccessPolicies: + Version: '2012-10-17' + Statement: + - + Effect: 'Allow' + Principal: + AWS: 'arn:aws:iam::123456789012:user/opensearch-user' + Action: 'es:*' + Resource: 'arn:aws:es:us-east-1:846973539254:domain/test/*' + LogPublishingOptions: + AUDIT_LOGS: + CloudWatchLogsLogGroupArn: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/opensearch/domains/opensearch-application-logs' + Enabled: true +`, + expected: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + DomainName: types.StringTest("test"), + DedicatedMasterEnabled: types.BoolTest(true), + LogPublishing: elasticsearch.LogPublishing{ + AuditEnabled: types.BoolTest(true), + CloudWatchLogGroupArn: types.StringTest("arn:aws:logs:us-east-1:123456789012:log-group:/aws/opensearch/domains/opensearch-application-logs"), + }, + TransitEncryption: elasticsearch.TransitEncryption{ + Enabled: types.BoolTest(true), + }, + AtRestEncryption: elasticsearch.AtRestEncryption{ + Enabled: types.BoolTest(true), + KmsKeyId: types.StringTest("mykey"), + }, + Endpoint: elasticsearch.Endpoint{ + EnforceHTTPS: types.BoolTest(true), + TLSPolicy: types.StringTest("Policy-Min-TLS-1-0-2019-07"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + OpenSearchServiceDomain: + Type: AWS::OpenSearchService::Domain + `, + expected: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{{}}, + }, + }, + { + name: "Elasticsearch", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + ElasticsearchDomain: + Type: AWS::Elasticsearch::Domain + Properties: + ElasticsearchClusterConfig: + DedicatedMasterEnabled: true + `, + expected: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + DedicatedMasterEnabled: types.BoolTest(true), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/elb/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/elb/adapt_test.go new file mode 100644 index 000000000000..2c5ee494e66d --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elb/adapt_test.go @@ -0,0 +1,85 @@ +package elb + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected elb.ELB + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: "2010-09-09" +Resources: + LoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + DependsOn: + - ALBLogsBucketPermission + Properties: + Name: "k8s-dev" + Scheme: internal + IpAddressType: ipv4 + LoadBalancerAttributes: + - Key: routing.http2.enabled + Value: "true" + - Key: deletion_protection.enabled + Value: "true" + - Key: routing.http.drop_invalid_header_fields.enabled + Value: "true" + - Key: access_logs.s3.enabled + Value: "true" + Tags: + - Key: ingress.k8s.aws/resource + Value: LoadBalancer + - Key: elbv2.k8s.aws/cluster + Value: "biomage-dev" + Type: application + Listener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - Type: 'redirect' + RedirectConfig: + Port: 443 + Protocol: HTTPS + StatusCode: HTTP_302 + LoadBalancerArn: !Ref LoadBalancer + Protocol: HTTPS + SslPolicy: "ELBSecurityPolicy-TLS-1-2-2017-01" +`, + expected: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Type: types.StringTest("application"), + DropInvalidHeaderFields: types.BoolTest(true), + Internal: types.Bool(true, types.NewTestMetadata()), + Listeners: []elb.Listener{ + { + Protocol: types.StringTest("HTTPS"), + TLSPolicy: types.StringTest("ELBSecurityPolicy-TLS-1-2-2017-01"), + DefaultActions: []elb.Action{ + { + Type: types.StringTest("redirect"), + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/elb/elb.go b/pkg/iac/adapters/cloudformation/aws/elb/elb.go new file mode 100644 index 000000000000..2ff1d2c74777 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elb/elb.go @@ -0,0 +1,13 @@ +package elb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an ELB instance +func Adapt(cfFile parser.FileContext) elb.ELB { + return elb.ELB{ + LoadBalancers: getLoadBalancers(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/elb/loadbalancer.go b/pkg/iac/adapters/cloudformation/aws/elb/loadbalancer.go new file mode 100644 index 000000000000..002b6487ba43 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elb/loadbalancer.go @@ -0,0 +1,81 @@ +package elb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getLoadBalancers(ctx parser.FileContext) (loadbalancers []elb.LoadBalancer) { + + loadBalanacerResources := ctx.GetResourcesByType("AWS::ElasticLoadBalancingV2::LoadBalancer") + + for _, r := range loadBalanacerResources { + lb := elb.LoadBalancer{ + Metadata: r.Metadata(), + Type: r.GetStringProperty("Type", "application"), + DropInvalidHeaderFields: checkForDropInvalidHeaders(r), + Internal: isInternal(r), + Listeners: getListeners(r, ctx), + } + loadbalancers = append(loadbalancers, lb) + } + + return loadbalancers +} + +func getListeners(lbr *parser.Resource, ctx parser.FileContext) (listeners []elb.Listener) { + + listenerResources := ctx.GetResourcesByType("AWS::ElasticLoadBalancingV2::Listener") + + for _, r := range listenerResources { + if r.GetStringProperty("LoadBalancerArn").Value() == lbr.ID() { + listener := elb.Listener{ + Metadata: r.Metadata(), + Protocol: r.GetStringProperty("Protocol", "HTTP"), + TLSPolicy: r.GetStringProperty("SslPolicy", ""), + DefaultActions: getDefaultListenerActions(r), + } + + listeners = append(listeners, listener) + } + } + return listeners +} + +func getDefaultListenerActions(r *parser.Resource) (actions []elb.Action) { + defaultActionsProp := r.GetProperty("DefaultActions") + if defaultActionsProp.IsNotList() { + return actions + } + for _, action := range defaultActionsProp.AsList() { + actions = append(actions, elb.Action{ + Metadata: action.Metadata(), + Type: action.GetProperty("Type").AsStringValue(), + }) + } + return actions +} + +func isInternal(r *parser.Resource) types.BoolValue { + schemeProp := r.GetProperty("Scheme") + if schemeProp.IsNotString() { + return r.BoolDefault(false) + } + return types.Bool(schemeProp.EqualTo("internal", parser.IgnoreCase), schemeProp.Metadata()) +} + +func checkForDropInvalidHeaders(r *parser.Resource) types.BoolValue { + attributesProp := r.GetProperty("LoadBalancerAttributes") + if attributesProp.IsNotList() { + return types.BoolDefault(false, r.Metadata()) + } + + for _, attr := range attributesProp.AsList() { + if attr.GetStringProperty("Key").Value() == "routing.http.drop_invalid_header_fields.enabled" { + return attr.GetBoolProperty("Value") + } + } + + return r.BoolDefault(false) +} diff --git a/pkg/iac/adapters/cloudformation/aws/iam/iam.go b/pkg/iac/adapters/cloudformation/aws/iam/iam.go new file mode 100644 index 000000000000..d3aab1646534 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/iam/iam.go @@ -0,0 +1,27 @@ +package iam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +// Adapt adapts an IAM instance +func Adapt(cfFile parser.FileContext) iam.IAM { + return iam.IAM{ + PasswordPolicy: iam.PasswordPolicy{ + Metadata: iacTypes.NewUnmanagedMetadata(), + ReusePreventionCount: iacTypes.IntDefault(0, iacTypes.NewUnmanagedMetadata()), + RequireLowercase: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + RequireUppercase: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + RequireNumbers: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + RequireSymbols: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + MaxAgeDays: iacTypes.IntDefault(0, iacTypes.NewUnmanagedMetadata()), + MinimumLength: iacTypes.IntDefault(0, iacTypes.NewUnmanagedMetadata()), + }, + Policies: getPolicies(cfFile), + Groups: getGroups(cfFile), + Users: getUsers(cfFile), + Roles: getRoles(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/iam/iam_test.go b/pkg/iac/adapters/cloudformation/aws/iam/iam_test.go new file mode 100644 index 000000000000..3e548dec0cf7 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/iam/iam_test.go @@ -0,0 +1,189 @@ +package iam + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/liamg/iamgo" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected iam.IAM + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + myIAMPolicy: + Type: 'AWS::IAM::Policy' + Properties: + PolicyName: TestPolicy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - 'cloudformation:Describe*' + Resource: '*' + Groups: + - !Ref MyGroup + Users: + - !Ref PublishUser + Roles: + - !Ref MyRole + MyGroup: + Type: AWS::IAM::Group + Properties: + GroupName: TestGroup + Policies: + - PolicyName: TestGroupPolicy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Resource: arn:*:cloudfront::*:distribution/* + Action: + - cloudfront:CreateDistribution + MyUser: + Type: AWS::IAM::User + Properties: + UserName: TestUser + Policies: + - PolicyName: TestUserPolicy + PolicyDocument: + Statement: + - Action: 's3:*' + Effect: Allow + Resource: + - 'arn:aws:s3:::testbucket' + MyRole: + Type: 'AWS::IAM::Role' + Properties: + RoleName: TestRole + Policies: + - PolicyName: TestRolePolicy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - 'sts:AssumeRole' + AccessKey: + Type: AWS::IAM::AccessKey + Properties: + UserName: !Ref MyUser + Status: Active +`, + expected: iam.IAM{ + Policies: []iam.Policy{ + { + Name: types.StringTest("TestPolicy"), + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"cloudformation:Describe*"}). + WithResources([]string{"*"}). + Build(), + ). + Build(), + } + }(), + }, + }, + Users: []iam.User{ + { + Name: types.StringTest("TestUser"), + Policies: []iam.Policy{ + { + Name: types.StringTest("TestUserPolicy"), + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"s3:*"}). + WithResources([]string{"arn:aws:s3:::testbucket"}). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + Groups: []iam.Group{ + { + Name: types.StringTest("TestGroup"), + Policies: []iam.Policy{ + { + Name: types.StringTest("TestGroupPolicy"), + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"cloudfront:CreateDistribution"}). + WithResources([]string{"arn:*:cloudfront::*:distribution/*"}). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + Roles: []iam.Role{ + { + Name: types.StringTest("TestRole"), + Policies: []iam.Policy{ + { + Name: types.StringTest("TestRolePolicy"), + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"sts:AssumeRole"}). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + + `, + expected: iam.IAM{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/iam/policy.go b/pkg/iac/adapters/cloudformation/aws/iam/policy.go new file mode 100644 index 000000000000..f83771f882d2 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/iam/policy.go @@ -0,0 +1,127 @@ +package iam + +import ( + "github.com/liamg/iamgo" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getPolicies(ctx parser.FileContext) (policies []iam.Policy) { + for _, policyResource := range ctx.GetResourcesByType("AWS::IAM::Policy") { + + policy := iam.Policy{ + Metadata: policyResource.Metadata(), + Name: policyResource.GetStringProperty("PolicyName"), + Document: iam.Document{ + Metadata: policyResource.Metadata(), + Parsed: iamgo.Document{}, + }, + Builtin: iacTypes.Bool(false, policyResource.Metadata()), + } + + if policyProp := policyResource.GetProperty("PolicyDocument"); policyProp.IsNotNil() { + doc, err := iamgo.Parse(policyProp.GetJsonBytes()) + if err != nil { + continue + } + policy.Document.Parsed = *doc + } + + policies = append(policies, policy) + } + return policies +} + +func getRoles(ctx parser.FileContext) (roles []iam.Role) { + for _, roleResource := range ctx.GetResourcesByType("AWS::IAM::Role") { + policyProp := roleResource.GetProperty("Policies") + roleName := roleResource.GetStringProperty("RoleName") + + roles = append(roles, iam.Role{ + Metadata: roleResource.Metadata(), + Name: roleName, + Policies: getPoliciesDocs(policyProp), + }) + } + return roles +} + +func getUsers(ctx parser.FileContext) (users []iam.User) { + for _, userResource := range ctx.GetResourcesByType("AWS::IAM::User") { + policyProp := userResource.GetProperty("Policies") + userName := userResource.GetStringProperty("UserName") + + users = append(users, iam.User{ + Metadata: userResource.Metadata(), + Name: userName, + LastAccess: iacTypes.TimeUnresolvable(userResource.Metadata()), + Policies: getPoliciesDocs(policyProp), + AccessKeys: getAccessKeys(ctx, userName.Value()), + }) + } + return users +} + +func getAccessKeys(ctx parser.FileContext, username string) (accessKeys []iam.AccessKey) { + // TODO: also search for a key by the logical id of the resource + for _, keyResource := range ctx.GetResourcesByType("AWS::IAM::AccessKey") { + keyUsername := keyResource.GetStringProperty("UserName") + if !keyUsername.EqualTo(username) { + continue + } + active := iacTypes.BoolDefault(false, keyResource.Metadata()) + if statusProp := keyResource.GetProperty("Status"); statusProp.IsString() { + active = iacTypes.Bool(statusProp.AsString() == "Active", statusProp.Metadata()) + } + + accessKeys = append(accessKeys, iam.AccessKey{ + Metadata: keyResource.Metadata(), + AccessKeyId: iacTypes.StringUnresolvable(keyResource.Metadata()), + CreationDate: iacTypes.TimeUnresolvable(keyResource.Metadata()), + LastAccess: iacTypes.TimeUnresolvable(keyResource.Metadata()), + Active: active, + }) + } + return accessKeys +} + +func getGroups(ctx parser.FileContext) (groups []iam.Group) { + for _, groupResource := range ctx.GetResourcesByType("AWS::IAM::Group") { + policyProp := groupResource.GetProperty("Policies") + groupName := groupResource.GetStringProperty("GroupName") + + groups = append(groups, iam.Group{ + Metadata: groupResource.Metadata(), + Name: groupName, + Policies: getPoliciesDocs(policyProp), + }) + } + return groups +} + +func getPoliciesDocs(policiesProp *parser.Property) []iam.Policy { + var policies []iam.Policy + + for _, policy := range policiesProp.AsList() { + policyProp := policy.GetProperty("PolicyDocument") + policyName := policy.GetStringProperty("PolicyName") + + doc, err := iamgo.Parse(policyProp.GetJsonBytes()) + if err != nil { + continue + } + + policies = append(policies, iam.Policy{ + Metadata: policyProp.Metadata(), + Name: policyName, + Document: iam.Document{ + Metadata: policyProp.Metadata(), + Parsed: *doc, + }, + Builtin: iacTypes.Bool(false, policyProp.Metadata()), + }) + } + return policies +} diff --git a/pkg/iac/adapters/cloudformation/aws/kinesis/kinesis.go b/pkg/iac/adapters/cloudformation/aws/kinesis/kinesis.go new file mode 100644 index 000000000000..c48ad26046bf --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/kinesis/kinesis.go @@ -0,0 +1,13 @@ +package kinesis + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/kinesis" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a Kinesis instance +func Adapt(cfFile parser.FileContext) kinesis.Kinesis { + return kinesis.Kinesis{ + Streams: getStreams(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/kinesis/kinesis_test.go b/pkg/iac/adapters/cloudformation/aws/kinesis/kinesis_test.go new file mode 100644 index 000000000000..ce38afadf806 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/kinesis/kinesis_test.go @@ -0,0 +1,57 @@ +package kinesis + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/kinesis" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected kinesis.Kinesis + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MyStream: + Type: 'AWS::Kinesis::Stream' + Properties: + StreamEncryption: + EncryptionType: KMS + KeyId: key +`, + expected: kinesis.Kinesis{ + Streams: []kinesis.Stream{ + { + Encryption: kinesis.Encryption{ + Type: types.StringTest("KMS"), + KMSKeyID: types.StringTest("key"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyStream: + Type: 'AWS::Kinesis::Stream' + `, + expected: kinesis.Kinesis{ + Streams: []kinesis.Stream{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/kinesis/stream.go b/pkg/iac/adapters/cloudformation/aws/kinesis/stream.go new file mode 100644 index 000000000000..6c10ec6134c0 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/kinesis/stream.go @@ -0,0 +1,30 @@ +package kinesis + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/kinesis" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getStreams(ctx parser.FileContext) (streams []kinesis.Stream) { + + streamResources := ctx.GetResourcesByType("AWS::Kinesis::Stream") + + for _, r := range streamResources { + + stream := kinesis.Stream{ + Metadata: r.Metadata(), + } + + if prop := r.GetProperty("StreamEncryption"); prop.IsNotNil() { + stream.Encryption = kinesis.Encryption{ + Metadata: prop.Metadata(), + Type: prop.GetStringProperty("EncryptionType", "KMS"), + KMSKeyID: prop.GetStringProperty("KeyId"), + } + } + + streams = append(streams, stream) + } + + return streams +} diff --git a/pkg/iac/adapters/cloudformation/aws/lambda/function.go b/pkg/iac/adapters/cloudformation/aws/lambda/function.go new file mode 100644 index 000000000000..f91a565d193a --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/lambda/function.go @@ -0,0 +1,48 @@ +package lambda + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/lambda" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getFunctions(ctx parser.FileContext) (functions []lambda.Function) { + + functionResources := ctx.GetResourcesByType("AWS::Lambda::Function") + + for _, r := range functionResources { + + function := lambda.Function{ + Metadata: r.Metadata(), + Permissions: getPermissions(r, ctx), + } + + if prop := r.GetProperty("TracingConfig"); prop.IsNotNil() { + function.Tracing = lambda.Tracing{ + Metadata: prop.Metadata(), + Mode: prop.GetStringProperty("Mode"), + } + } + + functions = append(functions, function) + } + + return functions +} + +func getPermissions(funcR *parser.Resource, ctx parser.FileContext) (perms []lambda.Permission) { + + permissionResources := ctx.GetResourcesByType("AWS::Lambda::Permission") + + for _, r := range permissionResources { + if prop := r.GetStringProperty("FunctionName"); prop.EqualTo(funcR.ID()) { + perm := lambda.Permission{ + Metadata: r.Metadata(), + Principal: r.GetStringProperty("Principal"), + SourceARN: r.GetStringProperty("SourceArn"), + } + perms = append(perms, perm) + } + } + + return perms +} diff --git a/pkg/iac/adapters/cloudformation/aws/lambda/lambda.go b/pkg/iac/adapters/cloudformation/aws/lambda/lambda.go new file mode 100644 index 000000000000..f89710d47e1e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/lambda/lambda.go @@ -0,0 +1,13 @@ +package lambda + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/lambda" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a lambda instance +func Adapt(cfFile parser.FileContext) lambda.Lambda { + return lambda.Lambda{ + Functions: getFunctions(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/lambda/lambda_test.go b/pkg/iac/adapters/cloudformation/aws/lambda/lambda_test.go new file mode 100644 index 000000000000..4262181f89ee --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/lambda/lambda_test.go @@ -0,0 +1,76 @@ +package lambda + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/lambda" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected lambda.Lambda + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + lambdaFunction: + Type: AWS::Lambda::Function + Properties: + TracingConfig: + Mode: Active + permission: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref lambdaFunction + Action: lambda:InvokeFunction + Principal: s3.amazonaws.com + SourceArn: arn +`, + expected: lambda.Lambda{ + Functions: []lambda.Function{ + { + Tracing: lambda.Tracing{ + Mode: types.StringTest("Active"), + }, + Permissions: []lambda.Permission{ + { + Principal: types.StringTest("s3.amazonaws.com"), + SourceARN: types.StringTest("arn"), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + lambdaFunction: + Type: AWS::Lambda::Function + permission: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref lambdaFunction + `, + expected: lambda.Lambda{ + Functions: []lambda.Function{ + { + Permissions: []lambda.Permission{{}}, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/mq/broker.go b/pkg/iac/adapters/cloudformation/aws/mq/broker.go new file mode 100644 index 000000000000..9e52023c11da --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/mq/broker.go @@ -0,0 +1,33 @@ +package mq + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/mq" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getBrokers(ctx parser.FileContext) (brokers []mq.Broker) { + for _, r := range ctx.GetResourcesByType("AWS::AmazonMQ::Broker") { + + broker := mq.Broker{ + Metadata: r.Metadata(), + PublicAccess: r.GetBoolProperty("PubliclyAccessible"), + Logging: mq.Logging{ + Metadata: r.Metadata(), + General: types.BoolDefault(false, r.Metadata()), + Audit: types.BoolDefault(false, r.Metadata()), + }, + } + + if prop := r.GetProperty("Logs"); prop.IsNotNil() { + broker.Logging = mq.Logging{ + Metadata: prop.Metadata(), + General: prop.GetBoolProperty("General"), + Audit: prop.GetBoolProperty("Audit"), + } + } + + brokers = append(brokers, broker) + } + return brokers +} diff --git a/pkg/iac/adapters/cloudformation/aws/mq/mq.go b/pkg/iac/adapters/cloudformation/aws/mq/mq.go new file mode 100644 index 000000000000..744e86f69a11 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/mq/mq.go @@ -0,0 +1,13 @@ +package mq + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/mq" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an MQ instance +func Adapt(cfFile parser.FileContext) mq.MQ { + return mq.MQ{ + Brokers: getBrokers(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/mq/mq_test.go b/pkg/iac/adapters/cloudformation/aws/mq/mq_test.go new file mode 100644 index 000000000000..b4f1d5048898 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/mq/mq_test.go @@ -0,0 +1,59 @@ +package mq + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/mq" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected mq.MQ + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + BasicBroker: + Type: "AWS::AmazonMQ::Broker" + Properties: + PubliclyAccessible: true + Logs: + Audit: true + General: true +`, + expected: mq.MQ{ + Brokers: []mq.Broker{ + { + PublicAccess: types.BoolTest(true), + Logging: mq.Logging{ + Audit: types.BoolTest(true), + General: types.BoolTest(true), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + BasicBroker: + Type: "AWS::AmazonMQ::Broker" + `, + expected: mq.MQ{ + Brokers: []mq.Broker{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/msk/cluster.go b/pkg/iac/adapters/cloudformation/aws/msk/cluster.go new file mode 100644 index 000000000000..c55d5745e22b --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/msk/cluster.go @@ -0,0 +1,80 @@ +package msk + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/msk" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getClusters(ctx parser.FileContext) (clusters []msk.Cluster) { + for _, r := range ctx.GetResourcesByType("AWS::MSK::Cluster") { + + cluster := msk.Cluster{ + Metadata: r.Metadata(), + EncryptionInTransit: msk.EncryptionInTransit{ + Metadata: r.Metadata(), + ClientBroker: iacTypes.StringDefault("TLS", r.Metadata()), + }, + EncryptionAtRest: msk.EncryptionAtRest{ + Metadata: r.Metadata(), + KMSKeyARN: iacTypes.StringDefault("", r.Metadata()), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + }, + Logging: msk.Logging{ + Metadata: r.Metadata(), + Broker: msk.BrokerLogging{ + Metadata: r.Metadata(), + S3: msk.S3Logging{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + }, + Cloudwatch: msk.CloudwatchLogging{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + }, + Firehose: msk.FirehoseLogging{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + }, + }, + }, + } + + if encProp := r.GetProperty("EncryptionInfo.EncryptionInTransit"); encProp.IsNotNil() { + cluster.EncryptionInTransit = msk.EncryptionInTransit{ + Metadata: encProp.Metadata(), + ClientBroker: encProp.GetStringProperty("ClientBroker", "TLS"), + } + } + + if encAtRestProp := r.GetProperty("EncryptionInfo.EncryptionAtRest"); encAtRestProp.IsNotNil() { + cluster.EncryptionAtRest = msk.EncryptionAtRest{ + Metadata: encAtRestProp.Metadata(), + KMSKeyARN: encAtRestProp.GetStringProperty("DataVolumeKMSKeyId", ""), + Enabled: iacTypes.BoolDefault(true, encAtRestProp.Metadata()), + } + } + + if loggingProp := r.GetProperty("LoggingInfo"); loggingProp.IsNotNil() { + cluster.Logging.Metadata = loggingProp.Metadata() + if brokerLoggingProp := loggingProp.GetProperty("BrokerLogs"); brokerLoggingProp.IsNotNil() { + cluster.Logging.Broker.Metadata = brokerLoggingProp.Metadata() + if s3Prop := brokerLoggingProp.GetProperty("S3"); s3Prop.IsNotNil() { + cluster.Logging.Broker.S3.Metadata = s3Prop.Metadata() + cluster.Logging.Broker.S3.Enabled = s3Prop.GetBoolProperty("Enabled", false) + } + if cwProp := brokerLoggingProp.GetProperty("CloudWatchLogs"); cwProp.IsNotNil() { + cluster.Logging.Broker.Cloudwatch.Metadata = cwProp.Metadata() + cluster.Logging.Broker.Cloudwatch.Enabled = cwProp.GetBoolProperty("Enabled", false) + } + if fhProp := brokerLoggingProp.GetProperty("Firehose"); fhProp.IsNotNil() { + cluster.Logging.Broker.Firehose.Metadata = fhProp.Metadata() + cluster.Logging.Broker.Firehose.Enabled = fhProp.GetBoolProperty("Enabled", false) + } + } + } + + clusters = append(clusters, cluster) + } + return clusters +} diff --git a/pkg/iac/adapters/cloudformation/aws/msk/msk.go b/pkg/iac/adapters/cloudformation/aws/msk/msk.go new file mode 100644 index 000000000000..76d7964e17a7 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/msk/msk.go @@ -0,0 +1,13 @@ +package msk + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/msk" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an MSK instance +func Adapt(cfFile parser.FileContext) msk.MSK { + return msk.MSK{ + Clusters: getClusters(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/msk/msk_test.go b/pkg/iac/adapters/cloudformation/aws/msk/msk_test.go new file mode 100644 index 000000000000..2cc5a6cd0945 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/msk/msk_test.go @@ -0,0 +1,87 @@ +package msk + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/msk" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected msk.MSK + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + cluster: + Type: AWS::MSK::Cluster + Properties: + EncryptionInfo: + EncryptionInTransit: + ClientBroker: 'PLAINTEXT' + EncryptionAtRest: + DataVolumeKMSKeyId: key + LoggingInfo: + BrokerLogs: + S3: + Enabled: true + CloudWatchLogs: + Enabled: true + Firehose: + Enabled: true +`, + expected: msk.MSK{ + Clusters: []msk.Cluster{ + { + EncryptionInTransit: msk.EncryptionInTransit{ + ClientBroker: types.StringTest("PLAINTEXT"), + }, + EncryptionAtRest: msk.EncryptionAtRest{ + KMSKeyARN: types.StringTest("key"), + Enabled: types.BoolTest(true), + }, + Logging: msk.Logging{ + Broker: msk.BrokerLogging{ + S3: msk.S3Logging{ + Enabled: types.BoolTest(true), + }, + Firehose: msk.FirehoseLogging{ + Enabled: types.BoolTest(true), + }, + Cloudwatch: msk.CloudwatchLogging{ + Enabled: types.BoolTest(true), + }, + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + cluster: + Type: AWS::MSK::Cluster + `, + expected: msk.MSK{ + Clusters: []msk.Cluster{{ + EncryptionInTransit: msk.EncryptionInTransit{ + ClientBroker: types.StringTest("TLS"), + }, + }}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/neptune/cluster.go b/pkg/iac/adapters/cloudformation/aws/neptune/cluster.go new file mode 100644 index 000000000000..33012cbd2236 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/neptune/cluster.go @@ -0,0 +1,34 @@ +package neptune + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/neptune" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getClusters(ctx parser.FileContext) (clusters []neptune.Cluster) { + for _, r := range ctx.GetResourcesByType("AWS::Neptune::DBCluster") { + + cluster := neptune.Cluster{ + Metadata: r.Metadata(), + Logging: neptune.Logging{ + Metadata: r.Metadata(), + Audit: getAuditLog(r), + }, + StorageEncrypted: r.GetBoolProperty("StorageEncrypted"), + KMSKeyID: r.GetStringProperty("KmsKeyId"), + } + clusters = append(clusters, cluster) + } + return clusters +} + +func getAuditLog(r *parser.Resource) types.BoolValue { + if logsProp := r.GetProperty("EnableCloudwatchLogsExports"); logsProp.IsList() { + if logsProp.Contains("audit") { + return types.Bool(true, logsProp.Metadata()) + } + } + + return types.BoolDefault(false, r.Metadata()) +} diff --git a/pkg/iac/adapters/cloudformation/aws/neptune/neptune.go b/pkg/iac/adapters/cloudformation/aws/neptune/neptune.go new file mode 100644 index 000000000000..2d63e6f45f69 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/neptune/neptune.go @@ -0,0 +1,13 @@ +package neptune + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/neptune" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a Neptune instance +func Adapt(cfFile parser.FileContext) neptune.Neptune { + return neptune.Neptune{ + Clusters: getClusters(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/neptune/neptune_test.go b/pkg/iac/adapters/cloudformation/aws/neptune/neptune_test.go new file mode 100644 index 000000000000..8e63a481ff2e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/neptune/neptune_test.go @@ -0,0 +1,59 @@ +package neptune + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/neptune" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected neptune.Neptune + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + cluster: + Type: AWS::Neptune::DBCluster + Properties: + StorageEncrypted: true + KmsKeyId: key + EnableCloudwatchLogsExports: + - audit +`, + expected: neptune.Neptune{ + Clusters: []neptune.Cluster{ + { + StorageEncrypted: types.BoolTest(true), + KMSKeyID: types.StringTest("key"), + Logging: neptune.Logging{ + Audit: types.BoolTest(true), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + cluster: + Type: AWS::Neptune::DBCluster + `, + expected: neptune.Neptune{ + Clusters: []neptune.Cluster{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/rds/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/rds/adapt_test.go new file mode 100644 index 000000000000..4875395c8506 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/rds/adapt_test.go @@ -0,0 +1,175 @@ +package rds + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected rds.RDS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + RDSCluster: + Type: 'AWS::RDS::DBCluster' + Properties: + DBClusterIdentifier: my-cluster1 + Engine: aurora-postgresql + StorageEncrypted: true + KmsKeyId: "your-kms-key-id" + PerformanceInsightsEnabled: true + PerformanceInsightsKmsKeyId: "test-kms-key-id" + PublicAccess: true + DeletionProtection: true + BackupRetentionPeriod: 2 + RDSDBInstance1: + Type: 'AWS::RDS::DBInstance' + Properties: + Engine: aurora-mysql + EngineVersion: "5.7.12" + DBInstanceIdentifier: test + DBClusterIdentifier: + Ref: RDSCluster + PubliclyAccessible: 'false' + DBInstanceClass: db.r3.xlarge + StorageEncrypted: true + KmsKeyId: "your-kms-key-id" + EnablePerformanceInsights: true + PerformanceInsightsKMSKeyId: "test-kms-key-id2" + MultiAZ: true + AutoMinorVersionUpgrade: true + DBInstanceArn: "arn:aws:rds:us-east-2:123456789012:db:my-mysql-instance-1" + EnableIAMDatabaseAuthentication: true + EnableCloudwatchLogsExports: + - "error" + - "general" + DBParameterGroupName: "testgroup" + Tags: + - Key: "keyname1" + Value: "value1" + - Key: "keyname2" + Value: "value2" + RDSDBParameterGroup: + Type: 'AWS::RDS::DBParameterGroup' + Properties: + Description: "CloudFormation Sample MySQL Parameter Group" + DBParameterGroupName: "testgroup" + Parameters: + sql_mode: IGNORE_SPACE + DbSecurityByEC2SecurityGroup: + Type: AWS::RDS::DBSecurityGroup + Properties: + GroupDescription: "Ingress for Amazon EC2 security group" +`, + expected: rds.RDS{ + Classic: rds.Classic{ + DBSecurityGroups: []rds.DBSecurityGroup{{}}, + }, + ParameterGroups: []rds.ParameterGroups{ + { + DBParameterGroupName: types.StringTest("testgroup"), + }, + }, + Clusters: []rds.Cluster{ + { + BackupRetentionPeriodDays: types.IntTest(2), + Engine: types.StringTest("aurora-postgresql"), + Encryption: rds.Encryption{ + EncryptStorage: types.BoolTest(true), + KMSKeyID: types.StringTest("your-kms-key-id"), + }, + PerformanceInsights: rds.PerformanceInsights{ + Enabled: types.BoolTest(true), + KMSKeyID: types.StringTest("test-kms-key-id"), + }, + PublicAccess: types.BoolTest(false), + DeletionProtection: types.BoolTest(true), + Instances: []rds.ClusterInstance{ + { + Instance: rds.Instance{ + StorageEncrypted: types.BoolTest(true), + Encryption: rds.Encryption{ + EncryptStorage: types.BoolTest(true), + KMSKeyID: types.StringTest("your-kms-key-id"), + }, + DBInstanceIdentifier: types.StringTest("test"), + PubliclyAccessible: types.BoolTest(false), + PublicAccess: types.BoolTest(false), + BackupRetentionPeriodDays: types.IntTest(1), + Engine: types.StringTest("aurora-mysql"), + EngineVersion: types.StringTest("5.7.12"), + MultiAZ: types.BoolTest(true), + AutoMinorVersionUpgrade: types.BoolTest(true), + DBInstanceArn: types.StringTest("arn:aws:rds:us-east-2:123456789012:db:my-mysql-instance-1"), + IAMAuthEnabled: types.BoolTest(true), + PerformanceInsights: rds.PerformanceInsights{ + Enabled: types.BoolTest(true), + KMSKeyID: types.StringTest("test-kms-key-id2"), + }, + EnabledCloudwatchLogsExports: []types.StringValue{ + types.StringTest("error"), + types.StringTest("general"), + }, + DBParameterGroups: []rds.DBParameterGroupsList{ + { + DBParameterGroupName: types.StringTest("testgroup"), + }, + }, + TagList: []rds.TagList{ + {}, + {}, + }, + }, + ClusterIdentifier: types.StringTest("RDSCluster"), + }, + }, + }, + }, + }, + }, + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + RDSCluster: + Type: 'AWS::RDS::DBCluster' + RDSDBInstance1: + Type: 'AWS::RDS::DBInstance' + RDSDBParameterGroup: + Type: 'AWS::RDS::DBParameterGroup' + DbSecurityByEC2SecurityGroup: + Type: AWS::RDS::DBSecurityGroup +`, + expected: rds.RDS{ + Classic: rds.Classic{ + DBSecurityGroups: []rds.DBSecurityGroup{{}}, + }, + ParameterGroups: []rds.ParameterGroups{{}}, + Clusters: []rds.Cluster{{ + Engine: types.StringTest("aurora"), + BackupRetentionPeriodDays: types.IntTest(1), + }}, + Instances: []rds.Instance{{ + BackupRetentionPeriodDays: types.IntTest(1), + PublicAccess: types.BoolTest(true), + DBParameterGroups: []rds.DBParameterGroupsList{{}}, + }}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } + +} diff --git a/pkg/iac/adapters/cloudformation/aws/rds/cluster.go b/pkg/iac/adapters/cloudformation/aws/rds/cluster.go new file mode 100644 index 000000000000..87bbc7ae64ba --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/rds/cluster.go @@ -0,0 +1,48 @@ +package rds + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getClusters(ctx parser.FileContext) (clusters map[string]rds.Cluster) { + clusters = make(map[string]rds.Cluster) + for _, clusterResource := range ctx.GetResourcesByType("AWS::RDS::DBCluster") { + clusters[clusterResource.ID()] = rds.Cluster{ + Metadata: clusterResource.Metadata(), + BackupRetentionPeriodDays: clusterResource.GetIntProperty("BackupRetentionPeriod", 1), + PerformanceInsights: rds.PerformanceInsights{ + Metadata: clusterResource.Metadata(), + Enabled: clusterResource.GetBoolProperty("PerformanceInsightsEnabled"), + KMSKeyID: clusterResource.GetStringProperty("PerformanceInsightsKmsKeyId"), + }, + Encryption: rds.Encryption{ + Metadata: clusterResource.Metadata(), + EncryptStorage: clusterResource.GetBoolProperty("StorageEncrypted"), + KMSKeyID: clusterResource.GetStringProperty("KmsKeyId"), + }, + PublicAccess: iacTypes.BoolDefault(false, clusterResource.Metadata()), + Engine: clusterResource.GetStringProperty("Engine", rds.EngineAurora), + LatestRestorableTime: iacTypes.TimeUnresolvable(clusterResource.Metadata()), + DeletionProtection: clusterResource.GetBoolProperty("DeletionProtection"), + } + } + return clusters +} + +func getClassic(ctx parser.FileContext) rds.Classic { + return rds.Classic{ + DBSecurityGroups: getClassicSecurityGroups(ctx), + } +} + +func getClassicSecurityGroups(ctx parser.FileContext) (groups []rds.DBSecurityGroup) { + for _, dbsgResource := range ctx.GetResourcesByType("AWS::RDS::DBSecurityGroup") { + group := rds.DBSecurityGroup{ + Metadata: dbsgResource.Metadata(), + } + groups = append(groups, group) + } + return groups +} diff --git a/pkg/iac/adapters/cloudformation/aws/rds/instance.go b/pkg/iac/adapters/cloudformation/aws/rds/instance.go new file mode 100644 index 000000000000..256eada02aac --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/rds/instance.go @@ -0,0 +1,133 @@ +package rds + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getClustersAndInstances(ctx parser.FileContext) ([]rds.Cluster, []rds.Instance) { + + clusterMap := getClusters(ctx) + + var orphans []rds.Instance + + for _, r := range ctx.GetResourcesByType("AWS::RDS::DBInstance") { + + instance := rds.Instance{ + Metadata: r.Metadata(), + BackupRetentionPeriodDays: r.GetIntProperty("BackupRetentionPeriod", 1), + ReplicationSourceARN: r.GetStringProperty("SourceDBInstanceIdentifier"), + PerformanceInsights: rds.PerformanceInsights{ + Metadata: r.Metadata(), + Enabled: r.GetBoolProperty("EnablePerformanceInsights"), + KMSKeyID: r.GetStringProperty("PerformanceInsightsKMSKeyId"), + }, + Encryption: rds.Encryption{ + Metadata: r.Metadata(), + EncryptStorage: r.GetBoolProperty("StorageEncrypted"), + KMSKeyID: r.GetStringProperty("KmsKeyId"), + }, + PublicAccess: r.GetBoolProperty("PubliclyAccessible", true), + Engine: r.GetStringProperty("Engine"), + IAMAuthEnabled: r.GetBoolProperty("EnableIAMDatabaseAuthentication"), + DeletionProtection: r.GetBoolProperty("DeletionProtection", false), + DBInstanceArn: r.GetStringProperty("DBInstanceArn"), + StorageEncrypted: r.GetBoolProperty("StorageEncrypted", false), + DBInstanceIdentifier: r.GetStringProperty("DBInstanceIdentifier"), + DBParameterGroups: getDBParameterGroups(ctx, r), + TagList: getTagList(r), + EnabledCloudwatchLogsExports: getEnabledCloudwatchLogsExports(r), + EngineVersion: r.GetStringProperty("EngineVersion"), + AutoMinorVersionUpgrade: r.GetBoolProperty("AutoMinorVersionUpgrade"), + MultiAZ: r.GetBoolProperty("MultiAZ"), + PubliclyAccessible: r.GetBoolProperty("PubliclyAccessible"), + LatestRestorableTime: types.TimeUnresolvable(r.Metadata()), + ReadReplicaDBInstanceIdentifiers: getReadReplicaDBInstanceIdentifiers(r), + } + + if clusterID := r.GetProperty("DBClusterIdentifier"); clusterID.IsString() { + if cluster, exist := clusterMap[clusterID.AsString()]; exist { + cluster.Instances = append(cluster.Instances, rds.ClusterInstance{ + Instance: instance, + ClusterIdentifier: clusterID.AsStringValue(), + }) + clusterMap[clusterID.AsString()] = cluster + } + } else { + orphans = append(orphans, instance) + } + } + + clusters := make([]rds.Cluster, 0, len(clusterMap)) + + for _, cluster := range clusterMap { + clusters = append(clusters, cluster) + } + + return clusters, orphans +} + +func getDBParameterGroups(ctx parser.FileContext, r *parser.Resource) (dbParameterGroup []rds.DBParameterGroupsList) { + + var parameterGroupList []rds.DBParameterGroupsList + + dbParameterGroupName := r.GetStringProperty("DBParameterGroupName") + + for _, r := range ctx.GetResourcesByType("AWS::RDS::DBParameterGroup") { + name := r.GetStringProperty("DBParameterGroupName") + // TODO: find by resource logical id + if !dbParameterGroupName.EqualTo(name.Value()) { + continue + } + dbpmgl := rds.DBParameterGroupsList{ + Metadata: r.Metadata(), + DBParameterGroupName: name, + KMSKeyID: types.StringUnresolvable(r.Metadata()), + } + parameterGroupList = append(dbParameterGroup, dbpmgl) + } + + return parameterGroupList +} + +func getEnabledCloudwatchLogsExports(r *parser.Resource) (enabledcloudwatchlogexportslist []types.StringValue) { + enabledCloudwatchLogExportList := r.GetProperty("EnableCloudwatchLogsExports") + + if enabledCloudwatchLogExportList.IsNil() || enabledCloudwatchLogExportList.IsNotList() { + return enabledcloudwatchlogexportslist + } + + for _, ecle := range enabledCloudwatchLogExportList.AsList() { + enabledcloudwatchlogexportslist = append(enabledcloudwatchlogexportslist, ecle.AsStringValue()) + } + return enabledcloudwatchlogexportslist +} + +func getTagList(r *parser.Resource) (taglist []rds.TagList) { + tagLists := r.GetProperty("Tags") + + if tagLists.IsNil() || tagLists.IsNotList() { + return taglist + } + + for _, tl := range tagLists.AsList() { + taglist = append(taglist, rds.TagList{ + Metadata: tl.Metadata(), + }) + } + return taglist +} + +func getReadReplicaDBInstanceIdentifiers(r *parser.Resource) (readreplicadbidentifier []types.StringValue) { + readReplicaDBIdentifier := r.GetProperty("SourceDBInstanceIdentifier") + + if readReplicaDBIdentifier.IsNil() || readReplicaDBIdentifier.IsNotList() { + return readreplicadbidentifier + } + + for _, rr := range readReplicaDBIdentifier.AsList() { + readreplicadbidentifier = append(readreplicadbidentifier, rr.AsStringValue()) + } + return readreplicadbidentifier +} diff --git a/pkg/iac/adapters/cloudformation/aws/rds/parameter_groups.go b/pkg/iac/adapters/cloudformation/aws/rds/parameter_groups.go new file mode 100644 index 000000000000..f47c2f70a706 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/rds/parameter_groups.go @@ -0,0 +1,44 @@ +package rds + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getParameterGroups(ctx parser.FileContext) (parametergroups []rds.ParameterGroups) { + + for _, r := range ctx.GetResourcesByType("AWS::RDS::DBParameterGroup") { + + paramgroup := rds.ParameterGroups{ + Metadata: r.Metadata(), + DBParameterGroupName: r.GetStringProperty("DBParameterGroupName"), + DBParameterGroupFamily: r.GetStringProperty("DBParameterGroupFamily"), + Parameters: getParameters(r), + } + + parametergroups = append(parametergroups, paramgroup) + } + + return parametergroups +} + +func getParameters(r *parser.Resource) (parameters []rds.Parameters) { + + dBParam := r.GetProperty("Parameters") + + // TODO: parameters is JSON + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbparametergroup.html#cfn-rds-dbparametergroup-parameters + if dBParam.IsNil() || dBParam.IsNotList() { + return parameters + } + + for _, dbp := range dBParam.AsList() { + parameters = append(parameters, rds.Parameters{ + Metadata: dbp.Metadata(), + ParameterName: types.StringDefault("", dbp.Metadata()), + ParameterValue: types.StringDefault("", dbp.Metadata()), + }) + } + return parameters +} diff --git a/pkg/iac/adapters/cloudformation/aws/rds/rds.go b/pkg/iac/adapters/cloudformation/aws/rds/rds.go new file mode 100644 index 000000000000..25118c20bbba --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/rds/rds.go @@ -0,0 +1,18 @@ +package rds + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an RDS instance +func Adapt(cfFile parser.FileContext) rds.RDS { + clusters, orphans := getClustersAndInstances(cfFile) + return rds.RDS{ + Instances: orphans, + Clusters: clusters, + Classic: getClassic(cfFile), + ParameterGroups: getParameterGroups(cfFile), + Snapshots: nil, + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/redshift/cluster.go b/pkg/iac/adapters/cloudformation/aws/redshift/cluster.go new file mode 100644 index 000000000000..c8acf8997af0 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/redshift/cluster.go @@ -0,0 +1,49 @@ +package redshift + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/redshift" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getClusters(ctx parser.FileContext) (clusters []redshift.Cluster) { + for _, r := range ctx.GetResourcesByType("AWS::Redshift::Cluster") { + + cluster := redshift.Cluster{ + Metadata: r.Metadata(), + ClusterIdentifier: r.GetStringProperty("ClusterIdentifier"), + AllowVersionUpgrade: r.GetBoolProperty("AllowVersionUpgrade", true), + NodeType: r.GetStringProperty("NodeType"), + NumberOfNodes: r.GetIntProperty("NumberOfNodes", 1), + PubliclyAccessible: r.GetBoolProperty("PubliclyAccessible"), + MasterUsername: r.GetStringProperty("MasterUsername"), + AutomatedSnapshotRetentionPeriod: r.GetIntProperty("AutomatedSnapshotRetentionPeriod", 1), + Encryption: redshift.Encryption{ + Metadata: r.Metadata(), + Enabled: r.GetBoolProperty("Encrypted"), + KMSKeyID: r.GetStringProperty("KmsKeyId"), + }, + EndPoint: redshift.EndPoint{ + Metadata: r.Metadata(), + Port: r.GetIntProperty("Endpoint.Port"), + }, + SubnetGroupName: r.GetStringProperty("ClusterSubnetGroupName", ""), + } + + clusters = append(clusters, cluster) + } + return clusters +} + +func getParameters(ctx parser.FileContext) (parameter []redshift.ClusterParameter) { + var parameters []redshift.ClusterParameter + for _, r := range ctx.GetResourcesByType("AWS::Redshift::ClusterParameterGroup") { + for _, par := range r.GetProperty("Parameters").AsList() { + parameters = append(parameters, redshift.ClusterParameter{ + Metadata: par.Metadata(), + ParameterName: par.GetStringProperty("ParameterName"), + ParameterValue: par.GetStringProperty("ParameterValue"), + }) + } + } + return parameters +} diff --git a/pkg/iac/adapters/cloudformation/aws/redshift/redshift.go b/pkg/iac/adapters/cloudformation/aws/redshift/redshift.go new file mode 100644 index 000000000000..899b6c50e147 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/redshift/redshift.go @@ -0,0 +1,16 @@ +package redshift + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/redshift" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a RedShift instance +func Adapt(cfFile parser.FileContext) redshift.Redshift { + return redshift.Redshift{ + Clusters: getClusters(cfFile), + SecurityGroups: getSecurityGroups(cfFile), + ClusterParameters: getParameters(cfFile), + ReservedNodes: nil, + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/redshift/redshift_test.go b/pkg/iac/adapters/cloudformation/aws/redshift/redshift_test.go new file mode 100644 index 000000000000..a14117a21961 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/redshift/redshift_test.go @@ -0,0 +1,111 @@ +package redshift + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/redshift" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected redshift.Redshift + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + myCluster: + Type: "AWS::Redshift::Cluster" + Properties: + DBName: "mydb" + ClusterIdentifier: myexamplecluster + AllowVersionUpgrade: false + MasterUsername: "master" + NodeType: "ds2.xlarge" + NumberOfNodes: 2 + PubliclyAccessible: true + AutomatedSnapshotRetentionPeriod: 2 + Encrypted: true + KmsKeyId: key + Endpoint: + Port: 2000 + ClusterSubnetGroupName: test + myClusterParameterGroup: + Type: "AWS::Redshift::ClusterParameterGroup" + Properties: + Parameters: + - + ParameterName: "enable_user_activity_logging" + ParameterValue: "true" + mySecGroup: + Type: AWS::Redshift::ClusterSecurityGroup + Properties: + Description: test + `, + expected: redshift.Redshift{ + Clusters: []redshift.Cluster{ + { + ClusterIdentifier: types.StringTest("myexamplecluster"), + AllowVersionUpgrade: types.BoolTest(false), + MasterUsername: types.StringTest("master"), + NodeType: types.StringTest("ds2.xlarge"), + NumberOfNodes: types.IntTest(2), + PubliclyAccessible: types.BoolTest(true), + AutomatedSnapshotRetentionPeriod: types.IntTest(2), + Encryption: redshift.Encryption{ + Enabled: types.BoolTest(true), + KMSKeyID: types.StringTest("key"), + }, + EndPoint: redshift.EndPoint{ + Port: types.IntTest(2000), + }, + SubnetGroupName: types.StringTest("test"), + }, + }, + ClusterParameters: []redshift.ClusterParameter{ + { + ParameterName: types.StringTest("enable_user_activity_logging"), + ParameterValue: types.StringTest("true"), + }, + }, + SecurityGroups: []redshift.SecurityGroup{ + { + Description: types.StringTest("test"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + myCluster: + Type: "AWS::Redshift::Cluster" + mySecGroup: + Type: AWS::Redshift::ClusterSecurityGroup + myClusterParameterGroup: + Type: "AWS::Redshift::ClusterParameterGroup" +`, + expected: redshift.Redshift{ + Clusters: []redshift.Cluster{ + { + AllowVersionUpgrade: types.BoolTest(true), + AutomatedSnapshotRetentionPeriod: types.IntTest(1), + NumberOfNodes: types.IntTest(1), + }, + }, + SecurityGroups: []redshift.SecurityGroup{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/redshift/security_group.go b/pkg/iac/adapters/cloudformation/aws/redshift/security_group.go new file mode 100644 index 000000000000..0223306783ae --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/redshift/security_group.go @@ -0,0 +1,17 @@ +package redshift + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/redshift" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getSecurityGroups(ctx parser.FileContext) (groups []redshift.SecurityGroup) { + for _, groupResource := range ctx.GetResourcesByType("AWS::Redshift::ClusterSecurityGroup") { + group := redshift.SecurityGroup{ + Metadata: groupResource.Metadata(), + Description: groupResource.GetProperty("Description").AsStringValue(), + } + groups = append(groups, group) + } + return groups +} diff --git a/pkg/iac/adapters/cloudformation/aws/s3/bucket.go b/pkg/iac/adapters/cloudformation/aws/s3/bucket.go new file mode 100644 index 000000000000..5f5329fc6714 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/s3/bucket.go @@ -0,0 +1,158 @@ +package s3 + +import ( + "cmp" + "regexp" + "slices" + "strings" + + s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var aclConvertRegex = regexp.MustCompile(`[A-Z][^A-Z]*`) + +func getBuckets(cfFile parser.FileContext) []s3.Bucket { + var buckets []s3.Bucket + bucketResources := cfFile.GetResourcesByType("AWS::S3::Bucket") + + for _, r := range bucketResources { + s3b := s3.Bucket{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("BucketName"), + PublicAccessBlock: getPublicAccessBlock(r), + Encryption: getEncryption(r, cfFile), + Versioning: s3.Versioning{ + Metadata: r.Metadata(), + Enabled: hasVersioning(r), + MFADelete: iacTypes.BoolUnresolvable(r.Metadata()), + }, + Logging: getLogging(r), + ACL: convertAclValue(r.GetStringProperty("AccessControl", "private")), + LifecycleConfiguration: getLifecycle(r), + AccelerateConfigurationStatus: r.GetStringProperty("AccelerateConfiguration.AccelerationStatus"), + Website: getWebsite(r), + BucketLocation: iacTypes.String("", r.Metadata()), + Objects: nil, + } + + buckets = append(buckets, s3b) + } + + slices.SortFunc(buckets, func(a, b s3.Bucket) int { + return cmp.Compare(a.Name.Value(), b.Name.Value()) + }) + + return buckets +} + +func getPublicAccessBlock(r *parser.Resource) *s3.PublicAccessBlock { + block := r.GetProperty("PublicAccessBlockConfiguration") + if block.IsNil() { + return nil + } + + return &s3.PublicAccessBlock{ + Metadata: block.Metadata(), + BlockPublicACLs: block.GetBoolProperty("BlockPublicAcls"), + BlockPublicPolicy: block.GetBoolProperty("BlockPublicPolicy"), + IgnorePublicACLs: block.GetBoolProperty("IgnorePublicAcls"), + RestrictPublicBuckets: block.GetBoolProperty("RestrictPublicBuckets"), + } +} + +func convertAclValue(aclValue iacTypes.StringValue) iacTypes.StringValue { + matches := aclConvertRegex.FindAllString(aclValue.Value(), -1) + + return iacTypes.String(strings.ToLower(strings.Join(matches, "-")), aclValue.GetMetadata()) +} + +func getLogging(r *parser.Resource) s3.Logging { + logging := s3.Logging{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + TargetBucket: iacTypes.StringDefault("", r.Metadata()), + } + + if config := r.GetProperty("LoggingConfiguration"); config.IsNotNil() { + logging.TargetBucket = config.GetStringProperty("DestinationBucketName") + if logging.TargetBucket.IsNotEmpty() || !logging.TargetBucket.GetMetadata().IsResolvable() { + logging.Enabled = iacTypes.Bool(true, config.Metadata()) + } + } + return logging +} + +func hasVersioning(r *parser.Resource) iacTypes.BoolValue { + versioningProp := r.GetProperty("VersioningConfiguration.Status") + + if versioningProp.IsNil() { + return iacTypes.BoolDefault(false, r.Metadata()) + } + + versioningEnabled := false + if versioningProp.EqualTo("Enabled") { + versioningEnabled = true + + } + return iacTypes.Bool(versioningEnabled, versioningProp.Metadata()) +} + +func getEncryption(r *parser.Resource, _ parser.FileContext) s3.Encryption { + encryption := s3.Encryption{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + Algorithm: iacTypes.StringDefault("", r.Metadata()), + KMSKeyId: iacTypes.StringDefault("", r.Metadata()), + } + + if encryptProps := r.GetProperty("BucketEncryption.ServerSideEncryptionConfiguration"); encryptProps.IsNotNil() { + for _, rule := range encryptProps.AsList() { + algo := rule.GetProperty("ServerSideEncryptionByDefault.SSEAlgorithm") + if algo.IsString() { + algoVal := algo.AsString() + isValidAlgo := slices.Contains(s3types.ServerSideEncryption("").Values(), s3types.ServerSideEncryption(algoVal)) + encryption.Enabled = iacTypes.Bool(isValidAlgo, algo.Metadata()) + encryption.Algorithm = algo.AsStringValue() + } + + kmsKeyProp := rule.GetProperty("ServerSideEncryptionByDefault.KMSMasterKeyID") + if !kmsKeyProp.IsEmpty() && kmsKeyProp.IsString() { + encryption.KMSKeyId = kmsKeyProp.AsStringValue() + } + } + } + + return encryption +} + +func getLifecycle(resource *parser.Resource) []s3.Rules { + RuleProp := resource.GetProperty("LifecycleConfiguration.Rules") + + var rule []s3.Rules + + if RuleProp.IsNil() || RuleProp.IsNotList() { + return rule + } + + for _, r := range RuleProp.AsList() { + rule = append(rule, s3.Rules{ + Metadata: r.Metadata(), + Status: r.GetStringProperty("Status"), + }) + } + return rule +} + +func getWebsite(r *parser.Resource) *s3.Website { + if block := r.GetProperty("WebsiteConfiguration"); block.IsNil() { + return nil + } else { + return &s3.Website{ + Metadata: block.Metadata(), + } + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/s3/s3.go b/pkg/iac/adapters/cloudformation/aws/s3/s3.go new file mode 100644 index 000000000000..988b51adc378 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/s3/s3.go @@ -0,0 +1,13 @@ +package s3 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an S3 instance +func Adapt(cfFile parser.FileContext) s3.S3 { + return s3.S3{ + Buckets: getBuckets(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go b/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go new file mode 100644 index 000000000000..ee8fffb39f75 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go @@ -0,0 +1,152 @@ +package s3 + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected s3.S3 + }{ + { + name: "complete s3 bucket", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Key: + Type: "AWS::KMS::Key" + LoggingBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: logging-bucket + Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: test-bucket + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + KMSMasterKeyID: + Fn::GetAtt: + - Key + - Arn + SSEAlgorithm: aws:kms + AccessControl: AwsExecRead + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + LoggingConfiguration: + DestinationBucketName: !Ref LoggingBucket + LogFilePrefix: testing-logs + LifecycleConfiguration: + Rules: + - Id: GlacierRule + Prefix: glacier + Status: Enabled + ExpirationInDays: 365 + AccelerateConfiguration: + AccelerationStatus: Enabled + VersioningConfiguration: + Status: Enabled + WebsiteConfiguration: + IndexDocument: index.html +`, + expected: s3.S3{ + Buckets: []s3.Bucket{ + { + Name: types.StringTest("logging-bucket"), + }, + { + Name: types.StringTest("test-bucket"), + Encryption: s3.Encryption{ + Enabled: types.BoolTest(true), + Algorithm: types.StringTest("aws:kms"), + KMSKeyId: types.StringTest("Key"), + }, + ACL: types.StringTest("aws-exec-read"), + PublicAccessBlock: &s3.PublicAccessBlock{ + BlockPublicACLs: types.BoolTest(true), + BlockPublicPolicy: types.BoolTest(true), + IgnorePublicACLs: types.BoolTest(true), + RestrictPublicBuckets: types.BoolTest(true), + }, + Logging: s3.Logging{ + TargetBucket: types.StringTest("LoggingBucket"), + Enabled: types.BoolTest(true), + }, + LifecycleConfiguration: []s3.Rules{ + { + Status: types.StringTest("Enabled"), + }, + }, + AccelerateConfigurationStatus: types.StringTest("Enabled"), + Versioning: s3.Versioning{ + Enabled: types.BoolTest(true), + }, + Website: &s3.Website{}, + }, + }, + }, + }, + { + name: "empty s3 bucket", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: test-bucket`, + expected: s3.S3{ + Buckets: []s3.Bucket{ + { + Name: types.StringTest("test-bucket"), + Encryption: s3.Encryption{ + Enabled: types.BoolDefault(false, types.NewTestMetadata()), + }, + }, + }, + }, + }, + { + name: "incorrect SSE algorithm", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: test-bucket + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + KMSMasterKeyID: alias/my-key + SSEAlgorithm: aes256 +`, + expected: s3.S3{ + Buckets: []s3.Bucket{ + { + Name: types.StringTest("test-bucket"), + Encryption: s3.Encryption{ + Enabled: types.BoolDefault(false, types.NewTestMetadata()), + KMSKeyId: types.StringTest("alias/my-key"), + Algorithm: types.StringTest("aes256"), + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } + +} diff --git a/pkg/iac/adapters/cloudformation/aws/sam/api.go b/pkg/iac/adapters/cloudformation/aws/sam/api.go new file mode 100644 index 000000000000..4d4f04e6e83a --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sam/api.go @@ -0,0 +1,96 @@ +package sam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getApis(cfFile parser.FileContext) (apis []sam.API) { + + apiResources := cfFile.GetResourcesByType("AWS::Serverless::Api") + for _, r := range apiResources { + api := sam.API{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("Name", ""), + TracingEnabled: r.GetBoolProperty("TracingEnabled"), + DomainConfiguration: getDomainConfiguration(r), + AccessLogging: getAccessLogging(r), + RESTMethodSettings: getRestMethodSettings(r), + } + + apis = append(apis, api) + } + + return apis +} + +func getRestMethodSettings(r *parser.Resource) sam.RESTMethodSettings { + + settings := sam.RESTMethodSettings{ + Metadata: r.Metadata(), + CacheDataEncrypted: iacTypes.BoolDefault(false, r.Metadata()), + LoggingEnabled: iacTypes.BoolDefault(false, r.Metadata()), + DataTraceEnabled: iacTypes.BoolDefault(false, r.Metadata()), + MetricsEnabled: iacTypes.BoolDefault(false, r.Metadata()), + } + + // TODO: MethodSettings is list + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-stage.html#cfn-apigateway-stage-methodsettings + settingsProp := r.GetProperty("MethodSettings") + if settingsProp.IsNotNil() { + + settings = sam.RESTMethodSettings{ + Metadata: settingsProp.Metadata(), + CacheDataEncrypted: settingsProp.GetBoolProperty("CacheDataEncrypted"), + LoggingEnabled: iacTypes.BoolDefault(false, settingsProp.Metadata()), + DataTraceEnabled: settingsProp.GetBoolProperty("DataTraceEnabled"), + MetricsEnabled: settingsProp.GetBoolProperty("MetricsEnabled"), + } + + if loggingLevel := settingsProp.GetProperty("LoggingLevel"); loggingLevel.IsNotNil() { + if loggingLevel.EqualTo("OFF", parser.IgnoreCase) { + settings.LoggingEnabled = iacTypes.Bool(false, loggingLevel.Metadata()) + } else { + settings.LoggingEnabled = iacTypes.Bool(true, loggingLevel.Metadata()) + } + } + } + + return settings +} + +func getAccessLogging(r *parser.Resource) sam.AccessLogging { + + logging := sam.AccessLogging{ + Metadata: r.Metadata(), + CloudwatchLogGroupARN: iacTypes.StringDefault("", r.Metadata()), + } + + if access := r.GetProperty("AccessLogSetting"); access.IsNotNil() { + logging = sam.AccessLogging{ + Metadata: access.Metadata(), + CloudwatchLogGroupARN: access.GetStringProperty("DestinationArn", ""), + } + } + + return logging +} + +func getDomainConfiguration(r *parser.Resource) sam.DomainConfiguration { + + domainConfig := sam.DomainConfiguration{ + Metadata: r.Metadata(), + } + + if domain := r.GetProperty("Domain"); domain.IsNotNil() { + domainConfig = sam.DomainConfiguration{ + Metadata: domain.Metadata(), + Name: domain.GetStringProperty("DomainName"), + SecurityPolicy: domain.GetStringProperty("SecurityPolicy"), + } + } + + return domainConfig + +} diff --git a/pkg/iac/adapters/cloudformation/aws/sam/function.go b/pkg/iac/adapters/cloudformation/aws/sam/function.go new file mode 100644 index 000000000000..161b078bf681 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sam/function.go @@ -0,0 +1,59 @@ +package sam + +import ( + "github.com/liamg/iamgo" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getFunctions(cfFile parser.FileContext) (functions []sam.Function) { + + functionResources := cfFile.GetResourcesByType("AWS::Serverless::Function") + for _, r := range functionResources { + function := sam.Function{ + Metadata: r.Metadata(), + FunctionName: r.GetStringProperty("FunctionName"), + Tracing: r.GetStringProperty("Tracing"), + ManagedPolicies: nil, + Policies: nil, + } + + setFunctionPolicies(r, &function) + functions = append(functions, function) + } + + return functions +} + +func setFunctionPolicies(r *parser.Resource, function *sam.Function) { + policies := r.GetProperty("Policies") + if policies.IsNotNil() { + if policies.IsString() { + function.ManagedPolicies = append(function.ManagedPolicies, policies.AsStringValue()) + } else if policies.IsList() { + for _, property := range policies.AsList() { + if property.IsMap() { + parsed, err := iamgo.Parse(property.GetJsonBytes(true)) + if err != nil { + continue + } + policy := iam.Policy{ + Metadata: property.Metadata(), + Name: iacTypes.StringDefault("", property.Metadata()), + Document: iam.Document{ + Metadata: property.Metadata(), + Parsed: *parsed, + }, + Builtin: iacTypes.Bool(false, property.Metadata()), + } + function.Policies = append(function.Policies, policy) + } else if property.IsString() { + function.ManagedPolicies = append(function.ManagedPolicies, property.AsStringValue()) + } + } + } + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/sam/http_api.go b/pkg/iac/adapters/cloudformation/aws/sam/http_api.go new file mode 100644 index 000000000000..02f9ba6c5ef9 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sam/http_api.go @@ -0,0 +1,65 @@ +package sam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getHttpApis(cfFile parser.FileContext) (apis []sam.HttpAPI) { + + apiResources := cfFile.GetResourcesByType("AWS::Serverless::HttpApi") + for _, r := range apiResources { + api := sam.HttpAPI{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("Name", ""), + DomainConfiguration: getDomainConfiguration(r), + AccessLogging: getAccessLoggingV2(r), + DefaultRouteSettings: getRouteSettings(r), + } + + apis = append(apis, api) + } + + return apis +} + +func getAccessLoggingV2(r *parser.Resource) sam.AccessLogging { + + logging := sam.AccessLogging{ + Metadata: r.Metadata(), + CloudwatchLogGroupARN: types.StringDefault("", r.Metadata()), + } + + if access := r.GetProperty("AccessLogSettings"); access.IsNotNil() { + logging = sam.AccessLogging{ + Metadata: access.Metadata(), + CloudwatchLogGroupARN: access.GetStringProperty("DestinationArn", ""), + } + } + + return logging +} + +func getRouteSettings(r *parser.Resource) sam.RouteSettings { + + routeSettings := sam.RouteSettings{ + Metadata: r.Metadata(), + LoggingEnabled: types.BoolDefault(false, r.Metadata()), + DataTraceEnabled: types.BoolDefault(false, r.Metadata()), + DetailedMetricsEnabled: types.BoolDefault(false, r.Metadata()), + } + + if route := r.GetProperty("DefaultRouteSettings"); route.IsNotNil() { + routeSettings = sam.RouteSettings{ + Metadata: route.Metadata(), + // TODO: LoggingLevel is string + LoggingEnabled: route.GetBoolProperty("LoggingLevel"), + DataTraceEnabled: route.GetBoolProperty("DataTraceEnabled"), + DetailedMetricsEnabled: route.GetBoolProperty("DetailedMetricsEnabled"), + } + } + + return routeSettings + +} diff --git a/pkg/iac/adapters/cloudformation/aws/sam/sam.go b/pkg/iac/adapters/cloudformation/aws/sam/sam.go new file mode 100644 index 000000000000..0f08854aa697 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sam/sam.go @@ -0,0 +1,17 @@ +package sam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an SAM instance +func Adapt(cfFile parser.FileContext) sam.SAM { + return sam.SAM{ + APIs: getApis(cfFile), + HttpAPIs: getHttpApis(cfFile), + Functions: getFunctions(cfFile), + StateMachines: getStateMachines(cfFile), + SimpleTables: getSimpleTables(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/sam/sam_test.go b/pkg/iac/adapters/cloudformation/aws/sam/sam_test.go new file mode 100644 index 000000000000..ec2fed201ea6 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sam/sam_test.go @@ -0,0 +1,213 @@ +package sam + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/liamg/iamgo" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected sam.SAM + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + ApiGatewayApi: + Type: AWS::Serverless::Api + Properties: + StageName: prod + Name: test + TracingEnabled: true + Domain: + DomainName: domain + SecurityPolicy: "TLS_1_2" + MethodSettings: + - DataTraceEnabled: true + CacheDataEncrypted: true + MetricsEnabled: true + LoggingLevel: INFO + AccessLogSetting: + DestinationArn: 'arn:aws:logs:us-east-1:123456789:log-group:my-log-group' + HttpApi: + Type: AWS::Serverless::HttpApi + Properties: + Name: test + Domain: + DomainName: test + SecurityPolicy: "TLS_1_2" + AccessLogSettings: + DestinationArn: 'arn:aws:logs:us-east-1:123456789:log-group:my-log-group' + DefaultRouteSettings: + LoggingLevel: INFO + DataTraceEnabled: true + DetailedMetricsEnabled: true + myFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: test + Tracing: Active + Policies: + - AWSLambdaExecute + - Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - s3:GetObject + Resource: 'arn:aws:s3:::my-bucket/*' + MySampleStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Logging: + Level: ALL + Tracing: + Enabled: true + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "cloudwatch:*" + Resource: "*" + myTable: + Type: AWS::Serverless::SimpleTable + Properties: + TableName: my-table + SSESpecification: + SSEEnabled: "true" + KMSMasterKeyId: "kmskey" +`, + expected: sam.SAM{ + APIs: []sam.API{ + { + Name: types.StringTest("test"), + TracingEnabled: types.BoolTest(true), + DomainConfiguration: sam.DomainConfiguration{ + Name: types.StringTest("domain"), + SecurityPolicy: types.StringTest("TLS_1_2"), + }, + AccessLogging: sam.AccessLogging{ + CloudwatchLogGroupARN: types.StringTest("arn:aws:logs:us-east-1:123456789:log-group:my-log-group"), + }, + }, + }, + HttpAPIs: []sam.HttpAPI{ + { + Name: types.StringTest("test"), + DomainConfiguration: sam.DomainConfiguration{ + Name: types.StringTest("test"), + SecurityPolicy: types.StringTest("TLS_1_2"), + }, + AccessLogging: sam.AccessLogging{ + CloudwatchLogGroupARN: types.StringTest("arn:aws:logs:us-east-1:123456789:log-group:my-log-group"), + }, + DefaultRouteSettings: sam.RouteSettings{ + DataTraceEnabled: types.BoolTest(true), + DetailedMetricsEnabled: types.BoolTest(true), + }, + }, + }, + Functions: []sam.Function{ + { + FunctionName: types.StringTest("test"), + Tracing: types.StringTest("Active"), + Policies: []iam.Policy{ + { + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"s3:GetObject"}). + WithResources([]string{"arn:aws:s3:::my-bucket/*"}). + Build(), + ). + Build(), + } + }(), + }, + }, + ManagedPolicies: []types.StringValue{ + types.StringTest("AWSLambdaExecute"), + }, + }, + }, + StateMachines: []sam.StateMachine{ + { + LoggingConfiguration: sam.LoggingConfiguration{ + LoggingEnabled: types.BoolTest(true), + }, + Tracing: sam.TracingConfiguration{ + Enabled: types.BoolTest(true), + }, + Policies: []iam.Policy{ + { + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"cloudwatch:*"}). + WithResources([]string{"*"}). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + SimpleTables: []sam.SimpleTable{ + { + TableName: types.StringTest("my-table"), + SSESpecification: sam.SSESpecification{ + Enabled: types.BoolTest(true), + KMSMasterKeyID: types.StringTest("kmskey"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + ApiGatewayApi: + Type: AWS::Serverless::Api + HttpApi: + Type: AWS::Serverless::HttpApi + myFunction: + Type: AWS::Serverless::Function + MySampleStateMachine: + Type: AWS::Serverless::StateMachine + myTable: + Type: AWS::Serverless::SimpleTable +`, + expected: sam.SAM{ + APIs: []sam.API{{}}, + HttpAPIs: []sam.HttpAPI{{}}, + Functions: []sam.Function{{}}, + StateMachines: []sam.StateMachine{{}}, + SimpleTables: []sam.SimpleTable{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/sam/state_machines.go b/pkg/iac/adapters/cloudformation/aws/sam/state_machines.go new file mode 100644 index 000000000000..2a57afd2bdb6 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sam/state_machines.go @@ -0,0 +1,82 @@ +package sam + +import ( + "github.com/liamg/iamgo" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getStateMachines(cfFile parser.FileContext) (stateMachines []sam.StateMachine) { + + stateMachineResources := cfFile.GetResourcesByType("AWS::Serverless::StateMachine") + for _, r := range stateMachineResources { + stateMachine := sam.StateMachine{ + Metadata: r.Metadata(), + Name: r.GetStringProperty("Name"), + LoggingConfiguration: sam.LoggingConfiguration{ + Metadata: r.Metadata(), + LoggingEnabled: iacTypes.BoolDefault(false, r.Metadata()), + }, + ManagedPolicies: nil, + Policies: nil, + Tracing: getTracingConfiguration(r), + } + + // TODO: By default, the level is set to OFF + if logging := r.GetProperty("Logging"); logging.IsNotNil() { + stateMachine.LoggingConfiguration.Metadata = logging.Metadata() + if level := logging.GetProperty("Level"); level.IsNotNil() { + stateMachine.LoggingConfiguration.LoggingEnabled = iacTypes.Bool(!level.EqualTo("OFF"), level.Metadata()) + } + } + + setStateMachinePolicies(r, &stateMachine) + stateMachines = append(stateMachines, stateMachine) + } + + return stateMachines +} + +func getTracingConfiguration(r *parser.Resource) sam.TracingConfiguration { + tracing := r.GetProperty("Tracing") + if tracing.IsNil() { + return sam.TracingConfiguration{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + } + } + + return sam.TracingConfiguration{ + Metadata: tracing.Metadata(), + Enabled: tracing.GetBoolProperty("Enabled"), + } +} + +func setStateMachinePolicies(r *parser.Resource, stateMachine *sam.StateMachine) { + policies := r.GetProperty("Policies") + if policies.IsNotNil() { + if policies.IsString() { + stateMachine.ManagedPolicies = append(stateMachine.ManagedPolicies, policies.AsStringValue()) + } else if policies.IsList() { + for _, property := range policies.AsList() { + parsed, err := iamgo.Parse(property.GetJsonBytes(true)) + if err != nil { + continue + } + policy := iam.Policy{ + Metadata: property.Metadata(), + Name: iacTypes.StringDefault("", property.Metadata()), + Document: iam.Document{ + Metadata: property.Metadata(), + Parsed: *parsed, + }, + Builtin: iacTypes.Bool(false, property.Metadata()), + } + stateMachine.Policies = append(stateMachine.Policies, policy) + } + } + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/sam/tables.go b/pkg/iac/adapters/cloudformation/aws/sam/tables.go new file mode 100644 index 000000000000..89e66acdf514 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sam/tables.go @@ -0,0 +1,39 @@ +package sam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getSimpleTables(cfFile parser.FileContext) (tables []sam.SimpleTable) { + + tableResources := cfFile.GetResourcesByType("AWS::Serverless::SimpleTable") + for _, r := range tableResources { + table := sam.SimpleTable{ + Metadata: r.Metadata(), + TableName: r.GetStringProperty("TableName"), + SSESpecification: getSSESpecification(r), + } + + tables = append(tables, table) + } + + return tables +} + +func getSSESpecification(r *parser.Resource) sam.SSESpecification { + if sse := r.GetProperty("SSESpecification"); sse.IsNotNil() { + return sam.SSESpecification{ + Metadata: sse.Metadata(), + Enabled: sse.GetBoolProperty("SSEEnabled"), + KMSMasterKeyID: sse.GetStringProperty("KMSMasterKeyId"), + } + } + + return sam.SSESpecification{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + KMSMasterKeyID: iacTypes.StringDefault("", r.Metadata()), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/sns/sns.go b/pkg/iac/adapters/cloudformation/aws/sns/sns.go new file mode 100644 index 000000000000..4264077bca57 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sns/sns.go @@ -0,0 +1,13 @@ +package sns + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sns" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a SNS instance +func Adapt(cfFile parser.FileContext) sns.SNS { + return sns.SNS{ + Topics: getTopics(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/sns/sns_test.go b/pkg/iac/adapters/cloudformation/aws/sns/sns_test.go new file mode 100644 index 000000000000..25f271db2073 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sns/sns_test.go @@ -0,0 +1,54 @@ +package sns + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sns" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected sns.SNS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MySNSTopic: + Type: AWS::SNS::Topic + Properties: + KmsMasterKeyId: mykey +`, + expected: sns.SNS{ + Topics: []sns.Topic{ + { + Encryption: sns.Encryption{ + KMSKeyID: types.StringTest("mykey"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MySNSTopic: + Type: AWS::SNS::Topic + `, + expected: sns.SNS{ + Topics: []sns.Topic{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/sns/topic.go b/pkg/iac/adapters/cloudformation/aws/sns/topic.go new file mode 100644 index 000000000000..59c5672bea82 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sns/topic.go @@ -0,0 +1,24 @@ +package sns + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sns" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getTopics(ctx parser.FileContext) (topics []sns.Topic) { + for _, r := range ctx.GetResourcesByType("AWS::SNS::Topic") { + + topic := sns.Topic{ + Metadata: r.Metadata(), + ARN: types.StringDefault("", r.Metadata()), + Encryption: sns.Encryption{ + Metadata: r.Metadata(), + KMSKeyID: r.GetStringProperty("KmsMasterKeyId"), + }, + } + + topics = append(topics, topic) + } + return topics +} diff --git a/pkg/iac/adapters/cloudformation/aws/sqs/queue.go b/pkg/iac/adapters/cloudformation/aws/sqs/queue.go new file mode 100644 index 000000000000..555fd54efd90 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sqs/queue.go @@ -0,0 +1,63 @@ +package sqs + +import ( + "fmt" + + "github.com/liamg/iamgo" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sqs" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func getQueues(ctx parser.FileContext) (queues []sqs.Queue) { + for _, r := range ctx.GetResourcesByType("AWS::SQS::Queue") { + queue := sqs.Queue{ + Metadata: r.Metadata(), + QueueURL: iacTypes.StringDefault("", r.Metadata()), + Encryption: sqs.Encryption{ + Metadata: r.Metadata(), + ManagedEncryption: iacTypes.Bool(false, r.Metadata()), + KMSKeyID: r.GetStringProperty("KmsMasterKeyId"), + }, + } + if policy, err := getPolicy(r.ID(), ctx); err == nil { + queue.Policies = append(queue.Policies, *policy) + } + queues = append(queues, queue) + } + return queues +} + +func getPolicy(id string, ctx parser.FileContext) (*iam.Policy, error) { + for _, policyResource := range ctx.GetResourcesByType("AWS::SQS::QueuePolicy") { + documentProp := policyResource.GetProperty("PolicyDocument") + if documentProp.IsNil() { + continue + } + queuesProp := policyResource.GetProperty("Queues") + if queuesProp.IsNil() { + continue + } + for _, queueRef := range queuesProp.AsList() { + if queueRef.IsString() && queueRef.AsString() == id { + raw := documentProp.GetJsonBytes() + parsed, err := iamgo.Parse(raw) + if err != nil { + continue + } + return &iam.Policy{ + Metadata: documentProp.Metadata(), + Name: iacTypes.StringDefault("", documentProp.Metadata()), + Document: iam.Document{ + Metadata: documentProp.Metadata(), + Parsed: *parsed, + }, + Builtin: iacTypes.Bool(false, documentProp.Metadata()), + }, nil + } + } + } + return nil, fmt.Errorf("no matching policy found") +} diff --git a/pkg/iac/adapters/cloudformation/aws/sqs/sqs.go b/pkg/iac/adapters/cloudformation/aws/sqs/sqs.go new file mode 100644 index 000000000000..4f01ba6861b0 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sqs/sqs.go @@ -0,0 +1,13 @@ +package sqs + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sqs" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an SQS instance +func Adapt(cfFile parser.FileContext) sqs.SQS { + return sqs.SQS{ + Queues: getQueues(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go b/pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go new file mode 100644 index 000000000000..8abeff2aca3e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go @@ -0,0 +1,86 @@ +package sqs + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sqs" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/liamg/iamgo" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected sqs.SQS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MyQueue: + Type: AWS::SQS::Queue + Properties: + QueueName: "SampleQueue" + KmsMasterKeyId: mykey + SampleSQSPolicy: + Type: AWS::SQS::QueuePolicy + Properties: + Queues: + - !Ref MyQueue + PolicyDocument: + Statement: + - + Action: + - "SQS:SendMessage" + Effect: "Allow" + Resource: "arn:aws:sqs:us-east-2:444455556666:queue2" +`, + expected: sqs.SQS{ + Queues: []sqs.Queue{ + { + Encryption: sqs.Encryption{ + KMSKeyID: types.StringTest("mykey"), + }, + Policies: []iam.Policy{ + { + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"SQS:SendMessage"}). + WithResources([]string{"arn:aws:sqs:us-east-2:444455556666:queue2"}). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MySNSTopic: + Type: AWS::SQS::Queue + `, + expected: sqs.SQS{ + Queues: []sqs.Queue{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ssm/secret.go b/pkg/iac/adapters/cloudformation/aws/ssm/secret.go new file mode 100644 index 000000000000..ae3a43ee28f7 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ssm/secret.go @@ -0,0 +1,18 @@ +package ssm + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ssm" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getSecrets(ctx parser.FileContext) (secrets []ssm.Secret) { + for _, r := range ctx.GetResourcesByType("AWS::SecretsManager::Secret") { + secret := ssm.Secret{ + Metadata: r.Metadata(), + KMSKeyID: r.GetStringProperty("KmsKeyId"), + } + + secrets = append(secrets, secret) + } + return secrets +} diff --git a/pkg/iac/adapters/cloudformation/aws/ssm/ssm.go b/pkg/iac/adapters/cloudformation/aws/ssm/ssm.go new file mode 100644 index 000000000000..88bc5485fe33 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ssm/ssm.go @@ -0,0 +1,13 @@ +package ssm + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ssm" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts an SSM instance +func Adapt(cfFile parser.FileContext) ssm.SSM { + return ssm.SSM{ + Secrets: getSecrets(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ssm/ssm_test.go b/pkg/iac/adapters/cloudformation/aws/ssm/ssm_test.go new file mode 100644 index 000000000000..9709207fec66 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ssm/ssm_test.go @@ -0,0 +1,53 @@ +package ssm + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ssm" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected ssm.SSM + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MySecretA: + Type: 'AWS::SecretsManager::Secret' + Properties: + Name: MySecretForAppA + KmsKeyId: alias/exampleAlias +`, + expected: ssm.SSM{ + Secrets: []ssm.Secret{ + { + KMSKeyID: types.StringTest("alias/exampleAlias"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MySecretA: + Type: 'AWS::SecretsManager::Secret' + `, + expected: ssm.SSM{ + Secrets: []ssm.Secret{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/workspaces/workspace.go b/pkg/iac/adapters/cloudformation/aws/workspaces/workspace.go new file mode 100644 index 000000000000..3966468849cc --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/workspaces/workspace.go @@ -0,0 +1,31 @@ +package workspaces + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/workspaces" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func getWorkSpaces(ctx parser.FileContext) (workSpaces []workspaces.WorkSpace) { + for _, r := range ctx.GetResourcesByType("AWS::WorkSpaces::Workspace") { + workspace := workspaces.WorkSpace{ + Metadata: r.Metadata(), + RootVolume: workspaces.Volume{ + Metadata: r.Metadata(), + Encryption: workspaces.Encryption{ + Metadata: r.Metadata(), + Enabled: r.GetBoolProperty("RootVolumeEncryptionEnabled"), + }, + }, + UserVolume: workspaces.Volume{ + Metadata: r.Metadata(), + Encryption: workspaces.Encryption{ + Metadata: r.Metadata(), + Enabled: r.GetBoolProperty("UserVolumeEncryptionEnabled"), + }, + }, + } + + workSpaces = append(workSpaces, workspace) + } + return workSpaces +} diff --git a/pkg/iac/adapters/cloudformation/aws/workspaces/workspaces.go b/pkg/iac/adapters/cloudformation/aws/workspaces/workspaces.go new file mode 100644 index 000000000000..578fa0d507c3 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/workspaces/workspaces.go @@ -0,0 +1,13 @@ +package workspaces + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/workspaces" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +// Adapt adapts a Workspaces instance +func Adapt(cfFile parser.FileContext) workspaces.WorkSpaces { + return workspaces.WorkSpaces{ + WorkSpaces: getWorkSpaces(cfFile), + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/workspaces/workspaces_test.go b/pkg/iac/adapters/cloudformation/aws/workspaces/workspaces_test.go new file mode 100644 index 000000000000..41e821e6466d --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/workspaces/workspaces_test.go @@ -0,0 +1,62 @@ +package workspaces + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/workspaces" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected workspaces.WorkSpaces + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MyWorkSpace: + Type: AWS::WorkSpaces::Workspace + Properties: + RootVolumeEncryptionEnabled: true + UserVolumeEncryptionEnabled: true +`, + expected: workspaces.WorkSpaces{ + WorkSpaces: []workspaces.WorkSpace{ + { + RootVolume: workspaces.Volume{ + Encryption: workspaces.Encryption{ + Enabled: types.BoolTest(true), + }, + }, + UserVolume: workspaces.Volume{ + Encryption: workspaces.Encryption{ + Enabled: types.BoolTest(true), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyWorkSpace: + Type: AWS::WorkSpaces::Workspace + `, + expected: workspaces.WorkSpaces{ + WorkSpaces: []workspaces.WorkSpace{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/testutil/testutil.go b/pkg/iac/adapters/cloudformation/testutil/testutil.go new file mode 100644 index 000000000000..f908519d4106 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/testutil/testutil.go @@ -0,0 +1,25 @@ +package testutil + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +type adaptFn[T any] func(fctx parser.FileContext) T + +func AdaptAndCompare[T any](t *testing.T, source string, expected any, fn adaptFn[T]) { + fsys := testutil.CreateFS(t, map[string]string{ + "main.yaml": source, + }) + + fctx, err := parser.New().ParseFile(context.TODO(), fsys, "main.yaml") + require.NoError(t, err) + + adapted := fn(*fctx) + testutil.AssertDefsecEqual(t, expected, adapted) +} diff --git a/pkg/iac/adapters/terraform/adapt.go b/pkg/iac/adapters/terraform/adapt.go new file mode 100644 index 000000000000..9028e7cc15f2 --- /dev/null +++ b/pkg/iac/adapters/terraform/adapt.go @@ -0,0 +1,31 @@ +package terraform + +import ( + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/azure" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/cloudstack" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/digitalocean" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/github" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/google" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/kubernetes" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/nifcloud" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/openstack" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/oracle" + "github.com/aquasecurity/trivy/pkg/iac/state" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) *state.State { + return &state.State{ + AWS: aws.Adapt(modules), + Azure: azure.Adapt(modules), + CloudStack: cloudstack.Adapt(modules), + DigitalOcean: digitalocean.Adapt(modules), + GitHub: github.Adapt(modules), + Google: google.Adapt(modules), + Kubernetes: kubernetes.Adapt(modules), + Nifcloud: nifcloud.Adapt(modules), + OpenStack: openstack.Adapt(modules), + Oracle: oracle.Adapt(modules), + } +} diff --git a/pkg/iac/adapters/terraform/aws/accessanalyzer/accessanalyzer.go b/pkg/iac/adapters/terraform/aws/accessanalyzer/accessanalyzer.go new file mode 100644 index 000000000000..93c76f979d55 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/accessanalyzer/accessanalyzer.go @@ -0,0 +1,40 @@ +package accessanalyzer + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/accessanalyzer" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) accessanalyzer.AccessAnalyzer { + return accessanalyzer.AccessAnalyzer{ + Analyzers: adaptTrails(modules), + } +} + +func adaptTrails(modules terraform.Modules) []accessanalyzer.Analyzer { + var analyzer []accessanalyzer.Analyzer + + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_accessanalyzer_analyzer") { + analyzer = append(analyzer, adaptAnalyzers(resource)) + } + } + return analyzer +} + +func adaptAnalyzers(resource *terraform.Block) accessanalyzer.Analyzer { + + analyzerName := resource.GetAttribute("analyzer_name") + analyzerNameAttr := analyzerName.AsStringValueOrDefault("", resource) + + arnAnalyzer := resource.GetAttribute("arn") + arnAnalyzerAttr := arnAnalyzer.AsStringValueOrDefault("", resource) + + return accessanalyzer.Analyzer{ + Metadata: resource.GetMetadata(), + Name: analyzerNameAttr, + ARN: arnAnalyzerAttr, + Active: types.BoolDefault(false, resource.GetMetadata()), + } +} diff --git a/pkg/iac/adapters/terraform/aws/adapt.go b/pkg/iac/adapters/terraform/aws/adapt.go new file mode 100644 index 000000000000..c9d9d94ecea6 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/adapt.go @@ -0,0 +1,79 @@ +package aws + +import ( + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/apigateway" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/athena" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/cloudfront" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/cloudtrail" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/cloudwatch" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/codebuild" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/config" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/documentdb" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/dynamodb" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/ecr" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/ecs" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/efs" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/eks" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/elasticache" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/elasticsearch" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/elb" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/emr" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/kinesis" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/kms" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/lambda" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/mq" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/msk" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/neptune" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/provider" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/rds" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/redshift" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/s3" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/sns" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/sqs" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/ssm" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/workspaces" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) aws.AWS { + return aws.AWS{ + Meta: aws.Meta{ + TFProviders: provider.Adapt(modules), + }, + APIGateway: apigateway.Adapt(modules), + Athena: athena.Adapt(modules), + Cloudfront: cloudfront.Adapt(modules), + CloudTrail: cloudtrail.Adapt(modules), + CloudWatch: cloudwatch.Adapt(modules), + CodeBuild: codebuild.Adapt(modules), + Config: config.Adapt(modules), + DocumentDB: documentdb.Adapt(modules), + DynamoDB: dynamodb.Adapt(modules), + EC2: ec2.Adapt(modules), + ECR: ecr.Adapt(modules), + ECS: ecs.Adapt(modules), + EFS: efs.Adapt(modules), + EKS: eks.Adapt(modules), + ElastiCache: elasticache.Adapt(modules), + Elasticsearch: elasticsearch.Adapt(modules), + ELB: elb.Adapt(modules), + EMR: emr.Adapt(modules), + IAM: iam.Adapt(modules), + Kinesis: kinesis.Adapt(modules), + KMS: kms.Adapt(modules), + Lambda: lambda.Adapt(modules), + MQ: mq.Adapt(modules), + MSK: msk.Adapt(modules), + Neptune: neptune.Adapt(modules), + RDS: rds.Adapt(modules), + Redshift: redshift.Adapt(modules), + S3: s3.Adapt(modules), + SNS: sns.Adapt(modules), + SQS: sqs.Adapt(modules), + SSM: ssm.Adapt(modules), + WorkSpaces: workspaces.Adapt(modules), + } +} diff --git a/pkg/iac/adapters/terraform/aws/apigateway/adapt.go b/pkg/iac/adapters/terraform/aws/apigateway/adapt.go new file mode 100644 index 000000000000..bf0e4ca86379 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/apigateway/adapt.go @@ -0,0 +1,21 @@ +package apigateway + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway" + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) apigateway.APIGateway { + return apigateway.APIGateway{ + V1: v1.APIGateway{ + APIs: adaptAPIsV1(modules), + DomainNames: adaptDomainNamesV1(modules), + }, + V2: v2.APIGateway{ + APIs: adaptAPIsV2(modules), + DomainNames: adaptDomainNamesV2(modules), + }, + } +} diff --git a/pkg/iac/adapters/terraform/aws/apigateway/adapt_test.go b/pkg/iac/adapters/terraform/aws/apigateway/adapt_test.go new file mode 100644 index 000000000000..92d96f396e9c --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/apigateway/adapt_test.go @@ -0,0 +1,233 @@ +package apigateway + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway" + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected apigateway.APIGateway + }{ + { + name: "basic", + terraform: ` +resource "aws_api_gateway_rest_api" "MyDemoAPI" { + name = "MyDemoAPI" + description = "This is my API for demonstration purposes" +} +resource "aws_api_gateway_resource" "example" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id +} +resource "aws_api_gateway_method" "example" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id + resource_id = aws_api_gateway_resource.example.id + http_method = "GET" + authorization = "NONE" +} +resource "aws_apigatewayv2_api" "example" { + name = "tfsec" + protocol_type = "HTTP" +} + + +resource "aws_apigatewayv2_stage" "example" { + api_id = aws_apigatewayv2_api.example.id + name = "tfsec" + access_log_settings { + destination_arn = "arn:123" + } +} + +resource "aws_api_gateway_domain_name" "example" { + domain_name = "v1.com" + security_policy = "TLS_1_0" +} + +resource "aws_apigatewayv2_domain_name" "example" { + domain_name = "v2.com" + domain_name_configuration { + security_policy = "TLS_1_2" + } +} +`, + expected: apigateway.APIGateway{ + V1: v1.APIGateway{ + APIs: []v1.API{ + { + Metadata: iacTypes.Metadata{}, + Name: String("MyDemoAPI"), + Resources: []v1.Resource{ + { + Methods: []v1.Method{ + { + HTTPMethod: String("GET"), + AuthorizationType: String("NONE"), + APIKeyRequired: Bool(false), + }, + }, + }, + }, + }, + }, + DomainNames: []v1.DomainName{ + { + Name: String("v1.com"), + SecurityPolicy: String("TLS_1_0"), + }, + }, + }, + V2: v2.APIGateway{ + APIs: []v2.API{ + { + Name: String("tfsec"), + ProtocolType: String("HTTP"), + Stages: []v2.Stage{ + { + Name: String("tfsec"), + AccessLogging: v2.AccessLogging{ + CloudwatchLogGroupARN: String("arn:123"), + }, + }, + }, + }, + }, + DomainNames: []v2.DomainName{ + { + Name: String("v2.com"), + SecurityPolicy: String("TLS_1_2"), + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Int(i int) iacTypes.IntValue { + return iacTypes.Int(i, iacTypes.NewTestMetadata()) +} + +func Bool(b bool) iacTypes.BoolValue { + return iacTypes.Bool(b, iacTypes.NewTestMetadata()) +} + +func String(s string) iacTypes.StringValue { + return iacTypes.String(s, iacTypes.NewTestMetadata()) +} +func TestLines(t *testing.T) { + src := ` + resource "aws_api_gateway_rest_api" "MyDemoAPI" { + name = "MyDemoAPI" + description = "This is my API for demonstration purposes" + } + + resource "aws_api_gateway_resource" "example" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id + } + + resource "aws_api_gateway_method" "example" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id + resource_id = aws_api_gateway_resource.example.id + http_method = "GET" + authorization = "NONE" + api_key_required = true + } + + resource "aws_apigatewayv2_api" "example" { + name = "tfsec" + protocol_type = "HTTP" + } + + resource "aws_apigatewayv2_stage" "example" { + api_id = aws_apigatewayv2_api.example.id + name = "tfsec" + access_log_settings { + destination_arn = "arn:123" + } + } + + resource "aws_api_gateway_domain_name" "example" { + domain_name = "v1.com" + security_policy = "TLS_1_0" + } + + ` + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.V1.APIs, 1) + require.Len(t, adapted.V2.APIs, 1) + require.Len(t, adapted.V1.DomainNames, 1) + + apiV1 := adapted.V1.APIs[0] + apiV2 := adapted.V2.APIs[0] + domainName := adapted.V1.DomainNames[0] + + assert.Equal(t, 2, apiV1.Metadata.Range().GetStartLine()) + assert.Equal(t, 5, apiV1.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, apiV1.Name.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, apiV1.Name.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, apiV1.Resources[0].Methods[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 17, apiV1.Resources[0].Methods[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 14, apiV1.Resources[0].Methods[0].HTTPMethod.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 14, apiV1.Resources[0].Methods[0].HTTPMethod.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 15, apiV1.Resources[0].Methods[0].AuthorizationType.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 15, apiV1.Resources[0].Methods[0].AuthorizationType.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 16, apiV1.Resources[0].Methods[0].APIKeyRequired.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 16, apiV1.Resources[0].Methods[0].APIKeyRequired.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 19, apiV2.Metadata.Range().GetStartLine()) + assert.Equal(t, 22, apiV2.Metadata.Range().GetEndLine()) + + assert.Equal(t, 20, apiV2.Name.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 20, apiV2.Name.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 21, apiV2.ProtocolType.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 21, apiV2.ProtocolType.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 24, apiV2.Stages[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 30, apiV2.Stages[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 26, apiV2.Stages[0].Name.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 26, apiV2.Stages[0].Name.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 27, apiV2.Stages[0].AccessLogging.Metadata.Range().GetStartLine()) + assert.Equal(t, 29, apiV2.Stages[0].AccessLogging.Metadata.Range().GetEndLine()) + + assert.Equal(t, 28, apiV2.Stages[0].AccessLogging.CloudwatchLogGroupARN.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 28, apiV2.Stages[0].AccessLogging.CloudwatchLogGroupARN.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 32, domainName.Metadata.Range().GetStartLine()) + assert.Equal(t, 35, domainName.Metadata.Range().GetEndLine()) + + assert.Equal(t, 33, domainName.Name.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 33, domainName.Name.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 34, domainName.SecurityPolicy.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 34, domainName.SecurityPolicy.GetMetadata().Range().GetEndLine()) + +} diff --git a/pkg/iac/adapters/terraform/aws/apigateway/apiv1.go b/pkg/iac/adapters/terraform/aws/apigateway/apiv1.go new file mode 100644 index 000000000000..3f03817b9e1b --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/apigateway/apiv1.go @@ -0,0 +1,115 @@ +package apigateway + +import ( + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func adaptAPIResourcesV1(modules terraform.Modules, apiBlock *terraform.Block) []v1.Resource { + var resources []v1.Resource + for _, resourceBlock := range modules.GetReferencingResources(apiBlock, "aws_api_gateway_resource", "rest_api_id") { + method := v1.Resource{ + Metadata: resourceBlock.GetMetadata(), + Methods: adaptAPIMethodsV1(modules, resourceBlock), + } + resources = append(resources, method) + } + return resources +} + +func adaptAPIMethodsV1(modules terraform.Modules, resourceBlock *terraform.Block) []v1.Method { + var methods []v1.Method + for _, methodBlock := range modules.GetReferencingResources(resourceBlock, "aws_api_gateway_method", "resource_id") { + method := v1.Method{ + Metadata: methodBlock.GetMetadata(), + HTTPMethod: methodBlock.GetAttribute("http_method").AsStringValueOrDefault("", methodBlock), + AuthorizationType: methodBlock.GetAttribute("authorization").AsStringValueOrDefault("", methodBlock), + APIKeyRequired: methodBlock.GetAttribute("api_key_required").AsBoolValueOrDefault(false, methodBlock), + } + methods = append(methods, method) + } + return methods +} + +func adaptAPIsV1(modules terraform.Modules) []v1.API { + + var apis []v1.API + apiStageIDs := modules.GetChildResourceIDMapByType("aws_api_gateway_stage") + + for _, apiBlock := range modules.GetResourcesByType("aws_api_gateway_rest_api") { + api := v1.API{ + Metadata: apiBlock.GetMetadata(), + Name: apiBlock.GetAttribute("name").AsStringValueOrDefault("", apiBlock), + Stages: nil, + Resources: adaptAPIResourcesV1(modules, apiBlock), + } + + for _, stageBlock := range modules.GetReferencingResources(apiBlock, "aws_api_gateway_stage", "rest_api_id") { + apiStageIDs.Resolve(stageBlock.ID()) + stage := adaptStageV1(stageBlock, modules) + + api.Stages = append(api.Stages, stage) + } + + apis = append(apis, api) + } + + orphanResources := modules.GetResourceByIDs(apiStageIDs.Orphans()...) + + if len(orphanResources) > 0 { + orphanage := v1.API{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Name: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + } + for _, stage := range orphanResources { + orphanage.Stages = append(orphanage.Stages, adaptStageV1(stage, modules)) + } + apis = append(apis, orphanage) + } + + return apis +} + +func adaptStageV1(stageBlock *terraform.Block, modules terraform.Modules) v1.Stage { + stage := v1.Stage{ + Metadata: stageBlock.GetMetadata(), + Name: stageBlock.GetAttribute("name").AsStringValueOrDefault("", stageBlock), + AccessLogging: v1.AccessLogging{ + Metadata: stageBlock.GetMetadata(), + CloudwatchLogGroupARN: iacTypes.StringDefault("", stageBlock.GetMetadata()), + }, + XRayTracingEnabled: stageBlock.GetAttribute("xray_tracing_enabled").AsBoolValueOrDefault(false, stageBlock), + } + for _, methodSettings := range modules.GetReferencingResources(stageBlock, "aws_api_gateway_method_settings", "stage_name") { + + restMethodSettings := v1.RESTMethodSettings{ + Metadata: methodSettings.GetMetadata(), + Method: iacTypes.String("", methodSettings.GetMetadata()), + CacheDataEncrypted: iacTypes.BoolDefault(false, methodSettings.GetMetadata()), + CacheEnabled: iacTypes.BoolDefault(false, methodSettings.GetMetadata()), + } + + if settings := methodSettings.GetBlock("settings"); settings.IsNotNil() { + if encrypted := settings.GetAttribute("cache_data_encrypted"); encrypted.IsNotNil() { + restMethodSettings.CacheDataEncrypted = settings.GetAttribute("cache_data_encrypted").AsBoolValueOrDefault(false, settings) + } + if encrypted := settings.GetAttribute("caching_enabled"); encrypted.IsNotNil() { + restMethodSettings.CacheEnabled = settings.GetAttribute("caching_enabled").AsBoolValueOrDefault(false, settings) + } + } + + stage.RESTMethodSettings = append(stage.RESTMethodSettings, restMethodSettings) + } + + stage.Name = stageBlock.GetAttribute("stage_name").AsStringValueOrDefault("", stageBlock) + if accessLogging := stageBlock.GetBlock("access_log_settings"); accessLogging.IsNotNil() { + stage.AccessLogging.Metadata = accessLogging.GetMetadata() + stage.AccessLogging.CloudwatchLogGroupARN = accessLogging.GetAttribute("destination_arn").AsStringValueOrDefault("", accessLogging) + } else { + stage.AccessLogging.Metadata = stageBlock.GetMetadata() + stage.AccessLogging.CloudwatchLogGroupARN = iacTypes.StringDefault("", stageBlock.GetMetadata()) + } + + return stage +} diff --git a/pkg/iac/adapters/terraform/aws/apigateway/apiv1_test.go b/pkg/iac/adapters/terraform/aws/apigateway/apiv1_test.go new file mode 100644 index 000000000000..cf69e8d0ee23 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/apigateway/apiv1_test.go @@ -0,0 +1,125 @@ +package apigateway + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" +) + +func Test_adaptAPIMethodsV1(t *testing.T) { + tests := []struct { + name string + terraform string + expected []v1.Method + }{ + { + name: "defaults", + terraform: ` +resource "aws_api_gateway_rest_api" "MyDemoAPI" { + name = "MyDemoAPI" + description = "This is my API for demonstration purposes" +} + +resource "aws_api_gateway_resource" "example" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id +} + +resource "aws_api_gateway_method" "example" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id + resource_id = aws_api_gateway_resource.example.id + http_method = "GET" + authorization = "NONE" +} +`, + expected: []v1.Method{ + { + HTTPMethod: String("GET"), + AuthorizationType: String("NONE"), + APIKeyRequired: Bool(false), + }, + }, + }, + { + name: "basic", + terraform: ` +resource "aws_api_gateway_rest_api" "MyDemoAPI" { + name = "MyDemoAPI" + description = "This is my API for demonstration purposes" +} + +resource "aws_api_gateway_resource" "example" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id +} + +resource "aws_api_gateway_method" "example" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id + resource_id = aws_api_gateway_resource.example.id + http_method = "GET" + authorization = "NONE" + api_key_required = true +} +`, + expected: []v1.Method{ + { + HTTPMethod: String("GET"), + AuthorizationType: String("NONE"), + APIKeyRequired: Bool(true), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + restApiBlock := modules.GetBlocks()[1] + adapted := adaptAPIMethodsV1(modules, restApiBlock) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptAPIsV1(t *testing.T) { + tests := []struct { + name string + terraform string + expected []v1.API + }{ + { + name: "defaults", + terraform: ` +resource "aws_api_gateway_rest_api" "example" { + +} +`, + expected: []v1.API{ + { + Name: String(""), + }, + }, + }, + { + name: "full", + terraform: ` +resource "aws_api_gateway_rest_api" "example" { + name = "tfsec" +} +`, + expected: []v1.API{ + { + Name: String("tfsec"), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptAPIsV1(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/aws/apigateway/apiv2.go b/pkg/iac/adapters/terraform/aws/apigateway/apiv2.go new file mode 100644 index 000000000000..8a6d12679802 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/apigateway/apiv2.go @@ -0,0 +1,69 @@ +package apigateway + +import ( + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func adaptAPIsV2(modules terraform.Modules) []v2.API { + + var apis []v2.API + apiStageIDs := modules.GetChildResourceIDMapByType("aws_apigatewayv2_stage") + + for _, module := range modules { + for _, apiBlock := range module.GetResourcesByType("aws_apigatewayv2_api") { + api := v2.API{ + Metadata: apiBlock.GetMetadata(), + Name: apiBlock.GetAttribute("name").AsStringValueOrDefault("", apiBlock), + ProtocolType: apiBlock.GetAttribute("protocol_type").AsStringValueOrDefault("", apiBlock), + Stages: nil, + } + + for _, stageBlock := range module.GetReferencingResources(apiBlock, "aws_apigatewayv2_stage", "api_id") { + apiStageIDs.Resolve(stageBlock.ID()) + + stage := adaptStageV2(stageBlock) + + api.Stages = append(api.Stages, stage) + } + + apis = append(apis, api) + } + } + + orphanResources := modules.GetResourceByIDs(apiStageIDs.Orphans()...) + if len(orphanResources) > 0 { + orphanage := v2.API{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Name: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + ProtocolType: iacTypes.StringUnresolvable(iacTypes.NewUnmanagedMetadata()), + Stages: nil, + } + for _, stage := range orphanResources { + orphanage.Stages = append(orphanage.Stages, adaptStageV2(stage)) + } + apis = append(apis, orphanage) + } + + return apis +} + +func adaptStageV2(stageBlock *terraform.Block) v2.Stage { + stage := v2.Stage{ + Metadata: stageBlock.GetMetadata(), + Name: stageBlock.GetAttribute("name").AsStringValueOrDefault("", stageBlock), + AccessLogging: v2.AccessLogging{ + Metadata: stageBlock.GetMetadata(), + CloudwatchLogGroupARN: iacTypes.StringDefault("", stageBlock.GetMetadata()), + }, + } + if accessLogging := stageBlock.GetBlock("access_log_settings"); accessLogging.IsNotNil() { + stage.AccessLogging.Metadata = accessLogging.GetMetadata() + stage.AccessLogging.CloudwatchLogGroupARN = accessLogging.GetAttribute("destination_arn").AsStringValueOrDefault("", accessLogging) + } else { + stage.AccessLogging.Metadata = stageBlock.GetMetadata() + stage.AccessLogging.CloudwatchLogGroupARN = iacTypes.StringDefault("", stageBlock.GetMetadata()) + } + return stage +} diff --git a/pkg/iac/adapters/terraform/aws/apigateway/apiv2_test.go b/pkg/iac/adapters/terraform/aws/apigateway/apiv2_test.go new file mode 100644 index 000000000000..fa73981c80e3 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/apigateway/apiv2_test.go @@ -0,0 +1,103 @@ +package apigateway + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" +) + +func Test_adaptAPIsV2(t *testing.T) { + tests := []struct { + name string + terraform string + expected []v2.API + }{ + { + name: "defaults", + terraform: ` +resource "aws_apigatewayv2_api" "example" { + protocol_type = "HTTP" +} +`, + expected: []v2.API{ + { + Name: String(""), + ProtocolType: String("HTTP"), + }, + }, + }, + { + name: "full", + terraform: ` +resource "aws_apigatewayv2_api" "example" { + name = "tfsec" + protocol_type = "HTTP" +} +`, + expected: []v2.API{ + { + Name: String("tfsec"), + ProtocolType: String("HTTP"), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptAPIsV2(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptStageV2(t *testing.T) { + tests := []struct { + name string + terraform string + expected v2.Stage + }{ + { + name: "defaults", + terraform: ` +resource "aws_apigatewayv2_stage" "example" { + +} +`, + expected: v2.Stage{ + Name: String(""), + AccessLogging: v2.AccessLogging{ + CloudwatchLogGroupARN: String(""), + }, + }, + }, + { + name: "basics", + terraform: ` +resource "aws_apigatewayv2_stage" "example" { + name = "tfsec" + access_log_settings { + destination_arn = "arn:123" + } +} +`, + expected: v2.Stage{ + Name: String("tfsec"), + AccessLogging: v2.AccessLogging{ + CloudwatchLogGroupARN: String("arn:123"), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptStageV2(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/aws/apigateway/namesv1.go b/pkg/iac/adapters/terraform/aws/apigateway/namesv1.go new file mode 100644 index 000000000000..7bc6af3edd4c --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/apigateway/namesv1.go @@ -0,0 +1,24 @@ +package apigateway + +import ( + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func adaptDomainNamesV1(modules terraform.Modules) []v1.DomainName { + + var domainNames []v1.DomainName + + for _, module := range modules { + for _, nameBlock := range module.GetResourcesByType("aws_api_gateway_domain_name") { + domainName := v1.DomainName{ + Metadata: nameBlock.GetMetadata(), + Name: nameBlock.GetAttribute("domain_name").AsStringValueOrDefault("", nameBlock), + SecurityPolicy: nameBlock.GetAttribute("security_policy").AsStringValueOrDefault("TLS_1_0", nameBlock), + } + domainNames = append(domainNames, domainName) + } + } + + return domainNames +} diff --git a/pkg/iac/adapters/terraform/aws/apigateway/namesv1_test.go b/pkg/iac/adapters/terraform/aws/apigateway/namesv1_test.go new file mode 100644 index 000000000000..bff0ac47848e --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/apigateway/namesv1_test.go @@ -0,0 +1,54 @@ +package apigateway + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" +) + +func Test_adaptDomainNamesV1(t *testing.T) { + tests := []struct { + name string + terraform string + expected []v1.DomainName + }{ + { + name: "defaults", + terraform: ` +resource "aws_api_gateway_domain_name" "example" { +} +`, + expected: []v1.DomainName{ + { + Name: String(""), + SecurityPolicy: String("TLS_1_0"), + }, + }, + }, + { + name: "basic", + terraform: ` +resource "aws_api_gateway_domain_name" "example" { + domain_name = "testing.com" + security_policy = "TLS_1_2" +} +`, + expected: []v1.DomainName{ + { + Name: String("testing.com"), + SecurityPolicy: String("TLS_1_2"), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptDomainNamesV1(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/aws/apigateway/namesv2.go b/pkg/iac/adapters/terraform/aws/apigateway/namesv2.go new file mode 100644 index 000000000000..e7beb2d6ed2f --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/apigateway/namesv2.go @@ -0,0 +1,28 @@ +package apigateway + +import ( + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func adaptDomainNamesV2(modules terraform.Modules) []v2.DomainName { + + var domainNames []v2.DomainName + + for _, module := range modules { + for _, nameBlock := range module.GetResourcesByType("aws_apigatewayv2_domain_name") { + domainName := v2.DomainName{ + Metadata: nameBlock.GetMetadata(), + Name: nameBlock.GetAttribute("domain_name").AsStringValueOrDefault("", nameBlock), + SecurityPolicy: types.StringDefault("TLS_1_0", nameBlock.GetMetadata()), + } + if config := nameBlock.GetBlock("domain_name_configuration"); config.IsNotNil() { + domainName.SecurityPolicy = config.GetAttribute("security_policy").AsStringValueOrDefault("TLS_1_0", config) + } + domainNames = append(domainNames, domainName) + } + } + + return domainNames +} diff --git a/pkg/iac/adapters/terraform/aws/apigateway/namesv2_test.go b/pkg/iac/adapters/terraform/aws/apigateway/namesv2_test.go new file mode 100644 index 000000000000..25c40ebcc231 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/apigateway/namesv2_test.go @@ -0,0 +1,56 @@ +package apigateway + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" +) + +func Test_adaptDomainNamesV2(t *testing.T) { + tests := []struct { + name string + terraform string + expected []v2.DomainName + }{ + { + name: "defaults", + terraform: ` +resource "aws_apigatewayv2_domain_name" "example" { +} +`, + expected: []v2.DomainName{ + { + Name: String(""), + SecurityPolicy: String("TLS_1_0"), + }, + }, + }, + { + name: "fully populated", + terraform: ` +resource "aws_apigatewayv2_domain_name" "example" { + domain_name = "testing.com" + domain_name_configuration { + security_policy = "TLS_1_2" + } +} +`, + expected: []v2.DomainName{ + { + Name: String("testing.com"), + SecurityPolicy: String("TLS_1_2"), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptDomainNamesV2(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/aws/athena/adapt.go b/pkg/iac/adapters/terraform/aws/athena/adapt.go new file mode 100644 index 000000000000..f8f56df99e87 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/athena/adapt.go @@ -0,0 +1,80 @@ +package athena + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/athena" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) athena.Athena { + return athena.Athena{ + Databases: adaptDatabases(modules), + Workgroups: adaptWorkgroups(modules), + } +} + +func adaptDatabases(modules terraform.Modules) []athena.Database { + var databases []athena.Database + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_athena_database") { + databases = append(databases, adaptDatabase(resource)) + } + } + return databases +} + +func adaptWorkgroups(modules terraform.Modules) []athena.Workgroup { + var workgroups []athena.Workgroup + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_athena_workgroup") { + workgroups = append(workgroups, adaptWorkgroup(resource)) + } + } + return workgroups +} + +func adaptDatabase(resource *terraform.Block) athena.Database { + database := athena.Database{ + Metadata: resource.GetMetadata(), + Name: resource.GetAttribute("name").AsStringValueOrDefault("", resource), + Encryption: athena.EncryptionConfiguration{ + Metadata: resource.GetMetadata(), + Type: iacTypes.StringDefault("", resource.GetMetadata()), + }, + } + if encryptionConfigBlock := resource.GetBlock("encryption_configuration"); encryptionConfigBlock.IsNotNil() { + database.Encryption.Metadata = encryptionConfigBlock.GetMetadata() + encryptionOptionAttr := encryptionConfigBlock.GetAttribute("encryption_option") + database.Encryption.Type = encryptionOptionAttr.AsStringValueOrDefault("", encryptionConfigBlock) + } + + return database +} + +func adaptWorkgroup(resource *terraform.Block) athena.Workgroup { + workgroup := athena.Workgroup{ + Metadata: resource.GetMetadata(), + Name: resource.GetAttribute("name").AsStringValueOrDefault("", resource), + Encryption: athena.EncryptionConfiguration{ + Metadata: resource.GetMetadata(), + Type: iacTypes.StringDefault("", resource.GetMetadata()), + }, + EnforceConfiguration: iacTypes.BoolDefault(false, resource.GetMetadata()), + } + + if configBlock := resource.GetBlock("configuration"); configBlock.IsNotNil() { + + enforceWGConfigAttr := configBlock.GetAttribute("enforce_workgroup_configuration") + workgroup.EnforceConfiguration = enforceWGConfigAttr.AsBoolValueOrDefault(true, configBlock) + + if resultConfigBlock := configBlock.GetBlock("result_configuration"); configBlock.IsNotNil() { + if encryptionConfigBlock := resultConfigBlock.GetBlock("encryption_configuration"); encryptionConfigBlock.IsNotNil() { + encryptionOptionAttr := encryptionConfigBlock.GetAttribute("encryption_option") + workgroup.Encryption.Metadata = encryptionConfigBlock.GetMetadata() + workgroup.Encryption.Type = encryptionOptionAttr.AsStringValueOrDefault("", encryptionConfigBlock) + } + } + } + + return workgroup +} diff --git a/pkg/iac/adapters/terraform/aws/athena/adapt_test.go b/pkg/iac/adapters/terraform/aws/athena/adapt_test.go new file mode 100644 index 000000000000..e734b024b274 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/athena/adapt_test.go @@ -0,0 +1,210 @@ +package athena + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/athena" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptDatabase(t *testing.T) { + tests := []struct { + name string + terraform string + expected athena.Database + }{ + { + name: "athena database", + terraform: ` + resource "aws_athena_database" "my_wg" { + name = "database_name" + + encryption_configuration { + encryption_option = "SSE_KMS" + } + } +`, + expected: athena.Database{ + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("database_name", iacTypes.NewTestMetadata()), + Encryption: athena.EncryptionConfiguration{ + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String(athena.EncryptionTypeSSEKMS, iacTypes.NewTestMetadata()), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptDatabase(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptWorkgroup(t *testing.T) { + tests := []struct { + name string + terraform string + expected athena.Workgroup + }{ + { + name: "encryption type SSE KMS", + terraform: ` + resource "aws_athena_workgroup" "my_wg" { + name = "example" + + configuration { + enforce_workgroup_configuration = true + + result_configuration { + encryption_configuration { + encryption_option = "SSE_KMS" + } + } + } + } +`, + expected: athena.Workgroup{ + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("example", iacTypes.NewTestMetadata()), + Encryption: athena.EncryptionConfiguration{ + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String(athena.EncryptionTypeSSEKMS, iacTypes.NewTestMetadata()), + }, + EnforceConfiguration: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + { + name: "configuration not enforced", + terraform: ` + resource "aws_athena_workgroup" "my_wg" { + name = "example" + + configuration { + enforce_workgroup_configuration = false + + result_configuration { + encryption_configuration { + encryption_option = "SSE_KMS" + } + } + } + } +`, + expected: athena.Workgroup{ + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("example", iacTypes.NewTestMetadata()), + Encryption: athena.EncryptionConfiguration{ + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String(athena.EncryptionTypeSSEKMS, iacTypes.NewTestMetadata()), + }, + EnforceConfiguration: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + { + name: "enforce configuration defaults to true", + terraform: ` + resource "aws_athena_workgroup" "my_wg" { + name = "example" + + configuration { + result_configuration { + encryption_configuration { + encryption_option = "" + } + } + } + } +`, + expected: athena.Workgroup{ + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("example", iacTypes.NewTestMetadata()), + Encryption: athena.EncryptionConfiguration{ + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String(athena.EncryptionTypeNone, iacTypes.NewTestMetadata()), + }, + EnforceConfiguration: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + { + name: "missing configuration block", + terraform: ` + resource "aws_athena_workgroup" "my_wg" { + name = "example" + } +`, + expected: athena.Workgroup{ + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("example", iacTypes.NewTestMetadata()), + Encryption: athena.EncryptionConfiguration{ + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String(athena.EncryptionTypeNone, iacTypes.NewTestMetadata()), + }, + EnforceConfiguration: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptWorkgroup(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_athena_database" "good_example" { + name = "database_name" + bucket = aws_s3_bucket.hoge.bucket + + encryption_configuration { + encryption_option = "SSE_KMS" + kms_key_arn = aws_kms_key.example.arn + } + } + + resource "aws_athena_workgroup" "good_example" { + name = "example" + + configuration { + enforce_workgroup_configuration = true + publish_cloudwatch_metrics_enabled = true + + result_configuration { + output_location = "s3://${aws_s3_bucket.example.bucket}/output/" + + encryption_configuration { + encryption_option = "SSE_KMS" + kms_key_arn = aws_kms_key.example.arn + } + } + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Databases, 1) + require.Len(t, adapted.Workgroups, 1) + + assert.Equal(t, 7, adapted.Databases[0].Encryption.Type.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, adapted.Databases[0].Encryption.Type.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 16, adapted.Workgroups[0].EnforceConfiguration.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 16, adapted.Workgroups[0].EnforceConfiguration.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 23, adapted.Workgroups[0].Encryption.Type.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 23, adapted.Workgroups[0].Encryption.Type.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go b/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go new file mode 100644 index 000000000000..a981241a83a4 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go @@ -0,0 +1,79 @@ +package cloudfront + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) cloudfront.Cloudfront { + return cloudfront.Cloudfront{ + Distributions: adaptDistributions(modules), + } +} + +func adaptDistributions(modules terraform.Modules) []cloudfront.Distribution { + var distributions []cloudfront.Distribution + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_cloudfront_distribution") { + distributions = append(distributions, adaptDistribution(resource)) + } + } + return distributions +} + +func adaptDistribution(resource *terraform.Block) cloudfront.Distribution { + + distribution := cloudfront.Distribution{ + Metadata: resource.GetMetadata(), + WAFID: types.StringDefault("", resource.GetMetadata()), + Logging: cloudfront.Logging{ + Metadata: resource.GetMetadata(), + Bucket: types.StringDefault("", resource.GetMetadata()), + }, + DefaultCacheBehaviour: cloudfront.CacheBehaviour{ + Metadata: resource.GetMetadata(), + ViewerProtocolPolicy: types.String("allow-all", resource.GetMetadata()), + }, + OrdererCacheBehaviours: nil, + ViewerCertificate: cloudfront.ViewerCertificate{ + Metadata: resource.GetMetadata(), + MinimumProtocolVersion: types.StringDefault("TLSv1", resource.GetMetadata()), + }, + } + + distribution.WAFID = resource.GetAttribute("web_acl_id").AsStringValueOrDefault("", resource) + + if loggingBlock := resource.GetBlock("logging_config"); loggingBlock.IsNotNil() { + distribution.Logging.Metadata = loggingBlock.GetMetadata() + bucketAttr := loggingBlock.GetAttribute("bucket") + distribution.Logging.Bucket = bucketAttr.AsStringValueOrDefault("", loggingBlock) + } + + if defaultCacheBlock := resource.GetBlock("default_cache_behavior"); defaultCacheBlock.IsNotNil() { + distribution.DefaultCacheBehaviour.Metadata = defaultCacheBlock.GetMetadata() + viewerProtocolPolicyAttr := defaultCacheBlock.GetAttribute("viewer_protocol_policy") + distribution.DefaultCacheBehaviour.ViewerProtocolPolicy = viewerProtocolPolicyAttr.AsStringValueOrDefault("allow-all", defaultCacheBlock) + } + + orderedCacheBlocks := resource.GetBlocks("ordered_cache_behavior") + for _, orderedCacheBlock := range orderedCacheBlocks { + viewerProtocolPolicyAttr := orderedCacheBlock.GetAttribute("viewer_protocol_policy") + viewerProtocolPolicyVal := viewerProtocolPolicyAttr.AsStringValueOrDefault("allow-all", orderedCacheBlock) + distribution.OrdererCacheBehaviours = append(distribution.OrdererCacheBehaviours, cloudfront.CacheBehaviour{ + Metadata: orderedCacheBlock.GetMetadata(), + ViewerProtocolPolicy: viewerProtocolPolicyVal, + }) + } + + if viewerCertBlock := resource.GetBlock("viewer_certificate"); viewerCertBlock.IsNotNil() { + distribution.ViewerCertificate = cloudfront.ViewerCertificate{ + Metadata: viewerCertBlock.GetMetadata(), + MinimumProtocolVersion: viewerCertBlock.GetAttribute("minimum_protocol_version").AsStringValueOrDefault("TLSv1", viewerCertBlock), + SSLSupportMethod: viewerCertBlock.GetAttribute("ssl_support_method").AsStringValueOrDefault("", viewerCertBlock), + CloudfrontDefaultCertificate: viewerCertBlock.GetAttribute("cloudfront_default_certificate").AsBoolValueOrDefault(false, viewerCertBlock), + } + } + + return distribution +} diff --git a/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go b/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go new file mode 100644 index 000000000000..eade38048204 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go @@ -0,0 +1,162 @@ +package cloudfront + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptDistribution(t *testing.T) { + tests := []struct { + name string + terraform string + expected cloudfront.Distribution + }{ + { + name: "configured", + terraform: ` + resource "aws_cloudfront_distribution" "example" { + logging_config { + bucket = "mylogs.s3.amazonaws.com" + } + + web_acl_id = "waf_id" + + default_cache_behavior { + viewer_protocol_policy = "redirect-to-https" + } + + ordered_cache_behavior { + viewer_protocol_policy = "redirect-to-https" + } + + viewer_certificate { + cloudfront_default_certificate = true + minimum_protocol_version = "TLSv1.2_2021" + ssl_support_method = "sni-only" + } + } +`, + expected: cloudfront.Distribution{ + Metadata: iacTypes.NewTestMetadata(), + WAFID: iacTypes.String("waf_id", iacTypes.NewTestMetadata()), + Logging: cloudfront.Logging{ + Metadata: iacTypes.NewTestMetadata(), + Bucket: iacTypes.String("mylogs.s3.amazonaws.com", iacTypes.NewTestMetadata()), + }, + DefaultCacheBehaviour: cloudfront.CacheBehaviour{ + Metadata: iacTypes.NewTestMetadata(), + ViewerProtocolPolicy: iacTypes.String("redirect-to-https", iacTypes.NewTestMetadata()), + }, + OrdererCacheBehaviours: []cloudfront.CacheBehaviour{ + { + Metadata: iacTypes.NewTestMetadata(), + ViewerProtocolPolicy: iacTypes.String("redirect-to-https", iacTypes.NewTestMetadata()), + }, + }, + ViewerCertificate: cloudfront.ViewerCertificate{ + Metadata: iacTypes.NewTestMetadata(), + MinimumProtocolVersion: iacTypes.String("TLSv1.2_2021", iacTypes.NewTestMetadata()), + CloudfrontDefaultCertificate: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + SSLSupportMethod: iacTypes.String("sni-only", iacTypes.NewTestMetadata()), + }, + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_cloudfront_distribution" "example" { + } +`, + expected: cloudfront.Distribution{ + Metadata: iacTypes.NewTestMetadata(), + WAFID: iacTypes.String("", iacTypes.NewTestMetadata()), + Logging: cloudfront.Logging{ + Metadata: iacTypes.NewTestMetadata(), + Bucket: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + DefaultCacheBehaviour: cloudfront.CacheBehaviour{ + Metadata: iacTypes.NewTestMetadata(), + ViewerProtocolPolicy: iacTypes.String("allow-all", iacTypes.NewTestMetadata()), + }, + + ViewerCertificate: cloudfront.ViewerCertificate{ + Metadata: iacTypes.NewTestMetadata(), + MinimumProtocolVersion: iacTypes.String("TLSv1", iacTypes.NewTestMetadata()), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptDistribution(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_cloudfront_distribution" "example" { + logging_config { + bucket = "mylogs.s3.amazonaws.com" + } + + web_acl_id = "waf_id" + + default_cache_behavior { + viewer_protocol_policy = "redirect-to-https" + } + + ordered_cache_behavior { + viewer_protocol_policy = "redirect-to-https" + } + + viewer_certificate { + cloudfront_default_certificate = true + minimum_protocol_version = "TLSv1.2_2021" + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Distributions, 1) + distribution := adapted.Distributions[0] + + assert.Equal(t, 2, distribution.Metadata.Range().GetStartLine()) + assert.Equal(t, 21, distribution.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, distribution.Logging.Metadata.Range().GetStartLine()) + assert.Equal(t, 5, distribution.Logging.Metadata.Range().GetEndLine()) + + assert.Equal(t, 7, distribution.WAFID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, distribution.WAFID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 9, distribution.DefaultCacheBehaviour.Metadata.Range().GetStartLine()) + assert.Equal(t, 11, distribution.DefaultCacheBehaviour.Metadata.Range().GetEndLine()) + + assert.Equal(t, 10, distribution.DefaultCacheBehaviour.ViewerProtocolPolicy.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 10, distribution.DefaultCacheBehaviour.ViewerProtocolPolicy.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 13, distribution.OrdererCacheBehaviours[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 15, distribution.OrdererCacheBehaviours[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 14, distribution.OrdererCacheBehaviours[0].ViewerProtocolPolicy.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 14, distribution.OrdererCacheBehaviours[0].ViewerProtocolPolicy.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 17, distribution.ViewerCertificate.Metadata.Range().GetStartLine()) + assert.Equal(t, 20, distribution.ViewerCertificate.Metadata.Range().GetEndLine()) + + assert.Equal(t, 19, distribution.ViewerCertificate.MinimumProtocolVersion.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 19, distribution.ViewerCertificate.MinimumProtocolVersion.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/cloudtrail/adapt.go b/pkg/iac/adapters/terraform/aws/cloudtrail/adapt.go new file mode 100644 index 000000000000..22464d85f80c --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/cloudtrail/adapt.go @@ -0,0 +1,67 @@ +package cloudtrail + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) cloudtrail.CloudTrail { + return cloudtrail.CloudTrail{ + Trails: adaptTrails(modules), + } +} + +func adaptTrails(modules terraform.Modules) []cloudtrail.Trail { + var trails []cloudtrail.Trail + + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_cloudtrail") { + trails = append(trails, adaptTrail(resource)) + } + } + return trails +} + +func adaptTrail(resource *terraform.Block) cloudtrail.Trail { + nameAttr := resource.GetAttribute("name") + nameVal := nameAttr.AsStringValueOrDefault("", resource) + + enableLogFileValidationAttr := resource.GetAttribute("enable_log_file_validation") + enableLogFileValidationVal := enableLogFileValidationAttr.AsBoolValueOrDefault(false, resource) + + isMultiRegionAttr := resource.GetAttribute("is_multi_region_trail") + isMultiRegionVal := isMultiRegionAttr.AsBoolValueOrDefault(false, resource) + + KMSKeyIDAttr := resource.GetAttribute("kms_key_id") + KMSKeyIDVal := KMSKeyIDAttr.AsStringValueOrDefault("", resource) + + var selectors []cloudtrail.EventSelector + for _, selBlock := range resource.GetBlocks("event_selector") { + var resources []cloudtrail.DataResource + for _, resBlock := range selBlock.GetBlocks("data_resource") { + resources = append(resources, cloudtrail.DataResource{ + Metadata: resBlock.GetMetadata(), + Type: resBlock.GetAttribute("type").AsStringValueOrDefault("", resBlock), + Values: resBlock.GetAttribute("values").AsStringValues(), + }) + } + selector := cloudtrail.EventSelector{ + Metadata: selBlock.GetMetadata(), + DataResources: resources, + ReadWriteType: selBlock.GetAttribute("read_write_type").AsStringValueOrDefault("All", selBlock), + } + selectors = append(selectors, selector) + } + + return cloudtrail.Trail{ + Metadata: resource.GetMetadata(), + Name: nameVal, + EnableLogFileValidation: enableLogFileValidationVal, + IsMultiRegion: isMultiRegionVal, + KMSKeyID: KMSKeyIDVal, + CloudWatchLogsLogGroupArn: resource.GetAttribute("cloud_watch_logs_group_arn").AsStringValueOrDefault("", resource), + IsLogging: resource.GetAttribute("enable_logging").AsBoolValueOrDefault(true, resource), + BucketName: resource.GetAttribute("s3_bucket_name").AsStringValueOrDefault("", resource), + EventSelectors: selectors, + } +} diff --git a/pkg/iac/adapters/terraform/aws/cloudtrail/adapt_test.go b/pkg/iac/adapters/terraform/aws/cloudtrail/adapt_test.go new file mode 100644 index 000000000000..9088b115752e --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/cloudtrail/adapt_test.go @@ -0,0 +1,105 @@ +package cloudtrail + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptTrail(t *testing.T) { + tests := []struct { + name string + terraform string + expected cloudtrail.Trail + }{ + { + name: "configured", + terraform: ` + resource "aws_cloudtrail" "example" { + name = "example" + is_multi_region_trail = true + + enable_log_file_validation = true + kms_key_id = "kms-key" + s3_bucket_name = "abcdefgh" + cloud_watch_logs_group_arn = "abc" + enable_logging = false + } +`, + expected: cloudtrail.Trail{ + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("example", iacTypes.NewTestMetadata()), + EnableLogFileValidation: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + IsMultiRegion: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("kms-key", iacTypes.NewTestMetadata()), + CloudWatchLogsLogGroupArn: iacTypes.String("abc", iacTypes.NewTestMetadata()), + IsLogging: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + BucketName: iacTypes.String("abcdefgh", iacTypes.NewTestMetadata()), + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_cloudtrail" "example" { + } +`, + expected: cloudtrail.Trail{ + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("", iacTypes.NewTestMetadata()), + EnableLogFileValidation: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + IsMultiRegion: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + BucketName: iacTypes.String("", iacTypes.NewTestMetadata()), + CloudWatchLogsLogGroupArn: iacTypes.String("", iacTypes.NewTestMetadata()), + IsLogging: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptTrail(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_cloudtrail" "example" { + name = "example" + is_multi_region_trail = true + + enable_log_file_validation = true + kms_key_id = "kms-key" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Trails, 1) + trail := adapted.Trails[0] + + assert.Equal(t, 2, trail.Metadata.Range().GetStartLine()) + assert.Equal(t, 8, trail.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, trail.Name.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, trail.Name.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 4, trail.IsMultiRegion.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, trail.IsMultiRegion.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 6, trail.EnableLogFileValidation.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, trail.EnableLogFileValidation.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 7, trail.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, trail.KMSKeyID.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/cloudwatch/adapt.go b/pkg/iac/adapters/terraform/aws/cloudwatch/adapt.go new file mode 100644 index 000000000000..c0b47b7f8d3e --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/cloudwatch/adapt.go @@ -0,0 +1,47 @@ +package cloudwatch + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) cloudwatch.CloudWatch { + return cloudwatch.CloudWatch{ + LogGroups: adaptLogGroups(modules), + } +} + +func adaptLogGroups(modules terraform.Modules) []cloudwatch.LogGroup { + var logGroups []cloudwatch.LogGroup + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_cloudwatch_log_group") { + logGroups = append(logGroups, adaptLogGroup(resource, module)) + } + } + return logGroups +} + +func adaptLogGroup(resource *terraform.Block, module *terraform.Module) cloudwatch.LogGroup { + nameAttr := resource.GetAttribute("name") + nameVal := nameAttr.AsStringValueOrDefault("", resource) + + KMSKeyIDAttr := resource.GetAttribute("kms_key_id") + KMSKeyIDVal := KMSKeyIDAttr.AsStringValueOrDefault("", resource) + + if keyBlock, err := module.GetReferencedBlock(KMSKeyIDAttr, resource); err == nil { + KMSKeyIDVal = types.String(keyBlock.FullName(), keyBlock.GetMetadata()) + } + + retentionInDaysAttr := resource.GetAttribute("retention_in_days") + retentionInDaysVal := retentionInDaysAttr.AsIntValueOrDefault(0, resource) + + return cloudwatch.LogGroup{ + Metadata: resource.GetMetadata(), + Arn: types.StringDefault("", resource.GetMetadata()), + Name: nameVal, + KMSKeyID: KMSKeyIDVal, + RetentionInDays: retentionInDaysVal, + MetricFilters: nil, + } +} diff --git a/pkg/iac/adapters/terraform/aws/cloudwatch/adapt_test.go b/pkg/iac/adapters/terraform/aws/cloudwatch/adapt_test.go new file mode 100644 index 000000000000..5febcd592dfe --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/cloudwatch/adapt_test.go @@ -0,0 +1,113 @@ +package cloudwatch + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptLogGroups(t *testing.T) { + tests := []struct { + name string + terraform string + expected []cloudwatch.LogGroup + }{ + { + name: "key referencing block", + terraform: ` + resource "aws_cloudwatch_log_group" "my-group" { + name = "my-group" + kms_key_id = aws_kms_key.log_key.arn + } + + resource "aws_kms_key" "log_key" { + } +`, + expected: []cloudwatch.LogGroup{ + { + Metadata: iacTypes.NewTestMetadata(), + Arn: iacTypes.String("", iacTypes.NewTestMetadata()), + Name: iacTypes.String("my-group", iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("aws_kms_key.log_key", iacTypes.NewTestMetadata()), + RetentionInDays: iacTypes.Int(0, iacTypes.NewTestMetadata()), + MetricFilters: nil, + }, + }, + }, + { + name: "key as string", + terraform: ` + resource "aws_cloudwatch_log_group" "my-group" { + name = "my-group" + kms_key_id = "key-as-string" + } +`, + expected: []cloudwatch.LogGroup{ + { + Metadata: iacTypes.NewTestMetadata(), + Arn: iacTypes.String("", iacTypes.NewTestMetadata()), + Name: iacTypes.String("my-group", iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("key-as-string", iacTypes.NewTestMetadata()), + RetentionInDays: iacTypes.Int(0, iacTypes.NewTestMetadata()), + }, + }, + }, + { + name: "missing key", + terraform: ` + resource "aws_cloudwatch_log_group" "my-group" { + name = "my-group" + retention_in_days = 3 + } +`, + expected: []cloudwatch.LogGroup{ + { + Metadata: iacTypes.NewTestMetadata(), + Arn: iacTypes.String("", iacTypes.NewTestMetadata()), + Name: iacTypes.String("my-group", iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + RetentionInDays: iacTypes.Int(3, iacTypes.NewTestMetadata()), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptLogGroups(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_cloudwatch_log_group" "my-group" { + name = "my-group" + kms_key_id = aws_kms_key.log_key.arn + retention_in_days = 3 + + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + require.Len(t, adapted.LogGroups, 1) + logGroup := adapted.LogGroups[0] + + assert.Equal(t, 3, logGroup.Name.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, logGroup.Name.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 4, logGroup.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, logGroup.KMSKeyID.GetMetadata().Range().GetStartLine()) + + assert.Equal(t, 5, logGroup.RetentionInDays.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 5, logGroup.RetentionInDays.GetMetadata().Range().GetStartLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/codebuild/adapt.go b/pkg/iac/adapters/terraform/aws/codebuild/adapt.go new file mode 100644 index 000000000000..f7fa4b4f35b5 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/codebuild/adapt.go @@ -0,0 +1,66 @@ +package codebuild + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/codebuild" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) codebuild.CodeBuild { + return codebuild.CodeBuild{ + Projects: adaptProjects(modules), + } +} + +func adaptProjects(modules terraform.Modules) []codebuild.Project { + var projects []codebuild.Project + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_codebuild_project") { + projects = append(projects, adaptProject(resource)) + } + } + return projects +} + +func adaptProject(resource *terraform.Block) codebuild.Project { + + project := codebuild.Project{ + Metadata: resource.GetMetadata(), + ArtifactSettings: codebuild.ArtifactSettings{ + Metadata: resource.GetMetadata(), + EncryptionEnabled: types.BoolDefault(true, resource.GetMetadata()), + }, + SecondaryArtifactSettings: nil, + } + + var hasArtifacts bool + + if artifactsBlock := resource.GetBlock("artifacts"); artifactsBlock.IsNotNil() { + project.ArtifactSettings.Metadata = artifactsBlock.GetMetadata() + typeAttr := artifactsBlock.GetAttribute("type") + encryptionDisabledAttr := artifactsBlock.GetAttribute("encryption_disabled") + hasArtifacts = typeAttr.NotEqual("NO_ARTIFACTS") + if encryptionDisabledAttr.IsTrue() && hasArtifacts { + project.ArtifactSettings.EncryptionEnabled = types.Bool(false, artifactsBlock.GetMetadata()) + } else { + project.ArtifactSettings.EncryptionEnabled = types.Bool(true, artifactsBlock.GetMetadata()) + } + } + + secondaryArtifactBlocks := resource.GetBlocks("secondary_artifacts") + for _, secondaryArtifactBlock := range secondaryArtifactBlocks { + + secondaryEncryptionEnabled := types.BoolDefault(true, secondaryArtifactBlock.GetMetadata()) + secondaryEncryptionDisabledAttr := secondaryArtifactBlock.GetAttribute("encryption_disabled") + if secondaryEncryptionDisabledAttr.IsTrue() && hasArtifacts { + secondaryEncryptionEnabled = types.Bool(false, secondaryArtifactBlock.GetMetadata()) + } + + project.SecondaryArtifactSettings = append(project.SecondaryArtifactSettings, codebuild.ArtifactSettings{ + Metadata: secondaryArtifactBlock.GetMetadata(), + EncryptionEnabled: secondaryEncryptionEnabled, + }) + } + + return project +} diff --git a/pkg/iac/adapters/terraform/aws/codebuild/adapt_test.go b/pkg/iac/adapters/terraform/aws/codebuild/adapt_test.go new file mode 100644 index 000000000000..58aeca7df9d1 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/codebuild/adapt_test.go @@ -0,0 +1,115 @@ +package codebuild + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/codebuild" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptProject(t *testing.T) { + tests := []struct { + name string + terraform string + expected codebuild.Project + }{ + { + name: "configured", + terraform: ` + resource "aws_codebuild_project" "codebuild" { + + artifacts { + encryption_disabled = false + } + + secondary_artifacts { + encryption_disabled = false + } + secondary_artifacts { + encryption_disabled = true + } + } +`, + expected: codebuild.Project{ + Metadata: iacTypes.NewTestMetadata(), + ArtifactSettings: codebuild.ArtifactSettings{ + Metadata: iacTypes.NewTestMetadata(), + EncryptionEnabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + SecondaryArtifactSettings: []codebuild.ArtifactSettings{ + { + Metadata: iacTypes.NewTestMetadata(), + EncryptionEnabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + { + Metadata: iacTypes.NewTestMetadata(), + EncryptionEnabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + { + name: "defaults - encryption enabled", + terraform: ` + resource "aws_codebuild_project" "codebuild" { + } +`, + expected: codebuild.Project{ + Metadata: iacTypes.NewTestMetadata(), + ArtifactSettings: codebuild.ArtifactSettings{ + Metadata: iacTypes.NewTestMetadata(), + EncryptionEnabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptProject(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_codebuild_project" "codebuild" { + artifacts { + encryption_disabled = false + } + + secondary_artifacts { + encryption_disabled = false + } + + secondary_artifacts { + encryption_disabled = true + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Projects, 1) + project := adapted.Projects[0] + + assert.Equal(t, 2, project.Metadata.Range().GetStartLine()) + assert.Equal(t, 14, project.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, project.ArtifactSettings.Metadata.Range().GetStartLine()) + assert.Equal(t, 5, project.ArtifactSettings.Metadata.Range().GetEndLine()) + + assert.Equal(t, 7, project.SecondaryArtifactSettings[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 9, project.SecondaryArtifactSettings[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 11, project.SecondaryArtifactSettings[1].Metadata.Range().GetStartLine()) + assert.Equal(t, 13, project.SecondaryArtifactSettings[1].Metadata.Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/config/adapt.go b/pkg/iac/adapters/terraform/aws/config/adapt.go new file mode 100644 index 000000000000..643fbd86720c --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/config/adapt.go @@ -0,0 +1,33 @@ +package config + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/config" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) config.Config { + return config.Config{ + ConfigurationAggregrator: adaptConfigurationAggregrator(modules), + } +} + +func adaptConfigurationAggregrator(modules terraform.Modules) config.ConfigurationAggregrator { + configurationAggregrator := config.ConfigurationAggregrator{ + Metadata: iacTypes.NewUnmanagedMetadata(), + SourceAllRegions: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + } + + for _, resource := range modules.GetResourcesByType("aws_config_configuration_aggregator") { + configurationAggregrator.Metadata = resource.GetMetadata() + aggregationBlock := resource.GetFirstMatchingBlock("account_aggregation_source", "organization_aggregation_source") + if aggregationBlock.IsNil() { + configurationAggregrator.SourceAllRegions = iacTypes.Bool(false, resource.GetMetadata()) + } else { + allRegionsAttr := aggregationBlock.GetAttribute("all_regions") + allRegionsVal := allRegionsAttr.AsBoolValueOrDefault(false, aggregationBlock) + configurationAggregrator.SourceAllRegions = allRegionsVal + } + } + return configurationAggregrator +} diff --git a/pkg/iac/adapters/terraform/aws/config/adapt_test.go b/pkg/iac/adapters/terraform/aws/config/adapt_test.go new file mode 100644 index 000000000000..94917b430fbe --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/config/adapt_test.go @@ -0,0 +1,80 @@ +package config + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/config" + + "github.com/stretchr/testify/assert" +) + +func Test_adaptConfigurationAggregrator(t *testing.T) { + tests := []struct { + name string + terraform string + expected config.ConfigurationAggregrator + }{ + { + name: "configured", + terraform: ` + resource "aws_config_configuration_aggregator" "example" { + name = "example" + + account_aggregation_source { + account_ids = ["123456789012"] + all_regions = true + } + } +`, + expected: config.ConfigurationAggregrator{ + Metadata: iacTypes.NewTestMetadata(), + SourceAllRegions: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_config_configuration_aggregator" "example" { + } +`, + expected: config.ConfigurationAggregrator{ + Metadata: iacTypes.NewTestMetadata(), + SourceAllRegions: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptConfigurationAggregrator(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_config_configuration_aggregator" "example" { + name = "example" + + account_aggregation_source { + account_ids = ["123456789012"] + all_regions = true + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + aggregator := adapted.ConfigurationAggregrator + + assert.Equal(t, 2, aggregator.Metadata.Range().GetStartLine()) + assert.Equal(t, 9, aggregator.Metadata.Range().GetEndLine()) + + assert.Equal(t, 7, aggregator.SourceAllRegions.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, aggregator.SourceAllRegions.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/documentdb/adapt.go b/pkg/iac/adapters/terraform/aws/documentdb/adapt.go new file mode 100644 index 000000000000..5c10b5195c97 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/documentdb/adapt.go @@ -0,0 +1,63 @@ +package documentdb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/documentdb" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) documentdb.DocumentDB { + return documentdb.DocumentDB{ + Clusters: adaptClusters(modules), + } +} + +func adaptClusters(modules terraform.Modules) []documentdb.Cluster { + var clusters []documentdb.Cluster + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_docdb_cluster") { + clusters = append(clusters, adaptCluster(resource, module)) + } + } + return clusters +} + +func adaptCluster(resource *terraform.Block, module *terraform.Module) documentdb.Cluster { + identifierAttr := resource.GetAttribute("cluster_identifier") + identifierVal := identifierAttr.AsStringValueOrDefault("", resource) + + var enabledLogExports []types.StringValue + var instances []documentdb.Instance + + enabledLogExportsAttr := resource.GetAttribute("enabled_cloudwatch_logs_exports") + for _, logExport := range enabledLogExportsAttr.AsStringValues() { + enabledLogExports = append(enabledLogExports, logExport) + } + + instancesRes := module.GetReferencingResources(resource, "aws_docdb_cluster_instance", "cluster_identifier") + for _, instanceRes := range instancesRes { + keyIDAttr := instanceRes.GetAttribute("kms_key_id") + keyIDVal := keyIDAttr.AsStringValueOrDefault("", instanceRes) + + instances = append(instances, documentdb.Instance{ + Metadata: instanceRes.GetMetadata(), + KMSKeyID: keyIDVal, + }) + } + + storageEncryptedAttr := resource.GetAttribute("storage_encrypted") + storageEncryptedVal := storageEncryptedAttr.AsBoolValueOrDefault(false, resource) + + KMSKeyIDAttr := resource.GetAttribute("kms_key_id") + KMSKeyIDVal := KMSKeyIDAttr.AsStringValueOrDefault("", resource) + + return documentdb.Cluster{ + Metadata: resource.GetMetadata(), + Identifier: identifierVal, + EnabledLogExports: enabledLogExports, + BackupRetentionPeriod: resource.GetAttribute("backup_retention_period").AsIntValueOrDefault(0, resource), + Instances: instances, + StorageEncrypted: storageEncryptedVal, + KMSKeyID: KMSKeyIDVal, + } +} diff --git a/pkg/iac/adapters/terraform/aws/documentdb/adapt_test.go b/pkg/iac/adapters/terraform/aws/documentdb/adapt_test.go new file mode 100644 index 000000000000..db7dbd7937e8 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/documentdb/adapt_test.go @@ -0,0 +1,124 @@ +package documentdb + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/documentdb" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptCluster(t *testing.T) { + tests := []struct { + name string + terraform string + expected documentdb.Cluster + }{ + { + name: "configured", + terraform: ` + resource "aws_docdb_cluster" "docdb" { + cluster_identifier = "my-docdb-cluster" + kms_key_id = "kms-key" + enabled_cloudwatch_logs_exports = "audit" + storage_encrypted = true + } + + resource "aws_docdb_cluster_instance" "cluster_instances" { + count = 1 + identifier = "my-docdb-cluster" + cluster_identifier = aws_docdb_cluster.docdb.id + kms_key_id = "kms-key#1" + } +`, + expected: documentdb.Cluster{ + Metadata: iacTypes.NewTestMetadata(), + Identifier: iacTypes.String("my-docdb-cluster", iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("kms-key", iacTypes.NewTestMetadata()), + EnabledLogExports: []iacTypes.StringValue{ + iacTypes.String("audit", iacTypes.NewTestMetadata()), + }, + Instances: []documentdb.Instance{ + { + Metadata: iacTypes.NewTestMetadata(), + KMSKeyID: iacTypes.String("kms-key#1", iacTypes.NewTestMetadata()), + }, + }, + StorageEncrypted: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_docdb_cluster" "docdb" { + } +`, + expected: documentdb.Cluster{ + Metadata: iacTypes.NewTestMetadata(), + Identifier: iacTypes.String("", iacTypes.NewTestMetadata()), + StorageEncrypted: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptCluster(modules.GetBlocks()[0], modules[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_docdb_cluster" "docdb" { + cluster_identifier = "my-docdb-cluster" + kms_key_id = "kms-key" + enabled_cloudwatch_logs_exports = "audit" + storage_encrypted = true + } + + resource "aws_docdb_cluster_instance" "cluster_instances" { + count = 1 + identifier = "my-docdb-cluster" + cluster_identifier = aws_docdb_cluster.docdb.id + kms_key_id = "kms-key" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Clusters, 1) + require.Len(t, adapted.Clusters[0].Instances, 1) + + cluster := adapted.Clusters[0] + instance := cluster.Instances[0] + + assert.Equal(t, 2, cluster.Metadata.Range().GetStartLine()) + assert.Equal(t, 7, cluster.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, cluster.Identifier.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, cluster.Identifier.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 4, cluster.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, cluster.KMSKeyID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 5, cluster.EnabledLogExports[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 5, cluster.EnabledLogExports[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 6, cluster.StorageEncrypted.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, cluster.StorageEncrypted.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 9, instance.Metadata.Range().GetStartLine()) + assert.Equal(t, 14, instance.Metadata.Range().GetEndLine()) + + assert.Equal(t, 13, instance.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 13, instance.KMSKeyID.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/dynamodb/adapt.go b/pkg/iac/adapters/terraform/aws/dynamodb/adapt.go new file mode 100644 index 000000000000..c4c1582c807b --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/dynamodb/adapt.go @@ -0,0 +1,94 @@ +package dynamodb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/dynamodb" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) dynamodb.DynamoDB { + return dynamodb.DynamoDB{ + DAXClusters: adaptClusters(modules), + Tables: adaptTables(modules), + } +} + +func adaptClusters(modules terraform.Modules) []dynamodb.DAXCluster { + var clusters []dynamodb.DAXCluster + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_dax_cluster") { + clusters = append(clusters, adaptCluster(resource, module)) + } + } + return clusters +} + +func adaptTables(modules terraform.Modules) []dynamodb.Table { + var tables []dynamodb.Table + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_dynamodb_table") { + tables = append(tables, adaptTable(resource, module)) + } + } + return tables +} + +func adaptCluster(resource *terraform.Block, module *terraform.Module) dynamodb.DAXCluster { + + cluster := dynamodb.DAXCluster{ + Metadata: resource.GetMetadata(), + ServerSideEncryption: dynamodb.ServerSideEncryption{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + KMSKeyID: iacTypes.StringDefault("", resource.GetMetadata()), + }, + PointInTimeRecovery: iacTypes.BoolDefault(false, resource.GetMetadata()), + } + + if ssEncryptionBlock := resource.GetBlock("server_side_encryption"); ssEncryptionBlock.IsNotNil() { + cluster.ServerSideEncryption.Metadata = ssEncryptionBlock.GetMetadata() + enabledAttr := ssEncryptionBlock.GetAttribute("enabled") + cluster.ServerSideEncryption.Enabled = enabledAttr.AsBoolValueOrDefault(false, ssEncryptionBlock) + } + + if recoveryBlock := resource.GetBlock("point_in_time_recovery"); recoveryBlock.IsNotNil() { + recoveryEnabledAttr := recoveryBlock.GetAttribute("enabled") + cluster.PointInTimeRecovery = recoveryEnabledAttr.AsBoolValueOrDefault(false, recoveryBlock) + } + + return cluster +} + +func adaptTable(resource *terraform.Block, module *terraform.Module) dynamodb.Table { + + table := dynamodb.Table{ + Metadata: resource.GetMetadata(), + ServerSideEncryption: dynamodb.ServerSideEncryption{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + KMSKeyID: iacTypes.StringDefault("", resource.GetMetadata()), + }, + PointInTimeRecovery: iacTypes.BoolDefault(false, resource.GetMetadata()), + } + + if ssEncryptionBlock := resource.GetBlock("server_side_encryption"); ssEncryptionBlock.IsNotNil() { + table.ServerSideEncryption.Metadata = ssEncryptionBlock.GetMetadata() + enabledAttr := ssEncryptionBlock.GetAttribute("enabled") + table.ServerSideEncryption.Enabled = enabledAttr.AsBoolValueOrDefault(false, ssEncryptionBlock) + + kmsKeyIdAttr := ssEncryptionBlock.GetAttribute("kms_key_arn") + table.ServerSideEncryption.KMSKeyID = kmsKeyIdAttr.AsStringValueOrDefault("alias/aws/dynamodb", ssEncryptionBlock) + + kmsBlock, err := module.GetReferencedBlock(kmsKeyIdAttr, resource) + if err == nil && kmsBlock.IsNotNil() { + table.ServerSideEncryption.KMSKeyID = iacTypes.String(kmsBlock.FullName(), kmsBlock.GetMetadata()) + } + } + + if recoveryBlock := resource.GetBlock("point_in_time_recovery"); recoveryBlock.IsNotNil() { + recoveryEnabledAttr := recoveryBlock.GetAttribute("enabled") + table.PointInTimeRecovery = recoveryEnabledAttr.AsBoolValueOrDefault(false, recoveryBlock) + } + + return table +} diff --git a/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go b/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go new file mode 100644 index 000000000000..ae7002ac697b --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go @@ -0,0 +1,175 @@ +package dynamodb + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/dynamodb" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptCluster(t *testing.T) { + tests := []struct { + name string + terraform string + expected dynamodb.DAXCluster + }{ + { + name: "cluster", + terraform: ` + resource "aws_dax_cluster" "example" { + server_side_encryption { + enabled = true + } + } +`, + expected: dynamodb.DAXCluster{ + Metadata: iacTypes.NewTestMetadata(), + ServerSideEncryption: dynamodb.ServerSideEncryption{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + PointInTimeRecovery: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptCluster(modules.GetBlocks()[0], modules[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptTable(t *testing.T) { + tests := []struct { + name string + terraform string + expected dynamodb.Table + }{ + { + name: "table", + terraform: ` + resource "aws_dynamodb_table" "example" { + name = "example" + + server_side_encryption { + enabled = true + kms_key_arn = "key-string" + } + + point_in_time_recovery { + enabled = true + } + } +`, + expected: dynamodb.Table{ + Metadata: iacTypes.NewTestMetadata(), + ServerSideEncryption: dynamodb.ServerSideEncryption{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("key-string", iacTypes.NewTestMetadata()), + }, + PointInTimeRecovery: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + { + name: "table no kms", + terraform: ` + resource "aws_dax_cluster" "example" { + server_side_encryption { + enabled = true + } + } +`, + expected: dynamodb.Table{ + Metadata: iacTypes.NewTestMetadata(), + ServerSideEncryption: dynamodb.ServerSideEncryption{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("alias/aws/dynamodb", iacTypes.NewTestMetadata()), + }, + PointInTimeRecovery: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + { + name: "reference key", + terraform: ` + resource "aws_dynamodb_table" "example" { + name = "example" + + server_side_encryption { + enabled = true + kms_key_arn = aws_kms_key.a.arn + } + } + + resource "aws_kms_key" "a" { + } +`, + expected: dynamodb.Table{ + Metadata: iacTypes.NewTestMetadata(), + ServerSideEncryption: dynamodb.ServerSideEncryption{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("aws_kms_key.a", iacTypes.NewTestMetadata()), + }, + PointInTimeRecovery: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptTable(modules.GetBlocks()[0], modules[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_dynamodb_table" "example" { + name = "example" + + server_side_encryption { + enabled = true + kms_key_arn = "key-string" + } + + point_in_time_recovery { + enabled = true + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.DAXClusters, 0) + require.Len(t, adapted.Tables, 1) + table := adapted.Tables[0] + + assert.Equal(t, 2, table.Metadata.Range().GetStartLine()) + assert.Equal(t, 13, table.Metadata.Range().GetEndLine()) + + assert.Equal(t, 5, table.ServerSideEncryption.Metadata.Range().GetStartLine()) + assert.Equal(t, 8, table.ServerSideEncryption.Metadata.Range().GetEndLine()) + + assert.Equal(t, 6, table.ServerSideEncryption.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, table.ServerSideEncryption.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 7, table.ServerSideEncryption.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, table.ServerSideEncryption.KMSKeyID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, table.PointInTimeRecovery.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 11, table.PointInTimeRecovery.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/ec2/adapt.go b/pkg/iac/adapters/terraform/aws/ec2/adapt.go new file mode 100644 index 000000000000..6b02431f3772 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/ec2/adapt.go @@ -0,0 +1,102 @@ +package ec2 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) ec2.EC2 { + + naclAdapter := naclAdapter{naclRuleIDs: modules.GetChildResourceIDMapByType("aws_network_acl_rule")} + sgAdapter := sgAdapter{sgRuleIDs: modules.GetChildResourceIDMapByType("aws_security_group_rule")} + + return ec2.EC2{ + Instances: getInstances(modules), + VPCs: adaptVPCs(modules), + SecurityGroups: sgAdapter.adaptSecurityGroups(modules), + Subnets: adaptSubnets(modules), + NetworkACLs: naclAdapter.adaptNetworkACLs(modules), + LaunchConfigurations: adaptLaunchConfigurations(modules), + LaunchTemplates: adaptLaunchTemplates(modules), + Volumes: adaptVolumes(modules), + } +} + +func getInstances(modules terraform.Modules) []ec2.Instance { + var instances []ec2.Instance + + blocks := modules.GetResourcesByType("aws_instance") + + for _, b := range blocks { + instance := ec2.Instance{ + Metadata: b.GetMetadata(), + MetadataOptions: getMetadataOptions(b), + UserData: b.GetAttribute("user_data").AsStringValueOrDefault("", b), + } + + if launchTemplate := findRelatedLaunchTemplate(modules, b); launchTemplate != nil { + instance = launchTemplate.Instance + } + + if instance.RootBlockDevice == nil { + instance.RootBlockDevice = &ec2.BlockDevice{ + Metadata: b.GetMetadata(), + Encrypted: types.BoolDefault(false, b.GetMetadata()), + } + } + + if rootBlockDevice := b.GetBlock("root_block_device"); rootBlockDevice.IsNotNil() { + instance.RootBlockDevice = &ec2.BlockDevice{ + Metadata: rootBlockDevice.GetMetadata(), + Encrypted: rootBlockDevice.GetAttribute("encrypted").AsBoolValueOrDefault(false, b), + } + } + + for _, ebsBlock := range b.GetBlocks("ebs_block_device") { + instance.EBSBlockDevices = append(instance.EBSBlockDevices, &ec2.BlockDevice{ + Metadata: ebsBlock.GetMetadata(), + Encrypted: ebsBlock.GetAttribute("encrypted").AsBoolValueOrDefault(false, b), + }) + } + + for _, resource := range modules.GetResourcesByType("aws_ebs_encryption_by_default") { + if resource.GetAttribute("enabled").NotEqual(false) { + instance.RootBlockDevice.Encrypted = types.BoolDefault(true, resource.GetMetadata()) + for i := 0; i < len(instance.EBSBlockDevices); i++ { + ebs := instance.EBSBlockDevices[i] + ebs.Encrypted = types.BoolDefault(true, resource.GetMetadata()) + } + } + } + + instances = append(instances, instance) + } + + return instances +} + +func findRelatedLaunchTemplate(modules terraform.Modules, instanceBlock *terraform.Block) *ec2.LaunchTemplate { + launchTemplateBlock := instanceBlock.GetBlock("launch_template") + if launchTemplateBlock.IsNil() { + return nil + } + + templateRef := launchTemplateBlock.GetAttribute("name") + + if !templateRef.IsResolvable() { + templateRef = launchTemplateBlock.GetAttribute("id") + } + + if templateRef.IsString() { + for _, r := range modules.GetResourcesByType("aws_launch_template") { + templateName := r.GetAttribute("name").AsStringValueOrDefault("", r).Value() + if templateRef.Equals(r.ID()) || templateRef.Equals(templateName) { + launchTemplate := adaptLaunchTemplate(r) + return &launchTemplate + } + } + } + + return nil +} diff --git a/pkg/iac/adapters/terraform/aws/ec2/adapt_test.go b/pkg/iac/adapters/terraform/aws/ec2/adapt_test.go new file mode 100644 index 000000000000..e539b3f827dc --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/ec2/adapt_test.go @@ -0,0 +1,254 @@ +package ec2 + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected ec2.EC2 + }{ + { + name: "configured", + terraform: ` + resource "aws_instance" "example" { + ami = "ami-7f89a64f" + instance_type = "t1.micro" + + root_block_device { + encrypted = true + } + + metadata_options { + http_tokens = "required" + http_endpoint = "disabled" + } + + ebs_block_device { + encrypted = true + } + + user_data = < 0 { + orphanage := ec2.SecurityGroup{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Description: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + IngressRules: nil, + EgressRules: nil, + IsDefault: iacTypes.BoolUnresolvable(iacTypes.NewUnmanagedMetadata()), + VPCID: iacTypes.StringUnresolvable(iacTypes.NewUnmanagedMetadata()), + } + for _, sgRule := range orphanResources { + if sgRule.GetAttribute("type").Equals("ingress") { + orphanage.IngressRules = append(orphanage.IngressRules, adaptSGRule(sgRule, modules)) + } else if sgRule.GetAttribute("type").Equals("egress") { + orphanage.EgressRules = append(orphanage.EgressRules, adaptSGRule(sgRule, modules)) + } + } + securityGroups = append(securityGroups, orphanage) + } + + return securityGroups +} + +func (a *naclAdapter) adaptNetworkACLs(modules terraform.Modules) []ec2.NetworkACL { + var networkACLs []ec2.NetworkACL + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_network_acl") { + networkACLs = append(networkACLs, a.adaptNetworkACL(resource, module)) + } + } + + orphanResources := modules.GetResourceByIDs(a.naclRuleIDs.Orphans()...) + if len(orphanResources) > 0 { + orphanage := ec2.NetworkACL{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Rules: nil, + IsDefaultRule: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + } + for _, naclRule := range orphanResources { + orphanage.Rules = append(orphanage.Rules, adaptNetworkACLRule(naclRule)) + } + networkACLs = append(networkACLs, orphanage) + } + + return networkACLs +} + +func (a *sgAdapter) adaptSecurityGroup(resource *terraform.Block, module terraform.Modules) ec2.SecurityGroup { + var ingressRules []ec2.SecurityGroupRule + var egressRules []ec2.SecurityGroupRule + + descriptionAttr := resource.GetAttribute("description") + descriptionVal := descriptionAttr.AsStringValueOrDefault("Managed by Terraform", resource) + + ingressBlocks := resource.GetBlocks("ingress") + for _, ingressBlock := range ingressBlocks { + ingressRules = append(ingressRules, adaptSGRule(ingressBlock, module)) + } + + egressBlocks := resource.GetBlocks("egress") + for _, egressBlock := range egressBlocks { + egressRules = append(egressRules, adaptSGRule(egressBlock, module)) + } + + rulesBlocks := module.GetReferencingResources(resource, "aws_security_group_rule", "security_group_id") + for _, ruleBlock := range rulesBlocks { + a.sgRuleIDs.Resolve(ruleBlock.ID()) + if ruleBlock.GetAttribute("type").Equals("ingress") { + ingressRules = append(ingressRules, adaptSGRule(ruleBlock, module)) + } else if ruleBlock.GetAttribute("type").Equals("egress") { + egressRules = append(egressRules, adaptSGRule(ruleBlock, module)) + } + } + + return ec2.SecurityGroup{ + Metadata: resource.GetMetadata(), + Description: descriptionVal, + IngressRules: ingressRules, + EgressRules: egressRules, + IsDefault: iacTypes.Bool(false, iacTypes.NewUnmanagedMetadata()), + VPCID: resource.GetAttribute("vpc_id").AsStringValueOrDefault("", resource), + } +} + +func adaptSGRule(resource *terraform.Block, modules terraform.Modules) ec2.SecurityGroupRule { + ruleDescAttr := resource.GetAttribute("description") + ruleDescVal := ruleDescAttr.AsStringValueOrDefault("", resource) + + var cidrs []iacTypes.StringValue + + cidrBlocks := resource.GetAttribute("cidr_blocks") + ipv6cidrBlocks := resource.GetAttribute("ipv6_cidr_blocks") + varBlocks := modules.GetBlocks().OfType("variable") + + for _, vb := range varBlocks { + if cidrBlocks.IsNotNil() && cidrBlocks.ReferencesBlock(vb) { + cidrBlocks = vb.GetAttribute("default") + } + if ipv6cidrBlocks.IsNotNil() && ipv6cidrBlocks.ReferencesBlock(vb) { + ipv6cidrBlocks = vb.GetAttribute("default") + } + } + + if cidrBlocks.IsNotNil() { + cidrs = cidrBlocks.AsStringValues() + } + + if ipv6cidrBlocks.IsNotNil() { + cidrs = append(cidrs, ipv6cidrBlocks.AsStringValues()...) + } + + return ec2.SecurityGroupRule{ + Metadata: resource.GetMetadata(), + Description: ruleDescVal, + CIDRs: cidrs, + } +} + +func (a *naclAdapter) adaptNetworkACL(resource *terraform.Block, module *terraform.Module) ec2.NetworkACL { + var networkRules []ec2.NetworkACLRule + rulesBlocks := module.GetReferencingResources(resource, "aws_network_acl_rule", "network_acl_id") + for _, ruleBlock := range rulesBlocks { + a.naclRuleIDs.Resolve(ruleBlock.ID()) + networkRules = append(networkRules, adaptNetworkACLRule(ruleBlock)) + } + return ec2.NetworkACL{ + Metadata: resource.GetMetadata(), + Rules: networkRules, + IsDefaultRule: iacTypes.BoolDefault(false, resource.GetMetadata()), + } +} + +func adaptNetworkACLRule(resource *terraform.Block) ec2.NetworkACLRule { + var cidrs []iacTypes.StringValue + + typeVal := iacTypes.StringDefault("ingress", resource.GetMetadata()) + + egressAtrr := resource.GetAttribute("egress") + if egressAtrr.IsTrue() { + typeVal = iacTypes.String("egress", egressAtrr.GetMetadata()) + } else if egressAtrr.IsNotNil() { + typeVal = iacTypes.String("ingress", egressAtrr.GetMetadata()) + } + + actionAttr := resource.GetAttribute("rule_action") + actionVal := actionAttr.AsStringValueOrDefault("", resource) + + protocolAtrr := resource.GetAttribute("protocol") + protocolVal := protocolAtrr.AsStringValueOrDefault("-1", resource) + + cidrAttr := resource.GetAttribute("cidr_block") + if cidrAttr.IsNotNil() { + cidrs = append(cidrs, cidrAttr.AsStringValueOrDefault("", resource)) + } + ipv4cidrAttr := resource.GetAttribute("ipv6_cidr_block") + if ipv4cidrAttr.IsNotNil() { + cidrs = append(cidrs, ipv4cidrAttr.AsStringValueOrDefault("", resource)) + } + + return ec2.NetworkACLRule{ + Metadata: resource.GetMetadata(), + Type: typeVal, + Action: actionVal, + Protocol: protocolVal, + CIDRs: cidrs, + } +} diff --git a/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go b/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go new file mode 100644 index 000000000000..ab372f8f1084 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go @@ -0,0 +1,338 @@ +package ec2 + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_AdaptVPC(t *testing.T) { + tests := []struct { + name string + terraform string + expected ec2.EC2 + }{ + { + name: "defined", + terraform: ` + resource "aws_flow_log" "this" { + vpc_id = aws_vpc.main.id + } + resource "aws_default_vpc" "default" { + tags = { + Name = "Default VPC" + } + } + + resource "aws_vpc" "main" { + cidr_block = "4.5.6.7/32" + } + + resource "aws_security_group" "example" { + name = "http" + description = "Allow inbound HTTP traffic" + + ingress { + description = "Rule #1" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = [aws_vpc.main.cidr_block] + } + + egress { + cidr_blocks = ["1.2.3.4/32"] + } + } + + resource "aws_network_acl_rule" "example" { + egress = false + protocol = "tcp" + from_port = 22 + to_port = 22 + rule_action = "allow" + cidr_block = "10.0.0.0/16" + } + + resource "aws_security_group_rule" "example" { + type = "ingress" + description = "Rule #2" + security_group_id = aws_security_group.example.id + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = [ + "1.2.3.4/32", + "4.5.6.7/32", + ] + } +`, + expected: ec2.EC2{ + VPCs: []ec2.VPC{ + { + Metadata: iacTypes.NewTestMetadata(), + IsDefault: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + ID: iacTypes.String("", iacTypes.NewTestMetadata()), + FlowLogsEnabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + { + Metadata: iacTypes.NewTestMetadata(), + IsDefault: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + ID: iacTypes.String("", iacTypes.NewTestMetadata()), + FlowLogsEnabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("Allow inbound HTTP traffic", iacTypes.NewTestMetadata()), + IsDefault: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + VPCID: iacTypes.String("", iacTypes.NewTestMetadata()), + IngressRules: []ec2.SecurityGroupRule{ + { + Metadata: iacTypes.NewTestMetadata(), + + Description: iacTypes.String("Rule #1", iacTypes.NewTestMetadata()), + CIDRs: []iacTypes.StringValue{ + iacTypes.String("4.5.6.7/32", iacTypes.NewTestMetadata()), + }, + }, + { + Metadata: iacTypes.NewTestMetadata(), + + Description: iacTypes.String("Rule #2", iacTypes.NewTestMetadata()), + CIDRs: []iacTypes.StringValue{ + iacTypes.String("1.2.3.4/32", iacTypes.NewTestMetadata()), + iacTypes.String("4.5.6.7/32", iacTypes.NewTestMetadata()), + }, + }, + }, + + EgressRules: []ec2.SecurityGroupRule{ + { + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("", iacTypes.NewTestMetadata()), + CIDRs: []iacTypes.StringValue{ + iacTypes.String("1.2.3.4/32", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + NetworkACLs: []ec2.NetworkACL{ + { + Metadata: iacTypes.NewTestMetadata(), + Rules: []ec2.NetworkACLRule{ + { + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String("ingress", iacTypes.NewTestMetadata()), + Action: iacTypes.String("allow", iacTypes.NewTestMetadata()), + Protocol: iacTypes.String("tcp", iacTypes.NewTestMetadata()), + CIDRs: []iacTypes.StringValue{ + iacTypes.String("10.0.0.0/16", iacTypes.NewTestMetadata()), + }, + }, + }, + IsDefaultRule: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_security_group" "example" { + ingress { + } + + egress { + } + } + + resource "aws_network_acl_rule" "example" { + } +`, + expected: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("Managed by Terraform", iacTypes.NewTestMetadata()), + IsDefault: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + VPCID: iacTypes.String("", iacTypes.NewTestMetadata()), + IngressRules: []ec2.SecurityGroupRule{ + { + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + + EgressRules: []ec2.SecurityGroupRule{ + { + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + NetworkACLs: []ec2.NetworkACL{ + { + Metadata: iacTypes.NewTestMetadata(), + Rules: []ec2.NetworkACLRule{ + { + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String("ingress", iacTypes.NewTestMetadata()), + Action: iacTypes.String("", iacTypes.NewTestMetadata()), + Protocol: iacTypes.String("-1", iacTypes.NewTestMetadata()), + }, + }, + IsDefaultRule: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + { + name: "aws_flow_log refer to locals", + terraform: ` +locals { + vpc_id = try(aws_vpc.this.id, "") +} + +resource "aws_vpc" "this" { +} + +resource "aws_flow_log" "this" { + vpc_id = local.vpc_id +} +`, + expected: ec2.EC2{ + VPCs: []ec2.VPC{ + { + Metadata: iacTypes.NewTestMetadata(), + IsDefault: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + ID: iacTypes.String("", iacTypes.NewTestMetadata()), + FlowLogsEnabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestVPCLines(t *testing.T) { + src := ` + resource "aws_default_vpc" "default" { + } + + resource "aws_vpc" "main" { + cidr_block = "4.5.6.7/32" + } + + resource "aws_security_group" "example" { + name = "http" + description = "Allow inbound HTTP traffic" + + ingress { + description = "HTTP from VPC" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = [aws_vpc.main.cidr_block] + } + + egress { + cidr_blocks = ["1.2.3.4/32"] + } + } + + resource "aws_security_group_rule" "example" { + type = "ingress" + security_group_id = aws_security_group.example.id + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = [ + "1.2.3.4/32", + "4.5.6.7/32", + ] + } + + resource "aws_network_acl_rule" "example" { + egress = false + protocol = "tcp" + from_port = 22 + to_port = 22 + rule_action = "allow" + cidr_block = "10.0.0.0/16" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.VPCs, 2) + require.Len(t, adapted.SecurityGroups, 1) + require.Len(t, adapted.NetworkACLs, 1) + + defaultVPC := adapted.VPCs[0] + securityGroup := adapted.SecurityGroups[0] + networkACL := adapted.NetworkACLs[0] + + assert.Equal(t, 2, defaultVPC.Metadata.Range().GetStartLine()) + assert.Equal(t, 3, defaultVPC.Metadata.Range().GetEndLine()) + + assert.Equal(t, 9, securityGroup.Metadata.Range().GetStartLine()) + assert.Equal(t, 24, securityGroup.Metadata.Range().GetEndLine()) + + assert.Equal(t, 11, securityGroup.Description.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 11, securityGroup.Description.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 13, securityGroup.IngressRules[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 19, securityGroup.IngressRules[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 14, securityGroup.IngressRules[0].Description.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 14, securityGroup.IngressRules[0].Description.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 18, securityGroup.IngressRules[0].CIDRs[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 18, securityGroup.IngressRules[0].CIDRs[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 26, securityGroup.IngressRules[1].Metadata.Range().GetStartLine()) + assert.Equal(t, 36, securityGroup.IngressRules[1].Metadata.Range().GetEndLine()) + + assert.Equal(t, 32, securityGroup.IngressRules[1].CIDRs[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 35, securityGroup.IngressRules[1].CIDRs[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 21, securityGroup.EgressRules[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 23, securityGroup.EgressRules[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 22, securityGroup.EgressRules[0].CIDRs[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 22, securityGroup.EgressRules[0].CIDRs[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 38, networkACL.Rules[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 45, networkACL.Rules[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 39, networkACL.Rules[0].Type.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 39, networkACL.Rules[0].Type.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 40, networkACL.Rules[0].Protocol.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 40, networkACL.Rules[0].Protocol.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 43, networkACL.Rules[0].Action.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 43, networkACL.Rules[0].Action.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 44, networkACL.Rules[0].CIDRs[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 44, networkACL.Rules[0].CIDRs[0].GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/ecr/adapt.go b/pkg/iac/adapters/terraform/aws/ecr/adapt.go new file mode 100644 index 000000000000..57d5686dc7f7 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/ecr/adapt.go @@ -0,0 +1,114 @@ +package ecr + +import ( + "github.com/liamg/iamgo" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecr" + iamp "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) ecr.ECR { + return ecr.ECR{ + Repositories: adaptRepositories(modules), + } +} + +func adaptRepositories(modules terraform.Modules) []ecr.Repository { + var repositories []ecr.Repository + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_ecr_repository") { + repositories = append(repositories, adaptRepository(resource, module, modules)) + } + } + return repositories +} + +func adaptRepository(resource *terraform.Block, module *terraform.Module, modules terraform.Modules) ecr.Repository { + repo := ecr.Repository{ + Metadata: resource.GetMetadata(), + ImageScanning: ecr.ImageScanning{ + Metadata: resource.GetMetadata(), + ScanOnPush: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + ImageTagsImmutable: iacTypes.BoolDefault(false, resource.GetMetadata()), + Policies: nil, + Encryption: ecr.Encryption{ + Metadata: resource.GetMetadata(), + Type: iacTypes.StringDefault("AES256", resource.GetMetadata()), + KMSKeyID: iacTypes.StringDefault("", resource.GetMetadata()), + }, + } + + if imageScanningBlock := resource.GetBlock("image_scanning_configuration"); imageScanningBlock.IsNotNil() { + repo.ImageScanning.Metadata = imageScanningBlock.GetMetadata() + scanOnPushAttr := imageScanningBlock.GetAttribute("scan_on_push") + repo.ImageScanning.ScanOnPush = scanOnPushAttr.AsBoolValueOrDefault(false, imageScanningBlock) + } + + mutabilityAttr := resource.GetAttribute("image_tag_mutability") + if mutabilityAttr.Equals("IMMUTABLE") { + repo.ImageTagsImmutable = iacTypes.Bool(true, mutabilityAttr.GetMetadata()) + } else if mutabilityAttr.Equals("MUTABLE") { + repo.ImageTagsImmutable = iacTypes.Bool(false, mutabilityAttr.GetMetadata()) + } + + policyBlocks := module.GetReferencingResources(resource, "aws_ecr_repository_policy", "repository") + for _, policyRes := range policyBlocks { + if policyAttr := policyRes.GetAttribute("policy"); policyAttr.IsString() { + + dataBlock, err := module.GetBlockByID(policyAttr.Value().AsString()) + if err != nil { + + parsed, err := iamgo.ParseString(policyAttr.Value().AsString()) + if err != nil { + continue + } + + policy := iamp.Policy{ + Metadata: policyRes.GetMetadata(), + Name: iacTypes.StringDefault("", policyRes.GetMetadata()), + Document: iamp.Document{ + Parsed: *parsed, + Metadata: policyAttr.GetMetadata(), + }, + Builtin: iacTypes.Bool(false, policyRes.GetMetadata()), + } + + repo.Policies = append(repo.Policies, policy) + } else if dataBlock.Type() == "data" && dataBlock.TypeLabel() == "aws_iam_policy_document" { + if doc, err := iam.ConvertTerraformDocument(modules, dataBlock); err == nil { + policy := iamp.Policy{ + Metadata: policyRes.GetMetadata(), + Name: iacTypes.StringDefault("", policyRes.GetMetadata()), + Document: iamp.Document{ + Parsed: doc.Document, + Metadata: doc.Source.GetMetadata(), + IsOffset: true, + }, + Builtin: iacTypes.Bool(false, policyRes.GetMetadata()), + } + repo.Policies = append(repo.Policies, policy) + } + } + } + } + + if encryptBlock := resource.GetBlock("encryption_configuration"); encryptBlock.IsNotNil() { + repo.Encryption.Metadata = encryptBlock.GetMetadata() + encryptionTypeAttr := encryptBlock.GetAttribute("encryption_type") + repo.Encryption.Type = encryptionTypeAttr.AsStringValueOrDefault("AES256", encryptBlock) + + kmsKeyAttr := encryptBlock.GetAttribute("kms_key") + repo.Encryption.KMSKeyID = kmsKeyAttr.AsStringValueOrDefault("", encryptBlock) + if kmsKeyAttr.IsResourceBlockReference("aws_kms_key") { + if keyBlock, err := module.GetReferencedBlock(kmsKeyAttr, encryptBlock); err == nil { + repo.Encryption.KMSKeyID = iacTypes.String(keyBlock.FullName(), keyBlock.GetMetadata()) + } + } + } + + return repo +} diff --git a/pkg/iac/adapters/terraform/aws/ecr/adapt_test.go b/pkg/iac/adapters/terraform/aws/ecr/adapt_test.go new file mode 100644 index 000000000000..746adf8eacad --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/ecr/adapt_test.go @@ -0,0 +1,247 @@ +package ecr + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecr" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + + "github.com/liamg/iamgo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptRepository(t *testing.T) { + tests := []struct { + name string + terraform string + expected ecr.Repository + }{ + { + name: "configured", + terraform: ` + resource "aws_kms_key" "ecr_kms" { + enable_key_rotation = true + } + + resource "aws_ecr_repository" "foo" { + name = "bar" + image_tag_mutability = "MUTABLE" + + image_scanning_configuration { + scan_on_push = true + } + + encryption_configuration { + encryption_type = "KMS" + kms_key = aws_kms_key.ecr_kms.key_id + } + } + + resource "aws_ecr_repository_policy" "foopolicy" { + repository = aws_ecr_repository.foo.name + + policy = < 0 { + var volumes []ecs.Volume + for _, volumeBlock := range volumeBlocks { + volumes = append(volumes, ecs.Volume{ + Metadata: volumeBlock.GetMetadata(), + EFSVolumeConfiguration: adaptEFSVolumeConfiguration(volumeBlock), + }) + } + return volumes + } + + return []ecs.Volume{} +} + +func adaptEFSVolumeConfiguration(volumeBlock *terraform.Block) ecs.EFSVolumeConfiguration { + EFSVolumeConfiguration := ecs.EFSVolumeConfiguration{ + Metadata: volumeBlock.GetMetadata(), + TransitEncryptionEnabled: types.BoolDefault(true, volumeBlock.GetMetadata()), + } + + if EFSConfigBlock := volumeBlock.GetBlock("efs_volume_configuration"); EFSConfigBlock.IsNotNil() { + EFSVolumeConfiguration.Metadata = EFSConfigBlock.GetMetadata() + transitEncryptionAttr := EFSConfigBlock.GetAttribute("transit_encryption") + EFSVolumeConfiguration.TransitEncryptionEnabled = types.Bool(transitEncryptionAttr.Equals("ENABLED"), EFSConfigBlock.GetMetadata()) + if transitEncryptionAttr.IsNotNil() { + EFSVolumeConfiguration.TransitEncryptionEnabled = types.Bool(transitEncryptionAttr.Equals("ENABLED"), transitEncryptionAttr.GetMetadata()) + } + } + + return EFSVolumeConfiguration +} diff --git a/pkg/iac/adapters/terraform/aws/ecs/adapt_test.go b/pkg/iac/adapters/terraform/aws/ecs/adapt_test.go new file mode 100644 index 000000000000..d7c0033cefba --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/ecs/adapt_test.go @@ -0,0 +1,245 @@ +package ecs + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecs" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptClusterSettings(t *testing.T) { + tests := []struct { + name string + terraform string + expected ecs.ClusterSettings + }{ + { + name: "container insights enabled", + terraform: ` + resource "aws_ecs_cluster" "example" { + name = "services-cluster" + + setting { + name = "containerInsights" + value = "enabled" + } + } +`, + expected: ecs.ClusterSettings{ + Metadata: iacTypes.NewTestMetadata(), + ContainerInsightsEnabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + { + name: "invalid name", + terraform: ` + resource "aws_ecs_cluster" "example" { + name = "services-cluster" + + setting { + name = "invalidName" + value = "enabled" + } + } +`, + expected: ecs.ClusterSettings{ + Metadata: iacTypes.NewTestMetadata(), + ContainerInsightsEnabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_ecs_cluster" "example" { + } +`, + expected: ecs.ClusterSettings{ + Metadata: iacTypes.NewTestMetadata(), + ContainerInsightsEnabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptClusterSettings(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptTaskDefinitionResource(t *testing.T) { + tests := []struct { + name string + terraform string + expected ecs.TaskDefinition + }{ + { + name: "configured", + terraform: ` + resource "aws_ecs_task_definition" "example" { + family = "service" + container_definitions = < 0 { + orphanage := elb.LoadBalancer{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Type: iacTypes.StringDefault(elb.TypeApplication, iacTypes.NewUnmanagedMetadata()), + DropInvalidHeaderFields: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + Internal: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + Listeners: nil, + } + for _, listenerResource := range orphanResources { + orphanage.Listeners = append(orphanage.Listeners, adaptListener(listenerResource, "application")) + } + loadBalancers = append(loadBalancers, orphanage) + } + + return loadBalancers +} + +func (a *adapter) adaptLoadBalancer(resource *terraform.Block, module terraform.Modules) elb.LoadBalancer { + var listeners []elb.Listener + + typeAttr := resource.GetAttribute("load_balancer_type") + typeVal := typeAttr.AsStringValueOrDefault("application", resource) + + dropInvalidHeadersAttr := resource.GetAttribute("drop_invalid_header_fields") + dropInvalidHeadersVal := dropInvalidHeadersAttr.AsBoolValueOrDefault(false, resource) + + internalAttr := resource.GetAttribute("internal") + internalVal := internalAttr.AsBoolValueOrDefault(false, resource) + + listenerBlocks := module.GetReferencingResources(resource, "aws_lb_listener", "load_balancer_arn") + listenerBlocks = append(listenerBlocks, module.GetReferencingResources(resource, "aws_alb_listener", "load_balancer_arn")...) + + for _, listenerBlock := range listenerBlocks { + a.listenerIDs.Resolve(listenerBlock.ID()) + listeners = append(listeners, adaptListener(listenerBlock, typeVal.Value())) + } + return elb.LoadBalancer{ + Metadata: resource.GetMetadata(), + Type: typeVal, + DropInvalidHeaderFields: dropInvalidHeadersVal, + Internal: internalVal, + Listeners: listeners, + } +} + +func (a *adapter) adaptClassicLoadBalancer(resource *terraform.Block, module terraform.Modules) elb.LoadBalancer { + internalAttr := resource.GetAttribute("internal") + internalVal := internalAttr.AsBoolValueOrDefault(false, resource) + + return elb.LoadBalancer{ + Metadata: resource.GetMetadata(), + Type: iacTypes.String("classic", resource.GetMetadata()), + DropInvalidHeaderFields: iacTypes.BoolDefault(false, resource.GetMetadata()), + Internal: internalVal, + Listeners: nil, + } +} + +func adaptListener(listenerBlock *terraform.Block, typeVal string) elb.Listener { + listener := elb.Listener{ + Metadata: listenerBlock.GetMetadata(), + Protocol: iacTypes.StringDefault("", listenerBlock.GetMetadata()), + TLSPolicy: iacTypes.StringDefault("", listenerBlock.GetMetadata()), + DefaultActions: nil, + } + + protocolAttr := listenerBlock.GetAttribute("protocol") + if typeVal == "application" { + listener.Protocol = protocolAttr.AsStringValueOrDefault("HTTP", listenerBlock) + } + + sslPolicyAttr := listenerBlock.GetAttribute("ssl_policy") + listener.TLSPolicy = sslPolicyAttr.AsStringValueOrDefault("", listenerBlock) + + for _, defaultActionBlock := range listenerBlock.GetBlocks("default_action") { + action := elb.Action{ + Metadata: defaultActionBlock.GetMetadata(), + Type: defaultActionBlock.GetAttribute("type").AsStringValueOrDefault("", defaultActionBlock), + } + listener.DefaultActions = append(listener.DefaultActions, action) + } + + return listener +} diff --git a/pkg/iac/adapters/terraform/aws/elb/adapt_test.go b/pkg/iac/adapters/terraform/aws/elb/adapt_test.go new file mode 100644 index 000000000000..b938ce563106 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/elb/adapt_test.go @@ -0,0 +1,160 @@ +package elb + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected elb.ELB + }{ + { + name: "configured", + terraform: ` + resource "aws_alb" "example" { + name = "good_alb" + internal = true + load_balancer_type = "application" + + access_logs { + bucket = aws_s3_bucket.lb_logs.bucket + prefix = "test-lb" + enabled = true + } + + drop_invalid_header_fields = true + } + + resource "aws_alb_listener" "example" { + load_balancer_arn = aws_alb.example.arn + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-TLS-1-1-2017-01" + + default_action { + type = "forward" + } + } +`, + expected: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String("application", iacTypes.NewTestMetadata()), + DropInvalidHeaderFields: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Internal: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Listeners: []elb.Listener{ + { + Metadata: iacTypes.NewTestMetadata(), + Protocol: iacTypes.String("HTTPS", iacTypes.NewTestMetadata()), + TLSPolicy: iacTypes.String("ELBSecurityPolicy-TLS-1-1-2017-01", iacTypes.NewTestMetadata()), + DefaultActions: []elb.Action{ + { + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String("forward", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_alb" "example" { + } +`, + expected: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String("application", iacTypes.NewTestMetadata()), + DropInvalidHeaderFields: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Internal: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Listeners: nil, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_alb" "example" { + name = "good_alb" + internal = true + load_balancer_type = "application" + drop_invalid_header_fields = true + + access_logs { + bucket = aws_s3_bucket.lb_logs.bucket + prefix = "test-lb" + enabled = true + } + } + + resource "aws_alb_listener" "example" { + load_balancer_arn = aws_alb.example.arn + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-TLS-1-1-2017-01" + + default_action { + type = "forward" + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.LoadBalancers, 1) + loadBalancer := adapted.LoadBalancers[0] + + assert.Equal(t, 2, loadBalancer.Metadata.Range().GetStartLine()) + assert.Equal(t, 13, loadBalancer.Metadata.Range().GetEndLine()) + + assert.Equal(t, 4, loadBalancer.Internal.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, loadBalancer.Internal.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 5, loadBalancer.Type.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 5, loadBalancer.Type.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 6, loadBalancer.DropInvalidHeaderFields.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, loadBalancer.DropInvalidHeaderFields.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 15, loadBalancer.Listeners[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 23, loadBalancer.Listeners[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 17, loadBalancer.Listeners[0].Protocol.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 17, loadBalancer.Listeners[0].Protocol.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 18, loadBalancer.Listeners[0].TLSPolicy.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 18, loadBalancer.Listeners[0].TLSPolicy.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 20, loadBalancer.Listeners[0].DefaultActions[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 22, loadBalancer.Listeners[0].DefaultActions[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 21, loadBalancer.Listeners[0].DefaultActions[0].Type.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 21, loadBalancer.Listeners[0].DefaultActions[0].Type.GetMetadata().Range().GetEndLine()) + +} diff --git a/pkg/iac/adapters/terraform/aws/emr/adapt.go b/pkg/iac/adapters/terraform/aws/emr/adapt.go new file mode 100644 index 000000000000..74bbc08516f4 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/emr/adapt.go @@ -0,0 +1,49 @@ +package emr + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/emr" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) emr.EMR { + return emr.EMR{ + Clusters: adaptClusters(modules), + SecurityConfiguration: adaptSecurityConfigurations(modules), + } +} +func adaptClusters(modules terraform.Modules) []emr.Cluster { + var clusters []emr.Cluster + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_emr_cluster") { + clusters = append(clusters, adaptCluster(resource)) + } + } + return clusters +} + +func adaptCluster(resource *terraform.Block) emr.Cluster { + + return emr.Cluster{ + Metadata: resource.GetMetadata(), + } +} + +func adaptSecurityConfigurations(modules terraform.Modules) []emr.SecurityConfiguration { + var securityConfiguration []emr.SecurityConfiguration + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_emr_security_configuration") { + securityConfiguration = append(securityConfiguration, adaptSecurityConfiguration(resource)) + } + } + return securityConfiguration +} + +func adaptSecurityConfiguration(resource *terraform.Block) emr.SecurityConfiguration { + + return emr.SecurityConfiguration{ + Metadata: resource.GetMetadata(), + Name: resource.GetAttribute("name").AsStringValueOrDefault("", resource), + Configuration: resource.GetAttribute("configuration").AsStringValueOrDefault("", resource), + } + +} diff --git a/pkg/iac/adapters/terraform/aws/emr/adapt_test.go b/pkg/iac/adapters/terraform/aws/emr/adapt_test.go new file mode 100644 index 000000000000..5b0e43f4186a --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/emr/adapt_test.go @@ -0,0 +1,115 @@ +package emr + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/emr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptSecurityConfiguration(t *testing.T) { + tests := []struct { + name string + terraform string + expected emr.SecurityConfiguration + }{ + { + name: "test", + terraform: ` + resource "aws_emr_security_configuration" "foo" { + name = "emrsc_test" + configuration = < 0, + }, nil + } + + if dataBlock.Type() == "data" && dataBlock.TypeLabel() == "aws_iam_policy_document" { + if doc, err := ConvertTerraformDocument(modules, dataBlock); err == nil { + return &iam.Document{ + Metadata: dataBlock.GetMetadata(), + Parsed: doc.Document, + IsOffset: true, + HasRefs: false, + }, nil + } + } + } + + return &iam.Document{ + Metadata: owner.GetMetadata(), + }, nil +} + +func unescapeVars(input string) string { + return strings.ReplaceAll(input, "&{", "${") +} + +// ConvertTerraformDocument converts a terraform data policy into an iamgo policy https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document +func ConvertTerraformDocument(modules terraform.Modules, block *terraform.Block) (*wrappedDocument, error) { + + builder := iamgo.NewPolicyBuilder() + + if sourceAttr := block.GetAttribute("source_json"); sourceAttr.IsString() { + doc, err := iamgo.ParseString(sourceAttr.Value().AsString()) + if err != nil { + return nil, err + } + builder = iamgo.PolicyBuilderFromDocument(*doc) + } + + if sourceDocumentsAttr := block.GetAttribute("source_policy_documents"); sourceDocumentsAttr.IsIterable() { + docs := findAllPolicies(modules, sourceDocumentsAttr) + for _, doc := range docs { + statements, _ := doc.Document.Statements() + for _, statement := range statements { + builder.WithStatement(statement) + } + } + } + + if idAttr := block.GetAttribute("policy_id"); idAttr.IsString() { + r := idAttr.GetMetadata().Range() + builder.WithId(idAttr.Value().AsString(), r.GetStartLine(), r.GetEndLine()) + } + + if versionAttr := block.GetAttribute("version"); versionAttr.IsString() { + r := versionAttr.GetMetadata().Range() + builder.WithVersion(versionAttr.Value().AsString(), r.GetStartLine(), r.GetEndLine()) + } + + for _, statementBlock := range block.GetBlocks("statement") { + statement := parseStatement(statementBlock) + builder.WithStatement(statement, statement.Range().StartLine, statement.Range().EndLine) + } + + if overrideDocumentsAttr := block.GetAttribute("override_policy_documents"); overrideDocumentsAttr.IsIterable() { + docs := findAllPolicies(modules, overrideDocumentsAttr) + for _, doc := range docs { + statements, _ := doc.Document.Statements() + for _, statement := range statements { + builder.WithStatement(statement, statement.Range().StartLine, statement.Range().EndLine) + } + } + } + + return &wrappedDocument{Document: builder.Build(), Source: block}, nil +} + +// nolint +func parseStatement(statementBlock *terraform.Block) iamgo.Statement { + + metadata := statementBlock.GetMetadata() + + builder := iamgo.NewStatementBuilder() + builder.WithRange(metadata.Range().GetStartLine(), metadata.Range().GetEndLine()) + + if sidAttr := statementBlock.GetAttribute("sid"); sidAttr.IsString() { + r := sidAttr.GetMetadata().Range() + builder.WithSid(sidAttr.Value().AsString(), r.GetStartLine(), r.GetEndLine()) + } + if actionsAttr := statementBlock.GetAttribute("actions"); actionsAttr.IsIterable() { + r := actionsAttr.GetMetadata().Range() + values := actionsAttr.AsStringValues().AsStrings() + builder.WithActions(values, r.GetStartLine(), r.GetEndLine()) + } + if notActionsAttr := statementBlock.GetAttribute("not_actions"); notActionsAttr.IsIterable() { + r := notActionsAttr.GetMetadata().Range() + values := notActionsAttr.AsStringValues().AsStrings() + builder.WithNotActions(values, r.GetStartLine(), r.GetEndLine()) + } + if resourcesAttr := statementBlock.GetAttribute("resources"); resourcesAttr.IsIterable() { + r := resourcesAttr.GetMetadata().Range() + values := resourcesAttr.AsStringValues().AsStrings() + builder.WithResources(values, r.GetStartLine(), r.GetEndLine()) + } + if notResourcesAttr := statementBlock.GetAttribute("not_resources"); notResourcesAttr.IsIterable() { + r := notResourcesAttr.GetMetadata().Range() + values := notResourcesAttr.AsStringValues().AsStrings() + builder.WithNotResources(values, r.GetStartLine(), r.GetEndLine()) + } + if effectAttr := statementBlock.GetAttribute("effect"); effectAttr.IsString() { + r := effectAttr.GetMetadata().Range() + builder.WithEffect(effectAttr.Value().AsString(), r.GetStartLine(), r.GetEndLine()) + } else { + builder.WithEffect(iamgo.EffectAllow) + } + + for _, principalBlock := range statementBlock.GetBlocks("principals") { + typeAttr := principalBlock.GetAttribute("type") + if !typeAttr.IsString() { + continue + } + identifiersAttr := principalBlock.GetAttribute("identifiers") + if !identifiersAttr.IsIterable() { + continue + } + r := principalBlock.GetMetadata().Range() + switch typeAttr.Value().AsString() { + case "*": + builder.WithAllPrincipals(true, r.GetStartLine(), r.GetEndLine()) + case "AWS": + values := identifiersAttr.AsStringValues().AsStrings() + builder.WithAWSPrincipals(values, r.GetStartLine(), r.GetEndLine()) + case "Federated": + values := identifiersAttr.AsStringValues().AsStrings() + builder.WithFederatedPrincipals(values, r.GetStartLine(), r.GetEndLine()) + case "Service": + values := identifiersAttr.AsStringValues().AsStrings() + builder.WithServicePrincipals(values, r.GetStartLine(), r.GetEndLine()) + case "CanonicalUser": + values := identifiersAttr.AsStringValues().AsStrings() + builder.WithCanonicalUsersPrincipals(values, r.GetStartLine(), r.GetEndLine()) + } + } + + for _, conditionBlock := range statementBlock.GetBlocks("condition") { + testAttr := conditionBlock.GetAttribute("test") + if !testAttr.IsString() { + continue + } + variableAttr := conditionBlock.GetAttribute("variable") + if !variableAttr.IsString() { + continue + } + valuesAttr := conditionBlock.GetAttribute("values") + values := valuesAttr.AsStringValues().AsStrings() + if valuesAttr.IsNil() || len(values) == 0 { + continue + } + + r := conditionBlock.GetMetadata().Range() + + builder.WithCondition( + testAttr.Value().AsString(), + variableAttr.Value().AsString(), + values, + r.GetStartLine(), + r.GetEndLine(), + ) + + } + return builder.Build() +} + +func findAllPolicies(modules terraform.Modules, attr *terraform.Attribute) []wrappedDocument { + var documents []wrappedDocument + + if !attr.IsIterable() { + return documents + } + + policyDocIDs := attr.AsStringValues().AsStrings() + for _, policyDocID := range policyDocIDs { + if policyDoc, err := modules.GetBlockById(policyDocID); err == nil { + if document, err := ConvertTerraformDocument(modules, policyDoc); err == nil { + documents = append(documents, *document) + } + } else if parsed, err := iamgo.Parse([]byte(unescapeVars(policyDocID))); err == nil { + documents = append(documents, wrappedDocument{ + Document: *parsed, + Source: attr, + }) + } + } + return documents +} diff --git a/pkg/iac/adapters/terraform/aws/iam/groups.go b/pkg/iac/adapters/terraform/aws/iam/groups.go new file mode 100644 index 000000000000..9053bf83be78 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/iam/groups.go @@ -0,0 +1,32 @@ +package iam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func adaptGroups(modules terraform.Modules) []iam.Group { + var groups []iam.Group + + for _, groupBlock := range modules.GetResourcesByType("aws_iam_group") { + group := iam.Group{ + Metadata: groupBlock.GetMetadata(), + Name: groupBlock.GetAttribute("name").AsStringValueOrDefault("", groupBlock), + } + + if policy, ok := applyForDependentResource( + modules, groupBlock.ID(), "name", "aws_iam_group_policy", "group", findPolicy(modules), + ); ok && policy != nil { + group.Policies = append(group.Policies, *policy) + } + + if policy, ok := applyForDependentResource( + modules, groupBlock.ID(), "name", "aws_iam_group_policy_attachment", "group", findAttachmentPolicy(modules), + ); ok && policy != nil { + group.Policies = append(group.Policies, *policy) + } + + groups = append(groups, group) + } + return groups +} diff --git a/pkg/iac/adapters/terraform/aws/iam/groups_test.go b/pkg/iac/adapters/terraform/aws/iam/groups_test.go new file mode 100644 index 000000000000..f522130b30d2 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/iam/groups_test.go @@ -0,0 +1,114 @@ +package iam + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" +) + +func Test_adaptGroups(t *testing.T) { + tests := []struct { + name string + terraform string + expected []iam.Group + }{ + { + name: "policy", + terraform: ` + resource "aws_iam_group_policy" "my_developer_policy" { + name = "my_developer_policy" + group = aws_iam_group.my_developers.name + + policy = < 0 { + orphanage := lambda.Function{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Tracing: lambda.Tracing{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Mode: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + }, + Permissions: nil, + } + for _, permission := range orphanResources { + orphanage.Permissions = append(orphanage.Permissions, a.adaptPermission(permission)) + } + functions = append(functions, orphanage) + } + + return functions +} + +func (a *adapter) adaptFunction(function *terraform.Block, modules terraform.Modules, orphans terraform.ResourceIDResolutions) lambda.Function { + var permissions []lambda.Permission + for _, module := range modules { + for _, p := range module.GetResourcesByType("aws_lambda_permission") { + if referencedBlock, err := module.GetReferencedBlock(p.GetAttribute("function_name"), p); err == nil && referencedBlock == function { + permissions = append(permissions, a.adaptPermission(p)) + delete(orphans, p.ID()) + } + } + } + + return lambda.Function{ + Metadata: function.GetMetadata(), + Tracing: a.adaptTracing(function), + Permissions: permissions, + } +} + +func (a *adapter) adaptTracing(function *terraform.Block) lambda.Tracing { + if tracingConfig := function.GetBlock("tracing_config"); tracingConfig.IsNotNil() { + return lambda.Tracing{ + Metadata: tracingConfig.GetMetadata(), + Mode: tracingConfig.GetAttribute("mode").AsStringValueOrDefault("", tracingConfig), + } + } + + return lambda.Tracing{ + Metadata: function.GetMetadata(), + Mode: iacTypes.StringDefault("", function.GetMetadata()), + } +} + +func (a *adapter) adaptPermission(permission *terraform.Block) lambda.Permission { + sourceARNAttr := permission.GetAttribute("source_arn") + sourceARN := sourceARNAttr.AsStringValueOrDefault("", permission) + + if len(sourceARNAttr.AllReferences()) > 0 { + sourceARN = iacTypes.String(sourceARNAttr.AllReferences()[0].NameLabel(), sourceARNAttr.GetMetadata()) + } + + return lambda.Permission{ + Metadata: permission.GetMetadata(), + Principal: permission.GetAttribute("principal").AsStringValueOrDefault("", permission), + SourceARN: sourceARN, + } +} diff --git a/pkg/iac/adapters/terraform/aws/lambda/adapt_test.go b/pkg/iac/adapters/terraform/aws/lambda/adapt_test.go new file mode 100644 index 000000000000..8ec555c7d90f --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/lambda/adapt_test.go @@ -0,0 +1,154 @@ +package lambda + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/lambda" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected lambda.Lambda + }{ + { + name: "reference arn", + terraform: ` + resource "aws_lambda_function" "example" { + filename = "lambda_function_payload.zip" + function_name = "lambda_function_name" + role = aws_iam_role.iam_for_lambda.arn + runtime = "nodejs12.x" + + tracing_config { + mode = "Passthrough" + } + } + + resource "aws_lambda_permission" "example" { + statement_id = "AllowExecutionFromSNS" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.example.function_name + principal = "sns.amazonaws.com" + source_arn = aws_sns_topic.default.arn + } +`, + expected: lambda.Lambda{ + Functions: []lambda.Function{ + { + Metadata: iacTypes.NewTestMetadata(), + Tracing: lambda.Tracing{ + Metadata: iacTypes.NewTestMetadata(), + Mode: iacTypes.String("Passthrough", iacTypes.NewTestMetadata()), + }, + Permissions: []lambda.Permission{ + { + Metadata: iacTypes.NewTestMetadata(), + Principal: iacTypes.String("sns.amazonaws.com", iacTypes.NewTestMetadata()), + SourceARN: iacTypes.String("default", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + { + name: "defaults (with an orphan)", + terraform: ` + resource "aws_lambda_function" "example" { + tracing_config { + } + } + + resource "aws_lambda_permission" "example" { + } +`, + expected: lambda.Lambda{ + Functions: []lambda.Function{ + { + Metadata: iacTypes.NewTestMetadata(), + Tracing: lambda.Tracing{ + Metadata: iacTypes.NewTestMetadata(), + Mode: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + { + Metadata: iacTypes.NewTestMetadata(), + Tracing: lambda.Tracing{ + Metadata: iacTypes.NewTestMetadata(), + Mode: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + Permissions: []lambda.Permission{ + { + Metadata: iacTypes.NewTestMetadata(), + Principal: iacTypes.String("", iacTypes.NewTestMetadata()), + SourceARN: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_lambda_function" "example" { + filename = "lambda_function_payload.zip" + function_name = "lambda_function_name" + role = aws_iam_role.iam_for_lambda.arn + runtime = "nodejs12.x" + + tracing_config { + mode = "Passthrough" + } + } + + resource "aws_lambda_permission" "example" { + statement_id = "AllowExecutionFromSNS" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.example.function_name + principal = "sns.amazonaws.com" + source_arn = "string arn" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Functions, 1) + function := adapted.Functions[0] + + assert.Equal(t, 2, function.Metadata.Range().GetStartLine()) + assert.Equal(t, 11, function.Metadata.Range().GetEndLine()) + + assert.Equal(t, 8, function.Tracing.Metadata.Range().GetStartLine()) + assert.Equal(t, 10, function.Tracing.Metadata.Range().GetEndLine()) + + assert.Equal(t, 9, function.Tracing.Mode.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 9, function.Tracing.Mode.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 13, function.Permissions[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 19, function.Permissions[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 17, function.Permissions[0].Principal.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 17, function.Permissions[0].Principal.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 18, function.Permissions[0].SourceARN.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 18, function.Permissions[0].SourceARN.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/mq/adapt.go b/pkg/iac/adapters/terraform/aws/mq/adapt.go new file mode 100644 index 000000000000..d0fed28d5b83 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/mq/adapt.go @@ -0,0 +1,48 @@ +package mq + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/mq" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) mq.MQ { + return mq.MQ{ + Brokers: adaptBrokers(modules), + } +} + +func adaptBrokers(modules terraform.Modules) []mq.Broker { + var brokers []mq.Broker + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_mq_broker") { + brokers = append(brokers, adaptBroker(resource)) + } + } + return brokers +} + +func adaptBroker(resource *terraform.Block) mq.Broker { + + broker := mq.Broker{ + Metadata: resource.GetMetadata(), + PublicAccess: types.BoolDefault(false, resource.GetMetadata()), + Logging: mq.Logging{ + Metadata: resource.GetMetadata(), + General: types.BoolDefault(false, resource.GetMetadata()), + Audit: types.BoolDefault(false, resource.GetMetadata()), + }, + } + + publicAccessAttr := resource.GetAttribute("publicly_accessible") + broker.PublicAccess = publicAccessAttr.AsBoolValueOrDefault(false, resource) + if logsBlock := resource.GetBlock("logs"); logsBlock.IsNotNil() { + broker.Logging.Metadata = logsBlock.GetMetadata() + auditAttr := logsBlock.GetAttribute("audit") + broker.Logging.Audit = auditAttr.AsBoolValueOrDefault(false, logsBlock) + generalAttr := logsBlock.GetAttribute("general") + broker.Logging.General = generalAttr.AsBoolValueOrDefault(false, logsBlock) + } + + return broker +} diff --git a/pkg/iac/adapters/terraform/aws/mq/adapt_test.go b/pkg/iac/adapters/terraform/aws/mq/adapt_test.go new file mode 100644 index 000000000000..fe8d77432b62 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/mq/adapt_test.go @@ -0,0 +1,118 @@ +package mq + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/mq" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptBroker(t *testing.T) { + tests := []struct { + name string + terraform string + expected mq.Broker + }{ + { + name: "audit logs", + terraform: ` + resource "aws_mq_broker" "example" { + logs { + audit = true + } + + publicly_accessible = false + } +`, + expected: mq.Broker{ + Metadata: iacTypes.NewTestMetadata(), + PublicAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Logging: mq.Logging{ + Metadata: iacTypes.NewTestMetadata(), + General: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Audit: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + { + name: "general logs", + terraform: ` + resource "aws_mq_broker" "example" { + logs { + general = true + } + + publicly_accessible = true + } +`, + expected: mq.Broker{ + Metadata: iacTypes.NewTestMetadata(), + PublicAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Logging: mq.Logging{ + Metadata: iacTypes.NewTestMetadata(), + General: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Audit: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_mq_broker" "example" { + } +`, + expected: mq.Broker{ + Metadata: iacTypes.NewTestMetadata(), + PublicAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Logging: mq.Logging{ + Metadata: iacTypes.NewTestMetadata(), + General: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Audit: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptBroker(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_mq_broker" "example" { + logs { + general = true + } + + publicly_accessible = true + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Brokers, 1) + broker := adapted.Brokers[0] + + assert.Equal(t, 2, broker.Metadata.Range().GetStartLine()) + assert.Equal(t, 8, broker.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, broker.Logging.Metadata.Range().GetStartLine()) + assert.Equal(t, 5, broker.Logging.Metadata.Range().GetEndLine()) + + assert.Equal(t, 4, broker.Logging.General.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, broker.Logging.General.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 7, broker.PublicAccess.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, broker.PublicAccess.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/msk/adapt.go b/pkg/iac/adapters/terraform/aws/msk/adapt.go new file mode 100644 index 000000000000..520b68b2a06c --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/msk/adapt.go @@ -0,0 +1,97 @@ +package msk + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/msk" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) msk.MSK { + return msk.MSK{ + Clusters: adaptClusters(modules), + } +} + +func adaptClusters(modules terraform.Modules) []msk.Cluster { + var clusters []msk.Cluster + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_msk_cluster") { + clusters = append(clusters, adaptCluster(resource)) + } + } + return clusters +} + +func adaptCluster(resource *terraform.Block) msk.Cluster { + cluster := msk.Cluster{ + Metadata: resource.GetMetadata(), + EncryptionInTransit: msk.EncryptionInTransit{ + Metadata: resource.GetMetadata(), + ClientBroker: iacTypes.StringDefault("TLS_PLAINTEXT", resource.GetMetadata()), + }, + EncryptionAtRest: msk.EncryptionAtRest{ + Metadata: resource.GetMetadata(), + KMSKeyARN: iacTypes.StringDefault("", resource.GetMetadata()), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + Logging: msk.Logging{ + Metadata: resource.GetMetadata(), + Broker: msk.BrokerLogging{ + Metadata: resource.GetMetadata(), + S3: msk.S3Logging{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + Cloudwatch: msk.CloudwatchLogging{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + Firehose: msk.FirehoseLogging{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + }, + }, + } + + if encryptBlock := resource.GetBlock("encryption_info"); encryptBlock.IsNotNil() { + if encryptionInTransitBlock := encryptBlock.GetBlock("encryption_in_transit"); encryptionInTransitBlock.IsNotNil() { + cluster.EncryptionInTransit.Metadata = encryptionInTransitBlock.GetMetadata() + if clientBrokerAttr := encryptionInTransitBlock.GetAttribute("client_broker"); clientBrokerAttr.IsNotNil() { + cluster.EncryptionInTransit.ClientBroker = clientBrokerAttr.AsStringValueOrDefault("TLS", encryptionInTransitBlock) + } + } + + if encryptionAtRestAttr := encryptBlock.GetAttribute("encryption_at_rest_kms_key_arn"); encryptionAtRestAttr.IsNotNil() { + cluster.EncryptionAtRest.Metadata = encryptionAtRestAttr.GetMetadata() + cluster.EncryptionAtRest.KMSKeyARN = encryptionAtRestAttr.AsStringValueOrDefault("", encryptBlock) + cluster.EncryptionAtRest.Enabled = iacTypes.Bool(true, encryptionAtRestAttr.GetMetadata()) + } + } + + if logBlock := resource.GetBlock("logging_info"); logBlock.IsNotNil() { + cluster.Logging.Metadata = logBlock.GetMetadata() + if brokerLogsBlock := logBlock.GetBlock("broker_logs"); brokerLogsBlock.IsNotNil() { + cluster.Logging.Broker.Metadata = brokerLogsBlock.GetMetadata() + if brokerLogsBlock.HasChild("s3") { + if s3Block := brokerLogsBlock.GetBlock("s3"); s3Block.IsNotNil() { + s3enabledAttr := s3Block.GetAttribute("enabled") + cluster.Logging.Broker.S3.Metadata = s3Block.GetMetadata() + cluster.Logging.Broker.S3.Enabled = s3enabledAttr.AsBoolValueOrDefault(false, s3Block) + } + } + if cloudwatchBlock := brokerLogsBlock.GetBlock("cloudwatch_logs"); cloudwatchBlock.IsNotNil() { + cwEnabledAttr := cloudwatchBlock.GetAttribute("enabled") + cluster.Logging.Broker.Cloudwatch.Metadata = cloudwatchBlock.GetMetadata() + cluster.Logging.Broker.Cloudwatch.Enabled = cwEnabledAttr.AsBoolValueOrDefault(false, cloudwatchBlock) + } + if firehoseBlock := brokerLogsBlock.GetBlock("firehose"); firehoseBlock.IsNotNil() { + firehoseEnabledAttr := firehoseBlock.GetAttribute("enabled") + cluster.Logging.Broker.Firehose.Metadata = firehoseBlock.GetMetadata() + cluster.Logging.Broker.Firehose.Enabled = firehoseEnabledAttr.AsBoolValueOrDefault(false, firehoseBlock) + } + } + } + + return cluster +} diff --git a/pkg/iac/adapters/terraform/aws/msk/adapt_test.go b/pkg/iac/adapters/terraform/aws/msk/adapt_test.go new file mode 100644 index 000000000000..dae131487b4f --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/msk/adapt_test.go @@ -0,0 +1,199 @@ +package msk + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/msk" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptCluster(t *testing.T) { + tests := []struct { + name string + terraform string + expected msk.Cluster + }{ + { + name: "configured", + terraform: ` + resource "aws_msk_cluster" "example" { + cluster_name = "example" + + encryption_info { + encryption_in_transit { + client_broker = "TLS" + in_cluster = true + } + encryption_at_rest_kms_key_arn = "foo-bar-key" + } + + logging_info { + broker_logs { + cloudwatch_logs { + enabled = true + log_group = aws_cloudwatch_log_group.test.name + } + firehose { + enabled = true + delivery_stream = aws_kinesis_firehose_delivery_stream.test_stream.name + } + s3 { + enabled = true + bucket = aws_s3_bucket.bucket.id + prefix = "logs/msk-" + } + } + } + } +`, + expected: msk.Cluster{ + Metadata: iacTypes.NewTestMetadata(), + EncryptionInTransit: msk.EncryptionInTransit{ + Metadata: iacTypes.NewTestMetadata(), + ClientBroker: iacTypes.String("TLS", iacTypes.NewTestMetadata()), + }, + EncryptionAtRest: msk.EncryptionAtRest{ + Metadata: iacTypes.NewTestMetadata(), + KMSKeyARN: iacTypes.String("foo-bar-key", iacTypes.NewTestMetadata()), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + Logging: msk.Logging{ + Metadata: iacTypes.NewTestMetadata(), + Broker: msk.BrokerLogging{ + Metadata: iacTypes.NewTestMetadata(), + S3: msk.S3Logging{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + Cloudwatch: msk.CloudwatchLogging{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + Firehose: msk.FirehoseLogging{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_msk_cluster" "example" { + } +`, + expected: msk.Cluster{ + Metadata: iacTypes.NewTestMetadata(), + EncryptionInTransit: msk.EncryptionInTransit{ + Metadata: iacTypes.NewTestMetadata(), + ClientBroker: iacTypes.String("TLS_PLAINTEXT", iacTypes.NewTestMetadata()), + }, + Logging: msk.Logging{ + Metadata: iacTypes.NewTestMetadata(), + Broker: msk.BrokerLogging{ + Metadata: iacTypes.NewTestMetadata(), + S3: msk.S3Logging{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + Cloudwatch: msk.CloudwatchLogging{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + Firehose: msk.FirehoseLogging{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptCluster(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_msk_cluster" "example" { + cluster_name = "example" + + encryption_info { + encryption_in_transit { + client_broker = "TLS" + in_cluster = true + } + encryption_at_rest_kms_key_arn = "foo-bar-key" + } + + logging_info { + broker_logs { + cloudwatch_logs { + enabled = true + log_group = aws_cloudwatch_log_group.test.name + } + firehose { + enabled = true + delivery_stream = aws_kinesis_firehose_delivery_stream.test_stream.name + } + s3 { + enabled = true + bucket = aws_s3_bucket.bucket.id + prefix = "logs/msk-" + } + } + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Clusters, 1) + cluster := adapted.Clusters[0] + + assert.Equal(t, 2, cluster.Metadata.Range().GetStartLine()) + assert.Equal(t, 30, cluster.Metadata.Range().GetEndLine()) + + assert.Equal(t, 6, cluster.EncryptionInTransit.Metadata.Range().GetStartLine()) + assert.Equal(t, 9, cluster.EncryptionInTransit.Metadata.Range().GetEndLine()) + + assert.Equal(t, 10, cluster.EncryptionAtRest.Metadata.Range().GetStartLine()) + assert.Equal(t, 10, cluster.EncryptionAtRest.Metadata.Range().GetEndLine()) + + assert.Equal(t, 13, cluster.Logging.Metadata.Range().GetStartLine()) + assert.Equal(t, 29, cluster.Logging.Metadata.Range().GetEndLine()) + + assert.Equal(t, 14, cluster.Logging.Broker.Metadata.Range().GetStartLine()) + assert.Equal(t, 28, cluster.Logging.Broker.Metadata.Range().GetEndLine()) + + assert.Equal(t, 15, cluster.Logging.Broker.Cloudwatch.Metadata.Range().GetStartLine()) + assert.Equal(t, 18, cluster.Logging.Broker.Cloudwatch.Metadata.Range().GetEndLine()) + + assert.Equal(t, 16, cluster.Logging.Broker.Cloudwatch.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 16, cluster.Logging.Broker.Cloudwatch.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 19, cluster.Logging.Broker.Firehose.Metadata.Range().GetStartLine()) + assert.Equal(t, 22, cluster.Logging.Broker.Firehose.Metadata.Range().GetEndLine()) + + assert.Equal(t, 20, cluster.Logging.Broker.Firehose.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 20, cluster.Logging.Broker.Firehose.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 23, cluster.Logging.Broker.S3.Metadata.Range().GetStartLine()) + assert.Equal(t, 27, cluster.Logging.Broker.S3.Metadata.Range().GetEndLine()) + + assert.Equal(t, 24, cluster.Logging.Broker.S3.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 24, cluster.Logging.Broker.S3.Enabled.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/neptune/adapt.go b/pkg/iac/adapters/terraform/aws/neptune/adapt.go new file mode 100644 index 000000000000..2dc3e344b110 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/neptune/adapt.go @@ -0,0 +1,50 @@ +package neptune + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/neptune" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) neptune.Neptune { + return neptune.Neptune{ + Clusters: adaptClusters(modules), + } +} + +func adaptClusters(modules terraform.Modules) []neptune.Cluster { + var clusters []neptune.Cluster + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_neptune_cluster") { + clusters = append(clusters, adaptCluster(resource)) + } + } + return clusters +} + +func adaptCluster(resource *terraform.Block) neptune.Cluster { + cluster := neptune.Cluster{ + Metadata: resource.GetMetadata(), + Logging: neptune.Logging{ + Metadata: resource.GetMetadata(), + Audit: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + StorageEncrypted: iacTypes.BoolDefault(false, resource.GetMetadata()), + KMSKeyID: iacTypes.StringDefault("", resource.GetMetadata()), + } + + if enableLogExportsAttr := resource.GetAttribute("enable_cloudwatch_logs_exports"); enableLogExportsAttr.IsNotNil() { + cluster.Logging.Metadata = enableLogExportsAttr.GetMetadata() + if enableLogExportsAttr.Contains("audit") { + cluster.Logging.Audit = iacTypes.Bool(true, enableLogExportsAttr.GetMetadata()) + } + } + + storageEncryptedAttr := resource.GetAttribute("storage_encrypted") + cluster.StorageEncrypted = storageEncryptedAttr.AsBoolValueOrDefault(false, resource) + + KMSKeyAttr := resource.GetAttribute("kms_key_arn") + cluster.KMSKeyID = KMSKeyAttr.AsStringValueOrDefault("", resource) + + return cluster +} diff --git a/pkg/iac/adapters/terraform/aws/neptune/adapt_test.go b/pkg/iac/adapters/terraform/aws/neptune/adapt_test.go new file mode 100644 index 000000000000..5ad4f3de82ad --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/neptune/adapt_test.go @@ -0,0 +1,96 @@ +package neptune + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/neptune" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptCluster(t *testing.T) { + tests := []struct { + name string + terraform string + expected neptune.Cluster + }{ + { + name: "configured", + terraform: ` + resource "aws_neptune_cluster" "example" { + enable_cloudwatch_logs_exports = ["audit"] + storage_encrypted = true + kms_key_arn = "kms-key" + } +`, + expected: neptune.Cluster{ + Metadata: iacTypes.NewTestMetadata(), + Logging: neptune.Logging{ + Metadata: iacTypes.NewTestMetadata(), + Audit: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + StorageEncrypted: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("kms-key", iacTypes.NewTestMetadata()), + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_neptune_cluster" "example" { + } +`, + expected: neptune.Cluster{ + Metadata: iacTypes.NewTestMetadata(), + Logging: neptune.Logging{ + Metadata: iacTypes.NewTestMetadata(), + Audit: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + StorageEncrypted: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptCluster(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_neptune_cluster" "example" { + enable_cloudwatch_logs_exports = ["audit"] + storage_encrypted = true + kms_key_arn = "kms-key" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Clusters, 1) + cluster := adapted.Clusters[0] + + assert.Equal(t, 2, cluster.Metadata.Range().GetStartLine()) + assert.Equal(t, 6, cluster.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, cluster.Logging.Metadata.Range().GetStartLine()) + assert.Equal(t, 3, cluster.Logging.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, cluster.Logging.Audit.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, cluster.Logging.Audit.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 4, cluster.StorageEncrypted.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, cluster.StorageEncrypted.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 5, cluster.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 5, cluster.KMSKeyID.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/provider/adapt.go b/pkg/iac/adapters/terraform/aws/provider/adapt.go new file mode 100644 index 000000000000..2641a8299840 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/provider/adapt.go @@ -0,0 +1,166 @@ +package provider + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +const ( + defaultMaxRetires = 25 + defaultSharedConfigFile = "~/.aws/config" + //#nosec G101 -- False positive + defaultSharedCredentialsFile = "~/.aws/credentials" +) + +func Adapt(modules terraform.Modules) []aws.TerraformProvider { + return adaptProviders(modules) +} + +func adaptProviders(modules terraform.Modules) []aws.TerraformProvider { + var providers []aws.TerraformProvider + for _, providerBlock := range modules.GetBlocks().OfType("provider") { + if providerBlock.Label() == "aws" { + providers = append(providers, adaptProvider(providerBlock)) + } + } + + return providers +} + +func adaptProvider(b *terraform.Block) aws.TerraformProvider { + return aws.TerraformProvider{ + Metadata: b.GetMetadata(), + Alias: getStringAttrValue("alias", b), + Version: getStringAttrValue("version", b), + AccessKey: getStringAttrValue("access_key", b), + AllowedAccountsIDs: b.GetAttribute("allowed_account_ids").AsStringValueSliceOrEmpty(), + AssumeRole: adaptAssumeRole(b), + AssumeRoleWithWebIdentity: adaptAssumeRoleWithWebIdentity(b), + CustomCABundle: getStringAttrValue("custom_ca_bundle", b), + DefaultTags: adaptDefaultTags(b), + EC2MetadataServiceEndpoint: getStringAttrValue("ec2_metadata_service_endpoint", b), + EC2MetadataServiceEndpointMode: getStringAttrValue("ec2_metadata_service_endpoint_mode", b), + Endpoints: adaptEndpoints(b), + ForbiddenAccountIDs: b.GetAttribute("forbidden_account_ids").AsStringValueSliceOrEmpty(), + HttpProxy: getStringAttrValue("http_proxy", b), + IgnoreTags: adaptIgnoreTags(b), + Insecure: b.GetAttribute("insecure").AsBoolValueOrDefault(false, b), + MaxRetries: b.GetAttribute("max_retries").AsIntValueOrDefault(defaultMaxRetires, b), + Profile: getStringAttrValue("profile", b), + Region: getStringAttrValue("region", b), + RetryMode: getStringAttrValue("retry_mode", b), + S3UsePathStyle: b.GetAttribute("s3_use_path_style").AsBoolValueOrDefault(false, b), + S3USEast1RegionalEndpoint: getStringAttrValue("s3_us_east_1_regional_endpoint", b), + SecretKey: getStringAttrValue("secret_key", b), + SharedConfigFiles: b.GetAttribute("shared_config_files").AsStringValuesOrDefault(b, defaultSharedConfigFile), + SharedCredentialsFiles: b.GetAttribute("shared_credentials_files").AsStringValuesOrDefault(b, defaultSharedCredentialsFile), + SkipCredentialsValidation: b.GetAttribute("skip_credentials_validation").AsBoolValueOrDefault(false, b), + SkipMetadataAPICheck: b.GetAttribute("skip_metadata_api_check").AsBoolValueOrDefault(false, b), + SkipRegionValidation: b.GetAttribute("skip_region_validation").AsBoolValueOrDefault(false, b), + SkipRequestingAccountID: b.GetAttribute("skip_requesting_account_id").AsBoolValueOrDefault(false, b), + STSRegion: getStringAttrValue("sts_region", b), + Token: getStringAttrValue("token", b), + UseDualstackEndpoint: b.GetAttribute("use_dualstack_endpoint").AsBoolValueOrDefault(false, b), + UseFIPSEndpoint: b.GetAttribute("use_fips_endpoint").AsBoolValueOrDefault(false, b), + } +} + +func adaptAssumeRole(p *terraform.Block) aws.AssumeRole { + assumeRoleBlock := p.GetBlock("assume_role") + + if assumeRoleBlock.IsNil() { + return aws.AssumeRole{ + Metadata: p.GetMetadata(), + Duration: types.StringDefault("", p.GetMetadata()), + ExternalID: types.StringDefault("", p.GetMetadata()), + Policy: types.StringDefault("", p.GetMetadata()), + RoleARN: types.StringDefault("", p.GetMetadata()), + SessionName: types.StringDefault("", p.GetMetadata()), + SourceIdentity: types.StringDefault("", p.GetMetadata()), + } + } + + return aws.AssumeRole{ + Metadata: assumeRoleBlock.GetMetadata(), + Duration: getStringAttrValue("duration", p), + ExternalID: getStringAttrValue("external_id", p), + Policy: getStringAttrValue("policy", p), + PolicyARNs: p.GetAttribute("policy_arns").AsStringValueSliceOrEmpty(), + RoleARN: getStringAttrValue("role_arn", p), + SessionName: getStringAttrValue("session_name", p), + SourceIdentity: getStringAttrValue("source_identity", p), + Tags: p.GetAttribute("tags").AsMapValue(), + TransitiveTagKeys: p.GetAttribute("transitive_tag_keys").AsStringValueSliceOrEmpty(), + } +} + +func adaptAssumeRoleWithWebIdentity(p *terraform.Block) aws.AssumeRoleWithWebIdentity { + block := p.GetBlock("assume_role_with_web_identity") + if block.IsNil() { + return aws.AssumeRoleWithWebIdentity{ + Metadata: p.GetMetadata(), + Duration: types.StringDefault("", p.GetMetadata()), + Policy: types.StringDefault("", p.GetMetadata()), + RoleARN: types.StringDefault("", p.GetMetadata()), + SessionName: types.StringDefault("", p.GetMetadata()), + WebIdentityToken: types.StringDefault("", p.GetMetadata()), + WebIdentityTokenFile: types.StringDefault("", p.GetMetadata()), + } + } + + return aws.AssumeRoleWithWebIdentity{ + Metadata: block.GetMetadata(), + Duration: getStringAttrValue("duration", p), + Policy: getStringAttrValue("policy", p), + PolicyARNs: p.GetAttribute("policy_arns").AsStringValueSliceOrEmpty(), + RoleARN: getStringAttrValue("role_arn", p), + SessionName: getStringAttrValue("session_name", p), + WebIdentityToken: getStringAttrValue("web_identity_token", p), + WebIdentityTokenFile: getStringAttrValue("web_identity_token_file", p), + } +} + +func adaptEndpoints(p *terraform.Block) types.MapValue { + block := p.GetBlock("endpoints") + if block.IsNil() { + return types.MapDefault(make(map[string]string), p.GetMetadata()) + } + + values := make(map[string]string) + + for name, attr := range block.Attributes() { + values[name] = attr.AsStringValueOrDefault("", block).Value() + } + + return types.Map(values, block.GetMetadata()) +} + +func adaptDefaultTags(p *terraform.Block) aws.DefaultTags { + attr, _ := p.GetNestedAttribute("default_tags.tags") + if attr.IsNil() { + return aws.DefaultTags{} + } + + return aws.DefaultTags{ + Metadata: attr.GetMetadata(), + Tags: attr.AsMapValue(), + } +} + +func adaptIgnoreTags(p *terraform.Block) aws.IgnoreTags { + block := p.GetBlock("ignore_tags") + if block.IsNil() { + return aws.IgnoreTags{} + } + + return aws.IgnoreTags{ + Metadata: block.GetMetadata(), + Keys: block.GetAttribute("keys").AsStringValueSliceOrEmpty(), + KeyPrefixes: block.GetAttribute("key_prefixes").AsStringValueSliceOrEmpty(), + } +} + +func getStringAttrValue(name string, parent *terraform.Block) types.StringValue { + return parent.GetAttribute(name).AsStringValueOrDefault("", parent) +} diff --git a/pkg/iac/adapters/terraform/aws/provider/adapt_test.go b/pkg/iac/adapters/terraform/aws/provider/adapt_test.go new file mode 100644 index 000000000000..9cbcc767e3b3 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/provider/adapt_test.go @@ -0,0 +1,128 @@ +package provider + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected []aws.TerraformProvider + }{ + { + name: "happy", + source: ` +variable "s3_use_path_style" { + default = true +} + +provider "aws" { + version = "~> 5.0" + region = "us-east-1" + profile = "localstack" + + access_key = "fake" + secret_key = "fake" + skip_credentials_validation = true + skip_metadata_api_check = true + skip_requesting_account_id = true + s3_use_path_style = var.s3_use_path_style + + endpoints { + dynamodb = "http://localhost:4566" + s3 = "http://localhost:4566" + } + + default_tags { + tags = { + Environment = "Local" + Name = "LocalStack" + } + } +}`, + expected: []aws.TerraformProvider{ + { + Version: types.String("~> 5.0", types.NewTestMetadata()), + Region: types.String("us-east-1", types.NewTestMetadata()), + DefaultTags: aws.DefaultTags{ + Metadata: types.NewTestMetadata(), + Tags: types.Map(map[string]string{ + "Environment": "Local", + "Name": "LocalStack", + }, types.NewTestMetadata()), + }, + Endpoints: types.Map(map[string]string{ + "dynamodb": "http://localhost:4566", + "s3": "http://localhost:4566", + }, types.NewTestMetadata()), + Profile: types.String("localstack", types.NewTestMetadata()), + AccessKey: types.String("fake", types.NewTestMetadata()), + SecretKey: types.String("fake", types.NewTestMetadata()), + SkipCredentialsValidation: types.Bool(true, types.NewTestMetadata()), + SkipMetadataAPICheck: types.Bool(true, types.NewTestMetadata()), + SkipRequestingAccountID: types.Bool(true, types.NewTestMetadata()), + S3UsePathStyle: types.Bool(true, types.NewTestMetadata()), + MaxRetries: types.IntDefault(defaultMaxRetires, types.NewTestMetadata()), + SharedConfigFiles: types.StringValueList{ + types.StringDefault(defaultSharedConfigFile, types.NewTestMetadata()), + }, + SharedCredentialsFiles: types.StringValueList{ + types.StringDefault(defaultSharedCredentialsFile, types.NewTestMetadata()), + }, + }, + }, + }, + { + name: "multiply provider configurations", + source: ` + +provider "aws" { + region = "us-east-1" +} + +provider "aws" { + alias = "west" + region = "us-west-2" +} +`, + expected: []aws.TerraformProvider{ + { + Region: types.String("us-east-1", types.NewTestMetadata()), + Endpoints: types.Map(make(map[string]string), types.NewTestMetadata()), + MaxRetries: types.IntDefault(defaultMaxRetires, types.NewTestMetadata()), + SharedConfigFiles: types.StringValueList{ + types.StringDefault(defaultSharedConfigFile, types.NewTestMetadata()), + }, + SharedCredentialsFiles: types.StringValueList{ + types.StringDefault(defaultSharedCredentialsFile, types.NewTestMetadata()), + }, + }, + { + Alias: types.String("west", types.NewTestMetadata()), + Region: types.String("us-west-2", types.NewTestMetadata()), + Endpoints: types.Map(make(map[string]string), types.NewTestMetadata()), + MaxRetries: types.IntDefault(defaultMaxRetires, types.NewTestMetadata()), + SharedConfigFiles: types.StringValueList{ + types.StringDefault(defaultSharedConfigFile, types.NewTestMetadata()), + }, + SharedCredentialsFiles: types.StringValueList{ + types.StringDefault(defaultSharedCredentialsFile, types.NewTestMetadata()), + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.source, ".tf") + testutil.AssertDefsecEqual(t, test.expected, Adapt(modules)) + }) + } +} diff --git a/pkg/iac/adapters/terraform/aws/rds/adapt.go b/pkg/iac/adapters/terraform/aws/rds/adapt.go new file mode 100644 index 000000000000..d99821c23950 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/rds/adapt.go @@ -0,0 +1,256 @@ +package rds + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) rds.RDS { + return rds.RDS{ + Instances: getInstances(modules), + Clusters: getClusters(modules), + Classic: getClassic(modules), + Snapshots: getSnapshots(modules), + ParameterGroups: getParameterGroups(modules), + } +} + +func getInstances(modules terraform.Modules) (instances []rds.Instance) { + for _, resource := range modules.GetResourcesByType("aws_db_instance") { + instances = append(instances, adaptInstance(resource, modules)) + } + + return instances +} + +func getParameterGroups(modules terraform.Modules) (parametergroups []rds.ParameterGroups) { + for _, resource := range modules.GetResourcesByType("aws_db_parameter_group") { + parametergroups = append(parametergroups, adaptDBParameterGroups(resource, modules)) + } + + return parametergroups +} + +func getSnapshots(modules terraform.Modules) (snapshots []rds.Snapshots) { + for _, resource := range modules.GetResourcesByType("aws_db_snapshot") { + snapshots = append(snapshots, adaptDBSnapshots(resource, modules)) + } + + return snapshots +} + +func getClusters(modules terraform.Modules) (clusters []rds.Cluster) { + + rdsInstanceMaps := modules.GetChildResourceIDMapByType("aws_rds_cluster_instance") + for _, resource := range modules.GetResourcesByType("aws_rds_cluster") { + cluster, instanceIDs := adaptCluster(resource, modules) + for _, id := range instanceIDs { + rdsInstanceMaps.Resolve(id) + } + clusters = append(clusters, cluster) + } + + orphanResources := modules.GetResourceByIDs(rdsInstanceMaps.Orphans()...) + + if len(orphanResources) > 0 { + orphanage := rds.Cluster{ + Metadata: iacTypes.NewUnmanagedMetadata(), + BackupRetentionPeriodDays: iacTypes.IntDefault(1, iacTypes.NewUnmanagedMetadata()), + ReplicationSourceARN: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + PerformanceInsights: rds.PerformanceInsights{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Enabled: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + KMSKeyID: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + }, + Instances: nil, + Encryption: rds.Encryption{ + Metadata: iacTypes.NewUnmanagedMetadata(), + EncryptStorage: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + KMSKeyID: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + }, + PublicAccess: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + Engine: iacTypes.StringUnresolvable(iacTypes.NewUnmanagedMetadata()), + LatestRestorableTime: iacTypes.TimeUnresolvable(iacTypes.NewUnmanagedMetadata()), + DeletionProtection: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + } + for _, orphan := range orphanResources { + orphanage.Instances = append(orphanage.Instances, adaptClusterInstance(orphan, modules)) + } + clusters = append(clusters, orphanage) + } + + return clusters +} + +func getClassic(modules terraform.Modules) rds.Classic { + classic := rds.Classic{ + DBSecurityGroups: nil, + } + for _, resource := range modules.GetResourcesByType("aws_db_security_group", "aws_redshift_security_group", "aws_elasticache_security_group") { + classic.DBSecurityGroups = append(classic.DBSecurityGroups, adaptClassicDBSecurityGroup(resource)) + } + return classic +} + +func adaptClusterInstance(resource *terraform.Block, modules terraform.Modules) rds.ClusterInstance { + clusterIdAttr := resource.GetAttribute("cluster_identifier") + clusterId := clusterIdAttr.AsStringValueOrDefault("", resource) + + if clusterIdAttr.IsResourceBlockReference("aws_rds_cluster") { + if referenced, err := modules.GetReferencedBlock(clusterIdAttr, resource); err == nil { + clusterId = iacTypes.String(referenced.FullName(), referenced.GetMetadata()) + } + } + + return rds.ClusterInstance{ + ClusterIdentifier: clusterId, + Instance: adaptInstance(resource, modules), + } +} + +func adaptClassicDBSecurityGroup(resource *terraform.Block) rds.DBSecurityGroup { + return rds.DBSecurityGroup{ + Metadata: resource.GetMetadata(), + } +} + +func adaptInstance(resource *terraform.Block, modules terraform.Modules) rds.Instance { + + var ReadReplicaDBInstanceIdentifiers []iacTypes.StringValue + rrdiAttr := resource.GetAttribute("replicate_source_db") + for _, rrdi := range rrdiAttr.AsStringValues() { + ReadReplicaDBInstanceIdentifiers = append(ReadReplicaDBInstanceIdentifiers, rrdi) + } + + var TagList []rds.TagList + tagres := resource.GetBlocks("tags") + for _, tagres := range tagres { + + TagList = append(TagList, rds.TagList{ + Metadata: tagres.GetMetadata(), + }) + } + + var EnabledCloudwatchLogsExports []iacTypes.StringValue + ecweAttr := resource.GetAttribute("enabled_cloudwatch_logs_exports") + for _, ecwe := range ecweAttr.AsStringValues() { + EnabledCloudwatchLogsExports = append(EnabledCloudwatchLogsExports, ecwe) + } + + replicaSource := resource.GetAttribute("replicate_source_db") + replicaSourceValue := "" + if replicaSource.IsNotNil() { + if referenced, err := modules.GetReferencedBlock(replicaSource, resource); err == nil { + replicaSourceValue = referenced.ID() + } + } + return rds.Instance{ + Metadata: resource.GetMetadata(), + BackupRetentionPeriodDays: resource.GetAttribute("backup_retention_period").AsIntValueOrDefault(0, resource), + ReplicationSourceARN: iacTypes.StringExplicit(replicaSourceValue, resource.GetMetadata()), + PerformanceInsights: adaptPerformanceInsights(resource), + Encryption: adaptEncryption(resource), + PublicAccess: resource.GetAttribute("publicly_accessible").AsBoolValueOrDefault(false, resource), + Engine: resource.GetAttribute("engine").AsStringValueOrDefault(rds.EngineAurora, resource), + IAMAuthEnabled: resource.GetAttribute("iam_database_authentication_enabled").AsBoolValueOrDefault(false, resource), + DeletionProtection: resource.GetAttribute("deletion_protection").AsBoolValueOrDefault(false, resource), + DBInstanceArn: resource.GetAttribute("arn").AsStringValueOrDefault("", resource), + StorageEncrypted: resource.GetAttribute("storage_encrypted").AsBoolValueOrDefault(true, resource), + DBInstanceIdentifier: resource.GetAttribute("identifier").AsStringValueOrDefault("", resource), + EngineVersion: resource.GetAttribute("engine_version").AsStringValueOrDefault("", resource), + AutoMinorVersionUpgrade: resource.GetAttribute("auto_minor_version_upgrade").AsBoolValueOrDefault(false, resource), + MultiAZ: resource.GetAttribute("multi_az").AsBoolValueOrDefault(false, resource), + PubliclyAccessible: resource.GetAttribute("publicly_accessible").AsBoolValueOrDefault(false, resource), + LatestRestorableTime: iacTypes.TimeUnresolvable(resource.GetMetadata()), + ReadReplicaDBInstanceIdentifiers: ReadReplicaDBInstanceIdentifiers, + TagList: TagList, + EnabledCloudwatchLogsExports: EnabledCloudwatchLogsExports, + } +} + +func adaptDBParameterGroups(resource *terraform.Block, modules terraform.Modules) rds.ParameterGroups { + + var Parameters []rds.Parameters + paramres := resource.GetBlocks("parameter") + for _, paramres := range paramres { + + Parameters = append(Parameters, rds.Parameters{ + Metadata: paramres.GetMetadata(), + ParameterName: iacTypes.StringDefault("", paramres.GetMetadata()), + ParameterValue: iacTypes.StringDefault("", paramres.GetMetadata()), + }) + } + + return rds.ParameterGroups{ + Metadata: resource.GetMetadata(), + DBParameterGroupName: resource.GetAttribute("name").AsStringValueOrDefault("", resource), + DBParameterGroupFamily: resource.GetAttribute("family").AsStringValueOrDefault("", resource), + Parameters: Parameters, + } +} + +func adaptDBSnapshots(resource *terraform.Block, modules terraform.Modules) rds.Snapshots { + + return rds.Snapshots{ + Metadata: resource.GetMetadata(), + DBSnapshotIdentifier: resource.GetAttribute("db_snapshot_identifier").AsStringValueOrDefault("", resource), + DBSnapshotArn: resource.GetAttribute("db_snapshot_arn").AsStringValueOrDefault("", resource), + Encrypted: resource.GetAttribute("encrypted").AsBoolValueOrDefault(true, resource), + KmsKeyId: resource.GetAttribute("kms_key_id").AsStringValueOrDefault("", resource), + SnapshotAttributes: nil, + } +} + +func adaptCluster(resource *terraform.Block, modules terraform.Modules) (rds.Cluster, []string) { + + clusterInstances, ids := getClusterInstances(resource, modules) + + var public bool + for _, instance := range clusterInstances { + if instance.PublicAccess.IsTrue() { + public = true + break + } + } + + return rds.Cluster{ + Metadata: resource.GetMetadata(), + BackupRetentionPeriodDays: resource.GetAttribute("backup_retention_period").AsIntValueOrDefault(1, resource), + ReplicationSourceARN: resource.GetAttribute("replication_source_identifier").AsStringValueOrDefault("", resource), + PerformanceInsights: adaptPerformanceInsights(resource), + Instances: clusterInstances, + Encryption: adaptEncryption(resource), + PublicAccess: iacTypes.Bool(public, resource.GetMetadata()), + Engine: resource.GetAttribute("engine").AsStringValueOrDefault(rds.EngineAurora, resource), + LatestRestorableTime: iacTypes.TimeUnresolvable(resource.GetMetadata()), + AvailabilityZones: resource.GetAttribute("availability_zones").AsStringValueSliceOrEmpty(), + DeletionProtection: resource.GetAttribute("deletion_protection").AsBoolValueOrDefault(false, resource), + }, ids +} + +func getClusterInstances(resource *terraform.Block, modules terraform.Modules) (clusterInstances []rds.ClusterInstance, instanceIDs []string) { + clusterInstanceResources := modules.GetReferencingResources(resource, "aws_rds_cluster_instance", "cluster_identifier") + + for _, ciResource := range clusterInstanceResources { + instanceIDs = append(instanceIDs, ciResource.ID()) + clusterInstances = append(clusterInstances, adaptClusterInstance(ciResource, modules)) + } + return clusterInstances, instanceIDs +} + +func adaptPerformanceInsights(resource *terraform.Block) rds.PerformanceInsights { + return rds.PerformanceInsights{ + Metadata: resource.GetMetadata(), + Enabled: resource.GetAttribute("performance_insights_enabled").AsBoolValueOrDefault(false, resource), + KMSKeyID: resource.GetAttribute("performance_insights_kms_key_id").AsStringValueOrDefault("", resource), + } +} + +func adaptEncryption(resource *terraform.Block) rds.Encryption { + return rds.Encryption{ + Metadata: resource.GetMetadata(), + EncryptStorage: resource.GetAttribute("storage_encrypted").AsBoolValueOrDefault(false, resource), + KMSKeyID: resource.GetAttribute("kms_key_id").AsStringValueOrDefault("", resource), + } +} diff --git a/pkg/iac/adapters/terraform/aws/rds/adapt_test.go b/pkg/iac/adapters/terraform/aws/rds/adapt_test.go new file mode 100644 index 000000000000..3776e4c4ad48 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/rds/adapt_test.go @@ -0,0 +1,331 @@ +package rds + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected rds.RDS + }{ + { + name: "defined", + terraform: ` + + resource "aws_rds_cluster" "example" { + engine = "aurora-mysql" + availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] + backup_retention_period = 7 + kms_key_id = "kms_key_1" + storage_encrypted = true + replication_source_identifier = "arn-of-a-source-db-cluster" + deletion_protection = true + } + + resource "aws_rds_cluster_instance" "example" { + cluster_identifier = aws_rds_cluster.example.id + name = "bar" + performance_insights_enabled = true + performance_insights_kms_key_id = "performance_key_0" + kms_key_id = "kms_key_0" + storage_encrypted = true + } + + resource "aws_db_security_group" "example" { + # ... + } + + resource "aws_db_instance" "example" { + publicly_accessible = false + backup_retention_period = 5 + skip_final_snapshot = true + performance_insights_enabled = true + performance_insights_kms_key_id = "performance_key_1" + storage_encrypted = true + kms_key_id = "kms_key_2" + } +`, + expected: rds.RDS{ + Instances: []rds.Instance{ + { + Metadata: iacTypes.NewTestMetadata(), + BackupRetentionPeriodDays: iacTypes.Int(5, iacTypes.NewTestMetadata()), + ReplicationSourceARN: iacTypes.String("", iacTypes.NewTestMetadata()), + PerformanceInsights: rds.PerformanceInsights{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("performance_key_1", iacTypes.NewTestMetadata()), + }, + Encryption: rds.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + EncryptStorage: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("kms_key_2", iacTypes.NewTestMetadata()), + }, + PublicAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Engine: iacTypes.String(rds.EngineAurora, iacTypes.NewTestMetadata()), + StorageEncrypted: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + Clusters: []rds.Cluster{ + { + Metadata: iacTypes.NewTestMetadata(), + BackupRetentionPeriodDays: iacTypes.Int(7, iacTypes.NewTestMetadata()), + ReplicationSourceARN: iacTypes.String("arn-of-a-source-db-cluster", iacTypes.NewTestMetadata()), + PerformanceInsights: rds.PerformanceInsights{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + Encryption: rds.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + EncryptStorage: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("kms_key_1", iacTypes.NewTestMetadata()), + }, + Instances: []rds.ClusterInstance{ + { + Instance: rds.Instance{ + Metadata: iacTypes.NewTestMetadata(), + BackupRetentionPeriodDays: iacTypes.Int(0, iacTypes.NewTestMetadata()), + ReplicationSourceARN: iacTypes.String("", iacTypes.NewTestMetadata()), + PerformanceInsights: rds.PerformanceInsights{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("performance_key_0", iacTypes.NewTestMetadata()), + }, + Encryption: rds.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + EncryptStorage: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("kms_key_0", iacTypes.NewTestMetadata()), + }, + PublicAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Engine: iacTypes.String(rds.EngineAurora, iacTypes.NewTestMetadata()), + StorageEncrypted: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + ClusterIdentifier: iacTypes.String("aws_rds_cluster.example", iacTypes.NewTestMetadata()), + }, + }, + PublicAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Engine: iacTypes.String(rds.EngineAuroraMysql, iacTypes.NewTestMetadata()), + AvailabilityZones: iacTypes.StringValueList{ + iacTypes.String("us-west-2a", iacTypes.NewTestMetadata()), + iacTypes.String("us-west-2b", iacTypes.NewTestMetadata()), + iacTypes.String("us-west-2c", iacTypes.NewTestMetadata()), + }, + DeletionProtection: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + Classic: rds.Classic{ + DBSecurityGroups: []rds.DBSecurityGroup{ + { + Metadata: iacTypes.NewTestMetadata(), + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptInstance(t *testing.T) { + tests := []struct { + name string + terraform string + expected rds.Instance + }{ + { + name: "instance defaults", + terraform: ` + resource "aws_db_instance" "example" { + } +`, + expected: rds.Instance{ + Metadata: iacTypes.NewTestMetadata(), + BackupRetentionPeriodDays: iacTypes.Int(0, iacTypes.NewTestMetadata()), + ReplicationSourceARN: iacTypes.String("", iacTypes.NewTestMetadata()), + PerformanceInsights: rds.PerformanceInsights{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + Encryption: rds.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + EncryptStorage: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + PublicAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Engine: iacTypes.String(rds.EngineAurora, iacTypes.NewTestMetadata()), + StorageEncrypted: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + IAMAuthEnabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptInstance(modules.GetBlocks()[0], modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptCluster(t *testing.T) { + tests := []struct { + name string + terraform string + expected rds.Cluster + }{ + { + name: "cluster defaults", + terraform: ` + resource "aws_rds_cluster" "example" { + } +`, + expected: rds.Cluster{ + Metadata: iacTypes.NewTestMetadata(), + BackupRetentionPeriodDays: iacTypes.Int(1, iacTypes.NewTestMetadata()), + ReplicationSourceARN: iacTypes.String("", iacTypes.NewTestMetadata()), + PerformanceInsights: rds.PerformanceInsights{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + Encryption: rds.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + EncryptStorage: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + PublicAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Engine: iacTypes.String(rds.EngineAurora, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted, _ := adaptCluster(modules.GetBlocks()[0], modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_rds_cluster" "example" { + backup_retention_period = 7 + kms_key_id = "kms_key_1" + storage_encrypted = true + replication_source_identifier = "arn-of-a-source-db-cluster" + } + + resource "aws_rds_cluster_instance" "example" { + cluster_identifier = aws_rds_cluster.example.id + backup_retention_period = 7 + performance_insights_enabled = true + performance_insights_kms_key_id = "performance_key" + storage_encrypted = true + kms_key_id = "kms_key_0" + } + + resource "aws_db_security_group" "example" { + } + + resource "aws_db_instance" "example" { + publicly_accessible = false + backup_retention_period = 7 + performance_insights_enabled = true + performance_insights_kms_key_id = "performance_key" + storage_encrypted = true + kms_key_id = "kms_key_0" + } +` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Clusters, 1) + require.Len(t, adapted.Instances, 1) + + cluster := adapted.Clusters[0] + instance := adapted.Instances[0] + classic := adapted.Classic + + assert.Equal(t, 2, cluster.Metadata.Range().GetStartLine()) + assert.Equal(t, 7, cluster.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, cluster.BackupRetentionPeriodDays.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, cluster.BackupRetentionPeriodDays.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 4, cluster.Encryption.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, cluster.Encryption.KMSKeyID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 5, cluster.Encryption.EncryptStorage.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 5, cluster.Encryption.EncryptStorage.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 6, cluster.ReplicationSourceARN.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, cluster.ReplicationSourceARN.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 9, cluster.Instances[0].Instance.Metadata.Range().GetStartLine()) + assert.Equal(t, 16, cluster.Instances[0].Instance.Metadata.Range().GetEndLine()) + + assert.Equal(t, 2, cluster.Instances[0].ClusterIdentifier.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, cluster.Instances[0].ClusterIdentifier.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, cluster.Instances[0].Instance.BackupRetentionPeriodDays.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 11, cluster.Instances[0].Instance.BackupRetentionPeriodDays.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 12, cluster.Instances[0].Instance.PerformanceInsights.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 12, cluster.Instances[0].Instance.PerformanceInsights.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 13, cluster.Instances[0].Instance.PerformanceInsights.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 13, cluster.Instances[0].Instance.PerformanceInsights.KMSKeyID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 14, cluster.Instances[0].Instance.Encryption.EncryptStorage.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 14, cluster.Instances[0].Instance.Encryption.EncryptStorage.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 15, cluster.Instances[0].Instance.Encryption.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 15, cluster.Instances[0].Instance.Encryption.KMSKeyID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 18, classic.DBSecurityGroups[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 19, classic.DBSecurityGroups[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 21, instance.Metadata.Range().GetStartLine()) + assert.Equal(t, 28, instance.Metadata.Range().GetEndLine()) + + assert.Equal(t, 22, instance.PublicAccess.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 22, instance.PublicAccess.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 23, instance.BackupRetentionPeriodDays.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 23, instance.BackupRetentionPeriodDays.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 24, instance.PerformanceInsights.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 24, instance.PerformanceInsights.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 25, instance.PerformanceInsights.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 25, instance.PerformanceInsights.KMSKeyID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 26, instance.Encryption.EncryptStorage.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 26, instance.Encryption.EncryptStorage.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 27, instance.Encryption.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 27, instance.Encryption.KMSKeyID.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/redshift/adapt.go b/pkg/iac/adapters/terraform/aws/redshift/adapt.go new file mode 100644 index 000000000000..37ede1a821ab --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/redshift/adapt.go @@ -0,0 +1,117 @@ +package redshift + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/redshift" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) redshift.Redshift { + return redshift.Redshift{ + Clusters: adaptClusters(modules), + SecurityGroups: adaptSecurityGroups(modules), + ClusterParameters: adaptParameters(modules), + ReservedNodes: nil, + } +} + +func adaptClusters(modules terraform.Modules) []redshift.Cluster { + var clusters []redshift.Cluster + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_redshift_cluster") { + clusters = append(clusters, adaptCluster(resource, module)) + } + } + return clusters +} + +func adaptSecurityGroups(modules terraform.Modules) []redshift.SecurityGroup { + var securityGroups []redshift.SecurityGroup + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_redshift_security_group") { + securityGroups = append(securityGroups, adaptSecurityGroup(resource)) + } + } + return securityGroups +} + +func adaptParameters(modules terraform.Modules) []redshift.ClusterParameter { + var Parameters []redshift.ClusterParameter + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_redshift_parameter_group") { + for _, r := range resource.GetBlocks("parameter") { + Parameters = append(Parameters, adaptParameter(r)) + } + } + } + return Parameters +} + +func adaptCluster(resource *terraform.Block, module *terraform.Module) redshift.Cluster { + cluster := redshift.Cluster{ + Metadata: resource.GetMetadata(), + ClusterIdentifier: resource.GetAttribute("cluster_identifier").AsStringValueOrDefault("", resource), + NodeType: resource.GetAttribute("node_type").AsStringValueOrDefault("", resource), + MasterUsername: resource.GetAttribute("master_username").AsStringValueOrDefault("", resource), + NumberOfNodes: resource.GetAttribute("number_of_nodes").AsIntValueOrDefault(1, resource), + PubliclyAccessible: resource.GetAttribute("publicly_accessible").AsBoolValueOrDefault(true, resource), + LoggingEnabled: iacTypes.Bool(false, resource.GetMetadata()), + AutomatedSnapshotRetentionPeriod: iacTypes.Int(0, resource.GetMetadata()), + AllowVersionUpgrade: resource.GetAttribute("allow_version_upgrade").AsBoolValueOrDefault(true, resource), + VpcId: iacTypes.String("", resource.GetMetadata()), + Encryption: redshift.Encryption{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + KMSKeyID: iacTypes.StringDefault("", resource.GetMetadata()), + }, + EndPoint: redshift.EndPoint{ + Metadata: resource.GetMetadata(), + Port: resource.GetAttribute("port").AsIntValueOrDefault(5439, resource), + }, + SubnetGroupName: iacTypes.StringDefault("", resource.GetMetadata()), + } + + encryptedAttr := resource.GetAttribute("encrypted") + cluster.Encryption.Enabled = encryptedAttr.AsBoolValueOrDefault(false, resource) + + if logBlock := resource.GetBlock("logging"); logBlock.IsNotNil() { + cluster.LoggingEnabled = logBlock.GetAttribute("enable").AsBoolValueOrDefault(false, logBlock) + } + + if snapBlock := resource.GetBlock("snapshot_copy"); snapBlock.IsNotNil() { + snapAttr := snapBlock.GetAttribute("retention_period") + cluster.AutomatedSnapshotRetentionPeriod = snapAttr.AsIntValueOrDefault(7, snapBlock) + } + + KMSKeyIDAttr := resource.GetAttribute("kms_key_id") + cluster.Encryption.KMSKeyID = KMSKeyIDAttr.AsStringValueOrDefault("", resource) + if KMSKeyIDAttr.IsResourceBlockReference("aws_kms_key") { + if kmsKeyBlock, err := module.GetReferencedBlock(KMSKeyIDAttr, resource); err == nil { + cluster.Encryption.KMSKeyID = iacTypes.String(kmsKeyBlock.FullName(), kmsKeyBlock.GetMetadata()) + } + } + + subnetGroupNameAttr := resource.GetAttribute("cluster_subnet_group_name") + cluster.SubnetGroupName = subnetGroupNameAttr.AsStringValueOrDefault("", resource) + + return cluster +} + +func adaptSecurityGroup(resource *terraform.Block) redshift.SecurityGroup { + descriptionAttr := resource.GetAttribute("description") + descriptionVal := descriptionAttr.AsStringValueOrDefault("Managed by Terraform", resource) + + return redshift.SecurityGroup{ + Metadata: resource.GetMetadata(), + Description: descriptionVal, + } +} + +func adaptParameter(resource *terraform.Block) redshift.ClusterParameter { + + return redshift.ClusterParameter{ + Metadata: resource.GetMetadata(), + ParameterName: resource.GetAttribute("name").AsStringValueOrDefault("", resource), + ParameterValue: resource.GetAttribute("value").AsStringValueOrDefault("", resource), + } +} diff --git a/pkg/iac/adapters/terraform/aws/redshift/adapt_test.go b/pkg/iac/adapters/terraform/aws/redshift/adapt_test.go new file mode 100644 index 000000000000..e52db1c2256b --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/redshift/adapt_test.go @@ -0,0 +1,229 @@ +package redshift + +import ( + "fmt" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/redshift" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected redshift.Redshift + }{ + { + name: "reference key id", + terraform: ` + resource "aws_kms_key" "redshift" { + enable_key_rotation = true + } + + resource "aws_redshift_cluster" "example" { + cluster_identifier = "tf-redshift-cluster" + publicly_accessible = false + number_of_nodes = 1 + allow_version_upgrade = false + port = 5440 + encrypted = true + kms_key_id = aws_kms_key.redshift.key_id + cluster_subnet_group_name = "redshift_subnet" + } + + resource "aws_redshift_security_group" "default" { + name = "redshift-sg" + description = "some description" + } +`, + expected: redshift.Redshift{ + Clusters: []redshift.Cluster{ + { + Metadata: iacTypes.NewTestMetadata(), + ClusterIdentifier: iacTypes.String("tf-redshift-cluster", iacTypes.NewTestMetadata()), + PubliclyAccessible: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + NumberOfNodes: iacTypes.Int(1, iacTypes.NewTestMetadata()), + AllowVersionUpgrade: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + EndPoint: redshift.EndPoint{ + Metadata: iacTypes.NewTestMetadata(), + Port: iacTypes.Int(5440, iacTypes.NewTestMetadata()), + }, + Encryption: redshift.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("aws_kms_key.redshift", iacTypes.NewTestMetadata()), + }, + SubnetGroupName: iacTypes.String("redshift_subnet", iacTypes.NewTestMetadata()), + }, + }, + SecurityGroups: []redshift.SecurityGroup{ + { + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("some description", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + fmt.Println(adapted.SecurityGroups[0].Description.Value()) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptCluster(t *testing.T) { + tests := []struct { + name string + terraform string + expected redshift.Cluster + }{ + { + name: "key as string", + terraform: ` + resource "aws_redshift_cluster" "example" { + cluster_identifier = "tf-redshift-cluster" + publicly_accessible = false + number_of_nodes = 1 + allow_version_upgrade = false + port = 5440 + encrypted = true + kms_key_id = "key-id" + cluster_subnet_group_name = "redshift_subnet" + } +`, + expected: redshift.Cluster{ + Metadata: iacTypes.NewTestMetadata(), + ClusterIdentifier: iacTypes.String("tf-redshift-cluster", iacTypes.NewTestMetadata()), + PubliclyAccessible: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + NumberOfNodes: iacTypes.Int(1, iacTypes.NewTestMetadata()), + AllowVersionUpgrade: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + EndPoint: redshift.EndPoint{ + Metadata: iacTypes.NewTestMetadata(), + Port: iacTypes.Int(5440, iacTypes.NewTestMetadata()), + }, + Encryption: redshift.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("key-id", iacTypes.NewTestMetadata()), + }, + SubnetGroupName: iacTypes.String("redshift_subnet", iacTypes.NewTestMetadata()), + }, + }, + { + name: "defaults", + terraform: ` + resource "aws_redshift_cluster" "example" { + } +`, + expected: redshift.Cluster{ + Metadata: iacTypes.NewTestMetadata(), + ClusterIdentifier: iacTypes.String("", iacTypes.NewTestMetadata()), + PubliclyAccessible: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + NumberOfNodes: iacTypes.Int(1, iacTypes.NewTestMetadata()), + AllowVersionUpgrade: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + EndPoint: redshift.EndPoint{ + Metadata: iacTypes.NewTestMetadata(), + Port: iacTypes.Int(5439, iacTypes.NewTestMetadata()), + }, + Encryption: redshift.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + SubnetGroupName: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptCluster(modules.GetBlocks()[0], modules[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptSecurityGroup(t *testing.T) { + tests := []struct { + name string + terraform string + expected redshift.SecurityGroup + }{ + { + name: "defaults", + terraform: ` +resource "" "example" { +} +`, + expected: redshift.SecurityGroup{ + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("Managed by Terraform", iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptSecurityGroup(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_kms_key" "redshift" { + enable_key_rotation = true + } + + resource "aws_redshift_cluster" "example" { + cluster_identifier = "tf-redshift-cluster" + encrypted = true + kms_key_id = aws_kms_key.redshift.key_id + cluster_subnet_group_name = "subnet name" + } + + resource "aws_redshift_security_group" "default" { + name = "redshift-sg" + description = "some description" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Clusters, 1) + require.Len(t, adapted.SecurityGroups, 1) + cluster := adapted.Clusters[0] + securityGroup := adapted.SecurityGroups[0] + + assert.Equal(t, 6, cluster.Metadata.Range().GetStartLine()) + assert.Equal(t, 11, cluster.Metadata.Range().GetEndLine()) + + assert.Equal(t, 8, cluster.Encryption.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 8, cluster.Encryption.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 2, cluster.Encryption.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, cluster.Encryption.KMSKeyID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 10, cluster.SubnetGroupName.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 10, cluster.SubnetGroupName.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 13, securityGroup.Metadata.Range().GetStartLine()) + assert.Equal(t, 16, securityGroup.Metadata.Range().GetEndLine()) + + assert.Equal(t, 15, securityGroup.Description.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 15, securityGroup.Description.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/s3/adapt.go b/pkg/iac/adapters/terraform/aws/s3/adapt.go new file mode 100644 index 000000000000..ef9c3052eec5 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/s3/adapt.go @@ -0,0 +1,18 @@ +package s3 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) s3.S3 { + + a := &adapter{ + modules: modules, + bucketMap: make(map[string]*s3.Bucket), + } + + return s3.S3{ + Buckets: a.adaptBuckets(), + } +} diff --git a/pkg/iac/adapters/terraform/aws/s3/adapt_test.go b/pkg/iac/adapters/terraform/aws/s3/adapt_test.go new file mode 100644 index 000000000000..65394abd3ea7 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/s3/adapt_test.go @@ -0,0 +1,412 @@ +package s3 + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" + "github.com/liamg/iamgo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_PublicAccessBlock(t *testing.T) { + testCases := []struct { + desc string + source string + expectedBuckets int + hasPublicAccess bool + }{ + { + desc: "public access block is found when using the bucket name as the lookup", + source: ` +resource "aws_s3_bucket" "example" { + bucket = "bucketname" +} + +resource "aws_s3_bucket_public_access_block" "example_access_block"{ + bucket = "bucketname" +} +`, + expectedBuckets: 1, + hasPublicAccess: true, + }, + { + desc: "public access block is found when using the bucket id as the lookup", + source: ` +resource "aws_s3_bucket" "example" { + bucket = "bucketname" +} + +resource "aws_s3_bucket_public_access_block" "example_access_block"{ + bucket = aws_s3_bucket.example.id +} +`, + expectedBuckets: 1, + hasPublicAccess: true, + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + + modules := tftestutil.CreateModulesFromSource(t, tC.source, ".tf") + s3Ctx := Adapt(modules) + + assert.Equal(t, tC.expectedBuckets, len(s3Ctx.Buckets)) + + for _, bucket := range s3Ctx.Buckets { + if tC.hasPublicAccess { + assert.NotNil(t, bucket.PublicAccessBlock) + } else { + assert.Nil(t, bucket.PublicAccessBlock) + } + } + + bucket := s3Ctx.Buckets[0] + assert.NotNil(t, bucket.PublicAccessBlock) + + }) + } + +} + +func Test_PublicAccessDoesNotReference(t *testing.T) { + testCases := []struct { + desc string + source string + }{ + { + desc: "just a bucket, no public access block", + source: ` +resource "aws_s3_bucket" "example" { + bucket = "bucketname" +} + `, + }, + { + desc: "bucket with unrelated public access block", + source: ` +resource "aws_s3_bucket" "example" { + bucket = "bucketname" +} + +resource "aws_s3_bucket_public_access_block" "example_access_block"{ + bucket = aws_s3_bucket.other.id +} + `, + }, + { + desc: "bucket with unrelated public access block via name", + source: ` +resource "aws_s3_bucket" "example" { + bucket = "bucketname" +} + +resource "aws_s3_bucket_public_access_block" "example_access_block"{ + bucket = "something" +} + `, + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, tC.source, ".tf") + s3Ctx := Adapt(modules) + require.Len(t, s3Ctx.Buckets, 1) + assert.Nil(t, s3Ctx.Buckets[0].PublicAccessBlock) + + }) + } +} + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected s3.S3 + }{ + { + name: "basic", + terraform: ` + resource "aws_s3_bucket" "example" { + bucket = "bucket" + } + + resource "aws_s3_bucket_public_access_block" "example" { + bucket = aws_s3_bucket.example.id + + restrict_public_buckets = true + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + + } + + resource "aws_s3_bucket_acl" "example" { + bucket = aws_s3_bucket.example.id + acl = "private" + } + + resource "aws_s3_bucket_server_side_encryption_configuration" "example" { + bucket = aws_s3_bucket.example.bucket + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = "string-key" + sse_algorithm = "aws:kms" + } + } + } + + resource "aws_s3_bucket_logging" "example" { + bucket = aws_s3_bucket.example.id + + target_bucket = aws_s3_bucket.example.id + target_prefix = "log/" + } + + resource "aws_s3_bucket_versioning" "versioning_example" { + bucket = aws_s3_bucket.example.id + versioning_configuration { + status = "Enabled" + mfa_delete = "Enabled" + } + } + + resource "aws_s3_bucket_policy" "allow_access_from_another_account" { + bucket = aws_s3_bucket.example.bucket + policy = data.aws_iam_policy_document.allow_access_from_another_account.json + } + + data "aws_iam_policy_document" "allow_access_from_another_account" { + statement { + + actions = [ + "s3:GetObject", + "s3:ListBucket", + ] + + resources = [ + "arn:aws:s3:::*", + ] + } + } + `, + expected: s3.S3{ + Buckets: []s3.Bucket{ + { + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("bucket", iacTypes.NewTestMetadata()), + PublicAccessBlock: &s3.PublicAccessBlock{ + Metadata: iacTypes.NewTestMetadata(), + BlockPublicACLs: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + BlockPublicPolicy: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + IgnorePublicACLs: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + RestrictPublicBuckets: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + BucketPolicies: []iam.Policy{ + { + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("", iacTypes.NewTestMetadata()), + Document: func() iam.Document { + + builder := iamgo.NewPolicyBuilder() + + sb := iamgo.NewStatementBuilder() + sb.WithEffect(iamgo.EffectAllow) + sb.WithActions([]string{"s3:GetObject", "s3:ListBucket"}) + sb.WithResources([]string{"arn:aws:s3:::*"}) + + builder.WithStatement(sb.Build()) + + return iam.Document{ + Parsed: builder.Build(), + Metadata: iacTypes.NewTestMetadata(), + IsOffset: true, + HasRefs: false, + } + }(), + Builtin: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + Encryption: s3.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Algorithm: iacTypes.String("aws:kms", iacTypes.NewTestMetadata()), + KMSKeyId: iacTypes.String("string-key", iacTypes.NewTestMetadata()), + }, + Versioning: s3.Versioning{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + MFADelete: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + Logging: s3.Logging{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + TargetBucket: iacTypes.String("aws_s3_bucket.example", iacTypes.NewTestMetadata()), + }, + ACL: iacTypes.String("private", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + { + name: "non-valid SSE algorithm", + terraform: ` +resource "aws_s3_bucket" "this" { + bucket = "test" +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "this" { + bucket = aws_s3_bucket.this.id + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "" + } + } +}`, + expected: s3.S3{ + Buckets: []s3.Bucket{ + { + Name: iacTypes.String("test", iacTypes.NewTestMetadata()), + Encryption: s3.Encryption{ + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + ACL: iacTypes.String("private", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_s3_bucket" "example" { + bucket = "bucket" + } + + resource "aws_s3_bucket_public_access_block" "example" { + bucket = aws_s3_bucket.example.id + + restrict_public_buckets = true + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + } + + resource "aws_s3_bucket_acl" "example" { + bucket = aws_s3_bucket.example.id + acl = "private" + } + + resource "aws_s3_bucket_server_side_encryption_configuration" "example" { + bucket = aws_s3_bucket.example.bucket + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = "string-key" + sse_algorithm = "aws:kms" + } + } + } + + resource "aws_s3_bucket_logging" "example" { + bucket = aws_s3_bucket.example.id + + target_bucket = aws_s3_bucket.example.id + target_prefix = "log/" + } + + resource "aws_s3_bucket_versioning" "versioning_example" { + bucket = aws_s3_bucket.example.id + versioning_configuration { + status = "Enabled" + } + } + + resource "aws_s3_bucket_policy" "allow_access_from_another_account" { + bucket = aws_s3_bucket.example.bucket + policy = data.aws_iam_policy_document.allow_access_from_another_account.json + } + + data "aws_iam_policy_document" "allow_access_from_another_account" { + statement { + + actions = [ + "s3:GetObject", + "s3:ListBucket", + ] + + resources = [ + "arn:aws:s3:::*", + ] + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Buckets, 1) + bucket := adapted.Buckets[0] + + assert.Equal(t, 2, bucket.Metadata.Range().GetStartLine()) + assert.Equal(t, 4, bucket.Metadata.Range().GetEndLine()) + + assert.Equal(t, 6, bucket.PublicAccessBlock.Metadata.Range().GetStartLine()) + assert.Equal(t, 13, bucket.PublicAccessBlock.Metadata.Range().GetEndLine()) + + assert.Equal(t, 9, bucket.PublicAccessBlock.RestrictPublicBuckets.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 9, bucket.PublicAccessBlock.RestrictPublicBuckets.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 10, bucket.PublicAccessBlock.BlockPublicACLs.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 10, bucket.PublicAccessBlock.BlockPublicACLs.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, bucket.PublicAccessBlock.BlockPublicPolicy.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 11, bucket.PublicAccessBlock.BlockPublicPolicy.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 12, bucket.PublicAccessBlock.IgnorePublicACLs.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 12, bucket.PublicAccessBlock.IgnorePublicACLs.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 17, bucket.ACL.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 17, bucket.ACL.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 20, bucket.Encryption.Metadata.Range().GetStartLine()) + assert.Equal(t, 29, bucket.Encryption.Metadata.Range().GetEndLine()) + + assert.Equal(t, 25, bucket.Encryption.KMSKeyId.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 25, bucket.Encryption.KMSKeyId.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 26, bucket.Encryption.Algorithm.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 26, bucket.Encryption.Algorithm.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 31, bucket.Logging.Metadata.Range().GetStartLine()) + assert.Equal(t, 36, bucket.Logging.Metadata.Range().GetEndLine()) + + assert.Equal(t, 34, bucket.Logging.TargetBucket.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 34, bucket.Logging.TargetBucket.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 38, bucket.Versioning.Metadata.Range().GetStartLine()) + assert.Equal(t, 43, bucket.Versioning.Metadata.Range().GetEndLine()) + + assert.Equal(t, 41, bucket.Versioning.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 41, bucket.Versioning.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 47, bucket.BucketPolicies[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 47, bucket.BucketPolicies[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 50, bucket.BucketPolicies[0].Document.Metadata.Range().GetStartLine()) + assert.Equal(t, 62, bucket.BucketPolicies[0].Document.Metadata.Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/s3/bucket.go b/pkg/iac/adapters/terraform/aws/s3/bucket.go new file mode 100644 index 000000000000..5ecf7e9ba21b --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/s3/bucket.go @@ -0,0 +1,289 @@ +package s3 + +import ( + "slices" + + s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type adapter struct { + modules terraform.Modules + bucketMap map[string]*s3.Bucket +} + +func (a *adapter) adaptBuckets() []s3.Bucket { + for _, block := range a.modules.GetResourcesByType("aws_s3_bucket") { + bucket := &s3.Bucket{ + Metadata: block.GetMetadata(), + Name: block.GetAttribute("bucket").AsStringValueOrDefault("", block), + PublicAccessBlock: nil, + BucketPolicies: nil, + Encryption: getEncryption(block, a), + Versioning: getVersioning(block, a), + Logging: getLogging(block, a), + ACL: getBucketAcl(block, a), + AccelerateConfigurationStatus: getAccelerateStatus(block, a), + BucketLocation: block.GetAttribute("region").AsStringValueOrDefault("", block), + LifecycleConfiguration: getLifecycle(block, a), + Website: getWebsite(block, a), + Objects: getObject(block, a), + } + a.bucketMap[block.ID()] = bucket + } + + a.adaptBucketPolicies() + a.adaptPublicAccessBlocks() + + var buckets []s3.Bucket + for _, bucket := range a.bucketMap { + buckets = append(buckets, *bucket) + } + + return buckets +} + +func getEncryption(block *terraform.Block, a *adapter) s3.Encryption { + if sseConfgihuration := block.GetBlock("server_side_encryption_configuration"); sseConfgihuration != nil { + return newS3Encryption(block, sseConfgihuration) + } + if val, ok := applyForBucketRelatedResource(a, block, "aws_s3_bucket_server_side_encryption_configuration", func(resource *terraform.Block) s3.Encryption { + return newS3Encryption(resource, resource) + }); ok { + return val + } + return s3.Encryption{ + Metadata: block.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, block.GetMetadata()), + KMSKeyId: iacTypes.StringDefault("", block.GetMetadata()), + Algorithm: iacTypes.StringDefault("", block.GetMetadata()), + } +} + +func newS3Encryption(root, sseConfgihuration *terraform.Block) s3.Encryption { + return s3.Encryption{ + Metadata: root.GetMetadata(), + Enabled: isEncrypted(sseConfgihuration), + Algorithm: terraform.MapNestedAttribute( + sseConfgihuration, + "rule.apply_server_side_encryption_by_default.sse_algorithm", + func(attr *terraform.Attribute, parent *terraform.Block) iacTypes.StringValue { + return attr.AsStringValueOrDefault("", parent) + }, + ), + KMSKeyId: terraform.MapNestedAttribute( + sseConfgihuration, + "rule.apply_server_side_encryption_by_default.kms_master_key_id", + func(attr *terraform.Attribute, parent *terraform.Block) iacTypes.StringValue { + return attr.AsStringValueOrDefault("", parent) + }, + ), + } +} + +func getVersioning(block *terraform.Block, a *adapter) s3.Versioning { + versioning := s3.Versioning{ + Metadata: block.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, block.GetMetadata()), + MFADelete: iacTypes.BoolDefault(false, block.GetMetadata()), + } + if lockBlock := block.GetBlock("object_lock_configuration"); lockBlock != nil { + if enabled := isObjeckLockEnabled(lockBlock); enabled != nil { + versioning.Enabled = *enabled + } + } + if vBlock := block.GetBlock("versioning"); vBlock != nil { + versioning.Enabled = vBlock.GetAttribute("enabled").AsBoolValueOrDefault(true, vBlock) + versioning.MFADelete = vBlock.GetAttribute("mfa_delete").AsBoolValueOrDefault(false, vBlock) + } + + if enabled, ok := applyForBucketRelatedResource(a, block, "aws_s3_bucket_object_lock_configuration", func(resource *terraform.Block) *iacTypes.BoolValue { + if block.GetAttribute("object_lock_enabled").IsTrue() { + return isObjeckLockEnabled(resource) + } + return nil + }); ok && enabled != nil { + versioning.Enabled = *enabled + } + + if val, ok := applyForBucketRelatedResource(a, block, "aws_s3_bucket_versioning", getVersioningFromResource); ok { + return val + } + return versioning +} + +func isObjeckLockEnabled(resource *terraform.Block) *iacTypes.BoolValue { + var val iacTypes.BoolValue + attr := resource.GetAttribute("object_lock_enabled") + switch { + case attr.IsNil(): // enabled by default + val = iacTypes.BoolDefault(true, resource.GetMetadata()) + case attr.Equals("Enabled"): + val = iacTypes.Bool(true, attr.GetMetadata()) + } + return &val +} + +// from aws_s3_bucket_versioning +func getVersioningFromResource(block *terraform.Block) s3.Versioning { + versioning := s3.Versioning{ + Metadata: block.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, block.GetMetadata()), + MFADelete: iacTypes.BoolDefault(false, block.GetMetadata()), + } + if config := block.GetBlock("versioning_configuration"); config != nil { + if status := config.GetAttribute("status"); status.IsNotNil() { + versioning.Enabled = iacTypes.Bool(status.Equals("Enabled", terraform.IgnoreCase), status.GetMetadata()) + } + if mfa := config.GetAttribute("mfa_delete"); mfa.IsNotNil() { + versioning.MFADelete = iacTypes.Bool(mfa.Equals("Enabled", terraform.IgnoreCase), mfa.GetMetadata()) + } + } + return versioning +} + +func getLogging(block *terraform.Block, a *adapter) s3.Logging { + if loggingBlock := block.GetBlock("logging"); loggingBlock.IsNotNil() { + targetBucket := loggingBlock.GetAttribute("target_bucket").AsStringValueOrDefault("", loggingBlock) + if referencedBlock, err := a.modules.GetReferencedBlock(loggingBlock.GetAttribute("target_bucket"), loggingBlock); err == nil { + targetBucket = iacTypes.String(referencedBlock.FullName(), loggingBlock.GetAttribute("target_bucket").GetMetadata()) + } + return s3.Logging{ + Metadata: loggingBlock.GetMetadata(), + Enabled: iacTypes.Bool(true, loggingBlock.GetMetadata()), + TargetBucket: targetBucket, + } + } + + if val, ok := applyForBucketRelatedResource(a, block, "aws_s3_bucket_logging", func(resource *terraform.Block) s3.Logging { + targetBucket := resource.GetAttribute("target-bucket").AsStringValueOrDefault("", resource) + if referencedBlock, err := a.modules.GetReferencedBlock(resource.GetAttribute("target_bucket"), resource); err == nil { + targetBucket = iacTypes.String(referencedBlock.FullName(), resource.GetAttribute("target_bucket").GetMetadata()) + } + return s3.Logging{ + Metadata: resource.GetMetadata(), + Enabled: hasLogging(resource), + TargetBucket: targetBucket, + } + }); ok { + return val + } + + return s3.Logging{ + Metadata: block.GetMetadata(), + Enabled: iacTypes.Bool(false, block.GetMetadata()), + TargetBucket: iacTypes.StringDefault("", block.GetMetadata()), + } +} + +func getBucketAcl(block *terraform.Block, a *adapter) iacTypes.StringValue { + aclAttr := block.GetAttribute("acl") + if aclAttr.IsString() { + return aclAttr.AsStringValueOrDefault("private", block) + } + + if val, ok := applyForBucketRelatedResource(a, block, "aws_s3_bucket_acl", func(resource *terraform.Block) iacTypes.StringValue { + return resource.GetAttribute("acl").AsStringValueOrDefault("private", resource) + }); ok { + return val + } + return iacTypes.StringDefault("private", block.GetMetadata()) +} + +func isEncrypted(sseConfgihuration *terraform.Block) iacTypes.BoolValue { + return terraform.MapNestedAttribute( + sseConfgihuration, + "rule.apply_server_side_encryption_by_default.sse_algorithm", + func(attr *terraform.Attribute, parent *terraform.Block) iacTypes.BoolValue { + if attr.IsNil() || !attr.IsString() { + return iacTypes.BoolDefault(false, parent.GetMetadata()) + } + algoVal := attr.Value().AsString() + isValidAlgo := slices.Contains(s3types.ServerSideEncryption("").Values(), s3types.ServerSideEncryption(algoVal)) + return iacTypes.Bool( + isValidAlgo, + attr.GetMetadata(), + ) + }, + ) +} + +func hasLogging(b *terraform.Block) iacTypes.BoolValue { + if loggingBlock := b.GetBlock("logging"); loggingBlock.IsNotNil() { + if targetAttr := loggingBlock.GetAttribute("target_bucket"); targetAttr.IsNotNil() && targetAttr.IsNotEmpty() { + return iacTypes.Bool(true, targetAttr.GetMetadata()) + } + return iacTypes.BoolDefault(false, loggingBlock.GetMetadata()) + } + if targetBucket := b.GetAttribute("target_bucket"); targetBucket.IsNotNil() { + return iacTypes.Bool(true, targetBucket.GetMetadata()) + } + return iacTypes.BoolDefault(false, b.GetMetadata()) +} + +func getLifecycle(b *terraform.Block, a *adapter) []s3.Rules { + + var rules []s3.Rules + for _, r := range a.modules.GetReferencingResources(b, "aws_s3_bucket_lifecycle_configuration", "bucket") { + ruleblock := r.GetBlocks("rule") + for _, rule := range ruleblock { + rules = append(rules, s3.Rules{ + Metadata: rule.GetMetadata(), + Status: rule.GetAttribute("status").AsStringValueOrDefault("Enabled", rule), + }) + } + } + return rules +} + +func getWebsite(b *terraform.Block, a *adapter) (website *s3.Website) { + for _, r := range a.modules.GetReferencingResources(b, "aws_s3_bucket_website_configuration", "bucket") { + website = &s3.Website{ + Metadata: r.GetMetadata(), + } + } + return website +} + +func getObject(b *terraform.Block, a *adapter) []s3.Contents { + var object []s3.Contents + for _, r := range a.modules.GetReferencingResources(b, "aws_s3_object", "bucket") { + object = append(object, s3.Contents{ + Metadata: r.GetMetadata(), + }) + } + return object +} + +func getAccelerateStatus(b *terraform.Block, a *adapter) iacTypes.StringValue { + var status iacTypes.StringValue + for _, r := range a.modules.GetReferencingResources(b, " aws_s3_bucket_accelerate_configuration", "bucket") { + status = r.GetAttribute("status").AsStringValueOrDefault("Enabled", r) + } + return status +} + +func applyForBucketRelatedResource[T any](a *adapter, block *terraform.Block, resType string, fn func(resource *terraform.Block) T) (T, bool) { + for _, resource := range a.modules.GetResourcesByType(resType) { + bucketAttr := resource.GetAttribute("bucket") + if bucketAttr.IsNotNil() { + if bucketAttr.IsString() { + actualBucketName := block.GetAttribute("bucket").AsStringValueOrDefault("", block).Value() + if bucketAttr.Equals(block.ID()) || bucketAttr.Equals(actualBucketName) { + return fn(resource), true + } + } + if referencedBlock, err := a.modules.GetReferencedBlock(bucketAttr, resource); err == nil { + if referencedBlock.ID() == block.ID() { + return fn(resource), true + } + } + } + + } + var res T + return res, false +} diff --git a/pkg/iac/adapters/terraform/aws/s3/bucket_test.go b/pkg/iac/adapters/terraform/aws/s3/bucket_test.go new file mode 100644 index 000000000000..069d0b39c86d --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/s3/bucket_test.go @@ -0,0 +1,330 @@ +package s3 + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + "github.com/stretchr/testify/require" + + "github.com/stretchr/testify/assert" +) + +func Test_GetBuckets(t *testing.T) { + + source := ` +resource "aws_s3_bucket" "bucket1" { + + +} +` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + +} + +func Test_BucketGetACL(t *testing.T) { + + source := ` +resource "aws_s3_bucket" "example" { + bucket = "yournamehere" + acl = "authenticated-read" + + # ... other configuration ... +}` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + assert.Equal(t, "authenticated-read", s3.Buckets[0].ACL.Value()) + +} + +func Test_V4BucketGetACL(t *testing.T) { + + source := ` +resource "aws_s3_bucket" "example" { + bucket = "yournamehere" +} + +resource "aws_s3_bucket_acl" "example" { + bucket = aws_s3_bucket.example.id + acl = "authenticated-read" +}` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + assert.Equal(t, "authenticated-read", s3.Buckets[0].ACL.Value()) + +} + +func Test_BucketGetLogging(t *testing.T) { + + source := ` +resource "aws_s3_bucket" "example" { + bucket = "yournamehere" + + # ... other configuration ... + logging { + target_bucket = aws_s3_bucket.log_bucket.id + target_prefix = "log/" + } +} +` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + assert.True(t, s3.Buckets[0].Logging.Enabled.Value()) + +} + +func Test_V4BucketGetLogging(t *testing.T) { + + source := ` +resource "aws_s3_bucket" "log_bucket" { + bucket = "example-log-bucket" + + # ... other configuration ... +} + +resource "aws_s3_bucket" "example" { + bucket = "yournamehere" + + # ... other configuration ... +} + +resource "aws_s3_bucket_logging" "example" { + bucket = aws_s3_bucket.example.id + target_bucket = aws_s3_bucket.log_bucket.id + target_prefix = "log/" +} +` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 2, len(s3.Buckets)) + for _, bucket := range s3.Buckets { + switch bucket.Name.Value() { + case "yournamehere": + assert.True(t, bucket.Logging.Enabled.Value()) + case "example-log-bucket": + assert.False(t, bucket.Logging.Enabled.Value()) + } + } +} + +func Test_BucketGetVersioning(t *testing.T) { + source := ` +resource "aws_s3_bucket" "example" { + bucket = "yournamehere" + + # ... other configuration ... + versioning { + enabled = true + } +}` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + assert.True(t, s3.Buckets[0].Versioning.Enabled.Value()) +} + +func Test_V4BucketGetVersioning(t *testing.T) { + source := ` +resource "aws_s3_bucket" "example" { + bucket = "yournamehere" + + # ... other configuration ... +} + +resource "aws_s3_bucket_versioning" "example" { + bucket = aws_s3_bucket.example.id + versioning_configuration { + status = "Enabled" + } +}` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + assert.True(t, s3.Buckets[0].Versioning.Enabled.Value()) +} + +func Test_BucketGetVersioningWithLockDeprecated(t *testing.T) { + source := ` +resource "aws_s3_bucket" "example" { + bucket = "mybucket" + object_lock_configuration { + object_lock_enabled = "Enabled" + } +} +` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + assert.True(t, s3.Buckets[0].Versioning.Enabled.Value()) + +} + +func Test_BucketGetVersioningWithLockForNewBucket(t *testing.T) { + source := ` +resource "aws_s3_bucket" "example" { + bucket = "mybucket" + object_lock_enabled = true +} + +resource "aws_s3_bucket_object_lock_configuration" "example" { + bucket = aws_s3_bucket.example.id +} +` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + assert.True(t, s3.Buckets[0].Versioning.Enabled.Value()) + +} + +func Test_BucketGetVersioningWhenLockDisabledButVersioningEnabled(t *testing.T) { + source := ` +resource "aws_s3_bucket" "example" { + bucket = "mybucket" +} + +resource "aws_s3_bucket_object_lock_configuration" "example" { + bucket = aws_s3_bucket.example.id +} + +resource "aws_s3_bucket_versioning" "example" { + bucket = aws_s3_bucket.example.id + versioning_configuration { + status = "Enabled" + } +} +` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + assert.True(t, s3.Buckets[0].Versioning.Enabled.Value()) + +} + +func Test_BucketGetEncryption(t *testing.T) { + + source := ` + resource "aws_s3_bucket" "example" { + bucket = "yournamehere" + + # ... other configuration ... + server_side_encryption_configuration { + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.mykey.arn + sse_algorithm = "aws:kms" + } + } + } +}` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + assert.True(t, s3.Buckets[0].Encryption.Enabled.Value()) +} + +func Test_V4BucketGetEncryption(t *testing.T) { + + source := ` +resource "aws_s3_bucket" "example" { + bucket = "yournamehere" + + # ... other configuration ... +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "example" { + bucket = aws_s3_bucket.example.id + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.mykey.arn + sse_algorithm = "aws:kms" + } + } +} +` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + assert.Equal(t, 1, len(s3.Buckets)) + assert.True(t, s3.Buckets[0].Encryption.Enabled.Value()) +} + +func Test_BucketWithPolicy(t *testing.T) { + + source := ` +resource "aws_s3_bucket" "bucket1" { + bucket = "lol" +} + +resource "aws_s3_bucket_policy" "allow_access_from_another_account" { + bucket = aws_s3_bucket.bucket1.id + policy = data.aws_iam_policy_document.allow_access_from_another_account.json +} + +data "aws_iam_policy_document" "allow_access_from_another_account" { + statement { + principals { + type = "AWS" + identifiers = ["123456789012"] + } + + actions = [ + "s3:GetObject", + "s3:ListBucket", + ] + + resources = [ + aws_s3_bucket.bucket1.arn, + ] + } +} + +` + modules := tftestutil.CreateModulesFromSource(t, source, ".tf") + + s3 := Adapt(modules) + + require.Equal(t, 1, len(s3.Buckets)) + require.Equal(t, 1, len(s3.Buckets[0].BucketPolicies)) + + policy := s3.Buckets[0].BucketPolicies[0] + + statements, _ := policy.Document.Parsed.Statements() + require.Equal(t, 1, len(statements)) + + principals, _ := statements[0].Principals() + actions, _ := statements[0].Actions() + + awsPrincipals, _ := principals.AWS() + require.Equal(t, 1, len(awsPrincipals)) + require.Equal(t, 2, len(actions)) + +} diff --git a/pkg/iac/adapters/terraform/aws/s3/policies.go b/pkg/iac/adapters/terraform/aws/s3/policies.go new file mode 100644 index 000000000000..3c8804957d0c --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/s3/policies.go @@ -0,0 +1,53 @@ +package s3 + +import ( + iamAdapter "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func (a *adapter) adaptBucketPolicies() { + + for _, b := range a.modules.GetResourcesByType("aws_s3_bucket_policy") { + + policyAttr := b.GetAttribute("policy") + if policyAttr.IsNil() { + continue + } + doc, err := iamAdapter.ParsePolicyFromAttr(policyAttr, b, a.modules) + if err != nil { + continue + } + + policy := iam.Policy{ + Metadata: policyAttr.GetMetadata(), + Name: iacTypes.StringDefault("", b.GetMetadata()), + Document: *doc, + Builtin: iacTypes.Bool(false, b.GetMetadata()), + } + + var bucketName string + bucketAttr := b.GetAttribute("bucket") + + if bucketAttr.IsNotNil() { + if referencedBlock, err := a.modules.GetReferencedBlock(bucketAttr, b); err == nil { + if bucket, ok := a.bucketMap[referencedBlock.ID()]; ok { + bucket.BucketPolicies = append(bucket.BucketPolicies, policy) + a.bucketMap[referencedBlock.ID()] = bucket + continue + } + } + } + + if bucketAttr.IsString() { + bucketName = bucketAttr.Value().AsString() + for id, bucket := range a.bucketMap { + if bucket.Name.EqualTo(bucketName) { + bucket.BucketPolicies = append(bucket.BucketPolicies, policy) + a.bucketMap[id] = bucket + break + } + } + } + } +} diff --git a/pkg/iac/adapters/terraform/aws/s3/public_access_block.go b/pkg/iac/adapters/terraform/aws/s3/public_access_block.go new file mode 100644 index 000000000000..eff4a71b4005 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/s3/public_access_block.go @@ -0,0 +1,41 @@ +package s3 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" +) + +func (a *adapter) adaptPublicAccessBlocks() { + + for _, b := range a.modules.GetResourcesByType("aws_s3_bucket_public_access_block") { + + pba := s3.PublicAccessBlock{ + Metadata: b.GetMetadata(), + BlockPublicACLs: b.GetAttribute("block_public_acls").AsBoolValueOrDefault(false, b), + BlockPublicPolicy: b.GetAttribute("block_public_policy").AsBoolValueOrDefault(false, b), + IgnorePublicACLs: b.GetAttribute("ignore_public_acls").AsBoolValueOrDefault(false, b), + RestrictPublicBuckets: b.GetAttribute("restrict_public_buckets").AsBoolValueOrDefault(false, b), + } + + var bucketName string + bucketAttr := b.GetAttribute("bucket") + if bucketAttr.IsNotNil() { + if referencedBlock, err := a.modules.GetReferencedBlock(bucketAttr, b); err == nil { + if bucket, ok := a.bucketMap[referencedBlock.ID()]; ok { + bucket.PublicAccessBlock = &pba + a.bucketMap[referencedBlock.ID()] = bucket + continue + } + } + } + if bucketAttr.IsString() { + bucketName = bucketAttr.Value().AsString() + for id, bucket := range a.bucketMap { + if bucketAttr.Equals(id) || bucket.Name.EqualTo(bucketName) { + bucket.PublicAccessBlock = &pba + a.bucketMap[id] = bucket + continue + } + } + } + } +} diff --git a/pkg/iac/adapters/terraform/aws/sns/adapt.go b/pkg/iac/adapters/terraform/aws/sns/adapt.go new file mode 100644 index 000000000000..761bb281a804 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/sns/adapt.go @@ -0,0 +1,38 @@ +package sns + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sns" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) sns.SNS { + return sns.SNS{ + Topics: adaptTopics(modules), + } +} + +func adaptTopics(modules terraform.Modules) []sns.Topic { + var topics []sns.Topic + for _, module := range modules { + for _, resource := range module.GetResourcesByType("aws_sns_topic") { + topics = append(topics, adaptTopic(resource)) + } + } + return topics +} + +func adaptTopic(resourceBlock *terraform.Block) sns.Topic { + return sns.Topic{ + Metadata: resourceBlock.GetMetadata(), + ARN: types.StringDefault("", resourceBlock.GetMetadata()), + Encryption: adaptEncryption(resourceBlock), + } +} + +func adaptEncryption(resourceBlock *terraform.Block) sns.Encryption { + return sns.Encryption{ + Metadata: resourceBlock.GetMetadata(), + KMSKeyID: resourceBlock.GetAttribute("kms_master_key_id").AsStringValueOrDefault("", resourceBlock), + } +} diff --git a/pkg/iac/adapters/terraform/aws/sns/adapt_test.go b/pkg/iac/adapters/terraform/aws/sns/adapt_test.go new file mode 100644 index 000000000000..caae71b2dc17 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/sns/adapt_test.go @@ -0,0 +1,81 @@ +package sns + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sns" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptTopic(t *testing.T) { + tests := []struct { + name string + terraform string + expected sns.Topic + }{ + { + name: "defined", + terraform: ` + resource "aws_sns_topic" "good_example" { + kms_master_key_id = "/blah" + } +`, + expected: sns.Topic{ + Metadata: iacTypes.NewTestMetadata(), + ARN: iacTypes.String("", iacTypes.NewTestMetadata()), + Encryption: sns.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + KMSKeyID: iacTypes.String("/blah", iacTypes.NewTestMetadata()), + }, + }, + }, + { + name: "default", + terraform: ` + resource "aws_sns_topic" "good_example" { + } +`, + expected: sns.Topic{ + Metadata: iacTypes.NewTestMetadata(), + ARN: iacTypes.String("", iacTypes.NewTestMetadata()), + Encryption: sns.Encryption{ + Metadata: iacTypes.NewTestMetadata(), + KMSKeyID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptTopic(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "aws_sns_topic" "good_example" { + kms_master_key_id = "/blah" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Topics, 1) + topic := adapted.Topics[0] + + assert.Equal(t, 2, topic.Metadata.Range().GetStartLine()) + assert.Equal(t, 4, topic.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, topic.Encryption.KMSKeyID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, topic.Encryption.KMSKeyID.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/aws/sqs/adapt.go b/pkg/iac/adapters/terraform/aws/sqs/adapt.go new file mode 100644 index 000000000000..432bda06eda2 --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/sqs/adapt.go @@ -0,0 +1,167 @@ +package sqs + +import ( + "github.com/google/uuid" + "github.com/liamg/iamgo" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/aws/iam" + iamp "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sqs" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) sqs.SQS { + return sqs.SQS{ + Queues: (&adapter{ + modules: modules, + queues: make(map[string]sqs.Queue), + }).adaptQueues(), + } +} + +type adapter struct { + modules terraform.Modules + queues map[string]sqs.Queue +} + +func (a *adapter) adaptQueues() []sqs.Queue { + for _, resource := range a.modules.GetResourcesByType("aws_sqs_queue") { + a.adaptQueue(resource) + } + + for _, policyBlock := range a.modules.GetResourcesByType("aws_sqs_queue_policy") { + + policy := iamp.Policy{ + Metadata: policyBlock.GetMetadata(), + Name: iacTypes.StringDefault("", policyBlock.GetMetadata()), + Document: iamp.Document{ + Metadata: policyBlock.GetMetadata(), + }, + Builtin: iacTypes.Bool(false, policyBlock.GetMetadata()), + } + if attr := policyBlock.GetAttribute("policy"); attr.IsString() { + dataBlock, err := a.modules.GetBlockById(attr.Value().AsString()) + if err != nil { + parsed, err := iamgo.ParseString(attr.Value().AsString()) + if err != nil { + continue + } + policy.Document.Parsed = *parsed + policy.Document.Metadata = attr.GetMetadata() + } else if dataBlock.Type() == "data" && dataBlock.TypeLabel() == "aws_iam_policy_document" { // nolint: goconst + if doc, err := iam.ConvertTerraformDocument(a.modules, dataBlock); err == nil { + policy.Document.Parsed = doc.Document + policy.Document.Metadata = doc.Source.GetMetadata() + policy.Document.IsOffset = true + } + } + } else if refBlock, err := a.modules.GetReferencedBlock(attr, policyBlock); err == nil { + if refBlock.Type() == "data" && refBlock.TypeLabel() == "aws_iam_policy_document" { // nolint: goconst + if doc, err := iam.ConvertTerraformDocument(a.modules, refBlock); err == nil { + policy.Document.Parsed = doc.Document + policy.Document.Metadata = doc.Source.GetMetadata() + } + } + } + + if urlAttr := policyBlock.GetAttribute("queue_url"); urlAttr.IsNotNil() { + if refBlock, err := a.modules.GetReferencedBlock(urlAttr, policyBlock); err == nil { + if queue, ok := a.queues[refBlock.ID()]; ok { + queue.Policies = append(queue.Policies, policy) + a.queues[refBlock.ID()] = queue + continue + } + } + } + + a.queues[uuid.NewString()] = sqs.Queue{ + Metadata: iacTypes.NewUnmanagedMetadata(), + QueueURL: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + Encryption: sqs.Encryption{ + Metadata: iacTypes.NewUnmanagedMetadata(), + ManagedEncryption: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + KMSKeyID: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + }, + Policies: []iamp.Policy{policy}, + } + } + + var queues []sqs.Queue + for _, queue := range a.queues { + queues = append(queues, queue) + } + return queues +} + +func (a *adapter) adaptQueue(resource *terraform.Block) { + + kmsKeyIdAttr := resource.GetAttribute("kms_master_key_id") + kmsKeyIdVal := kmsKeyIdAttr.AsStringValueOrDefault("", resource) + managedEncryption := resource.GetAttribute("sqs_managed_sse_enabled") + + var policies []iamp.Policy + if attr := resource.GetAttribute("policy"); attr.IsString() { + + dataBlock, err := a.modules.GetBlockById(attr.Value().AsString()) + if err != nil { + policy := iamp.Policy{ + Metadata: attr.GetMetadata(), + Name: iacTypes.StringDefault("", attr.GetMetadata()), + Document: iamp.Document{ + Metadata: attr.GetMetadata(), + }, + Builtin: iacTypes.Bool(false, attr.GetMetadata()), + } + parsed, err := iamgo.ParseString(attr.Value().AsString()) + if err == nil { + policy.Document.Parsed = *parsed + policy.Document.Metadata = attr.GetMetadata() + policy.Metadata = attr.GetMetadata() + policies = append(policies, policy) + } + } else if dataBlock.Type() == "data" && dataBlock.TypeLabel() == "aws_iam_policy_document" { + if doc, err := iam.ConvertTerraformDocument(a.modules, dataBlock); err == nil { + policy := iamp.Policy{ + Metadata: attr.GetMetadata(), + Name: iacTypes.StringDefault("", attr.GetMetadata()), + Document: iamp.Document{ + Metadata: doc.Source.GetMetadata(), + Parsed: doc.Document, + IsOffset: true, + HasRefs: false, + }, + Builtin: iacTypes.Bool(false, attr.GetMetadata()), + } + policies = append(policies, policy) + } + } + + } else if refBlock, err := a.modules.GetReferencedBlock(attr, resource); err == nil { + if refBlock.Type() == "data" && refBlock.TypeLabel() == "aws_iam_policy_document" { + if doc, err := iam.ConvertTerraformDocument(a.modules, refBlock); err == nil { + policy := iamp.Policy{ + Metadata: doc.Source.GetMetadata(), + Name: iacTypes.StringDefault("", doc.Source.GetMetadata()), + Document: iamp.Document{ + Metadata: doc.Source.GetMetadata(), + Parsed: doc.Document, + }, + Builtin: iacTypes.Bool(false, refBlock.GetMetadata()), + } + policies = append(policies, policy) + } + } + } + + a.queues[resource.ID()] = sqs.Queue{ + Metadata: resource.GetMetadata(), + QueueURL: iacTypes.StringDefault("", resource.GetMetadata()), + Encryption: sqs.Encryption{ + Metadata: resource.GetMetadata(), + ManagedEncryption: managedEncryption.AsBoolValueOrDefault(false, resource), + KMSKeyID: kmsKeyIdVal, + }, + Policies: policies, + } +} diff --git a/pkg/iac/adapters/terraform/aws/sqs/adapt_test.go b/pkg/iac/adapters/terraform/aws/sqs/adapt_test.go new file mode 100644 index 000000000000..dc95257d258e --- /dev/null +++ b/pkg/iac/adapters/terraform/aws/sqs/adapt_test.go @@ -0,0 +1,139 @@ +package sqs + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sqs" + + "github.com/liamg/iamgo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected sqs.SQS + }{ + { + name: "np kms key", + terraform: ` + resource "aws_sqs_queue" "good_example" { + + policy = <= azurerm 2.97.0 + if omsAgentBlock := resource.GetBlock("oms_agent"); omsAgentBlock.IsNotNil() { + cluster.AddonProfile.OMSAgent.Metadata = omsAgentBlock.GetMetadata() + cluster.AddonProfile.OMSAgent.Enabled = iacTypes.Bool(true, omsAgentBlock.GetMetadata()) + } + + // azurerm < 2.99.0 + if resource.HasChild("role_based_access_control") { + roleBasedAccessControlBlock := resource.GetBlock("role_based_access_control") + rbEnabledAttr := roleBasedAccessControlBlock.GetAttribute("enabled") + cluster.RoleBasedAccessControl.Metadata = roleBasedAccessControlBlock.GetMetadata() + cluster.RoleBasedAccessControl.Enabled = rbEnabledAttr.AsBoolValueOrDefault(false, roleBasedAccessControlBlock) + } + if resource.HasChild("role_based_access_control_enabled") { + // azurerm >= 2.99.0 + roleBasedAccessControlEnabledAttr := resource.GetAttribute("role_based_access_control_enabled") + cluster.RoleBasedAccessControl.Metadata = roleBasedAccessControlEnabledAttr.GetMetadata() + cluster.RoleBasedAccessControl.Enabled = roleBasedAccessControlEnabledAttr.AsBoolValueOrDefault(false, resource) + } + + if resource.HasChild("azure_active_directory_role_based_access_control") { + azureRoleBasedAccessControl := resource.GetBlock("azure_active_directory_role_based_access_control") + if azureRoleBasedAccessControl.IsNotNil() { + enabledAttr := azureRoleBasedAccessControl.GetAttribute("azure_rbac_enabled") + if !cluster.RoleBasedAccessControl.Enabled.IsTrue() { + cluster.RoleBasedAccessControl.Metadata = azureRoleBasedAccessControl.GetMetadata() + cluster.RoleBasedAccessControl.Enabled = enabledAttr.AsBoolValueOrDefault(false, azureRoleBasedAccessControl) + } + } + } + return cluster +} diff --git a/pkg/iac/adapters/terraform/azure/container/adapt_test.go b/pkg/iac/adapters/terraform/azure/container/adapt_test.go new file mode 100644 index 000000000000..13d8c712a621 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/container/adapt_test.go @@ -0,0 +1,261 @@ +package container + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/container" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptCluster(t *testing.T) { + tests := []struct { + name string + terraform string + expected container.KubernetesCluster + }{ + { + name: "defined", + terraform: ` + resource "azurerm_kubernetes_cluster" "example" { + private_cluster_enabled = true + + network_profile { + network_policy = "calico" + } + + api_server_access_profile { + + authorized_ip_ranges = [ + "1.2.3.4/32" + ] + + } + + addon_profile { + oms_agent { + enabled = true + } + } + + role_based_access_control { + enabled = true + } + } +`, + expected: container.KubernetesCluster{ + Metadata: iacTypes.NewTestMetadata(), + NetworkProfile: container.NetworkProfile{ + Metadata: iacTypes.NewTestMetadata(), + NetworkPolicy: iacTypes.String("calico", iacTypes.NewTestMetadata()), + }, + EnablePrivateCluster: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + APIServerAuthorizedIPRanges: []iacTypes.StringValue{ + iacTypes.String("1.2.3.4/32", iacTypes.NewTestMetadata()), + }, + AddonProfile: container.AddonProfile{ + Metadata: iacTypes.NewTestMetadata(), + OMSAgent: container.OMSAgent{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + RoleBasedAccessControl: container.RoleBasedAccessControl{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + { + name: "rbac with a new syntax", + terraform: ` + resource "azurerm_kubernetes_cluster" "example" { + role_based_access_control_enabled = true + } +`, + expected: container.KubernetesCluster{ + Metadata: iacTypes.NewTestMetadata(), + NetworkProfile: container.NetworkProfile{ + Metadata: iacTypes.NewTestMetadata(), + NetworkPolicy: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + EnablePrivateCluster: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + AddonProfile: container.AddonProfile{ + Metadata: iacTypes.NewTestMetadata(), + OMSAgent: container.OMSAgent{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + RoleBasedAccessControl: container.RoleBasedAccessControl{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + { + name: "defaults", + terraform: ` + resource "azurerm_kubernetes_cluster" "example" { + } +`, + expected: container.KubernetesCluster{ + Metadata: iacTypes.NewTestMetadata(), + NetworkProfile: container.NetworkProfile{ + Metadata: iacTypes.NewTestMetadata(), + NetworkPolicy: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + EnablePrivateCluster: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + AddonProfile: container.AddonProfile{ + Metadata: iacTypes.NewTestMetadata(), + OMSAgent: container.OMSAgent{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + RoleBasedAccessControl: container.RoleBasedAccessControl{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + { + name: "rbac off with k8s rbac on", + terraform: ` +resource "azurerm_kubernetes_cluster" "misreporting_example" { + role_based_access_control_enabled = true # Enable k8s RBAC + azure_active_directory_role_based_access_control { + managed = true # Enable AKS-managed Azure AAD integration + azure_rbac_enabled = false # Explicitly disable Azure RBAC for Kubernetes Authorization + } + } +`, + expected: container.KubernetesCluster{ + Metadata: iacTypes.NewTestMetadata(), + NetworkProfile: container.NetworkProfile{ + Metadata: iacTypes.NewTestMetadata(), + NetworkPolicy: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + EnablePrivateCluster: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + AddonProfile: container.AddonProfile{ + Metadata: iacTypes.NewTestMetadata(), + OMSAgent: container.OMSAgent{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + RoleBasedAccessControl: container.RoleBasedAccessControl{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptCluster(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "azurerm_kubernetes_cluster" "example" { + private_cluster_enabled = true + + network_profile { + network_policy = "calico" + } + + api_server_access_profile { + + authorized_ip_ranges = [ + "1.2.3.4/32" + ] + + } + + addon_profile { + oms_agent { + enabled = true + } + } + + role_based_access_control { + enabled = true + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.KubernetesClusters, 1) + cluster := adapted.KubernetesClusters[0] + + assert.Equal(t, 3, cluster.EnablePrivateCluster.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, cluster.EnablePrivateCluster.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 5, cluster.NetworkProfile.Metadata.Range().GetStartLine()) + assert.Equal(t, 7, cluster.NetworkProfile.Metadata.Range().GetEndLine()) + + assert.Equal(t, 6, cluster.NetworkProfile.NetworkPolicy.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, cluster.NetworkProfile.NetworkPolicy.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, cluster.APIServerAuthorizedIPRanges[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 13, cluster.APIServerAuthorizedIPRanges[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 17, cluster.AddonProfile.Metadata.Range().GetStartLine()) + assert.Equal(t, 21, cluster.AddonProfile.Metadata.Range().GetEndLine()) + + assert.Equal(t, 18, cluster.AddonProfile.OMSAgent.Metadata.Range().GetStartLine()) + assert.Equal(t, 20, cluster.AddonProfile.OMSAgent.Metadata.Range().GetEndLine()) + + assert.Equal(t, 19, cluster.AddonProfile.OMSAgent.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 19, cluster.AddonProfile.OMSAgent.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 23, cluster.RoleBasedAccessControl.Metadata.Range().GetStartLine()) + assert.Equal(t, 25, cluster.RoleBasedAccessControl.Metadata.Range().GetEndLine()) + + assert.Equal(t, 24, cluster.RoleBasedAccessControl.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 24, cluster.RoleBasedAccessControl.Enabled.GetMetadata().Range().GetEndLine()) +} + +func TestWithLocals(t *testing.T) { + src := ` + variable "ip_whitelist" { + description = "IP Ranges with allowed access." + type = list(string) + default = ["1.2.3.4"] +} + +locals { + ip_whitelist = concat(var.ip_whitelist, split(",", data.azurerm_public_ip.build_agents.ip_address)) +} + +resource "azurerm_kubernetes_cluster" "aks" { + # not working + api_server_access_profile { + authorized_ip_ranges = local.ip_whitelist + } + # working + api_server_access_profile { + authorized_ip_ranges = concat(var.ip_whitelist, split(",", data.azurerm_public_ip.example.ip_address)) + } +}` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.KubernetesClusters, 1) + cluster := adapted.KubernetesClusters[0] + require.Len(t, cluster.APIServerAuthorizedIPRanges, 1) + assert.False(t, cluster.APIServerAuthorizedIPRanges[0].GetMetadata().IsResolvable()) +} diff --git a/pkg/iac/adapters/terraform/azure/database/adapt.go b/pkg/iac/adapters/terraform/azure/database/adapt.go new file mode 100644 index 000000000000..ea39949dff72 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/database/adapt.go @@ -0,0 +1,439 @@ +package database + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/database" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) database.Database { + + mssqlAdapter := mssqlAdapter{ + alertPolicyIDs: modules.GetChildResourceIDMapByType("azurerm_mssql_server_security_alert_policy"), + auditingPolicyIDs: modules.GetChildResourceIDMapByType("azurerm_mssql_server_extended_auditing_policy", "azurerm_mssql_database_extended_auditing_policy"), + firewallIDs: modules.GetChildResourceIDMapByType("azurerm_sql_firewall_rule", "azurerm_mssql_firewall_rule"), + } + + mysqlAdapter := mysqlAdapter{ + firewallIDs: modules.GetChildResourceIDMapByType("azurerm_mysql_firewall_rule"), + } + + mariaDBAdapter := mariaDBAdapter{ + firewallIDs: modules.GetChildResourceIDMapByType("azurerm_mariadb_firewall_rule"), + } + + postgresqlAdapter := postgresqlAdapter{ + firewallIDs: modules.GetChildResourceIDMapByType("azurerm_postgresql_firewall_rule"), + } + + return database.Database{ + MSSQLServers: mssqlAdapter.adaptMSSQLServers(modules), + MariaDBServers: mariaDBAdapter.adaptMariaDBServers(modules), + MySQLServers: mysqlAdapter.adaptMySQLServers(modules), + PostgreSQLServers: postgresqlAdapter.adaptPostgreSQLServers(modules), + } +} + +type mssqlAdapter struct { + alertPolicyIDs terraform.ResourceIDResolutions + auditingPolicyIDs terraform.ResourceIDResolutions + firewallIDs terraform.ResourceIDResolutions +} + +type mysqlAdapter struct { + firewallIDs terraform.ResourceIDResolutions +} + +type mariaDBAdapter struct { + firewallIDs terraform.ResourceIDResolutions +} + +type postgresqlAdapter struct { + firewallIDs terraform.ResourceIDResolutions +} + +func (a *mssqlAdapter) adaptMSSQLServers(modules terraform.Modules) []database.MSSQLServer { + var mssqlServers []database.MSSQLServer + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_sql_server") { + mssqlServers = append(mssqlServers, a.adaptMSSQLServer(resource, module)) + } + for _, resource := range module.GetResourcesByType("azurerm_mssql_server") { + mssqlServers = append(mssqlServers, a.adaptMSSQLServer(resource, module)) + } + } + + orphanResources := modules.GetResourceByIDs(a.alertPolicyIDs.Orphans()...) + + if len(orphanResources) > 0 { + orphanage := database.MSSQLServer{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Server: database.Server{ + Metadata: iacTypes.NewUnmanagedMetadata(), + EnableSSLEnforcement: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + MinimumTLSVersion: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + EnablePublicNetworkAccess: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + FirewallRules: nil, + }, + ExtendedAuditingPolicies: nil, + SecurityAlertPolicies: nil, + } + for _, policy := range orphanResources { + orphanage.SecurityAlertPolicies = append(orphanage.SecurityAlertPolicies, adaptMSSQLSecurityAlertPolicy(policy)) + } + mssqlServers = append(mssqlServers, orphanage) + + } + + orphanResources = modules.GetResourceByIDs(a.auditingPolicyIDs.Orphans()...) + + if len(orphanResources) > 0 { + orphanage := database.MSSQLServer{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Server: database.Server{ + Metadata: iacTypes.NewUnmanagedMetadata(), + EnableSSLEnforcement: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + MinimumTLSVersion: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + EnablePublicNetworkAccess: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + FirewallRules: nil, + }, + } + for _, policy := range orphanResources { + orphanage.ExtendedAuditingPolicies = append(orphanage.ExtendedAuditingPolicies, adaptMSSQLExtendedAuditingPolicy(policy)) + } + mssqlServers = append(mssqlServers, orphanage) + + } + + orphanResources = modules.GetResourceByIDs(a.firewallIDs.Orphans()...) + + if len(orphanResources) > 0 { + orphanage := database.MSSQLServer{ + Metadata: iacTypes.NewUnmanagedMetadata(), + } + for _, policy := range orphanResources { + orphanage.FirewallRules = append(orphanage.FirewallRules, adaptFirewallRule(policy)) + } + mssqlServers = append(mssqlServers, orphanage) + + } + + return mssqlServers +} +func (a *mysqlAdapter) adaptMySQLServers(modules terraform.Modules) []database.MySQLServer { + var mySQLServers []database.MySQLServer + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_mysql_server") { + mySQLServers = append(mySQLServers, a.adaptMySQLServer(resource, module)) + } + } + + orphanResources := modules.GetResourceByIDs(a.firewallIDs.Orphans()...) + + if len(orphanResources) > 0 { + orphanage := database.MySQLServer{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Server: database.Server{ + Metadata: iacTypes.NewUnmanagedMetadata(), + EnableSSLEnforcement: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + MinimumTLSVersion: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + EnablePublicNetworkAccess: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + FirewallRules: nil, + }, + } + for _, policy := range orphanResources { + orphanage.FirewallRules = append(orphanage.FirewallRules, adaptFirewallRule(policy)) + } + mySQLServers = append(mySQLServers, orphanage) + + } + + return mySQLServers +} + +func (a *mariaDBAdapter) adaptMariaDBServers(modules terraform.Modules) []database.MariaDBServer { + var mariaDBServers []database.MariaDBServer + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_mariadb_server") { + mariaDBServers = append(mariaDBServers, a.adaptMariaDBServer(resource, module)) + } + } + + orphanResources := modules.GetResourceByIDs(a.firewallIDs.Orphans()...) + + if len(orphanResources) > 0 { + orphanage := database.MariaDBServer{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Server: database.Server{ + Metadata: iacTypes.NewUnmanagedMetadata(), + EnableSSLEnforcement: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + MinimumTLSVersion: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + EnablePublicNetworkAccess: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + FirewallRules: nil, + }, + } + for _, policy := range orphanResources { + orphanage.FirewallRules = append(orphanage.FirewallRules, adaptFirewallRule(policy)) + } + mariaDBServers = append(mariaDBServers, orphanage) + + } + + return mariaDBServers +} + +func (a *postgresqlAdapter) adaptPostgreSQLServers(modules terraform.Modules) []database.PostgreSQLServer { + var postgreSQLServers []database.PostgreSQLServer + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_postgresql_server") { + postgreSQLServers = append(postgreSQLServers, a.adaptPostgreSQLServer(resource, module)) + } + } + + orphanResources := modules.GetResourceByIDs(a.firewallIDs.Orphans()...) + + if len(orphanResources) > 0 { + orphanage := database.PostgreSQLServer{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Server: database.Server{ + Metadata: iacTypes.NewUnmanagedMetadata(), + EnableSSLEnforcement: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + MinimumTLSVersion: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + EnablePublicNetworkAccess: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + FirewallRules: nil, + }, + Config: database.PostgresSQLConfig{ + Metadata: iacTypes.NewUnmanagedMetadata(), + LogCheckpoints: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + ConnectionThrottling: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + LogConnections: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + }, + } + for _, policy := range orphanResources { + orphanage.FirewallRules = append(orphanage.FirewallRules, adaptFirewallRule(policy)) + } + postgreSQLServers = append(postgreSQLServers, orphanage) + + } + + return postgreSQLServers +} + +func (a *mssqlAdapter) adaptMSSQLServer(resource *terraform.Block, module *terraform.Module) database.MSSQLServer { + minTLSVersionVal := iacTypes.StringDefault("", resource.GetMetadata()) + publicAccessVal := iacTypes.BoolDefault(true, resource.GetMetadata()) + enableSSLEnforcementVal := iacTypes.BoolDefault(false, resource.GetMetadata()) + + var auditingPolicies []database.ExtendedAuditingPolicy + var alertPolicies []database.SecurityAlertPolicy + var firewallRules []database.FirewallRule + + if resource.TypeLabel() == "azurerm_mssql_server" { + minTLSVersionAttr := resource.GetAttribute("minimum_tls_version") + minTLSVersionVal = minTLSVersionAttr.AsStringValueOrDefault("", resource) + + publicAccessAttr := resource.GetAttribute("public_network_access_enabled") + publicAccessVal = publicAccessAttr.AsBoolValueOrDefault(true, resource) + + } + + alertPolicyBlocks := module.GetReferencingResources(resource, "azurerm_mssql_server_security_alert_policy", "server_name") + for _, alertBlock := range alertPolicyBlocks { + a.alertPolicyIDs.Resolve(alertBlock.ID()) + alertPolicies = append(alertPolicies, adaptMSSQLSecurityAlertPolicy(alertBlock)) + } + + auditingPoliciesBlocks := module.GetReferencingResources(resource, "azurerm_mssql_server_extended_auditing_policy", "server_id") + if resource.HasChild("extended_auditing_policy") { + auditingPoliciesBlocks = append(auditingPoliciesBlocks, resource.GetBlocks("extended_auditing_policy")...) + } + + databasesRes := module.GetReferencingResources(resource, "azurerm_mssql_database", "server_id") + for _, databaseRes := range databasesRes { + dbAuditingBlocks := module.GetReferencingResources(databaseRes, "azurerm_mssql_database_extended_auditing_policy", "database_id") + auditingPoliciesBlocks = append(auditingPoliciesBlocks, dbAuditingBlocks...) + } + + for _, auditBlock := range auditingPoliciesBlocks { + a.auditingPolicyIDs.Resolve(auditBlock.ID()) + auditingPolicies = append(auditingPolicies, adaptMSSQLExtendedAuditingPolicy(auditBlock)) + } + + firewallRuleBlocks := module.GetReferencingResources(resource, "azurerm_sql_firewall_rule", "server_name") + firewallRuleBlocks = append(firewallRuleBlocks, module.GetReferencingResources(resource, "azurerm_mssql_firewall_rule", "server_id")...) + for _, firewallBlock := range firewallRuleBlocks { + a.firewallIDs.Resolve(firewallBlock.ID()) + firewallRules = append(firewallRules, adaptFirewallRule(firewallBlock)) + } + + return database.MSSQLServer{ + Metadata: resource.GetMetadata(), + Server: database.Server{ + Metadata: resource.GetMetadata(), + EnableSSLEnforcement: enableSSLEnforcementVal, + MinimumTLSVersion: minTLSVersionVal, + EnablePublicNetworkAccess: publicAccessVal, + FirewallRules: firewallRules, + }, + ExtendedAuditingPolicies: auditingPolicies, + SecurityAlertPolicies: alertPolicies, + } +} + +func (a *mysqlAdapter) adaptMySQLServer(resource *terraform.Block, module *terraform.Module) database.MySQLServer { + var firewallRules []database.FirewallRule + + enableSSLEnforcementAttr := resource.GetAttribute("ssl_enforcement_enabled") + enableSSLEnforcementVal := enableSSLEnforcementAttr.AsBoolValueOrDefault(false, resource) + + minTLSVersionAttr := resource.GetAttribute("ssl_minimal_tls_version_enforced") + minTLSVersionVal := minTLSVersionAttr.AsStringValueOrDefault("TLSEnforcementDisabled", resource) + + publicAccessAttr := resource.GetAttribute("public_network_access_enabled") + publicAccessVal := publicAccessAttr.AsBoolValueOrDefault(true, resource) + + firewallRuleBlocks := module.GetReferencingResources(resource, "azurerm_mysql_firewall_rule", "server_name") + for _, firewallBlock := range firewallRuleBlocks { + a.firewallIDs.Resolve(firewallBlock.ID()) + firewallRules = append(firewallRules, adaptFirewallRule(firewallBlock)) + } + + return database.MySQLServer{ + Metadata: resource.GetMetadata(), + Server: database.Server{ + Metadata: resource.GetMetadata(), + EnableSSLEnforcement: enableSSLEnforcementVal, + MinimumTLSVersion: minTLSVersionVal, + EnablePublicNetworkAccess: publicAccessVal, + FirewallRules: firewallRules, + }, + } +} + +func (a *mariaDBAdapter) adaptMariaDBServer(resource *terraform.Block, module *terraform.Module) database.MariaDBServer { + var firewallRules []database.FirewallRule + + enableSSLEnforcementAttr := resource.GetAttribute("ssl_enforcement_enabled") + enableSSLEnforcementVal := enableSSLEnforcementAttr.AsBoolValueOrDefault(false, resource) + + publicAccessAttr := resource.GetAttribute("public_network_access_enabled") + publicAccessVal := publicAccessAttr.AsBoolValueOrDefault(true, resource) + + firewallRuleBlocks := module.GetReferencingResources(resource, "azurerm_mariadb_firewall_rule", "server_name") + for _, firewallBlock := range firewallRuleBlocks { + a.firewallIDs.Resolve(firewallBlock.ID()) + firewallRules = append(firewallRules, adaptFirewallRule(firewallBlock)) + } + + return database.MariaDBServer{ + Metadata: resource.GetMetadata(), + Server: database.Server{ + Metadata: resource.GetMetadata(), + EnableSSLEnforcement: enableSSLEnforcementVal, + MinimumTLSVersion: iacTypes.StringDefault("", resource.GetMetadata()), + EnablePublicNetworkAccess: publicAccessVal, + FirewallRules: firewallRules, + }, + } +} + +func (a *postgresqlAdapter) adaptPostgreSQLServer(resource *terraform.Block, module *terraform.Module) database.PostgreSQLServer { + var firewallRules []database.FirewallRule + + enableSSLEnforcementAttr := resource.GetAttribute("ssl_enforcement_enabled") + enableSSLEnforcementVal := enableSSLEnforcementAttr.AsBoolValueOrDefault(false, resource) + + minTLSVersionAttr := resource.GetAttribute("ssl_minimal_tls_version_enforced") + minTLSVersionVal := minTLSVersionAttr.AsStringValueOrDefault("TLSEnforcementDisabled", resource) + + publicAccessAttr := resource.GetAttribute("public_network_access_enabled") + publicAccessVal := publicAccessAttr.AsBoolValueOrDefault(true, resource) + + firewallRuleBlocks := module.GetReferencingResources(resource, "azurerm_postgresql_firewall_rule", "server_name") + for _, firewallBlock := range firewallRuleBlocks { + a.firewallIDs.Resolve(firewallBlock.ID()) + firewallRules = append(firewallRules, adaptFirewallRule(firewallBlock)) + } + + configBlocks := module.GetReferencingResources(resource, "azurerm_postgresql_configuration", "server_name") + config := adaptPostgreSQLConfig(resource, configBlocks) + + return database.PostgreSQLServer{ + Metadata: resource.GetMetadata(), + Server: database.Server{ + Metadata: resource.GetMetadata(), + EnableSSLEnforcement: enableSSLEnforcementVal, + MinimumTLSVersion: minTLSVersionVal, + EnablePublicNetworkAccess: publicAccessVal, + FirewallRules: firewallRules, + }, + Config: config, + } +} + +func adaptPostgreSQLConfig(resource *terraform.Block, configBlocks []*terraform.Block) database.PostgresSQLConfig { + config := database.PostgresSQLConfig{ + Metadata: resource.GetMetadata(), + LogCheckpoints: iacTypes.BoolDefault(false, resource.GetMetadata()), + ConnectionThrottling: iacTypes.BoolDefault(false, resource.GetMetadata()), + LogConnections: iacTypes.BoolDefault(false, resource.GetMetadata()), + } + + for _, configBlock := range configBlocks { + + nameAttr := configBlock.GetAttribute("name") + valAttr := configBlock.GetAttribute("value") + + if nameAttr.Equals("log_checkpoints") { + config.LogCheckpoints = iacTypes.Bool(valAttr.Equals("on"), valAttr.GetMetadata()) + } + if nameAttr.Equals("connection_throttling") { + config.ConnectionThrottling = iacTypes.Bool(valAttr.Equals("on"), valAttr.GetMetadata()) + } + if nameAttr.Equals("log_connections") { + config.LogConnections = iacTypes.Bool(valAttr.Equals("on"), valAttr.GetMetadata()) + } + } + + return config +} + +func adaptMSSQLSecurityAlertPolicy(resource *terraform.Block) database.SecurityAlertPolicy { + + emailAddressesAttr := resource.GetAttribute("email_addresses") + disabledAlertsAttr := resource.GetAttribute("disabled_alerts") + + emailAccountAdminsAttr := resource.GetAttribute("email_account_admins") + emailAccountAdminsVal := emailAccountAdminsAttr.AsBoolValueOrDefault(false, resource) + + return database.SecurityAlertPolicy{ + Metadata: resource.GetMetadata(), + EmailAddresses: emailAddressesAttr.AsStringValues(), + DisabledAlerts: disabledAlertsAttr.AsStringValues(), + EmailAccountAdmins: emailAccountAdminsVal, + } +} + +func adaptFirewallRule(resource *terraform.Block) database.FirewallRule { + startIPAttr := resource.GetAttribute("start_ip_address") + startIPVal := startIPAttr.AsStringValueOrDefault("", resource) + + endIPAttr := resource.GetAttribute("end_ip_address") + endIPVal := endIPAttr.AsStringValueOrDefault("", resource) + + return database.FirewallRule{ + Metadata: resource.GetMetadata(), + StartIP: startIPVal, + EndIP: endIPVal, + } +} + +func adaptMSSQLExtendedAuditingPolicy(resource *terraform.Block) database.ExtendedAuditingPolicy { + retentionInDaysAttr := resource.GetAttribute("retention_in_days") + retentionInDaysVal := retentionInDaysAttr.AsIntValueOrDefault(0, resource) + + return database.ExtendedAuditingPolicy{ + Metadata: resource.GetMetadata(), + RetentionInDays: retentionInDaysVal, + } +} diff --git a/pkg/iac/adapters/terraform/azure/database/adapt_test.go b/pkg/iac/adapters/terraform/azure/database/adapt_test.go new file mode 100644 index 000000000000..9616659b30e3 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/database/adapt_test.go @@ -0,0 +1,454 @@ +package database + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/database" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected database.Database + }{ + { + name: "postgresql", + terraform: ` + resource "azurerm_postgresql_server" "example" { + name = "example" + + public_network_access_enabled = true + ssl_enforcement_enabled = true + ssl_minimal_tls_version_enforced = "TLS1_2" + } + + resource "azurerm_postgresql_configuration" "example" { + name = "log_connections" + resource_group_name = azurerm_resource_group.example.name + server_name = azurerm_postgresql_server.example.name + value = "on" + } + + resource "azurerm_postgresql_configuration" "example" { + name = "log_checkpoints" + resource_group_name = azurerm_resource_group.example.name + server_name = azurerm_postgresql_server.example.name + value = "on" + } + + resource "azurerm_postgresql_configuration" "example" { + name = "connection_throttling" + resource_group_name = azurerm_resource_group.example.name + server_name = azurerm_postgresql_server.example.name + value = "on" + } + + resource "azurerm_postgresql_firewall_rule" "example" { + name = "office" + resource_group_name = azurerm_resource_group.example.name + server_name = azurerm_postgresql_server.example.name + start_ip_address = "40.112.8.12" + end_ip_address = "40.112.8.12" + } +`, + expected: database.Database{ + PostgreSQLServers: []database.PostgreSQLServer{ + { + Metadata: iacTypes.NewTestMetadata(), + Server: database.Server{ + Metadata: iacTypes.NewTestMetadata(), + EnableSSLEnforcement: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + MinimumTLSVersion: iacTypes.String("TLS1_2", iacTypes.NewTestMetadata()), + EnablePublicNetworkAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + FirewallRules: []database.FirewallRule{ + { + Metadata: iacTypes.NewTestMetadata(), + StartIP: iacTypes.String("40.112.8.12", iacTypes.NewTestMetadata()), + EndIP: iacTypes.String("40.112.8.12", iacTypes.NewTestMetadata()), + }, + }, + }, + Config: database.PostgresSQLConfig{ + Metadata: iacTypes.NewTestMetadata(), + LogConnections: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + LogCheckpoints: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + ConnectionThrottling: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + { + name: "mariadb", + terraform: ` + resource "azurerm_mariadb_server" "example" { + name = "example-mariadb-server" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + + public_network_access_enabled = false + ssl_enforcement_enabled = true + } + + resource "azurerm_mariadb_firewall_rule" "example" { + name = "test-rule" + server_name = azurerm_mariadb_server.example.name + start_ip_address = "40.112.0.0" + end_ip_address = "40.112.255.255" + } +`, + expected: database.Database{ + MariaDBServers: []database.MariaDBServer{ + { + Metadata: iacTypes.NewTestMetadata(), + Server: database.Server{ + Metadata: iacTypes.NewTestMetadata(), + EnableSSLEnforcement: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + MinimumTLSVersion: iacTypes.String("", iacTypes.NewTestMetadata()), + EnablePublicNetworkAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + FirewallRules: []database.FirewallRule{ + { + Metadata: iacTypes.NewTestMetadata(), + StartIP: iacTypes.String("40.112.0.0", iacTypes.NewTestMetadata()), + EndIP: iacTypes.String("40.112.255.255", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }, + { + name: "mysql", + terraform: ` + resource "azurerm_mysql_server" "example" { + public_network_access_enabled = true + ssl_enforcement_enabled = true + ssl_minimal_tls_version_enforced = "TLS1_2" + } + + resource "azurerm_mysql_firewall_rule" "example" { + server_name = azurerm_mysql_server.example.name + start_ip_address = "40.112.8.12" + end_ip_address = "40.112.8.12" + } + `, + expected: database.Database{ + MySQLServers: []database.MySQLServer{ + { + Metadata: iacTypes.NewTestMetadata(), + Server: database.Server{ + Metadata: iacTypes.NewTestMetadata(), + EnableSSLEnforcement: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + MinimumTLSVersion: iacTypes.String("TLS1_2", iacTypes.NewTestMetadata()), + EnablePublicNetworkAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + FirewallRules: []database.FirewallRule{ + { + Metadata: iacTypes.NewTestMetadata(), + StartIP: iacTypes.String("40.112.8.12", iacTypes.NewTestMetadata()), + EndIP: iacTypes.String("40.112.8.12", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }, + { + name: "ms sql", + terraform: ` + resource "azurerm_mssql_server" "example" { + name = "mssqlserver" + minimum_tls_version = "1.2" + public_network_access_enabled = false + } + + resource "azurerm_mssql_firewall_rule" "example" { + name = "FirewallRule1" + server_id = azurerm_mssql_server.example.id + start_ip_address = "10.0.17.62" + end_ip_address = "10.0.17.62" + } + + resource "azurerm_mssql_server_security_alert_policy" "example" { + resource_group_name = azurerm_resource_group.example.name + server_name = azurerm_mssql_server.example.name + disabled_alerts = [ + "Sql_Injection", + "Data_Exfiltration" + ] + email_account_admins = true + email_addresses = [ + "example@example.com" + ] + } + + resource "azurerm_mssql_server_extended_auditing_policy" "example" { + server_id = azurerm_mssql_server.example.id + retention_in_days = 6 + } + `, + expected: database.Database{ + MSSQLServers: []database.MSSQLServer{ + { + Metadata: iacTypes.NewTestMetadata(), + Server: database.Server{ + Metadata: iacTypes.NewTestMetadata(), + MinimumTLSVersion: iacTypes.String("1.2", iacTypes.NewTestMetadata()), + EnablePublicNetworkAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + EnableSSLEnforcement: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + FirewallRules: []database.FirewallRule{ + { + Metadata: iacTypes.NewTestMetadata(), + StartIP: iacTypes.String("10.0.17.62", iacTypes.NewTestMetadata()), + EndIP: iacTypes.String("10.0.17.62", iacTypes.NewTestMetadata()), + }, + }, + }, + ExtendedAuditingPolicies: []database.ExtendedAuditingPolicy{ + { + Metadata: iacTypes.NewTestMetadata(), + RetentionInDays: iacTypes.Int(6, iacTypes.NewTestMetadata()), + }, + }, + SecurityAlertPolicies: []database.SecurityAlertPolicy{ + { + Metadata: iacTypes.NewTestMetadata(), + EmailAddresses: []iacTypes.StringValue{ + iacTypes.String("example@example.com", iacTypes.NewTestMetadata()), + }, + DisabledAlerts: []iacTypes.StringValue{ + iacTypes.String("Sql_Injection", iacTypes.NewTestMetadata()), + iacTypes.String("Data_Exfiltration", iacTypes.NewTestMetadata()), + }, + EmailAccountAdmins: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "azurerm_postgresql_server" "example" { + public_network_access_enabled = true + ssl_enforcement_enabled = true + ssl_minimal_tls_version_enforced = "TLS1_2" + } + + resource "azurerm_postgresql_configuration" "example" { + name = "log_connections" + server_name = azurerm_postgresql_server.example.name + value = "on" + } + + resource "azurerm_postgresql_configuration" "example" { + name = "log_checkpoints" + server_name = azurerm_postgresql_server.example.name + value = "on" + } + + resource "azurerm_postgresql_configuration" "example" { + name = "connection_throttling" + server_name = azurerm_postgresql_server.example.name + value = "on" + } + + resource "azurerm_postgresql_firewall_rule" "example" { + name = "office" + server_name = azurerm_postgresql_server.example.name + start_ip_address = "40.112.8.12" + end_ip_address = "40.112.8.12" + } + + resource "azurerm_mariadb_server" "example" { + public_network_access_enabled = false + ssl_enforcement_enabled = true + } + + resource "azurerm_mariadb_firewall_rule" "example" { + name = "test-rule" + server_name = azurerm_mariadb_server.example.name + start_ip_address = "40.112.0.0" + end_ip_address = "40.112.255.255" + } + + resource "azurerm_mysql_server" "example" { + public_network_access_enabled = true + ssl_enforcement_enabled = true + ssl_minimal_tls_version_enforced = "TLS1_2" + } + + resource "azurerm_mysql_firewall_rule" "example" { + server_name = azurerm_mysql_server.example.name + start_ip_address = "40.112.8.12" + end_ip_address = "40.112.8.12" + } + + resource "azurerm_mssql_server" "example" { + name = "mssqlserver" + public_network_access_enabled = false + minimum_tls_version = "1.2" + } + + resource "azurerm_mssql_firewall_rule" "example" { + name = "FirewallRule1" + server_id = azurerm_mssql_server.example.id + start_ip_address = "10.0.17.62" + end_ip_address = "10.0.17.62" + } + + resource "azurerm_mssql_server_security_alert_policy" "example" { + server_name = azurerm_mssql_server.example.name + disabled_alerts = [ + "Sql_Injection", + "Data_Exfiltration" + ] + email_account_admins = true + email_addresses = [ + "example@example.com" + ] + } + + resource "azurerm_mssql_server_extended_auditing_policy" "example" { + server_id = azurerm_mssql_server.example.id + retention_in_days = 6 + } + ` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.PostgreSQLServers, 1) + require.Len(t, adapted.MariaDBServers, 1) + require.Len(t, adapted.MySQLServers, 1) + require.Len(t, adapted.MSSQLServers, 1) + + postgres := adapted.PostgreSQLServers[0] + mariadb := adapted.MariaDBServers[0] + mysql := adapted.MySQLServers[0] + mssql := adapted.MSSQLServers[0] + + assert.Equal(t, 2, postgres.Metadata.Range().GetStartLine()) + assert.Equal(t, 6, postgres.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, postgres.EnablePublicNetworkAccess.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, postgres.EnablePublicNetworkAccess.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 4, postgres.EnableSSLEnforcement.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, postgres.EnableSSLEnforcement.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 5, postgres.MinimumTLSVersion.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 5, postgres.MinimumTLSVersion.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, postgres.Config.LogConnections.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 11, postgres.Config.LogConnections.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 17, postgres.Config.LogCheckpoints.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 17, postgres.Config.LogCheckpoints.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 23, postgres.Config.ConnectionThrottling.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 23, postgres.Config.ConnectionThrottling.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 26, postgres.FirewallRules[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 31, postgres.FirewallRules[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 29, postgres.FirewallRules[0].StartIP.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 29, postgres.FirewallRules[0].StartIP.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 30, postgres.FirewallRules[0].EndIP.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 30, postgres.FirewallRules[0].EndIP.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 33, mariadb.Metadata.Range().GetStartLine()) + assert.Equal(t, 36, mariadb.Metadata.Range().GetEndLine()) + + assert.Equal(t, 34, mariadb.EnablePublicNetworkAccess.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 34, mariadb.EnablePublicNetworkAccess.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 35, mariadb.EnableSSLEnforcement.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 35, mariadb.EnableSSLEnforcement.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 38, mariadb.FirewallRules[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 43, mariadb.FirewallRules[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 41, mariadb.FirewallRules[0].StartIP.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 41, mariadb.FirewallRules[0].StartIP.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 42, mariadb.FirewallRules[0].EndIP.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 42, mariadb.FirewallRules[0].EndIP.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 45, mysql.Metadata.Range().GetStartLine()) + assert.Equal(t, 49, mysql.Metadata.Range().GetEndLine()) + + assert.Equal(t, 46, mysql.EnablePublicNetworkAccess.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 46, mysql.EnablePublicNetworkAccess.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 47, mysql.EnableSSLEnforcement.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 47, mysql.EnableSSLEnforcement.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 48, mysql.MinimumTLSVersion.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 48, mysql.MinimumTLSVersion.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 51, mysql.FirewallRules[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 55, mysql.FirewallRules[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 53, mysql.FirewallRules[0].StartIP.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 53, mysql.FirewallRules[0].StartIP.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 54, mysql.FirewallRules[0].EndIP.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 54, mysql.FirewallRules[0].EndIP.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 57, mssql.Metadata.Range().GetStartLine()) + assert.Equal(t, 61, mssql.Metadata.Range().GetEndLine()) + + assert.Equal(t, 59, mssql.EnablePublicNetworkAccess.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 59, mssql.EnablePublicNetworkAccess.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 60, mssql.MinimumTLSVersion.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 60, mssql.MinimumTLSVersion.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 63, mssql.FirewallRules[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 68, mssql.FirewallRules[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 66, mssql.FirewallRules[0].StartIP.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 66, mssql.FirewallRules[0].StartIP.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 67, mssql.FirewallRules[0].EndIP.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 67, mssql.FirewallRules[0].EndIP.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 70, mssql.SecurityAlertPolicies[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 80, mssql.SecurityAlertPolicies[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 72, mssql.SecurityAlertPolicies[0].DisabledAlerts[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 75, mssql.SecurityAlertPolicies[0].DisabledAlerts[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 76, mssql.SecurityAlertPolicies[0].EmailAccountAdmins.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 76, mssql.SecurityAlertPolicies[0].EmailAccountAdmins.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 77, mssql.SecurityAlertPolicies[0].EmailAddresses[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 79, mssql.SecurityAlertPolicies[0].EmailAddresses[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 82, mssql.ExtendedAuditingPolicies[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 85, mssql.ExtendedAuditingPolicies[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 84, mssql.ExtendedAuditingPolicies[0].RetentionInDays.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 84, mssql.ExtendedAuditingPolicies[0].RetentionInDays.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/azure/datafactory/adapt.go b/pkg/iac/adapters/terraform/azure/datafactory/adapt.go new file mode 100644 index 000000000000..2dfc108ee3e9 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/datafactory/adapt.go @@ -0,0 +1,33 @@ +package datafactory + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/datafactory" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) datafactory.DataFactory { + return datafactory.DataFactory{ + DataFactories: adaptFactories(modules), + } +} + +func adaptFactories(modules terraform.Modules) []datafactory.Factory { + var factories []datafactory.Factory + + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_data_factory") { + factories = append(factories, adaptFactory(resource)) + } + } + return factories +} + +func adaptFactory(resource *terraform.Block) datafactory.Factory { + enablePublicNetworkAttr := resource.GetAttribute("public_network_enabled") + enablePublicNetworkVal := enablePublicNetworkAttr.AsBoolValueOrDefault(true, resource) + + return datafactory.Factory{ + Metadata: resource.GetMetadata(), + EnablePublicNetwork: enablePublicNetworkVal, + } +} diff --git a/pkg/iac/adapters/terraform/azure/datafactory/adapt_test.go b/pkg/iac/adapters/terraform/azure/datafactory/adapt_test.go new file mode 100644 index 000000000000..cefe5709d42e --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/datafactory/adapt_test.go @@ -0,0 +1,78 @@ +package datafactory + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/datafactory" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptFactory(t *testing.T) { + tests := []struct { + name string + terraform string + expected datafactory.Factory + }{ + { + name: "defined", + terraform: ` + resource "azurerm_data_factory" "example" { + name = "example" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + public_network_enabled = false + } +`, + expected: datafactory.Factory{ + Metadata: iacTypes.NewTestMetadata(), + EnablePublicNetwork: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + { + name: "default", + terraform: ` + resource "azurerm_data_factory" "example" { + name = "example" + } +`, + expected: datafactory.Factory{ + Metadata: iacTypes.NewTestMetadata(), + EnablePublicNetwork: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptFactory(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "azurerm_data_factory" "example" { + name = "example" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + public_network_enabled = false + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.DataFactories, 1) + dataFactory := adapted.DataFactories[0] + + assert.Equal(t, 6, dataFactory.EnablePublicNetwork.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, dataFactory.EnablePublicNetwork.GetMetadata().Range().GetEndLine()) + +} diff --git a/pkg/iac/adapters/terraform/azure/datalake/adapt.go b/pkg/iac/adapters/terraform/azure/datalake/adapt.go new file mode 100644 index 000000000000..3f81d8925fd8 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/datalake/adapt.go @@ -0,0 +1,38 @@ +package datalake + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/datalake" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) datalake.DataLake { + return datalake.DataLake{ + Stores: adaptStores(modules), + } +} + +func adaptStores(modules terraform.Modules) []datalake.Store { + var stores []datalake.Store + + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_data_lake_store") { + stores = append(stores, adaptStore(resource)) + } + } + return stores +} + +func adaptStore(resource *terraform.Block) datalake.Store { + store := datalake.Store{ + Metadata: resource.GetMetadata(), + EnableEncryption: types.BoolDefault(true, resource.GetMetadata()), + } + encryptionStateAttr := resource.GetAttribute("encryption_state") + if encryptionStateAttr.Equals("Disabled") { + store.EnableEncryption = types.Bool(false, encryptionStateAttr.GetMetadata()) + } else if encryptionStateAttr.Equals("Enabled") { + store.EnableEncryption = types.Bool(true, encryptionStateAttr.GetMetadata()) + } + return store +} diff --git a/pkg/iac/adapters/terraform/azure/datalake/adapt_test.go b/pkg/iac/adapters/terraform/azure/datalake/adapt_test.go new file mode 100644 index 000000000000..c1ca5410384f --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/datalake/adapt_test.go @@ -0,0 +1,82 @@ +package datalake + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/datalake" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptStore(t *testing.T) { + tests := []struct { + name string + terraform string + expected datalake.Store + }{ + { + name: "enabled", + terraform: ` + resource "azurerm_data_lake_store" "good_example" { + encryption_state = "Enabled" + } +`, + expected: datalake.Store{ + Metadata: iacTypes.NewTestMetadata(), + EnableEncryption: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + { + name: "disabled", + terraform: ` + resource "azurerm_data_lake_store" "good_example" { + encryption_state = "Disabled" + } +`, + expected: datalake.Store{ + Metadata: iacTypes.NewTestMetadata(), + EnableEncryption: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + { + name: "enabled by default", + terraform: ` + resource "azurerm_data_lake_store" "good_example" { + } +`, + expected: datalake.Store{ + Metadata: iacTypes.NewTestMetadata(), + EnableEncryption: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptStore(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "azurerm_data_lake_store" "good_example" { + encryption_state = "Disabled" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Stores, 1) + store := adapted.Stores[0] + + assert.Equal(t, 3, store.EnableEncryption.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, store.EnableEncryption.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/azure/keyvault/adapt.go b/pkg/iac/adapters/terraform/azure/keyvault/adapt.go new file mode 100644 index 000000000000..87461bfbeedc --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/keyvault/adapt.go @@ -0,0 +1,157 @@ +package keyvault + +import ( + "time" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/keyvault" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) keyvault.KeyVault { + adapter := adapter{ + vaultSecretIDs: modules.GetChildResourceIDMapByType("azurerm_key_vault_secret"), + vaultKeyIDs: modules.GetChildResourceIDMapByType("azurerm_key_vault_key"), + } + + return keyvault.KeyVault{ + Vaults: adapter.adaptVaults(modules), + } +} + +type adapter struct { + vaultSecretIDs terraform.ResourceIDResolutions + vaultKeyIDs terraform.ResourceIDResolutions +} + +func (a *adapter) adaptVaults(modules terraform.Modules) []keyvault.Vault { + + var vaults []keyvault.Vault + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_key_vault") { + vaults = append(vaults, a.adaptVault(resource, module)) + + } + } + + orphanResources := modules.GetResourceByIDs(a.vaultSecretIDs.Orphans()...) + + if len(orphanResources) > 0 { + orphanage := keyvault.Vault{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Secrets: nil, + Keys: nil, + EnablePurgeProtection: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + SoftDeleteRetentionDays: iacTypes.IntDefault(0, iacTypes.NewUnmanagedMetadata()), + NetworkACLs: keyvault.NetworkACLs{ + Metadata: iacTypes.NewUnmanagedMetadata(), + DefaultAction: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + }, + } + for _, secretResource := range orphanResources { + orphanage.Secrets = append(orphanage.Secrets, adaptSecret(secretResource)) + } + vaults = append(vaults, orphanage) + } + + orphanResources = modules.GetResourceByIDs(a.vaultKeyIDs.Orphans()...) + + if len(orphanResources) > 0 { + orphanage := keyvault.Vault{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Secrets: nil, + Keys: nil, + EnablePurgeProtection: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + SoftDeleteRetentionDays: iacTypes.IntDefault(0, iacTypes.NewUnmanagedMetadata()), + NetworkACLs: keyvault.NetworkACLs{ + Metadata: iacTypes.NewUnmanagedMetadata(), + DefaultAction: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + }, + } + for _, secretResource := range orphanResources { + orphanage.Keys = append(orphanage.Keys, adaptKey(secretResource)) + } + vaults = append(vaults, orphanage) + } + + return vaults +} + +func (a *adapter) adaptVault(resource *terraform.Block, module *terraform.Module) keyvault.Vault { + var keys []keyvault.Key + var secrets []keyvault.Secret + + defaultActionVal := iacTypes.StringDefault("", resource.GetMetadata()) + + secretBlocks := module.GetReferencingResources(resource, "azurerm_key_vault_secret", "key_vault_id") + for _, secretBlock := range secretBlocks { + a.vaultSecretIDs.Resolve(secretBlock.ID()) + secrets = append(secrets, adaptSecret(secretBlock)) + } + + keyBlocks := module.GetReferencingResources(resource, "azurerm_key_vault_key", "key_vault_id") + for _, keyBlock := range keyBlocks { + a.vaultKeyIDs.Resolve(keyBlock.ID()) + keys = append(keys, adaptKey(keyBlock)) + } + + purgeProtectionAttr := resource.GetAttribute("purge_protection_enabled") + purgeProtectionVal := purgeProtectionAttr.AsBoolValueOrDefault(false, resource) + + softDeleteRetentionDaysAttr := resource.GetAttribute("soft_delete_retention_days") + softDeleteRetentionDaysVal := softDeleteRetentionDaysAttr.AsIntValueOrDefault(0, resource) + + aclMetadata := iacTypes.NewUnmanagedMetadata() + if aclBlock := resource.GetBlock("network_acls"); aclBlock.IsNotNil() { + aclMetadata = aclBlock.GetMetadata() + defaultActionAttr := aclBlock.GetAttribute("default_action") + defaultActionVal = defaultActionAttr.AsStringValueOrDefault("", resource.GetBlock("network_acls")) + } + + return keyvault.Vault{ + Metadata: resource.GetMetadata(), + Secrets: secrets, + Keys: keys, + EnablePurgeProtection: purgeProtectionVal, + SoftDeleteRetentionDays: softDeleteRetentionDaysVal, + NetworkACLs: keyvault.NetworkACLs{ + Metadata: aclMetadata, + DefaultAction: defaultActionVal, + }, + } +} + +func adaptSecret(resource *terraform.Block) keyvault.Secret { + contentTypeAttr := resource.GetAttribute("content_type") + contentTypeVal := contentTypeAttr.AsStringValueOrDefault("", resource) + + return keyvault.Secret{ + Metadata: resource.GetMetadata(), + ContentType: contentTypeVal, + ExpiryDate: resolveExpiryDate(resource), + } +} + +func adaptKey(resource *terraform.Block) keyvault.Key { + + return keyvault.Key{ + Metadata: resource.GetMetadata(), + ExpiryDate: resolveExpiryDate(resource), + } +} + +func resolveExpiryDate(resource *terraform.Block) iacTypes.TimeValue { + expiryDateAttr := resource.GetAttribute("expiration_date") + expiryDateVal := iacTypes.TimeDefault(time.Time{}, resource.GetMetadata()) + + if expiryDateAttr.IsString() { + expiryDateString := expiryDateAttr.Value().AsString() + if expiryDate, err := time.Parse(time.RFC3339, expiryDateString); err == nil { + expiryDateVal = iacTypes.Time(expiryDate, expiryDateAttr.GetMetadata()) + } + } else if expiryDateAttr.IsNotNil() { + expiryDateVal = iacTypes.TimeUnresolvable(expiryDateAttr.GetMetadata()) + } + + return expiryDateVal +} diff --git a/pkg/iac/adapters/terraform/azure/keyvault/adapt_test.go b/pkg/iac/adapters/terraform/azure/keyvault/adapt_test.go new file mode 100644 index 000000000000..b21d25b12c70 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/keyvault/adapt_test.go @@ -0,0 +1,270 @@ +package keyvault + +import ( + "testing" + "time" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/keyvault" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected keyvault.KeyVault + }{ + { + name: "defined", + terraform: ` + resource "azurerm_key_vault" "example" { + name = "examplekeyvault" + enabled_for_disk_encryption = true + soft_delete_retention_days = 7 + purge_protection_enabled = true + + network_acls { + bypass = "AzureServices" + default_action = "Deny" + } + } +`, + expected: keyvault.KeyVault{ + Vaults: []keyvault.Vault{ + { + Metadata: iacTypes.NewTestMetadata(), + EnablePurgeProtection: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + SoftDeleteRetentionDays: iacTypes.Int(7, iacTypes.NewTestMetadata()), + NetworkACLs: keyvault.NetworkACLs{ + Metadata: iacTypes.NewTestMetadata(), + DefaultAction: iacTypes.String("Deny", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + { + name: "defaults", + terraform: ` + resource "azurerm_key_vault" "example" { + } +`, + expected: keyvault.KeyVault{ + Vaults: []keyvault.Vault{ + { + Metadata: iacTypes.NewTestMetadata(), + EnablePurgeProtection: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + SoftDeleteRetentionDays: iacTypes.Int(0, iacTypes.NewTestMetadata()), + NetworkACLs: keyvault.NetworkACLs{ + Metadata: iacTypes.NewTestMetadata(), + DefaultAction: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptSecret(t *testing.T) { + tests := []struct { + name string + terraform string + expected keyvault.Secret + }{ + { + name: "defaults", + terraform: ` + resource "azurerm_key_vault_secret" "example" { + } +`, + expected: keyvault.Secret{ + Metadata: iacTypes.NewTestMetadata(), + ContentType: iacTypes.String("", iacTypes.NewTestMetadata()), + ExpiryDate: iacTypes.Time(time.Time{}, iacTypes.NewTestMetadata()), + }, + }, + { + name: "defined", + terraform: ` + resource "azurerm_key_vault_secret" "example" { + content_type = "password" + expiration_date = "1982-12-31T00:00:00Z" + } +`, + expected: keyvault.Secret{ + Metadata: iacTypes.NewTestMetadata(), + ContentType: iacTypes.String("password", iacTypes.NewTestMetadata()), + ExpiryDate: iacTypes.Time(func(timeVal string) time.Time { + parsed, _ := time.Parse(time.RFC3339, timeVal) + return parsed + }("1982-12-31T00:00:00Z"), iacTypes.NewTestMetadata())}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptSecret(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptKey(t *testing.T) { + tests := []struct { + name string + terraform string + expected keyvault.Key + }{ + { + name: "defined", + terraform: ` + resource "azurerm_key_vault_key" "example" { + name = "generated-certificate" + expiration_date = "1982-12-31T00:00:00Z" + } +`, + expected: keyvault.Key{ + Metadata: iacTypes.NewTestMetadata(), + ExpiryDate: iacTypes.Time(func(timeVal string) time.Time { + parsed, _ := time.Parse(time.RFC3339, timeVal) + return parsed + }("1982-12-31T00:00:00Z"), iacTypes.NewTestMetadata()), + }, + }, + { + name: "defaults", + terraform: ` + resource "azurerm_key_vault_key" "example" { + } +`, + expected: keyvault.Key{ + Metadata: iacTypes.NewTestMetadata(), + ExpiryDate: iacTypes.Time(time.Time{}, iacTypes.NewTestMetadata()), + }, + }, + { + name: "expiration date refers to the resource", + terraform: ` +terraform { + required_version = ">=1.3.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">=3.0.0" + } + time = { + source = "hashicorp/time" + version = ">=0.9.0" + } + } +} + +resource "azurerm_key_vault" "this" { + name = "keyvault" + location = "us-west" + resource_group_name = "resource-group" + tenant_id = "tenant-id" + sku_name = "Standard" +} + +resource "time_offset" "expiry" { + offset_years = 1 + base_rfc3339 = "YYYY-MM-DDTHH:MM:SSZ" +} + +resource "azurerm_key_vault_key" "this" { + name = "key" + key_vault_id = azurerm_key_vault.this.id + key_type = "RSA" + key_size = 2048 + key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"] + expiration_date = time_offset.expiry.rfc3339 +} +`, + expected: keyvault.Key{ + Metadata: iacTypes.NewTestMetadata(), + ExpiryDate: iacTypes.TimeUnresolvable(iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptKey(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "azurerm_key_vault" "example" { + name = "examplekeyvault" + enabled_for_disk_encryption = true + soft_delete_retention_days = 7 + purge_protection_enabled = true + + network_acls { + bypass = "AzureServices" + default_action = "Deny" + } + } + + resource "azurerm_key_vault_key" "example" { + key_vault_id = azurerm_key_vault.example.id + name = "generated-certificate" + expiration_date = "1982-12-31T00:00:00Z" + } + + resource "azurerm_key_vault_secret" "example" { + key_vault_id = azurerm_key_vault.example.id + content_type = "password" + expiration_date = "1982-12-31T00:00:00Z" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Vaults, 1) + require.Len(t, adapted.Vaults[0].Keys, 1) + require.Len(t, adapted.Vaults[0].Secrets, 1) + + vault := adapted.Vaults[0] + key := vault.Keys[0] + secret := vault.Secrets[0] + + assert.Equal(t, 5, vault.SoftDeleteRetentionDays.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 5, vault.SoftDeleteRetentionDays.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 6, vault.EnablePurgeProtection.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, vault.EnablePurgeProtection.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 10, vault.NetworkACLs.DefaultAction.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 10, vault.NetworkACLs.DefaultAction.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 17, key.ExpiryDate.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 17, key.ExpiryDate.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 22, secret.ContentType.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 22, secret.ContentType.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 23, secret.ExpiryDate.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 23, secret.ExpiryDate.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/azure/monitor/adapt.go b/pkg/iac/adapters/terraform/azure/monitor/adapt.go new file mode 100644 index 000000000000..a622bcdeaf41 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/monitor/adapt.go @@ -0,0 +1,56 @@ +package monitor + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/monitor" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) monitor.Monitor { + return monitor.Monitor{ + LogProfiles: adaptLogProfiles(modules), + } +} + +func adaptLogProfiles(modules terraform.Modules) []monitor.LogProfile { + var logProfiles []monitor.LogProfile + + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_monitor_log_profile") { + logProfiles = append(logProfiles, adaptLogProfile(resource)) + } + } + return logProfiles +} + +func adaptLogProfile(resource *terraform.Block) monitor.LogProfile { + + logProfile := monitor.LogProfile{ + Metadata: resource.GetMetadata(), + RetentionPolicy: monitor.RetentionPolicy{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + Days: iacTypes.IntDefault(0, resource.GetMetadata()), + }, + Categories: nil, + Locations: nil, + } + + if retentionPolicyBlock := resource.GetBlock("retention_policy"); retentionPolicyBlock.IsNotNil() { + logProfile.RetentionPolicy.Metadata = retentionPolicyBlock.GetMetadata() + enabledAttr := retentionPolicyBlock.GetAttribute("enabled") + logProfile.RetentionPolicy.Enabled = enabledAttr.AsBoolValueOrDefault(false, resource) + daysAttr := retentionPolicyBlock.GetAttribute("days") + logProfile.RetentionPolicy.Days = daysAttr.AsIntValueOrDefault(0, resource) + } + + if categoriesAttr := resource.GetAttribute("categories"); categoriesAttr.IsNotNil() { + logProfile.Categories = categoriesAttr.AsStringValues() + } + + if locationsAttr := resource.GetAttribute("locations"); locationsAttr.IsNotNil() { + logProfile.Locations = locationsAttr.AsStringValues() + } + + return logProfile +} diff --git a/pkg/iac/adapters/terraform/azure/monitor/adapt_test.go b/pkg/iac/adapters/terraform/azure/monitor/adapt_test.go new file mode 100644 index 000000000000..a0cf262fca7c --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/monitor/adapt_test.go @@ -0,0 +1,127 @@ +package monitor + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/monitor" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptLogProfile(t *testing.T) { + tests := []struct { + name string + terraform string + expected monitor.LogProfile + }{ + { + name: "defined", + terraform: ` + resource "azurerm_monitor_log_profile" "example" { + categories = [ + "Action", + "Delete", + "Write", + ] + + retention_policy { + enabled = true + days = 365 + } + + locations = [ + "eastus", + "eastus2", + "southcentralus" + ] + } +`, + expected: monitor.LogProfile{ + Metadata: iacTypes.NewTestMetadata(), + Categories: []iacTypes.StringValue{ + iacTypes.String("Action", iacTypes.NewTestMetadata()), + iacTypes.String("Delete", iacTypes.NewTestMetadata()), + iacTypes.String("Write", iacTypes.NewTestMetadata()), + }, + RetentionPolicy: monitor.RetentionPolicy{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Days: iacTypes.Int(365, iacTypes.NewTestMetadata()), + }, + Locations: []iacTypes.StringValue{ + iacTypes.String("eastus", iacTypes.NewTestMetadata()), + iacTypes.String("eastus2", iacTypes.NewTestMetadata()), + iacTypes.String("southcentralus", iacTypes.NewTestMetadata()), + }, + }, + }, + { + name: "default", + terraform: ` + resource "azurerm_monitor_log_profile" "example" { + } +`, + expected: monitor.LogProfile{ + Metadata: iacTypes.NewTestMetadata(), + RetentionPolicy: monitor.RetentionPolicy{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Days: iacTypes.Int(0, iacTypes.NewTestMetadata()), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptLogProfile(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "azurerm_monitor_log_profile" "example" { + categories = [ + "Action", + "Delete", + "Write", + ] + + retention_policy { + enabled = true + days = 365 + } + + locations = [ + "eastus", + "eastus2", + "southcentralus" + ] + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.LogProfiles, 1) + logProfile := adapted.LogProfiles[0] + + assert.Equal(t, 3, logProfile.Categories[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, logProfile.Categories[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 10, logProfile.RetentionPolicy.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 10, logProfile.RetentionPolicy.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, logProfile.RetentionPolicy.Days.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 11, logProfile.RetentionPolicy.Days.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 14, logProfile.Locations[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 18, logProfile.Locations[0].GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/azure/network/adapt.go b/pkg/iac/adapters/terraform/azure/network/adapt.go new file mode 100644 index 000000000000..b2866cd9100a --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/network/adapt.go @@ -0,0 +1,218 @@ +package network + +import ( + "strconv" + "strings" + + "github.com/google/uuid" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/network" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) network.Network { + return network.Network{ + SecurityGroups: (&adapter{ + modules: modules, + groups: make(map[string]network.SecurityGroup), + }).adaptSecurityGroups(), + NetworkWatcherFlowLogs: adaptWatcherLogs(modules), + } +} + +type adapter struct { + modules terraform.Modules + groups map[string]network.SecurityGroup +} + +func (a *adapter) adaptSecurityGroups() []network.SecurityGroup { + + for _, module := range a.modules { + for _, resource := range module.GetResourcesByType("azurerm_network_security_group") { + a.adaptSecurityGroup(resource) + } + } + + for _, ruleBlock := range a.modules.GetResourcesByType("azurerm_network_security_rule") { + rule := a.adaptSGRule(ruleBlock) + + groupAttr := ruleBlock.GetAttribute("network_security_group_name") + if groupAttr.IsNotNil() { + if referencedBlock, err := a.modules.GetReferencedBlock(groupAttr, ruleBlock); err == nil { + if group, ok := a.groups[referencedBlock.ID()]; ok { + group.Rules = append(group.Rules, rule) + a.groups[referencedBlock.ID()] = group + continue + } + } + + } + + a.groups[uuid.NewString()] = network.SecurityGroup{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Rules: []network.SecurityGroupRule{rule}, + } + } + + var securityGroups []network.SecurityGroup + for _, group := range a.groups { + securityGroups = append(securityGroups, group) + } + + return securityGroups +} + +func adaptWatcherLogs(modules terraform.Modules) []network.NetworkWatcherFlowLog { + var watcherLogs []network.NetworkWatcherFlowLog + + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_network_watcher_flow_log") { + watcherLogs = append(watcherLogs, adaptWatcherLog(resource)) + } + } + return watcherLogs +} + +func (a *adapter) adaptSecurityGroup(resource *terraform.Block) { + var rules []network.SecurityGroupRule + for _, ruleBlock := range resource.GetBlocks("security_rule") { + rules = append(rules, a.adaptSGRule(ruleBlock)) + } + a.groups[resource.ID()] = network.SecurityGroup{ + Metadata: resource.GetMetadata(), + Rules: rules, + } +} + +func (a *adapter) adaptSGRule(ruleBlock *terraform.Block) network.SecurityGroupRule { + + rule := network.SecurityGroupRule{ + Metadata: ruleBlock.GetMetadata(), + Outbound: iacTypes.BoolDefault(false, ruleBlock.GetMetadata()), + Allow: iacTypes.BoolDefault(true, ruleBlock.GetMetadata()), + SourceAddresses: nil, + SourcePorts: nil, + DestinationAddresses: nil, + DestinationPorts: nil, + Protocol: ruleBlock.GetAttribute("protocol").AsStringValueOrDefault("", ruleBlock), + } + + accessAttr := ruleBlock.GetAttribute("access") + if accessAttr.Equals("Allow") { + rule.Allow = iacTypes.Bool(true, accessAttr.GetMetadata()) + } else if accessAttr.Equals("Deny") { + rule.Allow = iacTypes.Bool(false, accessAttr.GetMetadata()) + } + + directionAttr := ruleBlock.GetAttribute("direction") + if directionAttr.Equals("Inbound") { + rule.Outbound = iacTypes.Bool(false, directionAttr.GetMetadata()) + } else if directionAttr.Equals("Outbound") { + rule.Outbound = iacTypes.Bool(true, directionAttr.GetMetadata()) + } + + a.adaptSource(ruleBlock, &rule) + a.adaptDestination(ruleBlock, &rule) + + return rule +} + +func (a *adapter) adaptSource(ruleBlock *terraform.Block, rule *network.SecurityGroupRule) { + if sourceAddressAttr := ruleBlock.GetAttribute("source_address_prefix"); sourceAddressAttr.IsString() { + rule.SourceAddresses = append(rule.SourceAddresses, sourceAddressAttr.AsStringValueOrDefault("", ruleBlock)) + } else if sourceAddressPrefixesAttr := ruleBlock.GetAttribute("source_address_prefixes"); sourceAddressPrefixesAttr.IsNotNil() { + rule.SourceAddresses = append(rule.SourceAddresses, sourceAddressPrefixesAttr.AsStringValues()...) + } + + if sourcePortRangesAttr := ruleBlock.GetAttribute("source_port_ranges"); sourcePortRangesAttr.IsNotNil() { + ports := sourcePortRangesAttr.AsStringValues() + for _, value := range ports { + rule.SourcePorts = append(rule.SourcePorts, expandRange(value.Value(), value.GetMetadata())) + } + } else if sourcePortRangeAttr := ruleBlock.GetAttribute("source_port_range"); sourcePortRangeAttr.IsString() { + rule.SourcePorts = append(rule.SourcePorts, expandRange(sourcePortRangeAttr.Value().AsString(), sourcePortRangeAttr.GetMetadata())) + } else if sourcePortRangeAttr := ruleBlock.GetAttribute("source_port_range"); sourcePortRangeAttr.IsNumber() { + f := sourcePortRangeAttr.AsNumber() + rule.SourcePorts = append(rule.SourcePorts, network.PortRange{ + Metadata: sourcePortRangeAttr.GetMetadata(), + Start: int(f), + End: int(f), + }) + } +} + +func (a *adapter) adaptDestination(ruleBlock *terraform.Block, rule *network.SecurityGroupRule) { + if destAddressAttr := ruleBlock.GetAttribute("destination_address_prefix"); destAddressAttr.IsString() { + rule.DestinationAddresses = append(rule.DestinationAddresses, destAddressAttr.AsStringValueOrDefault("", ruleBlock)) + } else if destAddressPrefixesAttr := ruleBlock.GetAttribute("destination_address_prefixes"); destAddressPrefixesAttr.IsNotNil() { + rule.DestinationAddresses = append(rule.DestinationAddresses, destAddressPrefixesAttr.AsStringValues()...) + } + + if destPortRangesAttr := ruleBlock.GetAttribute("destination_port_ranges"); destPortRangesAttr.IsNotNil() { + ports := destPortRangesAttr.AsStringValues() + for _, value := range ports { + rule.DestinationPorts = append(rule.DestinationPorts, expandRange(value.Value(), destPortRangesAttr.GetMetadata())) + } + } else if destPortRangeAttr := ruleBlock.GetAttribute("destination_port_range"); destPortRangeAttr.IsString() { + rule.DestinationPorts = append(rule.DestinationPorts, expandRange(destPortRangeAttr.Value().AsString(), destPortRangeAttr.GetMetadata())) + } else if destPortRangeAttr := ruleBlock.GetAttribute("destination_port_range"); destPortRangeAttr.IsNumber() { + f := destPortRangeAttr.AsNumber() + rule.DestinationPorts = append(rule.DestinationPorts, network.PortRange{ + Metadata: destPortRangeAttr.GetMetadata(), + Start: int(f), + End: int(f), + }) + } +} + +func expandRange(r string, m iacTypes.Metadata) network.PortRange { + start := 0 + end := 65535 + switch { + case r == "*": + case strings.Contains(r, "-"): + if parts := strings.Split(r, "-"); len(parts) == 2 { + if p1, err := strconv.ParseInt(parts[0], 10, 32); err == nil { + start = int(p1) + } + if p2, err := strconv.ParseInt(parts[1], 10, 32); err == nil { + end = int(p2) + } + } + default: + if val, err := strconv.ParseInt(r, 10, 32); err == nil { + start = int(val) + end = int(val) + } + } + + return network.PortRange{ + Metadata: m, + Start: start, + End: end, + } +} + +func adaptWatcherLog(resource *terraform.Block) network.NetworkWatcherFlowLog { + flowLog := network.NetworkWatcherFlowLog{ + Metadata: resource.GetMetadata(), + RetentionPolicy: network.RetentionPolicy{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + Days: iacTypes.IntDefault(0, resource.GetMetadata()), + }, + } + + if retentionPolicyBlock := resource.GetBlock("retention_policy"); retentionPolicyBlock.IsNotNil() { + flowLog.RetentionPolicy.Metadata = retentionPolicyBlock.GetMetadata() + + enabledAttr := retentionPolicyBlock.GetAttribute("enabled") + flowLog.RetentionPolicy.Enabled = enabledAttr.AsBoolValueOrDefault(false, retentionPolicyBlock) + + daysAttr := retentionPolicyBlock.GetAttribute("days") + flowLog.RetentionPolicy.Days = daysAttr.AsIntValueOrDefault(0, retentionPolicyBlock) + } + + return flowLog +} diff --git a/pkg/iac/adapters/terraform/azure/network/adapt_test.go b/pkg/iac/adapters/terraform/azure/network/adapt_test.go new file mode 100644 index 000000000000..a6abf380145c --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/network/adapt_test.go @@ -0,0 +1,261 @@ +package network + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/network" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected network.Network + }{ + { + name: "defined", + terraform: ` + resource "azurerm_network_security_rule" "example" { + name = "example_security_rule" + network_security_group_name = azurerm_network_security_group.example.name + direction = "Inbound" + access = "Allow" + protocol = "TCP" + source_port_range = "*" + destination_port_ranges = ["3389"] + source_address_prefix = "4.53.160.75" + destination_address_prefix = "*" + } + + resource "azurerm_network_security_group" "example" { + name = "tf-appsecuritygroup" + } + + resource "azurerm_network_watcher_flow_log" "example" { + resource_group_name = azurerm_resource_group.example.name + name = "example-log" + + retention_policy { + enabled = true + days = 7 + } + } +`, + expected: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: iacTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: iacTypes.NewTestMetadata(), + Outbound: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Allow: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + SourceAddresses: []iacTypes.StringValue{ + iacTypes.String("4.53.160.75", iacTypes.NewTestMetadata()), + }, + DestinationAddresses: []iacTypes.StringValue{ + iacTypes.String("*", iacTypes.NewTestMetadata()), + }, + SourcePorts: []network.PortRange{ + { + Metadata: iacTypes.NewTestMetadata(), + Start: 0, + End: 65535, + }, + }, + DestinationPorts: []network.PortRange{ + { + Metadata: iacTypes.NewTestMetadata(), + Start: 3389, + End: 3389, + }, + }, + Protocol: iacTypes.String("TCP", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + NetworkWatcherFlowLogs: []network.NetworkWatcherFlowLog{ + { + Metadata: iacTypes.NewTestMetadata(), + RetentionPolicy: network.RetentionPolicy{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Days: iacTypes.Int(7, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + { + name: "defaults", + terraform: ` + resource "azurerm_network_security_group" "example" { + name = "tf-appsecuritygroup" + security_rule { + } + } +`, + expected: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: iacTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: iacTypes.NewTestMetadata(), + Outbound: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Allow: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Protocol: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptWatcherLog(t *testing.T) { + tests := []struct { + name string + terraform string + expected network.NetworkWatcherFlowLog + }{ + { + name: "defined", + terraform: ` + resource "azurerm_network_watcher_flow_log" "watcher" { + retention_policy { + enabled = true + days = 90 + } + } +`, + expected: network.NetworkWatcherFlowLog{ + Metadata: iacTypes.NewTestMetadata(), + RetentionPolicy: network.RetentionPolicy{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Days: iacTypes.Int(90, iacTypes.NewTestMetadata()), + }, + }, + }, + { + name: "defaults", + terraform: ` + resource "azurerm_network_watcher_flow_log" "watcher" { + retention_policy { + } + } +`, + expected: network.NetworkWatcherFlowLog{ + Metadata: iacTypes.NewTestMetadata(), + RetentionPolicy: network.RetentionPolicy{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Days: iacTypes.Int(0, iacTypes.NewTestMetadata()), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptWatcherLog(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "azurerm_network_security_group" "example" { + name = "tf-appsecuritygroup" + } + + resource "azurerm_network_security_rule" "example" { + name = "example_security_rule" + network_security_group_name = azurerm_network_security_group.example.name + direction = "Inbound" + access = "Allow" + protocol = "TCP" + source_port_range = "*" + destination_port_ranges = ["3389"] + source_address_prefix = "4.53.160.75" + destination_address_prefix = "*" + } + + resource "azurerm_network_watcher_flow_log" "example" { + resource_group_name = azurerm_resource_group.example.name + name = "example-log" + + retention_policy { + enabled = true + days = 7 + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.SecurityGroups, 1) + require.Len(t, adapted.NetworkWatcherFlowLogs, 1) + + securityGroup := adapted.SecurityGroups[0] + rule := securityGroup.Rules[0] + watcher := adapted.NetworkWatcherFlowLogs[0] + + assert.Equal(t, 2, securityGroup.Metadata.Range().GetStartLine()) + assert.Equal(t, 4, securityGroup.Metadata.Range().GetEndLine()) + + assert.Equal(t, 6, rule.Metadata.Range().GetStartLine()) + assert.Equal(t, 16, rule.Metadata.Range().GetEndLine()) + + assert.Equal(t, 9, rule.Outbound.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 9, rule.Outbound.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 10, rule.Allow.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 10, rule.Allow.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, rule.Protocol.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 11, rule.Protocol.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 12, rule.SourcePorts[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 12, rule.SourcePorts[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 13, rule.DestinationPorts[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 13, rule.DestinationPorts[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 14, rule.SourceAddresses[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 14, rule.SourceAddresses[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 15, rule.DestinationAddresses[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 15, rule.DestinationAddresses[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 18, watcher.Metadata.Range().GetStartLine()) + assert.Equal(t, 26, watcher.Metadata.Range().GetEndLine()) + + assert.Equal(t, 22, watcher.RetentionPolicy.Metadata.Range().GetStartLine()) + assert.Equal(t, 25, watcher.RetentionPolicy.Metadata.Range().GetEndLine()) + + assert.Equal(t, 23, watcher.RetentionPolicy.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 23, watcher.RetentionPolicy.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 24, watcher.RetentionPolicy.Days.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 24, watcher.RetentionPolicy.Days.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/azure/securitycenter/adapt.go b/pkg/iac/adapters/terraform/azure/securitycenter/adapt.go new file mode 100644 index 000000000000..2053e8e9e587 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/securitycenter/adapt.go @@ -0,0 +1,59 @@ +package securitycenter + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) securitycenter.SecurityCenter { + return securitycenter.SecurityCenter{ + Contacts: adaptContacts(modules), + Subscriptions: adaptSubscriptions(modules), + } +} + +func adaptContacts(modules terraform.Modules) []securitycenter.Contact { + var contacts []securitycenter.Contact + + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_security_center_contact") { + contacts = append(contacts, adaptContact(resource)) + } + } + return contacts +} + +func adaptSubscriptions(modules terraform.Modules) []securitycenter.SubscriptionPricing { + var subscriptions []securitycenter.SubscriptionPricing + + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_security_center_subscription_pricing") { + subscriptions = append(subscriptions, adaptSubscription(resource)) + } + } + return subscriptions +} + +func adaptContact(resource *terraform.Block) securitycenter.Contact { + enableAlertNotifAttr := resource.GetAttribute("alert_notifications") + enableAlertNotifVal := enableAlertNotifAttr.AsBoolValueOrDefault(false, resource) + + phoneAttr := resource.GetAttribute("phone") + phoneVal := phoneAttr.AsStringValueOrDefault("", resource) + + return securitycenter.Contact{ + Metadata: resource.GetMetadata(), + EnableAlertNotifications: enableAlertNotifVal, + Phone: phoneVal, + } +} + +func adaptSubscription(resource *terraform.Block) securitycenter.SubscriptionPricing { + tierAttr := resource.GetAttribute("tier") + tierVal := tierAttr.AsStringValueOrDefault("Free", resource) + + return securitycenter.SubscriptionPricing{ + Metadata: resource.GetMetadata(), + Tier: tierVal, + } +} diff --git a/pkg/iac/adapters/terraform/azure/securitycenter/adapt_test.go b/pkg/iac/adapters/terraform/azure/securitycenter/adapt_test.go new file mode 100644 index 000000000000..3b61ebecf0b9 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/securitycenter/adapt_test.go @@ -0,0 +1,136 @@ +package securitycenter + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptContact(t *testing.T) { + tests := []struct { + name string + terraform string + expected securitycenter.Contact + }{ + { + name: "defined", + terraform: ` + resource "azurerm_security_center_contact" "example" { + phone = "+1-555-555-5555" + alert_notifications = true + } +`, + expected: securitycenter.Contact{ + Metadata: iacTypes.NewTestMetadata(), + EnableAlertNotifications: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Phone: iacTypes.String("+1-555-555-5555", iacTypes.NewTestMetadata()), + }, + }, + { + name: "defaults", + terraform: ` + resource "azurerm_security_center_contact" "example" { + } +`, + expected: securitycenter.Contact{ + Metadata: iacTypes.NewTestMetadata(), + EnableAlertNotifications: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Phone: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptContact(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptSubscription(t *testing.T) { + tests := []struct { + name string + terraform string + expected securitycenter.SubscriptionPricing + }{ + { + name: "free tier", + terraform: ` + resource "azurerm_security_center_subscription_pricing" "example" { + tier = "Free" + }`, + expected: securitycenter.SubscriptionPricing{ + Metadata: iacTypes.NewTestMetadata(), + Tier: iacTypes.String("Free", iacTypes.NewTestMetadata()), + }, + }, + { + name: "default - free tier", + terraform: ` + resource "azurerm_security_center_subscription_pricing" "example" { + }`, + expected: securitycenter.SubscriptionPricing{ + Metadata: iacTypes.NewTestMetadata(), + Tier: iacTypes.String("Free", iacTypes.NewTestMetadata()), + }, + }, + { + name: "standard tier", + terraform: ` + resource "azurerm_security_center_subscription_pricing" "example" { + tier = "Standard" + }`, + expected: securitycenter.SubscriptionPricing{ + Metadata: iacTypes.NewTestMetadata(), + Tier: iacTypes.String("Standard", iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptSubscription(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "azurerm_security_center_contact" "example" { + phone = "+1-555-555-5555" + alert_notifications = true + } + + resource "azurerm_security_center_subscription_pricing" "example" { + tier = "Standard" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Contacts, 1) + require.Len(t, adapted.Subscriptions, 1) + + contact := adapted.Contacts[0] + sub := adapted.Subscriptions[0] + + assert.Equal(t, 3, contact.Phone.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, contact.Phone.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 4, contact.EnableAlertNotifications.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, contact.EnableAlertNotifications.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 8, sub.Tier.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 8, sub.Tier.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/azure/storage/adapt.go b/pkg/iac/adapters/terraform/azure/storage/adapt.go new file mode 100644 index 000000000000..edc5f0029be7 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/storage/adapt.go @@ -0,0 +1,172 @@ +package storage + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/storage" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) storage.Storage { + accounts, containers, networkRules := adaptAccounts(modules) + + orphanAccount := storage.Account{ + Metadata: iacTypes.NewUnmanagedMetadata(), + NetworkRules: adaptOrphanNetworkRules(modules, networkRules), + EnforceHTTPS: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + Containers: adaptOrphanContainers(modules, containers), + QueueProperties: storage.QueueProperties{ + Metadata: iacTypes.NewUnmanagedMetadata(), + EnableLogging: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + }, + MinimumTLSVersion: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + } + + accounts = append(accounts, orphanAccount) + + return storage.Storage{ + Accounts: accounts, + } +} + +func adaptOrphanContainers(modules terraform.Modules, containers []string) (orphans []storage.Container) { + accountedFor := make(map[string]bool) + for _, container := range containers { + accountedFor[container] = true + } + for _, module := range modules { + for _, containerResource := range module.GetResourcesByType("azurerm_storage_container") { + if _, ok := accountedFor[containerResource.ID()]; ok { + continue + } + orphans = append(orphans, adaptContainer(containerResource)) + } + } + + return orphans +} + +func adaptOrphanNetworkRules(modules terraform.Modules, networkRules []string) (orphans []storage.NetworkRule) { + accountedFor := make(map[string]bool) + for _, networkRule := range networkRules { + accountedFor[networkRule] = true + } + + for _, module := range modules { + for _, networkRuleResource := range module.GetResourcesByType("azurerm_storage_account_network_rules") { + if _, ok := accountedFor[networkRuleResource.ID()]; ok { + continue + } + + orphans = append(orphans, adaptNetworkRule(networkRuleResource)) + } + } + + return orphans +} + +func adaptAccounts(modules terraform.Modules) ([]storage.Account, []string, []string) { + var accounts []storage.Account + var accountedForContainers []string + var accountedForNetworkRules []string + + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_storage_account") { + account := adaptAccount(resource) + containerResource := module.GetReferencingResources(resource, "azurerm_storage_container", "storage_account_name") + for _, containerBlock := range containerResource { + accountedForContainers = append(accountedForContainers, containerBlock.ID()) + account.Containers = append(account.Containers, adaptContainer(containerBlock)) + } + networkRulesResource := module.GetReferencingResources(resource, "azurerm_storage_account_network_rules", "storage_account_name") + for _, networkRuleBlock := range networkRulesResource { + accountedForNetworkRules = append(accountedForNetworkRules, networkRuleBlock.ID()) + account.NetworkRules = append(account.NetworkRules, adaptNetworkRule(networkRuleBlock)) + } + for _, queueBlock := range module.GetReferencingResources(resource, "azurerm_storage_queue", "storage_account_name") { + queue := storage.Queue{ + Metadata: queueBlock.GetMetadata(), + Name: queueBlock.GetAttribute("name").AsStringValueOrDefault("", queueBlock), + } + account.Queues = append(account.Queues, queue) + } + accounts = append(accounts, account) + } + } + + return accounts, accountedForContainers, accountedForNetworkRules +} + +func adaptAccount(resource *terraform.Block) storage.Account { + account := storage.Account{ + Metadata: resource.GetMetadata(), + NetworkRules: nil, + EnforceHTTPS: iacTypes.BoolDefault(true, resource.GetMetadata()), + Containers: nil, + QueueProperties: storage.QueueProperties{ + Metadata: resource.GetMetadata(), + EnableLogging: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + MinimumTLSVersion: iacTypes.StringDefault("TLS1_2", resource.GetMetadata()), + } + + networkRulesBlocks := resource.GetBlocks("network_rules") + for _, networkBlock := range networkRulesBlocks { + account.NetworkRules = append(account.NetworkRules, adaptNetworkRule(networkBlock)) + } + + httpsOnlyAttr := resource.GetAttribute("enable_https_traffic_only") + account.EnforceHTTPS = httpsOnlyAttr.AsBoolValueOrDefault(true, resource) + + queuePropertiesBlock := resource.GetBlock("queue_properties") + if queuePropertiesBlock.IsNotNil() { + account.QueueProperties.Metadata = queuePropertiesBlock.GetMetadata() + loggingBlock := queuePropertiesBlock.GetBlock("logging") + if loggingBlock.IsNotNil() { + account.QueueProperties.EnableLogging = iacTypes.Bool(true, loggingBlock.GetMetadata()) + } + } + + minTLSVersionAttr := resource.GetAttribute("min_tls_version") + account.MinimumTLSVersion = minTLSVersionAttr.AsStringValueOrDefault("TLS1_0", resource) + return account +} + +func adaptContainer(resource *terraform.Block) storage.Container { + accessTypeAttr := resource.GetAttribute("container_access_type") + publicAccess := iacTypes.StringDefault(storage.PublicAccessOff, resource.GetMetadata()) + + if accessTypeAttr.Equals("blob") { + publicAccess = iacTypes.String(storage.PublicAccessBlob, accessTypeAttr.GetMetadata()) + } else if accessTypeAttr.Equals("container") { + publicAccess = iacTypes.String(storage.PublicAccessContainer, accessTypeAttr.GetMetadata()) + } + + return storage.Container{ + Metadata: resource.GetMetadata(), + PublicAccess: publicAccess, + } +} + +func adaptNetworkRule(resource *terraform.Block) storage.NetworkRule { + var allowByDefault iacTypes.BoolValue + var bypass []iacTypes.StringValue + + defaultActionAttr := resource.GetAttribute("default_action") + + if defaultActionAttr.IsNotNil() { + allowByDefault = iacTypes.Bool(defaultActionAttr.Equals("Allow", terraform.IgnoreCase), defaultActionAttr.GetMetadata()) + } else { + allowByDefault = iacTypes.BoolDefault(false, resource.GetMetadata()) + } + + if resource.HasChild("bypass") { + bypassAttr := resource.GetAttribute("bypass") + bypass = bypassAttr.AsStringValues() + } + + return storage.NetworkRule{ + Metadata: resource.GetMetadata(), + Bypass: bypass, + AllowByDefault: allowByDefault, + } +} diff --git a/pkg/iac/adapters/terraform/azure/storage/adapt_test.go b/pkg/iac/adapters/terraform/azure/storage/adapt_test.go new file mode 100644 index 000000000000..3b13c3c4df35 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/storage/adapt_test.go @@ -0,0 +1,251 @@ +package storage + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected storage.Storage + }{ + { + name: "defined", + terraform: ` + resource "azurerm_resource_group" "example" { + name = "example" + } + + resource "azurerm_storage_account" "example" { + name = "storageaccountname" + resource_group_name = azurerm_resource_group.example.name + + network_rules { + default_action = "Deny" + bypass = ["Metrics", "AzureServices"] + } + + enable_https_traffic_only = true + queue_properties { + logging { + delete = true + read = true + write = true + version = "1.0" + retention_policy_days = 10 + } + } + min_tls_version = "TLS1_2" + } + + resource "azurerm_storage_account_network_rules" "test" { + resource_group_name = azurerm_resource_group.example.name + storage_account_name = azurerm_storage_account.example.name + + default_action = "Allow" + bypass = ["Metrics"] + } + + resource "azurerm_storage_container" "example" { + storage_account_name = azurerm_storage_account.example.name + resource_group_name = azurerm_resource_group.example.name + container_access_type = "blob" + } +`, + expected: storage.Storage{ + Accounts: []storage.Account{ + + { + Metadata: iacTypes.NewTestMetadata(), + EnforceHTTPS: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + MinimumTLSVersion: iacTypes.String("TLS1_2", iacTypes.NewTestMetadata()), + NetworkRules: []storage.NetworkRule{ + { + Metadata: iacTypes.NewTestMetadata(), + Bypass: []iacTypes.StringValue{ + iacTypes.String("Metrics", iacTypes.NewTestMetadata()), + iacTypes.String("AzureServices", iacTypes.NewTestMetadata()), + }, + AllowByDefault: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + { + Metadata: iacTypes.NewTestMetadata(), + Bypass: []iacTypes.StringValue{ + iacTypes.String("Metrics", iacTypes.NewTestMetadata()), + }, + AllowByDefault: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + QueueProperties: storage.QueueProperties{ + Metadata: iacTypes.NewTestMetadata(), + EnableLogging: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + Containers: []storage.Container{ + { + Metadata: iacTypes.NewTestMetadata(), + PublicAccess: iacTypes.String("blob", iacTypes.NewTestMetadata()), + }, + }, + }, + { + Metadata: iacTypes.NewUnmanagedMetadata(), + EnforceHTTPS: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + QueueProperties: storage.QueueProperties{ + Metadata: iacTypes.NewUnmanagedMetadata(), + EnableLogging: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + }, + MinimumTLSVersion: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + }, + }, + }, + }, + { + name: "orphans", + terraform: ` + resource "azurerm_storage_account_network_rules" "test" { + default_action = "Allow" + bypass = ["Metrics"] + } + + resource "azurerm_storage_container" "example" { + container_access_type = "blob" + } +`, + expected: storage.Storage{ + Accounts: []storage.Account{ + { + Metadata: iacTypes.NewUnmanagedMetadata(), + EnforceHTTPS: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + NetworkRules: []storage.NetworkRule{ + { + Metadata: iacTypes.NewTestMetadata(), + Bypass: []iacTypes.StringValue{ + iacTypes.String("Metrics", iacTypes.NewTestMetadata()), + }, + AllowByDefault: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + QueueProperties: storage.QueueProperties{ + Metadata: iacTypes.NewUnmanagedMetadata(), + EnableLogging: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + }, + MinimumTLSVersion: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + Containers: []storage.Container{ + { + Metadata: iacTypes.NewTestMetadata(), + PublicAccess: iacTypes.String("blob", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "azurerm_resource_group" "example" { + name = "example" + location = "West Europe" + } + + resource "azurerm_storage_account" "example" { + resource_group_name = azurerm_resource_group.example.name + + enable_https_traffic_only = true + min_tls_version = "TLS1_2" + + queue_properties { + logging { + delete = true + read = true + write = true + version = "1.0" + retention_policy_days = 10 + } + } + + network_rules { + default_action = "Deny" + bypass = ["Metrics", "AzureServices"] + } + } + + resource "azurerm_storage_account_network_rules" "test" { + resource_group_name = azurerm_resource_group.example.name + storage_account_name = azurerm_storage_account.example.name + + default_action = "Allow" + bypass = ["Metrics"] + } + + resource "azurerm_storage_container" "example" { + storage_account_name = azurerm_storage_account.example.name + resource_group_name = azurerm_resource_group.example.name + container_access_type = "blob" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Accounts, 2) //+orphans holder + account := adapted.Accounts[0] + + assert.Equal(t, 7, account.Metadata.Range().GetStartLine()) + assert.Equal(t, 27, account.Metadata.Range().GetEndLine()) + + assert.Equal(t, 10, account.EnforceHTTPS.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 10, account.EnforceHTTPS.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, account.MinimumTLSVersion.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 11, account.MinimumTLSVersion.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 13, account.QueueProperties.Metadata.Range().GetStartLine()) + assert.Equal(t, 21, account.QueueProperties.Metadata.Range().GetEndLine()) + + assert.Equal(t, 14, account.QueueProperties.EnableLogging.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 20, account.QueueProperties.EnableLogging.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 23, account.NetworkRules[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 26, account.NetworkRules[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 24, account.NetworkRules[0].AllowByDefault.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 24, account.NetworkRules[0].AllowByDefault.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 25, account.NetworkRules[0].Bypass[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 25, account.NetworkRules[0].Bypass[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 29, account.NetworkRules[1].Metadata.Range().GetStartLine()) + assert.Equal(t, 35, account.NetworkRules[1].Metadata.Range().GetEndLine()) + + assert.Equal(t, 33, account.NetworkRules[1].AllowByDefault.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 33, account.NetworkRules[1].AllowByDefault.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 34, account.NetworkRules[1].Bypass[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 34, account.NetworkRules[1].Bypass[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 37, account.Containers[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 41, account.Containers[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 40, account.Containers[0].PublicAccess.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 40, account.Containers[0].PublicAccess.GetMetadata().Range().GetEndLine()) + +} diff --git a/pkg/iac/adapters/terraform/azure/synapse/adapt.go b/pkg/iac/adapters/terraform/azure/synapse/adapt.go new file mode 100644 index 000000000000..37efcb596a9d --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/synapse/adapt.go @@ -0,0 +1,32 @@ +package synapse + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/synapse" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) synapse.Synapse { + return synapse.Synapse{ + Workspaces: adaptWorkspaces(modules), + } +} + +func adaptWorkspaces(modules terraform.Modules) []synapse.Workspace { + var workspaces []synapse.Workspace + for _, module := range modules { + for _, resource := range module.GetResourcesByType("azurerm_synapse_workspace") { + workspaces = append(workspaces, adaptWorkspace(resource)) + } + } + return workspaces +} + +func adaptWorkspace(resource *terraform.Block) synapse.Workspace { + enableManagedVNAttr := resource.GetAttribute("managed_virtual_network_enabled") + enableManagedVNVal := enableManagedVNAttr.AsBoolValueOrDefault(false, resource) + + return synapse.Workspace{ + Metadata: resource.GetMetadata(), + EnableManagedVirtualNetwork: enableManagedVNVal, + } +} diff --git a/pkg/iac/adapters/terraform/azure/synapse/adapt_test.go b/pkg/iac/adapters/terraform/azure/synapse/adapt_test.go new file mode 100644 index 000000000000..79062878e092 --- /dev/null +++ b/pkg/iac/adapters/terraform/azure/synapse/adapt_test.go @@ -0,0 +1,82 @@ +package synapse + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/synapse" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptWorkspace(t *testing.T) { + tests := []struct { + name string + terraform string + expected synapse.Workspace + }{ + { + name: "enabled", + terraform: ` + resource "azurerm_synapse_workspace" "example" { + managed_virtual_network_enabled = true + } +`, + expected: synapse.Workspace{ + Metadata: iacTypes.NewTestMetadata(), + EnableManagedVirtualNetwork: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + { + name: "disabled", + terraform: ` + resource "azurerm_synapse_workspace" "example" { + managed_virtual_network_enabled = false + } +`, + expected: synapse.Workspace{ + Metadata: iacTypes.NewTestMetadata(), + EnableManagedVirtualNetwork: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + { + name: "default", + terraform: ` + resource "azurerm_synapse_workspace" "example" { + } +`, + expected: synapse.Workspace{ + Metadata: iacTypes.NewTestMetadata(), + EnableManagedVirtualNetwork: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptWorkspace(modules.GetBlocks()[0]) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "azurerm_synapse_workspace" "example" { + managed_virtual_network_enabled = true + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Workspaces, 1) + workspace := adapted.Workspaces[0] + + assert.Equal(t, 3, workspace.EnableManagedVirtualNetwork.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, workspace.EnableManagedVirtualNetwork.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/cloudstack/adapt.go b/pkg/iac/adapters/terraform/cloudstack/adapt.go new file mode 100644 index 000000000000..d19a05bdf3a4 --- /dev/null +++ b/pkg/iac/adapters/terraform/cloudstack/adapt.go @@ -0,0 +1,13 @@ +package cloudstack + +import ( + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/cloudstack/compute" + "github.com/aquasecurity/trivy/pkg/iac/providers/cloudstack" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) cloudstack.CloudStack { + return cloudstack.CloudStack{ + Compute: compute.Adapt(modules), + } +} diff --git a/pkg/iac/adapters/terraform/cloudstack/compute/adapt.go b/pkg/iac/adapters/terraform/cloudstack/compute/adapt.go new file mode 100644 index 000000000000..4908f0325158 --- /dev/null +++ b/pkg/iac/adapters/terraform/cloudstack/compute/adapt.go @@ -0,0 +1,47 @@ +package compute + +import ( + "encoding/base64" + + "github.com/aquasecurity/trivy/pkg/iac/providers/cloudstack/compute" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) compute.Compute { + return compute.Compute{ + Instances: adaptInstances(modules), + } +} + +func adaptInstances(modules terraform.Modules) []compute.Instance { + var instances []compute.Instance + for _, module := range modules { + for _, resource := range module.GetResourcesByType("cloudstack_instance") { + instances = append(instances, adaptInstance(resource)) + } + } + return instances +} + +func adaptInstance(resource *terraform.Block) compute.Instance { + userDataAttr := resource.GetAttribute("user_data") + var encoded []byte + var err error + + if userDataAttr.IsNotNil() && userDataAttr.IsString() { + encoded, err = base64.StdEncoding.DecodeString(userDataAttr.Value().AsString()) + if err != nil { + encoded = []byte(userDataAttr.Value().AsString()) + } + return compute.Instance{ + Metadata: resource.GetMetadata(), + UserData: types.String(string(encoded), userDataAttr.GetMetadata()), + } + } + + return compute.Instance{ + Metadata: resource.GetMetadata(), + UserData: types.StringDefault("", resource.GetMetadata()), + } +} diff --git a/pkg/iac/adapters/terraform/cloudstack/compute/adapt_test.go b/pkg/iac/adapters/terraform/cloudstack/compute/adapt_test.go new file mode 100644 index 000000000000..14576c13bbf8 --- /dev/null +++ b/pkg/iac/adapters/terraform/cloudstack/compute/adapt_test.go @@ -0,0 +1,90 @@ +package compute + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/cloudstack/compute" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptInstance(t *testing.T) { + tests := []struct { + name string + terraform string + expected compute.Instance + }{ + { + name: "sensitive user data", + terraform: ` + resource "cloudstack_instance" "web" { + name = "server-1" + user_data = < 0 { + cluster.NodeConfig = cluster.NodePools[0].NodeConfig + a.clusterMap[id] = cluster + } + } + + var clusters []gke.Cluster + for _, cluster := range a.clusterMap { + clusters = append(clusters, cluster) + } + return clusters +} + +func (a *adapter) adaptCluster(resource *terraform.Block, module *terraform.Module) { + + cluster := gke.Cluster{ + Metadata: resource.GetMetadata(), + NodePools: nil, + IPAllocationPolicy: gke.IPAllocationPolicy{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + MasterAuthorizedNetworks: gke.MasterAuthorizedNetworks{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + CIDRs: []iacTypes.StringValue{}, + }, + NetworkPolicy: gke.NetworkPolicy{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + DatapathProvider: resource.GetAttribute("datapath_provider"). + AsStringValueOrDefault("DATAPATH_PROVIDER_UNSPECIFIED", resource), + PrivateCluster: gke.PrivateCluster{ + Metadata: resource.GetMetadata(), + EnablePrivateNodes: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + LoggingService: iacTypes.StringDefault("logging.googleapis.com/kubernetes", resource.GetMetadata()), + MonitoringService: iacTypes.StringDefault("monitoring.googleapis.com/kubernetes", resource.GetMetadata()), + MasterAuth: gke.MasterAuth{ + Metadata: resource.GetMetadata(), + ClientCertificate: gke.ClientCertificate{ + Metadata: resource.GetMetadata(), + IssueCertificate: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + Username: iacTypes.StringDefault("", resource.GetMetadata()), + Password: iacTypes.StringDefault("", resource.GetMetadata()), + }, + NodeConfig: gke.NodeConfig{ + Metadata: resource.GetMetadata(), + ImageType: iacTypes.StringDefault("", resource.GetMetadata()), + WorkloadMetadataConfig: gke.WorkloadMetadataConfig{ + Metadata: resource.GetMetadata(), + NodeMetadata: iacTypes.StringDefault("", resource.GetMetadata()), + }, + ServiceAccount: iacTypes.StringDefault("", resource.GetMetadata()), + EnableLegacyEndpoints: iacTypes.BoolDefault(true, resource.GetMetadata()), + }, + EnableShieldedNodes: iacTypes.BoolDefault(true, resource.GetMetadata()), + EnableLegacyABAC: iacTypes.BoolDefault(false, resource.GetMetadata()), + ResourceLabels: iacTypes.MapDefault(make(map[string]string), resource.GetMetadata()), + RemoveDefaultNodePool: iacTypes.BoolDefault(false, resource.GetMetadata()), + EnableAutpilot: iacTypes.BoolDefault(false, resource.GetMetadata()), + } + + if allocBlock := resource.GetBlock("ip_allocation_policy"); allocBlock.IsNotNil() { + cluster.IPAllocationPolicy.Metadata = allocBlock.GetMetadata() + cluster.IPAllocationPolicy.Enabled = iacTypes.Bool(true, allocBlock.GetMetadata()) + } + + if blocks := resource.GetBlocks("master_authorized_networks_config"); len(blocks) > 0 { + cluster.MasterAuthorizedNetworks = adaptMasterAuthNetworksAsBlocks(resource, blocks) + } + + if policyBlock := resource.GetBlock("network_policy"); policyBlock.IsNotNil() { + enabledAttr := policyBlock.GetAttribute("enabled") + cluster.NetworkPolicy.Metadata = policyBlock.GetMetadata() + cluster.NetworkPolicy.Enabled = enabledAttr.AsBoolValueOrDefault(false, policyBlock) + } + + if privBlock := resource.GetBlock("private_cluster_config"); privBlock.IsNotNil() { + privateNodesEnabledAttr := privBlock.GetAttribute("enable_private_nodes") + cluster.PrivateCluster.Metadata = privBlock.GetMetadata() + cluster.PrivateCluster.EnablePrivateNodes = privateNodesEnabledAttr.AsBoolValueOrDefault(false, privBlock) + } + + loggingAttr := resource.GetAttribute("logging_service") + cluster.LoggingService = loggingAttr.AsStringValueOrDefault("logging.googleapis.com/kubernetes", resource) + monitoringServiceAttr := resource.GetAttribute("monitoring_service") + cluster.MonitoringService = monitoringServiceAttr.AsStringValueOrDefault("monitoring.googleapis.com/kubernetes", resource) + + if masterBlock := resource.GetBlock("master_auth"); masterBlock.IsNotNil() { + cluster.MasterAuth = adaptMasterAuth(masterBlock) + } + + if configBlock := resource.GetBlock("node_config"); configBlock.IsNotNil() { + if configBlock.GetBlock("metadata").IsNotNil() { + cluster.NodeConfig.Metadata = configBlock.GetBlock("metadata").GetMetadata() + } + cluster.NodeConfig = adaptNodeConfig(configBlock) + } + + cluster.EnableShieldedNodes = resource.GetAttribute("enable_shielded_nodes").AsBoolValueOrDefault(true, resource) + + enableLegacyABACAttr := resource.GetAttribute("enable_legacy_abac") + cluster.EnableLegacyABAC = enableLegacyABACAttr.AsBoolValueOrDefault(false, resource) + + cluster.EnableAutpilot = resource.GetAttribute("enable_autopilot").AsBoolValueOrDefault(false, resource) + + resourceLabelsAttr := resource.GetAttribute("resource_labels") + if resourceLabelsAttr.IsNotNil() { + cluster.ResourceLabels = resourceLabelsAttr.AsMapValue() + } + + cluster.RemoveDefaultNodePool = resource.GetAttribute("remove_default_node_pool").AsBoolValueOrDefault(false, resource) + + a.clusterMap[resource.ID()] = cluster +} + +func (a *adapter) adaptNodePools() { + for _, nodePoolBlock := range a.modules.GetResourcesByType("google_container_node_pool") { + a.adaptNodePool(nodePoolBlock) + } +} + +func (a *adapter) adaptNodePool(resource *terraform.Block) { + nodeConfig := gke.NodeConfig{ + Metadata: resource.GetMetadata(), + ImageType: iacTypes.StringDefault("", resource.GetMetadata()), + WorkloadMetadataConfig: gke.WorkloadMetadataConfig{ + Metadata: resource.GetMetadata(), + NodeMetadata: iacTypes.StringDefault("", resource.GetMetadata()), + }, + ServiceAccount: iacTypes.StringDefault("", resource.GetMetadata()), + EnableLegacyEndpoints: iacTypes.BoolDefault(true, resource.GetMetadata()), + } + + management := gke.Management{ + Metadata: resource.GetMetadata(), + EnableAutoRepair: iacTypes.BoolDefault(false, resource.GetMetadata()), + EnableAutoUpgrade: iacTypes.BoolDefault(false, resource.GetMetadata()), + } + + if resource.HasChild("management") { + management.Metadata = resource.GetBlock("management").GetMetadata() + + autoRepairAttr := resource.GetBlock("management").GetAttribute("auto_repair") + management.EnableAutoRepair = autoRepairAttr.AsBoolValueOrDefault(false, resource.GetBlock("management")) + + autoUpgradeAttr := resource.GetBlock("management").GetAttribute("auto_upgrade") + management.EnableAutoUpgrade = autoUpgradeAttr.AsBoolValueOrDefault(false, resource.GetBlock("management")) + } + + if resource.HasChild("node_config") { + nodeConfig = adaptNodeConfig(resource.GetBlock("node_config")) + } + + nodePool := gke.NodePool{ + Metadata: resource.GetMetadata(), + Management: management, + NodeConfig: nodeConfig, + } + + clusterAttr := resource.GetAttribute("cluster") + if referencedCluster, err := a.modules.GetReferencedBlock(clusterAttr, resource); err == nil { + if referencedCluster.TypeLabel() == "google_container_cluster" { + if cluster, ok := a.clusterMap[referencedCluster.ID()]; ok { + cluster.NodePools = append(cluster.NodePools, nodePool) + a.clusterMap[referencedCluster.ID()] = cluster + return + } + } + } + + // we didn't find a cluster to put the nodepool in, so create a placeholder + a.clusterMap[uuid.NewString()] = gke.Cluster{ + Metadata: iacTypes.NewUnmanagedMetadata(), + NodePools: []gke.NodePool{nodePool}, + IPAllocationPolicy: gke.IPAllocationPolicy{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Enabled: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + }, + MasterAuthorizedNetworks: gke.MasterAuthorizedNetworks{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Enabled: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + CIDRs: nil, + }, + NetworkPolicy: gke.NetworkPolicy{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Enabled: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + }, + PrivateCluster: gke.PrivateCluster{ + Metadata: iacTypes.NewUnmanagedMetadata(), + EnablePrivateNodes: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + }, + LoggingService: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + MonitoringService: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + MasterAuth: gke.MasterAuth{ + Metadata: iacTypes.NewUnmanagedMetadata(), + ClientCertificate: gke.ClientCertificate{ + Metadata: iacTypes.NewUnmanagedMetadata(), + IssueCertificate: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + }, + Username: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + Password: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + }, + NodeConfig: gke.NodeConfig{ + Metadata: iacTypes.NewUnmanagedMetadata(), + ImageType: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + WorkloadMetadataConfig: gke.WorkloadMetadataConfig{ + Metadata: iacTypes.NewUnmanagedMetadata(), + NodeMetadata: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + }, + ServiceAccount: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + EnableLegacyEndpoints: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + }, + EnableShieldedNodes: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + EnableLegacyABAC: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + ResourceLabels: iacTypes.MapDefault(nil, iacTypes.NewUnmanagedMetadata()), + RemoveDefaultNodePool: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + EnableAutpilot: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + } +} + +func adaptNodeConfig(resource *terraform.Block) gke.NodeConfig { + + config := gke.NodeConfig{ + Metadata: resource.GetMetadata(), + ImageType: resource.GetAttribute("image_type").AsStringValueOrDefault("", resource), + WorkloadMetadataConfig: gke.WorkloadMetadataConfig{ + Metadata: resource.GetMetadata(), + NodeMetadata: iacTypes.StringDefault("UNSPECIFIED", resource.GetMetadata()), + }, + ServiceAccount: resource.GetAttribute("service_account").AsStringValueOrDefault("", resource), + EnableLegacyEndpoints: iacTypes.BoolDefault(true, resource.GetMetadata()), + } + + if metadata := resource.GetAttribute("metadata"); metadata.IsNotNil() { + legacyMetadata := metadata.MapValue("disable-legacy-endpoints") + if legacyMetadata.IsWhollyKnown() && legacyMetadata.Type() == cty.Bool { + config.EnableLegacyEndpoints = iacTypes.Bool(legacyMetadata.False(), metadata.GetMetadata()) + } + } + + workloadBlock := resource.GetBlock("workload_metadata_config") + if workloadBlock.IsNotNil() { + config.WorkloadMetadataConfig.Metadata = workloadBlock.GetMetadata() + modeAttr := workloadBlock.GetAttribute("node_metadata") + if modeAttr.IsNil() { + modeAttr = workloadBlock.GetAttribute("mode") // try newest version + } + config.WorkloadMetadataConfig.NodeMetadata = modeAttr.AsStringValueOrDefault("UNSPECIFIED", workloadBlock) + } + + return config +} + +func adaptMasterAuth(resource *terraform.Block) gke.MasterAuth { + clientCert := gke.ClientCertificate{ + Metadata: resource.GetMetadata(), + IssueCertificate: iacTypes.BoolDefault(false, resource.GetMetadata()), + } + + if resource.HasChild("client_certificate_config") { + clientCertAttr := resource.GetBlock("client_certificate_config").GetAttribute("issue_client_certificate") + clientCert.IssueCertificate = clientCertAttr.AsBoolValueOrDefault(false, resource.GetBlock("client_certificate_config")) + clientCert.Metadata = resource.GetBlock("client_certificate_config").GetMetadata() + } + + username := resource.GetAttribute("username").AsStringValueOrDefault("", resource) + password := resource.GetAttribute("password").AsStringValueOrDefault("", resource) + + return gke.MasterAuth{ + Metadata: resource.GetMetadata(), + ClientCertificate: clientCert, + Username: username, + Password: password, + } +} + +func adaptMasterAuthNetworksAsBlocks(parent *terraform.Block, blocks terraform.Blocks) gke.MasterAuthorizedNetworks { + var cidrs []iacTypes.StringValue + for _, block := range blocks { + for _, cidrBlock := range block.GetBlocks("cidr_blocks") { + if cidrAttr := cidrBlock.GetAttribute("cidr_block"); cidrAttr.IsNotNil() { + cidrs = append(cidrs, cidrAttr.AsStringValues()...) + } + } + } + enabled := iacTypes.Bool(true, blocks[0].GetMetadata()) + return gke.MasterAuthorizedNetworks{ + Metadata: blocks[0].GetMetadata(), + Enabled: enabled, + CIDRs: cidrs, + } +} diff --git a/pkg/iac/adapters/terraform/google/gke/adapt_test.go b/pkg/iac/adapters/terraform/google/gke/adapt_test.go new file mode 100644 index 000000000000..f45cdc24b2fb --- /dev/null +++ b/pkg/iac/adapters/terraform/google/gke/adapt_test.go @@ -0,0 +1,415 @@ +package gke + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/gke" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected gke.GKE + }{ + { + name: "separately defined pool", + terraform: ` +resource "google_service_account" "default" { + account_id = "service-account-id" + display_name = "Service Account" +} + +resource "google_container_cluster" "example" { + name = "my-gke-cluster" + + node_config { + metadata = { + disable-legacy-endpoints = true + } + } + + pod_security_policy_config { + enabled = "true" + } + + enable_legacy_abac = "true" + enable_shielded_nodes = "true" + + remove_default_node_pool = true + initial_node_count = 1 + monitoring_service = "monitoring.googleapis.com/kubernetes" + logging_service = "logging.googleapis.com/kubernetes" + + master_auth { + client_certificate_config { + issue_client_certificate = true + } + } + + master_authorized_networks_config { + cidr_blocks { + cidr_block = "10.10.128.0/24" + display_name = "internal" + } + } + + resource_labels = { + "env" = "staging" + } + + private_cluster_config { + enable_private_nodes = true + } + + network_policy { + enabled = true + } + + ip_allocation_policy {} + + enable_autopilot = true + + datapath_provider = "ADVANCED_DATAPATH" +} + +resource "google_container_node_pool" "primary_preemptible_nodes" { + cluster = google_container_cluster.example.name + node_count = 1 + + node_config { + service_account = google_service_account.default.email + metadata = { + disable-legacy-endpoints = true + } + image_type = "COS_CONTAINERD" + workload_metadata_config { + mode = "GCE_METADATA" + } + } + management { + auto_repair = true + auto_upgrade = true + } +} +`, + expected: gke.GKE{ + Clusters: []gke.Cluster{ + { + Metadata: iacTypes.NewTestMetadata(), + NodeConfig: gke.NodeConfig{ + Metadata: iacTypes.NewTestMetadata(), + ImageType: iacTypes.String("COS_CONTAINERD", iacTypes.NewTestMetadata()), + WorkloadMetadataConfig: gke.WorkloadMetadataConfig{ + Metadata: iacTypes.NewTestMetadata(), + NodeMetadata: iacTypes.String("GCE_METADATA", iacTypes.NewTestMetadata()), + }, + ServiceAccount: iacTypes.String("", iacTypes.NewTestMetadata()), + EnableLegacyEndpoints: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + NodePools: []gke.NodePool{ + { + Metadata: iacTypes.NewTestMetadata(), + Management: gke.Management{ + Metadata: iacTypes.NewTestMetadata(), + EnableAutoRepair: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + EnableAutoUpgrade: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + NodeConfig: gke.NodeConfig{ + Metadata: iacTypes.NewTestMetadata(), + ImageType: iacTypes.String("COS_CONTAINERD", iacTypes.NewTestMetadata()), + WorkloadMetadataConfig: gke.WorkloadMetadataConfig{ + Metadata: iacTypes.NewTestMetadata(), + NodeMetadata: iacTypes.String("GCE_METADATA", iacTypes.NewTestMetadata()), + }, + ServiceAccount: iacTypes.String("", iacTypes.NewTestMetadata()), + EnableLegacyEndpoints: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + IPAllocationPolicy: gke.IPAllocationPolicy{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + MasterAuthorizedNetworks: gke.MasterAuthorizedNetworks{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + CIDRs: []iacTypes.StringValue{ + iacTypes.String("10.10.128.0/24", iacTypes.NewTestMetadata()), + }, + }, + NetworkPolicy: gke.NetworkPolicy{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + DatapathProvider: iacTypes.String("ADVANCED_DATAPATH", iacTypes.NewTestMetadata()), + PrivateCluster: gke.PrivateCluster{ + Metadata: iacTypes.NewTestMetadata(), + EnablePrivateNodes: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + LoggingService: iacTypes.String("logging.googleapis.com/kubernetes", iacTypes.NewTestMetadata()), + MonitoringService: iacTypes.String("monitoring.googleapis.com/kubernetes", iacTypes.NewTestMetadata()), + MasterAuth: gke.MasterAuth{ + Metadata: iacTypes.NewTestMetadata(), + ClientCertificate: gke.ClientCertificate{ + Metadata: iacTypes.NewTestMetadata(), + IssueCertificate: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + Username: iacTypes.String("", iacTypes.NewTestMetadata()), + Password: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + EnableShieldedNodes: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + EnableLegacyABAC: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + ResourceLabels: iacTypes.Map(map[string]string{ + "env": "staging", + }, iacTypes.NewTestMetadata()), + RemoveDefaultNodePool: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + EnableAutpilot: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + { + name: "default node pool", + terraform: ` +resource "google_container_cluster" "example" { + node_config { + service_account = "service-account" + metadata = { + disable-legacy-endpoints = true + } + image_type = "COS" + workload_metadata_config { + mode = "GCE_METADATA" + } + } +} +`, + expected: gke.GKE{ + Clusters: []gke.Cluster{ + { + Metadata: iacTypes.NewTestMetadata(), + NodeConfig: gke.NodeConfig{ + Metadata: iacTypes.NewTestMetadata(), + ImageType: iacTypes.String("COS", iacTypes.NewTestMetadata()), + WorkloadMetadataConfig: gke.WorkloadMetadataConfig{ + Metadata: iacTypes.NewTestMetadata(), + NodeMetadata: iacTypes.String("GCE_METADATA", iacTypes.NewTestMetadata()), + }, + ServiceAccount: iacTypes.String("service-account", iacTypes.NewTestMetadata()), + EnableLegacyEndpoints: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + + IPAllocationPolicy: gke.IPAllocationPolicy{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + MasterAuthorizedNetworks: gke.MasterAuthorizedNetworks{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + CIDRs: []iacTypes.StringValue{}, + }, + NetworkPolicy: gke.NetworkPolicy{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + DatapathProvider: iacTypes.StringDefault("DATAPATH_PROVIDER_UNSPECIFIED", iacTypes.NewTestMetadata()), + PrivateCluster: gke.PrivateCluster{ + Metadata: iacTypes.NewTestMetadata(), + EnablePrivateNodes: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + LoggingService: iacTypes.String("logging.googleapis.com/kubernetes", iacTypes.NewTestMetadata()), + MonitoringService: iacTypes.String("monitoring.googleapis.com/kubernetes", iacTypes.NewTestMetadata()), + MasterAuth: gke.MasterAuth{ + Metadata: iacTypes.NewTestMetadata(), + ClientCertificate: gke.ClientCertificate{ + Metadata: iacTypes.NewTestMetadata(), + IssueCertificate: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + Username: iacTypes.String("", iacTypes.NewTestMetadata()), + Password: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + EnableShieldedNodes: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + EnableLegacyABAC: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + ResourceLabels: iacTypes.Map(map[string]string{}, iacTypes.NewTestMetadata()), + RemoveDefaultNodePool: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` +resource "google_container_cluster" "example" { + + node_config { + metadata = { + disable-legacy-endpoints = true + } + } + pod_security_policy_config { + enabled = "true" + } + + enable_legacy_abac = "true" + enable_shielded_nodes = "true" + + remove_default_node_pool = true + monitoring_service = "monitoring.googleapis.com/kubernetes" + logging_service = "logging.googleapis.com/kubernetes" + + master_auth { + client_certificate_config { + issue_client_certificate = true + } + } + + master_authorized_networks_config { + cidr_blocks { + cidr_block = "10.10.128.0/24" + } + } + + resource_labels = { + "env" = "staging" + } + + private_cluster_config { + enable_private_nodes = true + } + + network_policy { + enabled = true + } + ip_allocation_policy {} +} + +resource "google_container_node_pool" "primary_preemptible_nodes" { + cluster = google_container_cluster.example.name + + node_config { + metadata = { + disable-legacy-endpoints = true + } + service_account = google_service_account.default.email + image_type = "COS_CONTAINERD" + + workload_metadata_config { + mode = "GCE_METADATA" + } + } + management { + auto_repair = true + auto_upgrade = true + } +} +` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Clusters, 1) + cluster := adapted.Clusters[0] + nodePool := cluster.NodePools[0] + + assert.Equal(t, 2, cluster.Metadata.Range().GetStartLine()) + assert.Equal(t, 44, cluster.Metadata.Range().GetEndLine()) + + assert.Equal(t, 49, cluster.NodeConfig.Metadata.Range().GetStartLine()) + assert.Equal(t, 59, cluster.NodeConfig.Metadata.Range().GetEndLine()) + + assert.Equal(t, 50, cluster.NodeConfig.EnableLegacyEndpoints.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 52, cluster.NodeConfig.EnableLegacyEndpoints.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 13, cluster.EnableLegacyABAC.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 13, cluster.EnableLegacyABAC.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 14, cluster.EnableShieldedNodes.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 14, cluster.EnableShieldedNodes.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 16, cluster.RemoveDefaultNodePool.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 16, cluster.RemoveDefaultNodePool.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 17, cluster.MonitoringService.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 17, cluster.MonitoringService.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 18, cluster.LoggingService.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 18, cluster.LoggingService.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 20, cluster.MasterAuth.Metadata.Range().GetStartLine()) + assert.Equal(t, 24, cluster.MasterAuth.Metadata.Range().GetEndLine()) + + assert.Equal(t, 21, cluster.MasterAuth.ClientCertificate.Metadata.Range().GetStartLine()) + assert.Equal(t, 23, cluster.MasterAuth.ClientCertificate.Metadata.Range().GetEndLine()) + + assert.Equal(t, 22, cluster.MasterAuth.ClientCertificate.IssueCertificate.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 22, cluster.MasterAuth.ClientCertificate.IssueCertificate.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 26, cluster.MasterAuthorizedNetworks.Metadata.Range().GetStartLine()) + assert.Equal(t, 30, cluster.MasterAuthorizedNetworks.Metadata.Range().GetEndLine()) + + assert.Equal(t, 28, cluster.MasterAuthorizedNetworks.CIDRs[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 28, cluster.MasterAuthorizedNetworks.CIDRs[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 32, cluster.ResourceLabels.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 34, cluster.ResourceLabels.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 36, cluster.PrivateCluster.Metadata.Range().GetStartLine()) + assert.Equal(t, 38, cluster.PrivateCluster.Metadata.Range().GetEndLine()) + + assert.Equal(t, 37, cluster.PrivateCluster.EnablePrivateNodes.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 37, cluster.PrivateCluster.EnablePrivateNodes.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 40, cluster.NetworkPolicy.Metadata.Range().GetStartLine()) + assert.Equal(t, 42, cluster.NetworkPolicy.Metadata.Range().GetEndLine()) + + assert.Equal(t, 41, cluster.NetworkPolicy.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 41, cluster.NetworkPolicy.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 43, cluster.IPAllocationPolicy.Metadata.Range().GetStartLine()) + assert.Equal(t, 43, cluster.IPAllocationPolicy.Metadata.Range().GetEndLine()) + + assert.Equal(t, 46, nodePool.Metadata.Range().GetStartLine()) + assert.Equal(t, 64, nodePool.Metadata.Range().GetEndLine()) + + assert.Equal(t, 49, nodePool.NodeConfig.Metadata.Range().GetStartLine()) + assert.Equal(t, 59, nodePool.NodeConfig.Metadata.Range().GetEndLine()) + + assert.Equal(t, 53, nodePool.NodeConfig.ServiceAccount.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 53, nodePool.NodeConfig.ServiceAccount.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 54, nodePool.NodeConfig.ImageType.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 54, nodePool.NodeConfig.ImageType.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 56, nodePool.NodeConfig.WorkloadMetadataConfig.Metadata.Range().GetStartLine()) + assert.Equal(t, 58, nodePool.NodeConfig.WorkloadMetadataConfig.Metadata.Range().GetEndLine()) + + assert.Equal(t, 57, nodePool.NodeConfig.WorkloadMetadataConfig.NodeMetadata.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 57, nodePool.NodeConfig.WorkloadMetadataConfig.NodeMetadata.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 60, nodePool.Management.Metadata.Range().GetStartLine()) + assert.Equal(t, 63, nodePool.Management.Metadata.Range().GetEndLine()) + + assert.Equal(t, 61, nodePool.Management.EnableAutoRepair.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 61, nodePool.Management.EnableAutoRepair.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 62, nodePool.Management.EnableAutoUpgrade.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 62, nodePool.Management.EnableAutoUpgrade.GetMetadata().Range().GetEndLine()) + +} diff --git a/pkg/iac/adapters/terraform/google/iam/adapt.go b/pkg/iac/adapters/terraform/google/iam/adapt.go new file mode 100644 index 000000000000..4bc8014e14e8 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/iam/adapt.go @@ -0,0 +1,109 @@ +package iam + +import ( + "github.com/google/uuid" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) iam.IAM { + return (&adapter{ + orgs: make(map[string]iam.Organization), + modules: modules, + }).Adapt() +} + +type adapter struct { + modules terraform.Modules + orgs map[string]iam.Organization + folders []parentedFolder + projects []parentedProject + workloadIdentityPoolProviders []iam.WorkloadIdentityPoolProvider +} + +func (a *adapter) Adapt() iam.IAM { + a.adaptOrganizationIAM() + a.adaptFolders() + a.adaptFolderIAM() + a.adaptProjects() + a.adaptProjectIAM() + a.adaptWorkloadIdentityPoolProviders() + return a.merge() +} + +func (a *adapter) addOrg(blockID string) { + if _, ok := a.orgs[blockID]; !ok { + a.orgs[blockID] = iam.Organization{ + Metadata: types.NewUnmanagedMetadata(), + } + } +} + +func (a *adapter) merge() iam.IAM { + + // add projects to folders, orgs +PROJECT: + for _, project := range a.projects { + for i, folder := range a.folders { + if project.folderBlockID != "" && project.folderBlockID == folder.blockID { + folder.folder.Projects = append(folder.folder.Projects, project.project) + a.folders[i] = folder + continue PROJECT + } + } + if project.orgBlockID != "" { + if org, ok := a.orgs[project.orgBlockID]; ok { + org.Projects = append(org.Projects, project.project) + a.orgs[project.orgBlockID] = org + continue PROJECT + } + } + + org := iam.Organization{ + Metadata: types.NewUnmanagedMetadata(), + Projects: []iam.Project{project.project}, + } + a.orgs[uuid.NewString()] = org + } + + // add folders to folders, orgs +FOLDER_NESTED: // nolint: gocritic + for _, folder := range a.folders { + for i, existing := range a.folders { + if folder.parentBlockID != "" && folder.parentBlockID == existing.blockID { + existing.folder.Folders = append(existing.folder.Folders, folder.folder) + a.folders[i] = existing + continue FOLDER_NESTED // nolint: gocritic + } + + } + } +FOLDER_ORG: // nolint: gocritic + for _, folder := range a.folders { + if folder.parentBlockID != "" { + if org, ok := a.orgs[folder.parentBlockID]; ok { + org.Folders = append(org.Folders, folder.folder) + a.orgs[folder.parentBlockID] = org + continue FOLDER_ORG // nolint: gocritic + } + } else { + // add to placeholder? + org := iam.Organization{ + Metadata: types.NewUnmanagedMetadata(), + Folders: []iam.Folder{folder.folder}, + } + a.orgs[uuid.NewString()] = org + } + } + + output := iam.IAM{ + Organizations: nil, + WorkloadIdentityPoolProviders: a.workloadIdentityPoolProviders, + } + for _, org := range a.orgs { + output.Organizations = append(output.Organizations, org) + } + return output +} diff --git a/pkg/iac/adapters/terraform/google/iam/adapt_test.go b/pkg/iac/adapters/terraform/google/iam/adapt_test.go new file mode 100644 index 000000000000..8297d83a5335 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/iam/adapt_test.go @@ -0,0 +1,266 @@ +package iam + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected iam.IAM + }{ + { + name: "basic", + terraform: ` + data "google_organization" "org" { + domain = "example.com" + } + + resource "google_project" "my_project" { + name = "My Project" + project_id = "your-project-id" + org_id = data.google_organization.org.id + auto_create_network = true + } + + resource "google_folder" "department1" { + display_name = "Department 1" + parent = data.google_organization.org.id + } + + resource "google_folder_iam_member" "admin" { + folder = google_folder.department1.name + role = "roles/editor" + member = "user:alice@gmail.com" + } + + resource "google_folder_iam_binding" "folder-123" { + folder = google_folder.department1.name + role = "roles/nothing" + members = [ + "user:not-alice@gmail.com", + ] + } + + resource "google_organization_iam_member" "org-123" { + org_id = data.google_organization.org.id + role = "roles/whatever" + member = "user:member@gmail.com" + } + + resource "google_organization_iam_binding" "binding" { + org_id = data.google_organization.org.id + role = "roles/browser" + + members = [ + "user:member_2@gmail.com", + ] + } + + resource "google_iam_workload_identity_pool_provider" "example" { + workload_identity_pool_id = "example-pool" + workload_identity_pool_provider_id = "example-provider" + attribute_condition = "assertion.repository_owner=='your-github-organization'" + } +`, + expected: iam.IAM{ + Organizations: []iam.Organization{ + { + Metadata: iacTypes.NewTestMetadata(), + + Projects: []iam.Project{ + { + Metadata: iacTypes.NewTestMetadata(), + AutoCreateNetwork: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + + Folders: []iam.Folder{ + { + Metadata: iacTypes.NewTestMetadata(), + Members: []iam.Member{ + { + Metadata: iacTypes.NewTestMetadata(), + Member: iacTypes.String("user:alice@gmail.com", iacTypes.NewTestMetadata()), + Role: iacTypes.String("roles/editor", iacTypes.NewTestMetadata()), + DefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + Bindings: []iam.Binding{ + { + Metadata: iacTypes.NewTestMetadata(), + Members: []iacTypes.StringValue{ + iacTypes.String("user:not-alice@gmail.com", iacTypes.NewTestMetadata()), + }, + Role: iacTypes.String("roles/nothing", iacTypes.NewTestMetadata()), + IncludesDefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + Members: []iam.Member{ + { + Metadata: iacTypes.NewTestMetadata(), + Member: iacTypes.String("user:member@gmail.com", iacTypes.NewTestMetadata()), + Role: iacTypes.String("roles/whatever", iacTypes.NewTestMetadata()), + DefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + Bindings: []iam.Binding{ + { + Metadata: iacTypes.NewTestMetadata(), + Members: []iacTypes.StringValue{ + iacTypes.String("user:member_2@gmail.com", iacTypes.NewTestMetadata())}, + Role: iacTypes.String("roles/browser", iacTypes.NewTestMetadata()), + IncludesDefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + WorkloadIdentityPoolProviders: []iam.WorkloadIdentityPoolProvider{ + { + Metadata: iacTypes.NewTestMetadata(), + + WorkloadIdentityPoolId: iacTypes.String("example-pool", iacTypes.NewTestMetadata()), + WorkloadIdentityPoolProviderId: iacTypes.String("example-provider", iacTypes.NewTestMetadata()), + AttributeCondition: iacTypes.String("assertion.repository_owner=='your-github-organization'", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + data "google_organization" "org" { + domain = "example.com" + } + + resource "google_project" "my_project" { + name = "My Project" + project_id = "your-project-id" + org_id = data.google_organization.org.id + auto_create_network = true + } + + resource "google_folder" "department1" { + display_name = "Department 1" + parent = data.google_organization.org.id + } + + resource "google_folder_iam_binding" "folder-123" { + folder = google_folder.department1.name + role = "roles/nothing" + members = [ + "user:not-alice@gmail.com", + ] + } + + resource "google_folder_iam_member" "admin" { + folder = google_folder.department1.name + role = "roles/editor" + member = "user:alice@gmail.com" + } + + resource "google_organization_iam_member" "org-123" { + org_id = data.google_organization.org.id + role = "roles/whatever" + member = "user:member@gmail.com" + } + + resource "google_organization_iam_binding" "binding" { + org_id = data.google_organization.org.id + role = "roles/browser" + + members = [ + "user:member_2@gmail.com", + ] + } + + resource "google_iam_workload_identity_pool_provider" "example" { + workload_identity_pool_id = "example-pool" + workload_identity_pool_provider_id = "example-provider" + attribute_condition = "assertion.repository_owner=='your-github-organization'" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Organizations, 1) + require.Len(t, adapted.Organizations[0].Projects, 1) + require.Len(t, adapted.Organizations[0].Folders, 1) + require.Len(t, adapted.Organizations[0].Bindings, 1) + require.Len(t, adapted.Organizations[0].Members, 1) + require.Len(t, adapted.WorkloadIdentityPoolProviders, 1) + + project := adapted.Organizations[0].Projects[0] + folder := adapted.Organizations[0].Folders[0] + binding := adapted.Organizations[0].Bindings[0] + member := adapted.Organizations[0].Members[0] + pool := adapted.WorkloadIdentityPoolProviders[0] + + assert.Equal(t, 6, project.Metadata.Range().GetStartLine()) + assert.Equal(t, 11, project.Metadata.Range().GetEndLine()) + + assert.Equal(t, 10, project.AutoCreateNetwork.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 10, project.AutoCreateNetwork.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 13, folder.Metadata.Range().GetStartLine()) + assert.Equal(t, 16, folder.Metadata.Range().GetEndLine()) + + assert.Equal(t, 18, folder.Bindings[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 24, folder.Bindings[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 20, folder.Bindings[0].Role.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 20, folder.Bindings[0].Role.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 21, folder.Bindings[0].Members[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 23, folder.Bindings[0].Members[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 26, folder.Members[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 30, folder.Members[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 29, folder.Members[0].Member.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 29, folder.Members[0].Member.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 28, folder.Members[0].Role.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 28, folder.Members[0].Role.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 32, member.Metadata.Range().GetStartLine()) + assert.Equal(t, 36, member.Metadata.Range().GetEndLine()) + + assert.Equal(t, 34, member.Role.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 34, member.Role.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 35, member.Member.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 35, member.Member.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 38, binding.Metadata.Range().GetStartLine()) + assert.Equal(t, 45, binding.Metadata.Range().GetEndLine()) + + assert.Equal(t, 40, binding.Role.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 40, binding.Role.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 42, binding.Members[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 44, binding.Members[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 51, pool.Metadata.Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/google/iam/convert.go b/pkg/iac/adapters/terraform/google/iam/convert.go new file mode 100644 index 000000000000..0bf31b41b223 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/iam/convert.go @@ -0,0 +1,26 @@ +package iam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func ParsePolicyBlock(block *terraform.Block) []iam.Binding { + var bindings []iam.Binding + for _, bindingBlock := range block.GetBlocks("binding") { + binding := iam.Binding{ + Metadata: bindingBlock.GetMetadata(), + Members: nil, + Role: bindingBlock.GetAttribute("role").AsStringValueOrDefault("", bindingBlock), + IncludesDefaultServiceAccount: iacTypes.BoolDefault(false, bindingBlock.GetMetadata()), + } + membersAttr := bindingBlock.GetAttribute("members") + members := membersAttr.AsStringValues().AsStrings() + for _, member := range members { + binding.Members = append(binding.Members, iacTypes.String(member, membersAttr.GetMetadata())) + } + bindings = append(bindings, binding) + } + return bindings +} diff --git a/pkg/iac/adapters/terraform/google/iam/folder_iam.go b/pkg/iac/adapters/terraform/google/iam/folder_iam.go new file mode 100644 index 000000000000..eccdef2c638c --- /dev/null +++ b/pkg/iac/adapters/terraform/google/iam/folder_iam.go @@ -0,0 +1,117 @@ +package iam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +// see https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_folder_iam + +func (a *adapter) adaptFolderIAM() { + a.adaptFolderMembers() + a.adaptFolderBindings() +} + +func (a *adapter) adaptFolderMembers() { + for _, iamBlock := range a.modules.GetResourcesByType("google_folder_iam_member") { + member := a.adaptMember(iamBlock) + folderAttr := iamBlock.GetAttribute("folder") + if refBlock, err := a.modules.GetReferencedBlock(folderAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleFolder { + var foundFolder bool + for i, folder := range a.folders { + if folder.blockID == refBlock.ID() { + folder.folder.Members = append(folder.folder.Members, member) + a.folders[i] = folder + foundFolder = true + break + } + } + if foundFolder { + continue + } + } + } + + // we didn't find the folder - add an unmanaged one + a.folders = append(a.folders, parentedFolder{ + folder: iam.Folder{ + Metadata: types.NewUnmanagedMetadata(), + Members: []iam.Member{member}, + }, + }) + } +} + +func (a *adapter) adaptFolderBindings() { + + for _, iamBlock := range a.modules.GetResourcesByType("google_folder_iam_policy") { + + policyAttr := iamBlock.GetAttribute("policy_data") + if policyAttr.IsNil() { + continue + } + policyBlock, err := a.modules.GetReferencedBlock(policyAttr, iamBlock) + if err != nil { + continue + } + bindings := ParsePolicyBlock(policyBlock) + folderAttr := iamBlock.GetAttribute("folder") + + if refBlock, err := a.modules.GetReferencedBlock(folderAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleFolder { + var foundFolder bool + for i, folder := range a.folders { + if folder.blockID == refBlock.ID() { + folder.folder.Bindings = append(folder.folder.Bindings, bindings...) + a.folders[i] = folder + foundFolder = true + break + } + } + if foundFolder { + continue + } + + } + } + + // we didn't find the project - add an unmanaged one + a.folders = append(a.folders, parentedFolder{ + folder: iam.Folder{ + Metadata: types.NewUnmanagedMetadata(), + Bindings: bindings, + }, + }) + } + + for _, iamBlock := range a.modules.GetResourcesByType("google_folder_iam_binding") { + binding := a.adaptBinding(iamBlock) + folderAttr := iamBlock.GetAttribute("folder") + if refBlock, err := a.modules.GetReferencedBlock(folderAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleFolder { + var foundFolder bool + for i, folder := range a.folders { + if folder.blockID == refBlock.ID() { + folder.folder.Bindings = append(folder.folder.Bindings, binding) + a.folders[i] = folder + foundFolder = true + break + } + } + if foundFolder { + continue + } + + } + } + + // we didn't find the folder - add an unmanaged one + a.folders = append(a.folders, parentedFolder{ + folder: iam.Folder{ + Metadata: types.NewUnmanagedMetadata(), + Bindings: []iam.Binding{binding}, + }, + }) + } +} diff --git a/pkg/iac/adapters/terraform/google/iam/folders.go b/pkg/iac/adapters/terraform/google/iam/folders.go new file mode 100644 index 000000000000..1091f625dc0a --- /dev/null +++ b/pkg/iac/adapters/terraform/google/iam/folders.go @@ -0,0 +1,43 @@ +package iam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" +) + +const GoogleOrganization = "google_organization" +const GoogleFolder = "google_folder" + +type parentedFolder struct { + blockID string + parentBlockID string + parentRef string + folder iam.Folder +} + +func (a *adapter) adaptFolders() { + for _, folderBlock := range a.modules.GetResourcesByType(GoogleFolder) { + var folder parentedFolder + parentAttr := folderBlock.GetAttribute("parent") + if parentAttr.IsNil() { + continue + } + + folder.folder.Metadata = folderBlock.GetMetadata() + folder.blockID = folderBlock.ID() + if parentAttr.IsString() { + folder.parentRef = parentAttr.Value().AsString() + } + + if referencedBlock, err := a.modules.GetReferencedBlock(parentAttr, folderBlock); err == nil { + if referencedBlock.TypeLabel() == GoogleFolder { + folder.parentBlockID = referencedBlock.ID() + } + if referencedBlock.TypeLabel() == GoogleOrganization { + folder.parentBlockID = referencedBlock.ID() + a.addOrg(folder.parentBlockID) + } + } + + a.folders = append(a.folders, folder) + } +} diff --git a/pkg/iac/adapters/terraform/google/iam/org_iam.go b/pkg/iac/adapters/terraform/google/iam/org_iam.go new file mode 100644 index 000000000000..8ce88053a2f1 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/iam/org_iam.go @@ -0,0 +1,114 @@ +package iam + +import ( + "github.com/google/uuid" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +// see https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_organization_iam + +func (a *adapter) adaptOrganizationIAM() { + a.adaptOrganizationMembers() + a.adaptOrganizationBindings() +} + +func (a *adapter) adaptOrganizationMembers() { + for _, iamBlock := range a.modules.GetResourcesByType("google_organization_iam_member") { + member := a.adaptMember(iamBlock) + organizationAttr := iamBlock.GetAttribute("organization") + if organizationAttr.IsNil() { + organizationAttr = iamBlock.GetAttribute("org_id") + } + + if refBlock, err := a.modules.GetReferencedBlock(organizationAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleOrganization { + a.addOrg(refBlock.ID()) + org, ok := a.orgs[refBlock.ID()] + if !ok { + org = iam.Organization{ + Metadata: refBlock.GetMetadata(), + Folders: nil, + Projects: nil, + Members: []iam.Member{member}, + Bindings: nil, + } + } + org.Members = append(org.Members, member) + a.orgs[refBlock.ID()] = org + continue + } + } + + // we didn't find the organization - add an unmanaged one + placeholderID := uuid.NewString() + org := iam.Organization{ + Metadata: types.NewUnmanagedMetadata(), + Members: []iam.Member{member}, + } + a.orgs[placeholderID] = org + + } +} + +func (a *adapter) adaptOrganizationBindings() { + + for _, iamBlock := range a.modules.GetResourcesByType("google_organization_iam_policy") { + + policyAttr := iamBlock.GetAttribute("policy_data") + if policyAttr.IsNil() { + continue + } + policyBlock, err := a.modules.GetReferencedBlock(policyAttr, iamBlock) + if err != nil { + continue + } + bindings := ParsePolicyBlock(policyBlock) + orgAttr := iamBlock.GetAttribute("organization") + + if refBlock, err := a.modules.GetReferencedBlock(orgAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleOrganization { + if org, ok := a.orgs[refBlock.ID()]; ok { + org.Bindings = append(org.Bindings, bindings...) + a.orgs[refBlock.ID()] = org + continue + } + } + } + + // we didn't find the organization - add an unmanaged one + placeholderID := uuid.NewString() + org := iam.Organization{ + Metadata: types.NewUnmanagedMetadata(), + Bindings: bindings, + } + a.orgs[placeholderID] = org + } + + for _, iamBlock := range a.modules.GetResourcesByType("google_organization_iam_binding") { + binding := a.adaptBinding(iamBlock) + organizationAttr := iamBlock.GetAttribute("organization") + if organizationAttr.IsNil() { + organizationAttr = iamBlock.GetAttribute("org_id") + } + + if refBlock, err := a.modules.GetReferencedBlock(organizationAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleOrganization { + a.addOrg(refBlock.ID()) + org := a.orgs[refBlock.ID()] + org.Bindings = append(org.Bindings, binding) + a.orgs[refBlock.ID()] = org + continue + } + } + + // we didn't find the organization - add an unmanaged one + placeholderID := uuid.NewString() + org := iam.Organization{ + Metadata: types.NewUnmanagedMetadata(), + Bindings: []iam.Binding{binding}, + } + a.orgs[placeholderID] = org + } +} diff --git a/pkg/iac/adapters/terraform/google/iam/project_iam.go b/pkg/iac/adapters/terraform/google/iam/project_iam.go new file mode 100644 index 000000000000..a3f670ee3be3 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/iam/project_iam.go @@ -0,0 +1,287 @@ +package iam + +import ( + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +// see https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam + +const GoogleProject = "google_project" + +func (a *adapter) adaptProjectIAM() { + a.adaptProjectMembers() + a.adaptProjectBindings() +} + +func (a *adapter) adaptMember(iamBlock *terraform.Block) iam.Member { + return AdaptMember(iamBlock, a.modules) +} + +func AdaptMember(iamBlock *terraform.Block, modules terraform.Modules) iam.Member { + member := iam.Member{ + Metadata: iamBlock.GetMetadata(), + Member: iamBlock.GetAttribute("member").AsStringValueOrDefault("", iamBlock), + Role: iamBlock.GetAttribute("role").AsStringValueOrDefault("", iamBlock), + DefaultServiceAccount: iacTypes.BoolDefault(false, iamBlock.GetMetadata()), + } + + memberAttr := iamBlock.GetAttribute("member") + if referencedBlock, err := modules.GetReferencedBlock(memberAttr, iamBlock); err == nil { + if strings.HasSuffix(referencedBlock.TypeLabel(), "_default_service_account") { + member.DefaultServiceAccount = iacTypes.Bool(true, memberAttr.GetMetadata()) + } + } + + return member +} + +var projectMemberResources = []string{ + "google_project_iam_member", + "google_cloud_run_service_iam_member", + "google_compute_instance_iam_member", + "google_compute_subnetwork_iam_member", + "google_data_catalog_entry_group_iam_member", + "google_folder_iam_member", + "google_pubsub_subscription_iam_member", + "google_pubsub_topic_iam_member", + "google_sourcerepo_repository_iam_member", + "google_spanner_database_iam_member", + "google_spanner_instance_iam_member", + "google_storage_bucket_iam_member", +} + +func (a *adapter) adaptProjectMembers() { + + for _, memberType := range projectMemberResources { + for _, iamBlock := range a.modules.GetResourcesByType(memberType) { + member := a.adaptMember(iamBlock) + projectAttr := iamBlock.GetAttribute("project") + if projectAttr.IsString() { + var foundProject bool + projectID := projectAttr.Value().AsString() + for i, project := range a.projects { + if project.id == projectID { + project.project.Members = append(project.project.Members, member) + a.projects[i] = project + foundProject = true + break + } + } + if foundProject { + continue + } + } + + if refBlock, err := a.modules.GetReferencedBlock(projectAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleProject { + var foundProject bool + for i, project := range a.projects { + if project.blockID == refBlock.ID() { + project.project.Members = append(project.project.Members, member) + a.projects[i] = project + foundProject = true + break + } + } + if foundProject { + continue + } + + } + } + + // we didn't find the project - add an unmanaged one + // unless it already belongs to an existing folder + var foundFolder bool + if refBlock, err := a.modules.GetReferencedBlock(iamBlock.GetAttribute("folder"), iamBlock); err == nil { + for _, folder := range a.folders { + if folder.blockID == refBlock.ID() { + foundFolder = true + } + } + } + if foundFolder { + continue + } + + a.projects = append(a.projects, parentedProject{ + project: iam.Project{ + Metadata: iacTypes.NewUnmanagedMetadata(), + AutoCreateNetwork: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + Members: []iam.Member{member}, + Bindings: nil, + }, + }) + } + } +} + +func (a *adapter) adaptBinding(iamBlock *terraform.Block) iam.Binding { + return AdaptBinding(iamBlock, a.modules) +} + +func AdaptBinding(iamBlock *terraform.Block, modules terraform.Modules) iam.Binding { + binding := iam.Binding{ + Metadata: iamBlock.GetMetadata(), + Members: nil, + Role: iamBlock.GetAttribute("role").AsStringValueOrDefault("", iamBlock), + IncludesDefaultServiceAccount: iacTypes.BoolDefault(false, iamBlock.GetMetadata()), + } + membersAttr := iamBlock.GetAttribute("members") + members := membersAttr.AsStringValues().AsStrings() + for _, member := range members { + binding.Members = append(binding.Members, iacTypes.String(member, membersAttr.GetMetadata())) + } + if referencedBlock, err := modules.GetReferencedBlock(membersAttr, iamBlock); err == nil { + if strings.HasSuffix(referencedBlock.TypeLabel(), "_default_service_account") { + binding.IncludesDefaultServiceAccount = iacTypes.Bool(true, membersAttr.GetMetadata()) + } + } + return binding +} + +var projectBindingResources = []string{ + "google_project_iam_binding", + "google_cloud_run_service_iam_binding", + "google_compute_instance_iam_binding", + "google_compute_subnetwork_iam_binding", + "google_data_catalog_entry_group_iam_binding", + "google_folder_iam_binding", + "google_pubsub_subscription_iam_binding", + "google_pubsub_topic_iam_binding", + "google_sourcerepo_repository_iam_binding", + "google_spanner_database_iam_binding", + "google_spanner_instance_iam_binding", + "google_storage_bucket_iam_binding", +} + +func (a *adapter) adaptProjectDataBindings() { + for _, iamBlock := range a.modules.GetResourcesByType("google_project_iam_policy") { + + policyAttr := iamBlock.GetAttribute("policy_data") + if policyAttr.IsNil() { + continue + } + policyBlock, err := a.modules.GetReferencedBlock(policyAttr, iamBlock) + if err != nil { + continue + } + bindings := ParsePolicyBlock(policyBlock) + projectAttr := iamBlock.GetAttribute("project") + if projectAttr.IsString() { + var foundProject bool + projectID := projectAttr.Value().AsString() + for i, project := range a.projects { + if project.id == projectID { + project.project.Bindings = append(project.project.Bindings, bindings...) + a.projects[i] = project + foundProject = true + break + } + } + if foundProject { + continue + } + } + + if refBlock, err := a.modules.GetReferencedBlock(projectAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleProject { + var foundProject bool + for i, project := range a.projects { + if project.blockID == refBlock.ID() { + project.project.Bindings = append(project.project.Bindings, bindings...) + a.projects[i] = project + foundProject = true + break + } + } + if foundProject { + continue + } + + } + } + + // we didn't find the project - add an unmanaged one + a.projects = append(a.projects, parentedProject{ + project: iam.Project{ + Metadata: iacTypes.NewUnmanagedMetadata(), + AutoCreateNetwork: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + Members: nil, + Bindings: bindings, + }, + }) + } + +} + +func (a *adapter) adaptProjectBindings() { + + a.adaptProjectDataBindings() + + for _, bindingType := range projectBindingResources { + for _, iamBlock := range a.modules.GetResourcesByType(bindingType) { + binding := a.adaptBinding(iamBlock) + projectAttr := iamBlock.GetAttribute("project") + if projectAttr.IsString() { + var foundProject bool + projectID := projectAttr.Value().AsString() + for i, project := range a.projects { + if project.id == projectID { + project.project.Bindings = append(project.project.Bindings, binding) + a.projects[i] = project + foundProject = true + break + } + } + if foundProject { + continue + } + } + + if refBlock, err := a.modules.GetReferencedBlock(projectAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleProject { + var foundProject bool + for i, project := range a.projects { + if project.blockID == refBlock.ID() { + project.project.Bindings = append(project.project.Bindings, binding) + a.projects[i] = project + foundProject = true + break + } + } + if foundProject { + continue + } + + } + } + + // we didn't find the project - add an unmanaged one + // unless it already belongs to an existing folder + var foundFolder bool + if refBlock, err := a.modules.GetReferencedBlock(iamBlock.GetAttribute("folder"), iamBlock); err == nil { + for _, folder := range a.folders { + if folder.blockID == refBlock.ID() { + foundFolder = true + } + } + } + if foundFolder { + continue + } + a.projects = append(a.projects, parentedProject{ + project: iam.Project{ + Metadata: iacTypes.NewUnmanagedMetadata(), + AutoCreateNetwork: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + Members: nil, + Bindings: []iam.Binding{binding}, + }, + }) + } + } +} diff --git a/pkg/iac/adapters/terraform/google/iam/project_iam_test.go b/pkg/iac/adapters/terraform/google/iam/project_iam_test.go new file mode 100644 index 000000000000..fc2803c2dc4a --- /dev/null +++ b/pkg/iac/adapters/terraform/google/iam/project_iam_test.go @@ -0,0 +1,58 @@ +package iam + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" +) + +func Test_AdaptBinding(t *testing.T) { + tests := []struct { + name string + terraform string + expected iam.Binding + }{ + { + name: "defined", + terraform: ` + resource "google_organization_iam_binding" "binding" { + org_id = data.google_organization.org.id + role = "roles/browser" + + members = [ + "user:alice@gmail.com", + ] + }`, + expected: iam.Binding{ + Metadata: iacTypes.NewTestMetadata(), + Members: []iacTypes.StringValue{ + iacTypes.String("user:alice@gmail.com", iacTypes.NewTestMetadata())}, + Role: iacTypes.String("roles/browser", iacTypes.NewTestMetadata()), + IncludesDefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + { + name: "defaults", + terraform: ` + resource "google_organization_iam_binding" "binding" { + }`, + expected: iam.Binding{ + Metadata: iacTypes.NewTestMetadata(), + Role: iacTypes.String("", iacTypes.NewTestMetadata()), + IncludesDefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := AdaptBinding(modules.GetBlocks()[0], modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/google/iam/projects.go b/pkg/iac/adapters/terraform/google/iam/projects.go new file mode 100644 index 000000000000..f77ded00f3cc --- /dev/null +++ b/pkg/iac/adapters/terraform/google/iam/projects.go @@ -0,0 +1,58 @@ +package iam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" +) + +type parentedProject struct { + blockID string + orgBlockID string + folderBlockID string + id string + orgID string + folderID string + project iam.Project +} + +func (a *adapter) adaptProjects() { + for _, projectBlock := range a.modules.GetResourcesByType(GoogleProject) { + var project parentedProject + project.project.Metadata = projectBlock.GetMetadata() + idAttr := projectBlock.GetAttribute("project_id") + if !idAttr.IsString() { + continue + } + project.id = idAttr.Value().AsString() + + project.blockID = projectBlock.ID() + + orgAttr := projectBlock.GetAttribute("org_id") + if orgAttr.IsString() { + project.orgID = orgAttr.Value().AsString() + } + folderAttr := projectBlock.GetAttribute("folder_id") + if folderAttr.IsString() { + project.folderID = folderAttr.Value().AsString() + } + + autoCreateNetworkAttr := projectBlock.GetAttribute("auto_create_network") + project.project.AutoCreateNetwork = autoCreateNetworkAttr.AsBoolValueOrDefault(true, projectBlock) + + if orgAttr.IsNotNil() { + if referencedBlock, err := a.modules.GetReferencedBlock(orgAttr, projectBlock); err == nil { + if referencedBlock.TypeLabel() == GoogleOrganization { + project.orgBlockID = referencedBlock.ID() + a.addOrg(project.orgBlockID) + } + } + } + if folderAttr.IsNotNil() { + if referencedBlock, err := a.modules.GetReferencedBlock(folderAttr, projectBlock); err == nil { + if referencedBlock.TypeLabel() == GoogleFolder { + project.folderBlockID = referencedBlock.ID() + } + } + } + a.projects = append(a.projects, project) + } +} diff --git a/pkg/iac/adapters/terraform/google/iam/workload_identity_pool_providers.go b/pkg/iac/adapters/terraform/google/iam/workload_identity_pool_providers.go new file mode 100644 index 000000000000..242c3a09b907 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/iam/workload_identity_pool_providers.go @@ -0,0 +1,18 @@ +package iam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" +) + +// See https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider + +func (a *adapter) adaptWorkloadIdentityPoolProviders() { + for _, resource := range a.modules.GetResourcesByType("google_iam_workload_identity_pool_provider") { + a.workloadIdentityPoolProviders = append(a.workloadIdentityPoolProviders, iam.WorkloadIdentityPoolProvider{ + Metadata: resource.GetMetadata(), + WorkloadIdentityPoolId: resource.GetAttribute("workload_identity_pool_id").AsStringValueOrDefault("", resource), + WorkloadIdentityPoolProviderId: resource.GetAttribute("workload_identity_pool_provider_id").AsStringValueOrDefault("", resource), + AttributeCondition: resource.GetAttribute("attribute_condition").AsStringValueOrDefault("", resource), + }) + } +} diff --git a/pkg/iac/adapters/terraform/google/kms/adapt.go b/pkg/iac/adapters/terraform/google/kms/adapt.go new file mode 100644 index 000000000000..564fcd6cb365 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/kms/adapt.go @@ -0,0 +1,58 @@ +package kms + +import ( + "strconv" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/kms" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) kms.KMS { + return kms.KMS{ + KeyRings: adaptKeyRings(modules), + } +} + +func adaptKeyRings(modules terraform.Modules) []kms.KeyRing { + var keyRings []kms.KeyRing + for _, module := range modules { + for _, resource := range module.GetResourcesByType("google_kms_key_ring") { + var keys []kms.Key + + keyBlocks := module.GetReferencingResources(resource, "google_kms_crypto_key", "key_ring") + for _, keyBlock := range keyBlocks { + keys = append(keys, adaptKey(keyBlock)) + } + keyRings = append(keyRings, kms.KeyRing{ + Metadata: resource.GetMetadata(), + Keys: keys, + }) + } + } + return keyRings +} + +func adaptKey(resource *terraform.Block) kms.Key { + + key := kms.Key{ + Metadata: resource.GetMetadata(), + RotationPeriodSeconds: types.IntDefault(-1, resource.GetMetadata()), + } + + rotationPeriodAttr := resource.GetAttribute("rotation_period") + if !rotationPeriodAttr.IsString() { + return key + } + rotationStr := rotationPeriodAttr.Value().AsString() + if rotationStr[len(rotationStr)-1:] != "s" { + return key + } + seconds, err := strconv.Atoi(rotationStr[:len(rotationStr)-1]) + if err != nil { + return key + } + + key.RotationPeriodSeconds = types.Int(seconds, rotationPeriodAttr.GetMetadata()) + return key +} diff --git a/pkg/iac/adapters/terraform/google/kms/adapt_test.go b/pkg/iac/adapters/terraform/google/kms/adapt_test.go new file mode 100644 index 000000000000..c6025dc86f1e --- /dev/null +++ b/pkg/iac/adapters/terraform/google/kms/adapt_test.go @@ -0,0 +1,125 @@ +package kms + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/kms" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_adaptKeyRings(t *testing.T) { + tests := []struct { + name string + terraform string + expected []kms.KeyRing + }{ + { + name: "configured", + terraform: ` + resource "google_kms_key_ring" "keyring" { + name = "keyring-example" + } + + resource "google_kms_crypto_key" "example-key" { + name = "crypto-key-example" + key_ring = google_kms_key_ring.keyring.id + rotation_period = "7776000s" + } +`, + expected: []kms.KeyRing{ + { + Metadata: iacTypes.NewTestMetadata(), + Keys: []kms.Key{ + { + Metadata: iacTypes.NewTestMetadata(), + RotationPeriodSeconds: iacTypes.Int(7776000, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + { + name: "no keys", + terraform: ` + resource "google_kms_key_ring" "keyring" { + name = "keyring-example" + } + +`, + expected: []kms.KeyRing{ + { + Metadata: iacTypes.NewTestMetadata(), + }, + }, + }, + { + name: "default rotation period", + terraform: ` + resource "google_kms_key_ring" "keyring" { + name = "keyring-example" + } + + resource "google_kms_crypto_key" "example-key" { + name = "crypto-key-example" + key_ring = google_kms_key_ring.keyring.id + } +`, + expected: []kms.KeyRing{ + { + Metadata: iacTypes.NewTestMetadata(), + Keys: []kms.Key{ + { + Metadata: iacTypes.NewTestMetadata(), + RotationPeriodSeconds: iacTypes.Int(-1, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptKeyRings(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "google_kms_key_ring" "keyring" { + name = "keyring-example" + } + + resource "google_kms_crypto_key" "example-key" { + name = "crypto-key-example" + key_ring = google_kms_key_ring.keyring.id + rotation_period = "7776000s" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.KeyRings, 1) + require.Len(t, adapted.KeyRings[0].Keys, 1) + + key := adapted.KeyRings[0].Keys[0] + + assert.Equal(t, 2, adapted.KeyRings[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 4, adapted.KeyRings[0].Metadata.Range().GetEndLine()) + + assert.Equal(t, 6, key.Metadata.Range().GetStartLine()) + assert.Equal(t, 10, key.Metadata.Range().GetEndLine()) + + assert.Equal(t, 9, key.RotationPeriodSeconds.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 9, key.RotationPeriodSeconds.GetMetadata().Range().GetEndLine()) + +} diff --git a/pkg/iac/adapters/terraform/google/sql/adapt.go b/pkg/iac/adapters/terraform/google/sql/adapt.go new file mode 100644 index 000000000000..6418942384d4 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/sql/adapt.go @@ -0,0 +1,154 @@ +package sql + +import ( + "strconv" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) sql.SQL { + return sql.SQL{ + Instances: adaptInstances(modules), + } +} + +func adaptInstances(modules terraform.Modules) []sql.DatabaseInstance { + var instances []sql.DatabaseInstance + for _, module := range modules { + for _, resource := range module.GetResourcesByType("google_sql_database_instance") { + instances = append(instances, adaptInstance(resource)) + } + } + return instances +} + +func adaptInstance(resource *terraform.Block) sql.DatabaseInstance { + + instance := sql.DatabaseInstance{ + Metadata: resource.GetMetadata(), + DatabaseVersion: resource.GetAttribute("database_version").AsStringValueOrDefault("", resource), + IsReplica: iacTypes.BoolDefault(false, resource.GetMetadata()), + Settings: sql.Settings{ + Metadata: resource.GetMetadata(), + Flags: sql.Flags{ + Metadata: resource.GetMetadata(), + LogTempFileSize: iacTypes.IntDefault(-1, resource.GetMetadata()), + LocalInFile: iacTypes.BoolDefault(false, resource.GetMetadata()), + ContainedDatabaseAuthentication: iacTypes.BoolDefault(true, resource.GetMetadata()), + CrossDBOwnershipChaining: iacTypes.BoolDefault(true, resource.GetMetadata()), + LogCheckpoints: iacTypes.BoolDefault(false, resource.GetMetadata()), + LogConnections: iacTypes.BoolDefault(false, resource.GetMetadata()), + LogDisconnections: iacTypes.BoolDefault(false, resource.GetMetadata()), + LogLockWaits: iacTypes.BoolDefault(false, resource.GetMetadata()), + LogMinMessages: iacTypes.StringDefault("", resource.GetMetadata()), + LogMinDurationStatement: iacTypes.IntDefault(-1, resource.GetMetadata()), + }, + Backups: sql.Backups{ + Metadata: resource.GetMetadata(), + Enabled: iacTypes.BoolDefault(false, resource.GetMetadata()), + }, + IPConfiguration: sql.IPConfiguration{ + Metadata: resource.GetMetadata(), + RequireTLS: iacTypes.BoolDefault(false, resource.GetMetadata()), + EnableIPv4: iacTypes.BoolDefault(true, resource.GetMetadata()), + AuthorizedNetworks: nil, + }, + }, + } + + if attr := resource.GetAttribute("master_instance_name"); attr.IsNotNil() { + instance.IsReplica = iacTypes.Bool(true, attr.GetMetadata()) + } + + if settingsBlock := resource.GetBlock("settings"); settingsBlock.IsNotNil() { + instance.Settings.Metadata = settingsBlock.GetMetadata() + if blocks := settingsBlock.GetBlocks("database_flags"); len(blocks) > 0 { + adaptFlags(blocks, &instance.Settings.Flags) + } + if backupBlock := settingsBlock.GetBlock("backup_configuration"); backupBlock.IsNotNil() { + instance.Settings.Backups.Metadata = backupBlock.GetMetadata() + backupConfigEnabledAttr := backupBlock.GetAttribute("enabled") + instance.Settings.Backups.Enabled = backupConfigEnabledAttr.AsBoolValueOrDefault(false, backupBlock) + } + if settingsBlock.HasChild("ip_configuration") { + instance.Settings.IPConfiguration = adaptIPConfig(settingsBlock.GetBlock("ip_configuration")) + } + } + return instance +} + +// nolint +func adaptFlags(resources terraform.Blocks, flags *sql.Flags) { + for _, resource := range resources { + + nameAttr := resource.GetAttribute("name") + valueAttr := resource.GetAttribute("value") + + if !nameAttr.IsString() || valueAttr.IsNil() { + continue + } + + switch nameAttr.Value().AsString() { + case "log_temp_files": + if logTempInt, err := strconv.Atoi(valueAttr.Value().AsString()); err == nil { + flags.LogTempFileSize = iacTypes.Int(logTempInt, nameAttr.GetMetadata()) + } + case "log_min_messages": + flags.LogMinMessages = valueAttr.AsStringValueOrDefault("", resource) + case "log_min_duration_statement": + if logMinDS, err := strconv.Atoi(valueAttr.Value().AsString()); err == nil { + flags.LogMinDurationStatement = iacTypes.Int(logMinDS, nameAttr.GetMetadata()) + } + case "local_infile": + flags.LocalInFile = iacTypes.Bool(valueAttr.Equals("on"), valueAttr.GetMetadata()) + case "log_checkpoints": + flags.LogCheckpoints = iacTypes.Bool(valueAttr.Equals("on"), valueAttr.GetMetadata()) + case "log_connections": + flags.LogConnections = iacTypes.Bool(valueAttr.Equals("on"), valueAttr.GetMetadata()) + case "log_disconnections": + flags.LogDisconnections = iacTypes.Bool(valueAttr.Equals("on"), valueAttr.GetMetadata()) + case "log_lock_waits": + flags.LogLockWaits = iacTypes.Bool(valueAttr.Equals("on"), valueAttr.GetMetadata()) + case "contained database authentication": + flags.ContainedDatabaseAuthentication = iacTypes.Bool(valueAttr.Equals("on"), valueAttr.GetMetadata()) + case "cross db ownership chaining": + flags.CrossDBOwnershipChaining = iacTypes.Bool(valueAttr.Equals("on"), valueAttr.GetMetadata()) + } + } +} + +func adaptIPConfig(resource *terraform.Block) sql.IPConfiguration { + var authorizedNetworks []struct { + Name iacTypes.StringValue + CIDR iacTypes.StringValue + } + + tlsRequiredAttr := resource.GetAttribute("require_ssl") + tlsRequiredVal := tlsRequiredAttr.AsBoolValueOrDefault(false, resource) + + ipv4enabledAttr := resource.GetAttribute("ipv4_enabled") + ipv4enabledVal := ipv4enabledAttr.AsBoolValueOrDefault(true, resource) + + authNetworksBlocks := resource.GetBlocks("authorized_networks") + for _, authBlock := range authNetworksBlocks { + nameVal := authBlock.GetAttribute("name").AsStringValueOrDefault("", authBlock) + cidrVal := authBlock.GetAttribute("value").AsStringValueOrDefault("", authBlock) + + authorizedNetworks = append(authorizedNetworks, struct { + Name iacTypes.StringValue + CIDR iacTypes.StringValue + }{ + Name: nameVal, + CIDR: cidrVal, + }) + } + + return sql.IPConfiguration{ + Metadata: resource.GetMetadata(), + RequireTLS: tlsRequiredVal, + EnableIPv4: ipv4enabledVal, + AuthorizedNetworks: authorizedNetworks, + } +} diff --git a/pkg/iac/adapters/terraform/google/sql/adapt_test.go b/pkg/iac/adapters/terraform/google/sql/adapt_test.go new file mode 100644 index 000000000000..a31f649ffe99 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/sql/adapt_test.go @@ -0,0 +1,277 @@ +package sql + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected sql.SQL + }{ + { + name: "default flags", + terraform: ` + resource "google_sql_database_instance" "db" { + database_version = "POSTGRES_12" + settings { + backup_configuration { + enabled = true + } + ip_configuration { + ipv4_enabled = false + authorized_networks { + value = "108.12.12.0/24" + name = "internal" + } + require_ssl = true + } + } + } +`, + expected: sql.SQL{ + Instances: []sql.DatabaseInstance{ + { + Metadata: iacTypes.NewTestMetadata(), + IsReplica: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + DatabaseVersion: iacTypes.String("POSTGRES_12", iacTypes.NewTestMetadata()), + Settings: sql.Settings{ + Metadata: iacTypes.NewTestMetadata(), + Backups: sql.Backups{ + Metadata: iacTypes.NewTestMetadata(), + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + Flags: sql.Flags{ + Metadata: iacTypes.NewTestMetadata(), + LogMinDurationStatement: iacTypes.Int(-1, iacTypes.NewTestMetadata()), + ContainedDatabaseAuthentication: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + CrossDBOwnershipChaining: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + LocalInFile: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + LogCheckpoints: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + LogConnections: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + LogDisconnections: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + LogLockWaits: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + LogMinMessages: iacTypes.String("", iacTypes.NewTestMetadata()), + LogTempFileSize: iacTypes.Int(-1, iacTypes.NewTestMetadata()), + }, + IPConfiguration: sql.IPConfiguration{ + Metadata: iacTypes.NewTestMetadata(), + RequireTLS: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + EnableIPv4: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + AuthorizedNetworks: []struct { + Name iacTypes.StringValue + CIDR iacTypes.StringValue + }{ + { + Name: iacTypes.String("internal", iacTypes.NewTestMetadata()), + CIDR: iacTypes.String("108.12.12.0/24", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func Test_adaptInstances(t *testing.T) { + tests := []struct { + name string + terraform string + expected []sql.DatabaseInstance + }{ + { + name: "all flags", + terraform: ` +resource "google_sql_database_instance" "backup_source_instance" { + name = "test-instance" + database_version = "POSTGRES_11" + + project = "test-project" + region = "europe-west6" + deletion_protection = false + settings { + tier = "db-f1-micro" + backup_configuration { + enabled = true + } + ip_configuration { + ipv4_enabled = false + private_network = "test-network" + require_ssl = true + } + database_flags { + name = "log_connections" + value = "on" + } + database_flags { + name = "log_temp_files" + value = "0" + } + database_flags { + name = "log_checkpoints" + value = "on" + } + database_flags { + name = "log_disconnections" + value = "on" + } + database_flags { + name = "log_lock_waits" + value = "on" + } + } +} + `, + expected: []sql.DatabaseInstance{ + { + Metadata: iacTypes.NewTestMetadata(), + DatabaseVersion: iacTypes.String("POSTGRES_11", iacTypes.NewTestMetadata()), + IsReplica: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Settings: sql.Settings{ + Backups: sql.Backups{ + Enabled: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + Flags: sql.Flags{ + LogConnections: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + LogTempFileSize: iacTypes.Int(0, iacTypes.NewTestMetadata()), + LogCheckpoints: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + LogDisconnections: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + LogLockWaits: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + ContainedDatabaseAuthentication: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + CrossDBOwnershipChaining: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + LocalInFile: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + LogMinDurationStatement: iacTypes.Int(-1, iacTypes.NewTestMetadata()), + LogMinMessages: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + IPConfiguration: sql.IPConfiguration{ + EnableIPv4: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + RequireTLS: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptInstances(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "google_sql_database_instance" "backup_source_instance" { + name = "test-instance" + database_version = "POSTGRES_11" + + settings { + backup_configuration { + enabled = true + } + + ip_configuration { + ipv4_enabled = false + require_ssl = true + authorized_networks { + name = "internal" + value = "108.12.12.0/24" + } + } + + database_flags { + name = "log_connections" + value = "on" + } + database_flags { + name = "log_temp_files" + value = "0" + } + database_flags { + name = "log_checkpoints" + value = "on" + } + database_flags { + name = "log_disconnections" + value = "on" + } + database_flags { + name = "log_lock_waits" + value = "on" + } + } + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Instances, 1) + instance := adapted.Instances[0] + + assert.Equal(t, 2, instance.Metadata.Range().GetStartLine()) + assert.Equal(t, 41, instance.Metadata.Range().GetEndLine()) + + assert.Equal(t, 4, instance.DatabaseVersion.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, instance.DatabaseVersion.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 6, instance.Settings.Metadata.Range().GetStartLine()) + assert.Equal(t, 40, instance.Settings.Metadata.Range().GetEndLine()) + + assert.Equal(t, 7, instance.Settings.Backups.Metadata.Range().GetStartLine()) + assert.Equal(t, 9, instance.Settings.Backups.Metadata.Range().GetEndLine()) + + assert.Equal(t, 8, instance.Settings.Backups.Enabled.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 8, instance.Settings.Backups.Enabled.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, instance.Settings.IPConfiguration.Metadata.Range().GetStartLine()) + assert.Equal(t, 18, instance.Settings.IPConfiguration.Metadata.Range().GetEndLine()) + + assert.Equal(t, 12, instance.Settings.IPConfiguration.EnableIPv4.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 12, instance.Settings.IPConfiguration.EnableIPv4.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 13, instance.Settings.IPConfiguration.RequireTLS.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 13, instance.Settings.IPConfiguration.RequireTLS.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 15, instance.Settings.IPConfiguration.AuthorizedNetworks[0].Name.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 15, instance.Settings.IPConfiguration.AuthorizedNetworks[0].Name.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 16, instance.Settings.IPConfiguration.AuthorizedNetworks[0].CIDR.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 16, instance.Settings.IPConfiguration.AuthorizedNetworks[0].CIDR.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 22, instance.Settings.Flags.LogConnections.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 22, instance.Settings.Flags.LogConnections.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 25, instance.Settings.Flags.LogTempFileSize.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 25, instance.Settings.Flags.LogTempFileSize.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 34, instance.Settings.Flags.LogDisconnections.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 34, instance.Settings.Flags.LogDisconnections.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 38, instance.Settings.Flags.LogLockWaits.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 38, instance.Settings.Flags.LogLockWaits.GetMetadata().Range().GetEndLine()) + +} diff --git a/pkg/iac/adapters/terraform/google/storage/adapt.go b/pkg/iac/adapters/terraform/google/storage/adapt.go new file mode 100644 index 000000000000..9fe918030151 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/storage/adapt.go @@ -0,0 +1,129 @@ +package storage + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/google/storage" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Adapt(modules terraform.Modules) storage.Storage { + return storage.Storage{ + Buckets: (&adapter{modules: modules}).adaptBuckets(), + } +} + +type adapter struct { + modules terraform.Modules + bindings []parentedBinding + members []parentedMember + bindingMap terraform.ResourceIDResolutions + memberMap terraform.ResourceIDResolutions +} + +func (a *adapter) adaptBuckets() []storage.Bucket { + + a.bindingMap = a.modules.GetChildResourceIDMapByType("google_storage_bucket_iam_binding", "google_storage_bucket_iam_policy") + a.memberMap = a.modules.GetChildResourceIDMapByType("google_storage_bucket_iam_member") + + a.adaptMembers() + a.adaptBindings() + + var buckets []storage.Bucket + for _, module := range a.modules { + for _, resource := range module.GetResourcesByType(GoogleStorageBucket) { + buckets = append(buckets, a.adaptBucketResource(resource)) + } + } + + orphanage := storage.Bucket{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Name: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + Location: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + EnableUniformBucketLevelAccess: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), + Members: nil, + Bindings: nil, + } + for _, orphanedBindingID := range a.bindingMap.Orphans() { + for _, binding := range a.bindings { + if binding.blockID == orphanedBindingID { + orphanage.Bindings = append(orphanage.Bindings, binding.bindings...) + break + } + } + } + for _, orphanedMemberID := range a.memberMap.Orphans() { + for _, member := range a.members { + if member.blockID == orphanedMemberID { + orphanage.Members = append(orphanage.Members, member.member) + break + } + } + } + if len(orphanage.Bindings) > 0 || len(orphanage.Members) > 0 { + buckets = append(buckets, orphanage) + } + + return buckets +} + +func (a *adapter) adaptBucketResource(resourceBlock *terraform.Block) storage.Bucket { + + nameAttr := resourceBlock.GetAttribute("name") + nameValue := nameAttr.AsStringValueOrDefault("", resourceBlock) + + locationAttr := resourceBlock.GetAttribute("location") + locationValue := locationAttr.AsStringValueOrDefault("", resourceBlock) + + // See https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket#uniform_bucket_level_access + ublaAttr := resourceBlock.GetAttribute("uniform_bucket_level_access") + ublaValue := ublaAttr.AsBoolValueOrDefault(false, resourceBlock) + + bucket := storage.Bucket{ + Metadata: resourceBlock.GetMetadata(), + Name: nameValue, + Location: locationValue, + EnableUniformBucketLevelAccess: ublaValue, + Members: nil, + Bindings: nil, + Encryption: storage.BucketEncryption{ + Metadata: resourceBlock.GetMetadata(), + DefaultKMSKeyName: iacTypes.StringDefault("", resourceBlock.GetMetadata()), + }, + } + + if encBlock := resourceBlock.GetBlock("encryption"); encBlock.IsNotNil() { + bucket.Encryption.Metadata = encBlock.GetMetadata() + kmsKeyNameAttr := encBlock.GetAttribute("default_kms_key_name") + bucket.Encryption.DefaultKMSKeyName = kmsKeyNameAttr.AsStringValueOrDefault("", encBlock) + } + + var name string + if nameAttr.IsString() { + name = nameAttr.Value().AsString() + } + + for _, member := range a.members { + if member.bucketBlockID == resourceBlock.ID() { + bucket.Members = append(bucket.Members, member.member) + a.memberMap.Resolve(member.blockID) + continue + } + if name != "" && name == member.bucketID { + bucket.Members = append(bucket.Members, member.member) + a.memberMap.Resolve(member.blockID) + } + } + for _, binding := range a.bindings { + if binding.bucketBlockID == resourceBlock.ID() { + bucket.Bindings = append(bucket.Bindings, binding.bindings...) + a.bindingMap.Resolve(binding.blockID) + continue + } + if name != "" && name == binding.bucketID { + bucket.Bindings = append(bucket.Bindings, binding.bindings...) + a.bindingMap.Resolve(binding.blockID) + } + } + + return bucket +} diff --git a/pkg/iac/adapters/terraform/google/storage/adapt_test.go b/pkg/iac/adapters/terraform/google/storage/adapt_test.go new file mode 100644 index 000000000000..fe7a82e62f9c --- /dev/null +++ b/pkg/iac/adapters/terraform/google/storage/adapt_test.go @@ -0,0 +1,197 @@ +package storage + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/google/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Adapt(t *testing.T) { + tests := []struct { + name string + terraform string + expected storage.Storage + }{ + { + name: "defined", + terraform: ` + resource "google_storage_bucket" "static-site" { + name = "image-store.com" + location = "EU" + uniform_bucket_level_access = true + + encryption { + default_kms_key_name = "default-kms-key-name" + } + } + + resource "google_storage_bucket_iam_binding" "binding" { + bucket = google_storage_bucket.static-site.name + role = "roles/storage.admin #1" + members = [ + "group:test@example.com", + ] + } + + resource "google_storage_bucket_iam_member" "example" { + member = "serviceAccount:test@example.com" + bucket = google_storage_bucket.static-site.name + role = "roles/storage.admin #2" + }`, + expected: storage.Storage{ + Buckets: []storage.Bucket{ + { + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("image-store.com", iacTypes.NewTestMetadata()), + Location: iacTypes.String("EU", iacTypes.NewTestMetadata()), + EnableUniformBucketLevelAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Bindings: []iam.Binding{ + { + Metadata: iacTypes.NewTestMetadata(), + Members: []iacTypes.StringValue{ + iacTypes.String("group:test@example.com", iacTypes.NewTestMetadata()), + }, + Role: iacTypes.String("roles/storage.admin #1", iacTypes.NewTestMetadata()), + IncludesDefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + Members: []iam.Member{ + { + Metadata: iacTypes.NewTestMetadata(), + Member: iacTypes.String("serviceAccount:test@example.com", iacTypes.NewTestMetadata()), + Role: iacTypes.String("roles/storage.admin #2", iacTypes.NewTestMetadata()), + DefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + Encryption: storage.BucketEncryption{ + Metadata: iacTypes.NewTestMetadata(), + DefaultKMSKeyName: iacTypes.String("default-kms-key-name", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + { + name: "defaults", + terraform: ` + resource "google_storage_bucket" "static-site" { + } + + resource "google_storage_bucket_iam_binding" "binding" { + bucket = google_storage_bucket.static-site.name + } + + resource "google_storage_bucket_iam_member" "example" { + bucket = google_storage_bucket.static-site.name + }`, + expected: storage.Storage{ + Buckets: []storage.Bucket{ + { + Metadata: iacTypes.NewTestMetadata(), + Name: iacTypes.String("", iacTypes.NewTestMetadata()), + Location: iacTypes.String("", iacTypes.NewTestMetadata()), + EnableUniformBucketLevelAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + Bindings: []iam.Binding{ + { + Metadata: iacTypes.NewTestMetadata(), + Role: iacTypes.String("", iacTypes.NewTestMetadata()), + IncludesDefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + Members: []iam.Member{ + { + Metadata: iacTypes.NewTestMetadata(), + Member: iacTypes.String("", iacTypes.NewTestMetadata()), + Role: iacTypes.String("", iacTypes.NewTestMetadata()), + DefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + Encryption: storage.BucketEncryption{ + Metadata: iacTypes.NewTestMetadata(), + DefaultKMSKeyName: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := Adapt(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} + +func TestLines(t *testing.T) { + src := ` + resource "google_storage_bucket" "static-site" { + name = "image-store.com" + location = "EU" + uniform_bucket_level_access = true + } + + resource "google_storage_bucket_iam_binding" "binding" { + bucket = google_storage_bucket.static-site.name + role = "roles/storage.admin #1" + members = [ + "group:test@example.com", + ] + } + + resource "google_storage_bucket_iam_member" "example" { + member = "serviceAccount:test@example.com" + bucket = google_storage_bucket.static-site.name + role = "roles/storage.admin #2" + }` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Buckets, 1) + require.Len(t, adapted.Buckets[0].Bindings, 1) + require.Len(t, adapted.Buckets[0].Members, 1) + + bucket := adapted.Buckets[0] + binding := adapted.Buckets[0].Bindings[0] + member := adapted.Buckets[0].Members[0] + + assert.Equal(t, 2, bucket.Metadata.Range().GetStartLine()) + assert.Equal(t, 6, bucket.Metadata.Range().GetEndLine()) + + assert.Equal(t, 3, bucket.Name.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, bucket.Name.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 4, bucket.Location.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, bucket.Location.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 5, bucket.EnableUniformBucketLevelAccess.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 5, bucket.EnableUniformBucketLevelAccess.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 8, binding.Metadata.Range().GetStartLine()) + assert.Equal(t, 14, binding.Metadata.Range().GetEndLine()) + + assert.Equal(t, 10, binding.Role.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 10, binding.Role.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, binding.Members[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 13, binding.Members[0].GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 16, member.Metadata.Range().GetStartLine()) + assert.Equal(t, 20, member.Metadata.Range().GetEndLine()) + + assert.Equal(t, 17, member.Member.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 17, member.Member.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 19, member.Role.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 19, member.Role.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/google/storage/iam.go b/pkg/iac/adapters/terraform/google/storage/iam.go new file mode 100644 index 000000000000..be399304b168 --- /dev/null +++ b/pkg/iac/adapters/terraform/google/storage/iam.go @@ -0,0 +1,98 @@ +package storage + +import ( + iam2 "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/google/iam" + iamTypes "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" +) + +type parentedBinding struct { + blockID string + bucketID string + bucketBlockID string + bindings []iamTypes.Binding +} + +const GoogleStorageBucket = "google_storage_bucket" + +type parentedMember struct { + blockID string + bucketID string + bucketBlockID string + member iamTypes.Member +} + +func (a *adapter) adaptBindings() { + + for _, iamBlock := range a.modules.GetResourcesByType("google_storage_bucket_iam_policy") { + var parented parentedBinding + parented.blockID = iamBlock.ID() + + bucketAttr := iamBlock.GetAttribute("bucket") + if bucketAttr.IsString() { + parented.bucketID = bucketAttr.Value().AsString() + } + + if refBlock, err := a.modules.GetReferencedBlock(bucketAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleStorageBucket { + parented.bucketBlockID = refBlock.ID() + } + } + + policyAttr := iamBlock.GetAttribute("policy_data") + if policyAttr.IsNil() { + continue + } + + policyBlock, err := a.modules.GetReferencedBlock(policyAttr, iamBlock) + if err != nil { + continue + } + + parented.bindings = iam2.ParsePolicyBlock(policyBlock) + a.bindings = append(a.bindings, parented) + } + + for _, iamBlock := range a.modules.GetResourcesByType("google_storage_bucket_iam_binding") { + + var parented parentedBinding + parented.blockID = iamBlock.ID() + parented.bindings = []iamTypes.Binding{iam2.AdaptBinding(iamBlock, a.modules)} + + bucketAttr := iamBlock.GetAttribute("bucket") + if bucketAttr.IsString() { + parented.bucketID = bucketAttr.Value().AsString() + } + + if refBlock, err := a.modules.GetReferencedBlock(bucketAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleStorageBucket { + parented.bucketBlockID = refBlock.ID() + } + } + + a.bindings = append(a.bindings, parented) + } +} + +func (a *adapter) adaptMembers() { + + for _, iamBlock := range a.modules.GetResourcesByType("google_storage_bucket_iam_member") { + + var parented parentedMember + parented.blockID = iamBlock.ID() + parented.member = iam2.AdaptMember(iamBlock, a.modules) + + bucketAttr := iamBlock.GetAttribute("bucket") + if bucketAttr.IsString() { + parented.bucketID = bucketAttr.Value().AsString() + } + + if refBlock, err := a.modules.GetReferencedBlock(bucketAttr, iamBlock); err == nil { + if refBlock.TypeLabel() == GoogleStorageBucket { + parented.bucketBlockID = refBlock.ID() + } + } + + a.members = append(a.members, parented) + } + +} diff --git a/pkg/iac/adapters/terraform/kubernetes/adapt.go b/pkg/iac/adapters/terraform/kubernetes/adapt.go new file mode 100644 index 000000000000..52eed9db4052 --- /dev/null +++ b/pkg/iac/adapters/terraform/kubernetes/adapt.go @@ -0,0 +1,123 @@ +package kubernetes + +import ( + "regexp" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/providers/kubernetes" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +var versionRegex = regexp.MustCompile(`^v\d+(beta\d+)?$`) + +func Adapt(modules terraform.Modules) kubernetes.Kubernetes { + return kubernetes.Kubernetes{ + NetworkPolicies: adaptNetworkPolicies(modules), + } +} + +func adaptNetworkPolicies(modules terraform.Modules) []kubernetes.NetworkPolicy { + var networkPolicies []kubernetes.NetworkPolicy + for _, module := range modules { + for _, resource := range getBlocksIgnoreVersion(module, "resource", "kubernetes_network_policy") { + networkPolicies = append(networkPolicies, adaptNetworkPolicy(resource)) + } + } + return networkPolicies +} + +func adaptNetworkPolicy(resourceBlock *terraform.Block) kubernetes.NetworkPolicy { + + policy := kubernetes.NetworkPolicy{ + Metadata: resourceBlock.GetMetadata(), + Spec: kubernetes.NetworkPolicySpec{ + Metadata: resourceBlock.GetMetadata(), + Egress: kubernetes.Egress{ + Metadata: resourceBlock.GetMetadata(), + Ports: nil, + DestinationCIDRs: nil, + }, + Ingress: kubernetes.Ingress{ + Metadata: resourceBlock.GetMetadata(), + Ports: nil, + SourceCIDRs: nil, + }, + }, + } + + if specBlock := resourceBlock.GetBlock("spec"); specBlock.IsNotNil() { + if egressBlock := specBlock.GetBlock("egress"); egressBlock.IsNotNil() { + policy.Spec.Egress.Metadata = egressBlock.GetMetadata() + for _, port := range egressBlock.GetBlocks("ports") { + numberAttr := port.GetAttribute("number") + numberVal := numberAttr.AsStringValueOrDefault("", port) + + protocolAttr := port.GetAttribute("protocol") + protocolVal := protocolAttr.AsStringValueOrDefault("", port) + + policy.Spec.Egress.Ports = append(policy.Spec.Egress.Ports, kubernetes.Port{ + Metadata: port.GetMetadata(), + Number: numberVal, + Protocol: protocolVal, + }) + } + + for _, to := range egressBlock.GetBlocks("to") { + cidrAtrr := to.GetBlock("ip_block").GetAttribute("cidr") + cidrVal := cidrAtrr.AsStringValueOrDefault("", to) + + policy.Spec.Egress.DestinationCIDRs = append(policy.Spec.Egress.DestinationCIDRs, cidrVal) + } + } + + if ingressBlock := specBlock.GetBlock("ingress"); ingressBlock.IsNotNil() { + policy.Spec.Ingress.Metadata = ingressBlock.GetMetadata() + for _, port := range ingressBlock.GetBlocks("ports") { + numberAttr := port.GetAttribute("number") + numberVal := numberAttr.AsStringValueOrDefault("", port) + + protocolAttr := port.GetAttribute("protocol") + protocolVal := protocolAttr.AsStringValueOrDefault("", port) + + policy.Spec.Ingress.Ports = append(policy.Spec.Ingress.Ports, kubernetes.Port{ + Metadata: port.GetMetadata(), + Number: numberVal, + Protocol: protocolVal, + }) + } + + for _, from := range ingressBlock.GetBlocks("from") { + cidrAtrr := from.GetBlock("ip_block").GetAttribute("cidr") + cidrVal := cidrAtrr.AsStringValueOrDefault("", from) + + policy.Spec.Ingress.SourceCIDRs = append(policy.Spec.Ingress.SourceCIDRs, cidrVal) + } + } + } + + return policy +} + +// https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/guides/versioned-resources +func getBlocksIgnoreVersion(module *terraform.Module, blockType, resourceType string) terraform.Blocks { + var res terraform.Blocks + for _, block := range module.GetBlocks().OfType(blockType) { + if isMatchingTypeLabel(block.TypeLabel(), resourceType) { + res = append(res, block) + } + } + return res +} + +func isMatchingTypeLabel(typeLabel, resourceType string) bool { + if typeLabel == resourceType { + return true + } + + versionPart, found := strings.CutPrefix(typeLabel, resourceType+"_") + if !found { + return false + } + + return versionRegex.MatchString(versionPart) +} diff --git a/pkg/iac/adapters/terraform/kubernetes/adapt_test.go b/pkg/iac/adapters/terraform/kubernetes/adapt_test.go new file mode 100644 index 000000000000..eea390bd2e01 --- /dev/null +++ b/pkg/iac/adapters/terraform/kubernetes/adapt_test.go @@ -0,0 +1,60 @@ +package kubernetes + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsMatchingTypeLabel(t *testing.T) { + tests := []struct { + name string + typeLabel string + resourceType string + expected bool + }{ + { + name: "without version", + typeLabel: "kubernetes_network_policy", + resourceType: "kubernetes_network_policy", + expected: true, + }, + { + name: "v1", + typeLabel: "kubernetes_network_policy_v1", + resourceType: "kubernetes_network_policy", + expected: true, + }, + { + name: "beta version", + typeLabel: "kubernetes_horizontal_pod_autoscaler_v2beta2", + resourceType: "kubernetes_horizontal_pod_autoscaler", + expected: true, + }, + { + name: "another type of resource", + typeLabel: "kubernetes_network_policy", + resourceType: "kubernetes_horizontal_pod_autoscaler", + expected: false, + }, + { + name: "similar resource type", + typeLabel: "kubernetes_network_policy_test_v1", + resourceType: "kubernetes_network_policy", + expected: false, + }, + { + name: "empty resource type", + typeLabel: "kubernetes_network_policy_test_v1", + resourceType: "", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isMatchingTypeLabel(tt.typeLabel, tt.resourceType) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/computing/adapt.go b/pkg/iac/adapters/terraform/nifcloud/computing/adapt.go new file mode 100644 index 000000000000..399f492ba4e7 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/computing/adapt.go @@ -0,0 +1,16 @@ +package computing + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/computing" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) computing.Computing { + + sgAdapter := sgAdapter{sgRuleIDs: modules.GetChildResourceIDMapByType("nifcloud_security_group_rule")} + + return computing.Computing{ + SecurityGroups: sgAdapter.adaptSecurityGroups(modules), + Instances: adaptInstances(modules), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/computing/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/computing/adapt_test.go new file mode 100644 index 000000000000..be6efb30493f --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/computing/adapt_test.go @@ -0,0 +1,61 @@ +package computing + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLines(t *testing.T) { + src := ` +resource "nifcloud_instance" "example" { + security_group = nifcloud_security_group.example.group_name + + network_interface { + network_id = "net-COMMON_PRIVATE" + } +} + +resource "nifcloud_security_group" "example" { + group_name = "example" + description = "memo" +} + +resource "nifcloud_security_group_rule" "example" { + type = "IN" + security_group_names = [nifcloud_security_group.example.group_name] + from_port = 22 + to_port = 22 + protocol = "TCP" + description = "memo" + cidr_ip = "1.2.3.4/32" +} +` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Instances, 1) + require.Len(t, adapted.SecurityGroups, 1) + + instance := adapted.Instances[0] + sg := adapted.SecurityGroups[0] + + assert.Equal(t, 3, instance.SecurityGroup.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, instance.SecurityGroup.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 6, instance.NetworkInterfaces[0].NetworkID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, instance.NetworkInterfaces[0].NetworkID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 12, sg.Description.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 12, sg.Description.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 21, sg.IngressRules[0].Description.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 21, sg.IngressRules[0].Description.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 22, sg.IngressRules[0].CIDR.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 22, sg.IngressRules[0].CIDR.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/nifcloud/computing/instance.go b/pkg/iac/adapters/terraform/nifcloud/computing/instance.go new file mode 100644 index 000000000000..ebbce94439dd --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/computing/instance.go @@ -0,0 +1,35 @@ +package computing + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/computing" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func adaptInstances(modules terraform.Modules) []computing.Instance { + var instances []computing.Instance + + for _, resource := range modules.GetResourcesByType("nifcloud_instance") { + instances = append(instances, adaptInstance(resource)) + } + return instances +} + +func adaptInstance(resource *terraform.Block) computing.Instance { + var networkInterfaces []computing.NetworkInterface + networkInterfaceBlocks := resource.GetBlocks("network_interface") + for _, networkInterfaceBlock := range networkInterfaceBlocks { + networkInterfaces = append( + networkInterfaces, + computing.NetworkInterface{ + Metadata: networkInterfaceBlock.GetMetadata(), + NetworkID: networkInterfaceBlock.GetAttribute("network_id").AsStringValueOrDefault("", resource), + }, + ) + } + + return computing.Instance{ + Metadata: resource.GetMetadata(), + SecurityGroup: resource.GetAttribute("security_group").AsStringValueOrDefault("", resource), + NetworkInterfaces: networkInterfaces, + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/computing/instance_test.go b/pkg/iac/adapters/terraform/nifcloud/computing/instance_test.go new file mode 100644 index 000000000000..8055681c1353 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/computing/instance_test.go @@ -0,0 +1,69 @@ +package computing + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/computing" +) + +func Test_adaptInstances(t *testing.T) { + tests := []struct { + name string + terraform string + expected []computing.Instance + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_instance" "my_example" { + security_group = "example-security-group" + network_interface { + network_id = "net-COMMON_PRIVATE" + } + } +`, + expected: []computing.Instance{{ + Metadata: iacTypes.NewTestMetadata(), + SecurityGroup: iacTypes.String("example-security-group", iacTypes.NewTestMetadata()), + NetworkInterfaces: []computing.NetworkInterface{ + { + Metadata: iacTypes.NewTestMetadata(), + NetworkID: iacTypes.String("net-COMMON_PRIVATE", iacTypes.NewTestMetadata()), + }, + }, + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_instance" "my_example" { + network_interface { + } + } +`, + + expected: []computing.Instance{{ + Metadata: iacTypes.NewTestMetadata(), + SecurityGroup: iacTypes.String("", iacTypes.NewTestMetadata()), + NetworkInterfaces: []computing.NetworkInterface{ + { + Metadata: iacTypes.NewTestMetadata(), + NetworkID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptInstances(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/computing/security_group.go b/pkg/iac/adapters/terraform/nifcloud/computing/security_group.go new file mode 100644 index 000000000000..38699a6f2555 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/computing/security_group.go @@ -0,0 +1,76 @@ +package computing + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/computing" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type sgAdapter struct { + sgRuleIDs terraform.ResourceIDResolutions +} + +func (a *sgAdapter) adaptSecurityGroups(modules terraform.Modules) []computing.SecurityGroup { + var securityGroups []computing.SecurityGroup + for _, resource := range modules.GetResourcesByType("nifcloud_security_group") { + securityGroups = append(securityGroups, a.adaptSecurityGroup(resource, modules)) + } + orphanResources := modules.GetResourceByIDs(a.sgRuleIDs.Orphans()...) + if len(orphanResources) > 0 { + orphanage := computing.SecurityGroup{ + Metadata: iacTypes.NewUnmanagedMetadata(), + Description: iacTypes.StringDefault("", iacTypes.NewUnmanagedMetadata()), + IngressRules: nil, + } + for _, sgRule := range orphanResources { + if sgRule.GetAttribute("type").Equals("IN") { + orphanage.IngressRules = append(orphanage.IngressRules, adaptSGRule(sgRule, modules)) + } + if sgRule.GetAttribute("type").Equals("OUT") { + orphanage.EgressRules = append(orphanage.EgressRules, adaptSGRule(sgRule, modules)) + } + } + securityGroups = append(securityGroups, orphanage) + } + + return securityGroups +} + +func (a *sgAdapter) adaptSecurityGroup(resource *terraform.Block, module terraform.Modules) computing.SecurityGroup { + var ingressRules, egressRules []computing.SecurityGroupRule + + descriptionAttr := resource.GetAttribute("description") + descriptionVal := descriptionAttr.AsStringValueOrDefault("", resource) + + rulesBlocks := module.GetReferencingResources(resource, "nifcloud_security_group_rule", "security_group_names") + for _, ruleBlock := range rulesBlocks { + a.sgRuleIDs.Resolve(ruleBlock.ID()) + if ruleBlock.GetAttribute("type").Equals("IN") { + ingressRules = append(ingressRules, adaptSGRule(ruleBlock, module)) + } + if ruleBlock.GetAttribute("type").Equals("OUT") { + egressRules = append(egressRules, adaptSGRule(ruleBlock, module)) + } + } + + return computing.SecurityGroup{ + Metadata: resource.GetMetadata(), + Description: descriptionVal, + IngressRules: ingressRules, + EgressRules: egressRules, + } +} + +func adaptSGRule(resource *terraform.Block, modules terraform.Modules) computing.SecurityGroupRule { + ruleDescAttr := resource.GetAttribute("description") + ruleDescVal := ruleDescAttr.AsStringValueOrDefault("", resource) + + cidrAttr := resource.GetAttribute("cidr_ip") + cidrVal := cidrAttr.AsStringValueOrDefault("", resource) + + return computing.SecurityGroupRule{ + Metadata: resource.GetMetadata(), + Description: ruleDescVal, + CIDR: cidrVal, + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/computing/security_group_test.go b/pkg/iac/adapters/terraform/nifcloud/computing/security_group_test.go new file mode 100644 index 000000000000..250f99a96fdc --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/computing/security_group_test.go @@ -0,0 +1,84 @@ +package computing + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/computing" +) + +func Test_adaptSecurityGroups(t *testing.T) { + tests := []struct { + name string + terraform string + expected []computing.SecurityGroup + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_security_group" "example" { + group_name = "example" + description = "memo" + } + + resource "nifcloud_security_group_rule" "example" { + type = "IN" + security_group_names = [nifcloud_security_group.example.group_name] + from_port = 22 + to_port = 22 + protocol = "TCP" + description = "memo" + cidr_ip = "1.2.3.4/32" + } +`, + expected: []computing.SecurityGroup{{ + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("memo", iacTypes.NewTestMetadata()), + IngressRules: []computing.SecurityGroupRule{ + { + Metadata: iacTypes.NewTestMetadata(), + CIDR: iacTypes.String("1.2.3.4/32", iacTypes.NewTestMetadata()), + Description: iacTypes.String("memo", iacTypes.NewTestMetadata()), + }, + }, + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_security_group" "example" { + } + + resource "nifcloud_security_group_rule" "example" { + type = "IN" + security_group_names = [nifcloud_security_group.example.group_name] + } + +`, + + expected: []computing.SecurityGroup{{ + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("", iacTypes.NewTestMetadata()), + IngressRules: []computing.SecurityGroupRule{ + { + Metadata: iacTypes.NewTestMetadata(), + CIDR: iacTypes.String("", iacTypes.NewTestMetadata()), + Description: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + sgAdapter := sgAdapter{sgRuleIDs: modules.GetChildResourceIDMapByType("nifcloud_security_group_rule")} + adapted := sgAdapter.adaptSecurityGroups(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/dns/adapt.go b/pkg/iac/adapters/terraform/nifcloud/dns/adapt.go new file mode 100644 index 000000000000..c1118e98ef23 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/dns/adapt.go @@ -0,0 +1,12 @@ +package dns + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/dns" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) dns.DNS { + return dns.DNS{ + Records: adaptRecords(modules), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/dns/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/dns/adapt_test.go new file mode 100644 index 000000000000..38ff119054bd --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/dns/adapt_test.go @@ -0,0 +1,32 @@ +package dns + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLines(t *testing.T) { + src := ` +resource "nifcloud_dns_record" "example" { + type = "A" + record = "example-record" +} +` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.Records, 1) + + record := adapted.Records[0] + + assert.Equal(t, 3, record.Type.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, record.Type.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 4, record.Record.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, record.Record.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/nifcloud/dns/record.go b/pkg/iac/adapters/terraform/nifcloud/dns/record.go new file mode 100644 index 000000000000..84e61889cac0 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/dns/record.go @@ -0,0 +1,23 @@ +package dns + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/dns" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func adaptRecords(modules terraform.Modules) []dns.Record { + var records []dns.Record + + for _, resource := range modules.GetResourcesByType("nifcloud_dns_record") { + records = append(records, adaptRecord(resource)) + } + return records +} + +func adaptRecord(resource *terraform.Block) dns.Record { + return dns.Record{ + Metadata: resource.GetMetadata(), + Record: resource.GetAttribute("record").AsStringValueOrDefault("", resource), + Type: resource.GetAttribute("type").AsStringValueOrDefault("", resource), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/dns/record_test.go b/pkg/iac/adapters/terraform/nifcloud/dns/record_test.go new file mode 100644 index 000000000000..75fd1c8d06bc --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/dns/record_test.go @@ -0,0 +1,54 @@ +package dns + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/dns" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Test_adaptRecords(t *testing.T) { + tests := []struct { + name string + terraform string + expected []dns.Record + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_dns_record" "example" { + type = "A" + record = "example-record" + } +`, + expected: []dns.Record{{ + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String("A", iacTypes.NewTestMetadata()), + Record: iacTypes.String("example-record", iacTypes.NewTestMetadata()), + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_dns_record" "example" { + } +`, + + expected: []dns.Record{{ + Metadata: iacTypes.NewTestMetadata(), + Type: iacTypes.String("", iacTypes.NewTestMetadata()), + Record: iacTypes.String("", iacTypes.NewTestMetadata()), + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptRecords(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/nas/adapt.go b/pkg/iac/adapters/terraform/nifcloud/nas/adapt.go new file mode 100644 index 000000000000..c1b60fc551e2 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/nas/adapt.go @@ -0,0 +1,13 @@ +package nas + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/nas" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) nas.NAS { + return nas.NAS{ + NASSecurityGroups: adaptNASSecurityGroups(modules), + NASInstances: adaptNASInstances(modules), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/nas/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/nas/adapt_test.go new file mode 100644 index 000000000000..b43b874974b8 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/nas/adapt_test.go @@ -0,0 +1,44 @@ +package nas + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLines(t *testing.T) { + src := ` +resource "nifcloud_nas_instance" "example" { + network_id = "example-network" +} + +resource "nifcloud_nas_security_group" "example" { + description = "memo" + + rule { + cidr_ip = "0.0.0.0/0" + } +} +` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.NASInstances, 1) + require.Len(t, adapted.NASSecurityGroups, 1) + + nasInstance := adapted.NASInstances[0] + nasSecurityGroup := adapted.NASSecurityGroups[0] + + assert.Equal(t, 3, nasInstance.NetworkID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, nasInstance.NetworkID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 7, nasSecurityGroup.Description.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, nasSecurityGroup.Description.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 10, nasSecurityGroup.CIDRs[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 10, nasSecurityGroup.CIDRs[0].GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/nifcloud/nas/nas_instance.go b/pkg/iac/adapters/terraform/nifcloud/nas/nas_instance.go new file mode 100644 index 000000000000..e04024f0c4dd --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/nas/nas_instance.go @@ -0,0 +1,22 @@ +package nas + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/nas" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func adaptNASInstances(modules terraform.Modules) []nas.NASInstance { + var nasInstances []nas.NASInstance + + for _, resource := range modules.GetResourcesByType("nifcloud_nas_instance") { + nasInstances = append(nasInstances, adaptNASInstance(resource)) + } + return nasInstances +} + +func adaptNASInstance(resource *terraform.Block) nas.NASInstance { + return nas.NASInstance{ + Metadata: resource.GetMetadata(), + NetworkID: resource.GetAttribute("network_id").AsStringValueOrDefault("net-COMMON_PRIVATE", resource), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/nas/nas_instance_test.go b/pkg/iac/adapters/terraform/nifcloud/nas/nas_instance_test.go new file mode 100644 index 000000000000..29e52ea65037 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/nas/nas_instance_test.go @@ -0,0 +1,52 @@ +package nas + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/nas" +) + +func Test_adaptNASInstances(t *testing.T) { + tests := []struct { + name string + terraform string + expected []nas.NASInstance + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_nas_instance" "example" { + network_id = "example-network" + } +`, + expected: []nas.NASInstance{{ + Metadata: iacTypes.NewTestMetadata(), + NetworkID: iacTypes.String("example-network", iacTypes.NewTestMetadata()), + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_nas_instance" "example" { + } +`, + + expected: []nas.NASInstance{{ + Metadata: iacTypes.NewTestMetadata(), + NetworkID: iacTypes.String("net-COMMON_PRIVATE", iacTypes.NewTestMetadata()), + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptNASInstances(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/nas/nas_security_group.go b/pkg/iac/adapters/terraform/nifcloud/nas/nas_security_group.go new file mode 100644 index 000000000000..468cbddce309 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/nas/nas_security_group.go @@ -0,0 +1,30 @@ +package nas + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/nas" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func adaptNASSecurityGroups(modules terraform.Modules) []nas.NASSecurityGroup { + var nasSecurityGroups []nas.NASSecurityGroup + + for _, resource := range modules.GetResourcesByType("nifcloud_nas_security_group") { + nasSecurityGroups = append(nasSecurityGroups, adaptNASSecurityGroup(resource)) + } + return nasSecurityGroups +} + +func adaptNASSecurityGroup(resource *terraform.Block) nas.NASSecurityGroup { + var cidrs []iacTypes.StringValue + + for _, rule := range resource.GetBlocks("rule") { + cidrs = append(cidrs, rule.GetAttribute("cidr_ip").AsStringValueOrDefault("", resource)) + } + + return nas.NASSecurityGroup{ + Metadata: resource.GetMetadata(), + Description: resource.GetAttribute("description").AsStringValueOrDefault("", resource), + CIDRs: cidrs, + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/nas/nas_security_group_test.go b/pkg/iac/adapters/terraform/nifcloud/nas/nas_security_group_test.go new file mode 100644 index 000000000000..786aec2fa6d6 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/nas/nas_security_group_test.go @@ -0,0 +1,64 @@ +package nas + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/nas" +) + +func Test_adaptNASSecurityGroups(t *testing.T) { + tests := []struct { + name string + terraform string + expected []nas.NASSecurityGroup + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_nas_security_group" "example" { + description = "memo" + + rule { + cidr_ip = "0.0.0.0/0" + } + } +`, + expected: []nas.NASSecurityGroup{{ + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("memo", iacTypes.NewTestMetadata()), + CIDRs: []iacTypes.StringValue{ + iacTypes.String("0.0.0.0/0", iacTypes.NewTestMetadata()), + }, + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_nas_security_group" "example" { + rule { + } + } +`, + + expected: []nas.NASSecurityGroup{{ + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("", iacTypes.NewTestMetadata()), + CIDRs: []iacTypes.StringValue{ + iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptNASSecurityGroups(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/network/adapt.go b/pkg/iac/adapters/terraform/nifcloud/network/adapt.go new file mode 100644 index 000000000000..b3006abcd1d8 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/network/adapt.go @@ -0,0 +1,16 @@ +package network + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/network" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) network.Network { + + return network.Network{ + ElasticLoadBalancers: adaptElasticLoadBalancers(modules), + LoadBalancers: adaptLoadBalancers(modules), + Routers: adaptRouters(modules), + VpnGateways: adaptVpnGateways(modules), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/network/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/network/adapt_test.go new file mode 100644 index 000000000000..9a4277b28558 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/network/adapt_test.go @@ -0,0 +1,83 @@ +package network + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLines(t *testing.T) { + src := ` +resource "nifcloud_elb" "example" { + protocol = "HTTP" + + network_interface { + network_id = "net-COMMON_PRIVATE" + is_vip_network = false + } +} + +resource "nifcloud_load_balancer" "example" { + ssl_policy_id = "example-ssl-policy-id" + load_balancer_port = 8080 +} + +resource "nifcloud_router" "example" { + security_group = nifcloud_security_group.example.group_name + + network_interface { + network_id = "net-COMMON_PRIVATE" + } +} + +resource "nifcloud_security_group" "example" { + group_name = "example" + description = "memo" +} + +resource "nifcloud_vpn_gateway" "example" { + security_group = nifcloud_security_group.example.group_name +} +` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.ElasticLoadBalancers, 1) + require.Len(t, adapted.LoadBalancers, 1) + require.Len(t, adapted.Routers, 1) + require.Len(t, adapted.VpnGateways, 1) + + elb := adapted.ElasticLoadBalancers[0] + lb := adapted.LoadBalancers[0] + router := adapted.Routers[0] + vpngw := adapted.VpnGateways[0] + + assert.Equal(t, 3, elb.Listeners[0].Protocol.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, elb.Listeners[0].Protocol.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 6, elb.NetworkInterfaces[0].NetworkID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, elb.NetworkInterfaces[0].NetworkID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 7, elb.NetworkInterfaces[0].IsVipNetwork.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, elb.NetworkInterfaces[0].IsVipNetwork.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 12, lb.Listeners[0].TLSPolicy.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 12, lb.Listeners[0].TLSPolicy.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 13, lb.Listeners[0].Protocol.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 13, lb.Listeners[0].Protocol.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 17, router.SecurityGroup.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 17, router.SecurityGroup.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 20, router.NetworkInterfaces[0].NetworkID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 20, router.NetworkInterfaces[0].NetworkID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 30, vpngw.SecurityGroup.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 30, vpngw.SecurityGroup.GetMetadata().Range().GetEndLine()) + +} diff --git a/pkg/iac/adapters/terraform/nifcloud/network/elastic_load_balancer.go b/pkg/iac/adapters/terraform/nifcloud/network/elastic_load_balancer.go new file mode 100644 index 000000000000..89478aadea6e --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/network/elastic_load_balancer.go @@ -0,0 +1,50 @@ +package network + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/network" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func adaptElasticLoadBalancers(modules terraform.Modules) []network.ElasticLoadBalancer { + var elasticLoadBalancers []network.ElasticLoadBalancer + + for _, resource := range modules.GetResourcesByType("nifcloud_elb") { + elasticLoadBalancers = append(elasticLoadBalancers, adaptElasticLoadBalancer(resource, modules)) + } + return elasticLoadBalancers +} + +func adaptElasticLoadBalancer(resource *terraform.Block, modules terraform.Modules) network.ElasticLoadBalancer { + var listeners []network.ElasticLoadBalancerListener + var networkInterfaces []network.NetworkInterface + + networkInterfaceBlocks := resource.GetBlocks("network_interface") + for _, networkInterfaceBlock := range networkInterfaceBlocks { + networkInterfaces = append( + networkInterfaces, + network.NetworkInterface{ + Metadata: networkInterfaceBlock.GetMetadata(), + NetworkID: networkInterfaceBlock.GetAttribute("network_id").AsStringValueOrDefault("", resource), + IsVipNetwork: networkInterfaceBlock.GetAttribute("is_vip_network").AsBoolValueOrDefault(true, resource), + }, + ) + } + + listeners = append(listeners, adaptElasticLoadBalancerListener(resource)) + for _, listenerBlock := range modules.GetReferencingResources(resource, "nifcloud_elb_listener", "elb_id") { + listeners = append(listeners, adaptElasticLoadBalancerListener(listenerBlock)) + } + + return network.ElasticLoadBalancer{ + Metadata: resource.GetMetadata(), + NetworkInterfaces: networkInterfaces, + Listeners: listeners, + } +} + +func adaptElasticLoadBalancerListener(resource *terraform.Block) network.ElasticLoadBalancerListener { + return network.ElasticLoadBalancerListener{ + Metadata: resource.GetMetadata(), + Protocol: resource.GetAttribute("protocol").AsStringValueOrDefault("", resource), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/network/elastic_load_balancer_test.go b/pkg/iac/adapters/terraform/nifcloud/network/elastic_load_balancer_test.go new file mode 100644 index 000000000000..252396e387e0 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/network/elastic_load_balancer_test.go @@ -0,0 +1,88 @@ +package network + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/network" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Test_adaptElasticLoadBalancers(t *testing.T) { + tests := []struct { + name string + terraform string + expected []network.ElasticLoadBalancer + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_elb" "example" { + protocol = "HTTP" + + network_interface { + network_id = "net-COMMON_PRIVATE" + is_vip_network = false + } + } + + resource "nifcloud_elb_listener" "example" { + elb_id = nifcloud_elb.example.id + protocol = "HTTPS" + } +`, + expected: []network.ElasticLoadBalancer{{ + Metadata: iacTypes.NewTestMetadata(), + NetworkInterfaces: []network.NetworkInterface{ + { + Metadata: iacTypes.NewTestMetadata(), + NetworkID: iacTypes.String("net-COMMON_PRIVATE", iacTypes.NewTestMetadata()), + IsVipNetwork: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }, + }, + Listeners: []network.ElasticLoadBalancerListener{ + { + Metadata: iacTypes.NewTestMetadata(), + Protocol: iacTypes.String("HTTP", iacTypes.NewTestMetadata()), + }, + { + Metadata: iacTypes.NewTestMetadata(), + Protocol: iacTypes.String("HTTPS", iacTypes.NewTestMetadata()), + }, + }, + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_elb" "example" { + network_interface { + } + } +`, + + expected: []network.ElasticLoadBalancer{{ + Metadata: iacTypes.NewTestMetadata(), + NetworkInterfaces: []network.NetworkInterface{ + { + Metadata: iacTypes.NewTestMetadata(), + NetworkID: iacTypes.String("", iacTypes.NewTestMetadata()), + IsVipNetwork: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + Listeners: []network.ElasticLoadBalancerListener{{ + Metadata: iacTypes.NewTestMetadata(), + }}, + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptElasticLoadBalancers(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/network/load_balancer.go b/pkg/iac/adapters/terraform/nifcloud/network/load_balancer.go new file mode 100644 index 000000000000..d137f799756d --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/network/load_balancer.go @@ -0,0 +1,67 @@ +package network + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/network" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func adaptLoadBalancers(modules terraform.Modules) []network.LoadBalancer { + var loadBalancers []network.LoadBalancer + + for _, resource := range modules.GetResourcesByType("nifcloud_load_balancer") { + loadBalancers = append(loadBalancers, adaptLoadBalancer(resource, modules)) + } + + return loadBalancers +} + +func adaptLoadBalancer(resource *terraform.Block, modules terraform.Modules) network.LoadBalancer { + var listeners []network.LoadBalancerListener + + listeners = append(listeners, adaptListener(resource)) + for _, listenerBlock := range modules.GetReferencingResources(resource, "nifcloud_load_balancer_listener", "load_balancer_name") { + listeners = append(listeners, adaptListener(listenerBlock)) + } + + return network.LoadBalancer{ + Metadata: resource.GetMetadata(), + Listeners: listeners, + } +} + +func adaptListener(resource *terraform.Block) network.LoadBalancerListener { + protocolVal := iacTypes.String("", resource.GetMetadata()) + policyVal := iacTypes.String("", resource.GetMetadata()) + + portAttr := resource.GetAttribute("load_balancer_port") + if portAttr.IsNotNil() && portAttr.IsNumber() { + port := portAttr.AsNumber() + switch port { + case 21: + protocolVal = iacTypes.String("FTP", portAttr.GetMetadata()) + case 80: + protocolVal = iacTypes.String("HTTP", portAttr.GetMetadata()) + case 443: + protocolVal = iacTypes.String("HTTPS", portAttr.GetMetadata()) + default: + protocolVal = iacTypes.String("custom", portAttr.GetMetadata()) + } + } + + policyIDAttr := resource.GetAttribute("ssl_policy_id") + if policyIDAttr.IsNotNil() && policyIDAttr.IsString() { + policyVal = policyIDAttr.AsStringValueOrDefault("", resource) + } + + policyNameAttr := resource.GetAttribute("ssl_policy_name") + if policyNameAttr.IsNotNil() && policyNameAttr.IsString() { + policyVal = policyNameAttr.AsStringValueOrDefault("", resource) + } + + return network.LoadBalancerListener{ + Metadata: resource.GetMetadata(), + Protocol: protocolVal, + TLSPolicy: policyVal, + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/network/load_balancer_test.go b/pkg/iac/adapters/terraform/nifcloud/network/load_balancer_test.go new file mode 100644 index 000000000000..b94b0e43353d --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/network/load_balancer_test.go @@ -0,0 +1,73 @@ +package network + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/network" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Test_adaptLoadBalancers(t *testing.T) { + tests := []struct { + name string + terraform string + expected []network.LoadBalancer + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_load_balancer" "example" { + load_balancer_name = "example" + load_balancer_port = 80 + ssl_policy_id = "example-ssl-policy-id" + } + + resource "nifcloud_load_balancer_listener" "example" { + load_balancer_name = nifcloud_load_balancer.example.load_balancer_name + load_balancer_port = 443 + ssl_policy_name = "example-ssl-policy-name" + } + +`, + expected: []network.LoadBalancer{{ + Metadata: iacTypes.NewTestMetadata(), + Listeners: []network.LoadBalancerListener{ + { + Metadata: iacTypes.NewTestMetadata(), + TLSPolicy: iacTypes.String("example-ssl-policy-id", iacTypes.NewTestMetadata()), + Protocol: iacTypes.String("HTTP", iacTypes.NewTestMetadata()), + }, + { + Metadata: iacTypes.NewTestMetadata(), + TLSPolicy: iacTypes.String("example-ssl-policy-name", iacTypes.NewTestMetadata()), + Protocol: iacTypes.String("HTTPS", iacTypes.NewTestMetadata()), + }, + }, + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_load_balancer" "example" { + } +`, + + expected: []network.LoadBalancer{{ + Metadata: iacTypes.NewTestMetadata(), + Listeners: []network.LoadBalancerListener{{ + Metadata: iacTypes.NewTestMetadata(), + }}, + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptLoadBalancers(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/network/router.go b/pkg/iac/adapters/terraform/nifcloud/network/router.go new file mode 100644 index 000000000000..d75595279d7d --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/network/router.go @@ -0,0 +1,37 @@ +package network + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/network" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func adaptRouters(modules terraform.Modules) []network.Router { + var routers []network.Router + + for _, resource := range modules.GetResourcesByType("nifcloud_router") { + routers = append(routers, adaptRouter(resource)) + } + return routers +} + +func adaptRouter(resource *terraform.Block) network.Router { + var networkInterfaces []network.NetworkInterface + networkInterfaceBlocks := resource.GetBlocks("network_interface") + for _, networkInterfaceBlock := range networkInterfaceBlocks { + networkInterfaces = append( + networkInterfaces, + network.NetworkInterface{ + Metadata: networkInterfaceBlock.GetMetadata(), + NetworkID: networkInterfaceBlock.GetAttribute("network_id").AsStringValueOrDefault("", resource), + IsVipNetwork: types.Bool(false, networkInterfaceBlock.GetMetadata()), + }, + ) + } + + return network.Router{ + Metadata: resource.GetMetadata(), + SecurityGroup: resource.GetAttribute("security_group").AsStringValueOrDefault("", resource), + NetworkInterfaces: networkInterfaces, + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/network/router_test.go b/pkg/iac/adapters/terraform/nifcloud/network/router_test.go new file mode 100644 index 000000000000..e60c554917f6 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/network/router_test.go @@ -0,0 +1,68 @@ +package network + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/network" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Test_adaptRouters(t *testing.T) { + tests := []struct { + name string + terraform string + expected []network.Router + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_router" "example" { + security_group = "example-security-group" + network_interface { + network_id = "net-COMMON_PRIVATE" + } + } +`, + expected: []network.Router{{ + Metadata: iacTypes.NewTestMetadata(), + SecurityGroup: iacTypes.String("example-security-group", iacTypes.NewTestMetadata()), + NetworkInterfaces: []network.NetworkInterface{ + { + Metadata: iacTypes.NewTestMetadata(), + NetworkID: iacTypes.String("net-COMMON_PRIVATE", iacTypes.NewTestMetadata()), + }, + }, + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_router" "example" { + network_interface { + } + } +`, + + expected: []network.Router{{ + Metadata: iacTypes.NewTestMetadata(), + SecurityGroup: iacTypes.String("", iacTypes.NewTestMetadata()), + NetworkInterfaces: []network.NetworkInterface{ + { + Metadata: iacTypes.NewTestMetadata(), + NetworkID: iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }, + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptRouters(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/network/vpn_gateway.go b/pkg/iac/adapters/terraform/nifcloud/network/vpn_gateway.go new file mode 100644 index 000000000000..dcb6812a1ef7 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/network/vpn_gateway.go @@ -0,0 +1,22 @@ +package network + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/network" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func adaptVpnGateways(modules terraform.Modules) []network.VpnGateway { + var vpnGateways []network.VpnGateway + + for _, resource := range modules.GetResourcesByType("nifcloud_vpn_gateway") { + vpnGateways = append(vpnGateways, adaptVpnGateway(resource)) + } + return vpnGateways +} + +func adaptVpnGateway(resource *terraform.Block) network.VpnGateway { + return network.VpnGateway{ + Metadata: resource.GetMetadata(), + SecurityGroup: resource.GetAttribute("security_group").AsStringValueOrDefault("", resource), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/network/vpn_gateway_test.go b/pkg/iac/adapters/terraform/nifcloud/network/vpn_gateway_test.go new file mode 100644 index 000000000000..b9499c078518 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/network/vpn_gateway_test.go @@ -0,0 +1,51 @@ +package network + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/network" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Test_adaptVpnGateways(t *testing.T) { + tests := []struct { + name string + terraform string + expected []network.VpnGateway + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_vpn_gateway" "example" { + security_group = "example-security-group" + } +`, + expected: []network.VpnGateway{{ + Metadata: iacTypes.NewTestMetadata(), + SecurityGroup: iacTypes.String("example-security-group", iacTypes.NewTestMetadata()), + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_vpn_gateway" "example" { + } +`, + + expected: []network.VpnGateway{{ + Metadata: iacTypes.NewTestMetadata(), + SecurityGroup: iacTypes.String("", iacTypes.NewTestMetadata()), + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptVpnGateways(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/nifcloud.go b/pkg/iac/adapters/terraform/nifcloud/nifcloud.go new file mode 100644 index 000000000000..5f17fe6b235c --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/nifcloud.go @@ -0,0 +1,23 @@ +package nifcloud + +import ( + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/nifcloud/computing" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/nifcloud/dns" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/nifcloud/nas" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/nifcloud/network" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/nifcloud/rdb" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/nifcloud/sslcertificate" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) nifcloud.Nifcloud { + return nifcloud.Nifcloud{ + Computing: computing.Adapt(modules), + DNS: dns.Adapt(modules), + NAS: nas.Adapt(modules), + Network: network.Adapt(modules), + RDB: rdb.Adapt(modules), + SSLCertificate: sslcertificate.Adapt(modules), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/rdb/adapt.go b/pkg/iac/adapters/terraform/nifcloud/rdb/adapt.go new file mode 100644 index 000000000000..fd35b2236187 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/rdb/adapt.go @@ -0,0 +1,13 @@ +package rdb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/rdb" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) rdb.RDB { + return rdb.RDB{ + DBSecurityGroups: adaptDBSecurityGroups(modules), + DBInstances: adaptDBInstances(modules), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/rdb/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/rdb/adapt_test.go new file mode 100644 index 000000000000..5360773bd505 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/rdb/adapt_test.go @@ -0,0 +1,60 @@ +package rdb + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLines(t *testing.T) { + src := ` +resource "nifcloud_db_instance" "example" { + publicly_accessible = false + engine = "MySQL" + engine_version = "5.7.15" + backup_retention_period = 2 + network_id = "example-network" +} + +resource "nifcloud_db_security_group" "example" { + description = "memo" + + rule { + cidr_ip = "0.0.0.0/0" + } +} +` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.DBInstances, 1) + require.Len(t, adapted.DBSecurityGroups, 1) + + dbInstance := adapted.DBInstances[0] + dbSecurityGroup := adapted.DBSecurityGroups[0] + + assert.Equal(t, 3, dbInstance.PublicAccess.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, dbInstance.PublicAccess.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 4, dbInstance.Engine.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 4, dbInstance.Engine.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 5, dbInstance.EngineVersion.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 5, dbInstance.EngineVersion.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 6, dbInstance.BackupRetentionPeriodDays.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 6, dbInstance.BackupRetentionPeriodDays.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 7, dbInstance.NetworkID.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 7, dbInstance.NetworkID.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 11, dbSecurityGroup.Description.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 11, dbSecurityGroup.Description.GetMetadata().Range().GetEndLine()) + + assert.Equal(t, 14, dbSecurityGroup.CIDRs[0].GetMetadata().Range().GetStartLine()) + assert.Equal(t, 14, dbSecurityGroup.CIDRs[0].GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/nifcloud/rdb/db_instance.go b/pkg/iac/adapters/terraform/nifcloud/rdb/db_instance.go new file mode 100644 index 000000000000..69757e41e9a2 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/rdb/db_instance.go @@ -0,0 +1,26 @@ +package rdb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/rdb" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func adaptDBInstances(modules terraform.Modules) []rdb.DBInstance { + var dbInstances []rdb.DBInstance + + for _, resource := range modules.GetResourcesByType("nifcloud_db_instance") { + dbInstances = append(dbInstances, adaptDBInstance(resource)) + } + return dbInstances +} + +func adaptDBInstance(resource *terraform.Block) rdb.DBInstance { + return rdb.DBInstance{ + Metadata: resource.GetMetadata(), + BackupRetentionPeriodDays: resource.GetAttribute("backup_retention_period").AsIntValueOrDefault(0, resource), + Engine: resource.GetAttribute("engine").AsStringValueOrDefault("", resource), + EngineVersion: resource.GetAttribute("engine_version").AsStringValueOrDefault("", resource), + NetworkID: resource.GetAttribute("network_id").AsStringValueOrDefault("net-COMMON_PRIVATE", resource), + PublicAccess: resource.GetAttribute("publicly_accessible").AsBoolValueOrDefault(true, resource), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/rdb/db_instance_test.go b/pkg/iac/adapters/terraform/nifcloud/rdb/db_instance_test.go new file mode 100644 index 000000000000..de09fe59e4e8 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/rdb/db_instance_test.go @@ -0,0 +1,64 @@ +package rdb + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/rdb" +) + +func Test_adaptDBInstances(t *testing.T) { + tests := []struct { + name string + terraform string + expected []rdb.DBInstance + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_db_instance" "example" { + backup_retention_period = 2 + engine = "MySQL" + engine_version = "5.7.15" + publicly_accessible = false + network_id = "example-network" + } +`, + expected: []rdb.DBInstance{{ + Metadata: iacTypes.NewTestMetadata(), + BackupRetentionPeriodDays: iacTypes.Int(2, iacTypes.NewTestMetadata()), + Engine: iacTypes.String("MySQL", iacTypes.NewTestMetadata()), + EngineVersion: iacTypes.String("5.7.15", iacTypes.NewTestMetadata()), + NetworkID: iacTypes.String("example-network", iacTypes.NewTestMetadata()), + PublicAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_db_instance" "example" { + } +`, + + expected: []rdb.DBInstance{{ + Metadata: iacTypes.NewTestMetadata(), + BackupRetentionPeriodDays: iacTypes.Int(0, iacTypes.NewTestMetadata()), + Engine: iacTypes.String("", iacTypes.NewTestMetadata()), + EngineVersion: iacTypes.String("", iacTypes.NewTestMetadata()), + NetworkID: iacTypes.String("net-COMMON_PRIVATE", iacTypes.NewTestMetadata()), + PublicAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptDBInstances(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/rdb/db_security_group.go b/pkg/iac/adapters/terraform/nifcloud/rdb/db_security_group.go new file mode 100644 index 000000000000..e78d95ea0e3f --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/rdb/db_security_group.go @@ -0,0 +1,30 @@ +package rdb + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/rdb" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func adaptDBSecurityGroups(modules terraform.Modules) []rdb.DBSecurityGroup { + var dbSecurityGroups []rdb.DBSecurityGroup + + for _, resource := range modules.GetResourcesByType("nifcloud_db_security_group") { + dbSecurityGroups = append(dbSecurityGroups, adaptDBSecurityGroup(resource)) + } + return dbSecurityGroups +} + +func adaptDBSecurityGroup(resource *terraform.Block) rdb.DBSecurityGroup { + var cidrs []iacTypes.StringValue + + for _, rule := range resource.GetBlocks("rule") { + cidrs = append(cidrs, rule.GetAttribute("cidr_ip").AsStringValueOrDefault("", resource)) + } + + return rdb.DBSecurityGroup{ + Metadata: resource.GetMetadata(), + Description: resource.GetAttribute("description").AsStringValueOrDefault("", resource), + CIDRs: cidrs, + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/rdb/db_security_group_test.go b/pkg/iac/adapters/terraform/nifcloud/rdb/db_security_group_test.go new file mode 100644 index 000000000000..ab4fc34f1384 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/rdb/db_security_group_test.go @@ -0,0 +1,64 @@ +package rdb + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/rdb" +) + +func Test_adaptDBSecurityGroups(t *testing.T) { + tests := []struct { + name string + terraform string + expected []rdb.DBSecurityGroup + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_db_security_group" "example" { + description = "memo" + + rule { + cidr_ip = "0.0.0.0/0" + } + } +`, + expected: []rdb.DBSecurityGroup{{ + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("memo", iacTypes.NewTestMetadata()), + CIDRs: []iacTypes.StringValue{ + iacTypes.String("0.0.0.0/0", iacTypes.NewTestMetadata()), + }, + }}, + }, + { + name: "defaults", + terraform: ` + resource "nifcloud_db_security_group" "example" { + rule { + } + } +`, + + expected: []rdb.DBSecurityGroup{{ + Metadata: iacTypes.NewTestMetadata(), + Description: iacTypes.String("", iacTypes.NewTestMetadata()), + CIDRs: []iacTypes.StringValue{ + iacTypes.String("", iacTypes.NewTestMetadata()), + }, + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") + adapted := adaptDBSecurityGroups(modules) + testutil.AssertDefsecEqual(t, test.expected, adapted) + }) + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/sslcertificate/adapt.go b/pkg/iac/adapters/terraform/nifcloud/sslcertificate/adapt.go new file mode 100644 index 000000000000..1f1391397a12 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/sslcertificate/adapt.go @@ -0,0 +1,12 @@ +package sslcertificate + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/sslcertificate" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func Adapt(modules terraform.Modules) sslcertificate.SSLCertificate { + return sslcertificate.SSLCertificate{ + ServerCertificates: adaptServerCertificates(modules), + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/sslcertificate/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/sslcertificate/adapt_test.go new file mode 100644 index 000000000000..0c044cb049a9 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/sslcertificate/adapt_test.go @@ -0,0 +1,28 @@ +package sslcertificate + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLines(t *testing.T) { + src := ` +resource "nifcloud_ssl_certificate" "example" { + certificate = "generated-certificate" +} +` + + modules := tftestutil.CreateModulesFromSource(t, src, ".tf") + adapted := Adapt(modules) + + require.Len(t, adapted.ServerCertificates, 1) + + serverCertificate := adapted.ServerCertificates[0] + + assert.Equal(t, 3, serverCertificate.Expiration.GetMetadata().Range().GetStartLine()) + assert.Equal(t, 3, serverCertificate.Expiration.GetMetadata().Range().GetEndLine()) +} diff --git a/pkg/iac/adapters/terraform/nifcloud/sslcertificate/server_certificate.go b/pkg/iac/adapters/terraform/nifcloud/sslcertificate/server_certificate.go new file mode 100644 index 000000000000..7c08d0d78ca3 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/sslcertificate/server_certificate.go @@ -0,0 +1,41 @@ +package sslcertificate + +import ( + "crypto/x509" + "encoding/pem" + + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/sslcertificate" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func adaptServerCertificates(modules terraform.Modules) []sslcertificate.ServerCertificate { + var serverCertificates []sslcertificate.ServerCertificate + + for _, resource := range modules.GetResourcesByType("nifcloud_ssl_certificate") { + serverCertificates = append(serverCertificates, adaptServerCertificate(resource)) + } + return serverCertificates +} + +func adaptServerCertificate(resource *terraform.Block) sslcertificate.ServerCertificate { + certificateAttr := resource.GetAttribute("certificate") + expiryDateVal := iacTypes.TimeUnresolvable(resource.GetMetadata()) + + if certificateAttr.IsNotNil() { + expiryDateVal = iacTypes.TimeUnresolvable(certificateAttr.GetMetadata()) + if certificateAttr.IsString() { + certificateString := certificateAttr.Value().AsString() + if block, _ := pem.Decode([]byte(certificateString)); block != nil { + if cert, err := x509.ParseCertificate(block.Bytes); err == nil { + expiryDateVal = iacTypes.Time(cert.NotAfter, certificateAttr.GetMetadata()) + } + } + } + } + + return sslcertificate.ServerCertificate{ + Metadata: resource.GetMetadata(), + Expiration: expiryDateVal, + } +} diff --git a/pkg/iac/adapters/terraform/nifcloud/sslcertificate/server_certificate_test.go b/pkg/iac/adapters/terraform/nifcloud/sslcertificate/server_certificate_test.go new file mode 100644 index 000000000000..1b9006034ce2 --- /dev/null +++ b/pkg/iac/adapters/terraform/nifcloud/sslcertificate/server_certificate_test.go @@ -0,0 +1,70 @@ +package sslcertificate + +import ( + "testing" + "time" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/sslcertificate" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +const certificate = ` +-----BEGIN CERTIFICATE----- +MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ +hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa +rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv +zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW +r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V +-----END CERTIFICATE----- +` + +func Test_adaptServerCertificates(t *testing.T) { + tests := []struct { + name string + terraform string + expected []sslcertificate.ServerCertificate + }{ + { + name: "configured", + terraform: ` + resource "nifcloud_ssl_certificate" "example" { + certificate = < 0 { + appName = os.Args[0] + } + } else { + appName = filepath.Base(path) + } + + wd, _ := os.Getwd() + hostname, _ := os.Hostname() + + var inDocker bool + if _, err := os.Stat("/.dockerenv"); err == nil || !os.IsNotExist(err) { + inDocker = true + } + + var kernelInfo string + if data, err := os.ReadFile("/proc/version"); err == nil { + kernelInfo = strings.TrimSpace(string(data)) + } + + sys.Log("APP %s", appName) + sys.Log("VERSION %s", appVersion) + sys.Log("OS %s", runtime.GOOS) + sys.Log("ARCH %s", runtime.GOARCH) + sys.Log("KERNEL %s", kernelInfo) + sys.Log("TERM %s", os.Getenv("TERM")) + sys.Log("SHELL %s", os.Getenv("SHELL")) + sys.Log("GOVERSION %s", runtime.Version()) + sys.Log("GOROOT %s", runtime.GOROOT()) + sys.Log("CGO %t", cgoEnabled) + sys.Log("CPUCOUNT %d", runtime.NumCPU()) + sys.Log("MAXPROCS %d", runtime.GOMAXPROCS(0)) + sys.Log("WORKDIR %s", wd) + sys.Log("UID %d", os.Getuid()) + sys.Log("EUID %d", os.Geteuid()) + sys.Log("DOCKER %t", inDocker) + sys.Log("CI %t", os.Getenv("CI") != "") + sys.Log("HOSTNAME %s", hostname) + sys.Log("TEMP %s", os.TempDir()) + sys.Log("PATHSEP %c", filepath.Separator) + sys.Log("CMD %s", strings.Join(os.Args, " ")) +} diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go new file mode 100644 index 000000000000..6d1e627d11dc --- /dev/null +++ b/pkg/iac/detection/detect.go @@ -0,0 +1,302 @@ +package detection + +import ( + "bytes" + "encoding/json" + "io" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type FileType string + +const ( + FileTypeCloudFormation FileType = "cloudformation" + FileTypeTerraform FileType = "terraform" + FileTypeTerraformPlanJSON FileType = "terraformplan-json" + FileTypeTerraformPlanSnapshot FileType = "terraformplan-snapshot" + FileTypeDockerfile FileType = "dockerfile" + FileTypeKubernetes FileType = "kubernetes" + FileTypeRbac FileType = "rbac" + FileTypeYAML FileType = "yaml" + FileTypeTOML FileType = "toml" + FileTypeJSON FileType = "json" + FileTypeHelm FileType = "helm" + FileTypeAzureARM FileType = "azure-arm" +) + +var matchers = make(map[FileType]func(name string, r io.ReadSeeker) bool) + +// nolint +func init() { + + matchers[FileTypeJSON] = func(name string, r io.ReadSeeker) bool { + ext := filepath.Ext(filepath.Base(name)) + if !strings.EqualFold(ext, ".json") { + return false + } + if resetReader(r) == nil { + return true + } + + var content interface{} + return json.NewDecoder(r).Decode(&content) == nil + } + + matchers[FileTypeYAML] = func(name string, r io.ReadSeeker) bool { + ext := filepath.Ext(filepath.Base(name)) + if !strings.EqualFold(ext, ".yaml") && !strings.EqualFold(ext, ".yml") { + return false + } + if resetReader(r) == nil { + return true + } + + var content interface{} + return yaml.NewDecoder(r).Decode(&content) == nil + } + + matchers[FileTypeHelm] = func(name string, r io.ReadSeeker) bool { + if IsHelmChartArchive(name, r) { + return true + } + + return strings.HasSuffix(name, "hart.yaml") + } + + matchers[FileTypeTOML] = func(name string, r io.ReadSeeker) bool { + ext := filepath.Ext(filepath.Base(name)) + return strings.EqualFold(ext, ".toml") + } + + matchers[FileTypeTerraform] = func(name string, _ io.ReadSeeker) bool { + return IsTerraformFile(name) + } + + matchers[FileTypeTerraformPlanJSON] = func(name string, r io.ReadSeeker) bool { + if IsType(name, r, FileTypeJSON) { + if resetReader(r) == nil { + return false + } + + contents := make(map[string]interface{}) + err := json.NewDecoder(r).Decode(&contents) + if err == nil { + if _, ok := contents["terraform_version"]; ok { + _, stillOk := contents["format_version"] + return stillOk + } + } + } + return false + } + + matchers[FileTypeTerraformPlanSnapshot] = func(name string, r io.ReadSeeker) bool { + return snapshot.IsPlanSnapshot(r) + } + + matchers[FileTypeCloudFormation] = func(name string, r io.ReadSeeker) bool { + sniff := struct { + Resources map[string]map[string]interface{} `json:"Resources" yaml:"Resources"` + }{} + + switch { + case IsType(name, r, FileTypeYAML): + if resetReader(r) == nil { + return false + } + if err := yaml.NewDecoder(r).Decode(&sniff); err != nil { + return false + } + case IsType(name, r, FileTypeJSON): + if resetReader(r) == nil { + return false + } + if err := json.NewDecoder(r).Decode(&sniff); err != nil { + return false + } + default: + return false + } + + return sniff.Resources != nil + } + + matchers[FileTypeAzureARM] = func(name string, r io.ReadSeeker) bool { + + if resetReader(r) == nil { + return false + } + + sniff := struct { + ContentType string `json:"contentType"` + Parameters map[string]interface{} `json:"parameters"` + Resources []interface{} `json:"resources"` + }{} + metadata := types.NewUnmanagedMetadata() + if err := armjson.UnmarshalFromReader(r, &sniff, &metadata); err != nil { + return false + } + + return (sniff.Parameters != nil && len(sniff.Parameters) > 0) || + (sniff.Resources != nil && len(sniff.Resources) > 0) + } + + matchers[FileTypeDockerfile] = func(name string, _ io.ReadSeeker) bool { + requiredFiles := []string{"Dockerfile", "Containerfile"} + for _, requiredFile := range requiredFiles { + base := filepath.Base(name) + ext := filepath.Ext(base) + if strings.TrimSuffix(base, ext) == requiredFile { + return true + } + if strings.EqualFold(ext, "."+requiredFile) { + return true + } + } + return false + } + + matchers[FileTypeHelm] = func(name string, r io.ReadSeeker) bool { + helmFiles := []string{"Chart.yaml", ".helmignore", "values.schema.json", "NOTES.txt"} + for _, expected := range helmFiles { + if strings.HasSuffix(name, expected) { + return true + } + } + helmFileExtensions := []string{".yaml", ".tpl"} + ext := filepath.Ext(filepath.Base(name)) + for _, expected := range helmFileExtensions { + if strings.EqualFold(ext, expected) { + return true + } + } + return IsHelmChartArchive(name, r) + } + + matchers[FileTypeKubernetes] = func(name string, r io.ReadSeeker) bool { + + if !IsType(name, r, FileTypeYAML) && !IsType(name, r, FileTypeJSON) { + return false + } + if resetReader(r) == nil { + return false + } + + expectedProperties := []string{"apiVersion", "kind", "metadata"} + + if IsType(name, r, FileTypeJSON) { + if resetReader(r) == nil { + return false + } + + var result map[string]interface{} + if err := json.NewDecoder(r).Decode(&result); err != nil { + return false + } + + for _, expected := range expectedProperties { + if _, ok := result[expected]; !ok { + return false + } + } + return true + } + + // at this point, we need to inspect bytes + var buf bytes.Buffer + if _, err := io.Copy(&buf, r); err != nil { + return false + } + data := buf.Bytes() + + marker := "\n---\n" + altMarker := "\r\n---\r\n" + if bytes.Contains(data, []byte(altMarker)) { + marker = altMarker + } + + for _, partial := range strings.Split(string(data), marker) { + var result map[string]interface{} + if err := yaml.Unmarshal([]byte(partial), &result); err != nil { + continue + } + match := true + for _, expected := range expectedProperties { + if _, ok := result[expected]; !ok { + match = false + break + } + } + if match { + return true + } + } + + return false + } +} + +func IsTerraformFile(path string) bool { + for _, ext := range []string{".tf", ".tf.json", ".tfvars"} { + if strings.HasSuffix(path, ext) { + return true + } + } + + return false +} + +func IsType(name string, r io.ReadSeeker, t FileType) bool { + r = ensureSeeker(r) + f, ok := matchers[t] + if !ok { + return false + } + return f(name, r) +} + +func GetTypes(name string, r io.ReadSeeker) []FileType { + var matched []FileType + r = ensureSeeker(r) + for check, f := range matchers { + if f(name, r) { + matched = append(matched, check) + } + resetReader(r) + } + return matched +} + +func ensureSeeker(r io.Reader) io.ReadSeeker { + if r == nil { + return nil + } + if seeker, ok := r.(io.ReadSeeker); ok { + return seeker + } + + var buf bytes.Buffer + if _, err := io.Copy(&buf, r); err == nil { + return bytes.NewReader(buf.Bytes()) + } + + return nil +} + +func resetReader(r io.Reader) io.ReadSeeker { + if r == nil { + return nil + } + if seeker, ok := r.(io.ReadSeeker); ok { + _, _ = seeker.Seek(0, 0) + return seeker + } + return ensureSeeker(r) +} diff --git a/pkg/iac/detection/detect_test.go b/pkg/iac/detection/detect_test.go new file mode 100644 index 000000000000..e5f875071e16 --- /dev/null +++ b/pkg/iac/detection/detect_test.go @@ -0,0 +1,410 @@ +package detection + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Detection(t *testing.T) { + tests := []struct { + name string + path string + r io.ReadSeeker + expected []FileType + }{ + { + name: "text file, no reader", + path: "something.txt", + expected: nil, + }, + { + name: "text file, with reader", + path: "something.txt", + r: strings.NewReader("some file content"), + expected: nil, + }, + { + name: "terraform, no reader", + path: "main.tf", + expected: []FileType{ + FileTypeTerraform, + }, + }, + { + name: "terraform, with reader", + path: "main.tf", + r: strings.NewReader("some file content"), + expected: []FileType{ + FileTypeTerraform, + }, + }, + { + name: "terraform json, no reader", + path: "main.tf.json", + expected: []FileType{ + FileTypeTerraform, + FileTypeJSON, + }, + }, + { + name: "terraform json, with reader", + path: "main.tf.json", + r: strings.NewReader(` +{ + "variable": { + "example": { + "default": "hello" + } + } +} +`), + expected: []FileType{ + FileTypeTerraform, + FileTypeJSON, + }, + }, + { + name: "terraform vars, no reader", + path: "main.tfvars", + expected: []FileType{ + FileTypeTerraform, + }, + }, + { + name: "terraform vars, with reader", + path: "main.tfvars", + r: strings.NewReader("some_var = \"some value\""), + expected: []FileType{ + FileTypeTerraform, + }, + }, + { + name: "cloudformation, no reader", + path: "main.yaml", + expected: []FileType{ + FileTypeYAML, + FileTypeHelm, + }, + }, + { + name: "terraform plan, with reader", + path: "plan.json", + r: strings.NewReader(`{ + "format_version": "0.2", + "terraform_version": "1.0.3", + "variables": { + "bucket_name": { + "value": "tfsec-plan-testing" + } + }, + "planned_values": {}, + "resource_changes": [], + "prior_state": {}, + "configuration": {} + }`), + expected: []FileType{ + FileTypeTerraformPlanJSON, + FileTypeJSON, + }, + }, + { + name: "cloudformation, with reader", + path: "main.yaml", + r: strings.NewReader(`--- +AWSTemplateFormatVersion: 2010-09-09 + +Description: CodePipeline for continuous integration and continuous deployment + +Parameters: + RepositoryName: + Type: String + Description: Name of the CodeCommit repository + BuildDockerImage: + Type: String + Default: aws/codebuild/ubuntu-base:14.04 + Description: Docker image to use for the build phase + DeployDockerImage: + Type: String + Default: aws/codebuild/ubuntu-base:14.04 + Description: Docker image to use for the deployment phase + +Resources: + PipelineS3Bucket: + Type: AWS::S3::Bucket +`), + expected: []FileType{ + FileTypeCloudFormation, + FileTypeYAML, + FileTypeHelm, + }, + }, + { + name: "JSON with Resources, not cloudformation", + path: "whatever.json", + r: strings.NewReader(`{ + "Resources": ["something"] +}`), + expected: []FileType{ + FileTypeJSON, + }, + }, + { + name: "Dockerfile, no reader", + path: "Dockerfile", + r: nil, + expected: []FileType{ + FileTypeDockerfile, + }, + }, + { + name: "Containerfile, no reader", + path: "Containerfile", + r: nil, + expected: []FileType{ + FileTypeDockerfile, + }, + }, + { + name: "Dockerfile, reader", + path: "Dockerfile", + r: strings.NewReader("FROM ubuntu\n"), + expected: []FileType{ + FileTypeDockerfile, + }, + }, + { + name: "Dockerfile extension", + path: "lol.Dockerfile", + r: nil, + expected: []FileType{ + FileTypeDockerfile, + }, + }, + { + name: "kubernetes, no reader", + path: "k8s.yml", + r: nil, + expected: []FileType{ + FileTypeYAML, + }, + }, + { + name: "kubernetes, reader", + path: "k8s.yml", + r: strings.NewReader(`apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80`), + expected: []FileType{ + FileTypeKubernetes, + FileTypeYAML, + }, + }, + { + name: "kubernetes, reader, JSON", + path: "k8s.json", + r: strings.NewReader(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "nginx-deployment", + "labels": { + "app": "nginx" + } + }, + "spec": { + "replicas": 3, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:1.14.2", + "ports": [ + { + "containerPort": 80 + } + ] + } + ] + } + } + } +}`), + expected: []FileType{ + FileTypeKubernetes, + FileTypeJSON, + }, + }, + { + name: "YAML, no reader", + path: "file.yaml", + r: nil, + expected: []FileType{ + FileTypeYAML, + FileTypeHelm, + }, + }, + { + name: "YML, no reader", + path: "file.yml", + r: nil, + expected: []FileType{ + FileTypeYAML, + }, + }, + { + name: "YML uppercase", + path: "file.YML", + r: nil, + expected: []FileType{ + FileTypeYAML, + }, + }, + { + name: "TOML, no reader", + path: "file.toml", + r: nil, + expected: []FileType{ + FileTypeTOML, + }, + }, + { + name: "JSON, no reader", + path: "file.json", + r: nil, + expected: []FileType{ + FileTypeJSON, + }, + }, + { + name: "kubernetes, configmap", + path: "k8s.yml", + r: strings.NewReader(`apiVersion: v1 +kind: ConfigMap +metadata: + name: test + namespace: default +data: + AWS_ACCESS_KEY_ID: "XXX" + AWS_SECRET_ACCESS_KEY: "XXX"`), + expected: []FileType{ + FileTypeKubernetes, + FileTypeYAML, + }, + }, + { + name: "kubernetes, clusterRole", + path: "k8s.yml", + r: strings.NewReader(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + labels: + kubernetes.io/bootstrapping: rbac-defaults + rbac.authorization.k8s.io/aggregate-to-edit: "true" + name: view +rules: +- apiGroups: + - networking.k8s.io + resources: + - ingresses + - ingresses/status + - networkpolicies + verbs: + - get + - list + - watch`), + expected: []FileType{ + FileTypeKubernetes, + FileTypeYAML, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Run("GetTypes", func(t *testing.T) { + actualDetections := GetTypes(test.path, test.r) + assert.Equal(t, len(test.expected), len(actualDetections)) + for _, expected := range test.expected { + resetReader(test.r) + var found bool + for _, actual := range actualDetections { + if actual == expected { + found = true + break + } + } + assert.True(t, found, "%s should be detected", expected) + } + }) + for _, expected := range test.expected { + resetReader(test.r) + t.Run(fmt.Sprintf("IsType_%s", expected), func(t *testing.T) { + assert.True(t, IsType(test.path, test.r, expected)) + }) + } + t.Run("IsType_invalid", func(t *testing.T) { + resetReader(test.r) + assert.False(t, IsType(test.path, test.r, "invalid")) + }) + }) + } +} + +func BenchmarkIsType_SmallFile(b *testing.B) { + data, err := os.ReadFile(fmt.Sprintf("./testdata/%s", "small.file")) + assert.Nil(b, err) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = IsType(fmt.Sprintf("./testdata/%s", "small.file"), bytes.NewReader(data), FileTypeAzureARM) + } +} + +func BenchmarkIsType_BigFile(b *testing.B) { + data, err := os.ReadFile(fmt.Sprintf("./testdata/%s", "big.file")) + assert.Nil(b, err) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = IsType(fmt.Sprintf("./testdata/%s", "big.file"), bytes.NewReader(data), FileTypeAzureARM) + } +} diff --git a/pkg/iac/detection/peek.go b/pkg/iac/detection/peek.go new file mode 100644 index 000000000000..0e76115d9bd8 --- /dev/null +++ b/pkg/iac/detection/peek.go @@ -0,0 +1,53 @@ +package detection + +import ( + "archive/tar" + "compress/gzip" + "errors" + "io" + "strings" +) + +func IsHelmChartArchive(path string, file io.Reader) bool { + + if !IsArchive(path) { + return false + } + + var err error + var fr = file + + if IsZip(path) { + if fr, err = gzip.NewReader(file); err != nil { + return false + } + } + tr := tar.NewReader(fr) + + if tr == nil { + return false + } + + for { + header, err := tr.Next() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return false + } + + if header.Typeflag == tar.TypeReg && strings.HasSuffix(header.Name, "Chart.yaml") { + return true + } + } + return false +} + +func IsArchive(path string) bool { + return strings.HasSuffix(path, ".tar") || IsZip(path) +} + +func IsZip(path string) bool { + return strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") +} diff --git a/pkg/iac/detection/testdata/big.file b/pkg/iac/detection/testdata/big.file new file mode 100644 index 000000000000..e7f3c2d40ecc Binary files /dev/null and b/pkg/iac/detection/testdata/big.file differ diff --git a/pkg/iac/detection/testdata/small.file b/pkg/iac/detection/testdata/small.file new file mode 100644 index 000000000000..d8ae428a4800 --- /dev/null +++ b/pkg/iac/detection/testdata/small.file @@ -0,0 +1,3 @@ +{ + "content": "foo bar baz" +} \ No newline at end of file diff --git a/pkg/iac/framework/frameworks.go b/pkg/iac/framework/frameworks.go new file mode 100644 index 000000000000..82f43947d568 --- /dev/null +++ b/pkg/iac/framework/frameworks.go @@ -0,0 +1,11 @@ +package framework + +type Framework string + +const ( + Default Framework = "default" + Experimental Framework = "experimental" + CIS_AWS_1_2 Framework = "cis-aws-1.2" + CIS_AWS_1_4 Framework = "cis-aws-1.4" + ALL Framework = "all" +) diff --git a/pkg/iac/ignore/parse.go b/pkg/iac/ignore/parse.go new file mode 100644 index 000000000000..075f1f621203 --- /dev/null +++ b/pkg/iac/ignore/parse.go @@ -0,0 +1,168 @@ +package ignore + +import ( + "errors" + "strings" + "time" + + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +// RuleSectionParser defines the interface for parsing ignore rules. +type RuleSectionParser interface { + Key() string + Parse(string) bool + Param() any +} + +// Parse parses the configuration file and returns the Rules +func Parse(src, path string, parsers ...RuleSectionParser) Rules { + var rules Rules + for i, line := range strings.Split(src, "\n") { + line = strings.TrimSpace(line) + rng := types.NewRange(path, i+1, i+1, "", nil) + lineIgnores := parseLine(line, rng, parsers) + for _, lineIgnore := range lineIgnores { + rules = append(rules, lineIgnore) + } + } + + rules.shift() + + return rules +} + +func parseLine(line string, rng types.Range, parsers []RuleSectionParser) []Rule { + var rules []Rule + + sections := strings.Split(strings.TrimSpace(line), " ") + for _, section := range sections { + section := strings.TrimSpace(section) + section = strings.TrimLeftFunc(section, func(r rune) bool { + return r == '#' || r == '/' || r == '*' + }) + + section, exists := hasIgnoreRulePrefix(section) + if !exists { + continue + } + + rule, err := parseComment(section, rng, parsers) + if err != nil { + log.Logger.Debugf("Failed to parse rule at %s: %s", rng.String(), err.Error()) + continue + } + rules = append(rules, rule) + } + + return rules +} + +func hasIgnoreRulePrefix(s string) (string, bool) { + for _, prefix := range []string{"tfsec:", "trivy:"} { + if after, found := strings.CutPrefix(s, prefix); found { + return after, true + } + } + + return "", false +} + +func parseComment(input string, rng types.Range, parsers []RuleSectionParser) (Rule, error) { + rule := Rule{ + rng: rng, + sections: make(map[string]any), + } + + parsers = append(parsers, &expiryDateParser{ + rng: rng, + }) + + segments := strings.Split(input, ":") + + for i := 0; i < len(segments)-1; i += 2 { + key := segments[i] + val := segments[i+1] + if key == "ignore" { + // special case, because id and parameters are in the same section + idParser := &checkIDParser{ + StringMatchParser{SectionKey: "id"}, + } + if idParser.Parse(val) { + rule.sections[idParser.Key()] = idParser.Param() + } + } + + for _, parser := range parsers { + if parser.Key() != key { + continue + } + + if parser.Parse(val) { + rule.sections[parser.Key()] = parser.Param() + } + } + } + + if _, exists := rule.sections["id"]; !exists { + return Rule{}, errors.New("rule section with the `ignore` key is required") + } + + return rule, nil +} + +type StringMatchParser struct { + SectionKey string + param string +} + +func (s *StringMatchParser) Key() string { + return s.SectionKey +} + +func (s *StringMatchParser) Parse(str string) bool { + s.param = str + return str != "" +} + +func (s *StringMatchParser) Param() any { + return s.param +} + +type checkIDParser struct { + StringMatchParser +} + +func (s *checkIDParser) Parse(str string) bool { + if idx := strings.Index(str, "["); idx != -1 { + str = str[:idx] + } + return s.StringMatchParser.Parse(str) +} + +type expiryDateParser struct { + rng types.Range + expiry time.Time +} + +func (s *expiryDateParser) Key() string { + return "exp" +} + +func (s *expiryDateParser) Parse(str string) bool { + parsed, err := time.Parse("2006-01-02", str) + if err != nil { + log.Logger.Debugf("Incorrect time to ignore is specified: %s", str) + parsed = time.Time{} + } else if time.Now().After(parsed) { + log.Logger.Debug("Ignore rule time has expired for location: %s", s.rng.String()) + } + + s.expiry = parsed + return true +} + +func (s *expiryDateParser) Param() any { + return s.expiry +} diff --git a/pkg/iac/ignore/rule.go b/pkg/iac/ignore/rule.go new file mode 100644 index 000000000000..61057ce75f87 --- /dev/null +++ b/pkg/iac/ignore/rule.go @@ -0,0 +1,131 @@ +package ignore + +import ( + "regexp" + "slices" + "strings" + "time" + + "github.com/samber/lo" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +// Ignorer represents a function that checks if the rule should be ignored. +type Ignorer func(resultMeta types.Metadata, ignoredParam any) bool + +type Rules []Rule + +// Ignore checks if the rule should be ignored based on provided metadata, IDs, and ignorer functions. +func (r Rules) Ignore(m types.Metadata, ids []string, ignorers map[string]Ignorer) bool { + return slices.ContainsFunc(r, func(r Rule) bool { + return r.ignore(m, ids, ignorers) + }) +} + +func (r Rules) shift() { + var ( + currentRange *types.Range + offset int + ) + + for i := len(r) - 1; i > 0; i-- { + currentIgnore, nextIgnore := r[i], r[i-1] + if currentRange == nil { + currentRange = ¤tIgnore.rng + } + if nextIgnore.rng.GetStartLine()+1+offset == currentIgnore.rng.GetStartLine() { + r[i-1].rng = *currentRange + offset++ + } else { + currentRange = nil + offset = 0 + } + } +} + +// Rule represents a rule for ignoring vulnerabilities. +type Rule struct { + rng types.Range + sections map[string]any +} + +func (r Rule) ignore(m types.Metadata, ids []string, ignorers map[string]Ignorer) bool { + matchMeta, ok := r.matchRange(&m) + if !ok { + return false + } + + ignorers = lo.Assign(defaultIgnorers(ids), ignorers) + + for ignoreID, ignore := range ignorers { + if param, exists := r.sections[ignoreID]; exists { + if !ignore(*matchMeta, param) { + return false + } + } + } + + return true +} + +func (r Rule) matchRange(m *types.Metadata) (*types.Metadata, bool) { + metaHierarchy := m + for metaHierarchy != nil { + if r.rng.GetFilename() != metaHierarchy.Range().GetFilename() { + metaHierarchy = metaHierarchy.Parent() + continue + } + if metaHierarchy.Range().GetStartLine() == r.rng.GetStartLine()+1 || + metaHierarchy.Range().GetStartLine() == r.rng.GetStartLine() { + return metaHierarchy, true + } + metaHierarchy = metaHierarchy.Parent() + } + + return nil, false +} + +func defaultIgnorers(ids []string) map[string]Ignorer { + return map[string]Ignorer{ + "id": func(_ types.Metadata, param any) bool { + id, ok := param.(string) + if !ok { + return false + } + if id == "*" || len(ids) == 0 { + return true + } + + return slices.ContainsFunc(ids, func(s string) bool { + return MatchPattern(s, id) + }) + }, + "exp": func(_ types.Metadata, param any) bool { + expiry, ok := param.(time.Time) + return ok && time.Now().Before(expiry) + }, + } +} + +// MatchPattern checks if the pattern string matches the input pattern. +// The wildcard '*' in the pattern matches any sequence of characters. +func MatchPattern(input, pattern string) bool { + matched, err := regexp.MatchString(regexpFromPattern(pattern), input) + return err == nil && matched +} + +func regexpFromPattern(pattern string) string { + parts := strings.Split(pattern, "*") + if len(parts) == 1 { + return "^" + pattern + "$" + } + var sb strings.Builder + for i, literal := range parts { + if i > 0 { + sb.WriteString(".*") + } + sb.WriteString(regexp.QuoteMeta(literal)) + } + return "^" + sb.String() + "$" +} diff --git a/pkg/iac/ignore/rule_test.go b/pkg/iac/ignore/rule_test.go new file mode 100644 index 000000000000..da89ca2a3595 --- /dev/null +++ b/pkg/iac/ignore/rule_test.go @@ -0,0 +1,314 @@ +package ignore_test + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/ignore" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" +) + +func metadataWithLine(path string, line int) types.Metadata { + return types.NewMetadata(types.NewRange(path, line, line, "", nil), "") +} + +func TestRules_Ignore(t *testing.T) { + + const filename = "test" + + type args struct { + metadata types.Metadata + ids []string + } + + tests := []struct { + name string + src string + args args + shouldIgnore bool + }{ + { + name: "no ignore", + src: `#test`, + shouldIgnore: false, + }, + { + name: "one ignore rule", + src: `#trivy:ignore:rule-1`, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-1"}, + }, + shouldIgnore: true, + }, + { + name: "blank line between rule and finding", + src: `#trivy:ignore:rule-1`, + args: args{ + metadata: metadataWithLine(filename, 3), + ids: []string{"rule-1"}, + }, + shouldIgnore: false, + }, + { + name: "blank line between rules", + src: `#trivy:ignore:rule-1 + +#trivy:ignore:rule-2 +`, + args: args{ + metadata: metadataWithLine(filename, 4), + ids: []string{"rule-1"}, + }, + shouldIgnore: false, + }, + { + name: "rule and a finding on the same line", + src: `#trivy:ignore:rule-1`, + args: args{ + metadata: metadataWithLine(filename, 1), + ids: []string{"rule-1"}, + }, + shouldIgnore: true, + }, + { + name: "rule and a finding on the same line", + src: `test #trivy:ignore:rule-1`, + args: args{ + metadata: metadataWithLine(filename, 1), + ids: []string{"rule-1"}, + }, + shouldIgnore: true, + }, + { + name: "multiple rules on one line", + src: `test #trivy:ignore:rule-1 #trivy:ignore:rule-2`, + args: args{ + metadata: metadataWithLine(filename, 1), + ids: []string{"rule-2"}, + }, + shouldIgnore: true, + }, + { + name: "rule and find from different files", + src: `test #trivy:ignore:rule-1`, + args: args{ + metadata: metadataWithLine("another-file", 1), + ids: []string{"rule-2"}, + }, + shouldIgnore: false, + }, + { + name: "multiple ignore rule", + src: `#trivy:ignore:rule-1 +#trivy:ignore:rule-2 +`, + args: args{ + metadata: metadataWithLine(filename, 3), + ids: []string{"rule-1"}, + }, + shouldIgnore: true, + }, + { + name: "ignore section with params", + src: `#trivy:ignore:rule-1[param1=1]`, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-1"}, + }, + shouldIgnore: true, + }, + { + name: "id's don't match", + src: `#trivy:ignore:rule-1`, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-2"}, + }, + shouldIgnore: false, + }, + { + name: "without ignore section", + src: `#trivy:exp:2022-01-01`, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-2"}, + }, + shouldIgnore: false, + }, + { + name: "non valid ignore section", + src: `#trivy:ignore`, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-2"}, + }, + shouldIgnore: false, + }, + { + name: "ignore rule with expiry date passed", + src: `#trivy:ignore:rule-1:exp:2022-01-01`, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-1"}, + }, + shouldIgnore: false, + }, + { + name: "ignore rule with expiry date not passed", + src: `#trivy:ignore:rule-1:exp:2026-01-01`, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-1"}, + }, + shouldIgnore: true, + }, + { + name: "ignore rule with invalid expiry date", + src: `#trivy:ignore:rule-1:exp:2026-99-01`, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-1"}, + }, + shouldIgnore: false, + }, + { + name: "with valid wildcard", + src: `#trivy:ignore:rule-*`, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-1"}, + }, + shouldIgnore: true, + }, + { + name: "with non-valid wildcard", + src: `#trivy:ignore:rule-1-*d`, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-1-abc"}, + }, + shouldIgnore: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rules := ignore.Parse(tt.src, filename) + got := rules.Ignore(tt.args.metadata, tt.args.ids, nil) + assert.Equal(t, tt.shouldIgnore, got) + }) + } +} + +func TestRules_IgnoreWithCustomIgnorer(t *testing.T) { + const filename = "test" + + type args struct { + metadata types.Metadata + ids []string + ignorers map[string]ignore.Ignorer + } + + tests := []struct { + name string + src string + parser ignore.RuleSectionParser + args args + shouldIgnore bool + }{ + { + name: "happy", + src: `#trivy:ignore:rule-1:ws:dev`, + parser: &ignore.StringMatchParser{ + SectionKey: "ws", + }, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-1"}, + ignorers: map[string]ignore.Ignorer{ + "ws": func(_ types.Metadata, param any) bool { + ws, ok := param.(string) + if !ok { + return false + } + return ws == "dev" + }, + }, + }, + shouldIgnore: true, + }, + { + name: "with wildcard", + src: `#trivy:ignore:rule-1:ws:dev-*`, + parser: &ignore.StringMatchParser{ + SectionKey: "ws", + }, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-1"}, + ignorers: map[string]ignore.Ignorer{ + "ws": func(_ types.Metadata, param any) bool { + ws, ok := param.(string) + if !ok { + return false + } + return ignore.MatchPattern("dev-stage1", ws) + }, + }, + }, + shouldIgnore: true, + }, + { + name: "bad", + src: `#trivy:ignore:rule-1:ws:prod`, + parser: &ignore.StringMatchParser{ + SectionKey: "ws", + }, + args: args{ + metadata: metadataWithLine(filename, 2), + ids: []string{"rule-1"}, + ignorers: map[string]ignore.Ignorer{ + "ws": func(_ types.Metadata, param any) bool { + ws, ok := param.(string) + if !ok { + return false + } + return ws == "dev" + }, + }, + }, + shouldIgnore: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rules := ignore.Parse(tt.src, filename, tt.parser) + got := rules.Ignore(tt.args.metadata, tt.args.ids, tt.args.ignorers) + assert.Equal(t, tt.shouldIgnore, got) + }) + } +} + +func TestMatchPattern(t *testing.T) { + tests := []struct { + input string + pattern string + expected bool + }{ + {"foo-test-bar", "*-test-*", true}, + {"foo-test-bar", "*-example-*", false}, + {"test", "*test", true}, + {"example", "test", false}, + {"example-test", "*-test*", true}, + {"example-test", "*example-*", true}, + } + + for _, tc := range tests { + t.Run(tc.input+":"+tc.pattern, func(t *testing.T) { + got := ignore.MatchPattern(tc.input, tc.pattern) + assert.Equal(t, tc.expected, got) + }) + } +} diff --git a/pkg/iac/providers/aws/accessanalyzer/aa.go b/pkg/iac/providers/aws/accessanalyzer/aa.go new file mode 100644 index 000000000000..66be31561d04 --- /dev/null +++ b/pkg/iac/providers/aws/accessanalyzer/aa.go @@ -0,0 +1,19 @@ +package accessanalyzer + +import "github.com/aquasecurity/trivy/pkg/iac/types" + +type AccessAnalyzer struct { + Analyzers []Analyzer +} + +type Analyzer struct { + Metadata types.Metadata + ARN types.StringValue + Name types.StringValue + Active types.BoolValue + Findings []Findings +} + +type Findings struct { + Metadata types.Metadata +} diff --git a/pkg/iac/providers/aws/apigateway/ag.go b/pkg/iac/providers/aws/apigateway/ag.go new file mode 100644 index 000000000000..d87a9b364ee8 --- /dev/null +++ b/pkg/iac/providers/aws/apigateway/ag.go @@ -0,0 +1,11 @@ +package apigateway + +import ( + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" +) + +type APIGateway struct { + V1 v1.APIGateway + V2 v2.APIGateway +} diff --git a/pkg/iac/providers/aws/apigateway/v1/apigateway.go b/pkg/iac/providers/aws/apigateway/v1/apigateway.go new file mode 100755 index 000000000000..626d91d513b7 --- /dev/null +++ b/pkg/iac/providers/aws/apigateway/v1/apigateway.go @@ -0,0 +1,62 @@ +package v1 + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type APIGateway struct { + APIs []API + DomainNames []DomainName +} + +type API struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Stages []Stage + Resources []Resource +} + +type Stage struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + AccessLogging AccessLogging + XRayTracingEnabled iacTypes.BoolValue + RESTMethodSettings []RESTMethodSettings +} + +type Resource struct { + Metadata iacTypes.Metadata + Methods []Method +} + +type AccessLogging struct { + Metadata iacTypes.Metadata + CloudwatchLogGroupARN iacTypes.StringValue +} + +type RESTMethodSettings struct { + Metadata iacTypes.Metadata + Method iacTypes.StringValue + CacheDataEncrypted iacTypes.BoolValue + CacheEnabled iacTypes.BoolValue +} + +const ( + AuthorizationNone = "NONE" + AuthorizationCustom = "CUSTOM" + AuthorizationIAM = "AWS_IAM" + AuthorizationCognitoUserPools = "COGNITO_USER_POOLS" +) + +type Method struct { + Metadata iacTypes.Metadata + HTTPMethod iacTypes.StringValue + AuthorizationType iacTypes.StringValue + APIKeyRequired iacTypes.BoolValue +} + +type DomainName struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + SecurityPolicy iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/apigateway/v2/apigateway.go b/pkg/iac/providers/aws/apigateway/v2/apigateway.go new file mode 100755 index 000000000000..5a87e8ffbca7 --- /dev/null +++ b/pkg/iac/providers/aws/apigateway/v2/apigateway.go @@ -0,0 +1,41 @@ +package v2 + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type APIGateway struct { + APIs []API + DomainNames []DomainName +} + +const ( + ProtocolTypeUnknown string = "" + ProtocolTypeREST string = "REST" + ProtocolTypeHTTP string = "HTTP" + ProtocolTypeWebsocket string = "WEBSOCKET" +) + +type API struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + ProtocolType iacTypes.StringValue + Stages []Stage +} + +type Stage struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + AccessLogging AccessLogging +} + +type AccessLogging struct { + Metadata iacTypes.Metadata + CloudwatchLogGroupARN iacTypes.StringValue +} + +type DomainName struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + SecurityPolicy iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/athena/athena.go b/pkg/iac/providers/aws/athena/athena.go new file mode 100755 index 000000000000..537a4b63d6b0 --- /dev/null +++ b/pkg/iac/providers/aws/athena/athena.go @@ -0,0 +1,35 @@ +package athena + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Athena struct { + Databases []Database + Workgroups []Workgroup +} + +type Database struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Encryption EncryptionConfiguration +} + +type Workgroup struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Encryption EncryptionConfiguration + EnforceConfiguration iacTypes.BoolValue +} + +const ( + EncryptionTypeNone = "" + EncryptionTypeSSES3 = "SSE_S3" + EncryptionTypeSSEKMS = "SSE_KMS" + EncryptionTypeCSEKMS = "CSE_KMS" +) + +type EncryptionConfiguration struct { + Metadata iacTypes.Metadata + Type iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/aws.go b/pkg/iac/providers/aws/aws.go new file mode 100755 index 000000000000..15c80a84afa7 --- /dev/null +++ b/pkg/iac/providers/aws/aws.go @@ -0,0 +1,80 @@ +package aws + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/accessanalyzer" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/athena" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/codebuild" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/config" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/documentdb" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/dynamodb" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecr" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecs" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/efs" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/eks" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/emr" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/kinesis" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/kms" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/lambda" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/mq" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/msk" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/neptune" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/redshift" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sns" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sqs" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ssm" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/workspaces" +) + +type AWS struct { + Meta Meta + AccessAnalyzer accessanalyzer.AccessAnalyzer + APIGateway apigateway.APIGateway + Athena athena.Athena + Cloudfront cloudfront.Cloudfront + CloudTrail cloudtrail.CloudTrail + CloudWatch cloudwatch.CloudWatch + CodeBuild codebuild.CodeBuild + Config config.Config + DocumentDB documentdb.DocumentDB + DynamoDB dynamodb.DynamoDB + EC2 ec2.EC2 + ECR ecr.ECR + ECS ecs.ECS + EFS efs.EFS + EKS eks.EKS + ElastiCache elasticache.ElastiCache + Elasticsearch elasticsearch.Elasticsearch + ELB elb.ELB + EMR emr.EMR + IAM iam.IAM + Kinesis kinesis.Kinesis + KMS kms.KMS + Lambda lambda.Lambda + MQ mq.MQ + MSK msk.MSK + Neptune neptune.Neptune + RDS rds.RDS + Redshift redshift.Redshift + SAM sam.SAM + S3 s3.S3 + SNS sns.SNS + SQS sqs.SQS + SSM ssm.SSM + WorkSpaces workspaces.WorkSpaces +} + +type Meta struct { + TFProviders []TerraformProvider +} diff --git a/pkg/iac/providers/aws/cloudfront/cloudfront.go b/pkg/iac/providers/aws/cloudfront/cloudfront.go new file mode 100755 index 000000000000..0c4914e4c3bb --- /dev/null +++ b/pkg/iac/providers/aws/cloudfront/cloudfront.go @@ -0,0 +1,45 @@ +package cloudfront + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Cloudfront struct { + Distributions []Distribution +} + +type Distribution struct { + Metadata iacTypes.Metadata + WAFID iacTypes.StringValue + Logging Logging + DefaultCacheBehaviour CacheBehaviour + OrdererCacheBehaviours []CacheBehaviour + ViewerCertificate ViewerCertificate +} + +type Logging struct { + Metadata iacTypes.Metadata + Bucket iacTypes.StringValue +} + +type CacheBehaviour struct { + Metadata iacTypes.Metadata + ViewerProtocolPolicy iacTypes.StringValue +} + +const ( + ViewerPolicyProtocolAllowAll = "allow-all" + ViewerPolicyProtocolHTTPSOnly = "https-only" + ViewerPolicyProtocolRedirectToHTTPS = "redirect-to-https" +) + +const ( + ProtocolVersionTLS1_2 = "TLSv1.2_2021" +) + +type ViewerCertificate struct { + Metadata iacTypes.Metadata + CloudfrontDefaultCertificate iacTypes.BoolValue + SSLSupportMethod iacTypes.StringValue + MinimumProtocolVersion iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/cloudtrail/cloudtrail.go b/pkg/iac/providers/aws/cloudtrail/cloudtrail.go new file mode 100755 index 000000000000..8e5da87e7c89 --- /dev/null +++ b/pkg/iac/providers/aws/cloudtrail/cloudtrail.go @@ -0,0 +1,42 @@ +package cloudtrail + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type CloudTrail struct { + Trails []Trail +} + +func (c CloudTrail) MultiRegionTrails() (multiRegionTrails []Trail) { + for _, trail := range c.Trails { + if trail.IsMultiRegion.IsTrue() { + multiRegionTrails = append(multiRegionTrails, trail) + } + } + return multiRegionTrails +} + +type Trail struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + EnableLogFileValidation iacTypes.BoolValue + IsMultiRegion iacTypes.BoolValue + KMSKeyID iacTypes.StringValue + CloudWatchLogsLogGroupArn iacTypes.StringValue + IsLogging iacTypes.BoolValue + BucketName iacTypes.StringValue + EventSelectors []EventSelector +} + +type EventSelector struct { + Metadata iacTypes.Metadata + DataResources []DataResource + ReadWriteType iacTypes.StringValue // ReadOnly, WriteOnly, All. Default value is All for TF. +} + +type DataResource struct { + Metadata iacTypes.Metadata + Type iacTypes.StringValue // You can specify only the following value: "AWS::S3::Object", "AWS::Lambda::Function" and "AWS::DynamoDB::Table". + Values []iacTypes.StringValue // List of ARNs/partial ARNs - e.g. arn:aws:s3:::/ for all objects in a bucket, arn:aws:s3:::/key for specific objects +} diff --git a/pkg/iac/providers/aws/cloudwatch/cloudwatch.go b/pkg/iac/providers/aws/cloudwatch/cloudwatch.go new file mode 100755 index 000000000000..630ed84e64ef --- /dev/null +++ b/pkg/iac/providers/aws/cloudwatch/cloudwatch.go @@ -0,0 +1,63 @@ +package cloudwatch + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type CloudWatch struct { + LogGroups []LogGroup + Alarms []Alarm +} + +func (w CloudWatch) GetLogGroupByArn(arn string) (logGroup *LogGroup) { + for _, logGroup := range w.LogGroups { + if logGroup.Arn.EqualTo(arn) { + return &logGroup + } + } + return nil +} + +func (w CloudWatch) GetAlarmByMetricName(metricName string) (alarm *Alarm) { + for _, alarm := range w.Alarms { + if alarm.MetricName.EqualTo(metricName) { + return &alarm + } + } + return nil +} + +type Alarm struct { + Metadata iacTypes.Metadata + AlarmName iacTypes.StringValue + MetricName iacTypes.StringValue + Dimensions []AlarmDimension + Metrics []MetricDataQuery +} + +type AlarmDimension struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Value iacTypes.StringValue +} + +type MetricFilter struct { + Metadata iacTypes.Metadata + FilterName iacTypes.StringValue + FilterPattern iacTypes.StringValue +} + +type MetricDataQuery struct { + Metadata iacTypes.Metadata + Expression iacTypes.StringValue + ID iacTypes.StringValue +} + +type LogGroup struct { + Metadata iacTypes.Metadata + Arn iacTypes.StringValue + Name iacTypes.StringValue + KMSKeyID iacTypes.StringValue + RetentionInDays iacTypes.IntValue + MetricFilters []MetricFilter +} diff --git a/pkg/iac/providers/aws/codebuild/codebuild.go b/pkg/iac/providers/aws/codebuild/codebuild.go new file mode 100755 index 000000000000..34115ce40cd5 --- /dev/null +++ b/pkg/iac/providers/aws/codebuild/codebuild.go @@ -0,0 +1,20 @@ +package codebuild + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type CodeBuild struct { + Projects []Project +} + +type Project struct { + Metadata iacTypes.Metadata + ArtifactSettings ArtifactSettings + SecondaryArtifactSettings []ArtifactSettings +} + +type ArtifactSettings struct { + Metadata iacTypes.Metadata + EncryptionEnabled iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/config/config.go b/pkg/iac/providers/aws/config/config.go new file mode 100755 index 000000000000..ef18539f3bfa --- /dev/null +++ b/pkg/iac/providers/aws/config/config.go @@ -0,0 +1,14 @@ +package config + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Config struct { + ConfigurationAggregrator ConfigurationAggregrator +} + +type ConfigurationAggregrator struct { + Metadata iacTypes.Metadata + SourceAllRegions iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/documentdb/documentdb.go b/pkg/iac/providers/aws/documentdb/documentdb.go new file mode 100755 index 000000000000..c1bdc0a73854 --- /dev/null +++ b/pkg/iac/providers/aws/documentdb/documentdb.go @@ -0,0 +1,29 @@ +package documentdb + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type DocumentDB struct { + Clusters []Cluster +} + +const ( + LogExportAudit = "audit" + LogExportProfiler = "profiler" +) + +type Cluster struct { + Metadata iacTypes.Metadata + Identifier iacTypes.StringValue + EnabledLogExports []iacTypes.StringValue + BackupRetentionPeriod iacTypes.IntValue + Instances []Instance + StorageEncrypted iacTypes.BoolValue + KMSKeyID iacTypes.StringValue +} + +type Instance struct { + Metadata iacTypes.Metadata + KMSKeyID iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/dynamodb/dynamodb.go b/pkg/iac/providers/aws/dynamodb/dynamodb.go new file mode 100755 index 000000000000..eef0d5d532c0 --- /dev/null +++ b/pkg/iac/providers/aws/dynamodb/dynamodb.go @@ -0,0 +1,30 @@ +package dynamodb + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type DynamoDB struct { + DAXClusters []DAXCluster + Tables []Table +} + +type DAXCluster struct { + Metadata iacTypes.Metadata + ServerSideEncryption ServerSideEncryption + PointInTimeRecovery iacTypes.BoolValue +} + +type Table struct { + Metadata iacTypes.Metadata + ServerSideEncryption ServerSideEncryption + PointInTimeRecovery iacTypes.BoolValue +} + +type ServerSideEncryption struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + KMSKeyID iacTypes.StringValue +} + +const DefaultKMSKeyID = "alias/aws/dynamodb" diff --git a/pkg/iac/providers/aws/ec2/ec2.go b/pkg/iac/providers/aws/ec2/ec2.go new file mode 100755 index 000000000000..726e312f65aa --- /dev/null +++ b/pkg/iac/providers/aws/ec2/ec2.go @@ -0,0 +1,12 @@ +package ec2 + +type EC2 struct { + Instances []Instance + LaunchConfigurations []LaunchConfiguration + LaunchTemplates []LaunchTemplate + VPCs []VPC + SecurityGroups []SecurityGroup + NetworkACLs []NetworkACL + Subnets []Subnet + Volumes []Volume +} diff --git a/pkg/iac/providers/aws/ec2/instance.go b/pkg/iac/providers/aws/ec2/instance.go new file mode 100644 index 000000000000..09ecede5592b --- /dev/null +++ b/pkg/iac/providers/aws/ec2/instance.go @@ -0,0 +1,55 @@ +package ec2 + +import ( + "github.com/owenrumney/squealer/pkg/squealer" + + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Instance struct { + Metadata iacTypes.Metadata + MetadataOptions MetadataOptions + UserData iacTypes.StringValue + SecurityGroups []SecurityGroup + RootBlockDevice *BlockDevice + EBSBlockDevices []*BlockDevice +} + +type BlockDevice struct { + Metadata iacTypes.Metadata + Encrypted iacTypes.BoolValue +} + +type MetadataOptions struct { + Metadata iacTypes.Metadata + HttpTokens iacTypes.StringValue + HttpEndpoint iacTypes.StringValue +} + +func NewInstance(metadata iacTypes.Metadata) *Instance { + return &Instance{ + Metadata: metadata, + MetadataOptions: MetadataOptions{ + Metadata: metadata, + HttpTokens: iacTypes.StringDefault("optional", metadata), + HttpEndpoint: iacTypes.StringDefault("enabled", metadata), + }, + UserData: iacTypes.StringDefault("", metadata), + SecurityGroups: []SecurityGroup{}, + RootBlockDevice: nil, + EBSBlockDevices: nil, + } +} + +func (i *Instance) RequiresIMDSToken() bool { + return i.MetadataOptions.HttpTokens.EqualTo("required") +} + +func (i *Instance) HasHTTPEndpointDisabled() bool { + return i.MetadataOptions.HttpEndpoint.EqualTo("disabled") +} + +func (i *Instance) HasSensitiveInformationInUserData() bool { + scanner := squealer.NewStringScanner() + return scanner.Scan(i.UserData.Value()).TransgressionFound +} diff --git a/pkg/iac/providers/aws/ec2/launch.go b/pkg/iac/providers/aws/ec2/launch.go new file mode 100644 index 000000000000..9b7c4c711e01 --- /dev/null +++ b/pkg/iac/providers/aws/ec2/launch.go @@ -0,0 +1,29 @@ +package ec2 + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type LaunchConfiguration struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + AssociatePublicIP iacTypes.BoolValue + RootBlockDevice *BlockDevice + EBSBlockDevices []*BlockDevice + MetadataOptions MetadataOptions + UserData iacTypes.StringValue +} + +type LaunchTemplate struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Instance +} + +func (i *LaunchConfiguration) RequiresIMDSToken() bool { + return i.MetadataOptions.HttpTokens.EqualTo("required") +} + +func (i *LaunchConfiguration) HasHTTPEndpointDisabled() bool { + return i.MetadataOptions.HttpEndpoint.EqualTo("disabled") +} diff --git a/pkg/iac/providers/aws/ec2/subnet.go b/pkg/iac/providers/aws/ec2/subnet.go new file mode 100644 index 000000000000..18f605011e07 --- /dev/null +++ b/pkg/iac/providers/aws/ec2/subnet.go @@ -0,0 +1,10 @@ +package ec2 + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Subnet struct { + Metadata iacTypes.Metadata + MapPublicIpOnLaunch iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/ec2/volume.go b/pkg/iac/providers/aws/ec2/volume.go new file mode 100644 index 000000000000..3258a3ee6c5a --- /dev/null +++ b/pkg/iac/providers/aws/ec2/volume.go @@ -0,0 +1,16 @@ +package ec2 + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Volume struct { + Metadata iacTypes.Metadata + Encryption Encryption +} + +type Encryption struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + KMSKeyID iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/ec2/vpc.go b/pkg/iac/providers/aws/ec2/vpc.go new file mode 100644 index 000000000000..bce7fb4de2a8 --- /dev/null +++ b/pkg/iac/providers/aws/ec2/vpc.go @@ -0,0 +1,52 @@ +package ec2 + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type NetworkACL struct { + Metadata iacTypes.Metadata + Rules []NetworkACLRule + IsDefaultRule iacTypes.BoolValue +} + +type SecurityGroup struct { + Metadata iacTypes.Metadata + IsDefault iacTypes.BoolValue + Description iacTypes.StringValue + IngressRules []SecurityGroupRule + EgressRules []SecurityGroupRule + VPCID iacTypes.StringValue +} + +type SecurityGroupRule struct { + Metadata iacTypes.Metadata + Description iacTypes.StringValue + CIDRs []iacTypes.StringValue +} + +type VPC struct { + Metadata iacTypes.Metadata + ID iacTypes.StringValue + IsDefault iacTypes.BoolValue + SecurityGroups []SecurityGroup + FlowLogsEnabled iacTypes.BoolValue +} + +const ( + TypeIngress = "ingress" + TypeEgress = "egress" +) + +const ( + ActionAllow = "allow" + ActionDeny = "deny" +) + +type NetworkACLRule struct { + Metadata iacTypes.Metadata + Type iacTypes.StringValue + Action iacTypes.StringValue + Protocol iacTypes.StringValue + CIDRs []iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/ecr/ecr.go b/pkg/iac/providers/aws/ecr/ecr.go new file mode 100755 index 000000000000..053b7f13dace --- /dev/null +++ b/pkg/iac/providers/aws/ecr/ecr.go @@ -0,0 +1,34 @@ +package ecr + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type ECR struct { + Repositories []Repository +} + +type Repository struct { + Metadata iacTypes.Metadata + ImageScanning ImageScanning + ImageTagsImmutable iacTypes.BoolValue + Policies []iam.Policy + Encryption Encryption +} + +type ImageScanning struct { + Metadata iacTypes.Metadata + ScanOnPush iacTypes.BoolValue +} + +const ( + EncryptionTypeKMS = "KMS" + EncryptionTypeAES256 = "AES256" +) + +type Encryption struct { + Metadata iacTypes.Metadata + Type iacTypes.StringValue + KMSKeyID iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/ecs/ecs.go b/pkg/iac/providers/aws/ecs/ecs.go new file mode 100755 index 000000000000..b0728c2bbf7f --- /dev/null +++ b/pkg/iac/providers/aws/ecs/ecs.go @@ -0,0 +1,121 @@ +package ecs + +import ( + "encoding/json" + + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type ECS struct { + Clusters []Cluster + TaskDefinitions []TaskDefinition +} + +type Cluster struct { + Metadata iacTypes.Metadata + Settings ClusterSettings +} + +type ClusterSettings struct { + Metadata iacTypes.Metadata + ContainerInsightsEnabled iacTypes.BoolValue +} + +type TaskDefinition struct { + Metadata iacTypes.Metadata + Volumes []Volume + ContainerDefinitions []ContainerDefinition +} + +func CreateDefinitionsFromString(metadata iacTypes.Metadata, str string) ([]ContainerDefinition, error) { + var containerDefinitionsJSON []containerDefinitionJSON + if err := json.Unmarshal([]byte(str), &containerDefinitionsJSON); err != nil { + return nil, err + } + var definitions []ContainerDefinition + for _, j := range containerDefinitionsJSON { + definitions = append(definitions, j.convert(metadata)) + } + return definitions, nil +} + +// see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html +type containerDefinitionJSON struct { + Name string `json:"name"` + Image string `json:"image"` + CPU int `json:"cpu"` + Memory int `json:"memory"` + Essential bool `json:"essential"` + PortMappings []portMappingJSON `json:"portMappings"` + EnvVars []envVarJSON `json:"environment"` + Privileged bool `json:"privileged"` +} + +type envVarJSON struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type portMappingJSON struct { + ContainerPort int `json:"containerPort"` + HostPort int `json:"hostPort"` +} + +func (j containerDefinitionJSON) convert(metadata iacTypes.Metadata) ContainerDefinition { + var mappings []PortMapping + for _, jMapping := range j.PortMappings { + mappings = append(mappings, PortMapping{ + ContainerPort: iacTypes.Int(jMapping.ContainerPort, metadata), + HostPort: iacTypes.Int(jMapping.HostPort, metadata), + }) + } + var envVars []EnvVar + for _, env := range j.EnvVars { + envVars = append(envVars, EnvVar(env)) + } + return ContainerDefinition{ + Metadata: metadata, + Name: iacTypes.String(j.Name, metadata), + Image: iacTypes.String(j.Image, metadata), + CPU: iacTypes.Int(j.CPU, metadata), + Memory: iacTypes.Int(j.Memory, metadata), + Essential: iacTypes.Bool(j.Essential, metadata), + PortMappings: mappings, + Environment: envVars, + Privileged: iacTypes.Bool(j.Privileged, metadata), + } +} + +type ContainerDefinition struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Image iacTypes.StringValue + // TODO: CPU and Memory are strings + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html#cfn-ecs-taskdefinition-cpu + CPU iacTypes.IntValue + Memory iacTypes.IntValue + Essential iacTypes.BoolValue + PortMappings []PortMapping + Environment []EnvVar + Privileged iacTypes.BoolValue +} + +type EnvVar struct { + Name string + Value string +} + +type PortMapping struct { + ContainerPort iacTypes.IntValue + HostPort iacTypes.IntValue +} + +type Volume struct { + Metadata iacTypes.Metadata + EFSVolumeConfiguration EFSVolumeConfiguration +} + +type EFSVolumeConfiguration struct { + Metadata iacTypes.Metadata + TransitEncryptionEnabled iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/efs/efs.go b/pkg/iac/providers/aws/efs/efs.go new file mode 100755 index 000000000000..4e9765b7d500 --- /dev/null +++ b/pkg/iac/providers/aws/efs/efs.go @@ -0,0 +1,14 @@ +package efs + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type EFS struct { + FileSystems []FileSystem +} + +type FileSystem struct { + Metadata iacTypes.Metadata + Encrypted iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/eks/eks.go b/pkg/iac/providers/aws/eks/eks.go new file mode 100755 index 000000000000..9eab8b563f65 --- /dev/null +++ b/pkg/iac/providers/aws/eks/eks.go @@ -0,0 +1,32 @@ +package eks + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type EKS struct { + Clusters []Cluster +} + +type Cluster struct { + Metadata iacTypes.Metadata + Logging Logging + Encryption Encryption + PublicAccessEnabled iacTypes.BoolValue + PublicAccessCIDRs []iacTypes.StringValue +} + +type Logging struct { + Metadata iacTypes.Metadata + API iacTypes.BoolValue + Audit iacTypes.BoolValue + Authenticator iacTypes.BoolValue + ControllerManager iacTypes.BoolValue + Scheduler iacTypes.BoolValue +} + +type Encryption struct { + Metadata iacTypes.Metadata + Secrets iacTypes.BoolValue + KMSKeyID iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/elasticache/elasticache.go b/pkg/iac/providers/aws/elasticache/elasticache.go new file mode 100755 index 000000000000..4069601b3193 --- /dev/null +++ b/pkg/iac/providers/aws/elasticache/elasticache.go @@ -0,0 +1,29 @@ +package elasticache + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type ElastiCache struct { + Clusters []Cluster + ReplicationGroups []ReplicationGroup + SecurityGroups []SecurityGroup +} + +type Cluster struct { + Metadata iacTypes.Metadata + Engine iacTypes.StringValue + NodeType iacTypes.StringValue + SnapshotRetentionLimit iacTypes.IntValue // days +} + +type ReplicationGroup struct { + Metadata iacTypes.Metadata + TransitEncryptionEnabled iacTypes.BoolValue + AtRestEncryptionEnabled iacTypes.BoolValue +} + +type SecurityGroup struct { + Metadata iacTypes.Metadata + Description iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/elasticsearch/elasticsearch.go b/pkg/iac/providers/aws/elasticsearch/elasticsearch.go new file mode 100755 index 000000000000..4f3cb0583681 --- /dev/null +++ b/pkg/iac/providers/aws/elasticsearch/elasticsearch.go @@ -0,0 +1,53 @@ +package elasticsearch + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Elasticsearch struct { + Domains []Domain +} + +type Domain struct { + Metadata iacTypes.Metadata + DomainName iacTypes.StringValue + AccessPolicies iacTypes.StringValue + DedicatedMasterEnabled iacTypes.BoolValue + VpcId iacTypes.StringValue + LogPublishing LogPublishing + TransitEncryption TransitEncryption + AtRestEncryption AtRestEncryption + ServiceSoftwareOptions ServiceSoftwareOptions + Endpoint Endpoint +} + +type ServiceSoftwareOptions struct { + Metadata iacTypes.Metadata + CurrentVersion iacTypes.StringValue + NewVersion iacTypes.StringValue + UpdateAvailable iacTypes.BoolValue + UpdateStatus iacTypes.StringValue +} + +type Endpoint struct { + Metadata iacTypes.Metadata + EnforceHTTPS iacTypes.BoolValue + TLSPolicy iacTypes.StringValue +} + +type LogPublishing struct { + Metadata iacTypes.Metadata + AuditEnabled iacTypes.BoolValue + CloudWatchLogGroupArn iacTypes.StringValue +} + +type TransitEncryption struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} + +type AtRestEncryption struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + KmsKeyId iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/elb/elb.go b/pkg/iac/providers/aws/elb/elb.go new file mode 100755 index 000000000000..04803fd26380 --- /dev/null +++ b/pkg/iac/providers/aws/elb/elb.go @@ -0,0 +1,36 @@ +package elb + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type ELB struct { + LoadBalancers []LoadBalancer +} + +const ( + TypeApplication = "application" + TypeGateway = "gateway" + TypeNetwork = "network" + TypeClassic = "classic" +) + +type LoadBalancer struct { + Metadata iacTypes.Metadata + Type iacTypes.StringValue + DropInvalidHeaderFields iacTypes.BoolValue + Internal iacTypes.BoolValue + Listeners []Listener +} + +type Listener struct { + Metadata iacTypes.Metadata + Protocol iacTypes.StringValue + TLSPolicy iacTypes.StringValue + DefaultActions []Action +} + +type Action struct { + Metadata iacTypes.Metadata + Type iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/emr/emr.go b/pkg/iac/providers/aws/emr/emr.go new file mode 100644 index 000000000000..1d00618a0345 --- /dev/null +++ b/pkg/iac/providers/aws/emr/emr.go @@ -0,0 +1,28 @@ +package emr + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type EMR struct { + Clusters []Cluster + SecurityConfiguration []SecurityConfiguration +} + +type Cluster struct { + Metadata iacTypes.Metadata + Settings ClusterSettings +} + +type ClusterSettings struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + ReleaseLabel iacTypes.StringValue + ServiceRole iacTypes.StringValue +} + +type SecurityConfiguration struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Configuration iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/iam/actions.go b/pkg/iac/providers/aws/iam/actions.go new file mode 100644 index 000000000000..125d32c521da --- /dev/null +++ b/pkg/iac/providers/aws/iam/actions.go @@ -0,0 +1,5180 @@ +// Code generated by mage genallowedactions DO NOT EDIT. + +package iam + +var allowedActionsForResourceWildcardsMap = map[string]struct{}{ + "a2c:GetContainerizationJobDetails": {}, + "a2c:GetDeploymentJobDetails": {}, + "a2c:StartContainerizationJob": {}, + "a2c:StartDeploymentJob": {}, + "a4b:ApproveSkill": {}, + "a4b:AssociateSkillWithUsers": {}, + "a4b:CompleteRegistration": {}, + "a4b:CreateAddressBook": {}, + "a4b:CreateBusinessReportSchedule": {}, + "a4b:CreateConferenceProvider": {}, + "a4b:CreateContact": {}, + "a4b:CreateGatewayGroup": {}, + "a4b:CreateNetworkProfile": {}, + "a4b:CreateProfile": {}, + "a4b:CreateSkillGroup": {}, + "a4b:GetConferencePreference": {}, + "a4b:GetInvitationConfiguration": {}, + "a4b:ListBusinessReportSchedules": {}, + "a4b:ListConferenceProviders": {}, + "a4b:ListGatewayGroups": {}, + "a4b:ListSkills": {}, + "a4b:ListSkillsStoreCategories": {}, + "a4b:ListSkillsStoreSkillsByCategory": {}, + "a4b:PutConferencePreference": {}, + "a4b:PutDeviceSetupEvents": {}, + "a4b:PutInvitationConfiguration": {}, + "a4b:RegisterAVSDevice": {}, + "a4b:RegisterDevice": {}, + "a4b:RejectSkill": {}, + "a4b:ResolveRoom": {}, + "a4b:SearchAddressBooks": {}, + "a4b:SearchContacts": {}, + "a4b:SearchDevices": {}, + "a4b:SearchNetworkProfiles": {}, + "a4b:SearchProfiles": {}, + "a4b:SearchRooms": {}, + "a4b:SearchSkillGroups": {}, + "a4b:SearchUsers": {}, + "a4b:SendAnnouncement": {}, + "a4b:StartDeviceSync": {}, + "access-analyzer:CancelPolicyGeneration": {}, + "access-analyzer:CheckAccessNotGranted": {}, + "access-analyzer:CheckNoNewAccess": {}, + "access-analyzer:GetGeneratedPolicy": {}, + "access-analyzer:ListAnalyzers": {}, + "access-analyzer:ListPolicyGenerations": {}, + "access-analyzer:StartPolicyGeneration": {}, + "access-analyzer:ValidatePolicy": {}, + "acm-pca:CreateCertificateAuthority": {}, + "acm-pca:ListCertificateAuthorities": {}, + "acm:GetAccountConfiguration": {}, + "acm:ListCertificates": {}, + "acm:PutAccountConfiguration": {}, + "acm:RequestCertificate": {}, + "activate:CreateForm": {}, + "activate:GetAccountContact": {}, + "activate:GetContentInfo": {}, + "activate:GetCosts": {}, + "activate:GetCredits": {}, + "activate:GetMemberInfo": {}, + "activate:GetProgram": {}, + "activate:PutMemberInfo": {}, + "airflow:ListEnvironments": {}, + "amplify:ListApps": {}, + "amplifybackend:ListS3Buckets": {}, + "amplifyuibuilder:CreateComponent": {}, + "amplifyuibuilder:CreateForm": {}, + "amplifyuibuilder:CreateTheme": {}, + "amplifyuibuilder:ExchangeCodeForToken": {}, + "amplifyuibuilder:ExportComponents": {}, + "amplifyuibuilder:ExportForms": {}, + "amplifyuibuilder:ExportThemes": {}, + "amplifyuibuilder:GetMetadata": {}, + "amplifyuibuilder:ListCodegenJobs": {}, + "amplifyuibuilder:ListComponents": {}, + "amplifyuibuilder:ListForms": {}, + "amplifyuibuilder:ListThemes": {}, + "amplifyuibuilder:PutMetadataFlag": {}, + "amplifyuibuilder:RefreshToken": {}, + "amplifyuibuilder:ResetMetadataFlag": {}, + "amplifyuibuilder:StartCodegenJob": {}, + "aoss:BatchGetCollection": {}, + "aoss:BatchGetEffectiveLifecyclePolicy": {}, + "aoss:BatchGetLifecyclePolicy": {}, + "aoss:BatchGetVpcEndpoint": {}, + "aoss:CreateAccessPolicy": {}, + "aoss:CreateCollection": {}, + "aoss:CreateLifecyclePolicy": {}, + "aoss:CreateSecurityConfig": {}, + "aoss:CreateSecurityPolicy": {}, + "aoss:CreateVpcEndpoint": {}, + "aoss:DeleteAccessPolicy": {}, + "aoss:DeleteLifecyclePolicy": {}, + "aoss:DeleteSecurityConfig": {}, + "aoss:DeleteSecurityPolicy": {}, + "aoss:DeleteVpcEndpoint": {}, + "aoss:GetAccessPolicy": {}, + "aoss:GetAccountSettings": {}, + "aoss:GetPoliciesStats": {}, + "aoss:GetSecurityConfig": {}, + "aoss:GetSecurityPolicy": {}, + "aoss:ListAccessPolicies": {}, + "aoss:ListCollections": {}, + "aoss:ListLifecyclePolicies": {}, + "aoss:ListSecurityConfigs": {}, + "aoss:ListSecurityPolicies": {}, + "aoss:ListTagsForResource": {}, + "aoss:ListVpcEndpoints": {}, + "aoss:TagResource": {}, + "aoss:UntagResource": {}, + "aoss:UpdateAccessPolicy": {}, + "aoss:UpdateAccountSettings": {}, + "aoss:UpdateLifecyclePolicy": {}, + "aoss:UpdateSecurityConfig": {}, + "aoss:UpdateSecurityPolicy": {}, + "aoss:UpdateVpcEndpoint": {}, + "app-integrations:ListApplicationAssociations": {}, + "app-integrations:ListApplications": {}, + "app-integrations:ListDataIntegrationAssociations": {}, + "app-integrations:ListDataIntegrations": {}, + "app-integrations:ListEventIntegrationAssociations": {}, + "app-integrations:ListEventIntegrations": {}, + "appconfig:CreateApplication": {}, + "appconfig:CreateDeploymentStrategy": {}, + "appconfig:CreateExtension": {}, + "appconfig:CreateExtensionAssociation": {}, + "appconfig:ListApplications": {}, + "appconfig:ListDeploymentStrategies": {}, + "appconfig:ListExtensionAssociations": {}, + "appconfig:ListExtensions": {}, + "appfabric:ListAppBundles": {}, + "appflow:CreateConnectorProfile": {}, + "appflow:CreateFlow": {}, + "appflow:DescribeConnectorProfiles": {}, + "appflow:DescribeConnectors": {}, + "appflow:DescribeFlows": {}, + "appflow:RegisterConnector": {}, + "application-autoscaling:DescribeScalableTargets": {}, + "application-autoscaling:DescribeScalingActivities": {}, + "application-autoscaling:DescribeScalingPolicies": {}, + "application-autoscaling:DescribeScheduledActions": {}, + "application-cost-profiler:DeleteReportDefinition": {}, + "application-cost-profiler:GetReportDefinition": {}, + "application-cost-profiler:ImportApplicationUsage": {}, + "application-cost-profiler:ListReportDefinitions": {}, + "application-cost-profiler:PutReportDefinition": {}, + "application-cost-profiler:UpdateReportDefinition": {}, + "application-transformation:GetContainerization": {}, + "application-transformation:GetDeployment": {}, + "application-transformation:GetGroupingAssessment": {}, + "application-transformation:GetPortingCompatibilityAssessment": {}, + "application-transformation:GetPortingRecommendationAssessment": {}, + "application-transformation:GetRuntimeAssessment": {}, + "application-transformation:PutLogData": {}, + "application-transformation:PutMetricData": {}, + "application-transformation:StartContainerization": {}, + "application-transformation:StartDeployment": {}, + "application-transformation:StartGroupingAssessment": {}, + "application-transformation:StartPortingCompatibilityAssessment": {}, + "application-transformation:StartPortingRecommendationAssessment": {}, + "application-transformation:StartRuntimeAssessment": {}, + "applicationinsights:AddWorkload": {}, + "applicationinsights:CreateApplication": {}, + "applicationinsights:CreateComponent": {}, + "applicationinsights:CreateLogPattern": {}, + "applicationinsights:DeleteApplication": {}, + "applicationinsights:DeleteComponent": {}, + "applicationinsights:DeleteLogPattern": {}, + "applicationinsights:DescribeApplication": {}, + "applicationinsights:DescribeComponent": {}, + "applicationinsights:DescribeComponentConfiguration": {}, + "applicationinsights:DescribeComponentConfigurationRecommendation": {}, + "applicationinsights:DescribeLogPattern": {}, + "applicationinsights:DescribeObservation": {}, + "applicationinsights:DescribeProblem": {}, + "applicationinsights:DescribeProblemObservations": {}, + "applicationinsights:DescribeWorkload": {}, + "applicationinsights:Link": {}, + "applicationinsights:ListApplications": {}, + "applicationinsights:ListComponents": {}, + "applicationinsights:ListConfigurationHistory": {}, + "applicationinsights:ListLogPatternSets": {}, + "applicationinsights:ListLogPatterns": {}, + "applicationinsights:ListProblems": {}, + "applicationinsights:ListTagsForResource": {}, + "applicationinsights:ListWorkloads": {}, + "applicationinsights:RemoveWorkload": {}, + "applicationinsights:TagResource": {}, + "applicationinsights:UntagResource": {}, + "applicationinsights:UpdateApplication": {}, + "applicationinsights:UpdateComponent": {}, + "applicationinsights:UpdateComponentConfiguration": {}, + "applicationinsights:UpdateLogPattern": {}, + "applicationinsights:UpdateProblem": {}, + "applicationinsights:UpdateWorkload": {}, + "appmesh-preview:ListMeshes": {}, + "appmesh:ListMeshes": {}, + "apprunner:ListAutoScalingConfigurations": {}, + "apprunner:ListConnections": {}, + "apprunner:ListObservabilityConfigurations": {}, + "apprunner:ListServices": {}, + "apprunner:ListVpcConnectors": {}, + "apprunner:ListVpcIngressConnections": {}, + "appstream:CreateAppBlock": {}, + "appstream:CreateDirectoryConfig": {}, + "appstream:CreateUsageReportSubscription": {}, + "appstream:CreateUser": {}, + "appstream:DeleteDirectoryConfig": {}, + "appstream:DeleteUsageReportSubscription": {}, + "appstream:DeleteUser": {}, + "appstream:DescribeDirectoryConfigs": {}, + "appstream:DescribeUsageReportSubscriptions": {}, + "appstream:DescribeUsers": {}, + "appstream:DisableUser": {}, + "appstream:EnableUser": {}, + "appstream:ExpireSession": {}, + "appstream:ListTagsForResource": {}, + "appstream:UpdateDirectoryConfig": {}, + "appsync:CreateApiCache": {}, + "appsync:CreateApiKey": {}, + "appsync:CreateDataSource": {}, + "appsync:CreateDomainName": {}, + "appsync:CreateFunction": {}, + "appsync:CreateGraphqlApi": {}, + "appsync:CreateResolver": {}, + "appsync:CreateType": {}, + "appsync:DeleteApiCache": {}, + "appsync:DeleteApiKey": {}, + "appsync:DeleteDataSource": {}, + "appsync:DeleteFunction": {}, + "appsync:DeleteResolver": {}, + "appsync:DeleteResourcePolicy": {}, + "appsync:DeleteType": {}, + "appsync:EvaluateCode": {}, + "appsync:EvaluateMappingTemplate": {}, + "appsync:FlushApiCache": {}, + "appsync:GetApiCache": {}, + "appsync:GetDataSource": {}, + "appsync:GetDataSourceIntrospection": {}, + "appsync:GetFunction": {}, + "appsync:GetGraphqlApiEnvironmentVariables": {}, + "appsync:GetIntrospectionSchema": {}, + "appsync:GetResolver": {}, + "appsync:GetResourcePolicy": {}, + "appsync:GetSchemaCreationStatus": {}, + "appsync:GetType": {}, + "appsync:ListApiKeys": {}, + "appsync:ListDataSources": {}, + "appsync:ListDomainNames": {}, + "appsync:ListFunctions": {}, + "appsync:ListGraphqlApis": {}, + "appsync:ListResolvers": {}, + "appsync:ListResolversByFunction": {}, + "appsync:ListSourceApiAssociations": {}, + "appsync:ListTypes": {}, + "appsync:ListTypesByAssociation": {}, + "appsync:PutGraphqlApiEnvironmentVariables": {}, + "appsync:PutResourcePolicy": {}, + "appsync:SetWebACL": {}, + "appsync:StartDataSourceIntrospection": {}, + "appsync:StartSchemaCreation": {}, + "appsync:UpdateApiCache": {}, + "appsync:UpdateApiKey": {}, + "appsync:UpdateDataSource": {}, + "appsync:UpdateFunction": {}, + "appsync:UpdateResolver": {}, + "appsync:UpdateType": {}, + "aps:CreateWorkspace": {}, + "aps:GetDefaultScraperConfiguration": {}, + "aps:ListScrapers": {}, + "aps:ListWorkspaces": {}, + "arc-zonal-shift:ListAutoshifts": {}, + "arc-zonal-shift:ListManagedResources": {}, + "arc-zonal-shift:ListZonalShifts": {}, + "arsenal:RegisterOnPremisesAgent": {}, + "artifact:GetAccountSettings": {}, + "artifact:ListReports": {}, + "artifact:PutAccountSettings": {}, + "athena:GetCatalogs": {}, + "athena:GetExecutionEngine": {}, + "athena:GetExecutionEngines": {}, + "athena:GetNamespace": {}, + "athena:GetNamespaces": {}, + "athena:GetQueryExecutions": {}, + "athena:GetTable": {}, + "athena:GetTables": {}, + "athena:ListApplicationDPUSizes": {}, + "athena:ListCapacityReservations": {}, + "athena:ListDataCatalogs": {}, + "athena:ListEngineVersions": {}, + "athena:ListExecutors": {}, + "athena:ListWorkGroups": {}, + "athena:RunQuery": {}, + "auditmanager:CreateAssessment": {}, + "auditmanager:CreateAssessmentFramework": {}, + "auditmanager:CreateControl": {}, + "auditmanager:DeleteAssessmentFrameworkShare": {}, + "auditmanager:DeregisterAccount": {}, + "auditmanager:DeregisterOrganizationAdminAccount": {}, + "auditmanager:GetAccountStatus": {}, + "auditmanager:GetDelegations": {}, + "auditmanager:GetEvidenceFileUploadUrl": {}, + "auditmanager:GetInsights": {}, + "auditmanager:GetInsightsByAssessment": {}, + "auditmanager:GetOrganizationAdminAccount": {}, + "auditmanager:GetServicesInScope": {}, + "auditmanager:GetSettings": {}, + "auditmanager:ListAssessmentControlInsightsByControlDomain": {}, + "auditmanager:ListAssessmentFrameworkShareRequests": {}, + "auditmanager:ListAssessmentFrameworks": {}, + "auditmanager:ListAssessmentReports": {}, + "auditmanager:ListAssessments": {}, + "auditmanager:ListControlDomainInsights": {}, + "auditmanager:ListControlDomainInsightsByAssessment": {}, + "auditmanager:ListControlInsightsByControlDomain": {}, + "auditmanager:ListControls": {}, + "auditmanager:ListKeywordsForDataSource": {}, + "auditmanager:ListNotifications": {}, + "auditmanager:RegisterAccount": {}, + "auditmanager:RegisterOrganizationAdminAccount": {}, + "auditmanager:UpdateAssessmentFrameworkShare": {}, + "auditmanager:UpdateSettings": {}, + "auditmanager:ValidateAssessmentReportIntegrity": {}, + "autoscaling-plans:CreateScalingPlan": {}, + "autoscaling-plans:DeleteScalingPlan": {}, + "autoscaling-plans:DescribeScalingPlanResources": {}, + "autoscaling-plans:DescribeScalingPlans": {}, + "autoscaling-plans:GetScalingPlanResourceForecastData": {}, + "autoscaling-plans:UpdateScalingPlan": {}, + "autoscaling:DescribeAccountLimits": {}, + "autoscaling:DescribeAdjustmentTypes": {}, + "autoscaling:DescribeAutoScalingGroups": {}, + "autoscaling:DescribeAutoScalingInstances": {}, + "autoscaling:DescribeAutoScalingNotificationTypes": {}, + "autoscaling:DescribeInstanceRefreshes": {}, + "autoscaling:DescribeLaunchConfigurations": {}, + "autoscaling:DescribeLifecycleHookTypes": {}, + "autoscaling:DescribeLifecycleHooks": {}, + "autoscaling:DescribeLoadBalancerTargetGroups": {}, + "autoscaling:DescribeLoadBalancers": {}, + "autoscaling:DescribeMetricCollectionTypes": {}, + "autoscaling:DescribeNotificationConfigurations": {}, + "autoscaling:DescribePolicies": {}, + "autoscaling:DescribeScalingActivities": {}, + "autoscaling:DescribeScalingProcessTypes": {}, + "autoscaling:DescribeScheduledActions": {}, + "autoscaling:DescribeTags": {}, + "autoscaling:DescribeTerminationPolicyTypes": {}, + "autoscaling:DescribeTrafficSources": {}, + "autoscaling:DescribeWarmPool": {}, + "autoscaling:GetPredictiveScalingForecast": {}, + "aws-marketplace-management:GetAdditionalSellerNotificationRecipients": {}, + "aws-marketplace-management:GetBankAccountVerificationDetails": {}, + "aws-marketplace-management:GetSecondaryUserVerificationDetails": {}, + "aws-marketplace-management:GetSellerVerificationDetails": {}, + "aws-marketplace-management:PutAdditionalSellerNotificationRecipients": {}, + "aws-marketplace-management:PutBankAccountVerificationDetails": {}, + "aws-marketplace-management:PutSecondaryUserVerificationDetails": {}, + "aws-marketplace-management:PutSellerVerificationDetails": {}, + "aws-marketplace-management:uploadFiles": {}, + "aws-marketplace-management:viewMarketing": {}, + "aws-marketplace-management:viewReports": {}, + "aws-marketplace-management:viewSettings": {}, + "aws-marketplace-management:viewSupport": {}, + "aws-marketplace:AcceptAgreementApprovalRequest": {}, + "aws-marketplace:AcceptAgreementRequest": {}, + "aws-marketplace:AssociateProductsWithPrivateMarketplace": {}, + "aws-marketplace:BatchMeterUsage": {}, + "aws-marketplace:CancelAgreement": {}, + "aws-marketplace:CancelAgreementRequest": {}, + "aws-marketplace:CompleteTask": {}, + "aws-marketplace:CreateAgreementRequest": {}, + "aws-marketplace:CreatePrivateMarketplaceRequests": {}, + "aws-marketplace:DescribeAgreement": {}, + "aws-marketplace:DescribeBuilds": {}, + "aws-marketplace:DescribePrivateMarketplaceRequests": {}, + "aws-marketplace:DescribeProcurementSystemConfiguration": {}, + "aws-marketplace:DescribeTask": {}, + "aws-marketplace:DisassociateProductsFromPrivateMarketplace": {}, + "aws-marketplace:GetAgreementApprovalRequest": {}, + "aws-marketplace:GetAgreementRequest": {}, + "aws-marketplace:GetAgreementTerms": {}, + "aws-marketplace:ListAgreementApprovalRequests": {}, + "aws-marketplace:ListAgreementRequests": {}, + "aws-marketplace:ListBuilds": {}, + "aws-marketplace:ListChangeSets": {}, + "aws-marketplace:ListEntities": {}, + "aws-marketplace:ListEntitlementDetails": {}, + "aws-marketplace:ListPrivateListings": {}, + "aws-marketplace:ListPrivateMarketplaceRequests": {}, + "aws-marketplace:ListTasks": {}, + "aws-marketplace:MeterUsage": {}, + "aws-marketplace:PutProcurementSystemConfiguration": {}, + "aws-marketplace:RegisterUsage": {}, + "aws-marketplace:RejectAgreementApprovalRequest": {}, + "aws-marketplace:ResolveCustomer": {}, + "aws-marketplace:SearchAgreements": {}, + "aws-marketplace:StartBuild": {}, + "aws-marketplace:Subscribe": {}, + "aws-marketplace:Unsubscribe": {}, + "aws-marketplace:UpdateAgreementApprovalRequest": {}, + "aws-marketplace:UpdateTask": {}, + "aws-marketplace:ViewSubscriptions": {}, + "aws-portal:GetConsoleActionSetEnforced": {}, + "aws-portal:ModifyAccount": {}, + "aws-portal:ModifyBilling": {}, + "aws-portal:ModifyPaymentMethods": {}, + "aws-portal:UpdateConsoleActionSetEnforced": {}, + "aws-portal:ViewAccount": {}, + "aws-portal:ViewBilling": {}, + "aws-portal:ViewPaymentMethods": {}, + "aws-portal:ViewUsage": {}, + "awsconnector:GetConnectorHealth": {}, + "awsconnector:RegisterConnector": {}, + "awsconnector:ValidateConnectorId": {}, + "b2bi:CreateProfile": {}, + "b2bi:CreateTransformer": {}, + "b2bi:ListCapabilities": {}, + "b2bi:ListPartnerships": {}, + "b2bi:ListProfiles": {}, + "b2bi:ListTransformers": {}, + "backup-gateway:CreateGateway": {}, + "backup-gateway:ImportHypervisorConfiguration": {}, + "backup-gateway:ListGateways": {}, + "backup-gateway:ListHypervisors": {}, + "backup-gateway:ListVirtualMachines": {}, + "backup-storage:CommitBackupJob": {}, + "backup-storage:DeleteObjects": {}, + "backup-storage:DescribeBackupJob": {}, + "backup-storage:GetBaseBackup": {}, + "backup-storage:GetChunk": {}, + "backup-storage:GetIncrementalBaseBackup": {}, + "backup-storage:GetObjectMetadata": {}, + "backup-storage:ListChunks": {}, + "backup-storage:ListObjects": {}, + "backup-storage:MountCapsule": {}, + "backup-storage:NotifyObjectComplete": {}, + "backup-storage:PutChunk": {}, + "backup-storage:PutObject": {}, + "backup-storage:StartObject": {}, + "backup-storage:UpdateObjectComplete": {}, + "backup:DescribeBackupJob": {}, + "backup:DescribeCopyJob": {}, + "backup:DescribeGlobalSettings": {}, + "backup:DescribeProtectedResource": {}, + "backup:DescribeRegionSettings": {}, + "backup:DescribeReportJob": {}, + "backup:DescribeRestoreJob": {}, + "backup:ExportBackupPlanTemplate": {}, + "backup:GetBackupPlanFromJSON": {}, + "backup:GetBackupPlanFromTemplate": {}, + "backup:GetRestoreJobMetadata": {}, + "backup:GetRestoreTestingInferredMetadata": {}, + "backup:GetSupportedResourceTypes": {}, + "backup:ListBackupJobSummaries": {}, + "backup:ListBackupJobs": {}, + "backup:ListBackupPlanTemplates": {}, + "backup:ListBackupPlans": {}, + "backup:ListBackupVaults": {}, + "backup:ListCopyJobSummaries": {}, + "backup:ListCopyJobs": {}, + "backup:ListFrameworks": {}, + "backup:ListLegalHolds": {}, + "backup:ListProtectedResources": {}, + "backup:ListRecoveryPointsByResource": {}, + "backup:ListReportJobs": {}, + "backup:ListReportPlans": {}, + "backup:ListRestoreJobSummaries": {}, + "backup:ListRestoreJobs": {}, + "backup:ListRestoreJobsByProtectedResource": {}, + "backup:ListRestoreTestingPlans": {}, + "backup:PutRestoreValidationResult": {}, + "backup:StopBackupJob": {}, + "backup:UpdateGlobalSettings": {}, + "backup:UpdateRegionSettings": {}, + "batch:DescribeComputeEnvironments": {}, + "batch:DescribeJobDefinitions": {}, + "batch:DescribeJobQueues": {}, + "batch:DescribeJobs": {}, + "batch:DescribeSchedulingPolicies": {}, + "batch:ListJobs": {}, + "batch:ListSchedulingPolicies": {}, + "bcm-data-exports:ListExports": {}, + "bcm-data-exports:ListTables": {}, + "bedrock:AssociateThirdPartyKnowledgeBase": {}, + "bedrock:CreateAgent": {}, + "bedrock:CreateFoundationModelAgreement": {}, + "bedrock:CreateGuardrail": {}, + "bedrock:CreateKnowledgeBase": {}, + "bedrock:DeleteFoundationModelAgreement": {}, + "bedrock:DeleteModelInvocationLoggingConfiguration": {}, + "bedrock:GetFoundationModelAvailability": {}, + "bedrock:GetModelInvocationLoggingConfiguration": {}, + "bedrock:GetUseCaseForModelAccess": {}, + "bedrock:ListAgents": {}, + "bedrock:ListCustomModels": {}, + "bedrock:ListFoundationModelAgreementOffers": {}, + "bedrock:ListFoundationModels": {}, + "bedrock:ListKnowledgeBases": {}, + "bedrock:ListModelCustomizationJobs": {}, + "bedrock:ListModelEvaluationJobs": {}, + "bedrock:ListModelInvocationJobs": {}, + "bedrock:ListProvisionedModelThroughputs": {}, + "bedrock:PutFoundationModelEntitlement": {}, + "bedrock:PutModelInvocationLoggingConfiguration": {}, + "bedrock:PutUseCaseForModelAccess": {}, + "bedrock:RetrieveAndGenerate": {}, + "billing:GetBillingData": {}, + "billing:GetBillingDetails": {}, + "billing:GetBillingNotifications": {}, + "billing:GetBillingPreferences": {}, + "billing:GetContractInformation": {}, + "billing:GetCredits": {}, + "billing:GetIAMAccessPreference": {}, + "billing:GetSellerOfRecord": {}, + "billing:ListBillingViews": {}, + "billing:PutContractInformation": {}, + "billing:RedeemCredits": {}, + "billing:UpdateBillingPreferences": {}, + "billing:UpdateIAMAccessPreference": {}, + "billingconductor:CreatePricingRule": {}, + "billingconductor:ListAccountAssociations": {}, + "billingconductor:ListBillingGroupCostReports": {}, + "billingconductor:ListBillingGroups": {}, + "billingconductor:ListCustomLineItems": {}, + "billingconductor:ListPricingPlans": {}, + "billingconductor:ListPricingRules": {}, + "braket:AcceptUserAgreement": {}, + "braket:AccessBraketFeature": {}, + "braket:CreateJob": {}, + "braket:CreateQuantumTask": {}, + "braket:GetDevice": {}, + "braket:GetServiceLinkedRoleStatus": {}, + "braket:GetUserAgreementStatus": {}, + "braket:SearchDevices": {}, + "braket:SearchJobs": {}, + "braket:SearchQuantumTasks": {}, + "budgets:DescribeBudgetActionsForAccount": {}, + "bugbust:CreateEvent": {}, + "bugbust:ListEvents": {}, + "cases:CreateDomain": {}, + "cases:ListDomains": {}, + "cases:ListTagsForResource": {}, + "ce:CreateAnomalyMonitor": {}, + "ce:CreateAnomalySubscription": {}, + "ce:CreateCostCategoryDefinition": {}, + "ce:CreateNotificationSubscription": {}, + "ce:CreateReport": {}, + "ce:DeleteNotificationSubscription": {}, + "ce:DeleteReport": {}, + "ce:DescribeNotificationSubscription": {}, + "ce:DescribeReport": {}, + "ce:GetApproximateUsageRecords": {}, + "ce:GetConsoleActionSetEnforced": {}, + "ce:GetCostAndUsage": {}, + "ce:GetCostAndUsageWithResources": {}, + "ce:GetCostCategories": {}, + "ce:GetCostForecast": {}, + "ce:GetDimensionValues": {}, + "ce:GetPreferences": {}, + "ce:GetReservationCoverage": {}, + "ce:GetReservationPurchaseRecommendation": {}, + "ce:GetReservationUtilization": {}, + "ce:GetRightsizingRecommendation": {}, + "ce:GetSavingsPlanPurchaseRecommendationDetails": {}, + "ce:GetSavingsPlansCoverage": {}, + "ce:GetSavingsPlansPurchaseRecommendation": {}, + "ce:GetSavingsPlansUtilization": {}, + "ce:GetSavingsPlansUtilizationDetails": {}, + "ce:GetTags": {}, + "ce:GetUsageForecast": {}, + "ce:ListCostAllocationTags": {}, + "ce:ListCostCategoryDefinitions": {}, + "ce:ListSavingsPlansPurchaseRecommendationGeneration": {}, + "ce:ProvideAnomalyFeedback": {}, + "ce:StartSavingsPlansPurchaseRecommendationGeneration": {}, + "ce:UpdateConsoleActionSetEnforced": {}, + "ce:UpdateCostAllocationTagsStatus": {}, + "ce:UpdateNotificationSubscription": {}, + "ce:UpdatePreferences": {}, + "ce:UpdateReport": {}, + "chatbot:CreateChimeWebhookConfiguration": {}, + "chatbot:CreateMicrosoftTeamsChannelConfiguration": {}, + "chatbot:CreateSlackChannelConfiguration": {}, + "chatbot:DeleteMicrosoftTeamsChannelConfiguration": {}, + "chatbot:DeleteMicrosoftTeamsConfiguredTeam": {}, + "chatbot:DeleteMicrosoftTeamsUserIdentity": {}, + "chatbot:DeleteSlackUserIdentity": {}, + "chatbot:DeleteSlackWorkspaceAuthorization": {}, + "chatbot:DescribeChimeWebhookConfigurations": {}, + "chatbot:DescribeSlackChannelConfigurations": {}, + "chatbot:DescribeSlackChannels": {}, + "chatbot:DescribeSlackUserIdentities": {}, + "chatbot:DescribeSlackWorkspaces": {}, + "chatbot:GetAccountPreferences": {}, + "chatbot:GetMicrosoftTeamsChannelConfiguration": {}, + "chatbot:GetMicrosoftTeamsOauthParameters": {}, + "chatbot:GetSlackOauthParameters": {}, + "chatbot:ListMicrosoftTeamsChannelConfigurations": {}, + "chatbot:ListMicrosoftTeamsConfiguredTeams": {}, + "chatbot:ListMicrosoftTeamsUserIdentities": {}, + "chatbot:RedeemMicrosoftTeamsOauthCode": {}, + "chatbot:RedeemSlackOauthCode": {}, + "chatbot:UpdateAccountPreferences": {}, + "chatbot:UpdateMicrosoftTeamsChannelConfiguration": {}, + "chime:AcceptDelegate": {}, + "chime:ActivateUsers": {}, + "chime:AddDomain": {}, + "chime:AddOrUpdateGroups": {}, + "chime:AssociatePhoneNumberWithUser": {}, + "chime:AssociatePhoneNumbersWithVoiceConnectorGroup": {}, + "chime:AssociateSigninDelegateGroupsWithAccount": {}, + "chime:AuthorizeDirectory": {}, + "chime:BatchCreateRoomMembership": {}, + "chime:BatchDeletePhoneNumber": {}, + "chime:BatchSuspendUser": {}, + "chime:BatchUnsuspendUser": {}, + "chime:BatchUpdatePhoneNumber": {}, + "chime:BatchUpdateUser": {}, + "chime:ConnectDirectory": {}, + "chime:CreateAccount": {}, + "chime:CreateApiKey": {}, + "chime:CreateAppInstance": {}, + "chime:CreateAppInstanceBot": {}, + "chime:CreateAppInstanceUser": {}, + "chime:CreateBot": {}, + "chime:CreateCDRBucket": {}, + "chime:CreateMediaCapturePipeline": {}, + "chime:CreateMediaConcatenationPipeline": {}, + "chime:CreateMediaInsightsPipelineConfiguration": {}, + "chime:CreateMediaLiveConnectorPipeline": {}, + "chime:CreateMediaPipelineKinesisVideoStreamPool": {}, + "chime:CreateMeeting": {}, + "chime:CreateMeetingWithAttendees": {}, + "chime:CreatePhoneNumberOrder": {}, + "chime:CreateRoom": {}, + "chime:CreateRoomMembership": {}, + "chime:CreateSipMediaApplication": {}, + "chime:CreateUser": {}, + "chime:CreateVoiceConnector": {}, + "chime:CreateVoiceProfile": {}, + "chime:CreateVoiceProfileDomain": {}, + "chime:DeleteAccount": {}, + "chime:DeleteAccountOpenIdConfig": {}, + "chime:DeleteApiKey": {}, + "chime:DeleteCDRBucket": {}, + "chime:DeleteDelegate": {}, + "chime:DeleteDomain": {}, + "chime:DeleteEventsConfiguration": {}, + "chime:DeleteGroups": {}, + "chime:DeletePhoneNumber": {}, + "chime:DeleteRoom": {}, + "chime:DeleteRoomMembership": {}, + "chime:DeleteSipRule": {}, + "chime:DeleteVoiceConnectorGroup": {}, + "chime:DisassociatePhoneNumberFromUser": {}, + "chime:DisassociatePhoneNumbersFromVoiceConnectorGroup": {}, + "chime:DisassociateSigninDelegateGroupsFromAccount": {}, + "chime:DisconnectDirectory": {}, + "chime:GetAccount": {}, + "chime:GetAccountResource": {}, + "chime:GetAccountSettings": {}, + "chime:GetAccountWithOpenIdConfig": {}, + "chime:GetBot": {}, + "chime:GetCDRBucket": {}, + "chime:GetDomain": {}, + "chime:GetEventsConfiguration": {}, + "chime:GetGlobalSettings": {}, + "chime:GetMeetingDetail": {}, + "chime:GetMessagingSessionEndpoint": {}, + "chime:GetPhoneNumber": {}, + "chime:GetPhoneNumberOrder": {}, + "chime:GetPhoneNumberSettings": {}, + "chime:GetRetentionSettings": {}, + "chime:GetRoom": {}, + "chime:GetSipRule": {}, + "chime:GetTelephonyLimits": {}, + "chime:GetUser": {}, + "chime:GetUserActivityReportData": {}, + "chime:GetUserByEmail": {}, + "chime:GetUserSettings": {}, + "chime:GetVoiceConnectorGroup": {}, + "chime:InviteDelegate": {}, + "chime:InviteUsers": {}, + "chime:InviteUsersFromProvider": {}, + "chime:ListAccountUsageReportData": {}, + "chime:ListAccounts": {}, + "chime:ListApiKeys": {}, + "chime:ListAvailableVoiceConnectorRegions": {}, + "chime:ListBots": {}, + "chime:ListCDRBucket": {}, + "chime:ListCallingRegions": {}, + "chime:ListDelegates": {}, + "chime:ListDirectories": {}, + "chime:ListDomains": {}, + "chime:ListGroups": {}, + "chime:ListMediaCapturePipelines": {}, + "chime:ListMediaInsightsPipelineConfigurations": {}, + "chime:ListMediaPipelineKinesisVideoStreamPools": {}, + "chime:ListMediaPipelines": {}, + "chime:ListMeetingEvents": {}, + "chime:ListMeetings": {}, + "chime:ListMeetingsReportData": {}, + "chime:ListPhoneNumberOrders": {}, + "chime:ListPhoneNumbers": {}, + "chime:ListRoomMemberships": {}, + "chime:ListRooms": {}, + "chime:ListSipMediaApplications": {}, + "chime:ListSupportedPhoneNumberCountries": {}, + "chime:ListUsers": {}, + "chime:ListVoiceConnectorGroups": {}, + "chime:ListVoiceConnectors": {}, + "chime:ListVoiceProfileDomains": {}, + "chime:LogoutUser": {}, + "chime:PutEventsConfiguration": {}, + "chime:PutRetentionSettings": {}, + "chime:RedactConversationMessage": {}, + "chime:RedactRoomMessage": {}, + "chime:RegenerateSecurityToken": {}, + "chime:RenameAccount": {}, + "chime:RenewDelegate": {}, + "chime:ResetAccountResource": {}, + "chime:ResetPersonalPIN": {}, + "chime:RestorePhoneNumber": {}, + "chime:RetrieveDataExports": {}, + "chime:SearchAvailablePhoneNumbers": {}, + "chime:StartDataExport": {}, + "chime:StartMeetingTranscription": {}, + "chime:StopMeetingTranscription": {}, + "chime:SubmitSupportRequest": {}, + "chime:SuspendUsers": {}, + "chime:UnauthorizeDirectory": {}, + "chime:UpdateAccount": {}, + "chime:UpdateAccountOpenIdConfig": {}, + "chime:UpdateAccountResource": {}, + "chime:UpdateAccountSettings": {}, + "chime:UpdateBot": {}, + "chime:UpdateCDRSettings": {}, + "chime:UpdateGlobalSettings": {}, + "chime:UpdatePhoneNumber": {}, + "chime:UpdatePhoneNumberSettings": {}, + "chime:UpdateRoom": {}, + "chime:UpdateRoomMembership": {}, + "chime:UpdateSupportedLicenses": {}, + "chime:UpdateUser": {}, + "chime:UpdateUserLicenses": {}, + "chime:UpdateUserSettings": {}, + "chime:ValidateAccountResource": {}, + "chime:ValidateE911Address": {}, + "cleanrooms-ml:CreateTrainingDataset": {}, + "cleanrooms-ml:ListAudienceModels": {}, + "cleanrooms-ml:ListConfiguredAudienceModels": {}, + "cleanrooms-ml:ListTrainingDatasets": {}, + "cleanrooms:ListCollaborations": {}, + "cleanrooms:ListConfiguredTables": {}, + "cleanrooms:ListMemberships": {}, + "cloud9:CreateEnvironmentEC2": {}, + "cloud9:CreateEnvironmentSSH": {}, + "cloud9:GetMigrationExperiences": {}, + "cloud9:GetUserPublicKey": {}, + "cloud9:GetUserSettings": {}, + "cloud9:ListEnvironments": {}, + "cloud9:UpdateUserSettings": {}, + "cloud9:ValidateEnvironmentName": {}, + "clouddirectory:CreateSchema": {}, + "clouddirectory:ListDevelopmentSchemaArns": {}, + "clouddirectory:ListDirectories": {}, + "clouddirectory:ListManagedSchemaArns": {}, + "clouddirectory:ListPublishedSchemaArns": {}, + "clouddirectory:PutSchemaFromJson": {}, + "cloudformation:ActivateOrganizationsAccess": {}, + "cloudformation:ActivateType": {}, + "cloudformation:BatchDescribeTypeConfigurations": {}, + "cloudformation:CancelResourceRequest": {}, + "cloudformation:CreateGeneratedTemplate": {}, + "cloudformation:CreateResource": {}, + "cloudformation:CreateStackSet": {}, + "cloudformation:CreateUploadBucket": {}, + "cloudformation:DeactivateOrganizationsAccess": {}, + "cloudformation:DeactivateType": {}, + "cloudformation:DeleteGeneratedTemplate": {}, + "cloudformation:DeleteResource": {}, + "cloudformation:DeregisterType": {}, + "cloudformation:DescribeAccountLimits": {}, + "cloudformation:DescribeGeneratedTemplate": {}, + "cloudformation:DescribeOrganizationsAccess": {}, + "cloudformation:DescribePublisher": {}, + "cloudformation:DescribeResourceScan": {}, + "cloudformation:DescribeStackDriftDetectionStatus": {}, + "cloudformation:DescribeType": {}, + "cloudformation:DescribeTypeRegistration": {}, + "cloudformation:EstimateTemplateCost": {}, + "cloudformation:GetGeneratedTemplate": {}, + "cloudformation:GetResource": {}, + "cloudformation:GetResourceRequestStatus": {}, + "cloudformation:ListExports": {}, + "cloudformation:ListGeneratedTemplates": {}, + "cloudformation:ListImports": {}, + "cloudformation:ListResourceRequests": {}, + "cloudformation:ListResourceScanRelatedResources": {}, + "cloudformation:ListResourceScanResources": {}, + "cloudformation:ListResourceScans": {}, + "cloudformation:ListResources": {}, + "cloudformation:ListStackSets": {}, + "cloudformation:ListStacks": {}, + "cloudformation:ListTypeRegistrations": {}, + "cloudformation:ListTypeVersions": {}, + "cloudformation:ListTypes": {}, + "cloudformation:PublishType": {}, + "cloudformation:RegisterPublisher": {}, + "cloudformation:RegisterType": {}, + "cloudformation:SetTypeConfiguration": {}, + "cloudformation:SetTypeDefaultVersion": {}, + "cloudformation:StartResourceScan": {}, + "cloudformation:TestType": {}, + "cloudformation:UpdateGeneratedTemplate": {}, + "cloudformation:UpdateResource": {}, + "cloudformation:ValidateTemplate": {}, + "cloudfront:CreateFieldLevelEncryptionConfig": {}, + "cloudfront:CreateFieldLevelEncryptionProfile": {}, + "cloudfront:CreateKeyGroup": {}, + "cloudfront:CreateMonitoringSubscription": {}, + "cloudfront:CreateOriginAccessControl": {}, + "cloudfront:CreatePublicKey": {}, + "cloudfront:CreateSavingsPlan": {}, + "cloudfront:DeleteKeyGroup": {}, + "cloudfront:DeleteMonitoringSubscription": {}, + "cloudfront:DeletePublicKey": {}, + "cloudfront:GetKeyGroup": {}, + "cloudfront:GetKeyGroupConfig": {}, + "cloudfront:GetMonitoringSubscription": {}, + "cloudfront:GetPublicKey": {}, + "cloudfront:GetPublicKeyConfig": {}, + "cloudfront:GetSavingsPlan": {}, + "cloudfront:ListCachePolicies": {}, + "cloudfront:ListCloudFrontOriginAccessIdentities": {}, + "cloudfront:ListContinuousDeploymentPolicies": {}, + "cloudfront:ListDistributions": {}, + "cloudfront:ListDistributionsByCachePolicyId": {}, + "cloudfront:ListDistributionsByKeyGroup": {}, + "cloudfront:ListDistributionsByLambdaFunction": {}, + "cloudfront:ListDistributionsByOriginRequestPolicyId": {}, + "cloudfront:ListDistributionsByRealtimeLogConfig": {}, + "cloudfront:ListDistributionsByResponseHeadersPolicyId": {}, + "cloudfront:ListDistributionsByWebACLId": {}, + "cloudfront:ListFieldLevelEncryptionConfigs": {}, + "cloudfront:ListFieldLevelEncryptionProfiles": {}, + "cloudfront:ListFunctions": {}, + "cloudfront:ListKeyGroups": {}, + "cloudfront:ListKeyValueStores": {}, + "cloudfront:ListOriginAccessControls": {}, + "cloudfront:ListOriginRequestPolicies": {}, + "cloudfront:ListPublicKeys": {}, + "cloudfront:ListRateCards": {}, + "cloudfront:ListRealtimeLogConfigs": {}, + "cloudfront:ListResponseHeadersPolicies": {}, + "cloudfront:ListSavingsPlans": {}, + "cloudfront:ListStreamingDistributions": {}, + "cloudfront:ListUsages": {}, + "cloudfront:UpdateFieldLevelEncryptionConfig": {}, + "cloudfront:UpdateKeyGroup": {}, + "cloudfront:UpdatePublicKey": {}, + "cloudfront:UpdateSavingsPlan": {}, + "cloudhsm:AddTagsToResource": {}, + "cloudhsm:CreateHapg": {}, + "cloudhsm:CreateLunaClient": {}, + "cloudhsm:DeleteHapg": {}, + "cloudhsm:DeleteHsm": {}, + "cloudhsm:DeleteLunaClient": {}, + "cloudhsm:DescribeBackups": {}, + "cloudhsm:DescribeClusters": {}, + "cloudhsm:DescribeHapg": {}, + "cloudhsm:DescribeHsm": {}, + "cloudhsm:DescribeLunaClient": {}, + "cloudhsm:GetConfig": {}, + "cloudhsm:ListAvailableZones": {}, + "cloudhsm:ListHapgs": {}, + "cloudhsm:ListHsms": {}, + "cloudhsm:ListLunaClients": {}, + "cloudhsm:ListTagsForResource": {}, + "cloudhsm:ModifyHapg": {}, + "cloudhsm:ModifyHsm": {}, + "cloudhsm:ModifyLunaClient": {}, + "cloudhsm:RemoveTagsFromResource": {}, + "cloudshell:CreateEnvironment": {}, + "cloudtrail:DeregisterOrganizationDelegatedAdmin": {}, + "cloudtrail:DescribeTrails": {}, + "cloudtrail:GetImport": {}, + "cloudtrail:ListChannels": {}, + "cloudtrail:ListEventDataStores": {}, + "cloudtrail:ListImportFailures": {}, + "cloudtrail:ListImports": {}, + "cloudtrail:ListPublicKeys": {}, + "cloudtrail:ListServiceLinkedChannels": {}, + "cloudtrail:ListTrails": {}, + "cloudtrail:LookupEvents": {}, + "cloudtrail:RegisterOrganizationDelegatedAdmin": {}, + "cloudtrail:StartImport": {}, + "cloudtrail:StopImport": {}, + "cloudwatch:BatchGetServiceLevelIndicatorReport": {}, + "cloudwatch:CreateServiceLevelObjective": {}, + "cloudwatch:DeleteAnomalyDetector": {}, + "cloudwatch:DescribeAlarmsForMetric": {}, + "cloudwatch:DescribeAnomalyDetectors": {}, + "cloudwatch:DescribeInsightRules": {}, + "cloudwatch:EnableTopologyDiscovery": {}, + "cloudwatch:GenerateQuery": {}, + "cloudwatch:GetMetricData": {}, + "cloudwatch:GetMetricStatistics": {}, + "cloudwatch:GetMetricWidgetImage": {}, + "cloudwatch:GetTopologyDiscoveryStatus": {}, + "cloudwatch:GetTopologyMap": {}, + "cloudwatch:Link": {}, + "cloudwatch:ListDashboards": {}, + "cloudwatch:ListManagedInsightRules": {}, + "cloudwatch:ListMetricStreams": {}, + "cloudwatch:ListMetrics": {}, + "cloudwatch:ListServiceLevelObjectives": {}, + "cloudwatch:ListServices": {}, + "cloudwatch:PutAnomalyDetector": {}, + "cloudwatch:PutManagedInsightRules": {}, + "cloudwatch:PutMetricData": {}, + "codeartifact:CreateDomain": {}, + "codeartifact:CreateRepository": {}, + "codeartifact:ListDomains": {}, + "codeartifact:ListRepositories": {}, + "codebuild:DeleteOAuthToken": {}, + "codebuild:DeleteSourceCredentials": {}, + "codebuild:ImportSourceCredentials": {}, + "codebuild:ListBuildBatches": {}, + "codebuild:ListBuilds": {}, + "codebuild:ListConnectedOAuthAccounts": {}, + "codebuild:ListCuratedEnvironmentImages": {}, + "codebuild:ListFleets": {}, + "codebuild:ListProjects": {}, + "codebuild:ListReportGroups": {}, + "codebuild:ListReports": {}, + "codebuild:ListRepositories": {}, + "codebuild:ListSharedProjects": {}, + "codebuild:ListSharedReportGroups": {}, + "codebuild:ListSourceCredentials": {}, + "codebuild:PersistOAuthToken": {}, + "codecatalyst:AcceptConnection": {}, + "codecatalyst:CreateIdentityCenterApplication": {}, + "codecatalyst:CreateSpace": {}, + "codecatalyst:GetPendingConnection": {}, + "codecatalyst:ListConnections": {}, + "codecatalyst:ListIdentityCenterApplications": {}, + "codecatalyst:ListIdentityCenterApplicationsForSpace": {}, + "codecatalyst:RejectConnection": {}, + "codecommit:CreateApprovalRuleTemplate": {}, + "codecommit:DeleteApprovalRuleTemplate": {}, + "codecommit:GetApprovalRuleTemplate": {}, + "codecommit:ListApprovalRuleTemplates": {}, + "codecommit:ListRepositories": {}, + "codecommit:ListRepositoriesForApprovalRuleTemplate": {}, + "codecommit:UpdateApprovalRuleTemplateContent": {}, + "codecommit:UpdateApprovalRuleTemplateDescription": {}, + "codecommit:UpdateApprovalRuleTemplateName": {}, + "codedeploy-commands-secure:GetDeploymentSpecification": {}, + "codedeploy-commands-secure:PollHostCommand": {}, + "codedeploy-commands-secure:PutHostCommandAcknowledgement": {}, + "codedeploy-commands-secure:PutHostCommandComplete": {}, + "codedeploy:BatchGetDeploymentTargets": {}, + "codedeploy:ContinueDeployment": {}, + "codedeploy:DeleteGitHubAccountToken": {}, + "codedeploy:DeleteResourcesByExternalId": {}, + "codedeploy:GetDeploymentTarget": {}, + "codedeploy:ListApplications": {}, + "codedeploy:ListDeploymentConfigs": {}, + "codedeploy:ListDeploymentTargets": {}, + "codedeploy:ListGitHubAccountTokenNames": {}, + "codedeploy:ListOnPremisesInstances": {}, + "codedeploy:PutLifecycleEventHookExecutionStatus": {}, + "codedeploy:SkipWaitTimeForInstanceTermination": {}, + "codedeploy:StopDeployment": {}, + "codeguru-profiler:CreateProfilingGroup": {}, + "codeguru-profiler:GetFindingsReportAccountSummary": {}, + "codeguru-profiler:ListProfilingGroups": {}, + "codeguru-reviewer:AssociateRepository": {}, + "codeguru-reviewer:CreateConnectionToken": {}, + "codeguru-reviewer:GetMetricsData": {}, + "codeguru-reviewer:ListCodeReviews": {}, + "codeguru-reviewer:ListRepositoryAssociations": {}, + "codeguru-reviewer:ListThirdPartyRepositories": {}, + "codeguru-security:DeleteScansByCategory": {}, + "codeguru-security:GetAccountConfiguration": {}, + "codeguru-security:GetMetricsSummary": {}, + "codeguru-security:ListFindings": {}, + "codeguru-security:ListFindingsMetrics": {}, + "codeguru-security:ListScans": {}, + "codeguru-security:UpdateAccountConfiguration": {}, + "codeguru:GetCodeGuruFreeTrialSummary": {}, + "codepipeline:AcknowledgeJob": {}, + "codepipeline:AcknowledgeThirdPartyJob": {}, + "codepipeline:GetActionType": {}, + "codepipeline:GetJobDetails": {}, + "codepipeline:GetThirdPartyJobDetails": {}, + "codepipeline:ListActionTypes": {}, + "codepipeline:ListPipelines": {}, + "codepipeline:PollForThirdPartyJobs": {}, + "codepipeline:PutJobFailureResult": {}, + "codepipeline:PutJobSuccessResult": {}, + "codepipeline:PutThirdPartyJobFailureResult": {}, + "codepipeline:PutThirdPartyJobSuccessResult": {}, + "codestar-connections:CreateConnection": {}, + "codestar-connections:CreateHost": {}, + "codestar-connections:DeleteSyncConfiguration": {}, + "codestar-connections:GetIndividualAccessToken": {}, + "codestar-connections:GetInstallationUrl": {}, + "codestar-connections:GetResourceSyncStatus": {}, + "codestar-connections:GetSyncBlockerSummary": {}, + "codestar-connections:GetSyncConfiguration": {}, + "codestar-connections:ListHosts": {}, + "codestar-connections:ListInstallationTargets": {}, + "codestar-connections:ListRepositoryLinks": {}, + "codestar-connections:ListRepositorySyncDefinitions": {}, + "codestar-connections:ListSyncConfigurations": {}, + "codestar-connections:RegisterAppCode": {}, + "codestar-connections:StartAppRegistrationHandshake": {}, + "codestar-connections:StartOAuthHandshake": {}, + "codestar-connections:UpdateSyncBlocker": {}, + "codestar-connections:UpdateSyncConfiguration": {}, + "codestar-notifications:DeleteTarget": {}, + "codestar-notifications:ListEventTypes": {}, + "codestar-notifications:ListNotificationRules": {}, + "codestar-notifications:ListTargets": {}, + "codestar:CreateProject": {}, + "codestar:DescribeUserProfile": {}, + "codestar:ListProjects": {}, + "codestar:ListUserProfiles": {}, + "codewhisperer:GenerateRecommendations": {}, + "codewhisperer:ListProfiles": {}, + "cognito-identity:CreateIdentityPool": {}, + "cognito-identity:DeleteIdentities": {}, + "cognito-identity:DescribeIdentity": {}, + "cognito-identity:GetCredentialsForIdentity": {}, + "cognito-identity:GetId": {}, + "cognito-identity:GetOpenIdToken": {}, + "cognito-identity:ListIdentityPools": {}, + "cognito-identity:SetIdentityPoolRoles": {}, + "cognito-identity:SetPrincipalTagAttributeMap": {}, + "cognito-identity:UnlinkIdentity": {}, + "cognito-idp:AssociateSoftwareToken": {}, + "cognito-idp:ChangePassword": {}, + "cognito-idp:ConfirmDevice": {}, + "cognito-idp:ConfirmForgotPassword": {}, + "cognito-idp:ConfirmSignUp": {}, + "cognito-idp:CreateUserPool": {}, + "cognito-idp:DeleteUser": {}, + "cognito-idp:DeleteUserAttributes": {}, + "cognito-idp:DescribeUserPoolDomain": {}, + "cognito-idp:ForgetDevice": {}, + "cognito-idp:ForgotPassword": {}, + "cognito-idp:GetDevice": {}, + "cognito-idp:GetUser": {}, + "cognito-idp:GetUserAttributeVerificationCode": {}, + "cognito-idp:GlobalSignOut": {}, + "cognito-idp:InitiateAuth": {}, + "cognito-idp:ListDevices": {}, + "cognito-idp:ListUserPools": {}, + "cognito-idp:ResendConfirmationCode": {}, + "cognito-idp:RespondToAuthChallenge": {}, + "cognito-idp:RevokeToken": {}, + "cognito-idp:SetUserMFAPreference": {}, + "cognito-idp:SetUserSettings": {}, + "cognito-idp:SignUp": {}, + "cognito-idp:UpdateDeviceStatus": {}, + "cognito-idp:UpdateUserAttributes": {}, + "cognito-idp:VerifySoftwareToken": {}, + "cognito-idp:VerifyUserAttribute": {}, + "comprehend:BatchDetectDominantLanguage": {}, + "comprehend:BatchDetectEntities": {}, + "comprehend:BatchDetectKeyPhrases": {}, + "comprehend:BatchDetectSentiment": {}, + "comprehend:BatchDetectSyntax": {}, + "comprehend:BatchDetectTargetedSentiment": {}, + "comprehend:ContainsPiiEntities": {}, + "comprehend:DetectDominantLanguage": {}, + "comprehend:DetectKeyPhrases": {}, + "comprehend:DetectPiiEntities": {}, + "comprehend:DetectSentiment": {}, + "comprehend:DetectSyntax": {}, + "comprehend:DetectTargetedSentiment": {}, + "comprehend:DetectToxicContent": {}, + "comprehend:ListDocumentClassificationJobs": {}, + "comprehend:ListDocumentClassifierSummaries": {}, + "comprehend:ListDocumentClassifiers": {}, + "comprehend:ListDominantLanguageDetectionJobs": {}, + "comprehend:ListEndpoints": {}, + "comprehend:ListEntitiesDetectionJobs": {}, + "comprehend:ListEntityRecognizerSummaries": {}, + "comprehend:ListEntityRecognizers": {}, + "comprehend:ListEventsDetectionJobs": {}, + "comprehend:ListFlywheels": {}, + "comprehend:ListKeyPhrasesDetectionJobs": {}, + "comprehend:ListPiiEntitiesDetectionJobs": {}, + "comprehend:ListSentimentDetectionJobs": {}, + "comprehend:ListTargetedSentimentDetectionJobs": {}, + "comprehend:ListTopicsDetectionJobs": {}, + "comprehendmedical:DescribeEntitiesDetectionV2Job": {}, + "comprehendmedical:DescribeICD10CMInferenceJob": {}, + "comprehendmedical:DescribePHIDetectionJob": {}, + "comprehendmedical:DescribeRxNormInferenceJob": {}, + "comprehendmedical:DescribeSNOMEDCTInferenceJob": {}, + "comprehendmedical:DetectEntitiesV2": {}, + "comprehendmedical:DetectPHI": {}, + "comprehendmedical:InferICD10CM": {}, + "comprehendmedical:InferRxNorm": {}, + "comprehendmedical:InferSNOMEDCT": {}, + "comprehendmedical:ListEntitiesDetectionV2Jobs": {}, + "comprehendmedical:ListICD10CMInferenceJobs": {}, + "comprehendmedical:ListPHIDetectionJobs": {}, + "comprehendmedical:ListRxNormInferenceJobs": {}, + "comprehendmedical:ListSNOMEDCTInferenceJobs": {}, + "comprehendmedical:StartEntitiesDetectionV2Job": {}, + "comprehendmedical:StartICD10CMInferenceJob": {}, + "comprehendmedical:StartPHIDetectionJob": {}, + "comprehendmedical:StartRxNormInferenceJob": {}, + "comprehendmedical:StartSNOMEDCTInferenceJob": {}, + "comprehendmedical:StopEntitiesDetectionV2Job": {}, + "comprehendmedical:StopICD10CMInferenceJob": {}, + "comprehendmedical:StopPHIDetectionJob": {}, + "comprehendmedical:StopRxNormInferenceJob": {}, + "comprehendmedical:StopSNOMEDCTInferenceJob": {}, + "compute-optimizer:DeleteRecommendationPreferences": {}, + "compute-optimizer:DescribeRecommendationExportJobs": {}, + "compute-optimizer:ExportAutoScalingGroupRecommendations": {}, + "compute-optimizer:ExportEBSVolumeRecommendations": {}, + "compute-optimizer:ExportEC2InstanceRecommendations": {}, + "compute-optimizer:ExportECSServiceRecommendations": {}, + "compute-optimizer:ExportLambdaFunctionRecommendations": {}, + "compute-optimizer:ExportLicenseRecommendations": {}, + "compute-optimizer:GetAutoScalingGroupRecommendations": {}, + "compute-optimizer:GetEBSVolumeRecommendations": {}, + "compute-optimizer:GetEC2InstanceRecommendations": {}, + "compute-optimizer:GetEC2RecommendationProjectedMetrics": {}, + "compute-optimizer:GetECSServiceRecommendationProjectedMetrics": {}, + "compute-optimizer:GetECSServiceRecommendations": {}, + "compute-optimizer:GetEffectiveRecommendationPreferences": {}, + "compute-optimizer:GetEnrollmentStatus": {}, + "compute-optimizer:GetEnrollmentStatusesForOrganization": {}, + "compute-optimizer:GetLambdaFunctionRecommendations": {}, + "compute-optimizer:GetLicenseRecommendations": {}, + "compute-optimizer:GetRecommendationPreferences": {}, + "compute-optimizer:GetRecommendationSummaries": {}, + "compute-optimizer:PutRecommendationPreferences": {}, + "compute-optimizer:UpdateEnrollmentStatus": {}, + "config:BatchGetResourceConfig": {}, + "config:DeleteConfigurationRecorder": {}, + "config:DeleteDeliveryChannel": {}, + "config:DeletePendingAggregationRequest": {}, + "config:DeleteRemediationExceptions": {}, + "config:DeleteResourceConfig": {}, + "config:DeleteRetentionConfiguration": {}, + "config:DeliverConfigSnapshot": {}, + "config:DescribeAggregationAuthorizations": {}, + "config:DescribeComplianceByConfigRule": {}, + "config:DescribeComplianceByResource": {}, + "config:DescribeConfigRuleEvaluationStatus": {}, + "config:DescribeConfigRules": {}, + "config:DescribeConfigurationAggregators": {}, + "config:DescribeConfigurationRecorderStatus": {}, + "config:DescribeConfigurationRecorders": {}, + "config:DescribeConformancePackStatus": {}, + "config:DescribeConformancePacks": {}, + "config:DescribeDeliveryChannelStatus": {}, + "config:DescribeDeliveryChannels": {}, + "config:DescribeOrganizationConfigRuleStatuses": {}, + "config:DescribeOrganizationConfigRules": {}, + "config:DescribeOrganizationConformancePackStatuses": {}, + "config:DescribeOrganizationConformancePacks": {}, + "config:DescribePendingAggregationRequests": {}, + "config:DescribeRemediationExceptions": {}, + "config:DescribeRetentionConfigurations": {}, + "config:GetComplianceDetailsByResource": {}, + "config:GetComplianceSummaryByConfigRule": {}, + "config:GetComplianceSummaryByResourceType": {}, + "config:GetDiscoveredResourceCounts": {}, + "config:GetResourceConfigHistory": {}, + "config:GetResourceEvaluationSummary": {}, + "config:ListConformancePackComplianceScores": {}, + "config:ListDiscoveredResources": {}, + "config:ListResourceEvaluations": {}, + "config:ListStoredQueries": {}, + "config:PutConfigurationRecorder": {}, + "config:PutDeliveryChannel": {}, + "config:PutEvaluations": {}, + "config:PutRemediationExceptions": {}, + "config:PutResourceConfig": {}, + "config:PutRetentionConfiguration": {}, + "config:SelectResourceConfig": {}, + "config:StartConfigurationRecorder": {}, + "config:StartRemediationExecution": {}, + "config:StartResourceEvaluation": {}, + "config:StopConfigurationRecorder": {}, + "connect-campaigns:DeleteConnectInstanceConfig": {}, + "connect-campaigns:DeleteInstanceOnboardingJob": {}, + "connect-campaigns:GetConnectInstanceConfig": {}, + "connect-campaigns:GetInstanceOnboardingJobStatus": {}, + "connect-campaigns:ListCampaigns": {}, + "connect-campaigns:StartInstanceOnboardingJob": {}, + "connect:CreateInstance": {}, + "connect:ListInstances": {}, + "connect:SendChatIntegrationEvent": {}, + "consoleapp:ListDeviceIdentities": {}, + "consolidatedbilling:GetAccountBillingRole": {}, + "consolidatedbilling:ListLinkedAccounts": {}, + "controltower:CreateLandingZone": {}, + "controltower:CreateManagedAccount": {}, + "controltower:DeregisterManagedAccount": {}, + "controltower:DeregisterOrganizationalUnit": {}, + "controltower:DescribeAccountFactoryConfig": {}, + "controltower:DescribeCoreService": {}, + "controltower:DescribeGuardrail": {}, + "controltower:DescribeGuardrailForTarget": {}, + "controltower:DescribeLandingZoneConfiguration": {}, + "controltower:DescribeManagedAccount": {}, + "controltower:DescribeManagedOrganizationalUnit": {}, + "controltower:DescribeRegisterOrganizationalUnitOperation": {}, + "controltower:DescribeSingleSignOn": {}, + "controltower:DisableGuardrail": {}, + "controltower:EnableGuardrail": {}, + "controltower:GetAccountInfo": {}, + "controltower:GetAvailableUpdates": {}, + "controltower:GetControlOperation": {}, + "controltower:GetGuardrailComplianceStatus": {}, + "controltower:GetHomeRegion": {}, + "controltower:GetLandingZoneDriftStatus": {}, + "controltower:GetLandingZoneOperation": {}, + "controltower:GetLandingZoneStatus": {}, + "controltower:ListDirectoryGroups": {}, + "controltower:ListDriftDetails": {}, + "controltower:ListEnabledControls": {}, + "controltower:ListEnabledGuardrails": {}, + "controltower:ListExtendGovernancePrecheckDetails": {}, + "controltower:ListExternalConfigRuleCompliance": {}, + "controltower:ListGuardrailViolations": {}, + "controltower:ListGuardrails": {}, + "controltower:ListGuardrailsForTarget": {}, + "controltower:ListLandingZones": {}, + "controltower:ListManagedAccounts": {}, + "controltower:ListManagedAccountsForGuardrail": {}, + "controltower:ListManagedAccountsForParent": {}, + "controltower:ListManagedOrganizationalUnits": {}, + "controltower:ListManagedOrganizationalUnitsForGuardrail": {}, + "controltower:ManageOrganizationalUnit": {}, + "controltower:PerformPreLaunchChecks": {}, + "controltower:SetupLandingZone": {}, + "controltower:UpdateAccountFactoryConfig": {}, + "cost-optimization-hub:GetPreferences": {}, + "cost-optimization-hub:GetRecommendation": {}, + "cost-optimization-hub:ListEnrollmentStatuses": {}, + "cost-optimization-hub:ListRecommendationSummaries": {}, + "cost-optimization-hub:ListRecommendations": {}, + "cost-optimization-hub:UpdateEnrollmentStatus": {}, + "cost-optimization-hub:UpdatePreferences": {}, + "cur:DescribeReportDefinitions": {}, + "cur:GetClassicReport": {}, + "cur:GetClassicReportPreferences": {}, + "cur:GetUsageReport": {}, + "cur:PutClassicReportPreferences": {}, + "cur:ValidateReportDestination": {}, + "customer-verification:CreateCustomerVerificationDetails": {}, + "customer-verification:GetCustomerVerificationDetails": {}, + "customer-verification:GetCustomerVerificationEligibility": {}, + "customer-verification:UpdateCustomerVerificationDetails": {}, + "databrew:CreateDataset": {}, + "databrew:CreateProfileJob": {}, + "databrew:CreateProject": {}, + "databrew:CreateRecipe": {}, + "databrew:CreateRecipeJob": {}, + "databrew:CreateRuleset": {}, + "databrew:CreateSchedule": {}, + "databrew:ListDatasets": {}, + "databrew:ListJobs": {}, + "databrew:ListProjects": {}, + "databrew:ListRecipes": {}, + "databrew:ListRulesets": {}, + "databrew:ListSchedules": {}, + "dataexchange:CreateDataSet": {}, + "dataexchange:CreateEventAction": {}, + "dataexchange:CreateJob": {}, + "dataexchange:ListDataSets": {}, + "dataexchange:ListEventActions": {}, + "dataexchange:ListJobs": {}, + "datapipeline:CreatePipeline": {}, + "datapipeline:GetAccountLimits": {}, + "datapipeline:ListPipelines": {}, + "datapipeline:PollForTask": {}, + "datapipeline:PutAccountLimits": {}, + "datapipeline:ReportTaskRunnerHeartbeat": {}, + "datasync:CreateAgent": {}, + "datasync:CreateLocationAzureBlob": {}, + "datasync:CreateLocationEfs": {}, + "datasync:CreateLocationFsxLustre": {}, + "datasync:CreateLocationFsxOntap": {}, + "datasync:CreateLocationFsxOpenZfs": {}, + "datasync:CreateLocationFsxWindows": {}, + "datasync:CreateLocationHdfs": {}, + "datasync:CreateLocationNfs": {}, + "datasync:CreateLocationObjectStorage": {}, + "datasync:CreateLocationS3": {}, + "datasync:CreateLocationSmb": {}, + "datasync:ListAgents": {}, + "datasync:ListDiscoveryJobs": {}, + "datasync:ListLocations": {}, + "datasync:ListStorageSystems": {}, + "datasync:ListTaskExecutions": {}, + "datasync:ListTasks": {}, + "datazone:AcceptPredictions": {}, + "datazone:AcceptSubscriptionRequest": {}, + "datazone:CancelSubscription": {}, + "datazone:CreateAsset": {}, + "datazone:CreateAssetRevision": {}, + "datazone:CreateAssetType": {}, + "datazone:CreateDataSource": {}, + "datazone:CreateDomain": {}, + "datazone:CreateEnvironment": {}, + "datazone:CreateEnvironmentBlueprint": {}, + "datazone:CreateEnvironmentProfile": {}, + "datazone:CreateFormType": {}, + "datazone:CreateGlossary": {}, + "datazone:CreateGlossaryTerm": {}, + "datazone:CreateGroupProfile": {}, + "datazone:CreateListingChangeSet": {}, + "datazone:CreateProject": {}, + "datazone:CreateProjectMembership": {}, + "datazone:CreateSubscriptionGrant": {}, + "datazone:CreateSubscriptionRequest": {}, + "datazone:CreateSubscriptionTarget": {}, + "datazone:CreateUserProfile": {}, + "datazone:DeleteAsset": {}, + "datazone:DeleteAssetType": {}, + "datazone:DeleteDataSource": {}, + "datazone:DeleteDomainSharingPolicy": {}, + "datazone:DeleteEnvironment": {}, + "datazone:DeleteEnvironmentBlueprint": {}, + "datazone:DeleteEnvironmentBlueprintConfiguration": {}, + "datazone:DeleteEnvironmentProfile": {}, + "datazone:DeleteFormType": {}, + "datazone:DeleteGlossary": {}, + "datazone:DeleteGlossaryTerm": {}, + "datazone:DeleteListing": {}, + "datazone:DeleteProject": {}, + "datazone:DeleteProjectMembership": {}, + "datazone:DeleteSubscriptionGrant": {}, + "datazone:DeleteSubscriptionRequest": {}, + "datazone:DeleteSubscriptionTarget": {}, + "datazone:GetAsset": {}, + "datazone:GetAssetType": {}, + "datazone:GetDataSource": {}, + "datazone:GetDataSourceRun": {}, + "datazone:GetDomainSharingPolicy": {}, + "datazone:GetEnvironment": {}, + "datazone:GetEnvironmentActionLink": {}, + "datazone:GetEnvironmentBlueprint": {}, + "datazone:GetEnvironmentBlueprintConfiguration": {}, + "datazone:GetEnvironmentCredentials": {}, + "datazone:GetEnvironmentProfile": {}, + "datazone:GetFormType": {}, + "datazone:GetGlossary": {}, + "datazone:GetGlossaryTerm": {}, + "datazone:GetGroupProfile": {}, + "datazone:GetIamPortalLoginUrl": {}, + "datazone:GetListing": {}, + "datazone:GetMetadataGenerationRun": {}, + "datazone:GetProject": {}, + "datazone:GetSubscription": {}, + "datazone:GetSubscriptionEligibility": {}, + "datazone:GetSubscriptionGrant": {}, + "datazone:GetSubscriptionRequestDetails": {}, + "datazone:GetSubscriptionTarget": {}, + "datazone:GetUserProfile": {}, + "datazone:ListAccountEnvironments": {}, + "datazone:ListAssetRevisions": {}, + "datazone:ListDataSourceRunActivities": {}, + "datazone:ListDataSourceRuns": {}, + "datazone:ListDataSources": {}, + "datazone:ListDomains": {}, + "datazone:ListEnvironmentBlueprintConfigurationSummaries": {}, + "datazone:ListEnvironmentBlueprintConfigurations": {}, + "datazone:ListEnvironmentBlueprints": {}, + "datazone:ListEnvironmentProfiles": {}, + "datazone:ListEnvironments": {}, + "datazone:ListGroupsForUser": {}, + "datazone:ListMetadataGenerationRuns": {}, + "datazone:ListNotifications": {}, + "datazone:ListProjectMemberships": {}, + "datazone:ListProjects": {}, + "datazone:ListSubscriptionGrants": {}, + "datazone:ListSubscriptionRequests": {}, + "datazone:ListSubscriptionTargets": {}, + "datazone:ListSubscriptions": {}, + "datazone:ListWarehouseMetadata": {}, + "datazone:ProvisionDomain": {}, + "datazone:PutDomainSharingPolicy": {}, + "datazone:PutEnvironmentBlueprintConfiguration": {}, + "datazone:RefreshToken": {}, + "datazone:RejectPredictions": {}, + "datazone:RejectSubscriptionRequest": {}, + "datazone:RevokeSubscription": {}, + "datazone:Search": {}, + "datazone:SearchGroupProfiles": {}, + "datazone:SearchListings": {}, + "datazone:SearchTypes": {}, + "datazone:SearchUserProfiles": {}, + "datazone:SsoLogin": {}, + "datazone:SsoLogout": {}, + "datazone:StartDataSourceRun": {}, + "datazone:StartMetadataGenerationRun": {}, + "datazone:StopMetadataGenerationRun": {}, + "datazone:UpdateDataSource": {}, + "datazone:UpdateEnvironment": {}, + "datazone:UpdateEnvironmentBlueprint": {}, + "datazone:UpdateEnvironmentConfiguration": {}, + "datazone:UpdateEnvironmentDeploymentStatus": {}, + "datazone:UpdateEnvironmentProfile": {}, + "datazone:UpdateGlossary": {}, + "datazone:UpdateGlossaryTerm": {}, + "datazone:UpdateGroupProfile": {}, + "datazone:UpdateProject": {}, + "datazone:UpdateSubscriptionGrantStatus": {}, + "datazone:UpdateSubscriptionRequest": {}, + "datazone:UpdateSubscriptionTarget": {}, + "datazone:UpdateUserProfile": {}, + "datazone:ValidatePassRole": {}, + "dax:CreateParameterGroup": {}, + "dax:CreateSubnetGroup": {}, + "dax:DeleteParameterGroup": {}, + "dax:DeleteSubnetGroup": {}, + "dax:DescribeDefaultParameters": {}, + "dax:DescribeEvents": {}, + "dax:DescribeParameterGroups": {}, + "dax:DescribeParameters": {}, + "dax:DescribeSubnetGroups": {}, + "dax:UpdateParameterGroup": {}, + "dax:UpdateSubnetGroup": {}, + "dbqms:CreateFavoriteQuery": {}, + "dbqms:CreateTab": {}, + "dbqms:DeleteFavoriteQueries": {}, + "dbqms:DeleteQueryHistory": {}, + "dbqms:DeleteTab": {}, + "dbqms:DescribeFavoriteQueries": {}, + "dbqms:DescribeQueryHistory": {}, + "dbqms:DescribeTabs": {}, + "dbqms:GetQueryString": {}, + "dbqms:UpdateFavoriteQuery": {}, + "dbqms:UpdateQueryHistory": {}, + "dbqms:UpdateTab": {}, + "deepcomposer:AssociateCoupon": {}, + "deepracer:AdminGetAccountConfig": {}, + "deepracer:AdminListAssociatedResources": {}, + "deepracer:AdminListAssociatedUsers": {}, + "deepracer:AdminManageUser": {}, + "deepracer:AdminSetAccountConfig": {}, + "deepracer:CreateCar": {}, + "deepracer:CreateLeaderboard": {}, + "deepracer:GetAccountConfig": {}, + "deepracer:GetAlias": {}, + "deepracer:GetCars": {}, + "deepracer:ImportModel": {}, + "deepracer:ListLeaderboards": {}, + "deepracer:ListModels": {}, + "deepracer:ListPrivateLeaderboards": {}, + "deepracer:ListSubscribedPrivateLeaderboards": {}, + "deepracer:ListTracks": {}, + "deepracer:MigrateModels": {}, + "deepracer:SetAlias": {}, + "deepracer:TestRewardFunction": {}, + "detective:AcceptInvitation": {}, + "detective:BatchGetMembershipDatasources": {}, + "detective:CreateGraph": {}, + "detective:DisableOrganizationAdminAccount": {}, + "detective:DisassociateMembership": {}, + "detective:EnableOrganizationAdminAccount": {}, + "detective:GetPricingInformation": {}, + "detective:ListGraphs": {}, + "detective:ListInvitations": {}, + "detective:ListOrganizationAdminAccount": {}, + "detective:RejectInvitation": {}, + "devicefarm:CreateInstanceProfile": {}, + "devicefarm:CreateProject": {}, + "devicefarm:CreateTestGridProject": {}, + "devicefarm:CreateVPCEConfiguration": {}, + "devicefarm:GetAccountSettings": {}, + "devicefarm:GetOfferingStatus": {}, + "devicefarm:ListDeviceInstances": {}, + "devicefarm:ListDevices": {}, + "devicefarm:ListInstanceProfiles": {}, + "devicefarm:ListOfferingPromotions": {}, + "devicefarm:ListOfferingTransactions": {}, + "devicefarm:ListOfferings": {}, + "devicefarm:ListProjects": {}, + "devicefarm:ListTestGridProjects": {}, + "devicefarm:ListVPCEConfigurations": {}, + "devicefarm:PurchaseOffering": {}, + "devicefarm:RenewOffering": {}, + "devops-guru:DeleteInsight": {}, + "devops-guru:DescribeAccountHealth": {}, + "devops-guru:DescribeAccountOverview": {}, + "devops-guru:DescribeAnomaly": {}, + "devops-guru:DescribeEventSourcesConfig": {}, + "devops-guru:DescribeFeedback": {}, + "devops-guru:DescribeInsight": {}, + "devops-guru:DescribeOrganizationHealth": {}, + "devops-guru:DescribeOrganizationOverview": {}, + "devops-guru:DescribeOrganizationResourceCollectionHealth": {}, + "devops-guru:DescribeResourceCollectionHealth": {}, + "devops-guru:DescribeServiceIntegration": {}, + "devops-guru:GetCostEstimation": {}, + "devops-guru:GetResourceCollection": {}, + "devops-guru:ListAnomaliesForInsight": {}, + "devops-guru:ListAnomalousLogGroups": {}, + "devops-guru:ListEvents": {}, + "devops-guru:ListInsights": {}, + "devops-guru:ListMonitoredResources": {}, + "devops-guru:ListNotificationChannels": {}, + "devops-guru:ListOrganizationInsights": {}, + "devops-guru:ListRecommendations": {}, + "devops-guru:PutFeedback": {}, + "devops-guru:SearchInsights": {}, + "devops-guru:SearchOrganizationInsights": {}, + "devops-guru:StartCostEstimation": {}, + "devops-guru:UpdateEventSourcesConfig": {}, + "devops-guru:UpdateResourceCollection": {}, + "devops-guru:UpdateServiceIntegration": {}, + "directconnect:ConfirmCustomerAgreement": {}, + "directconnect:CreateDirectConnectGateway": {}, + "directconnect:DeleteDirectConnectGatewayAssociationProposal": {}, + "directconnect:DescribeCustomerMetadata": {}, + "directconnect:DescribeLocations": {}, + "directconnect:DescribeVirtualGateways": {}, + "directconnect:UpdateDirectConnectGatewayAssociation": {}, + "discovery:AssociateConfigurationItemsToApplication": {}, + "discovery:BatchDeleteAgents": {}, + "discovery:BatchDeleteImportData": {}, + "discovery:CreateApplication": {}, + "discovery:CreateTags": {}, + "discovery:DeleteApplications": {}, + "discovery:DeleteTags": {}, + "discovery:DescribeAgents": {}, + "discovery:DescribeBatchDeleteConfigurationTask": {}, + "discovery:DescribeConfigurations": {}, + "discovery:DescribeContinuousExports": {}, + "discovery:DescribeExportConfigurations": {}, + "discovery:DescribeExportTasks": {}, + "discovery:DescribeImportTasks": {}, + "discovery:DescribeTags": {}, + "discovery:DisassociateConfigurationItemsFromApplication": {}, + "discovery:ExportConfigurations": {}, + "discovery:GetDiscoverySummary": {}, + "discovery:GetNetworkConnectionGraph": {}, + "discovery:ListConfigurations": {}, + "discovery:ListServerNeighbors": {}, + "discovery:StartBatchDeleteConfigurationTask": {}, + "discovery:StartContinuousExport": {}, + "discovery:StartDataCollectionByAgentIds": {}, + "discovery:StartExportTask": {}, + "discovery:StartImportTask": {}, + "discovery:StopContinuousExport": {}, + "discovery:StopDataCollectionByAgentIds": {}, + "discovery:UpdateApplication": {}, + "dlm:CreateLifecyclePolicy": {}, + "dlm:GetLifecyclePolicies": {}, + "dms:BatchStartRecommendations": {}, + "dms:CreateDataProvider": {}, + "dms:CreateEndpoint": {}, + "dms:CreateEventSubscription": {}, + "dms:CreateFleetAdvisorCollector": {}, + "dms:CreateInstanceProfile": {}, + "dms:CreateReplicationInstance": {}, + "dms:CreateReplicationSubnetGroup": {}, + "dms:DeleteFleetAdvisorCollector": {}, + "dms:DeleteFleetAdvisorDatabases": {}, + "dms:DescribeAccountAttributes": {}, + "dms:DescribeCertificates": {}, + "dms:DescribeConnections": {}, + "dms:DescribeDataMigrations": {}, + "dms:DescribeEndpointSettings": {}, + "dms:DescribeEndpointTypes": {}, + "dms:DescribeEndpoints": {}, + "dms:DescribeEngineVersions": {}, + "dms:DescribeEventCategories": {}, + "dms:DescribeEventSubscriptions": {}, + "dms:DescribeEvents": {}, + "dms:DescribeFleetAdvisorCollectors": {}, + "dms:DescribeFleetAdvisorDatabases": {}, + "dms:DescribeFleetAdvisorLsaAnalysis": {}, + "dms:DescribeFleetAdvisorSchemaObjectSummary": {}, + "dms:DescribeFleetAdvisorSchemas": {}, + "dms:DescribeOrderableReplicationInstances": {}, + "dms:DescribePendingMaintenanceActions": {}, + "dms:DescribeRecommendationLimitations": {}, + "dms:DescribeRecommendations": {}, + "dms:DescribeReplicationConfigs": {}, + "dms:DescribeReplicationInstances": {}, + "dms:DescribeReplicationSubnetGroups": {}, + "dms:DescribeReplicationTasks": {}, + "dms:DescribeReplications": {}, + "dms:ImportCertificate": {}, + "dms:ModifyEventSubscription": {}, + "dms:ModifyFleetAdvisorCollector": {}, + "dms:ModifyFleetAdvisorCollectorStatuses": {}, + "dms:ModifyReplicationSubnetGroup": {}, + "dms:RunFleetAdvisorLsaAnalysis": {}, + "dms:StartRecommendations": {}, + "dms:UpdateSubscriptionsToEventBridge": {}, + "dms:UploadFileMetadataList": {}, + "docdb-elastic:CreateCluster": {}, + "docdb-elastic:ListClusterSnapshots": {}, + "docdb-elastic:ListClusters": {}, + "drs:BatchDeleteSnapshotRequestForDrs": {}, + "drs:CreateExtendedSourceServer": {}, + "drs:CreateLaunchConfigurationTemplate": {}, + "drs:CreateReplicationConfigurationTemplate": {}, + "drs:CreateSourceNetwork": {}, + "drs:CreateSourceServerForDrs": {}, + "drs:DescribeJobs": {}, + "drs:DescribeLaunchConfigurationTemplates": {}, + "drs:DescribeRecoveryInstances": {}, + "drs:DescribeReplicationConfigurationTemplates": {}, + "drs:DescribeReplicationServerAssociationsForDrs": {}, + "drs:DescribeSnapshotRequestsForDrs": {}, + "drs:DescribeSourceNetworks": {}, + "drs:DescribeSourceServers": {}, + "drs:GetAgentInstallationAssetsForDrs": {}, + "drs:GetChannelCommandsForDrs": {}, + "drs:InitializeService": {}, + "drs:ListExtensibleSourceServers": {}, + "drs:ListStagingAccounts": {}, + "drs:ListTagsForResource": {}, + "drs:SendChannelCommandResultForDrs": {}, + "drs:SendClientLogsForDrs": {}, + "drs:SendClientMetricsForDrs": {}, + "ds:CheckAlias": {}, + "ds:ConnectDirectory": {}, + "ds:CreateDirectory": {}, + "ds:CreateIdentityPoolDirectory": {}, + "ds:CreateMicrosoftAD": {}, + "ds:DescribeDirectories": {}, + "ds:DescribeSnapshots": {}, + "ds:DescribeTrusts": {}, + "ds:GetDirectoryLimits": {}, + "ds:ListLogSubscriptions": {}, + "dynamodb:DescribeEndpoints": {}, + "dynamodb:DescribeLimits": {}, + "dynamodb:DescribeReservedCapacity": {}, + "dynamodb:DescribeReservedCapacityOfferings": {}, + "dynamodb:ListBackups": {}, + "dynamodb:ListContributorInsights": {}, + "dynamodb:ListExports": {}, + "dynamodb:ListGlobalTables": {}, + "dynamodb:ListImports": {}, + "dynamodb:ListStreams": {}, + "dynamodb:ListTables": {}, + "dynamodb:PurchaseReservedCapacityOfferings": {}, + "ec2:AcceptReservedInstancesExchangeQuote": {}, + "ec2:AdvertiseByoipCidr": {}, + "ec2:AssociateIpamByoasn": {}, + "ec2:AssociateTrunkInterface": {}, + "ec2:BundleInstance": {}, + "ec2:CancelBundleTask": {}, + "ec2:CancelConversionTask": {}, + "ec2:CancelReservedInstancesListing": {}, + "ec2:ConfirmProductInstance": {}, + "ec2:CreateDefaultSubnet": {}, + "ec2:CreateDefaultVpc": {}, + "ec2:CreateReservedInstancesListing": {}, + "ec2:CreateSpotDatafeedSubscription": {}, + "ec2:CreateSubnetCidrReservation": {}, + "ec2:DeleteQueuedReservedInstances": {}, + "ec2:DeleteSpotDatafeedSubscription": {}, + "ec2:DeleteSubnetCidrReservation": {}, + "ec2:DeprovisionByoipCidr": {}, + "ec2:DeregisterInstanceEventNotificationAttributes": {}, + "ec2:DescribeAccountAttributes": {}, + "ec2:DescribeAddressTransfers": {}, + "ec2:DescribeAddresses": {}, + "ec2:DescribeAggregateIdFormat": {}, + "ec2:DescribeAvailabilityZones": {}, + "ec2:DescribeAwsNetworkPerformanceMetricSubscriptions": {}, + "ec2:DescribeBundleTasks": {}, + "ec2:DescribeByoipCidrs": {}, + "ec2:DescribeCapacityBlockOfferings": {}, + "ec2:DescribeCapacityReservationFleets": {}, + "ec2:DescribeCapacityReservations": {}, + "ec2:DescribeCarrierGateways": {}, + "ec2:DescribeClassicLinkInstances": {}, + "ec2:DescribeCoipPools": {}, + "ec2:DescribeConversionTasks": {}, + "ec2:DescribeCustomerGateways": {}, + "ec2:DescribeDhcpOptions": {}, + "ec2:DescribeEgressOnlyInternetGateways": {}, + "ec2:DescribeElasticGpus": {}, + "ec2:DescribeExportImageTasks": {}, + "ec2:DescribeExportTasks": {}, + "ec2:DescribeFastLaunchImages": {}, + "ec2:DescribeFastSnapshotRestores": {}, + "ec2:DescribeFleets": {}, + "ec2:DescribeFlowLogs": {}, + "ec2:DescribeFpgaImages": {}, + "ec2:DescribeHostReservationOfferings": {}, + "ec2:DescribeHostReservations": {}, + "ec2:DescribeHosts": {}, + "ec2:DescribeIamInstanceProfileAssociations": {}, + "ec2:DescribeIdFormat": {}, + "ec2:DescribeIdentityIdFormat": {}, + "ec2:DescribeImages": {}, + "ec2:DescribeImportImageTasks": {}, + "ec2:DescribeImportSnapshotTasks": {}, + "ec2:DescribeInstanceConnectEndpoints": {}, + "ec2:DescribeInstanceCreditSpecifications": {}, + "ec2:DescribeInstanceEventNotificationAttributes": {}, + "ec2:DescribeInstanceEventWindows": {}, + "ec2:DescribeInstanceStatus": {}, + "ec2:DescribeInstanceTopology": {}, + "ec2:DescribeInstanceTypeOfferings": {}, + "ec2:DescribeInstanceTypes": {}, + "ec2:DescribeInstances": {}, + "ec2:DescribeInternetGateways": {}, + "ec2:DescribeIpamByoasn": {}, + "ec2:DescribeIpamPools": {}, + "ec2:DescribeIpamResourceDiscoveries": {}, + "ec2:DescribeIpamResourceDiscoveryAssociations": {}, + "ec2:DescribeIpamScopes": {}, + "ec2:DescribeIpams": {}, + "ec2:DescribeIpv6Pools": {}, + "ec2:DescribeKeyPairs": {}, + "ec2:DescribeLaunchTemplateVersions": {}, + "ec2:DescribeLaunchTemplates": {}, + "ec2:DescribeLocalGatewayRouteTablePermissions": {}, + "ec2:DescribeLocalGatewayRouteTableVirtualInterfaceGroupAssociations": {}, + "ec2:DescribeLocalGatewayRouteTableVpcAssociations": {}, + "ec2:DescribeLocalGatewayRouteTables": {}, + "ec2:DescribeLocalGatewayVirtualInterfaceGroups": {}, + "ec2:DescribeLocalGatewayVirtualInterfaces": {}, + "ec2:DescribeLocalGateways": {}, + "ec2:DescribeLockedSnapshots": {}, + "ec2:DescribeManagedPrefixLists": {}, + "ec2:DescribeMovingAddresses": {}, + "ec2:DescribeNatGateways": {}, + "ec2:DescribeNetworkAcls": {}, + "ec2:DescribeNetworkInsightsAccessScopeAnalyses": {}, + "ec2:DescribeNetworkInsightsAccessScopes": {}, + "ec2:DescribeNetworkInsightsAnalyses": {}, + "ec2:DescribeNetworkInsightsPaths": {}, + "ec2:DescribeNetworkInterfaceAttribute": {}, + "ec2:DescribeNetworkInterfacePermissions": {}, + "ec2:DescribeNetworkInterfaces": {}, + "ec2:DescribePlacementGroups": {}, + "ec2:DescribePrefixLists": {}, + "ec2:DescribePrincipalIdFormat": {}, + "ec2:DescribePublicIpv4Pools": {}, + "ec2:DescribeRegions": {}, + "ec2:DescribeReplaceRootVolumeTasks": {}, + "ec2:DescribeReservedInstances": {}, + "ec2:DescribeReservedInstancesListings": {}, + "ec2:DescribeReservedInstancesModifications": {}, + "ec2:DescribeReservedInstancesOfferings": {}, + "ec2:DescribeRouteTables": {}, + "ec2:DescribeScheduledInstanceAvailability": {}, + "ec2:DescribeScheduledInstances": {}, + "ec2:DescribeSecurityGroupReferences": {}, + "ec2:DescribeSecurityGroupRules": {}, + "ec2:DescribeSecurityGroups": {}, + "ec2:DescribeSnapshotTierStatus": {}, + "ec2:DescribeSnapshots": {}, + "ec2:DescribeSpotDatafeedSubscription": {}, + "ec2:DescribeSpotFleetRequests": {}, + "ec2:DescribeSpotInstanceRequests": {}, + "ec2:DescribeSpotPriceHistory": {}, + "ec2:DescribeStaleSecurityGroups": {}, + "ec2:DescribeStoreImageTasks": {}, + "ec2:DescribeSubnets": {}, + "ec2:DescribeTags": {}, + "ec2:DescribeTrafficMirrorFilters": {}, + "ec2:DescribeTrafficMirrorSessions": {}, + "ec2:DescribeTrafficMirrorTargets": {}, + "ec2:DescribeTransitGatewayAttachments": {}, + "ec2:DescribeTransitGatewayConnectPeers": {}, + "ec2:DescribeTransitGatewayConnects": {}, + "ec2:DescribeTransitGatewayMulticastDomains": {}, + "ec2:DescribeTransitGatewayPeeringAttachments": {}, + "ec2:DescribeTransitGatewayPolicyTables": {}, + "ec2:DescribeTransitGatewayRouteTableAnnouncements": {}, + "ec2:DescribeTransitGatewayRouteTables": {}, + "ec2:DescribeTransitGatewayVpcAttachments": {}, + "ec2:DescribeTransitGateways": {}, + "ec2:DescribeTrunkInterfaceAssociations": {}, + "ec2:DescribeVerifiedAccessEndpoints": {}, + "ec2:DescribeVerifiedAccessGroups": {}, + "ec2:DescribeVerifiedAccessInstanceLoggingConfigurations": {}, + "ec2:DescribeVerifiedAccessInstanceWebAclAssociations": {}, + "ec2:DescribeVerifiedAccessInstances": {}, + "ec2:DescribeVerifiedAccessTrustProviders": {}, + "ec2:DescribeVolumeStatus": {}, + "ec2:DescribeVolumes": {}, + "ec2:DescribeVolumesModifications": {}, + "ec2:DescribeVpcClassicLink": {}, + "ec2:DescribeVpcClassicLinkDnsSupport": {}, + "ec2:DescribeVpcEndpointConnectionNotifications": {}, + "ec2:DescribeVpcEndpointConnections": {}, + "ec2:DescribeVpcEndpointServiceConfigurations": {}, + "ec2:DescribeVpcEndpointServices": {}, + "ec2:DescribeVpcEndpoints": {}, + "ec2:DescribeVpcPeeringConnections": {}, + "ec2:DescribeVpcs": {}, + "ec2:DescribeVpnConnections": {}, + "ec2:DescribeVpnGateways": {}, + "ec2:DisableAwsNetworkPerformanceMetricSubscription": {}, + "ec2:DisableEbsEncryptionByDefault": {}, + "ec2:DisableImageBlockPublicAccess": {}, + "ec2:DisableIpamOrganizationAdminAccount": {}, + "ec2:DisableSerialConsoleAccess": {}, + "ec2:DisableSnapshotBlockPublicAccess": {}, + "ec2:DisassociateIpamByoasn": {}, + "ec2:DisassociateTrunkInterface": {}, + "ec2:EnableAwsNetworkPerformanceMetricSubscription": {}, + "ec2:EnableEbsEncryptionByDefault": {}, + "ec2:EnableImageBlockPublicAccess": {}, + "ec2:EnableIpamOrganizationAdminAccount": {}, + "ec2:EnableReachabilityAnalyzerOrganizationSharing": {}, + "ec2:EnableSerialConsoleAccess": {}, + "ec2:EnableSnapshotBlockPublicAccess": {}, + "ec2:ExportTransitGatewayRoutes": {}, + "ec2:GetAssociatedIpv6PoolCidrs": {}, + "ec2:GetAwsNetworkPerformanceData": {}, + "ec2:GetDefaultCreditSpecification": {}, + "ec2:GetEbsDefaultKmsKeyId": {}, + "ec2:GetEbsEncryptionByDefault": {}, + "ec2:GetHostReservationPurchasePreview": {}, + "ec2:GetImageBlockPublicAccessState": {}, + "ec2:GetInstanceTypesFromInstanceRequirements": {}, + "ec2:GetReservedInstancesExchangeQuote": {}, + "ec2:GetSerialConsoleAccessStatus": {}, + "ec2:GetSnapshotBlockPublicAccessState": {}, + "ec2:GetSpotPlacementScores": {}, + "ec2:GetSubnetCidrReservations": {}, + "ec2:GetTransitGatewayAttachmentPropagations": {}, + "ec2:GetTransitGatewayPrefixListReferences": {}, + "ec2:GetTransitGatewayRouteTableAssociations": {}, + "ec2:GetTransitGatewayRouteTablePropagations": {}, + "ec2:GetVpnConnectionDeviceTypes": {}, + "ec2:InjectApiError": {}, + "ec2:ListImagesInRecycleBin": {}, + "ec2:ListSnapshotsInRecycleBin": {}, + "ec2:ModifyAvailabilityZoneGroup": {}, + "ec2:ModifyDefaultCreditSpecification": {}, + "ec2:ModifyEbsDefaultKmsKeyId": {}, + "ec2:ModifyIdFormat": {}, + "ec2:ModifyIdentityIdFormat": {}, + "ec2:MoveAddressToVpc": {}, + "ec2:ProvisionByoipCidr": {}, + "ec2:PurchaseReservedInstancesOffering": {}, + "ec2:PurchaseScheduledInstances": {}, + "ec2:RegisterInstanceEventNotificationAttributes": {}, + "ec2:ReportInstanceStatus": {}, + "ec2:ResetEbsDefaultKmsKeyId": {}, + "ec2:RestoreAddressToClassic": {}, + "ec2:RunScheduledInstances": {}, + "ec2:WithdrawByoipCidr": {}, + "ec2messages:AcknowledgeMessage": {}, + "ec2messages:DeleteMessage": {}, + "ec2messages:FailMessage": {}, + "ec2messages:GetEndpoint": {}, + "ec2messages:GetMessages": {}, + "ec2messages:SendReply": {}, + "ecr-public:GetAuthorizationToken": {}, + "ecr:BatchImportUpstreamImage": {}, + "ecr:CreatePullThroughCacheRule": {}, + "ecr:CreateRepository": {}, + "ecr:CreateRepositoryCreationTemplate": {}, + "ecr:DeletePullThroughCacheRule": {}, + "ecr:DeleteRegistryPolicy": {}, + "ecr:DeleteRepositoryCreationTemplate": {}, + "ecr:DescribePullThroughCacheRules": {}, + "ecr:DescribeRegistry": {}, + "ecr:DescribeRepositoryCreationTemplate": {}, + "ecr:GetAuthorizationToken": {}, + "ecr:GetRegistryPolicy": {}, + "ecr:GetRegistryScanningConfiguration": {}, + "ecr:PutRegistryPolicy": {}, + "ecr:PutRegistryScanningConfiguration": {}, + "ecr:PutReplicationConfiguration": {}, + "ecr:UpdatePullThroughCacheRule": {}, + "ecr:ValidatePullThroughCacheRule": {}, + "ecs:CreateCapacityProvider": {}, + "ecs:CreateCluster": {}, + "ecs:CreateTaskSet": {}, + "ecs:DeleteAccountSetting": {}, + "ecs:DeregisterTaskDefinition": {}, + "ecs:DescribeTaskDefinition": {}, + "ecs:DiscoverPollEndpoint": {}, + "ecs:ListAccountSettings": {}, + "ecs:ListClusters": {}, + "ecs:ListServices": {}, + "ecs:ListServicesByNamespace": {}, + "ecs:ListTaskDefinitionFamilies": {}, + "ecs:ListTaskDefinitions": {}, + "ecs:PutAccountSetting": {}, + "ecs:PutAccountSettingDefault": {}, + "ecs:RegisterTaskDefinition": {}, + "eks:CreateCluster": {}, + "eks:CreateEksAnywhereSubscription": {}, + "eks:DescribeAddonConfiguration": {}, + "eks:DescribeAddonVersions": {}, + "eks:ListAccessPolicies": {}, + "eks:ListClusters": {}, + "eks:ListEksAnywhereSubscriptions": {}, + "eks:RegisterCluster": {}, + "elasticache:DescribeCacheEngineVersions": {}, + "elasticache:DescribeEngineDefaultParameters": {}, + "elasticache:DescribeEvents": {}, + "elasticache:DescribeReservedCacheNodesOfferings": {}, + "elasticache:DescribeServiceUpdates": {}, + "elasticbeanstalk:CheckDNSAvailability": {}, + "elasticbeanstalk:CreateStorageLocation": {}, + "elasticbeanstalk:DescribeAccountAttributes": {}, + "elasticbeanstalk:ListPlatformBranches": {}, + "elasticfilesystem:CreateFileSystem": {}, + "elasticfilesystem:DescribeAccountPreferences": {}, + "elasticfilesystem:PutAccountPreferences": {}, + "elasticloadbalancing:DescribeAccountLimits": {}, + "elasticloadbalancing:DescribeInstanceHealth": {}, + "elasticloadbalancing:DescribeListenerCertificates": {}, + "elasticloadbalancing:DescribeListeners": {}, + "elasticloadbalancing:DescribeLoadBalancerAttributes": {}, + "elasticloadbalancing:DescribeLoadBalancerPolicies": {}, + "elasticloadbalancing:DescribeLoadBalancerPolicyTypes": {}, + "elasticloadbalancing:DescribeLoadBalancers": {}, + "elasticloadbalancing:DescribeRules": {}, + "elasticloadbalancing:DescribeSSLPolicies": {}, + "elasticloadbalancing:DescribeTags": {}, + "elasticloadbalancing:DescribeTargetGroupAttributes": {}, + "elasticloadbalancing:DescribeTargetGroups": {}, + "elasticloadbalancing:DescribeTargetHealth": {}, + "elasticloadbalancing:DescribeTrustStoreAssociations": {}, + "elasticloadbalancing:DescribeTrustStoreRevocations": {}, + "elasticloadbalancing:DescribeTrustStores": {}, + "elasticloadbalancing:SetWebAcl": {}, + "elasticmapreduce:CreateRepository": {}, + "elasticmapreduce:CreateSecurityConfiguration": {}, + "elasticmapreduce:CreateStudio": {}, + "elasticmapreduce:DeleteRepository": {}, + "elasticmapreduce:DeleteSecurityConfiguration": {}, + "elasticmapreduce:DescribeReleaseLabel": {}, + "elasticmapreduce:DescribeRepository": {}, + "elasticmapreduce:DescribeSecurityConfiguration": {}, + "elasticmapreduce:GetBlockPublicAccessConfiguration": {}, + "elasticmapreduce:LinkRepository": {}, + "elasticmapreduce:ListClusters": {}, + "elasticmapreduce:ListEditors": {}, + "elasticmapreduce:ListNotebookExecutions": {}, + "elasticmapreduce:ListReleaseLabels": {}, + "elasticmapreduce:ListRepositories": {}, + "elasticmapreduce:ListSecurityConfigurations": {}, + "elasticmapreduce:ListStudioSessionMappings": {}, + "elasticmapreduce:ListStudios": {}, + "elasticmapreduce:ListSupportedInstanceTypes": {}, + "elasticmapreduce:PutBlockPublicAccessConfiguration": {}, + "elasticmapreduce:RunJobFlow": {}, + "elasticmapreduce:UnlinkRepository": {}, + "elasticmapreduce:UpdateRepository": {}, + "elasticmapreduce:ViewEventsFromAllClustersInConsole": {}, + "elastictranscoder:CreatePipeline": {}, + "elastictranscoder:CreatePreset": {}, + "elastictranscoder:ListJobsByStatus": {}, + "elastictranscoder:ListPipelines": {}, + "elastictranscoder:ListPresets": {}, + "elastictranscoder:TestRole": {}, + "elemental-activations:CompleteAccountRegistration": {}, + "elemental-activations:CompleteFileUpload": {}, + "elemental-activations:DownloadSoftware": {}, + "elemental-activations:GenerateLicenses": {}, + "elemental-activations:StartAccountRegistration": {}, + "elemental-activations:StartFileUpload": {}, + "elemental-appliances-software:CompleteUpload": {}, + "elemental-appliances-software:CreateOrderV1": {}, + "elemental-appliances-software:GetAvsCorrectAddress": {}, + "elemental-appliances-software:GetBillingAddresses": {}, + "elemental-appliances-software:GetDeliveryAddressesV2": {}, + "elemental-appliances-software:GetOrder": {}, + "elemental-appliances-software:GetOrdersV2": {}, + "elemental-appliances-software:GetTaxes": {}, + "elemental-appliances-software:ListQuotes": {}, + "elemental-appliances-software:StartUpload": {}, + "elemental-appliances-software:SubmitOrderV1": {}, + "elemental-support-cases:CheckCasePermission": {}, + "elemental-support-cases:CreateCase": {}, + "elemental-support-cases:GetCase": {}, + "elemental-support-cases:GetCases": {}, + "elemental-support-cases:UpdateCase": {}, + "elemental-support-content:Query": {}, + "emr-containers:CreateJobTemplate": {}, + "emr-containers:CreateVirtualCluster": {}, + "emr-containers:ListJobTemplates": {}, + "emr-containers:ListVirtualClusters": {}, + "emr-serverless:CreateApplication": {}, + "emr-serverless:ListApplications": {}, + "entityresolution:CreateIdMappingWorkflow": {}, + "entityresolution:CreateMatchingWorkflow": {}, + "entityresolution:CreateSchemaMapping": {}, + "entityresolution:ListIdMappingWorkflows": {}, + "entityresolution:ListMatchingWorkflows": {}, + "entityresolution:ListSchemaMappings": {}, + "entityresolution:ListTagsForResource": {}, + "entityresolution:TagResource": {}, + "entityresolution:UntagResource": {}, + "es:AcceptInboundConnection": {}, + "es:AcceptInboundCrossClusterSearchConnection": {}, + "es:AuthorizeVpcEndpointAccess": {}, + "es:CreateElasticsearchServiceRole": {}, + "es:CreatePackage": {}, + "es:CreateServiceRole": {}, + "es:CreateVpcEndpoint": {}, + "es:DeleteElasticsearchServiceRole": {}, + "es:DeleteInboundConnection": {}, + "es:DeleteInboundCrossClusterSearchConnection": {}, + "es:DeleteOutboundConnection": {}, + "es:DeleteOutboundCrossClusterSearchConnection": {}, + "es:DeletePackage": {}, + "es:DeleteVpcEndpoint": {}, + "es:DescribeElasticsearchInstanceTypeLimits": {}, + "es:DescribeInboundConnections": {}, + "es:DescribeInboundCrossClusterSearchConnections": {}, + "es:DescribeInstanceTypeLimits": {}, + "es:DescribeOutboundConnections": {}, + "es:DescribeOutboundCrossClusterSearchConnections": {}, + "es:DescribePackages": {}, + "es:DescribeReservedElasticsearchInstanceOfferings": {}, + "es:DescribeReservedElasticsearchInstances": {}, + "es:DescribeReservedInstanceOfferings": {}, + "es:DescribeReservedInstances": {}, + "es:DescribeVpcEndpoints": {}, + "es:GetPackageVersionHistory": {}, + "es:ListDomainNames": {}, + "es:ListDomainsForPackage": {}, + "es:ListElasticsearchInstanceTypeDetails": {}, + "es:ListElasticsearchInstanceTypes": {}, + "es:ListElasticsearchVersions": {}, + "es:ListInstanceTypeDetails": {}, + "es:ListVersions": {}, + "es:ListVpcEndpointAccess": {}, + "es:ListVpcEndpoints": {}, + "es:ListVpcEndpointsForDomain": {}, + "es:PurchaseReservedElasticsearchInstanceOffering": {}, + "es:PurchaseReservedInstanceOffering": {}, + "es:RejectInboundConnection": {}, + "es:RejectInboundCrossClusterSearchConnection": {}, + "es:RevokeVpcEndpointAccess": {}, + "es:UpdatePackage": {}, + "es:UpdateVpcEndpoint": {}, + "events:ListApiDestinations": {}, + "events:ListArchives": {}, + "events:ListConnections": {}, + "events:ListEndpoints": {}, + "events:ListEventBuses": {}, + "events:ListEventSources": {}, + "events:ListPartnerEventSources": {}, + "events:ListReplays": {}, + "events:ListRuleNamesByTarget": {}, + "events:ListRules": {}, + "events:PutPartnerEvents": {}, + "events:PutPermission": {}, + "events:RemovePermission": {}, + "events:TestEventPattern": {}, + "evidently:CreateExperiment": {}, + "evidently:CreateFeature": {}, + "evidently:CreateLaunch": {}, + "evidently:CreateProject": {}, + "evidently:CreateSegment": {}, + "evidently:ListExperiments": {}, + "evidently:ListFeatures": {}, + "evidently:ListLaunches": {}, + "evidently:ListProjects": {}, + "evidently:ListSegmentReferences": {}, + "evidently:ListSegments": {}, + "evidently:ListTagsForResource": {}, + "evidently:TestSegmentPattern": {}, + "finspace:CreateKxEnvironment": {}, + "finspace:ListKxEnvironments": {}, + "firehose:ListDeliveryStreams": {}, + "fis:GetTargetResourceType": {}, + "fis:ListActions": {}, + "fis:ListExperimentTemplates": {}, + "fis:ListExperiments": {}, + "fis:ListTargetResourceTypes": {}, + "fms:AssociateAdminAccount": {}, + "fms:AssociateThirdPartyFirewall": {}, + "fms:DeleteNotificationChannel": {}, + "fms:DisassociateAdminAccount": {}, + "fms:DisassociateThirdPartyFirewall": {}, + "fms:GetAdminAccount": {}, + "fms:GetAdminScope": {}, + "fms:GetNotificationChannel": {}, + "fms:GetThirdPartyFirewallAssociationStatus": {}, + "fms:ListAdminAccountsForOrganization": {}, + "fms:ListAdminsManagingAccount": {}, + "fms:ListAppsLists": {}, + "fms:ListDiscoveredResources": {}, + "fms:ListMemberAccounts": {}, + "fms:ListPolicies": {}, + "fms:ListProtocolsLists": {}, + "fms:ListResourceSets": {}, + "fms:ListThirdPartyFirewallFirewallPolicies": {}, + "fms:PutAdminAccount": {}, + "fms:PutNotificationChannel": {}, + "forecast:CreateAutoPredictor": {}, + "forecast:ListDatasetGroups": {}, + "forecast:ListDatasetImportJobs": {}, + "forecast:ListDatasets": {}, + "forecast:ListExplainabilities": {}, + "forecast:ListExplainabilityExports": {}, + "forecast:ListForecastExportJobs": {}, + "forecast:ListForecasts": {}, + "forecast:ListMonitors": {}, + "forecast:ListPredictorBacktestExportJobs": {}, + "forecast:ListPredictors": {}, + "forecast:ListWhatIfAnalyses": {}, + "forecast:ListWhatIfForecastExports": {}, + "forecast:ListWhatIfForecasts": {}, + "frauddetector:BatchCreateVariable": {}, + "frauddetector:CreateList": {}, + "frauddetector:CreateVariable": {}, + "frauddetector:GetKMSEncryptionKey": {}, + "frauddetector:PutKMSEncryptionKey": {}, + "freertos:CreateSubscription": {}, + "freertos:DescribeHardwarePlatform": {}, + "freertos:GetEmpPatchUrl": {}, + "freertos:GetSoftwareURL": {}, + "freertos:GetSoftwareURLForConfiguration": {}, + "freertos:GetSubscriptionBillingAmount": {}, + "freertos:ListFreeRTOSVersions": {}, + "freertos:ListHardwarePlatforms": {}, + "freertos:ListHardwareVendors": {}, + "freertos:ListSoftwareConfigurations": {}, + "freertos:ListSoftwarePatches": {}, + "freertos:ListSubscriptionEmails": {}, + "freertos:ListSubscriptions": {}, + "freertos:UpdateEmailRecipients": {}, + "freertos:VerifyEmail": {}, + "freetier:GetFreeTierAlertPreference": {}, + "freetier:GetFreeTierUsage": {}, + "freetier:PutFreeTierAlertPreference": {}, + "fsx:DescribeBackups": {}, + "fsx:DescribeDataRepositoryAssociations": {}, + "fsx:DescribeDataRepositoryTasks": {}, + "fsx:DescribeFileCaches": {}, + "fsx:DescribeFileSystems": {}, + "fsx:DescribeSharedVpcConfiguration": {}, + "fsx:DescribeSnapshots": {}, + "fsx:DescribeStorageVirtualMachines": {}, + "fsx:DescribeVolumes": {}, + "fsx:UpdateSharedVpcConfiguration": {}, + "gamelift:AcceptMatch": {}, + "gamelift:CreateAlias": {}, + "gamelift:CreateBuild": {}, + "gamelift:CreateFleet": {}, + "gamelift:CreateGameServerGroup": {}, + "gamelift:CreateGameSession": {}, + "gamelift:CreateGameSessionQueue": {}, + "gamelift:CreateLocation": {}, + "gamelift:CreateMatchmakingConfiguration": {}, + "gamelift:CreateMatchmakingRuleSet": {}, + "gamelift:CreatePlayerSession": {}, + "gamelift:CreatePlayerSessions": {}, + "gamelift:CreateScript": {}, + "gamelift:CreateVpcPeeringAuthorization": {}, + "gamelift:CreateVpcPeeringConnection": {}, + "gamelift:DeleteVpcPeeringAuthorization": {}, + "gamelift:DeleteVpcPeeringConnection": {}, + "gamelift:DescribeEC2InstanceLimits": {}, + "gamelift:DescribeFleetAttributes": {}, + "gamelift:DescribeFleetCapacity": {}, + "gamelift:DescribeFleetUtilization": {}, + "gamelift:DescribeGameSessionDetails": {}, + "gamelift:DescribeGameSessionPlacement": {}, + "gamelift:DescribeGameSessionQueues": {}, + "gamelift:DescribeGameSessions": {}, + "gamelift:DescribeMatchmaking": {}, + "gamelift:DescribeMatchmakingConfigurations": {}, + "gamelift:DescribeMatchmakingRuleSets": {}, + "gamelift:DescribePlayerSessions": {}, + "gamelift:DescribeVpcPeeringAuthorizations": {}, + "gamelift:DescribeVpcPeeringConnections": {}, + "gamelift:GetGameSessionLogUrl": {}, + "gamelift:ListAliases": {}, + "gamelift:ListBuilds": {}, + "gamelift:ListFleets": {}, + "gamelift:ListGameServerGroups": {}, + "gamelift:ListLocations": {}, + "gamelift:ListScripts": {}, + "gamelift:SearchGameSessions": {}, + "gamelift:StartMatchBackfill": {}, + "gamelift:StartMatchmaking": {}, + "gamelift:StopGameSessionPlacement": {}, + "gamelift:StopMatchmaking": {}, + "gamelift:UpdateGameSession": {}, + "gamelift:ValidateMatchmakingRuleSet": {}, + "glacier:GetDataRetrievalPolicy": {}, + "glacier:ListProvisionedCapacity": {}, + "glacier:ListVaults": {}, + "glacier:PurchaseProvisionedCapacity": {}, + "glacier:SetDataRetrievalPolicy": {}, + "globalaccelerator:AdvertiseByoipCidr": {}, + "globalaccelerator:CreateAccelerator": {}, + "globalaccelerator:CreateCrossAccountAttachment": {}, + "globalaccelerator:CreateCustomRoutingAccelerator": {}, + "globalaccelerator:DeprovisionByoipCidr": {}, + "globalaccelerator:ListAccelerators": {}, + "globalaccelerator:ListByoipCidrs": {}, + "globalaccelerator:ListCrossAccountAttachments": {}, + "globalaccelerator:ListCrossAccountResourceAccounts": {}, + "globalaccelerator:ListCrossAccountResources": {}, + "globalaccelerator:ListCustomRoutingAccelerators": {}, + "globalaccelerator:ListCustomRoutingPortMappingsByDestination": {}, + "globalaccelerator:ProvisionByoipCidr": {}, + "globalaccelerator:WithdrawByoipCidr": {}, + "glue:CheckSchemaVersionValidity": {}, + "glue:CreateClassifier": {}, + "glue:CreateCrawler": {}, + "glue:CreateCustomEntityType": {}, + "glue:CreateDataQualityRuleset": {}, + "glue:CreateDevEndpoint": {}, + "glue:CreateMLTransform": {}, + "glue:CreateScript": {}, + "glue:CreateSecurityConfiguration": {}, + "glue:CreateSession": {}, + "glue:DeleteClassifier": {}, + "glue:DeleteSecurityConfiguration": {}, + "glue:DeregisterDataPreview": {}, + "glue:GetClassifier": {}, + "glue:GetClassifiers": {}, + "glue:GetColumnStatisticsTaskRun": {}, + "glue:GetColumnStatisticsTaskRuns": {}, + "glue:GetCrawlerMetrics": {}, + "glue:GetCrawlers": {}, + "glue:GetDataPreviewStatement": {}, + "glue:GetDataflowGraph": {}, + "glue:GetDevEndpoints": {}, + "glue:GetJobBookmark": {}, + "glue:GetJobs": {}, + "glue:GetMapping": {}, + "glue:GetNotebookInstanceStatus": {}, + "glue:GetPlan": {}, + "glue:GetSecurityConfiguration": {}, + "glue:GetSecurityConfigurations": {}, + "glue:GetTriggers": {}, + "glue:GlueNotebookAuthorize": {}, + "glue:GlueNotebookRefreshCredentials": {}, + "glue:ListBlueprints": {}, + "glue:ListColumnStatisticsTaskRuns": {}, + "glue:ListCrawlers": {}, + "glue:ListCrawls": {}, + "glue:ListCustomEntityTypes": {}, + "glue:ListDevEndpoints": {}, + "glue:ListJobs": {}, + "glue:ListRegistries": {}, + "glue:ListSessions": {}, + "glue:ListTriggers": {}, + "glue:ListWorkflows": {}, + "glue:ResetJobBookmark": {}, + "glue:RunDataPreviewStatement": {}, + "glue:SendFeedback": {}, + "glue:StartCompletion": {}, + "glue:StartCrawlerSchedule": {}, + "glue:StartNotebook": {}, + "glue:StopCrawlerSchedule": {}, + "glue:TerminateNotebook": {}, + "glue:TestConnection": {}, + "glue:UpdateClassifier": {}, + "glue:UpdateCrawlerSchedule": {}, + "glue:UseGlueStudio": {}, + "grafana:CreateWorkspace": {}, + "grafana:ListWorkspaces": {}, + "greengrass:AssociateServiceRoleToAccount": {}, + "greengrass:CreateConnectorDefinition": {}, + "greengrass:CreateCoreDefinition": {}, + "greengrass:CreateDeployment": {}, + "greengrass:CreateDeviceDefinition": {}, + "greengrass:CreateFunctionDefinition": {}, + "greengrass:CreateGroup": {}, + "greengrass:CreateLoggerDefinition": {}, + "greengrass:CreateResourceDefinition": {}, + "greengrass:CreateSoftwareUpdateJob": {}, + "greengrass:CreateSubscriptionDefinition": {}, + "greengrass:DisassociateServiceRoleFromAccount": {}, + "greengrass:GetServiceRoleForAccount": {}, + "greengrass:ListBulkDeployments": {}, + "greengrass:ListComponents": {}, + "greengrass:ListConnectorDefinitions": {}, + "greengrass:ListCoreDefinitions": {}, + "greengrass:ListCoreDevices": {}, + "greengrass:ListDeployments": {}, + "greengrass:ListDeviceDefinitions": {}, + "greengrass:ListFunctionDefinitions": {}, + "greengrass:ListGroups": {}, + "greengrass:ListLoggerDefinitions": {}, + "greengrass:ListResourceDefinitions": {}, + "greengrass:ListSubscriptionDefinitions": {}, + "greengrass:StartBulkDeployment": {}, + "groundstation:CreateConfig": {}, + "groundstation:CreateDataflowEndpointGroup": {}, + "groundstation:CreateEphemeris": {}, + "groundstation:CreateMissionProfile": {}, + "groundstation:GetMinuteUsage": {}, + "groundstation:ListConfigs": {}, + "groundstation:ListContacts": {}, + "groundstation:ListDataflowEndpointGroups": {}, + "groundstation:ListEphemerides": {}, + "groundstation:ListGroundStations": {}, + "groundstation:ListMissionProfiles": {}, + "groundstation:ListSatellites": {}, + "groundstation:RegisterAgent": {}, + "groundstation:ReserveContact": {}, + "groundtruthlabeling:AssociatePatchToManifestJob": {}, + "groundtruthlabeling:DescribeConsoleJob": {}, + "groundtruthlabeling:ListDatasetObjects": {}, + "groundtruthlabeling:RunFilterOrSampleDatasetJob": {}, + "groundtruthlabeling:RunGenerateManifestByCrawlingJob": {}, + "guardduty:AcceptAdministratorInvitation": {}, + "guardduty:AcceptInvitation": {}, + "guardduty:ArchiveFindings": {}, + "guardduty:CreateDetector": {}, + "guardduty:CreateIPSet": {}, + "guardduty:CreateMembers": {}, + "guardduty:CreatePublishingDestination": {}, + "guardduty:CreateSampleFindings": {}, + "guardduty:CreateThreatIntelSet": {}, + "guardduty:DeclineInvitations": {}, + "guardduty:DeleteInvitations": {}, + "guardduty:DeleteMembers": {}, + "guardduty:DescribeMalwareScans": {}, + "guardduty:DescribeOrganizationConfiguration": {}, + "guardduty:DisableOrganizationAdminAccount": {}, + "guardduty:DisassociateFromAdministratorAccount": {}, + "guardduty:DisassociateFromMasterAccount": {}, + "guardduty:DisassociateMembers": {}, + "guardduty:EnableOrganizationAdminAccount": {}, + "guardduty:GetAdministratorAccount": {}, + "guardduty:GetFindings": {}, + "guardduty:GetFindingsStatistics": {}, + "guardduty:GetInvitationsCount": {}, + "guardduty:GetMalwareScanSettings": {}, + "guardduty:GetMasterAccount": {}, + "guardduty:GetMemberDetectors": {}, + "guardduty:GetMembers": {}, + "guardduty:GetOrganizationStatistics": {}, + "guardduty:GetRemainingFreeTrialDays": {}, + "guardduty:GetUsageStatistics": {}, + "guardduty:InviteMembers": {}, + "guardduty:ListDetectors": {}, + "guardduty:ListFilters": {}, + "guardduty:ListFindings": {}, + "guardduty:ListIPSets": {}, + "guardduty:ListInvitations": {}, + "guardduty:ListMembers": {}, + "guardduty:ListOrganizationAdminAccounts": {}, + "guardduty:ListPublishingDestinations": {}, + "guardduty:ListThreatIntelSets": {}, + "guardduty:SendSecurityTelemetry": {}, + "guardduty:StartMalwareScan": {}, + "guardduty:StartMonitoringMembers": {}, + "guardduty:StopMonitoringMembers": {}, + "guardduty:UnarchiveFindings": {}, + "guardduty:UpdateFindingsFeedback": {}, + "guardduty:UpdateMalwareScanSettings": {}, + "guardduty:UpdateMemberDetectors": {}, + "guardduty:UpdateOrganizationConfiguration": {}, + "health:DescribeAffectedAccountsForOrganization": {}, + "health:DescribeAffectedEntitiesForOrganization": {}, + "health:DescribeEntityAggregates": {}, + "health:DescribeEntityAggregatesForOrganization": {}, + "health:DescribeEventAggregates": {}, + "health:DescribeEventDetailsForOrganization": {}, + "health:DescribeEventTypes": {}, + "health:DescribeEvents": {}, + "health:DescribeEventsForOrganization": {}, + "health:DescribeHealthServiceStatusForOrganization": {}, + "health:DisableHealthServiceAccessForOrganization": {}, + "health:EnableHealthServiceAccessForOrganization": {}, + "healthlake:CreateFHIRDatastore": {}, + "healthlake:ListFHIRDatastores": {}, + "honeycode:ApproveTeamAssociation": {}, + "honeycode:CreateTeam": {}, + "honeycode:CreateTenant": {}, + "honeycode:DeleteDomains": {}, + "honeycode:DeregisterGroups": {}, + "honeycode:DescribeTeam": {}, + "honeycode:ListDomains": {}, + "honeycode:ListGroups": {}, + "honeycode:ListTagsForResource": {}, + "honeycode:ListTeamAssociations": {}, + "honeycode:ListTenants": {}, + "honeycode:RegisterDomainForVerification": {}, + "honeycode:RegisterGroups": {}, + "honeycode:RejectTeamAssociation": {}, + "honeycode:RestartDomainVerification": {}, + "honeycode:TagResource": {}, + "honeycode:UntagResource": {}, + "honeycode:UpdateTeam": {}, + "iam:CreateAccountAlias": {}, + "iam:DeleteAccountAlias": {}, + "iam:DeleteAccountPasswordPolicy": {}, + "iam:DeleteCloudFrontPublicKey": {}, + "iam:GenerateCredentialReport": {}, + "iam:GetAccountAuthorizationDetails": {}, + "iam:GetAccountEmailAddress": {}, + "iam:GetAccountName": {}, + "iam:GetAccountPasswordPolicy": {}, + "iam:GetAccountSummary": {}, + "iam:GetCloudFrontPublicKey": {}, + "iam:GetContextKeysForCustomPolicy": {}, + "iam:GetCredentialReport": {}, + "iam:GetOrganizationsAccessReport": {}, + "iam:GetServiceLastAccessedDetails": {}, + "iam:GetServiceLastAccessedDetailsWithEntities": {}, + "iam:ListAccountAliases": {}, + "iam:ListCloudFrontPublicKeys": {}, + "iam:ListGroups": {}, + "iam:ListInstanceProfiles": {}, + "iam:ListOpenIDConnectProviders": {}, + "iam:ListPolicies": {}, + "iam:ListRoles": {}, + "iam:ListSAMLProviders": {}, + "iam:ListSTSRegionalEndpointsStatus": {}, + "iam:ListServerCertificates": {}, + "iam:ListUsers": {}, + "iam:ListVirtualMFADevices": {}, + "iam:SetSTSRegionalEndpointStatus": {}, + "iam:SetSecurityTokenServicePreferences": {}, + "iam:SimulateCustomPolicy": {}, + "iam:UpdateAccountEmailAddress": {}, + "iam:UpdateAccountName": {}, + "iam:UpdateAccountPasswordPolicy": {}, + "iam:UpdateCloudFrontPublicKey": {}, + "iam:UploadCloudFrontPublicKey": {}, + "identity-sync:CreateSyncProfile": {}, + "identitystore-auth:BatchDeleteSession": {}, + "identitystore-auth:BatchGetSession": {}, + "identitystore-auth:ListSessions": {}, + "imagebuilder:ListComponents": {}, + "imagebuilder:ListContainerRecipes": {}, + "imagebuilder:ListDistributionConfigurations": {}, + "imagebuilder:ListImagePipelines": {}, + "imagebuilder:ListImageRecipes": {}, + "imagebuilder:ListImages": {}, + "imagebuilder:ListInfrastructureConfigurations": {}, + "imagebuilder:ListLifecyclePolicies": {}, + "imagebuilder:ListWaitingWorkflowSteps": {}, + "imagebuilder:ListWorkflows": {}, + "importexport:CancelJob": {}, + "importexport:CreateJob": {}, + "importexport:GetShippingLabel": {}, + "importexport:GetStatus": {}, + "importexport:ListJobs": {}, + "importexport:UpdateJob": {}, + "inspector-scan:ScanSbom": {}, + "inspector2:AssociateMember": {}, + "inspector2:BatchGetAccountStatus": {}, + "inspector2:BatchGetCodeSnippet": {}, + "inspector2:BatchGetFindingDetails": {}, + "inspector2:BatchGetFreeTrialInfo": {}, + "inspector2:BatchGetMemberEc2DeepInspectionStatus": {}, + "inspector2:BatchUpdateMemberEc2DeepInspectionStatus": {}, + "inspector2:CancelFindingsReport": {}, + "inspector2:CancelSbomExport": {}, + "inspector2:CreateFindingsReport": {}, + "inspector2:CreateSbomExport": {}, + "inspector2:DescribeOrganizationConfiguration": {}, + "inspector2:Disable": {}, + "inspector2:DisableDelegatedAdminAccount": {}, + "inspector2:DisassociateMember": {}, + "inspector2:Enable": {}, + "inspector2:EnableDelegatedAdminAccount": {}, + "inspector2:GetCisScanReport": {}, + "inspector2:GetCisScanResultDetails": {}, + "inspector2:GetConfiguration": {}, + "inspector2:GetDelegatedAdminAccount": {}, + "inspector2:GetEc2DeepInspectionConfiguration": {}, + "inspector2:GetEncryptionKey": {}, + "inspector2:GetFindingsReportStatus": {}, + "inspector2:GetMember": {}, + "inspector2:GetSbomExport": {}, + "inspector2:ListAccountPermissions": {}, + "inspector2:ListCisScanConfigurations": {}, + "inspector2:ListCisScanResultsAggregatedByChecks": {}, + "inspector2:ListCisScanResultsAggregatedByTargetResource": {}, + "inspector2:ListCisScans": {}, + "inspector2:ListCoverage": {}, + "inspector2:ListCoverageStatistics": {}, + "inspector2:ListDelegatedAdminAccounts": {}, + "inspector2:ListFilters": {}, + "inspector2:ListFindingAggregations": {}, + "inspector2:ListFindings": {}, + "inspector2:ListMembers": {}, + "inspector2:ListTagsForResource": {}, + "inspector2:ListUsageTotals": {}, + "inspector2:ResetEncryptionKey": {}, + "inspector2:SearchVulnerabilities": {}, + "inspector2:SendCisSessionHealth": {}, + "inspector2:SendCisSessionTelemetry": {}, + "inspector2:StartCisSession": {}, + "inspector2:StopCisSession": {}, + "inspector2:UpdateConfiguration": {}, + "inspector2:UpdateEc2DeepInspectionConfiguration": {}, + "inspector2:UpdateEncryptionKey": {}, + "inspector2:UpdateOrgEc2DeepInspectionConfiguration": {}, + "inspector2:UpdateOrganizationConfiguration": {}, + "inspector:AddAttributesToFindings": {}, + "inspector:CreateAssessmentTarget": {}, + "inspector:CreateAssessmentTemplate": {}, + "inspector:CreateExclusionsPreview": {}, + "inspector:CreateResourceGroup": {}, + "inspector:DeleteAssessmentRun": {}, + "inspector:DeleteAssessmentTarget": {}, + "inspector:DeleteAssessmentTemplate": {}, + "inspector:DescribeAssessmentRuns": {}, + "inspector:DescribeAssessmentTargets": {}, + "inspector:DescribeAssessmentTemplates": {}, + "inspector:DescribeCrossAccountAccessRole": {}, + "inspector:DescribeExclusions": {}, + "inspector:DescribeFindings": {}, + "inspector:DescribeResourceGroups": {}, + "inspector:DescribeRulesPackages": {}, + "inspector:GetAssessmentReport": {}, + "inspector:GetExclusionsPreview": {}, + "inspector:GetTelemetryMetadata": {}, + "inspector:ListAssessmentRunAgents": {}, + "inspector:ListAssessmentRuns": {}, + "inspector:ListAssessmentTargets": {}, + "inspector:ListAssessmentTemplates": {}, + "inspector:ListEventSubscriptions": {}, + "inspector:ListExclusions": {}, + "inspector:ListFindings": {}, + "inspector:ListRulesPackages": {}, + "inspector:ListTagsForResource": {}, + "inspector:PreviewAgents": {}, + "inspector:RegisterCrossAccountAccessRole": {}, + "inspector:RemoveAttributesFromFindings": {}, + "inspector:SetTagsForResource": {}, + "inspector:StartAssessmentRun": {}, + "inspector:StopAssessmentRun": {}, + "inspector:SubscribeToEvent": {}, + "inspector:UnsubscribeFromEvent": {}, + "inspector:UpdateAssessmentTarget": {}, + "internetmonitor:ListMonitors": {}, + "invoicing:GetInvoiceEmailDeliveryPreferences": {}, + "invoicing:GetInvoicePDF": {}, + "invoicing:ListInvoiceSummaries": {}, + "invoicing:PutInvoiceEmailDeliveryPreferences": {}, + "iot-device-tester:CheckVersion": {}, + "iot-device-tester:DownloadTestSuite": {}, + "iot-device-tester:LatestIdt": {}, + "iot-device-tester:SendMetrics": {}, + "iot-device-tester:SupportedVersion": {}, + "iot1click:ClaimDevicesByClaimCode": {}, + "iot1click:ListDevices": {}, + "iot1click:ListProjects": {}, + "iot:AttachThingPrincipal": {}, + "iot:CancelAuditMitigationActionsTask": {}, + "iot:CancelAuditTask": {}, + "iot:CancelDetectMitigationActionsTask": {}, + "iot:ClearDefaultAuthorizer": {}, + "iot:CreateAuditSuppression": {}, + "iot:CreateCertificateFromCsr": {}, + "iot:CreateKeysAndCertificate": {}, + "iot:DeleteAccountAuditConfiguration": {}, + "iot:DeleteAuditSuppression": {}, + "iot:DeleteRegistrationCode": {}, + "iot:DeleteV2LoggingLevel": {}, + "iot:DescribeAccountAuditConfiguration": {}, + "iot:DescribeAuditFinding": {}, + "iot:DescribeAuditMitigationActionsTask": {}, + "iot:DescribeAuditSuppression": {}, + "iot:DescribeAuditTask": {}, + "iot:DescribeDefaultAuthorizer": {}, + "iot:DescribeDetectMitigationActionsTask": {}, + "iot:DescribeEndpoint": {}, + "iot:DescribeEventConfigurations": {}, + "iot:DescribeThingRegistrationTask": {}, + "iot:DetachThingPrincipal": {}, + "iot:GetIndexingConfiguration": {}, + "iot:GetLoggingOptions": {}, + "iot:GetPackageConfiguration": {}, + "iot:GetRegistrationCode": {}, + "iot:GetV2LoggingOptions": {}, + "iot:ListAttachedPolicies": {}, + "iot:ListAuditFindings": {}, + "iot:ListAuditMitigationActionsExecutions": {}, + "iot:ListAuditMitigationActionsTasks": {}, + "iot:ListAuditSuppressions": {}, + "iot:ListAuditTasks": {}, + "iot:ListAuthorizers": {}, + "iot:ListBillingGroups": {}, + "iot:ListCACertificates": {}, + "iot:ListCertificateProviders": {}, + "iot:ListCertificates": {}, + "iot:ListCertificatesByCA": {}, + "iot:ListCustomMetrics": {}, + "iot:ListDetectMitigationActionsTasks": {}, + "iot:ListDimensions": {}, + "iot:ListDomainConfigurations": {}, + "iot:ListFleetMetrics": {}, + "iot:ListIndices": {}, + "iot:ListJobTemplates": {}, + "iot:ListJobs": {}, + "iot:ListManagedJobTemplates": {}, + "iot:ListMitigationActions": {}, + "iot:ListOTAUpdates": {}, + "iot:ListOutgoingCertificates": {}, + "iot:ListPackageVersions": {}, + "iot:ListPackages": {}, + "iot:ListPolicies": {}, + "iot:ListPolicyPrincipals": {}, + "iot:ListPrincipalPolicies": {}, + "iot:ListPrincipalThings": {}, + "iot:ListProvisioningTemplates": {}, + "iot:ListRelatedResourcesForAuditFinding": {}, + "iot:ListRetainedMessages": {}, + "iot:ListRoleAliases": {}, + "iot:ListScheduledAudits": {}, + "iot:ListStreams": {}, + "iot:ListThingGroups": {}, + "iot:ListThingPrincipals": {}, + "iot:ListThingRegistrationTaskReports": {}, + "iot:ListThingRegistrationTasks": {}, + "iot:ListThingTypes": {}, + "iot:ListThings": {}, + "iot:ListTopicRuleDestinations": {}, + "iot:ListTopicRules": {}, + "iot:ListTunnels": {}, + "iot:ListV2LoggingLevels": {}, + "iot:OpenTunnel": {}, + "iot:PutVerificationStateOnViolation": {}, + "iot:RegisterCACertificate": {}, + "iot:RegisterCertificate": {}, + "iot:RegisterCertificateWithoutCA": {}, + "iot:RegisterThing": {}, + "iot:SetLoggingOptions": {}, + "iot:SetV2LoggingLevel": {}, + "iot:SetV2LoggingOptions": {}, + "iot:StartAuditMitigationActionsTask": {}, + "iot:StartOnDemandAuditTask": {}, + "iot:StartThingRegistrationTask": {}, + "iot:StopThingRegistrationTask": {}, + "iot:UpdateAccountAuditConfiguration": {}, + "iot:UpdateAuditSuppression": {}, + "iot:UpdateEventConfigurations": {}, + "iot:UpdateIndexingConfiguration": {}, + "iot:UpdatePackageConfiguration": {}, + "iot:ValidateSecurityProfileBehaviors": {}, + "iotanalytics:DescribeLoggingOptions": {}, + "iotanalytics:ListChannels": {}, + "iotanalytics:ListDatasets": {}, + "iotanalytics:ListDatastores": {}, + "iotanalytics:ListPipelines": {}, + "iotanalytics:PutLoggingOptions": {}, + "iotanalytics:RunPipelineActivity": {}, + "iotdeviceadvisor:CreateSuiteDefinition": {}, + "iotdeviceadvisor:GetEndpoint": {}, + "iotdeviceadvisor:ListSuiteDefinitions": {}, + "iotdeviceadvisor:StartSuiteRun": {}, + "iotevents:DescribeDetectorModelAnalysis": {}, + "iotevents:DescribeLoggingOptions": {}, + "iotevents:GetDetectorModelAnalysisResults": {}, + "iotevents:ListAlarmModels": {}, + "iotevents:ListDetectorModels": {}, + "iotevents:ListInputRoutings": {}, + "iotevents:ListInputs": {}, + "iotevents:PutLoggingOptions": {}, + "iotevents:StartDetectorModelAnalysis": {}, + "iotfleethub:CreateApplication": {}, + "iotfleethub:ListApplications": {}, + "iotfleetwise:GetEncryptionConfiguration": {}, + "iotfleetwise:GetLoggingOptions": {}, + "iotfleetwise:GetRegisterAccountStatus": {}, + "iotfleetwise:ListCampaigns": {}, + "iotfleetwise:ListDecoderManifests": {}, + "iotfleetwise:ListFleets": {}, + "iotfleetwise:ListModelManifests": {}, + "iotfleetwise:ListSignalCatalogs": {}, + "iotfleetwise:ListVehicles": {}, + "iotfleetwise:PutEncryptionConfiguration": {}, + "iotfleetwise:PutLoggingOptions": {}, + "iotfleetwise:RegisterAccount": {}, + "iotroborunner:CreateSite": {}, + "iotroborunner:ListSites": {}, + "iotsitewise:CreateAssetModel": {}, + "iotsitewise:CreateBulkImportJob": {}, + "iotsitewise:CreateGateway": {}, + "iotsitewise:CreatePortal": {}, + "iotsitewise:DescribeBulkImportJob": {}, + "iotsitewise:DescribeDefaultEncryptionConfiguration": {}, + "iotsitewise:DescribeLoggingOptions": {}, + "iotsitewise:DescribeStorageConfiguration": {}, + "iotsitewise:EnableSiteWiseIntegration": {}, + "iotsitewise:ExecuteQuery": {}, + "iotsitewise:ListAssetModels": {}, + "iotsitewise:ListBulkImportJobs": {}, + "iotsitewise:ListGateways": {}, + "iotsitewise:ListPortals": {}, + "iotsitewise:PutDefaultEncryptionConfiguration": {}, + "iotsitewise:PutLoggingOptions": {}, + "iotsitewise:PutStorageConfiguration": {}, + "iottwinmaker:CreateMetadataTransferJob": {}, + "iottwinmaker:CreateWorkspace": {}, + "iottwinmaker:GetPricingPlan": {}, + "iottwinmaker:ListMetadataTransferJobs": {}, + "iottwinmaker:ListWorkspaces": {}, + "iottwinmaker:UpdatePricingPlan": {}, + "iotwireless:AssociateAwsAccountWithPartnerAccount": {}, + "iotwireless:CreateDestination": {}, + "iotwireless:CreateDeviceProfile": {}, + "iotwireless:CreateFuotaTask": {}, + "iotwireless:CreateMulticastGroup": {}, + "iotwireless:CreateServiceProfile": {}, + "iotwireless:CreateWirelessDevice": {}, + "iotwireless:CreateWirelessGateway": {}, + "iotwireless:CreateWirelessGatewayTaskDefinition": {}, + "iotwireless:DeleteQueuedMessages": {}, + "iotwireless:GetEventConfigurationByResourceTypes": {}, + "iotwireless:GetLogLevelsByResourceTypes": {}, + "iotwireless:GetPositionEstimate": {}, + "iotwireless:GetServiceEndpoint": {}, + "iotwireless:ListDestinations": {}, + "iotwireless:ListDeviceProfiles": {}, + "iotwireless:ListEventConfigurations": {}, + "iotwireless:ListFuotaTasks": {}, + "iotwireless:ListMulticastGroups": {}, + "iotwireless:ListNetworkAnalyzerConfigurations": {}, + "iotwireless:ListPartnerAccounts": {}, + "iotwireless:ListPositionConfigurations": {}, + "iotwireless:ListQueuedMessages": {}, + "iotwireless:ListServiceProfiles": {}, + "iotwireless:ListWirelessDeviceImportTasks": {}, + "iotwireless:ListWirelessDevices": {}, + "iotwireless:ListWirelessGatewayTaskDefinitions": {}, + "iotwireless:ListWirelessGateways": {}, + "iotwireless:ResetAllResourceLogLevels": {}, + "iotwireless:StartSingleWirelessDeviceImportTask": {}, + "iotwireless:UpdateEventConfigurationByResourceTypes": {}, + "iotwireless:UpdateLogLevelsByResourceTypes": {}, + "iq:span": {}, + "ivs:ListEncoderConfigurations": {}, + "ivs:ListPlaybackRestrictionPolicies": {}, + "ivs:ListStorageConfigurations": {}, + "kafka:DescribeClusterOperation": {}, + "kafka:DescribeClusterOperationV2": {}, + "kafka:GetBootstrapBrokers": {}, + "kafka:GetCompatibleKafkaVersions": {}, + "kafka:ListClusters": {}, + "kafka:ListClustersV2": {}, + "kafka:ListConfigurations": {}, + "kafka:ListKafkaVersions": {}, + "kafka:ListReplicators": {}, + "kafka:ListVpcConnections": {}, + "kafkaconnect:CreateConnector": {}, + "kafkaconnect:CreateCustomPlugin": {}, + "kafkaconnect:CreateWorkerConfiguration": {}, + "kafkaconnect:DeleteConnector": {}, + "kafkaconnect:DeleteCustomPlugin": {}, + "kafkaconnect:ListConnectors": {}, + "kafkaconnect:ListCustomPlugins": {}, + "kafkaconnect:ListWorkerConfigurations": {}, + "kafkaconnect:UpdateConnector": {}, + "kendra-ranking:CreateRescoreExecutionPlan": {}, + "kendra-ranking:ListRescoreExecutionPlans": {}, + "kendra:CreateIndex": {}, + "kendra:ListIndices": {}, + "kinesis:DescribeLimits": {}, + "kinesis:DisableEnhancedMonitoring": {}, + "kinesis:EnableEnhancedMonitoring": {}, + "kinesis:ListStreams": {}, + "kinesis:UpdateShardCount": {}, + "kinesis:UpdateStreamMode": {}, + "kinesisanalytics:CreateApplication": {}, + "kinesisanalytics:DiscoverInputSchema": {}, + "kinesisanalytics:ListApplications": {}, + "kinesisvideo:ListEdgeAgentConfigurations": {}, + "kinesisvideo:ListSignalingChannels": {}, + "kinesisvideo:ListStreams": {}, + "kms:ConnectCustomKeyStore": {}, + "kms:CreateCustomKeyStore": {}, + "kms:CreateKey": {}, + "kms:DeleteCustomKeyStore": {}, + "kms:DescribeCustomKeyStores": {}, + "kms:DisconnectCustomKeyStore": {}, + "kms:GenerateRandom": {}, + "kms:ListAliases": {}, + "kms:ListKeys": {}, + "kms:ListRetirableGrants": {}, + "kms:UpdateCustomKeyStore": {}, + "lakeformation:AddLFTagsToResource": {}, + "lakeformation:BatchGrantPermissions": {}, + "lakeformation:BatchRevokePermissions": {}, + "lakeformation:CancelTransaction": {}, + "lakeformation:CommitTransaction": {}, + "lakeformation:CreateDataCellsFilter": {}, + "lakeformation:CreateLFTag": {}, + "lakeformation:CreateLakeFormationIdentityCenterConfiguration": {}, + "lakeformation:CreateLakeFormationOptIn": {}, + "lakeformation:DeleteDataCellsFilter": {}, + "lakeformation:DeleteLFTag": {}, + "lakeformation:DeleteLakeFormationIdentityCenterConfiguration": {}, + "lakeformation:DeleteLakeFormationOptIn": {}, + "lakeformation:DeleteObjectsOnCancel": {}, + "lakeformation:DeregisterResource": {}, + "lakeformation:DescribeLakeFormationIdentityCenterConfiguration": {}, + "lakeformation:DescribeResource": {}, + "lakeformation:DescribeTransaction": {}, + "lakeformation:ExtendTransaction": {}, + "lakeformation:GetDataAccess": {}, + "lakeformation:GetDataCellsFilter": {}, + "lakeformation:GetDataLakeSettings": {}, + "lakeformation:GetEffectivePermissionsForPath": {}, + "lakeformation:GetLFTag": {}, + "lakeformation:GetQueryState": {}, + "lakeformation:GetQueryStatistics": {}, + "lakeformation:GetResourceLFTags": {}, + "lakeformation:GetTableObjects": {}, + "lakeformation:GetWorkUnitResults": {}, + "lakeformation:GetWorkUnits": {}, + "lakeformation:GrantPermissions": {}, + "lakeformation:ListDataCellsFilter": {}, + "lakeformation:ListLFTags": {}, + "lakeformation:ListLakeFormationOptIns": {}, + "lakeformation:ListPermissions": {}, + "lakeformation:ListResources": {}, + "lakeformation:ListTableStorageOptimizers": {}, + "lakeformation:ListTransactions": {}, + "lakeformation:PutDataLakeSettings": {}, + "lakeformation:RegisterResource": {}, + "lakeformation:RemoveLFTagsFromResource": {}, + "lakeformation:RevokePermissions": {}, + "lakeformation:SearchDatabasesByLFTags": {}, + "lakeformation:SearchTablesByLFTags": {}, + "lakeformation:StartQueryPlanning": {}, + "lakeformation:StartTransaction": {}, + "lakeformation:UpdateDataCellsFilter": {}, + "lakeformation:UpdateLFTag": {}, + "lakeformation:UpdateLakeFormationIdentityCenterConfiguration": {}, + "lakeformation:UpdateResource": {}, + "lakeformation:UpdateTableObjects": {}, + "lakeformation:UpdateTableStorageOptimizer": {}, + "lambda:CreateCodeSigningConfig": {}, + "lambda:CreateEventSourceMapping": {}, + "lambda:GetAccountSettings": {}, + "lambda:ListCodeSigningConfigs": {}, + "lambda:ListEventSourceMappings": {}, + "lambda:ListFunctions": {}, + "lambda:ListLayerVersions": {}, + "lambda:ListLayers": {}, + "launchwizard:CreateAdditionalNode": {}, + "launchwizard:CreateDeployment": {}, + "launchwizard:CreateSettingsSet": {}, + "launchwizard:DeleteAdditionalNode": {}, + "launchwizard:DeleteApp": {}, + "launchwizard:DeleteDeployment": {}, + "launchwizard:DeleteSettingsSet": {}, + "launchwizard:DescribeAdditionalNode": {}, + "launchwizard:DescribeProvisionedApp": {}, + "launchwizard:DescribeProvisioningEvents": {}, + "launchwizard:DescribeSettingsSet": {}, + "launchwizard:GetDeployment": {}, + "launchwizard:GetInfrastructureSuggestion": {}, + "launchwizard:GetIpAddress": {}, + "launchwizard:GetResourceCostEstimate": {}, + "launchwizard:GetResourceRecommendation": {}, + "launchwizard:GetSettingsSet": {}, + "launchwizard:GetWorkload": {}, + "launchwizard:GetWorkloadAsset": {}, + "launchwizard:GetWorkloadAssets": {}, + "launchwizard:ListAdditionalNodes": {}, + "launchwizard:ListAllowedResources": {}, + "launchwizard:ListDeploymentEvents": {}, + "launchwizard:ListDeployments": {}, + "launchwizard:ListProvisionedApps": {}, + "launchwizard:ListResourceCostEstimates": {}, + "launchwizard:ListSettingsSets": {}, + "launchwizard:ListWorkloadDeploymentOptions": {}, + "launchwizard:ListWorkloadDeploymentPatterns": {}, + "launchwizard:ListWorkloads": {}, + "launchwizard:PutSettingsSet": {}, + "launchwizard:StartProvisioning": {}, + "launchwizard:UpdateSettingsSet": {}, + "lex:CreateTestSet": {}, + "lex:CreateUploadUrl": {}, + "lex:GetBotAliases": {}, + "lex:GetBots": {}, + "lex:GetBuiltinIntent": {}, + "lex:GetBuiltinIntents": {}, + "lex:GetBuiltinSlotTypes": {}, + "lex:GetImport": {}, + "lex:GetIntents": {}, + "lex:GetMigration": {}, + "lex:GetMigrations": {}, + "lex:GetSlotTypes": {}, + "lex:ListBots": {}, + "lex:ListBuiltInIntents": {}, + "lex:ListBuiltInSlotTypes": {}, + "lex:ListExports": {}, + "lex:ListImports": {}, + "lex:ListTestExecutions": {}, + "lex:ListTestSets": {}, + "lex:StartImport": {}, + "license-manager-linux-subscriptions:GetServiceSettings": {}, + "license-manager-linux-subscriptions:ListLinuxSubscriptionInstances": {}, + "license-manager-linux-subscriptions:ListLinuxSubscriptions": {}, + "license-manager-linux-subscriptions:UpdateServiceSettings": {}, + "license-manager-user-subscriptions:AssociateUser": {}, + "license-manager-user-subscriptions:DeregisterIdentityProvider": {}, + "license-manager-user-subscriptions:DisassociateUser": {}, + "license-manager-user-subscriptions:ListIdentityProviders": {}, + "license-manager-user-subscriptions:ListInstances": {}, + "license-manager-user-subscriptions:ListProductSubscriptions": {}, + "license-manager-user-subscriptions:ListUserAssociations": {}, + "license-manager-user-subscriptions:RegisterIdentityProvider": {}, + "license-manager-user-subscriptions:StartProductSubscription": {}, + "license-manager-user-subscriptions:StopProductSubscription": {}, + "license-manager-user-subscriptions:UpdateIdentityProviderSettings": {}, + "license-manager:CheckInLicense": {}, + "license-manager:CheckoutLicense": {}, + "license-manager:CreateLicense": {}, + "license-manager:CreateLicenseConfiguration": {}, + "license-manager:CreateLicenseConversionTaskForResource": {}, + "license-manager:CreateLicenseManagerReportGenerator": {}, + "license-manager:DeleteToken": {}, + "license-manager:ExtendLicenseConsumption": {}, + "license-manager:GetAccessToken": {}, + "license-manager:GetLicenseConversionTask": {}, + "license-manager:GetServiceSettings": {}, + "license-manager:ListDistributedGrants": {}, + "license-manager:ListLicenseConfigurations": {}, + "license-manager:ListLicenseConversionTasks": {}, + "license-manager:ListLicenseSpecificationsForResource": {}, + "license-manager:ListLicenses": {}, + "license-manager:ListReceivedGrants": {}, + "license-manager:ListReceivedGrantsForOrganization": {}, + "license-manager:ListReceivedLicenses": {}, + "license-manager:ListReceivedLicensesForOrganization": {}, + "license-manager:ListResourceInventory": {}, + "license-manager:ListTokens": {}, + "license-manager:UpdateServiceSettings": {}, + "lightsail:AllocateStaticIp": {}, + "lightsail:CopySnapshot": {}, + "lightsail:CreateBucket": {}, + "lightsail:CreateCertificate": {}, + "lightsail:CreateCloudFormationStack": {}, + "lightsail:CreateContactMethod": {}, + "lightsail:CreateContainerService": {}, + "lightsail:CreateContainerServiceRegistryLogin": {}, + "lightsail:CreateDisk": {}, + "lightsail:CreateDistribution": {}, + "lightsail:CreateDomain": {}, + "lightsail:CreateInstances": {}, + "lightsail:CreateKeyPair": {}, + "lightsail:CreateLoadBalancer": {}, + "lightsail:CreateRelationalDatabase": {}, + "lightsail:CreateRelationalDatabaseSnapshot": {}, + "lightsail:DeleteAutoSnapshot": {}, + "lightsail:DeleteContactMethod": {}, + "lightsail:DisableAddOn": {}, + "lightsail:DownloadDefaultKeyPair": {}, + "lightsail:EnableAddOn": {}, + "lightsail:GetActiveNames": {}, + "lightsail:GetAlarms": {}, + "lightsail:GetAutoSnapshots": {}, + "lightsail:GetBlueprints": {}, + "lightsail:GetBucketAccessKeys": {}, + "lightsail:GetBucketBundles": {}, + "lightsail:GetBucketMetricData": {}, + "lightsail:GetBuckets": {}, + "lightsail:GetBundles": {}, + "lightsail:GetCertificates": {}, + "lightsail:GetCloudFormationStackRecords": {}, + "lightsail:GetContactMethods": {}, + "lightsail:GetContainerAPIMetadata": {}, + "lightsail:GetContainerImages": {}, + "lightsail:GetContainerLog": {}, + "lightsail:GetContainerServiceDeployments": {}, + "lightsail:GetContainerServiceMetricData": {}, + "lightsail:GetContainerServicePowers": {}, + "lightsail:GetContainerServices": {}, + "lightsail:GetDisk": {}, + "lightsail:GetDiskSnapshot": {}, + "lightsail:GetDiskSnapshots": {}, + "lightsail:GetDisks": {}, + "lightsail:GetDistributionBundles": {}, + "lightsail:GetDistributionLatestCacheReset": {}, + "lightsail:GetDistributionMetricData": {}, + "lightsail:GetDistributions": {}, + "lightsail:GetDomain": {}, + "lightsail:GetDomains": {}, + "lightsail:GetExportSnapshotRecords": {}, + "lightsail:GetInstance": {}, + "lightsail:GetInstanceMetricData": {}, + "lightsail:GetInstancePortStates": {}, + "lightsail:GetInstanceSnapshot": {}, + "lightsail:GetInstanceSnapshots": {}, + "lightsail:GetInstanceState": {}, + "lightsail:GetInstances": {}, + "lightsail:GetKeyPair": {}, + "lightsail:GetKeyPairs": {}, + "lightsail:GetLoadBalancer": {}, + "lightsail:GetLoadBalancerMetricData": {}, + "lightsail:GetLoadBalancerTlsCertificates": {}, + "lightsail:GetLoadBalancerTlsPolicies": {}, + "lightsail:GetLoadBalancers": {}, + "lightsail:GetOperation": {}, + "lightsail:GetOperations": {}, + "lightsail:GetOperationsForResource": {}, + "lightsail:GetRegions": {}, + "lightsail:GetRelationalDatabase": {}, + "lightsail:GetRelationalDatabaseBlueprints": {}, + "lightsail:GetRelationalDatabaseBundles": {}, + "lightsail:GetRelationalDatabaseEvents": {}, + "lightsail:GetRelationalDatabaseLogEvents": {}, + "lightsail:GetRelationalDatabaseLogStreams": {}, + "lightsail:GetRelationalDatabaseMetricData": {}, + "lightsail:GetRelationalDatabaseParameters": {}, + "lightsail:GetRelationalDatabaseSnapshot": {}, + "lightsail:GetRelationalDatabaseSnapshots": {}, + "lightsail:GetRelationalDatabases": {}, + "lightsail:GetStaticIp": {}, + "lightsail:GetStaticIps": {}, + "lightsail:ImportKeyPair": {}, + "lightsail:IsVpcPeered": {}, + "lightsail:PeerVpc": {}, + "lightsail:SendContactMethodVerification": {}, + "lightsail:UnpeerVpc": {}, + "logs:CancelExportTask": {}, + "logs:CreateLogDelivery": {}, + "logs:DeleteAccountPolicy": {}, + "logs:DeleteLogDelivery": {}, + "logs:DeleteQueryDefinition": {}, + "logs:DeleteResourcePolicy": {}, + "logs:DescribeAccountPolicies": {}, + "logs:DescribeDeliveries": {}, + "logs:DescribeDeliveryDestinations": {}, + "logs:DescribeDeliverySources": {}, + "logs:DescribeDestinations": {}, + "logs:DescribeExportTasks": {}, + "logs:DescribeLogGroups": {}, + "logs:DescribeQueries": {}, + "logs:DescribeQueryDefinitions": {}, + "logs:DescribeResourcePolicies": {}, + "logs:GetLogDelivery": {}, + "logs:Link": {}, + "logs:ListLogDeliveries": {}, + "logs:PutAccountPolicy": {}, + "logs:PutQueryDefinition": {}, + "logs:PutResourcePolicy": {}, + "logs:StopLiveTail": {}, + "logs:StopQuery": {}, + "logs:TestMetricFilter": {}, + "logs:UpdateLogDelivery": {}, + "lookoutequipment:DescribeDataIngestionJob": {}, + "lookoutequipment:ListDatasets": {}, + "lookoutequipment:ListInferenceSchedulers": {}, + "lookoutequipment:ListModels": {}, + "lookoutequipment:ListRetrainingSchedulers": {}, + "lookoutmetrics:GetSampleData": {}, + "lookoutmetrics:ListAnomalyDetectors": {}, + "lookoutvision:CreateDataset": {}, + "lookoutvision:DeleteDataset": {}, + "lookoutvision:DescribeDataset": {}, + "lookoutvision:DescribeModelPackagingJob": {}, + "lookoutvision:DescribeTrialDetection": {}, + "lookoutvision:ListDatasetEntries": {}, + "lookoutvision:ListModelPackagingJobs": {}, + "lookoutvision:ListModels": {}, + "lookoutvision:ListProjects": {}, + "lookoutvision:ListTrialDetections": {}, + "lookoutvision:StartTrialDetection": {}, + "lookoutvision:UpdateDatasetEntries": {}, + "m2:CreateApplication": {}, + "m2:CreateEnvironment": {}, + "m2:GetSignedBluinsightsUrl": {}, + "m2:ListApplications": {}, + "m2:ListEngineVersions": {}, + "m2:ListEnvironments": {}, + "m2:ListTagsForResource": {}, + "machinelearning:DescribeBatchPredictions": {}, + "machinelearning:DescribeDataSources": {}, + "machinelearning:DescribeEvaluations": {}, + "machinelearning:DescribeMLModels": {}, + "macie2:AcceptInvitation": {}, + "macie2:CreateAllowList": {}, + "macie2:CreateInvitations": {}, + "macie2:CreateSampleFindings": {}, + "macie2:DeclineInvitations": {}, + "macie2:DeleteInvitations": {}, + "macie2:DescribeBuckets": {}, + "macie2:DescribeOrganizationConfiguration": {}, + "macie2:DisableMacie": {}, + "macie2:DisableOrganizationAdminAccount": {}, + "macie2:DisassociateFromAdministratorAccount": {}, + "macie2:DisassociateFromMasterAccount": {}, + "macie2:EnableMacie": {}, + "macie2:EnableOrganizationAdminAccount": {}, + "macie2:GetAdministratorAccount": {}, + "macie2:GetAutomatedDiscoveryConfiguration": {}, + "macie2:GetBucketStatistics": {}, + "macie2:GetClassificationExportConfiguration": {}, + "macie2:GetClassificationScope": {}, + "macie2:GetFindingStatistics": {}, + "macie2:GetFindings": {}, + "macie2:GetFindingsPublicationConfiguration": {}, + "macie2:GetInvitationsCount": {}, + "macie2:GetMacieSession": {}, + "macie2:GetMasterAccount": {}, + "macie2:GetResourceProfile": {}, + "macie2:GetRevealConfiguration": {}, + "macie2:GetSensitiveDataOccurrences": {}, + "macie2:GetSensitiveDataOccurrencesAvailability": {}, + "macie2:GetSensitivityInspectionTemplate": {}, + "macie2:GetUsageStatistics": {}, + "macie2:GetUsageTotals": {}, + "macie2:ListAllowLists": {}, + "macie2:ListClassificationJobs": {}, + "macie2:ListClassificationScopes": {}, + "macie2:ListCustomDataIdentifiers": {}, + "macie2:ListFindings": {}, + "macie2:ListFindingsFilters": {}, + "macie2:ListInvitations": {}, + "macie2:ListManagedDataIdentifiers": {}, + "macie2:ListMembers": {}, + "macie2:ListOrganizationAdminAccounts": {}, + "macie2:ListResourceProfileArtifacts": {}, + "macie2:ListResourceProfileDetections": {}, + "macie2:ListSensitivityInspectionTemplates": {}, + "macie2:PutClassificationExportConfiguration": {}, + "macie2:PutFindingsPublicationConfiguration": {}, + "macie2:SearchResources": {}, + "macie2:TestCustomDataIdentifier": {}, + "macie2:UpdateAutomatedDiscoveryConfiguration": {}, + "macie2:UpdateClassificationScope": {}, + "macie2:UpdateMacieSession": {}, + "macie2:UpdateMemberSession": {}, + "macie2:UpdateOrganizationConfiguration": {}, + "macie2:UpdateResourceProfile": {}, + "macie2:UpdateResourceProfileDetections": {}, + "macie2:UpdateRevealConfiguration": {}, + "macie2:UpdateSensitivityInspectionTemplate": {}, + "managedblockchain-query:BatchGetTokenBalance": {}, + "managedblockchain-query:GetAssetContract": {}, + "managedblockchain-query:GetTokenBalance": {}, + "managedblockchain-query:GetTransaction": {}, + "managedblockchain-query:ListAssetContracts": {}, + "managedblockchain-query:ListTokenBalances": {}, + "managedblockchain-query:ListTransactionEvents": {}, + "managedblockchain-query:ListTransactions": {}, + "managedblockchain:CreateAccessor": {}, + "managedblockchain:CreateNetwork": {}, + "managedblockchain:GET": {}, + "managedblockchain:Invoke": {}, + "managedblockchain:InvokeRpcBitcoinMainnet": {}, + "managedblockchain:InvokeRpcBitcoinTestnet": {}, + "managedblockchain:InvokeRpcPolygonMainnet": {}, + "managedblockchain:InvokeRpcPolygonMumbaiTestnet": {}, + "managedblockchain:ListAccessors": {}, + "managedblockchain:ListInvitations": {}, + "managedblockchain:ListNetworks": {}, + "managedblockchain:POST": {}, + "mechanicalturk:AcceptQualificationRequest": {}, + "mechanicalturk:ApproveAssignment": {}, + "mechanicalturk:AssociateQualificationWithWorker": {}, + "mechanicalturk:CreateAdditionalAssignmentsForHIT": {}, + "mechanicalturk:CreateHIT": {}, + "mechanicalturk:CreateHITType": {}, + "mechanicalturk:CreateHITWithHITType": {}, + "mechanicalturk:CreateQualificationType": {}, + "mechanicalturk:CreateWorkerBlock": {}, + "mechanicalturk:DeleteHIT": {}, + "mechanicalturk:DeleteQualificationType": {}, + "mechanicalturk:DeleteWorkerBlock": {}, + "mechanicalturk:DisassociateQualificationFromWorker": {}, + "mechanicalturk:GetAccountBalance": {}, + "mechanicalturk:GetAssignment": {}, + "mechanicalturk:GetFileUploadURL": {}, + "mechanicalturk:GetHIT": {}, + "mechanicalturk:GetQualificationScore": {}, + "mechanicalturk:GetQualificationType": {}, + "mechanicalturk:ListAssignmentsForHIT": {}, + "mechanicalturk:ListBonusPayments": {}, + "mechanicalturk:ListHITs": {}, + "mechanicalturk:ListHITsForQualificationType": {}, + "mechanicalturk:ListQualificationRequests": {}, + "mechanicalturk:ListQualificationTypes": {}, + "mechanicalturk:ListReviewPolicyResultsForHIT": {}, + "mechanicalturk:ListReviewableHITs": {}, + "mechanicalturk:ListWorkerBlocks": {}, + "mechanicalturk:ListWorkersWithQualificationType": {}, + "mechanicalturk:NotifyWorkers": {}, + "mechanicalturk:RejectAssignment": {}, + "mechanicalturk:RejectQualificationRequest": {}, + "mechanicalturk:SendBonus": {}, + "mechanicalturk:SendTestEventNotification": {}, + "mechanicalturk:UpdateExpirationForHIT": {}, + "mechanicalturk:UpdateHITReviewStatus": {}, + "mechanicalturk:UpdateHITTypeOfHIT": {}, + "mechanicalturk:UpdateNotificationSettings": {}, + "mechanicalturk:UpdateQualificationType": {}, + "mediaconnect:AddFlowMediaStreams": {}, + "mediaconnect:AddFlowOutputs": {}, + "mediaconnect:AddFlowSources": {}, + "mediaconnect:AddFlowVpcInterfaces": {}, + "mediaconnect:CreateFlow": {}, + "mediaconnect:DeleteFlow": {}, + "mediaconnect:DescribeFlow": {}, + "mediaconnect:DescribeFlowSourceMetadata": {}, + "mediaconnect:DescribeOffering": {}, + "mediaconnect:DescribeReservation": {}, + "mediaconnect:DiscoverGatewayPollEndpoint": {}, + "mediaconnect:GrantFlowEntitlements": {}, + "mediaconnect:ListEntitlements": {}, + "mediaconnect:ListFlows": {}, + "mediaconnect:ListGateways": {}, + "mediaconnect:ListOfferings": {}, + "mediaconnect:ListReservations": {}, + "mediaconnect:ListTagsForResource": {}, + "mediaconnect:PollGateway": {}, + "mediaconnect:PurchaseOffering": {}, + "mediaconnect:RemoveFlowMediaStream": {}, + "mediaconnect:RemoveFlowOutput": {}, + "mediaconnect:RemoveFlowSource": {}, + "mediaconnect:RemoveFlowVpcInterface": {}, + "mediaconnect:RevokeFlowEntitlement": {}, + "mediaconnect:StartFlow": {}, + "mediaconnect:StopFlow": {}, + "mediaconnect:SubmitGatewayStateChange": {}, + "mediaconnect:TagResource": {}, + "mediaconnect:UntagResource": {}, + "mediaconnect:UpdateFlow": {}, + "mediaconnect:UpdateFlowEntitlement": {}, + "mediaconnect:UpdateFlowMediaStream": {}, + "mediaconnect:UpdateFlowOutput": {}, + "mediaconnect:UpdateFlowSource": {}, + "mediaconvert:AssociateCertificate": {}, + "mediaconvert:CreatePreset": {}, + "mediaconvert:CreateQueue": {}, + "mediaconvert:DeletePolicy": {}, + "mediaconvert:DescribeEndpoints": {}, + "mediaconvert:DisassociateCertificate": {}, + "mediaconvert:GetPolicy": {}, + "mediaconvert:ListJobTemplates": {}, + "mediaconvert:ListPresets": {}, + "mediaconvert:ListQueues": {}, + "mediaconvert:PutPolicy": {}, + "mediaimport:CreateDatabaseBinarySnapshot": {}, + "medialive:BatchDelete": {}, + "medialive:BatchStart": {}, + "medialive:BatchStop": {}, + "medialive:DescribeAccountConfiguration": {}, + "medialive:ListChannels": {}, + "medialive:ListInputDeviceTransfers": {}, + "medialive:ListInputDevices": {}, + "medialive:ListInputSecurityGroups": {}, + "medialive:ListInputs": {}, + "medialive:ListMultiplexPrograms": {}, + "medialive:ListMultiplexes": {}, + "medialive:ListOfferings": {}, + "medialive:ListReservations": {}, + "medialive:UpdateAccountConfiguration": {}, + "mediapackage-vod:CreateAsset": {}, + "mediapackage-vod:CreatePackagingConfiguration": {}, + "mediapackage-vod:CreatePackagingGroup": {}, + "mediapackage-vod:ListAssets": {}, + "mediapackage-vod:ListPackagingConfigurations": {}, + "mediapackage-vod:ListPackagingGroups": {}, + "mediapackage:CreateChannel": {}, + "mediapackage:CreateHarvestJob": {}, + "mediapackage:CreateOriginEndpoint": {}, + "mediapackage:ListChannels": {}, + "mediapackage:ListHarvestJobs": {}, + "mediapackage:ListOriginEndpoints": {}, + "mediapackagev2:ListChannelGroups": {}, + "mediastore:CreateContainer": {}, + "mediastore:ListContainers": {}, + "mediatailor:CreateChannel": {}, + "mediatailor:CreateLiveSource": {}, + "mediatailor:CreateProgram": {}, + "mediatailor:CreateSourceLocation": {}, + "mediatailor:CreateVodSource": {}, + "mediatailor:ListAlerts": {}, + "mediatailor:ListChannels": {}, + "mediatailor:ListLiveSources": {}, + "mediatailor:ListPlaybackConfigurations": {}, + "mediatailor:ListSourceLocations": {}, + "mediatailor:ListVodSources": {}, + "mediatailor:PutPlaybackConfiguration": {}, + "medical-imaging:CreateDatastore": {}, + "medical-imaging:ListDatastores": {}, + "memorydb:CreateParameterGroup": {}, + "memorydb:CreateSubnetGroup": {}, + "memorydb:CreateUser": {}, + "memorydb:DescribeEngineVersions": {}, + "memorydb:DescribeEvents": {}, + "memorydb:DescribeReservedNodesOfferings": {}, + "memorydb:DescribeServiceUpdates": {}, + "mgh:CreateHomeRegionControl": {}, + "mgh:DeleteHomeRegionControl": {}, + "mgh:DescribeApplicationState": {}, + "mgh:DescribeHomeRegionControls": {}, + "mgh:GetHomeRegion": {}, + "mgh:ListApplicationStates": {}, + "mgh:ListMigrationTasks": {}, + "mgh:ListProgressUpdateStreams": {}, + "mgh:NotifyApplicationState": {}, + "mgn:BatchDeleteSnapshotRequestForMgn": {}, + "mgn:CreateApplication": {}, + "mgn:CreateConnector": {}, + "mgn:CreateLaunchConfigurationTemplate": {}, + "mgn:CreateReplicationConfigurationTemplate": {}, + "mgn:CreateVcenterClientForMgn": {}, + "mgn:CreateWave": {}, + "mgn:DescribeJobs": {}, + "mgn:DescribeLaunchConfigurationTemplates": {}, + "mgn:DescribeReplicationConfigurationTemplates": {}, + "mgn:DescribeReplicationServerAssociationsForMgn": {}, + "mgn:DescribeSnapshotRequestsForMgn": {}, + "mgn:DescribeSourceServers": {}, + "mgn:DescribeVcenterClients": {}, + "mgn:GetAgentInstallationAssetsForMgn": {}, + "mgn:GetChannelCommandsForMgn": {}, + "mgn:InitializeService": {}, + "mgn:ListApplications": {}, + "mgn:ListConnectors": {}, + "mgn:ListExports": {}, + "mgn:ListImports": {}, + "mgn:ListManagedAccounts": {}, + "mgn:ListTagsForResource": {}, + "mgn:ListWaves": {}, + "mgn:RegisterAgentForMgn": {}, + "mgn:SendChannelCommandResultForMgn": {}, + "mgn:SendClientLogsForMgn": {}, + "mgn:SendClientMetricsForMgn": {}, + "mgn:StartExport": {}, + "mgn:StartImport": {}, + "mgn:VerifyClientRoleForMgn": {}, + "migrationhub-orchestrator:CreateWorkflow": {}, + "migrationhub-orchestrator:GetMessage": {}, + "migrationhub-orchestrator:GetTemplate": {}, + "migrationhub-orchestrator:GetTemplateStep": {}, + "migrationhub-orchestrator:GetTemplateStepGroup": {}, + "migrationhub-orchestrator:ListPlugins": {}, + "migrationhub-orchestrator:ListTemplateStepGroups": {}, + "migrationhub-orchestrator:ListTemplateSteps": {}, + "migrationhub-orchestrator:ListTemplates": {}, + "migrationhub-orchestrator:ListWorkflows": {}, + "migrationhub-orchestrator:RegisterPlugin": {}, + "migrationhub-orchestrator:SendMessage": {}, + "migrationhub-strategy:GetAntiPattern": {}, + "migrationhub-strategy:GetApplicationComponentDetails": {}, + "migrationhub-strategy:GetApplicationComponentStrategies": {}, + "migrationhub-strategy:GetAssessment": {}, + "migrationhub-strategy:GetImportFileTask": {}, + "migrationhub-strategy:GetLatestAssessmentId": {}, + "migrationhub-strategy:GetMessage": {}, + "migrationhub-strategy:GetPortfolioPreferences": {}, + "migrationhub-strategy:GetPortfolioSummary": {}, + "migrationhub-strategy:GetRecommendationReportDetails": {}, + "migrationhub-strategy:GetServerDetails": {}, + "migrationhub-strategy:GetServerStrategies": {}, + "migrationhub-strategy:ListAnalyzableServers": {}, + "migrationhub-strategy:ListAntiPatterns": {}, + "migrationhub-strategy:ListApplicationComponents": {}, + "migrationhub-strategy:ListCollectors": {}, + "migrationhub-strategy:ListImportFileTask": {}, + "migrationhub-strategy:ListJarArtifacts": {}, + "migrationhub-strategy:ListServers": {}, + "migrationhub-strategy:PutPortfolioPreferences": {}, + "migrationhub-strategy:RegisterCollector": {}, + "migrationhub-strategy:SendMessage": {}, + "migrationhub-strategy:StartAssessment": {}, + "migrationhub-strategy:StartImportFileTask": {}, + "migrationhub-strategy:StartRecommendationReportGeneration": {}, + "migrationhub-strategy:StopAssessment": {}, + "migrationhub-strategy:UpdateApplicationComponentConfig": {}, + "migrationhub-strategy:UpdateCollectorConfiguration": {}, + "migrationhub-strategy:UpdateServerConfig": {}, + "mobileanalytics:PutEvents": {}, + "monitron:CreateProject": {}, + "monitron:ListProjects": {}, + "mq:CreateBroker": {}, + "mq:CreateConfiguration": {}, + "mq:DescribeBrokerEngineTypes": {}, + "mq:DescribeBrokerInstanceOptions": {}, + "mq:ListBrokers": {}, + "mq:ListConfigurations": {}, + "network-firewall:ListRuleGroups": {}, + "networkmanager-chat:CancelMessageResponse": {}, + "networkmanager-chat:CreateConversation": {}, + "networkmanager-chat:DeleteConversation": {}, + "networkmanager-chat:ListConversationMessages": {}, + "networkmanager-chat:ListConversations": {}, + "networkmanager-chat:NotifyConversationIsActive": {}, + "networkmanager-chat:SendConversationMessage": {}, + "networkmanager:CreateGlobalNetwork": {}, + "networkmanager:ListCoreNetworks": {}, + "networkmanager:ListOrganizationServiceAccessStatus": {}, + "networkmanager:ListPeerings": {}, + "networkmanager:StartOrganizationServiceAccessUpdate": {}, + "networkmonitor:CreateProbe": {}, + "networkmonitor:ListMonitors": {}, + "nimble:GetFeatureMap": {}, + "nimble:ListStudios": {}, + "notifications-contacts:CreateEmailContact": {}, + "notifications-contacts:ListEmailContacts": {}, + "notifications-contacts:ListTagsForResource": {}, + "notifications:CreateEventRule": {}, + "notifications:CreateNotificationConfiguration": {}, + "notifications:DeregisterNotificationHub": {}, + "notifications:ListChannels": {}, + "notifications:ListEventRules": {}, + "notifications:ListNotificationConfigurations": {}, + "notifications:ListNotificationEvents": {}, + "notifications:ListNotificationHubs": {}, + "notifications:ListTagsForResource": {}, + "notifications:RegisterNotificationHub": {}, + "oam:CreateSink": {}, + "oam:ListLinks": {}, + "oam:ListSinks": {}, + "omics:AcceptShare": {}, + "omics:CreateAnnotationStore": {}, + "omics:CreateReferenceStore": {}, + "omics:CreateRunGroup": {}, + "omics:CreateSequenceStore": {}, + "omics:CreateShare": {}, + "omics:CreateVariantStore": {}, + "omics:CreateWorkflow": {}, + "omics:DeleteShare": {}, + "omics:GetShare": {}, + "omics:ListAnnotationImportJobs": {}, + "omics:ListAnnotationStores": {}, + "omics:ListReferenceStores": {}, + "omics:ListRunGroups": {}, + "omics:ListRuns": {}, + "omics:ListSequenceStores": {}, + "omics:ListShares": {}, + "omics:ListTagsForResource": {}, + "omics:ListVariantImportJobs": {}, + "omics:ListVariantStores": {}, + "omics:ListWorkflows": {}, + "omics:StartAnnotationImportJob": {}, + "omics:StartRun": {}, + "omics:StartVariantImportJob": {}, + "one:CreateDeviceConfigurationTemplate": {}, + "one:CreateDeviceInstance": {}, + "one:CreateSite": {}, + "one:ListDeviceConfigurationTemplates": {}, + "one:ListDeviceInstances": {}, + "one:ListSites": {}, + "one:ListUsers": {}, + "opsworks-cm:AssociateNode": {}, + "opsworks-cm:CreateBackup": {}, + "opsworks-cm:CreateServer": {}, + "opsworks-cm:DeleteBackup": {}, + "opsworks-cm:DeleteServer": {}, + "opsworks-cm:DescribeAccountAttributes": {}, + "opsworks-cm:DescribeBackups": {}, + "opsworks-cm:DescribeEvents": {}, + "opsworks-cm:DescribeNodeAssociationStatus": {}, + "opsworks-cm:DescribeServers": {}, + "opsworks-cm:DisassociateNode": {}, + "opsworks-cm:ExportServerEngineAttribute": {}, + "opsworks-cm:ListTagsForResource": {}, + "opsworks-cm:RestoreServer": {}, + "opsworks-cm:StartMaintenance": {}, + "opsworks-cm:TagResource": {}, + "opsworks-cm:UntagResource": {}, + "opsworks-cm:UpdateServer": {}, + "opsworks-cm:UpdateServerEngineAttributes": {}, + "opsworks:CreateStack": {}, + "opsworks:CreateUserProfile": {}, + "opsworks:DeleteUserProfile": {}, + "opsworks:DescribeMyUserProfile": {}, + "opsworks:DescribeOperatingSystems": {}, + "opsworks:DescribeUserProfiles": {}, + "opsworks:UpdateMyUserProfile": {}, + "opsworks:UpdateUserProfile": {}, + "organizations:CreateAccount": {}, + "organizations:CreateGovCloudAccount": {}, + "organizations:CreateOrganization": {}, + "organizations:CreatePolicy": {}, + "organizations:DeleteOrganization": {}, + "organizations:DeleteResourcePolicy": {}, + "organizations:DescribeCreateAccountStatus": {}, + "organizations:DescribeOrganization": {}, + "organizations:DescribeResourcePolicy": {}, + "organizations:DisableAWSServiceAccess": {}, + "organizations:EnableAWSServiceAccess": {}, + "organizations:EnableAllFeatures": {}, + "organizations:LeaveOrganization": {}, + "organizations:ListAWSServiceAccessForOrganization": {}, + "organizations:ListAccounts": {}, + "organizations:ListCreateAccountStatus": {}, + "organizations:ListDelegatedAdministrators": {}, + "organizations:ListHandshakesForAccount": {}, + "organizations:ListHandshakesForOrganization": {}, + "organizations:ListPolicies": {}, + "organizations:ListRoots": {}, + "osis:CreatePipeline": {}, + "osis:ListPipelineBlueprints": {}, + "osis:ListPipelines": {}, + "osis:ValidatePipeline": {}, + "outposts:CancelOrder": {}, + "outposts:CreatePrivateConnectivityConfig": {}, + "outposts:CreateSite": {}, + "outposts:GetCatalogItem": {}, + "outposts:GetConnection": {}, + "outposts:GetOrder": {}, + "outposts:GetPrivateConnectivityConfig": {}, + "outposts:ListAssets": {}, + "outposts:ListCatalogItems": {}, + "outposts:ListOrders": {}, + "outposts:ListOutposts": {}, + "outposts:ListSites": {}, + "outposts:ListTagsForResource": {}, + "outposts:StartConnection": {}, + "panorama:CreateApplicationInstance": {}, + "panorama:CreateJobForDevices": {}, + "panorama:CreateNodeFromTemplateJob": {}, + "panorama:CreatePackage": {}, + "panorama:CreatePackageImportJob": {}, + "panorama:DescribeDeviceJob": {}, + "panorama:DescribeNode": {}, + "panorama:DescribeNodeFromTemplateJob": {}, + "panorama:DescribePackageImportJob": {}, + "panorama:DescribeSoftware": {}, + "panorama:GetWebSocketURL": {}, + "panorama:ListDevices": {}, + "panorama:ListNodeFromTemplateJobs": {}, + "panorama:ListNodes": {}, + "panorama:ListPackageImportJobs": {}, + "panorama:ListPackages": {}, + "panorama:ProvisionDevice": {}, + "partnercentral-account-management:AssociatePartnerAccount": {}, + "partnercentral-account-management:AssociatePartnerUser": {}, + "partnercentral-account-management:DisassociatePartnerUser": {}, + "payment-cryptography:CreateKey": {}, + "payment-cryptography:DecryptData": {}, + "payment-cryptography:EncryptData": {}, + "payment-cryptography:GenerateCardValidationData": {}, + "payment-cryptography:GenerateMac": {}, + "payment-cryptography:GeneratePinData": {}, + "payment-cryptography:GetParametersForExport": {}, + "payment-cryptography:GetParametersForImport": {}, + "payment-cryptography:ImportKey": {}, + "payment-cryptography:ListAliases": {}, + "payment-cryptography:ListKeys": {}, + "payment-cryptography:ReEncryptData": {}, + "payment-cryptography:TranslatePinData": {}, + "payment-cryptography:VerifyAuthRequestCryptogram": {}, + "payment-cryptography:VerifyCardValidationData": {}, + "payment-cryptography:VerifyMac": {}, + "payment-cryptography:VerifyPinData": {}, + "payments:CreatePaymentInstrument": {}, + "payments:DeletePaymentInstrument": {}, + "payments:GetPaymentInstrument": {}, + "payments:GetPaymentStatus": {}, + "payments:ListPaymentPreferences": {}, + "payments:MakePayment": {}, + "payments:UpdatePaymentPreferences": {}, + "pca-connector-ad:CreateConnector": {}, + "pca-connector-ad:CreateDirectoryRegistration": {}, + "pca-connector-ad:ListConnectors": {}, + "pca-connector-ad:ListDirectoryRegistrations": {}, + "pca-connector-ad:ListTagsForResource": {}, + "personalize:ListBatchInferenceJobs": {}, + "personalize:ListBatchSegmentJobs": {}, + "personalize:ListCampaigns": {}, + "personalize:ListDataInsightsJobs": {}, + "personalize:ListDatasetExportJobs": {}, + "personalize:ListDatasetGroups": {}, + "personalize:ListDatasetImportJobs": {}, + "personalize:ListDatasets": {}, + "personalize:ListEventTrackers": {}, + "personalize:ListFilters": {}, + "personalize:ListMetricAttributionMetrics": {}, + "personalize:ListMetricAttributions": {}, + "personalize:ListRecipes": {}, + "personalize:ListRecommenders": {}, + "personalize:ListSchemas": {}, + "personalize:ListSolutionVersions": {}, + "personalize:ListSolutions": {}, + "personalize:ListTagsForResource": {}, + "personalize:PutActionInteractions": {}, + "personalize:PutEvents": {}, + "personalize:TagResource": {}, + "personalize:UntagResource": {}, + "pipes:ListPipes": {}, + "polly:DescribeVoices": {}, + "polly:GetSpeechSynthesisTask": {}, + "polly:ListLexicons": {}, + "polly:ListSpeechSynthesisTasks": {}, + "pricing:DescribeServices": {}, + "pricing:GetAttributeValues": {}, + "pricing:GetPriceListFileUrl": {}, + "pricing:GetProducts": {}, + "pricing:ListPriceLists": {}, + "private-networks:ListNetworks": {}, + "private-networks:ListTagsForResource": {}, + "private-networks:Ping": {}, + "profile:GetProfileObjectTypeTemplate": {}, + "profile:ListAccountIntegrations": {}, + "profile:ListDomains": {}, + "profile:ListProfileObjectTypeTemplates": {}, + "proton:CreateEnvironmentAccountConnection": {}, + "proton:CreateServiceSyncConfig": {}, + "proton:CreateTemplateSyncConfig": {}, + "proton:DeleteAccountRoles": {}, + "proton:DeleteServiceSyncConfig": {}, + "proton:DeleteTemplateSyncConfig": {}, + "proton:GetAccountRoles": {}, + "proton:GetAccountSettings": {}, + "proton:GetRepositorySyncStatus": {}, + "proton:GetResourceTemplateVersionStatusCounts": {}, + "proton:GetResourcesSummary": {}, + "proton:GetServiceInstanceSyncStatus": {}, + "proton:GetServiceSyncBlockerSummary": {}, + "proton:GetServiceSyncConfig": {}, + "proton:GetTemplateSyncConfig": {}, + "proton:GetTemplateSyncStatus": {}, + "proton:ListDeployments": {}, + "proton:ListEnvironmentAccountConnections": {}, + "proton:ListEnvironmentTemplates": {}, + "proton:ListEnvironments": {}, + "proton:ListRepositories": {}, + "proton:ListRepositorySyncDefinitions": {}, + "proton:ListServiceInstances": {}, + "proton:ListServiceTemplates": {}, + "proton:ListServices": {}, + "proton:UpdateAccountRoles": {}, + "proton:UpdateAccountSettings": {}, + "proton:UpdateServiceSyncBlocker": {}, + "proton:UpdateServiceSyncConfig": {}, + "proton:UpdateTemplateSyncConfig": {}, + "purchase-orders:GetConsoleActionSetEnforced": {}, + "purchase-orders:ListPurchaseOrders": {}, + "purchase-orders:UpdateConsoleActionSetEnforced": {}, + "q:GetConversation": {}, + "q:GetTroubleshootingResults": {}, + "q:SendMessage": {}, + "q:StartConversation": {}, + "q:StartTroubleshootingAnalysis": {}, + "q:StartTroubleshootingResolutionExplanation": {}, + "qbusiness:AddUserLicenses": {}, + "qbusiness:CreateApplication": {}, + "qbusiness:CreateLicense": {}, + "qbusiness:ListApplications": {}, + "qbusiness:ListUserLicenses": {}, + "qbusiness:RemoveUserLicenses": {}, + "qldb:ListJournalS3Exports": {}, + "qldb:ListLedgers": {}, + "quicksight:AccountConfigurations": {}, + "quicksight:CreateAccountCustomization": {}, + "quicksight:CreateAccountSubscription": {}, + "quicksight:CreateCustomPermissions": {}, + "quicksight:CreateDataSource": {}, + "quicksight:CreateRoleMembership": {}, + "quicksight:CreateVPCConnection": {}, + "quicksight:DeleteCustomPermissions": {}, + "quicksight:DeleteIdentityPropagationConfig": {}, + "quicksight:DeleteRoleCustomPermission": {}, + "quicksight:DeleteRoleMembership": {}, + "quicksight:DescribeAccountSettings": {}, + "quicksight:DescribeCustomPermissions": {}, + "quicksight:DescribeIpRestriction": {}, + "quicksight:DescribeRoleCustomPermission": {}, + "quicksight:GetAnonymousUserEmbedUrl": {}, + "quicksight:GetGroupMapping": {}, + "quicksight:GetSessionEmbedUrl": {}, + "quicksight:ListCustomPermissions": {}, + "quicksight:ListCustomerManagedKeys": {}, + "quicksight:ListDataSets": {}, + "quicksight:ListDataSources": {}, + "quicksight:ListIdentityPropagationConfigs": {}, + "quicksight:ListIngestions": {}, + "quicksight:ListKMSKeysForUser": {}, + "quicksight:ListNamespaces": {}, + "quicksight:ListRefreshSchedules": {}, + "quicksight:ListRoleMemberships": {}, + "quicksight:ListTopicRefreshSchedules": {}, + "quicksight:ListTopics": {}, + "quicksight:ListVPCConnections": {}, + "quicksight:RegisterCustomerManagedKey": {}, + "quicksight:RemoveCustomerManagedKey": {}, + "quicksight:ScopeDownPolicy": {}, + "quicksight:SearchDirectoryGroups": {}, + "quicksight:SetGroupMapping": {}, + "quicksight:Subscribe": {}, + "quicksight:Unsubscribe": {}, + "quicksight:UpdateAccountSettings": {}, + "quicksight:UpdateCustomPermissions": {}, + "quicksight:UpdateIdentityPropagationConfig": {}, + "quicksight:UpdateIpRestriction": {}, + "quicksight:UpdatePublicSharingSettings": {}, + "quicksight:UpdateResourcePermissions": {}, + "quicksight:UpdateRoleCustomPermission": {}, + "ram:CreatePermission": {}, + "ram:CreateResourceShare": {}, + "ram:EnableSharingWithAwsOrganization": {}, + "ram:GetResourcePolicies": {}, + "ram:GetResourceShareAssociations": {}, + "ram:GetResourceShareInvitations": {}, + "ram:GetResourceShares": {}, + "ram:ListPermissionVersions": {}, + "ram:ListPermissions": {}, + "ram:ListPrincipals": {}, + "ram:ListReplacePermissionAssociationsWork": {}, + "ram:ListResourceTypes": {}, + "ram:ListResources": {}, + "rbin:ListRules": {}, + "rds:CancelExportTask": {}, + "rds:CreateDBProxy": {}, + "rds:CrossRegionCommunication": {}, + "rds:DescribeAccountAttributes": {}, + "rds:DescribeCertificates": {}, + "rds:DescribeDBEngineVersions": {}, + "rds:DescribeDBRecommendations": {}, + "rds:DescribeEngineDefaultClusterParameters": {}, + "rds:DescribeEngineDefaultParameters": {}, + "rds:DescribeEventCategories": {}, + "rds:DescribeEvents": {}, + "rds:DescribeExportTasks": {}, + "rds:DescribeOrderableDBInstanceOptions": {}, + "rds:DescribeRecommendationGroups": {}, + "rds:DescribeRecommendations": {}, + "rds:DescribeReservedDBInstancesOfferings": {}, + "rds:DescribeSourceRegions": {}, + "rds:ModifyCertificates": {}, + "rds:ModifyDBRecommendation": {}, + "rds:ModifyRecommendation": {}, + "rds:StartExportTask": {}, + "redshift-data:CancelStatement": {}, + "redshift-data:DescribeStatement": {}, + "redshift-data:GetStatementResult": {}, + "redshift-data:ListStatements": {}, + "redshift-serverless:CreateUsageLimit": {}, + "redshift-serverless:DeleteResourcePolicy": {}, + "redshift-serverless:DeleteScheduledAction": {}, + "redshift-serverless:DeleteSnapshotCopyConfiguration": {}, + "redshift-serverless:DeleteUsageLimit": {}, + "redshift-serverless:GetResourcePolicy": {}, + "redshift-serverless:GetScheduledAction": {}, + "redshift-serverless:GetTableRestoreStatus": {}, + "redshift-serverless:GetUsageLimit": {}, + "redshift-serverless:ListCustomDomainAssociations": {}, + "redshift-serverless:ListNamespaces": {}, + "redshift-serverless:ListScheduledActions": {}, + "redshift-serverless:ListTableRestoreStatus": {}, + "redshift-serverless:ListUsageLimits": {}, + "redshift-serverless:ListWorkgroups": {}, + "redshift-serverless:PutResourcePolicy": {}, + "redshift-serverless:UpdateScheduledAction": {}, + "redshift-serverless:UpdateSnapshotCopyConfiguration": {}, + "redshift-serverless:UpdateUsageLimit": {}, + "redshift-serverless:span": {}, + "redshift:AcceptReservedNodeExchange": {}, + "redshift:AddPartner": {}, + "redshift:AuthorizeEndpointAccess": {}, + "redshift:CancelQuery": {}, + "redshift:CancelQuerySession": {}, + "redshift:CreateAuthenticationProfile": {}, + "redshift:CreateEndpointAccess": {}, + "redshift:CreateRedshiftIdcApplication": {}, + "redshift:CreateSavedQuery": {}, + "redshift:CreateScheduledAction": {}, + "redshift:DeleteAuthenticationProfile": {}, + "redshift:DeleteEndpointAccess": {}, + "redshift:DeletePartner": {}, + "redshift:DeleteSavedQueries": {}, + "redshift:DeleteScheduledAction": {}, + "redshift:DescribeAccountAttributes": {}, + "redshift:DescribeAuthenticationProfiles": {}, + "redshift:DescribeClusterDbRevisions": {}, + "redshift:DescribeClusterParameterGroups": {}, + "redshift:DescribeClusterSecurityGroups": {}, + "redshift:DescribeClusterSnapshots": {}, + "redshift:DescribeClusterSubnetGroups": {}, + "redshift:DescribeClusterTracks": {}, + "redshift:DescribeClusterVersions": {}, + "redshift:DescribeClusters": {}, + "redshift:DescribeCustomDomainAssociations": {}, + "redshift:DescribeDataShares": {}, + "redshift:DescribeDataSharesForConsumer": {}, + "redshift:DescribeDataSharesForProducer": {}, + "redshift:DescribeDefaultClusterParameters": {}, + "redshift:DescribeEndpointAccess": {}, + "redshift:DescribeEndpointAuthorization": {}, + "redshift:DescribeEventCategories": {}, + "redshift:DescribeEventSubscriptions": {}, + "redshift:DescribeEvents": {}, + "redshift:DescribeHsmClientCertificates": {}, + "redshift:DescribeHsmConfigurations": {}, + "redshift:DescribeInboundIntegrations": {}, + "redshift:DescribeNodeConfigurationOptions": {}, + "redshift:DescribeOrderableClusterOptions": {}, + "redshift:DescribePartners": {}, + "redshift:DescribeQuery": {}, + "redshift:DescribeReservedNodeExchangeStatus": {}, + "redshift:DescribeReservedNodeOfferings": {}, + "redshift:DescribeReservedNodes": {}, + "redshift:DescribeSavedQueries": {}, + "redshift:DescribeScheduledActions": {}, + "redshift:DescribeSnapshotCopyGrants": {}, + "redshift:DescribeStorage": {}, + "redshift:DescribeTable": {}, + "redshift:DescribeTableRestoreStatus": {}, + "redshift:ExecuteQuery": {}, + "redshift:FetchResults": {}, + "redshift:GetReservedNodeExchangeConfigurationOptions": {}, + "redshift:GetReservedNodeExchangeOfferings": {}, + "redshift:ListDatabases": {}, + "redshift:ListRecommendations": {}, + "redshift:ListSavedQueries": {}, + "redshift:ListSchemas": {}, + "redshift:ListTables": {}, + "redshift:ModifyAuthenticationProfile": {}, + "redshift:ModifyClusterMaintenance": {}, + "redshift:ModifyEndpointAccess": {}, + "redshift:ModifySavedQuery": {}, + "redshift:ModifyScheduledAction": {}, + "redshift:PurchaseReservedNodeOffering": {}, + "redshift:RevokeEndpointAccess": {}, + "redshift:UpdatePartnerStatus": {}, + "redshift:ViewQueriesFromConsole": {}, + "redshift:ViewQueriesInConsole": {}, + "refactor-spaces:CreateApplication": {}, + "refactor-spaces:CreateEnvironment": {}, + "refactor-spaces:CreateRoute": {}, + "refactor-spaces:CreateService": {}, + "refactor-spaces:DeleteResourcePolicy": {}, + "refactor-spaces:GetResourcePolicy": {}, + "refactor-spaces:ListEnvironments": {}, + "refactor-spaces:ListTagsForResource": {}, + "refactor-spaces:PutResourcePolicy": {}, + "rekognition:CompareFaces": {}, + "rekognition:CreateFaceLivenessSession": {}, + "rekognition:DescribeProjects": {}, + "rekognition:DetectFaces": {}, + "rekognition:DetectLabels": {}, + "rekognition:DetectProtectiveEquipment": {}, + "rekognition:DetectText": {}, + "rekognition:GetCelebrityInfo": {}, + "rekognition:GetCelebrityRecognition": {}, + "rekognition:GetContentModeration": {}, + "rekognition:GetFaceDetection": {}, + "rekognition:GetFaceLivenessSessionResults": {}, + "rekognition:GetFaceSearch": {}, + "rekognition:GetLabelDetection": {}, + "rekognition:GetMediaAnalysisJob": {}, + "rekognition:GetPersonTracking": {}, + "rekognition:GetSegmentDetection": {}, + "rekognition:GetTextDetection": {}, + "rekognition:ListCollections": {}, + "rekognition:ListMediaAnalysisJobs": {}, + "rekognition:RecognizeCelebrities": {}, + "rekognition:StartCelebrityRecognition": {}, + "rekognition:StartContentModeration": {}, + "rekognition:StartFaceDetection": {}, + "rekognition:StartFaceLivenessSession": {}, + "rekognition:StartLabelDetection": {}, + "rekognition:StartPersonTracking": {}, + "rekognition:StartSegmentDetection": {}, + "rekognition:StartTextDetection": {}, + "repostspace:CreateSpace": {}, + "repostspace:ListSpaces": {}, + "resiliencehub:CreateApp": {}, + "resiliencehub:CreateResiliencyPolicy": {}, + "resiliencehub:ListAppAssessments": {}, + "resiliencehub:ListApps": {}, + "resiliencehub:ListResiliencyPolicies": {}, + "resiliencehub:ListSuggestedResiliencyPolicies": {}, + "resiliencehub:ListTagsForResource": {}, + "resource-explorer-2:BatchGetView": {}, + "resource-explorer-2:CreateIndex": {}, + "resource-explorer-2:CreateView": {}, + "resource-explorer-2:DisassociateDefaultView": {}, + "resource-explorer-2:GetAccountLevelServiceConfiguration": {}, + "resource-explorer-2:GetDefaultView": {}, + "resource-explorer-2:GetIndex": {}, + "resource-explorer-2:ListIndexes": {}, + "resource-explorer-2:ListIndexesForMembers": {}, + "resource-explorer-2:ListSupportedResourceTypes": {}, + "resource-explorer-2:ListViews": {}, + "resource-explorer:ListResourceTypes": {}, + "resource-explorer:ListResources": {}, + "resource-explorer:ListTags": {}, + "resource-groups:CreateGroup": {}, + "resource-groups:GetAccountSettings": {}, + "resource-groups:ListGroups": {}, + "resource-groups:SearchResources": {}, + "resource-groups:UpdateAccountSettings": {}, + "rhelkb:GetRhelURL": {}, + "robomaker:BatchDeleteWorlds": {}, + "robomaker:BatchDescribeSimulationJob": {}, + "robomaker:CreateDeploymentJob": {}, + "robomaker:CreateFleet": {}, + "robomaker:CreateRobot": {}, + "robomaker:CreateRobotApplication": {}, + "robomaker:CreateSimulationApplication": {}, + "robomaker:CreateSimulationJob": {}, + "robomaker:CreateWorldTemplate": {}, + "robomaker:ListDeploymentJobs": {}, + "robomaker:ListFleets": {}, + "robomaker:ListRobotApplications": {}, + "robomaker:ListRobots": {}, + "robomaker:ListSimulationApplications": {}, + "robomaker:ListSimulationJobBatches": {}, + "robomaker:ListSimulationJobs": {}, + "robomaker:ListWorldExportJobs": {}, + "robomaker:ListWorldGenerationJobs": {}, + "robomaker:ListWorldTemplates": {}, + "robomaker:ListWorlds": {}, + "robomaker:StartSimulationJobBatch": {}, + "rolesanywhere:CreateProfile": {}, + "rolesanywhere:CreateTrustAnchor": {}, + "rolesanywhere:ImportCrl": {}, + "rolesanywhere:ListCrls": {}, + "rolesanywhere:ListProfiles": {}, + "rolesanywhere:ListSubjects": {}, + "rolesanywhere:ListTagsForResource": {}, + "rolesanywhere:ListTrustAnchors": {}, + "route53-recovery-cluster:ListRoutingControls": {}, + "route53-recovery-control-config:ListAssociatedRoute53HealthChecks": {}, + "route53-recovery-control-config:ListClusters": {}, + "route53-recovery-control-config:ListControlPanels": {}, + "route53-recovery-control-config:ListRoutingControls": {}, + "route53-recovery-control-config:ListTagsForResource": {}, + "route53-recovery-readiness:CreateCrossAccountAuthorization": {}, + "route53-recovery-readiness:DeleteCrossAccountAuthorization": {}, + "route53-recovery-readiness:ListCells": {}, + "route53-recovery-readiness:ListCrossAccountAuthorizations": {}, + "route53-recovery-readiness:ListReadinessChecks": {}, + "route53-recovery-readiness:ListRecoveryGroups": {}, + "route53-recovery-readiness:ListResourceSets": {}, + "route53-recovery-readiness:ListRules": {}, + "route53-recovery-readiness:ListTagsForResources": {}, + "route53:CreateCidrCollection": {}, + "route53:CreateHealthCheck": {}, + "route53:CreateHostedZone": {}, + "route53:CreateReusableDelegationSet": {}, + "route53:CreateTrafficPolicy": {}, + "route53:GetAccountLimit": {}, + "route53:GetCheckerIpRanges": {}, + "route53:GetGeoLocation": {}, + "route53:GetHealthCheckCount": {}, + "route53:GetHostedZoneCount": {}, + "route53:GetTrafficPolicyInstanceCount": {}, + "route53:ListCidrCollections": {}, + "route53:ListGeoLocations": {}, + "route53:ListHealthChecks": {}, + "route53:ListHostedZones": {}, + "route53:ListHostedZonesByName": {}, + "route53:ListHostedZonesByVPC": {}, + "route53:ListReusableDelegationSets": {}, + "route53:ListTrafficPolicies": {}, + "route53:ListTrafficPolicyInstances": {}, + "route53:TestDNSAnswer": {}, + "route53domains:AcceptDomainTransferFromAnotherAwsAccount": {}, + "route53domains:AssociateDelegationSignerToDomain": {}, + "route53domains:CancelDomainTransferToAnotherAwsAccount": {}, + "route53domains:CheckDomainAvailability": {}, + "route53domains:CheckDomainTransferability": {}, + "route53domains:DeleteDomain": {}, + "route53domains:DeleteTagsForDomain": {}, + "route53domains:DisableDomainAutoRenew": {}, + "route53domains:DisableDomainTransferLock": {}, + "route53domains:DisassociateDelegationSignerFromDomain": {}, + "route53domains:EnableDomainAutoRenew": {}, + "route53domains:EnableDomainTransferLock": {}, + "route53domains:GetContactReachabilityStatus": {}, + "route53domains:GetDomainDetail": {}, + "route53domains:GetDomainSuggestions": {}, + "route53domains:GetOperationDetail": {}, + "route53domains:ListDomains": {}, + "route53domains:ListOperations": {}, + "route53domains:ListPrices": {}, + "route53domains:ListTagsForDomain": {}, + "route53domains:PushDomain": {}, + "route53domains:RegisterDomain": {}, + "route53domains:RejectDomainTransferFromAnotherAwsAccount": {}, + "route53domains:RenewDomain": {}, + "route53domains:ResendContactReachabilityEmail": {}, + "route53domains:ResendOperationAuthorization": {}, + "route53domains:RetrieveDomainAuthCode": {}, + "route53domains:TransferDomain": {}, + "route53domains:TransferDomainToAnotherAwsAccount": {}, + "route53domains:UpdateDomainContact": {}, + "route53domains:UpdateDomainContactPrivacy": {}, + "route53domains:UpdateDomainNameservers": {}, + "route53domains:UpdateTagsForDomain": {}, + "route53domains:ViewBilling": {}, + "route53resolver:CreateResolverQueryLogConfig": {}, + "route53resolver:GetResolverQueryLogConfigAssociation": {}, + "route53resolver:ListFirewallConfigs": {}, + "route53resolver:ListFirewallDomainLists": {}, + "route53resolver:ListFirewallRuleGroupAssociations": {}, + "route53resolver:ListFirewallRuleGroups": {}, + "route53resolver:ListOutpostResolvers": {}, + "route53resolver:ListResolverEndpoints": {}, + "route53resolver:ListResolverQueryLogConfigAssociations": {}, + "route53resolver:ListResolverQueryLogConfigs": {}, + "route53resolver:ListResolverRuleAssociations": {}, + "route53resolver:ListResolverRules": {}, + "rum:ListAppMonitors": {}, + "rum:ListTagsForResource": {}, + "s3-outposts:GetAccessPoint": {}, + "s3-outposts:ListAccessPoints": {}, + "s3-outposts:ListEndpoints": {}, + "s3-outposts:ListOutpostsWithS3": {}, + "s3-outposts:ListRegionalBuckets": {}, + "s3-outposts:ListSharedEndpoints": {}, + "s3:CreateJob": {}, + "s3:CreateStorageLensGroup": {}, + "s3:GetAccessPoint": {}, + "s3:GetAccountPublicAccessBlock": {}, + "s3:ListAccessGrantsInstances": {}, + "s3:ListAccessPoints": {}, + "s3:ListAccessPointsForObjectLambda": {}, + "s3:ListAllMyBuckets": {}, + "s3:ListJobs": {}, + "s3:ListMultiRegionAccessPoints": {}, + "s3:ListStorageLensConfigurations": {}, + "s3:ListStorageLensGroups": {}, + "s3:PutAccessPointPublicAccessBlock": {}, + "s3:PutAccountPublicAccessBlock": {}, + "s3:PutStorageLensConfiguration": {}, + "s3express:ListAllMyDirectoryBuckets": {}, + "sagemaker-geospatial:ListEarthObservationJobs": {}, + "sagemaker-geospatial:ListRasterDataCollections": {}, + "sagemaker-geospatial:ListVectorEnrichmentJobs": {}, + "sagemaker-geospatial:SearchRasterDataCollection": {}, + "sagemaker-groundtruth-synthetic:CreateProject": {}, + "sagemaker-groundtruth-synthetic:DeleteProject": {}, + "sagemaker-groundtruth-synthetic:GetAccountDetails": {}, + "sagemaker-groundtruth-synthetic:GetBatch": {}, + "sagemaker-groundtruth-synthetic:GetProject": {}, + "sagemaker-groundtruth-synthetic:ListBatchDataTransfers": {}, + "sagemaker-groundtruth-synthetic:ListBatchSummaries": {}, + "sagemaker-groundtruth-synthetic:ListProjectDataTransfers": {}, + "sagemaker-groundtruth-synthetic:ListProjectSummaries": {}, + "sagemaker-groundtruth-synthetic:StartBatchDataTransfer": {}, + "sagemaker-groundtruth-synthetic:StartProjectDataTransfer": {}, + "sagemaker-groundtruth-synthetic:UpdateBatch": {}, + "sagemaker:CreateLineageGroupPolicy": {}, + "sagemaker:DeleteLineageGroupPolicy": {}, + "sagemaker:DescribeLineageGroup": {}, + "sagemaker:DisableSagemakerServicecatalogPortfolio": {}, + "sagemaker:EnableSagemakerServicecatalogPortfolio": {}, + "sagemaker:GetLineageGroupPolicy": {}, + "sagemaker:GetSagemakerServicecatalogPortfolioStatus": {}, + "sagemaker:GetSearchSuggestions": {}, + "sagemaker:ListActions": {}, + "sagemaker:ListAlgorithms": {}, + "sagemaker:ListAppImageConfigs": {}, + "sagemaker:ListApps": {}, + "sagemaker:ListArtifacts": {}, + "sagemaker:ListAssociations": {}, + "sagemaker:ListAutoMLJobs": {}, + "sagemaker:ListCandidatesForAutoMLJob": {}, + "sagemaker:ListClusters": {}, + "sagemaker:ListCodeRepositories": {}, + "sagemaker:ListCompilationJobs": {}, + "sagemaker:ListContexts": {}, + "sagemaker:ListDataQualityJobDefinitions": {}, + "sagemaker:ListDeviceFleets": {}, + "sagemaker:ListDevices": {}, + "sagemaker:ListDomains": {}, + "sagemaker:ListEdgeDeploymentPlans": {}, + "sagemaker:ListEdgePackagingJobs": {}, + "sagemaker:ListEndpointConfigs": {}, + "sagemaker:ListEndpoints": {}, + "sagemaker:ListExperiments": {}, + "sagemaker:ListFeatureGroups": {}, + "sagemaker:ListFlowDefinitions": {}, + "sagemaker:ListHubs": {}, + "sagemaker:ListHumanLoops": {}, + "sagemaker:ListHumanTaskUis": {}, + "sagemaker:ListHyperParameterTuningJobs": {}, + "sagemaker:ListImages": {}, + "sagemaker:ListInferenceComponents": {}, + "sagemaker:ListInferenceExperiments": {}, + "sagemaker:ListInferenceRecommendationsJobSteps": {}, + "sagemaker:ListInferenceRecommendationsJobs": {}, + "sagemaker:ListLabelingJobs": {}, + "sagemaker:ListLineageGroups": {}, + "sagemaker:ListModelBiasJobDefinitions": {}, + "sagemaker:ListModelCards": {}, + "sagemaker:ListModelExplainabilityJobDefinitions": {}, + "sagemaker:ListModelMetadata": {}, + "sagemaker:ListModelPackageGroups": {}, + "sagemaker:ListModelQualityJobDefinitions": {}, + "sagemaker:ListModels": {}, + "sagemaker:ListMonitoringAlertHistory": {}, + "sagemaker:ListMonitoringAlerts": {}, + "sagemaker:ListMonitoringExecutions": {}, + "sagemaker:ListMonitoringSchedules": {}, + "sagemaker:ListNotebookInstanceLifecycleConfigs": {}, + "sagemaker:ListNotebookInstances": {}, + "sagemaker:ListPipelines": {}, + "sagemaker:ListProcessingJobs": {}, + "sagemaker:ListProjects": {}, + "sagemaker:ListResourceCatalogs": {}, + "sagemaker:ListSharedModelEvents": {}, + "sagemaker:ListSharedModels": {}, + "sagemaker:ListSpaces": {}, + "sagemaker:ListStageDevices": {}, + "sagemaker:ListStudioLifecycleConfigs": {}, + "sagemaker:ListSubscribedWorkteams": {}, + "sagemaker:ListTrainingJobs": {}, + "sagemaker:ListTransformJobs": {}, + "sagemaker:ListTrialComponents": {}, + "sagemaker:ListTrials": {}, + "sagemaker:ListUserProfiles": {}, + "sagemaker:ListWorkforces": {}, + "sagemaker:ListWorkteams": {}, + "sagemaker:PutLineageGroupPolicy": {}, + "sagemaker:QueryLineage": {}, + "sagemaker:RenderUiTemplate": {}, + "sagemaker:Search": {}, + "savingsplans:CreateSavingsPlan": {}, + "savingsplans:DescribeSavingsPlansOfferingRates": {}, + "savingsplans:DescribeSavingsPlansOfferings": {}, + "scheduler:ListScheduleGroups": {}, + "scheduler:ListSchedules": {}, + "schemas:CreateDiscoverer": {}, + "schemas:GetDiscoveredSchema": {}, + "sdb:ListDomains": {}, + "secretsmanager:BatchGetSecretValue": {}, + "secretsmanager:GetRandomPassword": {}, + "secretsmanager:ListSecrets": {}, + "securityhub:BatchGetConfigurationPolicyAssociations": {}, + "securityhub:BatchGetSecurityControls": {}, + "securityhub:BatchGetStandardsControlAssociations": {}, + "securityhub:BatchUpdateStandardsControlAssociations": {}, + "securityhub:CreateAutomationRule": {}, + "securityhub:CreateConfigurationPolicy": {}, + "securityhub:CreateFindingAggregator": {}, + "securityhub:GetConfigurationPolicyAssociation": {}, + "securityhub:GetSecurityControlDefinition": {}, + "securityhub:ListAutomationRules": {}, + "securityhub:ListConfigurationPolicies": {}, + "securityhub:ListConfigurationPolicyAssociations": {}, + "securityhub:ListFindingAggregators": {}, + "securityhub:ListSecurityControlDefinitions": {}, + "securityhub:ListStandardsControlAssociations": {}, + "securityhub:UpdateSecurityControl": {}, + "securitylake:CreateDataLakeExceptionSubscription": {}, + "securitylake:CreateSubscriber": {}, + "securitylake:DeleteDataLakeExceptionSubscription": {}, + "securitylake:DeregisterDataLakeDelegatedAdministrator": {}, + "securitylake:GetDataLakeExceptionSubscription": {}, + "securitylake:ListDataLakeExceptions": {}, + "securitylake:ListDataLakes": {}, + "securitylake:ListLogSources": {}, + "securitylake:ListSubscribers": {}, + "securitylake:RegisterDataLakeDelegatedAdministrator": {}, + "securitylake:UpdateDataLakeExceptionSubscription": {}, + "serverlessrepo:CreateApplication": {}, + "serverlessrepo:ListApplications": {}, + "serverlessrepo:SearchApplications": {}, + "servicecatalog:AssociateBudgetWithResource": {}, + "servicecatalog:AssociateProductWithPortfolio": {}, + "servicecatalog:BatchAssociateServiceActionWithProvisioningArtifact": {}, + "servicecatalog:BatchDisassociateServiceActionFromProvisioningArtifact": {}, + "servicecatalog:CopyProduct": {}, + "servicecatalog:CreateProvisionedProductPlan": {}, + "servicecatalog:CreateServiceAction": {}, + "servicecatalog:CreateTagOption": {}, + "servicecatalog:DeleteConstraint": {}, + "servicecatalog:DeleteProvisionedProductPlan": {}, + "servicecatalog:DeleteServiceAction": {}, + "servicecatalog:DeleteTagOption": {}, + "servicecatalog:DescribeConstraint": {}, + "servicecatalog:DescribeCopyProductStatus": {}, + "servicecatalog:DescribePortfolioShareStatus": {}, + "servicecatalog:DescribeProductView": {}, + "servicecatalog:DescribeProvisionedProduct": {}, + "servicecatalog:DescribeProvisionedProductPlan": {}, + "servicecatalog:DescribeRecord": {}, + "servicecatalog:DescribeServiceAction": {}, + "servicecatalog:DescribeServiceActionExecutionParameters": {}, + "servicecatalog:DescribeTagOption": {}, + "servicecatalog:DisableAWSOrganizationsAccess": {}, + "servicecatalog:DisassociateBudgetFromResource": {}, + "servicecatalog:DisassociateProductFromPortfolio": {}, + "servicecatalog:EnableAWSOrganizationsAccess": {}, + "servicecatalog:ExecuteProvisionedProductPlan": {}, + "servicecatalog:ExecuteProvisionedProductServiceAction": {}, + "servicecatalog:GetAWSOrganizationsAccessStatus": {}, + "servicecatalog:GetConfiguration": {}, + "servicecatalog:GetProvisionedProductOutputs": {}, + "servicecatalog:ListAcceptedPortfolioShares": {}, + "servicecatalog:ListApplications": {}, + "servicecatalog:ListAttributeGroups": {}, + "servicecatalog:ListBudgetsForResource": {}, + "servicecatalog:ListConstraintsForPortfolio": {}, + "servicecatalog:ListOrganizationPortfolioAccess": {}, + "servicecatalog:ListPortfolios": {}, + "servicecatalog:ListProvisionedProductPlans": {}, + "servicecatalog:ListProvisioningArtifactsForServiceAction": {}, + "servicecatalog:ListRecordHistory": {}, + "servicecatalog:ListResourcesForTagOption": {}, + "servicecatalog:ListServiceActions": {}, + "servicecatalog:ListStackInstancesForProvisionedProduct": {}, + "servicecatalog:ListTagOptions": {}, + "servicecatalog:NotifyProvisionProductEngineWorkflowResult": {}, + "servicecatalog:NotifyTerminateProvisionedProductEngineWorkflowResult": {}, + "servicecatalog:NotifyUpdateProvisionedProductEngineWorkflowResult": {}, + "servicecatalog:PutConfiguration": {}, + "servicecatalog:ScanProvisionedProducts": {}, + "servicecatalog:SearchProducts": {}, + "servicecatalog:SearchProductsAsAdmin": {}, + "servicecatalog:SearchProvisionedProducts": {}, + "servicecatalog:SyncResource": {}, + "servicecatalog:TerminateProvisionedProduct": {}, + "servicecatalog:UpdateConstraint": {}, + "servicecatalog:UpdateProvisionedProduct": {}, + "servicecatalog:UpdateProvisionedProductProperties": {}, + "servicecatalog:UpdateServiceAction": {}, + "servicecatalog:UpdateTagOption": {}, + "servicediscovery:CreateHttpNamespace": {}, + "servicediscovery:CreatePrivateDnsNamespace": {}, + "servicediscovery:CreatePublicDnsNamespace": {}, + "servicediscovery:DiscoverInstances": {}, + "servicediscovery:DiscoverInstancesRevision": {}, + "servicediscovery:GetInstance": {}, + "servicediscovery:GetInstancesHealthStatus": {}, + "servicediscovery:GetOperation": {}, + "servicediscovery:ListInstances": {}, + "servicediscovery:ListNamespaces": {}, + "servicediscovery:ListOperations": {}, + "servicediscovery:ListServices": {}, + "servicediscovery:ListTagsForResource": {}, + "servicediscovery:TagResource": {}, + "servicediscovery:UntagResource": {}, + "servicediscovery:UpdateInstanceCustomHealthStatus": {}, + "serviceextract:GetConfig": {}, + "servicequotas:AssociateServiceQuotaTemplate": {}, + "servicequotas:DeleteServiceQuotaIncreaseRequestFromTemplate": {}, + "servicequotas:DisassociateServiceQuotaTemplate": {}, + "servicequotas:GetAWSDefaultServiceQuota": {}, + "servicequotas:GetAssociationForServiceQuotaTemplate": {}, + "servicequotas:GetRequestedServiceQuotaChange": {}, + "servicequotas:GetServiceQuota": {}, + "servicequotas:GetServiceQuotaIncreaseRequestFromTemplate": {}, + "servicequotas:ListAWSDefaultServiceQuotas": {}, + "servicequotas:ListRequestedServiceQuotaChangeHistory": {}, + "servicequotas:ListRequestedServiceQuotaChangeHistoryByQuota": {}, + "servicequotas:ListServiceQuotaIncreaseRequestsInTemplate": {}, + "servicequotas:ListServiceQuotas": {}, + "servicequotas:ListServices": {}, + "servicequotas:ListTagsForResource": {}, + "servicequotas:TagResource": {}, + "servicequotas:UntagResource": {}, + "ses:CloneReceiptRuleSet": {}, + "ses:CreateConfigurationSet": {}, + "ses:CreateConfigurationSetEventDestination": {}, + "ses:CreateConfigurationSetTrackingOptions": {}, + "ses:CreateCustomVerificationEmailTemplate": {}, + "ses:CreateDedicatedIpPool": {}, + "ses:CreateEmailIdentity": {}, + "ses:CreateExportJob": {}, + "ses:CreateImportJob": {}, + "ses:CreateReceiptFilter": {}, + "ses:CreateReceiptRule": {}, + "ses:CreateReceiptRuleSet": {}, + "ses:CreateTemplate": {}, + "ses:DeleteConfigurationSet": {}, + "ses:DeleteConfigurationSetEventDestination": {}, + "ses:DeleteConfigurationSetTrackingOptions": {}, + "ses:DeleteCustomVerificationEmailTemplate": {}, + "ses:DeleteIdentity": {}, + "ses:DeleteIdentityPolicy": {}, + "ses:DeleteReceiptFilter": {}, + "ses:DeleteReceiptRule": {}, + "ses:DeleteReceiptRuleSet": {}, + "ses:DeleteSuppressedDestination": {}, + "ses:DeleteTemplate": {}, + "ses:DeleteVerifiedEmailAddress": {}, + "ses:DescribeActiveReceiptRuleSet": {}, + "ses:DescribeConfigurationSet": {}, + "ses:DescribeReceiptRule": {}, + "ses:DescribeReceiptRuleSet": {}, + "ses:GetAccount": {}, + "ses:GetAccountSendingEnabled": {}, + "ses:GetBlacklistReports": {}, + "ses:GetCustomVerificationEmailTemplate": {}, + "ses:GetDedicatedIp": {}, + "ses:GetDeliverabilityDashboardOptions": {}, + "ses:GetDomainDeliverabilityCampaign": {}, + "ses:GetIdentityDkimAttributes": {}, + "ses:GetIdentityMailFromDomainAttributes": {}, + "ses:GetIdentityNotificationAttributes": {}, + "ses:GetIdentityPolicies": {}, + "ses:GetIdentityVerificationAttributes": {}, + "ses:GetMessageInsights": {}, + "ses:GetSendQuota": {}, + "ses:GetSendStatistics": {}, + "ses:GetSuppressedDestination": {}, + "ses:GetTemplate": {}, + "ses:ListConfigurationSets": {}, + "ses:ListContactLists": {}, + "ses:ListCustomVerificationEmailTemplates": {}, + "ses:ListDedicatedIpPools": {}, + "ses:ListDeliverabilityTestReports": {}, + "ses:ListDomainDeliverabilityCampaigns": {}, + "ses:ListEmailIdentities": {}, + "ses:ListEmailTemplates": {}, + "ses:ListExportJobs": {}, + "ses:ListIdentities": {}, + "ses:ListIdentityPolicies": {}, + "ses:ListImportJobs": {}, + "ses:ListReceiptFilters": {}, + "ses:ListReceiptRuleSets": {}, + "ses:ListSuppressedDestinations": {}, + "ses:ListTemplates": {}, + "ses:ListVerifiedEmailAddresses": {}, + "ses:PutAccountDedicatedIpWarmupAttributes": {}, + "ses:PutAccountDetails": {}, + "ses:PutAccountSendingAttributes": {}, + "ses:PutAccountSuppressionAttributes": {}, + "ses:PutAccountVdmAttributes": {}, + "ses:PutConfigurationSetDeliveryOptions": {}, + "ses:PutDedicatedIpWarmupAttributes": {}, + "ses:PutDeliverabilityDashboardOption": {}, + "ses:PutIdentityPolicy": {}, + "ses:PutSuppressedDestination": {}, + "ses:ReorderReceiptRuleSet": {}, + "ses:SetActiveReceiptRuleSet": {}, + "ses:SetIdentityDkimEnabled": {}, + "ses:SetIdentityFeedbackForwardingEnabled": {}, + "ses:SetIdentityHeadersInNotificationsEnabled": {}, + "ses:SetIdentityMailFromDomain": {}, + "ses:SetIdentityNotificationTopic": {}, + "ses:SetReceiptRulePosition": {}, + "ses:TestRenderTemplate": {}, + "ses:UpdateAccountSendingEnabled": {}, + "ses:UpdateConfigurationSetEventDestination": {}, + "ses:UpdateConfigurationSetReputationMetricsEnabled": {}, + "ses:UpdateConfigurationSetSendingEnabled": {}, + "ses:UpdateConfigurationSetTrackingOptions": {}, + "ses:UpdateCustomVerificationEmailTemplate": {}, + "ses:UpdateReceiptRule": {}, + "ses:UpdateTemplate": {}, + "ses:VerifyDomainDkim": {}, + "ses:VerifyDomainIdentity": {}, + "ses:VerifyEmailAddress": {}, + "ses:VerifyEmailIdentity": {}, + "shield:AssociateDRTLogBucket": {}, + "shield:AssociateDRTRole": {}, + "shield:AssociateProactiveEngagementDetails": {}, + "shield:CreateProtection": {}, + "shield:CreateProtectionGroup": {}, + "shield:CreateSubscription": {}, + "shield:DeleteSubscription": {}, + "shield:DescribeAttackStatistics": {}, + "shield:DescribeDRTAccess": {}, + "shield:DescribeEmergencyContactSettings": {}, + "shield:DescribeSubscription": {}, + "shield:DisableApplicationLayerAutomaticResponse": {}, + "shield:DisableProactiveEngagement": {}, + "shield:DisassociateDRTLogBucket": {}, + "shield:DisassociateDRTRole": {}, + "shield:EnableApplicationLayerAutomaticResponse": {}, + "shield:EnableProactiveEngagement": {}, + "shield:GetSubscriptionState": {}, + "shield:ListAttacks": {}, + "shield:ListProtectionGroups": {}, + "shield:ListProtections": {}, + "shield:UpdateApplicationLayerAutomaticResponse": {}, + "shield:UpdateEmergencyContactSettings": {}, + "shield:UpdateSubscription": {}, + "signer:GetSigningPlatform": {}, + "signer:ListSigningJobs": {}, + "signer:ListSigningPlatforms": {}, + "signer:ListSigningProfiles": {}, + "signer:PutSigningProfile": {}, + "simspaceweaver:ListSimulations": {}, + "simspaceweaver:ListTagsForResource": {}, + "simspaceweaver:StartSimulation": {}, + "sms-voice:CreateConfigurationSet": {}, + "sms-voice:CreateConfigurationSetEventDestination": {}, + "sms-voice:CreateOptOutList": {}, + "sms-voice:CreateRegistration": {}, + "sms-voice:CreateRegistrationAttachment": {}, + "sms-voice:CreateVerifiedDestinationNumber": {}, + "sms-voice:DeleteConfigurationSet": {}, + "sms-voice:DeleteConfigurationSetEventDestination": {}, + "sms-voice:DeleteTextMessageSpendLimitOverride": {}, + "sms-voice:DeleteVoiceMessageSpendLimitOverride": {}, + "sms-voice:DescribeAccountAttributes": {}, + "sms-voice:DescribeAccountLimits": {}, + "sms-voice:DescribeRegistrationFieldDefinitions": {}, + "sms-voice:DescribeRegistrationSectionDefinitions": {}, + "sms-voice:DescribeRegistrationTypeDefinitions": {}, + "sms-voice:DescribeSpendLimits": {}, + "sms-voice:GetConfigurationSetEventDestinations": {}, + "sms-voice:ListConfigurationSets": {}, + "sms-voice:RequestSenderId": {}, + "sms-voice:SendVoiceMessage": {}, + "sms-voice:SetTextMessageSpendLimitOverride": {}, + "sms-voice:SetVoiceMessageSpendLimitOverride": {}, + "sms-voice:UpdateConfigurationSetEventDestination": {}, + "sms:CreateApp": {}, + "sms:CreateReplicationJob": {}, + "sms:DeleteApp": {}, + "sms:DeleteAppLaunchConfiguration": {}, + "sms:DeleteAppReplicationConfiguration": {}, + "sms:DeleteAppValidationConfiguration": {}, + "sms:DeleteReplicationJob": {}, + "sms:DeleteServerCatalog": {}, + "sms:DisassociateConnector": {}, + "sms:GenerateChangeSet": {}, + "sms:GenerateTemplate": {}, + "sms:GetApp": {}, + "sms:GetAppLaunchConfiguration": {}, + "sms:GetAppReplicationConfiguration": {}, + "sms:GetAppValidationConfiguration": {}, + "sms:GetAppValidationOutput": {}, + "sms:GetConnectors": {}, + "sms:GetReplicationJobs": {}, + "sms:GetReplicationRuns": {}, + "sms:GetServers": {}, + "sms:ImportAppCatalog": {}, + "sms:ImportServerCatalog": {}, + "sms:LaunchApp": {}, + "sms:ListApps": {}, + "sms:NotifyAppValidationOutput": {}, + "sms:PutAppLaunchConfiguration": {}, + "sms:PutAppReplicationConfiguration": {}, + "sms:PutAppValidationConfiguration": {}, + "sms:StartAppReplication": {}, + "sms:StartOnDemandAppReplication": {}, + "sms:StartOnDemandReplicationRun": {}, + "sms:StopAppReplication": {}, + "sms:TerminateApp": {}, + "sms:UpdateApp": {}, + "sms:UpdateReplicationJob": {}, + "snow-device-management:CreateTask": {}, + "snow-device-management:DescribeExecution": {}, + "snow-device-management:ListDevices": {}, + "snow-device-management:ListExecutions": {}, + "snow-device-management:ListTagsForResource": {}, + "snow-device-management:ListTasks": {}, + "snowball:CancelCluster": {}, + "snowball:CancelJob": {}, + "snowball:CreateAddress": {}, + "snowball:CreateCluster": {}, + "snowball:CreateJob": {}, + "snowball:CreateLongTermPricing": {}, + "snowball:CreateReturnShippingLabel": {}, + "snowball:DescribeAddress": {}, + "snowball:DescribeAddresses": {}, + "snowball:DescribeCluster": {}, + "snowball:DescribeJob": {}, + "snowball:DescribeReturnShippingLabel": {}, + "snowball:GetJobManifest": {}, + "snowball:GetJobUnlockCode": {}, + "snowball:GetSnowballUsage": {}, + "snowball:GetSoftwareUpdates": {}, + "snowball:ListClusterJobs": {}, + "snowball:ListClusters": {}, + "snowball:ListCompatibleImages": {}, + "snowball:ListJobs": {}, + "snowball:ListLongTermPricing": {}, + "snowball:ListPickupLocations": {}, + "snowball:ListServiceVersions": {}, + "snowball:UpdateCluster": {}, + "snowball:UpdateJob": {}, + "snowball:UpdateJobShipmentState": {}, + "snowball:UpdateLongTermPricing": {}, + "sns:CheckIfPhoneNumberIsOptedOut": {}, + "sns:CreatePlatformApplication": {}, + "sns:CreatePlatformEndpoint": {}, + "sns:CreateSMSSandboxPhoneNumber": {}, + "sns:DeleteEndpoint": {}, + "sns:DeletePlatformApplication": {}, + "sns:DeleteSMSSandboxPhoneNumber": {}, + "sns:GetEndpointAttributes": {}, + "sns:GetPlatformApplicationAttributes": {}, + "sns:GetSMSAttributes": {}, + "sns:GetSMSSandboxAccountStatus": {}, + "sns:GetSubscriptionAttributes": {}, + "sns:ListEndpointsByPlatformApplication": {}, + "sns:ListOriginationNumbers": {}, + "sns:ListPhoneNumbersOptedOut": {}, + "sns:ListPlatformApplications": {}, + "sns:ListSMSSandboxPhoneNumbers": {}, + "sns:ListSubscriptions": {}, + "sns:ListTopics": {}, + "sns:OptInPhoneNumber": {}, + "sns:SetEndpointAttributes": {}, + "sns:SetPlatformApplicationAttributes": {}, + "sns:SetSMSAttributes": {}, + "sns:SetSubscriptionAttributes": {}, + "sns:Unsubscribe": {}, + "sns:VerifySMSSandboxPhoneNumber": {}, + "sqlworkbench:BatchDeleteFolder": {}, + "sqlworkbench:CreateAccount": {}, + "sqlworkbench:CreateFolder": {}, + "sqlworkbench:DeleteTab": {}, + "sqlworkbench:GenerateSession": {}, + "sqlworkbench:GetAccountInfo": {}, + "sqlworkbench:GetAccountSettings": {}, + "sqlworkbench:GetAutocompletionMetadata": {}, + "sqlworkbench:GetAutocompletionResource": {}, + "sqlworkbench:GetQSqlRecommendations": {}, + "sqlworkbench:GetQueryExecutionHistory": {}, + "sqlworkbench:GetSchemaInference": {}, + "sqlworkbench:GetUserInfo": {}, + "sqlworkbench:GetUserWorkspaceSettings": {}, + "sqlworkbench:ListConnections": {}, + "sqlworkbench:ListDatabases": {}, + "sqlworkbench:ListFiles": {}, + "sqlworkbench:ListNotebooks": {}, + "sqlworkbench:ListQueryExecutionHistory": {}, + "sqlworkbench:ListRedshiftClusters": {}, + "sqlworkbench:ListSampleDatabases": {}, + "sqlworkbench:ListTabs": {}, + "sqlworkbench:ListTaggedResources": {}, + "sqlworkbench:PutTab": {}, + "sqlworkbench:PutUserWorkspaceSettings": {}, + "sqlworkbench:UpdateAccountConnectionSettings": {}, + "sqlworkbench:UpdateAccountExportSettings": {}, + "sqlworkbench:UpdateAccountGeneralSettings": {}, + "sqlworkbench:UpdateAccountQSqlSettings": {}, + "sqlworkbench:UpdateFolder": {}, + "sqs:ListQueues": {}, + "ssm-contacts:ListContacts": {}, + "ssm-contacts:ListEngagements": {}, + "ssm-contacts:ListRotations": {}, + "ssm-guiconnect:CancelConnection": {}, + "ssm-guiconnect:GetConnection": {}, + "ssm-guiconnect:StartConnection": {}, + "ssm-incidents:CreateReplicationSet": {}, + "ssm-incidents:CreateResponsePlan": {}, + "ssm-incidents:ListIncidentRecords": {}, + "ssm-incidents:ListReplicationSets": {}, + "ssm-incidents:ListResponsePlans": {}, + "ssm-sap:BackupDatabase": {}, + "ssm-sap:DeleteResourcePermission": {}, + "ssm-sap:GetApplication": {}, + "ssm-sap:GetDatabase": {}, + "ssm-sap:GetOperation": {}, + "ssm-sap:GetResourcePermission": {}, + "ssm-sap:ListApplications": {}, + "ssm-sap:ListDatabases": {}, + "ssm-sap:ListOperations": {}, + "ssm-sap:ListTagsForResource": {}, + "ssm-sap:PutResourcePermission": {}, + "ssm-sap:RegisterApplication": {}, + "ssm-sap:RestoreDatabase": {}, + "ssm-sap:UpdateHANABackupSettings": {}, + "ssm:CancelCommand": {}, + "ssm:CreateActivation": {}, + "ssm:CreateMaintenanceWindow": {}, + "ssm:CreateOpsItem": {}, + "ssm:CreateOpsMetadata": {}, + "ssm:CreatePatchBaseline": {}, + "ssm:DeleteActivation": {}, + "ssm:DeleteInventory": {}, + "ssm:DescribeActivations": {}, + "ssm:DescribeAutomationExecutions": {}, + "ssm:DescribeAvailablePatches": {}, + "ssm:DescribeInstanceInformation": {}, + "ssm:DescribeInstancePatchStates": {}, + "ssm:DescribeInstancePatchStatesForPatchGroup": {}, + "ssm:DescribeInstancePatches": {}, + "ssm:DescribeInstanceProperties": {}, + "ssm:DescribeInventoryDeletions": {}, + "ssm:DescribeMaintenanceWindowExecutionTaskInvocations": {}, + "ssm:DescribeMaintenanceWindowSchedule": {}, + "ssm:DescribeMaintenanceWindows": {}, + "ssm:DescribeMaintenanceWindowsForTarget": {}, + "ssm:DescribeOpsItems": {}, + "ssm:DescribeParameters": {}, + "ssm:DescribePatchBaselines": {}, + "ssm:DescribePatchGroupState": {}, + "ssm:DescribePatchGroups": {}, + "ssm:DescribePatchProperties": {}, + "ssm:DescribeSessions": {}, + "ssm:GetCommandInvocation": {}, + "ssm:GetDeployablePatchSnapshotForInstance": {}, + "ssm:GetInventory": {}, + "ssm:GetInventorySchema": {}, + "ssm:GetMaintenanceWindowExecution": {}, + "ssm:GetMaintenanceWindowExecutionTask": {}, + "ssm:GetMaintenanceWindowExecutionTaskInvocation": {}, + "ssm:GetManifest": {}, + "ssm:ListAssociations": {}, + "ssm:ListCommandInvocations": {}, + "ssm:ListCommands": {}, + "ssm:ListComplianceItems": {}, + "ssm:ListComplianceSummaries": {}, + "ssm:ListDocuments": {}, + "ssm:ListInventoryEntries": {}, + "ssm:ListOpsItemEvents": {}, + "ssm:ListOpsItemRelatedItems": {}, + "ssm:ListOpsMetadata": {}, + "ssm:ListResourceComplianceSummaries": {}, + "ssm:ListResourceDataSync": {}, + "ssm:PutConfigurePackageResult": {}, + "ssm:PutInventory": {}, + "ssm:RegisterManagedInstance": {}, + "ssmmessages:CreateControlChannel": {}, + "ssmmessages:CreateDataChannel": {}, + "ssmmessages:OpenControlChannel": {}, + "ssmmessages:OpenDataChannel": {}, + "sso-directory:AddMemberToGroup": {}, + "sso-directory:CompleteVirtualMfaDeviceRegistration": {}, + "sso-directory:CompleteWebAuthnDeviceRegistration": {}, + "sso-directory:CreateAlias": {}, + "sso-directory:CreateBearerToken": {}, + "sso-directory:CreateExternalIdPConfigurationForDirectory": {}, + "sso-directory:CreateGroup": {}, + "sso-directory:CreateProvisioningTenant": {}, + "sso-directory:CreateUser": {}, + "sso-directory:DeleteBearerToken": {}, + "sso-directory:DeleteExternalIdPCertificate": {}, + "sso-directory:DeleteExternalIdPConfigurationForDirectory": {}, + "sso-directory:DeleteGroup": {}, + "sso-directory:DeleteMfaDeviceForUser": {}, + "sso-directory:DeleteProvisioningTenant": {}, + "sso-directory:DeleteUser": {}, + "sso-directory:DescribeDirectory": {}, + "sso-directory:DescribeGroup": {}, + "sso-directory:DescribeGroups": {}, + "sso-directory:DescribeProvisioningTenant": {}, + "sso-directory:DescribeUser": {}, + "sso-directory:DescribeUserByUniqueAttribute": {}, + "sso-directory:DescribeUsers": {}, + "sso-directory:DisableExternalIdPConfigurationForDirectory": {}, + "sso-directory:DisableUser": {}, + "sso-directory:EnableExternalIdPConfigurationForDirectory": {}, + "sso-directory:EnableUser": {}, + "sso-directory:GetAWSSPConfigurationForDirectory": {}, + "sso-directory:GetUserPoolInfo": {}, + "sso-directory:ImportExternalIdPCertificate": {}, + "sso-directory:IsMemberInGroup": {}, + "sso-directory:ListBearerTokens": {}, + "sso-directory:ListExternalIdPCertificates": {}, + "sso-directory:ListExternalIdPConfigurationsForDirectory": {}, + "sso-directory:ListGroupsForMember": {}, + "sso-directory:ListGroupsForUser": {}, + "sso-directory:ListMembersInGroup": {}, + "sso-directory:ListMfaDevicesForUser": {}, + "sso-directory:ListProvisioningTenants": {}, + "sso-directory:RemoveMemberFromGroup": {}, + "sso-directory:SearchGroups": {}, + "sso-directory:SearchUsers": {}, + "sso-directory:StartVirtualMfaDeviceRegistration": {}, + "sso-directory:StartWebAuthnDeviceRegistration": {}, + "sso-directory:UpdateExternalIdPConfigurationForDirectory": {}, + "sso-directory:UpdateGroup": {}, + "sso-directory:UpdateGroupDisplayName": {}, + "sso-directory:UpdateMfaDeviceForUser": {}, + "sso-directory:UpdatePassword": {}, + "sso-directory:UpdateUser": {}, + "sso-directory:UpdateUserName": {}, + "sso-directory:VerifyEmail": {}, + "sso:AssociateDirectory": {}, + "sso:AssociateProfile": {}, + "sso:CreateApplicationInstance": {}, + "sso:CreateApplicationInstanceCertificate": {}, + "sso:CreateManagedApplicationInstance": {}, + "sso:CreateProfile": {}, + "sso:CreateTrust": {}, + "sso:DeleteApplicationInstance": {}, + "sso:DeleteApplicationInstanceCertificate": {}, + "sso:DeleteManagedApplicationInstance": {}, + "sso:DeletePermissionsPolicy": {}, + "sso:DeleteProfile": {}, + "sso:DescribeDirectories": {}, + "sso:DescribePermissionsPolicies": {}, + "sso:DescribeRegisteredRegions": {}, + "sso:DescribeTrusts": {}, + "sso:DisassociateDirectory": {}, + "sso:DisassociateProfile": {}, + "sso:GetApplicationInstance": {}, + "sso:GetApplicationTemplate": {}, + "sso:GetManagedApplicationInstance": {}, + "sso:GetMfaDeviceManagementForDirectory": {}, + "sso:GetPermissionSet": {}, + "sso:GetPermissionsPolicy": {}, + "sso:GetProfile": {}, + "sso:GetSSOStatus": {}, + "sso:GetSharedSsoConfiguration": {}, + "sso:GetSsoConfiguration": {}, + "sso:GetTrust": {}, + "sso:ImportApplicationInstanceServiceProviderMetadata": {}, + "sso:ListApplicationInstanceCertificates": {}, + "sso:ListApplicationInstances": {}, + "sso:ListApplicationTemplates": {}, + "sso:ListApplications": {}, + "sso:ListDirectoryAssociations": {}, + "sso:ListInstances": {}, + "sso:ListProfileAssociations": {}, + "sso:ListProfiles": {}, + "sso:PutMfaDeviceManagementForDirectory": {}, + "sso:PutPermissionsPolicy": {}, + "sso:SearchGroups": {}, + "sso:SearchUsers": {}, + "sso:StartSSO": {}, + "sso:UpdateApplicationInstanceActiveCertificate": {}, + "sso:UpdateApplicationInstanceDisplayData": {}, + "sso:UpdateApplicationInstanceResponseConfiguration": {}, + "sso:UpdateApplicationInstanceResponseSchemaConfiguration": {}, + "sso:UpdateApplicationInstanceSecurityConfiguration": {}, + "sso:UpdateApplicationInstanceServiceProviderConfiguration": {}, + "sso:UpdateApplicationInstanceStatus": {}, + "sso:UpdateDirectoryAssociation": {}, + "sso:UpdateManagedApplicationInstanceStatus": {}, + "sso:UpdateProfile": {}, + "sso:UpdateSSOConfiguration": {}, + "sso:UpdateTrust": {}, + "states:InvokeHTTPEndpoint": {}, + "states:ListActivities": {}, + "states:ListStateMachines": {}, + "states:RevealSecrets": {}, + "states:SendTaskFailure": {}, + "states:SendTaskHeartbeat": {}, + "states:SendTaskSuccess": {}, + "states:TestState": {}, + "storagegateway:ActivateGateway": {}, + "storagegateway:CreateTapePool": {}, + "storagegateway:DeleteTapeArchive": {}, + "storagegateway:DescribeTapeArchives": {}, + "storagegateway:ListAutomaticTapeCreationPolicies": {}, + "storagegateway:ListFileShares": {}, + "storagegateway:ListFileSystemAssociations": {}, + "storagegateway:ListGateways": {}, + "storagegateway:ListTapePools": {}, + "storagegateway:ListTapes": {}, + "storagegateway:ListVolumes": {}, + "sts:DecodeAuthorizationMessage": {}, + "sts:GetAccessKeyInfo": {}, + "sts:GetCallerIdentity": {}, + "sts:GetServiceBearerToken": {}, + "sts:GetSessionToken": {}, + "support:AddAttachmentsToSet": {}, + "support:AddCommunicationToCase": {}, + "support:CreateCase": {}, + "support:DescribeAttachment": {}, + "support:DescribeCaseAttributes": {}, + "support:DescribeCases": {}, + "support:DescribeCommunication": {}, + "support:DescribeCommunications": {}, + "support:DescribeCreateCaseOptions": {}, + "support:DescribeIssueTypes": {}, + "support:DescribeServices": {}, + "support:DescribeSeverityLevels": {}, + "support:DescribeSupportLevel": {}, + "support:DescribeSupportedLanguages": {}, + "support:DescribeTrustedAdvisorCheckRefreshStatuses": {}, + "support:DescribeTrustedAdvisorCheckResult": {}, + "support:DescribeTrustedAdvisorCheckSummaries": {}, + "support:DescribeTrustedAdvisorChecks": {}, + "support:InitiateCallForCase": {}, + "support:InitiateChatForCase": {}, + "support:PutCaseAttributes": {}, + "support:RateCaseCommunication": {}, + "support:RefreshTrustedAdvisorCheck": {}, + "support:ResolveCase": {}, + "support:SearchForCases": {}, + "supportapp:CreateSlackChannelConfiguration": {}, + "supportapp:DeleteAccountAlias": {}, + "supportapp:DeleteSlackChannelConfiguration": {}, + "supportapp:DeleteSlackWorkspaceConfiguration": {}, + "supportapp:DescribeSlackChannels": {}, + "supportapp:GetAccountAlias": {}, + "supportapp:GetSlackOauthParameters": {}, + "supportapp:ListSlackChannelConfigurations": {}, + "supportapp:ListSlackWorkspaceConfigurations": {}, + "supportapp:PutAccountAlias": {}, + "supportapp:RedeemSlackOauthCode": {}, + "supportapp:RegisterSlackWorkspaceForOrganization": {}, + "supportapp:UpdateSlackChannelConfiguration": {}, + "supportplans:CreateSupportPlanSchedule": {}, + "supportplans:GetSupportPlan": {}, + "supportplans:GetSupportPlanUpdateStatus": {}, + "supportplans:StartSupportPlanUpdate": {}, + "sustainability:GetCarbonFootprintSummary": {}, + "swf:ListDomains": {}, + "swf:RegisterDomain": {}, + "synthetics:CreateCanary": {}, + "synthetics:CreateGroup": {}, + "synthetics:DescribeCanaries": {}, + "synthetics:DescribeCanariesLastRun": {}, + "synthetics:DescribeRuntimeVersions": {}, + "synthetics:ListGroups": {}, + "tag:DescribeReportCreation": {}, + "tag:GetComplianceSummary": {}, + "tag:GetResources": {}, + "tag:GetTagKeys": {}, + "tag:GetTagValues": {}, + "tag:StartReportCreation": {}, + "tag:TagResources": {}, + "tag:UntagResources": {}, + "tax:BatchPutTaxRegistration": {}, + "tax:DeleteTaxRegistration": {}, + "tax:GetExemptions": {}, + "tax:GetTaxInfoReportingDocument": {}, + "tax:GetTaxInheritance": {}, + "tax:GetTaxInterview": {}, + "tax:GetTaxRegistration": {}, + "tax:GetTaxRegistrationDocument": {}, + "tax:ListTaxRegistrations": {}, + "tax:PutTaxInheritance": {}, + "tax:PutTaxInterview": {}, + "tax:PutTaxRegistration": {}, + "tax:UpdateExemptions": {}, + "textract:AnalyzeDocument": {}, + "textract:AnalyzeExpense": {}, + "textract:AnalyzeID": {}, + "textract:CreateAdapter": {}, + "textract:DetectDocumentText": {}, + "textract:GetDocumentAnalysis": {}, + "textract:GetDocumentTextDetection": {}, + "textract:GetExpenseAnalysis": {}, + "textract:GetLendingAnalysis": {}, + "textract:GetLendingAnalysisSummary": {}, + "textract:ListAdapterVersions": {}, + "textract:ListAdapters": {}, + "textract:StartDocumentAnalysis": {}, + "textract:StartDocumentTextDetection": {}, + "textract:StartExpenseAnalysis": {}, + "textract:StartLendingAnalysis": {}, + "thinclient:CreateEnvironment": {}, + "thinclient:ListDeviceSessions": {}, + "thinclient:ListDevices": {}, + "thinclient:ListEnvironments": {}, + "thinclient:ListSoftwareSets": {}, + "thinclient:ListTagsForResource": {}, + "timestream:CancelQuery": {}, + "timestream:CreateScheduledQuery": {}, + "timestream:DescribeBatchLoadTask": {}, + "timestream:DescribeEndpoints": {}, + "timestream:GetAwsBackupStatus": {}, + "timestream:GetAwsRestoreStatus": {}, + "timestream:ListBatchLoadTasks": {}, + "timestream:ListDatabases": {}, + "timestream:ListScheduledQueries": {}, + "timestream:ResumeBatchLoadTask": {}, + "timestream:SelectValues": {}, + "tiros:CreateQuery": {}, + "tiros:ExtendQuery": {}, + "tiros:GetQueryAnswer": {}, + "tiros:GetQueryExplanation": {}, + "tiros:GetQueryExtensionAccounts": {}, + "tnb:ListTagsForResource": {}, + "transcribe:CreateCallAnalyticsCategory": {}, + "transcribe:CreateLanguageModel": {}, + "transcribe:CreateMedicalVocabulary": {}, + "transcribe:CreateVocabulary": {}, + "transcribe:CreateVocabularyFilter": {}, + "transcribe:DeleteCallAnalyticsCategory": {}, + "transcribe:DeleteCallAnalyticsJob": {}, + "transcribe:GetCallAnalyticsCategory": {}, + "transcribe:GetCallAnalyticsJob": {}, + "transcribe:ListCallAnalyticsCategories": {}, + "transcribe:ListCallAnalyticsJobs": {}, + "transcribe:ListLanguageModels": {}, + "transcribe:ListMedicalScribeJobs": {}, + "transcribe:ListMedicalTranscriptionJobs": {}, + "transcribe:ListMedicalVocabularies": {}, + "transcribe:ListTagsForResource": {}, + "transcribe:ListTranscriptionJobs": {}, + "transcribe:ListVocabularies": {}, + "transcribe:ListVocabularyFilters": {}, + "transcribe:StartCallAnalyticsJob": {}, + "transcribe:StartCallAnalyticsStreamTranscription": {}, + "transcribe:StartCallAnalyticsStreamTranscriptionWebSocket": {}, + "transcribe:StartMedicalScribeJob": {}, + "transcribe:StartMedicalStreamTranscription": {}, + "transcribe:StartMedicalStreamTranscriptionWebSocket": {}, + "transcribe:StartMedicalTranscriptionJob": {}, + "transcribe:StartStreamTranscription": {}, + "transcribe:StartStreamTranscriptionWebSocket": {}, + "transcribe:StartTranscriptionJob": {}, + "transcribe:TagResource": {}, + "transcribe:UntagResource": {}, + "transcribe:UpdateCallAnalyticsCategory": {}, + "transfer:CreateConnector": {}, + "transfer:CreateProfile": {}, + "transfer:CreateServer": {}, + "transfer:CreateWorkflow": {}, + "transfer:DescribeSecurityPolicy": {}, + "transfer:ImportCertificate": {}, + "transfer:ListCertificates": {}, + "transfer:ListConnectors": {}, + "transfer:ListProfiles": {}, + "transfer:ListSecurityPolicies": {}, + "transfer:ListServers": {}, + "transfer:ListWorkflows": {}, + "transfer:UpdateAccess": {}, + "translate:DescribeTextTranslationJob": {}, + "translate:ListLanguages": {}, + "translate:ListParallelData": {}, + "translate:ListTerminologies": {}, + "translate:ListTextTranslationJobs": {}, + "translate:StopTextTranslationJob": {}, + "trustedadvisor:CreateEngagement": {}, + "trustedadvisor:CreateEngagementAttachment": {}, + "trustedadvisor:CreateEngagementCommunication": {}, + "trustedadvisor:DeleteNotificationConfigurationForDelegatedAdmin": {}, + "trustedadvisor:DescribeAccount": {}, + "trustedadvisor:DescribeAccountAccess": {}, + "trustedadvisor:DescribeChecks": {}, + "trustedadvisor:DescribeNotificationConfigurations": {}, + "trustedadvisor:DescribeNotificationPreferences": {}, + "trustedadvisor:DescribeOrganization": {}, + "trustedadvisor:DescribeOrganizationAccounts": {}, + "trustedadvisor:DescribeReports": {}, + "trustedadvisor:DescribeRisk": {}, + "trustedadvisor:DescribeRiskResources": {}, + "trustedadvisor:DescribeRisks": {}, + "trustedadvisor:DescribeServiceMetadata": {}, + "trustedadvisor:DownloadRisk": {}, + "trustedadvisor:GenerateReport": {}, + "trustedadvisor:GetEngagement": {}, + "trustedadvisor:GetEngagementAttachment": {}, + "trustedadvisor:GetEngagementType": {}, + "trustedadvisor:GetOrganizationRecommendation": {}, + "trustedadvisor:GetRecommendation": {}, + "trustedadvisor:ListAccountsForParent": {}, + "trustedadvisor:ListChecks": {}, + "trustedadvisor:ListEngagementCommunications": {}, + "trustedadvisor:ListEngagementTypes": {}, + "trustedadvisor:ListEngagements": {}, + "trustedadvisor:ListOrganizationRecommendationAccounts": {}, + "trustedadvisor:ListOrganizationRecommendationResources": {}, + "trustedadvisor:ListOrganizationRecommendations": {}, + "trustedadvisor:ListOrganizationalUnitsForParent": {}, + "trustedadvisor:ListRecommendationResources": {}, + "trustedadvisor:ListRecommendations": {}, + "trustedadvisor:ListRoots": {}, + "trustedadvisor:SetAccountAccess": {}, + "trustedadvisor:SetOrganizationAccess": {}, + "trustedadvisor:UpdateEngagement": {}, + "trustedadvisor:UpdateEngagementStatus": {}, + "trustedadvisor:UpdateNotificationConfigurations": {}, + "trustedadvisor:UpdateNotificationPreferences": {}, + "trustedadvisor:UpdateOrganizationRecommendationLifecycle": {}, + "trustedadvisor:UpdateRecommendationLifecycle": {}, + "trustedadvisor:UpdateRiskStatus": {}, + "ts:ListExecutions": {}, + "ts:ListTools": {}, + "ts:StartExecution": {}, + "vendor-insights:CreateDataSource": {}, + "vendor-insights:CreateSecurityProfile": {}, + "vendor-insights:GetProfileAccessTerms": {}, + "vendor-insights:ListDataSources": {}, + "vendor-insights:ListEntitledSecurityProfiles": {}, + "vendor-insights:ListSecurityProfiles": {}, + "verified-access:AllowVerifiedAccess": {}, + "verifiedpermissions:CreatePolicyStore": {}, + "verifiedpermissions:ListPolicyStores": {}, + "voiceid:CreateDomain": {}, + "voiceid:DescribeComplianceConsent": {}, + "voiceid:ListDomains": {}, + "voiceid:RegisterComplianceConsent": {}, + "vpc-lattice:ListAccessLogSubscriptions": {}, + "vpc-lattice:ListListeners": {}, + "vpc-lattice:ListRules": {}, + "vpc-lattice:ListServiceNetworkServiceAssociations": {}, + "vpc-lattice:ListServiceNetworkVpcAssociations": {}, + "vpc-lattice:ListServiceNetworks": {}, + "vpc-lattice:ListServices": {}, + "vpc-lattice:ListTagsForResource": {}, + "vpc-lattice:ListTargetGroups": {}, + "waf-regional:GetChangeToken": {}, + "waf-regional:GetChangeTokenStatus": {}, + "waf-regional:ListActivatedRulesInRuleGroup": {}, + "waf-regional:ListByteMatchSets": {}, + "waf-regional:ListGeoMatchSets": {}, + "waf-regional:ListIPSets": {}, + "waf-regional:ListLoggingConfigurations": {}, + "waf-regional:ListRateBasedRules": {}, + "waf-regional:ListRegexMatchSets": {}, + "waf-regional:ListRegexPatternSets": {}, + "waf-regional:ListRuleGroups": {}, + "waf-regional:ListRules": {}, + "waf-regional:ListSizeConstraintSets": {}, + "waf-regional:ListSqlInjectionMatchSets": {}, + "waf-regional:ListSubscribedRuleGroups": {}, + "waf-regional:ListWebACLs": {}, + "waf-regional:ListXssMatchSets": {}, + "waf:GetChangeToken": {}, + "waf:GetChangeTokenStatus": {}, + "waf:ListActivatedRulesInRuleGroup": {}, + "waf:ListByteMatchSets": {}, + "waf:ListGeoMatchSets": {}, + "waf:ListIPSets": {}, + "waf:ListLoggingConfigurations": {}, + "waf:ListRateBasedRules": {}, + "waf:ListRegexMatchSets": {}, + "waf:ListRegexPatternSets": {}, + "waf:ListRuleGroups": {}, + "waf:ListRules": {}, + "waf:ListSizeConstraintSets": {}, + "waf:ListSqlInjectionMatchSets": {}, + "waf:ListSubscribedRuleGroups": {}, + "waf:ListWebACLs": {}, + "waf:ListXssMatchSets": {}, + "wafv2:CheckCapacity": {}, + "wafv2:CreateAPIKey": {}, + "wafv2:DescribeAllManagedProducts": {}, + "wafv2:DescribeManagedProductsByVendor": {}, + "wafv2:DescribeManagedRuleGroup": {}, + "wafv2:GenerateMobileSdkReleaseUrl": {}, + "wafv2:GetDecryptedAPIKey": {}, + "wafv2:GetMobileSdkRelease": {}, + "wafv2:ListAPIKeys": {}, + "wafv2:ListAvailableManagedRuleGroupVersions": {}, + "wafv2:ListAvailableManagedRuleGroups": {}, + "wafv2:ListIPSets": {}, + "wafv2:ListLoggingConfigurations": {}, + "wafv2:ListManagedRuleSets": {}, + "wafv2:ListMobileSdkReleases": {}, + "wafv2:ListRegexPatternSets": {}, + "wafv2:ListRuleGroups": {}, + "wafv2:ListWebACLs": {}, + "wam:AuthenticatePackager": {}, + "wellarchitected:CreateProfile": {}, + "wellarchitected:CreateReviewTemplate": {}, + "wellarchitected:CreateWorkload": {}, + "wellarchitected:GetConsolidatedReport": {}, + "wellarchitected:GetProfileTemplate": {}, + "wellarchitected:ImportLens": {}, + "wellarchitected:ListLenses": {}, + "wellarchitected:ListNotifications": {}, + "wellarchitected:ListProfileNotifications": {}, + "wellarchitected:ListProfiles": {}, + "wellarchitected:ListReviewTemplates": {}, + "wellarchitected:ListShareInvitations": {}, + "wellarchitected:ListWorkloads": {}, + "wellarchitected:UpdateGlobalSettings": {}, + "wellarchitected:UpdateShareInvitation": {}, + "wickr:CreateNetwork": {}, + "wickr:ListNetworks": {}, + "wickr:ListTagsForResource": {}, + "wisdom:CreateAssistant": {}, + "wisdom:CreateKnowledgeBase": {}, + "wisdom:ListAssistants": {}, + "wisdom:ListKnowledgeBases": {}, + "wisdom:ListTagsForResource": {}, + "workdocs:AbortDocumentVersionUpload": {}, + "workdocs:ActivateUser": {}, + "workdocs:AddNotificationPermissions": {}, + "workdocs:AddResourcePermissions": {}, + "workdocs:AddUserToGroup": {}, + "workdocs:CheckAlias": {}, + "workdocs:CreateComment": {}, + "workdocs:CreateCustomMetadata": {}, + "workdocs:CreateFolder": {}, + "workdocs:CreateInstance": {}, + "workdocs:CreateLabels": {}, + "workdocs:CreateNotificationSubscription": {}, + "workdocs:CreateUser": {}, + "workdocs:DeactivateUser": {}, + "workdocs:DeleteComment": {}, + "workdocs:DeleteCustomMetadata": {}, + "workdocs:DeleteDocument": {}, + "workdocs:DeleteDocumentVersion": {}, + "workdocs:DeleteFolder": {}, + "workdocs:DeleteFolderContents": {}, + "workdocs:DeleteInstance": {}, + "workdocs:DeleteLabels": {}, + "workdocs:DeleteNotificationPermissions": {}, + "workdocs:DeleteNotificationSubscription": {}, + "workdocs:DeleteUser": {}, + "workdocs:DeregisterDirectory": {}, + "workdocs:DescribeActivities": {}, + "workdocs:DescribeAvailableDirectories": {}, + "workdocs:DescribeComments": {}, + "workdocs:DescribeDocumentVersions": {}, + "workdocs:DescribeFolderContents": {}, + "workdocs:DescribeGroups": {}, + "workdocs:DescribeInstances": {}, + "workdocs:DescribeNotificationPermissions": {}, + "workdocs:DescribeNotificationSubscriptions": {}, + "workdocs:DescribeResourcePermissions": {}, + "workdocs:DescribeRootFolders": {}, + "workdocs:DescribeUsers": {}, + "workdocs:DownloadDocumentVersion": {}, + "workdocs:GetCurrentUser": {}, + "workdocs:GetDocument": {}, + "workdocs:GetDocumentPath": {}, + "workdocs:GetDocumentVersion": {}, + "workdocs:GetFolder": {}, + "workdocs:GetFolderPath": {}, + "workdocs:GetGroup": {}, + "workdocs:GetResources": {}, + "workdocs:InitiateDocumentVersionUpload": {}, + "workdocs:RegisterDirectory": {}, + "workdocs:RemoveAllResourcePermissions": {}, + "workdocs:RemoveResourcePermission": {}, + "workdocs:RestoreDocumentVersions": {}, + "workdocs:SearchResources": {}, + "workdocs:UpdateDocument": {}, + "workdocs:UpdateDocumentVersion": {}, + "workdocs:UpdateFolder": {}, + "workdocs:UpdateInstanceAlias": {}, + "workdocs:UpdateUser": {}, + "workdocs:UpdateUserAdministrativeSettings": {}, + "worklink:CreateFleet": {}, + "worklink:ListFleets": {}, + "workmail:CreateOrganization": {}, + "workmail:DescribeDirectories": {}, + "workmail:DescribeKmsKeys": {}, + "workmail:DescribeOrganizations": {}, + "workmail:ListOrganizations": {}, + "workspaces-web:CreateBrowserSettings": {}, + "workspaces-web:CreateIpAccessSettings": {}, + "workspaces-web:CreateNetworkSettings": {}, + "workspaces-web:CreatePortal": {}, + "workspaces-web:CreateTrustStore": {}, + "workspaces-web:CreateUserAccessLoggingSettings": {}, + "workspaces-web:CreateUserSettings": {}, + "workspaces-web:ListBrowserSettings": {}, + "workspaces-web:ListIpAccessSettings": {}, + "workspaces-web:ListNetworkSettings": {}, + "workspaces-web:ListPortals": {}, + "workspaces-web:ListTagsForResource": {}, + "workspaces-web:ListTrustStoreCertificates": {}, + "workspaces-web:ListTrustStores": {}, + "workspaces-web:ListUserAccessLoggingSettings": {}, + "workspaces-web:ListUserSettings": {}, + "workspaces:CreateConnectionAlias": {}, + "workspaces:CreateIpGroup": {}, + "workspaces:CreateTags": {}, + "workspaces:DeleteTags": {}, + "workspaces:DescribeAccount": {}, + "workspaces:DescribeAccountModifications": {}, + "workspaces:DescribeApplications": {}, + "workspaces:DescribeConnectionAliases": {}, + "workspaces:DescribeTags": {}, + "workspaces:DescribeWorkspaceBundles": {}, + "workspaces:DescribeWorkspaceDirectories": {}, + "workspaces:DescribeWorkspaceImages": {}, + "workspaces:DescribeWorkspaces": {}, + "workspaces:DescribeWorkspacesConnectionStatus": {}, + "workspaces:ImportWorkspaceImage": {}, + "workspaces:ListAvailableManagementCidrRanges": {}, + "workspaces:ModifyAccount": {}, + "xray:BatchGetTraceSummaryById": {}, + "xray:BatchGetTraces": {}, + "xray:DeleteResourcePolicy": {}, + "xray:GetDistinctTraceGraphs": {}, + "xray:GetEncryptionConfig": {}, + "xray:GetGroups": {}, + "xray:GetInsight": {}, + "xray:GetInsightEvents": {}, + "xray:GetInsightImpactGraph": {}, + "xray:GetInsightSummaries": {}, + "xray:GetSamplingRules": {}, + "xray:GetSamplingStatisticSummaries": {}, + "xray:GetSamplingTargets": {}, + "xray:GetServiceGraph": {}, + "xray:GetTimeSeriesServiceStatistics": {}, + "xray:GetTraceGraph": {}, + "xray:GetTraceSummaries": {}, + "xray:Link": {}, + "xray:ListResourcePolicies": {}, + "xray:PutEncryptionConfig": {}, + "xray:PutResourcePolicy": {}, + "xray:PutTelemetryRecords": {}, + "xray:PutTraceSegments": {}, +} \ No newline at end of file diff --git a/pkg/iac/providers/aws/iam/iam.go b/pkg/iac/providers/aws/iam/iam.go new file mode 100644 index 000000000000..f12a5eae845f --- /dev/null +++ b/pkg/iac/providers/aws/iam/iam.go @@ -0,0 +1,120 @@ +package iam + +import ( + "github.com/liamg/iamgo" + + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type IAM struct { + PasswordPolicy PasswordPolicy + Policies []Policy + Groups []Group + Users []User + Roles []Role + ServerCertificates []ServerCertificate +} + +type ServerCertificate struct { + Metadata iacTypes.Metadata + Expiration iacTypes.TimeValue +} + +type Policy struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Document Document + Builtin iacTypes.BoolValue +} + +type Document struct { + Metadata iacTypes.Metadata + Parsed iamgo.Document + IsOffset bool + HasRefs bool +} + +func (d Document) ToRego() interface{} { + m := d.Metadata + doc, _ := d.Parsed.MarshalJSON() + input := map[string]interface{}{ + "filepath": m.Range().GetFilename(), + "startline": m.Range().GetStartLine(), + "endline": m.Range().GetEndLine(), + "managed": m.IsManaged(), + "explicit": m.IsExplicit(), + "value": string(doc), + "sourceprefix": m.Range().GetSourcePrefix(), + "fskey": iacTypes.CreateFSKey(m.Range().GetFS()), + "resource": m.Reference(), + } + + if m.Parent() != nil { + input["parent"] = m.Parent().ToRego() + } + + return input +} + +type Group struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Users []User + Policies []Policy +} + +type User struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Groups []Group + Policies []Policy + AccessKeys []AccessKey + MFADevices []MFADevice + LastAccess iacTypes.TimeValue +} + +func (u *User) HasLoggedIn() bool { + return u.LastAccess.GetMetadata().IsResolvable() && !u.LastAccess.IsNever() +} + +type MFADevice struct { + Metadata iacTypes.Metadata + IsVirtual iacTypes.BoolValue +} + +type AccessKey struct { + Metadata iacTypes.Metadata + AccessKeyId iacTypes.StringValue + Active iacTypes.BoolValue + CreationDate iacTypes.TimeValue + LastAccess iacTypes.TimeValue +} + +type Role struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Policies []Policy +} + +func (d Document) MetadataFromIamGo(r ...iamgo.Range) iacTypes.Metadata { + m := d.Metadata + if d.HasRefs { + return m + } + newRange := m.Range() + var start int + if !d.IsOffset { + start = newRange.GetStartLine() + } + for _, rng := range r { + newRange := iacTypes.NewRange( + newRange.GetLocalFilename(), + start+rng.StartLine, + start+rng.EndLine, + newRange.GetSourcePrefix(), + newRange.GetFS(), + ) + m = iacTypes.NewMetadata(newRange, m.Reference()).WithParent(m) + } + return m +} diff --git a/pkg/iac/providers/aws/iam/passwords.go b/pkg/iac/providers/aws/iam/passwords.go new file mode 100755 index 000000000000..09632ba029e1 --- /dev/null +++ b/pkg/iac/providers/aws/iam/passwords.go @@ -0,0 +1,16 @@ +package iam + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type PasswordPolicy struct { + Metadata iacTypes.Metadata + ReusePreventionCount iacTypes.IntValue + RequireLowercase iacTypes.BoolValue + RequireUppercase iacTypes.BoolValue + RequireNumbers iacTypes.BoolValue + RequireSymbols iacTypes.BoolValue + MaxAgeDays iacTypes.IntValue + MinimumLength iacTypes.IntValue +} diff --git a/pkg/iac/providers/aws/iam/wildcards.go b/pkg/iac/providers/aws/iam/wildcards.go new file mode 100755 index 000000000000..bee5c4e9637c --- /dev/null +++ b/pkg/iac/providers/aws/iam/wildcards.go @@ -0,0 +1,10 @@ +package iam + +func IsWildcardAllowed(actions ...string) (bool, string) { + for _, action := range actions { + if _, exist := allowedActionsForResourceWildcardsMap[action]; !exist { + return false, action + } + } + return true, "" +} diff --git a/pkg/iac/providers/aws/kinesis/kinesis.go b/pkg/iac/providers/aws/kinesis/kinesis.go new file mode 100755 index 000000000000..53e6c9d75f02 --- /dev/null +++ b/pkg/iac/providers/aws/kinesis/kinesis.go @@ -0,0 +1,24 @@ +package kinesis + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Kinesis struct { + Streams []Stream +} + +type Stream struct { + Metadata iacTypes.Metadata + Encryption Encryption +} + +const ( + EncryptionTypeKMS = "KMS" +) + +type Encryption struct { + Metadata iacTypes.Metadata + Type iacTypes.StringValue + KMSKeyID iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/kms/kms.go b/pkg/iac/providers/aws/kms/kms.go new file mode 100755 index 000000000000..a0ef986fd086 --- /dev/null +++ b/pkg/iac/providers/aws/kms/kms.go @@ -0,0 +1,19 @@ +package kms + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type KMS struct { + Keys []Key +} + +const ( + KeyUsageSignAndVerify = "SIGN_VERIFY" +) + +type Key struct { + Metadata iacTypes.Metadata + Usage iacTypes.StringValue + RotationEnabled iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/lambda/lambda.go b/pkg/iac/providers/aws/lambda/lambda.go new file mode 100755 index 000000000000..1d4bb483747d --- /dev/null +++ b/pkg/iac/providers/aws/lambda/lambda.go @@ -0,0 +1,31 @@ +package lambda + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Lambda struct { + Functions []Function +} + +type Function struct { + Metadata iacTypes.Metadata + Tracing Tracing + Permissions []Permission +} + +const ( + TracingModePassThrough = "PassThrough" + TracingModeActive = "Active" +) + +type Tracing struct { + Metadata iacTypes.Metadata + Mode iacTypes.StringValue +} + +type Permission struct { + Metadata iacTypes.Metadata + Principal iacTypes.StringValue + SourceARN iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/mq/mq.go b/pkg/iac/providers/aws/mq/mq.go new file mode 100755 index 000000000000..a0e383de6dc4 --- /dev/null +++ b/pkg/iac/providers/aws/mq/mq.go @@ -0,0 +1,21 @@ +package mq + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type MQ struct { + Brokers []Broker +} + +type Broker struct { + Metadata iacTypes.Metadata + PublicAccess iacTypes.BoolValue + Logging Logging +} + +type Logging struct { + Metadata iacTypes.Metadata + General iacTypes.BoolValue + Audit iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/msk/msk.go b/pkg/iac/providers/aws/msk/msk.go new file mode 100755 index 000000000000..25d398167ef3 --- /dev/null +++ b/pkg/iac/providers/aws/msk/msk.go @@ -0,0 +1,60 @@ +package msk + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type MSK struct { + Clusters []Cluster +} + +type Cluster struct { + Metadata iacTypes.Metadata + EncryptionInTransit EncryptionInTransit + EncryptionAtRest EncryptionAtRest + Logging Logging +} + +const ( + ClientBrokerEncryptionTLS = "TLS" + ClientBrokerEncryptionPlaintext = "PLAINTEXT" + ClientBrokerEncryptionTLSOrPlaintext = "TLS_PLAINTEXT" +) + +type EncryptionInTransit struct { + Metadata iacTypes.Metadata + ClientBroker iacTypes.StringValue +} + +type EncryptionAtRest struct { + Metadata iacTypes.Metadata + KMSKeyARN iacTypes.StringValue + Enabled iacTypes.BoolValue +} + +type Logging struct { + Metadata iacTypes.Metadata + Broker BrokerLogging +} + +type BrokerLogging struct { + Metadata iacTypes.Metadata + S3 S3Logging + Cloudwatch CloudwatchLogging + Firehose FirehoseLogging +} + +type S3Logging struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} + +type CloudwatchLogging struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} + +type FirehoseLogging struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/neptune/neptune.go b/pkg/iac/providers/aws/neptune/neptune.go new file mode 100755 index 000000000000..15966afe4d47 --- /dev/null +++ b/pkg/iac/providers/aws/neptune/neptune.go @@ -0,0 +1,21 @@ +package neptune + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Neptune struct { + Clusters []Cluster +} + +type Cluster struct { + Metadata iacTypes.Metadata + Logging Logging + StorageEncrypted iacTypes.BoolValue + KMSKeyID iacTypes.StringValue +} + +type Logging struct { + Metadata iacTypes.Metadata + Audit iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/provider.go b/pkg/iac/providers/aws/provider.go new file mode 100644 index 000000000000..475d1bd0691e --- /dev/null +++ b/pkg/iac/providers/aws/provider.go @@ -0,0 +1,77 @@ +package aws + +import "github.com/aquasecurity/trivy/pkg/iac/types" + +type TerraformProvider struct { + Metadata types.Metadata + // generic fields + Alias types.StringValue + Version types.StringValue + + // provider specific fields + AccessKey types.StringValue + AllowedAccountsIDs types.StringValueList + AssumeRole AssumeRole + AssumeRoleWithWebIdentity AssumeRoleWithWebIdentity + CustomCABundle types.StringValue + DefaultTags DefaultTags + EC2MetadataServiceEndpoint types.StringValue + EC2MetadataServiceEndpointMode types.StringValue + Endpoints types.MapValue + ForbiddenAccountIDs types.StringValueList + HttpProxy types.StringValue + IgnoreTags IgnoreTags + Insecure types.BoolValue + MaxRetries types.IntValue + Profile types.StringValue + Region types.StringValue + RetryMode types.StringValue + S3UsePathStyle types.BoolValue + S3USEast1RegionalEndpoint types.StringValue + SecretKey types.StringValue + SharedConfigFiles types.StringValueList + SharedCredentialsFiles types.StringValueList + SkipCredentialsValidation types.BoolValue + SkipMetadataAPICheck types.BoolValue + SkipRegionValidation types.BoolValue + SkipRequestingAccountID types.BoolValue + STSRegion types.StringValue + Token types.StringValue + UseDualstackEndpoint types.BoolValue + UseFIPSEndpoint types.BoolValue +} + +type AssumeRole struct { + Metadata types.Metadata + Duration types.StringValue + ExternalID types.StringValue + Policy types.StringValue + PolicyARNs types.StringValueList + RoleARN types.StringValue + SessionName types.StringValue + SourceIdentity types.StringValue + Tags types.MapValue + TransitiveTagKeys types.StringValueList +} + +type AssumeRoleWithWebIdentity struct { + Metadata types.Metadata + Duration types.StringValue + Policy types.StringValue + PolicyARNs types.StringValueList + RoleARN types.StringValue + SessionName types.StringValue + WebIdentityToken types.StringValue + WebIdentityTokenFile types.StringValue +} + +type IgnoreTags struct { + Metadata types.Metadata + Keys types.StringValueList + KeyPrefixes types.StringValueList +} + +type DefaultTags struct { + Metadata types.Metadata + Tags types.MapValue +} diff --git a/pkg/iac/providers/aws/rds/classic.go b/pkg/iac/providers/aws/rds/classic.go new file mode 100755 index 000000000000..41be09b743ef --- /dev/null +++ b/pkg/iac/providers/aws/rds/classic.go @@ -0,0 +1,13 @@ +package rds + +import ( + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Classic struct { + DBSecurityGroups []DBSecurityGroup +} + +type DBSecurityGroup struct { + Metadata types.Metadata +} diff --git a/pkg/iac/providers/aws/rds/rds.go b/pkg/iac/providers/aws/rds/rds.go new file mode 100755 index 000000000000..d5a55b188e63 --- /dev/null +++ b/pkg/iac/providers/aws/rds/rds.go @@ -0,0 +1,127 @@ +package rds + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type RDS struct { + Instances []Instance + Clusters []Cluster + Classic Classic + Snapshots []Snapshots + ParameterGroups []ParameterGroups +} + +type Instance struct { + Metadata iacTypes.Metadata + BackupRetentionPeriodDays iacTypes.IntValue + ReplicationSourceARN iacTypes.StringValue + PerformanceInsights PerformanceInsights + Encryption Encryption + PublicAccess iacTypes.BoolValue + Engine iacTypes.StringValue + IAMAuthEnabled iacTypes.BoolValue + DeletionProtection iacTypes.BoolValue + DBInstanceArn iacTypes.StringValue + StorageEncrypted iacTypes.BoolValue + DBInstanceIdentifier iacTypes.StringValue + DBParameterGroups []DBParameterGroupsList + TagList []TagList + EnabledCloudwatchLogsExports []iacTypes.StringValue + EngineVersion iacTypes.StringValue + AutoMinorVersionUpgrade iacTypes.BoolValue + MultiAZ iacTypes.BoolValue + PubliclyAccessible iacTypes.BoolValue + LatestRestorableTime iacTypes.TimeValue + ReadReplicaDBInstanceIdentifiers []iacTypes.StringValue +} + +type Cluster struct { + Metadata iacTypes.Metadata + BackupRetentionPeriodDays iacTypes.IntValue + ReplicationSourceARN iacTypes.StringValue + PerformanceInsights PerformanceInsights + Instances []ClusterInstance + Encryption Encryption + PublicAccess iacTypes.BoolValue + Engine iacTypes.StringValue + LatestRestorableTime iacTypes.TimeValue + AvailabilityZones []iacTypes.StringValue + DeletionProtection iacTypes.BoolValue + SkipFinalSnapshot iacTypes.BoolValue +} + +type Snapshots struct { + Metadata iacTypes.Metadata + DBSnapshotIdentifier iacTypes.StringValue + DBSnapshotArn iacTypes.StringValue + Encrypted iacTypes.BoolValue + KmsKeyId iacTypes.StringValue + SnapshotAttributes []DBSnapshotAttributes +} + +type Parameters struct { + Metadata iacTypes.Metadata + ParameterName iacTypes.StringValue + ParameterValue iacTypes.StringValue +} + +type ParameterGroups struct { + Metadata iacTypes.Metadata + DBParameterGroupName iacTypes.StringValue + DBParameterGroupFamily iacTypes.StringValue + Parameters []Parameters +} + +type DBSnapshotAttributes struct { + Metadata iacTypes.Metadata + AttributeValues []iacTypes.StringValue +} + +const ( + EngineAurora = "aurora" + EngineAuroraMysql = "aurora-mysql" + EngineAuroraPostgresql = "aurora-postgresql" + EngineMySQL = "mysql" + EnginePostgres = "postgres" + EngineCustomOracleEE = "custom-oracle-ee" + EngineOracleEE = "oracle-ee" + EngineOracleEECDB = "oracle-ee-cdb" + EngineOracleSE2 = "oracle-se2" + EngineOracleSE2CDB = "oracle-se2-cdb" + EngineSQLServerEE = "sqlserver-ee" + EngineSQLServerSE = "sqlserver-se" + EngineSQLServerEX = "sqlserver-ex" + EngineSQLServerWEB = "sqlserver-web" + EngineMariaDB = "mariadb" + EngineCustomSQLServerEE = "custom-sqlserver-ee" + EngineCustomSQLServerSE = "custom-sqlserver-se" + EngineCustomSQLServerWEB = "custom-sqlserver-web" +) + +type Encryption struct { + Metadata iacTypes.Metadata + EncryptStorage iacTypes.BoolValue + KMSKeyID iacTypes.StringValue +} + +type ClusterInstance struct { + Instance + ClusterIdentifier iacTypes.StringValue +} + +type PerformanceInsights struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + KMSKeyID iacTypes.StringValue +} + +type DBParameterGroupsList struct { + Metadata iacTypes.Metadata + DBParameterGroupName iacTypes.StringValue + KMSKeyID iacTypes.StringValue +} + +type TagList struct { + Metadata iacTypes.Metadata +} diff --git a/pkg/iac/providers/aws/redshift/redshift.go b/pkg/iac/providers/aws/redshift/redshift.go new file mode 100755 index 000000000000..cdafaa290525 --- /dev/null +++ b/pkg/iac/providers/aws/redshift/redshift.go @@ -0,0 +1,55 @@ +package redshift + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Redshift struct { + Clusters []Cluster + ReservedNodes []ReservedNode + ClusterParameters []ClusterParameter + SecurityGroups []SecurityGroup +} + +type SecurityGroup struct { + Metadata iacTypes.Metadata + Description iacTypes.StringValue +} + +type ReservedNode struct { + Metadata iacTypes.Metadata + NodeType iacTypes.StringValue +} + +type ClusterParameter struct { + Metadata iacTypes.Metadata + ParameterName iacTypes.StringValue + ParameterValue iacTypes.StringValue +} + +type Cluster struct { + Metadata iacTypes.Metadata + ClusterIdentifier iacTypes.StringValue + NodeType iacTypes.StringValue + VpcId iacTypes.StringValue + NumberOfNodes iacTypes.IntValue + PubliclyAccessible iacTypes.BoolValue + AllowVersionUpgrade iacTypes.BoolValue + MasterUsername iacTypes.StringValue + AutomatedSnapshotRetentionPeriod iacTypes.IntValue + LoggingEnabled iacTypes.BoolValue + EndPoint EndPoint + Encryption Encryption + SubnetGroupName iacTypes.StringValue +} + +type EndPoint struct { + Metadata iacTypes.Metadata + Port iacTypes.IntValue +} + +type Encryption struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + KMSKeyID iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/s3/bucket.go b/pkg/iac/providers/aws/s3/bucket.go new file mode 100755 index 000000000000..e884bbd4a7ff --- /dev/null +++ b/pkg/iac/providers/aws/s3/bucket.go @@ -0,0 +1,67 @@ +package s3 + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Bucket struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + PublicAccessBlock *PublicAccessBlock + BucketPolicies []iam.Policy + Encryption Encryption + Versioning Versioning + Logging Logging + ACL iacTypes.StringValue + BucketLocation iacTypes.StringValue + AccelerateConfigurationStatus iacTypes.StringValue + LifecycleConfiguration []Rules + Objects []Contents + Website *Website +} + +func (b *Bucket) HasPublicExposureACL() bool { + for _, publicACL := range []string{"public-read", "public-read-write", "website", "authenticated-read"} { + if b.ACL.EqualTo(publicACL) { + // if there is a public access block, check the public ACL blocks + if b.PublicAccessBlock != nil && b.PublicAccessBlock.Metadata.IsManaged() { + return b.PublicAccessBlock.IgnorePublicACLs.IsFalse() && b.PublicAccessBlock.BlockPublicACLs.IsFalse() + } + return true + } + } + return false +} + +type Logging struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + TargetBucket iacTypes.StringValue +} + +type Versioning struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + MFADelete iacTypes.BoolValue +} + +type Encryption struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + Algorithm iacTypes.StringValue + KMSKeyId iacTypes.StringValue +} + +type Rules struct { + Metadata iacTypes.Metadata + Status iacTypes.StringValue +} + +type Contents struct { + Metadata iacTypes.Metadata +} + +type Website struct { + Metadata iacTypes.Metadata +} diff --git a/pkg/iac/providers/aws/s3/bucket_public_access_block.go b/pkg/iac/providers/aws/s3/bucket_public_access_block.go new file mode 100755 index 000000000000..573cf6a1c0f7 --- /dev/null +++ b/pkg/iac/providers/aws/s3/bucket_public_access_block.go @@ -0,0 +1,23 @@ +package s3 + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type PublicAccessBlock struct { + Metadata iacTypes.Metadata + BlockPublicACLs iacTypes.BoolValue + BlockPublicPolicy iacTypes.BoolValue + IgnorePublicACLs iacTypes.BoolValue + RestrictPublicBuckets iacTypes.BoolValue +} + +func NewPublicAccessBlock(metadata iacTypes.Metadata) PublicAccessBlock { + return PublicAccessBlock{ + Metadata: metadata, + BlockPublicPolicy: iacTypes.BoolDefault(false, metadata), + BlockPublicACLs: iacTypes.BoolDefault(false, metadata), + IgnorePublicACLs: iacTypes.BoolDefault(false, metadata), + RestrictPublicBuckets: iacTypes.BoolDefault(false, metadata), + } +} diff --git a/pkg/iac/providers/aws/s3/s3.go b/pkg/iac/providers/aws/s3/s3.go new file mode 100755 index 000000000000..230269a9e660 --- /dev/null +++ b/pkg/iac/providers/aws/s3/s3.go @@ -0,0 +1,5 @@ +package s3 + +type S3 struct { + Buckets []Bucket +} diff --git a/pkg/iac/providers/aws/sam/api.go b/pkg/iac/providers/aws/sam/api.go new file mode 100644 index 000000000000..ba716f3e4d92 --- /dev/null +++ b/pkg/iac/providers/aws/sam/api.go @@ -0,0 +1,38 @@ +package sam + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type API struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + TracingEnabled iacTypes.BoolValue + DomainConfiguration DomainConfiguration + AccessLogging AccessLogging + RESTMethodSettings RESTMethodSettings +} + +type ApiAuth struct { + Metadata iacTypes.Metadata + ApiKeyRequired iacTypes.BoolValue +} + +type AccessLogging struct { + Metadata iacTypes.Metadata + CloudwatchLogGroupARN iacTypes.StringValue +} + +type DomainConfiguration struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + SecurityPolicy iacTypes.StringValue +} + +type RESTMethodSettings struct { + Metadata iacTypes.Metadata + CacheDataEncrypted iacTypes.BoolValue + LoggingEnabled iacTypes.BoolValue + DataTraceEnabled iacTypes.BoolValue + MetricsEnabled iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/sam/application.go b/pkg/iac/providers/aws/sam/application.go new file mode 100644 index 000000000000..99fb7b7cac5b --- /dev/null +++ b/pkg/iac/providers/aws/sam/application.go @@ -0,0 +1,17 @@ +package sam + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Application struct { + Metadata iacTypes.Metadata + LocationPath iacTypes.StringValue + Location Location +} + +type Location struct { + Metadata iacTypes.Metadata + ApplicationID iacTypes.StringValue + SemanticVersion iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/sam/function.go b/pkg/iac/providers/aws/sam/function.go new file mode 100644 index 000000000000..5fb47afd2b66 --- /dev/null +++ b/pkg/iac/providers/aws/sam/function.go @@ -0,0 +1,25 @@ +package sam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Function struct { + Metadata iacTypes.Metadata + FunctionName iacTypes.StringValue + Tracing iacTypes.StringValue + ManagedPolicies []iacTypes.StringValue + Policies []iam.Policy +} + +const ( + TracingModePassThrough = "PassThrough" + TracingModeActive = "Active" +) + +type Permission struct { + Metadata iacTypes.Metadata + Principal iacTypes.StringValue + SourceARN iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/sam/http_api.go b/pkg/iac/providers/aws/sam/http_api.go new file mode 100644 index 000000000000..eb1b488bc1bb --- /dev/null +++ b/pkg/iac/providers/aws/sam/http_api.go @@ -0,0 +1,20 @@ +package sam + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type HttpAPI struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + AccessLogging AccessLogging + DefaultRouteSettings RouteSettings + DomainConfiguration DomainConfiguration +} + +type RouteSettings struct { + Metadata iacTypes.Metadata + LoggingEnabled iacTypes.BoolValue + DataTraceEnabled iacTypes.BoolValue + DetailedMetricsEnabled iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/sam/sam.go b/pkg/iac/providers/aws/sam/sam.go new file mode 100644 index 000000000000..ed75777d8053 --- /dev/null +++ b/pkg/iac/providers/aws/sam/sam.go @@ -0,0 +1,10 @@ +package sam + +type SAM struct { + APIs []API + Applications []Application + Functions []Function + HttpAPIs []HttpAPI + SimpleTables []SimpleTable + StateMachines []StateMachine +} diff --git a/pkg/iac/providers/aws/sam/state_machine.go b/pkg/iac/providers/aws/sam/state_machine.go new file mode 100644 index 000000000000..caee979436de --- /dev/null +++ b/pkg/iac/providers/aws/sam/state_machine.go @@ -0,0 +1,25 @@ +package sam + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type StateMachine struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + LoggingConfiguration LoggingConfiguration + ManagedPolicies []iacTypes.StringValue + Policies []iam.Policy + Tracing TracingConfiguration +} + +type LoggingConfiguration struct { + Metadata iacTypes.Metadata + LoggingEnabled iacTypes.BoolValue +} + +type TracingConfiguration struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/sam/table.go b/pkg/iac/providers/aws/sam/table.go new file mode 100644 index 000000000000..33179771ee66 --- /dev/null +++ b/pkg/iac/providers/aws/sam/table.go @@ -0,0 +1,18 @@ +package sam + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type SimpleTable struct { + Metadata iacTypes.Metadata + TableName iacTypes.StringValue + SSESpecification SSESpecification +} + +type SSESpecification struct { + Metadata iacTypes.Metadata + + Enabled iacTypes.BoolValue + KMSMasterKeyID iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/sns/sns.go b/pkg/iac/providers/aws/sns/sns.go new file mode 100755 index 000000000000..49c2d370d56f --- /dev/null +++ b/pkg/iac/providers/aws/sns/sns.go @@ -0,0 +1,31 @@ +package sns + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type SNS struct { + Topics []Topic +} + +func NewTopic(arn string, metadata iacTypes.Metadata) *Topic { + return &Topic{ + Metadata: metadata, + ARN: iacTypes.String(arn, metadata), + Encryption: Encryption{ + Metadata: metadata, + KMSKeyID: iacTypes.StringDefault("", metadata), + }, + } +} + +type Topic struct { + Metadata iacTypes.Metadata + ARN iacTypes.StringValue + Encryption Encryption +} + +type Encryption struct { + Metadata iacTypes.Metadata + KMSKeyID iacTypes.StringValue +} diff --git a/pkg/iac/providers/aws/sqs/sqs.go b/pkg/iac/providers/aws/sqs/sqs.go new file mode 100755 index 000000000000..edafcb888a22 --- /dev/null +++ b/pkg/iac/providers/aws/sqs/sqs.go @@ -0,0 +1,23 @@ +package sqs + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type SQS struct { + Queues []Queue +} + +type Queue struct { + Metadata iacTypes.Metadata + QueueURL iacTypes.StringValue + Encryption Encryption + Policies []iam.Policy +} + +type Encryption struct { + Metadata iacTypes.Metadata + KMSKeyID iacTypes.StringValue + ManagedEncryption iacTypes.BoolValue +} diff --git a/pkg/iac/providers/aws/ssm/ssm.go b/pkg/iac/providers/aws/ssm/ssm.go new file mode 100755 index 000000000000..725e099f2b8d --- /dev/null +++ b/pkg/iac/providers/aws/ssm/ssm.go @@ -0,0 +1,16 @@ +package ssm + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type SSM struct { + Secrets []Secret +} + +type Secret struct { + Metadata iacTypes.Metadata + KMSKeyID iacTypes.StringValue +} + +const DefaultKMSKeyID = "alias/aws/secretsmanager" diff --git a/pkg/iac/providers/aws/workspaces/workspaces.go b/pkg/iac/providers/aws/workspaces/workspaces.go new file mode 100755 index 000000000000..beaf56eef645 --- /dev/null +++ b/pkg/iac/providers/aws/workspaces/workspaces.go @@ -0,0 +1,25 @@ +package workspaces + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type WorkSpaces struct { + WorkSpaces []WorkSpace +} + +type WorkSpace struct { + Metadata iacTypes.Metadata + RootVolume Volume + UserVolume Volume +} + +type Volume struct { + Metadata iacTypes.Metadata + Encryption Encryption +} + +type Encryption struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} diff --git a/pkg/iac/providers/azure/appservice/appservice.go b/pkg/iac/providers/azure/appservice/appservice.go new file mode 100755 index 000000000000..990adf98183a --- /dev/null +++ b/pkg/iac/providers/azure/appservice/appservice.go @@ -0,0 +1,30 @@ +package appservice + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type AppService struct { + Services []Service + FunctionApps []FunctionApp +} + +type Service struct { + Metadata iacTypes.Metadata + EnableClientCert iacTypes.BoolValue + Identity struct { + Type iacTypes.StringValue + } + Authentication struct { + Enabled iacTypes.BoolValue + } + Site struct { + EnableHTTP2 iacTypes.BoolValue + MinimumTLSVersion iacTypes.StringValue + } +} + +type FunctionApp struct { + Metadata iacTypes.Metadata + HTTPSOnly iacTypes.BoolValue +} diff --git a/pkg/iac/providers/azure/authorization/authorization.go b/pkg/iac/providers/azure/authorization/authorization.go new file mode 100755 index 000000000000..fee924724d3c --- /dev/null +++ b/pkg/iac/providers/azure/authorization/authorization.go @@ -0,0 +1,20 @@ +package authorization + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Authorization struct { + RoleDefinitions []RoleDefinition +} + +type RoleDefinition struct { + Metadata iacTypes.Metadata + Permissions []Permission + AssignableScopes []iacTypes.StringValue +} + +type Permission struct { + Metadata iacTypes.Metadata + Actions []iacTypes.StringValue +} diff --git a/pkg/iac/providers/azure/azure.go b/pkg/iac/providers/azure/azure.go new file mode 100755 index 000000000000..07774647a454 --- /dev/null +++ b/pkg/iac/providers/azure/azure.go @@ -0,0 +1,33 @@ +package azure + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/appservice" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/authorization" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/compute" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/container" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/database" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/datafactory" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/datalake" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/keyvault" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/monitor" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/network" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/storage" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/synapse" +) + +type Azure struct { + AppService appservice.AppService + Authorization authorization.Authorization + Compute compute.Compute + Container container.Container + Database database.Database + DataFactory datafactory.DataFactory + DataLake datalake.DataLake + KeyVault keyvault.KeyVault + Monitor monitor.Monitor + Network network.Network + SecurityCenter securitycenter.SecurityCenter + Storage storage.Storage + Synapse synapse.Synapse +} diff --git a/pkg/iac/providers/azure/compute/compute.go b/pkg/iac/providers/azure/compute/compute.go new file mode 100755 index 000000000000..d45105985afa --- /dev/null +++ b/pkg/iac/providers/azure/compute/compute.go @@ -0,0 +1,42 @@ +package compute + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Compute struct { + LinuxVirtualMachines []LinuxVirtualMachine + WindowsVirtualMachines []WindowsVirtualMachine + ManagedDisks []ManagedDisk +} + +type VirtualMachine struct { + Metadata iacTypes.Metadata + CustomData iacTypes.StringValue // NOT base64 encoded +} + +type LinuxVirtualMachine struct { + Metadata iacTypes.Metadata + VirtualMachine + OSProfileLinuxConfig OSProfileLinuxConfig +} + +type WindowsVirtualMachine struct { + Metadata iacTypes.Metadata + VirtualMachine +} + +type OSProfileLinuxConfig struct { + Metadata iacTypes.Metadata + DisablePasswordAuthentication iacTypes.BoolValue +} + +type ManagedDisk struct { + Metadata iacTypes.Metadata + Encryption Encryption +} + +type Encryption struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} diff --git a/pkg/iac/providers/azure/container/container.go b/pkg/iac/providers/azure/container/container.go new file mode 100755 index 000000000000..0af940d4ebf7 --- /dev/null +++ b/pkg/iac/providers/azure/container/container.go @@ -0,0 +1,38 @@ +package container + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Container struct { + KubernetesClusters []KubernetesCluster +} + +type KubernetesCluster struct { + Metadata iacTypes.Metadata + NetworkProfile NetworkProfile + EnablePrivateCluster iacTypes.BoolValue + APIServerAuthorizedIPRanges []iacTypes.StringValue + AddonProfile AddonProfile + RoleBasedAccessControl RoleBasedAccessControl +} + +type RoleBasedAccessControl struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} + +type AddonProfile struct { + Metadata iacTypes.Metadata + OMSAgent OMSAgent +} + +type OMSAgent struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} + +type NetworkProfile struct { + Metadata iacTypes.Metadata + NetworkPolicy iacTypes.StringValue // "", "calico", "azure" +} diff --git a/pkg/iac/providers/azure/database/database.go b/pkg/iac/providers/azure/database/database.go new file mode 100755 index 000000000000..34ac63d8f129 --- /dev/null +++ b/pkg/iac/providers/azure/database/database.go @@ -0,0 +1,68 @@ +package database + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Database struct { + MSSQLServers []MSSQLServer + MariaDBServers []MariaDBServer + MySQLServers []MySQLServer + PostgreSQLServers []PostgreSQLServer +} + +type MariaDBServer struct { + Metadata iacTypes.Metadata + Server +} + +type MySQLServer struct { + Metadata iacTypes.Metadata + Server +} + +type PostgreSQLServer struct { + Metadata iacTypes.Metadata + Server + Config PostgresSQLConfig +} + +type PostgresSQLConfig struct { + Metadata iacTypes.Metadata + LogCheckpoints iacTypes.BoolValue + ConnectionThrottling iacTypes.BoolValue + LogConnections iacTypes.BoolValue +} + +type Server struct { + Metadata iacTypes.Metadata + EnableSSLEnforcement iacTypes.BoolValue + MinimumTLSVersion iacTypes.StringValue + EnablePublicNetworkAccess iacTypes.BoolValue + FirewallRules []FirewallRule +} + +type MSSQLServer struct { + Metadata iacTypes.Metadata + Server + ExtendedAuditingPolicies []ExtendedAuditingPolicy + SecurityAlertPolicies []SecurityAlertPolicy +} + +type SecurityAlertPolicy struct { + Metadata iacTypes.Metadata + EmailAddresses []iacTypes.StringValue + DisabledAlerts []iacTypes.StringValue + EmailAccountAdmins iacTypes.BoolValue +} + +type ExtendedAuditingPolicy struct { + Metadata iacTypes.Metadata + RetentionInDays iacTypes.IntValue +} + +type FirewallRule struct { + Metadata iacTypes.Metadata + StartIP iacTypes.StringValue + EndIP iacTypes.StringValue +} diff --git a/pkg/iac/providers/azure/datafactory/datafactory.go b/pkg/iac/providers/azure/datafactory/datafactory.go new file mode 100755 index 000000000000..c8f0e91d010b --- /dev/null +++ b/pkg/iac/providers/azure/datafactory/datafactory.go @@ -0,0 +1,14 @@ +package datafactory + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type DataFactory struct { + DataFactories []Factory +} + +type Factory struct { + Metadata iacTypes.Metadata + EnablePublicNetwork iacTypes.BoolValue +} diff --git a/pkg/iac/providers/azure/datalake/datalake.go b/pkg/iac/providers/azure/datalake/datalake.go new file mode 100755 index 000000000000..a36f9a12c94a --- /dev/null +++ b/pkg/iac/providers/azure/datalake/datalake.go @@ -0,0 +1,14 @@ +package datalake + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type DataLake struct { + Stores []Store +} + +type Store struct { + Metadata iacTypes.Metadata + EnableEncryption iacTypes.BoolValue +} diff --git a/pkg/iac/providers/azure/keyvault/keyvault.go b/pkg/iac/providers/azure/keyvault/keyvault.go new file mode 100755 index 000000000000..8c263fa413f2 --- /dev/null +++ b/pkg/iac/providers/azure/keyvault/keyvault.go @@ -0,0 +1,34 @@ +package keyvault + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type KeyVault struct { + Vaults []Vault +} + +type Vault struct { + Metadata iacTypes.Metadata + Secrets []Secret + Keys []Key + EnablePurgeProtection iacTypes.BoolValue + SoftDeleteRetentionDays iacTypes.IntValue + NetworkACLs NetworkACLs +} + +type NetworkACLs struct { + Metadata iacTypes.Metadata + DefaultAction iacTypes.StringValue +} + +type Key struct { + Metadata iacTypes.Metadata + ExpiryDate iacTypes.TimeValue +} + +type Secret struct { + Metadata iacTypes.Metadata + ContentType iacTypes.StringValue + ExpiryDate iacTypes.TimeValue +} diff --git a/pkg/iac/providers/azure/monitor/monitor.go b/pkg/iac/providers/azure/monitor/monitor.go new file mode 100755 index 000000000000..e8ba51e40338 --- /dev/null +++ b/pkg/iac/providers/azure/monitor/monitor.go @@ -0,0 +1,22 @@ +package monitor + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Monitor struct { + LogProfiles []LogProfile +} + +type LogProfile struct { + Metadata iacTypes.Metadata + RetentionPolicy RetentionPolicy + Categories []iacTypes.StringValue + Locations []iacTypes.StringValue +} + +type RetentionPolicy struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + Days iacTypes.IntValue +} diff --git a/pkg/iac/providers/azure/network/network.go b/pkg/iac/providers/azure/network/network.go new file mode 100755 index 000000000000..71c56b62b465 --- /dev/null +++ b/pkg/iac/providers/azure/network/network.go @@ -0,0 +1,47 @@ +package network + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Network struct { + SecurityGroups []SecurityGroup + NetworkWatcherFlowLogs []NetworkWatcherFlowLog +} + +type SecurityGroup struct { + Metadata iacTypes.Metadata + Rules []SecurityGroupRule +} + +type SecurityGroupRule struct { + Metadata iacTypes.Metadata + Outbound iacTypes.BoolValue + Allow iacTypes.BoolValue + SourceAddresses []iacTypes.StringValue + SourcePorts []PortRange + DestinationAddresses []iacTypes.StringValue + DestinationPorts []PortRange + Protocol iacTypes.StringValue +} + +type PortRange struct { + Metadata iacTypes.Metadata + Start int + End int +} + +func (r PortRange) Includes(port int) bool { + return port >= r.Start && port <= r.End +} + +type NetworkWatcherFlowLog struct { + Metadata iacTypes.Metadata + RetentionPolicy RetentionPolicy +} + +type RetentionPolicy struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + Days iacTypes.IntValue +} diff --git a/pkg/iac/providers/azure/securitycenter/securitycenter.go b/pkg/iac/providers/azure/securitycenter/securitycenter.go new file mode 100755 index 000000000000..49dcd95d1ef0 --- /dev/null +++ b/pkg/iac/providers/azure/securitycenter/securitycenter.go @@ -0,0 +1,26 @@ +package securitycenter + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type SecurityCenter struct { + Contacts []Contact + Subscriptions []SubscriptionPricing +} + +type Contact struct { + Metadata iacTypes.Metadata + EnableAlertNotifications iacTypes.BoolValue + Phone iacTypes.StringValue +} + +const ( + TierFree = "Free" + TierStandard = "Standard" +) + +type SubscriptionPricing struct { + Metadata iacTypes.Metadata + Tier iacTypes.StringValue +} diff --git a/pkg/iac/providers/azure/storage/storage.go b/pkg/iac/providers/azure/storage/storage.go new file mode 100755 index 000000000000..cccc5d55eda1 --- /dev/null +++ b/pkg/iac/providers/azure/storage/storage.go @@ -0,0 +1,46 @@ +package storage + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Storage struct { + Accounts []Account +} + +type Account struct { + Metadata iacTypes.Metadata + NetworkRules []NetworkRule + EnforceHTTPS iacTypes.BoolValue + Containers []Container + QueueProperties QueueProperties + MinimumTLSVersion iacTypes.StringValue + Queues []Queue +} + +type Queue struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue +} + +type QueueProperties struct { + Metadata iacTypes.Metadata + EnableLogging iacTypes.BoolValue +} + +type NetworkRule struct { + Metadata iacTypes.Metadata + Bypass []iacTypes.StringValue + AllowByDefault iacTypes.BoolValue +} + +const ( + PublicAccessOff = "off" + PublicAccessBlob = "blob" + PublicAccessContainer = "container" +) + +type Container struct { + Metadata iacTypes.Metadata + PublicAccess iacTypes.StringValue +} diff --git a/pkg/iac/providers/azure/synapse/synapse.go b/pkg/iac/providers/azure/synapse/synapse.go new file mode 100755 index 000000000000..dc72175d7106 --- /dev/null +++ b/pkg/iac/providers/azure/synapse/synapse.go @@ -0,0 +1,14 @@ +package synapse + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Synapse struct { + Workspaces []Workspace +} + +type Workspace struct { + Metadata iacTypes.Metadata + EnableManagedVirtualNetwork iacTypes.BoolValue +} diff --git a/pkg/iac/providers/cloudstack/cloudstack.go b/pkg/iac/providers/cloudstack/cloudstack.go new file mode 100755 index 000000000000..d514eae513a3 --- /dev/null +++ b/pkg/iac/providers/cloudstack/cloudstack.go @@ -0,0 +1,9 @@ +package cloudstack + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/cloudstack/compute" +) + +type CloudStack struct { + Compute compute.Compute +} diff --git a/pkg/iac/providers/cloudstack/compute/compute.go b/pkg/iac/providers/cloudstack/compute/compute.go new file mode 100755 index 000000000000..cbfa55ba63d4 --- /dev/null +++ b/pkg/iac/providers/cloudstack/compute/compute.go @@ -0,0 +1,14 @@ +package compute + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Compute struct { + Instances []Instance +} + +type Instance struct { + Metadata iacTypes.Metadata + UserData iacTypes.StringValue // not b64 encoded pls +} diff --git a/pkg/iac/providers/digitalocean/compute/compute.go b/pkg/iac/providers/digitalocean/compute/compute.go new file mode 100755 index 000000000000..a0b0fe24761a --- /dev/null +++ b/pkg/iac/providers/digitalocean/compute/compute.go @@ -0,0 +1,50 @@ +package compute + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Compute struct { + Firewalls []Firewall + LoadBalancers []LoadBalancer + Droplets []Droplet + KubernetesClusters []KubernetesCluster +} + +type Firewall struct { + Metadata iacTypes.Metadata + OutboundRules []OutboundFirewallRule + InboundRules []InboundFirewallRule +} + +type KubernetesCluster struct { + Metadata iacTypes.Metadata + SurgeUpgrade iacTypes.BoolValue + AutoUpgrade iacTypes.BoolValue +} + +type LoadBalancer struct { + Metadata iacTypes.Metadata + ForwardingRules []ForwardingRule + RedirectHttpToHttps iacTypes.BoolValue +} + +type ForwardingRule struct { + Metadata iacTypes.Metadata + EntryProtocol iacTypes.StringValue +} + +type OutboundFirewallRule struct { + Metadata iacTypes.Metadata + DestinationAddresses []iacTypes.StringValue +} + +type InboundFirewallRule struct { + Metadata iacTypes.Metadata + SourceAddresses []iacTypes.StringValue +} + +type Droplet struct { + Metadata iacTypes.Metadata + SSHKeys []iacTypes.StringValue +} diff --git a/pkg/iac/providers/digitalocean/digitalocean.go b/pkg/iac/providers/digitalocean/digitalocean.go new file mode 100755 index 000000000000..d56240646279 --- /dev/null +++ b/pkg/iac/providers/digitalocean/digitalocean.go @@ -0,0 +1,11 @@ +package digitalocean + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/digitalocean/compute" + "github.com/aquasecurity/trivy/pkg/iac/providers/digitalocean/spaces" +) + +type DigitalOcean struct { + Compute compute.Compute + Spaces spaces.Spaces +} diff --git a/pkg/iac/providers/digitalocean/spaces/spaces.go b/pkg/iac/providers/digitalocean/spaces/spaces.go new file mode 100755 index 000000000000..2936e469da12 --- /dev/null +++ b/pkg/iac/providers/digitalocean/spaces/spaces.go @@ -0,0 +1,28 @@ +package spaces + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Spaces struct { + Buckets []Bucket +} + +type Bucket struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Objects []Object + ACL iacTypes.StringValue + ForceDestroy iacTypes.BoolValue + Versioning Versioning +} + +type Versioning struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} + +type Object struct { + Metadata iacTypes.Metadata + ACL iacTypes.StringValue +} diff --git a/pkg/iac/providers/dockerfile/dockerfile.go b/pkg/iac/providers/dockerfile/dockerfile.go new file mode 100644 index 000000000000..aed723989632 --- /dev/null +++ b/pkg/iac/providers/dockerfile/dockerfile.go @@ -0,0 +1,61 @@ +package dockerfile + +import ( + "reflect" + + "github.com/aquasecurity/trivy/pkg/iac/rego/convert" +) + +// NOTE: we are currently preserving mixed case json here for backward compatibility + +// Dockerfile represents a parsed Dockerfile +type Dockerfile struct { + Stages []Stage +} + +type Stage struct { + Name string + Commands []Command +} + +func (d Dockerfile) ToRego() interface{} { + return map[string]interface{}{ + "Stages": convert.SliceToRego(reflect.ValueOf(d.Stages)), + } +} + +func (s Stage) ToRego() interface{} { + return map[string]interface{}{ + "Name": s.Name, + "Commands": convert.SliceToRego(reflect.ValueOf(s.Commands)), + } +} + +// Command is the struct for each dockerfile command +type Command struct { + Cmd string + SubCmd string + Flags []string + Value []string + Original string + JSON bool + Stage int + Path string + StartLine int + EndLine int +} + +func (c Command) ToRego() interface{} { + return map[string]interface{}{ + "Cmd": c.Cmd, + "SubCmd": c.SubCmd, + "Flags": c.Flags, + "Value": c.Value, + "Original": c.Original, + "JSON": c.JSON, + "Stage": c.Stage, + "Path": c.Path, + "StartLine": c.StartLine, + "EndLine": c.EndLine, + } +} diff --git a/pkg/iac/providers/github/actions.go b/pkg/iac/providers/github/actions.go new file mode 100644 index 000000000000..0b28269e0c80 --- /dev/null +++ b/pkg/iac/providers/github/actions.go @@ -0,0 +1,19 @@ +package github + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Action struct { + Metadata iacTypes.Metadata + EnvironmentSecrets []EnvironmentSecret +} + +type EnvironmentSecret struct { + Metadata iacTypes.Metadata + Repository iacTypes.StringValue + Environment iacTypes.StringValue + SecretName iacTypes.StringValue + PlainTextValue iacTypes.StringValue + EncryptedValue iacTypes.StringValue +} diff --git a/pkg/iac/providers/github/branch_protections.go b/pkg/iac/providers/github/branch_protections.go new file mode 100755 index 000000000000..5d9f59599457 --- /dev/null +++ b/pkg/iac/providers/github/branch_protections.go @@ -0,0 +1,14 @@ +package github + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type BranchProtection struct { + Metadata iacTypes.Metadata + RequireSignedCommits iacTypes.BoolValue +} + +func (b BranchProtection) RequiresSignedCommits() bool { + return b.RequireSignedCommits.IsTrue() +} diff --git a/pkg/iac/providers/github/github.go b/pkg/iac/providers/github/github.go new file mode 100755 index 000000000000..449f94cecc30 --- /dev/null +++ b/pkg/iac/providers/github/github.go @@ -0,0 +1,7 @@ +package github + +type GitHub struct { + Repositories []Repository + EnvironmentSecrets []EnvironmentSecret + BranchProtections []BranchProtection +} diff --git a/pkg/iac/providers/github/repositories.go b/pkg/iac/providers/github/repositories.go new file mode 100755 index 000000000000..0fc4c96080d2 --- /dev/null +++ b/pkg/iac/providers/github/repositories.go @@ -0,0 +1,16 @@ +package github + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Repository struct { + Metadata iacTypes.Metadata + Public iacTypes.BoolValue + VulnerabilityAlerts iacTypes.BoolValue + Archived iacTypes.BoolValue +} + +func (r Repository) IsArchived() bool { + return r.Archived.IsTrue() +} diff --git a/pkg/iac/providers/google/bigquery/bigquery.go b/pkg/iac/providers/google/bigquery/bigquery.go new file mode 100755 index 000000000000..9a9bfb054dde --- /dev/null +++ b/pkg/iac/providers/google/bigquery/bigquery.go @@ -0,0 +1,26 @@ +package bigquery + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type BigQuery struct { + Datasets []Dataset +} + +type Dataset struct { + Metadata iacTypes.Metadata + ID iacTypes.StringValue + AccessGrants []AccessGrant +} + +const ( + SpecialGroupAllAuthenticatedUsers = "allAuthenticatedUsers" +) + +type AccessGrant struct { + Metadata iacTypes.Metadata + Role iacTypes.StringValue + Domain iacTypes.StringValue + SpecialGroup iacTypes.StringValue +} diff --git a/pkg/iac/providers/google/compute/compute.go b/pkg/iac/providers/google/compute/compute.go new file mode 100755 index 000000000000..ffa9db257bad --- /dev/null +++ b/pkg/iac/providers/google/compute/compute.go @@ -0,0 +1,9 @@ +package compute + +type Compute struct { + Disks []Disk + Networks []Network + SSLPolicies []SSLPolicy + ProjectMetadata ProjectMetadata + Instances []Instance +} diff --git a/pkg/iac/providers/google/compute/disk.go b/pkg/iac/providers/google/compute/disk.go new file mode 100755 index 000000000000..b636cb135651 --- /dev/null +++ b/pkg/iac/providers/google/compute/disk.go @@ -0,0 +1,17 @@ +package compute + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Disk struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Encryption DiskEncryption +} + +type DiskEncryption struct { + Metadata iacTypes.Metadata + RawKey iacTypes.BytesValue + KMSKeyLink iacTypes.StringValue +} diff --git a/pkg/iac/providers/google/compute/firewall.go b/pkg/iac/providers/google/compute/firewall.go new file mode 100755 index 000000000000..c122369d9954 --- /dev/null +++ b/pkg/iac/providers/google/compute/firewall.go @@ -0,0 +1,34 @@ +package compute + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Firewall struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + IngressRules []IngressRule + EgressRules []EgressRule + SourceTags []iacTypes.StringValue + TargetTags []iacTypes.StringValue +} + +type FirewallRule struct { + Metadata iacTypes.Metadata + Enforced iacTypes.BoolValue + IsAllow iacTypes.BoolValue + Protocol iacTypes.StringValue + Ports []iacTypes.IntValue +} + +type IngressRule struct { + Metadata iacTypes.Metadata + FirewallRule + SourceRanges []iacTypes.StringValue +} + +type EgressRule struct { + Metadata iacTypes.Metadata + FirewallRule + DestinationRanges []iacTypes.StringValue +} diff --git a/pkg/iac/providers/google/compute/instance.go b/pkg/iac/providers/google/compute/instance.go new file mode 100755 index 000000000000..d5189945f3b6 --- /dev/null +++ b/pkg/iac/providers/google/compute/instance.go @@ -0,0 +1,41 @@ +package compute + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Instance struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + NetworkInterfaces []NetworkInterface + ShieldedVM ShieldedVMConfig + ServiceAccount ServiceAccount + CanIPForward iacTypes.BoolValue + OSLoginEnabled iacTypes.BoolValue + EnableProjectSSHKeyBlocking iacTypes.BoolValue + EnableSerialPort iacTypes.BoolValue + BootDisks []Disk + AttachedDisks []Disk +} + +type ServiceAccount struct { + Metadata iacTypes.Metadata + Email iacTypes.StringValue + IsDefault iacTypes.BoolValue + Scopes []iacTypes.StringValue +} + +type NetworkInterface struct { + Metadata iacTypes.Metadata + Network *Network + SubNetwork *SubNetwork + HasPublicIP iacTypes.BoolValue + NATIP iacTypes.StringValue +} + +type ShieldedVMConfig struct { + Metadata iacTypes.Metadata + SecureBootEnabled iacTypes.BoolValue + IntegrityMonitoringEnabled iacTypes.BoolValue + VTPMEnabled iacTypes.BoolValue +} diff --git a/pkg/iac/providers/google/compute/metadata.go b/pkg/iac/providers/google/compute/metadata.go new file mode 100755 index 000000000000..9083854ba2af --- /dev/null +++ b/pkg/iac/providers/google/compute/metadata.go @@ -0,0 +1,10 @@ +package compute + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type ProjectMetadata struct { + Metadata iacTypes.Metadata + EnableOSLogin iacTypes.BoolValue +} diff --git a/pkg/iac/providers/google/compute/network.go b/pkg/iac/providers/google/compute/network.go new file mode 100755 index 000000000000..c8e7d0b3d56c --- /dev/null +++ b/pkg/iac/providers/google/compute/network.go @@ -0,0 +1,11 @@ +package compute + +import ( + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Network struct { + Metadata types.Metadata + Firewall *Firewall + Subnetworks []SubNetwork +} diff --git a/pkg/iac/providers/google/compute/ssl_policy.go b/pkg/iac/providers/google/compute/ssl_policy.go new file mode 100755 index 000000000000..38ee07c047f4 --- /dev/null +++ b/pkg/iac/providers/google/compute/ssl_policy.go @@ -0,0 +1,12 @@ +package compute + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type SSLPolicy struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Profile iacTypes.StringValue + MinimumTLSVersion iacTypes.StringValue +} diff --git a/pkg/iac/providers/google/compute/subnetwork.go b/pkg/iac/providers/google/compute/subnetwork.go new file mode 100755 index 000000000000..a0bd03d2a71c --- /dev/null +++ b/pkg/iac/providers/google/compute/subnetwork.go @@ -0,0 +1,12 @@ +package compute + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type SubNetwork struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Purpose iacTypes.StringValue + EnableFlowLogs iacTypes.BoolValue +} diff --git a/pkg/iac/providers/google/dns/dns.go b/pkg/iac/providers/google/dns/dns.go new file mode 100755 index 000000000000..93bc6fbb02c2 --- /dev/null +++ b/pkg/iac/providers/google/dns/dns.go @@ -0,0 +1,31 @@ +package dns + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type DNS struct { + ManagedZones []ManagedZone +} + +type ManagedZone struct { + Metadata iacTypes.Metadata + DNSSec DNSSec + Visibility iacTypes.StringValue +} + +func (m ManagedZone) IsPrivate() bool { + return m.Visibility.EqualTo("private", iacTypes.IgnoreCase) +} + +type DNSSec struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + DefaultKeySpecs []KeySpecs +} + +type KeySpecs struct { + Metadata iacTypes.Metadata + Algorithm iacTypes.StringValue + KeyType iacTypes.StringValue +} diff --git a/pkg/iac/providers/google/gke/gke.go b/pkg/iac/providers/google/gke/gke.go new file mode 100755 index 000000000000..3fe346f82f6e --- /dev/null +++ b/pkg/iac/providers/google/gke/gke.go @@ -0,0 +1,86 @@ +package gke + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type GKE struct { + Clusters []Cluster +} + +type Cluster struct { + Metadata iacTypes.Metadata + NodePools []NodePool + IPAllocationPolicy IPAllocationPolicy + MasterAuthorizedNetworks MasterAuthorizedNetworks + NetworkPolicy NetworkPolicy + PrivateCluster PrivateCluster + LoggingService iacTypes.StringValue + MonitoringService iacTypes.StringValue + MasterAuth MasterAuth + NodeConfig NodeConfig + EnableShieldedNodes iacTypes.BoolValue + EnableLegacyABAC iacTypes.BoolValue + ResourceLabels iacTypes.MapValue + RemoveDefaultNodePool iacTypes.BoolValue + EnableAutpilot iacTypes.BoolValue + DatapathProvider iacTypes.StringValue +} + +type NodeConfig struct { + Metadata iacTypes.Metadata + ImageType iacTypes.StringValue + WorkloadMetadataConfig WorkloadMetadataConfig + ServiceAccount iacTypes.StringValue + EnableLegacyEndpoints iacTypes.BoolValue +} + +type WorkloadMetadataConfig struct { + Metadata iacTypes.Metadata + NodeMetadata iacTypes.StringValue +} + +type MasterAuth struct { + Metadata iacTypes.Metadata + ClientCertificate ClientCertificate + Username iacTypes.StringValue + Password iacTypes.StringValue +} + +type ClientCertificate struct { + Metadata iacTypes.Metadata + IssueCertificate iacTypes.BoolValue +} + +type PrivateCluster struct { + Metadata iacTypes.Metadata + EnablePrivateNodes iacTypes.BoolValue +} + +type NetworkPolicy struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} + +type MasterAuthorizedNetworks struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue + CIDRs []iacTypes.StringValue +} + +type IPAllocationPolicy struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} + +type NodePool struct { + Metadata iacTypes.Metadata + Management Management + NodeConfig NodeConfig +} + +type Management struct { + Metadata iacTypes.Metadata + EnableAutoRepair iacTypes.BoolValue + EnableAutoUpgrade iacTypes.BoolValue +} diff --git a/pkg/iac/providers/google/google.go b/pkg/iac/providers/google/google.go new file mode 100755 index 000000000000..4616c9f0089d --- /dev/null +++ b/pkg/iac/providers/google/google.go @@ -0,0 +1,23 @@ +package google + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/google/bigquery" + "github.com/aquasecurity/trivy/pkg/iac/providers/google/compute" + "github.com/aquasecurity/trivy/pkg/iac/providers/google/dns" + "github.com/aquasecurity/trivy/pkg/iac/providers/google/gke" + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/google/kms" + "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" + "github.com/aquasecurity/trivy/pkg/iac/providers/google/storage" +) + +type Google struct { + BigQuery bigquery.BigQuery + Compute compute.Compute + DNS dns.DNS + GKE gke.GKE + KMS kms.KMS + IAM iam.IAM + SQL sql.SQL + Storage storage.Storage +} diff --git a/pkg/iac/providers/google/iam/iam.go b/pkg/iac/providers/google/iam/iam.go new file mode 100755 index 000000000000..c48192e76b97 --- /dev/null +++ b/pkg/iac/providers/google/iam/iam.go @@ -0,0 +1,88 @@ +package iam + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type IAM struct { + Organizations []Organization + WorkloadIdentityPoolProviders []WorkloadIdentityPoolProvider +} + +type Organization struct { + Metadata iacTypes.Metadata + Folders []Folder + Projects []Project + Members []Member + Bindings []Binding +} + +type Folder struct { + Metadata iacTypes.Metadata + Folders []Folder + Projects []Project + Members []Member + Bindings []Binding +} + +type Project struct { + Metadata iacTypes.Metadata + AutoCreateNetwork iacTypes.BoolValue + Members []Member + Bindings []Binding +} + +type Binding struct { + Metadata iacTypes.Metadata + Members []iacTypes.StringValue + Role iacTypes.StringValue + IncludesDefaultServiceAccount iacTypes.BoolValue +} + +type Member struct { + Metadata iacTypes.Metadata + Member iacTypes.StringValue + Role iacTypes.StringValue + DefaultServiceAccount iacTypes.BoolValue +} + +type WorkloadIdentityPoolProvider struct { + Metadata iacTypes.Metadata + WorkloadIdentityPoolId iacTypes.StringValue + WorkloadIdentityPoolProviderId iacTypes.StringValue + AttributeCondition iacTypes.StringValue +} + +func (p *IAM) AllProjects() []Project { + var projects []Project + for _, org := range p.Organizations { + projects = append(projects, org.Projects...) + for _, folder := range org.Folders { + projects = append(projects, folder.Projects...) + for _, desc := range folder.AllFolders() { + projects = append(projects, desc.Projects...) + } + } + } + return projects +} + +func (p *IAM) AllFolders() []Folder { + var folders []Folder + for _, org := range p.Organizations { + folders = append(folders, org.Folders...) + for _, folder := range org.Folders { + folders = append(folders, folder.AllFolders()...) + } + } + return folders +} + +func (f *Folder) AllFolders() []Folder { + var folders []Folder + for _, folder := range f.Folders { + folders = append(folders, folder) + folders = append(folders, folder.AllFolders()...) + } + return folders +} diff --git a/pkg/iac/providers/google/kms/kms.go b/pkg/iac/providers/google/kms/kms.go new file mode 100755 index 000000000000..4247db119e13 --- /dev/null +++ b/pkg/iac/providers/google/kms/kms.go @@ -0,0 +1,19 @@ +package kms + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type KMS struct { + KeyRings []KeyRing +} + +type KeyRing struct { + Metadata iacTypes.Metadata + Keys []Key +} + +type Key struct { + Metadata iacTypes.Metadata + RotationPeriodSeconds iacTypes.IntValue +} diff --git a/pkg/iac/providers/google/sql/sql.go b/pkg/iac/providers/google/sql/sql.go new file mode 100755 index 000000000000..18778dd1daef --- /dev/null +++ b/pkg/iac/providers/google/sql/sql.go @@ -0,0 +1,78 @@ +package sql + +import ( + "strings" + + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type SQL struct { + Instances []DatabaseInstance +} + +const ( + DatabaseFamilyMySQL = "MYSQL" + DatabaseFamilyPostgres = "POSTGRES" + DatabaseFamilySQLServer = "SQLSERVER" +) + +const ( + DatabaseVersionMySQL_5_6 = "MYSQL_5_6" + DatabaseVersionMySQL_5_7 = "MYSQL_5_7" + DatabaseVersionMySQL_8_0 = "MYSQL_8_0" + DatabaseVersionPostgres_9_6 = "POSTGRES_9_6" + DatabaseVersionPostgres_10 = "POSTGRES_10" + DatabaseVersionPostgres_11 = "POSTGRES_11" + DatabaseVersionPostgres_12 = "POSTGRES_12" + DatabaseVersionPostgres_13 = "POSTGRES_13" + DatabaseVersionSQLServer_2017_STANDARD = "SQLSERVER_2017_STANDARD" + DatabaseVersionSQLServer_2017_ENTERPRISE = "SQLSERVER_2017_ENTERPRISE" + DatabaseVersionSQLServer_2017_EXPRESS = "SQLSERVER_2017_EXPRESS" + DatabaseVersionSQLServer_2017_WEB = "SQLSERVER_2017_WEB" +) + +type DatabaseInstance struct { + Metadata iacTypes.Metadata + DatabaseVersion iacTypes.StringValue + Settings Settings + IsReplica iacTypes.BoolValue +} + +type Settings struct { + Metadata iacTypes.Metadata + Flags Flags + Backups Backups + IPConfiguration IPConfiguration +} +type Flags struct { + Metadata iacTypes.Metadata + LogTempFileSize iacTypes.IntValue + LocalInFile iacTypes.BoolValue + ContainedDatabaseAuthentication iacTypes.BoolValue + CrossDBOwnershipChaining iacTypes.BoolValue + LogCheckpoints iacTypes.BoolValue + LogConnections iacTypes.BoolValue + LogDisconnections iacTypes.BoolValue + LogLockWaits iacTypes.BoolValue + LogMinMessages iacTypes.StringValue // FATAL, PANIC, LOG, ERROR, WARN + LogMinDurationStatement iacTypes.IntValue +} + +type Backups struct { + Metadata iacTypes.Metadata + Enabled iacTypes.BoolValue +} + +type IPConfiguration struct { + Metadata iacTypes.Metadata + RequireTLS iacTypes.BoolValue + EnableIPv4 iacTypes.BoolValue + AuthorizedNetworks []struct { + Name iacTypes.StringValue + CIDR iacTypes.StringValue + } +} + +func (i *DatabaseInstance) DatabaseFamily() string { + return strings.Split(i.DatabaseVersion.Value(), "_")[0] +} diff --git a/pkg/iac/providers/google/storage/storage.go b/pkg/iac/providers/google/storage/storage.go new file mode 100755 index 000000000000..7650474b8640 --- /dev/null +++ b/pkg/iac/providers/google/storage/storage.go @@ -0,0 +1,25 @@ +package storage + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Storage struct { + Buckets []Bucket +} + +type Bucket struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Location iacTypes.StringValue + EnableUniformBucketLevelAccess iacTypes.BoolValue + Members []iam.Member + Bindings []iam.Binding + Encryption BucketEncryption +} + +type BucketEncryption struct { + Metadata iacTypes.Metadata + DefaultKMSKeyName iacTypes.StringValue +} diff --git a/pkg/iac/providers/kubernetes/kubernetes.go b/pkg/iac/providers/kubernetes/kubernetes.go new file mode 100755 index 000000000000..cf71291161a1 --- /dev/null +++ b/pkg/iac/providers/kubernetes/kubernetes.go @@ -0,0 +1,38 @@ +package kubernetes + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Kubernetes struct { + NetworkPolicies []NetworkPolicy +} + +type NetworkPolicy struct { + Metadata iacTypes.Metadata + Spec NetworkPolicySpec +} + +type NetworkPolicySpec struct { + Metadata iacTypes.Metadata + Egress Egress + Ingress Ingress +} + +type Egress struct { + Metadata iacTypes.Metadata + Ports []Port + DestinationCIDRs []iacTypes.StringValue +} + +type Ingress struct { + Metadata iacTypes.Metadata + Ports []Port + SourceCIDRs []iacTypes.StringValue +} + +type Port struct { + Metadata iacTypes.Metadata + Number iacTypes.StringValue // e.g. "http" or "80" + Protocol iacTypes.StringValue +} diff --git a/pkg/iac/providers/nifcloud/computing/computing.go b/pkg/iac/providers/nifcloud/computing/computing.go new file mode 100755 index 000000000000..aaef2361bf98 --- /dev/null +++ b/pkg/iac/providers/nifcloud/computing/computing.go @@ -0,0 +1,6 @@ +package computing + +type Computing struct { + SecurityGroups []SecurityGroup + Instances []Instance +} diff --git a/pkg/iac/providers/nifcloud/computing/instance.go b/pkg/iac/providers/nifcloud/computing/instance.go new file mode 100644 index 000000000000..2b729920f37f --- /dev/null +++ b/pkg/iac/providers/nifcloud/computing/instance.go @@ -0,0 +1,16 @@ +package computing + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Instance struct { + Metadata iacTypes.Metadata + SecurityGroup iacTypes.StringValue + NetworkInterfaces []NetworkInterface +} + +type NetworkInterface struct { + Metadata iacTypes.Metadata + NetworkID iacTypes.StringValue +} diff --git a/pkg/iac/providers/nifcloud/computing/security_group.go b/pkg/iac/providers/nifcloud/computing/security_group.go new file mode 100644 index 000000000000..f707d8a2825a --- /dev/null +++ b/pkg/iac/providers/nifcloud/computing/security_group.go @@ -0,0 +1,18 @@ +package computing + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type SecurityGroup struct { + Metadata iacTypes.Metadata + Description iacTypes.StringValue + IngressRules []SecurityGroupRule + EgressRules []SecurityGroupRule +} + +type SecurityGroupRule struct { + Metadata iacTypes.Metadata + Description iacTypes.StringValue + CIDR iacTypes.StringValue +} diff --git a/pkg/iac/providers/nifcloud/dns/dns.go b/pkg/iac/providers/nifcloud/dns/dns.go new file mode 100755 index 000000000000..7351506d7f6f --- /dev/null +++ b/pkg/iac/providers/nifcloud/dns/dns.go @@ -0,0 +1,5 @@ +package dns + +type DNS struct { + Records []Record +} diff --git a/pkg/iac/providers/nifcloud/dns/record.go b/pkg/iac/providers/nifcloud/dns/record.go new file mode 100644 index 000000000000..44275dfb27d0 --- /dev/null +++ b/pkg/iac/providers/nifcloud/dns/record.go @@ -0,0 +1,13 @@ +package dns + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +const ZoneRegistrationAuthTxt = "nifty-dns-verify=" + +type Record struct { + Metadata iacTypes.Metadata + Type iacTypes.StringValue + Record iacTypes.StringValue +} diff --git a/pkg/iac/providers/nifcloud/nas/nas.go b/pkg/iac/providers/nifcloud/nas/nas.go new file mode 100755 index 000000000000..e73a9c9efd70 --- /dev/null +++ b/pkg/iac/providers/nifcloud/nas/nas.go @@ -0,0 +1,6 @@ +package nas + +type NAS struct { + NASSecurityGroups []NASSecurityGroup + NASInstances []NASInstance +} diff --git a/pkg/iac/providers/nifcloud/nas/nas_instance.go b/pkg/iac/providers/nifcloud/nas/nas_instance.go new file mode 100644 index 000000000000..90567629ba09 --- /dev/null +++ b/pkg/iac/providers/nifcloud/nas/nas_instance.go @@ -0,0 +1,10 @@ +package nas + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type NASInstance struct { + Metadata iacTypes.Metadata + NetworkID iacTypes.StringValue +} diff --git a/pkg/iac/providers/nifcloud/nas/nas_security_group.go b/pkg/iac/providers/nifcloud/nas/nas_security_group.go new file mode 100644 index 000000000000..f5351f7ab150 --- /dev/null +++ b/pkg/iac/providers/nifcloud/nas/nas_security_group.go @@ -0,0 +1,11 @@ +package nas + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type NASSecurityGroup struct { + Metadata iacTypes.Metadata + Description iacTypes.StringValue + CIDRs []iacTypes.StringValue +} diff --git a/pkg/iac/providers/nifcloud/network/elastic_load_balancer.go b/pkg/iac/providers/nifcloud/network/elastic_load_balancer.go new file mode 100644 index 000000000000..82d977fcf8fa --- /dev/null +++ b/pkg/iac/providers/nifcloud/network/elastic_load_balancer.go @@ -0,0 +1,16 @@ +package network + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type ElasticLoadBalancer struct { + Metadata iacTypes.Metadata + NetworkInterfaces []NetworkInterface + Listeners []ElasticLoadBalancerListener +} + +type ElasticLoadBalancerListener struct { + Metadata iacTypes.Metadata + Protocol iacTypes.StringValue +} diff --git a/pkg/iac/providers/nifcloud/network/load_balancer.go b/pkg/iac/providers/nifcloud/network/load_balancer.go new file mode 100644 index 000000000000..860a50614f8a --- /dev/null +++ b/pkg/iac/providers/nifcloud/network/load_balancer.go @@ -0,0 +1,16 @@ +package network + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type LoadBalancer struct { + Metadata iacTypes.Metadata + Listeners []LoadBalancerListener +} + +type LoadBalancerListener struct { + Metadata iacTypes.Metadata + Protocol iacTypes.StringValue + TLSPolicy iacTypes.StringValue +} diff --git a/pkg/iac/providers/nifcloud/network/network.go b/pkg/iac/providers/nifcloud/network/network.go new file mode 100755 index 000000000000..dc337e0a2470 --- /dev/null +++ b/pkg/iac/providers/nifcloud/network/network.go @@ -0,0 +1,16 @@ +package network + +import iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + +type Network struct { + ElasticLoadBalancers []ElasticLoadBalancer + LoadBalancers []LoadBalancer + Routers []Router + VpnGateways []VpnGateway +} + +type NetworkInterface struct { + Metadata iacTypes.Metadata + NetworkID iacTypes.StringValue + IsVipNetwork iacTypes.BoolValue +} diff --git a/pkg/iac/providers/nifcloud/network/router.go b/pkg/iac/providers/nifcloud/network/router.go new file mode 100644 index 000000000000..c26aa9115729 --- /dev/null +++ b/pkg/iac/providers/nifcloud/network/router.go @@ -0,0 +1,11 @@ +package network + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Router struct { + Metadata iacTypes.Metadata + SecurityGroup iacTypes.StringValue + NetworkInterfaces []NetworkInterface +} diff --git a/pkg/iac/providers/nifcloud/network/vpn_gateway.go b/pkg/iac/providers/nifcloud/network/vpn_gateway.go new file mode 100644 index 000000000000..54ff74aab3d4 --- /dev/null +++ b/pkg/iac/providers/nifcloud/network/vpn_gateway.go @@ -0,0 +1,10 @@ +package network + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type VpnGateway struct { + Metadata iacTypes.Metadata + SecurityGroup iacTypes.StringValue +} diff --git a/pkg/iac/providers/nifcloud/nifcloud.go b/pkg/iac/providers/nifcloud/nifcloud.go new file mode 100755 index 000000000000..4126407925c6 --- /dev/null +++ b/pkg/iac/providers/nifcloud/nifcloud.go @@ -0,0 +1,19 @@ +package nifcloud + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/computing" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/dns" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/nas" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/network" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/rdb" + "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/sslcertificate" +) + +type Nifcloud struct { + Computing computing.Computing + DNS dns.DNS + NAS nas.NAS + Network network.Network + RDB rdb.RDB + SSLCertificate sslcertificate.SSLCertificate +} diff --git a/pkg/iac/providers/nifcloud/rdb/db_instance.go b/pkg/iac/providers/nifcloud/rdb/db_instance.go new file mode 100644 index 000000000000..be888d09210e --- /dev/null +++ b/pkg/iac/providers/nifcloud/rdb/db_instance.go @@ -0,0 +1,14 @@ +package rdb + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type DBInstance struct { + Metadata iacTypes.Metadata + BackupRetentionPeriodDays iacTypes.IntValue + Engine iacTypes.StringValue + EngineVersion iacTypes.StringValue + NetworkID iacTypes.StringValue + PublicAccess iacTypes.BoolValue +} diff --git a/pkg/iac/providers/nifcloud/rdb/db_security_group.go b/pkg/iac/providers/nifcloud/rdb/db_security_group.go new file mode 100644 index 000000000000..3c9e350e5cad --- /dev/null +++ b/pkg/iac/providers/nifcloud/rdb/db_security_group.go @@ -0,0 +1,11 @@ +package rdb + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type DBSecurityGroup struct { + Metadata iacTypes.Metadata + Description iacTypes.StringValue + CIDRs []iacTypes.StringValue +} diff --git a/pkg/iac/providers/nifcloud/rdb/rdb.go b/pkg/iac/providers/nifcloud/rdb/rdb.go new file mode 100755 index 000000000000..4aea31980708 --- /dev/null +++ b/pkg/iac/providers/nifcloud/rdb/rdb.go @@ -0,0 +1,6 @@ +package rdb + +type RDB struct { + DBSecurityGroups []DBSecurityGroup + DBInstances []DBInstance +} diff --git a/pkg/iac/providers/nifcloud/sslcertificate/server_certificate.go b/pkg/iac/providers/nifcloud/sslcertificate/server_certificate.go new file mode 100644 index 000000000000..86dc479851a3 --- /dev/null +++ b/pkg/iac/providers/nifcloud/sslcertificate/server_certificate.go @@ -0,0 +1,10 @@ +package sslcertificate + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type ServerCertificate struct { + Metadata iacTypes.Metadata + Expiration iacTypes.TimeValue +} diff --git a/pkg/iac/providers/nifcloud/sslcertificate/ssl_certificate.go b/pkg/iac/providers/nifcloud/sslcertificate/ssl_certificate.go new file mode 100755 index 000000000000..7ab46d870b16 --- /dev/null +++ b/pkg/iac/providers/nifcloud/sslcertificate/ssl_certificate.go @@ -0,0 +1,5 @@ +package sslcertificate + +type SSLCertificate struct { + ServerCertificates []ServerCertificate +} diff --git a/pkg/iac/providers/openstack/networking.go b/pkg/iac/providers/openstack/networking.go new file mode 100644 index 000000000000..944bc3fe9253 --- /dev/null +++ b/pkg/iac/providers/openstack/networking.go @@ -0,0 +1,27 @@ +package openstack + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Networking struct { + SecurityGroups []SecurityGroup +} + +type SecurityGroup struct { + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Description iacTypes.StringValue + Rules []SecurityGroupRule +} + +// SecurityGroupRule describes https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs/resources/networking_secgroup_rule_v2 +type SecurityGroupRule struct { + Metadata iacTypes.Metadata + IsIngress iacTypes.BoolValue + EtherType iacTypes.IntValue // 4 or 6 for ipv4/ipv6 + Protocol iacTypes.StringValue // e.g. tcp + PortMin iacTypes.IntValue + PortMax iacTypes.IntValue + CIDR iacTypes.StringValue +} diff --git a/pkg/iac/providers/openstack/openstack.go b/pkg/iac/providers/openstack/openstack.go new file mode 100755 index 000000000000..04a23f28fcee --- /dev/null +++ b/pkg/iac/providers/openstack/openstack.go @@ -0,0 +1,34 @@ +package openstack + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type OpenStack struct { + Compute Compute + Networking Networking +} + +type Compute struct { + Instances []Instance + Firewall Firewall +} + +type Firewall struct { + AllowRules []FirewallRule + DenyRules []FirewallRule +} + +type FirewallRule struct { + Metadata iacTypes.Metadata + Source iacTypes.StringValue + Destination iacTypes.StringValue + SourcePort iacTypes.StringValue + DestinationPort iacTypes.StringValue + Enabled iacTypes.BoolValue +} + +type Instance struct { + Metadata iacTypes.Metadata + AdminPassword iacTypes.StringValue +} diff --git a/pkg/iac/providers/oracle/oracle.go b/pkg/iac/providers/oracle/oracle.go new file mode 100755 index 000000000000..6d6a3ecbdfe5 --- /dev/null +++ b/pkg/iac/providers/oracle/oracle.go @@ -0,0 +1,18 @@ +package oracle + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Oracle struct { + Compute Compute +} + +type Compute struct { + AddressReservations []AddressReservation +} + +type AddressReservation struct { + Metadata iacTypes.Metadata + Pool iacTypes.StringValue // e.g. public-pool +} diff --git a/pkg/iac/providers/provider.go b/pkg/iac/providers/provider.go new file mode 100755 index 000000000000..cef13ee8f205 --- /dev/null +++ b/pkg/iac/providers/provider.go @@ -0,0 +1,51 @@ +package providers + +import ( + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// Provider is the provider that the check applies to +type Provider string + +const ( + UnknownProvider Provider = "" + AWSProvider Provider = "aws" + AzureProvider Provider = "azure" + CustomProvider Provider = "custom" + DigitalOceanProvider Provider = "digitalocean" + GeneralProvider Provider = "general" + GitHubProvider Provider = "github" + GoogleProvider Provider = "google" + KubernetesProvider Provider = "kubernetes" + OracleProvider Provider = "oracle" + OpenStackProvider Provider = "openstack" + NifcloudProvider Provider = "nifcloud" + CloudStackProvider Provider = "cloudstack" +) + +func RuleProviderToString(provider Provider) string { + return strings.ToUpper(string(provider)) +} + +func (p Provider) DisplayName() string { + switch p { + case "aws": + return strings.ToUpper(string(p)) + case "digitalocean": + return "Digital Ocean" + case "github": + return "GitHub" + case "openstack": + return "OpenStack" + case "cloudstack": + return "Cloudstack" + default: + return cases.Title(language.English).String(strings.ToLower(string(p))) + } +} +func (p Provider) ConstName() string { + return strings.ReplaceAll(p.DisplayName(), " ", "") +} diff --git a/pkg/iac/rego/build.go b/pkg/iac/rego/build.go new file mode 100644 index 000000000000..bfc62ee7b188 --- /dev/null +++ b/pkg/iac/rego/build.go @@ -0,0 +1,85 @@ +package rego + +import ( + "io/fs" + "path/filepath" + "strings" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/util" + + "github.com/aquasecurity/trivy/pkg/iac/rego/schemas" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func BuildSchemaSetFromPolicies(policies map[string]*ast.Module, paths []string, fsys fs.FS) (*ast.SchemaSet, bool, error) { + schemaSet := ast.NewSchemaSet() + schemaSet.Put(ast.MustParseRef("schema.input"), make(map[string]interface{})) // for backwards compat only + var customFound bool + for _, policy := range policies { + for _, annotation := range policy.Annotations { + for _, ss := range annotation.Schemas { + schemaName, err := ss.Schema.Ptr() + if err != nil { + continue + } + if schemaName != "input" { + if schema, ok := schemas.SchemaMap[types.Source(schemaName)]; ok { + customFound = true + schemaSet.Put(ast.MustParseRef(ss.Schema.String()), util.MustUnmarshalJSON([]byte(schema))) + } else { + b, err := findSchemaInFS(paths, fsys, schemaName) + if err != nil { + return schemaSet, true, err + } + if b != nil { + customFound = true + schemaSet.Put(ast.MustParseRef(ss.Schema.String()), util.MustUnmarshalJSON(b)) + } + } + } + } + } + } + + return schemaSet, customFound, nil +} + +// findSchemaInFS tries to find the schema anywhere in the specified FS +func findSchemaInFS(paths []string, srcFS fs.FS, schemaName string) ([]byte, error) { + var schema []byte + for _, path := range paths { + if err := fs.WalkDir(srcFS, sanitisePath(path), func(path string, info fs.DirEntry, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + if !IsJSONFile(info.Name()) { + return nil + } + if info.Name() == schemaName+".json" { + schema, err = fs.ReadFile(srcFS, filepath.ToSlash(path)) + if err != nil { + return err + } + return nil + } + return nil + }); err != nil { + return nil, err + } + } + return schema, nil +} + +func IsJSONFile(name string) bool { + return strings.HasSuffix(name, ".json") +} + +func sanitisePath(path string) string { + vol := filepath.VolumeName(path) + path = strings.TrimPrefix(path, vol) + return strings.TrimPrefix(strings.TrimPrefix(filepath.ToSlash(path), "./"), "/") +} diff --git a/pkg/iac/rego/convert/anonymous.go b/pkg/iac/rego/convert/anonymous.go new file mode 100644 index 000000000000..3563b0fccfc8 --- /dev/null +++ b/pkg/iac/rego/convert/anonymous.go @@ -0,0 +1,47 @@ +package convert + +import ( + "reflect" +) + +var converterInterface = reflect.TypeOf((*Converter)(nil)).Elem() + +func anonymousToRego(inputValue reflect.Value) interface{} { + + if inputValue.IsZero() { + return nil + } + + for inputValue.Type().Kind() == reflect.Interface { + if inputValue.IsNil() { + return nil + } + inputValue = inputValue.Elem() + } + + if inputValue.Type().Implements(converterInterface) { + returns := inputValue.MethodByName("ToRego").Call(nil) + return returns[0].Interface() + } + + for inputValue.Type().Kind() == reflect.Ptr { + if inputValue.IsNil() { + return nil + } + inputValue = inputValue.Elem() + } + + if inputValue.Type().Implements(converterInterface) { + returns := inputValue.MethodByName("ToRego").Call(nil) + return returns[0].Interface() + } + + switch kind := inputValue.Type().Kind(); kind { + case reflect.Struct: + return StructToRego(inputValue) + case reflect.Slice: + return SliceToRego(inputValue) + } + + return nil +} diff --git a/pkg/iac/rego/convert/converter.go b/pkg/iac/rego/convert/converter.go new file mode 100644 index 000000000000..e132d6875aa2 --- /dev/null +++ b/pkg/iac/rego/convert/converter.go @@ -0,0 +1,5 @@ +package convert + +type Converter interface { + ToRego() interface{} +} diff --git a/pkg/iac/rego/convert/slice.go b/pkg/iac/rego/convert/slice.go new file mode 100644 index 000000000000..8bb68a7fb551 --- /dev/null +++ b/pkg/iac/rego/convert/slice.go @@ -0,0 +1,32 @@ +package convert + +import ( + "reflect" +) + +func SliceToRego(inputValue reflect.Value) []interface{} { + + // make sure we have a struct literal + for inputValue.Type().Kind() == reflect.Ptr { + if inputValue.IsNil() { + return nil + } + inputValue = inputValue.Elem() + } + if inputValue.Type().Kind() != reflect.Slice { + panic("not a slice") + } + + output := make([]interface{}, inputValue.Len()) + + for i := 0; i < inputValue.Len(); i++ { + val := inputValue.Index(i) + if val.Type().Kind() == reflect.Ptr && val.IsZero() { + output[i] = nil + continue + } + output[i] = anonymousToRego(val) + } + + return output +} diff --git a/pkg/iac/rego/convert/slice_test.go b/pkg/iac/rego/convert/slice_test.go new file mode 100644 index 000000000000..5e071d0ed52a --- /dev/null +++ b/pkg/iac/rego/convert/slice_test.go @@ -0,0 +1,57 @@ +package convert + +import ( + "reflect" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" +) + +func Test_SliceConversion(t *testing.T) { + input := []struct { + X string + Y int + Z struct { + A float64 + } + }{ + {}, + } + input[0].Z.A = 123 + converted := SliceToRego(reflect.ValueOf(input)) + assert.Equal(t, []interface{}{map[string]interface{}{"z": map[string]interface{}{}}}, converted) +} + +func Test_SliceTypesConversion(t *testing.T) { + input := []types.StringValue{ + types.String("test1", types.NewTestMetadata()), + types.String("test2", types.NewTestMetadata()), + } + converted := SliceToRego(reflect.ValueOf(input)) + assert.Equal(t, []interface{}{ + map[string]interface{}{ + "value": "test1", + "filepath": "test.test", + "startline": 123, + "endline": 123, + "sourceprefix": "", + "managed": true, + "explicit": false, + "fskey": "", + "resource": "", + }, + map[string]interface{}{ + "value": "test2", + "filepath": "test.test", + "startline": 123, + "endline": 123, + "sourceprefix": "", + "managed": true, + "explicit": false, + "fskey": "", + "resource": "", + }, + }, converted) +} diff --git a/pkg/iac/rego/convert/struct.go b/pkg/iac/rego/convert/struct.go new file mode 100644 index 000000000000..c4819b96815e --- /dev/null +++ b/pkg/iac/rego/convert/struct.go @@ -0,0 +1,68 @@ +package convert + +import ( + "reflect" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type metadataProvider interface { + GetMetadata() types.Metadata +} + +var metadataInterface = reflect.TypeOf((*metadataProvider)(nil)).Elem() + +func StructToRego(inputValue reflect.Value) map[string]interface{} { + + // make sure we have a struct literal + for inputValue.Type().Kind() == reflect.Ptr || inputValue.Type().Kind() == reflect.Interface { + if inputValue.IsNil() { + return nil + } + inputValue = inputValue.Elem() + } + if inputValue.Type().Kind() != reflect.Struct { + panic("not a struct") + } + + output := make(map[string]interface{}, inputValue.NumField()) + + for i := 0; i < inputValue.NumField(); i++ { + field := inputValue.Field(i) + typ := inputValue.Type().Field(i) + name := typ.Name + if !typ.IsExported() { + continue + } + if field.Interface() == nil { + continue + } + val := anonymousToRego(reflect.ValueOf(field.Interface())) + if val == nil { + continue + } + key := strings.ToLower(name) + if _, ok := field.Interface().(types.Metadata); key == "metadata" && ok { + continue + } + output[strings.ToLower(name)] = val + } + + if inputValue.Type().Implements(metadataInterface) { + returns := inputValue.MethodByName("GetMetadata").Call(nil) + if metadata, ok := returns[0].Interface().(types.Metadata); ok { + output["__defsec_metadata"] = metadata.ToRego() + } + } else { + metaVal := inputValue.FieldByName("Metadata") + if metaVal.Kind() == reflect.Struct { + if meta, ok := metaVal.Interface().(types.Metadata); ok { + output["__defsec_metadata"] = meta.ToRego() + } + } + + } + + return output +} diff --git a/pkg/iac/rego/convert/struct_test.go b/pkg/iac/rego/convert/struct_test.go new file mode 100644 index 000000000000..ca72efabdedd --- /dev/null +++ b/pkg/iac/rego/convert/struct_test.go @@ -0,0 +1,21 @@ +package convert + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_StructConversion(t *testing.T) { + input := struct { + X string + Y int + Z struct { + A float64 + } + }{} + input.Z.A = 123 + converted := StructToRego(reflect.ValueOf(input)) + assert.Equal(t, map[string]interface{}{"z": map[string]interface{}{}}, converted) +} diff --git a/pkg/iac/rego/custom.go b/pkg/iac/rego/custom.go new file mode 100644 index 000000000000..c15b05a4577f --- /dev/null +++ b/pkg/iac/rego/custom.go @@ -0,0 +1,109 @@ +package rego + +import ( + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/types" +) + +func init() { + rego.RegisterBuiltin2(®o.Function{ + Name: "result.new", + Decl: types.NewFunction(types.Args(types.S, types.A), types.A), + }, + createResult, + ) + + rego.RegisterBuiltin1(®o.Function{ + Name: "isManaged", + Decl: types.NewFunction(types.Args(types.A), types.B), + }, + func(c rego.BuiltinContext, resource *ast.Term) (*ast.Term, error) { + metadata, err := createResult(c, ast.StringTerm(""), resource) + if err != nil { + return nil, err + } + return metadata.Get(ast.StringTerm("managed")), nil + }, + ) +} + +func createResult(ctx rego.BuiltinContext, msg, cause *ast.Term) (*ast.Term, error) { + + metadata := map[string]*ast.Term{ + "startline": ast.IntNumberTerm(0), + "endline": ast.IntNumberTerm(0), + "sourceprefix": ast.StringTerm(""), + "filepath": ast.StringTerm(""), + "explicit": ast.BooleanTerm(false), + "managed": ast.BooleanTerm(true), + "fskey": ast.StringTerm(""), + "resource": ast.StringTerm(""), + "parent": ast.NullTerm(), + } + if msg != nil { + metadata["msg"] = msg + } + + // universal + input := cause.Get(ast.StringTerm("__defsec_metadata")) + if input == nil { + // docker + input = cause + } + metadata = updateMetadata(metadata, input) + + if term := input.Get(ast.StringTerm("parent")); term != nil { + var err error + metadata["parent"], err = createResult(ctx, nil, term) + if err != nil { + return nil, err + } + } + + var values [][2]*ast.Term + for key, val := range metadata { + values = append(values, [2]*ast.Term{ + ast.StringTerm(key), + val, + }) + } + return ast.ObjectTerm(values...), nil +} + +func updateMetadata(metadata map[string]*ast.Term, input *ast.Term) map[string]*ast.Term { + if term := input.Get(ast.StringTerm("startline")); term != nil { + metadata["startline"] = term + } + if term := input.Get(ast.StringTerm("StartLine")); term != nil { + metadata["startline"] = term + } + if term := input.Get(ast.StringTerm("endline")); term != nil { + metadata["endline"] = term + } + if term := input.Get(ast.StringTerm("EndLine")); term != nil { + metadata["endline"] = term + } + if term := input.Get(ast.StringTerm("filepath")); term != nil { + metadata["filepath"] = term + } + if term := input.Get(ast.StringTerm("sourceprefix")); term != nil { + metadata["sourceprefix"] = term + } + if term := input.Get(ast.StringTerm("Path")); term != nil { + metadata["filepath"] = term + } + if term := input.Get(ast.StringTerm("explicit")); term != nil { + metadata["explicit"] = term + } + if term := input.Get(ast.StringTerm("managed")); term != nil { + metadata["managed"] = term + } + if term := input.Get(ast.StringTerm("fskey")); term != nil { + metadata["fskey"] = term + } + if term := input.Get(ast.StringTerm("resource")); term != nil { + metadata["resource"] = term + } + return metadata +} diff --git a/pkg/iac/rego/embed.go b/pkg/iac/rego/embed.go new file mode 100644 index 000000000000..16eff7345cc7 --- /dev/null +++ b/pkg/iac/rego/embed.go @@ -0,0 +1,108 @@ +package rego + +import ( + "context" + "io/fs" + "path/filepath" + "strings" + + "github.com/open-policy-agent/opa/ast" + + rules2 "github.com/aquasecurity/trivy-policies" + "github.com/aquasecurity/trivy/pkg/iac/rules" +) + +func init() { + + modules, err := LoadEmbeddedPolicies() + if err != nil { + // we should panic as the policies were not embedded properly + panic(err) + } + loadedLibs, err := LoadEmbeddedLibraries() + if err != nil { + panic(err) + } + for name, policy := range loadedLibs { + modules[name] = policy + } + + RegisterRegoRules(modules) +} + +func RegisterRegoRules(modules map[string]*ast.Module) { + ctx := context.TODO() + + schemaSet, _, _ := BuildSchemaSetFromPolicies(modules, nil, nil) + + compiler := ast.NewCompiler(). + WithSchemas(schemaSet). + WithCapabilities(nil). + WithUseTypeCheckAnnotations(true) + + compiler.Compile(modules) + if compiler.Failed() { + // we should panic as the embedded rego policies are syntactically incorrect... + panic(compiler.Errors) + } + + retriever := NewMetadataRetriever(compiler) + for _, module := range modules { + metadata, err := retriever.RetrieveMetadata(ctx, module) + if err != nil { + continue + } + if metadata.AVDID == "" { + continue + } + rules.Register( + metadata.ToRule(), + ) + } +} + +func LoadEmbeddedPolicies() (map[string]*ast.Module, error) { + return LoadPoliciesFromDirs(rules2.EmbeddedPolicyFileSystem, ".") +} + +func LoadEmbeddedLibraries() (map[string]*ast.Module, error) { + return LoadPoliciesFromDirs(rules2.EmbeddedLibraryFileSystem, ".") +} + +func LoadPoliciesFromDirs(target fs.FS, paths ...string) (map[string]*ast.Module, error) { + modules := make(map[string]*ast.Module) + for _, path := range paths { + if err := fs.WalkDir(target, sanitisePath(path), func(path string, info fs.DirEntry, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + if strings.HasSuffix(filepath.Dir(filepath.ToSlash(path)), filepath.Join("advanced", "optional")) { + return fs.SkipDir + } + + if !IsRegoFile(info.Name()) || IsDotFile(info.Name()) { + return nil + } + data, err := fs.ReadFile(target, filepath.ToSlash(path)) + if err != nil { + return err + } + module, err := ast.ParseModuleWithOpts(path, string(data), ast.ParserOptions{ + ProcessAnnotation: true, + }) + if err != nil { + // s.debug.Log("Failed to load module: %s, err: %s", filepath.ToSlash(path), err.Error()) + return err + } + modules[path] = module + return nil + }); err != nil { + return nil, err + } + } + return modules, nil +} diff --git a/pkg/iac/rego/embed_test.go b/pkg/iac/rego/embed_test.go new file mode 100644 index 000000000000..77a88e40680b --- /dev/null +++ b/pkg/iac/rego/embed_test.go @@ -0,0 +1,123 @@ +package rego + +import ( + "testing" + + rules2 "github.com/aquasecurity/trivy-policies" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/open-policy-agent/opa/ast" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_EmbeddedLoading(t *testing.T) { + + frameworkRules := rules.GetRegistered() + var found bool + for _, rule := range frameworkRules { + if rule.GetRule().RegoPackage != "" { + found = true + } + } + assert.True(t, found, "no embedded rego policies were registered as rules") +} + +func Test_RegisterRegoRules(t *testing.T) { + var testCases = []struct { + name string + inputPolicy string + expectedError bool + }{ + { + name: "happy path old single schema", + inputPolicy: `# METADATA +# title: "dummy title" +# description: "some description" +# scope: package +# schemas: +# - input: schema["input"] +# custom: +# input: +# selector: +# - type: dockerfile +package builtin.dockerfile.DS1234 +deny[res]{ + res := true +}`, + }, + { + name: "happy path new builtin single schema", + inputPolicy: `# METADATA +# title: "dummy title" +# description: "some description" +# scope: package +# schemas: +# - input: schema["dockerfile"] +# custom: +# input: +# selector: +# - type: dockerfile +package builtin.dockerfile.DS1234 +deny[res]{ + res := true +}`, + }, + { + name: "happy path new multiple schemas", + inputPolicy: `# METADATA +# title: "dummy title" +# description: "some description" +# scope: package +# schemas: +# - input: schema["dockerfile"] +# - input: schema["kubernetes"] +# custom: +# input: +# selector: +# - type: dockerfile +package builtin.dockerfile.DS1234 +deny[res]{ + res := true +}`, + }, + { + name: "sad path schema does not exist", + inputPolicy: `# METADATA +# title: "dummy title" +# description: "some description" +# scope: package +# schemas: +# - input: schema["invalid schema"] +# custom: +# input: +# selector: +# - type: dockerfile +package builtin.dockerfile.DS1234 +deny[res]{ + res := true +}`, + expectedError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + policies, err := LoadPoliciesFromDirs(rules2.EmbeddedLibraryFileSystem, ".") + require.NoError(t, err) + newRule, err := ast.ParseModuleWithOpts("/rules/newrule.rego", tc.inputPolicy, ast.ParserOptions{ + ProcessAnnotation: true, + }) + require.NoError(t, err) + + policies["/rules/newrule.rego"] = newRule + switch { + case tc.expectedError: + assert.Panics(t, func() { + RegisterRegoRules(policies) + }, tc.name) + default: + RegisterRegoRules(policies) + } + }) + } +} diff --git a/pkg/iac/rego/exceptions.go b/pkg/iac/rego/exceptions.go new file mode 100644 index 000000000000..7abe9e8a9afa --- /dev/null +++ b/pkg/iac/rego/exceptions.go @@ -0,0 +1,33 @@ +package rego + +import ( + "context" + "fmt" +) + +func (s *Scanner) isIgnored(ctx context.Context, namespace, ruleName string, input interface{}) (bool, error) { + if ignored, err := s.isNamespaceIgnored(ctx, namespace, input); err != nil { + return false, err + } else if ignored { + return true, nil + } + return s.isRuleIgnored(ctx, namespace, ruleName, input) +} + +func (s *Scanner) isNamespaceIgnored(ctx context.Context, namespace string, input interface{}) (bool, error) { + exceptionQuery := fmt.Sprintf("data.namespace.exceptions.exception[_] == %q", namespace) + result, _, err := s.runQuery(ctx, exceptionQuery, input, true) + if err != nil { + return false, fmt.Errorf("query namespace exceptions: %w", err) + } + return result.Allowed(), nil +} + +func (s *Scanner) isRuleIgnored(ctx context.Context, namespace, ruleName string, input interface{}) (bool, error) { + exceptionQuery := fmt.Sprintf("endswith(%q, data.%s.exception[_][_])", ruleName, namespace) + result, _, err := s.runQuery(ctx, exceptionQuery, input, true) + if err != nil { + return false, err + } + return result.Allowed(), nil +} diff --git a/pkg/iac/rego/load.go b/pkg/iac/rego/load.go new file mode 100644 index 000000000000..aeef80144472 --- /dev/null +++ b/pkg/iac/rego/load.go @@ -0,0 +1,210 @@ +package rego + +import ( + "context" + "fmt" + "io" + "io/fs" + "strings" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/bundle" +) + +func IsRegoFile(name string) bool { + return strings.HasSuffix(name, bundle.RegoExt) && !strings.HasSuffix(name, "_test"+bundle.RegoExt) +} + +func IsDotFile(name string) bool { + return strings.HasPrefix(name, ".") +} + +func (s *Scanner) loadPoliciesFromReaders(readers []io.Reader) (map[string]*ast.Module, error) { + modules := make(map[string]*ast.Module) + for i, r := range readers { + moduleName := fmt.Sprintf("reader_%d", i) + data, err := io.ReadAll(r) + if err != nil { + return nil, err + } + module, err := ast.ParseModuleWithOpts(moduleName, string(data), ast.ParserOptions{ + ProcessAnnotation: true, + }) + if err != nil { + return nil, err + } + modules[moduleName] = module + } + return modules, nil +} + +func (s *Scanner) loadEmbedded(enableEmbeddedLibraries, enableEmbeddedPolicies bool) error { + if enableEmbeddedLibraries { + loadedLibs, errLoad := LoadEmbeddedLibraries() + if errLoad != nil { + return fmt.Errorf("failed to load embedded rego libraries: %w", errLoad) + } + for name, policy := range loadedLibs { + s.policies[name] = policy + } + s.debug.Log("Loaded %d embedded libraries.", len(loadedLibs)) + } + + if enableEmbeddedPolicies { + loaded, err := LoadEmbeddedPolicies() + if err != nil { + return fmt.Errorf("failed to load embedded rego policies: %w", err) + } + for name, policy := range loaded { + s.policies[name] = policy + } + s.debug.Log("Loaded %d embedded policies.", len(loaded)) + } + + return nil +} + +func (s *Scanner) LoadPolicies(enableEmbeddedLibraries, enableEmbeddedPolicies bool, srcFS fs.FS, paths []string, readers []io.Reader) error { + + if s.policies == nil { + s.policies = make(map[string]*ast.Module) + } + + if s.policyFS != nil { + s.debug.Log("Overriding filesystem for policies!") + srcFS = s.policyFS + } + + if err := s.loadEmbedded(enableEmbeddedLibraries, enableEmbeddedPolicies); err != nil { + return err + } + + var err error + if len(paths) > 0 { + loaded, err := LoadPoliciesFromDirs(srcFS, paths...) + if err != nil { + return fmt.Errorf("failed to load rego policies from %s: %w", paths, err) + } + for name, policy := range loaded { + s.policies[name] = policy + } + s.debug.Log("Loaded %d policies from disk.", len(loaded)) + } + + if len(readers) > 0 { + loaded, err := s.loadPoliciesFromReaders(readers) + if err != nil { + return fmt.Errorf("failed to load rego policies from reader(s): %w", err) + } + for name, policy := range loaded { + s.policies[name] = policy + } + s.debug.Log("Loaded %d policies from reader(s).", len(loaded)) + } + + // gather namespaces + uniq := make(map[string]struct{}) + for _, module := range s.policies { + namespace := getModuleNamespace(module) + uniq[namespace] = struct{}{} + } + var namespaces []string + for namespace := range uniq { + namespaces = append(namespaces, namespace) + } + + dataFS := srcFS + if s.dataFS != nil { + s.debug.Log("Overriding filesystem for data!") + dataFS = s.dataFS + } + store, err := initStore(dataFS, s.dataDirs, namespaces) + if err != nil { + return fmt.Errorf("unable to load data: %w", err) + } + s.store = store + + return s.compilePolicies(srcFS, paths) +} + +func (s *Scanner) prunePoliciesWithError(compiler *ast.Compiler) error { + if len(compiler.Errors) > s.regoErrorLimit { + s.debug.Log("Error(s) occurred while loading policies") + return compiler.Errors + } + + for _, e := range compiler.Errors { + s.debug.Log("Error occurred while parsing: %s, %s", e.Location.File, e.Error()) + delete(s.policies, e.Location.File) + } + return nil +} + +func (s *Scanner) compilePolicies(srcFS fs.FS, paths []string) error { + + schemaSet, custom, err := BuildSchemaSetFromPolicies(s.policies, paths, srcFS) + if err != nil { + return err + } + if custom { + s.inputSchema = nil // discard auto detected input schema in favor of policy defined schema + } + + compiler := ast.NewCompiler(). + WithUseTypeCheckAnnotations(true). + WithCapabilities(ast.CapabilitiesForThisVersion()). + WithSchemas(schemaSet) + + compiler.Compile(s.policies) + if compiler.Failed() { + if err := s.prunePoliciesWithError(compiler); err != nil { + return err + } + return s.compilePolicies(srcFS, paths) + } + retriever := NewMetadataRetriever(compiler) + + if err := s.filterModules(retriever); err != nil { + return err + } + if s.inputSchema != nil { + schemaSet := ast.NewSchemaSet() + schemaSet.Put(ast.MustParseRef("schema.input"), s.inputSchema) + compiler.WithSchemas(schemaSet) + compiler.Compile(s.policies) + if compiler.Failed() { + if err := s.prunePoliciesWithError(compiler); err != nil { + return err + } + return s.compilePolicies(srcFS, paths) + } + } + s.compiler = compiler + s.retriever = retriever + return nil +} + +func (s *Scanner) filterModules(retriever *MetadataRetriever) error { + + filtered := make(map[string]*ast.Module) + for name, module := range s.policies { + meta, err := retriever.RetrieveMetadata(context.TODO(), module) + if err != nil { + return err + } + if len(meta.InputOptions.Selectors) == 0 { + s.debug.Log("WARNING: Module %s has no input selectors - it will be loaded for all inputs!", name) + filtered[name] = module + continue + } + for _, selector := range meta.InputOptions.Selectors { + if selector.Type == string(s.sourceType) { + filtered[name] = module + break + } + } + } + + s.policies = filtered + return nil +} diff --git a/pkg/iac/rego/load_test.go b/pkg/iac/rego/load_test.go new file mode 100644 index 000000000000..85f574e0c649 --- /dev/null +++ b/pkg/iac/rego/load_test.go @@ -0,0 +1,46 @@ +package rego + +import ( + "bytes" + "embed" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +//go:embed all:testdata/policies +var testEmbedFS embed.FS + +func Test_RegoScanning_WithSomeInvalidPolicies(t *testing.T) { + t.Run("allow no errors", func(t *testing.T) { + var debugBuf bytes.Buffer + scanner := NewScanner(types.SourceDockerfile) + scanner.SetRegoErrorLimit(0) + scanner.SetDebugWriter(&debugBuf) + p, _ := LoadPoliciesFromDirs(testEmbedFS, ".") + require.NotNil(t, p) + + scanner.policies = p + err := scanner.compilePolicies(testEmbedFS, []string{"policies"}) + require.ErrorContains(t, err, `want (one of): ["Cmd" "EndLine" "Flags" "JSON" "Original" "Path" "Stage" "StartLine" "SubCmd" "Value"]`) + assert.Contains(t, debugBuf.String(), "Error(s) occurred while loading policies") + }) + + t.Run("allow up to max 1 error", func(t *testing.T) { + var debugBuf bytes.Buffer + scanner := NewScanner(types.SourceDockerfile) + scanner.SetRegoErrorLimit(1) + scanner.SetDebugWriter(&debugBuf) + + p, _ := LoadPoliciesFromDirs(testEmbedFS, ".") + scanner.policies = p + + err := scanner.compilePolicies(testEmbedFS, []string{"policies"}) + require.NoError(t, err) + + assert.Contains(t, debugBuf.String(), "Error occurred while parsing: testdata/policies/invalid.rego, testdata/policies/invalid.rego:7") + }) + +} diff --git a/pkg/iac/rego/metadata.go b/pkg/iac/rego/metadata.go new file mode 100644 index 000000000000..f2a6505910d1 --- /dev/null +++ b/pkg/iac/rego/metadata.go @@ -0,0 +1,395 @@ +package rego + +import ( + "context" + "fmt" + "strings" + + "github.com/mitchellh/mapstructure" + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" + + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/severity" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type StaticMetadata struct { + ID string + AVDID string + Title string + ShortCode string + Aliases []string + Description string + Severity string + RecommendedActions string + PrimaryURL string + References []string + InputOptions InputOptions + Package string + Frameworks map[framework.Framework][]string + Provider string + Service string + Library bool + CloudFormation *scan.EngineMetadata + Terraform *scan.EngineMetadata +} + +func NewStaticMetadata(pkgPath string, inputOpt InputOptions) *StaticMetadata { + return &StaticMetadata{ + ID: "N/A", + Title: "N/A", + Severity: "UNKNOWN", + Description: fmt.Sprintf("Rego module: %s", pkgPath), + Package: pkgPath, + InputOptions: inputOpt, + Frameworks: make(map[framework.Framework][]string), + } +} + +func (sm *StaticMetadata) Update(meta map[string]any) error { + + upd := func(field *string, key string) { + if raw, ok := meta[key]; ok { + *field = fmt.Sprintf("%s", raw) + } + } + + upd(&sm.ID, "id") + upd(&sm.AVDID, "avd_id") + upd(&sm.Title, "title") + upd(&sm.ShortCode, "short_code") + upd(&sm.Description, "description") + upd(&sm.Service, "service") + upd(&sm.Provider, "provider") + upd(&sm.RecommendedActions, "recommended_actions") + upd(&sm.RecommendedActions, "recommended_action") + + if raw, ok := meta["severity"]; ok { + sm.Severity = strings.ToUpper(fmt.Sprintf("%s", raw)) + } + + if raw, ok := meta["library"]; ok { + if lib, ok := raw.(bool); ok { + sm.Library = lib + } + } + + if raw, ok := meta["url"]; ok { + sm.References = append(sm.References, fmt.Sprintf("%s", raw)) + } + if raw, ok := meta["frameworks"]; ok { + frameworks, ok := raw.(map[string][]string) + if !ok { + return fmt.Errorf("failed to parse framework metadata: not an object") + } + for fw, sections := range frameworks { + sm.Frameworks[framework.Framework(fw)] = sections + } + } + if raw, ok := meta["related_resources"]; ok { + switch relatedResources := raw.(type) { + case []map[string]any: + for _, relatedResource := range relatedResources { + if raw, ok := relatedResource["ref"]; ok { + sm.References = append(sm.References, fmt.Sprintf("%s", raw)) + } + } + case []string: + sm.References = append(sm.References, relatedResources...) + } + } + + sm.updateAliases(meta) + + var err error + if sm.CloudFormation, err = NewEngineMetadata("cloud_formation", meta); err != nil { + return err + } + + if sm.Terraform, err = NewEngineMetadata("terraform", meta); err != nil { + return err + } + + return nil +} + +func (sm *StaticMetadata) updateAliases(meta map[string]any) { + if raw, ok := meta["aliases"]; ok { + if aliases, ok := raw.([]interface{}); ok { + for _, a := range aliases { + sm.Aliases = append(sm.Aliases, fmt.Sprintf("%s", a)) + } + } + } +} + +func (sm *StaticMetadata) FromAnnotations(annotations *ast.Annotations) error { + sm.Title = annotations.Title + sm.Description = annotations.Description + for _, resource := range annotations.RelatedResources { + if !resource.Ref.IsAbs() { + continue + } + sm.References = append(sm.References, resource.Ref.String()) + } + if custom := annotations.Custom; custom != nil { + if err := sm.Update(custom); err != nil { + return err + } + } + if len(annotations.RelatedResources) > 0 { + sm.PrimaryURL = annotations.RelatedResources[0].Ref.String() + } + return nil +} + +func NewEngineMetadata(schema string, meta map[string]interface{}) (*scan.EngineMetadata, error) { + var sMap map[string]interface{} + if raw, ok := meta[schema]; ok { + sMap, ok = raw.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("failed to parse %s metadata: not an object", schema) + } + } + + var em scan.EngineMetadata + if val, ok := sMap["good_examples"].(string); ok { + em.GoodExamples = []string{val} + } + if val, ok := sMap["bad_examples"].(string); ok { + em.BadExamples = []string{val} + } + if val, ok := sMap["links"].(string); ok { + em.Links = []string{val} + } + if val, ok := sMap["remediation_markdown"].(string); ok { + em.RemediationMarkdown = val + } + + return &em, nil +} + +type InputOptions struct { + Combined bool + Selectors []Selector +} + +type Selector struct { + Type string + Subtypes []SubType +} + +type SubType struct { + Group string + Version string + Kind string + Namespace string + Service string // only for cloud + Provider string // only for cloud +} + +func (m StaticMetadata) ToRule() scan.Rule { + + provider := "generic" + if m.Provider != "" { + provider = m.Provider + } else if len(m.InputOptions.Selectors) > 0 { + provider = m.InputOptions.Selectors[0].Type + } + service := "general" + if m.Service != "" { + service = m.Service + } + + return scan.Rule{ + AVDID: m.AVDID, + Aliases: append(m.Aliases, m.ID), + ShortCode: m.ShortCode, + Summary: m.Title, + Explanation: m.Description, + Impact: "", + Resolution: m.RecommendedActions, + Provider: providers.Provider(provider), + Service: service, + Links: m.References, + Severity: severity.Severity(m.Severity), + RegoPackage: m.Package, + Frameworks: m.Frameworks, + CloudFormation: m.CloudFormation, + Terraform: m.Terraform, + } +} + +type MetadataRetriever struct { + compiler *ast.Compiler +} + +func NewMetadataRetriever(compiler *ast.Compiler) *MetadataRetriever { + return &MetadataRetriever{ + compiler: compiler, + } +} + +func (m *MetadataRetriever) findPackageAnnotations(module *ast.Module) *ast.Annotations { + annotationSet := m.compiler.GetAnnotationSet() + if annotationSet == nil { + return nil + } + for _, annotation := range annotationSet.Flatten() { + if annotation.GetPackage().Path.String() != module.Package.Path.String() || annotation.Annotations.Scope != "package" { + continue + } + return annotation.Annotations + } + return nil +} + +func (m *MetadataRetriever) RetrieveMetadata(ctx context.Context, module *ast.Module, contents ...any) (*StaticMetadata, error) { + + metadata := NewStaticMetadata( + module.Package.Path.String(), + m.queryInputOptions(ctx, module), + ) + + // read metadata from official rego annotations if possible + if annotations := m.findPackageAnnotations(module); annotations != nil { + if err := metadata.FromAnnotations(annotations); err != nil { + return nil, err + } + return metadata, nil + } + + // otherwise, try to read metadata from the rego module itself - we used to do this before annotations were a thing + namespace := getModuleNamespace(module) + metadataQuery := fmt.Sprintf("data.%s.__rego_metadata__", namespace) + + options := []func(*rego.Rego){ + rego.Query(metadataQuery), + rego.Compiler(m.compiler), + rego.Capabilities(nil), + } + // support dynamic metadata fields + for _, in := range contents { + options = append(options, rego.Input(in)) + } + + instance := rego.New(options...) + set, err := instance.Eval(ctx) + if err != nil { + return nil, err + } + + // no metadata supplied + if set == nil { + return metadata, nil + } + + if len(set) != 1 { + return nil, fmt.Errorf("failed to parse metadata: unexpected set length") + } + if len(set[0].Expressions) != 1 { + return nil, fmt.Errorf("failed to parse metadata: unexpected expression length") + } + expression := set[0].Expressions[0] + meta, ok := expression.Value.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("failed to parse metadata: not an object") + } + + if err := metadata.Update(meta); err != nil { + return nil, err + } + + return metadata, nil +} + +// nolint: gocyclo +func (m *MetadataRetriever) queryInputOptions(ctx context.Context, module *ast.Module) InputOptions { + + options := InputOptions{ + Combined: false, + Selectors: nil, + } + + var metadata map[string]interface{} + + // read metadata from official rego annotations if possible + if annotation := m.findPackageAnnotations(module); annotation != nil && annotation.Custom != nil { + if input, ok := annotation.Custom["input"]; ok { + if mapped, ok := input.(map[string]interface{}); ok { + metadata = mapped + } + } + } + + if metadata == nil { + + namespace := getModuleNamespace(module) + inputOptionQuery := fmt.Sprintf("data.%s.__rego_input__", namespace) + instance := rego.New( + rego.Query(inputOptionQuery), + rego.Compiler(m.compiler), + rego.Capabilities(nil), + ) + set, err := instance.Eval(ctx) + if err != nil { + return options + } + + if len(set) != 1 { + return options + } + if len(set[0].Expressions) != 1 { + return options + } + expression := set[0].Expressions[0] + meta, ok := expression.Value.(map[string]interface{}) + if !ok { + return options + } + metadata = meta + } + + if raw, ok := metadata["combine"]; ok { + if combine, ok := raw.(bool); ok { + options.Combined = combine + } + } + + if raw, ok := metadata["selector"]; ok { + if each, ok := raw.([]interface{}); ok { + for _, rawSelector := range each { + var selector Selector + if selectorMap, ok := rawSelector.(map[string]interface{}); ok { + if rawType, ok := selectorMap["type"]; ok { + selector.Type = fmt.Sprintf("%s", rawType) + // handle backward compatibility for "defsec" source type which is now "cloud" + if selector.Type == string(iacTypes.SourceDefsec) { + selector.Type = string(iacTypes.SourceCloud) + } + } + if subType, ok := selectorMap["subtypes"].([]interface{}); ok { + for _, subT := range subType { + if st, ok := subT.(map[string]interface{}); ok { + s := SubType{} + _ = mapstructure.Decode(st, &s) + selector.Subtypes = append(selector.Subtypes, s) + } + } + } + } + options.Selectors = append(options.Selectors, selector) + } + } + } + + return options + +} + +func getModuleNamespace(module *ast.Module) string { + return strings.TrimPrefix(module.Package.Path.String(), "data.") +} diff --git a/pkg/iac/rego/metadata_test.go b/pkg/iac/rego/metadata_test.go new file mode 100644 index 000000000000..d12b2d5d55f6 --- /dev/null +++ b/pkg/iac/rego/metadata_test.go @@ -0,0 +1,191 @@ +package rego + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_UpdateStaticMetadata(t *testing.T) { + t.Run("happy", func(t *testing.T) { + sm := StaticMetadata{ + ID: "i", + AVDID: "a", + Title: "t", + ShortCode: "sc", + Aliases: []string{"a", "b", "c"}, + Description: "d", + Severity: "s", + RecommendedActions: "ra", + PrimaryURL: "pu", + References: []string{"r"}, + Package: "pkg", + Provider: "pr", + Service: "srvc", + Library: false, + Frameworks: map[framework.Framework][]string{ + framework.Default: {"dd"}, + }, + } + + require.NoError(t, sm.Update( + map[string]any{ + "id": "i_n", + "avd_id": "a_n", + "title": "t_n", + "short_code": "sc_n", + "aliases": []any{"a_n", "b_n", "c_n"}, + "description": "d_n", + "service": "srvc_n", + "provider": "pr_n", + "recommended_actions": "ra_n", + "severity": "s_n", + "library": true, + "url": "r_n", + "frameworks": map[string][]string{ + "all": {"aa"}, + }, + }, + )) + + expected := StaticMetadata{ + ID: "i_n", + AVDID: "a_n", + Title: "t_n", + ShortCode: "sc_n", + Aliases: []string{"a", "b", "c", "a_n", "b_n", "c_n"}, + Description: "d_n", + Severity: "S_N", + RecommendedActions: "ra_n", + PrimaryURL: "pu", + References: []string{"r", "r_n"}, + Package: "pkg", + Provider: "pr_n", + Service: "srvc_n", + Library: true, + Frameworks: map[framework.Framework][]string{ + framework.Default: {"dd"}, + framework.ALL: {"aa"}, + }, + CloudFormation: &scan.EngineMetadata{}, + Terraform: &scan.EngineMetadata{}, + } + + assert.Equal(t, expected, sm) + }) + + t.Run("related resources are a map", func(t *testing.T) { + sm := StaticMetadata{ + References: []string{"r"}, + } + require.NoError(t, sm.Update(map[string]any{ + "related_resources": []map[string]any{ + { + "ref": "r1_n", + }, + { + "ref": "r2_n", + }, + }, + })) + + expected := StaticMetadata{ + References: []string{"r", "r1_n", "r2_n"}, + CloudFormation: &scan.EngineMetadata{}, + Terraform: &scan.EngineMetadata{}, + } + + assert.Equal(t, expected, sm) + }) + + t.Run("related resources are a string", func(t *testing.T) { + sm := StaticMetadata{ + References: []string{"r"}, + } + require.NoError(t, sm.Update(map[string]any{ + "related_resources": []string{"r1_n", "r2_n"}, + })) + + expected := StaticMetadata{ + References: []string{"r", "r1_n", "r2_n"}, + CloudFormation: &scan.EngineMetadata{}, + Terraform: &scan.EngineMetadata{}, + } + + assert.Equal(t, expected, sm) + }) +} + +func Test_getEngineMetadata(t *testing.T) { + inputSchema := map[string]interface{}{ + "terraform": map[string]interface{}{ + "good_examples": `resource "aws_cloudtrail" "good_example" { + is_multi_region_trail = true + + event_selector { + read_write_type = "All" + include_management_events = true + + data_resource { + type = "AWS::S3::Object" + values = ["${data.aws_s3_bucket.important-bucket.arn}/"] + } + } + }`, + }, + "cloud_formation": map[string]interface{}{"good_examples": `--- +Resources: + GoodExample: + Type: AWS::CloudTrail::Trail + Properties: + IsLogging: true + IsMultiRegionTrail: true + S3BucketName: "CloudtrailBucket" + S3KeyPrefix: "/trailing" + TrailName: "Cloudtrail"`, + }} + + var testCases = []struct { + schema string + want string + }{ + { + schema: "terraform", + want: `resource "aws_cloudtrail" "good_example" { + is_multi_region_trail = true + + event_selector { + read_write_type = "All" + include_management_events = true + + data_resource { + type = "AWS::S3::Object" + values = ["${data.aws_s3_bucket.important-bucket.arn}/"] + } + } + }`, + }, + {schema: "cloud_formation", + want: `--- +Resources: + GoodExample: + Type: AWS::CloudTrail::Trail + Properties: + IsLogging: true + IsMultiRegionTrail: true + S3BucketName: "CloudtrailBucket" + S3KeyPrefix: "/trailing" + TrailName: "Cloudtrail"`}, + } + + for _, tc := range testCases { + t.Run(tc.schema, func(t *testing.T) { + em, err := NewEngineMetadata(tc.schema, inputSchema) + assert.NoError(t, err) + assert.Equal(t, tc.want, em.GoodExamples[0]) + }) + } +} diff --git a/pkg/iac/rego/result.go b/pkg/iac/rego/result.go new file mode 100644 index 000000000000..dd2f7629d3a3 --- /dev/null +++ b/pkg/iac/rego/result.go @@ -0,0 +1,167 @@ +package rego + +import ( + "fmt" + "io/fs" + "strconv" + + "github.com/open-policy-agent/opa/rego" + + "github.com/aquasecurity/trivy/pkg/iac/scan" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type regoResult struct { + Filepath string + Resource string + StartLine int + EndLine int + SourcePrefix string + Message string + Explicit bool + Managed bool + FSKey string + FS fs.FS + Parent *regoResult +} + +func (r regoResult) GetMetadata() iacTypes.Metadata { + var m iacTypes.Metadata + if !r.Managed { + m = iacTypes.NewUnmanagedMetadata() + } else { + rng := iacTypes.NewRangeWithFSKey(r.Filepath, r.StartLine, r.EndLine, r.SourcePrefix, r.FSKey, r.FS) + if r.Explicit { + m = iacTypes.NewExplicitMetadata(rng, r.Resource) + } else { + m = iacTypes.NewMetadata(rng, r.Resource) + } + } + if r.Parent != nil { + return m.WithParent(r.Parent.GetMetadata()) + } + return m +} + +func (r regoResult) GetRawValue() interface{} { + return nil +} + +func parseResult(raw interface{}) *regoResult { + var result regoResult + result.Managed = true + switch val := raw.(type) { + case []interface{}: + var msg string + for _, item := range val { + switch raw := item.(type) { + case map[string]interface{}: + result = parseCause(raw) + case string: + msg = raw + } + } + result.Message = msg + case string: + result.Message = val + case map[string]interface{}: + result = parseCause(val) + default: + result.Message = "Rego policy resulted in DENY" + } + return &result +} + +func parseCause(cause map[string]interface{}) regoResult { + var result regoResult + result.Managed = true + if msg, ok := cause["msg"]; ok { + result.Message = fmt.Sprintf("%s", msg) + } + if filepath, ok := cause["filepath"]; ok { + result.Filepath = fmt.Sprintf("%s", filepath) + } + if msg, ok := cause["fskey"]; ok { + result.FSKey = fmt.Sprintf("%s", msg) + } + if msg, ok := cause["resource"]; ok { + result.Resource = fmt.Sprintf("%s", msg) + } + if start, ok := cause["startline"]; ok { + result.StartLine = parseLineNumber(start) + } + if end, ok := cause["endline"]; ok { + result.EndLine = parseLineNumber(end) + } + if prefix, ok := cause["sourceprefix"]; ok { + result.SourcePrefix = fmt.Sprintf("%s", prefix) + } + if explicit, ok := cause["explicit"]; ok { + if set, ok := explicit.(bool); ok { + result.Explicit = set + } + } + if managed, ok := cause["managed"]; ok { + if set, ok := managed.(bool); ok { + result.Managed = set + } + } + if parent, ok := cause["parent"]; ok { + if m, ok := parent.(map[string]interface{}); ok { + parentResult := parseCause(m) + result.Parent = &parentResult + } + } + return result +} + +func parseLineNumber(raw interface{}) int { + str := fmt.Sprintf("%s", raw) + n, _ := strconv.Atoi(str) + return n +} + +func (s *Scanner) convertResults(set rego.ResultSet, input Input, namespace, rule string, traces []string) scan.Results { + var results scan.Results + + offset := 0 + if input.Contents != nil { + if xx, ok := input.Contents.(map[string]interface{}); ok { + if md, ok := xx["__defsec_metadata"]; ok { + if md2, ok := md.(map[string]interface{}); ok { + if sl, ok := md2["offset"]; ok { + offset, _ = sl.(int) + } + } + } + } + } + for _, result := range set { + for _, expression := range result.Expressions { + values, ok := expression.Value.([]interface{}) + if !ok { + values = []interface{}{expression.Value} + } + + for _, value := range values { + regoResult := parseResult(value) + regoResult.FS = input.FS + if regoResult.Filepath == "" && input.Path != "" { + regoResult.Filepath = input.Path + } + if regoResult.Message == "" { + regoResult.Message = fmt.Sprintf("Rego policy rule: %s.%s", namespace, rule) + } + regoResult.StartLine += offset + regoResult.EndLine += offset + results.AddRego(regoResult.Message, namespace, rule, traces, regoResult) + } + } + } + return results +} + +func (s *Scanner) embellishResultsWithRuleMetadata(results scan.Results, metadata StaticMetadata) scan.Results { + results.SetRule(metadata.ToRule()) + return results +} diff --git a/pkg/iac/rego/result_test.go b/pkg/iac/rego/result_test.go new file mode 100644 index 000000000000..d958f7962b10 --- /dev/null +++ b/pkg/iac/rego/result_test.go @@ -0,0 +1,104 @@ +package rego + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_parseResult(t *testing.T) { + var testCases = []struct { + name string + input interface{} + want regoResult + }{ + { + name: "unknown", + input: nil, + want: regoResult{ + Managed: true, + Message: "Rego policy resulted in DENY", + }, + }, + { + name: "string", + input: "message", + want: regoResult{ + Managed: true, + Message: "message", + }, + }, + { + name: "strings", + input: []interface{}{"message"}, + want: regoResult{ + Managed: true, + Message: "message", + }, + }, + { + name: "maps", + input: []interface{}{ + "message", + map[string]interface{}{ + "filepath": "a.out", + }, + }, + want: regoResult{ + Managed: true, + Message: "message", + Filepath: "a.out", + }, + }, + { + name: "map", + input: map[string]interface{}{ + "msg": "message", + "filepath": "a.out", + "fskey": "abcd", + "resource": "resource", + "startline": "123", + "endline": "456", + "sourceprefix": "git", + "explicit": true, + "managed": true, + }, + want: regoResult{ + Message: "message", + Filepath: "a.out", + Resource: "resource", + StartLine: 123, + EndLine: 456, + SourcePrefix: "git", + FSKey: "abcd", + Explicit: true, + Managed: true, + }, + }, + { + name: "parent", + input: map[string]interface{}{ + "msg": "child", + "parent": map[string]interface{}{ + "msg": "parent", + }, + }, + want: regoResult{ + Message: "child", + Managed: true, + Parent: ®oResult{ + Message: "parent", + Managed: true, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + have := parseResult(tc.input) + assert.NotNil(t, have) + assert.Equal(t, tc.want, *have) + }) + } +} diff --git a/pkg/iac/rego/runtime.go b/pkg/iac/rego/runtime.go new file mode 100644 index 000000000000..6e28268d9971 --- /dev/null +++ b/pkg/iac/rego/runtime.go @@ -0,0 +1,28 @@ +package rego + +import ( + "os" + "strings" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/version" +) + +func addRuntimeValues() *ast.Term { + env := ast.NewObject() + for _, pair := range os.Environ() { + parts := strings.SplitN(pair, "=", 2) + if len(parts) == 1 { + env.Insert(ast.StringTerm(parts[0]), ast.NullTerm()) + } else if len(parts) > 1 { + env.Insert(ast.StringTerm(parts[0]), ast.StringTerm(parts[1])) + } + } + + obj := ast.NewObject() + obj.Insert(ast.StringTerm("env"), ast.NewTerm(env)) + obj.Insert(ast.StringTerm("version"), ast.StringTerm(version.Version)) + obj.Insert(ast.StringTerm("commit"), ast.StringTerm(version.Vcs)) + + return ast.NewTerm(obj) +} diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go new file mode 100644 index 000000000000..6969f957fa5a --- /dev/null +++ b/pkg/iac/rego/scanner.go @@ -0,0 +1,412 @@ +package rego + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/fs" + "strings" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/storage" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego/schemas" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var _ options.ConfigurableScanner = (*Scanner)(nil) + +type Scanner struct { + ruleNamespaces map[string]struct{} + policies map[string]*ast.Module + store storage.Store + dataDirs []string + runtimeValues *ast.Term + compiler *ast.Compiler + regoErrorLimit int + debug debug.Logger + traceWriter io.Writer + tracePerResult bool + retriever *MetadataRetriever + policyFS fs.FS + dataFS fs.FS + frameworks []framework.Framework + spec string + inputSchema interface{} // unmarshalled into this from a json schema document + sourceType types.Source +} + +func (s *Scanner) SetUseEmbeddedLibraries(b bool) { + // handled externally +} + +func (s *Scanner) SetSpec(spec string) { + s.spec = spec +} + +func (s *Scanner) SetRegoOnly(bool) {} + +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { + s.frameworks = frameworks +} + +func (s *Scanner) SetUseEmbeddedPolicies(b bool) { + // handled externally +} + +func (s *Scanner) trace(heading string, input interface{}) { + if s.traceWriter == nil { + return + } + data, err := json.MarshalIndent(input, "", " ") + if err != nil { + return + } + _, _ = fmt.Fprintf(s.traceWriter, "REGO %[1]s:\n%s\nEND REGO %[1]s\n\n", heading, string(data)) +} + +func (s *Scanner) SetPolicyFilesystem(fsys fs.FS) { + s.policyFS = fsys +} + +func (s *Scanner) SetDataFilesystem(fsys fs.FS) { + s.dataFS = fsys +} + +func (s *Scanner) SetPolicyReaders(_ []io.Reader) { + // NOTE: Policy readers option not applicable for rego, policies are loaded on-demand by other scanners. +} + +func (s *Scanner) SetDebugWriter(writer io.Writer) { + s.debug = debug.New(writer, "rego", "scanner") +} + +func (s *Scanner) SetTraceWriter(writer io.Writer) { + s.traceWriter = writer +} + +func (s *Scanner) SetPerResultTracingEnabled(b bool) { + s.tracePerResult = b +} + +func (s *Scanner) SetPolicyDirs(_ ...string) { + // NOTE: Policy dirs option not applicable for rego, policies are loaded on-demand by other scanners. +} + +func (s *Scanner) SetDataDirs(dirs ...string) { + s.dataDirs = dirs +} + +func (s *Scanner) SetPolicyNamespaces(namespaces ...string) { + for _, namespace := range namespaces { + s.ruleNamespaces[namespace] = struct{}{} + } +} + +func (s *Scanner) SetSkipRequiredCheck(_ bool) { + // NOTE: Skip required option not applicable for rego. +} + +func (s *Scanner) SetRegoErrorLimit(limit int) { + s.regoErrorLimit = limit +} + +type DynamicMetadata struct { + Warning bool + Filepath string + Message string + StartLine int + EndLine int +} + +func NewScanner(source types.Source, opts ...options.ScannerOption) *Scanner { + schema, ok := schemas.SchemaMap[source] + if !ok { + schema = schemas.Anything + } + + s := &Scanner{ + regoErrorLimit: ast.CompileErrorLimitDefault, + sourceType: source, + ruleNamespaces: map[string]struct{}{ + "builtin": {}, + "appshield": {}, + "defsec": {}, + }, + runtimeValues: addRuntimeValues(), + } + for _, opt := range opts { + opt(s) + } + if schema != schemas.None { + err := json.Unmarshal([]byte(schema), &s.inputSchema) + if err != nil { + panic(err) + } + } + return s +} + +func (s *Scanner) SetParentDebugLogger(l debug.Logger) { + s.debug = l.Extend("rego") +} + +func (s *Scanner) runQuery(ctx context.Context, query string, input interface{}, disableTracing bool) (rego.ResultSet, []string, error) { + + trace := (s.traceWriter != nil || s.tracePerResult) && !disableTracing + + regoOptions := []func(*rego.Rego){ + rego.Query(query), + rego.Compiler(s.compiler), + rego.Store(s.store), + rego.Runtime(s.runtimeValues), + rego.Trace(trace), + } + + if s.inputSchema != nil { + schemaSet := ast.NewSchemaSet() + schemaSet.Put(ast.MustParseRef("schema.input"), s.inputSchema) + regoOptions = append(regoOptions, rego.Schemas(schemaSet)) + } + + if input != nil { + regoOptions = append(regoOptions, rego.Input(input)) + } + + instance := rego.New(regoOptions...) + set, err := instance.Eval(ctx) + if err != nil { + return nil, nil, err + } + + // we also build a slice of trace lines for per-result tracing - primarily for fanal/trivy + var traces []string + + if trace { + if s.traceWriter != nil { + rego.PrintTrace(s.traceWriter, instance) + } + if s.tracePerResult { + traceBuffer := bytes.NewBuffer([]byte{}) + rego.PrintTrace(traceBuffer, instance) + traces = strings.Split(traceBuffer.String(), "\n") + } + } + return set, traces, nil +} + +type Input struct { + Path string `json:"path"` + FS fs.FS `json:"-"` + Contents interface{} `json:"contents"` +} + +func GetInputsContents(inputs []Input) []any { + results := make([]any, len(inputs)) + for i, c := range inputs { + results[i] = c.Contents + } + return results +} + +func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, error) { + + s.debug.Log("Scanning %d inputs...", len(inputs)) + + var results scan.Results + + for _, module := range s.policies { + + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + namespace := getModuleNamespace(module) + topLevel := strings.Split(namespace, ".")[0] + if _, ok := s.ruleNamespaces[topLevel]; !ok { + continue + } + + staticMeta, err := s.retriever.RetrieveMetadata(ctx, module, GetInputsContents(inputs)...) + if err != nil { + return nil, err + } + + if isPolicyWithSubtype(s.sourceType) { + // skip if policy isn't relevant to what is being scanned + if !isPolicyApplicable(staticMeta, inputs...) { + continue + } + } + + if len(inputs) == 0 { + continue + } + + usedRules := make(map[string]struct{}) + + // all rules + for _, rule := range module.Rules { + ruleName := rule.Head.Name.String() + if _, ok := usedRules[ruleName]; ok { + continue + } + usedRules[ruleName] = struct{}{} + if isEnforcedRule(ruleName) { + ruleResults, err := s.applyRule(ctx, namespace, ruleName, inputs, staticMeta.InputOptions.Combined) + if err != nil { + return nil, err + } + results = append(results, s.embellishResultsWithRuleMetadata(ruleResults, *staticMeta)...) + } + } + + } + + return results, nil +} + +func isPolicyWithSubtype(sourceType types.Source) bool { + for _, s := range []types.Source{types.SourceCloud, types.SourceDefsec, types.SourceKubernetes} { + if sourceType == s { + return true + } + } + return false +} + +func checkSubtype(ii map[string]interface{}, provider string, subTypes []SubType) bool { + if len(subTypes) == 0 { + return true + } + + for _, st := range subTypes { + switch services := ii[provider].(type) { + case map[string]interface{}: // cloud + for service := range services { + if (service == st.Service) && (st.Provider == provider) { + return true + } + } + case string: // k8s - logic can be improved + if strings.EqualFold(services, st.Group) || + strings.EqualFold(services, st.Version) || + strings.EqualFold(services, st.Kind) { + return true + } + } + } + return false +} + +func isPolicyApplicable(staticMetadata *StaticMetadata, inputs ...Input) bool { + for _, input := range inputs { + if ii, ok := input.Contents.(map[string]interface{}); ok { + for provider := range ii { + // TODO(simar): Add other providers + if !strings.Contains(strings.Join([]string{"kind", "aws", "azure"}, ","), provider) { + continue + } + + if len(staticMetadata.InputOptions.Selectors) == 0 { // policy always applies if no selectors + return true + } + + // check metadata for subtype + for _, s := range staticMetadata.InputOptions.Selectors { + if checkSubtype(ii, provider, s.Subtypes) { + return true + } + } + } + } + } + return false +} + +func (s *Scanner) applyRule(ctx context.Context, namespace, rule string, inputs []Input, combined bool) (scan.Results, error) { + + // handle combined evaluations if possible + if combined { + s.trace("INPUT", inputs) + return s.applyRuleCombined(ctx, namespace, rule, inputs) + } + + var results scan.Results + qualified := fmt.Sprintf("data.%s.%s", namespace, rule) + for _, input := range inputs { + s.trace("INPUT", input) + if ignored, err := s.isIgnored(ctx, namespace, rule, input.Contents); err != nil { + return nil, err + } else if ignored { + var result regoResult + result.FS = input.FS + result.Filepath = input.Path + result.Managed = true + results.AddIgnored(result) + continue + } + set, traces, err := s.runQuery(ctx, qualified, input.Contents, false) + if err != nil { + return nil, err + } + s.trace("RESULTSET", set) + ruleResults := s.convertResults(set, input, namespace, rule, traces) + if len(ruleResults) == 0 { // It passed because we didn't find anything wrong (NOT because it didn't exist) + var result regoResult + result.FS = input.FS + result.Filepath = input.Path + result.Managed = true + results.AddPassedRego(namespace, rule, traces, result) + continue + } + results = append(results, ruleResults...) + } + + return results, nil +} + +func (s *Scanner) applyRuleCombined(ctx context.Context, namespace, rule string, inputs []Input) (scan.Results, error) { + if len(inputs) == 0 { + return nil, nil + } + var results scan.Results + qualified := fmt.Sprintf("data.%s.%s", namespace, rule) + if ignored, err := s.isIgnored(ctx, namespace, rule, inputs); err != nil { + return nil, err + } else if ignored { + for _, input := range inputs { + var result regoResult + result.FS = input.FS + result.Filepath = input.Path + result.Managed = true + results.AddIgnored(result) + } + return results, nil + } + set, traces, err := s.runQuery(ctx, qualified, inputs, false) + if err != nil { + return nil, err + } + return s.convertResults(set, inputs[0], namespace, rule, traces), nil +} + +// severity is now set with metadata, so deny/warn/violation now behave the same way +func isEnforcedRule(name string) bool { + switch { + case name == "deny", strings.HasPrefix(name, "deny_"), + name == "warn", strings.HasPrefix(name, "warn_"), + name == "violation", strings.HasPrefix(name, "violation_"): + return true + } + return false +} diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go new file mode 100644 index 000000000000..5a2c96ccb13e --- /dev/null +++ b/pkg/iac/rego/scanner_test.go @@ -0,0 +1,978 @@ +package rego + +import ( + "bytes" + "context" + "io/fs" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/severity" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/liamg/memoryfs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +func CreateFS(t *testing.T, files map[string]string) fs.FS { + memfs := memoryfs.New() + for name, content := range files { + name := strings.TrimPrefix(name, "/") + err := memfs.MkdirAll(filepath.Dir(name), 0o700) + require.NoError(t, err) + err = memfs.WriteFile(name, []byte(content), 0o644) + require.NoError(t, err) + } + return memfs +} + +func Test_RegoScanning_Deny(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny { + input.evil +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + FS: srcFS, + }) + require.NoError(t, err) + + require.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) + + assert.Equal(t, "/evil.lol", results.GetFailed()[0].Metadata().Range().GetFilename()) + assert.False(t, results.GetFailed()[0].IsWarning()) +} + +func Test_RegoScanning_AbsolutePolicyPath_Deny(t *testing.T) { + + tmp := t.TempDir() + require.NoError(t, os.Mkdir(filepath.Join(tmp, "policies"), 0755)) + require.NoError(t, os.WriteFile(filepath.Join(tmp, "policies", "test.rego"), []byte(`package defsec.test + +deny { + input.evil +}`), 0600)) + + srcFS := os.DirFS(tmp) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"/policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + FS: srcFS, + }) + require.NoError(t, err) + + require.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) + + assert.Equal(t, "/evil.lol", results.GetFailed()[0].Metadata().Range().GetFilename()) + assert.False(t, results.GetFailed()[0].IsWarning()) +} + +func Test_RegoScanning_Warn(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +warn { + input.evil +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + require.Equal(t, 1, len(results.GetFailed())) + require.Equal(t, 0, len(results.GetPassed())) + require.Equal(t, 0, len(results.GetIgnored())) + + assert.True(t, results.GetFailed()[0].IsWarning()) +} + +func Test_RegoScanning_Allow(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny { + input.evil +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": false, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 0, len(results.GetFailed())) + require.Equal(t, 1, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) + + assert.Equal(t, "/evil.lol", results.GetPassed()[0].Metadata().Range().GetFilename()) +} + +func Test_RegoScanning_Namespace_Exception(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny { + input.evil +} +`, + "policies/exceptions.rego": ` +package namespace.exceptions + +import data.namespaces + +exception[ns] { + ns := data.namespaces[_] + startswith(ns, "defsec") +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 0, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 1, len(results.GetIgnored())) + +} + +func Test_RegoScanning_Namespace_Exception_WithoutMatch(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny { + input.evil +} +`, "policies/something.rego": ` +package builtin.test + +deny_something { + input.something +} +`, + "policies/exceptions.rego": ` +package namespace.exceptions + +import data.namespaces + +exception[ns] { + ns := data.namespaces[_] + startswith(ns, "builtin") +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 1, len(results.GetIgnored())) + +} + +func Test_RegoScanning_Rule_Exception(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test +deny_evil { + input.evil +} +`, + "policies/exceptions.rego": ` +package defsec.test + +exception[rules] { + rules := ["evil"] +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 0, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 1, len(results.GetIgnored())) +} + +func Test_RegoScanning_Rule_Exception_WithoutMatch(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test +deny_evil { + input.evil +} +`, + "policies/exceptions.rego": ` +package defsec.test + +exception[rules] { + rules := ["good"] +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) +} + +func Test_RegoScanning_WithRuntimeValues(t *testing.T) { + + _ = os.Setenv("DEFSEC_RUNTIME_VAL", "AOK") + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny_evil { + output := opa.runtime() + output.env.DEFSEC_RUNTIME_VAL == "AOK" +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) +} + +func Test_RegoScanning_WithDenyMessage(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny[msg] { + input.evil + msg := "oh no" +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + require.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) + + assert.Equal(t, "oh no", results.GetFailed()[0].Description()) + assert.Equal(t, "/evil.lol", results.GetFailed()[0].Metadata().Range().GetFilename()) +} + +func Test_RegoScanning_WithDenyMetadata_ImpliedPath(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny[res] { + input.evil + res := { + "msg": "oh no", + "startline": 123, + "endline": 456, + } +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + require.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) + + assert.Equal(t, "oh no", results.GetFailed()[0].Description()) + assert.Equal(t, "/evil.lol", results.GetFailed()[0].Metadata().Range().GetFilename()) + assert.Equal(t, 123, results.GetFailed()[0].Metadata().Range().GetStartLine()) + assert.Equal(t, 456, results.GetFailed()[0].Metadata().Range().GetEndLine()) + +} + +func Test_RegoScanning_WithDenyMetadata_PersistedPath(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny[res] { + input.evil + res := { + "msg": "oh no", + "startline": 123, + "endline": 456, + "filepath": "/blah.txt", + } +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + require.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) + + assert.Equal(t, "oh no", results.GetFailed()[0].Description()) + assert.Equal(t, "/blah.txt", results.GetFailed()[0].Metadata().Range().GetFilename()) + assert.Equal(t, 123, results.GetFailed()[0].Metadata().Range().GetStartLine()) + assert.Equal(t, 456, results.GetFailed()[0].Metadata().Range().GetEndLine()) + +} + +func Test_RegoScanning_WithStaticMetadata(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +__rego_metadata__ := { + "id": "AA001", + "avd_id": "AVD-XX-9999", + "title": "This is a title", + "short_code": "short-code", + "severity": "LOW", + "type": "Dockerfile Security Check", + "description": "This is a description", + "recommended_actions": "This is a recommendation", + "url": "https://google.com", +} + +deny[res] { + input.evil + res := { + "msg": "oh no", + "startline": 123, + "endline": 456, + "filepath": "/blah.txt", + } +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + require.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) + + failure := results.GetFailed()[0] + + assert.Equal(t, "oh no", failure.Description()) + assert.Equal(t, "/blah.txt", failure.Metadata().Range().GetFilename()) + assert.Equal(t, 123, failure.Metadata().Range().GetStartLine()) + assert.Equal(t, 456, failure.Metadata().Range().GetEndLine()) + assert.Equal(t, "AVD-XX-9999", failure.Rule().AVDID) + assert.True(t, failure.Rule().HasID("AA001")) + assert.Equal(t, "This is a title", failure.Rule().Summary) + assert.Equal(t, severity.Low, failure.Rule().Severity) + assert.Equal(t, "This is a recommendation", failure.Rule().Resolution) + assert.Equal(t, "https://google.com", failure.Rule().Links[0]) + +} + +func Test_RegoScanning_WithMatchingInputSelector(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +__rego_input__ := { + "selector": [{"type": "json"}], +} + +deny { + input.evil +} + +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) +} + +func Test_RegoScanning_WithNonMatchingInputSelector(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +__rego_input__ := { + "selector": [{"type": "testing"}], +} + +deny { + input.evil +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 0, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) +} + +func Test_RegoScanning_NoTracingByDefault(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny { + input.evil +} +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) + + assert.Len(t, results.GetFailed()[0].Traces(), 0) +} + +func Test_RegoScanning_GlobalTracingEnabled(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny { + input.evil +} +`, + }) + + traceBuffer := bytes.NewBuffer([]byte{}) + + scanner := NewScanner(types.SourceJSON, options.ScannerWithTrace(traceBuffer)) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) + + assert.Len(t, results.GetFailed()[0].Traces(), 0) + assert.Greater(t, len(traceBuffer.Bytes()), 0) +} + +func Test_RegoScanning_PerResultTracingEnabled(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +deny { + input.evil +} +`, + }) + + scanner := NewScanner(types.SourceJSON, options.ScannerWithPerResultTracing(true)) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "evil": true, + }, + }) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) + + assert.Greater(t, len(results.GetFailed()[0].Traces()), 0) +} + +func Test_dynamicMetadata(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +__rego_metadata__ := { + "title" : sprintf("i am %s",[input.text]) +} + +deny { + input.text +} + +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "text": "dynamic", + }, + }) + require.NoError(t, err) + assert.Equal(t, results[0].Rule().Summary, "i am dynamic") +} + +func Test_staticMetadata(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +__rego_metadata__ := { + "title" : "i am static" +} + +deny { + input.text +} + +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "text": "test", + }, + }) + require.NoError(t, err) + assert.Equal(t, results[0].Rule().Summary, "i am static") +} + +func Test_annotationMetadata(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": `# METADATA +# title: i am a title +# description: i am a description +# related_resources: +# - https://google.com +# custom: +# id: EG123 +# avd_id: AVD-EG-0123 +# severity: LOW +# recommended_action: have a cup of tea +package defsec.test + +deny { + input.text +} + +`, + "policies/test2.rego": `# METADATA +# title: i am another title +package defsec.test2 + +deny { + input.blah +} + +`, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "text": "test", + }, + }) + require.NoError(t, err) + require.Len(t, results.GetFailed(), 1) + failure := results.GetFailed()[0].Rule() + assert.Equal(t, "i am a title", failure.Summary) + assert.Equal(t, "i am a description", failure.Explanation) + require.Len(t, failure.Links, 1) + assert.Equal(t, "https://google.com", failure.Links[0]) + assert.Equal(t, "AVD-EG-0123", failure.AVDID) + assert.Equal(t, severity.Low, failure.Severity) + assert.Equal(t, "have a cup of tea", failure.Resolution) +} + +func Test_RegoScanning_WithInvalidInputSchema(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": `# METADATA +# schemas: +# - input: schema["input"] +package defsec.test + +deny { + input.evil == "lol" +} +`, + }) + + scanner := NewScanner(types.SourceDockerfile) + scanner.SetRegoErrorLimit(0) // override to not allow any errors + assert.ErrorContains( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + "undefined ref: input.evil", + ) +} + +func Test_RegoScanning_WithValidInputSchema(t *testing.T) { + + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": `# METADATA +# schemas: +# - input: schema["input"] +package defsec.test + +deny { + input.Stages[0].Commands[0].Cmd == "lol" +} +`, + }) + + scanner := NewScanner(types.SourceDockerfile) + assert.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) +} + +func Test_RegoScanning_WithFilepathToSchema(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": `# METADATA +# schemas: +# - input: schema["dockerfile"] +package defsec.test + +deny { + input.evil == "lol" +} +`, + }) + scanner := NewScanner(types.SourceJSON) + scanner.SetRegoErrorLimit(0) // override to not allow any errors + assert.ErrorContains( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + "undefined ref: input.evil", + ) +} + +func Test_RegoScanning_CustomData(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test +import data.settings.DS123.foo_bar_baz + +deny { + not foo_bar_baz +} +`, + }) + + dataFS := CreateFS(t, map[string]string{ + "data/data.json": `{ + "settings": { + "DS123":{ + "foo_bar_baz":false + } + } +}`, + "data/junk.txt": "this file should be ignored", + }) + + scanner := NewScanner(types.SourceJSON) + scanner.SetDataFilesystem(dataFS) + scanner.SetDataDirs(".") + + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{}) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) +} + +func Test_RegoScanning_InvalidFS(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test +import data.settings.DS123.foo_bar_baz + +deny { + not foo_bar_baz +} +`, + }) + + dataFS := CreateFS(t, map[string]string{ + "data/data.json": `{ + "settings": { + "DS123":{ + "foo_bar_baz":false + } + } +}`, + "data/junk.txt": "this file should be ignored", + }) + + scanner := NewScanner(types.SourceJSON) + scanner.SetDataFilesystem(dataFS) + scanner.SetDataDirs("X://") + + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{}) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) + assert.Equal(t, 0, len(results.GetPassed())) + assert.Equal(t, 0, len(results.GetIgnored())) +} diff --git a/pkg/iac/rego/schemas/00_schema.go b/pkg/iac/rego/schemas/00_schema.go new file mode 100644 index 000000000000..e6674912fe58 --- /dev/null +++ b/pkg/iac/rego/schemas/00_schema.go @@ -0,0 +1,22 @@ +package schemas + +import _ "embed" + +type Schema string + +var ( + None Schema = "" + Anything Schema = `{}` + + //go:embed dockerfile.json + Dockerfile Schema + + //go:embed kubernetes.json + Kubernetes Schema + + //go:embed rbac.json + RBAC Schema + + //go:embed cloud.json + Cloud Schema +) diff --git a/pkg/iac/rego/schemas/builder.go b/pkg/iac/rego/schemas/builder.go new file mode 100644 index 000000000000..76edec4163a8 --- /dev/null +++ b/pkg/iac/rego/schemas/builder.go @@ -0,0 +1,270 @@ +package schemas + +import ( + "fmt" + "reflect" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/rego/convert" + "github.com/aquasecurity/trivy/pkg/iac/state" +) + +type RawSchema struct { + Type string `json:"type"` // object + Properties map[string]Property `json:"properties,omitempty"` + Defs map[string]*Property `json:"definitions,omitempty"` +} + +type Property struct { + Type string `json:"type,omitempty"` + Ref string `json:"$ref,omitempty"` + Properties map[string]Property `json:"properties,omitempty"` + Items *Property `json:"items,omitempty"` +} + +type builder struct { + schema RawSchema +} + +func Build() (*RawSchema, error) { + + b := newBuilder() + + inputValue := reflect.ValueOf(state.State{}) + + err := b.fromInput(inputValue) + if err != nil { + return nil, err + } + + return &b.schema, nil +} + +func newBuilder() *builder { + return &builder{ + schema: RawSchema{ + Properties: nil, + Defs: nil, + }, + } +} + +func (b *builder) fromInput(inputValue reflect.Value) error { + + prop, err := b.readProperty("", nil, inputValue.Type(), 0) + if err != nil { + return err + } + if prop == nil { + return fmt.Errorf("property is nil") + } + b.schema.Properties = prop.Properties + b.schema.Type = prop.Type + return nil +} + +func refName(name string, parent, t reflect.Type) string { + if t.Name() == "" { // inline struct + return sanitize(parent.PkgPath() + "." + parent.Name() + "." + name) + } + return sanitize(t.PkgPath() + "." + t.Name()) +} + +func sanitize(s string) string { + return strings.ReplaceAll(s, "/", ".") +} + +func (b *builder) readProperty(name string, parent, inputType reflect.Type, indent int) (*Property, error) { + + if inputType.Kind() == reflect.Ptr { + inputType = inputType.Elem() + } + + switch inputType.String() { + case "types.Metadata", "types.Range", "types.Reference": + return nil, nil + } + + if b.schema.Defs != nil { + _, ok := b.schema.Defs[refName(name, parent, inputType)] + if ok { + return &Property{ + Type: "object", + Ref: "#/definitions/" + refName(name, parent, inputType), + }, nil + } + } + + fmt.Println(strings.Repeat(" ", indent) + name) + + switch kind := inputType.Kind(); kind { + case reflect.Struct: + return b.readStruct(name, parent, inputType, indent) + case reflect.Slice: + return b.readSlice(name, parent, inputType, indent) + case reflect.String: + return &Property{ + Type: "string", + }, nil + case reflect.Int: + return &Property{ + Type: "integer", + }, nil + case reflect.Bool: + return &Property{ + Type: "boolean", + }, nil + case reflect.Float32, reflect.Float64: + return &Property{ + Type: "number", + }, nil + } + + switch inputType.Name() { + case "BoolValue": + return &Property{ + Type: "object", + Properties: map[string]Property{ + "value": { + Type: "boolean", + }, + }, + }, nil + case "IntValue": + return &Property{ + Type: "object", + Properties: map[string]Property{ + "value": { + Type: "integer", + }, + }, + }, nil + case "StringValue", "TimeValue", "BytesValue": + return &Property{ + Type: "object", + Properties: map[string]Property{ + "value": { + Type: "string", + }, + }, + }, nil + case "MapValue": + return &Property{ + Type: "object", + Properties: map[string]Property{ + "value": { + Type: "object", + }, + }, + }, nil + + } + + fmt.Printf("WARNING: unsupported type: %s (%s)\n", inputType.Name(), inputType) + return nil, nil +} + +var converterInterface = reflect.TypeOf((*convert.Converter)(nil)).Elem() + +func (b *builder) readStruct(name string, parent, inputType reflect.Type, indent int) (*Property, error) { + + if b.schema.Defs == nil { + b.schema.Defs = make(map[string]*Property) + } + + def := &Property{ + Type: "object", + Properties: make(map[string]Property), + } + + if parent != nil { + b.schema.Defs[refName(name, parent, inputType)] = def + } + + if inputType.Implements(converterInterface) { + if inputType.Kind() == reflect.Ptr { + inputType = inputType.Elem() + } + returns := reflect.New(inputType).MethodByName("ToRego").Call(nil) + if err := b.readRego(def, name, parent, returns[0].Type(), returns[0].Interface(), indent); err != nil { + return nil, err + } + } else { + + for i := 0; i < inputType.NumField(); i++ { + field := inputType.Field(i) + prop, err := b.readProperty(field.Name, inputType, field.Type, indent+1) + if err != nil { + return nil, err + } + if prop == nil { + continue + } + key := strings.ToLower(field.Name) + if key == "metadata" { + continue + } + def.Properties[key] = *prop + } + } + + if parent == nil { + return def, nil + } + + return &Property{ + Type: "object", + Ref: "#/definitions/" + refName(name, parent, inputType), + }, nil +} + +func (b *builder) readSlice(name string, parent, inputType reflect.Type, indent int) (*Property, error) { + + items, err := b.readProperty(name, parent, inputType.Elem(), indent+1) + if err != nil { + return nil, err + } + + prop := &Property{ + Type: "array", + Items: items, + } + return prop, nil +} + +func (b *builder) readRego(def *Property, name string, parent, typ reflect.Type, raw interface{}, indent int) error { + + switch cast := raw.(type) { + case map[string]interface{}: + def.Type = "object" + for k, v := range cast { + child := &Property{ + Properties: make(map[string]Property), + } + if err := b.readRego(child, k, reflect.TypeOf(raw), reflect.TypeOf(v), v, indent+1); err != nil { + return err + } + def.Properties[k] = *child + } + case map[string]string: + def.Type = "object" + for k, v := range cast { + child := &Property{ + Properties: make(map[string]Property), + } + if err := b.readRego(child, k, reflect.TypeOf(raw), reflect.TypeOf(v), v, indent+1); err != nil { + return err + } + def.Properties[k] = *child + } + default: + prop, err := b.readProperty(name, parent, typ, indent) + if err != nil { + return err + } + *def = *prop + } + + return nil + +} diff --git a/pkg/iac/rego/schemas/cloud.json b/pkg/iac/rego/schemas/cloud.json new file mode 100644 index 000000000000..2ac3594e98c5 --- /dev/null +++ b/pkg/iac/rego/schemas/cloud.json @@ -0,0 +1,6836 @@ +{ + "type": "object", + "properties": { + "aws": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.AWS" + }, + "azure": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.Azure" + }, + "cloudstack": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.cloudstack.CloudStack" + }, + "digitalocean": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.DigitalOcean" + }, + "github": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.github.GitHub" + }, + "google": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.Google" + }, + "kubernetes": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Kubernetes" + }, + "nifcloud": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.Nifcloud" + }, + "openstack": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.openstack.OpenStack" + }, + "oracle": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.oracle.Oracle" + } + }, + "definitions": { + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.AWS": { + "type": "object", + "properties": { + "accessanalyzer": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.accessanalyzer.AccessAnalyzer" + }, + "apigateway": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.APIGateway" + }, + "athena": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.Athena" + }, + "cloudfront": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.Cloudfront" + }, + "cloudtrail": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.CloudTrail" + }, + "cloudwatch": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.CloudWatch" + }, + "codebuild": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.codebuild.CodeBuild" + }, + "config": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.config.Config" + }, + "documentdb": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.documentdb.DocumentDB" + }, + "dynamodb": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.DynamoDB" + }, + "ec2": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.EC2" + }, + "ecr": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.ECR" + }, + "ecs": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.ECS" + }, + "efs": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.efs.EFS" + }, + "eks": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.EKS" + }, + "elasticache": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.ElastiCache" + }, + "elasticsearch": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.Elasticsearch" + }, + "elb": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.ELB" + }, + "emr": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.EMR" + }, + "iam": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.IAM" + }, + "kinesis": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.kinesis.Kinesis" + }, + "kms": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.kms.KMS" + }, + "lambda": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Lambda" + }, + "meta": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.Meta" + }, + "mq": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.mq.MQ" + }, + "msk": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.MSK" + }, + "neptune": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.neptune.Neptune" + }, + "rds": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.RDS" + }, + "redshift": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.Redshift" + }, + "s3": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.S3" + }, + "sam": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.SAM" + }, + "sns": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sns.SNS" + }, + "sqs": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sqs.SQS" + }, + "ssm": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ssm.SSM" + }, + "workspaces": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.WorkSpaces" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.AssumeRole": { + "type": "object", + "properties": { + "duration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "externalid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "policy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "policyarns": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "rolearn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "sessionname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "sourceidentity": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "tags": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.MapValue" + }, + "transitivetagkeys": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.AssumeRoleWithWebIdentity": { + "type": "object", + "properties": { + "duration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "policy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "policyarns": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "rolearn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "sessionname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "webidentitytoken": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "webidentitytokenfile": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.DefaultTags": { + "type": "object", + "properties": { + "tags": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.MapValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.IgnoreTags": { + "type": "object", + "properties": { + "keyprefixes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "keys": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.Meta": { + "type": "object", + "properties": { + "tfproviders": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.TerraformProvider" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.TerraformProvider": { + "type": "object", + "properties": { + "accesskey": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "alias": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "allowedaccountsids": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "assumerole": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.AssumeRole" + }, + "assumerolewithwebidentity": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.AssumeRoleWithWebIdentity" + }, + "customcabundle": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "defaulttags": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.DefaultTags" + }, + "ec2metadataserviceendpoint": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "ec2metadataserviceendpointmode": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "endpoints": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.MapValue" + }, + "forbiddenaccountids": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "httpproxy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "ignoretags": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.IgnoreTags" + }, + "insecure": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "maxretries": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "profile": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "region": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "retrymode": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "s3useast1regionalendpoint": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "s3usepathstyle": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "secretkey": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "sharedconfigfiles": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "sharedcredentialsfiles": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "skipcredentialsvalidation": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "skipmetadataapicheck": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "skipregionvalidation": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "skiprequestingaccountid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "stsregion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "token": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "usedualstackendpoint": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "usefipsendpoint": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "version": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.accessanalyzer.AccessAnalyzer": { + "type": "object", + "properties": { + "analyzers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.accessanalyzer.Analyzer" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.accessanalyzer.Analyzer": { + "type": "object", + "properties": { + "active": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "arn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.accessanalyzer.Findings" + } + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.accessanalyzer.Findings": { + "type": "object" + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.APIGateway": { + "type": "object", + "properties": { + "v1": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.APIGateway" + }, + "v2": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.APIGateway" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.API": { + "type": "object", + "properties": { + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "resources": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.Resource" + } + }, + "stages": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.Stage" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.APIGateway": { + "type": "object", + "properties": { + "apis": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.API" + } + }, + "domainnames": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.DomainName" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.AccessLogging": { + "type": "object", + "properties": { + "cloudwatchloggrouparn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.DomainName": { + "type": "object", + "properties": { + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "securitypolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.Method": { + "type": "object", + "properties": { + "apikeyrequired": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "authorizationtype": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "httpmethod": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.RESTMethodSettings": { + "type": "object", + "properties": { + "cachedataencrypted": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "cacheenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "method": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.Resource": { + "type": "object", + "properties": { + "methods": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.Method" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.Stage": { + "type": "object", + "properties": { + "accesslogging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.AccessLogging" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "restmethodsettings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.RESTMethodSettings" + } + }, + "xraytracingenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.API": { + "type": "object", + "properties": { + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "protocoltype": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "stages": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.Stage" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.APIGateway": { + "type": "object", + "properties": { + "apis": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.API" + } + }, + "domainnames": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.DomainName" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.AccessLogging": { + "type": "object", + "properties": { + "cloudwatchloggrouparn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.DomainName": { + "type": "object", + "properties": { + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "securitypolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.Stage": { + "type": "object", + "properties": { + "accesslogging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.AccessLogging" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.Athena": { + "type": "object", + "properties": { + "databases": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.Database" + } + }, + "workgroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.Workgroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.Database": { + "type": "object", + "properties": { + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.EncryptionConfiguration" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.EncryptionConfiguration": { + "type": "object", + "properties": { + "type": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.Workgroup": { + "type": "object", + "properties": { + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.EncryptionConfiguration" + }, + "enforceconfiguration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.CacheBehaviour": { + "type": "object", + "properties": { + "viewerprotocolpolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.Cloudfront": { + "type": "object", + "properties": { + "distributions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.Distribution" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.Distribution": { + "type": "object", + "properties": { + "defaultcachebehaviour": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.CacheBehaviour" + }, + "logging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.Logging" + }, + "orderercachebehaviours": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.CacheBehaviour" + } + }, + "viewercertificate": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.ViewerCertificate" + }, + "wafid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.Logging": { + "type": "object", + "properties": { + "bucket": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.ViewerCertificate": { + "type": "object", + "properties": { + "cloudfrontdefaultcertificate": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "minimumprotocolversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "sslsupportmethod": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.CloudTrail": { + "type": "object", + "properties": { + "trails": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.Trail" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.DataResource": { + "type": "object", + "properties": { + "type": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "values": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.EventSelector": { + "type": "object", + "properties": { + "dataresources": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.DataResource" + } + }, + "readwritetype": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.Trail": { + "type": "object", + "properties": { + "bucketname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "cloudwatchlogsloggrouparn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "enablelogfilevalidation": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "eventselectors": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.EventSelector" + } + }, + "islogging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "ismultiregion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.Alarm": { + "type": "object", + "properties": { + "alarmname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "dimensions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.AlarmDimension" + } + }, + "metricname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "metrics": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.MetricDataQuery" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.AlarmDimension": { + "type": "object", + "properties": { + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "value": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.CloudWatch": { + "type": "object", + "properties": { + "alarms": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.Alarm" + } + }, + "loggroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.LogGroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.LogGroup": { + "type": "object", + "properties": { + "arn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "metricfilters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.MetricFilter" + } + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "retentionindays": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.MetricDataQuery": { + "type": "object", + "properties": { + "expression": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "id": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.MetricFilter": { + "type": "object", + "properties": { + "filtername": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "filterpattern": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.codebuild.ArtifactSettings": { + "type": "object", + "properties": { + "encryptionenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.codebuild.CodeBuild": { + "type": "object", + "properties": { + "projects": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.codebuild.Project" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.codebuild.Project": { + "type": "object", + "properties": { + "artifactsettings": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.codebuild.ArtifactSettings" + }, + "secondaryartifactsettings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.codebuild.ArtifactSettings" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.config.Config": { + "type": "object", + "properties": { + "configurationaggregrator": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.config.ConfigurationAggregrator" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.config.ConfigurationAggregrator": { + "type": "object", + "properties": { + "sourceallregions": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.documentdb.Cluster": { + "type": "object", + "properties": { + "backupretentionperiod": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "enabledlogexports": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "identifier": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "instances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.documentdb.Instance" + } + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "storageencrypted": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.documentdb.DocumentDB": { + "type": "object", + "properties": { + "clusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.documentdb.Cluster" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.documentdb.Instance": { + "type": "object", + "properties": { + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.DAXCluster": { + "type": "object", + "properties": { + "pointintimerecovery": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "serversideencryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.ServerSideEncryption" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.DynamoDB": { + "type": "object", + "properties": { + "daxclusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.DAXCluster" + } + }, + "tables": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.Table" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.ServerSideEncryption": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.Table": { + "type": "object", + "properties": { + "pointintimerecovery": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "serversideencryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.ServerSideEncryption" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.BlockDevice": { + "type": "object", + "properties": { + "encrypted": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.EC2": { + "type": "object", + "properties": { + "instances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Instance" + } + }, + "launchconfigurations": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.LaunchConfiguration" + } + }, + "launchtemplates": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.LaunchTemplate" + } + }, + "networkacls": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.NetworkACL" + } + }, + "securitygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.SecurityGroup" + } + }, + "subnets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Subnet" + } + }, + "volumes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Volume" + } + }, + "vpcs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.VPC" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Encryption": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Instance": { + "type": "object", + "properties": { + "ebsblockdevices": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.BlockDevice" + } + }, + "metadataoptions": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.MetadataOptions" + }, + "rootblockdevice": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.BlockDevice" + }, + "securitygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.SecurityGroup" + } + }, + "userdata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.LaunchConfiguration": { + "type": "object", + "properties": { + "associatepublicip": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "ebsblockdevices": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.BlockDevice" + } + }, + "metadataoptions": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.MetadataOptions" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "rootblockdevice": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.BlockDevice" + }, + "userdata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.LaunchTemplate": { + "type": "object", + "properties": { + "instance": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Instance" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.MetadataOptions": { + "type": "object", + "properties": { + "httpendpoint": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "httptokens": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.NetworkACL": { + "type": "object", + "properties": { + "isdefaultrule": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.NetworkACLRule" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.NetworkACLRule": { + "type": "object", + "properties": { + "action": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "cidrs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "protocol": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "type": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.SecurityGroup": { + "type": "object", + "properties": { + "description": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "egressrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.SecurityGroupRule" + } + }, + "ingressrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.SecurityGroupRule" + } + }, + "isdefault": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "vpcid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.SecurityGroupRule": { + "type": "object", + "properties": { + "cidrs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "description": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Subnet": { + "type": "object", + "properties": { + "mappubliciponlaunch": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.VPC": { + "type": "object", + "properties": { + "flowlogsenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "id": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "isdefault": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "securitygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.SecurityGroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Volume": { + "type": "object", + "properties": { + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Encryption" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.ECR": { + "type": "object", + "properties": { + "repositories": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.Repository" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.Encryption": { + "type": "object", + "properties": { + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "type": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.ImageScanning": { + "type": "object", + "properties": { + "scanonpush": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.Repository": { + "type": "object", + "properties": { + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.Encryption" + }, + "imagescanning": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.ImageScanning" + }, + "imagetagsimmutable": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "policies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.Cluster": { + "type": "object", + "properties": { + "settings": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.ClusterSettings" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.ClusterSettings": { + "type": "object", + "properties": { + "containerinsightsenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.ContainerDefinition": { + "type": "object", + "properties": { + "cpu": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "environment": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.EnvVar" + } + }, + "essential": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "image": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "memory": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "portmappings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.PortMapping" + } + }, + "privileged": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.ECS": { + "type": "object", + "properties": { + "clusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.Cluster" + } + }, + "taskdefinitions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.TaskDefinition" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.EFSVolumeConfiguration": { + "type": "object", + "properties": { + "transitencryptionenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.EnvVar": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.PortMapping": { + "type": "object", + "properties": { + "containerport": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "hostport": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.TaskDefinition": { + "type": "object", + "properties": { + "containerdefinitions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.ContainerDefinition" + } + }, + "volumes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.Volume" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.Volume": { + "type": "object", + "properties": { + "efsvolumeconfiguration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.EFSVolumeConfiguration" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.efs.EFS": { + "type": "object", + "properties": { + "filesystems": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.efs.FileSystem" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.efs.FileSystem": { + "type": "object", + "properties": { + "encrypted": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.Cluster": { + "type": "object", + "properties": { + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.Encryption" + }, + "logging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.Logging" + }, + "publicaccesscidrs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "publicaccessenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.EKS": { + "type": "object", + "properties": { + "clusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.Cluster" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.Encryption": { + "type": "object", + "properties": { + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "secrets": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.Logging": { + "type": "object", + "properties": { + "api": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "audit": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "authenticator": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "controllermanager": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "scheduler": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.Cluster": { + "type": "object", + "properties": { + "engine": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "nodetype": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "snapshotretentionlimit": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.ElastiCache": { + "type": "object", + "properties": { + "clusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.Cluster" + } + }, + "replicationgroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.ReplicationGroup" + } + }, + "securitygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.SecurityGroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.ReplicationGroup": { + "type": "object", + "properties": { + "atrestencryptionenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "transitencryptionenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.SecurityGroup": { + "type": "object", + "properties": { + "description": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.AtRestEncryption": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.Domain": { + "type": "object", + "properties": { + "accesspolicies": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "atrestencryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.AtRestEncryption" + }, + "dedicatedmasterenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "domainname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "endpoint": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.Endpoint" + }, + "logpublishing": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.LogPublishing" + }, + "servicesoftwareoptions": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.ServiceSoftwareOptions" + }, + "transitencryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.TransitEncryption" + }, + "vpcid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.Elasticsearch": { + "type": "object", + "properties": { + "domains": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.Domain" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.Endpoint": { + "type": "object", + "properties": { + "enforcehttps": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "tlspolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.LogPublishing": { + "type": "object", + "properties": { + "auditenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "cloudwatchloggrouparn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.ServiceSoftwareOptions": { + "type": "object", + "properties": { + "currentversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "newversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "updateavailable": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "updatestatus": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.TransitEncryption": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.Action": { + "type": "object", + "properties": { + "type": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.ELB": { + "type": "object", + "properties": { + "loadbalancers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.LoadBalancer" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.Listener": { + "type": "object", + "properties": { + "defaultactions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.Action" + } + }, + "protocol": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "tlspolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.LoadBalancer": { + "type": "object", + "properties": { + "dropinvalidheaderfields": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "internal": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "listeners": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.Listener" + } + }, + "type": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.Cluster": { + "type": "object", + "properties": { + "settings": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.ClusterSettings" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.ClusterSettings": { + "type": "object", + "properties": { + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "releaselabel": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "servicerole": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.EMR": { + "type": "object", + "properties": { + "clusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.Cluster" + } + }, + "securityconfiguration": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.SecurityConfiguration" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.SecurityConfiguration": { + "type": "object", + "properties": { + "configuration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.AccessKey": { + "type": "object", + "properties": { + "accesskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "active": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "creationdate": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" + }, + "lastaccess": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Document": { + "type": "object", + "properties": { + "endline": { + "type": "integer" + }, + "explicit": { + "type": "boolean" + }, + "filepath": { + "type": "string" + }, + "fskey": { + "type": "string" + }, + "managed": { + "type": "boolean" + }, + "resource": { + "type": "string" + }, + "sourceprefix": { + "type": "string" + }, + "startline": { + "type": "integer" + }, + "value": { + "type": "string" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Group": { + "type": "object", + "properties": { + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "policies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy" + } + }, + "users": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.User" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.IAM": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Group" + } + }, + "passwordpolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.PasswordPolicy" + }, + "policies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy" + } + }, + "roles": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Role" + } + }, + "servercertificates": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.ServerCertificate" + } + }, + "users": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.User" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.MFADevice": { + "type": "object", + "properties": { + "isvirtual": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.PasswordPolicy": { + "type": "object", + "properties": { + "maxagedays": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "minimumlength": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "requirelowercase": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "requirenumbers": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "requiresymbols": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "requireuppercase": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "reusepreventioncount": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy": { + "type": "object", + "properties": { + "builtin": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "document": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Document" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Role": { + "type": "object", + "properties": { + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "policies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.ServerCertificate": { + "type": "object", + "properties": { + "expiration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.User": { + "type": "object", + "properties": { + "accesskeys": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.AccessKey" + } + }, + "groups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Group" + } + }, + "lastaccess": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" + }, + "mfadevices": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.MFADevice" + } + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "policies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.kinesis.Encryption": { + "type": "object", + "properties": { + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "type": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.kinesis.Kinesis": { + "type": "object", + "properties": { + "streams": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.kinesis.Stream" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.kinesis.Stream": { + "type": "object", + "properties": { + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.kinesis.Encryption" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.kms.KMS": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.kms.Key" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.kms.Key": { + "type": "object", + "properties": { + "rotationenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "usage": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Function": { + "type": "object", + "properties": { + "permissions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Permission" + } + }, + "tracing": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Tracing" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Lambda": { + "type": "object", + "properties": { + "functions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Function" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Permission": { + "type": "object", + "properties": { + "principal": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "sourcearn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Tracing": { + "type": "object", + "properties": { + "mode": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.mq.Broker": { + "type": "object", + "properties": { + "logging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.mq.Logging" + }, + "publicaccess": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.mq.Logging": { + "type": "object", + "properties": { + "audit": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "general": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.mq.MQ": { + "type": "object", + "properties": { + "brokers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.mq.Broker" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.BrokerLogging": { + "type": "object", + "properties": { + "cloudwatch": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.CloudwatchLogging" + }, + "firehose": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.FirehoseLogging" + }, + "s3": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.S3Logging" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.CloudwatchLogging": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.Cluster": { + "type": "object", + "properties": { + "encryptionatrest": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.EncryptionAtRest" + }, + "encryptionintransit": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.EncryptionInTransit" + }, + "logging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.Logging" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.EncryptionAtRest": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmskeyarn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.EncryptionInTransit": { + "type": "object", + "properties": { + "clientbroker": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.FirehoseLogging": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.Logging": { + "type": "object", + "properties": { + "broker": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.BrokerLogging" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.MSK": { + "type": "object", + "properties": { + "clusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.Cluster" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.S3Logging": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.neptune.Cluster": { + "type": "object", + "properties": { + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "logging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.neptune.Logging" + }, + "storageencrypted": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.neptune.Logging": { + "type": "object", + "properties": { + "audit": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.neptune.Neptune": { + "type": "object", + "properties": { + "clusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.neptune.Cluster" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Classic": { + "type": "object", + "properties": { + "dbsecuritygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.DBSecurityGroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Cluster": { + "type": "object", + "properties": { + "availabilityzones": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "backupretentionperioddays": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "deletionprotection": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Encryption" + }, + "engine": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "instances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.ClusterInstance" + } + }, + "latestrestorabletime": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" + }, + "performanceinsights": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.PerformanceInsights" + }, + "publicaccess": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "replicationsourcearn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "skipfinalsnapshot": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.ClusterInstance": { + "type": "object", + "properties": { + "clusteridentifier": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "instance": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Instance" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.DBParameterGroupsList": { + "type": "object", + "properties": { + "dbparametergroupname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.DBSecurityGroup": { + "type": "object" + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.DBSnapshotAttributes": { + "type": "object", + "properties": { + "attributevalues": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Encryption": { + "type": "object", + "properties": { + "encryptstorage": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Instance": { + "type": "object", + "properties": { + "autominorversionupgrade": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "backupretentionperioddays": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "dbinstancearn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "dbinstanceidentifier": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "dbparametergroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.DBParameterGroupsList" + } + }, + "deletionprotection": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "enabledcloudwatchlogsexports": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Encryption" + }, + "engine": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "engineversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "iamauthenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "latestrestorabletime": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" + }, + "multiaz": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "performanceinsights": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.PerformanceInsights" + }, + "publicaccess": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "publiclyaccessible": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "readreplicadbinstanceidentifiers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "replicationsourcearn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "storageencrypted": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "taglist": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.TagList" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.ParameterGroups": { + "type": "object", + "properties": { + "dbparametergroupfamily": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "dbparametergroupname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "parameters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Parameters" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Parameters": { + "type": "object", + "properties": { + "parametername": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "parametervalue": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.PerformanceInsights": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.RDS": { + "type": "object", + "properties": { + "classic": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Classic" + }, + "clusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Cluster" + } + }, + "instances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Instance" + } + }, + "parametergroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.ParameterGroups" + } + }, + "snapshots": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Snapshots" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Snapshots": { + "type": "object", + "properties": { + "dbsnapshotarn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "dbsnapshotidentifier": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "encrypted": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "snapshotattributes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.DBSnapshotAttributes" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.TagList": { + "type": "object" + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.Cluster": { + "type": "object", + "properties": { + "allowversionupgrade": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "automatedsnapshotretentionperiod": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "clusteridentifier": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.Encryption" + }, + "endpoint": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.EndPoint" + }, + "loggingenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "masterusername": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "nodetype": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "numberofnodes": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "publiclyaccessible": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "subnetgroupname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "vpcid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.ClusterParameter": { + "type": "object", + "properties": { + "parametername": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "parametervalue": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.Encryption": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.EndPoint": { + "type": "object", + "properties": { + "port": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.Redshift": { + "type": "object", + "properties": { + "clusterparameters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.ClusterParameter" + } + }, + "clusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.Cluster" + } + }, + "reservednodes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.ReservedNode" + } + }, + "securitygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.SecurityGroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.ReservedNode": { + "type": "object", + "properties": { + "nodetype": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.SecurityGroup": { + "type": "object", + "properties": { + "description": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Bucket": { + "type": "object", + "properties": { + "accelerateconfigurationstatus": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "acl": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "bucketlocation": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "bucketpolicies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy" + } + }, + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Encryption" + }, + "lifecycleconfiguration": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Rules" + } + }, + "logging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Logging" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "objects": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Contents" + } + }, + "publicaccessblock": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.PublicAccessBlock" + }, + "versioning": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Versioning" + }, + "website": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Website" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Contents": { + "type": "object" + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Encryption": { + "type": "object", + "properties": { + "algorithm": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Logging": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "targetbucket": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.PublicAccessBlock": { + "type": "object", + "properties": { + "blockpublicacls": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "blockpublicpolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "ignorepublicacls": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "restrictpublicbuckets": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Rules": { + "type": "object", + "properties": { + "status": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.S3": { + "type": "object", + "properties": { + "buckets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Bucket" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Versioning": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "mfadelete": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Website": { + "type": "object" + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.API": { + "type": "object", + "properties": { + "accesslogging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.AccessLogging" + }, + "domainconfiguration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.DomainConfiguration" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "restmethodsettings": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.RESTMethodSettings" + }, + "tracingenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.AccessLogging": { + "type": "object", + "properties": { + "cloudwatchloggrouparn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.Application": { + "type": "object", + "properties": { + "location": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.Location" + }, + "locationpath": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.DomainConfiguration": { + "type": "object", + "properties": { + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "securitypolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.Function": { + "type": "object", + "properties": { + "functionname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "managedpolicies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "policies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy" + } + }, + "tracing": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.HttpAPI": { + "type": "object", + "properties": { + "accesslogging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.AccessLogging" + }, + "defaultroutesettings": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.RouteSettings" + }, + "domainconfiguration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.DomainConfiguration" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.Location": { + "type": "object", + "properties": { + "applicationid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "semanticversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.LoggingConfiguration": { + "type": "object", + "properties": { + "loggingenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.RESTMethodSettings": { + "type": "object", + "properties": { + "cachedataencrypted": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "datatraceenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "loggingenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "metricsenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.RouteSettings": { + "type": "object", + "properties": { + "datatraceenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "detailedmetricsenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "loggingenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.SAM": { + "type": "object", + "properties": { + "apis": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.API" + } + }, + "applications": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.Application" + } + }, + "functions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.Function" + } + }, + "httpapis": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.HttpAPI" + } + }, + "simpletables": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.SimpleTable" + } + }, + "statemachines": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.StateMachine" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.SSESpecification": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "kmsmasterkeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.SimpleTable": { + "type": "object", + "properties": { + "ssespecification": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.SSESpecification" + }, + "tablename": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.StateMachine": { + "type": "object", + "properties": { + "loggingconfiguration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.LoggingConfiguration" + }, + "managedpolicies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "policies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy" + } + }, + "tracing": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.TracingConfiguration" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.TracingConfiguration": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sns.Encryption": { + "type": "object", + "properties": { + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sns.SNS": { + "type": "object", + "properties": { + "topics": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sns.Topic" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sns.Topic": { + "type": "object", + "properties": { + "arn": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sns.Encryption" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sqs.Encryption": { + "type": "object", + "properties": { + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "managedencryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sqs.Queue": { + "type": "object", + "properties": { + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sqs.Encryption" + }, + "policies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy" + } + }, + "queueurl": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sqs.SQS": { + "type": "object", + "properties": { + "queues": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sqs.Queue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ssm.SSM": { + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ssm.Secret" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ssm.Secret": { + "type": "object", + "properties": { + "kmskeyid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.Encryption": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.Volume": { + "type": "object", + "properties": { + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.Encryption" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.WorkSpace": { + "type": "object", + "properties": { + "rootvolume": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.Volume" + }, + "uservolume": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.Volume" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.WorkSpaces": { + "type": "object", + "properties": { + "workspaces": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.WorkSpace" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.Azure": { + "type": "object", + "properties": { + "appservice": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.AppService" + }, + "authorization": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.authorization.Authorization" + }, + "compute": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.Compute" + }, + "container": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.Container" + }, + "database": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.Database" + }, + "datafactory": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.datafactory.DataFactory" + }, + "datalake": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.datalake.DataLake" + }, + "keyvault": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.KeyVault" + }, + "monitor": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.monitor.Monitor" + }, + "network": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.Network" + }, + "securitycenter": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.securitycenter.SecurityCenter" + }, + "storage": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Storage" + }, + "synapse": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.synapse.Synapse" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.AppService": { + "type": "object", + "properties": { + "functionapps": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.FunctionApp" + } + }, + "services": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Service" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.FunctionApp": { + "type": "object", + "properties": { + "httpsonly": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Service": { + "type": "object", + "properties": { + "authentication": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Service.Authentication" + }, + "enableclientcert": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "identity": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Service.Identity" + }, + "site": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Service.Site" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Service.Authentication": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Service.Identity": { + "type": "object", + "properties": { + "type": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Service.Site": { + "type": "object", + "properties": { + "enablehttp2": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "minimumtlsversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.authorization.Authorization": { + "type": "object", + "properties": { + "roledefinitions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.authorization.RoleDefinition" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.authorization.Permission": { + "type": "object", + "properties": { + "actions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.authorization.RoleDefinition": { + "type": "object", + "properties": { + "assignablescopes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "permissions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.authorization.Permission" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.Compute": { + "type": "object", + "properties": { + "linuxvirtualmachines": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.LinuxVirtualMachine" + } + }, + "manageddisks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.ManagedDisk" + } + }, + "windowsvirtualmachines": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.WindowsVirtualMachine" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.Encryption": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.LinuxVirtualMachine": { + "type": "object", + "properties": { + "osprofilelinuxconfig": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.OSProfileLinuxConfig" + }, + "virtualmachine": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.VirtualMachine" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.ManagedDisk": { + "type": "object", + "properties": { + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.Encryption" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.OSProfileLinuxConfig": { + "type": "object", + "properties": { + "disablepasswordauthentication": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.VirtualMachine": { + "type": "object", + "properties": { + "customdata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.WindowsVirtualMachine": { + "type": "object", + "properties": { + "virtualmachine": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.VirtualMachine" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.AddonProfile": { + "type": "object", + "properties": { + "omsagent": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.OMSAgent" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.Container": { + "type": "object", + "properties": { + "kubernetesclusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.KubernetesCluster" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.KubernetesCluster": { + "type": "object", + "properties": { + "addonprofile": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.AddonProfile" + }, + "apiserverauthorizedipranges": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "enableprivatecluster": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "networkprofile": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.NetworkProfile" + }, + "rolebasedaccesscontrol": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.RoleBasedAccessControl" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.NetworkProfile": { + "type": "object", + "properties": { + "networkpolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.OMSAgent": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.RoleBasedAccessControl": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.Database": { + "type": "object", + "properties": { + "mariadbservers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.MariaDBServer" + } + }, + "mssqlservers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.MSSQLServer" + } + }, + "mysqlservers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.MySQLServer" + } + }, + "postgresqlservers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.PostgreSQLServer" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.ExtendedAuditingPolicy": { + "type": "object", + "properties": { + "retentionindays": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.FirewallRule": { + "type": "object", + "properties": { + "endip": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "startip": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.MSSQLServer": { + "type": "object", + "properties": { + "extendedauditingpolicies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.ExtendedAuditingPolicy" + } + }, + "securityalertpolicies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.SecurityAlertPolicy" + } + }, + "server": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.Server" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.MariaDBServer": { + "type": "object", + "properties": { + "server": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.Server" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.MySQLServer": { + "type": "object", + "properties": { + "server": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.Server" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.PostgreSQLServer": { + "type": "object", + "properties": { + "config": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.PostgresSQLConfig" + }, + "server": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.Server" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.PostgresSQLConfig": { + "type": "object", + "properties": { + "connectionthrottling": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "logcheckpoints": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "logconnections": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.SecurityAlertPolicy": { + "type": "object", + "properties": { + "disabledalerts": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "emailaccountadmins": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "emailaddresses": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.Server": { + "type": "object", + "properties": { + "enablepublicnetworkaccess": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "enablesslenforcement": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "firewallrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.FirewallRule" + } + }, + "minimumtlsversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.datafactory.DataFactory": { + "type": "object", + "properties": { + "datafactories": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.datafactory.Factory" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.datafactory.Factory": { + "type": "object", + "properties": { + "enablepublicnetwork": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.datalake.DataLake": { + "type": "object", + "properties": { + "stores": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.datalake.Store" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.datalake.Store": { + "type": "object", + "properties": { + "enableencryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.Key": { + "type": "object", + "properties": { + "expirydate": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.KeyVault": { + "type": "object", + "properties": { + "vaults": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.Vault" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.NetworkACLs": { + "type": "object", + "properties": { + "defaultaction": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.Secret": { + "type": "object", + "properties": { + "contenttype": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "expirydate": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.Vault": { + "type": "object", + "properties": { + "enablepurgeprotection": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "keys": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.Key" + } + }, + "networkacls": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.NetworkACLs" + }, + "secrets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.Secret" + } + }, + "softdeleteretentiondays": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.monitor.LogProfile": { + "type": "object", + "properties": { + "categories": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "locations": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "retentionpolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.monitor.RetentionPolicy" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.monitor.Monitor": { + "type": "object", + "properties": { + "logprofiles": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.monitor.LogProfile" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.monitor.RetentionPolicy": { + "type": "object", + "properties": { + "days": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.Network": { + "type": "object", + "properties": { + "networkwatcherflowlogs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.NetworkWatcherFlowLog" + } + }, + "securitygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.SecurityGroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.NetworkWatcherFlowLog": { + "type": "object", + "properties": { + "retentionpolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.RetentionPolicy" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.PortRange": { + "type": "object", + "properties": { + "end": { + "type": "integer" + }, + "start": { + "type": "integer" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.RetentionPolicy": { + "type": "object", + "properties": { + "days": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.SecurityGroup": { + "type": "object", + "properties": { + "rules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.SecurityGroupRule" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.SecurityGroupRule": { + "type": "object", + "properties": { + "allow": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "destinationaddresses": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "destinationports": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.PortRange" + } + }, + "outbound": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "protocol": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "sourceaddresses": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "sourceports": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.PortRange" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.securitycenter.Contact": { + "type": "object", + "properties": { + "enablealertnotifications": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "phone": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.securitycenter.SecurityCenter": { + "type": "object", + "properties": { + "contacts": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.securitycenter.Contact" + } + }, + "subscriptions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.securitycenter.SubscriptionPricing" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.securitycenter.SubscriptionPricing": { + "type": "object", + "properties": { + "tier": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Account": { + "type": "object", + "properties": { + "containers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Container" + } + }, + "enforcehttps": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "minimumtlsversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "networkrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.NetworkRule" + } + }, + "queueproperties": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.QueueProperties" + }, + "queues": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Queue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Container": { + "type": "object", + "properties": { + "publicaccess": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.NetworkRule": { + "type": "object", + "properties": { + "allowbydefault": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "bypass": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Queue": { + "type": "object", + "properties": { + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.QueueProperties": { + "type": "object", + "properties": { + "enablelogging": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Storage": { + "type": "object", + "properties": { + "accounts": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Account" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.synapse.Synapse": { + "type": "object", + "properties": { + "workspaces": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.synapse.Workspace" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.azure.synapse.Workspace": { + "type": "object", + "properties": { + "enablemanagedvirtualnetwork": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.cloudstack.CloudStack": { + "type": "object", + "properties": { + "compute": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.cloudstack.compute.Compute" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.cloudstack.compute.Compute": { + "type": "object", + "properties": { + "instances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.cloudstack.compute.Instance" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.cloudstack.compute.Instance": { + "type": "object", + "properties": { + "userdata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.DigitalOcean": { + "type": "object", + "properties": { + "compute": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.Compute" + }, + "spaces": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Spaces" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.Compute": { + "type": "object", + "properties": { + "droplets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.Droplet" + } + }, + "firewalls": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.Firewall" + } + }, + "kubernetesclusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.KubernetesCluster" + } + }, + "loadbalancers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.LoadBalancer" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.Droplet": { + "type": "object", + "properties": { + "sshkeys": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.Firewall": { + "type": "object", + "properties": { + "inboundrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.InboundFirewallRule" + } + }, + "outboundrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.OutboundFirewallRule" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.ForwardingRule": { + "type": "object", + "properties": { + "entryprotocol": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.InboundFirewallRule": { + "type": "object", + "properties": { + "sourceaddresses": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.KubernetesCluster": { + "type": "object", + "properties": { + "autoupgrade": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "surgeupgrade": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.LoadBalancer": { + "type": "object", + "properties": { + "forwardingrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.ForwardingRule" + } + }, + "redirecthttptohttps": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.OutboundFirewallRule": { + "type": "object", + "properties": { + "destinationaddresses": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Bucket": { + "type": "object", + "properties": { + "acl": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "forcedestroy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "objects": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Object" + } + }, + "versioning": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Versioning" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Object": { + "type": "object", + "properties": { + "acl": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Spaces": { + "type": "object", + "properties": { + "buckets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Bucket" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Versioning": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.github.BranchProtection": { + "type": "object", + "properties": { + "requiresignedcommits": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.github.EnvironmentSecret": { + "type": "object", + "properties": { + "encryptedvalue": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "environment": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "plaintextvalue": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "repository": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "secretname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.github.GitHub": { + "type": "object", + "properties": { + "branchprotections": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.github.BranchProtection" + } + }, + "environmentsecrets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.github.EnvironmentSecret" + } + }, + "repositories": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.github.Repository" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.github.Repository": { + "type": "object", + "properties": { + "archived": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "public": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "vulnerabilityalerts": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.Google": { + "type": "object", + "properties": { + "bigquery": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.bigquery.BigQuery" + }, + "compute": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Compute" + }, + "dns": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.DNS" + }, + "gke": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.GKE" + }, + "iam": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.IAM" + }, + "kms": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.kms.KMS" + }, + "sql": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.SQL" + }, + "storage": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.storage.Storage" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.bigquery.AccessGrant": { + "type": "object", + "properties": { + "domain": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "role": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "specialgroup": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.bigquery.BigQuery": { + "type": "object", + "properties": { + "datasets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.bigquery.Dataset" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.bigquery.Dataset": { + "type": "object", + "properties": { + "accessgrants": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.bigquery.AccessGrant" + } + }, + "id": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Compute": { + "type": "object", + "properties": { + "disks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Disk" + } + }, + "instances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Instance" + } + }, + "networks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Network" + } + }, + "projectmetadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.ProjectMetadata" + }, + "sslpolicies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.SSLPolicy" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Disk": { + "type": "object", + "properties": { + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.DiskEncryption" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.DiskEncryption": { + "type": "object", + "properties": { + "kmskeylink": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "rawkey": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BytesValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.EgressRule": { + "type": "object", + "properties": { + "destinationranges": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "firewallrule": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.FirewallRule" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Firewall": { + "type": "object", + "properties": { + "egressrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.EgressRule" + } + }, + "ingressrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.IngressRule" + } + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "sourcetags": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "targettags": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.FirewallRule": { + "type": "object", + "properties": { + "enforced": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "isallow": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "ports": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + } + }, + "protocol": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.IngressRule": { + "type": "object", + "properties": { + "firewallrule": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.FirewallRule" + }, + "sourceranges": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Instance": { + "type": "object", + "properties": { + "attacheddisks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Disk" + } + }, + "bootdisks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Disk" + } + }, + "canipforward": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "enableprojectsshkeyblocking": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "enableserialport": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "networkinterfaces": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.NetworkInterface" + } + }, + "osloginenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "serviceaccount": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.ServiceAccount" + }, + "shieldedvm": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.ShieldedVMConfig" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Network": { + "type": "object", + "properties": { + "firewall": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Firewall" + }, + "subnetworks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.SubNetwork" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.NetworkInterface": { + "type": "object", + "properties": { + "haspublicip": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "natip": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "network": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Network" + }, + "subnetwork": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.SubNetwork" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.ProjectMetadata": { + "type": "object", + "properties": { + "enableoslogin": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.SSLPolicy": { + "type": "object", + "properties": { + "minimumtlsversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "profile": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.ServiceAccount": { + "type": "object", + "properties": { + "email": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "isdefault": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "scopes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.ShieldedVMConfig": { + "type": "object", + "properties": { + "integritymonitoringenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "securebootenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "vtpmenabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.SubNetwork": { + "type": "object", + "properties": { + "enableflowlogs": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "purpose": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.DNS": { + "type": "object", + "properties": { + "managedzones": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.ManagedZone" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.DNSSec": { + "type": "object", + "properties": { + "defaultkeyspecs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.KeySpecs" + } + }, + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.KeySpecs": { + "type": "object", + "properties": { + "algorithm": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "keytype": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.ManagedZone": { + "type": "object", + "properties": { + "dnssec": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.DNSSec" + }, + "visibility": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.ClientCertificate": { + "type": "object", + "properties": { + "issuecertificate": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.Cluster": { + "type": "object", + "properties": { + "datapathprovider": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "enableautpilot": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "enablelegacyabac": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "enableshieldednodes": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "ipallocationpolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.IPAllocationPolicy" + }, + "loggingservice": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "masterauth": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.MasterAuth" + }, + "masterauthorizednetworks": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.MasterAuthorizedNetworks" + }, + "monitoringservice": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "networkpolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.NetworkPolicy" + }, + "nodeconfig": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.NodeConfig" + }, + "nodepools": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.NodePool" + } + }, + "privatecluster": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.PrivateCluster" + }, + "removedefaultnodepool": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "resourcelabels": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.MapValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.GKE": { + "type": "object", + "properties": { + "clusters": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.Cluster" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.IPAllocationPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.Management": { + "type": "object", + "properties": { + "enableautorepair": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "enableautoupgrade": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.MasterAuth": { + "type": "object", + "properties": { + "clientcertificate": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.ClientCertificate" + }, + "password": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "username": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.MasterAuthorizedNetworks": { + "type": "object", + "properties": { + "cidrs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.NetworkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.NodeConfig": { + "type": "object", + "properties": { + "enablelegacyendpoints": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "imagetype": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "serviceaccount": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "workloadmetadataconfig": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.WorkloadMetadataConfig" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.NodePool": { + "type": "object", + "properties": { + "management": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.Management" + }, + "nodeconfig": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.NodeConfig" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.PrivateCluster": { + "type": "object", + "properties": { + "enableprivatenodes": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.WorkloadMetadataConfig": { + "type": "object", + "properties": { + "nodemetadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Binding": { + "type": "object", + "properties": { + "includesdefaultserviceaccount": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "members": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "role": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Folder": { + "type": "object", + "properties": { + "bindings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Binding" + } + }, + "folders": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Folder" + } + }, + "members": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Member" + } + }, + "projects": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Project" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.IAM": { + "type": "object", + "properties": { + "organizations": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Organization" + } + }, + "workloadidentitypoolproviders": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.WorkloadIdentityPoolProvider" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Member": { + "type": "object", + "properties": { + "defaultserviceaccount": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "member": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "role": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Organization": { + "type": "object", + "properties": { + "bindings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Binding" + } + }, + "folders": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Folder" + } + }, + "members": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Member" + } + }, + "projects": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Project" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Project": { + "type": "object", + "properties": { + "autocreatenetwork": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "bindings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Binding" + } + }, + "members": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Member" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.WorkloadIdentityPoolProvider": { + "type": "object", + "properties": { + "attributecondition": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "workloadidentitypoolid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "workloadidentitypoolproviderid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.kms.KMS": { + "type": "object", + "properties": { + "keyrings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.kms.KeyRing" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.kms.Key": { + "type": "object", + "properties": { + "rotationperiodseconds": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.kms.KeyRing": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.kms.Key" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.Backups": { + "type": "object", + "properties": { + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.DatabaseInstance": { + "type": "object", + "properties": { + "databaseversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "isreplica": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "settings": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.Settings" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.Flags": { + "type": "object", + "properties": { + "containeddatabaseauthentication": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "crossdbownershipchaining": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "localinfile": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "logcheckpoints": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "logconnections": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "logdisconnections": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "loglockwaits": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "logmindurationstatement": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "logminmessages": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "logtempfilesize": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.IPConfiguration": { + "type": "object", + "properties": { + "authorizednetworks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.IPConfiguration.AuthorizedNetworks" + } + }, + "enableipv4": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "requiretls": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.IPConfiguration.AuthorizedNetworks": { + "type": "object", + "properties": { + "cidr": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.SQL": { + "type": "object", + "properties": { + "instances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.DatabaseInstance" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.Settings": { + "type": "object", + "properties": { + "backups": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.Backups" + }, + "flags": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.Flags" + }, + "ipconfiguration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.IPConfiguration" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.storage.Bucket": { + "type": "object", + "properties": { + "bindings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Binding" + } + }, + "enableuniformbucketlevelaccess": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "encryption": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.storage.BucketEncryption" + }, + "location": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "members": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Member" + } + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.storage.BucketEncryption": { + "type": "object", + "properties": { + "defaultkmskeyname": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.google.storage.Storage": { + "type": "object", + "properties": { + "buckets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.storage.Bucket" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Egress": { + "type": "object", + "properties": { + "destinationcidrs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "ports": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Port" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Ingress": { + "type": "object", + "properties": { + "ports": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Port" + } + }, + "sourcecidrs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Kubernetes": { + "type": "object", + "properties": { + "networkpolicies": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.NetworkPolicy" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.NetworkPolicy": { + "type": "object", + "properties": { + "spec": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.NetworkPolicySpec" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.NetworkPolicySpec": { + "type": "object", + "properties": { + "egress": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Egress" + }, + "ingress": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Ingress" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Port": { + "type": "object", + "properties": { + "number": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "protocol": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.Nifcloud": { + "type": "object", + "properties": { + "computing": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.Computing" + }, + "dns": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.dns.DNS" + }, + "nas": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.nas.NAS" + }, + "network": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.Network" + }, + "rdb": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.rdb.RDB" + }, + "sslcertificate": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.sslcertificate.SSLCertificate" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.Computing": { + "type": "object", + "properties": { + "instances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.Instance" + } + }, + "securitygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.SecurityGroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.Instance": { + "type": "object", + "properties": { + "networkinterfaces": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.NetworkInterface" + } + }, + "securitygroup": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.NetworkInterface": { + "type": "object", + "properties": { + "networkid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.SecurityGroup": { + "type": "object", + "properties": { + "description": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "egressrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.SecurityGroupRule" + } + }, + "ingressrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.SecurityGroupRule" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.SecurityGroupRule": { + "type": "object", + "properties": { + "cidr": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "description": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.dns.DNS": { + "type": "object", + "properties": { + "records": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.dns.Record" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.dns.Record": { + "type": "object", + "properties": { + "record": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "type": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.nas.NAS": { + "type": "object", + "properties": { + "nasinstances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.nas.NASInstance" + } + }, + "nassecuritygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.nas.NASSecurityGroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.nas.NASInstance": { + "type": "object", + "properties": { + "networkid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.nas.NASSecurityGroup": { + "type": "object", + "properties": { + "cidrs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "description": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.ElasticLoadBalancer": { + "type": "object", + "properties": { + "listeners": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.ElasticLoadBalancerListener" + } + }, + "networkinterfaces": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.NetworkInterface" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.ElasticLoadBalancerListener": { + "type": "object", + "properties": { + "protocol": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.LoadBalancer": { + "type": "object", + "properties": { + "listeners": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.LoadBalancerListener" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.LoadBalancerListener": { + "type": "object", + "properties": { + "protocol": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "tlspolicy": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.Network": { + "type": "object", + "properties": { + "elasticloadbalancers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.ElasticLoadBalancer" + } + }, + "loadbalancers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.LoadBalancer" + } + }, + "routers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.Router" + } + }, + "vpngateways": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.VpnGateway" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.NetworkInterface": { + "type": "object", + "properties": { + "isvipnetwork": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "networkid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.Router": { + "type": "object", + "properties": { + "networkinterfaces": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.NetworkInterface" + } + }, + "securitygroup": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.VpnGateway": { + "type": "object", + "properties": { + "securitygroup": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.rdb.DBInstance": { + "type": "object", + "properties": { + "backupretentionperioddays": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "engine": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "engineversion": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "networkid": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "publicaccess": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.rdb.DBSecurityGroup": { + "type": "object", + "properties": { + "cidrs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + }, + "description": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.rdb.RDB": { + "type": "object", + "properties": { + "dbinstances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.rdb.DBInstance" + } + }, + "dbsecuritygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.rdb.DBSecurityGroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.sslcertificate.SSLCertificate": { + "type": "object", + "properties": { + "servercertificates": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.sslcertificate.ServerCertificate" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.sslcertificate.ServerCertificate": { + "type": "object", + "properties": { + "expiration": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.Compute": { + "type": "object", + "properties": { + "firewall": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.openstack.Firewall" + }, + "instances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.openstack.Instance" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.Firewall": { + "type": "object", + "properties": { + "allowrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.openstack.FirewallRule" + } + }, + "denyrules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.openstack.FirewallRule" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.FirewallRule": { + "type": "object", + "properties": { + "destination": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "destinationport": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "enabled": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "source": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "sourceport": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.Instance": { + "type": "object", + "properties": { + "adminpassword": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.Networking": { + "type": "object", + "properties": { + "securitygroups": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.openstack.SecurityGroup" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.OpenStack": { + "type": "object", + "properties": { + "compute": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.openstack.Compute" + }, + "networking": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.openstack.Networking" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.SecurityGroup": { + "type": "object", + "properties": { + "description": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "name": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.openstack.SecurityGroupRule" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.SecurityGroupRule": { + "type": "object", + "properties": { + "cidr": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "ethertype": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "isingress": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" + }, + "portmax": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "portmin": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "protocol": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.oracle.AddressReservation": { + "type": "object", + "properties": { + "pool": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.oracle.Compute": { + "type": "object", + "properties": { + "addressreservations": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.oracle.AddressReservation" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.oracle.Oracle": { + "type": "object", + "properties": { + "compute": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.oracle.Compute" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.types.BoolValue": { + "type": "object", + "properties": { + "endline": { + "type": "integer" + }, + "explicit": { + "type": "boolean" + }, + "filepath": { + "type": "string" + }, + "fskey": { + "type": "string" + }, + "managed": { + "type": "boolean" + }, + "resource": { + "type": "string" + }, + "sourceprefix": { + "type": "string" + }, + "startline": { + "type": "integer" + }, + "value": { + "type": "boolean" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.types.BytesValue": { + "type": "object", + "properties": { + "endline": { + "type": "integer" + }, + "explicit": { + "type": "boolean" + }, + "filepath": { + "type": "string" + }, + "fskey": { + "type": "string" + }, + "managed": { + "type": "boolean" + }, + "resource": { + "type": "string" + }, + "sourceprefix": { + "type": "string" + }, + "startline": { + "type": "integer" + }, + "value": { + "type": "string" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.types.IntValue": { + "type": "object", + "properties": { + "endline": { + "type": "integer" + }, + "explicit": { + "type": "boolean" + }, + "filepath": { + "type": "string" + }, + "fskey": { + "type": "string" + }, + "managed": { + "type": "boolean" + }, + "resource": { + "type": "string" + }, + "sourceprefix": { + "type": "string" + }, + "startline": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.types.MapValue": { + "type": "object", + "properties": { + "endline": { + "type": "integer" + }, + "explicit": { + "type": "boolean" + }, + "filepath": { + "type": "string" + }, + "fskey": { + "type": "string" + }, + "managed": { + "type": "boolean" + }, + "resource": { + "type": "string" + }, + "sourceprefix": { + "type": "string" + }, + "startline": { + "type": "integer" + }, + "value": { + "type": "object" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.types.StringValue": { + "type": "object", + "properties": { + "endline": { + "type": "integer" + }, + "explicit": { + "type": "boolean" + }, + "filepath": { + "type": "string" + }, + "fskey": { + "type": "string" + }, + "managed": { + "type": "boolean" + }, + "resource": { + "type": "string" + }, + "sourceprefix": { + "type": "string" + }, + "startline": { + "type": "integer" + }, + "value": { + "type": "string" + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.types.TimeValue": { + "type": "object", + "properties": { + "endline": { + "type": "integer" + }, + "explicit": { + "type": "boolean" + }, + "filepath": { + "type": "string" + }, + "fskey": { + "type": "string" + }, + "managed": { + "type": "boolean" + }, + "resource": { + "type": "string" + }, + "sourceprefix": { + "type": "string" + }, + "startline": { + "type": "integer" + }, + "value": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/pkg/iac/rego/schemas/dockerfile.json b/pkg/iac/rego/schemas/dockerfile.json new file mode 100644 index 000000000000..d769cb195bae --- /dev/null +++ b/pkg/iac/rego/schemas/dockerfile.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/aquasecurity/trivy-policies/blob/main/pkg/rego/schemas/dockerfile.json", + "type": "object", + "properties": { + "Stages": { + "type": "array", + "items": { + "$ref": "#/$defs/stage" + } + } + }, + "$defs": { + "stage": { + "type": "object", + "properties": { + "Name": { + "type": "string" + }, + "Commands": { + "type": "array", + "items": { + "$ref": "#/$defs/command" + } + } + } + }, + "command": { + "type": "object", + "properties": { + "Flags": { + "type": "array", + "items": { + "type": "string" + } + }, + "Value": { + "type": "array", + "items": { + "type": "string" + } + }, + "Cmd": { + "type": "string" + }, + "SubCmd": { + "type": "string" + }, + "Original": { + "type": "string" + }, + "Path": { + "type": "string" + }, + "JSON": { + "type": "boolean" + }, + "Stage": { + "type": "integer" + }, + "StartLine": { + "type": "integer" + }, + "EndLine": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/pkg/iac/rego/schemas/kubernetes.json b/pkg/iac/rego/schemas/kubernetes.json new file mode 100644 index 000000000000..1975944b7790 --- /dev/null +++ b/pkg/iac/rego/schemas/kubernetes.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/aquasecurity/trivy-policies/blob/main/pkg/rego/schemas/kubernetes.json", + "type": "object", + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "type": "object" + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "apiGroups": { + "type": "array", + "items": { + "type": "string" + } + }, + "resources": { + "type": "array", + "items": { + "type": "string" + } + }, + "resourceNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "verbs": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/pkg/iac/rego/schemas/rbac.json b/pkg/iac/rego/schemas/rbac.json new file mode 100644 index 000000000000..c251890f91fd --- /dev/null +++ b/pkg/iac/rego/schemas/rbac.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/aquasecurity/trivy-policies/blob/main/pkg/rego/schemas/rbac.json", + "type": "object", + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "type": "object" + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "apiGroups": { + "type": "array", + "items": { + "type": "string" + } + }, + "resources": { + "type": "array", + "items": { + "type": "string" + } + }, + "resourceNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "verbs": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/pkg/iac/rego/schemas/schemas.go b/pkg/iac/rego/schemas/schemas.go new file mode 100644 index 000000000000..73f92c10fc89 --- /dev/null +++ b/pkg/iac/rego/schemas/schemas.go @@ -0,0 +1,16 @@ +package schemas + +import ( + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var SchemaMap = map[types.Source]Schema{ + types.SourceDefsec: Cloud, + types.SourceCloud: Cloud, + types.SourceKubernetes: Kubernetes, + types.SourceRbac: Kubernetes, + types.SourceDockerfile: Dockerfile, + types.SourceTOML: Anything, + types.SourceYAML: Anything, + types.SourceJSON: Anything, +} diff --git a/pkg/iac/rego/store.go b/pkg/iac/rego/store.go new file mode 100644 index 000000000000..c75818d402e7 --- /dev/null +++ b/pkg/iac/rego/store.go @@ -0,0 +1,48 @@ +package rego + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/open-policy-agent/opa/loader" + "github.com/open-policy-agent/opa/storage" +) + +// initialize a store populated with OPA data files found in dataPaths +func initStore(dataFS fs.FS, dataPaths, namespaces []string) (storage.Store, error) { + // FilteredPaths will recursively find all file paths that contain a valid document + // extension from the given list of data paths. + allDocumentPaths, _ := loader.FilteredPathsFS(dataFS, dataPaths, func(abspath string, info os.FileInfo, depth int) bool { + if info.IsDir() { + return false // filter in, include + } + ext := strings.ToLower(filepath.Ext(info.Name())) + for _, filter := range []string{ + ".yaml", + ".yml", + ".json", + } { + if filter == ext { + return false // filter in, include + } + } + return true // filter out, exclude + }) + + documents, err := loader.NewFileLoader().WithFS(dataFS).All(allDocumentPaths) + if err != nil { + return nil, fmt.Errorf("load documents: %w", err) + } + + // pass all namespaces so that rego rule can refer to namespaces as data.namespaces + documents.Documents["namespaces"] = namespaces + + store, err := documents.Store() + if err != nil { + return nil, fmt.Errorf("get documents store: %w", err) + } + return store, nil +} diff --git a/pkg/iac/rego/testdata/policies/invalid.rego b/pkg/iac/rego/testdata/policies/invalid.rego new file mode 100644 index 000000000000..a2ef3607bc70 --- /dev/null +++ b/pkg/iac/rego/testdata/policies/invalid.rego @@ -0,0 +1,8 @@ +# METADATA +# schemas: +# - input: schema["input"] +package defsec.test_invalid + +deny { + input.Stages[0].Commands[0].FooBarNothingBurger == "lol" +} diff --git a/pkg/iac/rego/testdata/policies/valid.rego b/pkg/iac/rego/testdata/policies/valid.rego new file mode 100644 index 000000000000..74a96afeec0c --- /dev/null +++ b/pkg/iac/rego/testdata/policies/valid.rego @@ -0,0 +1,8 @@ +# METADATA +# schemas: +# - input: schema["input"] +package defsec.test_valid + +deny { + input.Stages[0].Commands[0].Cmd == "lol" +} diff --git a/pkg/iac/rules/providers.go b/pkg/iac/rules/providers.go new file mode 100644 index 000000000000..7c14aa1c627a --- /dev/null +++ b/pkg/iac/rules/providers.go @@ -0,0 +1,169 @@ +package rules + +import ( + "encoding/json" + "strings" +) + +type Provider struct { + Name string `json:"name"` + Services []Service `json:"services"` +} + +type Service struct { + Name string `json:"name"` + Checks []Check `json:"checks"` +} + +type Check struct { + Name string `json:"name"` + Description string `json:"description"` +} + +func GetProvidersHierarchy() (providers map[string]map[string][]string) { + + registeredRules := GetRegistered() + + provs := make(map[string]map[string][]string) + + for _, rule := range registeredRules { + + cNames := make(map[string]bool) + pName := strings.ToLower(rule.GetRule().Provider.DisplayName()) + sName := strings.ToLower(rule.GetRule().Service) + cName := rule.GetRule().AVDID + + if _, ok := provs[pName]; !ok { + provs[pName] = make(map[string][]string) + } + + if _, ok := provs[pName][sName]; !ok { + provs[pName][sName] = make([]string, 0) + } + + if _, ok := cNames[cName]; !ok { + cNames[cName] = true + provs[pName][sName] = append(provs[pName][sName], cName) + } + } + + return provs +} + +func GetProviders() (providers []Provider) { + + registeredRules := GetRegistered() + + provs := make(map[string]map[string][]Check) + + for _, rule := range registeredRules { + + pName := strings.ToLower(rule.GetRule().Provider.DisplayName()) + sName := strings.ToLower(rule.GetRule().Service) + cName := rule.GetRule().AVDID + desc := rule.GetRule().Summary + + if _, ok := provs[pName]; !ok { + provs[pName] = make(map[string][]Check) + } + + if _, ok := provs[pName][sName]; !ok { + provs[pName][sName] = []Check{} + } + + provs[pName][sName] = append(provs[pName][sName], Check{ + Name: cName, + Description: desc, + }) + } + + for providerName, providerServices := range provs { + var services []Service + for serviceName, checks := range providerServices { + services = append(services, Service{ + Name: serviceName, + Checks: checks, + }) + } + + providers = append(providers, Provider{ + Name: providerName, + Services: services, + }) + } + + return providers +} + +func GetProvidersAsJson() ([]byte, error) { + + providers := GetProviders() + + return json.MarshalIndent(providers, "", " ") +} + +func GetProviderNames() []string { + + registeredRules := GetRegistered() + + providers := make(map[string]bool) + + for _, rule := range registeredRules { + + if _, ok := providers[rule.GetRule().Provider.DisplayName()]; !ok { + providers[rule.GetRule().Provider.DisplayName()] = true + } + + } + + var uniqueProviders []string + for p := range providers { + uniqueProviders = append(uniqueProviders, p) + } + + return uniqueProviders + +} + +func GetProviderServiceNames(providerName string) []string { + + registeredRules := GetRegistered() + + services := make(map[string]bool) + + for _, rule := range registeredRules { + + if !strings.EqualFold(providerName, rule.GetRule().Provider.DisplayName()) { + continue + } + + if _, ok := services[rule.GetRule().Service]; !ok { + services[rule.GetRule().Service] = true + } + + } + var uniqueServices []string + for p := range services { + uniqueServices = append(uniqueServices, p) + } + + return uniqueServices +} + +func GetProviderServiceCheckNames(providerName, serviceName string) []string { + + registeredRules := GetRegistered() + + var checks []string + + for _, rule := range registeredRules { + + if !strings.EqualFold(providerName, rule.GetRule().Provider.DisplayName()) || + !strings.EqualFold(serviceName, rule.GetRule().Service) { + continue + } + + checks = append(checks, rule.GetRule().AVDID) + } + return checks +} diff --git a/pkg/iac/rules/register.go b/pkg/iac/rules/register.go new file mode 100755 index 000000000000..f34cdf904da3 --- /dev/null +++ b/pkg/iac/rules/register.go @@ -0,0 +1,137 @@ +package rules + +import ( + "sync" + + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy-policies/specs" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/scan" + dftypes "github.com/aquasecurity/trivy/pkg/iac/types" + ruleTypes "github.com/aquasecurity/trivy/pkg/iac/types/rules" +) + +type registry struct { + sync.RWMutex + index int + frameworks map[framework.Framework][]ruleTypes.RegisteredRule +} + +var coreRegistry = registry{ + frameworks: make(map[framework.Framework][]ruleTypes.RegisteredRule), +} + +func Reset() { + coreRegistry.Reset() +} + +func Register(rule scan.Rule) ruleTypes.RegisteredRule { + return coreRegistry.register(rule) +} + +func Deregister(rule ruleTypes.RegisteredRule) { + coreRegistry.deregister(rule) +} + +func (r *registry) register(rule scan.Rule) ruleTypes.RegisteredRule { + r.Lock() + defer r.Unlock() + if len(rule.Frameworks) == 0 { + rule.Frameworks = map[framework.Framework][]string{framework.Default: nil} + } + registeredRule := ruleTypes.RegisteredRule{ + Number: r.index, + Rule: rule, + } + r.index++ + for fw := range rule.Frameworks { + r.frameworks[fw] = append(r.frameworks[fw], registeredRule) + } + + r.frameworks[framework.ALL] = append(r.frameworks[framework.ALL], registeredRule) + + return registeredRule +} + +func (r *registry) deregister(rule ruleTypes.RegisteredRule) { + r.Lock() + defer r.Unlock() + for fw := range r.frameworks { + for i, registered := range r.frameworks[fw] { + if registered.Number == rule.Number { + r.frameworks[fw] = append(r.frameworks[fw][:i], r.frameworks[fw][i+1:]...) + break + } + } + } +} + +func (r *registry) getFrameworkRules(fw ...framework.Framework) []ruleTypes.RegisteredRule { + r.RLock() + defer r.RUnlock() + var registered []ruleTypes.RegisteredRule + if len(fw) == 0 { + fw = []framework.Framework{framework.Default} + } + unique := make(map[int]struct{}) + for _, f := range fw { + for _, rule := range r.frameworks[f] { + if _, ok := unique[rule.Number]; ok { + continue + } + registered = append(registered, rule) + unique[rule.Number] = struct{}{} + } + } + return registered +} + +func (r *registry) getSpecRules(spec string) []ruleTypes.RegisteredRule { + r.RLock() + defer r.RUnlock() + var specRules []ruleTypes.RegisteredRule + + var complianceSpec dftypes.ComplianceSpec + specContent := specs.GetSpec(spec) + if err := yaml.Unmarshal([]byte(specContent), &complianceSpec); err != nil { + return nil + } + + registered := r.getFrameworkRules(framework.ALL) + for _, rule := range registered { + for _, csRule := range complianceSpec.Spec.Controls { + if len(csRule.Checks) > 0 { + for _, c := range csRule.Checks { + if rule.GetRule().AVDID == c.ID { + specRules = append(specRules, rule) + } + } + } + } + } + + return specRules +} + +func (r *registry) Reset() { + r.Lock() + defer r.Unlock() + r.frameworks = make(map[framework.Framework][]ruleTypes.RegisteredRule) +} + +func GetFrameworkRules(fw ...framework.Framework) []ruleTypes.RegisteredRule { + return coreRegistry.getFrameworkRules(fw...) +} + +func GetSpecRules(spec string) []ruleTypes.RegisteredRule { + if len(spec) > 0 { + return coreRegistry.getSpecRules(spec) + } + + return GetFrameworkRules() +} + +func GetRegistered(fw ...framework.Framework) []ruleTypes.RegisteredRule { + return GetFrameworkRules(fw...) +} diff --git a/pkg/iac/rules/register_test.go b/pkg/iac/rules/register_test.go new file mode 100644 index 000000000000..22eec16c0c66 --- /dev/null +++ b/pkg/iac/rules/register_test.go @@ -0,0 +1,139 @@ +package rules + +import ( + "fmt" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Reset(t *testing.T) { + Reset() + rule := scan.Rule{} + _ = Register(rule) + assert.Equal(t, 1, len(GetFrameworkRules())) + Reset() + assert.Equal(t, 0, len(GetFrameworkRules())) +} + +func Test_Registration(t *testing.T) { + var tests = []struct { + name string + registeredFrameworks map[framework.Framework][]string + inputFrameworks []framework.Framework + expected bool + }{ + { + name: "rule without framework specified should be returned when no frameworks are requested", + expected: true, + }, + { + name: "rule without framework specified should not be returned when a specific framework is requested", + inputFrameworks: []framework.Framework{framework.CIS_AWS_1_2}, + expected: false, + }, + { + name: "rule without framework specified should be returned when the default framework is requested", + inputFrameworks: []framework.Framework{framework.Default}, + expected: true, + }, + { + name: "rule with default framework specified should be returned when the default framework is requested", + registeredFrameworks: map[framework.Framework][]string{framework.Default: {"1.1"}}, + inputFrameworks: []framework.Framework{framework.Default}, + expected: true, + }, + { + name: "rule with default framework specified should not be returned when a specific framework is requested", + registeredFrameworks: map[framework.Framework][]string{framework.Default: {"1.1"}}, + inputFrameworks: []framework.Framework{framework.CIS_AWS_1_2}, + expected: false, + }, + { + name: "rule with specific framework specified should not be returned when a default framework is requested", + registeredFrameworks: map[framework.Framework][]string{framework.CIS_AWS_1_2: {"1.1"}}, + inputFrameworks: []framework.Framework{framework.Default}, + expected: false, + }, + { + name: "rule with specific framework specified should be returned when the specific framework is requested", + registeredFrameworks: map[framework.Framework][]string{framework.CIS_AWS_1_2: {"1.1"}}, + inputFrameworks: []framework.Framework{framework.CIS_AWS_1_2}, + expected: true, + }, + { + name: "rule with multiple frameworks specified should be returned when the specific framework is requested", + registeredFrameworks: map[framework.Framework][]string{framework.CIS_AWS_1_2: {"1.1"}, "blah": {"1.2"}}, + inputFrameworks: []framework.Framework{framework.CIS_AWS_1_2}, + expected: true, + }, + { + name: "rule with multiple frameworks specified should be returned only once when multiple matching frameworks are requested", + registeredFrameworks: map[framework.Framework][]string{framework.CIS_AWS_1_2: {"1.1"}, "blah": {"1.2"}, "something": {"1.3"}}, + inputFrameworks: []framework.Framework{framework.CIS_AWS_1_2, "blah", "other"}, + expected: true, + }, + } + + for i, test := range tests { + t.Run(test.name, func(t *testing.T) { + Reset() + rule := scan.Rule{ + AVDID: fmt.Sprintf("%d-%s", i, test.name), + Frameworks: test.registeredFrameworks, + } + _ = Register(rule) + var found bool + for _, matchedRule := range GetFrameworkRules(test.inputFrameworks...) { + if matchedRule.GetRule().AVDID == rule.AVDID { + assert.False(t, found, "rule should not be returned more than once") + found = true + } + } + assert.Equal(t, test.expected, found, "rule should be returned if it matches any of the input frameworks") + }) + } +} + +func Test_Deregistration(t *testing.T) { + Reset() + registrationA := Register(scan.Rule{ + AVDID: "A", + }) + registrationB := Register(scan.Rule{ + AVDID: "B", + }) + assert.Equal(t, 2, len(GetFrameworkRules())) + Deregister(registrationA) + actual := GetFrameworkRules() + require.Equal(t, 1, len(actual)) + assert.Equal(t, "B", actual[0].GetRule().AVDID) + Deregister(registrationB) + assert.Equal(t, 0, len(GetFrameworkRules())) +} + +func Test_DeregistrationMultipleFrameworks(t *testing.T) { + Reset() + registrationA := Register(scan.Rule{ + AVDID: "A", + }) + registrationB := Register(scan.Rule{ + AVDID: "B", + Frameworks: map[framework.Framework][]string{ + "a": nil, + "b": nil, + "c": nil, + framework.Default: nil, + }, + }) + assert.Equal(t, 2, len(GetFrameworkRules())) + Deregister(registrationA) + actual := GetFrameworkRules() + require.Equal(t, 1, len(actual)) + assert.Equal(t, "B", actual[0].GetRule().AVDID) + Deregister(registrationB) + assert.Equal(t, 0, len(GetFrameworkRules())) +} diff --git a/pkg/iac/rules/rules.go b/pkg/iac/rules/rules.go new file mode 100644 index 000000000000..bb3eec14ae35 --- /dev/null +++ b/pkg/iac/rules/rules.go @@ -0,0 +1,83 @@ +package rules + +import ( + trules "github.com/aquasecurity/trivy-policies/pkg/rules" + + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/accessanalyzer" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/apigateway" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/athena" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/cloudfront" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/cloudtrail" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/cloudwatch" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/codebuild" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/config" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/documentdb" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/dynamodb" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/ec2" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/ecr" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/ecs" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/efs" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/eks" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/elasticache" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/elasticsearch" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/elb" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/emr" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/iam" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/kinesis" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/kms" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/lambda" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/mq" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/msk" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/neptune" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/rds" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/redshift" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/s3" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/sam" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/sns" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/sqs" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/ssm" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/workspaces" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/appservice" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/authorization" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/compute" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/container" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/database" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/datafactory" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/datalake" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/keyvault" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/monitor" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/network" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/securitycenter" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/storage" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/synapse" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/cloudstack/compute" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/digitalocean/compute" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/digitalocean/spaces" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/github/actions" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/github/branch_protections" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/github/repositories" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/bigquery" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/compute" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/dns" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/gke" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/iam" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/kms" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/sql" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/storage" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/computing" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/dns" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/nas" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/network" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/rdb" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/sslcertificate" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/openstack/compute" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/openstack/networking" + _ "github.com/aquasecurity/trivy-policies/checks/cloud/oracle/compute" + _ "github.com/aquasecurity/trivy-policies/checks/kubernetes/network" +) + +func init() { + for _, r := range trules.GetRules() { + Register(r) + } +} diff --git a/pkg/iac/scan/code.go b/pkg/iac/scan/code.go new file mode 100644 index 000000000000..5041b23ca2cc --- /dev/null +++ b/pkg/iac/scan/code.go @@ -0,0 +1,285 @@ +package scan + +import ( + "fmt" + "io/fs" + "path/filepath" + "strings" + + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Code struct { + Lines []Line +} + +type Line struct { + Number int `json:"Number"` + Content string `json:"Content"` + IsCause bool `json:"IsCause"` + Annotation string `json:"Annotation"` + Truncated bool `json:"Truncated"` + Highlighted string `json:"Highlighted,omitempty"` + FirstCause bool `json:"FirstCause"` + LastCause bool `json:"LastCause"` +} + +func (c *Code) IsCauseMultiline() bool { + var count int + for _, line := range c.Lines { + if line.IsCause { + count++ + if count > 1 { + return true + } + } + } + return false +} + +const ( + darkTheme = "solarized-dark256" + lightTheme = "github" +) + +type codeSettings struct { + theme string + allowTruncation bool + maxLines int + includeHighlighted bool +} + +var defaultCodeSettings = codeSettings{ + theme: darkTheme, + allowTruncation: true, + maxLines: 10, + includeHighlighted: true, +} + +type CodeOption func(*codeSettings) + +func OptionCodeWithTheme(theme string) CodeOption { + return func(s *codeSettings) { + s.theme = theme + } +} + +func OptionCodeWithDarkTheme() CodeOption { + return func(s *codeSettings) { + s.theme = darkTheme + } +} + +func OptionCodeWithLightTheme() CodeOption { + return func(s *codeSettings) { + s.theme = lightTheme + } +} + +func OptionCodeWithTruncation(truncate bool) CodeOption { + return func(s *codeSettings) { + s.allowTruncation = truncate + } +} + +func OptionCodeWithMaxLines(lines int) CodeOption { + return func(s *codeSettings) { + s.maxLines = lines + } +} + +func OptionCodeWithHighlighted(include bool) CodeOption { + return func(s *codeSettings) { + s.includeHighlighted = include + } +} + +func validateRange(r iacTypes.Range) error { + if r.GetStartLine() < 0 || r.GetStartLine() > r.GetEndLine() || r.GetEndLine() < 0 { + return fmt.Errorf("invalid range: %s", r.String()) + } + return nil +} + +// nolint +func (r *Result) GetCode(opts ...CodeOption) (*Code, error) { + + settings := defaultCodeSettings + for _, opt := range opts { + opt(&settings) + } + + srcFS := r.Metadata().Range().GetFS() + if srcFS == nil { + return nil, fmt.Errorf("code unavailable: result was not mapped to a known filesystem") + } + + innerRange := r.Range() + outerRange := innerRange + metadata := r.Metadata() + for { + if parent := metadata.Parent(); parent != nil && + parent.Range().GetFilename() == metadata.Range().GetFilename() && + parent.Range().GetStartLine() > 0 { + outerRange = parent.Range() + metadata = *parent + continue + } + break + } + + if err := validateRange(innerRange); err != nil { + return nil, err + } + if err := validateRange(outerRange); err != nil { + return nil, err + } + + slashed := filepath.ToSlash(r.fsPath) + slashed = strings.TrimPrefix(slashed, "/") + + content, err := fs.ReadFile(srcFS, slashed) + if err != nil { + return nil, fmt.Errorf("failed to read file from result filesystem (%#v): %w", srcFS, err) + } + + hasAnnotation := r.Annotation() != "" + + code := Code{ + Lines: nil, + } + + rawLines := strings.Split(string(content), "\n") + + var highlightedLines []string + if settings.includeHighlighted { + highlightedLines = highlight(iacTypes.CreateFSKey(innerRange.GetFS()), innerRange.GetLocalFilename(), content, settings.theme) + if len(highlightedLines) < len(rawLines) { + highlightedLines = rawLines + } + } else { + highlightedLines = make([]string, len(rawLines)) + } + + if outerRange.GetEndLine()-1 >= len(rawLines) || innerRange.GetStartLine() == 0 { + return nil, fmt.Errorf("invalid line number") + } + + shrink := settings.allowTruncation && outerRange.LineCount() > (innerRange.LineCount()+10) + + if shrink { + + if outerRange.GetStartLine() < innerRange.GetStartLine() { + code.Lines = append( + code.Lines, + Line{ + Content: rawLines[outerRange.GetStartLine()-1], + Highlighted: highlightedLines[outerRange.GetStartLine()-1], + Number: outerRange.GetStartLine(), + }, + ) + if outerRange.GetStartLine()+1 < innerRange.GetStartLine() { + code.Lines = append( + code.Lines, + Line{ + Truncated: true, + Number: outerRange.GetStartLine() + 1, + }, + ) + } + } + + for lineNo := innerRange.GetStartLine(); lineNo <= innerRange.GetEndLine(); lineNo++ { + + if lineNo-1 >= len(rawLines) || lineNo-1 >= len(highlightedLines) { + break + } + + line := Line{ + Number: lineNo, + Content: strings.TrimSuffix(rawLines[lineNo-1], "\r"), + Highlighted: strings.TrimSuffix(highlightedLines[lineNo-1], "\r"), + IsCause: true, + } + + if hasAnnotation && lineNo == innerRange.GetStartLine() { + line.Annotation = r.Annotation() + } + + code.Lines = append(code.Lines, line) + } + + if outerRange.GetEndLine() > innerRange.GetEndLine() { + if outerRange.GetEndLine() > innerRange.GetEndLine()+1 { + code.Lines = append( + code.Lines, + Line{ + Truncated: true, + Number: outerRange.GetEndLine() - 1, + }, + ) + } + code.Lines = append( + code.Lines, + Line{ + Content: rawLines[outerRange.GetEndLine()-1], + Highlighted: highlightedLines[outerRange.GetEndLine()-1], + Number: outerRange.GetEndLine(), + }, + ) + + } + + } else { + for lineNo := outerRange.GetStartLine(); lineNo <= outerRange.GetEndLine(); lineNo++ { + + line := Line{ + Number: lineNo, + Content: strings.TrimSuffix(rawLines[lineNo-1], "\r"), + Highlighted: strings.TrimSuffix(highlightedLines[lineNo-1], "\r"), + IsCause: lineNo >= innerRange.GetStartLine() && lineNo <= innerRange.GetEndLine(), + } + + if hasAnnotation && lineNo == innerRange.GetStartLine() { + line.Annotation = r.Annotation() + } + + code.Lines = append(code.Lines, line) + } + } + + if settings.allowTruncation && len(code.Lines) > settings.maxLines && settings.maxLines > 0 { + previouslyTruncated := settings.maxLines-1 > 0 && code.Lines[settings.maxLines-2].Truncated + if settings.maxLines-1 > 0 && code.Lines[settings.maxLines-1].LastCause { + code.Lines[settings.maxLines-2].LastCause = true + } + code.Lines[settings.maxLines-1] = Line{ + Truncated: true, + Number: code.Lines[settings.maxLines-1].Number, + } + if previouslyTruncated { + code.Lines = code.Lines[:settings.maxLines-1] + } else { + code.Lines = code.Lines[:settings.maxLines] + } + } + + var first, last bool + for i, line := range code.Lines { + if line.IsCause && !first { + code.Lines[i].FirstCause = true + first = true + continue + } + if first && !line.IsCause && i > 0 { + code.Lines[i-1].LastCause = true + last = true + break + } + } + if !last && len(code.Lines) > 0 { + code.Lines[len(code.Lines)-1].LastCause = true + } + + return &code, nil +} diff --git a/pkg/iac/scan/code_test.go b/pkg/iac/scan/code_test.go new file mode 100644 index 000000000000..c3ffe3725ef1 --- /dev/null +++ b/pkg/iac/scan/code_test.go @@ -0,0 +1,264 @@ +package scan + +import ( + "os" + "strings" + "testing" + + "github.com/liamg/memoryfs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestResult_GetCode(t *testing.T) { + + tests := []struct { + name string + source string + filename string + start int + end int + outerStart int + outerEnd int + expected []Line + options []CodeOption + wantErr bool + annotation string + }{ + { + name: "basic w/ defaults", + source: `1 +2 +3 +4`, + filename: "test.txt", + start: 2, + end: 3, + expected: []Line{ + { + Number: 2, + Content: "2", + IsCause: true, + Highlighted: "2", + FirstCause: true, + LastCause: false, + }, + { + Number: 3, + Content: "3", + IsCause: true, + Highlighted: "3", + FirstCause: false, + LastCause: true, + }, + }, + }, + { + name: "nested ranges", + source: `resource "aws_s3_bucket" "something" { + bucket = "something" +}`, + filename: "main.tf", + start: 2, + end: 2, + outerStart: 1, + outerEnd: 3, + options: []CodeOption{OptionCodeWithHighlighted(false)}, + expected: []Line{ + { + Number: 1, + Content: `resource "aws_s3_bucket" "something" {`, + }, + { + Number: 2, + Content: ` bucket = "something"`, + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + Content: "}", + }, + }, + }, + { + name: "bad filename", + source: `1 +2 +3 +4`, + filename: "", + start: 2, + end: 3, + wantErr: true, + }, + { + name: "no line numbers", + source: `1 +2 +3 +4`, + filename: "test.txt", + start: 0, + end: 0, + wantErr: true, + }, + { + name: "negative line numbers", + source: `1 +2 +3 +4`, + filename: "test.txt", + start: -2, + end: -1, + wantErr: true, + }, + { + name: "invalid line numbers", + source: `1 +2 +3 +4`, + filename: "test.txt", + start: 5, + end: 6, + wantErr: true, + }, + { + name: "syntax highlighting", + source: `FROM ubuntu`, + filename: "Dockerfile", + start: 1, + end: 1, + expected: []Line{ + { + Number: 1, + Content: "FROM ubuntu", + IsCause: true, + Highlighted: "\x1b[38;5;64mFROM\x1b[0m\x1b[38;5;37m ubuntu\x1b[0m", + FirstCause: true, + LastCause: true, + }, + }, + }, + { + name: "truncation", + source: strings.Repeat("If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.\n", 100), + filename: "longfile.txt", + start: 1, + end: 100, + expected: []Line{ + { + Number: 1, + Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + IsCause: true, + Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + FirstCause: true, + LastCause: false, + }, + { + Number: 2, + Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + IsCause: true, + Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + FirstCause: false, + LastCause: false, + }, + { + Number: 3, + Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + IsCause: true, + Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + FirstCause: false, + LastCause: false, + }, + { + Number: 4, + Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + IsCause: true, + Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + FirstCause: false, + LastCause: false, + }, + { + Number: 5, + Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + IsCause: true, + Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + FirstCause: false, + LastCause: false, + }, + { + Number: 6, + Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + IsCause: true, + Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + FirstCause: false, + LastCause: false, + }, + { + Number: 7, + Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + IsCause: true, + Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + FirstCause: false, + LastCause: false, + }, + { + Number: 8, + Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + IsCause: true, + Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + FirstCause: false, + LastCause: false, + }, + { + Number: 9, + Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + IsCause: true, + Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + FirstCause: false, + LastCause: true, + }, + { + Number: 10, + Truncated: true, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + system := memoryfs.New() + require.NoError(t, system.WriteFile(test.filename, []byte(test.source), os.ModePerm)) + meta := iacTypes.NewMetadata( + iacTypes.NewRange(test.filename, test.start, test.end, "", system), + "", + ) + if test.outerStart > 0 { + meta = meta.WithParent(iacTypes.NewMetadata( + iacTypes.NewRange(test.filename, test.outerStart, test.outerEnd, "", system), + "", + )) + } + result := &Result{ + annotation: test.annotation, + metadata: meta, + fsPath: test.filename, + } + code, err := result.GetCode(test.options...) + if test.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, test.expected, code.Lines) + }) + } + +} diff --git a/pkg/iac/scan/flat.go b/pkg/iac/scan/flat.go new file mode 100755 index 000000000000..c640b5fc14ac --- /dev/null +++ b/pkg/iac/scan/flat.go @@ -0,0 +1,72 @@ +package scan + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/severity" +) + +type FlatResult struct { + RuleID string `json:"rule_id"` + LongID string `json:"long_id"` + RuleSummary string `json:"rule_description"` + RuleProvider providers.Provider `json:"rule_provider"` + RuleService string `json:"rule_service"` + Impact string `json:"impact"` + Resolution string `json:"resolution"` + Links []string `json:"links"` + Description string `json:"description"` + RangeAnnotation string `json:"-"` + Severity severity.Severity `json:"severity"` + Warning bool `json:"warning"` + Status Status `json:"status"` + Resource string `json:"resource"` + Occurrences []Occurrence `json:"occurrences,omitempty"` + Location FlatRange `json:"location"` +} + +type FlatRange struct { + Filename string `json:"filename"` + StartLine int `json:"start_line"` + EndLine int `json:"end_line"` +} + +func (r Results) Flatten() []FlatResult { + var results []FlatResult + for _, original := range r { + results = append(results, original.Flatten()) + } + return results +} + +func (r *Result) Flatten() FlatResult { + rng := r.metadata.Range() + + resMetadata := r.metadata + + for resMetadata.Parent() != nil { + resMetadata = *resMetadata.Parent() + } + + return FlatResult{ + RuleID: r.rule.AVDID, + LongID: r.Rule().LongID(), + RuleSummary: r.rule.Summary, + RuleProvider: r.rule.Provider, + RuleService: r.rule.Service, + Impact: r.rule.Impact, + Resolution: r.rule.Resolution, + Links: r.rule.Links, + Description: r.Description(), + RangeAnnotation: r.Annotation(), + Severity: r.rule.Severity, + Status: r.status, + Resource: resMetadata.Reference(), + Occurrences: r.Occurrences(), + Warning: r.IsWarning(), + Location: FlatRange{ + Filename: rng.GetFilename(), + StartLine: rng.GetStartLine(), + EndLine: rng.GetEndLine(), + }, + } +} diff --git a/pkg/iac/scan/highlighting.go b/pkg/iac/scan/highlighting.go new file mode 100644 index 000000000000..12bd3fe086cf --- /dev/null +++ b/pkg/iac/scan/highlighting.go @@ -0,0 +1,123 @@ +package scan + +import ( + "bytes" + "fmt" + "strings" + "sync" + + "github.com/alecthomas/chroma" + "github.com/alecthomas/chroma/formatters" + "github.com/alecthomas/chroma/lexers" + "github.com/alecthomas/chroma/styles" +) + +type cache struct { + sync.RWMutex + data map[string][]string +} + +func (c *cache) Get(key string) ([]string, bool) { + c.RLock() + defer c.RUnlock() + data, ok := c.data[key] + return data, ok +} + +func (c *cache) Set(key string, data []string) { + c.Lock() + defer c.Unlock() + c.data[key] = data +} + +var globalCache = &cache{ + data: make(map[string][]string), +} + +func highlight(fsKey, filename string, input []byte, theme string) []string { + + key := fmt.Sprintf("%s|%s", fsKey, filename) + if lines, ok := globalCache.Get(key); ok { + return lines + } + + lexer := lexers.Match(filename) + if lexer == nil { + lexer = lexers.Fallback + } + lexer = chroma.Coalesce(lexer) + + style := styles.Get(theme) + if style == nil { + style = styles.Fallback + } + formatter := formatters.Get("terminal256") + if formatter == nil { + formatter = formatters.Fallback + } + + // replace windows line endings + input = bytes.ReplaceAll(input, []byte{0x0d}, []byte{}) + iterator, err := lexer.Tokenise(nil, string(input)) + if err != nil { + return nil + } + + buffer := bytes.NewBuffer([]byte{}) + if err := formatter.Format(buffer, style, iterator); err != nil { + return nil + } + + raw := shiftANSIOverLineEndings(buffer.Bytes()) + lines := strings.Split(string(raw), "\n") + globalCache.Set(key, lines) + return lines +} + +func shiftANSIOverLineEndings(input []byte) []byte { + var output []byte + prev := byte(0) + inCSI := false + csiShouldCarry := false + var csi []byte + var skipOutput bool + for _, r := range input { + skipOutput = false + if !inCSI { + switch { + case r == '\n': + if csiShouldCarry && len(csi) > 0 { + skipOutput = true + output = append(output, '\n') + output = append(output, csi...) + csi = nil + csiShouldCarry = false + } + case r == '[' && prev == 0x1b: + inCSI = true + csi = append(csi, 0x1b, '[') + output = output[:len(output)-1] + skipOutput = true + default: + csiShouldCarry = false + if len(csi) > 0 { + output = append(output, csi...) + csi = nil + } + } + } else { + csi = append(csi, r) + skipOutput = true + if r >= 0x40 && r <= 0x7E { + csiShouldCarry = true + inCSI = false + } + } + if !skipOutput { + output = append(output, r) + } + prev = r + } + + return append(output, csi...) +} diff --git a/pkg/iac/scan/result.go b/pkg/iac/scan/result.go new file mode 100644 index 000000000000..d9924c6aaeef --- /dev/null +++ b/pkg/iac/scan/result.go @@ -0,0 +1,382 @@ +package scan + +import ( + "fmt" + "io/fs" + "path/filepath" + "reflect" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/ignore" + "github.com/aquasecurity/trivy/pkg/iac/severity" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Status uint8 + +const ( + StatusFailed Status = iota + StatusPassed + StatusIgnored +) + +type Result struct { + rule Rule + description string + annotation string + status Status + metadata iacTypes.Metadata + severityOverride *severity.Severity + regoNamespace string + regoRule string + warning bool + traces []string + fsPath string +} + +func (r Result) RegoNamespace() string { + return r.regoNamespace +} + +func (r Result) RegoRule() string { + return r.regoRule +} + +func (r Result) Severity() severity.Severity { + if r.severityOverride != nil { + return *r.severityOverride + } + return r.Rule().Severity +} + +func (r *Result) IsWarning() bool { + return r.warning +} + +func (r *Result) OverrideSeverity(s severity.Severity) { + r.severityOverride = &s +} + +func (r *Result) OverrideDescription(description string) { + r.description = description +} + +func (r *Result) OverrideMetadata(metadata iacTypes.Metadata) { + r.metadata = metadata +} + +func (r *Result) OverrideStatus(status Status) { + r.status = status +} + +func (r *Result) OverrideAnnotation(annotation string) { + r.annotation = annotation +} + +func (r *Result) SetRule(ru Rule) { + r.rule = ru +} + +func (r Result) Status() Status { + return r.status +} + +func (r Result) Rule() Rule { + return r.rule +} + +func (r Result) Description() string { + return r.description +} + +func (r Result) Annotation() string { + return r.annotation +} + +func (r Result) Metadata() iacTypes.Metadata { + return r.metadata +} + +func (r Result) Range() iacTypes.Range { + return r.metadata.Range() +} + +func (r Result) Traces() []string { + return r.traces +} + +func (r *Result) AbsolutePath(fsRoot string, metadata iacTypes.Metadata) string { + if strings.HasSuffix(fsRoot, ":") { + fsRoot += "/" + } + + if metadata.IsUnmanaged() { + return "" + } + rng := metadata.Range() + if rng.GetSourcePrefix() != "" && !strings.HasPrefix(rng.GetSourcePrefix(), ".") { + return rng.GetFilename() + } + return filepath.Join(fsRoot, rng.GetLocalFilename()) +} + +func (r *Result) RelativePathTo(fsRoot, to string, metadata iacTypes.Metadata) string { + + absolute := r.AbsolutePath(fsRoot, metadata) + + if strings.HasSuffix(fsRoot, ":") { + fsRoot += "/" + } + + if metadata.IsUnmanaged() { + return absolute + } + rng := metadata.Range() + if rng.GetSourcePrefix() != "" && !strings.HasPrefix(rng.GetSourcePrefix(), ".") { + return absolute + } + if !strings.HasPrefix(rng.GetLocalFilename(), strings.TrimSuffix(fsRoot, "/")) { + return absolute + } + relative, err := filepath.Rel(to, rng.GetLocalFilename()) + if err != nil { + return absolute + } + return relative +} + +type Results []Result + +type MetadataProvider interface { + GetMetadata() iacTypes.Metadata + GetRawValue() interface{} +} + +func (r *Results) GetPassed() Results { + return r.filterStatus(StatusPassed) +} + +func (r *Results) GetIgnored() Results { + return r.filterStatus(StatusIgnored) +} + +func (r *Results) GetFailed() Results { + return r.filterStatus(StatusFailed) +} + +func (r *Results) filterStatus(status Status) Results { + var filtered Results + if r == nil { + return filtered + } + for _, res := range *r { + if res.Status() == status { + filtered = append(filtered, res) + } + } + return filtered +} + +func (r *Results) Add(description string, source interface{}) { + result := Result{ + description: description, + } + result.metadata = getMetadataFromSource(source) + if result.metadata.IsExplicit() { + result.annotation = getAnnotation(source) + } + rnge := result.metadata.Range() + result.fsPath = rnge.GetLocalFilename() + *r = append(*r, result) +} + +func (r *Results) AddRego(description, namespace, rule string, traces []string, source MetadataProvider) { + result := Result{ + description: description, + regoNamespace: namespace, + regoRule: rule, + warning: rule == "warn" || strings.HasPrefix(rule, "warn_"), + traces: traces, + } + result.metadata = getMetadataFromSource(source) + if result.metadata.IsExplicit() { + result.annotation = getAnnotation(source) + } + rnge := result.metadata.Range() + result.fsPath = rnge.GetLocalFilename() + *r = append(*r, result) +} + +func (r *Results) AddPassed(source interface{}, descriptions ...string) { + res := Result{ + description: strings.Join(descriptions, " "), + status: StatusPassed, + } + res.metadata = getMetadataFromSource(source) + rnge := res.metadata.Range() + res.fsPath = rnge.GetLocalFilename() + *r = append(*r, res) +} + +func getMetadataFromSource(source interface{}) iacTypes.Metadata { + if provider, ok := source.(MetadataProvider); ok { + return provider.GetMetadata() + } + + metaValue := reflect.ValueOf(source) + if metaValue.Kind() == reflect.Ptr { + metaValue = metaValue.Elem() + } + metaVal := metaValue.FieldByName("Metadata") + return metaVal.Interface().(iacTypes.Metadata) +} + +func getAnnotation(source interface{}) string { + if provider, ok := source.(MetadataProvider); ok { + return rawToString(provider.GetRawValue()) + } + return "" +} + +func (r *Results) AddPassedRego(namespace, rule string, traces []string, source interface{}) { + res := Result{ + status: StatusPassed, + regoNamespace: namespace, + regoRule: rule, + traces: traces, + } + res.metadata = getMetadataFromSource(source) + rnge := res.metadata.Range() + res.fsPath = rnge.GetLocalFilename() + *r = append(*r, res) +} + +func (r *Results) AddIgnored(source interface{}, descriptions ...string) { + res := Result{ + description: strings.Join(descriptions, " "), + status: StatusIgnored, + } + res.metadata = getMetadataFromSource(source) + rnge := res.metadata.Range() + res.fsPath = rnge.GetLocalFilename() + *r = append(*r, res) +} + +func (r *Results) Ignore(ignoreRules ignore.Rules, ignores map[string]ignore.Ignorer) { + for i, result := range *r { + allIDs := []string{ + result.Rule().LongID(), + result.Rule().AVDID, + strings.ToLower(result.Rule().AVDID), + result.Rule().ShortCode, + } + allIDs = append(allIDs, result.Rule().Aliases...) + + if ignoreRules.Ignore(result.Metadata(), allIDs, ignores) { + (*r)[i].OverrideStatus(StatusIgnored) + } + } +} + +func (r *Results) SetRule(rule Rule) { + for i := range *r { + (*r)[i].rule = rule + } +} + +func (r *Results) SetSourceAndFilesystem(source string, f fs.FS, logicalSource bool) { + for i := range *r { + m := (*r)[i].Metadata() + if m.IsUnmanaged() { + continue + } + rng := m.Range() + + newrng := iacTypes.NewRange(rng.GetLocalFilename(), rng.GetStartLine(), rng.GetEndLine(), source, f) + if logicalSource { + newrng = iacTypes.NewRangeWithLogicalSource(rng.GetLocalFilename(), rng.GetStartLine(), rng.GetEndLine(), + source, f) + } + parent := m.Parent() + switch { + case m.IsExplicit(): + m = iacTypes.NewExplicitMetadata(newrng, m.Reference()) + default: + m = iacTypes.NewMetadata(newrng, m.Reference()) + } + if parent != nil { + m.SetParentPtr(parent) + } + (*r)[i].OverrideMetadata(m) + } +} + +func rawToString(raw interface{}) string { + if raw == nil { + return "" + } + switch t := raw.(type) { + case int: + return fmt.Sprintf("%d", t) + case bool: + return fmt.Sprintf("%t", t) + case float64: + return fmt.Sprintf("%f", t) + case string: + return fmt.Sprintf("%q", t) + case []string: + var items []string + for _, item := range t { + items = append(items, rawToString(item)) + } + return fmt.Sprintf("[%s]", strings.Join(items, ", ")) + case []int: + var items []string + for _, item := range t { + items = append(items, rawToString(item)) + } + return fmt.Sprintf("[%s]", strings.Join(items, ", ")) + case []float64: + var items []string + for _, item := range t { + items = append(items, rawToString(item)) + } + return fmt.Sprintf("[%s]", strings.Join(items, ", ")) + case []bool: + var items []string + for _, item := range t { + items = append(items, rawToString(item)) + } + return fmt.Sprintf("[%s]", strings.Join(items, ", ")) + default: + return "?" + } +} + +type Occurrence struct { + Resource string `json:"resource"` + Filename string `json:"filename"` + StartLine int `json:"start_line"` + EndLine int `json:"end_line"` +} + +func (r *Result) Occurrences() []Occurrence { + var occurrences []Occurrence + + mod := &r.metadata + + for { + mod = mod.Parent() + if mod == nil { + break + } + parentRange := mod.Range() + occurrences = append(occurrences, Occurrence{ + Resource: mod.Reference(), + Filename: parentRange.GetFilename(), + StartLine: parentRange.GetStartLine(), + EndLine: parentRange.GetEndLine(), + }) + } + return occurrences +} diff --git a/pkg/iac/scan/result_test.go b/pkg/iac/scan/result_test.go new file mode 100644 index 000000000000..d5b9a4a577f0 --- /dev/null +++ b/pkg/iac/scan/result_test.go @@ -0,0 +1,56 @@ +package scan_test + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" +) + +func Test_Occurrences(t *testing.T) { + tests := []struct { + name string + factory func() *scan.Result + expected []scan.Occurrence + }{ + { + name: "happy", + factory: func() *scan.Result { + r := scan.Result{} + causeResourceMeta := types.NewMetadata(types.NewRange( + "main.tf", 1, 13, "", nil, + ), "module.aws-security-groups[\"db1\"]") + + parentMeta := types.NewMetadata(types.NewRange( + "terraform-aws-modules/security-group/aws/main.tf", 191, 227, "", nil, + ), "aws_security_group_rule.ingress_with_cidr_blocks[0]").WithParent(causeResourceMeta) + + r.OverrideMetadata(types.NewMetadata(types.NewRange( + "terraform-aws-modules/security-group/aws/main.tf", 197, 204, "", nil, + ), "aws_security_group_rule.ingress_with_cidr_blocks").WithParent(parentMeta)) + return &r + }, + expected: []scan.Occurrence{ + { + Resource: "aws_security_group_rule.ingress_with_cidr_blocks[0]", + Filename: "terraform-aws-modules/security-group/aws/main.tf", + StartLine: 191, + EndLine: 227, + }, + { + Resource: "module.aws-security-groups[\"db1\"]", + Filename: "main.tf", + StartLine: 1, + EndLine: 13, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.factory().Occurrences()) + }) + } +} diff --git a/pkg/iac/scan/rule.go b/pkg/iac/scan/rule.go new file mode 100755 index 000000000000..a1a3ada18e99 --- /dev/null +++ b/pkg/iac/scan/rule.go @@ -0,0 +1,168 @@ +package scan + +import ( + "fmt" + "regexp" + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/severity" + "github.com/aquasecurity/trivy/pkg/iac/state" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +type CheckFunc func(s *state.State) (results Results) + +type EngineMetadata struct { + GoodExamples []string `json:"good_examples,omitempty"` + BadExamples []string `json:"bad_examples,omitempty"` + RemediationMarkdown string `json:"remediation_markdown,omitempty"` + Links []string `json:"links,omitempty"` +} + +type CustomChecks struct { + Terraform *TerraformCustomCheck +} + +type TerraformCustomCheck struct { + RequiredTypes []string + RequiredLabels []string + RequiredSources []string + Check func(*terraform.Block, *terraform.Module) Results +} + +type Rule struct { + AVDID string `json:"avd_id"` + Aliases []string `json:"aliases"` + ShortCode string `json:"short_code"` + Summary string `json:"summary"` + Explanation string `json:"explanation"` + Impact string `json:"impact"` + Resolution string `json:"resolution"` + Provider providers.Provider `json:"provider"` + Service string `json:"service"` + Links []string `json:"links"` + Severity severity.Severity `json:"severity"` + Terraform *EngineMetadata `json:"terraform,omitempty"` + CloudFormation *EngineMetadata `json:"cloud_formation,omitempty"` + CustomChecks CustomChecks `json:"-"` + RegoPackage string `json:"-"` + Frameworks map[framework.Framework][]string `json:"frameworks"` + Check CheckFunc `json:"-"` +} + +func (r Rule) HasID(id string) bool { + if r.AVDID == id || r.LongID() == id { + return true + } + for _, alias := range r.Aliases { + if alias == id { + return true + } + } + return false +} + +func (r Rule) LongID() string { + return strings.ToLower(fmt.Sprintf("%s-%s-%s", r.Provider, r.Service, r.ShortCode)) +} + +func (r Rule) ServiceDisplayName() string { + return nicify(r.Service) +} + +func (r Rule) ShortCodeDisplayName() string { + return nicify(r.ShortCode) +} + +func (r Rule) CanCheck() bool { + return r.Check != nil +} + +func (r Rule) Evaluate(s *state.State) Results { + if !r.CanCheck() { + return nil + } + results := r.Check(s) + for i := range results { + results[i].SetRule(r) + } + return results +} + +var acronyms = []string{ + "acl", + "alb", + "api", + "arn", + "aws", + "cidr", + "db", + "dns", + "ebs", + "ec2", + "ecr", + "ecs", + "efs", + "eks", + "elb", + "gke", + "http", + "http2", + "https", + "iam", + "im", + "imds", + "ip", + "ips", + "kms", + "lb", + "md5", + "mfa", + "mq", + "msk", + "rbac", + "rdp", + "rds", + "rsa", + "sam", + "sgr", + "sha1", + "sha256", + "sns", + "sql", + "sqs", + "ssh", + "ssm", + "tls", + "ubla", + "vm", + "vpc", + "vtpm", + "waf", +} + +var specials = map[string]string{ + "dynamodb": "DynamoDB", + "documentdb": "DocumentDB", + "mysql": "MySQL", + "postgresql": "PostgreSQL", + "acls": "ACLs", + "ips": "IPs", + "bigquery": "BigQuery", +} + +func nicify(input string) string { + input = strings.ToLower(input) + for replace, with := range specials { + input = regexp.MustCompile(fmt.Sprintf("\\b%s\\b", replace)).ReplaceAllString(input, with) + } + for _, acronym := range acronyms { + input = regexp.MustCompile(fmt.Sprintf("\\b%s\\b", acronym)).ReplaceAllString(input, strings.ToUpper(acronym)) + } + return cases.Title(language.English).String(strings.ReplaceAll(input, "-", " ")) +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go new file mode 100644 index 000000000000..12bc71eae899 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go @@ -0,0 +1,71 @@ +package armjson + +import ( + "encoding/json" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/require" +) + +func BenchmarkUnmarshal_JFather(b *testing.B) { + target := make(map[string]interface{}) + input := []byte(`{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +}`) + + for n := 0; n < b.N; n++ { + metadata := types.NewTestMetadata() + require.NoError(b, Unmarshal(input, &target, &metadata)) + } +} + +func BenchmarkUnmarshal_Traditional(b *testing.B) { + target := make(map[string]interface{}) + input := []byte(`{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +}`) + + for n := 0; n < b.N; n++ { + require.NoError(b, json.Unmarshal(input, &target)) + } +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode.go new file mode 100644 index 000000000000..1f228065b9c5 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode.go @@ -0,0 +1,66 @@ +package armjson + +import ( + "fmt" + "reflect" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func (n *node) Decode(target interface{}) error { + v := reflect.ValueOf(target) + return n.decodeToValue(v) +} + +func (n *node) Metadata() types.Metadata { + return *n.metadata +} + +var unmarshaller = reflect.TypeOf((*Unmarshaller)(nil)).Elem() +var receiver = reflect.TypeOf((*MetadataReceiver)(nil)).Elem() + +func (n *node) decodeToValue(v reflect.Value) error { + + if v.Type().Implements(receiver) { + rec := v + defer func() { + rec.MethodByName("SetMetadata").Call([]reflect.Value{reflect.ValueOf(n.metadata)}) + }() + } + if v.Type().Implements(unmarshaller) { + returns := v.MethodByName("UnmarshalJSONWithMetadata").Call([]reflect.Value{reflect.ValueOf(n)}) + if err := returns[0].Interface(); err != nil { + return err.(error) + } + return nil + } + + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + + if !v.CanSet() { + return fmt.Errorf("target is not settable") + } + + switch n.kind { + case KindObject: + return n.decodeObject(v) + case KindArray: + return n.decodeArray(v) + case KindString: + return n.decodeString(v) + case KindNumber: + return n.decodeNumber(v) + case KindBoolean: + return n.decodeBoolean(v) + case KindNull: + return n.decodeNull(v) + case KindComment: + return n.decodeString(v) + case KindUnknown: + return fmt.Errorf("cannot decode unknown kind") + default: + return fmt.Errorf("decoding of kind 0x%x is not supported", n.kind) + } +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode_array.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode_array.go new file mode 100644 index 000000000000..75faada57252 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode_array.go @@ -0,0 +1,51 @@ +package armjson + +import ( + "fmt" + "reflect" +) + +func (n *node) decodeArray(v reflect.Value) error { + + length := len(n.content) + + var original reflect.Value + + switch v.Kind() { + case reflect.Array: + if v.Len() != length { + return fmt.Errorf("invalid length") + } + case reflect.Slice: + v.Set(reflect.MakeSlice(v.Type(), length, length)) + case reflect.Interface: + original = v + slice := reflect.ValueOf(make([]interface{}, length)) + v = reflect.New(slice.Type()).Elem() + v.Set(slice) + default: + return fmt.Errorf("invalid target type") + } + + elementType := v.Type().Elem() + for i, nodeElement := range n.content { + node := nodeElement.(*node) + targetElement := reflect.New(elementType).Elem() + addressable := targetElement + if targetElement.Kind() == reflect.Ptr { + targetElement.Set(reflect.New(elementType.Elem())) + } else { + addressable = targetElement.Addr() + } + if err := node.decodeToValue(addressable); err != nil { + return err + } + v.Index(i).Set(targetElement) + } + + if original.IsValid() { + original.Set(v) + } + + return nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode_boolean.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode_boolean.go new file mode 100644 index 000000000000..dbdef3a3253d --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode_boolean.go @@ -0,0 +1,18 @@ +package armjson + +import ( + "fmt" + "reflect" +) + +func (n *node) decodeBoolean(v reflect.Value) error { + switch v.Kind() { + case reflect.Bool: + v.SetBool(n.raw.(bool)) + case reflect.Interface: + v.Set(reflect.ValueOf(n.raw)) + default: + return fmt.Errorf("cannot decode boolean value to %s target", v.Kind()) + } + return nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode_meta_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode_meta_test.go new file mode 100644 index 000000000000..9924288497fe --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode_meta_test.go @@ -0,0 +1,40 @@ +package armjson + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type TestParent struct { + Child *TestChild `json:"child"` +} + +type TestChild struct { + Name string + Line int + Column int +} + +func (t *TestChild) UnmarshalJSONWithMetadata(node Node) error { + t.Line = node.Range().Start.Line + t.Column = node.Range().Start.Column + return node.Decode(&t.Name) +} + +func Test_DecodeWithMetadata(t *testing.T) { + example := []byte(` +{ + "child": "secret" +} +`) + var parent TestParent + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &parent, &metadata)) + assert.Equal(t, 3, parent.Child.Line) + assert.Equal(t, 12, parent.Child.Column) + assert.Equal(t, "secret", parent.Child.Name) +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode_null.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode_null.go new file mode 100644 index 000000000000..2cc86b3c1bb7 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode_null.go @@ -0,0 +1,10 @@ +package armjson + +import ( + "reflect" +) + +func (n *node) decodeNull(v reflect.Value) error { + v.Set(reflect.Zero(v.Type())) + return nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode_number.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode_number.go new file mode 100644 index 000000000000..653f6f1fbe06 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode_number.go @@ -0,0 +1,46 @@ +package armjson + +import ( + "fmt" + "reflect" +) + +func (n *node) decodeNumber(v reflect.Value) error { + + switch v.Kind() { + case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: + if i64, ok := n.raw.(int64); ok { + v.SetInt(i64) + return nil + } + if f64, ok := n.raw.(float64); ok { + v.SetInt(int64(f64)) + return nil + } + case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint: + if i64, ok := n.raw.(int64); ok { + v.SetUint(uint64(i64)) + return nil + } + if f64, ok := n.raw.(float64); ok { + v.SetUint(uint64(f64)) + return nil + } + case reflect.Float32, reflect.Float64: + if i64, ok := n.raw.(int64); ok { + v.SetFloat(float64(i64)) + return nil + } + if f64, ok := n.raw.(float64); ok { + v.SetFloat(f64) + return nil + } + case reflect.Interface: + v.Set(reflect.ValueOf(n.raw)) + return nil + default: + return fmt.Errorf("cannot decode number value to %s target", v.Kind()) + } + + return fmt.Errorf("internal value is not numeric") +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode_object.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode_object.go new file mode 100644 index 000000000000..57b611065242 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode_object.go @@ -0,0 +1,122 @@ +package armjson + +import ( + "fmt" + "reflect" + "strings" +) + +func (n *node) decodeObject(v reflect.Value) error { + switch v.Kind() { + case reflect.Struct: + return n.decodeObjectToStruct(v) + case reflect.Map: + return n.decodeObjectToMap(v) + case reflect.Interface: + target := reflect.New(reflect.TypeOf(make(map[string]interface{}, len(n.Content())))).Elem() + if err := n.decodeObjectToMap(target); err != nil { + return err + } + v.Set(target) + return nil + default: + return fmt.Errorf("cannot set object value to target of type %s", v.Kind()) + } +} + +func (n *node) decodeObjectToMap(v reflect.Value) error { + properties, err := n.objectAsMap() + if err != nil { + return err + } + + newMap := reflect.MakeMap(v.Type()) + valueType := v.Type().Elem() + + for key, value := range properties { + target := reflect.New(valueType).Elem() + addressable := target + if target.Kind() == reflect.Ptr { + target.Set(reflect.New(valueType.Elem())) + } else { + addressable = target.Addr() + } + if err := value.(*node).decodeToValue(addressable); err != nil { + return err + } + newMap.SetMapIndex(reflect.ValueOf(key), target) + } + + v.Set(newMap) + return nil + +} + +func (n *node) objectAsMap() (map[string]Node, error) { + if n.kind != KindObject { + return nil, fmt.Errorf("not an object") + } + properties := make(map[string]Node) + contents := n.content + for i := 0; i < len(contents); i += 2 { + key := contents[i] + if key.Kind() != KindString { + return nil, fmt.Errorf("invalid object key - please report this bug") + } + keyStr := key.(*node).raw.(string) + + if i+1 >= len(contents) { + return nil, fmt.Errorf("missing object value - please report this bug") + } + properties[keyStr] = contents[i+1] + } + return properties, nil +} + +func (n *node) decodeObjectToStruct(v reflect.Value) error { + + temp := reflect.New(v.Type()).Elem() + v.Set(temp) + + properties, err := n.objectAsMap() + if err != nil { + return err + } + + t := v.Type() + for i := 0; i < t.NumField(); i++ { + fv := t.Field(i) + tags := strings.Split(fv.Tag.Get("json"), ",") + var tagName string + for _, tag := range tags { + if tag != "omitempty" && tag != "-" { + tagName = tag + } + } + if tagName == "" { + tagName = fv.Name + } + + value, ok := properties[tagName] + if !ok { + // TODO: should we zero this value? + continue + } + + subject := v.Field(i) + + // if fields are nil pointers, initialize them with values of the correct type + if subject.Kind() == reflect.Ptr { + if subject.IsNil() { + subject.Set(reflect.New(subject.Type().Elem())) + } + } else { + subject = subject.Addr() + } + + if err := value.(*node).decodeToValue(subject); err != nil { + return err + } + } + return nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode_string.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode_string.go new file mode 100644 index 000000000000..c8f734b57024 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode_string.go @@ -0,0 +1,19 @@ +package armjson + +import ( + "fmt" + "reflect" +) + +func (n *node) decodeString(v reflect.Value) error { + + switch v.Kind() { + case reflect.String: + v.SetString(n.raw.(string)) + case reflect.Interface: + v.Set(reflect.ValueOf(n.raw)) + default: + return fmt.Errorf("cannot decode string value to non-string target: %s", v.Kind()) + } + return nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/kind.go b/pkg/iac/scanners/azure/arm/parser/armjson/kind.go new file mode 100644 index 000000000000..82712cc89225 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/kind.go @@ -0,0 +1,14 @@ +package armjson + +type Kind uint8 + +const ( + KindUnknown Kind = iota + KindNull + KindNumber + KindString + KindBoolean + KindArray + KindObject + KindComment +) diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/node.go b/pkg/iac/scanners/azure/arm/parser/armjson/node.go new file mode 100644 index 000000000000..af8f9188ebd8 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/node.go @@ -0,0 +1,59 @@ +package armjson + +import "github.com/aquasecurity/trivy/pkg/iac/types" + +type Node interface { + Comments() []Node + Range() Range + Decode(target interface{}) error + Kind() Kind + Content() []Node + Metadata() types.Metadata +} + +type Range struct { + Start Position + End Position +} + +type Position struct { + Line int + Column int +} + +type node struct { + raw interface{} + start Position + end Position + kind Kind + content []Node + comments []Node + metadata *types.Metadata + ref string +} + +func (n *node) Range() Range { + return Range{ + Start: n.start, + End: Position{ + Column: n.end.Column - 1, + Line: n.end.Line, + }, + } +} + +func (n *node) Comments() []Node { + return n.comments +} + +func (n *node) End() Position { + return n.end +} + +func (n *node) Kind() Kind { + return n.kind +} + +func (n *node) Content() []Node { + return n.content +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse.go new file mode 100644 index 000000000000..8248ec1a1723 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse.go @@ -0,0 +1,150 @@ +package armjson + +import ( + "fmt" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type parser struct { + position Position + size int + peeker *PeekReader +} + +func newParser(p *PeekReader, pos Position) *parser { + return &parser{ + position: pos, + peeker: p, + } +} + +func (p *parser) parse(rootMetadata *types.Metadata) (Node, error) { + root, err := p.parseElement(rootMetadata) + if err != nil { + return nil, err + } + root.(*node).updateMetadata("") + return root, nil +} + +func (p *parser) parseElement(parentMetadata *types.Metadata) (Node, error) { + if err := p.parseWhitespace(); err != nil { + return nil, err + } + n, err := p.parseValue(parentMetadata) + if err != nil { + return nil, err + } + if err := p.parseWhitespace(); err != nil { + return nil, err + } + return n, nil +} + +func (p *parser) parseValue(parentMetadata *types.Metadata) (Node, error) { + c, err := p.peeker.Peek() + if err != nil { + return nil, err + } + + switch c { + case '/': + return p.parseComment(parentMetadata) + case '"': + return p.parseString(parentMetadata) + case '{': + return p.parseObject(parentMetadata) + case '[': + return p.parseArray(parentMetadata) + case 'n': + return p.parseNull(parentMetadata) + case 't', 'f': + return p.parseBoolean(parentMetadata) + default: + if c == '-' || (c >= '0' && c <= '9') { + return p.parseNumber(parentMetadata) + } + return nil, fmt.Errorf("unexpected character '%c'", c) + } +} + +func (p *parser) next() (rune, error) { + b, err := p.peeker.Next() + if err != nil { + return 0, err + } + p.position.Column++ + p.size++ + return b, nil +} + +func (p *parser) undo() error { + if err := p.peeker.Undo(); err != nil { + return err + } + p.position.Column-- + p.size-- + return nil +} + +func (p *parser) makeError(format string, args ...interface{}) error { + return fmt.Errorf( + "error at line %d, column %d: %s", + p.position.Line, + p.position.Column, + fmt.Sprintf(format, args...), + ) +} + +func (p *parser) newNode(k Kind, parentMetadata *types.Metadata) (*node, *types.Metadata) { + n := &node{ + start: p.position, + kind: k, + } + metadata := types.NewMetadata( + types.NewRange(parentMetadata.Range().GetFilename(), n.start.Line, n.end.Line, "", parentMetadata.Range().GetFS()), + n.ref, + ) + metadata.SetParentPtr(parentMetadata) + n.metadata = &metadata + return n, n.metadata +} + +func (n *node) updateMetadata(prefix string) { + + var full string + // nolint:gocritic + if strings.HasPrefix(n.ref, "[") { + full = prefix + n.ref + } else if prefix != "" { + full = prefix + "." + n.ref + } else { + full = n.ref + } + + n.metadata.SetRange(types.NewRange(n.metadata.Range().GetFilename(), + n.start.Line, + n.end.Line, + "", + n.metadata.Range().GetFS())) + + n.metadata.SetReference(full) + + for i := range n.content { + n.content[i].(*node).updateMetadata(full) + } +} + +func (p *parser) swallowIfEqual(r rune) bool { + c, err := p.peeker.Peek() + if err != nil { + return false + } + if c != r { + return false + } + _, _ = p.next() + return true +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array.go new file mode 100644 index 000000000000..8d3795ee792a --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array.go @@ -0,0 +1,54 @@ +package armjson + +import ( + "fmt" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func (p *parser) parseArray(parentMetadata *types.Metadata) (Node, error) { + n, metadata := p.newNode(KindArray, parentMetadata) + + c, err := p.next() + if err != nil { + return nil, err + } + + if c != '[' { + return nil, p.makeError("expecting object delimiter") + } + if err := p.parseWhitespace(); err != nil { + return nil, err + } + // we've hit the end of the object + if p.swallowIfEqual(']') { + n.end = p.position + return n, nil + } + + // for each element + for { + + if err := p.parseWhitespace(); err != nil { + return nil, err + } + + val, err := p.parseElement(metadata) + if err != nil { + return nil, err + } + val.(*node).ref = fmt.Sprintf("[%d]", len(n.content)) + + n.content = append(n.content, val) + + // we've hit the end of the array + if p.swallowIfEqual(']') { + n.end = p.position + return n, nil + } + + if !p.swallowIfEqual(',') { + return nil, p.makeError("unexpected character - expecting , or ]") + } + } +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go new file mode 100644 index 000000000000..382da0b0c45d --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go @@ -0,0 +1,46 @@ +package armjson + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Array_Empty(t *testing.T) { + example := []byte(`[]`) + target := []int{} + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &target, &metadata)) + assert.Len(t, target, 0) +} + +func Test_Array_ToSlice(t *testing.T) { + example := []byte(`[1, 2, 3]`) + target := []int{} + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &target, &metadata)) + assert.Len(t, target, 3) + assert.EqualValues(t, []int{1, 2, 3}, target) +} + +func Test_Array_ToArray(t *testing.T) { + example := []byte(`[3, 2, 1]`) + target := [3]int{6, 6, 6} + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &target, &metadata)) + assert.Len(t, target, 3) + assert.EqualValues(t, [3]int{3, 2, 1}, target) +} + +func Test_Array_ToInterface(t *testing.T) { + example := []byte(`{ "List": [1, 2, 3] }`) + target := struct { + List interface{} + }{} + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &target, &metadata)) + assert.Len(t, target.List, 3) +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean.go new file mode 100644 index 000000000000..30903ea85973 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean.go @@ -0,0 +1,40 @@ +package armjson + +import ( + "fmt" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var trueRunes = []rune("true") +var falseRunes = []rune("false") + +func (p *parser) parseBoolean(parentMetadata *types.Metadata) (Node, error) { + + n, _ := p.newNode(KindBoolean, parentMetadata) + + r, err := p.peeker.Peek() + if err != nil { + return nil, err + } + + if r == 't' { + for _, expected := range trueRunes { + if !p.swallowIfEqual(expected) { + return nil, fmt.Errorf("unexpected character in boolean value") + } + } + n.raw = true + n.end = p.position + return n, err + } + + for _, expected := range falseRunes { + if !p.swallowIfEqual(expected) { + return nil, fmt.Errorf("unexpected character in boolean value") + } + } + n.raw = false + n.end = p.position + return n, nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go new file mode 100644 index 000000000000..106d55f426f5 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go @@ -0,0 +1,54 @@ +package armjson + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Boolean_True(t *testing.T) { + example := []byte(`true`) + var output bool + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.True(t, output) +} + +func Test_Boolean_False(t *testing.T) { + example := []byte(`false`) + var output bool + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.False(t, output) +} + +func Test_Boolean_ToNonBoolPointer(t *testing.T) { + example := []byte(`false`) + var output string + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.Error(t, err) +} + +func Test_Bool_ToUninitialisedPointer(t *testing.T) { + example := []byte(`true`) + var str *string + metadata := types.NewTestMetadata() + err := Unmarshal(example, str, &metadata) + require.Error(t, err) + assert.Nil(t, str) +} + +func Test_Bool_ToInterface(t *testing.T) { + example := []byte(`true`) + var output interface{} + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.True(t, output.(bool)) +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_comment.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_comment.go new file mode 100644 index 000000000000..3408d330c2b3 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_comment.go @@ -0,0 +1,98 @@ +package armjson + +import ( + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func (p *parser) parseComment(parentMetadata *types.Metadata) (Node, error) { + + if err := p.parseWhitespace(); err != nil { + return nil, err + } + + _, err := p.next() + if err != nil { + return nil, err + } + + b, err := p.next() + if err != nil { + return nil, err + } + + switch b { + case '/': + return p.parseLineComment(parentMetadata) + case '*': + return p.parseBlockComment(parentMetadata) + default: + return nil, p.makeError("expecting comment delimiter") + } +} + +func (p *parser) parseLineComment(parentMetadata *types.Metadata) (Node, error) { + + n, _ := p.newNode(KindComment, parentMetadata) + + var sb strings.Builder + for { + c, err := p.next() + if err != nil { + return nil, err + } + if c == '\n' { + p.position.Column = 1 + p.position.Line++ + break + } + sb.WriteRune(c) + } + + n.raw = sb.String() + n.end = p.position + + if err := p.parseWhitespace(); err != nil { + return nil, err + } + return n, nil +} + +func (p *parser) parseBlockComment(parentMetadata *types.Metadata) (Node, error) { + + n, _ := p.newNode(KindComment, parentMetadata) + + var sb strings.Builder + + for { + c, err := p.next() + if err != nil { + return nil, err + } + if c == '*' { + c, err := p.peeker.Peek() + if err != nil { + return nil, err + } + if c == '/' { + break + } + sb.WriteRune('*') + } else { + if c == '\n' { + p.position.Column = 1 + p.position.Line++ + } + sb.WriteRune(c) + } + } + + n.raw = sb.String() + + if err := p.parseWhitespace(); err != nil { + return nil, err + } + + return n, nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go new file mode 100644 index 000000000000..3584ebc97fa1 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go @@ -0,0 +1,131 @@ +package armjson + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/require" +) + +func Test_Complex(t *testing.T) { + target := make(map[string]interface{}) + input := `{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +}` + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal([]byte(input), &target, &metadata)) +} + +type Resource struct { + Line int + inner resourceInner +} + +type resourceInner struct { + Type string `json:"Type" yaml:"Type"` + Properties map[string]*Property `json:"Properties" yaml:"Properties"` +} + +func (r *Resource) UnmarshalJSONWithMetadata(node Node) error { + r.Line = node.Range().Start.Line + return node.Decode(&r.inner) +} + +type Parameter struct { + inner parameterInner +} + +type parameterInner struct { + Type string `json:"Type" yaml:"Type"` + Default interface{} `yaml:"Default"` +} + +func (p *Parameter) UnmarshalJSONWithMetadata(node Node) error { + return node.Decode(&p.inner) +} + +type Property struct { + Line int + inner propertyInner +} + +type CFType string + +type propertyInner struct { + Type CFType + Value interface{} `json:"Value" yaml:"Value"` +} + +func (p *Property) UnmarshalJSONWithMetadata(node Node) error { + p.Line = node.Range().Start.Line + return node.Decode(&p.inner) +} + +type Temp struct { + BucketName *Parameter + BucketKeyEnabled *Parameter +} + +type FileContext struct { + Parameters map[string]*Parameter `json:"Parameters" yaml:"Parameters"` + Resources map[string]*Resource `json:"Resources" yaml:"Resources"` +} + +func Test_CloudFormation(t *testing.T) { + var target FileContext + input := ` +{ + "Parameters": { + "BucketName": { + "Type": "String", + "Default": "naughty" + }, + "BucketKeyEnabled": { + "Type": "Boolean", + "Default": false + } + }, + "Resources": { + "S3Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Ref": "BucketName" + }, + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "BucketKeyEnabled": { + "Ref": "BucketKeyEnabled" + } + } + ] + } + } + } + } +} +` + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal([]byte(input), &target, &metadata)) +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_null.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_null.go new file mode 100644 index 000000000000..1a0011ec5dac --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_null.go @@ -0,0 +1,23 @@ +package armjson + +import ( + "fmt" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var nullRunes = []rune("null") + +func (p *parser) parseNull(parentMetadata *types.Metadata) (Node, error) { + + n, _ := p.newNode(KindNull, parentMetadata) + + for _, expected := range nullRunes { + if !p.swallowIfEqual(expected) { + return nil, fmt.Errorf("unexpected character") + } + } + n.raw = nil + n.end = p.position + return n, nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_null_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_null_test.go new file mode 100644 index 000000000000..4f6d109fdf4f --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_null_test.go @@ -0,0 +1,18 @@ +package armjson + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/require" +) + +func Test_Null(t *testing.T) { + example := []byte(`null`) + var output string + ref := &output + metadata := types.NewTestMetadata() + err := Unmarshal(example, &ref, &metadata) + require.NoError(t, err) +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_number.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_number.go new file mode 100644 index 000000000000..586fbd3a4841 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_number.go @@ -0,0 +1,163 @@ +package armjson + +import ( + "fmt" + "strconv" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func (p *parser) parseNumber(parentMetadata *types.Metadata) (Node, error) { + + n, _ := p.newNode(KindNumber, parentMetadata) + + var str string + + if p.swallowIfEqual('-') { + str = "-" + } + + integral, err := p.parseIntegral() + if err != nil { + return nil, err + } + fraction, err := p.parseFraction() + if err != nil { + return nil, err + } + exponent, err := p.parseExponent() + if err != nil { + return nil, err + } + + str = fmt.Sprintf("%s%s%s%s", str, integral, fraction, exponent) + n.end = p.position + + if fraction != "" || exponent != "" { + f, err := strconv.ParseFloat(str, 64) + if err != nil { + return nil, p.makeError("%s", err) + } + n.raw = f + return n, nil + } + + i, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return nil, p.makeError("%s", err) + } + n.raw = i + + return n, nil +} + +func (p *parser) parseIntegral() (string, error) { + r, err := p.next() + if err != nil { + return "", err + } + if r == '0' { + r, _ := p.peeker.Peek() + if r >= '0' && r <= '9' { + return "", p.makeError("invalid number") + } + return "0", nil + } + + var sb strings.Builder + if r < '1' || r > '9' { + return "", p.makeError("invalid number") + } + sb.WriteRune(r) + + for { + r, err := p.next() + if err != nil { + return sb.String(), nil + } + if r < '0' || r > '9' { + return sb.String(), p.undo() + } + sb.WriteRune(r) + } +} + +func (p *parser) parseFraction() (string, error) { + r, err := p.next() + if err != nil { + return "", nil + } + if r != '.' { + return "", p.undo() + } + + var sb strings.Builder + sb.WriteRune('.') + + for { + r, err := p.next() + if err != nil { + break + } + if r < '0' || r > '9' { + if err := p.undo(); err != nil { + return "", err + } + break + } + sb.WriteRune(r) + } + + str := sb.String() + if str == "." { + return "", p.makeError("invalid number - missing digits after decimal point") + } + + return str, nil +} + +func (p *parser) parseExponent() (string, error) { + r, err := p.next() + if err != nil { + return "", nil + } + if r != 'e' && r != 'E' { + return "", p.undo() + } + + var sb strings.Builder + sb.WriteRune(r) + + r, err = p.next() + if err != nil { + return "", nil + } + hasDigits := r >= '0' && r <= '9' + if r != '-' && r != '+' && !hasDigits { + return "", p.undo() + } + + sb.WriteRune(r) + + for { + r, err := p.next() + if err != nil { + break + } + if r < '0' || r > '9' { + if err := p.undo(); err != nil { + return "", err + } + break + } + hasDigits = true + sb.WriteRune(r) + } + + if !hasDigits { + return "", p.makeError("invalid number - no digits in exponent") + } + + return sb.String(), nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go new file mode 100644 index 000000000000..39226b090ede --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go @@ -0,0 +1,178 @@ +package armjson + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Number_IntToInt(t *testing.T) { + example := []byte(`123`) + var output int + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.Equal(t, 123, output) +} + +func Test_Number_IntToFloat(t *testing.T) { + example := []byte(`123`) + var output float64 + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.Equal(t, 123.0, output) +} + +func Test_Number_FloatToFloat(t *testing.T) { + example := []byte(`123.456`) + var output float64 + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.Equal(t, 123.456, output) +} + +func Test_Number_FloatToInt(t *testing.T) { + example := []byte(`123.456`) + var output int + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.Equal(t, 123, output) +} + +func Test_Number_FloatWithExponent(t *testing.T) { + cases := []struct { + in string + out float64 + }{ + { + in: `123.456e10`, + out: 123.456e+10, + }, + { + in: `123e+1`, + out: 123e+1, + }, + { + in: `123e-2`, + out: 123e-2, + }, + } + for _, test := range cases { + t.Run(test.in, func(t *testing.T) { + example := []byte(test.in) + var output float64 + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.Equal(t, test.out, output) + + }) + } +} + +func Test_Number_IntWithExponent(t *testing.T) { + cases := []struct { + in string + out int64 + }{ + { + in: `123e10`, + out: 123e+10, + }, + { + in: `123e+1`, + out: 123e+1, + }, + } + for _, test := range cases { + t.Run(test.in, func(t *testing.T) { + example := []byte(test.in) + var output int64 + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.Equal(t, test.out, output) + + }) + } +} + +func Test_Number_Ints(t *testing.T) { + cases := []struct { + in string + out int64 + err bool + }{ + { + in: `123e10`, + out: 123e+10, + }, + { + in: `-1`, + out: -1, + }, + { + in: `1.0123`, + out: 1, + }, + { + in: `0`, + out: 0, + }, + { + in: `01`, + err: true, + }, + { + in: ``, + err: true, + }, + { + in: `+1`, + err: true, + }, + { + in: `e`, + err: true, + }, + + { + in: `.123`, + err: true, + }, + + { + in: `.`, + err: true, + }, + + { + in: `00`, + err: true, + }, + { + in: `-`, + err: true, + }, + } + for _, test := range cases { + t.Run(test.in, func(t *testing.T) { + example := []byte(test.in) + var output int64 + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, test.out, output) + }) + } +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_object.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_object.go new file mode 100644 index 000000000000..392dbbbae697 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_object.go @@ -0,0 +1,143 @@ +package armjson + +import ( + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func (p *parser) parseObject(parentMetadata *types.Metadata) (Node, error) { + + n, metadata := p.newNode(KindObject, parentMetadata) + + c, err := p.next() + if err != nil { + return nil, err + } + + if c != '{' { + return nil, p.makeError("expecting object delimiter") + } + + if err := p.parseWhitespace(); err != nil { + return nil, err + } + + // we've hit the end of the object + if p.swallowIfEqual('}') { + n.end = p.position + return n, nil + } + + var nextComments []Node + return p.iterateObject(nextComments, metadata, n) + +} + +// nolint: gocyclo +func (p *parser) iterateObject(nextComments []Node, metadata *types.Metadata, n *node) (Node, error) { + for { + + if err := p.parseWhitespace(); err != nil { + return nil, err + } + + comments := make([]Node, len(nextComments)) + copy(comments, nextComments) + nextComments = nil + for { + peeked, err := p.peeker.Peek() + if err != nil { + return nil, err + } + if peeked != '/' { + break + } + comment, err := p.parseComment(metadata) + if err != nil { + return nil, err + } + comments = append(comments, comment) + } + + if comments != nil { + if err := p.parseWhitespace(); err != nil { + return nil, err + } + } + + key, err := p.parseString(metadata) + if err != nil { + return nil, err + } + + if err := p.parseWhitespace(); err != nil { + return nil, err + } + + if !p.swallowIfEqual(':') { + return nil, p.makeError("invalid character, expecting ':'") + } + + val, err := p.parseElement(metadata) + if err != nil { + return nil, err + } + ref := key.(*node).raw.(string) + key.(*node).ref = ref + val.(*node).ref = ref + + for { + peeked, err := p.peeker.Peek() + if err != nil { + return nil, err + } + if peeked != '/' { + break + } + comment, err := p.parseComment(metadata) + if err != nil { + return nil, err + } + comments = append(comments, comment) + } + + // we've hit the end of the object + if p.swallowIfEqual('}') { + key.(*node).comments = comments + val.(*node).comments = comments + n.content = append(n.content, key, val) + n.end = p.position + return n, nil + } + + if !p.swallowIfEqual(',') { + return nil, p.makeError("unexpected character - expecting , or }") + } + + for { + if err := p.parseWhitespace(); err != nil { + return nil, err + } + peeked, err := p.peeker.Peek() + if err != nil { + return nil, err + } + if peeked != '/' { + break + } + comment, err := p.parseComment(metadata) + if err != nil { + return nil, err + } + if comment.Range().Start.Line > val.Range().End.Line { + nextComments = append(nextComments, comment) + } else { + comments = append(comments, comment) + } + } + + key.(*node).comments = comments + val.(*node).comments = comments + n.content = append(n.content, key, val) + + } +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go new file mode 100644 index 000000000000..f689ebd0fa26 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go @@ -0,0 +1,115 @@ +package armjson + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Object(t *testing.T) { + example := []byte(`{ + "name": "testing", + "balance": 3.14 +}`) + target := struct { + Name string `json:"name"` + Balance float64 `json:"balance"` + }{} + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &target, &metadata)) + assert.Equal(t, "testing", target.Name) + assert.Equal(t, 3.14, target.Balance) +} + +func Test_ObjectWithPointers(t *testing.T) { + example := []byte(`{ + "name": "testing", + "balance": 3.14 +}`) + target := struct { + Name *string `json:"name"` + Balance *float64 `json:"balance"` + }{} + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &target, &metadata)) + assert.Equal(t, "testing", *target.Name) + assert.Equal(t, 3.14, *target.Balance) +} + +type nestedParent struct { + Child *nestedChild + Name string +} + +type nestedChild struct { + Blah string `json:"secret"` +} + +func Test_ObjectWithPointerToNestedStruct(t *testing.T) { + example := []byte(`{ + "Child": { + "secret": "password" + }, + "Name": "testing" +}`) + + var parent nestedParent + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &parent, &metadata)) + assert.Equal(t, "testing", parent.Name) + assert.Equal(t, "password", parent.Child.Blah) +} + +func Test_Object_ToMapStringInterface(t *testing.T) { + example := []byte(`{ + "Name": "testing" +}`) + + parent := make(map[string]interface{}) + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &parent, &metadata)) + assert.Equal(t, "testing", parent["Name"]) +} + +func Test_Object_ToNestedMapStringInterfaceFromIAM(t *testing.T) { + example := []byte(` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Action": "ec2:*", + "Resource": "*", + "Condition": { + "Bool": { + "aws:MultiFactorAuthPresent": ["true"] + } + } + } + ] +}`) + + parent := make(map[string]interface{}) + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &parent, &metadata)) +} + +func Test_Object_ToNestedMapStringInterface(t *testing.T) { + example := []byte(`{ + "Child": { + "secret": "password" + }, + "Name": "testing" +}`) + + parent := make(map[string]interface{}) + metadata := types.NewTestMetadata() + require.NoError(t, Unmarshal(example, &parent, &metadata)) + assert.Equal(t, "testing", parent["Name"]) + child := parent["Child"].(map[string]interface{}) + assert.Equal(t, "password", child["secret"]) +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_string.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_string.go new file mode 100644 index 000000000000..41847b914d7f --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_string.go @@ -0,0 +1,91 @@ +package armjson + +import ( + "strconv" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var escapes = map[rune]string{ + '\\': "\\", + '/': "/", + '"': "\"", + 'n': "\n", + 'r': "\r", + 'b': "\b", + 'f': "\f", + 't': "\t", +} + +// nolint: cyclop +func (p *parser) parseString(parentMetadata *types.Metadata) (Node, error) { + + n, _ := p.newNode(KindString, parentMetadata) + + b, err := p.next() + if err != nil { + return nil, err + } + + if b != '"' { + return nil, p.makeError("expecting string delimiter") + } + + var sb strings.Builder + + var inEscape bool + var inHex bool + var hex []rune + + for { + c, err := p.next() + if err != nil { + return nil, err + } + // nolint: gocritic + if inHex { + switch { + case c >= 'a' && c <= 'f', c >= 'A' && c <= 'F', c >= '0' && c <= '9': + hex = append(hex, c) + if len(hex) == 4 { + inHex = false + char, err := strconv.Unquote("\\u" + string(hex)) + if err != nil { + return nil, p.makeError("invalid unicode character '%s'", err) + } + sb.WriteString(char) + hex = nil + } + default: + return nil, p.makeError("invalid hexedecimal escape sequence '\\%s%c'", string(hex), c) + } + } else if inEscape { + inEscape = false + if c == 'u' { + inHex = true + continue + } + seq, ok := escapes[c] + if !ok { + return nil, p.makeError("invalid escape sequence '\\%c'", c) + } + sb.WriteString(seq) + } else { + switch c { + case '\\': + inEscape = true + case '"': + n.raw = sb.String() + n.end = p.position + return n, nil + default: + if c < 0x20 || c > 0x10FFFF { + return nil, p.makeError("invalid unescaped character '0x%X'", c) + } + sb.WriteRune(c) + } + } + + } +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go new file mode 100644 index 000000000000..b2c546f479d2 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go @@ -0,0 +1,37 @@ +package armjson + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_String(t *testing.T) { + example := []byte(`"hello"`) + var output string + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.Equal(t, "hello", output) +} + +func Test_StringToUninitialisedPointer(t *testing.T) { + example := []byte(`"hello"`) + var str *string + metadata := types.NewTestMetadata() + err := Unmarshal(example, str, &metadata) + require.Error(t, err) + assert.Nil(t, str) +} + +func Test_String_ToInterface(t *testing.T) { + example := []byte(`"hello"`) + var output interface{} + metadata := types.NewTestMetadata() + err := Unmarshal(example, &output, &metadata) + require.NoError(t, err) + assert.Equal(t, "hello", output) +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_whitespace.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_whitespace.go new file mode 100644 index 000000000000..ad5751147d3e --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_whitespace.go @@ -0,0 +1,29 @@ +package armjson + +import ( + "errors" + "io" +) + +func (p *parser) parseWhitespace() error { + for { + b, err := p.peeker.Peek() + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + return err + } + switch b { + case 0x0d, 0x20, 0x09: + case 0x0a: + p.position.Column = 1 + p.position.Line++ + default: + return nil + } + if _, err := p.next(); err != nil { + return err + } + } +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/reader.go b/pkg/iac/scanners/azure/arm/parser/armjson/reader.go new file mode 100644 index 000000000000..e05769f02da9 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/reader.go @@ -0,0 +1,36 @@ +package armjson + +import ( + "bufio" + "io" +) + +type PeekReader struct { + underlying *bufio.Reader +} + +func NewPeekReader(reader io.Reader) *PeekReader { + return &PeekReader{ + underlying: bufio.NewReader(reader), + } +} + +func (r *PeekReader) Next() (rune, error) { + c, _, err := r.underlying.ReadRune() + return c, err +} + +func (r *PeekReader) Undo() error { + return r.underlying.UnreadRune() +} + +func (r *PeekReader) Peek() (rune, error) { + c, _, err := r.underlying.ReadRune() + if err != nil { + return 0, err + } + if err := r.underlying.UnreadRune(); err != nil { + return 0, err + } + return c, nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/reader_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/reader_test.go new file mode 100644 index 000000000000..8017f30f9f98 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/reader_test.go @@ -0,0 +1,62 @@ +package armjson + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var input = `abcdefghijklmnopqrstuvwxyz` + +func Test_Peeker(t *testing.T) { + peeker := NewPeekReader(strings.NewReader(input)) + + var b rune + var err error + + for i := 0; i < 30; i++ { + b, err = peeker.Peek() + require.NoError(t, err) + assert.Equal(t, ('a'), b) + } + + b, err = peeker.Next() + require.NoError(t, err) + assert.Equal(t, ('a'), b) + + b, err = peeker.Next() + require.NoError(t, err) + assert.Equal(t, ('b'), b) + + b, err = peeker.Peek() + require.NoError(t, err) + assert.Equal(t, ('c'), b) + + for i := 0; i < 5; i++ { + b, err = peeker.Next() + require.NoError(t, err) + assert.Equal(t, []rune(input)[2+i], b) + } + + b, err = peeker.Peek() + require.NoError(t, err) + assert.Equal(t, ('h'), b) + + b, err = peeker.Next() + require.NoError(t, err) + assert.Equal(t, ('h'), b) + for i := 0; i < 18; i++ { + b, err = peeker.Next() + require.NoError(t, err) + assert.Equal(t, []rune(input)[8+i], b) + } + + _, err = peeker.Peek() + require.Error(t, err) + + _, err = peeker.Next() + require.Error(t, err) + +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go b/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go new file mode 100644 index 000000000000..a75942808eba --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go @@ -0,0 +1,40 @@ +package armjson + +import ( + "bytes" + "io" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Unmarshaller interface { + UnmarshalJSONWithMetadata(node Node) error +} + +type MetadataReceiver interface { + SetMetadata(m *types.Metadata) +} + +func Unmarshal(data []byte, target interface{}, metadata *types.Metadata) error { + node, err := newParser(NewPeekReader(bytes.NewReader(data)), Position{1, 1}).parse(metadata) + if err != nil { + return err + } + if err := node.Decode(target); err != nil { + return err + } + + return nil +} + +func UnmarshalFromReader(r io.ReadSeeker, target interface{}, metadata *types.Metadata) error { + node, err := newParser(NewPeekReader(r), Position{1, 1}).parse(metadata) + if err != nil { + return err + } + if err := node.Decode(target); err != nil { + return err + } + + return nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/parser.go b/pkg/iac/scanners/azure/arm/parser/parser.go new file mode 100644 index 000000000000..e5e171914b42 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/parser.go @@ -0,0 +1,193 @@ +package parser + +import ( + "context" + "fmt" + "io" + "io/fs" + "path/filepath" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/resolver" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Parser struct { + targetFS fs.FS + skipRequired bool + debug debug.Logger +} + +func (p *Parser) SetDebugWriter(writer io.Writer) { + p.debug = debug.New(writer, "azure", "arm") +} + +func (p *Parser) SetSkipRequiredCheck(b bool) { + p.skipRequired = b +} + +func New(targetFS fs.FS, opts ...options.ParserOption) *Parser { + p := &Parser{ + targetFS: targetFS, + } + for _, opt := range opts { + opt(p) + } + return p +} + +func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure2.Deployment, error) { + + var deployments []azure2.Deployment + + if err := fs.WalkDir(p.targetFS, dir, func(path string, entry fs.DirEntry, err error) error { + if err != nil { + return err + } + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if entry.IsDir() { + return nil + } + if !p.Required(path) { + return nil + } + f, err := p.targetFS.Open(path) + if err != nil { + return err + } + defer f.Close() + deployment, err := p.parseFile(f, path) + if err != nil { + return err + } + deployments = append(deployments, *deployment) + return nil + }); err != nil { + return nil, err + } + + return deployments, nil +} + +func (p *Parser) Required(path string) bool { + if p.skipRequired { + return true + } + if !strings.HasSuffix(path, ".json") { + return false + } + data, err := fs.ReadFile(p.targetFS, path) + if err != nil { + return false + } + var template Template + root := types.NewMetadata( + types.NewRange(filepath.Base(path), 0, 0, "", p.targetFS), + "", + ) + if err := armjson.Unmarshal(data, &template, &root); err != nil { + p.debug.Log("Error scanning %s: %s", path, err) + return false + } + + if template.Schema.Kind != azure2.KindString { + return false + } + + return strings.HasPrefix(template.Schema.AsString(), "https://schema.management.azure.com") +} + +func (p *Parser) parseFile(r io.Reader, filename string) (*azure2.Deployment, error) { + var template Template + data, err := io.ReadAll(r) + if err != nil { + return nil, err + } + root := types.NewMetadata( + types.NewRange(filename, 0, 0, "", p.targetFS), + "", + ).WithInternal(resolver.NewResolver()) + + if err := armjson.Unmarshal(data, &template, &root); err != nil { + return nil, fmt.Errorf("failed to parse template: %w", err) + } + return p.convertTemplate(template), nil +} + +func (p *Parser) convertTemplate(template Template) *azure2.Deployment { + + deployment := azure2.Deployment{ + Metadata: template.Metadata, + TargetScope: azure2.ScopeResourceGroup, // TODO: override from --resource-group? + Parameters: nil, + Variables: nil, + Resources: nil, + Outputs: nil, + } + + if r, ok := template.Metadata.Internal().(resolver.Resolver); ok { + r.SetDeployment(&deployment) + } + + // TODO: the references passed here should probably not be the name - maybe params.NAME.DefaultValue? + for name, param := range template.Parameters { + deployment.Parameters = append(deployment.Parameters, azure2.Parameter{ + Variable: azure2.Variable{ + Name: name, + Value: param.DefaultValue, + }, + Default: param.DefaultValue, + Decorators: nil, + }) + } + + for name, variable := range template.Variables { + deployment.Variables = append(deployment.Variables, azure2.Variable{ + Name: name, + Value: variable, + }) + } + + for name, output := range template.Outputs { + deployment.Outputs = append(deployment.Outputs, azure2.Output{ + Name: name, + Value: output, + }) + } + + for _, resource := range template.Resources { + deployment.Resources = append(deployment.Resources, p.convertResource(resource)) + } + + return &deployment +} + +func (p *Parser) convertResource(input Resource) azure2.Resource { + + var children []azure2.Resource + + for _, child := range input.Resources { + children = append(children, p.convertResource(child)) + } + + resource := azure2.Resource{ + Metadata: input.Metadata, + APIVersion: input.APIVersion, + Type: input.Type, + Kind: input.Kind, + Name: input.Name, + Location: input.Location, + Properties: input.Properties, + Resources: children, + } + + return resource +} diff --git a/pkg/iac/scanners/azure/arm/parser/parser_test.go b/pkg/iac/scanners/azure/arm/parser/parser_test.go new file mode 100644 index 000000000000..d54a147370e0 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/parser_test.go @@ -0,0 +1,338 @@ +package parser + +import ( + "context" + "io/fs" + "os" + "testing" + + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/resolver" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/liamg/memoryfs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +func createMetadata(targetFS fs.FS, filename string, start, end int, ref string, parent *types.Metadata) types.Metadata { + child := types.NewMetadata(types.NewRange(filename, start, end, "", targetFS), ref) + if parent != nil { + child.SetParentPtr(parent) + } + return child +} + +func TestParser_Parse(t *testing.T) { + + filename := "example.json" + + targetFS := memoryfs.New() + + tests := []struct { + name string + input string + want func() azure2.Deployment + wantDeployment bool + }{ + { + name: "invalid code", + input: `blah`, + wantDeployment: false, + }, + { + name: "basic param", + input: `{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", // another one + "contentVersion": "1.0.0.0", + "parameters": { + "storagePrefix": { + "type": "string", + "defaultValue": "x", + "maxLength": 11, + "minLength": 3 + } + }, + "resources": [] +}`, + want: func() azure2.Deployment { + + root := createMetadata(targetFS, filename, 0, 0, "", nil).WithInternal(resolver.NewResolver()) + metadata := createMetadata(targetFS, filename, 1, 13, "", &root) + parametersMetadata := createMetadata(targetFS, filename, 4, 11, "parameters", &metadata) + storageMetadata := createMetadata(targetFS, filename, 5, 10, "parameters.storagePrefix", ¶metersMetadata) + + return azure2.Deployment{ + Metadata: metadata, + TargetScope: azure2.ScopeResourceGroup, + Parameters: []azure2.Parameter{ + { + Variable: azure2.Variable{ + Name: "storagePrefix", + Value: azure2.NewValue("x", createMetadata(targetFS, filename, 7, 7, "parameters.storagePrefix.defaultValue", &storageMetadata)), + }, + Default: azure2.NewValue("x", createMetadata(targetFS, filename, 7, 7, "parameters.storagePrefix.defaultValue", &storageMetadata)), + Decorators: nil, + }, + }, + } + }, + wantDeployment: true, + }, + { + name: "storageAccount", + input: `{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", // another one + "contentVersion": "1.0.0.0", + "parameters": {}, + "resources": [ +{ + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-05-01", + "name": "myResource", + "location": "string", + "tags": { + "tagName1": "tagValue1", + "tagName2": "tagValue2" + }, + "sku": { + "name": "string" + }, + "kind": "string", + "extendedLocation": { + "name": "string", + "type": "EdgeZone" + }, + "identity": { + "type": "string", + "userAssignedIdentities": {} + }, + "properties": { + "allowSharedKeyAccess":false, + "customDomain": { + "name": "string", + "useSubDomainName":false, + "number": 123 + }, + "networkAcls": [ + { + "bypass": "AzureServices1" + }, + { + "bypass": "AzureServices2" + } + ] + } +} +] +}`, + want: func() azure2.Deployment { + + rootMetadata := createMetadata(targetFS, filename, 0, 0, "", nil).WithInternal(resolver.NewResolver()) + fileMetadata := createMetadata(targetFS, filename, 1, 45, "", &rootMetadata) + resourcesMetadata := createMetadata(targetFS, filename, 5, 44, "resources", &fileMetadata) + + resourceMetadata := createMetadata(targetFS, filename, 6, 43, "resources[0]", &resourcesMetadata) + + propertiesMetadata := createMetadata(targetFS, filename, 27, 42, "resources[0].properties", &resourceMetadata) + + customDomainMetadata := createMetadata(targetFS, filename, 29, 33, "resources[0].properties.customDomain", &propertiesMetadata) + networkACLListMetadata := createMetadata(targetFS, filename, 34, 41, "resources[0].properties.networkAcls", &propertiesMetadata) + + networkACL0Metadata := createMetadata(targetFS, filename, 35, 37, "resources[0].properties.networkAcls[0]", &networkACLListMetadata) + networkACL1Metadata := createMetadata(targetFS, filename, 38, 40, "resources[0].properties.networkAcls[1]", &networkACLListMetadata) + + return azure2.Deployment{ + Metadata: fileMetadata, + TargetScope: azure2.ScopeResourceGroup, + Resources: []azure2.Resource{ + { + Metadata: resourceMetadata, + APIVersion: azure2.NewValue( + "2022-05-01", + createMetadata(targetFS, filename, 8, 8, "resources[0].apiVersion", &resourceMetadata), + ), + Type: azure2.NewValue( + "Microsoft.Storage/storageAccounts", + createMetadata(targetFS, filename, 7, 7, "resources[0].type", &resourceMetadata), + ), + Kind: azure2.NewValue( + "string", + createMetadata(targetFS, filename, 18, 18, "resources[0].kind", &resourceMetadata), + ), + Name: azure2.NewValue( + "myResource", + createMetadata(targetFS, filename, 9, 9, "resources[0].name", &resourceMetadata), + ), + Location: azure2.NewValue( + "string", + createMetadata(targetFS, filename, 10, 10, "resources[0].location", &resourceMetadata), + ), + Properties: azure2.NewValue( + map[string]azure2.Value{ + "allowSharedKeyAccess": azure2.NewValue(false, createMetadata(targetFS, filename, 28, 28, "resources[0].properties.allowSharedKeyAccess", &propertiesMetadata)), + "customDomain": azure2.NewValue( + map[string]azure2.Value{ + "name": azure2.NewValue("string", createMetadata(targetFS, filename, 30, 30, "resources[0].properties.customDomain.name", &customDomainMetadata)), + "useSubDomainName": azure2.NewValue(false, createMetadata(targetFS, filename, 31, 31, "resources[0].properties.customDomain.useSubDomainName", &customDomainMetadata)), + "number": azure2.NewValue(int64(123), createMetadata(targetFS, filename, 32, 32, "resources[0].properties.customDomain.number", &customDomainMetadata)), + }, customDomainMetadata), + "networkAcls": azure2.NewValue( + []azure2.Value{ + azure2.NewValue( + map[string]azure2.Value{ + "bypass": azure2.NewValue("AzureServices1", createMetadata(targetFS, filename, 36, 36, "resources[0].properties.networkAcls[0].bypass", &networkACL0Metadata)), + }, + networkACL0Metadata, + ), + azure2.NewValue( + map[string]azure2.Value{ + "bypass": azure2.NewValue("AzureServices2", createMetadata(targetFS, filename, 39, 39, "resources[0].properties.networkAcls[1].bypass", &networkACL1Metadata)), + }, + networkACL1Metadata, + ), + }, networkACLListMetadata), + }, + propertiesMetadata, + ), + }, + }, + } + }, + + wantDeployment: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + require.NoError(t, targetFS.WriteFile(filename, []byte(tt.input), 0644)) + + p := New(targetFS, options.ParserWithDebug(os.Stderr)) + got, err := p.ParseFS(context.Background(), ".") + require.NoError(t, err) + + if !tt.wantDeployment { + assert.Len(t, got, 0) + return + } + + require.Len(t, got, 1) + want := tt.want() + g := got[0] + + require.Equal(t, want, g) + }) + } +} + +func Test_NestedResourceParsing(t *testing.T) { + + input := ` +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environment": { + "type": "string", + "allowedValues": [ + "dev", + "test", + "prod" + ] + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + }, + "storageAccountSkuName": { + "type": "string", + "defaultValue": "Standard_LRS" + }, + "storageAccountSkuTier": { + "type": "string", + "defaultValue": "Standard" + } + }, + "variables": { + "uniquePart": "[take(uniqueString(resourceGroup().id), 4)]", + "storageAccountName": "[concat('mystorageaccount', variables('uniquePart'), parameters('environment'))]", + "queueName": "myqueue" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "location": "[parameters('location')]", + "apiVersion": "2019-06-01", + "sku": { + "name": "[parameters('storageAccountSkuName')]", + "tier": "[parameters('storageAccountSkuTier')]" + }, + "kind": "StorageV2", + "properties": {}, + "resources": [ + { + "name": "[concat('default/', variables('queueName'))]", + "type": "queueServices/queues", + "apiVersion": "2019-06-01", + "dependsOn": [ + "[variables('storageAccountName')]" + ], + "properties": { + "metadata": {} + } + } + ] + } + ] +} +` + + targetFS := memoryfs.New() + + require.NoError(t, targetFS.WriteFile("nested.json", []byte(input), 0644)) + + p := New(targetFS, options.ParserWithDebug(os.Stderr)) + got, err := p.ParseFS(context.Background(), ".") + require.NoError(t, err) + require.Len(t, got, 1) + + deployment := got[0] + + require.Len(t, deployment.Resources, 1) + + storageAccountResource := deployment.Resources[0] + + require.Len(t, storageAccountResource.Resources, 1) + + queue := storageAccountResource.Resources[0] + + assert.Equal(t, "queueServices/queues", queue.Type.AsString()) +} + +// +// func Test_JsonFile(t *testing.T) { +// +// input, err := os.ReadFile("testdata/postgres.json") +// require.NoError(t, err) +// +// targetFS := memoryfs.New() +// +// require.NoError(t, targetFS.WriteFile("postgres.json", input, 0644)) +// +// p := New(targetFS, options.ParserWithDebug(os.Stderr)) +// got, err := p.ParseFS(context.Background(), ".") +// require.NoError(t, err) +// +// got[0].Resources[3].Name.Resolve() +// +// name := got[0].Resources[3].Name.AsString() +// assert.Equal(t, "myserver", name) +// +// } diff --git a/pkg/iac/scanners/azure/arm/parser/template.go b/pkg/iac/scanners/azure/arm/parser/template.go new file mode 100644 index 000000000000..ed7b3fa30238 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/template.go @@ -0,0 +1,78 @@ +package parser + +import ( + types2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Template struct { + Metadata types.Metadata `json:"-"` + Schema types2.Value `json:"$schema"` + ContentVersion types2.Value `json:"contentVersion"` + APIProfile types2.Value `json:"apiProfile"` + Parameters map[string]Parameter `json:"parameters"` + Variables map[string]types2.Value `json:"variables"` + Functions []Function `json:"functions"` + Resources []Resource `json:"resources"` + Outputs map[string]types2.Value `json:"outputs"` +} + +type Parameter struct { + Metadata types.Metadata + Type types2.Value `json:"type"` + DefaultValue types2.Value `json:"defaultValue"` + MaxLength types2.Value `json:"maxLength"` + MinLength types2.Value `json:"minLength"` +} + +type Function struct{} + +type Resource struct { + Metadata types.Metadata `json:"-"` + innerResource +} + +func (t *Template) SetMetadata(m *types.Metadata) { + t.Metadata = *m +} + +func (r *Resource) SetMetadata(m *types.Metadata) { + r.Metadata = *m +} + +func (p *Parameter) SetMetadata(m *types.Metadata) { + p.Metadata = *m +} + +type innerResource struct { + APIVersion types2.Value `json:"apiVersion"` + Type types2.Value `json:"type"` + Kind types2.Value `json:"kind"` + Name types2.Value `json:"name"` + Location types2.Value `json:"location"` + Tags types2.Value `json:"tags"` + Sku types2.Value `json:"sku"` + Properties types2.Value `json:"properties"` + Resources []Resource `json:"resources"` +} + +func (v *Resource) UnmarshalJSONWithMetadata(node armjson.Node) error { + + if err := node.Decode(&v.innerResource); err != nil { + return err + } + + v.Metadata = node.Metadata() + + for _, comment := range node.Comments() { + var str string + if err := comment.Decode(&str); err != nil { + return err + } + // TODO(someone): add support for metadata comments + // v.Metadata.Comments = append(v.Metadata.Comments, str) + } + + return nil +} diff --git a/pkg/iac/scanners/azure/arm/parser/template_test.go b/pkg/iac/scanners/azure/arm/parser/template_test.go new file mode 100644 index 000000000000..3c96c8cf588f --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/template_test.go @@ -0,0 +1,60 @@ +package parser + +import ( + "os" + "path/filepath" + "testing" + + types2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_JSONUnmarshal(t *testing.T) { + data, err := os.ReadFile(filepath.Join("testdata", "example.json")) + require.NoError(t, err) + var target Template + metadata := types.NewTestMetadata() + require.NoError(t, armjson.Unmarshal(data, &target, &metadata)) + assert.Equal(t, + "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + target.Schema.AsString(), + ) + require.Len(t, target.Schema.Comments, 2) + assert.Equal(t, " wow this is a comment", target.Schema.Comments[0]) + assert.Equal(t, " another one", target.Schema.Comments[1]) + + assert.Equal(t, "1.0.0.0", target.ContentVersion.Raw()) + require.Len(t, target.ContentVersion.Comments, 1) + assert.Equal(t, " this version is great", target.ContentVersion.Comments[0]) + + require.Contains(t, target.Parameters, "storagePrefix") + prefix := target.Parameters["storagePrefix"] + /* + "type": "string", + "defaultValue": "x", + "maxLength": 11, + "minLength": 3 + */ + assert.Equal(t, "string", prefix.Type.Raw()) + assert.Equal(t, types2.KindString, prefix.Type.Kind) + assert.Equal(t, 8, prefix.Type.Metadata.Range().GetStartLine()) + assert.Equal(t, 8, prefix.Type.Metadata.Range().GetEndLine()) + + assert.Equal(t, "x", prefix.DefaultValue.Raw()) + assert.Equal(t, types2.KindString, prefix.DefaultValue.Kind) + assert.Equal(t, 9, prefix.DefaultValue.Metadata.Range().GetStartLine()) + assert.Equal(t, 9, prefix.DefaultValue.Metadata.Range().GetEndLine()) + + assert.Equal(t, int64(11), prefix.MaxLength.Raw()) + assert.Equal(t, types2.KindNumber, prefix.MaxLength.Kind) + assert.Equal(t, 10, prefix.MaxLength.Metadata.Range().GetStartLine()) + assert.Equal(t, 10, prefix.MaxLength.Metadata.Range().GetEndLine()) + + assert.Equal(t, int64(3), prefix.MinLength.Raw()) + assert.Equal(t, types2.KindNumber, prefix.MinLength.Kind) + assert.Equal(t, 11, prefix.MinLength.Metadata.Range().GetStartLine()) + assert.Equal(t, 11, prefix.MinLength.Metadata.Range().GetEndLine()) +} diff --git a/pkg/iac/scanners/azure/arm/parser/testdata/example.json b/pkg/iac/scanners/azure/arm/parser/testdata/example.json new file mode 100644 index 000000000000..9698ed1a0583 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/testdata/example.json @@ -0,0 +1,15 @@ +{ + // wow this is a comment + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", // another one + // this version is great + "contentVersion": "1.0.0.0", + "parameters": { + "storagePrefix": { + "type": "string", + "defaultValue": "x", + "maxLength": 11, + "minLength": 3 + } + }, + "resources": [] +} \ No newline at end of file diff --git a/pkg/iac/scanners/azure/arm/parser/testdata/postgres.json b/pkg/iac/scanners/azure/arm/parser/testdata/postgres.json new file mode 100644 index 000000000000..670733fdd308 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/parser/testdata/postgres.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.10.61.36676", + "templateHash": "8074447630975889785" + } + }, + "resources": [ + { + "type": "Microsoft.DBforPostgreSQL/servers", + "apiVersion": "2017-12-01", + "name": "myPostgreSQLServer", + "location": "westus", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "administratorLogin": "myadmin", + "administratorLoginPassword": "myadminpassword", + "version": "9.6", + "sslEnforcement": "Enabled", + "storageProfile": { + "storageMB": 5120 + }, + "createMode": "Default", + "minimalTlsVersion": "1.2", + "publicNetworkAccess": "Enabled", + "FirewallRules": [ + { + "name": "AllowAllAzureIps", + "startIpAddress": "0.0.0.0/0" + } + ] + } + }, + { + "type": "Microsoft.DBforPostgreSQL/servers/configurations", + "apiVersion": "2017-12-01", + "name": "[format('{0}/{1}', 'myPostgreSQLServer', 'connection_throttling')]", + "properties": { + "value": "OFF" + }, + "dependsOn": [ + "[resourceId('Microsoft.DBforPostgreSQL/servers', 'myPostgreSQLServer')]" + ] + }, + { + "type": "Microsoft.DBforPostgreSQL/servers/configurations", + "apiVersion": "2017-12-01", + "name": "[format('{0}/{1}', 'myPostgreSQLServer', 'log_checkpoints')]", + "properties": { + "value": "OFF" + }, + "dependsOn": [ + "[resourceId('Microsoft.DBforPostgreSQL/servers', 'myPostgreSQLServer')]" + ] + }, + { + "type": "Microsoft.DBforPostgreSQL/servers/configurations", + "apiVersion": "2017-12-01", + "name": "[format('{0}/{1}', 'myPostgreSQLServer', 'log_connections')]", + "properties": { + "value": "OFF" + }, + "dependsOn": [ + "[resourceId('Microsoft.DBforPostgreSQL/servers', 'myPostgreSQLServer')]" + ] + } + ] +} \ No newline at end of file diff --git a/pkg/iac/scanners/azure/arm/scanner.go b/pkg/iac/scanners/azure/arm/scanner.go new file mode 100644 index 000000000000..d9ae227a0992 --- /dev/null +++ b/pkg/iac/scanners/azure/arm/scanner.go @@ -0,0 +1,185 @@ +package arm + +import ( + "context" + "fmt" + "io" + "io/fs" + "sync" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/arm" + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/state" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var _ scanners.FSScanner = (*Scanner)(nil) +var _ options.ConfigurableScanner = (*Scanner)(nil) + +type Scanner struct { // nolint: gocritic + scannerOptions []options.ScannerOption + parserOptions []options.ParserOption + debug debug.Logger + frameworks []framework.Framework + skipRequired bool + regoOnly bool + loadEmbeddedPolicies bool + loadEmbeddedLibraries bool + policyDirs []string + policyReaders []io.Reader + regoScanner *rego.Scanner + spec string + sync.Mutex +} + +func (s *Scanner) SetSpec(spec string) { + s.spec = spec +} + +func (s *Scanner) SetRegoOnly(regoOnly bool) { + s.regoOnly = regoOnly +} + +func New(opts ...options.ScannerOption) *Scanner { + scanner := &Scanner{ + scannerOptions: opts, + } + for _, opt := range opts { + opt(scanner) + } + return scanner +} + +func (s *Scanner) Name() string { + return "Azure ARM" +} + +func (s *Scanner) SetDebugWriter(writer io.Writer) { + s.debug = debug.New(writer, "azure", "arm") + s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) +} + +func (s *Scanner) SetPolicyDirs(dirs ...string) { + s.policyDirs = dirs +} + +func (s *Scanner) SetSkipRequiredCheck(skipRequired bool) { + s.skipRequired = skipRequired +} +func (s *Scanner) SetPolicyReaders(readers []io.Reader) { + s.policyReaders = readers +} + +func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} +func (s *Scanner) SetDataFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} + +func (s *Scanner) SetUseEmbeddedPolicies(b bool) { + s.loadEmbeddedPolicies = b +} + +func (s *Scanner) SetUseEmbeddedLibraries(b bool) { + s.loadEmbeddedLibraries = b +} + +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { + s.frameworks = frameworks +} + +func (s *Scanner) SetTraceWriter(io.Writer) {} +func (s *Scanner) SetPerResultTracingEnabled(bool) {} +func (s *Scanner) SetDataDirs(...string) {} +func (s *Scanner) SetPolicyNamespaces(...string) {} +func (s *Scanner) SetRegoErrorLimit(_ int) {} + +func (s *Scanner) initRegoScanner(srcFS fs.FS) error { + s.Lock() + defer s.Unlock() + if s.regoScanner != nil { + return nil + } + regoScanner := rego.NewScanner(types.SourceCloud, s.scannerOptions...) + regoScanner.SetParentDebugLogger(s.debug) + if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + return err + } + s.regoScanner = regoScanner + return nil +} + +func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) { + p := parser.New(fsys, s.parserOptions...) + deployments, err := p.ParseFS(ctx, dir) + if err != nil { + return nil, err + } + if err := s.initRegoScanner(fsys); err != nil { + return nil, err + } + + return s.scanDeployments(ctx, deployments, fsys) +} + +func (s *Scanner) scanDeployments(ctx context.Context, deployments []azure.Deployment, f fs.FS) (scan.Results, error) { + + var results scan.Results + + for _, deployment := range deployments { + + result, err := s.scanDeployment(ctx, deployment, f) + if err != nil { + return nil, err + } + results = append(results, result...) + } + + return results, nil +} + +func (s *Scanner) scanDeployment(ctx context.Context, deployment azure.Deployment, fsys fs.FS) (scan.Results, error) { + var results scan.Results + deploymentState := s.adaptDeployment(ctx, deployment) + if !s.regoOnly { + for _, rule := range rules.GetRegistered(s.frameworks...) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + if rule.GetRule().RegoPackage != "" { + continue + } + ruleResults := rule.Evaluate(deploymentState) + s.debug.Log("Found %d results for %s", len(ruleResults), rule.GetRule().AVDID) + if len(ruleResults) > 0 { + results = append(results, ruleResults...) + } + } + } + + regoResults, err := s.regoScanner.ScanInput(ctx, rego.Input{ + Path: deployment.Metadata.Range().GetFilename(), + FS: fsys, + Contents: deploymentState.ToRego(), + }) + if err != nil { + return nil, fmt.Errorf("rego scan error: %w", err) + } + + return append(results, regoResults...), nil +} + +func (s *Scanner) adaptDeployment(ctx context.Context, deployment azure.Deployment) *state.State { + return arm.Adapt(ctx, deployment) +} diff --git a/pkg/iac/scanners/azure/deployment.go b/pkg/iac/scanners/azure/deployment.go new file mode 100644 index 000000000000..f2f050f7cf95 --- /dev/null +++ b/pkg/iac/scanners/azure/deployment.go @@ -0,0 +1,179 @@ +package azure + +import ( + "os" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Deployment struct { + Metadata types.Metadata + TargetScope Scope + Parameters []Parameter + Variables []Variable + Resources []Resource + Outputs []Output +} + +type Parameter struct { + Variable + Default Value + Decorators []Decorator +} + +type Variable struct { + Name string + Value Value +} + +type Output Variable + +type Resource struct { + Metadata types.Metadata + APIVersion Value + Type Value + Kind Value + Name Value + Location Value + Tags Value + Sku Value + Properties Value + Resources []Resource +} + +type PropertyBag struct { + Metadata types.Metadata + Data map[string]Value +} + +type Decorator struct { + Name string + Args []Value +} + +type Scope string + +const ( + ScopeResourceGroup Scope = "resourceGroup" +) + +func (d *Deployment) GetResourcesByType(t string) []Resource { + var resources []Resource + for _, r := range d.Resources { + if r.Type.AsString() == t { + resources = append(resources, r) + } + } + return resources +} + +func (r *Resource) GetResourcesByType(t string) []Resource { + var resources []Resource + for _, res := range r.Resources { + if res.Type.AsString() == t { + resources = append(resources, res) + } + } + return resources +} + +func (d *Deployment) GetParameter(parameterName string) interface{} { + + for _, parameter := range d.Parameters { + if parameter.Name == parameterName { + return parameter.Value.Raw() + } + } + return nil +} + +func (d *Deployment) GetVariable(variableName string) interface{} { + + for _, variable := range d.Variables { + if variable.Name == variableName { + return variable.Value.Raw() + } + } + return nil +} + +func (d *Deployment) GetEnvVariable(envVariableName string) interface{} { + + if envVariable, exists := os.LookupEnv(envVariableName); exists { + return envVariable + } + return nil +} + +func (d *Deployment) GetOutput(outputName string) interface{} { + + for _, output := range d.Outputs { + if output.Name == outputName { + return output.Value.Raw() + } + } + return nil +} + +func (d *Deployment) GetDeployment() interface{} { + + type template struct { + Schema string `json:"$schema"` + ContentVersion string `json:"contentVersion"` + Parameters map[string]interface{} `json:"parameters"` + Variables map[string]interface{} `json:"variables"` + Resources []interface{} `json:"resources"` + Outputs map[string]interface{} `json:"outputs"` + } + + type templateLink struct { + URI string `json:"uri"` + } + + type properties struct { + TemplateLink templateLink `json:"templateLink"` + Template template `json:"template"` + TemplateHash string `json:"templateHash"` + Parameters map[string]interface{} `json:"parameters"` + Mode string `json:"mode"` + ProvisioningState string `json:"provisioningState"` + } + + deploymentShell := struct { + Name string `json:"name"` + Properties properties `json:"properties"` + }{ + Name: "Placeholder Deployment", + Properties: properties{ + TemplateLink: templateLink{ + URI: "https://placeholder.com", + }, + Template: template{ + Schema: "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + ContentVersion: "", + Parameters: make(map[string]interface{}), + Variables: make(map[string]interface{}), + Resources: make([]interface{}, 0), + Outputs: make(map[string]interface{}), + }, + }, + } + + for _, parameter := range d.Parameters { + deploymentShell.Properties.Template.Parameters[parameter.Name] = parameter.Value.Raw() + } + + for _, variable := range d.Variables { + deploymentShell.Properties.Template.Variables[variable.Name] = variable.Value.Raw() + } + + for _, resource := range d.Resources { + deploymentShell.Properties.Template.Resources = append(deploymentShell.Properties.Template.Resources, resource) + } + + for _, output := range d.Outputs { + deploymentShell.Properties.Template.Outputs[output.Name] = output.Value.Raw() + } + + return deploymentShell +} diff --git a/pkg/iac/scanners/azure/expressions/lex.go b/pkg/iac/scanners/azure/expressions/lex.go new file mode 100644 index 000000000000..09eb7b819eff --- /dev/null +++ b/pkg/iac/scanners/azure/expressions/lex.go @@ -0,0 +1,203 @@ +package expressions + +import ( + "bufio" + "fmt" + "strconv" + "strings" +) + +type TokenType uint16 + +const ( + TokenName TokenType = iota + TokenOpenParen + TokenCloseParen + TokenComma + TokenDot + TokenLiteralString + TokenLiteralInteger + TokenLiteralFloat + TokenNewLine +) + +type Token struct { + Type TokenType + Data interface{} +} + +type lexer struct { + reader *bufio.Reader +} + +func lex(expression string) ([]Token, error) { + lexer := &lexer{ + reader: bufio.NewReader(strings.NewReader(expression)), + } + return lexer.Lex() +} + +func (l *lexer) unread() { + _ = l.reader.UnreadRune() +} + +func (l *lexer) read() (rune, error) { + r, _, err := l.reader.ReadRune() + return r, err +} + +func (l *lexer) Lex() ([]Token, error) { + var tokens []Token + + for { + r, err := l.read() + if err != nil { + break + } + + switch r { + case ' ', '\t', '\r': + continue + case '\n': + tokens = append(tokens, Token{Type: TokenNewLine}) + case '(': + tokens = append(tokens, Token{Type: TokenOpenParen}) + case ')': + tokens = append(tokens, Token{Type: TokenCloseParen}) + case ',': + tokens = append(tokens, Token{Type: TokenComma}) + case '.': + tokens = append(tokens, Token{Type: TokenDot}) + case '"', '\'': + token, err := l.lexString(r) + if err != nil { + return nil, fmt.Errorf("string parse error: %w", err) + } + tokens = append(tokens, token) + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + l.unread() + token, err := l.lexNumber() + if err != nil { + return nil, fmt.Errorf("number parse error: %w", err) + } + tokens = append(tokens, token) + default: + l.unread() + tokens = append(tokens, l.lexKeyword()) + } + } + + return tokens, nil +} + +func (l *lexer) lexString(terminator rune) (Token, error) { + var sb strings.Builder + for { + r, err := l.read() + if err != nil { + break + } + if r == '\\' { + r, err := l.readEscapedChar() + if err != nil { + return Token{}, fmt.Errorf("bad escape: %w", err) + } + sb.WriteRune(r) + continue + } + if r == terminator { + break + } + sb.WriteRune(r) + } + return Token{ + Type: TokenLiteralString, + Data: sb.String(), + }, nil +} + +func (l *lexer) readEscapedChar() (rune, error) { + r, err := l.read() + if err != nil { + return 0, fmt.Errorf("unexpected EOF") + } + switch r { + case 'n': + return '\n', nil + case 'r': + return '\r', nil + case 't': + return '\t', nil + case '"', '\'': + return r, nil + default: + return 0, fmt.Errorf("'%c' is not a supported escape sequence", r) + } +} + +func (l *lexer) lexNumber() (Token, error) { + + var sb strings.Builder + var decimal bool + +LOOP: + for { + r, err := l.read() + if err != nil { + break + } + switch r { + case '.': + decimal = true + sb.WriteRune('.') + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + sb.WriteRune(r) + default: + l.unread() + break LOOP + } + } + + raw := sb.String() + if decimal { + fl, err := strconv.ParseFloat(raw, 64) + if err != nil { + return Token{}, err + } + return Token{ + Type: TokenLiteralFloat, + Data: fl, + }, nil + } + + i, err := strconv.ParseInt(raw, 10, 64) + if err != nil { + return Token{}, err + } + return Token{ + Type: TokenLiteralInteger, + Data: i, + }, nil +} + +func (l *lexer) lexKeyword() Token { + var sb strings.Builder +LOOP: + for { + r, err := l.read() + if err != nil { + break + } + switch { + case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '_': + sb.WriteRune(r) + default: + l.unread() + break LOOP + } + } + return Token{ + Type: TokenName, + Data: sb.String(), + } +} diff --git a/pkg/iac/scanners/azure/expressions/node.go b/pkg/iac/scanners/azure/expressions/node.go new file mode 100644 index 000000000000..a126a9e40c3f --- /dev/null +++ b/pkg/iac/scanners/azure/expressions/node.go @@ -0,0 +1,75 @@ +package expressions + +import ( + functions2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/functions" +) + +type Node interface { + Evaluate(deploymentProvider functions2.DeploymentData) interface{} +} + +type expressionValue struct { + val interface{} +} + +func (e expressionValue) Evaluate(deploymentProvider functions2.DeploymentData) interface{} { + if f, ok := e.val.(expression); ok { + return f.Evaluate(deploymentProvider) + } + return e.val +} + +type expression struct { + name string + args []Node +} + +func (f expression) Evaluate(deploymentProvider functions2.DeploymentData) interface{} { + args := make([]interface{}, len(f.args)) + for i, arg := range f.args { + args[i] = arg.Evaluate(deploymentProvider) + } + + return functions2.Evaluate(deploymentProvider, f.name, args...) +} + +func NewExpressionTree(code string) (Node, error) { + tokens, err := lex(code) + if err != nil { + return nil, err + } + + // create a walker for the nodes + tw := newTokenWalker(tokens) + + // generate the root function + return newFunctionNode(tw), nil +} + +func newFunctionNode(tw *tokenWalker) Node { + funcNode := &expression{ + name: tw.pop().Data.(string), + } + + for tw.hasNext() { + token := tw.pop() + if token == nil { + break + } + + switch token.Type { + case TokenCloseParen: + return funcNode + case TokenName: + if tw.peek().Type == TokenOpenParen { + // this is a function, unwind 1 + tw.unPop() + funcNode.args = append(funcNode.args, newFunctionNode(tw)) + } + case TokenLiteralString, TokenLiteralInteger, TokenLiteralFloat: + funcNode.args = append(funcNode.args, expressionValue{token.Data}) + } + + } + return funcNode +} diff --git a/pkg/iac/scanners/azure/expressions/token_walker.go b/pkg/iac/scanners/azure/expressions/token_walker.go new file mode 100644 index 000000000000..d07a238d1bd9 --- /dev/null +++ b/pkg/iac/scanners/azure/expressions/token_walker.go @@ -0,0 +1,40 @@ +package expressions + +type tokenWalker struct { + tokens []Token + currentPosition int +} + +func newTokenWalker(tokens []Token) *tokenWalker { + return &tokenWalker{ + tokens: tokens, + currentPosition: 0, + } +} + +func (t *tokenWalker) peek() Token { + if t.currentPosition >= len(t.tokens) { + return Token{} + } + return t.tokens[t.currentPosition] +} + +func (t *tokenWalker) hasNext() bool { + return t.currentPosition+1 < len(t.tokens) +} + +func (t *tokenWalker) unPop() { + if t.currentPosition > 0 { + t.currentPosition-- + } +} + +func (t *tokenWalker) pop() *Token { + if !t.hasNext() { + return nil + } + + token := t.tokens[t.currentPosition] + t.currentPosition++ + return &token +} diff --git a/pkg/iac/scanners/azure/functions/add.go b/pkg/iac/scanners/azure/functions/add.go new file mode 100644 index 000000000000..9eb699e2eb9b --- /dev/null +++ b/pkg/iac/scanners/azure/functions/add.go @@ -0,0 +1,15 @@ +package functions + +func Add(args ...interface{}) interface{} { + + if len(args) != 2 { + return nil + } + + if a, ok := args[0].(int); ok { + if b, ok := args[1].(int); ok { + return a + b + } + } + return nil +} diff --git a/pkg/iac/scanners/azure/functions/add_test.go b/pkg/iac/scanners/azure/functions/add_test.go new file mode 100644 index 000000000000..b88e9b8ee1cc --- /dev/null +++ b/pkg/iac/scanners/azure/functions/add_test.go @@ -0,0 +1,38 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Add(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected int + }{ + { + name: "Add with 1 and 2", + args: []interface{}{1, 2}, + expected: 3, + }, + { + name: "Add with 2 and 3", + args: []interface{}{2, 3}, + expected: 5, + }, + { + name: "Add with 3 and -4", + args: []interface{}{3, -4}, + expected: -1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Add(tt.args...) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/and.go b/pkg/iac/scanners/azure/functions/and.go new file mode 100644 index 000000000000..67070b5c2cb0 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/and.go @@ -0,0 +1,27 @@ +package functions + +func And(args ...interface{}) interface{} { + + if len(args) <= 1 { + return false + } + + arg0, ok := args[0].(bool) + if !ok { + return false + } + + benchmark := arg0 + + for _, arg := range args[1:] { + arg1, ok := arg.(bool) + if !ok { + return false + } + if benchmark != arg1 { + return false + } + + } + return true +} diff --git a/pkg/iac/scanners/azure/functions/and_test.go b/pkg/iac/scanners/azure/functions/and_test.go new file mode 100644 index 000000000000..6814e9288ca0 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/and_test.go @@ -0,0 +1,39 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_And(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected bool + }{ + { + name: "And with same 2 bools", + args: []interface{}{true, true}, + expected: true, + }, + { + name: "And with same 3 bools", + args: []interface{}{true, true, true}, + expected: true, + }, + { + name: "And with different 4 bools", + args: []interface{}{true, true, false, true}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := And(tt.args...) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/array.go b/pkg/iac/scanners/azure/functions/array.go new file mode 100644 index 000000000000..a1da05ef4fdc --- /dev/null +++ b/pkg/iac/scanners/azure/functions/array.go @@ -0,0 +1,29 @@ +package functions + +func Array(args ...interface{}) interface{} { + + if len(args) != 1 { + return "" + } + + switch ctype := args[0].(type) { + case int: + return []int{ctype} + case string: + return []string{ctype} + case map[string]interface{}: + var result []interface{} + for k, v := range ctype { + result = append(result, k, v) + } + return result + case interface{}: + switch ctype := ctype.(type) { + case []string: + return ctype + case []interface{}: + return ctype + } + } + return []interface{}{} +} diff --git a/pkg/iac/scanners/azure/functions/array_test.go b/pkg/iac/scanners/azure/functions/array_test.go new file mode 100644 index 000000000000..c4a376ea6080 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/array_test.go @@ -0,0 +1,44 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Array(t *testing.T) { + test := []struct { + name string + input []interface{} + expected interface{} + }{ + { + name: "array from an int", + input: []interface{}{1}, + expected: []int{1}, + }, + { + name: "array from a string", + input: []interface{}{"hello"}, + expected: []string{"hello"}, + }, + { + name: "array from a map", + input: []interface{}{map[string]interface{}{"hello": "world"}}, + expected: []interface{}{"hello", "world"}, + }, + { + name: "array from an slice", + input: []interface{}{ + []string{"hello", "world"}, + }, + expected: []string{"hello", "world"}, + }, + } + for _, tt := range test { + t.Run(tt.name, func(t *testing.T) { + actual := Array(tt.input...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/base64.go b/pkg/iac/scanners/azure/functions/base64.go new file mode 100644 index 000000000000..c3222e7675ec --- /dev/null +++ b/pkg/iac/scanners/azure/functions/base64.go @@ -0,0 +1,52 @@ +package functions + +import ( + "encoding/base64" + "encoding/json" +) + +func Base64(args ...interface{}) interface{} { + + if len(args) == 0 { + return nil + } + + input := args[0].(string) + + return base64.StdEncoding.EncodeToString([]byte(input)) +} + +func Base64ToString(args ...interface{}) interface{} { + if len(args) == 0 { + return nil + } + + input := args[0].(string) + + result, err := base64.StdEncoding.DecodeString(input) + if err != nil { + return "" + } + return string(result) +} + +func Base64ToJson(args ...interface{}) interface{} { + + if len(args) == 0 { + return nil + } + + input := args[0].(string) + + decoded, err := base64.StdEncoding.DecodeString(input) + if err != nil { + return nil + } + + var result map[string]interface{} + + if err := json.Unmarshal(decoded, &result); err != nil { + return nil + } + return result +} diff --git a/pkg/iac/scanners/azure/functions/base64_test.go b/pkg/iac/scanners/azure/functions/base64_test.go new file mode 100644 index 000000000000..f557b277930c --- /dev/null +++ b/pkg/iac/scanners/azure/functions/base64_test.go @@ -0,0 +1,85 @@ +package functions + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Base64Call(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "simple base64 call", + args: []interface{}{ + "hello, world", + }, + expected: "aGVsbG8sIHdvcmxk", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Base64(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} + +func Test_Base64ToStringCall(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "simple base64ToString call", + args: []interface{}{ + "aGVsbG8sIHdvcmxk", + }, + expected: "hello, world", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Base64ToString(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} + +func Test_Base64ToJsonCall(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "simple base64ToJson call", + args: []interface{}{ + "eyJoZWxsbyI6ICJ3b3JsZCJ9", + }, + expected: `{"hello":"world"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Base64ToJson(tt.args...) + + actualContent, err := json.Marshal(actual) + require.NoError(t, err) + + assert.Equal(t, tt.expected, string(actualContent)) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/bool.go b/pkg/iac/scanners/azure/functions/bool.go new file mode 100644 index 000000000000..0221a5a4b8ee --- /dev/null +++ b/pkg/iac/scanners/azure/functions/bool.go @@ -0,0 +1,20 @@ +package functions + +import "strings" + +func Bool(args ...interface{}) interface{} { + if len(args) != 1 { + return false + } + + switch input := args[0].(type) { + case bool: + return input + case string: + input = strings.ToLower(input) + return input == "true" || input == "1" || input == "yes" || input == "on" + case int: + return input == 1 + } + return false +} diff --git a/pkg/iac/scanners/azure/functions/bool_test.go b/pkg/iac/scanners/azure/functions/bool_test.go new file mode 100644 index 000000000000..6c520a9380f8 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/bool_test.go @@ -0,0 +1,63 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Bool(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected bool + }{ + { + name: "Bool with true", + args: []interface{}{true}, + expected: true, + }, + { + name: "Bool with false", + args: []interface{}{false}, + expected: false, + }, + { + name: "Bool with 1", + args: []interface{}{1}, + expected: true, + }, + { + name: "Bool with 0", + args: []interface{}{0}, + expected: false, + }, + { + name: "Bool with true string", + args: []interface{}{"true"}, + expected: true, + }, + { + name: "Bool with false string", + args: []interface{}{"false"}, + expected: false, + }, + { + name: "Bool with 1 string", + args: []interface{}{"1"}, + expected: true, + }, + { + name: "Bool with 0 string", + args: []interface{}{"0"}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Bool(tt.args...) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/casing.go b/pkg/iac/scanners/azure/functions/casing.go new file mode 100644 index 000000000000..56a93bbd7a4b --- /dev/null +++ b/pkg/iac/scanners/azure/functions/casing.go @@ -0,0 +1,29 @@ +package functions + +import "strings" + +func ToLower(args ...interface{}) interface{} { + if len(args) != 1 { + return "" + } + + input, ok := args[0].(string) + if !ok { + return "" + } + + return strings.ToLower(input) +} + +func ToUpper(args ...interface{}) interface{} { + if len(args) != 1 { + return "" + } + + input, ok := args[0].(string) + if !ok { + return "" + } + + return strings.ToUpper(input) +} diff --git a/pkg/iac/scanners/azure/functions/casing_test.go b/pkg/iac/scanners/azure/functions/casing_test.go new file mode 100644 index 000000000000..51c970e1765e --- /dev/null +++ b/pkg/iac/scanners/azure/functions/casing_test.go @@ -0,0 +1,71 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_ToLower(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "lowercase a string", + args: []interface{}{ + "HELLO", + }, + expected: "hello", + }, + { + name: "lowercase a string with a non-string input", + args: []interface{}{ + 10, + }, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := ToLower(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} + +func Test_ToUpper(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "uppercase a string", + args: []interface{}{ + "hello", + }, + expected: "HELLO", + }, + { + name: "uppercase a string with a non-string input", + args: []interface{}{ + 10, + }, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := ToUpper(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/coalesce.go b/pkg/iac/scanners/azure/functions/coalesce.go new file mode 100644 index 000000000000..b7ec261450f7 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/coalesce.go @@ -0,0 +1,10 @@ +package functions + +func Coalesce(args ...interface{}) interface{} { + for _, arg := range args { + if arg != nil { + return arg + } + } + return nil +} diff --git a/pkg/iac/scanners/azure/functions/coalesce_test.go b/pkg/iac/scanners/azure/functions/coalesce_test.go new file mode 100644 index 000000000000..361914df64cd --- /dev/null +++ b/pkg/iac/scanners/azure/functions/coalesce_test.go @@ -0,0 +1,56 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Coalesce(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "coalesce with nil", + args: []interface{}{ + nil, + }, + expected: nil, + }, + { + name: "coalesce with nil and string", + args: []interface{}{ + nil, + "test", + }, + expected: "test", + }, + { + name: "coalesce with nil and string and int", + args: []interface{}{ + nil, + "test", + 1, + }, + expected: "test", + }, + { + name: "coalesce with nil and nil and array", + args: []interface{}{ + nil, + nil, + []interface{}{"a", "b", "c"}, + }, + expected: []interface{}{"a", "b", "c"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Coalesce(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/concat.go b/pkg/iac/scanners/azure/functions/concat.go new file mode 100644 index 000000000000..800db04be77d --- /dev/null +++ b/pkg/iac/scanners/azure/functions/concat.go @@ -0,0 +1,28 @@ +package functions + +import ( + "fmt" +) + +func Concat(args ...interface{}) interface{} { + + switch args[0].(type) { + case string: + var result string + for _, arg := range args { + result += fmt.Sprintf("%v", arg) + } + return result + case interface{}: + var result []interface{} + for _, arg := range args { + argArr, ok := arg.([]interface{}) + if !ok { + continue + } + result = append(result, argArr...) + } + return result + } + return "" +} diff --git a/pkg/iac/scanners/azure/functions/concat_test.go b/pkg/iac/scanners/azure/functions/concat_test.go new file mode 100644 index 000000000000..7b0c461c960d --- /dev/null +++ b/pkg/iac/scanners/azure/functions/concat_test.go @@ -0,0 +1,94 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_StringConcatenation(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "simple string concatenation", + args: []interface{}{ + "hello", + ", ", + "world", + "!", + }, + expected: "hello, world!", + }, + { + name: "string concatenation with non strings", + args: []interface{}{ + "pi to 3 decimal places is ", + 3.142, + }, + expected: "pi to 3 decimal places is 3.142", + }, + { + name: "string concatenation with multiple primitives", + args: []interface{}{ + "to say that ", + 3, + " is greater than ", + 5, + " would be ", + false, + }, + expected: "to say that 3 is greater than 5 would be false", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + concatenated := Concat(tt.args...) + require.Equal(t, tt.expected, concatenated) + }) + } +} + +func Test_ArrayConcatenation(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected []interface{} + }{ + { + name: "simple array concatenation", + args: []interface{}{ + []interface{}{1, 2, 3}, + []interface{}{4, 5, 6}, + }, + expected: []interface{}{1, 2, 3, 4, 5, 6}, + }, + { + name: "array concatenation with non arrays", + args: []interface{}{ + []interface{}{1, 2, 3}, + 4, + }, + expected: []interface{}{1, 2, 3}, + }, + { + name: "array concatenation with multiple primitives", + args: []interface{}{ + []interface{}{1, 2, 3}, + 4, + []interface{}{5, 6, 7}, + }, + expected: []interface{}{1, 2, 3, 5, 6, 7}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + concatenated := Concat(tt.args...) + require.Equal(t, tt.expected, concatenated) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/contains.go b/pkg/iac/scanners/azure/functions/contains.go new file mode 100644 index 000000000000..a067d63dfa85 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/contains.go @@ -0,0 +1,40 @@ +package functions + +import ( + "fmt" + "strings" +) + +func Contains(args ...interface{}) interface{} { + + if len(args) != 2 { + return false + } + + container := args[0] + itemToFind := args[1] + + switch cType := container.(type) { + case string: + switch iType := itemToFind.(type) { + case string: + return strings.Contains(strings.ToLower(cType), strings.ToLower(iType)) + case int, int32, int64, uint, uint32, uint64: + return strings.Contains(strings.ToLower(cType), fmt.Sprintf("%d", iType)) + } + case []interface{}: + for _, item := range cType { + if item == itemToFind { + return true + } + } + case map[string]interface{}: + for key := range cType { + if key == itemToFind { + return true + } + } + } + + return false +} diff --git a/pkg/iac/scanners/azure/functions/contains_test.go b/pkg/iac/scanners/azure/functions/contains_test.go new file mode 100644 index 000000000000..e92f08fd5462 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/contains_test.go @@ -0,0 +1,95 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Contains(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected bool + }{ + { + name: "simple true string contains", + args: []interface{}{ + "hello, world", + "hell", + }, + expected: true, + }, + { + name: "simple false string contains", + args: []interface{}{ + "hello, world", + "help", + }, + expected: false, + }, + { + name: "simple true string contains with case sensitivity", + args: []interface{}{ + "hello, world", + "HELL", + }, + expected: true, + }, + { + name: "simple true string contains with number", + args: []interface{}{ + "You're my number 1", + 1, + }, + expected: true, + }, + { + name: "true object contains key", + args: []interface{}{ + map[string]interface{}{ + "hello": "world", + }, + "hello", + }, + expected: true, + }, + { + name: "false object contains key", + args: []interface{}{ + map[string]interface{}{ + "hello": "world", + }, + "world", + }, + expected: false, + }, + { + name: "true array contains value", + args: []interface{}{ + []interface{}{ + "hello", "world", + }, + "hello", + }, + expected: true, + }, + { + name: "false array contains value", + args: []interface{}{ + []interface{}{ + "hello", "world", + }, + "help", + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doesContain := Contains(tt.args...) + require.Equal(t, tt.expected, doesContain) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/copy_index.go b/pkg/iac/scanners/azure/functions/copy_index.go new file mode 100644 index 000000000000..d1289cc0a20d --- /dev/null +++ b/pkg/iac/scanners/azure/functions/copy_index.go @@ -0,0 +1,25 @@ +package functions + +var loopCounter = make(map[string]int) + +func CopyIndex(args ...interface{}) interface{} { + loopName := "default" + offset := 1 + if len(args) > 0 { + if providedLoopName, ok := args[0].(string); ok { + loopName = providedLoopName + } + } + if len(args) > 1 { + if providedOffset, ok := args[1].(int); ok { + offset = providedOffset + } + } + + if _, ok := loopCounter[loopName]; !ok { + loopCounter[loopName] = 0 + } + + loopCounter[loopName] += offset + return loopCounter[loopName] +} diff --git a/pkg/iac/scanners/azure/functions/copy_index_test.go b/pkg/iac/scanners/azure/functions/copy_index_test.go new file mode 100644 index 000000000000..041b258ca8cf --- /dev/null +++ b/pkg/iac/scanners/azure/functions/copy_index_test.go @@ -0,0 +1,52 @@ +package functions + +import "testing" + +func Test_CopyIndex(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected int + }{ + { + name: "CopyIndex with 1", + args: []interface{}{}, + expected: 1, + }, + { + name: "CopyIndex with 2", + args: []interface{}{}, + expected: 2, + }, + { + name: "CopyIndex with 3", + args: []interface{}{}, + expected: 3, + }, + { + name: "CopyIndex with loopName", + args: []interface{}{"loop1"}, + expected: 1, + }, + { + name: "CopyIndex with same lo" + + "opName", + args: []interface{}{"loop1"}, + expected: 2, + }, + { + name: "CopyIndex with loopName", + args: []interface{}{"loop2", 10}, + expected: 10, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := CopyIndex(tt.args...) + if got != tt.expected { + t.Errorf("CopyIndex() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/create_array.go b/pkg/iac/scanners/azure/functions/create_array.go new file mode 100644 index 000000000000..99f3558847a1 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/create_array.go @@ -0,0 +1,11 @@ +package functions + +func CreateArray(args ...interface{}) interface{} { + var result []interface{} + if len(args) == 0 { + return result + } + + result = append(result, args...) + return result +} diff --git a/pkg/iac/scanners/azure/functions/create_array_test.go b/pkg/iac/scanners/azure/functions/create_array_test.go new file mode 100644 index 000000000000..5e63074888cb --- /dev/null +++ b/pkg/iac/scanners/azure/functions/create_array_test.go @@ -0,0 +1,68 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_CreateArray(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "create array with strings", + args: []interface{}{ + "Hello", + "World", + }, + expected: []interface{}{"Hello", "World"}, + }, + { + name: "create array with ints", + + args: []interface{}{ + 1, 2, 3, + }, + expected: []interface{}{1, 2, 3}, + }, + { + name: "create array with arrays", + args: []interface{}{ + []interface{}{1, 2, 3}, + []interface{}{4, 5, 6}, + }, + expected: []interface{}{[]interface{}{1, 2, 3}, []interface{}{4, 5, 6}}, + }, + { + name: "create arrau with maps", + args: []interface{}{ + map[string]interface{}{ + "one": "a", + }, + map[string]interface{}{ + "two": "b", + }, + }, + expected: []interface{}{ + map[string]interface{}{ + "one": "a", + }, + map[string]interface{}{ + "two": "b", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := CreateArray(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/create_object.go b/pkg/iac/scanners/azure/functions/create_object.go new file mode 100644 index 000000000000..30dc239847f8 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/create_object.go @@ -0,0 +1,21 @@ +package functions + +func CreateObject(args ...interface{}) interface{} { + obj := make(map[string]interface{}) + if len(args) == 0 { + return obj + } + + // if there aren't even pairs then return an empty object + if len(args)%2 != 0 { + return obj + } + + for i := 0; i < len(args); i += 2 { + key := args[i].(string) + value := args[i+1] + obj[key] = value + } + + return obj +} diff --git a/pkg/iac/scanners/azure/functions/create_object_test.go b/pkg/iac/scanners/azure/functions/create_object_test.go new file mode 100644 index 000000000000..f695e38410fe --- /dev/null +++ b/pkg/iac/scanners/azure/functions/create_object_test.go @@ -0,0 +1,60 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_CreateObject(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "CreateObject with no args", + args: []interface{}{}, + expected: map[string]interface{}{}, + }, + { + name: "CreateObject with one arg", + args: []interface{}{"foo", "bar"}, + expected: map[string]interface{}{"foo": "bar"}, + }, + { + name: "CreateObject with two args", + args: []interface{}{"foo", "bar", "baz", "qux"}, + expected: map[string]interface{}{"foo": "bar", "baz": "qux"}, + }, + { + name: "CreateObject with three args", + args: []interface{}{"foo", "bar", "baz", 1, "quux", true}, + expected: map[string]interface{}{"foo": "bar", "baz": 1, "quux": true}, + }, + { + name: "CreateObject with odd number of args", + args: []interface{}{"foo", "bar", "baz"}, + expected: map[string]interface{}{}, + }, + { + name: "CreateObject with odd number of args", + args: []interface{}{"foo", "bar", "baz", []string{"Hello", "World"}}, + expected: map[string]interface{}{ + "foo": "bar", + "baz": []string{ + "Hello", "World", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := CreateObject(tt.args...) + assert.Equal(t, tt.expected, got) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/data_uri.go b/pkg/iac/scanners/azure/functions/data_uri.go new file mode 100644 index 000000000000..50f0835ee6ad --- /dev/null +++ b/pkg/iac/scanners/azure/functions/data_uri.go @@ -0,0 +1,36 @@ +package functions + +import ( + "fmt" + "strings" +) + +func DataUri(args ...interface{}) interface{} { + if len(args) == 0 { + return "" + } + + input, ok := args[0].(string) + if !ok { + return "" + } + + return fmt.Sprintf("data:text/plain;charset=utf8;base64,%s", Base64(input)) +} + +func DataUriToString(args ...interface{}) interface{} { + if len(args) == 0 { + return "" + } + + input, ok := args[0].(string) + if !ok { + return "" + } + parts := strings.Split(input, "base64,") + if len(parts) != 2 { + return "" + } + + return Base64ToString(parts[1]) +} diff --git a/pkg/iac/scanners/azure/functions/data_uri_test.go b/pkg/iac/scanners/azure/functions/data_uri_test.go new file mode 100644 index 000000000000..04f92249e093 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/data_uri_test.go @@ -0,0 +1,53 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_data_uri_from_string(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "data uri from string", + args: []interface{}{ + "Hello", + }, + expected: "data:text/plain;charset=utf8;base64,SGVsbG8=", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dataUri := DataUri(tt.args...) + require.Equal(t, tt.expected, dataUri) + }) + } +} + +func Test_string_from_data_uri(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "data uri to string", + args: []interface{}{ + "data:;base64,SGVsbG8sIFdvcmxkIQ==", + }, + expected: "Hello, World!", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dataUri := DataUriToString(tt.args...) + require.Equal(t, tt.expected, dataUri) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/date_time_add.go b/pkg/iac/scanners/azure/functions/date_time_add.go new file mode 100644 index 000000000000..c3b902b08965 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/date_time_add.go @@ -0,0 +1,115 @@ +package functions + +import ( + "fmt" + "regexp" + "strconv" + "time" +) + +var pattern = regexp.MustCompile(`^P((?P\d+)Y)?((?P\d+)M)?((?P\d+)W)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?)?$`) + +func DateTimeAdd(args ...interface{}) interface{} { + if len(args) < 2 { + return nil + } + + base, ok := args[0].(string) + if !ok { + return nil + } + + format := time.RFC3339 + if len(args) == 3 { + if providedFormat, ok := args[2].(string); ok { + format = convertFormat(providedFormat) + } + + } + + baseTime, err := time.Parse(format, base) + if err != nil { + return nil + } + + duration, err := parseISO8601(args[1].(string)) + if err != nil { + return nil + } + + timeDuration := duration.timeDuration() + baseTime = baseTime.Add(timeDuration) + + if ok { + return baseTime.Format(format) + } + + return baseTime.Format(time.RFC3339) +} + +type Iso8601Duration struct { + Y int + M int + W int + D int + // Time Component + TH int + TM int + TS int +} + +func parseISO8601(from string) (Iso8601Duration, error) { + var match []string + var d Iso8601Duration + + if pattern.MatchString(from) { + match = pattern.FindStringSubmatch(from) + } else { + return d, fmt.Errorf("could not parse duration string") + } + + for i, name := range pattern.SubexpNames() { + part := match[i] + if i == 0 || name == "" || part == "" { + continue + } + + val, err := strconv.Atoi(part) + if err != nil { + return d, err + } + switch name { + case "year": + d.Y = val + case "month": + d.M = val + case "week": + d.W = val + case "day": + d.D = val + case "hour": + d.TH = val + case "minute": + d.TM = val + case "second": + d.TS = val + default: + return d, fmt.Errorf("unknown field %s", name) + } + } + + return d, nil +} + +func (d Iso8601Duration) timeDuration() time.Duration { + var dur time.Duration + dur += time.Duration(d.TH) * time.Hour + dur += time.Duration(d.TM) * time.Minute + dur += time.Duration(d.TS) * time.Second + dur += time.Duration(d.D) * 24 * time.Hour + dur += time.Duration(d.W) * 7 * 24 * time.Hour + dur += time.Duration(d.M) * 30 * 24 * time.Hour + dur += time.Duration(d.Y) * 365 * 24 * time.Hour + + return dur +} diff --git a/pkg/iac/scanners/azure/functions/date_time_epoch.go b/pkg/iac/scanners/azure/functions/date_time_epoch.go new file mode 100644 index 000000000000..9b1802573269 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/date_time_epoch.go @@ -0,0 +1,38 @@ +package functions + +import ( + "time" + + smithyTime "github.com/aws/smithy-go/time" +) + +func DateTimeFromEpoch(args ...interface{}) interface{} { + if len(args) != 1 { + return nil + } + + epoch, ok := args[0].(int) + if !ok { + return nil + } + + return smithyTime.ParseEpochSeconds(float64(epoch)).Format(time.RFC3339) +} + +func DateTimeToEpoch(args ...interface{}) interface{} { + if len(args) != 1 { + return nil + } + + dateTime, ok := args[0].(string) + if !ok { + return nil + } + + parsed, err := time.Parse(time.RFC3339, dateTime) + if err != nil { + return nil + } + + return int(parsed.Unix()) +} diff --git a/pkg/iac/scanners/azure/functions/date_time_epoch_test.go b/pkg/iac/scanners/azure/functions/date_time_epoch_test.go new file mode 100644 index 000000000000..6cdf7a0442bd --- /dev/null +++ b/pkg/iac/scanners/azure/functions/date_time_epoch_test.go @@ -0,0 +1,51 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_DateTimeFromEpoch(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "datetime from epoch", + args: []interface{}{ + 1683040573, + }, + expected: "2023-05-02T15:16:13Z", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := DateTimeFromEpoch(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} + +func Test_DateTimeToEpoch(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "datetime to epoch", + args: []interface{}{ + "2023-05-02T15:16:13Z", + }, + expected: 1683040573, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := DateTimeToEpoch(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/datetime_add_test.go b/pkg/iac/scanners/azure/functions/datetime_add_test.go new file mode 100644 index 000000000000..b5c09d04a742 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/datetime_add_test.go @@ -0,0 +1,72 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_DateTimeAdd(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + + { + name: "datetime add 1 years", + args: []interface{}{ + "2010-01-01T00:00:00Z", + "P1Y", + }, + expected: "2011-01-01T00:00:00Z", + }, + { + name: "datetime add 3 months", + args: []interface{}{ + "2010-01-01T00:00:00Z", + "P3M", + }, + expected: "2010-04-01T00:00:00Z", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := DateTimeAdd(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} + +func Test_ISO8601DurationParse(t *testing.T) { + tests := []struct { + name string + args string + expected Iso8601Duration + }{ + + { + name: "parse 1 year", + args: "P1Y", + expected: Iso8601Duration{Y: 1}, + }, + { + name: "parse 3 months", + args: "P3M", + expected: Iso8601Duration{M: 3}, + }, + { + name: "parse 12 hours", + args: "PT12H", + expected: Iso8601Duration{TH: 12}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual, err := parseISO8601(tt.args) + require.NoError(t, err) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/deployment.go b/pkg/iac/scanners/azure/functions/deployment.go new file mode 100644 index 000000000000..afafb2b3587c --- /dev/null +++ b/pkg/iac/scanners/azure/functions/deployment.go @@ -0,0 +1,75 @@ +package functions + +type DeploymentData interface { + GetParameter(name string) interface{} + GetVariable(variableName string) interface{} + GetEnvVariable(envVariableName string) interface{} +} + +func Deployment(deploymentProvider DeploymentData, args ...interface{}) interface{} { + + /* + + { + "name": "", + "properties": { + "templateLink": { + "uri": "" + }, + "template": { + "$schema": "", + "contentVersion": "", + "parameters": {}, + "variables": {}, + "resources": [], + "outputs": {} + }, + "templateHash": "", + "parameters": {}, + "mode": "", + "provisioningState": "" + } + } + + */ + + return nil +} + +func Environment(envProvider DeploymentData, args ...interface{}) interface{} { + if len(args) == 0 { + return nil + } + + envVarName, ok := args[0].(string) + if !ok { + return nil + } + return envProvider.GetEnvVariable(envVarName) +} + +func Variables(varProvider DeploymentData, args ...interface{}) interface{} { + if len(args) == 0 { + return nil + } + + varName, ok := args[0].(string) + if !ok { + return nil + } + return varProvider.GetVariable(varName) +} + +func Parameters(paramProvider DeploymentData, args ...interface{}) interface{} { + if len(args) == 0 { + return nil + } + + paramName, ok := args[0].(string) + if !ok { + return nil + } + + return paramProvider.GetParameter(paramName) + +} diff --git a/pkg/iac/scanners/azure/functions/div.go b/pkg/iac/scanners/azure/functions/div.go new file mode 100644 index 000000000000..9de0dfb05f73 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/div.go @@ -0,0 +1,15 @@ +package functions + +func Div(args ...interface{}) interface{} { + + if len(args) != 2 { + return nil + } + + if a, ok := args[0].(int); ok { + if b, ok := args[1].(int); ok { + return a / b + } + } + return nil +} diff --git a/pkg/iac/scanners/azure/functions/div_test.go b/pkg/iac/scanners/azure/functions/div_test.go new file mode 100644 index 000000000000..49166190fb5d --- /dev/null +++ b/pkg/iac/scanners/azure/functions/div_test.go @@ -0,0 +1,38 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Div(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected int + }{ + { + name: "Div 2 by 1", + args: []interface{}{2, 1}, + expected: 2, + }, + { + name: "Div 4 by 2", + args: []interface{}{4, 2}, + expected: 2, + }, + { + name: "Div 6 by 2", + args: []interface{}{6, 2}, + expected: 3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Div(tt.args...) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/empty.go b/pkg/iac/scanners/azure/functions/empty.go new file mode 100644 index 000000000000..1dbe8396f7c3 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/empty.go @@ -0,0 +1,33 @@ +package functions + +func Empty(args ...interface{}) interface{} { + + if len(args) != 1 { + return false + } + + container := args[0] + + switch cType := container.(type) { + case string: + return cType == "" + case map[string]interface{}: + return len(cType) == 0 + case interface{}: + switch iType := cType.(type) { + case []string: + return len(iType) == 0 + case []bool: + return len(iType) == 0 + case []int: + return len(iType) == 0 + case []float64: + return len(iType) == 0 + case map[string]interface{}: + return len(iType) == 0 + } + + } + + return false +} diff --git a/pkg/iac/scanners/azure/functions/empty_test.go b/pkg/iac/scanners/azure/functions/empty_test.go new file mode 100644 index 000000000000..a21fb96cd8cd --- /dev/null +++ b/pkg/iac/scanners/azure/functions/empty_test.go @@ -0,0 +1,68 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Empty(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected bool + }{ + { + name: "string is empty", + args: []interface{}{ + "", + }, + expected: true, + }, + { + name: "string is not empty", + args: []interface{}{ + "hello, world", + }, + expected: false, + }, + { + name: "array is empty", + args: []interface{}{ + []string{}, + }, + expected: true, + }, + { + name: "array is not empty", + args: []interface{}{ + []string{"Hello", "World"}, + }, + expected: false, + }, + { + name: "map is empty", + args: []interface{}{ + map[string]interface{}{}, + }, + expected: true, + }, + { + name: "map is not empty", + args: []interface{}{ + map[string]interface{}{ + "hello": "world", + }, + "world", + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doesContain := Empty(tt.args...) + require.Equal(t, tt.expected, doesContain) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/ends_with.go b/pkg/iac/scanners/azure/functions/ends_with.go new file mode 100644 index 000000000000..2bcd66217ecb --- /dev/null +++ b/pkg/iac/scanners/azure/functions/ends_with.go @@ -0,0 +1,22 @@ +package functions + +import "strings" + +func EndsWith(args ...interface{}) interface{} { + + if len(args) != 2 { + return false + } + + stringToSearch, ok := args[0].(string) + if !ok { + return false + } + + stringToFind, ok := args[1].(string) + if !ok { + return false + } + + return strings.HasSuffix(stringToSearch, stringToFind) +} diff --git a/pkg/iac/scanners/azure/functions/ends_with_test.go b/pkg/iac/scanners/azure/functions/ends_with_test.go new file mode 100644 index 000000000000..b1d1900ba0d2 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/ends_with_test.go @@ -0,0 +1,41 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_EndsWith(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected bool + }{ + { + name: "string ends with", + args: []interface{}{ + "Hello world!", + "world!", + }, + expected: true, + }, + { + name: "string does not end with", + args: []interface{}{ + "Hello world!", + "world", + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := EndsWith(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/equals.go b/pkg/iac/scanners/azure/functions/equals.go new file mode 100644 index 000000000000..ca5174144cb8 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/equals.go @@ -0,0 +1,25 @@ +package functions + +func Equals(args ...interface{}) interface{} { + if len(args) != 2 { + return false + } + + slice1, ok := args[0].([]interface{}) + if ok { + slice2, ok := args[1].([]interface{}) + if ok { + if len(slice1) != len(slice2) { + return false + } + for i := 0; i < len(slice1); i++ { + if slice1[i] != slice2[i] { + return false + } + } + return true + } + } + + return args[0] == args[1] +} diff --git a/pkg/iac/scanners/azure/functions/equals_test.go b/pkg/iac/scanners/azure/functions/equals_test.go new file mode 100644 index 000000000000..e9ad7f03f7c7 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/equals_test.go @@ -0,0 +1,111 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Equals(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "equals with nil", + args: []interface{}{ + nil, + }, + expected: false, + }, + { + name: "equals with nil and string", + args: []interface{}{ + nil, + "test", + }, + expected: false, + }, + { + name: "equals with nil and string and int", + args: []interface{}{ + nil, + "test", + 1, + }, + expected: false, + }, + { + name: "equals with nil and nil and array", + args: []interface{}{ + nil, + nil, + []interface{}{"a", "b", "c"}, + }, + expected: false, + }, + { + name: "equals with nil and nil", + args: []interface{}{ + nil, + nil, + }, + expected: true, + }, + { + name: "equals with string and string", + args: []interface{}{ + "test", + "test", + }, + expected: true, + }, + { + name: "equals with string and string", + args: []interface{}{ + "test", + "test1", + }, + expected: false, + }, + { + name: "equals with int and int", + args: []interface{}{ + 1, + 1, + }, + expected: true, + }, + { + name: "equals with int and int", + args: []interface{}{ + 1, + 2, + }, + expected: false, + }, + { + name: "equals with array and array", + args: []interface{}{ + []interface{}{"a", "b", "c"}, + []interface{}{"a", "b", "c"}, + }, + expected: true, + }, + { + name: "equals with array and array", + args: []interface{}{ + []interface{}{"a", "b", "c"}, + []interface{}{"a", "b", "d"}, + }, + expected: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Equals(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/false.go b/pkg/iac/scanners/azure/functions/false.go new file mode 100644 index 000000000000..26309e333812 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/false.go @@ -0,0 +1,5 @@ +package functions + +func False(args ...interface{}) interface{} { + return false +} diff --git a/pkg/iac/scanners/azure/functions/first.go b/pkg/iac/scanners/azure/functions/first.go new file mode 100644 index 000000000000..3415b453ffe3 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/first.go @@ -0,0 +1,37 @@ +package functions + +func First(args ...interface{}) interface{} { + if len(args) != 1 { + return "" + } + + container := args[0] + + switch cType := container.(type) { + case string: + if len(cType) > 0 { + return string(cType[0]) + } + case interface{}: + switch iType := cType.(type) { + case []string: + if len(iType) > 0 { + return iType[0] + } + case []bool: + if len(iType) > 0 { + return iType[0] + } + case []int: + if len(iType) > 0 { + return iType[0] + } + case []float64: + if len(iType) > 0 { + return iType[0] + } + } + } + + return "" +} diff --git a/pkg/iac/scanners/azure/functions/first_test.go b/pkg/iac/scanners/azure/functions/first_test.go new file mode 100644 index 000000000000..5ce059750184 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/first_test.go @@ -0,0 +1,51 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_First(t *testing.T) { + test := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "first in empty string", + args: []interface{}{ + "", + }, + expected: "", + }, + { + name: "first in string", + args: []interface{}{ + "Hello", + }, + expected: "H", + }, + { + name: "first in empty slice", + args: []interface{}{ + []string{}, + }, + expected: "", + }, + { + name: "first in slice", + args: []interface{}{ + []string{"Hello", "World"}, + }, + expected: "Hello", + }, + } + + for _, tt := range test { + t.Run(tt.name, func(t *testing.T) { + actual := First(tt.args...) + require.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/float.go b/pkg/iac/scanners/azure/functions/float.go new file mode 100644 index 000000000000..512b471b9421 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/float.go @@ -0,0 +1,20 @@ +package functions + +import "strconv" + +func Float(args ...interface{}) interface{} { + if len(args) != 1 { + return 0.0 + } + if a, ok := args[0].(int); ok { + return float64(a) + } + if a, ok := args[0].(string); ok { + f, err := strconv.ParseFloat(a, 64) + if err != nil { + return 0.0 + } + return f + } + return 0.0 +} diff --git a/pkg/iac/scanners/azure/functions/float_test.go b/pkg/iac/scanners/azure/functions/float_test.go new file mode 100644 index 000000000000..a7f5f84a8c20 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/float_test.go @@ -0,0 +1,36 @@ +package functions + +import "testing" + +func Test_Float(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected float64 + }{ + { + name: "Float with 1", + args: []interface{}{1}, + expected: 1.0, + }, + { + name: "Float with 2", + args: []interface{}{"2"}, + expected: 2.0, + }, + { + name: "Float with 3", + args: []interface{}{"2.3"}, + expected: 2.3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Float(tt.args...) + if got != tt.expected { + t.Errorf("Float() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/format.go b/pkg/iac/scanners/azure/functions/format.go new file mode 100644 index 000000000000..207b9ebfdda7 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/format.go @@ -0,0 +1,31 @@ +package functions + +import ( + "fmt" + "strings" +) + +func Format(args ...interface{}) interface{} { + formatter := generateFormatterString(args...) + + return fmt.Sprintf(formatter, args[1:]...) +} + +func generateFormatterString(args ...interface{}) string { + + formatter, ok := args[0].(string) + if !ok { + return "" + } + for i, arg := range args[1:] { + switch arg.(type) { + case string: + formatter = strings.ReplaceAll(formatter, fmt.Sprintf("{%d}", i), "%s") + case int, int32, int64, uint, uint32, uint64: + formatter = strings.ReplaceAll(formatter, fmt.Sprintf("{%d}", i), "%d") + case float64, float32: + formatter = strings.ReplaceAll(formatter, fmt.Sprintf("{%d}", i), "%f") + } + } + return formatter +} diff --git a/pkg/iac/scanners/azure/functions/format_test.go b/pkg/iac/scanners/azure/functions/format_test.go new file mode 100644 index 000000000000..8d5e840c61a6 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/format_test.go @@ -0,0 +1,42 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_FormatCall(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "simple format call", + args: []interface{}{ + "{0}/{1}", + "myPostgreSQLServer", + "log_checkpoints", + }, + expected: "myPostgreSQLServer/log_checkpoints", + }, + { + name: "complex format call", + args: []interface{}{ + "{0} + {1} = {2}", + 1, 2, 3, + }, + expected: "1 + 2 = 3", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Format(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/functions.go b/pkg/iac/scanners/azure/functions/functions.go new file mode 100644 index 000000000000..f4ed7815f485 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/functions.go @@ -0,0 +1,99 @@ +package functions + +var deploymentFuncs = map[string]func(dp DeploymentData, args ...interface{}) interface{}{ + "parameters": Parameters, + "deployment": Deployment, + "environment": Environment, + "variables": Variables, +} +var generalFuncs = map[string]func(...interface{}) interface{}{ + + "add": Add, + "and": And, + "array": Array, + "base64": Base64, + "base64ToJson": Base64ToJson, + "bool": Bool, + "coalesce": Coalesce, + "concat": Concat, + "contains": Contains, + "copyIndex": CopyIndex, + "createArray": CreateArray, + "createObject": CreateObject, + "dataUri": DataUri, + "dataUriToString": DataUriToString, + "dateTimeAdd": DateTimeAdd, + "dateTimeFromEpoch": DateTimeFromEpoch, + "dateTimeToEpoch": DateTimeToEpoch, + "div": Div, + "empty": Empty, + "endsWith": EndsWith, + "equals": Equals, + "extensionResourceId": ExtensionResourceID, + "false": False, + "float": Float, + "format": Format, + "greater": Greater, + "greaterOrEquals": GreaterOrEquals, + "guid": Guid, + "if": If, + "indexOf": IndexOf, + "int": Int, + "intersection": Intersection, + "items": Items, + "join": Join, + "lastIndexOf": LastIndexOf, + "length": Length, + "less": Less, + "lessOrEquals": LessOrEquals, + // "list": List, + "managementGroup": ManagementGroup, + "managementGroupResourceId": ManagementGroupResourceID, + "max": Max, + "min": Min, + "mod": Mod, + "mul": Mul, + "newGuid": NewGuid, + "not": Not, + "null": Null, + "or": Or, + "padLeft": PadLeft, + "pickZones": PickZones, + "range": Range, + "reference": Reference, + "replace": Replace, + "resourceGroup": ResourceGroup, + "resourceId": ResourceID, + "skip": Skip, + "split": Split, + "startsWith": StartsWith, + "string": String, + "sub": Sub, + "subscription": Subscription, + "subscriptionResourceId": SubscriptionResourceID, + "substring": SubString, + "tenant": Tenant, + "tenantResourceId": TenantResourceID, + "toLower": ToLower, + "toUpper": ToUpper, + "trim": Trim, + "true": True, + "union": Union, + "union:": Union, + "uniqueString": UniqueString, + "uri": Uri, + "utcNow": UTCNow, +} + +func Evaluate(deploymentProvider DeploymentData, name string, args ...interface{}) interface{} { + + if f, ok := deploymentFuncs[name]; ok { + return f(deploymentProvider, args...) + } + + if f, ok := generalFuncs[name]; ok { + return f(args...) + } + + return nil +} diff --git a/pkg/iac/scanners/azure/functions/greater.go b/pkg/iac/scanners/azure/functions/greater.go new file mode 100644 index 000000000000..24bf79834641 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/greater.go @@ -0,0 +1,47 @@ +package functions + +func Greater(args ...interface{}) interface{} { + + if len(args) != 2 { + return false + } + + switch arg0 := args[0].(type) { + case int: + arg1, ok := args[1].(int) + if ok { + return arg0 > arg1 + } + case string: + arg1, ok := args[1].(string) + if ok { + return arg0 > arg1 + } + } + + return false +} + +func GreaterOrEquals(args ...interface{}) interface{} { + + if len(args) != 2 { + return false + } + + switch arg0 := args[0].(type) { + case nil: + return args[1] == nil + case int: + arg1, ok := args[1].(int) + if ok { + return arg0 >= arg1 + } + case string: + arg1, ok := args[1].(string) + if ok { + return arg0 >= arg1 + } + } + + return false +} diff --git a/pkg/iac/scanners/azure/functions/greater_test.go b/pkg/iac/scanners/azure/functions/greater_test.go new file mode 100644 index 000000000000..8d3e1b21b25e --- /dev/null +++ b/pkg/iac/scanners/azure/functions/greater_test.go @@ -0,0 +1,119 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Greater(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + + { + name: "greater with nil and string", + args: []interface{}{ + nil, + "test", + }, + expected: false, + }, + { + name: "greater with nil and nil", + args: []interface{}{ + nil, + nil, + }, + expected: false, + }, + { + name: "greater with string and string", + args: []interface{}{ + "test", + "test", + }, + expected: false, + }, + { + name: "greater with string and int", + args: []interface{}{ + "test", + 1, + }, + expected: false, + }, + { + name: "greater with int and int", + args: []interface{}{ + 1, + 1, + }, + expected: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Greater(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} + +func Test_GreaterThanOrEqual(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + + { + name: "greater with nil and string", + args: []interface{}{ + nil, + "test", + }, + expected: false, + }, + { + name: "greater with nil and nil", + args: []interface{}{ + nil, + nil, + }, + expected: true, + }, + { + name: "greater with string and string", + args: []interface{}{ + "test", + "test", + }, + expected: true, + }, + { + name: "greater with string and int", + args: []interface{}{ + "test", + 1, + }, + expected: false, + }, + { + name: "greater with int and int", + args: []interface{}{ + 1, + 1, + }, + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := GreaterOrEquals(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/guid.go b/pkg/iac/scanners/azure/functions/guid.go new file mode 100644 index 000000000000..d54bbacb1beb --- /dev/null +++ b/pkg/iac/scanners/azure/functions/guid.go @@ -0,0 +1,44 @@ +package functions + +import ( + "crypto/sha256" + "strings" + + "github.com/google/uuid" +) + +func Guid(args ...interface{}) interface{} { + + if len(args) == 0 { + return "" + } + + hashParts := make([]string, len(args)) + for i, str := range args { + hashParts[i] = str.(string) + } + + guid, err := generateSeededGUID(hashParts...) + if err != nil { + return "" + } + + return guid.String() +} + +func generateSeededGUID(seedParts ...string) (uuid.UUID, error) { + var id uuid.UUID + + stringToHash := strings.Join(seedParts, "") + + hsha2 := sha256.Sum256([]byte(stringToHash)) + + copy(id[:], hsha2[:16]) + id[6] = (id[6] & 0x0f) | 0x40 // Version 4 + id[8] = (id[8] & 0x3f) | 0x80 // Variant is 10 + return id, nil +} + +func NewGuid(args ...interface{}) interface{} { + return uuid.NewString() +} diff --git a/pkg/iac/scanners/azure/functions/guid_test.go b/pkg/iac/scanners/azure/functions/guid_test.go new file mode 100644 index 000000000000..0e47e5383a54 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/guid_test.go @@ -0,0 +1,35 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Guid(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "guid from a string", + args: []interface{}{ + "hello", + }, + expected: "2cf24dba-5fb0-430e-a6e8-3b2ac5b9e29e", + }, + { + name: "guid from an string", + args: []interface{}{}, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + guid := Guid(tt.args...) + require.Equal(t, tt.expected, guid) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/if.go b/pkg/iac/scanners/azure/functions/if.go new file mode 100644 index 000000000000..03fd35e360ff --- /dev/null +++ b/pkg/iac/scanners/azure/functions/if.go @@ -0,0 +1,15 @@ +package functions + +func If(args ...interface{}) interface{} { + + if len(args) != 3 { + return nil + } + + if condition, ok := args[0].(bool); ok { + if condition { + return args[1] + } + } + return args[2] +} diff --git a/pkg/iac/scanners/azure/functions/if_test.go b/pkg/iac/scanners/azure/functions/if_test.go new file mode 100644 index 000000000000..52c645fb30aa --- /dev/null +++ b/pkg/iac/scanners/azure/functions/if_test.go @@ -0,0 +1,44 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_If(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "If with true", + args: []interface{}{true, "true", "false"}, + expected: "true", + }, + { + name: "If with false", + args: []interface{}{false, "true", "false"}, + expected: "false", + }, + { + name: "If with true and slice returned", + args: []interface{}{ + true, + []interface{}{"Hello", "World"}, + []interface{}{"Goodbye", "World"}, + }, + expected: []interface{}{"Hello", "World"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := If(tt.args...) + assert.Equal(t, tt.expected, got) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/index_of.go b/pkg/iac/scanners/azure/functions/index_of.go new file mode 100644 index 000000000000..93896e21e897 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/index_of.go @@ -0,0 +1,22 @@ +package functions + +import "strings" + +func IndexOf(args ...interface{}) interface{} { + + if len(args) != 2 { + return -1 + } + + stringToSearch, ok := args[0].(string) + if !ok { + return -1 + } + + stringToFind, ok := args[1].(string) + if !ok { + return -1 + } + + return strings.Index(stringToSearch, stringToFind) +} diff --git a/pkg/iac/scanners/azure/functions/index_of_test.go b/pkg/iac/scanners/azure/functions/index_of_test.go new file mode 100644 index 000000000000..c35d59279942 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/index_of_test.go @@ -0,0 +1,48 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_IndexOf(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected int + }{ + { + name: "get index of string that is there", + args: []interface{}{ + "Hello world!", + "Hell", + }, + expected: 0, + }, + { + name: "get index of string that is there as well", + args: []interface{}{ + "Hello world!", + "world", + }, + expected: 6, + }, + { + name: "get index of string that isn't there", + args: []interface{}{ + "Hello world!", + "planet!", + }, + expected: -1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := IndexOf(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/int.go b/pkg/iac/scanners/azure/functions/int.go new file mode 100644 index 000000000000..f873a29fb0bf --- /dev/null +++ b/pkg/iac/scanners/azure/functions/int.go @@ -0,0 +1,20 @@ +package functions + +import "strconv" + +func Int(args ...interface{}) interface{} { + if len(args) != 1 { + return 0 + } + if a, ok := args[0].(int); ok { + return a + } + if a, ok := args[0].(string); ok { + i, err := strconv.Atoi(a) + if err != nil { + return 0 + } + return i + } + return 0 +} diff --git a/pkg/iac/scanners/azure/functions/int_test.go b/pkg/iac/scanners/azure/functions/int_test.go new file mode 100644 index 000000000000..0834ecdd6fc2 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/int_test.go @@ -0,0 +1,36 @@ +package functions + +import "testing" + +func Test_Int(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected int + }{ + { + name: "Int with 1", + args: []interface{}{1}, + expected: 1, + }, + { + name: "Int with 2", + args: []interface{}{"2"}, + expected: 2, + }, + { + name: "Int with 2.3", + args: []interface{}{"2.3"}, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Int(tt.args...) + if got != tt.expected { + t.Errorf("Int() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/intersection.go b/pkg/iac/scanners/azure/functions/intersection.go new file mode 100644 index 000000000000..d137a7c2aec8 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/intersection.go @@ -0,0 +1,76 @@ +package functions + +import "sort" + +func Intersection(args ...interface{}) interface{} { + + if args == nil || len(args) < 2 { + return []interface{}{} + } + + switch args[0].(type) { + case map[string]interface{}: + return intersectionMap(args...) + case interface{}: + return intersectionArray(args...) + } + + return []interface{}{} +} + +func intersectionArray(args ...interface{}) interface{} { + var result []interface{} + hash := make(map[interface{}]bool) + + for _, arg := range args[0].([]interface{}) { + hash[arg] = true + } + + for i := 1; i < len(args); i++ { + workingHash := make(map[interface{}]bool) + argArr, ok := args[i].([]interface{}) + if !ok { + continue + } + for _, item := range argArr { + if _, ok := hash[item]; ok { + workingHash[item] = true + } + } + hash = workingHash + } + + for k := range hash { + result = append(result, k) + } + + sort.Slice(result, func(i, j int) bool { + return result[i].(string) < result[j].(string) + }) + + return result +} + +func intersectionMap(args ...interface{}) interface{} { + hash := make(map[string]interface{}) + + for k, v := range args[0].(map[string]interface{}) { + hash[k] = v + } + + for i := 1; i < len(args); i++ { + workingHash := make(map[string]interface{}) + argArr, ok := args[i].(map[string]interface{}) + if !ok { + continue + } + for k, v := range argArr { + if ev, ok := hash[k]; ok && ev == v { + workingHash[k] = v + } + } + hash = workingHash + } + + return hash +} diff --git a/pkg/iac/scanners/azure/functions/intersection_test.go b/pkg/iac/scanners/azure/functions/intersection_test.go new file mode 100644 index 000000000000..42d23fee4bf7 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/intersection_test.go @@ -0,0 +1,106 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Intersect(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "intersect two arrays", + args: []interface{}{ + []interface{}{"a", "b", "c"}, + []interface{}{"b", "c", "d"}, + }, + expected: []interface{}{"b", "c"}, + }, + { + name: "intersect three arrays", + args: []interface{}{ + []interface{}{"a", "b", "c", "d"}, + []interface{}{"b", "c", "d"}, + []interface{}{"b", "c"}, + }, + expected: []interface{}{"b", "c"}, + }, + { + name: "intersect two arrays with one empty", + args: []interface{}{ + []interface{}{"a", "b", "c"}, + []interface{}{}, + }, + expected: []interface{}(nil), + }, + { + name: "intersect two arrays with both empty", + args: []interface{}{ + []interface{}{}, + []interface{}{}, + }, + expected: []interface{}(nil), + }, + { + name: "intersect two arrays with both nil", + args: []interface{}{ + nil, + nil, + }, + expected: []interface{}{}, + }, + { + name: "intersect two maps", + args: []interface{}{ + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + map[string]interface{}{ + "b": "b", + "c": "c", + "d": "d", + }, + }, + expected: map[string]interface{}{ + "b": "b", + "c": "c", + }, + }, + { + name: "intersect three maps", + args: []interface{}{ + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + map[string]interface{}{ + "b": "b", + "c": "c", + "d": "d", + }, + map[string]interface{}{ + "b": "b", + "d": "d", + }, + }, + expected: map[string]interface{}{ + "b": "b", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Intersection(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/items.go b/pkg/iac/scanners/azure/functions/items.go new file mode 100644 index 000000000000..2b40a369ea46 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/items.go @@ -0,0 +1,6 @@ +package functions + +func Items(args ...interface{}) interface{} { + + return nil +} diff --git a/pkg/iac/scanners/azure/functions/join.go b/pkg/iac/scanners/azure/functions/join.go new file mode 100644 index 000000000000..cdefa43fdad0 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/join.go @@ -0,0 +1,22 @@ +package functions + +import "strings" + +func Join(args ...interface{}) interface{} { + + if len(args) != 2 { + return "" + } + + container, ok := args[0].([]string) + if !ok { + return "" + } + + separator, ok := args[1].(string) + if !ok { + return "" + } + + return strings.Join(container, separator) +} diff --git a/pkg/iac/scanners/azure/functions/join_test.go b/pkg/iac/scanners/azure/functions/join_test.go new file mode 100644 index 000000000000..fab50a4e1e90 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/join_test.go @@ -0,0 +1,39 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Join(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "join strings with no items", + args: []interface{}{ + []string{}, + " ", + }, + expected: "", + }, + { + name: "join strings", + args: []interface{}{ + []string{"Hello", "World"}, + " ", + }, + expected: "Hello World", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Join(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/json.go b/pkg/iac/scanners/azure/functions/json.go new file mode 100644 index 000000000000..7694b358737b --- /dev/null +++ b/pkg/iac/scanners/azure/functions/json.go @@ -0,0 +1,20 @@ +package functions + +import "encoding/json" + +func JSON(args ...interface{}) interface{} { + if len(args) != 1 { + return "" + } + + value, ok := args[0].(string) + if !ok { + return "" + } + + var jsonType map[string]interface{} + if err := json.Unmarshal([]byte(value), &jsonType); err != nil { + return "" + } + return jsonType +} diff --git a/pkg/iac/scanners/azure/functions/json_test.go b/pkg/iac/scanners/azure/functions/json_test.go new file mode 100644 index 000000000000..1f04cd65026f --- /dev/null +++ b/pkg/iac/scanners/azure/functions/json_test.go @@ -0,0 +1,42 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_JSON(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected map[string]interface{} + }{ + { + name: "simple json string to json type", + args: []interface{}{ + `{"hello": "world"}`, + }, + expected: map[string]interface{}{ + "hello": "world", + }, + }, + { + name: "more complex json string to json type", + args: []interface{}{ + `{"hello": ["world", "world2"]}`, + }, + expected: map[string]interface{}{ + "hello": []interface{}{"world", "world2"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := JSON(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/last.go b/pkg/iac/scanners/azure/functions/last.go new file mode 100644 index 000000000000..8466ec6b669f --- /dev/null +++ b/pkg/iac/scanners/azure/functions/last.go @@ -0,0 +1,37 @@ +package functions + +func Last(args ...interface{}) interface{} { + if len(args) != 1 { + return "" + } + + container := args[0] + + switch cType := container.(type) { + case string: + if len(cType) > 0 { + return string(cType[len(cType)-1]) + } + case interface{}: + switch iType := cType.(type) { + case []string: + if len(iType) > 0 { + return iType[len(iType)-1] + } + case []bool: + if len(iType) > 0 { + return iType[len(iType)-1] + } + case []int: + if len(iType) > 0 { + return iType[len(iType)-1] + } + case []float64: + if len(iType) > 0 { + return iType[len(iType)-1] + } + } + } + + return "" +} diff --git a/pkg/iac/scanners/azure/functions/last_index_of.go b/pkg/iac/scanners/azure/functions/last_index_of.go new file mode 100644 index 000000000000..7dce6320d8fb --- /dev/null +++ b/pkg/iac/scanners/azure/functions/last_index_of.go @@ -0,0 +1,22 @@ +package functions + +import "strings" + +func LastIndexOf(args ...interface{}) interface{} { + + if len(args) != 2 { + return -1 + } + + stringToSearch, ok := args[0].(string) + if !ok { + return -1 + } + + stringToFind, ok := args[1].(string) + if !ok { + return -1 + } + + return strings.LastIndex(stringToSearch, stringToFind) +} diff --git a/pkg/iac/scanners/azure/functions/last_index_of_test.go b/pkg/iac/scanners/azure/functions/last_index_of_test.go new file mode 100644 index 000000000000..96b78d72dc5f --- /dev/null +++ b/pkg/iac/scanners/azure/functions/last_index_of_test.go @@ -0,0 +1,48 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_LastIndexOf(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected int + }{ + { + name: "get last index of string that is there", + args: []interface{}{ + "Hello world!", + "l", + }, + expected: 9, + }, + { + name: "get last index of string that is there as well", + args: []interface{}{ + "Hello world!", + "world", + }, + expected: 6, + }, + { + name: "get last index of string that isn't there", + args: []interface{}{ + "Hello world!", + "planet!", + }, + expected: -1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := LastIndexOf(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/last_test.go b/pkg/iac/scanners/azure/functions/last_test.go new file mode 100644 index 000000000000..2ceafbf8a69a --- /dev/null +++ b/pkg/iac/scanners/azure/functions/last_test.go @@ -0,0 +1,51 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Last(t *testing.T) { + test := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "last in empty string", + args: []interface{}{ + "", + }, + expected: "", + }, + { + name: "last in string", + args: []interface{}{ + "Hello", + }, + expected: "o", + }, + { + name: "last in empty slice", + args: []interface{}{ + []string{}, + }, + expected: "", + }, + { + name: "last in slice", + args: []interface{}{ + []string{"Hello", "World"}, + }, + expected: "World", + }, + } + + for _, tt := range test { + t.Run(tt.name, func(t *testing.T) { + actual := Last(tt.args...) + require.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/length.go b/pkg/iac/scanners/azure/functions/length.go new file mode 100644 index 000000000000..d74bfb2553bf --- /dev/null +++ b/pkg/iac/scanners/azure/functions/length.go @@ -0,0 +1,29 @@ +package functions + +func Length(args ...interface{}) interface{} { + + if len(args) != 1 { + return 0 + } + + switch ctype := args[0].(type) { + case string: + return len(ctype) + case map[string]interface{}: + return len(ctype) + case interface{}: + switch iType := ctype.(type) { + case []string: + return len(iType) + case []bool: + return len(iType) + case []int: + return len(iType) + case []float64: + return len(iType) + case []interface{}: + return len(iType) + } + } + return 0 +} diff --git a/pkg/iac/scanners/azure/functions/length_test.go b/pkg/iac/scanners/azure/functions/length_test.go new file mode 100644 index 000000000000..2d15ba4968cf --- /dev/null +++ b/pkg/iac/scanners/azure/functions/length_test.go @@ -0,0 +1,53 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Length(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected int + }{ + { + name: "length of a string", + args: []interface{}{ + "hello", + }, + expected: 5, + }, + { + name: "length of an empty string", + args: []interface{}{ + "", + }, + expected: 0, + }, + { + name: "length of an empty slice", + args: []interface{}{ + []string{}, + }, + expected: 0, + }, + { + name: "length of an slice with items", + args: []interface{}{ + []string{ + "hello", "world", + }, + }, + expected: 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Length(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/less.go b/pkg/iac/scanners/azure/functions/less.go new file mode 100644 index 000000000000..e25b3662c5c9 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/less.go @@ -0,0 +1,47 @@ +package functions + +func Less(args ...interface{}) interface{} { + + if len(args) != 2 { + return false + } + + switch arg0 := args[0].(type) { + case int: + arg1, ok := args[1].(int) + if ok { + return arg0 < arg1 + } + case string: + arg1, ok := args[1].(string) + if ok { + return arg0 < arg1 + } + } + + return false +} + +func LessOrEquals(args ...interface{}) interface{} { + + if len(args) != 2 { + return false + } + + switch arg0 := args[0].(type) { + case nil: + return args[1] == nil + case int: + arg1, ok := args[1].(int) + if ok { + return arg0 <= arg1 + } + case string: + arg1, ok := args[1].(string) + if ok { + return arg0 <= arg1 + } + } + + return false +} diff --git a/pkg/iac/scanners/azure/functions/less_test.go b/pkg/iac/scanners/azure/functions/less_test.go new file mode 100644 index 000000000000..706ee89db33f --- /dev/null +++ b/pkg/iac/scanners/azure/functions/less_test.go @@ -0,0 +1,119 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Less(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + + { + name: "less with nil and string", + args: []interface{}{ + nil, + "test", + }, + expected: false, + }, + { + name: "less with nil and nil", + args: []interface{}{ + nil, + nil, + }, + expected: false, + }, + { + name: "less with string and string", + args: []interface{}{ + "test", + "test", + }, + expected: false, + }, + { + name: "less with string and int", + args: []interface{}{ + "test", + 1, + }, + expected: false, + }, + { + name: "less with int and int", + args: []interface{}{ + 1, + 1, + }, + expected: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Less(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} + +func Test_LessThanOrEqual(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + + { + name: "less with nil and string", + args: []interface{}{ + nil, + "test", + }, + expected: false, + }, + { + name: "less with nil and nil", + args: []interface{}{ + nil, + nil, + }, + expected: true, + }, + { + name: "less with string and string", + args: []interface{}{ + "test", + "test", + }, + expected: true, + }, + { + name: "less with string and int", + args: []interface{}{ + "test", + 1, + }, + expected: false, + }, + { + name: "less with int and int", + args: []interface{}{ + 1, + 1, + }, + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := LessOrEquals(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/max.go b/pkg/iac/scanners/azure/functions/max.go new file mode 100644 index 000000000000..eb0338a4f894 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/max.go @@ -0,0 +1,32 @@ +package functions + +func Max(args ...interface{}) interface{} { + switch args[0].(type) { + case int: + var ints []int + for _, arg := range args { + ints = append(ints, arg.(int)) + } + return maxInt(ints) + case interface{}: + if iType, ok := args[0].([]int); ok { + return maxInt(iType) + } + } + return 0 +} + +func maxInt(args []int) int { + if len(args) == 0 { + return 0 + } + + max := args[0] + + for i := 1; i < len(args); i++ { + if args[i] > max { + max = args[i] + } + } + return max +} diff --git a/pkg/iac/scanners/azure/functions/max_test.go b/pkg/iac/scanners/azure/functions/max_test.go new file mode 100644 index 000000000000..942fad7e9e59 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/max_test.go @@ -0,0 +1,58 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Max(t *testing.T) { + test := []struct { + name string + args []interface{} + expected int + }{ + { + name: "max of empty slice", + args: []interface{}{ + []int{}, + }, + expected: 0, + }, + { + name: "max of slice", + args: []interface{}{ + []int{1, 2, 3}, + }, + expected: 3, + }, + { + name: "max of slice with negative numbers", + args: []interface{}{ + []int{-1, -2, -3}, + }, + expected: -1, + }, + { + name: "max of slice with negative and positive numbers", + args: []interface{}{ + []int{-1, 2, -3}, + }, + expected: 2, + }, + { + name: "max of comma separated numbers", + args: []interface{}{ + 1, 2, 3, 4, 5, + }, + expected: 5, + }, + } + + for _, tt := range test { + t.Run(tt.name, func(t *testing.T) { + actual := Max(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/min.go b/pkg/iac/scanners/azure/functions/min.go new file mode 100644 index 000000000000..5147c3bb2769 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/min.go @@ -0,0 +1,32 @@ +package functions + +func Min(args ...interface{}) interface{} { + switch args[0].(type) { + case int: + var ints []int + for _, arg := range args { + ints = append(ints, arg.(int)) + } + return minInt(ints) + case interface{}: + if iType, ok := args[0].([]int); ok { + return minInt(iType) + } + } + return 0 +} + +func minInt(args []int) int { + if len(args) == 0 { + return 0 + } + + min := args[0] + + for i := 1; i < len(args); i++ { + if args[i] < min { + min = args[i] + } + } + return min +} diff --git a/pkg/iac/scanners/azure/functions/min_test.go b/pkg/iac/scanners/azure/functions/min_test.go new file mode 100644 index 000000000000..28e12ef69de8 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/min_test.go @@ -0,0 +1,58 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Min(t *testing.T) { + test := []struct { + name string + args []interface{} + expected int + }{ + { + name: "min of empty slice", + args: []interface{}{ + []int{}, + }, + expected: 0, + }, + { + name: "min of slice", + args: []interface{}{ + []int{1, 2, 3}, + }, + expected: 1, + }, + { + name: "min of slice with negative numbers", + args: []interface{}{ + []int{-1, -2, -3}, + }, + expected: -3, + }, + { + name: "min of slice with negative and positive numbers", + args: []interface{}{ + []int{-1, 2, -3}, + }, + expected: -3, + }, + { + name: "min of comma separated numbers", + args: []interface{}{ + 1, 2, 3, 4, 5, + }, + expected: 1, + }, + } + + for _, tt := range test { + t.Run(tt.name, func(t *testing.T) { + actual := Min(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/mod.go b/pkg/iac/scanners/azure/functions/mod.go new file mode 100644 index 000000000000..34fb12b7a356 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/mod.go @@ -0,0 +1,14 @@ +package functions + +func Mod(args ...interface{}) interface{} { + if len(args) != 2 { + return 0 + } + + if a, ok := args[0].(int); ok { + if b, ok := args[1].(int); ok { + return a % b + } + } + return 0 +} diff --git a/pkg/iac/scanners/azure/functions/mod_test.go b/pkg/iac/scanners/azure/functions/mod_test.go new file mode 100644 index 000000000000..656e77e9aae3 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/mod_test.go @@ -0,0 +1,41 @@ +package functions + +import "testing" + +func Test_Mod(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected int + }{ + { + name: "Mod with 1 and 2", + args: []interface{}{1, 2}, + expected: 1, + }, + { + name: "Mod with 2 and 3", + args: []interface{}{2, 3}, + expected: 2, + }, + { + name: "Mod with 3 and -4", + args: []interface{}{3, -4}, + expected: 3, + }, + { + name: "Mod with 7 and 3", + args: []interface{}{7, 3}, + expected: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Mod(tt.args...) + if got != tt.expected { + t.Errorf("Mod() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/mul.go b/pkg/iac/scanners/azure/functions/mul.go new file mode 100644 index 000000000000..9d079728107f --- /dev/null +++ b/pkg/iac/scanners/azure/functions/mul.go @@ -0,0 +1,15 @@ +package functions + +func Mul(args ...interface{}) interface{} { + + if len(args) != 2 { + return nil + } + + if a, ok := args[0].(int); ok { + if b, ok := args[1].(int); ok { + return a * b + } + } + return nil +} diff --git a/pkg/iac/scanners/azure/functions/mul_test.go b/pkg/iac/scanners/azure/functions/mul_test.go new file mode 100644 index 000000000000..cf4ff57607b2 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/mul_test.go @@ -0,0 +1,38 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Mul(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected int + }{ + { + name: "multiply -2 by 1", + args: []interface{}{-2, 1}, + expected: -2, + }, + { + name: "multiply 4 by 2", + args: []interface{}{4, 2}, + expected: 8, + }, + { + name: "multiply 6 by 3", + args: []interface{}{6, 3}, + expected: 18, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Mul(tt.args...) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/not.go b/pkg/iac/scanners/azure/functions/not.go new file mode 100644 index 000000000000..5de10af5dffa --- /dev/null +++ b/pkg/iac/scanners/azure/functions/not.go @@ -0,0 +1,13 @@ +package functions + +func Not(args ...interface{}) interface{} { + + if len(args) != 1 { + return false + } + + if condition, ok := args[0].(bool); ok { + return !condition + } + return false +} diff --git a/pkg/iac/scanners/azure/functions/not_test.go b/pkg/iac/scanners/azure/functions/not_test.go new file mode 100644 index 000000000000..b1a209768f36 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/not_test.go @@ -0,0 +1,33 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Not(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected bool + }{ + { + name: "Not with true", + args: []interface{}{true}, + expected: false, + }, + { + name: "Not with false", + args: []interface{}{false}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Not(tt.args...) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/null.go b/pkg/iac/scanners/azure/functions/null.go new file mode 100644 index 000000000000..597c5485e9f5 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/null.go @@ -0,0 +1,5 @@ +package functions + +func Null(args ...interface{}) interface{} { + return nil +} diff --git a/pkg/iac/scanners/azure/functions/null_test.go b/pkg/iac/scanners/azure/functions/null_test.go new file mode 100644 index 000000000000..3394193415fb --- /dev/null +++ b/pkg/iac/scanners/azure/functions/null_test.go @@ -0,0 +1,12 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Null(t *testing.T) { + + assert.Nil(t, Null()) +} diff --git a/pkg/iac/scanners/azure/functions/or.go b/pkg/iac/scanners/azure/functions/or.go new file mode 100644 index 000000000000..87e6f8627ed4 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/or.go @@ -0,0 +1,20 @@ +package functions + +func Or(args ...interface{}) interface{} { + + if len(args) <= 1 { + return false + } + + for _, arg := range args { + arg1, ok := arg.(bool) + if !ok { + return false + } + if arg1 { + return true + } + + } + return false +} diff --git a/pkg/iac/scanners/azure/functions/or_test.go b/pkg/iac/scanners/azure/functions/or_test.go new file mode 100644 index 000000000000..2361c858a82a --- /dev/null +++ b/pkg/iac/scanners/azure/functions/or_test.go @@ -0,0 +1,44 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Or(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected bool + }{ + { + name: "And with same 2 bools", + args: []interface{}{true, true}, + expected: true, + }, + { + name: "And with same 3 bools", + args: []interface{}{true, true, true}, + expected: true, + }, + { + name: "And with different 4 bools", + args: []interface{}{true, true, false, true}, + expected: true, + }, + { + name: "And with same false 4 bools", + args: []interface{}{false, false, false, false}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Or(tt.args...) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/pad.go b/pkg/iac/scanners/azure/functions/pad.go new file mode 100644 index 000000000000..9d668210b11c --- /dev/null +++ b/pkg/iac/scanners/azure/functions/pad.go @@ -0,0 +1,32 @@ +package functions + +import "strings" + +func PadLeft(args ...interface{}) interface{} { + if len(args) != 3 { + return "" + } + + input, ok := args[0].(string) + if !ok { + return "" + } + + length, ok := args[1].(int) + if !ok { + return "" + } + + pad, ok := args[2].(string) + if !ok { + return "" + } + + if len(input) >= length { + return input + } + + repeat := (length - len(input)) / len(pad) + + return strings.Repeat(pad, repeat) + input +} diff --git a/pkg/iac/scanners/azure/functions/pad_test.go b/pkg/iac/scanners/azure/functions/pad_test.go new file mode 100644 index 000000000000..e7d274504298 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/pad_test.go @@ -0,0 +1,61 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_PadLeft(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "pad left with a input smaller than length", + args: []interface{}{ + "1234", + 8, + "0", + }, + expected: "00001234", + }, + { + name: "pad left with a input larger than length", + args: []interface{}{ + "1234", + 2, + "0", + }, + expected: "1234", + }, + { + name: "pad left with a input same as than length", + args: []interface{}{ + "1234", + 4, + "0", + }, + expected: "1234", + }, + { + name: "pad left with larger padding character", + args: []interface{}{ + "1234", + 8, + "00", + }, + expected: "00001234", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := PadLeft(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/parameters.go b/pkg/iac/scanners/azure/functions/parameters.go new file mode 100644 index 000000000000..b13ee3d60e4e --- /dev/null +++ b/pkg/iac/scanners/azure/functions/parameters.go @@ -0,0 +1 @@ +package functions diff --git a/pkg/iac/scanners/azure/functions/pick_zones.go b/pkg/iac/scanners/azure/functions/pick_zones.go new file mode 100644 index 000000000000..982936633dbe --- /dev/null +++ b/pkg/iac/scanners/azure/functions/pick_zones.go @@ -0,0 +1,23 @@ +package functions + +func PickZones(args ...interface{}) interface{} { + if len(args) < 3 { + return nil + } + numOfZones := 1 + + if len(args) > 3 { + numOfZones = args[3].(int) + if numOfZones > 3 { + numOfZones = 3 + } + } + + var zones []int + + for i := 1; i <= numOfZones; i++ { + zones = append(zones, i) + } + + return zones +} diff --git a/pkg/iac/scanners/azure/functions/pick_zones_test.go b/pkg/iac/scanners/azure/functions/pick_zones_test.go new file mode 100644 index 000000000000..19db480f9b0d --- /dev/null +++ b/pkg/iac/scanners/azure/functions/pick_zones_test.go @@ -0,0 +1,14 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_PickZones(t *testing.T) { + assert.Equal(t, []int{1}, PickZones("Microsoft.Compute", "virtualmachines", "eu-west-1")) + assert.Equal(t, []int{1, 2}, PickZones("Microsoft.Compute", "virtualmachines", "eu-west-1", 2)) + assert.Equal(t, []int{1, 2, 3}, PickZones("Microsoft.Compute", "virtualmachines", "eu-west-1", 3)) + assert.Equal(t, []int{1, 2, 3}, PickZones("Microsoft.Compute", "virtualmachines", "eu-west-1", 4)) +} diff --git a/pkg/iac/scanners/azure/functions/range.go b/pkg/iac/scanners/azure/functions/range.go new file mode 100644 index 000000000000..12a3526957d8 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/range.go @@ -0,0 +1,30 @@ +package functions + +func Range(args ...interface{}) interface{} { + + if len(args) != 2 { + return []interface{}{} + } + + start, ok := args[0].(int) + if !ok { + return []int{} + } + + count, ok := args[1].(int) + if !ok { + return []int{} + } + + if count > 10000 { + count = 10000 + } + + result := make([]int, count) + + for i := 0; i < count; i++ { + result[i] = start + i + } + + return result +} diff --git a/pkg/iac/scanners/azure/functions/range_test.go b/pkg/iac/scanners/azure/functions/range_test.go new file mode 100644 index 000000000000..9c0c6a084b6b --- /dev/null +++ b/pkg/iac/scanners/azure/functions/range_test.go @@ -0,0 +1,47 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Range(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "range for 3 from 1", + args: []interface{}{ + 1, + 3, + }, + expected: []int{1, 2, 3}, + }, + { + name: "range with for 10 from 3", + args: []interface{}{ + 3, + 10, + }, + expected: []int{3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + }, + { + name: "range with for 10 from -10", + args: []interface{}{ + -10, + 10, + }, + expected: []int{-10, -9, -8, -7, -6, -5, -4, -3, -2, -1}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Range(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/reference.go b/pkg/iac/scanners/azure/functions/reference.go new file mode 100644 index 000000000000..2f7b38ccf741 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/reference.go @@ -0,0 +1,12 @@ +package functions + +import "fmt" + +// Reference function can't work as per Azure because it requires Azure ARM logic +// best effort is to return the resourcename with a suffix to try and make it unique +func Reference(args ...interface{}) interface{} { + if len(args) < 1 { + return nil + } + return fmt.Sprintf("%v-reference", args[0]) +} diff --git a/pkg/iac/scanners/azure/functions/reference_test.go b/pkg/iac/scanners/azure/functions/reference_test.go new file mode 100644 index 000000000000..c669fe98d3f0 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/reference_test.go @@ -0,0 +1,12 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Reference(t *testing.T) { + assert.Equal(t, "test-reference", Reference("test")) + assert.Equal(t, "123-reference", Reference(123)) +} diff --git a/pkg/iac/scanners/azure/functions/replace.go b/pkg/iac/scanners/azure/functions/replace.go new file mode 100644 index 000000000000..09f829db2c50 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/replace.go @@ -0,0 +1,26 @@ +package functions + +import "strings" + +func Replace(args ...interface{}) interface{} { + if len(args) != 3 { + return "" + } + + input, ok := args[0].(string) + if !ok { + return "" + } + + purana, ok := args[1].(string) + if !ok { + return "" + } + + nava, ok := args[2].(string) + if !ok { + return "" + } + + return strings.ReplaceAll(input, purana, nava) +} diff --git a/pkg/iac/scanners/azure/functions/replace_test.go b/pkg/iac/scanners/azure/functions/replace_test.go new file mode 100644 index 000000000000..fe8fb40994cd --- /dev/null +++ b/pkg/iac/scanners/azure/functions/replace_test.go @@ -0,0 +1,41 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Replace(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "replace a string", + args: []interface{}{ + "hello", + "l", + "p", + }, + expected: "heppo", + }, + { + name: "replace a string with invalid replacement", + args: []interface{}{ + "hello", + "q", + "p", + }, + expected: "hello", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Replace(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/resource.go b/pkg/iac/scanners/azure/functions/resource.go new file mode 100644 index 000000000000..7eacfaeccff1 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/resource.go @@ -0,0 +1,48 @@ +package functions + +import ( + "fmt" +) + +func ResourceID(args ...interface{}) interface{} { + if len(args) < 2 { + return nil + } + + var resourceID string + + for _, arg := range args { + resourceID += "/" + fmt.Sprintf("%v", arg) + } + + return resourceID +} + +func ExtensionResourceID(args ...interface{}) interface{} { + if len(args) < 3 { + return nil + } + + var resourceID string + + for _, arg := range args { + resourceID += "/" + fmt.Sprintf("%v", arg) + } + + return resourceID +} + +func ResourceGroup(args ...interface{}) interface{} { + return fmt.Sprintf(`{ +"id": "/subscriptions/%s/resourceGroups/PlaceHolderResourceGroup", +"name": "Placeholder Resource Group", +"type":"Microsoft.Resources/resourceGroups", +"location": "westus", +"managedBy": "%s", +"tags": { +}, +"properties": { + "provisioningState": "Succeeded +} +}`, subscriptionID, managingResourceID) +} diff --git a/pkg/iac/scanners/azure/functions/resource_test.go b/pkg/iac/scanners/azure/functions/resource_test.go new file mode 100644 index 000000000000..d6dac14b4184 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/resource_test.go @@ -0,0 +1,12 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_ResourceID(t *testing.T) { + assert.Equal(t, "/test1/test2", ResourceID("test1", "test2")) + assert.Equal(t, "/test1/123", ResourceID("test1", 123)) +} diff --git a/pkg/iac/scanners/azure/functions/scope.go b/pkg/iac/scanners/azure/functions/scope.go new file mode 100644 index 000000000000..dcd1676b1945 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/scope.go @@ -0,0 +1,106 @@ +package functions + +import ( + "fmt" + + "github.com/google/uuid" +) + +var ( + tenantID = uuid.NewString() + groupID = uuid.NewString() + updaterID = uuid.NewString() + subscriptionID = uuid.NewString() + managingResourceID = uuid.NewString() +) + +func ManagementGroup(_ ...interface{}) interface{} { + + return fmt.Sprintf(`{ + "id": "/providers/Microsoft.Management/managementGroups/mgPlaceholder", + "name": "mgPlaceholder", + "properties": { + "details": { + "parent": { + "displayName": "Tenant Root Group", + "id": "/providers/Microsoft.Management/managementGroups/%[1]s", + "name": "%[1]s" + }, + "updatedBy": "%[2]s", + "updatedTime": "2020-07-23T21:05:52.661306Z", + "version": "1" + }, + "displayName": "Management PlaceHolder Group", + "tenantId": "%[3]s" + }, + "type": "/providers/Microsoft.Management/managementGroups" + } +`, groupID, updaterID, tenantID) +} + +func ManagementGroupResourceID(args ...interface{}) interface{} { + if len(args) < 2 { + return "" + } + + switch len(args) { + case 3: + return fmt.Sprintf("/providers/Microsoft.Management/managementGroups/%s/providers/%s/%s/%s", groupID, args[0], args[1], args[2]) + case 4: + return fmt.Sprintf("/providers/Microsoft.Management/managementGroups/%s/providers/%s/%s/%s", args[0], args[1], args[2], args[3]) + default: + return fmt.Sprintf("/providers/Microsoft.Management/managementGroups/%s/providers/%s/%s", groupID, args[0], args[1]) + } + +} + +func Subscription(_ ...interface{}) interface{} { + return fmt.Sprintf(`{ + "id": "/subscriptions/%[1]s", + "subscriptionId": "%[1]s", + "tenantId": "%[2]s", + "displayName": "Placeholder Subscription" +}`, subscriptionID, tenantID) +} + +func SubscriptionResourceID(args ...interface{}) interface{} { + if len(args) < 2 { + return nil + } + + switch len(args) { + + case 3: + return fmt.Sprintf("/subscriptions/%s/providers/%s/%s/%s", subscriptionID, args[0], args[1], args[2]) + case 4: + // subscription ID has been provided so use that + return fmt.Sprintf("/subscriptions/%s/providers/%s/%s/%s", args[0], args[1], args[2], args[3]) + default: + + return fmt.Sprintf("/subscriptions/%s/providers/%s/%s", subscriptionID, args[0], args[1]) + } +} + +func Tenant(_ ...interface{}) interface{} { + return fmt.Sprintf(`{ + "countryCode": "US", + "displayName": "Placeholder Tenant Name", + "id": "/tenants/%[1]s", + "tenantId": "%[1]s" + }`, tenantID) +} + +func TenantResourceID(args ...interface{}) interface{} { + if len(args) < 2 { + return nil + } + + switch len(args) { + case 3: + return fmt.Sprintf("/providers/%s/%s/%s", args[0], args[1], args[2]) + + default: + return fmt.Sprintf("/providers/%s/%s", args[0], args[1]) + } + +} diff --git a/pkg/iac/scanners/azure/functions/scope_test.go b/pkg/iac/scanners/azure/functions/scope_test.go new file mode 100644 index 000000000000..af84119e350e --- /dev/null +++ b/pkg/iac/scanners/azure/functions/scope_test.go @@ -0,0 +1,34 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_SubscriptionResourceID(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "scope resource id with subscription ID", + args: []interface{}{ + "4ec875a5-41a2-4837-88cf-4266466e65ed", + "Microsoft.Authorization/roleDefinitions", + "8e3af657-a8ff-443c-a75c-2fe8c4bcb635", + "b34282f6-5e3c-4306-8741-ebd7a871d187", + }, + expected: "/subscriptions/4ec875a5-41a2-4837-88cf-4266466e65ed/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635/b34282f6-5e3c-4306-8741-ebd7a871d187", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := SubscriptionResourceID(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/skip.go b/pkg/iac/scanners/azure/functions/skip.go new file mode 100644 index 000000000000..b68296fff66d --- /dev/null +++ b/pkg/iac/scanners/azure/functions/skip.go @@ -0,0 +1,34 @@ +package functions + +func Skip(args ...interface{}) interface{} { + if len(args) != 2 { + return "" + } + + count, ok := args[1].(int) + if !ok { + return "" + } + switch input := args[0].(type) { + case string: + if count > len(input) { + return "" + } + return input[count:] + case interface{}: + switch iType := input.(type) { + case []int: + return iType[count:] + case []string: + return iType[count:] + case []bool: + return iType[count:] + case []float64: + return iType[count:] + case []interface{}: + return iType[count:] + } + } + + return "" +} diff --git a/pkg/iac/scanners/azure/functions/skip_test.go b/pkg/iac/scanners/azure/functions/skip_test.go new file mode 100644 index 000000000000..692e6508f7f1 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/skip_test.go @@ -0,0 +1,65 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Skip(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "skip a string", + args: []interface{}{ + "hello", + 1, + }, + expected: "ello", + }, + { + name: "skip beyond the length a string", + args: []interface{}{ + "hello", + 6, + }, + expected: "", + }, + { + name: "skip with a zero count on a string", + args: []interface{}{ + "hello", + 0, + }, + expected: "hello", + }, + { + name: "skip with slice of ints", + args: []interface{}{ + []int{1, 2, 3, 4, 5}, + 2, + }, + expected: []int{3, 4, 5}, + }, + { + name: "skip with slice of strings", + args: []interface{}{ + []string{"hello", "world"}, + 1, + }, + expected: []string{"world"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Skip(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/split.go b/pkg/iac/scanners/azure/functions/split.go new file mode 100644 index 000000000000..47e62e96034a --- /dev/null +++ b/pkg/iac/scanners/azure/functions/split.go @@ -0,0 +1,35 @@ +package functions + +import "strings" + +func Split(args ...interface{}) interface{} { + if len(args) != 2 { + return "" + } + + input, ok := args[0].(string) + if !ok { + return "" + } + + switch separator := args[1].(type) { + case string: + return strings.Split(input, separator) + case interface{}: + if separator, ok := separator.([]string); ok { + m := make(map[rune]int) + for _, r := range separator { + r := rune(r[0]) + m[r] = 1 + } + + splitter := func(r rune) bool { + return m[r] == 1 + } + + return strings.FieldsFunc(input, splitter) + } + + } + return []string{} +} diff --git a/pkg/iac/scanners/azure/functions/split_test.go b/pkg/iac/scanners/azure/functions/split_test.go new file mode 100644 index 000000000000..e40df07526aa --- /dev/null +++ b/pkg/iac/scanners/azure/functions/split_test.go @@ -0,0 +1,38 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Split(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected []string + }{ + { + name: "split a string", + args: []interface{}{ + "hello, world", + ",", + }, + expected: []string{"hello", " world"}, + }, + { + name: "split a string with multiple separators", + args: []interface{}{ + "one;two,three", + []string{",", ";"}, + }, + expected: []string{"one", "two", "three"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Split(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/starts_with.go b/pkg/iac/scanners/azure/functions/starts_with.go new file mode 100644 index 000000000000..a4eb398cea3d --- /dev/null +++ b/pkg/iac/scanners/azure/functions/starts_with.go @@ -0,0 +1,22 @@ +package functions + +import "strings" + +func StartsWith(args ...interface{}) interface{} { + + if len(args) != 2 { + return false + } + + stringToSearch, ok := args[0].(string) + if !ok { + return false + } + + stringToFind, ok := args[1].(string) + if !ok { + return false + } + + return strings.HasPrefix(stringToSearch, stringToFind) +} diff --git a/pkg/iac/scanners/azure/functions/starts_with_test.go b/pkg/iac/scanners/azure/functions/starts_with_test.go new file mode 100644 index 000000000000..4a745478ee51 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/starts_with_test.go @@ -0,0 +1,41 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_StartsWith(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected bool + }{ + { + name: "string ends with", + args: []interface{}{ + "Hello, world!", + "Hello,", + }, + expected: true, + }, + { + name: "string does not end with", + args: []interface{}{ + "Hello world!", + "Hello,", + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := StartsWith(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/string.go b/pkg/iac/scanners/azure/functions/string.go new file mode 100644 index 000000000000..cba9997d9e9c --- /dev/null +++ b/pkg/iac/scanners/azure/functions/string.go @@ -0,0 +1,16 @@ +package functions + +import "fmt" + +func String(args ...interface{}) interface{} { + if len(args) != 1 { + return "" + } + + input, ok := args[0].(string) + if !ok { + return fmt.Sprintf("%v", args[0]) + } + + return input +} diff --git a/pkg/iac/scanners/azure/functions/string_test.go b/pkg/iac/scanners/azure/functions/string_test.go new file mode 100644 index 000000000000..ecab50ea8b65 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/string_test.go @@ -0,0 +1,44 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_String(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "string from a string", + args: []interface{}{ + "hello", + }, + expected: "hello", + }, + { + name: "string from a bool", + args: []interface{}{ + false, + }, + expected: "false", + }, + { + name: "string from an int", + args: []interface{}{ + 10, + }, + expected: "10", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := String(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/sub.go b/pkg/iac/scanners/azure/functions/sub.go new file mode 100644 index 000000000000..6013a8c0d509 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/sub.go @@ -0,0 +1,15 @@ +package functions + +func Sub(args ...interface{}) interface{} { + + if len(args) != 2 { + return nil + } + + if a, ok := args[0].(int); ok { + if b, ok := args[1].(int); ok { + return a - b + } + } + return nil +} diff --git a/pkg/iac/scanners/azure/functions/sub_test.go b/pkg/iac/scanners/azure/functions/sub_test.go new file mode 100644 index 000000000000..a3f9308a2710 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/sub_test.go @@ -0,0 +1,43 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Sub(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected int + }{ + { + name: "subtract 2 from 5", + args: []interface{}{5, 2}, + expected: 3, + }, + { + name: "subtract 2 from 1", + args: []interface{}{1, 2}, + expected: -1, + }, + { + name: "subtract 3 from 2", + args: []interface{}{2, 3}, + expected: -1, + }, + { + name: "subtract -4 from 3", + args: []interface{}{3, -4}, + expected: 7, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Sub(tt.args...) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/substring.go b/pkg/iac/scanners/azure/functions/substring.go new file mode 100644 index 000000000000..fed22f0d14a6 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/substring.go @@ -0,0 +1,36 @@ +package functions + +func SubString(args ...interface{}) interface{} { + if len(args) < 2 { + return "" + } + + input, ok := args[0].(string) + if !ok { + return "" + } + + start, ok := args[1].(int) + if !ok { + return "" + } + + if len(args) == 2 { + args = append(args, len(input)) + } + + length, ok := args[2].(int) + if !ok { + return "" + } + + if start > len(input) { + return "" + } + + if start+length > len(input) { + return input[start:] + } + + return input[start : start+length] +} diff --git a/pkg/iac/scanners/azure/functions/substring_test.go b/pkg/iac/scanners/azure/functions/substring_test.go new file mode 100644 index 000000000000..56e2ea107c73 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/substring_test.go @@ -0,0 +1,49 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_SubString(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "substring a string", + args: []interface{}{ + "hello", + 1, + 3, + }, + expected: "ell", + }, + { + name: "substring a string with no upper bound", + args: []interface{}{ + "hello", + 1, + }, + expected: "ello", + }, + { + name: "substring a string with start higher than the length", + args: []interface{}{ + "hello", + 10, + }, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := SubString(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/take.go b/pkg/iac/scanners/azure/functions/take.go new file mode 100644 index 000000000000..738c9d7d8064 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/take.go @@ -0,0 +1,49 @@ +package functions + +func Take(args ...interface{}) interface{} { + if len(args) != 2 { + return "" + } + + count, ok := args[1].(int) + if !ok { + return "" + } + switch input := args[0].(type) { + case string: + if count > len(input) { + return input + } + return input[:count] + case interface{}: + switch iType := input.(type) { + case []int: + if count > len(iType) { + return iType + } + return iType[:count] + case []string: + if count > len(iType) { + return iType + } + return iType[:count] + case []bool: + if count > len(iType) { + return iType + } + return iType[:count] + case []float64: + if count > len(iType) { + return iType + } + return iType[:count] + case []interface{}: + if count > len(iType) { + return iType + } + return iType[:count] + } + } + + return "" +} diff --git a/pkg/iac/scanners/azure/functions/take_test.go b/pkg/iac/scanners/azure/functions/take_test.go new file mode 100644 index 000000000000..68c19070a6e9 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/take_test.go @@ -0,0 +1,63 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Take(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "take a string", + args: []interface{}{ + "hello", + 2, + }, + expected: "he", + }, + { + name: "take a string with invalid count", + args: []interface{}{ + "hello", + 10, + }, + expected: "hello", + }, + { + name: "take a string from slice", + args: []interface{}{ + []string{"a", "b", "c"}, + 2, + }, + expected: []string{"a", "b"}, + }, + { + name: "take a string from a slice", + args: []interface{}{ + []string{"a", "b", "c"}, + 2, + }, + expected: []string{"a", "b"}, + }, + { + name: "take a string from a slice with invalid count", + args: []interface{}{ + []string{"a", "b", "c"}, + 10, + }, + expected: []string{"a", "b", "c"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Take(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/trim.go b/pkg/iac/scanners/azure/functions/trim.go new file mode 100644 index 000000000000..5215bbe7f43d --- /dev/null +++ b/pkg/iac/scanners/azure/functions/trim.go @@ -0,0 +1,16 @@ +package functions + +import "strings" + +func Trim(args ...interface{}) interface{} { + if len(args) != 1 { + return "" + } + + input, ok := args[0].(string) + if !ok { + return "" + } + + return strings.TrimSpace(input) +} diff --git a/pkg/iac/scanners/azure/functions/trim_test.go b/pkg/iac/scanners/azure/functions/trim_test.go new file mode 100644 index 000000000000..44a787b0f268 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/trim_test.go @@ -0,0 +1,71 @@ +package functions + +import "testing" + +func Test_Trim(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "trim a string", + args: []interface{}{ + " hello ", + }, + expected: "hello", + }, + { + name: "trim a string with multiple spaces", + args: []interface{}{ + " hello ", + }, + expected: "hello", + }, + { + name: "trim a string with tabs", + args: []interface{}{ + " hello ", + }, + expected: "hello", + }, + { + name: "trim a string with new lines", + args: []interface{}{ + ` + +hello + +`, + }, + expected: "hello", + }, + { + name: "trim a string with tabs, spaces and new lines", + args: []interface{}{ + ` + +hello + +`, + }, + expected: "hello", + }, + { + name: "trim a string with non string input", + args: []interface{}{ + 10, + }, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Trim(tt.args...) + if actual != tt.expected { + t.Errorf("Trim(%v) = %v, expected %v", tt.args, actual, tt.expected) + } + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/true.go b/pkg/iac/scanners/azure/functions/true.go new file mode 100644 index 000000000000..9f13af580757 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/true.go @@ -0,0 +1,5 @@ +package functions + +func True(args ...interface{}) interface{} { + return true +} diff --git a/pkg/iac/scanners/azure/functions/union.go b/pkg/iac/scanners/azure/functions/union.go new file mode 100644 index 000000000000..b0db1c3d6ed0 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/union.go @@ -0,0 +1,58 @@ +package functions + +import "sort" + +func Union(args ...interface{}) interface{} { + if len(args) == 0 { + return []interface{}{} + } + if len(args) == 1 { + return args[0] + } + + switch args[0].(type) { + case map[string]interface{}: + return unionMap(args...) + case interface{}: + return unionArray(args...) + } + + return []interface{}{} + +} + +func unionMap(args ...interface{}) interface{} { + result := make(map[string]interface{}) + + for _, arg := range args { + if iType, ok := arg.(map[string]interface{}); ok { + for k, v := range iType { + result[k] = v + } + } + } + + return result +} + +func unionArray(args ...interface{}) interface{} { + var result []interface{} + union := make(map[interface{}]bool) + + for _, arg := range args { + if iType, ok := arg.([]interface{}); ok { + for _, item := range iType { + union[item] = true + } + } + } + + for k := range union { + result = append(result, k) + } + sort.Slice(result, func(i, j int) bool { + return result[i].(string) < result[j].(string) + }) + + return result +} diff --git a/pkg/iac/scanners/azure/functions/union_test.go b/pkg/iac/scanners/azure/functions/union_test.go new file mode 100644 index 000000000000..56d5bf809088 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/union_test.go @@ -0,0 +1,110 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Union(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected interface{} + }{ + { + name: "union single array", + args: []interface{}{ + []interface{}{"a", "b", "c"}, + }, + expected: []interface{}{"a", "b", "c"}, + }, + { + name: "union two arrays", + args: []interface{}{ + []interface{}{"a", "b", "c"}, + []interface{}{"b", "c", "d"}, + }, + expected: []interface{}{"a", "b", "c", "d"}, + }, + { + name: "union two arrays", + args: []interface{}{ + []interface{}{"a", "b", "c"}, + []interface{}{"b", "c", "d"}, + []interface{}{"b", "c", "d", "e"}, + }, + expected: []interface{}{"a", "b", "c", "d", "e"}, + }, + { + name: "union single maps", + args: []interface{}{ + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + }, + expected: map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + }, + { + name: "union two maps", + args: []interface{}{ + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + map[string]interface{}{ + "b": "b", + "c": "c", + "d": "d", + }, + }, + expected: map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + "d": "d", + }, + }, + { + name: "union three maps", + args: []interface{}{ + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + map[string]interface{}{ + "b": "b", + "c": "c", + "d": "d", + }, + map[string]interface{}{ + "b": "b", + "c": "c", + "e": "e", + }, + }, + expected: map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + "d": "d", + "e": "e", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Union(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/unique_string.go b/pkg/iac/scanners/azure/functions/unique_string.go new file mode 100644 index 000000000000..fba35c6459ac --- /dev/null +++ b/pkg/iac/scanners/azure/functions/unique_string.go @@ -0,0 +1,21 @@ +package functions + +import ( + "crypto/sha256" + "fmt" + "strings" +) + +func UniqueString(args ...interface{}) interface{} { + if len(args) == 0 { + return "" + } + + hashParts := make([]string, len(args)) + for i, str := range args { + hashParts[i] = str.(string) + } + + hash := sha256.New().Sum([]byte(strings.Join(hashParts, ""))) + return fmt.Sprintf("%x", hash)[:13] +} diff --git a/pkg/iac/scanners/azure/functions/unique_string_test.go b/pkg/iac/scanners/azure/functions/unique_string_test.go new file mode 100644 index 000000000000..035591eb46aa --- /dev/null +++ b/pkg/iac/scanners/azure/functions/unique_string_test.go @@ -0,0 +1,38 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_UniqueString(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "unique string from a string", + args: []interface{}{ + "hello", + }, + expected: "68656c6c6fe3b", + }, + { + name: "unique string from a string", + args: []interface{}{ + "hello", + "world", + }, + expected: "68656c6c6f776", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := UniqueString(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/functions/uri.go b/pkg/iac/scanners/azure/functions/uri.go new file mode 100644 index 000000000000..949e12235dea --- /dev/null +++ b/pkg/iac/scanners/azure/functions/uri.go @@ -0,0 +1,29 @@ +package functions + +import ( + "net/url" + "path" +) + +func Uri(args ...interface{}) interface{} { + if len(args) != 2 { + return "" + } + + result, err := joinPath(args[0].(string), args[1].(string)) + if err != nil { + return "" + } + return result +} + +// Backport url.JoinPath until we're ready for Go 1.19 +func joinPath(base string, elem ...string) (string, error) { + u, err := url.Parse(base) + if err != nil { + return "", err + } + elem = append([]string{u.EscapedPath()}, elem...) + u.Path = path.Join(elem...) + return u.String(), nil +} diff --git a/pkg/iac/scanners/azure/functions/uri_test.go b/pkg/iac/scanners/azure/functions/uri_test.go new file mode 100644 index 000000000000..1a63fe6bbd01 --- /dev/null +++ b/pkg/iac/scanners/azure/functions/uri_test.go @@ -0,0 +1,48 @@ +package functions + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Uri(t *testing.T) { + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "uri from a base and relative with no trailing slash", + args: []interface{}{ + "http://contoso.org/firstpath", + "myscript.sh", + }, + expected: "http://contoso.org/firstpath/myscript.sh", + }, + { + name: "uri from a base and relative with trailing slash", + args: []interface{}{ + "http://contoso.org/firstpath/", + "myscript.sh", + }, + expected: "http://contoso.org/firstpath/myscript.sh", + }, + { + name: "uri from a base with trailing slash and relative with ../", + args: []interface{}{ + "http://contoso.org/firstpath/", + "../myscript.sh", + }, + expected: "http://contoso.org/myscript.sh", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Uri(tt.args...) + require.Equal(t, tt.expected, actual) + }) + } + +} diff --git a/pkg/iac/scanners/azure/functions/utc_now.go b/pkg/iac/scanners/azure/functions/utc_now.go new file mode 100644 index 000000000000..68c93bd58fee --- /dev/null +++ b/pkg/iac/scanners/azure/functions/utc_now.go @@ -0,0 +1,47 @@ +package functions + +import ( + "strings" + "time" +) + +func UTCNow(args ...interface{}) interface{} { + if len(args) > 1 { + return nil + } + + if len(args) == 1 { + format, ok := args[0].(string) + if ok { + goFormat := convertFormat(format) + return time.Now().UTC().Format(goFormat) + } + } + + return time.Now().UTC().Format(time.RFC3339) +} + +// don't look directly at this code +func convertFormat(format string) string { + goFormat := format + goFormat = strings.ReplaceAll(goFormat, "yyyy", "2006") + goFormat = strings.ReplaceAll(goFormat, "yy", "06") + goFormat = strings.ReplaceAll(goFormat, "MMMM", "January") + goFormat = strings.ReplaceAll(goFormat, "MMM", "Jan") + goFormat = strings.ReplaceAll(goFormat, "MM", "01") + goFormat = strings.ReplaceAll(goFormat, "M", "1") + goFormat = strings.ReplaceAll(goFormat, "dd", "02") + goFormat = strings.ReplaceAll(goFormat, "d", "2") + goFormat = strings.ReplaceAll(goFormat, "HH", "15") + goFormat = strings.ReplaceAll(goFormat, "H", "3") + goFormat = strings.ReplaceAll(goFormat, "hh", "03") + goFormat = strings.ReplaceAll(goFormat, "h", "3") + goFormat = strings.ReplaceAll(goFormat, "mm", "04") + goFormat = strings.ReplaceAll(goFormat, "m", "4") + goFormat = strings.ReplaceAll(goFormat, "ss", "05") + goFormat = strings.ReplaceAll(goFormat, "s", "5") + goFormat = strings.ReplaceAll(goFormat, "tt", "PM") + goFormat = strings.ReplaceAll(goFormat, "t", "PM") + return goFormat + +} diff --git a/pkg/iac/scanners/azure/functions/utc_now_test.go b/pkg/iac/scanners/azure/functions/utc_now_test.go new file mode 100644 index 000000000000..c203c3e70a0a --- /dev/null +++ b/pkg/iac/scanners/azure/functions/utc_now_test.go @@ -0,0 +1,40 @@ +package functions + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func Test_UTCNow(t *testing.T) { + + tests := []struct { + name string + args []interface{} + expected string + }{ + { + name: "utc now day", + args: []interface{}{ + "d", + }, + expected: fmt.Sprintf("%d", time.Now().UTC().Day()), + }, + { + name: "utc now date", + args: []interface{}{ + "yyyy-M-d", + }, + expected: fmt.Sprintf("%d-%d-%d", time.Now().UTC().Year(), time.Now().UTC().Month(), time.Now().UTC().Day()), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := UTCNow(tt.args...) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/pkg/iac/scanners/azure/resolver/resolver.go b/pkg/iac/scanners/azure/resolver/resolver.go new file mode 100644 index 000000000000..3794ed20ab64 --- /dev/null +++ b/pkg/iac/scanners/azure/resolver/resolver.go @@ -0,0 +1,51 @@ +package resolver + +import ( + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/expressions" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Resolver interface { + ResolveExpression(expression azure2.Value) azure2.Value + SetDeployment(d *azure2.Deployment) +} + +func NewResolver() Resolver { + return &resolver{} +} + +type resolver struct { + deployment *azure2.Deployment +} + +func (r *resolver) SetDeployment(d *azure2.Deployment) { + r.deployment = d +} + +func (r *resolver) ResolveExpression(expression azure2.Value) azure2.Value { + if expression.Kind != azure2.KindExpression { + return expression + } + if r.deployment == nil { + panic("cannot resolve expression on nil deployment") + } + code := expression.AsString() + + resolved, err := r.resolveExpressionString(code, expression.GetMetadata()) + if err != nil { + expression.Kind = azure2.KindUnresolvable + return expression + } + return resolved +} + +func (r *resolver) resolveExpressionString(code string, metadata iacTypes.Metadata) (azure2.Value, error) { + et, err := expressions.NewExpressionTree(code) + if err != nil { + return azure2.NullValue, err + } + + evaluatedValue := et.Evaluate(r.deployment) + return azure2.NewValue(evaluatedValue, metadata), nil +} diff --git a/pkg/iac/scanners/azure/resolver/resolver_test.go b/pkg/iac/scanners/azure/resolver/resolver_test.go new file mode 100644 index 000000000000..b4aabdce0a7f --- /dev/null +++ b/pkg/iac/scanners/azure/resolver/resolver_test.go @@ -0,0 +1,101 @@ +package resolver + +import ( + "testing" + "time" + + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/require" +) + +func Test_resolveFunc(t *testing.T) { + + tests := []struct { + name string + expr string + expected string + }{ + { + name: "simple format call", + expr: "format('{0}/{1}', 'myPostgreSQLServer', 'log_checkpoints')", + expected: "myPostgreSQLServer/log_checkpoints", + }, + { + name: "simple format call with numbers", + expr: "format('{0} + {1} = {2}', 1, 2, 3)", + expected: "1 + 2 = 3", + }, + { + name: "format with nested format", + expr: "format('{0} + {1} = {2}', format('{0}', 1), 2, 3)", + expected: "1 + 2 = 3", + }, + { + name: "format with multiple nested format", + expr: "format('{0} + {1} = {2}', format('{0}', 1), 2, format('{0}', 3))", + expected: "1 + 2 = 3", + }, + { + name: "format with nested base64", + expr: "format('the base64 of \"hello, world\" is {0}', base64('hello, world'))", + expected: "the base64 of \"hello, world\" is aGVsbG8sIHdvcmxk", + }, + { + name: "dateTimeAdd with add a day", + expr: "dateTimeAdd(utcNow('yyyy-MM-dd'), 'P1D', 'yyyy-MM-dd')", + expected: time.Now().UTC().AddDate(0, 0, 1).Format("2006-01-02"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resolver := resolver{} + + resolvedValue, err := resolver.resolveExpressionString(tt.expr, types.NewTestMetadata()) + require.NoError(t, err) + require.Equal(t, azure2.KindString, resolvedValue.Kind) + + require.Equal(t, tt.expected, resolvedValue.AsString()) + }) + } +} + +func Test_resolveParameter(t *testing.T) { + tests := []struct { + name string + deployment *azure2.Deployment + expr string + expected string + }{ + { + name: "format call with parameter", + deployment: &azure2.Deployment{ + Parameters: []azure2.Parameter{ + { + Variable: azure2.Variable{ + Name: "dbName", + Value: azure2.NewValue("myPostgreSQLServer", types.NewTestMetadata()), + }, + }, + }, + }, + expr: "format('{0}/{1}', parameters('dbName'), 'log_checkpoints')", + expected: "myPostgreSQLServer/log_checkpoints", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resolver := resolver{ + deployment: tt.deployment, + } + + resolvedValue, err := resolver.resolveExpressionString(tt.expr, types.NewTestMetadata()) + require.NoError(t, err) + require.Equal(t, azure2.KindString, resolvedValue.Kind) + + require.Equal(t, tt.expected, resolvedValue.AsString()) + }) + } + +} diff --git a/pkg/iac/scanners/azure/value.go b/pkg/iac/scanners/azure/value.go new file mode 100644 index 000000000000..1b21d62de380 --- /dev/null +++ b/pkg/iac/scanners/azure/value.go @@ -0,0 +1,358 @@ +package azure + +import ( + "strings" + "time" + + "golang.org/x/exp/slices" + + armjson2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type EvalContext struct{} + +type Kind string + +const ( + KindUnresolvable Kind = "unresolvable" + KindNull Kind = "null" + KindBoolean Kind = "boolean" + KindString Kind = "string" + KindNumber Kind = "number" + KindObject Kind = "object" + KindArray Kind = "array" + KindExpression Kind = "expression" +) + +type Value struct { + types.Metadata + rLit interface{} + rMap map[string]Value + rArr []Value + Kind Kind + Comments []string +} + +var NullValue = Value{ + Kind: KindNull, +} + +func NewValue(value interface{}, metadata types.Metadata) Value { + + v := Value{ + Metadata: metadata, + } + + switch ty := value.(type) { + case []interface{}: + v.Kind = KindArray + for _, child := range ty { + if internal, ok := child.(Value); ok { + v.rArr = append(v.rArr, internal) + } else { + v.rArr = append(v.rArr, NewValue(child, metadata)) + } + } + case []Value: + v.Kind = KindArray + v.rArr = append(v.rArr, ty...) + + case map[string]interface{}: + v.Kind = KindObject + v.rMap = make(map[string]Value) + for key, val := range ty { + if internal, ok := val.(Value); ok { + v.rMap[key] = internal + } else { + v.rMap[key] = NewValue(val, metadata) + } + } + case map[string]Value: + v.Kind = KindObject + v.rMap = make(map[string]Value) + for key, val := range ty { + v.rMap[key] = val + } + case string: + v.Kind = KindString + v.rLit = ty + case int, int64, int32, float32, float64, int8, int16, uint8, uint16, uint32, uint64: + v.Kind = KindNumber + v.rLit = ty + case bool: + v.Kind = KindBoolean + v.rLit = ty + case nil: + v.Kind = KindNull + v.rLit = ty + default: + v.Kind = KindUnresolvable + v.rLit = ty + } + + return v +} + +func (v *Value) GetMetadata() types.Metadata { + return v.Metadata +} + +func (v *Value) UnmarshalJSONWithMetadata(node armjson2.Node) error { + + v.updateValueKind(node) + + v.Metadata = node.Metadata() + + switch node.Kind() { + case armjson2.KindArray: + err := v.unmarshallArray(node) + if err != nil { + return err + } + case armjson2.KindObject: + err := v.unmarshalObject(node) + if err != nil { + return err + } + case armjson2.KindString: + err := v.unmarshalString(node) + if err != nil { + return err + } + default: + if err := node.Decode(&v.rLit); err != nil { + return err + } + } + + for _, comment := range node.Comments() { + var str string + if err := comment.Decode(&str); err != nil { + return err + } + // remove `\r` from comment when running windows + str = strings.ReplaceAll(str, "\r", "") + + v.Comments = append(v.Comments, str) + } + return nil +} + +func (v *Value) unmarshalString(node armjson2.Node) error { + var str string + if err := node.Decode(&str); err != nil { + return err + } + if strings.HasPrefix(str, "[") && !strings.HasPrefix(str, "[[") && strings.HasSuffix(str, "]") { + // function! + v.Kind = KindExpression + v.rLit = str[1 : len(str)-1] + } else { + v.rLit = str + } + return nil +} + +func (v *Value) unmarshalObject(node armjson2.Node) error { + obj := make(map[string]Value) + for i := 0; i < len(node.Content()); i += 2 { + var key string + if err := node.Content()[i].Decode(&key); err != nil { + return err + } + var val Value + if err := val.UnmarshalJSONWithMetadata(node.Content()[i+1]); err != nil { + return err + } + obj[key] = val + } + v.rMap = obj + return nil +} + +func (v *Value) unmarshallArray(node armjson2.Node) error { + var arr []Value + for _, child := range node.Content() { + var val Value + if err := val.UnmarshalJSONWithMetadata(child); err != nil { + return err + } + arr = append(arr, val) + } + v.rArr = arr + return nil +} + +func (v *Value) updateValueKind(node armjson2.Node) { + switch node.Kind() { + case armjson2.KindString: + v.Kind = KindString + case armjson2.KindNumber: + v.Kind = KindNumber + case armjson2.KindBoolean: + v.Kind = KindBoolean + case armjson2.KindObject: + v.Kind = KindObject + case armjson2.KindNull: + v.Kind = KindNull + case armjson2.KindArray: + v.Kind = KindArray + default: + panic(node.Kind()) + } +} + +func (v Value) AsString() string { + v.Resolve() + + if v.Kind != KindString { + return "" + } + + return v.rLit.(string) +} + +func (v Value) AsBool() bool { + v.Resolve() + if v.Kind != KindBoolean { + return false + } + return v.rLit.(bool) +} + +func (v Value) AsInt() int { + v.Resolve() + if v.Kind != KindNumber { + return 0 + } + return int(v.rLit.(int64)) +} + +func (v Value) AsFloat() float64 { + v.Resolve() + if v.Kind != KindNumber { + return 0 + } + return v.rLit.(float64) +} + +func (v Value) AsIntValue(defaultValue int, metadata types.Metadata) types.IntValue { + v.Resolve() + if v.Kind != KindNumber { + return types.Int(defaultValue, metadata) + } + return types.Int(v.AsInt(), metadata) +} + +func (v Value) AsBoolValue(defaultValue bool, metadata types.Metadata) types.BoolValue { + v.Resolve() + if v.Kind == KindString { + possibleValue := strings.ToLower(v.rLit.(string)) + if slices.Contains([]string{"true", "1", "yes", "on", "enabled"}, possibleValue) { + return types.Bool(true, metadata) + } + } + + if v.Kind != KindBoolean { + return types.Bool(defaultValue, metadata) + } + + return types.Bool(v.rLit.(bool), v.GetMetadata()) +} + +func (v Value) EqualTo(value interface{}) bool { + switch ty := value.(type) { + case string: + return v.AsString() == ty + default: + panic("not supported") + } +} + +func (v Value) AsStringValue(defaultValue string, metadata types.Metadata) types.StringValue { + v.Resolve() + if v.Kind != KindString { + return types.StringDefault(defaultValue, metadata) + } + return types.String(v.rLit.(string), v.Metadata) +} + +func (v Value) GetMapValue(key string) Value { + v.Resolve() + if v.Kind != KindObject { + return NullValue + } + return v.rMap[key] +} + +func (v Value) AsMap() map[string]Value { + v.Resolve() + if v.Kind != KindObject { + return nil + } + return v.rMap +} + +func (v Value) AsList() []Value { + v.Resolve() + if v.Kind != KindArray { + return nil + } + return v.rArr +} + +func (v Value) Raw() interface{} { + switch v.Kind { + case KindArray: + // TODO: recursively build raw array + return nil + case KindObject: + // TODO: recursively build raw object + return nil + default: + return v.rLit + } +} + +func (v *Value) Resolve() { + if v.Kind != KindExpression { + return + } + // if resolver, ok := v.Metadata.Internal().(Resolver); ok { + // *v = resolver.ResolveExpression(*v) + // } +} + +func (v Value) HasKey(key string) bool { + v.Resolve() + _, ok := v.rMap[key] + return ok +} + +func (v Value) AsTimeValue(metadata types.Metadata) types.TimeValue { + v.Resolve() + if v.Kind != KindString { + return types.Time(time.Time{}, metadata) + } + if v.Kind == KindNumber { + return types.Time(time.Unix(int64(v.AsFloat()), 0), metadata) + } + t, err := time.Parse(time.RFC3339, v.rLit.(string)) + if err != nil { + return types.Time(time.Time{}, metadata) + } + return types.Time(t, metadata) +} + +func (v Value) AsStringValuesList(defaultValue string) (stringValues []types.StringValue) { + v.Resolve() + if v.Kind != KindArray { + return + } + for _, item := range v.rArr { + stringValues = append(stringValues, item.AsStringValue(defaultValue, item.Metadata)) + } + + return stringValues +} diff --git a/pkg/iac/scanners/azure/value_test.go b/pkg/iac/scanners/azure/value_test.go new file mode 100644 index 000000000000..646ddc0b0cd0 --- /dev/null +++ b/pkg/iac/scanners/azure/value_test.go @@ -0,0 +1,13 @@ +package azure + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" +) + +func Test_ValueAsInt(t *testing.T) { + val := NewValue(int64(10), types.NewTestMetadata()) + assert.Equal(t, 10, val.AsInt()) +} diff --git a/pkg/iac/scanners/cloudformation/cftypes/types.go b/pkg/iac/scanners/cloudformation/cftypes/types.go new file mode 100644 index 000000000000..0dc3b8b586a2 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/cftypes/types.go @@ -0,0 +1,36 @@ +package cftypes + +import "reflect" + +type CfType string + +const ( + String CfType = "string" + Int CfType = "int" + Float64 CfType = "float64" + Bool CfType = "bool" + Map CfType = "map" + List CfType = "list" + Unknown CfType = "unknown" +) + +func TypeFromGoValue(value interface{}) CfType { + switch reflect.TypeOf(value).Kind() { + case reflect.String: + return String + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return Int + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return Int + case reflect.Float32, reflect.Float64: + return Float64 + case reflect.Bool: + return Bool + case reflect.Map: + return Map + case reflect.Slice: + return List + default: + return Unknown + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/errors.go b/pkg/iac/scanners/cloudformation/parser/errors.go new file mode 100644 index 000000000000..655f137cd271 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/errors.go @@ -0,0 +1,24 @@ +package parser + +import ( + "fmt" +) + +type InvalidContentError struct { + source string + err error +} + +func NewErrInvalidContent(source string, err error) *InvalidContentError { + return &InvalidContentError{ + source: source, + err: err, + } +} +func (e *InvalidContentError) Error() string { + return fmt.Sprintf("Invalid content in file: %s. Error: %v", e.source, e.err) +} + +func (e *InvalidContentError) Reason() error { + return e.err +} diff --git a/pkg/iac/scanners/cloudformation/parser/file_context.go b/pkg/iac/scanners/cloudformation/parser/file_context.go new file mode 100644 index 000000000000..746dae7e024b --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/file_context.go @@ -0,0 +1,63 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/ignore" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type SourceFormat string + +const ( + YamlSourceFormat SourceFormat = "yaml" + JsonSourceFormat SourceFormat = "json" +) + +type FileContexts []*FileContext + +type FileContext struct { + filepath string + lines []string + SourceFormat SourceFormat + Ignores ignore.Rules + Parameters map[string]*Parameter `json:"Parameters" yaml:"Parameters"` + Resources map[string]*Resource `json:"Resources" yaml:"Resources"` + Globals map[string]*Resource `json:"Globals" yaml:"Globals"` + Mappings map[string]interface{} `json:"Mappings,omitempty" yaml:"Mappings"` + Conditions map[string]Property `json:"Conditions,omitempty" yaml:"Conditions"` +} + +func (t *FileContext) GetResourceByLogicalID(name string) *Resource { + for n, r := range t.Resources { + if name == n { + return r + } + } + return nil +} + +func (t *FileContext) GetResourcesByType(names ...string) []*Resource { + var resources []*Resource + for _, r := range t.Resources { + for _, name := range names { + if name == r.Type() { + // + resources = append(resources, r) + } + } + } + return resources +} + +func (t *FileContext) Metadata() iacTypes.Metadata { + rng := iacTypes.NewRange(t.filepath, 1, len(t.lines), "", nil) + + return iacTypes.NewMetadata(rng, NewCFReference("Template", rng).String()) +} + +func (t *FileContext) OverrideParameters(params map[string]any) { + for key := range t.Parameters { + if val, ok := params[key]; ok { + t.Parameters[key].UpdateDefault(val) + } + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/file_context_test.go b/pkg/iac/scanners/cloudformation/parser/file_context_test.go new file mode 100644 index 000000000000..bbf5db4ddc39 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/file_context_test.go @@ -0,0 +1,61 @@ +package parser + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFileContext_OverrideParameters(t *testing.T) { + tests := []struct { + name string + ctx FileContext + arg map[string]any + expected map[string]*Parameter + }{ + { + name: "happy", + ctx: FileContext{ + Parameters: map[string]*Parameter{ + "BucketName": { + inner: parameterInner{ + Type: "String", + Default: "test", + }, + }, + "QueueName": { + inner: parameterInner{ + Type: "String", + }, + }, + }, + }, + arg: map[string]any{ + "BucketName": "test2", + "QueueName": "test", + "SomeKey": "some_value", + }, + expected: map[string]*Parameter{ + "BucketName": { + inner: parameterInner{ + Type: "String", + Default: "test2", + }, + }, + "QueueName": { + inner: parameterInner{ + Type: "String", + Default: "test", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.ctx.OverrideParameters(tt.arg) + assert.Equal(t, tt.expected, tt.ctx.Parameters) + }) + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_and.go b/pkg/iac/scanners/cloudformation/parser/fn_and.go new file mode 100644 index 000000000000..a155120e413a --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_and.go @@ -0,0 +1,40 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveAnd(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::And"].AsList() + + if len(refValue) < 2 { + return abortIntrinsic(property, "Fn::And should have at least 2 values, returning original Property") + } + + results := make([]bool, len(refValue)) + for i := 0; i < len(refValue); i++ { + + r := false + if refValue[i].IsBool() { + r = refValue[i].AsBool() + } + + results[i] = r + } + + theSame := allSameStrings(results) + return property.deriveResolved(cftypes.Bool, theSame), true +} + +func allSameStrings(a []bool) bool { + for i := 1; i < len(a); i++ { + if a[i] != a[0] { + return false + } + } + return true +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_and_test.go b/pkg/iac/scanners/cloudformation/parser/fn_and_test.go new file mode 100644 index 000000000000..2663270e99dc --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_and_test.go @@ -0,0 +1,185 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolve_and_value(t *testing.T) { + + property1 := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + } + + property2 := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + } + andProperty := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::And": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + property1, + property2, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(andProperty) + require.True(t, success) + + assert.True(t, resolvedProperty.IsTrue()) +} + +func Test_resolve_and_value_not_the_same(t *testing.T) { + + property1 := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + } + + property2 := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + } + andProperty := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::And": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + property1, + property2, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(andProperty) + require.True(t, success) + + assert.False(t, resolvedProperty.IsTrue()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_base64.go b/pkg/iac/scanners/cloudformation/parser/fn_base64.go new file mode 100644 index 000000000000..ad94ed08d6e8 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_base64.go @@ -0,0 +1,19 @@ +package parser + +import ( + "encoding/base64" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveBase64(property *Property) (*Property, bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::Base64"].AsString() + + retVal := base64.StdEncoding.EncodeToString([]byte(refValue)) + + return property.deriveResolved(cftypes.String, retVal), true +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go b/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go new file mode 100644 index 000000000000..7a30827f761c --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go @@ -0,0 +1,35 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "testing" +) + +func Test_resolve_base64_value(t *testing.T) { + + property := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Base64": { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "HelloWorld", + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + + assert.Equal(t, "SGVsbG9Xb3JsZA==", resolvedProperty.AsString()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_builtin.go b/pkg/iac/scanners/cloudformation/parser/fn_builtin.go new file mode 100644 index 000000000000..3fb21dca82de --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_builtin.go @@ -0,0 +1,65 @@ +package parser + +import ( + "fmt" + "net" + + "github.com/apparentlymart/go-cidr/cidr" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func GetAzs(property *Property) (*Property, bool) { + return property.deriveResolved(cftypes.List, []*Property{ + property.deriveResolved(cftypes.String, "us-east-1a"), + property.deriveResolved(cftypes.String, "us-east-1a"), + property.deriveResolved(cftypes.String, "us-east-1a"), + }), true +} + +func GetCidr(property *Property) (*Property, bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::Cidr"] + if refValue.IsNotList() || len(refValue.AsList()) != 3 { + return abortIntrinsic(property, "Fn::Cidr expects a list of 3 attributes") + } + + listParts := refValue.AsList() + ipaddressProp := listParts[0] + ipAddress := "10.0.0.0/2" + if ipaddressProp.IsString() { + ipAddress = ipaddressProp.AsString() + } + count := listParts[1].AsInt() + bit := listParts[2].AsInt() + + ranges, err := calculateCidrs(ipAddress, count, bit, property) + if err != nil { + return abortIntrinsic(property, "Could not calculate the required ranges") + } + return property.deriveResolved(cftypes.List, ranges), true +} + +func calculateCidrs(ipaddress string, count, bit int, original *Property) ([]*Property, error) { + + var cidrProperties []*Property + + _, network, err := net.ParseCIDR(ipaddress) + if err != nil { + return nil, err + } + + for i := 0; i < count; i++ { + next, err := cidr.Subnet(network, bit, i) + if err != nil { + return nil, fmt.Errorf("failed to create cidr blocks") + } + + cidrProperties = append(cidrProperties, original.deriveResolved(cftypes.String, next.String())) + } + + return cidrProperties, nil +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go b/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go new file mode 100644 index 000000000000..9a14029344a8 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go @@ -0,0 +1,63 @@ +package parser + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_cidr_generator(t *testing.T) { + + original := &Property{ + ctx: nil, + name: "cidr", + comment: "", + Inner: PropertyInner{ + Type: "", + Value: nil, + }, + } + + ranges, err := calculateCidrs("10.1.0.0/16", 4, 4, original) + require.Nil(t, err) + require.Len(t, ranges, 4) + + results := make(map[int]string) + for i, property := range ranges { + value := property.AsString() + results[i] = value + } + + assert.Equal(t, "10.1.0.0/20", results[0]) + assert.Equal(t, "10.1.16.0/20", results[1]) + assert.Equal(t, "10.1.32.0/20", results[2]) + assert.Equal(t, "10.1.48.0/20", results[3]) +} + +func Test_cidr_generator_8_bits(t *testing.T) { + original := &Property{ + ctx: nil, + name: "cidr", + comment: "", + Inner: PropertyInner{ + Type: "", + Value: nil, + }, + } + + ranges, err := calculateCidrs("10.1.0.0/16", 4, 8, original) + require.Nil(t, err) + require.Len(t, ranges, 4) + + results := make(map[int]string) + for i, property := range ranges { + value := property.AsString() + results[i] = value + } + + assert.Equal(t, "10.1.0.0/24", results[0]) + assert.Equal(t, "10.1.1.0/24", results[1]) + assert.Equal(t, "10.1.2.0/24", results[2]) + assert.Equal(t, "10.1.3.0/24", results[3]) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_condition.go b/pkg/iac/scanners/cloudformation/parser/fn_condition.go new file mode 100644 index 000000000000..8d5c923936ab --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_condition.go @@ -0,0 +1,21 @@ +package parser + +func ResolveCondition(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refProp := property.AsMap()["Condition"] + if refProp.IsNotString() { + return nil, false + } + refValue := refProp.AsString() + + for k, prop := range property.ctx.Conditions { + if k == refValue { + return prop.resolveValue() + } + } + + return nil, false +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go b/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go new file mode 100644 index 000000000000..0bea529c280e --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go @@ -0,0 +1,98 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolve_condition_value(t *testing.T) { + + fctx := new(FileContext) + fctx.Conditions = map[string]Property{ + "SomeCondition": { + ctx: fctx, + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + ctx: fctx, + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "some val", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "some val", + }, + }, + }, + }, + }, + }, + }, + }, + "EnableVersioning": { + ctx: fctx, + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Condition": { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "SomeCondition", + }, + }, + }, + }, + }, + } + + property := &Property{ + ctx: fctx, + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + ctx: fctx, + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "EnableVersioning", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "Enabled", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "Suspended", + }, + }, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + + assert.Equal(t, "Enabled", resolvedProperty.AsString()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_equals.go b/pkg/iac/scanners/cloudformation/parser/fn_equals.go new file mode 100644 index 000000000000..4043735849a2 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_equals.go @@ -0,0 +1,21 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveEquals(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::Equals"].AsList() + + if len(refValue) != 2 { + return abortIntrinsic(property, "Fn::Equals should have exactly 2 values, returning original Property") + } + + propA, _ := refValue[0].resolveValue() + propB, _ := refValue[1].resolveValue() + return property.deriveResolved(cftypes.Bool, propA.EqualTo(propB.RawValue())), true +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go b/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go new file mode 100644 index 000000000000..e3a806798393 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go @@ -0,0 +1,179 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolve_equals_value(t *testing.T) { + + property := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + + assert.True(t, resolvedProperty.IsTrue()) +} + +func Test_resolve_equals_value_to_false(t *testing.T) { + + property := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + + assert.False(t, resolvedProperty.IsTrue()) +} + +func Test_resolve_equals_value_to_true_when_boolean(t *testing.T) { + + property := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.Bool, + Value: true, + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.Bool, + Value: true, + }, + }, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + assert.True(t, resolvedProperty.IsTrue()) +} + +func Test_resolve_equals_value_when_one_is_a_reference(t *testing.T) { + + property := &Property{ + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "staging", + }, + }, + { + ctx: &FileContext{ + filepath: "", + Parameters: map[string]*Parameter{ + "Environment": { + inner: parameterInner{ + Type: "string", + Default: "staging", + }, + }, + }, + }, + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "Environment", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + + assert.True(t, resolvedProperty.IsTrue()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go b/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go new file mode 100644 index 000000000000..7767f0126456 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go @@ -0,0 +1,45 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveFindInMap(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::FindInMap"].AsList() + + if len(refValue) != 3 { + return abortIntrinsic(property, "Fn::FindInMap should have exactly 3 values, returning original Property") + } + + mapName := refValue[0].AsString() + topLevelKey := refValue[1].AsString() + secondaryLevelKey := refValue[2].AsString() + + if property.ctx == nil { + return abortIntrinsic(property, "the property does not have an attached context, returning original Property") + } + + m, ok := property.ctx.Mappings[mapName] + if !ok { + return abortIntrinsic(property, "could not find map %s, returning original Property") + } + + mapContents := m.(map[string]interface{}) + + k, ok := mapContents[topLevelKey] + if !ok { + return abortIntrinsic(property, "could not find %s in the %s map, returning original Property", topLevelKey, mapName) + } + + mapValues := k.(map[string]interface{}) + + if prop, ok := mapValues[secondaryLevelKey]; !ok { + return abortIntrinsic(property, "could not find a value for %s in %s, returning original Property", secondaryLevelKey, topLevelKey) + } else { + return property.deriveResolved(cftypes.String, prop), true + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_find_in_map_test.go b/pkg/iac/scanners/cloudformation/parser/fn_find_in_map_test.go new file mode 100644 index 000000000000..6063c39fc006 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_find_in_map_test.go @@ -0,0 +1,123 @@ +package parser + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "testing" +) + +func Test_resolve_find_in_map_value(t *testing.T) { + + source := `--- +Parameters: + Environment: + Type: String + Default: production +Mappings: + CacheNodeTypes: + production: + NodeType: cache.t2.large + test: + NodeType: cache.t2.small + dev: + NodeType: cache.t2.micro +Resources: + ElasticacheSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: Elasticache Security Group + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 11211 + ToPort: 11211 + SourceSecurityGroupName: !Ref InstanceSecurityGroup + ElasticacheCluster: + Type: 'AWS::ElastiCache::CacheCluster' + Properties: + Engine: memcached + CacheNodeType: !FindInMap [ CacheNodeTypes, production, NodeType ] + NumCacheNodes: '1' + VpcSecurityGroupIds: + - !GetAtt + - ElasticacheSecurityGroup + - GroupId +` + ctx := createTestFileContext(t, source) + require.NotNil(t, ctx) + + testRes := ctx.GetResourceByLogicalID("ElasticacheCluster") + assert.NotNil(t, testRes) + + nodeTypeProp := testRes.GetStringProperty("CacheNodeType", "") + assert.Equal(t, "cache.t2.large", nodeTypeProp.Value()) +} + +func Test_resolve_find_in_map_with_nested_intrinsic_value(t *testing.T) { + + source := `--- +Parameters: + Environment: + Type: String + Default: dev +Mappings: + CacheNodeTypes: + production: + NodeType: cache.t2.large + test: + NodeType: cache.t2.small + dev: + NodeType: cache.t2.micro +Resources: + ElasticacheSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: Elasticache Security Group + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 11211 + ToPort: 11211 + SourceSecurityGroupName: !Ref InstanceSecurityGroup + ElasticacheCluster: + Type: 'AWS::ElastiCache::CacheCluster' + Properties: + Engine: memcached + CacheNodeType: !FindInMap [ CacheNodeTypes, !Ref Environment, NodeType ] + NumCacheNodes: '1' + VpcSecurityGroupIds: + - !GetAtt + - ElasticacheSecurityGroup + - GroupId +` + ctx := createTestFileContext(t, source) + require.NotNil(t, ctx) + + testRes := ctx.GetResourceByLogicalID("ElasticacheCluster") + assert.NotNil(t, testRes) + + nodeTypeProp := testRes.GetStringProperty("CacheNodeType", "") + assert.Equal(t, "cache.t2.micro", nodeTypeProp.Value()) +} + +func Test_InferType(t *testing.T) { + source := `--- +Mappings: + ApiDB: + MultiAZ: + development: False +Resources: + ApiDB: + Type: AWS::RDS::DBInstance + Properties: + MultiAZ: !FindInMap [ApiDB, MultiAZ, development] +` + + ctx := createTestFileContext(t, source) + require.NotNil(t, ctx) + + testRes := ctx.GetResourceByLogicalID("ApiDB") + require.NotNil(t, testRes) + + nodeTypeProp := testRes.GetBoolProperty("MultiAZ") + assert.False(t, nodeTypeProp.Value()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go b/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go new file mode 100644 index 000000000000..f6754d16a9b3 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go @@ -0,0 +1,46 @@ +package parser + +import ( + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveGetAtt(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValueProp := property.AsMap()["Fn::GetAtt"] + + var refValue []string + + if refValueProp.IsString() { + refValue = strings.Split(refValueProp.AsString(), ".") + } + + if refValueProp.IsList() { + for _, p := range refValueProp.AsList() { + refValue = append(refValue, p.AsString()) + } + } + + if len(refValue) != 2 { + return abortIntrinsic(property, "Fn::GetAtt should have exactly 2 values, returning original Property") + } + + logicalId := refValue[0] + attribute := refValue[1] + + referencedResource := property.ctx.GetResourceByLogicalID(logicalId) + if referencedResource == nil || referencedResource.IsNil() { + return property.deriveResolved(cftypes.String, ""), true + } + + referencedProperty := referencedResource.GetProperty(attribute) + if referencedProperty.IsNil() { + return property.deriveResolved(cftypes.String, referencedResource.ID()), true + } + + return property.deriveResolved(referencedProperty.Type(), referencedProperty.RawValue()), true +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_get_attr_test.go b/pkg/iac/scanners/cloudformation/parser/fn_get_attr_test.go new file mode 100644 index 000000000000..ebd52da035b0 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_get_attr_test.go @@ -0,0 +1,50 @@ +package parser + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "testing" +) + +func Test_resolve_get_attr_value(t *testing.T) { + + source := `--- +Resources: + ElasticacheSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: Elasticache Security Group + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 11211 + ToPort: 11211 + SourceSecurityGroupName: !Ref InstanceSecurityGroup + ElasticacheCluster: + Type: 'AWS::ElastiCache::CacheCluster' + Properties: + Engine: memcached + CacheNodeType: cache.t2.micro + NumCacheNodes: '1' + VpcSecurityGroupIds: + - !GetAtt + - ElasticacheSecurityGroup + - GroupId +` + ctx := createTestFileContext(t, source) + require.NotNil(t, ctx) + + testRes := ctx.GetResourceByLogicalID("ElasticacheCluster") + assert.NotNil(t, testRes) + + sgProp := testRes.GetProperty("VpcSecurityGroupIds") + require.True(t, sgProp.IsNotNil()) + require.True(t, sgProp.IsList()) + + for _, property := range sgProp.AsList() { + resolved, success := ResolveIntrinsicFunc(property) + require.True(t, success) + assert.True(t, resolved.IsNotNil()) + } + +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_if.go b/pkg/iac/scanners/cloudformation/parser/fn_if.go new file mode 100644 index 000000000000..d444952ff38a --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_if.go @@ -0,0 +1,40 @@ +package parser + +func ResolveIf(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::If"].AsList() + + if len(refValue) != 3 { + return abortIntrinsic(property, "Fn::If should have exactly 3 values, returning original Property") + } + + condition, _ := refValue[0].resolveValue() + trueState, _ := refValue[1].resolveValue() + falseState, _ := refValue[2].resolveValue() + + conditionMet := false + + con, _ := condition.resolveValue() + if con.IsBool() { + conditionMet = con.AsBool() + } else if property.ctx.Conditions != nil && + condition.IsString() { + + condition := property.ctx.Conditions[condition.AsString()] + if condition.isFunction() { + con, _ := condition.resolveValue() + if con.IsBool() { + conditionMet = con.AsBool() + } + } + } + + if conditionMet { + return trueState, true + } else { + return falseState, true + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_if_test.go b/pkg/iac/scanners/cloudformation/parser/fn_if_test.go new file mode 100644 index 000000000000..0e5e41bbc963 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_if_test.go @@ -0,0 +1,55 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolve_if_value(t *testing.T) { + + property := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.Bool, + Value: true, + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + + assert.Equal(t, "foo", resolvedProperty.String()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_join.go b/pkg/iac/scanners/cloudformation/parser/fn_join.go new file mode 100644 index 000000000000..e1d39dc702f7 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_join.go @@ -0,0 +1,34 @@ +package parser + +import ( + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveJoin(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::Join"].AsList() + + if len(refValue) != 2 { + return abortIntrinsic(property, "Fn::Join should have exactly 2 values, returning original Property") + } + + joiner := refValue[0].AsString() + items := refValue[1].AsList() + + var itemValues []string + for _, item := range items { + resolved, success := item.resolveValue() + if success { + itemValues = append(itemValues, resolved.AsString()) + } + } + + joined := strings.Join(itemValues, joiner) + + return property.deriveResolved(cftypes.String, joined), true +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_join_test.go b/pkg/iac/scanners/cloudformation/parser/fn_join_test.go new file mode 100644 index 000000000000..8eca9ad5763b --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_join_test.go @@ -0,0 +1,151 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolve_join_value(t *testing.T) { + + property := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Join": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "::", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "s3", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "part1", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "part2", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + + assert.Equal(t, "s3::part1::part2", resolvedProperty.AsString()) +} + +func Test_resolve_join_value_with_reference(t *testing.T) { + + property := &Property{ + ctx: &FileContext{ + filepath: "", + Parameters: map[string]*Parameter{ + "Environment": { + inner: parameterInner{ + Type: "string", + Default: "staging", + }, + }, + }, + }, + name: "EnvironmentBucket", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Join": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "::", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "s3", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "part1", + }, + }, + { + ctx: &FileContext{ + filepath: "", + Parameters: map[string]*Parameter{ + "Environment": { + inner: parameterInner{ + Type: "string", + Default: "staging", + }, + }, + }, + }, + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "Environment", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + + assert.Equal(t, "s3::part1::staging", resolvedProperty.AsString()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_length.go b/pkg/iac/scanners/cloudformation/parser/fn_length.go new file mode 100644 index 000000000000..2026dd4170e9 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_length.go @@ -0,0 +1,26 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveLength(property *Property) (*Property, bool) { + if !property.isFunction() { + return property, true + } + + val := property.AsMap()["Fn::Length"] + if val.IsList() { + return property.deriveResolved(cftypes.Int, val.Len()), true + } else if val.IsMap() { + resolved, _ := val.resolveValue() + + if resolved.IsList() { + return property.deriveResolved(cftypes.Int, resolved.Len()), true + } + return resolved, false + } + + return property, false + +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_length_test.go b/pkg/iac/scanners/cloudformation/parser/fn_length_test.go new file mode 100644 index 000000000000..aa916ad0a972 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_length_test.go @@ -0,0 +1,99 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/stretchr/testify/require" +) + +func Test_ResolveLength_WhenPropIsArray(t *testing.T) { + prop := &Property{ + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Length": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.Int, + Value: 1, + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "IntParameter", + }, + }, + }, + }, + }, + }, + }, + } + resolved, ok := ResolveIntrinsicFunc(prop) + require.True(t, ok) + require.True(t, resolved.IsInt()) + require.Equal(t, 2, resolved.AsInt()) +} + +func Test_ResolveLength_WhenPropIsIntrinsicFunction(t *testing.T) { + fctx := &FileContext{ + Parameters: map[string]*Parameter{ + "SomeParameter": { + inner: parameterInner{ + Type: "string", + Default: "a|b|c|d", + }, + }, + }, + } + prop := &Property{ + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Length": { + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Split": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "|", + }, + }, + { + ctx: fctx, + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "SomeParameter", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + resolved, ok := ResolveIntrinsicFunc(prop) + require.True(t, ok) + require.True(t, resolved.IsInt()) + require.Equal(t, 4, resolved.AsInt()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_not.go b/pkg/iac/scanners/cloudformation/parser/fn_not.go new file mode 100644 index 000000000000..fa76db76319b --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_not.go @@ -0,0 +1,25 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveNot(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::Not"].AsList() + + if len(refValue) != 1 { + return abortIntrinsic(property, "Fn::No should have at only 1 values, returning original Property") + } + + funcToInvert, _ := refValue[0].resolveValue() + + if funcToInvert.IsBool() { + return property.deriveResolved(cftypes.Bool, !funcToInvert.AsBool()), true + } + + return property, false +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_not_test.go b/pkg/iac/scanners/cloudformation/parser/fn_not_test.go new file mode 100644 index 000000000000..44df6fa6d421 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_not_test.go @@ -0,0 +1,123 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolve_not_value(t *testing.T) { + property1 := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + } + + notProperty := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Not": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + property1, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(notProperty) + require.True(t, success) + + assert.True(t, resolvedProperty.IsTrue()) +} + +func Test_resolve_not_value_when_true(t *testing.T) { + property1 := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + } + + notProperty := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Not": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + property1, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(notProperty) + require.True(t, success) + + assert.False(t, resolvedProperty.IsTrue()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_or.go b/pkg/iac/scanners/cloudformation/parser/fn_or.go new file mode 100644 index 000000000000..48fd802d1065 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_or.go @@ -0,0 +1,41 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveOr(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::Or"].AsList() + + if len(refValue) < 2 { + return abortIntrinsic(property, "Fn::Or should have at least 2 values, returning original Property") + } + + results := make([]bool, len(refValue)) + for i := 0; i < len(refValue); i++ { + + r := false + if refValue[i].IsBool() { + r = refValue[i].AsBool() + } + + results[i] = r + } + + atleastOne := atleastOne(results) + return property.deriveResolved(cftypes.Bool, atleastOne), true +} + +func atleastOne(a []bool) bool { + for _, b := range a { + if b { + return true + } + } + + return false +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_or_test.go b/pkg/iac/scanners/cloudformation/parser/fn_or_test.go new file mode 100644 index 000000000000..18340031434b --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_or_test.go @@ -0,0 +1,183 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolve_or_value(t *testing.T) { + property1 := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + } + + property2 := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + } + orProperty := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Or": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + property1, + property2, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(orProperty) + require.True(t, success) + + assert.True(t, resolvedProperty.IsTrue()) +} + +func Test_resolve_or_value_when_neither_true(t *testing.T) { + property1 := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + } + + property2 := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "bar", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + } + orProperty := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Or": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + property1, + property2, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(orProperty) + require.True(t, success) + + assert.False(t, resolvedProperty.IsTrue()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_ref.go b/pkg/iac/scanners/cloudformation/parser/fn_ref.go new file mode 100644 index 000000000000..afc6ead7cf20 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_ref.go @@ -0,0 +1,55 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveReference(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refProp := property.AsMap()["Ref"] + if refProp.IsNotString() { + return property, false + } + refValue := refProp.AsString() + + if pseudo, ok := pseudoParameters[refValue]; ok { + return property.deriveResolved(pseudo.t, pseudo.val), true + } + + if property.ctx == nil { + return property, false + } + + var param *Parameter + for k := range property.ctx.Parameters { + if k != refValue { + continue + } + param = property.ctx.Parameters[k] + resolvedType := param.Type() + + switch param.Default().(type) { + case bool: + resolvedType = cftypes.Bool + case string: + resolvedType = cftypes.String + case int: + resolvedType = cftypes.Int + } + + resolved = property.deriveResolved(resolvedType, param.Default()) + return resolved, true + } + + for k := range property.ctx.Resources { + if k == refValue { + res := property.ctx.Resources[k] + resolved = property.deriveResolved(cftypes.String, res.ID()) + break + } + } + return resolved, true +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go b/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go new file mode 100644 index 000000000000..a535b30386e2 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go @@ -0,0 +1,88 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolve_referenced_value(t *testing.T) { + + property := &Property{ + ctx: &FileContext{ + filepath: "", + Parameters: map[string]*Parameter{ + "BucketName": { + inner: parameterInner{ + Type: "string", + Default: "someBucketName", + }, + }, + }, + }, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "BucketName", + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + + assert.Equal(t, "someBucketName", resolvedProperty.AsString()) +} + +func Test_property_value_correct_when_not_reference(t *testing.T) { + + property := &Property{ + ctx: &FileContext{ + filepath: "", + }, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.String, + Value: "someBucketName", + }, + } + + // should fail when trying to resolve function that is not in fact a function + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.False(t, success) + + assert.Equal(t, "someBucketName", resolvedProperty.AsString()) +} + +func Test_resolve_ref_with_pseudo_value(t *testing.T) { + source := `--- +Resources: + TestInstance: + Type: AWS::EC2::Instance + Properties: + ImageId: "ami-79fd7eee" + KeyName: !Join [":", ["aws", !Ref AWS::Region, "key" ]] +` + ctx := createTestFileContext(t, source) + require.NotNil(t, ctx) + + testRes := ctx.GetResourceByLogicalID("TestInstance") + require.NotNil(t, testRes) + + keyNameProp := testRes.GetProperty("KeyName") + require.NotNil(t, keyNameProp) + + assert.Equal(t, "aws:eu-west-1:key", keyNameProp.AsString()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_select.go b/pkg/iac/scanners/cloudformation/parser/fn_select.go new file mode 100644 index 000000000000..c528223a2325 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_select.go @@ -0,0 +1,41 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveSelect(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::Select"].AsList() + + if len(refValue) != 2 { + return abortIntrinsic(property, "Fn::Select should have exactly 2 values, returning original Property") + } + + index := refValue[0] + list := refValue[1] + + if index.IsNotInt() { + if index.IsConvertableTo(cftypes.Int) { + // + index = index.ConvertTo(cftypes.Int) + } else { + return abortIntrinsic(property, "index on property [%s] should be an int, returning original Property", property.name) + } + } + + if list.IsNotList() { + return abortIntrinsic(property, "list on property [%s] should be a list, returning original Property", property.name) + } + + listItems := list.AsList() + + if len(listItems) <= index.AsInt() { + return nil, false + } + + return listItems[index.AsInt()], true +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_select_test.go b/pkg/iac/scanners/cloudformation/parser/fn_select_test.go new file mode 100644 index 000000000000..92b634457b2d --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_select_test.go @@ -0,0 +1,77 @@ +package parser + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolve_select_value(t *testing.T) { + + source := `--- +Parameters: + EngineIndex: + Type: Integer + Default: 1 +Resources: + ElasticacheCluster: + Type: 'AWS::ElastiCache::CacheCluster' + Properties: + Engine: !Select [ !Ref EngineIndex, [memcached, redis ]] + CacheNodeType: cache.t2.micro + NumCacheNodes: '1' +` + ctx := createTestFileContext(t, source) + require.NotNil(t, ctx) + + testRes := ctx.GetResourceByLogicalID("ElasticacheCluster") + assert.NotNil(t, testRes) + + engineProp := testRes.GetProperty("Engine") + require.True(t, engineProp.IsNotNil()) + require.True(t, engineProp.IsString()) + + require.Equal(t, "redis", engineProp.AsString()) +} + +func Test_SelectPseudoListParam(t *testing.T) { + src := `--- +Resources: + myASGrpOne: + Type: AWS::AutoScaling::AutoScalingGroup + Version: "2009-05-15" + Properties: + AvailabilityZones: + - "us-east-1a" + LaunchConfigurationName: + Ref: MyLaunchConfiguration + MinSize: "0" + MaxSize: "0" + NotificationConfigurations: + - TopicARN: + Fn::Select: + - "1" + - Ref: AWS::NotificationARNs + NotificationTypes: + - autoscaling:EC2_INSTANCE_LAUNCH + - autoscaling:EC2_INSTANCE_LAUNCH_ERROR + +` + + ctx := createTestFileContext(t, src) + require.NotNil(t, ctx) + + resource := ctx.GetResourceByLogicalID("myASGrpOne") + require.NotNil(t, resource) + + notification := resource.GetProperty("NotificationConfigurations") + require.True(t, notification.IsNotNil()) + require.True(t, notification.IsList()) + first := notification.AsList()[0] + require.True(t, first.IsMap()) + topic, ok := first.AsMap()["TopicARN"] + require.True(t, ok) + require.Equal(t, "notification::arn::2", topic.AsString()) + +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_split.go b/pkg/iac/scanners/cloudformation/parser/fn_split.go new file mode 100644 index 000000000000..cddda20ef190 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_split.go @@ -0,0 +1,44 @@ +package parser + +import ( + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveSplit(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::Split"].AsList() + + if len(refValue) != 2 { + return abortIntrinsic(property, "Fn::Split should have exactly 2 values, returning original Property") + } + + delimiterProp := refValue[0] + splitProp := refValue[1] + + if !splitProp.IsString() || !delimiterProp.IsString() { + abortIntrinsic(property, "Fn::Split requires two strings as input, returning original Property") + + } + + propertyList := createPropertyList(splitProp, delimiterProp, property) + + return property.deriveResolved(cftypes.List, propertyList), true +} + +func createPropertyList(splitProp, delimiterProp, parent *Property) []*Property { + + splitString := splitProp.AsString() + delimiter := delimiterProp.AsString() + + splits := strings.Split(splitString, delimiter) + var props []*Property + for _, split := range splits { + props = append(props, parent.deriveResolved(cftypes.String, split)) + } + return props +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_split_test.go b/pkg/iac/scanners/cloudformation/parser/fn_split_test.go new file mode 100644 index 000000000000..59261ff57a20 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_split_test.go @@ -0,0 +1,56 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "testing" +) + +/* + Fn::Split: ["::", "s3::bucket::to::split"] + +*/ + +func Test_resolve_split_value(t *testing.T) { + + property := &Property{ + ctx: &FileContext{}, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Split": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "::", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "s3::bucket::to::split", + }, + }, + }, + }, + }, + }, + }, + } + + resolvedProperty, success := ResolveIntrinsicFunc(property) + require.True(t, success) + assert.True(t, resolvedProperty.IsNotNil()) + assert.True(t, resolvedProperty.IsList()) + listContents := resolvedProperty.AsList() + assert.Len(t, listContents, 4) + +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_sub.go b/pkg/iac/scanners/cloudformation/parser/fn_sub.go new file mode 100644 index 000000000000..52db66cf9757 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_sub.go @@ -0,0 +1,71 @@ +package parser + +import ( + "fmt" + "strconv" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func ResolveSub(property *Property) (resolved *Property, success bool) { + if !property.isFunction() { + return property, true + } + + refValue := property.AsMap()["Fn::Sub"] + + if refValue.IsString() { + return resolveStringSub(refValue, property), true + } + + if refValue.IsList() { + return resolveMapSub(refValue, property) + } + + return property, false +} + +func resolveMapSub(refValue, original *Property) (*Property, bool) { + refValues := refValue.AsList() + if len(refValues) != 2 { + return abortIntrinsic(original, "Fn::Sub with list expects 2 values, returning original property") + } + + workingString := refValues[0].AsString() + components := refValues[1].AsMap() + + for k, v := range components { + replacement := "[failed to resolve]" + switch v.Type() { + case cftypes.Map: + resolved, _ := ResolveIntrinsicFunc(v) + replacement = resolved.AsString() + case cftypes.String: + replacement = v.AsString() + case cftypes.Int: + replacement = strconv.Itoa(v.AsInt()) + case cftypes.Bool: + replacement = fmt.Sprintf("%v", v.AsBool()) + case cftypes.List: + var parts []string + for _, p := range v.AsList() { + parts = append(parts, p.String()) + } + replacement = fmt.Sprintf("[%s]", strings.Join(parts, ", ")) + } + workingString = strings.ReplaceAll(workingString, fmt.Sprintf("${%s}", k), replacement) + } + + return original.deriveResolved(cftypes.String, workingString), true +} + +func resolveStringSub(refValue, original *Property) *Property { + workingString := refValue.AsString() + + for k, param := range pseudoParameters { + workingString = strings.ReplaceAll(workingString, fmt.Sprintf("${%s}", k), fmt.Sprintf("%v", param.getRawValue())) + } + + return original.deriveResolved(cftypes.String, workingString) +} diff --git a/pkg/iac/scanners/cloudformation/parser/fn_sub_test.go b/pkg/iac/scanners/cloudformation/parser/fn_sub_test.go new file mode 100644 index 000000000000..5ab98a59692b --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/fn_sub_test.go @@ -0,0 +1,103 @@ +package parser + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolve_sub_value(t *testing.T) { + source := `--- +Resources: + TestInstance: + Type: AWS::EC2::Instance + Properties: + ImageId: "ami-79fd7eee" + KeyName: "testkey" + UserData: + !Sub | + #!/bin/bash -xe + yum update -y aws-cfn-bootstrap + /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfig --configsets wordpress_install --region ${AWS::Region} + /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region} +` + ctx := createTestFileContext(t, source) + require.NotNil(t, ctx) + + testRes := ctx.GetResourceByLogicalID("TestInstance") + require.NotNil(t, testRes) + + userDataProp := testRes.GetProperty("UserData") + require.NotNil(t, userDataProp) + + assert.Equal(t, "#!/bin/bash -xe\nyum update -y aws-cfn-bootstrap\n/opt/aws/bin/cfn-init -v --stack cfsec-test-stack --resource LaunchConfig --configsets wordpress_install --region eu-west-1\n/opt/aws/bin/cfn-signal -e $? --stack cfsec-test-stack --resource WebServerGroup --region eu-west-1\n", userDataProp.AsString()) +} + +func Test_resolve_sub_value_with_base64(t *testing.T) { + + source := `--- +Resources: + TestInstance: + Type: AWS::EC2::Instance + Properties: + ImageId: "ami-79fd7eee" + KeyName: "testkey" + UserData: + Fn::Base64: + !Sub | + #!/bin/bash -xe + yum update -y aws-cfn-bootstrap + /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfig --configsets wordpress_install --region ${AWS::Region} + /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region}` + ctx := createTestFileContext(t, source) + require.NotNil(t, ctx) + + testRes := ctx.GetResourceByLogicalID("TestInstance") + require.NotNil(t, testRes) + + userDataProp := testRes.GetProperty("UserData") + require.NotNil(t, userDataProp) + + assert.Equal(t, "IyEvYmluL2Jhc2ggLXhlCnl1bSB1cGRhdGUgLXkgYXdzLWNmbi1ib290c3RyYXAKL29wdC9hd3MvYmluL2Nmbi1pbml0IC12IC0tc3RhY2sgY2ZzZWMtdGVzdC1zdGFjayAtLXJlc291cmNlIExhdW5jaENvbmZpZyAtLWNvbmZpZ3NldHMgd29yZHByZXNzX2luc3RhbGwgLS1yZWdpb24gZXUtd2VzdC0xCi9vcHQvYXdzL2Jpbi9jZm4tc2lnbmFsIC1lICQ/IC0tc3RhY2sgY2ZzZWMtdGVzdC1zdGFjayAtLXJlc291cmNlIFdlYlNlcnZlckdyb3VwIC0tcmVnaW9uIGV1LXdlc3QtMQ==", userDataProp.AsString()) +} + +func Test_resolve_sub_value_with_map(t *testing.T) { + + source := `--- +Parameters: + RootDomainName: + Type: String + Default: somedomain.com +Resources: + TestDistribution: + Type: AWS::CloudFront::Distribution + Properties: + DistributionConfig: + DefaultCacheBehavior: + TargetOriginId: target + ViewerProtocolPolicy: https-only + Enabled: true + Origins: + - DomainName: + !Sub + - www.${Domain} + - { Domain: !Ref RootDomainName } + Id: somedomain1 + + +` + ctx := createTestFileContext(t, source) + require.NotNil(t, ctx) + + testRes := ctx.GetResourceByLogicalID("TestDistribution") + require.NotNil(t, testRes) + + originsList := testRes.GetProperty("DistributionConfig.Origins") + + domainNameProp := originsList.AsList()[0].GetProperty("DomainName") + require.NotNil(t, domainNameProp) + + assert.Equal(t, "www.somedomain.com", domainNameProp.AsString()) + +} diff --git a/pkg/iac/scanners/cloudformation/parser/intrinsics.go b/pkg/iac/scanners/cloudformation/parser/intrinsics.go new file mode 100644 index 000000000000..1dadc4f6d6fd --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/intrinsics.go @@ -0,0 +1,106 @@ +package parser + +import ( + "fmt" + "strings" + + "gopkg.in/yaml.v3" +) + +var intrinsicFuncs map[string]func(property *Property) (*Property, bool) + +func init() { + intrinsicFuncs = map[string]func(property *Property) (*Property, bool){ + "Ref": ResolveReference, + "Fn::Base64": ResolveBase64, + "Fn::Equals": ResolveEquals, + "Fn::Join": ResolveJoin, + "Fn::Split": ResolveSplit, + "Fn::Sub": ResolveSub, + "Fn::FindInMap": ResolveFindInMap, + "Fn::Select": ResolveSelect, + "Fn::GetAtt": ResolveGetAtt, + "Fn::GetAZs": GetAzs, + "Fn::Cidr": GetCidr, + "Fn::ImportValue": ImportPlaceholder, + "Fn::If": ResolveIf, + "Fn::And": ResolveAnd, + "Fn::Or": ResolveOr, + "Fn::Not": ResolveNot, + "Fn::Length": ResolveLength, + "Condition": ResolveCondition, + } +} + +func ImportPlaceholder(property *Property) (*Property, bool) { + property.unresolved = true + return property, false +} + +func PassthroughResolution(property *Property) (*Property, bool) { + return property, false +} + +func IsIntrinsicFunc(node *yaml.Node) bool { + if node == nil || node.Tag == "" { + return false + } + + nodeTag := strings.TrimPrefix(node.Tag, "!") + if nodeTag != "Ref" && nodeTag != "Condition" { + nodeTag = fmt.Sprintf("Fn::%s", nodeTag) + } + for tag := range intrinsicFuncs { + + if nodeTag == tag { + return true + } + } + return false +} + +func IsIntrinsic(key string) bool { + for tag := range intrinsicFuncs { + if tag == key { + return true + } + } + return false +} + +func ResolveIntrinsicFunc(property *Property) (*Property, bool) { + if property == nil { + return nil, false + } + if !property.IsMap() { + return property, false + } + + for funcName := range property.AsMap() { + if fn := intrinsicFuncs[funcName]; fn != nil { + prop, resolved := fn(property) + if prop == nil || !resolved { + return prop, false + } + + prop.inferType() + return prop, true + } + } + return property, false +} + +func getIntrinsicTag(tag string) string { + tag = strings.TrimPrefix(tag, "!") + switch tag { + case "Ref", "Contains": + return tag + default: + return fmt.Sprintf("Fn::%s", tag) + } +} + +func abortIntrinsic(property *Property, msg string, components ...string) (*Property, bool) { + // + return property, false +} diff --git a/pkg/iac/scanners/cloudformation/parser/intrinsics_test.go b/pkg/iac/scanners/cloudformation/parser/intrinsics_test.go new file mode 100644 index 000000000000..a69e04dd0fba --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/intrinsics_test.go @@ -0,0 +1,45 @@ +package parser + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" +) + +func Test_is_intrinsic_returns_expected(t *testing.T) { + + testCases := []struct { + nodeTag string + expectedResult bool + }{ + { + nodeTag: "!Ref", + expectedResult: true, + }, + { + nodeTag: "!Join", + expectedResult: true, + }, + { + nodeTag: "!Sub", + expectedResult: true, + }, + { + nodeTag: "!Equals", + expectedResult: true, + }, + { + nodeTag: "!Equal", + expectedResult: false, + }, + } + + for _, tt := range testCases { + n := &yaml.Node{ + Tag: tt.nodeTag, + } + assert.Equal(t, tt.expectedResult, IsIntrinsicFunc(n)) + } + +} diff --git a/pkg/iac/scanners/cloudformation/parser/parameter.go b/pkg/iac/scanners/cloudformation/parser/parameter.go new file mode 100644 index 000000000000..ecd727270022 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/parameter.go @@ -0,0 +1,127 @@ +package parser + +import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/liamg/jfather" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +type Parameter struct { + inner parameterInner +} + +type parameterInner struct { + Type string `yaml:"Type"` + Default interface{} `yaml:"Default"` +} + +func (p *Parameter) UnmarshalYAML(node *yaml.Node) error { + return node.Decode(&p.inner) +} + +func (p *Parameter) UnmarshalJSONWithMetadata(node jfather.Node) error { + return node.Decode(&p.inner) +} + +func (p *Parameter) Type() cftypes.CfType { + switch p.inner.Type { + case "Boolean": + return cftypes.Bool + case "String": + return cftypes.String + case "Integer": + return cftypes.Int + default: + return cftypes.String + } +} + +func (p *Parameter) Default() interface{} { + return p.inner.Default +} + +func (p *Parameter) UpdateDefault(inVal interface{}) { + passedVal := inVal.(string) + + switch p.inner.Type { + case "Boolean": + p.inner.Default, _ = strconv.ParseBool(passedVal) + case "String": + p.inner.Default = passedVal + case "Integer": + p.inner.Default, _ = strconv.Atoi(passedVal) + default: + p.inner.Default = passedVal + } +} + +type Parameters map[string]any + +func (p *Parameters) Merge(other Parameters) { + for k, v := range other { + (*p)[k] = v + } +} + +func (p *Parameters) UnmarshalJSON(data []byte) error { + (*p) = make(Parameters) + + if len(data) == 0 { + return nil + } + + switch { + case data[0] == '{' && data[len(data)-1] == '}': // object + // CodePipeline like format + var params struct { + Params map[string]any `json:"Parameters"` + } + + if err := json.Unmarshal(data, ¶ms); err != nil { + return err + } + + (*p) = params.Params + case data[0] == '[' && data[len(data)-1] == ']': // array + // Original format + var params []string + + if err := json.Unmarshal(data, ¶ms); err == nil { + for _, param := range params { + parts := strings.Split(param, "=") + if len(parts) != 2 { + return fmt.Errorf("invalid key-value parameter: %q", param) + } + (*p)[parts[0]] = parts[1] + } + return nil + } + + // CloudFormation like format + var cfparams []struct { + ParameterKey string `json:"ParameterKey"` + ParameterValue string `json:"ParameterValue"` + } + + d := json.NewDecoder(bytes.NewReader(data)) + d.DisallowUnknownFields() + if err := d.Decode(&cfparams); err != nil { + return err + } + + for _, param := range cfparams { + (*p)[param.ParameterKey] = param.ParameterValue + } + default: + return fmt.Errorf("unsupported parameters format") + } + + return nil +} diff --git a/pkg/iac/scanners/cloudformation/parser/parameters_test.go b/pkg/iac/scanners/cloudformation/parser/parameters_test.go new file mode 100644 index 000000000000..703f07f5fe12 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/parameters_test.go @@ -0,0 +1,89 @@ +package parser + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParameters_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + source string + expected Parameters + wantErr bool + }{ + { + name: "original format", + source: `[ + "Key1=Value1", + "Key2=Value2" + ]`, + expected: map[string]any{ + "Key1": "Value1", + "Key2": "Value2", + }, + }, + { + name: "CloudFormation like format", + source: `[ + { + "ParameterKey": "Key1", + "ParameterValue": "Value1" + }, + { + "ParameterKey": "Key2", + "ParameterValue": "Value2" + } + ]`, + expected: map[string]any{ + "Key1": "Value1", + "Key2": "Value2", + }, + }, + { + name: "CloudFormation like format, with unknown fields", + source: `[ + { + "ParameterKey": "Key1", + "ParameterValue": "Value1" + }, + { + "ParameterKey": "Key2", + "ParameterValue": "Value2", + "UsePreviousValue": true + } + ]`, + wantErr: true, + }, + { + name: "CodePipeline like format", + source: `{ + "Parameters": { + "Key1": "Value1", + "Key2": "Value2" + } + }`, + expected: map[string]any{ + "Key1": "Value1", + "Key2": "Value2", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var params Parameters + + err := json.Unmarshal([]byte(tt.source), ¶ms) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.expected, params) + }) + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/parser.go b/pkg/iac/scanners/cloudformation/parser/parser.go new file mode 100644 index 000000000000..65bf1440432d --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/parser.go @@ -0,0 +1,239 @@ +package parser + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "path/filepath" + "strings" + + "github.com/liamg/jfather" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/ignore" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +var _ options.ConfigurableParser = (*Parser)(nil) + +type Parser struct { + debug debug.Logger + skipRequired bool + parameterFiles []string + parameters map[string]any + overridedParameters Parameters + configsFS fs.FS +} + +func WithParameters(params map[string]any) options.ParserOption { + return func(cp options.ConfigurableParser) { + if p, ok := cp.(*Parser); ok { + p.parameters = params + } + } +} + +func WithParameterFiles(files ...string) options.ParserOption { + return func(cp options.ConfigurableParser) { + if p, ok := cp.(*Parser); ok { + p.parameterFiles = files + } + } +} + +func WithConfigsFS(fsys fs.FS) options.ParserOption { + return func(cp options.ConfigurableParser) { + if p, ok := cp.(*Parser); ok { + p.configsFS = fsys + } + } +} + +func (p *Parser) SetDebugWriter(writer io.Writer) { + p.debug = debug.New(writer, "cloudformation", "parser") +} + +func (p *Parser) SetSkipRequiredCheck(b bool) { + p.skipRequired = b +} + +func New(opts ...options.ParserOption) *Parser { + p := &Parser{} + for _, option := range opts { + option(p) + } + return p +} + +func (p *Parser) ParseFS(ctx context.Context, fsys fs.FS, dir string) (FileContexts, error) { + var contexts FileContexts + if err := fs.WalkDir(fsys, filepath.ToSlash(dir), func(path string, entry fs.DirEntry, err error) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if err != nil { + return err + } + if entry.IsDir() { + return nil + } + + if !p.Required(fsys, path) { + p.debug.Log("not a CloudFormation file, skipping %s", path) + return nil + } + + c, err := p.ParseFile(ctx, fsys, path) + if err != nil { + p.debug.Log("Error parsing file '%s': %s", path, err) + return nil + } + contexts = append(contexts, c) + return nil + }); err != nil { + return nil, err + } + return contexts, nil +} + +func (p *Parser) Required(fsys fs.FS, path string) bool { + if p.skipRequired { + return true + } + + f, err := fsys.Open(filepath.ToSlash(path)) + if err != nil { + return false + } + defer func() { _ = f.Close() }() + if data, err := io.ReadAll(f); err == nil { + return detection.IsType(path, bytes.NewReader(data), detection.FileTypeCloudFormation) + } + return false + +} + +func (p *Parser) ParseFile(ctx context.Context, fsys fs.FS, path string) (fctx *FileContext, err error) { + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("panic during parse: %s", e) + } + }() + + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + if p.configsFS == nil { + p.configsFS = fsys + } + + if err := p.parseParams(); err != nil { + return nil, fmt.Errorf("failed to parse parameters file: %w", err) + } + + sourceFmt := YamlSourceFormat + if strings.HasSuffix(strings.ToLower(path), ".json") { + sourceFmt = JsonSourceFormat + } + + f, err := fsys.Open(filepath.ToSlash(path)) + if err != nil { + return nil, err + } + defer func() { _ = f.Close() }() + + content, err := io.ReadAll(f) + if err != nil { + return nil, err + } + + lines := strings.Split(string(content), "\n") + + fctx = &FileContext{ + filepath: path, + lines: lines, + SourceFormat: sourceFmt, + } + + switch sourceFmt { + case YamlSourceFormat: + if err := yaml.Unmarshal(content, fctx); err != nil { + return nil, NewErrInvalidContent(path, err) + } + fctx.Ignores = ignore.Parse(string(content), path) + case JsonSourceFormat: + if err := jfather.Unmarshal(content, fctx); err != nil { + return nil, NewErrInvalidContent(path, err) + } + } + + fctx.OverrideParameters(p.overridedParameters) + + fctx.lines = lines + fctx.SourceFormat = sourceFmt + fctx.filepath = path + + p.debug.Log("Context loaded from source %s", path) + + // the context must be set to conditions before resources + for _, c := range fctx.Conditions { + c.setContext(fctx) + } + + for name, r := range fctx.Resources { + r.ConfigureResource(name, fsys, path, fctx) + } + + return fctx, nil +} + +func (p *Parser) parseParams() error { + if p.overridedParameters != nil { // parameters have already been parsed + return nil + } + + params := make(Parameters) + + var errs []error + + for _, path := range p.parameterFiles { + if parameters, err := p.parseParametersFile(path); err != nil { + errs = append(errs, err) + } else { + params.Merge(parameters) + } + } + + if len(errs) != 0 { + return errors.Join(errs...) + } + + params.Merge(p.parameters) + + p.overridedParameters = params + return nil +} + +func (p *Parser) parseParametersFile(path string) (Parameters, error) { + f, err := p.configsFS.Open(path) + if err != nil { + return nil, fmt.Errorf("parameters file %q open error: %w", path, err) + } + + var parameters Parameters + if err := json.NewDecoder(f).Decode(¶meters); err != nil { + return nil, err + } + return parameters, nil +} diff --git a/pkg/iac/scanners/cloudformation/parser/parser_test.go b/pkg/iac/scanners/cloudformation/parser/parser_test.go new file mode 100644 index 000000000000..3a517552af57 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/parser_test.go @@ -0,0 +1,373 @@ +package parser + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func parseFile(t *testing.T, source string, name string) (FileContexts, error) { + tmp, err := os.MkdirTemp(os.TempDir(), "defsec") + require.NoError(t, err) + defer func() { _ = os.RemoveAll(tmp) }() + require.NoError(t, os.WriteFile(filepath.Join(tmp, name), []byte(source), 0600)) + fs := os.DirFS(tmp) + return New().ParseFS(context.TODO(), fs, ".") +} + +func Test_parse_yaml(t *testing.T) { + + source := `--- +Parameters: + BucketName: + Type: String + Default: naughty + EncryptBucket: + Type: Boolean + Default: false +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: naughty + BucketEncryption: + ServerSideEncryptionConfiguration: + - BucketKeyEnabled: + Ref: EncryptBucket` + + files, err := parseFile(t, source, "cf.yaml") + require.NoError(t, err) + assert.Len(t, files, 1) + file := files[0] + + assert.Len(t, file.Resources, 1) + assert.Len(t, file.Parameters, 2) + + bucket, ok := file.Resources["S3Bucket"] + require.True(t, ok, "S3Bucket resource should be available") + assert.Equal(t, "cf.yaml", bucket.Range().GetFilename()) + assert.Equal(t, 10, bucket.Range().GetStartLine()) + assert.Equal(t, 17, bucket.Range().GetEndLine()) +} + +func Test_parse_json(t *testing.T) { + source := `{ + "Parameters": { + "BucketName": { + "Type": "String", + "Default": "naughty" + }, + "BucketKeyEnabled": { + "Type": "Boolean", + "Default": false + } + }, + "Resources": { + "S3Bucket": { + "Type": "AWS::S3::Bucket", + "properties": { + "BucketName": { + "Ref": "BucketName" + }, + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "BucketKeyEnabled": { + "Ref": "BucketKeyEnabled" + } + } + ] + } + } + } + } +} +` + + files, err := parseFile(t, source, "cf.json") + require.NoError(t, err) + assert.Len(t, files, 1) + file := files[0] + + assert.Len(t, file.Resources, 1) + assert.Len(t, file.Parameters, 2) +} + +func Test_parse_yaml_with_map_ref(t *testing.T) { + + source := `--- +Parameters: + BucketName: + Type: String + Default: referencedBucket + EncryptBucket: + Type: Boolean + Default: false +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: + Ref: BucketName + BucketEncryption: + ServerSideEncryptionConfiguration: + - BucketKeyEnabled: + Ref: EncryptBucket` + + files, err := parseFile(t, source, "cf.yaml") + require.NoError(t, err) + assert.Len(t, files, 1) + file := files[0] + + assert.Len(t, file.Resources, 1) + assert.Len(t, file.Parameters, 2) + + res := file.GetResourceByLogicalID("S3Bucket") + assert.NotNil(t, res) + + refProp := res.GetProperty("BucketName") + assert.False(t, refProp.IsNil()) + assert.Equal(t, "referencedBucket", refProp.AsString()) +} + +func Test_parse_yaml_with_intrinsic_functions(t *testing.T) { + + source := `--- +Parameters: + BucketName: + Type: String + Default: somebucket + EncryptBucket: + Type: Boolean + Default: false +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: !Ref BucketName + BucketEncryption: + ServerSideEncryptionConfiguration: + - BucketKeyEnabled: false +` + + files, err := parseFile(t, source, "cf.yaml") + require.NoError(t, err) + assert.Len(t, files, 1) + ctx := files[0] + + assert.Len(t, ctx.Resources, 1) + assert.Len(t, ctx.Parameters, 2) + + res := ctx.GetResourceByLogicalID("S3Bucket") + assert.NotNil(t, res) + + refProp := res.GetProperty("BucketName") + assert.False(t, refProp.IsNil()) + assert.Equal(t, "somebucket", refProp.AsString()) +} + +func createTestFileContext(t *testing.T, source string) *FileContext { + contexts, err := parseFile(t, source, "main.yaml") + require.NoError(t, err) + require.Len(t, contexts, 1) + return contexts[0] +} + +func Test_parse_yaml_use_condition_in_resource(t *testing.T) { + source := `--- +AWSTemplateFormatVersion: "2010-09-09" +Description: some description +Parameters: + ServiceName: + Type: String + Description: The service name + EnvName: + Type: String + Description: Optional environment name to prefix all resources with + Default: "" + +Conditions: + SuffixResources: !Not [!Equals [!Ref EnvName, ""]] + +Resources: + ErrorTimedOutMetricFilter: + Type: AWS::Logs::MetricFilter + Properties: + FilterPattern: '?ERROR ?error ?Error ?"timed out"' # If log contains one of these error words or timed out + LogGroupName: + !If [ + SuffixResources, + !Sub "/aws/lambda/${ServiceName}-${EnvName}", + !Sub "/aws/lambda/${ServiceName}", + ] + MetricTransformations: + - MetricName: !Sub "${ServiceName}-ErrorLogCount" + MetricNamespace: market-LogMetrics + MetricValue: 1 + DefaultValue: 0 +` + + files, err := parseFile(t, source, "cf.yaml") + require.NoError(t, err) + assert.Len(t, files, 1) + ctx := files[0] + + assert.Len(t, ctx.Parameters, 2) + assert.Len(t, ctx.Conditions, 1) + assert.Len(t, ctx.Resources, 1) + + res := ctx.GetResourceByLogicalID("ErrorTimedOutMetricFilter") + assert.NotNil(t, res) + + refProp := res.GetProperty("LogGroupName") + assert.False(t, refProp.IsNil()) + assert.Equal(t, "/aws/lambda/${ServiceName}", refProp.AsString()) +} + +func TestParse_WithParameters(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "main.yaml": `AWSTemplateFormatVersion: 2010-09-09 +Parameters: + KmsMasterKeyId: + Type: String +Resources: + TestQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: test-queue + KmsMasterKeyId: !Ref KmsMasterKeyId + `, + }) + + params := map[string]any{ + "KmsMasterKeyId": "some_id", + } + p := New(WithParameters(params)) + + files, err := p.ParseFS(context.TODO(), fs, ".") + require.NoError(t, err) + require.Len(t, files, 1) + + file := files[0] + res := file.GetResourceByLogicalID("TestQueue") + assert.NotNil(t, res) + + kmsProp := res.GetProperty("KmsMasterKeyId") + assert.False(t, kmsProp.IsNil()) + assert.Equal(t, "some_id", kmsProp.AsString()) +} + +func TestParse_WithParameterFiles(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.yaml": `AWSTemplateFormatVersion: 2010-09-09 +Parameters: + KmsMasterKeyId: + Type: String +Resources: + TestQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: test-queue + KmsMasterKeyId: !Ref KmsMasterKeyId +`, + "params.json": `[ + { + "ParameterKey": "KmsMasterKeyId", + "ParameterValue": "some_id" + } +] + `, + }) + + p := New(WithParameterFiles("params.json")) + + files, err := p.ParseFS(context.TODO(), fs, ".") + require.NoError(t, err) + require.Len(t, files, 1) + + file := files[0] + res := file.GetResourceByLogicalID("TestQueue") + assert.NotNil(t, res) + + kmsProp := res.GetProperty("KmsMasterKeyId") + assert.False(t, kmsProp.IsNil()) + assert.Equal(t, "some_id", kmsProp.AsString()) +} + +func TestParse_WithConfigFS(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "queue.yaml": `AWSTemplateFormatVersion: 2010-09-09 +Parameters: + KmsMasterKeyId: + Type: String +Resources: + TestQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: testqueue + KmsMasterKeyId: !Ref KmsMasterKeyId +`, + "bucket.yaml": `AWSTemplateFormatVersion: '2010-09-09' +Description: Bucket +Parameters: + BucketName: + Type: String +Resources: + S3Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Ref BucketName +`, + }) + + configFS := testutil.CreateFS(t, map[string]string{ + "/workdir/parameters/queue.json": `[ + { + "ParameterKey": "KmsMasterKeyId", + "ParameterValue": "some_id" + } + ] + `, + "/workdir/parameters/s3.json": `[ + { + "ParameterKey": "BucketName", + "ParameterValue": "testbucket" + } + ]`, + }) + + p := New( + WithParameterFiles("/workdir/parameters/queue.json", "/workdir/parameters/s3.json"), + WithConfigsFS(configFS), + ) + + files, err := p.ParseFS(context.TODO(), fs, ".") + require.NoError(t, err) + require.Len(t, files, 2) + + for _, file := range files { + if strings.Contains(file.filepath, "queue") { + res := file.GetResourceByLogicalID("TestQueue") + assert.NotNil(t, res) + + kmsProp := res.GetProperty("KmsMasterKeyId") + assert.False(t, kmsProp.IsNil()) + assert.Equal(t, "some_id", kmsProp.AsString()) + } else if strings.Contains(file.filepath, "s3") { + res := file.GetResourceByLogicalID("S3Bucket") + assert.NotNil(t, res) + + bucketNameProp := res.GetProperty("BucketName") + assert.False(t, bucketNameProp.IsNil()) + assert.Equal(t, "testbucket", bucketNameProp.AsString()) + } + } + +} diff --git a/pkg/iac/scanners/cloudformation/parser/property.go b/pkg/iac/scanners/cloudformation/parser/property.go new file mode 100644 index 000000000000..ae0c57050a23 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/property.go @@ -0,0 +1,424 @@ +package parser + +import ( + "encoding/json" + "io/fs" + "strconv" + "strings" + + "github.com/liamg/jfather" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type EqualityOptions = int + +const ( + IgnoreCase EqualityOptions = iota +) + +type Property struct { + ctx *FileContext + name string + comment string + rng iacTypes.Range + parentRange iacTypes.Range + Inner PropertyInner + logicalId string + unresolved bool +} + +type PropertyInner struct { + Type cftypes.CfType + Value interface{} `json:"Value" yaml:"Value"` +} + +func (p *Property) Comment() string { + return p.comment +} + +func (p *Property) setName(name string) { + p.name = name + if p.Type() == cftypes.Map { + for n, subProp := range p.AsMap() { + if subProp == nil { + continue + } + subProp.setName(n) + } + } +} + +func (p *Property) setContext(ctx *FileContext) { + p.ctx = ctx + + if p.IsMap() { + for _, subProp := range p.AsMap() { + if subProp == nil { + continue + } + subProp.setContext(ctx) + } + } + + if p.IsList() { + for _, subProp := range p.AsList() { + subProp.setContext(ctx) + } + } +} + +func (p *Property) setFileAndParentRange(target fs.FS, filepath string, parentRange iacTypes.Range) { + p.rng = iacTypes.NewRange(filepath, p.rng.GetStartLine(), p.rng.GetEndLine(), p.rng.GetSourcePrefix(), target) + p.parentRange = parentRange + + switch p.Type() { + case cftypes.Map: + for _, subProp := range p.AsMap() { + if subProp == nil { + continue + } + subProp.setFileAndParentRange(target, filepath, parentRange) + } + case cftypes.List: + for _, subProp := range p.AsList() { + if subProp == nil { + continue + } + subProp.setFileAndParentRange(target, filepath, parentRange) + } + } +} + +func (p *Property) UnmarshalYAML(node *yaml.Node) error { + p.rng = iacTypes.NewRange("", node.Line, calculateEndLine(node), "", nil) + + p.comment = node.LineComment + return setPropertyValueFromYaml(node, &p.Inner) +} + +func (p *Property) UnmarshalJSONWithMetadata(node jfather.Node) error { + p.rng = iacTypes.NewRange("", node.Range().Start.Line, node.Range().End.Line, "", nil) + return setPropertyValueFromJson(node, &p.Inner) +} + +func (p *Property) Type() cftypes.CfType { + return p.Inner.Type +} + +func (p *Property) Range() iacTypes.Range { + return p.rng +} + +func (p *Property) Metadata() iacTypes.Metadata { + return iacTypes.NewMetadata(p.Range(), p.name). + WithParent(iacTypes.NewMetadata(p.parentRange, p.logicalId)) +} + +func (p *Property) isFunction() bool { + if p == nil { + return false + } + if p.Type() == cftypes.Map { + for n := range p.AsMap() { + return IsIntrinsic(n) + } + } + return false +} + +func (p *Property) RawValue() interface{} { + return p.Inner.Value +} + +func (p *Property) AsRawStrings() ([]string, error) { + + if len(p.ctx.lines) < p.rng.GetEndLine() { + return p.ctx.lines, nil + } + return p.ctx.lines[p.rng.GetStartLine()-1 : p.rng.GetEndLine()], nil +} + +func (p *Property) resolveValue() (*Property, bool) { + if !p.isFunction() || p.IsUnresolved() { + return p, true + } + + resolved, ok := ResolveIntrinsicFunc(p) + if ok { + return resolved, true + } + + p.unresolved = true + return p, false +} + +func (p *Property) GetStringProperty(path string, defaultValue ...string) iacTypes.StringValue { + defVal := "" + if len(defaultValue) > 0 { + defVal = defaultValue[0] + } + + if p.IsUnresolved() { + return iacTypes.StringUnresolvable(p.Metadata()) + } + + prop := p.GetProperty(path) + if prop.IsNotString() { + return p.StringDefault(defVal) + } + return prop.AsStringValue() +} + +func (p *Property) StringDefault(defaultValue string) iacTypes.StringValue { + return iacTypes.StringDefault(defaultValue, p.Metadata()) +} + +func (p *Property) GetBoolProperty(path string, defaultValue ...bool) iacTypes.BoolValue { + defVal := false + if len(defaultValue) > 0 { + defVal = defaultValue[0] + } + + if p.IsUnresolved() { + return iacTypes.BoolUnresolvable(p.Metadata()) + } + + prop := p.GetProperty(path) + + if prop.isFunction() { + prop, _ = prop.resolveValue() + } + + if prop.IsNotBool() { + return p.inferBool(prop, defVal) + } + return prop.AsBoolValue() +} + +func (p *Property) GetIntProperty(path string, defaultValue ...int) iacTypes.IntValue { + defVal := 0 + if len(defaultValue) > 0 { + defVal = defaultValue[0] + } + + if p.IsUnresolved() { + return iacTypes.IntUnresolvable(p.Metadata()) + } + + prop := p.GetProperty(path) + + if prop.IsNotInt() { + return p.IntDefault(defVal) + } + return prop.AsIntValue() +} + +func (p *Property) BoolDefault(defaultValue bool) iacTypes.BoolValue { + return iacTypes.BoolDefault(defaultValue, p.Metadata()) +} + +func (p *Property) IntDefault(defaultValue int) iacTypes.IntValue { + return iacTypes.IntDefault(defaultValue, p.Metadata()) +} + +func (p *Property) GetProperty(path string) *Property { + + pathParts := strings.Split(path, ".") + + first := pathParts[0] + property := p + + if p.isFunction() { + property, _ = p.resolveValue() + } + + if property.IsNotMap() { + return nil + } + + for n, p := range property.AsMap() { + if n == first { + property = p + break + } + } + + if len(pathParts) == 1 || property == nil { + return property + } + + if nestedProperty := property.GetProperty(strings.Join(pathParts[1:], ".")); nestedProperty != nil { + if nestedProperty.isFunction() { + resolved, _ := nestedProperty.resolveValue() + return resolved + } else { + return nestedProperty + } + } + + return &Property{} +} + +func (p *Property) deriveResolved(propType cftypes.CfType, propValue interface{}) *Property { + return &Property{ + ctx: p.ctx, + name: p.name, + comment: p.comment, + rng: p.rng, + parentRange: p.parentRange, + logicalId: p.logicalId, + Inner: PropertyInner{ + Type: propType, + Value: propValue, + }, + } +} + +func (p *Property) ParentRange() iacTypes.Range { + return p.parentRange +} + +func (p *Property) inferBool(prop *Property, defaultValue bool) iacTypes.BoolValue { + if prop.IsString() { + if prop.EqualTo("true", IgnoreCase) { + return iacTypes.Bool(true, prop.Metadata()) + } + if prop.EqualTo("yes", IgnoreCase) { + return iacTypes.Bool(true, prop.Metadata()) + } + if prop.EqualTo("1", IgnoreCase) { + return iacTypes.Bool(true, prop.Metadata()) + } + if prop.EqualTo("false", IgnoreCase) { + return iacTypes.Bool(false, prop.Metadata()) + } + if prop.EqualTo("no", IgnoreCase) { + return iacTypes.Bool(false, prop.Metadata()) + } + if prop.EqualTo("0", IgnoreCase) { + return iacTypes.Bool(false, prop.Metadata()) + } + } + + if prop.IsInt() { + if prop.EqualTo(0) { + return iacTypes.Bool(false, prop.Metadata()) + } + if prop.EqualTo(1) { + return iacTypes.Bool(true, prop.Metadata()) + } + } + + return p.BoolDefault(defaultValue) +} + +func (p *Property) String() string { + r := "" + switch p.Type() { + case cftypes.String: + r = p.AsString() + case cftypes.Int: + r = strconv.Itoa(p.AsInt()) + } + return r +} + +func (p *Property) SetLogicalResource(id string) { + p.logicalId = id + + if p.isFunction() { + return + } + + if p.IsMap() { + for _, subProp := range p.AsMap() { + if subProp == nil { + continue + } + subProp.SetLogicalResource(id) + } + } + + if p.IsList() { + for _, subProp := range p.AsList() { + subProp.SetLogicalResource(id) + } + } + +} + +func (p *Property) GetJsonBytes(squashList ...bool) []byte { + if p.IsNil() { + return []byte{} + } + lines, err := p.AsRawStrings() + if err != nil { + return nil + } + if p.ctx.SourceFormat == JsonSourceFormat { + return []byte(strings.Join(lines, " ")) + } + + if len(squashList) > 0 { + lines[0] = strings.Replace(lines[0], "-", " ", 1) + } + + lines = removeLeftMargin(lines) + + yamlContent := strings.Join(lines, "\n") + var body interface{} + if err := yaml.Unmarshal([]byte(yamlContent), &body); err != nil { + return nil + } + jsonBody := convert(body) + policyJson, err := json.Marshal(jsonBody) + if err != nil { + return nil + } + return policyJson +} + +func (p *Property) GetJsonBytesAsString(squashList ...bool) string { + return string(p.GetJsonBytes(squashList...)) +} + +func removeLeftMargin(lines []string) []string { + if len(lines) == 0 { + return lines + } + prefixSpace := len(lines[0]) - len(strings.TrimLeft(lines[0], " ")) + + for i, line := range lines { + if len(line) >= prefixSpace { + lines[i] = line[prefixSpace:] + } + } + return lines +} + +func convert(input interface{}) interface{} { + switch x := input.(type) { + case map[interface{}]interface{}: + outpMap := make(map[string]interface{}) + for k, v := range x { + outpMap[k.(string)] = convert(v) + } + return outpMap + case []interface{}: + for i, v := range x { + x[i] = convert(v) + } + } + return input +} + +func (p *Property) inferType() { + typ := cftypes.TypeFromGoValue(p.Inner.Value) + if typ == cftypes.Unknown { + return + } + p.Inner.Type = typ +} diff --git a/pkg/iac/scanners/cloudformation/parser/property_conversion.go b/pkg/iac/scanners/cloudformation/parser/property_conversion.go new file mode 100644 index 000000000000..d286fa4dd797 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/property_conversion.go @@ -0,0 +1,129 @@ +package parser + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +func (p *Property) IsConvertableTo(conversionType cftypes.CfType) bool { + switch conversionType { + case cftypes.Int: + return p.isConvertableToInt() + case cftypes.Bool: + return p.isConvertableToBool() + case cftypes.String: + return p.isConvertableToString() + } + return false +} + +func (p *Property) isConvertableToString() bool { + switch p.Type() { + case cftypes.Map: + return false + case cftypes.List: + for _, p := range p.AsList() { + if !p.IsString() { + return false + } + } + } + return true +} + +func (p *Property) isConvertableToBool() bool { + switch p.Type() { + case cftypes.String: + return p.EqualTo("true", IgnoreCase) || p.EqualTo("false", IgnoreCase) || + p.EqualTo("1", IgnoreCase) || p.EqualTo("0", IgnoreCase) + + case cftypes.Int: + return p.EqualTo(1) || p.EqualTo(0) + } + return false +} + +func (p *Property) isConvertableToInt() bool { + switch p.Type() { + case cftypes.String: + if _, err := strconv.Atoi(p.AsString()); err == nil { + return true + } + case cftypes.Bool: + return true + } + return false +} + +func (p *Property) ConvertTo(conversionType cftypes.CfType) *Property { + + if !p.IsConvertableTo(conversionType) { + _, _ = fmt.Fprintf(os.Stderr, "property of type %s cannot be converted to %s\n", p.Type(), conversionType) + return p + } + switch conversionType { + case cftypes.Int: + return p.convertToInt() + case cftypes.Bool: + return p.convertToBool() + case cftypes.String: + return p.convertToString() + } + return p +} + +func (p *Property) convertToString() *Property { + switch p.Type() { + case cftypes.Int: + return p.deriveResolved(cftypes.String, strconv.Itoa(p.AsInt())) + case cftypes.Bool: + return p.deriveResolved(cftypes.String, fmt.Sprintf("%v", p.AsBool())) + case cftypes.List: + var parts []string + for _, property := range p.AsList() { + parts = append(parts, property.AsString()) + } + return p.deriveResolved(cftypes.String, fmt.Sprintf("[%s]", strings.Join(parts, ", "))) + } + return p +} + +func (p *Property) convertToBool() *Property { + switch p.Type() { + case cftypes.String: + if p.EqualTo("true", IgnoreCase) || p.EqualTo("1") { + return p.deriveResolved(cftypes.Bool, true) + } + if p.EqualTo("false", IgnoreCase) || p.EqualTo("0") { + return p.deriveResolved(cftypes.Bool, false) + } + case cftypes.Int: + if p.EqualTo(1) { + return p.deriveResolved(cftypes.Bool, true) + } + if p.EqualTo(0) { + return p.deriveResolved(cftypes.Bool, false) + } + } + return p +} + +func (p *Property) convertToInt() *Property { + // + switch p.Type() { + case cftypes.String: + if val, err := strconv.Atoi(p.AsString()); err == nil { + return p.deriveResolved(cftypes.Int, val) + } + case cftypes.Bool: + if p.IsTrue() { + return p.deriveResolved(cftypes.Int, 1) + } + return p.deriveResolved(cftypes.Int, 0) + } + return p +} diff --git a/pkg/iac/scanners/cloudformation/parser/property_helpers.go b/pkg/iac/scanners/cloudformation/parser/property_helpers.go new file mode 100644 index 000000000000..260ea106be79 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/property_helpers.go @@ -0,0 +1,266 @@ +package parser + +import ( + "strconv" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func (p *Property) IsNil() bool { + return p == nil || p.Inner.Value == nil +} + +func (p *Property) IsNotNil() bool { + return !p.IsUnresolved() && !p.IsNil() +} + +func (p *Property) Is(t cftypes.CfType) bool { + if p.IsNil() || p.IsUnresolved() { + return false + } + if p.isFunction() { + if prop, success := p.resolveValue(); success && prop != p { + return prop.Is(t) + } + } + return p.Inner.Type == t +} + +func (p *Property) IsString() bool { + return p.Is(cftypes.String) +} + +func (p *Property) IsNotString() bool { + return !p.IsUnresolved() && !p.IsString() +} + +func (p *Property) IsInt() bool { + return p.Is(cftypes.Int) +} + +func (p *Property) IsNotInt() bool { + return !p.IsUnresolved() && !p.IsInt() +} + +func (p *Property) IsMap() bool { + if p.IsNil() || p.IsUnresolved() { + return false + } + return p.Inner.Type == cftypes.Map +} + +func (p *Property) IsNotMap() bool { + return !p.IsUnresolved() && !p.IsMap() +} + +func (p *Property) IsList() bool { + return p.Is(cftypes.List) +} + +func (p *Property) IsNotList() bool { + return !p.IsUnresolved() && !p.IsList() +} + +func (p *Property) IsBool() bool { + return p.Is(cftypes.Bool) +} + +func (p *Property) IsUnresolved() bool { + return p != nil && p.unresolved +} + +func (p *Property) IsNotBool() bool { + return !p.IsUnresolved() && !p.IsBool() +} + +func (p *Property) AsString() string { + if p.isFunction() { + if prop, success := p.resolveValue(); success && prop != p { + return prop.AsString() + } + return "" + } + if p.IsNil() { + return "" + } + if !p.IsString() { + return "" + } + + return p.Inner.Value.(string) +} + +func (p *Property) AsStringValue() iacTypes.StringValue { + if p.unresolved { + return iacTypes.StringUnresolvable(p.Metadata()) + } + return iacTypes.StringExplicit(p.AsString(), p.Metadata()) +} + +func (p *Property) AsInt() int { + if p.isFunction() { + if prop, success := p.resolveValue(); success && prop != p { + return prop.AsInt() + } + return 0 + } + if p.IsNotInt() { + if p.isConvertableToInt() { + return p.convertToInt().AsInt() + } + return 0 + } + + return p.Inner.Value.(int) +} + +func (p *Property) AsIntValue() iacTypes.IntValue { + if p.unresolved { + return iacTypes.IntUnresolvable(p.Metadata()) + } + return iacTypes.IntExplicit(p.AsInt(), p.Metadata()) +} + +func (p *Property) AsBool() bool { + if p.isFunction() { + if prop, success := p.resolveValue(); success && prop != p { + return prop.AsBool() + } + return false + } + if !p.IsBool() { + return false + } + return p.Inner.Value.(bool) +} + +func (p *Property) AsBoolValue() iacTypes.BoolValue { + if p.unresolved { + return iacTypes.BoolUnresolvable(p.Metadata()) + } + return iacTypes.Bool(p.AsBool(), p.Metadata()) +} + +func (p *Property) AsMap() map[string]*Property { + val, ok := p.Inner.Value.(map[string]*Property) + if !ok { + return nil + } + return val +} + +func (p *Property) AsList() []*Property { + if p.isFunction() { + if prop, success := p.resolveValue(); success && prop != p { + return prop.AsList() + } + return []*Property{} + } + + if list, ok := p.Inner.Value.([]*Property); ok { + return list + } + return nil +} + +func (p *Property) Len() int { + return len(p.AsList()) +} + +func (p *Property) EqualTo(checkValue interface{}, equalityOptions ...EqualityOptions) bool { + var ignoreCase bool + for _, option := range equalityOptions { + if option == IgnoreCase { + ignoreCase = true + } + } + + switch checkerVal := checkValue.(type) { + case string: + if p.IsNil() { + return false + } + + if p.Inner.Type == cftypes.String || p.IsString() { + if ignoreCase { + return strings.EqualFold(p.AsString(), checkerVal) + } + return p.AsString() == checkerVal + } else if p.Inner.Type == cftypes.Int || p.IsInt() { + if val, err := strconv.Atoi(checkerVal); err == nil { + return p.AsInt() == val + } + } + return false + case bool: + if p.Inner.Type == cftypes.Bool || p.IsBool() { + return p.AsBool() == checkerVal + } + case int: + if p.Inner.Type == cftypes.Int || p.IsInt() { + return p.AsInt() == checkerVal + } + } + + return false + +} + +func (p *Property) IsTrue() bool { + if p.IsNil() || !p.IsBool() { + return false + } + + return p.AsBool() +} + +func (p *Property) IsEmpty() bool { + + if p.IsNil() { + return true + } + if p.IsUnresolved() { + return false + } + + switch p.Inner.Type { + case cftypes.String: + return p.AsString() == "" + case cftypes.List, cftypes.Map: + return len(p.AsList()) == 0 + default: + return false + } +} + +func (p *Property) Contains(checkVal interface{}) bool { + if p == nil || p.IsNil() { + return false + } + + switch p.Type() { + case cftypes.List: + for _, p := range p.AsList() { + if p.EqualTo(checkVal) { + return true + } + } + case cftypes.Map: + if _, ok := checkVal.(string); !ok { + return false + } + for key := range p.AsMap() { + if key == checkVal.(string) { + return true + } + } + case cftypes.String: + if _, ok := checkVal.(string); !ok { + return false + } + return strings.Contains(p.AsString(), checkVal.(string)) + } + return false +} diff --git a/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go b/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go new file mode 100644 index 000000000000..4b3779eac587 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go @@ -0,0 +1,195 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" +) + +func newProp(inner PropertyInner) *Property { + return &Property{ + name: "test_prop", + ctx: &FileContext{}, + rng: types.NewRange("testfile", 1, 1, "", nil), + Inner: inner, + } +} + +func Test_EqualTo(t *testing.T) { + tests := []struct { + name string + property *Property + checkValue interface{} + opts []EqualityOptions + isEqual bool + }{ + { + name: "prop is nil", + property: nil, + checkValue: "some value", + isEqual: false, + }, + { + name: "compare strings", + property: newProp(PropertyInner{ + Type: cftypes.String, + Value: "is str", + }), + checkValue: "is str", + isEqual: true, + }, + { + name: "compare strings ignoring case", + property: newProp(PropertyInner{ + Type: cftypes.String, + Value: "is str", + }), + opts: []EqualityOptions{IgnoreCase}, + checkValue: "Is StR", + isEqual: true, + }, + { + name: "strings ate not equal", + property: newProp(PropertyInner{ + Type: cftypes.String, + Value: "some value", + }), + checkValue: "some other value", + isEqual: false, + }, + { + name: "compare prop with a int represented by a string", + property: newProp(PropertyInner{ + Type: cftypes.Int, + Value: 147, + }), + checkValue: "147", + isEqual: true, + }, + { + name: "compare ints", + property: newProp(PropertyInner{ + Type: cftypes.Int, + Value: 701, + }), + checkValue: 701, + isEqual: true, + }, + { + name: "compare bools", + property: newProp(PropertyInner{ + Type: cftypes.Bool, + Value: true, + }), + checkValue: true, + isEqual: true, + }, + { + name: "prop is string fn", + property: newProp(PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.Bool, + Value: false, + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "bad", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "some value", + }, + }, + }, + }, + }, + }, + }), + checkValue: "some value", + isEqual: true, + }, + { + name: "prop is int fn", + property: newProp(PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.Bool, + Value: true, + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.Int, + Value: 121, + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.Int, + Value: -1, + }, + }, + }, + }, + }, + }, + }), + checkValue: 121, + isEqual: true, + }, + { + name: "prop is bool fn", + property: newProp(PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "foo", + }, + }, + }, + }, + }, + }, + }), + checkValue: true, + isEqual: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.isEqual, tt.property.EqualTo(tt.checkValue, tt.opts...)) + }) + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go new file mode 100644 index 000000000000..ab825f02b8fd --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go @@ -0,0 +1,48 @@ +package parser + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" +) + +type pseudoParameter struct { + t cftypes.CfType + val interface{} + raw interface{} +} + +var pseudoParameters = map[string]pseudoParameter{ + "AWS::AccountId": {t: cftypes.String, val: "123456789012"}, + "AWS::NotificationARNs": { + t: cftypes.List, + val: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "notification::arn::1", + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "notification::arn::2", + }, + }, + }, + raw: []string{"notification::arn::1", "notification::arn::2"}, + }, + "AWS::NoValue": {t: cftypes.String, val: ""}, + "AWS::Partition": {t: cftypes.String, val: "aws"}, + "AWS::Region": {t: cftypes.String, val: "eu-west-1"}, + "AWS::StackId": {t: cftypes.String, val: "arn:aws:cloudformation:eu-west-1:stack/ID"}, + "AWS::StackName": {t: cftypes.String, val: "cfsec-test-stack"}, + "AWS::URLSuffix": {t: cftypes.String, val: "amazonaws.com"}, +} + +func (p pseudoParameter) getRawValue() interface{} { + switch p.t { + case cftypes.List: + return p.raw + default: + return p.val + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/pseudo_parameters_test.go b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters_test.go new file mode 100644 index 000000000000..281bf9083a14 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters_test.go @@ -0,0 +1,36 @@ +package parser + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Raw(t *testing.T) { + tests := []struct { + name string + key string + expected interface{} + }{ + { + name: "parameter with a string type value", + key: "AWS::AccountId", + expected: "123456789012", + }, + { + name: "a parameter with a list type value", + key: "AWS::NotificationARNs", + expected: []string{"notification::arn::1", "notification::arn::2"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if val, ok := pseudoParameters[tt.key]; ok { + assert.Equal(t, tt.expected, val.getRawValue()) + } else { + t.Fatal("unexpected parameter key") + } + }) + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/reference.go b/pkg/iac/scanners/cloudformation/parser/reference.go new file mode 100644 index 000000000000..705eef2747af --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/reference.go @@ -0,0 +1,21 @@ +package parser + +import ( + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type CFReference struct { + logicalId string + resourceRange iacTypes.Range +} + +func NewCFReference(id string, resourceRange iacTypes.Range) CFReference { + return CFReference{ + logicalId: id, + resourceRange: resourceRange, + } +} + +func (cf CFReference) String() string { + return cf.resourceRange.String() +} diff --git a/pkg/iac/scanners/cloudformation/parser/resource.go b/pkg/iac/scanners/cloudformation/parser/resource.go new file mode 100644 index 000000000000..bd1351f234df --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/resource.go @@ -0,0 +1,208 @@ +package parser + +import ( + "io/fs" + "strings" + + "github.com/liamg/jfather" + "gopkg.in/yaml.v3" + + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Resource struct { + ctx *FileContext + rng iacTypes.Range + id string + comment string + Inner ResourceInner +} + +type ResourceInner struct { + Type string `json:"Type" yaml:"Type"` + Properties map[string]*Property `json:"Properties" yaml:"Properties"` +} + +func (r *Resource) ConfigureResource(id string, target fs.FS, filepath string, ctx *FileContext) { + r.setId(id) + r.setFile(target, filepath) + r.setContext(ctx) +} + +func (r *Resource) setId(id string) { + r.id = id + + for n, p := range r.properties() { + p.setName(n) + } +} + +func (r *Resource) setFile(target fs.FS, filepath string) { + r.rng = iacTypes.NewRange(filepath, r.rng.GetStartLine(), r.rng.GetEndLine(), r.rng.GetSourcePrefix(), target) + + for _, p := range r.Inner.Properties { + p.setFileAndParentRange(target, filepath, r.rng) + } +} + +func (r *Resource) setContext(ctx *FileContext) { + r.ctx = ctx + + for _, p := range r.Inner.Properties { + p.SetLogicalResource(r.id) + p.setContext(ctx) + } +} + +func (r *Resource) UnmarshalYAML(value *yaml.Node) error { + r.rng = iacTypes.NewRange("", value.Line-1, calculateEndLine(value), "", nil) + r.comment = value.LineComment + return value.Decode(&r.Inner) +} + +func (r *Resource) UnmarshalJSONWithMetadata(node jfather.Node) error { + r.rng = iacTypes.NewRange("", node.Range().Start.Line, node.Range().End.Line, "", nil) + return node.Decode(&r.Inner) +} + +func (r *Resource) ID() string { + return r.id +} + +func (r *Resource) Type() string { + return r.Inner.Type +} + +func (r *Resource) Range() iacTypes.Range { + return r.rng +} + +func (r *Resource) SourceFormat() SourceFormat { + return r.ctx.SourceFormat +} + +func (r *Resource) Metadata() iacTypes.Metadata { + return iacTypes.NewMetadata(r.Range(), NewCFReference(r.id, r.rng).String()) +} + +func (r *Resource) properties() map[string]*Property { + return r.Inner.Properties +} + +func (r *Resource) IsNil() bool { + return r.id == "" +} + +func (r *Resource) GetProperty(path string) *Property { + + pathParts := strings.Split(path, ".") + + first := pathParts[0] + property := &Property{} + + if p, exists := r.properties()[first]; exists { + property = p + } + + if len(pathParts) == 1 || property.IsNil() { + if property.isFunction() { + resolved, _ := property.resolveValue() + return resolved + } + return property + } + + if nestedProperty := property.GetProperty(strings.Join(pathParts[1:], ".")); nestedProperty != nil { + return nestedProperty + } + + return &Property{} +} + +func (r *Resource) GetStringProperty(path string, defaultValue ...string) iacTypes.StringValue { + defVal := "" + if len(defaultValue) > 0 { + defVal = defaultValue[0] + } + + prop := r.GetProperty(path) + + if prop.IsNotString() { + return r.StringDefault(defVal) + } + return prop.AsStringValue() +} + +func (r *Resource) GetBoolProperty(path string, defaultValue ...bool) iacTypes.BoolValue { + defVal := false + if len(defaultValue) > 0 { + defVal = defaultValue[0] + } + + prop := r.GetProperty(path) + + if prop.IsNotBool() { + return r.inferBool(prop, defVal) + } + return prop.AsBoolValue() +} + +func (r *Resource) GetIntProperty(path string, defaultValue ...int) iacTypes.IntValue { + defVal := 0 + if len(defaultValue) > 0 { + defVal = defaultValue[0] + } + + prop := r.GetProperty(path) + + if prop.IsNotInt() { + return r.IntDefault(defVal) + } + return prop.AsIntValue() +} + +func (r *Resource) StringDefault(defaultValue string) iacTypes.StringValue { + return iacTypes.StringDefault(defaultValue, r.Metadata()) +} + +func (r *Resource) BoolDefault(defaultValue bool) iacTypes.BoolValue { + return iacTypes.BoolDefault(defaultValue, r.Metadata()) +} + +func (r *Resource) IntDefault(defaultValue int) iacTypes.IntValue { + return iacTypes.IntDefault(defaultValue, r.Metadata()) +} + +func (r *Resource) inferBool(prop *Property, defaultValue bool) iacTypes.BoolValue { + if prop.IsString() { + if prop.EqualTo("true", IgnoreCase) { + return iacTypes.Bool(true, prop.Metadata()) + } + if prop.EqualTo("yes", IgnoreCase) { + return iacTypes.Bool(true, prop.Metadata()) + } + if prop.EqualTo("1", IgnoreCase) { + return iacTypes.Bool(true, prop.Metadata()) + } + if prop.EqualTo("false", IgnoreCase) { + return iacTypes.Bool(false, prop.Metadata()) + } + if prop.EqualTo("no", IgnoreCase) { + return iacTypes.Bool(false, prop.Metadata()) + } + if prop.EqualTo("0", IgnoreCase) { + return iacTypes.Bool(false, prop.Metadata()) + } + } + + if prop.IsInt() { + if prop.EqualTo(0) { + return iacTypes.Bool(false, prop.Metadata()) + } + if prop.EqualTo(1) { + return iacTypes.Bool(true, prop.Metadata()) + } + } + + return r.BoolDefault(defaultValue) +} diff --git a/pkg/iac/scanners/cloudformation/parser/resource_test.go b/pkg/iac/scanners/cloudformation/parser/resource_test.go new file mode 100644 index 000000000000..89d2448954e6 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/resource_test.go @@ -0,0 +1,75 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/stretchr/testify/require" +) + +func Test_GetProperty_PropIsFunction(t *testing.T) { + resource := Resource{ + Inner: ResourceInner{ + Type: "AWS::S3::Bucket", + Properties: map[string]*Property{ + "BucketName": { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "mybucket", + }, + }, + "VersioningConfiguration": { + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.Bool, + Value: false, + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Status": { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "Enabled", + }, + }, + }, + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Status": { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "Suspended", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + prop := resource.GetProperty("VersioningConfiguration.Status") + require.NotNil(t, prop) + require.True(t, prop.IsString()) + require.Equal(t, "Suspended", prop.AsString()) +} diff --git a/pkg/iac/scanners/cloudformation/parser/util.go b/pkg/iac/scanners/cloudformation/parser/util.go new file mode 100644 index 000000000000..03b9bf8da837 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/parser/util.go @@ -0,0 +1,142 @@ +package parser + +import ( + "strconv" + + "github.com/liamg/jfather" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser" +) + +func setPropertyValueFromJson(node jfather.Node, propertyData *PropertyInner) error { + + switch node.Kind() { + + case jfather.KindNumber: + propertyData.Type = cftypes.Float64 + return node.Decode(&propertyData.Value) + case jfather.KindBoolean: + propertyData.Type = cftypes.Bool + return node.Decode(&propertyData.Value) + case jfather.KindString: + propertyData.Type = cftypes.String + return node.Decode(&propertyData.Value) + case jfather.KindObject: + var childData map[string]*Property + if err := node.Decode(&childData); err != nil { + return err + } + propertyData.Type = cftypes.Map + propertyData.Value = childData + return nil + case jfather.KindArray: + var childData []*Property + if err := node.Decode(&childData); err != nil { + return err + } + propertyData.Type = cftypes.List + propertyData.Value = childData + return nil + default: + propertyData.Type = cftypes.String + return node.Decode(&propertyData.Value) + } + +} + +func setPropertyValueFromYaml(node *yaml.Node, propertyData *PropertyInner) error { + if IsIntrinsicFunc(node) { + var newContent []*yaml.Node + + newContent = append(newContent, &yaml.Node{ + Tag: "!!str", + Value: getIntrinsicTag(node.Tag), + Kind: yaml.ScalarNode, + }) + + newContent = createNode(node, newContent) + + node.Tag = string(parser.TagMap) + node.Kind = yaml.MappingNode + node.Content = newContent + } + + if node.Content == nil { + + switch node.Tag { + case "!!int": + propertyData.Type = cftypes.Int + propertyData.Value, _ = strconv.Atoi(node.Value) + case "!!bool": + propertyData.Type = cftypes.Bool + propertyData.Value, _ = strconv.ParseBool(node.Value) + case "!!float": + propertyData.Type = cftypes.Float64 + propertyData.Value, _ = strconv.ParseFloat(node.Value, 64) + case "!!str", "!!string": + propertyData.Type = cftypes.String + propertyData.Value = node.Value + } + return nil + } + + switch node.Tag { + case string(parser.TagMap): + var childData map[string]*Property + if err := node.Decode(&childData); err != nil { + return err + } + propertyData.Type = cftypes.Map + propertyData.Value = childData + return nil + case "!!seq": + var childData []*Property + if err := node.Decode(&childData); err != nil { + return err + } + propertyData.Type = cftypes.List + propertyData.Value = childData + return nil + } + + return nil +} + +func createNode(node *yaml.Node, newContent []*yaml.Node) []*yaml.Node { + if node.Content == nil { + newContent = append(newContent, &yaml.Node{ + Tag: "!!str", + Value: node.Value, + Kind: yaml.ScalarNode, + }) + } else { + + newNode := &yaml.Node{ + Content: node.Content, + Kind: node.Kind, + } + + switch node.Kind { + case yaml.SequenceNode: + newNode.Tag = "!!seq" + case yaml.MappingNode: + newNode.Tag = string(parser.TagMap) + case yaml.ScalarNode: + default: + newNode.Tag = node.Tag + } + newContent = append(newContent, newNode) + } + return newContent +} + +func calculateEndLine(node *yaml.Node) int { + if node.Content == nil { + return node.Line + } + + return calculateEndLine(node.Content[len(node.Content)-1]) + +} diff --git a/pkg/iac/scanners/cloudformation/scanner.go b/pkg/iac/scanners/cloudformation/scanner.go new file mode 100644 index 000000000000..0920f4425fdb --- /dev/null +++ b/pkg/iac/scanners/cloudformation/scanner.go @@ -0,0 +1,270 @@ +package cloudformation + +import ( + "context" + "fmt" + "io" + "io/fs" + "sort" + "sync" + + adapter "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation" + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func WithParameters(params map[string]any) options.ScannerOption { + return func(cs options.ConfigurableScanner) { + if s, ok := cs.(*Scanner); ok { + s.addParserOptions(parser.WithParameters(params)) + } + } +} + +func WithParameterFiles(files ...string) options.ScannerOption { + return func(cs options.ConfigurableScanner) { + if s, ok := cs.(*Scanner); ok { + s.addParserOptions(parser.WithParameterFiles(files...)) + } + } +} + +func WithConfigsFS(fsys fs.FS) options.ScannerOption { + return func(cs options.ConfigurableScanner) { + if s, ok := cs.(*Scanner); ok { + s.addParserOptions(parser.WithConfigsFS(fsys)) + } + } +} + +var _ scanners.FSScanner = (*Scanner)(nil) +var _ options.ConfigurableScanner = (*Scanner)(nil) + +type Scanner struct { // nolint: gocritic + debug debug.Logger + policyDirs []string + policyReaders []io.Reader + parser *parser.Parser + regoScanner *rego.Scanner + skipRequired bool + regoOnly bool + loadEmbeddedPolicies bool + loadEmbeddedLibraries bool + options []options.ScannerOption + parserOptions []options.ParserOption + frameworks []framework.Framework + spec string + sync.Mutex +} + +func (s *Scanner) addParserOptions(opt options.ParserOption) { + s.parserOptions = append(s.parserOptions, opt) +} + +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { + s.frameworks = frameworks +} + +func (s *Scanner) SetSpec(spec string) { + s.spec = spec +} + +func (s *Scanner) SetUseEmbeddedPolicies(b bool) { + s.loadEmbeddedPolicies = b +} + +func (s *Scanner) SetUseEmbeddedLibraries(b bool) { + s.loadEmbeddedLibraries = b +} + +func (s *Scanner) SetRegoOnly(regoOnly bool) { + s.regoOnly = regoOnly +} + +func (s *Scanner) Name() string { + return "CloudFormation" +} + +func (s *Scanner) SetPolicyReaders(readers []io.Reader) { + s.policyReaders = readers +} + +func (s *Scanner) SetSkipRequiredCheck(skip bool) { + s.skipRequired = skip +} + +func (s *Scanner) SetDebugWriter(writer io.Writer) { + s.debug = debug.New(writer, "cloudformation", "scanner") +} + +func (s *Scanner) SetPolicyDirs(dirs ...string) { + s.policyDirs = dirs +} + +func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} + +func (s *Scanner) SetDataFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} +func (s *Scanner) SetRegoErrorLimit(_ int) {} + +func (s *Scanner) SetTraceWriter(_ io.Writer) {} +func (s *Scanner) SetPerResultTracingEnabled(_ bool) {} +func (s *Scanner) SetDataDirs(_ ...string) {} +func (s *Scanner) SetPolicyNamespaces(_ ...string) {} + +// New creates a new Scanner +func New(opts ...options.ScannerOption) *Scanner { + s := &Scanner{ + options: opts, + } + for _, opt := range opts { + opt(s) + } + s.addParserOptions(options.ParserWithSkipRequiredCheck(s.skipRequired)) + s.parser = parser.New(s.parserOptions...) + return s +} + +func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { + s.Lock() + defer s.Unlock() + if s.regoScanner != nil { + return s.regoScanner, nil + } + regoScanner := rego.NewScanner(types.SourceCloud, s.options...) + regoScanner.SetParentDebugLogger(s.debug) + if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + return nil, err + } + s.regoScanner = regoScanner + return regoScanner, nil +} + +func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (results scan.Results, err error) { + + contexts, err := s.parser.ParseFS(ctx, fsys, dir) + if err != nil { + return nil, err + } + + if len(contexts) == 0 { + return nil, nil + } + + regoScanner, err := s.initRegoScanner(fsys) + if err != nil { + return nil, err + } + + for _, cfCtx := range contexts { + if cfCtx == nil { + continue + } + fileResults, err := s.scanFileContext(ctx, regoScanner, cfCtx, fsys) + if err != nil { + return nil, err + } + results = append(results, fileResults...) + } + sort.Slice(results, func(i, j int) bool { + return results[i].Rule().AVDID < results[j].Rule().AVDID + }) + return results, nil +} + +func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.Results, error) { + + cfCtx, err := s.parser.ParseFile(ctx, fsys, path) + if err != nil { + return nil, err + } + + regoScanner, err := s.initRegoScanner(fsys) + if err != nil { + return nil, err + } + + results, err := s.scanFileContext(ctx, regoScanner, cfCtx, fsys) + if err != nil { + return nil, err + } + results.SetSourceAndFilesystem("", fsys, false) + + sort.Slice(results, func(i, j int) bool { + return results[i].Rule().AVDID < results[j].Rule().AVDID + }) + return results, nil +} + +func (s *Scanner) scanFileContext(ctx context.Context, regoScanner *rego.Scanner, cfCtx *parser.FileContext, fsys fs.FS) (results scan.Results, err error) { + state := adapter.Adapt(*cfCtx) + if state == nil { + return nil, nil + } + if !s.regoOnly { + for _, rule := range rules.GetRegistered(s.frameworks...) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + if rule.GetRule().RegoPackage != "" { + continue + } + evalResult := rule.Evaluate(state) + if len(evalResult) > 0 { + s.debug.Log("Found %d results for %s", len(evalResult), rule.GetRule().AVDID) + for _, scanResult := range evalResult { + + ref := scanResult.Metadata().Reference() + + if ref == "" && scanResult.Metadata().Parent() != nil { + ref = scanResult.Metadata().Parent().Reference() + } + + description := getDescription(scanResult, ref) + scanResult.OverrideDescription(description) + results = append(results, scanResult) + } + } + } + } + regoResults, err := regoScanner.ScanInput(ctx, rego.Input{ + Path: cfCtx.Metadata().Range().GetFilename(), + FS: fsys, + Contents: state.ToRego(), + }) + if err != nil { + return nil, fmt.Errorf("rego scan error: %w", err) + } + results = append(results, regoResults...) + + results.Ignore(cfCtx.Ignores, nil) + + for _, ignored := range results.GetIgnored() { + s.debug.Log("Ignored '%s' at '%s'.", ignored.Rule().LongID(), ignored.Range()) + } + + return results, nil +} + +func getDescription(scanResult scan.Result, ref string) string { + switch scanResult.Status() { + case scan.StatusPassed: + return fmt.Sprintf("Resource '%s' passed check: %s", ref, scanResult.Rule().Summary) + case scan.StatusIgnored: + return fmt.Sprintf("Resource '%s' had check ignored: %s", ref, scanResult.Rule().Summary) + default: + return scanResult.Description() + } +} diff --git a/pkg/iac/scanners/cloudformation/scanner_test.go b/pkg/iac/scanners/cloudformation/scanner_test.go new file mode 100644 index 000000000000..3264609557ac --- /dev/null +++ b/pkg/iac/scanners/cloudformation/scanner_test.go @@ -0,0 +1,231 @@ +package cloudformation + +import ( + "context" + "strings" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +func Test_BasicScan(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "/code/main.yaml": `--- +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: public-bucket + +`, + "/rules/rule.rego": `package builtin.dockerfile.DS006 + +__rego_metadata__ := { + "id": "DS006", + "avd_id": "AVD-DS-0006", + "title": "COPY '--from' referring to the current image", + "short_code": "no-self-referencing-copy-from", + "version": "v1.0.0", + "severity": "CRITICAL", + "type": "Dockerfile Security Check", + "description": "COPY '--from' should not mention the current FROM alias, since it is impossible to copy from itself.", + "recommended_actions": "Change the '--from' so that it will not refer to itself", + "url": "https://docs.docker.com/develop/develop-images/multistage-build/", +} + +__rego_input__ := { + "combine": false, + "selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}], +} + +deny[res] { + res := { + "msg": "oh no", + "filepath": "code/main.yaml", + "startline": 6, + "endline": 6, + } +} + +`, + }) + + scanner := New(options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true)) + + results, err := scanner.ScanFS(context.TODO(), fs, "code") + require.NoError(t, err) + + require.Len(t, results.GetFailed(), 1) + + assert.Equal(t, scan.Rule{ + AVDID: "AVD-DS-0006", + Aliases: []string{"DS006"}, + ShortCode: "no-self-referencing-copy-from", + Summary: "COPY '--from' referring to the current image", + Explanation: "COPY '--from' should not mention the current FROM alias, since it is impossible to copy from itself.", + Impact: "", + Resolution: "Change the '--from' so that it will not refer to itself", + Provider: "cloud", + Service: "general", + Links: []string{"https://docs.docker.com/develop/develop-images/multistage-build/"}, + Severity: "CRITICAL", + Terraform: &scan.EngineMetadata{}, + CloudFormation: &scan.EngineMetadata{}, + CustomChecks: scan.CustomChecks{ + Terraform: (*scan.TerraformCustomCheck)(nil), + }, + RegoPackage: "data.builtin.dockerfile.DS006", + Frameworks: map[framework.Framework][]string{}, + }, results.GetFailed()[0].Rule()) + + failure := results.GetFailed()[0] + actualCode, err := failure.GetCode() + require.NoError(t, err) + for i := range actualCode.Lines { + actualCode.Lines[i].Highlighted = "" + } + assert.Equal(t, []scan.Line{ + { + Number: 6, + Content: " BucketName: public-bucket", + IsCause: true, + FirstCause: true, + LastCause: true, + Annotation: "", + }, + }, actualCode.Lines) +} + +const bucketNameCheck = `# METADATA +# title: "test rego" +# scope: package +# schemas: +# - input: schema["cloud"] +# custom: +# id: AVD-AWS-001 +# avd_id: AVD-AWS-001 +# provider: aws +# service: s3 +# severity: LOW +# input: +# selector: +# - type: cloud +# subtypes: +# - service: s3 +# provider: aws +package user.aws.aws001 + +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "test-bucket" + res := result.new("Denied", bucket.name) +} + +deny[res] { + bucket := input.aws.s3.buckets[_] + algo := bucket.encryption.algorithm + algo.value == "AES256" + res := result.new("Denied", algo) +} +` + +func TestIgnore(t *testing.T) { + tests := []struct { + name string + src string + ignored int + }{ + { + name: "without ignore", + src: `--- +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: test-bucket +`, + ignored: 0, + }, + { + name: "rule before resource", + src: `--- +Resources: +#trivy:ignore:AVD-AWS-001 + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: test-bucket +`, + ignored: 1, + }, + { + name: "rule before property", + src: `--- +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: +#trivy:ignore:AVD-AWS-001 + BucketName: test-bucket +`, + ignored: 1, + }, + { + name: "rule on the same line with the property", + src: `--- +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: test-bucket #trivy:ignore:AVD-AWS-001 +`, + ignored: 1, + }, + { + name: "rule on the same line with the nested property", + src: `--- +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: test-bucket + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 #trivy:ignore:AVD-AWS-001 +`, + ignored: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsys := testutil.CreateFS(t, map[string]string{ + "/code/main.yaml": tt.src, + }) + + scanner := New( + options.ScannerWithRegoOnly(true), + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithPolicyReader(strings.NewReader(bucketNameCheck)), + options.ScannerWithPolicyNamespaces("user"), + ) + + results, err := scanner.ScanFS(context.TODO(), fsys, "code") + require.NoError(t, err) + + if tt.ignored == 0 { + require.Len(t, results.GetFailed(), 1) + } else { + assert.Len(t, results.GetIgnored(), tt.ignored) + } + }) + } +} diff --git a/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go b/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go new file mode 100644 index 000000000000..47063669a24d --- /dev/null +++ b/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go @@ -0,0 +1,48 @@ +package test + +import ( + "bytes" + "context" + "os" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +func Test_basic_cloudformation_scanning(t *testing.T) { + cfScanner := cloudformation.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + + results, err := cfScanner.ScanFS(context.TODO(), os.DirFS("./examples/bucket"), ".") + require.NoError(t, err) + + assert.Greater(t, len(results.GetFailed()), 0) +} + +func Test_cloudformation_scanning_has_expected_errors(t *testing.T) { + cfScanner := cloudformation.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + + results, err := cfScanner.ScanFS(context.TODO(), os.DirFS("./examples/bucket"), ".") + require.NoError(t, err) + + assert.Greater(t, len(results.GetFailed()), 0) +} + +func Test_cloudformation_scanning_with_debug(t *testing.T) { + + debugWriter := bytes.NewBufferString("") + + scannerOptions := []options.ScannerOption{ + options.ScannerWithDebug(debugWriter), + } + cfScanner := cloudformation.New(scannerOptions...) + + _, err := cfScanner.ScanFS(context.TODO(), os.DirFS("./examples/bucket"), ".") + require.NoError(t, err) + + // check debug is as expected + assert.Greater(t, len(debugWriter.String()), 0) +} diff --git a/pkg/iac/scanners/cloudformation/test/examples/bucket/bucket.yaml b/pkg/iac/scanners/cloudformation/test/examples/bucket/bucket.yaml new file mode 100644 index 000000000000..21f1c25042b0 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/test/examples/bucket/bucket.yaml @@ -0,0 +1,24 @@ +--- +AWSTemplateFormatVersion: "2010-09-09" +Description: An example Stack for a bucket +Parameters: + BucketName: + Type: String + Default: naughty-bucket + EncryptBucket: + Type: Boolean + Default: false +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: + Ref: BucketName + PublicAccessBlockConfiguration: + BlockPublicAcls: false + BlockPublicPolicy: false + IgnorePublicAcls: true + RestrictPublicBuckets: false + BucketEncryption: + ServerSideEncryptionConfiguration: + - BucketKeyEnabled: !Ref EncryptBucket diff --git a/pkg/iac/scanners/cloudformation/test/examples/ignores/bucket_with_ignores.yaml b/pkg/iac/scanners/cloudformation/test/examples/ignores/bucket_with_ignores.yaml new file mode 100644 index 000000000000..ec5e8a8d7661 --- /dev/null +++ b/pkg/iac/scanners/cloudformation/test/examples/ignores/bucket_with_ignores.yaml @@ -0,0 +1,24 @@ +--- +AWSTemplateFormatVersion: "2010-09-09" +Description: An example Stack for a bucket +Parameters: + BucketName: + Type: String + Default: naughty-bucket + EncryptBucket: + Type: Boolean + Default: false +Resources: + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: + Ref: BucketName + PublicAccessBlockConfiguration: + BlockPublicAcls: false + BlockPublicPolicy: false # cfsec:ignore:AVD-AWS-0087 + IgnorePublicAcls: true + RestrictPublicBuckets: false + BucketEncryption: + ServerSideEncryptionConfiguration: + - BucketKeyEnabled: !Ref EncryptBucket diff --git a/pkg/iac/scanners/cloudformation/test/examples/roles/roles.yml b/pkg/iac/scanners/cloudformation/test/examples/roles/roles.yml new file mode 100644 index 000000000000..5b927457762b --- /dev/null +++ b/pkg/iac/scanners/cloudformation/test/examples/roles/roles.yml @@ -0,0 +1,51 @@ +Resources: + LambdaAPIRole: + Type: "AWS::IAM::Role" + Properties: + RoleName: "${self:service}-${self:provider.stage}-LambdaAPI" + Policies: + - PolicyName: "${self:service}-${self:provider.stage}-lambda" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "logs:CreateLogStream" + - "logs:CreateLogGroup" + - "logs:PutLogEvents" + Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${self:service}-${self:provider.stage}*:*" + - !If + - EnableCrossAccountSnsPublish + - PolicyName: "${self:service}-${self:provider.stage}-asngen-sns-publish" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "SNS:Publish" + Resource: + - !Sub "arn:aws:sns:${self:provider.region}:${self:provider.itopia_account_id}:${self:provider.stage}-*-PurchaseOrder.fifo" + - !Sub "arn:aws:sns:${self:provider.region}:${self:provider.itopia_account_id}:${self:provider.stage}-*-Vendor.fifo" + - !Sub "arn:aws:sns:${self:provider.region}:${self:provider.itopia_account_id}:${self:provider.stage}-*-Customer.fifo" + - !Sub "arn:aws:sns:${self:provider.region}:${self:provider.itopia_account_id}:${self:provider.stage}-*-Manufacturer.fifo" + - !Sub "arn:aws:sns:${self:provider.region}:${self:provider.itopia_account_id}:${self:provider.stage}-*-ManufacturerItem.fifo" + - !Sub "arn:aws:sns:${self:provider.region}:${self:provider.itopia_account_id}:${self:provider.stage}-*-Item.fifo" + - !Sub "arn:aws:sns:${self:provider.region}:${self:provider.itopia_account_id}:${self:provider.stage}-*-VendorItem.fifo" + - !Ref "AWS::NoValue" + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - "lambda.amazonaws.com" + Action: + - "sts:AssumeRole" + + + + +Conditions: + EnableCrossAccountSnsPublish: !Equals + - ${env:ALLOW_SNS_PUBLISH, true} + - true diff --git a/pkg/iac/scanners/dockerfile/parser/parser.go b/pkg/iac/scanners/dockerfile/parser/parser.go new file mode 100644 index 000000000000..d6ff7b4df21a --- /dev/null +++ b/pkg/iac/scanners/dockerfile/parser/parser.go @@ -0,0 +1,151 @@ +package parser + +import ( + "context" + "fmt" + "io" + "io/fs" + "path/filepath" + "strings" + + "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/moby/buildkit/frontend/dockerfile/parser" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +var _ options.ConfigurableParser = (*Parser)(nil) + +type Parser struct { + debug debug.Logger + skipRequired bool +} + +func (p *Parser) SetDebugWriter(writer io.Writer) { + p.debug = debug.New(writer, "dockerfile", "parser") +} + +func (p *Parser) SetSkipRequiredCheck(b bool) { + p.skipRequired = b +} + +// New creates a new Dockerfile parser +func New(opts ...options.ParserOption) *Parser { + p := &Parser{} + for _, option := range opts { + option(p) + } + return p +} + +func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]*dockerfile.Dockerfile, error) { + + files := make(map[string]*dockerfile.Dockerfile) + if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if err != nil { + return err + } + if entry.IsDir() { + return nil + } + if !p.Required(path) { + return nil + } + df, err := p.ParseFile(ctx, target, path) + if err != nil { + // TODO add debug for parse errors + return nil + } + files[path] = df + return nil + }); err != nil { + return nil, err + } + return files, nil +} + +// ParseFile parses Dockerfile content from the provided filesystem path. +func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (*dockerfile.Dockerfile, error) { + f, err := fsys.Open(filepath.ToSlash(path)) + if err != nil { + return nil, err + } + defer func() { _ = f.Close() }() + return p.parse(path, f) +} + +func (p *Parser) Required(path string) bool { + if p.skipRequired { + return true + } + return detection.IsType(path, nil, detection.FileTypeDockerfile) +} + +func (p *Parser) parse(path string, r io.Reader) (*dockerfile.Dockerfile, error) { + parsed, err := parser.Parse(r) + if err != nil { + return nil, fmt.Errorf("dockerfile parse error: %w", err) + } + + var parsedFile dockerfile.Dockerfile + var stage dockerfile.Stage + var stageIndex int + fromValue := "args" + for _, child := range parsed.AST.Children { + child.Value = strings.ToLower(child.Value) + + instr, err := instructions.ParseInstruction(child) + if err != nil { + return nil, fmt.Errorf("process dockerfile instructions: %w", err) + } + + if _, ok := instr.(*instructions.Stage); ok { + if len(stage.Commands) > 0 { + parsedFile.Stages = append(parsedFile.Stages, stage) + } + if fromValue != "args" { + stageIndex++ + } + fromValue = strings.TrimSpace(strings.TrimPrefix(child.Original, "FROM ")) + stage = dockerfile.Stage{ + Name: fromValue, + } + } + + cmd := dockerfile.Command{ + Cmd: child.Value, + Original: child.Original, + Flags: child.Flags, + Stage: stageIndex, + Path: path, + StartLine: child.StartLine, + EndLine: child.EndLine, + } + + if child.Next != nil && len(child.Next.Children) > 0 { + cmd.SubCmd = child.Next.Children[0].Value + child = child.Next.Children[0] + } + + cmd.JSON = child.Attributes["json"] + for n := child.Next; n != nil; n = n.Next { + cmd.Value = append(cmd.Value, n.Value) + } + + stage.Commands = append(stage.Commands, cmd) + + } + if len(stage.Commands) > 0 { + parsedFile.Stages = append(parsedFile.Stages, stage) + } + + return &parsedFile, nil +} diff --git a/pkg/iac/scanners/dockerfile/parser/parser_test.go b/pkg/iac/scanners/dockerfile/parser/parser_test.go new file mode 100644 index 000000000000..04a45ea4695d --- /dev/null +++ b/pkg/iac/scanners/dockerfile/parser/parser_test.go @@ -0,0 +1,56 @@ +package parser + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Parser(t *testing.T) { + input := `FROM ubuntu:18.04 +COPY . /app +RUN make /app +CMD python /app/app.py +` + + df, err := New().parse("Dockerfile", strings.NewReader(input)) + require.NoError(t, err) + + assert.Equal(t, 1, len(df.Stages)) + + require.Len(t, df.Stages, 1) + + assert.Equal(t, "ubuntu:18.04", df.Stages[0].Name) + commands := df.Stages[0].Commands + assert.Equal(t, 4, len(commands)) + + // FROM ubuntu:18.04 + assert.Equal(t, "from", commands[0].Cmd) + assert.Equal(t, "ubuntu:18.04", commands[0].Value[0]) + assert.Equal(t, "Dockerfile", commands[0].Path) + assert.Equal(t, 1, commands[0].StartLine) + assert.Equal(t, 1, commands[0].EndLine) + + // COPY . /app + assert.Equal(t, "copy", commands[1].Cmd) + assert.Equal(t, ". /app", strings.Join(commands[1].Value, " ")) + assert.Equal(t, "Dockerfile", commands[1].Path) + assert.Equal(t, 2, commands[1].StartLine) + assert.Equal(t, 2, commands[1].EndLine) + + // RUN make /app + assert.Equal(t, "run", commands[2].Cmd) + assert.Equal(t, "make /app", commands[2].Value[0]) + assert.Equal(t, "Dockerfile", commands[2].Path) + assert.Equal(t, 3, commands[2].StartLine) + assert.Equal(t, 3, commands[2].EndLine) + + // CMD python /app/app.py + assert.Equal(t, "cmd", commands[3].Cmd) + assert.Equal(t, "python /app/app.py", commands[3].Value[0]) + assert.Equal(t, "Dockerfile", commands[3].Path) + assert.Equal(t, 4, commands[3].StartLine) + assert.Equal(t, 4, commands[3].EndLine) +} diff --git a/pkg/iac/scanners/dockerfile/scanner.go b/pkg/iac/scanners/dockerfile/scanner.go new file mode 100644 index 000000000000..88a18e35ed1a --- /dev/null +++ b/pkg/iac/scanners/dockerfile/scanner.go @@ -0,0 +1,181 @@ +package dockerfile + +import ( + "context" + "io" + "io/fs" + "sync" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners" + "github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var _ scanners.FSScanner = (*Scanner)(nil) +var _ options.ConfigurableScanner = (*Scanner)(nil) + +type Scanner struct { // nolint: gocritic + debug debug.Logger + policyDirs []string + policyReaders []io.Reader + parser *parser.Parser + regoScanner *rego.Scanner + skipRequired bool + options []options.ScannerOption + frameworks []framework.Framework + spec string + sync.Mutex + loadEmbeddedLibraries bool + loadEmbeddedPolicies bool +} + +func (s *Scanner) SetSpec(spec string) { + s.spec = spec +} + +func (s *Scanner) SetRegoOnly(bool) { +} + +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { + s.frameworks = frameworks +} + +func (s *Scanner) SetUseEmbeddedPolicies(b bool) { + s.loadEmbeddedPolicies = b +} + +func (s *Scanner) SetUseEmbeddedLibraries(b bool) { + s.loadEmbeddedLibraries = b +} + +func (s *Scanner) Name() string { + return "Dockerfile" +} + +func (s *Scanner) SetPolicyReaders(readers []io.Reader) { + s.policyReaders = readers +} + +func (s *Scanner) SetSkipRequiredCheck(skip bool) { + s.skipRequired = skip +} + +func (s *Scanner) SetDebugWriter(writer io.Writer) { + s.debug = debug.New(writer, "dockerfile", "scanner") +} + +func (s *Scanner) SetTraceWriter(_ io.Writer) { + // handled by rego later - nothing to do for now... +} + +func (s *Scanner) SetPerResultTracingEnabled(_ bool) { + // handled by rego later - nothing to do for now... +} + +func (s *Scanner) SetPolicyDirs(dirs ...string) { + s.policyDirs = dirs +} + +func (s *Scanner) SetDataDirs(_ ...string) { + // handled by rego later - nothing to do for now... +} + +func (s *Scanner) SetPolicyNamespaces(_ ...string) { + // handled by rego later - nothing to do for now... +} + +func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} + +func (s *Scanner) SetDataFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} + +func (s *Scanner) SetRegoErrorLimit(_ int) { + // handled by rego when option is passed on +} + +func NewScanner(opts ...options.ScannerOption) *Scanner { + s := &Scanner{ + options: opts, + } + for _, opt := range opts { + opt(s) + } + s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) + return s +} + +func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, path string) (scan.Results, error) { + + files, err := s.parser.ParseFS(ctx, fsys, path) + if err != nil { + return nil, err + } + + if len(files) == 0 { + return nil, nil + } + + var inputs []rego.Input + for path, dfile := range files { + inputs = append(inputs, rego.Input{ + Path: path, + FS: fsys, + Contents: dfile.ToRego(), + }) + } + + results, err := s.scanRego(ctx, fsys, inputs...) + if err != nil { + return nil, err + } + return results, nil +} + +func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.Results, error) { + dockerfile, err := s.parser.ParseFile(ctx, fsys, path) + if err != nil { + return nil, err + } + s.debug.Log("Scanning %s...", path) + return s.scanRego(ctx, fsys, rego.Input{ + Path: path, + Contents: dockerfile.ToRego(), + }) +} + +func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { + s.Lock() + defer s.Unlock() + if s.regoScanner != nil { + return s.regoScanner, nil + } + + regoScanner := rego.NewScanner(types.SourceDockerfile, s.options...) + regoScanner.SetParentDebugLogger(s.debug) + if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + return nil, err + } + s.regoScanner = regoScanner + return regoScanner, nil +} + +func (s *Scanner) scanRego(ctx context.Context, srcFS fs.FS, inputs ...rego.Input) (scan.Results, error) { + regoScanner, err := s.initRegoScanner(srcFS) + if err != nil { + return nil, err + } + results, err := regoScanner.ScanInput(ctx, inputs...) + if err != nil { + return nil, err + } + results.SetSourceAndFilesystem("", srcFS, false) + return results, nil +} diff --git a/pkg/iac/scanners/dockerfile/scanner_test.go b/pkg/iac/scanners/dockerfile/scanner_test.go new file mode 100644 index 000000000000..e396b15e1b09 --- /dev/null +++ b/pkg/iac/scanners/dockerfile/scanner_test.go @@ -0,0 +1,637 @@ +package dockerfile + +import ( + "bytes" + "context" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/rego/schemas" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const DS006PolicyWithDockerfileSchema = `# METADATA +# title: "COPY '--from' referring to the current image" +# description: "COPY '--from' should not mention the current FROM alias, since it is impossible to copy from itself." +# scope: package +# schemas: +# - input: schema["dockerfile"] +# related_resources: +# - https://docs.docker.com/develop/develop-images/multistage-build/ +# custom: +# id: DS006 +# avd_id: AVD-DS-0006 +# severity: CRITICAL +# short_code: no-self-referencing-copy-from +# recommended_action: "Change the '--from' so that it will not refer to itself" +# input: +# selector: +# - type: dockerfile +package builtin.dockerfile.DS006 + +import data.lib.docker + +get_alias_from_copy[output] { + copies := docker.stage_copies[stage] + + copy := copies[_] + flag := copy.Flags[_] + contains(flag, "--from=") + parts := split(flag, "=") + + is_alias_current_from_alias(stage.Name, parts[1]) + args := parts[1] + output := { + "args": args, + "cmd": copy, + } +} + +is_alias_current_from_alias(current_name, current_alias) = allow { + current_name_lower := lower(current_name) + current_alias_lower := lower(current_alias) + + #expecting stage name as "myimage:tag as dep" + [_, alias] := regex.split(` + "`\\s+as\\s+`" + `, current_name_lower) + + alias == current_alias + + allow = true +} + +deny[res] { + output := get_alias_from_copy[_] + msg := sprintf("'COPY --from' should not mention current alias '%s' since it is impossible to copy from itself", [output.args]) + res := result.new(msg, output.cmd) +} +` + +const DS006PolicyWithMyFancyDockerfileSchema = `# METADATA +# title: "COPY '--from' referring to the current image" +# description: "COPY '--from' should not mention the current FROM alias, since it is impossible to copy from itself." +# scope: package +# schemas: +# - input: schema["myfancydockerfile"] +# related_resources: +# - https://docs.docker.com/develop/develop-images/multistage-build/ +# custom: +# id: DS006 +# avd_id: AVD-DS-0006 +# severity: CRITICAL +# short_code: no-self-referencing-copy-from +# recommended_action: "Change the '--from' so that it will not refer to itself" +# input: +# selector: +# - type: dockerfile +package builtin.dockerfile.DS006 + +import data.lib.docker + +get_alias_from_copy[output] { +copies := docker.stage_copies[stage] + +copy := copies[_] +flag := copy.Flags[_] +contains(flag, "--from=") +parts := split(flag, "=") + +is_alias_current_from_alias(stage.Name, parts[1]) +args := parts[1] +output := { +"args": args, +"cmd": copy, +} +} + +is_alias_current_from_alias(current_name, current_alias) = allow { +current_name_lower := lower(current_name) +current_alias_lower := lower(current_alias) + +#expecting stage name as "myimage:tag as dep" +[_, alias] := regex.split(` + "`\\s+as\\s+`" + `, current_name_lower) + +alias == current_alias + +allow = true +} + +deny[res] { +output := get_alias_from_copy[_] +msg := sprintf("'COPY --from' should not mention current alias '%s' since it is impossible to copy from itself", [output.args]) +res := result.new(msg, output.cmd) +} +` + +const DS006PolicyWithOldSchemaSelector = `# METADATA +# title: "COPY '--from' referring to the current image" +# description: "COPY '--from' should not mention the current FROM alias, since it is impossible to copy from itself." +# scope: package +# schemas: +# - input: schema["input"] +# related_resources: +# - https://docs.docker.com/develop/develop-images/multistage-build/ +# custom: +# id: DS006 +# avd_id: AVD-DS-0006 +# severity: CRITICAL +# short_code: no-self-referencing-copy-from +# recommended_action: "Change the '--from' so that it will not refer to itself" +# input: +# selector: +# - type: dockerfile +package builtin.dockerfile.DS006 + +import data.lib.docker + +get_alias_from_copy[output] { + copies := docker.stage_copies[stage] + + copy := copies[_] + flag := copy.Flags[_] + contains(flag, "--from=") + parts := split(flag, "=") + + is_alias_current_from_alias(stage.Name, parts[1]) + args := parts[1] + output := { + "args": args, + "cmd": copy, + } +} + +is_alias_current_from_alias(current_name, current_alias) = allow { + current_name_lower := lower(current_name) + current_alias_lower := lower(current_alias) + + #expecting stage name as "myimage:tag as dep" + [_, alias] := regex.split(` + "`\\s+as\\s+`" + `, current_name_lower) + + alias == current_alias + + allow = true +} + +deny[res] { + output := get_alias_from_copy[_] + msg := sprintf("'COPY --from' should not mention current alias '%s' since it is impossible to copy from itself", [output.args]) + res := result.new(msg, output.cmd) +} +` +const DS006LegacyWithOldStyleMetadata = `package builtin.dockerfile.DS006 + +__rego_metadata__ := { + "id": "DS006", + "avd_id": "AVD-DS-0006", + "title": "COPY '--from' referring to the current image", + "short_code": "no-self-referencing-copy-from", + "version": "v1.0.0", + "severity": "CRITICAL", + "type": "Dockerfile Security Check", + "description": "COPY '--from' should not mention the current FROM alias, since it is impossible to copy from itself.", + "recommended_actions": "Change the '--from' so that it will not refer to itself", + "url": "https://docs.docker.com/develop/develop-images/multistage-build/", +} + +__rego_input__ := { + "combine": false, + "selector": [{"type": "dockerfile"}], +} + +deny[res] { + res := { + "msg": "oh no", + "filepath": "code/Dockerfile", + "startline": 1, + "endline": 1, + } +}` + +func Test_BasicScanLegacyRegoMetadata(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "/code/Dockerfile": `FROM ubuntu +USER root +`, + "/rules/rule.rego": DS006LegacyWithOldStyleMetadata, + }) + + scanner := NewScanner(options.ScannerWithPolicyDirs("rules")) + + results, err := scanner.ScanFS(context.TODO(), fs, "code") + require.NoError(t, err) + + require.Len(t, results.GetFailed(), 1) + + failure := results.GetFailed()[0] + metadata := failure.Metadata() + assert.Equal(t, 1, metadata.Range().GetStartLine()) + assert.Equal(t, 1, metadata.Range().GetEndLine()) + assert.Equal(t, "code/Dockerfile", metadata.Range().GetFilename()) + + assert.Equal( + t, + scan.Rule{ + AVDID: "AVD-DS-0006", + Aliases: []string{"DS006"}, + ShortCode: "no-self-referencing-copy-from", + Summary: "COPY '--from' referring to the current image", + Explanation: "COPY '--from' should not mention the current FROM alias, since it is impossible to copy from itself.", + Impact: "", + Resolution: "Change the '--from' so that it will not refer to itself", + Provider: "dockerfile", + Service: "general", + Links: []string{"https://docs.docker.com/develop/develop-images/multistage-build/"}, + Severity: "CRITICAL", + Terraform: &scan.EngineMetadata{}, + CloudFormation: &scan.EngineMetadata{}, + CustomChecks: scan.CustomChecks{ + Terraform: (*scan.TerraformCustomCheck)(nil)}, + RegoPackage: "data.builtin.dockerfile.DS006", + Frameworks: map[framework.Framework][]string{}, + }, + results.GetFailed()[0].Rule(), + ) + + actualCode, err := results.GetFailed()[0].GetCode() + require.NoError(t, err) + for i := range actualCode.Lines { + actualCode.Lines[i].Highlighted = "" + } + assert.Equal(t, []scan.Line{ + { + Number: 1, + Content: "FROM ubuntu", + IsCause: true, + FirstCause: true, + LastCause: true, + Annotation: "", + }, + }, actualCode.Lines) +} + +func Test_BasicScanNewRegoMetadata(t *testing.T) { + var testCases = []struct { + name string + inputRegoPolicy string + expectedError string + expectedInputTraceLogs string + expectedOutputTraceLogs string + }{ + { + name: "old schema selector schema.input", + inputRegoPolicy: DS006PolicyWithOldSchemaSelector, + expectedInputTraceLogs: `REGO INPUT: +{ + "path": "code/Dockerfile", + "contents": { + "Stages": [ + { + "Commands": [ + { + "Cmd": "from", + "EndLine": 1, + "Flags": [], + "JSON": false, + "Original": "FROM golang:1.7.3 as dep", + "Path": "code/Dockerfile", + "Stage": 0, + "StartLine": 1, + "SubCmd": "", + "Value": [ + "golang:1.7.3", + "as", + "dep" + ] + }, + { + "Cmd": "copy", + "EndLine": 2, + "Flags": [ + "--from=dep" + ], + "JSON": false, + "Original": "COPY --from=dep /binary /", + "Path": "code/Dockerfile", + "Stage": 0, + "StartLine": 2, + "SubCmd": "", + "Value": [ + "/binary", + "/" + ] + } + ], + "Name": "golang:1.7.3 as dep" + } + ] + } +} +END REGO INPUT +`, + expectedOutputTraceLogs: `REGO RESULTSET: +[ + { + "expressions": [ + { + "value": [ + { + "endline": 2, + "explicit": false, + "filepath": "code/Dockerfile", + "fskey": "", + "managed": true, + "msg": "'COPY --from' should not mention current alias 'dep' since it is impossible to copy from itself", + "parent": null, + "resource": "", + "sourceprefix": "", + "startline": 2 + } + ], + "text": "data.builtin.dockerfile.DS006.deny", + "location": { + "row": 1, + "col": 1 + } + } + ] + } +] +END REGO RESULTSET + +`, + }, + { + name: "new schema selector schema.dockerfile", + inputRegoPolicy: DS006PolicyWithDockerfileSchema, + expectedInputTraceLogs: `REGO INPUT: +{ + "path": "code/Dockerfile", + "contents": { + "Stages": [ + { + "Commands": [ + { + "Cmd": "from", + "EndLine": 1, + "Flags": [], + "JSON": false, + "Original": "FROM golang:1.7.3 as dep", + "Path": "code/Dockerfile", + "Stage": 0, + "StartLine": 1, + "SubCmd": "", + "Value": [ + "golang:1.7.3", + "as", + "dep" + ] + }, + { + "Cmd": "copy", + "EndLine": 2, + "Flags": [ + "--from=dep" + ], + "JSON": false, + "Original": "COPY --from=dep /binary /", + "Path": "code/Dockerfile", + "Stage": 0, + "StartLine": 2, + "SubCmd": "", + "Value": [ + "/binary", + "/" + ] + } + ], + "Name": "golang:1.7.3 as dep" + } + ] + } +} +END REGO INPUT +`, + expectedOutputTraceLogs: `REGO RESULTSET: +[ + { + "expressions": [ + { + "value": [ + { + "endline": 2, + "explicit": false, + "filepath": "code/Dockerfile", + "fskey": "", + "managed": true, + "msg": "'COPY --from' should not mention current alias 'dep' since it is impossible to copy from itself", + "parent": null, + "resource": "", + "sourceprefix": "", + "startline": 2 + } + ], + "text": "data.builtin.dockerfile.DS006.deny", + "location": { + "row": 1, + "col": 1 + } + } + ] + } +] +END REGO RESULTSET + +`, + }, + { + name: "new schema selector with custom schema.myfancydockerfile", + inputRegoPolicy: DS006PolicyWithMyFancyDockerfileSchema, + expectedInputTraceLogs: `REGO INPUT: +{ + "path": "code/Dockerfile", + "contents": { + "Stages": [ + { + "Commands": [ + { + "Cmd": "from", + "EndLine": 1, + "Flags": [], + "JSON": false, + "Original": "FROM golang:1.7.3 as dep", + "Path": "code/Dockerfile", + "Stage": 0, + "StartLine": 1, + "SubCmd": "", + "Value": [ + "golang:1.7.3", + "as", + "dep" + ] + }, + { + "Cmd": "copy", + "EndLine": 2, + "Flags": [ + "--from=dep" + ], + "JSON": false, + "Original": "COPY --from=dep /binary /", + "Path": "code/Dockerfile", + "Stage": 0, + "StartLine": 2, + "SubCmd": "", + "Value": [ + "/binary", + "/" + ] + } + ], + "Name": "golang:1.7.3 as dep" + } + ] + } +} +END REGO INPUT +`, + expectedOutputTraceLogs: `REGO RESULTSET: +[ + { + "expressions": [ + { + "value": [ + { + "endline": 2, + "explicit": false, + "filepath": "code/Dockerfile", + "fskey": "", + "managed": true, + "msg": "'COPY --from' should not mention current alias 'dep' since it is impossible to copy from itself", + "parent": null, + "resource": "", + "sourceprefix": "", + "startline": 2 + } + ], + "text": "data.builtin.dockerfile.DS006.deny", + "location": { + "row": 1, + "col": 1 + } + } + ] + } +] +END REGO RESULTSET + +`, + }, + { + name: "new schema selector but invalid", + inputRegoPolicy: `# METADATA +# title: "COPY '--from' referring to the current image" +# description: "COPY '--from' should not mention the current FROM alias, since it is impossible to copy from itself." +# scope: package +# schemas: +# - input: schema["spooky-schema"] +# custom: +# input: +# selector: +# - type: dockerfile +package builtin.dockerfile.DS006 +deny[res]{ +res := true +}`, + expectedError: `1 error occurred: rules/rule.rego:12: rego_type_error: undefined schema: schema["spooky-schema"]`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + regoMap := make(map[string]string) + libs, err := rego.LoadEmbeddedLibraries() + require.NoError(t, err) + for name, library := range libs { + regoMap["/rules/"+name] = library.String() + } + regoMap["/code/Dockerfile"] = `FROM golang:1.7.3 as dep +COPY --from=dep /binary /` + regoMap["/rules/rule.rego"] = tc.inputRegoPolicy + regoMap["/rules/schemas/myfancydockerfile.json"] = string(schemas.Dockerfile) // just use the same for testing + fs := testutil.CreateFS(t, regoMap) + + var traceBuf bytes.Buffer + var debugBuf bytes.Buffer + + scanner := NewScanner( + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithTrace(&traceBuf), + options.ScannerWithDebug(&debugBuf), + options.ScannerWithRegoErrorLimits(0), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, "code") + if tc.expectedError != "" && err != nil { + require.Equal(t, tc.expectedError, err.Error(), tc.name) + } else { + require.NoError(t, err) + require.Len(t, results.GetFailed(), 1) + + failure := results.GetFailed()[0] + metadata := failure.Metadata() + assert.Equal(t, 2, metadata.Range().GetStartLine()) + assert.Equal(t, 2, metadata.Range().GetEndLine()) + assert.Equal(t, "code/Dockerfile", metadata.Range().GetFilename()) + + assert.Equal( + t, + scan.Rule{ + AVDID: "AVD-DS-0006", + Aliases: []string{"DS006"}, + ShortCode: "no-self-referencing-copy-from", + Summary: "COPY '--from' referring to the current image", + Explanation: "COPY '--from' should not mention the current FROM alias, since it is impossible to copy from itself.", + Impact: "", + Resolution: "Change the '--from' so that it will not refer to itself", + Provider: "dockerfile", + Service: "general", + Links: []string{"https://docs.docker.com/develop/develop-images/multistage-build/"}, + Severity: "CRITICAL", + Terraform: &scan.EngineMetadata{}, + CloudFormation: &scan.EngineMetadata{}, + CustomChecks: scan.CustomChecks{ + Terraform: (*scan.TerraformCustomCheck)(nil)}, + RegoPackage: "data.builtin.dockerfile.DS006", + Frameworks: map[framework.Framework][]string{}, + }, + results.GetFailed()[0].Rule(), + ) + + actualCode, err := results.GetFailed()[0].GetCode() + require.NoError(t, err) + for i := range actualCode.Lines { + actualCode.Lines[i].Highlighted = "" + } + assert.Equal(t, []scan.Line{ + { + Number: 2, + Content: "COPY --from=dep /binary /", + IsCause: true, + FirstCause: true, + LastCause: true, + Annotation: "", + }, + }, actualCode.Lines) + + // assert logs + assert.Contains(t, traceBuf.String(), tc.expectedInputTraceLogs, traceBuf.String()) + assert.Contains(t, traceBuf.String(), tc.expectedOutputTraceLogs, traceBuf.String()) + } + }) + } + +} diff --git a/pkg/iac/scanners/helm/options.go b/pkg/iac/scanners/helm/options.go new file mode 100644 index 000000000000..6ac412bf1e34 --- /dev/null +++ b/pkg/iac/scanners/helm/options.go @@ -0,0 +1,59 @@ +package helm + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scanners/helm/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +type ConfigurableHelmScanner interface { + options.ConfigurableScanner + AddParserOptions(options ...options.ParserOption) +} + +func ScannerWithValuesFile(paths ...string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if helmScanner, ok := s.(ConfigurableHelmScanner); ok { + helmScanner.AddParserOptions(parser.OptionWithValuesFile(paths...)) + } + } +} + +func ScannerWithValues(values ...string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if helmScanner, ok := s.(ConfigurableHelmScanner); ok { + helmScanner.AddParserOptions(parser.OptionWithValues(values...)) + } + } +} + +func ScannerWithFileValues(values ...string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if helmScanner, ok := s.(ConfigurableHelmScanner); ok { + helmScanner.AddParserOptions(parser.OptionWithFileValues(values...)) + } + } +} + +func ScannerWithStringValues(values ...string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if helmScanner, ok := s.(ConfigurableHelmScanner); ok { + helmScanner.AddParserOptions(parser.OptionWithStringValues(values...)) + } + } +} + +func ScannerWithAPIVersions(values ...string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if helmScanner, ok := s.(ConfigurableHelmScanner); ok { + helmScanner.AddParserOptions(parser.OptionWithAPIVersions(values...)) + } + } +} + +func ScannerWithKubeVersion(values string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if helmScanner, ok := s.(ConfigurableHelmScanner); ok { + helmScanner.AddParserOptions(parser.OptionWithKubeVersion(values)) + } + } +} diff --git a/pkg/iac/scanners/helm/parser/option.go b/pkg/iac/scanners/helm/parser/option.go new file mode 100644 index 000000000000..6de98d765182 --- /dev/null +++ b/pkg/iac/scanners/helm/parser/option.go @@ -0,0 +1,61 @@ +package parser + +import "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + +type ConfigurableHelmParser interface { + options.ConfigurableParser + SetValuesFile(...string) + SetValues(...string) + SetFileValues(...string) + SetStringValues(...string) + SetAPIVersions(...string) + SetKubeVersion(string) +} + +func OptionWithValuesFile(paths ...string) options.ParserOption { + return func(p options.ConfigurableParser) { + if helmParser, ok := p.(ConfigurableHelmParser); ok { + helmParser.SetValuesFile(paths...) + } + } +} + +func OptionWithValues(values ...string) options.ParserOption { + return func(p options.ConfigurableParser) { + if helmParser, ok := p.(ConfigurableHelmParser); ok { + helmParser.SetValues(values...) + } + } +} + +func OptionWithFileValues(values ...string) options.ParserOption { + return func(p options.ConfigurableParser) { + if helmParser, ok := p.(ConfigurableHelmParser); ok { + helmParser.SetValues(values...) + } + } +} + +func OptionWithStringValues(values ...string) options.ParserOption { + return func(p options.ConfigurableParser) { + if helmParser, ok := p.(ConfigurableHelmParser); ok { + helmParser.SetValues(values...) + } + } +} + +func OptionWithAPIVersions(values ...string) options.ParserOption { + return func(p options.ConfigurableParser) { + if helmParser, ok := p.(ConfigurableHelmParser); ok { + helmParser.SetAPIVersions(values...) + } + } +} + +func OptionWithKubeVersion(value string) options.ParserOption { + return func(p options.ConfigurableParser) { + if helmParser, ok := p.(ConfigurableHelmParser); ok { + helmParser.SetKubeVersion(value) + } + } +} diff --git a/pkg/iac/scanners/helm/parser/parser.go b/pkg/iac/scanners/helm/parser/parser.go new file mode 100644 index 000000000000..c8bc8a73bedd --- /dev/null +++ b/pkg/iac/scanners/helm/parser/parser.go @@ -0,0 +1,324 @@ +package parser + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/fs" + "path/filepath" + "regexp" + "sort" + "strings" + + "github.com/google/uuid" + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/releaseutil" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + detection2 "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +var manifestNameRegex = regexp.MustCompile("# Source: [^/]+/(.+)") + +type Parser struct { + helmClient *action.Install + rootPath string + ChartSource string + filepaths []string + debug debug.Logger + skipRequired bool + workingFS fs.FS + valuesFiles []string + values []string + fileValues []string + stringValues []string + apiVersions []string + kubeVersion string +} + +type ChartFile struct { + TemplateFilePath string + ManifestContent string +} + +func (p *Parser) SetDebugWriter(writer io.Writer) { + p.debug = debug.New(writer, "helm", "parser") +} + +func (p *Parser) SetSkipRequiredCheck(b bool) { + p.skipRequired = b +} + +func (p *Parser) SetValuesFile(s ...string) { + p.valuesFiles = s +} + +func (p *Parser) SetValues(values ...string) { + p.values = values +} + +func (p *Parser) SetFileValues(values ...string) { + p.fileValues = values +} + +func (p *Parser) SetStringValues(values ...string) { + p.stringValues = values +} + +func (p *Parser) SetAPIVersions(values ...string) { + p.apiVersions = values +} + +func (p *Parser) SetKubeVersion(value string) { + p.kubeVersion = value +} + +func New(path string, opts ...options.ParserOption) (*Parser, error) { + + client := action.NewInstall(&action.Configuration{}) + client.DryRun = true // don't do anything + client.Replace = true // skip name check + client.ClientOnly = true // don't try to talk to a cluster + + p := &Parser{ + helmClient: client, + ChartSource: path, + } + + for _, option := range opts { + option(p) + } + + if p.apiVersions != nil { + p.helmClient.APIVersions = p.apiVersions + } + + if p.kubeVersion != "" { + kubeVersion, err := chartutil.ParseKubeVersion(p.kubeVersion) + if err != nil { + return nil, err + } + + p.helmClient.KubeVersion = kubeVersion + } + + return p, nil +} + +func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) error { + p.workingFS = target + + if err := fs.WalkDir(p.workingFS, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if err != nil { + return err + } + if entry.IsDir() { + return nil + } + + if !p.required(path, p.workingFS) { + return nil + } + + if detection2.IsArchive(path) { + tarFS, err := p.addTarToFS(path) + if errors.Is(err, errSkipFS) { + // an unpacked Chart already exists + return nil + } else if err != nil { + return fmt.Errorf("failed to add tar %q to FS: %w", path, err) + } + + targetPath := filepath.Dir(path) + if targetPath == "" { + targetPath = "." + } + + if err := p.ParseFS(ctx, tarFS, targetPath); err != nil { + return fmt.Errorf("parse tar FS error: %w", err) + } + return nil + } else { + return p.addPaths(path) + } + }); err != nil { + return fmt.Errorf("walk dir error: %w", err) + } + + return nil +} + +func (p *Parser) addPaths(paths ...string) error { + for _, path := range paths { + if _, err := fs.Stat(p.workingFS, path); err != nil { + return err + } + + if strings.HasSuffix(path, "Chart.yaml") && p.rootPath == "" { + if err := p.extractChartName(path); err != nil { + return err + } + p.rootPath = filepath.Dir(path) + } + p.filepaths = append(p.filepaths, path) + } + return nil +} + +func (p *Parser) extractChartName(chartPath string) error { + + chrt, err := p.workingFS.Open(chartPath) + if err != nil { + return err + } + defer func() { _ = chrt.Close() }() + + var chartContent map[string]interface{} + if err := yaml.NewDecoder(chrt).Decode(&chartContent); err != nil { + // the chart likely has the name templated and so cannot be parsed as yaml - use a temporary name + if dir := filepath.Dir(chartPath); dir != "" && dir != "." { + p.helmClient.ReleaseName = dir + } else { + p.helmClient.ReleaseName = uuid.NewString() + } + return nil + } + + if name, ok := chartContent["name"]; !ok { + return fmt.Errorf("could not extract the chart name from %s", chartPath) + } else { + p.helmClient.ReleaseName = fmt.Sprintf("%v", name) + } + return nil +} + +func (p *Parser) RenderedChartFiles() ([]ChartFile, error) { + workingChart, err := p.loadChart() + if err != nil { + return nil, err + } + + workingRelease, err := p.getRelease(workingChart) + if err != nil { + return nil, err + } + + var manifests bytes.Buffer + _, _ = fmt.Fprintln(&manifests, strings.TrimSpace(workingRelease.Manifest)) + + splitManifests := releaseutil.SplitManifests(manifests.String()) + manifestsKeys := make([]string, 0, len(splitManifests)) + for k := range splitManifests { + manifestsKeys = append(manifestsKeys, k) + } + return p.getRenderedManifests(manifestsKeys, splitManifests), nil +} + +func (p *Parser) getRelease(chrt *chart.Chart) (*release.Release, error) { + opts := &ValueOptions{ + ValueFiles: p.valuesFiles, + Values: p.values, + FileValues: p.fileValues, + StringValues: p.stringValues, + } + + vals, err := opts.MergeValues() + if err != nil { + return nil, err + } + r, err := p.helmClient.RunWithContext(context.Background(), chrt, vals) + if err != nil { + return nil, err + } + + if r == nil { + return nil, fmt.Errorf("there is nothing in the release") + } + return r, nil +} + +func (p *Parser) loadChart() (*chart.Chart, error) { + + var files []*loader.BufferedFile + + for _, filePath := range p.filepaths { + b, err := fs.ReadFile(p.workingFS, filePath) + if err != nil { + return nil, err + } + + filePath = strings.TrimPrefix(filePath, p.rootPath+"/") + filePath = filepath.ToSlash(filePath) + files = append(files, &loader.BufferedFile{ + Name: filePath, + Data: b, + }) + } + + c, err := loader.LoadFiles(files) + if err != nil { + return nil, err + } + + if req := c.Metadata.Dependencies; req != nil { + if err := action.CheckDependencies(c, req); err != nil { + return nil, err + } + } + + return c, nil +} + +func (*Parser) getRenderedManifests(manifestsKeys []string, splitManifests map[string]string) []ChartFile { + sort.Sort(releaseutil.BySplitManifestsOrder(manifestsKeys)) + var manifestsToRender []ChartFile + for _, manifestKey := range manifestsKeys { + manifest := splitManifests[manifestKey] + submatch := manifestNameRegex.FindStringSubmatch(manifest) + if len(submatch) == 0 { + continue + } + manifestsToRender = append(manifestsToRender, ChartFile{ + TemplateFilePath: getManifestPath(manifest), + ManifestContent: manifest, + }) + } + return manifestsToRender +} + +func getManifestPath(manifest string) string { + lines := strings.Split(manifest, "\n") + if len(lines) == 0 { + return "unknown.yaml" + } + manifestFilePathParts := strings.SplitN(strings.TrimPrefix(lines[0], "# Source: "), "/", 2) + if len(manifestFilePathParts) > 1 { + return manifestFilePathParts[1] + } + return manifestFilePathParts[0] +} + +func (p *Parser) required(path string, workingFS fs.FS) bool { + if p.skipRequired { + return true + } + content, err := fs.ReadFile(workingFS, path) + if err != nil { + return false + } + + return detection2.IsType(path, bytes.NewReader(content), detection2.FileTypeHelm) +} diff --git a/pkg/iac/scanners/helm/parser/parser_tar.go b/pkg/iac/scanners/helm/parser/parser_tar.go new file mode 100644 index 000000000000..5455ab780683 --- /dev/null +++ b/pkg/iac/scanners/helm/parser/parser_tar.go @@ -0,0 +1,111 @@ +package parser + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + + "github.com/liamg/memoryfs" + + "github.com/aquasecurity/trivy/pkg/iac/detection" +) + +var errSkipFS = errors.New("skip parse FS") + +func (p *Parser) addTarToFS(path string) (fs.FS, error) { + tarFS := memoryfs.CloneFS(p.workingFS) + + file, err := tarFS.Open(path) + if err != nil { + return nil, fmt.Errorf("failed to open tar: %w", err) + } + defer file.Close() + + var tr *tar.Reader + + if detection.IsZip(path) { + zipped, err := gzip.NewReader(file) + if err != nil { + return nil, fmt.Errorf("failed to create gzip reader: %w", err) + } + defer zipped.Close() + tr = tar.NewReader(zipped) + } else { + tr = tar.NewReader(file) + } + + checkExistedChart := true + + for { + header, err := tr.Next() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, fmt.Errorf("failed to get next entry: %w", err) + } + + if checkExistedChart { + // Do not add archive files to FS if the chart already exists + // This can happen when the source chart is located next to an archived chart (with the `helm package` command) + // The first level folder in the archive is equal to the Chart name + if _, err := tarFS.Stat(filepath.Dir(path) + "/" + filepath.Dir(header.Name)); err == nil { + return nil, errSkipFS + } + checkExistedChart = false + } + + // get the individual path and extract to the current directory + entryPath := header.Name + + switch header.Typeflag { + case tar.TypeDir: + if err := tarFS.MkdirAll(entryPath, os.FileMode(header.Mode)); err != nil && !errors.Is(err, fs.ErrExist) { + return nil, err + } + case tar.TypeReg: + writePath := filepath.Dir(path) + "/" + entryPath + p.debug.Log("Unpacking tar entry %s", writePath) + + _ = tarFS.MkdirAll(filepath.Dir(writePath), fs.ModePerm) + + buf, err := copyChunked(tr, 1024) + if err != nil { + return nil, err + } + + p.debug.Log("writing file contents to %s", writePath) + if err := tarFS.WriteFile(writePath, buf.Bytes(), fs.ModePerm); err != nil { + return nil, fmt.Errorf("write file error: %w", err) + } + default: + return nil, fmt.Errorf("header type %q is not supported", header.Typeflag) + } + } + + if err := tarFS.Remove(path); err != nil { + return nil, fmt.Errorf("failed to remove tar from FS: %w", err) + } + + return tarFS, nil +} + +func copyChunked(src io.Reader, chunkSize int64) (*bytes.Buffer, error) { + buf := new(bytes.Buffer) + for { + if _, err := io.CopyN(buf, src, chunkSize); err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, fmt.Errorf("failed to copy: %w", err) + } + } + + return buf, nil +} diff --git a/pkg/iac/scanners/helm/parser/parser_test.go b/pkg/iac/scanners/helm/parser/parser_test.go new file mode 100644 index 000000000000..030b0efbb86f --- /dev/null +++ b/pkg/iac/scanners/helm/parser/parser_test.go @@ -0,0 +1,25 @@ +package parser + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseFS(t *testing.T) { + t.Run("source chart is located next to an same archived chart", func(t *testing.T) { + p, err := New(".") + require.NoError(t, err) + require.NoError(t, p.ParseFS(context.TODO(), os.DirFS(filepath.Join("testdata", "chart-and-archived-chart")), ".")) + + expectedFiles := []string{ + "my-chart/Chart.yaml", + "my-chart/templates/pod.yaml", + } + assert.Equal(t, expectedFiles, p.filepaths) + }) +} diff --git a/pkg/iac/scanners/helm/parser/testdata/chart-and-archived-chart/my-chart-0.1.0.tgz b/pkg/iac/scanners/helm/parser/testdata/chart-and-archived-chart/my-chart-0.1.0.tgz new file mode 100644 index 000000000000..e36b2b474f3e Binary files /dev/null and b/pkg/iac/scanners/helm/parser/testdata/chart-and-archived-chart/my-chart-0.1.0.tgz differ diff --git a/pkg/iac/scanners/helm/parser/testdata/chart-and-archived-chart/my-chart/Chart.yaml b/pkg/iac/scanners/helm/parser/testdata/chart-and-archived-chart/my-chart/Chart.yaml new file mode 100644 index 000000000000..767f748a8d59 --- /dev/null +++ b/pkg/iac/scanners/helm/parser/testdata/chart-and-archived-chart/my-chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: my-chart +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "1.16.0" diff --git a/pkg/iac/scanners/helm/parser/testdata/chart-and-archived-chart/my-chart/templates/pod.yaml b/pkg/iac/scanners/helm/parser/testdata/chart-and-archived-chart/my-chart/templates/pod.yaml new file mode 100644 index 000000000000..3649247c1bb1 --- /dev/null +++ b/pkg/iac/scanners/helm/parser/testdata/chart-and-archived-chart/my-chart/templates/pod.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 \ No newline at end of file diff --git a/pkg/iac/scanners/helm/parser/vals.go b/pkg/iac/scanners/helm/parser/vals.go new file mode 100644 index 000000000000..b54cd7c3a521 --- /dev/null +++ b/pkg/iac/scanners/helm/parser/vals.go @@ -0,0 +1,114 @@ +package parser + +import ( + "fmt" + "io" + "net/url" + "os" + "strings" + + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/strvals" +) + +type ValueOptions struct { + ValueFiles []string + StringValues []string + Values []string + FileValues []string +} + +// MergeValues merges values from files specified via -f/--values and directly +// via --set, --set-string, or --set-file, marshaling them to YAML +func (opts *ValueOptions) MergeValues() (map[string]interface{}, error) { + base := make(map[string]interface{}) + + // User specified a values files via -f/--values + for _, filePath := range opts.ValueFiles { + currentMap := make(map[string]interface{}) + + bytes, err := readFile(filePath) + if err != nil { + return nil, err + } + + if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { + return nil, fmt.Errorf("failed to parse %s: %w", filePath, err) + } + // Merge with the previous map + base = mergeMaps(base, currentMap) + } + + // User specified a value via --set + for _, value := range opts.Values { + if err := strvals.ParseInto(value, base); err != nil { + return nil, fmt.Errorf("failed parsing --set data, %w", err) + } + } + + // User specified a value via --set-string + for _, value := range opts.StringValues { + if err := strvals.ParseIntoString(value, base); err != nil { + return nil, fmt.Errorf("failed parsing --set-string data %w", err) + } + } + + // User specified a value via --set-file + for _, value := range opts.FileValues { + reader := func(rs []rune) (interface{}, error) { + bytes, err := readFile(string(rs)) + if err != nil { + return nil, err + } + return string(bytes), err + } + if err := strvals.ParseIntoFile(value, base, reader); err != nil { + return nil, fmt.Errorf("failed parsing --set-file data: %w", err) + } + } + + return base, nil +} + +func mergeMaps(a, b map[string]interface{}) map[string]interface{} { + out := make(map[string]interface{}, len(a)) + for k, v := range a { + out[k] = v + } + for k, v := range b { + if v, ok := v.(map[string]interface{}); ok { + if bv, ok := out[k]; ok { + if bv, ok := bv.(map[string]interface{}); ok { + out[k] = mergeMaps(bv, v) + continue + } + } + } + out[k] = v + } + return out +} + +// readFile load a file from stdin, the local directory, or a remote file with a url. +func readFile(filePath string) ([]byte, error) { + if strings.TrimSpace(filePath) == "-" { + return io.ReadAll(os.Stdin) + } + u, _ := url.Parse(filePath) + + // FIXME: maybe someone handle other protocols like ftp. + if u.Scheme == "http" || u.Scheme == "https" { + g, err := getter.NewHTTPGetter() + if err != nil { + return nil, err + } + data, err := g.Get(filePath, getter.WithURL(filePath)) + if err != nil { + return nil, err + } + return data.Bytes(), err + } else { + return os.ReadFile(filePath) + } +} diff --git a/pkg/iac/scanners/helm/scanner.go b/pkg/iac/scanners/helm/scanner.go new file mode 100644 index 000000000000..e2b666082c97 --- /dev/null +++ b/pkg/iac/scanners/helm/scanner.go @@ -0,0 +1,239 @@ +package helm + +import ( + "context" + "fmt" + "io" + "io/fs" + "path/filepath" + "strings" + "sync" + + "github.com/liamg/memoryfs" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners" + "github.com/aquasecurity/trivy/pkg/iac/scanners/helm/parser" + kparser "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var _ scanners.FSScanner = (*Scanner)(nil) +var _ options.ConfigurableScanner = (*Scanner)(nil) + +type Scanner struct { + policyDirs []string + dataDirs []string + debug debug.Logger + options []options.ScannerOption + parserOptions []options.ParserOption + policyReaders []io.Reader + loadEmbeddedLibraries bool + loadEmbeddedPolicies bool + policyFS fs.FS + skipRequired bool + frameworks []framework.Framework + spec string + regoScanner *rego.Scanner + mu sync.Mutex +} + +func (s *Scanner) SetSpec(spec string) { + s.spec = spec +} + +func (s *Scanner) SetRegoOnly(bool) { +} + +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { + s.frameworks = frameworks +} + +// New creates a new Scanner +func New(opts ...options.ScannerOption) *Scanner { + s := &Scanner{ + options: opts, + } + + for _, option := range opts { + option(s) + } + return s +} + +func (s *Scanner) AddParserOptions(opts ...options.ParserOption) { + s.parserOptions = append(s.parserOptions, opts...) +} + +func (s *Scanner) SetUseEmbeddedPolicies(b bool) { + s.loadEmbeddedPolicies = b +} + +func (s *Scanner) SetUseEmbeddedLibraries(b bool) { + s.loadEmbeddedLibraries = b +} + +func (s *Scanner) Name() string { + return "Helm" +} + +func (s *Scanner) SetPolicyReaders(readers []io.Reader) { + s.policyReaders = readers +} + +func (s *Scanner) SetSkipRequiredCheck(skip bool) { + s.skipRequired = skip +} + +func (s *Scanner) SetDebugWriter(writer io.Writer) { + s.debug = debug.New(writer, "helm", "scanner") +} + +func (s *Scanner) SetTraceWriter(_ io.Writer) { + // handled by rego later - nothing to do for now... +} + +func (s *Scanner) SetPerResultTracingEnabled(_ bool) { + // handled by rego later - nothing to do for now... +} + +func (s *Scanner) SetPolicyDirs(dirs ...string) { + s.policyDirs = dirs +} + +func (s *Scanner) SetDataDirs(dirs ...string) { + s.dataDirs = dirs +} + +func (s *Scanner) SetPolicyNamespaces(namespaces ...string) { + // handled by rego later - nothing to do for now... +} + +func (s *Scanner) SetPolicyFilesystem(policyFS fs.FS) { + s.policyFS = policyFS +} + +func (s *Scanner) SetDataFilesystem(_ fs.FS) {} +func (s *Scanner) SetRegoErrorLimit(_ int) {} + +func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, path string) (scan.Results, error) { + + if err := s.initRegoScanner(target); err != nil { + return nil, fmt.Errorf("failed to init rego scanner: %w", err) + } + + var results []scan.Result + if err := fs.WalkDir(target, path, func(path string, d fs.DirEntry, err error) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + if detection.IsArchive(path) { + if scanResults, err := s.getScanResults(path, ctx, target); err != nil { + return err + } else { + results = append(results, scanResults...) + } + } + + if strings.HasSuffix(path, "Chart.yaml") { + if scanResults, err := s.getScanResults(filepath.Dir(path), ctx, target); err != nil { + return err + } else { + results = append(results, scanResults...) + } + return fs.SkipDir + } + + return nil + }); err != nil { + return nil, err + } + + return results, nil + +} + +func (s *Scanner) getScanResults(path string, ctx context.Context, target fs.FS) (results []scan.Result, err error) { + helmParser, err := parser.New(path, s.parserOptions...) + if err != nil { + return nil, err + } + + if err := helmParser.ParseFS(ctx, target, path); err != nil { + return nil, err + } + + chartFiles, err := helmParser.RenderedChartFiles() + if err != nil { // not valid helm, maybe some other yaml etc., abort + s.debug.Log("Failed to render Chart files: %s", err) + return nil, nil + } + + for _, file := range chartFiles { + file := file + s.debug.Log("Processing rendered chart file: %s", file.TemplateFilePath) + + manifests, err := kparser.New().Parse(strings.NewReader(file.ManifestContent), file.TemplateFilePath) + if err != nil { + return nil, fmt.Errorf("unmarshal yaml: %w", err) + } + for _, manifest := range manifests { + fileResults, err := s.regoScanner.ScanInput(ctx, rego.Input{ + Path: file.TemplateFilePath, + Contents: manifest, + FS: target, + }) + if err != nil { + return nil, fmt.Errorf("scanning error: %w", err) + } + + if len(fileResults) > 0 { + renderedFS := memoryfs.New() + if err := renderedFS.MkdirAll(filepath.Dir(file.TemplateFilePath), fs.ModePerm); err != nil { + return nil, err + } + if err := renderedFS.WriteLazyFile(file.TemplateFilePath, func() (io.Reader, error) { + return strings.NewReader(file.ManifestContent), nil + }, fs.ModePerm); err != nil { + return nil, err + } + fileResults.SetSourceAndFilesystem(helmParser.ChartSource, renderedFS, detection.IsArchive(helmParser.ChartSource)) + } + + results = append(results, fileResults...) + } + + } + return results, nil +} + +func (s *Scanner) initRegoScanner(srcFS fs.FS) error { + s.mu.Lock() + defer s.mu.Unlock() + if s.regoScanner != nil { + return nil + } + regoScanner := rego.NewScanner(types.SourceKubernetes, s.options...) + regoScanner.SetParentDebugLogger(s.debug) + if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + return err + } + s.regoScanner = regoScanner + return nil +} diff --git a/pkg/iac/scanners/helm/test/mysql/.helmignore b/pkg/iac/scanners/helm/test/mysql/.helmignore new file mode 100644 index 000000000000..f0c131944441 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/pkg/iac/scanners/helm/test/mysql/Chart.lock b/pkg/iac/scanners/helm/test/mysql/Chart.lock new file mode 100644 index 000000000000..2a6356005c25 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.11.1 +digest: sha256:a000bcd4d4cdd813c67d633b5523b4a4cd478fb95f1cae665d9b0ba5c45b40e2 +generated: "2022-02-16T22:19:57.971058445Z" diff --git a/pkg/iac/scanners/helm/test/mysql/Chart.yaml b/pkg/iac/scanners/helm/test/mysql/Chart.yaml new file mode 100644 index 000000000000..7d5f5c6ce834 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/Chart.yaml @@ -0,0 +1,28 @@ +annotations: + category: Database +apiVersion: v2 +appVersion: 8.0.28 +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + tags: + - bitnami-common + version: 1.x.x +description: MySQL is a fast, reliable, scalable, and easy to use open source relational + database system. Designed to handle mission-critical, heavy-load production applications. +home: https://github.com/bitnami/charts/tree/master/bitnami/mysql +icon: https://bitnami.com/assets/stacks/mysql/img/mysql-stack-220x234.png +keywords: +- mysql +- database +- sql +- cluster +- high availability +maintainers: +- email: containers@bitnami.com + name: Bitnami +name: mysql +sources: +- https://github.com/bitnami/bitnami-docker-mysql +- https://mysql.com +version: 8.8.26 diff --git a/pkg/iac/scanners/helm/test/mysql/README.md b/pkg/iac/scanners/helm/test/mysql/README.md new file mode 100644 index 000000000000..b03fa495893f --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/README.md @@ -0,0 +1,491 @@ + + +# MySQL packaged by Bitnami + +MySQL is a fast, reliable, scalable, and easy to use open source relational database system. Designed to handle mission-critical, heavy-load production applications. + +[Overview of MySQL](http://www.mysql.com) + +Trademarks: This software listing is packaged by Bitnami. The respective trademarks mentioned in the offering are owned by the respective companies, and use of them does not imply any affiliation or endorsement. + +## TL;DR + +```bash +$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm install my-release bitnami/mysql +``` + +## Introduction + +This chart bootstraps a [MySQL](https://github.com/bitnami/bitnami-docker-mysql) replication cluster deployment on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This Helm chart has been tested on top of [Bitnami Kubernetes Production Runtime](https://kubeprod.io/) (BKPR). Deploy BKPR to get automated TLS certificates, logging and monitoring for your applications. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.2.0+ +- PV provisioner support in the underlying infrastructure + +## Installing the Chart + +To install the chart with the release name `my-release`: + +```bash +$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm install my-release bitnami/mysql +``` + +These commands deploy MySQL on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```bash +$ helm delete my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +### Global parameters + +| Name | Description | Value | +| ------------------------- | ----------------------------------------------- | ----- | +| `global.imageRegistry` | Global Docker image registry | `""` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` | +| `global.storageClass` | Global StorageClass for Persistent Volume(s) | `""` | + + +### Common parameters + +| Name | Description | Value | +| ------------------------ | --------------------------------------------------------------------------------------------------------- | --------------- | +| `nameOverride` | String to partially override common.names.fullname template (will maintain the release name) | `""` | +| `fullnameOverride` | String to fully override common.names.fullname template | `""` | +| `clusterDomain` | Cluster domain | `cluster.local` | +| `commonAnnotations` | Common annotations to add to all MySQL resources (sub-charts are not considered). Evaluated as a template | `{}` | +| `commonLabels` | Common labels to add to all MySQL resources (sub-charts are not considered). Evaluated as a template | `{}` | +| `extraDeploy` | Array with extra yaml to deploy with the chart. Evaluated as a template | `[]` | +| `schedulerName` | Use an alternate scheduler, e.g. "stork". | `""` | +| `diagnosticMode.enabled` | Enable diagnostic mode (all probes will be disabled and the command will be overridden) | `false` | +| `diagnosticMode.command` | Command to override all containers in the deployment | `["sleep"]` | +| `diagnosticMode.args` | Args to override all containers in the deployment | `["infinity"]` | + + +### MySQL common parameters + +| Name | Description | Value | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `image.registry` | MySQL image registry | `docker.io` | +| `image.repository` | MySQL image repository | `bitnami/mysql` | +| `image.tag` | MySQL image tag (immutable tags are recommended) | `8.0.28-debian-10-r0` | +| `image.pullPolicy` | MySQL image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `image.debug` | Specify if debug logs should be enabled | `false` | +| `architecture` | MySQL architecture (`standalone` or `replication`) | `standalone` | +| `auth.rootPassword` | Password for the `root` user. Ignored if existing secret is provided | `""` | +| `auth.database` | Name for a custom database to create | `my_database` | +| `auth.username` | Name for a custom user to create | `""` | +| `auth.password` | Password for the new user. Ignored if existing secret is provided | `""` | +| `auth.replicationUser` | MySQL replication user | `replicator` | +| `auth.replicationPassword` | MySQL replication user password. Ignored if existing secret is provided | `""` | +| `auth.existingSecret` | Use existing secret for password details. The secret has to contain the keys `mysql-root-password`, `mysql-replication-password` and `mysql-password` | `""` | +| `auth.forcePassword` | Force users to specify required passwords | `false` | +| `auth.usePasswordFiles` | Mount credentials as files instead of using an environment variable | `false` | +| `auth.customPasswordFiles` | Use custom password files when `auth.usePasswordFiles` is set to `true`. Define path for keys `root` and `user`, also define `replicator` if `architecture` is set to `replication` | `{}` | +| `initdbScripts` | Dictionary of initdb scripts | `{}` | +| `initdbScriptsConfigMap` | ConfigMap with the initdb scripts (Note: Overrides `initdbScripts`) | `""` | + + +### MySQL Primary parameters + +| Name | Description | Value | +| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------- | +| `primary.command` | Override default container command on MySQL Primary container(s) (useful when using custom images) | `[]` | +| `primary.args` | Override default container args on MySQL Primary container(s) (useful when using custom images) | `[]` | +| `primary.hostAliases` | Deployment pod host aliases | `[]` | +| `primary.configuration` | Configure MySQL Primary with a custom my.cnf file | `""` | +| `primary.existingConfigmap` | Name of existing ConfigMap with MySQL Primary configuration. | `""` | +| `primary.updateStrategy` | Update strategy type for the MySQL primary statefulset | `RollingUpdate` | +| `primary.rollingUpdatePartition` | Partition update strategy for MySQL Primary statefulset | `""` | +| `primary.podAnnotations` | Additional pod annotations for MySQL primary pods | `{}` | +| `primary.podAffinityPreset` | MySQL primary pod affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `primary.podAntiAffinityPreset` | MySQL primary pod anti-affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `primary.nodeAffinityPreset.type` | MySQL primary node affinity preset type. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `primary.nodeAffinityPreset.key` | MySQL primary node label key to match Ignored if `primary.affinity` is set. | `""` | +| `primary.nodeAffinityPreset.values` | MySQL primary node label values to match. Ignored if `primary.affinity` is set. | `[]` | +| `primary.affinity` | Affinity for MySQL primary pods assignment | `{}` | +| `primary.nodeSelector` | Node labels for MySQL primary pods assignment | `{}` | +| `primary.tolerations` | Tolerations for MySQL primary pods assignment | `[]` | +| `primary.podSecurityContext.enabled` | Enable security context for MySQL primary pods | `true` | +| `primary.podSecurityContext.fsGroup` | Group ID for the mounted volumes' filesystem | `1001` | +| `primary.containerSecurityContext.enabled` | MySQL primary container securityContext | `true` | +| `primary.containerSecurityContext.runAsUser` | User ID for the MySQL primary container | `1001` | +| `primary.resources.limits` | The resources limits for MySQL primary containers | `{}` | +| `primary.resources.requests` | The requested resources for MySQL primary containers | `{}` | +| `primary.livenessProbe.enabled` | Enable livenessProbe | `true` | +| `primary.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `5` | +| `primary.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `10` | +| `primary.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `1` | +| `primary.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `3` | +| `primary.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `primary.readinessProbe.enabled` | Enable readinessProbe | `true` | +| `primary.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `5` | +| `primary.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `10` | +| `primary.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` | +| `primary.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `3` | +| `primary.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `primary.startupProbe.enabled` | Enable startupProbe | `true` | +| `primary.startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `15` | +| `primary.startupProbe.periodSeconds` | Period seconds for startupProbe | `10` | +| `primary.startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `1` | +| `primary.startupProbe.failureThreshold` | Failure threshold for startupProbe | `10` | +| `primary.startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `primary.customLivenessProbe` | Override default liveness probe for MySQL primary containers | `{}` | +| `primary.customReadinessProbe` | Override default readiness probe for MySQL primary containers | `{}` | +| `primary.customStartupProbe` | Override default startup probe for MySQL primary containers | `{}` | +| `primary.extraFlags` | MySQL primary additional command line flags | `""` | +| `primary.extraEnvVars` | Extra environment variables to be set on MySQL primary containers | `[]` | +| `primary.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for MySQL primary containers | `""` | +| `primary.extraEnvVarsSecret` | Name of existing Secret containing extra env vars for MySQL primary containers | `""` | +| `primary.persistence.enabled` | Enable persistence on MySQL primary replicas using a `PersistentVolumeClaim`. If false, use emptyDir | `true` | +| `primary.persistence.existingClaim` | Name of an existing `PersistentVolumeClaim` for MySQL primary replicas | `""` | +| `primary.persistence.storageClass` | MySQL primary persistent volume storage Class | `""` | +| `primary.persistence.annotations` | MySQL primary persistent volume claim annotations | `{}` | +| `primary.persistence.accessModes` | MySQL primary persistent volume access Modes | `["ReadWriteOnce"]` | +| `primary.persistence.size` | MySQL primary persistent volume size | `8Gi` | +| `primary.persistence.selector` | Selector to match an existing Persistent Volume | `{}` | +| `primary.extraVolumes` | Optionally specify extra list of additional volumes to the MySQL Primary pod(s) | `[]` | +| `primary.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the MySQL Primary container(s) | `[]` | +| `primary.initContainers` | Add additional init containers for the MySQL Primary pod(s) | `[]` | +| `primary.sidecars` | Add additional sidecar containers for the MySQL Primary pod(s) | `[]` | +| `primary.service.type` | MySQL Primary K8s service type | `ClusterIP` | +| `primary.service.port` | MySQL Primary K8s service port | `3306` | +| `primary.service.nodePort` | MySQL Primary K8s service node port | `""` | +| `primary.service.clusterIP` | MySQL Primary K8s service clusterIP IP | `""` | +| `primary.service.loadBalancerIP` | MySQL Primary loadBalancerIP if service type is `LoadBalancer` | `""` | +| `primary.service.externalTrafficPolicy` | Enable client source IP preservation | `Cluster` | +| `primary.service.loadBalancerSourceRanges` | Addresses that are allowed when MySQL Primary service is LoadBalancer | `[]` | +| `primary.service.annotations` | Provide any additional annotations which may be required | `{}` | +| `primary.pdb.enabled` | Enable/disable a Pod Disruption Budget creation for MySQL primary pods | `false` | +| `primary.pdb.minAvailable` | Minimum number/percentage of MySQL primary pods that should remain scheduled | `1` | +| `primary.pdb.maxUnavailable` | Maximum number/percentage of MySQL primary pods that may be made unavailable | `""` | +| `primary.podLabels` | MySQL Primary pod label. If labels are same as commonLabels , this will take precedence | `{}` | + + +### MySQL Secondary parameters + +| Name | Description | Value | +| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `secondary.replicaCount` | Number of MySQL secondary replicas | `1` | +| `secondary.hostAliases` | Deployment pod host aliases | `[]` | +| `secondary.command` | Override default container command on MySQL Secondary container(s) (useful when using custom images) | `[]` | +| `secondary.args` | Override default container args on MySQL Secondary container(s) (useful when using custom images) | `[]` | +| `secondary.configuration` | Configure MySQL Secondary with a custom my.cnf file | `""` | +| `secondary.existingConfigmap` | Name of existing ConfigMap with MySQL Secondary configuration. | `""` | +| `secondary.updateStrategy` | Update strategy type for the MySQL secondary statefulset | `RollingUpdate` | +| `secondary.rollingUpdatePartition` | Partition update strategy for MySQL Secondary statefulset | `""` | +| `secondary.podAnnotations` | Additional pod annotations for MySQL secondary pods | `{}` | +| `secondary.podAffinityPreset` | MySQL secondary pod affinity preset. Ignored if `secondary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `secondary.podAntiAffinityPreset` | MySQL secondary pod anti-affinity preset. Ignored if `secondary.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `secondary.nodeAffinityPreset.type` | MySQL secondary node affinity preset type. Ignored if `secondary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `secondary.nodeAffinityPreset.key` | MySQL secondary node label key to match Ignored if `secondary.affinity` is set. | `""` | +| `secondary.nodeAffinityPreset.values` | MySQL secondary node label values to match. Ignored if `secondary.affinity` is set. | `[]` | +| `secondary.affinity` | Affinity for MySQL secondary pods assignment | `{}` | +| `secondary.nodeSelector` | Node labels for MySQL secondary pods assignment | `{}` | +| `secondary.tolerations` | Tolerations for MySQL secondary pods assignment | `[]` | +| `secondary.podSecurityContext.enabled` | Enable security context for MySQL secondary pods | `true` | +| `secondary.podSecurityContext.fsGroup` | Group ID for the mounted volumes' filesystem | `1001` | +| `secondary.containerSecurityContext.enabled` | MySQL secondary container securityContext | `true` | +| `secondary.containerSecurityContext.runAsUser` | User ID for the MySQL secondary container | `1001` | +| `secondary.resources.limits` | The resources limits for MySQL secondary containers | `{}` | +| `secondary.resources.requests` | The requested resources for MySQL secondary containers | `{}` | +| `secondary.livenessProbe.enabled` | Enable livenessProbe | `true` | +| `secondary.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `5` | +| `secondary.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `10` | +| `secondary.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `1` | +| `secondary.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `3` | +| `secondary.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `secondary.readinessProbe.enabled` | Enable readinessProbe | `true` | +| `secondary.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `5` | +| `secondary.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `10` | +| `secondary.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` | +| `secondary.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `3` | +| `secondary.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `secondary.startupProbe.enabled` | Enable startupProbe | `true` | +| `secondary.startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `15` | +| `secondary.startupProbe.periodSeconds` | Period seconds for startupProbe | `10` | +| `secondary.startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `1` | +| `secondary.startupProbe.failureThreshold` | Failure threshold for startupProbe | `15` | +| `secondary.startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `secondary.customLivenessProbe` | Override default liveness probe for MySQL secondary containers | `{}` | +| `secondary.customReadinessProbe` | Override default readiness probe for MySQL secondary containers | `{}` | +| `secondary.customStartupProbe` | Override default startup probe for MySQL secondary containers | `{}` | +| `secondary.extraFlags` | MySQL secondary additional command line flags | `""` | +| `secondary.extraEnvVars` | An array to add extra environment variables on MySQL secondary containers | `[]` | +| `secondary.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for MySQL secondary containers | `""` | +| `secondary.extraEnvVarsSecret` | Name of existing Secret containing extra env vars for MySQL secondary containers | `""` | +| `secondary.persistence.enabled` | Enable persistence on MySQL secondary replicas using a `PersistentVolumeClaim` | `true` | +| `secondary.persistence.storageClass` | MySQL secondary persistent volume storage Class | `""` | +| `secondary.persistence.annotations` | MySQL secondary persistent volume claim annotations | `{}` | +| `secondary.persistence.accessModes` | MySQL secondary persistent volume access Modes | `["ReadWriteOnce"]` | +| `secondary.persistence.size` | MySQL secondary persistent volume size | `8Gi` | +| `secondary.persistence.selector` | Selector to match an existing Persistent Volume | `{}` | +| `secondary.extraVolumes` | Optionally specify extra list of additional volumes to the MySQL secondary pod(s) | `[]` | +| `secondary.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the MySQL secondary container(s) | `[]` | +| `secondary.initContainers` | Add additional init containers for the MySQL secondary pod(s) | `[]` | +| `secondary.sidecars` | Add additional sidecar containers for the MySQL secondary pod(s) | `[]` | +| `secondary.service.type` | MySQL secondary Kubernetes service type | `ClusterIP` | +| `secondary.service.port` | MySQL secondary Kubernetes service port | `3306` | +| `secondary.service.nodePort` | MySQL secondary Kubernetes service node port | `""` | +| `secondary.service.clusterIP` | MySQL secondary Kubernetes service clusterIP IP | `""` | +| `secondary.service.loadBalancerIP` | MySQL secondary loadBalancerIP if service type is `LoadBalancer` | `""` | +| `secondary.service.externalTrafficPolicy` | Enable client source IP preservation | `Cluster` | +| `secondary.service.loadBalancerSourceRanges` | Addresses that are allowed when MySQL secondary service is LoadBalancer | `[]` | +| `secondary.service.annotations` | Provide any additional annotations which may be required | `{}` | +| `secondary.pdb.enabled` | Enable/disable a Pod Disruption Budget creation for MySQL secondary pods | `false` | +| `secondary.pdb.minAvailable` | Minimum number/percentage of MySQL secondary pods that should remain scheduled | `1` | +| `secondary.pdb.maxUnavailable` | Maximum number/percentage of MySQL secondary pods that may be made unavailable | `""` | +| `secondary.podLabels` | Additional pod labels for MySQL secondary pods | `{}` | + + +### RBAC parameters + +| Name | Description | Value | +| ---------------------------- | ------------------------------------------------------ | ------- | +| `serviceAccount.create` | Enable the creation of a ServiceAccount for MySQL pods | `true` | +| `serviceAccount.name` | Name of the created ServiceAccount | `""` | +| `serviceAccount.annotations` | Annotations for MySQL Service Account | `{}` | +| `rbac.create` | Whether to create & use RBAC resources or not | `false` | + + +### Network Policy + +| Name | Description | Value | +| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | ------- | +| `networkPolicy.enabled` | Enable creation of NetworkPolicy resources | `false` | +| `networkPolicy.allowExternal` | The Policy model to apply. | `true` | +| `networkPolicy.explicitNamespacesSelector` | A Kubernetes LabelSelector to explicitly select namespaces from which ingress traffic could be allowed to MySQL | `{}` | + + +### Volume Permissions parameters + +| Name | Description | Value | +| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `volumePermissions.enabled` | Enable init container that changes the owner and group of the persistent volume(s) mountpoint to `runAsUser:fsGroup` | `false` | +| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | +| `volumePermissions.image.repository` | Init container volume-permissions image repository | `bitnami/bitnami-shell` | +| `volumePermissions.image.tag` | Init container volume-permissions image tag (immutable tags are recommended) | `10-debian-10-r312` | +| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `IfNotPresent` | +| `volumePermissions.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `volumePermissions.resources` | Init container volume-permissions resources | `{}` | + + +### Metrics parameters + +| Name | Description | Value | +| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `metrics.enabled` | Start a side-car prometheus exporter | `false` | +| `metrics.image.registry` | Exporter image registry | `docker.io` | +| `metrics.image.repository` | Exporter image repository | `bitnami/mysqld-exporter` | +| `metrics.image.tag` | Exporter image tag (immutable tags are recommended) | `0.13.0-debian-10-r216` | +| `metrics.image.pullPolicy` | Exporter image pull policy | `IfNotPresent` | +| `metrics.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `metrics.service.type` | Kubernetes service type for MySQL Prometheus Exporter | `ClusterIP` | +| `metrics.service.port` | MySQL Prometheus Exporter service port | `9104` | +| `metrics.service.annotations` | Prometheus exporter service annotations | `{}` | +| `metrics.extraArgs.primary` | Extra args to be passed to mysqld_exporter on Primary pods | `[]` | +| `metrics.extraArgs.secondary` | Extra args to be passed to mysqld_exporter on Secondary pods | `[]` | +| `metrics.resources.limits` | The resources limits for MySQL prometheus exporter containers | `{}` | +| `metrics.resources.requests` | The requested resources for MySQL prometheus exporter containers | `{}` | +| `metrics.livenessProbe.enabled` | Enable livenessProbe | `true` | +| `metrics.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `120` | +| `metrics.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `10` | +| `metrics.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `1` | +| `metrics.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `3` | +| `metrics.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `metrics.readinessProbe.enabled` | Enable readinessProbe | `true` | +| `metrics.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `30` | +| `metrics.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `10` | +| `metrics.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` | +| `metrics.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `3` | +| `metrics.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `metrics.serviceMonitor.enabled` | Create ServiceMonitor Resource for scraping metrics using PrometheusOperator | `false` | +| `metrics.serviceMonitor.namespace` | Specify the namespace in which the serviceMonitor resource will be created | `""` | +| `metrics.serviceMonitor.interval` | Specify the interval at which metrics should be scraped | `30s` | +| `metrics.serviceMonitor.scrapeTimeout` | Specify the timeout after which the scrape is ended | `""` | +| `metrics.serviceMonitor.relabellings` | Specify Metric Relabellings to add to the scrape endpoint | `[]` | +| `metrics.serviceMonitor.honorLabels` | Specify honorLabels parameter to add the scrape endpoint | `false` | +| `metrics.serviceMonitor.additionalLabels` | Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with | `{}` | + + +The above parameters map to the env variables defined in [bitnami/mysql](https://github.com/bitnami/bitnami-docker-mysql). For more information please refer to the [bitnami/mysql](https://github.com/bitnami/bitnami-docker-mysql) image documentation. + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +$ helm install my-release \ + --set auth.rootPassword=secretpassword,auth.database=app_database \ + bitnami/mysql +``` + +The above command sets the MySQL `root` account password to `secretpassword`. Additionally it creates a database named `app_database`. + +> NOTE: Once this chart is deployed, it is not possible to change the application's access credentials, such as usernames or passwords, using Helm. To change these application credentials after deployment, delete any persistent volumes (PVs) used by the chart and re-deploy it, or use the application's built-in administrative tools if available. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```bash +$ helm install my-release -f values.yaml bitnami/mysql +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +## Configuration and installation details + +### [Rolling VS Immutable tags](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/) + +It is strongly recommended to use immutable tags in a production environment. This ensures your deployment does not change automatically if the same tag is updated with a different image. + +Bitnami will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist. + +### Use a different MySQL version + +To modify the application version used in this chart, specify a different version of the image using the `image.tag` parameter and/or a different repository using the `image.repository` parameter. Refer to the [chart documentation for more information on these parameters and how to use them with images from a private registry](https://docs.bitnami.com/kubernetes/infrastructure/mysql/configuration/change-image-version/). + +### Customize a new MySQL instance + +The [Bitnami MySQL](https://github.com/bitnami/bitnami-docker-mysql) image allows you to use your custom scripts to initialize a fresh instance. Custom scripts may be specified using the `initdbScripts` parameter. Alternatively, an external ConfigMap may be created with all the initialization scripts and the ConfigMap passed to the chart via the `initdbScriptsConfigMap` parameter. Note that this will override the `initdbScripts` parameter. + +The allowed extensions are `.sh`, `.sql` and `.sql.gz`. + +These scripts are treated differently depending on their extension. While `.sh` scripts are executed on all the nodes, `.sql` and `.sql.gz` scripts are only executed on the primary nodes. This is because `.sh` scripts support conditional tests to identify the type of node they are running on, while such tests are not supported in `.sql` or `sql.gz` files. + +Refer to the [chart documentation for more information and a usage example](http://docs.bitnami.com/kubernetes/infrastructure/mysql/configuration/customize-new-instance/). + +### Sidecars and Init Containers + +If you have a need for additional containers to run within the same pod as MySQL, you can do so via the `sidecars` config parameter. Simply define your container according to the Kubernetes container spec. + +```yaml +sidecars: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +``` + +Similarly, you can add extra init containers using the `initContainers` parameter. + +```yaml +initContainers: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +``` + +## Persistence + +The [Bitnami MySQL](https://github.com/bitnami/bitnami-docker-mysql) image stores the MySQL data and configurations at the `/bitnami/mysql` path of the container. + +The chart mounts a [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) volume at this location. The volume is created using dynamic volume provisioning by default. An existing PersistentVolumeClaim can also be defined for this purpose. + +If you encounter errors when working with persistent volumes, refer to our [troubleshooting guide for persistent volumes](https://docs.bitnami.com/kubernetes/faq/troubleshooting/troubleshooting-persistence-volumes/). + +## Network Policy + +To enable network policy for MySQL, install [a networking plugin that implements the Kubernetes NetworkPolicy spec](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy#before-you-begin), and set `networkPolicy.enabled` to `true`. + +For Kubernetes v1.5 & v1.6, you must also turn on NetworkPolicy by setting the DefaultDeny namespace annotation. Note: this will enforce policy for _all_ pods in the namespace: + +```console +$ kubectl annotate namespace default "net.beta.kubernetes.io/network-policy={\"ingress\":{\"isolation\":\"DefaultDeny\"}}" +``` + +With NetworkPolicy enabled, traffic will be limited to just port 3306. + +For more precise policy, set `networkPolicy.allowExternal=false`. This will only allow pods with the generated client label to connect to MySQL. +This label will be displayed in the output of a successful install. + +## Pod affinity + +This chart allows you to set your custom affinity using the `XXX.affinity` parameter(s). Find more information about Pod affinity in the [Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). + +As an alternative, you can use the preset configurations for pod affinity, pod anti-affinity, and node affinity available at the [bitnami/common](https://github.com/bitnami/charts/tree/master/bitnami/common#affinities) chart. To do so, set the `XXX.podAffinityPreset`, `XXX.podAntiAffinityPreset`, or `XXX.nodeAffinityPreset` parameters. + +## Troubleshooting + +Find more information about how to deal with common errors related to Bitnami's Helm charts in [this troubleshooting guide](https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues). + +## Upgrading + +It's necessary to set the `auth.rootPassword` parameter when upgrading for readiness/liveness probes to work properly. When you install this chart for the first time, some notes will be displayed providing the credentials you must use under the 'Administrator credentials' section. Please note down the password and run the command below to upgrade your chart: + +```bash +$ helm upgrade my-release bitnami/mysql --set auth.rootPassword=[ROOT_PASSWORD] +``` + +| Note: you need to substitute the placeholder _[ROOT_PASSWORD]_ with the value obtained in the installation notes. + +### To 8.0.0 + +- Several parameters were renamed or disappeared in favor of new ones on this major version: + - The terms *master* and *slave* have been replaced by the terms *primary* and *secondary*. Therefore, parameters prefixed with `master` or `slave` are now prefixed with `primary` or `secondary`, respectively. + - Credentials parameters are reorganized under the `auth` parameter. + - `replication.enabled` parameter is deprecated in favor of `architecture` parameter that accepts two values: `standalone` and `replication`. +- Chart labels were adapted to follow the [Helm charts standard labels](https://helm.sh/docs/chart_best_practices/labels/#standard-labels). +- This version also introduces `bitnami/common`, a [library chart](https://helm.sh/docs/topics/library_charts/#helm) as a dependency. More documentation about this new utility could be found [here](https://github.com/bitnami/charts/tree/master/bitnami/common#bitnami-common-library-chart). Please, make sure that you have updated the chart dependencies before executing any upgrade. + +Consequences: + +- Backwards compatibility is not guaranteed. To upgrade to `8.0.0`, install a new release of the MySQL chart, and migrate the data from your previous release. You have 2 alternatives to do so: + - Create a backup of the database, and restore it on the new release using tools such as [mysqldump](https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html). + - Reuse the PVC used to hold the master data on your previous release. To do so, use the `primary.persistence.existingClaim` parameter. The following example assumes that the release name is `mysql`: + +```bash +$ helm install mysql bitnami/mysql --set auth.rootPassword=[ROOT_PASSWORD] --set primary.persistence.existingClaim=[EXISTING_PVC] +``` + +| Note: you need to substitute the placeholder _[EXISTING_PVC]_ with the name of the PVC used on your previous release, and _[ROOT_PASSWORD]_ with the root password used in your previous release. + +### To 7.0.0 + +[On November 13, 2020, Helm v2 support formally ended](https://github.com/helm/charts#status-of-the-project). This major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +[Learn more about this change and related upgrade considerations](https://docs.bitnami.com/kubernetes/infrastructure/mysql/administration/upgrade-helm3/). + +### To 3.0.0 + +Backwards compatibility is not guaranteed unless you modify the labels used on the chart's deployments. +Use the workaround below to upgrade from versions previous to 3.0.0. The following example assumes that the release name is mysql: + +```console +$ kubectl delete statefulset mysql-master --cascade=false +$ kubectl delete statefulset mysql-slave --cascade=false +``` + +## License + +Copyright © 2022 Bitnami + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/.helmignore b/pkg/iac/scanners/helm/test/mysql/charts/common/.helmignore new file mode 100644 index 000000000000..50af03172541 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/Chart.yaml b/pkg/iac/scanners/helm/test/mysql/charts/common/Chart.yaml new file mode 100644 index 000000000000..87226649a57c --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/Chart.yaml @@ -0,0 +1,23 @@ +annotations: + category: Infrastructure +apiVersion: v2 +appVersion: 1.11.1 +description: A Library Helm Chart for grouping common logic between bitnami charts. + This chart is not deployable by itself. +home: https://github.com/bitnami/charts/tree/master/bitnami/common +icon: https://bitnami.com/downloads/logos/bitnami-mark.png +keywords: +- common +- helper +- template +- function +- bitnami +maintainers: +- email: containers@bitnami.com + name: Bitnami +name: common +sources: +- https://github.com/bitnami/charts +- https://www.bitnami.com/ +type: library +version: 1.11.1 diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/README.md b/pkg/iac/scanners/helm/test/mysql/charts/common/README.md new file mode 100644 index 000000000000..da84c426d0db --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/README.md @@ -0,0 +1,345 @@ +# Bitnami Common Library Chart + +A [Helm Library Chart](https://helm.sh/docs/topics/library_charts/#helm) for grouping common logic between bitnami charts. + +## TL;DR + +```yaml +dependencies: + - name: common + version: 0.x.x + repository: https://charts.bitnami.com/bitnami +``` + +```bash +$ helm dependency update +``` + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.names.fullname" . }} +data: + myvalue: "Hello World" +``` + +## Introduction + +This chart provides a common template helpers which can be used to develop new charts using [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This Helm chart has been tested on top of [Bitnami Kubernetes Production Runtime](https://kubeprod.io/) (BKPR). Deploy BKPR to get automated TLS certificates, logging and monitoring for your applications. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.2.0+ + +## Parameters + +The following table lists the helpers available in the library which are scoped in different sections. + +### Affinities + +| Helper identifier | Description | Expected Input | +|-------------------------------|------------------------------------------------------|------------------------------------------------| +| `common.affinities.node.soft` | Return a soft nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.node.hard` | Return a hard nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.pod.soft` | Return a soft podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | +| `common.affinities.pod.hard` | Return a hard podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | + +### Capabilities + +| Helper identifier | Description | Expected Input | +|------------------------------------------------|------------------------------------------------------------------------------------------------|-------------------| +| `common.capabilities.kubeVersion` | Return the target Kubernetes version (using client default if .Values.kubeVersion is not set). | `.` Chart context | +| `common.capabilities.cronjob.apiVersion` | Return the appropriate apiVersion for cronjob. | `.` Chart context | +| `common.capabilities.deployment.apiVersion` | Return the appropriate apiVersion for deployment. | `.` Chart context | +| `common.capabilities.statefulset.apiVersion` | Return the appropriate apiVersion for statefulset. | `.` Chart context | +| `common.capabilities.ingress.apiVersion` | Return the appropriate apiVersion for ingress. | `.` Chart context | +| `common.capabilities.rbac.apiVersion` | Return the appropriate apiVersion for RBAC resources. | `.` Chart context | +| `common.capabilities.crd.apiVersion` | Return the appropriate apiVersion for CRDs. | `.` Chart context | +| `common.capabilities.policy.apiVersion` | Return the appropriate apiVersion for podsecuritypolicy. | `.` Chart context | +| `common.capabilities.networkPolicy.apiVersion` | Return the appropriate apiVersion for networkpolicy. | `.` Chart context | +| `common.capabilities.supportsHelmVersion` | Returns true if the used Helm version is 3.3+ | `.` Chart context | + +### Errors + +| Helper identifier | Description | Expected Input | +|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| +| `common.errors.upgrade.passwords.empty` | It will ensure required passwords are given when we are upgrading a chart. If `validationErrors` is not empty it will throw an error and will stop the upgrade action. | `dict "validationErrors" (list $validationError00 $validationError01) "context" $` | + +### Images + +| Helper identifier | Description | Expected Input | +|-----------------------------|------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| `common.images.image` | Return the proper and full image name | `dict "imageRoot" .Values.path.to.the.image "global" $`, see [ImageRoot](#imageroot) for the structure. | +| `common.images.pullSecrets` | Return the proper Docker Image Registry Secret Names (deprecated: use common.images.renderPullSecrets instead) | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global` | +| `common.images.renderPullSecrets` | Return the proper Docker Image Registry Secret Names (evaluates values as templates) | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "context" $` | + +### Ingress + +| Helper identifier | Description | Expected Input | +|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.ingress.backend` | Generate a proper Ingress backend entry depending on the API version | `dict "serviceName" "foo" "servicePort" "bar"`, see the [Ingress deprecation notice](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/) for the syntax differences | +| `common.ingress.supportsPathType` | Prints "true" if the pathType field is supported | `.` Chart context | +| `common.ingress.supportsIngressClassname` | Prints "true" if the ingressClassname field is supported | `.` Chart context | +| `common.ingress.certManagerRequest` | Prints "true" if required cert-manager annotations for TLS signed certificates are set in the Ingress annotations | `dict "annotations" .Values.path.to.the.ingress.annotations` | + +### Labels + +| Helper identifier | Description | Expected Input | +|-----------------------------|------------------------------------------------------|-------------------| +| `common.labels.standard` | Return Kubernetes standard labels | `.` Chart context | +| `common.labels.matchLabels` | Return the proper Docker Image Registry Secret Names | `.` Chart context | + +### Names + +| Helper identifier | Description | Expected Input | +|-------------------------|------------------------------------------------------------|-------------------| +| `common.names.name` | Expand the name of the chart or use `.Values.nameOverride` | `.` Chart context | +| `common.names.fullname` | Create a default fully qualified app name. | `.` Chart context | +| `common.names.chart` | Chart name plus version | `.` Chart context | + +### Secrets + +| Helper identifier | Description | Expected Input | +|---------------------------|--------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.secrets.name` | Generate the name of the secret. | `dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $` see [ExistingSecret](#existingsecret) for the structure. | +| `common.secrets.key` | Generate secret key. | `dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName"` see [ExistingSecret](#existingsecret) for the structure. | +| `common.passwords.manage` | Generate secret password or retrieve one if already created. | `dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $`, length, strong and chartNAme fields are optional. | +| `common.secrets.exists` | Returns whether a previous generated secret already exists. | `dict "secret" "secret-name" "context" $` | + +### Storage + +| Helper identifier | Description | Expected Input | +|-------------------------------|---------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| `common.storage.class` | Return the proper Storage Class | `dict "persistence" .Values.path.to.the.persistence "global" $`, see [Persistence](#persistence) for the structure. | + +### TplValues + +| Helper identifier | Description | Expected Input | +|---------------------------|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.tplvalues.render` | Renders a value that contains template | `dict "value" .Values.path.to.the.Value "context" $`, value is the value should rendered as template, context frequently is the chart context `$` or `.` | + +### Utils + +| Helper identifier | Description | Expected Input | +|--------------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------| +| `common.utils.fieldToEnvVar` | Build environment variable name given a field. | `dict "field" "my-password"` | +| `common.utils.secret.getvalue` | Print instructions to get a secret value. | `dict "secret" "secret-name" "field" "secret-value-field" "context" $` | +| `common.utils.getValueFromKey` | Gets a value from `.Values` object given its key path | `dict "key" "path.to.key" "context" $` | +| `common.utils.getKeyFromList` | Returns first `.Values` key with a defined value or first of the list if all non-defined | `dict "keys" (list "path.to.key1" "path.to.key2") "context" $` | + +### Validations + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.validations.values.single.empty` | Validate a value must not be empty. | `dict "valueKey" "path.to.value" "secret" "secret.name" "field" "my-password" "subchart" "subchart" "context" $` secret, field and subchart are optional. In case they are given, the helper will generate a how to get instruction. See [ValidateValue](#validatevalue) | +| `common.validations.values.multiple.empty` | Validate a multiple values must not be empty. It returns a shared error for all the values. | `dict "required" (list $validateValueConf00 $validateValueConf01) "context" $`. See [ValidateValue](#validatevalue) | +| `common.validations.values.mariadb.passwords` | This helper will ensure required password for MariaDB are not empty. It returns a shared error for all the values. | `dict "secret" "mariadb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mariadb chart and the helper. | +| `common.validations.values.postgresql.passwords` | This helper will ensure required password for PostgreSQL are not empty. It returns a shared error for all the values. | `dict "secret" "postgresql-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use postgresql chart and the helper. | +| `common.validations.values.redis.passwords` | This helper will ensure required password for Redis™ are not empty. It returns a shared error for all the values. | `dict "secret" "redis-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use redis chart and the helper. | +| `common.validations.values.cassandra.passwords` | This helper will ensure required password for Cassandra are not empty. It returns a shared error for all the values. | `dict "secret" "cassandra-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use cassandra chart and the helper. | +| `common.validations.values.mongodb.passwords` | This helper will ensure required password for MongoDB® are not empty. It returns a shared error for all the values. | `dict "secret" "mongodb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mongodb chart and the helper. | + +### Warnings + +| Helper identifier | Description | Expected Input | +|------------------------------|----------------------------------|------------------------------------------------------------| +| `common.warnings.rollingTag` | Warning about using rolling tag. | `ImageRoot` see [ImageRoot](#imageroot) for the structure. | + +## Special input schemas + +### ImageRoot + +```yaml +registry: + type: string + description: Docker registry where the image is located + example: docker.io + +repository: + type: string + description: Repository and image name + example: bitnami/nginx + +tag: + type: string + description: image tag + example: 1.16.1-debian-10-r63 + +pullPolicy: + type: string + description: Specify a imagePullPolicy. Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + +pullSecrets: + type: array + items: + type: string + description: Optionally specify an array of imagePullSecrets (evaluated as templates). + +debug: + type: boolean + description: Set to true if you would like to see extra information on logs + example: false + +## An instance would be: +# registry: docker.io +# repository: bitnami/nginx +# tag: 1.16.1-debian-10-r63 +# pullPolicy: IfNotPresent +# debug: false +``` + +### Persistence + +```yaml +enabled: + type: boolean + description: Whether enable persistence. + example: true + +storageClass: + type: string + description: Ghost data Persistent Volume Storage Class, If set to "-", storageClassName: "" which disables dynamic provisioning. + example: "-" + +accessMode: + type: string + description: Access mode for the Persistent Volume Storage. + example: ReadWriteOnce + +size: + type: string + description: Size the Persistent Volume Storage. + example: 8Gi + +path: + type: string + description: Path to be persisted. + example: /bitnami + +## An instance would be: +# enabled: true +# storageClass: "-" +# accessMode: ReadWriteOnce +# size: 8Gi +# path: /bitnami +``` + +### ExistingSecret + +```yaml +name: + type: string + description: Name of the existing secret. + example: mySecret +keyMapping: + description: Mapping between the expected key name and the name of the key in the existing secret. + type: object + +## An instance would be: +# name: mySecret +# keyMapping: +# password: myPasswordKey +``` + +#### Example of use + +When we store sensitive data for a deployment in a secret, some times we want to give to users the possibility of using theirs existing secrets. + +```yaml +# templates/secret.yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }} + labels: + app: {{ include "common.names.fullname" . }} +type: Opaque +data: + password: {{ .Values.password | b64enc | quote }} + +# templates/dpl.yaml +--- +... + env: + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "common.secrets.name" (dict "existingSecret" .Values.existingSecret "context" $) }} + key: {{ include "common.secrets.key" (dict "existingSecret" .Values.existingSecret "key" "password") }} +... + +# values.yaml +--- +name: mySecret +keyMapping: + password: myPasswordKey +``` + +### ValidateValue + +#### NOTES.txt + +```console +{{- $validateValueConf00 := (dict "valueKey" "path.to.value00" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value01" "secret" "secretName" "field" "password-01") -}} + +{{ include "common.validations.values.multiple.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} +``` + +If we force those values to be empty we will see some alerts + +```console +$ helm install test mychart --set path.to.value00="",path.to.value01="" + 'path.to.value00' must not be empty, please add '--set path.to.value00=$PASSWORD_00' to the command. To get the current value: + + export PASSWORD_00=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-00}" | base64 --decode) + + 'path.to.value01' must not be empty, please add '--set path.to.value01=$PASSWORD_01' to the command. To get the current value: + + export PASSWORD_01=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-01}" | base64 --decode) +``` + +## Upgrading + +### To 1.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +**What changes were introduced in this major version?** + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Use `type: library`. [Here](https://v3.helm.sh/docs/faq/#library-chart-support) you can find more information. +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Charts + +**Considerations when upgrading to this version** + +- If you want to upgrade to this version from a previous one installed with Helm v3, you shouldn't face any issues +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version doesn't support Helm v2 anymore +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3 + +**Useful links** + +- https://docs.bitnami.com/tutorials/resolve-helm2-helm3-post-migration-issues/ +- https://helm.sh/docs/topics/v2_v3_migration/ +- https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/ + +## License + +Copyright © 2022 Bitnami + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_affinities.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_affinities.tpl new file mode 100644 index 000000000000..189ea403d558 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_affinities.tpl @@ -0,0 +1,102 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return a soft nodeAffinity definition +{{ include "common.affinities.nodes.soft" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.soft" -}} +preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . | quote }} + {{- end }} + weight: 1 +{{- end -}} + +{{/* +Return a hard nodeAffinity definition +{{ include "common.affinities.nodes.hard" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.hard" -}} +requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . | quote }} + {{- end }} +{{- end -}} + +{{/* +Return a nodeAffinity definition +{{ include "common.affinities.nodes" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.nodes.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.nodes.hard" . -}} + {{- end -}} +{{- end -}} + +{{/* +Return a soft podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.soft" (dict "component" "FOO" "extraMatchLabels" .Values.extraMatchLabels "context" $) -}} +*/}} +{{- define "common.affinities.pods.soft" -}} +{{- $component := default "" .component -}} +{{- $extraMatchLabels := default (dict) .extraMatchLabels -}} +preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 10 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + {{- range $key, $value := $extraMatchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace | quote }} + topologyKey: kubernetes.io/hostname + weight: 1 +{{- end -}} + +{{/* +Return a hard podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.hard" (dict "component" "FOO" "extraMatchLabels" .Values.extraMatchLabels "context" $) -}} +*/}} +{{- define "common.affinities.pods.hard" -}} +{{- $component := default "" .component -}} +{{- $extraMatchLabels := default (dict) .extraMatchLabels -}} +requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 8 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + {{- range $key, $value := $extraMatchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace | quote }} + topologyKey: kubernetes.io/hostname +{{- end -}} + +{{/* +Return a podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.pods" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.pods.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.pods.hard" . -}} + {{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_capabilities.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_capabilities.tpl new file mode 100644 index 000000000000..b94212bbe77c --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_capabilities.tpl @@ -0,0 +1,128 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return the target Kubernetes version +*/}} +{{- define "common.capabilities.kubeVersion" -}} +{{- if .Values.global }} + {{- if .Values.global.kubeVersion }} + {{- .Values.global.kubeVersion -}} + {{- else }} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} + {{- end -}} +{{- else }} +{{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for poddisruptionbudget. +*/}} +{{- define "common.capabilities.policy.apiVersion" -}} +{{- if semverCompare "<1.21-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "policy/v1beta1" -}} +{{- else -}} +{{- print "policy/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "common.capabilities.networkPolicy.apiVersion" -}} +{{- if semverCompare "<1.7-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for cronjob. +*/}} +{{- define "common.capabilities.cronjob.apiVersion" -}} +{{- if semverCompare "<1.21-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "batch/v1beta1" -}} +{{- else -}} +{{- print "batch/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "common.capabilities.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for statefulset. +*/}} +{{- define "common.capabilities.statefulset.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apps/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "common.capabilities.ingress.apiVersion" -}} +{{- if .Values.ingress -}} +{{- if .Values.ingress.apiVersion -}} +{{- .Values.ingress.apiVersion -}} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end }} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for RBAC resources. +*/}} +{{- define "common.capabilities.rbac.apiVersion" -}} +{{- if semverCompare "<1.17-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "rbac.authorization.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "rbac.authorization.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for CRDs. +*/}} +{{- define "common.capabilities.crd.apiVersion" -}} +{{- if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apiextensions.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "apiextensions.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the used Helm version is 3.3+. +A way to check the used Helm version was not introduced until version 3.3.0 with .Capabilities.HelmVersion, which contains an additional "{}}" structure. +This check is introduced as a regexMatch instead of {{ if .Capabilities.HelmVersion }} because checking for the key HelmVersion in <3.3 results in a "interface not found" error. +**To be removed when the catalog's minimun Helm version is 3.3** +*/}} +{{- define "common.capabilities.supportsHelmVersion" -}} +{{- if regexMatch "{(v[0-9])*[^}]*}}$" (.Capabilities | toString ) }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_errors.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_errors.tpl new file mode 100644 index 000000000000..a79cc2e322e0 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_errors.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Through error when upgrading using empty passwords values that must not be empty. + +Usage: +{{- $validationError00 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password00" "secret" "secretName" "field" "password-00") -}} +{{- $validationError01 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password01" "secret" "secretName" "field" "password-01") -}} +{{ include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $validationError00 $validationError01) "context" $) }} + +Required password params: + - validationErrors - String - Required. List of validation strings to be return, if it is empty it won't throw error. + - context - Context - Required. Parent context. +*/}} +{{- define "common.errors.upgrade.passwords.empty" -}} + {{- $validationErrors := join "" .validationErrors -}} + {{- if and $validationErrors .context.Release.IsUpgrade -}} + {{- $errorString := "\nPASSWORDS ERROR: You must provide your current passwords when upgrading the release." -}} + {{- $errorString = print $errorString "\n Note that even after reinstallation, old credentials may be needed as they may be kept in persistent volume claims." -}} + {{- $errorString = print $errorString "\n Further information can be obtained at https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues/#credential-errors-while-upgrading-chart-releases" -}} + {{- $errorString = print $errorString "\n%s" -}} + {{- printf $errorString $validationErrors | fail -}} + {{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_images.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_images.tpl new file mode 100644 index 000000000000..42ffbc7227eb --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_images.tpl @@ -0,0 +1,75 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper image name +{{ include "common.images.image" ( dict "imageRoot" .Values.path.to.the.image "global" $) }} +*/}} +{{- define "common.images.image" -}} +{{- $registryName := .imageRoot.registry -}} +{{- $repositoryName := .imageRoot.repository -}} +{{- $tag := .imageRoot.tag | toString -}} +{{- if .global }} + {{- if .global.imageRegistry }} + {{- $registryName = .global.imageRegistry -}} + {{- end -}} +{{- end -}} +{{- if $registryName }} +{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- else -}} +{{- printf "%s:%s" $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names (deprecated: use common.images.renderPullSecrets instead) +{{ include "common.images.pullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global) }} +*/}} +{{- define "common.images.pullSecrets" -}} + {{- $pullSecrets := list }} + + {{- if .global }} + {{- range .global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names evaluating values as templates +{{ include "common.images.renderPullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "context" $) }} +*/}} +{{- define "common.images.renderPullSecrets" -}} + {{- $pullSecrets := list }} + {{- $context := .context }} + + {{- if $context.Values.global }} + {{- range $context.Values.global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" $context)) -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" $context)) -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_ingress.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_ingress.tpl new file mode 100644 index 000000000000..8caf73a61082 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_ingress.tpl @@ -0,0 +1,68 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Generate backend entry that is compatible with all Kubernetes API versions. + +Usage: +{{ include "common.ingress.backend" (dict "serviceName" "backendName" "servicePort" "backendPort" "context" $) }} + +Params: + - serviceName - String. Name of an existing service backend + - servicePort - String/Int. Port name (or number) of the service. It will be translated to different yaml depending if it is a string or an integer. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.ingress.backend" -}} +{{- $apiVersion := (include "common.capabilities.ingress.apiVersion" .context) -}} +{{- if or (eq $apiVersion "extensions/v1beta1") (eq $apiVersion "networking.k8s.io/v1beta1") -}} +serviceName: {{ .serviceName }} +servicePort: {{ .servicePort }} +{{- else -}} +service: + name: {{ .serviceName }} + port: + {{- if typeIs "string" .servicePort }} + name: {{ .servicePort }} + {{- else if or (typeIs "int" .servicePort) (typeIs "float64" .servicePort) }} + number: {{ .servicePort | int }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +Print "true" if the API pathType field is supported +Usage: +{{ include "common.ingress.supportsPathType" . }} +*/}} +{{- define "common.ingress.supportsPathType" -}} +{{- if (semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .)) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the ingressClassname field is supported +Usage: +{{ include "common.ingress.supportsIngressClassname" . }} +*/}} +{{- define "common.ingress.supportsIngressClassname" -}} +{{- if semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if cert-manager required annotations for TLS signed +certificates are set in the Ingress annotations +Ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations +Usage: +{{ include "common.ingress.certManagerRequest" ( dict "annotations" .Values.path.to.the.ingress.annotations ) }} +*/}} +{{- define "common.ingress.certManagerRequest" -}} +{{ if or (hasKey .annotations "cert-manager.io/cluster-issuer") (hasKey .annotations "cert-manager.io/issuer") }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_labels.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_labels.tpl new file mode 100644 index 000000000000..252066c7e2b3 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_labels.tpl @@ -0,0 +1,18 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Kubernetes standard labels +*/}} +{{- define "common.labels.standard" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +helm.sh/chart: {{ include "common.names.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Labels to use on deploy.spec.selector.matchLabels and svc.spec.selector +*/}} +{{- define "common.labels.matchLabels" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_names.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_names.tpl new file mode 100644 index 000000000000..cf0323171f39 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_names.tpl @@ -0,0 +1,52 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "common.names.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.names.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "common.names.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create a default fully qualified dependency name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +Usage: +{{ include "common.names.dependency.fullname" (dict "chartName" "dependency-chart-name" "chartValues" .Values.dependency-chart "context" $) }} +*/}} +{{- define "common.names.dependency.fullname" -}} +{{- if .chartValues.fullnameOverride -}} +{{- .chartValues.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .chartName .chartValues.nameOverride -}} +{{- if contains $name .context.Release.Name -}} +{{- .context.Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .context.Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_secrets.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_secrets.tpl new file mode 100644 index 000000000000..a1afc1195996 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_secrets.tpl @@ -0,0 +1,131 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Generate secret name. + +Usage: +{{ include "common.secrets.name" (dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $) }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/master/bitnami/common#existingsecret + - defaultNameSuffix - String - Optional. It is used only if we have several secrets in the same deployment. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.secrets.name" -}} +{{- $name := (include "common.names.fullname" .context) -}} + +{{- if .defaultNameSuffix -}} +{{- $name = printf "%s-%s" $name .defaultNameSuffix | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- with .existingSecret -}} +{{- if not (typeIs "string" .) -}} +{{- with .name -}} +{{- $name = . -}} +{{- end -}} +{{- else -}} +{{- $name = . -}} +{{- end -}} +{{- end -}} + +{{- printf "%s" $name -}} +{{- end -}} + +{{/* +Generate secret key. + +Usage: +{{ include "common.secrets.key" (dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName") }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/master/bitnami/common#existingsecret + - key - String - Required. Name of the key in the secret. +*/}} +{{- define "common.secrets.key" -}} +{{- $key := .key -}} + +{{- if .existingSecret -}} + {{- if not (typeIs "string" .existingSecret) -}} + {{- if .existingSecret.keyMapping -}} + {{- $key = index .existingSecret.keyMapping $.key -}} + {{- end -}} + {{- end }} +{{- end -}} + +{{- printf "%s" $key -}} +{{- end -}} + +{{/* +Generate secret password or retrieve one if already created. + +Usage: +{{ include "common.secrets.passwords.manage" (dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - key - String - Required - Name of the key in the secret. + - providedValues - List - Required - The path to the validating value in the values.yaml, e.g: "mysql.password". Will pick first parameter with a defined value. + - length - int - Optional - Length of the generated random password. + - strong - Boolean - Optional - Whether to add symbols to the generated random password. + - chartName - String - Optional - Name of the chart used when said chart is deployed as a subchart. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.passwords.manage" -}} + +{{- $password := "" }} +{{- $subchart := "" }} +{{- $chartName := default "" .chartName }} +{{- $passwordLength := default 10 .length }} +{{- $providedPasswordKey := include "common.utils.getKeyFromList" (dict "keys" .providedValues "context" $.context) }} +{{- $providedPasswordValue := include "common.utils.getValueFromKey" (dict "key" $providedPasswordKey "context" $.context) }} +{{- $secretData := (lookup "v1" "Secret" $.context.Release.Namespace .secret).data }} +{{- if $secretData }} + {{- if hasKey $secretData .key }} + {{- $password = index $secretData .key }} + {{- else }} + {{- printf "\nPASSWORDS ERROR: The secret \"%s\" does not contain the key \"%s\"\n" .secret .key | fail -}} + {{- end -}} +{{- else if $providedPasswordValue }} + {{- $password = $providedPasswordValue | toString | b64enc | quote }} +{{- else }} + + {{- if .context.Values.enabled }} + {{- $subchart = $chartName }} + {{- end -}} + + {{- $requiredPassword := dict "valueKey" $providedPasswordKey "secret" .secret "field" .key "subchart" $subchart "context" $.context -}} + {{- $requiredPasswordError := include "common.validations.values.single.empty" $requiredPassword -}} + {{- $passwordValidationErrors := list $requiredPasswordError -}} + {{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" $passwordValidationErrors "context" $.context) -}} + + {{- if .strong }} + {{- $subStr := list (lower (randAlpha 1)) (randNumeric 1) (upper (randAlpha 1)) | join "_" }} + {{- $password = randAscii $passwordLength }} + {{- $password = regexReplaceAllLiteral "\\W" $password "@" | substr 5 $passwordLength }} + {{- $password = printf "%s%s" $subStr $password | toString | shuffle | b64enc | quote }} + {{- else }} + {{- $password = randAlphaNum $passwordLength | b64enc | quote }} + {{- end }} +{{- end -}} +{{- printf "%s" $password -}} +{{- end -}} + +{{/* +Returns whether a previous generated secret already exists + +Usage: +{{ include "common.secrets.exists" (dict "secret" "secret-name" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.exists" -}} +{{- $secret := (lookup "v1" "Secret" $.context.Release.Namespace .secret) }} +{{- if $secret }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_storage.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_storage.tpl new file mode 100644 index 000000000000..60e2a844f6eb --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_storage.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper Storage Class +{{ include "common.storage.class" ( dict "persistence" .Values.path.to.the.persistence "global" $) }} +*/}} +{{- define "common.storage.class" -}} + +{{- $storageClass := .persistence.storageClass -}} +{{- if .global -}} + {{- if .global.storageClass -}} + {{- $storageClass = .global.storageClass -}} + {{- end -}} +{{- end -}} + +{{- if $storageClass -}} + {{- if (eq "-" $storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" $storageClass -}} + {{- end -}} +{{- end -}} + +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_tplvalues.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_tplvalues.tpl new file mode 100644 index 000000000000..2db166851bb5 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_tplvalues.tpl @@ -0,0 +1,13 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Renders a value that contains template. +Usage: +{{ include "common.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "common.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_utils.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_utils.tpl new file mode 100644 index 000000000000..ea083a249f80 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_utils.tpl @@ -0,0 +1,62 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Print instructions to get a secret value. +Usage: +{{ include "common.utils.secret.getvalue" (dict "secret" "secret-name" "field" "secret-value-field" "context" $) }} +*/}} +{{- define "common.utils.secret.getvalue" -}} +{{- $varname := include "common.utils.fieldToEnvVar" . -}} +export {{ $varname }}=$(kubectl get secret --namespace {{ .context.Release.Namespace | quote }} {{ .secret }} -o jsonpath="{.data.{{ .field }}}" | base64 --decode) +{{- end -}} + +{{/* +Build env var name given a field +Usage: +{{ include "common.utils.fieldToEnvVar" dict "field" "my-password" }} +*/}} +{{- define "common.utils.fieldToEnvVar" -}} + {{- $fieldNameSplit := splitList "-" .field -}} + {{- $upperCaseFieldNameSplit := list -}} + + {{- range $fieldNameSplit -}} + {{- $upperCaseFieldNameSplit = append $upperCaseFieldNameSplit ( upper . ) -}} + {{- end -}} + + {{ join "_" $upperCaseFieldNameSplit }} +{{- end -}} + +{{/* +Gets a value from .Values given +Usage: +{{ include "common.utils.getValueFromKey" (dict "key" "path.to.key" "context" $) }} +*/}} +{{- define "common.utils.getValueFromKey" -}} +{{- $splitKey := splitList "." .key -}} +{{- $value := "" -}} +{{- $latestObj := $.context.Values -}} +{{- range $splitKey -}} + {{- if not $latestObj -}} + {{- printf "please review the entire path of '%s' exists in values" $.key | fail -}} + {{- end -}} + {{- $value = ( index $latestObj . ) -}} + {{- $latestObj = $value -}} +{{- end -}} +{{- printf "%v" (default "" $value) -}} +{{- end -}} + +{{/* +Returns first .Values key with a defined value or first of the list if all non-defined +Usage: +{{ include "common.utils.getKeyFromList" (dict "keys" (list "path.to.key1" "path.to.key2") "context" $) }} +*/}} +{{- define "common.utils.getKeyFromList" -}} +{{- $key := first .keys -}} +{{- $reverseKeys := reverse .keys }} +{{- range $reverseKeys }} + {{- $value := include "common.utils.getValueFromKey" (dict "key" . "context" $.context ) }} + {{- if $value -}} + {{- $key = . }} + {{- end -}} +{{- end -}} +{{- printf "%s" $key -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_warnings.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_warnings.tpl new file mode 100644 index 000000000000..ae10fa41ee7d --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/_warnings.tpl @@ -0,0 +1,14 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Warning about using rolling tag. +Usage: +{{ include "common.warnings.rollingTag" .Values.path.to.the.imageRoot }} +*/}} +{{- define "common.warnings.rollingTag" -}} + +{{- if and (contains "bitnami/" .repository) (not (.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .repository }}:{{ .tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} + +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_cassandra.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 000000000000..ded1ae3bcad7 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_cassandra.tpl @@ -0,0 +1,72 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Cassandra required passwords are not empty. + +Usage: +{{ include "common.validations.values.cassandra.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where Cassandra values are stored, e.g: "cassandra-passwords-secret" + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.cassandra.passwords" -}} + {{- $existingSecret := include "common.cassandra.values.existingSecret" . -}} + {{- $enabled := include "common.cassandra.values.enabled" . -}} + {{- $dbUserPrefix := include "common.cassandra.values.key.dbUser" . -}} + {{- $valueKeyPassword := printf "%s.password" $dbUserPrefix -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "cassandra-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.cassandra.values.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.cassandra.dbUser.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.dbUser.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled cassandra. + +Usage: +{{ include "common.cassandra.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.cassandra.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.cassandra.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key dbUser + +Usage: +{{ include "common.cassandra.values.key.dbUser" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.key.dbUser" -}} + {{- if .subchart -}} + cassandra.dbUser + {{- else -}} + dbUser + {{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_mariadb.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 000000000000..b6906ff77b72 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_mariadb.tpl @@ -0,0 +1,103 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MariaDB required passwords are not empty. + +Usage: +{{ include "common.validations.values.mariadb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MariaDB values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mariadb.passwords" -}} + {{- $existingSecret := include "common.mariadb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mariadb.values.enabled" . -}} + {{- $architecture := include "common.mariadb.values.architecture" . -}} + {{- $authPrefix := include "common.mariadb.values.key.auth" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicationPassword := printf "%s.replicationPassword" $authPrefix -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mariadb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- if not (empty $valueUsername) -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mariadb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" $valueKeyReplicationPassword "secret" .secret "field" "mariadb-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mariadb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mariadb. + +Usage: +{{ include "common.mariadb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mariadb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mariadb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mariadb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mariadb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.key.auth" -}} + {{- if .subchart -}} + mariadb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_mongodb.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 000000000000..a071ea4d3127 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_mongodb.tpl @@ -0,0 +1,108 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MongoDB® required passwords are not empty. + +Usage: +{{ include "common.validations.values.mongodb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MongoDB® values are stored, e.g: "mongodb-passwords-secret" + - subchart - Boolean - Optional. Whether MongoDB® is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mongodb.passwords" -}} + {{- $existingSecret := include "common.mongodb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mongodb.values.enabled" . -}} + {{- $authPrefix := include "common.mongodb.values.key.auth" . -}} + {{- $architecture := include "common.mongodb.values.architecture" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyDatabase := printf "%s.database" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicaSetKey := printf "%s.replicaSetKey" $authPrefix -}} + {{- $valueKeyAuthEnabled := printf "%s.enabled" $authPrefix -}} + + {{- $authEnabled := include "common.utils.getValueFromKey" (dict "key" $valueKeyAuthEnabled "context" .context) -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") (eq $authEnabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mongodb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- $valueDatabase := include "common.utils.getValueFromKey" (dict "key" $valueKeyDatabase "context" .context) }} + {{- if and $valueUsername $valueDatabase -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mongodb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replicaset") -}} + {{- $requiredReplicaSetKey := dict "valueKey" $valueKeyReplicaSetKey "secret" .secret "field" "mongodb-replica-set-key" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicaSetKey -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mongodb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDb is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mongodb. + +Usage: +{{ include "common.mongodb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mongodb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mongodb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mongodb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDB® is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.key.auth" -}} + {{- if .subchart -}} + mongodb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mongodb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_postgresql.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 000000000000..164ec0d01252 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_postgresql.tpl @@ -0,0 +1,129 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate PostgreSQL required passwords are not empty. + +Usage: +{{ include "common.validations.values.postgresql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where postgresql values are stored, e.g: "postgresql-passwords-secret" + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.postgresql.passwords" -}} + {{- $existingSecret := include "common.postgresql.values.existingSecret" . -}} + {{- $enabled := include "common.postgresql.values.enabled" . -}} + {{- $valueKeyPostgresqlPassword := include "common.postgresql.values.key.postgressPassword" . -}} + {{- $valueKeyPostgresqlReplicationEnabled := include "common.postgresql.values.key.replicationPassword" . -}} + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + {{- $requiredPostgresqlPassword := dict "valueKey" $valueKeyPostgresqlPassword "secret" .secret "field" "postgresql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlPassword -}} + + {{- $enabledReplication := include "common.postgresql.values.enabled.replication" . -}} + {{- if (eq $enabledReplication "true") -}} + {{- $requiredPostgresqlReplicationPassword := dict "valueKey" $valueKeyPostgresqlReplicationEnabled "secret" .secret "field" "postgresql-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to decide whether evaluate global values. + +Usage: +{{ include "common.postgresql.values.use.global" (dict "key" "key-of-global" "context" $) }} +Params: + - key - String - Required. Field to be evaluated within global, e.g: "existingSecret" +*/}} +{{- define "common.postgresql.values.use.global" -}} + {{- if .context.Values.global -}} + {{- if .context.Values.global.postgresql -}} + {{- index .context.Values.global.postgresql .key | quote -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.postgresql.values.existingSecret" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.existingSecret" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "existingSecret" "context" .context) -}} + + {{- if .subchart -}} + {{- default (.context.Values.postgresql.existingSecret | quote) $globalValue -}} + {{- else -}} + {{- default (.context.Values.existingSecret | quote) $globalValue -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled postgresql. + +Usage: +{{ include "common.postgresql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key postgressPassword. + +Usage: +{{ include "common.postgresql.values.key.postgressPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.postgressPassword" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "postgresqlUsername" "context" .context) -}} + + {{- if not $globalValue -}} + {{- if .subchart -}} + postgresql.postgresqlPassword + {{- else -}} + postgresqlPassword + {{- end -}} + {{- else -}} + global.postgresql.postgresqlPassword + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled.replication. + +Usage: +{{ include "common.postgresql.values.enabled.replication" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.enabled.replication" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.replication.enabled -}} + {{- else -}} + {{- printf "%v" .context.Values.replication.enabled -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key replication.password. + +Usage: +{{ include "common.postgresql.values.key.replicationPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.replicationPassword" -}} + {{- if .subchart -}} + postgresql.replication.password + {{- else -}} + replication.password + {{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_redis.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_redis.tpl new file mode 100644 index 000000000000..5d72959b9eee --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_redis.tpl @@ -0,0 +1,76 @@ + +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Redis™ required passwords are not empty. + +Usage: +{{ include "common.validations.values.redis.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where redis values are stored, e.g: "redis-passwords-secret" + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.redis.passwords" -}} + {{- $enabled := include "common.redis.values.enabled" . -}} + {{- $valueKeyPrefix := include "common.redis.values.keys.prefix" . -}} + {{- $standarizedVersion := include "common.redis.values.standarized.version" . }} + + {{- $existingSecret := ternary (printf "%s%s" $valueKeyPrefix "auth.existingSecret") (printf "%s%s" $valueKeyPrefix "existingSecret") (eq $standarizedVersion "true") }} + {{- $existingSecretValue := include "common.utils.getValueFromKey" (dict "key" $existingSecret "context" .context) }} + + {{- $valueKeyRedisPassword := ternary (printf "%s%s" $valueKeyPrefix "auth.password") (printf "%s%s" $valueKeyPrefix "password") (eq $standarizedVersion "true") }} + {{- $valueKeyRedisUseAuth := ternary (printf "%s%s" $valueKeyPrefix "auth.enabled") (printf "%s%s" $valueKeyPrefix "usePassword") (eq $standarizedVersion "true") }} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $useAuth := include "common.utils.getValueFromKey" (dict "key" $valueKeyRedisUseAuth "context" .context) -}} + {{- if eq $useAuth "true" -}} + {{- $requiredRedisPassword := dict "valueKey" $valueKeyRedisPassword "secret" .secret "field" "redis-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRedisPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled redis. + +Usage: +{{ include "common.redis.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.redis.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.redis.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right prefix path for the values + +Usage: +{{ include "common.redis.values.key.prefix" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.redis.values.keys.prefix" -}} + {{- if .subchart -}}redis.{{- else -}}{{- end -}} +{{- end -}} + +{{/* +Checks whether the redis chart's includes the standarizations (version >= 14) + +Usage: +{{ include "common.redis.values.standarized.version" (dict "context" $) }} +*/}} +{{- define "common.redis.values.standarized.version" -}} + + {{- $standarizedAuth := printf "%s%s" (include "common.redis.values.keys.prefix" .) "auth" -}} + {{- $standarizedAuthValues := include "common.utils.getValueFromKey" (dict "key" $standarizedAuth "context" .context) }} + + {{- if $standarizedAuthValues -}} + {{- true -}} + {{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_validations.tpl b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_validations.tpl new file mode 100644 index 000000000000..9a814cf40dcb --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/templates/validations/_validations.tpl @@ -0,0 +1,46 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate values must not be empty. + +Usage: +{{- $validateValueConf00 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-01") -}} +{{ include "common.validations.values.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" +*/}} +{{- define "common.validations.values.multiple.empty" -}} + {{- range .required -}} + {{- include "common.validations.values.single.empty" (dict "valueKey" .valueKey "secret" .secret "field" .field "context" $.context) -}} + {{- end -}} +{{- end -}} + +{{/* +Validate a value must not be empty. + +Usage: +{{ include "common.validations.value.empty" (dict "valueKey" "mariadb.password" "secret" "secretName" "field" "my-password" "subchart" "subchart" "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" + - subchart - String - Optional - Name of the subchart that the validated password is part of. +*/}} +{{- define "common.validations.values.single.empty" -}} + {{- $value := include "common.utils.getValueFromKey" (dict "key" .valueKey "context" .context) }} + {{- $subchart := ternary "" (printf "%s." .subchart) (empty .subchart) }} + + {{- if not $value -}} + {{- $varname := "my-value" -}} + {{- $getCurrentValue := "" -}} + {{- if and .secret .field -}} + {{- $varname = include "common.utils.fieldToEnvVar" . -}} + {{- $getCurrentValue = printf " To get the current value:\n\n %s\n" (include "common.utils.secret.getvalue" .) -}} + {{- end -}} + {{- printf "\n '%s' must not be empty, please add '--set %s%s=$%s' to the command.%s" .valueKey $subchart .valueKey $varname $getCurrentValue -}} + {{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/charts/common/values.yaml b/pkg/iac/scanners/helm/test/mysql/charts/common/values.yaml new file mode 100644 index 000000000000..f2df68e5e6af --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/charts/common/values.yaml @@ -0,0 +1,5 @@ +## bitnami/common +## It is required by CI/CD tools and processes. +## @skip exampleValue +## +exampleValue: common-chart diff --git a/pkg/iac/scanners/helm/test/mysql/ci/values-production-with-rbac.yaml b/pkg/iac/scanners/helm/test/mysql/ci/values-production-with-rbac.yaml new file mode 100644 index 000000000000..d3370c931113 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/ci/values-production-with-rbac.yaml @@ -0,0 +1,30 @@ +# Test values file for generating all of the yaml and check that +# the rendering is correct + +architecture: replication +auth: + usePasswordFiles: true + +primary: + extraEnvVars: + - name: TEST + value: "3" + podDisruptionBudget: + create: true + +secondary: + replicaCount: 2 + extraEnvVars: + - name: TEST + value: "2" + podDisruptionBudget: + create: true + +serviceAccount: + create: true + name: mysql-service-account +rbac: + create: true + +metrics: + enabled: true diff --git a/pkg/iac/scanners/helm/test/mysql/templates/NOTES.txt b/pkg/iac/scanners/helm/test/mysql/templates/NOTES.txt new file mode 100644 index 000000000000..1b8b6d5ea7d2 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/NOTES.txt @@ -0,0 +1,102 @@ +CHART NAME: {{ .Chart.Name }} +CHART VERSION: {{ .Chart.Version }} +APP VERSION: {{ .Chart.AppVersion }} + +** Please be patient while the chart is being deployed ** + +{{- if .Values.diagnosticMode.enabled }} +The chart has been deployed in diagnostic mode. All probes have been disabled and the command has been overwritten with: + + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 4 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 4 }} + +Get the list of pods by executing: + + kubectl get pods --namespace {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} + +Access the pod you want to debug by executing + + kubectl exec --namespace {{ .Release.Namespace }} -ti -- bash + +In order to replicate the container startup scripts execute this command: + + /opt/bitnami/scripts/mysql/entrypoint.sh /opt/bitnami/scripts/mysql/run.sh + +{{- else }} + +Tip: + + Watch the deployment status using the command: kubectl get pods -w --namespace {{ .Release.Namespace }} + +Services: + + echo Primary: {{ include "mysql.primary.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}:{{ .Values.primary.service.port }} +{{- if eq .Values.architecture "replication" }} + echo Secondary: {{ include "mysql.secondary.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}:{{ .Values.secondary.service.port }} +{{- end }} + +Execute the following to get the administrator credentials: + + echo Username: root + MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "mysql.secretName" . }} -o jsonpath="{.data.mysql-root-password}" | base64 --decode) + +To connect to your database: + + 1. Run a pod that you can use as a client: + + kubectl run {{ include "common.names.fullname" . }}-client --rm --tty -i --restart='Never' --image {{ template "mysql.image" . }} --namespace {{ .Release.Namespace }} --command -- bash + + 2. To connect to primary service (read/write): + + mysql -h {{ include "mysql.primary.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} -uroot -p"$MYSQL_ROOT_PASSWORD" + +{{- if eq .Values.architecture "replication" }} + + 3. To connect to secondary service (read-only): + + mysql -h {{ include "mysql.secondary.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} -uroot -p"$MYSQL_ROOT_PASSWORD" +{{- end }} + +{{ if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} +Note: Since NetworkPolicy is enabled, only pods with label {{ template "common.names.fullname" . }}-client=true" will be able to connect to MySQL. +{{- end }} + +{{- if .Values.metrics.enabled }} + +To access the MySQL Prometheus metrics from outside the cluster execute the following commands: + + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ printf "%s-metrics" (include "common.names.fullname" .) }} {{ .Values.metrics.service.port }}:{{ .Values.metrics.service.port }} & + curl http://127.0.0.1:{{ .Values.metrics.service.port }}/metrics + +{{- end }} + +To upgrade this helm chart: + + 1. Obtain the password as described on the 'Administrator credentials' section and set the 'root.password' parameter as shown below: + + ROOT_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ include "common.names.fullname" . }} -o jsonpath="{.data.mysql-root-password}" | base64 --decode) + helm upgrade --namespace {{ .Release.Namespace }} {{ .Release.Name }} bitnami/mysql --set auth.rootPassword=$ROOT_PASSWORD + +{{ include "mysql.validateValues" . }} +{{ include "mysql.checkRollingTags" . }} +{{- if and (not .Values.auth.existingSecret) (not .Values.auth.customPasswordFiles) -}} + {{- $secretName := include "mysql.secretName" . -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" "auth.rootPassword" "secret" $secretName "field" "mysql-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- if not (empty .Values.auth.username) -}} + {{- $requiredPassword := dict "valueKey" "auth.password" "secret" $secretName "field" "mysql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq .Values.architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" "auth.replicationPassword" "secret" $secretName "field" "mysql-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicationPassword -}} + {{- end -}} + + {{- $mysqlPasswordValidationErrors := include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" $) -}} + {{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" $mysqlPasswordValidationErrors "context" $) -}} +{{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/_helpers.tpl b/pkg/iac/scanners/helm/test/mysql/templates/_helpers.tpl new file mode 100644 index 000000000000..6c2bcff81398 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/_helpers.tpl @@ -0,0 +1,192 @@ +{{/* vim: set filetype=mustache: */}} + +{{- define "mysql.primary.fullname" -}} +{{- if eq .Values.architecture "replication" }} +{{- printf "%s-%s" (include "common.names.fullname" .) "primary" | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- include "common.names.fullname" . -}} +{{- end -}} +{{- end -}} + +{{- define "mysql.secondary.fullname" -}} +{{- printf "%s-%s" (include "common.names.fullname" .) "secondary" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Return the proper MySQL image name +*/}} +{{- define "mysql.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper metrics image name +*/}} +{{- define "mysql.metrics.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.metrics.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the init container volume-permissions image) +*/}} +{{- define "mysql.volumePermissions.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "mysql.imagePullSecrets" -}} +{{ include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.metrics.image .Values.volumePermissions.image) "global" .Values.global) }} +{{- end -}} + +{{ template "mysql.initdbScriptsCM" . }} +{{/* +Get the initialization scripts ConfigMap name. +*/}} +{{- define "mysql.initdbScriptsCM" -}} +{{- if .Values.initdbScriptsConfigMap -}} + {{- printf "%s" .Values.initdbScriptsConfigMap -}} +{{- else -}} + {{- printf "%s-init-scripts" (include "mysql.primary.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* + Returns the proper service account name depending if an explicit service account name is set + in the values file. If the name is not set it will default to either mysql.fullname if serviceAccount.create + is true or default otherwise. +*/}} +{{- define "mysql.serviceAccountName" -}} + {{- if .Values.serviceAccount.create -}} + {{ default (include "common.names.fullname" .) .Values.serviceAccount.name }} + {{- else -}} + {{ default "default" .Values.serviceAccount.name }} + {{- end -}} +{{- end -}} + +{{/* +Return the configmap with the MySQL Primary configuration +*/}} +{{- define "mysql.primary.configmapName" -}} +{{- if .Values.primary.existingConfigmap -}} + {{- printf "%s" (tpl .Values.primary.existingConfigmap $) -}} +{{- else -}} + {{- printf "%s" (include "mysql.primary.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a configmap object should be created for MySQL Secondary +*/}} +{{- define "mysql.primary.createConfigmap" -}} +{{- if and .Values.primary.configuration (not .Values.primary.existingConfigmap) }} + {{- true -}} +{{- else -}} +{{- end -}} +{{- end -}} + +{{/* +Return the configmap with the MySQL Primary configuration +*/}} +{{- define "mysql.secondary.configmapName" -}} +{{- if .Values.secondary.existingConfigmap -}} + {{- printf "%s" (tpl .Values.secondary.existingConfigmap $) -}} +{{- else -}} + {{- printf "%s" (include "mysql.secondary.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a configmap object should be created for MySQL Secondary +*/}} +{{- define "mysql.secondary.createConfigmap" -}} +{{- if and (eq .Values.architecture "replication") .Values.secondary.configuration (not .Values.secondary.existingConfigmap) }} + {{- true -}} +{{- else -}} +{{- end -}} +{{- end -}} + +{{/* +Return the secret with MySQL credentials +*/}} +{{- define "mysql.secretName" -}} + {{- if .Values.auth.existingSecret -}} + {{- printf "%s" .Values.auth.existingSecret -}} + {{- else -}} + {{- printf "%s" (include "common.names.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{/* +Return true if a secret object should be created for MySQL +*/}} +{{- define "mysql.createSecret" -}} +{{- if and (not .Values.auth.existingSecret) (not .Values.auth.customPasswordFiles) }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Returns the available value for certain key in an existing secret (if it exists), +otherwise it generates a random value. +*/}} +{{- define "getValueFromSecret" }} + {{- $len := (default 16 .Length) | int -}} + {{- $obj := (lookup "v1" "Secret" .Namespace .Name).data -}} + {{- if $obj }} + {{- index $obj .Key | b64dec -}} + {{- else -}} + {{- randAlphaNum $len -}} + {{- end -}} +{{- end }} + +{{- define "mysql.root.password" -}} + {{- if not (empty .Values.auth.rootPassword) }} + {{- .Values.auth.rootPassword }} + {{- else if (not .Values.auth.forcePassword) }} + {{- include "getValueFromSecret" (dict "Namespace" .Release.Namespace "Name" (include "common.names.fullname" .) "Length" 10 "Key" "mysql-root-password") }} + {{- else }} + {{- required "A MySQL Root Password is required!" .Values.auth.rootPassword }} + {{- end }} +{{- end -}} + +{{- define "mysql.password" -}} + {{- if and (not (empty .Values.auth.username)) (not (empty .Values.auth.password)) }} + {{- .Values.auth.password }} + {{- else if (not .Values.auth.forcePassword) }} + {{- include "getValueFromSecret" (dict "Namespace" .Release.Namespace "Name" (include "common.names.fullname" .) "Length" 10 "Key" "mysql-password") }} + {{- else }} + {{- required "A MySQL Database Password is required!" .Values.auth.password }} + {{- end }} +{{- end -}} + +{{- define "mysql.replication.password" -}} + {{- if not (empty .Values.auth.replicationPassword) }} + {{- .Values.auth.replicationPassword }} + {{- else if (not .Values.auth.forcePassword) }} + {{- include "getValueFromSecret" (dict "Namespace" .Release.Namespace "Name" (include "common.names.fullname" .) "Length" 10 "Key" "mysql-replication-password") }} + {{- else }} + {{- required "A MySQL Replication Password is required!" .Values.auth.replicationPassword }} + {{- end }} +{{- end -}} + +{{/* Check if there are rolling tags in the images */}} +{{- define "mysql.checkRollingTags" -}} +{{- include "common.warnings.rollingTag" .Values.image }} +{{- include "common.warnings.rollingTag" .Values.metrics.image }} +{{- include "common.warnings.rollingTag" .Values.volumePermissions.image }} +{{- end -}} + +{{/* +Compile all warnings into a single message, and call fail. +*/}} +{{- define "mysql.validateValues" -}} +{{- $messages := list -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message | fail -}} +{{- end -}} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/extra-list.yaml b/pkg/iac/scanners/helm/test/mysql/templates/extra-list.yaml new file mode 100644 index 000000000000..9ac65f9e16f4 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/extra-list.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/metrics-svc.yaml b/pkg/iac/scanners/helm/test/mysql/templates/metrics-svc.yaml new file mode 100644 index 000000000000..fb0d9d761dc6 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/metrics-svc.yaml @@ -0,0 +1,29 @@ +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ printf "%s-metrics" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + app.kubernetes.io/component: metrics + {{- if or .Values.metrics.service.annotations .Values.commonAnnotations }} + annotations: + {{- if .Values.metrics.service.annotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.service.annotations "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + type: {{ .Values.metrics.service.type }} + ports: + - port: {{ .Values.metrics.service.port }} + targetPort: metrics + protocol: TCP + name: metrics + selector: {{- include "common.labels.matchLabels" $ | nindent 4 }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/networkpolicy.yaml b/pkg/iac/scanners/helm/test/mysql/templates/networkpolicy.yaml new file mode 100644 index 000000000000..a0d1d01d4079 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/networkpolicy.yaml @@ -0,0 +1,38 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ template "common.capabilities.networkPolicy.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + ingress: + # Allow inbound connections + - ports: + - port: {{ .Values.primary.service.port }} + {{- if not .Values.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: + {{ template "common.names.fullname" . }}-client: "true" + {{- if .Values.networkPolicy.explicitNamespacesSelector }} + namespaceSelector: +{{ toYaml .Values.networkPolicy.explicitNamespacesSelector | indent 12 }} + {{- end }} + - podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 14 }} + {{- end }} + {{- if .Values.metrics.enabled }} + # Allow prometheus scrapes + - ports: + - port: 9104 + {{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/primary/configmap.yaml b/pkg/iac/scanners/helm/test/mysql/templates/primary/configmap.yaml new file mode 100644 index 000000000000..540b7b9072e9 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/primary/configmap.yaml @@ -0,0 +1,18 @@ +{{- if (include "mysql.primary.createConfigmap" .) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "mysql.primary.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: primary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + my.cnf: |- + {{ .Values.primary.configuration | nindent 4 }} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/primary/initialization-configmap.yaml b/pkg/iac/scanners/helm/test/mysql/templates/primary/initialization-configmap.yaml new file mode 100644 index 000000000000..83cbaea74883 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/primary/initialization-configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.initdbScripts (not .Values.initdbScriptsConfigMap) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-init-scripts" (include "mysql.primary.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: primary + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: +{{- include "common.tplvalues.render" (dict "value" .Values.initdbScripts "context" .) | nindent 2 }} +{{ end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/primary/pdb.yaml b/pkg/iac/scanners/helm/test/mysql/templates/primary/pdb.yaml new file mode 100644 index 000000000000..106ad5207e5a --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/primary/pdb.yaml @@ -0,0 +1,25 @@ +{{- if .Values.primary.pdb.enabled }} +apiVersion: {{ include "common.capabilities.policy.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "mysql.primary.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: primary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if .Values.primary.pdb.minAvailable }} + minAvailable: {{ .Values.primary.pdb.minAvailable }} + {{- end }} + {{- if .Values.primary.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.primary.pdb.maxUnavailable }} + {{- end }} + selector: + matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: primary +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/primary/statefulset.yaml b/pkg/iac/scanners/helm/test/mysql/templates/primary/statefulset.yaml new file mode 100644 index 000000000000..6f9c99ea66d9 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/primary/statefulset.yaml @@ -0,0 +1,368 @@ +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ include "mysql.primary.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: primary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.primary.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.primary.podLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: primary + serviceName: {{ include "mysql.primary.fullname" . }} + updateStrategy: + type: {{ .Values.primary.updateStrategy }} + {{- if (eq "Recreate" .Values.primary.updateStrategy) }} + rollingUpdate: null + {{- else if .Values.primary.rollingUpdatePartition }} + rollingUpdate: + partition: {{ .Values.primary.rollingUpdatePartition }} + {{- end }} + template: + metadata: + annotations: + {{- if (include "mysql.primary.createConfigmap" .) }} + checksum/configuration: {{ include (print $.Template.BasePath "/primary/configmap.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.primary.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.primary.podAnnotations "context" $) | nindent 8 }} + {{- end }} + labels: {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: primary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.primary.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.primary.podLabels "context" $ ) | nindent 8 }} + {{- end }} + spec: + {{- include "mysql.imagePullSecrets" . | nindent 6 }} + {{- if .Values.primary.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.primary.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.schedulerName }} + schedulerName: {{ .Values.schedulerName | quote }} + {{- end }} + serviceAccountName: {{ template "mysql.serviceAccountName" . }} + {{- if .Values.primary.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.primary.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.primary.podAffinityPreset "component" "primary" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.primary.podAntiAffinityPreset "component" "primary" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.primary.nodeAffinityPreset.type "key" .Values.primary.nodeAffinityPreset.key "values" .Values.primary.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.primary.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.primary.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.primary.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.primary.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} + {{- if .Values.primary.podSecurityContext.enabled }} + securityContext: {{- omit .Values.primary.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if or .Values.primary.initContainers (and .Values.primary.podSecurityContext.enabled .Values.volumePermissions.enabled .Values.primary.persistence.enabled) }} + initContainers: + {{- if .Values.primary.initContainers }} + {{- include "common.tplvalues.render" (dict "value" .Values.primary.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- if and .Values.primary.podSecurityContext.enabled .Values.volumePermissions.enabled .Values.primary.persistence.enabled }} + - name: volume-permissions + image: {{ include "mysql.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: + - /bin/bash + - -ec + - | + chown -R {{ .Values.primary.containerSecurityContext.runAsUser }}:{{ .Values.primary.podSecurityContext.fsGroup }} /bitnami/mysql + securityContext: + runAsUser: 0 + {{- if .Values.volumePermissions.resources }} + resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: data + mountPath: /bitnami/mysql + {{- end }} + {{- end }} + containers: + - name: mysql + image: {{ include "mysql.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.primary.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.primary.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.primary.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.primary.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.primary.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.primary.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} + {{- if .Values.auth.usePasswordFiles }} + - name: MYSQL_ROOT_PASSWORD_FILE + value: {{ default "/opt/bitnami/mysql/secrets/mysql-root-password" .Values.auth.customPasswordFiles.root }} + {{- else }} + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "mysql.secretName" . }} + key: mysql-root-password + {{- end }} + {{- if not (empty .Values.auth.username) }} + - name: MYSQL_USER + value: {{ .Values.auth.username | quote }} + {{- if .Values.auth.usePasswordFiles }} + - name: MYSQL_PASSWORD_FILE + value: {{ default "/opt/bitnami/mysql/secrets/mysql-password" .Values.auth.customPasswordFiles.user }} + {{- else }} + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "mysql.secretName" . }} + key: mysql-password + {{- end }} + {{- end }} + - name: MYSQL_DATABASE + value: {{ .Values.auth.database | quote }} + {{- if eq .Values.architecture "replication" }} + - name: MYSQL_REPLICATION_MODE + value: "master" + - name: MYSQL_REPLICATION_USER + value: {{ .Values.auth.replicationUser | quote }} + {{- if .Values.auth.usePasswordFiles }} + - name: MYSQL_REPLICATION_PASSWORD_FILE + value: {{ default "/opt/bitnami/mysql/secrets/mysql-replication-password" .Values.auth.customPasswordFiles.replicator }} + {{- else }} + - name: MYSQL_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "mysql.secretName" . }} + key: mysql-replication-password + {{- end }} + {{- end }} + {{- if .Values.primary.extraFlags }} + - name: MYSQL_EXTRA_FLAGS + value: "{{ .Values.primary.extraFlags }}" + {{- end }} + {{- if .Values.primary.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.primary.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.primary.extraEnvVarsCM .Values.primary.extraEnvVarsSecret }} + envFrom: + {{- if .Values.primary.extraEnvVarsCM }} + - configMapRef: + name: {{ .Values.primary.extraEnvVarsCM }} + {{- end }} + {{- if .Values.primary.extraEnvVarsSecret }} + - secretRef: + name: {{ .Values.primary.extraEnvVarsSecret }} + {{- end }} + {{- end }} + ports: + - name: mysql + containerPort: 3306 + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.primary.livenessProbe.enabled }} + livenessProbe: {{- omit .Values.primary.livenessProbe "enabled" | toYaml | nindent 12 }} + exec: + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE") + fi + mysqladmin status -uroot -p"${password_aux}" + {{- else if .Values.primary.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.primary.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.primary.readinessProbe.enabled }} + readinessProbe: {{- omit .Values.primary.readinessProbe "enabled" | toYaml | nindent 12 }} + exec: + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE") + fi + mysqladmin status -uroot -p"${password_aux}" + {{- else if .Values.primary.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.primary.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.primary.startupProbe.enabled }} + startupProbe: {{- omit .Values.primary.startupProbe "enabled" | toYaml | nindent 12 }} + exec: + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE") + fi + mysqladmin status -uroot -p"${password_aux}" + {{- else if .Values.primary.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.primary.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.primary.resources }} + resources: {{ toYaml .Values.primary.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: data + mountPath: /bitnami/mysql + {{- if or .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + mountPath: /docker-entrypoint-initdb.d + {{- end }} + {{- if or .Values.primary.configuration .Values.primary.existingConfigmap }} + - name: config + mountPath: /opt/bitnami/mysql/conf/my.cnf + subPath: my.cnf + {{- end }} + {{- if and .Values.auth.usePasswordFiles (not .Values.auth.customPasswordFiles) }} + - name: mysql-credentials + mountPath: /opt/bitnami/mysql/secrets/ + {{- end }} + {{- if .Values.primary.extraVolumeMounts }} + {{- include "common.tplvalues.render" (dict "value" .Values.primary.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ include "mysql.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + env: + {{- if .Values.auth.usePasswordFiles }} + - name: MYSQL_ROOT_PASSWORD_FILE + value: {{ default "/opt/bitnami/mysqld-exporter/secrets/mysql-root-password" .Values.auth.customPasswordFiles.root }} + {{- else }} + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "mysql.secretName" . }} + key: mysql-root-password + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else }} + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE") + fi + DATA_SOURCE_NAME="root:${password_aux}@(localhost:3306)/" /bin/mysqld_exporter {{- range .Values.metrics.extraArgs.primary }} {{ . }} {{- end }} + {{- end }} + ports: + - name: metrics + containerPort: 9104 + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.metrics.livenessProbe.enabled }} + livenessProbe: {{- omit .Values.metrics.livenessProbe "enabled" | toYaml | nindent 12 }} + httpGet: + path: /metrics + port: metrics + {{- end }} + {{- if .Values.metrics.readinessProbe.enabled }} + readinessProbe: {{- omit .Values.metrics.readinessProbe "enabled" | toYaml | nindent 12 }} + httpGet: + path: /metrics + port: metrics + {{- end }} + {{- end }} + {{- if .Values.metrics.resources }} + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} + {{- if and .Values.auth.usePasswordFiles (not .Values.auth.customPasswordFiles) }} + volumeMounts: + - name: mysql-credentials + mountPath: /opt/bitnami/mysqld-exporter/secrets/ + {{- end }} + {{- end }} + {{- if .Values.primary.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.primary.sidecars "context" $) | nindent 8 }} + {{- end }} + volumes: + {{- if or .Values.primary.configuration .Values.primary.existingConfigmap }} + - name: config + configMap: + name: {{ include "mysql.primary.configmapName" . }} + {{- end }} + {{- if or .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + configMap: + name: {{ include "mysql.initdbScriptsCM" . }} + {{- end }} + {{- if and .Values.auth.usePasswordFiles (not .Values.auth.customPasswordFiles) }} + - name: mysql-credentials + secret: + secretName: {{ include "mysql.secretName" . }} + items: + - key: mysql-root-password + path: mysql-root-password + - key: mysql-password + path: mysql-password + {{- if eq .Values.architecture "replication" }} + - key: mysql-replication-password + path: mysql-replication-password + {{- end }} + {{- end }} + {{- if .Values.primary.extraVolumes }} + {{- include "common.tplvalues.render" (dict "value" .Values.primary.extraVolumes "context" $) | nindent 8 }} + {{- end }} + {{- if and .Values.primary.persistence.enabled .Values.primary.persistence.existingClaim }} + - name: data + persistentVolumeClaim: + claimName: {{ tpl .Values.primary.persistence.existingClaim . }} + {{- else if not .Values.primary.persistence.enabled }} + - name: data + emptyDir: {} + {{- else if and .Values.primary.persistence.enabled (not .Values.primary.persistence.existingClaim) }} + volumeClaimTemplates: + - metadata: + name: data + labels: {{ include "common.labels.matchLabels" . | nindent 10 }} + app.kubernetes.io/component: primary + {{- if .Values.primary.persistence.annotations }} + annotations: + {{- toYaml .Values.primary.persistence.annotations | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range .Values.primary.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.primary.persistence.size | quote }} + {{ include "common.storage.class" (dict "persistence" .Values.primary.persistence "global" .Values.global) }} + {{- if .Values.primary.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.primary.persistence.selector "context" $) | nindent 10 }} + {{- end -}} + {{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/primary/svc-headless.yaml b/pkg/iac/scanners/helm/test/mysql/templates/primary/svc-headless.yaml new file mode 100644 index 000000000000..49e6e5798783 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/primary/svc-headless.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mysql.primary.fullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: primary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + publishNotReadyAddresses: true + ports: + - name: mysql + port: {{ .Values.primary.service.port }} + targetPort: mysql + selector: {{ include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: primary diff --git a/pkg/iac/scanners/helm/test/mysql/templates/primary/svc.yaml b/pkg/iac/scanners/helm/test/mysql/templates/primary/svc.yaml new file mode 100644 index 000000000000..b46e6faa8149 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/primary/svc.yaml @@ -0,0 +1,41 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mysql.primary.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: primary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.primary.service.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.primary.service.annotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.primary.service.type }} + {{- if and (eq .Values.primary.service.type "ClusterIP") .Values.primary.service.clusterIP }} + clusterIP: {{ .Values.primary.service.clusterIP }} + {{- end }} + {{- if and .Values.primary.service.loadBalancerIP (eq .Values.primary.service.type "LoadBalancer") }} + loadBalancerIP: {{ .Values.primary.service.loadBalancerIP }} + externalTrafficPolicy: {{ .Values.primary.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if and (eq .Values.primary.service.type "LoadBalancer") .Values.primary.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- toYaml .Values.primary.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} + ports: + - name: mysql + port: {{ .Values.primary.service.port }} + protocol: TCP + targetPort: mysql + {{- if (and (or (eq .Values.primary.service.type "NodePort") (eq .Values.primary.service.type "LoadBalancer")) .Values.primary.service.nodePort) }} + nodePort: {{ .Values.primary.service.nodePort }} + {{- else if eq .Values.primary.service.type "ClusterIP" }} + nodePort: null + {{- end }} + selector: {{ include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: primary diff --git a/pkg/iac/scanners/helm/test/mysql/templates/role.yaml b/pkg/iac/scanners/helm/test/mysql/templates/role.yaml new file mode 100644 index 000000000000..4cbdd5c9ff20 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/role.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.serviceAccount.create .Values.rbac.create }} +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +kind: Role +metadata: + name: {{ include "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +rules: + - apiGroups: + - "" + resources: + - endpoints + verbs: + - get +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/rolebinding.yaml b/pkg/iac/scanners/helm/test/mysql/templates/rolebinding.yaml new file mode 100644 index 000000000000..90ede32f5fc7 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/rolebinding.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.serviceAccount.create .Values.rbac.create }} +kind: RoleBinding +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + name: {{ include "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +subjects: + - kind: ServiceAccount + name: {{ include "mysql.serviceAccountName" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "common.names.fullname" . -}} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/secondary/configmap.yaml b/pkg/iac/scanners/helm/test/mysql/templates/secondary/configmap.yaml new file mode 100644 index 000000000000..682e3e19ba96 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/secondary/configmap.yaml @@ -0,0 +1,18 @@ +{{- if (include "mysql.secondary.createConfigmap" .) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "mysql.secondary.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: secondary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + my.cnf: |- + {{ .Values.secondary.configuration | nindent 4 }} +{{- end -}} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/secondary/pdb.yaml b/pkg/iac/scanners/helm/test/mysql/templates/secondary/pdb.yaml new file mode 100644 index 000000000000..49c7e167c0a2 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/secondary/pdb.yaml @@ -0,0 +1,25 @@ +{{- if and (eq .Values.architecture "replication") .Values.secondary.pdb.enabled }} +apiVersion: {{ include "common.capabilities.policy.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "mysql.secondary.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: secondary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if .Values.secondary.pdb.minAvailable }} + minAvailable: {{ .Values.secondary.pdb.minAvailable }} + {{- end }} + {{- if .Values.secondary.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.secondary.pdb.maxUnavailable }} + {{- end }} + selector: + matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: secondary +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/secondary/statefulset.yaml b/pkg/iac/scanners/helm/test/mysql/templates/secondary/statefulset.yaml new file mode 100644 index 000000000000..ef196ebf6df0 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/secondary/statefulset.yaml @@ -0,0 +1,338 @@ +{{- if eq .Values.architecture "replication" }} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ include "mysql.secondary.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: secondary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.secondary.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.secondary.podLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.secondary.replicaCount }} + selector: + matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: secondary + serviceName: {{ include "mysql.secondary.fullname" . }} + updateStrategy: + type: {{ .Values.secondary.updateStrategy }} + {{- if (eq "Recreate" .Values.secondary.updateStrategy) }} + rollingUpdate: null + {{- else if .Values.secondary.rollingUpdatePartition }} + rollingUpdate: + partition: {{ .Values.secondary.rollingUpdatePartition }} + {{- end }} + template: + metadata: + annotations: + {{- if (include "mysql.secondary.createConfigmap" .) }} + checksum/configuration: {{ include (print $.Template.BasePath "/secondary/configmap.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.secondary.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.secondary.podAnnotations "context" $) | nindent 8 }} + {{- end }} + labels: {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: secondary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.secondary.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.secondary.podLabels "context" $ ) | nindent 8 }} + {{- end }} + spec: + {{- include "mysql.imagePullSecrets" . | nindent 6 }} + {{- if .Values.secondary.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.secondary.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.schedulerName }} + schedulerName: {{ .Values.schedulerName | quote }} + {{- end }} + serviceAccountName: {{ include "mysql.serviceAccountName" . }} + {{- if .Values.secondary.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.secondary.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.secondary.podAffinityPreset "component" "secondary" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.secondary.podAntiAffinityPreset "component" "secondary" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.secondary.nodeAffinityPreset.type "key" .Values.secondary.nodeAffinityPreset.key "values" .Values.secondary.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.secondary.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.secondary.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.secondary.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.secondary.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} + {{- if .Values.secondary.podSecurityContext.enabled }} + securityContext: {{- omit .Values.secondary.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if or .Values.secondary.initContainers (and .Values.secondary.podSecurityContext.enabled .Values.volumePermissions.enabled .Values.secondary.persistence.enabled) }} + initContainers: + {{- if .Values.secondary.initContainers }} + {{- include "common.tplvalues.render" (dict "value" .Values.secondary.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- if and .Values.secondary.podSecurityContext.enabled .Values.volumePermissions.enabled .Values.secondary.persistence.enabled }} + - name: volume-permissions + image: {{ include "mysql.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: + - /bin/bash + - -ec + - | + chown -R {{ .Values.secondary.containerSecurityContext.runAsUser }}:{{ .Values.secondary.podSecurityContext.fsGroup }} /bitnami/mysql + securityContext: + runAsUser: 0 + {{- if .Values.volumePermissions.resources }} + resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: data + mountPath: /bitnami/mysql + {{- end }} + {{- end }} + containers: + - name: mysql + image: {{ include "mysql.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.secondary.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.secondary.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.secondary.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.secondary.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.secondary.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.secondary.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} + - name: MYSQL_REPLICATION_MODE + value: "slave" + - name: MYSQL_MASTER_HOST + value: {{ include "mysql.primary.fullname" . }} + - name: MYSQL_MASTER_PORT_NUMBER + value: {{ .Values.primary.service.port | quote }} + - name: MYSQL_MASTER_ROOT_USER + value: "root" + - name: MYSQL_REPLICATION_USER + value: {{ .Values.auth.replicationUser | quote }} + {{- if .Values.auth.usePasswordFiles }} + - name: MYSQL_MASTER_ROOT_PASSWORD_FILE + value: {{ default "/opt/bitnami/mysql/secrets/mysql-root-password" .Values.auth.customPasswordFiles.root }} + - name: MYSQL_REPLICATION_PASSWORD_FILE + value: {{ default "/opt/bitnami/mysql/secrets/mysql-replication-password" .Values.auth.customPasswordFiles.replicator }} + {{- else }} + - name: MYSQL_MASTER_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "mysql.secretName" . }} + key: mysql-root-password + - name: MYSQL_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "mysql.secretName" . }} + key: mysql-replication-password + {{- end }} + {{- if .Values.secondary.extraFlags }} + - name: MYSQL_EXTRA_FLAGS + value: "{{ .Values.secondary.extraFlags }}" + {{- end }} + {{- if .Values.secondary.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.secondary.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.secondary.extraEnvVarsCM .Values.secondary.extraEnvVarsSecret }} + envFrom: + {{- if .Values.secondary.extraEnvVarsCM }} + - configMapRef: + name: {{ .Values.secondary.extraEnvVarsCM }} + {{- end }} + {{- if .Values.secondary.extraEnvVarsSecret }} + - secretRef: + name: {{ .Values.secondary.extraEnvVarsSecret }} + {{- end }} + {{- end }} + ports: + - name: mysql + containerPort: 3306 + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.secondary.livenessProbe.enabled }} + livenessProbe: {{- omit .Values.secondary.livenessProbe "enabled" | toYaml | nindent 12 }} + exec: + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_MASTER_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_MASTER_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_MASTER_ROOT_PASSWORD_FILE") + fi + mysqladmin status -uroot -p"${password_aux}" + {{- else if .Values.secondary.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.secondary.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.secondary.readinessProbe.enabled }} + readinessProbe: {{- omit .Values.secondary.readinessProbe "enabled" | toYaml | nindent 12 }} + exec: + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_MASTER_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_MASTER_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_MASTER_ROOT_PASSWORD_FILE") + fi + mysqladmin status -uroot -p"${password_aux}" + {{- else if .Values.secondary.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.secondary.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.secondary.startupProbe.enabled }} + startupProbe: {{- omit .Values.secondary.startupProbe "enabled" | toYaml | nindent 12 }} + exec: + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_MASTER_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_MASTER_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_MASTER_ROOT_PASSWORD_FILE") + fi + mysqladmin status -uroot -p"${password_aux}" + {{- else if .Values.secondary.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.secondary.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.secondary.resources }} + resources: {{ toYaml .Values.secondary.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: data + mountPath: /bitnami/mysql + {{- if or .Values.secondary.configuration .Values.secondary.existingConfigmap }} + - name: config + mountPath: /opt/bitnami/mysql/conf/my.cnf + subPath: my.cnf + {{- end }} + {{- if and .Values.auth.usePasswordFiles (not .Values.auth.customPasswordFiles) }} + - name: mysql-credentials + mountPath: /opt/bitnami/mysql/secrets/ + {{- end }} + {{- if .Values.secondary.extraVolumeMounts }} + {{- include "common.tplvalues.render" (dict "value" .Values.secondary.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ include "mysql.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + env: + {{- if .Values.auth.usePasswordFiles }} + - name: MYSQL_ROOT_PASSWORD_FILE + value: {{ default "/opt/bitnami/mysqld-exporter/secrets/mysql-root-password" .Values.auth.customPasswordFiles.root }} + {{- else }} + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "mysql.secretName" . }} + key: mysql-root-password + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else }} + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE") + fi + DATA_SOURCE_NAME="root:${password_aux}@(localhost:3306)/" /bin/mysqld_exporter {{- range .Values.metrics.extraArgs.secondary }} {{ . }} {{- end }} + {{- end }} + ports: + - name: metrics + containerPort: 9104 + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.metrics.livenessProbe.enabled }} + livenessProbe: {{- omit .Values.metrics.livenessProbe "enabled" | toYaml | nindent 12 }} + httpGet: + path: /metrics + port: metrics + {{- end }} + {{- if .Values.metrics.readinessProbe.enabled }} + readinessProbe: {{- omit .Values.metrics.readinessProbe "enabled" | toYaml | nindent 12 }} + httpGet: + path: /metrics + port: metrics + {{- end }} + {{- end }} + {{- if .Values.metrics.resources }} + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} + {{- if and .Values.auth.usePasswordFiles (not .Values.auth.customPasswordFiles) }} + volumeMounts: + - name: mysql-credentials + mountPath: /opt/bitnami/mysqld-exporter/secrets/ + {{- end }} + {{- end }} + {{- if .Values.secondary.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.secondary.sidecars "context" $) | nindent 8 }} + {{- end }} + volumes: + {{- if or .Values.secondary.configuration .Values.secondary.existingConfigmap }} + - name: config + configMap: + name: {{ include "mysql.secondary.configmapName" . }} + {{- end }} + {{- if and .Values.auth.usePasswordFiles (not .Values.auth.customPasswordFiles) }} + - name: mysql-credentials + secret: + secretName: {{ template "mysql.secretName" . }} + items: + - key: mysql-root-password + path: mysql-root-password + - key: mysql-replication-password + path: mysql-replication-password + {{- end }} + {{- if .Values.secondary.extraVolumes }} + {{- include "common.tplvalues.render" (dict "value" .Values.secondary.extraVolumes "context" $) | nindent 8 }} + {{- end }} + {{- if not .Values.secondary.persistence.enabled }} + - name: data + emptyDir: {} + {{- else }} + volumeClaimTemplates: + - metadata: + name: data + labels: {{ include "common.labels.matchLabels" . | nindent 10 }} + app.kubernetes.io/component: secondary + {{- if .Values.secondary.persistence.annotations }} + annotations: + {{- toYaml .Values.secondary.persistence.annotations | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range .Values.secondary.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.secondary.persistence.size | quote }} + {{ include "common.storage.class" (dict "persistence" .Values.secondary.persistence "global" .Values.global) }} + {{- if .Values.secondary.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.secondary.persistence.selector "context" $) | nindent 10 }} + {{- end -}} + {{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/secondary/svc-headless.yaml b/pkg/iac/scanners/helm/test/mysql/templates/secondary/svc-headless.yaml new file mode 100644 index 000000000000..703d8e747b75 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/secondary/svc-headless.yaml @@ -0,0 +1,26 @@ +{{- if eq .Values.architecture "replication" }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mysql.secondary.fullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: secondary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + publishNotReadyAddresses: true + ports: + - name: mysql + port: {{ .Values.secondary.service.port }} + targetPort: mysql + selector: {{ include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: secondary +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/secondary/svc.yaml b/pkg/iac/scanners/helm/test/mysql/templates/secondary/svc.yaml new file mode 100644 index 000000000000..74a4c6ef5fb8 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/secondary/svc.yaml @@ -0,0 +1,43 @@ +{{- if eq .Values.architecture "replication" }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mysql.secondary.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: secondary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.secondary.service.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.secondary.service.annotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.secondary.service.type }} + {{- if and (eq .Values.secondary.service.type "ClusterIP") .Values.secondary.service.clusterIP }} + clusterIP: {{ .Values.secondary.service.clusterIP }} + {{- end }} + {{- if and .Values.secondary.service.loadBalancerIP (eq .Values.secondary.service.type "LoadBalancer") }} + loadBalancerIP: {{ .Values.secondary.service.loadBalancerIP }} + externalTrafficPolicy: {{ .Values.secondary.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if and (eq .Values.secondary.service.type "LoadBalancer") .Values.secondary.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- toYaml .Values.secondary.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} + ports: + - name: mysql + port: {{ .Values.secondary.service.port }} + protocol: TCP + targetPort: mysql + {{- if (and (or (eq .Values.secondary.service.type "NodePort") (eq .Values.secondary.service.type "LoadBalancer")) .Values.secondary.service.nodePort) }} + nodePort: {{ .Values.secondary.service.nodePort }} + {{- else if eq .Values.secondary.service.type "ClusterIP" }} + nodePort: null + {{- end }} + selector: {{ include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: secondary +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/secrets.yaml b/pkg/iac/scanners/helm/test/mysql/templates/secrets.yaml new file mode 100644 index 000000000000..9412fc35a5bc --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/secrets.yaml @@ -0,0 +1,21 @@ +{{- if eq (include "mysql.createSecret" .) "true" }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + mysql-root-password: {{ include "mysql.root.password" . | b64enc | quote }} + mysql-password: {{ include "mysql.password" . | b64enc | quote }} + {{- if eq .Values.architecture "replication" }} + mysql-replication-password: {{ include "mysql.replication.password" . | b64enc | quote }} + {{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/serviceaccount.yaml b/pkg/iac/scanners/helm/test/mysql/templates/serviceaccount.yaml new file mode 100644 index 000000000000..59eb10409d91 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/serviceaccount.yaml @@ -0,0 +1,22 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "mysql.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.serviceAccount.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.serviceAccount.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +{{- if (not .Values.auth.customPasswordFiles) }} +secrets: + - name: {{ template "mysql.secretName" . }} +{{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/templates/servicemonitor.yaml b/pkg/iac/scanners/helm/test/mysql/templates/servicemonitor.yaml new file mode 100644 index 000000000000..f082dd5409d6 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/templates/servicemonitor.yaml @@ -0,0 +1,42 @@ +{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "common.names.fullname" . }} + {{- if .Values.metrics.serviceMonitor.namespace }} + namespace: {{ .Values.metrics.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.additionalLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.serviceMonitor.additionalLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: metrics + {{- if .Values.metrics.serviceMonitor.interval }} + interval: {{ .Values.metrics.serviceMonitor.interval }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.honorLabels }} + honorLabels: {{ .Values.metrics.serviceMonitor.honorLabels }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.relabellings }} + metricRelabelings: {{- toYaml .Values.metrics.serviceMonitor.relabellings | nindent 6 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: metrics +{{- end }} diff --git a/pkg/iac/scanners/helm/test/mysql/values.schema.json b/pkg/iac/scanners/helm/test/mysql/values.schema.json new file mode 100644 index 000000000000..8021a4603600 --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/values.schema.json @@ -0,0 +1,178 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "architecture": { + "type": "string", + "title": "MySQL architecture", + "form": true, + "description": "Allowed values: `standalone` or `replication`", + "enum": ["standalone", "replication"] + }, + "auth": { + "type": "object", + "title": "Authentication configuration", + "form": true, + "required": ["database", "username", "password"], + "properties": { + "rootPassword": { + "type": "string", + "title": "MySQL root password", + "description": "Defaults to a random 10-character alphanumeric string if not set" + }, + "database": { + "type": "string", + "title": "MySQL custom database name" + }, + "username": { + "type": "string", + "title": "MySQL custom username" + }, + "password": { + "type": "string", + "title": "MySQL custom password" + }, + "replicationUser": { + "type": "string", + "title": "MySQL replication username" + }, + "replicationPassword": { + "type": "string", + "title": "MySQL replication password" + } + } + }, + "primary": { + "type": "object", + "title": "Primary database configuration", + "form": true, + "properties": { + "podSecurityContext": { + "type": "object", + "title": "MySQL primary Pod security context", + "properties": { + "enabled": { + "type": "boolean", + "default": false + }, + "fsGroup": { + "type": "integer", + "default": 1001, + "hidden": { + "value": false, + "path": "primary/podSecurityContext/enabled" + } + } + } + }, + "containerSecurityContext": { + "type": "object", + "title": "MySQL primary container security context", + "properties": { + "enabled": { + "type": "boolean", + "default": false + }, + "runAsUser": { + "type": "integer", + "default": 1001, + "hidden": { + "value": false, + "path": "primary/containerSecurityContext/enabled" + } + } + } + }, + "persistence": { + "type": "object", + "title": "Enable persistence using Persistent Volume Claims", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "title": "If true, use a Persistent Volume Claim, If false, use emptyDir" + }, + "size": { + "type": "string", + "title": "Persistent Volume Size", + "form": true, + "render": "slider", + "sliderMin": 1, + "sliderUnit": "Gi", + "hidden": { + "value": false, + "path": "primary/persistence/enabled" + } + } + } + } + } + }, + "secondary": { + "type": "object", + "title": "Secondary database configuration", + "form": true, + "properties": { + "podSecurityContext": { + "type": "object", + "title": "MySQL secondary Pod security context", + "properties": { + "enabled": { + "type": "boolean", + "default": false + }, + "fsGroup": { + "type": "integer", + "default": 1001, + "hidden": { + "value": false, + "path": "secondary/podSecurityContext/enabled" + } + } + } + }, + "containerSecurityContext": { + "type": "object", + "title": "MySQL secondary container security context", + "properties": { + "enabled": { + "type": "boolean", + "default": false + }, + "runAsUser": { + "type": "integer", + "default": 1001, + "hidden": { + "value": false, + "path": "secondary/containerSecurityContext/enabled" + } + } + } + }, + "persistence": { + "type": "object", + "title": "Enable persistence using Persistent Volume Claims", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "title": "If true, use a Persistent Volume Claim, If false, use emptyDir" + }, + "size": { + "type": "string", + "title": "Persistent Volume Size", + "form": true, + "render": "slider", + "sliderMin": 1, + "sliderUnit": "Gi", + "hidden": { + "value": false, + "path": "secondary/persistence/enabled" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/mysql/values.yaml b/pkg/iac/scanners/helm/test/mysql/values.yaml new file mode 100644 index 000000000000..3900e865955c --- /dev/null +++ b/pkg/iac/scanners/helm/test/mysql/values.yaml @@ -0,0 +1,1020 @@ +## @section Global parameters +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry, imagePullSecrets and storageClass + +## @param global.imageRegistry Global Docker image registry +## @param global.imagePullSecrets [array] Global Docker registry secret names as an array +## @param global.storageClass Global StorageClass for Persistent Volume(s) +## +global: + imageRegistry: "" + ## E.g. + ## imagePullSecrets: + ## - myRegistryKeySecretName + ## + imagePullSecrets: [] + storageClass: "" + +## @section Common parameters + +## @param nameOverride String to partially override common.names.fullname template (will maintain the release name) +## +nameOverride: "" +## @param fullnameOverride String to fully override common.names.fullname template +## +fullnameOverride: "" +## @param clusterDomain Cluster domain +## +clusterDomain: cluster.local +## @param commonAnnotations [object] Common annotations to add to all MySQL resources (sub-charts are not considered). Evaluated as a template +## +commonAnnotations: {} +## @param commonLabels [object] Common labels to add to all MySQL resources (sub-charts are not considered). Evaluated as a template +## +commonLabels: {} +## @param extraDeploy [array] Array with extra yaml to deploy with the chart. Evaluated as a template +## +extraDeploy: [] +## @param schedulerName Use an alternate scheduler, e.g. "stork". +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +schedulerName: "" + +## Enable diagnostic mode in the deployment +## +diagnosticMode: + ## @param diagnosticMode.enabled Enable diagnostic mode (all probes will be disabled and the command will be overridden) + ## + enabled: false + ## @param diagnosticMode.command Command to override all containers in the deployment + ## + command: + - sleep + ## @param diagnosticMode.args Args to override all containers in the deployment + ## + args: + - infinity + +## @section MySQL common parameters + +## Bitnami MySQL image +## ref: https://hub.docker.com/r/bitnami/mysql/tags/ +## @param image.registry MySQL image registry +## @param image.repository MySQL image repository +## @param image.tag MySQL image tag (immutable tags are recommended) +## @param image.pullPolicy MySQL image pull policy +## @param image.pullSecrets [array] Specify docker-registry secret names as an array +## @param image.debug Specify if debug logs should be enabled +## +image: + registry: docker.io + repository: bitnami/mysql + tag: 8.0.28-debian-10-r23 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## Example: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Set to true if you would like to see extra information on logs + ## It turns BASH and/or NAMI debugging in the image + ## + debug: false +## @param architecture MySQL architecture (`standalone` or `replication`) +## +architecture: standalone +## MySQL Authentication parameters +## +auth: + ## @param auth.rootPassword Password for the `root` user. Ignored if existing secret is provided + ## ref: https://github.com/bitnami/bitnami-docker-mysql#setting-the-root-password-on-first-run + ## + rootPassword: "" + ## @param auth.database Name for a custom database to create + ## ref: https://github.com/bitnami/bitnami-docker-mysql/blob/master/README.md#creating-a-database-on-first-run + ## + database: my_database + ## @param auth.username Name for a custom user to create + ## ref: https://github.com/bitnami/bitnami-docker-mysql/blob/master/README.md#creating-a-database-user-on-first-run + ## + username: "" + ## @param auth.password Password for the new user. Ignored if existing secret is provided + ## + password: "" + ## @param auth.replicationUser MySQL replication user + ## ref: https://github.com/bitnami/bitnami-docker-mysql#setting-up-a-replication-cluster + ## + replicationUser: replicator + ## @param auth.replicationPassword MySQL replication user password. Ignored if existing secret is provided + ## + replicationPassword: "" + ## @param auth.existingSecret Use existing secret for password details. The secret has to contain the keys `mysql-root-password`, `mysql-replication-password` and `mysql-password` + ## NOTE: When it's set the auth.rootPassword, auth.password, auth.replicationPassword are ignored. + ## + existingSecret: "" + ## @param auth.forcePassword Force users to specify required passwords + ## + forcePassword: false + ## @param auth.usePasswordFiles Mount credentials as files instead of using an environment variable + ## + usePasswordFiles: false + ## @param auth.customPasswordFiles [object] Use custom password files when `auth.usePasswordFiles` is set to `true`. Define path for keys `root` and `user`, also define `replicator` if `architecture` is set to `replication` + ## Example: + ## customPasswordFiles: + ## root: /vault/secrets/mysql-root + ## user: /vault/secrets/mysql-user + ## replicator: /vault/secrets/mysql-replicator + ## + customPasswordFiles: {} +## @param initdbScripts [object] Dictionary of initdb scripts +## Specify dictionary of scripts to be run at first boot +## Example: +## initdbScripts: +## my_init_script.sh: | +## #!/bin/bash +## echo "Do something." +## +initdbScripts: {} +## @param initdbScriptsConfigMap ConfigMap with the initdb scripts (Note: Overrides `initdbScripts`) +## +initdbScriptsConfigMap: "" + +## @section MySQL Primary parameters + +primary: + ## @param primary.command [array] Override default container command on MySQL Primary container(s) (useful when using custom images) + ## + command: [] + ## @param primary.args [array] Override default container args on MySQL Primary container(s) (useful when using custom images) + ## + args: [] + ## @param primary.hostAliases [array] Deployment pod host aliases + ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ + ## + hostAliases: [] + ## @param primary.configuration [string] Configure MySQL Primary with a custom my.cnf file + ## ref: https://mysql.com/kb/en/mysql/configuring-mysql-with-mycnf/#example-of-configuration-file + ## + configuration: |- + [mysqld] + default_authentication_plugin=mysql_native_password + skip-name-resolve + explicit_defaults_for_timestamp + basedir=/opt/bitnami/mysql + plugin_dir=/opt/bitnami/mysql/lib/plugin + port=3306 + socket=/opt/bitnami/mysql/tmp/mysql.sock + datadir=/bitnami/mysql/data + tmpdir=/opt/bitnami/mysql/tmp + max_allowed_packet=16M + bind-address=0.0.0.0 + pid-file=/opt/bitnami/mysql/tmp/mysqld.pid + log-error=/opt/bitnami/mysql/logs/mysqld.log + character-set-server=UTF8 + collation-server=utf8_general_ci + + [client] + port=3306 + socket=/opt/bitnami/mysql/tmp/mysql.sock + default-character-set=UTF8 + plugin_dir=/opt/bitnami/mysql/lib/plugin + + [manager] + port=3306 + socket=/opt/bitnami/mysql/tmp/mysql.sock + pid-file=/opt/bitnami/mysql/tmp/mysqld.pid + ## @param primary.existingConfigmap Name of existing ConfigMap with MySQL Primary configuration. + ## NOTE: When it's set the 'configuration' parameter is ignored + ## + existingConfigmap: "" + ## @param primary.updateStrategy Update strategy type for the MySQL primary statefulset + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies + ## + updateStrategy: RollingUpdate + ## @param primary.rollingUpdatePartition Partition update strategy for MySQL Primary statefulset + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + ## + rollingUpdatePartition: "" + ## @param primary.podAnnotations [object] Additional pod annotations for MySQL primary pods + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: {} + ## @param primary.podAffinityPreset MySQL primary pod affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAffinityPreset: "" + ## @param primary.podAntiAffinityPreset MySQL primary pod anti-affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAntiAffinityPreset: soft + ## MySQL Primary node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## + nodeAffinityPreset: + ## @param primary.nodeAffinityPreset.type MySQL primary node affinity preset type. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param primary.nodeAffinityPreset.key MySQL primary node label key to match Ignored if `primary.affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## @param primary.nodeAffinityPreset.values [array] MySQL primary node label values to match. Ignored if `primary.affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + ## @param primary.affinity [object] Affinity for MySQL primary pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: podAffinityPreset, podAntiAffinityPreset, and nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + ## @param primary.nodeSelector [object] Node labels for MySQL primary pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param primary.tolerations [array] Tolerations for MySQL primary pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## MySQL primary Pod security context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param primary.podSecurityContext.enabled Enable security context for MySQL primary pods + ## @param primary.podSecurityContext.fsGroup Group ID for the mounted volumes' filesystem + ## + podSecurityContext: + enabled: true + fsGroup: 1001 + ## MySQL primary container security context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param primary.containerSecurityContext.enabled MySQL primary container securityContext + ## @param primary.containerSecurityContext.runAsUser User ID for the MySQL primary container + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + ## MySQL primary container's resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## We usually recommend not to specify default resources and to leave this as a conscious + ## choice for the user. This also increases chances charts run on environments with little + ## resources, such as Minikube. If you do want to specify resources, uncomment the following + ## lines, adjust them as necessary, and remove the curly braces after 'resources:'. + ## @param primary.resources.limits [object] The resources limits for MySQL primary containers + ## @param primary.resources.requests [object] The requested resources for MySQL primary containers + ## + resources: + ## Example: + ## limits: + ## cpu: 250m + ## memory: 256Mi + limits: {} + ## Examples: + ## requests: + ## cpu: 250m + ## memory: 256Mi + requests: {} + ## Configure extra options for liveness probe + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param primary.livenessProbe.enabled Enable livenessProbe + ## @param primary.livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe + ## @param primary.livenessProbe.periodSeconds Period seconds for livenessProbe + ## @param primary.livenessProbe.timeoutSeconds Timeout seconds for livenessProbe + ## @param primary.livenessProbe.failureThreshold Failure threshold for livenessProbe + ## @param primary.livenessProbe.successThreshold Success threshold for livenessProbe + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 + ## Configure extra options for readiness probe + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param primary.readinessProbe.enabled Enable readinessProbe + ## @param primary.readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe + ## @param primary.readinessProbe.periodSeconds Period seconds for readinessProbe + ## @param primary.readinessProbe.timeoutSeconds Timeout seconds for readinessProbe + ## @param primary.readinessProbe.failureThreshold Failure threshold for readinessProbe + ## @param primary.readinessProbe.successThreshold Success threshold for readinessProbe + ## + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 + ## Configure extra options for startupProbe probe + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param primary.startupProbe.enabled Enable startupProbe + ## @param primary.startupProbe.initialDelaySeconds Initial delay seconds for startupProbe + ## @param primary.startupProbe.periodSeconds Period seconds for startupProbe + ## @param primary.startupProbe.timeoutSeconds Timeout seconds for startupProbe + ## @param primary.startupProbe.failureThreshold Failure threshold for startupProbe + ## @param primary.startupProbe.successThreshold Success threshold for startupProbe + ## + startupProbe: + enabled: true + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 10 + successThreshold: 1 + ## @param primary.customLivenessProbe [object] Override default liveness probe for MySQL primary containers + ## + customLivenessProbe: {} + ## @param primary.customReadinessProbe [object] Override default readiness probe for MySQL primary containers + ## + customReadinessProbe: {} + ## @param primary.customStartupProbe [object] Override default startup probe for MySQL primary containers + ## + customStartupProbe: {} + ## @param primary.extraFlags MySQL primary additional command line flags + ## Can be used to specify command line flags, for example: + ## E.g. + ## extraFlags: "--max-connect-errors=1000 --max_connections=155" + ## + extraFlags: "" + ## @param primary.extraEnvVars [array] Extra environment variables to be set on MySQL primary containers + ## E.g. + ## extraEnvVars: + ## - name: TZ + ## value: "Europe/Paris" + ## + extraEnvVars: [] + ## @param primary.extraEnvVarsCM Name of existing ConfigMap containing extra env vars for MySQL primary containers + ## + extraEnvVarsCM: "" + ## @param primary.extraEnvVarsSecret Name of existing Secret containing extra env vars for MySQL primary containers + ## + extraEnvVarsSecret: "" + ## Enable persistence using Persistent Volume Claims + ## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + ## @param primary.persistence.enabled Enable persistence on MySQL primary replicas using a `PersistentVolumeClaim`. If false, use emptyDir + ## + enabled: true + ## @param primary.persistence.existingClaim Name of an existing `PersistentVolumeClaim` for MySQL primary replicas + ## NOTE: When it's set the rest of persistence parameters are ignored + ## + existingClaim: "" + ## @param primary.persistence.storageClass MySQL primary persistent volume storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + storageClass: "" + ## @param primary.persistence.annotations [object] MySQL primary persistent volume claim annotations + ## + annotations: {} + ## @param primary.persistence.accessModes MySQL primary persistent volume access Modes + ## + accessModes: + - ReadWriteOnce + ## @param primary.persistence.size MySQL primary persistent volume size + ## + size: 8Gi + ## @param primary.persistence.selector [object] Selector to match an existing Persistent Volume + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} + ## @param primary.extraVolumes [array] Optionally specify extra list of additional volumes to the MySQL Primary pod(s) + ## + extraVolumes: [] + ## @param primary.extraVolumeMounts [array] Optionally specify extra list of additional volumeMounts for the MySQL Primary container(s) + ## + extraVolumeMounts: [] + ## @param primary.initContainers [array] Add additional init containers for the MySQL Primary pod(s) + ## + initContainers: [] + ## @param primary.sidecars [array] Add additional sidecar containers for the MySQL Primary pod(s) + ## + sidecars: [] + ## MySQL Primary Service parameters + ## + service: + ## @param primary.service.type MySQL Primary K8s service type + ## + type: ClusterIP + ## @param primary.service.port MySQL Primary K8s service port + ## + port: 3306 + ## @param primary.service.nodePort MySQL Primary K8s service node port + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + nodePort: "" + ## @param primary.service.clusterIP MySQL Primary K8s service clusterIP IP + ## e.g: + ## clusterIP: None + ## + clusterIP: "" + ## @param primary.service.loadBalancerIP MySQL Primary loadBalancerIP if service type is `LoadBalancer` + ## Set the LoadBalancer service type to internal only + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + loadBalancerIP: "" + ## @param primary.service.externalTrafficPolicy Enable client source IP preservation + ## ref https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param primary.service.loadBalancerSourceRanges [array] Addresses that are allowed when MySQL Primary service is LoadBalancer + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## E.g. + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param primary.service.annotations [object] Provide any additional annotations which may be required + ## + annotations: {} + ## MySQL primary Pod Disruption Budget configuration + ## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ + ## + pdb: + ## @param primary.pdb.enabled Enable/disable a Pod Disruption Budget creation for MySQL primary pods + ## + enabled: false + ## @param primary.pdb.minAvailable Minimum number/percentage of MySQL primary pods that should remain scheduled + ## + minAvailable: 1 + ## @param primary.pdb.maxUnavailable Maximum number/percentage of MySQL primary pods that may be made unavailable + ## + maxUnavailable: "" + ## @param primary.podLabels [object] MySQL Primary pod label. If labels are same as commonLabels , this will take precedence + ## + podLabels: {} + +## @section MySQL Secondary parameters + +secondary: + ## @param secondary.replicaCount Number of MySQL secondary replicas + ## + replicaCount: 1 + ## @param secondary.hostAliases [array] Deployment pod host aliases + ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ + ## + hostAliases: [] + ## @param secondary.command [array] Override default container command on MySQL Secondary container(s) (useful when using custom images) + ## + command: [] + ## @param secondary.args [array] Override default container args on MySQL Secondary container(s) (useful when using custom images) + ## + args: [] + ## @param secondary.configuration [string] Configure MySQL Secondary with a custom my.cnf file + ## ref: https://mysql.com/kb/en/mysql/configuring-mysql-with-mycnf/#example-of-configuration-file + ## + configuration: |- + [mysqld] + default_authentication_plugin=mysql_native_password + skip-name-resolve + explicit_defaults_for_timestamp + basedir=/opt/bitnami/mysql + port=3306 + socket=/opt/bitnami/mysql/tmp/mysql.sock + datadir=/bitnami/mysql/data + tmpdir=/opt/bitnami/mysql/tmp + max_allowed_packet=16M + bind-address=0.0.0.0 + pid-file=/opt/bitnami/mysql/tmp/mysqld.pid + log-error=/opt/bitnami/mysql/logs/mysqld.log + character-set-server=UTF8 + collation-server=utf8_general_ci + + [client] + port=3306 + socket=/opt/bitnami/mysql/tmp/mysql.sock + default-character-set=UTF8 + + [manager] + port=3306 + socket=/opt/bitnami/mysql/tmp/mysql.sock + pid-file=/opt/bitnami/mysql/tmp/mysqld.pid + ## @param secondary.existingConfigmap Name of existing ConfigMap with MySQL Secondary configuration. + ## NOTE: When it's set the 'configuration' parameter is ignored + ## + existingConfigmap: "" + ## @param secondary.updateStrategy Update strategy type for the MySQL secondary statefulset + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies + ## + updateStrategy: RollingUpdate + ## @param secondary.rollingUpdatePartition Partition update strategy for MySQL Secondary statefulset + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + ## + rollingUpdatePartition: "" + ## @param secondary.podAnnotations [object] Additional pod annotations for MySQL secondary pods + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: {} + ## @param secondary.podAffinityPreset MySQL secondary pod affinity preset. Ignored if `secondary.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAffinityPreset: "" + ## @param secondary.podAntiAffinityPreset MySQL secondary pod anti-affinity preset. Ignored if `secondary.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAntiAffinityPreset: soft + ## MySQL Secondary node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## + nodeAffinityPreset: + ## @param secondary.nodeAffinityPreset.type MySQL secondary node affinity preset type. Ignored if `secondary.affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param secondary.nodeAffinityPreset.key MySQL secondary node label key to match Ignored if `secondary.affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## @param secondary.nodeAffinityPreset.values [array] MySQL secondary node label values to match. Ignored if `secondary.affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + ## @param secondary.affinity [object] Affinity for MySQL secondary pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: podAffinityPreset, podAntiAffinityPreset, and nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + ## @param secondary.nodeSelector [object] Node labels for MySQL secondary pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param secondary.tolerations [array] Tolerations for MySQL secondary pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## MySQL secondary Pod security context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param secondary.podSecurityContext.enabled Enable security context for MySQL secondary pods + ## @param secondary.podSecurityContext.fsGroup Group ID for the mounted volumes' filesystem + ## + podSecurityContext: + enabled: true + fsGroup: 1001 + ## MySQL secondary container security context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param secondary.containerSecurityContext.enabled MySQL secondary container securityContext + ## @param secondary.containerSecurityContext.runAsUser User ID for the MySQL secondary container + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + ## MySQL secondary container's resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## We usually recommend not to specify default resources and to leave this as a conscious + ## choice for the user. This also increases chances charts run on environments with little + ## resources, such as Minikube. If you do want to specify resources, uncomment the following + ## lines, adjust them as necessary, and remove the curly braces after 'resources:'. + ## @param secondary.resources.limits [object] The resources limits for MySQL secondary containers + ## @param secondary.resources.requests [object] The requested resources for MySQL secondary containers + ## + resources: + ## Example: + ## limits: + ## cpu: 250m + ## memory: 256Mi + limits: {} + ## Examples: + ## requests: + ## cpu: 250m + ## memory: 256Mi + requests: {} + ## Configure extra options for liveness probe + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param secondary.livenessProbe.enabled Enable livenessProbe + ## @param secondary.livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe + ## @param secondary.livenessProbe.periodSeconds Period seconds for livenessProbe + ## @param secondary.livenessProbe.timeoutSeconds Timeout seconds for livenessProbe + ## @param secondary.livenessProbe.failureThreshold Failure threshold for livenessProbe + ## @param secondary.livenessProbe.successThreshold Success threshold for livenessProbe + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 + ## Configure extra options for readiness probe + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param secondary.readinessProbe.enabled Enable readinessProbe + ## @param secondary.readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe + ## @param secondary.readinessProbe.periodSeconds Period seconds for readinessProbe + ## @param secondary.readinessProbe.timeoutSeconds Timeout seconds for readinessProbe + ## @param secondary.readinessProbe.failureThreshold Failure threshold for readinessProbe + ## @param secondary.readinessProbe.successThreshold Success threshold for readinessProbe + ## + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 + ## Configure extra options for startupProbe probe + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param secondary.startupProbe.enabled Enable startupProbe + ## @param secondary.startupProbe.initialDelaySeconds Initial delay seconds for startupProbe + ## @param secondary.startupProbe.periodSeconds Period seconds for startupProbe + ## @param secondary.startupProbe.timeoutSeconds Timeout seconds for startupProbe + ## @param secondary.startupProbe.failureThreshold Failure threshold for startupProbe + ## @param secondary.startupProbe.successThreshold Success threshold for startupProbe + ## + startupProbe: + enabled: true + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 15 + successThreshold: 1 + ## @param secondary.customLivenessProbe [object] Override default liveness probe for MySQL secondary containers + ## + customLivenessProbe: {} + ## @param secondary.customReadinessProbe [object] Override default readiness probe for MySQL secondary containers + ## + customReadinessProbe: {} + ## @param secondary.customStartupProbe [object] Override default startup probe for MySQL secondary containers + ## + customStartupProbe: {} + ## @param secondary.extraFlags MySQL secondary additional command line flags + ## Can be used to specify command line flags, for example: + ## E.g. + ## extraFlags: "--max-connect-errors=1000 --max_connections=155" + ## + extraFlags: "" + ## @param secondary.extraEnvVars [array] An array to add extra environment variables on MySQL secondary containers + ## E.g. + ## extraEnvVars: + ## - name: TZ + ## value: "Europe/Paris" + ## + extraEnvVars: [] + ## @param secondary.extraEnvVarsCM Name of existing ConfigMap containing extra env vars for MySQL secondary containers + ## + extraEnvVarsCM: "" + ## @param secondary.extraEnvVarsSecret Name of existing Secret containing extra env vars for MySQL secondary containers + ## + extraEnvVarsSecret: "" + ## Enable persistence using Persistent Volume Claims + ## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + ## @param secondary.persistence.enabled Enable persistence on MySQL secondary replicas using a `PersistentVolumeClaim` + ## + enabled: true + ## @param secondary.persistence.storageClass MySQL secondary persistent volume storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + storageClass: "" + ## @param secondary.persistence.annotations [object] MySQL secondary persistent volume claim annotations + ## + annotations: {} + ## @param secondary.persistence.accessModes MySQL secondary persistent volume access Modes + ## + accessModes: + - ReadWriteOnce + ## @param secondary.persistence.size MySQL secondary persistent volume size + ## + size: 8Gi + ## @param secondary.persistence.selector [object] Selector to match an existing Persistent Volume + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} + ## @param secondary.extraVolumes [array] Optionally specify extra list of additional volumes to the MySQL secondary pod(s) + ## + extraVolumes: [] + ## @param secondary.extraVolumeMounts [array] Optionally specify extra list of additional volumeMounts for the MySQL secondary container(s) + ## + extraVolumeMounts: [] + ## @param secondary.initContainers [array] Add additional init containers for the MySQL secondary pod(s) + ## + initContainers: [] + ## @param secondary.sidecars [array] Add additional sidecar containers for the MySQL secondary pod(s) + ## + sidecars: [] + ## MySQL Secondary Service parameters + ## + service: + ## @param secondary.service.type MySQL secondary Kubernetes service type + ## + type: ClusterIP + ## @param secondary.service.port MySQL secondary Kubernetes service port + ## + port: 3306 + ## @param secondary.service.nodePort MySQL secondary Kubernetes service node port + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + nodePort: "" + ## @param secondary.service.clusterIP MySQL secondary Kubernetes service clusterIP IP + ## e.g: + ## clusterIP: None + ## + clusterIP: "" + ## @param secondary.service.loadBalancerIP MySQL secondary loadBalancerIP if service type is `LoadBalancer` + ## Set the LoadBalancer service type to internal only + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + loadBalancerIP: "" + ## @param secondary.service.externalTrafficPolicy Enable client source IP preservation + ## ref https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param secondary.service.loadBalancerSourceRanges [array] Addresses that are allowed when MySQL secondary service is LoadBalancer + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## E.g. + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param secondary.service.annotations [object] Provide any additional annotations which may be required + ## + annotations: {} + ## MySQL secondary Pod Disruption Budget configuration + ## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ + ## + pdb: + ## @param secondary.pdb.enabled Enable/disable a Pod Disruption Budget creation for MySQL secondary pods + ## + enabled: false + ## @param secondary.pdb.minAvailable Minimum number/percentage of MySQL secondary pods that should remain scheduled + ## + minAvailable: 1 + ## @param secondary.pdb.maxUnavailable Maximum number/percentage of MySQL secondary pods that may be made unavailable + ## + maxUnavailable: "" + ## @param secondary.podLabels [object] Additional pod labels for MySQL secondary pods + ## + podLabels: {} + +## @section RBAC parameters + +## MySQL pods ServiceAccount +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ +## +serviceAccount: + ## @param serviceAccount.create Enable the creation of a ServiceAccount for MySQL pods + ## + create: true + ## @param serviceAccount.name Name of the created ServiceAccount + ## If not set and create is true, a name is generated using the mysql.fullname template + ## + name: "" + ## @param serviceAccount.annotations [object] Annotations for MySQL Service Account + ## + annotations: {} +## Role Based Access +## ref: https://kubernetes.io/docs/admin/authorization/rbac/ +## +rbac: + ## @param rbac.create Whether to create & use RBAC resources or not + ## + create: false + +## @section Network Policy + +## MySQL Nework Policy configuration +## +networkPolicy: + ## @param networkPolicy.enabled Enable creation of NetworkPolicy resources + ## + enabled: false + ## @param networkPolicy.allowExternal The Policy model to apply. + ## When set to false, only pods with the correct + ## client label will have network access to the port MySQL is listening + ## on. When true, MySQL will accept connections from any source + ## (with the correct destination port). + ## + allowExternal: true + ## @param networkPolicy.explicitNamespacesSelector [object] A Kubernetes LabelSelector to explicitly select namespaces from which ingress traffic could be allowed to MySQL + ## If explicitNamespacesSelector is missing or set to {}, only client Pods that are in the networkPolicy's namespace + ## and that match other criteria, the ones that have the good label, can reach the DB. + ## But sometimes, we want the DB to be accessible to clients from other namespaces, in this case, we can use this + ## LabelSelector to select these namespaces, note that the networkPolicy's namespace should also be explicitly added. + ## + ## Example: + ## explicitNamespacesSelector: + ## matchLabels: + ## role: frontend + ## matchExpressions: + ## - {key: role, operator: In, values: [frontend]} + ## + explicitNamespacesSelector: {} + +## @section Volume Permissions parameters + +## Init containers parameters: +## volumePermissions: Change the owner and group of the persistent volume mountpoint to runAsUser:fsGroup values from the securityContext section. +## +volumePermissions: + ## @param volumePermissions.enabled Enable init container that changes the owner and group of the persistent volume(s) mountpoint to `runAsUser:fsGroup` + ## + enabled: false + ## @param volumePermissions.image.registry Init container volume-permissions image registry + ## @param volumePermissions.image.repository Init container volume-permissions image repository + ## @param volumePermissions.image.tag Init container volume-permissions image tag (immutable tags are recommended) + ## @param volumePermissions.image.pullPolicy Init container volume-permissions image pull policy + ## @param volumePermissions.image.pullSecrets [array] Specify docker-registry secret names as an array + ## + image: + registry: docker.io + repository: bitnami/bitnami-shell + tag: 10-debian-10-r349 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## @param volumePermissions.resources [object] Init container volume-permissions resources + ## + resources: {} + +## @section Metrics parameters + +## Mysqld Prometheus exporter parameters +## +metrics: + ## @param metrics.enabled Start a side-car prometheus exporter + ## + enabled: false + ## @param metrics.image.registry Exporter image registry + ## @param metrics.image.repository Exporter image repository + ## @param metrics.image.tag Exporter image tag (immutable tags are recommended) + ## @param metrics.image.pullPolicy Exporter image pull policy + ## @param metrics.image.pullSecrets [array] Specify docker-registry secret names as an array + ## + image: + registry: docker.io + repository: bitnami/mysqld-exporter + tag: 0.13.0-debian-10-r256 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## MySQL Prometheus exporter service parameters + ## Mysqld Prometheus exporter liveness and readiness probes + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes + ## @param metrics.service.type Kubernetes service type for MySQL Prometheus Exporter + ## @param metrics.service.port MySQL Prometheus Exporter service port + ## @param metrics.service.annotations [object] Prometheus exporter service annotations + ## + service: + type: ClusterIP + port: 9104 + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.metrics.service.port }}" + ## @param metrics.extraArgs.primary [array] Extra args to be passed to mysqld_exporter on Primary pods + ## @param metrics.extraArgs.secondary [array] Extra args to be passed to mysqld_exporter on Secondary pods + ## ref: https://github.com/prometheus/mysqld_exporter/ + ## E.g. + ## - --collect.auto_increment.columns + ## - --collect.binlog_size + ## - --collect.engine_innodb_status + ## - --collect.engine_tokudb_status + ## - --collect.global_status + ## - --collect.global_variables + ## - --collect.info_schema.clientstats + ## - --collect.info_schema.innodb_metrics + ## - --collect.info_schema.innodb_tablespaces + ## - --collect.info_schema.innodb_cmp + ## - --collect.info_schema.innodb_cmpmem + ## - --collect.info_schema.processlist + ## - --collect.info_schema.processlist.min_time + ## - --collect.info_schema.query_response_time + ## - --collect.info_schema.tables + ## - --collect.info_schema.tables.databases + ## - --collect.info_schema.tablestats + ## - --collect.info_schema.userstats + ## - --collect.perf_schema.eventsstatements + ## - --collect.perf_schema.eventsstatements.digest_text_limit + ## - --collect.perf_schema.eventsstatements.limit + ## - --collect.perf_schema.eventsstatements.timelimit + ## - --collect.perf_schema.eventswaits + ## - --collect.perf_schema.file_events + ## - --collect.perf_schema.file_instances + ## - --collect.perf_schema.indexiowaits + ## - --collect.perf_schema.tableiowaits + ## - --collect.perf_schema.tablelocks + ## - --collect.perf_schema.replication_group_member_stats + ## - --collect.slave_status + ## - --collect.slave_hosts + ## - --collect.heartbeat + ## - --collect.heartbeat.database + ## - --collect.heartbeat.table + ## + extraArgs: + primary: [] + secondary: [] + ## Mysqld Prometheus exporter resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## We usually recommend not to specify default resources and to leave this as a conscious + ## choice for the user. This also increases chances charts run on environments with little + ## resources, such as Minikube. If you do want to specify resources, uncomment the following + ## lines, adjust them as necessary, and remove the curly braces after 'resources:'. + ## @param metrics.resources.limits [object] The resources limits for MySQL prometheus exporter containers + ## @param metrics.resources.requests [object] The requested resources for MySQL prometheus exporter containers + ## + resources: + ## Example: + ## limits: + ## cpu: 100m + ## memory: 256Mi + limits: {} + ## Examples: + ## requests: + ## cpu: 100m + ## memory: 256Mi + requests: {} + ## Mysqld Prometheus exporter liveness probe + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes + ## @param metrics.livenessProbe.enabled Enable livenessProbe + ## @param metrics.livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe + ## @param metrics.livenessProbe.periodSeconds Period seconds for livenessProbe + ## @param metrics.livenessProbe.timeoutSeconds Timeout seconds for livenessProbe + ## @param metrics.livenessProbe.failureThreshold Failure threshold for livenessProbe + ## @param metrics.livenessProbe.successThreshold Success threshold for livenessProbe + ## + livenessProbe: + enabled: true + initialDelaySeconds: 120 + periodSeconds: 10 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + ## Mysqld Prometheus exporter readiness probe + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes + ## @param metrics.readinessProbe.enabled Enable readinessProbe + ## @param metrics.readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe + ## @param metrics.readinessProbe.periodSeconds Period seconds for readinessProbe + ## @param metrics.readinessProbe.timeoutSeconds Timeout seconds for readinessProbe + ## @param metrics.readinessProbe.failureThreshold Failure threshold for readinessProbe + ## @param metrics.readinessProbe.successThreshold Success threshold for readinessProbe + ## + readinessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + ## Prometheus Service Monitor + ## ref: https://github.com/coreos/prometheus-operator + ## + serviceMonitor: + ## @param metrics.serviceMonitor.enabled Create ServiceMonitor Resource for scraping metrics using PrometheusOperator + ## + enabled: false + ## @param metrics.serviceMonitor.namespace Specify the namespace in which the serviceMonitor resource will be created + ## + namespace: "" + ## @param metrics.serviceMonitor.interval Specify the interval at which metrics should be scraped + ## + interval: 30s + ## @param metrics.serviceMonitor.scrapeTimeout Specify the timeout after which the scrape is ended + ## e.g: + ## scrapeTimeout: 30s + ## + scrapeTimeout: "" + ## @param metrics.serviceMonitor.relabellings [array] Specify Metric Relabellings to add to the scrape endpoint + ## + relabellings: [] + ## @param metrics.serviceMonitor.honorLabels Specify honorLabels parameter to add the scrape endpoint + ## + honorLabels: false + ## @param metrics.serviceMonitor.additionalLabels [object] Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec + ## + additionalLabels: {} diff --git a/pkg/iac/scanners/helm/test/option_test.go b/pkg/iac/scanners/helm/test/option_test.go new file mode 100644 index 000000000000..8efb03f16116 --- /dev/null +++ b/pkg/iac/scanners/helm/test/option_test.go @@ -0,0 +1,227 @@ +package test + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/helm/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +func Test_helm_parser_with_options_with_values_file(t *testing.T) { + + tests := []struct { + testName string + chartName string + valuesFile string + }{ + { + testName: "Parsing directory 'testchart'", + chartName: "testchart", + valuesFile: "values/values.yaml", + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + chartName := test.chartName + + t.Logf("Running test: %s", test.testName) + + var opts []options.ParserOption + + if test.valuesFile != "" { + opts = append(opts, parser.OptionWithValuesFile(test.valuesFile)) + } + + helmParser, err := parser.New(chartName, opts...) + require.NoError(t, err) + require.NoError(t, helmParser.ParseFS(context.TODO(), os.DirFS(filepath.Join("testdata", chartName)), ".")) + manifests, err := helmParser.RenderedChartFiles() + require.NoError(t, err) + + assert.Len(t, manifests, 3) + + for _, manifest := range manifests { + expectedPath := filepath.Join("testdata", "expected", "options", chartName, manifest.TemplateFilePath) + + expectedContent, err := os.ReadFile(expectedPath) + require.NoError(t, err) + + cleanExpected := strings.ReplaceAll(string(expectedContent), "\r\n", "\n") + cleanActual := strings.ReplaceAll(manifest.ManifestContent, "\r\n", "\n") + + assert.Equal(t, cleanExpected, cleanActual) + } + }) + } +} + +func Test_helm_parser_with_options_with_set_value(t *testing.T) { + + tests := []struct { + testName string + chartName string + valuesFile string + values string + }{ + { + testName: "Parsing directory 'testchart'", + chartName: "testchart", + values: "securityContext.runAsUser=0", + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + chartName := test.chartName + + t.Logf("Running test: %s", test.testName) + + var opts []options.ParserOption + + if test.valuesFile != "" { + opts = append(opts, parser.OptionWithValuesFile(test.valuesFile)) + } + + if test.values != "" { + opts = append(opts, parser.OptionWithValues(test.values)) + } + + helmParser, err := parser.New(chartName, opts...) + require.NoError(t, err) + err = helmParser.ParseFS(context.TODO(), os.DirFS(filepath.Join("testdata", chartName)), ".") + require.NoError(t, err) + manifests, err := helmParser.RenderedChartFiles() + require.NoError(t, err) + + assert.Len(t, manifests, 3) + + for _, manifest := range manifests { + expectedPath := filepath.Join("testdata", "expected", "options", chartName, manifest.TemplateFilePath) + + expectedContent, err := os.ReadFile(expectedPath) + require.NoError(t, err) + + cleanExpected := strings.ReplaceAll(string(expectedContent), "\r\n", "\n") + cleanActual := strings.ReplaceAll(manifest.ManifestContent, "\r\n", "\n") + + assert.Equal(t, cleanExpected, cleanActual) + } + }) + } +} + +func Test_helm_parser_with_options_with_api_versions(t *testing.T) { + + tests := []struct { + testName string + chartName string + apiVersions []string + }{ + { + testName: "Parsing directory 'with-api-version'", + chartName: "with-api-version", + apiVersions: []string{"policy/v1/PodDisruptionBudget"}, + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + chartName := test.chartName + + t.Logf("Running test: %s", test.testName) + + var opts []options.ParserOption + + if len(test.apiVersions) > 0 { + opts = append(opts, parser.OptionWithAPIVersions(test.apiVersions...)) + } + + helmParser, err := parser.New(chartName, opts...) + require.NoError(t, err) + err = helmParser.ParseFS(context.TODO(), os.DirFS(filepath.Join("testdata", chartName)), ".") + require.NoError(t, err) + manifests, err := helmParser.RenderedChartFiles() + require.NoError(t, err) + + assert.Len(t, manifests, 1) + + for _, manifest := range manifests { + expectedPath := filepath.Join("testdata", "expected", "options", chartName, manifest.TemplateFilePath) + + expectedContent, err := os.ReadFile(expectedPath) + require.NoError(t, err) + + cleanExpected := strings.TrimSpace(strings.ReplaceAll(string(expectedContent), "\r\n", "\n")) + cleanActual := strings.TrimSpace(strings.ReplaceAll(manifest.ManifestContent, "\r\n", "\n")) + + assert.Equal(t, cleanExpected, cleanActual) + } + }) + } +} + +func Test_helm_parser_with_options_with_kube_versions(t *testing.T) { + + tests := []struct { + testName string + chartName string + kubeVersion string + expectedError string + }{ + { + testName: "Parsing directory 'with-kube-version'", + chartName: "with-kube-version", + kubeVersion: "1.60", + }, + { + testName: "Parsing directory 'with-kube-version' with invalid kube version", + chartName: "with-kube-version", + kubeVersion: "a.b.c", + expectedError: "Invalid Semantic Version", + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + chartName := test.chartName + + t.Logf("Running test: %s", test.testName) + + var opts []options.ParserOption + + opts = append(opts, parser.OptionWithKubeVersion(test.kubeVersion)) + + helmParser, err := parser.New(chartName, opts...) + if test.expectedError != "" { + require.EqualError(t, err, test.expectedError) + return + } + require.NoError(t, err) + require.NoError(t, helmParser.ParseFS(context.TODO(), os.DirFS(filepath.Join("testdata", chartName)), ".")) + manifests, err := helmParser.RenderedChartFiles() + require.NoError(t, err) + + assert.Len(t, manifests, 1) + + for _, manifest := range manifests { + expectedPath := filepath.Join("testdata", "expected", "options", chartName, manifest.TemplateFilePath) + + expectedContent, err := os.ReadFile(expectedPath) + require.NoError(t, err) + + cleanExpected := strings.TrimSpace(strings.ReplaceAll(string(expectedContent), "\r\n", "\n")) + cleanActual := strings.TrimSpace(strings.ReplaceAll(manifest.ManifestContent, "\r\n", "\n")) + + assert.Equal(t, cleanExpected, cleanActual) + } + }) + } +} diff --git a/pkg/iac/scanners/helm/test/parser_test.go b/pkg/iac/scanners/helm/test/parser_test.go new file mode 100644 index 000000000000..85a69469fb5d --- /dev/null +++ b/pkg/iac/scanners/helm/test/parser_test.go @@ -0,0 +1,196 @@ +package test + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/scanners/helm/parser" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_helm_parser(t *testing.T) { + + tests := []struct { + testName string + chartName string + }{ + { + testName: "Parsing directory 'testchart'", + chartName: "testchart", + }, + { + testName: "Parsing directory with tarred dependency", + chartName: "with-tarred-dep", + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + chartName := test.chartName + helmParser, err := parser.New(chartName) + require.NoError(t, err) + require.NoError(t, helmParser.ParseFS(context.TODO(), os.DirFS("testdata"), chartName)) + manifests, err := helmParser.RenderedChartFiles() + require.NoError(t, err) + + assert.Len(t, manifests, 3) + + for _, manifest := range manifests { + expectedPath := filepath.Join("testdata", "expected", chartName, manifest.TemplateFilePath) + + expectedContent, err := os.ReadFile(expectedPath) + require.NoError(t, err) + + got := strings.ReplaceAll(manifest.ManifestContent, "\r\n", "\n") + assert.Equal(t, strings.ReplaceAll(string(expectedContent), "\r\n", "\n"), got) + } + }) + } +} + +func Test_helm_parser_where_name_non_string(t *testing.T) { + + tests := []struct { + testName string + chartName string + }{ + { + testName: "Scanning chart with integer for name", + chartName: "numberName", + }, + } + + for _, test := range tests { + chartName := test.chartName + + t.Logf("Running test: %s", test.testName) + + helmParser, err := parser.New(chartName) + require.NoError(t, err) + require.NoError(t, helmParser.ParseFS(context.TODO(), os.DirFS(filepath.Join("testdata", chartName)), ".")) + } +} + +func Test_tar_is_chart(t *testing.T) { + + tests := []struct { + testName string + archiveFile string + isHelmChart bool + }{ + { + testName: "standard tarball", + archiveFile: "mysql-8.8.26.tar", + isHelmChart: true, + }, + { + testName: "gzip tarball with tar.gz extension", + archiveFile: "mysql-8.8.26.tar.gz", + isHelmChart: true, + }, + { + testName: "broken gzip tarball with tar.gz extension", + archiveFile: "aws-cluster-autoscaler-bad.tar.gz", + isHelmChart: true, + }, + { + testName: "gzip tarball with tgz extension", + archiveFile: "mysql-8.8.26.tgz", + isHelmChart: true, + }, + { + testName: "gzip tarball that has nothing of interest in it", + archiveFile: "nope.tgz", + isHelmChart: false, + }, + } + + for _, test := range tests { + + t.Logf("Running test: %s", test.testName) + testPath := filepath.Join("testdata", test.archiveFile) + file, err := os.Open(testPath) + defer func() { _ = file.Close() }() + require.NoError(t, err) + + assert.Equal(t, test.isHelmChart, detection.IsHelmChartArchive(test.archiveFile, file)) + + _ = file.Close() + } +} + +func Test_helm_tarball_parser(t *testing.T) { + + tests := []struct { + testName string + chartName string + archiveFile string + }{ + { + testName: "standard tarball", + chartName: "mysql", + archiveFile: "mysql-8.8.26.tar", + }, + { + testName: "gzip tarball with tar.gz extension", + chartName: "mysql", + archiveFile: "mysql-8.8.26.tar.gz", + }, + { + testName: "gzip tarball with tgz extension", + chartName: "mysql", + archiveFile: "mysql-8.8.26.tgz", + }, + } + + for _, test := range tests { + + t.Logf("Running test: %s", test.testName) + + testPath := filepath.Join("testdata", test.archiveFile) + + testTemp := t.TempDir() + testFileName := filepath.Join(testTemp, test.archiveFile) + require.NoError(t, copyArchive(testPath, testFileName)) + + testFs := os.DirFS(testTemp) + + helmParser, err := parser.New(test.archiveFile) + require.NoError(t, err) + require.NoError(t, helmParser.ParseFS(context.TODO(), testFs, ".")) + + manifests, err := helmParser.RenderedChartFiles() + require.NoError(t, err) + + assert.Len(t, manifests, 6) + + oneOf := []string{ + "configmap.yaml", + "statefulset.yaml", + "svc-headless.yaml", + "svc.yaml", + "secrets.yaml", + "serviceaccount.yaml", + } + + for _, manifest := range manifests { + filename := filepath.Base(manifest.TemplateFilePath) + assert.Contains(t, oneOf, filename) + + if strings.HasSuffix(manifest.TemplateFilePath, "secrets.yaml") { + continue + } + expectedPath := filepath.Join("testdata", "expected", test.chartName, manifest.TemplateFilePath) + + expectedContent, err := os.ReadFile(expectedPath) + require.NoError(t, err) + + assert.Equal(t, strings.ReplaceAll(string(expectedContent), "\r\n", "\n"), strings.ReplaceAll(manifest.ManifestContent, "\r\n", "\n")) + } + } +} diff --git a/pkg/iac/scanners/helm/test/scanner_test.go b/pkg/iac/scanners/helm/test/scanner_test.go new file mode 100644 index 000000000000..a46031a8fb98 --- /dev/null +++ b/pkg/iac/scanners/helm/test/scanner_test.go @@ -0,0 +1,361 @@ +package test + +import ( + "context" + "io" + "os" + "path/filepath" + "runtime" + "sort" + "strings" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_helm_scanner_with_archive(t *testing.T) { + // TODO(simar7): Figure out why this test fails on Winndows only + if runtime.GOOS == "windows" { + t.Skip("skipping test on windows") + } + + tests := []struct { + testName string + chartName string + path string + archiveName string + }{ + { + testName: "Parsing tarball 'mysql-8.8.26.tar'", + chartName: "mysql", + path: filepath.Join("testdata", "mysql-8.8.26.tar"), + archiveName: "mysql-8.8.26.tar", + }, + } + + for _, test := range tests { + t.Logf("Running test: %s", test.testName) + + helmScanner := helm.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + + testTemp := t.TempDir() + testFileName := filepath.Join(testTemp, test.archiveName) + require.NoError(t, copyArchive(test.path, testFileName)) + + testFs := os.DirFS(testTemp) + results, err := helmScanner.ScanFS(context.TODO(), testFs, ".") + require.NoError(t, err) + require.NotNil(t, results) + + failed := results.GetFailed() + assert.Equal(t, 13, len(failed)) + + visited := make(map[string]bool) + var errorCodes []string + for _, result := range failed { + id := result.Flatten().RuleID + if _, exists := visited[id]; !exists { + visited[id] = true + errorCodes = append(errorCodes, id) + } + } + assert.Len(t, errorCodes, 13) + + sort.Strings(errorCodes) + + assert.Equal(t, []string{ + "AVD-KSV-0001", "AVD-KSV-0003", + "AVD-KSV-0011", "AVD-KSV-0012", "AVD-KSV-0014", + "AVD-KSV-0015", "AVD-KSV-0016", "AVD-KSV-0018", + "AVD-KSV-0020", "AVD-KSV-0021", "AVD-KSV-0030", + "AVD-KSV-0104", "AVD-KSV-0106", + }, errorCodes) + } +} + +func Test_helm_scanner_with_missing_name_can_recover(t *testing.T) { + + tests := []struct { + testName string + chartName string + path string + archiveName string + }{ + { + testName: "Parsing tarball 'aws-cluster-autoscaler-bad.tar.gz'", + chartName: "aws-cluster-autoscaler", + path: filepath.Join("testdata", "aws-cluster-autoscaler-bad.tar.gz"), + archiveName: "aws-cluster-autoscaler-bad.tar.gz", + }, + } + + for _, test := range tests { + t.Logf("Running test: %s", test.testName) + + helmScanner := helm.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + + testTemp := t.TempDir() + testFileName := filepath.Join(testTemp, test.archiveName) + require.NoError(t, copyArchive(test.path, testFileName)) + + testFs := os.DirFS(testTemp) + _, err := helmScanner.ScanFS(context.TODO(), testFs, ".") + require.NoError(t, err) + } +} + +func Test_helm_scanner_with_dir(t *testing.T) { + // TODO(simar7): Figure out why this test fails on Winndows only + if runtime.GOOS == "windows" { + t.Skip("skipping test on windows") + } + + tests := []struct { + testName string + chartName string + }{ + { + testName: "Parsing directory testchart'", + chartName: "testchart", + }, + } + + for _, test := range tests { + + t.Logf("Running test: %s", test.testName) + + helmScanner := helm.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + + testFs := os.DirFS(filepath.Join("testdata", test.chartName)) + results, err := helmScanner.ScanFS(context.TODO(), testFs, ".") + require.NoError(t, err) + require.NotNil(t, results) + + failed := results.GetFailed() + assert.Equal(t, 14, len(failed)) + + visited := make(map[string]bool) + var errorCodes []string + for _, result := range failed { + id := result.Flatten().RuleID + if _, exists := visited[id]; !exists { + visited[id] = true + errorCodes = append(errorCodes, id) + } + } + + sort.Strings(errorCodes) + + assert.Equal(t, []string{ + "AVD-KSV-0001", "AVD-KSV-0003", + "AVD-KSV-0011", "AVD-KSV-0012", "AVD-KSV-0014", + "AVD-KSV-0015", "AVD-KSV-0016", "AVD-KSV-0018", + "AVD-KSV-0020", "AVD-KSV-0021", "AVD-KSV-0030", + "AVD-KSV-0104", "AVD-KSV-0106", + "AVD-KSV-0117", + }, errorCodes) + } +} + +func Test_helm_scanner_with_custom_policies(t *testing.T) { + // TODO(simar7): Figure out why this test fails on Winndows only + if runtime.GOOS == "windows" { + t.Skip("skipping test on windows") + } + + regoRule := ` +package user.kubernetes.ID001 + + +__rego_metadata__ := { + "id": "ID001", + "avd_id": "AVD-USR-ID001", + "title": "Services not allowed", + "severity": "LOW", + "description": "Services are not allowed because of some reasons.", +} + +__rego_input__ := { + "selector": [ + {"type": "kubernetes"}, + ], +} + +deny[res] { + input.kind == "Service" + msg := sprintf("Found service '%s' but services are not allowed", [input.metadata.name]) + res := result.new(msg, input) +} +` + tests := []struct { + testName string + chartName string + path string + archiveName string + }{ + { + testName: "Parsing tarball 'mysql-8.8.26.tar'", + chartName: "mysql", + path: filepath.Join("testdata", "mysql-8.8.26.tar"), + archiveName: "mysql-8.8.26.tar", + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + t.Logf("Running test: %s", test.testName) + + helmScanner := helm.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithPolicyNamespaces("user")) + + testTemp := t.TempDir() + testFileName := filepath.Join(testTemp, test.archiveName) + require.NoError(t, copyArchive(test.path, testFileName)) + + policyDirName := filepath.Join(testTemp, "rules") + require.NoError(t, os.Mkdir(policyDirName, 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(policyDirName, "rule.rego"), []byte(regoRule), 0o600)) + + testFs := os.DirFS(testTemp) + + results, err := helmScanner.ScanFS(context.TODO(), testFs, ".") + require.NoError(t, err) + require.NotNil(t, results) + + failed := results.GetFailed() + assert.Equal(t, 15, len(failed)) + + visited := make(map[string]bool) + var errorCodes []string + for _, result := range failed { + id := result.Flatten().RuleID + if _, exists := visited[id]; !exists { + visited[id] = true + errorCodes = append(errorCodes, id) + } + } + assert.Len(t, errorCodes, 14) + + sort.Strings(errorCodes) + + assert.Equal(t, []string{ + "AVD-KSV-0001", "AVD-KSV-0003", + "AVD-KSV-0011", "AVD-KSV-0012", "AVD-KSV-0014", + "AVD-KSV-0015", "AVD-KSV-0016", "AVD-KSV-0018", + "AVD-KSV-0020", "AVD-KSV-0021", "AVD-KSV-0030", + "AVD-KSV-0104", "AVD-KSV-0106", "AVD-USR-ID001", + }, errorCodes) + }) + } +} + +func copyArchive(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer func() { _ = in.Close() }() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer func() { _ = out.Close() }() + + if _, err := io.Copy(out, in); err != nil { + return err + } + return nil +} + +func Test_helm_chart_with_templated_name(t *testing.T) { + helmScanner := helm.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + testFs := os.DirFS(filepath.Join("testdata", "templated-name")) + _, err := helmScanner.ScanFS(context.TODO(), testFs, ".") + require.NoError(t, err) +} + +func TestCodeShouldNotBeMissing(t *testing.T) { + policy := `# METADATA +# title: "Test rego" +# description: "Test rego" +# scope: package +# schemas: +# - input: schema["kubernetes"] +# custom: +# id: ID001 +# avd_id: AVD-USR-ID001 +# severity: LOW +# input: +# selector: +# - type: kubernetes +package user.kubernetes.ID001 + +deny[res] { + input.spec.replicas == 3 + res := result.new("Replicas are not allowed", input) +} +` + helmScanner := helm.New( + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithEmbeddedLibraries(false), + options.ScannerWithPolicyNamespaces("user"), + options.ScannerWithPolicyReader(strings.NewReader(policy)), + ) + + results, err := helmScanner.ScanFS(context.TODO(), os.DirFS("testdata/simmilar-templates"), ".") + require.NoError(t, err) + + failedResults := results.GetFailed() + require.Len(t, failedResults, 1) + + failed := failedResults[0] + code, err := failed.GetCode() + require.NoError(t, err) + assert.NotNil(t, code) +} + +func TestScanSubchartOnce(t *testing.T) { + check := `# METADATA +# title: "Test rego" +# description: "Test rego" +# scope: package +# schemas: +# - input: schema["kubernetes"] +# custom: +# id: ID001 +# avd_id: AVD-USR-ID001 +# severity: LOW +# input: +# selector: +# - type: kubernetes +# subtypes: +# - kind: pod +package user.kubernetes.ID001 + +import data.lib.kubernetes + +deny[res] { + container := kubernetes.containers[_] + container.securityContext.readOnlyRootFilesystem == false + res := result.new("set 'securityContext.readOnlyRootFilesystem' to true", container) +} +` + + scanner := helm.New( + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithEmbeddedLibraries(true), + options.ScannerWithPolicyNamespaces("user"), + options.ScannerWithPolicyReader(strings.NewReader(check)), + ) + + results, err := scanner.ScanFS(context.TODO(), os.DirFS("testdata/with-subchart"), ".") + require.NoError(t, err) + require.Len(t, results, 1) + + assert.Len(t, results.GetFailed(), 0) +} diff --git a/pkg/iac/scanners/helm/test/testdata/aws-cluster-autoscaler-bad.tar.gz b/pkg/iac/scanners/helm/test/testdata/aws-cluster-autoscaler-bad.tar.gz new file mode 100644 index 000000000000..a66f228c9851 Binary files /dev/null and b/pkg/iac/scanners/helm/test/testdata/aws-cluster-autoscaler-bad.tar.gz differ diff --git a/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/configmap.yaml b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/configmap.yaml new file mode 100644 index 000000000000..9ee00d2c2c0c --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/configmap.yaml @@ -0,0 +1,42 @@ +# Source: mysql/templates/primary/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: mysql + namespace: + labels: + app.kubernetes.io/name: mysql + helm.sh/chart: mysql-8.8.26 + app.kubernetes.io/instance: mysql + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: primary +data: + my.cnf: |- + + [mysqld] + default_authentication_plugin=mysql_native_password + skip-name-resolve + explicit_defaults_for_timestamp + basedir=/opt/bitnami/mysql + plugin_dir=/opt/bitnami/mysql/lib/plugin + port=3306 + socket=/opt/bitnami/mysql/tmp/mysql.sock + datadir=/bitnami/mysql/data + tmpdir=/opt/bitnami/mysql/tmp + max_allowed_packet=16M + bind-address=0.0.0.0 + pid-file=/opt/bitnami/mysql/tmp/mysqld.pid + log-error=/opt/bitnami/mysql/logs/mysqld.log + character-set-server=UTF8 + collation-server=utf8_general_ci + + [client] + port=3306 + socket=/opt/bitnami/mysql/tmp/mysql.sock + default-character-set=UTF8 + plugin_dir=/opt/bitnami/mysql/lib/plugin + + [manager] + port=3306 + socket=/opt/bitnami/mysql/tmp/mysql.sock + pid-file=/opt/bitnami/mysql/tmp/mysqld.pid \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/statefulset.yaml b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/statefulset.yaml new file mode 100644 index 000000000000..a7f5f59d831b --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/statefulset.yaml @@ -0,0 +1,147 @@ +# Source: mysql/templates/primary/statefulset.yaml +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: mysql + namespace: + labels: + app.kubernetes.io/name: mysql + helm.sh/chart: mysql-8.8.26 + app.kubernetes.io/instance: mysql + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: primary +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: mysql + app.kubernetes.io/instance: mysql + app.kubernetes.io/component: primary + serviceName: mysql + updateStrategy: + type: RollingUpdate + template: + metadata: + annotations: + checksum/configuration: 6adfba795651cd736dfa943a87e0853ce417b9fb842b57535e3b1b4e762a33fd + labels: + app.kubernetes.io/name: mysql + helm.sh/chart: mysql-8.8.26 + app.kubernetes.io/instance: mysql + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: primary + spec: + + serviceAccountName: mysql + affinity: + podAffinity: + + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/name: mysql + app.kubernetes.io/instance: mysql + app.kubernetes.io/component: primary + namespaces: + - "" + topologyKey: kubernetes.io/hostname + weight: 1 + nodeAffinity: + + securityContext: + fsGroup: 1001 + containers: + - name: mysql + image: docker.io/bitnami/mysql:8.0.28-debian-10-r23 + imagePullPolicy: "IfNotPresent" + securityContext: + runAsUser: 1001 + env: + - name: BITNAMI_DEBUG + value: "false" + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: mysql + key: mysql-root-password + - name: MYSQL_DATABASE + value: "my_database" + ports: + - name: mysql + containerPort: 3306 + livenessProbe: + failureThreshold: 3 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + exec: + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE") + fi + mysqladmin status -uroot -p"${password_aux}" + readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + exec: + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE") + fi + mysqladmin status -uroot -p"${password_aux}" + startupProbe: + failureThreshold: 10 + initialDelaySeconds: 15 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + exec: + command: + - /bin/bash + - -ec + - | + password_aux="${MYSQL_ROOT_PASSWORD:-}" + if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then + password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE") + fi + mysqladmin status -uroot -p"${password_aux}" + resources: + limits: {} + requests: {} + volumeMounts: + - name: data + mountPath: /bitnami/mysql + - name: config + mountPath: /opt/bitnami/mysql/conf/my.cnf + subPath: my.cnf + volumes: + - name: config + configMap: + name: mysql + volumeClaimTemplates: + - metadata: + name: data + labels: + app.kubernetes.io/name: mysql + app.kubernetes.io/instance: mysql + app.kubernetes.io/component: primary + spec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: "8Gi" \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/svc-headless.yaml b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/svc-headless.yaml new file mode 100644 index 000000000000..9fe0f11c87ae --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/svc-headless.yaml @@ -0,0 +1,25 @@ +# Source: mysql/templates/primary/svc-headless.yaml +apiVersion: v1 +kind: Service +metadata: + name: mysql-headless + namespace: + labels: + app.kubernetes.io/name: mysql + helm.sh/chart: mysql-8.8.26 + app.kubernetes.io/instance: mysql + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: primary + annotations: +spec: + type: ClusterIP + clusterIP: None + publishNotReadyAddresses: true + ports: + - name: mysql + port: 3306 + targetPort: mysql + selector: + app.kubernetes.io/name: mysql + app.kubernetes.io/instance: mysql + app.kubernetes.io/component: primary \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/svc.yaml b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/svc.yaml new file mode 100644 index 000000000000..2bbdab8fe468 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/primary/svc.yaml @@ -0,0 +1,25 @@ +# Source: mysql/templates/primary/svc.yaml +apiVersion: v1 +kind: Service +metadata: + name: mysql + namespace: + labels: + app.kubernetes.io/name: mysql + helm.sh/chart: mysql-8.8.26 + app.kubernetes.io/instance: mysql + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: primary + annotations: +spec: + type: ClusterIP + ports: + - name: mysql + port: 3306 + protocol: TCP + targetPort: mysql + nodePort: null + selector: + app.kubernetes.io/name: mysql + app.kubernetes.io/instance: mysql + app.kubernetes.io/component: primary \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/secrets.yaml b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/secrets.yaml new file mode 100644 index 000000000000..ffa6909e2f04 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/secrets.yaml @@ -0,0 +1,15 @@ +# Source: mysql/templates/secrets.yaml +apiVersion: v1 +kind: Secret +metadata: + name: mysql + namespace: + labels: + app.kubernetes.io/name: mysql + helm.sh/chart: mysql-8.8.26 + app.kubernetes.io/instance: mysql + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + mysql-root-password: "aGZYYW1vN3V5NA==" + mysql-password: "eHR6YU9MR1VhbA==" \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/serviceaccount.yaml b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/serviceaccount.yaml new file mode 100644 index 000000000000..760b8bf731a5 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/mysql/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +# Source: mysql/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: mysql + namespace: + labels: + app.kubernetes.io/name: mysql + helm.sh/chart: mysql-8.8.26 + app.kubernetes.io/instance: mysql + app.kubernetes.io/managed-by: Helm + annotations: +secrets: + - name: mysql \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/options/testchart/templates/deployment.yaml b/pkg/iac/scanners/helm/test/testdata/expected/options/testchart/templates/deployment.yaml new file mode 100644 index 000000000000..c41133c72716 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/options/testchart/templates/deployment.yaml @@ -0,0 +1,46 @@ +# Source: testchart/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: testchart + labels: + helm.sh/chart: testchart-0.1.0 + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart + template: + metadata: + labels: + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart + spec: + serviceAccountName: testchart + securityContext: + {} + containers: + - name: testchart + securityContext: + runAsUser: 0 + image: "nginx:1.16.0" + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {} \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/options/testchart/templates/service.yaml b/pkg/iac/scanners/helm/test/testdata/expected/options/testchart/templates/service.yaml new file mode 100644 index 000000000000..6c6699f3d5dd --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/options/testchart/templates/service.yaml @@ -0,0 +1,21 @@ +# Source: testchart/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: testchart + labels: + helm.sh/chart: testchart-0.1.0 + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/options/testchart/templates/serviceaccount.yaml b/pkg/iac/scanners/helm/test/testdata/expected/options/testchart/templates/serviceaccount.yaml new file mode 100644 index 000000000000..6fe44a89bb3b --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/options/testchart/templates/serviceaccount.yaml @@ -0,0 +1,11 @@ +# Source: testchart/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: testchart + labels: + helm.sh/chart: testchart-0.1.0 + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/options/with-api-version/templates/pdb.yaml b/pkg/iac/scanners/helm/test/testdata/expected/options/with-api-version/templates/pdb.yaml new file mode 100644 index 000000000000..7c7ef5fd74d7 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/options/with-api-version/templates/pdb.yaml @@ -0,0 +1,17 @@ +# Source: with-api-version/templates/pdb.yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: with-api-version + labels: + helm.sh/chart: with-api-version-0.1.0 + app.kubernetes.io/name: with-api-version + app.kubernetes.io/instance: with-api-version + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + selector: + matchLabels: + app.kubernetes.io/name: with-api-version + app.kubernetes.io/instance: with-api-version + maxUnavailable: 0 diff --git a/pkg/iac/scanners/helm/test/testdata/expected/options/with-kube-version/templates/pdb.yaml b/pkg/iac/scanners/helm/test/testdata/expected/options/with-kube-version/templates/pdb.yaml new file mode 100644 index 000000000000..7c7ef5fd74d7 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/options/with-kube-version/templates/pdb.yaml @@ -0,0 +1,17 @@ +# Source: with-api-version/templates/pdb.yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: with-api-version + labels: + helm.sh/chart: with-api-version-0.1.0 + app.kubernetes.io/name: with-api-version + app.kubernetes.io/instance: with-api-version + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + selector: + matchLabels: + app.kubernetes.io/name: with-api-version + app.kubernetes.io/instance: with-api-version + maxUnavailable: 0 diff --git a/pkg/iac/scanners/helm/test/testdata/expected/testchart/templates/deployment.yaml b/pkg/iac/scanners/helm/test/testdata/expected/testchart/templates/deployment.yaml new file mode 100644 index 000000000000..8ace433f0c03 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/testchart/templates/deployment.yaml @@ -0,0 +1,46 @@ +# Source: testchart/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: testchart + labels: + helm.sh/chart: testchart-0.1.0 + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart + template: + metadata: + labels: + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart + spec: + serviceAccountName: testchart + securityContext: + {} + containers: + - name: testchart + securityContext: + {} + image: "nginx:1.16.0" + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {} \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/testchart/templates/service.yaml b/pkg/iac/scanners/helm/test/testdata/expected/testchart/templates/service.yaml new file mode 100644 index 000000000000..6c6699f3d5dd --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/testchart/templates/service.yaml @@ -0,0 +1,21 @@ +# Source: testchart/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: testchart + labels: + helm.sh/chart: testchart-0.1.0 + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/testchart/templates/serviceaccount.yaml b/pkg/iac/scanners/helm/test/testdata/expected/testchart/templates/serviceaccount.yaml new file mode 100644 index 000000000000..6fe44a89bb3b --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/testchart/templates/serviceaccount.yaml @@ -0,0 +1,11 @@ +# Source: testchart/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: testchart + labels: + helm.sh/chart: testchart-0.1.0 + app.kubernetes.io/name: testchart + app.kubernetes.io/instance: testchart + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/with-tarred-dep/templates/deployment.yaml b/pkg/iac/scanners/helm/test/testdata/expected/with-tarred-dep/templates/deployment.yaml new file mode 100644 index 000000000000..ed57d12a6e2b --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/with-tarred-dep/templates/deployment.yaml @@ -0,0 +1,78 @@ +# Source: with-tarred-dep/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: with-tarred-dep + labels: + app.kubernetes.io/name: with-tarred-dep + helm.sh/chart: with-tarred-dep-0.1.1 + app.kubernetes.io/instance: with-tarred-dep + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: with-tarred-dep + app.kubernetes.io/instance: with-tarred-dep + template: + metadata: + labels: + app.kubernetes.io/name: with-tarred-dep + helm.sh/chart: with-tarred-dep-0.1.1 + app.kubernetes.io/instance: with-tarred-dep + app.kubernetes.io/managed-by: Helm + spec: + containers: + - name: metadata-service + env: + - name: METADATASERVICE_UPSTREAM_API_URL + value: '' + - name: METADATASERVICE_OIDC_AUDIENCE + value: "" + - name: METADATASERVICE_OIDC_ISSUER + value: "" + - name: METADATASERVICE_OIDC_JWKSURI + value: "" + - name: METADATASERVICE_OIDC_CLAIMS_ROLES + value: "" + - name: METADATASERVICE_OIDC_CLAIMS_USERNAME + value: "" + - name: METADATASERVICE_DB_URI + valueFrom: + secretKeyRef: + name: with-tarred-dep-dbconn + key: uri + image: "ghcr.io/metal-toolbox/hollow-metadataservice:v0.0.1" + imagePullPolicy: Always + volumeMounts: + - name: dbcerts + mountPath: "/dbcerts" + readOnly: true + ports: + - name: http + containerPort: 8000 + protocol: TCP + livenessProbe: + httpGet: + path: /healthz/liveness + port: http + initialDelaySeconds: 5 + timeoutSeconds: 2 + readinessProbe: + httpGet: + path: /healthz/readiness + port: http + initialDelaySeconds: 5 + timeoutSeconds: 2 + resources: + limits: + cpu: 4 + memory: 4Gi + requests: + cpu: 4 + memory: 4Gi + volumes: + - name: dbcerts + secret: + secretName: with-tarred-dep-crdb-ca + defaultMode: 0400 \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/with-tarred-dep/templates/ingress.yaml b/pkg/iac/scanners/helm/test/testdata/expected/with-tarred-dep/templates/ingress.yaml new file mode 100644 index 000000000000..b48564477997 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/with-tarred-dep/templates/ingress.yaml @@ -0,0 +1,26 @@ +# Source: with-tarred-dep/templates/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: with-tarred-dep + labels: + app.kubernetes.io/name: with-tarred-dep + helm.sh/chart: with-tarred-dep-0.1.1 + app.kubernetes.io/instance: with-tarred-dep + app.kubernetes.io/managed-by: Helm +spec: + rules: + - host: metadata-service.mydomain + http: + paths: + - path: /($|metadata|userdata|2009-04-04) + pathType: Prefix + backend: + service: + name: with-tarred-dep + port: + name: http +# tls: [] +# hosts: +# - hollow-metadataservice.mydomain +# secretName: hollow-metadataservice-example-tls \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/expected/with-tarred-dep/templates/service.yaml b/pkg/iac/scanners/helm/test/testdata/expected/with-tarred-dep/templates/service.yaml new file mode 100644 index 000000000000..7d86aeb5b02b --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/expected/with-tarred-dep/templates/service.yaml @@ -0,0 +1,24 @@ +# Source: with-tarred-dep/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: with-tarred-dep + labels: + app.kubernetes.io/name: with-tarred-dep + helm.sh/chart: with-tarred-dep-0.1.1 + app.kubernetes.io/instance: with-tarred-dep + app.kubernetes.io/managed-by: Helm +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 8000 + - name: https + port: 443 + protocol: TCP + targetPort: 8000 + selector: + app.kubernetes.io/name: with-tarred-dep + app.kubernetes.io/instance: with-tarred-dep + type: ClusterIP \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/mysql-8.8.26.tar b/pkg/iac/scanners/helm/test/testdata/mysql-8.8.26.tar new file mode 100644 index 000000000000..53cb6802de42 Binary files /dev/null and b/pkg/iac/scanners/helm/test/testdata/mysql-8.8.26.tar differ diff --git a/pkg/iac/scanners/helm/test/testdata/mysql-8.8.26.tar.gz b/pkg/iac/scanners/helm/test/testdata/mysql-8.8.26.tar.gz new file mode 100644 index 000000000000..ff8bd1ab402e Binary files /dev/null and b/pkg/iac/scanners/helm/test/testdata/mysql-8.8.26.tar.gz differ diff --git a/pkg/iac/scanners/helm/test/testdata/mysql-8.8.26.tgz b/pkg/iac/scanners/helm/test/testdata/mysql-8.8.26.tgz new file mode 100644 index 000000000000..ff8bd1ab402e Binary files /dev/null and b/pkg/iac/scanners/helm/test/testdata/mysql-8.8.26.tgz differ diff --git a/pkg/iac/scanners/helm/test/testdata/nope.tgz b/pkg/iac/scanners/helm/test/testdata/nope.tgz new file mode 100644 index 000000000000..a47332d93877 Binary files /dev/null and b/pkg/iac/scanners/helm/test/testdata/nope.tgz differ diff --git a/pkg/iac/scanners/helm/test/testdata/numberName/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/numberName/Chart.yaml new file mode 100644 index 000000000000..e840fbabf456 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/numberName/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: 1001 +version: 1.0.0 \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/simmilar-templates/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/simmilar-templates/Chart.yaml new file mode 100644 index 000000000000..e5855a786639 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/simmilar-templates/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: test-license-manager +version: 1.10.0 +type: application +appVersion: 1.10.0 +deprecated: false diff --git a/pkg/iac/scanners/helm/test/testdata/simmilar-templates/templates/deployment.yaml b/pkg/iac/scanners/helm/test/testdata/simmilar-templates/templates/deployment.yaml new file mode 100644 index 000000000000..eb2b2a343d51 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/simmilar-templates/templates/deployment.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +spec: + replicas: 3 + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: test:latest + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 diff --git a/pkg/iac/scanners/helm/test/testdata/simmilar-templates/templates/manifest.yaml b/pkg/iac/scanners/helm/test/testdata/simmilar-templates/templates/manifest.yaml new file mode 100644 index 000000000000..49d9df010f82 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/simmilar-templates/templates/manifest.yaml @@ -0,0 +1,2 @@ +apiVersion: apps/v1 +kind: Deployment diff --git a/pkg/iac/scanners/helm/test/testdata/templated-name/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/templated-name/Chart.yaml new file mode 100644 index 000000000000..e675643d99c9 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/templated-name/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +name: {{COMPONENT_NAME}} +version: {{COMPONENT_VERSION}} +description: A Helm chart for Kubernetes +keywords: + - kublr + - audit diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/.helmignore b/pkg/iac/scanners/helm/test/testdata/testchart/.helmignore new file mode 100644 index 000000000000..0e8a0eb36f4c --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/testchart/Chart.yaml new file mode 100644 index 000000000000..0ffb7d074a72 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: testchart +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/templates/NOTES.txt b/pkg/iac/scanners/helm/test/testdata/testchart/templates/NOTES.txt new file mode 100644 index 000000000000..45e51670a862 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "testchart.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "testchart.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "testchart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "testchart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/templates/_helpers.tpl b/pkg/iac/scanners/helm/test/testdata/testchart/templates/_helpers.tpl new file mode 100644 index 000000000000..4b0db05bf5f5 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "testchart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "testchart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "testchart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "testchart.labels" -}} +helm.sh/chart: {{ include "testchart.chart" . }} +{{ include "testchart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "testchart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "testchart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "testchart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "testchart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/templates/deployment.yaml b/pkg/iac/scanners/helm/test/testdata/testchart/templates/deployment.yaml new file mode 100644 index 000000000000..cde22bc4fcc5 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/templates/deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "testchart.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "testchart.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "testchart.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/templates/hpa.yaml b/pkg/iac/scanners/helm/test/testdata/testchart/templates/hpa.yaml new file mode 100644 index 000000000000..51734471d41d --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "testchart.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/templates/ingress.yaml b/pkg/iac/scanners/helm/test/testdata/testchart/templates/ingress.yaml new file mode 100644 index 000000000000..9732d2a24a14 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "testchart.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "testchart.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/templates/service.yaml b/pkg/iac/scanners/helm/test/testdata/testchart/templates/service.yaml new file mode 100644 index 000000000000..86baf148215d --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "testchart.fullname" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "testchart.selectorLabels" . | nindent 4 }} diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/templates/serviceaccount.yaml b/pkg/iac/scanners/helm/test/testdata/testchart/templates/serviceaccount.yaml new file mode 100644 index 000000000000..f728deb2a6bb --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "testchart.serviceAccountName" . }} + labels: + {{- include "testchart.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/templates/tests/test-connection.yaml b/pkg/iac/scanners/helm/test/testdata/testchart/templates/tests/test-connection.yaml new file mode 100644 index 000000000000..a391ef1c462f --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "testchart.fullname" . }}-test-connection" + labels: + {{- include "testchart.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "testchart.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/pkg/iac/scanners/helm/test/testdata/testchart/values.yaml b/pkg/iac/scanners/helm/test/testdata/testchart/values.yaml new file mode 100644 index 000000000000..4acdf3c931bd --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/testchart/values.yaml @@ -0,0 +1,86 @@ +# Default values for testchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: nginx + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: + {} + # fsGroup: 2000 + +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: + {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/pkg/iac/scanners/helm/test/testdata/with-api-version/.helmignore b/pkg/iac/scanners/helm/test/testdata/with-api-version/.helmignore new file mode 100644 index 000000000000..0e8a0eb36f4c --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-api-version/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/pkg/iac/scanners/helm/test/testdata/with-api-version/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/with-api-version/Chart.yaml new file mode 100644 index 000000000000..22dab35d32f4 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-api-version/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: with-api-version +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/pkg/iac/scanners/helm/test/testdata/with-api-version/templates/_helpers.tpl b/pkg/iac/scanners/helm/test/testdata/with-api-version/templates/_helpers.tpl new file mode 100644 index 000000000000..cab726131dc5 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-api-version/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "with-api-version.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "with-api-version.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "with-api-version.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "with-api-version.labels" -}} +helm.sh/chart: {{ include "with-api-version.chart" . }} +{{ include "with-api-version.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "with-api-version.selectorLabels" -}} +app.kubernetes.io/name: {{ include "with-api-version.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "with-api-version.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "with-api-version.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/with-api-version/templates/pdb.yaml b/pkg/iac/scanners/helm/test/testdata/with-api-version/templates/pdb.yaml new file mode 100644 index 000000000000..a0a54cbc232b --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-api-version/templates/pdb.yaml @@ -0,0 +1,11 @@ +apiVersion: {{ $.Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" | ternary "policy/v1" "policy/v1beta1" }} +kind: PodDisruptionBudget +metadata: + name: {{ include "with-api-version.fullname" . }} + labels: + {{- include "with-api-version.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "with-api-version.selectorLabels" . | nindent 6 }} + maxUnavailable: 0 diff --git a/pkg/iac/scanners/helm/test/testdata/with-api-version/values.yaml b/pkg/iac/scanners/helm/test/testdata/with-api-version/values.yaml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/iac/scanners/helm/test/testdata/with-kube-version/.helmignore b/pkg/iac/scanners/helm/test/testdata/with-kube-version/.helmignore new file mode 100644 index 000000000000..0e8a0eb36f4c --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-kube-version/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/pkg/iac/scanners/helm/test/testdata/with-kube-version/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/with-kube-version/Chart.yaml new file mode 100644 index 000000000000..99c44c125940 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-kube-version/Chart.yaml @@ -0,0 +1,26 @@ +apiVersion: v2 +name: with-api-version +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" + +kubeVersion: ">=1.60.0-0" diff --git a/pkg/iac/scanners/helm/test/testdata/with-kube-version/templates/_helpers.tpl b/pkg/iac/scanners/helm/test/testdata/with-kube-version/templates/_helpers.tpl new file mode 100644 index 000000000000..cab726131dc5 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-kube-version/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "with-api-version.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "with-api-version.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "with-api-version.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "with-api-version.labels" -}} +helm.sh/chart: {{ include "with-api-version.chart" . }} +{{ include "with-api-version.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "with-api-version.selectorLabels" -}} +app.kubernetes.io/name: {{ include "with-api-version.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "with-api-version.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "with-api-version.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/with-kube-version/templates/pdb.yaml b/pkg/iac/scanners/helm/test/testdata/with-kube-version/templates/pdb.yaml new file mode 100644 index 000000000000..0c063e06df97 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-kube-version/templates/pdb.yaml @@ -0,0 +1,11 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "with-api-version.fullname" . }} + labels: + {{- include "with-api-version.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "with-api-version.selectorLabels" . | nindent 6 }} + maxUnavailable: 0 diff --git a/pkg/iac/scanners/helm/test/testdata/with-kube-version/values.yaml b/pkg/iac/scanners/helm/test/testdata/with-kube-version/values.yaml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/iac/scanners/helm/test/testdata/with-subchart/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/with-subchart/Chart.yaml new file mode 100644 index 000000000000..3c8c9b71ae45 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-subchart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: test +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "1.16.0" diff --git a/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/Chart.yaml new file mode 100644 index 000000000000..45cdc636218e --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: nginx +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "1.16.0" diff --git a/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/templates/pod.yaml b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/templates/pod.yaml new file mode 100644 index 000000000000..70b3a84a8130 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/templates/pod.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 8080 + securityContext: + readOnlyRootFilesystem: {{ .Values.readOnlyFs }} diff --git a/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/values.yaml b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/values.yaml new file mode 100644 index 000000000000..ff3cff9db1e3 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-subchart/charts/nginx/values.yaml @@ -0,0 +1 @@ +readOnlyFs: false diff --git a/pkg/iac/scanners/helm/test/testdata/with-subchart/values.yaml b/pkg/iac/scanners/helm/test/testdata/with-subchart/values.yaml new file mode 100644 index 000000000000..1e51a8fed1da --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-subchart/values.yaml @@ -0,0 +1,2 @@ +nginx: + readOnlyFs: true diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/.helmignore b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/.helmignore new file mode 100644 index 000000000000..50af03172541 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/Chart.yaml b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/Chart.yaml new file mode 100644 index 000000000000..bd163a944cae --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: with-tarred-dep +description: Test With Tarred Dependencies +type: application +version: 0.1.1 +appVersion: "1.0" +sources: + - https://github.com/test/with-tarred-dep +dependencies: + - name: common + repository: https://charts.bitnami.com/bitnami + tags: + - bitnami-common + version: 1.16.1 diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/LICENSE b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/LICENSE new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/charts/common-1.16.1.tgz b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/charts/common-1.16.1.tgz new file mode 100644 index 000000000000..6a2df2e15b93 Binary files /dev/null and b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/charts/common-1.16.1.tgz differ diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/renovate.json b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/renovate.json new file mode 100644 index 000000000000..a78e667b7736 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base" + ] + } \ No newline at end of file diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/.gitkeep b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/deployment.yaml b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/deployment.yaml new file mode 100644 index 000000000000..003d08eb745d --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/deployment.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "common.names.fullname" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + template: + metadata: + labels: {{- include "common.labels.standard" . | nindent 8 }} + spec: + containers: + - name: metadata-service + env: + - name: METADATASERVICE_UPSTREAM_API_URL + value: '{{ .Values.upstreamAPI }}' + - name: METADATASERVICE_OIDC_AUDIENCE + value: "{{ .Values.oidc.audience }}" + - name: METADATASERVICE_OIDC_ISSUER + value: "{{ .Values.oidc.issuer }}" + - name: METADATASERVICE_OIDC_JWKSURI + value: "{{ .Values.oidc.jwksuri }}" + - name: METADATASERVICE_OIDC_CLAIMS_ROLES + value: "{{ .Values.oidc.rolesClaim }}" + - name: METADATASERVICE_OIDC_CLAIMS_USERNAME + value: "{{ .Values.oidc.userClaim }}" + - name: METADATASERVICE_DB_URI + valueFrom: + secretKeyRef: + name: {{ template "common.names.fullname" . }}-dbconn + key: uri + image: "{{ .Values.metadataservice.image.repository }}:{{ .Values.metadataservice.image.tag }}" + imagePullPolicy: Always + volumeMounts: + - name: dbcerts + mountPath: "/dbcerts" + readOnly: true + ports: + - name: http + containerPort: 8000 + protocol: TCP + livenessProbe: + httpGet: + path: /healthz/liveness + port: http + initialDelaySeconds: 5 + timeoutSeconds: 2 + readinessProbe: + httpGet: + path: /healthz/readiness + port: http + initialDelaySeconds: 5 + timeoutSeconds: 2 + resources: +{{ toYaml .Values.resources | indent 12 }} + volumes: + - name: dbcerts + secret: + secretName: {{ template "common.names.fullname" . }}-crdb-ca + defaultMode: 0400 diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/ingress.yaml b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/ingress.yaml new file mode 100644 index 000000000000..45cd321ca9a9 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/ingress.yaml @@ -0,0 +1,36 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ template "common.names.fullname" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} +spec: + {{- if and .Values.ingress.ingressClassName (eq "true" (include "common.ingress.supportsIngressClassname" .)) }} + ingressClassName: {{ .Values.ingress.ingressClassName | quote }} + {{- end }} + rules: + {{- range .Values.ingress.hostnames }} + - host: {{ . }} + http: + paths: + - path: / + {{- if $.Values.ingress.publicPaths -}} + ( + {{- range $index,$path := $.Values.ingress.publicPaths }} + {{- if $index }}|{{ end }} + {{- $path }} + {{- end -}} + ) + {{- end }} + pathType: Prefix + backend: + service: + name: {{ template "common.names.fullname" $ }} + port: + name: http + {{- end }} +# tls: [] +# hosts: +# - hollow-metadataservice.mydomain +# secretName: hollow-metadataservice-example-tls +{{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/secrets-crdb-ca.yaml b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/secrets-crdb-ca.yaml new file mode 100644 index 000000000000..18c39c058dcd --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/secrets-crdb-ca.yaml @@ -0,0 +1,17 @@ +{{- if .Values.crdbCA }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "common.names.fullname" . }}-crdb-ca + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + ca.crt: {{ .Values.crdbCA | b64enc | quote }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/secrets-dbconn.yaml b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/secrets-dbconn.yaml new file mode 100644 index 000000000000..06c93061d08c --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/secrets-dbconn.yaml @@ -0,0 +1,17 @@ +{{- if .Values.dbconnURI }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "common.names.fullname" . }}-dbconn + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + uri: {{ .Values.dbconnURI | b64enc | quote }} +{{- end }} diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/service.yaml b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/service.yaml new file mode 100644 index 000000000000..fdb8b82d76f8 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/templates/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 8000 + - name: https + port: 443 + protocol: TCP + targetPort: 8000 + selector:{{ include "common.labels.matchLabels" . | nindent 4 }} + type: ClusterIP diff --git a/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/values.yaml b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/values.yaml new file mode 100644 index 000000000000..7a86583f54e3 --- /dev/null +++ b/pkg/iac/scanners/helm/test/testdata/with-tarred-dep/values.yaml @@ -0,0 +1,30 @@ +metadataservice: + image: + repository: ghcr.io/metal-toolbox/hollow-metadataservice + tag: "v0.0.1" + +ingress: + enabled: true + hostnames: + - metadata-service.mydomain + publicPaths: + - $ + - metadata + - userdata + - '2009-04-04' + +oidc: + audience: "" + issuer: "" + jwksuri: "" + rolesClaim: "" + userClaim: "" + +replicaCount: 1 +resources: + limits: + cpu: 4 + memory: 4Gi + requests: + cpu: 4 + memory: 4Gi diff --git a/pkg/iac/scanners/helm/test/values/values.yaml b/pkg/iac/scanners/helm/test/values/values.yaml new file mode 100644 index 000000000000..6f637160ffa9 --- /dev/null +++ b/pkg/iac/scanners/helm/test/values/values.yaml @@ -0,0 +1,3 @@ +--- +securityContext: + runAsUser: 0 \ No newline at end of file diff --git a/pkg/iac/scanners/json/parser/parser.go b/pkg/iac/scanners/json/parser/parser.go new file mode 100644 index 000000000000..3504e3e10a3c --- /dev/null +++ b/pkg/iac/scanners/json/parser/parser.go @@ -0,0 +1,89 @@ +package parser + +import ( + "context" + "encoding/json" + "io" + "io/fs" + "path/filepath" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +var _ options.ConfigurableParser = (*Parser)(nil) + +type Parser struct { + debug debug.Logger + skipRequired bool +} + +func (p *Parser) SetDebugWriter(writer io.Writer) { + p.debug = debug.New(writer, "json", "parser") +} + +func (p *Parser) SetSkipRequiredCheck(b bool) { + p.skipRequired = b +} + +// New creates a new parser +func New(opts ...options.ParserOption) *Parser { + p := &Parser{} + for _, opt := range opts { + opt(p) + } + return p +} + +func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]interface{}, error) { + + files := make(map[string]interface{}) + if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if err != nil { + return err + } + if entry.IsDir() { + return nil + } + if !p.Required(path) { + return nil + } + df, err := p.ParseFile(ctx, target, path) + if err != nil { + p.debug.Log("Parse error in '%s': %s", path, err) + return nil + } + files[path] = df + return nil + }); err != nil { + return nil, err + } + return files, nil +} + +// ParseFile parses Dockerfile content from the provided filesystem path. +func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (interface{}, error) { + f, err := fsys.Open(filepath.ToSlash(path)) + if err != nil { + return nil, err + } + defer func() { _ = f.Close() }() + var target interface{} + if err := json.NewDecoder(f).Decode(&target); err != nil { + return nil, err + } + return target, nil +} + +func (p *Parser) Required(path string) bool { + if p.skipRequired { + return true + } + return detection.IsType(path, nil, detection.FileTypeJSON) +} diff --git a/pkg/iac/scanners/json/parser/parser_test.go b/pkg/iac/scanners/json/parser/parser_test.go new file mode 100644 index 000000000000..2af3936d6124 --- /dev/null +++ b/pkg/iac/scanners/json/parser/parser_test.go @@ -0,0 +1,51 @@ +package parser + +import ( + "context" + "testing" + + "github.com/liamg/memoryfs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Parser(t *testing.T) { + input := `{ "x": { "y": 123, "z": ["a", "b", "c"]}}` + + memfs := memoryfs.New() + err := memfs.WriteFile("something.json", []byte(input), 0644) + require.NoError(t, err) + + data, err := New().ParseFile(context.TODO(), memfs, "something.json") + require.NoError(t, err) + + msi, ok := data.(map[string]interface{}) + require.True(t, ok) + + xObj, ok := msi["x"] + require.True(t, ok) + + xMsi, ok := xObj.(map[string]interface{}) + require.True(t, ok) + + yRaw, ok := xMsi["y"] + require.True(t, ok) + + y, ok := yRaw.(float64) + require.True(t, ok) + + assert.Equal(t, 123.0, y) + + zRaw, ok := xMsi["z"] + require.True(t, ok) + + z, ok := zRaw.([]interface{}) + require.True(t, ok) + + require.Len(t, z, 3) + + assert.Equal(t, "a", z[0]) + assert.Equal(t, "b", z[1]) + assert.Equal(t, "c", z[2]) + +} diff --git a/pkg/iac/scanners/json/scanner.go b/pkg/iac/scanners/json/scanner.go new file mode 100644 index 000000000000..5c53d0a10896 --- /dev/null +++ b/pkg/iac/scanners/json/scanner.go @@ -0,0 +1,170 @@ +package json + +import ( + "context" + "io" + "io/fs" + "sync" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners" + "github.com/aquasecurity/trivy/pkg/iac/scanners/json/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var _ scanners.FSScanner = (*Scanner)(nil) +var _ options.ConfigurableScanner = (*Scanner)(nil) + +type Scanner struct { // nolint: gocritic + debug debug.Logger + policyDirs []string + policyReaders []io.Reader + parser *parser.Parser + regoScanner *rego.Scanner + skipRequired bool + options []options.ScannerOption + sync.Mutex + frameworks []framework.Framework + spec string + loadEmbeddedPolicies bool + loadEmbeddedLibraries bool +} + +func (s *Scanner) SetRegoOnly(bool) { +} + +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { + s.frameworks = frameworks +} + +func (s *Scanner) SetSpec(spec string) { + s.spec = spec +} + +func (s *Scanner) SetUseEmbeddedPolicies(b bool) { + s.loadEmbeddedPolicies = b +} + +func (s *Scanner) SetUseEmbeddedLibraries(b bool) { + s.loadEmbeddedLibraries = b +} + +func (s *Scanner) SetPolicyReaders(readers []io.Reader) { + s.policyReaders = readers +} + +func (s *Scanner) SetDebugWriter(writer io.Writer) { + s.debug = debug.New(writer, "json", "scanner") +} + +func (s *Scanner) SetTraceWriter(_ io.Writer) { +} + +func (s *Scanner) SetPerResultTracingEnabled(_ bool) { +} + +func (s *Scanner) SetPolicyDirs(dirs ...string) { + s.policyDirs = dirs +} + +func (s *Scanner) SetDataDirs(_ ...string) {} +func (s *Scanner) SetPolicyNamespaces(_ ...string) {} + +func (s *Scanner) SetSkipRequiredCheck(skip bool) { + s.skipRequired = skip +} + +func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} + +func (s *Scanner) SetDataFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} +func (s *Scanner) SetRegoErrorLimit(_ int) {} + +func NewScanner(opts ...options.ScannerOption) *Scanner { + s := &Scanner{ + options: opts, + } + for _, opt := range opts { + opt(s) + } + s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) + return s +} + +func (s *Scanner) Name() string { + return "JSON" +} + +func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, path string) (scan.Results, error) { + + files, err := s.parser.ParseFS(ctx, fsys, path) + if err != nil { + return nil, err + } + + if len(files) == 0 { + return nil, nil + } + + var inputs []rego.Input + for path, file := range files { + inputs = append(inputs, rego.Input{ + Path: path, + FS: fsys, + Contents: file, + }) + } + + results, err := s.scanRego(ctx, fsys, inputs...) + if err != nil { + return nil, err + } + return results, nil +} + +func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.Results, error) { + parsed, err := s.parser.ParseFile(ctx, fsys, path) + if err != nil { + return nil, err + } + s.debug.Log("Scanning %s...", path) + return s.scanRego(ctx, fsys, rego.Input{ + Path: path, + Contents: parsed, + }) +} + +func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { + s.Lock() + defer s.Unlock() + if s.regoScanner != nil { + return s.regoScanner, nil + } + regoScanner := rego.NewScanner(types.SourceJSON, s.options...) + regoScanner.SetParentDebugLogger(s.debug) + if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + return nil, err + } + s.regoScanner = regoScanner + return regoScanner, nil +} + +func (s *Scanner) scanRego(ctx context.Context, srcFS fs.FS, inputs ...rego.Input) (scan.Results, error) { + regoScanner, err := s.initRegoScanner(srcFS) + if err != nil { + return nil, err + } + results, err := regoScanner.ScanInput(ctx, inputs...) + if err != nil { + return nil, err + } + results.SetSourceAndFilesystem("", srcFS, false) + return results, nil +} diff --git a/pkg/iac/scanners/json/scanner_test.go b/pkg/iac/scanners/json/scanner_test.go new file mode 100644 index 000000000000..774d26612823 --- /dev/null +++ b/pkg/iac/scanners/json/scanner_test.go @@ -0,0 +1,77 @@ +package json + +import ( + "context" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_BasicScan(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "/code/data.json": `{ "x": { "y": 123, "z": ["a", "b", "c"]}}`, + "/rules/rule.rego": `package builtin.json.lol + +__rego_metadata__ := { + "id": "ABC123", + "avd_id": "AVD-AB-0123", + "title": "title", + "short_code": "short", + "severity": "CRITICAL", + "type": "JSON Check", + "description": "description", + "recommended_actions": "actions", + "url": "https://example.com", +} + +__rego_input__ := { + "combine": false, + "selector": [{"type": "json"}], +} + +deny[res] { + input.x.y == 123 + res := { + "msg": "oh no", + "startline": 1, + "endline": 2, + } +} + +`, + }) + + scanner := NewScanner(options.ScannerWithPolicyDirs("rules")) + + results, err := scanner.ScanFS(context.TODO(), fs, "code") + require.NoError(t, err) + + require.Len(t, results.GetFailed(), 1) + + assert.Equal(t, scan.Rule{ + AVDID: "AVD-AB-0123", + Aliases: []string{"ABC123"}, + ShortCode: "short", + Summary: "title", + Explanation: "description", + Impact: "", + Resolution: "actions", + Provider: "json", + Service: "general", + Links: []string{"https://example.com"}, + Severity: "CRITICAL", + Terraform: &scan.EngineMetadata{}, + CloudFormation: &scan.EngineMetadata{}, + CustomChecks: scan.CustomChecks{ + Terraform: (*scan.TerraformCustomCheck)(nil), + }, + RegoPackage: "data.builtin.json.lol", + Frameworks: map[framework.Framework][]string{}, + }, results.GetFailed()[0].Rule()) +} diff --git a/pkg/iac/scanners/kubernetes/parser/manifest.go b/pkg/iac/scanners/kubernetes/parser/manifest.go new file mode 100644 index 000000000000..82da971b3e30 --- /dev/null +++ b/pkg/iac/scanners/kubernetes/parser/manifest.go @@ -0,0 +1,33 @@ +package parser + +import ( + "fmt" + + "gopkg.in/yaml.v3" +) + +type Manifest struct { + Path string + Content *ManifestNode +} + +func (m *Manifest) UnmarshalYAML(value *yaml.Node) error { + + switch value.Tag { + case string(TagMap): + node := new(ManifestNode) + node.Path = m.Path + if err := value.Decode(node); err != nil { + return err + } + m.Content = node + default: + return fmt.Errorf("failed to handle tag: %s", value.Tag) + } + + return nil +} + +func (m *Manifest) ToRego() interface{} { + return m.Content.ToRego() +} diff --git a/pkg/iac/scanners/kubernetes/parser/manifest_node.go b/pkg/iac/scanners/kubernetes/parser/manifest_node.go new file mode 100644 index 000000000000..1f82ca1e3680 --- /dev/null +++ b/pkg/iac/scanners/kubernetes/parser/manifest_node.go @@ -0,0 +1,140 @@ +package parser + +import ( + "fmt" + "strconv" + + "gopkg.in/yaml.v3" +) + +type TagType string + +const ( + TagBool TagType = "!!bool" + TagInt TagType = "!!int" + TagFloat TagType = "!!float" + TagStr TagType = "!!str" + TagString TagType = "!!string" + TagSlice TagType = "!!seq" + TagMap TagType = "!!map" +) + +type ManifestNode struct { + StartLine int + EndLine int + Offset int + Value interface{} + Type TagType + Path string +} + +func (r *ManifestNode) ToRego() interface{} { + if r == nil { + return nil + } + switch r.Type { + case TagBool, TagInt, TagString, TagStr: + return r.Value + case TagSlice: + var output []interface{} + for _, node := range r.Value.([]ManifestNode) { + output = append(output, node.ToRego()) + } + return output + case TagMap: + output := make(map[string]interface{}) + output["__defsec_metadata"] = map[string]interface{}{ + "startline": r.StartLine, + "endline": r.EndLine, + "filepath": r.Path, + "offset": r.Offset, + } + for key, node := range r.Value.(map[string]ManifestNode) { + output[key] = node.ToRego() + } + return output + } + return nil +} + +func (r *ManifestNode) UnmarshalYAML(node *yaml.Node) error { + + r.StartLine = node.Line + r.EndLine = node.Line + r.Type = TagType(node.Tag) + + switch TagType(node.Tag) { + case TagString, TagStr: + + r.Value = node.Value + case TagInt: + val, err := strconv.Atoi(node.Value) + if err != nil { + return err + } + r.Value = val + case TagFloat: + val, err := strconv.ParseFloat(node.Value, 64) + if err != nil { + return err + } + r.Value = val + case TagBool: + val, err := strconv.ParseBool(node.Value) + if err != nil { + return err + } + r.Value = val + case TagMap: + return r.handleMapTag(node) + case TagSlice: + return r.handleSliceTag(node) + + default: + return fmt.Errorf("node tag is not supported %s", node.Tag) + } + return nil +} + +func (r *ManifestNode) handleSliceTag(node *yaml.Node) error { + var nodes []ManifestNode + max := node.Line + for _, contentNode := range node.Content { + newNode := new(ManifestNode) + newNode.Path = r.Path + if err := contentNode.Decode(newNode); err != nil { + return err + } + if newNode.EndLine > max { + max = newNode.EndLine + } + nodes = append(nodes, *newNode) + } + r.EndLine = max + r.Value = nodes + return nil +} + +func (r *ManifestNode) handleMapTag(node *yaml.Node) error { + output := make(map[string]ManifestNode) + var key string + max := node.Line + for i, contentNode := range node.Content { + if i == 0 || i%2 == 0 { + key = contentNode.Value + } else { + newNode := new(ManifestNode) + newNode.Path = r.Path + if err := contentNode.Decode(newNode); err != nil { + return err + } + output[key] = *newNode + if newNode.EndLine > max { + max = newNode.EndLine + } + } + } + r.EndLine = max + r.Value = output + return nil +} diff --git a/pkg/iac/scanners/kubernetes/parser/parser.go b/pkg/iac/scanners/kubernetes/parser/parser.go new file mode 100644 index 000000000000..fd2c12ecb327 --- /dev/null +++ b/pkg/iac/scanners/kubernetes/parser/parser.go @@ -0,0 +1,137 @@ +package parser + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/fs" + "path/filepath" + "regexp" + "strings" + + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +var _ options.ConfigurableParser = (*Parser)(nil) + +type Parser struct { + debug debug.Logger + skipRequired bool +} + +func (p *Parser) SetDebugWriter(writer io.Writer) { + p.debug = debug.New(writer, "kubernetes", "parser") +} + +func (p *Parser) SetSkipRequiredCheck(b bool) { + p.skipRequired = b +} + +// New creates a new K8s parser +func New(opts ...options.ParserOption) *Parser { + p := &Parser{} + for _, option := range opts { + option(p) + } + return p +} + +func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]interface{}, error) { + files := make(map[string][]interface{}) + if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if err != nil { + return err + } + if entry.IsDir() { + return nil + } + if !p.required(target, path) { + return nil + } + parsed, err := p.ParseFile(ctx, target, path) + if err != nil { + p.debug.Log("Parse error in '%s': %s", path, err) + return nil + } + files[path] = parsed + return nil + }); err != nil { + return nil, err + } + return files, nil +} + +// ParseFile parses Kubernetes manifest from the provided filesystem path. +func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]interface{}, error) { + f, err := fsys.Open(filepath.ToSlash(path)) + if err != nil { + return nil, err + } + defer func() { _ = f.Close() }() + return p.Parse(f, path) +} + +func (p *Parser) required(fsys fs.FS, path string) bool { + if p.skipRequired { + return true + } + f, err := fsys.Open(filepath.ToSlash(path)) + if err != nil { + return false + } + defer func() { _ = f.Close() }() + if data, err := io.ReadAll(f); err == nil { + return detection.IsType(path, bytes.NewReader(data), detection.FileTypeKubernetes) + } + return false +} + +func (p *Parser) Parse(r io.Reader, path string) ([]interface{}, error) { + + contents, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + if len(contents) == 0 { + return nil, nil + } + + if strings.TrimSpace(string(contents))[0] == '{' { + var target interface{} + if err := json.Unmarshal(contents, &target); err != nil { + return nil, err + } + return []interface{}{target}, nil + } + + var results []interface{} + + re := regexp.MustCompile(`(?m:^---\r?\n)`) + pos := 0 + for _, partial := range re.Split(string(contents), -1) { + var result Manifest + result.Path = path + if err := yaml.Unmarshal([]byte(partial), &result); err != nil { + return nil, fmt.Errorf("unmarshal yaml: %w", err) + } + if result.Content != nil { + result.Content.Offset = pos + results = append(results, result.ToRego()) + } + pos += len(strings.Split(partial, "\n")) + } + + return results, nil +} diff --git a/pkg/iac/scanners/kubernetes/scanner.go b/pkg/iac/scanners/kubernetes/scanner.go new file mode 100644 index 000000000000..121c954990a3 --- /dev/null +++ b/pkg/iac/scanners/kubernetes/scanner.go @@ -0,0 +1,177 @@ +package kubernetes + +import ( + "context" + "io" + "io/fs" + "path/filepath" + "sort" + "sync" + + "github.com/liamg/memoryfs" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners" + "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var _ scanners.FSScanner = (*Scanner)(nil) +var _ options.ConfigurableScanner = (*Scanner)(nil) + +type Scanner struct { // nolint: gocritic + debug debug.Logger + options []options.ScannerOption + policyDirs []string + policyReaders []io.Reader + regoScanner *rego.Scanner + parser *parser.Parser + skipRequired bool + sync.Mutex + loadEmbeddedPolicies bool + frameworks []framework.Framework + spec string + loadEmbeddedLibraries bool +} + +func (s *Scanner) SetSpec(spec string) { + s.spec = spec +} + +func (s *Scanner) SetRegoOnly(bool) {} + +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { + s.frameworks = frameworks +} + +func (s *Scanner) SetUseEmbeddedPolicies(b bool) { + s.loadEmbeddedPolicies = b +} + +func (s *Scanner) SetUseEmbeddedLibraries(b bool) { + s.loadEmbeddedLibraries = b +} + +func (s *Scanner) SetPolicyReaders(readers []io.Reader) { + s.policyReaders = readers +} + +func (s *Scanner) SetSkipRequiredCheck(skip bool) { + s.skipRequired = skip +} + +func (s *Scanner) SetDebugWriter(writer io.Writer) { + s.debug = debug.New(writer, "kubernetes", "scanner") +} + +func (s *Scanner) SetTraceWriter(_ io.Writer) { +} + +func (s *Scanner) SetPerResultTracingEnabled(_ bool) { +} + +func (s *Scanner) SetPolicyDirs(dirs ...string) { + s.policyDirs = dirs +} + +func (s *Scanner) SetDataDirs(...string) {} +func (s *Scanner) SetPolicyNamespaces(_ ...string) { +} + +func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} + +func (s *Scanner) SetDataFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} +func (s *Scanner) SetRegoErrorLimit(_ int) {} + +func NewScanner(opts ...options.ScannerOption) *Scanner { + s := &Scanner{ + options: opts, + } + for _, opt := range opts { + opt(s) + } + s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) + return s +} + +func (s *Scanner) Name() string { + return "Kubernetes" +} + +func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { + s.Lock() + defer s.Unlock() + if s.regoScanner != nil { + return s.regoScanner, nil + } + regoScanner := rego.NewScanner(types.SourceKubernetes, s.options...) + regoScanner.SetParentDebugLogger(s.debug) + if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + return nil, err + } + s.regoScanner = regoScanner + return regoScanner, nil +} + +func (s *Scanner) ScanReader(ctx context.Context, filename string, reader io.Reader) (scan.Results, error) { + memfs := memoryfs.New() + if err := memfs.MkdirAll(filepath.Base(filename), 0o700); err != nil { + return nil, err + } + data, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + if err := memfs.WriteFile(filename, data, 0o644); err != nil { + return nil, err + } + return s.ScanFS(ctx, memfs, ".") +} + +func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, dir string) (scan.Results, error) { + + k8sFilesets, err := s.parser.ParseFS(ctx, target, dir) + if err != nil { + return nil, err + } + + if len(k8sFilesets) == 0 { + return nil, nil + } + + var inputs []rego.Input + for path, k8sFiles := range k8sFilesets { + for _, content := range k8sFiles { + inputs = append(inputs, rego.Input{ + Path: path, + FS: target, + Contents: content, + }) + } + } + + regoScanner, err := s.initRegoScanner(target) + if err != nil { + return nil, err + } + + s.debug.Log("Scanning %d files...", len(inputs)) + results, err := regoScanner.ScanInput(ctx, inputs...) + if err != nil { + return nil, err + } + results.SetSourceAndFilesystem("", target, false) + + sort.Slice(results, func(i, j int) bool { + return results[i].Rule().AVDID < results[j].Rule().AVDID + }) + return results, nil +} diff --git a/pkg/iac/scanners/kubernetes/scanner_test.go b/pkg/iac/scanners/kubernetes/scanner_test.go new file mode 100644 index 000000000000..931b2a09b8f2 --- /dev/null +++ b/pkg/iac/scanners/kubernetes/scanner_test.go @@ -0,0 +1,733 @@ +package kubernetes + +import ( + "context" + "os" + "strings" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_BasicScan(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "/code/example.yaml": ` +apiVersion: v1 +kind: Pod +metadata: + name: hello-cpu-limit +spec: + containers: + - command: ["sh", "-c", "echo 'Hello' && sleep 1h"] + image: busybox + name: hello +`, + "/rules/lib.k8s.rego": ` + package lib.kubernetes + + default is_gatekeeper = false + + is_gatekeeper { + has_field(input, "review") + has_field(input.review, "object") + } + + object = input { + not is_gatekeeper + } + + object = input.review.object { + is_gatekeeper + } + + format(msg) = gatekeeper_format { + is_gatekeeper + gatekeeper_format = {"msg": msg} + } + + format(msg) = msg { + not is_gatekeeper + } + + name = object.metadata.name + + default namespace = "default" + + namespace = object.metadata.namespace + + #annotations = object.metadata.annotations + + kind = object.kind + + is_pod { + kind = "Pod" + } + + is_cronjob { + kind = "CronJob" + } + + default is_controller = false + + is_controller { + kind = "Deployment" + } + + is_controller { + kind = "StatefulSet" + } + + is_controller { + kind = "DaemonSet" + } + + is_controller { + kind = "ReplicaSet" + } + + is_controller { + kind = "ReplicationController" + } + + is_controller { + kind = "Job" + } + + split_image(image) = [image, "latest"] { + not contains(image, ":") + } + + split_image(image) = [image_name, tag] { + [image_name, tag] = split(image, ":") + } + + pod_containers(pod) = all_containers { + keys = {"containers", "initContainers"} + all_containers = [c | keys[k]; c = pod.spec[k][_]] + } + + containers[container] { + pods[pod] + all_containers = pod_containers(pod) + container = all_containers[_] + } + + containers[container] { + all_containers = pod_containers(object) + container = all_containers[_] + } + + pods[pod] { + is_pod + pod = object + } + + pods[pod] { + is_controller + pod = object.spec.template + } + + pods[pod] { + is_cronjob + pod = object.spec.jobTemplate.spec.template + } + + volumes[volume] { + pods[pod] + volume = pod.spec.volumes[_] + } + + dropped_capability(container, cap) { + container.securityContext.capabilities.drop[_] == cap + } + + added_capability(container, cap) { + container.securityContext.capabilities.add[_] == cap + } + + has_field(obj, field) { + obj[field] + } + + no_read_only_filesystem(c) { + not has_field(c, "securityContext") + } + + no_read_only_filesystem(c) { + has_field(c, "securityContext") + not has_field(c.securityContext, "readOnlyRootFilesystem") + } + + privilege_escalation_allowed(c) { + not has_field(c, "securityContext") + } + + privilege_escalation_allowed(c) { + has_field(c, "securityContext") + has_field(c.securityContext, "allowPrivilegeEscalation") + } + + annotations[annotation] { + pods[pod] + annotation = pod.metadata.annotations + } + + host_ipcs[host_ipc] { + pods[pod] + host_ipc = pod.spec.hostIPC + } + + host_networks[host_network] { + pods[pod] + host_network = pod.spec.hostNetwork + } + + host_pids[host_pid] { + pods[pod] + host_pid = pod.spec.hostPID + } + + host_aliases[host_alias] { + pods[pod] + host_alias = pod.spec + } + `, + "/rules/lib.util.rego": ` + package lib.utils + + has_key(x, k) { + _ = x[k] + }`, + "/rules/rule.rego": ` +package builtin.kubernetes.KSV011 + +import data.lib.kubernetes +import data.lib.utils + +default failLimitsCPU = false + +__rego_metadata__ := { + "id": "KSV011", + "avd_id": "AVD-KSV-0011", + "title": "CPU not limited", + "short_code": "limit-cpu", + "version": "v1.0.0", + "severity": "LOW", + "type": "Kubernetes Security Check", + "description": "Enforcing CPU limits prevents DoS via resource exhaustion.", + "recommended_actions": "Set a limit value under 'containers[].resources.limits.cpu'.", + "url": "https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-resource-requests-and-limits", +} + +__rego_input__ := { + "combine": false, + "selector": [{"type": "kubernetes"}], +} + +# getLimitsCPUContainers returns all containers which have set resources.limits.cpu +getLimitsCPUContainers[container] { + allContainers := kubernetes.containers[_] + utils.has_key(allContainers.resources.limits, "cpu") + container := allContainers.name +} + +# getNoLimitsCPUContainers returns all containers which have not set +# resources.limits.cpu +getNoLimitsCPUContainers[container] { + container := kubernetes.containers[_].name + not getLimitsCPUContainers[container] +} + +# failLimitsCPU is true if containers[].resources.limits.cpu is not set +# for ANY container +failLimitsCPU { + count(getNoLimitsCPUContainers) > 0 +} + +deny[res] { + failLimitsCPU + + msg := kubernetes.format(sprintf("Container '%s' of %s '%s' should set 'resources.limits.cpu'", [getNoLimitsCPUContainers[_], kubernetes.kind, kubernetes.name])) + + res := { + "msg": msg, + "id": __rego_metadata__.id, + "title": __rego_metadata__.title, + "severity": __rego_metadata__.severity, + "type": __rego_metadata__.type, + "startline": 6, + "endline": 10, + } +} +`, + }) + + scanner := NewScanner(options.ScannerWithPolicyDirs("rules")) + + results, err := scanner.ScanFS(context.TODO(), fs, "code") + require.NoError(t, err) + + require.Len(t, results.GetFailed(), 1) + + assert.Equal(t, scan.Rule{ + AVDID: "AVD-KSV-0011", + Aliases: []string{"KSV011"}, + ShortCode: "limit-cpu", + Summary: "CPU not limited", + Explanation: "Enforcing CPU limits prevents DoS via resource exhaustion.", + Impact: "", + Resolution: "Set a limit value under 'containers[].resources.limits.cpu'.", + Provider: "kubernetes", + Service: "general", + Links: []string{"https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-resource-requests-and-limits"}, + Severity: "LOW", + Terraform: &scan.EngineMetadata{}, + CloudFormation: &scan.EngineMetadata{}, + CustomChecks: scan.CustomChecks{Terraform: (*scan.TerraformCustomCheck)(nil)}, + RegoPackage: "data.builtin.kubernetes.KSV011", + Frameworks: map[framework.Framework][]string{}, + }, results.GetFailed()[0].Rule()) + + failure := results.GetFailed()[0] + actualCode, err := failure.GetCode() + require.NoError(t, err) + for i := range actualCode.Lines { + actualCode.Lines[i].Highlighted = "" + } + assert.Equal(t, []scan.Line{ + { + Number: 6, + Content: "spec: ", + IsCause: true, + FirstCause: true, + Annotation: "", + }, + { + Number: 7, + Content: " containers: ", + IsCause: true, + Annotation: "", + }, + { + Number: 8, + Content: " - command: [\"sh\", \"-c\", \"echo 'Hello' && sleep 1h\"]", + IsCause: true, + Annotation: "", + }, + { + Number: 9, + Content: " image: busybox", + IsCause: true, + Annotation: "", + }, + { + Number: 10, + Content: " name: hello", + IsCause: true, + LastCause: true, + Annotation: "", + }, + }, actualCode.Lines) +} + +func Test_FileScan(t *testing.T) { + + results, err := NewScanner(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), options.ScannerWithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(` +apiVersion: v1 +kind: Pod +metadata: + name: hello-cpu-limit +spec: + containers: + - command: ["sh", "-c", "echo 'Hello' && sleep 1h"] + image: busybox + name: hello +`)) + require.NoError(t, err) + + assert.Greater(t, len(results.GetFailed()), 0) +} + +func Test_FileScan_WithSeparator(t *testing.T) { + + results, err := NewScanner(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(` +--- +--- +apiVersion: v1 +kind: Pod +metadata: + name: hello-cpu-limit +spec: + containers: + - command: ["sh", "-c", "echo 'Hello' && sleep 1h"] + image: busybox + name: hello +`)) + require.NoError(t, err) + + assert.Greater(t, len(results.GetFailed()), 0) +} + +func Test_FileScan_MultiManifests(t *testing.T) { + file := ` +--- +apiVersion: v1 +kind: Pod +metadata: + name: hello1-cpu-limit +spec: + containers: + - command: ["sh", "-c", "echo 'Hello1' && sleep 1h"] + image: busybox + name: hello1 +--- +apiVersion: v1 +kind: Pod +metadata: + name: hello2-cpu-limit +spec: + containers: + - command: ["sh", "-c", "echo 'Hello2' && sleep 1h"] + image: busybox + name: hello2 +` + + results, err := NewScanner( + options.ScannerWithEmbeddedPolicies(true), + options.ScannerWithEmbeddedLibraries(true), + options.ScannerWithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(file)) + require.NoError(t, err) + + assert.Greater(t, len(results.GetFailed()), 1) + fileLines := strings.Split(file, "\n") + for _, failure := range results.GetFailed() { + actualCode, err := failure.GetCode() + require.NoError(t, err) + assert.Greater(t, len(actualCode.Lines), 0) + for _, line := range actualCode.Lines { + assert.Greater(t, len(fileLines), line.Number) + assert.Equal(t, line.Content, fileLines[line.Number-1]) + } + } +} + +func Test_FileScanWithPolicyReader(t *testing.T) { + + results, err := NewScanner(options.ScannerWithPolicyReader(strings.NewReader(`package defsec + +deny[msg] { + msg = "fail" +} +`))).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(` +apiVersion: v1 +kind: Pod +metadata: + name: hello-cpu-limit +spec: + containers: + - command: ["sh", "-c", "echo 'Hello' && sleep 1h"] + image: busybox + name: hello +`)) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) +} + +func Test_FileScanJSON(t *testing.T) { + + results, err := NewScanner(options.ScannerWithPolicyReader(strings.NewReader(`package defsec + +deny[msg] { + input.kind == "Pod" + msg = "fail" +} +`))).ScanReader(context.TODO(), "k8s.json", strings.NewReader(` +{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": { + "name": "mongo", + "labels": { + "name": "mongo", + "role": "mongo" + } + }, + "spec": { + "volumes": [ + { + "name": "mongo-disk", + "gcePersistentDisk": { + "pdName": "mongo-disk", + "fsType": "ext4" + } + } + ], + "containers": [ + { + "name": "mongo", + "image": "mongo:latest", + "ports": [ + { + "name": "mongo", + "containerPort": 27017 + } + ], + "volumeMounts": [ + { + "name": "mongo-disk", + "mountPath": "/data/db" + } + ] + } + ] + } +} +`)) + require.NoError(t, err) + + assert.Equal(t, 1, len(results.GetFailed())) +} + +func Test_FileScanWithMetadata(t *testing.T) { + + results, err := NewScanner( + options.ScannerWithDebug(os.Stdout), + options.ScannerWithTrace(os.Stdout), + options.ScannerWithPolicyReader(strings.NewReader(`package defsec + +deny[msg] { + input.kind == "Pod" + msg := { + "msg": "fail", + "startline": 2, + "endline": 2, + "filepath": "chartname/template/serviceAccount.yaml" + } +} +`))).ScanReader( + context.TODO(), + "k8s.yaml", + strings.NewReader(` +apiVersion: v1 +kind: Pod +metadata: + name: hello-cpu-limit +spec: + containers: + - command: ["sh", "-c", "echo 'Hello' && sleep 1h"] + image: busybox + name: hello +`)) + require.NoError(t, err) + + assert.Greater(t, len(results.GetFailed()), 0) + + firstResult := results.GetFailed()[0] + assert.Equal(t, 2, firstResult.Metadata().Range().GetStartLine()) + assert.Equal(t, 2, firstResult.Metadata().Range().GetEndLine()) + assert.Equal(t, "chartname/template/serviceAccount.yaml", firstResult.Metadata().Range().GetFilename()) +} + +func Test_FileScanExampleWithResultFunction(t *testing.T) { + + results, err := NewScanner( + options.ScannerWithDebug(os.Stdout), + options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), + options.ScannerWithPolicyReader(strings.NewReader(`package defsec + +import data.lib.kubernetes + +default checkCapsDropAll = false + +__rego_metadata__ := { +"id": "KSV003", +"avd_id": "AVD-KSV-0003", +"title": "Default capabilities not dropped", +"short_code": "drop-default-capabilities", +"version": "v1.0.0", +"severity": "LOW", +"type": "Kubernetes Security Check", +"description": "The container should drop all default capabilities and add only those that are needed for its execution.", +"recommended_actions": "Add 'ALL' to containers[].securityContext.capabilities.drop.", +"url": "https://kubesec.io/basics/containers-securitycontext-capabilities-drop-index-all/", +} + +__rego_input__ := { +"combine": false, +"selector": [{"type": "kubernetes"}], +} + +# Get all containers which include 'ALL' in security.capabilities.drop +getCapsDropAllContainers[container] { +allContainers := kubernetes.containers[_] +lower(allContainers.securityContext.capabilities.drop[_]) == "all" +container := allContainers.name +} + +# Get all containers which don't include 'ALL' in security.capabilities.drop +getCapsNoDropAllContainers[container] { +container := kubernetes.containers[_] +not getCapsDropAllContainers[container.name] +} + +deny[res] { +output := getCapsNoDropAllContainers[_] + +msg := kubernetes.format(sprintf("Container '%s' of %s '%s' should add 'ALL' to 'securityContext.capabilities.drop'", [output.name, kubernetes.kind, kubernetes.name])) + +res := result.new(msg, output) +} + +`))).ScanReader( + context.TODO(), + "k8s.yaml", + strings.NewReader(` +apiVersion: v1 +kind: Pod +metadata: + name: hello-cpu-limit +spec: + containers: + - command: ["sh", "-c", "echo 'Hello' && sleep 1h"] + image: busybox + name: hello + securityContext: + capabilities: + drop: + - nothing +`)) + require.NoError(t, err) + + require.Greater(t, len(results.GetFailed()), 0) + + firstResult := results.GetFailed()[0] + assert.Equal(t, 8, firstResult.Metadata().Range().GetStartLine()) + assert.Equal(t, 14, firstResult.Metadata().Range().GetEndLine()) + assert.Equal(t, "k8s.yaml", firstResult.Metadata().Range().GetFilename()) +} + +func Test_checkPolicyIsApplicable(t *testing.T) { + srcFS := testutil.CreateFS(t, map[string]string{ + "policies/pod_policy.rego": `# METADATA +# title: "Process can elevate its own privileges" +# description: "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node." +# scope: package +# schemas: +# - input: schema["kubernetes"] +# related_resources: +# - https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted +# custom: +# id: KSV001 +# avd_id: AVD-KSV-0999 +# severity: MEDIUM +# short_code: no-self-privesc +# recommended_action: "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'." +# input: +# selector: +# - type: kubernetes +# subtypes: +# - kind: Pod +package builtin.kubernetes.KSV999 + +import data.lib.kubernetes +import data.lib.utils + +default checkAllowPrivilegeEscalation = false + +# getNoPrivilegeEscalationContainers returns the names of all containers which have +# securityContext.allowPrivilegeEscalation set to false. +getNoPrivilegeEscalationContainers[container] { + allContainers := kubernetes.containers[_] + allContainers.securityContext.allowPrivilegeEscalation == false + container := allContainers.name +} + +# getPrivilegeEscalationContainers returns the names of all containers which have +# securityContext.allowPrivilegeEscalation set to true or not set. +getPrivilegeEscalationContainers[container] { + containerName := kubernetes.containers[_].name + not getNoPrivilegeEscalationContainers[containerName] + container := kubernetes.containers[_] +} + +deny[res] { + output := getPrivilegeEscalationContainers[_] + msg := kubernetes.format(sprintf("Container '%s' of %s '%s' should set 'securityContext.allowPrivilegeEscalation' to false", [output.name, kubernetes.kind, kubernetes.name])) + res := result.new(msg, output) +} + +`, + "policies/namespace_policy.rego": `# METADATA +# title: "The default namespace should not be used" +# description: "ensure that default namespace should not be used" +# scope: package +# schemas: +# - input: schema["kubernetes"] +# related_resources: +# - https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ +# custom: +# id: KSV110 +# avd_id: AVD-KSV-0888 +# severity: LOW +# short_code: default-namespace-should-not-be-used +# recommended_action: "Ensure that namespaces are created to allow for appropriate segregation of Kubernetes resources and that all new resources are created in a specific namespace." +# input: +# selector: +# - type: kubernetes +# subtypes: +# - kind: Namespace +package builtin.kubernetes.KSV888 + +import data.lib.kubernetes + +default defaultNamespaceInUse = false + +defaultNamespaceInUse { + kubernetes.namespace == "default" +} + +deny[res] { + defaultNamespaceInUse + msg := sprintf("%s '%s' should not be set with 'default' namespace", [kubernetes.kind, kubernetes.name]) + res := result.new(msg, input.metadata.namespace) +} + +`, + "test/KSV001/pod.yaml": `apiVersion: v1 +kind: Pod +metadata: + name: hello-cpu-limit +spec: + containers: + - command: ["sh", "-c", "echo 'Hello' && sleep 1h"] + image: busybox + name: hello + securityContext: + capabilities: + drop: + - all +`, + }) + + scanner := NewScanner( + // options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), + options.ScannerWithEmbeddedLibraries(true), + options.ScannerWithPolicyDirs("policies/"), + options.ScannerWithPolicyFilesystem(srcFS), + ) + results, err := scanner.ScanFS(context.TODO(), srcFS, "test/KSV001") + require.NoError(t, err) + + require.NoError(t, err) + require.Len(t, results.GetFailed(), 1) + + failure := results.GetFailed()[0].Rule() + assert.Equal(t, "Process can elevate its own privileges", failure.Summary) +} diff --git a/pkg/iac/scanners/options/parser.go b/pkg/iac/scanners/options/parser.go new file mode 100644 index 000000000000..65ec41bb825d --- /dev/null +++ b/pkg/iac/scanners/options/parser.go @@ -0,0 +1,23 @@ +package options + +import "io" + +type ConfigurableParser interface { + SetDebugWriter(io.Writer) + SetSkipRequiredCheck(bool) +} + +type ParserOption func(s ConfigurableParser) + +func ParserWithSkipRequiredCheck(skip bool) ParserOption { + return func(s ConfigurableParser) { + s.SetSkipRequiredCheck(skip) + } +} + +// ParserWithDebug specifies an io.Writer for debug logs - if not set, they are discarded +func ParserWithDebug(w io.Writer) ParserOption { + return func(s ConfigurableParser) { + s.SetDebugWriter(w) + } +} diff --git a/pkg/iac/scanners/options/scanner.go b/pkg/iac/scanners/options/scanner.go new file mode 100644 index 000000000000..02c01be5c95a --- /dev/null +++ b/pkg/iac/scanners/options/scanner.go @@ -0,0 +1,128 @@ +package options + +import ( + "io" + "io/fs" + + "github.com/aquasecurity/trivy/pkg/iac/framework" +) + +type ConfigurableScanner interface { + SetDebugWriter(io.Writer) + SetTraceWriter(io.Writer) + SetPerResultTracingEnabled(bool) + SetPolicyDirs(...string) + SetDataDirs(...string) + SetPolicyNamespaces(...string) + SetSkipRequiredCheck(bool) + SetPolicyReaders([]io.Reader) + SetPolicyFilesystem(fs.FS) + SetDataFilesystem(fs.FS) + SetUseEmbeddedPolicies(bool) + SetFrameworks(frameworks []framework.Framework) + SetSpec(spec string) + SetRegoOnly(regoOnly bool) + SetRegoErrorLimit(limit int) + SetUseEmbeddedLibraries(bool) +} + +type ScannerOption func(s ConfigurableScanner) + +func ScannerWithFrameworks(frameworks ...framework.Framework) ScannerOption { + return func(s ConfigurableScanner) { + s.SetFrameworks(frameworks) + } +} + +func ScannerWithSpec(spec string) ScannerOption { + return func(s ConfigurableScanner) { + s.SetSpec(spec) + } +} + +func ScannerWithPolicyReader(readers ...io.Reader) ScannerOption { + return func(s ConfigurableScanner) { + s.SetPolicyReaders(readers) + } +} + +// ScannerWithDebug specifies an io.Writer for debug logs - if not set, they are discarded +func ScannerWithDebug(w io.Writer) ScannerOption { + return func(s ConfigurableScanner) { + s.SetDebugWriter(w) + } +} + +func ScannerWithEmbeddedPolicies(embedded bool) ScannerOption { + return func(s ConfigurableScanner) { + s.SetUseEmbeddedPolicies(embedded) + } +} + +func ScannerWithEmbeddedLibraries(enabled bool) ScannerOption { + return func(s ConfigurableScanner) { + s.SetUseEmbeddedLibraries(enabled) + } +} + +// ScannerWithTrace specifies an io.Writer for trace logs (mainly rego tracing) - if not set, they are discarded +func ScannerWithTrace(w io.Writer) ScannerOption { + return func(s ConfigurableScanner) { + s.SetTraceWriter(w) + } +} + +func ScannerWithPerResultTracing(enabled bool) ScannerOption { + return func(s ConfigurableScanner) { + s.SetPerResultTracingEnabled(enabled) + } +} + +func ScannerWithPolicyDirs(paths ...string) ScannerOption { + return func(s ConfigurableScanner) { + s.SetPolicyDirs(paths...) + } +} + +func ScannerWithDataDirs(paths ...string) ScannerOption { + return func(s ConfigurableScanner) { + s.SetDataDirs(paths...) + } +} + +// ScannerWithPolicyNamespaces - namespaces which indicate rego policies containing enforced rules +func ScannerWithPolicyNamespaces(namespaces ...string) ScannerOption { + return func(s ConfigurableScanner) { + s.SetPolicyNamespaces(namespaces...) + } +} + +func ScannerWithSkipRequiredCheck(skip bool) ScannerOption { + return func(s ConfigurableScanner) { + s.SetSkipRequiredCheck(skip) + } +} + +func ScannerWithPolicyFilesystem(f fs.FS) ScannerOption { + return func(s ConfigurableScanner) { + s.SetPolicyFilesystem(f) + } +} + +func ScannerWithDataFilesystem(f fs.FS) ScannerOption { + return func(s ConfigurableScanner) { + s.SetDataFilesystem(f) + } +} + +func ScannerWithRegoOnly(regoOnly bool) ScannerOption { + return func(s ConfigurableScanner) { + s.SetRegoOnly(regoOnly) + } +} + +func ScannerWithRegoErrorLimits(limit int) ScannerOption { + return func(s ConfigurableScanner) { + s.SetRegoErrorLimit(limit) + } +} diff --git a/pkg/iac/scanners/scanner.go b/pkg/iac/scanners/scanner.go new file mode 100644 index 000000000000..e792545b9e97 --- /dev/null +++ b/pkg/iac/scanners/scanner.go @@ -0,0 +1,21 @@ +package scanners + +import ( + "context" + "io/fs" + "os" + + "github.com/aquasecurity/trivy/pkg/iac/scan" +) + +type WriteFileFS interface { + WriteFile(name string, data []byte, perm os.FileMode) error +} + +type FSScanner interface { + // Name provides the human-readable name of the scanner e.g. "CloudFormation" + Name() string + // ScanFS scans the given filesystem for issues, starting at the provided directory. + // Use '.' to scan an entire filesystem. + ScanFS(ctx context.Context, fs fs.FS, dir string) (scan.Results, error) +} diff --git a/pkg/iac/scanners/terraform/attribute_test.go b/pkg/iac/scanners/terraform/attribute_test.go new file mode 100644 index 000000000000..e81486e938a2 --- /dev/null +++ b/pkg/iac/scanners/terraform/attribute_test.go @@ -0,0 +1,712 @@ +package terraform + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/stretchr/testify/assert" +) + +func Test_AttributeStartsWith(t *testing.T) { + var tests = []struct { + name string + source string + checkAttribute string + checkValue string + expectedResult bool + }{ + { + name: "bucket name starts with bucket", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" +}`, + checkAttribute: "bucket_name", + checkValue: "bucket", + expectedResult: true, + }, + { + name: "bucket acl starts with public", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" +}`, + checkAttribute: "acl", + checkValue: "public", + expectedResult: true, + }, + { + name: "bucket name doesn't start with secret", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" + logging { + target_bucket = aws_s3_bucket.log_bucket.id + target_prefix = "log/" + } +}`, + checkAttribute: "bucket_name", + checkValue: "secret_", + expectedResult: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + if !block.HasChild(test.checkAttribute) { + t.FailNow() + } + attr := block.GetAttribute(test.checkAttribute) + assert.Equal(t, test.expectedResult, attr.StartsWith(test.checkValue)) + } + } + }) + } +} + +func Test_AttributeEndsWith(t *testing.T) { + var tests = []struct { + name string + source string + checkAttribute string + checkValue string + expectedResult bool + }{ + { + name: "bucket name ends with Name", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" +}`, + checkAttribute: "bucket_name", + checkValue: "Name", + expectedResult: true, + }, + { + name: "bucket acl ends with read not Read", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" +}`, + checkAttribute: "acl", + checkValue: "Read", + expectedResult: false, + }, + { + name: "bucket name doesn't end with bucket", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" + logging { + target_bucket = aws_s3_bucket.log_bucket.id + target_prefix = "log/" + } +}`, + checkAttribute: "bucket_name", + checkValue: "_bucket", + expectedResult: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + if !block.HasChild(test.checkAttribute) { + t.FailNow() + } + attr := block.GetAttribute(test.checkAttribute) + assert.Equal(t, test.expectedResult, attr.EndsWith(test.checkValue)) + } + } + }) + } +} + +func Test_AttributeContains(t *testing.T) { + var tests = []struct { + name string + source string + checkAttribute string + checkValue string + expectedResult bool + ignoreCase bool + }{ + { + name: "bucket name contains Name", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" +}`, + checkAttribute: "bucket_name", + checkValue: "etNa", + expectedResult: true, + }, + { + name: "bucket acl doesn't contain private", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" +}`, + checkAttribute: "acl", + checkValue: "private", + expectedResult: false, + }, + { + name: "tags attribute is a map with a Department key", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" + tags = { + Department = "Finance" + } +}`, + checkAttribute: "tags", + checkValue: "Department", + expectedResult: true, + }, + { + name: "cidr_block has expected subnet", + source: ` +resource "aws_security_group" "my-security_group" { + cidr_block = ["10.0.0.0/16", "172.0.0.0/8" ] +}`, + checkAttribute: "cidr_block", + checkValue: "172.0.0.0/8", + expectedResult: true, + }, + { + name: "autoscaling group has propagated key defined 1st tag is present", + source: ` +resource "aws_autoscaling_group" "my-aws_autoscaling_group" { + tags = [ + { + "key" = "Name" + "propagate_at_launch" = "true" + "value" = "couchbase-seb-develop-dev" + }, + { + "key" = "app" + "propagate_at_launch" = "true" + "value" = "myapp" + } + ] +}`, + checkAttribute: "tags", + checkValue: "Name", + expectedResult: true, + }, + { + name: "autoscaling group has propagated key defined 2nd tag is present", + source: ` +resource "aws_autoscaling_group" "my-aws_autoscaling_group" { + tags = [ + { + "key" = "Name" + "propagate_at_launch" = "true" + "value" = "couchbase-seb-develop-dev" + }, + { + "key" = "app" + "propagate_at_launch" = "true" + "value" = "myapp" + } + ] +}`, + checkAttribute: "tags", + checkValue: "app", + expectedResult: true, + }, + { + name: "autoscaling group has propagated key defined and tag is not present", + source: ` +resource "aws_autoscaling_group" "my-aws_autoscaling_group" { + tags = [ + { + "key" = "Name" + "propagate_at_launch" = "true" + "value" = "couchbase-seb-develop-dev" + }, + { + "key" = "app" + "propagate_at_launch" = "true" + "value" = "myapp" + } + ] +}`, + checkAttribute: "tags", + checkValue: "NotThere", + expectedResult: false, + }, + { + name: "contains array of strings ignores case", + source: ` +resource "aws_security_group" "my-security_group" { + cidr_block = ["Foo", "Bar" ] +}`, + checkAttribute: "cidr_block", + checkValue: "foo", + expectedResult: true, + ignoreCase: true, + }, + { + name: "contains array of strings without ignore case", + source: ` +resource "aws_security_group" "my-security_group" { + cidr_block = ["Foo", "Bar" ] +}`, + checkAttribute: "cidr_block", + checkValue: "foo", + expectedResult: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, b := range module.GetBlocks() { + if !b.HasChild(test.checkAttribute) { + t.FailNow() + } + attr := b.GetAttribute(test.checkAttribute) + if test.ignoreCase { + assert.Equal(t, test.expectedResult, attr.Contains(test.checkValue, terraform.IgnoreCase)) + } else { + assert.Equal(t, test.expectedResult, attr.Contains(test.checkValue)) + } + } + } + }) + } +} + +func Test_AttributeIsAny(t *testing.T) { + var tests = []struct { + name string + source string + checkAttribute string + checkValue []interface{} + expectedResult bool + }{ + { + name: "bucket acl is not one of the specified acls", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" +}`, + checkAttribute: "acl", + checkValue: []interface{}{"private", "authenticated-read"}, + expectedResult: false, + }, + { + name: "bucket acl is one of the specified acls", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "private" +}`, + checkAttribute: "acl", + checkValue: []interface{}{"private", "authenticated-read"}, + expectedResult: true, + }, + { + name: "is is one of the provided valued", + source: ` +resource "aws_security_group" "my-security_group" { + count = 1 +}`, + checkAttribute: "count", + checkValue: []interface{}{1, 2}, + expectedResult: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + if !block.HasChild(test.checkAttribute) { + t.FailNow() + } + attr := block.GetAttribute(test.checkAttribute) + assert.Equal(t, test.expectedResult, attr.IsAny(test.checkValue...)) + } + } + }) + } +} + +func Test_AttributeIsNone(t *testing.T) { + var tests = []struct { + name string + source string + checkAttribute string + checkValue []interface{} + expectedResult bool + }{ + { + name: "bucket acl is not one of the specified acls", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" +}`, + checkAttribute: "acl", + checkValue: []interface{}{"private", "authenticated-read"}, + expectedResult: true, + }, + { + name: "bucket acl is one of the specified acls", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "private" +}`, + checkAttribute: "acl", + checkValue: []interface{}{"private", "authenticated-read"}, + expectedResult: false, + }, + { + name: "count is non-of the provided values", + source: ` +resource "aws_security_group" "my-security_group" { + count = 0 +}`, + checkAttribute: "count", + checkValue: []interface{}{1, 2}, + expectedResult: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + if !block.HasChild(test.checkAttribute) { + t.FailNow() + } + attr := block.GetAttribute(test.checkAttribute) + assert.Equal(t, test.expectedResult, attr.IsNone(test.checkValue...)) + } + } + }) + } +} + +func Test_AttributeIsEmpty(t *testing.T) { + var tests = []struct { + name string + source string + checkAttribute string + checkValue []interface{} + expectedResult bool + }{ + { + name: "bucket acl is not empty", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" +}`, + checkAttribute: "acl", + expectedResult: false, + }, + { + name: "bucket acl is empty", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "" +}`, + checkAttribute: "acl", + expectedResult: true, + }, + { + name: "tags is not empty", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" + tags = { + Department = "Finance" + } +}`, + checkAttribute: "tags", + expectedResult: false, + }, + { + name: "tags is empty", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" + tags = { + } +}`, + checkAttribute: "tags", + expectedResult: true, + }, + { + name: "cidr is not empty", + source: ` +resource "aws_security_group_rule" "example" { + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = "sg-123456" +}`, + checkAttribute: "cidr_blocks", + expectedResult: false, + }, + { + name: "cidr is empty", + source: ` +resource "aws_security_group_rule" "example" { + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = [] + security_group_id = "sg-123456" +}`, + checkAttribute: "cidr_blocks", + expectedResult: true, + }, + { + name: "from_port_is_not_empty", + source: ` +resource "aws_security_group_rule" "example" { + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = [] + security_group_id = "sg-123456" +}`, + checkAttribute: "from_port", + expectedResult: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + if !block.HasChild(test.checkAttribute) { + t.FailNow() + } + attr := block.GetAttribute(test.checkAttribute) + assert.Equal(t, test.expectedResult, attr.IsEmpty()) + } + } + }) + } +} + +func Test_AttributeIsLessThan(t *testing.T) { + var tests = []struct { + name string + source string + checkAttribute string + checkValue int + expectedResult bool + }{ + { + name: "check attribute is less than check value", + source: ` +resource "numerical_something" "my-bucket" { + value = 100 +}`, + checkAttribute: "value", + checkValue: 200, + expectedResult: true, + }, + { + name: "check attribute is not less than check value", + source: ` +resource "numerical_something" "my-bucket" { + value = 100 +}`, + checkAttribute: "value", + checkValue: 50, + expectedResult: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + if !block.HasChild(test.checkAttribute) { + t.FailNow() + } + attr := block.GetAttribute(test.checkAttribute) + assert.Equal(t, test.expectedResult, attr.LessThan(test.checkValue)) + } + } + }) + } +} + +func Test_AttributeIsLessThanOrEqual(t *testing.T) { + var tests = []struct { + name string + source string + checkAttribute string + checkValue int + expectedResult bool + }{ + { + name: "check attribute is less than or equal check value", + source: ` +resource "numerical_something" "my-bucket" { + value = 100 +}`, + checkAttribute: "value", + checkValue: 100, + expectedResult: true, + }, + { + name: "check attribute is not less than check value", + source: ` +resource "numerical_something" "my-bucket" { + value = 100 +}`, + checkAttribute: "value", + checkValue: 50, + expectedResult: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + if !block.HasChild(test.checkAttribute) { + t.FailNow() + } + attr := block.GetAttribute(test.checkAttribute) + assert.Equal(t, test.expectedResult, attr.LessThanOrEqualTo(test.checkValue)) + } + } + }) + } +} + +func Test_AttributeIsTrue(t *testing.T) { + var tests = []struct { + name string + source string + checkAttribute string + expectedResult bool + }{ + { + name: "check attribute is true", + source: ` +resource "boolean_something" "my-something" { + value = true +}`, + checkAttribute: "value", + expectedResult: true, + }, + { + name: "check attribute as string is true", + source: ` +resource "boolean_something" "my-something" { + value = "true" +}`, + checkAttribute: "value", + expectedResult: true, + }, + { + name: "check attribute as string is false", + source: ` +resource "boolean_something" "my-something" { + value = "true" +}`, + checkAttribute: "value", + expectedResult: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + if !block.HasChild(test.checkAttribute) { + t.FailNow() + } + attr := block.GetAttribute(test.checkAttribute) + assert.Equal(t, test.expectedResult, attr.IsTrue()) + } + } + }) + } +} + +func Test_AttributeIsFalse(t *testing.T) { + var tests = []struct { + name string + source string + checkAttribute string + expectedResult bool + }{ + { + name: "check attribute is false", + source: ` +resource "boolean_something" "my-something" { + value = false +}`, + checkAttribute: "value", + expectedResult: true, + }, + { + name: "check attribute as string is false", + source: ` +resource "boolean_something" "my-something" { + value = "false" +}`, + checkAttribute: "value", + expectedResult: true, + }, + { + name: "check attribute true", + source: ` +resource "boolean_something" "my-something" { + value = "true" +}`, + checkAttribute: "value", + expectedResult: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + if !block.HasChild(test.checkAttribute) { + t.FailNow() + } + attr := block.GetAttribute(test.checkAttribute) + assert.Equal(t, test.expectedResult, attr.IsFalse()) + } + } + }) + } +} diff --git a/pkg/iac/scanners/terraform/block_test.go b/pkg/iac/scanners/terraform/block_test.go new file mode 100644 index 000000000000..e65f224bafae --- /dev/null +++ b/pkg/iac/scanners/terraform/block_test.go @@ -0,0 +1,138 @@ +package terraform + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_IsPresentCheckOnBlock(t *testing.T) { + var tests = []struct { + name string + source string + expectedAttribute string + }{ + { + name: "expected attribute is present", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" +}`, + expectedAttribute: "bucket_name", + }, + { + name: "expected acl attribute is present", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" +}`, + expectedAttribute: "acl", + }, + { + name: "expected acl attribute is present", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" + logging { + target_bucket = aws_s3_bucket.log_bucket.id + target_prefix = "log/" + } +}`, + expectedAttribute: "logging", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + assert.Equal(t, block.HasChild(test.expectedAttribute), true) + assert.Equal(t, !block.HasChild(test.expectedAttribute), false) + } + } + }) + } +} + +func Test_IsNotPresentCheckOnBlock(t *testing.T) { + var tests = []struct { + name string + source string + expectedAttribute string + }{ + { + name: "expected attribute is not present", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + +}`, + expectedAttribute: "acl", + }, + { + name: "expected acl attribute is not present", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" + +}`, + expectedAttribute: "logging", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + assert.Equal(t, block.HasChild(test.expectedAttribute), false) + assert.Equal(t, !block.HasChild(test.expectedAttribute), true) + } + } + }) + } +} + +func Test_MissingChildNotFoundOnBlock(t *testing.T) { + var tests = []struct { + name string + source string + expectedAttribute string + }{ + { + name: "expected attribute is not present", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + +}`, + expectedAttribute: "acl", + }, + { + name: "expected acl attribute is not present", + source: ` +resource "aws_s3_bucket" "my-bucket" { + bucket_name = "bucketName" + acl = "public-read" + +}`, + expectedAttribute: "logging", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules := createModulesFromSource(t, test.source, ".tf") + for _, module := range modules { + for _, block := range module.GetBlocks() { + assert.Equal(t, block.MissingChild(test.expectedAttribute), true) + assert.Equal(t, !block.HasChild(test.expectedAttribute), true) + } + } + }) + } +} diff --git a/pkg/iac/scanners/terraform/count_test.go b/pkg/iac/scanners/terraform/count_test.go new file mode 100644 index 000000000000..2ce46c4388cc --- /dev/null +++ b/pkg/iac/scanners/terraform/count_test.go @@ -0,0 +1,193 @@ +package terraform + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/severity" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/stretchr/testify/assert" +) + +func Test_ResourcesWithCount(t *testing.T) { + var tests = []struct { + name string + source string + expectedResults int + }{ + { + name: "unspecified count defaults to 1", + source: ` + resource "bad" "this" {} +`, + expectedResults: 1, + }, + { + name: "count is literal 1", + source: ` + resource "bad" "this" { + count = 1 + } +`, + expectedResults: 1, + }, + { + name: "count is literal 99", + source: ` + resource "bad" "this" { + count = 99 + } +`, + expectedResults: 99, + }, + { + name: "count is literal 0", + source: ` + resource "bad" "this" { + count = 0 + } +`, + expectedResults: 0, + }, + { + name: "count is 0 from variable", + source: ` + variable "count" { + default = 0 + } + resource "bad" "this" { + count = var.count + } +`, + expectedResults: 0, + }, + { + name: "count is 1 from variable", + source: ` + variable "count" { + default = 1 + } + resource "bad" "this" { + count = var.count + } +`, + expectedResults: 1, + }, + { + name: "count is 1 from variable without default", + source: ` + variable "count" { + } + resource "bad" "this" { + count = var.count + } +`, + expectedResults: 1, + }, + { + name: "count is 0 from conditional", + source: ` + variable "enabled" { + default = false + } + resource "bad" "this" { + count = var.enabled ? 1 : 0 + } +`, + expectedResults: 0, + }, + { + name: "count is 1 from conditional", + source: ` + variable "enabled" { + default = true + } + resource "bad" "this" { + count = var.enabled ? 1 : 0 + } +`, + expectedResults: 1, + }, + { + name: "issue 962", + source: ` + resource "something" "else" { + count = 2 + ok = true + } + + resource "bad" "bad" { + secure = something.else[0].ok + } +`, + expectedResults: 0, + }, + { + name: "Test use of count.index", + source: ` +resource "bad" "thing" { + count = 1 + secure = var.things[count.index]["ok"] +} + +variable "things" { + description = "A list of maps that creates a number of sg" + type = list(map(string)) + + default = [ + { + ok = true + } + ] +} + `, + expectedResults: 0, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + r1 := scan.Rule{ + Provider: providers.AWSProvider, + Service: "service", + ShortCode: "abc123", + Severity: severity.High, + CustomChecks: scan.CustomChecks{ + Terraform: &scan.TerraformCustomCheck{ + RequiredLabels: []string{"bad"}, + Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { + if resourceBlock.GetAttribute("secure").IsTrue() { + return + } + results.Add( + "example problem", + resourceBlock, + ) + return + }, + }, + }, + } + reg := rules.Register(r1) + defer rules.Deregister(reg) + results := scanHCL(t, test.source) + var include string + var exclude string + if test.expectedResults > 0 { + include = r1.LongID() + } else { + exclude = r1.LongID() + } + assert.Equal(t, test.expectedResults, len(results.GetFailed())) + if include != "" { + testutil.AssertRuleFound(t, include, results, "false negative found") + } + if exclude != "" { + testutil.AssertRuleNotFound(t, exclude, results, "false positive found") + } + }) + } +} diff --git a/pkg/iac/scanners/terraform/deterministic_test.go b/pkg/iac/scanners/terraform/deterministic_test.go new file mode 100644 index 000000000000..258fe5bbbd16 --- /dev/null +++ b/pkg/iac/scanners/terraform/deterministic_test.go @@ -0,0 +1,50 @@ +package terraform + +import ( + "context" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" + "github.com/stretchr/testify/require" +) + +func Test_DeterministicResults(t *testing.T) { + + reg := rules.Register(badRule) + defer rules.Deregister(reg) + + fs := testutil.CreateFS(t, map[string]string{ + "first.tf": ` +resource "problem" "uhoh" { + bad = true + for_each = other.thing +} + `, + "second.tf": ` +resource "other" "thing" { + for_each = local.list +} + `, + "third.tf": ` +locals { + list = { + a = 1, + b = 2, + } +} + `, + }) + + for i := 0; i < 100; i++ { + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), ".") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + results, _ := executor.New().Execute(modules) + require.Len(t, results.GetFailed(), 2) + } +} diff --git a/pkg/iac/scanners/terraform/executor/executor.go b/pkg/iac/scanners/terraform/executor/executor.go new file mode 100644 index 000000000000..88dc1fa9801c --- /dev/null +++ b/pkg/iac/scanners/terraform/executor/executor.go @@ -0,0 +1,172 @@ +package executor + +import ( + "fmt" + "runtime" + "sort" + + "github.com/zclconf/go-cty/cty" + + adapter "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform" + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/ignore" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +// Executor scans HCL blocks by running all registered rules against them +type Executor struct { + workspaceName string + debug debug.Logger + resultsFilters []func(scan.Results) scan.Results + regoScanner *rego.Scanner + regoOnly bool + frameworks []framework.Framework +} + +// New creates a new Executor +func New(options ...Option) *Executor { + s := &Executor{ + regoOnly: false, + } + for _, option := range options { + option(s) + } + return s +} + +func (e *Executor) Execute(modules terraform.Modules) (scan.Results, error) { + + e.debug.Log("Adapting modules...") + infra := adapter.Adapt(modules) + e.debug.Log("Adapted %d module(s) into defsec state data.", len(modules)) + + threads := runtime.NumCPU() + if threads > 1 { + threads-- + } + + e.debug.Log("Using max routines of %d", threads) + + registeredRules := rules.GetRegistered(e.frameworks...) + e.debug.Log("Initialized %d rule(s).", len(registeredRules)) + + pool := NewPool(threads, registeredRules, modules, infra, e.regoScanner, e.regoOnly) + e.debug.Log("Created pool with %d worker(s) to apply rules.", threads) + + results, err := pool.Run() + if err != nil { + return nil, err + } + + e.debug.Log("Finished applying rules.") + + e.debug.Log("Applying ignores...") + var ignores ignore.Rules + for _, module := range modules { + ignores = append(ignores, module.Ignores()...) + } + + ignorers := map[string]ignore.Ignorer{ + "ws": workspaceIgnorer(e.workspaceName), + "ignore": attributeIgnorer(modules), + } + + results.Ignore(ignores, ignorers) + + for _, ignored := range results.GetIgnored() { + e.debug.Log("Ignored '%s' at '%s'.", ignored.Rule().LongID(), ignored.Range()) + } + + results = e.filterResults(results) + + e.sortResults(results) + return results, nil +} + +func (e *Executor) filterResults(results scan.Results) scan.Results { + if len(e.resultsFilters) > 0 && len(results) > 0 { + before := len(results.GetIgnored()) + e.debug.Log("Applying %d results filters to %d results...", len(results), before) + for _, filter := range e.resultsFilters { + results = filter(results) + } + e.debug.Log("Filtered out %d results.", len(results.GetIgnored())-before) + } + + return results +} + +func (e *Executor) sortResults(results []scan.Result) { + sort.Slice(results, func(i, j int) bool { + switch { + case results[i].Rule().LongID() < results[j].Rule().LongID(): + return true + case results[i].Rule().LongID() > results[j].Rule().LongID(): + return false + default: + return results[i].Range().String() > results[j].Range().String() + } + }) +} + +func ignoreByParams(params map[string]string, modules terraform.Modules, m *types.Metadata) bool { + if len(params) == 0 { + return true + } + block := modules.GetBlockByIgnoreRange(m) + if block == nil { + return true + } + for key, val := range params { + attr, _ := block.GetNestedAttribute(key) + if attr.IsNil() || !attr.Value().IsKnown() { + return false + } + switch attr.Type() { + case cty.String: + if !attr.Equals(val) { + return false + } + case cty.Number: + bf := attr.Value().AsBigFloat() + f64, _ := bf.Float64() + comparableInt := fmt.Sprintf("%d", int(f64)) + comparableFloat := fmt.Sprintf("%f", f64) + if val != comparableInt && val != comparableFloat { + return false + } + case cty.Bool: + if fmt.Sprintf("%t", attr.IsTrue()) != val { + return false + } + default: + return false + } + } + return true +} + +func workspaceIgnorer(ws string) ignore.Ignorer { + return func(_ types.Metadata, param any) bool { + ignoredWorkspace, ok := param.(string) + if !ok { + return false + } + return ignore.MatchPattern(ws, ignoredWorkspace) + } +} + +func attributeIgnorer(modules terraform.Modules) ignore.Ignorer { + return func(resultMeta types.Metadata, param any) bool { + params, ok := param.(map[string]string) + if !ok { + return false + } + return ignoreByParams(params, modules, &resultMeta) + } +} diff --git a/pkg/iac/scanners/terraform/executor/executor_test.go b/pkg/iac/scanners/terraform/executor/executor_test.go new file mode 100644 index 000000000000..ac663c313c17 --- /dev/null +++ b/pkg/iac/scanners/terraform/executor/executor_test.go @@ -0,0 +1,131 @@ +package executor + +import ( + "context" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" + "github.com/aquasecurity/trivy/pkg/iac/severity" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var panicRule = scan.Rule{ + Provider: providers.AWSProvider, + Service: "service", + ShortCode: "abc", + Severity: severity.High, + CustomChecks: scan.CustomChecks{ + Terraform: &scan.TerraformCustomCheck{ + RequiredTypes: []string{"resource"}, + RequiredLabels: []string{"problem"}, + Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { + if resourceBlock.GetAttribute("panic").IsTrue() { + panic("This is fine") + } + return + }, + }, + }, +} + +func Test_PanicInCheckNotAllowed(t *testing.T) { + + reg := rules.Register(panicRule) + defer rules.Deregister(reg) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +resource "problem" "this" { + panic = true +} +`, + }) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := New().Execute(modules) + assert.Error(t, err) + + assert.Equal(t, len(results.GetFailed()), 0) +} + +func Test_PanicInCheckAllowed(t *testing.T) { + + reg := rules.Register(panicRule) + defer rules.Deregister(reg) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +resource "problem" "this" { + panic = true +} +`, + }) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + _, err = New().Execute(modules) + assert.Error(t, err) +} + +func Test_PanicNotInCheckNotIncludePassed(t *testing.T) { + + reg := rules.Register(panicRule) + defer rules.Deregister(reg) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +resource "problem" "this" { + panic = true +} +`, + }) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, _ := New().Execute(modules) + require.NoError(t, err) + + assert.Equal(t, len(results.GetFailed()), 0) +} + +func Test_PanicNotInCheckNotIncludePassedStopOnError(t *testing.T) { + + reg := rules.Register(panicRule) + defer rules.Deregister(reg) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +resource "problem" "this" { + panic = true +} +`, + }) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + _, err = New().Execute(modules) + assert.Error(t, err) +} diff --git a/pkg/iac/scanners/terraform/executor/option.go b/pkg/iac/scanners/terraform/executor/option.go new file mode 100644 index 000000000000..a58d72867b54 --- /dev/null +++ b/pkg/iac/scanners/terraform/executor/option.go @@ -0,0 +1,48 @@ +package executor + +import ( + "io" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/scan" +) + +type Option func(s *Executor) + +func OptionWithFrameworks(frameworks ...framework.Framework) Option { + return func(s *Executor) { + s.frameworks = frameworks + } +} + +func OptionWithResultsFilter(f func(scan.Results) scan.Results) Option { + return func(s *Executor) { + s.resultsFilters = append(s.resultsFilters, f) + } +} + +func OptionWithDebugWriter(w io.Writer) Option { + return func(s *Executor) { + s.debug = debug.New(w, "terraform", "executor") + } +} + +func OptionWithWorkspaceName(workspaceName string) Option { + return func(s *Executor) { + s.workspaceName = workspaceName + } +} + +func OptionWithRegoScanner(s *rego.Scanner) Option { + return func(e *Executor) { + e.regoScanner = s + } +} + +func OptionWithRegoOnly(regoOnly bool) Option { + return func(e *Executor) { + e.regoOnly = regoOnly + } +} diff --git a/pkg/iac/scanners/terraform/executor/pool.go b/pkg/iac/scanners/terraform/executor/pool.go new file mode 100644 index 000000000000..69b8405ee3a7 --- /dev/null +++ b/pkg/iac/scanners/terraform/executor/pool.go @@ -0,0 +1,289 @@ +package executor + +import ( + "context" + "fmt" + "os" + "path/filepath" + runtimeDebug "runtime/debug" + "strings" + "sync" + + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/state" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + types "github.com/aquasecurity/trivy/pkg/iac/types/rules" +) + +type Pool struct { + size int + modules terraform.Modules + state *state.State + rules []types.RegisteredRule + rs *rego.Scanner + regoOnly bool +} + +func NewPool(size int, rules []types.RegisteredRule, modules terraform.Modules, st *state.State, regoScanner *rego.Scanner, regoOnly bool) *Pool { + return &Pool{ + size: size, + rules: rules, + state: st, + modules: modules, + rs: regoScanner, + regoOnly: regoOnly, + } +} + +// Run runs the job in the pool - this will only return an error if a job panics +func (p *Pool) Run() (scan.Results, error) { + + outgoing := make(chan Job, p.size*2) + + var workers []*Worker + for i := 0; i < p.size; i++ { + worker := NewWorker(outgoing) + go worker.Start() + workers = append(workers, worker) + } + + if p.rs != nil { + var basePath string + if len(p.modules) > 0 { + basePath = p.modules[0].RootPath() + } + outgoing <- ®oJob{ + state: p.state, + scanner: p.rs, + basePath: basePath, + } + } + + if !p.regoOnly { + for _, r := range p.rules { + if r.GetRule().CustomChecks.Terraform != nil && r.GetRule().CustomChecks.Terraform.Check != nil { + // run local hcl rule + for _, module := range p.modules { + mod := *module + outgoing <- &hclModuleRuleJob{ + module: &mod, + rule: r, + } + } + } else { + // run defsec rule + outgoing <- &infraRuleJob{ + state: p.state, + rule: r, + } + } + } + } + + close(outgoing) + + var results scan.Results + for _, worker := range workers { + results = append(results, worker.Wait()...) + if err := worker.Error(); err != nil { + return nil, err + } + } + + return results, nil +} + +type Job interface { + Run() (scan.Results, error) +} + +type infraRuleJob struct { + state *state.State + rule types.RegisteredRule +} + +type hclModuleRuleJob struct { + module *terraform.Module + rule types.RegisteredRule +} + +type regoJob struct { + state *state.State + scanner *rego.Scanner + basePath string +} + +func (h *infraRuleJob) Run() (_ scan.Results, err error) { + defer func() { + if panicErr := recover(); panicErr != nil { + err = fmt.Errorf("%s\n%s", panicErr, string(runtimeDebug.Stack())) + } + }() + + return h.rule.Evaluate(h.state), err +} + +func (h *hclModuleRuleJob) Run() (results scan.Results, err error) { + defer func() { + if panicErr := recover(); panicErr != nil { + err = fmt.Errorf("%s\n%s", panicErr, string(runtimeDebug.Stack())) + } + }() + customCheck := h.rule.GetRule().CustomChecks.Terraform + for _, block := range h.module.GetBlocks() { + if !isCustomCheckRequiredForBlock(customCheck, block) { + continue + } + results = append(results, customCheck.Check(block, h.module)...) + } + results.SetRule(h.rule.GetRule()) + return +} + +func (h *regoJob) Run() (results scan.Results, err error) { + regoResults, err := h.scanner.ScanInput(context.TODO(), rego.Input{ + Contents: h.state.ToRego(), + Path: h.basePath, + }) + if err != nil { + return nil, fmt.Errorf("rego scan error: %w", err) + } + return regoResults, nil +} + +// nolint +func isCustomCheckRequiredForBlock(custom *scan.TerraformCustomCheck, b *terraform.Block) bool { + + var found bool + for _, requiredType := range custom.RequiredTypes { + if b.Type() == requiredType { + found = true + break + } + } + if !found && len(custom.RequiredTypes) > 0 { + return false + } + + found = false + for _, requiredLabel := range custom.RequiredLabels { + if requiredLabel == "*" || (len(b.Labels()) > 0 && wildcardMatch(requiredLabel, b.TypeLabel())) { + found = true + break + } + } + if !found && len(custom.RequiredLabels) > 0 { + return false + } + + found = false + if len(custom.RequiredSources) > 0 && b.Type() == terraform.TypeModule.Name() { + if sourceAttr := b.GetAttribute("source"); sourceAttr.IsNotNil() { + values := sourceAttr.AsStringValues().AsStrings() + if len(values) == 0 { + return false + } + sourcePath := values[0] + + // resolve module source path to path relative to cwd + if strings.HasPrefix(sourcePath, ".") { + sourcePath = cleanPathRelativeToWorkingDir(filepath.Dir(b.GetMetadata().Range().GetFilename()), sourcePath) + } + + for _, requiredSource := range custom.RequiredSources { + if requiredSource == "*" || wildcardMatch(requiredSource, sourcePath) { + found = true + break + } + } + } + return found + } + + return true +} + +func cleanPathRelativeToWorkingDir(dir, path string) string { + absPath := filepath.Clean(filepath.Join(dir, path)) + wDir, err := os.Getwd() + if err != nil { + return absPath + } + relPath, err := filepath.Rel(wDir, absPath) + if err != nil { + return absPath + } + return relPath +} + +func wildcardMatch(pattern, subject string) bool { + if pattern == "" { + return false + } + parts := strings.Split(pattern, "*") + var lastIndex int + for i, part := range parts { + if part == "" { + continue + } + if i == 0 { + if !strings.HasPrefix(subject, part) { + return false + } + } + if i == len(parts)-1 { + if !strings.HasSuffix(subject, part) { + return false + } + } + newIndex := strings.Index(subject, part) + if newIndex < lastIndex { + return false + } + lastIndex = newIndex + } + return true +} + +type Worker struct { + incoming <-chan Job + mu sync.Mutex + results scan.Results + panic interface{} +} + +func NewWorker(incoming <-chan Job) *Worker { + w := &Worker{ + incoming: incoming, + } + w.mu.Lock() + return w +} + +func (w *Worker) Start() { + defer w.mu.Unlock() + w.results = nil + for job := range w.incoming { + func() { + results, err := job.Run() + if err != nil { + w.panic = err + } + w.results = append(w.results, results...) + }() + } +} + +func (w *Worker) Wait() scan.Results { + w.mu.Lock() + defer w.mu.Unlock() + return w.results +} + +func (w *Worker) Error() error { + if w.panic == nil { + return nil + } + return fmt.Errorf("job failed: %s", w.panic) +} diff --git a/pkg/iac/scanners/terraform/fs_test.go b/pkg/iac/scanners/terraform/fs_test.go new file mode 100644 index 000000000000..117b3c17ac6e --- /dev/null +++ b/pkg/iac/scanners/terraform/fs_test.go @@ -0,0 +1,22 @@ +package terraform + +import ( + "context" + "os" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_OS_FS(t *testing.T) { + s := New( + options.ScannerWithDebug(os.Stderr), + options.ScannerWithEmbeddedPolicies(true), + options.ScannerWithEmbeddedLibraries(true), + ) + results, err := s.ScanFS(context.TODO(), os.DirFS("testdata"), "fail") + require.NoError(t, err) + assert.Greater(t, len(results.GetFailed()), 0) +} diff --git a/pkg/iac/scanners/terraform/ignore_test.go b/pkg/iac/scanners/terraform/ignore_test.go new file mode 100644 index 000000000000..ddddd7a6e04e --- /dev/null +++ b/pkg/iac/scanners/terraform/ignore_test.go @@ -0,0 +1,749 @@ +package terraform + +import ( + "fmt" + "strings" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/severity" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/stretchr/testify/assert" +) + +var exampleRule = scan.Rule{ + Provider: providers.AWSProvider, + Service: "service", + ShortCode: "abc123", + AVDID: "AWS-ABC-123", + Aliases: []string{"aws-other-abc123"}, + Severity: severity.High, + CustomChecks: scan.CustomChecks{ + Terraform: &scan.TerraformCustomCheck{ + RequiredLabels: []string{"bad"}, + Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { + if attr, _ := resourceBlock.GetNestedAttribute("secure_settings.enabled"); attr.IsNotNil() { + if attr.IsFalse() { + results.Add("example problem", attr) + } + } else { + attr := resourceBlock.GetAttribute("secure") + if attr.IsNil() { + results.Add("example problem", resourceBlock) + } + if attr.IsFalse() { + results.Add("example problem", attr) + } + } + return + }, + }, + }, +} + +func Test_IgnoreAll(t *testing.T) { + + var testCases = []struct { + name string + inputOptions string + assertLength int + }{ + { + name: "IgnoreAll", + inputOptions: ` +resource "bad" "my-rule" { + secure = false // tfsec:ignore:* +} +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheBlock", + inputOptions: ` +// tfsec:ignore:* +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheBlockMatchingParamBool", + inputOptions: ` +// tfsec:ignore:*[secure=false] +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheBlockNotMatchingParamBool", + inputOptions: ` +// tfsec:ignore:*[secure=true] +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 1, + }, + { + name: "IgnoreLineAboveTheBlockMatchingParamString", + inputOptions: ` +// tfsec:ignore:*[name=myrule] +resource "bad" "my-rule" { + name = "myrule" + secure = false +} +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheBlockNotMatchingParamString", + inputOptions: ` +// tfsec:ignore:*[name=myrule2] +resource "bad" "my-rule" { + name = "myrule" + secure = false +} +`, + assertLength: 1, + }, + { + name: "IgnoreLineAboveTheBlockMatchingParamInt", + inputOptions: ` +// tfsec:ignore:*[port=123] +resource "bad" "my-rule" { + secure = false + port = 123 +} +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheBlockNotMatchingParamInt", + inputOptions: ` +// tfsec:ignore:*[port=456] +resource "bad" "my-rule" { + secure = false + port = 123 +} +`, + assertLength: 1, + }, + { + name: "IgnoreLineStackedAboveTheBlock", + inputOptions: ` +// tfsec:ignore:* +// tfsec:ignore:a +// tfsec:ignore:b +// tfsec:ignore:c +// tfsec:ignore:d +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 0, + }, + { + name: "IgnoreLineStackedAboveTheBlockWithoutMatch", + inputOptions: ` +#tfsec:ignore:* + +#tfsec:ignore:x +#tfsec:ignore:a +#tfsec:ignore:b +#tfsec:ignore:c +#tfsec:ignore:d +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 1, + }, + { + name: "IgnoreLineStackedAboveTheBlockWithHashesWithoutSpaces", + inputOptions: ` +#tfsec:ignore:* +#tfsec:ignore:a +#tfsec:ignore:b +#tfsec:ignore:c +#tfsec:ignore:d +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 0, + }, + { + name: "IgnoreLineStackedAboveTheBlockWithoutSpaces", + inputOptions: ` +//tfsec:ignore:* +//tfsec:ignore:a +//tfsec:ignore:b +//tfsec:ignore:c +//tfsec:ignore:d +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheLine", + inputOptions: ` +resource "bad" "my-rule" { + # tfsec:ignore:aws-service-abc123 + secure = false +} +`, + assertLength: 0, + }, + { + name: "IgnoreWithExpDateIfDateBreachedThenDontIgnore", + inputOptions: ` +resource "bad" "my-rule" { + secure = false # tfsec:ignore:aws-service-abc123:exp:2000-01-02 +} +`, + assertLength: 1, + }, + { + name: "IgnoreWithExpDateIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` +resource "bad" "my-rule" { + secure = false # tfsec:ignore:aws-service-abc123:exp:2221-01-02 +} +`, + assertLength: 0, + }, + { + name: "IgnoreWithExpDateIfDateInvalidThenDropTheIgnore", + inputOptions: ` +resource "bad" "my-rule" { + secure = false # tfsec:ignore:aws-service-abc123:exp:2221-13-02 +} +`, + assertLength: 1, + }, + { + name: "IgnoreAboveResourceBlockWithExpDateIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` +#tfsec:ignore:aws-service-abc123:exp:2221-01-02 +resource "bad" "my-rule" { +} +`, + assertLength: 0, + }, + { + name: "IgnoreAboveResourceBlockWithExpDateAndMultipleIgnoresIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` +# tfsec:ignore:aws-service-abc123:exp:2221-01-02 +resource "bad" "my-rule" { + +} +`, + assertLength: 0, + }, + { + name: "IgnoreForImpliedIAMResource", + inputOptions: ` +terraform { + required_version = "~> 1.1.6" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.48" + } + } +} + +# Retrieve an IAM group defined outside of this Terraform config. + +# tfsec:ignore:aws-iam-enforce-mfa +data "aws_iam_group" "externally_defined_group" { + group_name = "group-name" # tfsec:ignore:aws-iam-enforce-mfa +} + +# Create an IAM policy and attach it to the group. + +# tfsec:ignore:aws-iam-enforce-mfa +resource "aws_iam_policy" "test_policy" { + name = "test-policy" # tfsec:ignore:aws-iam-enforce-mfa + policy = data.aws_iam_policy_document.test_policy.json # tfsec:ignore:aws-iam-enforce-mfa +} + +# tfsec:ignore:aws-iam-enforce-mfa +resource "aws_iam_group_policy_attachment" "test_policy_attachment" { + group = data.aws_iam_group.externally_defined_group.group_name # tfsec:ignore:aws-iam-enforce-mfa + policy_arn = aws_iam_policy.test_policy.arn # tfsec:ignore:aws-iam-enforce-mfa +} + +# tfsec:ignore:aws-iam-enforce-mfa +data "aws_iam_policy_document" "test_policy" { + statement { + sid = "PublishToCloudWatch" # tfsec:ignore:aws-iam-enforce-mfa + actions = [ + "cloudwatch:PutMetricData", # tfsec:ignore:aws-iam-enforce-mfa + ] + resources = ["*"] # tfsec:ignore:aws-iam-enforce-mfa + } +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreAll", + inputOptions: ` +resource "bad" "my-rule" { + secure = false // trivy:ignore:* +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheBlock", + inputOptions: ` +// trivy:ignore:* +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheBlockMatchingParamBool", + inputOptions: ` +// trivy:ignore:*[secure=false] +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheBlockNotMatchingParamBool", + inputOptions: ` +// trivy:ignore:*[secure=true] +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 1, + }, + { + name: "TrivyIgnoreLineAboveTheBlockMatchingParamString", + inputOptions: ` +// trivy:ignore:*[name=myrule] +resource "bad" "my-rule" { + name = "myrule" + secure = false +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheBlockNotMatchingParamString", + inputOptions: ` +// trivy:ignore:*[name=myrule2] +resource "bad" "my-rule" { + name = "myrule" + secure = false +} +`, + assertLength: 1, + }, + { + name: "TrivyIgnoreLineAboveTheBlockMatchingParamInt", + inputOptions: ` +// trivy:ignore:*[port=123] +resource "bad" "my-rule" { + secure = false + port = 123 +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheBlockNotMatchingParamInt", + inputOptions: ` +// trivy:ignore:*[port=456] +resource "bad" "my-rule" { + secure = false + port = 123 +} +`, + assertLength: 1, + }, + { + name: "ignore by nested attribute", + inputOptions: ` +// trivy:ignore:*[secure_settings.enabled=false] +resource "bad" "my-rule" { + secure_settings { + enabled = false + } +} +`, + assertLength: 0, + }, + { + name: "ignore by nested attribute of another type", + inputOptions: ` +// trivy:ignore:*[secure_settings.enabled=1] +resource "bad" "my-rule" { + secure_settings { + enabled = false + } +} +`, + assertLength: 1, + }, + { + name: "ignore by non-existent nested attribute", + inputOptions: ` +// trivy:ignore:*[secure_settings.rule=myrule] +resource "bad" "my-rule" { + secure_settings { + enabled = false + } +} +`, + assertLength: 1, + }, + { + name: "ignore resource with `for_each` meta-argument", + inputOptions: ` +// trivy:ignore:*[secure=false] +resource "bad" "my-rule" { + for_each = toset(["false", "true", "false"]) + secure = each.key +} +`, + assertLength: 0, + }, + { + name: "ignore by dynamic block value", + inputOptions: ` +// trivy:ignore:*[secure_settings.enabled=false] +resource "bad" "my-rule" { + dynamic "secure_settings" { + for_each = ["false", "true"] + content { + enabled = secure_settings.value + } + } +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineStackedAboveTheBlock", + inputOptions: ` +// trivy:ignore:* +// trivy:ignore:a +// trivy:ignore:b +// trivy:ignore:c +// trivy:ignore:d +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineStackedAboveTheBlockWithoutMatch", + inputOptions: ` +#trivy:ignore:* + +#trivy:ignore:x +#trivy:ignore:a +#trivy:ignore:b +#trivy:ignore:c +#trivy:ignore:d +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 1, + }, + { + name: "TrivyIgnoreLineStackedAboveTheBlockWithHashesWithoutSpaces", + inputOptions: ` +#trivy:ignore:* +#trivy:ignore:a +#trivy:ignore:b +#trivy:ignore:c +#trivy:ignore:d +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineStackedAboveTheBlockWithoutSpaces", + inputOptions: ` +//trivy:ignore:* +//trivy:ignore:a +//trivy:ignore:b +//trivy:ignore:c +//trivy:ignore:d +resource "bad" "my-rule" { + secure = false +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheLine", + inputOptions: ` +resource "bad" "my-rule" { + # trivy:ignore:aws-service-abc123 + secure = false +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreWithExpDateIfDateBreachedThenDontIgnore", + inputOptions: ` +resource "bad" "my-rule" { + secure = false # trivy:ignore:aws-service-abc123:exp:2000-01-02 +} +`, + assertLength: 1, + }, + { + name: "TrivyIgnoreWithExpDateIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` +resource "bad" "my-rule" { + secure = false # trivy:ignore:aws-service-abc123:exp:2221-01-02 +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreWithExpDateIfDateInvalidThenDropTheIgnore", + inputOptions: ` +resource "bad" "my-rule" { + secure = false # trivy:ignore:aws-service-abc123:exp:2221-13-02 +} +`, + assertLength: 1, + }, + { + name: "TrivyIgnoreAboveResourceBlockWithExpDateIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` +#trivy:ignore:aws-service-abc123:exp:2221-01-02 +resource "bad" "my-rule" { +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreAboveResourceBlockWithExpDateAndMultipleIgnoresIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` +# trivy:ignore:aws-service-abc123:exp:2221-01-02 +resource "bad" "my-rule" { + +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreForImpliedIAMResource", + inputOptions: ` +terraform { + required_version = "~> 1.1.6" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.48" + } + } +} + +# Retrieve an IAM group defined outside of this Terraform config. + +# trivy:ignore:aws-iam-enforce-mfa +data "aws_iam_group" "externally_defined_group" { + group_name = "group-name" # trivy:ignore:aws-iam-enforce-mfa +} + +# Create an IAM policy and attach it to the group. + +# trivy:ignore:aws-iam-enforce-mfa +resource "aws_iam_policy" "test_policy" { + name = "test-policy" # trivy:ignore:aws-iam-enforce-mfa + policy = data.aws_iam_policy_document.test_policy.json # trivy:ignore:aws-iam-enforce-mfa +} + +# trivy:ignore:aws-iam-enforce-mfa +resource "aws_iam_group_policy_attachment" "test_policy_attachment" { + group = data.aws_iam_group.externally_defined_group.group_name # trivy:ignore:aws-iam-enforce-mfa + policy_arn = aws_iam_policy.test_policy.arn # trivy:ignore:aws-iam-enforce-mfa +} + +# trivy:ignore:aws-iam-enforce-mfa +data "aws_iam_policy_document" "test_policy" { + statement { + sid = "PublishToCloudWatch" # trivy:ignore:aws-iam-enforce-mfa + actions = [ + "cloudwatch:PutMetricData", # trivy:ignore:aws-iam-enforce-mfa + ] + resources = ["*"] # trivy:ignore:aws-iam-enforce-mfa + } +} +`, + assertLength: 0, + }, + } + + reg := rules.Register(exampleRule) + defer rules.Deregister(reg) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + results := scanHCL(t, tc.inputOptions) + assert.Len(t, results.GetFailed(), tc.assertLength) + }) + } +} + +func Test_IgnoreByWorkspace(t *testing.T) { + reg := rules.Register(exampleRule) + defer rules.Deregister(reg) + + tests := []struct { + name string + src string + expectedFailed int + }{ + { + name: "with expiry and workspace", + src: `# tfsec:ignore:aws-service-abc123:exp:2221-01-02:ws:testworkspace +resource "bad" "my-rule" {}`, + expectedFailed: 0, + }, + { + name: "bad workspace", + src: `# tfsec:ignore:aws-service-abc123:exp:2221-01-02:ws:otherworkspace +resource "bad" "my-rule" {}`, + expectedFailed: 1, + }, + { + name: "with expiry and workspace, trivy prefix", + src: `# trivy:ignore:aws-service-abc123:exp:2221-01-02:ws:testworkspace +resource "bad" "my-rule" {}`, + expectedFailed: 0, + }, + { + name: "bad workspace, trivy prefix", + src: `# trivy:ignore:aws-service-abc123:exp:2221-01-02:ws:otherworkspace +resource "bad" "my-rule" {}`, + expectedFailed: 1, + }, + { + name: "workspace with wildcard", + src: `# tfsec:ignore:*:ws:test* +resource "bad" "my-rule" {}`, + expectedFailed: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + results := scanHCLWithWorkspace(t, tt.src, "testworkspace") + assert.Len(t, results.GetFailed(), tt.expectedFailed) + }) + } +} + +func Test_IgnoreInline(t *testing.T) { + reg := rules.Register(exampleRule) + defer rules.Deregister(reg) + + results := scanHCL(t, fmt.Sprintf(` + resource "bad" "sample" { + secure = false # tfsec:ignore:%s + } + `, exampleRule.LongID())) + assert.Len(t, results.GetFailed(), 0) +} + +func Test_IgnoreWithAliasCodeStillIgnored(t *testing.T) { + reg := rules.Register(exampleRule) + defer rules.Deregister(reg) + + results := scanHCLWithWorkspace(t, ` +# tfsec:ignore:aws-other-abc123 +resource "bad" "my-rule" { + +} +`, "testworkspace") + assert.Len(t, results.GetFailed(), 0) +} + +func Test_TrivyIgnoreWithAliasCodeStillIgnored(t *testing.T) { + reg := rules.Register(exampleRule) + defer rules.Deregister(reg) + + results := scanHCLWithWorkspace(t, ` +# trivy:ignore:aws-other-abc123 +resource "bad" "my-rule" { + +} +`, "testworkspace") + assert.Len(t, results.GetFailed(), 0) +} + +func Test_TrivyIgnoreInline(t *testing.T) { + reg := rules.Register(exampleRule) + defer rules.Deregister(reg) + + results := scanHCL(t, fmt.Sprintf(` + resource "bad" "sample" { + secure = false # trivy:ignore:%s + } + `, exampleRule.LongID())) + assert.Len(t, results.GetFailed(), 0) +} + +func Test_IgnoreInlineByAVDID(t *testing.T) { + testCases := []struct { + input string + }{ + { + input: ` + resource "bad" "sample" { + secure = false # tfsec:ignore:%s + } + `, + }, + { + input: ` + resource "bad" "sample" { + secure = false # trivy:ignore:%s + } + `, + }, + } + + for _, tc := range testCases { + tc := tc + for _, id := range []string{exampleRule.AVDID, strings.ToLower(exampleRule.AVDID), exampleRule.ShortCode, exampleRule.LongID()} { + id := id + t.Run("", func(t *testing.T) { + reg := rules.Register(exampleRule) + defer rules.Deregister(reg) + results := scanHCL(t, fmt.Sprintf(tc.input, id)) + assert.Len(t, results.GetFailed(), 0) + }) + } + } +} diff --git a/pkg/iac/scanners/terraform/json_test.go b/pkg/iac/scanners/terraform/json_test.go new file mode 100644 index 000000000000..835425265f17 --- /dev/null +++ b/pkg/iac/scanners/terraform/json_test.go @@ -0,0 +1,103 @@ +package terraform + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/severity" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func TestScanningJSON(t *testing.T) { + + var tests = []struct { + name string + source string + shouldFail bool + }{ + { + name: "check results are picked up in tf json configs", + source: ` + { + "provider": { + "aws": { + "profile": null, + "region": "eu-west-1" + } + }, + "resource": { + "bad": { + "thing": { + "type": "ingress", + "cidr_blocks": ["0.0.0.0/0"], + "description": "testing" + } + } + } + }`, + shouldFail: true, + }, + { + name: "check attributes are checked in tf json configs", + source: ` + { + "provider": { + "aws": { + "profile": null, + "region": "eu-west-1" + } + }, + "resource": { + "bad": { + "or_not": { + "secure": true + } + } + } + }`, + shouldFail: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + r1 := scan.Rule{ + Provider: providers.AWSProvider, + Service: "service", + ShortCode: "abc123", + Severity: severity.High, + CustomChecks: scan.CustomChecks{ + Terraform: &scan.TerraformCustomCheck{ + RequiredLabels: []string{"bad"}, + Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { + if resourceBlock.GetAttribute("secure").IsTrue() { + return + } + results.Add("something", resourceBlock) + return + }, + }, + }, + } + reg := rules.Register(r1) + defer rules.Deregister(reg) + + results := scanJSON(t, test.source) + var include, exclude string + if test.shouldFail { + include = r1.LongID() + } else { + exclude = r1.LongID() + } + if include != "" { + testutil.AssertRuleFound(t, include, results, "false negative found") + } + if exclude != "" { + testutil.AssertRuleNotFound(t, exclude, results, "false positive found") + } + }) + } +} diff --git a/pkg/iac/scanners/terraform/module_test.go b/pkg/iac/scanners/terraform/module_test.go new file mode 100644 index 000000000000..61b1a0e359f6 --- /dev/null +++ b/pkg/iac/scanners/terraform/module_test.go @@ -0,0 +1,673 @@ +package terraform + +import ( + "bytes" + "context" + "fmt" + "os" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" + "github.com/aquasecurity/trivy/pkg/iac/severity" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-policies/checks/cloud/aws/iam" +) + +var badRule = scan.Rule{ + Provider: providers.AWSProvider, + Service: "service", + ShortCode: "abc", + Summary: "A stupid example check for a test.", + Impact: "You will look stupid", + Resolution: "Don't do stupid stuff", + Explanation: "Bad should not be set.", + Severity: severity.High, + CustomChecks: scan.CustomChecks{ + Terraform: &scan.TerraformCustomCheck{ + RequiredTypes: []string{"resource"}, + RequiredLabels: []string{"problem"}, + Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { + if attr := resourceBlock.GetAttribute("bad"); attr.IsTrue() { + results.Add("bad", attr) + } + return + }, + }, + }, +} + +// IMPORTANT: if this test is failing, you probably need to set the version of go-cty in go.mod to the same version that hcl uses. +func Test_GoCtyCompatibilityIssue(t *testing.T) { + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "/project/main.tf": ` +data "aws_vpc" "default" { + default = true +} + +module "test" { + source = "../modules/problem/" + cidr_block = data.aws_vpc.default.cidr_block +} +`, + "/modules/problem/main.tf": ` +variable "cidr_block" {} + +variable "open" { + default = false +} + +resource "aws_security_group" "this" { + name = "Test" + + ingress { + description = "HTTPs" + from_port = 443 + to_port = 443 + protocol = "tcp" + self = ! var.open + cidr_blocks = var.open ? [var.cidr_block] : null + } +} + +resource "problem" "uhoh" { + bad = true +} +`, + }) + + debug := bytes.NewBuffer([]byte{}) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true), options.ParserWithDebug(debug)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, badRule.LongID(), results, "") + if t.Failed() { + fmt.Println(debug.String()) + } +} + +func Test_ProblemInModuleInSiblingDir(t *testing.T) { + + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "/project/main.tf": ` +module "something" { + source = "../modules/problem" +} +`, + "modules/problem/main.tf": ` +resource "problem" "uhoh" { + bad = true +} +`}, + ) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, badRule.LongID(), results, "") + +} + +func Test_ProblemInModuleIgnored(t *testing.T) { + + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "/project/main.tf": ` +#tfsec:ignore:aws-service-abc +module "something" { + source = "../modules/problem" +} +`, + "modules/problem/main.tf": ` +resource "problem" "uhoh" { + bad = true +} +`}, + ) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleNotFound(t, badRule.LongID(), results, "") + +} + +func Test_ProblemInModuleInSubdirectory(t *testing.T) { + + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +module "something" { + source = "./modules/problem" +} +`, + "project/modules/problem/main.tf": ` +resource "problem" "uhoh" { + bad = true +} +`}) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, badRule.LongID(), results, "") + +} + +func Test_ProblemInModuleInParentDir(t *testing.T) { + + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +module "something" { + source = "../problem" +} +`, + "problem/main.tf": ` +resource "problem" "uhoh" { + bad = true +} +`}) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, badRule.LongID(), results, "") + +} + +func Test_ProblemInModuleReuse(t *testing.T) { + + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +module "something_good" { + source = "../modules/problem" + bad = false +} + +module "something_bad" { + source = "../modules/problem" + bad = true +} +`, + "modules/problem/main.tf": ` +variable "bad" { + default = false +} +resource "problem" "uhoh" { + bad = var.bad +} +`}) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, badRule.LongID(), results, "") + +} + +func Test_ProblemInNestedModule(t *testing.T) { + + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +module "something" { + source = "../modules/a" +} +`, + "modules/a/main.tf": ` + module "something" { + source = "../../modules/b" +} +`, + "modules/b/main.tf": ` +module "something" { + source = "../c" +} +`, + "modules/c/main.tf": ` +resource "problem" "uhoh" { + bad = true +} +`, + }) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true), options.ParserWithDebug(os.Stderr)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, badRule.LongID(), results, "") + +} + +func Test_ProblemInReusedNestedModule(t *testing.T) { + + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +module "something" { + source = "../modules/a" + bad = false +} + +module "something-bad" { + source = "../modules/a" + bad = true +} +`, + "modules/a/main.tf": ` +variable "bad" { + default = false +} +module "something" { + source = "../../modules/b" + bad = var.bad +} +`, + "modules/b/main.tf": ` +variable "bad" { + default = false +} +module "something" { + source = "../c" + bad = var.bad +} +`, + "modules/c/main.tf": ` +variable "bad" { + default = false +} +resource "problem" "uhoh" { + bad = var.bad +} +`, + }) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, badRule.LongID(), results, "") +} + +func Test_ProblemInInitialisedModule(t *testing.T) { + + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +module "something" { + source = "../modules/somewhere" + bad = false +} +`, + "modules/somewhere/main.tf": ` +module "something_nested" { + count = 1 + source = "github.com/some/module.git" + bad = true +} + +variable "bad" { + default = false +} + +`, + "project/.terraform/modules/something.something_nested/main.tf": ` +variable "bad" { + default = false +} +resource "problem" "uhoh" { + bad = var.bad +} +`, + "project/.terraform/modules/modules.json": ` + {"Modules":[ + {"Key":"something","Source":"../modules/somewhere","Version":"2.35.0","Dir":"../modules/somewhere"}, + {"Key":"something.something_nested","Source":"git::https://github.com/some/module.git","Version":"2.35.0","Dir":".terraform/modules/something.something_nested"} + ]} +`, + }) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, badRule.LongID(), results, "") +} + +func Test_ProblemInReusedInitialisedModule(t *testing.T) { + + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +module "something" { + source = "/nowhere" + bad = false +} +module "something2" { + source = "/nowhere" + bad = true +} +`, + "project/.terraform/modules/a/main.tf": ` +variable "bad" { + default = false +} +resource "problem" "uhoh" { + bad = var.bad +} +`, + "project/.terraform/modules/modules.json": ` + {"Modules":[{"Key":"something","Source":"/nowhere","Version":"2.35.0","Dir":".terraform/modules/a"},{"Key":"something2","Source":"/nowhere","Version":"2.35.0","Dir":".terraform/modules/a"}]} +`, + }) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, badRule.LongID(), results, "") + +} + +func Test_ProblemInDuplicateModuleNameAndPath(t *testing.T) { + registered := rules.Register(badRule) + defer rules.Deregister(registered) + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +module "something" { + source = "../modules/a" + bad = 0 +} + +module "something-bad" { + source = "../modules/a" + bad = 1 +} +`, + "modules/a/main.tf": ` +variable "bad" { + default = 0 +} +module "something" { + source = "../b" + bad = var.bad +} +`, + "modules/b/main.tf": ` +variable "bad" { + default = 0 +} +module "something" { + source = "../c" + bad = var.bad +} +`, + "modules/c/main.tf": ` +variable "bad" { + default = 0 +} +resource "problem" "uhoh" { + count = var.bad + bad = true +} +`, + }) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, badRule.LongID(), results, "") + +} + +func Test_Dynamic_Variables(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +resource "something" "this" { + + dynamic "blah" { + for_each = ["a"] + + content { + ok = true + } + } +} + +resource "bad" "thing" { + secure = something.this.blah[0].ok +} +`}) + + r1 := scan.Rule{ + Provider: providers.AWSProvider, + Service: "service", + ShortCode: "abc123", + Severity: severity.High, + CustomChecks: scan.CustomChecks{ + Terraform: &scan.TerraformCustomCheck{ + RequiredLabels: []string{"bad"}, + Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { + if resourceBlock.GetAttribute("secure").IsTrue() { + return + } + results.Add("example problem", resourceBlock) + return + }, + }, + }, + } + reg := rules.Register(r1) + defer rules.Deregister(reg) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleFound(t, r1.LongID(), results, "") +} + +func Test_Dynamic_Variables_FalsePositive(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` +resource "something" "else" { + x = 1 + dynamic "blah" { + for_each = toset(["true"]) + + content { + ok = each.value + } + } +} + +resource "bad" "thing" { + secure = something.else.blah.ok +} +`}) + + r1 := scan.Rule{ + Provider: providers.AWSProvider, + Service: "service", + ShortCode: "abc123", + Severity: severity.High, + CustomChecks: scan.CustomChecks{ + Terraform: &scan.TerraformCustomCheck{ + RequiredLabels: []string{"bad"}, + Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { + if resourceBlock.GetAttribute("secure").IsTrue() { + return + } + results.Add("example problem", resourceBlock) + return + }, + }, + }, + } + reg := rules.Register(r1) + defer rules.Deregister(reg) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleNotFound(t, r1.LongID(), results, "") +} + +func Test_ReferencesPassedToNestedModule(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": ` + +resource "aws_iam_group" "developers" { + name = "developers" +} + +module "something" { + source = "../modules/a" + group = aws_iam_group.developers.name +} +`, + "modules/a/main.tf": ` +variable "group" { + type = string +} + +resource "aws_iam_group_policy" "mfa" { + group = var.group + policy = data.aws_iam_policy_document.policy.json +} + +data "aws_iam_policy_document" "policy" { + statement { + sid = "main" + effect = "Allow" + + actions = ["s3:*"] + resources = ["*"] + condition { + test = "Bool" + variable = "aws:MultiFactorAuthPresent" + values = ["true"] + } + } +} +`}) + + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) + err := p.ParseFS(context.TODO(), "project") + require.NoError(t, err) + modules, _, err := p.EvaluateAll(context.TODO()) + require.NoError(t, err) + + results, err := executor.New().Execute(modules) + require.NoError(t, err) + + testutil.AssertRuleNotFound(t, iam.CheckEnforceGroupMFA.LongID(), results, "") + +} diff --git a/pkg/iac/scanners/terraform/options.go b/pkg/iac/scanners/terraform/options.go new file mode 100644 index 000000000000..f5a0d2223534 --- /dev/null +++ b/pkg/iac/scanners/terraform/options.go @@ -0,0 +1,86 @@ +package terraform + +import ( + "io/fs" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" +) + +type ConfigurableTerraformScanner interface { + options.ConfigurableScanner + SetForceAllDirs(bool) + AddExecutorOptions(options ...executor.Option) + AddParserOptions(options ...options.ParserOption) +} + +func ScannerWithTFVarsPaths(paths ...string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if tf, ok := s.(ConfigurableTerraformScanner); ok { + tf.AddParserOptions(parser.OptionWithTFVarsPaths(paths...)) + } + } +} + +func ScannerWithWorkspaceName(name string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if tf, ok := s.(ConfigurableTerraformScanner); ok { + tf.AddParserOptions(parser.OptionWithWorkspaceName(name)) + tf.AddExecutorOptions(executor.OptionWithWorkspaceName(name)) + } + } +} + +func ScannerWithAllDirectories(all bool) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if tf, ok := s.(ConfigurableTerraformScanner); ok { + tf.SetForceAllDirs(all) + } + } +} + +func ScannerWithSkipDownloaded(skip bool) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if !skip { + return + } + if tf, ok := s.(ConfigurableTerraformScanner); ok { + tf.AddExecutorOptions(executor.OptionWithResultsFilter(func(results scan.Results) scan.Results { + for i, result := range results { + prefix := result.Range().GetSourcePrefix() + if prefix != "" && !strings.HasPrefix(prefix, ".") { + results[i].OverrideStatus(scan.StatusIgnored) + } + } + return results + })) + } + } +} + +func ScannerWithDownloadsAllowed(allowed bool) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if tf, ok := s.(ConfigurableTerraformScanner); ok { + tf.AddParserOptions(parser.OptionWithDownloads(allowed)) + } + } +} + +func ScannerWithSkipCachedModules(b bool) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if tf, ok := s.(ConfigurableTerraformScanner); ok { + tf.AddParserOptions(parser.OptionWithSkipCachedModules(b)) + } + } +} + +func ScannerWithConfigsFileSystem(fsys fs.FS) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if tf, ok := s.(ConfigurableTerraformScanner); ok { + tf.AddParserOptions(parser.OptionWithConfigsFS(fsys)) + } + } +} diff --git a/pkg/iac/scanners/terraform/parser/evaluator.go b/pkg/iac/scanners/terraform/parser/evaluator.go new file mode 100644 index 000000000000..b93104f442cc --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/evaluator.go @@ -0,0 +1,534 @@ +package parser + +import ( + "context" + "errors" + "io/fs" + "reflect" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/ext/typeexpr" + "github.com/samber/lo" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/convert" + "golang.org/x/exp/slices" + + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/ignore" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + tfcontext "github.com/aquasecurity/trivy/pkg/iac/terraform/context" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +const ( + maxContextIterations = 32 +) + +type evaluator struct { + filesystem fs.FS + ctx *tfcontext.Context + blocks terraform.Blocks + inputVars map[string]cty.Value + moduleMetadata *modulesMetadata + projectRootPath string // root of the current scan + modulePath string + moduleName string + ignores ignore.Rules + parentParser *Parser + debug debug.Logger + allowDownloads bool + skipCachedModules bool +} + +func newEvaluator( + target fs.FS, + parentParser *Parser, + projectRootPath string, + modulePath string, + workingDir string, + moduleName string, + blocks terraform.Blocks, + inputVars map[string]cty.Value, + moduleMetadata *modulesMetadata, + workspace string, + ignores ignore.Rules, + logger debug.Logger, + allowDownloads bool, + skipCachedModules bool, +) *evaluator { + + // create a context to store variables and make functions available + ctx := tfcontext.NewContext(&hcl.EvalContext{ + Functions: Functions(target, modulePath), + }, nil) + + // these variables are made available by terraform to each module + ctx.SetByDot(cty.StringVal(workspace), "terraform.workspace") + ctx.SetByDot(cty.StringVal(projectRootPath), "path.root") + ctx.SetByDot(cty.StringVal(modulePath), "path.module") + ctx.SetByDot(cty.StringVal(workingDir), "path.cwd") + + // each block gets its own scope to define variables in + for _, b := range blocks { + b.OverrideContext(ctx.NewChild()) + } + + return &evaluator{ + filesystem: target, + parentParser: parentParser, + modulePath: modulePath, + moduleName: moduleName, + projectRootPath: projectRootPath, + ctx: ctx, + blocks: blocks, + inputVars: inputVars, + moduleMetadata: moduleMetadata, + ignores: ignores, + debug: logger, + allowDownloads: allowDownloads, + skipCachedModules: skipCachedModules, + } +} + +func (e *evaluator) evaluateStep() { + + e.ctx.Set(e.getValuesByBlockType("variable"), "var") + e.ctx.Set(e.getValuesByBlockType("locals"), "local") + e.ctx.Set(e.getValuesByBlockType("provider"), "provider") + + resources := e.getValuesByBlockType("resource") + for key, resource := range resources.AsValueMap() { + e.ctx.Set(resource, key) + } + + e.ctx.Set(e.getValuesByBlockType("data"), "data") + e.ctx.Set(e.getValuesByBlockType("output"), "output") + e.ctx.Set(e.getValuesByBlockType("module"), "module") +} + +// exportOutputs is used to export module outputs to the parent module +func (e *evaluator) exportOutputs() cty.Value { + data := make(map[string]cty.Value) + for _, block := range e.blocks.OfType("output") { + attr := block.GetAttribute("value") + if attr.IsNil() { + continue + } + data[block.Label()] = attr.Value() + e.debug.Log("Added module output %s=%s.", block.Label(), attr.Value().GoString()) + } + return cty.ObjectVal(data) +} + +func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[string]fs.FS) { + + fsKey := types.CreateFSKey(e.filesystem) + e.debug.Log("Filesystem key is '%s'", fsKey) + + fsMap := make(map[string]fs.FS) + fsMap[fsKey] = e.filesystem + + e.debug.Log("Starting module evaluation...") + e.evaluateSteps() + + // expand out resources and modules via count, for-each and dynamic + // (not a typo, we do this twice so every order is processed) + e.blocks = e.expandBlocks(e.blocks) + e.blocks = e.expandBlocks(e.blocks) + + e.debug.Log("Starting submodule evaluation...") + submodules := e.loadSubmodules(ctx) + + for i := 0; i < maxContextIterations; i++ { + changed := false + for _, sm := range submodules { + changed = changed || e.evaluateSubmodule(ctx, sm) + } + if !changed { + e.debug.Log("All submodules are evaluated at i=%d", i) + break + } + } + + e.debug.Log("Starting post-submodule evaluation...") + e.evaluateSteps() + + var modules terraform.Modules + for _, sm := range submodules { + modules = append(modules, sm.modules...) + fsMap = lo.Assign(fsMap, sm.fsMap) + } + + e.debug.Log("Finished processing %d submodule(s).", len(modules)) + + e.debug.Log("Module evaluation complete.") + rootModule := terraform.NewModule(e.projectRootPath, e.modulePath, e.blocks, e.ignores) + return append(terraform.Modules{rootModule}, modules...), fsMap +} + +type submodule struct { + definition *ModuleDefinition + eval *evaluator + modules terraform.Modules + lastState map[string]cty.Value + fsMap map[string]fs.FS +} + +func (e *evaluator) loadSubmodules(ctx context.Context) []*submodule { + var submodules []*submodule + + for _, definition := range e.loadModules(ctx) { + eval, err := definition.Parser.Load(ctx) + if errors.Is(err, ErrNoFiles) { + continue + } else if err != nil { + e.debug.Log("Failed to load submodule '%s': %s.", definition.Name, err) + continue + } + + submodules = append(submodules, &submodule{ + definition: definition, + eval: eval, + fsMap: make(map[string]fs.FS), + }) + } + + return submodules +} + +func (e *evaluator) evaluateSubmodule(ctx context.Context, sm *submodule) bool { + inputVars := sm.definition.inputVars() + if len(sm.modules) > 0 { + if reflect.DeepEqual(inputVars, sm.lastState) { + e.debug.Log("Submodule %s inputs unchanged", sm.definition.Name) + return false + } + } + + e.debug.Log("Evaluating submodule %s", sm.definition.Name) + sm.eval.inputVars = inputVars + sm.modules, sm.fsMap = sm.eval.EvaluateAll(ctx) + outputs := sm.eval.exportOutputs() + + // lastState needs to be captured after applying outputs – so that they + // don't get treated as changes – but before running post-submodule + // evaluation, so that changes from that can trigger re-evaluations of + // the submodule if/when they feed back into inputs. + e.ctx.Set(outputs, "module", sm.definition.Name) + sm.lastState = sm.definition.inputVars() + e.evaluateSteps() + return true +} + +func (e *evaluator) evaluateSteps() { + var lastContext hcl.EvalContext + for i := 0; i < maxContextIterations; i++ { + + e.evaluateStep() + + // if ctx matches the last evaluation, we can bail, nothing left to resolve + if i > 0 && reflect.DeepEqual(lastContext.Variables, e.ctx.Inner().Variables) { + break + } + if len(e.ctx.Inner().Variables) != len(lastContext.Variables) { + lastContext.Variables = make(map[string]cty.Value, len(e.ctx.Inner().Variables)) + } + for k, v := range e.ctx.Inner().Variables { + lastContext.Variables[k] = v + } + } +} + +func (e *evaluator) expandBlocks(blocks terraform.Blocks) terraform.Blocks { + return e.expandDynamicBlocks(e.expandBlockForEaches(e.expandBlockCounts(blocks), false)...) +} + +func (e *evaluator) expandDynamicBlocks(blocks ...*terraform.Block) terraform.Blocks { + for _, b := range blocks { + e.expandDynamicBlock(b) + } + return blocks +} + +func (e *evaluator) expandDynamicBlock(b *terraform.Block) { + for _, sub := range b.AllBlocks() { + e.expandDynamicBlock(sub) + } + for _, sub := range b.AllBlocks().OfType("dynamic") { + if sub.IsExpanded() { + continue + } + blockName := sub.TypeLabel() + expanded := e.expandBlockForEaches(terraform.Blocks{sub}, true) + for _, ex := range expanded { + if content := ex.GetBlock("content"); content.IsNotNil() { + _ = e.expandDynamicBlocks(content) + b.InjectBlock(content, blockName) + } + } + if len(expanded) > 0 { + sub.MarkExpanded() + } + } +} + +func isBlockSupportsForEachMetaArgument(block *terraform.Block) bool { + return slices.Contains([]string{"module", "resource", "data", "dynamic"}, block.Type()) +} + +func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool) terraform.Blocks { + var forEachFiltered terraform.Blocks + + for _, block := range blocks { + + forEachAttr := block.GetAttribute("for_each") + + if forEachAttr.IsNil() || block.IsExpanded() || !isBlockSupportsForEachMetaArgument(block) { + forEachFiltered = append(forEachFiltered, block) + continue + } + + forEachVal := forEachAttr.Value() + + if forEachVal.IsNull() || !forEachVal.IsKnown() || !forEachAttr.IsIterable() { + continue + } + + clones := make(map[string]cty.Value) + _ = forEachAttr.Each(func(key cty.Value, val cty.Value) { + + if val.IsNull() { + return + } + + // instances are identified by a map key (or set member) from the value provided to for_each + idx, err := convert.Convert(key, cty.String) + if err != nil { + e.debug.Log( + `Invalid "for-each" argument: map key (or set value) is not a string, but %s`, + key.Type().FriendlyName(), + ) + return + } + + // if the argument is a collection but not a map, then the resource identifier + // is the value of the collection. The exception is the use of for-each inside a dynamic block, + // because in this case the collection element may not be a primitive value. + if (forEachVal.Type().IsCollectionType() || forEachVal.Type().IsTupleType()) && + !forEachVal.Type().IsMapType() && !isDynamic { + stringVal, err := convert.Convert(val, cty.String) + if err != nil { + e.debug.Log("Failed to convert for-each arg %v to string", val) + return + } + idx = stringVal + } + + clone := block.Clone(idx) + + ctx := clone.Context() + + e.copyVariables(block, clone) + + ctx.SetByDot(idx, "each.key") + ctx.SetByDot(val, "each.value") + ctx.Set(idx, block.TypeLabel(), "key") + ctx.Set(val, block.TypeLabel(), "value") + + forEachFiltered = append(forEachFiltered, clone) + + values := clone.Values() + clones[idx.AsString()] = values + e.ctx.SetByDot(values, clone.GetMetadata().Reference()) + }) + + metadata := block.GetMetadata() + if len(clones) == 0 { + e.ctx.SetByDot(cty.EmptyTupleVal, metadata.Reference()) + } else { + // The for-each meta-argument creates multiple instances of the resource that are stored in the map. + // So we must replace the old resource with a map with the attributes of the resource. + e.ctx.Replace(cty.ObjectVal(clones), metadata.Reference()) + } + e.debug.Log("Expanded block '%s' into %d clones via 'for_each' attribute.", block.LocalName(), len(clones)) + } + + return forEachFiltered +} + +func isBlockSupportsCountMetaArgument(block *terraform.Block) bool { + return slices.Contains([]string{"module", "resource", "data"}, block.Type()) +} + +func (e *evaluator) expandBlockCounts(blocks terraform.Blocks) terraform.Blocks { + var countFiltered terraform.Blocks + for _, block := range blocks { + countAttr := block.GetAttribute("count") + if countAttr.IsNil() || block.IsExpanded() || !isBlockSupportsCountMetaArgument(block) { + countFiltered = append(countFiltered, block) + continue + } + count := 1 + countAttrVal := countAttr.Value() + if !countAttrVal.IsNull() && countAttrVal.IsKnown() && countAttrVal.Type() == cty.Number { + count = int(countAttr.AsNumber()) + } + + var clones []cty.Value + for i := 0; i < count; i++ { + clone := block.Clone(cty.NumberIntVal(int64(i))) + clones = append(clones, clone.Values()) + countFiltered = append(countFiltered, clone) + metadata := clone.GetMetadata() + e.ctx.SetByDot(clone.Values(), metadata.Reference()) + } + metadata := block.GetMetadata() + if len(clones) == 0 { + e.ctx.SetByDot(cty.EmptyTupleVal, metadata.Reference()) + } else { + e.ctx.SetByDot(cty.TupleVal(clones), metadata.Reference()) + } + e.debug.Log("Expanded block '%s' into %d clones via 'count' attribute.", block.LocalName(), len(clones)) + } + + return countFiltered +} + +func (e *evaluator) copyVariables(from, to *terraform.Block) { + + var fromBase string + var fromRel string + var toRel string + + switch from.Type() { + case "resource": + fromBase = from.TypeLabel() + fromRel = from.NameLabel() + toRel = to.NameLabel() + case "module": + fromBase = from.Type() + fromRel = from.TypeLabel() + toRel = to.TypeLabel() + default: + return + } + + srcValue := e.ctx.Root().Get(fromBase, fromRel) + if srcValue == cty.NilVal { + return + } + e.ctx.Root().Set(srcValue, fromBase, toRel) +} + +func (e *evaluator) evaluateVariable(b *terraform.Block) (cty.Value, error) { + if b.Label() == "" { + return cty.NilVal, errors.New("empty label - cannot resolve") + } + + attributes := b.Attributes() + if attributes == nil { + return cty.NilVal, errors.New("cannot resolve variable with no attributes") + } + + var valType cty.Type + var defaults *typeexpr.Defaults + if typeAttr, exists := attributes["type"]; exists { + ty, def, err := typeAttr.DecodeVarType() + if err != nil { + return cty.NilVal, err + } + valType = ty + defaults = def + } + + var val cty.Value + + if override, exists := e.inputVars[b.Label()]; exists { + val = override + } else if def, exists := attributes["default"]; exists { + val = def.NullableValue() + } else { + return cty.NilVal, errors.New("no value found") + } + + if valType != cty.NilType { + if defaults != nil { + val = defaults.Apply(val) + } + + typedVal, err := convert.Convert(val, valType) + if err != nil { + return cty.NilVal, err + } + return typedVal, nil + } + + return val, nil + +} + +func (e *evaluator) evaluateOutput(b *terraform.Block) (cty.Value, error) { + if b.Label() == "" { + return cty.NilVal, errors.New("empty label - cannot resolve") + } + + attribute := b.GetAttribute("value") + if attribute.IsNil() { + return cty.NilVal, errors.New("cannot resolve output with no attributes") + } + return attribute.Value(), nil +} + +// returns true if all evaluations were successful +func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { + + blocksOfType := e.blocks.OfType(blockType) + values := make(map[string]cty.Value) + + for _, b := range blocksOfType { + + switch b.Type() { + case "variable": // variables are special in that their value comes from the "default" attribute + val, err := e.evaluateVariable(b) + if err != nil { + continue + } + values[b.Label()] = val + case "output": + val, err := e.evaluateOutput(b) + if err != nil { + continue + } + values[b.Label()] = val + case "locals", "moved", "import": + for key, val := range b.Values().AsValueMap() { + values[key] = val + } + case "provider", "module", "check": + if b.Label() == "" { + continue + } + values[b.Label()] = b.Values() + case "resource", "data": + if len(b.Labels()) < 2 { + continue + } + + blockMap, ok := values[b.Labels()[0]] + if !ok { + values[b.Labels()[0]] = cty.ObjectVal(make(map[string]cty.Value)) + blockMap = values[b.Labels()[0]] + } + + valueMap := blockMap.AsValueMap() + if valueMap == nil { + valueMap = make(map[string]cty.Value) + } + + valueMap[b.Labels()[1]] = b.Values() + values[b.Labels()[0]] = cty.ObjectVal(valueMap) + } + } + + return cty.ObjectVal(values) +} diff --git a/pkg/iac/scanners/terraform/parser/funcs/cidr.go b/pkg/iac/scanners/terraform/parser/funcs/cidr.go new file mode 100644 index 000000000000..23b1c7be0d45 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/cidr.go @@ -0,0 +1,195 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "fmt" + "math/big" + + "github.com/apparentlymart/go-cidr/cidr" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + "github.com/zclconf/go-cty/cty/gocty" +) + +// CidrHostFunc constructs a function that calculates a full host IP address +// within a given IP network address prefix. +var CidrHostFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "prefix", + Type: cty.String, + }, + { + Name: "hostnum", + Type: cty.Number, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var hostNum *big.Int + if err := gocty.FromCtyValue(args[1], &hostNum); err != nil { + return cty.UnknownVal(cty.String), err + } + _, network, err := ParseCIDR(args[0].AsString()) + if err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err) + } + + ip, err := cidr.HostBig(network, hostNum) + if err != nil { + return cty.UnknownVal(cty.String), err + } + + return cty.StringVal(ip.String()), nil + }, +}) + +// CidrNetmaskFunc constructs a function that converts an IPv4 address prefix given +// in CIDR notation into a subnet mask address. +var CidrNetmaskFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "prefix", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + _, network, err := ParseCIDR(args[0].AsString()) + if err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err) + } + + if network.IP.To4() == nil { + return cty.UnknownVal(cty.String), fmt.Errorf("IPv6 addresses cannot have a netmask: %s", args[0].AsString()) + } + + return cty.StringVal(IP(network.Mask).String()), nil + }, +}) + +// CidrSubnetFunc constructs a function that calculates a subnet address within +// a given IP network address prefix. +var CidrSubnetFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "prefix", + Type: cty.String, + }, + { + Name: "newbits", + Type: cty.Number, + }, + { + Name: "netnum", + Type: cty.Number, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var newbits int + if err := gocty.FromCtyValue(args[1], &newbits); err != nil { + return cty.UnknownVal(cty.String), err + } + var netnum *big.Int + if err := gocty.FromCtyValue(args[2], &netnum); err != nil { + return cty.UnknownVal(cty.String), err + } + + _, network, err := ParseCIDR(args[0].AsString()) + if err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err) + } + + newNetwork, err := cidr.SubnetBig(network, newbits, netnum) + if err != nil { + return cty.UnknownVal(cty.String), err + } + + return cty.StringVal(newNetwork.String()), nil + }, +}) + +// CidrSubnetsFunc is similar to CidrSubnetFunc but calculates many consecutive +// subnet addresses at once, rather than just a single subnet extension. +var CidrSubnetsFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "prefix", + Type: cty.String, + }, + }, + VarParam: &function.Parameter{ + Name: "newbits", + Type: cty.Number, + }, + Type: function.StaticReturnType(cty.List(cty.String)), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + _, network, err := ParseCIDR(args[0].AsString()) + if err != nil { + return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid CIDR expression: %s", err) + } + startPrefixLen, _ := network.Mask.Size() + + prefixLengthArgs := args[1:] + if len(prefixLengthArgs) == 0 { + return cty.ListValEmpty(cty.String), nil + } + + var firstLength int + if err := gocty.FromCtyValue(prefixLengthArgs[0], &firstLength); err != nil { + return cty.UnknownVal(cty.String), function.NewArgError(1, err) + } + firstLength += startPrefixLen + + retVals := make([]cty.Value, len(prefixLengthArgs)) + + current, _ := cidr.PreviousSubnet(network, firstLength) + for i, lengthArg := range prefixLengthArgs { + var length int + if err := gocty.FromCtyValue(lengthArg, &length); err != nil { + return cty.UnknownVal(cty.String), function.NewArgError(i+1, err) + } + + if length < 1 { + return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "must extend prefix by at least one bit") + } + // For portability with 32-bit systems where the subnet number + // will be a 32-bit int, we only allow extension of 32 bits in + // one call even if we're running on a 64-bit machine. + // (Of course, this is significant only for IPv6.) + if length > 32 { + return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "may not extend prefix by more than 32 bits") + } + length += startPrefixLen + if length > (len(network.IP) * 8) { + protocol := "IP" + switch len(network.IP) * 8 { + case 32: + protocol = "IPv4" + case 128: + protocol = "IPv6" + } + return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "would extend prefix to %d bits, which is too long for an %s address", length, protocol) + } + + next, rollover := cidr.NextSubnet(current, length) + if rollover || !network.Contains(next.IP) { + // If we run out of suffix bits in the base CIDR prefix then + // NextSubnet will start incrementing the prefix bits, which + // we don't allow because it would then allocate addresses + // outside of the caller's given prefix. + return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "not enough remaining address space for a subnet with a prefix of %d bits after %s", length, current.String()) + } + + current = next + retVals[i] = cty.StringVal(current.String()) + } + + return cty.ListVal(retVals), nil + }, +}) diff --git a/pkg/iac/scanners/terraform/parser/funcs/collection.go b/pkg/iac/scanners/terraform/parser/funcs/collection.go new file mode 100644 index 000000000000..d5deb65a68e5 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/collection.go @@ -0,0 +1,652 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "errors" + "fmt" + "math/big" + "sort" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/convert" + "github.com/zclconf/go-cty/cty/function" + "github.com/zclconf/go-cty/cty/function/stdlib" + "github.com/zclconf/go-cty/cty/gocty" +) + +var LengthFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "value", + Type: cty.DynamicPseudoType, + AllowDynamicType: true, + AllowUnknown: true, + AllowMarked: true, + }, + }, + Type: func(args []cty.Value) (cty.Type, error) { + collTy := args[0].Type() + switch { + case collTy == cty.String || collTy.IsTupleType() || collTy.IsObjectType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType: + return cty.Number, nil + default: + return cty.Number, errors.New("argument must be a string, a collection type, or a structural type") + } + }, + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + coll := args[0] + collTy := args[0].Type() + marks := coll.Marks() + switch { + case collTy == cty.DynamicPseudoType: + return cty.UnknownVal(cty.Number).WithMarks(marks), nil + case collTy.IsTupleType(): + l := len(collTy.TupleElementTypes()) + return cty.NumberIntVal(int64(l)).WithMarks(marks), nil + case collTy.IsObjectType(): + l := len(collTy.AttributeTypes()) + return cty.NumberIntVal(int64(l)).WithMarks(marks), nil + case collTy == cty.String: + // We'll delegate to the cty stdlib strlen function here, because + // it deals with all of the complexities of tokenizing unicode + // grapheme clusters. + return stdlib.Strlen(coll) + case collTy.IsListType() || collTy.IsSetType() || collTy.IsMapType(): + return coll.Length(), nil + default: + // Should never happen, because of the checks in our Type func above + return cty.UnknownVal(cty.Number), errors.New("impossible value type for length(...)") + } + }, +}) + +// AllTrueFunc constructs a function that returns true if all elements of the +// list are true. If the list is empty, return true. +var AllTrueFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: cty.List(cty.Bool), + }, + }, + Type: function.StaticReturnType(cty.Bool), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + result := cty.True + for it := args[0].ElementIterator(); it.Next(); { + _, v := it.Element() + if !v.IsKnown() { + return cty.UnknownVal(cty.Bool), nil + } + if v.IsNull() { + return cty.False, nil + } + result = result.And(v) + if result.False() { + return cty.False, nil + } + } + return result, nil + }, +}) + +// AnyTrueFunc constructs a function that returns true if any element of the +// list is true. If the list is empty, return false. +var AnyTrueFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: cty.List(cty.Bool), + }, + }, + Type: function.StaticReturnType(cty.Bool), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + result := cty.False + var hasUnknown bool + for it := args[0].ElementIterator(); it.Next(); { + _, v := it.Element() + if !v.IsKnown() { + hasUnknown = true + continue + } + if v.IsNull() { + continue + } + result = result.Or(v) + if result.True() { + return cty.True, nil + } + } + if hasUnknown { + return cty.UnknownVal(cty.Bool), nil + } + return result, nil + }, +}) + +// CoalesceFunc constructs a function that takes any number of arguments and +// returns the first one that isn't empty. This function was copied from go-cty +// stdlib and modified so that it returns the first *non-empty* non-null element +// from a sequence, instead of merely the first non-null. +var CoalesceFunc = function.New(&function.Spec{ + Params: []function.Parameter{}, + VarParam: &function.Parameter{ + Name: "vals", + Type: cty.DynamicPseudoType, + AllowUnknown: true, + AllowDynamicType: true, + AllowNull: true, + }, + Type: func(args []cty.Value) (ret cty.Type, err error) { + argTypes := make([]cty.Type, len(args)) + for i, val := range args { + argTypes[i] = val.Type() + } + retType, _ := convert.UnifyUnsafe(argTypes) + if retType == cty.NilType { + return cty.NilType, errors.New("all arguments must have the same type") + } + return retType, nil + }, + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + for _, argVal := range args { + // We already know this will succeed because of the checks in our Type func above + argVal, _ = convert.Convert(argVal, retType) + if !argVal.IsKnown() { + return cty.UnknownVal(retType), nil + } + if argVal.IsNull() { + continue + } + if retType == cty.String && argVal.RawEquals(cty.StringVal("")) { + continue + } + + return argVal, nil + } + return cty.NilVal, errors.New("no non-null, non-empty-string arguments") + }, +}) + +// IndexFunc constructs a function that finds the element index for a given value in a list. +var IndexFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: cty.DynamicPseudoType, + }, + { + Name: "value", + Type: cty.DynamicPseudoType, + }, + }, + Type: function.StaticReturnType(cty.Number), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) { + return cty.NilVal, errors.New("argument must be a list or tuple") + } + + if !args[0].IsKnown() { + return cty.UnknownVal(cty.Number), nil + } + + if args[0].LengthInt() == 0 { // Easy path + return cty.NilVal, errors.New("cannot search an empty list") + } + + for it := args[0].ElementIterator(); it.Next(); { + i, v := it.Element() + eq, err := stdlib.Equal(v, args[1]) + if err != nil { + return cty.NilVal, err + } + if !eq.IsKnown() { + return cty.UnknownVal(cty.Number), nil + } + if eq.True() { + return i, nil + } + } + return cty.NilVal, errors.New("item not found") + + }, +}) + +// LookupFunc constructs a function that performs dynamic lookups of map types. +var LookupFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "inputMap", + Type: cty.DynamicPseudoType, + AllowMarked: true, + }, + { + Name: "key", + Type: cty.String, + AllowMarked: true, + }, + }, + VarParam: &function.Parameter{ + Name: "default", + Type: cty.DynamicPseudoType, + AllowUnknown: true, + AllowDynamicType: true, + AllowNull: true, + AllowMarked: true, + }, + Type: func(args []cty.Value) (ret cty.Type, err error) { + if len(args) < 1 || len(args) > 3 { + return cty.NilType, fmt.Errorf("lookup() takes two or three arguments, got %d", len(args)) + } + + ty := args[0].Type() + + switch { + case ty.IsObjectType(): + if !args[1].IsKnown() { + return cty.DynamicPseudoType, nil + } + + keyVal, _ := args[1].Unmark() + key := keyVal.AsString() + if ty.HasAttribute(key) { + return args[0].GetAttr(key).Type(), nil + } else if len(args) == 3 { + // if the key isn't found but a default is provided, + // return the default type + return args[2].Type(), nil + } + return cty.DynamicPseudoType, function.NewArgErrorf(0, "the given object has no attribute %q", key) + case ty.IsMapType(): + if len(args) == 3 { + _, err = convert.Convert(args[2], ty.ElementType()) + if err != nil { + return cty.NilType, function.NewArgErrorf(2, "the default value must have the same type as the map elements") + } + } + return ty.ElementType(), nil + default: + return cty.NilType, function.NewArgErrorf(0, "lookup() requires a map as the first argument") + } + }, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var defaultVal cty.Value + defaultValueSet := false + + if len(args) == 3 { + // intentionally leave default value marked + defaultVal = args[2] + defaultValueSet = true + } + + // keep track of marks from the collection and key + var markses []cty.ValueMarks + + // unmark collection, retain marks to reapply later + mapVar, mapMarks := args[0].Unmark() + markses = append(markses, mapMarks) + + // include marks on the key in the result + keyVal, keyMarks := args[1].Unmark() + if len(keyMarks) > 0 { + markses = append(markses, keyMarks) + } + lookupKey := keyVal.AsString() + + if !mapVar.IsKnown() { + return cty.UnknownVal(retType).WithMarks(markses...), nil + } + + if mapVar.Type().IsObjectType() { + if mapVar.Type().HasAttribute(lookupKey) { + return mapVar.GetAttr(lookupKey).WithMarks(markses...), nil + } + } else if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True { + return mapVar.Index(cty.StringVal(lookupKey)).WithMarks(markses...), nil + } + + if defaultValueSet { + defaultVal, err = convert.Convert(defaultVal, retType) + if err != nil { + return cty.NilVal, err + } + return defaultVal.WithMarks(markses...), nil + } + + return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf( + "lookup failed to find key %s", redactIfSensitive(lookupKey, keyMarks)) + }, +}) + +// MatchkeysFunc constructs a function that constructs a new list by taking a +// subset of elements from one list whose indexes match the corresponding +// indexes of values in another list. +var MatchkeysFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "values", + Type: cty.List(cty.DynamicPseudoType), + }, + { + Name: "keys", + Type: cty.List(cty.DynamicPseudoType), + }, + { + Name: "searchset", + Type: cty.List(cty.DynamicPseudoType), + }, + }, + Type: func(args []cty.Value) (cty.Type, error) { + ty, _ := convert.UnifyUnsafe([]cty.Type{args[1].Type(), args[2].Type()}) + if ty == cty.NilType { + return cty.NilType, errors.New("keys and searchset must be of the same type") + } + + // the return type is based on args[0] (values) + return args[0].Type(), nil + }, + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + if !args[0].IsKnown() { + return cty.UnknownVal(cty.List(retType.ElementType())), nil + } + + if args[0].LengthInt() != args[1].LengthInt() { + return cty.ListValEmpty(retType.ElementType()), errors.New("length of keys and values should be equal") + } + + output := make([]cty.Value, 0) + values := args[0] + + // Keys and searchset must be the same type. + // We can skip error checking here because we've already verified that + // they can be unified in the Type function + ty, _ := convert.UnifyUnsafe([]cty.Type{args[1].Type(), args[2].Type()}) + keys, _ := convert.Convert(args[1], ty) + searchset, _ := convert.Convert(args[2], ty) + + // if searchset is empty, return an empty list. + if searchset.LengthInt() == 0 { + return cty.ListValEmpty(retType.ElementType()), nil + } + + if !values.IsWhollyKnown() || !keys.IsWhollyKnown() { + return cty.UnknownVal(retType), nil + } + + i := 0 + for it := keys.ElementIterator(); it.Next(); { + _, key := it.Element() + for iter := searchset.ElementIterator(); iter.Next(); { + _, search := iter.Element() + eq, err := stdlib.Equal(key, search) + if err != nil { + return cty.NilVal, err + } + if !eq.IsKnown() { + return cty.ListValEmpty(retType.ElementType()), nil + } + if eq.True() { + v := values.Index(cty.NumberIntVal(int64(i))) + output = append(output, v) + break + } + } + i++ + } + + // if we haven't matched any key, then output is an empty list. + if len(output) == 0 { + return cty.ListValEmpty(retType.ElementType()), nil + } + return cty.ListVal(output), nil + }, +}) + +// OneFunc returns either the first element of a one-element list, or null +// if given a zero-element list. +var OneFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: cty.DynamicPseudoType, + }, + }, + Type: func(args []cty.Value) (cty.Type, error) { + ty := args[0].Type() + switch { + case ty.IsListType() || ty.IsSetType(): + return ty.ElementType(), nil + case ty.IsTupleType(): + etys := ty.TupleElementTypes() + switch len(etys) { + case 0: + // No specific type information, so we'll ultimately return + // a null value of unknown type. + return cty.DynamicPseudoType, nil + case 1: + return etys[0], nil + } + } + return cty.NilType, function.NewArgErrorf(0, "must be a list, set, or tuple value with either zero or one elements") + }, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + val := args[0] + ty := val.Type() + + // Our parameter spec above doesn't set AllowUnknown or AllowNull, + // so we can assume our top-level collection is both known and non-null + // in here. + + switch { + case ty.IsListType() || ty.IsSetType(): + lenVal := val.Length() + if !lenVal.IsKnown() { + return cty.UnknownVal(retType), nil + } + var l int + err := gocty.FromCtyValue(lenVal, &l) + if err != nil { + // It would be very strange to get here, because that would + // suggest that the length is either not a number or isn't + // an integer, which would suggest a bug in cty. + return cty.NilVal, fmt.Errorf("invalid collection length: %s", err) + } + switch l { + case 0: + return cty.NullVal(retType), nil + case 1: + var ret cty.Value + // We'll use an iterator here because that works for both lists + // and sets, whereas indexing directly would only work for lists. + // Since we've just checked the length, we should only actually + // run this loop body once. + for it := val.ElementIterator(); it.Next(); { + _, ret = it.Element() + } + return ret, nil + } + case ty.IsTupleType(): + etys := ty.TupleElementTypes() + switch len(etys) { + case 0: + return cty.NullVal(retType), nil + case 1: + ret := val.Index(cty.NumberIntVal(0)) + return ret, nil + } + } + return cty.NilVal, function.NewArgErrorf(0, "must be a list, set, or tuple value with either zero or one elements") + }, +}) + +// SumFunc constructs a function that returns the sum of all +// numbers provided in a list +var SumFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: cty.DynamicPseudoType, + }, + }, + Type: function.StaticReturnType(cty.Number), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + + if !args[0].CanIterateElements() { + return cty.NilVal, function.NewArgErrorf(0, "cannot sum noniterable") + } + + if args[0].LengthInt() == 0 { // Easy path + return cty.NilVal, function.NewArgErrorf(0, "cannot sum an empty list") + } + + arg := args[0].AsValueSlice() + ty := args[0].Type() + + if !ty.IsListType() && !ty.IsSetType() && !ty.IsTupleType() { + return cty.NilVal, function.NewArgErrorf(0, fmt.Sprintf("argument must be list, set, or tuple. Received %s", ty.FriendlyName())) + } + + if !args[0].IsWhollyKnown() { + return cty.UnknownVal(cty.Number), nil + } + + // big.Float.Add can panic if the input values are opposing infinities, + // so we must catch that here in order to remain within + // the cty Function abstraction. + defer func() { + if r := recover(); r != nil { + if _, ok := r.(big.ErrNaN); ok { + ret = cty.NilVal + err = fmt.Errorf("can't compute sum of opposing infinities") + } else { + // not a panic we recognize + panic(r) + } + } + }() + + s := arg[0] + if s.IsNull() { + return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") + } + s, err = convert.Convert(s, cty.Number) + if err != nil { + return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") + } + for _, v := range arg[1:] { + if v.IsNull() { + return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") + } + v, err = convert.Convert(v, cty.Number) + if err != nil { + return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") + } + s = s.Add(v) + } + + return s, nil + }, +}) + +// TransposeFunc constructs a function that takes a map of lists of strings and +// swaps the keys and values to produce a new map of lists of strings. +var TransposeFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "values", + Type: cty.Map(cty.List(cty.String)), + }, + }, + Type: function.StaticReturnType(cty.Map(cty.List(cty.String))), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + inputMap := args[0] + if !inputMap.IsWhollyKnown() { + return cty.UnknownVal(retType), nil + } + + outputMap := make(map[string]cty.Value) + tmpMap := make(map[string][]string) + + for it := inputMap.ElementIterator(); it.Next(); { + inKey, inVal := it.Element() + for iter := inVal.ElementIterator(); iter.Next(); { + _, val := iter.Element() + if !val.Type().Equals(cty.String) { + return cty.MapValEmpty(cty.List(cty.String)), errors.New("input must be a map of lists of strings") + } + + outKey := val.AsString() + if _, ok := tmpMap[outKey]; !ok { + tmpMap[outKey] = make([]string, 0) + } + outVal := tmpMap[outKey] + outVal = append(outVal, inKey.AsString()) + sort.Strings(outVal) + tmpMap[outKey] = outVal + } + } + + for outKey, outVal := range tmpMap { + values := make([]cty.Value, 0) + for _, v := range outVal { + values = append(values, cty.StringVal(v)) + } + outputMap[outKey] = cty.ListVal(values) + } + + if len(outputMap) == 0 { + return cty.MapValEmpty(cty.List(cty.String)), nil + } + + return cty.MapVal(outputMap), nil + }, +}) + +// ListFunc constructs a function that takes an arbitrary number of arguments +// and returns a list containing those values in the same order. +// +// This function is deprecated in Terraform v0.12 +var ListFunc = function.New(&function.Spec{ + Params: []function.Parameter{}, + VarParam: &function.Parameter{ + Name: "vals", + Type: cty.DynamicPseudoType, + AllowUnknown: true, + AllowDynamicType: true, + AllowNull: true, + }, + Type: func(args []cty.Value) (ret cty.Type, err error) { + return cty.DynamicPseudoType, fmt.Errorf("the \"list\" function was deprecated in Terraform v0.12 and is no longer available; use tolist([ ... ]) syntax to write a literal list") + }, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + return cty.DynamicVal, fmt.Errorf("the \"list\" function was deprecated in Terraform v0.12 and is no longer available; use tolist([ ... ]) syntax to write a literal list") + }, +}) + +// MapFunc constructs a function that takes an even number of arguments and +// returns a map whose elements are constructed from consecutive pairs of arguments. +// +// This function is deprecated in Terraform v0.12 +var MapFunc = function.New(&function.Spec{ + Params: []function.Parameter{}, + VarParam: &function.Parameter{ + Name: "vals", + Type: cty.DynamicPseudoType, + AllowUnknown: true, + AllowDynamicType: true, + AllowNull: true, + }, + Type: func(args []cty.Value) (ret cty.Type, err error) { + return cty.DynamicPseudoType, fmt.Errorf("the \"map\" function was deprecated in Terraform v0.12 and is no longer available; use tomap({ ... }) syntax to write a literal map") + }, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + return cty.DynamicVal, fmt.Errorf("the \"map\" function was deprecated in Terraform v0.12 and is no longer available; use tomap({ ... }) syntax to write a literal map") + }, +}) diff --git a/pkg/iac/scanners/terraform/parser/funcs/conversion.go b/pkg/iac/scanners/terraform/parser/funcs/conversion.go new file mode 100644 index 000000000000..18a1310e69f7 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/conversion.go @@ -0,0 +1,96 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "strconv" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/convert" + "github.com/zclconf/go-cty/cty/function" +) + +// MakeToFunc constructs a "to..." function, like "tostring", which converts +// its argument to a specific type or type kind. +// +// The given type wantTy can be any type constraint that cty's "convert" package +// would accept. In particular, this means that you can pass +// cty.List(cty.DynamicPseudoType) to mean "list of any single type", which +// will then cause cty to attempt to unify all of the element types when given +// a tuple. +func MakeToFunc(wantTy cty.Type) function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "v", + // We use DynamicPseudoType rather than wantTy here so that + // all values will pass through the function API verbatim and + // we can handle the conversion logic within the Type and + // Impl functions. This allows us to customize the error + // messages to be more appropriate for an explicit type + // conversion, whereas the cty function system produces + // messages aimed at _implicit_ type conversions. + Type: cty.DynamicPseudoType, + AllowNull: true, + AllowMarked: true, + AllowDynamicType: true, + }, + }, + Type: func(args []cty.Value) (cty.Type, error) { + gotTy := args[0].Type() + if gotTy.Equals(wantTy) { + return wantTy, nil + } + conv := convert.GetConversionUnsafe(args[0].Type(), wantTy) + if conv == nil { + // We'll use some specialized errors for some trickier cases, + // but most we can handle in a simple way. + switch { + case gotTy.IsTupleType() && wantTy.IsTupleType(): + return cty.NilType, function.NewArgErrorf(0, "incompatible tuple type for conversion: %s", convert.MismatchMessage(gotTy, wantTy)) + case gotTy.IsObjectType() && wantTy.IsObjectType(): + return cty.NilType, function.NewArgErrorf(0, "incompatible object type for conversion: %s", convert.MismatchMessage(gotTy, wantTy)) + default: + return cty.NilType, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) + } + } + // If a conversion is available then everything is fine. + return wantTy, nil + }, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + // We didn't set "AllowUnknown" on our argument, so it is guaranteed + // to be known here but may still be null. + ret, err := convert.Convert(args[0], retType) + if err != nil { + val, _ := args[0].UnmarkDeep() + // Because we used GetConversionUnsafe above, conversion can + // still potentially fail in here. For example, if the user + // asks to convert the string "a" to bool then we'll + // optimistically permit it during type checking but fail here + // once we note that the value isn't either "true" or "false". + gotTy := val.Type() + switch { + case Contains(args[0], MarkedSensitive): + // Generic message so we won't inadvertently disclose + // information about sensitive values. + return cty.NilVal, function.NewArgErrorf(0, "cannot convert this sensitive %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) + + case gotTy == cty.String && wantTy == cty.Bool: + what := "string" + if !val.IsNull() { + what = strconv.Quote(val.AsString()) + } + return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to bool; only the strings "true" or "false" are allowed`, what) + case gotTy == cty.String && wantTy == cty.Number: + what := "string" + if !val.IsNull() { + what = strconv.Quote(val.AsString()) + } + return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to number; given string must be a decimal representation of a number`, what) + default: + return cty.NilVal, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) + } + } + return ret, nil + }, + }) +} diff --git a/pkg/iac/scanners/terraform/parser/funcs/crypto.go b/pkg/iac/scanners/terraform/parser/funcs/crypto.go new file mode 100644 index 000000000000..894da1280c1a --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/crypto.go @@ -0,0 +1,270 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "crypto/md5" // nolint: gosec + "crypto/rsa" + "crypto/sha1" // nolint: gosec + "crypto/sha256" + "crypto/sha512" + "encoding/asn1" + "encoding/base64" + "encoding/hex" + "fmt" + "hash" + "io" + "io/fs" + "strings" + + uuidv5 "github.com/google/uuid" + uuid "github.com/hashicorp/go-uuid" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + "github.com/zclconf/go-cty/cty/gocty" + "golang.org/x/crypto/bcrypt" + "golang.org/x/crypto/ssh" +) + +var UUIDFunc = function.New(&function.Spec{ + Params: []function.Parameter{}, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + result, err := uuid.GenerateUUID() + if err != nil { + return cty.UnknownVal(cty.String), err + } + return cty.StringVal(result), nil + }, +}) + +var UUIDV5Func = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "namespace", + Type: cty.String, + }, + { + Name: "name", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var namespace uuidv5.UUID + switch { + case args[0].AsString() == "dns": + namespace = uuidv5.NameSpaceDNS + case args[0].AsString() == "url": + namespace = uuidv5.NameSpaceURL + case args[0].AsString() == "oid": + namespace = uuidv5.NameSpaceOID + case args[0].AsString() == "x500": + namespace = uuidv5.NameSpaceX500 + default: + if namespace, err = uuidv5.Parse(args[0].AsString()); err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("uuidv5() doesn't support namespace %s (%v)", args[0].AsString(), err) + } + } + val := args[1].AsString() + return cty.StringVal(uuidv5.NewSHA1(namespace, []byte(val)).String()), nil + }, +}) + +// Base64Sha256Func constructs a function that computes the SHA256 hash of a given string +// and encodes it with Base64. +var Base64Sha256Func = makeStringHashFunction(sha256.New, base64.StdEncoding.EncodeToString) + +// MakeFileBase64Sha256Func constructs a function that is like Base64Sha256Func but reads the +// contents of a file rather than hashing a given literal string. +func MakeFileBase64Sha256Func(target fs.FS, baseDir string) function.Function { + return makeFileHashFunction(target, baseDir, sha256.New, base64.StdEncoding.EncodeToString) +} + +// Base64Sha512Func constructs a function that computes the SHA256 hash of a given string +// and encodes it with Base64. +var Base64Sha512Func = makeStringHashFunction(sha512.New, base64.StdEncoding.EncodeToString) + +// MakeFileBase64Sha512Func constructs a function that is like Base64Sha512Func but reads the +// contents of a file rather than hashing a given literal string. +func MakeFileBase64Sha512Func(target fs.FS, baseDir string) function.Function { + return makeFileHashFunction(target, baseDir, sha512.New, base64.StdEncoding.EncodeToString) +} + +// BcryptFunc constructs a function that computes a hash of the given string using the Blowfish cipher. +var BcryptFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + }, + VarParam: &function.Parameter{ + Name: "cost", + Type: cty.Number, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + defaultCost := 10 + + if len(args) > 1 { + var val int + if err := gocty.FromCtyValue(args[1], &val); err != nil { + return cty.UnknownVal(cty.String), err + } + defaultCost = val + } + + if len(args) > 2 { + return cty.UnknownVal(cty.String), fmt.Errorf("bcrypt() takes no more than two arguments") + } + + input := args[0].AsString() + out, err := bcrypt.GenerateFromPassword([]byte(input), defaultCost) + if err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("error occurred generating password %s", err.Error()) + } + + return cty.StringVal(string(out)), nil + }, +}) + +// Md5Func constructs a function that computes the MD5 hash of a given string and encodes it with hexadecimal digits. +var Md5Func = makeStringHashFunction(md5.New, hex.EncodeToString) + +// MakeFileMd5Func constructs a function that is like Md5Func but reads the +// contents of a file rather than hashing a given literal string. +func MakeFileMd5Func(target fs.FS, baseDir string) function.Function { + return makeFileHashFunction(target, baseDir, md5.New, hex.EncodeToString) +} + +// RsaDecryptFunc constructs a function that decrypts an RSA-encrypted ciphertext. +var RsaDecryptFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "ciphertext", + Type: cty.String, + }, + { + Name: "privatekey", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + s := args[0].AsString() + key := args[1].AsString() + + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "failed to decode input %q: cipher text must be base64-encoded", s) + } + + rawKey, err := ssh.ParseRawPrivateKey([]byte(key)) + if err != nil { + var errStr string + switch e := err.(type) { + case asn1.SyntaxError: + errStr = strings.ReplaceAll(e.Error(), "asn1: syntax error", "invalid ASN1 data in the given private key") + case asn1.StructuralError: + errStr = strings.ReplaceAll(e.Error(), "asn1: structure error", "invalid ASN1 data in the given private key") + default: + errStr = fmt.Sprintf("invalid private key: %s", e) + } + return cty.UnknownVal(cty.String), function.NewArgErrorf(1, errStr) + } + privateKey, ok := rawKey.(*rsa.PrivateKey) + if !ok { + return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "invalid private key type %t", rawKey) + } + + out, err := rsa.DecryptPKCS1v15(nil, privateKey, b) + if err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("failed to decrypt: %s", err) + } + + return cty.StringVal(string(out)), nil + }, +}) + +// Sha1Func constructs a function that computes the SHA1 hash of a given string +// and encodes it with hexadecimal digits. +var Sha1Func = makeStringHashFunction(sha1.New, hex.EncodeToString) + +// MakeFileSha1Func constructs a function that is like Sha1Func but reads the +// contents of a file rather than hashing a given literal string. +func MakeFileSha1Func(target fs.FS, baseDir string) function.Function { + return makeFileHashFunction(target, baseDir, sha1.New, hex.EncodeToString) +} + +// Sha256Func constructs a function that computes the SHA256 hash of a given string +// and encodes it with hexadecimal digits. +var Sha256Func = makeStringHashFunction(sha256.New, hex.EncodeToString) + +// MakeFileSha256Func constructs a function that is like Sha256Func but reads the +// contents of a file rather than hashing a given literal string. +func MakeFileSha256Func(target fs.FS, baseDir string) function.Function { + return makeFileHashFunction(target, baseDir, sha256.New, hex.EncodeToString) +} + +// Sha512Func constructs a function that computes the SHA512 hash of a given string +// and encodes it with hexadecimal digits. +var Sha512Func = makeStringHashFunction(sha512.New, hex.EncodeToString) + +// MakeFileSha512Func constructs a function that is like Sha512Func but reads the +// contents of a file rather than hashing a given literal string. +func MakeFileSha512Func(target fs.FS, baseDir string) function.Function { + return makeFileHashFunction(target, baseDir, sha512.New, hex.EncodeToString) +} + +func makeStringHashFunction(hf func() hash.Hash, enc func([]byte) string) function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + s := args[0].AsString() + h := hf() + h.Write([]byte(s)) + rv := enc(h.Sum(nil)) + return cty.StringVal(rv), nil + }, + }) +} + +func makeFileHashFunction(target fs.FS, baseDir string, hf func() hash.Hash, enc func([]byte) string) function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "path", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + path := args[0].AsString() + f, err := openFile(target, baseDir, path) + if err != nil { + return cty.UnknownVal(cty.String), err + } + defer f.Close() + + h := hf() + _, err = io.Copy(h, f) + if err != nil { + return cty.UnknownVal(cty.String), err + } + rv := enc(h.Sum(nil)) + return cty.StringVal(rv), nil + }, + }) +} diff --git a/pkg/iac/scanners/terraform/parser/funcs/datetime.go b/pkg/iac/scanners/terraform/parser/funcs/datetime.go new file mode 100644 index 000000000000..11ed3c8a2214 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/datetime.go @@ -0,0 +1,157 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "fmt" + "time" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" +) + +// TimestampFunc constructs a function that returns a string representation of the current date and time. +var TimestampFunc = function.New(&function.Spec{ + Params: []function.Parameter{}, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + return cty.StringVal(time.Now().UTC().Format(time.RFC3339)), nil + }, +}) + +// MakeStaticTimestampFunc constructs a function that returns a string +// representation of the date and time specified by the provided argument. +func MakeStaticTimestampFunc(static time.Time) function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{}, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + return cty.StringVal(static.Format(time.RFC3339)), nil + }, + }) +} + +// TimeAddFunc constructs a function that adds a duration to a timestamp, returning a new timestamp. +var TimeAddFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "timestamp", + Type: cty.String, + }, + { + Name: "duration", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + ts, err := parseTimestamp(args[0].AsString()) + if err != nil { + return cty.UnknownVal(cty.String), err + } + duration, err := time.ParseDuration(args[1].AsString()) + if err != nil { + return cty.UnknownVal(cty.String), err + } + + return cty.StringVal(ts.Add(duration).Format(time.RFC3339)), nil + }, +}) + +// TimeCmpFunc is a function that compares two timestamps. +var TimeCmpFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "timestamp_a", + Type: cty.String, + }, + { + Name: "timestamp_b", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.Number), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + tsA, err := parseTimestamp(args[0].AsString()) + if err != nil { + return cty.UnknownVal(cty.String), function.NewArgError(0, err) + } + tsB, err := parseTimestamp(args[1].AsString()) + if err != nil { + return cty.UnknownVal(cty.String), function.NewArgError(1, err) + } + + switch { + case tsA.Equal(tsB): + return cty.NumberIntVal(0), nil + case tsA.Before(tsB): + return cty.NumberIntVal(-1), nil + default: + // By elimintation, tsA must be after tsB. + return cty.NumberIntVal(1), nil + } + }, +}) + +func parseTimestamp(ts string) (time.Time, error) { + t, err := time.Parse(time.RFC3339, ts) + if err != nil { + switch err := err.(type) { + case *time.ParseError: + // If err is a time.ParseError then its string representation is not + // appropriate since it relies on details of Go's strange date format + // representation, which a caller of our functions is not expected + // to be familiar with. + // + // Therefore we do some light transformation to get a more suitable + // error that should make more sense to our callers. These are + // still not awesome error messages, but at least they refer to + // the timestamp portions by name rather than by Go's example + // values. + if err.LayoutElem == "" && err.ValueElem == "" && err.Message != "" { + // For some reason err.Message is populated with a ": " prefix + // by the time package. + return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp%s", err.Message) + } + var what string + switch err.LayoutElem { + case "2006": + what = "year" + case "01": + what = "month" + case "02": + what = "day of month" + case "15": + what = "hour" + case "04": + what = "minute" + case "05": + what = "second" + case "Z07:00": + what = "UTC offset" + case "T": + return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: missing required time introducer 'T'") + case ":", "-": + if err.ValueElem == "" { + return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: end of string where %q is expected", err.LayoutElem) + } else { + return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: found %q where %q is expected", err.ValueElem, err.LayoutElem) + } + default: + // Should never get here, because time.RFC3339 includes only the + // above portions, but since that might change in future we'll + // be robust here. + what = "timestamp segment" + } + if err.ValueElem == "" { + return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: end of string before %s", what) + } else { + return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: cannot use %q as %s", err.ValueElem, what) + } + } + return time.Time{}, err + } + return t, nil +} diff --git a/pkg/iac/scanners/terraform/parser/funcs/encoding.go b/pkg/iac/scanners/terraform/parser/funcs/encoding.go new file mode 100644 index 000000000000..e5fb8490818f --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/encoding.go @@ -0,0 +1,192 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "log" + "net/url" + "unicode/utf8" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + "golang.org/x/text/encoding/ianaindex" +) + +// Base64DecodeFunc constructs a function that decodes a string containing a base64 sequence. +var Base64DecodeFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + AllowMarked: true, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + str, strMarks := args[0].Unmark() + s := str.AsString() + sDec, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode base64 data %s", redactIfSensitive(s, strMarks)) + } + if !utf8.Valid([]byte(sDec)) { + log.Printf("[DEBUG] the result of decoding the provided string is not valid UTF-8: %s", redactIfSensitive(sDec, strMarks)) + return cty.UnknownVal(cty.String), fmt.Errorf("the result of decoding the provided string is not valid UTF-8") + } + return cty.StringVal(string(sDec)).WithMarks(strMarks), nil + }, +}) + +// Base64EncodeFunc constructs a function that encodes a string to a base64 sequence. +var Base64EncodeFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + return cty.StringVal(base64.StdEncoding.EncodeToString([]byte(args[0].AsString()))), nil + }, +}) + +// TextEncodeBase64Func constructs a function that encodes a string to a target encoding and then to a base64 sequence. +var TextEncodeBase64Func = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "string", + Type: cty.String, + }, + { + Name: "encoding", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + encoding, err := ianaindex.IANA.Encoding(args[1].AsString()) + if err != nil || encoding == nil { + return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "%q is not a supported IANA encoding name or alias in this Terraform version", args[1].AsString()) + } + + encName, err := ianaindex.IANA.Name(encoding) + if err != nil { // would be weird, since we just read this encoding out + encName = args[1].AsString() + } + + encoder := encoding.NewEncoder() + encodedInput, err := encoder.Bytes([]byte(args[0].AsString())) + if err != nil { + // The string representations of "err" disclose implementation + // details of the underlying library, and the main error we might + // like to return a special message for is unexported as + // golang.org/x/text/encoding/internal.RepertoireError, so this + // is just a generic error message for now. + // + // We also don't include the string itself in the message because + // it can typically be very large, contain newline characters, + // etc. + return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "the given string contains characters that cannot be represented in %s", encName) + } + + return cty.StringVal(base64.StdEncoding.EncodeToString(encodedInput)), nil + }, +}) + +// TextDecodeBase64Func constructs a function that decodes a base64 sequence to a target encoding. +var TextDecodeBase64Func = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "source", + Type: cty.String, + }, + { + Name: "encoding", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + encoding, err := ianaindex.IANA.Encoding(args[1].AsString()) + if err != nil || encoding == nil { + return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "%q is not a supported IANA encoding name or alias in this Terraform version", args[1].AsString()) + } + + encName, err := ianaindex.IANA.Name(encoding) + if err != nil { // would be weird, since we just read this encoding out + encName = args[1].AsString() + } + + s := args[0].AsString() + sDec, err := base64.StdEncoding.DecodeString(s) + if err != nil { + switch err := err.(type) { + case base64.CorruptInputError: + return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "the given value is has an invalid base64 symbol at offset %d", int(err)) + default: + return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid source string: %w", err) + } + + } + + decoder := encoding.NewDecoder() + decoded, err := decoder.Bytes(sDec) + if err != nil || bytes.ContainsRune(decoded, '�') { + return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "the given string contains symbols that are not defined for %s", encName) + } + + return cty.StringVal(string(decoded)), nil + }, +}) + +// Base64GzipFunc constructs a function that compresses a string with gzip and then encodes the result in +// Base64 encoding. +var Base64GzipFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + s := args[0].AsString() + + var b bytes.Buffer + gz := gzip.NewWriter(&b) + if _, err := gz.Write([]byte(s)); err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("failed to write gzip raw data: %w", err) + } + if err := gz.Flush(); err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("failed to flush gzip writer: %w", err) + } + if err := gz.Close(); err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("failed to close gzip writer: %w", err) + } + return cty.StringVal(base64.StdEncoding.EncodeToString(b.Bytes())), nil + }, +}) + +// URLEncodeFunc constructs a function that applies URL encoding to a given string. +var URLEncodeFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + return cty.StringVal(url.QueryEscape(args[0].AsString())), nil + }, +}) diff --git a/pkg/iac/scanners/terraform/parser/funcs/filesystem.go b/pkg/iac/scanners/terraform/parser/funcs/filesystem.go new file mode 100644 index 000000000000..a53e975443f6 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/filesystem.go @@ -0,0 +1,407 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "encoding/base64" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "unicode/utf8" + + "github.com/bmatcuk/doublestar/v4" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/mitchellh/go-homedir" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" +) + +// MakeFileFunc constructs a function that takes a file path and returns the +// contents of that file, either directly as a string (where valid UTF-8 is +// required) or as a string containing base64 bytes. +func MakeFileFunc(target fs.FS, baseDir string, encBase64 bool) function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "path", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + path := args[0].AsString() + src, err := readFileBytes(target, baseDir, path) + if err != nil { + err = function.NewArgError(0, err) + return cty.UnknownVal(cty.String), err + } + + switch { + case encBase64: + enc := base64.StdEncoding.EncodeToString(src) + return cty.StringVal(enc), nil + default: + if !utf8.Valid(src) { + return cty.UnknownVal(cty.String), fmt.Errorf("contents of %s are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead", path) + } + return cty.StringVal(string(src)), nil + } + }, + }) +} + +// MakeTemplateFileFunc constructs a function that takes a file path and +// an arbitrary object of named values and attempts to render the referenced +// file as a template using HCL template syntax. +// +// The template itself may recursively call other functions so a callback +// must be provided to get access to those functions. The template cannot, +// however, access any variables defined in the scope: it is restricted only to +// those variables provided in the second function argument, to ensure that all +// dependencies on other graph nodes can be seen before executing this function. +// +// As a special exception, a referenced template file may not recursively call +// the templatefile function, since that would risk the same file being +// included into itself indefinitely. +func MakeTemplateFileFunc(target fs.FS, baseDir string, funcsCb func() map[string]function.Function) function.Function { + + params := []function.Parameter{ + { + Name: "path", + Type: cty.String, + }, + { + Name: "vars", + Type: cty.DynamicPseudoType, + }, + } + + loadTmpl := func(fn string) (hcl.Expression, error) { + // We re-use File here to ensure the same filename interpretation + // as it does, along with its other safety checks. + tmplVal, err := File(target, baseDir, cty.StringVal(fn)) + if err != nil { + return nil, err + } + + expr, diags := hclsyntax.ParseTemplate([]byte(tmplVal.AsString()), fn, hcl.Pos{Line: 1, Column: 1}) + if diags.HasErrors() { + return nil, diags + } + + return expr, nil + } + + renderTmpl := func(expr hcl.Expression, varsVal cty.Value) (cty.Value, error) { + if varsTy := varsVal.Type(); !(varsTy.IsMapType() || varsTy.IsObjectType()) { + return cty.DynamicVal, function.NewArgErrorf(1, "invalid vars value: must be a map") // or an object, but we don't strongly distinguish these most of the time + } + + ctx := &hcl.EvalContext{ + Variables: varsVal.AsValueMap(), + } + + // We require all of the variables to be valid HCL identifiers, because + // otherwise there would be no way to refer to them in the template + // anyway. Rejecting this here gives better feedback to the user + // than a syntax error somewhere in the template itself. + for n := range ctx.Variables { + if !hclsyntax.ValidIdentifier(n) { + // This error message intentionally doesn't describe _all_ of + // the different permutations that are technically valid as an + // HCL identifier, but rather focuses on what we might + // consider to be an "idiomatic" variable name. + return cty.DynamicVal, function.NewArgErrorf(1, "invalid template variable name %q: must start with a letter, followed by zero or more letters, digits, and underscores", n) + } + } + + // We'll pre-check references in the template here so we can give a + // more specialized error message than HCL would by default, so it's + // clearer that this problem is coming from a templatefile call. + for _, traversal := range expr.Variables() { + root := traversal.RootName() + if _, ok := ctx.Variables[root]; !ok { + return cty.DynamicVal, function.NewArgErrorf(1, "vars map does not contain key %q, referenced at %s", root, traversal[0].SourceRange()) + } + } + + givenFuncs := funcsCb() // this callback indirection is to avoid chicken/egg problems + funcs := make(map[string]function.Function, len(givenFuncs)) + for name, fn := range givenFuncs { + if name == "templatefile" { + // We stub this one out to prevent recursive calls. + funcs[name] = function.New(&function.Spec{ + Params: params, + Type: func(args []cty.Value) (cty.Type, error) { + return cty.NilType, fmt.Errorf("cannot recursively call templatefile from inside templatefile call") + }, + }) + continue + } + funcs[name] = fn + } + ctx.Functions = funcs + + val, diags := expr.Value(ctx) + if diags.HasErrors() { + return cty.DynamicVal, diags + } + return val, nil + } + + return function.New(&function.Spec{ + Params: params, + Type: func(args []cty.Value) (cty.Type, error) { + if !(args[0].IsKnown() && args[1].IsKnown()) { + return cty.DynamicPseudoType, nil + } + + // We'll render our template now to see what result type it produces. + // A template consisting only of a single interpolation an potentially + // return any type. + expr, err := loadTmpl(args[0].AsString()) + if err != nil { + return cty.DynamicPseudoType, err + } + + // This is safe even if args[1] contains unknowns because the HCL + // template renderer itself knows how to short-circuit those. + val, err := renderTmpl(expr, args[1]) + return val.Type(), err + }, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + expr, err := loadTmpl(args[0].AsString()) + if err != nil { + return cty.DynamicVal, err + } + return renderTmpl(expr, args[1]) + }, + }) + +} + +// MakeFileExistsFunc constructs a function that takes a path +// and determines whether a file exists at that path +func MakeFileExistsFunc(target fs.FS, baseDir string) function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "path", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + path := args[0].AsString() + path, err := homedir.Expand(path) + if err != nil { + return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to expand ~: %s", err) + } + + if !filepath.IsAbs(path) { + path = filepath.Join(baseDir, path) + } + + // Trivy uses a virtual file system + path = filepath.ToSlash(path) + + fi, err := fs.Stat(target, path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return cty.False, nil + } + return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to stat %s", path) + } + + if fi.Mode().IsRegular() { + return cty.True, nil + } + + return cty.False, fmt.Errorf("%s is not a regular file, but %q", + path, fi.Mode().String()) + }, + }) +} + +// MakeFileSetFunc constructs a function that takes a glob pattern +// and enumerates a file set from that pattern +func MakeFileSetFunc(target fs.FS, baseDir string) function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "path", + Type: cty.String, + }, + { + Name: "pattern", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.Set(cty.String)), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + path := args[0].AsString() + pattern := args[1].AsString() + + if !filepath.IsAbs(path) { + path = filepath.Join(baseDir, path) + } + + // Join the path to the glob pattern, while ensuring the full + // pattern is canonical for the host OS. The joined path is + // automatically cleaned during this operation. + pattern = filepath.Join(path, pattern) + // Trivy uses a virtual file system + path = filepath.ToSlash(path) + + matches, err := doublestar.Glob(target, pattern) + if err != nil { + return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to glob pattern (%s): %s", pattern, err) + } + + var matchVals []cty.Value + for _, match := range matches { + fi, err := os.Stat(match) + + if err != nil { + return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to stat (%s): %s", match, err) + } + + if !fi.Mode().IsRegular() { + continue + } + + // Remove the path and file separator from matches. + match, err = filepath.Rel(path, match) + + if err != nil { + return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to trim path of match (%s): %s", match, err) + } + + // Replace any remaining file separators with forward slash (/) + // separators for cross-system compatibility. + match = filepath.ToSlash(match) + + matchVals = append(matchVals, cty.StringVal(match)) + } + + if len(matchVals) == 0 { + return cty.SetValEmpty(cty.String), nil + } + + return cty.SetVal(matchVals), nil + }, + }) +} + +// BasenameFunc constructs a function that takes a string containing a filesystem path +// and removes all except the last portion from it. +var BasenameFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "path", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + return cty.StringVal(filepath.Base(args[0].AsString())), nil + }, +}) + +// DirnameFunc constructs a function that takes a string containing a filesystem path +// and removes the last portion from it. +var DirnameFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "path", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + return cty.StringVal(filepath.Dir(args[0].AsString())), nil + }, +}) + +// AbsPathFunc constructs a function that converts a filesystem path to an absolute path +var AbsPathFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "path", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + absPath, err := filepath.Abs(args[0].AsString()) + return cty.StringVal(filepath.ToSlash(absPath)), err + }, +}) + +// PathExpandFunc constructs a function that expands a leading ~ character to the current user's home directory. +var PathExpandFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "path", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + + homePath, err := homedir.Expand(args[0].AsString()) + return cty.StringVal(homePath), err + }, +}) + +func openFile(target fs.FS, baseDir, path string) (fs.File, error) { + path, err := homedir.Expand(path) + if err != nil { + return nil, fmt.Errorf("failed to expand ~: %s", err) + } + + if !filepath.IsAbs(path) { + path = filepath.Join(baseDir, path) + } + + // Trivy uses a virtual file system + path = filepath.ToSlash(path) + + if target != nil { + return target.Open(path) + } + return os.Open(path) +} + +func readFileBytes(target fs.FS, baseDir, path string) ([]byte, error) { + f, err := openFile(target, baseDir, path) + if err != nil { + if os.IsNotExist(err) { + // An extra Terraform-specific hint for this situation + return nil, fmt.Errorf("no file exists at %s; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource", path) + } + return nil, err + } + + src, err := io.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("failed to read %s", path) + } + + return src, nil +} + +// File reads the contents of the file at the given path. +// +// The file must contain valid UTF-8 bytes, or this function will return an error. +// +// The underlying function implementation works relative to a particular base +// directory, so this wrapper takes a base directory string and uses it to +// construct the underlying function before calling it. +func File(target fs.FS, baseDir string, path cty.Value) (cty.Value, error) { + fn := MakeFileFunc(target, baseDir, false) + return fn.Call([]cty.Value{path}) +} diff --git a/pkg/iac/scanners/terraform/parser/funcs/ip.go b/pkg/iac/scanners/terraform/parser/funcs/ip.go new file mode 100644 index 000000000000..d1cf0352e95f --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/ip.go @@ -0,0 +1,261 @@ +// Copied from github.com/hashicorp/terraform/internal/ipaddr +package funcs + +import ( + stdnet "net" +) + +// Bigger than we need, not too big to worry about overflow +const bigVal = 0xFFFFFF + +// Decimal to integer. +// Returns number, characters consumed, success. +func dtoi(s string) (n int, i int, ok bool) { + n = 0 + for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + n = n*10 + int(s[i]-'0') + if n >= bigVal { + return bigVal, i, false + } + } + if i == 0 { + return 0, 0, false + } + return n, i, true +} + +// Hexadecimal to integer. +// Returns number, characters consumed, success. +func xtoi(s string) (n int, i int, ok bool) { + n = 0 + for i = 0; i < len(s); i++ { + if '0' <= s[i] && s[i] <= '9' { + n *= 16 + n += int(s[i] - '0') + } else if 'a' <= s[i] && s[i] <= 'f' { + n *= 16 + n += int(s[i]-'a') + 10 + } else if 'A' <= s[i] && s[i] <= 'F' { + n *= 16 + n += int(s[i]-'A') + 10 + } else { + break + } + if n >= bigVal { + return 0, i, false + } + } + if i == 0 { + return 0, i, false + } + return n, i, true +} + +// +// Lean on the standard net lib as much as possible. +// + +type IP = stdnet.IP +type IPNet = stdnet.IPNet +type ParseError = stdnet.ParseError + +const IPv4len = stdnet.IPv4len +const IPv6len = stdnet.IPv6len + +var CIDRMask = stdnet.CIDRMask +var IPv4 = stdnet.IPv4 + +// Parse IPv4 address (d.d.d.d). +func parseIPv4(s string) IP { + var p [IPv4len]byte + for i := 0; i < IPv4len; i++ { + if len(s) == 0 { + // Missing octets. + return nil + } + if i > 0 { + if s[0] != '.' { + return nil + } + s = s[1:] + } + n, c, ok := dtoi(s) + if !ok || n > 0xFF { + return nil + } + // + // NOTE: This correct check was added for go-1.17, but is a + // backwards-incompatible change for Terraform users, who might have + // already written modules with leading zeroes. + // + // if c > 1 && s[0] == '0' { + // // Reject non-zero components with leading zeroes. + // return nil + //} + s = s[c:] + p[i] = byte(n) + } + if len(s) != 0 { + return nil + } + return IPv4(p[0], p[1], p[2], p[3]) +} + +// parseIPv6 parses s as a literal IPv6 address described in RFC 4291 +// and RFC 5952. +func parseIPv6(s string) (ip IP) { + ip = make(IP, IPv6len) + ellipsis := -1 // position of ellipsis in ip + + // Might have leading ellipsis + if len(s) >= 2 && s[0] == ':' && s[1] == ':' { + ellipsis = 0 + s = s[2:] + // Might be only ellipsis + if len(s) == 0 { + return ip + } + } + + // Loop, parsing hex numbers followed by colon. + i := 0 + for i < IPv6len { + // Hex number. + n, c, ok := xtoi(s) + if !ok || n > 0xFFFF { + return nil + } + + // If followed by dot, might be in trailing IPv4. + if c < len(s) && s[c] == '.' { + if ellipsis < 0 && i != IPv6len-IPv4len { + // Not the right place. + return nil + } + if i+IPv4len > IPv6len { + // Not enough room. + return nil + } + ip4 := parseIPv4(s) + if ip4 == nil { + return nil + } + ip[i] = ip4[12] + ip[i+1] = ip4[13] + ip[i+2] = ip4[14] + ip[i+3] = ip4[15] + s = "" + i += IPv4len + break + } + + // Save this 16-bit chunk. + ip[i] = byte(n >> 8) + ip[i+1] = byte(n) + i += 2 + + // Stop at end of string. + s = s[c:] + if len(s) == 0 { + break + } + + // Otherwise must be followed by colon and more. + if s[0] != ':' || len(s) == 1 { + return nil + } + s = s[1:] + + // Look for ellipsis. + if s[0] == ':' { + if ellipsis >= 0 { // already have one + return nil + } + ellipsis = i + s = s[1:] + if len(s) == 0 { // can be at end + break + } + } + } + + // Must have used entire string. + if len(s) != 0 { + return nil + } + + // If didn't parse enough, expand ellipsis. + if i < IPv6len { + if ellipsis < 0 { + return nil + } + n := IPv6len - i + for j := i - 1; j >= ellipsis; j-- { + ip[j+n] = ip[j] + } + for j := ellipsis + n - 1; j >= ellipsis; j-- { + ip[j] = 0 + } + } else if ellipsis >= 0 { + // Ellipsis must represent at least one 0 group. + return nil + } + return ip +} + +// ParseIP parses s as an IP address, returning the result. +// The string s can be in IPv4 dotted decimal ("192.0.2.1"), IPv6 +// ("2001:db8::68"), or IPv4-mapped IPv6 ("::ffff:192.0.2.1") form. +// If s is not a valid textual representation of an IP address, +// ParseIP returns nil. +func ParseIP(s string) IP { + for i := 0; i < len(s); i++ { + switch s[i] { + case '.': + return parseIPv4(s) + case ':': + return parseIPv6(s) + } + } + return nil +} + +// ParseCIDR parses s as a CIDR notation IP address and prefix length, +// like "192.0.2.0/24" or "2001:db8::/32", as defined in +// RFC 4632 and RFC 4291. +// +// It returns the IP address and the network implied by the IP and +// prefix length. +// For example, ParseCIDR("192.0.2.1/24") returns the IP address +// 192.0.2.1 and the network 192.0.2.0/24. +func ParseCIDR(s string) (IP, *IPNet, error) { + i := indexByteString(s, '/') + if i < 0 { + return nil, nil, &ParseError{Type: "CIDR address", Text: s} + } + addr, mask := s[:i], s[i+1:] + iplen := IPv4len + ip := parseIPv4(addr) + if ip == nil { + iplen = IPv6len + ip = parseIPv6(addr) + } + n, i, ok := dtoi(mask) + if ip == nil || !ok || i != len(mask) || n < 0 || n > 8*iplen { + return nil, nil, &ParseError{Type: "CIDR address", Text: s} + } + m := CIDRMask(n, 8*iplen) + return ip, &IPNet{IP: ip.Mask(m), Mask: m}, nil +} + +// This is copied from go/src/internal/bytealg, which includes versions +// optimized for various platforms. Those optimizations are elided here so we +// don't have to maintain them. +func indexByteString(s string, c byte) int { + for i := 0; i < len(s); i++ { + if s[i] == c { + return i + } + } + return -1 +} diff --git a/pkg/iac/scanners/terraform/parser/funcs/marks.go b/pkg/iac/scanners/terraform/parser/funcs/marks.go new file mode 100644 index 000000000000..abbc397f1e08 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/marks.go @@ -0,0 +1,44 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/marks +package funcs + +import ( + "github.com/zclconf/go-cty/cty" + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// valueMarks allow creating strictly typed values for use as cty.Value marks. +// The variable name for new values should be the title-cased format of the +// value to better match the GoString output for debugging. +type valueMark string + +func (m valueMark) GoString() string { + return "marks." + cases.Title(language.English).String(string(m)) +} + +// Has returns true if and only if the cty.Value has the given mark. +func Has(val cty.Value, mark valueMark) bool { + return val.HasMark(mark) +} + +// Contains returns true if the cty.Value or any any value within it contains +// the given mark. +func Contains(val cty.Value, mark valueMark) bool { + ret := false + _ = cty.Walk(val, func(_ cty.Path, v cty.Value) (bool, error) { + if v.HasMark(mark) { + ret = true + return false, nil + } + return true, nil + }) + return ret +} + +// MarkedSensitive indicates that this value is marked as sensitive in the context of +// Terraform. +const MarkedSensitive = valueMark("sensitive") + +// MarkedRaw is used to indicate to the repl that the value should be written without +// any formatting. +const MarkedRaw = valueMark("raw") diff --git a/pkg/iac/scanners/terraform/parser/funcs/number.go b/pkg/iac/scanners/terraform/parser/funcs/number.go new file mode 100644 index 000000000000..60ebd660bf18 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/number.go @@ -0,0 +1,157 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "math" + "math/big" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + "github.com/zclconf/go-cty/cty/gocty" +) + +// LogFunc constructs a function that returns the logarithm of a given number in a given base. +var LogFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "num", + Type: cty.Number, + }, + { + Name: "base", + Type: cty.Number, + }, + }, + Type: function.StaticReturnType(cty.Number), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var num float64 + if err := gocty.FromCtyValue(args[0], &num); err != nil { + return cty.UnknownVal(cty.String), err + } + + var base float64 + if err := gocty.FromCtyValue(args[1], &base); err != nil { + return cty.UnknownVal(cty.String), err + } + + return cty.NumberFloatVal(math.Log(num) / math.Log(base)), nil + }, +}) + +// PowFunc constructs a function that returns the logarithm of a given number in a given base. +var PowFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "num", + Type: cty.Number, + }, + { + Name: "power", + Type: cty.Number, + }, + }, + Type: function.StaticReturnType(cty.Number), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var num float64 + if err := gocty.FromCtyValue(args[0], &num); err != nil { + return cty.UnknownVal(cty.String), err + } + + var power float64 + if err := gocty.FromCtyValue(args[1], &power); err != nil { + return cty.UnknownVal(cty.String), err + } + + return cty.NumberFloatVal(math.Pow(num, power)), nil + }, +}) + +// SignumFunc constructs a function that returns the closest whole number greater +// than or equal to the given value. +var SignumFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "num", + Type: cty.Number, + }, + }, + Type: function.StaticReturnType(cty.Number), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var num int + if err := gocty.FromCtyValue(args[0], &num); err != nil { + return cty.UnknownVal(cty.String), err + } + switch { + case num < 0: + return cty.NumberIntVal(-1), nil + case num > 0: + return cty.NumberIntVal(+1), nil + default: + return cty.NumberIntVal(0), nil + } + }, +}) + +// ParseIntFunc constructs a function that parses a string argument and returns an integer of the specified base. +var ParseIntFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "number", + Type: cty.DynamicPseudoType, + AllowMarked: true, + }, + { + Name: "base", + Type: cty.Number, + AllowMarked: true, + }, + }, + + Type: func(args []cty.Value) (cty.Type, error) { + if !args[0].Type().Equals(cty.String) { + return cty.Number, function.NewArgErrorf(0, "first argument must be a string, not %s", args[0].Type().FriendlyName()) + } + return cty.Number, nil + }, + RefineResult: refineNotNull, + + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + var numstr string + var base int + var err error + + numArg, numMarks := args[0].Unmark() + if err = gocty.FromCtyValue(numArg, &numstr); err != nil { + return cty.UnknownVal(cty.String), function.NewArgError(0, err) + } + + baseArg, baseMarks := args[1].Unmark() + if err = gocty.FromCtyValue(baseArg, &base); err != nil { + return cty.UnknownVal(cty.Number), function.NewArgError(1, err) + } + + if base < 2 || base > 62 { + return cty.UnknownVal(cty.Number), function.NewArgErrorf( + 1, + "base must be a whole number between 2 and 62 inclusive", + ) + } + + num, ok := (&big.Int{}).SetString(numstr, base) + if !ok { + return cty.UnknownVal(cty.Number), function.NewArgErrorf( + 0, + "cannot parse %s as a base %s integer", + redactIfSensitive(numstr, numMarks), + redactIfSensitive(base, baseMarks), + ) + } + + parsedNum := cty.NumberVal((&big.Float{}).SetInt(num)).WithMarks(numMarks, baseMarks) + + return parsedNum, nil + }, +}) diff --git a/pkg/iac/scanners/terraform/parser/funcs/redact.go b/pkg/iac/scanners/terraform/parser/funcs/redact.go new file mode 100644 index 000000000000..f5908fc7da57 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/redact.go @@ -0,0 +1,20 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "fmt" + + "github.com/zclconf/go-cty/cty" +) + +func redactIfSensitive(value interface{}, markses ...cty.ValueMarks) string { + if Has(cty.DynamicVal.WithMarks(markses...), MarkedSensitive) { + return "(sensitive value)" + } + switch v := value.(type) { + case string: + return fmt.Sprintf("%q", v) + default: + return fmt.Sprintf("%v", v) + } +} diff --git a/pkg/iac/scanners/terraform/parser/funcs/refinements.go b/pkg/iac/scanners/terraform/parser/funcs/refinements.go new file mode 100644 index 000000000000..de9cb08b1604 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/refinements.go @@ -0,0 +1,10 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "github.com/zclconf/go-cty/cty" +) + +func refineNotNull(b *cty.RefinementBuilder) *cty.RefinementBuilder { + return b.NotNull() +} diff --git a/pkg/iac/scanners/terraform/parser/funcs/sensitive.go b/pkg/iac/scanners/terraform/parser/funcs/sensitive.go new file mode 100644 index 000000000000..3566a678fc9b --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/sensitive.go @@ -0,0 +1,74 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" +) + +// SensitiveFunc returns a value identical to its argument except that +// Terraform will consider it to be sensitive. +var SensitiveFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "value", + Type: cty.DynamicPseudoType, + AllowUnknown: true, + AllowNull: true, + AllowMarked: true, + AllowDynamicType: true, + }, + }, + Type: func(args []cty.Value) (cty.Type, error) { + // This function only affects the value's marks, so the result + // type is always the same as the argument type. + return args[0].Type(), nil + }, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + val, _ := args[0].Unmark() + return val.Mark(MarkedSensitive), nil + }, +}) + +// NonsensitiveFunc takes a sensitive value and returns the same value without +// the sensitive marking, effectively exposing the value. +var NonsensitiveFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "value", + Type: cty.DynamicPseudoType, + AllowUnknown: true, + AllowNull: true, + AllowMarked: true, + AllowDynamicType: true, + }, + }, + Type: func(args []cty.Value) (cty.Type, error) { + // This function only affects the value's marks, so the result + // type is always the same as the argument type. + return args[0].Type(), nil + }, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + v, marks := args[0].Unmark() + delete(marks, MarkedSensitive) // remove the sensitive marking + return v.WithMarks(marks), nil + }, +}) + +var IssensitiveFunc = function.New(&function.Spec{ + Params: []function.Parameter{{ + Name: "value", + Type: cty.DynamicPseudoType, + AllowUnknown: true, + AllowNull: true, + AllowMarked: true, + AllowDynamicType: true, + }}, + Type: func(args []cty.Value) (cty.Type, error) { + return cty.Bool, nil + }, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + s := args[0].HasMark(MarkedSensitive) + return cty.BoolVal(s), nil + }, +}) diff --git a/pkg/iac/scanners/terraform/parser/funcs/string.go b/pkg/iac/scanners/terraform/parser/funcs/string.go new file mode 100644 index 000000000000..f859c7af7a31 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/funcs/string.go @@ -0,0 +1,152 @@ +// Copied from github.com/hashicorp/terraform/internal/lang/funcs +package funcs + +import ( + "regexp" + "strings" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" +) + +// StartsWithFunc constructs a function that checks if a string starts with +// a specific prefix using strings.HasPrefix +var StartsWithFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + AllowUnknown: true, + }, + { + Name: "prefix", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.Bool), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + prefix := args[1].AsString() + + if !args[0].IsKnown() { + // If the unknown value has a known prefix then we might be + // able to still produce a known result. + if prefix == "" { + // The empty string is a prefix of any string. + return cty.True, nil + } + if knownPrefix := args[0].Range().StringPrefix(); knownPrefix != "" { + if strings.HasPrefix(knownPrefix, prefix) { + return cty.True, nil + } + if len(knownPrefix) >= len(prefix) { + // If the prefix we're testing is no longer than the known + // prefix and it didn't match then the full string with + // that same prefix can't match either. + return cty.False, nil + } + } + return cty.UnknownVal(cty.Bool), nil + } + + str := args[0].AsString() + + if strings.HasPrefix(str, prefix) { + return cty.True, nil + } + + return cty.False, nil + }, +}) + +// EndsWithFunc constructs a function that checks if a string ends with +// a specific suffix using strings.HasSuffix +var EndsWithFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + { + Name: "suffix", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.Bool), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + str := args[0].AsString() + suffix := args[1].AsString() + + if strings.HasSuffix(str, suffix) { + return cty.True, nil + } + + return cty.False, nil + }, +}) + +// ReplaceFunc constructs a function that searches a given string for another +// given substring, and replaces each occurrence with a given replacement string. +var ReplaceFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + { + Name: "substr", + Type: cty.String, + }, + { + Name: "replace", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + str := args[0].AsString() + substr := args[1].AsString() + replace := args[2].AsString() + + // We search/replace using a regexp if the string is surrounded + // in forward slashes. + if len(substr) > 1 && substr[0] == '/' && substr[len(substr)-1] == '/' { + re, err := regexp.Compile(substr[1 : len(substr)-1]) + if err != nil { + return cty.UnknownVal(cty.String), err + } + + return cty.StringVal(re.ReplaceAllString(str, replace)), nil + } + + return cty.StringVal(strings.Replace(str, substr, replace, -1)), nil + }, +}) + +// StrContainsFunc searches a given string for another given substring, +// if found the function returns true, otherwise returns false. +var StrContainsFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + { + Name: "substr", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + str := args[0].AsString() + substr := args[1].AsString() + + if strings.Contains(str, substr) { + return cty.True, nil + } + + return cty.False, nil + }, +}) diff --git a/pkg/iac/scanners/terraform/parser/functions.go b/pkg/iac/scanners/terraform/parser/functions.go new file mode 100644 index 000000000000..6f6406d8ed19 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/functions.go @@ -0,0 +1,137 @@ +package parser + +import ( + "io/fs" + + "github.com/hashicorp/hcl/v2/ext/tryfunc" + ctyyaml "github.com/zclconf/go-cty-yaml" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + "github.com/zclconf/go-cty/cty/function/stdlib" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/funcs" +) + +// Functions returns the set of functions that should be used to when evaluating +// expressions in the receiving scope. +func Functions(target fs.FS, baseDir string) map[string]function.Function { + return map[string]function.Function{ + "abs": stdlib.AbsoluteFunc, + "abspath": funcs.AbsPathFunc, + "alltrue": funcs.AllTrueFunc, + "anytrue": funcs.AnyTrueFunc, + "basename": funcs.BasenameFunc, + "base64decode": funcs.Base64DecodeFunc, + "base64encode": funcs.Base64EncodeFunc, + "base64gzip": funcs.Base64GzipFunc, + "base64sha256": funcs.Base64Sha256Func, + "base64sha512": funcs.Base64Sha512Func, + "bcrypt": funcs.BcryptFunc, + "can": tryfunc.CanFunc, + "ceil": stdlib.CeilFunc, + "chomp": stdlib.ChompFunc, + "cidrhost": funcs.CidrHostFunc, + "cidrnetmask": funcs.CidrNetmaskFunc, + "cidrsubnet": funcs.CidrSubnetFunc, + "cidrsubnets": funcs.CidrSubnetsFunc, + "coalesce": funcs.CoalesceFunc, + "coalescelist": stdlib.CoalesceListFunc, + "compact": stdlib.CompactFunc, + "concat": stdlib.ConcatFunc, + "contains": stdlib.ContainsFunc, + "csvdecode": stdlib.CSVDecodeFunc, + "dirname": funcs.DirnameFunc, + "distinct": stdlib.DistinctFunc, + "element": stdlib.ElementFunc, + "endswith": funcs.EndsWithFunc, + "chunklist": stdlib.ChunklistFunc, + "file": funcs.MakeFileFunc(target, baseDir, false), + "fileexists": funcs.MakeFileExistsFunc(target, baseDir), + "fileset": funcs.MakeFileSetFunc(target, baseDir), + "filebase64": funcs.MakeFileFunc(target, baseDir, true), + "filebase64sha256": funcs.MakeFileBase64Sha256Func(target, baseDir), + "filebase64sha512": funcs.MakeFileBase64Sha512Func(target, baseDir), + "filemd5": funcs.MakeFileMd5Func(target, baseDir), + "filesha1": funcs.MakeFileSha1Func(target, baseDir), + "filesha256": funcs.MakeFileSha256Func(target, baseDir), + "filesha512": funcs.MakeFileSha512Func(target, baseDir), + "flatten": stdlib.FlattenFunc, + "floor": stdlib.FloorFunc, + "format": stdlib.FormatFunc, + "formatdate": stdlib.FormatDateFunc, + "formatlist": stdlib.FormatListFunc, + "indent": stdlib.IndentFunc, + "index": funcs.IndexFunc, // stdlib.IndexFunc is not compatible + "join": stdlib.JoinFunc, + "jsondecode": stdlib.JSONDecodeFunc, + "jsonencode": stdlib.JSONEncodeFunc, + "keys": stdlib.KeysFunc, + "length": funcs.LengthFunc, + "list": funcs.ListFunc, + "log": stdlib.LogFunc, + "lookup": funcs.LookupFunc, + "lower": stdlib.LowerFunc, + "map": funcs.MapFunc, + "matchkeys": funcs.MatchkeysFunc, + "max": stdlib.MaxFunc, + "md5": funcs.Md5Func, + "merge": stdlib.MergeFunc, + "min": stdlib.MinFunc, + "one": funcs.OneFunc, + "parseint": stdlib.ParseIntFunc, + "pathexpand": funcs.PathExpandFunc, + "pow": stdlib.PowFunc, + "range": stdlib.RangeFunc, + "regex": stdlib.RegexFunc, + "regexall": stdlib.RegexAllFunc, + "replace": funcs.ReplaceFunc, + "reverse": stdlib.ReverseListFunc, + "rsadecrypt": funcs.RsaDecryptFunc, + "sensitive": funcs.SensitiveFunc, + "nonsensitive": funcs.NonsensitiveFunc, + "issensitive": funcs.IssensitiveFunc, + "setintersection": stdlib.SetIntersectionFunc, + "setproduct": stdlib.SetProductFunc, + "setsubtract": stdlib.SetSubtractFunc, + "setunion": stdlib.SetUnionFunc, + "sha1": funcs.Sha1Func, + "sha256": funcs.Sha256Func, + "sha512": funcs.Sha512Func, + "signum": stdlib.SignumFunc, + "slice": stdlib.SliceFunc, + "sort": stdlib.SortFunc, + "split": stdlib.SplitFunc, + "startswith": funcs.StartsWithFunc, + "strcontains": funcs.StrContainsFunc, + "strrev": stdlib.ReverseFunc, + "substr": stdlib.SubstrFunc, + "sum": funcs.SumFunc, + "textdecodebase64": funcs.TextDecodeBase64Func, + "textencodebase64": funcs.TextEncodeBase64Func, + "timestamp": funcs.TimestampFunc, + "timeadd": stdlib.TimeAddFunc, + "timecmp": funcs.TimeCmpFunc, + "title": stdlib.TitleFunc, + "tostring": funcs.MakeToFunc(cty.String), + "tonumber": funcs.MakeToFunc(cty.Number), + "tobool": funcs.MakeToFunc(cty.Bool), + "toset": funcs.MakeToFunc(cty.Set(cty.DynamicPseudoType)), + "tolist": funcs.MakeToFunc(cty.List(cty.DynamicPseudoType)), + "tomap": funcs.MakeToFunc(cty.Map(cty.DynamicPseudoType)), + "transpose": funcs.TransposeFunc, + "trim": stdlib.TrimFunc, + "trimprefix": stdlib.TrimPrefixFunc, + "trimspace": stdlib.TrimSpaceFunc, + "trimsuffix": stdlib.TrimSuffixFunc, + "try": tryfunc.TryFunc, + "upper": stdlib.UpperFunc, + "urlencode": funcs.URLEncodeFunc, + "uuid": funcs.UUIDFunc, + "uuidv5": funcs.UUIDV5Func, + "values": stdlib.ValuesFunc, + "yamldecode": ctyyaml.YAMLDecodeFunc, + "yamlencode": ctyyaml.YAMLEncodeFunc, + "zipmap": stdlib.ZipmapFunc, + } + +} diff --git a/pkg/iac/scanners/terraform/parser/load_module.go b/pkg/iac/scanners/terraform/parser/load_module.go new file mode 100644 index 000000000000..0bd6a6395936 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/load_module.go @@ -0,0 +1,163 @@ +package parser + +import ( + "context" + "fmt" + "io/fs" + "path" + "strings" + + "github.com/zclconf/go-cty/cty" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers" + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +type ModuleDefinition struct { + Name string + Path string + FileSystem fs.FS + Definition *terraform.Block + Parser *Parser + External bool +} + +func (d *ModuleDefinition) inputVars() map[string]cty.Value { + inputs := d.Definition.Values().AsValueMap() + if inputs == nil { + return make(map[string]cty.Value) + } + return inputs +} + +// loadModules reads all module blocks and loads them +func (e *evaluator) loadModules(ctx context.Context) []*ModuleDefinition { + var moduleDefinitions []*ModuleDefinition + + expanded := e.expandBlocks(e.blocks.OfType("module")) + + for _, moduleBlock := range expanded { + if moduleBlock.Label() == "" { + continue + } + moduleDefinition, err := e.loadModule(ctx, moduleBlock) + if err != nil { + e.debug.Log("Failed to load module %q. Maybe try 'terraform init'?", err) + continue + } + + e.debug.Log("Loaded module %q from %q.", moduleDefinition.Name, moduleDefinition.Path) + moduleDefinitions = append(moduleDefinitions, moduleDefinition) + } + + return moduleDefinitions +} + +// takes in a module "x" {} block and loads resources etc. into e.moduleBlocks - additionally returns variables to add to ["module.x.*"] variables +func (e *evaluator) loadModule(ctx context.Context, b *terraform.Block) (*ModuleDefinition, error) { + + metadata := b.GetMetadata() + + if b.Label() == "" { + return nil, fmt.Errorf("module without label at %s", metadata.Range()) + } + + var source string + attrs := b.Attributes() + for _, attr := range attrs { + if attr.Name() == "source" { + sourceVal := attr.Value() + if sourceVal.Type() == cty.String { + source = sourceVal.AsString() + } + } + } + if source == "" { + return nil, fmt.Errorf("could not read module source attribute at %s", metadata.Range().String()) + } + + if def, err := e.loadModuleFromTerraformCache(ctx, b, source); err == nil { + e.debug.Log("found module '%s' in .terraform/modules", source) + return def, nil + } + + // we don't have the module installed via 'terraform init' so we need to grab it... + return e.loadExternalModule(ctx, b, source) +} + +func (e *evaluator) loadModuleFromTerraformCache(ctx context.Context, b *terraform.Block, source string) (*ModuleDefinition, error) { + var modulePath string + if e.moduleMetadata != nil { + // if we have module metadata we can parse all the modules as they'll be cached locally! + name := b.ModuleName() + for _, module := range e.moduleMetadata.Modules { + if module.Key == name { + modulePath = path.Clean(path.Join(e.projectRootPath, module.Dir)) + break + } + } + } + if modulePath == "" { + return nil, fmt.Errorf("failed to load module from .terraform/modules") + } + if strings.HasPrefix(source, ".") { + source = "" + } + + if prefix, relativeDir, ok := strings.Cut(source, "//"); ok && !strings.HasSuffix(prefix, ":") && strings.Count(prefix, "/") == 2 { + if !strings.HasSuffix(modulePath, relativeDir) { + modulePath = fmt.Sprintf("%s/%s", modulePath, relativeDir) + } + } + + e.debug.Log("Module '%s' resolved to path '%s' in filesystem '%s' using modules.json", b.FullName(), modulePath, e.filesystem) + moduleParser := e.parentParser.newModuleParser(e.filesystem, source, modulePath, b.Label(), b) + if err := moduleParser.ParseFS(ctx, modulePath); err != nil { + return nil, err + } + return &ModuleDefinition{ + Name: b.Label(), + Path: modulePath, + Definition: b, + Parser: moduleParser, + FileSystem: e.filesystem, + }, nil +} + +func (e *evaluator) loadExternalModule(ctx context.Context, b *terraform.Block, source string) (*ModuleDefinition, error) { + + e.debug.Log("locating non-initialized module '%s'...", source) + + version := b.GetAttribute("version").AsStringValueOrDefault("", b).Value() + opt := resolvers.Options{ + Source: source, + OriginalSource: source, + Version: version, + OriginalVersion: version, + WorkingDir: e.projectRootPath, + Name: b.FullName(), + ModulePath: e.modulePath, + DebugLogger: e.debug.Extend("resolver"), + AllowDownloads: e.allowDownloads, + SkipCache: e.skipCachedModules, + } + + filesystem, prefix, downloadPath, err := resolveModule(ctx, e.filesystem, opt) + if err != nil { + return nil, err + } + prefix = path.Join(e.parentParser.moduleSource, prefix) + e.debug.Log("Module '%s' resolved to path '%s' in filesystem '%s' with prefix '%s'", b.FullName(), downloadPath, filesystem, prefix) + moduleParser := e.parentParser.newModuleParser(filesystem, prefix, downloadPath, b.Label(), b) + if err := moduleParser.ParseFS(ctx, downloadPath); err != nil { + return nil, err + } + return &ModuleDefinition{ + Name: b.Label(), + Path: downloadPath, + Definition: b, + Parser: moduleParser, + FileSystem: filesystem, + External: true, + }, nil +} diff --git a/pkg/iac/scanners/terraform/parser/load_module_metadata.go b/pkg/iac/scanners/terraform/parser/load_module_metadata.go new file mode 100644 index 000000000000..7b316f8d66e1 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/load_module_metadata.go @@ -0,0 +1,33 @@ +package parser + +import ( + "encoding/json" + "io/fs" + "path/filepath" +) + +type modulesMetadata struct { + Modules []struct { + Key string `json:"Key"` + Source string `json:"Source"` + Version string `json:"Version"` + Dir string `json:"Dir"` + } `json:"Modules"` +} + +func loadModuleMetadata(target fs.FS, fullPath string) (*modulesMetadata, string, error) { + metadataPath := filepath.Join(fullPath, ".terraform/modules/modules.json") // nolint: gocritic + + f, err := target.Open(metadataPath) + if err != nil { + return nil, metadataPath, err + } + defer func() { _ = f.Close() }() + + var metadata modulesMetadata + if err := json.NewDecoder(f).Decode(&metadata); err != nil { + return nil, metadataPath, err + } + + return &metadata, metadataPath, nil +} diff --git a/pkg/iac/scanners/terraform/parser/load_vars.go b/pkg/iac/scanners/terraform/parser/load_vars.go new file mode 100644 index 000000000000..58f67ce93910 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/load_vars.go @@ -0,0 +1,83 @@ +package parser + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + hcljson "github.com/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty/cty" +) + +func loadTFVars(srcFS fs.FS, filenames []string) (map[string]cty.Value, error) { + combinedVars := make(map[string]cty.Value) + + for _, env := range os.Environ() { + split := strings.Split(env, "=") + key := split[0] + if !strings.HasPrefix(key, "TF_VAR_") { + continue + } + key = strings.TrimPrefix(key, "TF_VAR_") + var val string + if len(split) > 1 { + val = split[1] + } + combinedVars[key] = cty.StringVal(val) + } + + for _, filename := range filenames { + vars, err := loadTFVarsFile(srcFS, filename) + if err != nil { + return nil, fmt.Errorf("failed to load tfvars from %s: %w", filename, err) + } + for k, v := range vars { + combinedVars[k] = v + } + } + + return combinedVars, nil +} + +func loadTFVarsFile(srcFS fs.FS, filename string) (map[string]cty.Value, error) { + inputVars := make(map[string]cty.Value) + if filename == "" { + return inputVars, nil + } + + src, err := fs.ReadFile(srcFS, filepath.ToSlash(filename)) + if err != nil { + return nil, err + } + + var attrs hcl.Attributes + if strings.HasSuffix(filename, ".json") { + variableFile, err := hcljson.Parse(src, filename) + if err != nil { + return nil, err + } + attrs, err = variableFile.Body.JustAttributes() + if err != nil { + return nil, err + } + } else { + variableFile, err := hclsyntax.ParseConfig(src, filename, hcl.Pos{Line: 1, Column: 1}) + if err != nil { + return nil, err + } + attrs, err = variableFile.Body.JustAttributes() + if err != nil { + return nil, err + } + } + + for _, attr := range attrs { + inputVars[attr.Name], _ = attr.Expr.Value(&hcl.EvalContext{}) + } + + return inputVars, nil +} diff --git a/pkg/iac/scanners/terraform/parser/load_vars_test.go b/pkg/iac/scanners/terraform/parser/load_vars_test.go new file mode 100644 index 000000000000..866e895f353e --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/load_vars_test.go @@ -0,0 +1,45 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/zclconf/go-cty/cty" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_TFVarsFile(t *testing.T) { + t.Run("tfvars file", func(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "test.tfvars": `instance_type = "t2.large"`, + }) + + vars, err := loadTFVars(fs, []string{"test.tfvars"}) + require.NoError(t, err) + assert.Equal(t, "t2.large", vars["instance_type"].AsString()) + }) + + t.Run("tfvars json file", func(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "test.tfvars.json": `{ + "variable": { + "foo": { + "default": "bar" + }, + "baz": "qux" + }, + "foo2": true, + "foo3": 3 +}`, + }) + + vars, err := loadTFVars(fs, []string{"test.tfvars.json"}) + require.NoError(t, err) + assert.Equal(t, "bar", vars["variable"].GetAttr("foo").GetAttr("default").AsString()) + assert.Equal(t, "qux", vars["variable"].GetAttr("baz").AsString()) + assert.Equal(t, true, vars["foo2"].True()) + assert.Equal(t, true, vars["foo3"].Equals(cty.NumberIntVal(3)).True()) + }) +} diff --git a/pkg/iac/scanners/terraform/parser/module_retrieval.go b/pkg/iac/scanners/terraform/parser/module_retrieval.go new file mode 100644 index 000000000000..cae84359498d --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/module_retrieval.go @@ -0,0 +1,33 @@ +package parser + +import ( + "context" + "fmt" + "io/fs" + + resolvers2 "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers" +) + +type ModuleResolver interface { + Resolve(context.Context, fs.FS, resolvers2.Options) (filesystem fs.FS, prefix string, downloadPath string, applies bool, err error) +} + +var defaultResolvers = []ModuleResolver{ + resolvers2.Cache, + resolvers2.Local, + resolvers2.Remote, + resolvers2.Registry, +} + +func resolveModule(ctx context.Context, current fs.FS, opt resolvers2.Options) (filesystem fs.FS, sourcePrefix, downloadPath string, err error) { + opt.Debug("Resolving module '%s' with source: '%s'...", opt.Name, opt.Source) + for _, resolver := range defaultResolvers { + if filesystem, prefix, path, applies, err := resolver.Resolve(ctx, current, opt); err != nil { + return nil, "", "", err + } else if applies { + opt.Debug("Module path is %s", path) + return filesystem, prefix, path, nil + } + } + return nil, "", "", fmt.Errorf("failed to resolve module '%s' with source: %s", opt.Name, opt.Source) +} diff --git a/pkg/iac/scanners/terraform/parser/modules.go b/pkg/iac/scanners/terraform/parser/modules.go new file mode 100644 index 000000000000..499fc2fb9647 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/modules.go @@ -0,0 +1,78 @@ +package parser + +import ( + "context" + "path" + "sort" + "strings" + + "github.com/samber/lo" + "github.com/zclconf/go-cty/cty" + + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +// FindRootModules takes a list of module paths and identifies the root local modules. +// It builds a graph based on the module dependencies and determines the modules that have no incoming dependencies, +// considering them as root modules. +func (p *Parser) FindRootModules(ctx context.Context, dirs []string) ([]string, error) { + for _, dir := range dirs { + if err := p.ParseFS(ctx, dir); err != nil { + return nil, err + } + } + + blocks, _, err := p.readBlocks(p.files) + if err != nil { + return nil, err + } + + g := buildGraph(blocks, dirs) + rootModules := g.rootModules() + sort.Strings(rootModules) + return rootModules, nil +} + +type modulesGraph map[string][]string + +func buildGraph(blocks terraform.Blocks, paths []string) modulesGraph { + moduleBlocks := blocks.OfType("module") + + graph := lo.SliceToMap(paths, func(p string) (string, []string) { + return p, nil + }) + + for _, block := range moduleBlocks { + sourceVal := block.GetAttribute("source").Value() + if sourceVal.Type() != cty.String { + continue + } + + source := sourceVal.AsString() + if strings.HasPrefix(source, ".") { + filename := block.GetMetadata().Range().GetFilename() + dir := path.Dir(filename) + graph[dir] = append(graph[dir], path.Join(dir, source)) + } + } + + return graph +} + +func (g modulesGraph) rootModules() []string { + incomingEdges := make(map[string]int) + for _, neighbors := range g { + for _, neighbor := range neighbors { + incomingEdges[neighbor]++ + } + } + + var roots []string + for module := range g { + if incomingEdges[module] == 0 { + roots = append(roots, module) + } + } + + return roots +} diff --git a/pkg/iac/scanners/terraform/parser/modules_test.go b/pkg/iac/scanners/terraform/parser/modules_test.go new file mode 100644 index 000000000000..142783160f01 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/modules_test.go @@ -0,0 +1,71 @@ +package parser + +import ( + "context" + "path" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" +) + +func TestFindRootModules(t *testing.T) { + tests := []struct { + name string + files map[string]string + expected []string + }{ + { + name: "multiple root modules", + files: map[string]string{ + "code/main.tf": ` +module "this" { + count = 0 + source = "./modules/s3" +}`, + "code/modules/s3/main.tf": ` +module "this" { + source = "./modules/logging" +} +resource "aws_s3_bucket" "this" { + bucket = "test" +}`, + "code/modules/s3/modules/logging/main.tf": ` +resource "aws_s3_bucket" "this" { + bucket = "test1" +}`, + "code/example/main.tf": ` +module "this" { + source = "../modules/s3" +}`, + }, + expected: []string{"code", "code/example"}, + }, + { + name: "without module block", + files: map[string]string{ + "code/infra1/main.tf": `resource "test" "this" {}`, + "code/infra2/main.tf": `resource "test" "this" {}`, + }, + expected: []string{"code/infra1", "code/infra2"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsys := testutil.CreateFS(t, tt.files) + parser := New(fsys, "", OptionStopOnHCLError(true)) + + modules := lo.Map(maps.Keys(tt.files), func(p string, _ int) string { + return path.Dir(p) + }) + + got, err := parser.FindRootModules(context.TODO(), modules) + require.NoError(t, err) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/terraform/parser/option.go b/pkg/iac/scanners/terraform/parser/option.go new file mode 100644 index 000000000000..887899496f1b --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/option.go @@ -0,0 +1,67 @@ +package parser + +import ( + "io/fs" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +type ConfigurableTerraformParser interface { + options.ConfigurableParser + SetTFVarsPaths(...string) + SetStopOnHCLError(bool) + SetWorkspaceName(string) + SetAllowDownloads(bool) + SetSkipCachedModules(bool) + SetConfigsFS(fsys fs.FS) +} + +type Option func(p ConfigurableTerraformParser) + +func OptionWithTFVarsPaths(paths ...string) options.ParserOption { + return func(p options.ConfigurableParser) { + if tf, ok := p.(ConfigurableTerraformParser); ok { + tf.SetTFVarsPaths(paths...) + } + } +} + +func OptionStopOnHCLError(stop bool) options.ParserOption { + return func(p options.ConfigurableParser) { + if tf, ok := p.(ConfigurableTerraformParser); ok { + tf.SetStopOnHCLError(stop) + } + } +} + +func OptionWithWorkspaceName(workspaceName string) options.ParserOption { + return func(p options.ConfigurableParser) { + if tf, ok := p.(ConfigurableTerraformParser); ok { + tf.SetWorkspaceName(workspaceName) + } + } +} + +func OptionWithDownloads(allowed bool) options.ParserOption { + return func(p options.ConfigurableParser) { + if tf, ok := p.(ConfigurableTerraformParser); ok { + tf.SetAllowDownloads(allowed) + } + } +} + +func OptionWithSkipCachedModules(b bool) options.ParserOption { + return func(p options.ConfigurableParser) { + if tf, ok := p.(ConfigurableTerraformParser); ok { + tf.SetSkipCachedModules(b) + } + } +} + +func OptionWithConfigsFS(fsys fs.FS) options.ParserOption { + return func(s options.ConfigurableParser) { + if p, ok := s.(ConfigurableTerraformParser); ok { + p.SetConfigsFS(fsys) + } + } +} diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go new file mode 100644 index 000000000000..b5b50dc913d7 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -0,0 +1,378 @@ +package parser + +import ( + "context" + "errors" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "sort" + "strings" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclparse" + "github.com/zclconf/go-cty/cty" + + "github.com/aquasecurity/trivy/pkg/extrafs" + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/ignore" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + tfcontext "github.com/aquasecurity/trivy/pkg/iac/terraform/context" +) + +type sourceFile struct { + file *hcl.File + path string +} + +var _ ConfigurableTerraformParser = (*Parser)(nil) + +// Parser is a tool for parsing terraform templates at a given file system location +type Parser struct { + projectRoot string + moduleName string + modulePath string + moduleSource string + moduleFS fs.FS + moduleBlock *terraform.Block + files []sourceFile + tfvarsPaths []string + stopOnHCLError bool + workspaceName string + underlying *hclparse.Parser + children []*Parser + options []options.ParserOption + debug debug.Logger + allowDownloads bool + skipCachedModules bool + fsMap map[string]fs.FS + skipRequired bool + configsFS fs.FS +} + +func (p *Parser) SetDebugWriter(writer io.Writer) { + p.debug = debug.New(writer, "terraform", "parser", "<"+p.moduleName+">") +} + +func (p *Parser) SetTFVarsPaths(s ...string) { + p.tfvarsPaths = s +} + +func (p *Parser) SetStopOnHCLError(b bool) { + p.stopOnHCLError = b +} + +func (p *Parser) SetWorkspaceName(s string) { + p.workspaceName = s +} + +func (p *Parser) SetAllowDownloads(b bool) { + p.allowDownloads = b +} + +func (p *Parser) SetSkipCachedModules(b bool) { + p.skipCachedModules = b +} + +func (p *Parser) SetSkipRequiredCheck(b bool) { + p.skipRequired = b +} + +func (p *Parser) SetConfigsFS(fsys fs.FS) { + p.configsFS = fsys +} + +// New creates a new Parser +func New(moduleFS fs.FS, moduleSource string, opts ...options.ParserOption) *Parser { + p := &Parser{ + workspaceName: "default", + underlying: hclparse.NewParser(), + options: opts, + moduleName: "root", + allowDownloads: true, + moduleFS: moduleFS, + moduleSource: moduleSource, + configsFS: moduleFS, + } + + for _, option := range opts { + option(p) + } + + return p +} + +func (p *Parser) newModuleParser(moduleFS fs.FS, moduleSource, modulePath, moduleName string, moduleBlock *terraform.Block) *Parser { + mp := New(moduleFS, moduleSource) + mp.modulePath = modulePath + mp.moduleBlock = moduleBlock + mp.moduleName = moduleName + mp.projectRoot = p.projectRoot + p.children = append(p.children, mp) + for _, option := range p.options { + option(mp) + } + return mp +} + +func (p *Parser) ParseFile(_ context.Context, fullPath string) error { + + isJSON := strings.HasSuffix(fullPath, ".tf.json") + isHCL := strings.HasSuffix(fullPath, ".tf") + if !isJSON && !isHCL { + return nil + } + + p.debug.Log("Parsing '%s'...", fullPath) + f, err := p.moduleFS.Open(filepath.ToSlash(fullPath)) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + data, err := io.ReadAll(f) + if err != nil { + return err + } + + if dir := path.Dir(fullPath); p.projectRoot == "" { + p.debug.Log("Setting project/module root to '%s'", dir) + p.projectRoot = dir + p.modulePath = dir + } + + var file *hcl.File + var diag hcl.Diagnostics + + if isHCL { + file, diag = p.underlying.ParseHCL(data, fullPath) + } else { + file, diag = p.underlying.ParseJSON(data, fullPath) + } + if diag != nil && diag.HasErrors() { + return diag + } + p.files = append(p.files, sourceFile{ + file: file, + path: fullPath, + }) + + p.debug.Log("Added file %s.", fullPath) + return nil +} + +// ParseFS parses a root module, where it exists at the root of the provided filesystem +func (p *Parser) ParseFS(ctx context.Context, dir string) error { + + dir = path.Clean(dir) + + if p.projectRoot == "" { + p.debug.Log("Setting project/module root to '%s'", dir) + p.projectRoot = dir + p.modulePath = dir + } + + slashed := filepath.ToSlash(dir) + p.debug.Log("Parsing FS from '%s'", slashed) + fileInfos, err := fs.ReadDir(p.moduleFS, slashed) + if err != nil { + return err + } + + var paths []string + for _, info := range fileInfos { + realPath := path.Join(dir, info.Name()) + if info.Type()&os.ModeSymlink != 0 { + extra, ok := p.moduleFS.(extrafs.FS) + if !ok { + // we can't handle symlinks in this fs type for now + p.debug.Log("Cannot resolve symlink '%s' in '%s' for this fs type", info.Name(), dir) + continue + } + realPath, err = extra.ResolveSymlink(info.Name(), dir) + if err != nil { + p.debug.Log("Failed to resolve symlink '%s' in '%s': %s", info.Name(), dir, err) + continue + } + info, err := extra.Stat(realPath) + if err != nil { + p.debug.Log("Failed to stat resolved symlink '%s': %s", realPath, err) + continue + } + if info.IsDir() { + continue + } + p.debug.Log("Resolved symlink '%s' in '%s' to '%s'", info.Name(), dir, realPath) + } else if info.IsDir() { + continue + } + paths = append(paths, realPath) + } + sort.Strings(paths) + for _, path := range paths { + if err := p.ParseFile(ctx, path); err != nil { + if p.stopOnHCLError { + return err + } + p.debug.Log("error parsing '%s': %s", path, err) + continue + } + } + + return nil +} + +var ErrNoFiles = errors.New("no files found") + +func (p *Parser) Load(ctx context.Context) (*evaluator, error) { + p.debug.Log("Evaluating module...") + + if len(p.files) == 0 { + p.debug.Log("No files found, nothing to do.") + return nil, ErrNoFiles + } + + blocks, ignores, err := p.readBlocks(p.files) + if err != nil { + return nil, err + } + p.debug.Log("Read %d block(s) and %d ignore(s) for module '%s' (%d file[s])...", len(blocks), len(ignores), p.moduleName, len(p.files)) + + var inputVars map[string]cty.Value + if p.moduleBlock != nil { + inputVars = p.moduleBlock.Values().AsValueMap() + p.debug.Log("Added %d input variables from module definition.", len(inputVars)) + } else { + inputVars, err = loadTFVars(p.configsFS, p.tfvarsPaths) + if err != nil { + return nil, err + } + p.debug.Log("Added %d variables from tfvars.", len(inputVars)) + } + + modulesMetadata, metadataPath, err := loadModuleMetadata(p.moduleFS, p.projectRoot) + if err != nil { + p.debug.Log("Error loading module metadata: %s.", err) + } else { + p.debug.Log("Loaded module metadata for %d module(s) from '%s'.", len(modulesMetadata.Modules), metadataPath) + } + + workingDir, err := os.Getwd() + if err != nil { + return nil, err + } + p.debug.Log("Working directory for module evaluation is '%s'", workingDir) + return newEvaluator( + p.moduleFS, + p, + p.projectRoot, + p.modulePath, + workingDir, + p.moduleName, + blocks, + inputVars, + modulesMetadata, + p.workspaceName, + ignores, + p.debug.Extend("evaluator"), + p.allowDownloads, + p.skipCachedModules, + ), nil +} + +func (p *Parser) EvaluateAll(ctx context.Context) (terraform.Modules, cty.Value, error) { + + e, err := p.Load(ctx) + if errors.Is(err, ErrNoFiles) { + return nil, cty.NilVal, nil + } + modules, fsMap := e.EvaluateAll(ctx) + p.debug.Log("Finished parsing module '%s'.", p.moduleName) + p.fsMap = fsMap + return modules, e.exportOutputs(), nil +} + +func (p *Parser) GetFilesystemMap() map[string]fs.FS { + if p.fsMap == nil { + return make(map[string]fs.FS) + } + return p.fsMap +} + +func (p *Parser) readBlocks(files []sourceFile) (terraform.Blocks, ignore.Rules, error) { + var blocks terraform.Blocks + var ignores ignore.Rules + moduleCtx := tfcontext.NewContext(&hcl.EvalContext{}, nil) + for _, file := range files { + fileBlocks, err := loadBlocksFromFile(file) + if err != nil { + if p.stopOnHCLError { + return nil, nil, err + } + p.debug.Log("Encountered HCL parse error: %s", err) + continue + } + for _, fileBlock := range fileBlocks { + blocks = append(blocks, terraform.NewBlock(fileBlock, moduleCtx, p.moduleBlock, nil, p.moduleSource, p.moduleFS)) + } + fileIgnores := ignore.Parse( + string(file.file.Bytes), + file.path, + &ignore.StringMatchParser{ + SectionKey: "ws", + }, + ¶mParser{}, + ) + ignores = append(ignores, fileIgnores...) + } + + sortBlocksByHierarchy(blocks) + return blocks, ignores, nil +} + +func loadBlocksFromFile(file sourceFile) (hcl.Blocks, error) { + contents, diagnostics := file.file.Body.Content(terraform.Schema) + if diagnostics != nil && diagnostics.HasErrors() { + return nil, diagnostics + } + if contents == nil { + return nil, nil + } + return contents.Blocks, nil +} + +type paramParser struct { + params map[string]string +} + +func (s *paramParser) Key() string { + return "ignore" +} + +func (s *paramParser) Parse(str string) bool { + s.params = make(map[string]string) + + idx := strings.Index(str, "[") + if idx == -1 { + return false + } + + str = str[idx+1:] + + paramStr := strings.TrimSuffix(str, "]") + for _, pair := range strings.Split(paramStr, ",") { + parts := strings.Split(pair, "=") + if len(parts) != 2 { + continue + } + s.params[parts[0]] = parts[1] + } + return true +} + +func (s *paramParser) Param() any { + return s.params +} diff --git a/pkg/iac/scanners/terraform/parser/parser_integration_test.go b/pkg/iac/scanners/terraform/parser/parser_integration_test.go new file mode 100644 index 000000000000..1ca634dc280f --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/parser_integration_test.go @@ -0,0 +1,51 @@ +package parser + +import ( + "context" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/stretchr/testify/require" +) + +func Test_DefaultRegistry(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` +module "registry" { + source = "terraform-aws-modules/vpc/aws" +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) + if err := parser.ParseFS(context.TODO(), "code"); err != nil { + t.Fatal(err) + } + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + require.Len(t, modules, 2) +} + +func Test_SpecificRegistry(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` +module "registry" { + source = "registry.terraform.io/terraform-aws-modules/vpc/aws" +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) + if err := parser.ParseFS(context.TODO(), "code"); err != nil { + t.Fatal(err) + } + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + require.Len(t, modules, 2) +} diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go new file mode 100644 index 000000000000..a20bb2a84b58 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -0,0 +1,1633 @@ +package parser + +import ( + "context" + "os" + "sort" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" +) + +func Test_BasicParsing(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "test.tf": ` + +locals { + proxy = var.cats_mother +} + +variable "cats_mother" { + default = "boots" +} + +provider "cats" { + +} + +moved { + +} + +import { + to = cats_cat.mittens + id = "mittens" +} + +resource "cats_cat" "mittens" { + name = "mittens" + special = true +} + +resource "cats_kitten" "the-great-destroyer" { + name = "the great destroyer" + parent = cats_cat.mittens.name +} + +data "cats_cat" "the-cats-mother" { + name = local.proxy +} + +check "cats_mittens_is_special" { + data "cats_cat" "mittens" { + name = "mittens" + } + + assert { + condition = data.cats_cat.mittens.special == true + error_message = "${data.cats_cat.mittens.name} must be special" + } +} + +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + + blocks := modules[0].GetBlocks() + + // variable + variables := blocks.OfType("variable") + require.Len(t, variables, 1) + assert.Equal(t, "variable", variables[0].Type()) + require.Len(t, variables[0].Labels(), 1) + assert.Equal(t, "cats_mother", variables[0].TypeLabel()) + defaultVal := variables[0].GetAttribute("default") + require.NotNil(t, defaultVal) + assert.Equal(t, cty.String, defaultVal.Value().Type()) + assert.Equal(t, "boots", defaultVal.Value().AsString()) + + // provider + providerBlocks := blocks.OfType("provider") + require.Len(t, providerBlocks, 1) + assert.Equal(t, "provider", providerBlocks[0].Type()) + require.Len(t, providerBlocks[0].Labels(), 1) + assert.Equal(t, "cats", providerBlocks[0].TypeLabel()) + + // resources + resourceBlocks := blocks.OfType("resource") + + sort.Slice(resourceBlocks, func(i, j int) bool { + return resourceBlocks[i].TypeLabel() < resourceBlocks[j].TypeLabel() + }) + + require.Len(t, resourceBlocks, 2) + require.Len(t, resourceBlocks[0].Labels(), 2) + + assert.Equal(t, "resource", resourceBlocks[0].Type()) + assert.Equal(t, "cats_cat", resourceBlocks[0].TypeLabel()) + assert.Equal(t, "mittens", resourceBlocks[0].NameLabel()) + + assert.Equal(t, "mittens", resourceBlocks[0].GetAttribute("name").Value().AsString()) + assert.True(t, resourceBlocks[0].GetAttribute("special").Value().True()) + + assert.Equal(t, "resource", resourceBlocks[1].Type()) + assert.Equal(t, "cats_kitten", resourceBlocks[1].TypeLabel()) + assert.Equal(t, "the great destroyer", resourceBlocks[1].GetAttribute("name").Value().AsString()) + assert.Equal(t, "mittens", resourceBlocks[1].GetAttribute("parent").Value().AsString()) + + // import + importBlocks := blocks.OfType("import") + + assert.Equal(t, "import", importBlocks[0].Type()) + require.NotNil(t, importBlocks[0].GetAttribute("to")) + assert.Equal(t, "mittens", importBlocks[0].GetAttribute("id").Value().AsString()) + + // data + dataBlocks := blocks.OfType("data") + require.Len(t, dataBlocks, 1) + require.Len(t, dataBlocks[0].Labels(), 2) + + assert.Equal(t, "data", dataBlocks[0].Type()) + assert.Equal(t, "cats_cat", dataBlocks[0].TypeLabel()) + assert.Equal(t, "the-cats-mother", dataBlocks[0].NameLabel()) + + assert.Equal(t, "boots", dataBlocks[0].GetAttribute("name").Value().AsString()) + + // check + checkBlocks := blocks.OfType("check") + require.Len(t, checkBlocks, 1) + require.Len(t, checkBlocks[0].Labels(), 1) + + assert.Equal(t, "check", checkBlocks[0].Type()) + assert.Equal(t, "cats_mittens_is_special", checkBlocks[0].TypeLabel()) + + require.NotNil(t, checkBlocks[0].GetBlock("data")) + require.NotNil(t, checkBlocks[0].GetBlock("assert")) +} + +func Test_Modules(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` +module "my-mod" { + source = "../module" + input = "ok" +} + +output "result" { + value = module.my-mod.mod_result +} +`, + "module/module.tf": ` +variable "input" { + default = "?" +} + +output "mod_result" { + value = var.input +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true), options.ParserWithDebug(os.Stderr)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + + require.Len(t, modules, 2) + rootModule := modules[0] + childModule := modules[1] + + moduleBlocks := rootModule.GetBlocks().OfType("module") + require.Len(t, moduleBlocks, 1) + + assert.Equal(t, "module", moduleBlocks[0].Type()) + assert.Equal(t, "module.my-mod", moduleBlocks[0].FullName()) + inputAttr := moduleBlocks[0].GetAttribute("input") + require.NotNil(t, inputAttr) + require.Equal(t, cty.String, inputAttr.Value().Type()) + assert.Equal(t, "ok", inputAttr.Value().AsString()) + + rootOutputs := rootModule.GetBlocks().OfType("output") + require.Len(t, rootOutputs, 1) + assert.Equal(t, "output.result", rootOutputs[0].FullName()) + valAttr := rootOutputs[0].GetAttribute("value") + require.NotNil(t, valAttr) + require.Equal(t, cty.String, valAttr.Type()) + assert.Equal(t, "ok", valAttr.Value().AsString()) + + childOutputs := childModule.GetBlocks().OfType("output") + require.Len(t, childOutputs, 1) + assert.Equal(t, "module.my-mod.output.mod_result", childOutputs[0].FullName()) + childValAttr := childOutputs[0].GetAttribute("value") + require.NotNil(t, childValAttr) + require.Equal(t, cty.String, childValAttr.Type()) + assert.Equal(t, "ok", childValAttr.Value().AsString()) + +} + +func Test_NestedParentModule(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` +module "my-mod" { + source = "../." + input = "ok" +} + +output "result" { + value = module.my-mod.mod_result +} +`, + "root.tf": ` +variable "input" { + default = "?" +} + +output "mod_result" { + value = var.input +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + require.Len(t, modules, 2) + rootModule := modules[0] + childModule := modules[1] + + moduleBlocks := rootModule.GetBlocks().OfType("module") + require.Len(t, moduleBlocks, 1) + + assert.Equal(t, "module", moduleBlocks[0].Type()) + assert.Equal(t, "module.my-mod", moduleBlocks[0].FullName()) + inputAttr := moduleBlocks[0].GetAttribute("input") + require.NotNil(t, inputAttr) + require.Equal(t, cty.String, inputAttr.Value().Type()) + assert.Equal(t, "ok", inputAttr.Value().AsString()) + + rootOutputs := rootModule.GetBlocks().OfType("output") + require.Len(t, rootOutputs, 1) + assert.Equal(t, "output.result", rootOutputs[0].FullName()) + valAttr := rootOutputs[0].GetAttribute("value") + require.NotNil(t, valAttr) + require.Equal(t, cty.String, valAttr.Type()) + assert.Equal(t, "ok", valAttr.Value().AsString()) + + childOutputs := childModule.GetBlocks().OfType("output") + require.Len(t, childOutputs, 1) + assert.Equal(t, "module.my-mod.output.mod_result", childOutputs[0].FullName()) + childValAttr := childOutputs[0].GetAttribute("value") + require.NotNil(t, childValAttr) + require.Equal(t, cty.String, childValAttr.Type()) + assert.Equal(t, "ok", childValAttr.Value().AsString()) +} + +func Test_UndefinedModuleOutputReference(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` +resource "something" "blah" { + value = module.x.y +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + require.Len(t, modules, 1) + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("something") + require.Len(t, blocks, 1) + block := blocks[0] + + attr := block.GetAttribute("value") + require.NotNil(t, attr) + + assert.Equal(t, false, attr.IsResolvable()) +} + +func Test_UndefinedModuleOutputReferenceInSlice(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` +resource "something" "blah" { + value = ["first", module.x.y, "last"] +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + require.Len(t, modules, 1) + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("something") + require.Len(t, blocks, 1) + block := blocks[0] + + attr := block.GetAttribute("value") + require.NotNil(t, attr) + + assert.Equal(t, true, attr.IsResolvable()) + + values := attr.AsStringValueSliceOrEmpty() + require.Len(t, values, 3) + + assert.Equal(t, "first", values[0].Value()) + assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + + assert.Equal(t, false, values[1].GetMetadata().IsResolvable()) + + assert.Equal(t, "last", values[2].Value()) + assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) +} + +func Test_TemplatedSliceValue(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` + +variable "x" { + default = "hello" +} + +resource "something" "blah" { + value = ["first", "${var.x}-${var.x}", "last"] +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + require.Len(t, modules, 1) + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("something") + require.Len(t, blocks, 1) + block := blocks[0] + + attr := block.GetAttribute("value") + require.NotNil(t, attr) + + assert.Equal(t, true, attr.IsResolvable()) + + values := attr.AsStringValueSliceOrEmpty() + require.Len(t, values, 3) + + assert.Equal(t, "first", values[0].Value()) + assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + + assert.Equal(t, "hello-hello", values[1].Value()) + assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) + + assert.Equal(t, "last", values[2].Value()) + assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) +} + +func Test_SliceOfVars(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` + +variable "x" { + default = "1" +} + +variable "y" { + default = "2" +} + +resource "something" "blah" { + value = [var.x, var.y] +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + require.Len(t, modules, 1) + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("something") + require.Len(t, blocks, 1) + block := blocks[0] + + attr := block.GetAttribute("value") + require.NotNil(t, attr) + + assert.Equal(t, true, attr.IsResolvable()) + + values := attr.AsStringValueSliceOrEmpty() + require.Len(t, values, 2) + + assert.Equal(t, "1", values[0].Value()) + assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + + assert.Equal(t, "2", values[1].Value()) + assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) +} + +func Test_VarSlice(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` + +variable "x" { + default = ["a", "b", "c"] +} + +resource "something" "blah" { + value = var.x +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + require.Len(t, modules, 1) + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("something") + require.Len(t, blocks, 1) + block := blocks[0] + + attr := block.GetAttribute("value") + require.NotNil(t, attr) + + assert.Equal(t, true, attr.IsResolvable()) + + values := attr.AsStringValueSliceOrEmpty() + require.Len(t, values, 3) + + assert.Equal(t, "a", values[0].Value()) + assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + + assert.Equal(t, "b", values[1].Value()) + assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) + + assert.Equal(t, "c", values[2].Value()) + assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) +} + +func Test_LocalSliceNested(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` + +variable "x" { + default = "a" +} + +locals { + y = [var.x, "b", "c"] +} + +resource "something" "blah" { + value = local.y +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + require.Len(t, modules, 1) + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("something") + require.Len(t, blocks, 1) + block := blocks[0] + + attr := block.GetAttribute("value") + require.NotNil(t, attr) + + assert.Equal(t, true, attr.IsResolvable()) + + values := attr.AsStringValueSliceOrEmpty() + require.Len(t, values, 3) + + assert.Equal(t, "a", values[0].Value()) + assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + + assert.Equal(t, "b", values[1].Value()) + assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) + + assert.Equal(t, "c", values[2].Value()) + assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) +} + +func Test_FunctionCall(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` + +variable "x" { + default = ["a", "b"] +} + +resource "something" "blah" { + value = concat(var.x, ["c"]) +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + + require.Len(t, modules, 1) + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("something") + require.Len(t, blocks, 1) + block := blocks[0] + + attr := block.GetAttribute("value") + require.NotNil(t, attr) + + assert.Equal(t, true, attr.IsResolvable()) + + values := attr.AsStringValueSliceOrEmpty() + require.Len(t, values, 3) + + assert.Equal(t, "a", values[0].Value()) + assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + + assert.Equal(t, "b", values[1].Value()) + assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) + + assert.Equal(t, "c", values[2].Value()) + assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) +} + +func Test_NullDefaultValueForVar(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "test.tf": ` +variable "bucket_name" { + type = string + default = null +} + +resource "aws_s3_bucket" "default" { + bucket = var.bucket_name != null ? var.bucket_name : "default" +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + require.Len(t, modules, 1) + + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("aws_s3_bucket") + require.Len(t, blocks, 1) + block := blocks[0] + + attr := block.GetAttribute("bucket") + require.NotNil(t, attr) + assert.Equal(t, "default", attr.Value().AsString()) +} + +func Test_MultipleInstancesOfSameResource(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "test.tf": ` + +resource "aws_kms_key" "key1" { + description = "Key #1" + enable_key_rotation = true +} + +resource "aws_kms_key" "key2" { + description = "Key #2" + enable_key_rotation = true +} + +resource "aws_s3_bucket" "this" { + bucket = "test" + } + + +resource "aws_s3_bucket_server_side_encryption_configuration" "this1" { + bucket = aws_s3_bucket.this.id + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.key1.arn + sse_algorithm = "aws:kms" + } + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "this2" { + bucket = aws_s3_bucket.this.id + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.key2.arn + sse_algorithm = "aws:kms" + } + } +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + assert.Len(t, modules, 1) + + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("aws_s3_bucket_server_side_encryption_configuration") + assert.Len(t, blocks, 2) + + for _, block := range blocks { + attr, parent := block.GetNestedAttribute("rule.apply_server_side_encryption_by_default.kms_master_key_id") + assert.Equal(t, "apply_server_side_encryption_by_default", parent.Type()) + assert.NotNil(t, attr) + assert.NotEmpty(t, attr.Value().AsString()) + } +} + +func Test_IfConfigFsIsNotSet_ThenUseModuleFsForVars(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": ` +variable "bucket_name" { + type = string +} +resource "aws_s3_bucket" "main" { + bucket = var.bucket_name +} +`, + "main.tfvars": `bucket_name = "test_bucket"`, + }) + parser := New(fs, "", + OptionStopOnHCLError(true), + OptionWithTFVarsPaths("main.tfvars"), + ) + + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + assert.Len(t, modules, 1) + + rootModule := modules[0] + blocks := rootModule.GetResourcesByType("aws_s3_bucket") + require.Len(t, blocks, 1) + + block := blocks[0] + + assert.Equal(t, "test_bucket", block.GetAttribute("bucket").AsStringValueOrDefault("", block).Value()) +} + +func Test_ForEachRefToLocals(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": ` +locals { + buckets = toset([ + "foo", + "bar", + ]) +} + +resource "aws_s3_bucket" "this" { + for_each = local.buckets + bucket = each.key +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + assert.Len(t, modules, 1) + + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("aws_s3_bucket") + assert.Len(t, blocks, 2) + + for _, block := range blocks { + attr := block.GetAttribute("bucket") + require.NotNil(t, attr) + assert.Contains(t, []string{"foo", "bar"}, attr.AsStringValueOrDefault("", block).Value()) + } +} + +func Test_ForEachRefToVariableWithDefault(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": ` +variable "buckets" { + type = set(string) + default = ["foo", "bar"] +} + +resource "aws_s3_bucket" "this" { + for_each = var.buckets + bucket = each.key +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + assert.Len(t, modules, 1) + + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("aws_s3_bucket") + assert.Len(t, blocks, 2) + + for _, block := range blocks { + attr := block.GetAttribute("bucket") + require.NotNil(t, attr) + assert.Contains(t, []string{"foo", "bar"}, attr.AsStringValueOrDefault("", block).Value()) + } +} + +func Test_ForEachRefToVariableFromFile(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": ` +variable "policy_rules" { + type = object({ + secure_tags = optional(map(object({ + session_matcher = optional(string) + priority = number + enabled = optional(bool, true) + })), {}) + }) +} + +resource "google_network_security_gateway_security_policy_rule" "secure_tag_rules" { + for_each = var.policy_rules.secure_tags + provider = google-beta + project = "test" + name = each.key + enabled = each.value.enabled + priority = each.value.priority + session_matcher = each.value.session_matcher +} +`, + "main.tfvars": ` +policy_rules = { + secure_tags = { + secure-tag-1 = { + session_matcher = "host() != 'google.com'" + priority = 1001 + } + } +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true), OptionWithTFVarsPaths("main.tfvars")) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + assert.Len(t, modules, 1) + + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("google_network_security_gateway_security_policy_rule") + assert.Len(t, blocks, 1) + + block := blocks[0] + + assert.Equal(t, "secure-tag-1", block.GetAttribute("name").AsStringValueOrDefault("", block).Value()) + assert.Equal(t, true, block.GetAttribute("enabled").AsBoolValueOrDefault(false, block).Value()) + assert.Equal(t, "host() != 'google.com'", block.GetAttribute("session_matcher").AsStringValueOrDefault("", block).Value()) + assert.Equal(t, 1001, block.GetAttribute("priority").AsIntValueOrDefault(0, block).Value()) +} + +func Test_ForEachRefersToMapThatContainsSameStringValues(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": `locals { + buckets = { + bucket1 = "test1" + bucket2 = "test1" + } +} + +resource "aws_s3_bucket" "this" { + for_each = local.buckets + bucket = each.key +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + assert.Len(t, modules, 1) + + bucketBlocks := modules.GetResourcesByType("aws_s3_bucket") + assert.Len(t, bucketBlocks, 2) + + var labels []string + + for _, b := range bucketBlocks { + labels = append(labels, b.Label()) + } + + expectedLabels := []string{ + `aws_s3_bucket.this["bucket1"]`, + `aws_s3_bucket.this["bucket2"]`, + } + assert.Equal(t, expectedLabels, labels) +} + +func TestDataSourceWithCountMetaArgument(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": ` +data "http" "example" { + count = 2 +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + assert.Len(t, modules, 1) + + rootModule := modules[0] + + httpDataSources := rootModule.GetDatasByType("http") + assert.Len(t, httpDataSources, 2) + + var labels []string + for _, b := range httpDataSources { + labels = append(labels, b.Label()) + } + + expectedLabels := []string{ + `http.example[0]`, + `http.example[1]`, + } + assert.Equal(t, expectedLabels, labels) +} + +func TestDataSourceWithForEachMetaArgument(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": ` +locals { + ports = ["80", "8080"] +} +data "http" "example" { + for_each = toset(local.ports) + url = "localhost:${each.key}" +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + assert.Len(t, modules, 1) + + rootModule := modules[0] + + httpDataSources := rootModule.GetDatasByType("http") + assert.Len(t, httpDataSources, 2) +} + +func TestForEach(t *testing.T) { + tests := []struct { + name string + src string + expectedBucketName string + expectedNameLabel string + }{ + { + name: "arg is set and ref to each.key", + src: `locals { + buckets = ["bucket1"] +} + +resource "aws_s3_bucket" "this" { + for_each = toset(local.buckets) + bucket = each.key +}`, + expectedBucketName: "bucket1", + expectedNameLabel: `this["bucket1"]`, + }, + { + name: "arg is set and ref to each.value", + src: `locals { + buckets = ["bucket1"] +} + +resource "aws_s3_bucket" "this" { + for_each = toset(local.buckets) + bucket = each.value +}`, + expectedBucketName: "bucket1", + expectedNameLabel: `this["bucket1"]`, + }, + { + name: "arg is map and ref to each.key", + src: `locals { + buckets = { + bucket1key = "bucket1value" + } +} + +resource "aws_s3_bucket" "this" { + for_each = local.buckets + bucket = each.key +}`, + expectedBucketName: "bucket1key", + expectedNameLabel: `this["bucket1key"]`, + }, + { + name: "arg is map and ref to each.value", + src: `locals { + buckets = { + bucket1key = "bucket1value" + } +} + +resource "aws_s3_bucket" "this" { + for_each = local.buckets + bucket = each.value +}`, + expectedBucketName: "bucket1value", + expectedNameLabel: `this["bucket1key"]`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + modules := parse(t, map[string]string{ + "main.tf": tt.src, + }) + require.Len(t, modules, 1) + + buckets := modules.GetResourcesByType("aws_s3_bucket") + assert.Len(t, buckets, 1) + + bucket := buckets[0] + bucketName := bucket.GetAttribute("bucket").Value().AsString() + assert.Equal(t, tt.expectedBucketName, bucketName) + + assert.Equal(t, tt.expectedNameLabel, bucket.NameLabel()) + }) + } + +} + +func TestForEachCountExpanded(t *testing.T) { + + tests := []struct { + name string + source string + expectedCount int + }{ + { + name: "arg is list of strings", + source: `locals { + buckets = ["bucket1", "bucket2"] +} + +resource "aws_s3_bucket" "this" { + for_each = local.buckets + bucket = each.key +}`, + expectedCount: 2, + }, + { + name: "arg is empty list", + source: `locals { + buckets = [] +} + +resource "aws_s3_bucket" "this" { + for_each = local.buckets + bucket = each.value +}`, + expectedCount: 0, + }, + { + name: "arg is empty set", + source: `locals { + buckets = toset([]) +} + +resource "aws_s3_bucket" "this" { + for_each = local.buckets + bucket = each.key +}`, + expectedCount: 0, + }, + { + name: "argument set with the same values", + source: `locals { + buckets = ["true", "true"] +} + +resource "aws_s3_bucket" "this" { + for_each = toset(local.buckets) + bucket = each.key +}`, + expectedCount: 1, + }, + { + name: "arg is non-valid set", + source: `locals { + buckets = [{ + bucket1key = "bucket1value" + }] +} + +resource "aws_s3_bucket" "this" { + for_each = toset(local.buckets) + bucket = each.value +}`, + expectedCount: 0, + }, + { + name: "arg is set of strings", + source: `locals { + buckets = ["bucket1", "bucket2"] +} + +resource "aws_s3_bucket" "this" { + for_each = toset(local.buckets) + bucket = each.key +}`, + expectedCount: 2, + }, + { + name: "arg is map", + source: `locals { + buckets = { + 1 = {} + 2 = {} + } +} + +resource "aws_s3_bucket" "this" { + for_each = local.buckets + bucket = each.key +}`, + expectedCount: 2, + }, + { + name: "arg is empty map", + source: `locals { + buckets = {} +} +resource "aws_s3_bucket" "this" { + for_each = local.buckets + bucket = each.value +} + `, + expectedCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + modules := parse(t, map[string]string{ + "main.tf": tt.source, + }) + assert.Len(t, modules, 1) + + bucketBlocks := modules.GetResourcesByType("aws_s3_bucket") + assert.Len(t, bucketBlocks, tt.expectedCount) + }) + } +} + +func TestForEachRefToResource(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": ` + locals { + vpcs = { + "test1" = { + cidr_block = "192.168.0.0/28" + } + "test2" = { + cidr_block = "192.168.1.0/28" + } + } +} + +resource "aws_vpc" "example" { + for_each = local.vpcs + cidr_block = each.value.cidr_block +} + +resource "aws_internet_gateway" "example" { + for_each = aws_vpc.example + vpc_id = each.key +} +`, + }) + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + require.Len(t, modules, 1) + + blocks := modules.GetResourcesByType("aws_internet_gateway") + require.Len(t, blocks, 2) + + var vpcIds []string + for _, b := range blocks { + vpcIds = append(vpcIds, b.GetAttribute("vpc_id").Value().AsString()) + } + + expectedVpcIds := []string{"test1", "test2"} + assert.Equal(t, expectedVpcIds, vpcIds) +} + +func TestArnAttributeOfBucketIsCorrect(t *testing.T) { + + t.Run("the bucket doesn't have a name", func(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": `resource "aws_s3_bucket" "this" {}`, + }) + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + require.Len(t, modules, 1) + + blocks := modules.GetResourcesByType("aws_s3_bucket") + assert.Len(t, blocks, 1) + + bucket := blocks[0] + + values := bucket.Values() + arnVal := values.GetAttr("arn") + assert.True(t, arnVal.Type().Equals(cty.String)) + + id := values.GetAttr("id").AsString() + + arn := arnVal.AsString() + assert.Equal(t, "arn:aws:s3:::"+id, arn) + }) + + t.Run("the bucket has a name", func(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": `resource "aws_s3_bucket" "this" { + bucket = "test" +} + +resource "aws_iam_role" "this" { + name = "test_role" + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Sid = "" + Principal = { + Service = "s3.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_iam_role_policy" "this" { + name = "test_policy" + role = aws_iam_role.this.id + policy = data.aws_iam_policy_document.this.json +} + +data "aws_iam_policy_document" "this" { + statement { + effect = "Allow" + actions = [ + "s3:GetObject" + ] + resources = ["${aws_s3_bucket.this.arn}/*"] + } +}`, + }) + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + require.Len(t, modules, 1) + + blocks := modules[0].GetDatasByType("aws_iam_policy_document") + assert.Len(t, blocks, 1) + + policyDoc := blocks[0] + + statement := policyDoc.GetBlock("statement") + resources := statement.GetAttribute("resources").AsStringValueSliceOrEmpty() + + assert.Len(t, resources, 1) + assert.True(t, resources[0].EqualTo("arn:aws:s3:::test/*")) + }) +} + +func TestForEachWithObjectsOfDifferentTypes(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": `module "backups" { + bucket_name = each.key + client = each.value.client + path_writers = each.value.path_writers + + for_each = { + "bucket1" = { + client = "client1" + path_writers = ["writer1"] // tuple with string + }, + "bucket2" = { + client = "client2" + path_writers = [] // empty tuple + } + } +} +`, + }) + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + assert.NoError(t, err) + assert.Len(t, modules, 1) +} + +func TestCountMetaArgument(t *testing.T) { + tests := []struct { + name string + src string + expected int + }{ + { + name: "zero resources", + src: `resource "test" "this" { + count = 0 +}`, + expected: 0, + }, + { + name: "several resources", + src: `resource "test" "this" { + count = 2 +}`, + expected: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsys := testutil.CreateFS(t, map[string]string{ + "main.tf": tt.src, + }) + parser := New(fsys, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + assert.Len(t, modules, 1) + + resources := modules.GetResourcesByType("test") + assert.Len(t, resources, tt.expected) + }) + } +} + +func TestCountMetaArgumentInModule(t *testing.T) { + tests := []struct { + name string + files map[string]string + expectedCountModules int + expectedCountResources int + }{ + { + name: "zero modules", + files: map[string]string{ + "main.tf": `module "this" { + count = 0 + source = "./modules/test" +}`, + "modules/test/main.tf": `resource "test" "this" {}`, + }, + expectedCountModules: 1, + expectedCountResources: 0, + }, + { + name: "several modules", + files: map[string]string{ + "main.tf": `module "this" { + count = 2 + source = "./modules/test" +}`, + "modules/test/main.tf": `resource "test" "this" {}`, + }, + expectedCountModules: 3, + expectedCountResources: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsys := testutil.CreateFS(t, tt.files) + parser := New(fsys, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + + assert.Len(t, modules, tt.expectedCountModules) + + resources := modules.GetResourcesByType("test") + assert.Len(t, resources, tt.expectedCountResources) + }) + } +} + +func TestDynamicBlocks(t *testing.T) { + t.Run("arg is list of int", func(t *testing.T) { + modules := parse(t, map[string]string{ + "main.tf": ` +resource "aws_security_group" "sg-webserver" { + vpc_id = "1111" + dynamic "ingress" { + for_each = [80, 443] + content { + from_port = ingress.value + to_port = ingress.value + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + } +} +`, + }) + require.Len(t, modules, 1) + + secGroups := modules.GetResourcesByType("aws_security_group") + assert.Len(t, secGroups, 1) + ingressBlocks := secGroups[0].GetBlocks("ingress") + assert.Len(t, ingressBlocks, 2) + + var inboundPorts []int + for _, ingress := range ingressBlocks { + fromPort := ingress.GetAttribute("from_port").AsIntValueOrDefault(-1, ingress).Value() + inboundPorts = append(inboundPorts, fromPort) + } + + assert.True(t, compareSets([]int{80, 443}, inboundPorts)) + }) + + t.Run("empty for-each", func(t *testing.T) { + modules := parse(t, map[string]string{ + "main.tf": ` +resource "aws_lambda_function" "analyzer" { + dynamic "vpc_config" { + for_each = [] + content {} + } +} +`, + }) + require.Len(t, modules, 1) + + functions := modules.GetResourcesByType("aws_lambda_function") + assert.Len(t, functions, 1) + vpcConfigs := functions[0].GetBlocks("vpc_config") + assert.Empty(t, vpcConfigs) + }) + + t.Run("arg is list of bool", func(t *testing.T) { + modules := parse(t, map[string]string{ + "main.tf": ` +resource "aws_lambda_function" "analyzer" { + dynamic "vpc_config" { + for_each = [true] + content {} + } +} +`, + }) + require.Len(t, modules, 1) + + functions := modules.GetResourcesByType("aws_lambda_function") + assert.Len(t, functions, 1) + vpcConfigs := functions[0].GetBlocks("vpc_config") + assert.Len(t, vpcConfigs, 1) + }) + + t.Run("arg is list of objects", func(t *testing.T) { + modules := parse(t, map[string]string{ + "main.tf": `locals { + cluster_network_policy = [{ + enabled = true + }] +} + +resource "google_container_cluster" "primary" { + name = "test" + + dynamic "network_policy" { + for_each = local.cluster_network_policy + + content { + enabled = network_policy.value.enabled + } + } +}`, + }) + require.Len(t, modules, 1) + + clusters := modules.GetResourcesByType("google_container_cluster") + assert.Len(t, clusters, 1) + + networkPolicies := clusters[0].GetBlocks("network_policy") + assert.Len(t, networkPolicies, 1) + + enabled := networkPolicies[0].GetAttribute("enabled") + assert.True(t, enabled.Value().True()) + }) + + t.Run("nested dynamic", func(t *testing.T) { + modules := parse(t, map[string]string{ + "main.tf": ` +resource "test_block" "this" { + name = "name" + location = "loc" + dynamic "env" { + for_each = ["1", "2"] + content { + dynamic "value_source" { + for_each = [true, true] + content {} + } + } + } +}`, + }) + require.Len(t, modules, 1) + + testResources := modules.GetResourcesByType("test_block") + assert.Len(t, testResources, 1) + envs := testResources[0].GetBlocks("env") + assert.Len(t, envs, 2) + + var sources []*terraform.Block + for _, env := range envs { + sources = append(sources, env.GetBlocks("value_source")...) + } + assert.Len(t, sources, 4) + }) +} + +func parse(t *testing.T, files map[string]string) terraform.Modules { + fs := testutil.CreateFS(t, files) + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + + return modules +} + +func compareSets(a []int, b []int) bool { + m := make(map[int]bool) + for _, el := range a { + m[el] = true + } + + for _, el := range b { + if !m[el] { + return false + } + } + + return true +} + +func TestModuleRefersToOutputOfAnotherModule(t *testing.T) { + files := map[string]string{ + "main.tf": ` +module "module2" { + source = "./modules/foo" +} + +module "module1" { + source = "./modules/bar" + test_var = module.module2.test_out +} +`, + "modules/foo/main.tf": ` +output "test_out" { + value = "test_value" +} +`, + "modules/bar/main.tf": ` +variable "test_var" {} + +resource "test_resource" "this" { + dynamic "dynamic_block" { + for_each = [var.test_var] + content { + some_attr = dynamic_block.value + } + } +} +`, + } + + modules := parse(t, files) + require.Len(t, modules, 3) + + resources := modules.GetResourcesByType("test_resource") + require.Len(t, resources, 1) + + attr, _ := resources[0].GetNestedAttribute("dynamic_block.some_attr") + require.NotNil(t, attr) + + assert.Equal(t, "test_value", attr.GetRawValue()) +} + +func TestCyclicModules(t *testing.T) { + files := map[string]string{ + "main.tf": ` +module "module2" { + source = "./modules/foo" + test_var = passthru.handover.from_1 +} + +// Demonstrates need for evaluateSteps between submodule evaluations. +resource "passthru" "handover" { + from_1 = module.module1.test_out + from_2 = module.module2.test_out +} + +module "module1" { + source = "./modules/bar" + test_var = passthru.handover.from_2 +} +`, + "modules/foo/main.tf": ` +variable "test_var" {} + +resource "test_resource" "this" { + dynamic "dynamic_block" { + for_each = [var.test_var] + content { + some_attr = dynamic_block.value + } + } +} + +output "test_out" { + value = "test_value" +} +`, + "modules/bar/main.tf": ` +variable "test_var" {} + +resource "test_resource" "this" { + dynamic "dynamic_block" { + for_each = [var.test_var] + content { + some_attr = dynamic_block.value + } + } +} + +output "test_out" { + value = test_resource.this.dynamic_block.some_attr +} +`, + } + + modules := parse(t, files) + require.Len(t, modules, 3) + + resources := modules.GetResourcesByType("test_resource") + require.Len(t, resources, 2) + + for _, res := range resources { + attr, _ := res.GetNestedAttribute("dynamic_block.some_attr") + require.NotNil(t, attr, res.FullName()) + assert.Equal(t, "test_value", attr.GetRawValue()) + } +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/cache.go b/pkg/iac/scanners/terraform/parser/resolvers/cache.go new file mode 100644 index 000000000000..6efc15f72dbb --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/cache.go @@ -0,0 +1,62 @@ +package resolvers + +import ( + "context" + "crypto/md5" // nolint + "fmt" + "io/fs" + "os" + "path/filepath" +) + +type cacheResolver struct{} + +var Cache = &cacheResolver{} + +const tempDirName = ".aqua" + +func locateCacheFS() (fs.FS, error) { + dir, err := locateCacheDir() + if err != nil { + return nil, err + } + return os.DirFS(dir), nil +} + +func locateCacheDir() (string, error) { + cacheDir := filepath.Join(os.TempDir(), tempDirName, "cache") + if err := os.MkdirAll(cacheDir, 0o750); err != nil { + return "", err + } + if !isWritable(cacheDir) { + return "", fmt.Errorf("cache directory is not writable") + } + return cacheDir, nil +} + +func (r *cacheResolver) Resolve(_ context.Context, _ fs.FS, opt Options) (filesystem fs.FS, prefix, downloadPath string, applies bool, err error) { + if opt.SkipCache { + opt.Debug("Cache is disabled.") + return nil, "", "", false, nil + } + cacheFS, err := locateCacheFS() + if err != nil { + opt.Debug("No cache filesystem is available on this machine.") + return nil, "", "", false, nil + } + key := cacheKey(opt.Source, opt.Version, opt.RelativePath) + opt.Debug("Trying to resolve: %s", key) + if info, err := fs.Stat(cacheFS, filepath.ToSlash(key)); err == nil && info.IsDir() { + opt.Debug("Module '%s' resolving via cache...", opt.Name) + cacheDir, err := locateCacheDir() + if err != nil { + return nil, "", "", true, err + } + return os.DirFS(filepath.Join(cacheDir, key)), opt.OriginalSource, ".", true, nil + } + return nil, "", "", false, nil +} + +func cacheKey(source, version, relativePath string) string { + return fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%s:%s:%s", source, version, relativePath)))) // nolint +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/local.go b/pkg/iac/scanners/terraform/parser/resolvers/local.go new file mode 100644 index 000000000000..eb053741c8ab --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/local.go @@ -0,0 +1,27 @@ +package resolvers + +import ( + "context" + "io/fs" + "path" + "path/filepath" +) + +type localResolver struct{} + +var Local = &localResolver{} + +func (r *localResolver) Resolve(_ context.Context, target fs.FS, opt Options) (filesystem fs.FS, prefix, downloadPath string, applies bool, err error) { + if !opt.hasPrefix(".", "..") { + return nil, "", "", false, nil + } + joined := path.Clean(path.Join(opt.ModulePath, opt.Source)) + if _, err := fs.Stat(target, filepath.ToSlash(joined)); err == nil { + opt.Debug("Module '%s' resolved locally to %s", opt.Name, joined) + return target, "", joined, true, nil + } + + clean := path.Clean(opt.Source) + opt.Debug("Module '%s' resolved locally to %s", opt.Name, clean) + return target, "", clean, true, nil +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/options.go b/pkg/iac/scanners/terraform/parser/resolvers/options.go new file mode 100644 index 000000000000..8373a78525cd --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/options.go @@ -0,0 +1,28 @@ +package resolvers + +import ( + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/debug" +) + +type Options struct { + Source, OriginalSource, Version, OriginalVersion, WorkingDir, Name, ModulePath string + DebugLogger debug.Logger + AllowDownloads bool + SkipCache bool + RelativePath string +} + +func (o *Options) hasPrefix(prefixes ...string) bool { + for _, prefix := range prefixes { + if strings.HasPrefix(o.Source, prefix) { + return true + } + } + return false +} + +func (o *Options) Debug(format string, args ...interface{}) { + o.DebugLogger.Log(format, args...) +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry.go b/pkg/iac/scanners/terraform/parser/resolvers/registry.go new file mode 100644 index 000000000000..30ceb1a6bceb --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry.go @@ -0,0 +1,189 @@ +package resolvers + +import ( + "context" + "encoding/json" + "fmt" + "io/fs" + "net/http" + "os" + "sort" + "strings" + "time" + + "golang.org/x/net/idna" + + "github.com/aquasecurity/go-version/pkg/semver" +) + +type registryResolver struct { + client *http.Client +} + +var Registry = ®istryResolver{ + client: &http.Client{ + // give it a maximum 5 seconds to resolve the module + Timeout: time.Second * 5, + }, +} + +type moduleVersions struct { + Modules []struct { + Versions []struct { + Version string `json:"version"` + } `json:"versions"` + } `json:"modules"` +} + +const registryHostname = "registry.terraform.io" + +// nolint +func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Options) (filesystem fs.FS, prefix string, downloadPath string, applies bool, err error) { + + if !opt.AllowDownloads { + return + } + + inputVersion := opt.Version + source, relativePath, _ := strings.Cut(opt.Source, "//") + parts := strings.Split(source, "/") + if len(parts) < 3 || len(parts) > 4 { + return + } + + hostname := registryHostname + var token string + if len(parts) == 4 { + hostname = parts[0] + parts = parts[1:] + + token, err = getPrivateRegistryTokenFromEnvVars(hostname) + if err == nil { + opt.Debug("Found a token for the registry at %s", hostname) + } else { + opt.Debug(err.Error()) + } + } + + moduleName := strings.Join(parts, "/") + + if opt.Version != "" { + versionUrl := fmt.Sprintf("https://%s/v1/modules/%s/versions", hostname, moduleName) + opt.Debug("Requesting module versions from registry using '%s'...", versionUrl) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, versionUrl, nil) + if err != nil { + return nil, "", "", true, err + } + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + resp, err := r.client.Do(req) + if err != nil { + return nil, "", "", true, err + } + defer func() { _ = resp.Body.Close() }() + if resp.StatusCode != http.StatusOK { + return nil, "", "", true, fmt.Errorf("unexpected status code for versions endpoint: %d", resp.StatusCode) + } + var availableVersions moduleVersions + if err := json.NewDecoder(resp.Body).Decode(&availableVersions); err != nil { + return nil, "", "", true, err + } + + opt.Version, err = resolveVersion(inputVersion, availableVersions) + if err != nil { + return nil, "", "", true, err + } + opt.Debug("Found version '%s' for constraint '%s'", opt.Version, inputVersion) + } + + var url string + if opt.Version == "" { + url = fmt.Sprintf("https://%s/v1/modules/%s/download", hostname, moduleName) + } else { + url = fmt.Sprintf("https://%s/v1/modules/%s/%s/download", hostname, moduleName, opt.Version) + } + + opt.Debug("Requesting module source from registry using '%s'...", url) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, "", "", true, err + } + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + if opt.Version != "" { + req.Header.Set("X-Terraform-Version", opt.Version) + } + + resp, err := r.client.Do(req) + if err != nil { + return nil, "", "", true, err + } + defer func() { _ = resp.Body.Close() }() + if resp.StatusCode != http.StatusNoContent { + return nil, "", "", true, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + opt.Source = resp.Header.Get("X-Terraform-Get") + opt.Debug("Module '%s' resolved via registry to new source: '%s'", opt.Name, opt.Source) + opt.RelativePath = relativePath + filesystem, prefix, downloadPath, _, err = Remote.Resolve(ctx, target, opt) + if err != nil { + return nil, "", "", true, err + } + + return filesystem, prefix, downloadPath, true, nil +} + +func getPrivateRegistryTokenFromEnvVars(hostname string) (string, error) { + token := "" + asciiHostname, err := idna.ToASCII(hostname) + if err != nil { + return "", fmt.Errorf("could not convert hostname %s to a punycode encoded ASCII string so cannot find token for this registry", hostname) + } + + envVar := fmt.Sprintf("TF_TOKEN_%s", strings.ReplaceAll(asciiHostname, ".", "_")) + token = os.Getenv(envVar) + + // Dashes in the hostname can optionally be converted to double underscores + if token == "" { + envVar = strings.ReplaceAll(envVar, "-", "__") + token = os.Getenv(envVar) + } + + if token == "" { + return "", fmt.Errorf("no token was found for the registry at %s", hostname) + } + return token, nil +} + +func resolveVersion(input string, versions moduleVersions) (string, error) { + if len(versions.Modules) != 1 { + return "", fmt.Errorf("1 module expected, found %d", len(versions.Modules)) + } + if len(versions.Modules[0].Versions) == 0 { + return "", fmt.Errorf("no available versions for module") + } + + constraints, err := semver.NewConstraints(input) + if err != nil { + return "", err + } + var realVersions semver.Collection + for _, rawVersion := range versions.Modules[0].Versions { + realVersion, err := semver.Parse(rawVersion.Version) + if err != nil { + continue + } + realVersions = append(realVersions, realVersion) + } + sort.Sort(sort.Reverse(realVersions)) + for _, realVersion := range realVersions { + if constraints.Check(realVersion) { + return realVersion.String(), nil + } + } + return "", fmt.Errorf("no available versions for module constraint '%s'", input) +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go b/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go new file mode 100644 index 000000000000..a36c19ae4c1e --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go @@ -0,0 +1,56 @@ +package resolvers + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_getPrivateRegistryTokenFromEnvVars_ErrorsWithNoEnvVarSet(t *testing.T) { + token, err := getPrivateRegistryTokenFromEnvVars("registry.example.com") + assert.Equal(t, "", token) + assert.Equal(t, "no token was found for the registry at registry.example.com", err.Error()) +} + +func Test_getPrivateRegistryTokenFromEnvVars_ConvertsSiteNameToEnvVar(t *testing.T) { + tests := []struct { + name string + siteName string + tokenName string + }{ + { + name: "returns string when simple env var set", + siteName: "registry.example.com", + tokenName: "TF_TOKEN_registry_example_com", + }, + { + name: "allows dashes in hostname to be dashes", + siteName: "my-registry.example.com", + tokenName: "TF_TOKEN_my-registry_example_com", + }, + { + name: "allows dashes in hostname to be double underscores", + siteName: "my-registry.example.com", + tokenName: "TF_TOKEN_my__registry_example_com", + }, + { + name: "handles utf8 to punycode correctly", + siteName: "例ãˆã°.com", + tokenName: "TF_TOKEN_xn--r8j3dr99h_com", + }, + { + name: "handles punycode with dash to underscore conversion", + siteName: "café.fr", + tokenName: "TF_TOKEN_xn____caf__dma_fr", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv(tt.tokenName, "abcd") + token, err := getPrivateRegistryTokenFromEnvVars(tt.siteName) + assert.Equal(t, "abcd", token) + assert.Equal(t, nil, err) + }) + } +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/remote.go b/pkg/iac/scanners/terraform/parser/resolvers/remote.go new file mode 100644 index 000000000000..4a6a26798a8a --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/remote.go @@ -0,0 +1,92 @@ +package resolvers + +import ( + "context" + "fmt" + "io/fs" + "os" + "path/filepath" + "sync/atomic" + + "github.com/hashicorp/go-getter" +) + +type remoteResolver struct { + count int32 +} + +var Remote = &remoteResolver{ + count: 0, +} + +func (r *remoteResolver) incrementCount(o Options) { + o.Debug("Incrementing the download counter") + atomic.CompareAndSwapInt32(&r.count, r.count, r.count+1) + o.Debug("Download counter is now %d", r.count) +} + +func (r *remoteResolver) GetDownloadCount() int { + return int(atomic.LoadInt32(&r.count)) +} + +func (r *remoteResolver) Resolve(ctx context.Context, _ fs.FS, opt Options) (filesystem fs.FS, prefix, downloadPath string, applies bool, err error) { + if !opt.hasPrefix("github.com/", "bitbucket.org/", "s3:", "git@", "git:", "hg:", "https:", "gcs:") { + return nil, "", "", false, nil + } + + if !opt.AllowDownloads { + return nil, "", "", false, nil + } + + key := cacheKey(opt.OriginalSource, opt.OriginalVersion, opt.RelativePath) + opt.Debug("Storing with cache key %s", key) + + baseCacheDir, err := locateCacheDir() + if err != nil { + return nil, "", "", true, fmt.Errorf("failed to locate cache directory: %w", err) + } + cacheDir := filepath.Join(baseCacheDir, key) + if err := r.download(ctx, opt, cacheDir); err != nil { + return nil, "", "", true, err + } + + r.incrementCount(opt) + opt.Debug("Successfully downloaded %s from %s", opt.Name, opt.Source) + opt.Debug("Module '%s' resolved via remote download.", opt.Name) + return os.DirFS(cacheDir), opt.Source, filepath.Join(".", opt.RelativePath), true, nil +} + +func (r *remoteResolver) download(ctx context.Context, opt Options, dst string) error { + _ = os.RemoveAll(dst) + if err := os.MkdirAll(filepath.Dir(dst), 0o750); err != nil { + return err + } + + var opts []getter.ClientOption + + // Overwrite the file getter so that a file will be copied + getter.Getters["file"] = &getter.FileGetter{Copy: true} + + opt.Debug("Downloading %s...", opt.Source) + + // Build the client + client := &getter.Client{ + Ctx: ctx, + Src: opt.Source, + Dst: dst, + Pwd: opt.WorkingDir, + Getters: getter.Getters, + Mode: getter.ClientModeAny, + Options: opts, + } + + if err := client.Get(); err != nil { + return fmt.Errorf("failed to download: %w", err) + } + + return nil +} + +func (r *remoteResolver) GetSourcePrefix(source string) string { + return source +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/writable.go b/pkg/iac/scanners/terraform/parser/resolvers/writable.go new file mode 100644 index 000000000000..84f471f779c2 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/writable.go @@ -0,0 +1,36 @@ +//go:build !windows +// +build !windows + +package resolvers + +import ( + "os" + "syscall" +) + +func isWritable(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + + if !info.IsDir() { + return false + } + + // Check if the user bit is enabled in file permission + if info.Mode().Perm()&(1<<(uint(7))) == 0 { + return false + } + + var stat syscall.Stat_t + if err = syscall.Stat(path, &stat); err != nil { + return false + } + + if uint32(os.Geteuid()) != stat.Uid { + return false + } + + return true +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/writable_windows.go b/pkg/iac/scanners/terraform/parser/resolvers/writable_windows.go new file mode 100644 index 000000000000..69cb3c7169b1 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/writable_windows.go @@ -0,0 +1,24 @@ +package resolvers + +import ( + "os" +) + +func isWritable(path string) bool { + + info, err := os.Stat(path) + if err != nil { + return false + } + + if !info.IsDir() { + return false + } + + // Check if the user bit is enabled in file permission + if info.Mode().Perm()&(1<<(uint(7))) == 0 { + return false + } + + return true +} diff --git a/pkg/iac/scanners/terraform/parser/sort.go b/pkg/iac/scanners/terraform/parser/sort.go new file mode 100644 index 000000000000..28fc79b1990c --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/sort.go @@ -0,0 +1,58 @@ +package parser + +import ( + "sort" + + "github.com/aquasecurity/trivy/pkg/iac/terraform" +) + +func sortBlocksByHierarchy(blocks terraform.Blocks) { + c := &counter{ + cache: make(map[string]int), + } + sort.Slice(blocks, func(i, j int) bool { + a := blocks[i] + b := blocks[j] + iDepth, jDepth := c.countBlockRecursion(a, blocks, 0), c.countBlockRecursion(b, blocks, 0) + switch { + case iDepth < jDepth: + return true + case iDepth > jDepth: + return false + default: + return blocks[i].FullName() < blocks[j].FullName() + } + }) +} + +type counter struct { + cache map[string]int +} + +func (c *counter) countBlockRecursion(block *terraform.Block, blocks terraform.Blocks, count int) int { + metadata := block.GetMetadata() + if cached, ok := c.cache[metadata.Reference()]; ok { + return cached + } + var maxCount int + var hasRecursion bool + for _, attrName := range []string{"for_each", "count"} { + if attr := block.GetAttribute(attrName); attr.IsNotNil() { + hasRecursion = true + for _, other := range blocks { + if attr.ReferencesBlock(other) { + depth := c.countBlockRecursion(other, blocks, count) + if depth > maxCount { + maxCount = depth + } + } + } + } + } + if hasRecursion { + maxCount++ + } + result := maxCount + count + c.cache[metadata.Reference()] = result + return result +} diff --git a/pkg/iac/scanners/terraform/parser/testdata/tfvars/terraform.tfvars b/pkg/iac/scanners/terraform/parser/testdata/tfvars/terraform.tfvars new file mode 100644 index 000000000000..23fee69e2bb1 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/testdata/tfvars/terraform.tfvars @@ -0,0 +1 @@ +instance_type = "t2.large" \ No newline at end of file diff --git a/pkg/iac/scanners/terraform/parser/testdata/tfvars/terraform.tfvars.json b/pkg/iac/scanners/terraform/parser/testdata/tfvars/terraform.tfvars.json new file mode 100644 index 000000000000..bde0e75763b1 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/testdata/tfvars/terraform.tfvars.json @@ -0,0 +1,10 @@ +{ + "variable": { + "foo": { + "default": "bar" + }, + "baz": "qux" + }, + "foo2": true, + "foo3": 3 +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraform/performance_test.go b/pkg/iac/scanners/terraform/performance_test.go new file mode 100644 index 000000000000..9015aa25b076 --- /dev/null +++ b/pkg/iac/scanners/terraform/performance_test.go @@ -0,0 +1,58 @@ +package terraform + +import ( + "context" + "fmt" + "io/fs" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" +) + +func BenchmarkCalculate(b *testing.B) { + + f, err := createBadBlocks() + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + p := parser.New(f, "", parser.OptionStopOnHCLError(true)) + if err := p.ParseFS(context.TODO(), "project"); err != nil { + b.Fatal(err) + } + modules, _, err := p.EvaluateAll(context.TODO()) + if err != nil { + b.Fatal(err) + } + executor.New().Execute(modules) + } +} + +func createBadBlocks() (fs.FS, error) { + + files := make(map[string]string) + + files["/project/main.tf"] = ` +module "something" { + source = "../modules/problem" +} +` + + for _, rule := range rules.GetRegistered() { + if rule.GetRule().Terraform == nil { + continue + } + for i, bad := range rule.GetRule().Terraform.BadExamples { + filename := fmt.Sprintf("/modules/problem/%s-%d.tf", rule.GetRule().LongID(), i) + files[filename] = bad + } + } + + f := testutil.CreateFS(&testing.T{}, files) + return f, nil +} diff --git a/pkg/iac/scanners/terraform/scanner.go b/pkg/iac/scanners/terraform/scanner.go new file mode 100644 index 000000000000..f5a3554d002d --- /dev/null +++ b/pkg/iac/scanners/terraform/scanner.go @@ -0,0 +1,325 @@ +package terraform + +import ( + "context" + "fmt" + "io" + "io/fs" + "path" + "path/filepath" + "sort" + "strings" + "sync" + + "github.com/aquasecurity/trivy/pkg/extrafs" + "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var _ scanners.FSScanner = (*Scanner)(nil) +var _ options.ConfigurableScanner = (*Scanner)(nil) +var _ ConfigurableTerraformScanner = (*Scanner)(nil) + +type Scanner struct { // nolint: gocritic + sync.Mutex + options []options.ScannerOption + parserOpt []options.ParserOption + executorOpt []executor.Option + dirs map[string]struct{} + forceAllDirs bool + policyDirs []string + policyReaders []io.Reader + regoScanner *rego.Scanner + execLock sync.RWMutex + debug debug.Logger + frameworks []framework.Framework + spec string + loadEmbeddedLibraries bool + loadEmbeddedPolicies bool +} + +func (s *Scanner) SetSpec(spec string) { + s.spec = spec +} + +func (s *Scanner) SetRegoOnly(regoOnly bool) { + s.executorOpt = append(s.executorOpt, executor.OptionWithRegoOnly(regoOnly)) +} + +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { + s.frameworks = frameworks +} + +func (s *Scanner) SetUseEmbeddedPolicies(b bool) { + s.loadEmbeddedPolicies = b +} + +func (s *Scanner) SetUseEmbeddedLibraries(b bool) { + s.loadEmbeddedLibraries = b +} + +func (s *Scanner) Name() string { + return "Terraform" +} + +func (s *Scanner) SetForceAllDirs(b bool) { + s.forceAllDirs = b +} + +func (s *Scanner) AddParserOptions(opts ...options.ParserOption) { + s.parserOpt = append(s.parserOpt, opts...) +} + +func (s *Scanner) AddExecutorOptions(opts ...executor.Option) { + s.executorOpt = append(s.executorOpt, opts...) +} + +func (s *Scanner) SetPolicyReaders(readers []io.Reader) { + s.policyReaders = readers +} + +func (s *Scanner) SetSkipRequiredCheck(skip bool) { + s.parserOpt = append(s.parserOpt, options.ParserWithSkipRequiredCheck(skip)) +} + +func (s *Scanner) SetDebugWriter(writer io.Writer) { + s.parserOpt = append(s.parserOpt, options.ParserWithDebug(writer)) + s.executorOpt = append(s.executorOpt, executor.OptionWithDebugWriter(writer)) + s.debug = debug.New(writer, "terraform", "scanner") +} + +func (s *Scanner) SetTraceWriter(_ io.Writer) { +} + +func (s *Scanner) SetPerResultTracingEnabled(_ bool) { +} + +func (s *Scanner) SetPolicyDirs(dirs ...string) { + s.policyDirs = dirs +} + +func (s *Scanner) SetDataDirs(_ ...string) {} +func (s *Scanner) SetPolicyNamespaces(_ ...string) {} + +func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} + +func (s *Scanner) SetDataFilesystem(_ fs.FS) { + // handled by rego when option is passed on +} +func (s *Scanner) SetRegoErrorLimit(_ int) {} + +func New(opts ...options.ScannerOption) *Scanner { + s := &Scanner{ + dirs: make(map[string]struct{}), + options: opts, + } + for _, opt := range opts { + opt(s) + } + return s +} + +func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { + s.Lock() + defer s.Unlock() + if s.regoScanner != nil { + return s.regoScanner, nil + } + regoScanner := rego.NewScanner(types.SourceCloud, s.options...) + regoScanner.SetParentDebugLogger(s.debug) + + if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + return nil, err + } + s.regoScanner = regoScanner + return regoScanner, nil +} + +// terraformRootModule represents the module to be used as the root module for Terraform deployment. +type terraformRootModule struct { + rootPath string + childs terraform.Modules + fsMap map[string]fs.FS +} + +func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, dir string) (scan.Results, error) { + + s.debug.Log("Scanning [%s] at '%s'...", target, dir) + + // find directories which directly contain tf files + modulePaths := s.findModules(target, dir, dir) + sort.Strings(modulePaths) + + if len(modulePaths) == 0 { + s.debug.Log("no modules found") + return nil, nil + } + + regoScanner, err := s.initRegoScanner(target) + if err != nil { + return nil, err + } + + s.execLock.Lock() + s.executorOpt = append(s.executorOpt, executor.OptionWithRegoScanner(regoScanner), executor.OptionWithFrameworks(s.frameworks...)) + s.execLock.Unlock() + + var allResults scan.Results + + p := parser.New(target, "", s.parserOpt...) + rootDirs, err := p.FindRootModules(ctx, modulePaths) + if err != nil { + return nil, fmt.Errorf("failed to find root modules: %w", err) + } + + rootModules := make([]terraformRootModule, 0, len(rootDirs)) + + // parse all root module directories + for _, dir := range rootDirs { + + s.debug.Log("Scanning root module '%s'...", dir) + + p := parser.New(target, "", s.parserOpt...) + + if err := p.ParseFS(ctx, dir); err != nil { + return nil, err + } + + modules, _, err := p.EvaluateAll(ctx) + if err != nil { + return nil, err + } + + rootModules = append(rootModules, terraformRootModule{ + rootPath: dir, + childs: modules, + fsMap: p.GetFilesystemMap(), + }) + } + + for _, module := range rootModules { + s.execLock.RLock() + e := executor.New(s.executorOpt...) + s.execLock.RUnlock() + results, err := e.Execute(module.childs) + if err != nil { + return nil, err + } + + for i, result := range results { + if result.Metadata().Range().GetFS() != nil { + continue + } + key := result.Metadata().Range().GetFSKey() + if key == "" { + continue + } + if filesystem, ok := module.fsMap[key]; ok { + override := scan.Results{ + result, + } + override.SetSourceAndFilesystem(result.Range().GetSourcePrefix(), filesystem, false) + results[i] = override[0] + } + } + + allResults = append(allResults, results...) + } + + return allResults, nil +} + +func (s *Scanner) removeNestedDirs(dirs []string) []string { + if s.forceAllDirs { + return dirs + } + var clean []string + for _, dirA := range dirs { + dirOK := true + for _, dirB := range dirs { + if dirA == dirB { + continue + } + if str, err := filepath.Rel(dirB, dirA); err == nil && !strings.HasPrefix(str, "..") { + dirOK = false + break + } + } + if dirOK { + clean = append(clean, dirA) + } + } + return clean +} + +func (s *Scanner) findModules(target fs.FS, scanDir string, dirs ...string) []string { + + var roots []string + var others []string + + for _, dir := range dirs { + if s.isRootModule(target, dir) { + roots = append(roots, dir) + if !s.forceAllDirs { + continue + } + } + + // if this isn't a root module, look at directories inside it + files, err := fs.ReadDir(target, filepath.ToSlash(dir)) + if err != nil { + continue + } + for _, file := range files { + realPath := path.Join(dir, file.Name()) + if symFS, ok := target.(extrafs.ReadLinkFS); ok { + realPath, err = symFS.ResolveSymlink(realPath, scanDir) + if err != nil { + s.debug.Log("failed to resolve symlink '%s': %s", file.Name(), err) + continue + } + } + if file.IsDir() { + others = append(others, realPath) + } else if statFS, ok := target.(fs.StatFS); ok { + info, err := statFS.Stat(filepath.ToSlash(realPath)) + if err != nil { + continue + } + if info.IsDir() { + others = append(others, realPath) + } + } + } + } + + if (len(roots) == 0 || s.forceAllDirs) && len(others) > 0 { + roots = append(roots, s.findModules(target, scanDir, others...)...) + } + + return s.removeNestedDirs(roots) +} + +func (s *Scanner) isRootModule(target fs.FS, dir string) bool { + files, err := fs.ReadDir(target, filepath.ToSlash(dir)) + if err != nil { + s.debug.Log("failed to read dir '%s' from filesystem [%s]: %s", dir, target, err) + return false + } + for _, file := range files { + if strings.HasSuffix(file.Name(), ".tf") || strings.HasSuffix(file.Name(), ".tf.json") { + return true + } + } + return false +} diff --git a/pkg/iac/scanners/terraform/scanner_integration_test.go b/pkg/iac/scanners/terraform/scanner_integration_test.go new file mode 100644 index 000000000000..47137d3bd6de --- /dev/null +++ b/pkg/iac/scanners/terraform/scanner_integration_test.go @@ -0,0 +1,246 @@ +package terraform + +import ( + "bytes" + "context" + "fmt" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_ScanRemoteModule(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": ` +module "s3_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + + bucket = "my-s3-bucket" +} +`, + "/rules/bucket_name.rego": ` +# METADATA +# schemas: +# - input: schema.input +# custom: +# avd_id: AVD-AWS-0001 +# input: +# selector: +# - type: cloud +# subtypes: +# - service: s3 +# provider: aws +package defsec.test.aws1 +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "" + res := result.new("The name of the bucket must not be empty", bucket) +}`, + }) + + debugLog := bytes.NewBuffer([]byte{}) + + scanner := New( + options.ScannerWithDebug(debugLog), + options.ScannerWithPolicyFilesystem(fs), + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithEmbeddedLibraries(false), + options.ScannerWithRegoOnly(true), + ScannerWithAllDirectories(true), + ScannerWithSkipCachedModules(true), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, ".") + require.NoError(t, err) + + assert.Len(t, results.GetPassed(), 1) + + if t.Failed() { + fmt.Printf("Debug logs:\n%s\n", debugLog.String()) + } +} + +func Test_ScanChildUseRemoteModule(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": ` +module "this" { + source = "./modules/s3" + bucket = "my-s3-bucket" +} +`, + "modules/s3/main.tf": ` +variable "bucket" { + type = string +} + +module "s3_bucket" { + source = "github.com/terraform-aws-modules/terraform-aws-s3-bucket?ref=v3.15.1" + bucket = var.bucket +} +`, + "rules/bucket_name.rego": ` +# METADATA +# schemas: +# - input: schema.input +# custom: +# avd_id: AVD-AWS-0001 +# input: +# selector: +# - type: cloud +# subtypes: +# - service: s3 +# provider: aws +package defsec.test.aws1 +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "" + res := result.new("The name of the bucket must not be empty", bucket) +}`, + }) + + debugLog := bytes.NewBuffer([]byte{}) + + scanner := New( + options.ScannerWithDebug(debugLog), + options.ScannerWithPolicyFilesystem(fs), + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithEmbeddedLibraries(false), + options.ScannerWithRegoOnly(true), + ScannerWithAllDirectories(true), + ScannerWithSkipCachedModules(true), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, ".") + require.NoError(t, err) + + assert.Len(t, results.GetPassed(), 1) + + if t.Failed() { + fmt.Printf("Debug logs:\n%s\n", debugLog.String()) + } +} + +func Test_OptionWithSkipDownloaded(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + + fs := testutil.CreateFS(t, map[string]string{ + "test/main.tf": ` +module "s3-bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "3.14.0" + bucket = "mybucket" + create_bucket = true +} +`, + // creating our own rule for the reliability of the test + "/rules/test.rego": ` +package defsec.abcdefg +__rego_input__ := { + "combine": false, + "selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}], +} +deny[cause] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "mybucket" + cause := bucket.name +}`, + }) + + t.Run("without skip", func(t *testing.T) { + scanner := New( + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithRegoOnly(true), + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithEmbeddedLibraries(true), + ) + results, err := scanner.ScanFS(context.TODO(), fs, "test") + require.NoError(t, err) + + assert.Len(t, results, 1) + assert.Len(t, results.GetFailed(), 1) + }) + + t.Run("with skip", func(t *testing.T) { + scanner := New( + ScannerWithSkipDownloaded(true), + ScannerWithSkipCachedModules(true), + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithRegoOnly(true), + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithEmbeddedLibraries(true), + ) + results, err := scanner.ScanFS(context.TODO(), fs, "test") + require.NoError(t, err) + + assert.Len(t, results, 1) + assert.Len(t, results.GetIgnored(), 1) + }) +} + +func Test_OptionWithSkipDownloadedIAMDocument(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + + fs := testutil.CreateFS(t, map[string]string{ + "test/main.tf": ` +module "karpenter" { + source = "terraform-aws-modules/eks/aws//modules/karpenter" + version = "19.21.0" + cluster_name = "test" + irsa_oidc_provider_arn = "example" +} +`, + // creating our own rule for the reliability of the test + "/rules/test.rego": ` +package defsec.abcdefg +__rego_input__ := { + "combine": false, + "selector": [{"type": "defsec", "subtypes": [{"service": "iam", "provider": "aws"}]}], +} +allows_permission(statements, permission, effect) { + statement := statements[_] + statement.Effect == effect + action = statement.Action[_] + action == permission +} +deny[res] { + policy := input.aws.iam.policies[_] + value = json.unmarshal(policy.document.value) + statements = value.Statement + not allows_permission(statements, "iam:PassRole", "Deny") + allows_permission(statements, "iam:PassRole", "Allow") + res = result.new("IAM policy allows 'iam:PassRole' action", policy.document) +} +`, + }) + + scanner := New( + ScannerWithSkipDownloaded(true), + ScannerWithSkipCachedModules(true), + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithRegoOnly(true), + options.ScannerWithEmbeddedLibraries(true), + options.ScannerWithEmbeddedPolicies(false), + ) + results, err := scanner.ScanFS(context.TODO(), fs, "test") + require.NoError(t, err) + assert.Len(t, results, 1) + + ignored := results.GetIgnored() + assert.Len(t, ignored, 1) + assert.NotNil(t, ignored[0].Metadata().Parent()) +} diff --git a/pkg/iac/scanners/terraform/scanner_test.go b/pkg/iac/scanners/terraform/scanner_test.go new file mode 100644 index 000000000000..047ceb972a2a --- /dev/null +++ b/pkg/iac/scanners/terraform/scanner_test.go @@ -0,0 +1,1192 @@ +package terraform + +import ( + "bytes" + "context" + "fmt" + "strconv" + "testing" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/severity" + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var alwaysFailRule = scan.Rule{ + Provider: providers.AWSProvider, + Service: "service", + ShortCode: "abc", + Severity: severity.High, + CustomChecks: scan.CustomChecks{ + Terraform: &scan.TerraformCustomCheck{ + RequiredTypes: []string{}, + RequiredLabels: []string{}, + Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { + results.Add("oh no", resourceBlock) + return + }, + }, + }, +} + +const emptyBucketRule = ` +# METADATA +# schemas: +# - input: schema.input +# custom: +# avd_id: AVD-AWS-0001 +# input: +# selector: +# - type: cloud +# subtypes: +# - service: s3 +# provider: aws +package defsec.test.aws1 +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "" + res := result.new("The name of the bucket must not be empty", bucket) +} +` + +func scanWithOptions(t *testing.T, code string, opt ...options.ScannerOption) scan.Results { + + fs := testutil.CreateFS(t, map[string]string{ + "project/main.tf": code, + }) + + scanner := New(opt...) + results, err := scanner.ScanFS(context.TODO(), fs, "project") + require.NoError(t, err) + return results +} + +func Test_OptionWithDebugWriter(t *testing.T) { + reg := rules.Register(alwaysFailRule) + defer rules.Deregister(reg) + + buffer := bytes.NewBuffer([]byte{}) + + scannerOpts := []options.ScannerOption{ + options.ScannerWithDebug(buffer), + } + _ = scanWithOptions(t, ` +resource "something" "else" {} +`, scannerOpts...) + require.Greater(t, buffer.Len(), 0) +} + +func Test_OptionWithPolicyDirs(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "/code/main.tf": ` +resource "aws_s3_bucket" "my-bucket" { + bucket = "evil" +} +`, + "/rules/test.rego": ` +package defsec.abcdefg + +__rego_metadata__ := { + "id": "TEST123", + "avd_id": "AVD-TEST-0123", + "title": "Buckets should not be evil", + "short_code": "no-evil-buckets", + "severity": "CRITICAL", + "type": "DefSec Security Check", + "description": "You should not allow buckets to be evil", + "recommended_actions": "Use a good bucket instead", + "url": "https://google.com/search?q=is+my+bucket+evil", +} + +__rego_input__ := { + "combine": false, + "selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}], +} + +deny[cause] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "evil" + cause := bucket.name +} +`, + }) + + debugLog := bytes.NewBuffer([]byte{}) + scanner := New( + options.ScannerWithDebug(debugLog), + options.ScannerWithPolicyFilesystem(fs), + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithRegoOnly(true), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, "code") + require.NoError(t, err) + + require.Len(t, results.GetFailed(), 1) + + failure := results.GetFailed()[0] + + assert.Equal(t, "AVD-TEST-0123", failure.Rule().AVDID) + + actualCode, err := failure.GetCode() + require.NoError(t, err) + for i := range actualCode.Lines { + actualCode.Lines[i].Highlighted = "" + } + assert.Equal(t, []scan.Line{ + { + Number: 2, + Content: "resource \"aws_s3_bucket\" \"my-bucket\" {", + IsCause: false, + FirstCause: false, + LastCause: false, + Annotation: "", + }, + { + Number: 3, + Content: "\tbucket = \"evil\"", + IsCause: true, + FirstCause: true, + LastCause: true, + Annotation: "", + }, + { + Number: 4, + Content: "}", + IsCause: false, + FirstCause: false, + LastCause: false, + Annotation: "", + }, + }, actualCode.Lines) + + if t.Failed() { + fmt.Printf("Debug logs:\n%s\n", debugLog.String()) + } + +} + +func Test_OptionWithPolicyNamespaces(t *testing.T) { + + tests := []struct { + includedNamespaces []string + policyNamespace string + wantFailure bool + }{ + { + includedNamespaces: nil, + policyNamespace: "blah", + wantFailure: false, + }, + { + includedNamespaces: nil, + policyNamespace: "appshield.something", + wantFailure: true, + }, + { + includedNamespaces: nil, + policyNamespace: "defsec.blah", + wantFailure: true, + }, + { + includedNamespaces: []string{"user"}, + policyNamespace: "users", + wantFailure: false, + }, + { + includedNamespaces: []string{"users"}, + policyNamespace: "something.users", + wantFailure: false, + }, + { + includedNamespaces: []string{"users"}, + policyNamespace: "users", + wantFailure: true, + }, + { + includedNamespaces: []string{"users"}, + policyNamespace: "users.my_rule", + wantFailure: true, + }, + { + includedNamespaces: []string{"a", "users", "b"}, + policyNamespace: "users", + wantFailure: true, + }, + { + includedNamespaces: []string{"user"}, + policyNamespace: "defsec", + wantFailure: true, + }, + } + + for i, test := range tests { + + t.Run(strconv.Itoa(i), func(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "/code/main.tf": ` +resource "aws_s3_bucket" "my-bucket" { + bucket = "evil" +} +`, + "/rules/test.rego": fmt.Sprintf(` +# METADATA +# custom: +# input: +# selector: +# - type: cloud +# subtypes: +# - service: s3 +# provider: aws +package %s + +deny[cause] { +bucket := input.aws.s3.buckets[_] +bucket.name.value == "evil" +cause := bucket.name +} + + `, test.policyNamespace), + }) + + scanner := New( + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithPolicyNamespaces(test.includedNamespaces...), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, "code") + require.NoError(t, err) + + var found bool + for _, result := range results.GetFailed() { + if result.RegoNamespace() == test.policyNamespace && result.RegoRule() == "deny" { + found = true + break + } + } + assert.Equal(t, test.wantFailure, found) + + }) + } + +} + +func Test_OptionWithRegoOnly(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "/code/main.tf": ` +resource "aws_s3_bucket" "my-bucket" { + bucket = "evil" +} +`, + "/rules/test.rego": ` +package defsec.abcdefg + +__rego_metadata__ := { + "id": "TEST123", + "avd_id": "AVD-TEST-0123", + "title": "Buckets should not be evil", + "short_code": "no-evil-buckets", + "severity": "CRITICAL", + "type": "DefSec Security Check", + "description": "You should not allow buckets to be evil", + "recommended_actions": "Use a good bucket instead", + "url": "https://google.com/search?q=is+my+bucket+evil", +} + +__rego_input__ := { + "combine": false, + "selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}], +} + +deny[cause] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "evil" + cause := bucket.name +} +`, + }) + + debugLog := bytes.NewBuffer([]byte{}) + scanner := New( + options.ScannerWithDebug(debugLog), + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithRegoOnly(true), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, "code") + require.NoError(t, err) + + require.Len(t, results.GetFailed(), 1) + assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) + + if t.Failed() { + fmt.Printf("Debug logs:\n%s\n", debugLog.String()) + } +} + +func Test_OptionWithRegoOnly_CodeHighlighting(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "/code/main.tf": ` +resource "aws_s3_bucket" "my-bucket" { + bucket = "evil" +} +`, + "/rules/test.rego": ` +package defsec.abcdefg + +__rego_metadata__ := { + "id": "TEST123", + "avd_id": "AVD-TEST-0123", + "title": "Buckets should not be evil", + "short_code": "no-evil-buckets", + "severity": "CRITICAL", + "type": "DefSec Security Check", + "description": "You should not allow buckets to be evil", + "recommended_actions": "Use a good bucket instead", + "url": "https://google.com/search?q=is+my+bucket+evil", +} + +__rego_input__ := { + "combine": false, + "selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}], +} + +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "evil" + res := result.new("oh no", bucket.name) +} +`, + }) + + debugLog := bytes.NewBuffer([]byte{}) + scanner := New( + options.ScannerWithDebug(debugLog), + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithRegoOnly(true), + options.ScannerWithEmbeddedLibraries(true), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, "code") + require.NoError(t, err) + + require.Len(t, results.GetFailed(), 1) + assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) + assert.NotNil(t, results[0].Metadata().Range().GetFS()) + + if t.Failed() { + fmt.Printf("Debug logs:\n%s\n", debugLog.String()) + } +} + +func Test_IAMPolicyRego(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "/code/main.tf": ` +resource "aws_sqs_queue_policy" "bad_example" { + queue_url = aws_sqs_queue.q.id + + policy = < 0 { + if t.Variables()[0].RootName() == "data" { + // we can't resolve data lookups at this time, so make unresolvable + return append(results, iacTypes.StringUnresolvable(a.metadata)) + } + } + subVal, err := t.Value(ctx) + if err != nil { + return append(results, iacTypes.StringUnresolvable(a.metadata)) + } + return a.valueToStrings(subVal) + default: + val, err := t.Value(a.ctx.Inner()) + if err != nil { + return append(results, iacTypes.StringUnresolvable(a.metadata)) + } + results = a.valueToStrings(val) + } + return results +} + +func (a *Attribute) valueToStrings(value cty.Value) (results []iacTypes.StringValue) { + defer func() { + if err := recover(); err != nil { + results = []iacTypes.StringValue{iacTypes.StringUnresolvable(a.metadata)} + } + }() + if value.IsNull() { + return []iacTypes.StringValue{iacTypes.StringUnresolvable(a.metadata)} + } + if !value.IsKnown() { + return []iacTypes.StringValue{iacTypes.StringUnresolvable(a.metadata)} + } + if value.Type().IsListType() || value.Type().IsTupleType() || value.Type().IsSetType() { + for _, val := range value.AsValueSlice() { + results = append(results, a.valueToString(val)) + } + } + return results +} + +func (a *Attribute) valueToString(value cty.Value) (result iacTypes.StringValue) { + defer func() { + if err := recover(); err != nil { + result = iacTypes.StringUnresolvable(a.metadata) + } + }() + + result = iacTypes.StringUnresolvable(a.metadata) + + if value.IsNull() || !value.IsKnown() { + return result + } + + switch value.Type() { + case cty.String: + return iacTypes.String(value.AsString(), a.metadata) + default: + return result + } +} + +func (a *Attribute) listContains(val cty.Value, stringToLookFor string, ignoreCase bool) bool { + if a == nil { + return false + } + + valueSlice := val.AsValueSlice() + for _, value := range valueSlice { + if value.IsNull() || !value.IsKnown() { + // there is nothing we can do with this value + continue + } + stringToTest := value + if value.Type().IsObjectType() || value.Type().IsMapType() { + valueMap := value.AsValueMap() + stringToTest = valueMap["key"] + } + if value.Type().HasDynamicTypes() { + for _, extracted := range a.extractListValues() { + if extracted == stringToLookFor { + return true + } + } + return false + } + if !value.IsKnown() { + continue + } + if ignoreCase && strings.EqualFold(stringToTest.AsString(), stringToLookFor) { + return true + } + if stringToTest.AsString() == stringToLookFor { + return true + } + } + return false +} + +func (a *Attribute) extractListValues() []string { + var values []string + if a.hclAttribute == nil || a.hclAttribute.Expr == nil || a.hclAttribute.Expr.Variables() == nil { + return values + } + for _, v := range a.hclAttribute.Expr.Variables() { + values = append(values, v.RootName()) + } + return values +} + +func (a *Attribute) mapContains(checkValue interface{}, val cty.Value) bool { + if a == nil { + return false + } + valueMap := val.AsValueMap() + switch t := checkValue.(type) { + case map[interface{}]interface{}: + for k, v := range t { + for key, value := range valueMap { + rawValue := getRawValue(value) + if key == k && evaluate(v, rawValue) { + return true + } + } + } + return false + case map[string]interface{}: + for k, v := range t { + for key, value := range valueMap { + rawValue := getRawValue(value) + if key == k && evaluate(v, rawValue) { + return true + } + } + } + return false + default: + for key := range valueMap { + if key == checkValue { + return true + } + } + return false + } +} + +func (a *Attribute) NotContains(checkValue interface{}, equalityOptions ...EqualityOption) bool { + return !a.Contains(checkValue, equalityOptions...) +} + +func (a *Attribute) Contains(checkValue interface{}, equalityOptions ...EqualityOption) bool { + if a == nil { + return false + } + ignoreCase := false + for _, option := range equalityOptions { + if option == IgnoreCase { + ignoreCase = true + } + } + val := a.Value() + if val.IsNull() { + return false + } + + if val.Type().IsObjectType() || val.Type().IsMapType() { + return a.mapContains(checkValue, val) + } + + stringToLookFor := fmt.Sprintf("%v", checkValue) + + if val.Type().IsListType() || val.Type().IsTupleType() { + return a.listContains(val, stringToLookFor, ignoreCase) + } + + if ignoreCase && containsIgnoreCase(val.AsString(), stringToLookFor) { + return true + } + + return strings.Contains(val.AsString(), stringToLookFor) +} + +func (a *Attribute) OnlyContains(checkValue interface{}) bool { + if a == nil { + return false + } + val := a.Value() + if val.IsNull() { + return false + } + + checkSlice, ok := checkValue.([]interface{}) + if !ok { + return false + } + + if val.Type().IsListType() || val.Type().IsTupleType() { + for _, value := range val.AsValueSlice() { + found := false + for _, cVal := range checkSlice { + switch t := cVal.(type) { + case string: + if t == value.AsString() { + found = true + break + } + case bool: + if t == value.True() { + found = true + break + } + case int, int8, int16, int32, int64: + i, _ := value.AsBigFloat().Int64() + if t == i { + found = true + break + } + case float32, float64: + f, _ := value.AsBigFloat().Float64() + if t == f { + found = true + break + } + } + + } + if !found { + return false + } + } + return true + } + + return false +} + +func containsIgnoreCase(left, substring string) bool { + return strings.Contains(strings.ToLower(left), strings.ToLower(substring)) +} + +func (a *Attribute) StartsWith(prefix interface{}) bool { + if a == nil { + return false + } + if a.Value().Type() == cty.String { + return strings.HasPrefix(a.Value().AsString(), fmt.Sprintf("%v", prefix)) + } + return false +} + +func (a *Attribute) EndsWith(suffix interface{}) bool { + if a == nil { + return false + } + if a.Value().Type() == cty.String { + return strings.HasSuffix(a.Value().AsString(), fmt.Sprintf("%v", suffix)) + } + return false +} + +type EqualityOption int + +const ( + IgnoreCase EqualityOption = iota +) + +func (a *Attribute) Equals(checkValue interface{}, equalityOptions ...EqualityOption) bool { + if a == nil { + return false + } + if a.Value().Type() == cty.String { + for _, option := range equalityOptions { + if option == IgnoreCase { + return strings.EqualFold(strings.ToLower(a.Value().AsString()), strings.ToLower(fmt.Sprintf("%v", checkValue))) + } + } + result := strings.EqualFold(a.Value().AsString(), fmt.Sprintf("%v", checkValue)) + return result + } + if a.Value().Type() == cty.Bool { + return a.Value().True() == checkValue + } + if a.Value().Type() == cty.Number { + checkNumber, err := gocty.ToCtyValue(checkValue, cty.Number) + if err != nil { + return false + } + return a.Value().RawEquals(checkNumber) + } + + return false +} + +func (a *Attribute) NotEqual(checkValue interface{}, equalityOptions ...EqualityOption) bool { + return !a.Equals(checkValue, equalityOptions...) +} + +func (a *Attribute) RegexMatches(re regexp.Regexp) bool { + if a == nil { + return false + } + if a.Value().Type() == cty.String { + match := re.MatchString(a.Value().AsString()) + return match + } + return false +} + +func (a *Attribute) IsNotAny(options ...interface{}) bool { + return !a.IsAny(options...) +} + +func (a *Attribute) IsAny(options ...interface{}) bool { + if a == nil { + return false + } + if a.Value().Type() == cty.String { + value := a.Value().AsString() + for _, option := range options { + if option == value { + return true + } + } + } + if a.Value().Type() == cty.Number { + for _, option := range options { + checkValue, err := gocty.ToCtyValue(option, cty.Number) + if err != nil { + return false + } + if a.Value().RawEquals(checkValue) { + return true + } + } + } + return false +} + +func (a *Attribute) IsNone(options ...interface{}) bool { + if a == nil { + return false + } + if a.Value().Type() == cty.String { + for _, option := range options { + if option == a.Value().AsString() { + return false + } + } + } + if a.Value().Type() == cty.Number { + for _, option := range options { + checkValue, err := gocty.ToCtyValue(option, cty.Number) + if err != nil { + return false + } + if a.Value().RawEquals(checkValue) { + return false + } + + } + } + + return true +} + +func (a *Attribute) IsTrue() bool { + if a == nil { + return false + } + switch a.Value().Type() { + case cty.Bool: + return a.Value().True() + case cty.String: + val := a.Value().AsString() + val = strings.Trim(val, "\"") + return strings.EqualFold(val, "true") + case cty.Number: + val := a.Value().AsBigFloat() + f, _ := val.Float64() + return f > 0 + } + return false +} + +func (a *Attribute) IsFalse() bool { + if a == nil { + return false + } + switch a.Value().Type() { + case cty.Bool: + return a.Value().False() + case cty.String: + val := a.Value().AsString() + val = strings.Trim(val, "\"") + return strings.EqualFold(val, "false") + case cty.Number: + val := a.Value().AsBigFloat() + f, _ := val.Float64() + return f == 0 + } + return false +} + +func (a *Attribute) IsEmpty() bool { + if a == nil { + return false + } + if a.Value().Type() == cty.String { + return a.Value().AsString() == "" + } + if a.Type().IsListType() || a.Type().IsTupleType() { + return len(a.Value().AsValueSlice()) == 0 + } + if a.Type().IsMapType() || a.Type().IsObjectType() { + return len(a.Value().AsValueMap()) == 0 + } + if a.Value().Type() == cty.Number { + // a number can't ever be empty + return false + } + if a.Value().IsNull() { + return a.isNullAttributeEmpty() + } + return true +} + +func (a *Attribute) IsNotEmpty() bool { + return !a.IsEmpty() +} + +func (a *Attribute) isNullAttributeEmpty() bool { + if a == nil { + return false + } + switch t := a.hclAttribute.Expr.(type) { + case *hclsyntax.FunctionCallExpr, *hclsyntax.ScopeTraversalExpr, + *hclsyntax.ConditionalExpr, *hclsyntax.LiteralValueExpr: + return false + case *hclsyntax.TemplateExpr: + // walk the parts of the expression to ensure that it has a literal value + for _, p := range t.Parts { + switch pt := p.(type) { + case *hclsyntax.LiteralValueExpr: + if pt != nil && !pt.Val.IsNull() { + return false + } + case *hclsyntax.ScopeTraversalExpr: + return false + } + } + } + return true +} + +func (a *Attribute) MapValue(mapKey string) cty.Value { + if a == nil { + return cty.NilVal + } + if a.Type().IsObjectType() || a.Type().IsMapType() { + attrMap := a.Value().AsValueMap() + for key, value := range attrMap { + if key == mapKey { + return value + } + } + } + return cty.NilVal +} + +func (a *Attribute) AsMapValue() iacTypes.MapValue { + if a.IsNil() || a.IsNotResolvable() || !a.IsMapOrObject() { + return iacTypes.MapValue{} + } + + values := make(map[string]string) + _ = a.Each(func(key, val cty.Value) { + if key.Type() == cty.String && val.Type() == cty.String { + values[key.AsString()] = val.AsString() + } + }) + + return iacTypes.Map(values, a.GetMetadata()) +} + +func (a *Attribute) LessThan(checkValue interface{}) bool { + if a == nil { + return false + } + if a.Value().Type() == cty.Number { + checkNumber, err := gocty.ToCtyValue(checkValue, cty.Number) + if err != nil { + return false + } + + return a.Value().LessThan(checkNumber).True() + } + return false +} + +func (a *Attribute) LessThanOrEqualTo(checkValue interface{}) bool { + if a == nil { + return false + } + if a.Value().Type() == cty.Number { + checkNumber, err := gocty.ToCtyValue(checkValue, cty.Number) + if err != nil { + return false + } + + return a.Value().LessThanOrEqualTo(checkNumber).True() + } + return false +} + +func (a *Attribute) GreaterThan(checkValue interface{}) bool { + if a == nil { + return false + } + if a.Value().Type() == cty.Number { + checkNumber, err := gocty.ToCtyValue(checkValue, cty.Number) + if err != nil { + return false + } + + return a.Value().GreaterThan(checkNumber).True() + } + return false +} + +func (a *Attribute) GreaterThanOrEqualTo(checkValue interface{}) bool { + if a == nil { + return false + } + if a.Value().Type() == cty.Number { + checkNumber, err := gocty.ToCtyValue(checkValue, cty.Number) + if err != nil { + return false + } + + return a.Value().GreaterThanOrEqualTo(checkNumber).True() + } + return false +} + +func (a *Attribute) IsDataBlockReference() bool { + if a == nil { + return false + } + if t, ok := a.hclAttribute.Expr.(*hclsyntax.ScopeTraversalExpr); ok { + split := t.Traversal.SimpleSplit() + return split.Abs.RootName() == "data" + } + return false +} + +func createDotReferenceFromTraversal(parentRef string, traversals ...hcl.Traversal) (*Reference, error) { + var refParts []string + var key cty.Value + for _, x := range traversals { + for _, p := range x { + switch part := p.(type) { + case hcl.TraverseRoot: + refParts = append(refParts, part.Name) + case hcl.TraverseAttr: + refParts = append(refParts, part.Name) + case hcl.TraverseIndex: + key = part.Key + } + } + } + ref, err := newReference(refParts, parentRef) + if err != nil { + return nil, err + } + ref.SetKey(key) + return ref, nil +} + +func (a *Attribute) ReferencesBlock(b *Block) bool { + if a == nil { + return false + } + for _, ref := range a.AllReferences() { + if ref.RefersTo(b.reference) { + return true + } + } + return false +} + +// nolint +func (a *Attribute) AllReferences(blocks ...*Block) []*Reference { + if a == nil { + return nil + } + refs := a.extractReferences() + for _, block := range blocks { + for _, ref := range refs { + if ref.TypeLabel() == "each" && block.HasChild("for_each") { + refs = append(refs, block.GetAttribute("for_each").AllReferences()...) + } + } + } + return refs +} + +// nolint +func (a *Attribute) referencesFromExpression(expression hcl.Expression) []*Reference { + var refs []*Reference + switch t := expression.(type) { + case *hclsyntax.ConditionalExpr: + if ref, err := createDotReferenceFromTraversal(a.module, t.TrueResult.Variables()...); err == nil { + refs = append(refs, ref) + } + if ref, err := createDotReferenceFromTraversal(a.module, t.FalseResult.Variables()...); err == nil { + refs = append(refs, ref) + } + if ref, err := createDotReferenceFromTraversal(a.module, t.Condition.Variables()...); err == nil { + refs = append(refs, ref) + } + case *hclsyntax.ScopeTraversalExpr: + if ref, err := createDotReferenceFromTraversal(a.module, t.Variables()...); err == nil { + refs = append(refs, ref) + } + case *hclsyntax.TemplateWrapExpr: + refs = a.referencesFromExpression(t.Wrapped) + case *hclsyntax.TemplateExpr: + for _, part := range t.Parts { + ref, err := createDotReferenceFromTraversal(a.module, part.Variables()...) + if err != nil { + continue + } + refs = append(refs, ref) + } + case *hclsyntax.TupleConsExpr: + for _, v := range t.Variables() { + if ref, err := createDotReferenceFromTraversal(a.module, v); err == nil { + refs = append(refs, ref) + } + } + case *hclsyntax.RelativeTraversalExpr: + switch s := t.Source.(type) { + case *hclsyntax.IndexExpr: + if collectionRef, err := createDotReferenceFromTraversal(a.module, s.Collection.Variables()...); err == nil { + key, _ := s.Key.Value(a.ctx.Inner()) + collectionRef.SetKey(key) + refs = append(refs, collectionRef) + } + default: + if ref, err := createDotReferenceFromTraversal(a.module, t.Source.Variables()...); err == nil { + refs = append(refs, ref) + } + } + default: + if reflect.TypeOf(expression).String() == "*json.expression" { + if ref, err := createDotReferenceFromTraversal(a.module, expression.Variables()...); err == nil { + refs = append(refs, ref) + } + } + } + return refs +} + +func (a *Attribute) extractReferences() []*Reference { + if a == nil { + return nil + } + return a.referencesFromExpression(a.hclAttribute.Expr) +} + +func (a *Attribute) IsResourceBlockReference(resourceType string) bool { + if a == nil { + return false + } + if t, ok := a.hclAttribute.Expr.(*hclsyntax.ScopeTraversalExpr); ok { + split := t.Traversal.SimpleSplit() + return split.Abs.RootName() == resourceType + } + return false +} + +func (a *Attribute) References(r Reference) bool { + if a == nil { + return false + } + for _, ref := range a.AllReferences() { + if ref.RefersTo(r) { + return true + } + } + return false +} + +func getRawValue(value cty.Value) interface{} { + if value.IsNull() || !value.IsKnown() { + return value + } + + typeName := value.Type().FriendlyName() + + switch typeName { + case "string": + return value.AsString() + case "number": + return value.AsBigFloat() + case "bool": + return value.True() + } + + return value +} + +func (a *Attribute) IsNil() bool { + return a == nil +} + +func (a *Attribute) IsNotNil() bool { + return !a.IsNil() +} + +func (a *Attribute) HasIntersect(checkValues ...interface{}) bool { + if !a.Type().IsListType() && !a.Type().IsTupleType() { + return false + } + + for _, item := range checkValues { + if a.Contains(item) { + return true + } + } + return false + +} + +func (a *Attribute) AsNumber() float64 { + if a.Value().Type() == cty.Number { + v, _ := a.Value().AsBigFloat().Float64() + return v + } + if a.Value().Type() == cty.String { + v, _ := strconv.ParseFloat(a.Value().AsString(), 64) + return v + } + panic("Attribute is not a number") +} diff --git a/pkg/iac/terraform/block.go b/pkg/iac/terraform/block.go new file mode 100644 index 000000000000..6807fddd0f7d --- /dev/null +++ b/pkg/iac/terraform/block.go @@ -0,0 +1,460 @@ +package terraform + +import ( + "fmt" + "io/fs" + "strings" + + "github.com/google/uuid" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/gocty" + + "github.com/aquasecurity/trivy/pkg/iac/terraform/context" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Block struct { + id string + hclBlock *hcl.Block + context *context.Context + moduleBlock *Block + parentBlock *Block + expanded bool + cloneIndex int + childBlocks []*Block + attributes []*Attribute + metadata iacTypes.Metadata + moduleSource string + moduleFS fs.FS + reference Reference +} + +func NewBlock(hclBlock *hcl.Block, ctx *context.Context, moduleBlock *Block, parentBlock *Block, moduleSource string, + moduleFS fs.FS, index ...cty.Value) *Block { + if ctx == nil { + ctx = context.NewContext(&hcl.EvalContext{}, nil) + } + + var r hcl.Range + switch body := hclBlock.Body.(type) { + case *hclsyntax.Body: + r = body.SrcRange + default: + r = hclBlock.DefRange + r.End = hclBlock.Body.MissingItemRange().End + } + moduleName := "root" + if moduleBlock != nil { + moduleName = moduleBlock.FullName() + } + rng := iacTypes.NewRange( + r.Filename, + r.Start.Line, + r.End.Line, + moduleSource, + moduleFS, + ) + + var parts []string + // if there are no labels then use the block type + // this is for the case where "special" keywords like "resource" are used + // as normal block names in top level blocks - see issue tfsec#1528 for an example + if hclBlock.Type != "resource" || len(hclBlock.Labels) == 0 { + parts = append(parts, hclBlock.Type) + } + parts = append(parts, hclBlock.Labels...) + + var parent string + if moduleBlock != nil { + parent = moduleBlock.FullName() + } + ref, _ := newReference(parts, parent) + if len(index) > 0 { + key := index[0] + ref.SetKey(key) + } + + metadata := iacTypes.NewMetadata(rng, ref.String()) + + if parentBlock != nil { + metadata = metadata.WithParent(parentBlock.metadata) + } else if moduleBlock != nil { + metadata = metadata.WithParent(moduleBlock.GetMetadata()) + } + + b := Block{ + id: uuid.New().String(), + context: ctx, + hclBlock: hclBlock, + moduleBlock: moduleBlock, + moduleSource: moduleSource, + moduleFS: moduleFS, + parentBlock: parentBlock, + metadata: metadata, + reference: *ref, + } + + var children Blocks + switch body := hclBlock.Body.(type) { + case *hclsyntax.Body: + for _, b2 := range body.Blocks { + children = append(children, NewBlock(b2.AsHCLBlock(), ctx, moduleBlock, &b, moduleSource, moduleFS)) + } + default: + content, _, diag := hclBlock.Body.PartialContent(Schema) + if diag == nil { + for _, hb := range content.Blocks { + children = append(children, NewBlock(hb, ctx, moduleBlock, &b, moduleSource, moduleFS)) + } + } + } + + b.childBlocks = children + + for _, attr := range b.createAttributes() { + b.attributes = append(b.attributes, NewAttribute(attr, ctx, moduleName, metadata, *ref, moduleSource, moduleFS)) + } + + return &b +} + +func (b *Block) ID() string { + return b.id +} + +func (b *Block) Reference() Reference { + return b.reference +} + +func (b *Block) GetMetadata() iacTypes.Metadata { + return b.metadata +} + +func (b *Block) GetRawValue() interface{} { + return nil +} + +func (b *Block) InjectBlock(block *Block, name string) { + block.hclBlock.Labels = []string{} + block.hclBlock.Type = name + for attrName, attr := range block.Attributes() { + b.context.Root().SetByDot(attr.Value(), fmt.Sprintf("%s.%s.%s", b.reference.String(), name, attrName)) + } + b.childBlocks = append(b.childBlocks, block) +} + +func (b *Block) MarkExpanded() { + b.expanded = true +} + +func (b *Block) IsExpanded() bool { + return b.expanded +} + +func (b *Block) Clone(index cty.Value) *Block { + var childCtx *context.Context + if b.context != nil { + childCtx = b.context.NewChild() + } else { + childCtx = context.NewContext(&hcl.EvalContext{}, nil) + } + + cloneHCL := *b.hclBlock + + clone := NewBlock(&cloneHCL, childCtx, b.moduleBlock, b.parentBlock, b.moduleSource, b.moduleFS, index) + if len(clone.hclBlock.Labels) > 0 { + position := len(clone.hclBlock.Labels) - 1 + labels := make([]string, len(clone.hclBlock.Labels)) + for i := 0; i < len(labels); i++ { + labels[i] = clone.hclBlock.Labels[i] + } + if index.IsKnown() && !index.IsNull() { + switch index.Type() { + case cty.Number: + f, _ := index.AsBigFloat().Float64() + labels[position] = fmt.Sprintf("%s[%d]", clone.hclBlock.Labels[position], int(f)) + case cty.String: + labels[position] = fmt.Sprintf("%s[%q]", clone.hclBlock.Labels[position], index.AsString()) + default: + labels[position] = fmt.Sprintf("%s[%#v]", clone.hclBlock.Labels[position], index) + } + } else { + labels[position] = fmt.Sprintf("%s[%d]", clone.hclBlock.Labels[position], b.cloneIndex) + } + clone.hclBlock.Labels = labels + } + indexVal, _ := gocty.ToCtyValue(index, cty.Number) + clone.context.SetByDot(indexVal, "count.index") + clone.MarkExpanded() + b.cloneIndex++ + return clone +} + +func (b *Block) Context() *context.Context { + return b.context +} + +func (b *Block) OverrideContext(ctx *context.Context) { + b.context = ctx + for _, block := range b.childBlocks { + block.OverrideContext(ctx.NewChild()) + } + for _, attr := range b.attributes { + attr.ctx = ctx + } +} + +func (b *Block) Type() string { + return b.hclBlock.Type +} + +func (b *Block) Labels() []string { + return b.hclBlock.Labels +} + +func (b *Block) GetFirstMatchingBlock(names ...string) *Block { + var returnBlock *Block + for _, name := range names { + childBlock := b.GetBlock(name) + if childBlock.IsNotNil() { + return childBlock + } + } + return returnBlock +} + +func (b *Block) createAttributes() hcl.Attributes { + switch body := b.hclBlock.Body.(type) { + case *hclsyntax.Body: + attributes := make(hcl.Attributes) + for _, a := range body.Attributes { + attributes[a.Name] = a.AsHCLAttribute() + } + return attributes + default: + _, body, diag := b.hclBlock.Body.PartialContent(Schema) + if diag != nil { + return nil + } + attrs, diag := body.JustAttributes() + if diag != nil { + return nil + } + return attrs + } +} + +func (b *Block) GetBlock(name string) *Block { + var returnBlock *Block + if b == nil || b.hclBlock == nil { + return returnBlock + } + for _, child := range b.childBlocks { + if child.Type() == name { + return child + } + } + return returnBlock +} + +func (b *Block) AllBlocks() Blocks { + if b == nil || b.hclBlock == nil { + return nil + } + return b.childBlocks +} + +func (b *Block) GetBlocks(name string) Blocks { + if b == nil || b.hclBlock == nil { + return nil + } + var results []*Block + for _, child := range b.childBlocks { + if child.Type() == name { + results = append(results, child) + } + } + return results +} + +func (b *Block) GetAttributes() []*Attribute { + if b == nil { + return nil + } + return b.attributes +} + +func (b *Block) GetAttribute(name string) *Attribute { + if b == nil || b.hclBlock == nil { + return nil + } + for _, attr := range b.attributes { + if attr.Name() == name { + return attr + } + } + return nil +} + +func (b *Block) GetNestedAttribute(name string) (*Attribute, *Block) { + + parts := strings.Split(name, ".") + blocks := parts[:len(parts)-1] + attrName := parts[len(parts)-1] + + working := b + for _, subBlock := range blocks { + if checkBlock := working.GetBlock(subBlock); checkBlock == nil { + return nil, working + } else { + working = checkBlock + } + } + + if working != nil { + if attr := working.GetAttribute(attrName); attr != nil { + return attr, working + } + } + + return nil, b +} + +func MapNestedAttribute[T any](block *Block, path string, f func(attr *Attribute, parent *Block) T) T { + return f(block.GetNestedAttribute(path)) +} + +// LocalName is the name relative to the current module +func (b *Block) LocalName() string { + return b.reference.String() +} + +func (b *Block) FullName() string { + + if b.moduleBlock != nil { + return fmt.Sprintf( + "%s.%s", + b.moduleBlock.FullName(), + b.LocalName(), + ) + } + + return b.LocalName() +} + +func (b *Block) ModuleName() string { + name := strings.TrimPrefix(b.LocalName(), "module.") + if b.moduleBlock != nil { + module := strings.TrimPrefix(b.moduleBlock.FullName(), "module.") + name = fmt.Sprintf( + "%s.%s", + module, + name, + ) + } + var parts []string + for _, part := range strings.Split(name, ".") { + part = strings.Split(part, "[")[0] + parts = append(parts, part) + } + return strings.Join(parts, ".") +} + +func (b *Block) UniqueName() string { + if b.moduleBlock != nil { + return fmt.Sprintf("%s:%s:%s", b.FullName(), b.metadata.Range().GetFilename(), b.moduleBlock.UniqueName()) + } + return fmt.Sprintf("%s:%s", b.FullName(), b.metadata.Range().GetFilename()) +} + +func (b *Block) TypeLabel() string { + if len(b.Labels()) > 0 { + return b.Labels()[0] + } + return "" +} + +func (b *Block) NameLabel() string { + if len(b.Labels()) > 1 { + return b.Labels()[1] + } + return "" +} + +func (b *Block) HasChild(childElement string) bool { + return b.GetAttribute(childElement).IsNotNil() || b.GetBlock(childElement).IsNotNil() +} + +func (b *Block) MissingChild(childElement string) bool { + if b == nil { + return true + } + + return !b.HasChild(childElement) +} + +func (b *Block) MissingNestedChild(name string) bool { + if b == nil { + return true + } + + parts := strings.Split(name, ".") + blocks := parts[:len(parts)-1] + last := parts[len(parts)-1] + + working := b + for _, subBlock := range blocks { + if checkBlock := working.GetBlock(subBlock); checkBlock == nil { + return true + } else { + working = checkBlock + } + } + return !working.HasChild(last) + +} + +func (b *Block) InModule() bool { + if b == nil { + return false + } + return b.moduleBlock != nil +} + +func (b *Block) Label() string { + return strings.Join(b.hclBlock.Labels, ".") +} + +func (b *Block) IsResourceType(resourceType string) bool { + return b.TypeLabel() == resourceType +} + +func (b *Block) IsEmpty() bool { + return len(b.AllBlocks()) == 0 && len(b.GetAttributes()) == 0 +} + +func (b *Block) Attributes() map[string]*Attribute { + attributes := make(map[string]*Attribute) + for _, attr := range b.GetAttributes() { + attributes[attr.Name()] = attr + } + return attributes +} + +func (b *Block) Values() cty.Value { + values := createPresetValues(b) + for _, attribute := range b.GetAttributes() { + values[attribute.Name()] = attribute.Value() + } + return cty.ObjectVal(postProcessValues(b, values)) +} + +func (b *Block) IsNil() bool { + return b == nil +} + +func (b *Block) IsNotNil() bool { + return !b.IsNil() +} diff --git a/pkg/iac/terraform/blocks.go b/pkg/iac/terraform/blocks.go new file mode 100644 index 000000000000..311e83583d26 --- /dev/null +++ b/pkg/iac/terraform/blocks.go @@ -0,0 +1,22 @@ +package terraform + +type Blocks []*Block + +func (blocks Blocks) OfType(t string) Blocks { + var results []*Block + for _, block := range blocks { + if block.Type() == t { + results = append(results, block) + } + } + return results +} + +func (blocks Blocks) WithID(id string) *Block { + for _, block := range blocks { + if block.ID() == id { + return block + } + } + return nil +} diff --git a/pkg/iac/terraform/context/context.go b/pkg/iac/terraform/context/context.go new file mode 100644 index 000000000000..0f4a58de9ac9 --- /dev/null +++ b/pkg/iac/terraform/context/context.go @@ -0,0 +1,134 @@ +package context + +import ( + "strings" + + "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" +) + +type Context struct { + ctx *hcl.EvalContext + parent *Context +} + +func NewContext(ctx *hcl.EvalContext, parent *Context) *Context { + if ctx.Variables == nil { + ctx.Variables = make(map[string]cty.Value) + } + return &Context{ + ctx: ctx, + parent: parent, + } +} + +func (c *Context) NewChild() *Context { + return NewContext(c.ctx.NewChild(), c) +} + +func (c *Context) Parent() *Context { + return c.parent +} + +func (c *Context) Inner() *hcl.EvalContext { + return c.ctx +} + +func (c *Context) Root() *Context { + root := c + for root.Parent() != nil { + root = root.Parent() + } + return root +} + +func (c *Context) Get(parts ...string) cty.Value { + if len(parts) == 0 { + return cty.NilVal + } + src := c.ctx.Variables + for i, part := range parts { + if i == len(parts)-1 { + return src[part] + } + nextPart := src[part] + if nextPart == cty.NilVal { + return cty.NilVal + } + src = nextPart.AsValueMap() + } + return cty.NilVal +} + +func (c *Context) GetByDot(path string) cty.Value { + return c.Get(strings.Split(path, ".")...) +} + +func (c *Context) SetByDot(val cty.Value, path string) { + c.Set(val, strings.Split(path, ".")...) +} + +func (c *Context) Set(val cty.Value, parts ...string) { + if len(parts) == 0 { + return + } + + v := mergeVars(c.ctx.Variables[parts[0]], parts[1:], val) + c.ctx.Variables[parts[0]] = v +} + +func (c *Context) Replace(val cty.Value, path string) { + parts := strings.Split(path, ".") + if len(parts) == 0 { + return + } + + delete(c.ctx.Variables, parts[0]) + c.Set(val, parts...) +} + +func mergeVars(src cty.Value, parts []string, value cty.Value) cty.Value { + + if len(parts) == 0 { + if isNotEmptyObject(src) && isNotEmptyObject(value) { + return mergeObjects(src, value) + } + return value + } + + data := make(map[string]cty.Value) + if src.Type().IsObjectType() && !src.IsNull() && src.LengthInt() > 0 { + data = src.AsValueMap() + tmp, ok := src.AsValueMap()[parts[0]] + if !ok { + src = cty.ObjectVal(make(map[string]cty.Value)) + } else { + src = tmp + } + } + + data[parts[0]] = mergeVars(src, parts[1:], value) + + return cty.ObjectVal(data) +} + +func mergeObjects(a, b cty.Value) cty.Value { + output := make(map[string]cty.Value) + + for key, val := range a.AsValueMap() { + output[key] = val + } + for key, val := range b.AsValueMap() { + old, exists := output[key] + if exists && isNotEmptyObject(old) && isNotEmptyObject(val) { + output[key] = mergeObjects(old, val) + } else { + output[key] = val + } + } + return cty.ObjectVal(output) +} + +func isNotEmptyObject(val cty.Value) bool { + return !val.IsNull() && val.IsKnown() && val.Type().IsObjectType() && val.LengthInt() > 0 +} diff --git a/pkg/iac/terraform/context/context_test.go b/pkg/iac/terraform/context/context_test.go new file mode 100644 index 000000000000..8185d7b9892d --- /dev/null +++ b/pkg/iac/terraform/context/context_test.go @@ -0,0 +1,238 @@ +package context + +import ( + "testing" + + "github.com/hashicorp/hcl/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/gocty" +) + +func Test_ContextVariables(t *testing.T) { + underlying := &hcl.EvalContext{} + ctx := NewContext(underlying, nil) + + val, err := gocty.ToCtyValue("hello", cty.String) + if err != nil { + t.Fatal(err) + } + + ctx.Set(val, "my", "value") + value := underlying.Variables["my"].AsValueMap()["value"] + assert.Equal(t, "hello", value.AsString()) + +} + +func Test_ContextVariablesPreservation(t *testing.T) { + + underlying := &hcl.EvalContext{} + underlying.Variables = make(map[string]cty.Value) + underlying.Variables["x"], _ = gocty.ToCtyValue("does it work?", cty.String) + str, _ := gocty.ToCtyValue("something", cty.String) + underlying.Variables["my"] = cty.ObjectVal(map[string]cty.Value{ + "other": str, + "obj": cty.ObjectVal(map[string]cty.Value{ + "another": str, + }), + }) + ctx := NewContext(underlying, nil) + + val, err := gocty.ToCtyValue("hello", cty.String) + if err != nil { + t.Fatal(err) + } + + ctx.Set(val, "my", "value") + assert.Equal(t, "hello", underlying.Variables["my"].AsValueMap()["value"].AsString()) + assert.Equal(t, "something", underlying.Variables["my"].AsValueMap()["other"].AsString()) + assert.Equal(t, "something", underlying.Variables["my"].AsValueMap()["obj"].AsValueMap()["another"].AsString()) + assert.Equal(t, "does it work?", underlying.Variables["x"].AsString()) + +} + +func Test_ContextVariablesPreservationByDot(t *testing.T) { + + underlying := &hcl.EvalContext{} + underlying.Variables = make(map[string]cty.Value) + underlying.Variables["x"], _ = gocty.ToCtyValue("does it work?", cty.String) + str, _ := gocty.ToCtyValue("something", cty.String) + underlying.Variables["my"] = cty.ObjectVal(map[string]cty.Value{ + "other": str, + "obj": cty.ObjectVal(map[string]cty.Value{ + "another": str, + }), + }) + ctx := NewContext(underlying, nil) + + val, err := gocty.ToCtyValue("hello", cty.String) + if err != nil { + t.Fatal(err) + } + + ctx.SetByDot(val, "my.something.value") + assert.Equal(t, "hello", underlying.Variables["my"].AsValueMap()["something"].AsValueMap()["value"].AsString()) + assert.Equal(t, "something", underlying.Variables["my"].AsValueMap()["other"].AsString()) + assert.Equal(t, "something", underlying.Variables["my"].AsValueMap()["obj"].AsValueMap()["another"].AsString()) + assert.Equal(t, "does it work?", underlying.Variables["x"].AsString()) +} + +func Test_ContextSetThenImmediateGet(t *testing.T) { + + underlying := &hcl.EvalContext{} + + ctx := NewContext(underlying, nil) + + ctx.Set(cty.ObjectVal(map[string]cty.Value{ + "mod_result": cty.StringVal("ok"), + }), "module", "modulename") + + val := ctx.Get("module", "modulename", "mod_result") + assert.Equal(t, "ok", val.AsString()) +} + +func Test_ContextSetThenImmediateGetWithChild(t *testing.T) { + + underlying := &hcl.EvalContext{} + + ctx := NewContext(underlying, nil) + + childCtx := ctx.NewChild() + + childCtx.Root().Set(cty.ObjectVal(map[string]cty.Value{ + "mod_result": cty.StringVal("ok"), + }), "module", "modulename") + + val := ctx.Get("module", "modulename", "mod_result") + assert.Equal(t, "ok", val.AsString()) +} + +func Test_MergeObjects(t *testing.T) { + + tests := []struct { + name string + oldVal cty.Value + newVal cty.Value + expected cty.Value + }{ + { + name: "happy", + oldVal: cty.ObjectVal(map[string]cty.Value{ + "this": cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("some_id"), + "arn": cty.StringVal("some_arn"), + }), + }), + newVal: cty.ObjectVal(map[string]cty.Value{ + "this": cty.ObjectVal(map[string]cty.Value{ + "arn": cty.StringVal("some_new_arn"), + "bucket": cty.StringVal("test"), + }), + }), + expected: cty.ObjectVal(map[string]cty.Value{ + "this": cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("some_id"), + "arn": cty.StringVal("some_new_arn"), + "bucket": cty.StringVal("test"), + }), + }), + }, + { + name: "old value is empty", + oldVal: cty.EmptyObjectVal, + newVal: cty.ObjectVal(map[string]cty.Value{ + "this": cty.ObjectVal(map[string]cty.Value{ + "bucket": cty.StringVal("test"), + }), + }), + expected: cty.ObjectVal(map[string]cty.Value{ + "this": cty.ObjectVal(map[string]cty.Value{ + "bucket": cty.StringVal("test"), + }), + }), + }, + { + name: "new value is empty", + oldVal: cty.ObjectVal(map[string]cty.Value{ + "this": cty.ObjectVal(map[string]cty.Value{ + "bucket": cty.StringVal("test"), + }), + }), + newVal: cty.EmptyObjectVal, + expected: cty.ObjectVal(map[string]cty.Value{ + "this": cty.ObjectVal(map[string]cty.Value{ + "bucket": cty.StringVal("test"), + }), + }), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, mergeObjects(tt.oldVal, tt.newVal)) + }) + } + +} + +func Test_IsNotEmptyObject(t *testing.T) { + tests := []struct { + name string + val cty.Value + expected bool + }{ + { + name: "happy", + val: cty.ObjectVal(map[string]cty.Value{ + "field": cty.NilVal, + }), + expected: true, + }, + { + name: "empty object", + val: cty.EmptyObjectVal, + expected: false, + }, + { + name: "nil value", + val: cty.NilVal, + expected: false, + }, + { + name: "dynamic value", + val: cty.DynamicVal, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, isNotEmptyObject(tt.val)) + }) + } +} + +func TestReplace(t *testing.T) { + t.Run("replacement of an existing value", func(t *testing.T) { + underlying := &hcl.EvalContext{} + ctx := NewContext(underlying, nil) + ctx.SetByDot(cty.StringVal("some-value"), "my.value") + require.NotEqual(t, cty.NilVal, ctx.GetByDot("my.value")) + ctx.Replace(cty.NumberIntVal(-1), "my.value") + assert.Equal(t, cty.NumberIntVal(-1), ctx.GetByDot("my.value")) + }) + + t.Run("replacement of a non-existing value", func(t *testing.T) { + underlying := &hcl.EvalContext{} + ctx := NewContext(underlying, nil) + ctx.Replace(cty.NumberIntVal(-1), "my.value") + assert.Equal(t, cty.NumberIntVal(-1), ctx.GetByDot("my.value")) + }) + + t.Run("empty path", func(t *testing.T) { + underlying := &hcl.EvalContext{} + ctx := NewContext(underlying, nil) + ctx.Replace(cty.NumberIntVal(-1), "") + }) +} diff --git a/pkg/iac/terraform/module.go b/pkg/iac/terraform/module.go new file mode 100644 index 000000000000..fec6ad7c8d0e --- /dev/null +++ b/pkg/iac/terraform/module.go @@ -0,0 +1,188 @@ +package terraform + +import ( + "fmt" + "strings" + + "github.com/aquasecurity/trivy/pkg/iac/ignore" +) + +type Module struct { + blocks Blocks + blockMap map[string]Blocks + rootPath string + modulePath string + ignores ignore.Rules + parent *Module +} + +func NewModule(rootPath, modulePath string, blocks Blocks, ignores ignore.Rules) *Module { + + blockMap := make(map[string]Blocks) + + for _, b := range blocks { + if b.NameLabel() != "" { + blockMap[b.TypeLabel()] = append(blockMap[b.TypeLabel()], b) + } + } + + return &Module{ + blocks: blocks, + ignores: ignores, + blockMap: blockMap, + rootPath: rootPath, + modulePath: modulePath, + } +} + +func (c *Module) SetParent(parent *Module) { + c.parent = parent +} + +func (c *Module) RootPath() string { + return c.rootPath +} + +func (c *Module) Ignores() ignore.Rules { + return c.ignores +} + +func (c *Module) GetBlocks() Blocks { + return c.blocks +} + +func (h *Module) GetBlocksByTypeLabel(typeLabel string) Blocks { + return h.blockMap[typeLabel] +} + +func (c *Module) getBlocksByType(blockType string, labels ...string) Blocks { + if blockType == "module" { + return c.getModuleBlocks() + } + var results Blocks + for _, label := range labels { + for _, block := range c.blockMap[label] { + if block.Type() == blockType { + results = append(results, block) + } + } + } + return results +} + +func (c *Module) getModuleBlocks() Blocks { + var results Blocks + for _, block := range c.blocks { + if block.Type() == "module" { + results = append(results, block) + } + } + return results +} + +func (c *Module) GetResourcesByType(labels ...string) Blocks { + return c.getBlocksByType("resource", labels...) +} + +func (c *Module) GetResourcesByIDs(ids ...string) Blocks { + var blocks Blocks + + for _, id := range ids { + if block := c.blocks.WithID(id); block != nil { + blocks = append(blocks, block) + } + } + return blocks +} + +func (c *Module) GetDatasByType(label string) Blocks { + return c.getBlocksByType("data", label) +} + +func (c *Module) GetProviderBlocksByProvider(providerName, alias string) Blocks { + var results Blocks + for _, block := range c.blocks { + if block.Type() == "provider" && len(block.Labels()) > 0 && block.TypeLabel() == providerName { + if alias != "" { + if block.HasChild("alias") && block.GetAttribute("alias").Equals(strings.ReplaceAll(alias, fmt.Sprintf("%s.", providerName), "")) { + results = append(results, block) + + } + } else if block.MissingChild("alias") { + results = append(results, block) + } + } + } + return results +} + +func (c *Module) GetReferencedBlock(referringAttr *Attribute, parentBlock *Block) (*Block, error) { + for _, ref := range referringAttr.AllReferences() { + if ref.TypeLabel() == "each" { + if forEachAttr := parentBlock.GetAttribute("for_each"); forEachAttr.IsNotNil() { + if b, err := c.GetReferencedBlock(forEachAttr, parentBlock); err == nil { + return b, nil + } + } + } + for _, block := range c.blocks { + if ref.RefersTo(block.reference) { + return block, nil + } + kref := *ref + kref.SetKey(parentBlock.reference.RawKey()) + if kref.RefersTo(block.reference) { + return block, nil + } + } + } + return nil, fmt.Errorf("no referenced block found in '%s'", referringAttr.Name()) +} + +func (c *Module) GetBlockByID(id string) (*Block, error) { + found := c.blocks.WithID(id) + if found == nil { + return nil, fmt.Errorf("no block found with id '%s'", id) + } + return found, nil +} + +func (c *Module) GetReferencingResources(originalBlock *Block, referencingLabel, referencingAttributeName string) Blocks { + return c.GetReferencingBlocks(originalBlock, "resource", referencingLabel, referencingAttributeName) +} + +func (c *Module) GetsModulesBySource(moduleSource string) (Blocks, error) { + var results Blocks + + modules := c.getModuleBlocks() + for _, module := range modules { + if module.HasChild("source") && module.GetAttribute("source").Equals(moduleSource) { + results = append(results, module) + } + } + return results, nil +} + +func (c *Module) GetReferencingBlocks(originalBlock *Block, referencingType, referencingLabel, referencingAttributeName string) Blocks { + blocks := c.getBlocksByType(referencingType, referencingLabel) + var results Blocks + for _, block := range blocks { + attr := block.GetAttribute(referencingAttributeName) + if attr == nil { + continue + } + if attr.References(originalBlock.reference) { + results = append(results, block) + } else { + for _, ref := range attr.AllReferences() { + if ref.TypeLabel() == "each" { + fe := block.GetAttribute("for_each") + if fe.References(originalBlock.reference) { + results = append(results, block) + } + } + } + } + } + return results +} diff --git a/pkg/iac/terraform/modules.go b/pkg/iac/terraform/modules.go new file mode 100644 index 000000000000..8a9cad25c433 --- /dev/null +++ b/pkg/iac/terraform/modules.go @@ -0,0 +1,108 @@ +package terraform + +import ( + "fmt" + + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +type Modules []*Module + +type ResourceIDResolutions map[string]bool + +func (r ResourceIDResolutions) Resolve(id string) { + r[id] = true +} + +func (r ResourceIDResolutions) Orphans() (orphanIDs []string) { + for id, resolved := range r { + if !resolved { + orphanIDs = append(orphanIDs, id) + } + } + return orphanIDs +} + +func (m Modules) GetResourcesByType(typeLabel ...string) Blocks { + var blocks Blocks + for _, module := range m { + blocks = append(blocks, module.GetResourcesByType(typeLabel...)...) + } + + return blocks +} + +func (m Modules) GetChildResourceIDMapByType(typeLabels ...string) ResourceIDResolutions { + blocks := m.GetResourcesByType(typeLabels...) + + idMap := make(map[string]bool) + for _, block := range blocks { + idMap[block.ID()] = false + } + + return idMap +} + +func (m Modules) GetReferencedBlock(referringAttr *Attribute, parentBlock *Block) (*Block, error) { + var bestMatch *Block + for _, module := range m { + b, err := module.GetReferencedBlock(referringAttr, parentBlock) + if err == nil { + if bestMatch == nil || b.moduleBlock == parentBlock.moduleBlock { + bestMatch = b + } + } + } + if bestMatch != nil { + return bestMatch, nil + } + return nil, fmt.Errorf("block not found") +} + +func (m Modules) GetReferencingResources(originalBlock *Block, referencingLabel, referencingAttributeName string) Blocks { + var blocks Blocks + for _, module := range m { + blocks = append(blocks, module.GetReferencingResources(originalBlock, referencingLabel, referencingAttributeName)...) + } + + return blocks +} + +func (m Modules) GetBlocks() Blocks { + var blocks Blocks + for _, module := range m { + blocks = append(blocks, module.GetBlocks()...) + } + return blocks +} + +func (m Modules) GetBlockById(id string) (*Block, error) { + for _, module := range m { + if found := module.blocks.WithID(id); found != nil { + return found, nil + } + + } + return nil, fmt.Errorf("block not found") +} + +func (m Modules) GetResourceByIDs(id ...string) Blocks { + var blocks Blocks + for _, module := range m { + blocks = append(blocks, module.GetResourcesByIDs(id...)...) + } + + return blocks +} + +func (m Modules) GetBlockByIgnoreRange(blockMetadata *types.Metadata) *Block { + for _, module := range m { + for _, block := range module.GetBlocks() { + metadata := block.GetMetadata() + if blockMetadata.Reference() == metadata.Reference() { + return block + } + } + } + return nil +} diff --git a/pkg/iac/terraform/presets.go b/pkg/iac/terraform/presets.go new file mode 100644 index 000000000000..d10d795a86d7 --- /dev/null +++ b/pkg/iac/terraform/presets.go @@ -0,0 +1,55 @@ +package terraform + +import ( + "fmt" + "strings" + + "github.com/google/uuid" + "github.com/zclconf/go-cty/cty" +) + +func createPresetValues(b *Block) map[string]cty.Value { + presets := make(map[string]cty.Value) + + // here we set up common "id" values that are set by the provider - this ensures all blocks have a default + // referencable id/arn. this isn't perfect, but the only way to link blocks in certain circumstances. + presets["id"] = cty.StringVal(b.ID()) + + if strings.HasPrefix(b.TypeLabel(), "aws_") { + presets["arn"] = cty.StringVal(b.ID()) + } + + // workaround for weird iam feature + switch b.TypeLabel() { + case "aws_iam_policy_document": + presets["json"] = cty.StringVal(b.ID()) + // If the user leaves the name blank, Terraform will automatically generate a unique name + case "aws_launch_template": + presets["name"] = cty.StringVal(uuid.New().String()) + } + + return presets + +} + +func postProcessValues(b *Block, input map[string]cty.Value) map[string]cty.Value { + + // alias id to "bucket" (bucket name) for s3 bucket resources + if strings.HasPrefix(b.TypeLabel(), "aws_s3_bucket") { + if bucket, ok := input["bucket"]; ok { + input["id"] = bucket + } else { + input["bucket"] = cty.StringVal(b.ID()) + } + } + + if b.TypeLabel() == "aws_s3_bucket" { + var bucketName string + if bucket := input["bucket"]; bucket.Type().Equals(cty.String) { + bucketName = bucket.AsString() + } + input["arn"] = cty.StringVal(fmt.Sprintf("arn:aws:s3:::%s", bucketName)) + } + + return input +} diff --git a/pkg/iac/terraform/reference.go b/pkg/iac/terraform/reference.go new file mode 100644 index 000000000000..978773da5010 --- /dev/null +++ b/pkg/iac/terraform/reference.go @@ -0,0 +1,177 @@ +package terraform + +import ( + "fmt" + + "github.com/zclconf/go-cty/cty" +) + +type Reference struct { + blockType Type + typeLabel string + nameLabel string + remainder []string + key cty.Value + parent string +} + +func extendReference(ref Reference, name string) Reference { + child := ref + child.remainder = make([]string, len(ref.remainder)) + if len(ref.remainder) > 0 { + copy(child.remainder, ref.remainder) + } + child.remainder = append(child.remainder, name) + return child +} + +func newReference(parts []string, parentKey string) (*Reference, error) { + + var ref Reference + + if len(parts) == 0 { + return nil, fmt.Errorf("cannot create empty reference") + } + + blockType, err := TypeFromRefName(parts[0]) + if err != nil { + blockType = &TypeResource + } + + ref.blockType = *blockType + + if ref.blockType.removeTypeInReference && parts[0] != blockType.name { + ref.typeLabel = parts[0] + if len(parts) > 1 { + ref.nameLabel = parts[1] + } + } else if len(parts) > 1 { + ref.typeLabel = parts[1] + if len(parts) > 2 { + ref.nameLabel = parts[2] + } else { + ref.nameLabel = ref.typeLabel + ref.typeLabel = "" + } + } + if len(parts) > 3 { + ref.remainder = parts[3:] + } + + if parentKey != "root" { + ref.parent = parentKey + } + + return &ref, nil +} + +func (r Reference) BlockType() Type { + return r.blockType +} + +func (r Reference) TypeLabel() string { + return r.typeLabel +} + +func (r Reference) NameLabel() string { + return r.nameLabel +} + +func (r Reference) HumanReadable() string { + if r.parent == "" { + return r.String() + } + return fmt.Sprintf("%s:%s", r.parent, r.String()) +} + +func (r Reference) LogicalID() string { + return r.String() +} + +func (r Reference) String() string { + + base := r.typeLabel + if r.nameLabel != "" { + base = fmt.Sprintf("%s.%s", base, r.nameLabel) + } + + if !r.blockType.removeTypeInReference { + base = r.blockType.Name() + if r.typeLabel != "" { + base += "." + r.typeLabel + } + if r.nameLabel != "" { + base += "." + r.nameLabel + } + } + + base += r.KeyBracketed() + + for _, rem := range r.remainder { + base += "." + rem + } + + return base +} + +func (r Reference) RefersTo(other Reference) bool { + + if r.BlockType() != other.BlockType() { + return false + } + if r.TypeLabel() != other.TypeLabel() { + return false + } + if r.NameLabel() != other.NameLabel() { + return false + } + if (r.Key() != "" || other.Key() != "") && r.Key() != other.Key() { + return false + } + return true +} + +func (r *Reference) SetKey(key cty.Value) { + if key.IsNull() || !key.IsKnown() { + return + } + r.key = key +} + +func (r Reference) KeyBracketed() string { + switch v := key(r).(type) { + case int: + return fmt.Sprintf("[%d]", v) + case string: + if v == "" { + return "" + } + return fmt.Sprintf("[%q]", v) + default: + return "" + } +} + +func (r Reference) RawKey() cty.Value { + return r.key +} + +func (r Reference) Key() string { + return fmt.Sprintf("%v", key(r)) +} + +func key(r Reference) interface{} { + if r.key.IsNull() || !r.key.IsKnown() { + return "" + } + switch r.key.Type() { + case cty.Number: + f := r.key.AsBigFloat() + f64, _ := f.Float64() + return int(f64) + case cty.String: + return r.key.AsString() + default: + return "" + } +} diff --git a/pkg/iac/terraform/reference_test.go b/pkg/iac/terraform/reference_test.go new file mode 100644 index 000000000000..1b6a7b59be73 --- /dev/null +++ b/pkg/iac/terraform/reference_test.go @@ -0,0 +1,171 @@ +package terraform + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" +) + +func Test_ReferenceParsing(t *testing.T) { + cases := []struct { + input []string + expected string + }{ + { + input: []string{"module", "my-mod"}, + expected: "module.my-mod", + }, + { + input: []string{"aws_s3_bucket", "test"}, + expected: "aws_s3_bucket.test", + }, + { + input: []string{"resource", "aws_s3_bucket", "test"}, + expected: "aws_s3_bucket.test", + }, + { + input: []string{"module", "my-mod"}, + expected: "module.my-mod", + }, + { + input: []string{"data", "aws_iam_policy_document", "s3_policy"}, + expected: "data.aws_iam_policy_document.s3_policy", + }, + { + input: []string{"provider", "aws"}, + expected: "provider.aws", + }, + { + input: []string{"output", "something"}, + expected: "output.something", + }, + } + + for _, test := range cases { + t.Run(test.expected, func(t *testing.T) { + ref, err := newReference(test.input, "") + assert.NoError(t, err) + assert.Equal(t, test.expected, ref.String()) + }) + } +} + +func Test_SetKey(t *testing.T) { + tests := []struct { + name string + key cty.Value + want cty.Value + }{ + { + name: "happy", + key: cty.StringVal("str"), + want: cty.StringVal("str"), + }, + { + name: "null key", + key: cty.NullVal(cty.String), + want: cty.Value{}, + }, + { + name: "unknown key", + key: cty.UnknownVal(cty.String), + want: cty.Value{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { + ref, err := newReference([]string{"resource", "test"}, "") + require.NoError(t, err) + + ref.SetKey(tt.key) + + assert.Equal(t, tt.want, ref.RawKey()) + }) + }) + } +} + +func Test_Key(t *testing.T) { + + tests := []struct { + name string + key cty.Value + want string + }{ + { + name: "empty key", + want: "", + }, + { + name: "str key", + key: cty.StringVal("some_value"), + want: "some_value", + }, + { + name: "number key", + key: cty.NumberIntVal(122), + want: "122", + }, + { + name: "bool key", + key: cty.BoolVal(true), + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { + ref, err := newReference([]string{"resource", "test"}, "") + require.NoError(t, err) + + ref.SetKey(tt.key) + + assert.Equal(t, tt.want, ref.Key()) + }) + }) + } +} + +func Test_KeyBracketed(t *testing.T) { + tests := []struct { + name string + key cty.Value + want string + }{ + { + name: "empty key", + want: "", + }, + { + name: "str key", + key: cty.StringVal("some_value"), + want: "[\"some_value\"]", + }, + { + name: "number key", + key: cty.NumberIntVal(122), + want: "[122]", + }, + { + name: "bool key", + key: cty.BoolVal(true), + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ref, err := newReference([]string{"resource", "test"}, "") + require.NoError(t, err) + + ref.SetKey(tt.key) + + assert.Equal(t, tt.want, ref.KeyBracketed()) + }) + } +} diff --git a/pkg/iac/terraform/resource_block.go b/pkg/iac/terraform/resource_block.go new file mode 100644 index 000000000000..3339675ee304 --- /dev/null +++ b/pkg/iac/terraform/resource_block.go @@ -0,0 +1,173 @@ +package terraform + +import ( + "bytes" + "fmt" + "regexp" + "strings" + "text/template" +) + +type PlanReference struct { + Value interface{} +} + +type PlanBlock struct { + Type string + Name string + BlockType string + Blocks map[string]map[string]interface{} + Attributes map[string]interface{} +} + +func NewPlanBlock(blockType, resourceType, resourceName string) *PlanBlock { + if blockType == "managed" { + blockType = "resource" + } + + return &PlanBlock{ + Type: resourceType, + Name: resourceName, + BlockType: blockType, + Blocks: make(map[string]map[string]interface{}), + Attributes: make(map[string]interface{}), + } +} + +func (rb *PlanBlock) HasAttribute(attribute string) bool { + for k := range rb.Attributes { + if k == attribute { + return true + } + } + return false +} + +func (rb *PlanBlock) ToHCL() string { + + resourceTmpl, err := template.New("resource").Funcs(template.FuncMap{ + "RenderValue": renderTemplateValue, + "RenderPrimitive": renderPrimitive, + }).Parse(resourceTemplate) + if err != nil { + panic(err) + } + + var res bytes.Buffer + if err := resourceTmpl.Execute(&res, map[string]interface{}{ + "BlockType": rb.BlockType, + "Type": rb.Type, + "Name": rb.Name, + "Attributes": rb.Attributes, + "Blocks": rb.Blocks, + }); err != nil { + return "" + } + return res.String() +} + +var resourceTemplate = `{{ .BlockType }} "{{ .Type }}" "{{ .Name }}" { + {{ range $name, $value := .Attributes }}{{ if $value }}{{ $name }} {{ RenderValue $value }} + {{end}}{{ end }}{{ range $name, $block := .Blocks }}{{ $name }} { + {{ range $name, $value := $block }}{{ if $value }}{{ $name }} {{ RenderValue $value }} + {{end}}{{ end }}} +{{end}}}` + +func renderTemplateValue(val interface{}) string { + switch t := val.(type) { + case map[string]interface{}: + return fmt.Sprintf("= %s", renderMap(t)) + case []interface{}: + if isMapSlice(t) { + return renderSlice(t) + } + return fmt.Sprintf("= %s", renderSlice(t)) + default: + return fmt.Sprintf("= %s", renderPrimitive(val)) + } +} + +func renderPrimitive(val interface{}) string { + switch t := val.(type) { + case PlanReference: + return fmt.Sprintf("%v", t.Value) + case string: + return parseStringPrimitive(t) + case map[string]interface{}: + return renderMap(t) + case []interface{}: + return renderSlice(t) + default: + return fmt.Sprintf("%#v", t) + } + +} + +func parseStringPrimitive(input string) string { + // we must escape templating + // ref: https://developer.hashicorp.com/terraform/language/expressions/strings#escape-sequences-1 + r := regexp.MustCompile(`((\$|\%)\{.+\})`) + ff := r.ReplaceAllStringFunc(input, func(s string) string { + s = strings.Replace(s, "$", "$$", 1) + s = strings.Replace(s, "%", "%%", 1) + return s + }) + if strings.Contains(ff, "\n") { + return fmt.Sprintf(`< i +} + +func (s IntValue) ToRego() interface{} { + m := s.metadata.ToRego().(map[string]interface{}) + m["value"] = s.Value() + return m +} diff --git a/pkg/iac/types/int_test.go b/pkg/iac/types/int_test.go new file mode 100644 index 000000000000..83e2d65cf0b0 --- /dev/null +++ b/pkg/iac/types/int_test.go @@ -0,0 +1,21 @@ +package types + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_IntJSON(t *testing.T) { + val := Int(0x66, NewMetadata(NewRange("main.tf", 123, 123, "", nil), "")) + data, err := json.Marshal(val) + require.NoError(t, err) + + var restored IntValue + err = json.Unmarshal(data, &restored) + require.NoError(t, err) + + assert.Equal(t, val, restored) +} diff --git a/pkg/iac/types/map.go b/pkg/iac/types/map.go new file mode 100755 index 000000000000..95a0e6851700 --- /dev/null +++ b/pkg/iac/types/map.go @@ -0,0 +1,92 @@ +package types + +import ( + "encoding/json" +) + +type MapValue struct { + BaseAttribute + value map[string]string +} + +func (b MapValue) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "value": b.value, + "metadata": b.metadata, + }) +} + +func (b *MapValue) UnmarshalJSON(data []byte) error { + var keys map[string]interface{} + if err := json.Unmarshal(data, &keys); err != nil { + return err + } + if keys["value"] != nil { + var target map[string]string + raw, err := json.Marshal(keys["value"]) + if err != nil { + return err + } + if err := json.Unmarshal(raw, &target); err != nil { + return err + } + b.value = target + } + if keys["metadata"] != nil { + raw, err := json.Marshal(keys["metadata"]) + if err != nil { + return err + } + var m Metadata + if err := json.Unmarshal(raw, &m); err != nil { + return err + } + b.metadata = m + } + return nil +} + +func Map(value map[string]string, m Metadata) MapValue { + return MapValue{ + value: value, + BaseAttribute: BaseAttribute{metadata: m}, + } +} + +func MapDefault(value map[string]string, m Metadata) MapValue { + b := Map(value, m) + b.BaseAttribute.metadata.isDefault = true + return b +} + +func MapExplicit(value map[string]string, m Metadata) MapValue { + b := Map(value, m) + b.BaseAttribute.metadata.isExplicit = true + return b +} + +func (b MapValue) Value() map[string]string { + return b.value +} + +func (b MapValue) GetRawValue() interface{} { + return b.value +} + +func (b MapValue) Len() int { + return len(b.value) +} + +func (b MapValue) HasKey(key string) bool { + if b.value == nil { + return false + } + _, ok := b.value[key] + return ok +} + +func (s MapValue) ToRego() interface{} { + m := s.metadata.ToRego().(map[string]interface{}) + m["value"] = s.Value() + return m +} diff --git a/pkg/iac/types/map_test.go b/pkg/iac/types/map_test.go new file mode 100644 index 000000000000..1dba9504b30c --- /dev/null +++ b/pkg/iac/types/map_test.go @@ -0,0 +1,25 @@ +package types + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_MapJSON(t *testing.T) { + val := Map(map[string]string{ + "yeah": "it", + "seems": "to", + "work": "fine", + }, NewMetadata(NewRange("main.tf", 123, 123, "", nil), "")) + data, err := json.Marshal(val) + require.NoError(t, err) + + var restored MapValue + err = json.Unmarshal(data, &restored) + require.NoError(t, err) + + assert.Equal(t, val, restored) +} diff --git a/pkg/iac/types/metadata.go b/pkg/iac/types/metadata.go new file mode 100755 index 000000000000..6b130eb3d17e --- /dev/null +++ b/pkg/iac/types/metadata.go @@ -0,0 +1,222 @@ +package types + +import ( + "encoding/json" + "fmt" + "strings" +) + +type Metadata struct { + rnge Range + ref string + isManaged bool + isDefault bool + isExplicit bool + isUnresolvable bool + parent *Metadata + internal interface{} +} + +func (m Metadata) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "range": m.rnge, + "ref": m.ref, + "managed": m.isManaged, + "default": m.isDefault, + "explicit": m.isExplicit, + "unresolvable": m.isUnresolvable, + "parent": m.parent, + }) +} + +func (m *Metadata) UnmarshalJSON(data []byte) error { + var keys map[string]interface{} + if err := json.Unmarshal(data, &keys); err != nil { + return err + } + if keys["range"] != nil { + raw, err := json.Marshal(keys["range"]) + if err != nil { + return err + } + var r Range + if err := json.Unmarshal(raw, &r); err != nil { + return err + } + m.rnge = r + } + if keys["ref"] != nil { + m.ref = keys["ref"].(string) + } + if keys["managed"] != nil { + m.isManaged = keys["managed"].(bool) + } + if keys["default"] != nil { + m.isDefault = keys["default"].(bool) + } + if keys["explicit"] != nil { + m.isExplicit = keys["explicit"].(bool) + } + if keys["unresolvable"] != nil { + m.isUnresolvable = keys["unresolvable"].(bool) + } + if keys["parent"] != nil { + if _, ok := keys["parent"].(map[string]interface{}); ok { + raw, err := json.Marshal(keys["parent"]) + if err != nil { + return err + } + var parent Metadata + if err := json.Unmarshal(raw, &parent); err != nil { + return err + } + m.parent = &parent + } + } + return nil +} + +func (m *Metadata) ToRego() interface{} { + input := map[string]interface{}{ + "filepath": m.Range().GetLocalFilename(), + "startline": m.Range().GetStartLine(), + "endline": m.Range().GetEndLine(), + "sourceprefix": m.Range().GetSourcePrefix(), + "managed": m.isManaged, + "explicit": m.isExplicit, + "fskey": CreateFSKey(m.Range().GetFS()), + "resource": m.Reference(), + } + if m.parent != nil { + input["parent"] = m.parent.ToRego() + } + return input +} + +func NewMetadata(r Range, ref string) Metadata { + return Metadata{ + rnge: r, + ref: ref, + isManaged: true, + } +} + +func NewUnresolvableMetadata(r Range, ref string) Metadata { + unres := NewMetadata(r, ref) + unres.isUnresolvable = true + return unres +} + +func NewExplicitMetadata(r Range, ref string) Metadata { + m := NewMetadata(r, ref) + m.isExplicit = true + return m +} + +func (m Metadata) WithParent(p Metadata) Metadata { + m.parent = &p + return m +} + +func (m *Metadata) SetParentPtr(p *Metadata) { + m.parent = p +} + +func (m Metadata) Parent() *Metadata { + return m.parent +} + +func (m Metadata) Root() Metadata { + meta := &m + for meta.Parent() != nil { + meta = meta.Parent() + } + return *meta +} + +func (m Metadata) WithInternal(internal interface{}) Metadata { + m.internal = internal + return m +} + +func (m Metadata) Internal() interface{} { + return m.internal +} + +func (m Metadata) IsMultiLine() bool { + return m.rnge.GetStartLine() < m.rnge.GetEndLine() +} + +func NewUnmanagedMetadata() Metadata { + m := NewMetadata(NewRange("", 0, 0, "", nil), "") + m.isManaged = false + return m +} + +func NewTestMetadata() Metadata { + return NewMetadata(NewRange("test.test", 123, 123, "", nil), "") +} + +func NewApiMetadata(provider string, parts ...string) Metadata { + return NewMetadata(NewRange(fmt.Sprintf("/%s/%s", provider, strings.Join(parts, "/")), 0, 0, "", nil), "") +} + +func NewRemoteMetadata(id string) Metadata { + return NewMetadata(NewRange(id, 0, 0, "remote", nil), id) +} + +func (m Metadata) IsDefault() bool { + return m.isDefault +} + +func (m Metadata) IsResolvable() bool { + return !m.isUnresolvable +} + +func (m Metadata) IsExplicit() bool { + return m.isExplicit +} + +func (m Metadata) String() string { + return m.ref +} + +func (m Metadata) Reference() string { + return m.ref +} + +func (m Metadata) Range() Range { + return m.rnge +} + +func (m Metadata) IsManaged() bool { + return m.isManaged +} + +func (m Metadata) IsUnmanaged() bool { + return !m.isManaged +} + +type BaseAttribute struct { + metadata Metadata +} + +func (b BaseAttribute) GetMetadata() Metadata { + return b.metadata +} + +func (m Metadata) GetMetadata() Metadata { + return m +} + +func (m Metadata) GetRawValue() interface{} { + return nil +} + +func (m *Metadata) SetReference(ref string) { + m.ref = ref +} + +func (m *Metadata) SetRange(r Range) { + m.rnge = r +} diff --git a/pkg/iac/types/metadata_test.go b/pkg/iac/types/metadata_test.go new file mode 100644 index 000000000000..1b263f3289de --- /dev/null +++ b/pkg/iac/types/metadata_test.go @@ -0,0 +1,35 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_MetadataToRego(t *testing.T) { + m1 := NewTestMetadata() + expected := map[string]interface{}{ + "endline": 123, + "explicit": false, + "filepath": "test.test", + "fskey": "", + "managed": true, + "resource": "", + "sourceprefix": "", + "startline": 123, + } + assert.Equal(t, expected, m1.ToRego()) + m2 := NewTestMetadata() + m1.SetParentPtr(&m2) + expected["parent"] = map[string]interface{}{ + "endline": 123, + "explicit": false, + "filepath": "test.test", + "fskey": "", + "managed": true, + "resource": "", + "sourceprefix": "", + "startline": 123, + } + assert.Equal(t, expected, m1.ToRego()) +} diff --git a/pkg/iac/types/range.go b/pkg/iac/types/range.go new file mode 100755 index 000000000000..938ba929b682 --- /dev/null +++ b/pkg/iac/types/range.go @@ -0,0 +1,148 @@ +package types + +import ( + "encoding/json" + "fmt" + "io/fs" + "path" +) + +func NewRange(filename string, startLine, endLine int, sourcePrefix string, srcFS fs.FS) Range { + r := Range{ + filename: filename, + startLine: startLine, + endLine: endLine, + fs: srcFS, + fsKey: CreateFSKey(srcFS), + sourcePrefix: sourcePrefix, + } + return r +} + +func NewRangeWithLogicalSource(filename string, startLine int, endLine int, sourcePrefix string, + srcFS fs.FS) Range { + r := Range{ + filename: filename, + startLine: startLine, + endLine: endLine, + fs: srcFS, + fsKey: CreateFSKey(srcFS), + sourcePrefix: sourcePrefix, + isLogicalSource: true, + } + return r +} + +func NewRangeWithFSKey(filename string, startLine, endLine int, sourcePrefix, fsKey string, fsys fs.FS) Range { + r := Range{ + filename: filename, + startLine: startLine, + endLine: endLine, + fs: fsys, + fsKey: fsKey, + sourcePrefix: sourcePrefix, + } + return r +} + +type Range struct { + filename string + startLine int + endLine int + sourcePrefix string + isLogicalSource bool + fs fs.FS + fsKey string +} + +func (r Range) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "filename": r.filename, + "startLine": r.startLine, + "endLine": r.endLine, + "sourcePrefix": r.sourcePrefix, + "fsKey": r.fsKey, + "isLogicalSource": r.isLogicalSource, + }) +} + +func (r *Range) UnmarshalJSON(data []byte) error { + var keys map[string]interface{} + if err := json.Unmarshal(data, &keys); err != nil { + return err + } + if keys["filename"] != nil { + r.filename = keys["filename"].(string) + } + if keys["startLine"] != nil { + r.startLine = int(keys["startLine"].(float64)) + } + if keys["endLine"] != nil { + r.endLine = int(keys["endLine"].(float64)) + } + if keys["sourcePrefix"] != nil { + r.sourcePrefix = keys["sourcePrefix"].(string) + } + if keys["fsKey"] != nil { + r.fsKey = keys["fsKey"].(string) + } + if keys["isLogicalSource"] != nil { + r.isLogicalSource = keys["isLogicalSource"].(bool) + } + return nil +} + +func (r Range) GetFSKey() string { + return r.fsKey +} + +func (r Range) LineCount() int { + if r.endLine == 0 { + return 0 + } + return (r.endLine - r.startLine) + 1 +} + +func (r Range) GetFilename() string { + if r.sourcePrefix == "" { + return r.filename + } + if r.isLogicalSource { + return fmt.Sprintf("%s:%s", r.sourcePrefix, r.filename) + } + return path.Join(r.sourcePrefix, r.filename) +} + +func (r Range) GetLocalFilename() string { + return r.filename +} + +func (r Range) GetStartLine() int { + return r.startLine +} + +func (r Range) GetEndLine() int { + return r.endLine +} + +func (r Range) IsMultiLine() bool { + return r.startLine < r.endLine +} + +func (r Range) String() string { + if r.startLine != r.endLine { + return fmt.Sprintf("%s:%d-%d", r.GetFilename(), r.startLine, r.endLine) + } + if r.startLine == 0 && r.endLine == 0 { + return r.GetFilename() + } + return fmt.Sprintf("%s:%d", r.GetFilename(), r.startLine) +} + +func (r Range) GetFS() fs.FS { + return r.fs +} + +func (r Range) GetSourcePrefix() string { + return r.sourcePrefix +} diff --git a/pkg/iac/types/rules/rule.go b/pkg/iac/types/rules/rule.go new file mode 100644 index 000000000000..b86a4ad6d11f --- /dev/null +++ b/pkg/iac/types/rules/rule.go @@ -0,0 +1,18 @@ +package rules + +import ( + "github.com/aquasecurity/trivy/pkg/iac/scan" +) + +type RegisteredRule struct { + scan.Rule + Number int +} + +func (r *RegisteredRule) GetRule() scan.Rule { + return r.Rule +} + +func (r *RegisteredRule) AddLink(link string) { + r.Rule.Links = append([]string{link}, r.Rule.Links...) +} diff --git a/pkg/iac/types/sources.go b/pkg/iac/types/sources.go new file mode 100644 index 000000000000..02e43dac3a35 --- /dev/null +++ b/pkg/iac/types/sources.go @@ -0,0 +1,14 @@ +package types + +type Source string + +const ( + SourceDockerfile Source = "dockerfile" + SourceKubernetes Source = "kubernetes" + SourceRbac Source = "rbac" // deprecated - please use "kubernetes" instead + SourceDefsec Source = "defsec" // deprecated - please use "cloud" instead + SourceCloud Source = "cloud" + SourceYAML Source = "yaml" + SourceJSON Source = "json" + SourceTOML Source = "toml" +) diff --git a/pkg/iac/types/string.go b/pkg/iac/types/string.go new file mode 100755 index 000000000000..1db865740cd3 --- /dev/null +++ b/pkg/iac/types/string.go @@ -0,0 +1,194 @@ +package types + +import ( + "encoding/json" + "strings" +) + +type StringEqualityOption int + +const ( + IgnoreCase StringEqualityOption = iota + IsPallindrome + IgnoreWhitespace +) + +func String(str string, m Metadata) StringValue { + return StringValue{ + value: str, + BaseAttribute: BaseAttribute{metadata: m}, + } +} + +func StringDefault(value string, m Metadata) StringValue { + b := String(value, m) + b.BaseAttribute.metadata.isDefault = true + return b +} + +func StringUnresolvable(m Metadata) StringValue { + b := String("", m) + b.BaseAttribute.metadata.isUnresolvable = true + return b +} + +func StringExplicit(value string, m Metadata) StringValue { + b := String(value, m) + b.BaseAttribute.metadata.isExplicit = true + return b +} + +func StringTest(value string) StringValue { + return String(value, NewTestMetadata()) +} + +type StringValueList []StringValue + +type StringValue struct { + BaseAttribute + value string +} + +func (l StringValueList) AsStrings() (output []string) { + for _, item := range l { + output = append(output, item.Value()) + } + return output +} + +type stringCheckFunc func(string, string) bool + +func (b StringValue) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "value": b.value, + "metadata": b.metadata, + }) +} + +func (b *StringValue) UnmarshalJSON(data []byte) error { + var keys map[string]interface{} + if err := json.Unmarshal(data, &keys); err != nil { + return err + } + if keys["value"] != nil { + b.value = keys["value"].(string) + } + if keys["metadata"] != nil { + raw, err := json.Marshal(keys["metadata"]) + if err != nil { + return err + } + var m Metadata + if err := json.Unmarshal(raw, &m); err != nil { + return err + } + b.metadata = m + } + return nil +} + +func (s StringValue) ToRego() interface{} { + m := s.metadata.ToRego().(map[string]interface{}) + m["value"] = s.Value() + return m +} + +func (s StringValue) IsOneOf(values ...string) bool { + if s.metadata.isUnresolvable { + return false + } + for _, value := range values { + if value == s.value { + return true + } + } + return false +} + +func (s StringValue) GetMetadata() Metadata { + return s.metadata +} + +func (s StringValue) Value() string { + return s.value +} + +func (b StringValue) GetRawValue() interface{} { + return b.value +} + +func (s StringValue) IsEmpty() bool { + if s.metadata.isUnresolvable { + return false + } + return s.value == "" +} + +func (s StringValue) IsNotEmpty() bool { + if s.metadata.isUnresolvable { + return false + } + return s.value != "" +} + +func (s StringValue) EqualTo(value string, equalityOptions ...StringEqualityOption) bool { + if s.metadata.isUnresolvable { + return false + } + + return s.executePredicate(value, func(a, b string) bool { return a == b }, equalityOptions...) +} + +func (s StringValue) NotEqualTo(value string, equalityOptions ...StringEqualityOption) bool { + if s.metadata.isUnresolvable { + return false + } + + return !s.EqualTo(value, equalityOptions...) +} + +func (s StringValue) StartsWith(prefix string, equalityOptions ...StringEqualityOption) bool { + if s.metadata.isUnresolvable { + return false + } + + return s.executePredicate(prefix, strings.HasPrefix, equalityOptions...) +} + +func (s StringValue) EndsWith(suffix string, equalityOptions ...StringEqualityOption) bool { + if s.metadata.isUnresolvable { + return false + } + return s.executePredicate(suffix, strings.HasSuffix, equalityOptions...) +} + +func (s StringValue) Contains(value string, equalityOptions ...StringEqualityOption) bool { + if s.metadata.isUnresolvable { + return false + } + return s.executePredicate(value, strings.Contains, equalityOptions...) +} + +func (s StringValue) executePredicate(value string, fn stringCheckFunc, equalityOptions ...StringEqualityOption) bool { + subjectString := s.value + searchString := value + + for _, eqOpt := range equalityOptions { + switch eqOpt { + case IgnoreCase: + subjectString = strings.ToLower(subjectString) + searchString = strings.ToLower(searchString) + case IsPallindrome: + var result string + for _, v := range subjectString { + result = string(v) + result + } + subjectString = result + case IgnoreWhitespace: + subjectString = strings.ReplaceAll(subjectString, " ", "") + searchString = strings.ReplaceAll(searchString, " ", "") + } + } + + return fn(subjectString, searchString) +} diff --git a/pkg/iac/types/string_test.go b/pkg/iac/types/string_test.go new file mode 100755 index 000000000000..1f135874bf18 --- /dev/null +++ b/pkg/iac/types/string_test.go @@ -0,0 +1,94 @@ +package types + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/stretchr/testify/assert" +) + +func Test_StringValueEqualTo(t *testing.T) { + testCases := []struct { + desc string + input string + check string + ignoreCase bool + expected bool + }{ + { + desc: "return truw when string is equal", + input: "something", + check: "", + expected: false, + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + + }) + } +} + +func Test_StringValueStartsWith(t *testing.T) { + testCases := []struct { + desc string + input string + prefix string + ignoreCase bool + expected bool + }{ + { + desc: "return true when starts with", + input: "something", + prefix: "some", + expected: true, + }, + { + desc: "return false when does not start with", + input: "something", + prefix: "nothing", + expected: false, + }, + { + desc: "return true when starts with", + input: "something", + prefix: "SOME", + ignoreCase: true, + expected: true, + }, + { + desc: "return false when does not start with", + input: "something", + prefix: "SOME", + expected: false, + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + + val := String(tC.input, fakeMetadata) + + var options []StringEqualityOption + + if tC.ignoreCase { + options = append(options, IgnoreCase) + } + + assert.Equal(t, tC.expected, val.StartsWith(tC.prefix, options...)) + }) + } +} + +func Test_StringJSON(t *testing.T) { + val := String("hello world", NewMetadata(NewRange("main.tf", 123, 123, "", nil), "")) + data, err := json.Marshal(val) + require.NoError(t, err) + + var restored StringValue + err = json.Unmarshal(data, &restored) + require.NoError(t, err) + + assert.Equal(t, val, restored) +} diff --git a/pkg/iac/types/time.go b/pkg/iac/types/time.go new file mode 100755 index 000000000000..4f3c8c455f2f --- /dev/null +++ b/pkg/iac/types/time.go @@ -0,0 +1,102 @@ +package types + +import ( + "encoding/json" + "time" +) + +type TimeValue struct { + BaseAttribute + value time.Time +} + +func (b TimeValue) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "value": b.value.Format(time.RFC3339), + "metadata": b.metadata, + }) +} + +func (b *TimeValue) UnmarshalJSON(data []byte) error { + var keys map[string]interface{} + if err := json.Unmarshal(data, &keys); err != nil { + return err + } + if keys["value"] != nil { + if ti, err := time.Parse(time.RFC3339, keys["value"].(string)); err == nil { + b.value = ti + } + } + if keys["metadata"] != nil { + raw, err := json.Marshal(keys["metadata"]) + if err != nil { + return err + } + var m Metadata + if err := json.Unmarshal(raw, &m); err != nil { + return err + } + b.metadata = m + } + return nil +} + +func Time(value time.Time, m Metadata) TimeValue { + return TimeValue{ + value: value, + BaseAttribute: BaseAttribute{metadata: m}, + } +} + +func TimeDefault(value time.Time, m Metadata) TimeValue { + b := Time(value, m) + b.BaseAttribute.metadata.isDefault = true + return b +} + +func TimeExplicit(value time.Time, m Metadata) TimeValue { + b := Time(value, m) + b.BaseAttribute.metadata.isExplicit = true + return b +} + +func TimeUnresolvable(m Metadata) TimeValue { + b := Time(time.Time{}, m) + b.BaseAttribute.metadata.isUnresolvable = true + return b +} + +func (t TimeValue) Value() time.Time { + return t.value +} + +func (t TimeValue) GetRawValue() interface{} { + return t.value +} + +func (t TimeValue) IsNever() bool { + if t.GetMetadata().isUnresolvable { + return false + } + return t.value.IsZero() +} + +func (t TimeValue) Before(i time.Time) bool { + if t.metadata.isUnresolvable { + return false + } + return t.value.Before(i) +} + +func (t TimeValue) After(i time.Time) bool { + if t.metadata.isUnresolvable { + return false + } + return t.value.After(i) +} + +func (t TimeValue) ToRego() interface{} { + m := t.metadata.ToRego().(map[string]interface{}) + m["value"] = t.Value().Format(time.RFC3339) + return m +} diff --git a/pkg/iac/types/time_test.go b/pkg/iac/types/time_test.go new file mode 100644 index 000000000000..5d38b0dfb570 --- /dev/null +++ b/pkg/iac/types/time_test.go @@ -0,0 +1,23 @@ +package types + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_TimeJSON(t *testing.T) { + val := Time(time.Now(), NewMetadata(NewRange("main.tf", 123, 123, "", nil), "")) + data, err := json.Marshal(val) + require.NoError(t, err) + + var restored TimeValue + err = json.Unmarshal(data, &restored) + require.NoError(t, err) + + assert.Equal(t, val.value.Format(time.RFC3339), restored.Value().Format(time.RFC3339)) + assert.Equal(t, val.metadata, restored.metadata) +} diff --git a/pkg/javadb/client.go b/pkg/javadb/client.go new file mode 100644 index 000000000000..86194b263569 --- /dev/null +++ b/pkg/javadb/client.go @@ -0,0 +1,187 @@ +package javadb + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "sync" + "time" + + "github.com/google/go-containerregistry/pkg/name" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-java-db/pkg/db" + "github.com/aquasecurity/trivy-java-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/oci" +) + +const ( + SchemaVersion = db.SchemaVersion + mediaType = "application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip" +) + +var DefaultRepository = fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-java-db", SchemaVersion) + +var updater *Updater + +type Updater struct { + repo name.Reference + dbDir string + skip bool + quiet bool + registryOption ftypes.RegistryOptions + once sync.Once // we need to update java-db once per run +} + +func (u *Updater) Update() error { + dbDir := u.dbDir + metac := db.NewMetadata(dbDir) + + meta, err := metac.Get() + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return xerrors.Errorf("Java DB metadata error: %w", err) + } else if u.skip { + log.Logger.Error("The first run cannot skip downloading Java DB") + return xerrors.New("'--skip-java-db-update' cannot be specified on the first run") + } + } + + if (meta.Version != SchemaVersion || meta.NextUpdate.Before(time.Now().UTC())) && !u.skip { + // Download DB + log.Logger.Infof("Java DB Repository: %s", u.repo) + log.Logger.Info("Downloading the Java DB...") + + // TODO: support remote options + var a *oci.Artifact + if a, err = oci.NewArtifact(u.repo.String(), u.quiet, u.registryOption); err != nil { + return xerrors.Errorf("oci error: %w", err) + } + if err = a.Download(context.Background(), dbDir, oci.DownloadOption{MediaType: mediaType}); err != nil { + return xerrors.Errorf("DB download error: %w", err) + } + + // Parse the newly downloaded metadata.json + meta, err = metac.Get() + if err != nil { + return xerrors.Errorf("Java DB metadata error: %w", err) + } + + // Update DownloadedAt + meta.DownloadedAt = time.Now().UTC() + if err = metac.Update(meta); err != nil { + return xerrors.Errorf("Java DB metadata update error: %w", err) + } + log.Logger.Info("The Java DB is cached for 3 days. If you want to update the database more frequently, " + + "the '--reset' flag clears the DB cache.") + } + + return nil +} + +func Init(cacheDir string, javaDBRepository name.Reference, skip, quiet bool, registryOption ftypes.RegistryOptions) { + updater = &Updater{ + repo: javaDBRepository, + dbDir: filepath.Join(cacheDir, "java-db"), + skip: skip, + quiet: quiet, + registryOption: registryOption, + } +} + +func Update() error { + if updater == nil { + return xerrors.New("Java DB client not initialized") + } + + var err error + updater.once.Do(func() { + err = updater.Update() + }) + return err +} + +type DB struct { + driver db.DB +} + +func NewClient() (*DB, error) { + if err := Update(); err != nil { + return nil, xerrors.Errorf("Java DB update failed: %s", err) + } + + dbc, err := db.New(updater.dbDir) + if err != nil { + return nil, xerrors.Errorf("Java DB open error: %w", err) + } + + return &DB{driver: dbc}, nil +} + +func (d *DB) Exists(groupID, artifactID string) (bool, error) { + index, err := d.driver.SelectIndexByArtifactIDAndGroupID(artifactID, groupID) + if err != nil { + return false, err + } + return index.ArtifactID != "", nil +} + +func (d *DB) SearchBySHA1(sha1 string) (jar.Properties, error) { + index, err := d.driver.SelectIndexBySha1(sha1) + if err != nil { + return jar.Properties{}, xerrors.Errorf("select error: %w", err) + } else if index.ArtifactID == "" { + return jar.Properties{}, xerrors.Errorf("digest %s: %w", sha1, jar.ArtifactNotFoundErr) + } + return jar.Properties{ + GroupID: index.GroupID, + ArtifactID: index.ArtifactID, + Version: index.Version, + }, nil +} + +func (d *DB) SearchByArtifactID(artifactID, version string) (string, error) { + indexes, err := d.driver.SelectIndexesByArtifactIDAndFileType(artifactID, version, types.JarType) + if err != nil { + return "", xerrors.Errorf("select error: %w", err) + } else if len(indexes) == 0 { + return "", xerrors.Errorf("artifactID %s: %w", artifactID, jar.ArtifactNotFoundErr) + } + sort.Slice(indexes, func(i, j int) bool { + return indexes[i].GroupID < indexes[j].GroupID + }) + + // Some artifacts might have the same artifactId. + // e.g. "javax.servlet:jstl" and "jstl:jstl" + groupIDs := make(map[string]int) + for _, index := range indexes { + if i, ok := groupIDs[index.GroupID]; ok { + groupIDs[index.GroupID] = i + 1 + continue + } + groupIDs[index.GroupID] = 1 + } + maxCount := 0 + var groupID string + for k, v := range groupIDs { + if v > maxCount { + maxCount = v + groupID = k + } + } + + return groupID, nil +} + +func (d *DB) Close() error { + if d == nil { + return nil + } + return d.driver.Close() +} diff --git a/pkg/k8s/commands/cluster.go b/pkg/k8s/commands/cluster.go new file mode 100644 index 000000000000..bf28f26f5d7f --- /dev/null +++ b/pkg/k8s/commands/cluster.go @@ -0,0 +1,52 @@ +package commands + +import ( + "context" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + k8sArtifacts "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" + "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" + "github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +// clusterRun runs scan on kubernetes cluster +func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) error { + if err := validateReportArguments(opts); err != nil { + return err + } + var artifacts []*k8sArtifacts.Artifact + var err error + switch opts.Format { + case types.FormatCycloneDX: + artifacts, err = trivyk8s.New(cluster, log.Logger).ListClusterBomInfo(ctx) + if err != nil { + return xerrors.Errorf("get k8s artifacts with node info error: %w", err) + } + case types.FormatJSON, types.FormatTable: + if opts.Scanners.AnyEnabled(types.MisconfigScanner) && slices.Contains(opts.Components, "infra") { + artifacts, err = trivyk8s.New(cluster, log.Logger, trivyk8s.WithExcludeOwned(opts.ExcludeOwned)).ListArtifactAndNodeInfo(ctx, + trivyk8s.WithScanJobNamespace(opts.NodeCollectorNamespace), + trivyk8s.WithIgnoreLabels(opts.ExcludeNodes), + trivyk8s.WithScanJobImageRef(opts.NodeCollectorImageRef), + trivyk8s.WithTolerations(opts.Tolerations)) + if err != nil { + return xerrors.Errorf("get k8s artifacts with node info error: %w", err) + } + } else { + artifacts, err = trivyk8s.New(cluster, log.Logger).ListArtifacts(ctx) + if err != nil { + return xerrors.Errorf("get k8s artifacts error: %w", err) + } + } + default: + return xerrors.Errorf(`unknown format %q. Use "json" or "table" or "cyclonedx"`, opts.Format) + } + + runner := newRunner(opts, cluster.GetCurrentContext()) + return runner.run(ctx, artifacts) +} diff --git a/pkg/k8s/commands/namespace.go b/pkg/k8s/commands/namespace.go new file mode 100644 index 000000000000..a748bf38dd9d --- /dev/null +++ b/pkg/k8s/commands/namespace.go @@ -0,0 +1,41 @@ +package commands + +import ( + "context" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" + "github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" +) + +// namespaceRun runs scan on kubernetes cluster +func namespaceRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) error { + if err := validateReportArguments(opts); err != nil { + return err + } + var trivyk trivyk8s.TrivyK8S + if opts.AllNamespaces { + trivyk = trivyk8s.New(cluster, log.Logger).AllNamespaces() + } else { + trivyk = trivyk8s.New(cluster, log.Logger).Namespace(getNamespace(opts, cluster.GetCurrentNamespace())) + } + + artifacts, err := trivyk.ListArtifacts(ctx) + if err != nil { + return xerrors.Errorf("get k8s artifacts error: %w", err) + } + + runner := newRunner(opts, cluster.GetCurrentContext()) + return runner.run(ctx, artifacts) +} + +func getNamespace(opts flag.Options, currentNamespace string) string { + if len(opts.K8sOptions.Namespace) > 0 { + return opts.K8sOptions.Namespace + } + + return currentNamespace +} diff --git a/pkg/k8s/commands/namespace_test.go b/pkg/k8s/commands/namespace_test.go new file mode 100644 index 000000000000..661360d7007b --- /dev/null +++ b/pkg/k8s/commands/namespace_test.go @@ -0,0 +1,47 @@ +package commands + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/flag" +) + +func Test_getNamespace(t *testing.T) { + + tests := []struct { + name string + currentNamespace string + opts flag.Options + want string + }{ + { + name: "--namespace=custom", + currentNamespace: "default", + opts: flag.Options{ + K8sOptions: flag.K8sOptions{ + Namespace: "custom", + }, + }, + want: "custom", + }, + { + name: "no namespaces passed", + currentNamespace: "default", + opts: flag.Options{ + K8sOptions: flag.K8sOptions{ + Namespace: "", + }, + }, + want: "default", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := getNamespace(test.opts, test.currentNamespace) + assert.Equal(t, test.want, got) + }) + } +} diff --git a/pkg/k8s/commands/resource.go b/pkg/k8s/commands/resource.go new file mode 100644 index 000000000000..10557e5a62f0 --- /dev/null +++ b/pkg/k8s/commands/resource.go @@ -0,0 +1,71 @@ +package commands + +import ( + "context" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" + "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" + "github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" +) + +// resourceRun runs scan on kubernetes cluster +func resourceRun(ctx context.Context, args []string, opts flag.Options, cluster k8s.Cluster) error { + kind, name, err := extractKindAndName(args) + if err != nil { + return err + } + + runner := newRunner(opts, cluster.GetCurrentContext()) + + var trivyk trivyk8s.TrivyK8S + + trivyk = trivyk8s.New(cluster, log.Logger, trivyk8s.WithExcludeOwned(opts.ExcludeOwned)) + + if opts.AllNamespaces { + trivyk = trivyk.AllNamespaces() + } else { + trivyk = trivyk.Namespace(getNamespace(opts, cluster.GetCurrentNamespace())) + } + + if name == "" { // pods or configmaps etc + if err = validateReportArguments(opts); err != nil { + return err + } + + targets, err := trivyk.Resources(kind).ListArtifacts(ctx) + if err != nil { + return err + } + + return runner.run(ctx, targets) + } + + // pod/NAME or pod NAME etc + artifact, err := trivyk.GetArtifact(ctx, kind, name) + if err != nil { + return err + } + + return runner.run(ctx, []*artifacts.Artifact{artifact}) +} + +func extractKindAndName(args []string) (string, string, error) { + switch len(args) { + case 1: + s := strings.Split(args[0], "/") + if len(s) != 2 { + return args[0], "", nil + } + + return s[0], s[1], nil + case 2: + return args[0], args[1], nil + } + + return "", "", xerrors.Errorf("can't parse arguments %v. Please run `trivy k8s` for usage.", args) +} diff --git a/pkg/k8s/commands/resource_test.go b/pkg/k8s/commands/resource_test.go new file mode 100644 index 000000000000..27ce9df5d42f --- /dev/null +++ b/pkg/k8s/commands/resource_test.go @@ -0,0 +1,61 @@ +package commands + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_extractKindAndName(t *testing.T) { + tests := []struct { + name string + args []string + expectedKind string + expectedName string + expectedError string + }{ + { + name: "one argument only", + args: []string{"deploy"}, + expectedKind: "deploy", + expectedName: "", + }, + { + name: "one argument only, multiple targets", + args: []string{"deploy,configmaps"}, + expectedKind: "deploy,configmaps", + expectedName: "", + }, + { + name: "bar separated", + args: []string{"deploy/orion"}, + expectedKind: "deploy", + expectedName: "orion", + }, + { + name: "space separated", + args: []string{"deploy", "lua"}, + expectedKind: "deploy", + expectedName: "lua", + }, + { + name: "multiple arguments separated", + args: []string{"test", "test", "test"}, + expectedError: "can't parse arguments [test test test]. Please run `trivy k8s` for usage.", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + kind, name, err := extractKindAndName(test.args) + + if len(test.expectedError) > 0 { + assert.Error(t, err, test.expectedError) + return + } + + assert.Equal(t, test.expectedKind, kind) + assert.Equal(t, test.expectedName, name) + }) + } +} diff --git a/pkg/k8s/commands/run.go b/pkg/k8s/commands/run.go new file mode 100644 index 000000000000..e9e3510f6bce --- /dev/null +++ b/pkg/k8s/commands/run.go @@ -0,0 +1,171 @@ +package commands + +import ( + "context" + "errors" + + "github.com/spf13/viper" + "golang.org/x/xerrors" + + k8sArtifacts "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" + "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" + cmd "github.com/aquasecurity/trivy/pkg/commands/artifact" + "github.com/aquasecurity/trivy/pkg/commands/operation" + cr "github.com/aquasecurity/trivy/pkg/compliance/report" + "github.com/aquasecurity/trivy/pkg/flag" + k8sRep "github.com/aquasecurity/trivy/pkg/k8s" + "github.com/aquasecurity/trivy/pkg/k8s/report" + "github.com/aquasecurity/trivy/pkg/k8s/scanner" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + clusterArtifact = "cluster" + allArtifact = "all" +) + +// Run runs a k8s scan +func Run(ctx context.Context, args []string, opts flag.Options) error { + cluster, err := k8s.GetCluster( + k8s.WithContext(opts.K8sOptions.ClusterContext), + k8s.WithKubeConfig(opts.K8sOptions.KubeConfig), + k8s.WithBurst(opts.K8sOptions.Burst), + k8s.WithQPS(opts.K8sOptions.QPS), + ) + if err != nil { + return xerrors.Errorf("failed getting k8s cluster: %w", err) + } + ctx, cancel := context.WithTimeout(ctx, opts.Timeout) + defer cancel() + + defer func() { + if errors.Is(err, context.DeadlineExceeded) { + log.Logger.Warn("Increase --timeout value") + } + }() + opts.K8sVersion = cluster.GetClusterVersion() + switch args[0] { + case clusterArtifact: + return clusterRun(ctx, opts, cluster) + case allArtifact: + if opts.Format == types.FormatCycloneDX { + return xerrors.Errorf("KBOM with CycloneDX format is not supported for all namespace scans") + } + return namespaceRun(ctx, opts, cluster) + default: // resourceArtifact + if opts.Format == types.FormatCycloneDX { + return xerrors.Errorf("KBOM with CycloneDX format is not supported for resource scans") + } + return resourceRun(ctx, args, opts, cluster) + } +} + +type runner struct { + flagOpts flag.Options + cluster string +} + +func newRunner(flagOpts flag.Options, cluster string) *runner { + return &runner{ + flagOpts, + cluster, + } +} + +func (r *runner) run(ctx context.Context, artifacts []*k8sArtifacts.Artifact) error { + runner, err := cmd.NewRunner(ctx, r.flagOpts) + if err != nil { + if errors.Is(err, cmd.SkipScan) { + return nil + } + return xerrors.Errorf("init error: %w", err) + } + defer func() { + if err := runner.Close(ctx); err != nil { + log.Logger.Errorf("failed to close runner: %s", err) + } + }() + + s := scanner.NewScanner(r.cluster, runner, r.flagOpts) + + // set scanners types by spec + if r.flagOpts.Compliance.Spec.ID != "" { + scanners, err := r.flagOpts.Compliance.Scanners() + if err != nil { + return xerrors.Errorf("scanner error: %w", err) + } + r.flagOpts.ScanOptions.Scanners = scanners + } + var rpt report.Report + rpt, err = s.Scan(ctx, artifacts) + if err != nil { + return xerrors.Errorf("k8s scan error: %w", err) + } + + output, cleanup, err := r.flagOpts.OutputWriter(ctx) + if err != nil { + return xerrors.Errorf("failed to create output file: %w", err) + } + defer cleanup() + + if r.flagOpts.Compliance.Spec.ID != "" { + var scanResults []types.Results + for _, rss := range rpt.Resources { + scanResults = append(scanResults, rss.Results) + } + complianceReport, err := cr.BuildComplianceReport(scanResults, r.flagOpts.Compliance) + if err != nil { + return xerrors.Errorf("compliance report build error: %w", err) + } + return cr.Write(ctx, complianceReport, cr.Option{ + Format: r.flagOpts.Format, + Report: r.flagOpts.ReportFormat, + Output: output, + }) + } + + if err := k8sRep.Write(ctx, rpt, report.Option{ + Format: r.flagOpts.Format, + Report: r.flagOpts.ReportFormat, + Output: output, + Severities: r.flagOpts.Severities, + Components: r.flagOpts.Components, + Scanners: r.flagOpts.ScanOptions.Scanners, + APIVersion: r.flagOpts.AppVersion, + }); err != nil { + return xerrors.Errorf("unable to write results: %w", err) + } + + operation.Exit(r.flagOpts, rpt.Failed()) + + return nil +} + +// Full-cluster scanning with '--format table' without explicit '--report all' is not allowed so that it won't mess up user's terminal. +// To show all the results, user needs to specify "--report all" explicitly +// even though the default value of "--report" is "all". +// +// e.g. +// $ trivy k8s --report all cluster +// $ trivy k8s --report all all +// +// Or they can use "--format json" with implicit "--report all". +// +// e.g. $ trivy k8s --format json cluster // All the results are shown in JSON +// +// Single resource scanning is allowed with implicit "--report all". +// +// e.g. $ trivy k8s pod myapp +func validateReportArguments(opts flag.Options) error { + if opts.ReportFormat == "all" && + !viper.IsSet("report") && + opts.Format == "table" { + + m := "All the results in the table format can mess up your terminal. Use \"--report all\" to tell Trivy to output it to your terminal anyway, or consider \"--report summary\" to show the summary output." + + return xerrors.New(m) + } + + return nil +} diff --git a/pkg/k8s/inject.go b/pkg/k8s/inject.go new file mode 100644 index 000000000000..31ffd2afffa7 --- /dev/null +++ b/pkg/k8s/inject.go @@ -0,0 +1,15 @@ +//go:build wireinject +// +build wireinject + +package k8s + +import ( + "github.com/google/wire" + + "github.com/aquasecurity/trivy/pkg/fanal/cache" +) + +func initializeScanK8s(localArtifactCache cache.LocalArtifactCache) *ScanKubernetes { + wire.Build(ScanSuperSet) + return &ScanKubernetes{} +} diff --git a/pkg/k8s/k8s.go b/pkg/k8s/k8s.go new file mode 100644 index 000000000000..91d09e0be64b --- /dev/null +++ b/pkg/k8s/k8s.go @@ -0,0 +1,39 @@ +package k8s + +import ( + "context" + + "github.com/google/wire" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/scanner" + "github.com/aquasecurity/trivy/pkg/scanner/local" + "github.com/aquasecurity/trivy/pkg/types" +) + +// ScanSuperSet binds the dependencies for k8s +var ScanSuperSet = wire.NewSet( + local.SuperSet, + wire.Bind(new(scanner.Driver), new(local.Scanner)), + NewScanKubernetes, +) + +// ScanKubernetes implements the scanner +type ScanKubernetes struct { + localScanner local.Scanner +} + +// NewScanKubernetes is the factory method for scanner +func NewScanKubernetes(s local.Scanner) *ScanKubernetes { + return &ScanKubernetes{localScanner: s} +} + +// NewKubernetesScanner is the factory method for scanner +func NewKubernetesScanner() *ScanKubernetes { + return initializeScanK8s(nil) +} + +// Scan scans k8s core components and return it findings +func (sk ScanKubernetes) Scan(ctx context.Context, target types.ScanTarget, options types.ScanOptions) (types.Results, ftypes.OS, error) { + return sk.localScanner.ScanTarget(ctx, target, options) +} diff --git a/pkg/k8s/report/cyclonedx.go b/pkg/k8s/report/cyclonedx.go new file mode 100644 index 000000000000..1d87769c29b2 --- /dev/null +++ b/pkg/k8s/report/cyclonedx.go @@ -0,0 +1,37 @@ +package report + +import ( + "context" + "io" + + cdx "github.com/CycloneDX/cyclonedx-go" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" +) + +// CycloneDXWriter implements types.Writer +type CycloneDXWriter struct { + encoder cdx.BOMEncoder + marshaler cyclonedx.Marshaler +} + +// NewCycloneDXWriter constract new CycloneDXWriter +func NewCycloneDXWriter(output io.Writer, format cdx.BOMFileFormat, appVersion string) CycloneDXWriter { + encoder := cdx.NewBOMEncoder(output, format) + encoder.SetPretty(true) + encoder.SetEscapeHTML(false) + return CycloneDXWriter{ + encoder: encoder, + marshaler: cyclonedx.NewMarshaler(appVersion), + } +} + +func (w CycloneDXWriter) Write(ctx context.Context, component *core.BOM) error { + bom, err := w.marshaler.Marshal(ctx, component) + if err != nil { + return xerrors.Errorf("CycloneDX marshal error: %w", err) + } + return w.encoder.Encode(bom) +} diff --git a/pkg/k8s/report/json.go b/pkg/k8s/report/json.go new file mode 100644 index 000000000000..12420c0704d5 --- /dev/null +++ b/pkg/k8s/report/json.go @@ -0,0 +1,40 @@ +package report + +import ( + "encoding/json" + "fmt" + "io" + + "golang.org/x/xerrors" +) + +type JSONWriter struct { + Output io.Writer + Report string +} + +// Write writes the results in JSON format +func (jw JSONWriter) Write(report Report) error { + var output []byte + var err error + + switch jw.Report { + case AllReport: + output, err = json.MarshalIndent(report, "", " ") + if err != nil { + return xerrors.Errorf("failed to write json: %w", err) + } + case SummaryReport: + output, err = json.MarshalIndent(report.consolidate(), "", " ") + if err != nil { + return xerrors.Errorf("failed to write json: %w", err) + } + default: + return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, jw.Report) + } + if _, err = fmt.Fprintln(jw.Output, string(output)); err != nil { + return xerrors.Errorf("failed to write json: %w", err) + } + + return nil +} diff --git a/pkg/k8s/report/report.go b/pkg/k8s/report/report.go new file mode 100644 index 000000000000..5de332a703bc --- /dev/null +++ b/pkg/k8s/report/report.go @@ -0,0 +1,285 @@ +package report + +import ( + "fmt" + "io" + "strings" + + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + AllReport = "all" + SummaryReport = "summary" + + workloadComponent = "workload" + infraComponent = "infra" + infraNamespace = "kube-system" +) + +type Option struct { + Format types.Format + Report string + Output io.Writer + Severities []dbTypes.Severity + ColumnHeading []string + Scanners types.Scanners + Components []string + APIVersion string +} + +// Report represents a kubernetes scan report +type Report struct { + SchemaVersion int `json:",omitempty"` + ClusterName string + Resources []Resource `json:",omitempty"` + BOM *core.BOM `json:"-"` + name string +} + +// ConsolidatedReport represents a kubernetes scan report with consolidated findings +type ConsolidatedReport struct { + SchemaVersion int `json:",omitempty"` + ClusterName string + Findings []Resource `json:",omitempty"` +} + +// Resource represents a kubernetes resource report +type Resource struct { + Namespace string `json:",omitempty"` + Kind string + Name string + Metadata types.Metadata `json:",omitempty"` + Results types.Results `json:",omitempty"` + Error string `json:",omitempty"` + + // original report + Report types.Report `json:"-"` +} + +func (r Resource) fullname() string { + return strings.ToLower(fmt.Sprintf("%s/%s/%s", r.Namespace, r.Kind, r.Name)) +} + +// Failed returns whether the k8s report includes any vulnerabilities or misconfigurations +func (r Report) Failed() bool { + for _, v := range r.Resources { + if v.Results.Failed() { + return true + } + } + return false +} + +func (r Report) consolidate() ConsolidatedReport { + consolidated := ConsolidatedReport{ + SchemaVersion: r.SchemaVersion, + ClusterName: r.ClusterName, + } + + index := make(map[string]Resource) + var vulnerabilities []Resource + for _, m := range r.Resources { + if vulnerabilitiesOrSecretResource(m) { + vulnerabilities = append(vulnerabilities, m) + } else { + index[m.fullname()] = m + } + } + + for _, v := range vulnerabilities { + key := v.fullname() + + if res, ok := index[key]; ok { + index[key] = Resource{ + Namespace: res.Namespace, + Kind: res.Kind, + Name: res.Name, + Metadata: res.Metadata, + Results: append(res.Results, v.Results...), + Error: res.Error, + } + + continue + } + + index[key] = v + } + + consolidated.Findings = maps.Values(index) + + return consolidated +} + +// Writer defines the result write operation +type Writer interface { + Write(Report) error +} + +type reports struct { + Report Report + Columns []string +} + +// SeparateMisconfigReports returns 3 reports based on scanners and components flags, +// - misconfiguration report +// - rbac report +// - infra checks report +func SeparateMisconfigReports(k8sReport Report, scanners types.Scanners, components []string) []reports { + + var workloadMisconfig, infraMisconfig, rbacAssessment, workloadVulnerabilities, infraVulnerabilities, workloadResource []Resource + for _, resource := range k8sReport.Resources { + switch { + case vulnerabilitiesOrSecretResource(resource): + if resource.Namespace == infraNamespace || nodeInfoResource(resource) { + infraVulnerabilities = append(infraVulnerabilities, nodeKind(resource)) + } else { + workloadVulnerabilities = append(workloadVulnerabilities, resource) + } + case scanners.Enabled(types.RBACScanner) && rbacResource(resource): + rbacAssessment = append(rbacAssessment, resource) + case infraResource(resource): + infraMisconfig = append(infraMisconfig, nodeKind(resource)) + case scanners.Enabled(types.MisconfigScanner) && + !rbacResource(resource) && + slices.Contains(components, workloadComponent): + workloadMisconfig = append(workloadMisconfig, resource) + } + } + + var r []reports + workloadResource = append(workloadResource, workloadVulnerabilities...) + workloadResource = append(workloadResource, workloadMisconfig...) + if shouldAddToReport(scanners, components, workloadComponent) { + workloadReport := Report{ + SchemaVersion: 0, + ClusterName: k8sReport.ClusterName, + Resources: workloadResource, + name: "Workload Assessment", + } + if slices.Contains(components, workloadComponent) { + r = append(r, reports{ + Report: workloadReport, + Columns: WorkloadColumns(), + }) + } + } + infraMisconfig = append(infraMisconfig, infraVulnerabilities...) + if shouldAddToReport(scanners, components, infraComponent) { + r = append(r, reports{ + Report: Report{ + SchemaVersion: 0, + ClusterName: k8sReport.ClusterName, + Resources: infraMisconfig, + name: "Infra Assessment", + }, + Columns: InfraColumns(), + }) + } + + if scanners.Enabled(types.RBACScanner) { + r = append(r, reports{ + Report: Report{ + SchemaVersion: 0, + ClusterName: k8sReport.ClusterName, + Resources: rbacAssessment, + name: "RBAC Assessment", + }, + Columns: RoleColumns(), + }) + } + + return r +} + +func rbacResource(misConfig Resource) bool { + return slices.Contains([]string{ + "Role", + "RoleBinding", + "ClusterRole", + "ClusterRoleBinding", + }, misConfig.Kind) +} + +func infraResource(misConfig Resource) bool { + return !rbacResource(misConfig) && (misConfig.Namespace == infraNamespace) || nodeInfoResource(misConfig) +} + +func CreateResource(artifact *artifacts.Artifact, report types.Report, err error) Resource { + r := createK8sResource(artifact, report.Results) + + r.Metadata = report.Metadata + r.Report = report + // if there was any error during the scan + if err != nil { + r.Error = err.Error() + } + + return r +} + +func nodeInfoResource(nodeInfo Resource) bool { + return nodeInfo.Kind == "NodeInfo" || nodeInfo.Kind == "NodeComponents" +} + +func createK8sResource(artifact *artifacts.Artifact, scanResults types.Results) Resource { + results := make([]types.Result, 0, len(scanResults)) + // fix target name + for _, result := range scanResults { + // if resource is a kubernetes file fix the target name, + // to avoid showing the temp file that was removed. + if result.Type == ftypes.Kubernetes { + result.Target = fmt.Sprintf("%s/%s", artifact.Kind, artifact.Name) + } + results = append(results, result) + } + + r := Resource{ + Namespace: artifact.Namespace, + Kind: artifact.Kind, + Name: artifact.Name, + Metadata: types.Metadata{}, + Results: results, + Report: types.Report{ + Results: results, + ArtifactName: artifact.Name, + }, + } + + return r +} + +func (r Report) PrintErrors() { + for _, resource := range r.Resources { + if resource.Error != "" { + log.Logger.Errorf("Error during vulnerabilities or misconfiguration scan: %s", resource.Error) + } + } +} + +func shouldAddToReport(scanners types.Scanners, components []string, componentType string) bool { + return scanners.AnyEnabled( + types.MisconfigScanner, + types.VulnerabilityScanner, + types.SecretScanner) && + slices.Contains(components, componentType) +} + +func vulnerabilitiesOrSecretResource(resource Resource) bool { + return len(resource.Results) > 0 && (len(resource.Results[0].Vulnerabilities) > 0 || len(resource.Results[0].Secrets) > 0) +} + +func nodeKind(resource Resource) Resource { + if nodeInfoResource(resource) { + resource.Kind = "Node" + } + return resource +} diff --git a/pkg/k8s/report/report_test.go b/pkg/k8s/report/report_test.go new file mode 100644 index 000000000000..6d14b52e12a9 --- /dev/null +++ b/pkg/k8s/report/report_test.go @@ -0,0 +1,611 @@ +package report + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + deployOrionWithMisconfigs = Resource{ + Namespace: "default", + Kind: "Deploy", + Name: "orion", + Metadata: types.Metadata{ + RepoTags: []string{ + "alpine:3.14", + }, + RepoDigests: []string{ + "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + }, + }, + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "ID100", + Status: types.MisconfStatusFailure, + Severity: "LOW", + }, + { + ID: "ID101", + Status: types.MisconfStatusFailure, + Severity: "MEDIUM", + }, + { + ID: "ID102", + Status: types.MisconfStatusFailure, + Severity: "HIGH", + }, + { + ID: "ID103", + Status: types.MisconfStatusFailure, + Severity: "CRITICAL", + }, + { + ID: "ID104", + Status: types.MisconfStatusFailure, + Severity: "UNKNOWN", + }, + { + ID: "ID105", + Status: types.MisconfStatusFailure, + Severity: "LOW", + }, + { + ID: "ID106", + Status: types.MisconfStatusFailure, + Severity: "HIGH", + }, + }, + }, + }, + } + + deployOrionWithVulns = Resource{ + Namespace: "default", + Kind: "Deploy", + Name: "orion", + Metadata: types.Metadata{ + RepoTags: []string{ + "alpine:3.14", + }, + RepoDigests: []string{ + "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + }, + }, + Results: types.Results{ + { + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-1111", + Vulnerability: dbTypes.Vulnerability{Severity: "LOW"}, + }, + { + VulnerabilityID: "CVE-2022-2222", + Vulnerability: dbTypes.Vulnerability{Severity: "MEDIUM"}, + }, + { + VulnerabilityID: "CVE-2022-3333", + Vulnerability: dbTypes.Vulnerability{Severity: "HIGH"}, + }, + { + VulnerabilityID: "CVE-2022-4444", + Vulnerability: dbTypes.Vulnerability{Severity: "CRITICAL"}, + }, + { + VulnerabilityID: "CVE-2022-5555", + Vulnerability: dbTypes.Vulnerability{Severity: "UNKNOWN"}, + }, + { + VulnerabilityID: "CVE-2022-6666", + Vulnerability: dbTypes.Vulnerability{Severity: "CRITICAL"}, + }, + { + VulnerabilityID: "CVE-2022-7777", + Vulnerability: dbTypes.Vulnerability{Severity: "MEDIUM"}, + }, + }, + }, + }, + } + + deployOrionWithBothVulnsAndMisconfigs = Resource{ + Namespace: "default", + Kind: "Deploy", + Name: "orion", + Metadata: types.Metadata{ + RepoTags: []string{ + "alpine:3.14", + }, + RepoDigests: []string{ + "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + }, + }, + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "ID100", + Status: types.MisconfStatusFailure, + Severity: "LOW", + }, + { + ID: "ID101", + Status: types.MisconfStatusFailure, + Severity: "MEDIUM", + }, + { + ID: "ID102", + Status: types.MisconfStatusFailure, + Severity: "HIGH", + }, + { + ID: "ID103", + Status: types.MisconfStatusFailure, + Severity: "CRITICAL", + }, + { + ID: "ID104", + Status: types.MisconfStatusFailure, + Severity: "UNKNOWN", + }, + { + ID: "ID105", + Status: types.MisconfStatusFailure, + Severity: "LOW", + }, + { + ID: "ID106", + Status: types.MisconfStatusFailure, + Severity: "HIGH", + }, + }, + }, + { + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-1111", + Vulnerability: dbTypes.Vulnerability{Severity: "LOW"}, + }, + { + VulnerabilityID: "CVE-2022-2222", + Vulnerability: dbTypes.Vulnerability{Severity: "MEDIUM"}, + }, + { + VulnerabilityID: "CVE-2022-3333", + Vulnerability: dbTypes.Vulnerability{Severity: "HIGH"}, + }, + { + VulnerabilityID: "CVE-2022-4444", + Vulnerability: dbTypes.Vulnerability{Severity: "CRITICAL"}, + }, + { + VulnerabilityID: "CVE-2022-5555", + Vulnerability: dbTypes.Vulnerability{Severity: "UNKNOWN"}, + }, + { + VulnerabilityID: "CVE-2022-6666", + Vulnerability: dbTypes.Vulnerability{Severity: "CRITICAL"}, + }, + { + VulnerabilityID: "CVE-2022-7777", + Vulnerability: dbTypes.Vulnerability{Severity: "MEDIUM"}, + }, + }, + }, + }, + } + + cronjobHelloWithVulns = Resource{ + Namespace: "default", + Kind: "Cronjob", + Name: "hello", + Metadata: types.Metadata{ + RepoTags: []string{ + "alpine:3.14", + }, + RepoDigests: []string{ + "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + }, + }, + Results: types.Results{ + {Vulnerabilities: []types.DetectedVulnerability{{VulnerabilityID: "CVE-2020-9999"}}}, + }, + } + + podPrometheusWithMisconfigs = Resource{ + Namespace: "default", + Kind: "Pod", + Name: "prometheus", + Metadata: types.Metadata{ + RepoTags: []string{ + "alpine:3.14", + }, + RepoDigests: []string{ + "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + }, + }, + Results: types.Results{ + {Misconfigurations: []types.DetectedMisconfiguration{{ID: "ID100"}}}, + }, + } + + roleWithMisconfig = Resource{ + Namespace: "default", + Kind: "Role", + Name: "system::leader-locking-kube-controller-manager", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "ID100", + Status: types.MisconfStatusFailure, + Severity: "MEDIUM", + }, + }, + }, + }, + } + + deployLuaWithSecrets = Resource{ + Namespace: "default", + Kind: "Deploy", + Name: "lua", + Results: types.Results{ + { + Secrets: []types.DetectedSecret{ + { + RuleID: "secret1", + Severity: "CRITICAL", + }, + { + RuleID: "secret2", + Severity: "MEDIUM", + }, + }, + }, + }, + } + + apiseverPodWithMisconfigAndInfra = Resource{ + Namespace: "kube-system", + Kind: "Pod", + Name: "kube-apiserver", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "KSV-ID100", + Status: types.MisconfStatusFailure, + Severity: "LOW", + }, + { + ID: "KSV-ID101", + Status: types.MisconfStatusFailure, + Severity: "MEDIUM", + }, + { + ID: "KSV-ID102", + Status: types.MisconfStatusFailure, + Severity: "HIGH", + }, + + { + ID: "KCV-ID100", + Status: types.MisconfStatusFailure, + Severity: "LOW", + }, + { + ID: "KCV-ID101", + Status: types.MisconfStatusFailure, + Severity: "MEDIUM", + }, + }, + }, + }, + } +) + +func TestReport_consolidate(t *testing.T) { + tests := []struct { + name string + report Report + expectedFindings map[string]Resource + }{ + { + name: "report with both misconfigs and vulnerabilities", + report: Report{ + Resources: []Resource{ + deployOrionWithVulns, + cronjobHelloWithVulns, + deployOrionWithMisconfigs, + podPrometheusWithMisconfigs, + }, + }, + expectedFindings: map[string]Resource{ + "default/deploy/orion": deployOrionWithBothVulnsAndMisconfigs, + "default/cronjob/hello": cronjobHelloWithVulns, + "default/pod/prometheus": podPrometheusWithMisconfigs, + }, + }, + { + name: "report with only misconfigurations", + report: Report{ + Resources: []Resource{ + deployOrionWithMisconfigs, + podPrometheusWithMisconfigs, + }, + }, + expectedFindings: map[string]Resource{ + "default/deploy/orion": deployOrionWithMisconfigs, + "default/pod/prometheus": podPrometheusWithMisconfigs, + }, + }, + { + name: "report with only vulnerabilities", + report: Report{ + Resources: []Resource{ + deployOrionWithVulns, + cronjobHelloWithVulns, + }, + }, + expectedFindings: map[string]Resource{ + "default/deploy/orion": deployOrionWithVulns, + "default/cronjob/hello": cronjobHelloWithVulns, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + consolidateReport := tt.report.consolidate() + for _, f := range consolidateReport.Findings { + key := f.fullname() + + expected, found := tt.expectedFindings[key] + if !found { + t.Errorf("key not found: %s", key) + } + + assert.Equal(t, expected, f) + } + }) + } +} + +func TestResource_fullname(t *testing.T) { + tests := []struct { + expected string + resource Resource + }{ + { + "default/deploy/orion", + deployOrionWithBothVulnsAndMisconfigs, + }, + { + "default/deploy/orion", + deployOrionWithMisconfigs, + }, + { + "default/cronjob/hello", + cronjobHelloWithVulns, + }, + { + "default/pod/prometheus", + podPrometheusWithMisconfigs, + }, + } + + for _, tt := range tests { + t.Run(tt.expected, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.resource.fullname()) + }) + } +} + +func TestResourceFailed(t *testing.T) { + tests := []struct { + name string + report Report + expected bool + }{ + { + name: "report with both misconfigs and vulnerabilities", + report: Report{ + Resources: []Resource{ + deployOrionWithVulns, + cronjobHelloWithVulns, + deployOrionWithMisconfigs, + podPrometheusWithMisconfigs, + }, + }, + expected: true, + }, + { + name: "report with only misconfigurations", + report: Report{ + Resources: []Resource{ + deployOrionWithMisconfigs, + podPrometheusWithMisconfigs, + }, + }, + expected: true, + }, + { + name: "report with only vulnerabilities", + report: Report{ + Resources: []Resource{ + deployOrionWithVulns, + cronjobHelloWithVulns, + }, + }, + expected: true, + }, + { + name: "report without vulnerabilities and misconfigurations", + report: Report{}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.report.Failed()) + }) + } +} + +func Test_rbacResource(t *testing.T) { + tests := []struct { + name string + misConfig Resource + want bool + }{ + { + name: "rbac Role resources", + misConfig: Resource{Kind: "Role"}, + want: true, + }, + { + name: "rbac ClusterRole resources", + misConfig: Resource{Kind: "ClusterRole"}, + want: true, + }, + { + name: "rbac RoleBinding resources", + misConfig: Resource{Kind: "RoleBinding"}, + want: true, + }, + { + name: "rbac ClusterRoleBinding resources", + misConfig: Resource{Kind: "ClusterRoleBinding"}, + want: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := rbacResource(test.misConfig) + assert.Equal(t, test.want, got) + }) + } +} + +func Test_separateMisconfigReports(t *testing.T) { + k8sReport := Report{ + Resources: []Resource{ + {Kind: "Role"}, + {Kind: "Deployment"}, + {Kind: "StatefulSet"}, + { + Kind: "Pod", + Namespace: "kube-system", + Results: []types.Result{ + {Misconfigurations: []types.DetectedMisconfiguration{{ID: "KCV-0001"}}}, + {Misconfigurations: []types.DetectedMisconfiguration{{ID: "KSV-0001"}}}, + }, + }, + }, + } + + tests := []struct { + name string + k8sReport Report + scanners types.Scanners + components []string + expectedReports []Report + }{ + { + name: "Config, Rbac, and Infra Reports", + k8sReport: k8sReport, + scanners: types.Scanners{ + types.MisconfigScanner, + types.RBACScanner, + }, + components: []string{ + workloadComponent, + infraComponent, + }, + expectedReports: []Report{ + // the order matter for the test + { + Resources: []Resource{ + {Kind: "Deployment"}, + {Kind: "StatefulSet"}, + }, + }, + {Resources: []Resource{{Kind: "Pod"}}}, + {Resources: []Resource{{Kind: "Role"}}}, + }, + }, + { + name: "Config and Infra for the same resource", + k8sReport: k8sReport, + scanners: types.Scanners{types.MisconfigScanner}, + components: []string{ + workloadComponent, + infraComponent, + }, + expectedReports: []Report{ + // the order matter for the test + { + Resources: []Resource{ + {Kind: "Deployment"}, + {Kind: "StatefulSet"}, + }, + }, + {Resources: []Resource{{Kind: "Pod"}}}, + }, + }, + { + name: "Role Report Only", + k8sReport: k8sReport, + scanners: types.Scanners{types.RBACScanner}, + expectedReports: []Report{ + {Resources: []Resource{{Kind: "Role"}}}, + }, + }, + { + name: "Config Report Only", + k8sReport: k8sReport, + scanners: types.Scanners{types.MisconfigScanner}, + components: []string{workloadComponent}, + expectedReports: []Report{ + { + Resources: []Resource{ + {Kind: "Deployment"}, + {Kind: "StatefulSet"}, + }, + }, + }, + }, + { + name: "Infra Report Only", + k8sReport: k8sReport, + scanners: types.Scanners{types.MisconfigScanner}, + components: []string{infraComponent}, + expectedReports: []Report{ + {Resources: []Resource{{Kind: "Pod"}}}, + }, + }, + + // TODO: add vuln only + // TODO: add secret only + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reports := SeparateMisconfigReports(tt.k8sReport, tt.scanners, tt.components) + assert.Equal(t, len(tt.expectedReports), len(reports)) + + for i := range reports { + assert.Equal(t, len(tt.expectedReports[i].Resources), len(reports[i].Report.Resources)) + for j, m := range tt.expectedReports[i].Resources { + assert.Equal(t, m.Kind, reports[i].Report.Resources[j].Kind) + } + } + }) + } +} diff --git a/pkg/k8s/report/summary.go b/pkg/k8s/report/summary.go new file mode 100644 index 000000000000..f35a1b3f6624 --- /dev/null +++ b/pkg/k8s/report/summary.go @@ -0,0 +1,215 @@ +package report + +import ( + "fmt" + "io" + "sort" + "strconv" + "strings" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/table" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + pkgReport "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" +) + +type SummaryWriter struct { + Output io.Writer + Severities []string + SeverityHeadings []string + ColumnsHeading []string +} + +func NewSummaryWriter(output io.Writer, requiredSevs []dbTypes.Severity, columnHeading []string) SummaryWriter { + var severities []string + var severityHeadings []string + severities, severityHeadings = getRequiredSeverities(requiredSevs) + return SummaryWriter{ + Output: output, + Severities: severities, + SeverityHeadings: severityHeadings, + ColumnsHeading: columnHeading, + } +} + +func ColumnHeading(scanners types.Scanners, components, availableColumns []string) []string { + columns := []string{ + NamespaceColumn, + ResourceColumn, + } + securityOptions := make(map[string]interface{}, 0) + // maintain column order (vuln,config,secret) + for _, check := range scanners { + switch check { + case types.VulnerabilityScanner: + securityOptions[VulnerabilitiesColumn] = nil + case types.MisconfigScanner: + if slices.Contains(components, workloadComponent) { + securityOptions[MisconfigurationsColumn] = nil + } + if slices.Contains(components, infraComponent) { + securityOptions[MisconfigurationsColumn] = nil + } + case types.SecretScanner: + securityOptions[SecretsColumn] = nil + case types.RBACScanner: + securityOptions[RbacAssessmentColumn] = nil + } + } + for _, col := range availableColumns { + if _, ok := securityOptions[col]; ok { + columns = append(columns, col) + } + } + return columns +} + +// Write writes the results in a summarized table format +func (s SummaryWriter) Write(report Report) error { + // no report column to print + if len(s.ColumnsHeading) == 2 { + return nil + } + consolidated := report.consolidate() + + if _, err := fmt.Fprintln(s.Output); err != nil { + return xerrors.Errorf("failed to write summary report: %w", err) + } + + if _, err := fmt.Fprintln(s.Output, report.name); err != nil { + return xerrors.Errorf("failed to write summary report title: %w", err) + } + + t := table.New(s.Output) + t.SetRowLines(false) + configureHeader(s, t, s.ColumnsHeading) + + sort.Slice(consolidated.Findings, func(i, j int) bool { + return consolidated.Findings[i].Namespace > consolidated.Findings[j].Namespace + }) + + for _, finding := range consolidated.Findings { + if !finding.Results.Failed() { + continue + } + vCount, mCount, sCount := accumulateSeverityCounts(finding) + name := fmt.Sprintf("%s/%s", finding.Kind, finding.Name) + rowParts := []string{ + finding.Namespace, + name, + } + + if slices.Contains(s.ColumnsHeading, VulnerabilitiesColumn) { + rowParts = append(rowParts, s.generateSummary(vCount)...) + } + + if slices.Contains(s.ColumnsHeading, MisconfigurationsColumn) || + slices.Contains(s.ColumnsHeading, RbacAssessmentColumn) { + rowParts = append(rowParts, s.generateSummary(mCount)...) + } + + if slices.Contains(s.ColumnsHeading, SecretsColumn) { + rowParts = append(rowParts, s.generateSummary(sCount)...) + } + + t.AddRow(rowParts...) + } + + t.Render() + + keyParts := []string{"Severities:"} + for _, s := range s.Severities { + keyParts = append(keyParts, fmt.Sprintf("%s=%s", s[:1], pkgReport.ColorizeSeverity(s, s))) + } + + _, _ = fmt.Fprintln(s.Output, strings.Join(keyParts, " ")) + _, _ = fmt.Fprintln(s.Output) + return nil +} + +func (s SummaryWriter) generateSummary(sevCount map[string]int) []string { + var parts []string + + for _, sev := range s.Severities { + if count, ok := sevCount[sev]; ok { + parts = append(parts, pkgReport.ColorizeSeverity(strconv.Itoa(count), sev)) + } else { + parts = append(parts, " ") + } + } + + return parts +} + +func getRequiredSeverities(requiredSevs []dbTypes.Severity) ([]string, []string) { + requiredSevOrder := []dbTypes.Severity{ + dbTypes.SeverityCritical, + dbTypes.SeverityHigh, + dbTypes.SeverityMedium, + dbTypes.SeverityLow, + dbTypes.SeverityUnknown, + } + var severities []string + var severityHeadings []string + for _, sev := range requiredSevOrder { + for _, p := range requiredSevs { + if p == sev { + severities = append(severities, sev.String()) + severityHeadings = append(severityHeadings, strings.ToUpper(sev.String()[:1])) + continue + } + } + } + return severities, severityHeadings +} + +func accumulateSeverityCounts(finding Resource) (map[string]int, map[string]int, map[string]int) { + vCount := make(map[string]int) + mCount := make(map[string]int) + sCount := make(map[string]int) + for _, r := range finding.Results { + for _, rv := range r.Vulnerabilities { + vCount[rv.Severity]++ + } + for _, rv := range r.Misconfigurations { + mCount[rv.Severity]++ + } + for _, rv := range r.Secrets { + sCount[rv.Severity]++ + } + } + return vCount, mCount, sCount +} + +func configureHeader(s SummaryWriter, t *table.Table, columnHeading []string) { + sevCount := len(s.Severities) + if len(columnHeading) > 2 { + headerRow := []string{ + columnHeading[0], + columnHeading[1], + } + // vulnerabilities headings + count := len(columnHeading) - len(headerRow) + colSpan := []int{ + 1, + 1, + } + headerAlignment := []table.Alignment{ + table.AlignLeft, + table.AlignLeft, + } + for i := 0; i < count; i++ { + headerRow = append(headerRow, s.SeverityHeadings...) + colSpan = append(colSpan, sevCount) + headerAlignment = append(headerAlignment, table.AlignCenter) + } + t.SetHeaders(columnHeading...) + t.AddHeaders(headerRow...) + t.SetAlignment(headerAlignment...) + t.SetAutoMergeHeaders(true) + t.SetHeaderColSpans(0, colSpan...) + } +} diff --git a/pkg/k8s/report/summary_test.go b/pkg/k8s/report/summary_test.go new file mode 100644 index 000000000000..8744db1c6233 --- /dev/null +++ b/pkg/k8s/report/summary_test.go @@ -0,0 +1,116 @@ +package report + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestReport_ColumnHeading(t *testing.T) { + allScanners := types.Scanners{ + types.VulnerabilityScanner, + types.MisconfigScanner, + types.SecretScanner, + types.RBACScanner, + } + + tests := []struct { + name string + scanners types.Scanners + components []string + availableColumns []string + want []string + }{ + { + name: "filter workload columns", + scanners: allScanners, + availableColumns: WorkloadColumns(), + components: []string{ + workloadComponent, + infraComponent, + }, + want: []string{ + NamespaceColumn, + ResourceColumn, + VulnerabilitiesColumn, + MisconfigurationsColumn, + SecretsColumn, + }, + }, + { + name: "filter rbac columns", + scanners: allScanners, + components: []string{}, + availableColumns: RoleColumns(), + want: []string{ + NamespaceColumn, + ResourceColumn, + RbacAssessmentColumn, + }, + }, + { + name: "filter infra columns", + scanners: allScanners, + components: []string{ + workloadComponent, + infraComponent, + }, + availableColumns: InfraColumns(), + want: []string{ + NamespaceColumn, + ResourceColumn, + VulnerabilitiesColumn, + MisconfigurationsColumn, + SecretsColumn, + }, + }, + { + name: "config column only", + scanners: types.Scanners{types.MisconfigScanner}, + components: []string{ + workloadComponent, + infraComponent, + }, + availableColumns: WorkloadColumns(), + want: []string{ + NamespaceColumn, + ResourceColumn, + MisconfigurationsColumn, + }, + }, + { + name: "secret column only", + scanners: types.Scanners{types.SecretScanner}, + components: []string{}, + availableColumns: WorkloadColumns(), + want: []string{ + NamespaceColumn, + ResourceColumn, + SecretsColumn, + }, + }, + { + name: "vuln column only", + scanners: types.Scanners{types.VulnerabilityScanner}, + components: []string{}, + availableColumns: WorkloadColumns(), + want: []string{ + NamespaceColumn, + ResourceColumn, + VulnerabilitiesColumn, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + column := ColumnHeading(tt.scanners, tt.components, tt.availableColumns) + if !assert.Equal(t, column, tt.want) { + t.Error(fmt.Errorf("TestReport_ColumnHeading want %v got %v", tt.want, column)) + } + }) + } +} diff --git a/pkg/k8s/report/table.go b/pkg/k8s/report/table.go new file mode 100644 index 000000000000..13dbe026912a --- /dev/null +++ b/pkg/k8s/report/table.go @@ -0,0 +1,86 @@ +package report + +import ( + "context" + "fmt" + "io" + "strings" + + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + pkgReport "github.com/aquasecurity/trivy/pkg/report/table" +) + +type TableWriter struct { + Report string + Output io.Writer + Severities []dbTypes.Severity + ColumnHeading []string +} + +const ( + NamespaceColumn = "Namespace" + ResourceColumn = "Resource" + VulnerabilitiesColumn = "Vulnerabilities" + MisconfigurationsColumn = "Misconfigurations" + SecretsColumn = "Secrets" + RbacAssessmentColumn = "RBAC Assessment" +) + +func WorkloadColumns() []string { + return []string{ + VulnerabilitiesColumn, + MisconfigurationsColumn, + SecretsColumn, + } +} + +func RoleColumns() []string { + return []string{RbacAssessmentColumn} +} + +func InfraColumns() []string { + return []string{ + VulnerabilitiesColumn, + MisconfigurationsColumn, + SecretsColumn, + } +} + +func (tw TableWriter) Write(ctx context.Context, report Report) error { + switch tw.Report { + case AllReport: + t := pkgReport.Writer{ + Output: tw.Output, + Severities: tw.Severities, + } + for i, r := range report.Resources { + if r.Report.Results.Failed() { + updateTargetContext(&report.Resources[i]) + err := t.Write(ctx, r.Report) + if err != nil { + return err + } + } + } + case SummaryReport: + writer := NewSummaryWriter(tw.Output, tw.Severities, tw.ColumnHeading) + return writer.Write(report) + default: + return xerrors.Errorf(`report %q not supported. Use "summary" or "all"`, tw.Report) + } + + return nil +} + +// updateTargetContext add context namespace, kind and name to the target +func updateTargetContext(r *Resource) { + targetName := fmt.Sprintf("namespace: %s, %s: %s", r.Namespace, strings.ToLower(r.Kind), r.Name) + if r.Kind == "NodeComponents" || r.Kind == "NodeInfo" { + targetName = fmt.Sprintf("node: %s", r.Name) + } + for i := range r.Report.Results { + r.Report.Results[i].Target = targetName + } +} diff --git a/pkg/k8s/scanner/io.go b/pkg/k8s/scanner/io.go new file mode 100644 index 000000000000..09709db6441e --- /dev/null +++ b/pkg/k8s/scanner/io.go @@ -0,0 +1,51 @@ +package scanner + +import ( + "fmt" + "os" + "regexp" + "runtime" + + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" + "github.com/aquasecurity/trivy/pkg/log" +) + +var r = regexp.MustCompile("\\\\|/|:|\\*|\\?|<|>") + +func createTempFile(artifact *artifacts.Artifact) (string, error) { + filename := fmt.Sprintf("%s-%s-%s-*.yaml", artifact.Namespace, artifact.Kind, artifact.Name) + + if runtime.GOOS == "windows" { + // removes characters not permitted in file/directory names on Windows + filename = filenameWindowsFriendly(filename) + } + file, err := os.CreateTemp("", filename) + if err != nil { + return "", xerrors.Errorf("creating tmp file error: %w", err) + } + defer func() { + if err := file.Close(); err != nil { + log.Logger.Errorf("failed to close temp file %s: %s:", file.Name(), err) + } + }() + + if err := yaml.NewEncoder(file).Encode(artifact.RawResource); err != nil { + removeFile(filename) + return "", xerrors.Errorf("marshaling resource error: %w", err) + } + + return file.Name(), nil +} + +func removeFile(filename string) { + if err := os.Remove(filename); err != nil { + log.Logger.Errorf("failed to remove temp file %s: %s:", filename, err) + } +} + +func filenameWindowsFriendly(name string) string { + return r.ReplaceAllString(name, "_") +} diff --git a/pkg/k8s/scanner/io_test.go b/pkg/k8s/scanner/io_test.go new file mode 100644 index 000000000000..7587d1bb8282 --- /dev/null +++ b/pkg/k8s/scanner/io_test.go @@ -0,0 +1,34 @@ +package scanner + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_FilenameWindowsFriendly(t *testing.T) { + + tests := []struct { + name string + fileName string + want string + }{ + { + name: "name with invalid char - colon", + fileName: `kube-system-Role-system:controller:bootstrap-signer-2934213283.yaml`, + want: `kube-system-Role-system_controller_bootstrap-signer-2934213283.yaml`, + }, + { + name: "name with no invalid chars", + fileName: `kube-system-Role-system-controller-bootstrap-signer-2934213283.yaml`, + want: `kube-system-Role-system-controller-bootstrap-signer-2934213283.yaml`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := filenameWindowsFriendly(test.fileName) + assert.Equal(t, test.want, got) + }) + } +} diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go new file mode 100644 index 000000000000..55fe4c1e9386 --- /dev/null +++ b/pkg/k8s/scanner/scanner.go @@ -0,0 +1,642 @@ +package scanner + +import ( + "bytes" + "context" + "fmt" + "sort" + "strings" + + ms "github.com/mitchellh/mapstructure" + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/go-version/pkg/version" + "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" + "github.com/aquasecurity/trivy-kubernetes/pkg/bom" + cmd "github.com/aquasecurity/trivy/pkg/commands/artifact" + "github.com/aquasecurity/trivy/pkg/digest" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/k8s" + "github.com/aquasecurity/trivy/pkg/k8s/report" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/parallel" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" + "github.com/aquasecurity/trivy/pkg/scanner/local" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + k8sCoreComponentNamespace = cyclonedx.Namespace + "resource:" + k8sComponentType = "Type" + k8sComponentName = "Name" + k8sComponentNode = "node" +) + +type Scanner struct { + cluster string + runner cmd.Runner + opts flag.Options +} + +func NewScanner(cluster string, runner cmd.Runner, opts flag.Options) *Scanner { + return &Scanner{ + cluster, + runner, + opts, + } +} + +func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) (report.Report, error) { + // disable logs before scanning + err := log.InitLogger(s.opts.Debug, true) + if err != nil { + return report.Report{}, xerrors.Errorf("logger error: %w", err) + } + + // enable log, this is done in a defer function, + // to enable logs even when the function returns earlier + // due to an error + defer func() { + err = log.InitLogger(s.opts.Debug, false) + if err != nil { + // we use log.Fatal here because the error was to enable the logger + log.Fatal(xerrors.Errorf("can't enable logger error: %w", err)) + } + }() + + if s.opts.Format == types.FormatCycloneDX { + kbom, err := s.clusterInfoToReportResources(artifactsData) + if err != nil { + return report.Report{}, err + } + return report.Report{ + SchemaVersion: 0, + BOM: kbom, + }, nil + } + var resourceArtifacts []*artifacts.Artifact + var k8sCoreArtifacts []*artifacts.Artifact + for _, artifact := range artifactsData { + if strings.HasSuffix(artifact.Kind, "Components") || strings.HasSuffix(artifact.Kind, "Cluster") { + k8sCoreArtifacts = append(k8sCoreArtifacts, artifact) + continue + } + resourceArtifacts = append(resourceArtifacts, artifact) + } + + var resources []report.Resource + + type scanResult struct { + vulns []report.Resource + misconfig report.Resource + } + + onItem := func(ctx context.Context, artifact *artifacts.Artifact) (scanResult, error) { + scanResults := scanResult{} + if s.opts.Scanners.AnyEnabled(types.VulnerabilityScanner, types.SecretScanner) { + opts := s.opts + opts.Credentials = make([]ftypes.Credential, len(s.opts.Credentials)) + copy(opts.Credentials, s.opts.Credentials) + // add image private registry credential auto detected from workload imagePullsecret / serviceAccount + if len(artifact.Credentials) > 0 { + for _, cred := range artifact.Credentials { + opts.RegistryOptions.Credentials = append(opts.RegistryOptions.Credentials, + ftypes.Credential{ + Username: cred.Username, + Password: cred.Password, + }, + ) + } + } + vulns, err := s.scanVulns(ctx, artifact, opts) + if err != nil { + return scanResult{}, xerrors.Errorf("scanning vulnerabilities error: %w", err) + } + scanResults.vulns = vulns + } + if local.ShouldScanMisconfigOrRbac(s.opts.Scanners) { + misconfig, err := s.scanMisconfigs(ctx, artifact) + if err != nil { + return scanResult{}, xerrors.Errorf("scanning misconfigurations error: %w", err) + } + scanResults.misconfig = misconfig + } + return scanResults, nil + } + + onResult := func(result scanResult) error { + resources = append(resources, result.vulns...) + // don't add empty misconfig results to resources slice to avoid an empty resource + if result.misconfig.Results != nil { + resources = append(resources, result.misconfig) + } + return nil + } + + p := parallel.NewPipeline(s.opts.Parallel, !s.opts.Quiet, resourceArtifacts, onItem, onResult) + err = p.Do(ctx) + if err != nil { + return report.Report{}, err + } + if s.opts.Scanners.AnyEnabled(types.VulnerabilityScanner) { + k8sResource, err := s.scanK8sVulns(ctx, k8sCoreArtifacts) + if err != nil { + return report.Report{}, err + } + resources = append(resources, k8sResource...) + } + return report.Report{ + SchemaVersion: 0, + ClusterName: s.cluster, + Resources: resources, + }, nil + +} + +func (s *Scanner) scanVulns(ctx context.Context, artifact *artifacts.Artifact, opts flag.Options) ([]report.Resource, error) { + resources := make([]report.Resource, 0, len(artifact.Images)) + + for _, image := range artifact.Images { + + opts.Target = image + + imageReport, err := s.runner.ScanImage(ctx, opts) + + if err != nil { + log.Logger.Warnf("failed to scan image %s: %s", image, err) + resources = append(resources, report.CreateResource(artifact, imageReport, err)) + continue + } + + resource, err := s.filter(ctx, imageReport, artifact) + if err != nil { + return nil, xerrors.Errorf("filter error: %w", err) + } + + resources = append(resources, resource) + } + + return resources, nil +} + +func (s *Scanner) scanMisconfigs(ctx context.Context, artifact *artifacts.Artifact) (report.Resource, error) { + configFile, err := createTempFile(artifact) + if err != nil { + return report.Resource{}, xerrors.Errorf("scan error: %w", err) + } + + s.opts.Target = configFile + + configReport, err := s.runner.ScanFilesystem(ctx, s.opts) + // remove config file after scanning + removeFile(configFile) + if err != nil { + log.Logger.Debugf("failed to scan config %s/%s: %s", artifact.Kind, artifact.Name, err) + return report.CreateResource(artifact, configReport, err), err + } + + return s.filter(ctx, configReport, artifact) +} +func (s *Scanner) filter(ctx context.Context, r types.Report, artifact *artifacts.Artifact) (report.Resource, error) { + var err error + r, err = s.runner.Filter(ctx, s.opts, r) + if err != nil { + return report.Resource{}, xerrors.Errorf("filter error: %w", err) + } + return report.CreateResource(artifact, r, nil), nil +} + +const ( + oci = "oci" + kubelet = "k8s.io/kubelet" + controlPlaneComponents = "ControlPlaneComponents" + clusterInfo = "Cluster" + nodeComponents = "NodeComponents" + nodeCoreComponents = "node-core-components" +) + +func (s *Scanner) scanK8sVulns(ctx context.Context, artifactsData []*artifacts.Artifact) ([]report.Resource, error) { + var resources []report.Resource + var nodeName string + if nodeName = s.findNodeName(artifactsData); nodeName == "" { + return resources, nil + } + + k8sScanner := k8s.NewKubernetesScanner() + scanOptions := types.ScanOptions{ + Scanners: s.opts.Scanners, + VulnType: s.opts.VulnType, + } + for _, artifact := range artifactsData { + switch artifact.Kind { + case controlPlaneComponents: + var comp bom.Component + err := ms.Decode(artifact.RawResource, &comp) + if err != nil { + return nil, err + } + + lang := k8sNamespace(comp.Version, nodeName) + results, _, err := k8sScanner.Scan(ctx, types.ScanTarget{ + Applications: []ftypes.Application{ + { + Type: ftypes.LangType(lang), + FilePath: artifact.Name, + Libraries: []ftypes.Package{ + { + Name: comp.Name, + Version: comp.Version, + }, + }, + }, + }, + }, scanOptions) + if err != nil { + return nil, err + } + if results != nil { + resource, err := s.filter(ctx, types.Report{ + Results: results, + ArtifactName: artifact.Name, + }, artifact) + if err != nil { + return nil, err + } + resources = append(resources, resource) + } + case nodeComponents: + var nf bom.NodeInfo + err := ms.Decode(artifact.RawResource, &nf) + if err != nil { + return nil, err + } + kubeletVersion := sanitizedVersion(nf.KubeletVersion) + lang := k8sNamespace(kubeletVersion, nodeName) + runtimeName, runtimeVersion := runtimeNameVersion(nf.ContainerRuntimeVersion) + results, _, err := k8sScanner.Scan(ctx, types.ScanTarget{ + Applications: []ftypes.Application{ + { + Type: ftypes.LangType(lang), + FilePath: artifact.Name, + Libraries: []ftypes.Package{ + { + Name: kubelet, + Version: kubeletVersion, + }, + }, + }, + { + Type: ftypes.GoBinary, + FilePath: artifact.Name, + Libraries: []ftypes.Package{ + { + Name: runtimeName, + Version: runtimeVersion, + }, + }, + }, + }, + }, scanOptions) + if err != nil { + return nil, err + } + if results != nil { + resource, err := s.filter(ctx, types.Report{ + Results: results, + ArtifactName: artifact.Name, + }, artifact) + if err != nil { + return nil, err + } + resources = append(resources, resource) + } + case clusterInfo: + var cf bom.ClusterInfo + err := ms.Decode(artifact.RawResource, &cf) + if err != nil { + return nil, err + } + lang := k8sNamespace(cf.Version, nodeName) + + results, _, err := k8sScanner.Scan(ctx, types.ScanTarget{ + Applications: []ftypes.Application{ + { + Type: ftypes.LangType(lang), + FilePath: artifact.Name, + Libraries: []ftypes.Package{ + { + Name: cf.Name, + Version: cf.Version, + }, + }, + }, + }, + }, scanOptions) + if err != nil { + return nil, err + } + if results != nil { + resource, err := s.filter(ctx, types.Report{ + Results: results, + ArtifactName: artifact.Name, + }, artifact) + if err != nil { + return nil, err + } + resources = append(resources, resource) + } + } + } + return resources, nil +} + +func (*Scanner) findNodeName(allArtifact []*artifacts.Artifact) string { + for _, artifact := range allArtifact { + if artifact.Kind != nodeComponents { + continue + } + return artifact.Name + } + return "" +} + +func (s *Scanner) clusterInfoToReportResources(allArtifact []*artifacts.Artifact) (*core.BOM, error) { + var rootComponent *core.Component + var coreComponents []*core.Component + + // Find the first node name to identify AKS cluster + var nodeName string + if nodeName = s.findNodeName(allArtifact); nodeName == "" { + return nil, fmt.Errorf("failed to find node name") + } + + kbom := core.NewBOM(core.Options{ + GenerateBOMRef: true, + }) + for _, artifact := range allArtifact { + switch artifact.Kind { + case controlPlaneComponents: + var comp bom.Component + if err := ms.Decode(artifact.RawResource, &comp); err != nil { + return nil, err + } + + controlPlane := &core.Component{ + Name: comp.Name, + Version: comp.Version, + Type: core.TypeApplication, + Properties: toProperties(comp.Properties, k8sCoreComponentNamespace), + PkgID: core.PkgID{ + PURL: generatePURL(comp.Name, comp.Version, nodeName), + }, + } + coreComponents = append(coreComponents, controlPlane) + + for _, c := range comp.Containers { + name := fmt.Sprintf("%s/%s", c.Registry, c.Repository) + cDigest := c.Digest + if !strings.Contains(c.Digest, string(digest.SHA256)) { + cDigest = fmt.Sprintf("%s:%s", string(digest.SHA256), cDigest) + } + ver := sanitizedVersion(c.Version) + + imagePURL, err := purl.New(purl.TypeOCI, types.Metadata{ + RepoDigests: []string{ + fmt.Sprintf("%s@%s", name, cDigest), + }, + }, ftypes.Package{}) + if err != nil { + return nil, xerrors.Errorf("failed to create PURL: %w", err) + } + + imageComponent := &core.Component{ + Type: core.TypeContainerImage, + Name: name, + Version: cDigest, + PkgID: core.PkgID{ + PURL: imagePURL.Unwrap(), + }, + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: fmt.Sprintf("%s:%s", name, ver), + }, + { + Name: core.PropertyPkgType, + Value: oci, + }, + }, + } + kbom.AddRelationship(controlPlane, imageComponent, core.RelationshipDependsOn) + } + case nodeComponents: + var nf bom.NodeInfo + err := ms.Decode(artifact.RawResource, &nf) + if err != nil { + return nil, err + } + coreComponents = append(coreComponents, s.nodeComponent(kbom, nf)) + case clusterInfo: + var cf bom.ClusterInfo + if err := ms.Decode(artifact.RawResource, &cf); err != nil { + return nil, err + } + rootComponent = &core.Component{ + Type: core.TypePlatform, + Name: cf.Name, + Version: cf.Version, + Properties: toProperties(cf.Properties, k8sCoreComponentNamespace), + PkgID: core.PkgID{ + PURL: generatePURL(cf.Name, cf.Version, nodeName), + }, + Root: true, + } + kbom.AddComponent(rootComponent) + default: + return nil, fmt.Errorf("resource kind %s is not supported", artifact.Kind) + } + } + + for _, c := range coreComponents { + kbom.AddRelationship(rootComponent, c, core.RelationshipContains) + } + + return kbom, nil +} + +func (s *Scanner) nodeComponent(b *core.BOM, nf bom.NodeInfo) *core.Component { + osName, osVersion := osNameVersion(nf.OsImage) + runtimeName, runtimeVersion := runtimeNameVersion(nf.ContainerRuntimeVersion) + kubeletVersion := sanitizedVersion(nf.KubeletVersion) + properties := toProperties(nf.Properties, "") + properties = append(properties, toProperties(map[string]string{ + k8sComponentType: k8sComponentNode, + k8sComponentName: nf.NodeName, + }, k8sCoreComponentNamespace)...) + + nodeComponent := &core.Component{ + Type: core.TypePlatform, + Name: nf.NodeName, + Properties: properties, + } + + osComponent := &core.Component{ + Type: core.TypeOS, + Name: osName, + Version: osVersion, + Properties: []core.Property{ + { + Name: "Class", + Value: string(types.ClassOSPkg), + }, + { + Name: "Type", + Value: osName, + }, + }, + } + b.AddRelationship(nodeComponent, osComponent, core.RelationshipContains) + + appComponent := &core.Component{ + Type: core.TypeApplication, + Name: nodeCoreComponents, + } + b.AddRelationship(nodeComponent, appComponent, core.RelationshipContains) + + kubeletComponent := &core.Component{ + Type: core.TypeApplication, + Name: kubelet, + Version: kubeletVersion, + Properties: []core.Property{ + { + Name: k8sComponentType, + Value: k8sComponentNode, + Namespace: k8sCoreComponentNamespace, + }, + { + Name: k8sComponentName, + Value: kubelet, + Namespace: k8sCoreComponentNamespace, + }, + }, + PkgID: core.PkgID{ + PURL: generatePURL(kubelet, kubeletVersion, nf.NodeName), + }, + } + b.AddRelationship(appComponent, kubeletComponent, core.RelationshipContains) + + runtimeComponent := &core.Component{ + Type: core.TypeApplication, + Name: runtimeName, + Version: runtimeVersion, + Properties: []core.Property{ + { + Name: k8sComponentType, + Value: k8sComponentNode, + Namespace: k8sCoreComponentNamespace, + }, + { + Name: k8sComponentName, + Value: runtimeName, + Namespace: k8sCoreComponentNamespace, + }, + }, + PkgID: core.PkgID{ + PURL: packageurl.NewPackageURL(packageurl.TypeGolang, "", runtimeName, runtimeVersion, packageurl.Qualifiers{}, ""), + }, + } + b.AddRelationship(appComponent, runtimeComponent, core.RelationshipContains) + + return nodeComponent +} + +func sanitizedVersion(ver string) string { + return strings.TrimPrefix(ver, "v") +} + +func osNameVersion(name string) (string, string) { + var buffer bytes.Buffer + var v string + var err error + parts := strings.Split(name, " ") + for _, p := range parts { + _, err = version.Parse(p) + if err != nil { + buffer.WriteString(p + " ") + continue + } + v = p + break + } + return strings.ToLower(strings.TrimSpace(buffer.String())), v +} + +func runtimeNameVersion(name string) (string, string) { + runtime, ver, ok := strings.Cut(name, "://") + if !ok { + return "", "" + } + + switch runtime { + case "cri-o": + name = "github.com/cri-o/cri-o" + case "containerd": + name = "github.com/containerd/containerd" + case "cri-dockerd": + name = "github.com/Mirantis/cri-dockerd" + } + return name, ver +} + +func toProperties(props map[string]string, namespace string) []core.Property { + properties := lo.MapToSlice(props, func(k, v string) core.Property { + return core.Property{ + Name: k, + Value: v, + Namespace: namespace, + } + }) + if len(properties) == 0 { + return nil + } + sort.Slice(properties, func(i, j int) bool { + return properties[i].Name < properties[j].Name + }) + return properties +} + +func generatePURL(name, ver, nodeName string) *packageurl.PackageURL { + var namespace string + // Identify k8s distribution. An empty namespace means upstream. + if namespace = k8sNamespace(ver, nodeName); namespace == "" { + return nil + } else if namespace == "kubernetes" { + namespace = "" + } + + return packageurl.NewPackageURL(purl.TypeK8s, namespace, name, ver, nil, "") +} + +func k8sNamespace(ver, nodeName string) string { + namespace := "kubernetes" + switch { + case strings.Contains(ver, "eks"): + namespace = purl.NamespaceEKS + case strings.Contains(ver, "gke"): + namespace = purl.NamespaceGKE + case strings.Contains(ver, "hotfix"): + if !strings.Contains(nodeName, "aks") { + // Unknown k8s distribution + return "" + } + namespace = purl.NamespaceAKS + case strings.Contains(nodeName, "ocp"): + namespace = purl.NamespaceOCP + } + return namespace +} diff --git a/pkg/k8s/scanner/scanner_test.go b/pkg/k8s/scanner/scanner_test.go new file mode 100644 index 000000000000..9269f78cf11b --- /dev/null +++ b/pkg/k8s/scanner/scanner_test.go @@ -0,0 +1,551 @@ +package scanner + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/uuid" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + "sort" + "testing" + + "github.com/package-url/packageurl-go" + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" + cmd "github.com/aquasecurity/trivy/pkg/commands/artifact" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/purl" +) + +func TestScanner_Scan(t *testing.T) { + flagOpts := flag.Options{ReportOptions: flag.ReportOptions{Format: "cyclonedx"}} + tests := []struct { + name string + clusterName string + artifacts []*artifacts.Artifact + wantComponents []*core.Component + }{ + { + name: "test cluster info with resources", + clusterName: "test-cluster", + artifacts: []*artifacts.Artifact{ + { + Namespace: "kube-system", + Kind: "Cluster", + Name: "k8s.io/kubernetes", + RawResource: map[string]interface{}{ + "name": "k8s.io/kubernetes", + "version": "1.21.1", + "type": "ClusterInfo", + "Properties": map[string]string{ + "Name": "kind-kind", + "Type": "cluster", + }, + }, + }, + { + Namespace: "kube-system", + Kind: "ControlPlaneComponents", + Name: "k8s.io/apiserver", + RawResource: map[string]interface{}{ + "Containers": []interface{}{ + map[string]interface{}{ + "Digest": "18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", + "ID": "kube-apiserver:v1.21.1", + "Registry": "k8s.gcr.io", + "Repository": "kube-apiserver", + "Version": "v1.21.1", + }, + }, + "Name": "k8s.io/apiserver", + "Version": "1.21.1", + }, + }, + { + Kind: "NodeComponents", + Name: "kind-control-plane", + RawResource: map[string]interface{}{ + "ContainerRuntimeVersion": "containerd://1.5.2", + "Hostname": "kind-control-plane", + "KubeProxyVersion": "6.2.13-300.fc38.aarch64", + "KubeletVersion": "v1.21.1", + "NodeName": "kind-control-plane", + "OsImage": "Ubuntu 21.04", + "Properties": map[string]string{ + "Architecture": "arm64", + "HostName": "kind-control-plane", + "KernelVersion": "6.2.15-300.fc38.aarch64", + "NodeRole": "master", + "OperatingSystem": "linux", + }, + }, + }, + }, + wantComponents: []*core.Component{ + { + Type: core.TypeApplication, + Name: "github.com/containerd/containerd", + Version: "1.5.2", + Properties: []core.Property{ + { + Name: k8sComponentName, + Value: "github.com/containerd/containerd", + Namespace: k8sCoreComponentNamespace, + }, + { + Name: k8sComponentType, + Value: "node", + Namespace: k8sCoreComponentNamespace, + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: "golang", + Name: "github.com/containerd/containerd", + Version: "1.5.2", + Qualifiers: packageurl.Qualifiers{}, + }, + BOMRef: "pkg:golang/github.com%2Fcontainerd%2Fcontainerd@1.5.2", + }, + }, + { + Type: core.TypeApplication, + Name: "k8s.io/apiserver", + Version: "1.21.1", + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "k8s.io/apiserver", + Version: "1.21.1", + }, + BOMRef: "pkg:k8s/k8s.io%2Fapiserver@1.21.1", + }, + }, + { + Type: core.TypeApplication, + Name: "k8s.io/kubelet", + Version: "1.21.1", + Properties: []core.Property{ + { + Name: k8sComponentName, + Value: "k8s.io/kubelet", + Namespace: k8sCoreComponentNamespace, + }, + { + Name: k8sComponentType, + Value: "node", + Namespace: k8sCoreComponentNamespace, + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: "k8s", + Name: "k8s.io/kubelet", + Version: "1.21.1", + }, + BOMRef: "pkg:k8s/k8s.io%2Fkubelet@1.21.1", + }, + }, + { + Type: core.TypeApplication, + Name: "node-core-components", + PkgID: core.PkgID{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000006", + }, + }, + { + Type: core.TypeContainerImage, + Name: "k8s.gcr.io/kube-apiserver", + Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: "oci", + Name: "kube-apiserver", + Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", + Qualifiers: packageurl.Qualifiers{ + { + Key: "repository_url", + Value: "k8s.gcr.io/kube-apiserver", + }, + }, + }, + BOMRef: "pkg:oci/kube-apiserver@sha256%3A18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?repository_url=k8s.gcr.io%2Fkube-apiserver", + }, + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: "k8s.gcr.io/kube-apiserver:1.21.1", + }, + { + Name: core.PropertyPkgType, + Value: "oci", + }, + }, + }, + { + Type: core.TypeOS, + Name: "ubuntu", + Version: "21.04", + Properties: []core.Property{ + { + Name: "Class", + Value: "os-pkgs", + Namespace: "", + }, + { + Name: "Type", + Value: "ubuntu", + Namespace: "", + }, + }, + PkgID: core.PkgID{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000005", + }, + }, + { + Type: core.TypePlatform, + Root: true, + Name: "k8s.io/kubernetes", + Version: "1.21.1", + Properties: []core.Property{ + { + Name: "Name", + Value: "kind-kind", + Namespace: k8sCoreComponentNamespace, + }, + { + Name: "Type", + Value: "cluster", + Namespace: k8sCoreComponentNamespace, + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "k8s.io/kubernetes", + Version: "1.21.1", + }, + BOMRef: "pkg:k8s/k8s.io%2Fkubernetes@1.21.1", + }, + }, + { + Type: core.TypePlatform, + Name: "kind-control-plane", + Properties: []core.Property{ + { + Name: "Architecture", + Value: "arm64", + }, + { + Name: "HostName", + Value: "kind-control-plane", + }, + { + Name: "KernelVersion", + Value: "6.2.15-300.fc38.aarch64", + }, + { + Name: k8sComponentName, + Value: "kind-control-plane", + Namespace: k8sCoreComponentNamespace, + }, + { + Name: "NodeRole", + Value: "master", + }, + { + Name: "OperatingSystem", + Value: "linux", + }, + { + Name: k8sComponentType, + Value: "node", + Namespace: k8sCoreComponentNamespace, + }, + }, + PkgID: core.PkgID{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000004", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d") + + runner, err := cmd.NewRunner(ctx, flagOpts) + assert.NoError(t, err) + + scanner := NewScanner(tt.clusterName, runner, flagOpts) + got, err := scanner.Scan(ctx, tt.artifacts) + require.NoError(t, err) + + gotComponents := maps.Values(got.BOM.Components()) + require.Equal(t, len(tt.wantComponents), len(gotComponents)) + + sort.Slice(gotComponents, func(i, j int) bool { + switch { + case gotComponents[i].Type != gotComponents[j].Type: + return gotComponents[i].Type < gotComponents[j].Type + case gotComponents[i].Name != gotComponents[j].Name: + return gotComponents[i].Name < gotComponents[j].Name + default: + return gotComponents[i].Version < gotComponents[j].Version + } + }) + for i, want := range tt.wantComponents { + assert.EqualExportedValues(t, *want, *gotComponents[i], want.Name) + } + }) + } +} + +func TestTestOsNameVersion(t *testing.T) { + tests := []struct { + name string + nameVersion string + compName string + compVersion string + }{ + + { + name: "valid version", + nameVersion: "ubuntu 20.04", + compName: "ubuntu", + compVersion: "20.04", + }, + { + name: "valid sem version", + nameVersion: "ubuntu 20.04.1", + compName: "ubuntu", + compVersion: "20.04.1", + }, + { + name: "non valid version", + nameVersion: "ubuntu", + compName: "ubuntu", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + name, version := osNameVersion(tt.nameVersion) + assert.Equal(t, name, tt.compName) + assert.Equal(t, version, tt.compVersion) + }) + } +} + +func TestGeneratePURL(t *testing.T) { + tests := []struct { + name string + compName string + compVersion string + nodeName string + want string + }{ + { + name: "native k8s component", + compName: "k8s.io/kubelet", + compVersion: "1.24.10", + nodeName: "kind-kind", + want: "pkg:k8s/k8s.io%2Fkubelet@1.24.10", + }, + + { + name: "GKE", + compName: "k8s.io/kubelet", + compVersion: "1.24.10-gke.2300", + nodeName: "gke-gke1796-default-pool-768cb718-sk1d", + want: "pkg:k8s/gke/k8s.io%2Fkubelet@1.24.10-gke.2300", + }, + { + name: "AKS", + compName: "k8s.io/kubelet", + compVersion: "1.24.10-hotfix.20221110", + nodeName: "aks-default-23814474-vmss000000", + want: "pkg:k8s/aks/k8s.io%2Fkubelet@1.24.10-hotfix.20221110", + }, + { + name: "EKS", + compName: "k8s.io/kubelet", + compVersion: "1.23.17-eks-8ccc7ba", + nodeName: "eks-vmss000000", + want: "pkg:k8s/eks/k8s.io%2Fkubelet@1.23.17-eks-8ccc7ba", + }, + { + name: "Rancher", + compName: "k8s.io/kubelet", + compVersion: "1.24.11+rke2r1", + nodeName: "ip-10-0-5-23", + want: "pkg:k8s/k8s.io%2Fkubelet@1.24.11%2Brke2r1", + }, + { + name: "OCP", + compName: "k8s.io/kubelet", + compVersion: "1.26.7+c7ee51f", + nodeName: "ocp413vpool14000-p8vnm-master-2", + want: "pkg:k8s/ocp/k8s.io%2Fkubelet@1.26.7%2Bc7ee51f", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := generatePURL(tt.compName, tt.compVersion, tt.nodeName) + assert.Equal(t, tt.want, got.String()) + }) + } +} + +func TestK8sNamespace(t *testing.T) { + tests := []struct { + name string + compVersion string + nodeName string + want string + }{ + { + name: "native k8s component", + compVersion: "1.24.10", + nodeName: "kind-kind", + want: "kubernetes", + }, + + { + name: "GKE", + compVersion: "1.24.10-gke.2300", + nodeName: "gke-gke1796-default-pool-768cb718-sk1d", + want: "gke", + }, + { + name: "AKS", + compVersion: "1.24.10-hotfix.20221110", + nodeName: "aks-default-23814474-vmss000000", + want: "aks", + }, + { + name: "EKS", + compVersion: "1.23.17-eks-8ccc7ba", + nodeName: "eks-vmss000000", + want: "eks", + }, + { + name: "Rancher", + compVersion: "1.24.11+rke2r1", + nodeName: "ip-10-0-5-23", + want: "kubernetes", + }, + { + name: "OCP", + compVersion: "1.26.7+c7ee51f", + nodeName: "ocp413vpool14000-p8vnm-master-2", + want: "ocp", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := k8sNamespace(tt.compVersion, tt.nodeName) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRuntimeVersion(t *testing.T) { + tests := []struct { + name string + runtimeVersion string + wantName string + wantVersion string + }{ + { + name: "containerd", + runtimeVersion: "containerd://1.5.2", + wantName: "github.com/containerd/containerd", + wantVersion: "1.5.2", + }, + { + name: "cri-o", + runtimeVersion: "cri-o://1.5.2", + wantName: "github.com/cri-o/cri-o", + wantVersion: "1.5.2", + }, + { + name: "cri-dockerd", + runtimeVersion: "cri-dockerd://1.5.2", + wantName: "github.com/Mirantis/cri-dockerd", + wantVersion: "1.5.2", + }, + { + name: "na runtime", + runtimeVersion: "cri:1.5.2", + wantName: "", + wantVersion: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotName, gotVersion := runtimeNameVersion(tt.runtimeVersion) + assert.Equal(t, tt.wantName, gotName) + assert.Equal(t, tt.wantVersion, gotVersion) + }) + } +} + +func TestFindNodeName(t *testing.T) { + tests := []struct { + name string + artifacts []*artifacts.Artifact + want string + }{ + { + name: "find node name", + artifacts: []*artifacts.Artifact{ + { + Namespace: "kube-system", + Kind: "Cluster", + Name: "k8s.io/kubernetes", + RawResource: map[string]interface{}{}, + }, + { + Namespace: "kube-system", + Kind: "ControlPlaneComponents", + Name: "k8s.io/apiserver", + RawResource: map[string]interface{}{}, + }, + { + Kind: "NodeComponents", + Name: "kind-control-plane", + RawResource: map[string]interface{}{}, + }, + }, + want: "kind-control-plane", + }, + { + name: "didn't find node name", + artifacts: []*artifacts.Artifact{ + { + Namespace: "kube-system", + Kind: "Cluster", + Name: "k8s.io/kubernetes", + RawResource: map[string]interface{}{}, + }, + { + Namespace: "kube-system", + Kind: "ControlPlaneComponents", + Name: "k8s.io/apiserver", + RawResource: map[string]interface{}{}, + }, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := Scanner{} + got := s.findNodeName(tt.artifacts) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/k8s/wire_gen.go b/pkg/k8s/wire_gen.go new file mode 100644 index 000000000000..2b2343a654b7 --- /dev/null +++ b/pkg/k8s/wire_gen.go @@ -0,0 +1,30 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package k8s + +import ( + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/fanal/applier" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/scanner/langpkg" + "github.com/aquasecurity/trivy/pkg/scanner/local" + "github.com/aquasecurity/trivy/pkg/scanner/ospkg" + "github.com/aquasecurity/trivy/pkg/vulnerability" +) + +// Injectors from inject.go: + +func initializeScanK8s(localArtifactCache cache.LocalArtifactCache) *ScanKubernetes { + applierApplier := applier.NewApplier(localArtifactCache) + scanner := ospkg.NewScanner() + langpkgScanner := langpkg.NewScanner() + config := db.Config{} + client := vulnerability.NewClient(config) + localScanner := local.NewScanner(applierApplier, scanner, langpkgScanner, client) + scanKubernetes := NewScanKubernetes(localScanner) + return scanKubernetes +} diff --git a/pkg/k8s/writer.go b/pkg/k8s/writer.go new file mode 100644 index 000000000000..13204501fb59 --- /dev/null +++ b/pkg/k8s/writer.go @@ -0,0 +1,52 @@ +package k8s + +import ( + "context" + "fmt" + + cdx "github.com/CycloneDX/cyclonedx-go" + + "github.com/aquasecurity/trivy/pkg/k8s/report" + "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" +) + +// Write writes the results in the give format +func Write(ctx context.Context, k8sreport report.Report, option report.Option) error { + k8sreport.PrintErrors() + + switch option.Format { + case types.FormatJSON: + jwriter := report.JSONWriter{ + Output: option.Output, + Report: option.Report, + } + return jwriter.Write(k8sreport) + case types.FormatTable: + separatedReports := report.SeparateMisconfigReports(k8sreport, option.Scanners, option.Components) + + if option.Report == report.SummaryReport { + target := fmt.Sprintf("Summary Report for %s", k8sreport.ClusterName) + table.RenderTarget(option.Output, target, table.IsOutputToTerminal(option.Output)) + } + + for _, r := range separatedReports { + writer := &report.TableWriter{ + Output: option.Output, + Report: option.Report, + Severities: option.Severities, + ColumnHeading: report.ColumnHeading(option.Scanners, option.Components, r.Columns), + } + + if err := writer.Write(ctx, r.Report); err != nil { + return err + } + } + + return nil + case types.FormatCycloneDX: + w := report.NewCycloneDXWriter(option.Output, cdx.BOMFileFormatJSON, option.APIVersion) + return w.Write(ctx, k8sreport.BOM) + } + return nil +} diff --git a/pkg/k8s/writer_test.go b/pkg/k8s/writer_test.go new file mode 100644 index 000000000000..e07c35044ae8 --- /dev/null +++ b/pkg/k8s/writer_test.go @@ -0,0 +1,409 @@ +package k8s + +import ( + "bytes" + "context" + "regexp" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/k8s/report" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + AllReport = "all" + SummaryReport = "summary" + + tableFormat = "table" + jsonFormat = "json" + cycloneDXFormat = "cyclonedx" + + workloadComponent = "workload" + infraComponent = "infra" +) + +var ( + roleWithMisconfig = report.Resource{ + Namespace: "default", + Kind: "Role", + Name: "system::leader-locking-kube-controller-manager", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "ID100", + Status: types.MisconfStatusFailure, + Severity: "MEDIUM", + }, + }, + }, + }, + } + apiseverPodWithMisconfigAndInfra = report.Resource{ + Namespace: "kube-system", + Kind: "Pod", + Name: "kube-apiserver", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "KSV-ID100", + Status: types.MisconfStatusFailure, + Severity: "LOW", + }, + { + ID: "KSV-ID101", + Status: types.MisconfStatusFailure, + Severity: "MEDIUM", + }, + { + ID: "KSV-ID102", + Status: types.MisconfStatusFailure, + Severity: "HIGH", + }, + + { + ID: "KCV-ID100", + Status: types.MisconfStatusFailure, + Severity: "LOW", + }, + { + ID: "KCV-ID101", + Status: types.MisconfStatusFailure, + Severity: "MEDIUM", + }, + }, + }, + }, + } + deployLuaWithSecrets = report.Resource{ + Namespace: "default", + Kind: "Deploy", + Name: "lua", + Results: types.Results{ + { + Secrets: []types.DetectedSecret{ + { + RuleID: "secret1", + Severity: "CRITICAL", + }, + { + RuleID: "secret2", + Severity: "MEDIUM", + }, + }, + }, + }, + } + deployOrionWithMisconfigs = report.Resource{ + Namespace: "default", + Kind: "Deploy", + Name: "orion", + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "ID100", + Status: types.MisconfStatusFailure, + Severity: "LOW", + }, + { + ID: "ID101", + Status: types.MisconfStatusFailure, + Severity: "MEDIUM", + }, + { + ID: "ID102", + Status: types.MisconfStatusFailure, + Severity: "HIGH", + }, + { + ID: "ID103", + Status: types.MisconfStatusFailure, + Severity: "CRITICAL", + }, + { + ID: "ID104", + Status: types.MisconfStatusFailure, + Severity: "UNKNOWN", + }, + { + ID: "ID105", + Status: types.MisconfStatusFailure, + Severity: "LOW", + }, + { + ID: "ID106", + Status: types.MisconfStatusFailure, + Severity: "HIGH", + }, + }, + }, + }, + } + deployOrionWithVulns = report.Resource{ + Namespace: "default", + Kind: "Deploy", + Name: "orion", + Results: types.Results{ + { + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-1111", + Vulnerability: dbTypes.Vulnerability{Severity: "LOW"}, + }, + { + VulnerabilityID: "CVE-2022-2222", + Vulnerability: dbTypes.Vulnerability{Severity: "MEDIUM"}, + }, + { + VulnerabilityID: "CVE-2022-3333", + Vulnerability: dbTypes.Vulnerability{Severity: "HIGH"}, + }, + { + VulnerabilityID: "CVE-2022-4444", + Vulnerability: dbTypes.Vulnerability{Severity: "CRITICAL"}, + }, + { + VulnerabilityID: "CVE-2022-5555", + Vulnerability: dbTypes.Vulnerability{Severity: "UNKNOWN"}, + }, + { + VulnerabilityID: "CVE-2022-6666", + Vulnerability: dbTypes.Vulnerability{Severity: "CRITICAL"}, + }, + { + VulnerabilityID: "CVE-2022-7777", + Vulnerability: dbTypes.Vulnerability{Severity: "MEDIUM"}, + }, + }, + }, + }, + } +) + +func TestReportWrite_Summary(t *testing.T) { + allSeverities := []dbTypes.Severity{ + dbTypes.SeverityUnknown, + dbTypes.SeverityLow, + dbTypes.SeverityMedium, + dbTypes.SeverityHigh, + dbTypes.SeverityCritical, + } + + tests := []struct { + name string + report report.Report + opt report.Option + scanners types.Scanners + components []string + severities []dbTypes.Severity + expectedOutput string + }{ + { + name: "Only config, all serverities", + report: report.Report{ + ClusterName: "test", + Resources: []report.Resource{deployOrionWithMisconfigs}, + }, + scanners: types.Scanners{types.MisconfigScanner}, + components: []string{workloadComponent}, + severities: allSeverities, + expectedOutput: `Summary Report for test +======================= + +Workload Assessment +┌───────────┬──────────────┬───────────────────┠+│ Namespace │ Resource │ Misconfigurations │ +│ │ ├───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ +├───────────┼──────────────┼───┼───┼───┼───┼───┤ +│ default │ Deploy/orion │ 1 │ 2 │ 1 │ 2 │ 1 │ +└───────────┴──────────────┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, + }, + { + name: "Only vuln, all serverities", + report: report.Report{ + ClusterName: "test", + Resources: []report.Resource{deployOrionWithVulns}, + }, + scanners: types.Scanners{types.VulnerabilityScanner}, + components: []string{workloadComponent}, + severities: allSeverities, + expectedOutput: `Summary Report for test +======================= + +Workload Assessment +┌───────────┬──────────────┬───────────────────┠+│ Namespace │ Resource │ Vulnerabilities │ +│ │ ├───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ +├───────────┼──────────────┼───┼───┼───┼───┼───┤ +│ default │ Deploy/orion │ 2 │ 1 │ 2 │ 1 │ 1 │ +└───────────┴──────────────┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, + }, + { + name: "Only rbac, all serverities", + report: report.Report{ + ClusterName: "test", + Resources: []report.Resource{roleWithMisconfig}, + }, + scanners: types.Scanners{types.RBACScanner}, + severities: allSeverities, + expectedOutput: `Summary Report for test +======================= + +RBAC Assessment +┌───────────┬─────────────────────────────────────────────────────┬───────────────────┠+│ Namespace │ Resource │ RBAC Assessment │ +│ │ ├───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ +├───────────┼─────────────────────────────────────────────────────┼───┼───┼───┼───┼───┤ +│ default │ Role/system::leader-locking-kube-controller-manager │ │ │ 1 │ │ │ +└───────────┴─────────────────────────────────────────────────────┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, + }, + { + name: "Only secret, all serverities", + report: report.Report{ + ClusterName: "test", + Resources: []report.Resource{deployLuaWithSecrets}, + }, + scanners: types.Scanners{types.SecretScanner}, + components: []string{workloadComponent}, + severities: allSeverities, + expectedOutput: `Summary Report for test +======================= + +Workload Assessment +┌───────────┬────────────┬───────────────────┠+│ Namespace │ Resource │ Secrets │ +│ │ ├───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ +├───────────┼────────────┼───┼───┼───┼───┼───┤ +│ default │ Deploy/lua │ 1 │ │ 1 │ │ │ +└───────────┴────────────┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, + }, + { + name: "apiserver, only infra and serverities", + report: report.Report{ + ClusterName: "test", + Resources: []report.Resource{apiseverPodWithMisconfigAndInfra}, + }, + scanners: types.Scanners{types.MisconfigScanner}, + components: []string{infraComponent}, + severities: allSeverities, + expectedOutput: `Summary Report for test +======================= + +Infra Assessment +┌─────────────┬────────────────────┬───────────────────┠+│ Namespace │ Resource │ Misconfigurations │ +│ │ ├───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ +├─────────────┼────────────────────┼───┼───┼───┼───┼───┤ +│ kube-system │ Pod/kube-apiserver │ │ 1 │ 2 │ 2 │ │ +└─────────────┴────────────────────┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, + }, + { + name: "apiserver, vuln,config,secret and serverities", + report: report.Report{ + ClusterName: "test", + Resources: []report.Resource{apiseverPodWithMisconfigAndInfra}, + }, + scanners: types.Scanners{ + types.VulnerabilityScanner, + types.MisconfigScanner, + types.SecretScanner, + }, + components: []string{infraComponent}, + severities: allSeverities, + expectedOutput: `Summary Report for test +======================= + +Infra Assessment +┌─────────────┬────────────────────┬───────────────────┬───────────────────┬───────────────────┠+│ Namespace │ Resource │ Vulnerabilities │ Misconfigurations │ Secrets │ +│ │ ├───┬───┬───┬───┬───┼───┬───┬───┬───┬───┼───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ C │ H │ M │ L │ U │ C │ H │ M │ L │ U │ +├─────────────┼────────────────────┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│ kube-system │ Pod/kube-apiserver │ │ │ │ │ │ │ 1 │ 2 │ 2 │ │ │ │ │ │ │ +└─────────────┴────────────────────┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, + }, + { + name: "apiserver, all misconfig and vuln scanners and serverities", + report: report.Report{ + ClusterName: "test", + Resources: []report.Resource{apiseverPodWithMisconfigAndInfra}, + }, + scanners: types.Scanners{ + types.MisconfigScanner, + types.VulnerabilityScanner, + }, + components: []string{ + workloadComponent, + infraComponent, + }, + severities: allSeverities, + expectedOutput: `Summary Report for test +======================= + +Workload Assessment +┌───────────┬──────────┬───────────────────┬───────────────────┠+│ Namespace │ Resource │ Vulnerabilities │ Misconfigurations │ +│ │ ├───┬───┬───┬───┬───┼───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ C │ H │ M │ L │ U │ +└───────────┴──────────┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN + + +Infra Assessment +┌─────────────┬────────────────────┬───────────────────┬───────────────────┠+│ Namespace │ Resource │ Vulnerabilities │ Misconfigurations │ +│ │ ├───┬───┬───┬───┬───┼───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ C │ H │ M │ L │ U │ +├─────────────┼────────────────────┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│ kube-system │ Pod/kube-apiserver │ │ │ │ │ │ │ 1 │ 2 │ 2 │ │ +└─────────────┴────────────────────┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + output := bytes.Buffer{} + + opt := report.Option{ + Format: "table", + Report: "summary", + Output: &output, + Scanners: tc.scanners, + Severities: tc.severities, + Components: tc.components, + } + + err := Write(context.Background(), tc.report, opt) + require.NoError(t, err) + assert.Equal(t, tc.expectedOutput, stripAnsi(output.String()), tc.name) + }) + } +} + +const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" + +var ansiRegexp = regexp.MustCompile(ansi) + +func stripAnsi(str string) string { + return strings.TrimSpace(ansiRegexp.ReplaceAllString(str, "")) +} diff --git a/pkg/licensing/category.go b/pkg/licensing/category.go new file mode 100644 index 000000000000..f99ccdc52af4 --- /dev/null +++ b/pkg/licensing/category.go @@ -0,0 +1,339 @@ +package licensing + +// Canonical names of the licenses. +// ported from https://github.com/google/licenseclassifier/blob/7c62d6fe8d3aa2f39c4affb58c9781d9dc951a2d/license_type.go#L24-L177 +const ( + // The names come from the https://spdx.org/licenses website, and are + // also the filenames of the licenses in licenseclassifier/licenses. + AFL11 = "AFL-1.1" + AFL12 = "AFL-1.2" + AFL20 = "AFL-2.0" + AFL21 = "AFL-2.1" + AFL30 = "AFL-3.0" + AGPL10 = "AGPL-1.0" + AGPL30 = "AGPL-3.0" + Apache10 = "Apache-1.0" + Apache11 = "Apache-1.1" + Apache20 = "Apache-2.0" + APSL10 = "APSL-1.0" + APSL11 = "APSL-1.1" + APSL12 = "APSL-1.2" + APSL20 = "APSL-2.0" + Artistic10cl8 = "Artistic-1.0-cl8" + Artistic10Perl = "Artistic-1.0-Perl" + Artistic10 = "Artistic-1.0" + Artistic20 = "Artistic-2.0" + BCL = "BCL" + Beerware = "Beerware" + BSD2ClauseFreeBSD = "BSD-2-Clause-FreeBSD" + BSD2ClauseNetBSD = "BSD-2-Clause-NetBSD" + BSD2Clause = "BSD-2-Clause" + BSD3ClauseAttribution = "BSD-3-Clause-Attribution" + BSD3ClauseClear = "BSD-3-Clause-Clear" + BSD3ClauseLBNL = "BSD-3-Clause-LBNL" + BSD3Clause = "BSD-3-Clause" + BSD4Clause = "BSD-4-Clause" + BSD4ClauseUC = "BSD-4-Clause-UC" + BSDProtection = "BSD-Protection" + BSL10 = "BSL-1.0" + CC010 = "CC0-1.0" + CCBY10 = "CC-BY-1.0" + CCBY20 = "CC-BY-2.0" + CCBY25 = "CC-BY-2.5" + CCBY30 = "CC-BY-3.0" + CCBY40 = "CC-BY-4.0" + CCBYNC10 = "CC-BY-NC-1.0" + CCBYNC20 = "CC-BY-NC-2.0" + CCBYNC25 = "CC-BY-NC-2.5" + CCBYNC30 = "CC-BY-NC-3.0" + CCBYNC40 = "CC-BY-NC-4.0" + CCBYNCND10 = "CC-BY-NC-ND-1.0" + CCBYNCND20 = "CC-BY-NC-ND-2.0" + CCBYNCND25 = "CC-BY-NC-ND-2.5" + CCBYNCND30 = "CC-BY-NC-ND-3.0" + CCBYNCND40 = "CC-BY-NC-ND-4.0" + CCBYNCSA10 = "CC-BY-NC-SA-1.0" + CCBYNCSA20 = "CC-BY-NC-SA-2.0" + CCBYNCSA25 = "CC-BY-NC-SA-2.5" + CCBYNCSA30 = "CC-BY-NC-SA-3.0" + CCBYNCSA40 = "CC-BY-NC-SA-4.0" + CCBYND10 = "CC-BY-ND-1.0" + CCBYND20 = "CC-BY-ND-2.0" + CCBYND25 = "CC-BY-ND-2.5" + CCBYND30 = "CC-BY-ND-3.0" + CCBYND40 = "CC-BY-ND-4.0" + CCBYSA10 = "CC-BY-SA-1.0" + CCBYSA20 = "CC-BY-SA-2.0" + CCBYSA25 = "CC-BY-SA-2.5" + CCBYSA30 = "CC-BY-SA-3.0" + CCBYSA40 = "CC-BY-SA-4.0" + CDDL10 = "CDDL-1.0" + CDDL11 = "CDDL-1.1" + CommonsClause = "Commons-Clause" + CPAL10 = "CPAL-1.0" + CPL10 = "CPL-1.0" + EGenix = "eGenix" + EPL10 = "EPL-1.0" + EPL20 = "EPL-2.0" + EUPL10 = "EUPL-1.0" + EUPL11 = "EUPL-1.1" + Facebook2Clause = "Facebook-2-Clause" + Facebook3Clause = "Facebook-3-Clause" + FacebookExamples = "Facebook-Examples" + FreeImage = "FreeImage" + FTL = "FTL" + GFDL11WithInvariants = "GFDL-1.1-invariants" + GFDL11NoInvariants = "GFDL-1.1-no-invariants" + GFDL11 = "GFDL-1.1" + GFDL12WithInvariants = "GFDL-1.2-invariants" + GFDL12NoInvariants = "GFDL-1.2-no-invariants" + GFDL12 = "GFDL-1.2" + GFDL13WithInvariants = "GFDL-1.3-invariants" + GFDL13NoInvariants = "GFDL-1.3-no-invariants" + GFDL13 = "GFDL-1.3" + GPL10 = "GPL-1.0" + GPL20 = "GPL-2.0" + GPL20withautoconfexception = "GPL-2.0-with-autoconf-exception" + GPL20withbisonexception = "GPL-2.0-with-bison-exception" + GPL20withclasspathexception = "GPL-2.0-with-classpath-exception" + GPL20withfontexception = "GPL-2.0-with-font-exception" + GPL20withGCCexception = "GPL-2.0-with-GCC-exception" + GPL30 = "GPL-3.0" + GPL30withautoconfexception = "GPL-3.0-with-autoconf-exception" + GPL30withGCCexception = "GPL-3.0-with-GCC-exception" + GUSTFont = "GUST-Font-License" + ImageMagick = "ImageMagick" + IPL10 = "IPL-1.0" + ISC = "ISC" + LGPL20 = "LGPL-2.0" + LGPL21 = "LGPL-2.1" + LGPL30 = "LGPL-3.0" + LGPLLR = "LGPLLR" + Libpng = "Libpng" + Lil10 = "Lil-1.0" + LinuxOpenIB = "Linux-OpenIB" + LPL102 = "LPL-1.02" + LPL10 = "LPL-1.0" + LPPL13c = "LPPL-1.3c" + MIT = "MIT" + MPL10 = "MPL-1.0" + MPL11 = "MPL-1.1" + MPL20 = "MPL-2.0" + MSPL = "MS-PL" + NCSA = "NCSA" + NPL10 = "NPL-1.0" + NPL11 = "NPL-1.1" + OFL11 = "OFL-1.1" + OpenSSL = "OpenSSL" + OpenVision = "OpenVision" + OSL10 = "OSL-1.0" + OSL11 = "OSL-1.1" + OSL20 = "OSL-2.0" + OSL21 = "OSL-2.1" + OSL30 = "OSL-3.0" + PHP301 = "PHP-3.01" + PHP30 = "PHP-3.0" + PIL = "PIL" + PostgreSQL = "PostgreSQL" + Python20complete = "Python-2.0-complete" + Python20 = "Python-2.0" + QPL10 = "QPL-1.0" + Ruby = "Ruby" + SGIB10 = "SGI-B-1.0" + SGIB11 = "SGI-B-1.1" + SGIB20 = "SGI-B-2.0" + SISSL12 = "SISSL-1.2" + SISSL = "SISSL" + Sleepycat = "Sleepycat" + UnicodeTOU = "Unicode-TOU" + UnicodeDFS2015 = "Unicode-DFS-2015" + UnicodeDFS2016 = "Unicode-DFS-2016" + Unlicense = "Unlicense" + UPL10 = "UPL-1.0" + W3C19980720 = "W3C-19980720" + W3C20150513 = "W3C-20150513" + W3C = "W3C" + WTFPL = "WTFPL" + X11 = "X11" + Xnet = "Xnet" + Zend20 = "Zend-2.0" + ZeroBSD = "0BSD" + ZlibAcknowledgement = "zlib-acknowledgement" + Zlib = "Zlib" + ZPL11 = "ZPL-1.1" + ZPL20 = "ZPL-2.0" + ZPL21 = "ZPL-2.1" +) + +var ( + // ForbiddenLicenses - Licenses that are forbidden to be used. + // ported from https://github.com/google/licenseclassifier/blob/7c62d6fe8d3aa2f39c4affb58c9781d9dc951a2d/license_type.go#L340-L364 + ForbiddenLicenses = []string{ + AGPL10, + AGPL30, + CCBYNC10, + CCBYNC20, + CCBYNC25, + CCBYNC30, + CCBYNC40, + CCBYNCND10, + CCBYNCND20, + CCBYNCND25, + CCBYNCND30, + CCBYNCND40, + CCBYNCSA10, + CCBYNCSA20, + CCBYNCSA25, + CCBYNCSA30, + CCBYNCSA40, + CommonsClause, + Facebook2Clause, + Facebook3Clause, + FacebookExamples, + WTFPL, + } + + // RestrictedLicenses - Licenses in this category require mandatory source distribution if we ship a product + // that includes third-party code protected by such a license. + // ported from https://github.com/google/licenseclassifier/blob/7c62d6fe8d3aa2f39c4affb58c9781d9dc951a2d/license_type.go#L182-L219 + RestrictedLicenses = []string{ + BCL, + CCBYND10, + CCBYND20, + CCBYND25, + CCBYND30, + CCBYND40, + CCBYSA10, + CCBYSA20, + CCBYSA25, + CCBYSA30, + CCBYSA40, + GPL10, + GPL20, + GPL20withautoconfexception, + GPL20withbisonexception, + GPL20withclasspathexception, + GPL20withfontexception, + GPL20withGCCexception, + GPL30, + GPL30withautoconfexception, + GPL30withGCCexception, + LGPL20, + LGPL21, + LGPL30, + NPL10, + NPL11, + OSL10, + OSL11, + OSL20, + OSL21, + OSL30, + QPL10, + Sleepycat, + } + + // ReciprocalLicenses - These licenses allow usage of software made available under such licenses freely + // in *unmodified* form. If the third-party source code is modified in any way these modifications to the + // original third-party source code must be made available. + ReciprocalLicenses = []string{ + APSL10, + APSL11, + APSL12, + APSL20, + CDDL10, + CDDL11, + CPL10, + EPL10, + EPL20, + FreeImage, + IPL10, + MPL10, + MPL11, + MPL20, + Ruby, + } + + // NoticeLicenses - These licenses contain few restrictions, allowing original or modified third-party software + // to be shipped in any product without endangering or encumbering our source code. + // All of the licenses in this category do, however, have an "original Copyright notice" or "advertising clause", + // wherein any external distributions must include the notice or clause specified in the license. + NoticeLicenses = []string{ + AFL11, + AFL12, + AFL20, + AFL21, + AFL30, + Apache10, + Apache11, + Apache20, + Artistic10cl8, + Artistic10Perl, + Artistic10, + Artistic20, + BSL10, + BSD2ClauseFreeBSD, + BSD2ClauseNetBSD, + BSD2Clause, + BSD3ClauseAttribution, + BSD3ClauseClear, + BSD3ClauseLBNL, + BSD3Clause, + BSD4Clause, + BSD4ClauseUC, + BSDProtection, + CCBY10, + CCBY20, + CCBY25, + CCBY30, + CCBY40, + FTL, + ISC, + ImageMagick, + Libpng, + Lil10, + LinuxOpenIB, + LPL102, + LPL10, + MSPL, + MIT, + NCSA, + OpenSSL, + PHP301, + PHP30, + PIL, + Python20, + Python20complete, + PostgreSQL, + SGIB10, + SGIB11, + SGIB20, + UnicodeDFS2015, + UnicodeDFS2016, + UnicodeTOU, + UPL10, + W3C19980720, + W3C20150513, + W3C, + X11, + Xnet, + Zend20, + ZlibAcknowledgement, + Zlib, + ZPL11, + ZPL20, + ZPL21, + } + + // PermissiveLicenses - These licenses can be used in (relatively rare) cases where third-party software is + // under a license (not "Public Domain" or "free for any use" like 'unencumbered') that is even more lenient + // than a 'notice' license. Use the 'permissive' license type when even a copyright notice is not required + // for license compliance. + PermissiveLicenses []string + + // UnencumberedLicenses - Licenses that basically declare that the code is "free for any use". + UnencumberedLicenses = []string{ + CC010, + Unlicense, + ZeroBSD, + } +) diff --git a/pkg/licensing/classifier.go b/pkg/licensing/classifier.go new file mode 100644 index 000000000000..0af770ef987a --- /dev/null +++ b/pkg/licensing/classifier.go @@ -0,0 +1,86 @@ +package licensing + +import ( + "fmt" + "io" + "sort" + "sync" + + classifier "github.com/google/licenseclassifier/v2" + "github.com/google/licenseclassifier/v2/assets" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +var ( + cf *classifier.Classifier + classifierOnce sync.Once + m sync.Mutex +) + +func initGoogleClassifier() error { + // Initialize the default classifier once. + // This loading is expensive and should be called only when the license classification is needed. + var err error + classifierOnce.Do(func() { + log.Logger.Debug("Loading the default license classifier...") + cf, err = assets.DefaultClassifier() + }) + return err +} + +// Classify detects and classifies the license found in a file +func Classify(filePath string, r io.Reader, confidenceLevel float64) (*types.LicenseFile, error) { + content, err := io.ReadAll(r) + if err != nil { + return nil, xerrors.Errorf("unable to read a license file %q: %w", filePath, err) + } + if err = initGoogleClassifier(); err != nil { + return nil, err + } + + var findings types.LicenseFindings + var matchType types.LicenseType + seen := make(map[string]struct{}) + + // cf.Match is not thread safe + m.Lock() + + // Use 'github.com/google/licenseclassifier' to find licenses + result := cf.Match(cf.Normalize(content)) + + m.Unlock() + + for _, match := range result.Matches { + if match.Confidence <= confidenceLevel { + continue + } + if _, ok := seen[match.Name]; ok { + continue + } + + seen[match.Name] = struct{}{} + + switch match.MatchType { + case "Header": + matchType = types.LicenseTypeHeader + case "License": + matchType = types.LicenseTypeFile + } + licenseLink := fmt.Sprintf("https://spdx.org/licenses/%s.html", match.Name) + + findings = append(findings, types.LicenseFinding{ + Name: match.Name, + Confidence: match.Confidence, + Link: licenseLink, + }) + } + sort.Sort(findings) + return &types.LicenseFile{ + Type: matchType, + FilePath: filePath, + Findings: findings, + }, nil +} diff --git a/pkg/licensing/classifier_test.go b/pkg/licensing/classifier_test.go new file mode 100644 index 000000000000..0a559f89f486 --- /dev/null +++ b/pkg/licensing/classifier_test.go @@ -0,0 +1,78 @@ +package licensing_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" +) + +func TestClassifier_FullClassify(t *testing.T) { + tests := []struct { + name string + filePath string + want *types.LicenseFile + wantErr assert.ErrorAssertionFunc + }{ + { + name: "C file with AGPL-3.0", + filePath: "testdata/licensed.c", + want: &types.LicenseFile{ + Type: types.LicenseTypeHeader, + FilePath: "testdata/licensed.c", + Findings: []types.LicenseFinding{ + { + Name: "AGPL-3.0", + Confidence: 1, + Link: "https://spdx.org/licenses/AGPL-3.0.html", + }, + }, + }, + }, + { + name: "Creative commons License file", + filePath: "testdata/LICENSE_creativecommons", + want: &types.LicenseFile{ + Type: types.LicenseTypeFile, + FilePath: "testdata/LICENSE_creativecommons", + Findings: []types.LicenseFinding{ + { + Name: "Commons-Clause", + Confidence: 1, + Link: "https://spdx.org/licenses/Commons-Clause.html", + }, + }, + }, + }, + { + name: "Apache 2 License file", + filePath: "testdata/LICENSE_apache2", + want: &types.LicenseFile{ + Type: types.LicenseTypeFile, + FilePath: "testdata/LICENSE_apache2", + Findings: []types.LicenseFinding{ + { + Name: "Apache-2.0", + Confidence: 1, + Link: "https://spdx.org/licenses/Apache-2.0.html", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + contentFile, err := os.Open(tt.filePath) + require.NoError(t, err) + defer contentFile.Close() + + got, err := licensing.Classify(tt.filePath, contentFile, 0.9) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/licensing/expression/expression.go b/pkg/licensing/expression/expression.go new file mode 100644 index 000000000000..59848e57eef7 --- /dev/null +++ b/pkg/licensing/expression/expression.go @@ -0,0 +1,81 @@ +package expression + +import ( + "strings" + "unicode" + + "golang.org/x/xerrors" +) + +var ( + ErrInvalidExpression = xerrors.New("invalid expression error") +) + +type NormalizeFunc func(license string) string + +func parse(license string) (Expression, error) { + l := NewLexer(strings.NewReader(license)) + if yyParse(l) != 0 { + return nil, xerrors.Errorf("license parse error: %w", l.Err()) + } else if err := l.Err(); err != nil { + return nil, err + } + + return l.result, nil +} + +func Normalize(license string, fn ...NormalizeFunc) (string, error) { + expr, err := parse(license) + if err != nil { + return "", xerrors.Errorf("license (%s) parse error: %w", license, err) + } + expr = normalize(expr, fn...) + + return expr.String(), nil +} + +func normalize(expr Expression, fn ...NormalizeFunc) Expression { + switch e := expr.(type) { + case SimpleExpr: + for _, f := range fn { + e.license = f(e.license) + } + return e + case CompoundExpr: + e.left = normalize(e.left, fn...) + e.right = normalize(e.right, fn...) + e.conjunction.literal = strings.ToUpper(e.conjunction.literal) // e.g. "and" => "AND" + return e + } + + return expr +} + +// NormalizeForSPDX replaces ' ' to '-' in license-id. +// SPDX license MUST NOT be white space between a license-id. +// There MUST be white space on either side of the operator "WITH". +// ref: https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions +func NormalizeForSPDX(s string) string { + var b strings.Builder + for _, c := range s { + switch { + // spec: idstring = 1*(ALPHA / DIGIT / "-" / "." ) + case isAlphabet(c) || unicode.IsNumber(c) || c == '-' || c == '.': + _, _ = b.WriteRune(c) + case c == ':': + // TODO: Support DocumentRef + _, _ = b.WriteRune(c) + default: + // Replace invalid characters with '-' + _, _ = b.WriteRune('-') + } + } + return b.String() +} + +func isAlphabet(r rune) bool { + if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') { + return false + } + return true +} diff --git a/pkg/licensing/expression/expression_test.go b/pkg/licensing/expression/expression_test.go new file mode 100644 index 000000000000..0e3eaa7ae7cc --- /dev/null +++ b/pkg/licensing/expression/expression_test.go @@ -0,0 +1,56 @@ +package expression + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNormalize(t *testing.T) { + tests := []struct { + name string + license string + fn NormalizeFunc + want string + wantErr string + }{ + { + name: "SPDX, space", + license: "AFL 2.0", + fn: NormalizeForSPDX, + want: "AFL-2.0", + }, + { + name: "SPDX, exception", + license: "AFL 2.0 with Linux-syscall-note exception", + fn: NormalizeForSPDX, + want: "AFL-2.0 WITH Linux-syscall-note-exception", + }, + { + name: "SPDX, invalid chars", + license: "LGPL_2.1_only or MIT OR BSD-3>Clause", + fn: NormalizeForSPDX, + want: "LGPL-2.1-only OR MIT OR BSD-3-Clause", + }, + { + name: "upper", + license: "LGPL-2.1-only OR MIT", + fn: strings.ToUpper, + want: "LGPL-2.1-ONLY OR MIT", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Normalize(tt.license, tt.fn) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + require.NoError(t, err) + assert.Equalf(t, tt.want, got, "NormalizeWithExpression(%v)", tt.license) + }) + } +} diff --git a/pkg/licensing/expression/lexer.go b/pkg/licensing/expression/lexer.go new file mode 100644 index 000000000000..64b8d17ef405 --- /dev/null +++ b/pkg/licensing/expression/lexer.go @@ -0,0 +1,119 @@ +package expression + +import ( + "bufio" + "errors" + "io" + "strings" + "unicode" + "unicode/utf8" + + multierror "github.com/hashicorp/go-multierror" +) + +type Lexer struct { + s *bufio.Scanner + result Expression + errs error +} + +func NewLexer(reader io.Reader) *Lexer { + scanner := bufio.NewScanner(reader) + scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { + // The implementation references bufio.ScanWords() + + // Skip leading spaces. + start := 0 + for width := 0; start < len(data); start += width { + var r rune + r, width = utf8.DecodeRune(data[start:]) + if !unicode.IsSpace(r) { + break + } + } + // Process terminal symbols + if len(data) > start && (data[start] == '(' || data[start] == ')' || data[start] == '+') { + return start + 1, data[start : start+1], nil + } + + // Scan until space or token, marking end of word. + for width, i := 0, start; i < len(data); i += width { + var r rune + r, width = utf8.DecodeRune(data[i:]) + switch r { + case '(', ')': + return i, data[start:i], nil + case '+': + // Peek the next rune + if len(data) > i+width { + adv := i + i += width + r, width = utf8.DecodeRune(data[i:]) + if unicode.IsSpace(r) || r == '(' || r == ')' { + return adv, data[start:adv], nil + } + } else if atEOF { + return i, data[start:i], nil + } + default: + if unicode.IsSpace(r) { + return i + width, data[start:i], nil + } + } + } + // If we're at EOF, we have a final, non-empty, non-terminated word. Return it. + if atEOF && len(data) > start { + return len(data), data[start:], nil + } + // Request more data. + return start, nil, nil + }) + + return &Lexer{ + s: scanner, + } +} + +func (l *Lexer) Lex(lval *yySymType) int { + if !l.s.Scan() { + return 0 + } + + var token int + literal := l.s.Text() + switch literal { + case "(", ")", "+": + token = int(literal[0]) + default: + token = lookup(literal) + } + + lval.token = Token{ + token: token, + literal: literal, + } + + if err := l.s.Err(); err != nil { + l.errs = multierror.Append(l.errs, l.s.Err()) + } + + return lval.token.token +} + +func (l *Lexer) Error(e string) { + l.errs = multierror.Append(l.errs, errors.New(e)) +} + +func (l *Lexer) Err() error { + return l.errs +} + +func lookup(t string) int { + t = strings.ToUpper(t) + for i, name := range yyToknames { + if t == name { + return yyPrivate + (i - 1) + } + } + return IDENT +} diff --git a/pkg/licensing/expression/lexer_test.go b/pkg/licensing/expression/lexer_test.go new file mode 100644 index 000000000000..d9492fb80465 --- /dev/null +++ b/pkg/licensing/expression/lexer_test.go @@ -0,0 +1,239 @@ +package expression + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLexer_Lex(t *testing.T) { + tests := []struct { + name string + input string + want []Token + }{ + { + name: "simple", + input: "GPL-2.0-only", + want: []Token{ + { + token: IDENT, + literal: "GPL-2.0-only", + }, + }, + }, + { + name: "with space", + input: "Public Domain", + want: []Token{ + { + token: IDENT, + literal: "Public", + }, + { + token: IDENT, + literal: "Domain", + }, + }, + }, + { + name: "and", + input: "Public Domain AND MIT", + want: []Token{ + { + token: IDENT, + literal: "Public", + }, + { + token: IDENT, + literal: "Domain", + }, + { + token: AND, + literal: "AND", + }, + { + token: IDENT, + literal: "MIT", + }, + }, + }, + { + name: "or", + input: "LGPL-2.1-only OR MIT OR BSD-3-Clause", + want: []Token{ + { + token: IDENT, + literal: "LGPL-2.1-only", + }, + { + token: OR, + literal: "OR", + }, + { + token: IDENT, + literal: "MIT", + }, + { + token: OR, + literal: "OR", + }, + { + token: IDENT, + literal: "BSD-3-Clause", + }, + }, + }, + { + name: "parenthesis", + input: "LGPL-2.1-only AND (MIT OR BSD-3-Clause)", + want: []Token{ + { + token: IDENT, + literal: "LGPL-2.1-only", + }, + { + token: AND, + literal: "AND", + }, + { + token: int('('), + literal: "(", + }, + { + token: IDENT, + literal: "MIT", + }, + { + token: OR, + literal: "OR", + }, + { + token: IDENT, + literal: "BSD-3-Clause", + }, + { + token: int(')'), + literal: ")", + }, + }, + }, + { + name: "exception", + input: "LGPL-2.1-only AND GPL-2.0-or-later WITH Bison-exception-2.2", + want: []Token{ + { + token: IDENT, + literal: "LGPL-2.1-only", + }, + { + token: AND, + literal: "AND", + }, + { + token: IDENT, + literal: "GPL-2.0-or-later", + }, + { + token: WITH, + literal: "WITH", + }, + { + token: IDENT, + literal: "Bison-exception-2.2", + }, + }, + }, + { + name: "plus", + input: "Public Domain+", + want: []Token{ + { + token: IDENT, + literal: "Public", + }, + { + token: IDENT, + literal: "Domain", + }, + { + token: int('+'), + literal: "+", + }, + }, + }, + { + name: "plus in the middle", + input: "ISC+IBM", + want: []Token{ + { + token: IDENT, + literal: "ISC+IBM", + }, + }, + }, + { + name: "plus with the parenthesis", + input: "(GPL1.0+)", + want: []Token{ + { + token: int('('), + literal: "(", + }, + { + token: IDENT, + literal: "GPL1.0", + }, + { + token: int('+'), + literal: "+", + }, + { + token: int(')'), + literal: ")", + }, + }, + }, + { + name: "utf-8", + input: "GPL1.0+ " + string(byte(0x20)) + "ã‚🇯🇵" + " and LGPL1.0", + want: []Token{ + { + token: IDENT, + literal: "GPL1.0", + }, + { + token: int('+'), + literal: "+", + }, + { + token: IDENT, + literal: "ã‚🇯🇵", + }, + { + token: AND, + literal: "and", + }, + { + token: IDENT, + literal: "LGPL1.0", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := NewLexer(strings.NewReader(tt.input)) + var got []Token + var lval yySymType + for l.Lex(&lval) != 0 { + got = append(got, lval.token) + lval = yySymType{} + } + require.NoError(t, l.Err()) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/licensing/expression/parser.go b/pkg/licensing/expression/parser.go new file mode 100644 index 000000000000..ef80d948f9cc --- /dev/null +++ b/pkg/licensing/expression/parser.go @@ -0,0 +1,2 @@ +//go:generate goyacc -o parser_gen.go parser.go.y +package expression diff --git a/pkg/licensing/expression/parser.go.y b/pkg/licensing/expression/parser.go.y new file mode 100644 index 000000000000..82bd11028619 --- /dev/null +++ b/pkg/licensing/expression/parser.go.y @@ -0,0 +1,73 @@ +%{ +package expression +%} + +%union{ + token Token + expr Expression +} + +%type license +%type simple +%type plus +%type compound +%token IDENT OR AND WITH + +%left OR +%left AND +%right WITH +%right '+' + +%% + +license + : compound + { + $$ = $1 + if l, ok := yylex.(*Lexer); ok{ + l.result = $$ + } + } + +simple + : IDENT + { + $$ = SimpleExpr{license: $1.literal} + } + | simple IDENT /* e.g. Public Domain */ + { + $$ = SimpleExpr{license: $1.String() + " " + $2.literal} + } + +plus + : simple '+' + { + $$ = SimpleExpr{license: $1.String(), hasPlus: true} + } + +compound + : simple { + $$ = $1 + } + | plus { + $$ = $1 + } + | compound AND compound /* compound-expression "AND" compound-expression */ + { + $$ = CompoundExpr{left: $1, conjunction: $2, right: $3} + } + | compound OR compound /* compound-expression "OR" compound-expression */ + { + $$ = CompoundExpr{left: $1, conjunction: $2, right: $3} + } + | compound WITH compound /* simple-expression "WITH" license-exception-id */ + { + $$ = CompoundExpr{left: $1, conjunction: $2, right: $3} + } + | '(' compound ')' + { + $$ = $2 + } + + +%% \ No newline at end of file diff --git a/pkg/licensing/expression/parser_gen.go b/pkg/licensing/expression/parser_gen.go new file mode 100644 index 000000000000..32f65276374e --- /dev/null +++ b/pkg/licensing/expression/parser_gen.go @@ -0,0 +1,507 @@ +// Code generated by goyacc -o parser_gen.go parser.go.y. DO NOT EDIT. + +//line parser.go.y:2 +package expression + +import __yyfmt__ "fmt" + +//line parser.go.y:2 + +//line parser.go.y:5 +type yySymType struct { + yys int + token Token + expr Expression +} + +const IDENT = 57346 +const OR = 57347 +const AND = 57348 +const WITH = 57349 + +var yyToknames = [...]string{ + "$end", + "error", + "$unk", + "IDENT", + "OR", + "AND", + "WITH", + "'+'", + "'('", + "')'", +} + +var yyStatenames = [...]string{} + +const yyEofCode = 1 +const yyErrCode = 2 +const yyInitialStackSize = 16 + +//line parser.go.y:73 + +//line yacctab:1 +var yyExca = [...]int8{ + -1, 1, + 1, -1, + -2, 0, +} + +const yyPrivate = 57344 + +const yyLast = 22 + +var yyAct = [...]int8{ + 8, 7, 9, 2, 10, 16, 9, 4, 11, 12, + 6, 13, 14, 15, 3, 5, 8, 7, 9, 7, + 9, 1, +} + +var yyPact = [...]int16{ + 6, -1000, 11, 0, -1000, 6, -1000, 6, 6, 6, + -1000, -1000, -5, -1, 13, -1, -1000, +} + +var yyPgo = [...]int8{ + 0, 21, 14, 7, 3, +} + +var yyR1 = [...]int8{ + 0, 1, 2, 2, 3, 4, 4, 4, 4, 4, + 4, +} + +var yyR2 = [...]int8{ + 0, 1, 1, 2, 2, 1, 1, 3, 3, 3, + 3, +} + +var yyChk = [...]int16{ + -1000, -1, -4, -2, -3, 9, 4, 6, 5, 7, + 4, 8, -4, -4, -4, -4, 10, +} + +var yyDef = [...]int8{ + 0, -2, 1, 5, 6, 0, 2, 0, 0, 0, + 3, 4, 0, 7, 8, 9, 10, +} + +var yyTok1 = [...]int8{ + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 9, 10, 3, 8, +} + +var yyTok2 = [...]int8{ + 2, 3, 4, 5, 6, 7, +} + +var yyTok3 = [...]int8{ + 0, +} + +var yyErrorMessages = [...]struct { + state int + token int + msg string +}{} + +//line yaccpar:1 + +/* parser for yacc output */ + +var ( + yyDebug = 0 + yyErrorVerbose = false +) + +type yyLexer interface { + Lex(lval *yySymType) int + Error(s string) +} + +type yyParser interface { + Parse(yyLexer) int + Lookahead() int +} + +type yyParserImpl struct { + lval yySymType + stack [yyInitialStackSize]yySymType + char int +} + +func (p *yyParserImpl) Lookahead() int { + return p.char +} + +func yyNewParser() yyParser { + return &yyParserImpl{} +} + +const yyFlag = -1000 + +func yyTokname(c int) string { + if c >= 1 && c-1 < len(yyToknames) { + if yyToknames[c-1] != "" { + return yyToknames[c-1] + } + } + return __yyfmt__.Sprintf("tok-%v", c) +} + +func yyStatname(s int) string { + if s >= 0 && s < len(yyStatenames) { + if yyStatenames[s] != "" { + return yyStatenames[s] + } + } + return __yyfmt__.Sprintf("state-%v", s) +} + +func yyErrorMessage(state, lookAhead int) string { + const TOKSTART = 4 + + if !yyErrorVerbose { + return "syntax error" + } + + for _, e := range yyErrorMessages { + if e.state == state && e.token == lookAhead { + return "syntax error: " + e.msg + } + } + + res := "syntax error: unexpected " + yyTokname(lookAhead) + + // To match Bison, suggest at most four expected tokens. + expected := make([]int, 0, 4) + + // Look for shiftable tokens. + base := int(yyPact[state]) + for tok := TOKSTART; tok-1 < len(yyToknames); tok++ { + if n := base + tok; n >= 0 && n < yyLast && int(yyChk[int(yyAct[n])]) == tok { + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + } + + if yyDef[state] == -2 { + i := 0 + for yyExca[i] != -1 || int(yyExca[i+1]) != state { + i += 2 + } + + // Look for tokens that we accept or reduce. + for i += 2; yyExca[i] >= 0; i += 2 { + tok := int(yyExca[i]) + if tok < TOKSTART || yyExca[i+1] == 0 { + continue + } + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + + // If the default action is to accept or reduce, give up. + if yyExca[i+1] != 0 { + return res + } + } + + for i, tok := range expected { + if i == 0 { + res += ", expecting " + } else { + res += " or " + } + res += yyTokname(tok) + } + return res +} + +func yylex1(lex yyLexer, lval *yySymType) (char, token int) { + token = 0 + char = lex.Lex(lval) + if char <= 0 { + token = int(yyTok1[0]) + goto out + } + if char < len(yyTok1) { + token = int(yyTok1[char]) + goto out + } + if char >= yyPrivate { + if char < yyPrivate+len(yyTok2) { + token = int(yyTok2[char-yyPrivate]) + goto out + } + } + for i := 0; i < len(yyTok3); i += 2 { + token = int(yyTok3[i+0]) + if token == char { + token = int(yyTok3[i+1]) + goto out + } + } + +out: + if token == 0 { + token = int(yyTok2[1]) /* unknown char */ + } + if yyDebug >= 3 { + __yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char)) + } + return char, token +} + +func yyParse(yylex yyLexer) int { + return yyNewParser().Parse(yylex) +} + +func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int { + var yyn int + var yyVAL yySymType + var yyDollar []yySymType + _ = yyDollar // silence set and not used + yyS := yyrcvr.stack[:] + + Nerrs := 0 /* number of errors */ + Errflag := 0 /* error recovery flag */ + yystate := 0 + yyrcvr.char = -1 + yytoken := -1 // yyrcvr.char translated into internal numbering + defer func() { + // Make sure we report no lookahead when not parsing. + yystate = -1 + yyrcvr.char = -1 + yytoken = -1 + }() + yyp := -1 + goto yystack + +ret0: + return 0 + +ret1: + return 1 + +yystack: + /* put a state and value onto the stack */ + if yyDebug >= 4 { + __yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate)) + } + + yyp++ + if yyp >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyS[yyp] = yyVAL + yyS[yyp].yys = yystate + +yynewstate: + yyn = int(yyPact[yystate]) + if yyn <= yyFlag { + goto yydefault /* simple state */ + } + if yyrcvr.char < 0 { + yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) + } + yyn += yytoken + if yyn < 0 || yyn >= yyLast { + goto yydefault + } + yyn = int(yyAct[yyn]) + if int(yyChk[yyn]) == yytoken { /* valid shift */ + yyrcvr.char = -1 + yytoken = -1 + yyVAL = yyrcvr.lval + yystate = yyn + if Errflag > 0 { + Errflag-- + } + goto yystack + } + +yydefault: + /* default state action */ + yyn = int(yyDef[yystate]) + if yyn == -2 { + if yyrcvr.char < 0 { + yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) + } + + /* look through exception table */ + xi := 0 + for { + if yyExca[xi+0] == -1 && int(yyExca[xi+1]) == yystate { + break + } + xi += 2 + } + for xi += 2; ; xi += 2 { + yyn = int(yyExca[xi+0]) + if yyn < 0 || yyn == yytoken { + break + } + } + yyn = int(yyExca[xi+1]) + if yyn < 0 { + goto ret0 + } + } + if yyn == 0 { + /* error ... attempt to resume parsing */ + switch Errflag { + case 0: /* brand new error */ + yylex.Error(yyErrorMessage(yystate, yytoken)) + Nerrs++ + if yyDebug >= 1 { + __yyfmt__.Printf("%s", yyStatname(yystate)) + __yyfmt__.Printf(" saw %s\n", yyTokname(yytoken)) + } + fallthrough + + case 1, 2: /* incompletely recovered error ... try again */ + Errflag = 3 + + /* find a state where "error" is a legal shift action */ + for yyp >= 0 { + yyn = int(yyPact[yyS[yyp].yys]) + yyErrCode + if yyn >= 0 && yyn < yyLast { + yystate = int(yyAct[yyn]) /* simulate a shift of "error" */ + if int(yyChk[yystate]) == yyErrCode { + goto yystack + } + } + + /* the current p has no shift on "error", pop stack */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) + } + yyp-- + } + /* there is no state on the stack with an error shift ... abort */ + goto ret1 + + case 3: /* no shift yet; clobber input char */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken)) + } + if yytoken == yyEofCode { + goto ret1 + } + yyrcvr.char = -1 + yytoken = -1 + goto yynewstate /* try again in the same state */ + } + } + + /* reduction by production yyn */ + if yyDebug >= 2 { + __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) + } + + yynt := yyn + yypt := yyp + _ = yypt // guard against "declared and not used" + + yyp -= int(yyR2[yyn]) + // yyp is now the index of $0. Perform the default action. Iff the + // reduced production is ε, $1 is possibly out of range. + if yyp+1 >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyVAL = yyS[yyp+1] + + /* consult goto table to find next state */ + yyn = int(yyR1[yyn]) + yyg := int(yyPgo[yyn]) + yyj := yyg + yyS[yyp].yys + 1 + + if yyj >= yyLast { + yystate = int(yyAct[yyg]) + } else { + yystate = int(yyAct[yyj]) + if int(yyChk[yystate]) != -yyn { + yystate = int(yyAct[yyg]) + } + } + // dummy call; replaced with literal code + switch yynt { + + case 1: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:25 + { + yyVAL.expr = yyDollar[1].expr + if l, ok := yylex.(*Lexer); ok { + l.result = yyVAL.expr + } + } + case 2: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:34 + { + yyVAL.expr = SimpleExpr{license: yyDollar[1].token.literal} + } + case 3: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:38 + { + yyVAL.expr = SimpleExpr{license: yyDollar[1].expr.String() + " " + yyDollar[2].token.literal} + } + case 4: + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:44 + { + yyVAL.expr = SimpleExpr{license: yyDollar[1].expr.String(), hasPlus: true} + } + case 5: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:49 + { + yyVAL.expr = yyDollar[1].expr + } + case 6: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:52 + { + yyVAL.expr = yyDollar[1].expr + } + case 7: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:56 + { + yyVAL.expr = CompoundExpr{left: yyDollar[1].expr, conjunction: yyDollar[2].token, right: yyDollar[3].expr} + } + case 8: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:60 + { + yyVAL.expr = CompoundExpr{left: yyDollar[1].expr, conjunction: yyDollar[2].token, right: yyDollar[3].expr} + } + case 9: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:64 + { + yyVAL.expr = CompoundExpr{left: yyDollar[1].expr, conjunction: yyDollar[2].token, right: yyDollar[3].expr} + } + case 10: + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:68 + { + yyVAL.expr = yyDollar[2].expr + } + } + goto yystack /* stack new state and value */ +} diff --git a/pkg/licensing/expression/parser_test.go b/pkg/licensing/expression/parser_test.go new file mode 100644 index 000000000000..a828df902008 --- /dev/null +++ b/pkg/licensing/expression/parser_test.go @@ -0,0 +1,156 @@ +package expression + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + input string + want Expression + wantStr string + wantErr string + }{ + { + name: "single license", + input: "Public Domain", + want: SimpleExpr{ + license: "Public Domain", + }, + wantStr: "Public Domain", + }, + { + name: "tag:value license", + input: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", + want: SimpleExpr{ + license: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", + }, + wantStr: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", + }, + { + name: "symbols", + input: "Public ._-+", + want: SimpleExpr{ + license: "Public ._-", + hasPlus: true, + }, + wantStr: "Public ._-+", + }, + { + name: "multi licenses", + input: "Public Domain AND ( GPLv2+ or AFL ) AND LGPLv2+ with distribution exceptions", + want: CompoundExpr{ + left: CompoundExpr{ + left: SimpleExpr{ + license: "Public Domain", + }, + conjunction: Token{ + token: AND, + literal: "AND", + }, + right: CompoundExpr{ + left: SimpleExpr{ + license: "GPLv2", + hasPlus: true, + }, + conjunction: Token{ + token: OR, + literal: "or", + }, + right: SimpleExpr{ + license: "AFL", + }, + }, + }, + conjunction: Token{ + token: AND, + literal: "AND", + }, + right: CompoundExpr{ + left: SimpleExpr{ + license: "LGPLv2", + hasPlus: true, + }, + conjunction: Token{ + token: WITH, + literal: "with", + }, + right: SimpleExpr{ + license: "distribution exceptions", + }, + }, + }, + wantStr: "Public Domain AND (GPLv2+ or AFL) AND LGPLv2+ with distribution exceptions", + }, + { + name: "nested licenses", + input: "Public Domain AND ( GPLv2+ or AFL AND ( CC0 or LGPL1.0) )", + want: CompoundExpr{ + left: SimpleExpr{ + license: "Public Domain", + }, + conjunction: Token{ + token: AND, + literal: "AND", + }, + right: CompoundExpr{ + left: SimpleExpr{ + license: "GPLv2", + hasPlus: true, + }, + conjunction: Token{ + token: OR, + literal: "or", + }, + right: CompoundExpr{ + left: SimpleExpr{ + license: "AFL", + }, + conjunction: Token{ + token: AND, + literal: "AND", + }, + right: CompoundExpr{ + left: SimpleExpr{ + license: "CC0", + }, + conjunction: Token{ + token: OR, + literal: "or", + }, + right: SimpleExpr{ + license: "LGPL1.0", + }, + }, + }, + }, + }, + wantStr: "Public Domain AND (GPLv2+ or AFL AND (CC0 or LGPL1.0))", + }, + { + name: "bad path close bracket not found", + input: "Public Domain AND ( GPLv2+ ", + wantErr: "syntax error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := NewLexer(strings.NewReader(tt.input)) + ret := yyParse(l) + err := l.Err() + if tt.wantErr != "" { + assert.Equal(t, ret, 1) + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, l.result) + assert.Equal(t, tt.wantStr, l.result.String()) + }) + } +} diff --git a/pkg/licensing/expression/types.go b/pkg/licensing/expression/types.go new file mode 100644 index 000000000000..f5315f4ffb0f --- /dev/null +++ b/pkg/licensing/expression/types.go @@ -0,0 +1,83 @@ +package expression + +import ( + "fmt" + + "golang.org/x/exp/slices" + + "github.com/aquasecurity/trivy/pkg/licensing" +) + +var versioned = []string{ + licensing.AGPL10, + licensing.AGPL30, + licensing.GFDL11WithInvariants, + licensing.GFDL11NoInvariants, + licensing.GFDL11, + licensing.GFDL12WithInvariants, + licensing.GFDL12NoInvariants, + licensing.GFDL12, + licensing.GFDL13WithInvariants, + licensing.GFDL13NoInvariants, + licensing.GFDL13, + licensing.GPL10, + licensing.GPL20, + licensing.GPL30, + licensing.LGPL20, + licensing.LGPL21, + licensing.LGPL30, +} + +type Expression interface { + String() string +} + +type Token struct { + token int + literal string +} + +type SimpleExpr struct { + license string + hasPlus bool +} + +func (s SimpleExpr) String() string { + if slices.Contains(versioned, s.license) { + if s.hasPlus { + // e.g. AGPL-1.0-or-later + return s.license + "-or-later" + } + // e.g. GPL-1.0-only + return s.license + "-only" + } + + if s.hasPlus { + return s.license + "+" + } + return s.license +} + +type CompoundExpr struct { + left Expression + conjunction Token + right Expression +} + +func (c CompoundExpr) String() string { + left := c.left.String() + if l, ok := c.left.(CompoundExpr); ok { + // e.g. (A OR B) AND C + if c.conjunction.token > l.conjunction.token { + left = fmt.Sprintf("(%s)", left) + } + } + right := c.right.String() + if r, ok := c.right.(CompoundExpr); ok { + // e.g. A AND (B OR C) + if c.conjunction.token > r.conjunction.token { + right = fmt.Sprintf("(%s)", right) + } + } + return fmt.Sprintf("%s %s %s", left, c.conjunction.literal, right) +} diff --git a/pkg/licensing/expression/y.output b/pkg/licensing/expression/y.output new file mode 100644 index 000000000000..6e39c0c8f4b6 --- /dev/null +++ b/pkg/licensing/expression/y.output @@ -0,0 +1,172 @@ + +state 0 + $accept: .license $end + + IDENT shift 6 + '(' shift 5 + . error + + license goto 1 + simple goto 3 + plus goto 4 + compound goto 2 + +state 1 + $accept: license.$end + + $end accept + . error + + +state 2 + license: compound. (1) + compound: compound.AND compound + compound: compound.OR compound + compound: compound.WITH compound + + OR shift 8 + AND shift 7 + WITH shift 9 + . reduce 1 (src line 23) + + +state 3 + simple: simple.IDENT + plus: simple.'+' + compound: simple. (5) + + IDENT shift 10 + '+' shift 11 + . reduce 5 (src line 48) + + +state 4 + compound: plus. (6) + + . reduce 6 (src line 52) + + +state 5 + compound: '('.compound ')' + + IDENT shift 6 + '(' shift 5 + . error + + simple goto 3 + plus goto 4 + compound goto 12 + +state 6 + simple: IDENT. (2) + + . reduce 2 (src line 32) + + +state 7 + compound: compound AND.compound + + IDENT shift 6 + '(' shift 5 + . error + + simple goto 3 + plus goto 4 + compound goto 13 + +state 8 + compound: compound OR.compound + + IDENT shift 6 + '(' shift 5 + . error + + simple goto 3 + plus goto 4 + compound goto 14 + +state 9 + compound: compound WITH.compound + + IDENT shift 6 + '(' shift 5 + . error + + simple goto 3 + plus goto 4 + compound goto 15 + +state 10 + simple: simple IDENT. (3) + + . reduce 3 (src line 37) + + +state 11 + plus: simple '+'. (4) + + . reduce 4 (src line 42) + + +state 12 + compound: compound.AND compound + compound: compound.OR compound + compound: compound.WITH compound + compound: '(' compound.')' + + OR shift 8 + AND shift 7 + WITH shift 9 + ')' shift 16 + . error + + +state 13 + compound: compound.AND compound + compound: compound AND compound. (7) + compound: compound.OR compound + compound: compound.WITH compound + + WITH shift 9 + . reduce 7 (src line 55) + + +state 14 + compound: compound.AND compound + compound: compound.OR compound + compound: compound OR compound. (8) + compound: compound.WITH compound + + AND shift 7 + WITH shift 9 + . reduce 8 (src line 59) + + +state 15 + compound: compound.AND compound + compound: compound.OR compound + compound: compound.WITH compound + compound: compound WITH compound. (9) + + WITH shift 9 + . reduce 9 (src line 63) + + +state 16 + compound: '(' compound ')'. (10) + + . reduce 10 (src line 67) + + +10 terminals, 5 nonterminals +11 grammar rules, 17/16000 states +0 shift/reduce, 0 reduce/reduce conflicts reported +54 working sets used +memory: parser 15/240000 +13 extra closures +23 shift entries, 1 exceptions +8 goto entries +8 entries saved by goto default +Optimizer space used: output 22/240000 +22 table entries, 0 zero +maximum spread: 10, maximum offset: 9 diff --git a/pkg/licensing/normalize.go b/pkg/licensing/normalize.go new file mode 100644 index 000000000000..0493747a9430 --- /dev/null +++ b/pkg/licensing/normalize.go @@ -0,0 +1,214 @@ +package licensing + +import ( + "regexp" + "strings" +) + +var mapping = map[string]string{ + // GPL + "GPL-1": GPL10, + "GPL-1+": GPL10, + "GPL 1.0": GPL10, + "GPL 1": GPL10, + "GPL2": GPL20, + "GPL 2.0": GPL20, + "GPL 2": GPL20, + "GPL-2": GPL20, + "GPL-2.0-ONLY": GPL20, + "GPL2+": GPL20, + "GPLV2": GPL20, + "GPLV2+": GPL20, + "GPL-2+": GPL20, + "GPL-2.0+": GPL20, + "GPL-2.0-OR-LATER": GPL20, + "GPL-2+ WITH AUTOCONF EXCEPTION": GPL20withautoconfexception, + "GPL-2+-with-bison-exception": GPL20withbisonexception, + "GPL3": GPL30, + "GPL 3.0": GPL30, + "GPL 3": GPL30, + "GPLV3": GPL30, + "GPLV3+": GPL30, + "GPL-3": GPL30, + "GPL-3.0-ONLY": GPL30, + "GPL3+": GPL30, + "GPL-3+": GPL30, + "GPL-3.0-OR-LATER": GPL30, + "GPL-3+ WITH AUTOCONF EXCEPTION": GPL30withautoconfexception, + "GPL-3+-WITH-BISON-EXCEPTION": GPL20withbisonexception, + "GPL": GPL30, // 2? 3? + + // LGPL + "LGPL2": LGPL20, + "LGPL 2": LGPL20, + "LGPL 2.0": LGPL20, + "LGPL-2": LGPL20, + "LGPL2+": LGPL20, + "LGPL-2+": LGPL20, + "LGPL-2.0+": LGPL20, + "LGPL-2.1": LGPL21, + "LGPL 2.1": LGPL21, + "LGPL-2.1+": LGPL21, + "LGPLV2.1+": LGPL21, + "LGPL-3": LGPL30, + "LGPL 3": LGPL30, + "LGPL-3+": LGPL30, + "LGPL": LGPL30, // 2? 3? + "GNU LESSER": LGPL30, // 2? 3? + + // MPL + "MPL1.0": MPL10, + "MPL1": MPL10, + "MPL 1.0": MPL10, + "MPL 1": MPL10, + "MPL2.0": MPL20, + "MPL 2.0": MPL20, + "MPL2": MPL20, + "MPL 2": MPL20, + + // BSD + "BSD": BSD3Clause, // 2? 3? + "BSD-2-CLAUSE": BSD2Clause, + "BSD-3-CLAUSE": BSD3Clause, + "BSD-4-CLAUSE": BSD4Clause, + "BSD 2 CLAUSE": BSD2Clause, + "BSD 2-CLAUSE": BSD2Clause, + "BSD 2-CLAUSE LICENSE": BSD2Clause, + "THE BSD 2-CLAUSE LICENSE": BSD2Clause, + "THE 2-CLAUSE BSD LICENSE": BSD2Clause, + "TWO-CLAUSE BSD-STYLE LICENSE": BSD2Clause, + "BSD 3 CLAUSE": BSD3Clause, + "BSD 3-CLAUSE": BSD3Clause, + "BSD 3-CLAUSE LICENSE": BSD3Clause, + "THE BSD 3-CLAUSE LICENSE": BSD3Clause, + "BSD 3-CLAUSE \"NEW\" OR \"REVISED\" LICENSE (BSD-3-CLAUSE)": BSD3Clause, + "ECLIPSE DISTRIBUTION LICENSE (NEW BSD LICENSE)": BSD3Clause, + "NEW BSD LICENSE": BSD3Clause, + "MODIFIED BSD LICENSE": BSD3Clause, + "REVISED BSD": BSD3Clause, + "REVISED BSD LICENSE": BSD3Clause, + "THE NEW BSD LICENSE": BSD3Clause, + "3-CLAUSE BSD LICENSE": BSD3Clause, + "BSD 3-CLAUSE NEW LICENSE": BSD3Clause, + "BSD LICENSE": BSD3Clause, + "EDL 1.0": BSD3Clause, + "ECLIPSE DISTRIBUTION LICENSE - V 1.0": BSD3Clause, + "ECLIPSE DISTRIBUTION LICENSE V. 1.0": BSD3Clause, + "ECLIPSE DISTRIBUTION LICENSE V1.0": BSD3Clause, + "THE BSD LICENSE": BSD4Clause, + + // APACHE + "APACHE LICENSE": Apache10, + "APACHE SOFTWARE LICENSES": Apache10, + "APACHE": Apache20, // 1? 2? + "APACHE 2.0": Apache20, + "APACHE 2": Apache20, + "APACHE V2": Apache20, + "APACHE 2.0 LICENSE": Apache20, + "APACHE SOFTWARE LICENSE, VERSION 2.0": Apache20, + "THE APACHE SOFTWARE LICENSE, VERSION 2.0": Apache20, + "APACHE LICENSE (V2.0)": Apache20, + "APACHE LICENSE 2.0": Apache20, + "APACHE LICENSE V2.0": Apache20, + "APACHE LICENSE VERSION 2.0": Apache20, + "APACHE LICENSE, VERSION 2.0": Apache20, + "APACHE PUBLIC LICENSE 2.0": Apache20, + "APACHE SOFTWARE LICENSE - VERSION 2.0": Apache20, + "THE APACHE LICENSE, VERSION 2.0": Apache20, + "APACHE-2.0 LICENSE": Apache20, + "APACHE 2 STYLE LICENSE": Apache20, + "ASF 2.0": Apache20, + + // CC0-1.0 + "CC0 1.0 UNIVERSAL": CC010, + "PUBLIC DOMAIN, PER CREATIVE COMMONS CC0": CC010, + + // CDDL 1.0 + "CDDL 1.0": CDDL10, + "CDDL LICENSE": CDDL10, + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.0": CDDL10, + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.0": CDDL10, + + // CDDL 1.1 + "CDDL 1.1": CDDL11, + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.1": CDDL11, + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.1": CDDL11, + + // EPL 1.0 + "ECLIPSE PUBLIC LICENSE - VERSION 1.0": EPL10, + "ECLIPSE PUBLIC LICENSE (EPL) 1.0": EPL10, + "ECLIPSE PUBLIC LICENSE V1.0": EPL10, + "ECLIPSE PUBLIC LICENSE, VERSION 1.0": EPL10, + "ECLIPSE PUBLIC LICENSE - V 1.0": EPL10, + "ECLIPSE PUBLIC LICENSE - V1.0": EPL10, + "ECLIPSE PUBLIC LICENSE (EPL), VERSION 1.0": EPL10, + + // EPL 2.0 + "ECLIPSE PUBLIC LICENSE - VERSION 2.0": EPL20, + "EPL 2.0": EPL20, + "ECLIPSE PUBLIC LICENSE - V 2.0": EPL20, + "ECLIPSE PUBLIC LICENSE V2.0": EPL20, + "ECLIPSE PUBLIC LICENSE, VERSION 2.0": EPL20, + "THE ECLIPSE PUBLIC LICENSE VERSION 2.0": EPL20, + "ECLIPSE PUBLIC LICENSE V. 2.0": EPL20, + + "RUBY": Ruby, + "ZLIB": Zlib, + + // Public Domain + "PUBLIC DOMAIN": Unlicense, +} + +// pythonLicenseExceptions contains licenses that we cannot separate correctly using our logic. +// first word after separator (or/and) => license name +var pythonLicenseExceptions = map[string]string{ + "lesser": "GNU Library or Lesser General Public License (LGPL)", + "distribution": "Common Development and Distribution License 1.0 (CDDL-1.0)", + "disclaimer": "Historical Permission Notice and Disclaimer (HPND)", +} + +// Split licenses without considering "and"/"or" +// examples: +// 'GPL-1+,GPL-2' => {"GPL-1+", "GPL-2"} +// 'GPL-1+ or Artistic or Artistic-dist' => {"GPL-1+", "Artistic", "Artistic-dist"} +// 'LGPLv3+_or_GPLv2+' => {"LGPLv3+", "GPLv2"} +// 'BSD-3-CLAUSE and GPL-2' => {"BSD-3-CLAUSE", "GPL-2"} +// 'GPL-1+ or Artistic, and BSD-4-clause-POWERDOG' => {"GPL-1+", "Artistic", "BSD-4-clause-POWERDOG"} +// 'BSD 3-Clause License or Apache License, Version 2.0' => {"BSD 3-Clause License", "Apache License, Version 2.0"} +// var LicenseSplitRegexp = regexp.MustCompile("(,?[_ ]+or[_ ]+)|(,?[_ ]+and[_ ])|(,[ ]*)") + +var licenseSplitRegexp = regexp.MustCompile("(,?[_ ]+(?:or|and)[_ ]+)|(,[ ]*)") + +func Normalize(name string) string { + if l, ok := mapping[strings.ToUpper(name)]; ok { + return l + } + return name +} + +func SplitLicenses(str string) []string { + var licenses []string + for _, maybeLic := range licenseSplitRegexp.Split(str, -1) { + lower := strings.ToLower(maybeLic) + firstWord, _, _ := strings.Cut(lower, " ") + if len(licenses) > 0 { + // e.g. `Apache License, Version 2.0` + if firstWord == "ver" || firstWord == "version" { + licenses[len(licenses)-1] += ", " + maybeLic + continue + // e.g. `GNU Lesser General Public License v2 or later (LGPLv2+)` + } else if firstWord == "later" { + licenses[len(licenses)-1] += " or " + maybeLic + continue + } else if lic, ok := pythonLicenseExceptions[firstWord]; ok { + // Check `or` and `and` separators + if lic == licenses[len(licenses)-1]+" or "+maybeLic || lic == licenses[len(licenses)-1]+" and "+maybeLic { + licenses[len(licenses)-1] = lic + } + continue + } + } + licenses = append(licenses, maybeLic) + } + return licenses +} diff --git a/pkg/licensing/normalize_test.go b/pkg/licensing/normalize_test.go new file mode 100644 index 000000000000..28934f4f2340 --- /dev/null +++ b/pkg/licensing/normalize_test.go @@ -0,0 +1,108 @@ +package licensing_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/licensing" +) + +func TestSplitLicenses(t *testing.T) { + tests := []struct { + name string + license string + licenses []string + }{ + { + "simple list comma-separated", + "GPL-1+,GPL-2", + []string{ + "GPL-1+", + "GPL-2", + }, + }, + { + "simple list comma-separated", + "GPL-1+,GPL-2,GPL-3", + []string{ + "GPL-1+", + "GPL-2", + "GPL-3", + }, + }, + { + "3 licenses 'or'-separated", + "GPL-1+ or Artistic or Artistic-dist", + []string{ + "GPL-1+", + "Artistic", + "Artistic-dist", + }, + }, + { + "two licenses _or_ separated", + "LGPLv3+_or_GPLv2+", + []string{ + "LGPLv3+", + "GPLv2+", + }, + }, + { + "licenses `and`-separated", + "BSD-3-CLAUSE and GPL-2", + []string{ + "BSD-3-CLAUSE", + "GPL-2", + }, + }, + { + "three licenses and/or separated", + "GPL-1+ or Artistic, and BSD-4-clause-POWERDOG", + []string{ + "GPL-1+", + "Artistic", + "BSD-4-clause-POWERDOG", + }, + }, + { + "two licenses with version", + "Apache License,Version 2.0, OSET Public License version 2.1", + []string{ + "Apache License, Version 2.0", + "OSET Public License version 2.1", + }, + }, + { + "the license starts with `ver`", + "verbatim and BSD-4-clause", + []string{ + "verbatim", + "BSD-4-clause", + }, + }, + { + "the license with `or later`", + "GNU Affero General Public License v3 or later (AGPLv3+)", + []string{ + "GNU Affero General Public License v3 or later (AGPLv3+)", + }, + }, + { + "Python license exceptions", + "GNU Library or Lesser General Public License (LGPL), Common Development and Distribution License 1.0 (CDDL-1.0), Historical Permission Notice and Disclaimer (HPND)", + []string{ + "GNU Library or Lesser General Public License (LGPL)", + "Common Development and Distribution License 1.0 (CDDL-1.0)", + "Historical Permission Notice and Disclaimer (HPND)", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res := licensing.SplitLicenses(tt.license) + assert.Equal(t, tt.licenses, res) + }) + } +} diff --git a/pkg/licensing/scanner.go b/pkg/licensing/scanner.go new file mode 100644 index 000000000000..b246a05b3a01 --- /dev/null +++ b/pkg/licensing/scanner.go @@ -0,0 +1,44 @@ +package licensing + +import ( + "golang.org/x/exp/slices" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type ScannerOption struct { + IgnoredLicenses []string + LicenseCategories map[types.LicenseCategory][]string +} + +type Scanner struct { + categories map[types.LicenseCategory][]string +} + +func NewScanner(categories map[types.LicenseCategory][]string) Scanner { + return Scanner{categories: categories} +} + +func (s *Scanner) Scan(licenseName string) (types.LicenseCategory, string) { + for category, names := range s.categories { + if slices.Contains(names, licenseName) { + return category, categoryToSeverity(category).String() + } + } + return types.CategoryUnknown, dbTypes.SeverityUnknown.String() +} + +func categoryToSeverity(category types.LicenseCategory) dbTypes.Severity { + switch category { + case types.CategoryForbidden: + return dbTypes.SeverityCritical + case types.CategoryRestricted: + return dbTypes.SeverityHigh + case types.CategoryReciprocal: + return dbTypes.SeverityMedium + case types.CategoryNotice, types.CategoryPermissive, types.CategoryUnencumbered: + return dbTypes.SeverityLow + } + return dbTypes.SeverityUnknown +} diff --git a/pkg/licensing/scanner_test.go b/pkg/licensing/scanner_test.go new file mode 100644 index 000000000000..11102e27a44a --- /dev/null +++ b/pkg/licensing/scanner_test.go @@ -0,0 +1,63 @@ +package licensing_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" +) + +func TestScanner_Scan(t *testing.T) { + tests := []struct { + name string + categories map[types.LicenseCategory][]string + licenseName string + wantCategory types.LicenseCategory + wantSeverity string + }{ + { + name: "forbidden", + categories: map[types.LicenseCategory][]string{ + types.CategoryForbidden: { + licensing.BSD3Clause, + licensing.Apache20, + }, + }, + licenseName: licensing.Apache20, + wantCategory: types.CategoryForbidden, + wantSeverity: "CRITICAL", + }, + { + name: "restricted", + categories: map[types.LicenseCategory][]string{ + types.CategoryForbidden: { + licensing.GPL30, + }, + types.CategoryRestricted: { + licensing.BSD3Clause, + licensing.Apache20, + }, + }, + licenseName: licensing.BSD3Clause, + wantCategory: types.CategoryRestricted, + wantSeverity: "HIGH", + }, + { + name: "unknown", + categories: map[types.LicenseCategory][]string{}, + licenseName: licensing.BSD3Clause, + wantCategory: types.CategoryUnknown, + wantSeverity: "UNKNOWN", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := licensing.NewScanner(tt.categories) + gotCategory, gotSeverity := s.Scan(tt.licenseName) + assert.Equalf(t, tt.wantCategory, gotCategory, "Scan(%v)", tt.licenseName) + assert.Equalf(t, tt.wantSeverity, gotSeverity, "Scan(%v)", tt.licenseName) + }) + } +} diff --git a/pkg/licensing/testdata/LICENSE_apache2 b/pkg/licensing/testdata/LICENSE_apache2 new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/pkg/licensing/testdata/LICENSE_apache2 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pkg/licensing/testdata/LICENSE_creativecommons b/pkg/licensing/testdata/LICENSE_creativecommons new file mode 100644 index 000000000000..85097f820dd2 --- /dev/null +++ b/pkg/licensing/testdata/LICENSE_creativecommons @@ -0,0 +1,13 @@ +Commons Clause Restriction + +The Software is provided to you by the Licensor under the License, as defined below, subject to +the following condition. + +Without limiting other conditions in the License, the grant of rights under the License will not +include, and the License does not grant to you, the right to Sell the Software. +For purposes of the foregoing, “Sell†means practicing any or all of the rights granted to you +under the License to provide to third parties, for a fee or other consideration (including without +limitation fees for hosting or consulting/ support services related to the Software), a product or +service whose value derives, entirely or substantially, from the functionality of the Software. +Any license notice or attribution required by the License must also include this Commons Cause +License Condition notice. diff --git a/pkg/licensing/testdata/licensed.c b/pkg/licensing/testdata/licensed.c new file mode 100644 index 000000000000..f66b373b7e68 --- /dev/null +++ b/pkg/licensing/testdata/licensed.c @@ -0,0 +1,24 @@ +/* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. + +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ + +int func1() { + /* implementation */ + return 0; +} + +int func2() { + /* implementation */ + return 0; +} diff --git a/pkg/licensing/testdata/styles.css b/pkg/licensing/testdata/styles.css new file mode 100644 index 000000000000..9e842cbc841c --- /dev/null +++ b/pkg/licensing/testdata/styles.css @@ -0,0 +1,409 @@ +/*! + * Datepicker for Bootstrap + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * +*/ + +.datepicker { + padding: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + direction: ltr; + /*.dow { + border-top: 1px solid #ddd !important; + }*/ + +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; + top: -7px; + left: 6px; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + position: absolute; + top: -6px; + left: 7px; +} +.datepicker > div { + display: none; +} +.datepicker.days div.datepicker-days { + display: block; +} +.datepicker.months div.datepicker-months { + display: block; +} +.datepicker.years div.datepicker-years { + display: block; +} +.datepicker table { + margin: 0; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: linear-gradient(to top, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000 !important; +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { + background-color: #006dcc; + background-image: linear-gradient(to top, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + background-color: #006dcc; + background-image: linear-gradient(to top, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span.old { + color: #999999; +} +.datepicker th.switch { + width: 145px; +} +.datepicker thead tr:first-child th, +.datepicker tfoot tr:first-child th { + cursor: pointer; +} +.datepicker thead tr:first-child th:hover, +.datepicker tfoot tr:first-child th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.datepicker thead tr:first-child th.cw { + cursor: default; + background-color: transparent; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + display: block; + cursor: pointer; + width: 16px; + height: 16px; +} +/*! + * Timepicker Component for Twitter Bootstrap + * + * Copyright 2013 Joris de Wit + * + * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +.bootstrap-timepicker { + position: relative; +} +.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu { + left: auto; + right: 0; +} +.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before { + left: auto; + right: 12px; +} +.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after { + left: auto; + right: 13px; +} +.bootstrap-timepicker .add-on { + cursor: pointer; +} +.bootstrap-timepicker .add-on i { + display: inline-block; + width: 16px; + height: 16px; +} +.bootstrap-timepicker-widget.dropdown-menu { + padding: 2px 3px 2px 2px; +} +.bootstrap-timepicker-widget.dropdown-menu.open { + display: inline-block; +} +.bootstrap-timepicker-widget.dropdown-menu:before { + border-bottom: 7px solid rgba(0, 0, 0, 0.2); + border-left: 7px solid transparent; + border-right: 7px solid transparent; + content: ""; + display: inline-block; + left: 9px; + position: absolute; + top: -7px; +} +.bootstrap-timepicker-widget.dropdown-menu:after { + border-bottom: 6px solid #FFFFFF; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + content: ""; + display: inline-block; + left: 10px; + position: absolute; + top: -6px; +} +.bootstrap-timepicker-widget a.btn, +.bootstrap-timepicker-widget input { + border-radius: 4px; +} +.bootstrap-timepicker-widget table { + width: 100%; + margin: 0; +} +.bootstrap-timepicker-widget table td { + text-align: center; + height: 30px; + margin: 0; + padding: 2px; +} +.bootstrap-timepicker-widget table td:not(.separator) { + min-width: 30px; +} +.bootstrap-timepicker-widget table td span { + width: 100%; +} +.bootstrap-timepicker-widget table td a { + border: 1px transparent solid; + width: 100%; + display: inline-block; + margin: 0; + padding: 8px 0; + outline: 0; + color: #333; +} +.bootstrap-timepicker-widget table td a:hover { + text-decoration: none; + background-color: #eee; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border-color: #ddd; +} +.bootstrap-timepicker-widget table td a i { + margin-top: 2px; +} +.bootstrap-timepicker-widget table td input { + width: 25px; + margin: 0; + text-align: center; +} +.bootstrap-timepicker-widget .modal-content { + padding: 4px; +} +@media (min-width: 767px) { + .bootstrap-timepicker-widget.modal { + width: 200px; + margin-left: -100px; + } +} +@media (max-width: 767px) { + .bootstrap-timepicker { + width: 100%; + } + + .bootstrap-timepicker .dropdown-menu { + width: 100%; + } +} \ No newline at end of file diff --git a/pkg/licensing/testdata/unlicensed.c b/pkg/licensing/testdata/unlicensed.c new file mode 100644 index 000000000000..30a3abaf63b6 --- /dev/null +++ b/pkg/licensing/testdata/unlicensed.c @@ -0,0 +1,10 @@ + +int func1() { + /* implementation */ + return 0; +} + +int func2() { + /* implementation */ + return 0; +} diff --git a/pkg/log/logger.go b/pkg/log/logger.go index 7e305056ce79..89354def9185 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -1,55 +1,129 @@ package log import ( + "os" + "runtime" + "strings" + + xlog "github.com/masahiro331/go-xfs-filesystem/log" "go.uber.org/zap" "go.uber.org/zap/zapcore" "golang.org/x/xerrors" + + flog "github.com/aquasecurity/trivy/pkg/fanal/log" ) -var Logger *zap.SugaredLogger +var ( + // Logger is the global variable for logging + Logger *zap.SugaredLogger + debugOption bool +) -func InitLogger(debug bool) (err error) { - Logger, err = newLogger(debug) +func init() { + // Set the default logger + Logger, _ = NewLogger(false, false) // nolint: errcheck +} + +// InitLogger initialize the logger variable +func InitLogger(debug, disable bool) (err error) { + debugOption = debug + Logger, err = NewLogger(debug, disable) if err != nil { - return xerrors.Errorf("error in new logger: %w", err) + return xerrors.Errorf("failed to initialize a logger: %w", err) } + + // Set logger for fanal + flog.SetLogger(Logger) + + // Set logger for go-xfs-filesystem + xlog.SetLogger(Logger) + return nil } -func newLogger(debug bool) (*zap.SugaredLogger, error) { - level := zap.NewAtomicLevel() - if debug { - level.SetLevel(zapcore.DebugLevel) - } else { - level.SetLevel(zapcore.InfoLevel) +// NewLogger is the factory method to return the instance of logger +func NewLogger(debug, disable bool) (*zap.SugaredLogger, error) { + // First, define our level-handling logic. + errorPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl >= zapcore.ErrorLevel + }) + logPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + if debug { + return lvl < zapcore.ErrorLevel + } + // Not enable debug level + return zapcore.DebugLevel < lvl && lvl < zapcore.ErrorLevel + }) + + encoderLevel := zapcore.CapitalColorLevelEncoder + // when running on Windows, don't log with color + if runtime.GOOS == "windows" { + encoderLevel = zapcore.CapitalLevelEncoder } - myConfig := zap.Config{ - Level: level, - Encoding: "console", - Development: debug, - DisableStacktrace: !debug, - DisableCaller: !debug, - EncoderConfig: zapcore.EncoderConfig{ - TimeKey: "Time", - LevelKey: "Level", - NameKey: "Name", - CallerKey: "Caller", - MessageKey: "Msg", - StacktraceKey: "St", - EncodeLevel: zapcore.CapitalColorLevelEncoder, - EncodeTime: zapcore.ISO8601TimeEncoder, - EncodeDuration: zapcore.StringDurationEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, - }, - OutputPaths: []string{"stdout"}, - ErrorOutputPaths: []string{"stderr"}, + encoderConfig := zapcore.EncoderConfig{ + TimeKey: "Time", + LevelKey: "Level", + NameKey: "Name", + CallerKey: "Caller", + MessageKey: "Msg", + StacktraceKey: "St", + EncodeLevel: encoderLevel, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, } - logger, err := myConfig.Build() - if err != nil { - return nil, xerrors.Errorf("failed to build zap config: %w", err) + + consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig) + + // High-priority output should also go to standard error, and low-priority + // output should also go to standard out. + consoleLogs := zapcore.Lock(os.Stderr) + consoleErrors := zapcore.Lock(os.Stderr) + if disable { + devNull, err := os.Create(os.DevNull) + if err != nil { + return nil, err + } + // Discard low-priority output + consoleLogs = zapcore.Lock(devNull) } + core := zapcore.NewTee( + zapcore.NewCore(consoleEncoder, consoleErrors, errorPriority), + zapcore.NewCore(consoleEncoder, consoleLogs, logPriority), + ) + + opts := []zap.Option{zap.ErrorOutput(zapcore.Lock(os.Stderr))} + if debug { + opts = append(opts, zap.Development()) + } + logger := zap.New(core, opts...) + return logger.Sugar(), nil } + +// Fatal for logging fatal errors +func Fatal(err error) { + if debugOption { + Logger.Fatalf("%+v", err) + } + Logger.Fatal(err) +} + +func String(key, val string) zap.Field { + if key == "" || val == "" { + return zap.Skip() + } + return zap.String(key, val) +} + +type PrefixedLogger struct { + Name string +} + +func (d *PrefixedLogger) Write(p []byte) (n int, err error) { + Logger.Debugf("[%s] %s", d.Name, strings.TrimSpace(string(p))) + return len(p), nil +} diff --git a/pkg/mapfs/file.go b/pkg/mapfs/file.go new file mode 100644 index 000000000000..3bff68f19050 --- /dev/null +++ b/pkg/mapfs/file.go @@ -0,0 +1,377 @@ +package mapfs + +import ( + "io" + "io/fs" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "golang.org/x/xerrors" + + xsync "github.com/aquasecurity/trivy/pkg/x/sync" +) + +var separator = "/" + +// file represents one of them: +// - an actual file +// - a virtual file +// - a virtual dir +type file struct { + underlyingPath string // underlying file path + data []byte // virtual file, only either of 'path' or 'data' has a value. + stat fileStat + files xsync.Map[string, *file] +} + +func (f *file) isVirtual() bool { + return len(f.data) != 0 || f.stat.IsDir() +} + +func (f *file) Open(name string) (fs.File, error) { + if name == "" || name == "." { + return f.open() + } + + if sub, err := f.getFile(name); err == nil { + return sub.open() + } + + return nil, &fs.PathError{ + Op: "open", + Path: name, + Err: fs.ErrNotExist, + } +} + +func (f *file) open() (fs.File, error) { + switch { + case f.stat.IsDir(): // Directory + entries, err := f.ReadDir(".") + if err != nil { + return nil, xerrors.Errorf("read dir error: %w", err) + } + return &mapDir{ + path: f.underlyingPath, + fileStat: f.stat, + entry: entries, + }, nil + case len(f.data) != 0: // Virtual file + return &openMapFile{ + path: f.stat.name, + file: f, + offset: 0, + }, nil + default: // Real file + return os.Open(f.underlyingPath) + } +} + +func (f *file) Remove(name string) error { + if name == "" || name == "." { + return nil + } + + return f.removePath(name, false) +} + +func (f *file) RemoveAll(name string) error { + if name == "" || name == "." { + return nil + } + + return f.removePath(name, true) +} + +func (f *file) removePath(name string, recursive bool) error { + parts := strings.Split(name, separator) + if len(parts) == 1 { + sub, ok := f.files.Load(name) + if !ok { + return fs.ErrNotExist + } + if sub.files.Len() != 0 && !recursive { + return fs.ErrInvalid + } + f.files.Delete(name) + return nil + } + + sub, err := f.getFile(parts[0]) + if err != nil { + return err + } else if !sub.stat.IsDir() { + return fs.ErrNotExist + } + + return sub.removePath(strings.Join(parts[1:], separator), recursive) +} + +func (f *file) getFile(name string) (*file, error) { + if name == "" || name == "." { + return f, nil + } + parts := strings.Split(name, separator) + if len(parts) == 1 { + f, ok := f.files.Load(name) + if ok { + return f, nil + } + return nil, fs.ErrNotExist + } + + sub, ok := f.files.Load(parts[0]) + if !ok || !sub.stat.IsDir() { + return nil, fs.ErrNotExist + } + + return sub.getFile(strings.Join(parts[1:], separator)) +} + +func (f *file) ReadDir(name string) ([]fs.DirEntry, error) { + if name == "" || name == "." { + var entries []fs.DirEntry + var err error + f.files.Range(func(name string, value *file) bool { + if value.isVirtual() { + entries = append(entries, &value.stat) + } else { + var fi os.FileInfo + fi, err = os.Stat(value.underlyingPath) + if err != nil { + return false + } + entries = append(entries, &fileStat{ + name: name, + size: fi.Size(), + mode: fi.Mode(), + modTime: fi.ModTime(), + sys: fi.Sys(), + }) + } + return true + }) + if err != nil { + return nil, xerrors.Errorf("range error: %w", err) + } + sort.Slice(entries, func(i, j int) bool { return entries[i].Name() < entries[j].Name() }) + return entries, nil + } + + parts := strings.Split(name, separator) + dir, ok := f.files.Load(parts[0]) + if !ok || !dir.stat.IsDir() { + return nil, fs.ErrNotExist + } + return dir.ReadDir(strings.Join(parts[1:], separator)) +} + +func (f *file) MkdirAll(path string, perm fs.FileMode) error { + parts := strings.Split(path, separator) + + if path == "" || path == "." { + return nil + } + + if perm&fs.ModeDir == 0 { + perm |= fs.ModeDir + } + + sub := &file{ + stat: fileStat{ + name: parts[0], + size: 0x100, + modTime: time.Now(), + mode: perm, + }, + files: xsync.Map[string, *file]{}, + } + + // Create the directory when the key is not present + sub, loaded := f.files.LoadOrStore(parts[0], sub) + if loaded && !sub.stat.IsDir() { + return fs.ErrExist + } + + if len(parts) == 1 { + return nil + } + + return sub.MkdirAll(strings.Join(parts[1:], separator), perm) +} + +func (f *file) WriteFile(path, underlyingPath string) error { + parts := strings.Split(path, separator) + + if len(parts) == 1 { + f.files.Store(parts[0], &file{ + underlyingPath: underlyingPath, + }) + return nil + } + + dir, ok := f.files.Load(parts[0]) + if !ok || !dir.stat.IsDir() { + return fs.ErrNotExist + } + + return dir.WriteFile(strings.Join(parts[1:], separator), underlyingPath) +} + +func (f *file) WriteVirtualFile(path string, data []byte, mode fs.FileMode) error { + if mode&fs.ModeDir != 0 { + return xerrors.Errorf("invalid perm: %v", mode) + } + parts := strings.Split(path, separator) + + if len(parts) == 1 { + f.files.Store(parts[0], &file{ + data: data, + stat: fileStat{ + name: parts[0], + size: int64(len(data)), + mode: mode, + modTime: time.Now(), + }, + }) + return nil + } + + dir, ok := f.files.Load(parts[0]) + if !ok || !dir.stat.IsDir() { + return fs.ErrNotExist + } + + return dir.WriteVirtualFile(strings.Join(parts[1:], separator), data, mode) +} + +func (f *file) glob(pattern string) ([]string, error) { + var entries []string + parts := strings.Split(pattern, separator) + + var err error + f.files.Range(func(name string, sub *file) bool { + if ok, err := filepath.Match(parts[0], name); err != nil { + return false + } else if ok { + if len(parts) == 1 { + entries = append(entries, name) + } else { + subEntries, err := sub.glob(strings.Join(parts[1:], separator)) + if err != nil { + return false + } + for _, sub := range subEntries { + entries = append(entries, name+separator+sub) + } + } + } + return true + }) + if err != nil { + return nil, xerrors.Errorf("range error: %w", err) + } + + sort.Strings(entries) + return entries, nil +} + +// An openMapFile is a regular (non-directory) fs.File open for reading. +// ported from https://github.com/golang/go/blob/99bc53f5e819c2d2d49f2a56c488898085be3982/src/testing/fstest/mapfs.go +type openMapFile struct { + path string + *file + offset int64 +} + +func (f *openMapFile) Stat() (fs.FileInfo, error) { return &f.file.stat, nil } + +func (f *openMapFile) Close() error { return nil } + +func (f *openMapFile) Read(b []byte) (int, error) { + if f.offset >= int64(len(f.file.data)) { + return 0, io.EOF + } + if f.offset < 0 { + return 0, &fs.PathError{ + Op: "read", + Path: f.path, + Err: fs.ErrInvalid, + } + } + n := copy(b, f.file.data[f.offset:]) + f.offset += int64(n) + return n, nil +} + +func (f *openMapFile) Seek(offset int64, whence int) (int64, error) { + switch whence { + case 0: + // offset += 0 + case 1: + offset += f.offset + case 2: + offset += int64(len(f.file.data)) + } + if offset < 0 || offset > int64(len(f.file.data)) { + return 0, &fs.PathError{ + Op: "seek", + Path: f.path, + Err: fs.ErrInvalid, + } + } + f.offset = offset + return offset, nil +} + +func (f *openMapFile) ReadAt(b []byte, offset int64) (int, error) { + if offset < 0 || offset > int64(len(f.file.data)) { + return 0, &fs.PathError{ + Op: "read", + Path: f.path, + Err: fs.ErrInvalid, + } + } + n := copy(b, f.file.data[offset:]) + if n < len(b) { + return n, io.EOF + } + return n, nil +} + +// A mapDir is a directory fs.File (so also fs.ReadDirFile) open for reading. +type mapDir struct { + path string + fileStat + entry []fs.DirEntry + offset int +} + +func (d *mapDir) Stat() (fs.FileInfo, error) { return &d.fileStat, nil } +func (d *mapDir) Close() error { return nil } +func (d *mapDir) Read(_ []byte) (int, error) { + return 0, &fs.PathError{ + Op: "read", + Path: d.path, + Err: fs.ErrInvalid, + } +} + +func (d *mapDir) ReadDir(count int) ([]fs.DirEntry, error) { + n := len(d.entry) - d.offset + if n == 0 && count > 0 { + return nil, io.EOF + } + if count > 0 && n > count { + n = count + } + list := make([]fs.DirEntry, n) + for i := range list { + list[i] = d.entry[d.offset+i] + } + d.offset += n + return list, nil +} diff --git a/pkg/mapfs/fs.go b/pkg/mapfs/fs.go new file mode 100644 index 000000000000..471730cc533e --- /dev/null +++ b/pkg/mapfs/fs.go @@ -0,0 +1,247 @@ +package mapfs + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + "time" + + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + xsync "github.com/aquasecurity/trivy/pkg/x/sync" +) + +type allFS interface { + fs.ReadFileFS + fs.ReadDirFS + fs.StatFS + fs.GlobFS + fs.SubFS +} + +// Make sure FS implements all the interfaces +var _ allFS = &FS{} + +// FS is an in-memory filesystem +type FS struct { + root *file + + // When the underlyingRoot has a value, it allows access to the local filesystem outside of this in-memory filesystem. + // The set path is used as the starting point when accessing the local filesystem. + // In other words, although mapfs.Open("../foo") would normally result in an error, if this option is enabled, + // it will be executed as os.Open(filepath.Join(underlyingRoot, "../foo")). + underlyingRoot string +} + +type Option func(*FS) + +// WithUnderlyingRoot returns an option to set the underlying root path for the in-memory filesystem. +func WithUnderlyingRoot(root string) Option { + return func(fsys *FS) { + fsys.underlyingRoot = root + } +} + +// New creates a new filesystem +func New(opts ...Option) *FS { + fsys := &FS{ + root: &file{ + stat: fileStat{ + name: ".", + size: 0x100, + modTime: time.Now(), + mode: 0o0700 | fs.ModeDir, + }, + files: xsync.Map[string, *file]{}, + }, + } + for _, opt := range opts { + opt(fsys) + } + return fsys +} + +// Filter removes the specified skippedFiles and returns a new FS +func (m *FS) Filter(skippedFiles []string) (*FS, error) { + if len(skippedFiles) == 0 { + return m, nil + } + filter := func(path string, _ fs.DirEntry) (bool, error) { + return slices.Contains(skippedFiles, path), nil + } + return m.FilterFunc(filter) +} + +func (m *FS) FilterFunc(fn func(path string, d fs.DirEntry) (bool, error)) (*FS, error) { + newFS := New(WithUnderlyingRoot(m.underlyingRoot)) + err := fs.WalkDir(m, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return newFS.MkdirAll(path, d.Type().Perm()) + } + + if filtered, err := fn(path, d); err != nil { + return err + } else if filtered { + return nil + } + + f, err := m.root.getFile(path) + if err != nil { + return xerrors.Errorf("unable to get %s: %w", path, err) + } + // Virtual file + if f.underlyingPath == "" { + return newFS.WriteVirtualFile(path, f.data, f.stat.mode) + } + return newFS.WriteFile(path, f.underlyingPath) + }) + if err != nil { + return nil, xerrors.Errorf("walk error %w", err) + } + + return newFS, nil +} + +func (m *FS) CopyFilesUnder(dir string) error { + return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } else if d.IsDir() { + return m.MkdirAll(path, d.Type()) + } + return m.WriteFile(path, path) + }) +} + +// Stat returns a FileInfo describing the file. +func (m *FS) Stat(name string) (fs.FileInfo, error) { + if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + return os.Stat(filepath.Join(m.underlyingRoot, name)) + } + + name = cleanPath(name) + f, err := m.root.getFile(name) + if err != nil { + return nil, &fs.PathError{ + Op: "stat", + Path: name, + Err: err, + } + } + if f.isVirtual() { + return &f.stat, nil + } + return os.Stat(f.underlyingPath) +} + +// ReadDir reads the named directory +// and returns a list of directory entries sorted by filename. +func (m *FS) ReadDir(name string) ([]fs.DirEntry, error) { + if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + return os.ReadDir(filepath.Join(m.underlyingRoot, name)) + } + return m.root.ReadDir(cleanPath(name)) +} + +// Open opens the named file for reading. +func (m *FS) Open(name string) (fs.File, error) { + if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + return os.Open(filepath.Join(m.underlyingRoot, name)) + } + return m.root.Open(cleanPath(name)) +} + +// WriteFile creates a mapping between path and underlyingPath. +func (m *FS) WriteFile(path, underlyingPath string) error { + return m.root.WriteFile(cleanPath(path), underlyingPath) +} + +// WriteVirtualFile writes the specified bytes to the named file. If the file exists, it will be overwritten. +func (m *FS) WriteVirtualFile(path string, data []byte, mode fs.FileMode) error { + return m.root.WriteVirtualFile(cleanPath(path), data, mode) +} + +// MkdirAll creates a directory named path, +// along with any necessary parents, and returns nil, +// or else returns an error. +// The permission bits perm (before umask) are used for all +// directories that MkdirAll creates. +// If path is already a directory, MkdirAll does nothing +// and returns nil. +func (m *FS) MkdirAll(path string, perm fs.FileMode) error { + return m.root.MkdirAll(cleanPath(path), perm) +} + +// ReadFile reads the named file and returns its contents. +// A successful call returns a nil error, not io.EOF. +// (Because ReadFile reads the whole file, the expected EOF +// from the final Read is not treated as an error to be reported.) +// +// The caller is permitted to modify the returned byte slice. +// This method should return a copy of the underlying data. +func (m *FS) ReadFile(name string) ([]byte, error) { + if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + return os.ReadFile(filepath.Join(m.underlyingRoot, name)) + } + + f, err := m.root.Open(cleanPath(name)) + if err != nil { + return nil, err + } + defer func() { _ = f.Close() }() + return io.ReadAll(f) +} + +// Sub returns an FS corresponding to the subtree rooted at dir. +func (m *FS) Sub(dir string) (fs.FS, error) { + d, err := m.root.getFile(cleanPath(dir)) + if err != nil { + return nil, err + } + return &FS{ + root: d, + }, nil +} + +// Glob returns the names of all files matching pattern or nil +// if there is no matching file. The syntax of patterns is the same +// as in Match. The pattern may describe hierarchical names such as +// /usr/*/bin/ed (assuming the Separator is '/'). +// +// Glob ignores file system errors such as I/O errors reading directories. +// The only possible returned error is ErrBadPattern, when pattern +// is malformed. +func (m *FS) Glob(pattern string) ([]string, error) { + return m.root.glob(pattern) +} + +// Remove deletes a file or directory from the filesystem +func (m *FS) Remove(path string) error { + return m.root.Remove(cleanPath(path)) +} + +// RemoveAll deletes a file or directory and any children if present from the filesystem +func (m *FS) RemoveAll(path string) error { + return m.root.RemoveAll(cleanPath(path)) +} + +func cleanPath(path string) string { + // Convert the volume name like 'C:' into dir like 'C\' + if vol := filepath.VolumeName(path); len(vol) > 0 { + newVol := strings.TrimSuffix(vol, ":") + newVol = fmt.Sprintf("%s%c", newVol, filepath.Separator) + path = strings.Replace(path, vol, newVol, 1) + } + path = filepath.Clean(path) + path = filepath.ToSlash(path) + path = strings.TrimLeft(path, "/") // Remove the leading slash + return path +} diff --git a/pkg/mapfs/fs_test.go b/pkg/mapfs/fs_test.go new file mode 100644 index 000000000000..22b659d7f387 --- /dev/null +++ b/pkg/mapfs/fs_test.go @@ -0,0 +1,480 @@ +package mapfs_test + +import ( + "io" + "io/fs" + "runtime" + "testing" + + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/mapfs" +) + +type fileInfo struct { + name string + fileMode fs.FileMode + isDir bool + size int64 +} + +var ( + filePerm = lo.Ternary(runtime.GOOS == "windows", fs.FileMode(0666), fs.FileMode(0644)) + helloFileInfo = fileInfo{ + name: "hello.txt", + fileMode: filePerm, + isDir: false, + size: 11, + } + btxtFileInfo = fileInfo{ + name: "b.txt", + fileMode: filePerm, + isDir: false, + size: 3, + } + virtualFileInfo = fileInfo{ + name: "virtual.txt", + fileMode: 0600, + isDir: false, + size: 7, + } + cdirFileInfo = fileInfo{ + name: "c", + fileMode: fs.FileMode(0700) | fs.ModeDir, + isDir: true, + size: 256, + } +) + +func initFS(t *testing.T) *mapfs.FS { + fsys := mapfs.New() + require.NoError(t, fsys.MkdirAll("a/b/c", 0700)) + require.NoError(t, fsys.MkdirAll("a/b/empty", 0700)) + require.NoError(t, fsys.WriteFile("hello.txt", "testdata/hello.txt")) + require.NoError(t, fsys.WriteFile("a/b/b.txt", "testdata/b.txt")) + require.NoError(t, fsys.WriteFile("a/b/c/c.txt", "testdata/c.txt")) + require.NoError(t, fsys.WriteFile("a/b/c/.dotfile", "testdata/dotfile")) + require.NoError(t, fsys.WriteVirtualFile("a/b/c/virtual.txt", []byte("virtual"), 0600)) + return fsys +} + +func assertFileInfo(t *testing.T, want fileInfo, got fs.FileInfo) { + if got == nil { + return + } + assert.Equal(t, want.name, got.Name()) + assert.Equal(t, want.fileMode, got.Mode()) + assert.Equal(t, want.isDir, got.Mode().IsDir()) + assert.Equal(t, want.isDir, got.IsDir()) + assert.Equal(t, want.size, got.Size()) +} + +func TestFS_Filter(t *testing.T) { + fsys := initFS(t) + t.Run("empty files", func(t *testing.T) { + newFS, err := fsys.Filter(nil) + require.NoError(t, err) + assert.Equal(t, fsys, newFS) + }) + t.Run("happy", func(t *testing.T) { + newFS, err := fsys.Filter([]string{ + "hello.txt", + "a/b/c/.dotfile", + }) + require.NoError(t, err) + _, err = newFS.Stat("hello.txt") + require.ErrorIs(t, err, fs.ErrNotExist) + _, err = newFS.Stat("a/b/c/.dotfile") + require.ErrorIs(t, err, fs.ErrNotExist) + fi, err := newFS.Stat("a/b/c/c.txt") + require.NoError(t, err) + assert.Equal(t, "c.txt", fi.Name()) + }) +} + +func TestFS_Stat(t *testing.T) { + tests := []struct { + name string + filePath string + want fileInfo + wantErr assert.ErrorAssertionFunc + }{ + { + name: "ordinary file", + filePath: "hello.txt", + want: helloFileInfo, + wantErr: assert.NoError, + }, + { + name: "nested file", + filePath: "a/b/b.txt", + want: btxtFileInfo, + wantErr: assert.NoError, + }, + { + name: "virtual file", + filePath: "a/b/c/virtual.txt", + want: virtualFileInfo, + wantErr: assert.NoError, + }, + { + name: "dir", + filePath: "a/b/c", + want: cdirFileInfo, + wantErr: assert.NoError, + }, + { + name: "no such file", + filePath: "nosuch.txt", + wantErr: assert.Error, + }, + } + + for _, tt := range tests { + fsys := initFS(t) + t.Run(tt.name, func(t *testing.T) { + got, err := fsys.Stat(tt.filePath) + tt.wantErr(t, err) + assertFileInfo(t, tt.want, got) + }) + } +} + +func TestFS_ReadDir(t *testing.T) { + type dirEntry struct { + name string + fileMode fs.FileMode + isDir bool + size int64 + fileInfo fileInfo + } + + tests := []struct { + name string + filePath string + want []dirEntry + wantErr assert.ErrorAssertionFunc + }{ + { + name: "at root", + filePath: ".", + want: []dirEntry{ + { + name: "a", + fileMode: fs.FileMode(0700) | fs.ModeDir, + isDir: true, + size: 0x100, + fileInfo: fileInfo{ + name: "a", + fileMode: fs.FileMode(0700) | fs.ModeDir, + isDir: true, + size: 0x100, + }, + }, + { + name: "hello.txt", + fileMode: filePerm, + isDir: false, + size: 11, + fileInfo: helloFileInfo, + }, + }, + wantErr: assert.NoError, + }, + { + name: "multiple files", + filePath: "a/b/c", + want: []dirEntry{ + { + name: ".dotfile", + fileMode: filePerm, + isDir: false, + size: 7, + fileInfo: fileInfo{ + name: ".dotfile", + fileMode: filePerm, + isDir: false, + size: 7, + }, + }, + { + name: "c.txt", + fileMode: filePerm, + isDir: false, + size: 0, + fileInfo: fileInfo{ + name: "c.txt", + fileMode: filePerm, + isDir: false, + size: 0, + }, + }, + { + name: "virtual.txt", + fileMode: 0600, + isDir: false, + size: 0, + fileInfo: virtualFileInfo, + }, + }, + wantErr: assert.NoError, + }, + { + name: "no such dir", + filePath: "nosuch/", + wantErr: assert.Error, + }, + } + + for _, tt := range tests { + fsys := initFS(t) + t.Run(tt.name, func(t *testing.T) { + entries, err := fsys.ReadDir(tt.filePath) + tt.wantErr(t, err) + + for _, z := range lo.Zip2(entries, tt.want) { + got, want := z.A, z.B + assert.Equal(t, want.name, got.Name()) + assert.Equal(t, want.fileMode, got.Type(), want.name) + assert.Equal(t, want.isDir, got.IsDir(), want.name) + + fi, err := got.Info() + require.NoError(t, err) + assertFileInfo(t, want.fileInfo, fi) + } + }) + } +} + +func TestFS_Open(t *testing.T) { + type file struct { + fileInfo fileInfo + body string + } + + tests := []struct { + name string + filePath string + want file + wantErr assert.ErrorAssertionFunc + }{ + { + name: "ordinary file", + filePath: "hello.txt", + want: file{ + fileInfo: helloFileInfo, + body: "hello world", + }, + wantErr: assert.NoError, + }, + { + name: "virtual file", + filePath: "a/b/c/virtual.txt", + want: file{ + fileInfo: virtualFileInfo, + body: "virtual", + }, + wantErr: assert.NoError, + }, + { + name: "dir", + filePath: "a/b/c", + want: file{ + fileInfo: cdirFileInfo, + }, + wantErr: assert.NoError, + }, + { + name: "no such file", + filePath: "nosuch.txt", + wantErr: assert.Error, + }, + } + + for _, tt := range tests { + fsys := initFS(t) + t.Run(tt.name, func(t *testing.T) { + f, err := fsys.Open(tt.filePath) + tt.wantErr(t, err) + if f == nil { + return + } + defer func() { + require.NoError(t, f.Close()) + }() + + fi, err := f.Stat() + require.NoError(t, err) + assertFileInfo(t, tt.want.fileInfo, fi) + + if tt.want.body != "" { + b, err := io.ReadAll(f) + require.NoError(t, err) + assert.Equal(t, tt.want.body, string(b)) + } + }) + } +} + +func TestFS_ReadFile(t *testing.T) { + tests := []struct { + name string + filePath string + want string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "ordinary file", + filePath: "hello.txt", + want: "hello world", + wantErr: assert.NoError, + }, + { + name: "virtual file", + filePath: "a/b/c/virtual.txt", + want: "virtual", + wantErr: assert.NoError, + }, + { + name: "no such file", + filePath: "nosuch.txt", + wantErr: assert.Error, + }, + } + + for _, tt := range tests { + fsys := initFS(t) + t.Run(tt.name, func(t *testing.T) { + b, err := fsys.ReadFile(tt.filePath) + tt.wantErr(t, err) + assert.Equal(t, tt.want, string(b)) + }) + } +} + +func TestFS_Sub(t *testing.T) { + fsys := initFS(t) + sub, err := fsys.Sub("a/b") + require.NoError(t, err) + + data, err := sub.(fs.ReadFileFS).ReadFile("c/.dotfile") + require.NoError(t, err) + assert.Equal(t, "dotfile", string(data)) +} + +func TestFS_Glob(t *testing.T) { + tests := []struct { + name string + pattern string + want []string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "root", + pattern: "*", + want: []string{ + "a", + "hello.txt", + }, + wantErr: assert.NoError, + }, + { + name: "pattern", + pattern: "*/b/c/*.txt", + want: []string{ + "a/b/c/c.txt", + "a/b/c/virtual.txt", + }, + wantErr: assert.NoError, + }, + { + name: "no such", + pattern: "nosuch", + wantErr: assert.NoError, + }, + } + + for _, tt := range tests { + fsys := initFS(t) + t.Run(tt.name, func(t *testing.T) { + results, err := fsys.Glob(tt.pattern) + tt.wantErr(t, err) + assert.Equal(t, tt.want, results) + }) + } +} + +func TestFS_Remove(t *testing.T) { + tests := []struct { + name string + path string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "ordinary file", + path: "hello.txt", + wantErr: assert.NoError, + }, + { + name: "nested file", + path: "a/b/b.txt", + wantErr: assert.NoError, + }, + { + name: "virtual file", + path: "a/b/c/virtual.txt", + wantErr: assert.NoError, + }, + { + name: "empty dir", + path: "a/b/empty", + wantErr: assert.NoError, + }, + { + name: "empty path", + path: "", + wantErr: assert.NoError, + }, + { + name: "non-empty dir", + path: "a/b/c", + wantErr: assert.Error, + }, + } + + for _, tt := range tests { + fsys := initFS(t) + t.Run(tt.name, func(t *testing.T) { + err := fsys.Remove(tt.path) + tt.wantErr(t, err) + if err != nil || tt.path == "" { + return + } + + _, err = fsys.Stat(tt.path) + require.ErrorIs(t, err, fs.ErrNotExist) + }) + } +} + +func TestFS_RemoveAll(t *testing.T) { + fsys := initFS(t) + t.Run("ordinary file", func(t *testing.T) { + err := fsys.RemoveAll("hello.txt") + require.NoError(t, err) + _, err = fsys.Stat("hello.txt") + require.ErrorIs(t, err, fs.ErrNotExist) + }) + t.Run("non-empty dir", func(t *testing.T) { + err := fsys.RemoveAll("a/b") + require.NoError(t, err) + _, err = fsys.Stat("a/b/c/c.txt") + require.ErrorIs(t, err, fs.ErrNotExist) + _, err = fsys.Stat("a/b/c/.dotfile") + require.ErrorIs(t, err, fs.ErrNotExist) + _, err = fsys.Stat("a/b/c/virtual.txt") + require.ErrorIs(t, err, fs.ErrNotExist) + }) +} diff --git a/pkg/mapfs/testdata/b.txt b/pkg/mapfs/testdata/b.txt new file mode 100644 index 000000000000..01f02e32ce8a --- /dev/null +++ b/pkg/mapfs/testdata/b.txt @@ -0,0 +1 @@ +bbb \ No newline at end of file diff --git a/pkg/mapfs/testdata/c.txt b/pkg/mapfs/testdata/c.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/mapfs/testdata/dotfile b/pkg/mapfs/testdata/dotfile new file mode 100644 index 000000000000..a21a8f598711 --- /dev/null +++ b/pkg/mapfs/testdata/dotfile @@ -0,0 +1 @@ +dotfile \ No newline at end of file diff --git a/pkg/mapfs/testdata/hello.txt b/pkg/mapfs/testdata/hello.txt new file mode 100644 index 000000000000..95d09f2b1015 --- /dev/null +++ b/pkg/mapfs/testdata/hello.txt @@ -0,0 +1 @@ +hello world \ No newline at end of file diff --git a/pkg/mapfs/types.go b/pkg/mapfs/types.go new file mode 100644 index 000000000000..545ab822429b --- /dev/null +++ b/pkg/mapfs/types.go @@ -0,0 +1,26 @@ +package mapfs + +import ( + "io/fs" + "time" +) + +// A fileStat is the implementation of FileInfo returned by Stat and Lstat. +// Ported from https://github.com/golang/go/blob/518889b35cb07f3e71963f2ccfc0f96ee26a51ce/src/os/types_unix.go +type fileStat struct { + name string + size int64 + mode fs.FileMode + modTime time.Time + sys any +} + +func (fstat *fileStat) Name() string { return fstat.name } +func (fstat *fileStat) Size() int64 { return fstat.size } +func (fstat *fileStat) Mode() fs.FileMode { return fstat.mode } +func (fstat *fileStat) IsDir() bool { return fstat.mode.IsDir() } +func (fstat *fileStat) ModTime() time.Time { return fstat.modTime } +func (fstat *fileStat) Sys() any { return &fstat.sys } + +func (fstat *fileStat) Info() (fs.FileInfo, error) { return fstat, nil } +func (fstat *fileStat) Type() fs.FileMode { return fstat.mode } diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go new file mode 100644 index 000000000000..6a30c9b69ec4 --- /dev/null +++ b/pkg/misconf/scanner.go @@ -0,0 +1,523 @@ +package misconf + +import ( + "context" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm" + cfscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation" + cfparser "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + dfscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile" + helm2 "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" + k8sscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" + tfprawscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot" + tfpjsonscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/mapfs" + + _ "embed" +) + +var enablediacTypes = map[detection.FileType]types.ConfigType{ + detection.FileTypeAzureARM: types.AzureARM, + detection.FileTypeCloudFormation: types.CloudFormation, + detection.FileTypeTerraform: types.Terraform, + detection.FileTypeDockerfile: types.Dockerfile, + detection.FileTypeKubernetes: types.Kubernetes, + detection.FileTypeHelm: types.Helm, + detection.FileTypeTerraformPlanJSON: types.TerraformPlanJSON, + detection.FileTypeTerraformPlanSnapshot: types.TerraformPlanSnapshot, +} + +type ScannerOption struct { + Debug bool + Trace bool + RegoOnly bool + Namespaces []string + PolicyPaths []string + DataPaths []string + DisableEmbeddedPolicies bool + DisableEmbeddedLibraries bool + + HelmValues []string + HelmValueFiles []string + HelmFileValues []string + HelmStringValues []string + HelmAPIVersions []string + HelmKubeVersion string + TerraformTFVars []string + CloudFormationParamVars []string + TfExcludeDownloaded bool + K8sVersion string +} + +func (o *ScannerOption) Sort() { + sort.Strings(o.Namespaces) + sort.Strings(o.PolicyPaths) + sort.Strings(o.DataPaths) +} + +type Scanner struct { + fileType detection.FileType + scanner scanners.FSScanner + hasFilePattern bool +} + +func NewAzureARMScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { + return newScanner(detection.FileTypeAzureARM, filePatterns, opt) +} + +func NewCloudFormationScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { + return newScanner(detection.FileTypeCloudFormation, filePatterns, opt) +} + +func NewDockerfileScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { + return newScanner(detection.FileTypeDockerfile, filePatterns, opt) +} + +func NewHelmScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { + return newScanner(detection.FileTypeHelm, filePatterns, opt) +} + +func NewKubernetesScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { + return newScanner(detection.FileTypeKubernetes, filePatterns, opt) +} + +func NewTerraformScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { + return newScanner(detection.FileTypeTerraform, filePatterns, opt) +} + +func NewTerraformPlanJSONScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { + return newScanner(detection.FileTypeTerraformPlanJSON, filePatterns, opt) +} + +func NewTerraformPlanSnapshotScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { + return newScanner(detection.FileTypeTerraformPlanSnapshot, filePatterns, opt) +} + +func newScanner(t detection.FileType, filePatterns []string, opt ScannerOption) (*Scanner, error) { + opts, err := scannerOptions(t, opt) + if err != nil { + return nil, err + } + + var scanner scanners.FSScanner + switch t { + case detection.FileTypeAzureARM: + scanner = arm.New(opts...) + case detection.FileTypeCloudFormation: + scanner = cfscanner.New(opts...) + case detection.FileTypeDockerfile: + scanner = dfscanner.NewScanner(opts...) + case detection.FileTypeHelm: + scanner = helm2.New(opts...) + case detection.FileTypeKubernetes: + scanner = k8sscanner.NewScanner(opts...) + case detection.FileTypeTerraform: + scanner = terraform.New(opts...) + case detection.FileTypeTerraformPlanJSON: + scanner = tfpjsonscanner.New(opts...) + case detection.FileTypeTerraformPlanSnapshot: + scanner = tfprawscanner.New(opts...) + } + + return &Scanner{ + fileType: t, + scanner: scanner, + hasFilePattern: hasFilePattern(t, filePatterns), + }, nil +} + +func (s *Scanner) Scan(ctx context.Context, fsys fs.FS) ([]types.Misconfiguration, error) { + newfs, err := s.filterFS(fsys) + if err != nil { + return nil, xerrors.Errorf("fs filter error: %w", err) + } else if newfs == nil { + // Skip scanning if no relevant files are found + return nil, nil + } + + log.Logger.Debugf("Scanning %s files for misconfigurations...", s.scanner.Name()) + results, err := s.scanner.ScanFS(ctx, newfs, ".") + if err != nil { + var invalidContentError *cfparser.InvalidContentError + if errors.As(err, &invalidContentError) { + log.Logger.Errorf("scan %q was broken with InvalidContentError: %v", s.scanner.Name(), err) + return nil, nil + } + return nil, xerrors.Errorf("scan config error: %w", err) + } + + configType := enablediacTypes[s.fileType] + misconfs := ResultsToMisconf(configType, s.scanner.Name(), results) + + // Sort misconfigurations + for _, misconf := range misconfs { + sort.Sort(misconf.Successes) + sort.Sort(misconf.Warnings) + sort.Sort(misconf.Failures) + } + + return misconfs, nil +} + +func (s *Scanner) filterFS(fsys fs.FS) (fs.FS, error) { + mfs, ok := fsys.(*mapfs.FS) + if !ok { + // Unable to filter this filesystem + return fsys, nil + } + + var foundRelevantFile bool + filter := func(path string, d fs.DirEntry) (bool, error) { + file, err := fsys.Open(path) + if err != nil { + return false, err + } + rs, ok := file.(io.ReadSeeker) + if !ok { + return false, xerrors.Errorf("type assertion error: %w", err) + } + defer file.Close() + + if !s.hasFilePattern && !detection.IsType(path, rs, s.fileType) { + return true, nil + } + foundRelevantFile = true + return false, nil + } + newfs, err := mfs.FilterFunc(filter) + if err != nil { + return nil, xerrors.Errorf("fs filter error: %w", err) + } + if !foundRelevantFile { + return nil, nil + } + return newfs, nil +} + +func scannerOptions(t detection.FileType, opt ScannerOption) ([]options.ScannerOption, error) { + opts := []options.ScannerOption{ + options.ScannerWithSkipRequiredCheck(true), + options.ScannerWithEmbeddedPolicies(!opt.DisableEmbeddedPolicies), + options.ScannerWithEmbeddedLibraries(!opt.DisableEmbeddedLibraries), + } + + policyFS, policyPaths, err := CreatePolicyFS(opt.PolicyPaths) + if err != nil { + return nil, err + } + if policyFS != nil { + opts = append(opts, options.ScannerWithPolicyFilesystem(policyFS)) + } + + dataFS, dataPaths, err := CreateDataFS(opt.DataPaths, opt.K8sVersion) + if err != nil { + return nil, err + } + opts = append(opts, + options.ScannerWithDataDirs(dataPaths...), + options.ScannerWithDataFilesystem(dataFS), + ) + + if opt.Debug { + opts = append(opts, options.ScannerWithDebug(&log.PrefixedLogger{Name: "misconf"})) + } + + if opt.Trace { + opts = append(opts, options.ScannerWithPerResultTracing(true)) + } + + if opt.RegoOnly { + opts = append(opts, options.ScannerWithRegoOnly(true)) + } + + if len(policyPaths) > 0 { + opts = append(opts, options.ScannerWithPolicyDirs(policyPaths...)) + } + + if len(opt.DataPaths) > 0 { + opts = append(opts, options.ScannerWithDataDirs(opt.DataPaths...)) + } + + if len(opt.Namespaces) > 0 { + opts = append(opts, options.ScannerWithPolicyNamespaces(opt.Namespaces...)) + } + + switch t { + case detection.FileTypeHelm: + return addHelmOpts(opts, opt), nil + case detection.FileTypeTerraform, detection.FileTypeTerraformPlanSnapshot: + return addTFOpts(opts, opt) + case detection.FileTypeCloudFormation: + return addCFOpts(opts, opt) + default: + return opts, nil + } +} + +func hasFilePattern(t detection.FileType, filePatterns []string) bool { + for _, pattern := range filePatterns { + if strings.HasPrefix(pattern, fmt.Sprintf("%s:", t)) { + return true + } + } + return false +} + +func addTFOpts(opts []options.ScannerOption, scannerOption ScannerOption) ([]options.ScannerOption, error) { + if len(scannerOption.TerraformTFVars) > 0 { + configFS, err := createConfigFS(scannerOption.TerraformTFVars) + if err != nil { + return nil, xerrors.Errorf("failed to create Terraform config FS: %w", err) + } + opts = append( + opts, + terraform.ScannerWithTFVarsPaths(scannerOption.TerraformTFVars...), + terraform.ScannerWithConfigsFileSystem(configFS), + ) + } + + opts = append(opts, + terraform.ScannerWithAllDirectories(true), + terraform.ScannerWithSkipDownloaded(scannerOption.TfExcludeDownloaded), + ) + + return opts, nil +} + +func addCFOpts(opts []options.ScannerOption, scannerOption ScannerOption) ([]options.ScannerOption, error) { + if len(scannerOption.CloudFormationParamVars) > 0 { + configFS, err := createConfigFS(scannerOption.CloudFormationParamVars) + if err != nil { + return nil, xerrors.Errorf("failed to create CloudFormation config FS: %w", err) + } + opts = append( + opts, + cfscanner.WithParameterFiles(scannerOption.CloudFormationParamVars...), + cfscanner.WithConfigsFS(configFS), + ) + } + return opts, nil +} + +func addHelmOpts(opts []options.ScannerOption, scannerOption ScannerOption) []options.ScannerOption { + if len(scannerOption.HelmValueFiles) > 0 { + opts = append(opts, helm2.ScannerWithValuesFile(scannerOption.HelmValueFiles...)) + } + + if len(scannerOption.HelmValues) > 0 { + opts = append(opts, helm2.ScannerWithValues(scannerOption.HelmValues...)) + } + + if len(scannerOption.HelmFileValues) > 0 { + opts = append(opts, helm2.ScannerWithFileValues(scannerOption.HelmFileValues...)) + } + + if len(scannerOption.HelmStringValues) > 0 { + opts = append(opts, helm2.ScannerWithStringValues(scannerOption.HelmStringValues...)) + } + + if len(scannerOption.HelmAPIVersions) > 0 { + opts = append(opts, helm2.ScannerWithAPIVersions(scannerOption.HelmAPIVersions...)) + } + + if scannerOption.HelmKubeVersion != "" { + opts = append(opts, helm2.ScannerWithKubeVersion(scannerOption.HelmKubeVersion)) + } + + return opts +} + +func createConfigFS(paths []string) (fs.FS, error) { + mfs := mapfs.New() + for _, path := range paths { + if err := mfs.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil && !errors.Is(err, fs.ErrExist) { + return nil, xerrors.Errorf("create dir error: %w", err) + } + if err := mfs.WriteFile(path, path); err != nil { + return nil, xerrors.Errorf("write file error: %w", err) + } + } + return mfs, nil +} + +func CreatePolicyFS(policyPaths []string) (fs.FS, []string, error) { + if len(policyPaths) == 0 { + return nil, nil, nil + } + + mfs := mapfs.New() + for _, p := range policyPaths { + abs, err := filepath.Abs(p) + if err != nil { + return nil, nil, xerrors.Errorf("failed to derive absolute path from '%s': %w", p, err) + } + fi, err := os.Stat(abs) + if errors.Is(err, os.ErrNotExist) { + return nil, nil, xerrors.Errorf("policy file %q not found", abs) + } else if err != nil { + return nil, nil, xerrors.Errorf("file %q stat error: %w", abs, err) + } + + if fi.IsDir() { + if err = mfs.CopyFilesUnder(abs); err != nil { + return nil, nil, xerrors.Errorf("mapfs file copy error: %w", err) + } + } else { + if err := mfs.MkdirAll(filepath.Dir(abs), os.ModePerm); err != nil && !errors.Is(err, fs.ErrExist) { + return nil, nil, xerrors.Errorf("mapfs mkdir error: %w", err) + } + if err := mfs.WriteFile(abs, abs); err != nil { + return nil, nil, xerrors.Errorf("mapfs write error: %w", err) + } + } + } + + // policy paths are no longer needed as fs.FS contains only needed files now. + policyPaths = []string{"."} + + return mfs, policyPaths, nil +} + +func CreateDataFS(dataPaths []string, opts ...string) (fs.FS, []string, error) { + fsys := mapfs.New() + + // Check if k8sVersion is provided + if len(opts) > 0 { + k8sVersion := opts[0] + if err := fsys.MkdirAll("system", 0700); err != nil { + return nil, nil, err + } + data := []byte(fmt.Sprintf(`{"k8s": {"version": %q}}`, k8sVersion)) + if err := fsys.WriteVirtualFile("system/k8s-version.json", data, 0600); err != nil { + return nil, nil, err + } + } + + for _, path := range dataPaths { + if err := fsys.CopyFilesUnder(path); err != nil { + return nil, nil, err + } + } + + // dataPaths are no longer needed as fs.FS contains only needed files now. + dataPaths = []string{"."} + + return fsys, dataPaths, nil +} + +// ResultsToMisconf is exported for trivy-plugin-aqua purposes only +func ResultsToMisconf(configType types.ConfigType, scannerName string, results scan.Results) []types.Misconfiguration { + misconfs := make(map[string]types.Misconfiguration) + + for _, result := range results { + flattened := result.Flatten() + + query := fmt.Sprintf("data.%s.%s", result.RegoNamespace(), result.RegoRule()) + + ruleID := result.Rule().AVDID + if result.RegoNamespace() != "" && len(result.Rule().Aliases) > 0 { + ruleID = result.Rule().Aliases[0] + } + + cause := NewCauseWithCode(result) + + misconfResult := types.MisconfResult{ + Namespace: result.RegoNamespace(), + Query: query, + Message: flattened.Description, + PolicyMetadata: types.PolicyMetadata{ + ID: ruleID, + AVDID: result.Rule().AVDID, + Type: fmt.Sprintf("%s Security Check", scannerName), + Title: result.Rule().Summary, + Description: result.Rule().Explanation, + Severity: string(flattened.Severity), + RecommendedActions: flattened.Resolution, + References: flattened.Links, + }, + CauseMetadata: cause, + Traces: result.Traces(), + } + + filePath := flattened.Location.Filename + misconf, ok := misconfs[filePath] + if !ok { + misconf = types.Misconfiguration{ + FileType: configType, + FilePath: filepath.ToSlash(filePath), // defsec return OS-aware path + } + } + + if flattened.Warning { + misconf.Warnings = append(misconf.Warnings, misconfResult) + } else { + switch flattened.Status { + case scan.StatusPassed: + misconf.Successes = append(misconf.Successes, misconfResult) + case scan.StatusIgnored: + misconf.Exceptions = append(misconf.Exceptions, misconfResult) + case scan.StatusFailed: + misconf.Failures = append(misconf.Failures, misconfResult) + } + } + misconfs[filePath] = misconf + } + + return types.ToMisconfigurations(misconfs) +} + +func NewCauseWithCode(underlying scan.Result) types.CauseMetadata { + flat := underlying.Flatten() + cause := types.CauseMetadata{ + Resource: flat.Resource, + Provider: flat.RuleProvider.DisplayName(), + Service: flat.RuleService, + StartLine: flat.Location.StartLine, + EndLine: flat.Location.EndLine, + } + for _, o := range flat.Occurrences { + cause.Occurrences = append(cause.Occurrences, types.Occurrence{ + Resource: o.Resource, + Filename: o.Filename, + Location: types.Location{ + StartLine: o.StartLine, + EndLine: o.EndLine, + }, + }) + } + if code, err := underlying.GetCode(); err == nil { + cause.Code = types.Code{ + Lines: lo.Map(code.Lines, func(l scan.Line, i int) types.Line { + return types.Line{ + Number: l.Number, + Content: l.Content, + IsCause: l.IsCause, + Annotation: l.Annotation, + Truncated: l.Truncated, + Highlighted: l.Highlighted, + FirstCause: l.FirstCause, + LastCause: l.LastCause, + } + }), + } + } + return cause +} diff --git a/pkg/misconf/scanner_test.go b/pkg/misconf/scanner_test.go new file mode 100644 index 000000000000..03c76ebba2c7 --- /dev/null +++ b/pkg/misconf/scanner_test.go @@ -0,0 +1,200 @@ +package misconf + +import ( + "context" + "io/fs" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/mapfs" +) + +func TestScannerOption_Sort(t *testing.T) { + type fields struct { + Namespaces []string + PolicyPaths []string + DataPaths []string + } + tests := []struct { + name string + fields fields + want ScannerOption + }{ + { + name: "happy path", + fields: fields{ + Namespaces: []string{ + "main", + "custom", + "default", + }, + PolicyPaths: []string{"policy"}, + DataPaths: []string{ + "data/b", + "data/c", + "data/a", + }, + }, + want: ScannerOption{ + Namespaces: []string{ + "custom", + "default", + "main", + }, + PolicyPaths: []string{"policy"}, + DataPaths: []string{ + "data/a", + "data/b", + "data/c", + }, + }, + }, + { + name: "missing some fields", + fields: fields{ + Namespaces: []string{"main"}, + PolicyPaths: nil, + DataPaths: nil, + }, + want: ScannerOption{ + Namespaces: []string{"main"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := ScannerOption{ + Namespaces: tt.fields.Namespaces, + PolicyPaths: tt.fields.PolicyPaths, + DataPaths: tt.fields.DataPaths, + } + o.Sort() + + assert.Equal(t, tt.want, o) + }) + } +} + +func TestScanner_Scan(t *testing.T) { + type fields struct { + filePatterns []string + opt ScannerOption + } + type file struct { + path string + content []byte + } + tests := []struct { + name string + scannerFunc func(filePatterns []string, opt ScannerOption) (*Scanner, error) + fields fields + files []file + wantFilePath string + wantFileType types.ConfigType + misconfsExpected int + }{ + { + name: "happy path. Dockerfile", + scannerFunc: NewDockerfileScanner, + fields: fields{ + opt: ScannerOption{}, + }, + files: []file{ + { + path: "Dockerfile", + content: []byte(`FROM alpine`), + }, + }, + wantFilePath: "Dockerfile", + wantFileType: types.Dockerfile, + misconfsExpected: 1, + }, + { + name: "happy path. Dockerfile with custom file name", + scannerFunc: NewDockerfileScanner, + fields: fields{ + filePatterns: []string{"dockerfile:dockerf"}, + opt: ScannerOption{}, + }, + files: []file{ + { + path: "dockerf", + content: []byte(`FROM alpine`), + }, + }, + wantFilePath: "dockerf", + wantFileType: types.Dockerfile, + misconfsExpected: 1, + }, + { + name: "happy path. terraform plan file", + scannerFunc: NewTerraformPlanJSONScanner, + files: []file{ + { + path: "main.tfplan.json", + content: []byte(`{"format_version":"1.1","terraform_version":"1.4.6","planned_values":{"root_module":{"resources":[{"address":"aws_s3_bucket.my-bucket","mode":"managed","type":"aws_s3_bucket","name":"my-bucket","provider_name":"registry.terraform.io/hashicorp/aws","schema_version":0,"values":{"bucket":"evil","force_destroy":false,"tags":null,"timeouts":null},"sensitive_values":{"cors_rule":[],"grant":[],"lifecycle_rule":[],"logging":[],"object_lock_configuration":[],"replication_configuration":[],"server_side_encryption_configuration":[],"tags_all":{},"versioning":[],"website":[]}}]}},"resource_changes":[{"address":"aws_s3_bucket.my-bucket","mode":"managed","type":"aws_s3_bucket","name":"my-bucket","provider_name":"registry.terraform.io/hashicorp/aws","change":{"actions":["create"],"before":null,"after":{"bucket":"evil","force_destroy":false,"tags":null,"timeouts":null},"after_unknown":{"acceleration_status":true,"acl":true,"arn":true,"bucket_domain_name":true,"bucket_prefix":true,"bucket_regional_domain_name":true,"cors_rule":true,"grant":true,"hosted_zone_id":true,"id":true,"lifecycle_rule":true,"logging":true,"object_lock_configuration":true,"object_lock_enabled":true,"policy":true,"region":true,"replication_configuration":true,"request_payer":true,"server_side_encryption_configuration":true,"tags_all":true,"versioning":true,"website":true,"website_domain":true,"website_endpoint":true},"before_sensitive":false,"after_sensitive":{"cors_rule":[],"grant":[],"lifecycle_rule":[],"logging":[],"object_lock_configuration":[],"replication_configuration":[],"server_side_encryption_configuration":[],"tags_all":{},"versioning":[],"website":[]}}}],"configuration":{"provider_config":{"aws":{"name":"aws","full_name":"registry.terraform.io/hashicorp/aws","expressions":{"profile":{"constant_value":"foo-bar-123123123"},"region":{"constant_value":"us-west-1"}}}},"root_module":{"resources":[{"address":"aws_s3_bucket.my-bucket","mode":"managed","type":"aws_s3_bucket","name":"my-bucket","provider_config_key":"aws","expressions":{"bucket":{"constant_value":"evil"}},"schema_version":0}]}}}`), + }, + }, + wantFilePath: "main.tf", + wantFileType: types.TerraformPlanJSON, + misconfsExpected: 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a virtual filesystem for testing + fsys := mapfs.New() + for _, f := range tt.files { + err := fsys.WriteVirtualFile(f.path, f.content, 0666) + require.NoError(t, err) + } + + s, err := tt.scannerFunc(tt.fields.filePatterns, tt.fields.opt) + require.NoError(t, err) + + misconfs, err := s.Scan(context.Background(), fsys) + require.NoError(t, err) + require.Equal(t, tt.misconfsExpected, len(misconfs), "wrong number of misconfigurations found") + if tt.misconfsExpected == 1 { + assert.Equal(t, tt.wantFilePath, misconfs[0].FilePath, "filePaths don't equal") + assert.Equal(t, tt.wantFileType, misconfs[0].FileType, "fileTypes don't equal") + } + }) + } +} + +func Test_createPolicyFS(t *testing.T) { + t.Run("outside pwd", func(t *testing.T) { + tmpDir := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "subdir/testdir"), 0750)) + f, got, err := CreatePolicyFS([]string{filepath.Join(tmpDir, "subdir/testdir")}) + assertFS(t, tmpDir, f, got, err) + }) +} + +func Test_CreateDataFS(t *testing.T) { + t.Run("outside pwd", func(t *testing.T) { + tmpDir := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "subdir/testdir"), 0750)) + f, got, err := CreateDataFS([]string{filepath.Join(tmpDir, "subdir/testdir")}) + assertFS(t, tmpDir, f, got, err) + }) +} + +func assertFS(t *testing.T, tmpDir string, f fs.FS, got []string, err error) { + t.Helper() + + require.NoError(t, err) + assert.Equal(t, []string{"."}, got) + + d, err := f.Open(tmpDir) + require.NoError(t, err) + stat, err := d.Stat() + require.NoError(t, err) + assert.True(t, stat.IsDir()) +} diff --git a/pkg/module/api/api.go b/pkg/module/api/api.go new file mode 100644 index 000000000000..84c3bdd20b59 --- /dev/null +++ b/pkg/module/api/api.go @@ -0,0 +1,26 @@ +package api + +import "github.com/aquasecurity/trivy/pkg/module/serialize" + +const ( + Version = 1 + + ActionInsert serialize.PostScanAction = "INSERT" + ActionUpdate serialize.PostScanAction = "UPDATE" + ActionDelete serialize.PostScanAction = "DELETE" +) + +type Module interface { + Version() int + Name() string +} + +type Analyzer interface { + RequiredFiles() []string + Analyze(filePath string) (*serialize.AnalysisResult, error) +} + +type PostScanner interface { + PostScanSpec() serialize.PostScanSpec + PostScan(serialize.Results) (serialize.Results, error) +} diff --git a/pkg/module/command.go b/pkg/module/command.go new file mode 100644 index 000000000000..9114aea1ced6 --- /dev/null +++ b/pkg/module/command.go @@ -0,0 +1,55 @@ +package module + +import ( + "context" + "os" + "path/filepath" + + "github.com/google/go-containerregistry/pkg/name" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/oci" +) + +const mediaType = "application/vnd.module.wasm.content.layer.v1+wasm" + +// Install installs a module +func Install(ctx context.Context, dir, repo string, quiet bool, opt types.RegistryOptions) error { + ref, err := name.ParseReference(repo) + if err != nil { + return xerrors.Errorf("repository parse error: %w", err) + } + + log.Logger.Infof("Installing the module from %s...", repo) + artifact, err := oci.NewArtifact(repo, quiet, opt) + if err != nil { + return xerrors.Errorf("module initialize error: %w", err) + } + + dst := filepath.Join(dir, ref.Context().Name()) + log.Logger.Debugf("Installing the module to %s...", dst) + + if err = artifact.Download(ctx, dst, oci.DownloadOption{MediaType: mediaType}); err != nil { + return xerrors.Errorf("module download error: %w", err) + } + + return nil +} + +// Uninstall uninstalls a module +func Uninstall(_ context.Context, dir, repo string) error { + ref, err := name.ParseReference(repo) + if err != nil { + return xerrors.Errorf("repository parse error: %w", err) + } + + log.Logger.Infof("Uninstalling %s ...", repo) + dst := filepath.Join(dir, ref.Context().Name()) + if err = os.RemoveAll(dst); err != nil { + return xerrors.Errorf("remove error: %w", err) + } + + return nil +} diff --git a/pkg/module/memfs.go b/pkg/module/memfs.go new file mode 100644 index 000000000000..d407fd21e88c --- /dev/null +++ b/pkg/module/memfs.go @@ -0,0 +1,71 @@ +package module + +import ( + "io" + "io/fs" + "path/filepath" + "time" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/mapfs" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +// memFS is a wrapper of mapfs.FS and can change its underlying file system +// at runtime. This implements fs.FS. +type memFS struct { + current *mapfs.FS +} + +// Open implements fs.FS. +func (m *memFS) Open(name string) (fs.File, error) { + if m.current != nil { + return m.current.Open(name) + } + // memFS is always a directory. + if name == "." { + return &emptyDir{}, nil + } + return nil, fs.ErrNotExist +} + +// initialize changes the underlying memory file system with the given file path and contents. +// +// Note: it is always to safe swap the underlying FS with this API since this is called only at the beginning of +// Analyze interface call, which is not concurrently called per module instance. +func (m *memFS) initialize(filePath string, content xio.ReadSeekerAt) error { + mfs := mapfs.New() + if err := mfs.MkdirAll(filepath.Dir(filePath), fs.ModePerm); err != nil { + return xerrors.Errorf("mapfs mkdir error: %w", err) + } + b, err := io.ReadAll(content) + if err != nil { + return xerrors.Errorf("read error: %w", err) + } + err = mfs.WriteVirtualFile(filePath, b, fs.ModePerm) + if err != nil { + return xerrors.Errorf("mapfs write error: %w", err) + } + + m.current = mfs + return nil +} + +type emptyDir struct{} + +func (emptyDir) Close() (err error) { return } +func (emptyDir) Stat() (fs.FileInfo, error) { return fakeRootDirInfo{}, nil } +func (emptyDir) Read([]byte) (int, error) { + return 0, &fs.PathError{Op: "read", Path: "/", Err: fs.ErrInvalid} +} + +type fakeRootDirInfo struct{} + +func (fakeRootDirInfo) Name() string { return "/" } +func (fakeRootDirInfo) Size() int64 { return 0 } +func (fakeRootDirInfo) Mode() fs.FileMode { return fs.ModeDir | 0o500 } +func (fakeRootDirInfo) ModTime() time.Time { return time.Unix(0, 0) } +func (fakeRootDirInfo) IsDir() bool { return true } +func (fakeRootDirInfo) Sys() interface{} { return nil } +func (emptyDir) ReadDir(int) (dirents []fs.DirEntry, err error) { return } diff --git a/pkg/module/memfs_test.go b/pkg/module/memfs_test.go new file mode 100644 index 000000000000..fe60004cb783 --- /dev/null +++ b/pkg/module/memfs_test.go @@ -0,0 +1,59 @@ +package module + +import ( + "errors" + "io" + "io/fs" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMemFS(t *testing.T) { + m := &memFS{} + require.Nil(t, m.current) + + const path, content = "/usr/foo/bar.txt", "my-content" + err := m.initialize(path, strings.NewReader(content)) + require.NoError(t, err) + require.NotNil(t, m.current) + + t.Run("happy", func(t *testing.T) { + f, err := m.Open(path) + require.NoError(t, err) + actual, err := io.ReadAll(f) + require.NoError(t, err) + require.Equal(t, content, string(actual)) + }) + + t.Run("not found", func(t *testing.T) { + _, err = m.Open(path + "tmp") + require.ErrorIs(t, err, os.ErrNotExist) + }) +} + +func TestMemFS_NilIsDirectory(t *testing.T) { + // Wasm module initializes before an FS has been + // associated to this memFS. We handle nil + // so that the guest knows that the mount will map + // to a directory in the future. + m := &memFS{} + require.Nil(t, m.current) + + f, err := m.Open(".") + require.NoError(t, err) + + t.Run("stat is dir", func(t *testing.T) { + st, err := f.Stat() + require.NoError(t, err) + require.True(t, st.IsDir()) + }) + + t.Run("read invalid", func(t *testing.T) { + buf := make([]byte, 4) + _, err = f.Read(buf) + require.True(t, errors.Is(err, fs.ErrInvalid)) + }) +} diff --git a/pkg/module/module.go b/pkg/module/module.go new file mode 100644 index 000000000000..3d670999e1b9 --- /dev/null +++ b/pkg/module/module.go @@ -0,0 +1,749 @@ +package module + +import ( + "context" + "encoding/json" + "io/fs" + "os" + "path/filepath" + "regexp" + "sync" + + "github.com/samber/lo" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + wasi "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/log" + tapi "github.com/aquasecurity/trivy/pkg/module/api" + "github.com/aquasecurity/trivy/pkg/module/serialize" + "github.com/aquasecurity/trivy/pkg/scanner/post" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +var ( + logFunctions = map[string]api.GoModuleFunc{ + "debug": logDebug, + "info": logInfo, + "warn": logWarn, + "error": logError, + } + + RelativeDir = filepath.Join(".trivy", "modules") + + DefaultDir = dir() +) + +// logDebug is defined as an api.GoModuleFunc for lower overhead vs reflection. +func logDebug(_ context.Context, mod api.Module, params []uint64) { + offset, size := uint32(params[0]), uint32(params[1]) + + buf := readMemory(mod.Memory(), offset, size) + if buf != nil { + log.Logger.Debug(string(buf)) + } + + return +} + +// logInfo is defined as an api.GoModuleFunc for lower overhead vs reflection. +func logInfo(_ context.Context, mod api.Module, params []uint64) { + offset, size := uint32(params[0]), uint32(params[1]) + + buf := readMemory(mod.Memory(), offset, size) + if buf != nil { + log.Logger.Info(string(buf)) + } + + return +} + +// logWarn is defined as an api.GoModuleFunc for lower overhead vs reflection. +func logWarn(_ context.Context, mod api.Module, params []uint64) { + offset, size := uint32(params[0]), uint32(params[1]) + + buf := readMemory(mod.Memory(), offset, size) + if buf != nil { + log.Logger.Warn(string(buf)) + } + + return +} + +// logError is defined as an api.GoModuleFunc for lower overhead vs reflection. +func logError(_ context.Context, mod api.Module, params []uint64) { + offset, size := uint32(params[0]), uint32(params[1]) + + buf := readMemory(mod.Memory(), offset, size) + if buf != nil { + log.Logger.Error(string(buf)) + } + + return +} + +func readMemory(mem api.Memory, offset, size uint32) []byte { + buf, ok := mem.Read(offset, size) + if !ok { + log.Logger.Errorf("Memory.Read(%d, %d) out of range", offset, size) + return nil + } + return buf +} + +type Options struct { + Dir string + EnabledModules []string +} + +type Manager struct { + cache wazero.CompilationCache + modules []*wasmModule + dir string + enabledModules []string +} + +func NewManager(ctx context.Context, opts Options) (*Manager, error) { + m := &Manager{ + dir: opts.Dir, + enabledModules: opts.EnabledModules, + } + + // Create a new WebAssembly Runtime. + m.cache = wazero.NewCompilationCache() + + // Load WASM modules in local + if err := m.loadModules(ctx); err != nil { + return nil, xerrors.Errorf("module load error: %w", err) + } + + return m, nil +} + +func (m *Manager) loadModules(ctx context.Context) error { + _, err := os.Stat(m.dir) + if os.IsNotExist(err) { + return nil + } + log.Logger.Debugf("Module dir: %s", m.dir) + + err = filepath.Walk(m.dir, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } else if info.IsDir() || filepath.Ext(info.Name()) != ".wasm" { + return nil + } + + rel, err := filepath.Rel(m.dir, path) + if err != nil { + return xerrors.Errorf("failed to get a relative path: %w", err) + } + + log.Logger.Infof("Reading %s...", rel) + wasmCode, err := os.ReadFile(path) + if err != nil { + return xerrors.Errorf("file read error: %w", err) + } + + p, err := newWASMPlugin(ctx, m.cache, wasmCode) + if err != nil { + return xerrors.Errorf("WASM module init error %s: %w", rel, err) + } + + // Skip Loading WASM modules if not in the list of enable modules flag. + if len(m.enabledModules) > 0 && !slices.Contains(m.enabledModules, p.Name()) { + return nil + } + + log.Logger.Infof("%s loaded", rel) + m.modules = append(m.modules, p) + + return nil + }) + if err != nil { + return xerrors.Errorf("module walk error: %w", err) + } + + return nil +} + +func (m *Manager) Register() { + for _, mod := range m.modules { + mod.Register() + } +} + +func (m *Manager) Deregister() { + for _, mod := range m.modules { + analyzer.DeregisterAnalyzer(analyzer.Type(mod.Name())) + post.DeregisterPostScanner(mod.Name()) + } +} + +func (m *Manager) Close(ctx context.Context) error { + return m.cache.Close(ctx) +} + +func splitPtrSize(u uint64) (uint32, uint32) { + ptr := uint32(u >> 32) + size := uint32(u) + return ptr, size +} + +func ptrSizeToString(mem api.Memory, ptrSize uint64) (string, error) { + ptr, size := splitPtrSize(ptrSize) + buf := readMemory(mem, ptr, size) + if buf == nil { + return "", xerrors.New("unable to read memory") + } + return string(buf), nil +} + +// stringToPtr returns a pointer and size pair for the given string in a way compatible with WebAssembly numeric types. +func stringToPtrSize(ctx context.Context, s string, mod api.Module, malloc api.Function) (uint64, uint64, error) { + size := uint64(len(s)) + results, err := malloc.Call(ctx, size) + if err != nil { + return 0, 0, xerrors.Errorf("malloc error: %w", err) + } + + // The pointer is a linear memory offset, which is where we write the string. + ptr := results[0] + if !mod.Memory().Write(uint32(ptr), []byte(s)) { + return 0, 0, xerrors.Errorf("Memory.Write(%d, %d) out of range of memory size %d", + ptr, size, mod.Memory().Size()) + } + + return ptr, size, nil +} + +func unmarshal(mem api.Memory, ptrSize uint64, v any) error { + ptr, size := splitPtrSize(ptrSize) + buf := readMemory(mem, ptr, size) + if buf == nil { + return xerrors.New("unable to read memory") + } + if err := json.Unmarshal(buf, v); err != nil { + return xerrors.Errorf("unmarshal error: %w", err) + } + + return nil +} + +func marshal(ctx context.Context, m api.Module, malloc api.Function, v any) (uint64, uint64, error) { + b, err := json.Marshal(v) + if err != nil { + return 0, 0, xerrors.Errorf("marshal error: %w", err) + } + + size := uint64(len(b)) + results, err := malloc.Call(ctx, size) + if err != nil { + return 0, 0, xerrors.Errorf("malloc error: %w", err) + } + + // The pointer is a linear memory offset, which is where we write the marshaled value. + ptr := results[0] + if !m.Memory().Write(uint32(ptr), b) { + return 0, 0, xerrors.Errorf("Memory.Write(%d, %d) out of range of memory size %d", + ptr, size, m.Memory().Size()) + } + + return ptr, size, nil +} + +type wasmModule struct { + mod api.Module + memFS *memFS + mux sync.Mutex + + name string + version int + requiredFiles []*regexp.Regexp + + isAnalyzer bool + isPostScanner bool + postScanSpec serialize.PostScanSpec + + // Exported functions + analyze api.Function + postScan api.Function + malloc api.Function // TinyGo specific + free api.Function // TinyGo specific +} + +func newWASMPlugin(ctx context.Context, ccache wazero.CompilationCache, code []byte) (*wasmModule, error) { + mf := &memFS{} + config := wazero.NewModuleConfig().WithStdout(os.Stdout).WithFS(mf) + + // Create an empty namespace so that multiple modules will not conflict + r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig().WithCompilationCache(ccache)) + + // Instantiate a Go-defined module named "env" that exports functions. + envBuilder := r.NewHostModuleBuilder("env") + + // Avoid reflection for logging as it implies an overhead of >1us per call. + for n, f := range logFunctions { + envBuilder.NewFunctionBuilder(). + WithGoModuleFunction(f, []api.ValueType{ + api.ValueTypeI32, + api.ValueTypeI32, + }, []api.ValueType{}). + WithParameterNames("offset", "size"). + Export(n) + } + + if _, err := envBuilder.Instantiate(ctx); err != nil { + return nil, xerrors.Errorf("wasm module build error: %w", err) + } + + if _, err := wasi.NewBuilder(r).Instantiate(ctx); err != nil { + return nil, xerrors.Errorf("WASI init error: %w", err) + } + + // Compile the WebAssembly module using the default configuration. + compiled, err := r.CompileModule(ctx, code) + if err != nil { + return nil, xerrors.Errorf("module compile error: %w", err) + } + + // InstantiateModule runs the "_start" function which is what TinyGo compiles "main" to. + mod, err := r.InstantiateModule(ctx, compiled, config) + if err != nil { + return nil, xerrors.Errorf("module init error: %w", err) + } + + // These are undocumented, but exported. See tinygo-org/tinygo#2788 + // TODO: improve TinyGo specific code + malloc := mod.ExportedFunction("malloc") + free := mod.ExportedFunction("free") + + // Get a module name + name, err := moduleName(ctx, mod) + if err != nil { + return nil, xerrors.Errorf("failed to get a module name: %w", err) + } + + // Get a module version + version, err := moduleVersion(ctx, mod) + if err != nil { + return nil, xerrors.Errorf("failed to get a module version: %w", err) + } + + // Get a module API version + apiVersion, err := moduleAPIVersion(ctx, mod) + if err != nil { + return nil, xerrors.Errorf("failed to get a module version: %w", err) + } + + if apiVersion != tapi.Version { + log.Logger.Infof("Ignore %s@v%d module due to API version mismatch, got: %d, want: %d", + name, version, apiVersion, tapi.Version) + return nil, nil + } + + isAnalyzer, err := moduleIsAnalyzer(ctx, mod) + if err != nil { + return nil, xerrors.Errorf("failed to check if the module is an analyzer: %w", err) + } + + isPostScanner, err := moduleIsPostScanner(ctx, mod) + if err != nil { + return nil, xerrors.Errorf("failed to check if the module is a post scanner: %w", err) + } + + // Get exported functions by WASM module + analyzeFunc := mod.ExportedFunction("analyze") + if analyzeFunc == nil { + return nil, xerrors.New("analyze() must be exported") + } + postScanFunc := mod.ExportedFunction("post_scan") + if postScanFunc == nil { + return nil, xerrors.New("post_scan() must be exported") + } + + var requiredFiles []*regexp.Regexp + if isAnalyzer { + // Get required files + requiredFiles, err = moduleRequiredFiles(ctx, mod) + if err != nil { + return nil, xerrors.Errorf("failed to get required files: %w", err) + } + } + + var postScanSpec serialize.PostScanSpec + if isPostScanner { + // This spec defines how the module works in post scanning like INSERT, UPDATE and DELETE. + postScanSpec, err = modulePostScanSpec(ctx, mod) + if err != nil { + return nil, xerrors.Errorf("failed to get a post scan spec: %w", err) + } + } + + return &wasmModule{ + mod: mod, + memFS: mf, + name: name, + version: version, + requiredFiles: requiredFiles, + + isAnalyzer: isAnalyzer, + isPostScanner: isPostScanner, + postScanSpec: postScanSpec, + + analyze: analyzeFunc, + postScan: postScanFunc, + malloc: malloc, + free: free, + }, nil +} + +func (m *wasmModule) Register() { + log.Logger.Infof("Registering WASM module: %s@v%d", m.name, m.version) + if m.isAnalyzer { + log.Logger.Debugf("Registering custom analyzer in %s@v%d", m.name, m.version) + analyzer.RegisterAnalyzer(m) + } + if m.isPostScanner { + log.Logger.Debugf("Registering custom post scanner in %s@v%d", m.name, m.version) + post.RegisterPostScanner(m) + } +} + +func (m *wasmModule) Close(ctx context.Context) error { + return m.mod.Close(ctx) +} + +func (m *wasmModule) Type() analyzer.Type { + return analyzer.Type(m.name) +} + +func (m *wasmModule) Name() string { + return m.name +} + +func (m *wasmModule) Version() int { + return m.version +} + +func (m *wasmModule) Required(filePath string, _ os.FileInfo) bool { + for _, r := range m.requiredFiles { + if r.MatchString(filePath) { + return true + } + } + return false +} + +func (m *wasmModule) Analyze(ctx context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + filePath := "/" + filepath.ToSlash(input.FilePath) + log.Logger.Debugf("Module %s: analyzing %s...", m.name, filePath) + + // Wasm module instances are not Goroutine safe, so we take look here since Analyze might be called concurrently. + // TODO: This is temporary solution and we could improve the Analyze performance by having module instance pool. + m.mux.Lock() + defer m.mux.Unlock() + + if err := m.memFS.initialize(filePath, input.Content); err != nil { + return nil, err + } + + inputPtr, inputSize, err := stringToPtrSize(ctx, filePath, m.mod, m.malloc) + if err != nil { + return nil, xerrors.Errorf("failed to write string to memory: %w", err) + } + defer m.free.Call(ctx, inputPtr) // nolint: errcheck + + analyzeRes, err := m.analyze.Call(ctx, inputPtr, inputSize) + if err != nil { + return nil, xerrors.Errorf("analyze error: %w", err) + } else if len(analyzeRes) != 1 { + return nil, xerrors.New("invalid signature: analyze") + } + + var result analyzer.AnalysisResult + if err = unmarshal(m.mod.Memory(), analyzeRes[0], &result); err != nil { + return nil, xerrors.Errorf("invalid return value: %w", err) + } + + return &result, nil +} + +// PostScan performs post scanning +// e.g. Remove a vulnerability, change severity, etc. +func (m *wasmModule) PostScan(ctx context.Context, results types.Results) (types.Results, error) { + // Find custom resources + var custom serialize.Result + for _, result := range results { + if result.Class == types.ClassCustom { + custom = serialize.Result(result) + break + } + } + + arg := serialize.Results{custom} + switch m.postScanSpec.Action { + case tapi.ActionUpdate, tapi.ActionDelete: + // Pass the relevant results to the module + arg = append(arg, findIDs(m.postScanSpec.IDs, results)...) + } + + // Marshal the argument into WASM memory so that the WASM module can read it. + inputPtr, inputSize, err := marshal(ctx, m.mod, m.malloc, arg) + if err != nil { + return nil, xerrors.Errorf("post scan marshal error: %w", err) + } + defer m.free.Call(ctx, inputPtr) //nolint: errcheck + + analyzeRes, err := m.postScan.Call(ctx, inputPtr, inputSize) + if err != nil { + return nil, xerrors.Errorf("post scan invocation error: %w", err) + } else if len(analyzeRes) != 1 { + return nil, xerrors.New("invalid signature: post_scan") + } + + var got types.Results + if err = unmarshal(m.mod.Memory(), analyzeRes[0], &got); err != nil { + return nil, xerrors.Errorf("post scan unmarshal error: %w", err) + } + + switch m.postScanSpec.Action { + case tapi.ActionInsert: + results = append(results, lo.Filter(got, func(r types.Result, _ int) bool { + return r.Class != types.ClassCustom && r.Class != "" + })...) + case tapi.ActionUpdate: + updateResults(got, results) + case tapi.ActionDelete: + deleteResults(got, results) + } + + return results, nil +} + +func findIDs(ids []string, results types.Results) serialize.Results { + var filtered serialize.Results + for _, result := range results { + if result.Class == types.ClassCustom { + continue + } + vulns := lo.Filter(result.Vulnerabilities, func(v types.DetectedVulnerability, _ int) bool { + return slices.Contains(ids, v.VulnerabilityID) + }) + misconfs := lo.Filter(result.Misconfigurations, func(m types.DetectedMisconfiguration, _ int) bool { + return slices.Contains(ids, m.ID) + }) + if len(vulns) > 0 || len(misconfs) > 0 { + filtered = append(filtered, serialize.Result{ + Target: result.Target, + Class: result.Class, + Type: result.Type, + Vulnerabilities: vulns, + Misconfigurations: misconfs, + }) + } + } + return filtered +} + +func updateResults(gotResults, results types.Results) { + for _, g := range gotResults { + for i, result := range results { + if g.Target == result.Target && g.Class == result.Class && g.Type == result.Type { + results[i].Vulnerabilities = lo.Map(result.Vulnerabilities, func(v types.DetectedVulnerability, _ int) types.DetectedVulnerability { + // Update vulnerabilities in the existing result + for _, got := range g.Vulnerabilities { + if got.VulnerabilityID == v.VulnerabilityID && got.PkgName == v.PkgName && + got.PkgPath == v.PkgPath && got.InstalledVersion == v.InstalledVersion { + + // Override vulnerability details + v.SeveritySource = got.SeveritySource + v.Vulnerability = got.Vulnerability + } + } + return v + }) + + results[i].Misconfigurations = lo.Map(result.Misconfigurations, func(m types.DetectedMisconfiguration, _ int) types.DetectedMisconfiguration { + // Update misconfigurations in the existing result + for _, got := range g.Misconfigurations { + if got.ID == m.ID && + got.CauseMetadata.StartLine == m.CauseMetadata.StartLine && + got.CauseMetadata.EndLine == m.CauseMetadata.EndLine { + + // Override misconfiguration details + m.CauseMetadata = got.CauseMetadata + m.Severity = got.Severity + m.Status = got.Status + } + } + return m + }) + } + } + } +} + +func deleteResults(gotResults, results types.Results) { + for _, gotResult := range gotResults { + for i, result := range results { + // Remove vulnerabilities and misconfigurations from the existing result + if gotResult.Target == result.Target && gotResult.Class == result.Class && gotResult.Type == result.Type { + results[i].Vulnerabilities = lo.Reject(result.Vulnerabilities, func(v types.DetectedVulnerability, _ int) bool { + for _, got := range gotResult.Vulnerabilities { + if got.VulnerabilityID == v.VulnerabilityID && got.PkgName == v.PkgName && + got.PkgPath == v.PkgPath && got.InstalledVersion == v.InstalledVersion { + return true + } + } + return false + }) + results[i].Misconfigurations = lo.Reject(result.Misconfigurations, func(v types.DetectedMisconfiguration, _ int) bool { + for _, got := range gotResult.Misconfigurations { + if got.ID == v.ID && got.Status == v.Status && + got.CauseMetadata.StartLine == v.CauseMetadata.StartLine && + got.CauseMetadata.EndLine == v.CauseMetadata.EndLine { + return true + } + } + return false + }) + } + } + } +} + +func moduleName(ctx context.Context, mod api.Module) (string, error) { + nameFunc := mod.ExportedFunction("name") + if nameFunc == nil { + return "", xerrors.New("name() must be exported") + } + + nameRes, err := nameFunc.Call(ctx) + if err != nil { + return "", xerrors.Errorf("wasm function name() invocation error: %w", err) + } else if len(nameRes) != 1 { + return "", xerrors.New("invalid signature: name()") + } + + name, err := ptrSizeToString(mod.Memory(), nameRes[0]) + if err != nil { + return "", xerrors.Errorf("invalid return value: %w", err) + } + return name, nil +} + +func moduleVersion(ctx context.Context, mod api.Module) (int, error) { + versionFunc := mod.ExportedFunction("version") + if versionFunc == nil { + return 0, xerrors.New("version() must be exported") + } + + versionRes, err := versionFunc.Call(ctx) + if err != nil { + return 0, xerrors.Errorf("wasm function version() invocation error: %w", err) + } else if len(versionRes) != 1 { + return 0, xerrors.New("invalid signature: version") + } + + return int(uint32(versionRes[0])), nil +} + +func moduleAPIVersion(ctx context.Context, mod api.Module) (int, error) { + versionFunc := mod.ExportedFunction("api_version") + if versionFunc == nil { + return 0, xerrors.New("api_version() must be exported") + } + + versionRes, err := versionFunc.Call(ctx) + if err != nil { + return 0, xerrors.Errorf("wasm function api_version() invocation error: %w", err) + } else if len(versionRes) != 1 { + return 0, xerrors.New("invalid signature: api_version") + } + + return int(uint32(versionRes[0])), nil +} + +func moduleRequiredFiles(ctx context.Context, mod api.Module) ([]*regexp.Regexp, error) { + requiredFilesFunc := mod.ExportedFunction("required") + if requiredFilesFunc == nil { + return nil, xerrors.New("required() must be exported") + } + + requiredFilesRes, err := requiredFilesFunc.Call(ctx) + if err != nil { + return nil, xerrors.Errorf("wasm function required() invocation error: %w", err) + } else if len(requiredFilesRes) != 1 { + return nil, xerrors.New("invalid signature: required_files") + } + + var fileRegexps serialize.StringSlice + if err = unmarshal(mod.Memory(), requiredFilesRes[0], &fileRegexps); err != nil { + return nil, xerrors.Errorf("invalid return value: %w", err) + } + + var requiredFiles []*regexp.Regexp + for _, file := range fileRegexps { + re, err := regexp.Compile(file) + if err != nil { + return nil, xerrors.Errorf("regexp compile error: %w", err) + } + requiredFiles = append(requiredFiles, re) + } + + return requiredFiles, nil +} + +func moduleIsAnalyzer(ctx context.Context, mod api.Module) (bool, error) { + return isType(ctx, mod, "is_analyzer") +} + +func moduleIsPostScanner(ctx context.Context, mod api.Module) (bool, error) { + return isType(ctx, mod, "is_post_scanner") +} + +func isType(ctx context.Context, mod api.Module, name string) (bool, error) { + isFunc := mod.ExportedFunction(name) + if isFunc == nil { + return false, xerrors.Errorf("%s() must be exported", name) + } + + isRes, err := isFunc.Call(ctx) + if err != nil { + return false, xerrors.Errorf("wasm function %s() invocation error: %w", name, err) + } else if len(isRes) != 1 { + return false, xerrors.Errorf("invalid signature: %s", name) + } + + return isRes[0] > 0, nil +} + +func dir() string { + return filepath.Join(fsutils.HomeDir(), RelativeDir) +} + +func modulePostScanSpec(ctx context.Context, mod api.Module) (serialize.PostScanSpec, error) { + postScanSpecFunc := mod.ExportedFunction("post_scan_spec") + if postScanSpecFunc == nil { + return serialize.PostScanSpec{}, xerrors.New("post_scan_spec() must be exported") + } + + postScanSpecRes, err := postScanSpecFunc.Call(ctx) + if err != nil { + return serialize.PostScanSpec{}, xerrors.Errorf("wasm function post_scan_spec() invocation error: %w", err) + } else if len(postScanSpecRes) != 1 { + return serialize.PostScanSpec{}, xerrors.New("invalid signature: post_scan_spec") + } + + var spec serialize.PostScanSpec + if err = unmarshal(mod.Memory(), postScanSpecRes[0], &spec); err != nil { + return serialize.PostScanSpec{}, xerrors.Errorf("invalid return value: %w", err) + } + + return spec, nil +} diff --git a/pkg/module/module_test.go b/pkg/module/module_test.go new file mode 100644 index 000000000000..581b7ca3ed55 --- /dev/null +++ b/pkg/module/module_test.go @@ -0,0 +1,133 @@ +package module_test + +import ( + "context" + "io/fs" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/module" + "github.com/aquasecurity/trivy/pkg/scanner/post" +) + +func TestManager_Register(t *testing.T) { + if runtime.GOOS == "windows" { + // WASM tests difficult on Windows + t.Skip("Test satisfied adequately by Linux tests") + } + tests := []struct { + name string + moduleDir string + enabledModules []string + wantAnalyzerVersions analyzer.Versions + wantPostScannerVersions map[string]int + wantErr bool + }{ + { + name: "happy path", + moduleDir: "testdata/happy", + wantAnalyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "happy": 1, + }, + PostAnalyzers: map[string]int{}, + }, + wantPostScannerVersions: map[string]int{ + "happy": 1, + }, + }, + { + name: "only analyzer", + moduleDir: "testdata/analyzer", + wantAnalyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "analyzer": 1, + }, + PostAnalyzers: map[string]int{}, + }, + wantPostScannerVersions: map[string]int{}, + }, + { + name: "only post scanner", + moduleDir: "testdata/scanner", + wantAnalyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{}, + PostAnalyzers: map[string]int{}, + }, + wantPostScannerVersions: map[string]int{ + "scanner": 2, + }, + }, + { + name: "no module dir", + moduleDir: "no-such-dir", + wantAnalyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{}, + PostAnalyzers: map[string]int{}, + }, + wantPostScannerVersions: map[string]int{}, + }, + { + name: "pass enabled modules", + moduleDir: "testdata", + enabledModules: []string{ + "happy", + "analyzer", + }, + wantAnalyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "happy": 1, + "analyzer": 1, + }, + PostAnalyzers: map[string]int{}, + }, + wantPostScannerVersions: map[string]int{ + "happy": 1, + }, + }, + } + + // Confirm that wasm modules are generated beforehand + var count int + err := filepath.WalkDir("testdata", func(path string, d fs.DirEntry, err error) error { + if filepath.Ext(path) == ".wasm" { + count++ + } + return nil + }) + require.NoError(t, err) + // WASM modules must be generated before running the tests. + require.Equal(t, count, 3, "missing WASM modules, try 'mage test:unit' or 'mage test:generateModules'") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m, err := module.NewManager(context.Background(), module.Options{ + Dir: tt.moduleDir, + EnabledModules: tt.enabledModules, + }) + require.NoError(t, err) + + // Register analyzer and post scanner from WASM module + m.Register() + + // Remove registered analyzers and post scanners so that it will not affect other tests. + defer m.Deregister() + + // Confirm the analyzer is registered + a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got := a.AnalyzerVersions() + assert.Equal(t, tt.wantAnalyzerVersions, got) + + // Confirm the post scanner is registered + gotScannerVersions := post.ScannerVersions() + assert.Equal(t, tt.wantPostScannerVersions, gotScannerVersions) + }) + } +} diff --git a/pkg/module/serialize/types.go b/pkg/module/serialize/types.go new file mode 100644 index 000000000000..df72a953eee3 --- /dev/null +++ b/pkg/module/serialize/types.go @@ -0,0 +1,42 @@ +package serialize + +import ( + "github.com/aquasecurity/trivy/pkg/types" +) + +type StringSlice []string + +type AnalysisResult struct { + // TODO: support other fields as well + // OS *types.OS + // Repository *types.Repository + // PackageInfos []types.PackageInfo + // Applications []types.Application + // Secrets []types.Secret + // SystemInstalledFiles []string // A list of files installed by OS package manager + + // Currently it supports custom resources only + CustomResources []CustomResource +} + +type CustomResource struct { + Type string + FilePath string + Data interface{} +} + +type PostScanAction string + +type PostScanSpec struct { + // What action the module will do in post scanning. + // value: INSERT, UPDATE and DELETE + Action PostScanAction + + // IDs represent which vulnerability and misconfiguration ID will be updated or deleted in post scanning. + // When the action is UPDATE, the matched result will be passed to the module. + IDs []string +} + +type Results []Result + +type Result types.Result diff --git a/pkg/module/testdata/analyzer/analyzer.go b/pkg/module/testdata/analyzer/analyzer.go new file mode 100644 index 000000000000..5e15aa625e0a --- /dev/null +++ b/pkg/module/testdata/analyzer/analyzer.go @@ -0,0 +1,38 @@ +//go:generate tinygo build -o analyzer.wasm -scheduler=none -target=wasi --no-debug analyzer.go +//go:build tinygo.wasm + +package main + +import ( + "github.com/aquasecurity/trivy/pkg/module/serialize" + "github.com/aquasecurity/trivy/pkg/module/wasm" +) + +const ( + moduleVersion = 1 + moduleName = "analyzer" +) + +func main() { + wasm.RegisterModule(AnalyzerModule{}) +} + +type AnalyzerModule struct{} + +func (AnalyzerModule) Version() int { + return moduleVersion +} + +func (AnalyzerModule) Name() string { + return moduleName +} + +func (AnalyzerModule) RequiredFiles() []string { + return []string{ + `foo(.?)`, + } +} + +func (s AnalyzerModule) Analyze(_ string) (*serialize.AnalysisResult, error) { + return nil, nil +} diff --git a/pkg/module/testdata/happy/happy.go b/pkg/module/testdata/happy/happy.go new file mode 100644 index 000000000000..c2bd7b006840 --- /dev/null +++ b/pkg/module/testdata/happy/happy.go @@ -0,0 +1,47 @@ +//go:generate tinygo build -o happy.wasm -scheduler=none -target=wasi --no-debug happy.go +//go:build tinygo.wasm + +package main + +import ( + "github.com/aquasecurity/trivy/pkg/module/api" + "github.com/aquasecurity/trivy/pkg/module/serialize" + "github.com/aquasecurity/trivy/pkg/module/wasm" +) + +const ( + moduleVersion = 1 + moduleName = "happy" +) + +func main() { + wasm.RegisterModule(HappyModule{}) +} + +type HappyModule struct{} + +func (HappyModule) Version() int { + return moduleVersion +} + +func (HappyModule) Name() string { + return moduleName +} + +func (HappyModule) RequiredFiles() []string { + return []string{} +} + +func (s HappyModule) Analyze(_ string) (*serialize.AnalysisResult, error) { + return nil, nil +} + +func (HappyModule) PostScanSpec() serialize.PostScanSpec { + return serialize.PostScanSpec{ + Action: api.ActionInsert, // Add new vulnerabilities + } +} + +func (HappyModule) PostScan(_ serialize.Results) (serialize.Results, error) { + return nil, nil +} diff --git a/pkg/module/testdata/scanner/scanner.go b/pkg/module/testdata/scanner/scanner.go new file mode 100644 index 000000000000..d631984ce410 --- /dev/null +++ b/pkg/module/testdata/scanner/scanner.go @@ -0,0 +1,39 @@ +//go:generate tinygo build -o scanner.wasm -scheduler=none -target=wasi --no-debug scanner.go +//go:build tinygo.wasm + +package main + +import ( + "github.com/aquasecurity/trivy/pkg/module/api" + "github.com/aquasecurity/trivy/pkg/module/serialize" + "github.com/aquasecurity/trivy/pkg/module/wasm" +) + +const ( + moduleVersion = 2 + moduleName = "scanner" +) + +func main() { + wasm.RegisterModule(PostScannerModule{}) +} + +type PostScannerModule struct{} + +func (PostScannerModule) Version() int { + return moduleVersion +} + +func (PostScannerModule) Name() string { + return moduleName +} + +func (PostScannerModule) PostScanSpec() serialize.PostScanSpec { + return serialize.PostScanSpec{ + Action: api.ActionInsert, // Add new vulnerabilities + } +} + +func (PostScannerModule) PostScan(_ serialize.Results) (serialize.Results, error) { + return nil, nil +} diff --git a/pkg/module/wasm/sdk.go b/pkg/module/wasm/sdk.go new file mode 100644 index 000000000000..25b9f1e777c3 --- /dev/null +++ b/pkg/module/wasm/sdk.go @@ -0,0 +1,174 @@ +//go:build tinygo.wasm + +package wasm + +// This package is designed to be imported by WASM modules. +// TinyGo can build this package, but Go cannot. + +import ( + "encoding/json" + "fmt" + "reflect" + "unsafe" + + "github.com/aquasecurity/trivy/pkg/module/api" + "github.com/aquasecurity/trivy/pkg/module/serialize" +) + +func Debug(message string) { + message = fmt.Sprintf("Module %s: %s", module.Name(), message) + ptr, size := stringToPtr(message) + _debug(ptr, size) +} + +func Info(message string) { + message = fmt.Sprintf("Module %s: %s", module.Name(), message) + ptr, size := stringToPtr(message) + _info(ptr, size) +} + +func Warn(message string) { + message = fmt.Sprintf("Module %s: %s", module.Name(), message) + ptr, size := stringToPtr(message) + _warn(ptr, size) +} + +func Error(message string) { + message = fmt.Sprintf("Module %s: %s", module.Name(), message) + ptr, size := stringToPtr(message) + _error(ptr, size) +} + +//go:wasmimport env debug +func _debug(ptr uint32, size uint32) + +//go:wasmimport env info +func _info(ptr uint32, size uint32) + +//go:wasmimport env warn +func _warn(ptr uint32, size uint32) + +//go:wasmimport env error +func _error(ptr uint32, size uint32) + +var module api.Module + +func RegisterModule(p api.Module) { + module = p +} + +//export name +func _name() uint64 { + name := module.Name() + ptr, size := stringToPtr(name) + return (uint64(ptr) << uint64(32)) | uint64(size) +} + +//export api_version +func _apiVersion() uint32 { + return api.Version +} + +//export version +func _version() uint32 { + return uint32(module.Version()) +} + +//export is_analyzer +func _isAnalyzer() uint64 { + if _, ok := module.(api.Analyzer); !ok { + return 0 + } + return 1 +} + +//export required +func _required() uint64 { + files := module.(api.Analyzer).RequiredFiles() + ss := serialize.StringSlice(files) + return marshal(ss) +} + +//export analyze +func _analyze(ptr, size uint32) uint64 { + filePath := ptrToString(ptr, size) + custom, err := module.(api.Analyzer).Analyze(filePath) + if err != nil { + Error(fmt.Sprintf("analyze error: %s", err)) + return 0 + } + return marshal(custom) +} + +//export is_post_scanner +func _isPostScanner() uint64 { + if _, ok := module.(api.PostScanner); !ok { + return 0 + } + return 1 +} + +//export post_scan_spec +func _post_scan_spec() uint64 { + return marshal(module.(api.PostScanner).PostScanSpec()) +} + +//export post_scan +func _post_scan(ptr, size uint32) uint64 { + var results serialize.Results + if err := unmarshal(ptr, size, &results); err != nil { + Error(fmt.Sprintf("post scan error: %s", err)) + return 0 + } + + results, err := module.(api.PostScanner).PostScan(results) + if err != nil { + Error(fmt.Sprintf("post scan error: %s", err)) + return 0 + } + return marshal(results) +} + +func marshal(v any) uint64 { + b, err := json.Marshal(v) + if err != nil { + Error(fmt.Sprintf("marshal error: %s", err)) + return 0 + } + + p := uintptr(unsafe.Pointer(&b[0])) + return (uint64(p) << uint64(32)) | uint64(len(b)) +} + +func unmarshal(ptr, size uint32, v any) error { + var b []byte + s := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + s.Len = uintptr(size) + s.Cap = uintptr(size) + s.Data = uintptr(ptr) + + if err := json.Unmarshal(b, v); err != nil { + return fmt.Errorf("unmarshal error: %s", err) + } + + return nil +} + +// ptrToString returns a string from WebAssembly compatible numeric types representing its pointer and length. +func ptrToString(ptr uint32, size uint32) string { + // Get a slice view of the underlying bytes in the stream. We use SliceHeader, not StringHeader + // as it allows us to fix the capacity to what was allocated. + return *(*string)(unsafe.Pointer(&reflect.SliceHeader{ + Data: uintptr(ptr), + Len: uintptr(size), // Tinygo requires these as uintptrs even if they are int fields. + Cap: uintptr(size), // ^^ See https://github.com/tinygo-org/tinygo/issues/1284 + })) +} + +// stringToPtr returns a pointer and size pair for the given string in a way compatible with WebAssembly numeric types. +func stringToPtr(s string) (uint32, uint32) { + buf := []byte(s) + ptr := &buf[0] + unsafePtr := uintptr(unsafe.Pointer(ptr)) + return uint32(unsafePtr), uint32(len(buf)) +} diff --git a/pkg/oci/artifact.go b/pkg/oci/artifact.go new file mode 100644 index 000000000000..bab9ec7065ab --- /dev/null +++ b/pkg/oci/artifact.go @@ -0,0 +1,208 @@ +package oci + +import ( + "context" + "io" + "os" + "path/filepath" + "sync" + + "github.com/cheggaaa/pb/v3" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/downloader" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/remote" +) + +const ( + // Artifact types + CycloneDXArtifactType = "application/vnd.cyclonedx+json" + SPDXArtifactType = "application/spdx+json" + + // Media types + OCIImageManifest = "application/vnd.oci.image.manifest.v1+json" + + // Annotations + titleAnnotation = "org.opencontainers.image.title" +) + +var SupportedSBOMArtifactTypes = []string{ + CycloneDXArtifactType, + SPDXArtifactType, +} + +// Option is a functional option +type Option func(*Artifact) + +// WithImage takes an OCI v1 Image +func WithImage(img v1.Image) Option { + return func(a *Artifact) { + a.image = img + } +} + +// Artifact is used to download artifacts such as vulnerability database and policies from OCI registries. +type Artifact struct { + m sync.Mutex + repository string + quiet bool + + // For OCI registries + types.RegistryOptions + + image v1.Image // For testing +} + +// NewArtifact returns a new artifact +func NewArtifact(repo string, quiet bool, registryOpt types.RegistryOptions, opts ...Option) (*Artifact, error) { + art := &Artifact{ + repository: repo, + quiet: quiet, + RegistryOptions: registryOpt, + } + + for _, o := range opts { + o(art) + } + return art, nil +} + +func (a *Artifact) populate(ctx context.Context, opt types.RegistryOptions) error { + if a.image != nil { + return nil + } + + a.m.Lock() + defer a.m.Unlock() + + var nameOpts []name.Option + if opt.Insecure { + nameOpts = append(nameOpts, name.Insecure) + } + + ref, err := name.ParseReference(a.repository, nameOpts...) + if err != nil { + return xerrors.Errorf("repository name error (%s): %w", a.repository, err) + } + + a.image, err = remote.Image(ctx, ref, opt) + if err != nil { + return xerrors.Errorf("OCI repository error: %w", err) + } + return nil +} + +type DownloadOption struct { + MediaType string // Accept any media type if not specified + Filename string // Use the annotation if not specified +} + +func (a *Artifact) Download(ctx context.Context, dir string, opt DownloadOption) error { + if err := a.populate(ctx, a.RegistryOptions); err != nil { + return err + } + + layers, err := a.image.Layers() + if err != nil { + return xerrors.Errorf("OCI layer error: %w", err) + } + + manifest, err := a.image.Manifest() + if err != nil { + return xerrors.Errorf("OCI manifest error: %w", err) + } + + // A single layer is only supported now. + if len(layers) != 1 || len(manifest.Layers) != 1 { + return xerrors.Errorf("OCI artifact must be a single layer") + } + + // Take the first layer + layer := layers[0] + + // Take the file name of the first layer if not specified + fileName := opt.Filename + if fileName == "" { + if v, ok := manifest.Layers[0].Annotations[titleAnnotation]; !ok { + return xerrors.Errorf("annotation %s is missing", titleAnnotation) + } else { + fileName = v + } + } + + layerMediaType, err := layer.MediaType() + if err != nil { + return xerrors.Errorf("media type error: %w", err) + } else if opt.MediaType != "" && opt.MediaType != string(layerMediaType) { + return xerrors.Errorf("unacceptable media type: %s", string(layerMediaType)) + } + + if err = a.download(ctx, layer, fileName, dir); err != nil { + return xerrors.Errorf("oci download error: %w", err) + } + + return nil +} + +func (a *Artifact) download(ctx context.Context, layer v1.Layer, fileName, dir string) error { + size, err := layer.Size() + if err != nil { + return xerrors.Errorf("size error: %w", err) + } + + rc, err := layer.Compressed() + if err != nil { + return xerrors.Errorf("failed to fetch the layer: %w", err) + } + defer rc.Close() + + // Show progress bar + bar := pb.Full.Start64(size) + if a.quiet { + bar.SetWriter(io.Discard) + } + pr := bar.NewProxyReader(rc) + defer bar.Finish() + + // https://github.com/hashicorp/go-getter/issues/326 + tempDir, err := os.MkdirTemp("", "trivy") + if err != nil { + return xerrors.Errorf("failed to create a temp dir: %w", err) + } + + f, err := os.Create(filepath.Join(tempDir, fileName)) + if err != nil { + return xerrors.Errorf("failed to create a temp file: %w", err) + } + defer func() { + _ = f.Close() + _ = os.RemoveAll(tempDir) + }() + + // Download the layer content into a temporal file + if _, err = io.Copy(f, pr); err != nil { + return xerrors.Errorf("copy error: %w", err) + } + + // Decompress the downloaded file if it is compressed and copy it into the dst + if err = downloader.Download(ctx, f.Name(), dir, dir); err != nil { + return xerrors.Errorf("download error: %w", err) + } + + return nil +} + +func (a *Artifact) Digest(ctx context.Context) (string, error) { + if err := a.populate(ctx, a.RegistryOptions); err != nil { + return "", err + } + + digest, err := a.image.Digest() + if err != nil { + return "", xerrors.Errorf("digest error: %w", err) + } + return digest.String(), nil +} diff --git a/pkg/oci/artifact_test.go b/pkg/oci/artifact_test.go new file mode 100644 index 000000000000..d28dbe55bc1e --- /dev/null +++ b/pkg/oci/artifact_test.go @@ -0,0 +1,140 @@ +package oci_test + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + fakei "github.com/google/go-containerregistry/pkg/v1/fake" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/oci" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +type fakeLayer struct { + v1.Layer +} + +func (f fakeLayer) MediaType() (types.MediaType, error) { + return "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", nil +} + +func TestArtifact_Download(t *testing.T) { + layer, err := tarball.LayerFromFile("testdata/test.tar.gz") + require.NoError(t, err) + + txtLayer, err := tarball.LayerFromFile("testdata/test.txt") + require.NoError(t, err) + + flayer := fakeLayer{layer} + + type layersReturns struct { + layers []v1.Layer + err error + } + tests := []struct { + name string + input string + mediaType string + layersReturns layersReturns + want string + wantErr string + }{ + { + name: "happy path", + input: "testdata/test.tar.gz", + mediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", + layersReturns: layersReturns{ + layers: []v1.Layer{flayer}, + }, + want: "Hello, world", + }, + { + name: "sad: two layers", + mediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", + layersReturns: layersReturns{ + layers: []v1.Layer{ + layer, + layer, + }, + }, + wantErr: "OCI artifact must be a single layer", + }, + { + name: "sad: Layers returns an error", + mediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", + layersReturns: layersReturns{ + err: fmt.Errorf("error"), + }, + wantErr: "OCI layer error", + }, + { + name: "invalid gzip", + input: "testdata/test.txt", + layersReturns: layersReturns{ + layers: []v1.Layer{txtLayer}, + }, + wantErr: "unexpected EOF", + }, + { + name: "sad: media type doesn't match", + mediaType: "unknown", + layersReturns: layersReturns{ + layers: []v1.Layer{layer}, + }, + wantErr: "unacceptable media type", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + fsutils.SetCacheDir(tempDir) + + // Mock image + img := new(fakei.FakeImage) + img.LayersReturns(tt.layersReturns.layers, tt.layersReturns.err) + img.ManifestReturns(&v1.Manifest{ + Layers: []v1.Descriptor{ + { + MediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", + Size: 100, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "cba33656188782852f58993f45b68bfb8577f64cdcf02a604e3fc2afbeb5f2d8", + }, + Annotations: map[string]string{ + "org.opencontainers.image.title": "bundle.tar.gz", + }, + }, + }, + }, nil) + + artifact, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img)) + require.NoError(t, err) + + err = artifact.Download(context.Background(), tempDir, oci.DownloadOption{ + MediaType: tt.mediaType, + }) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + + // Assert + got, err := os.ReadFile(filepath.Join(tempDir, "test.txt")) + require.NoError(t, err) + + assert.Equal(t, tt.want, string(got)) + }) + } +} diff --git a/pkg/oci/testdata/test.tar.gz b/pkg/oci/testdata/test.tar.gz new file mode 100644 index 000000000000..8fc713487111 Binary files /dev/null and b/pkg/oci/testdata/test.tar.gz differ diff --git a/pkg/oci/testdata/test.txt b/pkg/oci/testdata/test.txt new file mode 100644 index 000000000000..dbe9dba55ea8 --- /dev/null +++ b/pkg/oci/testdata/test.txt @@ -0,0 +1 @@ +Hello, world \ No newline at end of file diff --git a/pkg/parallel/pipeline.go b/pkg/parallel/pipeline.go new file mode 100644 index 000000000000..7dbc70f3e17c --- /dev/null +++ b/pkg/parallel/pipeline.go @@ -0,0 +1,115 @@ +package parallel + +import ( + "context" + + "github.com/cheggaaa/pb/v3" + "golang.org/x/sync/errgroup" +) + +const defaultWorkers = 5 + +// Pipeline represents a structure for performing parallel processing. +// T represents the input element type and U represents the output element type. +type Pipeline[T, U any] struct { + numWorkers int + items []T + onItem onItem[T, U] + onResult onResult[U] + progress bool +} + +// onItem represents a function type that takes an input element and returns an output element. +type onItem[T, U any] func(context.Context, T) (U, error) + +// onResult represents a function type that takes an output element. +type onResult[U any] func(U) error + +func NewPipeline[T, U any](numWorkers int, progress bool, items []T, + fn1 onItem[T, U], fn2 onResult[U]) Pipeline[T, U] { + if fn2 == nil { + // In case where there is no need to process the return values + fn2 = func(_ U) error { return nil } + } + if numWorkers == 0 { + numWorkers = defaultWorkers + } + return Pipeline[T, U]{ + numWorkers: numWorkers, + progress: progress, + items: items, + onItem: fn1, + onResult: fn2, + } +} + +// Do executes pipeline processing. +// It exits when any error occurs. +func (p *Pipeline[T, U]) Do(ctx context.Context) error { + // progress bar + var bar *pb.ProgressBar + if p.progress { + bar = pb.StartNew(len(p.items)) + defer bar.Finish() + } + + g, ctx := errgroup.WithContext(ctx) + itemCh := make(chan T) + + // Start a goroutine to send input data + g.Go(func() error { + defer close(itemCh) + for _, item := range p.items { + if p.progress { + bar.Increment() + } + select { + case itemCh <- item: + case <-ctx.Done(): + return ctx.Err() + } + } + return nil + }) + + // Generate a channel for sending output data + results := make(chan U) + + // Start a fixed number of goroutines to process items. + for i := 0; i < p.numWorkers; i++ { + g.Go(func() error { + for item := range itemCh { + res, err := p.onItem(ctx, item) + if err != nil { + return err + } + select { + case results <- res: + case <-ctx.Done(): + return ctx.Err() + } + } + return nil + }) + } + + go func() { + _ = g.Wait() + close(results) + }() + + // Process output data received from the channel + for res := range results { + if err := p.onResult(res); err != nil { + return err + } + } + + // Check whether any of the goroutines failed. Since g is accumulating the + // errors, we don't need to send them (or check for them) in the individual + // results sent on the channel. + if err := g.Wait(); err != nil { + return err + } + return nil +} diff --git a/pkg/parallel/pipeline_test.go b/pkg/parallel/pipeline_test.go new file mode 100644 index 000000000000..60b8cec100ab --- /dev/null +++ b/pkg/parallel/pipeline_test.go @@ -0,0 +1,114 @@ +package parallel_test + +import ( + "context" + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/parallel" +) + +func TestPipeline_Do(t *testing.T) { + type field struct { + numWorkers int + items []float64 + onItem func(context.Context, float64) (float64, error) + } + type testCase struct { + name string + field field + want float64 + wantErr require.ErrorAssertionFunc + } + tests := []testCase{ + { + name: "pow", + field: field{ + numWorkers: 5, + items: []float64{ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + }, + onItem: func(_ context.Context, f float64) (float64, error) { + return math.Pow(f, 2), nil + }, + }, + want: 385, + wantErr: require.NoError, + }, + { + name: "ceil", + field: field{ + numWorkers: 3, + items: []float64{ + 1.1, + 2.2, + 3.3, + 4.4, + 5.5, + -1.1, + -2.2, + -3.3, + }, + onItem: func(_ context.Context, f float64) (float64, error) { + return math.Round(f), nil + }, + }, + want: 10, + wantErr: require.NoError, + }, + { + name: "error in series", + field: field{ + numWorkers: 1, + items: []float64{ + 1, + 2, + 3, + }, + onItem: func(_ context.Context, f float64) (float64, error) { + return 0, fmt.Errorf("error") + }, + }, + wantErr: require.Error, + }, + { + name: "error in parallel", + field: field{ + numWorkers: 3, + items: []float64{ + 1, + 2, + }, + onItem: func(_ context.Context, f float64) (float64, error) { + return 0, fmt.Errorf("error") + }, + }, + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got float64 + p := parallel.NewPipeline(tt.field.numWorkers, false, tt.field.items, tt.field.onItem, func(f float64) error { + got += f + return nil + }) + err := p.Do(context.Background()) + tt.wantErr(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/parallel/walk.go b/pkg/parallel/walk.go new file mode 100644 index 000000000000..1156560a7212 --- /dev/null +++ b/pkg/parallel/walk.go @@ -0,0 +1,119 @@ +package parallel + +import ( + "context" + "io/fs" + + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +const defaultParallel = 5 + +type onFile[T any] func(string, fs.FileInfo, xio.ReadSeekerAt) (T, error) +type onWalkResult[T any] func(T) error + +func WalkDir[T any](ctx context.Context, fsys fs.FS, root string, parallel int, + onFile onFile[T], onResult onWalkResult[T]) error { + + g, ctx := errgroup.WithContext(ctx) + paths := make(chan string) + + g.Go(func() error { + defer close(paths) + err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } else if !d.Type().IsRegular() { + return nil + } + + // check if file is empty + info, err := d.Info() + if err != nil { + return err + } else if info.Size() == 0 { + log.Logger.Debugf("%s is empty, skip this file", path) + return nil + } + + select { + case paths <- path: + case <-ctx.Done(): + return ctx.Err() + } + return nil + }) + if err != nil { + return xerrors.Errorf("walk error: %w", err) + } + return nil + }) + + // Start a fixed number of goroutines to read and digest files. + c := make(chan T) + if parallel == 0 { + parallel = defaultParallel + } + for i := 0; i < parallel; i++ { + g.Go(func() error { + for path := range paths { + if err := walk(ctx, fsys, path, c, onFile); err != nil { + return err + } + } + return nil + }) + } + go func() { + _ = g.Wait() + close(c) + }() + + for res := range c { + if err := onResult(res); err != nil { + return err + } + } + // Check whether any of the goroutines failed. Since g is accumulating the + // errors, we don't need to send them (or check for them) in the individual + // results sent on the channel. + if err := g.Wait(); err != nil { + return err + } + return nil +} + +func walk[T any](ctx context.Context, fsys fs.FS, path string, c chan T, onFile onFile[T]) error { + f, err := fsys.Open(path) + if err != nil { + return xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + info, err := f.Stat() + if err != nil { + return xerrors.Errorf("stat error: %w", err) + } + + rsa, ok := f.(xio.ReadSeekerAt) + if !ok { + return xerrors.New("type assertion failed") + } + res, err := onFile(path, info, rsa) + if err != nil { + log.Logger.Debugw("Walk error", zap.String("file_path", path), zap.Error(err)) + return nil + } + + select { + case c <- res: + case <-ctx.Done(): + return ctx.Err() + } + return nil +} diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go new file mode 100644 index 000000000000..f96f02a80c00 --- /dev/null +++ b/pkg/plugin/plugin.go @@ -0,0 +1,379 @@ +package plugin + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/downloader" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +const ( + configFile = "plugin.yaml" +) + +var ( + pluginsRelativeDir = filepath.Join(".trivy", "plugins") + + officialPlugins = map[string]string{ + "kubectl": "github.com/aquasecurity/trivy-plugin-kubectl", + "aqua": "github.com/aquasecurity/trivy-plugin-aqua", + } +) + +// Plugin represents a plugin. +type Plugin struct { + Name string `yaml:"name"` + Repository string `yaml:"repository"` + Version string `yaml:"version"` + Usage string `yaml:"usage"` + Description string `yaml:"description"` + Platforms []Platform `yaml:"platforms"` + + // runtime environment for testability + GOOS string `yaml:"_goos"` + GOARCH string `yaml:"_goarch"` +} + +// Platform represents where the execution file exists per platform. +type Platform struct { + Selector *Selector + URI string + Bin string +} + +// Selector represents the environment. +type Selector struct { + OS string + Arch string +} + +type RunOptions struct { + Args []string + Stdin io.Reader +} + +func (p Plugin) Cmd(ctx context.Context, opts RunOptions) (*exec.Cmd, error) { + platform, err := p.selectPlatform() + if err != nil { + return nil, xerrors.Errorf("platform selection error: %w", err) + } + + execFile := filepath.Join(dir(), p.Name, platform.Bin) + + cmd := exec.CommandContext(ctx, execFile, opts.Args...) + cmd.Stdin = os.Stdin + if opts.Stdin != nil { + cmd.Stdin = opts.Stdin + } + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = os.Environ() + + return cmd, nil +} + +type Wait func() error + +// Start starts the plugin +// +// After a successful call to Start the Wait method must be called. +func (p Plugin) Start(ctx context.Context, opts RunOptions) (Wait, error) { + cmd, err := p.Cmd(ctx, opts) + if err != nil { + return nil, xerrors.Errorf("cmd: %w", err) + } + + if err = cmd.Start(); err != nil { + return nil, xerrors.Errorf("plugin start: %w", err) + } + return cmd.Wait, nil +} + +// Run runs the plugin +func (p Plugin) Run(ctx context.Context, opts RunOptions) error { + cmd, err := p.Cmd(ctx, opts) + if err != nil { + return xerrors.Errorf("cmd: %w", err) + } + + // If an error is found during the execution of the plugin, figure + // out if the error was from not being able to execute the plugin or + // an error set by the plugin itself. + if err = cmd.Run(); err != nil { + if _, ok := err.(*exec.ExitError); !ok { + return xerrors.Errorf("exit: %w", err) + } + return xerrors.Errorf("plugin exec: %w", err) + } + return nil +} + +func (p Plugin) selectPlatform() (Platform, error) { + // These values are only filled in during unit tests. + if p.GOOS == "" { + p.GOOS = runtime.GOOS + } + if p.GOARCH == "" { + p.GOARCH = runtime.GOARCH + } + + for _, platform := range p.Platforms { + if platform.Selector == nil { + return platform, nil + } + + selector := platform.Selector + if (selector.OS == "" || p.GOOS == selector.OS) && + (selector.Arch == "" || p.GOARCH == selector.Arch) { + log.Logger.Debugf("Platform found, os: %s, arch: %s", selector.OS, selector.Arch) + return platform, nil + } + } + return Platform{}, xerrors.New("platform not found") +} + +func (p Plugin) install(ctx context.Context, dst, pwd string) error { + log.Logger.Debugf("Installing the plugin to %s...", dst) + platform, err := p.selectPlatform() + if err != nil { + return xerrors.Errorf("platform selection error: %w", err) + } + + log.Logger.Debugf("Downloading the execution file from %s...", platform.URI) + if err = downloader.Download(ctx, platform.URI, dst, pwd); err != nil { + return xerrors.Errorf("unable to download the execution file (%s): %w", platform.URI, err) + } + return nil +} + +func (p Plugin) dir() (string, error) { + if p.Name == "" { + return "", xerrors.Errorf("'name' is empty") + } + + // e.g. ~/.trivy/plugins/kubectl + return filepath.Join(dir(), p.Name), nil +} + +// Install installs a plugin +func Install(ctx context.Context, url string, force bool) (Plugin, error) { + // Replace short names with full qualified names + // e.g. kubectl => github.com/aquasecurity/trivy-plugin-kubectl + if v, ok := officialPlugins[url]; ok { + url = v + } + + if !force { + // If the plugin is already installed, it skips installing the plugin. + if p, installed := isInstalled(url); installed { + return p, nil + } + } + + log.Logger.Infof("Installing the plugin from %s...", url) + tempDir, err := downloader.DownloadToTempDir(ctx, url) + if err != nil { + return Plugin{}, xerrors.Errorf("download failed: %w", err) + } + defer os.RemoveAll(tempDir) + + log.Logger.Info("Loading the plugin metadata...") + plugin, err := loadMetadata(tempDir) + if err != nil { + return Plugin{}, xerrors.Errorf("failed to load the plugin metadata: %w", err) + } + + pluginDir, err := plugin.dir() + if err != nil { + return Plugin{}, xerrors.Errorf("failed to determine the plugin dir: %w", err) + } + + if err = plugin.install(ctx, pluginDir, tempDir); err != nil { + return Plugin{}, xerrors.Errorf("failed to install the plugin: %w", err) + } + + // Copy plugin.yaml into the plugin dir + if _, err = fsutils.CopyFile(filepath.Join(tempDir, configFile), filepath.Join(pluginDir, configFile)); err != nil { + return Plugin{}, xerrors.Errorf("failed to copy plugin.yaml: %w", err) + } + + return plugin, nil +} + +// Uninstall installs the plugin +func Uninstall(name string) error { + pluginDir := filepath.Join(dir(), name) + return os.RemoveAll(pluginDir) +} + +// Information gets the information about an installed plugin +func Information(name string) (string, error) { + plugin, err := load(name) + if err != nil { + return "", xerrors.Errorf("plugin load error: %w", err) + } + + return fmt.Sprintf(` +Plugin: %s + Description: %s + Version: %s + Usage: %s +`, plugin.Name, plugin.Description, plugin.Version, plugin.Usage), nil +} + +// List gets a list of all installed plugins +func List() (string, error) { + if _, err := os.Stat(dir()); err != nil { + if os.IsNotExist(err) { + return "No Installed Plugins\n", nil + } + return "", xerrors.Errorf("stat error: %w", err) + } + plugins, err := LoadAll() + if err != nil { + return "", xerrors.Errorf("unable to load plugins: %w", err) + } + pluginList := []string{"Installed Plugins:"} + for _, plugin := range plugins { + pluginList = append(pluginList, fmt.Sprintf(" Name: %s\n Version: %s\n", plugin.Name, plugin.Version)) + } + + return strings.Join(pluginList, "\n"), nil +} + +// Update updates an existing plugin +func Update(name string) error { + plugin, err := load(name) + if err != nil { + return xerrors.Errorf("plugin load error: %w", err) + } + + log.Logger.Infof("Updating plugin '%s'", name) + updated, err := Install(nil, plugin.Repository, true) + if err != nil { + return xerrors.Errorf("unable to perform an update installation: %w", err) + } + + if plugin.Version == updated.Version { + log.Logger.Infof("The %s plugin is the latest version. [%s]", name, plugin.Version) + } else { + log.Logger.Infof("Updated '%s' from %s to %s", name, plugin.Version, updated.Version) + } + return nil +} + +// LoadAll loads all plugins +func LoadAll() ([]Plugin, error) { + pluginsDir := dir() + dirs, err := os.ReadDir(pluginsDir) + if err != nil { + return nil, xerrors.Errorf("failed to read %s: %w", pluginsDir, err) + } + + var plugins []Plugin + for _, d := range dirs { + if !d.IsDir() { + continue + } + plugin, err := loadMetadata(filepath.Join(pluginsDir, d.Name())) + if err != nil { + log.Logger.Warnf("plugin load error: %s", err) + continue + } + plugins = append(plugins, plugin) + } + return plugins, nil +} + +// Start starts the plugin +func Start(ctx context.Context, name string, opts RunOptions) (Wait, error) { + plugin, err := load(name) + if err != nil { + return nil, xerrors.Errorf("plugin load error: %w", err) + } + + wait, err := plugin.Start(ctx, opts) + if err != nil { + return nil, xerrors.Errorf("unable to run %s plugin: %w", plugin.Name, err) + } + return wait, nil +} + +// RunWithURL runs the plugin with URL +func RunWithURL(ctx context.Context, url string, opts RunOptions) error { + plugin, err := Install(ctx, url, false) + if err != nil { + return xerrors.Errorf("plugin install error: %w", err) + } + + if err = plugin.Run(ctx, opts); err != nil { + return xerrors.Errorf("unable to run %s plugin: %w", plugin.Name, err) + } + return nil +} + +func IsPredefined(name string) bool { + _, ok := officialPlugins[name] + return ok +} + +func load(name string) (Plugin, error) { + pluginDir := filepath.Join(dir(), name) + if _, err := os.Stat(pluginDir); err != nil { + if os.IsNotExist(err) { + return Plugin{}, xerrors.Errorf("could not find a plugin called '%s', did you install it?", name) + } + return Plugin{}, xerrors.Errorf("plugin stat error: %w", err) + } + + plugin, err := loadMetadata(pluginDir) + if err != nil { + return Plugin{}, xerrors.Errorf("unable to load plugin metadata: %w", err) + } + + return plugin, nil +} + +func loadMetadata(dir string) (Plugin, error) { + filePath := filepath.Join(dir, configFile) + f, err := os.Open(filePath) + if err != nil { + return Plugin{}, xerrors.Errorf("file open error: %w", err) + } + + var plugin Plugin + if err = yaml.NewDecoder(f).Decode(&plugin); err != nil { + return Plugin{}, xerrors.Errorf("yaml decode error: %w", err) + } + + return plugin, nil +} + +func dir() string { + return filepath.Join(fsutils.HomeDir(), pluginsRelativeDir) +} + +func isInstalled(url string) (Plugin, bool) { + installedPlugins, err := LoadAll() + if err != nil { + return Plugin{}, false + } + + for _, plugin := range installedPlugins { + if plugin.Repository == url { + return plugin, true + } + } + return Plugin{}, false +} diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go new file mode 100644 index 000000000000..f9ee7aac2b89 --- /dev/null +++ b/pkg/plugin/plugin_test.go @@ -0,0 +1,445 @@ +package plugin_test + +import ( + "context" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/plugin" +) + +func TestPlugin_Run(t *testing.T) { + if runtime.GOOS == "windows" { + // the test.sh script can't be run on windows so skipping + t.Skip("Test satisfied adequately by Linux tests") + } + type fields struct { + Name string + Repository string + Version string + Usage string + Description string + Platforms []plugin.Platform + GOOS string + GOARCH string + } + tests := []struct { + name string + fields fields + opts plugin.RunOptions + wantErr string + }{ + { + name: "happy path", + fields: fields{ + Name: "test_plugin", + Repository: "github.com/aquasecurity/trivy-plugin-test", + Version: "0.1.0", + Usage: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + Selector: &plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + URI: "github.com/aquasecurity/trivy-plugin-test", + Bin: "test.sh", + }, + }, + GOOS: "linux", + GOARCH: "amd64", + }, + }, + { + name: "no selector", + fields: fields{ + Name: "test_plugin", + Repository: "github.com/aquasecurity/trivy-plugin-test", + Version: "0.1.0", + Usage: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + URI: "github.com/aquasecurity/trivy-plugin-test", + Bin: "test.sh", + }, + }, + }, + }, + { + name: "no matched platform", + fields: fields{ + Name: "test_plugin", + Repository: "github.com/aquasecurity/trivy-plugin-test", + Version: "0.1.0", + Usage: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + Selector: &plugin.Selector{ + OS: "darwin", + Arch: "amd64", + }, + URI: "github.com/aquasecurity/trivy-plugin-test", + Bin: "test.sh", + }, + }, + GOOS: "linux", + GOARCH: "amd64", + }, + wantErr: "platform not found", + }, + { + name: "no execution file", + fields: fields{ + Name: "test_plugin", + Repository: "github.com/aquasecurity/trivy-plugin-test", + Version: "0.1.0", + Usage: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + Selector: &plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + URI: "github.com/aquasecurity/trivy-plugin-test", + Bin: "nonexistence.sh", + }, + }, + GOOS: "linux", + GOARCH: "amd64", + }, + wantErr: "no such file or directory", + }, + { + name: "plugin exec error", + fields: fields{ + Name: "error_plugin", + Repository: "github.com/aquasecurity/trivy-plugin-error", + Version: "0.1.0", + Usage: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + Selector: &plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + URI: "github.com/aquasecurity/trivy-plugin-test", + Bin: "test.sh", + }, + }, + GOOS: "linux", + GOARCH: "amd64", + }, + wantErr: "plugin exec: exit status 1", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + os.Setenv("XDG_DATA_HOME", "testdata") + defer os.Unsetenv("XDG_DATA_HOME") + + p := plugin.Plugin{ + Name: tt.fields.Name, + Repository: tt.fields.Repository, + Version: tt.fields.Version, + Usage: tt.fields.Usage, + Description: tt.fields.Description, + Platforms: tt.fields.Platforms, + GOOS: tt.fields.GOOS, + GOARCH: tt.fields.GOARCH, + } + + err := p.Run(context.Background(), tt.opts) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + }) + } +} + +func TestInstall(t *testing.T) { + if runtime.GOOS == "windows" { + // the test.sh script can't be run on windows so skipping + t.Skip("Test satisfied adequately by Linux tests") + } + tests := []struct { + name string + url string + want plugin.Plugin + wantFile string + wantErr string + }{ + { + name: "happy path", + url: "testdata/test_plugin", + want: plugin.Plugin{ + Name: "test_plugin", + Repository: "github.com/aquasecurity/trivy-plugin-test", + Version: "0.1.0", + Usage: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + Selector: &plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + URI: "./test.sh", + Bin: "./test.sh", + }, + }, + GOOS: "linux", + GOARCH: "amd64", + }, + wantFile: ".trivy/plugins/test_plugin/test.sh", + }, + { + name: "plugin not found", + url: "testdata/not_found", + want: plugin.Plugin{ + Name: "test_plugin", + Repository: "github.com/aquasecurity/trivy-plugin-test", + Version: "0.1.0", + Usage: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + Selector: &plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + URI: "./test.sh", + Bin: "./test.sh", + }, + }, + GOOS: "linux", + GOARCH: "amd64", + }, + wantErr: "no such file or directory", + }, + { + name: "no plugin.yaml", + url: "testdata/no_yaml", + want: plugin.Plugin{ + Name: "no_yaml", + Repository: "github.com/aquasecurity/trivy-plugin-test", + Version: "0.1.0", + Usage: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + Selector: &plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + URI: "./test.sh", + Bin: "./test.sh", + }, + }, + GOOS: "linux", + GOARCH: "amd64", + }, + wantErr: "file open error", + }, + } + + log.InitLogger(false, true) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // The test plugin will be installed here + dst := t.TempDir() + os.Setenv("XDG_DATA_HOME", dst) + + got, err := plugin.Install(context.Background(), tt.url, false) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + + assert.Equal(t, tt.want, got) + assert.FileExists(t, filepath.Join(dst, tt.wantFile)) + }) + } +} + +func TestUninstall(t *testing.T) { + if runtime.GOOS == "windows" { + // the test.sh script can't be run on windows so skipping + t.Skip("Test satisfied adequately by Linux tests") + } + pluginName := "test_plugin" + + tempDir := t.TempDir() + pluginDir := filepath.Join(tempDir, ".trivy", "plugins", pluginName) + + // Create the test plugin directory + err := os.MkdirAll(pluginDir, os.ModePerm) + require.NoError(t, err) + + // Create the test file + err = os.WriteFile(filepath.Join(pluginDir, "test.sh"), []byte(`foo`), os.ModePerm) + require.NoError(t, err) + + // Uninstall the plugin + err = plugin.Uninstall(pluginName) + assert.NoError(t, err) + assert.NoFileExists(t, pluginDir) +} + +func TestInformation(t *testing.T) { + if runtime.GOOS == "windows" { + // the test.sh script can't be run on windows so skipping + t.Skip("Test satisfied adequately by Linux tests") + } + pluginName := "test_plugin" + + tempDir := t.TempDir() + pluginDir := filepath.Join(tempDir, ".trivy", "plugins", pluginName) + + t.Setenv("XDG_DATA_HOME", tempDir) + + // Create the test plugin directory + err := os.MkdirAll(pluginDir, os.ModePerm) + require.NoError(t, err) + + // write the plugin name + pluginMetadata := `name: "test_plugin" +repository: github.com/aquasecurity/trivy-plugin-test +version: "0.1.0" +usage: test +description: A simple test plugin` + + err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginMetadata), os.ModePerm) + require.NoError(t, err) + + // Get Information for the plugin + info, err := plugin.Information(pluginName) + require.NoError(t, err) + assert.Equal(t, "\nPlugin: test_plugin\n Description: A simple test plugin\n Version: 0.1.0\n Usage: test\n", info) + + // Get Information for unknown plugin + info, err = plugin.Information("unknown") + require.Error(t, err) + assert.ErrorContains(t, err, "could not find a plugin called 'unknown', did you install it?") +} + +func TestLoadAll1(t *testing.T) { + if runtime.GOOS == "windows" { + // the test.sh script can't be run on windows so skipping + t.Skip("Test satisfied adequately by Linux tests") + } + tests := []struct { + name string + dir string + want []plugin.Plugin + wantErr string + }{ + { + name: "happy path", + dir: "testdata", + want: []plugin.Plugin{ + { + Name: "test_plugin", + Repository: "github.com/aquasecurity/trivy-plugin-test", + Version: "0.1.0", + Usage: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + Selector: &plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + URI: "./test.sh", + Bin: "./test.sh", + }, + }, + GOOS: "linux", + GOARCH: "amd64", + }, + }, + }, + { + name: "sad path", + dir: "sad", + wantErr: "no such file or directory", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + os.Setenv("XDG_DATA_HOME", tt.dir) + defer os.Unsetenv("XDG_DATA_HOME") + + got, err := plugin.LoadAll() + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestUpdate(t *testing.T) { + if runtime.GOOS == "windows" { + // the test.sh script can't be run on windows so skipping + t.Skip("Test satisfied adequately by Linux tests") + } + pluginName := "test_plugin" + + tempDir := t.TempDir() + pluginDir := filepath.Join(tempDir, ".trivy", "plugins", pluginName) + + t.Setenv("XDG_DATA_HOME", tempDir) + + // Create the test plugin directory + err := os.MkdirAll(pluginDir, os.ModePerm) + require.NoError(t, err) + + // write the plugin name + pluginMetadata := `name: "test_plugin" +repository: testdata/test_plugin +version: "0.0.5" +usage: test +description: A simple test plugin` + + err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginMetadata), os.ModePerm) + require.NoError(t, err) + + // verify initial version + verifyVersion(t, pluginName, "0.0.5") + + // Update the existing plugin + err = plugin.Update(pluginName) + require.NoError(t, err) + + // verify plugin updated + verifyVersion(t, pluginName, "0.1.0") +} + +func verifyVersion(t *testing.T, pluginName, expectedVersion string) { + plugins, err := plugin.LoadAll() + require.NoError(t, err) + for _, plugin := range plugins { + if plugin.Name == pluginName { + assert.Equal(t, expectedVersion, plugin.Version) + } + } +} diff --git a/pkg/plugin/testdata/.trivy/plugins/dummy.txt b/pkg/plugin/testdata/.trivy/plugins/dummy.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/plugin/testdata/.trivy/plugins/error_plugin/test.sh b/pkg/plugin/testdata/.trivy/plugins/error_plugin/test.sh new file mode 100755 index 000000000000..91c5ffc57bca --- /dev/null +++ b/pkg/plugin/testdata/.trivy/plugins/error_plugin/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 1 \ No newline at end of file diff --git a/pkg/plugin/testdata/.trivy/plugins/test_plugin/plugin.yaml b/pkg/plugin/testdata/.trivy/plugins/test_plugin/plugin.yaml new file mode 100644 index 000000000000..7c2021b29196 --- /dev/null +++ b/pkg/plugin/testdata/.trivy/plugins/test_plugin/plugin.yaml @@ -0,0 +1,14 @@ +name: "test_plugin" +repository: github.com/aquasecurity/trivy-plugin-test +version: "0.1.0" +usage: test +description: test +platforms: + - selector: + os: linux + arch: amd64 + uri: ./test.sh + bin: ./test.sh +# for testing +_goos: linux +_goarch: amd64 \ No newline at end of file diff --git a/pkg/plugin/testdata/.trivy/plugins/test_plugin/test.sh b/pkg/plugin/testdata/.trivy/plugins/test_plugin/test.sh new file mode 100755 index 000000000000..490535e047e7 --- /dev/null +++ b/pkg/plugin/testdata/.trivy/plugins/test_plugin/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "foo" \ No newline at end of file diff --git a/pkg/plugin/testdata/no_yaml/.keep b/pkg/plugin/testdata/no_yaml/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/plugin/testdata/test_plugin/plugin.yaml b/pkg/plugin/testdata/test_plugin/plugin.yaml new file mode 100644 index 000000000000..7c2021b29196 --- /dev/null +++ b/pkg/plugin/testdata/test_plugin/plugin.yaml @@ -0,0 +1,14 @@ +name: "test_plugin" +repository: github.com/aquasecurity/trivy-plugin-test +version: "0.1.0" +usage: test +description: test +platforms: + - selector: + os: linux + arch: amd64 + uri: ./test.sh + bin: ./test.sh +# for testing +_goos: linux +_goarch: amd64 \ No newline at end of file diff --git a/pkg/plugin/testdata/test_plugin/test.sh b/pkg/plugin/testdata/test_plugin/test.sh new file mode 100755 index 000000000000..490535e047e7 --- /dev/null +++ b/pkg/plugin/testdata/test_plugin/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "foo" \ No newline at end of file diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go new file mode 100644 index 000000000000..9dc802c8207e --- /dev/null +++ b/pkg/policy/policy.go @@ -0,0 +1,245 @@ +package policy + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/open-policy-agent/opa/bundle" + "golang.org/x/xerrors" + "k8s.io/utils/clock" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/oci" +) + +const ( + BundleVersion = 0 // Latest released MAJOR version for trivy-policies + BundleRepository = "ghcr.io/aquasecurity/trivy-policies" + policyMediaType = "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip" + updateInterval = 24 * time.Hour +) + +type options struct { + artifact *oci.Artifact + clock clock.Clock +} + +// WithOCIArtifact takes an OCI artifact +func WithOCIArtifact(art *oci.Artifact) Option { + return func(opts *options) { + opts.artifact = art + } +} + +// WithClock takes a clock +func WithClock(c clock.Clock) Option { + return func(opts *options) { + opts.clock = c + } +} + +// Option is a functional option +type Option func(*options) + +// Client implements policy operations +type Client struct { + *options + policyDir string + policyBundleRepo string + quiet bool +} + +// Metadata holds default policy metadata +type Metadata struct { + Digest string + DownloadedAt time.Time +} + +func (m Metadata) String() string { + return fmt.Sprintf(`Policy Bundle: + Digest: %s + DownloadedAt: %s +`, m.Digest, m.DownloadedAt.UTC()) +} + +// NewClient is the factory method for policy client +func NewClient(cacheDir string, quiet bool, policyBundleRepo string, opts ...Option) (*Client, error) { + o := &options{ + clock: clock.RealClock{}, + } + + for _, opt := range opts { + opt(o) + } + + if policyBundleRepo == "" { + policyBundleRepo = fmt.Sprintf("%s:%d", BundleRepository, BundleVersion) + } + + return &Client{ + options: o, + policyDir: filepath.Join(cacheDir, "policy"), + policyBundleRepo: policyBundleRepo, + quiet: quiet, + }, nil +} + +func (c *Client) populateOCIArtifact(registryOpts types.RegistryOptions) error { + if c.artifact == nil { + log.Logger.Debugf("Using URL: %s to load policy bundle", c.policyBundleRepo) + art, err := oci.NewArtifact(c.policyBundleRepo, c.quiet, registryOpts) + if err != nil { + return xerrors.Errorf("OCI artifact error: %w", err) + } + c.artifact = art + } + return nil +} + +// DownloadBuiltinPolicies download default policies from GitHub Pages +func (c *Client) DownloadBuiltinPolicies(ctx context.Context, registryOpts types.RegistryOptions) error { + if err := c.populateOCIArtifact(registryOpts); err != nil { + return xerrors.Errorf("OPA bundle error: %w", err) + } + + dst := c.contentDir() + if err := c.artifact.Download(ctx, dst, oci.DownloadOption{MediaType: policyMediaType}); err != nil { + return xerrors.Errorf("download error: %w", err) + } + + digest, err := c.artifact.Digest(ctx) + if err != nil { + return xerrors.Errorf("digest error: %w", err) + } + log.Logger.Debugf("Digest of the built-in policies: %s", digest) + + // Update metadata.json with the new digest and the current date + if err = c.updateMetadata(digest, c.clock.Now()); err != nil { + return xerrors.Errorf("unable to update the policy metadata: %w", err) + } + + return nil +} + +// LoadBuiltinPolicies loads default policies +func (c *Client) LoadBuiltinPolicies() ([]string, error) { + f, err := os.Open(c.manifestPath()) + if err != nil { + return nil, xerrors.Errorf("manifest file open error (%s): %w", c.manifestPath(), err) + } + defer f.Close() + + var manifest bundle.Manifest + if err = json.NewDecoder(f).Decode(&manifest); err != nil { + return nil, xerrors.Errorf("json decode error (%s): %w", c.manifestPath(), err) + } + + // If the "roots" field is not included in the manifest it defaults to [""] + // which means that ALL data and policy must come from the bundle. + if manifest.Roots == nil || len(*manifest.Roots) == 0 { + return []string{c.contentDir()}, nil + } + + var policyPaths []string + for _, root := range *manifest.Roots { + policyPaths = append(policyPaths, filepath.Join(c.contentDir(), root)) + } + + return policyPaths, nil +} + +// NeedsUpdate returns if the default policy should be updated +func (c *Client) NeedsUpdate(ctx context.Context, registryOpts types.RegistryOptions) (bool, error) { + meta, err := c.GetMetadata() + if err != nil { + return true, nil + } + + // No need to update if it's been within a day since the last update. + if c.clock.Now().Before(meta.DownloadedAt.Add(updateInterval)) { + return false, nil + } + + if err = c.populateOCIArtifact(registryOpts); err != nil { + return false, xerrors.Errorf("OPA bundle error: %w", err) + } + + digest, err := c.artifact.Digest(ctx) + if err != nil { + return false, xerrors.Errorf("digest error: %w", err) + } + + if meta.Digest != digest { + return true, nil + } + + // Update DownloadedAt with the current time. + // Otherwise, if there are no updates in the remote registry, + // the digest will be fetched every time even after this. + if err = c.updateMetadata(meta.Digest, time.Now()); err != nil { + return false, xerrors.Errorf("unable to update the policy metadata: %w", err) + } + + return false, nil +} + +func (c *Client) contentDir() string { + return filepath.Join(c.policyDir, "content") +} + +func (c *Client) metadataPath() string { + return filepath.Join(c.policyDir, "metadata.json") +} + +func (c *Client) manifestPath() string { + return filepath.Join(c.contentDir(), bundle.ManifestExt) +} + +func (c *Client) updateMetadata(digest string, now time.Time) error { + f, err := os.Create(c.metadataPath()) + if err != nil { + return xerrors.Errorf("failed to open a policy manifest: %w", err) + } + defer f.Close() + + meta := Metadata{ + Digest: digest, + DownloadedAt: now, + } + + if err = json.NewEncoder(f).Encode(meta); err != nil { + return xerrors.Errorf("json encode error: %w", err) + } + + return nil +} + +func (c *Client) GetMetadata() (*Metadata, error) { + f, err := os.Open(c.metadataPath()) + if err != nil { + log.Logger.Debugf("Failed to open the policy metadata: %s", err) + return nil, err + } + defer f.Close() + + var meta Metadata + if err = json.NewDecoder(f).Decode(&meta); err != nil { + log.Logger.Warnf("Policy metadata decode error: %s", err) + return nil, err + } + + return &meta, nil +} + +func (c *Client) Clear() error { + log.Logger.Info("Removing policy bundle...") + if err := os.RemoveAll(c.policyDir); err != nil { + return xerrors.Errorf("failed to remove policy bundle: %w", err) + } + return nil +} diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go new file mode 100644 index 000000000000..0eb3190bf31d --- /dev/null +++ b/pkg/policy/policy_test.go @@ -0,0 +1,400 @@ +package policy_test + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "testing" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" + fakei "github.com/google/go-containerregistry/pkg/v1/fake" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/utils/clock" + fake "k8s.io/utils/clock/testing" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/oci" + "github.com/aquasecurity/trivy/pkg/policy" +) + +type fakeLayer struct { + v1.Layer +} + +func (f fakeLayer) MediaType() (types.MediaType, error) { + return "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", nil +} + +func newFakeLayer(t *testing.T) v1.Layer { + layer, err := tarball.LayerFromFile("testdata/bundle.tar.gz") + require.NoError(t, err) + require.NotNil(t, layer) + + return fakeLayer{layer} +} + +type brokenLayer struct { + v1.Layer +} + +func (b brokenLayer) MediaType() (types.MediaType, error) { + return "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", nil +} + +func (b brokenLayer) Compressed() (io.ReadCloser, error) { + return nil, fmt.Errorf("compressed error") +} + +func newBrokenLayer(t *testing.T) v1.Layer { + layer, err := tarball.LayerFromFile("testdata/bundle.tar.gz") + require.NoError(t, err) + + return brokenLayer{layer} +} + +func TestClient_LoadBuiltinPolicies(t *testing.T) { + tests := []struct { + name string + cacheDir string + want []string + wantErr string + }{ + { + name: "happy path", + cacheDir: "testdata/happy", + want: []string{ + filepath.Join("testdata/happy/policy/content/kubernetes"), + filepath.Join("testdata/happy/policy/content/docker"), + }, + }, + { + name: "empty roots", + cacheDir: "testdata/empty", + want: []string{ + filepath.Join("testdata/empty/policy/content"), + }, + }, + { + name: "broken manifest", + cacheDir: "testdata/broken", + want: []string{}, + wantErr: "json decode error", + }, + { + name: "no such file", + cacheDir: "testdata/unknown", + want: []string{}, + wantErr: "manifest file open error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Mock image + img := new(fakei.FakeImage) + img.LayersReturns([]v1.Layer{newFakeLayer(t)}, nil) + img.ManifestReturns(&v1.Manifest{ + Layers: []v1.Descriptor{ + { + MediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", + Size: 100, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "cba33656188782852f58993f45b68bfb8577f64cdcf02a604e3fc2afbeb5f2d8", + }, + Annotations: map[string]string{ + "org.opencontainers.image.title": "bundle.tar.gz", + }, + }, + }, + }, nil) + + // Mock OCI artifact + art, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img)) + require.NoError(t, err) + + c, err := policy.NewClient(tt.cacheDir, true, "", policy.WithOCIArtifact(art)) + require.NoError(t, err) + + got, err := c.LoadBuiltinPolicies() + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_NeedsUpdate(t *testing.T) { + type digestReturns struct { + h v1.Hash + err error + } + tests := []struct { + name string + clock clock.Clock + digestReturns digestReturns + metadata interface{} + want bool + wantErr bool + }{ + { + name: "recent download", + clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), + digestReturns: digestReturns{ + h: v1.Hash{ + Algorithm: "sha256", + Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + }, + }, + metadata: policy.Metadata{ + Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`, + DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + want: false, + }, + { + name: "same digest", + clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)), + digestReturns: digestReturns{ + h: v1.Hash{ + Algorithm: "sha256", + Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + }, + }, + metadata: policy.Metadata{ + Digest: `sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d`, + DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + want: false, + }, + { + name: "different digest", + clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)), + digestReturns: digestReturns{ + h: v1.Hash{ + Algorithm: "sha256", + Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + }, + }, + metadata: policy.Metadata{ + Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`, + DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + want: true, + }, + { + name: "sad: Digest returns an error", + clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)), + digestReturns: digestReturns{ + err: fmt.Errorf("error"), + }, + metadata: policy.Metadata{ + Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`, + DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + want: false, + wantErr: true, + }, + { + name: "sad: non-existent metadata", + clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), + want: true, + }, + { + name: "sad: broken metadata", + clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), + metadata: `"foo"`, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up a temporary directory + tmpDir := t.TempDir() + + // Mock image + img := new(fakei.FakeImage) + img.LayersReturns([]v1.Layer{newFakeLayer(t)}, nil) + img.DigestReturns(tt.digestReturns.h, tt.digestReturns.err) + img.ManifestReturns(&v1.Manifest{ + Layers: []v1.Descriptor{ + { + MediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", + Size: 100, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "cba33656188782852f58993f45b68bfb8577f64cdcf02a604e3fc2afbeb5f2d8", + }, + Annotations: map[string]string{ + "org.opencontainers.image.title": "bundle.tar.gz", + }, + }, + }, + }, nil) + + // Create a policy directory + err := os.MkdirAll(filepath.Join(tmpDir, "policy"), os.ModePerm) + require.NoError(t, err) + + if tt.metadata != nil { + b, err := json.Marshal(tt.metadata) + require.NoError(t, err) + + // Write a metadata file + metadataPath := filepath.Join(tmpDir, "policy", "metadata.json") + err = os.WriteFile(metadataPath, b, os.ModePerm) + require.NoError(t, err) + } + + art, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img)) + require.NoError(t, err) + + c, err := policy.NewClient(tmpDir, true, "", policy.WithOCIArtifact(art), policy.WithClock(tt.clock)) + require.NoError(t, err) + + // Assert results + got, err := c.NeedsUpdate(context.Background(), ftypes.RegistryOptions{}) + assert.Equal(t, tt.wantErr, err != nil) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_DownloadBuiltinPolicies(t *testing.T) { + type digestReturns struct { + h v1.Hash + err error + } + type layersReturns struct { + layers []v1.Layer + err error + } + tests := []struct { + name string + clock clock.Clock + layersReturns layersReturns + digestReturns digestReturns + want *policy.Metadata + wantErr string + }{ + { + name: "happy path", + clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), + layersReturns: layersReturns{ + layers: []v1.Layer{newFakeLayer(t)}, + }, + digestReturns: digestReturns{ + h: v1.Hash{ + Algorithm: "sha256", + Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + }, + }, + want: &policy.Metadata{ + Digest: "sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + DownloadedAt: time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC), + }, + }, + { + name: "sad: broken layer", + clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), + layersReturns: layersReturns{ + layers: []v1.Layer{newBrokenLayer(t)}, + }, + digestReturns: digestReturns{ + h: v1.Hash{ + Algorithm: "sha256", + Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + }, + }, + wantErr: "compressed error", + }, + { + name: "sad: Digest returns an error", + clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), + layersReturns: layersReturns{ + layers: []v1.Layer{newFakeLayer(t)}, + }, + digestReturns: digestReturns{ + err: fmt.Errorf("error"), + }, + want: &policy.Metadata{ + Digest: "sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + DownloadedAt: time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC), + }, + wantErr: "digest error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + + // Mock image + img := new(fakei.FakeImage) + img.DigestReturns(tt.digestReturns.h, tt.digestReturns.err) + img.LayersReturns(tt.layersReturns.layers, tt.layersReturns.err) + img.ManifestReturns(&v1.Manifest{ + Layers: []v1.Descriptor{ + { + MediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", + Size: 100, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "cba33656188782852f58993f45b68bfb8577f64cdcf02a604e3fc2afbeb5f2d8", + }, + Annotations: map[string]string{ + "org.opencontainers.image.title": "bundle.tar.gz", + }, + }, + }, + }, nil) + + // Mock OCI artifact + art, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img)) + require.NoError(t, err) + + c, err := policy.NewClient(tempDir, true, "", policy.WithClock(tt.clock), policy.WithOCIArtifact(art)) + require.NoError(t, err) + + err = c.DownloadBuiltinPolicies(context.Background(), ftypes.RegistryOptions{}) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + assert.NoError(t, err) + + // Assert metadata.json + metadata := filepath.Join(tempDir, "policy", "metadata.json") + b, err := os.ReadFile(metadata) + require.NoError(t, err) + + got := new(policy.Metadata) + err = json.Unmarshal(b, got) + require.NoError(t, err) + + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_Clear(t *testing.T) { + cacheDir := t.TempDir() + err := os.MkdirAll(filepath.Join(cacheDir, "policy"), 0755) + require.NoError(t, err) + + c, err := policy.NewClient(cacheDir, true, "") + require.NoError(t, err) + require.NoError(t, c.Clear()) +} diff --git a/pkg/policy/testdata/broken/policy/content/.manifest b/pkg/policy/testdata/broken/policy/content/.manifest new file mode 100644 index 000000000000..c7759bfead42 --- /dev/null +++ b/pkg/policy/testdata/broken/policy/content/.manifest @@ -0,0 +1,3 @@ +{ + "revision": "1", + } diff --git a/pkg/policy/testdata/bundle.tar.gz b/pkg/policy/testdata/bundle.tar.gz new file mode 100644 index 000000000000..83fd553a7aa2 Binary files /dev/null and b/pkg/policy/testdata/bundle.tar.gz differ diff --git a/pkg/policy/testdata/empty/policy/content/.manifest b/pkg/policy/testdata/empty/policy/content/.manifest new file mode 100644 index 000000000000..68cfbeb68d24 --- /dev/null +++ b/pkg/policy/testdata/empty/policy/content/.manifest @@ -0,0 +1,3 @@ +{ + "revision": "1" +} \ No newline at end of file diff --git a/pkg/policy/testdata/happy/policy/content/.manifest b/pkg/policy/testdata/happy/policy/content/.manifest new file mode 100644 index 000000000000..a5a92a163963 --- /dev/null +++ b/pkg/policy/testdata/happy/policy/content/.manifest @@ -0,0 +1,4 @@ +{ + "revision": "1", + "roots": ["kubernetes", "docker"] + } \ No newline at end of file diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go new file mode 100644 index 000000000000..59a4b99df30c --- /dev/null +++ b/pkg/purl/purl.go @@ -0,0 +1,496 @@ +package purl + +import ( + "fmt" + "strconv" + "strings" + + cn "github.com/google/go-containerregistry/pkg/name" + version "github.com/knqyf263/go-rpm-version" + packageurl "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + TypeOCI = "oci" + + // TypeK8s is a custom type for Kubernetes components in PURL. + // - namespace: The service provider such as EKS or GKE. It is not case sensitive and must be lowercased. + // Known namespaces: + // - empty (upstream) + // - eks (AWS) + // - aks (GCP) + // - gke (Azure) + // - rke (Rancher) + // - name: The k8s component name and is case sensitive. + // - version: The combined version and release of a component. + // + // Examples: + // - pkg:k8s/upstream/k8s.io%2Fapiserver@1.24.1 + // - pkg:k8s/eks/k8s.io%2Fkube-proxy@1.26.2-eksbuild.1 + TypeK8s = "k8s" + + NamespaceEKS = "eks" + NamespaceAKS = "aks" + NamespaceGKE = "gke" + NamespaceOCP = "ocp" + + TypeUnknown = "unknown" +) + +type PackageURL packageurl.PackageURL + +func FromString(s string) (*PackageURL, error) { + p, err := packageurl.FromString(s) + if err != nil { + return nil, xerrors.Errorf("failed to parse purl(%s): %w", s, err) + } + + if len(p.Qualifiers) == 0 { + p.Qualifiers = nil + } + + return lo.ToPtr(PackageURL(p)), nil +} + +// nolint: gocyclo +func New(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) (*PackageURL, error) { + qualifiers := parseQualifier(pkg) + pkg.Epoch = 0 // we moved Epoch to qualifiers so we don't need it in version + + ptype := purlType(t) + name := pkg.Name + ver := utils.FormatVersion(pkg) + namespace := "" + subpath := "" + + switch ptype { + case packageurl.TypeRPM: + ns, qs := parseRPM(metadata.OS, pkg.Modularitylabel) + namespace = string(ns) + qualifiers = append(qualifiers, qs...) + case packageurl.TypeDebian: + qualifiers = append(qualifiers, parseDeb(metadata.OS)...) + if metadata.OS != nil { + namespace = string(metadata.OS.Family) + } + case packageurl.TypeApk: + var qs packageurl.Qualifiers + name, namespace, qs = parseApk(name, metadata.OS) + qualifiers = append(qualifiers, qs...) + case packageurl.TypeMaven, string(ftypes.Gradle): // TODO: replace with packageurl.TypeGradle once they add it. + namespace, name = parseMaven(name) + case packageurl.TypePyPi: + name = parsePyPI(name) + case packageurl.TypeComposer: + namespace, name = parseComposer(name) + case packageurl.TypeGolang: + namespace, name = parseGolang(name) + if name == "" { + return nil, nil + } + case packageurl.TypeNPM: + namespace, name = parseNpm(name) + case packageurl.TypeSwift: + namespace, name = parseSwift(name) + case packageurl.TypeCocoapods: + name, subpath = parseCocoapods(name) + case packageurl.TypeOCI: + purl, err := parseOCI(metadata) + if err != nil { + return nil, err + } else if purl == nil { + return nil, nil + } + return (*PackageURL)(purl), nil + } + + return (*PackageURL)(packageurl.NewPackageURL(ptype, namespace, name, ver, qualifiers, subpath)), nil +} + +func (p *PackageURL) Unwrap() *packageurl.PackageURL { + if p == nil { + return nil + } + purl := (*packageurl.PackageURL)(p) + if len(purl.Qualifiers) == 0 { + purl.Qualifiers = nil + } + return purl +} + +// LangType returns an application type in Trivy +// nolint: gocyclo +func (p *PackageURL) LangType() ftypes.LangType { + switch p.Type { + case packageurl.TypeComposer: + return ftypes.Composer + case packageurl.TypeMaven: + return ftypes.Jar + case packageurl.TypeGem: + return ftypes.GemSpec + case packageurl.TypeConda: + return ftypes.CondaPkg + case packageurl.TypePyPi: + return ftypes.PythonPkg + case packageurl.TypeGolang: + return ftypes.GoBinary + case packageurl.TypeNPM: + return ftypes.NodePkg + case packageurl.TypeCargo: + return ftypes.Cargo + case packageurl.TypeNuget: + return ftypes.NuGet + case packageurl.TypeSwift: + return ftypes.Swift + case packageurl.TypeCocoapods: + return ftypes.Cocoapods + case packageurl.TypeHex: + return ftypes.Hex + case packageurl.TypeConan: + return ftypes.Conan + case packageurl.TypePub: + return ftypes.Pub + case packageurl.TypeBitnami: + return ftypes.Bitnami + case TypeK8s: + switch p.Namespace { + case NamespaceEKS: + return ftypes.EKS + case NamespaceGKE: + return ftypes.GKE + case NamespaceAKS: + return ftypes.AKS + case NamespaceOCP: + return ftypes.OCP + case "": + return ftypes.K8sUpstream + } + return TypeUnknown + default: + return TypeUnknown + } +} + +func (p *PackageURL) Class() types.ResultClass { + switch p.Type { + case packageurl.TypeApk, packageurl.TypeDebian, packageurl.TypeRPM: + // OS packages + return types.ClassOSPkg + default: + if p.LangType() == TypeUnknown { + return types.ClassUnknown + } + // Language-specific packages + return types.ClassLangPkg + } +} + +func (p *PackageURL) Package() *ftypes.Package { + pkgName := p.Name + if p.Namespace != "" && p.Class() != types.ClassOSPkg { + if p.Type == packageurl.TypeMaven || p.Type == packageurl.TypeGradle { + // Maven and Gradle packages separate ":" + // e.g. org.springframework:spring-core + pkgName = p.Namespace + ":" + p.Name + } else { + pkgName = p.Namespace + "/" + p.Name + } + } + + // CocoaPods purl has no namespace, but has subpath + // https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#cocoapods + if p.Subpath != "" && p.Type == packageurl.TypeCocoapods { + // CocoaPods uses / format for package name + // e.g. `pkg:cocoapods/GoogleUtilities@7.5.2#NSData+zlib` => `GoogleUtilities/NSData+zlib` + pkgName = p.Name + "/" + p.Subpath + } + + pkg := &ftypes.Package{ + ID: dependency.ID(p.LangType(), pkgName, p.Version), + Name: pkgName, + Version: p.Version, + Identifier: ftypes.PkgIdentifier{ + PURL: p.Unwrap(), + }, + } + for _, q := range p.Qualifiers { + switch q.Key { + case "arch": + pkg.Arch = q.Value + case "modularitylabel": + pkg.Modularitylabel = q.Value + case "epoch": + epoch, err := strconv.Atoi(q.Value) + if err == nil { + pkg.Epoch = epoch + } + } + } + + if p.Type == packageurl.TypeRPM { + rpmVer := version.NewVersion(p.Version) + pkg.Release = rpmVer.Release() + pkg.Version = rpmVer.Version() + } + + return pkg +} + +// Match returns true if the given PURL "target" satisfies the constraint PURL "p". +// - If the constraint does not have a version, it will match any version in the target. +// - If the constraint has qualifiers, the target must have the same set of qualifiers to match. +func (p *PackageURL) Match(target *packageurl.PackageURL) bool { + if target == nil { + return false + } + switch { + case p.Type != target.Type: + return false + case p.Namespace != target.Namespace: + return false + case p.Name != target.Name: + return false + case p.Version != "" && p.Version != target.Version: + return false + case p.Subpath != "" && p.Subpath != target.Subpath: + return false + } + + // All qualifiers in the constraint must be in the target to match + q := target.Qualifiers.Map() + for k, v1 := range p.Qualifiers.Map() { + if v2, ok := q[k]; !ok || v1 != v2 { + return false + } + } + return true +} + +func (p *PackageURL) String() string { + return p.Unwrap().String() +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#oci +func parseOCI(metadata types.Metadata) (*packageurl.PackageURL, error) { + if len(metadata.RepoDigests) == 0 { + return nil, nil + } + + digest, err := cn.NewDigest(metadata.RepoDigests[0]) + if err != nil { + return nil, xerrors.Errorf("failed to parse digest: %w", err) + } + + name := strings.ToLower(digest.RepositoryStr()) + index := strings.LastIndex(name, "/") + if index != -1 { + name = name[index+1:] + } + + var qualifiers packageurl.Qualifiers + if repoURL := digest.Repository.Name(); repoURL != "" { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "repository_url", + Value: repoURL, + }) + } + if arch := metadata.ImageConfig.Architecture; arch != "" { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "arch", + Value: metadata.ImageConfig.Architecture, + }) + } + + return packageurl.NewPackageURL(packageurl.TypeOCI, "", name, digest.DigestStr(), qualifiers, ""), nil +} + +// ref. https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#apk +func parseApk(pkgName string, fos *ftypes.OS) (string, string, packageurl.Qualifiers) { + // the name must be lowercase + pkgName = strings.ToLower(pkgName) + + if fos == nil { + return pkgName, "", nil + } + + // the namespace must be lowercase + ns := strings.ToLower(string(fos.Family)) + qs := packageurl.Qualifiers{ + { + Key: "distro", + Value: fos.Name, + }, + } + + return pkgName, ns, qs +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#deb +func parseDeb(fos *ftypes.OS) packageurl.Qualifiers { + + if fos == nil { + return packageurl.Qualifiers{} + } + + distro := fmt.Sprintf("%s-%s", fos.Family, fos.Name) + return packageurl.Qualifiers{ + { + Key: "distro", + Value: distro, + }, + } +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#rpm +func parseRPM(fos *ftypes.OS, modularityLabel string) (ftypes.OSType, packageurl.Qualifiers) { + if fos == nil { + return "", packageurl.Qualifiers{} + } + + // SLES string has whitespace + family := fos.Family + if fos.Family == ftypes.SLES { + family = "sles" + } + + qualifiers := packageurl.Qualifiers{ + { + Key: "distro", + Value: fmt.Sprintf("%s-%s", family, fos.Name), + }, + } + + if modularityLabel != "" { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "modularitylabel", + Value: modularityLabel, + }) + } + return family, qualifiers +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#maven +func parseMaven(pkgName string) (string, string) { + // The group id is the "namespace" and the artifact id is the "name". + name := strings.ReplaceAll(pkgName, ":", "/") + return parsePkgName(name) +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#golang +func parseGolang(pkgName string) (string, string) { + // The PURL will be skipped when the package name is a local path, since it can't identify a software package. + if strings.HasPrefix(pkgName, "./") || strings.HasPrefix(pkgName, "../") { + return "", "" + } + name := strings.ToLower(pkgName) + return parsePkgName(name) +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#pypi +func parsePyPI(pkgName string) string { + // PyPi treats - and _ as the same character and is not case-sensitive. + // Therefore a Pypi package name must be lowercased and underscore "_" replaced with a dash "-". + return strings.ToLower(strings.ReplaceAll(pkgName, "_", "-")) +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#composer +func parseComposer(pkgName string) (string, string) { + return parsePkgName(pkgName) +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#swift +func parseSwift(pkgName string) (string, string) { + return parsePkgName(pkgName) +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#cocoapods +func parseCocoapods(pkgName string) (string, string) { + var subpath string + pkgName, subpath, _ = strings.Cut(pkgName, "/") + return pkgName, subpath +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#npm +func parseNpm(pkgName string) (string, string) { + // the name must be lowercased + name := strings.ToLower(pkgName) + return parsePkgName(name) +} + +func purlType(t ftypes.TargetType) string { + switch t { + case ftypes.Jar, ftypes.Pom, ftypes.Gradle: + return packageurl.TypeMaven + case ftypes.Bundler, ftypes.GemSpec: + return packageurl.TypeGem + case ftypes.NuGet, ftypes.DotNetCore, ftypes.PackagesProps: + return packageurl.TypeNuget + case ftypes.CondaPkg: + return packageurl.TypeConda + case ftypes.PythonPkg, ftypes.Pip, ftypes.Pipenv, ftypes.Poetry: + return packageurl.TypePyPi + case ftypes.GoBinary, ftypes.GoModule: + return packageurl.TypeGolang + case ftypes.Npm, ftypes.NodePkg, ftypes.Yarn, ftypes.Pnpm: + return packageurl.TypeNPM + case ftypes.Cocoapods: + return packageurl.TypeCocoapods + case ftypes.Swift: + return packageurl.TypeSwift + case ftypes.Hex: + return packageurl.TypeHex + case ftypes.Conan: + return packageurl.TypeConan + case ftypes.Pub: + return packageurl.TypePub + case ftypes.RustBinary, ftypes.Cargo: + return packageurl.TypeCargo + case ftypes.Alpine: + return packageurl.TypeApk + case ftypes.Debian, ftypes.Ubuntu: + return packageurl.TypeDebian + case ftypes.RedHat, ftypes.CentOS, ftypes.Rocky, ftypes.Alma, + ftypes.Amazon, ftypes.Fedora, ftypes.Oracle, ftypes.OpenSUSE, + ftypes.OpenSUSELeap, ftypes.OpenSUSETumbleweed, ftypes.SLES, ftypes.Photon: + return packageurl.TypeRPM + case TypeOCI: + return packageurl.TypeOCI + } + return string(t) +} + +func parseQualifier(pkg ftypes.Package) packageurl.Qualifiers { + var qualifiers packageurl.Qualifiers + if pkg.Arch != "" { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "arch", + Value: pkg.Arch, + }) + } + if pkg.Epoch != 0 { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "epoch", + Value: strconv.Itoa(pkg.Epoch), + }) + } + + return qualifiers +} + +func parsePkgName(name string) (string, string) { + var namespace string + index := strings.LastIndex(name, "/") + if index != -1 { + namespace = name[:index] + name = name[index+1:] + } + return namespace, name + +} diff --git a/pkg/purl/purl_test.go b/pkg/purl/purl_test.go new file mode 100644 index 000000000000..79b2647cff4d --- /dev/null +++ b/pkg/purl/purl_test.go @@ -0,0 +1,823 @@ +package purl_test + +import ( + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/package-url/packageurl-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestNewPackageURL(t *testing.T) { + testCases := []struct { + name string + typ ftypes.TargetType + pkg ftypes.Package + metadata types.Metadata + want *purl.PackageURL + wantErr string + }{ + { + name: "maven package", + typ: ftypes.Jar, + pkg: ftypes.Package{ + Name: "org.springframework:spring-core", + Version: "5.3.14", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-core", + Version: "5.3.14", + }, + }, + { + name: "gradle package", + typ: ftypes.Gradle, + pkg: ftypes.Package{ + Name: "org.springframework:spring-core", + Version: "5.3.14", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-core", + Version: "5.3.14", + }, + }, + { + name: "yarn package", + typ: ftypes.Yarn, + pkg: ftypes.Package{ + Name: "@xtuc/ieee754", + Version: "1.2.0", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeNPM, + Namespace: "@xtuc", + Name: "ieee754", + Version: "1.2.0", + }, + }, + { + name: "yarn package with non-namespace", + typ: ftypes.Yarn, + pkg: ftypes.Package{ + Name: "lodash", + Version: "4.17.21", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "lodash", + Version: "4.17.21", + }, + }, + { + name: "pnpm package", + typ: ftypes.Pnpm, + pkg: ftypes.Package{ + Name: "@xtuc/ieee754", + Version: "1.2.0", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeNPM, + Namespace: "@xtuc", + Name: "ieee754", + Version: "1.2.0", + }, + }, + { + name: "pnpm package with non-namespace", + typ: ftypes.Pnpm, + pkg: ftypes.Package{ + Name: "lodash", + Version: "4.17.21", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "lodash", + Version: "4.17.21", + }, + }, + { + name: "pypi package", + typ: ftypes.PythonPkg, + pkg: ftypes.Package{ + Name: "Django_test", + Version: "1.2.0", + }, + want: &purl.PackageURL{ + Type: packageurl.TypePyPi, + Name: "django-test", + Version: "1.2.0", + }, + }, + { + name: "conda package", + typ: ftypes.CondaPkg, + pkg: ftypes.Package{ + Name: "absl-py", + Version: "0.4.1", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeConda, + Name: "absl-py", + Version: "0.4.1", + }, + }, + { + name: "composer package", + typ: ftypes.Composer, + pkg: ftypes.Package{ + Name: "symfony/contracts", + Version: "v1.0.2", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "symfony", + Name: "contracts", + Version: "v1.0.2", + }, + }, + { + name: "golang package", + typ: ftypes.GoModule, + pkg: ftypes.Package{ + Name: "github.com/go-sql-driver/Mysql", + Version: "v1.5.0", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/go-sql-driver", + Name: "mysql", + Version: "v1.5.0", + }, + }, + { + name: "golang package with a local path", + typ: ftypes.GoModule, + pkg: ftypes.Package{ + Name: "./private_repos/cnrm.googlesource.com/cnrm/", + Version: "(devel)", + }, + want: nil, + }, + { + name: "hex package", + typ: ftypes.Hex, + pkg: ftypes.Package{ + ID: "bunt@0.2.0", + Name: "bunt", + Version: "0.2.0", + Locations: []ftypes.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, + }, + want: &purl.PackageURL{ + Type: packageurl.TypeHex, + Name: "bunt", + Version: "0.2.0", + }, + }, + { + name: "dart package", + typ: ftypes.Pub, + pkg: ftypes.Package{ + Name: "http", + Version: "0.13.2", + }, + want: &purl.PackageURL{ + Type: packageurl.TypePub, + Name: "http", + Version: "0.13.2", + }, + }, + { + name: "swift package", + typ: ftypes.Swift, + pkg: ftypes.Package{ + ID: "github.com/apple/swift-atomics@1.1.0", + Name: "github.com/apple/swift-atomics", + Version: "1.1.0", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeSwift, + Namespace: "github.com/apple", + Name: "swift-atomics", + Version: "1.1.0", + }, + }, + { + name: "cocoapods package", + typ: ftypes.Cocoapods, + pkg: ftypes.Package{ + ID: "GoogleUtilities/NSData+zlib@7.5.2", + Name: "GoogleUtilities/NSData+zlib", + Version: "7.5.2", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeCocoapods, + Name: "GoogleUtilities", + Version: "7.5.2", + Subpath: "NSData+zlib", + }, + }, + { + name: "rust binary", + typ: ftypes.RustBinary, + pkg: ftypes.Package{ + ID: "abomination@0.7.3", + Name: "abomination", + Version: "0.7.3", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeCargo, + Name: "abomination", + Version: "0.7.3", + }, + }, + { + name: "dotnet Packages.props", + typ: ftypes.PackagesProps, + pkg: ftypes.Package{ + ID: "Newtonsoft.Json@9.0.1", + Name: "Newtonsoft.Json", + Version: "9.0.1", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeNuget, + Name: "Newtonsoft.Json", + Version: "9.0.1", + }, + }, + { + name: "os package", + typ: ftypes.RedHat, + pkg: ftypes.Package{ + Name: "acl", + Version: "2.2.53", + Release: "1.el8", + Epoch: 1, + Arch: "aarch64", + SrcName: "acl", + SrcVersion: "2.2.53", + SrcRelease: "1.el8", + SrcEpoch: 1, + Modularitylabel: "", + }, + + metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: ftypes.RedHat, + Name: "8", + }, + }, + want: &purl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "acl", + Version: "2.2.53-1.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "aarch64", + }, + { + Key: "epoch", + Value: "1", + }, + { + Key: "distro", + Value: "redhat-8", + }, + }, + }, + }, + { + name: "container", + typ: purl.TypeOCI, + metadata: types.Metadata{ + RepoTags: []string{ + "cblmariner2preview.azurecr.io/base/core:2.0.20220124-amd64", + }, + RepoDigests: []string{ + "cblmariner2preview.azurecr.io/base/core@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + "cblmariner2preview.azurecr.io/base/core@sha256:016bb1f5735e43b2738cd3fd1979b62608fe1727132b2506c17ba0e1f6a6ed8a", + }, + ImageConfig: v1.ConfigFile{ + Architecture: "amd64", + }, + }, + want: &purl.PackageURL{ + Type: packageurl.TypeOCI, + Namespace: "", + Name: "core", + Version: "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + Qualifiers: packageurl.Qualifiers{ + { + Key: "repository_url", + Value: "cblmariner2preview.azurecr.io/base/core", + }, + { + Key: "arch", + Value: "amd64", + }, + }, + }, + }, + { + name: "container local", + typ: purl.TypeOCI, + metadata: types.Metadata{ + RepoTags: []string{}, + RepoDigests: []string{}, + ImageConfig: v1.ConfigFile{ + Architecture: "amd64", + }, + ImageID: "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + }, + want: nil, + }, + { + name: "container with implicit registry", + typ: purl.TypeOCI, + metadata: types.Metadata{ + RepoTags: []string{ + "alpine:3.14", + "alpine:latest", + }, + RepoDigests: []string{ + "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + "alpine:latest@sha256:016bb1f5735e43b2738cd3fd1979b62608fe1727132b2506c17ba0e1f6a6ed8a", + }, + ImageConfig: v1.ConfigFile{ + Architecture: "amd64", + }, + }, + want: &purl.PackageURL{ + Type: packageurl.TypeOCI, + Namespace: "", + Name: "alpine", + Version: "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + Qualifiers: packageurl.Qualifiers{ + { + Key: "repository_url", + Value: "index.docker.io/library/alpine", + }, + { + Key: "arch", + Value: "amd64", + }, + }, + }, + }, + { + name: "sad path", + typ: purl.TypeOCI, + metadata: types.Metadata{ + RepoTags: []string{ + "cblmariner2preview.azurecr.io/base/core:2.0.20220124-amd64", + }, + RepoDigests: []string{ + "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + }, + }, + wantErr: "failed to parse digest", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + packageURL, err := purl.New(tc.typ, tc.metadata, tc.pkg) + if tc.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.want, packageURL, tc.name) + }) + } +} + +func TestFromString(t *testing.T) { + testCases := []struct { + name string + purl string + want purl.PackageURL + wantErr string + }{ + { + name: "happy path for maven", + purl: "pkg:maven/org.springframework/spring-core@5.0.4.RELEASE", + want: purl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Version: "5.0.4.RELEASE", + Name: "spring-core", + }, + }, + { + name: "happy path for npm", + purl: "pkg:npm/bootstrap@5.0.2", + want: purl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "bootstrap", + Version: "5.0.2", + }, + }, + { + name: "happy path for coocapods", + purl: "pkg:cocoapods/GoogleUtilities@7.5.2#NSData+zlib", + want: purl.PackageURL{ + Type: packageurl.TypeCocoapods, + Name: "GoogleUtilities", + Version: "7.5.2", + Subpath: "NSData+zlib", + }, + }, + { + name: "happy path for hex", + purl: "pkg:hex/plug@1.14.0", + want: purl.PackageURL{ + Type: packageurl.TypeHex, + Name: "plug", + Version: "1.14.0", + }, + }, + { + name: "happy path for dart", + purl: "pkg:pub/http@0.13.2", + want: purl.PackageURL{ + Type: packageurl.TypePub, + Name: "http", + Version: "0.13.2", + }, + }, + { + name: "happy path for apk", + purl: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?distro=3.14.2&epoch=1", + want: purl.PackageURL{ + Type: string(analyzer.TypeApk), + Namespace: "alpine", + Name: "alpine-baselayout", + Version: "3.2.0-r16", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.14.2", + }, + { + Key: "epoch", + Value: "1", + }, + }, + }, + }, + { + name: "happy path for rpm", + purl: "pkg:rpm/redhat/containers-common@0.1.14", + want: purl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "containers-common", + Version: "0.1.14", + }, + }, + { + name: "happy path for conda", + purl: "pkg:conda/absl-py@0.4.1", + want: purl.PackageURL{ + Type: packageurl.TypeConda, + Name: "absl-py", + Version: "0.4.1", + }, + }, + { + name: "bad rpm", + purl: "pkg:rpm/redhat/a--@1.0.0", + want: purl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "a--", + Version: "1.0.0", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + pkg, err := purl.FromString(tc.purl) + if tc.wantErr != "" { + assert.ErrorContains(t, err, tc.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.want, *pkg, tc.name) + }) + } +} + +func TestPackageURL_Package(t *testing.T) { + tests := []struct { + name string + pkgURL *purl.PackageURL + wantPkg *ftypes.Package + }{ + { + name: "rpm + Qualifiers", + pkgURL: &purl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "nodejs-full-i18n", + Version: "10.21.0-3.module_el8.2.0+391+8da3adc6", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "x86_64", + }, + { + Key: "epoch", + Value: "1", + }, + { + Key: "modularitylabel", + Value: "nodejs:10:8020020200707141642:6a468ee4", + }, + { + Key: "distro", + Value: "redhat-8", + }, + }, + }, + wantPkg: &ftypes.Package{ + ID: "nodejs-full-i18n@10.21.0-3.module_el8.2.0+391+8da3adc6", + Name: "nodejs-full-i18n", + Version: "10.21.0", + Release: "3.module_el8.2.0+391+8da3adc6", + Arch: "x86_64", + Epoch: 1, + Modularitylabel: "nodejs:10:8020020200707141642:6a468ee4", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "nodejs-full-i18n", + Version: "10.21.0-3.module_el8.2.0+391+8da3adc6", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "x86_64", + }, + { + Key: "epoch", + Value: "1", + }, + { + Key: "modularitylabel", + Value: "nodejs:10:8020020200707141642:6a468ee4", + }, + { + Key: "distro", + Value: "redhat-8", + }, + }, + }, + }, + }, + }, + { + name: "composer with namespace", + pkgURL: &purl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "symfony", + Name: "contracts", + Version: "1.0.2", + }, + + wantPkg: &ftypes.Package{ + ID: "symfony/contracts@1.0.2", + Name: "symfony/contracts", + Version: "1.0.2", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "symfony", + Name: "contracts", + Version: "1.0.2", + }, + }, + }, + }, + { + name: "maven with namespace", + pkgURL: &purl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-core", + Version: "5.0.4.RELEASE", + }, + + wantPkg: &ftypes.Package{ + ID: "org.springframework:spring-core:5.0.4.RELEASE", + Name: "org.springframework:spring-core", + Version: "5.0.4.RELEASE", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-core", + Version: "5.0.4.RELEASE", + }, + }, + }, + }, + { + name: "cocoapods with subpath", + pkgURL: &purl.PackageURL{ + Type: packageurl.TypeCocoapods, + Version: "4.2.0", + Name: "AppCenter", + Subpath: "Analytics", + }, + + wantPkg: &ftypes.Package{ + ID: "AppCenter/Analytics@4.2.0", + Name: "AppCenter/Analytics", + Version: "4.2.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeCocoapods, + Version: "4.2.0", + Name: "AppCenter", + Subpath: "Analytics", + }, + }, + }, + }, + { + name: "wrong epoch", + pkgURL: &purl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "acl", + Version: "2.2.53-1.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "epoch", + Value: "wrong", + }, + }, + }, + wantPkg: &ftypes.Package{ + ID: "acl@2.2.53-1.el8", + Name: "acl", + Version: "2.2.53", + Release: "1.el8", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "acl", + Version: "2.2.53-1.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "epoch", + Value: "wrong", + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.pkgURL.Package() + assert.Equal(t, tt.wantPkg, got) + }) + } +} + +func TestPackageURL_LangType(t *testing.T) { + tests := []struct { + name string + purl packageurl.PackageURL + want ftypes.LangType + }{ + { + name: "maven", + purl: packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-core", + Version: "5.0.4.RELEASE", + }, + want: ftypes.Jar, + }, + { + name: "k8s", + purl: packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "kubelet", + Version: "1.21.1", + }, + want: ftypes.K8sUpstream, + }, + { + name: "eks", + purl: packageurl.PackageURL{ + Type: purl.TypeK8s, + Namespace: purl.NamespaceEKS, + Name: "kubelet", + Version: "1.21.1", + }, + want: ftypes.EKS, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := (purl.PackageURL)(tt.purl) + assert.Equalf(t, tt.want, p.LangType(), "LangType()") + }) + } +} + +func TestPackageURL_Match(t *testing.T) { + tests := []struct { + name string + constraint string + target string + want bool + }{ + { + name: "same purl", + constraint: "pkg:golang/github.com/aquasecurity/trivy@0.49.0", + target: "pkg:golang/github.com/aquasecurity/trivy@0.49.0", + want: true, + }, + { + name: "different type", + constraint: "pkg:golang/github.com/aquasecurity/trivy@0.49.0", + target: "pkg:maven/github.com/aquasecurity/trivy@0.49.0", + want: false, + }, + { + name: "different namespace", + constraint: "pkg:golang/github.com/aquasecurity/trivy@0.49.0", + target: "pkg:golang/github.com/aquasecurity2/trivy@0.49.0", + want: false, + }, + { + name: "different name", + constraint: "pkg:golang/github.com/aquasecurity/trivy@0.49.0", + target: "pkg:golang/github.com/aquasecurity/tracee@0.49.0", + want: false, + }, + { + name: "different version", + constraint: "pkg:golang/github.com/aquasecurity/trivy@0.49.0", + target: "pkg:golang/github.com/aquasecurity/trivy@0.49.1", + want: false, + }, + { + name: "version wildcard", + constraint: "pkg:golang/github.com/aquasecurity/trivy", + target: "pkg:golang/github.com/aquasecurity/trivy@0.50.0", + want: true, + }, + { + name: "different qualifier", + constraint: "pkg:bitnami/wordpress@6.2.0?arch=arm64&distro=debian-12", + target: "pkg:bitnami/wordpress@6.2.0?arch=arm64&distro=debian-13", + want: false, + }, + { + name: "target more qualifiers", + constraint: "pkg:bitnami/wordpress@6.2.0?arch=arm64", + target: "pkg:bitnami/wordpress@6.2.0?arch=arm64&distro=debian-13", + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, err := purl.FromString(tt.constraint) + require.NoError(t, err) + + p, err := purl.FromString(tt.target) + require.NoError(t, err) + + assert.Equalf(t, tt.want, c.Match(p.Unwrap()), "Match()") + }) + } +} diff --git a/pkg/rekor/client.go b/pkg/rekor/client.go new file mode 100644 index 000000000000..a902c3a6f865 --- /dev/null +++ b/pkg/rekor/client.go @@ -0,0 +1,134 @@ +package rekor + +import ( + "context" + "net/url" + + httptransport "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + "github.com/sigstore/rekor/pkg/generated/client" + eclient "github.com/sigstore/rekor/pkg/generated/client/entries" + "github.com/sigstore/rekor/pkg/generated/client/index" + "github.com/sigstore/rekor/pkg/generated/models" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" +) + +const ( + MaxGetEntriesLimit = 10 + + treeIDLen = 16 + uuidLen = 64 +) + +var ErrOverGetEntriesLimit = xerrors.Errorf("over get entries limit") + +// EntryID is a hex-format string. The length of the string is 80 or 64. +// If the length is 80, it consists of two elements, the TreeID and the UUID. If the length is 64, +// it consists only of the UUID. +// cf. https://github.com/sigstore/rekor/blob/4923f60f4ae55ccd4baf28d182e8f55c2d8097d3/pkg/sharding/sharding.go#L25-L36 +type EntryID struct { + TreeID string + UUID string +} + +func NewEntryID(entryID string) (EntryID, error) { + switch len(entryID) { + case treeIDLen + uuidLen: + return EntryID{TreeID: entryID[:treeIDLen], UUID: entryID[treeIDLen:]}, nil + case uuidLen: + return EntryID{TreeID: "", UUID: entryID}, nil + default: + return EntryID{}, xerrors.New("invalid Entry ID length") + } +} + +func (e EntryID) String() string { + return e.TreeID + e.UUID +} + +type Entry struct { + Statement []byte +} + +type Client struct { + *client.Rekor +} + +func NewClient(rekorURL string) (*Client, error) { + u, err := url.Parse(rekorURL) + if err != nil { + return nil, xerrors.Errorf("failed to parse url: %w", err) + } + + c := client.New( + httptransport.New(u.Host, client.DefaultBasePath, []string{u.Scheme}), + strfmt.Default, + ) + return &Client{Rekor: c}, nil +} + +func (c *Client) Search(ctx context.Context, hash string) ([]EntryID, error) { + log.Logger.Debugf("Search for %s in Rekor", hash) + params := index.NewSearchIndexParamsWithContext(ctx).WithQuery(&models.SearchIndex{Hash: hash}) + resp, err := c.Index.SearchIndex(params) + if err != nil { + return nil, xerrors.Errorf("failed to search: %w", err) + } + + ids := make([]EntryID, len(resp.Payload)) + for i, id := range resp.Payload { + ids[i], err = NewEntryID(id) + if err != nil { + return nil, xerrors.Errorf("invalid entry UUID: %w", err) + } + } + + return ids, nil +} + +func (c *Client) GetEntries(ctx context.Context, entryIDs []EntryID) ([]Entry, error) { + if len(entryIDs) > MaxGetEntriesLimit { + return []Entry{}, ErrOverGetEntriesLimit + } + + ids := make([]string, len(entryIDs)) + uuids := make([]string, len(entryIDs)) + + for i, id := range entryIDs { + ids[i] = id.String() + uuids[i] = id.UUID + } + + params := eclient.NewSearchLogQueryParamsWithContext(ctx).WithEntry(&models.SearchLogQuery{ + EntryUUIDs: ids, + }) + + resp, err := c.Entries.SearchLogQuery(params) + if err != nil { + return []Entry{}, xerrors.Errorf("failed to fetch log entries by UUIDs: %w", err) + } + + entries := make([]Entry, 0, len(resp.Payload)) + for _, payload := range resp.Payload { + for id, entry := range payload { + eid, err := NewEntryID(id) + if err != nil { + return []Entry{}, xerrors.Errorf("failed to parse response entryID: %w", err) + } + + if !slices.Contains(uuids, eid.UUID) { + continue + } + + if entry.Attestation == nil { + continue + } + entries = append(entries, Entry{Statement: entry.Attestation.Data}) + } + } + + return entries, nil +} diff --git a/pkg/rekor/client_test.go b/pkg/rekor/client_test.go new file mode 100644 index 000000000000..96a50155dbec --- /dev/null +++ b/pkg/rekor/client_test.go @@ -0,0 +1,164 @@ +package rekor_test + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/rekor" +) + +func TestClient_Search(t *testing.T) { + type args struct { + hash string + } + tests := []struct { + name string + mockResponseFile string + args args + want []rekor.EntryID + wantErr string + }{ + { + name: "happy path", + mockResponseFile: "testdata/search-response.json", + args: args{ + hash: "92251458088c638061cda8fd8b403b76d661a4dc6b7ee71b6affcf1872557b2b", + }, + want: []rekor.EntryID{ + { + TreeID: "392f8ecba72f4326", + UUID: "eb624a7403756250b5f2ad58842a99d1653cd6f147f4ce9eda2da350bd908a55", + }, + { + TreeID: "392f8ecba72f4326", + UUID: "414eaca77bd19bf5f378725d7fd79309605a81b69cc0101f5cd3119d0a216523", + }, + { + TreeID: "", + UUID: "414eaca77bd19bf5f378725d7fd79309605a81b69cc0101f5cd3119d0a012345", + }, + }, + }, + { + name: "invalid UUID", + mockResponseFile: "testdata/search-invalid-response.json", + args: args{ + hash: "92251458088c638061cda8fd8b403b76d661a4dc6b7ee71b6affcf1872557b2b", + }, + wantErr: "invalid entry UUID", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, tt.mockResponseFile) + return + })) + defer ts.Close() + + c, err := rekor.NewClient(ts.URL) + require.NoError(t, err) + + got, err := c.Search(context.Background(), tt.args.hash) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_GetEntries(t *testing.T) { + type args struct { + uuids []rekor.EntryID + } + tests := []struct { + name string + mockResponseFile string + args args + want []rekor.Entry + wantErr error + }{ + { + name: "happy path", + mockResponseFile: "testdata/log-entries.json", + args: args{ + uuids: []rekor.EntryID{ + { + TreeID: "392f8ecba72f4326", + UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b1e", + }, + { + TreeID: "392f8ecba72f4326", + UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a741a2f", + }, + }, + }, + want: []rekor.Entry{ + { + Statement: []byte(`{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"cosign.sigstore.dev/attestation/v1","subject":[{"name":"ghcr.io/aquasecurity/trivy-test-images","digest":{"sha256":"20d3f693dcffa44d6b24eae88783324d25cc132c22089f70e4fbfb858625b062"}}],"predicate":{"Data":"\"foo\\n\"\n","Timestamp":"2022-08-26T01:17:17Z"}}`), + }, + { + Statement: []byte(`{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"cosign.sigstore.dev/attestation/v1","subject":[{"name":"ghcr.io/aquasecurity/trivy-test-images","digest":{"sha256":"20d3f693dcffa44d6b24eae88783324d25cc132c22089f70e4fbfb858625b062"}}],"predicate":{"Data":"\"bar\\n\"\n","Timestamp":"2022-08-26T01:17:17Z"}}`), + }, + }, + }, + { + name: "no attestation", + mockResponseFile: "testdata/log-entries-no-attestation.json", + args: args{ + uuids: []rekor.EntryID{ + { + TreeID: "392f8ecba72f4326", + UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b1e", + }, + }, + }, + want: []rekor.Entry{}, + }, + { + name: "over get entries limit", + args: args{ + uuids: []rekor.EntryID{ + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b10"}, + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b11"}, + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b12"}, + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b13"}, + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b14"}, + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b15"}, + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b16"}, + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b17"}, + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b18"}, + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b19"}, + {TreeID: "392f8ecba72f4326", UUID: "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b1a"}, + }, + }, + want: []rekor.Entry{}, + wantErr: rekor.ErrOverGetEntriesLimit, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, tt.mockResponseFile) + return + })) + defer ts.Close() + + client, err := rekor.NewClient(ts.URL) + require.NoError(t, err) + + got, err := client.GetEntries(context.Background(), tt.args.uuids) + require.Equal(t, tt.wantErr, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/rekor/testdata/log-entries-no-attestation.json b/pkg/rekor/testdata/log-entries-no-attestation.json new file mode 100644 index 000000000000..d35a5d97eb45 --- /dev/null +++ b/pkg/rekor/testdata/log-entries-no-attestation.json @@ -0,0 +1,32 @@ +[ + { + "8b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b1e": { + "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI3ODIxNDNlMzlmMWU3YTA0ZTNmNmRhMmQ4OGIxYzA1N2U1NjU3MzYzYzRmOTA2NzlmM2U4YTA3MWI3NjE5ZTAyIn0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZWJiZmRkZGE2Mjc3YWYxOTllOTNjNWJiNWNmNTk5OGE3OTMxMWRlMjM4ZTQ5YmNjOGFjMjQxMDI2OTg3NjFiYiJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdseFowRjNTVUpCWjBsVllXaHNPRUZSZDFsWlYwNVpiblY2ZGxGdk9FVnJOMWRNVFVSdmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlFU1RKTlJFVjRUbnBGTTFkb1kwNU5ha2wzVDBSSk1rMUVSWGxPZWtVelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZMV21aRVF6bHBhbFZ5Y2xwQldFOWpXRllyUVhGSFJVbFRTbEV6VkhScVNuZEpkRUVLZFRFM1JtbDJhV3BuU2sxaFlVaEdORGNyVDNaMk9WUjFla0ZEUTNscFNVVjVVRFV5WlhJMlptRjVibVpLWVZWcU9FdFBRMEZWYTNkblowWkdUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZIUWxkVUNrTXdkVVUzZFRSUWNVUlZSakZZVjBjMFFsVldWVXBCZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZE1RbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTREJGWlhkQ05VRklZMEZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBNS01UZHRTbWhuUVVGQ1FVMUJVMFJDUjBGcFJVRm9TMDlCU2tkV1ZsaENiMWN4VERSNGFsazVlV0pXT0daVVVYTjVUU3R2VUVwSWVEazVTMjlMWVVwVlF3cEpVVVJDWkRsbGMxUTBNazFTVG5nM1ZtOUJNMXBhS3pWNGFraE5aV1I2YW1WeFEyWm9aVGN2ZDFweFlUbFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXZDa0ZFUW14QmFrVkJjbkJrZVhsRlJqYzNiMkp5VEVOTVVYcHpZbUl4TTJsc05qZDNkek00WTA1MGFtZE5RbWw2WTJWVWFrUmlZMlZMZVZGU04xUktOSE1LWkVOc2Nsa3hZMUJCYWtFNGFYQjZTVVE0VlUxQ2FHeGtTbVV2WlhKR2NHZHROMnN3TldGaWMybFBOM1Y1ZFZadVMyOVZOazByVFhKNlZWVXJaVGxHZHdwSlJHaENhblZSYTFkUll6MEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", + "integratedTime": 1661476639, + "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", + "logIndex": 3280165, + "verification": { + "inclusionProof": { + "hashes": [ + "da70e43d33aadff047c7aa4542a1c2c0f039555b4ebb75773659b246a096d983", + "8e6876b02a01bc1e0491802b967f8b490e46fc3fb5e48986c092fe648377801b", + "eaf1241ffc88cfa3bc51697a678122d9425258967d2975ddd43bd720aa693a42", + "9420c625e610223867a58f840505674b0b3d741c24c432505c6738f2ac4f688d", + "be1b7b409a68ebcdc48c8ab773e72008454203fa4412344f25f6b1a13cb49773", + "5950c17122cae78ec19ea5f531887b7b7aad3ce14beeac68b91f115b388725df", + "664dc6a32f46aaa70be3e2206606890bebc928d4e876c405eade9a244778626e", + "48d515eeab9e86cbab194944afbc744e4c589c7b6701f1d635b70d180e0cfa3d", + "296ac93f733e66cf78544a1412c7724f6fd32ad1aeeff6359c89a5047d0093bc", + "ba20fa75d6d10494608f7716384ae46d62968d7a2ac0d1d49101e3b949d38c90", + "6d494b237648126525b08f975c736a55d1f7a64472fcc2782bbc16733c608d7b", + "efb36cfc54705d8cd921a621a9389ffa03956b15d68bfabadac2b4853852079b" + ], + "logIndex": 3280165, + "rootHash": "d714a81604a4a6ea1a6a485296b112b559eea0b6b93580afcb7d382a5944f03f", + "treeSize": 3280179 + }, + "signedEntryTimestamp": "MEQCICXqUEWZzu0q2tk89u7hEBIKCxmRTQGmH+DRcwvdZoPkAiBJEbBsMdLtTWxxg8XNrJ6bRH2QskAJsKnzsgBjAsAo9A==" + } + } + } +] diff --git a/pkg/rekor/testdata/log-entries.json b/pkg/rekor/testdata/log-entries.json new file mode 100644 index 000000000000..45ec42a175ad --- /dev/null +++ b/pkg/rekor/testdata/log-entries.json @@ -0,0 +1,68 @@ +[ + { + "392f8ecba72f43268b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a742b1e": { + "attestation": { + "data": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vYXF1YXNlY3VyaXR5L3RyaXZ5LXRlc3QtaW1hZ2VzIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjIwZDNmNjkzZGNmZmE0NGQ2YjI0ZWFlODg3ODMzMjRkMjVjYzEzMmMyMjA4OWY3MGU0ZmJmYjg1ODYyNWIwNjIifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6IlwiZm9vXFxuXCJcbiIsIlRpbWVzdGFtcCI6IjIwMjItMDgtMjZUMDE6MTc6MTdaIn19" + }, + "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI3ODIxNDNlMzlmMWU3YTA0ZTNmNmRhMmQ4OGIxYzA1N2U1NjU3MzYzYzRmOTA2NzlmM2U4YTA3MWI3NjE5ZTAyIn0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZWJiZmRkZGE2Mjc3YWYxOTllOTNjNWJiNWNmNTk5OGE3OTMxMWRlMjM4ZTQ5YmNjOGFjMjQxMDI2OTg3NjFiYiJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdseFowRjNTVUpCWjBsVllXaHNPRUZSZDFsWlYwNVpiblY2ZGxGdk9FVnJOMWRNVFVSdmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlFU1RKTlJFVjRUbnBGTTFkb1kwNU5ha2wzVDBSSk1rMUVSWGxPZWtVelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZMV21aRVF6bHBhbFZ5Y2xwQldFOWpXRllyUVhGSFJVbFRTbEV6VkhScVNuZEpkRUVLZFRFM1JtbDJhV3BuU2sxaFlVaEdORGNyVDNaMk9WUjFla0ZEUTNscFNVVjVVRFV5WlhJMlptRjVibVpLWVZWcU9FdFBRMEZWYTNkblowWkdUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZIUWxkVUNrTXdkVVUzZFRSUWNVUlZSakZZVjBjMFFsVldWVXBCZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZE1RbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTREJGWlhkQ05VRklZMEZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBNS01UZHRTbWhuUVVGQ1FVMUJVMFJDUjBGcFJVRm9TMDlCU2tkV1ZsaENiMWN4VERSNGFsazVlV0pXT0daVVVYTjVUU3R2VUVwSWVEazVTMjlMWVVwVlF3cEpVVVJDWkRsbGMxUTBNazFTVG5nM1ZtOUJNMXBhS3pWNGFraE5aV1I2YW1WeFEyWm9aVGN2ZDFweFlUbFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXZDa0ZFUW14QmFrVkJjbkJrZVhsRlJqYzNiMkp5VEVOTVVYcHpZbUl4TTJsc05qZDNkek00WTA1MGFtZE5RbWw2WTJWVWFrUmlZMlZMZVZGU04xUktOSE1LWkVOc2Nsa3hZMUJCYWtFNGFYQjZTVVE0VlUxQ2FHeGtTbVV2WlhKR2NHZHROMnN3TldGaWMybFBOM1Y1ZFZadVMyOVZOazByVFhKNlZWVXJaVGxHZHdwSlJHaENhblZSYTFkUll6MEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", + "integratedTime": 1661476639, + "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", + "logIndex": 3280165, + "verification": { + "inclusionProof": { + "hashes": [ + "da70e43d33aadff047c7aa4542a1c2c0f039555b4ebb75773659b246a096d983", + "8e6876b02a01bc1e0491802b967f8b490e46fc3fb5e48986c092fe648377801b", + "eaf1241ffc88cfa3bc51697a678122d9425258967d2975ddd43bd720aa693a42", + "9420c625e610223867a58f840505674b0b3d741c24c432505c6738f2ac4f688d", + "be1b7b409a68ebcdc48c8ab773e72008454203fa4412344f25f6b1a13cb49773", + "5950c17122cae78ec19ea5f531887b7b7aad3ce14beeac68b91f115b388725df", + "664dc6a32f46aaa70be3e2206606890bebc928d4e876c405eade9a244778626e", + "48d515eeab9e86cbab194944afbc744e4c589c7b6701f1d635b70d180e0cfa3d", + "296ac93f733e66cf78544a1412c7724f6fd32ad1aeeff6359c89a5047d0093bc", + "ba20fa75d6d10494608f7716384ae46d62968d7a2ac0d1d49101e3b949d38c90", + "6d494b237648126525b08f975c736a55d1f7a64472fcc2782bbc16733c608d7b", + "efb36cfc54705d8cd921a621a9389ffa03956b15d68bfabadac2b4853852079b" + ], + "logIndex": 3280165, + "rootHash": "d714a81604a4a6ea1a6a485296b112b559eea0b6b93580afcb7d382a5944f03f", + "treeSize": 3280179 + }, + "signedEntryTimestamp": "MEQCICXqUEWZzu0q2tk89u7hEBIKCxmRTQGmH+DRcwvdZoPkAiBJEbBsMdLtTWxxg8XNrJ6bRH2QskAJsKnzsgBjAsAo9A==" + } + } + }, + { + "392f8ecba72f43268b5b2debb565fd5cb05ae0d3935351fa3faabce558bede72e197b5722a741a2f": { + "attestation": { + "data": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vYXF1YXNlY3VyaXR5L3RyaXZ5LXRlc3QtaW1hZ2VzIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjIwZDNmNjkzZGNmZmE0NGQ2YjI0ZWFlODg3ODMzMjRkMjVjYzEzMmMyMjA4OWY3MGU0ZmJmYjg1ODYyNWIwNjIifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6IlwiYmFyXFxuXCJcbiIsIlRpbWVzdGFtcCI6IjIwMjItMDgtMjZUMDE6MTc6MTdaIn19" + }, + "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI3ODIxNDNlMzlmMWU3YTA0ZTNmNmRhMmQ4OGIxYzA1N2U1NjU3MzYzYzRmOTA2NzlmM2U4YTA3MWI3NjE5ZTAyIn0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZWJiZmRkZGE2Mjc3YWYxOTllOTNjNWJiNWNmNTk5OGE3OTMxMWRlMjM4ZTQ5YmNjOGFjMjQxMDI2OTg3NjFiYiJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdseFowRjNTVUpCWjBsVllXaHNPRUZSZDFsWlYwNVpiblY2ZGxGdk9FVnJOMWRNVFVSdmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlFU1RKTlJFVjRUbnBGTTFkb1kwNU5ha2wzVDBSSk1rMUVSWGxPZWtVelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZMV21aRVF6bHBhbFZ5Y2xwQldFOWpXRllyUVhGSFJVbFRTbEV6VkhScVNuZEpkRUVLZFRFM1JtbDJhV3BuU2sxaFlVaEdORGNyVDNaMk9WUjFla0ZEUTNscFNVVjVVRFV5WlhJMlptRjVibVpLWVZWcU9FdFBRMEZWYTNkblowWkdUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZIUWxkVUNrTXdkVVUzZFRSUWNVUlZSakZZVjBjMFFsVldWVXBCZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZE1RbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTREJGWlhkQ05VRklZMEZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBNS01UZHRTbWhuUVVGQ1FVMUJVMFJDUjBGcFJVRm9TMDlCU2tkV1ZsaENiMWN4VERSNGFsazVlV0pXT0daVVVYTjVUU3R2VUVwSWVEazVTMjlMWVVwVlF3cEpVVVJDWkRsbGMxUTBNazFTVG5nM1ZtOUJNMXBhS3pWNGFraE5aV1I2YW1WeFEyWm9aVGN2ZDFweFlUbFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXZDa0ZFUW14QmFrVkJjbkJrZVhsRlJqYzNiMkp5VEVOTVVYcHpZbUl4TTJsc05qZDNkek00WTA1MGFtZE5RbWw2WTJWVWFrUmlZMlZMZVZGU04xUktOSE1LWkVOc2Nsa3hZMUJCYWtFNGFYQjZTVVE0VlUxQ2FHeGtTbVV2WlhKR2NHZHROMnN3TldGaWMybFBOM1Y1ZFZadVMyOVZOazByVFhKNlZWVXJaVGxHZHdwSlJHaENhblZSYTFkUll6MEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", + "integratedTime": 1661476639, + "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", + "logIndex": 3280165, + "verification": { + "inclusionProof": { + "hashes": [ + "da70e43d33aadff047c7aa4542a1c2c0f039555b4ebb75773659b246a096d983", + "8e6876b02a01bc1e0491802b967f8b490e46fc3fb5e48986c092fe648377801b", + "eaf1241ffc88cfa3bc51697a678122d9425258967d2975ddd43bd720aa693a42", + "9420c625e610223867a58f840505674b0b3d741c24c432505c6738f2ac4f688d", + "be1b7b409a68ebcdc48c8ab773e72008454203fa4412344f25f6b1a13cb49773", + "5950c17122cae78ec19ea5f531887b7b7aad3ce14beeac68b91f115b388725df", + "664dc6a32f46aaa70be3e2206606890bebc928d4e876c405eade9a244778626e", + "48d515eeab9e86cbab194944afbc744e4c589c7b6701f1d635b70d180e0cfa3d", + "296ac93f733e66cf78544a1412c7724f6fd32ad1aeeff6359c89a5047d0093bc", + "ba20fa75d6d10494608f7716384ae46d62968d7a2ac0d1d49101e3b949d38c90", + "6d494b237648126525b08f975c736a55d1f7a64472fcc2782bbc16733c608d7b", + "efb36cfc54705d8cd921a621a9389ffa03956b15d68bfabadac2b4853852079b" + ], + "logIndex": 3280165, + "rootHash": "d714a81604a4a6ea1a6a485296b112b559eea0b6b93580afcb7d382a5944f03f", + "treeSize": 3280179 + }, + "signedEntryTimestamp": "MEQCICXqUEWZzu0q2tk89u7hEBIKCxmRTQGmH+DRcwvdZoPkAiBJEbBsMdLtTWxxg8XNrJ6bRH2QskAJsKnzsgBjAsAo9A==" + } + } + } +] diff --git a/pkg/rekor/testdata/search-invalid-response.json b/pkg/rekor/testdata/search-invalid-response.json new file mode 100644 index 000000000000..18ae148b6f1c --- /dev/null +++ b/pkg/rekor/testdata/search-invalid-response.json @@ -0,0 +1,4 @@ +[ + "foo", + "bar" +] diff --git a/pkg/rekor/testdata/search-response.json b/pkg/rekor/testdata/search-response.json new file mode 100644 index 000000000000..3607ce13df56 --- /dev/null +++ b/pkg/rekor/testdata/search-response.json @@ -0,0 +1,5 @@ +[ + "392f8ecba72f4326eb624a7403756250b5f2ad58842a99d1653cd6f147f4ce9eda2da350bd908a55", + "392f8ecba72f4326414eaca77bd19bf5f378725d7fd79309605a81b69cc0101f5cd3119d0a216523", + "414eaca77bd19bf5f378725d7fd79309605a81b69cc0101f5cd3119d0a012345" +] diff --git a/pkg/rekortest/server.go b/pkg/rekortest/server.go new file mode 100644 index 000000000000..0e9207507492 --- /dev/null +++ b/pkg/rekortest/server.go @@ -0,0 +1,366 @@ +package rekortest + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/CycloneDX/cyclonedx-go" + "github.com/in-toto/in-toto-golang/in_toto" + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" + "github.com/samber/lo" + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/attestation" +) + +var ( + indexRes = map[string][]string{ + // Contain a SBOM attestation for a container image + "sha256:782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02": { + "392f8ecba72f4326eb624a7403756250b5f2ad58842a99d1653cd6f147f4ce9eda2da350bd908a55", + "392f8ecba72f4326414eaca77bd19bf5f378725d7fd79309605a81b69cc0101f5cd3119d0a216523", + }, + // Contain a SBOM attestation for go.mod + "sha256:23f4e10c43c7654e33a3c9570913c8c9c528292762f1a5c4a97253e9e4e4b238": { + "24296fb24b8ad77aa715cdfd264ce34c4d544375d7bd7cd029bf5a48ef25217a13fdba562e0889ca", + }, + // Contain an empty SBOM attestation + "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03": { + "24296fb24b8ad77a8d47be2e40bfe910f0ffc842e86b5685dd85d1c903ef78bb6362125816426fe9", + }, + } + + imageSBOMAttestation = in_toto.Statement{ + StatementHeader: in_toto.StatementHeader{ + Type: "https://in-toto.io/Statement/v0.1", + PredicateType: "https://cyclonedx.org/bom", + Subject: []in_toto.Subject{ + { + Name: "index.docker.io/knqyf263/cosign-test", + Digest: slsa.DigestSet{ + "sha256": "a777c9c66ba177ccfea23f2a216ff6721e78a662cd17019488c417135299cd89", + }, + }, + }, + }, + Predicate: &attestation.CosignPredicate{ + Data: &cyclonedx.BOM{ + BOMFormat: cyclonedx.BOMFormat, + SerialNumber: "urn:uuid:6453fd82-71f4-47c8-ad12-01775619c443", + SpecVersion: cyclonedx.SpecVersion1_5, + Version: 1, + Metadata: &cyclonedx.Metadata{ + Timestamp: "2022-09-15T13:53:49+00:00", + Tools: &cyclonedx.ToolsChoice{ + Components: &[]cyclonedx.Component{ + { + Type: cyclonedx.ComponentTypeApplication, + Name: "trivy", + Group: "aquasecurity", + Version: "dev", + }, + }, + }, + Component: &cyclonedx.Component{ + BOMRef: "pkg:oci/alpine@sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad?repository_url=index.docker.io%2Flibrary%2Falpine\u0026arch=amd64", + Type: cyclonedx.ComponentTypeContainer, + Name: "alpine:3.16", + PackageURL: "pkg:oci/alpine@sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad?repository_url=index.docker.io%2Flibrary%2Falpine\u0026arch=amd64", + Properties: &[]cyclonedx.Property{ + { + Name: "aquasecurity:trivy:SchemaVersion", + Value: "2", + }, + { + Name: "aquasecurity:trivy:ImageID", + Value: "sha256:9c6f0724472873bb50a2ae67a9e7adcb57673a183cea8b06eb778dca859181b5", + }, + { + Name: "aquasecurity:trivy:RepoDigest", + Value: "alpine@sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad", + }, + { + Name: "aquasecurity:trivy:DiffID", + Value: "sha256:994393dc58e7931862558d06e46aa2bb17487044f670f310dffe1d24e4d1eec7", + }, + { + Name: "aquasecurity:trivy:RepoTag", + Value: "alpine:3.16", + }, + }, + }, + }, + Components: &[]cyclonedx.Component{ + { + BOMRef: "fad4eb97-3d2a-4499-ace7-2c94444148a7", + Type: cyclonedx.ComponentTypeOS, + Name: "alpine", + Version: "3.16.2", + Properties: &[]cyclonedx.Property{ + { + Name: "aquasecurity:trivy:Type", + Value: "alpine", + }, + { + Name: "aquasecurity:trivy:Class", + Value: "os-pkgs", + }, + }, + }, + { + BOMRef: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.2", + Type: cyclonedx.ComponentTypeLibrary, + Name: "musl", + Version: "1.2.3-r0", + Licenses: &cyclonedx.Licenses{ + {Expression: "MIT"}, + }, + PackageURL: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.2", + Properties: &[]cyclonedx.Property{ + { + Name: "aquasecurity:trivy:PkgType", + Value: "alpine", + }, + { + Name: "aquasecurity:trivy:SrcName", + Value: "musl", + }, + { + Name: "aquasecurity:trivy:SrcVersion", + Value: "1.2.3-r0", + }, + { + Name: "aquasecurity:trivy:LayerDiffID", + Value: "sha256:994393dc58e7931862558d06e46aa2bb17487044f670f310dffe1d24e4d1eec7", + }, + }, + }, + }, + Dependencies: &[]cyclonedx.Dependency{ + { + Ref: "pkg:oci/alpine@sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad?repository_url=index.docker.io%2Flibrary%2Falpine&arch=amd64", + Dependencies: &[]string{ + "fad4eb97-3d2a-4499-ace7-2c94444148a7", + }, + }, + { + Ref: "fad4eb97-3d2a-4499-ace7-2c94444148a7", + Dependencies: &[]string{ + "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.2", + }, + }, + }, + }, + }, + } + + gomodSBOMAttestation = in_toto.Statement{ + StatementHeader: in_toto.StatementHeader{ + Type: "https://in-toto.io/Statement/v0.1", + PredicateType: "https://cyclonedx.org/bom", + Subject: []in_toto.Subject{ + { + Name: "go.mod", + Digest: slsa.DigestSet{ + "sha256": "23f4e10c43c7654e33a3c9570913c8c9c528292762f1a5c4a97253e9e4e4b238", + }, + }, + }, + }, + Predicate: &attestation.CosignPredicate{ + Data: &cyclonedx.BOM{ + BOMFormat: cyclonedx.BOMFormat, + SerialNumber: "urn:uuid:8b16c9a3-e957-4c85-b43d-7dd05ea0421c", + SpecVersion: cyclonedx.SpecVersion1_5, + Version: 1, + Metadata: &cyclonedx.Metadata{ + Timestamp: "2022-10-21T09:50:08+00:00", + Tools: &cyclonedx.ToolsChoice{ + Components: &[]cyclonedx.Component{ + { + Type: cyclonedx.ComponentTypeApplication, + Name: "trivy", + Group: "aquasecurity", + Version: "dev", + }, + }, + }, + Component: &cyclonedx.Component{ + BOMRef: "ef8385d7-a56f-495a-a220-7b0a2e940d39", + Type: cyclonedx.ComponentTypeApplication, + Name: "go.mod", + Properties: &[]cyclonedx.Property{ + { + Name: "aquasecurity:trivy:SchemaVersion", + Value: "2", + }, + }, + }, + }, + Components: &[]cyclonedx.Component{ + { + BOMRef: "bb8b7541-2b08-4692-9363-8f79da5c1a31", + Type: cyclonedx.ComponentTypeApplication, + Name: "go.mod", + Properties: &[]cyclonedx.Property{ + { + Name: "aquasecurity:trivy:Type", + Value: "gomod", + }, + { + Name: "aquasecurity:trivy:Class", + Value: "lang-pkgs", + }, + }, + }, + { + BOMRef: "pkg:golang/github.com/spf13/cobra@1.5.0", + Type: cyclonedx.ComponentTypeLibrary, + Name: "github.com/spf13/cobra", + Version: "1.5.0", + PackageURL: "pkg:golang/github.com/spf13/cobra@1.5.0", + Properties: &[]cyclonedx.Property{ + { + Name: "aquasecurity:trivy:PkgType", + Value: "gomod", + }, + }, + }, + }, + Dependencies: &[]cyclonedx.Dependency{ + { + Ref: "ef8385d7-a56f-495a-a220-7b0a2e940d39", + Dependencies: &[]string{ + "bb8b7541-2b08-4692-9363-8f79da5c1a31", + }, + }, + { + Ref: "bb8b7541-2b08-4692-9363-8f79da5c1a31", + Dependencies: &[]string{ + "pkg:golang/github.com/spf13/cobra@1.5.0", + }, + }, + }, + }, + }, + } + + emptySBOMAttestation = in_toto.Statement{ + StatementHeader: in_toto.StatementHeader{ + Type: "https://in-toto.io/Statement/v0.1", + PredicateType: "https://cyclonedx.org/bom", + }, + Predicate: &attestation.CosignPredicate{ + Data: &cyclonedx.BOM{ + BOMFormat: cyclonedx.BOMFormat, + SpecVersion: cyclonedx.SpecVersion1_5, + Version: 2, + }, + }, + } + + entries = map[string]models.LogEntryAnon{ + "392f8ecba72f4326414eaca77bd19bf5f378725d7fd79309605a81b69cc0101f5cd3119d0a216523": { + Attestation: &models.LogEntryAnonAttestation{ + Data: mustMarshal(imageSBOMAttestation), + }, + Body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI3ODIxNDNlMzlmMWU3YTA0ZTNmNmRhMmQ4OGIxYzA1N2U1NjU3MzYzYzRmOTA2NzlmM2U4YTA3MWI3NjE5ZTAyIn0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZWJiZmRkZGE2Mjc3YWYxOTllOTNjNWJiNWNmNTk5OGE3OTMxMWRlMjM4ZTQ5YmNjOGFjMjQxMDI2OTg3NjFiYiJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdseFowRjNTVUpCWjBsVllXaHNPRUZSZDFsWlYwNVpiblY2ZGxGdk9FVnJOMWRNVFVSdmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlFU1RKTlJFVjRUbnBGTTFkb1kwNU5ha2wzVDBSSk1rMUVSWGxPZWtVelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZMV21aRVF6bHBhbFZ5Y2xwQldFOWpXRllyUVhGSFJVbFRTbEV6VkhScVNuZEpkRUVLZFRFM1JtbDJhV3BuU2sxaFlVaEdORGNyVDNaMk9WUjFla0ZEUTNscFNVVjVVRFV5WlhJMlptRjVibVpLWVZWcU9FdFBRMEZWYTNkblowWkdUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZIUWxkVUNrTXdkVVUzZFRSUWNVUlZSakZZVjBjMFFsVldWVXBCZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZE1RbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTREJGWlhkQ05VRklZMEZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBNS01UZHRTbWhuUVVGQ1FVMUJVMFJDUjBGcFJVRm9TMDlCU2tkV1ZsaENiMWN4VERSNGFsazVlV0pXT0daVVVYTjVUU3R2VUVwSWVEazVTMjlMWVVwVlF3cEpVVVJDWkRsbGMxUTBNazFTVG5nM1ZtOUJNMXBhS3pWNGFraE5aV1I2YW1WeFEyWm9aVGN2ZDFweFlUbFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXZDa0ZFUW14QmFrVkJjbkJrZVhsRlJqYzNiMkp5VEVOTVVYcHpZbUl4TTJsc05qZDNkek00WTA1MGFtZE5RbWw2WTJWVWFrUmlZMlZMZVZGU04xUktOSE1LWkVOc2Nsa3hZMUJCYWtFNGFYQjZTVVE0VlUxQ2FHeGtTbVV2WlhKR2NHZHROMnN3TldGaWMybFBOM1Y1ZFZadVMyOVZOazByVFhKNlZWVXJaVGxHZHdwSlJHaENhblZSYTFkUll6MEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", + IntegratedTime: lo.ToPtr(int64(1661476639)), + LogID: lo.ToPtr("c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"), + LogIndex: lo.ToPtr(int64(3280165)), + Verification: nil, + }, + "392f8ecba72f4326eb624a7403756250b5f2ad58842a99d1653cd6f147f4ce9eda2da350bd908a55": { + Attestation: &models.LogEntryAnonAttestation{ + Data: []byte(`{"apiVersion":"0.0.1","kind":"intoto","spec":{"content":{"hash":{"algorithm":"sha256","value":"782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02"},"payloadHash":{"algorithm":"sha256","value":"ebbfddda6277af199e93c5bb5cf5998a79311de238e49bcc8ac24102698761bb"}},"publicKey":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNwRENDQWlxZ0F3SUJBZ0lVYWhsOEFRd1lZV05ZbnV6dlFvOEVrN1dMTURvd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpJd09ESTJNREV4TnpFM1doY05Nakl3T0RJMk1ERXlOekUzV2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVLWmZEQzlpalVyclpBWE9jWFYrQXFHRUlTSlEzVHRqSndJdEEKdTE3Rml2aWpnSk1hYUhGNDcrT3Z2OVR1ekFDQ3lpSUV5UDUyZXI2ZmF5bmZKYVVqOEtPQ0FVa3dnZ0ZGTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVHQldUCkMwdUU3dTRQcURVRjFYV0c0QlVWVUpBd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d0pRWURWUjBSQVFIL0JCc3dHWUVYYzJGemIyRnJhWEpoTmpFeE5FQm5iV0ZwYkM1amIyMHdLUVlLS3dZQgpCQUdEdnpBQkFRUWJhSFIwY0hNNkx5OWhZMk52ZFc1MGN5NW5iMjluYkdVdVkyOXRNSUdMQmdvckJnRUVBZFo1CkFnUUNCSDBFZXdCNUFIY0FDR0NTOENoUy8yaEYwZEZySjRTY1JXY1lyQlk5d3pqU2JlYThJZ1kyYjNJQUFBR0MKMTdtSmhnQUFCQU1BU0RCR0FpRUFoS09BSkdWVlhCb1cxTDR4alk5eWJWOGZUUXN5TStvUEpIeDk5S29LYUpVQwpJUURCZDllc1Q0Mk1STng3Vm9BM1paKzV4akhNZWR6amVxQ2ZoZTcvd1pxYTlUQUtCZ2dxaGtqT1BRUURBd05vCkFEQmxBakVBcnBkeXlFRjc3b2JyTENMUXpzYmIxM2lsNjd3dzM4Y050amdNQml6Y2VUakRiY2VLeVFSN1RKNHMKZENsclkxY1BBakE4aXB6SUQ4VU1CaGxkSmUvZXJGcGdtN2swNWFic2lPN3V5dVZuS29VNk0rTXJ6VVUrZTlGdwpJRGhCanVRa1dRYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}}`), + }, + Body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI3ODIxNDNlMzlmMWU3YTA0ZTNmNmRhMmQ4OGIxYzA1N2U1NjU3MzYzYzRmOTA2NzlmM2U4YTA3MWI3NjE5ZTAyIn0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZWJiZmRkZGE2Mjc3YWYxOTllOTNjNWJiNWNmNTk5OGE3OTMxMWRlMjM4ZTQ5YmNjOGFjMjQxMDI2OTg3NjFiYiJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdseFowRjNTVUpCWjBsVllXaHNPRUZSZDFsWlYwNVpiblY2ZGxGdk9FVnJOMWRNVFVSdmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlFU1RKTlJFVjRUbnBGTTFkb1kwNU5ha2wzVDBSSk1rMUVSWGxPZWtVelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZMV21aRVF6bHBhbFZ5Y2xwQldFOWpXRllyUVhGSFJVbFRTbEV6VkhScVNuZEpkRUVLZFRFM1JtbDJhV3BuU2sxaFlVaEdORGNyVDNaMk9WUjFla0ZEUTNscFNVVjVVRFV5WlhJMlptRjVibVpLWVZWcU9FdFBRMEZWYTNkblowWkdUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZIUWxkVUNrTXdkVVUzZFRSUWNVUlZSakZZVjBjMFFsVldWVXBCZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZE1RbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTREJGWlhkQ05VRklZMEZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBNS01UZHRTbWhuUVVGQ1FVMUJVMFJDUjBGcFJVRm9TMDlCU2tkV1ZsaENiMWN4VERSNGFsazVlV0pXT0daVVVYTjVUU3R2VUVwSWVEazVTMjlMWVVwVlF3cEpVVVJDWkRsbGMxUTBNazFTVG5nM1ZtOUJNMXBhS3pWNGFraE5aV1I2YW1WeFEyWm9aVGN2ZDFweFlUbFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXZDa0ZFUW14QmFrVkJjbkJrZVhsRlJqYzNiMkp5VEVOTVVYcHpZbUl4TTJsc05qZDNkek00WTA1MGFtZE5RbWw2WTJWVWFrUmlZMlZMZVZGU04xUktOSE1LWkVOc2Nsa3hZMUJCYWtFNGFYQjZTVVE0VlUxQ2FHeGtTbVV2WlhKR2NHZHROMnN3TldGaWMybFBOM1Y1ZFZadVMyOVZOazByVFhKNlZWVXJaVGxHZHdwSlJHaENhblZSYTFkUll6MEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", + IntegratedTime: lo.ToPtr(int64(1661476639)), + LogID: lo.ToPtr("c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"), + LogIndex: lo.ToPtr(int64(3280165)), + Verification: nil, + }, + "24296fb24b8ad77aa715cdfd264ce34c4d544375d7bd7cd029bf5a48ef25217a13fdba562e0889ca": { + Attestation: &models.LogEntryAnonAttestation{ + Data: mustMarshal(gomodSBOMAttestation), + }, + Body: nil, // not used at the moment + IntegratedTime: lo.ToPtr(int64(1664451604)), + LogID: lo.ToPtr("c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"), + LogIndex: lo.ToPtr(int64(4215471)), + Verification: nil, + }, + "24296fb24b8ad77a8d47be2e40bfe910f0ffc842e86b5685dd85d1c903ef78bb6362125816426fe9": { + Attestation: &models.LogEntryAnonAttestation{ + Data: mustMarshal(emptySBOMAttestation), + }, + Body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI3ODIxNDNlMzlmMWU3YTA0ZTNmNmRhMmQ4OGIxYzA1N2U1NjU3MzYzYzRmOTA2NzlmM2U4YTA3MWI3NjE5ZTAyIn0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZWJiZmRkZGE2Mjc3YWYxOTllOTNjNWJiNWNmNTk5OGE3OTMxMWRlMjM4ZTQ5YmNjOGFjMjQxMDI2OTg3NjFiYiJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdseFowRjNTVUpCWjBsVllXaHNPRUZSZDFsWlYwNVpiblY2ZGxGdk9FVnJOMWRNVFVSdmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlFU1RKTlJFVjRUbnBGTTFkb1kwNU5ha2wzVDBSSk1rMUVSWGxPZWtVelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZMV21aRVF6bHBhbFZ5Y2xwQldFOWpXRllyUVhGSFJVbFRTbEV6VkhScVNuZEpkRUVLZFRFM1JtbDJhV3BuU2sxaFlVaEdORGNyVDNaMk9WUjFla0ZEUTNscFNVVjVVRFV5WlhJMlptRjVibVpLWVZWcU9FdFBRMEZWYTNkblowWkdUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZIUWxkVUNrTXdkVVUzZFRSUWNVUlZSakZZVjBjMFFsVldWVXBCZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZE1RbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTREJGWlhkQ05VRklZMEZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBNS01UZHRTbWhuUVVGQ1FVMUJVMFJDUjBGcFJVRm9TMDlCU2tkV1ZsaENiMWN4VERSNGFsazVlV0pXT0daVVVYTjVUU3R2VUVwSWVEazVTMjlMWVVwVlF3cEpVVVJDWkRsbGMxUTBNazFTVG5nM1ZtOUJNMXBhS3pWNGFraE5aV1I2YW1WeFEyWm9aVGN2ZDFweFlUbFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXZDa0ZFUW14QmFrVkJjbkJrZVhsRlJqYzNiMkp5VEVOTVVYcHpZbUl4TTJsc05qZDNkek00WTA1MGFtZE5RbWw2WTJWVWFrUmlZMlZMZVZGU04xUktOSE1LWkVOc2Nsa3hZMUJCYWtFNGFYQjZTVVE0VlUxQ2FHeGtTbVV2WlhKR2NHZHROMnN3TldGaWMybFBOM1Y1ZFZadVMyOVZOazByVFhKNlZWVXJaVGxHZHdwSlJHaENhblZSYTFkUll6MEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", + IntegratedTime: lo.ToPtr(int64(1661476639)), + LogID: lo.ToPtr("c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"), + LogIndex: lo.ToPtr(int64(3280165)), + Verification: nil, + }, + } +) + +type Server struct { + ts *httptest.Server +} + +func NewServer(t *testing.T) *Server { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/index/retrieve": + var params models.SearchIndex + err := json.NewDecoder(r.Body).Decode(¶ms) + require.NoError(t, err) + + if res, ok := indexRes[params.Hash]; ok { + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(res) + require.NoError(t, err) + } else { + http.Error(w, "something wrong", http.StatusNotFound) + } + case "/api/v1/log/entries/retrieve": + var params models.SearchLogQuery + err := json.NewDecoder(r.Body).Decode(¶ms) + require.NoError(t, err) + + resEntries := models.LogEntry{} + for _, uuid := range params.EntryUUIDs { + if e, ok := entries[uuid]; !ok { + http.Error(w, "no such uuid", http.StatusNotFound) + return + } else { + resEntries[uuid] = e + } + } + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode([]models.LogEntry{resEntries}) + require.NoError(t, err) + } + return + })) + + return &Server{ts: ts} +} + +func (s *Server) URL() string { + return s.ts.URL +} + +func (s *Server) Close() { + s.ts.Close() +} + +func mustMarshal(v any) []byte { + b, err := json.Marshal(v) + if err != nil { + panic(err) + } + return b +} diff --git a/pkg/remic/run.go b/pkg/remic/run.go deleted file mode 100644 index ea3eac0576c1..000000000000 --- a/pkg/remic/run.go +++ /dev/null @@ -1,85 +0,0 @@ -package remic - -import ( - l "log" - "os" - "strings" - - "github.com/knqyf263/trivy/pkg/scanner" - - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "github.com/knqyf263/trivy/pkg/vulnsrc" - - "github.com/urfave/cli" - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/db" - "github.com/knqyf263/trivy/pkg/log" - "github.com/knqyf263/trivy/pkg/report" -) - -func Run(c *cli.Context) (err error) { - debug := c.Bool("debug") - if err = log.InitLogger(debug); err != nil { - l.Fatal(err) - } - - args := c.Args() - if len(args) == 0 { - return xerrors.New(`remic" requires at least 1 argument.`) - } - - o := c.String("output") - output := os.Stdout - if o != "" { - if output, err = os.Create(o); err != nil { - return err - } - } - - var severities []vulnerability.Severity - for _, s := range strings.Split(c.String("severity"), ",") { - severity, err := vulnerability.NewSeverity(s) - if err != nil { - return err - } - severities = append(severities, severity) - } - - if err = db.Init(); err != nil { - return err - } - - if err = vulnsrc.Update(); err != nil { - return err - } - - fileName := args[0] - f, err := os.Open(fileName) - if err != nil { - return xerrors.Errorf("failed to open a file: %w", err) - } - defer f.Close() - - result, err := scanner.ScanFile(f, severities) - if err != nil { - return xerrors.Errorf("failed to scan a file: %w", err) - } - - var writer report.Writer - switch c.String("format") { - case "table": - writer = &report.TableWriter{Output: output} - case "json": - writer = &report.JsonWriter{Output: output} - default: - return xerrors.New("unknown format") - } - - if err = writer.Write([]report.Result{result}); err != nil { - return xerrors.Errorf("failed to write results: %w", err) - } - - return nil -} diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go new file mode 100644 index 000000000000..08924704a7d5 --- /dev/null +++ b/pkg/remote/remote.go @@ -0,0 +1,235 @@ +package remote + +import ( + "context" + "crypto/tls" + "net" + "net/http" + "time" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + v1types "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/hashicorp/go-multierror" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/image/registry" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +type Descriptor = remote.Descriptor + +// Get is a wrapper of google/go-containerregistry/pkg/v1/remote.Get +// so that it can try multiple authentication methods. +func Get(ctx context.Context, ref name.Reference, option types.RegistryOptions) (*Descriptor, error) { + transport, err := httpTransport(option) + if err != nil { + return nil, xerrors.Errorf("failed to create http transport: %w", err) + } + + var errs error + // Try each authentication method until it succeeds + for _, authOpt := range authOptions(ctx, ref, option) { + remoteOpts := []remote.Option{ + remote.WithTransport(transport), + authOpt, + } + + if option.Platform.Platform != nil { + p, err := resolvePlatform(ref, option.Platform, remoteOpts) + if err != nil { + return nil, xerrors.Errorf("platform error: %w", err) + } + // Don't pass platform when the specified image is single-arch. + if p.Platform != nil { + remoteOpts = append(remoteOpts, remote.WithPlatform(*p.Platform)) + } + } + + desc, err := remote.Get(ref, remoteOpts...) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + + if option.Platform.Force { + if err = satisfyPlatform(desc, lo.FromPtr(option.Platform.Platform)); err != nil { + return nil, err + } + } + return desc, nil + } + + // No authentication succeeded + return nil, errs +} + +// Image is a wrapper of google/go-containerregistry/pkg/v1/remote.Image +// so that it can try multiple authentication methods. +func Image(ctx context.Context, ref name.Reference, option types.RegistryOptions) (v1.Image, error) { + transport, err := httpTransport(option) + if err != nil { + return nil, xerrors.Errorf("failed to create http transport: %w", err) + } + + var errs error + // Try each authentication method until it succeeds + for _, authOpt := range authOptions(ctx, ref, option) { + remoteOpts := []remote.Option{ + remote.WithTransport(transport), + authOpt, + } + index, err := remote.Image(ref, remoteOpts...) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + return index, nil + } + + // No authentication succeeded + return nil, errs +} + +// Referrers is a wrapper of google/go-containerregistry/pkg/v1/remote.Referrers +// so that it can try multiple authentication methods. +func Referrers(ctx context.Context, d name.Digest, option types.RegistryOptions) (v1.ImageIndex, error) { + transport, err := httpTransport(option) + if err != nil { + return nil, xerrors.Errorf("failed to create http transport: %w", err) + } + + var errs error + // Try each authentication method until it succeeds + for _, authOpt := range authOptions(ctx, d, option) { + remoteOpts := []remote.Option{ + remote.WithTransport(transport), + authOpt, + } + index, err := remote.Referrers(d, remoteOpts...) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + return index, nil + } + + // No authentication succeeded + return nil, errs +} + +func httpTransport(option types.RegistryOptions) (*http.Transport, error) { + d := &net.Dialer{ + Timeout: 10 * time.Minute, + } + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.DialContext = d.DialContext + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: option.Insecure} + + if len(option.ClientCert) != 0 && len(option.ClientKey) != 0 { + cert, err := tls.X509KeyPair(option.ClientCert, option.ClientKey) + if err != nil { + return nil, err + } + tr.TLSClientConfig.Certificates = []tls.Certificate{cert} + } + + return tr, nil +} + +func authOptions(ctx context.Context, ref name.Reference, option types.RegistryOptions) []remote.Option { + var opts []remote.Option + for _, cred := range option.Credentials { + opts = append(opts, remote.WithAuth(&authn.Basic{ + Username: cred.Username, + Password: cred.Password, + })) + } + + domain := ref.Context().RegistryStr() + token := registry.GetToken(ctx, domain, option) + if !lo.IsEmpty(token) { + opts = append(opts, remote.WithAuth(&token)) + } + + switch { + case option.RegistryToken != "": + bearer := authn.Bearer{Token: option.RegistryToken} + return []remote.Option{remote.WithAuth(&bearer)} + default: + // Use the keychain anyway at the end + opts = append(opts, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + return opts + } +} + +// resolvePlatform resolves the OS platform for a given image reference. +// If the platform has an empty OS, the function will attempt to find the first OS +// in the image's manifest list and return the platform with the detected OS. +// It ignores the specified platform if the image is not multi-arch. +func resolvePlatform(ref name.Reference, p types.Platform, options []remote.Option) (types.Platform, error) { + if p.OS != "" { + return p, nil + } + + // OS wildcard, implicitly pick up the first os found in the image list. + // e.g. */amd64 + d, err := remote.Get(ref, options...) + if err != nil { + return types.Platform{}, xerrors.Errorf("image get error: %w", err) + } + switch d.MediaType { + case v1types.OCIManifestSchema1, v1types.DockerManifestSchema2: + // We want an index but the registry has an image, not multi-arch. We just ignore "--platform". + log.Logger.Debug("Ignore --platform as the image is not multi-arch") + return types.Platform{}, nil + case v1types.OCIImageIndex, v1types.DockerManifestList: + // These are expected. + } + + index, err := d.ImageIndex() + if err != nil { + return types.Platform{}, xerrors.Errorf("image index error: %w", err) + } + + m, err := index.IndexManifest() + if err != nil { + return types.Platform{}, xerrors.Errorf("remote index manifest error: %w", err) + } + if len(m.Manifests) == 0 { + log.Logger.Debug("Ignore '--platform' as the image is not multi-arch") + return types.Platform{}, nil + } + if m.Manifests[0].Platform != nil { + newPlatform := p.DeepCopy() + // Replace with the detected OS + // e.g. */amd64 => linux/amd64 + newPlatform.OS = m.Manifests[0].Platform.OS + + // Return the platform with the found OS + return types.Platform{ + Platform: newPlatform, + Force: p.Force, + }, nil + } + return types.Platform{}, nil +} + +func satisfyPlatform(desc *remote.Descriptor, platform v1.Platform) error { + img, err := desc.Image() + if err != nil { + return err + } + c, err := img.ConfigFile() + if err != nil { + return err + } + if !lo.FromPtr(c.Platform()).Satisfies(platform) { + return xerrors.Errorf("the specified platform not found") + } + return nil +} diff --git a/pkg/remote/remote_test.go b/pkg/remote/remote_test.go new file mode 100644 index 000000000000..63a98ba1fce2 --- /dev/null +++ b/pkg/remote/remote_test.go @@ -0,0 +1,208 @@ +package remote + +import ( + "context" + "encoding/base64" + "fmt" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/testdocker/auth" + "github.com/aquasecurity/testdocker/registry" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func setupPrivateRegistry() *httptest.Server { + imagePaths := map[string]string{ + "v2/library/alpine:3.10": "../fanal/test/testdata/alpine-310.tar.gz", + } + tr := registry.NewDockerRegistry(registry.Option{ + Images: imagePaths, + Auth: auth.Auth{ + User: "test", + Password: "testpass", + Secret: "secret", + }, + }) + + return tr +} + +// setupConfigDir sets up an isolated configDir() for this test. +func setupConfigDir(t *testing.T) string { + p := t.TempDir() + t.Setenv("DOCKER_CONFIG", p) + return p +} + +func setupDockerConfig(t *testing.T, content string) { + cd := setupConfigDir(t) + p := filepath.Join(cd, "config.json") + + err := os.WriteFile(p, []byte(content), 0600) + require.NoError(t, err) +} + +func encode(user, pass string) string { + delimited := fmt.Sprintf("%s:%s", user, pass) + return base64.StdEncoding.EncodeToString([]byte(delimited)) +} + +func TestGet(t *testing.T) { + tr := setupPrivateRegistry() + defer tr.Close() + + serverAddr := tr.Listener.Addr().String() + + type args struct { + imageName string + config string + option types.RegistryOptions + } + tests := []struct { + name string + args args + want *Descriptor + wantErr string + }{ + { + name: "single credential", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + option: types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: "test", + Password: "testpass", + }, + }, + Insecure: true, + }, + }, + }, + { + name: "multiple credential", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + option: types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: "foo", + Password: "bar", + }, + { + Username: "test", + Password: "testpass", + }, + }, + Insecure: true, + }, + }, + }, + { + name: "keychain", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + config: fmt.Sprintf(`{"auths": {"%s": {"auth": %q}}}`, serverAddr, encode("test", "testpass")), + option: types.RegistryOptions{ + Insecure: true, + }, + }, + }, + { + name: "platform", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + option: types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: "test", + Password: "testpass", + }, + }, + Insecure: true, + Platform: types.Platform{ + Platform: &v1.Platform{ + OS: "", + Architecture: "amd64", + }, + }, + }, + }, + }, + { + name: "force platform", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + option: types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: "test", + Password: "testpass", + }, + }, + Insecure: true, + Platform: types.Platform{ + Force: true, + Platform: &v1.Platform{ + OS: "windows", + Architecture: "amd64", + }, + }, + }, + }, + wantErr: "the specified platform not found", + }, + { + name: "bad credential", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + option: types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: "foo", + Password: "bar", + }, + }, + Insecure: true, + }, + }, + wantErr: "invalid username/password", + }, + { + name: "bad keychain", + args: args{ + imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), + config: fmt.Sprintf(`{"auths": {"%s": {"auth": %q}}}`, serverAddr, encode("foo", "bar")), + option: types.RegistryOptions{ + Insecure: true, + }, + }, + wantErr: "invalid username/password", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n, err := name.ParseReference(tt.args.imageName) + require.NoError(t, err) + + if tt.args.config != "" { + setupDockerConfig(t, tt.args.config) + } + + _, err = Get(context.Background(), n, tt.args.option) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr, err) + return + } + assert.NoError(t, err) + }) + } +} diff --git a/pkg/report/cyclonedx/cyclonedx.go b/pkg/report/cyclonedx/cyclonedx.go new file mode 100644 index 000000000000..76cd91d67752 --- /dev/null +++ b/pkg/report/cyclonedx/cyclonedx.go @@ -0,0 +1,44 @@ +package cyclonedx + +import ( + "context" + "io" + + cdx "github.com/CycloneDX/cyclonedx-go" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" + "github.com/aquasecurity/trivy/pkg/types" +) + +// Writer implements types.Writer +type Writer struct { + output io.Writer + format cdx.BOMFileFormat + marshaler cyclonedx.Marshaler +} + +func NewWriter(output io.Writer, appVersion string) Writer { + return Writer{ + output: output, + format: cdx.BOMFileFormatJSON, + marshaler: cyclonedx.NewMarshaler(appVersion), + } +} + +// Write writes the results in CycloneDX format +func (w Writer) Write(ctx context.Context, report types.Report) error { + bom, err := w.marshaler.MarshalReport(ctx, report) + if err != nil { + return xerrors.Errorf("CycloneDX marshal error: %w", err) + } + + encoder := cdx.NewBOMEncoder(w.output, w.format) + encoder.SetPretty(true) + encoder.SetEscapeHTML(false) + if err = encoder.Encode(bom); err != nil { + return xerrors.Errorf("failed to encode bom: %w", err) + } + + return nil +} diff --git a/pkg/report/github/github.go b/pkg/report/github/github.go new file mode 100644 index 000000000000..5de466ad6beb --- /dev/null +++ b/pkg/report/github/github.go @@ -0,0 +1,194 @@ +package github + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "strings" + "time" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + DirectRelationship string = "direct" + IndirectRelationship string = "indirect" + RuntimeScope string = "runtime" + DevelopmentScope string = "development" +) + +type Package struct { + PackageUrl string `json:"package_url,omitempty"` + Relationship string `json:"relationship,omitempty"` + Dependencies []string `json:"dependencies,omitempty"` + Scope string `json:"scope,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` +} + +type File struct { + SrcLocation string `json:"source_location,omitempty"` +} + +type Metadata map[string]interface{} + +type Manifest struct { + Name string `json:"name,omitempty"` + File *File `json:"file,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` + Resolved map[string]Package `json:"resolved,omitempty"` +} + +type Job struct { + Correlator string `json:"correlator,omitempty"` + Id string `json:"id,omitempty"` +} +type Detector struct { + Name string `json:"name"` + Version string `json:"version"` + Url string `json:"url"` +} + +type DependencySnapshot struct { + Version int `json:"version"` + Detector Detector `json:"detector"` + Metadata Metadata `json:"metadata,omitempty"` + Ref string `json:"ref,omitempty"` + Sha string `json:"sha,omitempty"` + Job *Job `json:"job,omitempty"` + Scanned string `json:"scanned,omitempty"` + Manifests map[string]Manifest `json:"manifests,omitempty"` +} + +// Writer generates JSON for GitHub Dependency Snapshots +type Writer struct { + Output io.Writer + Version string +} + +func (w Writer) Write(ctx context.Context, report types.Report) error { + snapshot := &DependencySnapshot{} + + // use now() method that can be overwritten while integration tests run + snapshot.Scanned = clock.Now(ctx).Format(time.RFC3339) + snapshot.Detector = Detector{ + Name: "trivy", + Version: w.Version, + Url: "https://github.com/aquasecurity/trivy", + } + snapshot.Version = 0 // The version of the repository snapshot submission. + + snapshot.Ref = os.Getenv("GITHUB_REF") + snapshot.Sha = os.Getenv("GITHUB_SHA") + + snapshot.Job = &Job{ + Correlator: fmt.Sprintf("%s_%s", os.Getenv("GITHUB_WORKFLOW"), os.Getenv("GITHUB_JOB")), + Id: os.Getenv("GITHUB_RUN_ID"), + } + + snapshot.Metadata = getMetadata(report) + + manifests := make(map[string]Manifest) + + for _, result := range report.Results { + if result.Packages == nil { + continue + } + + manifest := Manifest{} + manifest.Name = string(result.Type) + // show path for language-specific packages only + if result.Class == types.ClassLangPkg { + if report.ArtifactType == ftypes.ArtifactContainerImage { + // `RepoDigests` ~= /@sha256: + // `RepoTag` ~= /: + // By concatenating the hash from `RepoDigests` at the end of `RepoTag` we get all the information + imageReference := strings.Join(report.Metadata.RepoTags, ", ") + imageWithHash := strings.Join(report.Metadata.RepoDigests, ", ") + _, imageHash, found := strings.Cut(imageWithHash, "@") + if found { + imageReference += "@" + imageHash + } + // Replacing `source_location` in manifest by the image name, tag and hash + manifest.File = &File{ + SrcLocation: imageReference, + } + + } else { + manifest.File = &File{ + SrcLocation: result.Target, + } + } + } + + resolved := make(map[string]Package) + + for _, pkg := range result.Packages { + var err error + githubPkg := Package{} + githubPkg.Scope = RuntimeScope + githubPkg.Relationship = getPkgRelationshipType(pkg) + githubPkg.Dependencies = pkg.DependsOn // TODO: replace with PURL + githubPkg.PackageUrl, err = buildPurl(result.Type, report.Metadata, pkg) + if err != nil { + return xerrors.Errorf("unable to build purl for %s: %w", pkg.Name, err) + } + + if pkg.FilePath != "" { + githubPkg.Metadata = Metadata{"source_location": pkg.FilePath} + } + + resolved[pkg.Name] = githubPkg + } + + manifest.Resolved = resolved + manifests[result.Target] = manifest + } + + snapshot.Manifests = manifests + + output, err := json.MarshalIndent(snapshot, "", " ") + if err != nil { + return xerrors.Errorf("failed to marshal github dependency snapshots: %w", err) + } + + if _, err = fmt.Fprint(w.Output, string(output)); err != nil { + return xerrors.Errorf("failed to write github dependency snapshots: %w", err) + } + return nil +} + +func getMetadata(report types.Report) Metadata { + metadata := Metadata{} + if report.Metadata.RepoTags != nil { + metadata["aquasecurity:trivy:RepoTag"] = strings.Join(report.Metadata.RepoTags, ", ") + } + if report.Metadata.RepoDigests != nil { + metadata["aquasecurity:trivy:RepoDigest"] = strings.Join(report.Metadata.RepoDigests, ", ") + } + return metadata +} + +func getPkgRelationshipType(pkg ftypes.Package) string { + if pkg.Indirect { + return IndirectRelationship + } + return DirectRelationship +} + +func buildPurl(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) (string, error) { + packageUrl, err := purl.New(t, metadata, pkg) + if err != nil { + return "", xerrors.Errorf("purl error: %w", err) + } + if packageUrl == nil { + return "", nil + } + return packageUrl.String(), nil +} diff --git a/pkg/report/github/github_test.go b/pkg/report/github/github_test.go new file mode 100644 index 000000000000..596fd8dd3862 --- /dev/null +++ b/pkg/report/github/github_test.go @@ -0,0 +1,254 @@ +package github_test + +import ( + "bytes" + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/report/github" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestWriter_Write(t *testing.T) { + tests := []struct { + name string + report types.Report + want map[string]github.Manifest + }{ + { + name: "os packages", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "alpine:3.14", + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.14", + Eosl: true, + }, + }, + Results: types.Results{ + { + Target: "yarn.lock", + Class: "lang-pkgs", + Type: "yarn", + Packages: []ftypes.Package{ + { + Name: "@xtuc/ieee754", + Version: "1.2.0", + }, + { + Name: "@xtuc/long", + Version: "4.2.2", + }, + { + Name: "@xtuc/binaryen", + Version: "1.37.33", + Indirect: true, + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + }, + }, + }, + }, + { + Target: "alpine:3.14 (alpine 3.14.10)", + Class: "os-pkgs", + Type: "alpine", + Packages: []ftypes.Package{ + { + ID: "apk-tools@2.12.7-r0", + Name: "apk-tools", + Version: "2.12.7-r0", + Arch: "x86_64", + SrcName: "apk-tools", + SrcVersion: "2.12.7-r0", + }, + }, + }, + }, + }, + want: map[string]github.Manifest{ + "alpine:3.14 (alpine 3.14.10)": { + Name: "alpine", + Resolved: map[string]github.Package{ + "apk-tools": { + PackageUrl: "pkg:apk/alpine/apk-tools@2.12.7-r0?arch=x86_64&distro=3.14", + Relationship: "direct", + Scope: "runtime", + }, + }, + }, + "yarn.lock": { + Name: "yarn", + File: &github.File{ + SrcLocation: "yarn.lock", + }, + Resolved: map[string]github.Package{ + "@xtuc/ieee754": { + PackageUrl: "pkg:npm/%40xtuc/ieee754@1.2.0", + Relationship: "direct", + Scope: "runtime", + }, + "@xtuc/long": { + PackageUrl: "pkg:npm/%40xtuc/long@4.2.2", + Relationship: "direct", + Scope: "runtime", + }, + "@xtuc/binaryen": { + PackageUrl: "pkg:npm/%40xtuc/binaryen@1.37.33", + Relationship: "indirect", + Scope: "runtime", + }, + }, + }, + }, + }, + { + name: "maven", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "my-java-app", + Results: types.Results{ + { + Target: "pom.xml", + Class: "lang-pkgs", + Type: "pom", + Packages: []ftypes.Package{ + { + Name: "com.google.code.gson:gson", + Version: "2.2.2", + }, + { + Name: "net.sf.opencsv:opencsv", + Version: "2.3", + }, + }, + }, + }, + }, + want: map[string]github.Manifest{ + "pom.xml": { + Name: "pom", + File: &github.File{ + SrcLocation: "pom.xml", + }, + Resolved: map[string]github.Package{ + "com.google.code.gson:gson": { + PackageUrl: "pkg:maven/com.google.code.gson/gson@2.2.2", + Relationship: "direct", + Scope: "runtime", + }, + "net.sf.opencsv:opencsv": { + PackageUrl: "pkg:maven/net.sf.opencsv/opencsv@2.3", + Relationship: "direct", + Scope: "runtime", + }, + }, + }, + }, + }, + { + name: "pypi from image", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "fake_repo.azurecr.io/image_name", + ArtifactType: "container_image", + Metadata: types.Metadata{ + RepoDigests: []string{"fake_repo.azurecr.io/image_name@sha256:a7c92cdcb3d010f6edeb37ddcdbacab14981aa31e7f1140e0097dc1b8e834c49"}, + RepoTags: []string{"fake_repo.azurecr.io/image_name:latest"}, + }, + Results: types.Results{ + { + Target: "Python", + Class: "lang-pkgs", + Type: "python-pkg", + Packages: []ftypes.Package{ + { + Name: "jwcrypto", + Version: "0.7", + Licenses: []string{ + "LGPLv3+", + }, + Layer: ftypes.Layer{ + Digest: "sha256:ddc612ba4e74ea5633a93e19e7c32f61f5f230073b21a070302a61ef5eec5c50", + DiffID: "sha256:12935ef6ce21a266aef8df75d601cebf7e935edd01e9f19fab16ccb78fbb9a5e", + }, + FilePath: "opt/pyenv/versions/3.11.2/lib/python3.11/site-packages/jwcrypto-0.7.dist-info/METADATA", + }, + { + Name: "matplotlib", + Version: "3.5.3", + Licenses: []string{ + "PSF", + }, + Layer: ftypes.Layer{ + Digest: "sha256:ddc612ba4e74ea5633a93e19e7c32f61f5f230073b21a070302a61ef5eec5c50", + DiffID: "sha256:12935ef6ce21a266aef8df75d601cebf7e935edd01e9f19fab16ccb78fbb9a5e", + }, + FilePath: "opt/pyenv/versions/3.11.2/lib/python3.11/site-packages/matplotlib-3.5.3.dist-info/METADATA", + }, + }, + }, + }, + }, + want: map[string]github.Manifest{ + "Python": { + Name: "python-pkg", + File: &github.File{ + SrcLocation: "fake_repo.azurecr.io/image_name:latest@sha256:a7c92cdcb3d010f6edeb37ddcdbacab14981aa31e7f1140e0097dc1b8e834c49", + }, + Resolved: map[string]github.Package{ + "jwcrypto": { + PackageUrl: "pkg:pypi/jwcrypto@0.7", + Relationship: "direct", + Scope: "runtime", + Metadata: github.Metadata{"source_location": "opt/pyenv/versions/3.11.2/lib/python3.11/site-packages/jwcrypto-0.7.dist-info/METADATA"}, + }, + "matplotlib": { + PackageUrl: "pkg:pypi/matplotlib@3.5.3", + Relationship: "direct", + Scope: "runtime", + Metadata: github.Metadata{"source_location": "opt/pyenv/versions/3.11.2/lib/python3.11/site-packages/matplotlib-3.5.3.dist-info/METADATA"}, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + written := bytes.NewBuffer(nil) + w := github.Writer{ + Output: written, + } + + inputResults := tt.report + + err := w.Write(context.Background(), inputResults) + assert.NoError(t, err) + + var got github.DependencySnapshot + err = json.Unmarshal(written.Bytes(), &got) + assert.NoError(t, err, "invalid github written") + assert.Equal(t, tt.want, got.Manifests, tt.name) + }) + } +} diff --git a/pkg/report/json.go b/pkg/report/json.go new file mode 100644 index 000000000000..57ac92cfe8ca --- /dev/null +++ b/pkg/report/json.go @@ -0,0 +1,30 @@ +package report + +import ( + "context" + "encoding/json" + "fmt" + "io" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/types" +) + +// JSONWriter implements result Writer +type JSONWriter struct { + Output io.Writer +} + +// Write writes the results in JSON format +func (jw JSONWriter) Write(ctx context.Context, report types.Report) error { + output, err := json.MarshalIndent(report, "", " ") + if err != nil { + return xerrors.Errorf("failed to marshal json: %w", err) + } + + if _, err = fmt.Fprintln(jw.Output, string(output)); err != nil { + return xerrors.Errorf("failed to write json: %w", err) + } + return nil +} diff --git a/pkg/report/json_test.go b/pkg/report/json_test.go new file mode 100644 index 000000000000..493fd6d8f641 --- /dev/null +++ b/pkg/report/json_test.go @@ -0,0 +1,99 @@ +package report_test + +import ( + "bytes" + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestReportWriter_JSON(t *testing.T) { + testCases := []struct { + name string + detectedVulns []types.DetectedVulnerability + want types.Report + }{ + { + name: "happy path", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{ + vulnerability.NVD: dbTypes.SeverityHigh, + }, + }, + }, + }, + want: types.Report{ + SchemaVersion: 2, + ArtifactName: "alpine:3.14", + Results: types.Results{ + types.Result{ + Target: "foojson", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{ + vulnerability.NVD: dbTypes.SeverityHigh, + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + jsonWritten := bytes.NewBuffer(nil) + jw := report.JSONWriter{ + Output: jsonWritten, + } + + inputResults := types.Report{ + SchemaVersion: 2, + ArtifactName: "alpine:3.14", + Results: types.Results{ + { + Target: "foojson", + Vulnerabilities: tc.detectedVulns, + }, + }, + } + + err := jw.Write(context.Background(), inputResults) + assert.NoError(t, err) + + var got types.Report + err = json.Unmarshal(jsonWritten.Bytes(), &got) + assert.NoError(t, err, "invalid json written") + + assert.Equal(t, tc.want, got, tc.name) + }) + } +} diff --git a/pkg/report/predicate/vuln.go b/pkg/report/predicate/vuln.go new file mode 100644 index 000000000000..9c975a4be76a --- /dev/null +++ b/pkg/report/predicate/vuln.go @@ -0,0 +1,89 @@ +package predicate + +import ( + "context" + "encoding/json" + "fmt" + "io" + "time" + + "github.com/package-url/packageurl-go" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/types" +) + +// CosignVulnPredicate represents the Cosign Vulnerability Scan Record. +// CosignVulnPredicate is based on structures in the Cosign repository. +// We defined them ourselves to reduce our dependence on the repository. +// cf. https://github.com/sigstore/cosign/blob/e0547cff64f98585a837a524ff77ff6b47ff5609/pkg/cosign/attestation/attestation.go#L45-L50 +type CosignVulnPredicate struct { + Invocation Invocation `json:"invocation"` + Scanner Scanner `json:"scanner"` + Metadata Metadata `json:"metadata"` +} + +type Invocation struct { + Parameters interface{} `json:"parameters"` + URI string `json:"uri"` + EventID string `json:"event_id"` + BuilderID string `json:"builder.id"` +} + +type DB struct { + URI string `json:"uri"` + Version string `json:"version"` +} + +type Scanner struct { + URI string `json:"uri"` + Version string `json:"version"` + DB DB `json:"db"` + Result types.Report `json:"result"` +} + +type Metadata struct { + ScanStartedOn time.Time `json:"scanStartedOn"` + ScanFinishedOn time.Time `json:"scanFinishedOn"` +} + +type VulnWriter struct { + output io.Writer + version string +} + +func NewVulnWriter(output io.Writer, version string) VulnWriter { + return VulnWriter{ + output: output, + version: version, + } +} + +func (w VulnWriter) Write(ctx context.Context, report types.Report) error { + predicate := CosignVulnPredicate{} + + purl := packageurl.NewPackageURL("github", "aquasecurity", "trivy", w.version, nil, "") + predicate.Scanner = Scanner{ + URI: purl.ToString(), + Version: w.version, + Result: report, + } + + now := clock.Now(ctx) + predicate.Metadata = Metadata{ + ScanStartedOn: now, + ScanFinishedOn: now, + } + + output, err := json.MarshalIndent(predicate, "", " ") + if err != nil { + return xerrors.Errorf("failed to marshal cosign vulnerability predicate: %w", err) + } + + if _, err = fmt.Fprint(w.output, string(output)); err != nil { + return xerrors.Errorf("failed to write cosign vulnerability predicate: %w", err) + } + return nil + +} diff --git a/pkg/report/predicate/vuln_test.go b/pkg/report/predicate/vuln_test.go new file mode 100644 index 000000000000..8477a3971bd4 --- /dev/null +++ b/pkg/report/predicate/vuln_test.go @@ -0,0 +1,115 @@ +package predicate_test + +import ( + "bytes" + "context" + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/require" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/report/predicate" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestWriter_Write(t *testing.T) { + tests := []struct { + name string + detectedVulns []types.DetectedVulnerability + want predicate.CosignVulnPredicate + }{ + { + name: "happy path", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{ + vulnerability.NVD: dbTypes.SeverityHigh, + }, + }, + }, + }, + want: predicate.CosignVulnPredicate{ + Scanner: predicate.Scanner{ + URI: "pkg:github/aquasecurity/trivy@dev", + Version: "dev", + Result: types.Report{ + SchemaVersion: 2, + ArtifactName: "alpine:3.14", + ArtifactType: ftypes.ArtifactType(""), + Metadata: types.Metadata{}, + Results: types.Results{ + { + Target: "foojson", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{ + vulnerability.NVD: dbTypes.SeverityHigh, + }, + }, + }, + }, + }, + }, + }, + }, + Metadata: predicate.Metadata{ + ScanStartedOn: time.Date(2022, time.July, 22, 12, 20, 30, 5, time.UTC), + ScanFinishedOn: time.Date(2022, time.July, 22, 12, 20, 30, 5, time.UTC), + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inputResults := types.Report{ + SchemaVersion: 2, + ArtifactName: "alpine:3.14", + Results: types.Results{ + { + Target: "foojson", + Vulnerabilities: tt.detectedVulns, + }, + }, + } + + output := bytes.NewBuffer(nil) + + ctx := clock.With(context.Background(), time.Date(2022, 7, 22, 12, 20, 30, 5, time.UTC)) + writer := predicate.NewVulnWriter(output, "dev") + + err := writer.Write(ctx, inputResults) + require.NoError(t, err) + + var got predicate.CosignVulnPredicate + err = json.Unmarshal(output.Bytes(), &got) + require.NoError(t, err, "invalid json written") + + require.Equal(t, tt.want, got, tt.name) + + }) + } +} diff --git a/pkg/report/sarif.go b/pkg/report/sarif.go new file mode 100644 index 000000000000..2f9dd5891516 --- /dev/null +++ b/pkg/report/sarif.go @@ -0,0 +1,393 @@ +package report + +import ( + "context" + "fmt" + "html" + "io" + "path/filepath" + "regexp" + "strings" + + containerName "github.com/google/go-containerregistry/pkg/name" + "github.com/owenrumney/go-sarif/v2/sarif" + "golang.org/x/xerrors" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + sarifOsPackageVulnerability = "OsPackageVulnerability" + sarifLanguageSpecificVulnerability = "LanguageSpecificPackageVulnerability" + sarifConfigFiles = "Misconfiguration" + sarifSecretFiles = "Secret" + sarifLicenseFiles = "License" + sarifUnknownIssue = "UnknownIssue" + + sarifError = "error" + sarifWarning = "warning" + sarifNote = "note" + sarifNone = "none" + + columnKind = "utf16CodeUnits" + + builtinRulesUrl = "https://github.com/aquasecurity/trivy/blob/main/pkg/fanal/secret/builtin-rules.go" // list all secrets +) + +var ( + rootPath = "file:///" + + // pathRegex to extract file path in case string includes (distro:version) + pathRegex = regexp.MustCompile(`(?P.+?)(?:\s*\((?:.*?)\).*?)?$`) +) + +// SarifWriter implements result Writer +type SarifWriter struct { + Output io.Writer + Version string + run *sarif.Run + locationCache map[string][]location + Target string +} + +type sarifData struct { + title string + vulnerabilityId string + shortDescription string + fullDescription string + helpText string + helpMarkdown string + resourceClass types.ResultClass + severity string + url string + resultIndex int + artifactLocation string + locationMessage string + message string + cvssScore string + locations []location +} + +type location struct { + startLine int + endLine int +} + +func (sw *SarifWriter) addSarifRule(data *sarifData) { + r := sw.run.AddRule(data.vulnerabilityId). + WithName(toSarifRuleName(data.resourceClass)). + WithDescription(data.vulnerabilityId). + WithShortDescription(&sarif.MultiformatMessageString{Text: &data.shortDescription}). + WithFullDescription(&sarif.MultiformatMessageString{Text: &data.fullDescription}). + WithHelp(&sarif.MultiformatMessageString{ + Text: &data.helpText, + Markdown: &data.helpMarkdown, + }). + WithDefaultConfiguration(&sarif.ReportingConfiguration{ + Level: toSarifErrorLevel(data.severity), + }). + WithProperties(sarif.Properties{ + "tags": []string{ + data.title, + "security", + data.severity, + }, + "precision": "very-high", + "security-severity": data.cvssScore, + }) + if data.url != "" { + r.WithHelpURI(data.url) + } +} + +func (sw *SarifWriter) addSarifResult(data *sarifData) { + sw.addSarifRule(data) + + result := sarif.NewRuleResult(data.vulnerabilityId). + WithRuleIndex(data.resultIndex). + WithMessage(sarif.NewTextMessage(data.message)). + WithLevel(toSarifErrorLevel(data.severity)). + WithLocations(toSarifLocations(data.locations, data.artifactLocation, data.locationMessage)) + sw.run.AddResult(result) +} + +func getRuleIndex(id string, indexes map[string]int) int { + if i, ok := indexes[id]; ok { + return i + } else { + l := len(indexes) + indexes[id] = l + return l + } +} + +func (sw *SarifWriter) Write(ctx context.Context, report types.Report) error { + sarifReport, err := sarif.New(sarif.Version210) + if err != nil { + return xerrors.Errorf("error creating a new sarif template: %w", err) + } + sw.run = sarif.NewRunWithInformationURI("Trivy", "https://github.com/aquasecurity/trivy") + sw.run.Tool.Driver.WithVersion(sw.Version) + sw.run.Tool.Driver.WithFullName("Trivy Vulnerability Scanner") + sw.locationCache = make(map[string][]location) + if report.ArtifactType == ftypes.ArtifactContainerImage { + sw.run.Properties = sarif.Properties{ + "imageName": report.ArtifactName, + "repoTags": report.Metadata.RepoTags, + "repoDigests": report.Metadata.RepoDigests, + } + } + if sw.Target != "" { + absPath, _ := filepath.Abs(sw.Target) + rootPath = fmt.Sprintf("file://%s/", absPath) + } + + ruleIndexes := make(map[string]int) + for _, res := range report.Results { + target := ToPathUri(res.Target, res.Class) + + for _, vuln := range res.Vulnerabilities { + fullDescription := vuln.Description + if fullDescription == "" { + fullDescription = vuln.Title + } + path := target + if vuln.PkgPath != "" { + path = ToPathUri(vuln.PkgPath, res.Class) + } + sw.addSarifResult(&sarifData{ + title: "vulnerability", + vulnerabilityId: vuln.VulnerabilityID, + severity: vuln.Severity, + cvssScore: getCVSSScore(vuln), + url: vuln.PrimaryURL, + resourceClass: res.Class, + artifactLocation: path, + locationMessage: fmt.Sprintf("%v: %v@%v", path, vuln.PkgName, vuln.InstalledVersion), + locations: sw.getLocations(vuln.PkgName, vuln.InstalledVersion, path, res.Packages), + resultIndex: getRuleIndex(vuln.VulnerabilityID, ruleIndexes), + shortDescription: html.EscapeString(vuln.Title), + fullDescription: html.EscapeString(fullDescription), + helpText: fmt.Sprintf("Vulnerability %v\nSeverity: %v\nPackage: %v\nFixed Version: %v\nLink: [%v](%v)\n%v", + vuln.VulnerabilityID, vuln.Severity, vuln.PkgName, vuln.FixedVersion, vuln.VulnerabilityID, vuln.PrimaryURL, vuln.Description), + helpMarkdown: fmt.Sprintf("**Vulnerability %v**\n| Severity | Package | Fixed Version | Link |\n| --- | --- | --- | --- |\n|%v|%v|%v|[%v](%v)|\n\n%v", + vuln.VulnerabilityID, vuln.Severity, vuln.PkgName, vuln.FixedVersion, vuln.VulnerabilityID, vuln.PrimaryURL, vuln.Description), + message: fmt.Sprintf("Package: %v\nInstalled Version: %v\nVulnerability %v\nSeverity: %v\nFixed Version: %v\nLink: [%v](%v)", + vuln.PkgName, vuln.InstalledVersion, vuln.VulnerabilityID, vuln.Severity, vuln.FixedVersion, vuln.VulnerabilityID, vuln.PrimaryURL), + }) + } + for _, misconf := range res.Misconfigurations { + locationURI := clearURI(res.Target) + sw.addSarifResult(&sarifData{ + title: "misconfiguration", + vulnerabilityId: misconf.ID, + severity: misconf.Severity, + cvssScore: severityToScore(misconf.Severity), + url: misconf.PrimaryURL, + resourceClass: res.Class, + artifactLocation: locationURI, + locationMessage: locationURI, + locations: []location{ + { + startLine: misconf.CauseMetadata.StartLine, + endLine: misconf.CauseMetadata.EndLine, + }, + }, + resultIndex: getRuleIndex(misconf.ID, ruleIndexes), + shortDescription: html.EscapeString(misconf.Title), + fullDescription: html.EscapeString(misconf.Description), + helpText: fmt.Sprintf("Misconfiguration %v\nType: %s\nSeverity: %v\nCheck: %v\nMessage: %v\nLink: [%v](%v)\n%s", + misconf.ID, misconf.Type, misconf.Severity, misconf.Title, misconf.Message, misconf.ID, misconf.PrimaryURL, misconf.Description), + helpMarkdown: fmt.Sprintf("**Misconfiguration %v**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|%v|%v|%v|%s|[%v](%v)|\n\n%v", + misconf.ID, misconf.Type, misconf.Severity, misconf.Title, misconf.Message, misconf.ID, misconf.PrimaryURL, misconf.Description), + message: fmt.Sprintf("Artifact: %v\nType: %v\nVulnerability %v\nSeverity: %v\nMessage: %v\nLink: [%v](%v)", + locationURI, res.Type, misconf.ID, misconf.Severity, misconf.Message, misconf.ID, misconf.PrimaryURL), + }) + } + for _, secret := range res.Secrets { + sw.addSarifResult(&sarifData{ + title: "secret", + vulnerabilityId: secret.RuleID, + severity: secret.Severity, + cvssScore: severityToScore(secret.Severity), + url: builtinRulesUrl, + resourceClass: res.Class, + artifactLocation: target, + locationMessage: target, + locations: []location{ + { + startLine: secret.StartLine, + endLine: secret.EndLine, + }, + }, + resultIndex: getRuleIndex(secret.RuleID, ruleIndexes), + shortDescription: html.EscapeString(secret.Title), + fullDescription: html.EscapeString(secret.Match), + helpText: fmt.Sprintf("Secret %v\nSeverity: %v\nMatch: %s", + secret.Title, secret.Severity, secret.Match), + helpMarkdown: fmt.Sprintf("**Secret %v**\n| Severity | Match |\n| --- | --- |\n|%v|%v|", + secret.Title, secret.Severity, secret.Match), + message: fmt.Sprintf("Artifact: %v\nType: %v\nSecret %v\nSeverity: %v\nMatch: %v", + res.Target, res.Type, secret.Title, secret.Severity, secret.Match), + }) + } + for _, license := range res.Licenses { + id := fmt.Sprintf("%s:%s", license.PkgName, license.Name) + desc := fmt.Sprintf("%s in %s", license.Name, license.PkgName) + sw.addSarifResult(&sarifData{ + title: "license", + vulnerabilityId: id, + severity: license.Severity, + cvssScore: severityToScore(license.Severity), + url: license.Link, + resourceClass: res.Class, + artifactLocation: target, + resultIndex: getRuleIndex(id, ruleIndexes), + shortDescription: desc, + fullDescription: desc, + helpText: fmt.Sprintf("License %s\nClassification: %s\nPkgName: %s\nPath: %s", + license.Name, license.Category, license.PkgName, license.FilePath), + helpMarkdown: fmt.Sprintf("**License %s**\n| PkgName | Classification | Path |\n| --- | --- | --- |\n|%s|%s|%s|", + license.Name, license.PkgName, license.Category, license.FilePath), + message: fmt.Sprintf("Artifact: %s\nLicense %s\nPkgName: %s\n Classification: %s\n Path: %s", + res.Target, license.Name, license.Category, license.PkgName, license.FilePath), + }) + } + + } + sw.run.ColumnKind = columnKind + sw.run.OriginalUriBaseIDs = map[string]*sarif.ArtifactLocation{ + "ROOTPATH": {URI: &rootPath}, + } + sarifReport.AddRun(sw.run) + return sarifReport.PrettyWrite(sw.Output) +} + +func toSarifLocations(locations []location, artifactLocation, locationMessage string) []*sarif.Location { + var sarifLocs []*sarif.Location + // add default (hardcoded) location for vulnerabilities that don't support locations + if len(locations) == 0 { + locations = append(locations, location{ + startLine: 1, + endLine: 1, + }) + } + + // some dependencies can be placed in multiple places. + // e.g.https://github.com/aquasecurity/go-dep-parser/pull/134#discussion_r985353240 + // create locations for each place. + + for _, l := range locations { + // location is missed. Use default (hardcoded) value (misconfigurations have this case) + if l.startLine == 0 && l.endLine == 0 { + l.startLine = 1 + l.endLine = 1 + } + region := sarif.NewRegion().WithStartLine(l.startLine).WithEndLine(l.endLine).WithStartColumn(1).WithEndColumn(1) + loc := sarif.NewPhysicalLocation(). + WithArtifactLocation(sarif.NewSimpleArtifactLocation(artifactLocation).WithUriBaseId("ROOTPATH")). + WithRegion(region) + sarifLocs = append(sarifLocs, sarif.NewLocation().WithMessage(sarif.NewTextMessage(locationMessage)).WithPhysicalLocation(loc)) + } + + return sarifLocs +} + +func toSarifRuleName(class types.ResultClass) string { + switch class { + case types.ClassOSPkg: + return sarifOsPackageVulnerability + case types.ClassLangPkg: + return sarifLanguageSpecificVulnerability + case types.ClassConfig: + return sarifConfigFiles + case types.ClassSecret: + return sarifSecretFiles + case types.ClassLicense, types.ClassLicenseFile: + return sarifLicenseFiles + default: + return sarifUnknownIssue + } +} + +func toSarifErrorLevel(severity string) string { + switch severity { + case "CRITICAL", "HIGH": + return sarifError + case "MEDIUM": + return sarifWarning + case "LOW", "UNKNOWN": + return sarifNote + default: + return sarifNone + } +} + +func ToPathUri(input string, resultClass types.ResultClass) string { + // we only need to convert OS input + // e.g. image names, digests, etc... + if resultClass != types.ClassOSPkg { + return input + } + var matches = pathRegex.FindStringSubmatch(input) + if matches != nil { + input = matches[pathRegex.SubexpIndex("path")] + } + ref, err := containerName.ParseReference(input) + if err == nil { + input = ref.Context().RepositoryStr() + } + + return clearURI(input) +} + +func clearURI(s string) string { + return strings.ReplaceAll(strings.ReplaceAll(s, "\\", "/"), "git::https:/", "") +} + +func (sw *SarifWriter) getLocations(name, version, path string, pkgs []ftypes.Package) []location { + id := fmt.Sprintf("%s@%s@%s", path, name, version) + locs, ok := sw.locationCache[id] + if !ok { + for _, pkg := range pkgs { + if name == pkg.Name && version == pkg.Version { + for _, l := range pkg.Locations { + loc := location{ + startLine: l.StartLine, + endLine: l.EndLine, + } + locs = append(locs, loc) + } + sw.locationCache[id] = locs + return locs + } + } + } + return locs +} + +func getCVSSScore(vuln types.DetectedVulnerability) string { + // Take the vendor score + if cvss, ok := vuln.CVSS[vuln.SeveritySource]; ok { + return fmt.Sprintf("%.1f", cvss.V3Score) + } + + // Converts severity to score + return severityToScore(vuln.Severity) +} + +func severityToScore(severity string) string { + switch severity { + case "CRITICAL": + return "9.5" + case "HIGH": + return "8.0" + case "MEDIUM": + return "5.5" + case "LOW": + return "2.0" + default: + return "0.0" + } +} diff --git a/pkg/report/sarif_test.go b/pkg/report/sarif_test.go new file mode 100644 index 000000000000..fe46514002b6 --- /dev/null +++ b/pkg/report/sarif_test.go @@ -0,0 +1,723 @@ +package report_test + +import ( + "bytes" + "context" + "encoding/json" + "testing" + + "github.com/owenrumney/go-sarif/v2/sarif" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestReportWriter_Sarif(t *testing.T) { + tests := []struct { + name string + input types.Report + want *sarif.Report + }{ + { + name: "report with vulnerabilities", + input: types.Report{ + ArtifactName: "debian:9", + ArtifactType: ftypes.ArtifactContainerImage, + Metadata: types.Metadata{ + RepoTags: []string{ + "debian:9", + }, + RepoDigests: []string{ + "debian@sha256:a8cc1744bbdd5266678e3e8b3e6387e45c053218438897e86876f2eb104e5534", + }, + }, + Results: types.Results{ + { + Target: "library/test", + Class: types.ClassOSPkg, + Packages: []ftypes.Package{ + { + Name: "foo", + Version: "1.2.3", + Locations: []ftypes.Location{ + { + StartLine: 5, + EndLine: 10, + }, + { + StartLine: 15, + EndLine: 20, + }, + }, + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + SeveritySource: "redhat", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{ + vulnerability.NVD: dbTypes.SeverityCritical, + vulnerability.RedHat: dbTypes.SeverityHigh, + }, + CVSS: map[dbTypes.SourceID]dbTypes.CVSS{ + vulnerability.NVD: { + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + V3Score: 9.8, + }, + vulnerability.RedHat: { + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + V3Score: 7.5, + }, + }, + }, + }, + }, + }, + }, + }, + want: &sarif.Report{ + Version: "2.1.0", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Runs: []*sarif.Run{ + { + Tool: sarif.Tool{ + Driver: &sarif.ToolComponent{ + FullName: lo.ToPtr("Trivy Vulnerability Scanner"), + Name: "Trivy", + Version: lo.ToPtr(""), + InformationURI: lo.ToPtr("https://github.com/aquasecurity/trivy"), + Rules: []*sarif.ReportingDescriptor{ + { + ID: "CVE-2020-0001", + Name: lo.ToPtr("OsPackageVulnerability"), + ShortDescription: &sarif.MultiformatMessageString{Text: lo.ToPtr("foobar")}, + FullDescription: &sarif.MultiformatMessageString{Text: lo.ToPtr("baz")}, + DefaultConfiguration: &sarif.ReportingConfiguration{ + Level: "error", + }, + HelpURI: lo.ToPtr("https://avd.aquasec.com/nvd/cve-2020-0001"), + Properties: map[string]interface{}{ + "tags": []interface{}{ + "vulnerability", + "security", + "HIGH", + }, + "precision": "very-high", + "security-severity": "7.5", + }, + Help: &sarif.MultiformatMessageString{ + Text: lo.ToPtr("Vulnerability CVE-2020-0001\nSeverity: HIGH\nPackage: foo\nFixed Version: 3.4.5\nLink: [CVE-2020-0001](https://avd.aquasec.com/nvd/cve-2020-0001)\nbaz"), + Markdown: lo.ToPtr("**Vulnerability CVE-2020-0001**\n| Severity | Package | Fixed Version | Link |\n| --- | --- | --- | --- |\n|HIGH|foo|3.4.5|[CVE-2020-0001](https://avd.aquasec.com/nvd/cve-2020-0001)|\n\nbaz"), + }, + }, + }, + }, + }, + Results: []*sarif.Result{ + { + RuleID: lo.ToPtr("CVE-2020-0001"), + RuleIndex: lo.ToPtr[uint](0), + Level: lo.ToPtr("error"), + Message: sarif.Message{Text: lo.ToPtr("Package: foo\nInstalled Version: 1.2.3\nVulnerability CVE-2020-0001\nSeverity: HIGH\nFixed Version: 3.4.5\nLink: [CVE-2020-0001](https://avd.aquasec.com/nvd/cve-2020-0001)")}, + Locations: []*sarif.Location{ + { + Message: &sarif.Message{Text: lo.ToPtr("library/test: foo@1.2.3")}, + PhysicalLocation: &sarif.PhysicalLocation{ + ArtifactLocation: &sarif.ArtifactLocation{ + URI: lo.ToPtr("library/test"), + URIBaseId: lo.ToPtr("ROOTPATH"), + }, + Region: &sarif.Region{ + StartLine: lo.ToPtr(5), + EndLine: lo.ToPtr(10), + StartColumn: lo.ToPtr(1), + EndColumn: lo.ToPtr(1), + }, + }, + }, + { + Message: &sarif.Message{Text: lo.ToPtr("library/test: foo@1.2.3")}, + PhysicalLocation: &sarif.PhysicalLocation{ + ArtifactLocation: &sarif.ArtifactLocation{ + URI: lo.ToPtr("library/test"), + URIBaseId: lo.ToPtr("ROOTPATH"), + }, + Region: &sarif.Region{ + StartLine: lo.ToPtr(15), + EndLine: lo.ToPtr(20), + StartColumn: lo.ToPtr(1), + EndColumn: lo.ToPtr(1), + }, + }, + }, + }, + }, + }, + ColumnKind: "utf16CodeUnits", + OriginalUriBaseIDs: map[string]*sarif.ArtifactLocation{ + "ROOTPATH": { + URI: lo.ToPtr("file:///"), + }, + }, + PropertyBag: sarif.PropertyBag{ + Properties: map[string]interface{}{ + "imageName": "debian:9", + "repoDigests": []interface{}{"debian@sha256:a8cc1744bbdd5266678e3e8b3e6387e45c053218438897e86876f2eb104e5534"}, + "repoTags": []interface{}{"debian:9"}, + }, + }, + }, + }, + }, + }, + { + name: "report with misconfigurations", + input: types.Report{ + Results: types.Results{ + { + Target: "library/test", + Class: types.ClassConfig, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Kubernetes Security Check", + ID: "KSV001", + Title: "Image tag ':latest' used", + Message: "Message", + Severity: "HIGH", + PrimaryURL: "https://avd.aquasec.com/appshield/ksv001", + Status: types.MisconfStatusFailure, + }, + { + Type: "Kubernetes Security Check", + ID: "KSV002", + Title: "SYS_ADMIN capability added", + Message: "Message", + Severity: "CRITICAL", + PrimaryURL: "https://avd.aquasec.com/appshield/ksv002", + Status: types.MisconfStatusPassed, + }, + }, + }, + }, + }, + want: &sarif.Report{ + Version: "2.1.0", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Runs: []*sarif.Run{ + { + Tool: sarif.Tool{ + Driver: &sarif.ToolComponent{ + FullName: lo.ToPtr("Trivy Vulnerability Scanner"), + Name: "Trivy", + Version: lo.ToPtr(""), + InformationURI: lo.ToPtr("https://github.com/aquasecurity/trivy"), + Rules: []*sarif.ReportingDescriptor{ + { + ID: "KSV001", + Name: lo.ToPtr("Misconfiguration"), + ShortDescription: &sarif.MultiformatMessageString{Text: lo.ToPtr("Image tag ':latest' used")}, + FullDescription: &sarif.MultiformatMessageString{Text: lo.ToPtr("")}, + DefaultConfiguration: &sarif.ReportingConfiguration{ + Level: "error", + }, + HelpURI: lo.ToPtr("https://avd.aquasec.com/appshield/ksv001"), + Properties: map[string]interface{}{ + "tags": []interface{}{ + "misconfiguration", + "security", + "HIGH", + }, + "precision": "very-high", + "security-severity": "8.0", + }, + Help: &sarif.MultiformatMessageString{ + Text: lo.ToPtr("Misconfiguration KSV001\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: Image tag ':latest' used\nMessage: Message\nLink: [KSV001](https://avd.aquasec.com/appshield/ksv001)\n"), + Markdown: lo.ToPtr("**Misconfiguration KSV001**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|Image tag ':latest' used|Message|[KSV001](https://avd.aquasec.com/appshield/ksv001)|\n\n"), + }, + }, + { + ID: "KSV002", + Name: lo.ToPtr("Misconfiguration"), + ShortDescription: &sarif.MultiformatMessageString{Text: lo.ToPtr("SYS_ADMIN capability added")}, + FullDescription: &sarif.MultiformatMessageString{Text: lo.ToPtr("")}, + DefaultConfiguration: &sarif.ReportingConfiguration{ + Level: "error", + }, + HelpURI: lo.ToPtr("https://avd.aquasec.com/appshield/ksv002"), + Properties: map[string]interface{}{ + "tags": []interface{}{ + "misconfiguration", + "security", + "CRITICAL", + }, + "precision": "very-high", + "security-severity": "9.5", + }, + Help: &sarif.MultiformatMessageString{ + Text: lo.ToPtr("Misconfiguration KSV002\nType: Kubernetes Security Check\nSeverity: CRITICAL\nCheck: SYS_ADMIN capability added\nMessage: Message\nLink: [KSV002](https://avd.aquasec.com/appshield/ksv002)\n"), + Markdown: lo.ToPtr("**Misconfiguration KSV002**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|CRITICAL|SYS_ADMIN capability added|Message|[KSV002](https://avd.aquasec.com/appshield/ksv002)|\n\n"), + }, + }, + }, + }, + }, + Results: []*sarif.Result{ + { + RuleID: lo.ToPtr("KSV001"), + RuleIndex: lo.ToPtr[uint](0), + Level: lo.ToPtr("error"), + Message: sarif.Message{Text: lo.ToPtr("Artifact: library/test\nType: \nVulnerability KSV001\nSeverity: HIGH\nMessage: Message\nLink: [KSV001](https://avd.aquasec.com/appshield/ksv001)")}, + Locations: []*sarif.Location{ + { + Message: &sarif.Message{Text: lo.ToPtr("library/test")}, + PhysicalLocation: &sarif.PhysicalLocation{ + ArtifactLocation: &sarif.ArtifactLocation{ + URI: lo.ToPtr("library/test"), + URIBaseId: lo.ToPtr("ROOTPATH"), + }, + Region: &sarif.Region{ + StartLine: lo.ToPtr(1), + EndLine: lo.ToPtr(1), + StartColumn: lo.ToPtr(1), + EndColumn: lo.ToPtr(1), + }, + }, + }, + }, + }, + { + RuleID: lo.ToPtr("KSV002"), + RuleIndex: lo.ToPtr[uint](1), + Level: lo.ToPtr("error"), + Message: sarif.Message{Text: lo.ToPtr("Artifact: library/test\nType: \nVulnerability KSV002\nSeverity: CRITICAL\nMessage: Message\nLink: [KSV002](https://avd.aquasec.com/appshield/ksv002)")}, + Locations: []*sarif.Location{ + { + Message: &sarif.Message{Text: lo.ToPtr("library/test")}, + PhysicalLocation: &sarif.PhysicalLocation{ + ArtifactLocation: &sarif.ArtifactLocation{ + URI: lo.ToPtr("library/test"), + URIBaseId: lo.ToPtr("ROOTPATH"), + }, + Region: &sarif.Region{ + StartLine: lo.ToPtr(1), + EndLine: lo.ToPtr(1), + StartColumn: lo.ToPtr(1), + EndColumn: lo.ToPtr(1), + }, + }, + }, + }, + }, + }, + ColumnKind: "utf16CodeUnits", + OriginalUriBaseIDs: map[string]*sarif.ArtifactLocation{ + "ROOTPATH": { + URI: lo.ToPtr("file:///"), + }, + }, + }, + }, + }, + }, + { + name: "report with secrets", + input: types.Report{ + Results: types.Results{ + { + Target: "library/test", + Class: types.ClassSecret, + Secrets: []types.DetectedSecret{ + { + RuleID: "aws-secret-access-key", + Category: "AWS", + Severity: "CRITICAL", + Title: "AWS Secret Access Key", + StartLine: 1, + EndLine: 1, + Match: "'AWS_secret_KEY'=\"****************************************\"", + }, + }, + }, + }, + }, + want: &sarif.Report{ + Version: "2.1.0", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Runs: []*sarif.Run{ + { + Tool: sarif.Tool{ + Driver: &sarif.ToolComponent{ + FullName: lo.ToPtr("Trivy Vulnerability Scanner"), + Name: "Trivy", + Version: lo.ToPtr(""), + InformationURI: lo.ToPtr("https://github.com/aquasecurity/trivy"), + Rules: []*sarif.ReportingDescriptor{ + { + ID: "aws-secret-access-key", + Name: lo.ToPtr("Secret"), + ShortDescription: &sarif.MultiformatMessageString{Text: lo.ToPtr("AWS Secret Access Key")}, + FullDescription: &sarif.MultiformatMessageString{Text: lo.ToPtr("\u0026#39;AWS_secret_KEY\u0026#39;=\u0026#34;****************************************\u0026#34;")}, + DefaultConfiguration: &sarif.ReportingConfiguration{ + Level: "error", + }, + HelpURI: lo.ToPtr("https://github.com/aquasecurity/trivy/blob/main/pkg/fanal/secret/builtin-rules.go"), + Properties: map[string]interface{}{ + "tags": []interface{}{ + "secret", + "security", + "CRITICAL", + }, + "precision": "very-high", + "security-severity": "9.5", + }, + Help: &sarif.MultiformatMessageString{ + Text: lo.ToPtr("Secret AWS Secret Access Key\nSeverity: CRITICAL\nMatch: 'AWS_secret_KEY'=\"****************************************\""), + Markdown: lo.ToPtr("**Secret AWS Secret Access Key**\n| Severity | Match |\n| --- | --- |\n|CRITICAL|'AWS_secret_KEY'=\"****************************************\"|"), + }, + }, + }, + }, + }, + Results: []*sarif.Result{ + { + RuleID: lo.ToPtr("aws-secret-access-key"), + RuleIndex: lo.ToPtr[uint](0), + Level: lo.ToPtr("error"), + Message: sarif.Message{Text: lo.ToPtr("Artifact: library/test\nType: \nSecret AWS Secret Access Key\nSeverity: CRITICAL\nMatch: 'AWS_secret_KEY'=\"****************************************\"")}, + Locations: []*sarif.Location{ + { + Message: &sarif.Message{Text: lo.ToPtr("library/test")}, + PhysicalLocation: &sarif.PhysicalLocation{ + ArtifactLocation: &sarif.ArtifactLocation{ + URI: lo.ToPtr("library/test"), + URIBaseId: lo.ToPtr("ROOTPATH"), + }, + Region: &sarif.Region{ + StartLine: lo.ToPtr(1), + EndLine: lo.ToPtr(1), + StartColumn: lo.ToPtr(1), + EndColumn: lo.ToPtr(1), + }, + }, + }, + }, + }, + }, + ColumnKind: "utf16CodeUnits", + OriginalUriBaseIDs: map[string]*sarif.ArtifactLocation{ + "ROOTPATH": { + URI: lo.ToPtr("file:///"), + }, + }, + }, + }, + }, + }, + { + name: "report with licenses", + input: types.Report{ + Results: types.Results{ + { + Target: "OS Packages", + Class: "license", + Licenses: []types.DetectedLicense{ + { + Severity: "HIGH", + Category: "restricted", + PkgName: "alpine-base", + FilePath: "", + Name: "GPL-3.0", + Confidence: 1, + Link: "", + }, + }, + }, + }, + }, + want: &sarif.Report{ + Version: "2.1.0", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Runs: []*sarif.Run{ + { + Tool: sarif.Tool{ + Driver: &sarif.ToolComponent{ + FullName: lo.ToPtr("Trivy Vulnerability Scanner"), + Name: "Trivy", + Version: lo.ToPtr(""), + InformationURI: lo.ToPtr("https://github.com/aquasecurity/trivy"), + Rules: []*sarif.ReportingDescriptor{ + { + ID: "alpine-base:GPL-3.0", + Name: lo.ToPtr("License"), + ShortDescription: sarif.NewMultiformatMessageString("GPL-3.0 in alpine-base"), + FullDescription: sarif.NewMultiformatMessageString("GPL-3.0 in alpine-base"), + DefaultConfiguration: sarif.NewReportingConfiguration().WithLevel("error"), + Help: sarif.NewMultiformatMessageString("License GPL-3.0\nClassification: restricted\nPkgName: alpine-base\nPath: "). + WithMarkdown("**License GPL-3.0**\n| PkgName | Classification | Path |\n| --- | --- | --- |\n|alpine-base|restricted||"), + Properties: map[string]interface{}{ + "tags": []interface{}{ + "license", + "security", + "HIGH", + }, + "precision": "very-high", + "security-severity": "8.0", + }, + }, + }, + }, + }, + Results: []*sarif.Result{ + { + RuleID: lo.ToPtr("alpine-base:GPL-3.0"), + RuleIndex: lo.ToPtr(uint(0)), + Level: lo.ToPtr("error"), + Message: sarif.Message{Text: lo.ToPtr("Artifact: OS Packages\nLicense GPL-3.0\nPkgName: restricted\n Classification: alpine-base\n Path: ")}, + Locations: []*sarif.Location{ + { + Message: sarif.NewTextMessage(""), + PhysicalLocation: &sarif.PhysicalLocation{ + ArtifactLocation: &sarif.ArtifactLocation{ + URI: lo.ToPtr("OS Packages"), + URIBaseId: lo.ToPtr("ROOTPATH"), + }, + Region: &sarif.Region{ + StartLine: lo.ToPtr(1), + EndLine: lo.ToPtr(1), + StartColumn: lo.ToPtr(1), + EndColumn: lo.ToPtr(1), + }, + }, + }, + }, + }, + }, + ColumnKind: "utf16CodeUnits", + OriginalUriBaseIDs: map[string]*sarif.ArtifactLocation{ + "ROOTPATH": { + URI: lo.ToPtr("file:///"), + }, + }, + }, + }, + }, + }, + { + name: "no vulns", + want: &sarif.Report{ + Version: "2.1.0", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Runs: []*sarif.Run{ + { + Tool: sarif.Tool{ + Driver: &sarif.ToolComponent{ + FullName: lo.ToPtr("Trivy Vulnerability Scanner"), + Name: "Trivy", + Version: lo.ToPtr(""), + InformationURI: lo.ToPtr("https://github.com/aquasecurity/trivy"), + Rules: []*sarif.ReportingDescriptor{}, + }, + }, + Results: []*sarif.Result{}, + ColumnKind: "utf16CodeUnits", + OriginalUriBaseIDs: map[string]*sarif.ArtifactLocation{ + "ROOTPATH": { + URI: lo.ToPtr("file:///"), + }, + }, + }, + }, + }, + }, + { + name: "ref to github", + input: types.Report{ + Results: types.Results{ + { + Target: "git::https:/github.com/terraform-google-modules/terraform-google-kubernetes-engine?ref=c4809044b52b91505bfba5ef9f25526aa0361788/modules/workload-identity/main.tf", + Class: types.ClassConfig, + Type: ftypes.Terraform, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Terraform Security Check", + ID: "AVD-GCP-0007", + AVDID: "AVD-GCP-0007", + Title: "Service accounts should not have roles assigned with excessive privileges", + Description: "Service accounts should have a minimal set of permissions assigned in order to do their job. They should never have excessive access as if compromised, an attacker can escalate privileges and take over the entire account.", + Message: "Service account is granted a privileged role.", + Query: "data..", + Resolution: "Limit service account access to minimal required set", + Severity: "HIGH", + PrimaryURL: "https://avd.aquasec.com/misconfig/avd-gcp-0007", + References: []string{ + "https://cloud.google.com/iam/docs/understanding-roles", + "https://avd.aquasec.com/misconfig/avd-gcp-0007", + }, + Status: "Fail", + CauseMetadata: ftypes.CauseMetadata{ + StartLine: 91, + EndLine: 91, + Occurrences: []ftypes.Occurrence{ + { + Resource: "google_project_iam_member.workload_identity_sa_bindings[\"roles/storage.admin\"]", + Filename: "git::https:/github.com/terraform-google-modules/terraform-google-kubernetes-engine?ref=c4809044b52b91505bfba5ef9f25526aa0361788/modules/workload-identity/main.tf", + Location: ftypes.Location{ + StartLine: 87, + EndLine: 93, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: &sarif.Report{ + Version: "2.1.0", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Runs: []*sarif.Run{ + { + Tool: *sarif.NewTool( + &sarif.ToolComponent{ + FullName: lo.ToPtr("Trivy Vulnerability Scanner"), + Name: "Trivy", + Version: lo.ToPtr(""), + InformationURI: lo.ToPtr("https://github.com/aquasecurity/trivy"), + Rules: []*sarif.ReportingDescriptor{ + { + ID: "AVD-GCP-0007", + Name: lo.ToPtr("Misconfiguration"), + ShortDescription: sarif.NewMultiformatMessageString("Service accounts should not have roles assigned with excessive privileges"), + FullDescription: sarif.NewMultiformatMessageString("Service accounts should have a minimal set of permissions assigned in order to do their job. They should never have excessive access as if compromised, an attacker can escalate privileges and take over the entire account."), + DefaultConfiguration: &sarif.ReportingConfiguration{ + Level: "error", + }, + HelpURI: lo.ToPtr("https://avd.aquasec.com/misconfig/avd-gcp-0007"), + Help: &sarif.MultiformatMessageString{ + Text: lo.ToPtr("Misconfiguration AVD-GCP-0007\nType: Terraform Security Check\nSeverity: HIGH\nCheck: Service accounts should not have roles assigned with excessive privileges\nMessage: Service account is granted a privileged role.\nLink: [AVD-GCP-0007](https://avd.aquasec.com/misconfig/avd-gcp-0007)\nService accounts should have a minimal set of permissions assigned in order to do their job. They should never have excessive access as if compromised, an attacker can escalate privileges and take over the entire account."), + Markdown: lo.ToPtr("**Misconfiguration AVD-GCP-0007**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Terraform Security Check|HIGH|Service accounts should not have roles assigned with excessive privileges|Service account is granted a privileged role.|[AVD-GCP-0007](https://avd.aquasec.com/misconfig/avd-gcp-0007)|\n\nService accounts should have a minimal set of permissions assigned in order to do their job. They should never have excessive access as if compromised, an attacker can escalate privileges and take over the entire account."), + }, + Properties: sarif.Properties{ + "tags": []interface{}{ + "misconfiguration", + "security", + "HIGH", + }, + "precision": "very-high", + "security-severity": "8.0", + }, + }, + }, + }, + ), + Results: []*sarif.Result{ + { + RuleID: lo.ToPtr("AVD-GCP-0007"), + RuleIndex: lo.ToPtr(uint(0)), + Level: lo.ToPtr("error"), + Message: *sarif.NewTextMessage("Artifact: github.com/terraform-google-modules/terraform-google-kubernetes-engine?ref=c4809044b52b91505bfba5ef9f25526aa0361788/modules/workload-identity/main.tf\nType: terraform\nVulnerability AVD-GCP-0007\nSeverity: HIGH\nMessage: Service account is granted a privileged role.\nLink: [AVD-GCP-0007](https://avd.aquasec.com/misconfig/avd-gcp-0007)"), + Locations: []*sarif.Location{ + { + PhysicalLocation: sarif.NewPhysicalLocation(). + WithArtifactLocation( + &sarif.ArtifactLocation{ + URI: lo.ToPtr("github.com/terraform-google-modules/terraform-google-kubernetes-engine?ref=c4809044b52b91505bfba5ef9f25526aa0361788/modules/workload-identity/main.tf"), + URIBaseId: lo.ToPtr("ROOTPATH"), + }, + ). + WithRegion( + &sarif.Region{ + StartLine: lo.ToPtr(91), + StartColumn: lo.ToPtr(1), + EndLine: lo.ToPtr(91), + EndColumn: lo.ToPtr(1), + }, + ), + Message: sarif.NewTextMessage("github.com/terraform-google-modules/terraform-google-kubernetes-engine?ref=c4809044b52b91505bfba5ef9f25526aa0361788/modules/workload-identity/main.tf"), + }, + }, + }, + }, + ColumnKind: "utf16CodeUnits", + OriginalUriBaseIDs: map[string]*sarif.ArtifactLocation{ + "ROOTPATH": { + URI: lo.ToPtr("file:///"), + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sarifWritten := bytes.NewBuffer(nil) + w := report.SarifWriter{ + Output: sarifWritten, + } + err := w.Write(context.TODO(), tt.input) + assert.NoError(t, err) + + result := &sarif.Report{} + err = json.Unmarshal(sarifWritten.Bytes(), result) + assert.NoError(t, err) + assert.Equal(t, tt.want, result) + }) + } +} + +func TestToPathUri(t *testing.T) { + tests := []struct { + input string + resultClass types.ResultClass + output string + }{ + { + input: "almalinux@sha256:08042694fffd61e6a0b3a22dadba207c8937977915ff6b1879ad744fd6638837", + resultClass: types.ClassOSPkg, + output: "library/almalinux", + }, + { + input: "alpine:latest (alpine 3.13.4)", + resultClass: types.ClassOSPkg, + output: "library/alpine", + }, + { + input: "docker.io/my-organization/my-app:2c6912aee7bde44b84d810aed106ca84f40e2e29", + resultClass: types.ClassOSPkg, + output: "my-organization/my-app", + }, + { + input: "lib/test", + resultClass: types.ClassLangPkg, + output: "lib/test", + }, + { + input: "lib(2)/test", + resultClass: types.ClassSecret, + output: "lib(2)/test", + }, + } + + for _, test := range tests { + got := report.ToPathUri(test.input, test.resultClass) + if got != test.output { + t.Errorf("toPathUri(%q) got %q, wanted %q", test.input, got, test.output) + } + } +} diff --git a/pkg/report/spdx/spdx.go b/pkg/report/spdx/spdx.go new file mode 100644 index 000000000000..a8550ddca0f3 --- /dev/null +++ b/pkg/report/spdx/spdx.go @@ -0,0 +1,65 @@ +package spdx + +import ( + "context" + "encoding/json" + "io" + + "github.com/spdx/tools-golang/spdx/v2/v2_3" + "github.com/spdx/tools-golang/tagvalue" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/sbom/spdx" + "github.com/aquasecurity/trivy/pkg/types" +) + +type Writer struct { + output io.Writer + version string + format types.Format + marshaler *spdx.Marshaler +} + +func NewWriter(output io.Writer, version string, spdxFormat types.Format) Writer { + return Writer{ + output: output, + version: version, + format: spdxFormat, + marshaler: spdx.NewMarshaler(version), + } +} + +func (w Writer) Write(ctx context.Context, report types.Report) error { + spdxDoc, err := w.marshaler.MarshalReport(ctx, report) + if err != nil { + return xerrors.Errorf("failed to marshal spdx: %w", err) + } + + if w.format == "spdx-json" { + if err := writeSPDXJson(spdxDoc, w.output); err != nil { + return xerrors.Errorf("failed to save spdx json: %w", err) + } + } else { + if err := tagvalue.Write(spdxDoc, w.output); err != nil { + return xerrors.Errorf("failed to save spdx tag-value: %w", err) + } + } + + return nil +} + +// writeSPDXJson writes in human-readable format(multiple lines) +// use function from `github.com/spdx/tools-golang` after release https://github.com/spdx/tools-golang/pull/213 +func writeSPDXJson(doc *v2_3.Document, w io.Writer) error { + buf, err := json.MarshalIndent(doc, "", " ") + if err != nil { + return err + } + + _, err = w.Write(buf) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/report/table/licensing.go b/pkg/report/table/licensing.go new file mode 100644 index 000000000000..f758b9876df5 --- /dev/null +++ b/pkg/report/table/licensing.go @@ -0,0 +1,185 @@ +package table + +import ( + "bytes" + "sort" + "strings" + "sync" + + "github.com/fatih/color" + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "github.com/aquasecurity/table" + "github.com/aquasecurity/tml" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +type pkgLicenseRenderer struct { + w *bytes.Buffer + tableWriter *table.Table + result types.Result + isTerminal bool + severities []dbTypes.Severity + once *sync.Once +} + +func NewPkgLicenseRenderer(result types.Result, isTerminal bool, severities []dbTypes.Severity) pkgLicenseRenderer { + buf := bytes.NewBuffer([]byte{}) + return pkgLicenseRenderer{ + w: buf, + tableWriter: newTableWriter(buf, isTerminal), + result: result, + isTerminal: isTerminal, + severities: severities, + once: new(sync.Once), + } +} + +func (r pkgLicenseRenderer) Render() string { + r.setHeaders() + r.setRows() + + total, summaries := summarize(r.severities, r.countSeverities()) + + target := r.result.Target + " (license)" + RenderTarget(r.w, target, r.isTerminal) + r.printf("Total: %d (%s)\n\n", total, strings.Join(summaries, ", ")) + + r.tableWriter.Render() + + return r.w.String() +} + +func (r pkgLicenseRenderer) setHeaders() { + header := []string{"Package", "License", "Classification", "Severity"} + r.tableWriter.SetHeaders(header...) +} + +func (r pkgLicenseRenderer) setRows() { + for _, l := range r.result.Licenses { + var row []string + if r.isTerminal { + row = []string{ + l.PkgName, l.Name, colorizeLicenseCategory(l.Category), ColorizeSeverity(l.Severity, l.Severity), + } + } else { + row = []string{ + l.PkgName, l.Name, string(l.Category), l.Severity, + } + } + r.tableWriter.AddRow(row...) + } +} + +func (r pkgLicenseRenderer) countSeverities() map[string]int { + severityCount := make(map[string]int) + for _, l := range r.result.Licenses { + severityCount[l.Severity]++ + } + return severityCount +} + +func (r *pkgLicenseRenderer) printf(format string, args ...interface{}) { + // nolint + _ = tml.Fprintf(r.w, format, args...) +} + +type fileLicenseRenderer struct { + w *bytes.Buffer + tableWriter *table.Table + result types.Result + isTerminal bool + severities []dbTypes.Severity + once *sync.Once +} + +func NewFileLicenseRenderer(result types.Result, isTerminal bool, severities []dbTypes.Severity) fileLicenseRenderer { + buf := bytes.NewBuffer([]byte{}) + return fileLicenseRenderer{ + w: buf, + tableWriter: newTableWriter(buf, isTerminal), + result: result, + isTerminal: isTerminal, + severities: severities, + once: new(sync.Once), + } +} + +func (r fileLicenseRenderer) Render() string { + r.setHeaders() + r.setRows() + + total, summaries := summarize(r.severities, r.countSeverities()) + + target := r.result.Target + " (license)" + RenderTarget(r.w, target, r.isTerminal) + r.printf("Total: %d (%s)\n\n", total, strings.Join(summaries, ", ")) + + r.tableWriter.Render() + + return r.w.String() +} + +func (r fileLicenseRenderer) setHeaders() { + header := []string{"Classification", "Severity", "License", "File Location"} + r.tableWriter.SetHeaders(header...) +} + +func (r fileLicenseRenderer) setRows() { + sort.Slice(r.result.Licenses, func(i, j int) bool { + a := r.result.Licenses[i] + b := r.result.Licenses[j] + if a.Severity != b.Severity { + return 0 < dbTypes.CompareSeverityString(b.Severity, a.Severity) + } + if a.Category != b.Category { + return a.Category > b.Category + } + if a.Name != b.Name { + return a.Name < b.Name + } + return a.FilePath < b.FilePath + }) + + for _, l := range r.result.Licenses { + var row []string + if r.isTerminal { + row = []string{ + colorizeLicenseCategory(l.Category), ColorizeSeverity(l.Severity, l.Severity), l.Name, l.FilePath, + } + } else { + row = []string{ + string(l.Category), l.Severity, l.Name, l.FilePath, + } + } + r.tableWriter.AddRow(row...) + } +} + +func (r fileLicenseRenderer) countSeverities() map[string]int { + severityCount := make(map[string]int) + for _, l := range r.result.Licenses { + severityCount[l.Severity]++ + } + return severityCount +} + +func (r *fileLicenseRenderer) printf(format string, args ...interface{}) { + // nolint + _ = tml.Fprintf(r.w, format, args...) +} + +func colorizeLicenseCategory(category ftypes.LicenseCategory) string { + switch category { + case ftypes.CategoryForbidden: + return color.New(color.FgRed).Sprintf("Forbidden") + case ftypes.CategoryRestricted: + return color.New(color.FgHiRed).Sprintf("Restricted") + case ftypes.CategoryUnknown: + return color.New(color.FgCyan).Sprintf("Non Standard") + } + return cases.Title(language.AmericanEnglish).String(string(category)) +} diff --git a/pkg/report/table/misconfig.go b/pkg/report/table/misconfig.go new file mode 100644 index 000000000000..3f9d345c93ea --- /dev/null +++ b/pkg/report/table/misconfig.go @@ -0,0 +1,234 @@ +package table + +import ( + "bytes" + "fmt" + "strings" + + "github.com/fatih/color" + "golang.org/x/term" + + "github.com/aquasecurity/tml" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +const ( + severityCritical = "CRITICAL" + severityHigh = "HIGH" + severityMedium = "MEDIUM" + severityLow = "LOW" +) + +type misconfigRenderer struct { + w *bytes.Buffer + result types.Result + severities []dbTypes.Severity + trace bool + includeNonFailures bool + width int + ansi bool +} + +func NewMisconfigRenderer(result types.Result, severities []dbTypes.Severity, trace, includeNonFailures, ansi bool) *misconfigRenderer { + width, _, err := term.GetSize(0) + if err != nil || width == 0 { + width = 40 + } + if !ansi { + tml.DisableFormatting() + } + return &misconfigRenderer{ + w: bytes.NewBuffer([]byte{}), + result: result, + severities: severities, + trace: trace, + includeNonFailures: includeNonFailures, + width: width, + ansi: ansi, + } +} + +func (r *misconfigRenderer) Render() string { + target := fmt.Sprintf("%s (%s)", r.result.Target, r.result.Type) + RenderTarget(r.w, target, r.ansi) + + total, summaries := summarize(r.severities, r.countSeverities()) + + summary := r.result.MisconfSummary + r.printf("Tests: %d (SUCCESSES: %d, FAILURES: %d, EXCEPTIONS: %d)\n", + summary.Successes+summary.Failures+summary.Exceptions, summary.Successes, summary.Failures, summary.Exceptions) + r.printf("Failures: %d (%s)\n\n", total, strings.Join(summaries, ", ")) + + for _, m := range r.result.Misconfigurations { + r.renderSingle(m) + } + + // For debugging + if r.trace { + r.outputTrace() + } + return r.w.String() +} + +func (r *misconfigRenderer) countSeverities() map[string]int { + severityCount := make(map[string]int) + for _, misconf := range r.result.Misconfigurations { + if misconf.Status == types.MisconfStatusFailure { + severityCount[misconf.Severity]++ + } + } + return severityCount +} + +func (r *misconfigRenderer) printf(format string, args ...interface{}) { + // nolint + _ = tml.Fprintf(r.w, format, args...) +} + +func (r *misconfigRenderer) println(input string) { + // nolint + tml.Fprintln(r.w, input) +} + +func (r *misconfigRenderer) printDoubleDivider() { + r.printf("%s\r\n", strings.Repeat("â•", r.width)) +} + +func (r *misconfigRenderer) printSingleDivider() { + r.printf("%s\r\n", strings.Repeat("─", r.width)) +} + +func (r *misconfigRenderer) renderSingle(misconf types.DetectedMisconfiguration) { + r.renderSummary(misconf) + r.renderCode(misconf) + r.printf("\r\n\r\n") +} + +func (r *misconfigRenderer) renderSummary(misconf types.DetectedMisconfiguration) { + + // show pass/fail/exception unless we are only showing failures + if r.includeNonFailures { + switch misconf.Status { + case types.MisconfStatusPassed: + r.printf("%s: ", misconf.Status) + case types.MisconfStatusFailure: + r.printf("%s: ", misconf.Status) + case types.MisconfStatusException: + r.printf("%s: ", misconf.Status) + } + } + + // severity + switch misconf.Severity { + case severityCritical: + r.printf("%s: ", misconf.Severity) + case severityHigh: + r.printf("%s: ", misconf.Severity) + case severityMedium: + r.printf("%s: ", misconf.Severity) + case severityLow: + r.printf("%s: ", misconf.Severity) + default: + r.printf("%s: ", misconf.Severity) + } + + // heading + r.printf("%s\r\n", misconf.Message) + r.printDoubleDivider() + + // description + r.printf("%s\r\n", misconf.Description) + + // show link if we have one + if misconf.PrimaryURL != "" { + r.printf("\r\nSee %s\r\n", misconf.PrimaryURL) + } + + r.printSingleDivider() +} + +func (r *misconfigRenderer) renderCode(misconf types.DetectedMisconfiguration) { + // highlight code if we can... + if lines := misconf.CauseMetadata.Code.Lines; len(lines) > 0 { + + var lineInfo string + if misconf.CauseMetadata.StartLine > 0 { + lineInfo = tml.Sprintf(":%d", misconf.CauseMetadata.StartLine) + if misconf.CauseMetadata.EndLine > misconf.CauseMetadata.StartLine { + lineInfo = tml.Sprintf("%s-%d", lineInfo, misconf.CauseMetadata.EndLine) + } + } + r.printf(" %s%s\r\n", r.result.Target, lineInfo) + for i, occ := range misconf.CauseMetadata.Occurrences { + lineInfo := fmt.Sprintf("%d-%d", occ.Location.StartLine, occ.Location.EndLine) + if occ.Location.StartLine >= occ.Location.EndLine { + lineInfo = fmt.Sprintf("%d", occ.Location.StartLine) + } + + r.printf( + " %svia %s:%s (%s)\n", + strings.Repeat(" ", i+2), + occ.Filename, + lineInfo, + occ.Resource, + ) + } + + r.printSingleDivider() + for i, line := range lines { + switch { + case line.Truncated: + r.printf("%4s ", strings.Repeat(".", len(fmt.Sprintf("%d", line.Number)))) + case line.IsCause: + r.printf("%4d ", line.Number) + switch { + case (line.FirstCause && line.LastCause) || len(lines) == 1: + r.printf("[ ") + case line.FirstCause || i == 0: + r.printf("┌ ") + case line.LastCause || i == len(lines)-1: + r.printf("â”” ") + default: + r.printf("│ ") + } + default: + r.printf("%4d ", line.Number) + } + + if r.ansi { + r.printf("%s\r\n", line.Highlighted) + } else { + r.printf("%s\r\n", line.Content) + } + } + r.printSingleDivider() + } +} + +func (r *misconfigRenderer) outputTrace() { + blue := color.New(color.FgBlue).SprintFunc() + green := color.New(color.FgGreen).SprintfFunc() + red := color.New(color.FgRed).SprintfFunc() + + for _, misconf := range r.result.Misconfigurations { + if len(misconf.Traces) == 0 { + continue + } + + c := green + if misconf.Status == types.MisconfStatusFailure { + c = red + } + + r.println(c("\nID: %s", misconf.ID)) + r.println(c("File: %s", r.result.Target)) + r.println(c("Namespace: %s", misconf.Namespace)) + r.println(c("Query: %s", misconf.Query)) + r.println(c("Message: %s", misconf.Message)) + for _, t := range misconf.Traces { + r.println(blue("TRACE ") + t) + } + r.println("") + } +} diff --git a/pkg/report/table/misconfig_test.go b/pkg/report/table/misconfig_test.go new file mode 100644 index 000000000000..a57399f0e253 --- /dev/null +++ b/pkg/report/table/misconfig_test.go @@ -0,0 +1,350 @@ +package table_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestMisconfigRenderer(t *testing.T) { + + tests := []struct { + name string + input types.Result + includeNonFailures bool + want string + }{ + { + name: "single result", + input: types.Result{ + Target: "my-file", + MisconfSummary: &types.MisconfSummary{Successes: 0, Failures: 1, Exceptions: 0}, + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "AVD-XYZ-0123", + Title: "Config file is bad", + Description: "Your config file is not good.", + Message: "Oh no, a bad config.", + Severity: "HIGH", + PrimaryURL: "https://google.com/search?q=bad%20config", + Status: "FAIL", + }, + }, + }, + includeNonFailures: false, + want: ` +my-file () +========== +Tests: 1 (SUCCESSES: 0, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0) + +HIGH: Oh no, a bad config. +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Your config file is not good. + +See https://google.com/search?q=bad%20config +──────────────────────────────────────── + + +`, + }, + { + name: "single result with code", + input: types.Result{ + Target: "my-file", + MisconfSummary: &types.MisconfSummary{Successes: 0, Failures: 1, Exceptions: 0}, + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "AVD-XYZ-0123", + Title: "Config file is bad", + Description: "Your config file is not good.", + Message: "Oh no, a bad config.", + Severity: "HIGH", + PrimaryURL: "https://google.com/search?q=bad%20config", + Status: "FAIL", + CauseMetadata: ftypes.CauseMetadata{ + Resource: "", + Provider: "", + Service: "", + StartLine: 0, + EndLine: 0, + Code: ftypes.Code{ + Lines: []ftypes.Line{ + { + Number: 1, + }, + { + Number: 2, + Content: "bad: true", + Highlighted: "\x1b[37mbad:\x1b[0m true", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + }, + }, + }, + }, + }, + }, + }, + includeNonFailures: false, + want: ` +my-file () +========== +Tests: 1 (SUCCESSES: 0, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0) + +HIGH: Oh no, a bad config. +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Your config file is not good. + +See https://google.com/search?q=bad%20config +──────────────────────────────────────── + my-file +──────────────────────────────────────── + 1 + 2 [ bad: true + 3 +──────────────────────────────────────── + + +`, + }, + { + name: "multiple results", + input: types.Result{ + Target: "my-file", + MisconfSummary: &types.MisconfSummary{Successes: 1, Failures: 1, Exceptions: 0}, + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "AVD-XYZ-0123", + Title: "Config file is bad", + Description: "Your config file is not good.", + Message: "Oh no, a bad config.", + Severity: "HIGH", + PrimaryURL: "https://google.com/search?q=bad%20config", + Status: "FAIL", + CauseMetadata: ftypes.CauseMetadata{ + StartLine: 2, + EndLine: 2, + Code: ftypes.Code{ + Lines: []ftypes.Line{ + { + Number: 1, + }, + { + Number: 2, + Content: "bad: true", + Highlighted: "\x1b[37mbad:\x1b[0m true", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + }, + }, + }, + }, + }, + { + ID: "AVD-XYZ-0456", + Title: "Config file is bad again", + Description: "Your config file is still not good.", + Message: "Oh no, a bad config AGAIN.", + Severity: "MEDIUM", + PrimaryURL: "https://google.com/search?q=bad%20config", + Status: "PASS", + }, + }, + }, + includeNonFailures: true, + want: ` +my-file () +========== +Tests: 2 (SUCCESSES: 1, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0) + +FAIL: HIGH: Oh no, a bad config. +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Your config file is not good. + +See https://google.com/search?q=bad%20config +──────────────────────────────────────── + my-file:2 +──────────────────────────────────────── + 1 + 2 [ bad: true + 3 +──────────────────────────────────────── + + +PASS: MEDIUM: Oh no, a bad config AGAIN. +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Your config file is still not good. + +See https://google.com/search?q=bad%20config +──────────────────────────────────────── + + +`, + }, + { + name: "resource name in report", + input: types.Result{ + Target: "terraform-aws-modules/security-group/aws/main.tf", + Class: types.ClassConfig, + Type: "terraform", + MisconfSummary: &types.MisconfSummary{ + Successes: 5, + Failures: 1, + Exceptions: 0, + }, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Terraform Security Check", + ID: "AVD-AWS-0107", + AVDID: "AVS-AWS-0107", + Title: "An ingress security group rule allows traffic from /0", + Description: "Opening up ports to the public internet is generally to be avoided. You should restrict access to IP addresses or ranges that explicitly require it where possible.", + Message: "Security group rule allows ingress from public internet.", + Query: "data..", + Resolution: "Set a more restrictive cidr range", + Severity: "CRITICAL", + PrimaryURL: "https://avd.aquasec.com/misconfig/avd-aws-0107", + References: []string{ + "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html", + "https://avd.aquasec.com/misconfig/avd-aws-0107", + }, + Status: "FAIL", + CauseMetadata: ftypes.CauseMetadata{ + Resource: "module.aws-security-groups[\"db1\"]", + Provider: "AWS", + Service: "ec2", + StartLine: 197, + EndLine: 204, + Code: ftypes.Code{ + Lines: []ftypes.Line{ + { + Number: 191, + Content: "resource \"aws_security_group_rule\" \"ingress_with_cidr_blocks\" {", + IsCause: false, + }, + { + Number: 192, + Truncated: true, + }, + { + Number: 197, + Truncated: true, + }, + { + Number: 198, + Content: " \",\",", + IsCause: true, + }, + { + Number: 199, + Content: " lookup(", + IsCause: true, + }, + { + Number: 200, + Content: " var.ingress_with_cidr_blocks[count.index],", + IsCause: true, + }, + { + Number: 201, + Content: " \"cidr_blocks\",", + IsCause: true, + }, + { + Number: 202, + Content: " join(\",\", var.ingress_cidr_blocks),", + IsCause: true, + }, + { + Number: 203, + Content: " ),", + IsCause: true, + LastCause: true, + }, + { + Number: 204, + Truncated: true, + }, + }, + }, + Occurrences: []ftypes.Occurrence{ + { + Resource: "aws_security_group_rule.ingress_with_cidr_blocks[0]", + Filename: "terraform-aws-modules/security-group/aws/main.tf", + Location: ftypes.Location{ + StartLine: 191, + EndLine: 227, + }, + }, + { + Resource: "module.aws-security-groups[\"db1\"]", + Filename: "sg.tf", + Location: ftypes.Location{ + StartLine: 1, + EndLine: 13, + }, + }, + }, + }, + }, + }, + }, + want: ` +terraform-aws-modules/security-group/aws/main.tf (terraform) +============================================================ +Tests: 6 (SUCCESSES: 5, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1) + +CRITICAL: Security group rule allows ingress from public internet. +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +Opening up ports to the public internet is generally to be avoided. You should restrict access to IP addresses or ranges that explicitly require it where possible. + +See https://avd.aquasec.com/misconfig/avd-aws-0107 +──────────────────────────────────────── + terraform-aws-modules/security-group/aws/main.tf:197-204 + via terraform-aws-modules/security-group/aws/main.tf:191-227 (aws_security_group_rule.ingress_with_cidr_blocks[0]) + via sg.tf:1-13 (module.aws-security-groups["db1"]) +──────────────────────────────────────── + 191 resource "aws_security_group_rule" "ingress_with_cidr_blocks" { + ... + ... + 198 │ ",", + 199 │ lookup( + 200 │ var.ingress_with_cidr_blocks[count.index], + 201 │ "cidr_blocks", + 202 │ join(",", var.ingress_cidr_blocks), + 203 â”” ), + ... +──────────────────────────────────────── + + +`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + severities := []dbTypes.Severity{dbTypes.SeverityLow, dbTypes.SeverityMedium, dbTypes.SeverityHigh, + dbTypes.SeverityCritical} + renderer := table.NewMisconfigRenderer(test.input, severities, false, test.includeNonFailures, false) + assert.Equal(t, test.want, strings.ReplaceAll(renderer.Render(), "\r\n", "\n")) + }) + } +} diff --git a/pkg/report/table/secret.go b/pkg/report/table/secret.go new file mode 100644 index 000000000000..1a647a4f55fb --- /dev/null +++ b/pkg/report/table/secret.go @@ -0,0 +1,163 @@ +package table + +import ( + "bytes" + "fmt" + "strings" + + "golang.org/x/term" + + "github.com/aquasecurity/tml" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +type secretRenderer struct { + w *bytes.Buffer + target string + secrets []types.DetectedSecret + severities []dbTypes.Severity + width int + ansi bool +} + +func NewSecretRenderer(target string, secrets []types.DetectedSecret, ansi bool, severities []dbTypes.Severity) *secretRenderer { + width, _, err := term.GetSize(0) + if err != nil || width == 0 { + width = 40 + } + if !ansi { + tml.DisableFormatting() + } + return &secretRenderer{ + w: bytes.NewBuffer([]byte{}), + target: target, + secrets: secrets, + severities: severities, + width: width, + ansi: ansi, + } +} + +func (r *secretRenderer) Render() string { + target := r.target + " (secrets)" + RenderTarget(r.w, target, r.ansi) + + severityCount := r.countSeverities() + total, summaries := summarize(r.severities, severityCount) + + r.printf("Total: %d (%s)\n\n", total, strings.Join(summaries, ", ")) + + for _, m := range r.secrets { + r.renderSingle(m) + } + return r.w.String() +} + +func (r *secretRenderer) countSeverities() map[string]int { + severityCount := make(map[string]int) + for _, secret := range r.secrets { + severity := secret.Severity + severityCount[severity]++ + } + return severityCount +} + +func (r *secretRenderer) printf(format string, args ...interface{}) { + // nolint + _ = tml.Fprintf(r.w, format, args...) +} + +func (r *secretRenderer) printDoubleDivider() { + r.printf("%s\r\n", strings.Repeat("â•", r.width)) +} + +func (r *secretRenderer) printSingleDivider() { + r.printf("%s\r\n", strings.Repeat("─", r.width)) +} + +func (r *secretRenderer) renderSingle(secret types.DetectedSecret) { + r.renderSummary(secret) + r.renderCode(secret) + r.printf("\r\n\r\n") +} + +func (r *secretRenderer) renderSummary(secret types.DetectedSecret) { + + // severity + switch secret.Severity { + case severityCritical: + r.printf("%s: ", secret.Severity) + case severityHigh: + r.printf("%s: ", secret.Severity) + case severityMedium: + r.printf("%s: ", secret.Severity) + case severityLow: + r.printf("%s: ", secret.Severity) + default: + r.printf("%s: ", secret.Severity) + } + + // heading + r.printf("%s (%s)\r\n", secret.Category, secret.RuleID) + r.printDoubleDivider() + + // description + r.printf("%s\r\n", secret.Title) + + r.printSingleDivider() +} + +func (r *secretRenderer) renderCode(secret types.DetectedSecret) { + // highlight code if we can... + if lines := secret.Code.Lines; len(lines) > 0 { + + var lineInfo string + if secret.StartLine > 0 { + lineInfo = tml.Sprintf(":%d", secret.StartLine) + if secret.EndLine > secret.StartLine { + lineInfo = tml.Sprintf("%s-%d", lineInfo, secret.EndLine) + } + } + + var note string + if c := secret.Layer.CreatedBy; c != "" { + if len(c) > 40 { + // Too long + c = c[:40] + } + note = fmt.Sprintf(" (added by '%s')", c) + } else if secret.Layer.DiffID != "" { + note = fmt.Sprintf(" (added in layer '%s')", strings.TrimPrefix(secret.Layer.DiffID, "sha256:")[:12]) + } + r.printf(" %s%s%s\r\n", r.target, lineInfo, note) + r.printSingleDivider() + + for i, line := range lines { + switch { + case line.Truncated: + r.printf("%4s ", strings.Repeat(".", len(fmt.Sprintf("%d", line.Number)))) + case line.IsCause: + r.printf("%4d ", line.Number) + switch { + case (line.FirstCause && line.LastCause) || len(lines) == 1: + r.printf("[ ") + case line.FirstCause || i == 0: + r.printf("┌ ") + case line.LastCause || i == len(lines)-1: + r.printf("â”” ") + default: + r.printf("│ ") + } + default: + r.printf("%4d ", line.Number) + } + if r.ansi { + r.printf("%s\r\n", line.Highlighted) + } else { + r.printf("%s\r\n", line.Content) + } + } + r.printSingleDivider() + } +} diff --git a/pkg/report/table/secret_test.go b/pkg/report/table/secret_test.go new file mode 100644 index 000000000000..92011f8d17ff --- /dev/null +++ b/pkg/report/table/secret_test.go @@ -0,0 +1,146 @@ +package table_test + +import ( + "github.com/aquasecurity/trivy/pkg/types" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/report/table" +) + +func TestSecretRenderer(t *testing.T) { + + tests := []struct { + name string + input []types.DetectedSecret + want string + }{ + { + name: "single line", + input: []types.DetectedSecret{ + { + RuleID: "rule-id", + Category: ftypes.SecretRuleCategory("category"), + Title: "this is a title", + Severity: "HIGH", + Layer: ftypes.Layer{DiffID: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203"}, + StartLine: 1, + EndLine: 1, + Code: ftypes.Code{ + Lines: []ftypes.Line{ + { + Number: 1, + Content: "password=secret", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + Match: "secret", + }, + }, + want: ` +my-file (secrets) +================= +Total: 1 (MEDIUM: 0, HIGH: 1) + +HIGH: category (rule-id) +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +this is a title +──────────────────────────────────────── + my-file:1 (added in layer 'beee9f30bc1f') +──────────────────────────────────────── + 1 [ password=secret +──────────────────────────────────────── + + +`, + }, + { + name: "multiple line", + input: []types.DetectedSecret{ + { + RuleID: "rule-id", + Category: ftypes.SecretRuleCategory("category"), + Title: "this is a title", + Severity: "HIGH", + Layer: ftypes.Layer{ + DiffID: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + CreatedBy: "COPY my-file my-file", + }, + StartLine: 3, + EndLine: 4, + Code: ftypes.Code{ + Lines: []ftypes.Line{ + { + Number: 1, + Content: "#!/bin/bash", + }, + { + Number: 2, + Content: "", + }, + { + Number: 3, + Content: "password=this is a \\", + IsCause: true, + FirstCause: true, + }, + { + Number: 4, + Content: "secret password", + IsCause: true, + LastCause: true, + }, + { + Number: 5, + Content: "some-app --password $password", + }, + { + Number: 6, + Content: "echo all done", + }, + }, + }, + Match: "secret", + }, + }, + want: ` +my-file (secrets) +================= +Total: 1 (MEDIUM: 0, HIGH: 1) + +HIGH: category (rule-id) +â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +this is a title +──────────────────────────────────────── + my-file:3-4 (added by 'COPY my-file my-file') +──────────────────────────────────────── + 1 #!/bin/bash + 2 + 3 ┌ password=this is a \ + 4 â”” secret password + 5 some-app --password $password + 6 echo all done +──────────────────────────────────────── + + +`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + renderer := table.NewSecretRenderer("my-file", test.input, false, []dbTypes.Severity{ + dbTypes.SeverityHigh, + dbTypes.SeverityMedium, + }) + assert.Equal(t, test.want, strings.ReplaceAll(renderer.Render(), "\r\n", "\n")) + }) + } +} diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go new file mode 100644 index 000000000000..f636b71f9560 --- /dev/null +++ b/pkg/report/table/table.go @@ -0,0 +1,167 @@ +package table + +import ( + "context" + "fmt" + "io" + "os" + "runtime" + "strings" + + "github.com/fatih/color" + "golang.org/x/exp/slices" + + "github.com/aquasecurity/table" + "github.com/aquasecurity/tml" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + SeverityColor = []func(a ...interface{}) string{ + color.New(color.FgCyan).SprintFunc(), // UNKNOWN + color.New(color.FgBlue).SprintFunc(), // LOW + color.New(color.FgYellow).SprintFunc(), // MEDIUM + color.New(color.FgHiRed).SprintFunc(), // HIGH + color.New(color.FgRed).SprintFunc(), // CRITICAL + } +) + +// Writer implements Writer and output in tabular form +type Writer struct { + Severities []dbTypes.Severity + Output io.Writer + + // Show dependency origin tree + Tree bool + + // Show suppressed findings + ShowSuppressed bool + + // For misconfigurations + IncludeNonFailures bool + Trace bool + + // For licenses + LicenseRiskThreshold int + IgnoredLicenses []string +} + +type Renderer interface { + Render() string +} + +// Write writes the result on standard output +func (tw Writer) Write(_ context.Context, report types.Report) error { + + for _, result := range report.Results { + // Not display a table of custom resources + if result.Class == types.ClassCustom { + continue + } + tw.write(result) + } + return nil +} + +func (tw Writer) write(result types.Result) { + if result.IsEmpty() && result.Class != types.ClassOSPkg { + return + } + + var renderer Renderer + switch { + // vulnerability + case result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg: + renderer = NewVulnerabilityRenderer(result, tw.isOutputToTerminal(), tw.Tree, tw.ShowSuppressed, tw.Severities) + // misconfiguration + case result.Class == types.ClassConfig: + renderer = NewMisconfigRenderer(result, tw.Severities, tw.Trace, tw.IncludeNonFailures, tw.isOutputToTerminal()) + // secret + case result.Class == types.ClassSecret: + renderer = NewSecretRenderer(result.Target, result.Secrets, tw.isOutputToTerminal(), tw.Severities) + // package license + case result.Class == types.ClassLicense: + renderer = NewPkgLicenseRenderer(result, tw.isOutputToTerminal(), tw.Severities) + // file license + case result.Class == types.ClassLicenseFile: + renderer = NewFileLicenseRenderer(result, tw.isOutputToTerminal(), tw.Severities) + default: + return + } + + _, _ = fmt.Fprint(tw.Output, renderer.Render()) +} + +func (tw Writer) isOutputToTerminal() bool { + return IsOutputToTerminal(tw.Output) +} + +func newTableWriter(output io.Writer, isTerminal bool) *table.Table { + tableWriter := table.New(output) + if isTerminal { // use ansi output if we're not piping elsewhere + tableWriter.SetHeaderStyle(table.StyleBold) + tableWriter.SetLineStyle(table.StyleDim) + } + tableWriter.SetBorders(true) + tableWriter.SetAutoMerge(true) + tableWriter.SetRowLines(true) + + return tableWriter +} + +func summarize(specifiedSeverities []dbTypes.Severity, severityCount map[string]int) (int, []string) { + var total int + var severities []string + for _, sev := range specifiedSeverities { + severities = append(severities, sev.String()) + } + + var summaries []string + for _, severity := range dbTypes.SeverityNames { + if !slices.Contains(severities, severity) { + continue + } + count := severityCount[severity] + r := fmt.Sprintf("%s: %d", severity, count) + summaries = append(summaries, r) + total += count + } + + return total, summaries +} + +func IsOutputToTerminal(output io.Writer) bool { + if runtime.GOOS == "windows" { + // if its windows, we don't support formatting + return false + } + + if output != os.Stdout { + return false + } + o, err := os.Stdout.Stat() + if err != nil { + return false + } + return (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice +} + +func RenderTarget(w io.Writer, target string, isTerminal bool) { + if isTerminal { + // nolint + _ = tml.Fprintf(w, "\n%s\n\n", target) + } else { + _, _ = fmt.Fprintf(w, "\n%s\n", target) + _, _ = fmt.Fprintf(w, "%s\n", strings.Repeat("=", len(target))) + } +} + +func ColorizeSeverity(value, severity string) string { + for i, name := range dbTypes.SeverityNames { + if severity == name { + return SeverityColor[i](value) + } + } + return color.New(color.FgBlue).SprintFunc()(severity) +} diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go new file mode 100644 index 000000000000..6b357a6d08dc --- /dev/null +++ b/pkg/report/table/table_test.go @@ -0,0 +1,92 @@ +package table_test + +import ( + "bytes" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestWriter_Write(t *testing.T) { + testCases := []struct { + name string + results types.Results + expectedOutput string + includeNonFailures bool + }{ + { + name: "vulnerability and custom resource", + results: types.Results{ + { + Target: "test", + Class: types.ClassLangPkg, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Status: dbTypes.StatusWillNotFix, + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + }, + }, + }, + CustomResources: []ftypes.CustomResource{ + { + Type: "test", + Data: "test", + }, + }, + }, + }, + expectedOutput: ` +test () +======= +Total: 1 (MEDIUM: 0, HIGH: 1) + +┌─────────┬───────────────┬──────────┬──────────────┬───────────────────┬───────────────┬───────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼───────────────┼──────────┼──────────────┼───────────────────┼───────────────┼───────────────────────────────────────────┤ +│ foo │ CVE-2020-0001 │ HIGH │ will_not_fix │ 1.2.3 │ │ foobar │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-0001 │ +└─────────┴───────────────┴──────────┴──────────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘ +`, + }, + { + name: "no vulns", + results: types.Results{ + { + Target: "test", + Class: types.ClassLangPkg, + }, + }, + expectedOutput: ``, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tableWritten := bytes.Buffer{} + writer := table.Writer{ + Output: &tableWritten, + Tree: true, + IncludeNonFailures: tc.includeNonFailures, + Severities: []dbTypes.Severity{ + dbTypes.SeverityHigh, + dbTypes.SeverityMedium, + }, + } + err := writer.Write(nil, types.Report{Results: tc.results}) + assert.NoError(t, err) + assert.Equal(t, tc.expectedOutput, tableWritten.String(), tc.name) + }) + } +} diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go new file mode 100644 index 000000000000..bdfa9bf1af1d --- /dev/null +++ b/pkg/report/table/vulnerability.go @@ -0,0 +1,355 @@ +package table + +import ( + "bytes" + "fmt" + "path/filepath" + "sort" + "strings" + "sync" + + "github.com/samber/lo" + "github.com/xlab/treeprint" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + + "github.com/aquasecurity/table" + "github.com/aquasecurity/tml" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +var showSuppressedOnce = sync.OnceFunc(func() { + log.Logger.Info(`Some vulnerabilities have been ignored/suppressed. Use the "--show-suppressed" flag to display them.`) +}) + +type vulnerabilityRenderer struct { + w *bytes.Buffer + result types.Result + isTerminal bool + tree bool // Show dependency tree + showSuppressed bool // Show suppressed vulnerabilities + severities []dbTypes.Severity + once *sync.Once +} + +func NewVulnerabilityRenderer(result types.Result, isTerminal, tree, suppressed bool, severities []dbTypes.Severity) *vulnerabilityRenderer { + buf := bytes.NewBuffer([]byte{}) + if !isTerminal { + tml.DisableFormatting() + } + return &vulnerabilityRenderer{ + w: buf, + result: result, + isTerminal: isTerminal, + tree: tree, + showSuppressed: suppressed, + severities: severities, + once: new(sync.Once), + } +} + +func (r *vulnerabilityRenderer) Render() string { + r.renderDetectedVulnerabilities() + + if r.tree { + r.renderDependencyTree() + } + + if r.showSuppressed { + r.renderModifiedVulnerabilities() + } else if len(r.result.ModifiedFindings) > 0 { + showSuppressedOnce() + } + + return r.w.String() +} + +func (r *vulnerabilityRenderer) renderDetectedVulnerabilities() { + tw := newTableWriter(r.w, r.isTerminal) + r.setHeaders(tw) + r.setVulnerabilityRows(tw, r.result.Vulnerabilities) + + severityCount := r.countSeverities(r.result.Vulnerabilities) + total, summaries := summarize(r.severities, severityCount) + + target := r.result.Target + if r.result.Class == types.ClassLangPkg { + target += fmt.Sprintf(" (%s)", r.result.Type) + } + RenderTarget(r.w, target, r.isTerminal) + r.printf("Total: %d (%s)\n\n", total, strings.Join(summaries, ", ")) + + tw.Render() +} + +func (r *vulnerabilityRenderer) setHeaders(tw *table.Table) { + if len(r.result.Vulnerabilities) == 0 { + return + } + header := []string{ + "Library", + "Vulnerability", + "Severity", + "Status", + "Installed Version", + "Fixed Version", + "Title", + } + tw.SetHeaders(header...) +} + +func (r *vulnerabilityRenderer) setVulnerabilityRows(tw *table.Table, vulns []types.DetectedVulnerability) { + for _, v := range vulns { + lib := v.PkgName + if v.PkgPath != "" { + // get path to root jar + // for other languages return unchanged path + pkgPath := rootJarFromPath(v.PkgPath) + fileName := filepath.Base(pkgPath) + lib = fmt.Sprintf("%s (%s)", v.PkgName, fileName) + r.once.Do(func() { + log.Logger.Infof("Table result includes only package filenames. Use '--format json' option to get the full path to the package file.") + }) + } + + title := v.Title + if title == "" { + title = v.Description + } + splitTitle := strings.Split(title, " ") + if len(splitTitle) >= 12 { + title = strings.Join(splitTitle[:12], " ") + "..." + } + + if len(v.PrimaryURL) > 0 { + if r.isTerminal { + title = tml.Sprintf("%s\n%s", title, v.PrimaryURL) + } else { + title = fmt.Sprintf("%s\n%s", title, v.PrimaryURL) + } + } + + var row []string + if r.isTerminal { + row = []string{ + lib, + v.VulnerabilityID, + ColorizeSeverity(v.Severity, v.Severity), + v.Status.String(), + v.InstalledVersion, + v.FixedVersion, + strings.TrimSpace(title), + } + } else { + row = []string{ + lib, + v.VulnerabilityID, + v.Severity, + v.Status.String(), + v.InstalledVersion, + v.FixedVersion, + strings.TrimSpace(title), + } + } + + tw.AddRow(row...) + } +} + +func (r *vulnerabilityRenderer) countSeverities(vulns []types.DetectedVulnerability) map[string]int { + severityCount := make(map[string]int) + for _, v := range vulns { + severityCount[v.Severity]++ + } + return severityCount +} + +func (r *vulnerabilityRenderer) renderModifiedVulnerabilities() { + tw := newTableWriter(r.w, r.isTerminal) + header := []string{ + "Library", + "Vulnerability", + "Severity", + "Status", + "Statement", + "Source", + } + tw.SetHeaders(header...) + + var total int + for _, m := range r.result.ModifiedFindings { + if m.Type != types.FindingTypeVulnerability { + continue + } + vuln := m.Finding.(types.DetectedVulnerability) + total++ + + stmt := lo.Ternary(m.Statement != "", m.Statement, "N/A") + tw.AddRow(vuln.PkgName, vuln.VulnerabilityID, vuln.Severity, string(m.Status), stmt, m.Source) + } + + if total == 0 { + return + } + + title := fmt.Sprintf("Suppressed Vulnerabilities (Total: %d)", total) + if r.isTerminal { + // nolint + _ = tml.Fprintf(r.w, "\n%s\n\n", title) + } else { + _, _ = fmt.Fprintf(r.w, "\n%s\n", title) + _, _ = fmt.Fprintf(r.w, "%s\n", strings.Repeat("=", len(title))) + } + + tw.Render() +} + +func (r *vulnerabilityRenderer) renderDependencyTree() { + // Get parents of each dependency + parents := ftypes.Packages(r.result.Packages).ParentDeps() + if len(parents) == 0 { + return + } + ancestors := traverseAncestors(r.result.Packages, parents) + + root := treeprint.NewWithRoot(fmt.Sprintf(` +Dependency Origin Tree (Reversed) +================================= +%s`, r.result.Target)) + + // This count is next to the package ID. + // e.g. node-fetch@1.7.3 (MEDIUM: 2, HIGH: 1, CRITICAL: 3) + pkgSeverityCount := make(map[string]map[string]int) + for _, vuln := range r.result.Vulnerabilities { + cnts, ok := pkgSeverityCount[vuln.PkgID] + if !ok { + cnts = make(map[string]int) + } + + cnts[vuln.Severity]++ + pkgSeverityCount[vuln.PkgID] = cnts + } + + // Extract vulnerable packages + vulnPkgs := lo.Filter(r.result.Packages, func(pkg ftypes.Package, _ int) bool { + return lo.ContainsBy(r.result.Vulnerabilities, func(vuln types.DetectedVulnerability) bool { + return pkg.ID == vuln.PkgID + }) + }) + + // Render tree + for _, vulnPkg := range vulnPkgs { + _, summaries := summarize(r.severities, pkgSeverityCount[vulnPkg.ID]) + topLvlID := tml.Sprintf("%s, (%s)", vulnPkg.ID, strings.Join(summaries, ", ")) + + branch := root.AddBranch(topLvlID) + addParents(branch, vulnPkg, parents, ancestors, map[string]struct{}{vulnPkg.ID: {}}, 1) + + } + r.printf(root.String()) +} + +func (r *vulnerabilityRenderer) printf(format string, args ...interface{}) { + // nolint + _ = tml.Fprintf(r.w, format, args...) +} + +func addParents(topItem treeprint.Tree, pkg ftypes.Package, parentMap map[string]ftypes.Packages, ancestors map[string][]string, + seen map[string]struct{}, depth int) { + if !pkg.Indirect { + return + } + + roots := make(map[string]struct{}) + for _, parent := range parentMap[pkg.ID] { + if _, ok := seen[parent.ID]; ok { + continue + } + seen[parent.ID] = struct{}{} // to avoid infinite loops + + if depth == 1 && !parent.Indirect { + topItem.AddBranch(parent.ID) + } else { + // We omit intermediate dependencies and show only direct dependencies + // as this could make the dependency tree huge. + for _, ancestor := range ancestors[parent.ID] { + roots[ancestor] = struct{}{} + } + } + } + + // Omitted + rootIDs := lo.Filter(maps.Keys(roots), func(pkgID string, _ int) bool { + _, ok := seen[pkgID] + return !ok + }) + sort.Strings(rootIDs) + if len(rootIDs) > 0 { + branch := topItem.AddBranch("...(omitted)...") + for _, rootID := range rootIDs { + branch.AddBranch(rootID) + } + } +} + +func traverseAncestors(pkgs []ftypes.Package, parentMap map[string]ftypes.Packages) map[string][]string { + ancestors := make(map[string][]string) + for _, pkg := range pkgs { + ancestors[pkg.ID] = findAncestor(pkg.ID, parentMap, make(map[string]struct{})) + } + return ancestors +} + +func findAncestor(pkgID string, parentMap map[string]ftypes.Packages, seen map[string]struct{}) []string { + ancestors := make(map[string]struct{}) + seen[pkgID] = struct{}{} + for _, parent := range parentMap[pkgID] { + if _, ok := seen[parent.ID]; ok { + continue + } + switch { + case !parent.Indirect: + ancestors[parent.ID] = struct{}{} + case len(parentMap[parent.ID]) == 0: + // Direct dependencies cannot be identified in some package managers like "package-lock.json" v1, + // then the "Indirect" field can be always true. We try to guess direct dependencies in this case. + // A dependency with no parents must be a direct dependency. + // + // e.g. + // -> styled-components + // -> fbjs + // -> isomorphic-fetch + // -> node-fetch + // + // Even if `styled-components` is not marked as a direct dependency, it must be a direct dependency + // as it has no parents. Note that it doesn't mean `fbjs` is an indirect dependency. + ancestors[parent.ID] = struct{}{} + default: + for _, a := range findAncestor(parent.ID, parentMap, seen) { + ancestors[a] = struct{}{} + } + } + } + return maps.Keys(ancestors) +} + +var jarExtensions = []string{ + ".jar", + ".war", + ".par", + ".ear", +} + +func rootJarFromPath(path string) string { + // File paths are always forward-slashed in Trivy + paths := strings.Split(path, "/") + for i, p := range paths { + if slices.Contains(jarExtensions, filepath.Ext(p)) { + return strings.Join(paths[:i+1], "/") + } + } + return path +} diff --git a/pkg/report/table/vulnerability_test.go b/pkg/report/table/vulnerability_test.go new file mode 100644 index 000000000000..d941edc796f1 --- /dev/null +++ b/pkg/report/table/vulnerability_test.go @@ -0,0 +1,410 @@ +package table_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" +) + +func Test_vulnerabilityRenderer_Render(t *testing.T) { + tests := []struct { + name string + result types.Result + want string + includeNonFailures bool + showSuppressed bool + }{ + { + name: "happy path full", + result: types.Result{ + Target: "test", + Class: types.ClassLangPkg, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Status: dbTypes.StatusWillNotFix, + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + }, + }, + }, + // It won't be shown as `showSuppressed` is false. + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Source: ".trivyignore", + Finding: types.DetectedVulnerability{ + VulnerabilityID: "CVE-2020-0002", + }, + }, + }, + }, + want: ` +test () +======= +Total: 1 (MEDIUM: 0, HIGH: 1) + +┌─────────┬───────────────┬──────────┬──────────────┬───────────────────┬───────────────┬───────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼───────────────┼──────────┼──────────────┼───────────────────┼───────────────┼───────────────────────────────────────────┤ +│ foo │ CVE-2020-0001 │ HIGH │ will_not_fix │ 1.2.3 │ │ foobar │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-0001 │ +└─────────┴───────────────┴──────────┴──────────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘ +`, + }, + { + name: "happy path with filePath in result", + result: types.Result{ + Target: "test", + Class: types.ClassLangPkg, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + PkgPath: "foo/bar", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Status: dbTypes.StatusFixed, + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + }, + }, + }, + }, + want: ` +test () +======= +Total: 1 (MEDIUM: 0, HIGH: 1) + +┌───────────┬───────────────┬──────────┬────────┬───────────────────┬───────────────┬───────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├───────────┼───────────────┼──────────┼────────┼───────────────────┼───────────────┼───────────────────────────────────────────┤ +│ foo (bar) │ CVE-2020-0001 │ HIGH │ fixed │ 1.2.3 │ 3.4.5 │ foobar │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-0001 │ +└───────────┴───────────────┴──────────┴────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘ +`, + }, + { + name: "no title for vuln and missing primary link", + result: types.Result{ + Target: "test", + Class: types.ClassLangPkg, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + Status: dbTypes.StatusFixed, + Vulnerability: dbTypes.Vulnerability{ + Description: "foobar", + Severity: "HIGH", + }, + }, + }, + }, + want: ` +test () +======= +Total: 1 (MEDIUM: 0, HIGH: 1) + +┌─────────┬───────────────┬──────────┬────────┬───────────────────┬───────────────┬────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼───────────────┼──────────┼────────┼───────────────────┼───────────────┼────────┤ +│ foo │ CVE-2020-0001 │ HIGH │ fixed │ 1.2.3 │ 3.4.5 │ foobar │ +└─────────┴───────────────┴──────────┴────────┴───────────────────┴───────────────┴────────┘ +`, + }, + { + name: "long title for vuln", + result: types.Result{ + Target: "test", + Class: types.ClassLangPkg, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-1234", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-1234", + Status: dbTypes.StatusFixed, + Vulnerability: dbTypes.Vulnerability{ + Title: "a b c d e f g h i j k l m n o p q r s t u v", + Description: "foobar", + Severity: "HIGH", + }, + }, + }, + }, + want: ` +test () +======= +Total: 1 (MEDIUM: 0, HIGH: 1) + +┌─────────┬───────────────┬──────────┬────────┬───────────────────┬───────────────┬───────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼───────────────┼──────────┼────────┼───────────────────┼───────────────┼───────────────────────────────────────────┤ +│ foo │ CVE-2020-1234 │ HIGH │ fixed │ 1.2.3 │ 3.4.5 │ a b c d e f g h i j k l... │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-1234 │ +└─────────┴───────────────┴──────────┴────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘ +`, + }, + { + name: "happy path with vulnerability origin graph with direct dependency info", + result: types.Result{ + Target: "package-lock.json", + Class: types.ClassLangPkg, + Type: "npm", + Packages: []ftypes.Package{ + { + ID: "node-fetch@1.7.3", + Name: "node-fetch", + Version: "1.7.3", + Indirect: true, + }, + { + ID: "isomorphic-fetch@2.2.1", + Name: "isomorphic-fetch", + Version: "2.2.1", + Indirect: true, + DependsOn: []string{ + "node-fetch@1.7.3", + }, + }, + { + ID: "fbjs@0.8.18", + Name: "fbjs", + Version: "0.8.18", + Indirect: true, + DependsOn: []string{ + "isomorphic-fetch@2.2.1", + }, + }, + { + ID: "sanitize-html@1.20.0", + Name: "sanitize-html", + Version: "1.20.0", + }, + { + ID: "styled-components@3.1.3", + Name: "styled-components", + Version: "3.1.3", + DependsOn: []string{ + "fbjs@0.8.18", + }, + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-0235", + PkgID: "node-fetch@1.7.3", + PkgName: "node-fetch", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + }, + InstalledVersion: "1.7.3", + FixedVersion: "2.6.7, 3.1.1", + Status: dbTypes.StatusFixed, + }, + { + VulnerabilityID: "CVE-2021-26539", + PkgID: "sanitize-html@1.20.0", + PkgName: "sanitize-html", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "MEDIUM", + }, + InstalledVersion: "1.20.0", + FixedVersion: "2.3.1", + Status: dbTypes.StatusFixed, + }, + }, + }, + want: ` +package-lock.json (npm) +======================= +Total: 2 (MEDIUM: 1, HIGH: 1) + +┌───────────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┬────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├───────────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┼────────┤ +│ node-fetch │ CVE-2022-0235 │ HIGH │ fixed │ 1.7.3 │ 2.6.7, 3.1.1 │ foobar │ +├───────────────┼────────────────┼──────────┤ ├───────────────────┼───────────────┤ │ +│ sanitize-html │ CVE-2021-26539 │ MEDIUM │ │ 1.20.0 │ 2.3.1 │ │ +└───────────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────┴────────┘ + +Dependency Origin Tree (Reversed) +================================= +package-lock.json +├── node-fetch@1.7.3, (MEDIUM: 0, HIGH: 1) +│ └── ...(omitted)... +│ └── styled-components@3.1.3 +└── sanitize-html@1.20.0, (MEDIUM: 1, HIGH: 0) +`, + }, + { + name: "happy path with vulnerability origin graph without direct dependency info", + result: types.Result{ + Target: "package-lock.json", + Class: types.ClassLangPkg, + Type: "npm", + Packages: []ftypes.Package{ + { + ID: "node-fetch@1.7.3", + Name: "node-fetch", + Version: "1.7.3", + Indirect: true, + }, + { + ID: "isomorphic-fetch@2.2.1", + Name: "isomorphic-fetch", + Version: "2.2.1", + Indirect: true, + DependsOn: []string{ + "node-fetch@1.7.3", + }, + }, + { + ID: "fbjs@0.8.18", + Name: "fbjs", + Version: "0.8.18", + Indirect: true, + DependsOn: []string{ + "isomorphic-fetch@2.2.1", + }, + }, + { + ID: "styled-components@3.1.3", + Name: "styled-components", + Version: "3.1.3", + Indirect: true, + DependsOn: []string{ + "fbjs@0.8.18", + }, + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-0235", + PkgID: "node-fetch@1.7.3", + PkgName: "node-fetch", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + }, + InstalledVersion: "1.7.3", + FixedVersion: "2.6.7, 3.1.1", + Status: dbTypes.StatusFixed, + }, + }, + }, + want: ` +package-lock.json (npm) +======================= +Total: 1 (MEDIUM: 0, HIGH: 1) + +┌────────────┬───────────────┬──────────┬────────┬───────────────────┬───────────────┬────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├────────────┼───────────────┼──────────┼────────┼───────────────────┼───────────────┼────────┤ +│ node-fetch │ CVE-2022-0235 │ HIGH │ fixed │ 1.7.3 │ 2.6.7, 3.1.1 │ foobar │ +└────────────┴───────────────┴──────────┴────────┴───────────────────┴───────────────┴────────┘ + +Dependency Origin Tree (Reversed) +================================= +package-lock.json +└── node-fetch@1.7.3, (MEDIUM: 0, HIGH: 1) + └── ...(omitted)... + └── styled-components@3.1.3 +`, + }, + { + name: "show suppressed vulnerabilities", + result: types.Result{ + Target: "test", + Class: types.ClassLangPkg, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Status: dbTypes.StatusWillNotFix, + Vulnerability: dbTypes.Vulnerability{ + Title: "title1", + Description: "desc1", + Severity: "HIGH", + }, + }, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Statement: "Not exploitable", + Source: ".trivyignore.yaml", + Finding: types.DetectedVulnerability{ + VulnerabilityID: "CVE-2020-0002", + PkgName: "bar", + InstalledVersion: "1.2.3", + Status: dbTypes.StatusWillNotFix, + Vulnerability: dbTypes.Vulnerability{ + Title: "title2", + Description: "desc2", + Severity: "MEDIUM", + }, + }, + }, + }, + }, + showSuppressed: true, + want: ` +test () +======= +Total: 1 (MEDIUM: 0, HIGH: 1) + +┌─────────┬───────────────┬──────────┬──────────────┬───────────────────┬───────────────┬───────────────────────────────────────────┠+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼───────────────┼──────────┼──────────────┼───────────────────┼───────────────┼───────────────────────────────────────────┤ +│ foo │ CVE-2020-0001 │ HIGH │ will_not_fix │ 1.2.3 │ │ title1 │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-0001 │ +└─────────┴───────────────┴──────────┴──────────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘ + +Suppressed Vulnerabilities (Total: 1) +===================================== +┌─────────┬───────────────┬──────────┬─────────┬─────────────────┬───────────────────┠+│ Library │ Vulnerability │ Severity │ Status │ Statement │ Source │ +├─────────┼───────────────┼──────────┼─────────┼─────────────────┼───────────────────┤ +│ bar │ CVE-2020-0002 │ MEDIUM │ ignored │ Not exploitable │ .trivyignore.yaml │ +└─────────┴───────────────┴──────────┴─────────┴─────────────────┴───────────────────┘ +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := table.NewVulnerabilityRenderer(tt.result, false, true, tt.showSuppressed, []dbTypes.Severity{ + dbTypes.SeverityHigh, + dbTypes.SeverityMedium, + }) + assert.Equal(t, tt.want, r.Render(), tt.name) + }) + } +} diff --git a/pkg/report/template.go b/pkg/report/template.go new file mode 100644 index 000000000000..7a28a65cfbb2 --- /dev/null +++ b/pkg/report/template.go @@ -0,0 +1,84 @@ +package report + +import ( + "bytes" + "context" + "encoding/xml" + "html" + "io" + "os" + "strings" + "text/template" + + "github.com/Masterminds/sprig/v3" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +// CustomTemplateFuncMap is used to overwrite existing functions for testing. +var CustomTemplateFuncMap = make(map[string]interface{}) + +// TemplateWriter write result in custom format defined by user's template +type TemplateWriter struct { + Output io.Writer + Template *template.Template +} + +// NewTemplateWriter is the factory method to return TemplateWriter object +func NewTemplateWriter(output io.Writer, outputTemplate, appVersion string) (*TemplateWriter, error) { + if strings.HasPrefix(outputTemplate, "@") { + buf, err := os.ReadFile(strings.TrimPrefix(outputTemplate, "@")) + if err != nil { + return nil, xerrors.Errorf("error retrieving template from path: %w", err) + } + outputTemplate = string(buf) + } + var templateFuncMap template.FuncMap = sprig.GenericFuncMap() + templateFuncMap["escapeXML"] = func(input string) string { + escaped := &bytes.Buffer{} + if err := xml.EscapeText(escaped, []byte(input)); err != nil { + log.Logger.Error("error while escapeString to XML: %s", err) + return input + } + return escaped.String() + } + templateFuncMap["endWithPeriod"] = func(input string) string { + if !strings.HasSuffix(input, ".") { + input += "." + } + return input + } + templateFuncMap["escapeString"] = html.EscapeString + templateFuncMap["sourceID"] = func(input string) dbTypes.SourceID { + return dbTypes.SourceID(input) + } + templateFuncMap["appVersion"] = func() string { + return appVersion + } + + // Overwrite functions + for k, v := range CustomTemplateFuncMap { + templateFuncMap[k] = v + } + + tmpl, err := template.New("output template").Funcs(templateFuncMap).Parse(outputTemplate) + if err != nil { + return nil, xerrors.Errorf("error parsing template: %w", err) + } + return &TemplateWriter{ + Output: output, + Template: tmpl, + }, nil +} + +// Write writes result +func (tw TemplateWriter) Write(ctx context.Context, report types.Report) error { + err := tw.Template.Execute(tw.Output, report.Results) + if err != nil { + return xerrors.Errorf("failed to write with template: %w", err) + } + return nil +} diff --git a/pkg/report/template_test.go b/pkg/report/template_test.go new file mode 100644 index 000000000000..4422e2c0e136 --- /dev/null +++ b/pkg/report/template_test.go @@ -0,0 +1,190 @@ +package report_test + +import ( + "bytes" + "context" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestReportWriter_Template(t *testing.T) { + testCases := []struct { + name string + detectedVulns []types.DetectedVulnerability + template string + expected string + }{ + { + name: "happy path", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "foo", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{ + "nvd": 1, + }, + }, + }, + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "bar", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0001", + PkgName: "baz", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + }, + template: "{{ range . }}{{ range .Vulnerabilities}}{{ println .VulnerabilityID .Severity }}{{ end }}{{ end }}", + expected: "CVE-2019-0000 HIGH\nCVE-2019-0000 HIGH\nCVE-2019-0001 CRITICAL\n", + }, + { + name: "happy path", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "123", + PkgName: `foo \ test`, + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + Vulnerability: dbTypes.Vulnerability{ + Title: `gcc: POWER9 "DARN" RNG intrinsic produces repeated output`, + Description: `curl version curl \X 7.20.0 to and including curl 7.59.0 contains a CWE-126: Buffer Over-read vulnerability in denial of service that can result in curl can be tricked into reading data beyond the end of a heap based buffer used to store downloaded RTSP content.. This vulnerability appears to have been fixed in curl < 7.20.0 and curl >= 7.60.0.`, + Severity: "HIGH", + }, + }, + }, + template: ` +{{- range . -}} +{{- $failures := len .Vulnerabilities }} + + {{- if not (eq .Type "") }} + + + + {{- end -}} + {{ range .Vulnerabilities }} + + {{ escapeXML .Description }} + + {{- end }} + +{{- end }} +`, + + expected: ` + + + + + + curl version curl \X 7.20.0 to and including curl 7.59.0 contains a CWE-126: Buffer Over-read vulnerability in denial of service that can result in curl can be tricked into reading data beyond the end of a heap based buffer used to store downloaded RTSP content.. This vulnerability appears to have been fixed in curl < 7.20.0 and curl >= 7.60.0. + + +`, + }, + { + name: "happy path with/without period description should return with period", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "foo", + Vulnerability: dbTypes.Vulnerability{ + Description: "without period", + }, + }, + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "bar", + Vulnerability: dbTypes.Vulnerability{ + Description: "with period.", + }, + }, + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "bar", + Vulnerability: dbTypes.Vulnerability{ + Description: `with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close().`, + }, + }, + }, + template: `{{ range . }}{{ range .Vulnerabilities}}{{.VulnerabilityID}} {{ endWithPeriod (escapeString .Description) | printf "%q" }}{{ end }}{{ end }}`, + expected: `CVE-2019-0000 "without period."CVE-2019-0000 "with period."CVE-2019-0000 "with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close()."`, + }, + { + name: "Calculate using sprig", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "foo", + Vulnerability: dbTypes.Vulnerability{ + Description: "without period", + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "bar", + Vulnerability: dbTypes.Vulnerability{ + Description: "with period.", + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "bar", + Vulnerability: dbTypes.Vulnerability{ + Description: `with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close().`, + Severity: dbTypes.SeverityHigh.String(), + }, + }, + }, + template: `{{ $high := 0 }}{{ $critical := 0 }}{{ range . }}{{ range .Vulnerabilities}}{{ if eq .Severity "HIGH" }}{{ $high = add $high 1 }}{{ end }}{{ if eq .Severity "CRITICAL" }}{{ $critical = add $critical 1 }}{{ end }}{{ end }}Critical: {{ $critical }}, High: {{ $high }}{{ end }}`, + expected: `Critical: 2, High: 1`, + }, + { + name: "happy path: env var parsing", + detectedVulns: []types.DetectedVulnerability{}, + template: `{{ lower (env "AWS_ACCOUNT_ID") }}`, + expected: `123456789012`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := clock.With(context.Background(), time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC)) + + os.Setenv("AWS_ACCOUNT_ID", "123456789012") + got := bytes.Buffer{} + inputReport := types.Report{ + Results: types.Results{ + { + Target: "foojunit", + Type: "test", + Vulnerabilities: tc.detectedVulns, + }, + }, + } + + w, err := report.NewTemplateWriter(&got, tc.template, "dev") + require.NoError(t, err) + err = w.Write(ctx, inputReport) + assert.NoError(t, err) + assert.Equal(t, tc.expected, got.String()) + }) + } +} diff --git a/pkg/report/testdata/new.json.golden b/pkg/report/testdata/new.json.golden new file mode 100644 index 000000000000..dd8f29506a63 --- /dev/null +++ b/pkg/report/testdata/new.json.golden @@ -0,0 +1,27 @@ +{ + "ImageID": "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + "RepoTags": [ + "test:latest" + ], + "RepoDigests": [ + "test@sha256:0bd0e9e03a022c3b0226667621da84fc9bf562a9056130424b5bfbd8bcb0397f" + ], + "Results": [ + { + "Target": "foojson", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-0001", + "PkgName": "foo", + "InstalledVersion": "1.2.3", + "FixedVersion": "3.4.5", + "Layer": {}, + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-0001", + "Title": "foobar", + "Description": "baz", + "Severity": "HIGH" + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/report/testdata/old.json.golden b/pkg/report/testdata/old.json.golden new file mode 100644 index 000000000000..78c2f6760f63 --- /dev/null +++ b/pkg/report/testdata/old.json.golden @@ -0,0 +1,18 @@ +[ + { + "Target": "foojson", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-0001", + "PkgName": "foo", + "InstalledVersion": "1.2.3", + "FixedVersion": "3.4.5", + "Layer": {}, + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-0001", + "Title": "foobar", + "Description": "baz", + "Severity": "HIGH" + } + ] + } +] \ No newline at end of file diff --git a/pkg/report/writer.go b/pkg/report/writer.go index 7b5797cc6841..274688591151 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -1,96 +1,122 @@ package report import ( - "encoding/json" - "fmt" + "context" + "errors" "io" "strings" "golang.org/x/xerrors" - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "github.com/knqyf263/trivy/pkg/types" - "github.com/olekukonko/tablewriter" + cr "github.com/aquasecurity/trivy/pkg/compliance/report" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/report/cyclonedx" + "github.com/aquasecurity/trivy/pkg/report/github" + "github.com/aquasecurity/trivy/pkg/report/predicate" + "github.com/aquasecurity/trivy/pkg/report/spdx" + "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" ) -type Results []Result - -type Result struct { - FileName string `json:"file"` - Vulnerabilities []types.Vulnerability -} - -type Writer interface { - Write(Results) error -} - -type TableWriter struct { - Output io.Writer -} +const ( + SchemaVersion = 2 +) -func (tw TableWriter) Write(results Results) error { - for _, result := range results { - tw.write(result) +// Write writes the result to output, format as passed in argument +func Write(ctx context.Context, report types.Report, option flag.Options) (err error) { + output, cleanup, err := option.OutputWriter(ctx) + if err != nil { + return xerrors.Errorf("failed to create a file: %w", err) } - return nil -} -func (tw TableWriter) write(result Result) { - table := tablewriter.NewWriter(tw.Output) - table.SetHeader([]string{"Library", "Vulnerability ID", "Severity", "Installed Version", "Fixed Version", "Title"}) + defer func() { + if cerr := cleanup(); cerr != nil { + err = errors.Join(err, cerr) + } + }() - severityCount := map[string]int{} - for _, v := range result.Vulnerabilities { - severityCount[v.Severity]++ + // Compliance report + if option.Compliance.Spec.ID != "" { + return complianceWrite(ctx, report, option, output) + } - title := v.Title - if title == "" { - title = v.Description + var writer Writer + switch option.Format { + case types.FormatTable: + writer = &table.Writer{ + Output: output, + Severities: option.Severities, + Tree: option.DependencyTree, + ShowSuppressed: option.ShowSuppressed, + IncludeNonFailures: option.IncludeNonFailures, + Trace: option.Trace, + LicenseRiskThreshold: option.LicenseRiskThreshold, + IgnoredLicenses: option.IgnoredLicenses, } - splittedTitle := strings.Split(title, " ") - if len(splittedTitle) >= 12 { - title = strings.Join(splittedTitle[:12], " ") + "..." + case types.FormatJSON: + writer = &JSONWriter{Output: output} + case types.FormatGitHub: + writer = &github.Writer{ + Output: output, + Version: option.AppVersion, } - table.Append([]string{v.PkgName, v.VulnerabilityID, vulnerability.ColorizeSeverity(v.Severity), - v.InstalledVersion, v.FixedVersion, title}) - } - - var results []string - for _, severity := range vulnerability.SeverityNames { - r := fmt.Sprintf("%s: %d", severity, severityCount[severity]) - results = append(results, r) + case types.FormatCycloneDX: + // TODO: support xml format option with cyclonedx writer + writer = cyclonedx.NewWriter(output, option.AppVersion) + case types.FormatSPDX, types.FormatSPDXJSON: + writer = spdx.NewWriter(output, option.AppVersion, option.Format) + case types.FormatTemplate: + // We keep `sarif.tpl` template working for backward compatibility for a while. + if strings.HasPrefix(option.Template, "@") && strings.HasSuffix(option.Template, "sarif.tpl") { + log.Logger.Warn("Using `--template sarif.tpl` is deprecated. Please migrate to `--format sarif`. See https://github.com/aquasecurity/trivy/discussions/1571") + writer = &SarifWriter{ + Output: output, + Version: option.AppVersion, + } + break + } + var err error + if writer, err = NewTemplateWriter(output, option.Template, option.AppVersion); err != nil { + return xerrors.Errorf("failed to initialize template writer: %w", err) + } + case types.FormatSarif: + target := "" + if report.ArtifactType == ftypes.ArtifactFilesystem { + target = option.Target + } + writer = &SarifWriter{ + Output: output, + Version: option.AppVersion, + Target: target, + } + case types.FormatCosignVuln: + writer = predicate.NewVulnWriter(output, option.AppVersion) + default: + return xerrors.Errorf("unknown format: %v", option.Format) } - fmt.Printf("\n%s\n", result.FileName) - fmt.Println(strings.Repeat("=", len(result.FileName))) - fmt.Printf("Total: %d (%s)\n\n", len(result.Vulnerabilities), strings.Join(results, ", ")) - - if len(result.Vulnerabilities) == 0 { - return + if err = writer.Write(ctx, report); err != nil { + return xerrors.Errorf("failed to write results: %w", err) } - table.SetAutoMergeCells(true) - table.SetRowLine(true) - table.Render() - return -} - -type JsonWriter struct { - Output io.Writer + return nil } -func (jw JsonWriter) Write(results Results) error { - out := map[string][]types.Vulnerability{} - for _, result := range results { - out[result.FileName] = result.Vulnerabilities - } - output, err := json.MarshalIndent(out, "", " ") +func complianceWrite(ctx context.Context, report types.Report, opt flag.Options, output io.Writer) error { + complianceReport, err := cr.BuildComplianceReport([]types.Results{report.Results}, opt.Compliance) if err != nil { - return xerrors.Errorf("failed to marshal json: %w", err) + return xerrors.Errorf("compliance report build error: %w", err) } + return cr.Write(ctx, complianceReport, cr.Option{ + Format: opt.Format, + Report: opt.ReportFormat, + Output: output, + Severities: opt.Severities, + }) +} - if _, err = fmt.Fprint(jw.Output, string(output)); err != nil { - return xerrors.Errorf("failed to write json: %w", err) - } - return nil +// Writer defines the result write operation +type Writer interface { + Write(context.Context, types.Report) error } diff --git a/pkg/report/writer_internal_test.go b/pkg/report/writer_internal_test.go new file mode 100644 index 000000000000..28a916d81549 --- /dev/null +++ b/pkg/report/writer_internal_test.go @@ -0,0 +1,44 @@ +package report + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReportWriter_toSarifErrorLevel(t *testing.T) { + tests := []struct { + severity string + sarifErrorLevel string + }{ + { + severity: "CRITICAL", + sarifErrorLevel: "error", + }, + { + severity: "HIGH", + sarifErrorLevel: "error", + }, + { + severity: "MEDIUM", + sarifErrorLevel: "warning", + }, + { + severity: "LOW", + sarifErrorLevel: "note", + }, + { + severity: "UNKNOWN", + sarifErrorLevel: "note", + }, + { + severity: "OTHER", + sarifErrorLevel: "none", + }, + } + for _, tc := range tests { + t.Run(tc.severity, func(t *testing.T) { + assert.Equal(t, tc.sarifErrorLevel, toSarifErrorLevel(tc.severity), tc.severity) + }) + } +} diff --git a/pkg/report/writer_test.go b/pkg/report/writer_test.go new file mode 100644 index 000000000000..e0e4274cb221 --- /dev/null +++ b/pkg/report/writer_test.go @@ -0,0 +1,84 @@ +package report_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestResults_Failed(t *testing.T) { + tests := []struct { + name string + results types.Results + want bool + }{ + { + name: "no vulnerabilities and misconfigurations", + results: types.Results{ + { + Target: "test", + Type: "test", + }, + }, + want: false, + }, + { + name: "vulnerabilities found", + results: types.Results{ + { + Target: "test", + Type: "test", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2021-0001", + PkgName: "test", + }, + }, + }, + }, + want: true, + }, + { + name: "failed misconfigurations", + results: types.Results{ + { + Target: "test", + Type: "test", + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Docker Security Check", + ID: "ID-001", + Status: types.MisconfStatusFailure, + }, + }, + }, + }, + want: true, + }, + { + name: "passed misconfigurations", + results: types.Results{ + { + Target: "test", + Type: "test", + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Docker Security Check", + ID: "ID-001", + Status: types.MisconfStatusPassed, + }, + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.results.Failed() + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/result/filter.go b/pkg/result/filter.go new file mode 100644 index 000000000000..dc92d8aff5ec --- /dev/null +++ b/pkg/result/filter.go @@ -0,0 +1,348 @@ +package result + +import ( + "context" + "fmt" + "os" + "path/filepath" + "sort" + + "github.com/open-policy-agent/opa/rego" + "github.com/samber/lo" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/sbom/core" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/vex" +) + +const ( + // DefaultIgnoreFile is the file name to be evaluated + DefaultIgnoreFile = ".trivyignore" +) + +type FilterOption struct { + Severities []dbTypes.Severity + IgnoreStatuses []dbTypes.Status + IncludeNonFailures bool + IgnoreFile string + PolicyFile string + IgnoreLicenses []string + VEXPath string +} + +// Filter filters out the report +func Filter(ctx context.Context, report types.Report, opt FilterOption) error { + ignoreConf, err := ParseIgnoreFile(ctx, opt.IgnoreFile) + if err != nil { + return xerrors.Errorf("%s error: %w", opt.IgnoreFile, err) + } + + for i := range report.Results { + if err = FilterResult(ctx, &report.Results[i], ignoreConf, opt); err != nil { + return xerrors.Errorf("unable to filter vulnerabilities: %w", err) + } + } + + // Filter out vulnerabilities based on the given VEX document. + if err = filterByVEX(report, opt); err != nil { + return xerrors.Errorf("VEX error: %w", err) + } + + return nil +} + +// FilterResult filters out the result +func FilterResult(ctx context.Context, result *types.Result, ignoreConf IgnoreConfig, opt FilterOption) error { + // Convert dbTypes.Severity to string + severities := lo.Map(opt.Severities, func(s dbTypes.Severity, _ int) string { + return s.String() + }) + + filterVulnerabilities(result, severities, opt.IgnoreStatuses, ignoreConf) + filterMisconfigurations(result, severities, opt.IncludeNonFailures, ignoreConf) + filterSecrets(result, severities, ignoreConf) + filterLicenses(result, severities, opt.IgnoreLicenses, ignoreConf) + + if opt.PolicyFile != "" { + if err := applyPolicy(ctx, result, opt.PolicyFile); err != nil { + return xerrors.Errorf("failed to apply the policy: %w", err) + } + } + sort.Sort(types.BySeverity(result.Vulnerabilities)) + + return nil +} + +// filterByVEX determines whether a detected vulnerability should be filtered out based on the provided VEX document. +// If the VEX document is not nil and the vulnerability is either not affected or fixed according to the VEX statement, +// the vulnerability is filtered out. +func filterByVEX(report types.Report, opt FilterOption) error { + vexDoc, err := vex.New(opt.VEXPath, report) + if err != nil { + return err + } else if vexDoc == nil { + return nil + } + + bom, err := sbomio.NewEncoder(core.Options{}).Encode(report) + if err != nil { + return xerrors.Errorf("unable to encode the SBOM: %w", err) + } + + for i, result := range report.Results { + if len(result.Vulnerabilities) == 0 { + continue + } + vexDoc.Filter(&report.Results[i], bom) + } + return nil +} + +func filterVulnerabilities(result *types.Result, severities []string, ignoreStatuses []dbTypes.Status, ignoreConfig IgnoreConfig) { + uniqVulns := make(map[string]types.DetectedVulnerability) + for _, vuln := range result.Vulnerabilities { + if vuln.Severity == "" { + vuln.Severity = dbTypes.SeverityUnknown.String() + } + + switch { + // Filter by severity + case !slices.Contains(severities, vuln.Severity): + continue + // Filter by status + case slices.Contains(ignoreStatuses, vuln.Status): + continue + } + + // Filter by ignore file + if f := ignoreConfig.MatchVulnerability(vuln.VulnerabilityID, result.Target, vuln.PkgPath, vuln.PkgIdentifier.PURL); f != nil { + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(vuln, types.FindingStatusIgnored, f.Statement, ignoreConfig.FilePath)) + continue + } + + // Check if there is a duplicate vulnerability + key := fmt.Sprintf("%s/%s/%s/%s", vuln.VulnerabilityID, vuln.PkgName, vuln.InstalledVersion, vuln.PkgPath) + if old, ok := uniqVulns[key]; ok && !shouldOverwrite(old, vuln) { + continue + } + uniqVulns[key] = vuln + } + + // Override the detected vulnerabilities + result.Vulnerabilities = maps.Values(uniqVulns) + if len(result.Vulnerabilities) == 0 { + result.Vulnerabilities = nil + } +} + +func filterMisconfigurations(result *types.Result, severities []string, includeNonFailures bool, + ignoreConfig IgnoreConfig) { + var filtered []types.DetectedMisconfiguration + result.MisconfSummary = new(types.MisconfSummary) + + for _, misconf := range result.Misconfigurations { + // Filter by severity + if !slices.Contains(severities, misconf.Severity) { + continue + } + + // Filter by ignore file + if f := ignoreConfig.MatchMisconfiguration(misconf.ID, misconf.AVDID, result.Target); f != nil { + result.MisconfSummary.Exceptions++ + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(misconf, types.FindingStatusIgnored, f.Statement, ignoreConfig.FilePath)) + continue + } + + // Count successes, failures, and exceptions + summarize(misconf.Status, result.MisconfSummary) + + if misconf.Status != types.MisconfStatusFailure && !includeNonFailures { + continue + } + filtered = append(filtered, misconf) + } + + result.Misconfigurations = filtered + if result.MisconfSummary.Empty() { + result.Misconfigurations = nil + result.MisconfSummary = nil + } +} + +func filterSecrets(result *types.Result, severities []string, ignoreConfig IgnoreConfig) { + var filtered []types.DetectedSecret + for _, secret := range result.Secrets { + if !slices.Contains(severities, secret.Severity) { + // Filter by severity + continue + } else if f := ignoreConfig.MatchSecret(secret.RuleID, result.Target); f != nil { + // Filter by ignore file + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(secret, types.FindingStatusIgnored, f.Statement, ignoreConfig.FilePath)) + continue + } + filtered = append(filtered, secret) + } + result.Secrets = filtered +} + +func filterLicenses(result *types.Result, severities, ignoreLicenseNames []string, ignoreConfig IgnoreConfig) { + // Merge ignore license names into ignored findings + var ignoreLicenses IgnoreConfig + for _, licenseName := range ignoreLicenseNames { + ignoreLicenses.Licenses = append(ignoreLicenses.Licenses, IgnoreFinding{ + ID: licenseName, + }) + } + + var filtered []types.DetectedLicense + for _, l := range result.Licenses { + // Filter by severity + if !slices.Contains(severities, l.Severity) { + continue + } + + // Filter by `--ignored-licenses` + if f := ignoreLicenses.MatchLicense(l.Name, l.FilePath); f != nil { + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(l, types.FindingStatusIgnored, "", "--ignored-licenses")) + continue + } + + // Filter by ignore file + if f := ignoreConfig.MatchLicense(l.Name, l.FilePath); f != nil { + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(l, types.FindingStatusIgnored, f.Statement, ignoreConfig.FilePath)) + continue + } + + filtered = append(filtered, l) + } + result.Licenses = filtered +} + +func summarize(status types.MisconfStatus, summary *types.MisconfSummary) { + switch status { + case types.MisconfStatusFailure: + summary.Failures++ + case types.MisconfStatusPassed: + summary.Successes++ + case types.MisconfStatusException: + summary.Exceptions++ + } +} + +func applyPolicy(ctx context.Context, result *types.Result, policyFile string) error { + policy, err := os.ReadFile(policyFile) + if err != nil { + return xerrors.Errorf("unable to read the policy file: %w", err) + } + + query, err := rego.New( + rego.Query("data.trivy.ignore"), + rego.Module("lib.rego", module), + rego.Module("trivy.rego", string(policy)), + ).PrepareForEval(ctx) + if err != nil { + return xerrors.Errorf("unable to prepare for eval: %w", err) + } + + policyFile = filepath.ToSlash(filepath.Clean(policyFile)) + + // Vulnerabilities + var filteredVulns []types.DetectedVulnerability + for _, vuln := range result.Vulnerabilities { + ignored, err := evaluate(ctx, query, vuln) + if err != nil { + return err + } + if ignored { + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(vuln, types.FindingStatusIgnored, "Filtered by Rego", policyFile)) + continue + } + filteredVulns = append(filteredVulns, vuln) + } + result.Vulnerabilities = filteredVulns + + // Misconfigurations + var filteredMisconfs []types.DetectedMisconfiguration + for _, misconf := range result.Misconfigurations { + ignored, err := evaluate(ctx, query, misconf) + if err != nil { + return err + } + if ignored { + result.MisconfSummary.Exceptions++ + switch misconf.Status { + case types.MisconfStatusFailure: + result.MisconfSummary.Failures-- + case types.MisconfStatusPassed: + result.MisconfSummary.Successes-- + } + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(misconf, types.FindingStatusIgnored, "Filtered by Rego", policyFile)) + continue + } + filteredMisconfs = append(filteredMisconfs, misconf) + } + result.Misconfigurations = filteredMisconfs + + // Secrets + var filteredSecrets []types.DetectedSecret + for _, scrt := range result.Secrets { + ignored, err := evaluate(ctx, query, scrt) + if err != nil { + return err + } + if ignored { + continue + } + filteredSecrets = append(filteredSecrets, scrt) + } + result.Secrets = filteredSecrets + + // Licenses + var filteredLicenses []types.DetectedLicense + for _, lic := range result.Licenses { + ignored, err := evaluate(ctx, query, lic) + if err != nil { + return err + } + if ignored { + continue + } + filteredLicenses = append(filteredLicenses, lic) + } + result.Licenses = filteredLicenses + + return nil +} + +func evaluate(ctx context.Context, query rego.PreparedEvalQuery, input interface{}) (bool, error) { + results, err := query.Eval(ctx, rego.EvalInput(input)) + if err != nil { + return false, xerrors.Errorf("unable to evaluate the policy: %w", err) + } else if len(results) == 0 { + // Handle undefined result. + return false, nil + } + ignore, ok := results[0].Expressions[0].Value.(bool) + if !ok { + // Handle unexpected result type. + return false, xerrors.New("the policy must return boolean") + } + return ignore, nil +} + +func shouldOverwrite(oldVuln, newVuln types.DetectedVulnerability) bool { + // The same vulnerability must be picked always. + return oldVuln.FixedVersion < newVuln.FixedVersion +} diff --git a/pkg/result/filter_test.go b/pkg/result/filter_test.go new file mode 100644 index 000000000000..d98048c6af1b --- /dev/null +++ b/pkg/result/filter_test.go @@ -0,0 +1,1022 @@ +package result_test + +import ( + "context" + "github.com/package-url/packageurl-go" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/result" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestFilter(t *testing.T) { + var ( + vuln1 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2019-0001", + PkgName: "foo", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "foo", + Version: "1.2.3", + }, + }, + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityLow.String(), + }, + } + vuln2 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2019-0002", + PkgName: "foo", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "foo", + Version: "4.5.6", + }, + }, + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + } + vuln3 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2019-0003", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityLow.String(), + }, + } + vuln4 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2019-0004", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityLow.String(), + }, + } + vuln5 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2019-0005", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityLow.String(), + }, + } + vuln6 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2019-0006", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "foo", + Version: "1.2.3", + }, + }, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityLow.String(), + }, + } + vuln7 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2019-0007", + PkgName: "bar", + InstalledVersion: "2.3.4", + FixedVersion: "2.3.5", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "bar", + Version: "2.3.4", + }, + }, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityLow.String(), + }, + } + misconf1 = types.DetectedMisconfiguration{ + Type: "Kubernetes Security Check", + ID: "ID100", + AVDID: "AVD-ID100", + Title: "Bad Deployment", + Message: "something bad", + Severity: dbTypes.SeverityHigh.String(), + Status: types.MisconfStatusFailure, + } + misconf2 = types.DetectedMisconfiguration{ + Type: "Kubernetes Security Check", + ID: "ID200", + AVDID: "AVD-ID200", + Title: "Bad Pod", + Message: "something bad", + Severity: dbTypes.SeverityLow.String(), + Status: types.MisconfStatusPassed, + } + misconf3 = types.DetectedMisconfiguration{ + Type: "Kubernetes Security Check", + ID: "ID300", + AVDID: "AVD-ID300", + Title: "Bad Job", + Message: "something bad", + Severity: dbTypes.SeverityLow.String(), + Status: types.MisconfStatusFailure, + } + secret1 = types.DetectedSecret{ + RuleID: "generic-wanted-rule", + Severity: dbTypes.SeverityHigh.String(), + Title: "Secret that should pass filter on rule id", + StartLine: 1, + EndLine: 2, + Match: "*****", + } + secret2 = types.DetectedSecret{ + RuleID: "generic-unwanted-rule", + Severity: dbTypes.SeverityLow.String(), + Title: "Secret that should not pass filter on rule id", + StartLine: 3, + EndLine: 4, + Match: "*****", + } + secret3 = types.DetectedSecret{ + RuleID: "generic-unwanted-rule2", + Severity: dbTypes.SeverityLow.String(), + Title: "Secret that should not pass filter on rule id", + StartLine: 5, + EndLine: 6, + Match: "*****", + } + license1 = types.DetectedLicense{ + Name: "GPL-3.0", + Severity: dbTypes.SeverityLow.String(), + FilePath: "usr/share/gcc/python/libstdcxx/v6/__init__.py", + Category: "restricted", + Confidence: 1, + } + license2 = types.DetectedLicense{ + Name: "GPL-3.0", + Severity: dbTypes.SeverityLow.String(), + FilePath: "usr/share/gcc/python/libstdcxx/v6/printers.py", + Category: "restricted", + Confidence: 1, + } + ) + type args struct { + report types.Report + severities []dbTypes.Severity + ignoreStatuses []dbTypes.Status + ignoreFile string + policyFile string + vexPath string + } + tests := []struct { + name string + args args + want types.Report + }{ + { + name: "severities", + args: args{ + report: types.Report{ + Results: []types.Result{ + { + Vulnerabilities: []types.DetectedVulnerability{ + vuln1, // filtered + vuln2, + }, + Misconfigurations: []types.DetectedMisconfiguration{ + misconf1, + misconf2, // filtered + }, + Secrets: []types.DetectedSecret{ + secret1, + secret2, // filtered + }, + }, + }, + }, + severities: []dbTypes.Severity{ + dbTypes.SeverityCritical, + dbTypes.SeverityHigh, + }, + }, + want: types.Report{ + Results: []types.Result{ + { + Vulnerabilities: []types.DetectedVulnerability{ + vuln2, + }, + MisconfSummary: &types.MisconfSummary{ + Successes: 0, + Failures: 1, + Exceptions: 0, + }, + Misconfigurations: []types.DetectedMisconfiguration{ + misconf1, + }, + Secrets: []types.DetectedSecret{ + secret1, + }, + }, + }, + }, + }, + { + name: "filter by VEX", + args: args{ + report: types.Report{ + Results: types.Results{ + types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln1, + vuln2, + }, + }, + }, + }, + severities: []dbTypes.Severity{ + dbTypes.SeverityCritical, + dbTypes.SeverityHigh, + dbTypes.SeverityMedium, + dbTypes.SeverityLow, + dbTypes.SeverityUnknown, + }, + vexPath: "testdata/openvex.json", + }, + want: types.Report{ + Results: types.Results{ + types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln2, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusNotAffected, + Statement: "vulnerable_code_not_in_execute_path", + Source: "OpenVEX", + Finding: vuln1, + }, + }, + }, + }, + }, + }, + { + name: "ignore unfixed", + args: args{ + report: types.Report{ + Results: types.Results{ + types.Result{ + Target: "debian:11 (debian 11)", + Vulnerabilities: []types.DetectedVulnerability{ + vuln1, + vuln2, + }, + }, + }, + }, + severities: []dbTypes.Severity{dbTypes.SeverityHigh}, + ignoreStatuses: []dbTypes.Status{ + dbTypes.StatusWillNotFix, + dbTypes.StatusEndOfLife, + }, + }, + want: types.Report{ + Results: types.Results{ + { + Target: "debian:11 (debian 11)", + }, + }, + }, + }, + { + name: "ignore file", + args: args{ + report: types.Report{ + Results: types.Results{ + { + Target: "package-lock.json", + Class: types.ClassLangPkg, + Vulnerabilities: []types.DetectedVulnerability{ + vuln1, // ignored + vuln2, // filtered by severity + vuln3, + vuln4, + vuln5, // ignored + vuln6, // ignored + }, + }, + { + Target: "deployment.yaml", + Class: types.ClassConfig, + Misconfigurations: []types.DetectedMisconfiguration{ + misconf1, + misconf2, + misconf3, + }, + }, + { + Target: "config.yaml", + Secrets: []types.DetectedSecret{ + secret1, + secret2, + }, + }, + }, + }, + severities: []dbTypes.Severity{ + dbTypes.SeverityLow, + dbTypes.SeverityHigh, + }, + ignoreFile: "testdata/.trivyignore", + }, + want: types.Report{ + Results: types.Results{ + { + Target: "package-lock.json", + Class: types.ClassLangPkg, + Vulnerabilities: []types.DetectedVulnerability{ + vuln3, + vuln4, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore", + Finding: vuln1, + }, + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore", + Finding: vuln5, + }, + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore", + Finding: vuln6, + }, + }, + }, + { + Target: "deployment.yaml", + Class: types.ClassConfig, + MisconfSummary: &types.MisconfSummary{ + Successes: 1, + Failures: 1, + Exceptions: 1, + }, + Misconfigurations: []types.DetectedMisconfiguration{ + misconf1, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeMisconfiguration, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore", + Finding: misconf3, + }, + }, + }, + { + Target: "config.yaml", + Secrets: []types.DetectedSecret{ + secret1, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeSecret, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore", + Finding: secret2, + }, + }, + }, + }, + }, + }, + { + name: "ignore yaml", + args: args{ + report: types.Report{ + Results: types.Results{ + { + Target: "foo/package-lock.json", + Vulnerabilities: []types.DetectedVulnerability{ + vuln1, // ignored + vuln2, // filtered by severity + vuln3, // ignored + vuln4, + vuln5, // ignored + vuln6, + vuln7, // filtered by PURL + }, + }, + { + Target: "app/Dockerfile", + Misconfigurations: []types.DetectedMisconfiguration{ + misconf1, // ignored + misconf2, // ignored + misconf3, + }, + }, + { + Target: "config.yaml", + Secrets: []types.DetectedSecret{ + secret1, + secret2, // ignored + secret3, // ignored + }, + }, + { + Target: "LICENSE.txt", + Licenses: []types.DetectedLicense{ + license1, // ignored + license2, + }, + }, + }, + }, + ignoreFile: "testdata/.trivyignore.yaml", + severities: []dbTypes.Severity{ + dbTypes.SeverityLow, + dbTypes.SeverityHigh, + }, + }, + want: types.Report{ + Results: types.Results{ + { + Target: "foo/package-lock.json", + Vulnerabilities: []types.DetectedVulnerability{ + vuln4, + vuln6, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore.yaml", + Finding: vuln1, + }, + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore.yaml", + Finding: vuln3, + }, + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore.yaml", + Finding: vuln5, + }, + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore.yaml", + Finding: vuln7, + }, + }, + }, + { + Target: "app/Dockerfile", + MisconfSummary: &types.MisconfSummary{ + Successes: 0, + Failures: 1, + Exceptions: 2, + }, + Misconfigurations: []types.DetectedMisconfiguration{ + misconf3, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeMisconfiguration, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore.yaml", + Finding: misconf1, + }, + { + Type: types.FindingTypeMisconfiguration, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore.yaml", + Finding: misconf2, + }, + }, + }, + { + Target: "config.yaml", + Secrets: []types.DetectedSecret{ + secret1, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeSecret, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore.yaml", + Finding: secret2, + }, + { + Type: types.FindingTypeSecret, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore.yaml", + Finding: secret3, + }, + }, + }, + { + Target: "LICENSE.txt", + Licenses: []types.DetectedLicense{ + license2, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeLicense, + Status: types.FindingStatusIgnored, + Source: "testdata/.trivyignore.yaml", + Finding: license1, + }, + }, + }, + }, + }, + }, + { + name: "policy file for vulnerabilities", + args: args{ + report: types.Report{ + Results: types.Results{ + { + Vulnerabilities: []types.DetectedVulnerability{ + vuln1, + vuln2, // ignored by severity + vuln3, // ignored by policy + }, + }, + }, + }, + severities: []dbTypes.Severity{dbTypes.SeverityLow}, + policyFile: "./testdata/ignore-vuln.rego", + }, + want: types.Report{ + Results: types.Results{ + { + Vulnerabilities: []types.DetectedVulnerability{ + vuln1, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Statement: "Filtered by Rego", + Source: "testdata/ignore-vuln.rego", + Finding: vuln3, + }, + }, + }, + }, + }, + }, + { + name: "policy file for misconfigurations", + args: args{ + report: types.Report{ + Results: types.Results{ + { + Misconfigurations: []types.DetectedMisconfiguration{ + misconf1, + misconf2, + misconf3, // ignored by policy + }, + }, + }, + }, + severities: []dbTypes.Severity{ + dbTypes.SeverityLow, + dbTypes.SeverityHigh, + }, + policyFile: "./testdata/ignore-misconf.rego", + }, + want: types.Report{ + Results: types.Results{ + { + MisconfSummary: &types.MisconfSummary{ + Successes: 1, + Failures: 1, + Exceptions: 1, + }, + Misconfigurations: []types.DetectedMisconfiguration{ + misconf1, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeMisconfiguration, + Status: types.FindingStatusIgnored, + Statement: "Filtered by Rego", + Source: "testdata/ignore-misconf.rego", + Finding: misconf3, + }, + }, + }, + }, + }, + }, + { + name: "ignore file for licenses and secrets", + args: args{ + report: types.Report{ + Results: types.Results{ + { + Licenses: []types.DetectedLicense{ + { + Name: "GPL-3.0", + Severity: dbTypes.SeverityLow.String(), + FilePath: "usr/share/gcc/python/libstdcxx/v6/__init__.py", + Category: "restricted", + Confidence: 1, + }, + { + Name: "GPL-3.0", + Severity: dbTypes.SeverityLow.String(), + FilePath: "usr/share/gcc/python/libstdcxx/v6/printers.py", + Category: "restricted", + Confidence: 1, + }, + }, + Secrets: []types.DetectedSecret{ + { + RuleID: "generic-passed-rule", + Severity: dbTypes.SeverityLow.String(), + Title: "Secret should pass filter", + StartLine: 1, + EndLine: 2, + Match: "*****", + }, + { + RuleID: "generic-ignored-rule", + Severity: dbTypes.SeverityLow.String(), + Title: "Secret should be ignored", + StartLine: 3, + EndLine: 4, + Match: "*****", + }, + }, + }, + }, + }, + severities: []dbTypes.Severity{dbTypes.SeverityLow}, + policyFile: "./testdata/test-ignore-policy-licenses-and-secrets.rego", + }, + want: types.Report{ + Results: types.Results{ + { + Licenses: []types.DetectedLicense{ + { + Name: "GPL-3.0", + Severity: dbTypes.SeverityLow.String(), + FilePath: "usr/share/gcc/python/libstdcxx/v6/__init__.py", + Category: "restricted", + Confidence: 1, + }, + }, + Secrets: []types.DetectedSecret{ + { + RuleID: "generic-passed-rule", + Severity: dbTypes.SeverityLow.String(), + Title: "Secret should pass filter", + StartLine: 1, + EndLine: 2, + Match: "*****", + }, + }, + }, + }, + }, + }, + { + name: "happy path with duplicates, one with empty fixed version", + args: args{ + report: types.Report{ + Results: types.Results{ + { + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityLow.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityLow.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0002", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0002", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.5", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2018-0001", + PkgName: "baz", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + }, + { + VulnerabilityID: "CVE-2018-0001", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2018-0002", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: "", + }, + }, + { + VulnerabilityID: "CVE-2018-0002", + PkgName: "bar", + InstalledVersion: "2.0.0", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: "", + }, + }, + }, + }, + }, + }, + severities: []dbTypes.Severity{ + dbTypes.SeverityCritical, + dbTypes.SeverityHigh, + dbTypes.SeverityUnknown, + }, + }, + want: types.Report{ + Results: types.Results{ + { + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2018-0001", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0002", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.5", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2018-0002", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityUnknown.String(), + }, + }, + { + VulnerabilityID: "CVE-2018-0002", + PkgName: "bar", + InstalledVersion: "2.0.0", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityUnknown.String(), + }, + }, + { + VulnerabilityID: "CVE-2018-0001", + PkgName: "baz", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with duplicates and different package paths", + args: args{ + report: types.Report{ + Results: types.Results{ + { + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0001", + PkgPath: "some/path/a.jar", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0001", + PkgPath: "some/other/path/a.jar", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0002", + PkgName: "baz", + PkgPath: "some/path/b.jar", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0002", + PkgPath: "some/path/b.jar", + PkgName: "baz", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0003", + PkgPath: "some/path/c.jar", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: "", + }, + }, + { + VulnerabilityID: "CVE-2019-0003", + PkgName: "bar", + PkgPath: "some/path/c.jar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: "", + }, + }, + { + VulnerabilityID: "CVE-2019-0003", + PkgName: "bar", + PkgPath: "some/other/path/c.jar", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: "", + }, + }, + }, + }, + }, + }, + severities: []dbTypes.Severity{ + dbTypes.SeverityCritical, + dbTypes.SeverityHigh, + dbTypes.SeverityUnknown, + }, + }, + want: types.Report{ + Results: types.Results{ + { + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0001", + PkgPath: "some/other/path/a.jar", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0001", + PkgPath: "some/path/a.jar", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0003", + PkgName: "bar", + PkgPath: "some/other/path/c.jar", + InstalledVersion: "1.2.3", + FixedVersion: "", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityUnknown.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0003", + PkgName: "bar", + PkgPath: "some/path/c.jar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityUnknown.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0002", + PkgPath: "some/path/b.jar", + PkgName: "baz", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeTime := time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC) + ctx := clock.With(context.Background(), fakeTime) + + err := result.Filter(ctx, tt.args.report, result.FilterOption{ + Severities: tt.args.severities, + VEXPath: tt.args.vexPath, + IgnoreStatuses: tt.args.ignoreStatuses, + IgnoreFile: tt.args.ignoreFile, + PolicyFile: tt.args.policyFile, + }) + require.NoError(t, err) + assert.Equal(t, tt.want, tt.args.report) + }) + } +} diff --git a/pkg/result/ignore.go b/pkg/result/ignore.go new file mode 100644 index 000000000000..25f7d03837d7 --- /dev/null +++ b/pkg/result/ignore.go @@ -0,0 +1,279 @@ +package result + +import ( + "bufio" + "context" + "errors" + "io/fs" + "os" + "path/filepath" + "strings" + "time" + + "github.com/bmatcuk/doublestar/v4" + "github.com/package-url/packageurl-go" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/purl" +) + +// IgnoreFinding represents an item to be ignored. +type IgnoreFinding struct { + // ID is the identifier of the vulnerability, misconfiguration, secret, or license. + // e.g. CVE-2019-8331, AVD-AWS-0175, etc. + // required: true + ID string `yaml:"id"` + + // Paths is the list of file paths to ignore. + // If Paths is not set, the ignore finding is applied to all files. + // required: false + Paths []string `yaml:"paths"` + + // PURLs is the list of packages to ignore. + // If PURLs is not set, the ignore finding is applied to packages. + // The field is currently available only for vulnerabilities. + // required: false + PURLs []*purl.PackageURL `yaml:"-"` // Filled in UnmarshalYAML + + // ExpiredAt is the expiration date of the ignore finding. + // If ExpiredAt is not set, the ignore finding is always valid. + // required: false + ExpiredAt time.Time `yaml:"expired_at"` + + // Statement describes the reason for ignoring the finding. + // required: false + Statement string `yaml:"statement"` +} + +// UnmarshalYAML is a custom unmarshaler for IgnoreFinding that handles +// the conversion of PURLs from strings to purl.PackageURL objects. +func (i *IgnoreFinding) UnmarshalYAML(value *yaml.Node) error { + // Define a shadow type to prevent infinite recursion + type plain IgnoreFinding + var tmp struct { + plain `yaml:",inline"` + PURLs []string `yaml:"purls"` + } + if err := value.Decode(&tmp); err != nil { + return err + } + + *i = IgnoreFinding(tmp.plain) + + for _, pattern := range i.Paths { + if !doublestar.ValidatePattern(pattern) { + return xerrors.Errorf("invalid path pattern in the ignore file, id: %s, path: %s", i.ID, pattern) + } + } + + // Convert string PURLs to purl.PackageURL objects + for _, purlStr := range tmp.PURLs { + parsedPURL, err := purl.FromString(purlStr) + if err != nil { + return xerrors.Errorf("purl error in the ignore file: %w", err) + } + i.PURLs = append(i.PURLs, parsedPURL) + } + + return nil +} + +type IgnoreFindings []IgnoreFinding + +func (f *IgnoreFindings) Match(id, path string, pkg *packageurl.PackageURL) *IgnoreFinding { + for _, finding := range *f { + if id != finding.ID { + continue + } + if !matchPath(path, finding.Paths) || !matchPURL(pkg, finding.PURLs) { + continue + } + + log.Logger.Debugw("Ignored", log.String("id", id), log.String("path", path)) + return &finding + + } + return nil +} + +func matchPath(path string, patterns []string) bool { + if len(patterns) == 0 { + return true + } + + for _, pattern := range patterns { + // Patterns are already validated, so we ignore errors here + if matched, _ := doublestar.Match(pattern, path); matched { + return true + } + } + return false +} + +func matchPURL(target *packageurl.PackageURL, purls []*purl.PackageURL) bool { + if target == nil || len(purls) == 0 { + return true + } + + for _, p := range purls { + if p.Match(target) { + return true + } + } + return false +} + +func (f *IgnoreFindings) Prune(ctx context.Context) { + var findings IgnoreFindings + for _, finding := range *f { + // Filter out expired ignore findings + if !finding.ExpiredAt.IsZero() && finding.ExpiredAt.Before(clock.Now(ctx)) { + continue + } + findings = append(findings, finding) + } + *f = findings +} + +// IgnoreConfig represents the structure of .trivyignore.yaml. +type IgnoreConfig struct { + FilePath string + Vulnerabilities IgnoreFindings `yaml:"vulnerabilities"` + Misconfigurations IgnoreFindings `yaml:"misconfigurations"` + Secrets IgnoreFindings `yaml:"secrets"` + Licenses IgnoreFindings `yaml:"licenses"` +} + +func (c *IgnoreConfig) MatchVulnerability(vulnID, filePath, pkgPath string, pkg *packageurl.PackageURL) *IgnoreFinding { + paths := []string{ + filePath, + pkgPath, + } + for _, p := range paths { + if f := c.Vulnerabilities.Match(vulnID, p, pkg); f != nil { + return f + } + } + return nil +} + +func (c *IgnoreConfig) MatchMisconfiguration(misconfID, avdID, filePath string) *IgnoreFinding { + ids := []string{ + misconfID, + avdID, + } + for _, id := range ids { + if f := c.Misconfigurations.Match(id, filePath, nil); f != nil { + return f + } + } + return nil +} + +func (c *IgnoreConfig) MatchSecret(secretID, filePath string) *IgnoreFinding { + return c.Secrets.Match(secretID, filePath, nil) +} + +func (c *IgnoreConfig) MatchLicense(licenseID, filePath string) *IgnoreFinding { + return c.Licenses.Match(licenseID, filePath, nil) +} + +func ParseIgnoreFile(ctx context.Context, ignoreFile string) (IgnoreConfig, error) { + var conf IgnoreConfig + if _, err := os.Stat(ignoreFile); errors.Is(err, fs.ErrNotExist) { + // .trivyignore doesn't necessarily exist + return IgnoreConfig{}, nil + } else if filepath.Ext(ignoreFile) == ".yml" || filepath.Ext(ignoreFile) == ".yaml" { + conf, err = parseIgnoreYAML(ignoreFile) + if err != nil { + return IgnoreConfig{}, xerrors.Errorf("%s parse error: %w", ignoreFile, err) + } + } else { + ignoredFindings, err := parseIgnore(ignoreFile) + if err != nil { + return IgnoreConfig{}, xerrors.Errorf("%s parse error: %w", ignoreFile, err) + } + + // IDs in .trivyignore are treated as IDs for all scanners + // as it is unclear which type of security issue they are + conf = IgnoreConfig{ + Vulnerabilities: ignoredFindings, + Misconfigurations: ignoredFindings, + Secrets: ignoredFindings, + Licenses: ignoredFindings, + } + } + + conf.Vulnerabilities.Prune(ctx) + conf.Misconfigurations.Prune(ctx) + conf.Secrets.Prune(ctx) + conf.Licenses.Prune(ctx) + conf.FilePath = filepath.ToSlash(filepath.Clean(ignoreFile)) + + return conf, nil +} + +func parseIgnoreYAML(ignoreFile string) (IgnoreConfig, error) { + // Read .trivyignore.yaml + f, err := os.Open(ignoreFile) + if err != nil { + return IgnoreConfig{}, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + log.Logger.Debugf("Found an ignore yaml: %s", ignoreFile) + + // Parse the YAML content + var ignoreConfig IgnoreConfig + if err = yaml.NewDecoder(f).Decode(&ignoreConfig); err != nil { + return IgnoreConfig{}, xerrors.Errorf("yaml decode error: %w", err) + } + return ignoreConfig, nil +} + +func parseIgnore(ignoreFile string) (IgnoreFindings, error) { + f, err := os.Open(ignoreFile) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + log.Logger.Debugf("Found an ignore file: %s", ignoreFile) + + var ignoredFindings IgnoreFindings + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "#") || line == "" { + continue + } + // Process all fields + var exp time.Time + fields := strings.Fields(line) + if len(fields) > 1 { + exp, err = getExpirationDate(fields) + if err != nil { + log.Logger.Warnf("Error while parsing expiration date in .trivyignore file: %s", err) + continue + } + } + ignoredFindings = append(ignoredFindings, IgnoreFinding{ + ID: fields[0], + ExpiredAt: exp, + }) + } + + return ignoredFindings, nil +} + +func getExpirationDate(fields []string) (time.Time, error) { + for _, field := range fields { + if strings.HasPrefix(field, "exp:") { + return time.Parse("2006-01-02", strings.TrimPrefix(field, "exp:")) + } + } + + return time.Time{}, nil +} diff --git a/pkg/result/module.go b/pkg/result/module.go new file mode 100644 index 000000000000..9dcfe3275b52 --- /dev/null +++ b/pkg/result/module.go @@ -0,0 +1,67 @@ +package result + +const ( + module = ` +package lib.trivy + +parse_cvss_vector_v3(cvss) = vector { + s := split(cvss, "/") + vector := { + "AttackVector": attack_vector[s[1]], + "AttackComplexity": attack_complexity[s[2]], + "PrivilegesRequired": privileges_required[s[3]], + "UserInteraction": user_interaction[s[4]], + "Scope": scope[s[5]], + "Confidentiality": confidentiality[s[6]], + "Integrity": integrity[s[7]], + "Availability": availability[s[8]], + } +} + +attack_vector := { + "AV:N": "Network", + "AV:A": "Adjacent", + "AV:L": "Local", + "AV:P": "Physical", +} + +attack_complexity := { + "AC:L": "Low", + "AC:H": "High", +} + +privileges_required := { + "PR:N": "None", + "PR:L": "Low", + "PR:H": "High", +} + +user_interaction := { + "UI:N": "None", + "UI:R": "Required", +} + +scope := { + "S:U": "Unchanged", + "S:C": "Changed", +} + +confidentiality := { + "C:N": "None", + "C:L": "Low", + "C:H": "High", +} + +integrity := { + "I:N": "None", + "I:L": "Low", + "I:H": "High", +} + +availability := { + "A:N": "None", + "A:L": "Low", + "A:H": "High", +} +` +) diff --git a/pkg/result/testdata/.trivyignore b/pkg/result/testdata/.trivyignore new file mode 100644 index 000000000000..fb2e885ab842 --- /dev/null +++ b/pkg/result/testdata/.trivyignore @@ -0,0 +1,12 @@ +# vulnerabilities +CVE-2019-0001 +CVE-2019-0002 +CVE-2019-0004 exp:2020-01-01 +CVE-2019-0005 exp:9999-01-01 +CVE-2019-0006 exp:9999-01-01 key2:value2 + +# misconfigurations +ID300 + +# secrets +generic-unwanted-rule \ No newline at end of file diff --git a/pkg/result/testdata/.trivyignore.yaml b/pkg/result/testdata/.trivyignore.yaml new file mode 100644 index 000000000000..9690cb9944cd --- /dev/null +++ b/pkg/result/testdata/.trivyignore.yaml @@ -0,0 +1,40 @@ +vulnerabilities: + - id: CVE-2019-0001 + paths: + - "**/*-lock.json" + - id: CVE-2019-0002 + paths: + - "bar/package.json" + - id: CVE-2019-0003 + - id: CVE-2019-0004 + paths: + - "bar/package.json" + - id: CVE-2019-0005 + expired_at: 2023-01-01 + - id: CVE-2019-0006 + expired_at: 2020-01-01 + - id: CVE-2019-0007 + purls: + - "pkg:golang/github.com/aquasecurity/aaa@9.9.9" + - "pkg:golang/github.com/aquasecurity/bar" # Match + +misconfigurations: + - id: ID100 + - id: ID200 + paths: + - "app/Dockerfile" + - id: ID300 + paths: + - "docs/Dockerfile" +secrets: + - id: generic-unwanted-rule + - id: generic-unwanted-rule2 + paths: + - "**/config.yaml" + - id: generic-wanted-rule + paths: + - "foo/secret.txt" +licenses: + - id: GPL-3.0 + paths: + - "usr/share/gcc/python/libstdcxx/v6/__init__.py" \ No newline at end of file diff --git a/pkg/result/testdata/fixtures/sad.yaml b/pkg/result/testdata/fixtures/sad.yaml new file mode 100644 index 000000000000..f7d55cb4b458 --- /dev/null +++ b/pkg/result/testdata/fixtures/sad.yaml @@ -0,0 +1,6 @@ +- bucket: vulnerability + pairs: + - key: CVE-2019-0001 + value: + Title: + - aaa diff --git a/pkg/result/testdata/fixtures/vulnerability.yaml b/pkg/result/testdata/fixtures/vulnerability.yaml new file mode 100644 index 000000000000..623c14a24612 --- /dev/null +++ b/pkg/result/testdata/fixtures/vulnerability.yaml @@ -0,0 +1,59 @@ +- bucket: vulnerability + pairs: + - key: CVE-2019-0001 + value: + Title: dos + Description: dos vulnerability + Severity: MEDIUM + References: + - http://example.com + LastModifiedDate: "2020-01-01T01:01:00Z" + PublishedDate: "2001-01-01T01:01:00Z" + - key: CVE-2019-0002 + value: + Title: dos + Description: dos vulnerability + Severity: UNKNOWN + VendorSeverity: + nvd: 1 + References: + - http://example.com + LastModifiedDate: "2020-01-01T01:01:00Z" + PublishedDate: "2001-01-01T01:01:00Z" + - key: CVE-2019-0003 + value: + Title: dos + Description: dos vulnerability + References: + - http://example.com + - key: CVE-2019-0004 + value: + Title: dos + Description: dos vulnerability + Severity: MEDIUM + VendorSeverity: + redhat: 1 + CVSS: + nvd: + V2Vector: AV:N/AC:L/Au:N/C:P/I:P/A:P + V2Score: 4.5 + V3Vector: CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H + V3Score: 5.6 + redhat: + V2Vector: AV:N/AC:M/Au:N/C:N/I:P/A:N + V2Score: 7.8 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + V3Score: 9.8 + References: + - http://example.com + CweIDs: + - CWE-311 + - key: CVE-2019-0005 + value: + Title: COVID-19 + Description: a nasty virus vulnerability for humans + Severity: MEDIUM + VendorSeverity: + ghsa: 4 + References: + - "https://www.who.int/emergencies/diseases/novel-coronavirus-2019" diff --git a/pkg/result/testdata/ignore-misconf.rego b/pkg/result/testdata/ignore-misconf.rego new file mode 100644 index 000000000000..1aa5062ddf24 --- /dev/null +++ b/pkg/result/testdata/ignore-misconf.rego @@ -0,0 +1,9 @@ +package trivy + +import data.lib.trivy + +default ignore=false + +ignore { + input.AVDID != "AVD-ID100" +} diff --git a/pkg/result/testdata/ignore-vuln.rego b/pkg/result/testdata/ignore-vuln.rego new file mode 100644 index 000000000000..12c2621ec2da --- /dev/null +++ b/pkg/result/testdata/ignore-vuln.rego @@ -0,0 +1,5 @@ +package trivy + +ignore { + input.VulnerabilityID != "CVE-2019-0001" +} diff --git a/pkg/result/testdata/openvex.json b/pkg/result/testdata/openvex.json new file mode 100644 index 000000000000..dcdd344700c7 --- /dev/null +++ b/pkg/result/testdata/openvex.json @@ -0,0 +1,17 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "author": "Aqua Security", + "role": "Project Release Bot", + "timestamp": "2023-01-16T19:07:16.853479631-06:00", + "version": 1, + "statements": [ + { + "vulnerability": {"name": "CVE-2019-0001"}, + "products": [ + {"@id": "pkg:golang/github.com/aquasecurity/foo@1.2.3"} + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] +} diff --git a/pkg/result/testdata/test-ignore-policy-licenses-and-secrets.rego b/pkg/result/testdata/test-ignore-policy-licenses-and-secrets.rego new file mode 100644 index 000000000000..b53c16a11ffa --- /dev/null +++ b/pkg/result/testdata/test-ignore-policy-licenses-and-secrets.rego @@ -0,0 +1,15 @@ +package trivy + +import data.lib.trivy + +default ignore=false + +ignore { + input.Name == "GPL-3.0" + input.FilePath == "usr/share/gcc/python/libstdcxx/v6/printers.py" +} + +ignore { + input.RuleID == "generic-ignored-rule" + input.Title == "Secret should be ignored" +} diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go new file mode 100644 index 000000000000..36a69b88c04f --- /dev/null +++ b/pkg/rpc/client/client.go @@ -0,0 +1,99 @@ +package client + +import ( + "context" + "crypto/tls" + "net/http" + + "golang.org/x/xerrors" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + r "github.com/aquasecurity/trivy/pkg/rpc" + "github.com/aquasecurity/trivy/pkg/types" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" + rpc "github.com/aquasecurity/trivy/rpc/scanner" +) + +type options struct { + rpcClient rpc.Scanner +} + +type Option func(*options) + +// WithRPCClient takes rpc client for testability +func WithRPCClient(c rpc.Scanner) Option { + return func(opts *options) { + opts.rpcClient = c + } +} + +// ScannerOption holds options for RPC client +type ScannerOption struct { + RemoteURL string + Insecure bool + CustomHeaders http.Header +} + +// Scanner implements the RPC scanner +type Scanner struct { + customHeaders http.Header + client rpc.Scanner +} + +// NewScanner is the factory method to return RPC Scanner +func NewScanner(scannerOptions ScannerOption, opts ...Option) Scanner { + httpClient := &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: scannerOptions.Insecure, + }, + }, + } + + c := rpc.NewScannerProtobufClient(scannerOptions.RemoteURL, httpClient) + + o := &options{rpcClient: c} + for _, opt := range opts { + opt(o) + } + + return Scanner{ + customHeaders: scannerOptions.CustomHeaders, + client: o.rpcClient, + } +} + +// Scan scans the image +func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys []string, opts types.ScanOptions) (types.Results, ftypes.OS, error) { + ctx = WithCustomHeaders(ctx, s.customHeaders) + + // Convert to the rpc struct + licenseCategories := make(map[string]*rpc.Licenses) + for category, names := range opts.LicenseCategories { + licenseCategories[string(category)] = &rpc.Licenses{Names: names} + } + + var res *rpc.ScanResponse + err := r.Retry(func() error { + var err error + res, err = s.client.Scan(ctx, &rpc.ScanRequest{ + Target: target, + ArtifactId: artifactKey, + BlobIds: blobKeys, + Options: &rpc.ScanOptions{ + VulnType: opts.VulnType, + Scanners: xstrings.ToStringSlice(opts.Scanners), + ListAllPackages: opts.ListAllPackages, + LicenseCategories: licenseCategories, + IncludeDevDeps: opts.IncludeDevDeps, + }, + }) + return err + }) + if err != nil { + return nil, ftypes.OS{}, xerrors.Errorf("failed to detect vulnerabilities via RPC: %w", err) + } + + return r.ConvertFromRPCResults(res.Results), r.ConvertFromRPCOS(res.Os), nil +} diff --git a/pkg/rpc/client/client_test.go b/pkg/rpc/client/client_test.go new file mode 100644 index 000000000000..012d5799ade9 --- /dev/null +++ b/pkg/rpc/client/client_test.go @@ -0,0 +1,255 @@ +package client + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/golang/protobuf/ptypes/timestamp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/encoding/protojson" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/utils" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/rpc/common" + rpc "github.com/aquasecurity/trivy/rpc/scanner" +) + +func TestScanner_Scan(t *testing.T) { + type args struct { + target string + imageID string + layerIDs []string + options types.ScanOptions + } + tests := []struct { + name string + customHeaders http.Header + args args + expectation *rpc.ScanResponse + wantResults types.Results + wantOS ftypes.OS + wantEosl bool + wantErr string + }{ + { + name: "happy path", + customHeaders: http.Header{ + "Trivy-Token": []string{"foo"}, + }, + args: args{ + target: "alpine:3.11", + imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{"os"}, + }, + }, + expectation: &rpc.ScanResponse{ + Os: &common.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + Results: []*rpc.Result{ + { + Target: "alpine:3.11", + Vulnerabilities: []*common.Vulnerability{ + { + VulnerabilityId: "CVE-2020-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Title: "DoS", + Description: "Denial os Service", + Severity: common.Severity_CRITICAL, + References: []string{"http://example.com"}, + SeveritySource: "nvd", + VendorSeverity: map[string]common.Severity{ + string(vulnerability.NVD): common.Severity_MEDIUM, + string(vulnerability.RedHat): common.Severity_MEDIUM, + }, + Cvss: map[string]*common.CVSS{ + "nvd": { + V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 7.2, + V3Score: 7.8, + }, + "redhat": { + V2Vector: "AV:H/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 4.2, + V3Score: 2.8, + }, + }, + CweIds: []string{"CWE-78"}, + Layer: &common.Layer{ + DiffId: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + LastModifiedDate: ×tamp.Timestamp{ + Seconds: 1577840460, + }, + PublishedDate: ×tamp.Timestamp{ + Seconds: 978310860, + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "alpine:3.11", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Title: "DoS", + Description: "Denial os Service", + Severity: "CRITICAL", + References: []string{"http://example.com"}, + VendorSeverity: dbTypes.VendorSeverity{ + vulnerability.NVD: dbTypes.SeverityMedium, + vulnerability.RedHat: dbTypes.SeverityMedium, + }, + CVSS: dbTypes.VendorCVSS{ + "nvd": { + V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 7.2, + V3Score: 7.8, + }, + "redhat": { + V2Vector: "AV:H/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 4.2, + V3Score: 2.8, + }, + }, + CweIDs: []string{"CWE-78"}, + LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), + PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), + }, + SeveritySource: "nvd", + Layer: ftypes.Layer{ + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + }, + }, + }, + }, + wantOS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + }, + { + name: "sad path: Scan returns an error", + customHeaders: http.Header{ + "Trivy-Token": []string{"foo"}, + }, + args: args{ + target: "alpine:3.11", + imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{"os"}, + }, + }, + wantErr: "failed to detect vulnerabilities via RPC", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if tt.expectation == nil { + e := map[string]interface{}{ + "code": "not_found", + "msg": "expectation is empty", + } + b, _ := json.Marshal(e) + w.WriteHeader(http.StatusBadGateway) + w.Write(b) + return + } + b, err := protojson.Marshal(tt.expectation) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "json marshaling error: %v", err) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(b) + })) + client := rpc.NewScannerJSONClient(ts.URL, ts.Client()) + + s := NewScanner(ScannerOption{CustomHeaders: tt.customHeaders}, WithRPCClient(client)) + + gotResults, gotOS, err := s.Scan(context.Background(), tt.args.target, tt.args.imageID, tt.args.layerIDs, tt.args.options) + + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + require.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } + + require.NoError(t, err, tt.name) + assert.Equal(t, tt.wantResults, gotResults) + assert.Equal(t, tt.wantOS, gotOS) + }) + } +} + +func TestScanner_ScanServerInsecure(t *testing.T) { + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + defer ts.Close() + + tests := []struct { + name string + insecure bool + wantErr string + }{ + { + name: "happy path", + insecure: true, + }, + { + name: "sad path", + insecure: false, + wantErr: "failed to do request", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := rpc.NewScannerProtobufClient(ts.URL, &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: tt.insecure, + }, + }, + }) + s := NewScanner(ScannerOption{Insecure: tt.insecure}, WithRPCClient(c)) + _, _, err := s.Scan(context.Background(), "dummy", "", nil, types.ScanOptions{}) + + if tt.wantErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + }) + } +} diff --git a/pkg/rpc/client/headers.go b/pkg/rpc/client/headers.go new file mode 100644 index 000000000000..463fb60149a4 --- /dev/null +++ b/pkg/rpc/client/headers.go @@ -0,0 +1,21 @@ +package client + +import ( + "context" + "net/http" + + "github.com/twitchtv/twirp" + + "github.com/aquasecurity/trivy/pkg/log" +) + +// WithCustomHeaders adds custom headers to request headers +func WithCustomHeaders(ctx context.Context, customHeaders http.Header) context.Context { + // Attach the headers to a context + ctxWithToken, err := twirp.WithHTTPRequestHeaders(ctx, customHeaders) + if err != nil { + log.Logger.Warnf("twirp error setting headers: %s", err) + return ctx + } + return ctxWithToken +} diff --git a/pkg/rpc/client/headers_test.go b/pkg/rpc/client/headers_test.go new file mode 100644 index 000000000000..8f5ecd6ff607 --- /dev/null +++ b/pkg/rpc/client/headers_test.go @@ -0,0 +1,50 @@ +package client + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/twitchtv/twirp" +) + +func TestWithCustomHeaders(t *testing.T) { + type args struct { + ctx context.Context + customHeaders http.Header + } + tests := []struct { + name string + args args + want http.Header + }{ + { + name: "happy path", + args: args{ + ctx: context.Background(), + customHeaders: http.Header{ + "Trivy-Token": []string{"token"}, + }, + }, + want: http.Header{ + "Trivy-Token": []string{"token"}, + }, + }, + { + name: "sad path, invalid headers passed in", + args: args{ + ctx: context.Background(), + customHeaders: http.Header{ + "Content-Type": []string{"token"}, + }, + }, + want: http.Header(nil), + }, + } + for _, tt := range tests { + gotCtx := WithCustomHeaders(tt.args.ctx, tt.args.customHeaders) + header, _ := twirp.HTTPRequestHeaders(gotCtx) + assert.Equal(t, tt.want, header, tt.name) + } +} diff --git a/pkg/rpc/convert.go b/pkg/rpc/convert.go new file mode 100644 index 000000000000..4edec4f1f24e --- /dev/null +++ b/pkg/rpc/convert.go @@ -0,0 +1,1009 @@ +package rpc + +import ( + "time" + + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/digest" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/rpc/cache" + "github.com/aquasecurity/trivy/rpc/common" + "github.com/aquasecurity/trivy/rpc/scanner" +) + +var LicenseCategoryMap = map[common.LicenseCategory_Enum]ftypes.LicenseCategory{ + common.LicenseCategory_UNSPECIFIED: "", + common.LicenseCategory_FORBIDDEN: ftypes.CategoryForbidden, + common.LicenseCategory_RESTRICTED: ftypes.CategoryRestricted, + common.LicenseCategory_RECIPROCAL: ftypes.CategoryReciprocal, + common.LicenseCategory_NOTICE: ftypes.CategoryNotice, + common.LicenseCategory_PERMISSIVE: ftypes.CategoryPermissive, + common.LicenseCategory_UNENCUMBERED: ftypes.CategoryUnencumbered, + common.LicenseCategory_UNKNOWN: ftypes.CategoryUnknown, +} + +var LicenseTypeMap = map[common.LicenseType_Enum]ftypes.LicenseType{ + common.LicenseType_UNSPECIFIED: "", + common.LicenseType_DPKG: ftypes.LicenseTypeDpkg, + common.LicenseType_HEADER: ftypes.LicenseTypeHeader, + common.LicenseType_LICENSE_FILE: ftypes.LicenseTypeFile, +} + +// ByValueOr returns the key from the map of the first matched value, +// or default key if the value is not present. +func ByValueOr[K, V comparable](m map[K]V, val V, d K) K { + for k, v := range m { + if v == val { + return k + } + } + return d +} + +// ConvertToRPCPkgs returns the list of RPC package objects +func ConvertToRPCPkgs(pkgs []ftypes.Package) []*common.Package { + var rpcPkgs []*common.Package + for _, pkg := range pkgs { + rpcPkgs = append(rpcPkgs, &common.Package{ + Id: pkg.ID, + Name: pkg.Name, + Version: pkg.Version, + Release: pkg.Release, + Epoch: int32(pkg.Epoch), + Arch: pkg.Arch, + Identifier: ConvertToRPCPkgIdentifier(pkg.Identifier), + Dev: pkg.Dev, + SrcName: pkg.SrcName, + SrcVersion: pkg.SrcVersion, + SrcRelease: pkg.SrcRelease, + SrcEpoch: int32(pkg.SrcEpoch), + Licenses: pkg.Licenses, + Locations: ConvertToRPCLocations(pkg.Locations), + Layer: ConvertToRPCLayer(pkg.Layer), + FilePath: pkg.FilePath, + DependsOn: pkg.DependsOn, + Digest: pkg.Digest.String(), + Indirect: pkg.Indirect, + }) + } + return rpcPkgs +} + +func ConvertToRPCPkgIdentifier(pkg ftypes.PkgIdentifier) *common.PkgIdentifier { + if pkg.Empty() { + return nil + } + + var p string + if pkg.PURL != nil { + p = pkg.PURL.String() + } + return &common.PkgIdentifier{ + Purl: p, + BomRef: pkg.BOMRef, + } +} + +func ConvertToRPCLocations(pkgLocs []ftypes.Location) []*common.Location { + var locations []*common.Location + for _, pkgLoc := range pkgLocs { + locations = append(locations, &common.Location{ + StartLine: int32(pkgLoc.StartLine), + EndLine: int32(pkgLoc.EndLine), + }) + } + return locations +} + +func ConvertToRPCCustomResources(resources []ftypes.CustomResource) []*common.CustomResource { + var rpcResources []*common.CustomResource + for _, r := range resources { + data, err := structpb.NewValue(r.Data) + if err != nil { + log.Logger.Warn(err) + } + rpcResources = append(rpcResources, &common.CustomResource{ + Type: r.Type, + FilePath: r.FilePath, + Layer: &common.Layer{ + Digest: r.Layer.Digest, + DiffId: r.Layer.DiffID, + }, + Data: data, + }) + } + return rpcResources +} + +func ConvertToRPCCode(code ftypes.Code) *common.Code { + var rpcLines []*common.Line + for _, line := range code.Lines { + rpcLines = append(rpcLines, &common.Line{ + Number: int32(line.Number), + Content: line.Content, + IsCause: line.IsCause, + Annotation: line.Annotation, + Truncated: line.Truncated, + Highlighted: line.Highlighted, + FirstCause: line.FirstCause, + LastCause: line.LastCause, + }) + } + return &common.Code{ + Lines: rpcLines, + } +} + +func ConvertToRPCSecrets(secrets []ftypes.Secret) []*common.Secret { + var rpcSecrets []*common.Secret + for _, s := range secrets { + rpcSecrets = append(rpcSecrets, &common.Secret{ + Filepath: s.FilePath, + Findings: ConvertToRPCSecretFindings(s.Findings), + }) + } + return rpcSecrets +} + +func ConvertToRPCSecretFindings(findings []ftypes.SecretFinding) []*common.SecretFinding { + var rpcFindings []*common.SecretFinding + for _, f := range findings { + rpcFindings = append(rpcFindings, &common.SecretFinding{ + RuleId: f.RuleID, + Category: string(f.Category), + Severity: f.Severity, + Title: f.Title, + EndLine: int32(f.EndLine), + StartLine: int32(f.StartLine), + Code: ConvertToRPCCode(f.Code), + Match: f.Match, + Layer: ConvertToRPCLayer(f.Layer), + }) + } + return rpcFindings +} + +func ConvertToRPCLicenseFiles(licenses []ftypes.LicenseFile) []*common.LicenseFile { + var rpcLicenses []*common.LicenseFile + + for _, lic := range licenses { + rpcLicenses = append(rpcLicenses, &common.LicenseFile{ + LicenseType: ConvertToRPCLicenseType(lic.Type), + FilePath: lic.FilePath, + PkgName: lic.PkgName, + Fingings: ConvertToRPCLicenseFindings(lic.Findings), + Layer: ConvertToRPCLayer(lic.Layer), + }) + } + + return rpcLicenses +} + +func ConvertToRPCLicenseFindings(findings ftypes.LicenseFindings) []*common.LicenseFinding { + var rpcFindings []*common.LicenseFinding + + for _, f := range findings { + rpcFindings = append(rpcFindings, &common.LicenseFinding{ + Category: ConvertToRPCLicenseCategory(f.Category), + Name: f.Name, + Confidence: float32(f.Confidence), + Link: f.Link, + }) + } + + return rpcFindings +} + +// ConvertFromRPCPkgs returns list of Fanal package objects +func ConvertFromRPCPkgs(rpcPkgs []*common.Package) []ftypes.Package { + var pkgs []ftypes.Package + for _, pkg := range rpcPkgs { + pkgs = append(pkgs, ftypes.Package{ + ID: pkg.Id, + Name: pkg.Name, + Version: pkg.Version, + Release: pkg.Release, + Epoch: int(pkg.Epoch), + Arch: pkg.Arch, + Identifier: ConvertFromRPCPkgIdentifier(pkg.Identifier), + Dev: pkg.Dev, + SrcName: pkg.SrcName, + SrcVersion: pkg.SrcVersion, + SrcRelease: pkg.SrcRelease, + SrcEpoch: int(pkg.SrcEpoch), + Licenses: pkg.Licenses, + Locations: ConvertFromRPCLocation(pkg.Locations), + Layer: ConvertFromRPCLayer(pkg.Layer), + FilePath: pkg.FilePath, + DependsOn: pkg.DependsOn, + Digest: digest.Digest(pkg.Digest), + Indirect: pkg.Indirect, + }) + } + return pkgs +} + +func ConvertFromRPCPkgIdentifier(pkg *common.PkgIdentifier) ftypes.PkgIdentifier { + if pkg == nil { + return ftypes.PkgIdentifier{} + } + + pkgID := ftypes.PkgIdentifier{ + BOMRef: pkg.BomRef, + } + + if pkg.Purl != "" { + pu, err := packageurl.FromString(pkg.Purl) + if err != nil { + log.Logger.Error("Failed to parse PURL (%s): %s", pkg.Purl, err) + } + pkgID.PURL = &pu + } + + return pkgID +} + +func ConvertFromRPCLocation(locs []*common.Location) []ftypes.Location { + var pkgLocs []ftypes.Location + for _, loc := range locs { + pkgLocs = append(pkgLocs, ftypes.Location{ + StartLine: int(loc.StartLine), + EndLine: int(loc.EndLine), + }) + } + return pkgLocs +} + +// ConvertToRPCVulns returns common.Vulnerability +func ConvertToRPCVulns(vulns []types.DetectedVulnerability) []*common.Vulnerability { + var rpcVulns []*common.Vulnerability + for _, vuln := range vulns { + severity, err := dbTypes.NewSeverity(vuln.Severity) + if err != nil { + log.Logger.Warn(err) + } + cvssMap := make(map[string]*common.CVSS) // This is needed because protobuf generates a map[string]*CVSS type + for vendor, vendorSeverity := range vuln.CVSS { + cvssMap[string(vendor)] = &common.CVSS{ + V2Vector: vendorSeverity.V2Vector, + V3Vector: vendorSeverity.V3Vector, + V2Score: vendorSeverity.V2Score, + V3Score: vendorSeverity.V3Score, + } + } + vensorSeverityMap := make(map[string]common.Severity) + for vendor, vendorSeverity := range vuln.VendorSeverity { + vensorSeverityMap[string(vendor)] = common.Severity(vendorSeverity) + } + + var lastModifiedDate, publishedDate *timestamppb.Timestamp + if vuln.LastModifiedDate != nil { + lastModifiedDate = timestamppb.New(*vuln.LastModifiedDate) // nolint: errcheck + } + + if vuln.PublishedDate != nil { + publishedDate = timestamppb.New(*vuln.PublishedDate) // nolint: errcheck + } + + var customAdvisoryData, customVulnData *structpb.Value + if vuln.Custom != nil { + customAdvisoryData, _ = structpb.NewValue(vuln.Custom) // nolint: errcheck + } + if vuln.Vulnerability.Custom != nil { + customVulnData, _ = structpb.NewValue(vuln.Vulnerability.Custom) // nolint: errcheck + } + + rpcVulns = append(rpcVulns, &common.Vulnerability{ + VulnerabilityId: vuln.VulnerabilityID, + VendorIds: vuln.VendorIDs, + PkgId: vuln.PkgID, + PkgName: vuln.PkgName, + PkgPath: vuln.PkgPath, + InstalledVersion: vuln.InstalledVersion, + FixedVersion: vuln.FixedVersion, + PkgIdentifier: ConvertToRPCPkgIdentifier(vuln.PkgIdentifier), + Status: int32(vuln.Status), + Title: vuln.Title, + Description: vuln.Description, + Severity: common.Severity(severity), + VendorSeverity: vensorSeverityMap, + References: vuln.References, + Layer: ConvertToRPCLayer(vuln.Layer), + Cvss: cvssMap, + SeveritySource: string(vuln.SeveritySource), + CweIds: vuln.CweIDs, + PrimaryUrl: vuln.PrimaryURL, + LastModifiedDate: lastModifiedDate, + PublishedDate: publishedDate, + CustomAdvisoryData: customAdvisoryData, + CustomVulnData: customVulnData, + DataSource: ConvertToRPCDataSource(vuln.DataSource), + }) + } + return rpcVulns +} + +// ConvertToRPCMisconfs returns common.DetectedMisconfigurations +func ConvertToRPCMisconfs(misconfs []types.DetectedMisconfiguration) []*common.DetectedMisconfiguration { + var rpcMisconfs []*common.DetectedMisconfiguration + for _, m := range misconfs { + severity, err := dbTypes.NewSeverity(m.Severity) + if err != nil { + log.Logger.Warn(err) + } + + rpcMisconfs = append(rpcMisconfs, &common.DetectedMisconfiguration{ + Type: m.Type, + Id: m.ID, + AvdId: m.AVDID, + Title: m.Title, + Description: m.Description, + Message: m.Message, + Namespace: m.Namespace, + Query: m.Query, + Resolution: m.Resolution, + Severity: common.Severity(severity), + PrimaryUrl: m.PrimaryURL, + References: m.References, + Status: string(m.Status), + Layer: ConvertToRPCLayer(m.Layer), + CauseMetadata: ConvertToRPCCauseMetadata(m.CauseMetadata), + }) + } + return rpcMisconfs +} + +// ConvertToRPCLayer returns common.Layer +func ConvertToRPCLayer(layer ftypes.Layer) *common.Layer { + return &common.Layer{ + Digest: layer.Digest, + DiffId: layer.DiffID, + CreatedBy: layer.CreatedBy, + } +} + +func ConvertToRPCPolicyMetadata(policy ftypes.PolicyMetadata) *common.PolicyMetadata { + return &common.PolicyMetadata{ + Id: policy.ID, + AdvId: policy.AVDID, + Type: policy.Type, + Title: policy.Title, + Description: policy.Description, + Severity: policy.Severity, + RecommendedActions: policy.RecommendedActions, + References: policy.References, + } +} + +func ConvertToRPCCauseMetadata(cause ftypes.CauseMetadata) *common.CauseMetadata { + return &common.CauseMetadata{ + Resource: cause.Resource, + Provider: cause.Provider, + Service: cause.Service, + StartLine: int32(cause.StartLine), + EndLine: int32(cause.EndLine), + Code: ConvertToRPCCode(cause.Code), + } +} + +// ConvertToRPCDataSource returns common.DataSource +func ConvertToRPCDataSource(ds *dbTypes.DataSource) *common.DataSource { + if ds == nil { + return nil + } + return &common.DataSource{ + Id: string(ds.ID), + Name: ds.Name, + Url: ds.URL, + } +} + +// ConvertFromRPCResults converts scanner.Result to types.Result +func ConvertFromRPCResults(rpcResults []*scanner.Result) []types.Result { + var results []types.Result + for _, result := range rpcResults { + results = append(results, types.Result{ + Target: result.Target, + Vulnerabilities: ConvertFromRPCVulns(result.Vulnerabilities), + Misconfigurations: ConvertFromRPCMisconfs(result.Misconfigurations), + Class: types.ResultClass(result.Class), + Type: ftypes.TargetType(result.Type), + Packages: ConvertFromRPCPkgs(result.Packages), + CustomResources: ConvertFromRPCCustomResources(result.CustomResources), + Secrets: ConvertFromRPCDetectedSecrets(result.Secrets), + Licenses: ConvertFromRPCDetectedLicenses(result.Licenses), + }) + } + return results +} + +func ConvertFromRPCDetectedLicenses(rpcLicenses []*common.DetectedLicense) []types.DetectedLicense { + var licenses []types.DetectedLicense + for _, l := range rpcLicenses { + severity := dbTypes.Severity(l.Severity) + licenses = append(licenses, types.DetectedLicense{ + Severity: severity.String(), + Category: ConvertFromRPCLicenseCategory(l.Category), + PkgName: l.PkgName, + FilePath: l.FilePath, + Name: l.Name, + Confidence: float64(l.Confidence), + Link: l.Link, + }) + } + return licenses +} + +func ConvertFromRPCLicenseCategory(rpcCategory common.LicenseCategory_Enum) ftypes.LicenseCategory { + return lo.ValueOr(LicenseCategoryMap, rpcCategory, "") +} + +func ConvertFromRPCLicenseType(rpcLicenseType common.LicenseType_Enum) ftypes.LicenseType { + return lo.ValueOr(LicenseTypeMap, rpcLicenseType, "") +} + +// ConvertFromRPCCustomResources converts array of cache.CustomResource to fanal.CustomResource +func ConvertFromRPCCustomResources(rpcCustomResources []*common.CustomResource) []ftypes.CustomResource { + var resources []ftypes.CustomResource + for _, res := range rpcCustomResources { + resources = append(resources, ftypes.CustomResource{ + Type: res.Type, + FilePath: res.FilePath, + Layer: ftypes.Layer{ + Digest: res.Layer.Digest, + DiffID: res.Layer.DiffId, + }, + Data: res.Data, + }) + } + return resources +} + +func ConvertFromRPCCode(rpcCode *common.Code) ftypes.Code { + var lines []ftypes.Line + for _, line := range rpcCode.Lines { + lines = append(lines, ftypes.Line{ + Number: int(line.Number), + Content: line.Content, + IsCause: line.IsCause, + Annotation: line.Annotation, + Truncated: line.Truncated, + Highlighted: line.Highlighted, + FirstCause: line.FirstCause, + LastCause: line.LastCause, + }) + } + return ftypes.Code{ + Lines: lines, + } +} + +func ConvertFromRPCDetectedSecrets(rpcFindings []*common.SecretFinding) []types.DetectedSecret { + if len(rpcFindings) == 0 { + return nil + } + return lo.Map(ConvertFromRPCSecretFindings(rpcFindings), func(s ftypes.SecretFinding, _ int) types.DetectedSecret { + return types.DetectedSecret(s) + }) +} + +func ConvertFromRPCSecretFindings(rpcFindings []*common.SecretFinding) []ftypes.SecretFinding { + var findings []ftypes.SecretFinding + for _, finding := range rpcFindings { + findings = append(findings, ftypes.SecretFinding{ + RuleID: finding.RuleId, + Category: ftypes.SecretRuleCategory(finding.Category), + Severity: finding.Severity, + Title: finding.Title, + StartLine: int(finding.StartLine), + EndLine: int(finding.EndLine), + Code: ConvertFromRPCCode(finding.Code), + Match: finding.Match, + Layer: ftypes.Layer{ + Digest: finding.Layer.Digest, + DiffID: finding.Layer.DiffId, + CreatedBy: finding.Layer.CreatedBy, + }, + }) + } + return findings +} + +func ConvertFromRPCSecrets(recSecrets []*common.Secret) []ftypes.Secret { + var secrets []ftypes.Secret + for _, secret := range recSecrets { + secrets = append(secrets, ftypes.Secret{ + FilePath: secret.Filepath, + Findings: ConvertFromRPCSecretFindings(secret.Findings), + }) + } + return secrets +} + +func ConvertFromRPCLicenseFiles(rpcLicenses []*common.LicenseFile) []ftypes.LicenseFile { + var licenses []ftypes.LicenseFile + + for _, lic := range rpcLicenses { + licenses = append(licenses, ftypes.LicenseFile{ + Type: ConvertFromRPCLicenseType(lic.LicenseType), + FilePath: lic.FilePath, + PkgName: lic.PkgName, + Findings: ConvertFromRPCLicenseFindings(lic.Fingings), + Layer: ConvertFromRPCLayer(lic.Layer), + }) + } + + return licenses +} + +func ConvertFromRPCLicenseFindings(rpcFindings []*common.LicenseFinding) ftypes.LicenseFindings { + var findings ftypes.LicenseFindings + + for _, finding := range rpcFindings { + findings = append(findings, ftypes.LicenseFinding{ + Category: ConvertFromRPCLicenseCategory(finding.Category), + Name: finding.Name, + Confidence: float64(finding.Confidence), + Link: finding.Link, + }) + } + + return findings +} + +// ConvertFromRPCVulns converts []*common.Vulnerability to []types.DetectedVulnerability +func ConvertFromRPCVulns(rpcVulns []*common.Vulnerability) []types.DetectedVulnerability { + var vulns []types.DetectedVulnerability + for _, vuln := range rpcVulns { + severity := dbTypes.Severity(vuln.Severity) + cvssMap := make(dbTypes.VendorCVSS) // This is needed because protobuf generates a map[string]*CVSS type + for vendor, vendorSeverity := range vuln.Cvss { + cvssMap[dbTypes.SourceID(vendor)] = dbTypes.CVSS{ + V2Vector: vendorSeverity.V2Vector, + V3Vector: vendorSeverity.V3Vector, + V2Score: vendorSeverity.V2Score, + V3Score: vendorSeverity.V3Score, + } + } + vensorSeverityMap := make(dbTypes.VendorSeverity) + for vendor, vendorSeverity := range vuln.VendorSeverity { + vensorSeverityMap[dbTypes.SourceID(vendor)] = dbTypes.Severity(vendorSeverity) + } + + var lastModifiedDate, publishedDate *time.Time + if vuln.LastModifiedDate != nil { + lastModifiedDate = lo.ToPtr(vuln.LastModifiedDate.AsTime()) + } + if vuln.PublishedDate != nil { + publishedDate = lo.ToPtr(vuln.PublishedDate.AsTime()) + } + + vulns = append(vulns, types.DetectedVulnerability{ + VulnerabilityID: vuln.VulnerabilityId, + VendorIDs: vuln.VendorIds, + PkgID: vuln.PkgId, + PkgName: vuln.PkgName, + PkgPath: vuln.PkgPath, + InstalledVersion: vuln.InstalledVersion, + FixedVersion: vuln.FixedVersion, + PkgIdentifier: ConvertFromRPCPkgIdentifier(vuln.PkgIdentifier), + Status: dbTypes.Status(vuln.Status), + Vulnerability: dbTypes.Vulnerability{ + Title: vuln.Title, + Description: vuln.Description, + Severity: severity.String(), + CVSS: cvssMap, + References: vuln.References, + CweIDs: vuln.CweIds, + LastModifiedDate: lastModifiedDate, + PublishedDate: publishedDate, + Custom: vuln.CustomVulnData.AsInterface(), + VendorSeverity: vensorSeverityMap, + }, + Layer: ConvertFromRPCLayer(vuln.Layer), + SeveritySource: dbTypes.SourceID(vuln.SeveritySource), + PrimaryURL: vuln.PrimaryUrl, + Custom: vuln.CustomAdvisoryData.AsInterface(), + DataSource: ConvertFromRPCDataSource(vuln.DataSource), + }) + } + return vulns +} + +// ConvertFromRPCMisconfs converts []*common.DetectedMisconfigurations to []types.DetectedMisconfiguration +func ConvertFromRPCMisconfs(rpcMisconfs []*common.DetectedMisconfiguration) []types.DetectedMisconfiguration { + var misconfs []types.DetectedMisconfiguration + for _, rpcMisconf := range rpcMisconfs { + misconfs = append(misconfs, types.DetectedMisconfiguration{ + Type: rpcMisconf.Type, + ID: rpcMisconf.Id, + AVDID: rpcMisconf.AvdId, + Title: rpcMisconf.Title, + Description: rpcMisconf.Description, + Message: rpcMisconf.Message, + Namespace: rpcMisconf.Namespace, + Query: rpcMisconf.Query, + Resolution: rpcMisconf.Resolution, + Severity: rpcMisconf.Severity.String(), + PrimaryURL: rpcMisconf.PrimaryUrl, + References: rpcMisconf.References, + Status: types.MisconfStatus(rpcMisconf.Status), + Layer: ConvertFromRPCLayer(rpcMisconf.Layer), + CauseMetadata: ConvertFromRPCCauseMetadata(rpcMisconf.CauseMetadata), + }) + } + return misconfs +} + +// ConvertFromRPCLayer converts *common.Layer to fanal.Layer +func ConvertFromRPCLayer(rpcLayer *common.Layer) ftypes.Layer { + if rpcLayer == nil { + return ftypes.Layer{} + } + return ftypes.Layer{ + Digest: rpcLayer.Digest, + DiffID: rpcLayer.DiffId, + CreatedBy: rpcLayer.CreatedBy, + } +} + +func ConvertFromRPCPolicyMetadata(rpcPolicy *common.PolicyMetadata) ftypes.PolicyMetadata { + if rpcPolicy == nil { + return ftypes.PolicyMetadata{} + } + + return ftypes.PolicyMetadata{ + ID: rpcPolicy.Id, + AVDID: rpcPolicy.AdvId, + Type: rpcPolicy.Type, + Title: rpcPolicy.Title, + Description: rpcPolicy.Description, + Severity: rpcPolicy.Severity, + RecommendedActions: rpcPolicy.RecommendedActions, + References: rpcPolicy.References, + } +} + +func ConvertFromRPCCauseMetadata(rpcCause *common.CauseMetadata) ftypes.CauseMetadata { + if rpcCause == nil { + return ftypes.CauseMetadata{} + } + return ftypes.CauseMetadata{ + Resource: rpcCause.Resource, + Provider: rpcCause.Provider, + Service: rpcCause.Service, + StartLine: int(rpcCause.StartLine), + EndLine: int(rpcCause.EndLine), + Code: ConvertFromRPCCode(rpcCause.Code), + } +} + +// ConvertFromRPCOS converts common.OS to fanal.OS +func ConvertFromRPCOS(rpcOS *common.OS) ftypes.OS { + if rpcOS == nil { + return ftypes.OS{} + } + return ftypes.OS{ + Family: ftypes.OSType(rpcOS.Family), + Name: rpcOS.Name, + Eosl: rpcOS.Eosl, + Extended: rpcOS.Extended, + } +} + +// ConvertFromRPCRepository converts common.Repository to fanal.Repository +func ConvertFromRPCRepository(rpcRepo *common.Repository) *ftypes.Repository { + if rpcRepo == nil { + return nil + } + return &ftypes.Repository{ + Family: ftypes.OSType(rpcRepo.Family), + Release: rpcRepo.Release, + } +} + +// ConvertFromRPCDataSource converts *common.DataSource to *dbTypes.DataSource +func ConvertFromRPCDataSource(ds *common.DataSource) *dbTypes.DataSource { + if ds == nil { + return nil + } + return &dbTypes.DataSource{ + ID: dbTypes.SourceID(ds.Id), + Name: ds.Name, + URL: ds.Url, + } +} + +// ConvertFromRPCPackageInfos converts common.PackageInfo to fanal.PackageInfo +func ConvertFromRPCPackageInfos(rpcPkgInfos []*common.PackageInfo) []ftypes.PackageInfo { + var pkgInfos []ftypes.PackageInfo + for _, rpcPkgInfo := range rpcPkgInfos { + pkgInfos = append(pkgInfos, ftypes.PackageInfo{ + FilePath: rpcPkgInfo.FilePath, + Packages: ConvertFromRPCPkgs(rpcPkgInfo.Packages), + }) + } + return pkgInfos +} + +// ConvertFromRPCApplications converts common.Application to fanal.Application +func ConvertFromRPCApplications(rpcApps []*common.Application) []ftypes.Application { + var apps []ftypes.Application + for _, rpcApp := range rpcApps { + apps = append(apps, ftypes.Application{ + Type: ftypes.LangType(rpcApp.Type), + FilePath: rpcApp.FilePath, + Libraries: ConvertFromRPCPkgs(rpcApp.Libraries), + }) + } + return apps +} + +// ConvertFromRPCMisconfigurations converts common.Misconfiguration to fanal.Misconfiguration +func ConvertFromRPCMisconfigurations(rpcMisconfs []*common.Misconfiguration) []ftypes.Misconfiguration { + var misconfs []ftypes.Misconfiguration + for _, rpcMisconf := range rpcMisconfs { + misconfs = append(misconfs, ftypes.Misconfiguration{ + FileType: ftypes.ConfigType(rpcMisconf.FileType), + FilePath: rpcMisconf.FilePath, + Successes: ConvertFromRPCMisconfResults(rpcMisconf.Successes), + Warnings: ConvertFromRPCMisconfResults(rpcMisconf.Warnings), + Failures: ConvertFromRPCMisconfResults(rpcMisconf.Failures), + Exceptions: ConvertFromRPCMisconfResults(rpcMisconf.Exceptions), + Layer: ftypes.Layer{}, + }) + } + return misconfs +} + +// ConvertFromRPCMisconfResults converts common.MisconfResult to fanal.MisconfResult +func ConvertFromRPCMisconfResults(rpcResults []*common.MisconfResult) []ftypes.MisconfResult { + var results []ftypes.MisconfResult + for _, r := range rpcResults { + results = append(results, ftypes.MisconfResult{ + Namespace: r.Namespace, + Message: r.Message, + PolicyMetadata: ConvertFromRPCPolicyMetadata(r.PolicyMetadata), + CauseMetadata: ConvertFromRPCCauseMetadata(r.CauseMetadata), + }) + } + return results +} + +// ConvertFromRPCPutArtifactRequest converts cache.PutArtifactRequest to fanal.PutArtifactRequest +func ConvertFromRPCPutArtifactRequest(req *cache.PutArtifactRequest) ftypes.ArtifactInfo { + return ftypes.ArtifactInfo{ + SchemaVersion: int(req.ArtifactInfo.SchemaVersion), + Architecture: req.ArtifactInfo.Architecture, + Created: req.ArtifactInfo.Created.AsTime(), + DockerVersion: req.ArtifactInfo.DockerVersion, + OS: req.ArtifactInfo.Os, + HistoryPackages: ConvertFromRPCPkgs(req.ArtifactInfo.HistoryPackages), + } +} + +// ConvertFromRPCPutBlobRequest returns ftypes.BlobInfo +func ConvertFromRPCPutBlobRequest(req *cache.PutBlobRequest) ftypes.BlobInfo { + return ftypes.BlobInfo{ + SchemaVersion: int(req.BlobInfo.SchemaVersion), + Digest: req.BlobInfo.Digest, + DiffID: req.BlobInfo.DiffId, + OS: ConvertFromRPCOS(req.BlobInfo.Os), + Repository: ConvertFromRPCRepository(req.BlobInfo.Repository), + PackageInfos: ConvertFromRPCPackageInfos(req.BlobInfo.PackageInfos), + Applications: ConvertFromRPCApplications(req.BlobInfo.Applications), + Misconfigurations: ConvertFromRPCMisconfigurations(req.BlobInfo.Misconfigurations), + OpaqueDirs: req.BlobInfo.OpaqueDirs, + WhiteoutFiles: req.BlobInfo.WhiteoutFiles, + CustomResources: ConvertFromRPCCustomResources(req.BlobInfo.CustomResources), + Secrets: ConvertFromRPCSecrets(req.BlobInfo.Secrets), + Licenses: ConvertFromRPCLicenseFiles(req.BlobInfo.Licenses), + } +} + +// ConvertToRPCOS returns common.OS +func ConvertToRPCOS(fos ftypes.OS) *common.OS { + return &common.OS{ + Family: string(fos.Family), + Name: fos.Name, + Eosl: fos.Eosl, + Extended: fos.Extended, + } +} + +// ConvertToRPCRepository returns common.Repository +func ConvertToRPCRepository(repo *ftypes.Repository) *common.Repository { + if repo == nil { + return nil + } + return &common.Repository{ + Family: string(repo.Family), + Release: repo.Release, + } +} + +// ConvertToRPCArtifactInfo returns PutArtifactRequest +func ConvertToRPCArtifactInfo(imageID string, imageInfo ftypes.ArtifactInfo) *cache.PutArtifactRequest { + + t := timestamppb.New(imageInfo.Created) + if err := t.CheckValid(); err != nil { + log.Logger.Warnf("invalid timestamp: %s", err) + } + + return &cache.PutArtifactRequest{ + ArtifactId: imageID, + ArtifactInfo: &cache.ArtifactInfo{ + SchemaVersion: int32(imageInfo.SchemaVersion), + Architecture: imageInfo.Architecture, + Created: t, + DockerVersion: imageInfo.DockerVersion, + Os: imageInfo.OS, + HistoryPackages: ConvertToRPCPkgs(imageInfo.HistoryPackages), + }, + } +} + +// ConvertToRPCPutBlobRequest returns PutBlobRequest +func ConvertToRPCPutBlobRequest(diffID string, blobInfo ftypes.BlobInfo) *cache.PutBlobRequest { + var packageInfos []*common.PackageInfo + for _, pkgInfo := range blobInfo.PackageInfos { + packageInfos = append(packageInfos, &common.PackageInfo{ + FilePath: pkgInfo.FilePath, + Packages: ConvertToRPCPkgs(pkgInfo.Packages), + }) + } + + var applications []*common.Application + for _, app := range blobInfo.Applications { + applications = append(applications, &common.Application{ + Type: string(app.Type), + FilePath: app.FilePath, + Libraries: ConvertToRPCPkgs(app.Libraries), + }) + } + + var misconfigurations []*common.Misconfiguration + for _, m := range blobInfo.Misconfigurations { + misconfigurations = append(misconfigurations, &common.Misconfiguration{ + FileType: string(m.FileType), + FilePath: m.FilePath, + Successes: ConvertToMisconfResults(m.Successes), + Warnings: ConvertToMisconfResults(m.Warnings), + Failures: ConvertToMisconfResults(m.Failures), + Exceptions: ConvertToMisconfResults(m.Exceptions), + }) + + } + + var customResources []*common.CustomResource + for _, res := range blobInfo.CustomResources { + data, err := structpb.NewValue(res.Data) + if err != nil { + + } else { + customResources = append(customResources, &common.CustomResource{ + Type: res.Type, + FilePath: res.FilePath, + Layer: &common.Layer{ + Digest: res.Layer.Digest, + DiffId: res.Layer.DiffID, + }, + Data: data, + }) + } + } + + return &cache.PutBlobRequest{ + DiffId: diffID, + BlobInfo: &cache.BlobInfo{ + SchemaVersion: ftypes.BlobJSONSchemaVersion, + Digest: blobInfo.Digest, + DiffId: blobInfo.DiffID, + Os: ConvertToRPCOS(blobInfo.OS), + Repository: ConvertToRPCRepository(blobInfo.Repository), + PackageInfos: packageInfos, + Applications: applications, + Misconfigurations: misconfigurations, + OpaqueDirs: blobInfo.OpaqueDirs, + WhiteoutFiles: blobInfo.WhiteoutFiles, + CustomResources: customResources, + Secrets: ConvertToRPCSecrets(blobInfo.Secrets), + Licenses: ConvertToRPCLicenseFiles(blobInfo.Licenses), + }, + } +} + +// ConvertToMisconfResults returns common.MisconfResult +func ConvertToMisconfResults(results []ftypes.MisconfResult) []*common.MisconfResult { + var rpcResults []*common.MisconfResult + for _, r := range results { + rpcResults = append(rpcResults, &common.MisconfResult{ + Namespace: r.Namespace, + Message: r.Message, + PolicyMetadata: ConvertToRPCPolicyMetadata(r.PolicyMetadata), + CauseMetadata: ConvertToRPCCauseMetadata(r.CauseMetadata), + }) + } + return rpcResults +} + +// ConvertToMissingBlobsRequest returns MissingBlobsRequest object +func ConvertToMissingBlobsRequest(imageID string, layerIDs []string) *cache.MissingBlobsRequest { + return &cache.MissingBlobsRequest{ + ArtifactId: imageID, + BlobIds: layerIDs, + } +} + +// ConvertToRPCScanResponse converts types.Result to ScanResponse +func ConvertToRPCScanResponse(results types.Results, fos ftypes.OS) *scanner.ScanResponse { + var rpcResults []*scanner.Result + for _, result := range results { + secretFindings := lo.Map(result.Secrets, func(s types.DetectedSecret, _ int) ftypes.SecretFinding { + return ftypes.SecretFinding(s) + }) + rpcResults = append(rpcResults, &scanner.Result{ + Target: result.Target, + Class: string(result.Class), + Type: string(result.Type), + Packages: ConvertToRPCPkgs(result.Packages), + Vulnerabilities: ConvertToRPCVulns(result.Vulnerabilities), + Misconfigurations: ConvertToRPCMisconfs(result.Misconfigurations), + Secrets: ConvertToRPCSecretFindings(secretFindings), + Licenses: ConvertToRPCLicenses(result.Licenses), + CustomResources: ConvertToRPCCustomResources(result.CustomResources), + }) + } + + return &scanner.ScanResponse{ + Os: ConvertToRPCOS(fos), + Results: rpcResults, + } +} + +func ConvertToRPCLicenses(licenses []types.DetectedLicense) []*common.DetectedLicense { + var rpcLicenses []*common.DetectedLicense + for _, l := range licenses { + severity, err := dbTypes.NewSeverity(l.Severity) + if err != nil { + log.Logger.Warn(err) + } + rpcLicenses = append(rpcLicenses, &common.DetectedLicense{ + Severity: common.Severity(severity), + Category: ConvertToRPCLicenseCategory(l.Category), + PkgName: l.PkgName, + FilePath: l.FilePath, + Name: l.Name, + Confidence: float32(l.Confidence), + Link: l.Link, + }) + } + + return rpcLicenses +} + +func ConvertToRPCLicenseCategory(category ftypes.LicenseCategory) common.LicenseCategory_Enum { + return ByValueOr(LicenseCategoryMap, category, common.LicenseCategory_UNSPECIFIED) +} + +func ConvertToRPCLicenseType(ty ftypes.LicenseType) common.LicenseType_Enum { + return ByValueOr(LicenseTypeMap, ty, common.LicenseType_UNSPECIFIED) +} + +func ConvertToDeleteBlobsRequest(blobIDs []string) *cache.DeleteBlobsRequest { + return &cache.DeleteBlobsRequest{BlobIds: blobIDs} +} + +func ConvertFromDeleteBlobsRequest(deleteBlobsRequest *cache.DeleteBlobsRequest) []string { + if deleteBlobsRequest == nil { + return []string{} + } + return deleteBlobsRequest.GetBlobIds() +} diff --git a/pkg/rpc/convert_test.go b/pkg/rpc/convert_test.go new file mode 100644 index 000000000000..a74f8eecb99b --- /dev/null +++ b/pkg/rpc/convert_test.go @@ -0,0 +1,1042 @@ +package rpc + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/rpc/common" + "github.com/aquasecurity/trivy/rpc/scanner" +) + +func TestConvertToRpcPkgs(t *testing.T) { + type args struct { + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + want []*common.Package + }{ + { + name: "happy path", + args: args{ + pkgs: []ftypes.Package{ + { + Name: "binary", + Version: "1.2.3", + Release: "1", + Epoch: 2, + Arch: "x86_64", + SrcName: "src", + SrcVersion: "1.2.3", + SrcRelease: "1", + SrcEpoch: 2, + Licenses: []string{"MIT"}, + Locations: []ftypes.Location{ + { + StartLine: 10, + EndLine: 20, + }, + { + StartLine: 22, + EndLine: 32, + }, + }, + Layer: ftypes.Layer{ + Digest: "sha256:6a428f9f83b0a29f1fdd2ccccca19a9bab805a925b8eddf432a5a3d3da04afbc", + DiffID: "sha256:39982b2a789afc156fff00c707d0ff1c6ab4af8f1666a8df4787714059ce24e7", + }, + Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", + Indirect: true, + }, + }, + }, + want: []*common.Package{ + { + Name: "binary", + Version: "1.2.3", + Release: "1", + Epoch: 2, + Arch: "x86_64", + SrcName: "src", + SrcVersion: "1.2.3", + SrcRelease: "1", + SrcEpoch: 2, + Licenses: []string{"MIT"}, + Locations: []*common.Location{ + { + StartLine: 10, + EndLine: 20, + }, + { + StartLine: 22, + EndLine: 32, + }, + }, + Layer: &common.Layer{ + Digest: "sha256:6a428f9f83b0a29f1fdd2ccccca19a9bab805a925b8eddf432a5a3d3da04afbc", + DiffId: "sha256:39982b2a789afc156fff00c707d0ff1c6ab4af8f1666a8df4787714059ce24e7", + }, + Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", + Indirect: true, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertToRPCPkgs(tt.args.pkgs) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestConvertFromRpcPkgs(t *testing.T) { + type args struct { + rpcPkgs []*common.Package + } + tests := []struct { + name string + args args + want []ftypes.Package + }{ + { + args: args{ + rpcPkgs: []*common.Package{ + { + Name: "binary", + Version: "1.2.3", + Release: "1", + Epoch: 2, + Arch: "x86_64", + SrcName: "src", + SrcVersion: "1.2.3", + SrcRelease: "1", + SrcEpoch: 2, + Licenses: []string{"MIT"}, + Locations: []*common.Location{ + { + StartLine: 10, + EndLine: 20, + }, + { + StartLine: 22, + EndLine: 32, + }, + }, + Layer: &common.Layer{ + Digest: "sha256:6a428f9f83b0a29f1fdd2ccccca19a9bab805a925b8eddf432a5a3d3da04afbc", + DiffId: "sha256:39982b2a789afc156fff00c707d0ff1c6ab4af8f1666a8df4787714059ce24e7", + }, + Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", + Indirect: true, + }, + }, + }, + want: []ftypes.Package{ + { + Name: "binary", + Version: "1.2.3", + Release: "1", + Epoch: 2, + Arch: "x86_64", + SrcName: "src", + SrcVersion: "1.2.3", + SrcRelease: "1", + SrcEpoch: 2, + Licenses: []string{"MIT"}, + Locations: []ftypes.Location{ + { + StartLine: 10, + EndLine: 20, + }, + { + StartLine: 22, + EndLine: 32, + }, + }, + Layer: ftypes.Layer{ + Digest: "sha256:6a428f9f83b0a29f1fdd2ccccca19a9bab805a925b8eddf432a5a3d3da04afbc", + DiffID: "sha256:39982b2a789afc156fff00c707d0ff1c6ab4af8f1666a8df4787714059ce24e7", + }, + Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", + Indirect: true, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertFromRPCPkgs(tt.args.rpcPkgs) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestConvertToRpcVulns(t *testing.T) { + fixedPublishedDate := time.Unix(1257894000, 0) + fixedLastModifiedDate := time.Unix(1257894010, 0) + + type args struct { + vulns []types.DetectedVulnerability + } + tests := []struct { + name string + args args + want []*common.Vulnerability + }{ + { + name: "happy path", + args: args{ + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Title: "DoS", + Description: "Denial of Service", + Severity: "MEDIUM", + VendorSeverity: dbTypes.VendorSeverity{ + vulnerability.RedHat: dbTypes.SeverityMedium, + }, + CVSS: dbTypes.VendorCVSS{ + vulnerability.RedHat: { + V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 7.2, + V3Score: 7.8, + }, + }, + References: []string{"http://example.com"}, + PublishedDate: &fixedPublishedDate, + LastModifiedDate: &fixedLastModifiedDate, + }, + Layer: ftypes.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/CVE-2019-0001", + DataSource: &dbTypes.DataSource{ + Name: "GitHub Security Advisory Maven", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + }, + }, + }, + want: []*common.Vulnerability{ + { + VulnerabilityId: "CVE-2019-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Title: "DoS", + Description: "Denial of Service", + Severity: common.Severity_MEDIUM, + VendorSeverity: map[string]common.Severity{ + string(vulnerability.RedHat): common.Severity_MEDIUM, + }, + Cvss: map[string]*common.CVSS{ + "redhat": { + V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 7.2, + V3Score: 7.8, + }, + }, + References: []string{"http://example.com"}, + Layer: &common.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + PrimaryUrl: "https://avd.aquasec.com/nvd/CVE-2019-0001", + PublishedDate: timestamppb.New(fixedPublishedDate), + LastModifiedDate: timestamppb.New(fixedLastModifiedDate), + DataSource: &common.DataSource{ + Name: "GitHub Security Advisory Maven", + Url: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + }, + }, + }, + { + name: "invalid severity", + args: args{ + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0002", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Title: "DoS", + Description: "Denial of Service", + Severity: "INVALID", + References: []string{"http://example.com"}, + }, + Layer: ftypes.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + DataSource: &dbTypes.DataSource{ + Name: "GitHub Security Advisory Maven", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + }, + }, + }, + want: []*common.Vulnerability{ + { + VulnerabilityId: "CVE-2019-0002", + PkgName: "bar", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Title: "DoS", + Description: "Denial of Service", + Severity: common.Severity_UNKNOWN, + VendorSeverity: make(map[string]common.Severity), + Cvss: make(map[string]*common.CVSS), + References: []string{"http://example.com"}, + Layer: &common.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + DataSource: &common.DataSource{ + Name: "GitHub Security Advisory Maven", + Url: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertToRPCVulns(tt.args.vulns) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestConvertFromRPCResults(t *testing.T) { + fixedPublishedDate := time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC) + fixedLastModifiedDate := time.Date(2009, 11, 10, 23, 0, 10, 0, time.UTC) + + type args struct { + rpcResults []*scanner.Result + } + tests := []struct { + name string + args args + want []types.Result + }{ + { + name: "happy path", + args: args{ + rpcResults: []*scanner.Result{ + { + Target: "alpine:3.10", + Type: string(ftypes.Alpine), + Vulnerabilities: []*common.Vulnerability{ + { + VulnerabilityId: "CVE-2019-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Title: "DoS", + Description: "Denial of Service", + Severity: common.Severity_MEDIUM, + SeveritySource: string(vulnerability.NVD), + CweIds: []string{ + "CWE-123", + "CWE-456", + }, + VendorSeverity: map[string]common.Severity{ + string(vulnerability.RedHat): common.Severity_MEDIUM, + }, + Cvss: map[string]*common.CVSS{ + string(vulnerability.RedHat): { + V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 7.2, + V3Score: 7.8, + }, + }, + References: []string{"http://example.com"}, + Layer: &common.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + PrimaryUrl: "https://avd.aquasec.com/nvd/CVE-2019-0001", + PublishedDate: timestamppb.New(fixedPublishedDate), + LastModifiedDate: timestamppb.New(fixedLastModifiedDate), + DataSource: &common.DataSource{ + Name: "GitHub Security Advisory Maven", + Url: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + }, + }, + }, + }, + }, + want: []types.Result{ + { + Target: "alpine:3.10", + Type: ftypes.Alpine, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Layer: ftypes.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + SeveritySource: vulnerability.NVD, + PrimaryURL: "https://avd.aquasec.com/nvd/CVE-2019-0001", + Vulnerability: dbTypes.Vulnerability{ + Title: "DoS", + Description: "Denial of Service", + Severity: common.Severity_MEDIUM.String(), + VendorSeverity: dbTypes.VendorSeverity{ + vulnerability.RedHat: dbTypes.SeverityMedium, + }, + CweIDs: []string{ + "CWE-123", + "CWE-456", + }, + CVSS: dbTypes.VendorCVSS{ + vulnerability.RedHat: { + V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 7.2, + V3Score: 7.8, + }, + }, + References: []string{"http://example.com"}, + PublishedDate: &fixedPublishedDate, + LastModifiedDate: &fixedLastModifiedDate, + }, + DataSource: &dbTypes.DataSource{ + Name: "GitHub Security Advisory Maven", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + }, + }, + }, + }, + }, + { + name: "happy path - with nil dates", + args: args{ + rpcResults: []*scanner.Result{ + { + Target: "alpine:3.10", + Type: string(ftypes.Alpine), + Vulnerabilities: []*common.Vulnerability{ + { + VulnerabilityId: "CVE-2019-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Title: "DoS", + Description: "Denial of Service", + Severity: common.Severity_MEDIUM, + SeveritySource: string(vulnerability.NVD), + CweIds: []string{ + "CWE-123", + "CWE-456", + }, + Cvss: map[string]*common.CVSS{ + "redhat": { + V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 7.2, + V3Score: 7.8, + }, + }, + References: []string{"http://example.com"}, + Layer: &common.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + PrimaryUrl: "https://avd.aquasec.com/nvd/CVE-2019-0001", + PublishedDate: nil, + LastModifiedDate: nil, + }, + }, + }, + }, + }, + want: []types.Result{ + { + Target: "alpine:3.10", + Type: ftypes.Alpine, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Layer: ftypes.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + SeveritySource: vulnerability.NVD, + PrimaryURL: "https://avd.aquasec.com/nvd/CVE-2019-0001", + Vulnerability: dbTypes.Vulnerability{ + Title: "DoS", + Description: "Denial of Service", + Severity: common.Severity_MEDIUM.String(), + CweIDs: []string{ + "CWE-123", + "CWE-456", + }, + VendorSeverity: make(dbTypes.VendorSeverity), + CVSS: dbTypes.VendorCVSS{ + vulnerability.RedHat: { + V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 7.2, + V3Score: 7.8, + }, + }, + References: []string{"http://example.com"}, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertFromRPCResults(tt.args.rpcResults) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestConvertFromRPCMisconfs(t *testing.T) { + type args struct { + misconfs []*common.DetectedMisconfiguration + } + tests := []struct { + name string + args args + want []types.DetectedMisconfiguration + }{ + { + name: "happy path misconf", + args: args{ + misconfs: []*common.DetectedMisconfiguration{ + { + Type: "Dockerfile Security Check", + Id: "DS005", + AvdId: "AVD-DS-0005", + Title: "ADD instead of COPY", + Description: "You should use COPY instead of ADD unless you want to extract a tar file. Note that an ADD command will extract a tar file, which adds the risk of Zip-based vulnerabilities. Accordingly, it is advised to use a COPY command, which does not extract tar files.", + Message: "Consider using 'COPY . /app' command instead of 'ADD . /app'", + Namespace: "builtin.dockerfile.DS005", + Query: "data.builtin.dockerfile.DS005.deny", + Resolution: "Use COPY instead of ADD", + Severity: common.Severity_LOW, + PrimaryUrl: "https://avd.aquasec.com/misconfig/ds005", + References: []string{ + "https://docs.docker.com/engine/reference/builder/#add", + "https://avd.aquasec.com/misconfig/ds005", + }, + Status: "FAIL", + Layer: &common.Layer{}, + CauseMetadata: &common.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + StartLine: 3, + EndLine: 3, + Code: &common.Code{ + Lines: []*common.Line{ + { + Number: 3, + Content: "ADD . /app", + IsCause: true, + Annotation: "", + Truncated: false, + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + }, + }, + }, + want: []types.DetectedMisconfiguration{ + { + Type: "Dockerfile Security Check", + ID: "DS005", + AVDID: "AVD-DS-0005", + Title: "ADD instead of COPY", + Description: "You should use COPY instead of ADD unless you want to extract a tar file. Note that an ADD command will extract a tar file, which adds the risk of Zip-based vulnerabilities. Accordingly, it is advised to use a COPY command, which does not extract tar files.", + Message: "Consider using 'COPY . /app' command instead of 'ADD . /app'", + Namespace: "builtin.dockerfile.DS005", + Query: "data.builtin.dockerfile.DS005.deny", + Resolution: "Use COPY instead of ADD", + Severity: "LOW", + PrimaryURL: "https://avd.aquasec.com/misconfig/ds005", + References: []string{ + "https://docs.docker.com/engine/reference/builder/#add", + "https://avd.aquasec.com/misconfig/ds005", + }, + Status: "FAIL", + Layer: ftypes.Layer{}, + CauseMetadata: ftypes.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + StartLine: 3, + EndLine: 3, + Code: ftypes.Code{ + Lines: []ftypes.Line{ + { + Number: 3, + Content: "ADD . /app", + IsCause: true, + Annotation: "", + Truncated: false, + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertFromRPCMisconfs(tt.args.misconfs) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestConvertToRPCMiconfs(t *testing.T) { + type args struct { + misconfs []types.DetectedMisconfiguration + } + tests := []struct { + name string + args args + want []*common.DetectedMisconfiguration + }{ + { + name: "happy path misconf", + args: args{ + misconfs: []types.DetectedMisconfiguration{ + { + Type: "Dockerfile Security Check", + ID: "DS005", + AVDID: "AVD-DS-0005", + Title: "ADD instead of COPY", + Description: "You should use COPY instead of ADD unless you want to extract a tar file. Note that an ADD command will extract a tar file, which adds the risk of Zip-based vulnerabilities. Accordingly, it is advised to use a COPY command, which does not extract tar files.", + Message: "Consider using 'COPY . /app' command instead of 'ADD . /app'", + Namespace: "builtin.dockerfile.DS005", + Query: "data.builtin.dockerfile.DS005.deny", + Resolution: "Use COPY instead of ADD", + Severity: "LOW", + PrimaryURL: "https://avd.aquasec.com/misconfig/ds005", + References: []string{ + "https://docs.docker.com/engine/reference/builder/#add", + "https://avd.aquasec.com/misconfig/ds005", + }, + Status: "FAIL", + Layer: ftypes.Layer{}, + CauseMetadata: ftypes.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + StartLine: 3, + EndLine: 3, + Code: ftypes.Code{ + Lines: []ftypes.Line{ + { + Number: 3, + Content: "ADD . /app", + IsCause: true, + Annotation: "", + Truncated: false, + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + }, + }, + }, + want: []*common.DetectedMisconfiguration{ + { + Type: "Dockerfile Security Check", + Id: "DS005", + AvdId: "AVD-DS-0005", + Title: "ADD instead of COPY", + Description: "You should use COPY instead of ADD unless you want to extract a tar file. Note that an ADD command will extract a tar file, which adds the risk of Zip-based vulnerabilities. Accordingly, it is advised to use a COPY command, which does not extract tar files.", + Message: "Consider using 'COPY . /app' command instead of 'ADD . /app'", + Namespace: "builtin.dockerfile.DS005", + Query: "data.builtin.dockerfile.DS005.deny", + Resolution: "Use COPY instead of ADD", + Severity: common.Severity_LOW, + PrimaryUrl: "https://avd.aquasec.com/misconfig/ds005", + References: []string{ + "https://docs.docker.com/engine/reference/builder/#add", + "https://avd.aquasec.com/misconfig/ds005", + }, + Status: "FAIL", + Layer: &common.Layer{}, + CauseMetadata: &common.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + StartLine: 3, + EndLine: 3, + Code: &common.Code{ + Lines: []*common.Line{ + { + Number: 3, + Content: "ADD . /app", + IsCause: true, + Annotation: "", + Truncated: false, + FirstCause: true, + LastCause: true, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertToRPCMisconfs(tt.args.misconfs) + assert.Equal(t, tt.want, got, tt.name) + }) + } +} + +func TestConvertFromRPCLicenses(t *testing.T) { + tests := []struct { + name string + rpcLicenses []*common.DetectedLicense + want []types.DetectedLicense + }{ + { + name: "happy", + rpcLicenses: []*common.DetectedLicense{ + { + Severity: common.Severity_HIGH, + Category: common.LicenseCategory_RESTRICTED, + PkgName: "alpine-baselayout", + FilePath: "some-path", + Name: "GPL-2.0", + Confidence: 1, + Link: "https://some-link", + }, + }, + want: []types.DetectedLicense{ + { + Severity: "HIGH", + Category: "restricted", + PkgName: "alpine-baselayout", + FilePath: "some-path", + Name: "GPL-2.0", + Confidence: 1, + Link: "https://some-link", + }, + }, + }, + { + name: "no licenses", + rpcLicenses: nil, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertFromRPCDetectedLicenses(tt.rpcLicenses) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestConvertToRPCLicenses(t *testing.T) { + tests := []struct { + name string + licenses []types.DetectedLicense + want []*common.DetectedLicense + }{ + { + name: "happy", + licenses: []types.DetectedLicense{ + { + Severity: "HIGH", + Category: "restricted", + PkgName: "alpine-baselayout", + FilePath: "some-path", + Name: "GPL-2.0", + Confidence: 1, + Link: "https://some-link", + }, + }, + want: []*common.DetectedLicense{ + { + Severity: common.Severity_HIGH, + Category: common.LicenseCategory_RESTRICTED, + PkgName: "alpine-baselayout", + FilePath: "some-path", + Name: "GPL-2.0", + Confidence: 1, + Link: "https://some-link", + }, + }, + }, + { + name: "no licenses", + licenses: nil, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertToRPCLicenses(tt.licenses) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestConvertToRPCLicenseCategory(t *testing.T) { + tests := []struct { + name string + category ftypes.LicenseCategory + want common.LicenseCategory_Enum + }{ + { + name: "happy", + category: ftypes.CategoryNotice, + want: common.LicenseCategory_NOTICE, + }, + { + name: "unspecified", + category: "", + want: common.LicenseCategory_UNSPECIFIED, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertToRPCLicenseCategory(tt.category) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestConvertFromRPCLicenseCategory(t *testing.T) { + tests := []struct { + name string + rpcCategory common.LicenseCategory_Enum + want ftypes.LicenseCategory + }{ + { + name: "happy", + rpcCategory: common.LicenseCategory_RESTRICTED, + want: ftypes.CategoryRestricted, + }, + { + name: "unspecified", + rpcCategory: common.LicenseCategory_UNSPECIFIED, + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertFromRPCLicenseCategory(tt.rpcCategory) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestConvertToRPCLicenseType(t *testing.T) { + tests := []struct { + name string + ty ftypes.LicenseType + want common.LicenseType_Enum + }{ + { + name: "happy", + ty: ftypes.LicenseTypeFile, + want: common.LicenseType_LICENSE_FILE, + }, + { + name: "unspecified", + ty: "", + want: common.LicenseType_UNSPECIFIED, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertToRPCLicenseType(tt.ty) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestConvertFromRPCLicenseType(t *testing.T) { + tests := []struct { + name string + rpcType common.LicenseType_Enum + want ftypes.LicenseType + }{ + { + name: "happy", + rpcType: common.LicenseType_LICENSE_FILE, + want: ftypes.LicenseTypeFile, + }, + { + name: "unspecified", + rpcType: common.LicenseType_UNSPECIFIED, + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConvertFromRPCLicenseType(tt.rpcType) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestConvertToRPCLicenseFiles(t *testing.T) { + tests := []struct { + name string + licenseFiles []ftypes.LicenseFile + want []*common.LicenseFile + }{ + { + name: "happy", + licenseFiles: []ftypes.LicenseFile{ + { + Type: ftypes.LicenseTypeFile, + PkgName: "alpine-baselayout", + FilePath: "some-path", + Findings: ftypes.LicenseFindings{ + { + Category: ftypes.CategoryRestricted, + Name: "GPL-2.0", + Confidence: 1, + Link: "https://some-link", + }, + }, + Layer: ftypes.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + }, + want: []*common.LicenseFile{ + { + LicenseType: common.LicenseType_LICENSE_FILE, + PkgName: "alpine-baselayout", + FilePath: "some-path", + Fingings: []*common.LicenseFinding{ + { + Category: common.LicenseCategory_RESTRICTED, + Name: "GPL-2.0", + Confidence: 1, + Link: "https://some-link", + }, + }, + Layer: &common.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, ConvertToRPCLicenseFiles(tt.licenseFiles)) + }) + } +} + +func TestConvertFromRPCLicenseFiles(t *testing.T) { + tests := []struct { + name string + licenseFiles []*common.LicenseFile + want []ftypes.LicenseFile + }{ + { + name: "happy", + licenseFiles: []*common.LicenseFile{ + { + LicenseType: common.LicenseType_LICENSE_FILE, + PkgName: "alpine-baselayout", + FilePath: "some-path", + Fingings: []*common.LicenseFinding{ + { + Category: common.LicenseCategory_RESTRICTED, + Name: "GPL-2.0", + Confidence: 1, + Link: "https://some-link", + }, + }, + Layer: &common.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + }, + want: []ftypes.LicenseFile{ + { + Type: ftypes.LicenseTypeFile, + PkgName: "alpine-baselayout", + FilePath: "some-path", + Findings: ftypes.LicenseFindings{ + { + Category: ftypes.CategoryRestricted, + Name: "GPL-2.0", + Confidence: 1, + Link: "https://some-link", + }, + }, + Layer: ftypes.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, ConvertFromRPCLicenseFiles(tt.licenseFiles)) + }) + } +} diff --git a/pkg/rpc/retry.go b/pkg/rpc/retry.go new file mode 100644 index 000000000000..1c807a04b465 --- /dev/null +++ b/pkg/rpc/retry.go @@ -0,0 +1,42 @@ +package rpc + +import ( + "time" + + "github.com/cenkalti/backoff" + "github.com/twitchtv/twirp" + + "github.com/aquasecurity/trivy/pkg/log" +) + +const ( + maxRetries = 10 +) + +// Retry executes the function again using backoff until maxRetries or success +func Retry(f func() error) error { + operation := func() error { + err := f() + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + return backoff.Permanent(err) + } + if twerr.Code() == twirp.Unavailable { + return err + } + return backoff.Permanent(err) + } + return nil + } + + b := backoff.WithMaxRetries(backoff.NewExponentialBackOff(), maxRetries) + err := backoff.RetryNotify(operation, b, func(err error, _ time.Duration) { + log.Logger.Warn(err) + log.Logger.Info("Retrying HTTP request...") + }) + if err != nil { + return err + } + return nil +} diff --git a/pkg/rpc/server/inject.go b/pkg/rpc/server/inject.go new file mode 100644 index 000000000000..453e4e80a892 --- /dev/null +++ b/pkg/rpc/server/inject.go @@ -0,0 +1,15 @@ +//go:build wireinject +// +build wireinject + +package server + +import ( + "github.com/google/wire" + + "github.com/aquasecurity/trivy/pkg/fanal/cache" +) + +func initializeScanServer(localArtifactCache cache.LocalArtifactCache) *ScanServer { + wire.Build(ScanSuperSet) + return &ScanServer{} +} diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go new file mode 100644 index 000000000000..7433bf20a560 --- /dev/null +++ b/pkg/rpc/server/listen.go @@ -0,0 +1,193 @@ +package server + +import ( + "context" + "encoding/json" + "net/http" + "os" + "sync" + "time" + + "github.com/NYTimes/gziphandler" + "github.com/google/go-containerregistry/pkg/name" + "github.com/twitchtv/twirp" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy-db/pkg/metadata" + dbc "github.com/aquasecurity/trivy/pkg/db" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + "github.com/aquasecurity/trivy/pkg/version" + rpcCache "github.com/aquasecurity/trivy/rpc/cache" + rpcScanner "github.com/aquasecurity/trivy/rpc/scanner" +) + +const updateInterval = 1 * time.Hour + +// Server represents Trivy server +type Server struct { + appVersion string + addr string + cacheDir string + token string + tokenHeader string + dbRepository name.Reference + + // For OCI registries + types.RegistryOptions +} + +// NewServer returns an instance of Server +func NewServer(appVersion, addr, cacheDir, token, tokenHeader string, dbRepository name.Reference, opt types.RegistryOptions) Server { + return Server{ + appVersion: appVersion, + addr: addr, + cacheDir: cacheDir, + token: token, + tokenHeader: tokenHeader, + dbRepository: dbRepository, + RegistryOptions: opt, + } +} + +// ListenAndServe starts Trivy server +func (s Server) ListenAndServe(ctx context.Context, serverCache cache.Cache, skipDBUpdate bool) error { + requestWg := &sync.WaitGroup{} + dbUpdateWg := &sync.WaitGroup{} + + go func() { + worker := newDBWorker(dbc.NewClient(s.cacheDir, true, dbc.WithDBRepository(s.dbRepository))) + for { + time.Sleep(updateInterval) + if err := worker.update(ctx, s.appVersion, s.cacheDir, skipDBUpdate, dbUpdateWg, requestWg, s.RegistryOptions); err != nil { + log.Logger.Errorf("%+v\n", err) + } + } + }() + + mux := newServeMux(ctx, serverCache, dbUpdateWg, requestWg, s.token, s.tokenHeader, s.cacheDir) + log.Logger.Infof("Listening %s...", s.addr) + + return http.ListenAndServe(s.addr, mux) +} + +func newServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup, + token, tokenHeader, cacheDir string) *http.ServeMux { + withWaitGroup := func(base http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Stop processing requests during DB update + dbUpdateWg.Wait() + + // Wait for all requests to be processed before DB update + requestWg.Add(1) + defer requestWg.Done() + + base.ServeHTTP(w, r.WithContext(ctx)) + + }) + } + + mux := http.NewServeMux() + + scanServer := rpcScanner.NewScannerServer(initializeScanServer(serverCache), nil) + scanHandler := withToken(withWaitGroup(scanServer), token, tokenHeader) + mux.Handle(rpcScanner.ScannerPathPrefix, gziphandler.GzipHandler(scanHandler)) + + layerServer := rpcCache.NewCacheServer(NewCacheServer(serverCache), nil) + layerHandler := withToken(withWaitGroup(layerServer), token, tokenHeader) + mux.Handle(rpcCache.CachePathPrefix, gziphandler.GzipHandler(layerHandler)) + + mux.HandleFunc("/healthz", func(rw http.ResponseWriter, r *http.Request) { + if _, err := rw.Write([]byte("ok")); err != nil { + log.Logger.Errorf("health check error: %s", err) + } + }) + + mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", "application/json") + + if err := json.NewEncoder(w).Encode(version.NewVersionInfo(cacheDir)); err != nil { + log.Logger.Errorf("get version error: %s", err) + } + }) + + return mux +} + +func withToken(base http.Handler, token, tokenHeader string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if token != "" && token != r.Header.Get(tokenHeader) { + rpcScanner.WriteError(w, twirp.NewError(twirp.Unauthenticated, "invalid token")) + return + } + base.ServeHTTP(w, r) + }) +} + +type dbWorker struct { + dbClient dbc.Operation +} + +func newDBWorker(dbClient dbc.Operation) dbWorker { + return dbWorker{dbClient: dbClient} +} + +func (w dbWorker) update(ctx context.Context, appVersion, cacheDir string, + skipDBUpdate bool, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RegistryOptions) error { + log.Logger.Debug("Check for DB update...") + needsUpdate, err := w.dbClient.NeedsUpdate(appVersion, skipDBUpdate) + if err != nil { + return xerrors.Errorf("failed to check if db needs an update") + } else if !needsUpdate { + return nil + } + + log.Logger.Info("Updating DB...") + if err = w.hotUpdate(ctx, cacheDir, dbUpdateWg, requestWg, opt); err != nil { + return xerrors.Errorf("failed DB hot update: %w", err) + } + return nil +} + +func (w dbWorker) hotUpdate(ctx context.Context, cacheDir string, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RegistryOptions) error { + tmpDir, err := os.MkdirTemp("", "db") + if err != nil { + return xerrors.Errorf("failed to create a temp dir: %w", err) + } + defer os.RemoveAll(tmpDir) + + if err = w.dbClient.Download(ctx, tmpDir, opt); err != nil { + return xerrors.Errorf("failed to download vulnerability DB: %w", err) + } + + log.Logger.Info("Suspending all requests during DB update") + dbUpdateWg.Add(1) + defer dbUpdateWg.Done() + + log.Logger.Info("Waiting for all requests to be processed before DB update...") + requestWg.Wait() + + if err = db.Close(); err != nil { + return xerrors.Errorf("failed to close DB: %w", err) + } + + // Copy trivy.db + if _, err = fsutils.CopyFile(db.Path(tmpDir), db.Path(cacheDir)); err != nil { + return xerrors.Errorf("failed to copy the database file: %w", err) + } + + // Copy metadata.json + if _, err = fsutils.CopyFile(metadata.Path(tmpDir), metadata.Path(cacheDir)); err != nil { + return xerrors.Errorf("failed to copy the metadata file: %w", err) + } + + log.Logger.Info("Reopening DB...") + if err = db.Init(cacheDir); err != nil { + return xerrors.Errorf("failed to open DB: %w", err) + } + + return nil +} diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go new file mode 100644 index 000000000000..5fff3f3bc46e --- /dev/null +++ b/pkg/rpc/server/listen_test.go @@ -0,0 +1,315 @@ +package server + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "path" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy-db/pkg/metadata" + dbFile "github.com/aquasecurity/trivy/pkg/db" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/policy" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + "github.com/aquasecurity/trivy/pkg/version" + rpcCache "github.com/aquasecurity/trivy/rpc/cache" +) + +func Test_dbWorker_update(t *testing.T) { + timeNextUpdate := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC) + timeUpdateAt := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC) + + type needsUpdateInput struct { + appVersion string + skip bool + } + type needsUpdateOutput struct { + needsUpdate bool + err error + } + type needsUpdate struct { + input needsUpdateInput + output needsUpdateOutput + } + + type download struct { + call bool + err error + } + + type args struct { + appVersion string + } + tests := []struct { + name string + needsUpdate needsUpdate + download download + args args + want metadata.Metadata + wantErr string + }{ + { + name: "happy path", + needsUpdate: needsUpdate{ + input: needsUpdateInput{ + appVersion: "1", + skip: false, + }, + output: needsUpdateOutput{needsUpdate: true}, + }, + download: download{ + call: true, + }, + args: args{appVersion: "1"}, + want: metadata.Metadata{ + Version: 1, + NextUpdate: timeNextUpdate, + UpdatedAt: timeUpdateAt, + }, + }, + { + name: "not update", + needsUpdate: needsUpdate{ + input: needsUpdateInput{ + appVersion: "1", + skip: false, + }, + output: needsUpdateOutput{needsUpdate: false}, + }, + args: args{appVersion: "1"}, + }, + { + name: "skip update", + needsUpdate: needsUpdate{ + input: needsUpdateInput{ + appVersion: "1", + skip: true, + }, + output: needsUpdateOutput{needsUpdate: false}, + }, + args: args{appVersion: "1"}, + }, + { + name: "NeedsUpdate returns an error", + needsUpdate: needsUpdate{ + input: needsUpdateInput{ + appVersion: "1", + skip: false, + }, + output: needsUpdateOutput{err: xerrors.New("fail")}, + }, + args: args{appVersion: "1"}, + wantErr: "failed to check if db needs an update", + }, + { + name: "Download returns an error", + needsUpdate: needsUpdate{ + input: needsUpdateInput{ + appVersion: "1", + skip: false, + }, + output: needsUpdateOutput{needsUpdate: true}, + }, + download: download{ + call: true, + err: xerrors.New("fail"), + }, + args: args{appVersion: "1"}, + wantErr: "failed DB hot update", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cacheDir := t.TempDir() + + require.NoError(t, db.Init(cacheDir), tt.name) + + mockDBClient := new(dbFile.MockOperation) + mockDBClient.On("NeedsUpdate", + tt.needsUpdate.input.appVersion, tt.needsUpdate.input.skip).Return( + tt.needsUpdate.output.needsUpdate, tt.needsUpdate.output.err) + + defer func() { _ = db.Close() }() + + if tt.download.call { + mockDBClient.On("Download", mock.Anything, mock.Anything, mock.Anything).Run( + func(args mock.Arguments) { + // fake download: copy testdata/new.db to tmpDir/db/trivy.db + tmpDir := args.String(1) + err := os.MkdirAll(db.Dir(tmpDir), 0744) + require.NoError(t, err) + + _, err = fsutils.CopyFile("testdata/new.db", db.Path(tmpDir)) + require.NoError(t, err) + + // fake download: copy testdata/metadata.json to tmpDir/db/metadata.json + _, err = fsutils.CopyFile("testdata/metadata.json", metadata.Path(tmpDir)) + require.NoError(t, err) + }).Return(tt.download.err) + } + + w := newDBWorker(mockDBClient) + + var dbUpdateWg, requestWg sync.WaitGroup + err := w.update(context.Background(), tt.args.appVersion, cacheDir, + tt.needsUpdate.input.skip, &dbUpdateWg, &requestWg, ftypes.RegistryOptions{}) + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } + require.NoError(t, err, tt.name) + + if !tt.download.call { + return + } + + mc := metadata.NewClient(cacheDir) + got, err := mc.Get() + assert.NoError(t, err, tt.name) + assert.Equal(t, tt.want, got, tt.name) + + mockDBClient.AssertExpectations(t) + }) + } +} + +func Test_newServeMux(t *testing.T) { + type args struct { + token string + tokenHeader string + } + tests := []struct { + name string + args args + path string + header http.Header + want int + }{ + { + name: "health check", + path: "/healthz", + want: http.StatusOK, + }, + { + name: "cache endpoint", + path: path.Join(rpcCache.CachePathPrefix, "MissingBlobs"), + header: http.Header{ + "Content-Type": []string{"application/protobuf"}, + }, + want: http.StatusOK, + }, + { + name: "with token", + args: args{ + token: "test", + tokenHeader: "Authorization", + }, + path: path.Join(rpcCache.CachePathPrefix, "MissingBlobs"), + header: http.Header{ + "Authorization": []string{"test"}, + "Content-Type": []string{"application/protobuf"}, + }, + want: http.StatusOK, + }, + { + name: "sad path: no handler", + path: "/sad", + header: http.Header{ + "Content-Type": []string{"application/protobuf"}, + }, + want: http.StatusNotFound, + }, + { + name: "sad path: invalid token", + args: args{ + token: "test", + tokenHeader: "Authorization", + }, + path: path.Join(rpcCache.CachePathPrefix, "MissingBlobs"), + header: http.Header{ + "Content-Type": []string{"application/protobuf"}, + }, + want: http.StatusUnauthorized, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dbUpdateWg, requestWg := &sync.WaitGroup{}, &sync.WaitGroup{} + + c, err := cache.NewFSCache(t.TempDir()) + require.NoError(t, err) + defer func() { _ = c.Close() }() + + ts := httptest.NewServer(newServeMux(context.Background(), c, dbUpdateWg, requestWg, tt.args.token, + tt.args.tokenHeader, ""), + ) + defer ts.Close() + + var resp *http.Response + url := ts.URL + tt.path + if tt.header == nil { + resp, err = http.Get(url) + } else { + req, err := http.NewRequest(http.MethodPost, url, nil) + require.NoError(t, err) + + req.Header = tt.header + client := new(http.Client) + resp, err = client.Do(req) + } + + require.NoError(t, err) + assert.Equal(t, tt.want, resp.StatusCode) + defer resp.Body.Close() + }) + } +} + +func Test_VersionEndpoint(t *testing.T) { + dbUpdateWg, requestWg := &sync.WaitGroup{}, &sync.WaitGroup{} + c, err := cache.NewFSCache(t.TempDir()) + require.NoError(t, err) + defer func() { _ = c.Close() }() + + ts := httptest.NewServer(newServeMux(context.Background(), c, dbUpdateWg, requestWg, "", "", + "testdata/testcache"), + ) + defer ts.Close() + + resp, err := http.Get(ts.URL + "/version") + require.NoError(t, err) + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + var versionInfo version.VersionInfo + require.NoError(t, json.NewDecoder(resp.Body).Decode(&versionInfo)) + + expected := version.VersionInfo{ + Version: "dev", + VulnerabilityDB: &metadata.Metadata{ + Version: 2, + NextUpdate: time.Date(2023, 7, 20, 18, 11, 37, 696263532, time.UTC), + UpdatedAt: time.Date(2023, 7, 20, 12, 11, 37, 696263932, time.UTC), + DownloadedAt: time.Date(2023, 7, 25, 7, 1, 41, 239158000, time.UTC), + }, + PolicyBundle: &policy.Metadata{ + Digest: "sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43", + DownloadedAt: time.Date(2023, 7, 23, 16, 40, 33, 122462000, time.UTC), + }, + } + assert.Equal(t, expected, versionInfo) +} diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go new file mode 100644 index 000000000000..d4cb91294def --- /dev/null +++ b/pkg/rpc/server/server.go @@ -0,0 +1,116 @@ +package server + +import ( + "context" + + google_protobuf "github.com/golang/protobuf/ptypes/empty" + "github.com/google/wire" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/rpc" + "github.com/aquasecurity/trivy/pkg/scanner" + "github.com/aquasecurity/trivy/pkg/scanner/local" + "github.com/aquasecurity/trivy/pkg/types" + rpcCache "github.com/aquasecurity/trivy/rpc/cache" + rpcScanner "github.com/aquasecurity/trivy/rpc/scanner" +) + +// ScanSuperSet binds the dependencies for server +var ScanSuperSet = wire.NewSet( + local.SuperSet, + wire.Bind(new(scanner.Driver), new(local.Scanner)), + NewScanServer, +) + +// ScanServer implements the scanner +type ScanServer struct { + localScanner scanner.Driver +} + +// NewScanServer is the factory method for scanner +func NewScanServer(s scanner.Driver) *ScanServer { + return &ScanServer{localScanner: s} +} + +// Log and return an error +func teeError(err error) error { + log.Logger.Errorf("%+v", err) + return err +} + +// Scan scans and return response +func (s *ScanServer) Scan(ctx context.Context, in *rpcScanner.ScanRequest) (*rpcScanner.ScanResponse, error) { + scanners := lo.Map(in.Options.Scanners, func(s string, index int) types.Scanner { + return types.Scanner(s) + }) + options := types.ScanOptions{ + VulnType: in.Options.VulnType, + Scanners: scanners, + ListAllPackages: in.Options.ListAllPackages, + IncludeDevDeps: in.Options.IncludeDevDeps, + } + results, os, err := s.localScanner.Scan(ctx, in.Target, in.ArtifactId, in.BlobIds, options) + if err != nil { + return nil, teeError(xerrors.Errorf("failed scan, %s: %w", in.Target, err)) + } + + return rpc.ConvertToRPCScanResponse(results, os), nil +} + +// CacheServer implements the cache +type CacheServer struct { + cache cache.Cache +} + +// NewCacheServer is the factory method for cacheServer +func NewCacheServer(c cache.Cache) *CacheServer { + return &CacheServer{cache: c} +} + +// PutArtifact puts the artifacts in cache +func (s *CacheServer) PutArtifact(_ context.Context, in *rpcCache.PutArtifactRequest) (*google_protobuf.Empty, error) { + if in.ArtifactInfo == nil { + return nil, teeError(xerrors.Errorf("empty image info")) + } + imageInfo := rpc.ConvertFromRPCPutArtifactRequest(in) + if err := s.cache.PutArtifact(in.ArtifactId, imageInfo); err != nil { + return nil, teeError(xerrors.Errorf("unable to store image info in cache: %w", err)) + } + return &google_protobuf.Empty{}, nil +} + +// PutBlob puts the blobs in cache +func (s *CacheServer) PutBlob(_ context.Context, in *rpcCache.PutBlobRequest) (*google_protobuf.Empty, error) { + if in.BlobInfo == nil { + return nil, teeError(xerrors.Errorf("empty layer info")) + } + layerInfo := rpc.ConvertFromRPCPutBlobRequest(in) + if err := s.cache.PutBlob(in.DiffId, layerInfo); err != nil { + return nil, teeError(xerrors.Errorf("unable to store layer info in cache: %w", err)) + } + return &google_protobuf.Empty{}, nil +} + +// MissingBlobs returns missing blobs from cache +func (s *CacheServer) MissingBlobs(_ context.Context, in *rpcCache.MissingBlobsRequest) (*rpcCache.MissingBlobsResponse, error) { + missingArtifact, blobIDs, err := s.cache.MissingBlobs(in.ArtifactId, in.BlobIds) + if err != nil { + return nil, teeError(xerrors.Errorf("failed to get missing blobs: %w", err)) + } + return &rpcCache.MissingBlobsResponse{ + MissingArtifact: missingArtifact, + MissingBlobIds: blobIDs, + }, nil +} + +// DeleteBlobs removes blobs by IDs +func (s *CacheServer) DeleteBlobs(_ context.Context, in *rpcCache.DeleteBlobsRequest) (*google_protobuf.Empty, error) { + blobIDs := rpc.ConvertFromDeleteBlobsRequest(in) + if err := s.cache.DeleteBlobs(blobIDs); err != nil { + return nil, teeError(xerrors.Errorf("failed to remove a blobs: %w", err)) + } + return &google_protobuf.Empty{}, nil +} diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go new file mode 100644 index 000000000000..3022ff2cdf0a --- /dev/null +++ b/pkg/rpc/server/server_test.go @@ -0,0 +1,588 @@ +package server + +import ( + "context" + "errors" + "testing" + "time" + + google_protobuf "github.com/golang/protobuf/ptypes/empty" + "github.com/golang/protobuf/ptypes/timestamp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/timestamppb" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/utils" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/scanner" + "github.com/aquasecurity/trivy/pkg/types" + rpcCache "github.com/aquasecurity/trivy/rpc/cache" + "github.com/aquasecurity/trivy/rpc/common" + rpcScanner "github.com/aquasecurity/trivy/rpc/scanner" +) + +type mockCache struct { + cache.MockArtifactCache + cache.MockLocalArtifactCache +} + +func TestScanServer_Scan(t *testing.T) { + type args struct { + in *rpcScanner.ScanRequest + } + tests := []struct { + name string + args args + scanExpectation scanner.DriverScanExpectation + want *rpcScanner.ScanResponse + wantErr string + }{ + { + name: "happy path", + args: args{ + in: &rpcScanner.ScanRequest{ + Target: "alpine:3.11", + ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + BlobIds: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + Options: &rpcScanner.ScanOptions{}, + }, + }, + scanExpectation: scanner.DriverScanExpectation{ + Args: scanner.DriverScanArgs{ + CtxAnything: true, + Target: "alpine:3.11", + ImageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + LayerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + OptionsAnything: true, + }, + Returns: scanner.DriverScanReturns{ + Results: types.Results{ + { + Target: "alpine:3.11 (alpine 3.11)", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + SeveritySource: "nvd", + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: "MEDIUM", + VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{ + vulnerability.NVD: dbTypes.SeverityMedium, + }, + References: []string{"http://example.com"}, + LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), + PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001", + DataSource: &dbTypes.DataSource{ + Name: "DOS vulnerabilities", + URL: "https://vuld-db-example.com/", + }, + }, + }, + Type: "alpine", + }, + }, + OsFound: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + }, + }, + want: &rpcScanner.ScanResponse{ + Os: &common.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + Results: []*rpcScanner.Result{ + { + Target: "alpine:3.11 (alpine 3.11)", + Vulnerabilities: []*common.Vulnerability{ + { + VulnerabilityId: "CVE-2019-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Severity: common.Severity_MEDIUM, + SeveritySource: "nvd", + Layer: &common.Layer{}, + Cvss: map[string]*common.CVSS{}, + VendorSeverity: map[string]common.Severity{ + string(vulnerability.NVD): common.Severity_MEDIUM, + }, + PrimaryUrl: "https://avd.aquasec.com/nvd/cve-2019-0001", + Title: "dos", + Description: "dos vulnerability", + References: []string{"http://example.com"}, + LastModifiedDate: ×tamp.Timestamp{ + Seconds: 1577840460, + }, + PublishedDate: ×tamp.Timestamp{ + Seconds: 978310860, + }, + DataSource: &common.DataSource{ + Name: "DOS vulnerabilities", + Url: "https://vuld-db-example.com/", + }, + }, + }, + Type: "alpine", + }, + }, + }, + }, + { + name: "sad path: Scan returns an error", + args: args{ + in: &rpcScanner.ScanRequest{ + Target: "alpine:3.11", + ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + BlobIds: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + Options: &rpcScanner.ScanOptions{}, + }, + }, + scanExpectation: scanner.DriverScanExpectation{ + Args: scanner.DriverScanArgs{ + CtxAnything: true, + Target: "alpine:3.11", + ImageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + LayerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + OptionsAnything: true, + }, + Returns: scanner.DriverScanReturns{ + Err: errors.New("error"), + }, + }, + wantErr: "failed scan, alpine:3.11", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockDriver := new(scanner.MockDriver) + mockDriver.ApplyScanExpectation(tt.scanExpectation) + + s := NewScanServer(mockDriver) + got, err := s.Scan(context.Background(), tt.args.in) + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } + assert.NoError(t, err, tt.name) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestCacheServer_PutArtifact(t *testing.T) { + type args struct { + in *rpcCache.PutArtifactRequest + } + tests := []struct { + name string + args args + putImage cache.ArtifactCachePutArtifactExpectation + want *google_protobuf.Empty + wantErr string + }{ + { + name: "happy path", + args: args{ + in: &rpcCache.PutArtifactRequest{ + ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + ArtifactInfo: &rpcCache.ArtifactInfo{ + SchemaVersion: 1, + Architecture: "amd64", + Created: func() *timestamp.Timestamp { + d := time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC) + t := timestamppb.New(d) + return t + }(), + DockerVersion: "18.09", + Os: "linux", + }, + }, + }, + putImage: cache.ArtifactCachePutArtifactExpectation{ + Args: cache.ArtifactCachePutArtifactArgs{ + ArtifactID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + ArtifactInfo: ftypes.ArtifactInfo{ + SchemaVersion: 1, + Architecture: "amd64", + Created: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), + DockerVersion: "18.09", + OS: "linux", + }, + }, + }, + want: &google_protobuf.Empty{}, + }, + { + name: "sad path", + args: args{ + in: &rpcCache.PutArtifactRequest{ + ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + ArtifactInfo: &rpcCache.ArtifactInfo{ + SchemaVersion: 1, + Created: func() *timestamp.Timestamp { + d := time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC) + t := timestamppb.New(d) + return t + }(), + }, + }, + }, + putImage: cache.ArtifactCachePutArtifactExpectation{ + Args: cache.ArtifactCachePutArtifactArgs{ + ArtifactID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + ArtifactInfo: ftypes.ArtifactInfo{ + SchemaVersion: 1, + Created: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), + }, + }, + Returns: cache.ArtifactCachePutArtifactReturns{ + Err: xerrors.New("error"), + }, + }, + wantErr: "unable to store image info in cache", + }, + { + name: "sad path: empty image info", + args: args{ + in: &rpcCache.PutArtifactRequest{}, + }, + wantErr: "empty image info", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCache := new(mockCache) + mockCache.ApplyPutArtifactExpectation(tt.putImage) + + s := NewCacheServer(mockCache) + got, err := s.PutArtifact(context.Background(), tt.args.in) + + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } else { + assert.NoError(t, err, tt.name) + } + + assert.Equal(t, tt.want, got) + }) + } +} + +func TestCacheServer_PutBlob(t *testing.T) { + type args struct { + in *rpcCache.PutBlobRequest + } + tests := []struct { + name string + args args + putLayer cache.ArtifactCachePutBlobExpectation + want *google_protobuf.Empty + wantErr string + }{ + { + name: "happy path", + args: args{ + in: &rpcCache.PutBlobRequest{ + DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + BlobInfo: &rpcCache.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + Os: &common.OS{ + Family: "alpine", + Name: "3.11", + }, + PackageInfos: []*common.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: []*common.Package{ + { + Name: "binary", + Version: "1.2.3", + Release: "1", + Epoch: 2, + Arch: "x86_64", + SrcName: "src", + SrcVersion: "1.2.3", + SrcRelease: "1", + SrcEpoch: 2, + Layer: &common.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + { + Name: "vim-minimal", + Version: "7.4.160", + Release: "5.el7", + Epoch: 2, + Arch: "x86_64", + SrcName: "vim", + SrcVersion: "7.4.160", + SrcRelease: "5.el7", + SrcEpoch: 2, + Layer: &common.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + { + Name: "node-minimal", + Version: "17.1.0", + Release: "5.el7", + Epoch: 2, + Arch: "x86_64", + SrcName: "node", + SrcVersion: "17.1.0", + SrcRelease: "5.el7", + SrcEpoch: 2, + Layer: nil, + }, + }, + }, + }, + Applications: []*common.Application{ + { + Type: "composer", + FilePath: "php-app/composer.lock", + Libraries: []*common.Package{ + { + Name: "guzzlehttp/guzzle", + Version: "6.2.0", + }, + { + Name: "guzzlehttp/promises", + Version: "v1.3.1", + }, + }, + }, + }, + OpaqueDirs: []string{"etc/"}, + WhiteoutFiles: []string{"etc/hostname"}, + }, + }, + }, + putLayer: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + BlobInfo: ftypes.BlobInfo{ + SchemaVersion: 1, + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + }, + PackageInfos: []ftypes.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: ftypes.Packages{ + { + Name: "binary", + Version: "1.2.3", + Release: "1", + Epoch: 2, + Arch: "x86_64", + SrcName: "src", + SrcVersion: "1.2.3", + SrcRelease: "1", + SrcEpoch: 2, + Layer: ftypes.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + { + Name: "vim-minimal", + Version: "7.4.160", + Release: "5.el7", + Epoch: 2, + Arch: "x86_64", + SrcName: "vim", + SrcVersion: "7.4.160", + SrcRelease: "5.el7", + SrcEpoch: 2, + Layer: ftypes.Layer{ + Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + { + Name: "node-minimal", + Version: "17.1.0", + Release: "5.el7", + Epoch: 2, + Arch: "x86_64", + SrcName: "node", + SrcVersion: "17.1.0", + SrcRelease: "5.el7", + SrcEpoch: 2, + Layer: ftypes.Layer{}, + }, + }, + }, + }, + Applications: []ftypes.Application{ + { + Type: "composer", + FilePath: "php-app/composer.lock", + Libraries: ftypes.Packages{ + { + Name: "guzzlehttp/guzzle", + Version: "6.2.0", + }, + { + Name: "guzzlehttp/promises", + Version: "v1.3.1", + }, + }, + }, + }, + OpaqueDirs: []string{"etc/"}, + WhiteoutFiles: []string{"etc/hostname"}, + }, + }, + }, + want: &google_protobuf.Empty{}, + }, + { + name: "sad path", + args: args{ + in: &rpcCache.PutBlobRequest{ + BlobInfo: &rpcCache.BlobInfo{ + SchemaVersion: 1, + }, + }, + }, + putLayer: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfoAnything: true, + }, + Returns: cache.ArtifactCachePutBlobReturns{ + Err: xerrors.New("error"), + }, + }, + wantErr: "unable to store layer info in cache", + }, + { + name: "sad path: empty layer info", + args: args{ + in: &rpcCache.PutBlobRequest{}, + }, + putLayer: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfoAnything: true, + }, + Returns: cache.ArtifactCachePutBlobReturns{ + Err: xerrors.New("error"), + }, + }, + wantErr: "empty layer info", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCache := new(mockCache) + mockCache.ApplyPutBlobExpectation(tt.putLayer) + + s := NewCacheServer(mockCache) + got, err := s.PutBlob(context.Background(), tt.args.in) + + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } else { + assert.NoError(t, err, tt.name) + } + + assert.Equal(t, tt.want, got) + }) + } +} + +func TestCacheServer_MissingBlobs(t *testing.T) { + type args struct { + ctx context.Context + in *rpcCache.MissingBlobsRequest + } + tests := []struct { + name string + args args + getArtifactCacheMissingBlobsExpectations []cache.ArtifactCacheMissingBlobsExpectation + want *rpcCache.MissingBlobsResponse + wantErr string + }{ + { + name: "happy path", + args: args{ + in: &rpcCache.MissingBlobsRequest{ + ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + BlobIds: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + }, + }, + getArtifactCacheMissingBlobsExpectations: []cache.ArtifactCacheMissingBlobsExpectation{ + { + Args: cache.ArtifactCacheMissingBlobsArgs{ + ArtifactID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + BlobIDs: []string{ + "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + }, + }, + Returns: cache.ArtifactCacheMissingBlobsReturns{ + MissingArtifact: false, + MissingBlobIDs: []string{"sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5"}, + Err: nil, + }, + }, + }, + want: &rpcCache.MissingBlobsResponse{ + MissingArtifact: false, + MissingBlobIds: []string{"sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCache := new(mockCache) + mockCache.ApplyMissingBlobsExpectations(tt.getArtifactCacheMissingBlobsExpectations) + + s := NewCacheServer(mockCache) + got, err := s.MissingBlobs(tt.args.ctx, tt.args.in) + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } else { + assert.NoError(t, err, tt.name) + } + + assert.Equal(t, tt.want, got) + mockCache.MockArtifactCache.AssertExpectations(t) + }) + } +} diff --git a/pkg/rpc/server/testdata/metadata.json b/pkg/rpc/server/testdata/metadata.json new file mode 100644 index 000000000000..dfc2957b6295 --- /dev/null +++ b/pkg/rpc/server/testdata/metadata.json @@ -0,0 +1 @@ +{"Version":1,"NextUpdate":"3000-01-01T0:00:00.0Z","UpdatedAt":"3000-01-01T0:00:00.0Z"} \ No newline at end of file diff --git a/pkg/rpc/server/testdata/new.db b/pkg/rpc/server/testdata/new.db new file mode 100644 index 000000000000..4ee279a13d3a Binary files /dev/null and b/pkg/rpc/server/testdata/new.db differ diff --git a/pkg/rpc/server/testdata/testcache/db/metadata.json b/pkg/rpc/server/testdata/testcache/db/metadata.json new file mode 100644 index 000000000000..e9a4157ed5c5 --- /dev/null +++ b/pkg/rpc/server/testdata/testcache/db/metadata.json @@ -0,0 +1 @@ +{"Version":2,"NextUpdate":"2023-07-20T18:11:37.696263532Z","UpdatedAt":"2023-07-20T12:11:37.696263932Z","DownloadedAt":"2023-07-25T07:01:41.239158Z"} \ No newline at end of file diff --git a/pkg/rpc/server/testdata/testcache/policy/metadata.json b/pkg/rpc/server/testdata/testcache/policy/metadata.json new file mode 100644 index 000000000000..18f6f4801597 --- /dev/null +++ b/pkg/rpc/server/testdata/testcache/policy/metadata.json @@ -0,0 +1 @@ +{"Digest":"sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43","DownloadedAt":"2023-07-23T17:40:33.122462+01:00"} \ No newline at end of file diff --git a/pkg/rpc/server/wire_gen.go b/pkg/rpc/server/wire_gen.go new file mode 100644 index 000000000000..81f5ba451a72 --- /dev/null +++ b/pkg/rpc/server/wire_gen.go @@ -0,0 +1,30 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package server + +import ( + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/fanal/applier" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/scanner/langpkg" + "github.com/aquasecurity/trivy/pkg/scanner/local" + "github.com/aquasecurity/trivy/pkg/scanner/ospkg" + "github.com/aquasecurity/trivy/pkg/vulnerability" +) + +// Injectors from inject.go: + +func initializeScanServer(localArtifactCache cache.LocalArtifactCache) *ScanServer { + applierApplier := applier.NewApplier(localArtifactCache) + scanner := ospkg.NewScanner() + langpkgScanner := langpkg.NewScanner() + config := db.Config{} + client := vulnerability.NewClient(config) + localScanner := local.NewScanner(applierApplier, scanner, langpkgScanner, client) + scanServer := NewScanServer(localScanner) + return scanServer +} diff --git a/pkg/run.go b/pkg/run.go deleted file mode 100644 index 87d83326a8eb..000000000000 --- a/pkg/run.go +++ /dev/null @@ -1,134 +0,0 @@ -package pkg - -import ( - l "log" - "os" - "strings" - - "github.com/knqyf263/fanal/cache" - - "github.com/knqyf263/trivy/pkg/utils" - - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "github.com/knqyf263/trivy/pkg/report" - "github.com/knqyf263/trivy/pkg/scanner" - "github.com/knqyf263/trivy/pkg/vulnsrc" - - "github.com/urfave/cli" - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/db" - "github.com/knqyf263/trivy/pkg/log" -) - -func Run(c *cli.Context) (err error) { - cliVersion := c.App.Version - - debug := c.Bool("debug") - if err = log.InitLogger(debug); err != nil { - l.Fatal(err) - } - log.Logger.Debugf("cache dir: %s", utils.CacheDir()) - - clean := c.Bool("clean") - if clean { - log.Logger.Info("Cleaning caches...") - if err = cache.Clear(); err != nil { - return xerrors.New("failed to remove image layer cache") - } - if err = os.RemoveAll(utils.CacheDir()); err != nil { - return xerrors.New("failed to remove cache") - } - return nil - } - - args := c.Args() - filePath := c.String("input") - if filePath == "" && len(args) == 0 { - log.Logger.Info(`trivy" requires at least 1 argument or --input option.`) - cli.ShowAppHelpAndExit(c, 1) - } - - utils.Quiet = c.Bool("quiet") - - o := c.String("output") - output := os.Stdout - if o != "" { - if output, err = os.Create(o); err != nil { - return xerrors.Errorf("failed to create an output file: %w", err) - } - } - - var severities []vulnerability.Severity - for _, s := range strings.Split(c.String("severity"), ",") { - severity, err := vulnerability.NewSeverity(s) - if err != nil { - log.Logger.Infof("error in severity option: %s", err) - cli.ShowAppHelpAndExit(c, 1) - } - severities = append(severities, severity) - } - - if c.Bool("refresh") { - log.Logger.Info("Resetting DB...") - if err = db.Reset(); err != nil { - return xerrors.Errorf("error in refresh DB: %w", err) - } - } - - if err = db.Init(); err != nil { - return xerrors.Errorf("error in vulnerability DB initialize: %w", err) - } - - dbVersion := db.GetVersion() - if dbVersion != "" && dbVersion != cliVersion { - log.Logger.Fatal("Detected version update of trivy. Please try again with --refresh option") - } - - if !c.Bool("skip-update") { - if err = vulnsrc.Update(); err != nil { - return xerrors.Errorf("error in vulnerability DB update: %w", err) - } - } - - ignoreUnfixed := c.Bool("ignore-unfixed") - - var imageName string - if filePath == "" { - imageName = args[0] - } - results, err := scanner.ScanImage(imageName, filePath, severities, ignoreUnfixed) - if err != nil { - return xerrors.Errorf("error in image scan: %w", err) - } - - var writer report.Writer - switch c.String("format") { - case "table": - writer = &report.TableWriter{Output: output} - case "json": - writer = &report.JsonWriter{Output: output} - default: - xerrors.New("unknown format") - } - - if err = writer.Write(results); err != nil { - return xerrors.Errorf("failed to write results: %w", err) - } - - if err = db.SetVersion(cliVersion); err != nil { - return xerrors.Errorf("unexpected error: %w", err) - } - - exitCode := c.Int("exit-code") - if exitCode != 0 { - for _, result := range results { - if len(result.Vulnerabilities) > 0 { - os.Exit(exitCode) - } - } - } - - return nil -} diff --git a/pkg/sbom/core/bom.go b/pkg/sbom/core/bom.go new file mode 100644 index 000000000000..893796a0fe0d --- /dev/null +++ b/pkg/sbom/core/bom.go @@ -0,0 +1,329 @@ +package core + +import ( + "sort" + + "github.com/package-url/packageurl-go" + + dtypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/uuid" +) + +const ( + TypeFilesystem ComponentType = "filesystem" + TypeRepository ComponentType = "repository" + TypeContainerImage ComponentType = "container_image" + TypeVM ComponentType = "vm" + TypeApplication ComponentType = "application" + TypeLibrary ComponentType = "library" + TypeOS ComponentType = "os" + TypePlatform ComponentType = "platform" + + // Metadata properties + PropertySchemaVersion = "SchemaVersion" + PropertyType = "Type" + PropertyClass = "Class" + + // Image properties + PropertySize = "Size" + PropertyImageID = "ImageID" + PropertyRepoDigest = "RepoDigest" + PropertyDiffID = "DiffID" + PropertyRepoTag = "RepoTag" + + // Package properties + PropertyPkgID = "PkgID" + PropertyPkgType = "PkgType" + PropertySrcName = "SrcName" + PropertySrcVersion = "SrcVersion" + PropertySrcRelease = "SrcRelease" + PropertySrcEpoch = "SrcEpoch" + PropertyModularitylabel = "Modularitylabel" + PropertyFilePath = "FilePath" + PropertyLayerDigest = "LayerDigest" + PropertyLayerDiffID = "LayerDiffID" + + // Relationships + RelationshipDescribes RelationshipType = "describes" + RelationshipContains RelationshipType = "contains" + RelationshipDependsOn RelationshipType = "depends_on" +) + +type ComponentType string +type RelationshipType string + +// BOM represents an intermediate representation of a component for SBOM. +type BOM struct { + SerialNumber string + Version int + + rootID uuid.UUID + components map[uuid.UUID]*Component + relationships map[uuid.UUID][]Relationship + + // Vulnerabilities is a list of vulnerabilities that affect the component. + // CycloneDX: vulnerabilities + // SPDX: N/A + vulnerabilities map[uuid.UUID][]Vulnerability + + // purls is a map of package URLs to UUIDs + // This is used to ensure that each package URL is only represented once in the BOM. + purls map[string][]uuid.UUID + + // opts is a set of options for the BOM. + opts Options +} + +type Component struct { + // id is the unique identifier of the component for internal use. + // It's transparently generated by UUIDv4 + id uuid.UUID + + // Type is the type of the component + // CycloneDX: component.type + Type ComponentType + + // Root represents the root of the BOM + // Only one root is allowed in a BOM. + // CycloneDX: metadata.component + Root bool + + // Name is the name of the component + // CycloneDX: component.name + // SPDX: package.name + Name string + + // Group is the group of the component + // CycloneDX: component.group + // SPDX: N/A + Group string + + // Version is the version of the component + // CycloneDX: component.version + // SPDX: package.versionInfo + Version string + + // SrcName is the name of the source component + // CycloneDX: N/A + // SPDX: package.sourceInfo + SrcName string + + // SrcVersion is the version of the source component + // CycloneDX: N/A + // SPDX: package.sourceInfo + SrcVersion string + + // SrcFile is the file path where the component is found. + // CycloneDX: N/A + // SPDX: package.sourceInfo + SrcFile string + + // Licenses is a list of licenses that apply to the component + // CycloneDX: component.licenses + // SPDX: package.licenseConcluded, package.licenseDeclared + Licenses []string + + // PkgID has PURL and BOMRef for the component + // PURL: + // CycloneDX: component.purl + // SPDX: package.externalRefs.referenceLocator + // BOMRef: + // CycloneDX: component.bom-ref + // SPDX: N/A + PkgID PkgID + + // Supplier is the name of the supplier of the component + // CycloneDX: component.supplier + // SPDX: package.supplier + Supplier string + + // Files is a list of files that are part of the component. + // CycloneDX: component.properties + // SPDX: files + Files []File + + // Properties is a list of key-value pairs that provide additional information about the component + // CycloneDX: component.properties + // SPDX: package.attributionTexts + Properties Properties `hash:"set"` +} + +func (c *Component) ID() uuid.UUID { + return c.id +} + +type File struct { + // Path is a path of the file. + // CycloneDX: N/A + // SPDX: package.files[].fileName + Path string + + // Hash is a hash that uniquely identify the component. + // A file can have several digests with different algorithms, like SHA1, SHA256, etc. + // CycloneDX: component.hashes + // SPDX: package.files[].checksums + Digests []digest.Digest +} + +type Property struct { + Name string + Value string + Namespace string +} + +type Properties []Property + +func (p Properties) Len() int { return len(p) } +func (p Properties) Less(i, j int) bool { + if p[i].Name != p[j].Name { + return p[i].Name < p[j].Name + } + return p[i].Value < p[j].Value +} +func (p Properties) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +type Relationship struct { + Dependency uuid.UUID + Type RelationshipType +} + +type PkgID struct { + PURL *packageurl.PackageURL + BOMRef string +} + +type Vulnerability struct { + dtypes.Vulnerability + ID string + PkgID string + PkgName string + InstalledVersion string + FixedVersion string + PrimaryURL string + DataSource *dtypes.DataSource +} + +type Options struct { + GenerateBOMRef bool +} + +func NewBOM(opts Options) *BOM { + return &BOM{ + components: make(map[uuid.UUID]*Component), + relationships: make(map[uuid.UUID][]Relationship), + vulnerabilities: make(map[uuid.UUID][]Vulnerability), + purls: make(map[string][]uuid.UUID), + opts: opts, + } +} + +func (b *BOM) setupComponent(c *Component) { + if c.id == uuid.Nil { + c.id = uuid.New() + } + if c.PkgID.PURL != nil { + p := c.PkgID.PURL.String() + b.purls[p] = append(b.purls[p], c.id) + } + sort.Sort(c.Properties) +} + +func (b *BOM) AddComponent(c *Component) { + b.setupComponent(c) + if c.Root { + b.rootID = c.id + } + b.components[c.id] = c +} + +func (b *BOM) AddRelationship(parent, child *Component, relationshipType RelationshipType) { + // Check the wrong parent to avoid `panic` + if parent == nil { + return + } + if parent.id == uuid.Nil { + b.AddComponent(parent) + } + + if child == nil { + // It is possible that `relationships` already contains this parent. + // Check this to avoid overwriting. + if _, ok := b.relationships[parent.id]; !ok { + b.relationships[parent.id] = nil // Meaning no dependencies + } + return + } + + if child.id == uuid.Nil { + b.AddComponent(child) + } + + b.relationships[parent.id] = append(b.relationships[parent.id], Relationship{ + Type: relationshipType, + Dependency: child.id, + }) +} + +func (b *BOM) AddVulnerabilities(c *Component, vulns []Vulnerability) { + if c.id == uuid.Nil { + b.AddComponent(c) + } + if _, ok := b.vulnerabilities[c.id]; ok { + return + } + b.vulnerabilities[c.id] = vulns +} + +func (b *BOM) Root() *Component { + root, ok := b.components[b.rootID] + if !ok { + return nil + } + if b.opts.GenerateBOMRef { + root.PkgID.BOMRef = b.bomRef(root) + } + return root +} + +func (b *BOM) Components() map[uuid.UUID]*Component { + // Fill in BOMRefs for components + if b.opts.GenerateBOMRef { + for id, c := range b.components { + b.components[id].PkgID.BOMRef = b.bomRef(c) + } + } + return b.components +} + +func (b *BOM) Relationships() map[uuid.UUID][]Relationship { + return b.relationships +} + +func (b *BOM) Vulnerabilities() map[uuid.UUID][]Vulnerability { + return b.vulnerabilities +} + +func (b *BOM) NumComponents() int { + return len(b.components) + 1 // +1 for the root component +} + +// bomRef returns BOMRef for CycloneDX +// When multiple lock files have the same dependency with the same name and version, PURL in the BOM can conflict. +// In that case, PURL cannot be used as a unique identifier, and UUIDv4 be used for BOMRef. +func (b *BOM) bomRef(c *Component) string { + if c.PkgID.BOMRef != "" { + return c.PkgID.BOMRef + } + // Return the UUID of the component if the PURL is not present. + if c.PkgID.PURL == nil { + return c.id.String() + } + p := c.PkgID.PURL.String() + + // Return the UUID of the component if the PURL is not unique in the BOM. + if len(b.purls[p]) > 1 { + return c.id.String() + } + return p +} diff --git a/pkg/sbom/cyclonedx/marshal.go b/pkg/sbom/cyclonedx/marshal.go new file mode 100644 index 000000000000..684b1b7d235d --- /dev/null +++ b/pkg/sbom/cyclonedx/marshal.go @@ -0,0 +1,501 @@ +package cyclonedx + +import ( + "context" + "fmt" + "slices" + "sort" + "strconv" + "strings" + + cdx "github.com/CycloneDX/cyclonedx-go" + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "golang.org/x/xerrors" + + dtypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/sbom/core" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" +) + +const ( + ToolVendor = "aquasecurity" + ToolName = "trivy" + Namespace = ToolVendor + ":" + ToolName + ":" + + // https://json-schema.org/understanding-json-schema/reference/string.html#dates-and-times + timeLayout = "2006-01-02T15:04:05+00:00" +) + +type Marshaler struct { + appVersion string // Trivy version + bom *core.BOM + componentIDs map[uuid.UUID]string +} + +func NewMarshaler(version string) Marshaler { + return Marshaler{ + appVersion: version, + } +} + +// MarshalReport converts the Trivy report to the CycloneDX format +func (m *Marshaler) MarshalReport(ctx context.Context, report types.Report) (*cdx.BOM, error) { + // Convert into an intermediate representation + opts := core.Options{GenerateBOMRef: true} + bom, err := sbomio.NewEncoder(opts).Encode(report) + if err != nil { + return nil, xerrors.Errorf("failed to marshal report: %w", err) + } + + return m.Marshal(ctx, bom) +} + +// Marshal converts the Trivy component to the CycloneDX format +func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*cdx.BOM, error) { + m.bom = bom + m.componentIDs = make(map[uuid.UUID]string, m.bom.NumComponents()) + + cdxBOM := cdx.NewBOM() + cdxBOM.SerialNumber = uuid.New().URN() + cdxBOM.Metadata = m.Metadata(ctx) + + var err error + if cdxBOM.Metadata.Component, err = m.MarshalRoot(); err != nil { + return nil, xerrors.Errorf("failed to marshal component: %w", err) + } + + if cdxBOM.Components, err = m.marshalComponents(); err != nil { + return nil, xerrors.Errorf("failed to marshal components: %w", err) + } + + cdxBOM.Dependencies = m.marshalDependencies() + cdxBOM.Vulnerabilities = m.marshalVulnerabilities() + + return cdxBOM, nil +} + +func (m *Marshaler) Metadata(ctx context.Context) *cdx.Metadata { + return &cdx.Metadata{ + Timestamp: clock.Now(ctx).UTC().Format(timeLayout), + Tools: &cdx.ToolsChoice{ + Components: &[]cdx.Component{ + { + Type: cdx.ComponentTypeApplication, + Group: ToolVendor, + Name: ToolName, + Version: m.appVersion, + }, + }, + }, + } +} + +func (m *Marshaler) MarshalRoot() (*cdx.Component, error) { + return m.MarshalComponent(m.bom.Root()) +} + +func (m *Marshaler) MarshalComponent(component *core.Component) (*cdx.Component, error) { + componentType, err := m.componentType(component.Type) + if err != nil { + return nil, xerrors.Errorf("failed to get cdx component type: %w", err) + } + + cdxComponent := &cdx.Component{ + BOMRef: component.PkgID.BOMRef, + Type: componentType, + Name: component.Name, + Group: component.Group, + Version: component.Version, + PackageURL: m.PackageURL(component.PkgID.PURL), + Supplier: m.Supplier(component.Supplier), + Hashes: m.Hashes(component.Files), + Licenses: m.Licenses(component.Licenses), + Properties: m.Properties(component.Properties), + } + m.componentIDs[component.ID()] = cdxComponent.BOMRef + + return cdxComponent, nil +} + +func (m *Marshaler) marshalComponents() (*[]cdx.Component, error) { + var cdxComponents []cdx.Component + for _, component := range m.bom.Components() { + if component.Root { + continue + } + c, err := m.MarshalComponent(component) + if err != nil { + return nil, xerrors.Errorf("failed to marshal component: %w", err) + } + cdxComponents = append(cdxComponents, *c) + } + + // CycloneDX requires an empty slice rather than a nil slice + if len(cdxComponents) == 0 { + return &[]cdx.Component{}, nil + } + + // Sort components by BOM-Ref + sort.Slice(cdxComponents, func(i, j int) bool { + return cdxComponents[i].BOMRef < cdxComponents[j].BOMRef + }) + return &cdxComponents, nil +} + +func (m *Marshaler) marshalDependencies() *[]cdx.Dependency { + var dependencies []cdx.Dependency + for key, rels := range m.bom.Relationships() { + ref, ok := m.componentIDs[key] + if !ok { + continue + } + + deps := lo.FilterMap(rels, func(rel core.Relationship, _ int) (string, bool) { + d, ok := m.componentIDs[rel.Dependency] + return d, ok + }) + sort.Strings(deps) + + dependencies = append(dependencies, cdx.Dependency{ + Ref: ref, + Dependencies: &deps, + }) + } + + // Sort dependencies by BOM-Ref + sort.Slice(dependencies, func(i, j int) bool { + return dependencies[i].Ref < dependencies[j].Ref + }) + return &dependencies +} + +func (m *Marshaler) marshalVulnerabilities() *[]cdx.Vulnerability { + vulns := make(map[string]*cdx.Vulnerability) + for id, vv := range m.bom.Vulnerabilities() { + bomRef := m.componentIDs[id] + for _, v := range vv { + // If the same vulnerability affects multiple packages, those packages will be aggregated into one vulnerability. + // Vulnerability component (CVE-2020-26247) + // -> Library component (nokogiri /srv/app1/vendor/bundle/ruby/3.0.0/specifications/nokogiri-1.10.0.gemspec) + // -> Library component (nokogiri /srv/app2/vendor/bundle/ruby/3.0.0/specifications/nokogiri-1.10.0.gemspec) + if vuln, ok := vulns[v.ID]; ok { + *vuln.Affects = append(*vuln.Affects, m.affects(bomRef, v.InstalledVersion)) + if v.FixedVersion != "" { + // new recommendation + rec := fmt.Sprintf("Upgrade %s to version %s", v.PkgName, v.FixedVersion) + // previous recommendations + recs := strings.Split(vuln.Recommendation, "; ") + if !slices.Contains(recs, rec) { + recs = append(recs, rec) + slices.Sort(recs) + vuln.Recommendation = strings.Join(recs, "; ") + } + } + } else { + vulns[v.ID] = m.marshalVulnerability(bomRef, v) + } + } + } + + vulnList := lo.MapToSlice(vulns, func(_ string, value *cdx.Vulnerability) cdx.Vulnerability { + sort.Slice(*value.Affects, func(i, j int) bool { + return (*value.Affects)[i].Ref < (*value.Affects)[j].Ref + }) + return *value + }) + sort.Slice(vulnList, func(i, j int) bool { + return vulnList[i].ID < vulnList[j].ID + }) + return &vulnList +} + +// componentType converts the Trivy component type to the CycloneDX component type +func (*Marshaler) componentType(t core.ComponentType) (cdx.ComponentType, error) { + switch t { + case core.TypeContainerImage, core.TypeVM: + return cdx.ComponentTypeContainer, nil + case core.TypeApplication, core.TypeFilesystem, core.TypeRepository: + return cdx.ComponentTypeApplication, nil + case core.TypeLibrary: + return cdx.ComponentTypeLibrary, nil + case core.TypeOS: + return cdx.ComponentTypeOS, nil + case core.TypePlatform: + return cdx.ComponentTypePlatform, nil + } + return "", xerrors.Errorf("unknown component type: %s", t) +} + +func (*Marshaler) PackageURL(p *packageurl.PackageURL) string { + if p == nil { + return "" + } + return p.String() +} + +func (*Marshaler) Supplier(supplier string) *cdx.OrganizationalEntity { + if supplier == "" { + return nil + } + return &cdx.OrganizationalEntity{ + Name: supplier, + } +} + +func (*Marshaler) Hashes(files []core.File) *[]cdx.Hash { + digests := lo.FlatMap(files, func(file core.File, _ int) []digest.Digest { + return file.Digests + }) + if len(digests) == 0 { + return nil + } + + var cdxHashes []cdx.Hash + for _, d := range digests { + var alg cdx.HashAlgorithm + switch d.Algorithm() { + case digest.SHA1: + alg = cdx.HashAlgoSHA1 + case digest.SHA256: + alg = cdx.HashAlgoSHA256 + case digest.MD5: + alg = cdx.HashAlgoMD5 + default: + log.Logger.Debugf("Unable to convert %q algorithm to CycloneDX format", d.Algorithm()) + continue + } + + cdxHashes = append(cdxHashes, cdx.Hash{ + Algorithm: alg, + Value: d.Encoded(), + }) + } + return &cdxHashes +} + +func (*Marshaler) Licenses(licenses []string) *cdx.Licenses { + if len(licenses) == 0 { + return nil + } + choices := lo.Map(licenses, func(license string, i int) cdx.LicenseChoice { + return cdx.LicenseChoice{ + License: &cdx.License{ + Name: license, + }, + } + }) + return lo.ToPtr(cdx.Licenses(choices)) +} + +func (*Marshaler) Properties(properties []core.Property) *[]cdx.Property { + cdxProps := make([]cdx.Property, 0, len(properties)) + for _, property := range properties { + namespace := Namespace + if len(property.Namespace) > 0 { + namespace = property.Namespace + } + + cdxProps = append(cdxProps, cdx.Property{ + Name: namespace + property.Name, + Value: property.Value, + }) + } + sort.Slice(cdxProps, func(i, j int) bool { + if cdxProps[i].Name != cdxProps[j].Name { + return cdxProps[i].Name < cdxProps[j].Name + } + return cdxProps[i].Value < cdxProps[j].Value + }) + return &cdxProps +} + +func (*Marshaler) affects(ref, version string) cdx.Affects { + return cdx.Affects{ + Ref: ref, + Range: &[]cdx.AffectedVersions{ + { + Version: version, + Status: cdx.VulnerabilityStatusAffected, + // "AffectedVersions.Range" is not included, because it does not exist in DetectedVulnerability. + }, + }, + } +} + +func (*Marshaler) advisories(refs []string) *[]cdx.Advisory { + refs = lo.Uniq(refs) + advs := lo.FilterMap(refs, func(ref string, _ int) (cdx.Advisory, bool) { + return cdx.Advisory{URL: ref}, ref != "" + }) + + // cyclonedx converts link to empty `[]cdx.Advisory` to `null` + // `bom-1.5.schema.json` doesn't support this - `Invalid type. Expected: array, given: null` + // we need to explicitly set `nil` for empty `refs` slice + if len(advs) == 0 { + return nil + } + + return &advs +} + +func (m *Marshaler) marshalVulnerability(bomRef string, vuln core.Vulnerability) *cdx.Vulnerability { + v := &cdx.Vulnerability{ + ID: vuln.ID, + Source: m.source(vuln.DataSource), + Ratings: m.ratings(vuln), + CWEs: m.cwes(vuln.CweIDs), + Description: vuln.Description, + Advisories: m.advisories(append([]string{vuln.PrimaryURL}, vuln.References...)), + } + if vuln.FixedVersion != "" { + v.Recommendation = fmt.Sprintf("Upgrade %s to version %s", vuln.PkgName, vuln.FixedVersion) + } + if vuln.PublishedDate != nil { + v.Published = vuln.PublishedDate.Format(timeLayout) + } + if vuln.LastModifiedDate != nil { + v.Updated = vuln.LastModifiedDate.Format(timeLayout) + } + + v.Affects = &[]cdx.Affects{m.affects(bomRef, vuln.InstalledVersion)} + + return v +} + +func (*Marshaler) source(source *dtypes.DataSource) *cdx.Source { + if source == nil { + return nil + } + + return &cdx.Source{ + Name: string(source.ID), + URL: source.URL, + } +} + +func (*Marshaler) cwes(cweIDs []string) *[]int { + // to skip cdx.Vulnerability.CWEs when generating json + // we should return 'clear' nil without 'type' interface part + if cweIDs == nil { + return nil + } + var ret []int + for _, cweID := range cweIDs { + number, err := strconv.Atoi(strings.TrimPrefix(strings.ToLower(cweID), "cwe-")) + if err != nil { + log.Logger.Debugf("cwe id parse error: %s", err) + continue + } + ret = append(ret, number) + } + return &ret +} + +func (m *Marshaler) ratings(vuln core.Vulnerability) *[]cdx.VulnerabilityRating { + rates := make([]cdx.VulnerabilityRating, 0) // nolint:gocritic // To export an empty array in JSON + for sourceID, severity := range vuln.VendorSeverity { + // When the vendor also provides CVSS score/vector + if cvss, ok := vuln.CVSS[sourceID]; ok { + if cvss.V2Score != 0 || cvss.V2Vector != "" { + rates = append(rates, m.ratingV2(sourceID, severity, cvss)) + } + if cvss.V3Score != 0 || cvss.V3Vector != "" { + rates = append(rates, m.ratingV3(sourceID, severity, cvss)) + } + } else { // When the vendor provides only severity + rate := cdx.VulnerabilityRating{ + Source: &cdx.Source{ + Name: string(sourceID), + }, + Severity: m.severity(severity), + } + rates = append(rates, rate) + } + } + + // For consistency + sort.Slice(rates, func(i, j int) bool { + if rates[i].Source.Name != rates[j].Source.Name { + return rates[i].Source.Name < rates[j].Source.Name + } + if rates[i].Method != rates[j].Method { + return rates[i].Method < rates[j].Method + } + if rates[i].Score != nil && rates[j].Score != nil { + return *rates[i].Score < *rates[j].Score + } + return rates[i].Vector < rates[j].Vector + }) + return &rates +} + +func (m *Marshaler) ratingV2(sourceID dtypes.SourceID, severity dtypes.Severity, cvss dtypes.CVSS) cdx.VulnerabilityRating { + cdxSeverity := m.severity(severity) + + // Trivy keeps only CVSSv3 severity for NVD. + // The CVSSv2 severity must be calculated according to CVSSv2 score. + if sourceID == vulnerability.NVD { + cdxSeverity = m.nvdSeverityV2(cvss.V2Score) + } + return cdx.VulnerabilityRating{ + Source: &cdx.Source{ + Name: string(sourceID), + }, + Score: &cvss.V2Score, + Method: cdx.ScoringMethodCVSSv2, + Severity: cdxSeverity, + Vector: cvss.V2Vector, + } +} + +func (m *Marshaler) ratingV3(sourceID dtypes.SourceID, severity dtypes.Severity, cvss dtypes.CVSS) cdx.VulnerabilityRating { + rate := cdx.VulnerabilityRating{ + Source: &cdx.Source{ + Name: string(sourceID), + }, + Score: &cvss.V3Score, + Method: cdx.ScoringMethodCVSSv3, + Severity: m.severity(severity), + Vector: cvss.V3Vector, + } + if strings.HasPrefix(cvss.V3Vector, "CVSS:3.1") { + rate.Method = cdx.ScoringMethodCVSSv31 + } + return rate +} + +// severity converts the Trivy severity to the CycloneDX severity +func (*Marshaler) severity(s dtypes.Severity) cdx.Severity { + switch s { + case dtypes.SeverityLow: + return cdx.SeverityLow + case dtypes.SeverityMedium: + return cdx.SeverityMedium + case dtypes.SeverityHigh: + return cdx.SeverityHigh + case dtypes.SeverityCritical: + return cdx.SeverityCritical + default: + return cdx.SeverityUnknown + } +} + +func (*Marshaler) nvdSeverityV2(score float64) cdx.Severity { + // cf. https://nvd.nist.gov/vuln-metrics/cvss + switch { + case score < 4.0: + return cdx.SeverityInfo + case 4.0 <= score && score < 7.0: + return cdx.SeverityMedium + case 7.0 <= score: + return cdx.SeverityHigh + } + return cdx.SeverityUnknown +} diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go new file mode 100644 index 000000000000..de723236a66a --- /dev/null +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -0,0 +1,2099 @@ +package cyclonedx_test + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/package-url/packageurl-go" + "testing" + "time" + + cdx "github.com/CycloneDX/cyclonedx-go" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + dtypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" +) + +func TestMarshaler_MarshalReport(t *testing.T) { + testSBOM := core.NewBOM(core.Options{GenerateBOMRef: true}) + testSBOM.AddComponent(&core.Component{ + Root: true, + Type: core.TypeApplication, + Name: "jackson-databind-2.13.4.1.jar", + PkgID: core.PkgID{ + BOMRef: "aff65b54-6009-4c32-968d-748949ef46e8", + }, + Properties: []core.Property{ + { + Name: "SchemaVersion", + Value: "2", + }, + }, + }) + + tests := []struct { + name string + inputReport types.Report + want *cdx.BOM + }{ + { + name: "happy path for container scan", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "rails:latest", + ArtifactType: ftypes.ArtifactContainerImage, + Metadata: types.Metadata{ + Size: 1024, + OS: &ftypes.OS{ + Family: ftypes.CentOS, + Name: "8.3.2011", + Eosl: true, + }, + ImageID: "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", + RepoTags: []string{"rails:latest"}, + DiffIDs: []string{"sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a"}, + RepoDigests: []string{"rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177"}, + ImageConfig: v1.ConfigFile{ + Architecture: "arm64", + }, + }, + Results: types.Results{ + { + Target: "rails:latest (centos 8.3.2011)", + Class: types.ClassOSPkg, + Type: ftypes.CentOS, + Packages: []ftypes.Package{ + { + ID: "binutils@2.30-93.el8", + Name: "binutils", + Version: "2.30", + Release: "93.el8", + Epoch: 0, + Arch: "aarch64", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "centos", + Name: "binutils", + Version: "2.30-93.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "aarch64", + }, + { + Key: "distro", + Value: "centos-8.3.2011", + }, + }, + }, + }, + SrcName: "binutils", + SrcVersion: "2.30", + SrcRelease: "93.el8", + SrcEpoch: 0, + Modularitylabel: "", + Licenses: []string{"GPLv3+"}, + Maintainer: "CentOS", + Digest: "md5:7459cec61bb4d1b0ca8107e25e0dd005", + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2018-20623", + PkgID: "binutils@2.30-93.el8", + PkgName: "binutils", + InstalledVersion: "2.30-93.el8", + Layer: ftypes.Layer{ + DiffID: "sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a", + }, + SeveritySource: vulnerability.RedHatOVAL, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2018-20623", + DataSource: &dtypes.DataSource{ + ID: vulnerability.RedHatOVAL, + Name: "Red Hat OVAL v2", + URL: "https://www.redhat.com/security/data/oval/v2/", + }, + Vulnerability: dtypes.Vulnerability{ + Title: "binutils: Use-after-free in the error function", + Description: "In GNU Binutils 2.31.1, there is a use-after-free in the error function in elfcomm.c when called from the process_archive function in readelf.c via a crafted ELF file.", + Severity: dtypes.SeverityMedium.String(), + VendorSeverity: dtypes.VendorSeverity{ + vulnerability.NVD: dtypes.SeverityMedium, + vulnerability.RedHatOVAL: dtypes.SeverityMedium, + }, + CweIDs: []string{"CWE-416"}, + CVSS: dtypes.VendorCVSS{ + vulnerability.NVD: dtypes.CVSS{ + V2Vector: "AV:N/AC:M/Au:N/C:N/I:N/A:P", + V3Vector: "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + V2Score: 4.3, + V3Score: 5.5, + }, + vulnerability.RedHatOVAL: dtypes.CVSS{ + V3Vector: "CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", + V3Score: 5.3, + }, + }, + PublishedDate: lo.ToPtr(time.Date(2018, 12, 31, 19, 29, 0, 0, time.UTC)), + LastModifiedDate: lo.ToPtr(time.Date(2019, 10, 31, 1, 15, 0, 0, time.UTC)), + }, + }, + }, + }, + { + Target: "app/subproject/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: []ftypes.Package{ + { + // This package conflicts + ID: "actionpack@7.0.0", + Name: "actionpack", + Version: "7.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.0", + }, + }, + Indirect: false, + }, + { + ID: "actioncontroller@7.0.0", + Name: "actioncontroller", + Version: "7.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actioncontroller", + Version: "7.0.0", + }, + }, + Indirect: false, + DependsOn: []string{ + "actionpack@7.0.0", + }, + }, + }, + }, + { + Target: "app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: []ftypes.Package{ + { + // This package conflicts + ID: "actionpack@7.0.0", + Name: "actionpack", + Version: "7.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.0", + }, + }, + }, + }, + }, + { + Target: "app/datacollector.deps.json", + Class: types.ClassLangPkg, + Type: ftypes.DotNetCore, + Packages: []ftypes.Package{ + { + ID: "Newtonsoft.Json@9.0.1", + Name: "Newtonsoft.Json", + Version: "9.0.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNuget, + Name: "Newtonsoft.Json", + Version: "9.0.1", + }, + }, + }, + }, + }, + { + Target: "usr/local/bin/tfsec", + Class: types.ClassLangPkg, + Type: ftypes.GoBinary, + Packages: []ftypes.Package{ + { + Name: "golang.org/x/crypto", + Version: "v0.0.0-20210421170649-83a5a9bb288b", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "golang.org/x", + Name: "crypto", + Version: "v0.0.0-20210421170649-83a5a9bb288b", + }, + }, + }, + // dependency has been replaced with local directory + { + Name: "./api", + Version: "(devel)", + }, + }, + }, + }, + }, + want: &cdx.BOM{ + XMLNS: "http://cyclonedx.org/schema/bom/1.5", + BOMFormat: "CycloneDX", + SpecVersion: cdx.SpecVersion1_5, + JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000014", + Version: 1, + Metadata: &cdx.Metadata{ + Timestamp: "2021-08-25T12:20:30+00:00", + Tools: &cdx.ToolsChoice{ + Components: &[]cdx.Component{ + { + Type: cdx.ComponentTypeApplication, + Name: "trivy", + Group: "aquasecurity", + Version: "dev", + }, + }, + }, + Component: &cdx.Component{ + Type: cdx.ComponentTypeContainer, + BOMRef: "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails", + PackageURL: "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails", + Name: "rails:latest", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:DiffID", + Value: "sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a", + }, + { + Name: "aquasecurity:trivy:ImageID", + Value: "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", + }, + { + Name: "aquasecurity:trivy:RepoDigest", + Value: "rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177", + }, + { + Name: "aquasecurity:trivy:RepoTag", + Value: "rails:latest", + }, + { + Name: "aquasecurity:trivy:SchemaVersion", + Value: "2", + }, + { + Name: "aquasecurity:trivy:Size", + Value: "1024", + }, + }, + }, + }, + Components: &[]cdx.Component{ + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + Type: cdx.ComponentTypeOS, + Name: "centos", + Version: "8.3.2011", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:Class", + Value: "os-pkgs", + }, + { + Name: "aquasecurity:trivy:Type", + Value: "centos", + }, + }, + }, + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000004", + Type: cdx.ComponentTypeApplication, + Name: "app/subproject/Gemfile.lock", + Version: "", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:Class", + Value: "lang-pkgs", + }, + { + Name: "aquasecurity:trivy:Type", + Value: "bundler", + }, + }, + }, + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000005", + Type: cdx.ComponentTypeLibrary, + Name: "actionpack", + Version: "7.0.0", + PackageURL: "pkg:gem/actionpack@7.0.0", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgID", + Value: "actionpack@7.0.0", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "bundler", + }, + }, + }, + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000007", + Type: cdx.ComponentTypeApplication, + Name: "app/Gemfile.lock", + Version: "", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:Class", + Value: "lang-pkgs", + }, + { + Name: "aquasecurity:trivy:Type", + Value: "bundler", + }, + }, + }, + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000008", + Type: cdx.ComponentTypeLibrary, + Name: "actionpack", + Version: "7.0.0", + PackageURL: "pkg:gem/actionpack@7.0.0", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgID", + Value: "actionpack@7.0.0", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "bundler", + }, + }, + }, + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000009", + Type: cdx.ComponentTypeApplication, + Name: "app/datacollector.deps.json", + Version: "", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:Class", + Value: "lang-pkgs", + }, + { + Name: "aquasecurity:trivy:Type", + Value: "dotnet-core", + }, + }, + }, + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000011", + Type: cdx.ComponentTypeApplication, + Name: "usr/local/bin/tfsec", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:Class", + Value: "lang-pkgs", + }, + { + Name: "aquasecurity:trivy:Type", + Value: "gobinary", + }, + }, + }, + { + // Use UUID for local Go packages + BOMRef: "3ff14136-e09f-4df9-80ea-000000000013", + Type: cdx.ComponentTypeLibrary, + Name: "./api", + Version: "(devel)", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgType", + Value: "gobinary", + }, + }, + }, + { + BOMRef: "pkg:gem/actioncontroller@7.0.0", + Type: cdx.ComponentTypeLibrary, + Name: "actioncontroller", + Version: "7.0.0", + PackageURL: "pkg:gem/actioncontroller@7.0.0", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgID", + Value: "actioncontroller@7.0.0", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "bundler", + }, + }, + }, + { + BOMRef: "pkg:golang/golang.org/x/crypto@v0.0.0-20210421170649-83a5a9bb288b", + Type: cdx.ComponentTypeLibrary, + Name: "golang.org/x/crypto", + Version: "v0.0.0-20210421170649-83a5a9bb288b", + PackageURL: "pkg:golang/golang.org/x/crypto@v0.0.0-20210421170649-83a5a9bb288b", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgType", + Value: "gobinary", + }, + }, + }, + { + BOMRef: "pkg:nuget/Newtonsoft.Json@9.0.1", + Type: cdx.ComponentTypeLibrary, + Name: "Newtonsoft.Json", + Version: "9.0.1", + PackageURL: "pkg:nuget/Newtonsoft.Json@9.0.1", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgID", + Value: "Newtonsoft.Json@9.0.1", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "dotnet-core", + }, + }, + }, + { + BOMRef: "pkg:rpm/centos/binutils@2.30-93.el8?arch=aarch64&distro=centos-8.3.2011", + Type: cdx.ComponentTypeLibrary, + Name: "binutils", + Version: "2.30-93.el8", + Licenses: &cdx.Licenses{ + cdx.LicenseChoice{ + License: &cdx.License{ + Name: "GPLv3+", + }, + }, + }, + PackageURL: "pkg:rpm/centos/binutils@2.30-93.el8?arch=aarch64&distro=centos-8.3.2011", + Supplier: &cdx.OrganizationalEntity{ + Name: "CentOS", + }, + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgID", + Value: "binutils@2.30-93.el8", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "centos", + }, + { + Name: "aquasecurity:trivy:SrcName", + Value: "binutils", + }, + { + Name: "aquasecurity:trivy:SrcRelease", + Value: "93.el8", + }, + { + Name: "aquasecurity:trivy:SrcVersion", + Value: "2.30", + }, + }, + Hashes: &[]cdx.Hash{ + { + Algorithm: cdx.HashAlgoMD5, + Value: "7459cec61bb4d1b0ca8107e25e0dd005", + }, + }, + }, + }, + Dependencies: &[]cdx.Dependency{ + { + Ref: "3ff14136-e09f-4df9-80ea-000000000002", + Dependencies: &[]string{ + "pkg:rpm/centos/binutils@2.30-93.el8?arch=aarch64&distro=centos-8.3.2011", + }, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000004", + Dependencies: &[]string{ + "3ff14136-e09f-4df9-80ea-000000000005", + "pkg:gem/actioncontroller@7.0.0", + }, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000005", + Dependencies: &[]string{}, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000007", + Dependencies: &[]string{ + "3ff14136-e09f-4df9-80ea-000000000008", + }, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000008", + Dependencies: &[]string{}, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000009", + Dependencies: &[]string{ + "pkg:nuget/Newtonsoft.Json@9.0.1", + }, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000011", + Dependencies: &[]string{ + "3ff14136-e09f-4df9-80ea-000000000013", + "pkg:golang/golang.org/x/crypto@v0.0.0-20210421170649-83a5a9bb288b", + }, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000013", + Dependencies: lo.ToPtr([]string{}), + }, + { + Ref: "pkg:gem/actioncontroller@7.0.0", + Dependencies: &[]string{ + "3ff14136-e09f-4df9-80ea-000000000005", + }, + }, + { + Ref: "pkg:golang/golang.org/x/crypto@v0.0.0-20210421170649-83a5a9bb288b", + Dependencies: lo.ToPtr([]string{}), + }, + { + Ref: "pkg:nuget/Newtonsoft.Json@9.0.1", + Dependencies: lo.ToPtr([]string{}), + }, + { + Ref: "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails", + Dependencies: &[]string{ + "3ff14136-e09f-4df9-80ea-000000000002", + "3ff14136-e09f-4df9-80ea-000000000004", + "3ff14136-e09f-4df9-80ea-000000000007", + "3ff14136-e09f-4df9-80ea-000000000009", + "3ff14136-e09f-4df9-80ea-000000000011", + }, + }, + { + Ref: "pkg:rpm/centos/binutils@2.30-93.el8?arch=aarch64&distro=centos-8.3.2011", + Dependencies: lo.ToPtr([]string{}), + }, + }, + Vulnerabilities: &[]cdx.Vulnerability{ + { + ID: "CVE-2018-20623", + Source: &cdx.Source{ + Name: string(vulnerability.RedHatOVAL), + URL: "https://www.redhat.com/security/data/oval/v2/", + }, + Ratings: &[]cdx.VulnerabilityRating{ + { + Source: &cdx.Source{ + Name: string(vulnerability.NVD), + URL: "", + }, + Score: lo.ToPtr(4.3), + Severity: cdx.SeverityMedium, + Method: cdx.ScoringMethodCVSSv2, + Vector: "AV:N/AC:M/Au:N/C:N/I:N/A:P", + }, + { + Source: &cdx.Source{ + Name: string(vulnerability.NVD), + URL: "", + }, + Score: lo.ToPtr(5.5), + Severity: cdx.SeverityMedium, + Method: cdx.ScoringMethodCVSSv3, + Vector: "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + }, + { + Source: &cdx.Source{ + Name: string(vulnerability.RedHatOVAL), + URL: "", + }, + Score: lo.ToPtr(5.3), + Severity: cdx.SeverityMedium, + Method: cdx.ScoringMethodCVSSv3, + Vector: "CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", + }, + }, + CWEs: &[]int{ + 416, + }, + Description: "In GNU Binutils 2.31.1, there is a use-after-free in the error function in elfcomm.c when called from the process_archive function in readelf.c via a crafted ELF file.", + Published: "2018-12-31T19:29:00+00:00", + Updated: "2019-10-31T01:15:00+00:00", + Advisories: &[]cdx.Advisory{ + { + URL: "https://avd.aquasec.com/nvd/cve-2018-20623", + }, + }, + Affects: &[]cdx.Affects{ + { + Ref: "pkg:rpm/centos/binutils@2.30-93.el8?arch=aarch64&distro=centos-8.3.2011", + Range: &[]cdx.AffectedVersions{ + { + Version: "2.30-93.el8", + Status: cdx.VulnerabilityStatusAffected, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path for local container scan", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "centos:latest", + ArtifactType: ftypes.ArtifactContainerImage, + Metadata: types.Metadata{ + Size: 1024, + OS: &ftypes.OS{ + Family: ftypes.CentOS, + Name: "8.3.2011", + Eosl: true, + }, + ImageID: "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", + RepoTags: []string{"centos:latest"}, + RepoDigests: []string{}, + ImageConfig: v1.ConfigFile{ + Architecture: "arm64", + }, + }, + Results: types.Results{ + { + Target: "centos:latest (centos 8.3.2011)", + Class: types.ClassOSPkg, + Type: ftypes.CentOS, + Packages: []ftypes.Package{ + { + ID: "acl@2.2.53-1.el8", + Name: "acl", + Version: "2.2.53", + Release: "1.el8", + Epoch: 1, + Arch: "aarch64", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "centos", + Name: "acl", + Version: "2.2.53-1.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "aarch64", + }, + { + Key: "distro", + Value: "centos-8.3.2011", + }, + { + Key: "epoch", + Value: "1", + }, + }, + }, + }, + SrcName: "acl", + SrcVersion: "2.2.53", + SrcRelease: "1.el8", + SrcEpoch: 1, + Modularitylabel: "", + Licenses: []string{"GPLv2+"}, + DependsOn: []string{ + "glibc@2.28-151.el8", + }, + Digest: "md5:483792b8b5f9eb8be7dc4407733118d0", + }, + { + ID: "glibc@2.28-151.el8", + Name: "glibc", + Version: "2.28", + Release: "151.el8", + Epoch: 0, + Arch: "aarch64", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "centos", + Name: "glibc", + Version: "2.28-151.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "aarch64", + }, + { + Key: "distro", + Value: "centos-8.3.2011", + }, + }, + }, + }, + SrcName: "glibc", + SrcVersion: "2.28", + SrcRelease: "151.el8", + SrcEpoch: 0, + Modularitylabel: "", + Licenses: []string{"GPLv2+"}, + Digest: "md5:969b3c9231627022f8bf7ac70de807a1", + }, + }, + }, + { + Target: "Ruby", + Class: types.ClassLangPkg, + Type: ftypes.GemSpec, + Packages: []ftypes.Package{ + { + ID: "actionpack@7.0.0", + Name: "actionpack", + Version: "7.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.0", + }, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", + }, + FilePath: "tools/project-john/specifications/actionpack.gemspec", + }, + { + ID: "actionpack@7.0.1", + Name: "actionpack", + Version: "7.0.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.1", + }, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", + }, + FilePath: "tools/project-doe/specifications/actionpack.gemspec", + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-23633", + PkgID: "actionpack@7.0.0", + PkgName: "actionpack", + PkgPath: "tools/project-john/specifications/actionpack.gemspec", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.0", + }, + }, + InstalledVersion: "7.0.0", + FixedVersion: "~> 5.2.6, >= 5.2.6.2, ~> 6.0.4, >= 6.0.4.6, ~> 6.1.4, >= 6.1.4.6, >= 7.0.2.2", + SeveritySource: vulnerability.RubySec, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2022-23633", + DataSource: &dtypes.DataSource{ + ID: vulnerability.RubySec, + Name: "Ruby Advisory Database", + URL: "https://github.com/rubysec/ruby-advisory-db", + }, + Vulnerability: dtypes.Vulnerability{ + Title: "rubygem-actionpack: information leak between requests", + Description: "Action Pack is a framework for handling and responding to web requests. Under certain circumstances response bodies will not be closed. In the event a response is *not* notified of a `close`, `ActionDispatch::Executor` will not know to reset thread local state for the next request. This can lead to data being leaked to subsequent requests.This has been fixed in Rails 7.0.2.1, 6.1.4.5, 6.0.4.5, and 5.2.6.1. Upgrading is highly recommended, but to work around this problem a middleware described in GHSA-wh98-p28r-vrc9 can be used.", + Severity: dtypes.SeverityMedium.String(), + VendorSeverity: dtypes.VendorSeverity{ + vulnerability.NVD: dtypes.SeverityMedium, + vulnerability.RedHat: dtypes.SeverityLow, + vulnerability.RubySec: dtypes.SeverityHigh, + }, + CVSS: dtypes.VendorCVSS{ + vulnerability.NVD: dtypes.CVSS{ + V2Vector: "AV:N/AC:L/Au:N/C:C/I:P/A:C", + V3Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + V2Score: 9.7, + V3Score: 5.9, + }, + vulnerability.RedHat: dtypes.CVSS{ + V3Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + V3Score: 5.9, + }, + }, + References: []string{ + "http://www.openwall.com/lists/oss-security/2022/02/11/5", + "https://access.redhat.com/security/cve/CVE-2022-23633", + }, + PublishedDate: lo.ToPtr(time.Date(2022, 2, 11, 21, 15, 0, 0, time.UTC)), + LastModifiedDate: lo.ToPtr(time.Date(2022, 2, 22, 21, 47, 0, 0, time.UTC)), + }, + }, + { + VulnerabilityID: "CVE-2022-23633", + PkgID: "actionpack@7.0.1", + PkgName: "actionpack", + PkgPath: "tools/project-doe/specifications/actionpack.gemspec", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.1", + }, + }, + InstalledVersion: "7.0.1", + FixedVersion: "~> 5.2.6, >= 5.2.6.2, ~> 6.0.4, >= 6.0.4.6, ~> 6.1.4, >= 6.1.4.6, >= 7.0.2.2", + SeveritySource: vulnerability.RubySec, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2022-23633", + DataSource: &dtypes.DataSource{ + ID: vulnerability.RubySec, + Name: "Ruby Advisory Database", + URL: "https://github.com/rubysec/ruby-advisory-db", + }, + Vulnerability: dtypes.Vulnerability{ + Title: "rubygem-actionpack: information leak between requests", + Description: "Action Pack is a framework for handling and responding to web requests. Under certain circumstances response bodies will not be closed. In the event a response is *not* notified of a `close`, `ActionDispatch::Executor` will not know to reset thread local state for the next request. This can lead to data being leaked to subsequent requests.This has been fixed in Rails 7.0.2.1, 6.1.4.5, 6.0.4.5, and 5.2.6.1. Upgrading is highly recommended, but to work around this problem a middleware described in GHSA-wh98-p28r-vrc9 can be used.", + Severity: dtypes.SeverityMedium.String(), + VendorSeverity: dtypes.VendorSeverity{ + vulnerability.NVD: dtypes.SeverityMedium, + vulnerability.RedHat: dtypes.SeverityLow, + vulnerability.RubySec: dtypes.SeverityHigh, + }, + CVSS: dtypes.VendorCVSS{ + vulnerability.NVD: dtypes.CVSS{ + V2Vector: "AV:N/AC:L/Au:N/C:C/I:P/A:C", + V3Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + V2Score: 9.7, + V3Score: 5.9, + }, + vulnerability.RedHat: dtypes.CVSS{ + V3Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + V3Score: 5.9, + }, + }, + References: []string{ + "http://www.openwall.com/lists/oss-security/2022/02/11/5", + "https://access.redhat.com/security/cve/CVE-2022-23633", + }, + PublishedDate: lo.ToPtr(time.Date(2022, 2, 11, 21, 15, 0, 0, time.UTC)), + LastModifiedDate: lo.ToPtr(time.Date(2022, 2, 22, 21, 47, 0, 0, time.UTC)), + }, + }, + }, + }, + }, + }, + want: &cdx.BOM{ + XMLNS: "http://cyclonedx.org/schema/bom/1.5", + BOMFormat: "CycloneDX", + SpecVersion: cdx.SpecVersion1_5, + JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000007", + Version: 1, + Metadata: &cdx.Metadata{ + Timestamp: "2021-08-25T12:20:30+00:00", + Tools: &cdx.ToolsChoice{ + Components: &[]cdx.Component{ + { + Type: cdx.ComponentTypeApplication, + Name: "trivy", + Group: "aquasecurity", + Version: "dev", + }, + }, + }, + Component: &cdx.Component{ + Type: cdx.ComponentTypeContainer, + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", + PackageURL: "", + Name: "centos:latest", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:ImageID", + Value: "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", + }, + { + Name: "aquasecurity:trivy:RepoTag", + Value: "centos:latest", + }, + { + Name: "aquasecurity:trivy:SchemaVersion", + Value: "2", + }, + { + Name: "aquasecurity:trivy:Size", + Value: "1024", + }, + }, + }, + }, + Components: &[]cdx.Component{ + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + Type: cdx.ComponentTypeOS, + Name: string(ftypes.CentOS), + Version: "8.3.2011", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:Class", + Value: "os-pkgs", + }, + { + Name: "aquasecurity:trivy:Type", + Value: "centos", + }, + }, + }, + { + BOMRef: "pkg:gem/actionpack@7.0.0", + Type: cdx.ComponentTypeLibrary, + Name: "actionpack", + Version: "7.0.0", + PackageURL: "pkg:gem/actionpack@7.0.0", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:FilePath", + Value: "tools/project-john/specifications/actionpack.gemspec", + }, + { + Name: "aquasecurity:trivy:LayerDiffID", + Value: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", + }, + { + Name: "aquasecurity:trivy:PkgID", + Value: "actionpack@7.0.0", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "gemspec", + }, + }, + }, + { + BOMRef: "pkg:gem/actionpack@7.0.1", + Type: cdx.ComponentTypeLibrary, + Name: "actionpack", + Version: "7.0.1", + PackageURL: "pkg:gem/actionpack@7.0.1", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:FilePath", + Value: "tools/project-doe/specifications/actionpack.gemspec", + }, + { + Name: "aquasecurity:trivy:LayerDiffID", + Value: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", + }, + { + Name: "aquasecurity:trivy:PkgID", + Value: "actionpack@7.0.1", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "gemspec", + }, + }, + }, + { + BOMRef: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1", + Type: cdx.ComponentTypeLibrary, + Name: "acl", + Version: "1:2.2.53-1.el8", + Licenses: &cdx.Licenses{ + cdx.LicenseChoice{ + License: &cdx.License{ + Name: "GPLv2+", + }, + }, + }, + PackageURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgID", + Value: "acl@2.2.53-1.el8", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "centos", + }, + { + Name: "aquasecurity:trivy:SrcEpoch", + Value: "1", + }, + { + Name: "aquasecurity:trivy:SrcName", + Value: "acl", + }, + { + Name: "aquasecurity:trivy:SrcRelease", + Value: "1.el8", + }, + { + Name: "aquasecurity:trivy:SrcVersion", + Value: "2.2.53", + }, + }, + Hashes: &[]cdx.Hash{ + { + Algorithm: cdx.HashAlgoMD5, + Value: "483792b8b5f9eb8be7dc4407733118d0", + }, + }, + }, + { + BOMRef: "pkg:rpm/centos/glibc@2.28-151.el8?arch=aarch64&distro=centos-8.3.2011", + Type: cdx.ComponentTypeLibrary, + Name: "glibc", + Version: "2.28-151.el8", + Licenses: &cdx.Licenses{ + cdx.LicenseChoice{ + License: &cdx.License{ + Name: "GPLv2+", + }, + }, + }, + PackageURL: "pkg:rpm/centos/glibc@2.28-151.el8?arch=aarch64&distro=centos-8.3.2011", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgID", + Value: "glibc@2.28-151.el8", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "centos", + }, + { + Name: "aquasecurity:trivy:SrcName", + Value: "glibc", + }, + { + Name: "aquasecurity:trivy:SrcRelease", + Value: "151.el8", + }, + { + Name: "aquasecurity:trivy:SrcVersion", + Value: "2.28", + }, + }, + Hashes: &[]cdx.Hash{ + { + Algorithm: cdx.HashAlgoMD5, + Value: "969b3c9231627022f8bf7ac70de807a1", + }, + }, + }, + }, + Dependencies: &[]cdx.Dependency{ + { + Ref: "3ff14136-e09f-4df9-80ea-000000000001", + Dependencies: &[]string{ + "3ff14136-e09f-4df9-80ea-000000000002", + "pkg:gem/actionpack@7.0.0", + "pkg:gem/actionpack@7.0.1", + }, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000002", + Dependencies: &[]string{ + "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1", + // Trivy is unable to identify the direct OS packages as of today. + "pkg:rpm/centos/glibc@2.28-151.el8?arch=aarch64&distro=centos-8.3.2011", + }, + }, + { + Ref: "pkg:gem/actionpack@7.0.0", + Dependencies: lo.ToPtr([]string{}), + }, + { + Ref: "pkg:gem/actionpack@7.0.1", + Dependencies: lo.ToPtr([]string{}), + }, + { + Ref: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1", + Dependencies: &[]string{ + "pkg:rpm/centos/glibc@2.28-151.el8?arch=aarch64&distro=centos-8.3.2011", + }, + }, + { + Ref: "pkg:rpm/centos/glibc@2.28-151.el8?arch=aarch64&distro=centos-8.3.2011", + Dependencies: lo.ToPtr([]string{}), + }, + }, + Vulnerabilities: &[]cdx.Vulnerability{ + { + ID: "CVE-2022-23633", + Source: &cdx.Source{ + Name: string(vulnerability.RubySec), + URL: "https://github.com/rubysec/ruby-advisory-db", + }, + Recommendation: "Upgrade actionpack to version ~> 5.2.6, >= 5.2.6.2, ~> 6.0.4, >= 6.0.4.6, ~> 6.1.4, >= 6.1.4.6, >= 7.0.2.2", + Ratings: &[]cdx.VulnerabilityRating{ + { + Source: &cdx.Source{ + Name: string(vulnerability.NVD), + }, + Score: lo.ToPtr(9.7), + Severity: cdx.SeverityHigh, + Method: cdx.ScoringMethodCVSSv2, + Vector: "AV:N/AC:L/Au:N/C:C/I:P/A:C", + }, + { + Source: &cdx.Source{ + Name: string(vulnerability.NVD), + }, + Score: lo.ToPtr(5.9), + Severity: cdx.SeverityMedium, + Method: cdx.ScoringMethodCVSSv31, + Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + }, + { + Source: &cdx.Source{ + Name: string(vulnerability.RedHat), + }, + Score: lo.ToPtr(5.9), + Severity: cdx.SeverityLow, + Method: cdx.ScoringMethodCVSSv31, + Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + }, + { + Source: &cdx.Source{ + Name: string(vulnerability.RubySec), + }, + Severity: cdx.SeverityHigh, + }, + }, + Description: "Action Pack is a framework for handling and responding to web requests. Under certain circumstances response bodies will not be closed. In the event a response is *not* notified of a `close`, `ActionDispatch::Executor` will not know to reset thread local state for the next request. This can lead to data being leaked to subsequent requests.This has been fixed in Rails 7.0.2.1, 6.1.4.5, 6.0.4.5, and 5.2.6.1. Upgrading is highly recommended, but to work around this problem a middleware described in GHSA-wh98-p28r-vrc9 can be used.", + Advisories: &[]cdx.Advisory{ + { + URL: "https://avd.aquasec.com/nvd/cve-2022-23633", + }, + { + URL: "http://www.openwall.com/lists/oss-security/2022/02/11/5", + }, + { + URL: "https://access.redhat.com/security/cve/CVE-2022-23633", + }, + }, + Published: "2022-02-11T21:15:00+00:00", + Updated: "2022-02-22T21:47:00+00:00", + Affects: &[]cdx.Affects{ + { + Ref: "pkg:gem/actionpack@7.0.0", + Range: &[]cdx.AffectedVersions{ + { + Version: "7.0.0", + Status: cdx.VulnerabilityStatusAffected, + }, + }, + }, + { + Ref: "pkg:gem/actionpack@7.0.1", + Range: &[]cdx.AffectedVersions{ + { + Version: "7.0.1", + Status: cdx.VulnerabilityStatusAffected, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path for fs scan", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "masahiro331/CVE-2021-41098", + ArtifactType: ftypes.ArtifactFilesystem, + Results: types.Results{ + { + Target: "Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: []ftypes.Package{ + { + Name: "actioncable", + Version: "6.1.4.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actioncable", + Version: "6.1.4.1", + }, + }, + }, + }, + }, + { + Target: "Java", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "org.springframework:spring-web", + Version: "5.3.22", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-web", + Version: "5.3.22", + }, + }, + FilePath: "spring-web-5.3.22.jar", + }, + }, + }, + { + Target: "yarn.lock", + Class: types.ClassLangPkg, + Type: ftypes.Yarn, + Packages: []ftypes.Package{ + { + ID: "@babel/helper-string-parser@7.23.4", + Name: "@babel/helper-string-parser", + Version: "7.23.4", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Namespace: "@babel", + Name: "helper-string-parser", + Version: "7.23.4", + }, + }, + }, + }, + }, + }, + }, + want: &cdx.BOM{ + XMLNS: "http://cyclonedx.org/schema/bom/1.5", + BOMFormat: "CycloneDX", + SpecVersion: cdx.SpecVersion1_5, + JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000007", + Version: 1, + Metadata: &cdx.Metadata{ + Timestamp: "2021-08-25T12:20:30+00:00", + Tools: &cdx.ToolsChoice{ + Components: &[]cdx.Component{ + { + Type: cdx.ComponentTypeApplication, + Name: "trivy", + Group: "aquasecurity", + Version: "dev", + }, + }, + }, + Component: &cdx.Component{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", + Type: cdx.ComponentTypeApplication, + Name: "masahiro331/CVE-2021-41098", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:SchemaVersion", + Value: "2", + }, + }, + }, + }, + Components: &[]cdx.Component{ + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + Type: cdx.ComponentTypeApplication, + Name: "Gemfile.lock", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:Class", + Value: "lang-pkgs", + }, + { + Name: "aquasecurity:trivy:Type", + Value: "bundler", + }, + }, + }, + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000005", + Type: cdx.ComponentTypeApplication, + Name: "yarn.lock", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:Class", + Value: "lang-pkgs", + }, + { + Name: "aquasecurity:trivy:Type", + Value: "yarn", + }, + }, + }, + { + BOMRef: "pkg:gem/actioncable@6.1.4.1", + Type: "library", + Name: "actioncable", + Version: "6.1.4.1", + PackageURL: "pkg:gem/actioncable@6.1.4.1", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgType", + Value: "bundler", + }, + }, + }, + { + BOMRef: "pkg:maven/org.springframework/spring-web@5.3.22", + Type: "library", + Name: "spring-web", + Group: "org.springframework", + Version: "5.3.22", + PackageURL: "pkg:maven/org.springframework/spring-web@5.3.22", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:FilePath", + Value: "spring-web-5.3.22.jar", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "jar", + }, + }, + }, + { + BOMRef: "pkg:npm/%40babel/helper-string-parser@7.23.4", + Type: "library", + Name: "helper-string-parser", + Group: "@babel", + Version: "7.23.4", + PackageURL: "pkg:npm/%40babel/helper-string-parser@7.23.4", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgID", + Value: "@babel/helper-string-parser@7.23.4", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "yarn", + }, + }, + }, + }, + Vulnerabilities: &[]cdx.Vulnerability{}, + Dependencies: &[]cdx.Dependency{ + { + Ref: "3ff14136-e09f-4df9-80ea-000000000001", + Dependencies: &[]string{ + "3ff14136-e09f-4df9-80ea-000000000002", + "3ff14136-e09f-4df9-80ea-000000000005", + "pkg:maven/org.springframework/spring-web@5.3.22", + }, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000002", + Dependencies: &[]string{ + "pkg:gem/actioncable@6.1.4.1", + }, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000005", + Dependencies: &[]string{ + "pkg:npm/%40babel/helper-string-parser@7.23.4", + }, + }, + { + Ref: "pkg:gem/actioncable@6.1.4.1", + Dependencies: lo.ToPtr([]string{}), + }, + { + Ref: "pkg:maven/org.springframework/spring-web@5.3.22", + Dependencies: lo.ToPtr([]string{}), + }, + { + Ref: "pkg:npm/%40babel/helper-string-parser@7.23.4", + Dependencies: lo.ToPtr([]string{}), + }, + }, + }, + }, + { + name: "happy path for sbom (cyclonedx) scan", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "./report.cdx.json", + ArtifactType: ftypes.ArtifactCycloneDX, + Results: types.Results{ + { + Target: "Java", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "com.fasterxml.jackson.core:jackson-databind", + Version: "2.13.4.1", + Identifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.13.4.1", + }, + }, + FilePath: "jackson-databind-2.13.4.1.jar", + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-42003", + PkgName: "com.fasterxml.jackson.core:jackson-databind", + PkgPath: "jackson-databind-2.13.4.1.jar", + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.13.4.1", + }, + }, + InstalledVersion: "2.13.4.1", + FixedVersion: "2.12.7.1, 2.13.4.2", + Status: dtypes.StatusFixed, + SeveritySource: "ghsa", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2022-42003", + DataSource: &dtypes.DataSource{ + ID: vulnerability.GHSA, + Name: "GitHub Security Advisory Maven", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + Vulnerability: dtypes.Vulnerability{ + Title: "jackson-databind: deep wrapper array nesting wrt UNWRAP_SINGLE_VALUE_ARRAYS", + Description: "In FasterXML jackson-databind before versions 2.13.4.1 and 2.12.17.1, resource exhaustion can occur because of a lack of a check in primitive value deserializers to avoid deep wrapper array nesting, when the UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled.", + Severity: dtypes.SeverityHigh.String(), + VendorSeverity: dtypes.VendorSeverity{ + vulnerability.GHSA: dtypes.SeverityHigh, + }, + CVSS: dtypes.VendorCVSS{ + vulnerability.GHSA: dtypes.CVSS{ + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + V3Score: 7.5, + }, + }, + References: []string{ + "https://access.redhat.com/security/cve/CVE-2022-42003", + }, + PublishedDate: lo.ToPtr(time.Date(2022, 10, 02, 05, 15, 0, 0, time.UTC)), + LastModifiedDate: lo.ToPtr(time.Date(2022, 12, 20, 10, 15, 0, 0, time.UTC)), + }, + }, + }, + }, + }, + BOM: testSBOM, + }, + want: &cdx.BOM{ + XMLNS: "http://cyclonedx.org/schema/bom/1.5", + BOMFormat: "CycloneDX", + SpecVersion: cdx.SpecVersion1_5, + JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000002", + Version: 1, + Metadata: &cdx.Metadata{ + Timestamp: "2021-08-25T12:20:30+00:00", + Tools: &cdx.ToolsChoice{ + Components: &[]cdx.Component{ + { + Type: cdx.ComponentTypeApplication, + Name: "trivy", + Group: "aquasecurity", + Version: "dev", + }, + }, + }, + Component: &cdx.Component{ + BOMRef: "aff65b54-6009-4c32-968d-748949ef46e8", // The original bom-ref is used + Type: cdx.ComponentTypeApplication, + Name: "jackson-databind-2.13.4.1.jar", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:SchemaVersion", + Value: "2", + }, + }, + }, + }, + Components: &[]cdx.Component{ + { + BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", + Type: cdx.ComponentTypeLibrary, + Group: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.13.4.1", + PackageURL: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:FilePath", + Value: "jackson-databind-2.13.4.1.jar", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "jar", + }, + }, + }, + }, + Vulnerabilities: &[]cdx.Vulnerability{ + { + ID: "CVE-2022-42003", + Source: &cdx.Source{ + Name: string(vulnerability.GHSA), + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + Recommendation: "Upgrade com.fasterxml.jackson.core:jackson-databind to version 2.12.7.1, 2.13.4.2", + Ratings: &[]cdx.VulnerabilityRating{ + { + Source: &cdx.Source{ + Name: string(vulnerability.GHSA), + }, + Score: lo.ToPtr(7.5), + Severity: cdx.SeverityHigh, + Method: cdx.ScoringMethodCVSSv31, + Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + }, + }, + Description: "In FasterXML jackson-databind before versions 2.13.4.1 and 2.12.17.1, resource exhaustion can occur because of a lack of a check in primitive value deserializers to avoid deep wrapper array nesting, when the UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled.", + Advisories: &[]cdx.Advisory{ + { + URL: "https://avd.aquasec.com/nvd/cve-2022-42003", + }, + { + URL: "https://access.redhat.com/security/cve/CVE-2022-42003", + }, + }, + Published: "2022-10-02T05:15:00+00:00", + Updated: "2022-12-20T10:15:00+00:00", + Affects: &[]cdx.Affects{ + { + Ref: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", + Range: &[]cdx.AffectedVersions{ + { + Version: "2.13.4.1", + Status: cdx.VulnerabilityStatusAffected, + }, + }, + }, + }, + }, + }, + Dependencies: &[]cdx.Dependency{ + { + Ref: "aff65b54-6009-4c32-968d-748949ef46e8", + Dependencies: &[]string{ + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", + }, + }, + { + Ref: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", + Dependencies: lo.ToPtr([]string{}), + }, + }, + }, + }, + { + name: "happy path. 2 packages for 1 CVE", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "CVE-2023-34468", + ArtifactType: ftypes.ArtifactFilesystem, + Results: types.Results{ + { + Target: "Java", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "org.apache.nifi:nifi-dbcp-base", + Version: "1.20.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.nifi", + Name: "nifi-dbcp-base", + Version: "1.20.0", + }, + }, + FilePath: "nifi-dbcp-base-1.20.0.jar", + }, + { + Name: "org.apache.nifi:nifi-hikari-dbcp-service", + Version: "1.20.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.nifi", + Name: "nifi-hikari-dbcp-service", + Version: "1.20.0", + }, + }, + FilePath: "nifi-hikari-dbcp-service-1.20.0.jar", + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2023-34468", + PkgName: "org.apache.nifi:nifi-dbcp-base", + PkgPath: "nifi-dbcp-base-1.20.0.jar", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.nifi", + Name: "nifi-dbcp-base", + Version: "1.20.0", + }, + }, + InstalledVersion: "1.20.0", + FixedVersion: "1.22.0", + SeveritySource: vulnerability.GHSA, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2023-34468", + DataSource: &dtypes.DataSource{ + ID: vulnerability.GHSA, + Name: "GitHub Security Advisory Maven", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + Vulnerability: dtypes.Vulnerability{ + Title: "Apache NiFi vulnerable to Code Injection", + Description: "The DBCPConnectionPool and HikariCPConnectionPool Controller Services in Apache NiFi 0.0.2 through 1.21.0...", + Severity: dtypes.SeverityHigh.String(), + CweIDs: []string{ + "CWE-94", + }, + VendorSeverity: dtypes.VendorSeverity{ + vulnerability.GHSA: dtypes.SeverityHigh, + vulnerability.NVD: dtypes.SeverityHigh, + }, + CVSS: dtypes.VendorCVSS{ + vulnerability.GHSA: dtypes.CVSS{ + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V3Score: 8.8, + }, + vulnerability.NVD: dtypes.CVSS{ + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V3Score: 8.8, + }, + }, + References: []string{ + "http://www.openwall.com/lists/oss-security/2023/06/12/3", + "https://github.com/advisories/GHSA-xm2m-2q6h-22jw", + }, + PublishedDate: lo.ToPtr(time.Date(2023, 6, 12, 16, 15, 0, 0, time.UTC)), + LastModifiedDate: lo.ToPtr(time.Date(2023, 6, 21, 02, 20, 0, 0, time.UTC)), + }, + }, + { + VulnerabilityID: "CVE-2023-34468", + PkgName: "org.apache.nifi:nifi-hikari-dbcp-service", + PkgPath: "nifi-hikari-dbcp-service-1.20.0.jar", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.nifi", + Name: "nifi-hikari-dbcp-service", + Version: "1.20.0", + }, + }, + InstalledVersion: "1.20.0", + FixedVersion: "1.22.0", + SeveritySource: vulnerability.GHSA, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2023-34468", + DataSource: &dtypes.DataSource{ + ID: vulnerability.GHSA, + Name: "GitHub Security Advisory Maven", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + Vulnerability: dtypes.Vulnerability{ + Title: "Apache NiFi vulnerable to Code Injection", + Description: "The DBCPConnectionPool and HikariCPConnectionPool Controller Services in Apache NiFi 0.0.2 through 1.21.0...", + Severity: dtypes.SeverityHigh.String(), + CweIDs: []string{ + "CWE-94", + }, + VendorSeverity: dtypes.VendorSeverity{ + vulnerability.GHSA: dtypes.SeverityHigh, + vulnerability.NVD: dtypes.SeverityHigh, + }, + CVSS: dtypes.VendorCVSS{ + vulnerability.GHSA: dtypes.CVSS{ + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V3Score: 8.8, + }, + vulnerability.NVD: dtypes.CVSS{ + V3Vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V3Score: 8.8, + }, + }, + References: []string{ + "http://www.openwall.com/lists/oss-security/2023/06/12/3", + "https://github.com/advisories/GHSA-xm2m-2q6h-22jw", + }, + PublishedDate: lo.ToPtr(time.Date(2023, 6, 12, 16, 15, 0, 0, time.UTC)), + LastModifiedDate: lo.ToPtr(time.Date(2023, 6, 21, 02, 20, 0, 0, time.UTC)), + }, + }, + }, + }, + }, + }, + want: &cdx.BOM{ + XMLNS: "http://cyclonedx.org/schema/bom/1.5", + BOMFormat: "CycloneDX", + SpecVersion: cdx.SpecVersion1_5, + JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000004", + Version: 1, + Metadata: &cdx.Metadata{ + Timestamp: "2021-08-25T12:20:30+00:00", + Tools: &cdx.ToolsChoice{ + Components: &[]cdx.Component{ + { + Type: cdx.ComponentTypeApplication, + Name: "trivy", + Group: "aquasecurity", + Version: "dev", + }, + }, + }, + Component: &cdx.Component{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", + Type: cdx.ComponentTypeApplication, + Name: "CVE-2023-34468", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:SchemaVersion", + Value: "2", + }, + }, + }, + }, + Components: &[]cdx.Component{ + { + BOMRef: "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0", + Type: "library", + Name: "nifi-dbcp-base", + Group: "org.apache.nifi", + Version: "1.20.0", + PackageURL: "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:FilePath", + Value: "nifi-dbcp-base-1.20.0.jar", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "jar", + }, + }, + }, + { + BOMRef: "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0", + Type: "library", + Name: "nifi-hikari-dbcp-service", + Group: "org.apache.nifi", + Version: "1.20.0", + PackageURL: "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:FilePath", + Value: "nifi-hikari-dbcp-service-1.20.0.jar", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "jar", + }, + }, + }, + }, + Dependencies: &[]cdx.Dependency{ + { + Ref: "3ff14136-e09f-4df9-80ea-000000000001", + Dependencies: &[]string{ + "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0", + "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0", + }, + }, + { + Ref: "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0", + Dependencies: lo.ToPtr([]string{}), + }, + { + Ref: "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0", + Dependencies: lo.ToPtr([]string{}), + }, + }, + Vulnerabilities: &[]cdx.Vulnerability{ + { + ID: "CVE-2023-34468", + Source: &cdx.Source{ + Name: string(vulnerability.GHSA), + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven", + }, + Recommendation: "Upgrade org.apache.nifi:nifi-dbcp-base to version 1.22.0; Upgrade org.apache.nifi:nifi-hikari-dbcp-service to version 1.22.0", + Ratings: &[]cdx.VulnerabilityRating{ + { + Source: &cdx.Source{ + Name: string(vulnerability.GHSA), + }, + Score: lo.ToPtr(8.8), + Severity: cdx.SeverityHigh, + Method: cdx.ScoringMethodCVSSv31, + Vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + }, + { + Source: &cdx.Source{ + Name: string(vulnerability.NVD), + }, + Score: lo.ToPtr(8.8), + Severity: cdx.SeverityHigh, + Method: cdx.ScoringMethodCVSSv31, + Vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + }, + }, + CWEs: lo.ToPtr([]int{94}), + Description: "The DBCPConnectionPool and HikariCPConnectionPool Controller Services in Apache NiFi 0.0.2 through 1.21.0...", + Advisories: &[]cdx.Advisory{ + { + URL: "https://avd.aquasec.com/nvd/cve-2023-34468", + }, + { + URL: "http://www.openwall.com/lists/oss-security/2023/06/12/3", + }, + { + URL: "https://github.com/advisories/GHSA-xm2m-2q6h-22jw", + }, + }, + Published: "2023-06-12T16:15:00+00:00", + Updated: "2023-06-21T02:20:00+00:00", + Affects: &[]cdx.Affects{ + { + Ref: "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0", + Range: &[]cdx.AffectedVersions{ + { + Version: "1.20.0", + Status: cdx.VulnerabilityStatusAffected, + }, + }, + }, + { + Ref: "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0", + Range: &[]cdx.AffectedVersions{ + { + Version: "1.20.0", + Status: cdx.VulnerabilityStatusAffected, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path aggregate results", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "test-aggregate", + ArtifactType: ftypes.ArtifactRepository, + Results: types.Results{ + { + Target: "Node.js", + Class: types.ClassLangPkg, + Type: ftypes.NodePkg, + Packages: []ftypes.Package{ + { + ID: "ruby-typeprof@0.20.1", + Name: "ruby-typeprof", + Version: "0.20.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "ruby-typeprof", + Version: "0.20.1", + }, + }, + Licenses: []string{"MIT"}, + Layer: ftypes.Layer{ + DiffID: "sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e", + }, + FilePath: "usr/local/lib/ruby/gems/3.1.0/gems/typeprof-0.21.1/vscode/package.json", + }, + }, + }, + }, + }, + want: &cdx.BOM{ + XMLNS: "http://cyclonedx.org/schema/bom/1.5", + BOMFormat: "CycloneDX", + SpecVersion: cdx.SpecVersion1_5, + JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000003", + Version: 1, + Metadata: &cdx.Metadata{ + Timestamp: "2021-08-25T12:20:30+00:00", + Tools: &cdx.ToolsChoice{ + Components: &[]cdx.Component{ + { + Type: cdx.ComponentTypeApplication, + Name: "trivy", + Group: "aquasecurity", + Version: "dev", + }, + }, + }, + Component: &cdx.Component{ + Type: cdx.ComponentTypeApplication, + Name: "test-aggregate", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:SchemaVersion", + Value: "2", + }, + }, + }, + }, + Components: &[]cdx.Component{ + { + BOMRef: "pkg:npm/ruby-typeprof@0.20.1", + Type: "library", + Name: "ruby-typeprof", + Version: "0.20.1", + PackageURL: "pkg:npm/ruby-typeprof@0.20.1", + Licenses: &cdx.Licenses{ + cdx.LicenseChoice{ + License: &cdx.License{ + Name: "MIT", + }, + }, + }, + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:FilePath", + Value: "usr/local/lib/ruby/gems/3.1.0/gems/typeprof-0.21.1/vscode/package.json", + }, + { + Name: "aquasecurity:trivy:LayerDiffID", + Value: "sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e", + }, + { + Name: "aquasecurity:trivy:PkgID", + Value: "ruby-typeprof@0.20.1", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "node-pkg", + }, + }, + }, + }, + Vulnerabilities: &[]cdx.Vulnerability{}, + Dependencies: &[]cdx.Dependency{ + { + Ref: "3ff14136-e09f-4df9-80ea-000000000001", + Dependencies: &[]string{ + "pkg:npm/ruby-typeprof@0.20.1", + }, + }, + { + Ref: "pkg:npm/ruby-typeprof@0.20.1", + Dependencies: lo.ToPtr([]string{}), + }, + }, + }, + }, + { + name: "happy path empty", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "empty/path", + ArtifactType: ftypes.ArtifactFilesystem, + Results: types.Results{}, + }, + want: &cdx.BOM{ + XMLNS: "http://cyclonedx.org/schema/bom/1.5", + BOMFormat: "CycloneDX", + SpecVersion: cdx.SpecVersion1_5, + JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000002", + Version: 1, + Metadata: &cdx.Metadata{ + Timestamp: "2021-08-25T12:20:30+00:00", + Tools: &cdx.ToolsChoice{ + Components: &[]cdx.Component{ + { + Type: cdx.ComponentTypeApplication, + Name: "trivy", + Group: "aquasecurity", + Version: "dev", + }, + }, + }, + Component: &cdx.Component{ + Type: cdx.ComponentTypeApplication, + Name: "empty/path", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:SchemaVersion", + Value: "2", + }, + }, + }, + }, + Components: &[]cdx.Component{}, + Vulnerabilities: &[]cdx.Vulnerability{}, + Dependencies: &[]cdx.Dependency{ + { + Ref: "3ff14136-e09f-4df9-80ea-000000000001", + Dependencies: lo.ToPtr([]string{}), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) + uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d") + + marshaler := cyclonedx.NewMarshaler("dev") + got, err := marshaler.MarshalReport(ctx, tt.inputReport) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/sbom/cyclonedx/testdata/happy/bom.json b/pkg/sbom/cyclonedx/testdata/happy/bom.json new file mode 100644 index 000000000000..a7a1a474b8bd --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/bom.json @@ -0,0 +1,279 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "container", + "name": "maven-test-project", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "maven-test-project:latest" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + "type": "library", + "name": "musl", + "version": "1.2.3-r0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + "properties": [ + { + "name": "aquasecurity:trivy:SrcName", + "value": "musl" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "1.2.3-r0" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + } + ] + }, + { + "bom-ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "type": "operating-system", + "name": "alpine", + "version": "3.16.0", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "alpine" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + } + ] + }, + { + "bom-ref": "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", + "type": "library", + "name": "child-project", + "group": "org.codehaus.mojo", + "version": "1.0", + "purl": "pkg:maven/org.codehaus.mojo/child-project@1.0", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "app/maven/target/child-project-1.0.jar" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "jar" + } + ] + }, + { + "bom-ref": "pkg:maven/com.example/example@0.0.1", + "type": "library", + "name": "com.example:example", + "version": "0.0.1", + "purl": "pkg:maven/com.example/example@0.0.1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } + ] + }, + { + "bom-ref": "pkg:npm/@example/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", + "type": "library", + "group": "@example", + "name": "bootstrap", + "version": "5.0.2", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:npm/@example/bootstrap@5.0.2", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "app/app/package.json" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "node-pkg" + } + ] + }, + { + "bom-ref": "pkg:composer/pear/log@1.13.1", + "type": "library", + "name": "pear/log", + "version": "1.13.1", + "purl": "pkg:composer/pear/log@1.13.1", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } + ] + }, + { + "bom-ref": "pkg:composer/pear/pear_exception@v1.0.0", + "type": "library", + "name": "pear/pear_exception", + "version": "v1.0.0", + "purl": "pkg:composer/pear/pear_exception@v1.0.0", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } + ] + }, + { + "bom-ref": "100925ff-7c0a-470f-a725-8fb973b40e7b", + "type": "application", + "name": "app/composer/composer.lock", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "composer" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + } + ] + }, + { + "bom-ref": "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + "type": "library", + "name": "github.com/package-url/packageurl-go", + "version": "v0.1.1-0.20220203205134-d70459300c8a", + "purl": "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } + ] + }, + { + "bom-ref": "1a111e6b-a682-470e-8b0e-aaa49d93cd39", + "type": "application", + "name": "app/gobinary/gobinary", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "gobinary" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + } + ] + }, + { + "bom-ref": "43b87f73-4b96-25ab-845b-489321cea146", + "type": "application", + "name": "app/gradle/target/gradle.lockfile", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "gradle" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + } + ] + } + ], + "dependencies": [ + { + "ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "dependsOn": [ + "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0" + ] + }, + { + "ref": "100925ff-7c0a-470f-a725-8fb973b40e7b", + "dependsOn": [ + "pkg:composer/pear/log@1.13.1", + "pkg:composer/pear/pear_exception@v1.0.0" + ] + }, + { + "ref": "1a111e6b-a682-470e-8b0e-aaa49d93cd39", + "dependsOn": [ + "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a" + ] + }, + { + "ref": "43b87f73-4b96-25ab-845b-489321cea146", + "dependsOn": [ + "pkg:maven/com.example/example@0.0.1" + ] + }, + { + "ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "dependsOn": [ + "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", + "pkg:maven/com.example/example@0.0.1", + "pkg:npm/@example/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", + "100925ff-7c0a-470f-a725-8fb973b40e7b", + "1a111e6b-a682-470e-8b0e-aaa49d93cd39" + ] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/happy/empty-bom.json b/pkg/sbom/cyclonedx/testdata/happy/empty-bom.json new file mode 100644 index 000000000000..b61f7bef7b5f --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/empty-bom.json @@ -0,0 +1,48 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "tools": [ + { + "vendor": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ], + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "container", + "name": "maven-test-project", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "maven-test-project:latest" + } + ] + } + }, + "dependencies": [ + { + "ref": "0f585d64-4815-4b72-92c5-97dae191fa4a" + } + ] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/happy/empty-metadata-component-bom.json b/pkg/sbom/cyclonedx/testdata/happy/empty-metadata-component-bom.json new file mode 100644 index 000000000000..2fb29d2647af --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/empty-metadata-component-bom.json @@ -0,0 +1,20 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + } + }, + "dependencies": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/happy/group-in-name.json b/pkg/sbom/cyclonedx/testdata/happy/group-in-name.json new file mode 100644 index 000000000000..f7e7e44dc437 --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/group-in-name.json @@ -0,0 +1,62 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:8366a7c8-229c-4518-b86c-8a1bcf69af01", + "version": 1, + "metadata": { + "timestamp": "2023-06-20T04:32:10+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "b0ae8323-eb7b-4be5-bc5c-4849fd795ec0", + "type": "application", + "name": "spring-web-5.3.22.jar", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + "type": "library", + "name": "org.springframework:spring-web", + "version": "5.3.22", + "purl": "pkg:maven/org.springframework/spring-web@5.3.22", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "spring-web-5.3.22.jar" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "jar" + } + ] + } + ], + "dependencies": [ + { + "ref": "b0ae8323-eb7b-4be5-bc5c-4849fd795ec0", + "dependsOn": [ + "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar" + ] + }, + { + "ref": "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + "dependsOn": [] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/happy/independent-library-bom.json b/pkg/sbom/cyclonedx/testdata/happy/independent-library-bom.json new file mode 100644 index 000000000000..164eed844166 --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/independent-library-bom.json @@ -0,0 +1,63 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "application", + "name": "maven-test-project", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:composer/pear/core@1.13.1", + "type": "library", + "name": "pear/core", + "version": "1.13.1", + "purl": "pkg:composer/pear/core@1.13.1" + }, + { + "bom-ref": "pkg:composer/pear/log@1.13.1", + "type": "library", + "name": "pear/log", + "version": "1.13.1", + "purl": "pkg:composer/pear/log@1.13.1" + }, + { + "bom-ref": "pkg:composer/pear/pear_exception@v1.0.0", + "type": "library", + "name": "pear/pear_exception", + "version": "v1.0.0", + "purl": "pkg:composer/pear/pear_exception@v1.0.0" + } + ], + "dependencies": [ + { + "ref": "pkg:composer/pear/core@1.13.1", + "dependsOn": [ + "pkg:composer/pear/log@1.13.1", + "pkg:composer/pear/pear_exception@v1.0.0" + ] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/happy/infinite-loop-bom.json b/pkg/sbom/cyclonedx/testdata/happy/infinite-loop-bom.json new file mode 100644 index 000000000000..d1080b4de92a --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/infinite-loop-bom.json @@ -0,0 +1,182 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:a085f5e7-f5c1-4bc0-96be-ffa4d235ebc8", + "version": 1, + "metadata": { + "timestamp": "2023-04-06T05:41:44+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "pkg:oci/ubuntu@sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21?repository_url=index.docker.io%2Flibrary%2Fubuntu\u0026arch=amd64", + "type": "container", + "name": "ubuntu", + "purl": "pkg:oci/ubuntu@sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21?repository_url=index.docker.io%2Flibrary%2Fubuntu\u0026arch=amd64", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:08d22c0ceb150ddeb2237c5fa3129c0183f3cc6f5eeb2e7aa4016da3ad02140a" + }, + { + "name": "aquasecurity:trivy:RepoDigest", + "value": "ubuntu@sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "ubuntu:latest" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:deb/ubuntu/libc6@2.35-0ubuntu3.1?distro=ubuntu-22.04", + "type": "library", + "name": "libc6", + "version": "2.35-0ubuntu3.1", + "licenses": [ + { + "license": { + "name": "LGPL-2.1" + } + }, + { + "license": { + "name": "GPL-2.0" + } + }, + { + "license": { + "name": "GFDL-1.3" + } + } + ], + "purl": "pkg:deb/ubuntu/libc6@2.35-0ubuntu3.1?distro=ubuntu-22.04", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "libc6@2.35-0ubuntu3.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "ubuntu" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "glibc" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.35" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0ubuntu3.1" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:74ac377868f863e123f24c409f79709f7563fa464557c36a09cf6f85c8b92b7f" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466" + } + ] + }, + { + "bom-ref": "pkg:deb/ubuntu/libcrypt1@4.4.27-1?epoch=1\u0026distro=ubuntu-22.04", + "type": "library", + "name": "libcrypt1", + "version": "4.4.27-1", + "purl": "pkg:deb/ubuntu/libcrypt1@4.4.27-1?epoch=1\u0026distro=ubuntu-22.04", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "libcrypt1@1:4.4.27-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "ubuntu" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libxcrypt" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "4.4.27" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "1" + }, + { + "name": "aquasecurity:trivy:SrcEpoch", + "value": "1" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:74ac377868f863e123f24c409f79709f7563fa464557c36a09cf6f85c8b92b7f" + }, + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466" + } + ] + }, + { + "bom-ref": "71577c3e-dbc6-41be-ba9e-c5268bc2e5d4", + "type": "operating-system", + "name": "ubuntu", + "version": "22.04", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "ubuntu" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + } + ] + } + ], + "dependencies": [ + { + "ref": "71577c3e-dbc6-41be-ba9e-c5268bc2e5d4", + "dependsOn": [ + "pkg:deb/ubuntu/libc6@2.35-0ubuntu3.1?distro=ubuntu-22.04", + "pkg:deb/ubuntu/libcrypt1@4.4.27-1?epoch=1\u0026distro=ubuntu-22.04" + ] + }, + { + "ref": "pkg:deb/ubuntu/libc6@2.35-0ubuntu3.1?distro=ubuntu-22.04", + "dependsOn": [ + "pkg:deb/ubuntu/libcrypt1@4.4.27-1?epoch=1\u0026distro=ubuntu-22.04" + ] + }, + { + "ref": "pkg:deb/ubuntu/libcrypt1@4.4.27-1?epoch=1\u0026distro=ubuntu-22.04", + "dependsOn": [ + "pkg:deb/ubuntu/libc6@2.35-0ubuntu3.1?distro=ubuntu-22.04" + ] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/happy/invalid-purl.json b/pkg/sbom/cyclonedx/testdata/happy/invalid-purl.json new file mode 100644 index 000000000000..070da15fbb05 --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/invalid-purl.json @@ -0,0 +1,55 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "application", + "name": "maven-test-project", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:composer/pear/core@1.13.1", + "type": "library", + "name": "pear/core", + "version": "1.13.1", + "purl": "pkg:composer/pear/core@1.13.1" + }, + { + "bom-ref": "pkg:composer/pear/log@1.13.1", + "type": "library", + "name": "pear/log", + "version": "1.13.1", + "purl": "invalid-purl-format" + } + ], + "dependencies": [ + { + "ref": "pkg:composer/pear/core@1.13.1", + "dependsOn": [ + "pkg:composer/pear/log@1.13.1" + ] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/happy/kbom.json b/pkg/sbom/cyclonedx/testdata/happy/kbom.json new file mode 100644 index 000000000000..7d4a5b82b65e --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/kbom.json @@ -0,0 +1,427 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:e2daaea6-d96f-4b84-960c-0d72c348cd23", + "version": 1, + "metadata": { + "timestamp": "2023-09-29T06:25:00+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "pkg:k8s/k8s.io%2Fkubernetes@1.27.4", + "type": "platform", + "name": "k8s.io/kubernetes", + "version": "1.27.4", + "purl": "pkg:k8s/k8s.io%2Fkubernetes@1.27.4", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "cluster" + } + ] + } + }, + "components": [ + { + "bom-ref": "5262e708-f1a3-4fca-a1c3-0a8384f7f4a5", + "type": "operating-system", + "name": "ubuntu", + "version": "22.04.2", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "ubuntu" + } + ] + }, + { + "bom-ref": "a62abb1f-cb38-4fde-90f3-2bda3b87ddb2", + "type": "application", + "name": "node-core-components" + }, + { + "bom-ref": "a6350ac3-52f6-4c5f-a3e3-184b9a634bef", + "type": "platform", + "name": "minikube", + "properties": [ + { + "name": "aquasecurity:trivy:Architecture", + "value": "arm64" + }, + { + "name": "aquasecurity:trivy:HostName", + "value": "minikube" + }, + { + "name": "aquasecurity:trivy:KernelVersion", + "value": "5.15.49-linuxkit-pr" + }, + { + "name": "aquasecurity:trivy:NodeRole", + "value": "master" + }, + { + "name": "aquasecurity:trivy:OperatingSystem", + "value": "linux" + }, + { + "name": "aquasecurity:trivy:resource:Name", + "value": "minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "node" + } + ] + }, + { + "bom-ref": "b19a88a3-017d-4e70-a73a-75f48696ec0f", + "type": "application", + "name": "kube-dns", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "coredns-5d78c9869d-nd92n" + } + ] + }, + { + "bom-ref": "b1c502c9-3c6e-43af-822b-1cb55c6c6ff3", + "type": "application", + "name": "go.etcd.io/etcd/v3", + "version": "3.5.7-0", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "etcd-minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "controlPlane" + } + ] + }, + { + "bom-ref": "pkg:golang/docker@24.0.4", + "type": "application", + "name": "docker", + "version": "24.0.4", + "purl": "pkg:golang/docker@24.0.4", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "docker" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "node" + } + ] + }, + { + "bom-ref": "pkg:k8s/k8s.io%2Fapiserver@1.27.4", + "type": "application", + "name": "k8s.io/apiserver", + "version": "1.27.4", + "purl": "pkg:k8s/k8s.io%2Fapiserver@1.27.4", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "kube-apiserver-minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "controlPlane" + } + ] + }, + { + "bom-ref": "pkg:k8s/k8s.io%2Fcontroller-manager@1.27.4", + "type": "application", + "name": "k8s.io/controller-manager", + "version": "1.27.4", + "purl": "pkg:k8s/k8s.io%2Fcontroller-manager@1.27.4", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "kube-controller-manager-minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "controlPlane" + } + ] + }, + { + "bom-ref": "pkg:k8s/k8s.io%2Fkube-proxy@1.27.4", + "type": "application", + "name": "k8s.io/kube-proxy", + "version": "1.27.4", + "purl": "pkg:k8s/k8s.io%2Fkube-proxy@1.27.4", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "kube-proxy-4wftc" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "node" + } + ] + }, + { + "bom-ref": "pkg:k8s/k8s.io%2Fkube-scheduler@1.27.4", + "type": "application", + "name": "k8s.io/kube-scheduler", + "version": "1.27.4", + "purl": "pkg:k8s/k8s.io%2Fkube-scheduler@1.27.4", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "kube-scheduler-minikube" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "controlPlane" + } + ] + }, + { + "bom-ref": "pkg:k8s/k8s.io%2Fkubelet@1.27.4", + "type": "application", + "name": "k8s.io/kubelet", + "version": "1.27.4", + "purl": "pkg:k8s/k8s.io%2Fkubelet@1.27.4", + "properties": [ + { + "name": "aquasecurity:trivy:resource:Name", + "value": "k8s.io/kubelet" + }, + { + "name": "aquasecurity:trivy:resource:Type", + "value": "node" + } + ] + }, + { + "bom-ref": "pkg:oci/coredns@sha256%3Aa0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e?repository_url=registry.k8s.io%2Fcoredns%2Fcoredns", + "type": "container", + "name": "registry.k8s.io/coredns/coredns", + "version": "sha256:a0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e", + "purl": "pkg:oci/coredns@sha256%3Aa0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e?repository_url=registry.k8s.io%2Fcoredns%2Fcoredns", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/coredns/coredns:1.10.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + }, + { + "bom-ref": "pkg:oci/etcd@sha256%3A51eae8381dcb1078289fa7b4f3df2630cdc18d09fb56f8e56b41c40e191d6c83?repository_url=registry.k8s.io%2Fetcd", + "type": "container", + "name": "registry.k8s.io/etcd", + "version": "sha256:51eae8381dcb1078289fa7b4f3df2630cdc18d09fb56f8e56b41c40e191d6c83", + "purl": "pkg:oci/etcd@sha256%3A51eae8381dcb1078289fa7b4f3df2630cdc18d09fb56f8e56b41c40e191d6c83?repository_url=registry.k8s.io%2Fetcd", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/etcd:3.5.7-0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + }, + { + "bom-ref": "pkg:oci/kube-apiserver@sha256%3A697cd88d94f7f2ef42144cb3072b016dcb2e9251f0e7d41a7fede557e555452d?repository_url=registry.k8s.io%2Fkube-apiserver", + "type": "container", + "name": "registry.k8s.io/kube-apiserver", + "version": "sha256:697cd88d94f7f2ef42144cb3072b016dcb2e9251f0e7d41a7fede557e555452d", + "purl": "pkg:oci/kube-apiserver@sha256%3A697cd88d94f7f2ef42144cb3072b016dcb2e9251f0e7d41a7fede557e555452d?repository_url=registry.k8s.io%2Fkube-apiserver", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/kube-apiserver:1.27.4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + }, + { + "bom-ref": "pkg:oci/kube-controller-manager@sha256%3A6286e500782ad6d0b37a1b8be57fc73f597dc931dfc73ff18ce534059803b265?repository_url=registry.k8s.io%2Fkube-controller-manager", + "type": "container", + "name": "registry.k8s.io/kube-controller-manager", + "version": "sha256:6286e500782ad6d0b37a1b8be57fc73f597dc931dfc73ff18ce534059803b265", + "purl": "pkg:oci/kube-controller-manager@sha256%3A6286e500782ad6d0b37a1b8be57fc73f597dc931dfc73ff18ce534059803b265?repository_url=registry.k8s.io%2Fkube-controller-manager", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/kube-controller-manager:1.27.4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + }, + { + "bom-ref": "pkg:oci/kube-proxy@sha256%3A4bcb707da9898d2625f5d4edc6d0c96519a24f16db914fc673aa8f97e41dbabf?repository_url=registry.k8s.io%2Fkube-proxy", + "type": "container", + "name": "registry.k8s.io/kube-proxy", + "version": "sha256:4bcb707da9898d2625f5d4edc6d0c96519a24f16db914fc673aa8f97e41dbabf", + "purl": "pkg:oci/kube-proxy@sha256%3A4bcb707da9898d2625f5d4edc6d0c96519a24f16db914fc673aa8f97e41dbabf?repository_url=registry.k8s.io%2Fkube-proxy", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/kube-proxy:1.27.4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + }, + { + "bom-ref": "pkg:oci/kube-scheduler@sha256%3A5897d7a97d23dce25cbf36fcd6e919180a8ef904bf5156583ffdb6a733ab04af?repository_url=registry.k8s.io%2Fkube-scheduler", + "type": "container", + "name": "registry.k8s.io/kube-scheduler", + "version": "sha256:5897d7a97d23dce25cbf36fcd6e919180a8ef904bf5156583ffdb6a733ab04af", + "purl": "pkg:oci/kube-scheduler@sha256%3A5897d7a97d23dce25cbf36fcd6e919180a8ef904bf5156583ffdb6a733ab04af?repository_url=registry.k8s.io%2Fkube-scheduler", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "registry.k8s.io/kube-scheduler:1.27.4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "oci" + } + ] + } + ], + "dependencies": [ + { + "ref": "5262e708-f1a3-4fca-a1c3-0a8384f7f4a5", + "dependsOn": [] + }, + { + "ref": "a62abb1f-cb38-4fde-90f3-2bda3b87ddb2", + "dependsOn": [ + "pkg:golang/docker@24.0.4", + "pkg:k8s/k8s.io%2Fkubelet@1.27.4" + ] + }, + { + "ref": "a6350ac3-52f6-4c5f-a3e3-184b9a634bef", + "dependsOn": [ + "5262e708-f1a3-4fca-a1c3-0a8384f7f4a5", + "a62abb1f-cb38-4fde-90f3-2bda3b87ddb2" + ] + }, + { + "ref": "b19a88a3-017d-4e70-a73a-75f48696ec0f", + "dependsOn": [ + "pkg:oci/coredns@sha256%3Aa0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e?repository_url=registry.k8s.io%2Fcoredns%2Fcoredns" + ] + }, + { + "ref": "b1c502c9-3c6e-43af-822b-1cb55c6c6ff3", + "dependsOn": [ + "pkg:oci/etcd@sha256%3A51eae8381dcb1078289fa7b4f3df2630cdc18d09fb56f8e56b41c40e191d6c83?repository_url=registry.k8s.io%2Fetcd" + ] + }, + { + "ref": "pkg:golang/docker@24.0.4", + "dependsOn": [] + }, + { + "ref": "pkg:k8s/k8s.io%2Fapiserver@1.27.4", + "dependsOn": [ + "pkg:oci/kube-apiserver@sha256%3A697cd88d94f7f2ef42144cb3072b016dcb2e9251f0e7d41a7fede557e555452d?repository_url=registry.k8s.io%2Fkube-apiserver" + ] + }, + { + "ref": "pkg:k8s/k8s.io%2Fcontroller-manager@1.27.4", + "dependsOn": [ + "pkg:oci/kube-controller-manager@sha256%3A6286e500782ad6d0b37a1b8be57fc73f597dc931dfc73ff18ce534059803b265?repository_url=registry.k8s.io%2Fkube-controller-manager" + ] + }, + { + "ref": "pkg:k8s/k8s.io%2Fkube-proxy@1.27.4", + "dependsOn": [ + "pkg:oci/kube-proxy@sha256%3A4bcb707da9898d2625f5d4edc6d0c96519a24f16db914fc673aa8f97e41dbabf?repository_url=registry.k8s.io%2Fkube-proxy" + ] + }, + { + "ref": "pkg:k8s/k8s.io%2Fkube-scheduler@1.27.4", + "dependsOn": [ + "pkg:oci/kube-scheduler@sha256%3A5897d7a97d23dce25cbf36fcd6e919180a8ef904bf5156583ffdb6a733ab04af?repository_url=registry.k8s.io%2Fkube-scheduler" + ] + }, + { + "ref": "pkg:k8s/k8s.io%2Fkubelet@1.27.4", + "dependsOn": [] + }, + { + "ref": "pkg:k8s/k8s.io%2Fkubernetes@1.27.4", + "dependsOn": [ + "a6350ac3-52f6-4c5f-a3e3-184b9a634bef", + "b19a88a3-017d-4e70-a73a-75f48696ec0f", + "b1c502c9-3c6e-43af-822b-1cb55c6c6ff3", + "pkg:k8s/k8s.io%2Fapiserver@1.27.4", + "pkg:k8s/k8s.io%2Fcontroller-manager@1.27.4", + "pkg:k8s/k8s.io%2Fkube-proxy@1.27.4", + "pkg:k8s/k8s.io%2Fkube-scheduler@1.27.4" + ] + }, + { + "ref": "pkg:oci/coredns@sha256%3Aa0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e?repository_url=registry.k8s.io%2Fcoredns%2Fcoredns", + "dependsOn": [] + }, + { + "ref": "pkg:oci/etcd@sha256%3A51eae8381dcb1078289fa7b4f3df2630cdc18d09fb56f8e56b41c40e191d6c83?repository_url=registry.k8s.io%2Fetcd", + "dependsOn": [] + }, + { + "ref": "pkg:oci/kube-apiserver@sha256%3A697cd88d94f7f2ef42144cb3072b016dcb2e9251f0e7d41a7fede557e555452d?repository_url=registry.k8s.io%2Fkube-apiserver", + "dependsOn": [] + }, + { + "ref": "pkg:oci/kube-controller-manager@sha256%3A6286e500782ad6d0b37a1b8be57fc73f597dc931dfc73ff18ce534059803b265?repository_url=registry.k8s.io%2Fkube-controller-manager", + "dependsOn": [] + }, + { + "ref": "pkg:oci/kube-proxy@sha256%3A4bcb707da9898d2625f5d4edc6d0c96519a24f16db914fc673aa8f97e41dbabf?repository_url=registry.k8s.io%2Fkube-proxy", + "dependsOn": [] + }, + { + "ref": "pkg:oci/kube-scheduler@sha256%3A5897d7a97d23dce25cbf36fcd6e919180a8ef904bf5156583ffdb6a733ab04af?repository_url=registry.k8s.io%2Fkube-scheduler", + "dependsOn": [] + } + ], + "vulnerabilities": [] +} diff --git a/pkg/sbom/cyclonedx/testdata/happy/os-only-bom.json b/pkg/sbom/cyclonedx/testdata/happy/os-only-bom.json new file mode 100644 index 000000000000..837c16754211 --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/os-only-bom.json @@ -0,0 +1,77 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "container", + "name": "maven-test-project", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "maven-test-project:latest" + } + ] + } + }, + "components": [ + { + "bom-ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "type": "operating-system", + "name": "alpine", + "version": "3.16.0", + "properties": [ + { + "name": "aquasecurity:trivy:Type", + "value": "alpine" + }, + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + } + ] + } + ], + "dependencies": [ + { + "ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "dependsOn": [] + }, + { + "ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "dependsOn": [ + "60e9f57b-d4a6-4f71-ad14-0893ac609182" + ] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/happy/third-party-bom-no-os.json b/pkg/sbom/cyclonedx/testdata/happy/third-party-bom-no-os.json new file mode 100644 index 000000000000..436b7dcbf892 --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/third-party-bom-no-os.json @@ -0,0 +1,38 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "container", + "name": "test-project" + } + }, + "components": [ + { + "bom-ref": "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + "type": "library", + "name": "musl", + "version": "1.2.3-r0", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0" + }, + { + "bom-ref": "pkg:composer/pear/log@1.13.1", + "type": "library", + "name": "pear/log", + "version": "1.13.1", + "purl": "pkg:composer/pear/log@1.13.1" + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/happy/third-party-bom.json b/pkg/sbom/cyclonedx/testdata/happy/third-party-bom.json new file mode 100644 index 000000000000..d8b5068f5cc0 --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/third-party-bom.json @@ -0,0 +1,56 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "container", + "name": "test-project" + } + }, + "components": [ + { + "bom-ref": "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + "type": "library", + "name": "musl", + "version": "1.2.3-r0", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0" + }, + { + "bom-ref": "60e9f57b-d4a6-4f71-ad14-0893ac609182", + "type": "operating-system", + "name": "alpine", + "version": "3.16.0" + }, + { + "bom-ref": "pkg:composer/pear/log@1.13.1", + "type": "library", + "name": "pear/log", + "version": "1.13.1", + "purl": "pkg:composer/pear/log@1.13.1", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ] + }, + { + "bom-ref": "pkg:composer/pear/pear_exception@v1.0.0", + "type": "library", + "name": "pear/pear_exception", + "version": "v1.0.0", + "purl": "pkg:composer/pear/pear_exception@v1.0.0" + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/happy/unrelated-bom.json b/pkg/sbom/cyclonedx/testdata/happy/unrelated-bom.json new file mode 100644 index 000000000000..aecf8e05abfb --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/unrelated-bom.json @@ -0,0 +1,67 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", + "version": 1, + "metadata": { + "timestamp": "2022-05-28T10:20:03.79527Z", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "0f585d64-4815-4b72-92c5-97dae191fa4a", + "type": "application", + "name": "maven-test-project", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:composer/pear/log@1.13.1", + "type": "library", + "name": "pear/log", + "version": "1.13.1", + "purl": "pkg:composer/pear/log@1.13.1" + }, + { + "bom-ref": "pkg:composer/pear/pear_exception@v1.0.0", + "type": "library", + "name": "pear/pear_exception", + "version": "v1.0.0", + "purl": "pkg:composer/pear/pear_exception@v1.0.0" + }, + { + "bom-ref": "100925ff-7c0a-470f-a725-8fb973b40e7b", + "type": "application", + "name": "app/composer/composer.lock" + } + ], + "dependencies": [ + { + "ref": "100925ff-7c0a-470f-a725-8fb973b40e7b", + "dependsOn": [ + "pkg:composer/pear/log@1.13.1", + "pkg:composer/pear/pear_exception@v1.0.0" + ] + }, + { + "ref": "pkg:composer/pear/log@1.13.1" + }, + { + "ref": "pkg:composer/pear/pear_exception@v1.0.0" + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/testdata/sad/invalid-serial.json b/pkg/sbom/cyclonedx/testdata/sad/invalid-serial.json new file mode 100644 index 000000000000..4edeb3f3ec13 --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/sad/invalid-serial.json @@ -0,0 +1,6 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": 1000000, + "version": 1 +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/unmarshal.go b/pkg/sbom/cyclonedx/unmarshal.go new file mode 100644 index 000000000000..8821fe8b111a --- /dev/null +++ b/pkg/sbom/cyclonedx/unmarshal.go @@ -0,0 +1,264 @@ +package cyclonedx + +import ( + "bytes" + "errors" + "io" + "strings" + + cdx "github.com/CycloneDX/cyclonedx-go" + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "go.uber.org/zap" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/sbom/core" +) + +var ( + ErrUnsupportedType = errors.New("unsupported type") +) + +type BOM struct { + *core.BOM +} + +func DecodeJSON(r io.Reader) (*cdx.BOM, error) { + bom := cdx.NewBOM() + decoder := cdx.NewBOMDecoder(r, cdx.BOMFileFormatJSON) + if err := decoder.Decode(bom); err != nil { + return nil, xerrors.Errorf("CycloneDX decode error: %w", err) + } + return bom, nil +} + +func (b *BOM) UnmarshalJSON(data []byte) error { + log.Logger.Debug("Unmarshalling CycloneDX JSON...") + if b.BOM == nil { + b.BOM = core.NewBOM(core.Options{GenerateBOMRef: true}) + } + + cdxBOM, err := DecodeJSON(bytes.NewReader(data)) + if err != nil { + return xerrors.Errorf("CycloneDX decode error: %w", err) + } + + if !IsTrivySBOM(cdxBOM) { + log.Logger.Warnf("Third-party SBOM may lead to inaccurate vulnerability detection") + log.Logger.Warnf("Recommend using Trivy to generate SBOMs") + } + + if err = b.parseBOM(cdxBOM); err != nil { + return xerrors.Errorf("failed to parse sbom: %w", err) + } + + // Store the original metadata + b.BOM.SerialNumber = cdxBOM.SerialNumber + b.BOM.Version = cdxBOM.Version + + return nil +} + +func (b *BOM) parseBOM(bom *cdx.BOM) error { + // Convert all CycloneDX components into Trivy components + components := b.parseComponents(bom.Components) + + // Convert the metadata component into Trivy component + mComponent, err := b.parseMetadataComponent(bom) + if err != nil { + return xerrors.Errorf("failed to parse root component: %w", err) + } else if mComponent != nil { + components[mComponent.PkgID.BOMRef] = mComponent + } + + // Parse dependencies and build relationships + for _, dep := range lo.FromPtr(bom.Dependencies) { + ref, ok := components[dep.Ref] + if !ok { + continue + } + for _, depRef := range lo.FromPtr(dep.Dependencies) { + dependency, ok := components[depRef] + if !ok { + continue + } + b.BOM.AddRelationship(ref, dependency, core.RelationshipDependsOn) + } + } + return nil +} + +func (b *BOM) parseMetadataComponent(bom *cdx.BOM) (*core.Component, error) { + if bom.Metadata == nil || bom.Metadata.Component == nil { + return nil, nil + } + root, err := b.parseComponent(*bom.Metadata.Component) + if err != nil { + return nil, xerrors.Errorf("failed to parse metadata component: %w", err) + } + root.Root = true + b.BOM.AddComponent(root) + return root, nil +} + +func (b *BOM) parseComponents(cdxComponents *[]cdx.Component) map[string]*core.Component { + components := make(map[string]*core.Component) + for _, component := range lo.FromPtr(cdxComponents) { + c, err := b.parseComponent(component) + if errors.Is(err, ErrUnsupportedType) { + log.Logger.Infow("Skipping the component with the unsupported type", + zap.String("bom-ref", component.BOMRef), zap.String("type", string(component.Type))) + continue + } else if err != nil { + log.Logger.Warnw("Failed to parse component: %s", zap.Error(err)) + continue + } + + b.BOM.AddComponent(c) + components[component.BOMRef] = c + } + return components +} + +func (b *BOM) parseComponent(c cdx.Component) (*core.Component, error) { + componentType, err := b.unmarshalType(c.Type) + if err != nil { + return nil, xerrors.Errorf("failed to unmarshal component type: %w", err) + } + + // Parse PURL + var purl packageurl.PackageURL + if c.PackageURL != "" { + purl, err = packageurl.FromString(c.PackageURL) + if err != nil { + return nil, xerrors.Errorf("failed to parse PURL: %w", err) + } + } + + component := &core.Component{ + Type: componentType, + Name: c.Name, + Group: c.Group, + Version: c.Version, + Licenses: b.unmarshalLicenses(c.Licenses), + Files: []core.File{ + { + Digests: b.unmarshalHashes(c.Hashes), + }, + }, + PkgID: core.PkgID{ + PURL: &purl, + BOMRef: c.BOMRef, + }, + Supplier: b.unmarshalSupplier(c.Supplier), + Properties: b.unmarshalProperties(c.Properties), + } + + return component, nil +} + +func (b *BOM) unmarshalType(t cdx.ComponentType) (core.ComponentType, error) { + var ctype core.ComponentType + switch t { + case cdx.ComponentTypeContainer: + ctype = core.TypeContainerImage + case cdx.ComponentTypeApplication: + ctype = core.TypeApplication + case cdx.ComponentTypeLibrary: + ctype = core.TypeLibrary + case cdx.ComponentTypeOS: + ctype = core.TypeOS + case cdx.ComponentTypePlatform: + ctype = core.TypePlatform + default: + return "", ErrUnsupportedType + } + return ctype, nil +} + +// parsePackageLicenses checks all supported license fields and returns a list of licenses. +// https://cyclonedx.org/docs/1.5/json/#components_items_licenses +func (b *BOM) unmarshalLicenses(l *cdx.Licenses) []string { + var licenses []string + for _, license := range lo.FromPtr(l) { + if license.License != nil { + // Trivy uses `Name` field to marshal licenses + if license.License.Name != "" { + licenses = append(licenses, license.License.Name) + continue + } + + if license.License.ID != "" { + licenses = append(licenses, license.License.ID) + continue + } + } + + if license.Expression != "" { + licenses = append(licenses, license.Expression) + continue + } + + } + return licenses +} + +func (b *BOM) unmarshalHashes(hashes *[]cdx.Hash) []digest.Digest { + var digests []digest.Digest + for _, h := range lo.FromPtr(hashes) { + var alg digest.Algorithm + switch h.Algorithm { + case cdx.HashAlgoSHA1: + alg = digest.SHA1 + case cdx.HashAlgoSHA256: + alg = digest.SHA256 + case cdx.HashAlgoMD5: + alg = digest.MD5 + default: + log.Logger.Warnf("Unsupported hash algorithm: %s", h.Algorithm) + } + digests = append(digests, digest.NewDigestFromString(alg, h.Value)) + } + return digests +} + +func (b *BOM) unmarshalSupplier(supplier *cdx.OrganizationalEntity) string { + if supplier == nil { + return "" + } + return supplier.Name +} + +func (b *BOM) unmarshalProperties(properties *[]cdx.Property) []core.Property { + var props []core.Property + for _, p := range lo.FromPtr(properties) { + props = append(props, core.Property{ + Name: strings.TrimPrefix(p.Name, Namespace), + Value: p.Value, + }) + } + return props +} + +func IsTrivySBOM(c *cdx.BOM) bool { + if c == nil || c.Metadata == nil || c.Metadata.Tools == nil { + return false + } + + for _, component := range lo.FromPtr(c.Metadata.Tools.Components) { + if component.Group == ToolVendor && component.Name == ToolName { + return true + } + } + + // Metadata.Tools array is deprecated (as of CycloneDX v1.5). We check this field for backward compatibility. + // cf. https://github.com/CycloneDX/cyclonedx-go/blob/b9654ae9b4705645152d20eb9872b5f3d73eac49/cyclonedx.go#L988 + for _, tool := range lo.FromPtr(c.Metadata.Tools.Tools) { + if tool.Vendor == ToolVendor && tool.Name == ToolName { + return true + } + } + return false +} diff --git a/pkg/sbom/cyclonedx/unmarshal_test.go b/pkg/sbom/cyclonedx/unmarshal_test.go new file mode 100644 index 000000000000..6ffd9ce89ad9 --- /dev/null +++ b/pkg/sbom/cyclonedx/unmarshal_test.go @@ -0,0 +1,767 @@ +package cyclonedx_test + +import ( + "encoding/json" + "github.com/aquasecurity/trivy/pkg/purl" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/package-url/packageurl-go" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" +) + +func TestUnmarshaler_Unmarshal(t *testing.T) { + tests := []struct { + name string + inputFile string + want types.SBOM + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/happy/bom.json", + want: types.SBOM{ + Metadata: types.Metadata{ + ImageID: "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5", + DiffIDs: []string{ + "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + RepoTags: []string{ + "maven-test-project:latest", + }, + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.16.0", + }, + }, + Packages: []ftypes.PackageInfo{ + { + Packages: ftypes.Packages{ + { + ID: "musl@1.2.3-r0", + Name: "musl", + Version: "1.2.3-r0", + SrcName: "musl", + SrcVersion: "1.2.3-r0", + Licenses: []string{"MIT"}, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "musl", + Version: "1.2.3-r0", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.16.0", + }, + }, + }, + BOMRef: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + }, + Layer: ftypes.Layer{ + DiffID: "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + }, + }, + }, + }, + Applications: []ftypes.Application{ + { + Type: ftypes.Composer, + FilePath: "app/composer/composer.lock", + Libraries: ftypes.Packages{ + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "log", + Version: "1.13.1", + }, + BOMRef: "pkg:composer/pear/log@1.13.1", + }, + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + { + ID: "pear/pear_exception@v1.0.0", + Name: "pear/pear_exception", + Version: "v1.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "pear_exception", + Version: "v1.0.0", + }, + BOMRef: "pkg:composer/pear/pear_exception@v1.0.0", + }, + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + }, + }, + { + Type: ftypes.GoBinary, + FilePath: "app/gobinary/gobinary", + Libraries: ftypes.Packages{ + { + ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + Name: "github.com/package-url/packageurl-go", + Version: "v0.1.1-0.20220203205134-d70459300c8a", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/package-url", + Name: "packageurl-go", + Version: "v0.1.1-0.20220203205134-d70459300c8a", + }, + BOMRef: "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + }, + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + }, + }, + { + Type: ftypes.Gradle, + FilePath: "app/gradle/target/gradle.lockfile", + Libraries: ftypes.Packages{ + { + ID: "com.example:example:0.0.1", + Name: "com.example:example", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.example", + Name: "example", + Version: "0.0.1", + }, + BOMRef: "pkg:maven/com.example/example@0.0.1", + }, + Version: "0.0.1", + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + }, + }, + { + Type: ftypes.Jar, + Libraries: ftypes.Packages{ + { + ID: "org.codehaus.mojo:child-project:1.0", + Name: "org.codehaus.mojo:child-project", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.codehaus.mojo", + Name: "child-project", + Version: "1.0", + }, + BOMRef: "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", + }, + Version: "1.0", + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + FilePath: "app/maven/target/child-project-1.0.jar", + }, + }, + }, + { + Type: ftypes.NodePkg, + FilePath: "", + Libraries: ftypes.Packages{ + { + ID: "@example/bootstrap@5.0.2", + Name: "@example/bootstrap", + Version: "5.0.2", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Namespace: "@example", + Name: "bootstrap", + Version: "5.0.2", + }, + BOMRef: "pkg:npm/@example/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", + }, + Licenses: []string{"MIT"}, + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + FilePath: "app/app/package.json", + }, + }, + }, + }, + }, + }, + { + name: "happy path KBOM", + inputFile: "testdata/happy/kbom.json", + want: types.SBOM{ + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "ubuntu", + Name: "22.04.2", + }, + }, + Applications: []ftypes.Application{ + { + Type: ftypes.GoBinary, + Libraries: ftypes.Packages{ + { + ID: "docker@v24.0.4", + Name: "docker", + Version: "24.0.4", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Name: "docker", + Version: "24.0.4", + }, + BOMRef: "pkg:golang/docker@24.0.4", + }, + }, + }, + }, + { + Type: ftypes.K8sUpstream, + Libraries: ftypes.Packages{ + { + ID: "k8s.io/apiserver@1.27.4", + Name: "k8s.io/apiserver", + Version: "1.27.4", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "k8s.io/apiserver", + Version: "1.27.4", + }, + BOMRef: "pkg:k8s/k8s.io%2Fapiserver@1.27.4", + }, + }, + { + ID: "k8s.io/controller-manager@1.27.4", + Name: "k8s.io/controller-manager", + Version: "1.27.4", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "k8s.io/controller-manager", + Version: "1.27.4", + }, + BOMRef: "pkg:k8s/k8s.io%2Fcontroller-manager@1.27.4", + }, + }, + { + ID: "k8s.io/kube-proxy@1.27.4", + Name: "k8s.io/kube-proxy", + Version: "1.27.4", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "k8s.io/kube-proxy", + Version: "1.27.4", + }, + BOMRef: "pkg:k8s/k8s.io%2Fkube-proxy@1.27.4", + }, + }, + { + ID: "k8s.io/kube-scheduler@1.27.4", + Name: "k8s.io/kube-scheduler", + Version: "1.27.4", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "k8s.io/kube-scheduler", + Version: "1.27.4", + }, + BOMRef: "pkg:k8s/k8s.io%2Fkube-scheduler@1.27.4", + }, + }, + { + ID: "k8s.io/kubelet@1.27.4", + Name: "k8s.io/kubelet", + Version: "1.27.4", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "k8s.io/kubelet", + Version: "1.27.4", + }, + BOMRef: "pkg:k8s/k8s.io%2Fkubelet@1.27.4", + }, + }, + { + ID: "k8s.io/kubernetes@1.27.4", + Name: "k8s.io/kubernetes", + Version: "1.27.4", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "k8s.io/kubernetes", + Version: "1.27.4", + }, + BOMRef: "pkg:k8s/k8s.io%2Fkubernetes@1.27.4", + }, + DependsOn: []string{ + "k8s.io/apiserver@1.27.4", + "k8s.io/controller-manager@1.27.4", + "k8s.io/kube-proxy@1.27.4", + "k8s.io/kube-scheduler@1.27.4", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with infinity loop", + inputFile: "testdata/happy/infinite-loop-bom.json", + want: types.SBOM{ + Metadata: types.Metadata{ + ImageID: "sha256:08d22c0ceb150ddeb2237c5fa3129c0183f3cc6f5eeb2e7aa4016da3ad02140a", + DiffIDs: []string{ + "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466", + }, + RepoTags: []string{ + "ubuntu:latest", + }, + RepoDigests: []string{ + "ubuntu@sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21", + }, + OS: &ftypes.OS{ + Family: "ubuntu", + Name: "22.04", + }, + }, + Packages: []ftypes.PackageInfo{ + { + Packages: ftypes.Packages{ + { + ID: "libc6@2.35-0ubuntu3.1", + Name: "libc6", + Version: "2.35-0ubuntu3.1", + SrcName: "glibc", + SrcVersion: "2.35", + SrcRelease: "0ubuntu3.1", + Licenses: []string{ + "LGPL-2.1", + "GPL-2.0", + "GFDL-1.3", + }, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "ubuntu", + Name: "libc6", + Version: "2.35-0ubuntu3.1", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "ubuntu-22.04", + }, + }, + }, + BOMRef: "pkg:deb/ubuntu/libc6@2.35-0ubuntu3.1?distro=ubuntu-22.04", + }, + DependsOn: []string{ + "libcrypt1@1:4.4.27-1", + }, + Layer: ftypes.Layer{ + Digest: "sha256:74ac377868f863e123f24c409f79709f7563fa464557c36a09cf6f85c8b92b7f", + DiffID: "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466", + }, + }, + { + ID: "libcrypt1@1:4.4.27-1", + Name: "libcrypt1", + Version: "4.4.27-1", + Epoch: 1, + SrcName: "libxcrypt", + SrcVersion: "4.4.27", + SrcRelease: "1", + SrcEpoch: 1, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "ubuntu", + Name: "libcrypt1", + Version: "4.4.27-1", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "ubuntu-22.04", + }, + { + Key: "epoch", + Value: "1", + }, + }, + }, + BOMRef: "pkg:deb/ubuntu/libcrypt1@4.4.27-1?epoch=1&distro=ubuntu-22.04", + }, + DependsOn: []string{ + "libc6@2.35-0ubuntu3.1", + }, + Layer: ftypes.Layer{ + Digest: "sha256:74ac377868f863e123f24c409f79709f7563fa464557c36a09cf6f85c8b92b7f", + DiffID: "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path for third party sbom", + inputFile: "testdata/happy/third-party-bom.json", + want: types.SBOM{ + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.16.0", + }, + }, + Packages: []ftypes.PackageInfo{ + { + Packages: ftypes.Packages{ + { + ID: "musl@1.2.3-r0", + Name: "musl", + Version: "1.2.3-r0", + SrcName: "musl", + SrcVersion: "1.2.3-r0", + Licenses: []string{"MIT"}, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "musl", + Version: "1.2.3-r0", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.16.0", + }, + }, + }, + BOMRef: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + }, + }, + }, + }, + }, + Applications: []ftypes.Application{ + { + Type: "composer", + FilePath: "", + Libraries: ftypes.Packages{ + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "log", + Version: "1.13.1", + }, + BOMRef: "pkg:composer/pear/log@1.13.1", + }, + Licenses: []string{"MIT"}, + }, + { + ID: "pear/pear_exception@v1.0.0", + Name: "pear/pear_exception", + Version: "v1.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "pear_exception", + Version: "v1.0.0", + }, + BOMRef: "pkg:composer/pear/pear_exception@v1.0.0", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path for third party sbom, no operation-system component", + inputFile: "testdata/happy/third-party-bom-no-os.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: "composer", + FilePath: "", + Libraries: ftypes.Packages{ + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "log", + Version: "1.13.1", + }, + BOMRef: "pkg:composer/pear/log@1.13.1", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path for unrelated bom", + inputFile: "testdata/happy/unrelated-bom.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: "composer", + FilePath: "", + Libraries: ftypes.Packages{ + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "log", + Version: "1.13.1", + }, + BOMRef: "pkg:composer/pear/log@1.13.1", + }, + }, + { + ID: "pear/pear_exception@v1.0.0", + Name: "pear/pear_exception", + Version: "v1.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "pear_exception", + Version: "v1.0.0", + }, + BOMRef: "pkg:composer/pear/pear_exception@v1.0.0", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path for independent library bom", + inputFile: "testdata/happy/independent-library-bom.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: "composer", + FilePath: "", + Libraries: ftypes.Packages{ + { + ID: "pear/core@1.13.1", + Name: "pear/core", + Version: "1.13.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "core", + Version: "1.13.1", + }, + BOMRef: "pkg:composer/pear/core@1.13.1", + }, + DependsOn: []string{ + "pear/log@1.13.1", + "pear/pear_exception@v1.0.0", + }, + }, + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "log", + Version: "1.13.1", + }, + BOMRef: "pkg:composer/pear/log@1.13.1", + }, + }, + { + ID: "pear/pear_exception@v1.0.0", + Name: "pear/pear_exception", + Version: "v1.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "pear_exception", + Version: "v1.0.0", + }, + BOMRef: "pkg:composer/pear/pear_exception@v1.0.0", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path for jar where name is GroupID and ArtifactID", + inputFile: "testdata/happy/group-in-name.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: "jar", + Libraries: ftypes.Packages{ + { + ID: "org.springframework:spring-web:5.3.22", + Name: "org.springframework:spring-web", + Version: "5.3.22", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-web", + Version: "5.3.22", + }, + BOMRef: "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + }, + FilePath: "spring-web-5.3.22.jar", + }, + }, + }, + }, + }, + }, + { + name: "happy path only os component", + inputFile: "testdata/happy/os-only-bom.json", + want: types.SBOM{ + Metadata: types.Metadata{ + ImageID: "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5", + DiffIDs: []string{ + "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + RepoTags: []string{ + "maven-test-project:latest", + }, + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.16.0", + }, + }, + }, + }, + { + name: "happy path empty component", + inputFile: "testdata/happy/empty-bom.json", + want: types.SBOM{ + Metadata: types.Metadata{ + ImageID: "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5", + DiffIDs: []string{ + "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + RepoTags: []string{ + "maven-test-project:latest", + }, + }, + }, + }, + { + name: "happy path empty metadata component", + inputFile: "testdata/happy/empty-metadata-component-bom.json", + want: types.SBOM{}, + }, + { + name: "invalid purl", + inputFile: "testdata/happy/invalid-purl.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: ftypes.Composer, + Libraries: ftypes.Packages{ + { + ID: "pear/core@1.13.1", + Name: "pear/core", + Version: "1.13.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "core", + Version: "1.13.1", + }, + BOMRef: "pkg:composer/pear/core@1.13.1", + }, + }, + }, + }, + }, + }, + }, + { + name: "invalid serial", + inputFile: "testdata/sad/invalid-serial.json", + wantErr: "CycloneDX decode error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + var cdx cyclonedx.BOM + err = json.NewDecoder(f).Decode(&cdx) + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + + var got types.SBOM + err = sbomio.NewDecoder(cdx.BOM).Decode(&got) + require.NoError(t, err) + + got.BOM = nil + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go new file mode 100644 index 000000000000..af61f41b5a8c --- /dev/null +++ b/pkg/sbom/io/decode.go @@ -0,0 +1,380 @@ +package io + +import ( + "errors" + "slices" + "sort" + "strconv" + + debver "github.com/knqyf263/go-deb-version" + rpmver "github.com/knqyf263/go-rpm-version" + "github.com/package-url/packageurl-go" + "go.uber.org/zap" + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" +) + +var ( + ErrPURLEmpty = errors.New("purl empty error") + ErrUnsupportedType = errors.New("unsupported type") +) + +type Decoder struct { + bom *core.BOM + + osID uuid.UUID + pkgs map[uuid.UUID]*ftypes.Package + apps map[uuid.UUID]*ftypes.Application +} + +func NewDecoder(bom *core.BOM) *Decoder { + return &Decoder{ + bom: bom, + pkgs: make(map[uuid.UUID]*ftypes.Package), + apps: make(map[uuid.UUID]*ftypes.Application), + } +} + +func (m *Decoder) Decode(sbom *types.SBOM) error { + // Parse the root component + if err := m.decodeRoot(sbom); err != nil { + return xerrors.Errorf("failed to decode root component: %w", err) + } + + // Parse all components + if err := m.decodeComponents(sbom); err != nil { + return xerrors.Errorf("failed to decode components: %w", err) + } + + // Build dependency graph between packages + m.buildDependencyGraph() + + // Add OS packages + m.addOSPkgs(sbom) + + // Add language-specific packages + m.addLangPkgs(sbom) + + // Add remaining packages + if err := m.addOrphanPkgs(sbom); err != nil { + return xerrors.Errorf("failed to aggregate packages: %w", err) + } + + sort.Slice(sbom.Applications, func(i, j int) bool { + if sbom.Applications[i].Type != sbom.Applications[j].Type { + return sbom.Applications[i].Type < sbom.Applications[j].Type + } + return sbom.Applications[i].FilePath < sbom.Applications[j].FilePath + }) + + sbom.BOM = m.bom + + return nil +} + +func (m *Decoder) decodeRoot(s *types.SBOM) error { + root := m.bom.Root() + if root == nil { + return nil // No root found + } + + var err error + for _, prop := range root.Properties { + switch prop.Name { + case core.PropertyImageID: + s.Metadata.ImageID = prop.Value + case core.PropertySize: + if s.Metadata.Size, err = strconv.ParseInt(prop.Value, 10, 64); err != nil { + return xerrors.Errorf("failed to convert size: %w", err) + } + case core.PropertyRepoDigest: + s.Metadata.RepoDigests = append(s.Metadata.RepoDigests, prop.Value) + case core.PropertyDiffID: + s.Metadata.DiffIDs = append(s.Metadata.DiffIDs, prop.Value) + case core.PropertyRepoTag: + s.Metadata.RepoTags = append(s.Metadata.RepoTags, prop.Value) + } + } + return nil +} + +func (m *Decoder) decodeComponents(sbom *types.SBOM) error { + for id, c := range m.bom.Components() { + switch c.Type { + case core.TypeOS: + if m.osID != uuid.Nil { + return xerrors.New("multiple OS components are not supported") + } + m.osID = id + sbom.Metadata.OS = &ftypes.OS{ + Family: ftypes.OSType(c.Name), + Name: c.Version, + } + continue + case core.TypeApplication: + if app := m.decodeApplication(c); app.Type != "" { + m.apps[id] = app + continue + } + } + + // Third-party SBOMs may contain packages in types other than "Library" + if c.Type == core.TypeLibrary || c.PkgID.PURL != nil { + pkg, err := m.decodeLibrary(c) + if errors.Is(err, ErrUnsupportedType) || errors.Is(err, ErrPURLEmpty) { + continue + } else if err != nil { + return xerrors.Errorf("failed to decode library: %w", err) + } + m.pkgs[id] = pkg + } + } + + return nil +} + +// buildDependencyGraph builds a dependency graph between packages +func (m *Decoder) buildDependencyGraph() { + for id, rels := range m.bom.Relationships() { + pkg, ok := m.pkgs[id] + if !ok { + continue + } + for _, rel := range rels { + dep, ok := m.pkgs[rel.Dependency] + if !ok { + continue + } + pkg.DependsOn = append(pkg.DependsOn, dep.ID) + } + continue + } +} + +func (m *Decoder) decodeApplication(c *core.Component) *ftypes.Application { + var app ftypes.Application + for _, prop := range c.Properties { + if prop.Name == core.PropertyType { + app.Type = ftypes.LangType(prop.Value) + } + } + + // Aggregation Types use the name of the language (e.g. `Java`, `Python`, etc.) as the component name. + // Other language files use the file path as their name. + if !slices.Contains(ftypes.AggregatingTypes, app.Type) { + app.FilePath = c.Name + } + return &app +} + +func (m *Decoder) decodeLibrary(c *core.Component) (*ftypes.Package, error) { + p := (*purl.PackageURL)(c.PkgID.PURL) + if p == nil { + log.Logger.Debugw("Skipping a component without PURL", + zap.String("name", c.Name), zap.String("version", c.Version)) + return nil, ErrPURLEmpty + } + + pkg := p.Package() + if p.Class() == types.ClassUnknown { + log.Logger.Debugw("Skipping a component with an unsupported type", + zap.String("name", c.Name), zap.String("version", c.Version), zap.String("type", p.Type)) + return nil, ErrUnsupportedType + } + pkg.Name = m.pkgName(pkg, c) + pkg.ID = dependency.ID(p.LangType(), pkg.Name, p.Version) // Re-generate ID with the updated name + + var err error + for _, prop := range c.Properties { + switch prop.Name { + case core.PropertyPkgID: + pkg.ID = prop.Value + case core.PropertyFilePath: + pkg.FilePath = prop.Value + case core.PropertySrcName: + pkg.SrcName = prop.Value + case core.PropertySrcVersion: + pkg.SrcVersion = prop.Value + case core.PropertySrcRelease: + pkg.SrcRelease = prop.Value + case core.PropertySrcEpoch: + if pkg.SrcEpoch, err = strconv.Atoi(prop.Value); err != nil { + return nil, xerrors.Errorf("invalid src epoch: %w", err) + } + case core.PropertyModularitylabel: + pkg.Modularitylabel = prop.Value + case core.PropertyLayerDigest: + pkg.Layer.Digest = prop.Value + case core.PropertyLayerDiffID: + pkg.Layer.DiffID = prop.Value + } + } + + pkg.Identifier.BOMRef = c.PkgID.BOMRef + pkg.Licenses = c.Licenses + + for _, f := range c.Files { + if f.Path != "" && pkg.FilePath == "" { + pkg.FilePath = f.Path + } + // An empty path represents a package digest + if f.Path == "" && len(f.Digests) > 0 { + pkg.Digest = f.Digests[0] + } + } + + if p.Class() == types.ClassOSPkg { + m.fillSrcPkg(c, pkg) + } + + return pkg, nil +} + +// pkgName returns the package name. +// PURL loses case-sensitivity (e.g. Go, Npm, PyPI), so we have to use an original package name. +func (m *Decoder) pkgName(pkg *ftypes.Package, c *core.Component) string { + p := c.PkgID.PURL + + // A name from PURL takes precedence for CocoaPods since it has subpath. + if c.PkgID.PURL.Type == packageurl.TypeCocoapods { + return pkg.Name + } + + if c.Group != "" { + if p.Type == packageurl.TypeMaven || p.Type == packageurl.TypeGradle { + return c.Group + ":" + c.Name + } + return c.Group + "/" + c.Name + } + return c.Name +} + +func (m *Decoder) fillSrcPkg(c *core.Component, pkg *ftypes.Package) { + if c.SrcName != "" && pkg.SrcName == "" { + pkg.SrcName = c.SrcName + } + m.parseSrcVersion(pkg, c.SrcVersion) + + // Fill source package information for components in third-party SBOMs . + if pkg.SrcName == "" { + pkg.SrcName = pkg.Name + } + if pkg.SrcVersion == "" { + pkg.SrcVersion = pkg.Version + } + if pkg.SrcRelease == "" { + pkg.SrcRelease = pkg.Release + } + if pkg.SrcEpoch == 0 { + pkg.SrcEpoch = pkg.Epoch + } +} + +// parseSrcVersion parses the version of the source package. +func (m *Decoder) parseSrcVersion(pkg *ftypes.Package, ver string) { + if ver == "" { + return + } + switch pkg.Identifier.PURL.Type { + case packageurl.TypeRPM: + v := rpmver.NewVersion(ver) + pkg.SrcEpoch = v.Epoch() + pkg.SrcVersion = v.Version() + pkg.SrcRelease = v.Release() + case packageurl.TypeDebian: + v, err := debver.NewVersion(ver) + if err != nil { + log.Logger.Debugw("Failed to parse Debian version", zap.Error(err)) + return + } + pkg.SrcEpoch = v.Epoch() + pkg.SrcVersion = v.Version() + pkg.SrcRelease = v.Revision() + } +} + +// addOSPkgs traverses relationships and adds OS packages +func (m *Decoder) addOSPkgs(sbom *types.SBOM) { + var pkgs []ftypes.Package + for _, rel := range m.bom.Relationships()[m.osID] { + pkg, ok := m.pkgs[rel.Dependency] + if !ok { + continue + } + pkgs = append(pkgs, *pkg) + delete(m.pkgs, rel.Dependency) // Delete the added package + } + if len(pkgs) == 0 { + return + } + sbom.Packages = []ftypes.PackageInfo{{Packages: pkgs}} +} + +// addLangPkgs traverses relationships and adds language-specific packages +func (m *Decoder) addLangPkgs(sbom *types.SBOM) { + for id, app := range m.apps { + for _, rel := range m.bom.Relationships()[id] { + pkg, ok := m.pkgs[rel.Dependency] + if !ok { + continue + } + app.Libraries = append(app.Libraries, *pkg) + delete(m.pkgs, rel.Dependency) // Delete the added package + } + sbom.Applications = append(sbom.Applications, *app) + } +} + +// addOrphanPkgs adds orphan packages. +// Orphan packages are packages that are not related to any components. +func (m *Decoder) addOrphanPkgs(sbom *types.SBOM) error { + osPkgMap := make(map[string]ftypes.Packages) + langPkgMap := make(map[ftypes.LangType]ftypes.Packages) + for _, pkg := range m.pkgs { + p := (*purl.PackageURL)(pkg.Identifier.PURL) + switch p.Class() { + case types.ClassOSPkg: + osPkgMap[p.Type] = append(osPkgMap[p.Type], *pkg) + case types.ClassLangPkg: + langType := p.LangType() + langPkgMap[langType] = append(langPkgMap[langType], *pkg) + } + } + + if len(osPkgMap) > 1 { + return xerrors.Errorf("multiple types of OS packages in SBOM are not supported (%q)", maps.Keys(osPkgMap)) + } + + // Add OS packages only when OS is detected. + for _, pkgs := range osPkgMap { + if sbom.Metadata.OS == nil || !sbom.Metadata.OS.Detected() { + log.Logger.Warn("Ignore the OS package as no OS is detected.") + break + } + + // TODO: mismatch between the OS and the packages should be rejected. + // e.g. OS: debian, Packages: rpm + sort.Sort(pkgs) + sbom.Packages = append(sbom.Packages, ftypes.PackageInfo{Packages: pkgs}) + + break // Just take the first element + } + + // Add language-specific packages + for pkgType, pkgs := range langPkgMap { + sort.Sort(pkgs) + sbom.Applications = append(sbom.Applications, ftypes.Application{ + Type: pkgType, + Libraries: pkgs, + }) + } + return nil +} diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go new file mode 100644 index 000000000000..5bb181992975 --- /dev/null +++ b/pkg/sbom/io/encode.go @@ -0,0 +1,363 @@ +package io + +import ( + "fmt" + "slices" + "strconv" + + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/digest" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +type Encoder struct { + bom *core.BOM + opts core.Options +} + +func NewEncoder(opts core.Options) *Encoder { + return &Encoder{opts: opts} +} + +func (e *Encoder) Encode(report types.Report) (*core.BOM, error) { + // Metadata component + root, err := e.rootComponent(report) + if err != nil { + return nil, xerrors.Errorf("failed to create root component: %w", err) + } + + e.bom = core.NewBOM(e.opts) + e.bom.AddComponent(root) + + for _, result := range report.Results { + e.encodeResult(root, report.Metadata, result) + } + + // Components that do not have their own dependencies MUST be declared as empty elements within the graph. + if _, ok := e.bom.Relationships()[root.ID()]; !ok { + e.bom.AddRelationship(root, nil, "") + } + return e.bom, nil +} + +func (e *Encoder) rootComponent(r types.Report) (*core.Component, error) { + root := &core.Component{ + Root: true, + Name: r.ArtifactName, + } + + props := []core.Property{ + { + Name: core.PropertySchemaVersion, + Value: strconv.Itoa(r.SchemaVersion), + }, + } + + switch r.ArtifactType { + case ftypes.ArtifactContainerImage: + root.Type = core.TypeContainerImage + props = append(props, core.Property{ + Name: core.PropertyImageID, + Value: r.Metadata.ImageID, + }) + + p, err := purl.New(purl.TypeOCI, r.Metadata, ftypes.Package{}) + if err != nil { + return nil, xerrors.Errorf("failed to new package url for oci: %w", err) + } + if p != nil { + root.PkgID.PURL = p.Unwrap() + } + + case ftypes.ArtifactVM: + root.Type = core.TypeVM + case ftypes.ArtifactFilesystem: + root.Type = core.TypeFilesystem + case ftypes.ArtifactRepository: + root.Type = core.TypeRepository + case ftypes.ArtifactCycloneDX: + return r.BOM.Root(), nil + } + + if r.Metadata.Size != 0 { + props = append(props, core.Property{ + Name: core.PropertySize, + Value: strconv.FormatInt(r.Metadata.Size, 10), + }) + } + + for _, d := range r.Metadata.RepoDigests { + props = append(props, core.Property{ + Name: core.PropertyRepoDigest, + Value: d, + }) + } + + for _, id := range r.Metadata.DiffIDs { + props = append(props, core.Property{ + Name: core.PropertyDiffID, + Value: id, + }) + } + + for _, tag := range r.Metadata.RepoTags { + props = append(props, core.Property{ + Name: core.PropertyRepoTag, + Value: tag, + }) + } + + root.Properties = filterProperties(props) + + return root, nil +} + +func (e *Encoder) encodeResult(root *core.Component, metadata types.Metadata, result types.Result) { + if slices.Contains(ftypes.AggregatingTypes, result.Type) { + // If a package is language-specific package that isn't associated with a lock file, + // it will be a dependency of a component under "metadata". + // e.g. + // Container component (alpine:3.15) ----------------------- #1 + // -> Library component (npm package, express-4.17.3) ---- #2 + // -> Library component (python package, django-4.0.2) --- #2 + // -> etc. + // ref. https://cyclonedx.org/use-cases/#inventory + + // Dependency graph from #1 to #2 + e.encodePackages(root, result) + } else if result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg { + // If a package is OS package, it will be a dependency of "Operating System" component. + // e.g. + // Container component (alpine:3.15) --------------------- #1 + // -> Operating System Component (Alpine Linux 3.15) --- #2 + // -> Library component (bash-4.12) ------------------ #3 + // -> Library component (vim-8.2) ------------------ #3 + // -> etc. + // + // Else if a package is language-specific package associated with a lock file, + // it will be a dependency of "Application" component. + // e.g. + // Container component (alpine:3.15) ------------------------ #1 + // -> Application component (/app/package-lock.json) ------ #2 + // -> Library component (npm package, express-4.17.3) --- #3 + // -> Library component (npm package, lodash-4.17.21) --- #3 + // -> etc. + + // #2 + appComponent := e.resultComponent(root, result, metadata.OS) + + // #3 + e.encodePackages(appComponent, result) + } +} + +func (e *Encoder) encodePackages(parent *core.Component, result types.Result) { + // Get dependency parents first + parents := ftypes.Packages(result.Packages).ParentDeps() + + // Group vulnerabilities by package ID + vulns := make(map[string][]core.Vulnerability) + for _, vuln := range result.Vulnerabilities { + v := e.vulnerability(vuln) + vulns[v.PkgID] = append(vulns[v.PkgID], v) + } + + // Convert packages into components and add them to the BOM + components := make(map[string]*core.Component, len(result.Packages)) + for i, pkg := range result.Packages { + pkgID := lo.Ternary(pkg.ID == "", fmt.Sprintf("%s@%s", pkg.Name, pkg.Version), pkg.ID) + result.Packages[i].ID = pkgID + + // Convert packages to components + c := e.component(result, pkg) + components[pkgID+pkg.FilePath] = c + + // Add a component + e.bom.AddComponent(c) + + // Add vulnerabilities + if vv := vulns[pkgID]; vv != nil { + e.bom.AddVulnerabilities(c, vv) + } + } + + // Build a dependency graph + for _, pkg := range result.Packages { + // Skip indirect dependencies + if pkg.Indirect && len(parents[pkg.ID]) != 0 { + continue + } + + directPkg := components[pkg.ID+pkg.FilePath] + e.bom.AddRelationship(parent, directPkg, core.RelationshipContains) + + for _, dep := range pkg.DependsOn { + indirectPkg, ok := components[dep] + if !ok { + continue + } + e.bom.AddRelationship(directPkg, indirectPkg, core.RelationshipDependsOn) + } + + // Components that do not have their own dependencies MUST be declared as empty elements within the graph. + // TODO: Should check if the component has actually no dependencies or the dependency graph is not supported. + if len(pkg.DependsOn) == 0 { + e.bom.AddRelationship(directPkg, nil, "") + } + } +} + +func (e *Encoder) resultComponent(root *core.Component, r types.Result, osFound *ftypes.OS) *core.Component { + component := &core.Component{ + Name: r.Target, + Properties: []core.Property{ + { + Name: core.PropertyType, + Value: string(r.Type), + }, + { + Name: core.PropertyClass, + Value: string(r.Class), + }, + }, + } + + switch r.Class { + case types.ClassOSPkg: + if osFound != nil { + component.Name = string(osFound.Family) + component.Version = osFound.Name + } + component.Type = core.TypeOS + case types.ClassLangPkg: + component.Type = core.TypeApplication + } + + e.bom.AddRelationship(root, component, core.RelationshipContains) + return component +} + +func (*Encoder) component(result types.Result, pkg ftypes.Package) *core.Component { + name := pkg.Name + version := utils.FormatVersion(pkg) + var group string + // there are cases when we can't build purl + // e.g. local Go packages + if pu := pkg.Identifier.PURL; pu != nil { + version = pu.Version + for _, q := range pu.Qualifiers { + if q.Key == "epoch" && q.Value != "0" { + version = fmt.Sprintf("%s:%s", q.Value, version) + } + } + + // Use `group` field for GroupID and `name` for ArtifactID for java files + // https://github.com/aquasecurity/trivy/issues/4675 + // Use `group` field for npm scopes + // https://github.com/aquasecurity/trivy/issues/5908 + if pu.Type == packageurl.TypeMaven || pu.Type == packageurl.TypeNPM { + name = pu.Name + group = pu.Namespace + } + } + + properties := []core.Property{ + { + Name: core.PropertyPkgID, + Value: pkg.ID, + }, + { + Name: core.PropertyPkgType, + Value: string(result.Type), + }, + { + Name: core.PropertyFilePath, + Value: pkg.FilePath, + }, + { + Name: core.PropertySrcName, + Value: pkg.SrcName, + }, + { + Name: core.PropertySrcVersion, + Value: pkg.SrcVersion, + }, + { + Name: core.PropertySrcRelease, + Value: pkg.SrcRelease, + }, + { + Name: core.PropertySrcEpoch, + Value: strconv.Itoa(pkg.SrcEpoch), + }, + { + Name: core.PropertyModularitylabel, + Value: pkg.Modularitylabel, + }, + { + Name: core.PropertyLayerDigest, + Value: pkg.Layer.Digest, + }, + { + Name: core.PropertyLayerDiffID, + Value: pkg.Layer.DiffID, + }, + } + + var files []core.File + if pkg.FilePath != "" || pkg.Digest != "" { + files = append(files, core.File{ + Path: pkg.FilePath, + Digests: lo.Ternary(pkg.Digest != "", []digest.Digest{pkg.Digest}, nil), + }) + } + + // TODO(refactor): simplify the list of conditions + var srcFile string + if result.Class == types.ClassLangPkg && !slices.Contains(ftypes.AggregatingTypes, result.Type) { + srcFile = result.Target + } + + return &core.Component{ + Type: core.TypeLibrary, + Name: name, + Group: group, + Version: version, + SrcName: pkg.SrcName, + SrcVersion: utils.FormatSrcVersion(pkg), + SrcFile: srcFile, + PkgID: core.PkgID{ + PURL: pkg.Identifier.PURL, + }, + Supplier: pkg.Maintainer, + Licenses: pkg.Licenses, + Files: files, + Properties: filterProperties(properties), + } +} + +func (*Encoder) vulnerability(vuln types.DetectedVulnerability) core.Vulnerability { + return core.Vulnerability{ + Vulnerability: vuln.Vulnerability, + ID: vuln.VulnerabilityID, + PkgID: lo.Ternary(vuln.PkgID == "", fmt.Sprintf("%s@%s", vuln.PkgName, vuln.InstalledVersion), vuln.PkgID), + PkgName: vuln.PkgName, + InstalledVersion: vuln.InstalledVersion, + FixedVersion: vuln.FixedVersion, + PrimaryURL: vuln.PrimaryURL, + DataSource: vuln.DataSource, + } +} + +func filterProperties(props []core.Property) []core.Property { + return lo.Filter(props, func(property core.Property, _ int) bool { + return !(property.Value == "" || (property.Name == core.PropertySrcEpoch && property.Value == "0")) + }) +} diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go new file mode 100644 index 000000000000..a57bddd9983d --- /dev/null +++ b/pkg/sbom/io/encode_test.go @@ -0,0 +1,340 @@ +package io_test + +import ( + dtypes "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/sbom/core" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" + "github.com/package-url/packageurl-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func TestEncoder_Encode(t *testing.T) { + tests := []struct { + name string + report types.Report + wantComponents map[uuid.UUID]*core.Component + wantRels map[uuid.UUID][]core.Relationship + wantVulns map[uuid.UUID][]core.Vulnerability + wantErr string + }{ + { + name: "container image", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "debian:12", + ArtifactType: ftypes.ArtifactContainerImage, + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: ftypes.Debian, + Name: "12", + }, + RepoTags: []string{ + "debian:latest", + "debian:12", + }, + RepoDigests: []string{ + "debian@sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + }, + }, + Results: []types.Result{ + { + Target: "debian:12", + Type: ftypes.Debian, + Class: types.ClassOSPkg, + Packages: []ftypes.Package{ + { + ID: "libc6@2.37-15.1", + Name: "libc6", + Version: "2.37-15.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Name: "libc6", + Version: "2.37-15.1", + }, + }, + }, + { + ID: "curl@7.50.3-1", + Name: "curl", + Version: "7.50.3-1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Name: "curl", + Version: "7.50.3-1", + }, + }, + DependsOn: []string{ + "libc6@2.37-15.1", + }, + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + PkgName: "curl", + PkgID: "curl@7.50.3-1", + VulnerabilityID: "CVE-2021-22876", + InstalledVersion: "7.50.3-1", + FixedVersion: "7.50.3-1+deb9u1", + Vulnerability: dtypes.Vulnerability{ + Severity: "HIGH", + }, + }, + }, + }, + { + Target: "Java", + Type: ftypes.Jar, + Class: types.ClassLangPkg, + Packages: []ftypes.Package{ + { + ID: "org.apache.xmlgraphics/batik-anim:1.9.1", + Name: "org.apache.xmlgraphics/batik-anim", + Version: "1.9.1", + FilePath: "/app/batik-anim-1.9.1.jar", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.xmlgraphics", + Name: "batik-anim", + Version: "1.9.1", + }, + }, + }, + }, + }, + }, + }, + wantComponents: map[uuid.UUID]*core.Component{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): { + Type: core.TypeContainerImage, + Name: "debian:12", + Root: true, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeOCI, + Name: "debian", + Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + Qualifiers: packageurl.Qualifiers{ + { + Key: "repository_url", + Value: "index.docker.io/library/debian", + }, + }, + }, + BOMRef: "pkg:oci/debian@sha256%3A4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90?repository_url=index.docker.io%2Flibrary%2Fdebian", + }, + Properties: []core.Property{ + { + Name: core.PropertyRepoDigest, + Value: "debian@sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + }, + { + Name: core.PropertyRepoTag, + Value: "debian:12", + }, + { + Name: core.PropertyRepoTag, + Value: "debian:latest", + }, + { + Name: core.PropertySchemaVersion, + Value: "2", + }, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): { + Type: core.TypeOS, + Name: "debian", + Version: "12", + Properties: []core.Property{ + { + Name: core.PropertyClass, + Value: "os-pkgs", + }, + { + Name: core.PropertyType, + Value: "debian", + }, + }, + PkgID: core.PkgID{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"): { + Type: core.TypeLibrary, + Name: "libc6", + Version: "2.37-15.1", + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: "libc6@2.37-15.1", + }, + { + Name: core.PropertyPkgType, + Value: "debian", + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Name: "libc6", + Version: "2.37-15.1", + }, + BOMRef: "pkg:deb/libc6@2.37-15.1", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"): { + Type: core.TypeLibrary, + Name: "curl", + Version: "7.50.3-1", + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: "curl@7.50.3-1", + }, + { + Name: core.PropertyPkgType, + Value: "debian", + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Name: "curl", + Version: "7.50.3-1", + }, + BOMRef: "pkg:deb/curl@7.50.3-1", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"): { + Type: core.TypeLibrary, + Group: "org.apache.xmlgraphics", + Name: "batik-anim", + Version: "1.9.1", + Files: []core.File{ + { + Path: "/app/batik-anim-1.9.1.jar", + }, + }, + Properties: []core.Property{ + { + Name: core.PropertyFilePath, + Value: "/app/batik-anim-1.9.1.jar", + }, + { + Name: core.PropertyPkgID, + Value: "org.apache.xmlgraphics/batik-anim:1.9.1", + }, + { + Name: core.PropertyPkgType, + Value: "jar", + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.xmlgraphics", + Name: "batik-anim", + Version: "1.9.1", + }, + BOMRef: "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1", + }, + }, + }, + wantRels: map[uuid.UUID][]core.Relationship{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"), + Type: core.RelationshipContains, + }, + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"), + Type: core.RelationshipContains, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"), + Type: core.RelationshipContains, + }, + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"), + Type: core.RelationshipContains, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"): nil, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"), + Type: core.RelationshipDependsOn, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"): nil, + }, + wantVulns: map[uuid.UUID][]core.Vulnerability{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"): { + { + ID: "CVE-2021-22876", + PkgID: "curl@7.50.3-1", + PkgName: "curl", + InstalledVersion: "7.50.3-1", + FixedVersion: "7.50.3-1+deb9u1", + Vulnerability: dtypes.Vulnerability{ + Severity: "HIGH", + }, + }, + }, + }, + }, + { + name: "invalid digest", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "debian:12", + ArtifactType: ftypes.ArtifactContainerImage, + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: ftypes.Debian, + Name: "12", + }, + RepoTags: []string{ + "debian:12", + }, + RepoDigests: []string{ + "debian@sha256:123", + }, + }, + }, + wantErr: "failed to parse digest", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d") + + opts := core.Options{GenerateBOMRef: true} + got, err := sbomio.NewEncoder(opts).Encode(tt.report) + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + + require.Len(t, got.Components(), len(tt.wantComponents)) + for id, want := range tt.wantComponents { + assert.EqualExportedValues(t, *want, *got.Components()[id]) + } + + assert.Equal(t, tt.wantRels, got.Relationships()) + assert.Equal(t, tt.wantVulns, got.Vulnerabilities()) + }) + } +} diff --git a/pkg/sbom/sbom.go b/pkg/sbom/sbom.go new file mode 100644 index 000000000000..5b1055ed7174 --- /dev/null +++ b/pkg/sbom/sbom.go @@ -0,0 +1,235 @@ +package sbom + +import ( + "bufio" + "encoding/json" + "encoding/xml" + "io" + "strings" + + "github.com/in-toto/in-toto-golang/in_toto" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/attestation" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" + "github.com/aquasecurity/trivy/pkg/sbom/spdx" + "github.com/aquasecurity/trivy/pkg/types" +) + +type Format string + +const ( + FormatCycloneDXJSON Format = "cyclonedx-json" + FormatCycloneDXXML Format = "cyclonedx-xml" + FormatSPDXJSON Format = "spdx-json" + FormatSPDXTV Format = "spdx-tv" + FormatSPDXXML Format = "spdx-xml" + FormatAttestCycloneDXJSON Format = "attest-cyclonedx-json" + FormatUnknown Format = "unknown" + + // FormatLegacyCosignAttestCycloneDXJSON is used to support the older format of CycloneDX JSON Attestation + // produced by the Cosign V1. + // ref. https://github.com/sigstore/cosign/pull/2718 + FormatLegacyCosignAttestCycloneDXJSON Format = "legacy-cosign-attest-cyclonedx-json" + + // PredicateCycloneDXBeforeV05 is the PredicateCycloneDX value defined in in-toto-golang before v0.5.0. + // This is necessary for backward-compatible SBOM detection. + // ref. https://github.com/in-toto/in-toto-golang/pull/188 + PredicateCycloneDXBeforeV05 = "https://cyclonedx.org/schema" +) + +var ErrUnknownFormat = xerrors.New("Unknown SBOM format") + +type cdxHeader struct { + // XML specific field + XMLNS string `json:"-" xml:"xmlns,attr"` + + // JSON specific field + BOMFormat string `json:"bomFormat" xml:"-"` +} + +type spdxHeader struct { + SpdxID string `json:"SPDXID"` +} + +func IsCycloneDXJSON(r io.ReadSeeker) (bool, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return false, xerrors.Errorf("seek error: %w", err) + } + + var cdxBom cdxHeader + if err := json.NewDecoder(r).Decode(&cdxBom); err == nil { + if cdxBom.BOMFormat == "CycloneDX" { + return true, nil + } + } + return false, nil +} +func IsCycloneDXXML(r io.ReadSeeker) (bool, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return false, xerrors.Errorf("seek error: %w", err) + } + + var cdxBom cdxHeader + if err := xml.NewDecoder(r).Decode(&cdxBom); err == nil { + if strings.HasPrefix(cdxBom.XMLNS, "http://cyclonedx.org") { + return true, nil + } + } + return false, nil +} + +func IsSPDXJSON(r io.ReadSeeker) (bool, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return false, xerrors.Errorf("seek error: %w", err) + } + + var spdxBom spdxHeader + if err := json.NewDecoder(r).Decode(&spdxBom); err == nil { + if strings.HasPrefix(spdxBom.SpdxID, "SPDX") { + return true, nil + } + } + return false, nil +} + +func IsSPDXTV(r io.ReadSeeker) (bool, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return false, xerrors.Errorf("seek error: %w", err) + } + + if scanner := bufio.NewScanner(r); scanner.Scan() { + if strings.HasPrefix(scanner.Text(), "SPDX") { + return true, nil + } + } + return false, nil +} + +func DetectFormat(r io.ReadSeeker) (Format, error) { + // Rewind the SBOM file at the end + defer r.Seek(0, io.SeekStart) + + // Try CycloneDX JSON + if ok, err := IsCycloneDXJSON(r); err != nil { + return FormatUnknown, err + } else if ok { + return FormatCycloneDXJSON, nil + } + + // Try CycloneDX XML + if ok, err := IsCycloneDXXML(r); err != nil { + return FormatUnknown, err + } else if ok { + return FormatCycloneDXXML, nil + } + + // Try SPDX json + if ok, err := IsSPDXJSON(r); err != nil { + return FormatUnknown, err + } else if ok { + return FormatSPDXJSON, nil + } + + // Try SPDX tag-value + if ok, err := IsSPDXTV(r); err != nil { + return FormatUnknown, err + } else if ok { + return FormatSPDXTV, nil + } + + if _, err := r.Seek(0, io.SeekStart); err != nil { + return FormatUnknown, xerrors.Errorf("seek error: %w", err) + } + + // Try in-toto attestation + format, ok := decodeAttestCycloneDXJSONFormat(r) + if ok { + return format, nil + } + + return FormatUnknown, nil +} + +func decodeAttestCycloneDXJSONFormat(r io.ReadSeeker) (Format, bool) { + var s attestation.Statement + + if err := json.NewDecoder(r).Decode(&s); err != nil { + return "", false + } + + if s.PredicateType != in_toto.PredicateCycloneDX && s.PredicateType != PredicateCycloneDXBeforeV05 { + return "", false + } + + if s.Predicate == nil { + return "", false + } + + m, ok := s.Predicate.(map[string]interface{}) + if !ok { + return "", false + } + + if _, ok := m["Data"]; ok { + return FormatLegacyCosignAttestCycloneDXJSON, true + } + + return FormatAttestCycloneDXJSON, true +} + +func Decode(f io.Reader, format Format) (types.SBOM, error) { + var ( + v interface{} + bom = core.NewBOM(core.Options{}) + decoder interface{ Decode(any) error } + ) + + switch format { + case FormatCycloneDXJSON: + v = &cyclonedx.BOM{BOM: bom} + decoder = json.NewDecoder(f) + case FormatAttestCycloneDXJSON: + // dsse envelope + // => in-toto attestation + // => CycloneDX JSON + v = &attestation.Statement{ + Predicate: &cyclonedx.BOM{BOM: bom}, + } + decoder = json.NewDecoder(f) + case FormatLegacyCosignAttestCycloneDXJSON: + // dsse envelope + // => in-toto attestation + // => cosign predicate + // => CycloneDX JSON + v = &attestation.Statement{ + Predicate: &attestation.CosignPredicate{ + Data: &cyclonedx.BOM{BOM: bom}, + }, + } + decoder = json.NewDecoder(f) + case FormatSPDXJSON: + v = &spdx.SPDX{BOM: bom} + decoder = json.NewDecoder(f) + case FormatSPDXTV: + v = &spdx.SPDX{BOM: bom} + decoder = spdx.NewTVDecoder(f) + default: + return types.SBOM{}, xerrors.Errorf("%s scanning is not yet supported", format) + + } + + // Decode a file content into core.BOM + if err := decoder.Decode(v); err != nil { + return types.SBOM{}, xerrors.Errorf("failed to decode: %w", err) + } + + var sbom types.SBOM + if err := sbomio.NewDecoder(bom).Decode(&sbom); err != nil { + return types.SBOM{}, xerrors.Errorf("failed to decode: %w", err) + } + + return sbom, nil +} diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go new file mode 100644 index 000000000000..6c1490fe1aec --- /dev/null +++ b/pkg/sbom/spdx/marshal.go @@ -0,0 +1,538 @@ +package spdx + +import ( + "context" + "fmt" + "sort" + "strings" + "time" + + "github.com/mitchellh/hashstructure/v2" + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/spdx/v2/common" + spdxutils "github.com/spdx/tools-golang/utils" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/licensing/expression" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/sbom/core" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" +) + +const ( + DocumentSPDXIdentifier = "DOCUMENT" + DocumentNamespace = "http://aquasecurity.github.io/trivy" + CreatorOrganization = "aquasecurity" + CreatorTool = "trivy" + noneField = "NONE" +) + +const ( + CategoryPackageManager = "PACKAGE-MANAGER" + RefTypePurl = "purl" + + // Package Purpose fields + PackagePurposeOS = "OPERATING-SYSTEM" + PackagePurposeContainer = "CONTAINER" + PackagePurposeSource = "SOURCE" + PackagePurposeApplication = "APPLICATION" + PackagePurposeLibrary = "LIBRARY" + + PackageSupplierNoAssertion = "NOASSERTION" + PackageSupplierOrganization = "Organization" + + RelationShipContains = common.TypeRelationshipContains + RelationShipDescribe = common.TypeRelationshipDescribe + RelationShipDependsOn = common.TypeRelationshipDependsOn + + ElementOperatingSystem = "OperatingSystem" + ElementApplication = "Application" + ElementPackage = "Package" + ElementFile = "File" +) + +var ( + SourcePackagePrefix = "built package from" + SourceFilePrefix = "package found in" +) + +// duplicateProperties contains a list of properties contained in other fields. +var duplicateProperties = []string{ + // `SourceInfo` contains SrcName and SrcVersion (it contains PropertySrcRelease and PropertySrcEpoch) + core.PropertySrcName, + core.PropertySrcRelease, + core.PropertySrcEpoch, + core.PropertySrcVersion, + // `File` contains filePath. + core.PropertyFilePath, +} + +type Marshaler struct { + format spdx.Document + hasher Hash + appVersion string // Trivy version. It needed for `creator` field +} + +type Hash func(v interface{}, format hashstructure.Format, opts *hashstructure.HashOptions) (uint64, error) + +type marshalOption func(*Marshaler) + +func WithHasher(hasher Hash) marshalOption { + return func(opts *Marshaler) { + opts.hasher = hasher + } +} + +func NewMarshaler(version string, opts ...marshalOption) *Marshaler { + m := &Marshaler{ + format: spdx.Document{}, + hasher: hashstructure.Hash, + appVersion: version, + } + + for _, opt := range opts { + opt(m) + } + + return m +} + +func (m *Marshaler) MarshalReport(ctx context.Context, report types.Report) (*spdx.Document, error) { + // Convert into an intermediate representation + bom, err := sbomio.NewEncoder(core.Options{}).Encode(report) + if err != nil { + return nil, xerrors.Errorf("failed to marshal report: %w", err) + } + + return m.Marshal(ctx, bom) +} + +func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*spdx.Document, error) { + var ( + relationShips []*spdx.Relationship + packages []*spdx.Package + ) + + root := bom.Root() + pkgDownloadLocation := m.packageDownloadLocation(root) + + // Component ID => SPDX ID + packageIDs := make(map[uuid.UUID]spdx.ElementID) + + // Root package contains OS, OS packages, language-specific packages and so on. + rootPkg, err := m.rootSPDXPackage(root, pkgDownloadLocation) + if err != nil { + return nil, xerrors.Errorf("failed to generate a root package: %w", err) + } + packages = append(packages, rootPkg) + relationShips = append(relationShips, + m.spdxRelationShip(DocumentSPDXIdentifier, rootPkg.PackageSPDXIdentifier, RelationShipDescribe), + ) + packageIDs[root.ID()] = rootPkg.PackageSPDXIdentifier + + var files []*spdx.File + for _, c := range bom.Components() { + if c.Root { + continue + } + spdxPackage, err := m.spdxPackage(c, pkgDownloadLocation) + if err != nil { + return nil, xerrors.Errorf("spdx package error: %w", err) + } + packages = append(packages, &spdxPackage) + packageIDs[c.ID()] = spdxPackage.PackageSPDXIdentifier + + spdxFiles, err := m.spdxFiles(c) + if err != nil { + return nil, xerrors.Errorf("spdx files error: %w", err) + } else if len(spdxFiles) == 0 { + continue + } + + files = append(files, spdxFiles...) + for _, file := range spdxFiles { + relationShips = append(relationShips, + m.spdxRelationShip(spdxPackage.PackageSPDXIdentifier, file.FileSPDXIdentifier, RelationShipContains), + ) + } + verificationCode, err := spdxutils.GetVerificationCode(spdxFiles, "") + if err != nil { + return nil, xerrors.Errorf("package verification error: %w", err) + } + spdxPackage.FilesAnalyzed = true + spdxPackage.PackageVerificationCode = &verificationCode + } + + for id, rels := range bom.Relationships() { + for _, rel := range rels { + refA, ok := packageIDs[id] + if !ok { + continue + } + refB, ok := packageIDs[rel.Dependency] + if !ok { + continue + } + relationShips = append(relationShips, m.spdxRelationShip(refA, refB, m.spdxRelationshipType(rel.Type))) + } + } + sortPackages(packages) + sortRelationships(relationShips) + sortFiles(files) + + return &spdx.Document{ + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + SPDXIdentifier: DocumentSPDXIdentifier, + DocumentName: root.Name, + DocumentNamespace: getDocumentNamespace(root), + CreationInfo: &spdx.CreationInfo{ + Creators: []common.Creator{ + { + Creator: CreatorOrganization, + CreatorType: "Organization", + }, + { + Creator: fmt.Sprintf("%s-%s", CreatorTool, m.appVersion), + CreatorType: "Tool", + }, + }, + Created: clock.Now(ctx).UTC().Format(time.RFC3339), + }, + Packages: packages, + Relationships: relationShips, + Files: files, + }, nil +} + +func (m *Marshaler) packageDownloadLocation(root *core.Component) string { + location := noneField + // this field is used for git/mercurial/subversion/bazaar: + // https://spdx.github.io/spdx-spec/v2.2.2/package-information/#77-package-download-location-field + if root.Type == core.TypeRepository { + // Trivy currently only supports git repositories. Format examples: + // git+https://git.myproject.org/MyProject.git + // git+http://git.myproject.org/MyProject + location = fmt.Sprintf("git+%s", root.Name) + } + return location +} + +func (m *Marshaler) rootSPDXPackage(root *core.Component, pkgDownloadLocation string) (*spdx.Package, error) { + var externalReferences []*spdx.PackageExternalReference + // When the target is a container image, add PURL to the external references of the root package. + if root.PkgID.PURL != nil { + externalReferences = append(externalReferences, m.purlExternalReference(root.PkgID.PURL.String())) + } + + pkgID, err := calcPkgID(m.hasher, fmt.Sprintf("%s-%s", root.Name, root.Type)) + if err != nil { + return nil, xerrors.Errorf("failed to get %s package ID: %w", pkgID, err) + } + + pkgPurpose := PackagePurposeSource + if root.Type == core.TypeContainerImage { + pkgPurpose = PackagePurposeContainer + } + + return &spdx.Package{ + PackageName: root.Name, + PackageSPDXIdentifier: elementID(camelCase(string(root.Type)), pkgID), + PackageDownloadLocation: pkgDownloadLocation, + PackageAttributionTexts: m.spdxAttributionTexts(root), + PackageExternalReferences: externalReferences, + PrimaryPackagePurpose: pkgPurpose, + }, nil +} + +func (m *Marshaler) appendAttributionText(attributionTexts []string, key, value string) []string { + if value == "" { + return attributionTexts + } + return append(attributionTexts, fmt.Sprintf("%s: %s", key, value)) +} + +func (m *Marshaler) purlExternalReference(packageURL string) *spdx.PackageExternalReference { + return &spdx.PackageExternalReference{ + Category: CategoryPackageManager, + RefType: RefTypePurl, + Locator: packageURL, + } +} + +func (m *Marshaler) spdxPackage(c *core.Component, pkgDownloadLocation string) (spdx.Package, error) { + pkgID, err := calcPkgID(m.hasher, c) + if err != nil { + return spdx.Package{}, xerrors.Errorf("failed to get os metadata package ID: %w", err) + } + + var elementType, purpose, license, sourceInfo string + var supplier *spdx.Supplier + switch c.Type { + case core.TypeOS: + elementType = ElementOperatingSystem + purpose = PackagePurposeOS + case core.TypeApplication: + elementType = ElementApplication + purpose = PackagePurposeApplication + case core.TypeLibrary: + elementType = ElementPackage + purpose = PackagePurposeLibrary + license = m.spdxLicense(c) + + if c.SrcName != "" { + sourceInfo = fmt.Sprintf("%s: %s %s", SourcePackagePrefix, c.SrcName, c.SrcVersion) + } else if c.SrcFile != "" { + sourceInfo = fmt.Sprintf("%s: %s", SourceFilePrefix, c.SrcFile) + } + + supplier = &spdx.Supplier{Supplier: PackageSupplierNoAssertion} + if c.Supplier != "" { + supplier = &spdx.Supplier{ + SupplierType: PackageSupplierOrganization, // Always use "Organization" at the moment as it is difficult to distinguish between "Person" or "Organization". + Supplier: c.Supplier, + } + } + } + + var pkgExtRefs []*spdx.PackageExternalReference + if c.PkgID.PURL != nil { + pkgExtRefs = []*spdx.PackageExternalReference{m.purlExternalReference(c.PkgID.PURL.String())} + } + + var digests []digest.Digest + for _, f := range c.Files { + // The file digests are stored separately. + if f.Path != "" { + continue + } + digests = append(digests, f.Digests...) + } + + return spdx.Package{ + PackageSPDXIdentifier: elementID(elementType, pkgID), + PackageName: spdxPkgName(c), + PackageVersion: c.Version, + PrimaryPackagePurpose: purpose, + PackageDownloadLocation: pkgDownloadLocation, + PackageExternalReferences: pkgExtRefs, + PackageAttributionTexts: m.spdxAttributionTexts(c), + PackageSourceInfo: sourceInfo, + PackageSupplier: supplier, + PackageChecksums: m.spdxChecksums(digests), + + // The Declared License is what the authors of a project believe govern the package + PackageLicenseConcluded: license, + + // The Concluded License field is the license the SPDX file creator believes governs the package + PackageLicenseDeclared: license, + }, nil +} + +func spdxPkgName(component *core.Component) string { + if p := component.PkgID.PURL; p != nil && component.Group != "" { + if p.Type == packageurl.TypeMaven || p.Type == packageurl.TypeGradle { + return component.Group + ":" + component.Name + } + return component.Group + "/" + component.Name + } + return component.Name +} + +func (m *Marshaler) spdxAttributionTexts(c *core.Component) []string { + var texts []string + for _, p := range c.Properties { + // Add properties that are not in other fields. + if !slices.Contains(duplicateProperties, p.Name) { + texts = m.appendAttributionText(texts, p.Name, p.Value) + } + } + return texts +} + +func (m *Marshaler) spdxLicense(c *core.Component) string { + if len(c.Licenses) == 0 { + return noneField + } + return NormalizeLicense(c.Licenses) +} + +func (m *Marshaler) spdxChecksums(digests []digest.Digest) []common.Checksum { + var checksums []common.Checksum + for _, d := range digests { + var alg spdx.ChecksumAlgorithm + switch d.Algorithm() { + case digest.SHA1: + alg = spdx.SHA1 + case digest.SHA256: + alg = spdx.SHA256 + case digest.MD5: + alg = spdx.MD5 + default: + return nil + } + checksums = append(checksums, spdx.Checksum{ + Algorithm: alg, + Value: d.Encoded(), + }) + } + + return checksums +} + +func (m *Marshaler) spdxFiles(c *core.Component) ([]*spdx.File, error) { + var files []*spdx.File + for _, file := range c.Files { + if file.Path == "" || len(file.Digests) == 0 { + continue + } + spdxFile, err := m.spdxFile(file.Path, file.Digests) + if err != nil { + return nil, xerrors.Errorf("failed to parse file: %w", err) + } + files = append(files, spdxFile) + } + return files, nil +} + +func (m *Marshaler) spdxFile(filePath string, digests []digest.Digest) (*spdx.File, error) { + pkgID, err := calcPkgID(m.hasher, filePath) + if err != nil { + return nil, xerrors.Errorf("failed to get %s package ID: %w", filePath, err) + } + return &spdx.File{ + FileSPDXIdentifier: spdx.ElementID(fmt.Sprintf("File-%s", pkgID)), + FileName: filePath, + Checksums: m.spdxChecksums(digests), + }, nil +} + +func (m *Marshaler) spdxRelationShip(refA, refB spdx.ElementID, operator string) *spdx.Relationship { + ref := spdx.Relationship{ + RefA: common.MakeDocElementID("", string(refA)), + RefB: common.MakeDocElementID("", string(refB)), + Relationship: operator, + } + return &ref +} + +func (m *Marshaler) spdxRelationshipType(relType core.RelationshipType) string { + switch relType { + case core.RelationshipDependsOn: + return RelationShipDependsOn + case core.RelationshipContains: + return RelationShipContains + case core.RelationshipDescribes: + return RelationShipDescribe + default: + return RelationShipDependsOn + } +} + +func sortPackages(pkgs []*spdx.Package) { + sort.Slice(pkgs, func(i, j int) bool { + switch { + case pkgs[i].PrimaryPackagePurpose != pkgs[j].PrimaryPackagePurpose: + return pkgs[i].PrimaryPackagePurpose < pkgs[j].PrimaryPackagePurpose + case pkgs[i].PackageName != pkgs[j].PackageName: + return pkgs[i].PackageName < pkgs[j].PackageName + default: + return pkgs[i].PackageSPDXIdentifier < pkgs[j].PackageSPDXIdentifier + } + }) +} + +func sortRelationships(rels []*spdx.Relationship) { + sort.Slice(rels, func(i, j int) bool { + switch { + case rels[i].RefA.ElementRefID != rels[j].RefA.ElementRefID: + return rels[i].RefA.ElementRefID < rels[j].RefA.ElementRefID + case rels[i].RefB.ElementRefID != rels[j].RefB.ElementRefID: + return rels[i].RefB.ElementRefID < rels[j].RefB.ElementRefID + default: + return rels[i].Relationship < rels[j].Relationship + } + }) +} + +func sortFiles(files []*spdx.File) { + sort.Slice(files, func(i, j int) bool { + switch { + case files[i].FileName != files[j].FileName: + return files[i].FileName < files[j].FileName + default: + return files[i].FileSPDXIdentifier < files[j].FileSPDXIdentifier + } + }) +} + +func elementID(elementType, pkgID string) spdx.ElementID { + return spdx.ElementID(fmt.Sprintf("%s-%s", elementType, pkgID)) +} + +func getDocumentNamespace(root *core.Component) string { + return fmt.Sprintf("%s/%s/%s-%s", + DocumentNamespace, + string(root.Type), + strings.ReplaceAll(strings.ReplaceAll(root.Name, "https://", ""), "http://", ""), // remove http(s):// prefix when scanning repos + uuid.New().String(), + ) +} + +func calcPkgID(h Hash, v interface{}) (string, error) { + f, err := h(v, hashstructure.FormatV2, &hashstructure.HashOptions{ + ZeroNil: true, + SlicesAsSets: true, + }) + if err != nil { + return "", xerrors.Errorf("could not build package ID for %+v: %w", v, err) + } + + return fmt.Sprintf("%x", f), nil +} + +func camelCase(inputUnderScoreStr string) (camelCase string) { + isToUpper := false + for k, v := range inputUnderScoreStr { + if k == 0 { + camelCase = strings.ToUpper(string(inputUnderScoreStr[0])) + } else { + if isToUpper { + camelCase += strings.ToUpper(string(v)) + isToUpper = false + } else { + if v == '_' { + isToUpper = true + } else { + camelCase += string(v) + } + } + } + } + return +} + +func NormalizeLicense(licenses []string) string { + license := strings.Join(lo.Map(licenses, func(license string, index int) string { + // e.g. GPL-3.0-with-autoconf-exception + license = strings.ReplaceAll(license, "-with-", " WITH ") + license = strings.ReplaceAll(license, "-WITH-", " WITH ") + + return fmt.Sprintf("(%s)", license) + }), " AND ") + s, err := expression.Normalize(license, licensing.Normalize, expression.NormalizeForSPDX) + if err != nil { + // Not fail on the invalid license + log.Logger.Warnf("Unable to marshal SPDX licenses %q", license) + return "" + } + return s +} diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go new file mode 100644 index 000000000000..c7757de8ca81 --- /dev/null +++ b/pkg/sbom/spdx/marshal_test.go @@ -0,0 +1,1268 @@ +package spdx_test + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/package-url/packageurl-go" + "hash/fnv" + "testing" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/mitchellh/hashstructure/v2" + "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/spdx/v2/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/report" + tspdx "github.com/aquasecurity/trivy/pkg/sbom/spdx" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" +) + +func TestMarshaler_Marshal(t *testing.T) { + testCases := []struct { + name string + inputReport types.Report + wantSBOM *spdx.Document + }{ + { + name: "happy path for container scan", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "rails:latest", + ArtifactType: ftypes.ArtifactContainerImage, + Metadata: types.Metadata{ + Size: 1024, + OS: &ftypes.OS{ + Family: ftypes.CentOS, + Name: "8.3.2011", + Eosl: true, + }, + ImageID: "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", + RepoTags: []string{"rails:latest"}, + DiffIDs: []string{"sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a"}, + RepoDigests: []string{"rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177"}, + ImageConfig: v1.ConfigFile{ + Architecture: "arm64", + }, + }, + Results: types.Results{ + { + Target: "rails:latest (centos 8.3.2011)", + Class: types.ClassOSPkg, + Type: ftypes.CentOS, + Packages: []ftypes.Package{ + { + Name: "binutils", + Version: "2.30", + Release: "93.el8", + Epoch: 0, + Arch: "aarch64", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "centos", + Name: "binutils", + Version: "2.30-93.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "aarch64", + }, + { + Key: "distro", + Value: "centos-8.3.2011", + }, + }, + }, + }, + SrcName: "binutils", + SrcVersion: "2.30", + SrcRelease: "93.el8", + SrcEpoch: 0, + Modularitylabel: "", + Licenses: []string{"GPLv3+"}, + Maintainer: "CentOS", + Digest: "md5:7459cec61bb4d1b0ca8107e25e0dd005", + }, + }, + }, + { + Target: "app/subproject/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: []ftypes.Package{ + { + Name: "actionpack", + Version: "7.0.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.1", + }, + }, + }, + { + Name: "actioncontroller", + Version: "7.0.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actioncontroller", + Version: "7.0.1", + }, + }, + }, + }, + }, + { + Target: "app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: []ftypes.Package{ + { + Name: "actionpack", + Version: "7.0.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.1", + }, + }, + }, + }, + }, + }, + }, + wantSBOM: &spdx.Document{ + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + SPDXIdentifier: "DOCUMENT", + DocumentName: "rails:latest", + DocumentNamespace: "http://aquasecurity.github.io/trivy/container_image/rails:latest-3ff14136-e09f-4df9-80ea-000000000009", + CreationInfo: &spdx.CreationInfo{ + Creators: []common.Creator{ + { + Creator: "aquasecurity", + CreatorType: "Organization", + }, + { + Creator: "trivy-0.38.1", + CreatorType: "Tool", + }, + }, + Created: "2021-08-25T12:20:30Z", + }, + Packages: []*spdx.Package{ + { + PackageSPDXIdentifier: spdx.ElementID("Application-9f48cdd13858abaf"), + PackageDownloadLocation: "NONE", + PackageName: "app/Gemfile.lock", + PrimaryPackagePurpose: tspdx.PackagePurposeApplication, + PackageAttributionTexts: []string{ + "Class: lang-pkgs", + "Type: bundler", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Application-692290f4b2235359"), + PackageDownloadLocation: "NONE", + PackageName: "app/subproject/Gemfile.lock", + PrimaryPackagePurpose: tspdx.PackagePurposeApplication, + PackageAttributionTexts: []string{ + "Class: lang-pkgs", + "Type: bundler", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("ContainerImage-9396d894cd0cb6cb"), + PackageDownloadLocation: "NONE", + PackageName: "rails:latest", + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails", + }, + }, + PackageAttributionTexts: []string{ + "DiffID: sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a", + "ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", + "RepoDigest: rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177", + "RepoTag: rails:latest", + "SchemaVersion: 2", + "Size: 1024", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeContainer, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-b8d4663e6d412e7"), + PackageDownloadLocation: "NONE", + PackageName: "actioncontroller", + PackageVersion: "7.0.1", + PackageLicenseConcluded: "NONE", + PackageLicenseDeclared: "NONE", + PackageAttributionTexts: []string{ + "PkgType: bundler", + }, + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:gem/actioncontroller@7.0.1", + }, + }, + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + PackageSourceInfo: "package found in: app/subproject/Gemfile.lock", + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-3b51e821f6796568"), + PackageDownloadLocation: "NONE", + PackageName: "actionpack", + PackageVersion: "7.0.1", + PackageLicenseConcluded: "NONE", + PackageLicenseDeclared: "NONE", + PackageAttributionTexts: []string{ + "PkgType: bundler", + }, + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:gem/actionpack@7.0.1", + }, + }, + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + PackageSourceInfo: "package found in: app/subproject/Gemfile.lock", + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-fb5630bc7d55a21c"), + PackageDownloadLocation: "NONE", + PackageName: "actionpack", + PackageVersion: "7.0.1", + PackageLicenseConcluded: "NONE", + PackageLicenseDeclared: "NONE", + PackageAttributionTexts: []string{ + "PkgType: bundler", + }, + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:gem/actionpack@7.0.1", + }, + }, + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + PackageSourceInfo: "package found in: app/Gemfile.lock", + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-5d43902b18ed2e2c"), + PackageDownloadLocation: "NONE", + PackageName: "binutils", + PackageVersion: "2.30-93.el8", + PackageLicenseConcluded: "GPL-3.0-or-later", + PackageLicenseDeclared: "GPL-3.0-or-later", + PackageAttributionTexts: []string{ + "PkgType: centos", + }, + PackageSupplier: &spdx.Supplier{ + SupplierType: tspdx.PackageSupplierOrganization, + Supplier: "CentOS", + }, + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:rpm/centos/binutils@2.30-93.el8?arch=aarch64&distro=centos-8.3.2011", + }, + }, + PackageSourceInfo: "built package from: binutils 2.30-93.el8", + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageChecksums: []common.Checksum{ + { + Algorithm: common.MD5, + Value: "7459cec61bb4d1b0ca8107e25e0dd005", + }, + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("OperatingSystem-20f7fa3049cc748c"), + PackageDownloadLocation: "NONE", + PackageName: "centos", + PackageVersion: "8.3.2011", + PrimaryPackagePurpose: tspdx.PackagePurposeOS, + PackageAttributionTexts: []string{ + "Class: os-pkgs", + "Type: centos", + }, + }, + }, + Relationships: []*spdx.Relationship{ + { + RefA: spdx.DocElementID{ElementRefID: "Application-692290f4b2235359"}, + RefB: spdx.DocElementID{ElementRefID: "Package-3b51e821f6796568"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Application-692290f4b2235359"}, + RefB: spdx.DocElementID{ElementRefID: "Package-b8d4663e6d412e7"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Application-9f48cdd13858abaf"}, + RefB: spdx.DocElementID{ElementRefID: "Package-fb5630bc7d55a21c"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"}, + RefB: spdx.DocElementID{ElementRefID: "Application-692290f4b2235359"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"}, + RefB: spdx.DocElementID{ElementRefID: "Application-9f48cdd13858abaf"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"}, + RefB: spdx.DocElementID{ElementRefID: "OperatingSystem-20f7fa3049cc748c"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"}, + RefB: spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"}, + Relationship: "DESCRIBES", + }, + { + RefA: spdx.DocElementID{ElementRefID: "OperatingSystem-20f7fa3049cc748c"}, + RefB: spdx.DocElementID{ElementRefID: "Package-5d43902b18ed2e2c"}, + Relationship: "CONTAINS", + }, + }, + OtherLicenses: nil, + Annotations: nil, + Reviews: nil, + }, + }, + { + name: "happy path for local container scan", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "centos:latest", + ArtifactType: ftypes.ArtifactContainerImage, + Metadata: types.Metadata{ + Size: 1024, + OS: &ftypes.OS{ + Family: ftypes.CentOS, + Name: "8.3.2011", + Eosl: true, + }, + ImageID: "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", + RepoTags: []string{"centos:latest"}, + RepoDigests: []string{}, + ImageConfig: v1.ConfigFile{ + Architecture: "arm64", + }, + }, + Results: types.Results{ + { + Target: "centos:latest (centos 8.3.2011)", + Class: types.ClassOSPkg, + Type: ftypes.CentOS, + Packages: []ftypes.Package{ + { + Name: "acl", + Version: "2.2.53", + Release: "1.el8", + Epoch: 1, + Arch: "aarch64", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "centos", + Name: "acl", + Version: "2.2.53-1.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "aarch64", + }, + { + Key: "distro", + Value: "centos-8.3.2011", + }, + { + Key: "epoch", + Value: "1", + }, + }, + }, + }, + SrcName: "acl", + SrcVersion: "2.2.53", + SrcRelease: "1.el8", + SrcEpoch: 1, + Modularitylabel: "", + Licenses: []string{"GPLv2+"}, + Digest: "md5:483792b8b5f9eb8be7dc4407733118d0", + }, + }, + }, + { + Target: "Ruby", + Class: types.ClassLangPkg, + Type: ftypes.GemSpec, + Packages: []ftypes.Package{ + { + Name: "actionpack", + Version: "7.0.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.1", + }, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", + }, + FilePath: "tools/project-john/specifications/actionpack.gemspec", + Digest: "sha1:d2f9f9aed5161f6e4116a3f9573f41cd832f137c", + }, + { + Name: "actionpack", + Version: "7.0.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.1", + }, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", + }, + FilePath: "tools/project-doe/specifications/actionpack.gemspec", + Digest: "sha1:413f98442c83808042b5d1d2611a346b999bdca5", + }, + }, + }, + }, + }, + wantSBOM: &spdx.Document{ + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + SPDXIdentifier: "DOCUMENT", + DocumentName: "centos:latest", + DocumentNamespace: "http://aquasecurity.github.io/trivy/container_image/centos:latest-3ff14136-e09f-4df9-80ea-000000000006", + CreationInfo: &spdx.CreationInfo{ + Creators: []common.Creator{ + { + Creator: "aquasecurity", + CreatorType: "Organization", + }, + { + Creator: "trivy-0.38.1", + CreatorType: "Tool", + }, + }, + Created: "2021-08-25T12:20:30Z", + }, + Packages: []*spdx.Package{ + { + PackageName: "centos:latest", + PackageSPDXIdentifier: "ContainerImage-413bfede37ad01fc", + PackageDownloadLocation: "NONE", + PackageAttributionTexts: []string{ + "ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", + "RepoTag: centos:latest", + "SchemaVersion: 2", + "Size: 1024", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeContainer, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-40c4059fe08523bf"), + PackageDownloadLocation: "NONE", + PackageName: "acl", + PackageVersion: "1:2.2.53-1.el8", + PackageLicenseConcluded: "GPL-2.0-or-later", + PackageLicenseDeclared: "GPL-2.0-or-later", + PackageAttributionTexts: []string{ + "PkgType: centos", + }, + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1", + }, + }, + PackageSourceInfo: "built package from: acl 1:2.2.53-1.el8", + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + PackageChecksums: []common.Checksum{ + { + Algorithm: common.MD5, + Value: "483792b8b5f9eb8be7dc4407733118d0", + }, + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-69f68dd639314edd"), + PackageDownloadLocation: "NONE", + PackageName: "actionpack", + PackageVersion: "7.0.1", + PackageLicenseConcluded: "NONE", + PackageLicenseDeclared: "NONE", + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:gem/actionpack@7.0.1", + }, + }, + PackageAttributionTexts: []string{ + "LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", + "PkgType: gemspec", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + FilesAnalyzed: true, + PackageVerificationCode: &spdx.PackageVerificationCode{ + Value: "688d98e7e5660b879fd1fc548af8c0df3b7d785a", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-da2cda24d2ecbfe6"), + PackageDownloadLocation: "NONE", + PackageName: "actionpack", + PackageVersion: "7.0.1", + PackageLicenseConcluded: "NONE", + PackageLicenseDeclared: "NONE", + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:gem/actionpack@7.0.1", + }, + }, + PackageAttributionTexts: []string{ + "LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", + "PkgType: gemspec", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + FilesAnalyzed: true, + PackageVerificationCode: &spdx.PackageVerificationCode{ + Value: "c7526b18eaaeb410e82cb0da9288dd02b38ea171", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("OperatingSystem-20f7fa3049cc748c"), + PackageDownloadLocation: "NONE", + PackageName: "centos", + PackageVersion: "8.3.2011", + PrimaryPackagePurpose: tspdx.PackagePurposeOS, + PackageAttributionTexts: []string{ + "Class: os-pkgs", + "Type: centos", + }, + }, + }, + Files: []*spdx.File{ + { + FileSPDXIdentifier: "File-fa42187221d0d0a8", + FileName: "tools/project-doe/specifications/actionpack.gemspec", + Checksums: []spdx.Checksum{ + { + Algorithm: spdx.SHA1, + Value: "413f98442c83808042b5d1d2611a346b999bdca5", + }, + }, + }, + { + FileSPDXIdentifier: "File-6a540784b0dc6d55", + FileName: "tools/project-john/specifications/actionpack.gemspec", + Checksums: []spdx.Checksum{ + { + Algorithm: spdx.SHA1, + Value: "d2f9f9aed5161f6e4116a3f9573f41cd832f137c", + }, + }, + }, + }, + Relationships: []*spdx.Relationship{ + { + RefA: spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"}, + RefB: spdx.DocElementID{ElementRefID: "OperatingSystem-20f7fa3049cc748c"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"}, + RefB: spdx.DocElementID{ElementRefID: "Package-69f68dd639314edd"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"}, + RefB: spdx.DocElementID{ElementRefID: "Package-da2cda24d2ecbfe6"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"}, + RefB: spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"}, + Relationship: "DESCRIBES", + }, + { + RefA: spdx.DocElementID{ElementRefID: "OperatingSystem-20f7fa3049cc748c"}, + RefB: spdx.DocElementID{ElementRefID: "Package-40c4059fe08523bf"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Package-69f68dd639314edd"}, + RefB: spdx.DocElementID{ElementRefID: "File-fa42187221d0d0a8"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Package-da2cda24d2ecbfe6"}, + RefB: spdx.DocElementID{ElementRefID: "File-6a540784b0dc6d55"}, + Relationship: "CONTAINS", + }, + }, + + OtherLicenses: nil, + Annotations: nil, + Reviews: nil, + }, + }, + { + name: "happy path for fs scan", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "masahiro331/CVE-2021-41098", + ArtifactType: ftypes.ArtifactFilesystem, + Results: types.Results{ + { + Target: "Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: []ftypes.Package{ + { + Name: "actioncable", + Version: "6.1.4.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actioncable", + Version: "6.1.4.1", + }, + }, + }, + }, + }, + { + Target: "pom.xml", + Class: types.ClassLangPkg, + Type: ftypes.Pom, + Packages: []ftypes.Package{ + { + ID: "com.example:example:1.0.0", + Name: "com.example:example", + Version: "1.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.example", + Name: "example", + Version: "1.0.0", + }, + }, + }, + }, + }, + }, + }, + wantSBOM: &spdx.Document{ + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + SPDXIdentifier: "DOCUMENT", + DocumentName: "masahiro331/CVE-2021-41098", + DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/masahiro331/CVE-2021-41098-3ff14136-e09f-4df9-80ea-000000000006", + CreationInfo: &spdx.CreationInfo{ + Creators: []common.Creator{ + { + Creator: "aquasecurity", + CreatorType: "Organization", + }, + { + Creator: "trivy-0.38.1", + CreatorType: "Tool", + }, + }, + Created: "2021-08-25T12:20:30Z", + }, + Packages: []*spdx.Package{ + { + PackageSPDXIdentifier: spdx.ElementID("Application-ed046c4a6b4da30f"), + PackageDownloadLocation: "NONE", + PackageName: "Gemfile.lock", + PrimaryPackagePurpose: tspdx.PackagePurposeApplication, + PackageAttributionTexts: []string{ + "Class: lang-pkgs", + "Type: bundler", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Application-800d9e6e0f88ab3a"), + PackageDownloadLocation: "NONE", + PackageName: "pom.xml", + PrimaryPackagePurpose: tspdx.PackagePurposeApplication, + PackageAttributionTexts: []string{ + "Class: lang-pkgs", + "Type: pom", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-e78eaf94802a53dc"), + PackageDownloadLocation: "NONE", + PackageName: "actioncable", + PackageVersion: "6.1.4.1", + PackageLicenseConcluded: "NONE", + PackageLicenseDeclared: "NONE", + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:gem/actioncable@6.1.4.1", + }, + }, + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + PackageSourceInfo: "package found in: Gemfile.lock", + PackageAttributionTexts: []string{ + "PkgType: bundler", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-69cd7625c68537c7"), + PackageDownloadLocation: "NONE", + PackageName: "com.example:example", + PackageVersion: "1.0.0", + PackageLicenseConcluded: "NONE", + PackageLicenseDeclared: "NONE", + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:maven/com.example/example@1.0.0", + }, + }, + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + PackageSourceInfo: "package found in: pom.xml", + PackageAttributionTexts: []string{ + "PkgID: com.example:example:1.0.0", + "PkgType: pom", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Filesystem-5af0f1f08c20909a"), + PackageDownloadLocation: "NONE", + PackageName: "masahiro331/CVE-2021-41098", + PackageAttributionTexts: []string{ + "SchemaVersion: 2", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeSource, + }, + }, + Relationships: []*spdx.Relationship{ + { + RefA: spdx.DocElementID{ElementRefID: "Application-800d9e6e0f88ab3a"}, + RefB: spdx.DocElementID{ElementRefID: "Package-69cd7625c68537c7"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Application-ed046c4a6b4da30f"}, + RefB: spdx.DocElementID{ElementRefID: "Package-e78eaf94802a53dc"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"}, + RefB: spdx.DocElementID{ElementRefID: "Filesystem-5af0f1f08c20909a"}, + Relationship: "DESCRIBES", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Filesystem-5af0f1f08c20909a"}, + RefB: spdx.DocElementID{ElementRefID: "Application-800d9e6e0f88ab3a"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Filesystem-5af0f1f08c20909a"}, + RefB: spdx.DocElementID{ElementRefID: "Application-ed046c4a6b4da30f"}, + Relationship: "CONTAINS", + }, + }, + }, + }, + { + name: "happy path aggregate results", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "http://test-aggregate", + ArtifactType: ftypes.ArtifactRepository, + Results: types.Results{ + { + Target: "Node.js", + Class: types.ClassLangPkg, + Type: ftypes.NodePkg, + Packages: []ftypes.Package{ + { + Name: "ruby-typeprof", + Version: "0.20.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "ruby-typeprof", + Version: "0.20.1", + }, + }, + Licenses: []string{"MIT"}, + Layer: ftypes.Layer{ + DiffID: "sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e", + }, + Digest: "sha256:a5efa82f08774597165e8c1a102d45d0406913b74c184883ac91f409ae26009d", + FilePath: "usr/local/lib/ruby/gems/3.1.0/gems/typeprof-0.21.1/vscode/package.json", + }, + }, + }, + }, + }, + wantSBOM: &spdx.Document{ + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + SPDXIdentifier: "DOCUMENT", + DocumentName: "http://test-aggregate", + DocumentNamespace: "http://aquasecurity.github.io/trivy/repository/test-aggregate-3ff14136-e09f-4df9-80ea-000000000003", + CreationInfo: &spdx.CreationInfo{ + Creators: []common.Creator{ + { + Creator: "aquasecurity", + CreatorType: "Organization", + }, + { + Creator: "trivy-0.38.1", + CreatorType: "Tool", + }, + }, + Created: "2021-08-25T12:20:30Z", + }, + Packages: []*spdx.Package{ + { + PackageSPDXIdentifier: spdx.ElementID("Package-52b8e939bac2d133"), + PackageDownloadLocation: "git+http://test-aggregate", + PackageName: "ruby-typeprof", + PackageVersion: "0.20.1", + PackageLicenseConcluded: "MIT", + PackageLicenseDeclared: "MIT", + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:npm/ruby-typeprof@0.20.1", + }, + }, + PackageAttributionTexts: []string{ + "LayerDiffID: sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e", + "PkgType: node-pkg", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + FilesAnalyzed: true, + PackageVerificationCode: &spdx.PackageVerificationCode{ + Value: "da39a3ee5e6b4b0d3255bfef95601890afd80709", + }, + }, + { + PackageSPDXIdentifier: "Repository-1a78857c1a6a759e", + PackageName: "http://test-aggregate", + PackageDownloadLocation: "git+http://test-aggregate", + PackageAttributionTexts: []string{ + "SchemaVersion: 2", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeSource, + }, + }, + Files: []*spdx.File{ + { + FileName: "usr/local/lib/ruby/gems/3.1.0/gems/typeprof-0.21.1/vscode/package.json", + FileSPDXIdentifier: "File-a52825a3e5bc6dfe", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA256, + Value: "a5efa82f08774597165e8c1a102d45d0406913b74c184883ac91f409ae26009d", + }, + }, + }, + }, + Relationships: []*spdx.Relationship{ + { + RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"}, + RefB: spdx.DocElementID{ElementRefID: "Repository-1a78857c1a6a759e"}, + Relationship: "DESCRIBES", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Package-52b8e939bac2d133"}, + RefB: spdx.DocElementID{ElementRefID: "File-a52825a3e5bc6dfe"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Repository-1a78857c1a6a759e"}, + RefB: spdx.DocElementID{ElementRefID: "Package-52b8e939bac2d133"}, + Relationship: "CONTAINS", + }, + }, + }, + }, + { + name: "happy path empty", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "empty/path", + ArtifactType: ftypes.ArtifactFilesystem, + Results: types.Results{}, + }, + wantSBOM: &spdx.Document{ + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + SPDXIdentifier: "DOCUMENT", + DocumentName: "empty/path", + DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/empty/path-3ff14136-e09f-4df9-80ea-000000000002", + + CreationInfo: &spdx.CreationInfo{ + Creators: []common.Creator{ + { + Creator: "aquasecurity", + CreatorType: "Organization", + }, + { + Creator: "trivy-0.38.1", + CreatorType: "Tool", + }, + }, + Created: "2021-08-25T12:20:30Z", + }, + Packages: []*spdx.Package{ + { + PackageName: "empty/path", + PackageSPDXIdentifier: "Filesystem-70f34983067dba86", + PackageDownloadLocation: "NONE", + PackageAttributionTexts: []string{ + "SchemaVersion: 2", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeSource, + }, + }, + Relationships: []*spdx.Relationship{ + { + RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"}, + RefB: spdx.DocElementID{ElementRefID: "Filesystem-70f34983067dba86"}, + Relationship: "DESCRIBES", + }, + }, + }, + }, + { + name: "happy path secret", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "secret", + ArtifactType: ftypes.ArtifactFilesystem, + Results: types.Results{ + { + Target: "key.pem", + Class: types.ClassSecret, + Secrets: []types.DetectedSecret{ + { + RuleID: "private-key", + Category: "AsymmetricPrivateKey", + Severity: "HIGH", + Title: "Asymmetric Private Key", + StartLine: 1, + EndLine: 1, + }, + }, + }, + }, + }, + wantSBOM: &spdx.Document{ + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + SPDXIdentifier: "DOCUMENT", + DocumentName: "secret", + DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/secret-3ff14136-e09f-4df9-80ea-000000000002", + CreationInfo: &spdx.CreationInfo{ + Creators: []common.Creator{ + { + Creator: "aquasecurity", + CreatorType: "Organization", + }, + { + Creator: "trivy-0.38.1", + CreatorType: "Tool", + }, + }, + Created: "2021-08-25T12:20:30Z", + }, + Packages: []*spdx.Package{ + { + PackageName: "secret", + PackageSPDXIdentifier: "Filesystem-5c08d34162a2c5d3", + PackageDownloadLocation: "NONE", + PackageAttributionTexts: []string{ + "SchemaVersion: 2", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeSource, + }, + }, + Relationships: []*spdx.Relationship{ + { + RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"}, + RefB: spdx.DocElementID{ElementRefID: "Filesystem-5c08d34162a2c5d3"}, + Relationship: "DESCRIBES", + }, + }, + }, + }, + { + name: "go library local", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "go-artifact", + ArtifactType: ftypes.ArtifactFilesystem, + Results: types.Results{ + { + Target: "/usr/local/bin/test", + Class: types.ClassLangPkg, + Type: ftypes.GoBinary, + Packages: []ftypes.Package{ + { + Name: "./private_repos/cnrm.googlesource.com/cnrm/", + Version: "(devel)", + }, + { + Name: "golang.org/x/crypto", + Version: "v0.0.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "golang.org/x", + Name: "crypto", + Version: "v0.0.1", + }, + }, + }, + }, + }, + }, + }, + wantSBOM: &spdx.Document{ + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + SPDXIdentifier: "DOCUMENT", + DocumentName: "go-artifact", + DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/go-artifact-3ff14136-e09f-4df9-80ea-000000000005", + CreationInfo: &spdx.CreationInfo{ + Creators: []common.Creator{ + { + Creator: "aquasecurity", + CreatorType: "Organization", + }, + { + Creator: "trivy-0.38.1", + CreatorType: "Tool", + }, + }, + Created: "2021-08-25T12:20:30Z", + }, + Packages: []*spdx.Package{ + { + PackageSPDXIdentifier: spdx.ElementID("Application-aab0f4e8cf174c67"), + PackageDownloadLocation: "NONE", + PackageName: "/usr/local/bin/test", + PrimaryPackagePurpose: tspdx.PackagePurposeApplication, + PackageAttributionTexts: []string{ + "Class: lang-pkgs", + "Type: gobinary", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-9a16e221e11f8a90"), + PackageDownloadLocation: "NONE", + PackageName: "./private_repos/cnrm.googlesource.com/cnrm/", + PackageVersion: "(devel)", + PackageLicenseConcluded: "NONE", + PackageLicenseDeclared: "NONE", + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + PackageSourceInfo: "package found in: /usr/local/bin/test", + PackageAttributionTexts: []string{ + "PkgType: gobinary", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Package-b9b7ae633941e083"), + PackageDownloadLocation: "NONE", + PackageName: "golang.org/x/crypto", + PackageVersion: "v0.0.1", + PackageLicenseConcluded: "NONE", + PackageLicenseDeclared: "NONE", + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:golang/golang.org/x/crypto@v0.0.1", + }, + }, + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + PackageSourceInfo: "package found in: /usr/local/bin/test", + PackageAttributionTexts: []string{ + "PkgType: gobinary", + }, + }, + { + PackageName: "go-artifact", + PackageSPDXIdentifier: "Filesystem-e340f27468b382be", + PackageDownloadLocation: "NONE", + PackageAttributionTexts: []string{ + "SchemaVersion: 2", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeSource, + }, + }, + Relationships: []*spdx.Relationship{ + { + RefA: spdx.DocElementID{ElementRefID: "Application-aab0f4e8cf174c67"}, + RefB: spdx.DocElementID{ElementRefID: "Package-9a16e221e11f8a90"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Application-aab0f4e8cf174c67"}, + RefB: spdx.DocElementID{ElementRefID: "Package-b9b7ae633941e083"}, + Relationship: "CONTAINS", + }, + { + RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"}, + RefB: spdx.DocElementID{ElementRefID: "Filesystem-e340f27468b382be"}, + Relationship: "DESCRIBES", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Filesystem-e340f27468b382be"}, + RefB: spdx.DocElementID{ElementRefID: "Application-aab0f4e8cf174c67"}, + Relationship: "CONTAINS", + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Fake function calculating the hash value + h := fnv.New64() + hasher := func(v any, format hashstructure.Format, opts *hashstructure.HashOptions) (uint64, error) { + h.Reset() + + var str string + switch vv := v.(type) { + case *core.Component: + str = vv.Name + vv.Version + vv.SrcFile + for _, f := range vv.Files { + str += f.Path + } + case string: + str = vv + default: + require.Failf(t, "unknown type", "%T", v) + } + + if _, err := h.Write([]byte(str)); err != nil { + return 0, err + } + + return h.Sum64(), nil + } + + ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) + uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d") + + marshaler := tspdx.NewMarshaler("0.38.1", tspdx.WithHasher(hasher)) + spdxDoc, err := marshaler.MarshalReport(ctx, tc.inputReport) + require.NoError(t, err) + + assert.Equal(t, tc.wantSBOM, spdxDoc) + }) + } +} + +func Test_GetLicense(t *testing.T) { + tests := []struct { + name string + input []string + want string + }{ + { + name: "happy path", + input: []string{ + "GPLv2+", + }, + want: "GPL-2.0-or-later", + }, + { + name: "happy path with multi license", + input: []string{ + "GPLv2+", + "GPLv3+", + }, + want: "GPL-2.0-or-later AND GPL-3.0-or-later", + }, + { + name: "happy path with OR operator", + input: []string{ + "GPLv2+", + "LGPL 2.0 or GNU LESSER", + }, + want: "GPL-2.0-or-later AND (LGPL-2.0-only OR LGPL-3.0-only)", + }, + { + name: "happy path with AND operator", + input: []string{ + "GPLv2+", + "LGPL 2.0 and GNU LESSER", + }, + want: "GPL-2.0-or-later AND LGPL-2.0-only AND LGPL-3.0-only", + }, + { + name: "happy path with WITH operator", + input: []string{ + "AFL 2.0", + "AFL 3.0 with distribution exception", + }, + want: "AFL-2.0 AND AFL-3.0 WITH distribution-exception", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tspdx.NormalizeLicense(tt.input)) + }) + } +} diff --git a/pkg/sbom/spdx/testdata/happy/bom.json b/pkg/sbom/spdx/testdata/happy/bom.json new file mode 100644 index 000000000000..823c0471351f --- /dev/null +++ b/pkg/sbom/spdx/testdata/happy/bom.json @@ -0,0 +1,230 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2022-09-12T17:02:46.826609Z", + "creators": [ + "Tool: trivy-dev", + "Organization: aquasecurity" + ] + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "http://aquasecurity.github.io/trivy/container/meven-test-project-eb7a0384-b04a-4fc6-8afb-1662fe59ca79", + "name": "maven-test-projecct", + "packages": [ + { + "SPDXID": "SPDXRef-Application-150e605f5f17224d", + "filesAnalyzed": false, + "name": "jar", + "sourceInfo": "Java" + }, + { + "SPDXID": "SPDXRef-Application-24f8a80152e2c0fc", + "filesAnalyzed": false, + "name": "node-pkg", + "sourceInfo": "Node.js" + }, + { + "SPDXID": "SPDXRef-Application-36324ee492e03f0a", + "filesAnalyzed": false, + "name": "gobinary", + "sourceInfo": "app/gobinary/gobinary" + }, + { + "SPDXID": "SPDXRef-Application-4af197c15114fb0e", + "filesAnalyzed": false, + "name": "composer", + "sourceInfo": "app/composer/composer.lock" + }, + { + "SPDXID": "SPDXRef-ContainerImage-b5d81cde5f95c8fc", + "attributionTexts": [ + "SchemaVersion: 2", + "ImageID: sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5", + "DiffID: sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + "DiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + "RepoTag: maven-test-project:latest", + "RepoTag: tmp-test:latest" + ], + "filesAnalyzed": false, + "name": "meven-test-project" + }, + { + "SPDXID": "SPDXRef-OperatingSystem-bd17bf9010aa612c", + "filesAnalyzed": false, + "name": "alpine", + "versionInfo": "3.16.0" + }, + { + "SPDXID": "SPDXRef-Package-2906575950df652b", + "attributionTexts": [ + "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:composer/pear/log@1.13.1", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "name": "pear/log", + "versionInfo": "1.13.1" + }, + { + "SPDXID": "SPDXRef-Package-2a53baa495b9ddaf", + "attributionTexts": [ + "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:maven/org.codehaus.mojo/child-project@1.0", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "name": "org.codehaus.mojo:child-project", + "versionInfo": "1.0" + }, + { + "SPDXID": "SPDXRef-Package-5e2e255ac76747ef", + "attributionTexts": [ + "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:composer/pear/pear_exception@v1.0.0", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "name": "pear/pear_exception", + "versionInfo": "v1.0.0" + }, + { + "SPDXID": "SPDXRef-Package-5f1dbaff8de5eb06", + "attributionTexts": [ + "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:npm/bootstrap@5.0.2", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "name": "bootstrap", + "versionInfo": "5.0.2" + }, + { + "SPDXID": "SPDXRef-Package-84ebffe38343d949", + "attributionTexts": [ + "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "name": "github.com/package-url/packageurl-go", + "versionInfo": "v0.1.1-0.20220203205134-d70459300c8a" + }, + { + "SPDXID": "SPDXRef-Package-b7ebaf0233f1ef7b", + "attributionTexts": [ + "LayerDiffID: sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "name": "musl", + "sourceInfo": "built package from: musl 1.2.3-r0", + "versionInfo": "1.2.3-r0" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-ContainerImage-b5d81cde5f95c8fc", + "relationshipType": "DESCRIBES", + "spdxElementId": "SPDXRef-DOCUMENT" + }, + { + "relatedSpdxElement": "SPDXRef-OperatingSystem-bd17bf9010aa612c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-ContainerImage-b5d81cde5f95c8fc" + }, + { + "relatedSpdxElement": "SPDXRef-Package-b7ebaf0233f1ef7b", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-OperatingSystem-bd17bf9010aa612c" + }, + { + "relatedSpdxElement": "SPDXRef-Application-150e605f5f17224d", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-ContainerImage-b5d81cde5f95c8fc" + }, + { + "relatedSpdxElement": "SPDXRef-Package-2a53baa495b9ddaf", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-Application-150e605f5f17224d" + }, + { + "relatedSpdxElement": "SPDXRef-Application-24f8a80152e2c0fc", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-ContainerImage-b5d81cde5f95c8fc" + }, + { + "relatedSpdxElement": "SPDXRef-Package-5f1dbaff8de5eb06", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-Application-24f8a80152e2c0fc" + }, + { + "relatedSpdxElement": "SPDXRef-Application-4af197c15114fb0e", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-ContainerImage-b5d81cde5f95c8fc" + }, + { + "relatedSpdxElement": "SPDXRef-Package-2906575950df652b", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-Application-4af197c15114fb0e" + }, + { + "relatedSpdxElement": "SPDXRef-Package-5e2e255ac76747ef", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-Application-4af197c15114fb0e" + }, + { + "relatedSpdxElement": "SPDXRef-Application-36324ee492e03f0a", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-ContainerImage-b5d81cde5f95c8fc" + }, + { + "relatedSpdxElement": "SPDXRef-Package-84ebffe38343d949", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-Application-36324ee492e03f0a" + } + ], + "spdxVersion": "SPDX-2.2" +} \ No newline at end of file diff --git a/pkg/sbom/spdx/testdata/happy/empty-bom.json b/pkg/sbom/spdx/testdata/happy/empty-bom.json new file mode 100644 index 000000000000..8835078b8111 --- /dev/null +++ b/pkg/sbom/spdx/testdata/happy/empty-bom.json @@ -0,0 +1,34 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2022-09-12T17:03:35.840861Z", + "creators": [ + "Tool: trivy-dev", + "Organization: aquasecurity" + ] + }, + "dataLicense": "CC0-1.0", + "documentDescribes": [ + "SPDXRef-ContainerImage-7f428bd075b13fe8" + ], + "documentNamespace": "http://aquasecurity.github.io/trivy/container/maven-test-project-3e87878b-eac3-4baa-af11-bdf2c5eab8ea", + "name": "maven-test-project", + "packages": [ + { + "SPDXID": "SPDXRef-ContainerImage-7f428bd075b13fe8", + "attributionTexts": [ + "SchemaVersion: 2" + ], + "filesAnalyzed": false, + "name": "maven-test-project" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-ContainerImage-7f428bd075b13fe8", + "relationshipType": "DESCRIBE", + "spdxElementId": "SPDXRef-DOCUMENT" + } + ], + "spdxVersion": "SPDX-2.2" +} \ No newline at end of file diff --git a/pkg/sbom/spdx/testdata/happy/no-relationship.json b/pkg/sbom/spdx/testdata/happy/no-relationship.json new file mode 100644 index 000000000000..16495c54e1df --- /dev/null +++ b/pkg/sbom/spdx/testdata/happy/no-relationship.json @@ -0,0 +1,97 @@ +{ + "SPDXID": "SPDXRef-elasticsearch", + "spdxVersion": "SPDX-2.3", + "creationInfo": { + "created": "2023-05-17T15:59:30.511Z", + "creators": [ + "Organization: VMware, Inc." + ] + }, + "name": "SPDX document for Elasticsearch 8.7.1", + "dataLicense": "CC0-1.0", + "documentDescribes": [ + "SPDXRef-elasticsearch" + ], + "documentNamespace": "elasticsearch-8.7.1", + "packages": [ + { + "SPDXID": "SPDXRef-elasticsearch", + "name": "Elasticsearch", + "versionInfo": "8.7.1", + "downloadLocation": "https://github.com/elastic/elasticsearch/archive/v8.7.1.tar.gz", + "licenseConcluded": "Elastic-2.0", + "licenseDeclared": "Elastic-2.0", + "filesAnalyzed": false, + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:*:elasticsearch:elasticsearch:8.7.1:*:*:*:*:*:*:*" + } + ] + }, + { + "name": "co.elastic.apm:apm-agent", + "SPDXID": "SPDXRef-Package-d6465ccdd5385c16", + "versionInfo": "1.36.0", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "copyrightText": "", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/co.elastic.apm/apm-agent@1.36.0" + } + ], + "primaryPackagePurpose": "LIBRARY", + "files": [ + { + "fileName":"modules/apm/elastic-apm-agent-1.36.0.jar", + "SPDXID": "SPDXRef-File-4d457bf4ff3526ea", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d2a9ad9b159eb650d25add9395c4f4198f200066" + } + ], + "copyrightText": "" + } + ] + }, + { + "name": "co.elastic.apm:apm-agent-cached-lookup-key", + "SPDXID": "SPDXRef-Package-8e3a2cf58d7bd790", + "versionInfo": "1.36.0", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "copyrightText": "", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0" + } + ], + "primaryPackagePurpose": "LIBRARY", + "files": [ + { + "fileName": "modules/apm/elastic-apm-agent-1.36.0.jar", + "SPDXID": "SPDXRef-File-4d457bf4ff3526ea", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d2a9ad9b159eb650d25add9395c4f4198f200066" + } + ], + "copyrightText": "" + } + ] + } + ], + "files": [] +} \ No newline at end of file diff --git a/pkg/sbom/spdx/testdata/happy/os-only-bom.json b/pkg/sbom/spdx/testdata/happy/os-only-bom.json new file mode 100644 index 000000000000..7cc9425f001a --- /dev/null +++ b/pkg/sbom/spdx/testdata/happy/os-only-bom.json @@ -0,0 +1,42 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2022-09-12T17:04:09.262672Z", + "creators": [ + "Tool: trivy-dev", + "Organization: aquasecurity" + ] + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "http://aquasecurity.github.io/trivy/container/maven-test-project-1a255568-e896-498f-93de-7975a5cb0212", + "name": "maven-test-project", + "packages": [ + { + "SPDXID": "SPDXRef-ContainerImage-fbbee9b988766322", + "attributionTexts": [ + "SchemaVersion: 2" + ], + "filesAnalyzed": false, + "name": "maven-test-project" + }, + { + "SPDXID": "SPDXRef-OperatingSystem-bd17bf9010aa612c", + "filesAnalyzed": false, + "name": "alpine", + "versionInfo": "3.16.0" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-ContainerImage-fbbee9b988766322", + "relationshipType": "DESCRIBE", + "spdxElementId": "SPDXRef-DOCUMENT" + }, + { + "relatedSpdxElement": "SPDXRef-OperatingSystem-bd17bf9010aa612c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-ContainerImage-fbbee9b988766322" + } + ], + "spdxVersion": "SPDX-2.2" +} \ No newline at end of file diff --git a/pkg/sbom/spdx/testdata/happy/unrelated-bom.json b/pkg/sbom/spdx/testdata/happy/unrelated-bom.json new file mode 100644 index 000000000000..2465e65f8b42 --- /dev/null +++ b/pkg/sbom/spdx/testdata/happy/unrelated-bom.json @@ -0,0 +1,82 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2022-09-12T17:04:28.43059Z", + "creators": [ + "Tool: trivy-dev", + "Organization: aquasecurity" + ] + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "http://aquasecurity.github.io/trivy/application/maven-test-project-8aa3b6db-bae7-4755-b43e-00b4bcf46410", + "name": "maven-test-project", + "packages": [ + { + "SPDXID": "SPDXRef-Application-193be12e25033404", + "filesAnalyzed": false, + "name": "composer", + "sourceInfo": "app/composer/composer.lock" + }, + { + "SPDXID": "SPDXRef-Filesystem-12d960c003a8275b", + "attributionTexts": [ + "SchemaVersion: 2" + ], + "filesAnalyzed": false, + "name": "maven-test-project" + }, + { + "SPDXID": "SPDXRef-Package-2906575950df652b", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:composer/pear/log@1.13.1", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "name": "pear/log", + "versionInfo": "1.13.1" + }, + { + "SPDXID": "SPDXRef-Package-5e2e255ac76747ef", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:composer/pear/pear_exception@v1.0.0", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "name": "pear/pear_exception", + "versionInfo": "v1.0.0" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-Filesystem-12d960c003a8275b", + "relationshipType": "DESCRIBE", + "spdxElementId": "SPDXRef-DOCUMENT" + }, + { + "relatedSpdxElement": "SPDXRef-Application-193be12e25033404", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-Filesystem-12d960c003a8275b" + }, + { + "relatedSpdxElement": "SPDXRef-Package-2906575950df652b", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-Application-193be12e25033404" + }, + { + "relatedSpdxElement": "SPDXRef-Package-5e2e255ac76747ef", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-Application-193be12e25033404" + } + ], + "spdxVersion": "SPDX-2.2" +} \ No newline at end of file diff --git a/pkg/sbom/spdx/testdata/happy/with-file-as-relationship-parent.json b/pkg/sbom/spdx/testdata/happy/with-file-as-relationship-parent.json new file mode 100644 index 000000000000..798e75d0a52a --- /dev/null +++ b/pkg/sbom/spdx/testdata/happy/with-file-as-relationship-parent.json @@ -0,0 +1,54 @@ +{ + "files": [ + { + "fileName": "./Modules/Microsoft.PowerShell.PSResourceGet/_manifest/spdx_2.2/manifest.spdx.json", + "SPDXID": "SPDXRef-File--Modules-Microsoft.PowerShell.PSResourceGet--manifest-spdx-2.2-manifest.spdx.json-2B9FB98F5CA97DC84FD382A8F8E68F663C003362", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "4201b0989938842ef8c11a006184e0b1466bd7f9bb2af61d89a4c8318d43466e" + }, + { + "algorithm": "SHA1", + "checksumValue": "2b9fb98f5ca97dc84fd382a8f8e68f663c003362" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION", + "fileTypes": [ + "SPDX" + ] + } + ], + "externalDocumentRefs": [], + "relationships": [ + { + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-RootPackage", + "spdxElementId": "SPDXRef-DOCUMENT" + }, + { + "relationshipType": "DESCRIBED_BY", + "relatedSpdxElement": "SPDXRef-DOCUMENT", + "spdxElementId": "SPDXRef-File--Modules-Microsoft.PowerShell.PSResourceGet--manifest-spdx-2.2-manifest.spdx.json-2B9FB98F5CA97DC84FD382A8F8E68F663C003362" + } + ], + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "PowerShell Linux Arm32 7.5.0-preview.2", + "documentNamespace": "https://sbom.microsoft/1:2QSF7qZlbE-F7QrUJlEo7g:pHp_nUFvDUijZ4LrJ4RhoQ/696:458654/PowerShell%20Linux%20Arm32:7.5.0-preview.2:pDkyTHXmgUOdzSXIq9CiqA", + "creationInfo": { + "created": "2024-02-22T00:43:53Z", + "creators": [ + "Organization: Microsoft", + "Tool: Microsoft.SBOMTool-2.2.3" + ] + }, + "documentDescribes": [ + "SPDXRef-RootPackage" + ] +} \ No newline at end of file diff --git a/pkg/sbom/spdx/testdata/happy/with-files-in-relationships-bom.json b/pkg/sbom/spdx/testdata/happy/with-files-in-relationships-bom.json new file mode 100644 index 000000000000..577bff237946 --- /dev/null +++ b/pkg/sbom/spdx/testdata/happy/with-files-in-relationships-bom.json @@ -0,0 +1,91 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "app", + "documentNamespace": "http://aquasecurity.github.io/trivy/filesystem/app-8e571278-2221-4dcd-bc56-0b256210fa91", + "creationInfo": { + "licenseListVersion": "", + "creators": [ + "Organization: aquasecurity", + "Tool: trivy-dev" + ], + "created": "2023-05-31T05:58:45Z" + }, + "packages": [ + { + "name": "app", + "SPDXID": "SPDXRef-Filesystem-13b142ca391a006e", + "downloadLocation": "NONE", + "copyrightText": "", + "attributionTexts": [ + "SchemaVersion: 2" + ], + "primaryPackagePurpose": "SOURCE" + }, + { + "name": "node-pkg", + "SPDXID": "SPDXRef-Application-24f8a80152e2c0fc", + "downloadLocation": "NONE", + "sourceInfo": "Node.js", + "copyrightText": "", + "primaryPackagePurpose": "APPLICATION" + }, + { + "name": "yargs-parser", + "SPDXID": "SPDXRef-Package-c3508825bf3861d8", + "versionInfo": "21.1.1", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "licenseConcluded": "ISC", + "licenseDeclared": "ISC", + "copyrightText": "", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/yargs-parser@21.1.1" + } + ], + "attributionTexts": [ + "PkgID: yargs-parser@21.1.1" + ], + "primaryPackagePurpose": "LIBRARY" + } + ], + "files": [ + { + "fileName": "node_modules/yargs-parser/package.json", + "SPDXID": "SPDXRef-File-51bb5f929ef68877", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "69e70ec702f9df4ff64024b5fdea4644f1ce6c97" + } + ], + "copyrightText": "" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-Filesystem-13b142ca391a006e", + "relationshipType": "DESCRIBES" + }, + { + "spdxElementId": "SPDXRef-Filesystem-13b142ca391a006e", + "relatedSpdxElement": "SPDXRef-Application-24f8a80152e2c0fc", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Application-24f8a80152e2c0fc", + "relatedSpdxElement": "SPDXRef-Package-c3508825bf3861d8", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Package-c3508825bf3861d8", + "relatedSpdxElement": "SPDXRef-File-51bb5f929ef68877", + "relationshipType": "CONTAINS" + } + ] +} \ No newline at end of file diff --git a/pkg/sbom/spdx/testdata/happy/with-hasfiles-bom.json b/pkg/sbom/spdx/testdata/happy/with-hasfiles-bom.json new file mode 100644 index 000000000000..c0d836442405 --- /dev/null +++ b/pkg/sbom/spdx/testdata/happy/with-hasfiles-bom.json @@ -0,0 +1,86 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2023-05-31T04:56:43Z", + "creators": [ + "Tool: trivy-dev", + "Organization: aquasecurity" + ] + }, + "dataLicense": "CC0-1.0", + "documentDescribes": [ + "SPDXRef-Filesystem-13b142ca391a006e" + ], + "documentNamespace": "http://aquasecurity.github.io/trivy/filesystem/app-ab9e166e-7229-4d03-947e-8ffc9c60cf6f", + "files": [ + { + "SPDXID": "SPDXRef-File-51bb5f929ef68877", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "69e70ec702f9df4ff64024b5fdea4644f1ce6c97" + } + ], + "fileName": "node_modules/yargs-parser/package.json" + } + ], + "name": "app", + "packages": [ + { + "SPDXID": "SPDXRef-Application-24f8a80152e2c0fc", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "name": "node-pkg", + "sourceInfo": "Node.js" + }, + { + "SPDXID": "SPDXRef-Filesystem-13b142ca391a006e", + "attributionTexts": [ + "SchemaVersion: 2" + ], + "downloadLocation": "NONE", + "filesAnalyzed": false, + "name": "app" + }, + { + "SPDXID": "SPDXRef-Package-c3508825bf3861d8", + "attributionTexts": [ + "PkgID: yargs-parser@21.1.1" + ], + "downloadLocation": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:npm/yargs-parser@21.1.1", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "hasFiles": [ + "SPDXRef-File-51bb5f929ef68877" + ], + "licenseConcluded": "ISC", + "licenseDeclared": "ISC", + "name": "yargs-parser", + "versionInfo": "21.1.1" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-Filesystem-13b142ca391a006e", + "relationshipType": "DESCRIBES", + "spdxElementId": "SPDXRef-DOCUMENT" + }, + { + "relatedSpdxElement": "SPDXRef-Application-24f8a80152e2c0fc", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-Filesystem-13b142ca391a006e" + }, + { + "relatedSpdxElement": "SPDXRef-Package-c3508825bf3861d8", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-Application-24f8a80152e2c0fc" + } + ], + "spdxVersion": "SPDX-2.2" +} \ No newline at end of file diff --git a/pkg/sbom/spdx/testdata/sad/invalid-purl.json b/pkg/sbom/spdx/testdata/sad/invalid-purl.json new file mode 100644 index 000000000000..da87237d54b7 --- /dev/null +++ b/pkg/sbom/spdx/testdata/sad/invalid-purl.json @@ -0,0 +1,58 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2022-09-12T17:02:46.826609Z", + "creators": [ + "Tool: trivy-dev", + "Organization: aquasecurity" + ] + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "http://aquasecurity.github.io/trivy/container/meven-test-project-eb7a0384-b04a-4fc6-8afb-1662fe59ca79", + "name": "maven-test-projecct", + "packages": [ + { + "SPDXID": "SPDXRef-ContainerImage-b5d81cde5f95c8fc", + "filesAnalyzed": false, + "name": "meven-test-project" + }, + { + "SPDXID": "SPDXRef-OperatingSystem-bd17bf9010aa612c", + "filesAnalyzed": false, + "name": "alpine", + "versionInfo": "3.16.0" + }, + { + "SPDXID": "SPDXRef-Package-b7ebaf0233f1ef7b", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:invalid", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "name": "musl", + "sourceInfo": "built package from: musl", + "versionInfo": "1.2.3-r0" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-ContainerImage-b5d81cde5f95c8fc", + "relationshipType": "DESCRIBE", + "spdxElementId": "SPDXRef-DOCUMENT" + }, + { + "relatedSpdxElement": "SPDXRef-OperatingSystem-bd17bf9010aa612c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-ContainerImage-b5d81cde5f95c8fc" + }, + { + "relatedSpdxElement": "SPDXRef-Package-b7ebaf0233f1ef7b", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-OperatingSystem-bd17bf9010aa612c" + } + ], + "spdxVersion": "SPDX-2.2" +} \ No newline at end of file diff --git a/pkg/sbom/spdx/unmarshal.go b/pkg/sbom/spdx/unmarshal.go new file mode 100644 index 000000000000..bda18c16980a --- /dev/null +++ b/pkg/sbom/spdx/unmarshal.go @@ -0,0 +1,280 @@ +package spdx + +import ( + "bytes" + "fmt" + "io" + "strings" + + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "github.com/spdx/tools-golang/json" + "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/spdx/v2/common" + "github.com/spdx/tools-golang/tagvalue" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/sbom/core" +) + +type SPDX struct { + *core.BOM + + trivySBOM bool + pkgFilePaths map[common.ElementID]string +} + +func NewTVDecoder(r io.Reader) *TVDecoder { + return &TVDecoder{r: r} +} + +type TVDecoder struct { + r io.Reader +} + +func (tv *TVDecoder) Decode(v interface{}) error { + spdxDocument, err := tagvalue.Read(tv.r) + if err != nil { + return xerrors.Errorf("failed to load tag-value spdx: %w", err) + } + + a, ok := v.(*SPDX) + if !ok { + return xerrors.Errorf("invalid struct type tag-value decoder needed SPDX struct") + } + if err = a.unmarshal(spdxDocument); err != nil { + return xerrors.Errorf("failed to unmarshal spdx: %w", err) + } + + return nil +} + +func (s *SPDX) UnmarshalJSON(b []byte) error { + if s.BOM == nil { + s.BOM = core.NewBOM(core.Options{}) + } + if s.pkgFilePaths == nil { + s.pkgFilePaths = make(map[common.ElementID]string) + } + + spdxDocument, err := json.Read(bytes.NewReader(b)) + if err != nil { + return xerrors.Errorf("failed to load spdx json: %w", err) + } + + if err = s.unmarshal(spdxDocument); err != nil { + return xerrors.Errorf("failed to unmarshal spdx: %w", err) + } + return nil +} + +func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error { + s.trivySBOM = s.isTrivySBOM(spdxDocument) + + // Parse files and find file paths for packages + s.parseFiles(spdxDocument) + + // Convert all SPDX packages into Trivy components + components, err := s.parsePackages(spdxDocument) + if err != nil { + return xerrors.Errorf("package parse error: %w", err) + } + + // Parse relationships and build the dependency graph + for _, rel := range spdxDocument.Relationships { + // Skip the DESCRIBES relationship. + if rel.Relationship == common.TypeRelationshipDescribe || rel.Relationship == "DESCRIBE" { + continue + } + + compA, ok := components[rel.RefA.ElementRefID] + if !ok { // Skip if parent is not Package + continue + } + + compB, ok := components[rel.RefB.ElementRefID] + if !ok { // Skip if child is not Package + continue + } + + s.BOM.AddRelationship(compA, compB, s.parseRelationshipType(rel.Relationship)) + } + + return nil +} + +// parseFiles parses Relationships and finds filepaths for packages +func (s *SPDX) parseFiles(spdxDocument *spdx.Document) { + fileSPDXIdentifierMap := lo.SliceToMap(spdxDocument.Files, func(file *spdx.File) (common.ElementID, *spdx.File) { + return file.FileSPDXIdentifier, file + }) + + for _, rel := range spdxDocument.Relationships { + if rel.Relationship != common.TypeRelationshipContains && rel.Relationship != "CONTAIN" { + // Skip the DESCRIBES relationship. + continue + } + + // hasFiles field is deprecated + // https://github.com/spdx/tools-golang/issues/171 + // hasFiles values converted in Relationships + // https://github.com/spdx/tools-golang/pull/201 + if isFile(rel.RefB.ElementRefID) { + file, ok := fileSPDXIdentifierMap[rel.RefB.ElementRefID] + if ok { + // Save filePaths for packages + // Insert filepath will be later + s.pkgFilePaths[rel.RefA.ElementRefID] = file.FileName + } + continue + } + } +} + +func (s *SPDX) parsePackages(spdxDocument *spdx.Document) (map[common.ElementID]*core.Component, error) { + // Find a root package + var rootID common.ElementID + for _, rel := range spdxDocument.Relationships { + if rel.RefA.ElementRefID == DocumentSPDXIdentifier && rel.Relationship == RelationShipDescribe { + rootID = rel.RefB.ElementRefID + break + } + } + + // Convert packages into components + components := make(map[common.ElementID]*core.Component) + for _, pkg := range spdxDocument.Packages { + component, err := s.parsePackage(*pkg) + if err != nil { + return nil, xerrors.Errorf("failed to parse package: %w", err) + } + components[pkg.PackageSPDXIdentifier] = component + + if pkg.PackageSPDXIdentifier == rootID { + component.Root = true + } + s.BOM.AddComponent(component) + } + return components, nil +} + +func (s *SPDX) parsePackage(spdxPkg spdx.Package) (*core.Component, error) { + var err error + component := &core.Component{ + Type: s.parseType(spdxPkg), + Name: spdxPkg.PackageName, + Version: spdxPkg.PackageVersion, + } + + // PURL + if component.PkgID.PURL, err = s.parseExternalReferences(spdxPkg.PackageExternalReferences); err != nil { + return nil, xerrors.Errorf("external references error: %w", err) + } + + // License + if spdxPkg.PackageLicenseDeclared != "NONE" { + component.Licenses = strings.Split(spdxPkg.PackageLicenseDeclared, ",") + } + + // Source package + if strings.HasPrefix(spdxPkg.PackageSourceInfo, SourcePackagePrefix) { + srcPkgName := strings.TrimPrefix(spdxPkg.PackageSourceInfo, fmt.Sprintf("%s: ", SourcePackagePrefix)) + component.SrcName, component.SrcVersion, _ = strings.Cut(srcPkgName, " ") + } + + // Files + // TODO: handle checksums as well + if path, ok := s.pkgFilePaths[spdxPkg.PackageSPDXIdentifier]; ok { + component.Files = []core.File{ + {Path: path}, + } + } else if len(spdxPkg.Files) > 0 { + component.Files = []core.File{ + {Path: spdxPkg.Files[0].FileName}, // Take the first file name + } + } + + // Attributions + for _, attr := range spdxPkg.PackageAttributionTexts { + k, v, ok := strings.Cut(attr, ": ") + if !ok { + continue + } + component.Properties = append(component.Properties, core.Property{ + Name: k, + Value: v, + }) + } + + // For backward-compatibility + // Older Trivy versions put the file path in "sourceInfo" and the package type in "name". + if s.trivySBOM && component.Type == core.TypeApplication && spdxPkg.PackageSourceInfo != "" { + component.Name = spdxPkg.PackageSourceInfo + component.Properties = append(component.Properties, core.Property{ + Name: core.PropertyType, + Value: spdxPkg.PackageName, + }) + } + + return component, nil +} + +func (s *SPDX) parseType(pkg spdx.Package) core.ComponentType { + id := string(pkg.PackageSPDXIdentifier) + switch { + case strings.HasPrefix(id, ElementOperatingSystem): + return core.TypeOS + case strings.HasPrefix(id, ElementApplication): + return core.TypeApplication + case strings.HasPrefix(id, ElementPackage): + return core.TypeLibrary + default: + return core.TypeLibrary // unknown is handled as a library + } +} + +func (s *SPDX) parseRelationshipType(rel string) core.RelationshipType { + switch rel { + case common.TypeRelationshipDescribe: + return core.RelationshipDescribes + case common.TypeRelationshipContains, "CONTAIN": + return core.RelationshipContains + case common.TypeRelationshipDependsOn: + return core.RelationshipDependsOn + default: + return core.RelationshipContains + } +} + +func (s *SPDX) parseExternalReferences(refs []*spdx.PackageExternalReference) (*packageurl.PackageURL, error) { + for _, ref := range refs { + // Extract the package information from PURL + if ref.RefType != RefTypePurl || ref.Category != CategoryPackageManager { + continue + } + + packageURL, err := packageurl.FromString(ref.Locator) + if err != nil { + return nil, xerrors.Errorf("failed to parse purl from string: %w", err) + } + return &packageURL, nil + } + return nil, nil +} + +func (s *SPDX) isTrivySBOM(spdxDocument *spdx.Document) bool { + if spdxDocument == nil || spdxDocument.CreationInfo == nil || spdxDocument.CreationInfo.Creators == nil { + return false + } + + for _, c := range spdxDocument.CreationInfo.Creators { + if c.CreatorType == "Tool" && strings.HasPrefix(c.Creator, "trivy") { + return true + } + } + return false +} + +func isFile(elementID spdx.ElementID) bool { + return strings.HasPrefix(string(elementID), ElementFile) +} diff --git a/pkg/sbom/spdx/unmarshal_test.go b/pkg/sbom/spdx/unmarshal_test.go new file mode 100644 index 000000000000..73f7d2dc934f --- /dev/null +++ b/pkg/sbom/spdx/unmarshal_test.go @@ -0,0 +1,372 @@ +package spdx_test + +import ( + "encoding/json" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" + "github.com/package-url/packageurl-go" + "os" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/sbom/spdx" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestUnmarshaler_Unmarshal(t *testing.T) { + tests := []struct { + name string + inputFile string + want types.SBOM + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/happy/bom.json", + want: types.SBOM{ + Metadata: types.Metadata{ + ImageID: "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5", + DiffIDs: []string{ + "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + RepoTags: []string{ + "maven-test-project:latest", + "tmp-test:latest", + }, + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.16.0", + }, + }, + Packages: []ftypes.PackageInfo{ + { + Packages: ftypes.Packages{ + { + ID: "musl@1.2.3-r0", + Name: "musl", + Version: "1.2.3-r0", + SrcName: "musl", + SrcVersion: "1.2.3-r0", + Licenses: []string{"MIT"}, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeApk, + Namespace: "alpine", + Name: "musl", + Version: "1.2.3-r0", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.16.0", + }, + }, + }, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + }, + }, + }, + }, + Applications: []ftypes.Application{ + { + Type: "composer", + FilePath: "app/composer/composer.lock", + Libraries: ftypes.Packages{ + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "log", + Version: "1.13.1", + }, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + { + ID: "pear/pear_exception@v1.0.0", + Name: "pear/pear_exception", + Version: "v1.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "pear_exception", + Version: "v1.0.0", + }, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + }, + }, + { + Type: "gobinary", + FilePath: "app/gobinary/gobinary", + Libraries: ftypes.Packages{ + { + ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", + Name: "github.com/package-url/packageurl-go", + Version: "v0.1.1-0.20220203205134-d70459300c8a", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/package-url", + Name: "packageurl-go", + Version: "v0.1.1-0.20220203205134-d70459300c8a", + }, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + }, + }, + { + Type: "jar", + Libraries: ftypes.Packages{ + { + ID: "org.codehaus.mojo:child-project:1.0", + Name: "org.codehaus.mojo:child-project", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.codehaus.mojo", + Name: "child-project", + Version: "1.0", + }, + }, + Version: "1.0", + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + }, + }, + { + Type: "node-pkg", + Libraries: ftypes.Packages{ + { + ID: "bootstrap@5.0.2", + Name: "bootstrap", + Version: "5.0.2", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "bootstrap", + Version: "5.0.2", + }, + }, + Licenses: []string{"MIT"}, + Layer: ftypes.Layer{ + DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path for bom with hasFiles field", + inputFile: "testdata/happy/with-hasfiles-bom.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: ftypes.NodePkg, + Libraries: ftypes.Packages{ + { + ID: "yargs-parser@21.1.1", + Name: "yargs-parser", + Version: "21.1.1", + Licenses: []string{"ISC"}, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "yargs-parser", + Version: "21.1.1", + }, + }, + FilePath: "node_modules/yargs-parser/package.json", + }, + }, + }, + }, + }, + }, + { + name: "happy path for bom files in relationships", + inputFile: "testdata/happy/with-files-in-relationships-bom.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: "node-pkg", + Libraries: ftypes.Packages{ + { + ID: "yargs-parser@21.1.1", + Name: "yargs-parser", + Version: "21.1.1", + Licenses: []string{"ISC"}, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "yargs-parser", + Version: "21.1.1", + }, + }, + FilePath: "node_modules/yargs-parser/package.json", + }, + }, + }, + }, + }, + }, + { + name: "happy path for unrelated bom", + inputFile: "testdata/happy/unrelated-bom.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: "composer", + FilePath: "app/composer/composer.lock", + Libraries: ftypes.Packages{ + { + ID: "pear/log@1.13.1", + Name: "pear/log", + Version: "1.13.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "log", + Version: "1.13.1", + }, + }, + }, + { + ID: "pear/pear_exception@v1.0.0", + Name: "pear/pear_exception", + Version: "v1.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "pear_exception", + Version: "v1.0.0", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with no relationship", + inputFile: "testdata/happy/no-relationship.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: ftypes.Jar, + Libraries: ftypes.Packages{ + { + ID: "co.elastic.apm:apm-agent:1.36.0", + Name: "co.elastic.apm:apm-agent", + Version: "1.36.0", + FilePath: "modules/apm/elastic-apm-agent-1.36.0.jar", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "co.elastic.apm", + Name: "apm-agent", + Version: "1.36.0", + }, + }, + }, + { + ID: "co.elastic.apm:apm-agent-cached-lookup-key:1.36.0", + Name: "co.elastic.apm:apm-agent-cached-lookup-key", + Version: "1.36.0", + FilePath: "modules/apm/elastic-apm-agent-1.36.0.jar", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "co.elastic.apm", + Name: "apm-agent-cached-lookup-key", + Version: "1.36.0", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with file as parent of relationship", + inputFile: "testdata/happy/with-file-as-relationship-parent.json", + want: types.SBOM{}, + }, + { + name: "happy path only os component", + inputFile: "testdata/happy/os-only-bom.json", + want: types.SBOM{ + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.16.0", + }, + }, + }, + }, + { + name: "happy path empty component", + inputFile: "testdata/happy/empty-bom.json", + want: types.SBOM{}, + }, + { + name: "sad path invalid purl", + inputFile: "testdata/sad/invalid-purl.json", + wantErr: "purl is missing type or name", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + var v spdx.SPDX + err = json.NewDecoder(f).Decode(&v) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + var got types.SBOM + err = sbomio.NewDecoder(v.BOM).Decode(&got) + require.NoError(t, err) + + // Not compare BOM + got.BOM = nil + + sort.Slice(got.Applications, func(i, j int) bool { + return got.Applications[i].Type < got.Applications[j].Type + }) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/scanner/langpkg/scan.go b/pkg/scanner/langpkg/scan.go new file mode 100644 index 000000000000..9480c8a13614 --- /dev/null +++ b/pkg/scanner/langpkg/scan.go @@ -0,0 +1,100 @@ +package langpkg + +import ( + "sort" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/detector/library" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + PkgTargets = map[ftypes.LangType]string{ + ftypes.PythonPkg: "Python", + ftypes.CondaPkg: "Conda", + ftypes.GemSpec: "Ruby", + ftypes.NodePkg: "Node.js", + ftypes.Jar: "Java", + ftypes.K8sUpstream: "Kubernetes", + } +) + +type Scanner interface { + Packages(target types.ScanTarget, options types.ScanOptions) types.Results + Scan(target types.ScanTarget, options types.ScanOptions) (types.Results, error) +} + +type scanner struct{} + +func NewScanner() Scanner { + return &scanner{} +} + +func (s *scanner) Packages(target types.ScanTarget, _ types.ScanOptions) types.Results { + var results types.Results + for _, app := range target.Applications { + if len(app.Libraries) == 0 { + continue + } + + results = append(results, types.Result{ + Target: targetName(app.Type, app.FilePath), + Class: types.ClassLangPkg, + Type: app.Type, + Packages: app.Libraries, + }) + } + return results +} + +func (s *scanner) Scan(target types.ScanTarget, _ types.ScanOptions) (types.Results, error) { + apps := target.Applications + log.Logger.Infof("Number of language-specific files: %d", len(apps)) + if len(apps) == 0 { + return nil, nil + } + + var results types.Results + printedTypes := make(map[ftypes.LangType]struct{}) + for _, app := range apps { + if len(app.Libraries) == 0 { + continue + } + + // Prevent the same log messages from being displayed many times for the same type. + if _, ok := printedTypes[app.Type]; !ok { + log.Logger.Infof("Detecting %s vulnerabilities...", app.Type) + printedTypes[app.Type] = struct{}{} + } + + log.Logger.Debugf("Detecting library vulnerabilities, type: %s, path: %s", app.Type, app.FilePath) + vulns, err := library.Detect(app.Type, app.Libraries) + if err != nil { + return nil, xerrors.Errorf("failed vulnerability detection of libraries: %w", err) + } else if len(vulns) == 0 { + continue + } + + results = append(results, types.Result{ + Target: targetName(app.Type, app.FilePath), + Vulnerabilities: vulns, + Class: types.ClassLangPkg, + Type: app.Type, + }) + } + sort.Slice(results, func(i, j int) bool { + return results[i].Target < results[j].Target + }) + return results, nil +} + +func targetName(appType ftypes.LangType, filePath string) string { + if t, ok := PkgTargets[appType]; ok && filePath == "" { + // When the file path is empty, we will overwrite it with the pre-defined value. + return t + } + return filePath +} diff --git a/pkg/scanner/library/bundler/advisory.go b/pkg/scanner/library/bundler/advisory.go deleted file mode 100644 index b93cae358272..000000000000 --- a/pkg/scanner/library/bundler/advisory.go +++ /dev/null @@ -1,122 +0,0 @@ -package bundler - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/etcd-io/bbolt" - - "github.com/knqyf263/trivy/pkg/db" - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/git" - "github.com/knqyf263/trivy/pkg/utils" - "gopkg.in/yaml.v2" -) - -const ( - dbURL = "https://github.com/rubysec/ruby-advisory-db.git" -) - -var ( - repoPath = filepath.Join(utils.CacheDir(), "ruby-advisory-db") -) - -type AdvisoryDB map[string][]Advisory - -type Advisory struct { - Gem string - Cve string - Osvdb string - Title string - Url string - Description string - CvssV2 float64 `yaml:"cvss_v2"` - CvssV3 float64 `yaml:"cvss_v3"` - PatchedVersions []string `yaml:"patched_versions"` - UnaffectedVersions []string `yaml:"unaffected_versions"` - Related Related -} - -type Related struct { - Cve []string - Url []string -} - -func (s *Scanner) UpdateDB() (err error) { - if _, err := git.CloneOrPull(dbURL, repoPath); err != nil { - return xerrors.Errorf("error in %s security DB update: %w", s.Type(), err) - } - s.db, err = s.walk() - return err -} - -func (s *Scanner) walk() (AdvisoryDB, error) { - advisoryDB := AdvisoryDB{} - root := filepath.Join(repoPath, "gems") - - var vulns []vulnerability.Vulnerability - err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if info.IsDir() { - return nil - } - buf, err := ioutil.ReadFile(path) - if err != nil { - return xerrors.Errorf("failed to read a file: %w", err) - } - - advisory := Advisory{} - err = yaml.Unmarshal(buf, &advisory) - if err != nil { - return xerrors.Errorf("failed to unmarshal YAML: %w", err) - } - - // for detecting vulnerabilities - advisories, ok := advisoryDB[advisory.Gem] - if !ok { - advisories = []Advisory{} - } - advisoryDB[advisory.Gem] = append(advisories, advisory) - - // for displaying vulnerability detail - var vulnerabilityID string - if advisory.Cve != "" { - vulnerabilityID = fmt.Sprintf("CVE-%s", advisory.Cve) - } else if advisory.Osvdb != "" { - vulnerabilityID = fmt.Sprintf("OSVDB-%s", advisory.Osvdb) - } - vulns = append(vulns, vulnerability.Vulnerability{ - ID: vulnerabilityID, - CvssScore: advisory.CvssV2, - CvssScoreV3: advisory.CvssV3, - References: append([]string{advisory.Url}, advisory.Related.Url...), - Title: advisory.Title, - Description: advisory.Description, - }) - - return nil - }) - if err != nil { - return nil, xerrors.Errorf("error in file walk: %w", err) - } - - if err = s.saveVulnerabilities(vulns); err != nil { - return nil, err - } - return advisoryDB, nil -} - -func (s *Scanner) saveVulnerabilities(vulns []vulnerability.Vulnerability) error { - return vulnerability.BatchUpdate(func(b *bbolt.Bucket) error { - for _, vuln := range vulns { - if err := db.Put(b, vuln.ID, vulnerability.RubySec, vuln); err != nil { - return xerrors.Errorf("failed to save %s vulnerability: %w", s.Type(), err) - } - } - return nil - }) -} diff --git a/pkg/scanner/library/bundler/scan.go b/pkg/scanner/library/bundler/scan.go deleted file mode 100644 index 1f7265056048..000000000000 --- a/pkg/scanner/library/bundler/scan.go +++ /dev/null @@ -1,67 +0,0 @@ -package bundler - -import ( - "fmt" - "os" - "strings" - - "github.com/knqyf263/go-dep-parser/pkg/bundler" - ptypes "github.com/knqyf263/go-dep-parser/pkg/types" - "github.com/knqyf263/go-version" - "github.com/knqyf263/trivy/pkg/scanner/utils" - "github.com/knqyf263/trivy/pkg/types" - "golang.org/x/xerrors" -) - -const ( - scannerType = "bundler" -) - -type Scanner struct { - db AdvisoryDB -} - -func NewScanner() *Scanner { - return &Scanner{} -} - -func (s *Scanner) Detect(pkgName string, pkgVer *version.Version) ([]types.Vulnerability, error) { - var vulns []types.Vulnerability - for _, advisory := range s.db[pkgName] { - if utils.MatchVersions(pkgVer, advisory.PatchedVersions) { - continue - } - if utils.MatchVersions(pkgVer, advisory.UnaffectedVersions) { - continue - } - - var vulnerabilityID string - if advisory.Cve != "" { - vulnerabilityID = fmt.Sprintf("CVE-%s", advisory.Cve) - } else if advisory.Osvdb != "" { - vulnerabilityID = fmt.Sprintf("OSVDB-%s", advisory.Osvdb) - } - - vuln := types.Vulnerability{ - VulnerabilityID: vulnerabilityID, - PkgName: strings.TrimSpace(advisory.Gem), - Title: strings.TrimSpace(advisory.Title), - InstalledVersion: pkgVer.String(), - FixedVersion: strings.Join(advisory.PatchedVersions, ", "), - } - vulns = append(vulns, vuln) - } - return vulns, nil -} - -func (s *Scanner) ParseLockfile(f *os.File) ([]ptypes.Library, error) { - libs, err := bundler.Parse(f) - if err != nil { - return nil, xerrors.Errorf("invalid Gemfile.lock format: %w", err) - } - return libs, nil -} - -func (s *Scanner) Type() string { - return scannerType -} diff --git a/pkg/scanner/library/composer/advisory.go b/pkg/scanner/library/composer/advisory.go deleted file mode 100644 index 39047942a42c..000000000000 --- a/pkg/scanner/library/composer/advisory.go +++ /dev/null @@ -1,103 +0,0 @@ -package composer - -import ( - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/etcd-io/bbolt" - - "github.com/knqyf263/trivy/pkg/db" - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/utils" - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "github.com/knqyf263/trivy/pkg/git" - "gopkg.in/yaml.v2" -) - -const ( - dbURL = "https://github.com/FriendsOfPHP/security-advisories" -) - -var ( - repoPath = filepath.Join(utils.CacheDir(), "php-security-advisories") -) - -type AdvisoryDB map[string][]Advisory - -type Advisory struct { - Cve string - Title string - Link string - Reference string - Branches map[string]Branch -} - -type Branch struct { - Versions []string -} - -func (s *Scanner) UpdateDB() (err error) { - if _, err := git.CloneOrPull(dbURL, repoPath); err != nil { - return err - } - s.db, err = s.walk() - return err -} - -func (s *Scanner) walk() (AdvisoryDB, error) { - advisoryDB := AdvisoryDB{} - var vulns []vulnerability.Vulnerability - err := filepath.Walk(repoPath, func(path string, info os.FileInfo, err error) error { - if info.IsDir() || !strings.HasPrefix(info.Name(), "CVE-") { - return nil - } - buf, err := ioutil.ReadFile(path) - if err != nil { - return err - } - - advisory := Advisory{} - err = yaml.Unmarshal(buf, &advisory) - if err != nil { - return err - } - - // for detecting vulnerabilities - advisories, ok := advisoryDB[advisory.Reference] - if !ok { - advisories = []Advisory{} - } - advisoryDB[advisory.Reference] = append(advisories, advisory) - - // for displaying vulnerability detail - vulns = append(vulns, vulnerability.Vulnerability{ - ID: advisory.Cve, - References: []string{advisory.Link}, - Title: advisory.Title, - }) - - return nil - }) - if err != nil { - return nil, xerrors.Errorf("error in file walk: %w", err) - } - if err = s.saveVulnerabilities(vulns); err != nil { - return nil, err - } - return advisoryDB, nil -} - -func (s Scanner) saveVulnerabilities(vulns []vulnerability.Vulnerability) error { - return vulnerability.BatchUpdate(func(b *bbolt.Bucket) error { - for _, vuln := range vulns { - if err := db.Put(b, vuln.ID, vulnerability.PhpSecurityAdvisories, vuln); err != nil { - return xerrors.Errorf("failed to save %s vulnerability: %w", s.Type(), err) - } - } - return nil - }) -} diff --git a/pkg/scanner/library/composer/scan.go b/pkg/scanner/library/composer/scan.go deleted file mode 100644 index 9658c4f78f84..000000000000 --- a/pkg/scanner/library/composer/scan.go +++ /dev/null @@ -1,69 +0,0 @@ -package composer - -import ( - "fmt" - "os" - "strings" - - "golang.org/x/xerrors" - - "github.com/knqyf263/go-dep-parser/pkg/composer" - ptypes "github.com/knqyf263/go-dep-parser/pkg/types" - "github.com/knqyf263/go-version" - "github.com/knqyf263/trivy/pkg/scanner/utils" - "github.com/knqyf263/trivy/pkg/types" -) - -const ( - scannerType = "composer" -) - -type Scanner struct { - db AdvisoryDB -} - -func NewScanner() *Scanner { - return &Scanner{} -} - -func (s *Scanner) Detect(pkgName string, pkgVer *version.Version) ([]types.Vulnerability, error) { - var vulns []types.Vulnerability - ref := fmt.Sprintf("composer://%s", pkgName) - for _, advisory := range s.db[ref] { - var affectedVersions []string - var patchedVersions []string - for _, branch := range advisory.Branches { - for _, version := range branch.Versions { - if !strings.HasPrefix(version, "<=") && strings.HasPrefix(version, "<") { - patchedVersions = append(patchedVersions, strings.Trim(version, "<")) - } - } - affectedVersions = append(affectedVersions, strings.Join(branch.Versions, ", ")) - } - - if !utils.MatchVersions(pkgVer, affectedVersions) { - continue - } - - vuln := types.Vulnerability{ - VulnerabilityID: advisory.Cve, - PkgName: pkgName, - Title: strings.TrimSpace(advisory.Title), - InstalledVersion: pkgVer.String(), - FixedVersion: strings.Join(patchedVersions, ", "), - } - vulns = append(vulns, vuln) - } - return vulns, nil -} - -func (s *Scanner) ParseLockfile(f *os.File) ([]ptypes.Library, error) { - libs, err := composer.Parse(f) - if err != nil { - return nil, xerrors.Errorf("invalid composer.lock format: %w", err) - } - return libs, nil -} -func (s *Scanner) Type() string { - return scannerType -} diff --git a/pkg/scanner/library/npm/advisory.go b/pkg/scanner/library/npm/advisory.go deleted file mode 100644 index 81807526600f..000000000000 --- a/pkg/scanner/library/npm/advisory.go +++ /dev/null @@ -1,122 +0,0 @@ -package npm - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/etcd-io/bbolt" - - "github.com/knqyf263/trivy/pkg/db" - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/utils" - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "github.com/knqyf263/trivy/pkg/git" -) - -const ( - dbURL = "https://github.com/nodejs/security-wg.git" -) - -var ( - repoPath = filepath.Join(utils.CacheDir(), "nodejs-security-wg") -) - -type AdvisoryDB map[string][]Advisory - -type Advisory struct { - ID int - Title string - ModuleName string `json:"module_name""` - Cves []string - VulnerableVersions string `json:"vulnerable_versions"` - PatchedVersions string `json:"patched_versions"` - Overview string - Recommendation string - References []string - CvssScoreNumber json.Number `json:"cvss_score"` - CvssScore float64 -} - -func (s *Scanner) UpdateDB() (err error) { - if _, err := git.CloneOrPull(dbURL, repoPath); err != nil { - return err - } - s.db, err = s.walk() - return err -} - -func (s *Scanner) walk() (AdvisoryDB, error) { - advisoryDB := AdvisoryDB{} - var vulns []vulnerability.Vulnerability - err := filepath.Walk(filepath.Join(repoPath, "vuln"), func(path string, info os.FileInfo, err error) error { - if info.IsDir() || !strings.HasSuffix(info.Name(), ".json") { - return nil - } - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - - advisory := Advisory{} - if err = json.NewDecoder(f).Decode(&advisory); err != nil { - return err - } - advisory.ModuleName = strings.ToLower(advisory.ModuleName) - - // `cvss_score` returns float or string like "4.8 (MEDIUM)" - s := strings.Split(advisory.CvssScoreNumber.String(), " ") - advisory.CvssScore, err = strconv.ParseFloat(s[0], 64) - if err != nil { - advisory.CvssScore = -1 - } - - // for detecting vulnerabilities - advisories, ok := advisoryDB[advisory.ModuleName] - if !ok { - advisories = []Advisory{} - } - advisoryDB[advisory.ModuleName] = append(advisories, advisory) - - // for displaying vulnerability detail - vulnerabilityIDs := advisory.Cves - if len(vulnerabilityIDs) == 0 { - vulnerabilityIDs = []string{fmt.Sprintf("NSWG-ECO-%d", advisory.ID)} - } - for _, vulnID := range vulnerabilityIDs { - vulns = append(vulns, vulnerability.Vulnerability{ - ID: vulnID, - CvssScore: advisory.CvssScore, - References: advisory.References, - Title: advisory.Title, - Description: advisory.Overview, - }) - } - - return nil - }) - if err != nil { - return nil, xerrors.Errorf("error in file walk: %w", err) - } - if err = s.saveVulnerabilities(vulns); err != nil { - return nil, err - } - return advisoryDB, nil -} - -func (s Scanner) saveVulnerabilities(vulns []vulnerability.Vulnerability) error { - return vulnerability.BatchUpdate(func(b *bbolt.Bucket) error { - for _, vuln := range vulns { - if err := db.Put(b, vuln.ID, vulnerability.NodejsSecurityWg, vuln); err != nil { - return xerrors.Errorf("failed to save %s vulnerability: %w", s.Type(), err) - } - } - return nil - }) -} diff --git a/pkg/scanner/library/npm/scan.go b/pkg/scanner/library/npm/scan.go deleted file mode 100644 index fbf50ec12fa7..000000000000 --- a/pkg/scanner/library/npm/scan.go +++ /dev/null @@ -1,82 +0,0 @@ -package npm - -import ( - "fmt" - "os" - "strings" - - "golang.org/x/xerrors" - - "github.com/knqyf263/go-dep-parser/pkg/npm" - ptypes "github.com/knqyf263/go-dep-parser/pkg/types" - "github.com/knqyf263/go-version" - "github.com/knqyf263/trivy/pkg/scanner/utils" - "github.com/knqyf263/trivy/pkg/types" -) - -const ( - scannerType = "npm" -) - -type Scanner struct { - db AdvisoryDB -} - -func NewScanner() *Scanner { - return &Scanner{} -} - -func (s *Scanner) Detect(pkgName string, pkgVer *version.Version) ([]types.Vulnerability, error) { - replacer := strings.NewReplacer(".alpha", "-alpha", ".beta", "-beta", ".rc", "-rc", " <", ", <", " >", ", >") - var vulns []types.Vulnerability - for _, advisory := range s.db[pkgName] { - // e.g. <= 2.15.0 || >= 3.0.0 <= 3.8.2 - // => {"<=2.15.0", ">= 3.0.0, <= 3.8.2"} - var vulnerableVersions []string - for _, version := range strings.Split(advisory.VulnerableVersions, " || ") { - version = strings.TrimSpace(version) - vulnerableVersions = append(vulnerableVersions, replacer.Replace(version)) - } - - if !utils.MatchVersions(pkgVer, vulnerableVersions) { - continue - } - - var patchedVersions []string - for _, version := range strings.Split(advisory.PatchedVersions, " || ") { - version = strings.TrimSpace(version) - patchedVersions = append(patchedVersions, replacer.Replace(version)) - } - - if utils.MatchVersions(pkgVer, patchedVersions) { - continue - } - - if len(advisory.Cves) == 0 { - advisory.Cves = []string{fmt.Sprintf("NSWG-ECO-%d", advisory.ID)} - } - - for _, cveID := range advisory.Cves { - vuln := types.Vulnerability{ - VulnerabilityID: cveID, - PkgName: pkgName, - Title: strings.TrimSpace(advisory.Title), - InstalledVersion: pkgVer.String(), - FixedVersion: strings.Join(patchedVersions, ", "), - } - vulns = append(vulns, vuln) - } - } - return vulns, nil -} - -func (s *Scanner) ParseLockfile(f *os.File) ([]ptypes.Library, error) { - libs, err := npm.Parse(f) - if err != nil { - return nil, xerrors.Errorf("invalid package-lock.json format: %w", err) - } - return libs, nil -} -func (s *Scanner) Type() string { - return scannerType -} diff --git a/pkg/scanner/library/pipenv/advisory.go b/pkg/scanner/library/pipenv/advisory.go deleted file mode 100644 index 52391e6839b8..000000000000 --- a/pkg/scanner/library/pipenv/advisory.go +++ /dev/null @@ -1,92 +0,0 @@ -package pipenv - -import ( - "encoding/json" - "os" - "path/filepath" - - "github.com/etcd-io/bbolt" - - "github.com/knqyf263/trivy/pkg/db" - - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/utils" - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "github.com/knqyf263/trivy/pkg/git" -) - -const ( - dbURL = "https://github.com/pyupio/safety-db.git" -) - -var ( - repoPath = filepath.Join(utils.CacheDir(), "python-safety-db") -) - -type AdvisoryDB map[string][]Advisory - -type Advisory struct { - ID string - Advisory string - Cve string - Specs []string - Version string `json:"v"` -} - -func (s *Scanner) UpdateDB() (err error) { - if _, err := git.CloneOrPull(dbURL, repoPath); err != nil { - return err - } - s.db, err = s.parse() - if err != nil { - return xerrors.Errorf("failed to parse python safety-db: %w", err) - } - return nil -} - -func (s *Scanner) parse() (AdvisoryDB, error) { - advisoryDB := AdvisoryDB{} - f, err := os.Open(filepath.Join(repoPath, "data", "insecure_full.json")) - if err != nil { - return nil, err - } - defer f.Close() - - // for detecting vulnerabilities - if err = json.NewDecoder(f).Decode(&advisoryDB); err != nil { - return nil, err - } - - // for displaying vulnerability detail - var vulns []vulnerability.Vulnerability - for _, advisories := range advisoryDB { - for _, advisory := range advisories { - vulnerabilityID := advisory.Cve - if vulnerabilityID == "" { - vulnerabilityID = advisory.ID - } - vulns = append(vulns, vulnerability.Vulnerability{ - ID: vulnerabilityID, - Title: advisory.Advisory, - }) - } - } - if err = s.saveVulnerabilities(vulns); err != nil { - return nil, err - } - - return advisoryDB, nil -} - -func (s Scanner) saveVulnerabilities(vulns []vulnerability.Vulnerability) error { - return vulnerability.BatchUpdate(func(b *bbolt.Bucket) error { - for _, vuln := range vulns { - if err := db.Put(b, vuln.ID, vulnerability.PythonSafetyDB, vuln); err != nil { - return xerrors.Errorf("failed to save %s vulnerability: %w", s.Type(), err) - } - } - return nil - }) -} diff --git a/pkg/scanner/library/pipenv/scan.go b/pkg/scanner/library/pipenv/scan.go deleted file mode 100644 index 78cc16bbcd8c..000000000000 --- a/pkg/scanner/library/pipenv/scan.go +++ /dev/null @@ -1,73 +0,0 @@ -package pipenv - -import ( - "os" - "strings" - - "golang.org/x/xerrors" - - "github.com/knqyf263/go-dep-parser/pkg/pipenv" - ptypes "github.com/knqyf263/go-dep-parser/pkg/types" - "github.com/knqyf263/go-version" - "github.com/knqyf263/trivy/pkg/scanner/utils" - "github.com/knqyf263/trivy/pkg/types" -) - -const ( - scannerType = "pipenv" -) - -type Scanner struct { - db AdvisoryDB -} - -func NewScanner() *Scanner { - return &Scanner{} -} - -func (s *Scanner) Detect(pkgName string, pkgVer *version.Version) ([]types.Vulnerability, error) { - var vulns []types.Vulnerability - for _, advisory := range s.db[pkgName] { - if !utils.MatchVersions(pkgVer, advisory.Specs) { - continue - } - - vulnerabilityID := advisory.Cve - if vulnerabilityID == "" { - vulnerabilityID = advisory.ID - } - - vuln := types.Vulnerability{ - VulnerabilityID: vulnerabilityID, - PkgName: pkgName, - Title: strings.TrimSpace(advisory.Advisory), - InstalledVersion: pkgVer.String(), - FixedVersion: createFixedVersions(advisory.Specs), - } - vulns = append(vulns, vuln) - } - return vulns, nil -} - -func createFixedVersions(specs []string) string { - var fixedVersions []string - for _, spec := range specs { - for _, s := range strings.Split(spec, ",") { - if !strings.HasPrefix(s, "<=") && strings.HasPrefix(s, "<") { - fixedVersions = append(fixedVersions, strings.TrimPrefix(s, "<")) - } - } - } - return strings.Join(fixedVersions, ", ") -} - -func (s *Scanner) ParseLockfile(f *os.File) ([]ptypes.Library, error) { - libs, err := pipenv.Parse(f) - if err != nil { - return nil, xerrors.Errorf("invalid Pipfile.lock format: %w", err) - } - return libs, nil -} -func (s *Scanner) Type() string { - return scannerType -} diff --git a/pkg/scanner/library/scan.go b/pkg/scanner/library/scan.go deleted file mode 100644 index 5f1e8b7d7fcb..000000000000 --- a/pkg/scanner/library/scan.go +++ /dev/null @@ -1,116 +0,0 @@ -package library - -import ( - "os" - "path/filepath" - - "github.com/knqyf263/fanal/analyzer" - _ "github.com/knqyf263/fanal/analyzer/library/bundler" - _ "github.com/knqyf263/fanal/analyzer/library/composer" - _ "github.com/knqyf263/fanal/analyzer/library/npm" - _ "github.com/knqyf263/fanal/analyzer/library/pipenv" - "github.com/knqyf263/fanal/extractor" - ptypes "github.com/knqyf263/go-dep-parser/pkg/types" - "github.com/knqyf263/go-version" - "github.com/knqyf263/trivy/pkg/log" - "github.com/knqyf263/trivy/pkg/scanner/library/bundler" - "github.com/knqyf263/trivy/pkg/scanner/library/composer" - "github.com/knqyf263/trivy/pkg/scanner/library/npm" - "github.com/knqyf263/trivy/pkg/scanner/library/pipenv" - "github.com/knqyf263/trivy/pkg/types" - "golang.org/x/xerrors" -) - -type Scanner interface { - UpdateDB() error - ParseLockfile(*os.File) ([]ptypes.Library, error) - Detect(string, *version.Version) ([]types.Vulnerability, error) - Type() string -} - -func NewScanner(filename string) Scanner { - var scanner Scanner - switch filename { - case "Gemfile.lock": - scanner = bundler.NewScanner() - case "composer.lock": - scanner = composer.NewScanner() - case "package-lock.json": - scanner = npm.NewScanner() - case "Pipfile.lock": - scanner = pipenv.NewScanner() - default: - return nil - } - return scanner -} - -func Scan(files extractor.FileMap) (map[string][]types.Vulnerability, error) { - results, err := analyzer.GetLibraries(files) - if err != nil { - return nil, xerrors.Errorf("failed to analyze libraries: %w", err) - } - - vulnerabilities := map[string][]types.Vulnerability{} - for path, pkgs := range results { - log.Logger.Debugf("Detecting library vulnerabilities, path: %s", path) - scanner := NewScanner(filepath.Base(string(path))) - if scanner == nil { - return nil, xerrors.New("unknown file type") - } - - vulns, err := scan(scanner, pkgs) - if err != nil { - return nil, xerrors.Errorf("failed to scan %s vulnerabilities: %w", scanner.Type(), err) - } - - if len(vulns) != 0 { - vulnerabilities[string(path)] = vulns - } - } - return vulnerabilities, nil -} - -func ScanFile(f *os.File) ([]types.Vulnerability, error) { - scanner := NewScanner(f.Name()) - if scanner == nil { - return nil, xerrors.New("unknown file type") - } - - pkgs, err := scanner.ParseLockfile(f) - if err != nil { - return nil, err - } - - vulns, err := scan(scanner, pkgs) - if err != nil { - return nil, err - } - return vulns, nil -} - -func scan(scanner Scanner, pkgs []ptypes.Library) ([]types.Vulnerability, error) { - log.Logger.Infof("Updating %s Security DB...", scanner.Type()) - err := scanner.UpdateDB() - if err != nil { - return nil, xerrors.Errorf("failed to update %s advisories: %w", scanner.Type(), err) - } - - log.Logger.Infof("Detecting %s vulnerabilities...", scanner.Type()) - var vulnerabilities []types.Vulnerability - for _, pkg := range pkgs { - v, err := version.NewVersion(pkg.Version) - if err != nil { - log.Logger.Debug(err) - continue - } - - vulns, err := scanner.Detect(pkg.Name, v) - if err != nil { - return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", scanner.Type(), err) - } - vulnerabilities = append(vulnerabilities, vulns...) - } - - return vulnerabilities, nil -} diff --git a/pkg/scanner/local/mock_applier.go b/pkg/scanner/local/mock_applier.go new file mode 100644 index 000000000000..628b872965e5 --- /dev/null +++ b/pkg/scanner/local/mock_applier.go @@ -0,0 +1,73 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package local + +import ( + mock "github.com/stretchr/testify/mock" + + types "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// MockApplier is an autogenerated mock type for the Applier type +type MockApplier struct { + mock.Mock +} + +type ApplierApplyLayersArgs struct { + ArtifactID string + ArtifactIDAnything bool + BlobIDs []string + BlobIDsAnything bool +} + +type ApplierApplyLayersReturns struct { + Detail types.ArtifactDetail + Err error +} + +type ApplierApplyLayersExpectation struct { + Args ApplierApplyLayersArgs + Returns ApplierApplyLayersReturns +} + +func (_m *MockApplier) ApplyApplyLayersExpectation(e ApplierApplyLayersExpectation) { + var args []interface{} + if e.Args.ArtifactIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.ArtifactID) + } + if e.Args.BlobIDsAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.BlobIDs) + } + _m.On("ApplyLayers", args...).Return(e.Returns.Detail, e.Returns.Err) +} + +func (_m *MockApplier) ApplyApplyLayersExpectations(expectations []ApplierApplyLayersExpectation) { + for _, e := range expectations { + _m.ApplyApplyLayersExpectation(e) + } +} + +// ApplyLayers provides a mock function with given fields: artifactID, blobIDs +func (_m *MockApplier) ApplyLayers(artifactID string, blobIDs []string) (types.ArtifactDetail, error) { + ret := _m.Called(artifactID, blobIDs) + + var r0 types.ArtifactDetail + if rf, ok := ret.Get(0).(func(string, []string) types.ArtifactDetail); ok { + r0 = rf(artifactID, blobIDs) + } else { + r0 = ret.Get(0).(types.ArtifactDetail) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, []string) error); ok { + r1 = rf(artifactID, blobIDs) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go new file mode 100644 index 000000000000..b5437549c5cc --- /dev/null +++ b/pkg/scanner/local/scan.go @@ -0,0 +1,485 @@ +package local + +import ( + "context" + "errors" + "fmt" + "sort" + "strings" + "sync" + + "github.com/google/wire" + "github.com/samber/lo" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/applier" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/langpkg" + "github.com/aquasecurity/trivy/pkg/scanner/ospkg" + "github.com/aquasecurity/trivy/pkg/scanner/post" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/vulnerability" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/all" + _ "github.com/aquasecurity/trivy/pkg/fanal/handler/all" +) + +// SuperSet binds dependencies for Local scan +var SuperSet = wire.NewSet( + vulnerability.SuperSet, + applier.NewApplier, + ospkg.NewScanner, + langpkg.NewScanner, + NewScanner, +) + +// Scanner implements the OspkgDetector and LibraryDetector +type Scanner struct { + applier applier.Applier + osPkgScanner ospkg.Scanner + langPkgScanner langpkg.Scanner + vulnClient vulnerability.Client +} + +// NewScanner is the factory method for Scanner +func NewScanner(a applier.Applier, osPkgScanner ospkg.Scanner, langPkgScanner langpkg.Scanner, + vulnClient vulnerability.Client) Scanner { + return Scanner{ + applier: a, + osPkgScanner: osPkgScanner, + langPkgScanner: langPkgScanner, + vulnClient: vulnClient, + } +} + +// Scan scans the artifact and return results. +func (s Scanner) Scan(ctx context.Context, targetName, artifactKey string, blobKeys []string, options types.ScanOptions) ( + types.Results, ftypes.OS, error) { + detail, err := s.applier.ApplyLayers(artifactKey, blobKeys) + switch { + case errors.Is(err, analyzer.ErrUnknownOS): + log.Logger.Debug("OS is not detected.") + + // Packages may contain OS-independent binary information even though OS is not detected. + if len(detail.Packages) != 0 { + detail.OS = ftypes.OS{Family: "none"} + } + + // If OS is not detected and repositories are detected, we'll try to use repositories as OS. + if detail.Repository != nil { + log.Logger.Debugf("Package repository: %s %s", detail.Repository.Family, detail.Repository.Release) + log.Logger.Debugf("Assuming OS is %s %s.", detail.Repository.Family, detail.Repository.Release) + detail.OS = ftypes.OS{ + Family: detail.Repository.Family, + Name: detail.Repository.Release, + } + } + case errors.Is(err, analyzer.ErrNoPkgsDetected): + log.Logger.Warn("No OS package is detected. Make sure you haven't deleted any files that contain information about the installed packages.") + log.Logger.Warn(`e.g. files under "/lib/apk/db/", "/var/lib/dpkg/" and "/var/lib/rpm"`) + case err != nil: + return nil, ftypes.OS{}, xerrors.Errorf("failed to apply layers: %w", err) + } + + target := types.ScanTarget{ + Name: targetName, + OS: detail.OS, + Repository: detail.Repository, + Packages: mergePkgs(detail.Packages, detail.ImageConfig.Packages, options), + Applications: detail.Applications, + Misconfigurations: mergeMisconfigurations(targetName, detail), + Secrets: mergeSecrets(targetName, detail), + Licenses: detail.Licenses, + CustomResources: detail.CustomResources, + } + + return s.ScanTarget(ctx, target, options) +} + +func (s Scanner) ScanTarget(ctx context.Context, target types.ScanTarget, options types.ScanOptions) (types.Results, ftypes.OS, error) { + var eosl bool + var results, pkgResults types.Results + var err error + + // By default, we need to remove dev dependencies from the result + // IncludeDevDeps option allows you not to remove them + excludeDevDeps(target.Applications, options.IncludeDevDeps) + + // Fill OS packages and language-specific packages + if options.ListAllPackages { + if res := s.osPkgScanner.Packages(target, options); len(res.Packages) != 0 { + pkgResults = append(pkgResults, res) + } + pkgResults = append(pkgResults, s.langPkgScanner.Packages(target, options)...) + } + + // Scan packages for vulnerabilities + if options.Scanners.Enabled(types.VulnerabilityScanner) { + var vulnResults types.Results + vulnResults, eosl, err = s.scanVulnerabilities(ctx, target, options) + if err != nil { + return nil, ftypes.OS{}, xerrors.Errorf("failed to detect vulnerabilities: %w", err) + } + target.OS.Eosl = eosl + + // Merge package results into vulnerability results + mergedResults := s.fillPkgsInVulns(pkgResults, vulnResults) + + results = append(results, mergedResults...) + } else { + // If vulnerability scanning is not enabled, it just adds package results. + results = append(results, pkgResults...) + } + + // Store misconfigurations + results = append(results, s.misconfsToResults(target.Misconfigurations, options)...) + + // Store secrets + results = append(results, s.secretsToResults(target.Secrets, options)...) + + // Scan licenses + results = append(results, s.scanLicenses(target, options)...) + + // For WASM plugins and custom analyzers + if len(target.CustomResources) != 0 { + results = append(results, types.Result{ + Class: types.ClassCustom, + CustomResources: target.CustomResources, + }) + } + + for i := range results { + // Fill vulnerability details + s.vulnClient.FillInfo(results[i].Vulnerabilities) + } + + // Post scanning + results, err = post.Scan(ctx, results) + if err != nil { + return nil, ftypes.OS{}, xerrors.Errorf("post scan error: %w", err) + } + + return results, target.OS, nil +} + +func (s Scanner) scanVulnerabilities(ctx context.Context, target types.ScanTarget, options types.ScanOptions) ( + types.Results, bool, error) { + var eosl bool + var results types.Results + + if slices.Contains(options.VulnType, types.VulnTypeOS) { + vuln, detectedEOSL, err := s.osPkgScanner.Scan(ctx, target, options) + if err != nil { + return nil, false, xerrors.Errorf("unable to scan OS packages: %w", err) + } else if vuln.Target != "" { + results = append(results, vuln) + } + eosl = detectedEOSL + } + + if slices.Contains(options.VulnType, types.VulnTypeLibrary) { + vulns, err := s.langPkgScanner.Scan(target, options) + if err != nil { + return nil, false, xerrors.Errorf("failed to scan application libraries: %w", err) + } + results = append(results, vulns...) + } + + return results, eosl, nil +} + +func (s Scanner) fillPkgsInVulns(pkgResults, vulnResults types.Results) types.Results { + var results types.Results + if len(pkgResults) == 0 { // '--list-all-pkgs' == false or packages not found + return vulnResults + } + for _, result := range pkgResults { + if r, found := lo.Find(vulnResults, func(r types.Result) bool { + return r.Class == result.Class && r.Target == result.Target && r.Type == result.Type + }); found { + r.Packages = result.Packages + results = append(results, r) + } else { // when package result has no vulnerabilities we still need to add it to result(for 'list-all-pkgs') + results = append(results, result) + } + } + return results +} + +func (s Scanner) misconfsToResults(misconfs []ftypes.Misconfiguration, options types.ScanOptions) types.Results { + if !ShouldScanMisconfigOrRbac(options.Scanners) && + !options.ImageConfigScanners.Enabled(types.MisconfigScanner) { + return nil + } + + return s.MisconfsToResults(misconfs) +} + +// MisconfsToResults is exported for trivy-plugin-aqua purposes only +func (s Scanner) MisconfsToResults(misconfs []ftypes.Misconfiguration) types.Results { + log.Logger.Infof("Detected config files: %d", len(misconfs)) + var results types.Results + for _, misconf := range misconfs { + log.Logger.Debugf("Scanned config file: %s", misconf.FilePath) + + var detected []types.DetectedMisconfiguration + + for _, f := range misconf.Failures { + detected = append(detected, toDetectedMisconfiguration(f, dbTypes.SeverityCritical, types.MisconfStatusFailure, misconf.Layer)) + } + for _, w := range misconf.Warnings { + detected = append(detected, toDetectedMisconfiguration(w, dbTypes.SeverityMedium, types.MisconfStatusFailure, misconf.Layer)) + } + for _, w := range misconf.Successes { + detected = append(detected, toDetectedMisconfiguration(w, dbTypes.SeverityUnknown, types.MisconfStatusPassed, misconf.Layer)) + } + for _, w := range misconf.Exceptions { + detected = append(detected, toDetectedMisconfiguration(w, dbTypes.SeverityUnknown, types.MisconfStatusException, misconf.Layer)) + } + + results = append(results, types.Result{ + Target: misconf.FilePath, + Class: types.ClassConfig, + Type: misconf.FileType, + Misconfigurations: detected, + }) + } + + sort.Slice(results, func(i, j int) bool { + return results[i].Target < results[j].Target + }) + + return results +} + +func (s Scanner) secretsToResults(secrets []ftypes.Secret, options types.ScanOptions) types.Results { + if !options.Scanners.Enabled(types.SecretScanner) { + return nil + } + + var results types.Results + for _, secret := range secrets { + log.Logger.Debugf("Secret file: %s", secret.FilePath) + + results = append(results, types.Result{ + Target: secret.FilePath, + Class: types.ClassSecret, + Secrets: lo.Map(secret.Findings, func(secret ftypes.SecretFinding, index int) types.DetectedSecret { + return types.DetectedSecret(secret) + }), + }) + } + return results +} + +func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions) types.Results { + if !options.Scanners.Enabled(types.LicenseScanner) { + return nil + } + + var results types.Results + scanner := licensing.NewScanner(options.LicenseCategories) + + // License - OS packages + var osPkgLicenses []types.DetectedLicense + for _, pkg := range target.Packages { + for _, license := range pkg.Licenses { + category, severity := scanner.Scan(license) + osPkgLicenses = append(osPkgLicenses, types.DetectedLicense{ + Severity: severity, + Category: category, + PkgName: pkg.Name, + Name: license, + Confidence: 1.0, + }) + } + } + results = append(results, types.Result{ + Target: "OS Packages", + Class: types.ClassLicense, + Licenses: osPkgLicenses, + }) + + // License - language-specific packages + for _, app := range target.Applications { + var langLicenses []types.DetectedLicense + for _, lib := range app.Libraries { + for _, license := range lib.Licenses { + category, severity := scanner.Scan(license) + langLicenses = append(langLicenses, types.DetectedLicense{ + Severity: severity, + Category: category, + PkgName: lib.Name, + Name: license, + // Lock files use app.FilePath - https://github.com/aquasecurity/trivy/blob/6ccc0a554b07b05fd049f882a1825a0e1e0aabe1/pkg/fanal/types/artifact.go#L245-L246 + // Applications use lib.FilePath - https://github.com/aquasecurity/trivy/blob/6ccc0a554b07b05fd049f882a1825a0e1e0aabe1/pkg/fanal/types/artifact.go#L93-L94 + FilePath: lo.Ternary(lib.FilePath != "", lib.FilePath, app.FilePath), + Confidence: 1.0, + }) + } + } + + targetName := app.FilePath + if t, ok := langpkg.PkgTargets[app.Type]; ok && targetName == "" { + // When the file path is empty, we will overwrite it with the pre-defined value. + targetName = t + } + results = append(results, types.Result{ + Target: targetName, + Class: types.ClassLicense, + Licenses: langLicenses, + }) + } + + // License - file header or license file + var fileLicenses []types.DetectedLicense + for _, license := range target.Licenses { + for _, finding := range license.Findings { + category, severity := scanner.Scan(finding.Name) + fileLicenses = append(fileLicenses, types.DetectedLicense{ + Severity: severity, + Category: category, + FilePath: license.FilePath, + Name: finding.Name, + Confidence: finding.Confidence, + Link: finding.Link, + }) + + } + } + results = append(results, types.Result{ + Target: "Loose File License(s)", + Class: types.ClassLicenseFile, + Licenses: fileLicenses, + }) + + return results +} + +func toDetectedMisconfiguration(res ftypes.MisconfResult, defaultSeverity dbTypes.Severity, + status types.MisconfStatus, layer ftypes.Layer) types.DetectedMisconfiguration { + + severity := defaultSeverity + sev, err := dbTypes.NewSeverity(res.Severity) + if err != nil { + log.Logger.Warnf("severity must be %s, but %s", dbTypes.SeverityNames, res.Severity) + } else { + severity = sev + } + + msg := strings.TrimSpace(res.Message) + if msg == "" { + msg = "No issues found" + } + + var primaryURL string + + // empty namespace implies a go rule from defsec, "builtin" refers to a built-in rego rule + // this ensures we don't generate bad links for custom policies + if res.Namespace == "" || strings.HasPrefix(res.Namespace, "builtin.") { + primaryURL = fmt.Sprintf("https://avd.aquasec.com/misconfig/%s", strings.ToLower(res.ID)) + res.References = append(res.References, primaryURL) + } + + if primaryURL == "" && len(res.References) > 0 { + primaryURL = res.References[0] + } + + return types.DetectedMisconfiguration{ + ID: res.ID, + AVDID: res.AVDID, + Type: res.Type, + Title: res.Title, + Description: res.Description, + Message: msg, + Resolution: res.RecommendedActions, + Namespace: res.Namespace, + Query: res.Query, + Severity: severity.String(), + PrimaryURL: primaryURL, + References: res.References, + Status: status, + Layer: layer, + Traces: res.Traces, + CauseMetadata: ftypes.CauseMetadata{ + Resource: res.Resource, + Provider: res.Provider, + Service: res.Service, + StartLine: res.StartLine, + EndLine: res.EndLine, + Code: res.Code, + Occurrences: res.Occurrences, + }, + } +} + +func ShouldScanMisconfigOrRbac(scanners types.Scanners) bool { + return scanners.AnyEnabled(types.MisconfigScanner, types.RBACScanner) +} + +// excludeDevDeps removes development dependencies from the list of applications +func excludeDevDeps(apps []ftypes.Application, include bool) { + if include { + return + } + + onceInfo := sync.OnceFunc(func() { + log.Logger.Info("Suppressing dependencies for development and testing. To display them, try the '--include-dev-deps' flag.") + }) + for i := range apps { + apps[i].Libraries = lo.Filter(apps[i].Libraries, func(lib ftypes.Package, index int) bool { + if lib.Dev { + onceInfo() + } + return !lib.Dev + }) + } +} + +func mergePkgs(pkgs, pkgsFromCommands []ftypes.Package, options types.ScanOptions) []ftypes.Package { + if !options.ScanRemovedPackages || len(pkgsFromCommands) == 0 { + return pkgs + } + + // pkg has priority over pkgsFromCommands + uniqPkgs := make(map[string]struct{}) + for _, pkg := range pkgs { + uniqPkgs[pkg.Name] = struct{}{} + } + for _, pkg := range pkgsFromCommands { + if _, ok := uniqPkgs[pkg.Name]; ok { + continue + } + pkgs = append(pkgs, pkg) + } + return pkgs +} + +// mergeMisconfigurations merges misconfigurations on container image config +func mergeMisconfigurations(targetName string, detail ftypes.ArtifactDetail) []ftypes.Misconfiguration { + if detail.ImageConfig.Misconfiguration == nil { + return detail.Misconfigurations + } + + // Append misconfigurations on container image config + misconf := detail.ImageConfig.Misconfiguration + misconf.FilePath = targetName // Set the target name to the file path as container image config is not a real file. + return append(detail.Misconfigurations, *misconf) +} + +// mergeSecrets merges secrets on container image config. +func mergeSecrets(targetName string, detail ftypes.ArtifactDetail) []ftypes.Secret { + if detail.ImageConfig.Secret == nil { + return detail.Secrets + } + + // Append secrets on container image config + secret := detail.ImageConfig.Secret + secret.FilePath = targetName // Set the target name to the file path as container image config is not a real file. + return append(detail.Secrets, *secret) +} diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go new file mode 100644 index 000000000000..3b173957cc23 --- /dev/null +++ b/pkg/scanner/local/scan_test.go @@ -0,0 +1,1386 @@ +package local + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/scanner/langpkg" + "github.com/aquasecurity/trivy/pkg/scanner/ospkg" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/vulnerability" +) + +func TestScanner_Scan(t *testing.T) { + type args struct { + target string + layerIDs []string + options types.ScanOptions + } + tests := []struct { + name string + args args + fixtures []string + applyLayersExpectation ApplierApplyLayersExpectation + wantResults types.Results + wantOS ftypes.OS + wantErr string + }{ + { + name: "happy path", + args: args{ + target: "alpine:latest", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{ + types.VulnTypeOS, + types.VulnTypeLibrary, + }, + Scanners: types.Scanners{types.VulnerabilityScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + OS: ftypes.OS{ + Family: ftypes.Alpine, + Name: "3.11", + }, + Packages: []ftypes.Package{ + { + Name: "musl", + Version: "1.2.3", + SrcName: "musl", + SrcVersion: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + }, + Applications: []ftypes.Application{ + { + Type: ftypes.Bundler, + FilePath: "/app/Gemfile.lock", + Libraries: []ftypes.Package{ + { + Name: "rails", + Version: "4.0.2", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + }, + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "alpine:latest (alpine 3.11)", + Class: types.ClassOSPkg, + Type: ftypes.Alpine, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-9999", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: "HIGH", + }, + }, + }, + }, + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: "rails", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), + }, + }, + }, + }, + }, + wantOS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + }, + { + name: "happy path license scanner", + args: args{ + target: "alpine:latest", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + Scanners: types.Scanners{types.LicenseScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + OS: ftypes.OS{ + Family: ftypes.Alpine, + Name: "3.11", + }, + Packages: []ftypes.Package{ + { + Name: "musl", + Version: "1.2.3", + SrcName: "musl", + SrcVersion: "1.2.3", + Licenses: []string{"MIT"}, + Layer: ftypes.Layer{ + DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + }, + Applications: []ftypes.Application{ + { + Type: ftypes.GoModule, + FilePath: "/app/go.mod", + Libraries: []ftypes.Package{ + { + Name: "github.com/google/uuid", + Version: "1.6.0", + FilePath: "", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + Licenses: []string{"LGPL"}, + }, + }, + }, + { + Type: ftypes.PythonPkg, + FilePath: "", + Libraries: []ftypes.Package{ + { + Name: "urllib3", + Version: "3.2.1", + FilePath: "/usr/lib/python/site-packages/urllib3-3.2.1/METADATA", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + Licenses: []string{"MIT"}, + }, + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "OS Packages", + Class: types.ClassLicense, + Licenses: []types.DetectedLicense{ + { + Severity: "UNKNOWN", + Category: "unknown", + PkgName: "musl", + Name: "MIT", + Confidence: 1, + }, + }, + }, + { + Target: "/app/go.mod", + Class: types.ClassLicense, + Licenses: []types.DetectedLicense{ + { + Severity: "UNKNOWN", + Category: "unknown", + PkgName: "github.com/google/uuid", + FilePath: "/app/go.mod", + Name: "LGPL", + Confidence: 1, + Link: "", + }, + }, + }, + { + Target: "Python", + Class: types.ClassLicense, + Licenses: []types.DetectedLicense{ + { + Severity: "UNKNOWN", + Category: "unknown", + PkgName: "urllib3", + FilePath: "/usr/lib/python/site-packages/urllib3-3.2.1/METADATA", + Name: "MIT", + Confidence: 1, + }, + }, + }, + { + Target: "Loose File License(s)", + Class: types.ClassLicenseFile, + }, + }, + wantOS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: false, + }, + }, + { + name: "happy path with list all packages", + args: args{ + target: "alpine:latest", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{ + types.VulnTypeOS, + types.VulnTypeLibrary, + }, + Scanners: types.Scanners{types.VulnerabilityScanner}, + ListAllPackages: true, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + }, + Packages: []ftypes.Package{ + { + Name: "musl", + Version: "1.2.3", + SrcName: "musl", + SrcVersion: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + { + Name: "ausl", + Version: "1.2.3", + SrcName: "ausl", + SrcVersion: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + }, + Applications: []ftypes.Application{ + { + Type: "bundler", + FilePath: "/app/Gemfile.lock", + Libraries: []ftypes.Package{ + { + Name: "rails", + Version: "4.0.2", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + }, + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "alpine:latest (alpine 3.11)", + Class: types.ClassOSPkg, + Type: ftypes.Alpine, + Packages: []ftypes.Package{ + { + Name: "ausl", + Version: "1.2.3", + SrcName: "ausl", + SrcVersion: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + { + Name: "musl", + Version: "1.2.3", + SrcName: "musl", + SrcVersion: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + }, + // For backward compatibility, will be removed + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-9999", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: "HIGH", + }, + }, + }, + }, + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: []ftypes.Package{ + { + Name: "rails", + Version: "4.0.2", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + }, + }, + // For backward compatibility, will be removed + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: "rails", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), + }, + }, + }, + }, + }, + wantOS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + }, + { + name: "happy path with list all packages and without vulnerabilities", + args: args{ + target: "alpine:latest", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{ + types.VulnTypeOS, + types.VulnTypeLibrary, + }, + Scanners: types.Scanners{types.VulnerabilityScanner}, + ListAllPackages: true, + }, + }, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + }, + Packages: []ftypes.Package{ + { + Name: "musl", + Version: "1.2.3", + SrcName: "musl", + SrcVersion: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + { + Name: "ausl", + Version: "1.2.3", + SrcName: "ausl", + SrcVersion: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + }, + Applications: []ftypes.Application{ + { + Type: "bundler", + FilePath: "/app/Gemfile.lock", + Libraries: []ftypes.Package{ + { + Name: "rails", + Version: "4.0.2", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + }, + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "alpine:latest (alpine 3.11)", + Class: types.ClassOSPkg, + Type: ftypes.Alpine, + Packages: []ftypes.Package{ + { + Name: "ausl", + Version: "1.2.3", + SrcName: "ausl", + SrcVersion: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + { + Name: "musl", + Version: "1.2.3", + SrcName: "musl", + SrcVersion: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + }, + }, + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: []ftypes.Package{ + { + Name: "rails", + Version: "4.0.2", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + }, + }, + }, + }, + wantOS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + }, + { + name: "happy path with empty os", + args: args{ + target: "alpine:latest", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{ + types.VulnTypeOS, + types.VulnTypeLibrary, + }, + Scanners: types.Scanners{types.VulnerabilityScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + OS: ftypes.OS{}, + Applications: []ftypes.Application{ + { + Type: ftypes.Bundler, + FilePath: "/app/Gemfile.lock", + Libraries: []ftypes.Package{ + { + Name: "innocent", // no vulnerability + Version: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + }, + }, + { + Type: ftypes.Bundler, + FilePath: "/app/Gemfile.lock", + Libraries: []ftypes.Package{ + { + Name: "rails", // one vulnerability + Version: "4.0.2", + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: "bundler", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: "rails", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), + }, + }, + }, + }, + }, + wantOS: ftypes.OS{}, + }, + { + name: "happy path. Empty filePaths (e.g. Scanned SBOM)", + args: args{ + target: "./result.cdx", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeLibrary}, + Scanners: types.Scanners{types.VulnerabilityScanner}, + ListAllPackages: true, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + Applications: []ftypes.Application{ + { + Type: "bundler", + FilePath: "", + Libraries: []ftypes.Package{ + { + Name: "rails", + Version: "4.0.2", + }, + }, + }, + { + Type: "composer", + FilePath: "", + Libraries: []ftypes.Package{ + { + Name: "laravel/framework", + Version: "6.0.0", + }, + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: []ftypes.Package{ + { + Name: "rails", + Version: "4.0.2", + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: "rails", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), + }, + }, + }, + }, + { + Target: "", + Class: types.ClassLangPkg, + Type: ftypes.Composer, + Packages: []ftypes.Package{ + { + Name: "laravel/framework", + Version: "6.0.0", + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2021-21263", + PkgName: "laravel/framework", + InstalledVersion: "6.0.0", + FixedVersion: "8.22.1, 7.30.3, 6.20.12", + Status: dbTypes.StatusFixed, + }, + }, + }, + }, + }, + { + name: "happy path with no package", + args: args{ + target: "alpine:latest", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{ + types.VulnTypeOS, + types.VulnTypeLibrary, + }, + Scanners: types.Scanners{types.VulnerabilityScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + }, + Applications: []ftypes.Application{ + { + Type: "bundler", + FilePath: "/app/Gemfile.lock", + Libraries: []ftypes.Package{ + { + Name: "rails", + Version: "4.0.2", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + }, + }, + }, + }, + }, + Err: analyzer.ErrNoPkgsDetected, + }, + }, + wantResults: types.Results{ + { + Target: "alpine:latest (alpine 3.11)", + Class: types.ClassOSPkg, + Type: ftypes.Alpine, + }, + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: "rails", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), + }, + }, + }, + }, + }, + wantOS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + }, + { + name: "happy path with unsupported os", + args: args{ + target: "fedora:27", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{ + types.VulnTypeOS, + types.VulnTypeLibrary, + }, + Scanners: types.Scanners{types.VulnerabilityScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + OS: ftypes.OS{ + Family: "fedora", + Name: "27", + }, + Applications: []ftypes.Application{ + { + Type: "bundler", + FilePath: "/app/Gemfile.lock", + Libraries: []ftypes.Package{ + { + Name: "rails", + Version: "4.0.2", + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: "rails", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), + }, + }, + }, + }, + }, + wantOS: ftypes.OS{ + Family: "fedora", + Name: "27", + }, + }, + { + name: "happy path with a scratch image", + args: args{ + target: "busybox:latest", + layerIDs: []string{"sha256:a6d503001157aedc826853f9b67f26d35966221b158bff03849868ae4a821116"}, + options: types.ScanOptions{ + VulnType: []string{ + types.VulnTypeOS, + types.VulnTypeLibrary, + }, + Scanners: types.Scanners{types.VulnerabilityScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:a6d503001157aedc826853f9b67f26d35966221b158bff03849868ae4a821116"}, + }, + Returns: ApplierApplyLayersReturns{ + Err: analyzer.ErrUnknownOS, + }, + }, + wantResults: nil, + }, + { + name: "happy path with only language-specific package detection", + args: args{ + target: "alpine:latest", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeLibrary}, + Scanners: types.Scanners{types.VulnerabilityScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + }, + Packages: []ftypes.Package{ + { + Name: "musl", + Version: "1.2.3", + SrcName: "musl", + SrcVersion: "1.2.3", + }, + }, + Applications: []ftypes.Application{ + { + Type: "bundler", + FilePath: "/app/Gemfile.lock", + Libraries: []ftypes.Package{ + { + Name: "rails", + Version: "4.0.2", + Layer: ftypes.Layer{ + DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", + }, + }, + }, + }, + { + Type: "composer", + FilePath: "/app/composer-lock.json", + Libraries: []ftypes.Package{ + { + Name: "laravel/framework", + Version: "6.0.0", + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: "rails", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), + }, + }, + }, + }, + { + Target: "/app/composer-lock.json", + Class: types.ClassLangPkg, + Type: ftypes.Composer, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2021-21263", + PkgName: "laravel/framework", + InstalledVersion: "6.0.0", + FixedVersion: "8.22.1, 7.30.3, 6.20.12", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + }, + }, + }, + wantOS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + }, + }, + { + name: "happy path with misconfigurations", + args: args{ + target: "/app/configs", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + Scanners: types.Scanners{types.MisconfigScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + Misconfigurations: []ftypes.Misconfiguration{ + { + FileType: ftypes.Kubernetes, + FilePath: "/app/configs/pod.yaml", + Warnings: []ftypes.MisconfResult{ + { + Namespace: "main.kubernetes.id300", + PolicyMetadata: ftypes.PolicyMetadata{ + ID: "ID300", + Type: "Kubernetes Security Check", + Title: "Bad Deployment", + Severity: "DUMMY", + }, + }, + }, + Exceptions: ftypes.MisconfResults{ + { + Namespace: "main.kubernetes.id100", + PolicyMetadata: ftypes.PolicyMetadata{ + ID: "ID100", + Type: "Kubernetes Security Check", + Title: "Bad Deployment", + Severity: "HIGH", + }, + }, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + { + FileType: ftypes.Kubernetes, + FilePath: "/app/configs/deployment.yaml", + Successes: []ftypes.MisconfResult{ + { + Namespace: "builtin.kubernetes.id200", + PolicyMetadata: ftypes.PolicyMetadata{ + ID: "ID200", + Type: "Kubernetes Security Check", + Title: "Bad Deployment", + Severity: "MEDIUM", + }, + }, + }, + Failures: ftypes.MisconfResults{ + { + Namespace: "main.kubernetes.id100", + Message: "something bad", + PolicyMetadata: ftypes.PolicyMetadata{ + ID: "ID100", + Type: "Kubernetes Security Check", + Title: "Bad Deployment", + Severity: "HIGH", + }, + }, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "/app/configs/deployment.yaml", + Class: types.ClassConfig, + Type: ftypes.Kubernetes, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Kubernetes Security Check", + ID: "ID100", + Title: "Bad Deployment", + Message: "something bad", + Namespace: "main.kubernetes.id100", + Severity: "HIGH", + Status: types.MisconfStatusFailure, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + { + Type: "Kubernetes Security Check", + ID: "ID200", + Title: "Bad Deployment", + Message: "No issues found", + Namespace: "builtin.kubernetes.id200", + Severity: "MEDIUM", + PrimaryURL: "https://avd.aquasec.com/misconfig/id200", + References: []string{ + "https://avd.aquasec.com/misconfig/id200", + }, + Status: types.MisconfStatusPassed, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + }, + }, + { + Target: "/app/configs/pod.yaml", + Class: types.ClassConfig, + Type: ftypes.Kubernetes, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Kubernetes Security Check", + ID: "ID300", + Title: "Bad Deployment", + Message: "No issues found", + Namespace: "main.kubernetes.id300", + Severity: "MEDIUM", + Status: types.MisconfStatusFailure, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + { + Type: "Kubernetes Security Check", + ID: "ID100", + Title: "Bad Deployment", + Message: "No issues found", + Namespace: "main.kubernetes.id100", + Severity: "HIGH", + Status: types.MisconfStatusException, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + }, + }, + }, + }, + }, + { + name: "sad path: ApplyLayers returns an error", + args: args{ + target: "alpine:latest", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{ + types.VulnTypeOS, + types.VulnTypeLibrary, + }, + Scanners: types.Scanners{types.VulnerabilityScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Err: errors.New("error"), + }, + }, + wantErr: "failed to apply layers", + }, + { + name: "sad path: library.Detect returns an error", + args: args{ + target: "alpine:latest", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeLibrary}, + Scanners: types.Scanners{types.VulnerabilityScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/sad.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + }, + Packages: []ftypes.Package{ + { + Name: "musl", + Version: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + }, + }, + Applications: []ftypes.Application{ + { + Type: "bundler", + FilePath: "/app/Gemfile.lock", + Libraries: []ftypes.Package{ + { + Name: "rails", + Version: "6.0", + Layer: ftypes.Layer{ + DiffID: "sha256:9bdb2c849099a99c8ab35f6fd7469c623635e8f4479a0a5a3df61e22bae509f6", + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: "failed to scan application libraries", + }, + { + name: "scan image history", + args: args{ + target: "alpine:latest", + layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + options: types.ScanOptions{ + ImageConfigScanners: types.Scanners{types.MisconfigScanner}, + }, + }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, + applyLayersExpectation: ApplierApplyLayersExpectation{ + Args: ApplierApplyLayersArgs{ + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + Returns: ApplierApplyLayersReturns{ + Detail: ftypes.ArtifactDetail{ + OS: ftypes.OS{ + Family: ftypes.Alpine, + Name: "3.11", + }, + Misconfigurations: []ftypes.Misconfiguration{ + { + FileType: ftypes.Dockerfile, + FilePath: "Dockerfile", + Successes: ftypes.MisconfResults{ + { + Namespace: "builtin.dockerfile.DS001", + Query: "data.builtin.dockerfile.DS001.deny", + Message: "", + PolicyMetadata: ftypes.PolicyMetadata{ + ID: "DS001", + AVDID: "AVD-DS-0001", + Type: "Dockerfile Security Check", + Title: "':latest' tag used", + Description: "When using a 'FROM' statement you should use a specific tag to avoid uncontrolled behavior when the image is updated.", + Severity: "MEDIUM", + RecommendedActions: "Add a tag to the image in the 'FROM' statement", + }, + CauseMetadata: ftypes.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + Code: ftypes.Code{}, + }, + }, + }, + Failures: ftypes.MisconfResults{ + { + Namespace: "builtin.dockerfile.DS002", + Query: "data.builtin.dockerfile.DS002.deny", + Message: "Specify at least 1 USER command in Dockerfile with non-root user as argument", + PolicyMetadata: ftypes.PolicyMetadata{ + ID: "DS002", + AVDID: "AVD-DS-0002", + Type: "Dockerfile Security Check", + Title: "Image user should not be 'root'", + Description: "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", + Severity: "HIGH", + RecommendedActions: "Add 'USER ' line to the Dockerfile", + }, + CauseMetadata: ftypes.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + Code: ftypes.Code{}, + }, + }, + }, + }, + }, + }, + }, + }, + wantResults: types.Results{ + { + Target: "Dockerfile", + Class: types.ClassConfig, + Type: ftypes.Dockerfile, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Namespace: "builtin.dockerfile.DS002", + Query: "data.builtin.dockerfile.DS002.deny", + Message: "Specify at least 1 USER command in Dockerfile with non-root user as argument", + Type: "Dockerfile Security Check", + ID: "DS002", + AVDID: "AVD-DS-0002", + Title: "Image user should not be 'root'", + Description: "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", + Severity: "HIGH", + Resolution: "Add 'USER ' line to the Dockerfile", + Status: types.MisconfStatusFailure, + PrimaryURL: "https://avd.aquasec.com/misconfig/ds002", + References: []string{"https://avd.aquasec.com/misconfig/ds002"}, + CauseMetadata: ftypes.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + Code: ftypes.Code{}, + }, + }, + { + Namespace: "builtin.dockerfile.DS001", + Query: "data.builtin.dockerfile.DS001.deny", + Message: "No issues found", + Type: "Dockerfile Security Check", + ID: "DS001", + AVDID: "AVD-DS-0001", + Title: "':latest' tag used", + Description: "When using a 'FROM' statement you should use a specific tag to avoid uncontrolled behavior when the image is updated.", + Severity: "MEDIUM", + Resolution: "Add a tag to the image in the 'FROM' statement", + Status: types.MisconfStatusPassed, + CauseMetadata: ftypes.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + Code: ftypes.Code{}, + }, + PrimaryURL: "https://avd.aquasec.com/misconfig/ds001", + References: []string{"https://avd.aquasec.com/misconfig/ds001"}, + }, + }, + }, + }, + wantOS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: false, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + applier := new(MockApplier) + applier.ApplyApplyLayersExpectation(tt.applyLayersExpectation) + + s := NewScanner(applier, ospkg.NewScanner(), langpkg.NewScanner(), vulnerability.NewClient(db.Config{})) + gotResults, gotOS, err := s.Scan(context.Background(), tt.args.target, "", tt.args.layerIDs, tt.args.options) + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + require.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } + + require.NoError(t, err, tt.name) + assert.Equal(t, tt.wantResults, gotResults) + assert.Equal(t, tt.wantOS, gotOS) + + applier.AssertExpectations(t) + }) + } +} diff --git a/pkg/scanner/local/testdata/fixtures/happy.yaml b/pkg/scanner/local/testdata/fixtures/happy.yaml new file mode 100644 index 000000000000..3b4d81b8e703 --- /dev/null +++ b/pkg/scanner/local/testdata/fixtures/happy.yaml @@ -0,0 +1,50 @@ +- bucket: alpine 3.11 + pairs: + - bucket: musl + pairs: + - key: CVE-2020-9999 + value: + FixedVersion: 1.2.4 +- bucket: "rubygems::GitHub Security Advisory RubyGems" + pairs: + - bucket: rails + pairs: + - key: CVE-2014-0081 + value: + PatchedVersions: + - "4.0.3" + - "3.2.17" + VulnerableVersions: + - ">= 4.0.0, < 4.0.3" + - ">= 3.0.0, < 3.2.17" + +- bucket: "composer::GitHub Security Advisory Composer" + pairs: + - bucket: laravel/framework + pairs: + - key: CVE-2021-21263 + value: + PatchedVersions: + - 8.22.1 + - 7.30.3 + - 6.20.12 + VulnerableVersions: + - ">= 8.0.0, < 8.22.1" + - ">= 7.0.0, < 7.30.3" + - "< 6.20.12" +- bucket: vulnerability + pairs: + - key: CVE-2020-9999 + value: + Title: dos + Description: dos vulnerability + Severity: HIGH + - key: CVE-2014-0081 + value: + Title: xss + Description: xss vulnerability + Severity: MEDIUM + References: + - http://example.com + LastModifiedDate: "2020-02-01T01:01:00Z" + PublishedDate: "2020-01-01T01:01:00Z" \ No newline at end of file diff --git a/pkg/scanner/local/testdata/fixtures/sad.yaml b/pkg/scanner/local/testdata/fixtures/sad.yaml new file mode 100644 index 000000000000..10bca78803f7 --- /dev/null +++ b/pkg/scanner/local/testdata/fixtures/sad.yaml @@ -0,0 +1,7 @@ +- bucket: "rubygems::uby-advisory-db" + pairs: + - bucket: rails + pairs: + - key: CVE-2014-0081 + value: + PatchedVersions: "invalid" diff --git a/pkg/scanner/mock_driver.go b/pkg/scanner/mock_driver.go new file mode 100644 index 000000000000..79ff2c81a6ef --- /dev/null +++ b/pkg/scanner/mock_driver.go @@ -0,0 +1,110 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package scanner + +import ( + "context" + + mock "github.com/stretchr/testify/mock" + + fanaltypes "github.com/aquasecurity/trivy/pkg/fanal/types" + + types "github.com/aquasecurity/trivy/pkg/types" +) + +// MockDriver is an autogenerated mock type for the Driver type +type MockDriver struct { + mock.Mock +} + +type DriverScanArgs struct { + Ctx context.Context + CtxAnything bool + Target string + TargetAnything bool + ImageID string + ImageIDAnything bool + LayerIDs []string + LayerIDsAnything bool + Options types.ScanOptions + OptionsAnything bool +} + +type DriverScanReturns struct { + Results types.Results + OsFound fanaltypes.OS + Err error +} + +type DriverScanExpectation struct { + Args DriverScanArgs + Returns DriverScanReturns +} + +func (_m *MockDriver) ApplyScanExpectation(e DriverScanExpectation) { + var args []interface{} + if e.Args.CtxAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.Ctx) + } + if e.Args.TargetAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.Target) + } + if e.Args.ImageIDAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.ImageID) + } + if e.Args.LayerIDsAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.LayerIDs) + } + if e.Args.OptionsAnything { + args = append(args, mock.Anything) + } else { + args = append(args, e.Args.Options) + } + _m.On("Scan", args...).Return(e.Returns.Results, e.Returns.OsFound, e.Returns.Err) +} + +func (_m *MockDriver) ApplyScanExpectations(expectations []DriverScanExpectation) { + for _, e := range expectations { + _m.ApplyScanExpectation(e) + } +} + +// Scan provides a mock function with given fields: ctx, target, imageID, layerIDs, options +func (_m *MockDriver) Scan(ctx context.Context, target string, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, fanaltypes.OS, error) { + ret := _m.Called(ctx, target, artifactKey, blobKeys, options) + + var r0 types.Results + if rf, ok := ret.Get(0).(func(context.Context, string, string, []string, types.ScanOptions) types.Results); ok { + r0 = rf(ctx, target, artifactKey, blobKeys, options) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Results) + } + } + + var r1 fanaltypes.OS + if rf, ok := ret.Get(1).(func(context.Context, string, string, []string, types.ScanOptions) fanaltypes.OS); ok { + r1 = rf(ctx, target, artifactKey, blobKeys, options) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(fanaltypes.OS) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(context.Context, string, string, []string, types.ScanOptions) error); ok { + r2 = rf(ctx, target, artifactKey, blobKeys, options) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} diff --git a/pkg/scanner/ospkg/alpine/alpine.go b/pkg/scanner/ospkg/alpine/alpine.go deleted file mode 100644 index 1785ae3f18f2..000000000000 --- a/pkg/scanner/ospkg/alpine/alpine.go +++ /dev/null @@ -1,57 +0,0 @@ -package alpine - -import ( - "strings" - - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/scanner/utils" - - "github.com/knqyf263/go-rpm-version" - - "github.com/knqyf263/trivy/pkg/vulnsrc/alpine" - - "github.com/knqyf263/fanal/analyzer" - "github.com/knqyf263/trivy/pkg/log" - "github.com/knqyf263/trivy/pkg/types" -) - -type Scanner struct{} - -func NewScanner() *Scanner { - return &Scanner{} -} - -func (s *Scanner) Detect(osVer string, pkgs []analyzer.Package) ([]types.Vulnerability, error) { - log.Logger.Info("Detecting Alpine vulnerabilities...") - if strings.Count(osVer, ".") > 1 { - osVer = osVer[:strings.LastIndex(osVer, ".")] - } - log.Logger.Debugf("alpine: os version: %s", osVer) - log.Logger.Debugf("alpine: the number of packages: %s", len(pkgs)) - - var vulns []types.Vulnerability - for _, pkg := range pkgs { - advisories, err := alpine.Get(osVer, pkg.Name) - if err != nil { - return nil, xerrors.Errorf("failed to get alpine advisories: %w", err) - } - - installed := utils.FormatVersion(pkg) - installedVersion := version.NewVersion(installed) - - for _, adv := range advisories { - fixedVersion := version.NewVersion(adv.FixedVersion) - if installedVersion.LessThan(fixedVersion) { - vuln := types.Vulnerability{ - VulnerabilityID: adv.VulnerabilityID, - PkgName: pkg.Name, - InstalledVersion: installed, - FixedVersion: adv.FixedVersion, - } - vulns = append(vulns, vuln) - } - } - } - return vulns, nil -} diff --git a/pkg/scanner/ospkg/debian/debian.go b/pkg/scanner/ospkg/debian/debian.go deleted file mode 100644 index 1a075733afb1..000000000000 --- a/pkg/scanner/ospkg/debian/debian.go +++ /dev/null @@ -1,81 +0,0 @@ -package debian - -import ( - "strings" - - "golang.org/x/xerrors" - - "github.com/knqyf263/go-deb-version" - "github.com/knqyf263/trivy/pkg/scanner/utils" - - "github.com/knqyf263/fanal/analyzer" - "github.com/knqyf263/trivy/pkg/log" - "github.com/knqyf263/trivy/pkg/types" - "github.com/knqyf263/trivy/pkg/vulnsrc/debian" - debianoval "github.com/knqyf263/trivy/pkg/vulnsrc/debian-oval" -) - -type Scanner struct{} - -func NewScanner() *Scanner { - return &Scanner{} -} - -func (s *Scanner) Detect(osVer string, pkgs []analyzer.Package) ([]types.Vulnerability, error) { - log.Logger.Info("Detecting Debian vulnerabilities...") - - if strings.Count(osVer, ".") > 0 { - osVer = osVer[:strings.Index(osVer, ".")] - } - log.Logger.Debugf("debian: os version: %s", osVer) - log.Logger.Debugf("debian: the number of packages: %s", len(pkgs)) - - var vulns []types.Vulnerability - for _, pkg := range pkgs { - if pkg.Type != analyzer.TypeSource { - continue - } - advisories, err := debianoval.Get(osVer, pkg.Name) - if err != nil { - return nil, xerrors.Errorf("failed to get debian OVAL: %w", err) - } - - installed := utils.FormatVersion(pkg) - installedVersion, err := version.NewVersion(installed) - if err != nil { - log.Logger.Debugf("failed to parse Debian installed package version: %w", err) - continue - } - - for _, adv := range advisories { - fixedVersion, err := version.NewVersion(adv.FixedVersion) - if err != nil { - log.Logger.Debugf("failed to parse Debian package version: %w", err) - continue - } - - if installedVersion.LessThan(fixedVersion) { - vuln := types.Vulnerability{ - VulnerabilityID: adv.VulnerabilityID, - PkgName: pkg.Name, - InstalledVersion: installed, - FixedVersion: adv.FixedVersion, - } - vulns = append(vulns, vuln) - } - } - advisories, err = debian.Get(osVer, pkg.Name) - if err != nil { - return nil, xerrors.Errorf("failed to get debian advisory: %w", err) - } - for _, adv := range advisories { - vuln := types.Vulnerability{ - VulnerabilityID: adv.VulnerabilityID, - PkgName: pkg.Name, - InstalledVersion: installed, - } - vulns = append(vulns, vuln) - } - } - return vulns, nil -} diff --git a/pkg/scanner/ospkg/redhat/redhat.go b/pkg/scanner/ospkg/redhat/redhat.go deleted file mode 100644 index f1e958ebc7ce..000000000000 --- a/pkg/scanner/ospkg/redhat/redhat.go +++ /dev/null @@ -1,56 +0,0 @@ -package redhat - -import ( - "strings" - - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/scanner/utils" - - "github.com/knqyf263/go-rpm-version" - - "github.com/knqyf263/fanal/analyzer" - "github.com/knqyf263/trivy/pkg/log" - "github.com/knqyf263/trivy/pkg/types" - "github.com/knqyf263/trivy/pkg/vulnsrc/redhat" -) - -type Scanner struct{} - -func NewScanner() *Scanner { - return &Scanner{} -} - -func (s *Scanner) Detect(osVer string, pkgs []analyzer.Package) ([]types.Vulnerability, error) { - log.Logger.Info("Detecting RHEL/CentOS vulnerabilities...") - if strings.Count(osVer, ".") > 0 { - osVer = osVer[:strings.Index(osVer, ".")] - } - log.Logger.Debugf("redhat: os version: %s", osVer) - log.Logger.Debugf("redhat: the number of packages: %s", len(pkgs)) - - var vulns []types.Vulnerability - for _, pkg := range pkgs { - advisories, err := redhat.Get(osVer, pkg.Name) - if err != nil { - return nil, xerrors.Errorf("failed to get Red Hat advisories: %w", err) - } - - installed := utils.FormatVersion(pkg) - installedVersion := version.NewVersion(installed) - for _, adv := range advisories { - fixedVersion := version.NewVersion(adv.FixedVersion) - - if installedVersion.LessThan(fixedVersion) { - vuln := types.Vulnerability{ - VulnerabilityID: adv.VulnerabilityID, - PkgName: pkg.Name, - InstalledVersion: installed, - FixedVersion: adv.FixedVersion, - } - vulns = append(vulns, vuln) - } - } - } - return vulns, nil -} diff --git a/pkg/scanner/ospkg/scan.go b/pkg/scanner/ospkg/scan.go index 69a848da5d39..ebc94b1dab9c 100644 --- a/pkg/scanner/ospkg/scan.go +++ b/pkg/scanner/ospkg/scan.go @@ -1,57 +1,69 @@ package ospkg import ( - "github.com/knqyf263/fanal/analyzer" - fos "github.com/knqyf263/fanal/analyzer/os" - _ "github.com/knqyf263/fanal/analyzer/os/alpine" - _ "github.com/knqyf263/fanal/analyzer/os/debianbase" - _ "github.com/knqyf263/fanal/analyzer/os/redhatbase" - _ "github.com/knqyf263/fanal/analyzer/pkg/apk" - _ "github.com/knqyf263/fanal/analyzer/pkg/dpkg" - "github.com/knqyf263/fanal/extractor" - "github.com/knqyf263/trivy/pkg/log" - "github.com/knqyf263/trivy/pkg/scanner/ospkg/alpine" - "github.com/knqyf263/trivy/pkg/scanner/ospkg/debian" - "github.com/knqyf263/trivy/pkg/scanner/ospkg/redhat" - "github.com/knqyf263/trivy/pkg/scanner/ospkg/ubuntu" - "github.com/knqyf263/trivy/pkg/types" + "context" + "errors" + "fmt" + "sort" + "time" + "golang.org/x/xerrors" + + ospkgDetector "github.com/aquasecurity/trivy/pkg/detector/ospkg" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" ) type Scanner interface { - Detect(string, []analyzer.Package) ([]types.Vulnerability, error) + Packages(target types.ScanTarget, options types.ScanOptions) types.Result + Scan(ctx context.Context, target types.ScanTarget, options types.ScanOptions) (types.Result, bool, error) +} + +type scanner struct{} + +func NewScanner() Scanner { + return &scanner{} } -func Scan(files extractor.FileMap) (string, string, []types.Vulnerability, error) { - os, err := analyzer.GetOS(files) - if err != nil { - return "", "", nil, xerrors.Errorf("failed to analyze OS: %w", err) +func (s *scanner) Packages(target types.ScanTarget, _ types.ScanOptions) types.Result { + if len(target.Packages) == 0 || !target.OS.Detected() { + return types.Result{} } - log.Logger.Debugf("OS family: %s, OS version: %s", os.Family, os.Name) - - var s Scanner - switch os.Family { - case fos.Alpine: - s = alpine.NewScanner() - case fos.Debian: - s = debian.NewScanner() - case fos.Ubuntu: - s = ubuntu.NewScanner() - case fos.RedHat, fos.CentOS: - s = redhat.NewScanner() - default: - return "", "", nil, xerrors.New("unsupported os") + + sort.Sort(target.Packages) + return types.Result{ + Target: fmt.Sprintf("%s (%s %s)", target.Name, target.OS.Family, target.OS.Name), + Class: types.ClassOSPkg, + Type: target.OS.Family, + Packages: target.Packages, } - pkgs, err := analyzer.GetPackages(files) - if err != nil { - return "", "", nil, xerrors.Errorf("failed to analyze OS packages: %w", err) +} + +func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, _ types.ScanOptions) (types.Result, bool, error) { + if !target.OS.Detected() { + log.Logger.Debug("Detected OS: unknown") + return types.Result{}, false, nil + } + log.Logger.Infof("Detected OS: %s", target.OS.Family) + + if target.OS.Extended { + // TODO: move the logic to each detector + target.OS.Name += "-ESM" } - log.Logger.Debugf("the number of packages: %d", len(pkgs)) - vulns, err := s.Detect(os.Name, pkgs) - if err != nil { - return "", "", nil, xerrors.Errorf("failed to detect vulnerabilities: %w", err) + vulns, eosl, err := ospkgDetector.Detect(ctx, "", target.OS.Family, target.OS.Name, target.Repository, time.Time{}, + target.Packages) + if errors.Is(err, ospkgDetector.ErrUnsupportedOS) { + return types.Result{}, false, nil + } else if err != nil { + return types.Result{}, false, xerrors.Errorf("failed vulnerability detection of OS packages: %w", err) } - return os.Family, os.Name, vulns, nil + artifactDetail := fmt.Sprintf("%s (%s %s)", target.Name, target.OS.Family, target.OS.Name) + return types.Result{ + Target: artifactDetail, + Vulnerabilities: vulns, + Class: types.ClassOSPkg, + Type: target.OS.Family, + }, eosl, nil } diff --git a/pkg/scanner/ospkg/scan_bsd.go b/pkg/scanner/ospkg/scan_bsd.go deleted file mode 100644 index 69230da143b8..000000000000 --- a/pkg/scanner/ospkg/scan_bsd.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build freebsd netbsd openbsd - -package ospkg - -import ( - _ "github.com/knqyf263/fanal/analyzer/pkg/rpmcmd" -) diff --git a/pkg/scanner/ospkg/scan_unix.go b/pkg/scanner/ospkg/scan_unix.go deleted file mode 100644 index a9172665617f..000000000000 --- a/pkg/scanner/ospkg/scan_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build linux darwin - -package ospkg - -import ( - _ "github.com/knqyf263/fanal/analyzer/pkg/rpmcmd" - // TODO: Eliminate the dependency on "rpm" command - // _ "github.com/knqyf263/fanal/analyzer/pkg/rpm" -) diff --git a/pkg/scanner/ospkg/ubuntu/ubuntu.go b/pkg/scanner/ospkg/ubuntu/ubuntu.go deleted file mode 100644 index 0cf3a8ffe3ad..000000000000 --- a/pkg/scanner/ospkg/ubuntu/ubuntu.go +++ /dev/null @@ -1,64 +0,0 @@ -package ubuntu - -import ( - "github.com/knqyf263/go-deb-version" - "github.com/knqyf263/trivy/pkg/scanner/utils" - "golang.org/x/xerrors" - - "github.com/knqyf263/fanal/analyzer" - "github.com/knqyf263/trivy/pkg/log" - "github.com/knqyf263/trivy/pkg/types" - "github.com/knqyf263/trivy/pkg/vulnsrc/ubuntu" -) - -type Scanner struct{} - -func NewScanner() *Scanner { - return &Scanner{} -} - -func (s *Scanner) Detect(osVer string, pkgs []analyzer.Package) ([]types.Vulnerability, error) { - log.Logger.Info("Detecting Ubuntu vulnerabilities...") - log.Logger.Debugf("ubuntu: os version: %s", osVer) - log.Logger.Debugf("ubuntu: the number of packages: %s", len(pkgs)) - - var vulns []types.Vulnerability - for _, pkg := range pkgs { - advisories, err := ubuntu.Get(osVer, pkg.Name) - if err != nil { - return nil, xerrors.Errorf("failed to get Ubuntu advisories: %w", err) - } - - installed := utils.FormatVersion(pkg) - installedVersion, err := version.NewVersion(installed) - if err != nil { - log.Logger.Debugf("failed to parse Ubuntu installed package version: %w", err) - continue - } - - for _, adv := range advisories { - vuln := types.Vulnerability{ - VulnerabilityID: adv.VulnerabilityID, - PkgName: pkg.Name, - InstalledVersion: installed, - FixedVersion: adv.FixedVersion, - } - - if adv.FixedVersion == "" { - vulns = append(vulns, vuln) - continue - } - - fixedVersion, err := version.NewVersion(adv.FixedVersion) - if err != nil { - log.Logger.Debugf("failed to parse Ubuntu package version: %w", err) - continue - } - - if installedVersion.LessThan(fixedVersion) { - vulns = append(vulns, vuln) - } - } - } - return vulns, nil -} diff --git a/pkg/scanner/post/post_scan.go b/pkg/scanner/post/post_scan.go new file mode 100644 index 000000000000..53f72819b1c9 --- /dev/null +++ b/pkg/scanner/post/post_scan.go @@ -0,0 +1,45 @@ +package post + +import ( + "context" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/types" +) + +type Scanner interface { + Name() string + Version() int + PostScan(ctx context.Context, results types.Results) (types.Results, error) +} + +func RegisterPostScanner(s Scanner) { + // Avoid duplication + postScanners[s.Name()] = s +} + +func DeregisterPostScanner(name string) { + delete(postScanners, name) +} + +func ScannerVersions() map[string]int { + versions := make(map[string]int) + for _, s := range postScanners { + versions[s.Name()] = s.Version() + } + return versions +} + +var postScanners = make(map[string]Scanner) + +func Scan(ctx context.Context, results types.Results) (types.Results, error) { + var err error + for _, s := range postScanners { + results, err = s.PostScan(ctx, results) + if err != nil { + return nil, xerrors.Errorf("%s post scan error: %w", s.Name(), err) + } + } + return results, nil +} diff --git a/pkg/scanner/post/post_scan_test.go b/pkg/scanner/post/post_scan_test.go new file mode 100644 index 000000000000..981f74bdfc14 --- /dev/null +++ b/pkg/scanner/post/post_scan_test.go @@ -0,0 +1,103 @@ +package post_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/scanner/post" + "github.com/aquasecurity/trivy/pkg/types" +) + +type testPostScanner struct{} + +func (testPostScanner) Name() string { + return "test" +} + +func (testPostScanner) Version() int { + return 1 +} + +func (testPostScanner) PostScan(ctx context.Context, results types.Results) (types.Results, error) { + for i, r := range results { + if r.Target == "bad" { + return nil, errors.New("bad") + } + for j := range r.Vulnerabilities { + results[i].Vulnerabilities[j].Severity = "LOW" + } + } + return results, nil +} + +func TestScan(t *testing.T) { + tests := []struct { + name string + results types.Results + want types.Results + wantErr bool + }{ + { + name: "happy path", + results: types.Results{ + { + Target: "test", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: "CRITICAL", + }, + }, + }, + }, + }, + want: types.Results{ + { + Target: "test", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Severity: "LOW", + }, + }, + }, + }, + }, + }, + { + name: "sad path", + results: types.Results{ + { + Target: "bad", + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := testPostScanner{} + post.RegisterPostScanner(s) + defer func() { + post.DeregisterPostScanner(s.Name()) + }() + + results, err := post.Scan(context.Background(), tt.results) + require.Equal(t, err != nil, tt.wantErr) + assert.Equal(t, results, tt.want) + }) + } +} diff --git a/pkg/scanner/scan.go b/pkg/scanner/scan.go index 8d186d533717..4cf647b66d13 100644 --- a/pkg/scanner/scan.go +++ b/pkg/scanner/scan.go @@ -2,237 +2,207 @@ package scanner import ( "context" - "flag" - "fmt" - "os" - "sort" - "github.com/genuinetools/reg/registry" - - "github.com/knqyf263/trivy/pkg/log" + "github.com/google/wire" + "golang.org/x/xerrors" - "github.com/knqyf263/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + aimage "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" + flocal "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" + "github.com/aquasecurity/trivy/pkg/fanal/artifact/repo" + "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" + "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/rpc/client" + "github.com/aquasecurity/trivy/pkg/scanner/local" + "github.com/aquasecurity/trivy/pkg/types" +) - "github.com/knqyf263/trivy/pkg/scanner/library" +/////////////// +// Standalone +/////////////// - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" +// StandaloneSuperSet is used in the standalone mode +var StandaloneSuperSet = wire.NewSet( + local.SuperSet, + wire.Bind(new(Driver), new(local.Scanner)), + NewScanner, +) - "github.com/knqyf263/trivy/pkg/types" +// StandaloneDockerSet binds docker dependencies +var StandaloneDockerSet = wire.NewSet( + image.NewContainerImage, + aimage.NewArtifact, + StandaloneSuperSet, +) - "github.com/knqyf263/trivy/pkg/scanner/ospkg" +// StandaloneArchiveSet binds archive scan dependencies +var StandaloneArchiveSet = wire.NewSet( + image.NewArchiveImage, + aimage.NewArtifact, + StandaloneSuperSet, +) - "golang.org/x/crypto/ssh/terminal" - "golang.org/x/xerrors" +// StandaloneFilesystemSet binds filesystem dependencies +var StandaloneFilesystemSet = wire.NewSet( + flocal.NewArtifact, + StandaloneSuperSet, +) - "github.com/knqyf263/fanal/analyzer" - "github.com/knqyf263/fanal/extractor" +// StandaloneRepositorySet binds repository dependencies +var StandaloneRepositorySet = wire.NewSet( + repo.NewArtifact, + StandaloneSuperSet, ) -var ( - sources = []string{vulnerability.Nvd, vulnerability.RedHat, vulnerability.Debian, - vulnerability.DebianOVAL, vulnerability.Alpine, vulnerability.RubySec, vulnerability.PhpSecurityAdvisories, - vulnerability.NodejsSecurityWg, vulnerability.PythonSafetyDB} +// StandaloneSBOMSet binds sbom dependencies +var StandaloneSBOMSet = wire.NewSet( + sbom.NewArtifact, + StandaloneSuperSet, ) -func ScanImage(imageName, filePath string, severities []vulnerability.Severity, ignoreUnfixed bool) (report.Results, error) { - var results report.Results - var err error - ctx := context.Background() +// StandaloneVMSet binds vm dependencies +var StandaloneVMSet = wire.NewSet( + vm.NewArtifact, + StandaloneSuperSet, +) - image, err := registry.ParseImage(imageName) - if err != nil { - return nil, xerrors.Errorf("invalid image: %w", err) - } - if image.Tag == "latest" { - log.Logger.Warn("You should avoid using the :latest tag as it is cached. You need to specify '--clean' option when :latest image is changed") - } +///////////////// +// Client/Server +///////////////// - var target string - var files extractor.FileMap - if imageName != "" { - target = imageName - files, err = analyzer.Analyze(ctx, imageName) - if err != nil { - return nil, xerrors.Errorf("failed to analyze image: %w", err) - } - } else if filePath != "" { - target = filePath - rc, err := openStream(filePath) - if err != nil { - return nil, xerrors.Errorf("failed to open stream: %w", err) - } +// RemoteSuperSet is used in the client mode +var RemoteSuperSet = wire.NewSet( + client.NewScanner, + wire.Value([]client.Option(nil)), + wire.Bind(new(Driver), new(client.Scanner)), + NewScanner, +) - files, err = analyzer.AnalyzeFromFile(ctx, rc) - if err != nil { - return nil, err - } - } else { - return nil, xerrors.New("image name or image file must be specified") - } +// RemoteFilesystemSet binds filesystem dependencies for client/server mode +var RemoteFilesystemSet = wire.NewSet( + flocal.NewArtifact, + RemoteSuperSet, +) - osFamily, osVersion, osVulns, err := ospkg.Scan(files) - if err != nil { - return nil, xerrors.New("failed to scan image") +// RemoteRepositorySet binds repository dependencies for client/server mode +var RemoteRepositorySet = wire.NewSet( + repo.NewArtifact, + RemoteSuperSet, +) - } +// RemoteSBOMSet binds sbom dependencies for client/server mode +var RemoteSBOMSet = wire.NewSet( + sbom.NewArtifact, + RemoteSuperSet, +) - results = append(results, report.Result{ - FileName: fmt.Sprintf("%s (%s %s)", target, osFamily, osVersion), - Vulnerabilities: processVulnerabilties(osVulns, severities, ignoreUnfixed), - }) +// RemoteVMSet binds vm dependencies for client/server mode +var RemoteVMSet = wire.NewSet( + vm.NewArtifact, + RemoteSuperSet, +) - libVulns, err := library.Scan(files) - if err != nil { - return nil, xerrors.New("failed to scan libraries") - } - for path, vulns := range libVulns { - results = append(results, report.Result{ - FileName: path, - Vulnerabilities: processVulnerabilties(vulns, severities, ignoreUnfixed), - }) - } +// RemoteDockerSet binds remote docker dependencies +var RemoteDockerSet = wire.NewSet( + aimage.NewArtifact, + image.NewContainerImage, + RemoteSuperSet, +) - return results, nil -} +// RemoteArchiveSet binds remote archive dependencies +var RemoteArchiveSet = wire.NewSet( + aimage.NewArtifact, + image.NewArchiveImage, + RemoteSuperSet, +) -func ScanFile(f *os.File, severities []vulnerability.Severity) (report.Result, error) { - vulns, err := library.ScanFile(f) - if err != nil { - return report.Result{}, xerrors.New("failed to scan libraries in file") - } - result := report.Result{ - FileName: f.Name(), - Vulnerabilities: processVulnerabilties(vulns, severities, false), - } - return result, nil +// Scanner implements the Artifact and Driver operations +type Scanner struct { + driver Driver + artifact artifact.Artifact } -func processVulnerabilties(vulns []types.Vulnerability, severities []vulnerability.Severity, ignoreUnfixed bool) []types.Vulnerability { - var vulnerabilities []types.Vulnerability - for _, vuln := range vulns { - sev, title, description, references := getDetail(vuln.VulnerabilityID) - - // Filter vulnerabilities by severity - for _, s := range severities { - if s == sev { - vuln.Severity = fmt.Sprint(sev) - vuln.Title = title - vuln.Description = description - vuln.References = references - - // Ignore unfixed vulnerabilities - if ignoreUnfixed && vuln.FixedVersion == "" { - continue - } - vulnerabilities = append(vulnerabilities, vuln) - break - } - } - } - sort.Slice(vulnerabilities, func(i, j int) bool { - if vulnerabilities[i].PkgName != vulnerabilities[j].PkgName { - return vulnerabilities[i].PkgName < vulnerabilities[j].PkgName - } - return vulnerability.CompareSeverityString(vulnerabilities[j].Severity, vulnerabilities[i].Severity) - }) - return vulnerabilities +// Driver defines operations of scanner +type Driver interface { + Scan(ctx context.Context, target, artifactKey string, blobKeys []string, options types.ScanOptions) ( + results types.Results, osFound ftypes.OS, err error) } -func openStream(path string) (*os.File, error) { - if path == "-" { - if terminal.IsTerminal(0) { - flag.Usage() - os.Exit(64) - } else { - return os.Stdin, nil - } +// NewScanner is the factory method of Scanner +func NewScanner(driver Driver, ar artifact.Artifact) Scanner { + return Scanner{ + driver: driver, + artifact: ar, } - return os.Open(path) } -func getDetail(vulnID string) (vulnerability.Severity, string, string, []string) { - details, err := vulnerability.Get(vulnID) +// ScanArtifact scans the artifacts and returns results +func (s Scanner) ScanArtifact(ctx context.Context, options types.ScanOptions) (types.Report, error) { + artifactInfo, err := s.artifact.Inspect(ctx) if err != nil { - log.Logger.Debug(err) - return vulnerability.SeverityUnknown, "", "", nil - } else if len(details) == 0 { - return vulnerability.SeverityUnknown, "", "", nil + return types.Report{}, xerrors.Errorf("failed analysis: %w", err) } - return getSeverity(details), getTitle(details), getDescription(details), getReferences(details) -} - -func getSeverity(details map[string]vulnerability.Vulnerability) vulnerability.Severity { - for _, source := range sources { - d, ok := details[source] - if !ok { - continue + defer func() { + if err := s.artifact.Clean(artifactInfo); err != nil { + log.Logger.Warnf("Failed to clean the artifact %q: %v", artifactInfo.Name, err) } - if d.Severity != 0 { - return d.Severity - } else if d.SeverityV3 != 0 { - return d.SeverityV3 - } else if d.CvssScore > 0 { - return scoreToSeverity(d.CvssScore) - } else if d.CvssScoreV3 > 0 { - return scoreToSeverity(d.CvssScoreV3) - } - } - return vulnerability.SeverityUnknown -} + }() -func getTitle(details map[string]vulnerability.Vulnerability) string { - for _, source := range sources { - d, ok := details[source] - if !ok { - continue - } - if d.Title != "" { - return d.Title - } - } - return "" + results, osFound, err := s.driver.Scan(ctx, artifactInfo.Name, artifactInfo.ID, artifactInfo.BlobIDs, options) + if err != nil { + return types.Report{}, xerrors.Errorf("scan failed: %w", err) + } + + ptros := &osFound + if osFound.Detected() && osFound.Eosl { + log.Logger.Warnf("This OS version is no longer supported by the distribution: %s %s", osFound.Family, osFound.Name) + log.Logger.Warnf("The vulnerability detection may be insufficient because security updates are not provided") + } else if !osFound.Detected() { + ptros = nil + } + + // Layer makes sense only when scanning container images + if artifactInfo.Type != ftypes.ArtifactContainerImage { + removeLayer(results) + } + + return types.Report{ + SchemaVersion: report.SchemaVersion, + CreatedAt: clock.Now(ctx), + ArtifactName: artifactInfo.Name, + ArtifactType: artifactInfo.Type, + Metadata: types.Metadata{ + OS: ptros, + + // Container image + ImageID: artifactInfo.ImageMetadata.ID, + DiffIDs: artifactInfo.ImageMetadata.DiffIDs, + RepoTags: artifactInfo.ImageMetadata.RepoTags, + RepoDigests: artifactInfo.ImageMetadata.RepoDigests, + ImageConfig: artifactInfo.ImageMetadata.ConfigFile, + }, + Results: results, + BOM: artifactInfo.BOM, + }, nil } -func getDescription(details map[string]vulnerability.Vulnerability) string { - for _, source := range sources { - d, ok := details[source] - if !ok { - continue - } - if d.Description != "" { - return d.Description - } - } - return "" -} +func removeLayer(results types.Results) { + for i := range results { + result := results[i] -func getReferences(details map[string]vulnerability.Vulnerability) []string { - references := map[string]struct{}{} - for _, source := range sources { - d, ok := details[source] - if !ok { - continue + for j := range result.Packages { + result.Packages[j].Layer = ftypes.Layer{} } - for _, ref := range d.References { - references[ref] = struct{}{} + for j := range result.Vulnerabilities { + result.Vulnerabilities[j].Layer = ftypes.Layer{} + } + for j := range result.Misconfigurations { + result.Misconfigurations[j].Layer = ftypes.Layer{} } } - var refs []string - for ref := range references { - refs = append(refs, ref) - } - return refs -} - -func scoreToSeverity(score float64) vulnerability.Severity { - if score >= 9.0 { - return vulnerability.SeverityCritical - } else if score >= 7.0 { - return vulnerability.SeverityHigh - } else if score >= 4.0 { - return vulnerability.SeverityMedium - } else if score > 0.0 { - return vulnerability.SeverityLow - } - return vulnerability.SeverityUnknown } diff --git a/pkg/scanner/scan_test.go b/pkg/scanner/scan_test.go new file mode 100644 index 000000000000..78606299303b --- /dev/null +++ b/pkg/scanner/scan_test.go @@ -0,0 +1,215 @@ +package scanner + +import ( + "context" + "errors" + "github.com/aquasecurity/trivy/pkg/clock" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestScanner_ScanArtifact(t *testing.T) { + type args struct { + options types.ScanOptions + } + tests := []struct { + name string + args args + inspectExpectation artifact.ArtifactInspectExpectation + scanExpectation DriverScanExpectation + want types.Report + wantErr string + }{ + { + name: "happy path", + args: args{ + options: types.ScanOptions{VulnType: []string{"os"}}, + }, + inspectExpectation: artifact.ArtifactInspectExpectation{ + Args: artifact.ArtifactInspectArgs{ + CtxAnything: true, + }, + Returns: artifact.ArtifactInspectReturns{ + Reference: ftypes.ArtifactReference{ + Name: "alpine:3.11", + Type: ftypes.ArtifactContainerImage, + ID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + ImageMetadata: ftypes.ImageMetadata{ + ID: "sha256:e389ae58922402a7ded319e79f06ac428d05698d8e61ecbe88d2cf850e42651d", + DiffIDs: []string{"sha256:9a5d14f9f5503e55088666beef7e85a8d9625d4fa7418e2fe269e9c54bcb853c"}, + RepoTags: []string{"alpine:3.11"}, + RepoDigests: []string{"alpine@sha256:0bd0e9e03a022c3b0226667621da84fc9bf562a9056130424b5bfbd8bcb0397f"}, + }, + }, + }, + }, + scanExpectation: DriverScanExpectation{ + Args: DriverScanArgs{ + CtxAnything: true, + Target: "alpine:3.11", + ImageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + LayerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + Options: types.ScanOptions{VulnType: []string{"os"}}, + }, + Returns: DriverScanReturns{ + Results: types.Results{ + { + Target: "alpine:3.11", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-9999", + PkgName: "vim", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Layer: ftypes.Layer{ + Digest: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + }, + }, + { + Target: "node-app/package-lock.json", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-11358", + PkgName: "jquery", + InstalledVersion: "3.3.9", + FixedVersion: ">=3.4.0", + }, + }, + Type: "npm", + }, + }, + OsFound: ftypes.OS{ + Family: "alpine", + Name: "3.10", + Eosl: true, + }, + }, + }, + want: types.Report{ + SchemaVersion: 2, + CreatedAt: time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC), + ArtifactName: "alpine:3.11", + ArtifactType: ftypes.ArtifactContainerImage, + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.10", + Eosl: true, + }, + ImageID: "sha256:e389ae58922402a7ded319e79f06ac428d05698d8e61ecbe88d2cf850e42651d", + DiffIDs: []string{"sha256:9a5d14f9f5503e55088666beef7e85a8d9625d4fa7418e2fe269e9c54bcb853c"}, + RepoTags: []string{"alpine:3.11"}, + RepoDigests: []string{"alpine@sha256:0bd0e9e03a022c3b0226667621da84fc9bf562a9056130424b5bfbd8bcb0397f"}, + }, + Results: types.Results{ + { + Target: "alpine:3.11", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-9999", + PkgName: "vim", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Layer: ftypes.Layer{ + Digest: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + }, + }, + }, + }, + { + Target: "node-app/package-lock.json", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-11358", + PkgName: "jquery", + InstalledVersion: "3.3.9", + FixedVersion: ">=3.4.0", + }, + }, + Type: "npm", + }, + }, + }, + }, + { + name: "sad path: AnalyzerAnalyze returns an error", + args: args{ + options: types.ScanOptions{VulnType: []string{"os"}}, + }, + inspectExpectation: artifact.ArtifactInspectExpectation{ + Args: artifact.ArtifactInspectArgs{ + CtxAnything: true, + }, + Returns: artifact.ArtifactInspectReturns{ + Err: errors.New("error"), + }, + }, + wantErr: "failed analysis", + }, + { + name: "sad path: Scan returns an error", + args: args{ + options: types.ScanOptions{VulnType: []string{"os"}}, + }, + inspectExpectation: artifact.ArtifactInspectExpectation{ + Args: artifact.ArtifactInspectArgs{ + CtxAnything: true, + }, + Returns: artifact.ArtifactInspectReturns{ + Reference: ftypes.ArtifactReference{ + Name: "alpine:3.11", + ID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + }, + }, + }, + scanExpectation: DriverScanExpectation{ + Args: DriverScanArgs{ + CtxAnything: true, + Target: "alpine:3.11", + ImageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", + LayerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + Options: types.ScanOptions{VulnType: []string{"os"}}, + }, + Returns: DriverScanReturns{ + Err: errors.New("error"), + }, + }, + wantErr: "scan failed", + }, + } + for _, tt := range tests { + ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) + t.Run(tt.name, func(t *testing.T) { + d := new(MockDriver) + d.ApplyScanExpectation(tt.scanExpectation) + + mockArtifact := new(artifact.MockArtifact) + mockArtifact.ApplyInspectExpectation(tt.inspectExpectation) + + s := NewScanner(d, mockArtifact) + got, err := s.ScanArtifact(ctx, tt.args.options) + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + require.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } else { + require.NoError(t, err, tt.name) + } + + assert.Equal(t, tt.want, got, tt.name) + }) + } +} diff --git a/pkg/scanner/utils/utils.go b/pkg/scanner/utils/utils.go index e11f430bc654..74b5397525b1 100644 --- a/pkg/scanner/utils/utils.go +++ b/pkg/scanner/utils/utils.go @@ -2,39 +2,28 @@ package utils import ( "fmt" - "strings" - "github.com/knqyf263/fanal/analyzer" - - "github.com/knqyf263/go-version" - "github.com/knqyf263/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) -var ( - replacer = strings.NewReplacer(".alpha", "-alpha", ".beta", "-beta", ".rc", "-rc") -) +// FormatVersion formats the package version based on epoch, version & release +func FormatVersion(pkg types.Package) string { + return formatVersion(pkg.Epoch, pkg.Version, pkg.Release) +} -func MatchVersions(currentVersion *version.Version, rangeVersions []string) bool { - for _, p := range rangeVersions { - c, err := version.NewConstraint(replacer.Replace(p)) - if err != nil { - log.Logger.Debug("NewConstraint", "error", err) - return false - } - if c.Check(currentVersion) { - return true - } - } - return false +// FormatSrcVersion formats the package version based on source epoch, version & release +func FormatSrcVersion(pkg types.Package) string { + return formatVersion(pkg.SrcEpoch, pkg.SrcVersion, pkg.SrcRelease) } -func FormatVersion(pkg analyzer.Package) string { - v := pkg.Version - if pkg.Release != "" { - v = fmt.Sprintf("%s-%s", v, pkg.Release) +func formatVersion(epoch int, version, release string) string { + v := version + if release != "" { + v = fmt.Sprintf("%s-%s", v, release) } - if pkg.Epoch != 0 { - v = fmt.Sprintf("%d:%s", pkg.Epoch, v) + if epoch != 0 { + v = fmt.Sprintf("%d:%s", epoch, v) } return v + } diff --git a/pkg/scanner/utils/utils_test.go b/pkg/scanner/utils/utils_test.go new file mode 100644 index 000000000000..e4c28cebf811 --- /dev/null +++ b/pkg/scanner/utils/utils_test.go @@ -0,0 +1,73 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestFormatSrcVersion(t *testing.T) { + tests := []struct { + name string + pkg types.Package + want string + }{ + { + name: "happy path", + pkg: types.Package{ + SrcVersion: "1.2.3", + SrcRelease: "1", + }, + want: "1.2.3-1", + }, + { + name: "with epoch", + pkg: types.Package{ + SrcEpoch: 2, + SrcVersion: "1.2.3", + SrcRelease: "alpha", + }, + want: "2:1.2.3-alpha", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := FormatSrcVersion(tt.pkg) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestFormatVersion(t *testing.T) { + tests := []struct { + name string + pkg types.Package + want string + }{ + { + name: "happy path", + pkg: types.Package{ + Version: "1.2.3", + Release: "1", + }, + want: "1.2.3-1", + }, + { + name: "with epoch", + pkg: types.Package{ + Epoch: 2, + Version: "1.2.3", + Release: "alpha", + }, + want: "2:1.2.3-alpha", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := FormatVersion(tt.pkg) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/semaphore/semaphore.go b/pkg/semaphore/semaphore.go new file mode 100644 index 000000000000..52005ab43e79 --- /dev/null +++ b/pkg/semaphore/semaphore.go @@ -0,0 +1,12 @@ +package semaphore + +import "golang.org/x/sync/semaphore" + +const defaultSize = 5 + +func New(parallel int) *semaphore.Weighted { + if parallel == 0 { + parallel = defaultSize + } + return semaphore.NewWeighted(int64(parallel)) +} diff --git a/pkg/types/finding.go b/pkg/types/finding.go new file mode 100644 index 000000000000..9e194b8e6b74 --- /dev/null +++ b/pkg/types/finding.go @@ -0,0 +1,47 @@ +package types + +type FindingType string +type FindingStatus string + +const ( + FindingTypeVulnerability FindingType = "vulnerability" + FindingTypeMisconfiguration FindingType = "misconfiguration" + FindingTypeSecret FindingType = "secret" + FindingTypeLicense FindingType = "license" + + FindingStatusIgnored FindingStatus = "ignored" // Trivy + FindingStatusUnknown FindingStatus = "unknown" // Trivy + FindingStatusNotAffected FindingStatus = "not_affected" // VEX + FindingStatusAffected FindingStatus = "affected" // VEX + FindingStatusFixed FindingStatus = "fixed" // VEX + FindingStatusUnderInvestigation FindingStatus = "under_investigation" // VEX +) + +// Finding represents one of the findings that Trivy can detect, +// such as vulnerabilities, misconfigurations, secrets, and licenses. +type finding interface { + findingType() FindingType +} + +// ModifiedFinding represents a security finding that has been modified by an external source, +// such as .trivyignore and VEX. Currently, it is primarily used to account for vulnerabilities +// that are ignored via .trivyignore or identified as not impactful through VEX. +// However, it is planned to also store vulnerabilities whose severity has been adjusted by VEX, +// or that have been detected through Wasm modules in the future. +type ModifiedFinding struct { + Type FindingType + Status FindingStatus + Statement string + Source string + Finding finding // one of findings +} + +func NewModifiedFinding(f finding, status FindingStatus, statement, source string) ModifiedFinding { + return ModifiedFinding{ + Type: f.findingType(), + Status: status, + Statement: statement, + Source: source, + Finding: f, + } +} diff --git a/pkg/types/library.go b/pkg/types/library.go index fc4b5eb9719c..02dfc8a64839 100644 --- a/pkg/types/library.go +++ b/pkg/types/library.go @@ -1,6 +1,7 @@ package types -type Library struct{ - Name string +// Library holds the attribute of a package library +type Library struct { + Name string Version string } diff --git a/pkg/types/license.go b/pkg/types/license.go new file mode 100644 index 000000000000..baca0328a457 --- /dev/null +++ b/pkg/types/license.go @@ -0,0 +1,33 @@ +package types + +import ( + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type DetectedLicense struct { + // Severity is the consistent parameter indicating how severe the issue is + Severity string + + // Category holds the license category such as "forbidden" + Category types.LicenseCategory + + // PkgName holds a package name of the license. + // It will be empty if FilePath is filled. + PkgName string + + // PkgName holds a file path of the license. + // It will be empty if PkgName is filled. + FilePath string // for file license + + // Name holds a detected license name + Name string + + // Confidence is level of the match. The confidence level is between 0.0 and 1.0, with 1.0 indicating an + // exact match and 0.0 indicating a complete mismatch + Confidence float64 + + // Link is a SPDX link of the license + Link string +} + +func (DetectedLicense) findingType() FindingType { return FindingTypeLicense } diff --git a/pkg/types/misconfiguration.go b/pkg/types/misconfiguration.go new file mode 100644 index 000000000000..ebd21a23d36b --- /dev/null +++ b/pkg/types/misconfiguration.go @@ -0,0 +1,41 @@ +package types + +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + +// DetectedMisconfiguration holds detected misconfigurations +type DetectedMisconfiguration struct { + Type string `json:",omitempty"` + ID string `json:",omitempty"` + AVDID string `json:",omitempty"` + Title string `json:",omitempty"` + Description string `json:",omitempty"` + Message string `json:",omitempty"` + Namespace string `json:",omitempty"` + Query string `json:",omitempty"` + Resolution string `json:",omitempty"` + Severity string `json:",omitempty"` + PrimaryURL string `json:",omitempty"` + References []string `json:",omitempty"` + Status MisconfStatus `json:",omitempty"` + Layer ftypes.Layer `json:",omitempty"` + CauseMetadata ftypes.CauseMetadata `json:",omitempty"` + + // For debugging + Traces []string `json:",omitempty"` +} + +// MisconfStatus represents a status of misconfiguration +type MisconfStatus string + +const ( + // MisconfStatusPassed represents successful status + MisconfStatusPassed MisconfStatus = "PASS" + + // MisconfStatusFailure represents failure status + MisconfStatusFailure MisconfStatus = "FAIL" + + // MisconfStatusException Passed represents the status of exception + MisconfStatusException MisconfStatus = "EXCEPTION" +) + +func (DetectedMisconfiguration) findingType() FindingType { return FindingTypeMisconfiguration } diff --git a/pkg/types/report.go b/pkg/types/report.go new file mode 100644 index 000000000000..e6c96d9564eb --- /dev/null +++ b/pkg/types/report.go @@ -0,0 +1,156 @@ +package types + +import ( + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" // nolint: goimports + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/sbom/core" +) + +// Report represents a scan result +type Report struct { + SchemaVersion int `json:",omitempty"` + CreatedAt time.Time `json:",omitempty"` + ArtifactName string `json:",omitempty"` + ArtifactType ftypes.ArtifactType `json:",omitempty"` + Metadata Metadata `json:",omitempty"` + Results Results `json:",omitempty"` + + // parsed SBOM + BOM *core.BOM `json:"-"` // Just for internal usage, not exported in JSON +} + +// Metadata represents a metadata of artifact +type Metadata struct { + Size int64 `json:",omitempty"` + OS *ftypes.OS `json:",omitempty"` + + // Container image + ImageID string `json:",omitempty"` + DiffIDs []string `json:",omitempty"` + RepoTags []string `json:",omitempty"` + RepoDigests []string `json:",omitempty"` + ImageConfig v1.ConfigFile `json:",omitempty"` +} + +// Results to hold list of Result +type Results []Result + +type ResultClass string +type Compliance = string +type Format string + +const ( + ClassUnknown ResultClass = "unknown" + ClassOSPkg ResultClass = "os-pkgs" // For detected packages and vulnerabilities in OS packages + ClassLangPkg ResultClass = "lang-pkgs" // For detected packages and vulnerabilities in language-specific packages + ClassConfig ResultClass = "config" // For detected misconfigurations + ClassSecret ResultClass = "secret" // For detected secrets + ClassLicense ResultClass = "license" // For detected package licenses + ClassLicenseFile ResultClass = "license-file" // For detected licenses in files + ClassCustom ResultClass = "custom" + + ComplianceK8sNsa = Compliance("k8s-nsa") + ComplianceK8sCIS = Compliance("k8s-cis") + ComplianceK8sPSSBaseline = Compliance("k8s-pss-baseline") + ComplianceK8sPSSRestricted = Compliance("k8s-pss-restricted") + ComplianceAWSCIS12 = Compliance("aws-cis-1.2") + ComplianceAWSCIS14 = Compliance("aws-cis-1.4") + ComplianceDockerCIS = Compliance("docker-cis") + + FormatTable Format = "table" + FormatJSON Format = "json" + FormatTemplate Format = "template" + FormatSarif Format = "sarif" + FormatCycloneDX Format = "cyclonedx" + FormatSPDX Format = "spdx" + FormatSPDXJSON Format = "spdx-json" + FormatGitHub Format = "github" + FormatCosignVuln Format = "cosign-vuln" +) + +var ( + SupportedFormats = []Format{ + FormatTable, + FormatJSON, + FormatTemplate, + FormatSarif, + FormatCycloneDX, + FormatSPDX, + FormatSPDXJSON, + FormatGitHub, + FormatCosignVuln, + } + SupportedSBOMFormats = []Format{ + FormatCycloneDX, + FormatSPDX, + FormatSPDXJSON, + FormatGitHub, + } + SupportedCompliances = []string{ + ComplianceK8sNsa, + ComplianceK8sCIS, + ComplianceK8sPSSBaseline, + ComplianceK8sPSSRestricted, + ComplianceAWSCIS12, + ComplianceAWSCIS14, + ComplianceDockerCIS, + } +) + +// Result holds a target and detected vulnerabilities +type Result struct { + Target string `json:"Target"` + Class ResultClass `json:"Class,omitempty"` + Type ftypes.TargetType `json:"Type,omitempty"` + Packages []ftypes.Package `json:"Packages,omitempty"` + Vulnerabilities []DetectedVulnerability `json:"Vulnerabilities,omitempty"` + MisconfSummary *MisconfSummary `json:"MisconfSummary,omitempty"` + Misconfigurations []DetectedMisconfiguration `json:"Misconfigurations,omitempty"` + Secrets []DetectedSecret `json:"Secrets,omitempty"` + Licenses []DetectedLicense `json:"Licenses,omitempty"` + CustomResources []ftypes.CustomResource `json:"CustomResources,omitempty"` + + // ModifiedFindings holds a list of findings that have been modified from their original state. + // This can include vulnerabilities that have been marked as ignored, not affected, or have had + // their severity adjusted. It is currently available only in the table format. + ModifiedFindings []ModifiedFinding `json:"-"` +} + +func (r *Result) IsEmpty() bool { + return len(r.Packages) == 0 && len(r.Vulnerabilities) == 0 && len(r.Misconfigurations) == 0 && + len(r.Secrets) == 0 && len(r.Licenses) == 0 && len(r.CustomResources) == 0 && len(r.ModifiedFindings) == 0 +} + +type MisconfSummary struct { + Successes int + Failures int + Exceptions int +} + +func (s MisconfSummary) Empty() bool { + return s.Successes == 0 && s.Failures == 0 && s.Exceptions == 0 +} + +// Failed returns whether the result includes any vulnerabilities, misconfigurations or secrets +func (results Results) Failed() bool { + for _, r := range results { + if len(r.Vulnerabilities) > 0 { + return true + } + for _, m := range r.Misconfigurations { + if m.Status == MisconfStatusFailure { + return true + } + } + if len(r.Secrets) > 0 { + return true + } + if len(r.Licenses) > 0 { + return true + } + } + return false +} diff --git a/pkg/types/sbom.go b/pkg/types/sbom.go new file mode 100644 index 000000000000..82f6139e48f7 --- /dev/null +++ b/pkg/types/sbom.go @@ -0,0 +1,29 @@ +package types + +import ( + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/sbom/core" +) + +type SBOMSource = string + +type SBOM struct { + Metadata Metadata + + Packages []ftypes.PackageInfo + Applications []ftypes.Application + + BOM *core.BOM +} + +const ( + SBOMSourceOCI = SBOMSource("oci") + SBOMSourceRekor = SBOMSource("rekor") +) + +var ( + SBOMSources = []string{ + SBOMSourceOCI, + SBOMSourceRekor, + } +) diff --git a/pkg/types/scan.go b/pkg/types/scan.go new file mode 100644 index 000000000000..0b5833b12faa --- /dev/null +++ b/pkg/types/scan.go @@ -0,0 +1,33 @@ +package types + +import ( + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// ScanTarget holds the attributes for scanning. +type ScanTarget struct { + Name string // container image name, file path, etc + OS types.OS + Repository *types.Repository + Packages types.Packages + Applications []types.Application + Misconfigurations []types.Misconfiguration + Secrets []types.Secret + Licenses []types.LicenseFile + + // CustomResources hold analysis results from custom analyzers. + // It is for extensibility and not used in OSS. + CustomResources []types.CustomResource +} + +// ScanOptions holds the attributes for scanning vulnerabilities +type ScanOptions struct { + VulnType []string + Scanners Scanners + ImageConfigScanners Scanners // Scanners for container image configuration + ScanRemovedPackages bool + ListAllPackages bool + LicenseCategories map[types.LicenseCategory][]string + FilePatterns []string + IncludeDevDeps bool +} diff --git a/pkg/types/secret.go b/pkg/types/secret.go new file mode 100644 index 000000000000..4e86059575e1 --- /dev/null +++ b/pkg/types/secret.go @@ -0,0 +1,9 @@ +package types + +import ( + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type DetectedSecret ftypes.SecretFinding + +func (DetectedSecret) findingType() FindingType { return FindingTypeSecret } diff --git a/pkg/types/target.go b/pkg/types/target.go new file mode 100644 index 000000000000..134bded69cd7 --- /dev/null +++ b/pkg/types/target.go @@ -0,0 +1,85 @@ +package types + +import ( + "golang.org/x/exp/slices" +) + +// VulnType represents vulnerability type +type VulnType = string + +// Scanner represents the type of security scanning +type Scanner string + +// Scanners is a slice of scanners +type Scanners []Scanner + +const ( + // VulnTypeUnknown is a vulnerability type of unknown + VulnTypeUnknown = VulnType("unknown") + + // VulnTypeOS is a vulnerability type of OS packages + VulnTypeOS = VulnType("os") + + // VulnTypeLibrary is a vulnerability type of programming language dependencies + VulnTypeLibrary = VulnType("library") + + // UnknownScanner is the scanner of unknown + UnknownScanner = Scanner("unknown") + + // NoneScanner is the scanner of none + NoneScanner = Scanner("none") + + // VulnerabilityScanner is the scanner of vulnerabilities + VulnerabilityScanner = Scanner("vuln") + + // MisconfigScanner is the scanner of misconfigurations + MisconfigScanner = Scanner("misconfig") + + // SecretScanner is the scanner of secrets + SecretScanner = Scanner("secret") + + // RBACScanner is the scanner of rbac assessment + RBACScanner = Scanner("rbac") + + // LicenseScanner is the scanner of licenses + LicenseScanner = Scanner("license") +) + +var ( + VulnTypes = []string{ + VulnTypeOS, + VulnTypeLibrary, + } + + AllScanners = Scanners{ + VulnerabilityScanner, + MisconfigScanner, + RBACScanner, + SecretScanner, + LicenseScanner, + NoneScanner, + } + + // AllImageConfigScanners has a list of available scanners on container image config. + // The container image in container registries consists of manifest, config and layers. + // Trivy is also able to detect security issues on the image config. + AllImageConfigScanners = Scanners{ + MisconfigScanner, + SecretScanner, + NoneScanner, + } +) + +func (scanners Scanners) Enabled(s Scanner) bool { + return slices.Contains(scanners, s) +} + +// AnyEnabled returns true if any of the passed scanners is included. +func (scanners Scanners) AnyEnabled(ss ...Scanner) bool { + for _, s := range ss { + if scanners.Enabled(s) { + return true + } + } + return false +} diff --git a/pkg/types/vulnerability.go b/pkg/types/vulnerability.go index 6413d9477e85..693b212d077d 100644 --- a/pkg/types/vulnerability.go +++ b/pkg/types/vulnerability.go @@ -1,13 +1,61 @@ package types -type Vulnerability struct { - VulnerabilityID string - PkgName string - InstalledVersion string - FixedVersion string - - Title string - Description string - Severity string - References []string +import ( + "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// DetectedVulnerability holds the information of detected vulnerabilities +type DetectedVulnerability struct { + VulnerabilityID string `json:",omitempty"` + VendorIDs []string `json:",omitempty"` + PkgID string `json:",omitempty"` // It is used to construct dependency graph. + PkgName string `json:",omitempty"` + PkgPath string `json:",omitempty"` // This field is populated in the case of language-specific packages such as egg/wheel and gemspec + PkgIdentifier ftypes.PkgIdentifier `json:",omitempty"` + InstalledVersion string `json:",omitempty"` + FixedVersion string `json:",omitempty"` + Status types.Status `json:",omitempty"` + Layer ftypes.Layer `json:",omitempty"` + SeveritySource types.SourceID `json:",omitempty"` + PrimaryURL string `json:",omitempty"` + + // DataSource holds where the advisory comes from + DataSource *types.DataSource `json:",omitempty"` + + // Custom is for extensibility and not supposed to be used in OSS + Custom interface{} `json:",omitempty"` + + // Embed vulnerability details + types.Vulnerability +} + +func (DetectedVulnerability) findingType() FindingType { return FindingTypeVulnerability } + +// BySeverity implements sort.Interface based on the Severity field. +type BySeverity []DetectedVulnerability + +// Len returns the length of DetectedVulnerabilities +func (v BySeverity) Len() int { return len(v) } + +// Less compares 2 DetectedVulnerabilities based on package name, severity, vulnerabilityID and package path +func (v BySeverity) Less(i, j int) bool { + if v[i].PkgName != v[j].PkgName { + return v[i].PkgName < v[j].PkgName + } else if v[i].InstalledVersion != v[j].InstalledVersion { + return v[i].InstalledVersion < v[j].InstalledVersion + } + ret := types.CompareSeverityString( + v[j].Severity, v[i].Severity, + ) + if ret != 0 { + return ret > 0 + } + if v[i].VulnerabilityID != v[j].VulnerabilityID { + return v[i].VulnerabilityID < v[j].VulnerabilityID + } + return v[i].PkgPath < v[j].PkgPath } + +// Swap swaps 2 vulnerability +func (v BySeverity) Swap(i, j int) { v[i], v[j] = v[j], v[i] } diff --git a/pkg/utils/fsutils/fs.go b/pkg/utils/fsutils/fs.go new file mode 100644 index 000000000000..8e15a575a753 --- /dev/null +++ b/pkg/utils/fsutils/fs.go @@ -0,0 +1,123 @@ +package fsutils + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + + "go.uber.org/zap" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" +) + +const ( + xdgDataHome = "XDG_DATA_HOME" +) + +var cacheDir string + +// defaultCacheDir returns/creates the cache-dir to be used for trivy operations +func defaultCacheDir() string { + tmpDir, err := os.UserCacheDir() + if err != nil { + tmpDir = os.TempDir() + } + return filepath.Join(tmpDir, "trivy") +} + +// CacheDir returns the directory used for caching +func CacheDir() string { + if cacheDir == "" { + return defaultCacheDir() + } + return cacheDir +} + +// SetCacheDir sets the trivy cacheDir +func SetCacheDir(dir string) { + cacheDir = dir +} + +func HomeDir() string { + dataHome := os.Getenv(xdgDataHome) + if dataHome != "" { + return dataHome + } + + homeDir, _ := os.UserHomeDir() + return homeDir +} + +// CopyFile copies the file content from scr to dst +func CopyFile(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, xerrors.Errorf("file (%s) stat error: %w", src, err) + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + defer destination.Close() + n, err := io.Copy(destination, source) + return n, err +} + +func DirExists(path string) bool { + if f, err := os.Stat(path); os.IsNotExist(err) || !f.IsDir() { + return false + } + return true +} + +type WalkDirRequiredFunc func(path string, d fs.DirEntry) bool + +type WalkDirFunc func(path string, d fs.DirEntry, r io.Reader) error + +func WalkDir(fsys fs.FS, root string, required WalkDirRequiredFunc, fn WalkDirFunc) error { + return fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } else if !d.Type().IsRegular() || !required(path, d) { + return nil + } + + f, err := fsys.Open(path) + if err != nil { + return xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + if err = fn(path, d, f); err != nil { + log.Logger.Debugw("Walk error", zap.String("file_path", path), zap.Error(err)) + } + return nil + }) +} + +func RequiredExt(exts ...string) WalkDirRequiredFunc { + return func(filePath string, _ fs.DirEntry) bool { + return slices.Contains(exts, filepath.Ext(filePath)) + } +} + +func RequiredFile(fileNames ...string) WalkDirRequiredFunc { + return func(filePath string, _ fs.DirEntry) bool { + return slices.Contains(fileNames, filepath.Base(filePath)) + } +} diff --git a/pkg/utils/fsutils/fs_test.go b/pkg/utils/fsutils/fs_test.go new file mode 100644 index 000000000000..b6304ea7edb3 --- /dev/null +++ b/pkg/utils/fsutils/fs_test.go @@ -0,0 +1,74 @@ +package fsutils + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func touch(t *testing.T, name string) { + f, err := os.Create(name) + if err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } +} + +func write(t *testing.T, name string, content string) { + err := os.WriteFile(name, []byte(content), 0666) + if err != nil { + t.Fatal(err) + } +} + +func TestCopyFile(t *testing.T) { + type args struct { + src string + dst string + } + tests := []struct { + name string + args args + content []byte + want string + wantErr string + }{ + { + name: "happy path", + content: []byte("this is a content"), + args: args{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + src := tt.args.src + if tt.args.src == "" { + s, err := os.CreateTemp("", "src") + require.NoError(t, err, tt.name) + _, err = s.Write(tt.content) + require.NoError(t, err, tt.name) + src = s.Name() + } + + dst := tt.args.dst + if tt.args.dst == "" { + d, err := os.CreateTemp("", "dst") + require.NoError(t, err, tt.name) + dst = d.Name() + require.NoError(t, d.Close(), tt.name) + } + + _, err := CopyFile(src, dst) + if tt.wantErr != "" { + require.NotNil(t, err, tt.name) + assert.Equal(t, err.Error(), tt.wantErr, tt.name) + } else { + assert.NoError(t, err, tt.name) + } + }) + } +} diff --git a/pkg/utils/progress.go b/pkg/utils/progress.go deleted file mode 100644 index b01bd9dc19a3..000000000000 --- a/pkg/utils/progress.go +++ /dev/null @@ -1,63 +0,0 @@ -package utils - -import ( - "time" - - "github.com/briandowns/spinner" - pb "gopkg.in/cheggaaa/pb.v1" -) - -var ( - Quiet = false -) - -type Spinner struct { - client *spinner.Spinner -} - -func NewSpinner(suffix string) *Spinner { - if Quiet { - return &Spinner{} - } - s := spinner.New(spinner.CharSets[36], 100*time.Millisecond) - s.Suffix = suffix - return &Spinner{client: s} -} - -func (s *Spinner) Start() { - if s.client == nil { - return - } - s.client.Start() -} -func (s *Spinner) Stop() { - if s.client == nil { - return - } - s.client.Stop() -} - -type ProgressBar struct { - client *pb.ProgressBar -} - -func PbStartNew(total int) *ProgressBar { - if Quiet { - return &ProgressBar{} - } - bar := pb.StartNew(total) - return &ProgressBar{client: bar} -} - -func (p *ProgressBar) Increment() { - if p.client == nil { - return - } - p.client.Increment() -} -func (p *ProgressBar) Finish() { - if p.client == nil { - return - } - p.client.Finish() -} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go deleted file mode 100644 index 0ff3d57f84d8..000000000000 --- a/pkg/utils/utils.go +++ /dev/null @@ -1,113 +0,0 @@ -package utils - -import ( - "bytes" - "io" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/knqyf263/trivy/pkg/log" - "golang.org/x/xerrors" -) - -func CacheDir() string { - cacheDir, err := os.UserCacheDir() - if err != nil { - cacheDir = os.TempDir() - } - dir := filepath.Join(cacheDir, "trivy") - return dir -} - -func FileWalk(root string, targetFiles map[string]struct{}, walkFn func(r io.Reader, path string) error) error { - err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if info.IsDir() { - return nil - } - - rel, err := filepath.Rel(root, path) - if err != nil { - return xerrors.Errorf("error in filepath rel: %w", err) - } - - if _, ok := targetFiles[rel]; !ok { - return nil - } - - if info.Size() == 0 { - log.Logger.Debugf("invalid size: %s", path) - return nil - } - - f, err := os.Open(path) - defer f.Close() - if err != nil { - return xerrors.Errorf("failed to open file: %w", err) - } - - if err = walkFn(f, path); err != nil { - return err - } - return nil - }) - if err != nil { - return xerrors.Errorf("error in file walk: %w", err) - } - return nil -} - -func IsCommandAvailable(name string) bool { - cmd := exec.Command(name, "--help") - if err := cmd.Run(); err != nil { - return false - } - return true -} - -func Exists(path string) (bool, error) { - _, err := os.Stat(path) - if err == nil { - return true, nil - } - if os.IsNotExist(err) { - return false, nil - } - return true, err -} - -func StringInSlice(a string, list []string) bool { - for _, b := range list { - if b == a { - return true - } - } - return false -} - -func Exec(command string, args []string) (string, error) { - cmd := exec.Command(command, args...) - var stdoutBuf, stderrBuf bytes.Buffer - cmd.Stdout = &stdoutBuf - cmd.Stderr = &stderrBuf - if err := cmd.Run(); err != nil { - log.Logger.Debug(stderrBuf.String()) - return "", xerrors.Errorf("failed to exec: %w", err) - } - return stdoutBuf.String(), nil -} - -func FilterTargets(prefixPath string, targets map[string]struct{}) (map[string]struct{}, error) { - filtered := map[string]struct{}{} - for filename := range targets { - if strings.HasPrefix(filename, prefixPath) { - rel, err := filepath.Rel(prefixPath, filename) - if err != nil { - return nil, xerrors.Errorf("error in filepath rel: %w", err) - } - filtered[rel] = struct{}{} - } - } - return filtered, nil -} diff --git a/pkg/uuid/uuid.go b/pkg/uuid/uuid.go new file mode 100644 index 000000000000..3e3ad456a805 --- /dev/null +++ b/pkg/uuid/uuid.go @@ -0,0 +1,36 @@ +//go:build !tinygo.wasm + +package uuid + +import ( + "fmt" + "testing" + + "github.com/google/uuid" +) + +type UUID = uuid.UUID + +var ( + newUUID func() uuid.UUID = uuid.New + Nil = uuid.Nil + MustParse = uuid.MustParse +) + +// SetFakeUUID sets a fake UUID for testing. +// The 'format' is used to generate a fake UUID and +// must contain a single '%d' which will be replaced with a counter. +func SetFakeUUID(t *testing.T, format string) { + var count int + newUUID = func() uuid.UUID { + count++ + return uuid.Must(uuid.Parse(fmt.Sprintf(format, count))) + } + t.Cleanup(func() { + newUUID = uuid.New + }) +} + +func New() UUID { + return newUUID() +} diff --git a/pkg/uuid/uuid_tinygo.go b/pkg/uuid/uuid_tinygo.go new file mode 100644 index 000000000000..fe328cea9282 --- /dev/null +++ b/pkg/uuid/uuid_tinygo.go @@ -0,0 +1,13 @@ +//go:build tinygo.wasm + +package uuid + +// TinyGo doesn't work with github.com/google/uuid + +type UUID string + +func (UUID) String() string { return "" } + +const Nil = "" + +func New() UUID { return "" } diff --git a/pkg/version/testdata/testcache/db/metadata.json b/pkg/version/testdata/testcache/db/metadata.json new file mode 100644 index 000000000000..e9a4157ed5c5 --- /dev/null +++ b/pkg/version/testdata/testcache/db/metadata.json @@ -0,0 +1 @@ +{"Version":2,"NextUpdate":"2023-07-20T18:11:37.696263532Z","UpdatedAt":"2023-07-20T12:11:37.696263932Z","DownloadedAt":"2023-07-25T07:01:41.239158Z"} \ No newline at end of file diff --git a/pkg/version/testdata/testcache/java-db/metadata.json b/pkg/version/testdata/testcache/java-db/metadata.json new file mode 100644 index 000000000000..b8c43fa523c6 --- /dev/null +++ b/pkg/version/testdata/testcache/java-db/metadata.json @@ -0,0 +1 @@ +{"Version":1,"NextUpdate":"2023-07-28T01:03:52.169192565Z","UpdatedAt":"2023-07-25T01:03:52.169192765Z","DownloadedAt":"2023-07-25T09:37:48.906152Z"} diff --git a/pkg/version/testdata/testcache/policy/metadata.json b/pkg/version/testdata/testcache/policy/metadata.json new file mode 100644 index 000000000000..18f6f4801597 --- /dev/null +++ b/pkg/version/testdata/testcache/policy/metadata.json @@ -0,0 +1 @@ +{"Digest":"sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43","DownloadedAt":"2023-07-23T17:40:33.122462+01:00"} \ No newline at end of file diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 000000000000..421fff6f1e6e --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,107 @@ +package version + +import ( + "fmt" + "path/filepath" + + "github.com/aquasecurity/trivy-db/pkg/metadata" + javadb "github.com/aquasecurity/trivy-java-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/policy" +) + +var ( + ver = "dev" +) + +func AppVersion() string { + return ver +} + +type VersionInfo struct { + Version string `json:",omitempty"` + VulnerabilityDB *metadata.Metadata `json:",omitempty"` + JavaDB *metadata.Metadata `json:",omitempty"` + PolicyBundle *policy.Metadata `json:",omitempty"` +} + +func formatDBMetadata(title string, meta metadata.Metadata) string { + return fmt.Sprintf(`%s: + Version: %d + UpdatedAt: %s + NextUpdate: %s + DownloadedAt: %s +`, title, meta.Version, meta.UpdatedAt.UTC(), meta.NextUpdate.UTC(), meta.DownloadedAt.UTC()) +} + +func (v *VersionInfo) String() string { + output := fmt.Sprintf("Version: %s\n", v.Version) + if v.VulnerabilityDB != nil { + output += formatDBMetadata("Vulnerability DB", *v.VulnerabilityDB) + } + if v.JavaDB != nil { + output += formatDBMetadata("Java DB", *v.JavaDB) + } + if v.PolicyBundle != nil { + output += v.PolicyBundle.String() + } + return output +} + +func NewVersionInfo(cacheDir string) VersionInfo { + var dbMeta *metadata.Metadata + var javadbMeta *metadata.Metadata + + mc := metadata.NewClient(cacheDir) + meta, err := mc.Get() + if err != nil { + log.Logger.Debugw("Failed to get DB metadata", "error", err) + } + if !meta.UpdatedAt.IsZero() && !meta.NextUpdate.IsZero() && meta.Version != 0 { + dbMeta = &metadata.Metadata{ + Version: meta.Version, + NextUpdate: meta.NextUpdate.UTC(), + UpdatedAt: meta.UpdatedAt.UTC(), + DownloadedAt: meta.DownloadedAt.UTC(), + } + } + + mcJava := javadb.NewMetadata(filepath.Join(cacheDir, "java-db")) + metaJava, err := mcJava.Get() + if err != nil { + log.Logger.Debugw("Failed to get Java DB metadata", "error", err) + } + if !metaJava.UpdatedAt.IsZero() && !metaJava.NextUpdate.IsZero() && metaJava.Version != 0 { + javadbMeta = &metadata.Metadata{ + Version: metaJava.Version, + NextUpdate: metaJava.NextUpdate.UTC(), + UpdatedAt: metaJava.UpdatedAt.UTC(), + DownloadedAt: metaJava.DownloadedAt.UTC(), + } + } + + var pbMeta *policy.Metadata + pc, err := policy.NewClient(cacheDir, false, "") + if err != nil { + log.Logger.Debugw("Failed to instantiate policy client", "error", err) + } + if pc != nil && err == nil { + pbMetaRaw, err := pc.GetMetadata() + + if err != nil { + log.Logger.Debugw("Failed to get policy metadata", "error", err) + } else { + pbMeta = &policy.Metadata{ + Digest: pbMetaRaw.Digest, + DownloadedAt: pbMetaRaw.DownloadedAt.UTC(), + } + } + } + + return VersionInfo{ + Version: ver, + VulnerabilityDB: dbMeta, + JavaDB: javadbMeta, + PolicyBundle: pbMeta, + } +} diff --git a/pkg/version/version_test.go b/pkg/version/version_test.go new file mode 100644 index 000000000000..84411593541a --- /dev/null +++ b/pkg/version/version_test.go @@ -0,0 +1,55 @@ +package version + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy-db/pkg/metadata" + "github.com/aquasecurity/trivy/pkg/policy" +) + +func Test_BuildVersionInfo(t *testing.T) { + + expected := VersionInfo{ + Version: "dev", + VulnerabilityDB: &metadata.Metadata{ + Version: 2, + NextUpdate: time.Date(2023, 7, 20, 18, 11, 37, 696263532, time.UTC), + UpdatedAt: time.Date(2023, 7, 20, 12, 11, 37, 696263932, time.UTC), + DownloadedAt: time.Date(2023, 7, 25, 7, 1, 41, 239158000, time.UTC), + }, + JavaDB: &metadata.Metadata{ + Version: 1, + NextUpdate: time.Date(2023, 7, 28, 1, 3, 52, 169192565, time.UTC), + UpdatedAt: time.Date(2023, 7, 25, 1, 3, 52, 169192765, time.UTC), + DownloadedAt: time.Date(2023, 7, 25, 9, 37, 48, 906152000, time.UTC), + }, + PolicyBundle: &policy.Metadata{ + Digest: "sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43", + DownloadedAt: time.Date(2023, 7, 23, 16, 40, 33, 122462000, time.UTC), + }, + } + assert.Equal(t, expected, NewVersionInfo("testdata/testcache")) +} + +func Test_VersionInfoString(t *testing.T) { + expected := `Version: dev +Vulnerability DB: + Version: 2 + UpdatedAt: 2023-07-20 12:11:37.696263932 +0000 UTC + NextUpdate: 2023-07-20 18:11:37.696263532 +0000 UTC + DownloadedAt: 2023-07-25 07:01:41.239158 +0000 UTC +Java DB: + Version: 1 + UpdatedAt: 2023-07-25 01:03:52.169192765 +0000 UTC + NextUpdate: 2023-07-28 01:03:52.169192565 +0000 UTC + DownloadedAt: 2023-07-25 09:37:48.906152 +0000 UTC +Policy Bundle: + Digest: sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43 + DownloadedAt: 2023-07-23 16:40:33.122462 +0000 UTC +` + versionInfo := NewVersionInfo("testdata/testcache") + assert.Equal(t, expected, versionInfo.String()) +} diff --git a/pkg/vex/csaf.go b/pkg/vex/csaf.go new file mode 100644 index 000000000000..d5d68f76adb9 --- /dev/null +++ b/pkg/vex/csaf.go @@ -0,0 +1,148 @@ +package vex + +import ( + csaf "github.com/csaf-poc/csaf_distribution/v3/csaf" + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "go.uber.org/zap" + + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/types" +) + +type CSAF struct { + advisory csaf.Advisory + logger *zap.SugaredLogger +} + +func newCSAF(advisory csaf.Advisory) VEX { + return &CSAF{ + advisory: advisory, + logger: log.Logger.With(zap.String("VEX format", "CSAF")), + } +} + +func (v *CSAF) Filter(result *types.Result, _ *core.BOM) { + result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { + found, ok := lo.Find(v.advisory.Vulnerabilities, func(item *csaf.Vulnerability) bool { + return string(*item.CVE) == vuln.VulnerabilityID + }) + if !ok { + return true + } + + if status := v.match(found, vuln.PkgIdentifier.PURL); status != "" { + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(vuln, status, statement(found), "CSAF VEX")) + return false + } + return true + }) +} + +func (v *CSAF) match(vuln *csaf.Vulnerability, pkgURL *packageurl.PackageURL) types.FindingStatus { + if pkgURL == nil || vuln.ProductStatus == nil { + return "" + } + + matchProduct := func(purls []*purl.PackageURL, pkgURL *packageurl.PackageURL) bool { + for _, p := range purls { + if p.Match(pkgURL) { + return true + } + } + return false + } + + productStatusMap := map[types.FindingStatus]csaf.Products{ + types.FindingStatusNotAffected: lo.FromPtr(vuln.ProductStatus.KnownNotAffected), + types.FindingStatusFixed: lo.FromPtr(vuln.ProductStatus.Fixed), + } + for status, productRange := range productStatusMap { + for _, product := range productRange { + if matchProduct(v.getProductPurls(lo.FromPtr(product)), pkgURL) { + v.logger.Infow("Filtered out the detected vulnerability", + zap.String("vulnerability-id", string(*vuln.CVE)), + zap.String("status", string(status))) + return status + } + for relationship, purls := range v.inspectProductRelationships(lo.FromPtr(product)) { + if matchProduct(purls, pkgURL) { + v.logger.Warnw("Filtered out the detected vulnerability", + zap.String("vulnerability-id", string(*vuln.CVE)), + zap.String("status", string(status)), + zap.String("relationship", string(relationship))) + return status + } + } + } + } + + return "" +} + +// getProductPurls returns a slice of PackageURLs associated to a given product +func (v *CSAF) getProductPurls(product csaf.ProductID) []*purl.PackageURL { + return purlsFromProductIdentificationHelpers(v.advisory.ProductTree.CollectProductIdentificationHelpers(product)) +} + +// inspectProductRelationships returns a map of PackageURLs associated to each relationship category +// iterating over relationships looking for sub-products that might be part of the original product +func (v *CSAF) inspectProductRelationships(product csaf.ProductID) map[csaf.RelationshipCategory][]*purl.PackageURL { + subProductsMap := make(map[csaf.RelationshipCategory]csaf.Products) + if v.advisory.ProductTree.RelationShips == nil { + return nil + } + + for _, rel := range lo.FromPtr(v.advisory.ProductTree.RelationShips) { + if rel != nil { + relationship := lo.FromPtr(rel.Category) + switch relationship { + case csaf.CSAFRelationshipCategoryDefaultComponentOf, + csaf.CSAFRelationshipCategoryInstalledOn, + csaf.CSAFRelationshipCategoryInstalledWith: + if fpn := rel.FullProductName; fpn != nil && lo.FromPtr(fpn.ProductID) == product { + subProductsMap[relationship] = append(subProductsMap[relationship], rel.ProductReference) + } + } + } + } + + purlsMap := make(map[csaf.RelationshipCategory][]*purl.PackageURL) + for relationship, subProducts := range subProductsMap { + var helpers []*csaf.ProductIdentificationHelper + for _, subProductRef := range subProducts { + helpers = append(helpers, v.advisory.ProductTree.CollectProductIdentificationHelpers(lo.FromPtr(subProductRef))...) + } + purlsMap[relationship] = purlsFromProductIdentificationHelpers(helpers) + } + + return purlsMap +} + +// purlsFromProductIdentificationHelpers returns a slice of PackageURLs given a slice of ProductIdentificationHelpers. +func purlsFromProductIdentificationHelpers(helpers []*csaf.ProductIdentificationHelper) []*purl.PackageURL { + return lo.FilterMap(helpers, func(helper *csaf.ProductIdentificationHelper, _ int) (*purl.PackageURL, bool) { + if helper == nil || helper.PURL == nil { + return nil, false + } + p, err := purl.FromString(string(*helper.PURL)) + if err != nil { + log.Logger.Errorw("Invalid PURL", zap.String("purl", string(*helper.PURL)), zap.Error(err)) + return nil, false + } + return p, true + }) +} + +func statement(vuln *csaf.Vulnerability) string { + threat, ok := lo.Find(vuln.Threats, func(threat *csaf.Threat) bool { + return lo.FromPtr(threat.Category) == csaf.CSAFThreatCategoryImpact + }) + if !ok { + return "" + } + return lo.FromPtr(threat.Details) +} diff --git a/pkg/vex/cyclonedx.go b/pkg/vex/cyclonedx.go new file mode 100644 index 000000000000..685fefebf304 --- /dev/null +++ b/pkg/vex/cyclonedx.go @@ -0,0 +1,98 @@ +package vex + +import ( + cdx "github.com/CycloneDX/cyclonedx-go" + "github.com/samber/lo" + "go.uber.org/zap" + + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/types" +) + +type CycloneDX struct { + sbom *core.BOM + statements []Statement + logger *zap.SugaredLogger +} + +type Statement struct { + VulnerabilityID string + Affects []string + Status types.FindingStatus + Justification string +} + +func newCycloneDX(sbom *core.BOM, vex *cdx.BOM) *CycloneDX { + var stmts []Statement + for _, vuln := range lo.FromPtr(vex.Vulnerabilities) { + affects := lo.Map(lo.FromPtr(vuln.Affects), func(item cdx.Affects, index int) string { + return item.Ref + }) + + analysis := lo.FromPtr(vuln.Analysis) + stmts = append(stmts, Statement{ + VulnerabilityID: vuln.ID, + Affects: affects, + Status: cdxStatus(analysis.State), + Justification: string(analysis.Justification), + }) + } + return &CycloneDX{ + sbom: sbom, + statements: stmts, + logger: log.Logger.With(zap.String("VEX format", "CycloneDX")), + } +} + +func (v *CycloneDX) Filter(result *types.Result, _ *core.BOM) { + result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { + stmt, ok := lo.Find(v.statements, func(item Statement) bool { + return item.VulnerabilityID == vuln.VulnerabilityID + }) + if !ok { + return true + } + if !v.affected(vuln, stmt) { + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(vuln, stmt.Status, stmt.Justification, "CycloneDX VEX")) + return false + } + return true + }) +} + +func (v *CycloneDX) affected(vuln types.DetectedVulnerability, stmt Statement) bool { + for _, affect := range stmt.Affects { + // Affect must be BOM-Link at the moment + link, err := cdx.ParseBOMLink(affect) + if err != nil { + v.logger.Warnw("Unable to parse BOM-Link", zap.String("affect", affect)) + continue + } + if v.sbom.SerialNumber != link.SerialNumber() || v.sbom.Version != link.Version() { + v.logger.Warnw("URN doesn't match with SBOM", + zap.String("serial number", link.SerialNumber()), + zap.Int("version", link.Version())) + continue + } + if vuln.PkgIdentifier.Match(link.Reference()) && (stmt.Status == types.FindingStatusNotAffected || stmt.Status == types.FindingStatusFixed) { + return false + } + } + return true +} + +func cdxStatus(s cdx.ImpactAnalysisState) types.FindingStatus { + switch s { + case cdx.IASResolved, cdx.IASResolvedWithPedigree: + return types.FindingStatusFixed + case cdx.IASExploitable: + return types.FindingStatusAffected + case cdx.IASInTriage: + return types.FindingStatusUnderInvestigation + case cdx.IASFalsePositive, cdx.IASNotAffected: + return types.FindingStatusNotAffected + } + return types.FindingStatusUnknown +} diff --git a/pkg/vex/openvex.go b/pkg/vex/openvex.go new file mode 100644 index 000000000000..a6cae6de7ac8 --- /dev/null +++ b/pkg/vex/openvex.go @@ -0,0 +1,67 @@ +package vex + +import ( + openvex "github.com/openvex/go-vex/pkg/vex" + "github.com/samber/lo" + + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/types" +) + +type OpenVEX struct { + vex openvex.VEX +} + +func newOpenVEX(vex openvex.VEX) VEX { + return &OpenVEX{ + vex: vex, + } +} + +func (v *OpenVEX) Filter(result *types.Result, bom *core.BOM) { + result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { + if vuln.PkgIdentifier.PURL == nil { + return true + } + + stmts := v.Matches(vuln, bom) + if len(stmts) == 0 { + return true + } + + // Take the latest statement for a given vulnerability and product + // as a sequence of statements can be overridden by the newer one. + // cf. https://github.com/openvex/spec/blob/fa5ba0c0afedb008dc5ebad418548cacf16a3ca7/OPENVEX-SPEC.md#the-vex-statement + stmt := stmts[len(stmts)-1] + if stmt.Status == openvex.StatusNotAffected || stmt.Status == openvex.StatusFixed { + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(vuln, findingStatus(stmt.Status), string(stmt.Justification), "OpenVEX")) + return false + } + return true + }) +} + +func (v *OpenVEX) Matches(vuln types.DetectedVulnerability, bom *core.BOM) []openvex.Statement { + root := bom.Root() + if root != nil && root.PkgID.PURL != nil { + stmts := v.vex.Matches(vuln.VulnerabilityID, root.PkgID.PURL.String(), []string{vuln.PkgIdentifier.PURL.String()}) + if len(stmts) != 0 { + return stmts + } + } + return v.vex.Matches(vuln.VulnerabilityID, vuln.PkgIdentifier.PURL.String(), nil) +} + +func findingStatus(status openvex.Status) types.FindingStatus { + switch status { + case openvex.StatusNotAffected: + return types.FindingStatusNotAffected + case openvex.StatusFixed: + return types.FindingStatusFixed + case openvex.StatusUnderInvestigation: + return types.FindingStatusUnderInvestigation + default: + return types.FindingStatusUnknown + } +} diff --git a/pkg/vex/testdata/csaf-affected.json b/pkg/vex/testdata/csaf-affected.json new file mode 100644 index 000000000000..56e9bb4d8a53 --- /dev/null +++ b/pkg/vex/testdata/csaf-affected.json @@ -0,0 +1,93 @@ +{ + "document": { + "category": "csaf_vex", + "csaf_version": "2.0", + "notes": [ + { + "category": "summary", + "text": "Example Company VEX document. Unofficial content for demonstration purposes only.", + "title": "Author comment" + } + ], + "publisher": { + "category": "vendor", + "name": "Example Company ProductCERT", + "namespace": "https://psirt.example.com" + }, + "title": "Example VEX Document Use Case 1 - Affected", + "tracking": { + "current_release_date": "2022-03-03T11:00:00.000Z", + "generator": { + "date": "2022-03-03T11:00:00.000Z", + "engine": { + "name": "Secvisogram", + "version": "1.11.0" + } + }, + "id": "2022-EVD-UC-01-A-001", + "initial_release_date": "2022-03-03T11:00:00.000Z", + "revision_history": [ + { + "date": "2022-03-03T11:00:00.000Z", + "number": "1", + "summary": "Initial version." + } + ], + "status": "final", + "version": "1" + } + }, + "product_tree": { + "branches": [ + { + "branches": [ + { + "branches": [ + { + "category": "product_version", + "name": "1.0", + "product": { + "name": "Example Company DEF 1.0", + "product_id": "CSAFPID-0001", + "product_identification_helper": { + "purl": "pkg:maven/org.example.company/def@1.0" + } + } + } + ], + "category": "product_name", + "name": "DEF" + } + ], + "category": "vendor", + "name": "Example Company" + } + ] + }, + "vulnerabilities": [ + { + "cve": "CVE-2021-44228", + "notes": [ + { + "category": "description", + "text": "Apache Log4j2 2.0-beta9 through 2.15.0 (excluding security releases 2.12.2, 2.12.3, and 2.3.1) JNDI features used in configuration, log messages, and parameters do not protect against attacker controlled LDAP and other JNDI related endpoints. An attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled. From log4j 2.15.0, this behavior has been disabled by default. From version 2.16.0 (along with 2.12.2, 2.12.3, and 2.3.1), this functionality has been completely removed. Note that this vulnerability is specific to log4j-core and does not affect log4net, log4cxx, or other Apache Logging Services projects.", + "title": "CVE description" + } + ], + "product_status": { + "known_affected": [ + "CSAFPID-0001" + ] + }, + "remediations": [ + { + "category": "vendor_fix", + "details": "Customers should update to version 1.1 of product DEF which fixes the issue.", + "product_ids": [ + "CSAFPID-0001" + ] + } + ] + } + ] +} diff --git a/pkg/vex/testdata/csaf-not-affected-sub-components.json b/pkg/vex/testdata/csaf-not-affected-sub-components.json new file mode 100644 index 000000000000..a6fc9f088257 --- /dev/null +++ b/pkg/vex/testdata/csaf-not-affected-sub-components.json @@ -0,0 +1,126 @@ +{ + "document": { + "category": "csaf_vex", + "csaf_version": "2.0", + "publisher": { + "category": "vendor", + "name": "VMWare, Inc.", + "namespace": "https://tanzu.vmware.com/application-catalog" + }, + "title": "ArgoCD 2.9.3-2 Amd64 Debian12 Advisory", + "tracking": { + "current_release_date": "2024-01-04T17:17:25+01:00", + "generator": { + "engine": { + "name": "Bitnami VEX CLI", + "version": "1.0.0" + } + }, + "id": "fcf5bd33-41c3-45f9-885a-c2ee812f49c9", + "initial_release_date": "2024-01-04T17:17:25+01:00", + "revision_history": [ + { + "date": "2024-01-04T17:17:25+01:00", + "number": "1", + "summary": "Initial version." + } + ], + "status": "final", + "version": "1" + } + }, + "product_tree": { + "branches": [ + { + "branches": [ + { + "branches": [ + { + "category": "product_version", + "name": "2.9.3-2", + "product": { + "name": "Argo CD 2.9.3-2", + "product_id": "argo-cd-2.9.3-2-amd64-debian-12", + "product_identification_helper": { + "purl": "pkg:bitnami/argo-cd@2.9.3-2?arch=amd64\u0026distro=debian-12" + } + } + } + ], + "category": "product_name", + "name": "Argo CD" + } + ], + "category": "vendor", + "name": "VMWare, Inc." + }, + { + "branches": [ + { + "branches": [ + { + "category": "product_version", + "name": "v1.24.2", + "product": { + "name": "Kubernetes v1.24.2", + "product_id": "kubernetes-v1.24.2", + "product_identification_helper": { + "purl": "pkg:golang/k8s.io/kubernetes@v1.24.2" + } + } + } + ], + "category": "product_name", + "name": "kubernetes" + } + ], + "category": "vendor", + "name": "k8s.io" + } + ], + "relationships": [ + { + "product_reference": "kubernetes-v1.24.2", + "category": "default_component_of", + "relates_to_product_reference": "argo-cd-2.9.3-2-amd64-debian-12", + "full_product_name": { + "product_id": "argo-cd-2.9.3-2-amd64-debian-12-kubernetes", + "name": "Argo CD uses kubernetes golang library" + } + } + ] + }, + "vulnerabilities": [ + { + "cve": "CVE-2023-2727", + "flags": [ + { + "date": "2024-01-04T17:17:25+01:00", + "label": "vulnerable_code_cannot_be_controlled_by_adversary", + "product_ids": [ + "argo-cd-2.9.3-2-amd64-debian-12-kubernetes" + ] + } + ], + "notes": [ + { + "category": "description", + "text": "Users may be able to launch containers using images that are restricted by ImagePolicyWebhook when using ephemeral containers. Kubernetes clusters are only affected if the ImagePolicyWebhook admission plugin is used together with ephemeral containers.", + "title": "CVE description" + } + ], + "product_status": { + "known_not_affected": [ + "argo-cd-2.9.3-2-amd64-debian-12-kubernetes" + ] + }, + "threats": [ + { + "category": "impact", + "date": "2024-01-04T17:17:25+01:00", + "details": "The asset uses the component as a dependency in the code, but the vulnerability only affects Kubernetes clusters https://github.com/kubernetes/kubernetes/issues/118640" + } + ] + } + ] +} diff --git a/pkg/vex/testdata/csaf-not-affected.json b/pkg/vex/testdata/csaf-not-affected.json new file mode 100644 index 000000000000..dce0b4a712d6 --- /dev/null +++ b/pkg/vex/testdata/csaf-not-affected.json @@ -0,0 +1,93 @@ +{ + "document": { + "category": "csaf_vex", + "csaf_version": "2.0", + "notes": [ + { + "category": "summary", + "text": "Example Company VEX document. Unofficial content for demonstration purposes only.", + "title": "Author comment" + } + ], + "publisher": { + "category": "vendor", + "name": "Example Company ProductCERT", + "namespace": "https://psirt.example.com" + }, + "title": "AquaSecurity example VEX document", + "tracking": { + "current_release_date": "2022-03-03T11:00:00.000Z", + "generator": { + "date": "2022-03-03T11:00:00.000Z", + "engine": { + "name": "Secvisogram", + "version": "1.11.0" + } + }, + "id": "2022-EVD-UC-01-A-001", + "initial_release_date": "2022-03-03T11:00:00.000Z", + "revision_history": [ + { + "date": "2022-03-03T11:00:00.000Z", + "number": "1", + "summary": "Initial version." + } + ], + "status": "final", + "version": "1" + } + }, + "product_tree": { + "branches": [ + { + "branches": [ + { + "branches": [ + { + "category": "product_version", + "name": "2.6.0", + "product": { + "name": "Spring Boot 2.6.0", + "product_id": "SPB-00260", + "product_identification_helper": { + "purl": "pkg:maven/org.springframework.boot/spring-boot@2.6.0" + } + } + } + ], + "category": "product_name", + "name": "Spring Boot" + } + ], + "category": "vendor", + "name": "Spring" + } + ] + }, + "vulnerabilities": [ + { + "cve": "CVE-2021-44228", + "notes": [ + { + "category": "description", + "text": "Apache Log4j2 2.0-beta9 through 2.15.0 (excluding security releases 2.12.2, 2.12.3, and 2.3.1) JNDI features used in configuration, log messages, and parameters do not protect against attacker controlled LDAP and other JNDI related endpoints. An attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled. From log4j 2.15.0, this behavior has been disabled by default. From version 2.16.0 (along with 2.12.2, 2.12.3, and 2.3.1), this functionality has been completely removed. Note that this vulnerability is specific to log4j-core and does not affect log4net, log4cxx, or other Apache Logging Services projects.", + "title": "CVE description" + } + ], + "product_status": { + "known_not_affected": [ + "SPB-00260" + ] + }, + "threats": [ + { + "category": "impact", + "details": "Class with vulnerable code was removed before shipping.", + "product_ids": [ + "SPB-00260" + ] + } + ] + } + ] +} diff --git a/pkg/vex/testdata/cyclonedx.json b/pkg/vex/testdata/cyclonedx.json new file mode 100644 index 000000000000..ccc4396981b5 --- /dev/null +++ b/pkg/vex/testdata/cyclonedx.json @@ -0,0 +1,44 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 1, + "vulnerabilities": [ + { + "id": "CVE-2018-7489", + "source": { + "name": "NVD", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-9997" + }, + "analysis": { + "state": "not_affected", + "justification": "code_not_reachable" + }, + "affects": [ + { + "ref": "urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.8.0" + } + ] + }, + { + "id": "CVE-2022-27943", + "source": { + "name": "ubuntu", + "url": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "affects": [ + { + "ref": "urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:deb/ubuntu/libstdc%2B%2B6@12.3.0-1ubuntu1~22.04?arch=amd64&distro=ubuntu-22.04", + "versions": [ + { + "version": "12.3.0-1ubuntu1~22.04", + "status": "affected" + } + ] + } + ], + "analysis": { + "state": "not_affected" + } + } + ] +} diff --git a/pkg/vex/testdata/openvex-multiple.json b/pkg/vex/testdata/openvex-multiple.json new file mode 100644 index 000000000000..11c9ac5d192e --- /dev/null +++ b/pkg/vex/testdata/openvex-multiple.json @@ -0,0 +1,33 @@ +{ + "@context": "https://openvex.dev/ns", + "author": "Aqua Security", + "role": "Project Release Bot", + "timestamp": "2023-01-16T19:07:16.853479631-06:00", + "version": 1, + "statements": [ + { + "timestamp": "2023-01-15T01:02:03.853479631-06:00", + "vulnerability": { + "name": "CVE-2021-44228" + }, + "products": [ + { + "@id": "pkg:maven/org.springframework.boot/spring-boot@2.6.0" + } + ], + "status": "affected" + }, + { + "vulnerability": { + "name": "CVE-2021-44228" + }, + "products": [ + { + "@id": "pkg:maven/org.springframework.boot/spring-boot@2.6.0" + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] +} diff --git a/pkg/vex/testdata/openvex-oci.json b/pkg/vex/testdata/openvex-oci.json new file mode 100644 index 000000000000..667ca5e3d049 --- /dev/null +++ b/pkg/vex/testdata/openvex-oci.json @@ -0,0 +1,26 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "author": "Aqua Security", + "role": "Project Release Bot", + "timestamp": "2023-01-16T19:07:16.853479631-06:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-2022-3715" + }, + "products": [ + { + "@id": "pkg:oci/debian", + "subcomponents": [ + { + "@id": "pkg:deb/debian/bash" + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] +} diff --git a/pkg/vex/testdata/openvex.json b/pkg/vex/testdata/openvex.json new file mode 100644 index 000000000000..4acaf3aff95e --- /dev/null +++ b/pkg/vex/testdata/openvex.json @@ -0,0 +1,21 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "author": "Aqua Security", + "role": "Project Release Bot", + "timestamp": "2023-01-16T19:07:16.853479631-06:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-2021-44228" + }, + "products": [ + { + "@id": "pkg:maven/org.springframework.boot/spring-boot@2.6.0" + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] +} diff --git a/pkg/vex/testdata/unknown.json b/pkg/vex/testdata/unknown.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/pkg/vex/testdata/unknown.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go new file mode 100644 index 000000000000..0e47bf03bf52 --- /dev/null +++ b/pkg/vex/vex.go @@ -0,0 +1,106 @@ +package vex + +import ( + "encoding/json" + "io" + "os" + + csaf "github.com/csaf-poc/csaf_distribution/v3/csaf" + "github.com/hashicorp/go-multierror" + openvex "github.com/openvex/go-vex/pkg/vex" + "github.com/sirupsen/logrus" + "golang.org/x/xerrors" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/sbom" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" + "github.com/aquasecurity/trivy/pkg/types" +) + +// VEX represents Vulnerability Exploitability eXchange. It abstracts multiple VEX formats. +// Note: This is in the experimental stage and does not yet support many specifications. +// The implementation may change significantly. +type VEX interface { + Filter(*types.Result, *core.BOM) +} + +func New(filePath string, report types.Report) (VEX, error) { + if filePath == "" { + return nil, nil + } + f, err := os.Open(filePath) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + var errs error + // Try CycloneDX JSON + if ok, err := sbom.IsCycloneDXJSON(f); err != nil { + errs = multierror.Append(errs, err) + } else if ok { + return decodeCycloneDXJSON(f, report) + } + + // Try OpenVEX + if v, err := decodeOpenVEX(f); err != nil { + errs = multierror.Append(errs, err) + } else if v != nil { + return v, nil + } + + // Try CSAF + if v, err := decodeCSAF(f); err != nil { + errs = multierror.Append(errs, err) + } else if v != nil { + return v, nil + } + + return nil, xerrors.Errorf("unable to load VEX: %w", errs) +} + +func decodeCycloneDXJSON(r io.ReadSeeker, report types.Report) (VEX, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("seek error: %w", err) + } + vex, err := cyclonedx.DecodeJSON(r) + if err != nil { + return nil, xerrors.Errorf("json decode error: %w", err) + } + if report.ArtifactType != ftypes.ArtifactCycloneDX { + return nil, xerrors.New("CycloneDX VEX can be used with CycloneDX SBOM") + } + return newCycloneDX(report.BOM, vex), nil +} + +func decodeOpenVEX(r io.ReadSeeker) (VEX, error) { + // openvex/go-vex outputs log messages by default + logrus.SetOutput(io.Discard) + + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("seek error: %w", err) + } + var openVEX openvex.VEX + if err := json.NewDecoder(r).Decode(&openVEX); err != nil { + return nil, err + } + if openVEX.Context == "" { + return nil, nil + } + return newOpenVEX(openVEX), nil +} + +func decodeCSAF(r io.ReadSeeker) (VEX, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("seek error: %w", err) + } + var adv csaf.Advisory + if err := json.NewDecoder(r).Decode(&adv); err != nil { + return nil, err + } + if adv.Vulnerabilities == nil { + return nil, nil + } + return newCSAF(adv), nil +} diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go new file mode 100644 index 000000000000..77d2aff3c63e --- /dev/null +++ b/pkg/vex/vex_test.go @@ -0,0 +1,427 @@ +package vex_test + +import ( + "github.com/aquasecurity/trivy/pkg/sbom/core" + "os" + "testing" + + "github.com/package-url/packageurl-go" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/vex" +) + +var ( + vuln1 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2021-44228", + PkgName: "spring-boot", + InstalledVersion: "2.6.0", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework.boot", + Name: "spring-boot", + Version: "2.6.0", + }, + }, + } + vuln2 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2021-0001", + PkgName: "spring-boot", + InstalledVersion: "2.6.0", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework.boot", + Name: "spring-boot", + Version: "2.6.0", + }, + }, + } + vuln3 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2022-3715", + PkgName: "bash", + InstalledVersion: "5.2.15", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "debian", + Name: "bash", + Version: "5.2.15", + }, + }, + } +) + +func TestMain(m *testing.M) { + log.InitLogger(false, true) + os.Exit(m.Run()) +} + +func TestVEX_Filter(t *testing.T) { + type fields struct { + filePath string + report types.Report + } + type args struct { + vulns []types.DetectedVulnerability + bom *core.BOM + } + tests := []struct { + name string + fields fields + args args + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "OpenVEX", + fields: fields{ + filePath: "testdata/openvex.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{vuln1}, + bom: newTestBOM(), + }, + want: []types.DetectedVulnerability{}, + }, + { + name: "OpenVEX, multiple statements", + fields: fields{ + filePath: "testdata/openvex-multiple.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{ + vuln1, // filtered by VEX + vuln2, + }, + bom: newTestBOM(), + }, + want: []types.DetectedVulnerability{ + vuln2, + }, + }, + { + name: "OpenVEX, subcomponents, oci image", + fields: fields{ + filePath: "testdata/openvex-oci.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{ + vuln3, + }, + bom: newTestBOM(), + }, + want: []types.DetectedVulnerability{}, + }, + { + name: "OpenVEX, subcomponents, wrong oci image", + fields: fields{ + filePath: "testdata/openvex-oci.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{vuln3}, + bom: newTestBOM2(), + }, + want: []types.DetectedVulnerability{vuln3}, + }, + { + name: "CycloneDX SBOM with CycloneDX VEX", + fields: fields{ + filePath: "testdata/cyclonedx.json", + report: types.Report{ + ArtifactType: ftypes.ArtifactCycloneDX, + BOM: &core.BOM{ + SerialNumber: "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", + Version: 1, + }, + }, + }, + args: args{ + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2018-7489", + PkgName: "jackson-databind", + InstalledVersion: "2.8.0", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.8.0", + }, + }, + }, + { + VulnerabilityID: "CVE-2018-7490", + PkgName: "jackson-databind", + InstalledVersion: "2.8.0", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.8.0", + }, + }, + }, + { + VulnerabilityID: "CVE-2022-27943", + PkgID: "libstdc++6@12.3.0-1ubuntu1~22.04", + PkgName: "libstdc++6", + InstalledVersion: "12.3.0-1ubuntu1~22.04", + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:deb/ubuntu/libstdc%2B%2B6@12.3.0-1ubuntu1~22.04?distro=ubuntu-22.04&arch=amd64", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "ubuntu", + Name: "libstdc++6", + Version: "12.3.0-1ubuntu1~22.04", + Qualifiers: []packageurl.Qualifier{ + { + Key: "arch", + Value: "amd64", + }, + { + Key: "distro", + Value: "ubuntu-22.04", + }, + }, + }, + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2018-7490", + PkgName: "jackson-databind", + InstalledVersion: "2.8.0", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.8.0", + }, + }, + }, + }, + }, + { + name: "CycloneDX VEX wrong URN", + fields: fields{ + filePath: "testdata/cyclonedx.json", + report: types.Report{ + ArtifactType: ftypes.ArtifactCycloneDX, + BOM: &core.BOM{ + SerialNumber: "urn:uuid:wrong", + Version: 1, + }, + }, + }, + args: args{ + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2018-7489", + PkgName: "jackson-databind", + InstalledVersion: "2.8.0", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.8.0", + }, + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2018-7489", + PkgName: "jackson-databind", + InstalledVersion: "2.8.0", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.8.0", + }, + }, + }, + }, + }, + { + name: "CSAF (not affected vuln)", + fields: fields{ + filePath: "testdata/csaf-not-affected.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2021-44228", + PkgName: "spring-boot", + InstalledVersion: "2.6.0", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework.boot", + Name: "spring-boot", + Version: "2.6.0", + }, + }, + }, + }, + }, + want: []types.DetectedVulnerability{}, + }, + { + name: "CSAF (affected vuln)", + fields: fields{ + filePath: "testdata/csaf-affected.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2021-44228", + PkgName: "def", + InstalledVersion: "1.0", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.example.company", + Name: "def", + Version: "1.0", + }, + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2021-44228", + PkgName: "def", + InstalledVersion: "1.0", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.example.company", + Name: "def", + Version: "1.0", + }, + }, + }, + }, + }, + { + name: "CSAF (not affected vuln) with sub components", + fields: fields{ + filePath: "testdata/csaf-not-affected-sub-components.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2023-2727", + PkgName: "kubernetes", + InstalledVersion: "v1.24.2", + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "k8s.io", + Name: "kubernetes", + Version: "v1.24.2", + }, + }, + }, + }, + }, + want: []types.DetectedVulnerability{}, + }, + { + name: "unknown format", + fields: fields{ + filePath: "testdata/unknown.json", + }, + args: args{}, + wantErr: "unable to load VEX", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v, err := vex.New(tt.fields.filePath, tt.fields.report) + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + + got := &types.Result{ + Vulnerabilities: tt.args.vulns, + } + v.Filter(got, tt.args.bom) + assert.Equal(t, tt.want, got.Vulnerabilities) + }) + } +} + +func newTestBOM() *core.BOM { + bom := core.NewBOM(core.Options{}) + bom.AddComponent(&core.Component{ + Root: true, + Type: core.TypeContainerImage, + Name: "debian:12", + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeOCI, + Name: "debian", + Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + Qualifiers: packageurl.Qualifiers{ + { + Key: "tag", + Value: "12", + }, + { + Key: "repository_url", + Value: "docker.io/library/debian", + }, + }, + }, + }, + }) + return bom +} + +func newTestBOM2() *core.BOM { + bom := core.NewBOM(core.Options{}) + bom.AddComponent(&core.Component{ + Root: true, + Type: core.TypeContainerImage, + Name: "ubuntu:24.04", + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeOCI, + Name: "ubuntu", + Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + Qualifiers: packageurl.Qualifiers{ + { + Key: "tag", + Value: "24.04", + }, + { + Key: "repository_url", + Value: "docker.io/library/ubuntu", + }, + }, + }, + }, + }) + return bom +} diff --git a/pkg/vulnerability/testdata/fixtures/sad.yaml b/pkg/vulnerability/testdata/fixtures/sad.yaml new file mode 100644 index 000000000000..f7d55cb4b458 --- /dev/null +++ b/pkg/vulnerability/testdata/fixtures/sad.yaml @@ -0,0 +1,6 @@ +- bucket: vulnerability + pairs: + - key: CVE-2019-0001 + value: + Title: + - aaa diff --git a/pkg/vulnerability/testdata/fixtures/vulnerability.yaml b/pkg/vulnerability/testdata/fixtures/vulnerability.yaml new file mode 100644 index 000000000000..eb4acaa651fc --- /dev/null +++ b/pkg/vulnerability/testdata/fixtures/vulnerability.yaml @@ -0,0 +1,75 @@ +- bucket: vulnerability + pairs: + - key: CVE-2019-0001 + value: + Title: dos + Description: dos vulnerability + Severity: MEDIUM + References: + - http://example.com + LastModifiedDate: "2020-01-01T01:01:00Z" + PublishedDate: "2001-01-01T01:01:00Z" + - key: CVE-2019-0004 + value: + Title: dos + Description: dos vulnerability + Severity: MEDIUM + VendorSeverity: + redhat: 1 + CVSS: + nvd: + V2Vector: AV:N/AC:L/Au:N/C:P/I:P/A:P + V2Score: 4.5 + V3Vector: CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H + V3Score: 5.6 + redhat: + V2Vector: AV:N/AC:M/Au:N/C:N/I:P/A:N + V2Score: 7.8 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + V3Score: 9.8 + References: + - http://example.com + CweIDs: + - CWE-311 + - key: CVE-2019-0005 + value: + Title: COVID-19 + Description: a nasty virus vulnerability for humans + Severity: MEDIUM + VendorSeverity: + ghsa: 4 + References: + - "https://www.who.int/emergencies/diseases/novel-coronavirus-2019" + - key: RUSTSEC-2018-0017 + value: + Title: dos + Description: dos vulnerability + Severity: UNKNOWN + VendorSeverity: + nvd: 1 + LastModifiedDate: "2020-01-01T01:01:00Z" + PublishedDate: "2001-01-01T01:01:00Z" + - key: GHSA-28fw-88hq-6jmm + value: + Title: dos + Description: dos vulnerability + References: + - http://example.com + - key: CVE-2022-0001 + value: + Title: dos + Description: dos vulnerability + VendorSeverity: + nvd: 1 + ghsa: 3 + References: + - http://example.com + - key: GHSA-0000-aaaa-1111 + value: + Title: dos + Description: dos vulnerability + VendorSeverity: + nvd: 1 + ghsa: 3 + References: + - http://example.com diff --git a/pkg/vulnerability/vulnerability.go b/pkg/vulnerability/vulnerability.go new file mode 100644 index 000000000000..504d9293e873 --- /dev/null +++ b/pkg/vulnerability/vulnerability.go @@ -0,0 +1,157 @@ +package vulnerability + +import ( + "strings" + + "github.com/google/wire" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +var ( + primaryURLPrefixes = map[dbTypes.SourceID][]string{ + vulnerability.Debian: { + "http://www.debian.org", + "https://www.debian.org", + }, + vulnerability.Ubuntu: { + "http://www.ubuntu.com", + "https://usn.ubuntu.com", + }, + vulnerability.RedHat: {"https://access.redhat.com"}, + vulnerability.SuseCVRF: { + "http://lists.opensuse.org", + "https://lists.opensuse.org", + }, + vulnerability.OracleOVAL: { + "http://linux.oracle.com/errata", + "https://linux.oracle.com/errata", + }, + vulnerability.NodejsSecurityWg: { + "https://www.npmjs.com", + "https://hackerone.com", + }, + vulnerability.RubySec: {"https://groups.google.com"}, + } +) + +// SuperSet binds the dependencies +var SuperSet = wire.NewSet( + wire.Struct(new(db.Config)), + wire.Bind(new(db.Operation), new(db.Config)), + NewClient, +) + +// Client manipulates vulnerabilities +type Client struct { + dbc db.Operation +} + +// NewClient is the factory method for Client +func NewClient(dbc db.Operation) Client { + return Client{dbc: dbc} +} + +// FillInfo fills extra info in vulnerability objects +func (c Client) FillInfo(vulns []types.DetectedVulnerability) { + for i := range vulns { + // Add the vulnerability status + // Some vendors such as Red Hat have their own vulnerability status, and we use it. + // Otherwise, we put "fixed" or "affected" according to the fixed version. + if vulns[i].FixedVersion != "" { + vulns[i].Status = dbTypes.StatusFixed + } else if vulns[i].Status == dbTypes.StatusUnknown { + vulns[i].Status = dbTypes.StatusAffected + } + + // Get the vulnerability detail + vulnID := vulns[i].VulnerabilityID + vuln, err := c.dbc.GetVulnerability(vulnID) + if err != nil { + log.Logger.Warnf("Error while getting vulnerability details: %s", err) + continue + } + + // Detect the data source + var source dbTypes.SourceID + if vulns[i].DataSource != nil { + source = vulns[i].DataSource.ID + } + + // Select the severity according to the detected source. + severity, severitySource := c.getVendorSeverity(vulnID, &vuln, source) + + // The vendor might provide package-specific severity like Debian. + // For example, CVE-2015-2328 in Debian has "unimportant" for mongodb and "low" for pcre3. + // In that case, we keep the severity as is. + if vulns[i].SeveritySource != "" { + severity = vulns[i].Severity + severitySource = vulns[i].SeveritySource + + // Store package-specific severity in vendor severities + if vuln.VendorSeverity == nil { + vuln.VendorSeverity = make(dbTypes.VendorSeverity) + } + s, _ := dbTypes.NewSeverity(severity) // skip error handling because `SeverityUnknown` will be returned in case of error + vuln.VendorSeverity[severitySource] = s + } + + // Add the vulnerability detail + vulns[i].Vulnerability = vuln + + vulns[i].Severity = severity + vulns[i].SeveritySource = severitySource + vulns[i].PrimaryURL = c.getPrimaryURL(vulnID, vuln.References, source) + } +} + +func (c Client) getVendorSeverity(vulnID string, vuln *dbTypes.Vulnerability, source dbTypes.SourceID) (string, dbTypes.SourceID) { + if vs, ok := vuln.VendorSeverity[source]; ok { + return vs.String(), source + } + + // use severity from GitHub for all GHSA-xxx vulnerabilities + if strings.HasPrefix(vulnID, "GHSA-") { + if vs, ok := vuln.VendorSeverity[vulnerability.GHSA]; ok { + return vs.String(), vulnerability.GHSA + } + } + + // Try NVD as a fallback if it exists + if vs, ok := vuln.VendorSeverity[vulnerability.NVD]; ok { + return vs.String(), vulnerability.NVD + } + + if vuln.Severity == "" { + return dbTypes.SeverityUnknown.String(), "" + } + + return vuln.Severity, "" +} + +func (c Client) getPrimaryURL(vulnID string, refs []string, source dbTypes.SourceID) string { + switch { + case strings.HasPrefix(vulnID, "CVE-"): + return "https://avd.aquasec.com/nvd/" + strings.ToLower(vulnID) + case strings.HasPrefix(vulnID, "RUSTSEC-"): + return "https://osv.dev/vulnerability/" + vulnID + case strings.HasPrefix(vulnID, "GHSA-"): + return "https://github.com/advisories/" + vulnID + case strings.HasPrefix(vulnID, "TEMP-"): + return "https://security-tracker.debian.org/tracker/" + vulnID + } + + prefixes := primaryURLPrefixes[source] + for _, pre := range prefixes { + for _, ref := range refs { + if strings.HasPrefix(ref, pre) { + return ref + } + } + } + return "" +} diff --git a/pkg/vulnerability/vulnerability_test.go b/pkg/vulnerability/vulnerability_test.go new file mode 100644 index 000000000000..11dac691503e --- /dev/null +++ b/pkg/vulnerability/vulnerability_test.go @@ -0,0 +1,313 @@ +package vulnerability_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/utils" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/types" + vuln "github.com/aquasecurity/trivy/pkg/vulnerability" +) + +func TestClient_FillInfo(t *testing.T) { + tests := []struct { + name string + fixtures []string + vulns []types.DetectedVulnerability + expectedVulnerabilities []types.DetectedVulnerability + }{ + { + name: "happy path, with only OS vulnerability but no vendor severity, no NVD", + fixtures: []string{"testdata/fixtures/vulnerability.yaml"}, + vulns: []types.DetectedVulnerability{ + {VulnerabilityID: "CVE-2019-0001"}, + }, + expectedVulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0001", + Status: dbTypes.StatusAffected, + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: dbTypes.SeverityMedium.String(), + References: []string{"http://example.com"}, + LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), + PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001", + }, + }, + }, + { + name: "happy path, with only OS vulnerability but no vendor severity, yes NVD", + fixtures: []string{"testdata/fixtures/vulnerability.yaml"}, + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "RUSTSEC-2018-0017", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.OSV, + Name: "RustSec Advisory Database", + URL: "https://github.com/RustSec/advisory-db", + }, + }, + }, + expectedVulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "RUSTSEC-2018-0017", + Status: dbTypes.StatusAffected, + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: dbTypes.SeverityLow.String(), + VendorSeverity: dbTypes.VendorSeverity{ + vulnerability.NVD: dbTypes.SeverityLow, + }, + LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), + PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), + }, + SeveritySource: vulnerability.NVD, + PrimaryURL: "https://osv.dev/vulnerability/RUSTSEC-2018-0017", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.OSV, + Name: "RustSec Advisory Database", + URL: "https://github.com/RustSec/advisory-db", + }, + }, + }, + }, + { + name: "happy path, with only OS vulnerability but no severity, no vendor severity, no NVD", + fixtures: []string{"testdata/fixtures/vulnerability.yaml"}, + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "GHSA-28fw-88hq-6jmm", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.GHSA, + Name: "GitHub Security Advisory Pip", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip", + }, + }, + }, + expectedVulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "GHSA-28fw-88hq-6jmm", + Status: dbTypes.StatusAffected, + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: dbTypes.SeverityUnknown.String(), + References: []string{"http://example.com"}, + }, + PrimaryURL: "https://github.com/advisories/GHSA-28fw-88hq-6jmm", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.GHSA, + Name: "GitHub Security Advisory Pip", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip", + }, + }, + }, + }, + { + name: "happy path, with only OS vulnerability, yes vendor severity, with both NVD and CVSS info", + fixtures: []string{"testdata/fixtures/vulnerability.yaml"}, + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0004", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.RedHat, + Name: "Red Hat OVAL v2", + URL: "https://www.redhat.com/security/data/oval/v2/", + }, + }, + }, + expectedVulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0004", + Status: dbTypes.StatusAffected, + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: dbTypes.SeverityLow.String(), + VendorSeverity: dbTypes.VendorSeverity{ + vulnerability.RedHat: dbTypes.SeverityLow, + }, + CweIDs: []string{"CWE-311"}, + References: []string{"http://example.com"}, + CVSS: map[dbTypes.SourceID]dbTypes.CVSS{ + vulnerability.NVD: { + V2Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P", + V2Score: 4.5, + V3Vector: "CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H", + V3Score: 5.6, + }, + vulnerability.RedHat: { + V2Vector: "AV:N/AC:M/Au:N/C:N/I:P/A:N", + V2Score: 7.8, + V3Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + V3Score: 9.8, + }, + }, + }, + SeveritySource: vulnerability.RedHat, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0004", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.RedHat, + Name: "Red Hat OVAL v2", + URL: "https://www.redhat.com/security/data/oval/v2/", + }, + }, + }, + }, + { + name: "happy path, with only library vulnerability", + fixtures: []string{"testdata/fixtures/vulnerability.yaml"}, + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0005", + Status: dbTypes.StatusAffected, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.GHSA, + Name: "GitHub Security Advisory Pip", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip", + }, + }, + }, + expectedVulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0005", + Status: dbTypes.StatusAffected, + Vulnerability: dbTypes.Vulnerability{ + Title: "COVID-19", + Description: "a nasty virus vulnerability for humans", + Severity: dbTypes.SeverityCritical.String(), + VendorSeverity: dbTypes.VendorSeverity{ + vulnerability.GHSA: dbTypes.SeverityCritical, + }, + References: []string{"https://www.who.int/emergencies/diseases/novel-coronavirus-2019"}, + }, + SeveritySource: vulnerability.GHSA, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0005", + DataSource: &dbTypes.DataSource{ + ID: vulnerability.GHSA, + Name: "GitHub Security Advisory Pip", + URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip", + }, + }, + }, + }, + { + name: "happy path, with package-specific severity", + fixtures: []string{"testdata/fixtures/vulnerability.yaml"}, + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0001", + SeveritySource: vulnerability.Debian, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityLow.String(), + }, + }, + }, + expectedVulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0001", + Status: dbTypes.StatusAffected, + SeveritySource: vulnerability.Debian, + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: dbTypes.SeverityLow.String(), + VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{ + vulnerability.Debian: dbTypes.SeverityLow, + }, + References: []string{"http://example.com"}, + LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), + PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001", + }, + }, + }, + { + name: "happy path. GHSA-xxx. Severity gets from ghsa", + fixtures: []string{"testdata/fixtures/vulnerability.yaml"}, + vulns: []types.DetectedVulnerability{ + {VulnerabilityID: "GHSA-0000-aaaa-1111"}, + }, + expectedVulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "GHSA-0000-aaaa-1111", + Status: dbTypes.StatusAffected, + SeveritySource: vulnerability.GHSA, + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: dbTypes.SeverityHigh.String(), + References: []string{"http://example.com"}, + VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{ + "nvd": dbTypes.SeverityLow, + "ghsa": dbTypes.SeverityHigh, + }, + }, + PrimaryURL: "https://github.com/advisories/GHSA-0000-aaaa-1111", + }, + }, + }, + { + name: "happy path. CVE-xxx. Severity gets from nvd", + fixtures: []string{"testdata/fixtures/vulnerability.yaml"}, + vulns: []types.DetectedVulnerability{ + {VulnerabilityID: "CVE-2022-0001"}, + }, + expectedVulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-0001", + Status: dbTypes.StatusAffected, + SeveritySource: vulnerability.NVD, + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: dbTypes.SeverityLow.String(), + References: []string{"http://example.com"}, + VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{ + "nvd": dbTypes.SeverityLow, + "ghsa": dbTypes.SeverityHigh, + }, + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2022-0001", + }, + }, + }, + { + name: "GetVulnerability returns an error", + fixtures: []string{"testdata/fixtures/sad.yaml"}, + vulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0004", + Status: dbTypes.StatusAffected, + }, + }, + expectedVulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0004", + Status: dbTypes.StatusAffected, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + c := vuln.NewClient(db.Config{}) + c.FillInfo(tt.vulns) + assert.Equal(t, tt.expectedVulnerabilities, tt.vulns, tt.name) + }) + } +} diff --git a/pkg/vulnsrc/alpine/alpine.go b/pkg/vulnsrc/alpine/alpine.go deleted file mode 100644 index f69db0aa49a8..000000000000 --- a/pkg/vulnsrc/alpine/alpine.go +++ /dev/null @@ -1,111 +0,0 @@ -package alpine - -import ( - "encoding/json" - "fmt" - "io" - "path/filepath" - - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "github.com/knqyf263/trivy/pkg/db" - "github.com/knqyf263/trivy/pkg/log" - - "golang.org/x/xerrors" - - bolt "github.com/etcd-io/bbolt" - "github.com/knqyf263/trivy/pkg/utils" -) - -const ( - alpineDir = "alpine" -) - -var ( - platformFormat = "alpine %s" -) - -func Update(dir string, updatedFiles map[string]struct{}) error { - rootDir := filepath.Join(dir, alpineDir) - targets, err := utils.FilterTargets(alpineDir, updatedFiles) - if err != nil { - return xerrors.Errorf("failed to filter target files: %w", err) - } else if len(targets) == 0 { - log.Logger.Debug("Alpine: no updated file") - return nil - } - log.Logger.Debugf("Alpine updated files: %d", len(targets)) - - bar := utils.PbStartNew(len(targets)) - defer bar.Finish() - - var cves []AlpineCVE - err = utils.FileWalk(rootDir, targets, func(r io.Reader, path string) error { - var cve AlpineCVE - if err = json.NewDecoder(r).Decode(&cve); err != nil { - return xerrors.Errorf("failed to decode Alpine JSON: %w", err) - } - cves = append(cves, cve) - bar.Increment() - return nil - }) - if err != nil { - return xerrors.Errorf("error in Alpine walk: %w", err) - } - - if err = save(cves); err != nil { - return xerrors.Errorf("error in Alpine save: %w", err) - } - - return nil -} - -func save(cves []AlpineCVE) error { - log.Logger.Debug("Saving Alpine DB") - - err := db.BatchUpdate(func(tx *bolt.Tx) error { - for _, cve := range cves { - platformName := fmt.Sprintf(platformFormat, cve.Release) - pkgName := cve.Package - advisory := Advisory{ - VulnerabilityID: cve.VulnerabilityID, - FixedVersion: cve.FixedVersion, - Repository: cve.Repository, - } - if err := db.PutNestedBucket(tx, platformName, pkgName, cve.VulnerabilityID, advisory); err != nil { - return xerrors.Errorf("failed to save alpine advisory: %w", err) - } - - vuln := vulnerability.Vulnerability{ - Title: cve.Subject, - Description: cve.Description, - } - if err := vulnerability.Put(tx, cve.VulnerabilityID, vulnerability.Alpine, vuln); err != nil { - return xerrors.Errorf("failed to save alpine vulnerability: %w", err) - } - } - return nil - }) - if err != nil { - return xerrors.Errorf("error in db batch update: %w", err) - } - return nil -} - -func Get(release string, pkgName string) ([]Advisory, error) { - bucket := fmt.Sprintf(platformFormat, release) - advisories, err := db.ForEach(bucket, pkgName) - if err != nil { - return nil, xerrors.Errorf("error in Alpine foreach: %w", err) - } - - var results []Advisory - for _, v := range advisories { - var advisory Advisory - if err = json.Unmarshal(v, &advisory); err != nil { - return nil, xerrors.Errorf("failed to unmarshal Alpine JSON: %w", err) - } - results = append(results, advisory) - } - return results, nil -} diff --git a/pkg/vulnsrc/alpine/types.go b/pkg/vulnsrc/alpine/types.go deleted file mode 100644 index 4c5ecb6eb79d..000000000000 --- a/pkg/vulnsrc/alpine/types.go +++ /dev/null @@ -1,17 +0,0 @@ -package alpine - -type AlpineCVE struct { - VulnerabilityID string - Release string - Package string - Repository string - FixedVersion string - Subject string - Description string -} - -type Advisory struct { - VulnerabilityID string - FixedVersion string - Repository string -} diff --git a/pkg/vulnsrc/debian-oval/debian-oval.go b/pkg/vulnsrc/debian-oval/debian-oval.go deleted file mode 100644 index 27b9a8b01c10..000000000000 --- a/pkg/vulnsrc/debian-oval/debian-oval.go +++ /dev/null @@ -1,164 +0,0 @@ -package debianoval - -import ( - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/knqyf263/trivy/pkg/vulnsrc/debian" - - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - bolt "github.com/etcd-io/bbolt" - "github.com/knqyf263/trivy/pkg/db" - "github.com/knqyf263/trivy/pkg/log" - - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/utils" -) - -var ( - debianDir = filepath.Join("oval", "debian") - // e.g. debian oval 8 - platformFormat = "debian oval %s" -) - -func Update(dir string, updatedFiles map[string]struct{}) error { - rootDir := filepath.Join(dir, debianDir) - targets, err := utils.FilterTargets(debianDir, updatedFiles) - if err != nil { - return xerrors.Errorf("failed to filter target files: %w", err) - } else if len(targets) == 0 { - log.Logger.Debug("Debian OVAL: no updated file") - return nil - } - log.Logger.Debugf("Debian OVAL updated files: %d", len(targets)) - - bar := utils.PbStartNew(len(targets)) - defer bar.Finish() - - var cves []DebianOVAL - err = utils.FileWalk(rootDir, targets, func(r io.Reader, path string) error { - var cve DebianOVAL - if err = json.NewDecoder(r).Decode(&cve); err != nil { - return xerrors.Errorf("failed to decode Debian OVAL JSON: %w", err) - } - - dirs := strings.Split(path, string(os.PathSeparator)) - if len(dirs) < 3 { - log.Logger.Debugf("invalid path: %s", path) - return nil - } - cve.Release = dirs[len(dirs)-3] - cves = append(cves, cve) - bar.Increment() - return nil - }) - if err != nil { - return xerrors.Errorf("error in Debian OVAL walk: %w", err) - } - - if err = save(cves); err != nil { - return xerrors.Errorf("error in Debian OVAL save: %w", err) - } - - return nil -} - -// from https://github.com/kotakanbe/goval-dictionary/blob/c462c07a5cd0b6de52f167e9aa4298083edfc356/models/debian.go#L53 -func walkDebian(cri Criteria, pkgs []Package) []Package { - for _, c := range cri.Criterions { - ss := strings.Split(c.Comment, " DPKG is earlier than ") - if len(ss) != 2 { - continue - } - - // "0" means notyetfixed or erroneous information. - // Not available because "0" includes erroneous info... - if ss[1] == "0" { - continue - } - pkgs = append(pkgs, Package{ - Name: ss[0], - FixedVersion: strings.Split(ss[1], " ")[0], - }) - } - - if len(cri.Criterias) == 0 { - return pkgs - } - for _, c := range cri.Criterias { - pkgs = walkDebian(c, pkgs) - } - return pkgs -} - -func save(cves []DebianOVAL) error { - log.Logger.Debug("Saving Debian OVAL") - err := db.BatchUpdate(func(tx *bolt.Tx) error { - for _, cve := range cves { - affectedPkgs := walkDebian(cve.Criteria, []Package{}) - for _, affectedPkg := range affectedPkgs { - // stretch => 9 - majorVersion, ok := debian.DebianReleasesMapping[cve.Release] - if !ok { - continue - } - platformName := fmt.Sprintf(platformFormat, majorVersion) - cveID := cve.Metadata.Title - advisory := vulnerability.Advisory{ - VulnerabilityID: cveID, - FixedVersion: affectedPkg.FixedVersion, - } - if err := db.PutNestedBucket(tx, platformName, affectedPkg.Name, cveID, advisory); err != nil { - return xerrors.Errorf("failed to save Debian OVAL advisory: %w", err) - } - - var references []string - for _, ref := range cve.Metadata.References { - references = append(references, ref.RefURL) - } - - vuln := vulnerability.Vulnerability{ - Description: cve.Metadata.Description, - References: references, - } - - if err := vulnerability.Put(tx, cveID, vulnerability.DebianOVAL, vuln); err != nil { - return xerrors.Errorf("failed to save Debian OVAL vulnerability: %w", err) - } - } - } - return nil - }) - if err != nil { - return xerrors.Errorf("error in batch update: %w", err) - } - - return nil -} - -func Get(release string, pkgName string) ([]vulnerability.Advisory, error) { - bucket := fmt.Sprintf(platformFormat, release) - advisories, err := db.ForEach(bucket, pkgName) - if err != nil { - return nil, xerrors.Errorf("error in Debian OVAL foreach: %w", err) - } - if len(advisories) == 0 { - return nil, nil - } - - var results []vulnerability.Advisory - for _, v := range advisories { - var advisory vulnerability.Advisory - if err = json.Unmarshal(v, &advisory); err != nil { - return nil, xerrors.Errorf("failed to unmarshal Debian OVAL JSON: %w", err) - } - results = append(results, advisory) - } - return results, nil -} diff --git a/pkg/vulnsrc/debian-oval/types.go b/pkg/vulnsrc/debian-oval/types.go deleted file mode 100644 index 1f8cf2c15313..000000000000 --- a/pkg/vulnsrc/debian-oval/types.go +++ /dev/null @@ -1,43 +0,0 @@ -package debianoval - -type DebianOVAL struct { - Metadata Metadata - Criteria Criteria - Release string -} - -type Metadata struct { - Title string - AffectedList []Affected - Description string - References []Reference -} - -type Affected struct { - Family string - Platform string - Product string -} - -type Criteria struct { - Operator string - Criterias []Criteria - Criterions []Criterion -} - -type Reference struct { - Source string - RefID string - RefURL string -} - -type Criterion struct { - Negate bool - TestRef string - Comment string -} - -type Package struct { - Name string - FixedVersion string -} diff --git a/pkg/vulnsrc/debian/debian.go b/pkg/vulnsrc/debian/debian.go deleted file mode 100644 index 92abdf70e9db..000000000000 --- a/pkg/vulnsrc/debian/debian.go +++ /dev/null @@ -1,154 +0,0 @@ -package debian - -import ( - "encoding/json" - "fmt" - "io" - "path/filepath" - "strings" - - bolt "github.com/etcd-io/bbolt" - "github.com/knqyf263/trivy/pkg/db" - "github.com/knqyf263/trivy/pkg/log" - "github.com/knqyf263/trivy/pkg/utils" - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - "golang.org/x/xerrors" -) - -const ( - debianDir = "debian" -) - -var ( - // e.g. debian 8 - platformFormat = "debian %s" - DebianReleasesMapping = map[string]string{ - // Code names - "squeeze": "6", - "wheezy": "7", - "jessie": "8", - "stretch": "9", - "buster": "10", - "sid": "unstable", - } -) - -func Update(dir string, updatedFiles map[string]struct{}) error { - rootDir := filepath.Join(dir, debianDir) - targets, err := utils.FilterTargets(debianDir, updatedFiles) - if err != nil { - return xerrors.Errorf("failed to filter target files: %w", err) - } else if len(targets) == 0 { - log.Logger.Debug("Debian: no updated file") - return nil - } - log.Logger.Debugf("Debian updated files: %d", len(targets)) - - bar := utils.PbStartNew(len(targets)) - defer bar.Finish() - - var cves []DebianCVE - err = utils.FileWalk(rootDir, targets, func(r io.Reader, path string) error { - var cve DebianCVE - if err = json.NewDecoder(r).Decode(&cve); err != nil { - return xerrors.Errorf("failed to decode Debian JSON: %w", err) - } - - cve.VulnerabilityID = strings.TrimSuffix(filepath.Base(path), ".json") - cve.Package = filepath.Base(filepath.Dir(path)) - cves = append(cves, cve) - - bar.Increment() - return nil - }) - if err != nil { - return xerrors.Errorf("error in Debian walk: %w", err) - } - - if err = save(cves); err != nil { - return xerrors.Errorf("error in Debian save: %w", err) - } - - return nil -} - -func save(cves []DebianCVE) error { - log.Logger.Debug("Saving Debian DB") - err := db.BatchUpdate(func(tx *bolt.Tx) error { - for _, cve := range cves { - for _, release := range cve.Releases { - for releaseStr := range release.Repositories { - majorVersion, ok := DebianReleasesMapping[releaseStr] - if !ok { - continue - } - platformName := fmt.Sprintf(platformFormat, majorVersion) - if release.Status != "open" { - continue - } - advisory := vulnerability.Advisory{ - VulnerabilityID: cve.VulnerabilityID, - //Severity: severityFromUrgency(release.Urgency), - } - if err := db.PutNestedBucket(tx, platformName, cve.Package, cve.VulnerabilityID, advisory); err != nil { - return xerrors.Errorf("failed to save Debian advisory: %w", err) - } - - vuln := vulnerability.Vulnerability{ - Severity: severityFromUrgency(release.Urgency), - Description: cve.Description, - } - - if err := vulnerability.Put(tx, cve.VulnerabilityID, vulnerability.Debian, vuln); err != nil { - return xerrors.Errorf("failed to save Debian vulnerability: %w", err) - } - } - } - } - return nil - }) - if err != nil { - return xerrors.Errorf("error in batch update: %w", err) - } - - return nil -} - -func Get(release string, pkgName string) ([]vulnerability.Advisory, error) { - bucket := fmt.Sprintf(platformFormat, release) - advisories, err := db.ForEach(bucket, pkgName) - if err != nil { - return nil, xerrors.Errorf("error in Debian foreach: %w", err) - } - if len(advisories) == 0 { - return nil, nil - } - - var results []vulnerability.Advisory - for _, v := range advisories { - var advisory vulnerability.Advisory - if err = json.Unmarshal(v, &advisory); err != nil { - return nil, xerrors.Errorf("failed to unmarshal Debian JSON: %w", err) - } - results = append(results, advisory) - } - return results, nil -} - -func severityFromUrgency(urgency string) vulnerability.Severity { - switch urgency { - case "not yet assigned": - return vulnerability.SeverityUnknown - - case "end-of-life", "unimportant", "low", "low*", "low**": - return vulnerability.SeverityLow - - case "medium", "medium*", "medium**": - return vulnerability.SeverityMedium - - case "high", "high*", "high**": - return vulnerability.SeverityHigh - default: - return vulnerability.SeverityUnknown - } -} diff --git a/pkg/vulnsrc/debian/types.go b/pkg/vulnsrc/debian/types.go deleted file mode 100644 index 5e726aaa494c..000000000000 --- a/pkg/vulnsrc/debian/types.go +++ /dev/null @@ -1,15 +0,0 @@ -package debian - -type DebianCVE struct { - Description string `json:"description"` - Releases map[string]Release `json:"releases"` - Scope string `json:"scope"` - Package string - VulnerabilityID string -} - -type Release struct { - Repositories map[string]string `json:"repositories"` - Status string `json:"status"` - Urgency string `json:"urgency"` -} diff --git a/pkg/vulnsrc/nvd/nvd.go b/pkg/vulnsrc/nvd/nvd.go deleted file mode 100644 index 07f9412d9d6f..000000000000 --- a/pkg/vulnsrc/nvd/nvd.go +++ /dev/null @@ -1,97 +0,0 @@ -package nvd - -import ( - "encoding/json" - "io" - "path/filepath" - - "github.com/knqyf263/trivy/pkg/log" - - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/utils" - - bolt "github.com/etcd-io/bbolt" - "github.com/knqyf263/trivy/pkg/db" -) - -const ( - nvdDir = "nvd" -) - -func Update(dir string, updatedFiles map[string]struct{}) error { - rootDir := filepath.Join(dir, nvdDir) - targets, err := utils.FilterTargets(nvdDir, updatedFiles) - if err != nil { - return xerrors.Errorf("failed to filter target files: %w", err) - } else if len(targets) == 0 { - log.Logger.Debug("NVD: no updated file") - return nil - } - log.Logger.Debugf("NVD updated files: %d", len(targets)) - - bar := utils.PbStartNew(len(targets)) - defer bar.Finish() - var items []Item - err = utils.FileWalk(rootDir, targets, func(r io.Reader, _ string) error { - item := Item{} - if err := json.NewDecoder(r).Decode(&item); err != nil { - return xerrors.Errorf("failed to decode NVD JSON: %w", err) - } - items = append(items, item) - bar.Increment() - return nil - }) - if err != nil { - return xerrors.Errorf("error in NVD walk: %w", err) - } - - if err = save(items); err != nil { - return xerrors.Errorf("error in NVD save: %w", err) - } - - return nil -} - -func save(items []Item) error { - log.Logger.Debug("NVD batch update") - err := vulnerability.BatchUpdate(func(b *bolt.Bucket) error { - for _, item := range items { - cveID := item.Cve.Meta.ID - severity, _ := vulnerability.NewSeverity(item.Impact.BaseMetricV2.Severity) - severityV3, _ := vulnerability.NewSeverity(item.Impact.BaseMetricV3.CvssV3.BaseSeverity) - - var references []string - for _, ref := range item.Cve.References.ReferenceDataList { - references = append(references, ref.URL) - } - - var description string - for _, d := range item.Cve.Description.DescriptionDataList { - if d.Value != "" { - description = d.Value - break - } - } - - vuln := vulnerability.Vulnerability{ - Severity: severity, - SeverityV3: severityV3, - References: references, - Title: "", - Description: description, - } - - if err := db.Put(b, cveID, vulnerability.Nvd, vuln); err != nil { - return err - } - } - return nil - }) - if err != nil { - return xerrors.Errorf("error in batch update: %w", err) - } - return nil -} diff --git a/pkg/vulnsrc/nvd/types.go b/pkg/vulnsrc/nvd/types.go deleted file mode 100644 index 8d4892f46b70..000000000000 --- a/pkg/vulnsrc/nvd/types.go +++ /dev/null @@ -1,55 +0,0 @@ -package nvd - -type NVD struct { - CVEItems []Item `json:"CVE_Items"` -} - -type Item struct { - Cve Cve - Impact Impact -} - -type Cve struct { - Meta Meta `json:"CVE_data_meta"` - References References - Description Description -} - -type Meta struct { - ID string -} - -type Impact struct { - BaseMetricV2 BaseMetricV2 - BaseMetricV3 BaseMetricV3 -} - -type BaseMetricV2 struct { - Severity string -} - -type BaseMetricV3 struct { - CvssV3 CvssV3 -} - -type CvssV3 struct { - BaseSeverity string -} - -type References struct { - ReferenceDataList []ReferenceData `json:"reference_data"` -} -type ReferenceData struct { - Name string - Refsource string - URL string -} - -type Description struct { - DescriptionDataList []DescriptionData `json:"description_data"` -} - -type DescriptionData struct { - Lang string - Value string -} diff --git a/pkg/vulnsrc/redhat/redhat.go b/pkg/vulnsrc/redhat/redhat.go deleted file mode 100644 index b9b04ab3ed95..000000000000 --- a/pkg/vulnsrc/redhat/redhat.go +++ /dev/null @@ -1,247 +0,0 @@ -package redhat - -import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "path/filepath" - "strconv" - "strings" - - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "github.com/knqyf263/trivy/pkg/log" - - bolt "github.com/etcd-io/bbolt" - "github.com/knqyf263/trivy/pkg/db" - "github.com/knqyf263/trivy/pkg/utils" - "golang.org/x/xerrors" -) - -const ( - redhatDir = "redhat" - platformFormat = "Red Hat Enterprise Linux %s" -) - -var ( - targetPlatforms = []string{"Red Hat Enterprise Linux 5", "Red Hat Enterprise Linux 6", "Red Hat Enterprise Linux 7"} - targetStatus = []string{"Affected", "Fix deferred", "Will not fix"} -) - -func Update(dir string, updatedFiles map[string]struct{}) error { - rootDir := filepath.Join(dir, redhatDir) - targets, err := utils.FilterTargets(redhatDir, updatedFiles) - if err != nil { - return xerrors.Errorf("failed to filter target files: %w", err) - } else if len(targets) == 0 { - log.Logger.Debug("Red Hat: no updated file") - return nil - } - log.Logger.Debugf("Red Hat updated files: %d", len(targets)) - - bar := utils.PbStartNew(len(targets)) - defer bar.Finish() - - var cves []RedhatCVE - err = utils.FileWalk(rootDir, targets, func(r io.Reader, _ string) error { - content, err := ioutil.ReadAll(r) - if err != nil { - return err - } - cve := RedhatCVE{} - if err = json.Unmarshal(content, &cve); err != nil { - return xerrors.Errorf("failed to decode RedHat JSON: %w", err) - } - switch cve.TempAffectedRelease.(type) { - case []interface{}: - var ar RedhatCVEAffectedReleaseArray - if err = json.Unmarshal(content, &ar); err != nil { - return xerrors.Errorf("unknown affected_release type: %w", err) - } - cve.AffectedRelease = ar.AffectedRelease - case map[string]interface{}: - var ar RedhatCVEAffectedReleaseObject - if err = json.Unmarshal(content, &ar); err != nil { - return xerrors.Errorf("unknown affected_release type: %w", err) - } - cve.AffectedRelease = []RedhatAffectedRelease{ar.AffectedRelease} - case nil: - default: - return xerrors.New("unknown affected_release type") - } - - switch cve.TempPackageState.(type) { - case []interface{}: - var ps RedhatCVEPackageStateArray - if err = json.Unmarshal(content, &ps); err != nil { - return xerrors.Errorf("unknown package_state type: %w", err) - } - cve.PackageState = ps.PackageState - case map[string]interface{}: - var ps RedhatCVEPackageStateObject - if err = json.Unmarshal(content, &ps); err != nil { - return xerrors.Errorf("unknown package_state type: %w", err) - } - cve.PackageState = []RedhatPackageState{ps.PackageState} - case nil: - default: - return xerrors.New("unknown package_state type") - } - cves = append(cves, cve) - bar.Increment() - return nil - }) - if err != nil { - return xerrors.Errorf("error in RedHat walk: %w", err) - } - - if err = save(cves); err != nil { - return xerrors.Errorf("error in RedHat save: %w", err) - } - - return nil -} - -// platformName: pkgStatus -type platform map[string]pkg - -// pkgName: advisoryStatus -type pkg map[string]advisory - -// cveID: version -type advisory map[string]interface{} - -func save(cves []RedhatCVE) error { - log.Logger.Debug("Saving RedHat DB") - err := db.BatchUpdate(func(tx *bolt.Tx) error { - for _, cve := range cves { - for _, affected := range cve.AffectedRelease { - if affected.Package == "" { - continue - } - // e.g. Red Hat Enterprise Linux 7 - platformName := affected.ProductName - if !utils.StringInSlice(affected.ProductName, targetPlatforms) { - continue - } - - pkgName, version := splitPkgName(affected.Package) - advisory := vulnerability.Advisory{ - VulnerabilityID: cve.Name, - FixedVersion: version, - } - if err := db.PutNestedBucket(tx, platformName, pkgName, cve.Name, advisory); err != nil { - return xerrors.Errorf("failed to save Red Hat advisory: %w", err) - } - - } - - for _, pkgState := range cve.PackageState { - pkgName := pkgState.PackageName - if pkgName == "" { - continue - } - // e.g. Red Hat Enterprise Linux 7 - platformName := pkgState.ProductName - if !utils.StringInSlice(platformName, targetPlatforms) { - continue - } - if !utils.StringInSlice(pkgState.FixState, targetStatus) { - continue - } - - advisory := vulnerability.Advisory{ - // this means all versions - FixedVersion: "", - VulnerabilityID: cve.Name, - } - if err := db.PutNestedBucket(tx, platformName, pkgName, cve.Name, advisory); err != nil { - return xerrors.Errorf("failed to save Red Hat advisory: %w", err) - } - - } - - cvssScore, _ := strconv.ParseFloat(cve.Cvss.CvssBaseScore, 64) - cvss3Score, _ := strconv.ParseFloat(cve.Cvss3.Cvss3BaseScore, 64) - - title := strings.TrimPrefix(strings.TrimSpace(cve.Bugzilla.Description), cve.Name) - - vuln := vulnerability.Vulnerability{ - CvssScore: cvssScore, - CvssScoreV3: cvss3Score, - Severity: severityFromThreat(cve.ThreatSeverity), - References: cve.References, - Title: strings.TrimSpace(title), - Description: strings.TrimSpace(strings.Join(cve.Details, "")), - } - if err := vulnerability.Put(tx, cve.Name, vulnerability.RedHat, vuln); err != nil { - return xerrors.Errorf("failed to save Red Hat vulnerability: %w", err) - } - } - return nil - }) - if err != nil { - return err - } - - return nil -} - -func Get(majorVersion string, pkgName string) ([]vulnerability.Advisory, error) { - bucket := fmt.Sprintf(platformFormat, majorVersion) - advisories, err := db.ForEach(bucket, pkgName) - if err != nil { - return nil, xerrors.Errorf("error in Red Hat foreach: %w", err) - } - if len(advisories) == 0 { - return nil, nil - } - - var results []vulnerability.Advisory - for _, v := range advisories { - var advisory vulnerability.Advisory - if err = json.Unmarshal(v, &advisory); err != nil { - return nil, xerrors.Errorf("failed to unmarshal Red Hat JSON: %w", err) - } - results = append(results, advisory) - } - return results, nil -} - -// ref. https://github.com/rpm-software-management/yum/blob/043e869b08126c1b24e392f809c9f6871344c60d/rpmUtils/miscutils.py#L301 -func splitPkgName(pkgName string) (string, string) { - var version string - - // Trim release - index := strings.LastIndex(pkgName, "-") - if index == -1 { - return "", "" - } - version = pkgName[index:] - pkgName = pkgName[:index] - - // Trim version - index = strings.LastIndex(pkgName, "-") - if index == -1 { - return "", "" - } - version = pkgName[index+1:] + version - pkgName = pkgName[:index] - - return pkgName, version -} - -func severityFromThreat(sev string) vulnerability.Severity { - switch strings.Title(sev) { - case "Low": - return vulnerability.SeverityLow - case "Moderate": - return vulnerability.SeverityMedium - case "Important": - return vulnerability.SeverityHigh - case "Critical": - return vulnerability.SeverityCritical - } - return vulnerability.SeverityUnknown -} diff --git a/pkg/vulnsrc/redhat/types.go b/pkg/vulnsrc/redhat/types.go deleted file mode 100644 index 1c2493b0739d..000000000000 --- a/pkg/vulnsrc/redhat/types.go +++ /dev/null @@ -1,80 +0,0 @@ -package redhat - -type RedhatCVE struct { - ThreatSeverity string `json:"threat_severity"` - PublicDate string `json:"public_date"` - Bugzilla RedhatBugzilla `json:"bugzilla"` - Cvss RedhatCvss `json:"cvss"` - Cvss3 RedhatCvss3 `json:"cvss3"` - Iava string `json:"iava"` - Cwe string `json:"cwe"` - Statement string `json:"statement"` - Acknowledgement string `json:"acknowledgement"` - Mitigation string `json:"mitigation"` - TempAffectedRelease interface{} `json:"affected_release"` // affected_release is array or object - AffectedRelease []RedhatAffectedRelease - TempPackageState interface{} `json:"package_state"` // package_state is array or object - PackageState []RedhatPackageState - Name string `json:"name"` - DocumentDistribution string `json:"document_distribution"` - - Details []string `json:"details" gorm:"-"` - References []string `json:"references" gorm:"-"` -} - -type RedhatCVEAffectedReleaseArray struct { - AffectedRelease []RedhatAffectedRelease `json:"affected_release"` -} - -type RedhatCVEAffectedReleaseObject struct { - AffectedRelease RedhatAffectedRelease `json:"affected_release"` -} - -type RedhatCVEPackageStateArray struct { - PackageState []RedhatPackageState `json:"package_state"` -} - -type RedhatCVEPackageStateObject struct { - PackageState RedhatPackageState `json:"package_state"` -} - -type RedhatDetail struct { - Detail string `sql:"type:text"` -} - -type RedhatReference struct { - Reference string `sql:"type:text"` -} - -type RedhatBugzilla struct { - Description string `json:"description" sql:"type:text"` - BugzillaID string `json:"id"` - URL string `json:"url"` -} - -type RedhatCvss struct { - CvssBaseScore string `json:"cvss_base_score"` - CvssScoringVector string `json:"cvss_scoring_vector"` - Status string `json:"status"` -} - -type RedhatCvss3 struct { - Cvss3BaseScore string `json:"cvss3_base_score"` - Cvss3ScoringVector string `json:"cvss3_scoring_vector"` - Status string `json:"status"` -} - -type RedhatAffectedRelease struct { - ProductName string `json:"product_name"` - ReleaseDate string `json:"release_date"` - Advisory string `json:"advisory"` - Package string `json:"package"` - Cpe string `json:"cpe"` -} - -type RedhatPackageState struct { - ProductName string `json:"product_name"` - FixState string `json:"fix_state"` - PackageName string `json:"package_name"` - Cpe string `json:"cpe"` -} diff --git a/pkg/vulnsrc/ubuntu/types.go b/pkg/vulnsrc/ubuntu/types.go deleted file mode 100644 index 4e74600c1448..000000000000 --- a/pkg/vulnsrc/ubuntu/types.go +++ /dev/null @@ -1,18 +0,0 @@ -package ubuntu - -type UbuntuCVE struct { - Description string `json:"description"` - Candidate string - Priority string - Patches map[PackageName]Patch - References []string -} - -type PackageName string -type Release string -type Patch map[Release]Status - -type Status struct { - Status string - Note string -} diff --git a/pkg/vulnsrc/ubuntu/ubuntu.go b/pkg/vulnsrc/ubuntu/ubuntu.go deleted file mode 100644 index f5aba032aeed..000000000000 --- a/pkg/vulnsrc/ubuntu/ubuntu.go +++ /dev/null @@ -1,164 +0,0 @@ -package ubuntu - -import ( - "encoding/json" - "fmt" - "io" - "path/filepath" - - "github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability" - - "github.com/knqyf263/trivy/pkg/log" - - bolt "github.com/etcd-io/bbolt" - - "github.com/knqyf263/trivy/pkg/db" - "golang.org/x/xerrors" - - "github.com/knqyf263/trivy/pkg/utils" -) - -const ( - ubuntuDir = "ubuntu" - platformFormat = "ubuntu %s" - t -) - -var ( - targetStatus = []string{"needed", "deferred", "released"} - UbuntuReleasesMapping = map[string]string{ - "precise": "12.04", - "quantal": "12.10", - "raring": "13.04", - "trusty": "14.04", - "utopic": "14.10", - "vivid": "15.04", - "wily": "15.10", - "xenial": "16.04", - "yakkety": "16.10", - "zesty": "17.04", - "artful": "17.10", - "bionic": "18.04", - "cosmic": "18.10", - "disco": "19.04", - } -) - -func Update(dir string, updatedFiles map[string]struct{}) error { - rootDir := filepath.Join(dir, ubuntuDir) - targets, err := utils.FilterTargets(ubuntuDir, updatedFiles) - if err != nil { - return xerrors.Errorf("failed to filter target files: %w", err) - } else if len(targets) == 0 { - log.Logger.Debug("Ubuntu: no updated file") - return nil - } - log.Logger.Debugf("Ubuntu OVAL updated files: %d", len(targets)) - - bar := utils.PbStartNew(len(targets)) - defer bar.Finish() - - var cves []UbuntuCVE - err = utils.FileWalk(rootDir, targets, func(r io.Reader, path string) error { - var cve UbuntuCVE - if err = json.NewDecoder(r).Decode(&cve); err != nil { - return xerrors.Errorf("failed to decode Ubuntu JSON: %w", err) - } - cves = append(cves, cve) - bar.Increment() - return nil - }) - if err != nil { - return xerrors.Errorf("error in Ubuntu walk: %w", err) - } - - if err = save(cves); err != nil { - return xerrors.Errorf("error in Ubuntu save: %w", err) - } - - return nil -} - -func save(cves []UbuntuCVE) error { - log.Logger.Debug("Saving Ubuntu DB") - err := db.BatchUpdate(func(tx *bolt.Tx) error { - for _, cve := range cves { - for packageName, patch := range cve.Patches { - pkgName := string(packageName) - for release, status := range patch { - if !utils.StringInSlice(status.Status, targetStatus) { - continue - } - osVersion, ok := UbuntuReleasesMapping[string(release)] - if !ok { - continue - } - platformName := fmt.Sprintf(platformFormat, osVersion) - advisory := vulnerability.Advisory{ - VulnerabilityID: cve.Candidate, - } - if status.Status == "released" { - advisory.FixedVersion = status.Note - } - if err := db.PutNestedBucket(tx, platformName, pkgName, cve.Candidate, advisory); err != nil { - return xerrors.Errorf("failed to save Ubuntu advisory: %w", err) - } - - vuln := vulnerability.Vulnerability{ - Severity: severityFromPriority(cve.Priority), - References: cve.References, - Description: cve.Description, - // TODO - Title: "", - } - if err := vulnerability.Put(tx, cve.Candidate, vulnerability.Ubuntu, vuln); err != nil { - return xerrors.Errorf("failed to save Ubuntu vulnerability: %w", err) - } - } - } - } - return nil - }) - if err != nil { - return xerrors.Errorf("error in batch update: %w", err) - } - return nil -} - -func Get(release string, pkgName string) ([]vulnerability.Advisory, error) { - bucket := fmt.Sprintf(platformFormat, release) - advisories, err := db.ForEach(bucket, pkgName) - if err != nil { - return nil, xerrors.Errorf("error in Ubuntu foreach: %w", err) - } - if len(advisories) == 0 { - return nil, nil - } - - var results []vulnerability.Advisory - for _, v := range advisories { - var advisory vulnerability.Advisory - if err = json.Unmarshal(v, &advisory); err != nil { - return nil, xerrors.Errorf("failed to unmarshal Ubuntu JSON: %w", err) - } - results = append(results, advisory) - } - return results, nil -} - -func severityFromPriority(priority string) vulnerability.Severity { - switch priority { - case "untriaged": - return vulnerability.SeverityUnknown - case "negligible", "low": - return vulnerability.SeverityLow - case "medium": - return vulnerability.SeverityMedium - case "high": - return vulnerability.SeverityHigh - case "critical": - return vulnerability.SeverityCritical - default: - return vulnerability.SeverityUnknown - } -} diff --git a/pkg/vulnsrc/vulnerability/const.go b/pkg/vulnsrc/vulnerability/const.go deleted file mode 100644 index eeb5c3e2f743..000000000000 --- a/pkg/vulnsrc/vulnerability/const.go +++ /dev/null @@ -1,18 +0,0 @@ -package vulnerability - -const ( - // Data source - Nvd = "nvd" - RedHat = "redhat" - Debian = "debian" - DebianOVAL = "debian-oval" - Ubuntu = "ubuntu" - CentOS = "centos" - Fedora = "fedora" - Amazon = "amazon" - Alpine = "alpine" - RubySec = "ruby-advisory-db" - PhpSecurityAdvisories = "php-security-advisories" - NodejsSecurityWg = "nodejs-security-wg" - PythonSafetyDB = "python-safety-db" -) diff --git a/pkg/vulnsrc/vulnerability/types.go b/pkg/vulnsrc/vulnerability/types.go deleted file mode 100644 index 29c5eb75e4f2..000000000000 --- a/pkg/vulnsrc/vulnerability/types.go +++ /dev/null @@ -1,67 +0,0 @@ -package vulnerability - -import ( - "fmt" - "time" - - "github.com/fatih/color" -) - -type Severity int - -const ( - SeverityUnknown Severity = iota - SeverityLow - SeverityMedium - SeverityHigh - SeverityCritical -) - -var ( - SeverityNames = []string{ - "UNKNOWN", - "LOW", - "MEDIUM", - "HIGH", - "CRITICAL", - } - SeverityColor = []func(a ...interface{}) string{ - color.New(color.FgCyan).SprintFunc(), - color.New(color.FgBlue).SprintFunc(), - color.New(color.FgYellow).SprintFunc(), - color.New(color.FgHiRed).SprintFunc(), - color.New(color.FgRed).SprintFunc(), - } -) - -func NewSeverity(severity string) (Severity, error) { - for i, name := range SeverityNames { - if severity == name { - return Severity(i), nil - } - } - return SeverityUnknown, fmt.Errorf("unknown severity: %s", severity) -} - -func CompareSeverityString(sev1, sev2 string) bool { - s1, _ := NewSeverity(sev1) - s2, _ := NewSeverity(sev2) - return s1 < s2 -} - -func ColorizeSeverity(severity string) string { - for i, name := range SeverityNames { - if severity == name { - return SeverityColor[i](severity) - } - } - return color.New(color.FgBlue).SprintFunc()(severity) -} - -func (s Severity) String() string { - return SeverityNames[s] -} - -type LastUpdated struct { - Date time.Time -} diff --git a/pkg/vulnsrc/vulnerability/vulnerability.go b/pkg/vulnsrc/vulnerability/vulnerability.go deleted file mode 100644 index 626027b7efc0..000000000000 --- a/pkg/vulnsrc/vulnerability/vulnerability.go +++ /dev/null @@ -1,71 +0,0 @@ -package vulnerability - -import ( - "encoding/json" - - bolt "github.com/etcd-io/bbolt" - "github.com/knqyf263/trivy/pkg/db" - "golang.org/x/xerrors" -) - -const ( - rootBucket = "vulnerability" -) - -type Vulnerability struct { - ID string // e.g. CVE-2019-8331, OSVDB-104365 - CvssScore float64 - CvssScoreV3 float64 - Severity Severity - SeverityV3 Severity - References []string - Title string - Description string -} - -type Advisory struct { - VulnerabilityID string - FixedVersion string -} - -func Put(tx *bolt.Tx, cveID, source string, vuln Vulnerability) error { - root, err := tx.CreateBucketIfNotExists([]byte(rootBucket)) - if err != nil { - return err - } - return db.Put(root, cveID, source, vuln) -} - -func Update(cveID, source string, vuln Vulnerability) error { - return db.Update(rootBucket, cveID, source, vuln) -} - -func BatchUpdate(fn func(b *bolt.Bucket) error) error { - return db.BatchUpdate(func(tx *bolt.Tx) error { - root, err := tx.CreateBucketIfNotExists([]byte(rootBucket)) - if err != nil { - return err - } - return fn(root) - }) -} - -func Get(cveID string) (map[string]Vulnerability, error) { - values, err := db.ForEach(rootBucket, cveID) - if err != nil { - return nil, xerrors.Errorf("error in NVD get: %w", err) - } - if len(values) == 0 { - return nil, nil - } - - vulns := map[string]Vulnerability{} - for source, value := range values { - var vuln Vulnerability - if err = json.Unmarshal(value, &vuln); err != nil { - return nil, xerrors.Errorf("failed to unmarshal Vulnerability JSON: %w", err) - } - vulns[source] = vuln - } - return vulns, nil -} diff --git a/pkg/vulnsrc/vulnsrc.go b/pkg/vulnsrc/vulnsrc.go deleted file mode 100644 index 19541db534a2..000000000000 --- a/pkg/vulnsrc/vulnsrc.go +++ /dev/null @@ -1,75 +0,0 @@ -package vulnsrc - -import ( - "path/filepath" - - "github.com/knqyf263/trivy/pkg/git" - "github.com/knqyf263/trivy/pkg/log" - "github.com/knqyf263/trivy/pkg/utils" - "github.com/knqyf263/trivy/pkg/vulnsrc/alpine" - "github.com/knqyf263/trivy/pkg/vulnsrc/debian" - debianoval "github.com/knqyf263/trivy/pkg/vulnsrc/debian-oval" - "github.com/knqyf263/trivy/pkg/vulnsrc/nvd" - "github.com/knqyf263/trivy/pkg/vulnsrc/redhat" - "github.com/knqyf263/trivy/pkg/vulnsrc/ubuntu" - "golang.org/x/xerrors" -) - -const ( - repoURL = "https://github.com/knqyf263/vuln-list.git" -) - -func Update() (err error) { - log.Logger.Info("Updating vulnerability database...") - - // Clone vuln-list repository - dir := filepath.Join(utils.CacheDir(), "vuln-list") - updatedFiles, err := git.CloneOrPull(repoURL, dir) - if err != nil { - return xerrors.Errorf("error in vulnsrc clone or pull: %w", err) - } - log.Logger.Debugf("total updated files: %d", len(updatedFiles)) - - // Only last_updated.json - if len(updatedFiles) <= 1 { - return nil - } - - // Update NVD - log.Logger.Info("Updating NVD data...") - if err = nvd.Update(dir, updatedFiles); err != nil { - return xerrors.Errorf("error in NVD update: %w", err) - } - - // Update Alpine OVAL - log.Logger.Info("Updating Alpine data...") - if err = alpine.Update(dir, updatedFiles); err != nil { - return xerrors.Errorf("error in Alpine OVAL update: %w", err) - } - - //Update RedHat - log.Logger.Info("Updating RedHat data...") - if err = redhat.Update(dir, updatedFiles); err != nil { - return xerrors.Errorf("error in RedHat update: %w", err) - } - - // Update Debian - log.Logger.Info("Updating Debian data...") - if err = debian.Update(dir, updatedFiles); err != nil { - return xerrors.Errorf("error in Debian update: %w", err) - } - - // Update Debian OVAL - log.Logger.Info("Updating Debian OVAL data...") - if err = debianoval.Update(dir, updatedFiles); err != nil { - return xerrors.Errorf("error in Debian OVAL update: %w", err) - } - - //Update Ubuntu - log.Logger.Info("Updating Ubuntu data...") - if err = ubuntu.Update(dir, updatedFiles); err != nil { - return xerrors.Errorf("error in Ubuntu update: %w", err) - } - - return nil -} diff --git a/pkg/x/io/io.go b/pkg/x/io/io.go new file mode 100644 index 000000000000..1fe3d6c0c2fc --- /dev/null +++ b/pkg/x/io/io.go @@ -0,0 +1,73 @@ +package io + +import ( + "bytes" + "io" + + "golang.org/x/xerrors" +) + +type ReadSeekerAt interface { + io.ReadSeeker + io.ReaderAt +} + +type ReadSeekCloserAt interface { + io.ReadSeekCloser + io.ReaderAt +} + +func NewReadSeekerAt(r io.Reader) (ReadSeekerAt, error) { + if rr, ok := r.(ReadSeekerAt); ok { + return rr, nil + } + + buff := bytes.NewBuffer([]byte{}) + if _, err := io.Copy(buff, r); err != nil { + return nil, xerrors.Errorf("copy error: %w", err) + } + + return bytes.NewReader(buff.Bytes()), nil +} + +func NewReadSeekerAtWithSize(r io.Reader) (ReadSeekerAt, int64, error) { + rsa, err := NewReadSeekerAt(r) + if err != nil { + return nil, 0, err + } + + br, ok := rsa.(*bytes.Reader) + if ok { + return rsa, br.Size(), nil + } + + size, err := getSeekerSize(rsa) + if err != nil { + return nil, 0, xerrors.Errorf("get size error: %w", err) + } + return rsa, size, nil +} + +func getSeekerSize(s io.Seeker) (int64, error) { + size, err := s.Seek(0, io.SeekEnd) + if err != nil { + return 0, xerrors.Errorf("seek error: %w", err) + } + + if _, err = s.Seek(0, io.SeekStart); err != nil { + return 0, xerrors.Errorf("seek error: %w", err) + } + return size, nil +} + +// NopCloser returns a ReadSeekCloserAt with a no-op Close method wrapping +// the provided Reader r. +func NopCloser(r ReadSeekerAt) ReadSeekCloserAt { + return nopCloser{r} +} + +type nopCloser struct { + ReadSeekerAt +} + +func (nopCloser) Close() error { return nil } diff --git a/pkg/x/path/path.go b/pkg/x/path/path.go new file mode 100644 index 000000000000..8bf5e335365b --- /dev/null +++ b/pkg/x/path/path.go @@ -0,0 +1,13 @@ +package path + +import ( + "strings" + + "golang.org/x/exp/slices" +) + +// Contains reports whether the path contains the subpath. +func Contains(filePath, subpath string) bool { + ss := strings.Split(filePath, "/") + return slices.Contains(ss, subpath) +} diff --git a/pkg/x/path/path_test.go b/pkg/x/path/path_test.go new file mode 100644 index 000000000000..77d6eea9ecfe --- /dev/null +++ b/pkg/x/path/path_test.go @@ -0,0 +1,50 @@ +package path + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContains(t *testing.T) { + type args struct { + filePath string + subpath string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "file", + args: args{ + filePath: "go.mod", + subpath: "go.mod", + }, + want: true, + }, + { + name: "dir", + args: args{ + filePath: "app/node_modules/express/package.json", + subpath: "node_modules", + }, + want: true, + }, + { + name: "path", + args: args{ + filePath: "app/node_modules/express/package.json", + subpath: "app/node_modules", + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Contains(tt.args.filePath, tt.args.subpath) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/x/strings/strings.go b/pkg/x/strings/strings.go new file mode 100644 index 000000000000..78c87231605a --- /dev/null +++ b/pkg/x/strings/strings.go @@ -0,0 +1,25 @@ +package strings + +import "github.com/samber/lo" + +type String interface { + ~string +} + +func ToStringSlice[T String](ss []T) []string { + if ss == nil { + return nil + } + return lo.Map(ss, func(s T, _ int) string { + return string(s) + }) +} + +func ToTSlice[T String](ss []string) []T { + if ss == nil { + return nil + } + return lo.Map(ss, func(s string, _ int) T { + return T(s) + }) +} diff --git a/pkg/x/sync/sync.go b/pkg/x/sync/sync.go new file mode 100644 index 000000000000..9841779efacf --- /dev/null +++ b/pkg/x/sync/sync.go @@ -0,0 +1,46 @@ +package sync + +import "sync" + +type Map[K comparable, V any] struct { + m sync.Map +} + +func (m *Map[K, V]) Delete(key K) { m.m.Delete(key) } + +func (m *Map[K, V]) Load(key K) (value V, ok bool) { + v, ok := m.m.Load(key) + if !ok { + return value, ok + } + return v.(V), ok +} + +func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) { + v, loaded := m.m.LoadAndDelete(key) + if !loaded { + return value, loaded + } + return v.(V), loaded +} + +func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { + a, loaded := m.m.LoadOrStore(key, value) + return a.(V), loaded +} + +func (m *Map[K, V]) Range(f func(key K, value V) bool) { + m.m.Range(func(key, value any) bool { return f(key.(K), value.(V)) }) +} + +func (m *Map[K, V]) Store(key K, value V) { m.m.Store(key, value) } + +// Len returns the length of the map +func (m *Map[K, V]) Len() int { + var i int + m.m.Range(func(k, v interface{}) bool { + i++ + return true + }) + return i +} diff --git a/project.json b/project.json new file mode 100644 index 000000000000..4d785ebf0bd2 --- /dev/null +++ b/project.json @@ -0,0 +1,102 @@ +{ + "name": "repo-repos-trivy", + "root": "repos/trivy", + "projectType": "library", + "targets": { + "status": { + "executor": "nx:run-commands", + "options": { + "command": "git -C repos/trivy status --short || true" + }, + "metadata": { + "supervisorRequired": true + } + }, + "fetch": { + "executor": "nx:run-commands", + "options": { + "command": "git -C repos/trivy fetch --all --prune || true" + }, + "metadata": { + "supervisorRequired": true + } + }, + "log": { + "executor": "nx:run-commands", + "options": { + "command": "git -C repos/trivy log --oneline -10 || true" + }, + "metadata": { + "supervisorRequired": true + } + }, + "manifests": { + "executor": "nx:run-commands", + "options": { + "command": "find repos/trivy \\( -name package.json -o -name pyproject.toml -o -name Cargo.toml -o -name go.mod -o -name setup.py \\) -not -path '*/node_modules/*' -not -path '*/testdata/*' -not -path '*/fixtures/*' -print | sort" + }, + "metadata": { + "supervisorRequired": true + } + }, + "go-test": { + "executor": "nx:run-commands", + "options": { + "command": "sh -lc 'cd repos/trivy && go test ./... || true'" + }, + "metadata": { + "supervisorRequired": true + } + }, + "go-build": { + "executor": "nx:run-commands", + "options": { + "command": "sh -lc 'cd repos/trivy && go build ./... || true'" + }, + "metadata": { + "supervisorRequired": true + } + }, + "go-vet": { + "executor": "nx:run-commands", + "options": { + "command": "sh -lc 'cd repos/trivy && go vet ./... || true'" + }, + "metadata": { + "supervisorRequired": true + } + }, + "go-mod-tidy": { + "executor": "nx:run-commands", + "options": { + "command": "sh -lc 'cd repos/trivy && go mod tidy || true'" + }, + "metadata": { + "supervisorRequired": true + } + }, + "security-go": { + "executor": "nx:run-commands", + "options": { + "command": "sh -lc 'cd repos/trivy && govulncheck ./... || true'" + }, + "metadata": { + "supervisorRequired": true + } + }, + "security": { + "executor": "nx:run-commands", + "options": { + "command": "sh -lc 'printf \"security targets: security-go\\n\"'" + }, + "metadata": { + "supervisorRequired": true + } + } + }, + "tags": [ + "scope:repos", + "type:subrepo", + "lang:go" + ] +} \ No newline at end of file diff --git a/rpc/cache/service.pb.go b/rpc/cache/service.pb.go new file mode 100644 index 000000000000..d3a1fa1a63b9 --- /dev/null +++ b/rpc/cache/service.pb.go @@ -0,0 +1,889 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.4 +// source: rpc/cache/service.proto + +package cache + +import ( + common "github.com/aquasecurity/trivy/rpc/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ArtifactInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SchemaVersion int32 `protobuf:"varint,1,opt,name=schema_version,json=schemaVersion,proto3" json:"schema_version,omitempty"` + Architecture string `protobuf:"bytes,2,opt,name=architecture,proto3" json:"architecture,omitempty"` + Created *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created,proto3" json:"created,omitempty"` + DockerVersion string `protobuf:"bytes,4,opt,name=docker_version,json=dockerVersion,proto3" json:"docker_version,omitempty"` + Os string `protobuf:"bytes,5,opt,name=os,proto3" json:"os,omitempty"` + HistoryPackages []*common.Package `protobuf:"bytes,6,rep,name=history_packages,json=historyPackages,proto3" json:"history_packages,omitempty"` +} + +func (x *ArtifactInfo) Reset() { + *x = ArtifactInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_cache_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ArtifactInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ArtifactInfo) ProtoMessage() {} + +func (x *ArtifactInfo) ProtoReflect() protoreflect.Message { + mi := &file_rpc_cache_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ArtifactInfo.ProtoReflect.Descriptor instead. +func (*ArtifactInfo) Descriptor() ([]byte, []int) { + return file_rpc_cache_service_proto_rawDescGZIP(), []int{0} +} + +func (x *ArtifactInfo) GetSchemaVersion() int32 { + if x != nil { + return x.SchemaVersion + } + return 0 +} + +func (x *ArtifactInfo) GetArchitecture() string { + if x != nil { + return x.Architecture + } + return "" +} + +func (x *ArtifactInfo) GetCreated() *timestamppb.Timestamp { + if x != nil { + return x.Created + } + return nil +} + +func (x *ArtifactInfo) GetDockerVersion() string { + if x != nil { + return x.DockerVersion + } + return "" +} + +func (x *ArtifactInfo) GetOs() string { + if x != nil { + return x.Os + } + return "" +} + +func (x *ArtifactInfo) GetHistoryPackages() []*common.Package { + if x != nil { + return x.HistoryPackages + } + return nil +} + +type PutArtifactRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ArtifactId string `protobuf:"bytes,1,opt,name=artifact_id,json=artifactId,proto3" json:"artifact_id,omitempty"` + ArtifactInfo *ArtifactInfo `protobuf:"bytes,2,opt,name=artifact_info,json=artifactInfo,proto3" json:"artifact_info,omitempty"` +} + +func (x *PutArtifactRequest) Reset() { + *x = PutArtifactRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_cache_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PutArtifactRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutArtifactRequest) ProtoMessage() {} + +func (x *PutArtifactRequest) ProtoReflect() protoreflect.Message { + mi := &file_rpc_cache_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutArtifactRequest.ProtoReflect.Descriptor instead. +func (*PutArtifactRequest) Descriptor() ([]byte, []int) { + return file_rpc_cache_service_proto_rawDescGZIP(), []int{1} +} + +func (x *PutArtifactRequest) GetArtifactId() string { + if x != nil { + return x.ArtifactId + } + return "" +} + +func (x *PutArtifactRequest) GetArtifactInfo() *ArtifactInfo { + if x != nil { + return x.ArtifactInfo + } + return nil +} + +type BlobInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SchemaVersion int32 `protobuf:"varint,1,opt,name=schema_version,json=schemaVersion,proto3" json:"schema_version,omitempty"` + Os *common.OS `protobuf:"bytes,2,opt,name=os,proto3" json:"os,omitempty"` + Repository *common.Repository `protobuf:"bytes,11,opt,name=repository,proto3" json:"repository,omitempty"` + PackageInfos []*common.PackageInfo `protobuf:"bytes,3,rep,name=package_infos,json=packageInfos,proto3" json:"package_infos,omitempty"` + Applications []*common.Application `protobuf:"bytes,4,rep,name=applications,proto3" json:"applications,omitempty"` + Misconfigurations []*common.Misconfiguration `protobuf:"bytes,9,rep,name=misconfigurations,proto3" json:"misconfigurations,omitempty"` + OpaqueDirs []string `protobuf:"bytes,5,rep,name=opaque_dirs,json=opaqueDirs,proto3" json:"opaque_dirs,omitempty"` + WhiteoutFiles []string `protobuf:"bytes,6,rep,name=whiteout_files,json=whiteoutFiles,proto3" json:"whiteout_files,omitempty"` + Digest string `protobuf:"bytes,7,opt,name=digest,proto3" json:"digest,omitempty"` + DiffId string `protobuf:"bytes,8,opt,name=diff_id,json=diffId,proto3" json:"diff_id,omitempty"` + CustomResources []*common.CustomResource `protobuf:"bytes,10,rep,name=custom_resources,json=customResources,proto3" json:"custom_resources,omitempty"` + Secrets []*common.Secret `protobuf:"bytes,12,rep,name=secrets,proto3" json:"secrets,omitempty"` + Licenses []*common.LicenseFile `protobuf:"bytes,13,rep,name=licenses,proto3" json:"licenses,omitempty"` +} + +func (x *BlobInfo) Reset() { + *x = BlobInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_cache_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlobInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlobInfo) ProtoMessage() {} + +func (x *BlobInfo) ProtoReflect() protoreflect.Message { + mi := &file_rpc_cache_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlobInfo.ProtoReflect.Descriptor instead. +func (*BlobInfo) Descriptor() ([]byte, []int) { + return file_rpc_cache_service_proto_rawDescGZIP(), []int{2} +} + +func (x *BlobInfo) GetSchemaVersion() int32 { + if x != nil { + return x.SchemaVersion + } + return 0 +} + +func (x *BlobInfo) GetOs() *common.OS { + if x != nil { + return x.Os + } + return nil +} + +func (x *BlobInfo) GetRepository() *common.Repository { + if x != nil { + return x.Repository + } + return nil +} + +func (x *BlobInfo) GetPackageInfos() []*common.PackageInfo { + if x != nil { + return x.PackageInfos + } + return nil +} + +func (x *BlobInfo) GetApplications() []*common.Application { + if x != nil { + return x.Applications + } + return nil +} + +func (x *BlobInfo) GetMisconfigurations() []*common.Misconfiguration { + if x != nil { + return x.Misconfigurations + } + return nil +} + +func (x *BlobInfo) GetOpaqueDirs() []string { + if x != nil { + return x.OpaqueDirs + } + return nil +} + +func (x *BlobInfo) GetWhiteoutFiles() []string { + if x != nil { + return x.WhiteoutFiles + } + return nil +} + +func (x *BlobInfo) GetDigest() string { + if x != nil { + return x.Digest + } + return "" +} + +func (x *BlobInfo) GetDiffId() string { + if x != nil { + return x.DiffId + } + return "" +} + +func (x *BlobInfo) GetCustomResources() []*common.CustomResource { + if x != nil { + return x.CustomResources + } + return nil +} + +func (x *BlobInfo) GetSecrets() []*common.Secret { + if x != nil { + return x.Secrets + } + return nil +} + +func (x *BlobInfo) GetLicenses() []*common.LicenseFile { + if x != nil { + return x.Licenses + } + return nil +} + +type PutBlobRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DiffId string `protobuf:"bytes,1,opt,name=diff_id,json=diffId,proto3" json:"diff_id,omitempty"` + BlobInfo *BlobInfo `protobuf:"bytes,3,opt,name=blob_info,json=blobInfo,proto3" json:"blob_info,omitempty"` +} + +func (x *PutBlobRequest) Reset() { + *x = PutBlobRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_cache_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PutBlobRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutBlobRequest) ProtoMessage() {} + +func (x *PutBlobRequest) ProtoReflect() protoreflect.Message { + mi := &file_rpc_cache_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutBlobRequest.ProtoReflect.Descriptor instead. +func (*PutBlobRequest) Descriptor() ([]byte, []int) { + return file_rpc_cache_service_proto_rawDescGZIP(), []int{3} +} + +func (x *PutBlobRequest) GetDiffId() string { + if x != nil { + return x.DiffId + } + return "" +} + +func (x *PutBlobRequest) GetBlobInfo() *BlobInfo { + if x != nil { + return x.BlobInfo + } + return nil +} + +type PutResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Os *common.OS `protobuf:"bytes,1,opt,name=os,proto3" json:"os,omitempty"` + Eosl bool `protobuf:"varint,2,opt,name=eosl,proto3" json:"eosl,omitempty"` +} + +func (x *PutResponse) Reset() { + *x = PutResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_cache_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PutResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutResponse) ProtoMessage() {} + +func (x *PutResponse) ProtoReflect() protoreflect.Message { + mi := &file_rpc_cache_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutResponse.ProtoReflect.Descriptor instead. +func (*PutResponse) Descriptor() ([]byte, []int) { + return file_rpc_cache_service_proto_rawDescGZIP(), []int{4} +} + +func (x *PutResponse) GetOs() *common.OS { + if x != nil { + return x.Os + } + return nil +} + +func (x *PutResponse) GetEosl() bool { + if x != nil { + return x.Eosl + } + return false +} + +type MissingBlobsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ArtifactId string `protobuf:"bytes,1,opt,name=artifact_id,json=artifactId,proto3" json:"artifact_id,omitempty"` + BlobIds []string `protobuf:"bytes,2,rep,name=blob_ids,json=blobIds,proto3" json:"blob_ids,omitempty"` +} + +func (x *MissingBlobsRequest) Reset() { + *x = MissingBlobsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_cache_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MissingBlobsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MissingBlobsRequest) ProtoMessage() {} + +func (x *MissingBlobsRequest) ProtoReflect() protoreflect.Message { + mi := &file_rpc_cache_service_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MissingBlobsRequest.ProtoReflect.Descriptor instead. +func (*MissingBlobsRequest) Descriptor() ([]byte, []int) { + return file_rpc_cache_service_proto_rawDescGZIP(), []int{5} +} + +func (x *MissingBlobsRequest) GetArtifactId() string { + if x != nil { + return x.ArtifactId + } + return "" +} + +func (x *MissingBlobsRequest) GetBlobIds() []string { + if x != nil { + return x.BlobIds + } + return nil +} + +type MissingBlobsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MissingArtifact bool `protobuf:"varint,1,opt,name=missing_artifact,json=missingArtifact,proto3" json:"missing_artifact,omitempty"` + MissingBlobIds []string `protobuf:"bytes,2,rep,name=missing_blob_ids,json=missingBlobIds,proto3" json:"missing_blob_ids,omitempty"` +} + +func (x *MissingBlobsResponse) Reset() { + *x = MissingBlobsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_cache_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MissingBlobsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MissingBlobsResponse) ProtoMessage() {} + +func (x *MissingBlobsResponse) ProtoReflect() protoreflect.Message { + mi := &file_rpc_cache_service_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MissingBlobsResponse.ProtoReflect.Descriptor instead. +func (*MissingBlobsResponse) Descriptor() ([]byte, []int) { + return file_rpc_cache_service_proto_rawDescGZIP(), []int{6} +} + +func (x *MissingBlobsResponse) GetMissingArtifact() bool { + if x != nil { + return x.MissingArtifact + } + return false +} + +func (x *MissingBlobsResponse) GetMissingBlobIds() []string { + if x != nil { + return x.MissingBlobIds + } + return nil +} + +type DeleteBlobsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BlobIds []string `protobuf:"bytes,1,rep,name=blob_ids,json=blobIds,proto3" json:"blob_ids,omitempty"` +} + +func (x *DeleteBlobsRequest) Reset() { + *x = DeleteBlobsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_cache_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteBlobsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteBlobsRequest) ProtoMessage() {} + +func (x *DeleteBlobsRequest) ProtoReflect() protoreflect.Message { + mi := &file_rpc_cache_service_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteBlobsRequest.ProtoReflect.Descriptor instead. +func (*DeleteBlobsRequest) Descriptor() ([]byte, []int) { + return file_rpc_cache_service_proto_rawDescGZIP(), []int{7} +} + +func (x *DeleteBlobsRequest) GetBlobIds() []string { + if x != nil { + return x.BlobIds + } + return nil +} + +var File_rpc_cache_service_proto protoreflect.FileDescriptor + +var file_rpc_cache_service_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x72, 0x70, 0x63, 0x2f, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x88, 0x02, 0x0a, 0x0c, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, + 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x34, 0x0a, + 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x5f, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x6f, 0x63, + 0x6b, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x40, 0x0a, 0x10, 0x68, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x0f, 0x68, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0x78, 0x0a, 0x12, + 0x50, 0x75, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, + 0x74, 0x49, 0x64, 0x12, 0x41, 0x0a, 0x0d, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, + 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x72, 0x74, 0x69, + 0x66, 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0c, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, + 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x83, 0x05, 0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x62, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x02, 0x6f, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x38, 0x0a, 0x0a, + 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x3e, 0x0a, 0x0d, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x4c, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x64, 0x69, + 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, + 0x44, 0x69, 0x72, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x68, 0x69, 0x74, 0x65, 0x6f, 0x75, 0x74, + 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x68, + 0x69, 0x74, 0x65, 0x6f, 0x75, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, + 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, + 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x69, 0x64, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x66, 0x66, 0x49, 0x64, 0x12, 0x47, 0x0a, 0x10, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x52, 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x07, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, + 0x6c, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x22, 0x60, 0x0a, 0x0e, + 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, + 0x0a, 0x07, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x64, 0x69, 0x66, 0x66, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x62, 0x5f, + 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x62, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x43, + 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, + 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x65, 0x6f, 0x73, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x65, + 0x6f, 0x73, 0x6c, 0x22, 0x51, 0x0a, 0x13, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, + 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x72, + 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x62, + 0x6c, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x62, + 0x6c, 0x6f, 0x62, 0x49, 0x64, 0x73, 0x22, 0x6b, 0x0a, 0x14, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, + 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, + 0x0a, 0x10, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, + 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, + 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, + 0x49, 0x64, 0x73, 0x22, 0x2f, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x6c, 0x6f, + 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, 0x6f, + 0x62, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6c, 0x6f, + 0x62, 0x49, 0x64, 0x73, 0x32, 0xbb, 0x02, 0x0a, 0x05, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x49, + 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x22, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, + 0x75, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x41, 0x0a, 0x07, 0x50, 0x75, 0x74, + 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, + 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x59, 0x0a, 0x0c, + 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x12, 0x23, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, + 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x24, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x12, 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, + 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x6c, + 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x3b, 0x63, 0x61, + 0x63, 0x68, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_rpc_cache_service_proto_rawDescOnce sync.Once + file_rpc_cache_service_proto_rawDescData = file_rpc_cache_service_proto_rawDesc +) + +func file_rpc_cache_service_proto_rawDescGZIP() []byte { + file_rpc_cache_service_proto_rawDescOnce.Do(func() { + file_rpc_cache_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_rpc_cache_service_proto_rawDescData) + }) + return file_rpc_cache_service_proto_rawDescData +} + +var file_rpc_cache_service_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_rpc_cache_service_proto_goTypes = []interface{}{ + (*ArtifactInfo)(nil), // 0: trivy.cache.v1.ArtifactInfo + (*PutArtifactRequest)(nil), // 1: trivy.cache.v1.PutArtifactRequest + (*BlobInfo)(nil), // 2: trivy.cache.v1.BlobInfo + (*PutBlobRequest)(nil), // 3: trivy.cache.v1.PutBlobRequest + (*PutResponse)(nil), // 4: trivy.cache.v1.PutResponse + (*MissingBlobsRequest)(nil), // 5: trivy.cache.v1.MissingBlobsRequest + (*MissingBlobsResponse)(nil), // 6: trivy.cache.v1.MissingBlobsResponse + (*DeleteBlobsRequest)(nil), // 7: trivy.cache.v1.DeleteBlobsRequest + (*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp + (*common.Package)(nil), // 9: trivy.common.Package + (*common.OS)(nil), // 10: trivy.common.OS + (*common.Repository)(nil), // 11: trivy.common.Repository + (*common.PackageInfo)(nil), // 12: trivy.common.PackageInfo + (*common.Application)(nil), // 13: trivy.common.Application + (*common.Misconfiguration)(nil), // 14: trivy.common.Misconfiguration + (*common.CustomResource)(nil), // 15: trivy.common.CustomResource + (*common.Secret)(nil), // 16: trivy.common.Secret + (*common.LicenseFile)(nil), // 17: trivy.common.LicenseFile + (*emptypb.Empty)(nil), // 18: google.protobuf.Empty +} +var file_rpc_cache_service_proto_depIdxs = []int32{ + 8, // 0: trivy.cache.v1.ArtifactInfo.created:type_name -> google.protobuf.Timestamp + 9, // 1: trivy.cache.v1.ArtifactInfo.history_packages:type_name -> trivy.common.Package + 0, // 2: trivy.cache.v1.PutArtifactRequest.artifact_info:type_name -> trivy.cache.v1.ArtifactInfo + 10, // 3: trivy.cache.v1.BlobInfo.os:type_name -> trivy.common.OS + 11, // 4: trivy.cache.v1.BlobInfo.repository:type_name -> trivy.common.Repository + 12, // 5: trivy.cache.v1.BlobInfo.package_infos:type_name -> trivy.common.PackageInfo + 13, // 6: trivy.cache.v1.BlobInfo.applications:type_name -> trivy.common.Application + 14, // 7: trivy.cache.v1.BlobInfo.misconfigurations:type_name -> trivy.common.Misconfiguration + 15, // 8: trivy.cache.v1.BlobInfo.custom_resources:type_name -> trivy.common.CustomResource + 16, // 9: trivy.cache.v1.BlobInfo.secrets:type_name -> trivy.common.Secret + 17, // 10: trivy.cache.v1.BlobInfo.licenses:type_name -> trivy.common.LicenseFile + 2, // 11: trivy.cache.v1.PutBlobRequest.blob_info:type_name -> trivy.cache.v1.BlobInfo + 10, // 12: trivy.cache.v1.PutResponse.os:type_name -> trivy.common.OS + 1, // 13: trivy.cache.v1.Cache.PutArtifact:input_type -> trivy.cache.v1.PutArtifactRequest + 3, // 14: trivy.cache.v1.Cache.PutBlob:input_type -> trivy.cache.v1.PutBlobRequest + 5, // 15: trivy.cache.v1.Cache.MissingBlobs:input_type -> trivy.cache.v1.MissingBlobsRequest + 7, // 16: trivy.cache.v1.Cache.DeleteBlobs:input_type -> trivy.cache.v1.DeleteBlobsRequest + 18, // 17: trivy.cache.v1.Cache.PutArtifact:output_type -> google.protobuf.Empty + 18, // 18: trivy.cache.v1.Cache.PutBlob:output_type -> google.protobuf.Empty + 6, // 19: trivy.cache.v1.Cache.MissingBlobs:output_type -> trivy.cache.v1.MissingBlobsResponse + 18, // 20: trivy.cache.v1.Cache.DeleteBlobs:output_type -> google.protobuf.Empty + 17, // [17:21] is the sub-list for method output_type + 13, // [13:17] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name +} + +func init() { file_rpc_cache_service_proto_init() } +func file_rpc_cache_service_proto_init() { + if File_rpc_cache_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_rpc_cache_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ArtifactInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_cache_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PutArtifactRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_cache_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlobInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_cache_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PutBlobRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_cache_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PutResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_cache_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MissingBlobsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_cache_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MissingBlobsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_cache_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteBlobsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_rpc_cache_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_rpc_cache_service_proto_goTypes, + DependencyIndexes: file_rpc_cache_service_proto_depIdxs, + MessageInfos: file_rpc_cache_service_proto_msgTypes, + }.Build() + File_rpc_cache_service_proto = out.File + file_rpc_cache_service_proto_rawDesc = nil + file_rpc_cache_service_proto_goTypes = nil + file_rpc_cache_service_proto_depIdxs = nil +} diff --git a/rpc/cache/service.proto b/rpc/cache/service.proto new file mode 100644 index 000000000000..a1b113a202c7 --- /dev/null +++ b/rpc/cache/service.proto @@ -0,0 +1,69 @@ +syntax = "proto3"; + +package trivy.cache.v1; +option go_package = "github.com/aquasecurity/trivy/rpc/cache;cache"; + +import "google/protobuf/timestamp.proto"; +import "rpc/common/service.proto"; +import "google/protobuf/empty.proto"; + +service Cache { + rpc PutArtifact(PutArtifactRequest) returns (google.protobuf.Empty); + rpc PutBlob(PutBlobRequest) returns (google.protobuf.Empty); + rpc MissingBlobs(MissingBlobsRequest) returns (MissingBlobsResponse); + rpc DeleteBlobs(DeleteBlobsRequest) returns (google.protobuf.Empty); +} + +message ArtifactInfo { + int32 schema_version = 1; + string architecture = 2; + google.protobuf.Timestamp created = 3; + string docker_version = 4; + string os = 5; + repeated common.Package history_packages = 6; +} + +message PutArtifactRequest { + string artifact_id = 1; + ArtifactInfo artifact_info = 2; +} + +message BlobInfo { + int32 schema_version = 1; + common.OS os = 2; + common.Repository repository = 11; + repeated common.PackageInfo package_infos = 3; + repeated common.Application applications = 4; + repeated common.Misconfiguration misconfigurations = 9; + repeated string opaque_dirs = 5; + repeated string whiteout_files = 6; + string digest = 7; + string diff_id = 8; + repeated common.CustomResource custom_resources = 10; + repeated common.Secret secrets = 12; + repeated common.LicenseFile licenses = 13; +} + +message PutBlobRequest { + string diff_id = 1; + BlobInfo blob_info = 3; +} + +message PutResponse { + common.OS os = 1; + bool eosl = 2; +} + +message MissingBlobsRequest { + string artifact_id = 1; + repeated string blob_ids = 2; +} + +message MissingBlobsResponse { + bool missing_artifact = 1; + repeated string missing_blob_ids = 2; +} + +message DeleteBlobsRequest { + repeated string blob_ids = 1; +} diff --git a/rpc/cache/service.twirp.go b/rpc/cache/service.twirp.go new file mode 100644 index 000000000000..f5f138ef5091 --- /dev/null +++ b/rpc/cache/service.twirp.go @@ -0,0 +1,1989 @@ +// Code generated by protoc-gen-twirp v8.1.0, DO NOT EDIT. +// source: rpc/cache/service.proto + +package cache + +import context "context" +import fmt "fmt" +import http "net/http" +import ioutil "io/ioutil" +import json "encoding/json" +import strconv "strconv" +import strings "strings" + +import protojson "google.golang.org/protobuf/encoding/protojson" +import proto "google.golang.org/protobuf/proto" +import twirp "github.com/twitchtv/twirp" +import ctxsetters "github.com/twitchtv/twirp/ctxsetters" + +import google_protobuf2 "google.golang.org/protobuf/types/known/emptypb" + +import bytes "bytes" +import errors "errors" +import io "io" +import path "path" +import url "net/url" + +// Version compatibility assertion. +// If the constant is not defined in the package, that likely means +// the package needs to be updated to work with this generated code. +// See https://twitchtv.github.io/twirp/docs/version_matrix.html +const _ = twirp.TwirpPackageMinVersion_8_1_0 + +// =============== +// Cache Interface +// =============== + +type Cache interface { + PutArtifact(context.Context, *PutArtifactRequest) (*google_protobuf2.Empty, error) + + PutBlob(context.Context, *PutBlobRequest) (*google_protobuf2.Empty, error) + + MissingBlobs(context.Context, *MissingBlobsRequest) (*MissingBlobsResponse, error) + + DeleteBlobs(context.Context, *DeleteBlobsRequest) (*google_protobuf2.Empty, error) +} + +// ===================== +// Cache Protobuf Client +// ===================== + +type cacheProtobufClient struct { + client HTTPClient + urls [4]string + interceptor twirp.Interceptor + opts twirp.ClientOptions +} + +// NewCacheProtobufClient creates a Protobuf client that implements the Cache interface. +// It communicates using Protobuf and can be configured with a custom HTTPClient. +func NewCacheProtobufClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Cache { + if c, ok := client.(*http.Client); ok { + client = withoutRedirects(c) + } + + clientOpts := twirp.ClientOptions{} + for _, o := range opts { + o(&clientOpts) + } + + // Using ReadOpt allows backwards and forwads compatibility with new options in the future + literalURLs := false + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) + var pathPrefix string + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { + pathPrefix = "/twirp" // default prefix + } + + // Build method URLs: []/./ + serviceURL := sanitizeBaseURL(baseURL) + serviceURL += baseServicePath(pathPrefix, "trivy.cache.v1", "Cache") + urls := [4]string{ + serviceURL + "PutArtifact", + serviceURL + "PutBlob", + serviceURL + "MissingBlobs", + serviceURL + "DeleteBlobs", + } + + return &cacheProtobufClient{ + client: client, + urls: urls, + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), + opts: clientOpts, + } +} + +func (c *cacheProtobufClient) PutArtifact(ctx context.Context, in *PutArtifactRequest) (*google_protobuf2.Empty, error) { + ctx = ctxsetters.WithPackageName(ctx, "trivy.cache.v1") + ctx = ctxsetters.WithServiceName(ctx, "Cache") + ctx = ctxsetters.WithMethodName(ctx, "PutArtifact") + caller := c.callPutArtifact + if c.interceptor != nil { + caller = func(ctx context.Context, req *PutArtifactRequest) (*google_protobuf2.Empty, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*PutArtifactRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*PutArtifactRequest) when calling interceptor") + } + return c.callPutArtifact(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *cacheProtobufClient) callPutArtifact(ctx context.Context, in *PutArtifactRequest) (*google_protobuf2.Empty, error) { + out := new(google_protobuf2.Empty) + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +func (c *cacheProtobufClient) PutBlob(ctx context.Context, in *PutBlobRequest) (*google_protobuf2.Empty, error) { + ctx = ctxsetters.WithPackageName(ctx, "trivy.cache.v1") + ctx = ctxsetters.WithServiceName(ctx, "Cache") + ctx = ctxsetters.WithMethodName(ctx, "PutBlob") + caller := c.callPutBlob + if c.interceptor != nil { + caller = func(ctx context.Context, req *PutBlobRequest) (*google_protobuf2.Empty, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*PutBlobRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*PutBlobRequest) when calling interceptor") + } + return c.callPutBlob(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *cacheProtobufClient) callPutBlob(ctx context.Context, in *PutBlobRequest) (*google_protobuf2.Empty, error) { + out := new(google_protobuf2.Empty) + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[1], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +func (c *cacheProtobufClient) MissingBlobs(ctx context.Context, in *MissingBlobsRequest) (*MissingBlobsResponse, error) { + ctx = ctxsetters.WithPackageName(ctx, "trivy.cache.v1") + ctx = ctxsetters.WithServiceName(ctx, "Cache") + ctx = ctxsetters.WithMethodName(ctx, "MissingBlobs") + caller := c.callMissingBlobs + if c.interceptor != nil { + caller = func(ctx context.Context, req *MissingBlobsRequest) (*MissingBlobsResponse, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*MissingBlobsRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*MissingBlobsRequest) when calling interceptor") + } + return c.callMissingBlobs(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*MissingBlobsResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*MissingBlobsResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *cacheProtobufClient) callMissingBlobs(ctx context.Context, in *MissingBlobsRequest) (*MissingBlobsResponse, error) { + out := new(MissingBlobsResponse) + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[2], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +func (c *cacheProtobufClient) DeleteBlobs(ctx context.Context, in *DeleteBlobsRequest) (*google_protobuf2.Empty, error) { + ctx = ctxsetters.WithPackageName(ctx, "trivy.cache.v1") + ctx = ctxsetters.WithServiceName(ctx, "Cache") + ctx = ctxsetters.WithMethodName(ctx, "DeleteBlobs") + caller := c.callDeleteBlobs + if c.interceptor != nil { + caller = func(ctx context.Context, req *DeleteBlobsRequest) (*google_protobuf2.Empty, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*DeleteBlobsRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*DeleteBlobsRequest) when calling interceptor") + } + return c.callDeleteBlobs(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *cacheProtobufClient) callDeleteBlobs(ctx context.Context, in *DeleteBlobsRequest) (*google_protobuf2.Empty, error) { + out := new(google_protobuf2.Empty) + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[3], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +// ================= +// Cache JSON Client +// ================= + +type cacheJSONClient struct { + client HTTPClient + urls [4]string + interceptor twirp.Interceptor + opts twirp.ClientOptions +} + +// NewCacheJSONClient creates a JSON client that implements the Cache interface. +// It communicates using JSON and can be configured with a custom HTTPClient. +func NewCacheJSONClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Cache { + if c, ok := client.(*http.Client); ok { + client = withoutRedirects(c) + } + + clientOpts := twirp.ClientOptions{} + for _, o := range opts { + o(&clientOpts) + } + + // Using ReadOpt allows backwards and forwads compatibility with new options in the future + literalURLs := false + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) + var pathPrefix string + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { + pathPrefix = "/twirp" // default prefix + } + + // Build method URLs: []/./ + serviceURL := sanitizeBaseURL(baseURL) + serviceURL += baseServicePath(pathPrefix, "trivy.cache.v1", "Cache") + urls := [4]string{ + serviceURL + "PutArtifact", + serviceURL + "PutBlob", + serviceURL + "MissingBlobs", + serviceURL + "DeleteBlobs", + } + + return &cacheJSONClient{ + client: client, + urls: urls, + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), + opts: clientOpts, + } +} + +func (c *cacheJSONClient) PutArtifact(ctx context.Context, in *PutArtifactRequest) (*google_protobuf2.Empty, error) { + ctx = ctxsetters.WithPackageName(ctx, "trivy.cache.v1") + ctx = ctxsetters.WithServiceName(ctx, "Cache") + ctx = ctxsetters.WithMethodName(ctx, "PutArtifact") + caller := c.callPutArtifact + if c.interceptor != nil { + caller = func(ctx context.Context, req *PutArtifactRequest) (*google_protobuf2.Empty, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*PutArtifactRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*PutArtifactRequest) when calling interceptor") + } + return c.callPutArtifact(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *cacheJSONClient) callPutArtifact(ctx context.Context, in *PutArtifactRequest) (*google_protobuf2.Empty, error) { + out := new(google_protobuf2.Empty) + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +func (c *cacheJSONClient) PutBlob(ctx context.Context, in *PutBlobRequest) (*google_protobuf2.Empty, error) { + ctx = ctxsetters.WithPackageName(ctx, "trivy.cache.v1") + ctx = ctxsetters.WithServiceName(ctx, "Cache") + ctx = ctxsetters.WithMethodName(ctx, "PutBlob") + caller := c.callPutBlob + if c.interceptor != nil { + caller = func(ctx context.Context, req *PutBlobRequest) (*google_protobuf2.Empty, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*PutBlobRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*PutBlobRequest) when calling interceptor") + } + return c.callPutBlob(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *cacheJSONClient) callPutBlob(ctx context.Context, in *PutBlobRequest) (*google_protobuf2.Empty, error) { + out := new(google_protobuf2.Empty) + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[1], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +func (c *cacheJSONClient) MissingBlobs(ctx context.Context, in *MissingBlobsRequest) (*MissingBlobsResponse, error) { + ctx = ctxsetters.WithPackageName(ctx, "trivy.cache.v1") + ctx = ctxsetters.WithServiceName(ctx, "Cache") + ctx = ctxsetters.WithMethodName(ctx, "MissingBlobs") + caller := c.callMissingBlobs + if c.interceptor != nil { + caller = func(ctx context.Context, req *MissingBlobsRequest) (*MissingBlobsResponse, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*MissingBlobsRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*MissingBlobsRequest) when calling interceptor") + } + return c.callMissingBlobs(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*MissingBlobsResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*MissingBlobsResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *cacheJSONClient) callMissingBlobs(ctx context.Context, in *MissingBlobsRequest) (*MissingBlobsResponse, error) { + out := new(MissingBlobsResponse) + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[2], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +func (c *cacheJSONClient) DeleteBlobs(ctx context.Context, in *DeleteBlobsRequest) (*google_protobuf2.Empty, error) { + ctx = ctxsetters.WithPackageName(ctx, "trivy.cache.v1") + ctx = ctxsetters.WithServiceName(ctx, "Cache") + ctx = ctxsetters.WithMethodName(ctx, "DeleteBlobs") + caller := c.callDeleteBlobs + if c.interceptor != nil { + caller = func(ctx context.Context, req *DeleteBlobsRequest) (*google_protobuf2.Empty, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*DeleteBlobsRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*DeleteBlobsRequest) when calling interceptor") + } + return c.callDeleteBlobs(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *cacheJSONClient) callDeleteBlobs(ctx context.Context, in *DeleteBlobsRequest) (*google_protobuf2.Empty, error) { + out := new(google_protobuf2.Empty) + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[3], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +// ==================== +// Cache Server Handler +// ==================== + +type cacheServer struct { + Cache + interceptor twirp.Interceptor + hooks *twirp.ServerHooks + pathPrefix string // prefix for routing + jsonSkipDefaults bool // do not include unpopulated fields (default values) in the response + jsonCamelCase bool // JSON fields are serialized as lowerCamelCase rather than keeping the original proto names +} + +// NewCacheServer builds a TwirpServer that can be used as an http.Handler to handle +// HTTP requests that are routed to the right method in the provided svc implementation. +// The opts are twirp.ServerOption modifiers, for example twirp.WithServerHooks(hooks). +func NewCacheServer(svc Cache, opts ...interface{}) TwirpServer { + serverOpts := newServerOpts(opts) + + // Using ReadOpt allows backwards and forwads compatibility with new options in the future + jsonSkipDefaults := false + _ = serverOpts.ReadOpt("jsonSkipDefaults", &jsonSkipDefaults) + jsonCamelCase := false + _ = serverOpts.ReadOpt("jsonCamelCase", &jsonCamelCase) + var pathPrefix string + if ok := serverOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { + pathPrefix = "/twirp" // default prefix + } + + return &cacheServer{ + Cache: svc, + hooks: serverOpts.Hooks, + interceptor: twirp.ChainInterceptors(serverOpts.Interceptors...), + pathPrefix: pathPrefix, + jsonSkipDefaults: jsonSkipDefaults, + jsonCamelCase: jsonCamelCase, + } +} + +// writeError writes an HTTP response with a valid Twirp error format, and triggers hooks. +// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) +func (s *cacheServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { + writeError(ctx, resp, err, s.hooks) +} + +// handleRequestBodyError is used to handle error when the twirp server cannot read request +func (s *cacheServer) handleRequestBodyError(ctx context.Context, resp http.ResponseWriter, msg string, err error) { + if context.Canceled == ctx.Err() { + s.writeError(ctx, resp, twirp.NewError(twirp.Canceled, "failed to read request: context canceled")) + return + } + if context.DeadlineExceeded == ctx.Err() { + s.writeError(ctx, resp, twirp.NewError(twirp.DeadlineExceeded, "failed to read request: deadline exceeded")) + return + } + s.writeError(ctx, resp, twirp.WrapError(malformedRequestError(msg), err)) +} + +// CachePathPrefix is a convenience constant that may identify URL paths. +// Should be used with caution, it only matches routes generated by Twirp Go clients, +// with the default "/twirp" prefix and default CamelCase service and method names. +// More info: https://twitchtv.github.io/twirp/docs/routing.html +const CachePathPrefix = "/twirp/trivy.cache.v1.Cache/" + +func (s *cacheServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + ctx := req.Context() + ctx = ctxsetters.WithPackageName(ctx, "trivy.cache.v1") + ctx = ctxsetters.WithServiceName(ctx, "Cache") + ctx = ctxsetters.WithResponseWriter(ctx, resp) + + var err error + ctx, err = callRequestReceived(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + if req.Method != "POST" { + msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } + + // Verify path format: []/./ + prefix, pkgService, method := parseTwirpPath(req.URL.Path) + if pkgService != "trivy.cache.v1.Cache" { + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } + if prefix != s.pathPrefix { + msg := fmt.Sprintf("invalid path prefix %q, expected %q, on path %q", prefix, s.pathPrefix, req.URL.Path) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } + + switch method { + case "PutArtifact": + s.servePutArtifact(ctx, resp, req) + return + case "PutBlob": + s.servePutBlob(ctx, resp, req) + return + case "MissingBlobs": + s.serveMissingBlobs(ctx, resp, req) + return + case "DeleteBlobs": + s.serveDeleteBlobs(ctx, resp, req) + return + default: + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } +} + +func (s *cacheServer) servePutArtifact(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + header := req.Header.Get("Content-Type") + i := strings.Index(header, ";") + if i == -1 { + i = len(header) + } + switch strings.TrimSpace(strings.ToLower(header[:i])) { + case "application/json": + s.servePutArtifactJSON(ctx, resp, req) + case "application/protobuf": + s.servePutArtifactProtobuf(ctx, resp, req) + default: + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) + twerr := badRouteError(msg, req.Method, req.URL.Path) + s.writeError(ctx, resp, twerr) + } +} + +func (s *cacheServer) servePutArtifactJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "PutArtifact") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + d := json.NewDecoder(req.Body) + rawReqBody := json.RawMessage{} + if err := d.Decode(&rawReqBody); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + reqContent := new(PutArtifactRequest) + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + + handler := s.Cache.PutArtifact + if s.interceptor != nil { + handler = func(ctx context.Context, req *PutArtifactRequest) (*google_protobuf2.Empty, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*PutArtifactRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*PutArtifactRequest) when calling interceptor") + } + return s.Cache.PutArtifact(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *google_protobuf2.Empty + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *google_protobuf2.Empty and nil error while calling PutArtifact. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} + respBytes, err := marshaler.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/json") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *cacheServer) servePutArtifactProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "PutArtifact") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + buf, err := ioutil.ReadAll(req.Body) + if err != nil { + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) + return + } + reqContent := new(PutArtifactRequest) + if err = proto.Unmarshal(buf, reqContent); err != nil { + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) + return + } + + handler := s.Cache.PutArtifact + if s.interceptor != nil { + handler = func(ctx context.Context, req *PutArtifactRequest) (*google_protobuf2.Empty, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*PutArtifactRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*PutArtifactRequest) when calling interceptor") + } + return s.Cache.PutArtifact(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *google_protobuf2.Empty + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *google_protobuf2.Empty and nil error while calling PutArtifact. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + respBytes, err := proto.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/protobuf") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *cacheServer) servePutBlob(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + header := req.Header.Get("Content-Type") + i := strings.Index(header, ";") + if i == -1 { + i = len(header) + } + switch strings.TrimSpace(strings.ToLower(header[:i])) { + case "application/json": + s.servePutBlobJSON(ctx, resp, req) + case "application/protobuf": + s.servePutBlobProtobuf(ctx, resp, req) + default: + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) + twerr := badRouteError(msg, req.Method, req.URL.Path) + s.writeError(ctx, resp, twerr) + } +} + +func (s *cacheServer) servePutBlobJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "PutBlob") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + d := json.NewDecoder(req.Body) + rawReqBody := json.RawMessage{} + if err := d.Decode(&rawReqBody); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + reqContent := new(PutBlobRequest) + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + + handler := s.Cache.PutBlob + if s.interceptor != nil { + handler = func(ctx context.Context, req *PutBlobRequest) (*google_protobuf2.Empty, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*PutBlobRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*PutBlobRequest) when calling interceptor") + } + return s.Cache.PutBlob(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *google_protobuf2.Empty + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *google_protobuf2.Empty and nil error while calling PutBlob. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} + respBytes, err := marshaler.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/json") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *cacheServer) servePutBlobProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "PutBlob") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + buf, err := ioutil.ReadAll(req.Body) + if err != nil { + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) + return + } + reqContent := new(PutBlobRequest) + if err = proto.Unmarshal(buf, reqContent); err != nil { + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) + return + } + + handler := s.Cache.PutBlob + if s.interceptor != nil { + handler = func(ctx context.Context, req *PutBlobRequest) (*google_protobuf2.Empty, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*PutBlobRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*PutBlobRequest) when calling interceptor") + } + return s.Cache.PutBlob(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *google_protobuf2.Empty + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *google_protobuf2.Empty and nil error while calling PutBlob. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + respBytes, err := proto.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/protobuf") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *cacheServer) serveMissingBlobs(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + header := req.Header.Get("Content-Type") + i := strings.Index(header, ";") + if i == -1 { + i = len(header) + } + switch strings.TrimSpace(strings.ToLower(header[:i])) { + case "application/json": + s.serveMissingBlobsJSON(ctx, resp, req) + case "application/protobuf": + s.serveMissingBlobsProtobuf(ctx, resp, req) + default: + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) + twerr := badRouteError(msg, req.Method, req.URL.Path) + s.writeError(ctx, resp, twerr) + } +} + +func (s *cacheServer) serveMissingBlobsJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "MissingBlobs") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + d := json.NewDecoder(req.Body) + rawReqBody := json.RawMessage{} + if err := d.Decode(&rawReqBody); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + reqContent := new(MissingBlobsRequest) + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + + handler := s.Cache.MissingBlobs + if s.interceptor != nil { + handler = func(ctx context.Context, req *MissingBlobsRequest) (*MissingBlobsResponse, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*MissingBlobsRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*MissingBlobsRequest) when calling interceptor") + } + return s.Cache.MissingBlobs(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*MissingBlobsResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*MissingBlobsResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *MissingBlobsResponse + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *MissingBlobsResponse and nil error while calling MissingBlobs. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} + respBytes, err := marshaler.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/json") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *cacheServer) serveMissingBlobsProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "MissingBlobs") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + buf, err := ioutil.ReadAll(req.Body) + if err != nil { + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) + return + } + reqContent := new(MissingBlobsRequest) + if err = proto.Unmarshal(buf, reqContent); err != nil { + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) + return + } + + handler := s.Cache.MissingBlobs + if s.interceptor != nil { + handler = func(ctx context.Context, req *MissingBlobsRequest) (*MissingBlobsResponse, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*MissingBlobsRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*MissingBlobsRequest) when calling interceptor") + } + return s.Cache.MissingBlobs(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*MissingBlobsResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*MissingBlobsResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *MissingBlobsResponse + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *MissingBlobsResponse and nil error while calling MissingBlobs. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + respBytes, err := proto.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/protobuf") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *cacheServer) serveDeleteBlobs(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + header := req.Header.Get("Content-Type") + i := strings.Index(header, ";") + if i == -1 { + i = len(header) + } + switch strings.TrimSpace(strings.ToLower(header[:i])) { + case "application/json": + s.serveDeleteBlobsJSON(ctx, resp, req) + case "application/protobuf": + s.serveDeleteBlobsProtobuf(ctx, resp, req) + default: + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) + twerr := badRouteError(msg, req.Method, req.URL.Path) + s.writeError(ctx, resp, twerr) + } +} + +func (s *cacheServer) serveDeleteBlobsJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "DeleteBlobs") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + d := json.NewDecoder(req.Body) + rawReqBody := json.RawMessage{} + if err := d.Decode(&rawReqBody); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + reqContent := new(DeleteBlobsRequest) + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + + handler := s.Cache.DeleteBlobs + if s.interceptor != nil { + handler = func(ctx context.Context, req *DeleteBlobsRequest) (*google_protobuf2.Empty, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*DeleteBlobsRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*DeleteBlobsRequest) when calling interceptor") + } + return s.Cache.DeleteBlobs(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *google_protobuf2.Empty + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *google_protobuf2.Empty and nil error while calling DeleteBlobs. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} + respBytes, err := marshaler.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/json") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *cacheServer) serveDeleteBlobsProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "DeleteBlobs") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + buf, err := ioutil.ReadAll(req.Body) + if err != nil { + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) + return + } + reqContent := new(DeleteBlobsRequest) + if err = proto.Unmarshal(buf, reqContent); err != nil { + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) + return + } + + handler := s.Cache.DeleteBlobs + if s.interceptor != nil { + handler = func(ctx context.Context, req *DeleteBlobsRequest) (*google_protobuf2.Empty, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*DeleteBlobsRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*DeleteBlobsRequest) when calling interceptor") + } + return s.Cache.DeleteBlobs(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*google_protobuf2.Empty) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*google_protobuf2.Empty) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *google_protobuf2.Empty + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *google_protobuf2.Empty and nil error while calling DeleteBlobs. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + respBytes, err := proto.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/protobuf") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *cacheServer) ServiceDescriptor() ([]byte, int) { + return twirpFileDescriptor0, 0 +} + +func (s *cacheServer) ProtocGenTwirpVersion() string { + return "v8.1.0" +} + +// PathPrefix returns the base service path, in the form: "//./" +// that is everything in a Twirp route except for the . This can be used for routing, +// for example to identify the requests that are targeted to this service in a mux. +func (s *cacheServer) PathPrefix() string { + return baseServicePath(s.pathPrefix, "trivy.cache.v1", "Cache") +} + +// ===== +// Utils +// ===== + +// HTTPClient is the interface used by generated clients to send HTTP requests. +// It is fulfilled by *(net/http).Client, which is sufficient for most users. +// Users can provide their own implementation for special retry policies. +// +// HTTPClient implementations should not follow redirects. Redirects are +// automatically disabled if *(net/http).Client is passed to client +// constructors. See the withoutRedirects function in this file for more +// details. +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// TwirpServer is the interface generated server structs will support: they're +// HTTP handlers with additional methods for accessing metadata about the +// service. Those accessors are a low-level API for building reflection tools. +// Most people can think of TwirpServers as just http.Handlers. +type TwirpServer interface { + http.Handler + + // ServiceDescriptor returns gzipped bytes describing the .proto file that + // this service was generated from. Once unzipped, the bytes can be + // unmarshalled as a + // google.golang.org/protobuf/types/descriptorpb.FileDescriptorProto. + // + // The returned integer is the index of this particular service within that + // FileDescriptorProto's 'Service' slice of ServiceDescriptorProtos. This is a + // low-level field, expected to be used for reflection. + ServiceDescriptor() ([]byte, int) + + // ProtocGenTwirpVersion is the semantic version string of the version of + // twirp used to generate this file. + ProtocGenTwirpVersion() string + + // PathPrefix returns the HTTP URL path prefix for all methods handled by this + // service. This can be used with an HTTP mux to route Twirp requests. + // The path prefix is in the form: "//./" + // that is, everything in a Twirp route except for the at the end. + PathPrefix() string +} + +func newServerOpts(opts []interface{}) *twirp.ServerOptions { + serverOpts := &twirp.ServerOptions{} + for _, opt := range opts { + switch o := opt.(type) { + case twirp.ServerOption: + o(serverOpts) + case *twirp.ServerHooks: // backwards compatibility, allow to specify hooks as an argument + twirp.WithServerHooks(o)(serverOpts) + case nil: // backwards compatibility, allow nil value for the argument + continue + default: + panic(fmt.Sprintf("Invalid option type %T, please use a twirp.ServerOption", o)) + } + } + return serverOpts +} + +// WriteError writes an HTTP response with a valid Twirp error format (code, msg, meta). +// Useful outside of the Twirp server (e.g. http middleware), but does not trigger hooks. +// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) +func WriteError(resp http.ResponseWriter, err error) { + writeError(context.Background(), resp, err, nil) +} + +// writeError writes Twirp errors in the response and triggers hooks. +func writeError(ctx context.Context, resp http.ResponseWriter, err error, hooks *twirp.ServerHooks) { + // Convert to a twirp.Error. Non-twirp errors are converted to internal errors. + var twerr twirp.Error + if !errors.As(err, &twerr) { + twerr = twirp.InternalErrorWith(err) + } + + statusCode := twirp.ServerHTTPStatusFromErrorCode(twerr.Code()) + ctx = ctxsetters.WithStatusCode(ctx, statusCode) + ctx = callError(ctx, hooks, twerr) + + respBody := marshalErrorToJSON(twerr) + + resp.Header().Set("Content-Type", "application/json") // Error responses are always JSON + resp.Header().Set("Content-Length", strconv.Itoa(len(respBody))) + resp.WriteHeader(statusCode) // set HTTP status code and send response + + _, writeErr := resp.Write(respBody) + if writeErr != nil { + // We have three options here. We could log the error, call the Error + // hook, or just silently ignore the error. + // + // Logging is unacceptable because we don't have a user-controlled + // logger; writing out to stderr without permission is too rude. + // + // Calling the Error hook would confuse users: it would mean the Error + // hook got called twice for one request, which is likely to lead to + // duplicated log messages and metrics, no matter how well we document + // the behavior. + // + // Silently ignoring the error is our least-bad option. It's highly + // likely that the connection is broken and the original 'err' says + // so anyway. + _ = writeErr + } + + callResponseSent(ctx, hooks) +} + +// sanitizeBaseURL parses the the baseURL, and adds the "http" scheme if needed. +// If the URL is unparsable, the baseURL is returned unchaged. +func sanitizeBaseURL(baseURL string) string { + u, err := url.Parse(baseURL) + if err != nil { + return baseURL // invalid URL will fail later when making requests + } + if u.Scheme == "" { + u.Scheme = "http" + } + return u.String() +} + +// baseServicePath composes the path prefix for the service (without ). +// e.g.: baseServicePath("/twirp", "my.pkg", "MyService") +// +// returns => "/twirp/my.pkg.MyService/" +// +// e.g.: baseServicePath("", "", "MyService") +// +// returns => "/MyService/" +func baseServicePath(prefix, pkg, service string) string { + fullServiceName := service + if pkg != "" { + fullServiceName = pkg + "." + service + } + return path.Join("/", prefix, fullServiceName) + "/" +} + +// parseTwirpPath extracts path components form a valid Twirp route. +// Expected format: "[]/./" +// e.g.: prefix, pkgService, method := parseTwirpPath("/twirp/pkg.Svc/MakeHat") +func parseTwirpPath(path string) (string, string, string) { + parts := strings.Split(path, "/") + if len(parts) < 2 { + return "", "", "" + } + method := parts[len(parts)-1] + pkgService := parts[len(parts)-2] + prefix := strings.Join(parts[0:len(parts)-2], "/") + return prefix, pkgService, method +} + +// getCustomHTTPReqHeaders retrieves a copy of any headers that are set in +// a context through the twirp.WithHTTPRequestHeaders function. +// If there are no headers set, or if they have the wrong type, nil is returned. +func getCustomHTTPReqHeaders(ctx context.Context) http.Header { + header, ok := twirp.HTTPRequestHeaders(ctx) + if !ok || header == nil { + return nil + } + copied := make(http.Header) + for k, vv := range header { + if vv == nil { + copied[k] = nil + continue + } + copied[k] = make([]string, len(vv)) + copy(copied[k], vv) + } + return copied +} + +// newRequest makes an http.Request from a client, adding common headers. +func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) { + req, err := http.NewRequest("POST", url, reqBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if customHeader := getCustomHTTPReqHeaders(ctx); customHeader != nil { + req.Header = customHeader + } + req.Header.Set("Accept", contentType) + req.Header.Set("Content-Type", contentType) + req.Header.Set("Twirp-Version", "v8.1.0") + return req, nil +} + +// JSON serialization for errors +type twerrJSON struct { + Code string `json:"code"` + Msg string `json:"msg"` + Meta map[string]string `json:"meta,omitempty"` +} + +// marshalErrorToJSON returns JSON from a twirp.Error, that can be used as HTTP error response body. +// If serialization fails, it will use a descriptive Internal error instead. +func marshalErrorToJSON(twerr twirp.Error) []byte { + // make sure that msg is not too large + msg := twerr.Msg() + if len(msg) > 1e6 { + msg = msg[:1e6] + } + + tj := twerrJSON{ + Code: string(twerr.Code()), + Msg: msg, + Meta: twerr.MetaMap(), + } + + buf, err := json.Marshal(&tj) + if err != nil { + buf = []byte("{\"type\": \"" + twirp.Internal + "\", \"msg\": \"There was an error but it could not be serialized into JSON\"}") // fallback + } + + return buf +} + +// errorFromResponse builds a twirp.Error from a non-200 HTTP response. +// If the response has a valid serialized Twirp error, then it's returned. +// If not, the response status code is used to generate a similar twirp +// error. See twirpErrorFromIntermediary for more info on intermediary errors. +func errorFromResponse(resp *http.Response) twirp.Error { + statusCode := resp.StatusCode + statusText := http.StatusText(statusCode) + + if isHTTPRedirect(statusCode) { + // Unexpected redirect: it must be an error from an intermediary. + // Twirp clients don't follow redirects automatically, Twirp only handles + // POST requests, redirects should only happen on GET and HEAD requests. + location := resp.Header.Get("Location") + msg := fmt.Sprintf("unexpected HTTP status code %d %q received, Location=%q", statusCode, statusText, location) + return twirpErrorFromIntermediary(statusCode, msg, location) + } + + respBodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return wrapInternal(err, "failed to read server error response body") + } + + var tj twerrJSON + dec := json.NewDecoder(bytes.NewReader(respBodyBytes)) + dec.DisallowUnknownFields() + if err := dec.Decode(&tj); err != nil || tj.Code == "" { + // Invalid JSON response; it must be an error from an intermediary. + msg := fmt.Sprintf("Error from intermediary with HTTP status code %d %q", statusCode, statusText) + return twirpErrorFromIntermediary(statusCode, msg, string(respBodyBytes)) + } + + errorCode := twirp.ErrorCode(tj.Code) + if !twirp.IsValidErrorCode(errorCode) { + msg := "invalid type returned from server error response: " + tj.Code + return twirp.InternalError(msg).WithMeta("body", string(respBodyBytes)) + } + + twerr := twirp.NewError(errorCode, tj.Msg) + for k, v := range tj.Meta { + twerr = twerr.WithMeta(k, v) + } + return twerr +} + +// twirpErrorFromIntermediary maps HTTP errors from non-twirp sources to twirp errors. +// The mapping is similar to gRPC: https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md. +// Returned twirp Errors have some additional metadata for inspection. +func twirpErrorFromIntermediary(status int, msg string, bodyOrLocation string) twirp.Error { + var code twirp.ErrorCode + if isHTTPRedirect(status) { // 3xx + code = twirp.Internal + } else { + switch status { + case 400: // Bad Request + code = twirp.Internal + case 401: // Unauthorized + code = twirp.Unauthenticated + case 403: // Forbidden + code = twirp.PermissionDenied + case 404: // Not Found + code = twirp.BadRoute + case 429: // Too Many Requests + code = twirp.ResourceExhausted + case 502, 503, 504: // Bad Gateway, Service Unavailable, Gateway Timeout + code = twirp.Unavailable + default: // All other codes + code = twirp.Unknown + } + } + + twerr := twirp.NewError(code, msg) + twerr = twerr.WithMeta("http_error_from_intermediary", "true") // to easily know if this error was from intermediary + twerr = twerr.WithMeta("status_code", strconv.Itoa(status)) + if isHTTPRedirect(status) { + twerr = twerr.WithMeta("location", bodyOrLocation) + } else { + twerr = twerr.WithMeta("body", bodyOrLocation) + } + return twerr +} + +func isHTTPRedirect(status int) bool { + return status >= 300 && status <= 399 +} + +// wrapInternal wraps an error with a prefix as an Internal error. +// The original error cause is accessible by github.com/pkg/errors.Cause. +func wrapInternal(err error, prefix string) twirp.Error { + return twirp.InternalErrorWith(&wrappedError{prefix: prefix, cause: err}) +} + +type wrappedError struct { + prefix string + cause error +} + +func (e *wrappedError) Error() string { return e.prefix + ": " + e.cause.Error() } +func (e *wrappedError) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As +func (e *wrappedError) Cause() error { return e.cause } // for github.com/pkg/errors + +// ensurePanicResponses makes sure that rpc methods causing a panic still result in a Twirp Internal +// error response (status 500), and error hooks are properly called with the panic wrapped as an error. +// The panic is re-raised so it can be handled normally with middleware. +func ensurePanicResponses(ctx context.Context, resp http.ResponseWriter, hooks *twirp.ServerHooks) { + if r := recover(); r != nil { + // Wrap the panic as an error so it can be passed to error hooks. + // The original error is accessible from error hooks, but not visible in the response. + err := errFromPanic(r) + twerr := &internalWithCause{msg: "Internal service panic", cause: err} + // Actually write the error + writeError(ctx, resp, twerr, hooks) + // If possible, flush the error to the wire. + f, ok := resp.(http.Flusher) + if ok { + f.Flush() + } + + panic(r) + } +} + +// errFromPanic returns the typed error if the recovered panic is an error, otherwise formats as error. +func errFromPanic(p interface{}) error { + if err, ok := p.(error); ok { + return err + } + return fmt.Errorf("panic: %v", p) +} + +// internalWithCause is a Twirp Internal error wrapping an original error cause, +// but the original error message is not exposed on Msg(). The original error +// can be checked with go1.13+ errors.Is/As, and also by (github.com/pkg/errors).Unwrap +type internalWithCause struct { + msg string + cause error +} + +func (e *internalWithCause) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As +func (e *internalWithCause) Cause() error { return e.cause } // for github.com/pkg/errors +func (e *internalWithCause) Error() string { return e.msg + ": " + e.cause.Error() } +func (e *internalWithCause) Code() twirp.ErrorCode { return twirp.Internal } +func (e *internalWithCause) Msg() string { return e.msg } +func (e *internalWithCause) Meta(key string) string { return "" } +func (e *internalWithCause) MetaMap() map[string]string { return nil } +func (e *internalWithCause) WithMeta(key string, val string) twirp.Error { return e } + +// malformedRequestError is used when the twirp server cannot unmarshal a request +func malformedRequestError(msg string) twirp.Error { + return twirp.NewError(twirp.Malformed, msg) +} + +// badRouteError is used when the twirp server cannot route a request +func badRouteError(msg string, method, url string) twirp.Error { + err := twirp.NewError(twirp.BadRoute, msg) + err = err.WithMeta("twirp_invalid_route", method+" "+url) + return err +} + +// withoutRedirects makes sure that the POST request can not be redirected. +// The standard library will, by default, redirect requests (including POSTs) if it gets a 302 or +// 303 response, and also 301s in go1.8. It redirects by making a second request, changing the +// method to GET and removing the body. This produces very confusing error messages, so instead we +// set a redirect policy that always errors. This stops Go from executing the redirect. +// +// We have to be a little careful in case the user-provided http.Client has its own CheckRedirect +// policy - if so, we'll run through that policy first. +// +// Because this requires modifying the http.Client, we make a new copy of the client and return it. +func withoutRedirects(in *http.Client) *http.Client { + copy := *in + copy.CheckRedirect = func(req *http.Request, via []*http.Request) error { + if in.CheckRedirect != nil { + // Run the input's redirect if it exists, in case it has side effects, but ignore any error it + // returns, since we want to use ErrUseLastResponse. + err := in.CheckRedirect(req, via) + _ = err // Silly, but this makes sure generated code passes errcheck -blank, which some people use. + } + return http.ErrUseLastResponse + } + return © +} + +// doProtobufRequest makes a Protobuf request to the remote Twirp service. +func doProtobufRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { + reqBodyBytes, err := proto.Marshal(in) + if err != nil { + return ctx, wrapInternal(err, "failed to marshal proto request") + } + reqBody := bytes.NewBuffer(reqBodyBytes) + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + req, err := newRequest(ctx, url, reqBody, "application/protobuf") + if err != nil { + return ctx, wrapInternal(err, "could not build request") + } + ctx, err = callClientRequestPrepared(ctx, hooks, req) + if err != nil { + return ctx, err + } + + req = req.WithContext(ctx) + resp, err := client.Do(req) + if err != nil { + return ctx, wrapInternal(err, "failed to do request") + } + + defer func() { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = wrapInternal(cerr, "failed to close response body") + } + }() + + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + if resp.StatusCode != 200 { + return ctx, errorFromResponse(resp) + } + + respBodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return ctx, wrapInternal(err, "failed to read response body") + } + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + if err = proto.Unmarshal(respBodyBytes, out); err != nil { + return ctx, wrapInternal(err, "failed to unmarshal proto response") + } + return ctx, nil +} + +// doJSONRequest makes a JSON request to the remote Twirp service. +func doJSONRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { + marshaler := &protojson.MarshalOptions{UseProtoNames: true} + reqBytes, err := marshaler.Marshal(in) + if err != nil { + return ctx, wrapInternal(err, "failed to marshal json request") + } + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + req, err := newRequest(ctx, url, bytes.NewReader(reqBytes), "application/json") + if err != nil { + return ctx, wrapInternal(err, "could not build request") + } + ctx, err = callClientRequestPrepared(ctx, hooks, req) + if err != nil { + return ctx, err + } + + req = req.WithContext(ctx) + resp, err := client.Do(req) + if err != nil { + return ctx, wrapInternal(err, "failed to do request") + } + + defer func() { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = wrapInternal(cerr, "failed to close response body") + } + }() + + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + if resp.StatusCode != 200 { + return ctx, errorFromResponse(resp) + } + + d := json.NewDecoder(resp.Body) + rawRespBody := json.RawMessage{} + if err := d.Decode(&rawRespBody); err != nil { + return ctx, wrapInternal(err, "failed to unmarshal json response") + } + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawRespBody, out); err != nil { + return ctx, wrapInternal(err, "failed to unmarshal json response") + } + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + return ctx, nil +} + +// Call twirp.ServerHooks.RequestReceived if the hook is available +func callRequestReceived(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { + if h == nil || h.RequestReceived == nil { + return ctx, nil + } + return h.RequestReceived(ctx) +} + +// Call twirp.ServerHooks.RequestRouted if the hook is available +func callRequestRouted(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { + if h == nil || h.RequestRouted == nil { + return ctx, nil + } + return h.RequestRouted(ctx) +} + +// Call twirp.ServerHooks.ResponsePrepared if the hook is available +func callResponsePrepared(ctx context.Context, h *twirp.ServerHooks) context.Context { + if h == nil || h.ResponsePrepared == nil { + return ctx + } + return h.ResponsePrepared(ctx) +} + +// Call twirp.ServerHooks.ResponseSent if the hook is available +func callResponseSent(ctx context.Context, h *twirp.ServerHooks) { + if h == nil || h.ResponseSent == nil { + return + } + h.ResponseSent(ctx) +} + +// Call twirp.ServerHooks.Error if the hook is available +func callError(ctx context.Context, h *twirp.ServerHooks, err twirp.Error) context.Context { + if h == nil || h.Error == nil { + return ctx + } + return h.Error(ctx, err) +} + +func callClientResponseReceived(ctx context.Context, h *twirp.ClientHooks) { + if h == nil || h.ResponseReceived == nil { + return + } + h.ResponseReceived(ctx) +} + +func callClientRequestPrepared(ctx context.Context, h *twirp.ClientHooks, req *http.Request) (context.Context, error) { + if h == nil || h.RequestPrepared == nil { + return ctx, nil + } + return h.RequestPrepared(ctx, req) +} + +func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) { + if h == nil || h.Error == nil { + return + } + h.Error(ctx, err) +} + +var twirpFileDescriptor0 = []byte{ + // 830 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xdf, 0x8f, 0xdb, 0x44, + 0x10, 0x56, 0x92, 0xbb, 0x4b, 0x32, 0xf9, 0x71, 0xc7, 0x52, 0x5a, 0x37, 0x54, 0x6d, 0x64, 0x40, + 0x0a, 0x0f, 0xd8, 0xe2, 0xa0, 0x12, 0x12, 0x02, 0x71, 0xbd, 0x02, 0x8a, 0xd4, 0x8a, 0x63, 0x8b, + 0x90, 0xe0, 0x25, 0x38, 0xeb, 0x75, 0xb2, 0x3a, 0xdb, 0xeb, 0xdb, 0x59, 0x07, 0xf2, 0xcc, 0x0b, + 0xff, 0x13, 0xff, 0x1c, 0xda, 0xb5, 0x9d, 0xc4, 0x49, 0x7a, 0xa2, 0x2f, 0x91, 0x77, 0xe6, 0x9b, + 0x6f, 0x67, 0xbe, 0xf9, 0xec, 0xc0, 0x23, 0x95, 0x31, 0x9f, 0x05, 0x6c, 0xc9, 0x7d, 0xe4, 0x6a, + 0x25, 0x18, 0xf7, 0x32, 0x25, 0xb5, 0x24, 0x43, 0xad, 0xc4, 0x6a, 0xed, 0xd9, 0x94, 0xb7, 0xfa, + 0x7c, 0xf4, 0x6c, 0x21, 0xe5, 0x22, 0xe6, 0xbe, 0xcd, 0xce, 0xf3, 0xc8, 0xd7, 0x22, 0xe1, 0xa8, + 0x83, 0x24, 0x2b, 0x0a, 0x46, 0x8e, 0x65, 0x92, 0x49, 0x22, 0xd3, 0x3a, 0xd5, 0xe8, 0xc3, 0xfd, + 0x52, 0x9e, 0x64, 0x7a, 0x5d, 0x24, 0xdd, 0x7f, 0x9a, 0xd0, 0xbf, 0x52, 0x5a, 0x44, 0x01, 0xd3, + 0xd3, 0x34, 0x92, 0xe4, 0x13, 0x18, 0x22, 0x5b, 0xf2, 0x24, 0x98, 0xad, 0xb8, 0x42, 0x21, 0x53, + 0xa7, 0x31, 0x6e, 0x4c, 0x4e, 0xe9, 0xa0, 0x88, 0xfe, 0x5a, 0x04, 0x89, 0x0b, 0xfd, 0x40, 0xb1, + 0xa5, 0xd0, 0x9c, 0xe9, 0x5c, 0x71, 0xa7, 0x39, 0x6e, 0x4c, 0xba, 0xb4, 0x16, 0x23, 0x5f, 0x42, + 0x9b, 0x29, 0x1e, 0x68, 0x1e, 0x3a, 0xad, 0x71, 0x63, 0xd2, 0xbb, 0x1c, 0x79, 0x45, 0x2b, 0x5e, + 0xd5, 0x8a, 0xf7, 0x4b, 0x35, 0x05, 0xad, 0xa0, 0xa6, 0x81, 0x50, 0xb2, 0x5b, 0xae, 0x36, 0x0d, + 0x9c, 0x58, 0xee, 0x41, 0x11, 0xad, 0x1a, 0x18, 0x42, 0x53, 0xa2, 0x73, 0x6a, 0x53, 0x4d, 0x89, + 0xe4, 0x3b, 0xb8, 0x58, 0x0a, 0xd4, 0x52, 0xad, 0x67, 0x59, 0xc0, 0x6e, 0x83, 0x05, 0x47, 0xe7, + 0x6c, 0xdc, 0x9a, 0xf4, 0x2e, 0x3f, 0xf0, 0x4a, 0x2d, 0xad, 0x38, 0xde, 0x4d, 0x91, 0xa5, 0xe7, + 0x25, 0xbc, 0x3c, 0xa3, 0xfb, 0x17, 0x90, 0x9b, 0x5c, 0x57, 0x62, 0x50, 0x7e, 0x97, 0x73, 0xd4, + 0xe4, 0x19, 0xf4, 0x82, 0x32, 0x34, 0x13, 0xa1, 0x15, 0xa3, 0x4b, 0xa1, 0x0a, 0x4d, 0x43, 0x72, + 0x05, 0x83, 0x2d, 0x20, 0x8d, 0xa4, 0x95, 0xa2, 0x77, 0xf9, 0xc4, 0xab, 0x6f, 0xd0, 0xdb, 0x55, + 0xd9, 0x08, 0xb5, 0x3d, 0xb9, 0x7f, 0x9f, 0x42, 0xe7, 0x45, 0x2c, 0xe7, 0xef, 0xb2, 0x80, 0xb1, + 0x9d, 0xbf, 0xb8, 0xeb, 0xa2, 0x3e, 0xe1, 0x4f, 0x6f, 0xac, 0x22, 0x5f, 0x01, 0x28, 0x9e, 0x49, + 0x14, 0x66, 0x4a, 0xa7, 0x67, 0x91, 0x4e, 0x1d, 0x49, 0x37, 0x79, 0xba, 0x83, 0x25, 0xdf, 0xc2, + 0xa0, 0xd4, 0xd0, 0x4e, 0x84, 0x4e, 0xcb, 0x0a, 0xf9, 0xf8, 0xa8, 0x90, 0xc5, 0x3c, 0xd9, 0xf6, + 0x80, 0xe4, 0x1b, 0xe8, 0x07, 0x59, 0x16, 0x0b, 0x16, 0x68, 0x21, 0x53, 0x74, 0x4e, 0x8e, 0x95, + 0x5f, 0x6d, 0x11, 0xb4, 0x06, 0x27, 0xaf, 0xe0, 0xbd, 0x44, 0x20, 0x93, 0x69, 0x24, 0x16, 0xb9, + 0x2a, 0x39, 0xba, 0x96, 0xe3, 0x69, 0x9d, 0xe3, 0xf5, 0x1e, 0x8c, 0x1e, 0x16, 0x9a, 0x05, 0xca, + 0x2c, 0xb8, 0xcb, 0xf9, 0x2c, 0x14, 0xca, 0x38, 0xa6, 0x65, 0x16, 0x58, 0x84, 0x5e, 0x0a, 0x85, + 0x46, 0xf0, 0x3f, 0x8d, 0x69, 0x65, 0xae, 0x67, 0x91, 0x88, 0x4b, 0xdf, 0x74, 0xe9, 0xa0, 0x8a, + 0xfe, 0x60, 0x82, 0xe4, 0x21, 0x9c, 0x85, 0x62, 0xc1, 0x51, 0x3b, 0x6d, 0xeb, 0x81, 0xf2, 0x44, + 0x1e, 0x41, 0x3b, 0x14, 0x51, 0x64, 0xcc, 0xd1, 0xa9, 0x12, 0x51, 0x34, 0x0d, 0xc9, 0x8f, 0x70, + 0xc1, 0x72, 0xd4, 0x32, 0x99, 0x29, 0x8e, 0x32, 0x57, 0x8c, 0xa3, 0x03, 0x76, 0x8a, 0x27, 0xf5, + 0x29, 0xae, 0x2d, 0x8a, 0x96, 0x20, 0x7a, 0xce, 0x6a, 0x67, 0x24, 0x1e, 0xb4, 0x91, 0x33, 0xc5, + 0x35, 0x3a, 0x7d, 0x5b, 0xff, 0xa0, 0x5e, 0xff, 0xc6, 0x26, 0x69, 0x05, 0x22, 0xcf, 0xa1, 0x13, + 0x0b, 0xc6, 0x53, 0xe4, 0xe8, 0x0c, 0x8e, 0x49, 0xff, 0xaa, 0xc8, 0x9a, 0xb9, 0xe8, 0x06, 0xea, + 0xfe, 0x01, 0xc3, 0x9b, 0x5c, 0x1b, 0x1f, 0x56, 0xde, 0xdf, 0x19, 0xad, 0x51, 0x1b, 0xed, 0x39, + 0x74, 0xe7, 0xb1, 0x9c, 0x17, 0x7e, 0x6f, 0xd5, 0x9d, 0x55, 0xf9, 0xbd, 0x32, 0x34, 0xed, 0xcc, + 0xcb, 0x27, 0xf7, 0x1a, 0x7a, 0x37, 0xb9, 0xa6, 0x1c, 0x33, 0x99, 0x22, 0x2f, 0x2d, 0xdc, 0xb8, + 0xc7, 0xc2, 0x04, 0x4e, 0xb8, 0xc4, 0xd8, 0xda, 0xbc, 0x43, 0xed, 0xb3, 0xfb, 0x33, 0xbc, 0xff, + 0x5a, 0x20, 0x8a, 0x74, 0x61, 0x6e, 0xc0, 0xff, 0xfd, 0x9e, 0x3e, 0x86, 0x4e, 0xd1, 0x73, 0x68, + 0x5e, 0x1b, 0xb3, 0xe0, 0xb6, 0x6d, 0x2c, 0x44, 0xf7, 0x16, 0x1e, 0xd4, 0x29, 0xcb, 0x06, 0x3f, + 0x85, 0x8b, 0xa4, 0x88, 0xcf, 0x2a, 0x22, 0x4b, 0xdc, 0xa1, 0xe7, 0x65, 0xbc, 0x7a, 0xa9, 0xc9, + 0x64, 0x0b, 0xdd, 0xbb, 0x65, 0x98, 0x6c, 0xa9, 0xcd, 0x65, 0x3e, 0x90, 0x97, 0x3c, 0xe6, 0x9a, + 0xd7, 0xda, 0xdf, 0xed, 0xae, 0x51, 0xeb, 0xee, 0xf2, 0xdf, 0x26, 0x9c, 0x5e, 0x1b, 0x55, 0xc9, + 0xd4, 0xea, 0xb7, 0xb9, 0xd3, 0xdd, 0x97, 0xfc, 0xf0, 0xf3, 0x35, 0x7a, 0x78, 0xf0, 0xc9, 0xfd, + 0xde, 0x7c, 0xfd, 0xc9, 0x15, 0xb4, 0xcb, 0x65, 0x93, 0xa7, 0x47, 0x68, 0x76, 0x5c, 0xf0, 0x56, + 0x8a, 0xdf, 0xa0, 0xbf, 0xab, 0x1a, 0xf9, 0x68, 0x9f, 0xe7, 0xc8, 0x9a, 0x46, 0x1f, 0xdf, 0x0f, + 0x2a, 0x85, 0x9f, 0x42, 0x6f, 0x47, 0xa3, 0xc3, 0x41, 0x0f, 0x05, 0x7c, 0x5b, 0x97, 0x2f, 0xfc, + 0xdf, 0x3f, 0x5b, 0x08, 0xbd, 0xcc, 0xe7, 0xc6, 0x5a, 0x7e, 0x70, 0x97, 0x07, 0xc8, 0x59, 0xae, + 0x84, 0x5e, 0xfb, 0x96, 0xd4, 0xdf, 0xfc, 0x03, 0x7f, 0x6d, 0x7f, 0xe7, 0x67, 0x96, 0xe0, 0x8b, + 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x82, 0x05, 0x5f, 0x23, 0x9b, 0x07, 0x00, 0x00, +} diff --git a/rpc/common/service.pb.go b/rpc/common/service.pb.go new file mode 100644 index 000000000000..0174ab2c7f5f --- /dev/null +++ b/rpc/common/service.pb.go @@ -0,0 +1,3212 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.4 +// source: rpc/common/service.proto + +package common + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Severity int32 + +const ( + Severity_UNKNOWN Severity = 0 + Severity_LOW Severity = 1 + Severity_MEDIUM Severity = 2 + Severity_HIGH Severity = 3 + Severity_CRITICAL Severity = 4 +) + +// Enum value maps for Severity. +var ( + Severity_name = map[int32]string{ + 0: "UNKNOWN", + 1: "LOW", + 2: "MEDIUM", + 3: "HIGH", + 4: "CRITICAL", + } + Severity_value = map[string]int32{ + "UNKNOWN": 0, + "LOW": 1, + "MEDIUM": 2, + "HIGH": 3, + "CRITICAL": 4, + } +) + +func (x Severity) Enum() *Severity { + p := new(Severity) + *p = x + return p +} + +func (x Severity) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Severity) Descriptor() protoreflect.EnumDescriptor { + return file_rpc_common_service_proto_enumTypes[0].Descriptor() +} + +func (Severity) Type() protoreflect.EnumType { + return &file_rpc_common_service_proto_enumTypes[0] +} + +func (x Severity) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Severity.Descriptor instead. +func (Severity) EnumDescriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{0} +} + +type LicenseCategory_Enum int32 + +const ( + LicenseCategory_UNSPECIFIED LicenseCategory_Enum = 0 + LicenseCategory_FORBIDDEN LicenseCategory_Enum = 1 + LicenseCategory_RESTRICTED LicenseCategory_Enum = 2 + LicenseCategory_RECIPROCAL LicenseCategory_Enum = 3 + LicenseCategory_NOTICE LicenseCategory_Enum = 4 + LicenseCategory_PERMISSIVE LicenseCategory_Enum = 5 + LicenseCategory_UNENCUMBERED LicenseCategory_Enum = 6 + LicenseCategory_UNKNOWN LicenseCategory_Enum = 7 +) + +// Enum value maps for LicenseCategory_Enum. +var ( + LicenseCategory_Enum_name = map[int32]string{ + 0: "UNSPECIFIED", + 1: "FORBIDDEN", + 2: "RESTRICTED", + 3: "RECIPROCAL", + 4: "NOTICE", + 5: "PERMISSIVE", + 6: "UNENCUMBERED", + 7: "UNKNOWN", + } + LicenseCategory_Enum_value = map[string]int32{ + "UNSPECIFIED": 0, + "FORBIDDEN": 1, + "RESTRICTED": 2, + "RECIPROCAL": 3, + "NOTICE": 4, + "PERMISSIVE": 5, + "UNENCUMBERED": 6, + "UNKNOWN": 7, + } +) + +func (x LicenseCategory_Enum) Enum() *LicenseCategory_Enum { + p := new(LicenseCategory_Enum) + *p = x + return p +} + +func (x LicenseCategory_Enum) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LicenseCategory_Enum) Descriptor() protoreflect.EnumDescriptor { + return file_rpc_common_service_proto_enumTypes[1].Descriptor() +} + +func (LicenseCategory_Enum) Type() protoreflect.EnumType { + return &file_rpc_common_service_proto_enumTypes[1] +} + +func (x LicenseCategory_Enum) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use LicenseCategory_Enum.Descriptor instead. +func (LicenseCategory_Enum) EnumDescriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{24, 0} +} + +type LicenseType_Enum int32 + +const ( + LicenseType_UNSPECIFIED LicenseType_Enum = 0 + LicenseType_DPKG LicenseType_Enum = 1 + LicenseType_HEADER LicenseType_Enum = 2 + LicenseType_LICENSE_FILE LicenseType_Enum = 3 +) + +// Enum value maps for LicenseType_Enum. +var ( + LicenseType_Enum_name = map[int32]string{ + 0: "UNSPECIFIED", + 1: "DPKG", + 2: "HEADER", + 3: "LICENSE_FILE", + } + LicenseType_Enum_value = map[string]int32{ + "UNSPECIFIED": 0, + "DPKG": 1, + "HEADER": 2, + "LICENSE_FILE": 3, + } +) + +func (x LicenseType_Enum) Enum() *LicenseType_Enum { + p := new(LicenseType_Enum) + *p = x + return p +} + +func (x LicenseType_Enum) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LicenseType_Enum) Descriptor() protoreflect.EnumDescriptor { + return file_rpc_common_service_proto_enumTypes[2].Descriptor() +} + +func (LicenseType_Enum) Type() protoreflect.EnumType { + return &file_rpc_common_service_proto_enumTypes[2] +} + +func (x LicenseType_Enum) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use LicenseType_Enum.Descriptor instead. +func (LicenseType_Enum) EnumDescriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{25, 0} +} + +type OS struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Family string `protobuf:"bytes,1,opt,name=family,proto3" json:"family,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Eosl bool `protobuf:"varint,3,opt,name=eosl,proto3" json:"eosl,omitempty"` + Extended bool `protobuf:"varint,4,opt,name=extended,proto3" json:"extended,omitempty"` +} + +func (x *OS) Reset() { + *x = OS{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OS) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OS) ProtoMessage() {} + +func (x *OS) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OS.ProtoReflect.Descriptor instead. +func (*OS) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{0} +} + +func (x *OS) GetFamily() string { + if x != nil { + return x.Family + } + return "" +} + +func (x *OS) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *OS) GetEosl() bool { + if x != nil { + return x.Eosl + } + return false +} + +func (x *OS) GetExtended() bool { + if x != nil { + return x.Extended + } + return false +} + +type Repository struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Family string `protobuf:"bytes,1,opt,name=family,proto3" json:"family,omitempty"` + Release string `protobuf:"bytes,2,opt,name=release,proto3" json:"release,omitempty"` +} + +func (x *Repository) Reset() { + *x = Repository{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Repository) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Repository) ProtoMessage() {} + +func (x *Repository) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Repository.ProtoReflect.Descriptor instead. +func (*Repository) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{1} +} + +func (x *Repository) GetFamily() string { + if x != nil { + return x.Family + } + return "" +} + +func (x *Repository) GetRelease() string { + if x != nil { + return x.Release + } + return "" +} + +type PackageInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FilePath string `protobuf:"bytes,1,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + Packages []*Package `protobuf:"bytes,2,rep,name=packages,proto3" json:"packages,omitempty"` +} + +func (x *PackageInfo) Reset() { + *x = PackageInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PackageInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PackageInfo) ProtoMessage() {} + +func (x *PackageInfo) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PackageInfo.ProtoReflect.Descriptor instead. +func (*PackageInfo) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{2} +} + +func (x *PackageInfo) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + +func (x *PackageInfo) GetPackages() []*Package { + if x != nil { + return x.Packages + } + return nil +} + +type Application struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + FilePath string `protobuf:"bytes,2,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + Libraries []*Package `protobuf:"bytes,3,rep,name=libraries,proto3" json:"libraries,omitempty"` +} + +func (x *Application) Reset() { + *x = Application{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Application) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Application) ProtoMessage() {} + +func (x *Application) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Application.ProtoReflect.Descriptor instead. +func (*Application) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{3} +} + +func (x *Application) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Application) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + +func (x *Application) GetLibraries() []*Package { + if x != nil { + return x.Libraries + } + return nil +} + +type Package struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // binary package + // e.g. bind-utils + Id string `protobuf:"bytes,13,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + Release string `protobuf:"bytes,3,opt,name=release,proto3" json:"release,omitempty"` + Epoch int32 `protobuf:"varint,4,opt,name=epoch,proto3" json:"epoch,omitempty"` + Identifier *PkgIdentifier `protobuf:"bytes,19,opt,name=identifier,proto3" json:"identifier,omitempty"` + Arch string `protobuf:"bytes,5,opt,name=arch,proto3" json:"arch,omitempty"` + // src package containing some binary packages + // e.g. bind + SrcName string `protobuf:"bytes,6,opt,name=src_name,json=srcName,proto3" json:"src_name,omitempty"` + SrcVersion string `protobuf:"bytes,7,opt,name=src_version,json=srcVersion,proto3" json:"src_version,omitempty"` + SrcRelease string `protobuf:"bytes,8,opt,name=src_release,json=srcRelease,proto3" json:"src_release,omitempty"` + SrcEpoch int32 `protobuf:"varint,9,opt,name=src_epoch,json=srcEpoch,proto3" json:"src_epoch,omitempty"` + Licenses []string `protobuf:"bytes,15,rep,name=licenses,proto3" json:"licenses,omitempty"` + Locations []*Location `protobuf:"bytes,20,rep,name=locations,proto3" json:"locations,omitempty"` + Layer *Layer `protobuf:"bytes,11,opt,name=layer,proto3" json:"layer,omitempty"` + FilePath string `protobuf:"bytes,12,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + DependsOn []string `protobuf:"bytes,14,rep,name=depends_on,json=dependsOn,proto3" json:"depends_on,omitempty"` + Digest string `protobuf:"bytes,16,opt,name=digest,proto3" json:"digest,omitempty"` + Dev bool `protobuf:"varint,17,opt,name=dev,proto3" json:"dev,omitempty"` + Indirect bool `protobuf:"varint,18,opt,name=indirect,proto3" json:"indirect,omitempty"` +} + +func (x *Package) Reset() { + *x = Package{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Package) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Package) ProtoMessage() {} + +func (x *Package) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Package.ProtoReflect.Descriptor instead. +func (*Package) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{4} +} + +func (x *Package) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Package) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Package) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *Package) GetRelease() string { + if x != nil { + return x.Release + } + return "" +} + +func (x *Package) GetEpoch() int32 { + if x != nil { + return x.Epoch + } + return 0 +} + +func (x *Package) GetIdentifier() *PkgIdentifier { + if x != nil { + return x.Identifier + } + return nil +} + +func (x *Package) GetArch() string { + if x != nil { + return x.Arch + } + return "" +} + +func (x *Package) GetSrcName() string { + if x != nil { + return x.SrcName + } + return "" +} + +func (x *Package) GetSrcVersion() string { + if x != nil { + return x.SrcVersion + } + return "" +} + +func (x *Package) GetSrcRelease() string { + if x != nil { + return x.SrcRelease + } + return "" +} + +func (x *Package) GetSrcEpoch() int32 { + if x != nil { + return x.SrcEpoch + } + return 0 +} + +func (x *Package) GetLicenses() []string { + if x != nil { + return x.Licenses + } + return nil +} + +func (x *Package) GetLocations() []*Location { + if x != nil { + return x.Locations + } + return nil +} + +func (x *Package) GetLayer() *Layer { + if x != nil { + return x.Layer + } + return nil +} + +func (x *Package) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + +func (x *Package) GetDependsOn() []string { + if x != nil { + return x.DependsOn + } + return nil +} + +func (x *Package) GetDigest() string { + if x != nil { + return x.Digest + } + return "" +} + +func (x *Package) GetDev() bool { + if x != nil { + return x.Dev + } + return false +} + +func (x *Package) GetIndirect() bool { + if x != nil { + return x.Indirect + } + return false +} + +type PkgIdentifier struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Purl string `protobuf:"bytes,1,opt,name=purl,proto3" json:"purl,omitempty"` + BomRef string `protobuf:"bytes,2,opt,name=bom_ref,json=bomRef,proto3" json:"bom_ref,omitempty"` +} + +func (x *PkgIdentifier) Reset() { + *x = PkgIdentifier{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PkgIdentifier) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PkgIdentifier) ProtoMessage() {} + +func (x *PkgIdentifier) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PkgIdentifier.ProtoReflect.Descriptor instead. +func (*PkgIdentifier) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{5} +} + +func (x *PkgIdentifier) GetPurl() string { + if x != nil { + return x.Purl + } + return "" +} + +func (x *PkgIdentifier) GetBomRef() string { + if x != nil { + return x.BomRef + } + return "" +} + +type Location struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StartLine int32 `protobuf:"varint,1,opt,name=start_line,json=startLine,proto3" json:"start_line,omitempty"` + EndLine int32 `protobuf:"varint,2,opt,name=end_line,json=endLine,proto3" json:"end_line,omitempty"` +} + +func (x *Location) Reset() { + *x = Location{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Location) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Location) ProtoMessage() {} + +func (x *Location) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Location.ProtoReflect.Descriptor instead. +func (*Location) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{6} +} + +func (x *Location) GetStartLine() int32 { + if x != nil { + return x.StartLine + } + return 0 +} + +func (x *Location) GetEndLine() int32 { + if x != nil { + return x.EndLine + } + return 0 +} + +type Misconfiguration struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FileType string `protobuf:"bytes,1,opt,name=file_type,json=fileType,proto3" json:"file_type,omitempty"` + FilePath string `protobuf:"bytes,2,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + Successes []*MisconfResult `protobuf:"bytes,3,rep,name=successes,proto3" json:"successes,omitempty"` + Warnings []*MisconfResult `protobuf:"bytes,4,rep,name=warnings,proto3" json:"warnings,omitempty"` + Failures []*MisconfResult `protobuf:"bytes,5,rep,name=failures,proto3" json:"failures,omitempty"` + Exceptions []*MisconfResult `protobuf:"bytes,6,rep,name=exceptions,proto3" json:"exceptions,omitempty"` +} + +func (x *Misconfiguration) Reset() { + *x = Misconfiguration{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Misconfiguration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Misconfiguration) ProtoMessage() {} + +func (x *Misconfiguration) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Misconfiguration.ProtoReflect.Descriptor instead. +func (*Misconfiguration) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{7} +} + +func (x *Misconfiguration) GetFileType() string { + if x != nil { + return x.FileType + } + return "" +} + +func (x *Misconfiguration) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + +func (x *Misconfiguration) GetSuccesses() []*MisconfResult { + if x != nil { + return x.Successes + } + return nil +} + +func (x *Misconfiguration) GetWarnings() []*MisconfResult { + if x != nil { + return x.Warnings + } + return nil +} + +func (x *Misconfiguration) GetFailures() []*MisconfResult { + if x != nil { + return x.Failures + } + return nil +} + +func (x *Misconfiguration) GetExceptions() []*MisconfResult { + if x != nil { + return x.Exceptions + } + return nil +} + +type MisconfResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + PolicyMetadata *PolicyMetadata `protobuf:"bytes,7,opt,name=policy_metadata,json=policyMetadata,proto3" json:"policy_metadata,omitempty"` + CauseMetadata *CauseMetadata `protobuf:"bytes,8,opt,name=cause_metadata,json=causeMetadata,proto3" json:"cause_metadata,omitempty"` +} + +func (x *MisconfResult) Reset() { + *x = MisconfResult{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MisconfResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MisconfResult) ProtoMessage() {} + +func (x *MisconfResult) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MisconfResult.ProtoReflect.Descriptor instead. +func (*MisconfResult) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{8} +} + +func (x *MisconfResult) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *MisconfResult) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *MisconfResult) GetPolicyMetadata() *PolicyMetadata { + if x != nil { + return x.PolicyMetadata + } + return nil +} + +func (x *MisconfResult) GetCauseMetadata() *CauseMetadata { + if x != nil { + return x.CauseMetadata + } + return nil +} + +type PolicyMetadata struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + AdvId string `protobuf:"bytes,2,opt,name=adv_id,json=advId,proto3" json:"adv_id,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"` + Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"` + Severity string `protobuf:"bytes,6,opt,name=severity,proto3" json:"severity,omitempty"` + RecommendedActions string `protobuf:"bytes,7,opt,name=recommended_actions,json=recommendedActions,proto3" json:"recommended_actions,omitempty"` + References []string `protobuf:"bytes,8,rep,name=references,proto3" json:"references,omitempty"` +} + +func (x *PolicyMetadata) Reset() { + *x = PolicyMetadata{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PolicyMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyMetadata) ProtoMessage() {} + +func (x *PolicyMetadata) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyMetadata.ProtoReflect.Descriptor instead. +func (*PolicyMetadata) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{9} +} + +func (x *PolicyMetadata) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *PolicyMetadata) GetAdvId() string { + if x != nil { + return x.AdvId + } + return "" +} + +func (x *PolicyMetadata) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *PolicyMetadata) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *PolicyMetadata) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *PolicyMetadata) GetSeverity() string { + if x != nil { + return x.Severity + } + return "" +} + +func (x *PolicyMetadata) GetRecommendedActions() string { + if x != nil { + return x.RecommendedActions + } + return "" +} + +func (x *PolicyMetadata) GetReferences() []string { + if x != nil { + return x.References + } + return nil +} + +type DetectedMisconfiguration struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"` + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + Namespace string `protobuf:"bytes,6,opt,name=namespace,proto3" json:"namespace,omitempty"` + Resolution string `protobuf:"bytes,7,opt,name=resolution,proto3" json:"resolution,omitempty"` + Severity Severity `protobuf:"varint,8,opt,name=severity,proto3,enum=trivy.common.Severity" json:"severity,omitempty"` + PrimaryUrl string `protobuf:"bytes,9,opt,name=primary_url,json=primaryUrl,proto3" json:"primary_url,omitempty"` + References []string `protobuf:"bytes,10,rep,name=references,proto3" json:"references,omitempty"` + Status string `protobuf:"bytes,11,opt,name=status,proto3" json:"status,omitempty"` + Layer *Layer `protobuf:"bytes,12,opt,name=layer,proto3" json:"layer,omitempty"` + CauseMetadata *CauseMetadata `protobuf:"bytes,13,opt,name=cause_metadata,json=causeMetadata,proto3" json:"cause_metadata,omitempty"` + AvdId string `protobuf:"bytes,14,opt,name=avd_id,json=avdId,proto3" json:"avd_id,omitempty"` + Query string `protobuf:"bytes,15,opt,name=query,proto3" json:"query,omitempty"` +} + +func (x *DetectedMisconfiguration) Reset() { + *x = DetectedMisconfiguration{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DetectedMisconfiguration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DetectedMisconfiguration) ProtoMessage() {} + +func (x *DetectedMisconfiguration) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DetectedMisconfiguration.ProtoReflect.Descriptor instead. +func (*DetectedMisconfiguration) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{10} +} + +func (x *DetectedMisconfiguration) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *DetectedMisconfiguration) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *DetectedMisconfiguration) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *DetectedMisconfiguration) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *DetectedMisconfiguration) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *DetectedMisconfiguration) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *DetectedMisconfiguration) GetResolution() string { + if x != nil { + return x.Resolution + } + return "" +} + +func (x *DetectedMisconfiguration) GetSeverity() Severity { + if x != nil { + return x.Severity + } + return Severity_UNKNOWN +} + +func (x *DetectedMisconfiguration) GetPrimaryUrl() string { + if x != nil { + return x.PrimaryUrl + } + return "" +} + +func (x *DetectedMisconfiguration) GetReferences() []string { + if x != nil { + return x.References + } + return nil +} + +func (x *DetectedMisconfiguration) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *DetectedMisconfiguration) GetLayer() *Layer { + if x != nil { + return x.Layer + } + return nil +} + +func (x *DetectedMisconfiguration) GetCauseMetadata() *CauseMetadata { + if x != nil { + return x.CauseMetadata + } + return nil +} + +func (x *DetectedMisconfiguration) GetAvdId() string { + if x != nil { + return x.AvdId + } + return "" +} + +func (x *DetectedMisconfiguration) GetQuery() string { + if x != nil { + return x.Query + } + return "" +} + +type Vulnerability struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + VulnerabilityId string `protobuf:"bytes,1,opt,name=vulnerability_id,json=vulnerabilityId,proto3" json:"vulnerability_id,omitempty"` + PkgName string `protobuf:"bytes,2,opt,name=pkg_name,json=pkgName,proto3" json:"pkg_name,omitempty"` + InstalledVersion string `protobuf:"bytes,3,opt,name=installed_version,json=installedVersion,proto3" json:"installed_version,omitempty"` + FixedVersion string `protobuf:"bytes,4,opt,name=fixed_version,json=fixedVersion,proto3" json:"fixed_version,omitempty"` + Title string `protobuf:"bytes,5,opt,name=title,proto3" json:"title,omitempty"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` + Severity Severity `protobuf:"varint,7,opt,name=severity,proto3,enum=trivy.common.Severity" json:"severity,omitempty"` + References []string `protobuf:"bytes,8,rep,name=references,proto3" json:"references,omitempty"` + PkgIdentifier *PkgIdentifier `protobuf:"bytes,25,opt,name=pkg_identifier,json=pkgIdentifier,proto3" json:"pkg_identifier,omitempty"` + Layer *Layer `protobuf:"bytes,10,opt,name=layer,proto3" json:"layer,omitempty"` + SeveritySource string `protobuf:"bytes,11,opt,name=severity_source,json=severitySource,proto3" json:"severity_source,omitempty"` + Cvss map[string]*CVSS `protobuf:"bytes,12,rep,name=cvss,proto3" json:"cvss,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + CweIds []string `protobuf:"bytes,13,rep,name=cwe_ids,json=cweIds,proto3" json:"cwe_ids,omitempty"` + PrimaryUrl string `protobuf:"bytes,14,opt,name=primary_url,json=primaryUrl,proto3" json:"primary_url,omitempty"` + PublishedDate *timestamppb.Timestamp `protobuf:"bytes,15,opt,name=published_date,json=publishedDate,proto3" json:"published_date,omitempty"` + LastModifiedDate *timestamppb.Timestamp `protobuf:"bytes,16,opt,name=last_modified_date,json=lastModifiedDate,proto3" json:"last_modified_date,omitempty"` + CustomAdvisoryData *structpb.Value `protobuf:"bytes,17,opt,name=custom_advisory_data,json=customAdvisoryData,proto3" json:"custom_advisory_data,omitempty"` + CustomVulnData *structpb.Value `protobuf:"bytes,18,opt,name=custom_vuln_data,json=customVulnData,proto3" json:"custom_vuln_data,omitempty"` + VendorIds []string `protobuf:"bytes,19,rep,name=vendor_ids,json=vendorIds,proto3" json:"vendor_ids,omitempty"` + DataSource *DataSource `protobuf:"bytes,20,opt,name=data_source,json=dataSource,proto3" json:"data_source,omitempty"` + VendorSeverity map[string]Severity `protobuf:"bytes,21,rep,name=vendor_severity,json=vendorSeverity,proto3" json:"vendor_severity,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3,enum=trivy.common.Severity"` + PkgPath string `protobuf:"bytes,22,opt,name=pkg_path,json=pkgPath,proto3" json:"pkg_path,omitempty"` + PkgId string `protobuf:"bytes,23,opt,name=pkg_id,json=pkgId,proto3" json:"pkg_id,omitempty"` + Status int32 `protobuf:"varint,24,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *Vulnerability) Reset() { + *x = Vulnerability{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Vulnerability) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Vulnerability) ProtoMessage() {} + +func (x *Vulnerability) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Vulnerability.ProtoReflect.Descriptor instead. +func (*Vulnerability) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{11} +} + +func (x *Vulnerability) GetVulnerabilityId() string { + if x != nil { + return x.VulnerabilityId + } + return "" +} + +func (x *Vulnerability) GetPkgName() string { + if x != nil { + return x.PkgName + } + return "" +} + +func (x *Vulnerability) GetInstalledVersion() string { + if x != nil { + return x.InstalledVersion + } + return "" +} + +func (x *Vulnerability) GetFixedVersion() string { + if x != nil { + return x.FixedVersion + } + return "" +} + +func (x *Vulnerability) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Vulnerability) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Vulnerability) GetSeverity() Severity { + if x != nil { + return x.Severity + } + return Severity_UNKNOWN +} + +func (x *Vulnerability) GetReferences() []string { + if x != nil { + return x.References + } + return nil +} + +func (x *Vulnerability) GetPkgIdentifier() *PkgIdentifier { + if x != nil { + return x.PkgIdentifier + } + return nil +} + +func (x *Vulnerability) GetLayer() *Layer { + if x != nil { + return x.Layer + } + return nil +} + +func (x *Vulnerability) GetSeveritySource() string { + if x != nil { + return x.SeveritySource + } + return "" +} + +func (x *Vulnerability) GetCvss() map[string]*CVSS { + if x != nil { + return x.Cvss + } + return nil +} + +func (x *Vulnerability) GetCweIds() []string { + if x != nil { + return x.CweIds + } + return nil +} + +func (x *Vulnerability) GetPrimaryUrl() string { + if x != nil { + return x.PrimaryUrl + } + return "" +} + +func (x *Vulnerability) GetPublishedDate() *timestamppb.Timestamp { + if x != nil { + return x.PublishedDate + } + return nil +} + +func (x *Vulnerability) GetLastModifiedDate() *timestamppb.Timestamp { + if x != nil { + return x.LastModifiedDate + } + return nil +} + +func (x *Vulnerability) GetCustomAdvisoryData() *structpb.Value { + if x != nil { + return x.CustomAdvisoryData + } + return nil +} + +func (x *Vulnerability) GetCustomVulnData() *structpb.Value { + if x != nil { + return x.CustomVulnData + } + return nil +} + +func (x *Vulnerability) GetVendorIds() []string { + if x != nil { + return x.VendorIds + } + return nil +} + +func (x *Vulnerability) GetDataSource() *DataSource { + if x != nil { + return x.DataSource + } + return nil +} + +func (x *Vulnerability) GetVendorSeverity() map[string]Severity { + if x != nil { + return x.VendorSeverity + } + return nil +} + +func (x *Vulnerability) GetPkgPath() string { + if x != nil { + return x.PkgPath + } + return "" +} + +func (x *Vulnerability) GetPkgId() string { + if x != nil { + return x.PkgId + } + return "" +} + +func (x *Vulnerability) GetStatus() int32 { + if x != nil { + return x.Status + } + return 0 +} + +type DataSource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *DataSource) Reset() { + *x = DataSource{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DataSource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataSource) ProtoMessage() {} + +func (x *DataSource) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DataSource.ProtoReflect.Descriptor instead. +func (*DataSource) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{12} +} + +func (x *DataSource) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *DataSource) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DataSource) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type Layer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Digest string `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"` + DiffId string `protobuf:"bytes,2,opt,name=diff_id,json=diffId,proto3" json:"diff_id,omitempty"` + CreatedBy string `protobuf:"bytes,3,opt,name=created_by,json=createdBy,proto3" json:"created_by,omitempty"` +} + +func (x *Layer) Reset() { + *x = Layer{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Layer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Layer) ProtoMessage() {} + +func (x *Layer) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Layer.ProtoReflect.Descriptor instead. +func (*Layer) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{13} +} + +func (x *Layer) GetDigest() string { + if x != nil { + return x.Digest + } + return "" +} + +func (x *Layer) GetDiffId() string { + if x != nil { + return x.DiffId + } + return "" +} + +func (x *Layer) GetCreatedBy() string { + if x != nil { + return x.CreatedBy + } + return "" +} + +type CauseMetadata struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Resource string `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Service string `protobuf:"bytes,3,opt,name=service,proto3" json:"service,omitempty"` + StartLine int32 `protobuf:"varint,4,opt,name=start_line,json=startLine,proto3" json:"start_line,omitempty"` + EndLine int32 `protobuf:"varint,5,opt,name=end_line,json=endLine,proto3" json:"end_line,omitempty"` + Code *Code `protobuf:"bytes,6,opt,name=code,proto3" json:"code,omitempty"` +} + +func (x *CauseMetadata) Reset() { + *x = CauseMetadata{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CauseMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CauseMetadata) ProtoMessage() {} + +func (x *CauseMetadata) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CauseMetadata.ProtoReflect.Descriptor instead. +func (*CauseMetadata) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{14} +} + +func (x *CauseMetadata) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *CauseMetadata) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *CauseMetadata) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +func (x *CauseMetadata) GetStartLine() int32 { + if x != nil { + return x.StartLine + } + return 0 +} + +func (x *CauseMetadata) GetEndLine() int32 { + if x != nil { + return x.EndLine + } + return 0 +} + +func (x *CauseMetadata) GetCode() *Code { + if x != nil { + return x.Code + } + return nil +} + +type CVSS struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + V2Vector string `protobuf:"bytes,1,opt,name=v2_vector,json=v2Vector,proto3" json:"v2_vector,omitempty"` + V3Vector string `protobuf:"bytes,2,opt,name=v3_vector,json=v3Vector,proto3" json:"v3_vector,omitempty"` + V2Score float64 `protobuf:"fixed64,3,opt,name=v2_score,json=v2Score,proto3" json:"v2_score,omitempty"` + V3Score float64 `protobuf:"fixed64,4,opt,name=v3_score,json=v3Score,proto3" json:"v3_score,omitempty"` +} + +func (x *CVSS) Reset() { + *x = CVSS{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CVSS) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CVSS) ProtoMessage() {} + +func (x *CVSS) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CVSS.ProtoReflect.Descriptor instead. +func (*CVSS) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{15} +} + +func (x *CVSS) GetV2Vector() string { + if x != nil { + return x.V2Vector + } + return "" +} + +func (x *CVSS) GetV3Vector() string { + if x != nil { + return x.V3Vector + } + return "" +} + +func (x *CVSS) GetV2Score() float64 { + if x != nil { + return x.V2Score + } + return 0 +} + +func (x *CVSS) GetV3Score() float64 { + if x != nil { + return x.V3Score + } + return 0 +} + +type CustomResource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + FilePath string `protobuf:"bytes,2,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + Layer *Layer `protobuf:"bytes,3,opt,name=layer,proto3" json:"layer,omitempty"` + Data *structpb.Value `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *CustomResource) Reset() { + *x = CustomResource{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CustomResource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CustomResource) ProtoMessage() {} + +func (x *CustomResource) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CustomResource.ProtoReflect.Descriptor instead. +func (*CustomResource) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{16} +} + +func (x *CustomResource) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *CustomResource) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + +func (x *CustomResource) GetLayer() *Layer { + if x != nil { + return x.Layer + } + return nil +} + +func (x *CustomResource) GetData() *structpb.Value { + if x != nil { + return x.Data + } + return nil +} + +type Line struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"` + Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` + IsCause bool `protobuf:"varint,3,opt,name=is_cause,json=isCause,proto3" json:"is_cause,omitempty"` + Annotation string `protobuf:"bytes,4,opt,name=annotation,proto3" json:"annotation,omitempty"` + Truncated bool `protobuf:"varint,5,opt,name=truncated,proto3" json:"truncated,omitempty"` + Highlighted string `protobuf:"bytes,6,opt,name=highlighted,proto3" json:"highlighted,omitempty"` + FirstCause bool `protobuf:"varint,7,opt,name=first_cause,json=firstCause,proto3" json:"first_cause,omitempty"` + LastCause bool `protobuf:"varint,8,opt,name=last_cause,json=lastCause,proto3" json:"last_cause,omitempty"` +} + +func (x *Line) Reset() { + *x = Line{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Line) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Line) ProtoMessage() {} + +func (x *Line) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Line.ProtoReflect.Descriptor instead. +func (*Line) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{17} +} + +func (x *Line) GetNumber() int32 { + if x != nil { + return x.Number + } + return 0 +} + +func (x *Line) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *Line) GetIsCause() bool { + if x != nil { + return x.IsCause + } + return false +} + +func (x *Line) GetAnnotation() string { + if x != nil { + return x.Annotation + } + return "" +} + +func (x *Line) GetTruncated() bool { + if x != nil { + return x.Truncated + } + return false +} + +func (x *Line) GetHighlighted() string { + if x != nil { + return x.Highlighted + } + return "" +} + +func (x *Line) GetFirstCause() bool { + if x != nil { + return x.FirstCause + } + return false +} + +func (x *Line) GetLastCause() bool { + if x != nil { + return x.LastCause + } + return false +} + +type Code struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Lines []*Line `protobuf:"bytes,1,rep,name=lines,proto3" json:"lines,omitempty"` +} + +func (x *Code) Reset() { + *x = Code{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Code) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Code) ProtoMessage() {} + +func (x *Code) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Code.ProtoReflect.Descriptor instead. +func (*Code) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{18} +} + +func (x *Code) GetLines() []*Line { + if x != nil { + return x.Lines + } + return nil +} + +type SecretFinding struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RuleId string `protobuf:"bytes,1,opt,name=rule_id,json=ruleId,proto3" json:"rule_id,omitempty"` + Category string `protobuf:"bytes,2,opt,name=category,proto3" json:"category,omitempty"` + Severity string `protobuf:"bytes,3,opt,name=severity,proto3" json:"severity,omitempty"` + Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"` + StartLine int32 `protobuf:"varint,5,opt,name=start_line,json=startLine,proto3" json:"start_line,omitempty"` + EndLine int32 `protobuf:"varint,6,opt,name=end_line,json=endLine,proto3" json:"end_line,omitempty"` + Code *Code `protobuf:"bytes,7,opt,name=code,proto3" json:"code,omitempty"` + Match string `protobuf:"bytes,8,opt,name=match,proto3" json:"match,omitempty"` + Layer *Layer `protobuf:"bytes,10,opt,name=layer,proto3" json:"layer,omitempty"` +} + +func (x *SecretFinding) Reset() { + *x = SecretFinding{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SecretFinding) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SecretFinding) ProtoMessage() {} + +func (x *SecretFinding) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SecretFinding.ProtoReflect.Descriptor instead. +func (*SecretFinding) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{19} +} + +func (x *SecretFinding) GetRuleId() string { + if x != nil { + return x.RuleId + } + return "" +} + +func (x *SecretFinding) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *SecretFinding) GetSeverity() string { + if x != nil { + return x.Severity + } + return "" +} + +func (x *SecretFinding) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *SecretFinding) GetStartLine() int32 { + if x != nil { + return x.StartLine + } + return 0 +} + +func (x *SecretFinding) GetEndLine() int32 { + if x != nil { + return x.EndLine + } + return 0 +} + +func (x *SecretFinding) GetCode() *Code { + if x != nil { + return x.Code + } + return nil +} + +func (x *SecretFinding) GetMatch() string { + if x != nil { + return x.Match + } + return "" +} + +func (x *SecretFinding) GetLayer() *Layer { + if x != nil { + return x.Layer + } + return nil +} + +type Secret struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filepath string `protobuf:"bytes,1,opt,name=filepath,proto3" json:"filepath,omitempty"` + Findings []*SecretFinding `protobuf:"bytes,2,rep,name=findings,proto3" json:"findings,omitempty"` +} + +func (x *Secret) Reset() { + *x = Secret{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Secret) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Secret) ProtoMessage() {} + +func (x *Secret) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Secret.ProtoReflect.Descriptor instead. +func (*Secret) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{20} +} + +func (x *Secret) GetFilepath() string { + if x != nil { + return x.Filepath + } + return "" +} + +func (x *Secret) GetFindings() []*SecretFinding { + if x != nil { + return x.Findings + } + return nil +} + +type DetectedLicense struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Severity Severity `protobuf:"varint,1,opt,name=severity,proto3,enum=trivy.common.Severity" json:"severity,omitempty"` + Category LicenseCategory_Enum `protobuf:"varint,2,opt,name=category,proto3,enum=trivy.common.LicenseCategory_Enum" json:"category,omitempty"` + PkgName string `protobuf:"bytes,3,opt,name=pkg_name,json=pkgName,proto3" json:"pkg_name,omitempty"` + FilePath string `protobuf:"bytes,4,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` + Confidence float32 `protobuf:"fixed32,6,opt,name=confidence,proto3" json:"confidence,omitempty"` + Link string `protobuf:"bytes,7,opt,name=link,proto3" json:"link,omitempty"` +} + +func (x *DetectedLicense) Reset() { + *x = DetectedLicense{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DetectedLicense) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DetectedLicense) ProtoMessage() {} + +func (x *DetectedLicense) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DetectedLicense.ProtoReflect.Descriptor instead. +func (*DetectedLicense) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{21} +} + +func (x *DetectedLicense) GetSeverity() Severity { + if x != nil { + return x.Severity + } + return Severity_UNKNOWN +} + +func (x *DetectedLicense) GetCategory() LicenseCategory_Enum { + if x != nil { + return x.Category + } + return LicenseCategory_UNSPECIFIED +} + +func (x *DetectedLicense) GetPkgName() string { + if x != nil { + return x.PkgName + } + return "" +} + +func (x *DetectedLicense) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + +func (x *DetectedLicense) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DetectedLicense) GetConfidence() float32 { + if x != nil { + return x.Confidence + } + return 0 +} + +func (x *DetectedLicense) GetLink() string { + if x != nil { + return x.Link + } + return "" +} + +type LicenseFile struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LicenseType LicenseType_Enum `protobuf:"varint,1,opt,name=license_type,json=licenseType,proto3,enum=trivy.common.LicenseType_Enum" json:"license_type,omitempty"` + FilePath string `protobuf:"bytes,2,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + PkgName string `protobuf:"bytes,3,opt,name=pkg_name,json=pkgName,proto3" json:"pkg_name,omitempty"` + Fingings []*LicenseFinding `protobuf:"bytes,4,rep,name=fingings,proto3" json:"fingings,omitempty"` + Layer *Layer `protobuf:"bytes,5,opt,name=layer,proto3" json:"layer,omitempty"` +} + +func (x *LicenseFile) Reset() { + *x = LicenseFile{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LicenseFile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LicenseFile) ProtoMessage() {} + +func (x *LicenseFile) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LicenseFile.ProtoReflect.Descriptor instead. +func (*LicenseFile) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{22} +} + +func (x *LicenseFile) GetLicenseType() LicenseType_Enum { + if x != nil { + return x.LicenseType + } + return LicenseType_UNSPECIFIED +} + +func (x *LicenseFile) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + +func (x *LicenseFile) GetPkgName() string { + if x != nil { + return x.PkgName + } + return "" +} + +func (x *LicenseFile) GetFingings() []*LicenseFinding { + if x != nil { + return x.Fingings + } + return nil +} + +func (x *LicenseFile) GetLayer() *Layer { + if x != nil { + return x.Layer + } + return nil +} + +type LicenseFinding struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Category LicenseCategory_Enum `protobuf:"varint,1,opt,name=category,proto3,enum=trivy.common.LicenseCategory_Enum" json:"category,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Confidence float32 `protobuf:"fixed32,3,opt,name=confidence,proto3" json:"confidence,omitempty"` + Link string `protobuf:"bytes,4,opt,name=link,proto3" json:"link,omitempty"` +} + +func (x *LicenseFinding) Reset() { + *x = LicenseFinding{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LicenseFinding) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LicenseFinding) ProtoMessage() {} + +func (x *LicenseFinding) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LicenseFinding.ProtoReflect.Descriptor instead. +func (*LicenseFinding) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{23} +} + +func (x *LicenseFinding) GetCategory() LicenseCategory_Enum { + if x != nil { + return x.Category + } + return LicenseCategory_UNSPECIFIED +} + +func (x *LicenseFinding) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *LicenseFinding) GetConfidence() float32 { + if x != nil { + return x.Confidence + } + return 0 +} + +func (x *LicenseFinding) GetLink() string { + if x != nil { + return x.Link + } + return "" +} + +// Enumerations are wrapped with a message to improve the readability of +// enumerations in generated code and avoid name conflicts. +// https://github.com/golang/protobuf/issues/513 +type LicenseCategory struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *LicenseCategory) Reset() { + *x = LicenseCategory{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LicenseCategory) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LicenseCategory) ProtoMessage() {} + +func (x *LicenseCategory) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LicenseCategory.ProtoReflect.Descriptor instead. +func (*LicenseCategory) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{24} +} + +type LicenseType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *LicenseType) Reset() { + *x = LicenseType{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_common_service_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LicenseType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LicenseType) ProtoMessage() {} + +func (x *LicenseType) ProtoReflect() protoreflect.Message { + mi := &file_rpc_common_service_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LicenseType.ProtoReflect.Descriptor instead. +func (*LicenseType) Descriptor() ([]byte, []int) { + return file_rpc_common_service_proto_rawDescGZIP(), []int{25} +} + +var File_rpc_common_service_proto protoreflect.FileDescriptor + +var file_rpc_common_service_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x60, 0x0a, 0x02, 0x4f, 0x53, 0x12, 0x16, 0x0a, + 0x06, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, + 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x65, 0x6f, 0x73, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x65, 0x6f, 0x73, 0x6c, 0x12, 0x1a, 0x0a, + 0x08, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x08, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x22, 0x3e, 0x0a, 0x0a, 0x52, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x61, 0x6d, 0x69, 0x6c, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, + 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0x5d, 0x0a, 0x0b, 0x50, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, + 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, + 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, + 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0x73, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, + 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x33, 0x0a, 0x09, 0x6c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, + 0x67, 0x65, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x22, 0xc1, 0x04, + 0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x3b, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x6b, 0x67, 0x49, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x63, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x72, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x72, 0x63, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x72, 0x63, 0x5f, 0x72, 0x65, 0x6c, 0x65, + 0x61, 0x73, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x72, 0x63, 0x52, 0x65, + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x72, 0x63, 0x5f, 0x65, 0x70, 0x6f, + 0x63, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x73, 0x72, 0x63, 0x45, 0x70, 0x6f, + 0x63, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x0f, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x34, + 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, + 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, + 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x5f, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x09, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x4f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x64, + 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, + 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x65, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x03, 0x64, 0x65, 0x76, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x22, 0x3c, 0x0a, 0x0d, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x70, 0x75, 0x72, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x6f, 0x6d, 0x5f, 0x72, 0x65, + 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x6f, 0x6d, 0x52, 0x65, 0x66, 0x22, + 0x44, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, + 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, + 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x22, 0xb6, 0x02, 0x0a, 0x10, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, + 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, + 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, + 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, + 0x50, 0x61, 0x74, 0x68, 0x12, 0x39, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, + 0x37, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, + 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x73, 0x12, 0x3b, 0x0a, 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf3, + 0x01, 0x0a, 0x0d, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0f, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, + 0x0e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, + 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x07, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x52, + 0x02, 0x69, 0x64, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x22, 0xf0, 0x01, 0x0a, 0x0e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x64, 0x76, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x64, 0x76, 0x49, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2f, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, + 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xf7, 0x03, 0x0a, 0x18, 0x44, 0x65, 0x74, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x73, 0x6f, + 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, + 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, + 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, + 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, + 0x12, 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x76, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x76, 0x64, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x22, 0xff, 0x09, 0x0a, 0x0d, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, + 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, + 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, + 0x69, 0x78, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, + 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x70, 0x6b, 0x67, 0x5f, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, + 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0d, 0x70, 0x6b, + 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x05, 0x6c, + 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, + 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, + 0x74, 0x79, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x39, 0x0a, 0x04, 0x63, 0x76, 0x73, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, + 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x76, 0x73, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x63, 0x76, 0x73, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x77, + 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x63, 0x77, 0x65, + 0x49, 0x64, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, + 0x79, 0x55, 0x72, 0x6c, 0x12, 0x41, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, + 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x10, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x44, 0x61, 0x74, + 0x65, 0x12, 0x48, 0x0a, 0x14, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x76, 0x69, + 0x73, 0x6f, 0x72, 0x79, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x41, + 0x64, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x10, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x75, 0x6c, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, + 0x0a, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x09, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0b, + 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0a, 0x64, 0x61, 0x74, + 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x76, 0x65, 0x6e, 0x64, 0x6f, + 0x72, 0x5f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2f, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x56, 0x65, + 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0e, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, + 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x16, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x15, 0x0a, 0x06, + 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x6b, + 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x18, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x4b, 0x0a, 0x09, 0x43, + 0x76, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x56, 0x53, 0x53, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x13, 0x56, 0x65, 0x6e, 0x64, + 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x42, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x57, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, + 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x69, 0x66, 0x66, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x66, 0x66, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, + 0x22, 0xc3, 0x01, 0x0a, 0x0d, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, + 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, + 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x26, + 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x64, 0x65, + 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x76, 0x0a, 0x04, 0x43, 0x56, 0x53, 0x53, 0x12, 0x1b, + 0x0a, 0x09, 0x76, 0x32, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x76, 0x32, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x76, + 0x33, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x76, 0x33, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x32, 0x5f, 0x73, + 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x32, 0x53, 0x63, + 0x6f, 0x72, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x33, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x33, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x98, + 0x01, 0x0a, 0x0e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, + 0x74, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x2a, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf3, 0x01, 0x0a, 0x04, 0x4c, 0x69, + 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x43, 0x61, 0x75, 0x73, 0x65, 0x12, + 0x1e, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x20, 0x0a, + 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x12, + 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, 0x65, 0x22, + 0x30, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, + 0x73, 0x22, 0x9f, 0x02, 0x0a, 0x0d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, + 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, + 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, + 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, + 0x63, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4a, 0x04, 0x08, + 0x09, 0x10, 0x0a, 0x22, 0x5d, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, + 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x73, 0x22, 0x85, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, + 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, + 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x08, 0x63, 0x61, + 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6e, 0x75, 0x6d, + 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, + 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, + 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, + 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, + 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0xed, 0x01, 0x0a, 0x0b, 0x4c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x6c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x45, 0x6e, 0x75, 0x6d, + 0x52, 0x0b, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, + 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, + 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, 0x73, 0x12, + 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, + 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x4c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3e, 0x0a, + 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, + 0x6e, 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, + 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x81, 0x01, 0x0a, 0x04, 0x45, 0x6e, + 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 0x44, 0x45, 0x4e, + 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, 0x45, 0x44, + 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x43, 0x49, 0x50, 0x52, 0x4f, 0x43, 0x41, 0x4c, + 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x54, 0x49, 0x43, 0x45, 0x10, 0x04, 0x12, 0x0e, + 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x56, 0x45, 0x10, 0x05, 0x12, 0x10, + 0x0a, 0x0c, 0x55, 0x4e, 0x45, 0x4e, 0x43, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x45, 0x44, 0x10, 0x06, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x07, 0x22, 0x4e, 0x0a, + 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3f, 0x0a, 0x04, + 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x50, 0x4b, 0x47, 0x10, 0x01, 0x12, + 0x0a, 0x0a, 0x06, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4c, + 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x44, 0x0a, + 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, + 0x0a, 0x0a, 0x06, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x48, + 0x49, 0x47, 0x48, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, + 0x4c, 0x10, 0x04, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3b, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_rpc_common_service_proto_rawDescOnce sync.Once + file_rpc_common_service_proto_rawDescData = file_rpc_common_service_proto_rawDesc +) + +func file_rpc_common_service_proto_rawDescGZIP() []byte { + file_rpc_common_service_proto_rawDescOnce.Do(func() { + file_rpc_common_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_rpc_common_service_proto_rawDescData) + }) + return file_rpc_common_service_proto_rawDescData +} + +var file_rpc_common_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_rpc_common_service_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_rpc_common_service_proto_goTypes = []interface{}{ + (Severity)(0), // 0: trivy.common.Severity + (LicenseCategory_Enum)(0), // 1: trivy.common.LicenseCategory.Enum + (LicenseType_Enum)(0), // 2: trivy.common.LicenseType.Enum + (*OS)(nil), // 3: trivy.common.OS + (*Repository)(nil), // 4: trivy.common.Repository + (*PackageInfo)(nil), // 5: trivy.common.PackageInfo + (*Application)(nil), // 6: trivy.common.Application + (*Package)(nil), // 7: trivy.common.Package + (*PkgIdentifier)(nil), // 8: trivy.common.PkgIdentifier + (*Location)(nil), // 9: trivy.common.Location + (*Misconfiguration)(nil), // 10: trivy.common.Misconfiguration + (*MisconfResult)(nil), // 11: trivy.common.MisconfResult + (*PolicyMetadata)(nil), // 12: trivy.common.PolicyMetadata + (*DetectedMisconfiguration)(nil), // 13: trivy.common.DetectedMisconfiguration + (*Vulnerability)(nil), // 14: trivy.common.Vulnerability + (*DataSource)(nil), // 15: trivy.common.DataSource + (*Layer)(nil), // 16: trivy.common.Layer + (*CauseMetadata)(nil), // 17: trivy.common.CauseMetadata + (*CVSS)(nil), // 18: trivy.common.CVSS + (*CustomResource)(nil), // 19: trivy.common.CustomResource + (*Line)(nil), // 20: trivy.common.Line + (*Code)(nil), // 21: trivy.common.Code + (*SecretFinding)(nil), // 22: trivy.common.SecretFinding + (*Secret)(nil), // 23: trivy.common.Secret + (*DetectedLicense)(nil), // 24: trivy.common.DetectedLicense + (*LicenseFile)(nil), // 25: trivy.common.LicenseFile + (*LicenseFinding)(nil), // 26: trivy.common.LicenseFinding + (*LicenseCategory)(nil), // 27: trivy.common.LicenseCategory + (*LicenseType)(nil), // 28: trivy.common.LicenseType + nil, // 29: trivy.common.Vulnerability.CvssEntry + nil, // 30: trivy.common.Vulnerability.VendorSeverityEntry + (*timestamppb.Timestamp)(nil), // 31: google.protobuf.Timestamp + (*structpb.Value)(nil), // 32: google.protobuf.Value +} +var file_rpc_common_service_proto_depIdxs = []int32{ + 7, // 0: trivy.common.PackageInfo.packages:type_name -> trivy.common.Package + 7, // 1: trivy.common.Application.libraries:type_name -> trivy.common.Package + 8, // 2: trivy.common.Package.identifier:type_name -> trivy.common.PkgIdentifier + 9, // 3: trivy.common.Package.locations:type_name -> trivy.common.Location + 16, // 4: trivy.common.Package.layer:type_name -> trivy.common.Layer + 11, // 5: trivy.common.Misconfiguration.successes:type_name -> trivy.common.MisconfResult + 11, // 6: trivy.common.Misconfiguration.warnings:type_name -> trivy.common.MisconfResult + 11, // 7: trivy.common.Misconfiguration.failures:type_name -> trivy.common.MisconfResult + 11, // 8: trivy.common.Misconfiguration.exceptions:type_name -> trivy.common.MisconfResult + 12, // 9: trivy.common.MisconfResult.policy_metadata:type_name -> trivy.common.PolicyMetadata + 17, // 10: trivy.common.MisconfResult.cause_metadata:type_name -> trivy.common.CauseMetadata + 0, // 11: trivy.common.DetectedMisconfiguration.severity:type_name -> trivy.common.Severity + 16, // 12: trivy.common.DetectedMisconfiguration.layer:type_name -> trivy.common.Layer + 17, // 13: trivy.common.DetectedMisconfiguration.cause_metadata:type_name -> trivy.common.CauseMetadata + 0, // 14: trivy.common.Vulnerability.severity:type_name -> trivy.common.Severity + 8, // 15: trivy.common.Vulnerability.pkg_identifier:type_name -> trivy.common.PkgIdentifier + 16, // 16: trivy.common.Vulnerability.layer:type_name -> trivy.common.Layer + 29, // 17: trivy.common.Vulnerability.cvss:type_name -> trivy.common.Vulnerability.CvssEntry + 31, // 18: trivy.common.Vulnerability.published_date:type_name -> google.protobuf.Timestamp + 31, // 19: trivy.common.Vulnerability.last_modified_date:type_name -> google.protobuf.Timestamp + 32, // 20: trivy.common.Vulnerability.custom_advisory_data:type_name -> google.protobuf.Value + 32, // 21: trivy.common.Vulnerability.custom_vuln_data:type_name -> google.protobuf.Value + 15, // 22: trivy.common.Vulnerability.data_source:type_name -> trivy.common.DataSource + 30, // 23: trivy.common.Vulnerability.vendor_severity:type_name -> trivy.common.Vulnerability.VendorSeverityEntry + 21, // 24: trivy.common.CauseMetadata.code:type_name -> trivy.common.Code + 16, // 25: trivy.common.CustomResource.layer:type_name -> trivy.common.Layer + 32, // 26: trivy.common.CustomResource.data:type_name -> google.protobuf.Value + 20, // 27: trivy.common.Code.lines:type_name -> trivy.common.Line + 21, // 28: trivy.common.SecretFinding.code:type_name -> trivy.common.Code + 16, // 29: trivy.common.SecretFinding.layer:type_name -> trivy.common.Layer + 22, // 30: trivy.common.Secret.findings:type_name -> trivy.common.SecretFinding + 0, // 31: trivy.common.DetectedLicense.severity:type_name -> trivy.common.Severity + 1, // 32: trivy.common.DetectedLicense.category:type_name -> trivy.common.LicenseCategory.Enum + 2, // 33: trivy.common.LicenseFile.license_type:type_name -> trivy.common.LicenseType.Enum + 26, // 34: trivy.common.LicenseFile.fingings:type_name -> trivy.common.LicenseFinding + 16, // 35: trivy.common.LicenseFile.layer:type_name -> trivy.common.Layer + 1, // 36: trivy.common.LicenseFinding.category:type_name -> trivy.common.LicenseCategory.Enum + 18, // 37: trivy.common.Vulnerability.CvssEntry.value:type_name -> trivy.common.CVSS + 0, // 38: trivy.common.Vulnerability.VendorSeverityEntry.value:type_name -> trivy.common.Severity + 39, // [39:39] is the sub-list for method output_type + 39, // [39:39] is the sub-list for method input_type + 39, // [39:39] is the sub-list for extension type_name + 39, // [39:39] is the sub-list for extension extendee + 0, // [0:39] is the sub-list for field type_name +} + +func init() { file_rpc_common_service_proto_init() } +func file_rpc_common_service_proto_init() { + if File_rpc_common_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_rpc_common_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OS); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Repository); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PackageInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Application); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Package); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PkgIdentifier); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Location); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Misconfiguration); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MisconfResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PolicyMetadata); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DetectedMisconfiguration); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Vulnerability); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DataSource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Layer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CauseMetadata); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CVSS); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CustomResource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Line); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Code); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SecretFinding); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Secret); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DetectedLicense); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LicenseFile); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LicenseFinding); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LicenseCategory); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_common_service_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LicenseType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_rpc_common_service_proto_rawDesc, + NumEnums: 3, + NumMessages: 28, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_rpc_common_service_proto_goTypes, + DependencyIndexes: file_rpc_common_service_proto_depIdxs, + EnumInfos: file_rpc_common_service_proto_enumTypes, + MessageInfos: file_rpc_common_service_proto_msgTypes, + }.Build() + File_rpc_common_service_proto = out.File + file_rpc_common_service_proto_rawDesc = nil + file_rpc_common_service_proto_goTypes = nil + file_rpc_common_service_proto_depIdxs = nil +} diff --git a/rpc/common/service.proto b/rpc/common/service.proto new file mode 100644 index 000000000000..d5c1472b4aef --- /dev/null +++ b/rpc/common/service.proto @@ -0,0 +1,268 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +package trivy.common; +option go_package = "github.com/aquasecurity/trivy/rpc/common;common"; + +import "google/protobuf/struct.proto"; + +message OS { + string family = 1; + string name = 2; + bool eosl = 3; + bool extended = 4; +} + +message Repository { + string family = 1; + string release = 2; +} + +message PackageInfo { + string file_path = 1; + repeated Package packages = 2; +} + +message Application { + string type = 1; + string file_path = 2; + repeated Package libraries = 3; +} + +message Package { + // binary package + // e.g. bind-utils + string id = 13; + string name = 1; + string version = 2; + string release = 3; + int32 epoch = 4; + PkgIdentifier identifier = 19; + string arch = 5; + // src package containing some binary packages + // e.g. bind + string src_name = 6; + string src_version = 7; + string src_release = 8; + int32 src_epoch = 9; + repeated string licenses = 15; + repeated Location locations = 20; + Layer layer = 11; + string file_path = 12; + repeated string depends_on = 14; + string digest = 16; + bool dev = 17; + bool indirect = 18; +} + +message PkgIdentifier { + string purl = 1; + string bom_ref = 2; +} + +message Location { + int32 start_line = 1; + int32 end_line = 2; +} + +message Misconfiguration { + string file_type = 1; + string file_path = 2; + repeated MisconfResult successes = 3; + repeated MisconfResult warnings = 4; + repeated MisconfResult failures = 5; + repeated MisconfResult exceptions = 6; +} + +message MisconfResult { + string namespace = 1; + string message = 2; + reserved 3 to 6; + reserved "type", "id", "title", "severity"; + PolicyMetadata policy_metadata = 7; + CauseMetadata cause_metadata = 8; +} + +message PolicyMetadata { + string id = 1; + string adv_id = 2; + string type = 3; + string title = 4; + string description = 5; + string severity = 6; + string recommended_actions = 7; + repeated string references = 8; +} + +message DetectedMisconfiguration { + string type = 1; + string id = 2; + string title = 3; + string description = 4; + string message = 5; + string namespace = 6; + string resolution = 7; + Severity severity = 8; + string primary_url = 9; + repeated string references = 10; + string status = 11; + Layer layer = 12; + CauseMetadata cause_metadata = 13; + string avd_id = 14; + string query = 15; +} + +message Vulnerability { + string vulnerability_id = 1; + string pkg_name = 2; + string installed_version = 3; + string fixed_version = 4; + string title = 5; + string description = 6; + Severity severity = 7; + repeated string references = 8; + PkgIdentifier pkg_identifier = 25; + Layer layer = 10; + string severity_source = 11; + map cvss = 12; + repeated string cwe_ids = 13; + string primary_url = 14; + google.protobuf.Timestamp published_date = 15; + google.protobuf.Timestamp last_modified_date = 16; + google.protobuf.Value custom_advisory_data = 17; + google.protobuf.Value custom_vuln_data = 18; + repeated string vendor_ids = 19; + DataSource data_source = 20; + map vendor_severity = 21; + string pkg_path = 22; + string pkg_id = 23; + int32 status = 24; +} + +message DataSource { + string id = 1; + string name = 2; + string url = 3; +} + +message Layer { + string digest = 1; + string diff_id = 2; + string created_by = 3; +} + +message CauseMetadata { + string resource = 1; + string provider = 2; + string service = 3; + int32 start_line = 4; + int32 end_line = 5; + Code code = 6; +} + +enum Severity { + UNKNOWN = 0; + LOW = 1; + MEDIUM = 2; + HIGH = 3; + CRITICAL = 4; +} + +message CVSS { + string v2_vector = 1; + string v3_vector = 2; + double v2_score = 3; + double v3_score = 4; +} + +message CustomResource { + string type = 1; + string file_path = 2; + Layer layer = 3; + google.protobuf.Value data = 4; +} + +message Line { + int32 number = 1; + string content = 2; + bool is_cause = 3; + string annotation = 4; + bool truncated = 5; + string highlighted = 6; + bool first_cause = 7; + bool last_cause = 8; +} + +message Code { + repeated Line lines = 1; +} + +message SecretFinding { + string rule_id = 1; + string category = 2; + string severity = 3; + string title = 4; + int32 start_line = 5; + int32 end_line = 6; + Code code = 7; + string match = 8; + Layer layer = 10; + + reserved 9; // deprecated 'deleted' +} + +message Secret { + string filepath = 1; + repeated SecretFinding findings = 2; +} + +message DetectedLicense { + Severity severity = 1; + LicenseCategory.Enum category = 2; + string pkg_name = 3; + string file_path = 4; + string name = 5; + float confidence = 6; + string link = 7; +} + +message LicenseFile { + LicenseType.Enum license_type = 1; + string file_path = 2; + string pkg_name = 3; + repeated LicenseFinding fingings = 4; + Layer layer = 5; +} + +message LicenseFinding { + LicenseCategory.Enum category = 1; + string name = 2; + float confidence = 3; + string link = 4; +} + +// Enumerations are wrapped with a message to improve the readability of +// enumerations in generated code and avoid name conflicts. +// https://github.com/golang/protobuf/issues/513 +message LicenseCategory { + enum Enum { + UNSPECIFIED = 0; + FORBIDDEN = 1; + RESTRICTED = 2; + RECIPROCAL = 3; + NOTICE = 4; + PERMISSIVE = 5; + UNENCUMBERED = 6; + UNKNOWN = 7; + } +} + +message LicenseType { + enum Enum { + UNSPECIFIED = 0; + DPKG = 1; + HEADER = 2; + LICENSE_FILE = 3; + } +} diff --git a/rpc/scanner/service.pb.go b/rpc/scanner/service.pb.go new file mode 100644 index 000000000000..eb75c1d582b6 --- /dev/null +++ b/rpc/scanner/service.pb.go @@ -0,0 +1,613 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.4 +// source: rpc/scanner/service.proto + +package scanner + +import ( + common "github.com/aquasecurity/trivy/rpc/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ScanRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"` // image name or tar file path + ArtifactId string `protobuf:"bytes,2,opt,name=artifact_id,json=artifactId,proto3" json:"artifact_id,omitempty"` + BlobIds []string `protobuf:"bytes,3,rep,name=blob_ids,json=blobIds,proto3" json:"blob_ids,omitempty"` + Options *ScanOptions `protobuf:"bytes,4,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *ScanRequest) Reset() { + *x = ScanRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_scanner_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ScanRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScanRequest) ProtoMessage() {} + +func (x *ScanRequest) ProtoReflect() protoreflect.Message { + mi := &file_rpc_scanner_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScanRequest.ProtoReflect.Descriptor instead. +func (*ScanRequest) Descriptor() ([]byte, []int) { + return file_rpc_scanner_service_proto_rawDescGZIP(), []int{0} +} + +func (x *ScanRequest) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *ScanRequest) GetArtifactId() string { + if x != nil { + return x.ArtifactId + } + return "" +} + +func (x *ScanRequest) GetBlobIds() []string { + if x != nil { + return x.BlobIds + } + return nil +} + +func (x *ScanRequest) GetOptions() *ScanOptions { + if x != nil { + return x.Options + } + return nil +} + +// cf. +// https://stackoverflow.com/questions/38886789/protobuf3-how-to-describe-map-of-repeated-string +type Licenses struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` +} + +func (x *Licenses) Reset() { + *x = Licenses{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_scanner_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Licenses) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Licenses) ProtoMessage() {} + +func (x *Licenses) ProtoReflect() protoreflect.Message { + mi := &file_rpc_scanner_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Licenses.ProtoReflect.Descriptor instead. +func (*Licenses) Descriptor() ([]byte, []int) { + return file_rpc_scanner_service_proto_rawDescGZIP(), []int{1} +} + +func (x *Licenses) GetNames() []string { + if x != nil { + return x.Names + } + return nil +} + +type ScanOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + VulnType []string `protobuf:"bytes,1,rep,name=vuln_type,json=vulnType,proto3" json:"vuln_type,omitempty"` + Scanners []string `protobuf:"bytes,2,rep,name=scanners,proto3" json:"scanners,omitempty"` + ListAllPackages bool `protobuf:"varint,3,opt,name=list_all_packages,json=listAllPackages,proto3" json:"list_all_packages,omitempty"` + LicenseCategories map[string]*Licenses `protobuf:"bytes,4,rep,name=license_categories,json=licenseCategories,proto3" json:"license_categories,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + IncludeDevDeps bool `protobuf:"varint,5,opt,name=include_dev_deps,json=includeDevDeps,proto3" json:"include_dev_deps,omitempty"` +} + +func (x *ScanOptions) Reset() { + *x = ScanOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_scanner_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ScanOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScanOptions) ProtoMessage() {} + +func (x *ScanOptions) ProtoReflect() protoreflect.Message { + mi := &file_rpc_scanner_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScanOptions.ProtoReflect.Descriptor instead. +func (*ScanOptions) Descriptor() ([]byte, []int) { + return file_rpc_scanner_service_proto_rawDescGZIP(), []int{2} +} + +func (x *ScanOptions) GetVulnType() []string { + if x != nil { + return x.VulnType + } + return nil +} + +func (x *ScanOptions) GetScanners() []string { + if x != nil { + return x.Scanners + } + return nil +} + +func (x *ScanOptions) GetListAllPackages() bool { + if x != nil { + return x.ListAllPackages + } + return false +} + +func (x *ScanOptions) GetLicenseCategories() map[string]*Licenses { + if x != nil { + return x.LicenseCategories + } + return nil +} + +func (x *ScanOptions) GetIncludeDevDeps() bool { + if x != nil { + return x.IncludeDevDeps + } + return false +} + +type ScanResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Os *common.OS `protobuf:"bytes,1,opt,name=os,proto3" json:"os,omitempty"` + Results []*Result `protobuf:"bytes,3,rep,name=results,proto3" json:"results,omitempty"` +} + +func (x *ScanResponse) Reset() { + *x = ScanResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_scanner_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ScanResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScanResponse) ProtoMessage() {} + +func (x *ScanResponse) ProtoReflect() protoreflect.Message { + mi := &file_rpc_scanner_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScanResponse.ProtoReflect.Descriptor instead. +func (*ScanResponse) Descriptor() ([]byte, []int) { + return file_rpc_scanner_service_proto_rawDescGZIP(), []int{3} +} + +func (x *ScanResponse) GetOs() *common.OS { + if x != nil { + return x.Os + } + return nil +} + +func (x *ScanResponse) GetResults() []*Result { + if x != nil { + return x.Results + } + return nil +} + +// Result is the same as github.com/aquasecurity/trivy/pkg/report.Result +type Result struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"` + Vulnerabilities []*common.Vulnerability `protobuf:"bytes,2,rep,name=vulnerabilities,proto3" json:"vulnerabilities,omitempty"` + Misconfigurations []*common.DetectedMisconfiguration `protobuf:"bytes,4,rep,name=misconfigurations,proto3" json:"misconfigurations,omitempty"` + Class string `protobuf:"bytes,6,opt,name=class,proto3" json:"class,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Packages []*common.Package `protobuf:"bytes,5,rep,name=packages,proto3" json:"packages,omitempty"` + CustomResources []*common.CustomResource `protobuf:"bytes,7,rep,name=custom_resources,json=customResources,proto3" json:"custom_resources,omitempty"` + Secrets []*common.SecretFinding `protobuf:"bytes,8,rep,name=secrets,proto3" json:"secrets,omitempty"` + Licenses []*common.DetectedLicense `protobuf:"bytes,9,rep,name=licenses,proto3" json:"licenses,omitempty"` +} + +func (x *Result) Reset() { + *x = Result{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_scanner_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Result) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Result) ProtoMessage() {} + +func (x *Result) ProtoReflect() protoreflect.Message { + mi := &file_rpc_scanner_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Result.ProtoReflect.Descriptor instead. +func (*Result) Descriptor() ([]byte, []int) { + return file_rpc_scanner_service_proto_rawDescGZIP(), []int{4} +} + +func (x *Result) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *Result) GetVulnerabilities() []*common.Vulnerability { + if x != nil { + return x.Vulnerabilities + } + return nil +} + +func (x *Result) GetMisconfigurations() []*common.DetectedMisconfiguration { + if x != nil { + return x.Misconfigurations + } + return nil +} + +func (x *Result) GetClass() string { + if x != nil { + return x.Class + } + return "" +} + +func (x *Result) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Result) GetPackages() []*common.Package { + if x != nil { + return x.Packages + } + return nil +} + +func (x *Result) GetCustomResources() []*common.CustomResource { + if x != nil { + return x.CustomResources + } + return nil +} + +func (x *Result) GetSecrets() []*common.SecretFinding { + if x != nil { + return x.Secrets + } + return nil +} + +func (x *Result) GetLicenses() []*common.DetectedLicense { + if x != nil { + return x.Licenses + } + return nil +} + +var File_rpc_scanner_service_proto protoreflect.FileDescriptor + +var file_rpc_scanner_service_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x1a, 0x18, 0x72, + 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9a, 0x01, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, + 0x1f, 0x0a, 0x0b, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x62, 0x49, 0x64, 0x73, 0x12, 0x37, 0x0a, 0x07, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x63, 0x61, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x20, 0x0a, 0x08, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xe3, 0x02, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x76, 0x75, 0x6c, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x12, + 0x2a, 0x0a, 0x11, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x61, 0x63, 0x6b, + 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6c, 0x69, 0x73, 0x74, + 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x63, 0x0a, 0x12, 0x6c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, + 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x6c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, + 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x65, 0x76, 0x5f, + 0x64, 0x65, 0x70, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x44, 0x65, 0x76, 0x44, 0x65, 0x70, 0x73, 0x1a, 0x60, 0x0a, 0x16, 0x4c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, + 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x64, 0x0a, 0x0c, + 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x02, + 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x32, + 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x22, 0xd5, 0x03, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, + 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0f, 0x76, 0x75, 0x6c, + 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x11, + 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, + 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x08, + 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, + 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, + 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, + 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x32, 0x50, 0x0a, 0x07, 0x53, 0x63, + 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x04, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1d, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, + 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, + 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x3b, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, + 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_rpc_scanner_service_proto_rawDescOnce sync.Once + file_rpc_scanner_service_proto_rawDescData = file_rpc_scanner_service_proto_rawDesc +) + +func file_rpc_scanner_service_proto_rawDescGZIP() []byte { + file_rpc_scanner_service_proto_rawDescOnce.Do(func() { + file_rpc_scanner_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_rpc_scanner_service_proto_rawDescData) + }) + return file_rpc_scanner_service_proto_rawDescData +} + +var file_rpc_scanner_service_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_rpc_scanner_service_proto_goTypes = []interface{}{ + (*ScanRequest)(nil), // 0: trivy.scanner.v1.ScanRequest + (*Licenses)(nil), // 1: trivy.scanner.v1.Licenses + (*ScanOptions)(nil), // 2: trivy.scanner.v1.ScanOptions + (*ScanResponse)(nil), // 3: trivy.scanner.v1.ScanResponse + (*Result)(nil), // 4: trivy.scanner.v1.Result + nil, // 5: trivy.scanner.v1.ScanOptions.LicenseCategoriesEntry + (*common.OS)(nil), // 6: trivy.common.OS + (*common.Vulnerability)(nil), // 7: trivy.common.Vulnerability + (*common.DetectedMisconfiguration)(nil), // 8: trivy.common.DetectedMisconfiguration + (*common.Package)(nil), // 9: trivy.common.Package + (*common.CustomResource)(nil), // 10: trivy.common.CustomResource + (*common.SecretFinding)(nil), // 11: trivy.common.SecretFinding + (*common.DetectedLicense)(nil), // 12: trivy.common.DetectedLicense +} +var file_rpc_scanner_service_proto_depIdxs = []int32{ + 2, // 0: trivy.scanner.v1.ScanRequest.options:type_name -> trivy.scanner.v1.ScanOptions + 5, // 1: trivy.scanner.v1.ScanOptions.license_categories:type_name -> trivy.scanner.v1.ScanOptions.LicenseCategoriesEntry + 6, // 2: trivy.scanner.v1.ScanResponse.os:type_name -> trivy.common.OS + 4, // 3: trivy.scanner.v1.ScanResponse.results:type_name -> trivy.scanner.v1.Result + 7, // 4: trivy.scanner.v1.Result.vulnerabilities:type_name -> trivy.common.Vulnerability + 8, // 5: trivy.scanner.v1.Result.misconfigurations:type_name -> trivy.common.DetectedMisconfiguration + 9, // 6: trivy.scanner.v1.Result.packages:type_name -> trivy.common.Package + 10, // 7: trivy.scanner.v1.Result.custom_resources:type_name -> trivy.common.CustomResource + 11, // 8: trivy.scanner.v1.Result.secrets:type_name -> trivy.common.SecretFinding + 12, // 9: trivy.scanner.v1.Result.licenses:type_name -> trivy.common.DetectedLicense + 1, // 10: trivy.scanner.v1.ScanOptions.LicenseCategoriesEntry.value:type_name -> trivy.scanner.v1.Licenses + 0, // 11: trivy.scanner.v1.Scanner.Scan:input_type -> trivy.scanner.v1.ScanRequest + 3, // 12: trivy.scanner.v1.Scanner.Scan:output_type -> trivy.scanner.v1.ScanResponse + 12, // [12:13] is the sub-list for method output_type + 11, // [11:12] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_rpc_scanner_service_proto_init() } +func file_rpc_scanner_service_proto_init() { + if File_rpc_scanner_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_rpc_scanner_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScanRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_scanner_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Licenses); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_scanner_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScanOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_scanner_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScanResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_scanner_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Result); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_rpc_scanner_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_rpc_scanner_service_proto_goTypes, + DependencyIndexes: file_rpc_scanner_service_proto_depIdxs, + MessageInfos: file_rpc_scanner_service_proto_msgTypes, + }.Build() + File_rpc_scanner_service_proto = out.File + file_rpc_scanner_service_proto_rawDesc = nil + file_rpc_scanner_service_proto_goTypes = nil + file_rpc_scanner_service_proto_depIdxs = nil +} diff --git a/rpc/scanner/service.proto b/rpc/scanner/service.proto new file mode 100644 index 000000000000..63f98d2779c6 --- /dev/null +++ b/rpc/scanner/service.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package trivy.scanner.v1; +option go_package = "github.com/aquasecurity/trivy/rpc/scanner;scanner"; + +import "rpc/common/service.proto"; + +service Scanner { + rpc Scan(ScanRequest) returns (ScanResponse); +} + +message ScanRequest { + string target = 1; // image name or tar file path + string artifact_id = 2; + repeated string blob_ids = 3; + ScanOptions options = 4; +} + +// cf. +// https://stackoverflow.com/questions/38886789/protobuf3-how-to-describe-map-of-repeated-string +message Licenses { + repeated string names = 1; +} + +message ScanOptions { + repeated string vuln_type = 1; + repeated string scanners = 2; + bool list_all_packages = 3; + map license_categories = 4; + bool include_dev_deps = 5; +} + +message ScanResponse { + common.OS os = 1; + repeated Result results = 3; +} + +// Result is the same as github.com/aquasecurity/trivy/pkg/report.Result +message Result { + string target = 1; + repeated common.Vulnerability vulnerabilities = 2; + repeated common.DetectedMisconfiguration misconfigurations = 4; + string class = 6; + string type = 3; + repeated common.Package packages = 5; + repeated common.CustomResource custom_resources = 7; + repeated common.SecretFinding secrets = 8; + repeated common.DetectedLicense licenses = 9; +} \ No newline at end of file diff --git a/rpc/scanner/service.twirp.go b/rpc/scanner/service.twirp.go new file mode 100644 index 000000000000..4c88aec5db46 --- /dev/null +++ b/rpc/scanner/service.twirp.go @@ -0,0 +1,1140 @@ +// Code generated by protoc-gen-twirp v8.1.0, DO NOT EDIT. +// source: rpc/scanner/service.proto + +package scanner + +import context "context" +import fmt "fmt" +import http "net/http" +import ioutil "io/ioutil" +import json "encoding/json" +import strconv "strconv" +import strings "strings" + +import protojson "google.golang.org/protobuf/encoding/protojson" +import proto "google.golang.org/protobuf/proto" +import twirp "github.com/twitchtv/twirp" +import ctxsetters "github.com/twitchtv/twirp/ctxsetters" + +import bytes "bytes" +import errors "errors" +import io "io" +import path "path" +import url "net/url" + +// Version compatibility assertion. +// If the constant is not defined in the package, that likely means +// the package needs to be updated to work with this generated code. +// See https://twitchtv.github.io/twirp/docs/version_matrix.html +const _ = twirp.TwirpPackageMinVersion_8_1_0 + +// ================= +// Scanner Interface +// ================= + +type Scanner interface { + Scan(context.Context, *ScanRequest) (*ScanResponse, error) +} + +// ======================= +// Scanner Protobuf Client +// ======================= + +type scannerProtobufClient struct { + client HTTPClient + urls [1]string + interceptor twirp.Interceptor + opts twirp.ClientOptions +} + +// NewScannerProtobufClient creates a Protobuf client that implements the Scanner interface. +// It communicates using Protobuf and can be configured with a custom HTTPClient. +func NewScannerProtobufClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Scanner { + if c, ok := client.(*http.Client); ok { + client = withoutRedirects(c) + } + + clientOpts := twirp.ClientOptions{} + for _, o := range opts { + o(&clientOpts) + } + + // Using ReadOpt allows backwards and forwads compatibility with new options in the future + literalURLs := false + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) + var pathPrefix string + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { + pathPrefix = "/twirp" // default prefix + } + + // Build method URLs: []/./ + serviceURL := sanitizeBaseURL(baseURL) + serviceURL += baseServicePath(pathPrefix, "trivy.scanner.v1", "Scanner") + urls := [1]string{ + serviceURL + "Scan", + } + + return &scannerProtobufClient{ + client: client, + urls: urls, + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), + opts: clientOpts, + } +} + +func (c *scannerProtobufClient) Scan(ctx context.Context, in *ScanRequest) (*ScanResponse, error) { + ctx = ctxsetters.WithPackageName(ctx, "trivy.scanner.v1") + ctx = ctxsetters.WithServiceName(ctx, "Scanner") + ctx = ctxsetters.WithMethodName(ctx, "Scan") + caller := c.callScan + if c.interceptor != nil { + caller = func(ctx context.Context, req *ScanRequest) (*ScanResponse, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*ScanRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*ScanRequest) when calling interceptor") + } + return c.callScan(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*ScanResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*ScanResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *scannerProtobufClient) callScan(ctx context.Context, in *ScanRequest) (*ScanResponse, error) { + out := new(ScanResponse) + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +// =================== +// Scanner JSON Client +// =================== + +type scannerJSONClient struct { + client HTTPClient + urls [1]string + interceptor twirp.Interceptor + opts twirp.ClientOptions +} + +// NewScannerJSONClient creates a JSON client that implements the Scanner interface. +// It communicates using JSON and can be configured with a custom HTTPClient. +func NewScannerJSONClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Scanner { + if c, ok := client.(*http.Client); ok { + client = withoutRedirects(c) + } + + clientOpts := twirp.ClientOptions{} + for _, o := range opts { + o(&clientOpts) + } + + // Using ReadOpt allows backwards and forwads compatibility with new options in the future + literalURLs := false + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) + var pathPrefix string + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { + pathPrefix = "/twirp" // default prefix + } + + // Build method URLs: []/./ + serviceURL := sanitizeBaseURL(baseURL) + serviceURL += baseServicePath(pathPrefix, "trivy.scanner.v1", "Scanner") + urls := [1]string{ + serviceURL + "Scan", + } + + return &scannerJSONClient{ + client: client, + urls: urls, + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), + opts: clientOpts, + } +} + +func (c *scannerJSONClient) Scan(ctx context.Context, in *ScanRequest) (*ScanResponse, error) { + ctx = ctxsetters.WithPackageName(ctx, "trivy.scanner.v1") + ctx = ctxsetters.WithServiceName(ctx, "Scanner") + ctx = ctxsetters.WithMethodName(ctx, "Scan") + caller := c.callScan + if c.interceptor != nil { + caller = func(ctx context.Context, req *ScanRequest) (*ScanResponse, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*ScanRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*ScanRequest) when calling interceptor") + } + return c.callScan(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*ScanResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*ScanResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *scannerJSONClient) callScan(ctx context.Context, in *ScanRequest) (*ScanResponse, error) { + out := new(ScanResponse) + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +// ====================== +// Scanner Server Handler +// ====================== + +type scannerServer struct { + Scanner + interceptor twirp.Interceptor + hooks *twirp.ServerHooks + pathPrefix string // prefix for routing + jsonSkipDefaults bool // do not include unpopulated fields (default values) in the response + jsonCamelCase bool // JSON fields are serialized as lowerCamelCase rather than keeping the original proto names +} + +// NewScannerServer builds a TwirpServer that can be used as an http.Handler to handle +// HTTP requests that are routed to the right method in the provided svc implementation. +// The opts are twirp.ServerOption modifiers, for example twirp.WithServerHooks(hooks). +func NewScannerServer(svc Scanner, opts ...interface{}) TwirpServer { + serverOpts := newServerOpts(opts) + + // Using ReadOpt allows backwards and forwads compatibility with new options in the future + jsonSkipDefaults := false + _ = serverOpts.ReadOpt("jsonSkipDefaults", &jsonSkipDefaults) + jsonCamelCase := false + _ = serverOpts.ReadOpt("jsonCamelCase", &jsonCamelCase) + var pathPrefix string + if ok := serverOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { + pathPrefix = "/twirp" // default prefix + } + + return &scannerServer{ + Scanner: svc, + hooks: serverOpts.Hooks, + interceptor: twirp.ChainInterceptors(serverOpts.Interceptors...), + pathPrefix: pathPrefix, + jsonSkipDefaults: jsonSkipDefaults, + jsonCamelCase: jsonCamelCase, + } +} + +// writeError writes an HTTP response with a valid Twirp error format, and triggers hooks. +// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) +func (s *scannerServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { + writeError(ctx, resp, err, s.hooks) +} + +// handleRequestBodyError is used to handle error when the twirp server cannot read request +func (s *scannerServer) handleRequestBodyError(ctx context.Context, resp http.ResponseWriter, msg string, err error) { + if context.Canceled == ctx.Err() { + s.writeError(ctx, resp, twirp.NewError(twirp.Canceled, "failed to read request: context canceled")) + return + } + if context.DeadlineExceeded == ctx.Err() { + s.writeError(ctx, resp, twirp.NewError(twirp.DeadlineExceeded, "failed to read request: deadline exceeded")) + return + } + s.writeError(ctx, resp, twirp.WrapError(malformedRequestError(msg), err)) +} + +// ScannerPathPrefix is a convenience constant that may identify URL paths. +// Should be used with caution, it only matches routes generated by Twirp Go clients, +// with the default "/twirp" prefix and default CamelCase service and method names. +// More info: https://twitchtv.github.io/twirp/docs/routing.html +const ScannerPathPrefix = "/twirp/trivy.scanner.v1.Scanner/" + +func (s *scannerServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + ctx := req.Context() + ctx = ctxsetters.WithPackageName(ctx, "trivy.scanner.v1") + ctx = ctxsetters.WithServiceName(ctx, "Scanner") + ctx = ctxsetters.WithResponseWriter(ctx, resp) + + var err error + ctx, err = callRequestReceived(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + if req.Method != "POST" { + msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } + + // Verify path format: []/./ + prefix, pkgService, method := parseTwirpPath(req.URL.Path) + if pkgService != "trivy.scanner.v1.Scanner" { + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } + if prefix != s.pathPrefix { + msg := fmt.Sprintf("invalid path prefix %q, expected %q, on path %q", prefix, s.pathPrefix, req.URL.Path) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } + + switch method { + case "Scan": + s.serveScan(ctx, resp, req) + return + default: + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } +} + +func (s *scannerServer) serveScan(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + header := req.Header.Get("Content-Type") + i := strings.Index(header, ";") + if i == -1 { + i = len(header) + } + switch strings.TrimSpace(strings.ToLower(header[:i])) { + case "application/json": + s.serveScanJSON(ctx, resp, req) + case "application/protobuf": + s.serveScanProtobuf(ctx, resp, req) + default: + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) + twerr := badRouteError(msg, req.Method, req.URL.Path) + s.writeError(ctx, resp, twerr) + } +} + +func (s *scannerServer) serveScanJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "Scan") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + d := json.NewDecoder(req.Body) + rawReqBody := json.RawMessage{} + if err := d.Decode(&rawReqBody); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + reqContent := new(ScanRequest) + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + + handler := s.Scanner.Scan + if s.interceptor != nil { + handler = func(ctx context.Context, req *ScanRequest) (*ScanResponse, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*ScanRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*ScanRequest) when calling interceptor") + } + return s.Scanner.Scan(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*ScanResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*ScanResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *ScanResponse + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *ScanResponse and nil error while calling Scan. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} + respBytes, err := marshaler.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/json") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *scannerServer) serveScanProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "Scan") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + buf, err := ioutil.ReadAll(req.Body) + if err != nil { + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) + return + } + reqContent := new(ScanRequest) + if err = proto.Unmarshal(buf, reqContent); err != nil { + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) + return + } + + handler := s.Scanner.Scan + if s.interceptor != nil { + handler = func(ctx context.Context, req *ScanRequest) (*ScanResponse, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*ScanRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*ScanRequest) when calling interceptor") + } + return s.Scanner.Scan(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*ScanResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*ScanResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *ScanResponse + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *ScanResponse and nil error while calling Scan. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + respBytes, err := proto.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/protobuf") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *scannerServer) ServiceDescriptor() ([]byte, int) { + return twirpFileDescriptor0, 0 +} + +func (s *scannerServer) ProtocGenTwirpVersion() string { + return "v8.1.0" +} + +// PathPrefix returns the base service path, in the form: "//./" +// that is everything in a Twirp route except for the . This can be used for routing, +// for example to identify the requests that are targeted to this service in a mux. +func (s *scannerServer) PathPrefix() string { + return baseServicePath(s.pathPrefix, "trivy.scanner.v1", "Scanner") +} + +// ===== +// Utils +// ===== + +// HTTPClient is the interface used by generated clients to send HTTP requests. +// It is fulfilled by *(net/http).Client, which is sufficient for most users. +// Users can provide their own implementation for special retry policies. +// +// HTTPClient implementations should not follow redirects. Redirects are +// automatically disabled if *(net/http).Client is passed to client +// constructors. See the withoutRedirects function in this file for more +// details. +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// TwirpServer is the interface generated server structs will support: they're +// HTTP handlers with additional methods for accessing metadata about the +// service. Those accessors are a low-level API for building reflection tools. +// Most people can think of TwirpServers as just http.Handlers. +type TwirpServer interface { + http.Handler + + // ServiceDescriptor returns gzipped bytes describing the .proto file that + // this service was generated from. Once unzipped, the bytes can be + // unmarshalled as a + // google.golang.org/protobuf/types/descriptorpb.FileDescriptorProto. + // + // The returned integer is the index of this particular service within that + // FileDescriptorProto's 'Service' slice of ServiceDescriptorProtos. This is a + // low-level field, expected to be used for reflection. + ServiceDescriptor() ([]byte, int) + + // ProtocGenTwirpVersion is the semantic version string of the version of + // twirp used to generate this file. + ProtocGenTwirpVersion() string + + // PathPrefix returns the HTTP URL path prefix for all methods handled by this + // service. This can be used with an HTTP mux to route Twirp requests. + // The path prefix is in the form: "//./" + // that is, everything in a Twirp route except for the at the end. + PathPrefix() string +} + +func newServerOpts(opts []interface{}) *twirp.ServerOptions { + serverOpts := &twirp.ServerOptions{} + for _, opt := range opts { + switch o := opt.(type) { + case twirp.ServerOption: + o(serverOpts) + case *twirp.ServerHooks: // backwards compatibility, allow to specify hooks as an argument + twirp.WithServerHooks(o)(serverOpts) + case nil: // backwards compatibility, allow nil value for the argument + continue + default: + panic(fmt.Sprintf("Invalid option type %T, please use a twirp.ServerOption", o)) + } + } + return serverOpts +} + +// WriteError writes an HTTP response with a valid Twirp error format (code, msg, meta). +// Useful outside of the Twirp server (e.g. http middleware), but does not trigger hooks. +// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) +func WriteError(resp http.ResponseWriter, err error) { + writeError(context.Background(), resp, err, nil) +} + +// writeError writes Twirp errors in the response and triggers hooks. +func writeError(ctx context.Context, resp http.ResponseWriter, err error, hooks *twirp.ServerHooks) { + // Convert to a twirp.Error. Non-twirp errors are converted to internal errors. + var twerr twirp.Error + if !errors.As(err, &twerr) { + twerr = twirp.InternalErrorWith(err) + } + + statusCode := twirp.ServerHTTPStatusFromErrorCode(twerr.Code()) + ctx = ctxsetters.WithStatusCode(ctx, statusCode) + ctx = callError(ctx, hooks, twerr) + + respBody := marshalErrorToJSON(twerr) + + resp.Header().Set("Content-Type", "application/json") // Error responses are always JSON + resp.Header().Set("Content-Length", strconv.Itoa(len(respBody))) + resp.WriteHeader(statusCode) // set HTTP status code and send response + + _, writeErr := resp.Write(respBody) + if writeErr != nil { + // We have three options here. We could log the error, call the Error + // hook, or just silently ignore the error. + // + // Logging is unacceptable because we don't have a user-controlled + // logger; writing out to stderr without permission is too rude. + // + // Calling the Error hook would confuse users: it would mean the Error + // hook got called twice for one request, which is likely to lead to + // duplicated log messages and metrics, no matter how well we document + // the behavior. + // + // Silently ignoring the error is our least-bad option. It's highly + // likely that the connection is broken and the original 'err' says + // so anyway. + _ = writeErr + } + + callResponseSent(ctx, hooks) +} + +// sanitizeBaseURL parses the the baseURL, and adds the "http" scheme if needed. +// If the URL is unparsable, the baseURL is returned unchaged. +func sanitizeBaseURL(baseURL string) string { + u, err := url.Parse(baseURL) + if err != nil { + return baseURL // invalid URL will fail later when making requests + } + if u.Scheme == "" { + u.Scheme = "http" + } + return u.String() +} + +// baseServicePath composes the path prefix for the service (without ). +// e.g.: baseServicePath("/twirp", "my.pkg", "MyService") +// +// returns => "/twirp/my.pkg.MyService/" +// +// e.g.: baseServicePath("", "", "MyService") +// +// returns => "/MyService/" +func baseServicePath(prefix, pkg, service string) string { + fullServiceName := service + if pkg != "" { + fullServiceName = pkg + "." + service + } + return path.Join("/", prefix, fullServiceName) + "/" +} + +// parseTwirpPath extracts path components form a valid Twirp route. +// Expected format: "[]/./" +// e.g.: prefix, pkgService, method := parseTwirpPath("/twirp/pkg.Svc/MakeHat") +func parseTwirpPath(path string) (string, string, string) { + parts := strings.Split(path, "/") + if len(parts) < 2 { + return "", "", "" + } + method := parts[len(parts)-1] + pkgService := parts[len(parts)-2] + prefix := strings.Join(parts[0:len(parts)-2], "/") + return prefix, pkgService, method +} + +// getCustomHTTPReqHeaders retrieves a copy of any headers that are set in +// a context through the twirp.WithHTTPRequestHeaders function. +// If there are no headers set, or if they have the wrong type, nil is returned. +func getCustomHTTPReqHeaders(ctx context.Context) http.Header { + header, ok := twirp.HTTPRequestHeaders(ctx) + if !ok || header == nil { + return nil + } + copied := make(http.Header) + for k, vv := range header { + if vv == nil { + copied[k] = nil + continue + } + copied[k] = make([]string, len(vv)) + copy(copied[k], vv) + } + return copied +} + +// newRequest makes an http.Request from a client, adding common headers. +func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) { + req, err := http.NewRequest("POST", url, reqBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if customHeader := getCustomHTTPReqHeaders(ctx); customHeader != nil { + req.Header = customHeader + } + req.Header.Set("Accept", contentType) + req.Header.Set("Content-Type", contentType) + req.Header.Set("Twirp-Version", "v8.1.0") + return req, nil +} + +// JSON serialization for errors +type twerrJSON struct { + Code string `json:"code"` + Msg string `json:"msg"` + Meta map[string]string `json:"meta,omitempty"` +} + +// marshalErrorToJSON returns JSON from a twirp.Error, that can be used as HTTP error response body. +// If serialization fails, it will use a descriptive Internal error instead. +func marshalErrorToJSON(twerr twirp.Error) []byte { + // make sure that msg is not too large + msg := twerr.Msg() + if len(msg) > 1e6 { + msg = msg[:1e6] + } + + tj := twerrJSON{ + Code: string(twerr.Code()), + Msg: msg, + Meta: twerr.MetaMap(), + } + + buf, err := json.Marshal(&tj) + if err != nil { + buf = []byte("{\"type\": \"" + twirp.Internal + "\", \"msg\": \"There was an error but it could not be serialized into JSON\"}") // fallback + } + + return buf +} + +// errorFromResponse builds a twirp.Error from a non-200 HTTP response. +// If the response has a valid serialized Twirp error, then it's returned. +// If not, the response status code is used to generate a similar twirp +// error. See twirpErrorFromIntermediary for more info on intermediary errors. +func errorFromResponse(resp *http.Response) twirp.Error { + statusCode := resp.StatusCode + statusText := http.StatusText(statusCode) + + if isHTTPRedirect(statusCode) { + // Unexpected redirect: it must be an error from an intermediary. + // Twirp clients don't follow redirects automatically, Twirp only handles + // POST requests, redirects should only happen on GET and HEAD requests. + location := resp.Header.Get("Location") + msg := fmt.Sprintf("unexpected HTTP status code %d %q received, Location=%q", statusCode, statusText, location) + return twirpErrorFromIntermediary(statusCode, msg, location) + } + + respBodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return wrapInternal(err, "failed to read server error response body") + } + + var tj twerrJSON + dec := json.NewDecoder(bytes.NewReader(respBodyBytes)) + dec.DisallowUnknownFields() + if err := dec.Decode(&tj); err != nil || tj.Code == "" { + // Invalid JSON response; it must be an error from an intermediary. + msg := fmt.Sprintf("Error from intermediary with HTTP status code %d %q", statusCode, statusText) + return twirpErrorFromIntermediary(statusCode, msg, string(respBodyBytes)) + } + + errorCode := twirp.ErrorCode(tj.Code) + if !twirp.IsValidErrorCode(errorCode) { + msg := "invalid type returned from server error response: " + tj.Code + return twirp.InternalError(msg).WithMeta("body", string(respBodyBytes)) + } + + twerr := twirp.NewError(errorCode, tj.Msg) + for k, v := range tj.Meta { + twerr = twerr.WithMeta(k, v) + } + return twerr +} + +// twirpErrorFromIntermediary maps HTTP errors from non-twirp sources to twirp errors. +// The mapping is similar to gRPC: https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md. +// Returned twirp Errors have some additional metadata for inspection. +func twirpErrorFromIntermediary(status int, msg string, bodyOrLocation string) twirp.Error { + var code twirp.ErrorCode + if isHTTPRedirect(status) { // 3xx + code = twirp.Internal + } else { + switch status { + case 400: // Bad Request + code = twirp.Internal + case 401: // Unauthorized + code = twirp.Unauthenticated + case 403: // Forbidden + code = twirp.PermissionDenied + case 404: // Not Found + code = twirp.BadRoute + case 429: // Too Many Requests + code = twirp.ResourceExhausted + case 502, 503, 504: // Bad Gateway, Service Unavailable, Gateway Timeout + code = twirp.Unavailable + default: // All other codes + code = twirp.Unknown + } + } + + twerr := twirp.NewError(code, msg) + twerr = twerr.WithMeta("http_error_from_intermediary", "true") // to easily know if this error was from intermediary + twerr = twerr.WithMeta("status_code", strconv.Itoa(status)) + if isHTTPRedirect(status) { + twerr = twerr.WithMeta("location", bodyOrLocation) + } else { + twerr = twerr.WithMeta("body", bodyOrLocation) + } + return twerr +} + +func isHTTPRedirect(status int) bool { + return status >= 300 && status <= 399 +} + +// wrapInternal wraps an error with a prefix as an Internal error. +// The original error cause is accessible by github.com/pkg/errors.Cause. +func wrapInternal(err error, prefix string) twirp.Error { + return twirp.InternalErrorWith(&wrappedError{prefix: prefix, cause: err}) +} + +type wrappedError struct { + prefix string + cause error +} + +func (e *wrappedError) Error() string { return e.prefix + ": " + e.cause.Error() } +func (e *wrappedError) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As +func (e *wrappedError) Cause() error { return e.cause } // for github.com/pkg/errors + +// ensurePanicResponses makes sure that rpc methods causing a panic still result in a Twirp Internal +// error response (status 500), and error hooks are properly called with the panic wrapped as an error. +// The panic is re-raised so it can be handled normally with middleware. +func ensurePanicResponses(ctx context.Context, resp http.ResponseWriter, hooks *twirp.ServerHooks) { + if r := recover(); r != nil { + // Wrap the panic as an error so it can be passed to error hooks. + // The original error is accessible from error hooks, but not visible in the response. + err := errFromPanic(r) + twerr := &internalWithCause{msg: "Internal service panic", cause: err} + // Actually write the error + writeError(ctx, resp, twerr, hooks) + // If possible, flush the error to the wire. + f, ok := resp.(http.Flusher) + if ok { + f.Flush() + } + + panic(r) + } +} + +// errFromPanic returns the typed error if the recovered panic is an error, otherwise formats as error. +func errFromPanic(p interface{}) error { + if err, ok := p.(error); ok { + return err + } + return fmt.Errorf("panic: %v", p) +} + +// internalWithCause is a Twirp Internal error wrapping an original error cause, +// but the original error message is not exposed on Msg(). The original error +// can be checked with go1.13+ errors.Is/As, and also by (github.com/pkg/errors).Unwrap +type internalWithCause struct { + msg string + cause error +} + +func (e *internalWithCause) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As +func (e *internalWithCause) Cause() error { return e.cause } // for github.com/pkg/errors +func (e *internalWithCause) Error() string { return e.msg + ": " + e.cause.Error() } +func (e *internalWithCause) Code() twirp.ErrorCode { return twirp.Internal } +func (e *internalWithCause) Msg() string { return e.msg } +func (e *internalWithCause) Meta(key string) string { return "" } +func (e *internalWithCause) MetaMap() map[string]string { return nil } +func (e *internalWithCause) WithMeta(key string, val string) twirp.Error { return e } + +// malformedRequestError is used when the twirp server cannot unmarshal a request +func malformedRequestError(msg string) twirp.Error { + return twirp.NewError(twirp.Malformed, msg) +} + +// badRouteError is used when the twirp server cannot route a request +func badRouteError(msg string, method, url string) twirp.Error { + err := twirp.NewError(twirp.BadRoute, msg) + err = err.WithMeta("twirp_invalid_route", method+" "+url) + return err +} + +// withoutRedirects makes sure that the POST request can not be redirected. +// The standard library will, by default, redirect requests (including POSTs) if it gets a 302 or +// 303 response, and also 301s in go1.8. It redirects by making a second request, changing the +// method to GET and removing the body. This produces very confusing error messages, so instead we +// set a redirect policy that always errors. This stops Go from executing the redirect. +// +// We have to be a little careful in case the user-provided http.Client has its own CheckRedirect +// policy - if so, we'll run through that policy first. +// +// Because this requires modifying the http.Client, we make a new copy of the client and return it. +func withoutRedirects(in *http.Client) *http.Client { + copy := *in + copy.CheckRedirect = func(req *http.Request, via []*http.Request) error { + if in.CheckRedirect != nil { + // Run the input's redirect if it exists, in case it has side effects, but ignore any error it + // returns, since we want to use ErrUseLastResponse. + err := in.CheckRedirect(req, via) + _ = err // Silly, but this makes sure generated code passes errcheck -blank, which some people use. + } + return http.ErrUseLastResponse + } + return © +} + +// doProtobufRequest makes a Protobuf request to the remote Twirp service. +func doProtobufRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { + reqBodyBytes, err := proto.Marshal(in) + if err != nil { + return ctx, wrapInternal(err, "failed to marshal proto request") + } + reqBody := bytes.NewBuffer(reqBodyBytes) + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + req, err := newRequest(ctx, url, reqBody, "application/protobuf") + if err != nil { + return ctx, wrapInternal(err, "could not build request") + } + ctx, err = callClientRequestPrepared(ctx, hooks, req) + if err != nil { + return ctx, err + } + + req = req.WithContext(ctx) + resp, err := client.Do(req) + if err != nil { + return ctx, wrapInternal(err, "failed to do request") + } + + defer func() { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = wrapInternal(cerr, "failed to close response body") + } + }() + + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + if resp.StatusCode != 200 { + return ctx, errorFromResponse(resp) + } + + respBodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return ctx, wrapInternal(err, "failed to read response body") + } + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + if err = proto.Unmarshal(respBodyBytes, out); err != nil { + return ctx, wrapInternal(err, "failed to unmarshal proto response") + } + return ctx, nil +} + +// doJSONRequest makes a JSON request to the remote Twirp service. +func doJSONRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { + marshaler := &protojson.MarshalOptions{UseProtoNames: true} + reqBytes, err := marshaler.Marshal(in) + if err != nil { + return ctx, wrapInternal(err, "failed to marshal json request") + } + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + req, err := newRequest(ctx, url, bytes.NewReader(reqBytes), "application/json") + if err != nil { + return ctx, wrapInternal(err, "could not build request") + } + ctx, err = callClientRequestPrepared(ctx, hooks, req) + if err != nil { + return ctx, err + } + + req = req.WithContext(ctx) + resp, err := client.Do(req) + if err != nil { + return ctx, wrapInternal(err, "failed to do request") + } + + defer func() { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = wrapInternal(cerr, "failed to close response body") + } + }() + + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + if resp.StatusCode != 200 { + return ctx, errorFromResponse(resp) + } + + d := json.NewDecoder(resp.Body) + rawRespBody := json.RawMessage{} + if err := d.Decode(&rawRespBody); err != nil { + return ctx, wrapInternal(err, "failed to unmarshal json response") + } + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawRespBody, out); err != nil { + return ctx, wrapInternal(err, "failed to unmarshal json response") + } + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + return ctx, nil +} + +// Call twirp.ServerHooks.RequestReceived if the hook is available +func callRequestReceived(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { + if h == nil || h.RequestReceived == nil { + return ctx, nil + } + return h.RequestReceived(ctx) +} + +// Call twirp.ServerHooks.RequestRouted if the hook is available +func callRequestRouted(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { + if h == nil || h.RequestRouted == nil { + return ctx, nil + } + return h.RequestRouted(ctx) +} + +// Call twirp.ServerHooks.ResponsePrepared if the hook is available +func callResponsePrepared(ctx context.Context, h *twirp.ServerHooks) context.Context { + if h == nil || h.ResponsePrepared == nil { + return ctx + } + return h.ResponsePrepared(ctx) +} + +// Call twirp.ServerHooks.ResponseSent if the hook is available +func callResponseSent(ctx context.Context, h *twirp.ServerHooks) { + if h == nil || h.ResponseSent == nil { + return + } + h.ResponseSent(ctx) +} + +// Call twirp.ServerHooks.Error if the hook is available +func callError(ctx context.Context, h *twirp.ServerHooks, err twirp.Error) context.Context { + if h == nil || h.Error == nil { + return ctx + } + return h.Error(ctx, err) +} + +func callClientResponseReceived(ctx context.Context, h *twirp.ClientHooks) { + if h == nil || h.ResponseReceived == nil { + return + } + h.ResponseReceived(ctx) +} + +func callClientRequestPrepared(ctx context.Context, h *twirp.ClientHooks, req *http.Request) (context.Context, error) { + if h == nil || h.RequestPrepared == nil { + return ctx, nil + } + return h.RequestPrepared(ctx, req) +} + +func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) { + if h == nil || h.Error == nil { + return + } + h.Error(ctx, err) +} + +var twirpFileDescriptor0 = []byte{ + // 665 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xdd, 0x6e, 0xda, 0x4a, + 0x10, 0x16, 0x10, 0xc0, 0x0c, 0x47, 0x27, 0x64, 0x75, 0x4e, 0xe4, 0x90, 0xa6, 0x45, 0x5c, 0x54, + 0xa8, 0x17, 0xd0, 0x90, 0x56, 0xfd, 0xbb, 0x6a, 0x93, 0xb4, 0x8a, 0xd4, 0x2a, 0xd1, 0x12, 0xf5, + 0xa2, 0x37, 0xee, 0xb2, 0x9e, 0xd0, 0x55, 0x8c, 0xed, 0xec, 0xac, 0x91, 0x78, 0x95, 0xbe, 0x57, + 0x9f, 0xa0, 0x2f, 0x52, 0x79, 0xbd, 0x46, 0x81, 0x24, 0xbd, 0xb2, 0x67, 0xe6, 0x9b, 0x6f, 0xbe, + 0xdd, 0xfd, 0x34, 0xb0, 0xa7, 0x53, 0x39, 0x22, 0x29, 0xe2, 0x18, 0xf5, 0x88, 0x50, 0x2f, 0x94, + 0xc4, 0x61, 0xaa, 0x13, 0x93, 0xb0, 0x8e, 0xd1, 0x6a, 0xb1, 0x1c, 0xba, 0xe2, 0x70, 0x71, 0xd8, + 0xf5, 0x73, 0xb0, 0x4c, 0xe6, 0xf3, 0x24, 0x5e, 0xc7, 0xf6, 0x7f, 0x56, 0xa0, 0x3d, 0x91, 0x22, + 0xe6, 0x78, 0x93, 0x21, 0x19, 0xb6, 0x0b, 0x0d, 0x23, 0xf4, 0x0c, 0x8d, 0x5f, 0xe9, 0x55, 0x06, + 0x2d, 0xee, 0x22, 0xf6, 0x04, 0xda, 0x42, 0x1b, 0x75, 0x25, 0xa4, 0x09, 0x54, 0xe8, 0x57, 0x6d, + 0x11, 0xca, 0xd4, 0x59, 0xc8, 0xf6, 0xc0, 0x9b, 0x46, 0xc9, 0x34, 0x50, 0x21, 0xf9, 0xb5, 0x5e, + 0x6d, 0xd0, 0xe2, 0xcd, 0x3c, 0x3e, 0x0b, 0x89, 0xbd, 0x82, 0x66, 0x92, 0x1a, 0x95, 0xc4, 0xe4, + 0x6f, 0xf5, 0x2a, 0x83, 0xf6, 0xf8, 0x60, 0xb8, 0xa9, 0x70, 0x98, 0x6b, 0x38, 0x2f, 0x40, 0xbc, + 0x44, 0xf7, 0x7b, 0xe0, 0x7d, 0x56, 0x12, 0x63, 0x42, 0x62, 0xff, 0x41, 0x3d, 0x16, 0x73, 0x24, + 0xbf, 0x62, 0xc9, 0x8b, 0xa0, 0xff, 0xbb, 0x5a, 0xc8, 0x77, 0xad, 0x6c, 0x1f, 0x5a, 0x8b, 0x2c, + 0x8a, 0x03, 0xb3, 0x4c, 0xd1, 0x21, 0xbd, 0x3c, 0x71, 0xb9, 0x4c, 0x91, 0x75, 0xc1, 0x73, 0x13, + 0xc9, 0xaf, 0x16, 0xb5, 0x32, 0x66, 0xcf, 0x60, 0x27, 0x52, 0x64, 0x02, 0x11, 0x45, 0x41, 0x2a, + 0xe4, 0xb5, 0x98, 0x61, 0x7e, 0x8e, 0xca, 0xc0, 0xe3, 0xdb, 0x79, 0xe1, 0x7d, 0x14, 0x5d, 0xb8, + 0x34, 0x93, 0xc0, 0xa2, 0x42, 0x56, 0x20, 0x85, 0xc1, 0x59, 0xa2, 0x15, 0xe6, 0x47, 0xab, 0x0d, + 0xda, 0xe3, 0x17, 0x7f, 0x3d, 0xda, 0xd0, 0x1d, 0xe7, 0x78, 0xd5, 0x76, 0x1a, 0x1b, 0xbd, 0xe4, + 0x3b, 0xd1, 0x66, 0x9e, 0x0d, 0xa0, 0xa3, 0x62, 0x19, 0x65, 0x21, 0x06, 0x21, 0x2e, 0x82, 0x10, + 0x53, 0xf2, 0xeb, 0x56, 0xcf, 0xbf, 0x2e, 0x7f, 0x82, 0x8b, 0x13, 0x4c, 0xa9, 0xfb, 0x1d, 0x76, + 0xef, 0xa7, 0x65, 0x1d, 0xa8, 0x5d, 0xe3, 0xd2, 0xbd, 0x64, 0xfe, 0xcb, 0x9e, 0x43, 0x7d, 0x21, + 0xa2, 0x0c, 0xed, 0x03, 0xb6, 0xc7, 0xdd, 0xbb, 0x6a, 0xcb, 0x0b, 0xe7, 0x05, 0xf0, 0x6d, 0xf5, + 0x75, 0xa5, 0x1f, 0xc2, 0x3f, 0x85, 0x47, 0x28, 0x4d, 0x62, 0x42, 0xd6, 0x83, 0x6a, 0x42, 0x96, + 0xb6, 0x3d, 0xee, 0x38, 0x8a, 0xc2, 0x5d, 0xc3, 0xf3, 0x09, 0xaf, 0x26, 0xc4, 0xc6, 0xd0, 0xd4, + 0x48, 0x59, 0x64, 0x0a, 0x33, 0xb4, 0xc7, 0xfe, 0xdd, 0x49, 0xdc, 0x02, 0x78, 0x09, 0xec, 0xff, + 0xaa, 0x41, 0xa3, 0xc8, 0x3d, 0xe8, 0xc2, 0x53, 0xd8, 0xce, 0x5f, 0x13, 0xb5, 0x98, 0xaa, 0x48, + 0x99, 0xfc, 0xda, 0xab, 0x96, 0x7e, 0x7f, 0x5d, 0xc5, 0xd7, 0x5b, 0xa0, 0x25, 0xdf, 0xec, 0x61, + 0x97, 0xb0, 0x33, 0x57, 0x24, 0x93, 0xf8, 0x4a, 0xcd, 0x32, 0x2d, 0x4a, 0x6b, 0xe6, 0x44, 0x4f, + 0xd7, 0x89, 0x4e, 0xd0, 0xa0, 0x34, 0x18, 0x7e, 0xd9, 0x80, 0xf3, 0xbb, 0x04, 0xb9, 0x43, 0x65, + 0x24, 0x88, 0xfc, 0x86, 0xd5, 0x5c, 0x04, 0x8c, 0xc1, 0x96, 0x35, 0x63, 0xcd, 0x26, 0xed, 0x3f, + 0x3b, 0x04, 0x6f, 0xe5, 0xb1, 0xba, 0x1d, 0xfb, 0xff, 0xfa, 0x58, 0x67, 0x35, 0xbe, 0x82, 0xb1, + 0x4f, 0xd0, 0x91, 0x19, 0x99, 0x64, 0x1e, 0x68, 0xa4, 0x24, 0xd3, 0x12, 0xc9, 0x6f, 0xda, 0xd6, + 0x47, 0xeb, 0xad, 0xc7, 0x16, 0xc5, 0x1d, 0x88, 0x6f, 0xcb, 0xb5, 0x98, 0xd8, 0x4b, 0x68, 0x12, + 0x4a, 0x8d, 0x86, 0x7c, 0xef, 0xbe, 0xab, 0x9b, 0xd8, 0xe2, 0x47, 0x15, 0x87, 0x2a, 0x9e, 0xf1, + 0x12, 0xcb, 0xde, 0x80, 0xe7, 0x3c, 0x4a, 0x7e, 0xcb, 0xf6, 0x1d, 0xdc, 0x7f, 0x53, 0xce, 0x3f, + 0x7c, 0x05, 0x1f, 0x5f, 0x40, 0x73, 0x52, 0xbc, 0x3a, 0x3b, 0x85, 0xad, 0xfc, 0x97, 0x3d, 0xb0, + 0x00, 0xdc, 0x12, 0xea, 0x3e, 0x7e, 0xa8, 0x5c, 0xf8, 0xef, 0xc3, 0xd1, 0xb7, 0xc3, 0x99, 0x32, + 0x3f, 0xb2, 0x69, 0x3e, 0x7c, 0x24, 0x6e, 0x32, 0x41, 0x28, 0x33, 0xad, 0xcc, 0x72, 0x64, 0x1b, + 0x47, 0xb7, 0x76, 0xe3, 0x3b, 0xf7, 0x9d, 0x36, 0xec, 0xc2, 0x3b, 0xfa, 0x13, 0x00, 0x00, 0xff, + 0xff, 0x67, 0xa9, 0x8c, 0x3c, 0x39, 0x05, 0x00, 0x00, +}